pax_global_header00006660000000000000000000000064141564631400014516gustar00rootroot0000000000000052 comment=a7ddc5b3c2981cf15fc477fbd12397afcc559e4b glewlwyd-2.6.1/000077500000000000000000000000001415646314000133625ustar00rootroot00000000000000glewlwyd-2.6.1/.dockerignore000066400000000000000000000004141415646314000160350ustar00rootroot00000000000000*.o *.so *.key *.crt *.pfx *.csr *.crl *.pem *.der *.log *.err *.so /*.conf valgrind.txt glewlwyd webapp/test-*.html webapp-src/test-*.html webapp-src/node_modules webapp-src/output webapp-src/yarn.lock webapp-src/package-lock.json webapp-src/config.json .git .github glewlwyd-2.6.1/.github/000077500000000000000000000000001415646314000147225ustar00rootroot00000000000000glewlwyd-2.6.1/.github/ISSUE_TEMPLATE/000077500000000000000000000000001415646314000171055ustar00rootroot00000000000000glewlwyd-2.6.1/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000017741415646314000216100ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: "[Issue]" labels: '' assignees: '' --- --- name: Glewlwyd bug report about: Create a report to help improve Glewlwyd title: "[Issue]" labels: '' assignees: '' --- **Describe the issue** A clear and concise description of what the issue is, including a clear and concise title. **To Reproduce** If possible, post a sample code to reproduce the issue. **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **System (please complete the following information):** - OS/Environment [e.g. Debian Stretch, Ubuntu 19.04] - Browser used [e.g. Mozilla Firefox 69, Chrome 77, lynx 2.9] - Glewlwyd Version [e.g. 2.0.0, 1.4.9] - Source installation [e.g. Distribution package, GitHub package, build from source] - If applicable, what option did you use to build Glewlwyd **Additional context** Add any other context about the problem here. glewlwyd-2.6.1/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000013441415646314000226340ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: "[Feature request]" labels: '' assignees: '' --- --- name: Glewlwyd feature request about: Suggest an idea for this project title: "[Feature request]" labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. It would be nive to have... **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. glewlwyd-2.6.1/.github/workflows/000077500000000000000000000000001415646314000167575ustar00rootroot00000000000000glewlwyd-2.6.1/.github/workflows/ccpp.yml000066400000000000000000000045771415646314000204440ustar00rootroot00000000000000name: C/C++ CI on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: install dependencies run: | sudo apt-get update sudo apt-get install -y libconfig-dev libsystemd-dev libjansson-dev libcurl4-gnutls-dev libldap2-dev libmicrohttpd-dev libsqlite3-dev sqlite3 libpq-dev liboath-dev mariadb-client default-libmysqlclient-dev cmake pkg-config check libsubunit-dev cppcheck gnutls-bin libcbor-dev - name: cppcheck run: cppcheck --force --enable=warning,missingInclude --error-exitcode=1 src/ - name: build run: | mkdir build cd build cmake -DBUILD_GLEWLWYD_TESTING=ON -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib .. make sudo make install - name: build-webapp run: | cd webapp-src npm install make build-webapp make build-polyfill-webapp - name: test run: | sqlite3 /tmp/glewlwyd.db < docs/database/init.sqlite3.sql sqlite3 /tmp/glewlwyd.db < test/glewlwyd-test.sql cd build glewlwyd --config-file=../test/glewlwyd-travis.conf & export G_PID=$! ../test/cert/create-cert.sh || (cat ../test/cert/certtool.log && false) ln -s ../test/cert/ . ln -s ../test/ . make test || (cat Testing/Temporary/LastTest.log && cat /tmp/glewlwyd.log && false) kill $G_PID glewlwyd --config-file=cert/glewlwyd-cert-ci.conf & make glewlwyd_scheme_certificate glewlwyd_oidc_client_certificate sleep 1 export G_PID=$! (./glewlwyd_scheme_certificate && ./glewlwyd_oidc_client_certificate) || (cat /tmp/glewlwyd-https.log && false) kill $G_PID glewlwyd --config-file=test/glewlwyd-profile-delete-disable.conf & sleep 1 export G_PID=$! ./glewlwyd_profile_delete disable || (cat /tmp/glewlwyd-disable.log && false) kill $G_PID glewlwyd --config-file=test/glewlwyd-profile-delete-yes.conf & sleep 1 export G_PID=$! ./glewlwyd_profile_delete delete || (cat /tmp/glewlwyd-delete.log && false) kill $G_PID make glewlwyd_prometheus glewlwyd --config-file=test/glewlwyd-prometheus.conf & sleep 1 export G_PID=$! ./glewlwyd_prometheus || (cat /tmp/glewlwyd-prometheus.log && false) kill $G_PID glewlwyd-2.6.1/.github/workflows/codeql-analysis.yml000066400000000000000000000040311415646314000225700ustar00rootroot00000000000000name: "CodeQL" on: push: branches: [master, ] pull_request: # The branches below must be a subset of the branches above branches: [master] schedule: - cron: '0 18 * * 5' jobs: analyse: name: Analyse runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v2 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. fetch-depth: 2 # If this run was triggered by a pull request event, then checkout # the head of the pull request instead of the merge commit. - run: git checkout HEAD^2 if: ${{ github.event_name == 'pull_request' }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v1 # Override language selection by uncommenting this and choosing your languages # with: # languages: go, javascript, csharp, python, cpp, java # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) #- name: Autobuild # uses: github/codeql-action/autobuild@v1 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language - run: | sudo apt-get update sudo apt-get install -y libconfig-dev libsystemd-dev libjansson-dev libcurl4-gnutls-dev libldap2-dev libmicrohttpd-dev libsqlite3-dev sqlite3 libpq-dev liboath-dev mariadb-client default-libmysqlclient-dev cmake pkg-config gnutls-bin libcbor-dev mkdir build cd build cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib .. make sudo make install - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 glewlwyd-2.6.1/.gitignore000066400000000000000000000002311415646314000153460ustar00rootroot00000000000000*.o *.so *.key *.crt *.pfx *.csr *.crl *.pem *.der *.p12 *.jwks *.log *.err *.so /*.conf valgrind.txt glewlwyd webapp/test-*.html webapp-src/test-*.html glewlwyd-2.6.1/CHANGELOG.md000066400000000000000000000324651415646314000152050ustar00rootroot00000000000000# Glewlwyd Changelog ## 2.6.1 This is a security release, please upgrade your Glewlwyd version. - Fix bug in OTP registration - Fix several UI bugs - Improve user registration UI and OTP scheme registration - Add callback function `plugin_user_revoke` in plugins - Add config file option `add_x_frame_option_header_deny` to allow removing header `X-Frame-Options: deny` - Fix escalation bug ## 2.6.0 The `"Green Zone Release"` - Add option to forbid a scheme to be registered in the profile and/or the reset credentials pages - Add prometheus metrics endpoint - Improve security when updating modules - Allow to force PKCE all the time or when use specified scopes - Implement [Client-Initiated Backchannel Authentication Flow](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html) - Implement [OAuth 2.0 Authorization Server Issuer Identification](https://www.ietf.org/id/draft-ietf-oauth-iss-auth-resp-01.html) - Improve IETF strict option in OIDC plugin by handling signatures and encryption properties - User registration: suggest a new username when a username exists - Allow to remove all sessions and/or revoke all tokens - Implement [OpenID Connect Front-Channel Logout 1.0 - draft 04](https://openid.net/specs/openid-connect-frontchannel-1_0.html) - Implement [OpenID Connect Back-Channel Logout 1.0 - draft 06](https://openid.net/specs/openid-connect-backchannel-1_0.html) - Upgrade DPoP implementation to [draft 4.0](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-dpop-04) ## 2.5.4 - Fix possible buffer overflow in webauthn registration (CVE-2021-40818) - Update dependencies versions ## 2.5.3 - Fix UI bugs - UI: Improve session expiration error - Update SQLite3 password management by increasing PBKDF2 iterations and allowing to set iterations value - IO: Add German translation, thanks to Andy2903 - OIDC: Support more signature and encryption algorithms - Fix CORS bug - Implement [OAuth 2.0 JWT Secured Authorization Request (JAR) Draft 32](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-jwsreq-32) - Allow default properties on client registration - Allow access tokens use in clent registration to be used only once - Improve client and client grant management in the profile page ## 2.5.2 - Fix annoying bug in scheme validation during login - Fix scheme verification bug - Fix docker image builder ## 2.5.1 - Add `identify` action to authenticate via schemes oauth2 or certificate without giving the username - Fix change password issue in the admin interface - Add oidc config `restrict-scope-client-property` to restrict a client to certain scopes if needed - Allow to reconnect on session closed ## 2.5.0 The `"Recontainment Release"` - Fix `aud` property to fit JWT access token spec - Add support for OAuth 2.0 Demonstration of Proof-of-Possession at the Application Layer (DPoP) [Draft 01](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-dpop-01) - Allow multiple passwords for users - Implement [Resource Indicators for OAuth 2.0](https://tools.ietf.org/html/rfc8707) for OIDC plugin - Implement [Content-Encoding](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding) to compress response bodies using `gzip` or `deflate` when relevant - Implement [OAuth 2.0 Rich Authorization Requests Draft 03](https://www.ietf.org/archive/id/draft-ietf-oauth-rar-03.html) - Implement [OAuth 2.0 Pushed Authorization Requests Draft 05](https://tools.ietf.org/html/draft-ietf-oauth-par-05) ## 2.4.0 The `"Second Wave Release"` - Allow user to update its e-mail - Allow user to reset its credentials - Handle callback url for registration and reset credentials - Update certificate scheme management: remove online certiticate generation and add certificate validation via DN - Implement revoke tokens on code replay for oauth2 and oidc plugins - Show `client_id` and `redirect_uri` on grant scope - Remove `parameters` object on `*_load()` functions result - Scheme WebAuthn: disable fmt `none` by default - Allow to add granted scope list in `id_token` and `/userinfo` - Fix last login refresh without authentication bug - Add endpoint `/mod/reload/` to reload modules lists - Add Event log messages - Add parameter Scheme Required to a scope scheme group - Add API key to use administration APIs via scripts without a cookie session ## 2.3.3 - Limit scheme available output This is a security release, please upgrade your Glewlwyd version. To mitigate server configuration leaks, I recommend the following actions: - If you use the TLS Certificate Scheme with [Allow to emit PKCS#12 certificates for the clients](https://github.com/babelouest/glewlwyd/blob/2.3/docs/CERTIFICATE.md#allow-to-emit-pkcs12-certificates-for-the-clients) enabled, please revoke the issuer certificate and use new ones - If you use the Webauthn Scheme, it's reommended to regenerate the [Random seed used to mitigate intrusion](https://github.com/babelouest/glewlwyd/blob/2.3/docs/WEBAUTHN.md#random-seed-used-to-mitigate-intrusion) - If you use the Oauth2 Scheme, please change the [clients secrets](https://github.com/babelouest/glewlwyd/blob/2.3/docs/OAUTH2_SCHEME.md#secret) - If yout use the Email code scheme and use a [SMTP password](https://github.com/babelouest/glewlwyd/blob/2.3/docs/EMAIL.md#smtp-password-if-required), please to change this password ## 2.3.2 - Allow to specify a public JWKS for OIDC plugin - Fix official docker image builder - Fix load module files on filesystems that don't fully support `readdir()`, closes #150 - Fix Small UI bugs - Add manpage - Add documentation on reverse proxy with examples for Apache and Nginx ## 2.3.1 - Upgrade Bootstrap to 4.5 - Replace Font-Awesome 5 with [Fork-Awesome](https://forkaweso.me/) - Fix Mock scheme in profile page ## 2.3.0 The `"Saint-Jean-Baptiste Release"` - Replace libjwt with Rhonabwy - Allow messages encryption (incoming and outcoming) - Allow OIDC plugin to use multiple signing or encryption keys via a JWKS - Add support for CRYPT hash in ldap modules, closes #114 - Add [Session Management](https://openid.net/specs/openid-connect-session-1_0.html) for OIDC plugin - Update access token claims to fit [JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens - draft 05](https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-05) - Add [JWT Response for OAuth Token Introspection](https://tools.ietf.org/html/draft-ietf-oauth-jwt-introspection-response-08) - Adapt client registration `redirect_uri` check to make Glewlwyd OIDC plugin conform to [OAuth 2.0 for Native Apps](https://tools.ietf.org/html/rfc8252) specification - Add [OAuth 2.0 Device Grant](https://tools.ietf.org/html/rfc8628) - Add `id_token` in response type `password` when the scope `openid` is added - Disable response type `password` by default for OIDC plugin config - Scope `openid` is assumed to be always granted to clients for OIDC plugin - Add `one-time-use` refresh token option - Add [OAuth 2.0 Dynamic Client Registration Management Protocol](https://tools.ietf.org/html/rfc7592) for OIDC plugin - Breaking change since 2.2: Client Registration input parameters are now conform to [OAuth 2.0 Dynamic Client Registration Protocol](https://tools.ietf.org/html/rfc7591) - Add [OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens](https://tools.ietf.org/html/rfc8705) - Allow multi-languages e-mails in [e-mail scheme](docs/EMAIL.md) and [registration plugin](docs/REGISTER.md) - Multiple bugfixes in UI and API ## 2.2.0 The `"Containment Release"` - Add [OAuth2/OIDC authentication scheme](https://github.com/babelouest/glewlwyd/blob/master/docs/OAUTH2_SCHEME.md) to authenticate to Glewlwyd via an external provider - Add [Proof Key for Code Exchange by OAuth Public Clients](https://tools.ietf.org/html/rfc7636) for OAuth2 and OIDC plugins - Add [token introspection](https://tools.ietf.org/html/rfc7662) and [token revocation](https://tools.ietf.org/html/rfc7009) for OAuth2 and OIDC plugins - Add [OpenID Connect Dynamic Registration](http://openid.net/specs/openid-connect-registration-1_0.html) for OIDC plugin - Add [Form Post Response Mode](http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html) for OIDC plugin - Allow signed JWT requests using RSA or ECDSA algorithms in `/auth` or `/token` endpoints - Catch close signal in another thread (Closes: #103) - Fix bug to make Glewlwyd compatible with Apache Module [auth_openidc](https://github.com/zmartzone/mod_auth_openidc) ## 2.1.1 - Add claims `exp` and `nbf` in access tokens (see #99) - Fix libjwt version required to help Debian Buster users ## 2.1.0 - Add custom css files so users can safely adapt css to their own identity - Add packed format support in webauthn scheme - improve webauthn scheme - Fix i18n errors and typos - Add Dutch translation in UI - Add HTTP Basic Authentication Scheme - Add `defaultScheme` option in UI config for passwordless authentication - Add `bind_address` option in the config file - Add possibility for users to remove their own account - Add plugin `Register` to allow users to create new accounts - Add HTTP Basic Auth scheme - Multiple bugfixes and UI improvements - Many thanks to all helpers who send feedback and bugfixes! Keep running :-) ## 2.0.0 - Fix UI bugs - Fix Microsoft Edge bug - Add possibility to build UI with Internet Explorer support - Fix GCC9 warnings - Add `autocomplete="off"` and `autofocus` properties in some input - Clean UI code a lot by adding most libraries in `package.json` instead of static files in `webapp-src/js` - Use vanilla `qrcode-generator` instead of `jquery.qrcode` because the last one embedded the first one, so it was overkill ## 2.0.0-rc2 - Allow to emit certificates for certificate scheme - Bug fixes and improvements on certificate scheme - Fix UI bugs - Fix small backend bugs - Add docker image - Add Fail2ban script and config ## 2.0.0-rc1 - Improve documentation - Improve OpenID Connect core plugin - Add OpenID Connect discovery - Add OpenID Connect core requests - Add OpenID Connect address claims - Add option max_age for session passwords - Change OpenID Connect access token payload format to match id_token format - Fix PostgreSQL database - TOTP: forbid to use the same code twice - Allow to use environment variables instead of or in addition to configuration file - Add scheme TLS certificate - Allow to use profile picture for users ## 2.0.0-b3 - Add OpenID Connect core plugin - Fix lots of bugs and memory leaks - Add more tests - Change return type of all modules function `*_init()` to `json_t *` so the front-end will know about the error - Improve documentation - Can use environment variables as config parameters ## 2.0.0-b2 - Fix sample config with correct variable names, fix #57 - Fix webauthn bugs - Improve documentation - Fix build on supported platforms - Fix #59 and add action reset to modules - Make build and tests reproductive using Huddersfield ## 2.0.0-b1 - Massive rework for the better good - Introduction of modules to handle different backend users, clients and authentication scheme - Backends: - Database (user and client) - LDAP (user and client) - HTTP (user only) - Schemes: - password - HOTP/TOTP - Code sent by e-mail - webauthn - Introduction of plugins to handle authentication workflows - Legacy OAuth2 workflow - User Interface revamped ## 1.4.9 - Small bugfixes - Clean some memory leaks ## 1.4.8 - Add Travis CI script - Fix http_auth backend ## 1.4.7 - Adapt Glewlwyd build to the new version of the underlying libraries: Orcania, Yder, Hoel, Ulfius (thanks ythogtha!) - Improve doc about front-end pages, as mentioned in #46, and fix libjwt install doc ## 1.4.6 - Fix client confidential bug in code authorization flow, thanks to Bisco ## 1.4.5 - Add last glewlwyd_resource ## 1.4.4 - Add current token scope list in the API `/api/profile` when authenticated with the OAuth2 token - Fix issue in client_check that made it not check properly if a client is authorized or not ## 1.4.3 - LDAP search error more verbose - Fix LDAP search pagination ## 1.4.2 - Add option `auth_code_match_ip_address` to prevent glewlwyd to check the match of the IP address that requested a code and the IP address that requested the refresh token - Fix bug with confidential clients that were not able to get refresh tokens - Fix bug that made Glewlwyd crash when try to add users and ldap auth was disabled ## 1.4.1 - Update libraries dependency versions ## 1.4.0 - Add LDAP config properties search_scope, scope_property_user_match and scope_property_client_match - Add Debian hardening patch on Makefile - Add journald log mode ## 1.3.3 - Fix client_credentials bug - Move documentation to /docs ## 1.3.2 - Add CMake install script ## 1.3.1 - Make glewlwyd admin application URL more changeable - fix minor bugs and memory leaks ## 1.3.0 - Add http_auth backend #29 ## 1.2.4 - Fix bug when scope doesn't exist and is requested ## 1.2.3 - fix a bug on the case letters for the username in the tokens ## 1.2.2 - Security improvement ## 1.2.1 - Improve install procedure for database init ## 1.2.0 - Add ECDSA signatures and now supports different signature size with the config parameter key_size. If none is specified in the config file, default key_size value is 512 ## 1.1.2 - Fix bug in update last_seen value for a refresh token ## 1.1.1 - Update API prefix to new default value ## 1.1.0 - Limit Ulfius functionalities with the one needed ## 1.0.1 - Improve documentation on Ulfius usage ## 1.0.0 - First stable release glewlwyd-2.6.1/CMakeLists.txt000066400000000000000000001321761415646314000161340ustar00rootroot00000000000000# # Glewlwyd # # CMake file used to build program # # Copyright 2016-2021 Nicolas Mora # # This program is free software; you can redistribute it and/or # modify it under the terms of the MIT License # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # cmake_minimum_required(VERSION 3.5) project(glewlwyd C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror") # library info set(PROGRAM_VERSION_MAJOR "2") set(PROGRAM_VERSION_MINOR "6") set(PROGRAM_VERSION_PATCH "1") set(PROJECT_DESCRIPTION "Single Sign On server, OAuth2, Openid Connect, multiple factor authentication with, HOTP/TOTP, FIDO2, TLS Certificates, etc. extensible via plugins ") set(PROJECT_BUGREPORT_PATH "https://github.com/babelouest/glewlwyd/issues") set(ORCANIA_VERSION_REQUIRED "2.2.1") set(YDER_VERSION_REQUIRED "1.4.14") set(ULFIUS_VERSION_REQUIRED "2.7.7") set(HOEL_VERSION_REQUIRED "1.4.18") set(RHONABWY_VERSION_REQUIRED "1.1.2") set(IDDAWC_VERSION_REQUIRED "1.1.1") set(LIBCBOR_VERSION_REQUIRED "0.5.0") set(JANSSON_VERSION_REQUIRED "2.11") set(USER_MODULES_SRC_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/user/") set(USER_MODULES "") set(USER_MIDDLEWARE_MODULES_SRC_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/user_middleware/") set(USER_MIDDLEWARE_MODULES "") set(CLIENT_MODULES_SRC_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/client/") set(CLIENT_MODULES "") set(SCHEME_MODULES_SRC_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/scheme/") set(SCHEME_MODULES "") set(PLUGIN_MODULES_SRC_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/plugin/") set(PLUGIN_MODULES "") set(RESOURCES_SRC_PATH "${CMAKE_CURRENT_SOURCE_DIR}/docs/resources/ulfius/") include(GNUInstallDirs) include(CheckSymbolExists) # cmake modules set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake-modules) # check if _GNU_SOURCE is available if (NOT _GNU_SOURCE) check_symbol_exists(__GNU_LIBRARY__ "features.h" _GNU_SOURCE) if (NOT _GNU_SOURCE) unset(_GNU_SOURCE CACHE) check_symbol_exists(_GNU_SOURCE "features.h" _GNU_SOURCE) endif () endif () if (_GNU_SOURCE) add_definitions(-D_GNU_SOURCE) endif () set(GLWD_LIBS "-ldl -lcrypt") find_package(Threads REQUIRED) set(GLWD_LIBS ${GLWD_LIBS} ${CMAKE_THREAD_LIBS_INIT}) include(FindJansson) find_package(Jansson ${JANSSON_VERSION_REQUIRED} REQUIRED) if (JANSSON_FOUND) set(GLWD_LIBS ${GLWD_LIBS} ${JANSSON_LIBRARIES}) include_directories(${JANSSON_INCLUDE_DIRS}) endif () include(FindGnuTLS) find_package(GnuTLS REQUIRED) if (GNUTLS_FOUND) set(GLWD_LIBS ${GLWD_LIBS} ${GNUTLS_LIBRARIES}) include_directories(${GNUTLS_INCLUDE_DIRS}) endif () include(FindNettle) find_package(Nettle REQUIRED) if (NETTLE_FOUND) set(GLWD_LIBS ${GLWD_LIBS} ${NETTLE_LIBRARIES}) include_directories(${NETTLE_INCLUDE_DIRS}) endif () include(FindLibconfig) find_package(Libconfig REQUIRED) if (LIBCONFIG_FOUND) set(GLWD_LIBS ${GLWD_LIBS} ${LIBCONFIG_LIBRARIES}) include_directories(${LIBCONFIG_INCLUDE_DIRS}) endif () include(FindMHD) find_package(MHD REQUIRED) if (MHD_FOUND) set(GLWD_LIBS ${GLWD_LIBS} ${MHD_LIBRARIES}) include_directories(${MHD_INCLUDE_DIRS}) endif () include(FindZLIB) find_package(ZLIB REQUIRED) if (ZLIB_FOUND) set(GLWD_LIBS ${GLWD_LIBS} ${ZLIB_LIBRARIES}) include_directories(${ZLIB_INCLUDE_DIRS}) endif () # build include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src) add_executable(glewlwyd ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd.h ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd-common.h ${CMAKE_CURRENT_SOURCE_DIR}/src/static_compressed_inmemory_website_callback.h ${CMAKE_CURRENT_SOURCE_DIR}/src/http_compression_callback.h ${CMAKE_CURRENT_SOURCE_DIR}/src/client.c ${CMAKE_CURRENT_SOURCE_DIR}/src/misc.c ${CMAKE_CURRENT_SOURCE_DIR}/src/module.c ${CMAKE_CURRENT_SOURCE_DIR}/src/plugin.c ${CMAKE_CURRENT_SOURCE_DIR}/src/scope.c ${CMAKE_CURRENT_SOURCE_DIR}/src/session.c ${CMAKE_CURRENT_SOURCE_DIR}/src/static_compressed_inmemory_website_callback.c ${CMAKE_CURRENT_SOURCE_DIR}/src/http_compression_callback.c ${CMAKE_CURRENT_SOURCE_DIR}/src/user.c ${CMAKE_CURRENT_SOURCE_DIR}/src/api_key.c ${CMAKE_CURRENT_SOURCE_DIR}/src/metrics.c ${CMAKE_CURRENT_SOURCE_DIR}/src/webservice.c ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd.c ) set(SEARCH_ORCANIA OFF CACHE BOOL "Force to false") # Avoid to search and download orcania during orcania search and download set(SEARCH_YDER OFF CACHE BOOL "Force to false") # Avoid to search and download yder during ulfius and hoel search and download set(SEARCH_ORCANIA_U OFF CACHE BOOL "Force to false") # Avoid to search and download orcania during ulfius search and download set(BUILD_UWSC OFF CACHE BOOL "Force to false") # Avoid to build uwsc during ulfius search and download set(SEARCH_ORCANIA_H OFF CACHE BOOL "Force to false") # Avoid to search and download orcania during hoel search and download set(SEARCH_ORCANIA_R OFF CACHE BOOL "Force to false") # Avoid to search and download orcania during hoel search and download set(SEARCH_YDER_R OFF CACHE BOOL "Force to false") # Avoid to search and download yder during ulfius and hoel search and download set(SEARCH_ULFIUS_R OFF CACHE BOOL "Force to false") # Avoid to search and download ulfius during rhonabwy search and download set(SEARCH_ORCANIA_I OFF CACHE BOOL "Force to false") # Avoid to search and download orcania during iddawc search and download set(SEARCH_YDER_I OFF CACHE BOOL "Force to false") # Avoid to search and download yder during iddawc search and download set(SEARCH_ULFIUS_I OFF CACHE BOOL "Force to false") # Avoid to search and download ulfius during iddawc search and download set(SEARCH_RHONABWY_I OFF CACHE BOOL "Force to false") # Avoid to search and download rhonabwy during iddawc search and download option(DOWNLOAD_DEPENDENCIES "Download required dependencies" ON) set(Orcania_FIND_QUIETLY ON) # force to find Orcania quietly include(FindOrcania) find_package(Orcania ${ORCANIA_VERSION_REQUIRED} QUIET) # try to find orcania if (ORCANIA_FOUND) include_directories(${orcania_SOURCE_DIR}/include) elseif (NOT ORCANIA_FOUND AND DOWNLOAD_DEPENDENCIES) include(DownloadProject) message(STATUS "Download Orcania ${ORCANIA_VERSION_REQUIRED}") download_project(PROJ orcania # ... otherwise, download archive URL "https://github.com/babelouest/orcania/archive/v${ORCANIA_VERSION_REQUIRED}.tar.gz" QUIET) add_subdirectory(${orcania_SOURCE_DIR} ${orcania_BINARY_DIR}) include_directories(${orcania_SOURCE_DIR}/include) add_dependencies(glewlwyd orcania) set(ORCANIA_LIBRARIES orcania) include_directories(${orcania_BINARY_DIR}) else () message( FATAL_ERROR "Orcania not found") endif () set(GLWD_LIBS ${GLWD_LIBS} ${ORCANIA_LIBRARIES}) set(Yder_FIND_QUIETLY ON) # force to find Yder quietly include(FindYder) find_package(Yder ${YDER_VERSION_REQUIRED} QUIET) # try to find Yder if (YDER_FOUND) include_directories(${yder_SOURCE_DIR}/include) elseif (NOT YDER_FOUND AND DOWNLOAD_DEPENDENCIES) include(DownloadProject) message(STATUS "Download Yder ${YDER_VERSION_REQUIRED}") option(CHECK_ORCANIA "specific param" off) download_project(PROJ yder # ... otherwise, download archive URL "https://github.com/babelouest/yder/archive/v${YDER_VERSION_REQUIRED}.tar.gz" QUIET) add_subdirectory(${yder_SOURCE_DIR} ${yder_BINARY_DIR}) include_directories(${yder_SOURCE_DIR}/include) include_directories(${orcania_SOURCE_DIR}/include) add_dependencies(glewlwyd yder) add_dependencies(yder orcania) set(YDER_LIBRARIES yder) include_directories(${yder_BINARY_DIR}) else () message( FATAL_ERROR "Yder not found") endif () set(GLWD_LIBS ${GLWD_LIBS} ${YDER_LIBRARIES}) set(Ulfius_FIND_QUIETLY ON) include(FindUlfius) find_package(Ulfius ${ULFIUS_VERSION_REQUIRED} QUIET) if (ULFIUS_FOUND) include_directories(${ulfius_SOURCE_DIR}/include) elseif (NOT ULFIUS_FOUND AND DOWNLOAD_DEPENDENCIES) include(DownloadProject) option(WITH_WEBSOCKET "specific param" off) option(INSTALL_HEADER "specific param" off) option(CHECK_YDER "specific param" off) message(STATUS "Download Ulfius ${ULFIUS_VERSION_REQUIRED}") download_project(PROJ ulfius URL "https://github.com/babelouest/ulfius/archive/v${ULFIUS_VERSION_REQUIRED}.tar.gz" QUIET) add_subdirectory(${ulfius_SOURCE_DIR} ${ulfius_BINARY_DIR}) include_directories(${yder_SOURCE_DIR}/include) include_directories(${orcania_SOURCE_DIR}/include) include_directories(${ulfius_SOURCE_DIR}/include) add_dependencies(glewlwyd ulfius) add_dependencies(ulfius yder) add_dependencies(ulfius orcania) set(ULFIUS_LIBRARIES ulfius) include_directories(${ulfius_BINARY_DIR}) else () message( FATAL_ERROR "Ulfius not found") endif () set(GLWD_LIBS ${GLWD_LIBS} ${ULFIUS_LIBRARIES}) set(Hoel_FIND_QUIETLY ON) include(FindHoel) find_package(Hoel ${HOEL_VERSION_REQUIRED} QUIET) if (HOEL_FOUND) include_directories(${hoel_SOURCE_DIR}/include) elseif (NOT HOEL_FOUND AND DOWNLOAD_DEPENDENCIES) include(DownloadProject) option(INSTALL_HEADER "specific param" off) option(CHECK_YDER "specific param" off) message(STATUS "Download Hoel ${HOEL_VERSION_REQUIRED}") download_project(PROJ hoel URL "https://github.com/babelouest/hoel/archive/v${HOEL_VERSION_REQUIRED}.tar.gz" QUIET) add_subdirectory(${hoel_SOURCE_DIR} ${hoel_BINARY_DIR}) include_directories(${hoel_SOURCE_DIR}/include) add_dependencies(glewlwyd hoel) add_dependencies(hoel yder) add_dependencies(hoel orcania) set(HOEL_LIBRARIES hoel) include_directories(${hoel_BINARY_DIR}) else () message( FATAL_ERROR "Hoel not found") endif () set(GLWD_LIBS ${GLWD_LIBS} ${HOEL_LIBRARIES}) target_link_libraries(glewlwyd ${GLWD_LIBS}) set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.3.4)") execute_process(COMMAND lsb_release -is OUTPUT_VARIABLE DISTRIB_CODENAME_T OUTPUT_STRIP_TRAILING_WHITESPACE ) string( TOLOWER "${DISTRIB_CODENAME_T}" DISTRIB_CODENAME ) execute_process(COMMAND lsb_release -cs OUTPUT_VARIABLE RELEASE_CODENAME_T OUTPUT_STRIP_TRAILING_WHITESPACE ) string( TOLOWER "${RELEASE_CODENAME_T}" RELEASE_CODENAME ) option(WITH_MOCK "Build Mock modules" off) # user modules if (WITH_MOCK OR BUILD_GLEWLWYD_TESTING) set(MOCK_LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd-common.h ${CMAKE_CURRENT_SOURCE_DIR}/src/misc.c ${USER_MODULES_SRC_PATH}/mock.c) add_library(usermodmock MODULE ${MOCK_LIB_SRC}) set_target_properties(usermodmock PROPERTIES COMPILE_OPTIONS -Wextra LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/user") target_link_libraries(usermodmock ${GLWD_LIBS}) set(USER_MODULES ${USER_MODULES} usermodmock) endif () option(WITH_USER_DATABASE "Build Database backend user module" on) if (WITH_USER_DATABASE) set(LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd-common.h ${CMAKE_CURRENT_SOURCE_DIR}/src/misc.c ${USER_MODULES_SRC_PATH}/database.c) add_library(usermoddatabase MODULE ${LIB_SRC}) set_target_properties(usermoddatabase PROPERTIES COMPILE_OPTIONS -Wextra LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/user") target_link_libraries(usermoddatabase ${GLWD_LIBS}) set(USER_MODULES ${USER_MODULES} usermoddatabase) endif () option(WITH_USER_LDAP "Build LDAP backend user module" on) if (WITH_USER_LDAP) include(FindLdap) find_package(Ldap REQUIRED) if (LDAP_FOUND) include_directories(${LDAP_INCLUDE_DIRS}) endif () set(LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd-common.h ${CMAKE_CURRENT_SOURCE_DIR}/src/misc.c ${USER_MODULES_SRC_PATH}/ldap.c) if ("${DISTRIB_CODENAME}" STREQUAL "ubuntu" AND "${RELEASE_CODENAME}" STREQUAL "impish") set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}, libldap-2.5-0") else () set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}, libldap-2.4-2") endif () add_library(usermodldap MODULE ${LIB_SRC}) set_target_properties(usermodldap PROPERTIES COMPILE_OPTIONS -Wextra LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/user") target_link_libraries(usermodldap ${GLWD_LIBS} ${LDAP_LIBRARIES} "-llber") set(USER_MODULES ${USER_MODULES} usermodldap) endif () option(WITH_USER_HTTP "Build HTTP backend user module" on) if (WITH_USER_HTTP) set(LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd-common.h ${CMAKE_CURRENT_SOURCE_DIR}/src/misc.c ${USER_MODULES_SRC_PATH}/http.c) add_library(usermodhttp MODULE ${LIB_SRC}) set_target_properties(usermodhttp PROPERTIES COMPILE_OPTIONS -Wextra LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/user") target_link_libraries(usermodhttp ${GLWD_LIBS}) set(USER_MODULES ${USER_MODULES} usermodhttp) endif () # user middleware modules if (WITH_MOCK OR BUILD_GLEWLWYD_TESTING) set(MOCK_LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd-common.h ${CMAKE_CURRENT_SOURCE_DIR}/src/misc.c ${USER_MIDDLEWARE_MODULES_SRC_PATH}/mock.c) add_library(usermidmodmock MODULE ${MOCK_LIB_SRC}) set_target_properties(usermidmodmock PROPERTIES COMPILE_OPTIONS -Wextra LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/user_middleware") target_link_libraries(usermidmodmock ${GLWD_LIBS}) set(USER_MIDDLEWARE_MODULES ${USER_MIDDLEWARE_MODULES} usermidmodmock) endif () # clients modules if (WITH_MOCK OR BUILD_GLEWLWYD_TESTING) set(MOCK_LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd-common.h ${CMAKE_CURRENT_SOURCE_DIR}/src/misc.c ${CLIENT_MODULES_SRC_PATH}/mock.c) add_library(clientmodmock MODULE ${MOCK_LIB_SRC}) set_target_properties(clientmodmock PROPERTIES COMPILE_OPTIONS -Wextra LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/client") target_link_libraries(clientmodmock ${GLWD_LIBS}) set(CLIENT_MODULES ${CLIENT_MODULES} clientmodmock) endif () option(WITH_CLIENT_DATABASE "Build Database backend client module" on) if (WITH_CLIENT_DATABASE) set(LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd-common.h ${CMAKE_CURRENT_SOURCE_DIR}/src/misc.c ${CLIENT_MODULES_SRC_PATH}/database.c) add_library(clientmoddatabase MODULE ${LIB_SRC}) set_target_properties(clientmoddatabase PROPERTIES COMPILE_OPTIONS -Wextra LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/client") target_link_libraries(clientmoddatabase ${GLWD_LIBS}) set(CLIENT_MODULES ${CLIENT_MODULES} clientmoddatabase) endif () option(WITH_CLIENT_LDAP "Build LDAP backend client module" on) if (WITH_CLIENT_LDAP) include(FindLdap) find_package(Ldap REQUIRED) if (LDAP_FOUND) include_directories(${LDAP_INCLUDE_DIRS}) endif () set(LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd-common.h ${CMAKE_CURRENT_SOURCE_DIR}/src/misc.c ${CLIENT_MODULES_SRC_PATH}/ldap.c) if ("${DISTRIB_CODENAME}" STREQUAL "ubuntu" AND "${RELEASE_CODENAME}" STREQUAL "impish") set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}, libldap-2.5-0") else () set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}, libldap-2.4-2") endif () add_library(clientmodldap MODULE ${LIB_SRC}) set_target_properties(clientmodldap PROPERTIES COMPILE_OPTIONS -Wextra LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/client") target_link_libraries(clientmodldap ${GLWD_LIBS} ${LDAP_LIBRARIES} "-llber") set(CLIENT_MODULES ${CLIENT_MODULES} clientmodldap) endif () # schemes modules if (WITH_MOCK OR BUILD_GLEWLWYD_TESTING) set(MOCK_LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd-common.h ${CMAKE_CURRENT_SOURCE_DIR}/src/misc.c ${SCHEME_MODULES_SRC_PATH}/mock.c) set(MOCK_LIBS ${ULFIUS_LIBRARIES} "-ldl") add_library(schememodmock MODULE ${MOCK_LIB_SRC}) set_target_properties(schememodmock PROPERTIES COMPILE_OPTIONS -Wextra LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/scheme") target_link_libraries(schememodmock ${GLWD_LIBS}) set(SCHEME_MODULES ${SCHEME_MODULES} schememodmock) endif () option(WITH_SCHEME_RETYPE_PASSWORD "Build retype password scheme module" on) if (WITH_SCHEME_RETYPE_PASSWORD) set(LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd-common.h ${CMAKE_CURRENT_SOURCE_DIR}/src/misc.c ${SCHEME_MODULES_SRC_PATH}/password.c) add_library(schememodpassword MODULE ${LIB_SRC}) set_target_properties(schememodpassword PROPERTIES COMPILE_OPTIONS -Wextra LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/scheme") target_link_libraries(schememodpassword ${GLWD_LIBS}) set(SCHEME_MODULES ${SCHEME_MODULES} schememodpassword) endif () option(WITH_SCHEME_EMAIL "Build e-mail scheme module" on) if (WITH_SCHEME_EMAIL) set(LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd-common.h ${CMAKE_CURRENT_SOURCE_DIR}/src/misc.c ${SCHEME_MODULES_SRC_PATH}/email.c) add_library(schememodemail MODULE ${LIB_SRC}) set_target_properties(schememodemail PROPERTIES COMPILE_OPTIONS -Wextra LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/scheme") target_link_libraries(schememodemail ${GLWD_LIBS}) set(SCHEME_MODULES ${SCHEME_MODULES} schememodemail) endif () option(WITH_SCHEME_OTP "Build OTP scheme module" on) if (WITH_SCHEME_OTP) include(FindLibOath) find_package(LibOath REQUIRED) if (LIBOATH_FOUND) include_directories(${LIBOATH_INCLUDE_DIR}) endif () set(LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd-common.h ${CMAKE_CURRENT_SOURCE_DIR}/src/misc.c ${SCHEME_MODULES_SRC_PATH}/otp.c) set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}, liboath0 (>= 2.6.0)") add_library(schememodotp MODULE ${LIB_SRC}) set_target_properties(schememodotp PROPERTIES COMPILE_OPTIONS -Wextra LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/scheme") target_link_libraries(schememodotp ${GLWD_LIBS} ${LIBOATH_LIBRARIES}) set(SCHEME_MODULES ${SCHEME_MODULES} schememodotp) endif () option(WITH_SCHEME_WEBAUTHN "Build WebAuthn scheme module" on) if (WITH_SCHEME_WEBAUTHN) include(FindLibCBOR) find_package(LibCBOR REQUIRED) if (LIBCBOR_FOUND) include_directories(${LIBCBOR_INCLUDE_DIRS}) endif () include(FindLdap) find_package(Ldap REQUIRED) if (LDAP_FOUND) include_directories(${LDAP_INCLUDE_DIRS}) endif () set(LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd-common.h ${CMAKE_CURRENT_SOURCE_DIR}/src/misc.c ${SCHEME_MODULES_SRC_PATH}/webauthn.c) if ("${DISTRIB_CODENAME}" STREQUAL "ubuntu") set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}, libcbor0.6") endif () if ("${DISTRIB_CODENAME}" STREQUAL "debian" AND NOT ${RELEASE_CODENAME} STREQUAL "stretch") set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}, libcbor0|libcbor0.8") endif () set(Rhonabwy_FIND_QUIETLY ON) # force to find Rhonabwy quietly include(FindRhonabwy) find_package(Rhonabwy ${RHONABWY_VERSION_REQUIRED} QUIET) # try to find rhonabwy if (RHONABWY_FOUND) include_directories(${rhonabwy_SOURCE_DIR}/include) elseif (NOT RHONABWY_FOUND AND DOWNLOAD_DEPENDENCIES) include(DownloadProject) message(STATUS "Download Rhonabwy ${RHONABWY_VERSION_REQUIRED}") download_project(PROJ rhonabwy # ... otherwise, download archive URL "https://github.com/babelouest/rhonabwy/archive/v${RHONABWY_VERSION_REQUIRED}.tar.gz" QUIET) add_subdirectory(${rhonabwy_SOURCE_DIR} ${rhonabwy_BINARY_DIR}) include_directories(${rhonabwy_SOURCE_DIR}/include) set(RHONABWY_LIBRARIES rhonabwy) include_directories(${rhonabwy_BINARY_DIR}) add_dependencies(rhonabwy orcania) add_dependencies(rhonabwy yder) add_dependencies(rhonabwy ulfius) set (RHONABWY_FOUND ON) else () message( FATAL_ERROR "Rhonabwy not found") endif () add_library(schememodwebauthn MODULE ${LIB_SRC}) set_target_properties(schememodwebauthn PROPERTIES COMPILE_OPTIONS -Wextra LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/scheme") target_link_libraries(schememodwebauthn ${GLWD_LIBS} ${LIBCBOR_LIBRARIES} ${LDAP_LIBRARIES} ${RHONABWY_LIBRARIES}) set(SCHEME_MODULES ${SCHEME_MODULES} schememodwebauthn) endif () option(WITH_SCHEME_CERTIFICATE "Build certificate scheme module" on) if (WITH_SCHEME_CERTIFICATE) set(LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd-common.h ${CMAKE_CURRENT_SOURCE_DIR}/src/misc.c ${SCHEME_MODULES_SRC_PATH}/certificate.c) add_library(schememodcertificate MODULE ${LIB_SRC}) set_target_properties(schememodcertificate PROPERTIES COMPILE_OPTIONS -Wextra LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/scheme") target_link_libraries(schememodcertificate ${GLWD_LIBS}) set(SCHEME_MODULES ${SCHEME_MODULES} schememodcertificate) endif () option(WITH_SCHEME_HTTP "Build HTTP Basic Auth scheme module" on) if (WITH_SCHEME_HTTP) set(LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd-common.h ${CMAKE_CURRENT_SOURCE_DIR}/src/misc.c ${SCHEME_MODULES_SRC_PATH}/http.c) add_library(schememodhttp MODULE ${LIB_SRC}) set_target_properties(schememodhttp PROPERTIES COMPILE_OPTIONS -Wextra LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/scheme") target_link_libraries(schememodhttp ${GLWD_LIBS}) set(SCHEME_MODULES ${SCHEME_MODULES} schememodhttp) endif () option(WITH_SCHEME_OAUTH2 "Build Oauth2/OIDC scheme module" on) if (WITH_SCHEME_OAUTH2) set(LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd-common.h ${CMAKE_CURRENT_SOURCE_DIR}/src/misc.c ${SCHEME_MODULES_SRC_PATH}/oauth2.c) if (NOT ${RELEASE_CODENAME} STREQUAL "stretch" AND NOT ${RELEASE_CODENAME} STREQUAL "bionic") set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}") endif () if (NOT RHONABWY_FOUND) set(Rhonabwy_FIND_QUIETLY ON) # force to find Rhonabwy quietly include(FindRhonabwy) find_package(Rhonabwy ${RHONABWY_VERSION_REQUIRED} QUIET) # try to find rhonabwy if (RHONABWY_FOUND) include_directories(${rhonabwy_SOURCE_DIR}/include) elseif (NOT RHONABWY_FOUND AND DOWNLOAD_DEPENDENCIES) include(DownloadProject) message(STATUS "Download Rhonabwy ${RHONABWY_VERSION_REQUIRED}") download_project(PROJ rhonabwy # ... otherwise, download archive URL "https://github.com/babelouest/rhonabwy/archive/v${RHONABWY_VERSION_REQUIRED}.tar.gz" QUIET) add_subdirectory(${rhonabwy_SOURCE_DIR} ${rhonabwy_BINARY_DIR}) include_directories(${rhonabwy_SOURCE_DIR}/include) set(RHONABWY_LIBRARIES rhonabwy) include_directories(${rhonabwy_BINARY_DIR}) add_dependencies(rhonabwy orcania) add_dependencies(rhonabwy yder) add_dependencies(rhonabwy ulfius) set (RHONABWY_FOUND ON) else () message( FATAL_ERROR "Rhonabwy not found") endif () endif () set(Iddawc_FIND_QUIETLY ON) # force to find Iddawc quietly include(FindIddawc) find_package(Iddawc ${IDDAWC_VERSION_REQUIRED} QUIET) # try to find iddawc if (IDDAWC_FOUND) include_directories(${iddawc_SOURCE_DIR}/include) elseif (NOT IDDAWC_FOUND AND DOWNLOAD_DEPENDENCIES) include(DownloadProject) message(STATUS "Download Iddawc ${IDDAWC_VERSION_REQUIRED}") set(BUILD_IDWCC OFF) download_project(PROJ iddawc # ... otherwise, download archive URL "https://github.com/babelouest/iddawc/archive/v${IDDAWC_VERSION_REQUIRED}.tar.gz" QUIET) add_subdirectory(${iddawc_SOURCE_DIR} ${iddawc_BINARY_DIR}) include_directories(${iddawc_SOURCE_DIR}/include) set(IDDAWC_LIBRARIES iddawc) include_directories(${iddawc_BINARY_DIR}) set (IDDAWC_FOUND ON) add_dependencies(iddawc orcania) add_dependencies(iddawc yder) add_dependencies(iddawc ulfius) add_dependencies(iddawc rhonabwy) else () message( FATAL_ERROR "Iddawc not found") endif () add_library(schememodoauth2 MODULE ${LIB_SRC}) set_target_properties(schememodoauth2 PROPERTIES COMPILE_OPTIONS -Wextra LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/scheme") target_link_libraries(schememodoauth2 ${GLWD_LIBS} ${RHONABWY_LIBRARIES} ${IDDAWC_LIBRARIES}) set(SCHEME_MODULES ${SCHEME_MODULES} schememodoauth2) endif () # plugins modules if (WITH_MOCK OR BUILD_GLEWLWYD_TESTING) set(MOCK_LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd-common.h ${CMAKE_CURRENT_SOURCE_DIR}/src/misc.c ${PLUGIN_MODULES_SRC_PATH}/mock.c) set(MOCK_LIBS "-ldl") add_library(pluginmodmock MODULE ${MOCK_LIB_SRC}) set_target_properties(pluginmodmock PROPERTIES COMPILE_OPTIONS -Wextra LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugin") target_link_libraries(pluginmodmock ${MOCK_LIBS}) set(PLUGIN_MODULES ${PLUGIN_MODULES} pluginmodmock) endif () option(WITH_PLUGIN_OAUTH2 "Build legacy oauth2 plugin module" on) if (WITH_PLUGIN_OAUTH2) if (NOT RHONABWY_FOUND) set(Rhonabwy_FIND_QUIETLY ON) # force to find Rhonabwy quietly include(FindRhonabwy) find_package(Rhonabwy ${RHONABWY_VERSION_REQUIRED} QUIET) # try to find rhonabwy if (RHONABWY_FOUND) include_directories(${rhonabwy_SOURCE_DIR}/include) elseif (NOT RHONABWY_FOUND AND DOWNLOAD_DEPENDENCIES) include(DownloadProject) message(STATUS "Download Rhonabwy ${RHONABWY_VERSION_REQUIRED}") download_project(PROJ rhonabwy # ... otherwise, download archive URL "https://github.com/babelouest/rhonabwy/archive/v${RHONABWY_VERSION_REQUIRED}.tar.gz" QUIET) add_subdirectory(${rhonabwy_SOURCE_DIR} ${rhonabwy_BINARY_DIR}) include_directories(${rhonabwy_SOURCE_DIR}/include) set(RHONABWY_LIBRARIES rhonabwy) include_directories(${rhonabwy_BINARY_DIR}) add_dependencies(rhonabwy orcania) add_dependencies(rhonabwy yder) add_dependencies(rhonabwy ulfius) set (RHONABWY_FOUND ON) else () message( FATAL_ERROR "Rhonabwy not found") endif () endif () include_directories(${RESOURCES_SRC_PATH}) set(LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd-common.h ${CMAKE_CURRENT_SOURCE_DIR}/src/misc.c ${PLUGIN_MODULES_SRC_PATH}/protocol_oauth2.c ${RESOURCES_SRC_PATH}/glewlwyd_resource.c ${RESOURCES_SRC_PATH}/glewlwyd_resource.h) add_library(protocol_oauth2 MODULE ${LIB_SRC}) set_target_properties(protocol_oauth2 PROPERTIES COMPILE_OPTIONS -Wextra LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugin") target_link_libraries(protocol_oauth2 ${GLWD_LIBS} ${RHONABWY_LIBRARIES}) set(PLUGIN_MODULES ${PLUGIN_MODULES} protocol_oauth2) endif () option(WITH_PLUGIN_OIDC "Build OpenID Connect plugin module" on) if (WITH_PLUGIN_OIDC) if (NOT RHONABWY_FOUND) set(Rhonabwy_FIND_QUIETLY ON) # force to find Rhonabwy quietly include(FindRhonabwy) find_package(Rhonabwy ${RHONABWY_VERSION_REQUIRED} QUIET) # try to find rhonabwy if (RHONABWY_FOUND) include_directories(${rhonabwy_SOURCE_DIR}/include) elseif (NOT RHONABWY_FOUND AND DOWNLOAD_DEPENDENCIES) include(DownloadProject) message(STATUS "Download Rhonabwy ${RHONABWY_VERSION_REQUIRED}") download_project(PROJ rhonabwy # ... otherwise, download archive URL "https://github.com/babelouest/rhonabwy/archive/v${RHONABWY_VERSION_REQUIRED}.tar.gz" QUIET) add_subdirectory(${rhonabwy_SOURCE_DIR} ${rhonabwy_BINARY_DIR}) include_directories(${rhonabwy_SOURCE_DIR}/include) set(RHONABWY_LIBRARIES rhonabwy) include_directories(${rhonabwy_BINARY_DIR}) add_dependencies(rhonabwy orcania) add_dependencies(rhonabwy yder) add_dependencies(rhonabwy ulfius) set (RHONABWY_FOUND ON) else () message( FATAL_ERROR "Rhonabwy not found") endif () endif () include_directories(${RESOURCES_SRC_PATH}) set(LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd-common.h ${CMAKE_CURRENT_SOURCE_DIR}/src/misc.c ${PLUGIN_MODULES_SRC_PATH}/protocol_oidc.c ${RESOURCES_SRC_PATH}/oidc_resource.c ${RESOURCES_SRC_PATH}/oidc_resource.h) add_library(protocol_oidc MODULE ${LIB_SRC}) set_target_properties(protocol_oidc PROPERTIES COMPILE_OPTIONS -Wextra LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugin") target_link_libraries(protocol_oidc ${GLWD_LIBS} ${RHONABWY_LIBRARIES}) set(PLUGIN_MODULES ${PLUGIN_MODULES} protocol_oidc) endif () option(WITH_PLUGIN_REGISTER "Build register plugin module" on) if (WITH_PLUGIN_REGISTER) set(LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/glewlwyd-common.h ${CMAKE_CURRENT_SOURCE_DIR}/src/misc.c ${PLUGIN_MODULES_SRC_PATH}/register.c) add_library(protocol_register MODULE ${LIB_SRC}) set_target_properties(protocol_register PROPERTIES COMPILE_OPTIONS -Wextra LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugin") target_link_libraries(protocol_register ${GLWD_LIBS}) set(PLUGIN_MODULES ${PLUGIN_MODULES} protocol_register) endif () # tests option(BUILD_GLEWLWYD_TESTING "Build the testing tree" OFF) if (BUILD_GLEWLWYD_TESTING) set(WITH_MOCK ON) include(FindCheck) find_package(Check REQUIRED) if (CHECK_FOUND) include(FindSubunit) find_package(Subunit REQUIRED) enable_testing() set(CMAKE_CTEST_COMMAND ctest -V) set(TST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/test) find_package(Threads REQUIRED) set(TST_LIBS ${TST_LIBS} ${JANSSON_LIBRARIES}) set(TST_LIBS ${TST_LIBS} ${ORCANIA_LIBRARIES}) set(TST_LIBS ${TST_LIBS} ${YDER_LIBRARIES}) set(TST_LIBS ${TST_LIBS} ${ULFIUS_LIBRARIES}) set(TST_LIBS ${TST_LIBS} ${RHONABWY_LIBRARIES}) set(TST_LIBS ${TST_LIBS} ${GNUTLS_LIBRARIES}) set(TST_LIBS ${TST_LIBS} ${CHECK_LIBRARIES} ${SUBUNIT_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} m rt) set(TESTS glewlwyd_admin_mod_type glewlwyd_admin_mod_user glewlwyd_admin_mod_user_middleware glewlwyd_admin_mod_user_auth_scheme glewlwyd_admin_mod_client glewlwyd_admin_mod_plugin glewlwyd_admin_api_key glewlwyd_auth_password glewlwyd_auth_scheme glewlwyd_auth_grant glewlwyd_auth_check_scheme glewlwyd_auth_scheme_trigger glewlwyd_auth_scheme_register glewlwyd_auth_profile glewlwyd_auth_session_manage glewlwyd_auth_profile_get_scheme_available glewlwyd_crud_user glewlwyd_crud_user_middleware glewlwyd_crud_client glewlwyd_crud_scope glewlwyd_mod_user_http glewlwyd_mod_user_irl glewlwyd_mod_user_multiple_password_irl glewlwyd_mod_client_irl glewlwyd_profile_delete glewlwyd_scheme_forbidden ) if (WITH_PLUGIN_OAUTH2) set (TST_LIBS ${TST_LIBS} ${GNUTLS_LIBRARIES}) set (TESTS ${TESTS} glewlwyd_oauth2_additional_parameters glewlwyd_oauth2_auth_code glewlwyd_oauth2_code glewlwyd_oauth2_code_client_confidential glewlwyd_oauth2_implicit glewlwyd_oauth2_resource_owner_pwd_cred glewlwyd_oauth2_resource_owner_pwd_cred_client_confidential glewlwyd_oauth2_client_cred glewlwyd_oauth2_refresh_token glewlwyd_oauth2_refresh_token_client_confidential glewlwyd_oauth2_delete_token glewlwyd_oauth2_delete_token_client_confidential glewlwyd_oauth2_profile glewlwyd_oauth2_profile_impersonate glewlwyd_oauth2_refresh_manage glewlwyd_oauth2_refresh_manage_session glewlwyd_oauth2_irl glewlwyd_oauth2_code_challenge glewlwyd_oauth2_token_introspection glewlwyd_oauth2_token_revocation glewlwyd_oauth2_device_authorization glewlwyd_oauth2_code_replay glewlwyd_oauth2_scheme_required ) endif () if (WITH_PLUGIN_OIDC) set (TST_LIBS ${TST_LIBS} ${GNUTLS_LIBRARIES}) set (TESTS ${TESTS} glewlwyd_oidc_additional_parameters glewlwyd_oidc_auth_code glewlwyd_oidc_client_cred glewlwyd_oidc_code glewlwyd_oidc_code_client_confidential glewlwyd_oidc_code_idtoken glewlwyd_oidc_delete_token glewlwyd_oidc_delete_token_client_confidential glewlwyd_oidc_hybrid_id_token_code glewlwyd_oidc_hybrid_id_token_token_code glewlwyd_oidc_hybrid_token_code glewlwyd_oidc_implicit_id_token glewlwyd_oidc_implicit_id_token_token glewlwyd_oidc_irl glewlwyd_oidc_only_no_refresh glewlwyd_oidc_optional_request_parameters glewlwyd_oidc_refresh_manage glewlwyd_oidc_refresh_manage_session glewlwyd_oidc_refresh_token glewlwyd_oidc_refresh_token_client_confidential glewlwyd_oidc_resource_owner_pwd_cred glewlwyd_oidc_resource_owner_pwd_cred_client_confidential glewlwyd_oidc_token glewlwyd_oidc_userinfo glewlwyd_oidc_discovery glewlwyd_oidc_client_secret glewlwyd_oidc_request_jwt glewlwyd_oidc_subject_type glewlwyd_oidc_address_claim glewlwyd_oidc_claims_scopes glewlwyd_oidc_claim_request glewlwyd_oidc_code_challenge glewlwyd_oidc_token_introspection glewlwyd_oidc_token_revocation glewlwyd_oidc_client_registration glewlwyd_oidc_jwt_encrypted glewlwyd_oidc_jwks_config glewlwyd_oidc_session_management glewlwyd_oidc_device_authorization glewlwyd_oidc_refresh_token_one_use glewlwyd_oidc_client_registration_management glewlwyd_oidc_code_replay glewlwyd_oidc_scheme_required glewlwyd_oidc_dpop glewlwyd_oidc_resource glewlwyd_oidc_rich_auth_requests glewlwyd_oidc_pushed_auth_requests glewlwyd_oidc_reduced_scope glewlwyd_oidc_all_algs glewlwyd_oidc_ciba glewlwyd_oidc_auth_iss_is glewlwyd_oidc_jarm glewlwyd_oidc_fapi ) set(TESTS_SSL ${TESTS_SSL} glewlwyd_oidc_client_certificate) endif () set(TESTS_PROMETHEUS glewlwyd_prometheus) if (WITH_PLUGIN_REGISTER) set (TESTS ${TESTS} glewlwyd_register ) endif () if (WITH_SCHEME_OTP) set (TST_LIBS ${TST_LIBS} ${LIBOATH_LIBRARIES}) set (TESTS ${TESTS} glewlwyd_scheme_otp) endif () if (WITH_SCHEME_WEBAUTHN) set(TST_LIBS ${TST_LIBS} ${LIBCBOR_LIBRARIES}) set (TESTS ${TESTS} glewlwyd_scheme_webauthn) endif () if (WITH_SCHEME_EMAIL) set (TESTS ${TESTS} glewlwyd_scheme_mail) endif () if (WITH_SCHEME_RETYPE_PASSWORD) set (TESTS ${TESTS} glewlwyd_scheme_retype_password) endif () if (WITH_SCHEME_HTTP) set(TESTS ${TESTS} glewlwyd_scheme_http) endif () if (WITH_SCHEME_OAUTH2) set(TST_LIBS ${TST_LIBS} ${IDDAWC_LIBRARIES}) set(TESTS ${TESTS} glewlwyd_scheme_oauth2) endif () if (WITH_SCHEME_CERTIFICATE) set(TESTS_SSL ${TESTS_SSL} glewlwyd_scheme_certificate) endif () configure_file( "${CMAKE_MODULE_PATH}/CTestCustom.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/CTestCustom.cmake" @ONLY) set(COUNTER 0) foreach (t ${TESTS}) add_executable(${t} EXCLUDE_FROM_ALL ${TST_DIR}/${t}.c ${TST_DIR}/unit-tests.c ${TST_DIR}/unit-tests.h) target_include_directories(${t} PUBLIC ${TST_DIR}) target_link_libraries(${t} PUBLIC ${TST_LIBS}) if (NOT ${t} MATCHES "_irl$") add_test(NAME ${t} WORKING_DIRECTORY ${TST_DIR} COMMAND ${t}) elseif (${t} MATCHES "_user_irl$") FILE(GLOB JsonIrl ${TST_DIR}/mod_user_*.json) foreach (j ${JsonIrl}) add_test(NAME "${t}_${COUNTER}" WORKING_DIRECTORY ${TST_DIR} COMMAND ${t} ${j}) MATH(EXPR COUNTER "${COUNTER}+1") endforeach () elseif (${t} MATCHES "_user_multiple_password_irl$") FILE(GLOB JsonIrl ${TST_DIR}/mod_multiple_password_*.json) foreach (j ${JsonIrl}) add_test(NAME "${t}_${COUNTER}" WORKING_DIRECTORY ${TST_DIR} COMMAND ${t} ${j}) MATH(EXPR COUNTER "${COUNTER}+1") endforeach () elseif (${t} MATCHES "_client_irl$") FILE(GLOB JsonIrl ${TST_DIR}/mod_client_*.json) foreach (j ${JsonIrl}) add_test(NAME "${t}_${COUNTER}" WORKING_DIRECTORY ${TST_DIR} COMMAND ${t} ${j}) MATH(EXPR COUNTER "${COUNTER}+1") endforeach () elseif (${t} MATCHES "_oauth2_irl$") FILE(GLOB JsonIrl ${TST_DIR}/plugin_oauth2_*.json) foreach (j ${JsonIrl}) add_test(NAME "${t}_${COUNTER}" WORKING_DIRECTORY ${TST_DIR} COMMAND ${t} ${j}) MATH(EXPR COUNTER "${COUNTER}+1") endforeach () elseif (${t} MATCHES "_oidc_irl$") FILE(GLOB JsonIrl ${TST_DIR}/plugin_iodc_*.json) foreach (j ${JsonIrl}) add_test(NAME "${t}_${COUNTER}" WORKING_DIRECTORY ${TST_DIR} COMMAND ${t} ${j}) MATH(EXPR COUNTER "${COUNTER}+1") endforeach () endif () endforeach () foreach (t ${TESTS_SSL}) add_executable(${t} EXCLUDE_FROM_ALL ${TST_DIR}/${t}.c ${TST_DIR}/unit-tests.c ${TST_DIR}/unit-tests.h) target_include_directories(${t} PUBLIC ${TST_DIR}) target_link_libraries(${t} PUBLIC ${TST_LIBS}) endforeach () foreach (t ${TESTS_PROMETHEUS}) add_executable(${t} EXCLUDE_FROM_ALL ${TST_DIR}/${t}.c ${TST_DIR}/unit-tests.c ${TST_DIR}/unit-tests.h) target_include_directories(${t} PUBLIC ${TST_DIR}) target_link_libraries(${t} PUBLIC ${TST_LIBS}) endforeach () endif () endif () # install target install(TARGETS glewlwyd RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES docs/glewlwyd.conf.sample DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/glewlwyd RENAME glewlwyd.conf COMPONENT config) install(DIRECTORY webapp/ DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/glewlwyd/webapp/ COMPONENT runtime) install(FILES webapp/config.json.sample DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/glewlwyd/webapp/ RENAME config.json COMPONENT runtime) install(TARGETS ${USER_MODULES} DESTINATION ${CMAKE_INSTALL_LIBDIR}/glewlwyd/user) install(TARGETS ${USER_MIDDLEWARE_MODULES} DESTINATION ${CMAKE_INSTALL_LIBDIR}/glewlwyd/user_middleware) install(TARGETS ${CLIENT_MODULES} DESTINATION ${CMAKE_INSTALL_LIBDIR}/glewlwyd/client) install(TARGETS ${SCHEME_MODULES} DESTINATION ${CMAKE_INSTALL_LIBDIR}/glewlwyd/scheme) install(TARGETS ${PLUGIN_MODULES} DESTINATION ${CMAKE_INSTALL_LIBDIR}/glewlwyd/plugin) install(DIRECTORY docs/database/ DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/database/ COMPONENT runtime) install(DIRECTORY docs/fail2ban/ DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/fail2ban/ COMPONENT runtime) install(FILES README.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/ COMPONENT runtime) install(FILES CHANGELOG.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/ COMPONENT runtime) install(FILES docs/API.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) install(FILES docs/CERTIFICATE.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) install(FILES docs/CLIENT_DATABASE.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) install(FILES docs/CLIENT_LDAP.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) install(FILES docs/EMAIL.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) install(FILES docs/GETTING_STARTED.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) install(FILES docs/INSTALL.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) install(FILES docs/HTTP.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) install(FILES docs/OAUTH2.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) install(FILES docs/OAUTH2_SCHEME.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) install(FILES docs/OIDC.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) install(FILES docs/OTP.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) install(FILES docs/README.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) install(FILES docs/REGISTER.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) install(FILES docs/SCOPE.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) install(FILES docs/USER_DATABASE.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) install(FILES docs/USER_HTTP.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) install(FILES docs/USER_LDAP.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) install(FILES docs/USER.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) install(FILES docs/WEBAUTHN.md DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) install(FILES docs/glewlwyd.service DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) install(FILES docs/glewlwyd-init DESTINATION ${CMAKE_INSTALL_DATADIR}/glewlwyd/docs/ COMPONENT runtime) INSTALL(FILES docs/glewlwyd.8 DESTINATION ${CMAKE_INSTALL_MANDIR}/man8 COMPONENT runtime) set(TARGETS glewlwyd) install(TARGETS ${TARGETS} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} CONFIGURATIONS Release) # uninstall target if (NOT TARGET uninstall) configure_file( "${CMAKE_MODULE_PATH}/CMakeUninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) endif () # packaging set(CPACK_PACKAGE_VERSION_MAJOR ${PROGRAM_VERSION_MAJOR}) set(CPACK_PACKAGE_VERSION_MINOR ${PROGRAM_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${PROGRAM_VERSION_PATCH}) set(PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}_${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") set(PACKAGE_IGNORED_FILES "${CMAKE_CURRENT_BINARY_DIR}/;/.git/;.gitignore;~$;${CPACK_SOURCE_IGNORE_FILES}") set(CPACK_GENERATOR "TGZ;DEB") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "mail@babelouest.org") set(CPACK_DEBIAN_PACKAGE_DESCRIPTION ${PROJECT_DESCRIPTION}) set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/babelouest/glewlwyd") set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}, libconfig9, libgnutls30 (>= 3.5.0), liborcania|liborcania-dev (>= ${ORCANIA_VERSION_REQUIRED}), libyder|libyder-dev (>= ${YDER_VERSION_REQUIRED}), libulfius|libulfius-dev (>= ${ULFIUS_VERSION_REQUIRED}), libhoel|libhoel-dev (>= ${HOEL_VERSION_REQUIRED}), lsb-base (>= 3.0-6)") set(CPACK_PACKAGE_FILE_NAME ${PACKAGE_FILE_NAME}) if (NOT ${RELEASE_CODENAME} STREQUAL "stretch" AND NOT ${RELEASE_CODENAME} STREQUAL "bionic") set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}, libjansson4 (>= ${JANSSON_VERSION_REQUIRED})") endif () option(BUILD_RPM "Build a RPM for your system" OFF) if (BUILD_RPM) set(CPACK_GENERATOR "TGZ;DEB;RPM") set(CPACK_RPM_PACKAGE_LICENSE "GPL") set(CPACK_RPM_PACKAGE_URL "http://babelouest.github.io/glewlwyd/") set(CPACK_RPM_PACKAGE_REQUIRES "openldap, liboath, libconfig, gnutls >= 3.5.0, jansson >= ${JANSSON_VERSION_REQUIRED}, liborcania >= ${ORCANIA_VERSION_REQUIRED}, libyder >= ${YDER_VERSION_REQUIRED}, libulfius >= ${ULFIUS_VERSION_REQUIRED}, libhoel >= ${HOEL_VERSION_REQUIRED}") endif () set(CPACK_SOURCE_GENERATOR "TGZ") set(CPACK_SOURCE_PACKAGE_FILE_NAME ${PACKAGE_FILE_NAME}) set(CPACK_SOURCE_IGNORE_FILES ${PACKAGE_IGNORED_FILES}) include(CPack) add_custom_target(dist_g COMMAND ${CMAKE_MAKE_PROGRAM} package_source) message(STATUS "Download required dependencies: ${DOWNLOAD_DEPENDENCIES}") message(STATUS "Build Mock modules: ${WITH_MOCK}") message(STATUS "Build backend user module Database: ${WITH_USER_DATABASE}") message(STATUS "Build backend user module LDAP: ${WITH_USER_LDAP}") message(STATUS "Build backend user module HTTP: ${WITH_USER_HTTP}") message(STATUS "Build backend client module Database: ${WITH_CLIENT_DATABASE}") message(STATUS "Build backend client module LDAP: ${WITH_CLIENT_LDAP}") message(STATUS "Build scheme module retype password: ${WITH_SCHEME_RETYPE_PASSWORD}") message(STATUS "Build scheme module e-mail code: ${WITH_SCHEME_EMAIL}") message(STATUS "Build scheme module OTP: ${WITH_SCHEME_OTP}") message(STATUS "Build scheme module WebAuthn: ${WITH_SCHEME_WEBAUTHN}") message(STATUS "Build scheme module TLS Certificate: ${WITH_SCHEME_CERTIFICATE}") message(STATUS "Build scheme module HTTP: ${WITH_SCHEME_HTTP}") message(STATUS "Build scheme module OAuth2: ${WITH_SCHEME_OAUTH2}") message(STATUS "Build plugin module legacy oauth2: ${WITH_PLUGIN_OAUTH2}") message(STATUS "Build plugin module OpenID Connect: ${WITH_PLUGIN_OIDC}") message(STATUS "Build plugin register new account: ${WITH_PLUGIN_REGISTER}") message(STATUS "Build the testing tree: ${BUILD_GLEWLWYD_TESTING}") message(STATUS "Build RPM package: ${BUILD_RPM}") glewlwyd-2.6.1/Dockerfile000066400000000000000000000043651415646314000153640ustar00rootroot00000000000000FROM alpine:latest AS builder COPY README.md /opt/glewlwyd/README.md COPY CHANGELOG.md /opt/glewlwyd/CHANGELOG.md COPY docs/ /opt/glewlwyd/docs/ COPY src/ /opt/glewlwyd/src/ COPY CMakeLists.txt /opt/glewlwyd/ COPY cmake-modules/ /opt/glewlwyd/cmake-modules/ COPY webapp/ /opt/glewlwyd/webapp/ # Install required packages RUN apk add --no-cache \ git \ make \ cmake \ wget \ gcc \ g++ \ jansson-dev \ gnutls-dev \ autoconf \ automake \ libmicrohttpd-dev \ libcurl \ curl-dev \ libconfig-dev \ libgcrypt-dev \ sqlite-dev \ mariadb-dev \ postgresql-dev \ util-linux-dev \ openldap-dev \ bash \ oath-toolkit-dev \ libtool && \ (cd /opt && wget https://github.com/PJK/libcbor/archive/v0.7.0.tar.gz -O libcbor.tar.gz && \ tar xf libcbor.tar.gz && cd libcbor-0.7.0 && mkdir build && cd build && \ cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib .. && make && make install) && \ ls -l /opt/glewlwyd/ && \ mkdir /opt/glewlwyd/build && cd /opt/glewlwyd/build/ && \ cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib -DWITH_JOURNALD=off .. && \ make && make install FROM alpine:latest AS runner RUN apk add --no-cache \ wget \ sqlite \ libconfig \ jansson \ gnutls \ libcurl \ libldap \ libmicrohttpd \ sqlite-libs \ libpq \ oath-toolkit-liboath \ mariadb-connector-c \ bash COPY --from=builder /usr/lib/libcbor.* /usr/lib/ COPY --from=builder /usr/lib/liborcania* /usr/lib/ COPY --from=builder /usr/lib/libyder* /usr/lib/ COPY --from=builder /usr/lib/libhoel* /usr/lib/ COPY --from=builder /usr/lib/libulfius* /usr/lib/ COPY --from=builder /usr/lib/librhonabwy* /usr/lib/ COPY --from=builder /usr/lib/libiddawc* /usr/lib/ COPY --from=builder /usr/lib/glewlwyd/ /usr/lib/glewlwyd/ COPY --from=builder /usr/bin/glewlwyd /usr/bin COPY --from=builder /usr/share/glewlwyd/ /usr/share/glewlwyd/ COPY --from=builder /usr/share/glewlwyd/webapp/config.json /etc/glewlwyd/ COPY --from=builder /usr/etc/glewlwyd/ /etc/glewlwyd/ RUN rm /usr/share/glewlwyd/webapp/config.json RUN ln -s /etc/glewlwyd/config.json /usr/share/glewlwyd/webapp/config.json COPY ["docs/docker/entrypoint.sh", "/"] CMD ["/entrypoint.sh"] glewlwyd-2.6.1/Dockerfile-ci000066400000000000000000000052051415646314000157470ustar00rootroot00000000000000FROM alpine:latest AS builder COPY README.md /opt/glewlwyd/README.md COPY CHANGELOG.md /opt/glewlwyd/CHANGELOG.md COPY docs/ /opt/glewlwyd/docs/ COPY src/ /opt/glewlwyd/src/ COPY test/ /opt/glewlwyd/test/ COPY CMakeLists.txt /opt/glewlwyd/ COPY cmake-modules/ /opt/glewlwyd/cmake-modules/ COPY webapp/ /opt/glewlwyd/webapp/ # Install required packages RUN apk add --no-cache \ git \ make \ cmake \ wget \ gcc \ g++ \ jansson-dev \ gnutls-dev \ autoconf \ automake \ libmicrohttpd-dev \ libcurl \ curl-dev \ libconfig-dev \ libgcrypt-dev \ sqlite-dev \ sqlite \ mariadb-dev \ postgresql-dev \ util-linux-dev \ openldap-dev \ bash \ oath-toolkit-dev \ libtool && \ (cd /opt && wget https://github.com/PJK/libcbor/archive/v0.7.0.tar.gz -O libcbor.tar.gz && \ tar xf libcbor.tar.gz && cd libcbor-0.7.0 && mkdir build && cd build && \ cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib .. && make && make install) && \ ls -l /opt/glewlwyd/ && \ mkdir /opt/glewlwyd/build && cd /opt/glewlwyd/build/ && \ cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib -DWITH_JOURNALD=off -DWITH_MOCK=on .. && \ make && make install RUN sqlite3 /tmp/glewlwyd.db < /opt/glewlwyd/docs/database/init.sqlite3.sql RUN sqlite3 /tmp/glewlwyd.db < /opt/glewlwyd/test/glewlwyd-test.sql FROM alpine:latest AS runner RUN apk add --no-cache \ wget \ sqlite \ libconfig \ jansson \ gnutls \ libcurl \ libldap \ libmicrohttpd \ sqlite-libs \ libpq \ oath-toolkit-liboath \ mariadb-connector-c \ bash COPY --from=builder /usr/lib/libcbor.* /usr/lib/ COPY --from=builder /usr/lib/liborcania* /usr/lib/ COPY --from=builder /usr/lib/libyder* /usr/lib/ COPY --from=builder /usr/lib/libhoel* /usr/lib/ COPY --from=builder /usr/lib/libulfius* /usr/lib/ COPY --from=builder /usr/lib/librhonabwy* /usr/lib/ COPY --from=builder /usr/lib/libiddawc* /usr/lib/ COPY --from=builder /usr/lib/glewlwyd/ /usr/lib/glewlwyd/ COPY --from=builder /usr/bin/glewlwyd /usr/bin COPY --from=builder /usr/share/glewlwyd/ /usr/share/glewlwyd/ COPY --from=builder /usr/share/glewlwyd/webapp/config.json /etc/glewlwyd/ COPY --from=builder /usr/etc/glewlwyd/ /etc/glewlwyd/ COPY --from=builder /opt/glewlwyd/test/glewlwyd-ci.conf /etc/glewlwyd/ COPY --from=builder /tmp/glewlwyd.db /tmp/glewlwyd.db RUN rm /usr/share/glewlwyd/webapp/config.json RUN ln -s /etc/glewlwyd/config.json /usr/share/glewlwyd/webapp/config.json COPY ["docs/docker/entrypoint.sh", "/"] ENTRYPOINT ["/usr/bin/glewlwyd", "--config-file=/etc/glewlwyd/glewlwyd-ci.conf", "-mconsole"] glewlwyd-2.6.1/LICENSE000066400000000000000000001046531415646314000144000ustar00rootroot00000000000000Copyright 2016-2020 Nicolas Mora and blessed contributors GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {one line to give the program's name and a brief idea of what it does.} Copyright (C) {year} {name of author} This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: {project} Copyright (C) {year} {fullname} This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . glewlwyd-2.6.1/Makefile000066400000000000000000000032201415646314000150170ustar00rootroot00000000000000# # Glewlwyd OAuth2 Authorization Server # # Makefile used to build the software # # Copyright 2016-2019 Nicolas Mora # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU GENERAL PUBLIC LICENSE # License as published by the Free Software Foundation; # version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this program. If not, see . # GLEWLWYD_SOURCE=./src GLEWLWYD_TESTS=./test GLEWLWYD_DOCS=./docs GLEWLWYD_DOCKER=./docs/docker all: cd $(GLEWLWYD_SOURCE) && $(MAKE) $* debug: cd $(GLEWLWYD_SOURCE) && $(MAKE) debug $* install: cd $(GLEWLWYD_SOURCE) && $(MAKE) install $* memcheck: cd $(GLEWLWYD_SOURCE) && $(MAKE) memcheck $* test-debug: cd $(GLEWLWYD_SOURCE) && $(MAKE) test-debug $* check: cd $(GLEWLWYD_TESTS) && $(MAKE) test $* clean: cd $(GLEWLWYD_SOURCE) && $(MAKE) clean cd $(GLEWLWYD_TESTS) && $(MAKE) clean cd $(GLEWLWYD_DOCKER) && $(MAKE) clean docker rmi -f babelouest/glewlwyd:src babelouest/glewlwyd:ci docker system prune -f docker: docker build -t babelouest/glewlwyd:src . docker-ci: docker build --file=Dockerfile-ci -t babelouest/glewlwyd:ci . manpage: cd $(GLEWLWYD_SOURCE) && $(MAKE) $* help2man $(GLEWLWYD_SOURCE)/glewlwyd -s 8 -n "Single-Sign-On (SSO) server with multiple factor authentication" > $(GLEWLWYD_DOCS)/glewlwyd.8 glewlwyd-2.6.1/README.md000066400000000000000000000115171415646314000146460ustar00rootroot00000000000000# Glewlwyd SSO server ![C/C++ CI](https://github.com/babelouest/glewlwyd/workflows/C/C++%20CI/badge.svg) ![CodeQL](https://github.com/babelouest/glewlwyd/workflows/CodeQL/badge.svg) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3475/badge)](https://bestpractices.coreinfrastructure.org/projects/3475) ## Single-Sign-On (SSO) server with multiple factor authentication for OAuth2 and OpenID Connect authentication **[Glewlwyd 2.6.1 is available](https://github.com/babelouest/glewlwyd/releases/latest). Feel free to [install](docs/INSTALL.md) it, test it, use it, and [send feedback](https://github.com/babelouest/glewlwyd/issues) if you feel like it!** **Go to the [online demo](https://babelouest.io/glewlwyd) to test Glewlwyd's features** ![logged in](docs/screenshots/login-nopassword.png) ## Process supported: - [OpenID Connect/OAuth2](docs/OIDC.md) - [OAuth2](docs/OAUTH2.md): Legacy plugin, it's recommended to use the OpenID Connect/OAuth2 for new installations ## User authentication via multiple factors: - [Password](https://xkcd.com/936/) - [One-time password (TOTP/HOTP)](docs/OTP.md) - [WebAuthn (Yubikey, Android and Apple fingerprint or face id, etc.)](docs/WEBAUTHN.md) - [One-time password sent via e-mail](docs/EMAIL.md) - [TLS Certificate](docs/CERTIFICATE.md) - [External OAuth2/OIDC providers](docs/OAUTH2_SCHEME.md) - [HTTP Backend service providing Basic Authentication](docs/HTTP.md) ## Users and clients can be storage backends: - [Database](docs/USER_DATABASE.md) - [LDAP service](docs/USER_LDAP.md) - [HTTP Backend service providing Basic Authentication](docs/USER_HTTP.md) (Users only) ## User registration New users can [register a new account](docs/REGISTER.md) with the possibility to confirm their e-mail address or not. During the registration process, the new user may be expected to register their passwords, as well as other authentication factors: - One-time password (TOTP/HOTP) - WebAuthn (Yubikey, Android devices) - TLS Certificate - External OAuth2/OIDC providers Existing users can update their e-mail by sending a confirmation link to the new e-mail. ## Lost credentials Existing users can reset their credentials if their password or authentication schemes are lost or unavailable. Credentials can be reset by different factors: - A link sent to the user's e-mail - A one-time use recovery code See the [register/update e-mail/reset credentials documentation](docs/REGISTER.md) for more information on the registration, update e-mail or reset credentials features. Based on a plugin architecture to make it easier to add or update storage backends, authentication schemes or process. ## Passwordless authentication Adding new authentication schemes or backend storage for users and clients is possible via the plugin architecture. ## Architecture and performance The backend API server is fully written in C and uses a small amount of resources. Its plugin architecture makes it easy to add new modules or plugins, or modify existing ones with less risks to have unmaintainable code. # Installation The full installation documentation is available in the [Install documentation](docs/INSTALL.md). ## Docker A docker image is available for tests on localhost. To test the image, run the following command: ```shell $ docker run --rm -it -p 4593:4593 babelouest/glewlwyd:latest ``` And open the address [http://localhost:4593/](http://localhost:4593/) on your browser. - User: `admin` - Password: `password` This Docker image can be used for tests or for real use by changing the configuration files. More information in the [install documentation](docs/INSTALL.md#docker). ## Getting started The [Getting started documentation](docs/GETTING_STARTED.md) will help administrators configure Glewlwyd's modules and authentication schemes. ## User documentation The [user documentation](docs/USER.md) will help Glewlwyd's users manage their profile and log in to Glewlwyd. ## Core API The full core REST API documentation is available in the [API documentation](docs/API.md) ## Plugins architecture You can update the existing plugins or add new ones depending on your needs, check out the documentation available for each type of plugin: - [User backend modules](src/user/) - [User middleware backend modules](src/user_middleware/) - [Client backend modules](src/client/) - [Authentication schemes modules](src/scheme/) - [Plugins](src/plugin/) (Register, OAuth2 or OIDC plugins) ## Screenshots Go to the [Screenshots](docs/screenshots) folder to get a visual idea of Glewlwyd. ## Questions, problems or feature requests You can open an [issue](https://github.com/babelouest/glewlwyd/issues), a [pull request](https://github.com/babelouest/ulfius/pulls) or send me an [e-mail](mailto:mail@babelouest.io). Any help is much appreciated! You can visit the IRC channel #glewlwyd on the [Libera.​Chat](https://libera.chat/) network. glewlwyd-2.6.1/_config.yml000066400000000000000000000000331415646314000155050ustar00rootroot00000000000000theme: jekyll-theme-tactileglewlwyd-2.6.1/cmake-modules/000077500000000000000000000000001415646314000161105ustar00rootroot00000000000000glewlwyd-2.6.1/cmake-modules/CMakeUninstall.cmake.in000066400000000000000000000020641415646314000223730ustar00rootroot00000000000000if (NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") endif (NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) string(REGEX REPLACE "\n" ";" files "${files}") foreach (file ${files}) message(STATUS "Uninstalling $ENV{DESTDIR}${file}") if (IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") exec_program( "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" OUTPUT_VARIABLE rm_out RETURN_VALUE rm_retval ) if (NOT "${rm_retval}" STREQUAL 0) message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") endif (NOT "${rm_retval}" STREQUAL 0) else (IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") message(STATUS "File $ENV{DESTDIR}${file} does not exist.") endif (IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") endforeach (file) glewlwyd-2.6.1/cmake-modules/CTestCustom.cmake.in000066400000000000000000000001421415646314000217310ustar00rootroot00000000000000string(REPLACE ";" " " TESTS "@TESTS@") set(CTEST_CUSTOM_PRE_TEST "@CMAKE_MAKE_PROGRAM@ ${TESTS}")glewlwyd-2.6.1/cmake-modules/DownloadProject.CMakeLists.cmake.in000066400000000000000000000012051415646314000246110ustar00rootroot00000000000000# Distributed under the OSI-approved MIT License. See accompanying # file LICENSE or https://github.com/Crascit/DownloadProject for details. cmake_minimum_required(VERSION 2.8.12) project(${DL_ARGS_PROJ}-download NONE) include(ExternalProject) ExternalProject_Add(${DL_ARGS_PROJ}-download ${DL_ARGS_UNPARSED_ARGUMENTS} SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" BINARY_DIR "${DL_ARGS_BINARY_DIR}" CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" TEST_COMMAND "" ) glewlwyd-2.6.1/cmake-modules/DownloadProject.cmake000066400000000000000000000175531415646314000222230ustar00rootroot00000000000000# Distributed under the OSI-approved MIT License. See accompanying # file LICENSE or https://github.com/Crascit/DownloadProject for details. # # MODULE: DownloadProject # # PROVIDES: # download_project( PROJ projectName # [PREFIX prefixDir] # [DOWNLOAD_DIR downloadDir] # [SOURCE_DIR srcDir] # [BINARY_DIR binDir] # [QUIET] # ... # ) # # Provides the ability to download and unpack a tarball, zip file, git repository, # etc. at configure time (i.e. when the cmake command is run). How the downloaded # and unpacked contents are used is up to the caller, but the motivating case is # to download source code which can then be included directly in the build with # add_subdirectory() after the call to download_project(). Source and build # directories are set up with this in mind. # # The PROJ argument is required. The projectName value will be used to construct # the following variables upon exit (obviously replace projectName with its actual # value): # # projectName_SOURCE_DIR # projectName_BINARY_DIR # # The SOURCE_DIR and BINARY_DIR arguments are optional and would not typically # need to be provided. They can be specified if you want the downloaded source # and build directories to be located in a specific place. The contents of # projectName_SOURCE_DIR and projectName_BINARY_DIR will be populated with the # locations used whether you provide SOURCE_DIR/BINARY_DIR or not. # # The DOWNLOAD_DIR argument does not normally need to be set. It controls the # location of the temporary CMake build used to perform the download. # # The PREFIX argument can be provided to change the base location of the default # values of DOWNLOAD_DIR, SOURCE_DIR and BINARY_DIR. If all of those three arguments # are provided, then PREFIX will have no effect. The default value for PREFIX is # CMAKE_BINARY_DIR. # # The QUIET option can be given if you do not want to show the output associated # with downloading the specified project. # # In addition to the above, any other options are passed through unmodified to # ExternalProject_Add() to perform the actual download, patch and update steps. # The following ExternalProject_Add() options are explicitly prohibited (they # are reserved for use by the download_project() command): # # CONFIGURE_COMMAND # BUILD_COMMAND # INSTALL_COMMAND # TEST_COMMAND # # Only those ExternalProject_Add() arguments which relate to downloading, patching # and updating of the project sources are intended to be used. Also note that at # least one set of download-related arguments are required. # # If using CMake 3.2 or later, the UPDATE_DISCONNECTED option can be used to # prevent a check at the remote end for changes every time CMake is run # after the first successful download. See the documentation of the ExternalProject # module for more information. It is likely you will want to use this option if it # is available to you. Note, however, that the ExternalProject implementation contains # bugs which result in incorrect handling of the UPDATE_DISCONNECTED option when # using the URL download method or when specifying a SOURCE_DIR with no download # method. Fixes for these have been created, the last of which is scheduled for # inclusion in CMake 3.8.0. Details can be found here: # # https://gitlab.kitware.com/cmake/cmake/commit/bdca68388bd57f8302d3c1d83d691034b7ffa70c # https://gitlab.kitware.com/cmake/cmake/issues/16428 # # If you experience build errors related to the update step, consider avoiding # the use of UPDATE_DISCONNECTED. # # EXAMPLE USAGE: # # include(DownloadProject) # download_project(PROJ googletest # GIT_REPOSITORY https://github.com/google/googletest.git # GIT_TAG master # UPDATE_DISCONNECTED 1 # QUIET # ) # # add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) # #======================================================================================== set(_DownloadProjectDir "${CMAKE_CURRENT_LIST_DIR}") include(CMakeParseArguments) function(download_project) set(options QUIET) set(oneValueArgs PROJ PREFIX DOWNLOAD_DIR SOURCE_DIR BINARY_DIR # Prevent the following from being passed through CONFIGURE_COMMAND BUILD_COMMAND INSTALL_COMMAND TEST_COMMAND ) set(multiValueArgs "") cmake_parse_arguments(DL_ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) # Hide output if requested if (DL_ARGS_QUIET) set(OUTPUT_QUIET "OUTPUT_QUIET") else() unset(OUTPUT_QUIET) message(STATUS "Downloading/updating ${DL_ARGS_PROJ}") endif() # Set up where we will put our temporary CMakeLists.txt file and also # the base point below which the default source and binary dirs will be. # The prefix must always be an absolute path. if (NOT DL_ARGS_PREFIX) set(DL_ARGS_PREFIX "${CMAKE_BINARY_DIR}") else() get_filename_component(DL_ARGS_PREFIX "${DL_ARGS_PREFIX}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") endif() if (NOT DL_ARGS_DOWNLOAD_DIR) set(DL_ARGS_DOWNLOAD_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-download") endif() # Ensure the caller can know where to find the source and build directories if (NOT DL_ARGS_SOURCE_DIR) set(DL_ARGS_SOURCE_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-src") endif() if (NOT DL_ARGS_BINARY_DIR) set(DL_ARGS_BINARY_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-build") endif() set(${DL_ARGS_PROJ}_SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" PARENT_SCOPE) set(${DL_ARGS_PROJ}_BINARY_DIR "${DL_ARGS_BINARY_DIR}" PARENT_SCOPE) # The way that CLion manages multiple configurations, it causes a copy of # the CMakeCache.txt to be copied across due to it not expecting there to # be a project within a project. This causes the hard-coded paths in the # cache to be copied and builds to fail. To mitigate this, we simply # remove the cache if it exists before we configure the new project. It # is safe to do so because it will be re-generated. Since this is only # executed at the configure step, it should not cause additional builds or # downloads. file(REMOVE "${DL_ARGS_DOWNLOAD_DIR}/CMakeCache.txt") # Create and build a separate CMake project to carry out the download. # If we've already previously done these steps, they will not cause # anything to be updated, so extra rebuilds of the project won't occur. # Make sure to pass through CMAKE_MAKE_PROGRAM in case the main project # has this set to something not findable on the PATH. configure_file("${_DownloadProjectDir}/DownloadProject.CMakeLists.cmake.in" "${DL_ARGS_DOWNLOAD_DIR}/CMakeLists.txt") execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" -D "CMAKE_MAKE_PROGRAM:FILE=${CMAKE_MAKE_PROGRAM}" . RESULT_VARIABLE result ${OUTPUT_QUIET} WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" ) if(result) message(FATAL_ERROR "CMake step for ${DL_ARGS_PROJ} failed: ${result}") endif() execute_process(COMMAND ${CMAKE_COMMAND} --build . RESULT_VARIABLE result ${OUTPUT_QUIET} WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" ) if(result) message(FATAL_ERROR "Build step for ${DL_ARGS_PROJ} failed: ${result}") endif() endfunction() glewlwyd-2.6.1/cmake-modules/FindCheck.cmake000066400000000000000000000050411415646314000207300ustar00rootroot00000000000000#.rst: # FindCheck # ----------- # # Find Check # # Find Check headers and libraries. # # :: # # CHECK_FOUND - True if Check found. # CHECK_INCLUDE_DIRS - Where to find check.h. # CHECK_LIBRARIES - List of libraries when using Check. # CHECK_VERSION_STRING - The version of Check found. #============================================================================= # Copyright 2018 Silvio Clecio # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; # version 2.1 of the License. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this library. If not, see . #============================================================================= # Sat Jan 20 23:33:47 -03 2018 find_package(PkgConfig QUIET) pkg_check_modules(PC_CHECK QUIET check) find_path(CHECK_INCLUDE_DIR NAMES check.h HINTS ${PC_CHECK_INCLUDEDIR} ${PC_CHECK_INCLUDE_DIRS}) find_library(CHECK_LIBRARY NAMES check libcheck HINTS ${PC_CHECK_LIBDIR} ${PC_CHECK_LIBRARY_DIRS}) if (PC_CHECK_VERSION) set(CHECK_VERSION_STRING ${PC_CHECK_VERSION}) elseif (CHECK_INCLUDE_DIR AND EXISTS "${CHECK_INCLUDE_DIR}/check.h") set(check_version_list MAJOR MINOR MICRO) foreach (v ${check_version_list}) set(regex_check_version "^#define CHECK_${v}_VERSION +\\(?([0-9]+)\\)?$") file(STRINGS "${CHECK_INCLUDE_DIR}/check.h" check_version_${v} REGEX "${regex_check_version}") string(REGEX REPLACE "${regex_check_version}" "\\1" check_version_${v} "${check_version_${v}}") unset(regex_check_version) endforeach () set(CHECK_VERSION_STRING "${check_version_MAJOR}.${check_version_MINOR}.${check_version_MICRO}") foreach (v check_version_list) unset(check_version_${v}) endforeach () unset(check_version_list) endif () include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Check REQUIRED_VARS CHECK_LIBRARY CHECK_INCLUDE_DIR VERSION_VAR CHECK_VERSION_STRING) if (CHECK_FOUND) set(CHECK_LIBRARIES ${CHECK_LIBRARY}) set(CHECK_INCLUDE_DIRS ${CHECK_INCLUDE_DIR}) endif () mark_as_advanced(CHECK_INCLUDE_DIR CHECK_LIBRARY)glewlwyd-2.6.1/cmake-modules/FindHoel.cmake000066400000000000000000000050531415646314000206050ustar00rootroot00000000000000#.rst: # FindHoel # ----------- # # Find Hoel # # Find Hoel headers and libraries. # # :: # # HOEL_FOUND - True if Hoel found. # HOEL_INCLUDE_DIRS - Where to find hoel.h. # HOEL_LIBRARIES - List of libraries when using Hoel. # HOEL_VERSION_STRING - The version of Hoel found. #============================================================================= # Copyright 2018 Nicolas Mora # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; # version 2.1 of the License. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this library. If not, see . #============================================================================= find_package(PkgConfig QUIET) pkg_check_modules(PC_HOEL QUIET libhoel) find_path(HOEL_INCLUDE_DIR NAMES hoel.h HINTS ${PC_HOEL_INCLUDEDIR} ${PC_HOEL_INCLUDE_DIRS}) find_library(HOEL_LIBRARY NAMES hoel libhoel HINTS ${PC_HOEL_LIBDIR} ${PC_HOEL_LIBRARY_DIRS}) set(HOEL_VERSION_STRING 0.0.0) if (PC_HOEL_VERSION) set(HOEL_VERSION_STRING ${PC_HOEL_VERSION}) elseif (HOEL_INCLUDE_DIR AND EXISTS "${HOEL_INCLUDE_DIR}/hoel.h") set(regex_hoel_version "^#define[ \t]+HOEL_VERSION[ \t]+([^\"]+).*") file(STRINGS "${HOEL_INCLUDE_DIR}/hoel.h" hoel_version REGEX "${regex_hoel_version}") string(REGEX REPLACE "${regex_hoel_version}" "\\1" HOEL_VERSION_STRING "${hoel_version}") unset(regex_hoel_version) unset(hoel_version) if (NOT HOEL_VERSION_STRING) set(regex_hoel_version "^#define[ \t]+HOEL_VERSION[ \t]+([^\"]+).*") file(STRINGS "${HOEL_INCLUDE_DIR}/hoel-cfg.h" hoel_version REGEX "${regex_hoel_version}") string(REGEX REPLACE "${regex_hoel_version}" "\\1" HOEL_VERSION_STRING "${hoel_version}") unset(regex_hoel_version) unset(hoel_version) endif () endif () include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Hoel REQUIRED_VARS HOEL_LIBRARY HOEL_INCLUDE_DIR VERSION_VAR HOEL_VERSION_STRING) if (HOEL_FOUND) set(HOEL_LIBRARIES ${HOEL_LIBRARY}) set(HOEL_INCLUDE_DIRS ${HOEL_INCLUDE_DIR}) endif () mark_as_advanced(HOEL_INCLUDE_DIR HOEL_LIBRARY) glewlwyd-2.6.1/cmake-modules/FindIddawc.cmake000066400000000000000000000052501415646314000211100ustar00rootroot00000000000000#.rst: # FindIddawc # ----------- # # Find Iddawc # # Find Iddawc headers and libraries. # # :: # # IDDAWC_FOUND - True if Iddawc found. # IDDAWC_INCLUDE_DIRS - Where to find iddawc.h. # IDDAWC_LIBRARIES - List of libraries when using Iddawc. # IDDAWC_VERSION_STRING - The version of Iddawc found. #============================================================================= # Copyright 2018 Nicolas Mora # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; # version 2.1 of the License. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this library. If not, see . #============================================================================= find_package(PkgConfig QUIET) pkg_check_modules(PC_IDDAWC QUIET libiddawc) find_path(IDDAWC_INCLUDE_DIR NAMES iddawc.h HINTS ${PC_IDDAWC_INCLUDEDIR} ${PC_IDDAWC_INCLUDE_DIRS}) find_library(IDDAWC_LIBRARY NAMES iddawc libiddawc HINTS ${PC_IDDAWC_LIBDIR} ${PC_IDDAWC_LIBRARY_DIRS}) set(IDDAWC_VERSION_STRING 0.0.0) if (PC_IDDAWC_VERSION) set(IDDAWC_VERSION_STRING ${PC_IDDAWC_VERSION}) elseif (IDDAWC_INCLUDE_DIR AND EXISTS "${IDDAWC_INCLUDE_DIR}/iddawc.h") set(regex_iddawc_version "^#define[ \t]+IDDAWC_VERSION[ \t]+([^\"]+).*") file(STRINGS "${IDDAWC_INCLUDE_DIR}/iddawc.h" iddawc_version REGEX "${regex_iddawc_version}") string(REGEX REPLACE "${regex_iddawc_version}" "\\1" IDDAWC_VERSION_STRING "${iddawc_version}") unset(regex_iddawc_version) unset(iddawc_version) if (NOT IDDAWC_VERSION_STRING) set(regex_iddawc_version "^#define[ \t]+IDDAWC_VERSION[ \t]+([^\"]+).*") file(STRINGS "${IDDAWC_INCLUDE_DIR}/iddawc-cfg.h" iddawc_version REGEX "${regex_iddawc_version}") string(REGEX REPLACE "${regex_iddawc_version}" "\\1" IDDAWC_VERSION_STRING "${iddawc_version}") unset(regex_iddawc_version) unset(iddawc_version) endif() endif () include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Iddawc REQUIRED_VARS IDDAWC_LIBRARY IDDAWC_INCLUDE_DIR VERSION_VAR IDDAWC_VERSION_STRING) if (IDDAWC_FOUND) set(IDDAWC_LIBRARIES ${IDDAWC_LIBRARY}) set(IDDAWC_INCLUDE_DIRS ${IDDAWC_INCLUDE_DIR}) endif () mark_as_advanced(IDDAWC_INCLUDE_DIR IDDAWC_LIBRARY) glewlwyd-2.6.1/cmake-modules/FindJansson.cmake000066400000000000000000000045071415646314000213340ustar00rootroot00000000000000#.rst: # FindJansson # ----------- # # Find Jansson # # Find Jansson headers and libraries. # # :: # # JANSSON_FOUND - True if Jansson found. # JANSSON_INCLUDE_DIRS - Where to find jansson.h. # JANSSON_LIBRARIES - List of libraries when using Jansson. # JANSSON_VERSION_STRING - The version of Jansson found. #============================================================================= # Copyright 2018 Silvio Clecio # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; # version 2.1 of the License. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this library. If not, see . #============================================================================= # Sat Jan 20 12:32:26 -03 2018 find_package(PkgConfig QUIET) pkg_check_modules(PC_JANSSON QUIET jansson) find_path(JANSSON_INCLUDE_DIR NAMES jansson.h HINTS ${PC_JANSSON_INCLUDEDIR} ${PC_JANSSON_INCLUDE_DIRS}) find_library(JANSSON_LIBRARY NAMES jansson libjansson HINTS ${PC_JANSSON_LIBDIR} ${PC_JANSSON_LIBRARY_DIRS}) if (PC_JANSSON_VERSION) set(JANSSON_VERSION_STRING ${PC_JANSSON_VERSION}) elseif (JANSSON_INCLUDE_DIR AND EXISTS "${JANSSON_INCLUDE_DIR}/jansson.h") set(regex_jansson_version "^#define[ \t]+JANSSON_VERSION[ \t]+\"([^\"]+)\".*") file(STRINGS "${JANSSON_INCLUDE_DIR}/jansson.h" jansson_version REGEX "${regex_jansson_version}") string(REGEX REPLACE "${regex_jansson_version}" "\\1" JANSSON_VERSION_STRING "${jansson_version}") unset(regex_jansson_version) unset(jansson_version) endif () include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Jansson REQUIRED_VARS JANSSON_LIBRARY JANSSON_INCLUDE_DIR VERSION_VAR JANSSON_VERSION_STRING) if (JANSSON_FOUND) set(JANSSON_LIBRARIES ${JANSSON_LIBRARY}) set(JANSSON_INCLUDE_DIRS ${JANSSON_INCLUDE_DIR}) endif () mark_as_advanced(JANSSON_INCLUDE_DIR JANSSON_LIBRARY)glewlwyd-2.6.1/cmake-modules/FindLdap.cmake000066400000000000000000000031471415646314000206000ustar00rootroot00000000000000#.rst: # FindLdap # ----------- # # Find Ldap # # Find Ldap headers and libraries. # # :: # # LDAP_FOUND - True if Ldap found. # LDAP_INCLUDE_DIRS - Where to find ldap.h. # LDAP_LIBRARIES - List of libraries when using Ldap. #============================================================================= # Copyright 2018 Nicolas Mora # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; # version 2.1 of the License. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this library. If not, see . #============================================================================= find_package(PkgConfig QUIET) pkg_check_modules(PC_LDAP QUIET ldap) find_path(LDAP_INCLUDE_DIR NAMES ldap.h HINTS ${PC_LDAP_INCLUDEDIR} ${PC_LDAP_INCLUDE_DIRS}) find_library(LDAP_LIBRARY NAMES ldap libldap HINTS ${PC_LDAP_LIBDIR} ${PC_LDAP_LIBRARY_DIRS}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Ldap REQUIRED_VARS LDAP_LIBRARY LDAP_INCLUDE_DIR) if (LDAP_FOUND) set(LDAP_LIBRARIES ${LDAP_LIBRARY}) set(LDAP_INCLUDE_DIRS ${LDAP_INCLUDE_DIR}) endif () mark_as_advanced(LDAP_INCLUDE_DIR LDAP_LIBRARY) glewlwyd-2.6.1/cmake-modules/FindLibCBOR.cmake000066400000000000000000000033001415646314000210630ustar00rootroot00000000000000#.rst: # FindLibCBOR # ----------- # # Find LibCBOR # # Find LibCBOR headers and libraries. # # :: # # LIBCBOR_FOUND - True if LibCBOR found. # LIBCBOR_INCLUDE_DIRS - Where to find cbor.h. # LIBCBOR_LIBRARIES - List of libraries when using LibCBOR. #============================================================================= # Copyright 2019 Nicolas Mora # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; # version 2.1 of the License. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this library. If not, see . #============================================================================= find_package(PkgConfig QUIET) pkg_check_modules(PC_LIBCBOR QUIET libcbor) find_path(LIBCBOR_INCLUDE_DIR NAMES cbor.h HINTS ${PC_LIBCBOR_INCLUDEDIR} ${PC_LIBCBOR_INCLUDE_DIRS}) find_library(LIBCBOR_LIBRARY NAMES cbor libcbor liblibcbor HINTS ${PC_LIBCBOR_LIBDIR} ${PC_LIBCBOR_LIBRARY_DIRS}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(LibCBOR REQUIRED_VARS LIBCBOR_LIBRARY LIBCBOR_INCLUDE_DIR) if (LIBCBOR_FOUND) set(LIBCBOR_LIBRARIES ${LIBCBOR_LIBRARY}) set(LIBCBOR_INCLUDE_DIRS ${LIBCBOR_INCLUDE_DIR}) endif () mark_as_advanced(LIBCBOR_INCLUDE_DIR LIBCBOR_LIBRARY) glewlwyd-2.6.1/cmake-modules/FindLibOath.cmake000066400000000000000000000032751415646314000212440ustar00rootroot00000000000000#.rst: # FindLibOath # ----------- # # Find LibOath # # Find LibOath headers and libraries. # # :: # # LIBOATH_FOUND - True if LibOath found. # LIBOATH_INCLUDE_DIRS - Where to find oath.h. # LIBOATH_LIBRARIES - List of libraries when using LibOath. #============================================================================= # Copyright 2018 Nicolas Mora # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; # version 2.1 of the License. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this library. If not, see . #============================================================================= find_package(PkgConfig QUIET) pkg_check_modules(PC_LIBOATH QUIET liboath) find_path(LIBOATH_INCLUDE_DIR NAMES liboath/oath.h HINTS ${PC_LIBOATH_INCLUDEDIR} ${PC_LIBOATH_INCLUDE_DIRS}) find_library(LIBOATH_LIBRARY NAMES oath liboath HINTS ${PC_LIBOATH_LIBDIR} ${PC_LIBOATH_LIBRARY_DIRS}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(LibOath REQUIRED_VARS LIBOATH_LIBRARY LIBOATH_INCLUDE_DIR) if (LIBOATH_FOUND) set(LIBOATH_LIBRARIES ${LIBOATH_LIBRARY}) set(LIBOATH_INCLUDE_DIRS ${LIBOATH_INCLUDE_DIR}) endif () mark_as_advanced(LIBOATH_INCLUDE_DIR LIBOATH_LIBRARY) glewlwyd-2.6.1/cmake-modules/FindLibconfig.cmake000066400000000000000000000033611415646314000216120ustar00rootroot00000000000000#.rst: # FindLibconfig # ----------- # # Find Libconfig # # Find Libconfig headers and libraries. # # :: # # LIBCONFIG_FOUND - True if Libconfig found. # LIBCONFIG_INCLUDE_DIRS - Where to find jwt.h. # LIBCONFIG_LIBRARIES - List of libraries when using Libconfig. #============================================================================= # Copyright 2018 Nicolas Mora # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; # version 2.1 of the License. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this library. If not, see . #============================================================================= find_package(PkgConfig QUIET) pkg_check_modules(PC_LIBCONFIG QUIET libconfig) find_path(LIBCONFIG_INCLUDE_DIR NAMES libconfig.h HINTS ${PC_LIBCONFIG_INCLUDEDIR} ${PC_LIBCONFIG_INCLUDE_DIRS}) find_library(LIBCONFIG_LIBRARY NAMES libconfig config HINTS ${PC_LIBCONFIG_LIBDIR} ${PC_LIBCONFIG_LIBRARY_DIRS}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Libconfig REQUIRED_VARS LIBCONFIG_LIBRARY LIBCONFIG_INCLUDE_DIR) if (LIBCONFIG_FOUND) set(LIBCONFIG_LIBRARIES ${LIBCONFIG_LIBRARY}) set(LIBCONFIG_INCLUDE_DIRS ${LIBCONFIG_INCLUDE_DIR}) endif () mark_as_advanced(LIBCONFIG_INCLUDE_DIR LIBCONFIG_LIBRARY) glewlwyd-2.6.1/cmake-modules/FindMHD.cmake000066400000000000000000000054101415646314000203230ustar00rootroot00000000000000#.rst: # FindMHD # ----------- # # Find libmicrohttpd # # Find libmicrohttpd headers and libraries. # # :: # # MHD_FOUND - True if libmicrohttpd found. # MHD_INCLUDE_DIRS - Where to find microhttpd.h. # MHD_LIBRARIES - List of libraries when using libmicrohttpd. # MHD_VERSION_STRING - The version of libmicrohttpd found. #============================================================================= # Copyright 2018 Nicolas Mora # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; # version 2.1 of the License. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this library. If not, see . #============================================================================= find_package(PkgConfig QUIET) pkg_check_modules(PC_MHD QUIET libmicrohttpd) find_path(MHD_INCLUDE_DIR NAMES microhttpd.h HINTS ${PC_MHD_INCLUDEDIR} ${PC_MHD_INCLUDE_DIRS}) find_library(MHD_LIBRARY NAMES libmicrohttpd microhttpd HINTS ${PC_MHD_LIBDIR} ${PC_MHD_LIBRARY_DIRS}) if (PC_MHD_VERSION) set(MHD_VERSION_STRING ${PC_MHD_VERSION}) elseif (MHD_INCLUDE_DIR AND EXISTS "${MHD_INCLUDE_DIR}/microhttpd.h") set(regex_mhd_version "^#define[ \t]+MHD_VERSION[ \t]+([^\"]+).*") file(STRINGS "${MHD_INCLUDE_DIR}/microhttpd.h" mhd_version REGEX "${regex_mhd_version}") string(REGEX REPLACE "${regex_mhd_version}" "\\1" MHD_VERSION_NUM "${mhd_version}") unset(regex_mhd_version) unset(mhd_version) # parse MHD_VERSION from numerical format 0x12345678 to string format "12.34.56.78" so the version value can be compared to the one returned by pkg-config string(SUBSTRING ${MHD_VERSION_NUM} 2 2 MHD_VERSION_STRING_MAJOR) string(SUBSTRING ${MHD_VERSION_NUM} 4 2 MHD_VERSION_STRING_MINOR) string(SUBSTRING ${MHD_VERSION_NUM} 6 2 MHD_VERSION_STRING_REVISION) string(SUBSTRING ${MHD_VERSION_NUM} 8 2 MHD_VERSION_STRING_PATCH) set(MHD_VERSION_STRING "${MHD_VERSION_STRING_MAJOR}.${MHD_VERSION_STRING_MINOR}.${MHD_VERSION_STRING_REVISION}.${MHD_VERSION_STRING_PATCH}") endif () include(FindPackageHandleStandardArgs) find_package_handle_standard_args(MHD REQUIRED_VARS MHD_LIBRARY MHD_INCLUDE_DIR VERSION_VAR MHD_VERSION_STRING) if (MHD_FOUND) set(MHD_LIBRARIES ${MHD_LIBRARY}) set(MHD_INCLUDE_DIRS ${MHD_INCLUDE_DIR}) endif () mark_as_advanced(MHD_INCLUDE_DIR MHD_LIBRARY) glewlwyd-2.6.1/cmake-modules/FindNettle.cmake000066400000000000000000000032531415646314000211510ustar00rootroot00000000000000#.rst: # FindNettle # ----------- # # Find Nettle # # Find Nettle headers and libraries. # # :: # # NETTLE_FOUND - True if Nettle found. # NETTLE_INCLUDE_DIRS - Where to find nettle.h. # NETTLE_LIBRARIES - List of libraries when using Nettle. #============================================================================= # Copyright 2019 Nicolas Mora # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; # version 2.1 of the License. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this library. If not, see . #============================================================================= find_package(PkgConfig QUIET) pkg_check_modules(PC_NETTLE QUIET nettle) find_path(NETTLE_INCLUDE_DIR NAMES nettle/version.h HINTS ${PC_NETTLE_INCLUDEDIR} ${PC_NETTLE_INCLUDE_DIRS}) find_library(NETTLE_LIBRARY NAMES nettle libnettle HINTS ${PC_NETTLE_LIBDIR} ${PC_NETTLE_LIBRARY_DIRS}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Nettle REQUIRED_VARS NETTLE_LIBRARY NETTLE_INCLUDE_DIR) if (NETTLE_FOUND) set(NETTLE_LIBRARIES ${NETTLE_LIBRARY}) set(NETTLE_INCLUDE_DIRS ${NETTLE_INCLUDE_DIR}) endif () mark_as_advanced(NETTLE_INCLUDE_DIR NETTLE_LIBRARY) glewlwyd-2.6.1/cmake-modules/FindOrcania.cmake000066400000000000000000000054351415646314000212760ustar00rootroot00000000000000#.rst: # FindOrcania # ----------- # # Find Orcania # # Find Orcania headers and libraries. # # :: # # ORCANIA_FOUND - True if Orcania found. # ORCANIA_INCLUDE_DIRS - Where to find orcania.h. # ORCANIA_LIBRARIES - List of libraries when using Orcania. # ORCANIA_VERSION_STRING - The version of Orcania found. #============================================================================= # Copyright 2018 Silvio Clecio # Copyright 2018 Nicolas Mora # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; # version 2.1 of the License. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this library. If not, see . #============================================================================= find_package(PkgConfig QUIET) pkg_check_modules(PC_ORCANIA QUIET liborcania) find_path(ORCANIA_INCLUDE_DIR NAMES orcania.h HINTS ${PC_ORCANIA_INCLUDEDIR} ${PC_ORCANIA_INCLUDE_DIRS}) find_library(ORCANIA_LIBRARY NAMES orcania liborcania HINTS ${PC_ORCANIA_LIBDIR} ${PC_ORCANIA_LIBRARY_DIRS}) set(ORCANIA_VERSION_STRING 0.0.0) if (PC_ORCANIA_VERSION) set(ORCANIA_VERSION_STRING ${PC_ORCANIA_VERSION}) elseif (ORCANIA_INCLUDE_DIR AND EXISTS "${ORCANIA_INCLUDE_DIR}/orcania.h") set(regex_orcania_version "^#define[ \t]+ORCANIA_VERSION[ \t]+([^\"]+).*") file(STRINGS "${ORCANIA_INCLUDE_DIR}/orcania.h" orcania_version REGEX "${regex_orcania_version}") string(REGEX REPLACE "${regex_orcania_version}" "\\1" ORCANIA_VERSION_STRING "${orcania_version}") unset(regex_orcania_version) unset(orcania_version) if (NOT ORCANIA_VERSION_STRING) set(regex_orcania_version "^#define[ \t]+ORCANIA_VERSION[ \t]+([^\"]+).*") file(STRINGS "${ORCANIA_INCLUDE_DIR}/orcania-cfg.h" orcania_version REGEX "${regex_orcania_version}") string(REGEX REPLACE "${regex_orcania_version}" "\\1" ORCANIA_VERSION_STRING "${orcania_version}") unset(regex_orcania_version) unset(orcania_version) endif () endif () include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Orcania REQUIRED_VARS ORCANIA_LIBRARY ORCANIA_INCLUDE_DIR VERSION_VAR ORCANIA_VERSION_STRING) if (ORCANIA_FOUND) set(ORCANIA_LIBRARIES ${ORCANIA_LIBRARY}) set(ORCANIA_INCLUDE_DIRS ${ORCANIA_INCLUDE_DIR}) endif () mark_as_advanced(ORCANIA_INCLUDE_DIR ORCANIA_LIBRARY) glewlwyd-2.6.1/cmake-modules/FindRhonabwy.cmake000066400000000000000000000054461415646314000215150ustar00rootroot00000000000000#.rst: # FindRhonabwy # ----------- # # Find Rhonabwy # # Find Rhonabwy headers and libraries. # # :: # # RHONABWY_FOUND - True if Rhonabwy found. # RHONABWY_INCLUDE_DIRS - Where to find rhonabwy.h. # RHONABWY_LIBRARIES - List of libraries when using Rhonabwy. # RHONABWY_VERSION_STRING - The version of Rhonabwy found. #============================================================================= # Copyright 2018 Nicolas Mora # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; # version 2.1 of the License. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this library. If not, see . #============================================================================= find_package(PkgConfig QUIET) pkg_check_modules(PC_RHONABWY QUIET librhonabwy) find_path(RHONABWY_INCLUDE_DIR NAMES rhonabwy.h HINTS ${PC_RHONABWY_INCLUDEDIR} ${PC_RHONABWY_INCLUDE_DIRS}) find_library(RHONABWY_LIBRARY NAMES rhonabwy librhonabwy HINTS ${PC_RHONABWY_LIBDIR} ${PC_RHONABWY_LIBRARY_DIRS}) set(RHONABWY_VERSION_STRING 0.0.0) if (PC_RHONABWY_VERSION) set(RHONABWY_VERSION_STRING ${PC_RHONABWY_VERSION}) elseif (RHONABWY_INCLUDE_DIR AND EXISTS "${RHONABWY_INCLUDE_DIR}/rhonabwy.h") set(regex_rhonabwy_version "^#define[ \t]+RHONABWY_VERSION[ \t]+([^\"]+).*") file(STRINGS "${RHONABWY_INCLUDE_DIR}/rhonabwy.h" rhonabwy_version REGEX "${regex_rhonabwy_version}") string(REGEX REPLACE "${regex_rhonabwy_version}" "\\1" RHONABWY_VERSION_STRING "${rhonabwy_version}") unset(regex_rhonabwy_version) unset(rhonabwy_version) if (NOT RHONABWY_VERSION_STRING) set(regex_rhonabwy_version "^#define[ \t]+RHONABWY_VERSION[ \t]+([^\"]+).*") file(STRINGS "${RHONABWY_INCLUDE_DIR}/rhonabwy-cfg.h" rhonabwy_version REGEX "${regex_rhonabwy_version}") string(REGEX REPLACE "${regex_rhonabwy_version}" "\\1" RHONABWY_VERSION_STRING "${rhonabwy_version}") unset(regex_rhonabwy_version) unset(rhonabwy_version) endif() endif () include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Rhonabwy REQUIRED_VARS RHONABWY_LIBRARY RHONABWY_INCLUDE_DIR VERSION_VAR RHONABWY_VERSION_STRING) if (RHONABWY_FOUND) set(RHONABWY_LIBRARIES ${RHONABWY_LIBRARY}) set(RHONABWY_INCLUDE_DIRS ${RHONABWY_INCLUDE_DIR}) endif () mark_as_advanced(RHONABWY_INCLUDE_DIR RHONABWY_LIBRARY) glewlwyd-2.6.1/cmake-modules/FindSubunit.cmake000066400000000000000000000036421415646314000213510ustar00rootroot00000000000000#.rst: # FindSubunit # ----------- # # Find Subunit # # Find Subunit headers and libraries. # # :: # # SUBUNIT_FOUND - True if Subunit found. # SUBUNIT_INCLUDE_DIRS - Where to find subunit/child.h. # SUBUNIT_LIBRARIES - List of libraries when using Subunit. # SUBUNIT_VERSION_STRING - The version of Subunit found. #============================================================================= # Copyright 2018 Silvio Clecio # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; # version 2.1 of the License. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this library. If not, see . #============================================================================= # Fri Jan 19 22:27:51 -03 2018 find_package(PkgConfig QUIET) pkg_check_modules(PC_SUBUNIT QUIET libsubunit) set(SUBUNIT_VERSION_STRING "${PC_SUBUNIT_VERSION}") find_path(SUBUNIT_INCLUDE_DIR NAMES child.h HINTS ${PC_SUBUNIT_INCLUDEDIR} ${PC_SUBUNIT_INCLUDE_DIRS} PATH_SUFFIXES subunit) find_library(SUBUNIT_LIBRARY NAMES subunit libsubunit HINTS ${PC_SUBUNIT_LIBDIR} ${PC_SUBUNIT_LIBRARY_DIRS}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Subunit REQUIRED_VARS SUBUNIT_LIBRARY SUBUNIT_INCLUDE_DIR VERSION_VAR SUBUNIT_VERSION_STRING) if (SUBUNIT_FOUND) set(SUBUNIT_LIBRARIES ${SUBUNIT_LIBRARY}) set(SUBUNIT_INCLUDE_DIRS ${SUBUNIT_INCLUDE_DIR}) endif () mark_as_advanced(SUBUNIT_INCLUDE_DIR SUBUNIT_LIBRARY)glewlwyd-2.6.1/cmake-modules/FindUlfius.cmake000066400000000000000000000052501415646314000211640ustar00rootroot00000000000000#.rst: # FindUlfius # ----------- # # Find Ulfius # # Find Ulfius headers and libraries. # # :: # # ULFIUS_FOUND - True if Ulfius found. # ULFIUS_INCLUDE_DIRS - Where to find ulfius.h. # ULFIUS_LIBRARIES - List of libraries when using Ulfius. # ULFIUS_VERSION_STRING - The version of Ulfius found. #============================================================================= # Copyright 2018 Nicolas Mora # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; # version 2.1 of the License. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this library. If not, see . #============================================================================= find_package(PkgConfig QUIET) pkg_check_modules(PC_ULFIUS QUIET libulfius) find_path(ULFIUS_INCLUDE_DIR NAMES ulfius.h HINTS ${PC_ULFIUS_INCLUDEDIR} ${PC_ULFIUS_INCLUDE_DIRS}) find_library(ULFIUS_LIBRARY NAMES ulfius libulfius HINTS ${PC_ULFIUS_LIBDIR} ${PC_ULFIUS_LIBRARY_DIRS}) set(ULFIUS_VERSION_STRING 0.0.0) if (PC_ULFIUS_VERSION) set(ULFIUS_VERSION_STRING ${PC_ULFIUS_VERSION}) elseif (ULFIUS_INCLUDE_DIR AND EXISTS "${ULFIUS_INCLUDE_DIR}/ulfius.h") set(regex_ulfius_version "^#define[ \t]+ULFIUS_VERSION[ \t]+([^\"]+).*") file(STRINGS "${ULFIUS_INCLUDE_DIR}/ulfius.h" ulfius_version REGEX "${regex_ulfius_version}") string(REGEX REPLACE "${regex_ulfius_version}" "\\1" ULFIUS_VERSION_STRING "${ulfius_version}") unset(regex_ulfius_version) unset(ulfius_version) if (NOT ULFIUS_VERSION_STRING) set(regex_ulfius_version "^#define[ \t]+ULFIUS_VERSION[ \t]+([^\"]+).*") file(STRINGS "${ULFIUS_INCLUDE_DIR}/ulfius-cfg.h" ulfius_version REGEX "${regex_ulfius_version}") string(REGEX REPLACE "${regex_ulfius_version}" "\\1" ULFIUS_VERSION_STRING "${ulfius_version}") unset(regex_ulfius_version) unset(ulfius_version) endif() endif () include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Ulfius REQUIRED_VARS ULFIUS_LIBRARY ULFIUS_INCLUDE_DIR VERSION_VAR ULFIUS_VERSION_STRING) if (ULFIUS_FOUND) set(ULFIUS_LIBRARIES ${ULFIUS_LIBRARY}) set(ULFIUS_INCLUDE_DIRS ${ULFIUS_INCLUDE_DIR}) endif () mark_as_advanced(ULFIUS_INCLUDE_DIR ULFIUS_LIBRARY) glewlwyd-2.6.1/cmake-modules/FindYder.cmake000066400000000000000000000051411415646314000206170ustar00rootroot00000000000000#.rst: # FindYder # ----------- # # Find Yder # # Find Yder headers and libraries. # # :: # # YDER_FOUND - True if Yder found. # YDER_INCLUDE_DIRS - Where to find yder.h. # YDER_LIBRARIES - List of libraries when using Yder. # YDER_VERSION_STRING - The version of Yder found. #============================================================================= # Copyright 2018 Nicolas Mora # Copyright 2018 Silvio Clecio # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; # version 2.1 of the License. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this library. If not, see . #============================================================================= find_package(PkgConfig QUIET) pkg_check_modules(PC_YDER QUIET libyder) find_path(YDER_INCLUDE_DIR NAMES yder.h HINTS ${PC_YDER_INCLUDEDIR} ${PC_YDER_INCLUDE_DIRS}) find_library(YDER_LIBRARY NAMES yder libyder HINTS ${PC_YDER_LIBDIR} ${PC_YDER_LIBRARY_DIRS}) set(YDER_VERSION_STRING 0.0.0) if (PC_YDER_VERSION) set(YDER_VERSION_STRING ${PC_YDER_VERSION}) elseif (YDER_INCLUDE_DIR AND EXISTS "${YDER_INCLUDE_DIR}/yder.h") set(regex_yder_version "^#define[ \t]+YDER_VERSION[ \t]+([^\"]+).*") file(STRINGS "${YDER_INCLUDE_DIR}/yder.h" yder_version REGEX "${regex_yder_version}") string(REGEX REPLACE "${regex_yder_version}" "\\1" YDER_VERSION_STRING "${yder_version}") unset(regex_yder_version) unset(yder_version) if (NOT YDER_VERSION_STRING) set(regex_yder_version "^#define[ \t]+YDER_VERSION[ \t]+([^\"]+).*") file(STRINGS "${YDER_INCLUDE_DIR}/yder-cfg.h" yder_version REGEX "${regex_yder_version}") string(REGEX REPLACE "${regex_yder_version}" "\\1" YDER_VERSION_STRING "${yder_version}") unset(regex_yder_version) unset(yder_version) endif () endif () include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Yder REQUIRED_VARS YDER_LIBRARY YDER_INCLUDE_DIR VERSION_VAR YDER_VERSION_STRING) if (YDER_FOUND) set(YDER_LIBRARIES ${YDER_LIBRARY}) set(YDER_INCLUDE_DIRS ${YDER_INCLUDE_DIR}) endif () mark_as_advanced(YDER_INCLUDE_DIR YDER_LIBRARY) glewlwyd-2.6.1/docs/000077500000000000000000000000001415646314000143125ustar00rootroot00000000000000glewlwyd-2.6.1/docs/API.md000066400000000000000000002316471415646314000152620ustar00rootroot00000000000000# Glewlwyd API description [![License: CC BY 4.0](https://licensebuttons.net/l/by/4.0/80x15.png)](https://creativecommons.org/licenses/by/4.0/) This document is intended to describe Glewlwyd's core API endpoints. Glewlwyd's core API endpoints are used to manage core functionalities data. - [Endpoints authentication](#endpoints-authentication) - [Prefix](#prefix) - [Content-type](#content-type) - [Error response](#error-response) - [Configuration](#configuration) - [Get server configuration](#get-server-configuration) - [Plugins and modules management](#plugins-and-modules-management) - [Get all modules available](#get-all-modules-available) - [Reload all modules](#reload-all-modules) - [Get all user module instances available](#get-all-user-module-instances-available) - [Get a user module instance](#get-a-user-module-instance) - [Add a new user module instance](#add-a-new-user-module-instance) - [Update an existing user module instance](#update-an-existing-user-module-instance) - [Delete an existing user module instance](#delete-an-existing-user-module-instance) - [Enable or disable an existing user module instance](#enable-or-disable-an-existing-user-module-instance) - [Get a user middleware module instance](#get-a-user-middleware-module-instance) - [Add a new user middleware module instance](#add-a-new-user-middleware-module-instance) - [Update an existing user middleware module instance](#update-an-existing-user-middleware-module-instance) - [Delete an existing user middleware module instance](#delete-an-existing-user-middleware-module-instance) - [Enable or disable an existing user middleware module instance](#enable-or-disable-an-existing-user-middleware-module-instance) - [Get all client module instances available](#get-all-client-module-instances-available) - [Get a client module instance](#get-a-client-module-instance) - [Add a new client module instance](#add-a-new-client-module-instance) - [Update an existing client module instance](#update-an-existing-client-module-instance) - [Delete an existing client module instance](#delete-an-existing-client-module-instance) - [Enable or disable an existing client module instance](#enable-or-disable-an-existing-client-module-instance) - [Get all user auth scheme module instances available](#get-all-user-auth-scheme-module-instances-available) - [Get a user auth scheme module instance](#get-a-user-auth-scheme-module-instance) - [Add a new user auth scheme module instance](#add-a-new-user-auth-scheme-module-instance) - [Update an existing user auth scheme module instance](#update-an-existing-user-auth-scheme-module-instance) - [Delete an existing user auth scheme module instance](#delete-an-existing-user-auth-scheme-module-instance) - [Enable or disable an existing user auth scheme module instance](#enable-or-disable-an-existing-user-auth-scheme-module-instance) - [Get all plugin module instances available](#get-all-plugin-module-instances-available) - [Get a plugin module instance](#get-a-plugin-module-instance) - [Add a new plugin module instance](#add-a-new-plugin-module-instance) - [Update an existing plugin module instance](#update-an-existing-plugin-module-instance) - [Delete an existing plugin module instance](#delete-an-existing-plugin-module-instance) - [Enable or disable an existing plugin module instance](#enable-or-disable-an-existing-plugin-module-instance) - [Users management](#users-management) - [Get a list of users available](#get-a-list-of-users-available) - [Get a user](#get-a-user) - [Add a new user](#add-a-new-user) - [Update an existing user](#update-an-existing-user) - [Delete an existing user](#delete-an-existing-user) - [Clients management](#clients-management) - [Get a list of clients available](#get-a-list-of-clients-available) - [Get a client](#get-a-client) - [Add a new client](#add-a-new-client) - [Update an existing client](#update-an-existing-client) - [Delete an existing client](#delete-an-existing-client) - [Scopes management](#scopes-management) - [Get a list of scopes available](#get-a-list-of-scopes-available) - [Get a scope](#get-a-scope) - [Add a new scope](#add-a-new-scope) - [Update an existing scope](#update-an-existing-scope) - [Delete an existing scope](#delete-an-existing-scope) - [API Keys management](#api-keys-management) - [Get a list of API keys available](#get-a-list-of-api-keys-available) - [Create a new API key](#create-a-new-api-key) - [Disable an API key](#disable-an-api-key) - [User authentication](#user-authentication) - [Authenticate a user with password](#authenticate-a-user-with-password) - [Authenticate a user with an authentication scheme](#authenticate-a-user-with-an-authentication-scheme) - [Get authorized scopes from a scope list for a user](#get-authorized-scopes-from-a-scope-list-for-a-user) - [Trigger a scheme](#trigger-a-scheme) - [Change current user with another user authenticated in this session](#change-current-user-with-another-user-authenticated-in-this-session) - [Grant scopes](#grant-scopes) - [Get list of granted scopes for a client by the user](#get-list-of-granted-scopes-for-a-client-by-the-user) - [Update granted scope for a client by a user](#update-granted-scope-for-a-client-by-a-user) - [User profile](#user-profile) - [Get list of connected profiles](#get-list-of-connected-profiles) - [Update current profile](#update-current-profile) - [Change user password for current profile](#change-user-password-for-current-profile) - [Delete current profile](#delete-current-profile) - [Get list of plugins available](#get-list-of-plugins-available) - [Get sessions for current profile](#get-sessions-for-current-profile) - [Disable a session for current profile](#disable-a-session-for-current-profile) - [Get list of schemes available](#get-list-of-schemes-available) - [Register an auth scheme for current profile](#register-an-auth-scheme-for-current-profile) - [Get registration on an auth scheme for current profile](#get-registration-on-an-auth-scheme-for-current-profile) - [Profile delegation](#profile-delegation) - [Update profile by delegation](#update-profile-by-delegation) - [Get sessions for profile by delegation](#get-sessions-for-profile-by-delegation) - [Disable a session for a profile by delegation](#disable-a-session-for-a-profile-by-delegation) - [Get list of plugins available by delegation](#get-list-of-plugins-available-by-delegation) - [Get list of schemes available by delegation](#get-list-of-schemes-available-by-delegation) - [Register an auth scheme for a profile by delegation](#register-an-auth-scheme-for-a-profile-by-delegation) - [Get registration on an auth scheme for a profile by delegation](#get-registration-on-an-auth-scheme-for-a-profile-by-delegation) - [Authentication Scheme APIs](#authentication-scheme-apis) - [Mock authentication scheme](#mock-authentication-scheme) - [E-mail OTP authentication scheme](#e-mail-otp-authentication-scheme) - [HOTP/TOTP authentication scheme](#hotptotp-authentication-scheme) - [Password authentication scheme](#password-authentication-scheme) - [WebAuthn authentication scheme](#webauthn-authentication-scheme) - [TLS Certificate authentication scheme](#tls-certificate-authentication-scheme) ## Endpoints authentication All the endpoints require proper authentication to provide their service. The authentication method used is a session cookie. For each endpoint, the scope required will be defined in the `Security` paragraph. The admin scope name is `g_admin`, the profile scope name is `g_profile`, but these values can be changed in the configuration file. ## Prefix All URLs are based on the prefix you will setup. In this document, all endpoints will assume they use the prefix `/api`. ## Content-type All request and response body use `application/json` content-type. ## Error response The HTTP status codes used are the following: - 200 OK: no error - 400 Invalid parameters: The user has sent invalid data. The details of all the errors may be present in the response body - 401 Unauthorized: The user isn't authorized - 403 Forbidden: The user isn't allowed - 404 Not found: The specified resource doesn't exist - 500 Server error: An error occurred on the server ## Configuration ### Get server configuration This endpoint is the only one accessible directly from the root of the server. #### URL `/config` #### Method `GET` #### Success response Code 200 Content ```javascript { "api_prefix": string, prefix to access the APIs "admin_scope": string, name of the admin scope "profile_scope": string, name of the profile scope "delete_profile": string, "yes" if the user is allowed to delete its profile, "no" otherwise } ``` Example ```javascript { "api_prefix":"api", "admin_scope":"g_admin", "profile_scope":"g_profile", "delete_profile": "no" } ``` ## Plugins and modules management ### Get all modules available Return the list of all modules available for all types of modules #### URL `/api/mod/type/` #### Method `GET` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters #### Success response Code 200 Content ```javascript { user: [ name: string, display_name: string description: string ], client: [ name: string, display_name: string description: string ], scheme: [ name: string, display_name: string description: string ], plugin: [ name: string, display_name: string description: string ] } ``` Example ```javascript { "user":[ { "name":"http", "display_name":"HTTP auth backend user module", "description":"Module to store users in the database" }, { "name":"mock", "display_name":"Mock user module", "description":"Mock user module for glewlwyd tests" } ], "client":[ { "name":"mock", "display_name":"Mock scheme module", "description":"Mock scheme module for glewlwyd tests" }, { "name":"database", "display_name":"Database backend client module", "description":"Module to store clients in the database" } ], "scheme":[ { "name":"mock", "display_name":"Mock", "description":"Mock scheme module for glewlwyd tests" }, { "name":"retype-password", "display_name":"Short session password", "description":"Glewlwyd authentification via user password with a short session duration" } ], "plugin":[ { "name":"mock", "display_name":"Mock plugin", "description":"Mock plugin description" }, { "name":"oauth2-glewlwyd", "display_name":"Glewlwyd OAuth2 plugin", "description":"Plugin for legacy Glewlwyd OAuth2 workflow" } ] } ``` ### Reload all modules Reload all the modules and instances, useful if you have multiple Glewlwyd instances connected to the same database #### URL `/api/mod/reload/` #### Method `PUT` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters #### Success response Code 200 ### Get all user module instances available Return the list of all instances available for user modules #### URL `/api/mod/user/` #### Method `GET` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters #### Success response Code 200 Content ```javascript [{ module: string, name of the module name: string, name of the instance display_name: string parameters: object, parameters used for the initialization of this instance order_rank: number readonly: boolean enabled: boolean }] ``` Example ```javascript [ { "module":"mock", "name":"mock", "display_name":"Mock user module", "order_rank":0, "parameters":{ "username-prefix":"", "password":"password" }, "readonly":false, "enabled":true } ] ``` ### Get a user module instance Return the details of a user module instance #### URL `/api/mod/user/{name}` #### Method `GET` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `name`: name of the instance #### Success response Code 200 Content ```javascript { module: string, name of the module name: string, name of the instance display_name: string parameters: object, parameters used for the initialization of this instance order_rank: number readonly: boolean enabled: boolean } ``` Example ```javascript { "module":"mock", "name":"mock", "display_name":"Mock user module", "order_rank":0, "parameters":{ "username-prefix":"", "password":"password" }, "readonly":false, "enabled":true } ``` Code 404 Module not found ### Add a new user module instance Add a new user module instance #### URL `/api/mod/user/` #### Method `POST` #### Security User with scope `g_admin` authorized or valid API key header. #### Body Parameters ```javascript { module: string, name of the module, must be an existing user module available name: string, name of the instance, maximum 128 characters display_name: string, long name of the instance, maximum 256 characters parameters: object, parameters used for the initialization of this instance order_rank: number, priority of this instance to get a user readonly: boolean, set to true if the instance is in read only mode } ``` #### Success response Code 200 Instance added Code 400 Error input parameters Content A JSON array with the error messages ### Update an existing user module instance #### URL `/api/mod/user/{name}` #### Method `PUT` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `name`: name of the instance #### Body Parameters ```javascript { display_name: string, long name of the instance, maximum 256 characters parameters: object, parameters used for the initialization of this instance order_rank: number, priority of this instance to get a user readonly: boolean, set to true if the instance is in read only mode } ``` #### Success response Code 200 Instance updated Code 404 Instance not found Code 400 Error input parameters Content A JSON array with the error messages ### Delete an existing user module instance #### URL `/api/mod/user/{name}` #### Method `DELETE` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `name`: name of the instance #### Success response Code 200 Instance removed Code 404 Instance not found ### Enable or disable an existing user module instance #### URL `/api/mod/user/{name}/{action}` #### Method `PUT` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `name`: name of the instance `action`: either `enable` or `disable` or `reset` #### Success response Code 200 Action executed Code 404 Instance not found ### Get all user middleware module instances available Return the list of all instances available for user middleware modules #### URL `/api/mod/user_middleware/` #### Method `GET` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters #### Success response Code 200 Content ```javascript [{ module: string, name of the module name: string, name of the instance display_name: string parameters: object, parameters used for the initialization of this instance order_rank: number enabled: boolean }] ``` Example ```javascript [ { "module":"mock", "name":"mock", "display_name":"Mock user middleware module", "order_rank":0, "parameters":{ "username-prefix":"", "password":"password" }, "enabled":true } ] ``` ### Get a user middleware module instance Return the details of a user middleware module instance #### URL `/api/mod/user_middleware/{name}` #### Method `GET` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `name`: name of the instance #### Success response Code 200 Content ```javascript { module: string, name of the module name: string, name of the instance display_name: string parameters: object, parameters used for the initialization of this instance order_rank: number enabled: boolean } ``` Example ```javascript { "module":"mock", "name":"mock", "display_name":"Mock user module", "order_rank":0, "parameters":{ "username-prefix":"", "password":"password" }, "enabled":true } ``` Code 404 Module not found ### Add a new user middleware module instance Add a new user middleware module instance #### URL `/api/mod/user_middleware/` #### Method `POST` #### Security User with scope `g_admin` authorized or valid API key header. #### Body Parameters ```javascript { module: string, name of the module, must be an existing user module available name: string, name of the instance, maximum 128 characters display_name: string, long name of the instance, maximum 256 characters parameters: object, parameters used for the initialization of this instance order_rank: number, priority of this instance to get a user } ``` #### Success response Code 200 Instance added Code 400 Error input parameters Content A JSON array with the error messages ### Update an existing user middleware module instance #### URL `/api/mod/user_middleware/{name}` #### Method `PUT` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `name`: name of the instance #### Body Parameters ```javascript { display_name: string, long name of the instance, maximum 256 characters parameters: object, parameters used for the initialization of this instance order_rank: number, priority of this instance to get a user } ``` #### Success response Code 200 Instance updated Code 404 Instance not found Code 400 Error input parameters Content A JSON array with the error messages ### Delete an existing user middleware module instance #### URL `/api/mod/user_middleware/{name}` #### Method `DELETE` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `name`: name of the instance #### Success response Code 200 Instance removed Code 404 Instance not found ### Enable or disable an existing user middleware module instance #### URL `/api/mod/user_middleware/{name}/{action}` #### Method `PUT` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `name`: name of the instance `action`: either `enable` or `disable` or `reset` #### Success response Code 200 Action executed Code 404 Instance not found ### Get all client module instances available Return the list of all instances available for client modules #### URL `/api/mod/client/` #### Method `GET` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters #### Success response Code 200 Content ```javascript [{ module: string, name of the module name: string, name of the instance display_name: string parameters: object, parameters used for the initialization of this instance order_rank: number readonly: boolean enabled: boolean }] ``` Example ```javascript [ { "module":"mock", "name":"mock", "display_name":"Mock client module", "order_rank":0, "parameters":{ "username-prefix":"", "password":"password" }, "readonly":false, "enabled":true } ] ``` ### Get a client module instance Return the details of a client module instance #### URL `/api/mod/client/{name}` #### Method `GET` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `name`: name of the instance #### Success response Code 200 Content ```javascript { module: string, name of the module name: string, name of the instance display_name: string parameters: object, parameters used for the initialization of this instance order_rank: number readonly: boolean enabled: boolean } ``` Example ```javascript { "module":"mock", "name":"mock", "display_name":"Mock client module", "order_rank":0, "parameters":{ "username-prefix":"", "password":"password" }, "readonly":false, "enabled":true } ``` Code 404 Module not found ### Add a new client module instance Add a new client module instance #### URL `/api/mod/client/` #### Method `POST` #### Security User with scope `g_admin` authorized or valid API key header. #### Body Parameters ```javascript { module: string, name of the module, must be an existing client module available name: string, name of the instance, maximum 128 characters display_name: string, long name of the instance, maximum 256 characters parameters: object, parameters used for the initialization of this instance order_rank: number, priority of this instance to get a client readonly: boolean, set to true if the instance is in read only mode } ``` #### Success response Code 200 Instance added Code 400 Error input parameters Content A JSON array with the error messages ### Update an existing client module instance #### URL `/api/mod/client/{name}` #### Method `PUT` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `name`: name of the instance #### Body Parameters ```javascript { display_name: string, long name of the instance, maximum 256 characters parameters: object, parameters used for the initialization of this instance order_rank: number, priority of this instance to get a client readonly: boolean, set to true if the instance is in read only mode } ``` #### Success response Code 200 Instance updated Code 404 Instance not found Code 400 Error input parameters Content A JSON array with the error messages ### Delete an existing client module instance #### URL `/api/mod/client/{name}` #### Method `DELETE` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `name`: name of the instance #### Success response Code 200 Instance removed Code 404 Instance not found ### Enable or disable an existing client module instance #### URL `/api/mod/client/{name}/{action}` #### Method `PUT` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `name`: name of the instance `action`: either `enable` or `disable` or `reset` #### Success response Code 200 Action executed Code 404 Instance not found ### Get all user auth scheme module instances available Return the list of all instances available for user auth scheme modules #### URL `/api/mod/scheme/` #### Method `GET` #### Security user with scope `g_admin` authorized. #### URL Parameters #### Success response Code 200 Content ```javascript [{ module: string, name of the module name: string, name of the instance display_name: string parameters: object, parameters used for the initialization of this instance enabled: boolean }] ``` Example ```javascript [ { "module":"mock", "name":"mock_scheme_42", "display_name":"Mock 42", "expiration":600, "max_use":0, "parameters":{ "mock-value":"42" }, "allow_user_register":true, "enabled":true } ] ``` ### Get a user auth scheme module instance Return the details of a user auth scheme module instance #### URL `/api/mod/scheme/{name}` #### Method `GET` #### Security user with scope `g_admin` authorized. #### URL Parameters `name`: name of the instance #### Success response Code 200 Content ```javascript { module: string, name of the module name: string, name of the instance display_name: string parameters: object, parameters used for the initialization of this instance enabled: boolean } ``` Example ```javascript { "module":"mock", "name":"mock_scheme_42", "display_name":"Mock 42", "expiration":600, "max_use":0, "parameters":{ "mock-value":"42" }, "allow_user_register":true, "enabled":true } ``` Code 404 Module not found ### Add a new user auth scheme module instance Add a new user auth scheme module instance #### URL `/api/mod/scheme/` #### Method `POST` #### Security user with scope `g_admin` authorized. #### Body Parameters ```javascript { module: string, name of the module, must be an existing user auth scheme module available name: string, name of the instance, maximum 128 characters display_name: string, long name of the instance, maximum 256 characters duration: number, duration of the scheme authentication in seconds max_use: number, maximum use of the scheme authentication per session parameters: object, parameters used for the initialization of this instance } ``` #### Success response Code 200 Instance added Code 400 Error input parameters Content A JSON array with the error messages ### Update an existing user auth scheme module instance #### URL `/api/mod/scheme/{name}` #### Method `PUT` #### Security user with scope `g_admin` authorized. #### URL Parameters `name`: name of the instance #### Body Parameters ```javascript { display_name: string, long name of the instance, maximum 256 characters duration: number, duration of the scheme authentication in seconds max_use: number, maximum use of the scheme authentication per session parameters: object, parameters used for the initialization of this instance } ``` #### Success response Code 200 Instance updated Code 404 Instance not found Code 400 Error input parameters Content A JSON array with the error messages ### Delete an existing user auth scheme module instance #### URL `/api/mod/scheme/{name}` #### Method `DELETE` #### Security user with scope `g_admin` authorized. #### URL Parameters `name`: name of the instance #### Success response Code 200 Instance removed Code 404 Instance not found ### Enable or disable an existing user auth scheme module instance #### URL `/api/mod/scheme/{name}/{action}` #### Method `PUT` #### Security user with scope `g_admin` authorized. #### URL Parameters `name`: name of the instance `action`: either `enable` or `disable` or `reset` #### Success response Code 200 Action executed Code 404 Instance not found ### Get all plugin module instances available Return the list of all instances available for plugin modules #### URL `/api/mod/plugin/` #### Method `GET` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters #### Success response Code 200 Content ```javascript [{ module: string, name of the module name: string, name of the instance display_name: string parameters: object, parameters used for the initialization of this instance order_rank: number readonly: boolean enabled: boolean }] ``` Example ```javascript [ { "module":"oauth2-glewlwyd", "name":"glwd", "display_name":"OAuth2 Glewlwyd plugin", "parameters":{ "jwt-type":"sha", "jwt-key-size":"256", "key":"secret", "access-token-duration":3600, "refresh-token-duration":1209600, "code-duration":600, "refresh-token-rolling":true, "auth-type-code-enabled":true, "auth-type-implicit-enabled":true, "auth-type-password-enabled":true, "auth-type-client-enabled":true, "auth-type-refresh-enabled":true, "scope":[ { "name":"g_profile", "refresh-token-rolling":true }, { "name":"scope1", "refresh-token-rolling":true }, { "name":"scope2", "refresh-token-rolling":false, "refresh-token-duration":7200 } ] }, "enabled":true } ] ``` ### Get a plugin module instance Return the details of a plugin module instance #### URL `/api/mod/plugin/{name}` #### Method `GET` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `name`: name of the instance #### Success response Code 200 Content ```javascript { module: string, name of the module name: string, name of the instance display_name: string parameters: object, parameters used for the initialization of this instance order_rank: number readonly: boolean enabled: boolean } ``` Example ```javascript { "module":"oauth2-glewlwyd", "name":"glwd", "display_name":"OAuth2 Glewlwyd plugin", "parameters":{ "jwt-type":"sha", "jwt-key-size":"256", "key":"secret", "access-token-duration":3600, "refresh-token-duration":1209600, "code-duration":600, "refresh-token-rolling":true, "auth-type-code-enabled":true, "auth-type-implicit-enabled":true, "auth-type-password-enabled":true, "auth-type-client-enabled":true, "auth-type-refresh-enabled":true, "scope":[ { "name":"g_profile", "refresh-token-rolling":true }, { "name":"scope1", "refresh-token-rolling":true }, { "name":"scope2", "refresh-token-rolling":false, "refresh-token-duration":7200 } ] }, "enabled":true } ``` Code 404 Module not found ### Add a new plugin module instance Add a new plugin module instance #### URL `/api/mod/plugin/` #### Method `POST` #### Security User with scope `g_admin` authorized or valid API key header. #### Body Parameters ```javascript { module: string, name of the module, must be an existing plugin module available name: string, name of the instance, maximum 128 characters display_name: string, long name of the instance, maximum 256 characters parameters: object, parameters used for the initialization of this instance order_rank: number, priority of this instance to get a plugin readonly: boolean, set to true if the instance is in read only mode } ``` #### Success response Code 200 Instance added Code 400 Error input parameters Content A JSON array with the error messages ### Update an existing plugin module instance #### URL `/api/mod/plugin/{name}` #### Method `PUT` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `name`: name of the instance #### Body Parameters ```javascript { display_name: string, long name of the instance, maximum 256 characters parameters: object, parameters used for the initialization of this instance order_rank: number, priority of this instance to get a plugin readonly: boolean, set to true if the instance is in read only mode } ``` #### Success response Code 200 Instance updated Code 404 Instance not found Code 400 Error input parameters Content A JSON array with the error messages ### Delete an existing plugin module instance #### URL `/api/mod/plugin/{name}` #### Method `DELETE` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `name`: name of the instance #### Success response Code 200 Instance removed Code 404 Instance not found ### Enable or disable an existing plugin module instance #### URL `/api/mod/plugin/{name}/{action}` #### Method `PUT` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `name`: name of the instance `action`: either `enable` or `disable` or `reset` #### Success response Code 200 Action executed Code 404 Instance not found ## Users management ### Get a list of users available Return a list of users available #### URL `/api/user/` #### Method `GET` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `offset`: number, the offset to start the list, default 0 `limit`: number, the maximal number of elements in the list, default 100 `source`: string, the instance name to limit the result, if not set, all instances will be used `pattern`: string, the pattern to filter the result #### Success response Code 200 Content ```javascript [{ username: string, mandatory scope:[ scope_value: string, mandatory, array can be empty ], enabled: boolean, mandatory name: string, optional email: string, optional other values: string or array of strings, optional, depends on what's returned by the module instance }] ``` Example ```javascript [ { username: "user1", scope: [ "g_profile", "scope1", "scope2" ], enabled: true, name: "Dave Lopper", email: "user1@glewlwyd", alias: [ "dev", "plop" ] }, { username: "user2", scope: [ "g_profile" ], enabled: true, name: "Dave Lopper 2", email: "user2@glewlwyd" } ] ``` ### Get a user Return the details of a plugin module instance #### URL `/api/user/{username}` #### Method `GET` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `username`: username to return, mandatory `source`: user module instance to look for the user, optional, if not set look on all instances #### Success response Code 200 Content ```javascript { username: string, mandatory scope:[ scope_value: string, mandatory, array can be empty ], enabled: boolean, mandatory name: string, optional email: string, optional other values: string or array of strings, optional, depends on what's returned by the module instance } ``` Example ```javascript { username: "user1", scope: [ "g_profile", "scope1", "scope2" ], enabled: true, name: "Dave Lopper", email: "user1@glewlwyd", alias: [ "dev", "plop" ] } ``` Code 404 User not found ### Add a new user #### URL `/api/user/` #### Method `POST` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `source`: user module instance to look for the user, optional, if not set, the first instance in write mode in order rank will host the new user #### Body Parameters ```javascript { username: string, mandatory scope:[ scope_value: string, mandatory, array can be empty ], password: string, optional, if not set, the user won't be able to authenticate enabled: boolean, optional, default true name: string, optional email: string, optional other values: string or array of strings, optional, depends on the module instance } ``` #### Success response Code 200 User added Code 400 Error input parameters Content A JSON array with the error messages ### Update an existing user #### URL `/api/user/{username}` #### Method `PUT` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `username`: username of the user to update `source`: user module instance to look for the user, optional, if not set look on all instances #### Body Parameters ```javascript { username: string, mandatory scope:[ scope_value: string, mandatory, array can be empty ], enabled: boolean, optional, default true name: string, optional email: string, optional other values: string or array of strings, optional, depends on the module instance } ``` #### Success response Code 200 User updated Code 404 User not found Code 400 Error input parameters Content A JSON array with the error messages ### Delete an existing user #### URL `/api/user/{username}` #### Method `DELETE` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `username`: username of the user to delete #### Success response Code 200 User removed Code 404 User not found ## Clients management ### Get a list of clients available Return a list of clients available #### URL `/api/client/` #### Method `GET` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `offset`: number, the offset to start the list, default 0 `limit`: number, the maximal number of elements in the list, default 100 `source`: string, the instance name to limit the result, if not set, all instances will be used `pattern`: string, the pattern to filter the result #### Success response Code 200 Content ```javascript [{ client_id: string, mandatory scope:[ scope_value: string, mandatory, array can be empty ], enabled: boolean, mandatory name: string, optional description: string, optional confidential: boolean, optional other values: string or array of strings, optional, depends on what's returned by the module instance }] ``` Example ```javascript [ { client_id: "client1", scope: [ "scope1", "scope2" ], enabled: true, name: "First client", confidential: true, redirect_uri: [ "http://example.com/" ] }, { client_id: "client2", scope: [ "scope1" ], enabled: true, name: "Second client", confidential: false, redirect_uri: [ "http://another.example.com" ] } ] ``` ### Get a client Return the details of a plugin module instance #### URL `/api/client/{client_id}` #### Method `GET` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `client_id`: client_id to return, mandatory `source`: client module instance to look for the client, optional, if not set look on all instances #### Success response Code 200 Content ```javascript { client_id: string, mandatory scope:[ scope_value: string, mandatory, array can be empty ], enabled: boolean, mandatory name: string, optional description: string, optional confidential: boolean, optional other values: string or array of strings, optional, depends on what's returned by the module instance } ``` Example ```javascript { client_id: "client1", scope: [ "scope1", "scope2" ], enabled: true, name: "First client", confidential: true, redirect_uri: [ "http://example.com/" ] } ``` Code 404 Client not found ### Add a new client #### URL `/api/client/` #### Method `POST` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `source`: client module instance to look for the client, optional, if not set, the first instance in write mode in order rank will host the new client #### Body Parameters ```javascript { client_id: string, mandatory scope:[ scope_value: string, mandatory, array can be empty ], password: string, optional, if not set, the client won't be able to authenticate enabled: boolean, optional, default true name: string, optional description: string, optional other values: string or array of strings, optional, depends on the module instance } ``` #### Success response Code 200 Client added Code 400 Error input parameters Content A JSON array with the error messages ### Update an existing client #### URL `/api/client/{client_id}` #### Method `PUT` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `client_id`: client_id of the client to update `source`: client module instance to look for the client, optional, if not set look on all instances #### Body Parameters ```javascript { client_id: string, mandatory scope:[ scope_value: string, mandatory, array can be empty ], password: string, optional, if not set, the client won't be able to authenticate enabled: boolean, optional, default true name: string, optional description: string, optional other values: string or array of strings, optional, depends on the module instance } ``` #### Success response Code 200 Client updated Code 404 Client not found Code 400 Error input parameters Content A JSON array with the error messages ### Delete an existing client #### URL `/api/client/{client_id}` #### Method `DELETE` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `client_id`: client_id of the client to delete #### Success response Code 200 Client removed Code 404 Client not found ## Scopes management ### Get a list of scopes available Return a list of scopes available #### URL `/api/scope/` #### Method `GET` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `offset`: number, the offset to start the list, default 0 `limit`: number, the maximal number of elements in the list, default 100, if limit is explicitly set to 0, no limit `source`: string, the instance name to limit the result, if not set, all instances will be used `pattern`: string, the pattern to filter the result #### Success response Code 200 Content ```javascript [{ name: string, mandatory display_name: string, mandatory description: string, mandatory password_required: boolean, mandatory scheme: { group_name: [ { scheme_type: module type, string, mandatory scheme_name: module name, string, mandatory scheme_display_name: module display name, string, mandatory } ] } }] ``` Example ```javascript [ { name: "scope1", display_name: "First scope", description: "The first scope", password_required: true, scheme: { group1: [ { scheme_type: "mock", scheme_name: "mock1", scheme_display_name: "First mock scheme" }, { scheme_type: "mock", scheme_name: "mock2", scheme_display_name: "Second mock scheme" } ] } }, { name: "scope2", scope: [ "scope1" ], enabled: true, name: "Second scope", confidential: false, redirect_uri: [ "http://another.example.com" ] } ] ``` ### Get a scope Return the details of a plugin module instance #### URL `/api/scope/{name}` #### Method `GET` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `name`: name to return, mandatory `source`: scope module instance to look for the scope, optional, if not set look on all instances #### Success response Code 200 Content ```javascript { name: string, mandatory scope:[ scope_value: string, mandatory, array can be empty ], enabled: boolean, mandatory name: string, optional description: string, optional confidential: boolean, optional other values: string or array of strings, optional, depends on what's returned by the module instance } ``` Example ```javascript { name: "scope1", scope: [ "scope1", "scope2" ], enabled: true, name: "First scope", confidential: true, redirect_uri: [ "http://example.com/" ] } ``` Code 404 Scope not found ### Add a new scope #### URL `/api/scope/` #### Method `POST` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `source`: scope module instance to look for the scope, optional, if not set, the first instance in write mode in order rank will host the new scope #### Body Parameters ```javascript { name: string, mandatory scope:[ scope_value: string, mandatory, array can be empty ], password: string, optional, if not set, the scope won't be able to authenticate enabled: boolean, optional, default true name: string, optional description: string, optional other values: string or array of strings, optional, depends on the module instance } ``` #### Success response Code 200 Scope added Code 400 Error input parameters Content A JSON array with the error messages ### Update an existing scope #### URL `/api/scope/{name}` #### Method `PUT` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `name`: name of the scope to update `source`: scope module instance to look for the scope, optional, if not set look on all instances #### Body Parameters ```javascript { name: string, mandatory scope:[ scope_value: string, mandatory, array can be empty ], password: string, optional, if not set, the scope won't be able to authenticate enabled: boolean, optional, default true name: string, optional description: string, optional other values: string or array of strings, optional, depends on the module instance } ``` #### Success response Code 200 Scope updated Code 404 Scope not found Code 400 Error input parameters Content A JSON array with the error messages ### Delete an existing scope #### URL `/api/scope/{name}` #### Method `DELETE` #### Security User with scope `g_admin` authorized or valid API key header. #### URL Parameters `name`: name of the scope to delete #### Success response Code 200 Scope removed Code 404 Scope not found ## API Keys management ### Get a list of API keys available Return a list of API keys available #### URL `/api/key/` #### Method `GET` #### Security User with scope `g_admin` authorized. #### URL Parameters `offset`: number, the offset to start the list, default 0 `limit`: number, the maximal number of elements in the list, default 100, if limit is explicitly set to 0, no limit `source`: string, the instance name to limit the result, if not set, all instances will be used `pattern`: string, the pattern to filter the result #### Success response Code 200 Content ```javascript [{ token_hash: string, mandatory counter: integer, mandatory username: string, mandatory issued_at: integer, mandatory issued_for: string, mandatory user_agent: string, mandatory enabled: boolean, mandatory }] ``` Example ```javascript [ { "token_hash":"{SHA512}SDb_4UvKcD_4[...]9xSVRunzcJg==", "counter":42, "username":"admin", "issued_at":735700882, "issued_for":"1.2.3.4", "user_agent":"NCSA_Mosaic/2.0 (Windows 3.1)","enabled":true }, { "token_hash":"{SHA512}BOw-ad5HskG11[...]N90_LUcuDnkxdRI5TN_w==", "counter":15, "username":"operator", "issued_at":1466571360, "issued_for":"4.3.2.1", "user_agent":"Lynx/2.8.9rel.1 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/3.6.5", "enabled":false }, { "token_hash":"{SHA512}_pkJyrClgz66[...]aIgWl8X8L4r0DwG3A==", "counter":0, "username":"admin", "issued_at":1600774400, "issued_for":"6.6.6.6", "user_agent":"curl/7.20.0 (x86_64-redhat-linux-gnu) libcurl/7.20.0 OpenSSL/0.9.8b zlib/1.2.3 libidn/0.6.5", "enabled":true } ] ``` ### Create a new API key #### URL `/api/key/` #### Method `POST` #### Security User with scope `g_admin` authorized. #### Success response Code 200 Content ```javascript { key: string, mandatory } ``` Example ```javascript { key: "abcdEFGH1234" } ``` ### Disable an API key #### URL `/api/key/{token_hash}` #### Method `DELETE` #### URL Parameters `token_hash`: identifier of the token to disable #### Security User with scope `g_admin` authorized. #### Success response Code 200 ## User authentication ### Authenticate a user with password #### URL `/api/auth` #### Method `POST` #### Body Parameters ```javascript { username: string, mandatory password: string, mandatory } ``` #### Success response Code 200 User authenticated. A session cookie is sent to the browser. This cookie will be used by all endpoints that require authentication. Code 400 Error input parameters Code 401 Authentication failure ### Authenticate a user with an authentication scheme #### URL `/api/auth` #### Method `POST` #### Body Parameters ```javascript { username: string, optional for schemes mock, OAuth2 and TLS certificate with scheme storage option, mandatory otherwise scheme_type: string, mandatory scheme_name: string: mandatory value: object, mandatory, content depends on the scheme } ``` #### Success response Code 200 User authenticated A session cookie is sent to the browser. This cookie will be used by all endpoints that require authentication. Code 400 Error input parameters Code 401 Authentication failure ### Get authorized scopes from a scope list for a user #### URL `/auth/scheme/` #### Method `GET` #### Security Valid user session. #### URL Parameters `scope`: list of scopes requested, separated by spaces #### Success response Code 200 Content ```javascript { "scope_name": { // Name of the scope "password_required": boolean, wether the password is required to access this scope "schemes":{ "group_name": [ { "scheme_type": string, module name of the scheme "scheme_name": string, module instance name "scheme_display_name": string, mudule instance display name "scheme_authenticated": boolean, wether this scheme is authenticated for this user on this session "scheme_registered": boolean wether this scheme is registered for this user } ] }, "display_name": string, display name of the scope "description":string, description of the scope "password_authenticated": boolean, wether this scope is authenticated for this user on this session "available": boolean, wether this scope is available for this user } } ``` Code 400 Error input parameters Code 401 Authentication failure ### Trigger a scheme #### URL `/api/auth/scheme/trigger` #### Method `POST` #### Body Parameters ```javascript { username: string, optional for schemes mock, OAuth2 and TLS certificate with scheme storage option, mandatory otherwise scheme_type: string, mandatory scheme_name: string: mandatory value: object, mandatory, content depends on the scheme } ``` #### Success response Code 200 User authenticated Code 400 Error input parameters Code 401 Authentication failure ### Change current user with another user authenticated in this session #### URL `/api/auth` #### Method `POST` #### Body Parameters ```javascript { username: string, mandatory } ``` #### Success response Code 200 Current user changed Code 400 Error input parameters Code 401 Authentication failure ## Grant scopes ### Get list of granted scopes for a client by the user #### URL `/api/auth/grant/{client_id}/{scope_list}` #### Method `GET` #### Security User with scope `g_profile` authorized. #### URL Parameters `client_id`: client_id of the client `scope_list`: list of scopes separated by a space #### Success response Code 200 Content ```javascript { "client":{ "client_id": string, client_id "name": string, client name }, "scope":[ { "name": string, scope name "display_name": string, scope display name "description": string, scope description "password_required": boolean, wether this scope has password required to login "granted": boolean, wether this scope is granted to this client by this user } ] } ``` Code 401 No enabled authenticated user for this session ### Update granted scope for a client by a user #### URL `/auth/grant/{client_id}/` #### Method `PUT` #### Security User with scope `g_profile` authorized. #### Body Parameters ```javascript { "scope": string, scope list granted to this client separated by a comma, set the list empty to remove all grant } ``` #### Success response Code 200 User updated Code 400 Error input parameters Content A JSON array with the error messages ## User profile ### Get list of connected profiles The first element in the returned array is the current user #### URL `/api/profile_list` #### Method `GET` #### Security User with scope `g_profile` authorized. #### Success response Code 200 Content ```javascript [ { username: string, mandatory scope:[ scope_value: string, mandatory, array can be empty ], name: string, optional email: string, optional other values: string or array of strings, optional, depends on what's returned by the module instance for the profile } ] ``` Code 401 No enabled authenticated user for this session ### Update current profile #### URL `/api/profile` #### Method `PUT` #### Security User with scope `g_profile` authorized. #### Body Parameters ```javascript { username: string, mandatory name: string, optional other values: string or array of strings, optional, depends on what's exptected by the module instance for the profile } ``` #### Success response Code 200 User updated Code 400 Error input parameters Content A JSON array with the error messages ### Change user password for current profile #### URL `/api/profile/password` #### Method `PUT` #### Security User with scope `g_profile` authorized. #### Body Parameters ```javascript { username: string, mandatory, same username as current user old_password: string, mandatory password: string, mandatory } ``` #### Success response Code 200 User password updated Code 400 Error input parameters Content A JSON array with the error messages ### Delete current profile #### URL `/api/profile/` #### Method `DELETE` #### Security User with scope `g_profile` authorized. #### Success response Code 200 Profile successfully removed Code 403 The user is not allowed to remove its own profile. ### Get list of plugins available #### URL `/api/profile/plugin` #### Method `GET` #### Security User with scope `g_profile` authorized. #### Success response Code 200 Content ```javascript [ { "module": string, plugin type "name": string, plugin instance name "display_name": string, plugin instance display name } ] ``` Code 401 No enabled authenticated user for this session ### Get list of granted scopes per clients #### URL `/api/profile/grant` #### Method `GET` #### Security User with scope `g_profile` authorized. #### Success response Code 200 Content ```javascript [ { "client_id": string, client_id of the client "name": string, client name "description": string, client description "scope": [ { "name": string, name of the scope "display_name": string, display name of the scope "description": string, description of the scope } ] } ] ``` Code 401 No enabled authenticated user for this session ### Get sessions for current profile #### URL `/api/profile/session` #### Method `GET` #### Security User with scope `g_profile` authorized. #### Success response Code 200 Content ```javascript [ { session_hash: string, user_agent: string, issued_for: string, expiration: string, last_login: string, enabled: string } ] ``` Code 401 No enabled authenticated user for this session ### Disable a session for current profile #### URL `/api/profile/session/{session_hash}` #### Method `DELETE` #### Security User with scope `g_profile` authorized. #### Success response Code 200 Session disabled Code 401 No enabled authenticated user for this session ### Get list of schemes available #### URL `/api/profile/scheme` #### Method `GET` #### Security User with scope `g_profile` authorized. #### Success response Code 200 Content ```javascript [ { "module": string, module name of the scheme "name": string, module instance name "display_name": string, module instance display name "expiration": number, duration of an authentication with this scheme "max_use": number, number of thime this scheme authenticated can be used in a plugin "allow_user_register": boolean, wether the user can register this scheme "enabled": boolean } ] ``` Code 401 No enabled authenticated user for this session ### Register an auth scheme for current profile #### URL `/api/profile/scheme/register/` #### Method `POST` #### Security User with scope `g_profile` authorized. #### Body Parameters ```javascript { username: string, mandatory scheme_type: string, mandatory scheme_name: string: mandatory value: object, mandatory, content depends on the scheme } ``` #### Success response Code 200 Scheme registered Code 400 Error input parameters Content A JSON array with the error messages Code 401 No enabled authenticated user for this session ### Get registration on an auth scheme for current profile #### URL `/api/profile/scheme/register/` #### Method `PUT` #### Security User with scope `g_profile` authorized. #### Body Parameters ```javascript { username: string, mandatory scheme_type: string, mandatory scheme_name: string: mandatory } ``` #### Success response Code 200 Content Depends on the scheme Code 400 Error input parameters Content A JSON array with the error messages Code 401 No enabled authenticated user for this session ## Profile delegation All those endpoints are available for an administrator in order to access and update a user's profile and its schemes registration ### Update profile by delegation #### URL `/api/delegate/profile?username=` #### Method `PUT` #### Security Admin with scope `g_admin` authorized. #### Body Parameters ```javascript { username: string, mandatory name: string, optional other values: string or array of strings, optional, depends on what's exptected by the module instance for the profile } ``` #### Success response Code 200 User updated Code 400 Error input parameters Content A JSON array with the error messages ### Get sessions for profile by delegation #### URL `/api/delegate/profile/session?username=` #### Method `GET` #### Security User with scope `g_admin` authorized or valid API key header. #### Success response Code 200 Content ```javascript [ { session_hash: string, user_agent: string, issued_for: string, expiration: string, last_login: string, enabled: string } ] ``` Code 401 No enabled authenticated admin for this session ### Disable a session for a profile by delegation #### URL `/api/delegate/profile/session/{session_hash}?username=` #### Method `DELETE` #### Security User with scope `g_admin` authorized or valid API key header. #### Success response Code 200 Session disabled Code 401 No enabled authenticated user for this session ### Get list of plugins available by delegation #### URL `/api/delegate/profile/plugin?username=` #### Method `GET` #### Security User with scope `g_admin` authorized or valid API key header. #### Success response Code 200 Content ```javascript [ { "module": string, plugin type "name": string, plugin instance name "display_name": string, plugin instance display name } ] ``` Code 401 No enabled authenticated user for this session ### Get list of schemes available by delegation #### URL `/api/delegate/profile/scheme?username=` #### Method `GET` #### Security User with scope `g_admin` authorized or valid API key header. #### Success response Code 200 Content ```javascript [ { "module": string, module name of the scheme "name": string, module instance name "display_name": string, module instance display name "expiration": number, duration of an authentication with this scheme "max_use": number, number of thime this scheme authenticated can be used in a plugin "allow_user_register": boolean, wether the user can register this scheme "enabled": boolean } ] ``` Code 401 No enabled authenticated user for this session ### Register an auth scheme for a profile by delegation #### URL `/api/delegate/profile/scheme/register/?username=` #### Method `POST` #### Security User with scope `g_admin` authorized or valid API key header. #### Body Parameters ```javascript { username: string, mandatory scheme_type: string, mandatory scheme_name: string: mandatory value: object, mandatory, content depends on the scheme } ``` #### Success response Code 200 Scheme registered Code 400 Error input parameters Content A JSON array with the error messages Code 401 No enabled authenticated user for this session ### Get registration on an auth scheme for a profile by delegation #### URL `/api/delegate/profile/scheme/register/?username=` #### Method `PUT` #### Security User with scope `g_admin` authorized or valid API key header. #### Body Parameters ```javascript { username: string, mandatory scheme_type: string, mandatory scheme_name: string: mandatory } ``` #### Success response Code 200 Content Depends on the scheme Code 400 Error input parameters Content A JSON array with the error messages Code 401 No enabled authenticated user for this session ## Authentication Scheme APIs This chapter will describe the specific parameters for the authentication schemes. The following APIs will be further explained for each scheme: - Register scheme: `POST` `/api/profile/scheme/register/` - Get scheme registration: `PUT` `/api/profile/scheme/register/` - Trigger scheme: `POST` `/api/auth/scheme/trigger` - Authentication using scheme: `POST` `/api/auth` For each APIs, the request and/or response body format will be described. This documentation is an addendum to the generic API documentation above. ### Mock authentication scheme This scheme exist for tests use only, it simply is an authentication scheme that will behave as a normal scheme and implements all the functions to help designing and programming Glewlwyd. #### Register scheme ##### Request body format ```javascript { username: string, mandatory scheme_type: "mock" scheme_name: string: mandatory value: { register: boolean, mandatory } } ``` Set the parameter `value.register` to `true` or `false` to enable or disable the scheme for the user. #### Get scheme registration ##### Request body format ```javascript { username: string, mandatory scheme_type: "mock" scheme_name: string: mandatory } ``` ##### Response body format ```javascript boolean ``` The response value will be `true` or `false` depending on the scheme registration status. #### Trigger scheme ##### Request body format ```javascript { username: string, mandatory scheme_type: "mock" scheme_name: string: mandatory value: {} empty object, mandatory } ``` ##### Response body format ```javascript { code: string, will contain the mock value expected to authenticate the user } ``` #### Authentication using scheme ##### Request body format ```javascript { username: string, optional scheme_type: "mock" scheme_name: string: mandatory value: { username: string, mandatory if omitted below code: string, mandatory } } ``` ### E-mail OTP authentication scheme This authentication scheme sends an one-time password to the e-mail of the user and awaits for the user to retype the password in the login page. Therefore, no registration is possible for the user, and the get registration API will return a HTTP status 200 if the user has an e-mail address and 400 otherwise. #### Trigger scheme ##### Request body format ```javascript { username: string, mandatory scheme_type: "email" scheme_name: string: mandatory value: {} empty object, mandatory } ``` #### Authentication using scheme ##### Request body format ```javascript { username: string, mandatory scheme_type: "email" scheme_name: string: mandatory value: { code: string, mandatory } } ``` ### HOTP/TOTP authentication scheme #### Register scheme ##### Request body format Request a random secret for a new OTP to the server. The random secret will be returned in a base32 string format. ```javascript { username: string, mandatory scheme_type: "otp" scheme_name: string: mandatory value: { generate-secret: true } } ``` Delete current OTP setting for the current user ```javascript { username: string, mandatory scheme_type: "otp" scheme_name: string: mandatory value: { type: "NONE" } } ``` Setup current OTP setting as HOTP ```javascript { username: string, mandatory scheme_type: "otp" scheme_name: string: mandatory value: { type: "HOTP" secret: base32 string, mandatory moving_factor: positive integer, optional } } ``` Setup current OTP setting as TOTP ```javascript { username: string, mandatory scheme_type: "otp" scheme_name: string: mandatory value: { type: "TOTP" secret: base32 string, mandatory time_step_size: non zero positive integer, optional } } ``` #### Get scheme registration ##### Request body format ```javascript { username: string, mandatory scheme_type: "otp" scheme_name: string: mandatory } ``` ##### Response body format ```javascript { digits: integer, number of digits required on the authentication code issuer: string, will contain the issuer value (a metadata) hotp-allow: boolean, set to true if the user is allowed to register a HOTP totp-allow: boolean, set to true if the user is allowed to register a TOTP type: string, will contain one of the following values: "NONE", "HOTP" or "TOTP" secret: string, secret registered if a scheme is registered moving_factor: integer time_step_size: integer } ``` Example, for a TOTP registration: ```javascript { digits: 6, issuer: "https://glewlwyd.tld", hotp-allow: true, totp-allow: true, type: "TOTP", secret: "abcd1234===", moving_factor: 0 time_step_size: 30 } ``` #### Trigger scheme N/A #### Authentication using scheme ##### Request body format ```javascript { username: string, mandatory scheme_type: "otp" scheme_name: string: mandatory value: { value: string, mandatory } } ``` ### Password authentication scheme This authentication scheme simply uses the password field of the user - if it's possible - therefore, this scheme simply has an authentication API implemented #### Register scheme N/A #### Get scheme registration N/A #### Trigger scheme N/A #### Authentication using scheme ##### Request body format ```javascript { username: string, mandatory scheme_type: "otp" scheme_name: string: mandatory value: { password: string, mandatory } } ``` ### WebAuthn authentication scheme This scheme is based on the [WebAuthn API](https://w3c.github.io/webauthn/). #### Register scheme ##### Request body format ```javascript { username: string, mandatory scheme_type: "webauthn" scheme_name: string: mandatory value: { register: string, values available are "new-credential", "register-credential", "remove-credential", "disable-credential", "enable-credential", "edit-credential", "trigger-assertion" or "validate-assertion" // Other values depending on the register value } } ``` - Request body format for `register: "new-credential"` ```javascript { username: string, mandatory scheme_type: "webauthn" scheme_name: string: mandatory value: { register: "new-credential" } } ``` - Response body format for `register: "new-credential"` ```javascript { session: string challenge: string pubKey-cred-params: array of string, list of public key formats accepted attestation-required: boolean, set to false if scheme accepts fmt type "none" user: { id: string name: string } rpId: string, relaying part id } ``` - Request body format for `register: "register-credential"` ```javascript { username: string, mandatory scheme_type: "webauthn" scheme_name: string: mandatory value: { register: "register-credential", session: string, must be the session id sent in the previous "register-credential" credential: { // The credential object is returned by the WebAuthn API call `navigator.credentials.create` in the profile page rawId: string response: { clientDataJSON: string attestationObject: string } } } } ``` - Response body format for `register: "register-credential"` HTTP Status 200 on success, 401 on error registration, 400 on error parameters, 404 on error in the session id, 500 otherwise - Request body format for `register: "remove-credential"`, `register: "disable-credential"`, `register: "enable-credential"` ```javascript { username: string, mandatory scheme_type: "webauthn" scheme_name: string: mandatory value: { register: "remove-credential" || "disable-credential" || enable-credential", credential_id: string, must a valid `credential_id` returned by the get_gegister API } } ``` - Response body format for `register: "register-credential"` HTTP Status 200 on success, 400 on error parameters, 404 on error in the credential_id, 500 otherwise - Request body format for `register: "trigger-assertion"` ```javascript { username: string, mandatory scheme_type: "webauthn" scheme_name: string: mandatory value: { register: "trigger-assertion" } } ``` - Response body format for `register: "trigger-assertion"` ```javascript { allowCredentials: array of strings, list of credentials accepted for the user session: string challenge: string user: { id: string name: string } rpId: string, relaying part id } ``` - Request body format for `register: "validate-assertion"` ```javascript { username: string, mandatory scheme_type: "webauthn" scheme_name: string: mandatory value: { register: "validate-assertion", session: string, must be the session id sent in the previous "register-credential" credential: { // The credential object is returned by the WebAuthn API call `navigator.credentials.get` in the profile page rawId: string response: { clientDataJSON: string authenticatorData: string } } } } ``` - Response body format for `register: "register-credential"` HTTP Status 200 on success, 401 on error registration, 400 on error parameters, 404 on error in the session id, 500 otherwise #### Get scheme registration ##### Response body format ```javascript [ { credential_id: string, identifier of the credential name: string, display name of the credential created_at: integer, UNIX epoch time where the credential was created status: string, status of the credential, values available are "registered" or "disabled" } ] ``` #### Trigger scheme ##### Request body format ```javascript { username: string, mandatory scheme_type: "webauthn" scheme_name: string: mandatory value: {} } ``` ##### Response body format ```javascript { allowCredentials: array of strings, list of credentials accepted for the user, can be a fake list depending on the scheme configuration and the username requested session: string challenge: string user: { id: string name: string } rpId: string, relaying part id } ``` #### Authentication using scheme ##### Request body format ```javascript { username: string, mandatory scheme_type: "webauthn" scheme_name: string: mandatory value: { register: "validate-assertion", session: string, must be the session id sent in the previous "register-credential" credential: { // The credential object is returned by the WebAuthn API call `navigator.credentials.get` in the profile page rawId: string response: { clientDataJSON: string authenticatorData: string } } } } ``` ### TLS Certificate authentication scheme This authentication scheme is based on TLS certificate authentication. The first level of authentication is provided by the TLS layer deep down the application, therefore a user can't authenticate with an invalid certificate or a certificate not provided by the configured CA. The scheme module is used to integrate the auth result in Glewlwyd's authentication process with the session, and is also used to manage registered certificates and emit new certificates if possible. To successfully authenticate a user in Glewlwyd's process using the TLS certificate, the used certificate must be valid for the TLS layer AND must be previously registered in the user's authorized certificate list. #### Register scheme ##### Request body format ```javascript { username: string, mandatory scheme_type: "certificate" scheme_name: string: mandatory value: { register: string, values available are "test-certificate", "upload-certificate", "use-certificate", "request-certificate", "toggle-certificate" or "delete-certificate" // Other values depending on the register value } } ``` - Request body format for `register: "test-certificate"` ```javascript { username: string, mandatory scheme_type: "certificate" scheme_name: string: mandatory value: { register: "test-certificate" } } ``` - Response for `register: "test-certificate"` HTTP Status 200 on success, 401 on test error, 400 on error parameters, 500 otherwise - Request body format for `register: "upload-certificate"` ```javascript { username: string, mandatory scheme_type: "certificate" scheme_name: string: mandatory value: { register: "upload-certificate" x509: string, a X509 certificate in PEM format } } ``` - Response for `register: "upload-certificate"` HTTP Status 200 on success, 400 on error parameters, 500 otherwise - Request body format for `register: "use-certificate"` This register action uses the certificate currently used in the TLS layer. ```javascript { username: string, mandatory scheme_type: "certificate" scheme_name: string: mandatory value: { register: "use-certificate" } } ``` - Response for `register: "upload-certificate"` HTTP Status 200 on success, 400 on error parameters, 500 otherwise - Request body format for `register: "request-certificate"` This register action returns a `PKCS#12` file in DER format encoded in base64 with a random generated password to unlock the provided file. ```javascript { username: string, mandatory scheme_type: "certificate" scheme_name: string: mandatory value: { register: "request-certificate" } } ``` - Response body format for `register: "request-certificate"` ```javascript { p12: string, PKCS#12 file in DER format encoded in base64 password: string, used to unlck the PKCS#12 file } ``` - Request body format for `register: "toggle-certificate"` This register action enable or disable a registered certificate ```javascript { username: string, mandatory scheme_type: "certificate" scheme_name: string: mandatory value: { register: "toggle-certificate" certificate_id: string, a certificate_id registered to the user, mandatory enabled: boolean, set the status to enabled or disabled for the certificate } } ``` - Request body format for `register: "delete-certificate"` This register action deletes a registered certificate ```javascript { username: string, mandatory scheme_type: "certificate" scheme_name: string: mandatory value: { register: "delete-certificate" certificate_id: string, a certificate_id registered to the user, mandatory } } ``` #### Get scheme registration ##### Response body format ```javascript [ { credential_id: string, identifier of the certificate certificate_dn: DN of the certificate certificate_issuer_dn: DN Of the issuer of the certificate activation: integer, UNIX Epoch time where the certificate was activated expiration: integer, UNIX Epoch time where the certificate will expire last_used: integer, UNIX Epoch time when the certificate was last succesfully used to authenticate a user, optional last_user_agent: string, value of the user-agent when the certificate was last succesfully used to authenticate a user, optional enabled: boolean } ] ``` #### Trigger scheme N/A #### Authentication using scheme ##### Request body format ```javascript { username: string, optional if option scheme storage is set, mandatory otherwise scheme_type: "certificate" scheme_name: string: mandatory value: {} } ``` ### OAuth2/OIDC Client Authentication scheme #### Register scheme ##### Request body format ```javascript { username: string, mandatory scheme_type: "oauth2" scheme_name: string: mandatory value: { action: string, values available are "new", "callback", "delete" // Other values depending on the register action value } } ``` - Request body format for `action: "new"` ```javascript { username: string, mandatory scheme_type: "certificate" scheme_name: string: mandatory value: { action: "new", provider: string, provider name to register complete_url: string, uri for registration completion } } ``` - Response for `action: "new"` ```javascript { redirect_to: string, url to the authorization uri } ``` HTTP Status 200 on success, 400 on error parameters, 500 otherwise - Request body format for `action: "callback"` ```javascript { username: string, mandatory scheme_type: "certificate" scheme_name: string: mandatory value: { action: "callback", provider: string, provider name to register state: string redirect_to: string } } ``` HTTP Status 200 on success, 400 on error parameters, 500 otherwise - Request body format for `action: "delete"` ```javascript { username: string, mandatory scheme_type: "certificate" scheme_name: string: mandatory value: { action: "delete", provider: string, provider name to delete } } ``` HTTP Status 200 on success, 400 on error parameters, 500 otherwise #### Get scheme registration ##### Response body format ```javascript [ { provider: string, identifier of the provider logo_uri: string, uri of the provider logo logo_fa: string, fork-awesome logo name enabled: boolean created_at: null } ] ``` #### Trigger scheme ##### Request body format - Get provider list ```javascript { username: string, mandatory scheme_type: "oauth2" scheme_name: string: mandatory value: { provider_list: true } } ``` - Response for `provider_list` ```javascript [ { provider: string, identifier of the provider logo_uri: string, uri of the provider logo logo_fa: string, fork-awesome logo name enabled: boolean created_at: null } ] ``` - Run authentication flow ```javascript { username: string, optional scheme_type: "certificate" scheme_name: string: mandatory value: { provider: string, provider name to authenticate } } ``` - Response for `action: "new"` ```javascript { redirect_to: string, url to the authorization uri } ``` #### Authentication using scheme ##### Request body format ```javascript { username: string, optional scheme_type: "certificate" scheme_name: string: mandatory value: { provider: string, provider name to register state: string redirect_to: string } } ``` HTTP Status 200 on success, 400 on error parameters, 500 otherwise glewlwyd-2.6.1/docs/CERTIFICATE.md000066400000000000000000000116721415646314000163650ustar00rootroot00000000000000# Glewlwyd TLS Certificate Schema documentation [![License: CC BY 4.0](https://licensebuttons.net/l/by/4.0/80x15.png)](https://creativecommons.org/licenses/by/4.0/) ![scheme-certificate](screenshots/scheme-certificate.png) The TLS Certificate Schema implements authentication based on the [Client-authenticated TLS handshake](https://en.wikipedia.org/wiki/Transport_Layer_Security#Client-authenticated_TLS_handshake) protocol. !!!Full disclosure!!! This authentication scheme has been implemented based on the documentation and examples I could find. But there may be other and better ways to implement this type of authentication. If you find bugs, weird behaviours, or wish new features, please [open an issue](https://github.com/babelouest/glewlwyd/issues) in the GitHub repository or send an e-mail. ## Installation In the administration page, go to `Parameters/Authentication schemes` and add a new scheme by clicking on the `+` button. In the modal, enter a name and a display name (the name must be unique among all authentication scheme instances), and a scheme session expiration in seconds. Select the type `Client certificate` in the Type drop-down button. Below is the definition of all parameters. ### Name Name (identifier) of the scheme, must be unique among all the scheme instances, even of a different type. ### Display name Name of the instance displayed to the user. ### Expiration (seconds) Number of seconds to expire a valid session. ### Max use per session (0: unlimited) Maximum number of times a valid authentication with this scheme is possible. This is an additional parameter used to enforce the security of the session and forbid to reuse this session for other authentications. ### Allow users to register If this option is unchecked, only administrator can register this scheme for every user via the administration page. ### Certificate source Source of the client certificate to be picked by the scheme. Either in the TLS session, the HTTP header or both. If you intent to use Glewlwyd directly, set this option to `TLS Session`. You must also configure Glewlwyd in secure mode with the proper `secure_connection_ca_file` value. This configuration value must be set to your CA certificate or your CA chain certificate in order to validate clients certificates provided. Important security warning! If you don't use Glewlwyd behind a reverse proxy to forward the certificate in the header, this option MUST be set to `TLS Session` only, otherwise, an attacker could manually change the header value, to fake any valid user without having to know its certificate key. If you set this value to `HTTP Header` or `both`, it allows to use Glewlwyd behind a reverse proxy such as Apache's mod `proxy`. You must then configure the proxy to validate the clients certificate and key using your CA certificate and if the client certificate is valid, the proxy must forward the X509 certificate to Glewlwyd in a specified header. ### Corresponding header property This option will store tame of the header property that will contain the client certificate in PEM format without newlines. Using the example config above, you must set this value to `SSL_CLIENT_CERT`. ### Use scheme storage If this option is set to `yes`, the registered certificates will be stored in the database, in a specific table for this scheme. If this option is set to `no`, the registered certificates will be extracted from the user properties. Also, at least one of the fields `Certificate property` or `DN Property` must be filled. If a user has a DN value and one or more certificates in its properties, only the DN will be checked to validate its certificate. ### Certificate property This option is available if the option `Use scheme storage` is set to `no`. It is used to specify the user property that will store the certificates used to authenticate the user. ### Certificate format This option is available if the option `Use scheme storage` is set to `no`. This is used to specify the certificate format stored in the user properties: `PEM` or `DER`. Because Glewlwyd's internal format of the user properties is JSON, if the certificate format is set to `DER`, the certificate must be converted to base64. In fact, this option exists to use the LDAP property `userCertificate` which is stored in DER format. In that case, the property `userCertificate` must be converted in base64 in the [LDAP backend configuration](USER_LDAP.md#convert). ### DN Property This option is available if the option `Use scheme storage` is set to `no`. It is used to specify the user property that will store the user DN to authenticate the certificate. ### CA Certificates This section was designed to validate the client certificate using the full chain of trust until the root CA. ### Use a CA If this option is set to `yes`, the admin will be allowed to add the CA chain files to validate each user certificates. Then, the admin can add one or multiple files by clicking `Browse`, select the file, then click `Upload`. glewlwyd-2.6.1/docs/CLIENT_DATABASE.md000066400000000000000000000076751415646314000170550ustar00rootroot00000000000000# Glewlwyd Client Module Database Backend documentation [![License: CC BY 4.0](https://licensebuttons.net/l/by/4.0/80x15.png)](https://creativecommons.org/licenses/by/4.0/) ![mod-client-database](screenshots/mod-user-database.png) The database backend uses a database to store information and passwords for clients. The database can be the same one as the Glewlwyd database or another database of another supported type. ## Installation In the administration page, go to `Parameters/Clients data sources` and add a new client module by clicking on the `+` button. In the modal, enter a name and a display name (the name must be unique among all client backend instances). Select the type `Database backend client module` in the Type drop-down button. Below is the definition of all parameters. ### Name Name (identifier) of the module instance, must be unique among all the client backend module instances, even of a different type. ### Display name Name of the instance displayed to the client. ### Read only Check this option if you want to use this backend as read-only. All client properties such as e-mail, name, password, scopes can't be modifier with Glewlwyd, even administrators. ### PKBDF2 iterations (SQLite3) Number of iterations for the password digest in SQlite3 databases only. Currently (2021), it's recommended to iterate at least 100 000 times. ### Use the same connection as Glewlwyd server Uncheck this option if you want to use a different database that will store the clients. The new database must have the structure already present. Use one of the following script to initialize the database: - MariaDB: [database.mariadb.sql](../src/client/database.mariadb.sql) - Postgre SQL: [database.postgre.sql](../src/client/database.postgre.sql) - SQLite 3: [database.sqlite3.sql](../src/client/database.sqlite3.sql) ### Database type This option is available if the option `Use the same connection as Glewlwyd server` is disabled. Select the database backend among the ones available. ### Path to the database (SQLite 3) This option is available if the database type selected is SQLite 3. Enter the path of the SQLite 3 database on the server. ### Host (MariaDB/MySQL) This option is available if the database type selected is MariaDB/MySQL. Hostname of the MariaDB/MySQL server. ### Clientname (MariaDB/MySQL) This option is available if the database type selected is MariaDB/MySQL. Clientname to connect to the MariaDB/MySQL server. ### Password (MariaDB/MySQL) This option is available if the database type selected is MariaDB/MySQL. Password to connect to the MariaDB/MySQL server. ### Database name (MariaDB/MySQL) This option is available if the database type selected is MariaDB/MySQL. Name of the database hosting the Glewlwyd client backend tables. ### TCP Connection port (0: system default) (MariaDB/MySQL) This option is available if the database type selected is MariaDB/MySQL. TCP Port used to connect to the MariaDB/MySQL database. Set 0 if you want to use default system port. ### Postgre SQL connection string (PostgreSQL) This option is available if the database type selected is PostgreSQL. SQL Connection string used to connect to the PostgreSQL database. ### Specific data format This section allows to specify new properties for the client. The properties may be available for schemes, plugins, in the admin page or in the profile page. By default, when you add a new client backend, the properties `redirect_uri` and `authorization_type` are added with multiple values and read/write modes. #### Property Property name, ex: `phone`, `address`, `human`, etc. #### Multiple values If this option is checked, the property values will be available as an array of string values, otherwise a single string value. #### Read (admin) If this option is checked, plugins and administrators can have access to this property in read mode. #### Write (admin) If this option is checked, plugins and administrators can have access to this property in write mode. glewlwyd-2.6.1/docs/CLIENT_LDAP.md000066400000000000000000000121371415646314000164160ustar00rootroot00000000000000# Glewlwyd Client Module LDAP Backend documentation [![License: CC BY 4.0](https://licensebuttons.net/l/by/4.0/80x15.png)](https://creativecommons.org/licenses/by/4.0/) ![mod-client-ldap](screenshots/mod-client-ldap.png) The database backend uses a LDAP service to store information and passwords for clients. ## Installation In the administration page, go to `Parameters/Clients data sources` and add a new client module by clicking on the `+` button. In the modal, enter a name and a display name (the name must be unique among all client backend instances). Select the type `LDAP backend client module` in the Type drop-down button. Below is the definition of all parameters. ### Name Name (identifier) of the module instance, must be unique among all the client backend module instances, even of a different type. ### Display name Name of the instance displayed to the client. ### Read only Check this option if you want to use this backend as read-only. All client properties such as e-mail, name, password, scopes can't be modifier with Glewlwyd, even administrators. ### Connection URI URI to connect to the LDAP service, ex: ldaps://ldap.example.com/ ### Connection DN DN used to access the LDAP service. The DN must have write access if you want to use this backend in write mode. ### Connection password Password to use with the `Connection DN`. ### Search page size Page size to list clients in this backend. This option must be lower than the maximum of results that the LDAP service can send. ### Search base Base DN to look for clients. ### Search scope Search scope on the LDAP Base DN. Values available are `one`, `subtree`, `children`. ### Search filter Filter to apply when performing a search of clients. ### Client ID property Client ID of the client. This property will be used to build the search filter on a client connection. You can specify multiple values by separating them with a comma `,`. On read mode, the first value will be used, on write mode, all values will be used. ### Name property Name of the client. You can specify multiple values by separating them with a comma `,`. On read mode, the first value will be used, on write mode, all values will be used. ### Scope property Scopes available for the client. The LDAP property must store multiple values. You can specify multiple values by separating them with a comma `,`. On read mode, the first value will be used, on write mode, all values will be used. ### Description property Property used to store the client description value. You can specify multiple values by separating them with a comma `,`. On read mode, the first value will be used, on write mode, all values will be used. ### Confidential property Property used to store the client confidential flag value. The value available are "0" (non confidential client) and "1" (confidential client). You can specify multiple values by separating them with a comma `,`. On read mode, the first value will be used, on write mode, all values will be used. ### Password property. Property used to store the client password. This property is not used if the instance is in read-only mode. You can specify multiple values by separating them with a comma `,`. On read mode, the first value will be used, on write mode, all values will be used. ### Algorithm Algorithm used to hash the client password. This property is not used if the instance is in read-only mode. ### rdn property This property is mandatory to store the rdn property. This property is not used if the instance is in read-only mode. You can specify multiple values by separating them with a comma `,`. ### Object class property for a new client This value will contain all the object class values when Glewlwyd will create new clients in the LDAP backend. Values must be separated with a comma `,`. ### Specific data format This section allows to specify new properties for the client. The properties may be available for schemes, plugins, in the admin page or in the profile page. By default, when you add a new client backend, the properties `redirect_uri` and `authorization_type` are added with multiple values and read/write modes. #### Property Property name, ex: `phone`, `address`, `human`, etc. #### LDAP Property Corresponding LDAP property name. #### Multiple values If this option is checked, the property values will be available as an array of string values, otherwise a single string value. #### Convert If this option is set to `base64`, the property content will be converted to base64 for Glewlwyd. On the other hand, if this property is writable by Glewlwyd, the data must be in base64. ### Scope field property This section allows to specify a correspondence between a Glewlwyd scope and a value in the scope property. The main goal is to use an existing LDAP service whose clients have property that can be related to scopes (group names, etc.). For example, the group name value `accounting` will correspond to the scope `mail`. #### LDAP value LDAP value that must match. #### Corresponding scope Name of the scope that will be returned. This value must be an existing scope name. #### Match How the LDAP value must match. glewlwyd-2.6.1/docs/EMAIL.md000066400000000000000000000074121415646314000154670ustar00rootroot00000000000000# Glewlwyd Email code Schema Documentation [![License: CC BY 4.0](https://licensebuttons.net/l/by/4.0/80x15.png)](https://creativecommons.org/licenses/by/4.0/) ![scheme-email](screenshots/scheme-email.png) The Email code Schema implements authentification based on random One-Time-Password generated on demand. ## Installation In the administration page, go to `Parameters/Authentication schemes` and add a new scheme by clicking on the `+` button. In the modal, enter a name and a display name (the name must be unique among all authentication scheme instances), and a scheme session expiration in seconds. Select the type `Email code` in the Type dropdown button. Below is the definition of all parameters. ### Name Name (identifier) of the scheme, must be unique among all the scheme instances, even of a different type. ### Display name Name of the instance displayed to the user. ### Expiration (seconds) Number of seconds to expire a valid session. ### Max use per session (0: unlimited) Maximum number of times a valid authentification with this scheme is possible. This is an additional parameter used to enforce the security of the session and forbid to reuse this session for other authentications. ### Allow users to register If this option is unchecked, only administrator can register this scheme for every user via the administration page. ### Code length Length of the code that must be sent by the user. Must be a positive non null integer. ### Code duration (in seconds) Duration of the code validity in seconds. This option must be short enough to limitate brute force attacks, but long enough to give time for the user to receive the message, open the message, then type the password in Glewlwyd authentification page. ### SMTP Server Address of the SMTP server that will relay the messages to the users, mandatory. ### Port SMTP (0: System default) TCP port the SMTP server is listening to. Must be between 0 and 65535. If 0 is set, Glewlwyd will use the system default port for SMTP, usually 25 or 587, mandatory. ### Use a TLS connection Check this option if the SMTP server requires TLS to connect. ### Check server certificate Check this option if you want Glewlwyd to check the SMTP server certificate before relaying the e-mail. This is highly recommended if TLS connection is checked, useless otherwise. ### SMTP username (if required) username used to authenticate to the SMTP server if required by the SMTP server, optional. ### SMTP password (if required) password used to authenticate to the SMTP server if required by the SMTP server, optional. ### E-mail sender address Address used as sender in the e-mails, required. ### Content-Type Content-Type for the e-mails, default is plain text but you can set an HTML body if youo need to. ### User lang property User property which will contain the default lang value used for the e-mail templates. The lang value must be an exact match of the lang template. If the user lang doens't exist in the templates or if the user has no lang property, the e-mail template will use the default language. ### Lang Dropdown value to select, add or remove lang templates for the e-mails. ### Default lang Checkbox to specify what lang is the default language. In case the user has no language value or its language value doesn't exist in the templates. ### E-mail subject Subject used on the e-mails for the current lang, required. ### E-mail body, the pattern {CODE} will be replaced by the code. The pattern for the body on the e-mails for the current lang, You must use at least once the string `{CODE}` in the pattern to be replaced by the code. Example, by using the following e-mail pattern: ``` Glewlwyd authentification code: {CODE} ``` Users will receive the following message: ``` Glewlwyd authentification code: 123456 ``` glewlwyd-2.6.1/docs/GETTING_STARTED.md000066400000000000000000001115561415646314000171340ustar00rootroot00000000000000# Getting started with Glewlwyd 2.0 [![License: CC BY 4.0](https://licensebuttons.net/l/by/4.0/80x15.png)](https://creativecommons.org/licenses/by/4.0/) - [Installation](#installation) - [First connection to the administration page](#first-connection-to-the-administration-page) - [Configure backends, schemes, scopes and plugins](#configure-backends-schemes-and-plugins) - [User backend modules](#user-backend-modules) - [Database backend](#database-backend) - [LDAP backend](#ldap-backend) - [HTTP authentication backend](#http-authentication) - [Client backend module](#client-backend-module) - [Database backend](#database-backend) - [LDAP backend](#ldap-backend) - [Authentication schemes](#authentication-schemes) - [E-mail code scheme](#e-mail-code-scheme) - [WebAuthn scheme](#webauthn-scheme) - [HOTP/TOTP scheme](#hotp-totp-scheme) - [TLS Certificate scheme](#tls-certificate-scheme) - [Retype-password scheme](#retype-password-scheme) - [HTTP Basic Authentication scheme](#http-basic-authentication-scheme) - [OAuth2/OIDC Client Authentication scheme](#oauth2oidc-client-authentication-scheme) - [Scopes](#scopes) - [Plugins](#plugins) - [Glewlwyd OAuth2 plugin](#glewlwyd-oauth2-plugin) - [OpenID Connect Core Plugin](#openid-connect-core-plugin) - [Register new user plugin](#register-new-user-plugin) - [Configure environment to use Glewlwyd OAuth2](#configure-environment-to-use-glewlwyd-oauth2) - [Create the client](#create-the-client) - [Configure scopes](#configure-scopes) - [Setup the required scopes for a user](#setup-the-required-scopes-for-a-user) - [Access to administration API via API keys](#access-to-administration-api-via-api-keys) - [Use an API key in a script](#use-an-api-key-in-a-script) - [Prometheus metrics](#prometheus-metrics) - [How-Tos](#how-tos) - [Use case: Configure Glewlwyd to authenticate with Taliesin](#use-case-configure-glewlwyd-to-authenticate-with-taliesin) - [Use case: Configure a registration process with a confirmed e-mail address and OTP, WebAuthn or OAuth2 Client schemes](#use-case-configure-a-registration-process-with-a-confirmed-e-mail-address-and-otp-webauthn-or-oauth2-client-schemes) - [Use case: Connect Webmail RainLoop with OpenID Connect plugin using Dovecot configured with a master password](#use-case-connect-webmail-rainloop-with-openid-connect-plugin-using-dovecot-configured-with-a-master-password) - [User profile delegation](#user-profile-delegation) - [Add or update additional properties for users and clients](#add-or-update-additional-properties-for-users-and-clients) - [Non-password authentication](#non-password-authentication) - [Multiple password authentication](#multiple-password-authentication) - [Test a client configuration with Idwcc](#test-a-client-configuration-with-idwcc) - [Troubleshooting](#troubleshooting) - [Impossible to log in as administrator - N-factor issue](#impossible-to-log-in-as-administrator---n-factor-issue) - [Impossible to log in as administrator - Password lost](#impossible-to-log-in-as-administrator---password-lost) The installation comes with a default configuration that can be updated or overwritten via the administration page or the configuration file. The default configuration uses Glewlwyd's database as backend for users and clients. The scopes `g_admin` and `g_profile` (for admin page and profile page) are configured for a session duration of 10 minutes. The following plugins are available but must be instantiated and configured either: - Glewlwyd OAuth2 plugin - Glewlwyd OpenID Connect Core plugin - Register new user/Update e-mail/Reset credentials plugin ## Installation Install Glewlwyd via the packages, CMake or Makefile. See the [installation documentation](INSTALL.md) for more details. ## First connection to the administration page ![admin](screenshots/user-list.png) Open the administration page in your browser. The default URL is [http://localhost:4593/](http://localhost:4593/). Use the default login `admin` with the default password `password`. Make sure to change the default password for the `admin` user to a more secure password. ## Configure backends, schemes and plugins ### User backend modules ![mod-user-list](screenshots/mod-user-list.png) Go to `parameters/user` menu in the navigation tab. Click on the `+` button to add a new user backend instance. The user backend modules available are: - Database - LDAP - HTTP authentication You can add the same instance of the same user backend module as many times as you want. A user backend module is identified by its module name and its instance name, example `database/localDB`, `ldap/companyAD`. The instance name must be unique though, i.e. you can't have `database/local` and `ldap/local` as user backend instances. #### Database backend The database backend requires an access to a database. You can use the same backend as the Glewlwyd server or use a different database. If you use a different database, it must be initialized with the script available in `src/user/database.[sqlite3|mariadb|postgresql].sql`. Read the full [documentation](USER_DATABASE.md). #### LDAP backend The LDAP backend requires access to a LDAP service such as OpenLDAP or Active Directory. Read the full [documentation](USER_LDAP.md). #### HTTP authentication With this user backend module, every time a user/password access is required, Glewlwyd will use the login/password provided to authenticate on the HTTP service configured and return the result: user valid, user invalid or server error. You must set at least one scope that will be available for all users connecting via this backend. This module is read-only, and no user data will be stored in Glewlwyd's storage system, except the user sessions. Which means getting the user list of an HTTP backend will always return an empty list and getting the details of any username will return a build-up JSON object with the following data: ```javascript // Response example for the username `user1`: { "username": "user1", "scope": ["g_profile","scope1"], "enabled": true } ``` Read the full [documentation](USER_HTTP.md). ### Client backend module ![mod-client-list](screenshots/mod-client-list.png) Go to `parameters/client` menu in the navigation tab. Click on the `+` button to add a new client backend instance. The client backend modules available are: - Database - LDAP You can add the same instance of the same client backend module as many times as you want. A client backend module is distinguished by its module name and its instance name, example `database/localDB`, `ldap/companyAD`. #### Database backend The database backend requires an access to a database. You can use the same backend as the Glewlwyd server or use a different database. If you use a different database, it must be initialized with the script available in `src/client/database.[sqlite3|mariadb|postgresql].sql`. Read the full [documentation](CLIENT_DATABASE.md). #### LDAP backend The LDAP backend requires access to a LDAP service such as OpenLDAP or Active Directory. Read the full [documentation](CLIENT_LDAP.md). ### Authentication schemes ![mod-scheme-list](screenshots/mod-scheme-list.png) When an authentication scheme needs to store specific data for a user, it will use the database rather than the user backend. So a user will be able to register a scheme even if the user backend is in read-only mode. Please note that a user won't be able to register nor authenticate with a scheme if the scheme isn't required to authenticate one of the user's scope. Go to `parameters/schemes` menu in the navigation tab. Click on the `+` button to add a new scheme instance. The scheme modules available are: - E-mail code scheme - WebAuthn scheme - HOTP/TOTP scheme - TLS Certificate scheme - Retype-password scheme - HTTP Basic Authentication scheme - OAuth2/OIDC Client Authentication scheme You can add instances of the same scheme as many times as you want, if you need different configurations or to access different scopes in different contexts. A scheme instance is distinguished by its module name and its instance name, example `webauthn/AdminWebauthn`, `webauthn/UserWebauthn`. Users will need to register some schemes such as HOTP/TOTP or WebAuthn. If the option `Allow users to register` is unchecked for a scheme, the users won't be able to register it, only administrators via delegation will be able to register for users. #### E-mail code scheme The requirements to use this scheme is a smtp server available, able to relay codes sent via `SMTP`. Read the full [documentation](EMAIL.md). #### WebAuthn scheme The WebAuthn Schema implements authentication based on the [WebAuthn API](https://w3c.github.io/webauthn/). This allows users to authenticate to Glewlwyd using physical devices: Android or Apple phones, Yubikeys, etc. Read the full [documentation](WEBAUTHN.md). #### HOTP/TOTP scheme The OTP Schema implements authentication based on One-Time-Password using OATH standard defined in [HOTP](https://tools.ietf.org/html/rfc4226) and [TOTP](https://tools.ietf.org/html/rfc6238). Read the full [documentation](OTP.md). #### TLS Certificate scheme The TLS Certificates scheme requires [SSL/TLS with CA certificate](https://github.com/babelouest/glewlwyd/blob/master/docs/INSTALL.md#ssltls) enabled or a reverse proxy configured to authenticate certificates and transfer the certificate data to Glewlwyd's API. Read the full [documentation](CERTIFICATE.md). #### Retype-password scheme The Retype-password schema allows to mandatory retype the user password to authenticate, even if the session is authenticated with a valid password. This scheme may be useful to force user to retype its password in some critical process. #### HTTP Basic Authentication scheme The HTTP Basic Authentication performs a login/password authentication against a specified webservice that requires HTTP Basic Authentication. This scheme is similar to [HTTP authentication backend](#http-authentication) but requires users to be already added to any backend (Database or LDAP). The advantage is that you can specify different scopes for each users and add any other additional data for the users. Read the full [documentation](HTTP.md). #### OAuth2/OIDC Client Authentication scheme The OAuth2/OIDC Client Authentication scheme allows users to login to Glewlwyd via a trusted external OAuth2 or OpenID Connect provider where they have a valid account. The administrator must enter the trusted providers settings to allow users to use those providers. Mainstream providers such as Google, Microsoft, Facebook, GitHub, etc. are compatible with this scheme. Read the full [documentation](OAUTH2_SCHEME.md). ### Scopes ![scope-list](screenshots/scope-list.png) Go to `parameters/Scopes` menu in the navigation tab. Click on the `+` button to add a new scope. ![scope-add](screenshots/scope-add.png) Read the full [documentation for the scope management](SCOPE.md). ### Plugins ![plugin-list](screenshots/plugin-list.png) Go to `parameters/plugins` menu in the navigation tab. Click on the `+` button to add a new plugin instance. The plugins available are: - Glewlwyd OAuth2 plugin - OpenID Connect Core #### Glewlwyd OAuth2 plugin This module has the same behaviour as the legacy Glewlwyd 1.x OAuth2. The new features available are: - Allow to use a refresh token as a `"rolling refresh"`, so every time an access token is refreshed, the lifetime of the refresh token will be reset to the original duration. Then if a token is refreshed periodically, users won't have to reconnect and request a new refresh token every 2 weeks or so. - Allow to overwrite default `rolling refresh` setting and refresh token duration for every scope individually. `rolling refresh` disabled and the lowest refresh token duration have precedence in case of conflicting scopes settings. - Allow to add multiple user properties in the `access_token` rather than one. Read the full [documentation](OAUTH2.md). When the plugin instance is enabled, its endpoints available are: - `/api//auth` - `/api//token` - `/api//profile` #### OpenID Connect Core Plugin This plugin implements the OpenID Connect Core standard. Read the full [documentation](OIDC.md). When the plugin instance is enabled, its endpoints available are: - `/api//auth` - `/api//token` - `/api//userinfo` #### Register new user plugin This plugin allows new users to register to the Glewlwyd service and create a new account. New users may need to confirm their e-mail, set a password to register and/or authentication schemes such as OTP, WebAuthn or TLS Certificate, depending on the configuration. Read the full [documentation](REGISTER.md). ### Configure environment to use Glewlwyd OAuth2 You need a resource service that requires glewlwyd access tokens to work. Some applications are available in my GitHub repositories, like [Taliesin](https://github.com/babelouest/taliesin), an audio streaming server, [Hutch](https://github.com/babelouest/hutch), a password and secret locker, or [Angharad](https://github.com/babelouest/angharad), a house automation server. You need to add a client, at least one scope, and setup the scope(s) for a user. #### Create the client Click on the `+` button on the client list page in Glewlwyd admin app. You must at least set a client_id, a redirect URI and the authorization types required. #### Configure scopes Go to `Scopes` menu in the navigation tab. Click on the `+` button to add a new Scope. By default, a scope requires only the password for authentication. You can specify additional authentication schemes and/or unset password authentication. You can gather authentication schemes in groups to allow multiple authentication factor, and you can have multiple groups to force more than one additional authentication factor. The authentication group model can be represented as the following schema: Scope 1: password `AND` (mail `OR` WebAuthn) `AND` (TOTP `OR` certificate) Scope 2: (mail `OR` certificate `OR` WebAuthn) #### Setup the required scopes for a user Go to `Users` menu in the navigation tab, Click on the `Edit` button for an existing user or click on the `+` button to add a new user. Then, set the previously created scope to this user. When the user will connect to the client with Glewlwyd, he will need to validate the authentication schemes for the scopes required with this client. ### Access to administration API via API keys ![api-key-list](screenshots/api-key-list.png) ![api-key-add](screenshots/api-key-add.png) Glewlwyd allows to access all administration API endpoints using an API key to authenticate instead of the admin session cookie. This can be useful if you want to run API commands through scripts for example. An API key allows to access all admin APIs described in the [API documentation](API.md), except the APIs to manage API keys for security reasons. An API key has no expiration date. Therefore be very careful with those, don't loose them, save them carefully with their creation datestamp, so they won't leak somewhere and allow bad people to do bad things. You can disable an API key in the admin page. #### Use an API key in a script The API key must be added in the request header `Authorization` with the prefix `token `. Example: getting the list of users using an API key in a curl command: ```shell $ curl 'http://localhost:4593/api/user' -H 'Authorization: token XJcv1MRnK33EHAedPGELl0yXx2W6vUPu' ``` ### Prometheus metrics If the Prometheus endpoint is enabled, then you can access the prometheus metrics via the default url [http://localhost:4594/](http://localhost:4594/). By default, Glewlwyd logs the following metrics: ``` General - Total number of successful authentication - Total number of successful authentication by scheme - Total number of invalid authentication - Total number of invalid authentication by scheme - Total number of database errors OAuth2 plugin - Total number of code provided - Total number of device code provided - Total number of refresh tokens provided - Total number of access tokens provided - Total number of client tokens provided - Total number of unauthorized client attempt - Total number of invalid code - Total number of invalid device code - Total number of invalid refresh token - Total number of invalid access token OIDC plugin - Total number of code provided - Total number of device code provided - Total number of id_token provided - Total number of refresh tokens provided - Total number of access tokens provided - Total number of client tokens provided - Total number of unauthorized client attempt - Total number of invalid code - Total number of invalid device code - Total number of invalid refresh token - Total number of invalid access token Registration plugin - Total number of registration started - Total number of registration completed - Total number of registration cancelled - Total number of e-mails updated - Total number of reset credentials started - Total number of reset credentials completed ``` ## How-Tos ### Use case: Configure Glewlwyd to authenticate with Taliesin This use case is based on the following assertions: - Glewlwyd is freshly installed with default configuration - Glewlwyd is installed on the local machine and available at the address [http://localhost:4593/](http://localhost:4593/) - Taliesin is installed on the local machine and available at the address [http://localhost:8576/](http://localhost:8576/) - The scope `taliesin` will be configured as a rolling refresh, with password only - The scope `taliesin_admin` will be configured as standard refresh token, without rolling refresh enabled, with password and OTP 2nd factor or WebAuthn enabled - The tokens are jwt signed with a RSA 256 key, the key file and the certificate must be available. To create a RSA key/certificate pair, run the following commands on a Linux shell with openssl installed: ```shell $ # private key $ openssl genrsa -out private-rsa.key 4096 $ # public key $ openssl rsa -in private-rsa.key -outform PEM -pubout -out public-rsa.pem ``` Open the Glewlwyd admin page [http://localhost:4593/](http://localhost:4593/) in your browser, connect as administrator (admin/password) #### Step 1: Change admin password Click on the `Change password` menu on the navigation tab, there, you should change the `admin` password with a more efficient password. #### Step 2: Add OTP and WebAuthn schemes Go to `parameters/schemes` menu in the navigation tab. Click on the `+` button to add a new scheme instance. In the new scheme modal, enter in the name field `otp`, in the display name field `OTP`, select `HOTP/TOTP` in the type drop-down. Leave the other default parameters as is and click save. The scheme OTP should appear in the scheme list. Then click again on the `+` button to add the WebAuthn scheme. In the new scheme modal, enter in the field name `webauthn`, in the display name field `WebAuthn`, select `WebAuthn` in the type drop-down. Leave the other default parameters as is and click save. The scheme OTP should appear in the scheme list. #### Step 3: Add scopes in Glewlwyd - Add the scope `taliesin`, check the password checkbox - Add the scope `taliesin_admin`, check the password checkbox and add the schemes `OTP` and `WebAuthn` to the scope #### Step 4: Add a Glewlwyd OAuth2 plugin instance Go to `parameters/plugins` menu in the navigation tab. Click on the `+` button to add a new plugin instance. In the new plugin modal, enter in the name field `glwd`, in the display name field `Glewlwyd OAuth2`, select `Glewlwyd OAuth2` in the type drop-down. There, select `RSA` as JWT type, 256 as key size, set your private and public key. Deploy `Specific scope parameters` and add the scope `taliesin_admin`, disable `rolling refresh` for this scope. Click `Ok`. #### Step 5: Add the client Taliesin Go to `Clients` menu in the navigation tab. Click on the `+` button to add a new client. In the new client modal, enter `taliesin` in the client ID field, `Taliesin` in the name field. Add the redirect URI `http://localhost:8576/`, add the following authorization types: `code`, `token` and `refresh_token`. #### Step 6: Add a simple user and setup admin user Go to `Users` menu in the navigation tab. Click on the `+` button to add a new user. In the new user modal, enter `t_user` in the username field, `Taliesin User` in the name field, set a password (8 characters minimum), add the scopes `g_profile` and `taliesin`. Click `Ok`. The new user `t_user` should appear in the users list. #### Step 7: Configure OTP and WebAuthn for admin Click on the `delegate profile` button for the user `admin`. In the new page, select `otp` in the navigation bar. Create a new OTP scheme for the user `admin`, select `TOTP` in the drop-down list, click on the `Generate` button to generate a random secret for this scheme, click `Save`. Your TOTP scheme is configured. Then you can reproduce this configuration on another device, like an OTP application on a smartphone. You can use the generated QR Code in the profile page. Register a new WebAuthn device. Select `WebAuthn` in the navigation bar. Two types of WebAuthn devices are currently supported: `fido-u2f` types like Yubikeys and `android-safetynet` types like Android phones or tablet, version 7 or above. Click on the button `Register` to add a new registration. Follow the steps on your browser to complete the registration. You can test the authentication by clicking on the `Test` button. #### Step 8: Configure Taliesin's `config.json` file Open the file `webapp/config.json` on an editor. The file should look like this now: ```json { "taliesinApiUrl": "http://localhost:8576/api", "storageType": "local", "useWebsocket": true, "oauth2Config": { "enabled": true, "storageType": "local", "responseType": "code", "serverUrl": "http://localhost:4593/api/glwd", "authUrl": "auth", "tokenUrl": "token", "clientId": "taliesin", "redirectUri": "http://localhost:8576/", "scope": "taliesin taliesin_admin", "profileUrl": "profile" } } ``` #### Step 9: Open Taliesin in your browser Open the URL [http://localhost:8576/](http://localhost:8576/) in your browser. Click on the login button, you should be redirected to Glewlwyd's login page. There, log in with your admin password. After that, use the second factor authentication of your choice. When completed, click on the `Continue` button which should be enabled. You will be redirected to Taliesin with a valid login and able to use the application as administrator, enjoy! ### Use case: Configure a registration process with a confirmed e-mail address and OTP, WebAuthn or OAuth2 Client schemes Let's say you want to organize a conference with an open registration, you don't want your users to remember a password but you want a secure login. New users will use their e-mail address as username, will have to register a new OTP before completing registration, then will be allowed to register their Facebook or Google account to login to your application, and also will be allowed to register a WebAuthn device such as Yubikey or an android phone with fingerprint scan. This use case will describe configuration steps to have a Glewlwyd instance where new users will have to register an account using a verified e-mail address, at least the OTP scheme and optionally a WebAuthn or an external OAuth2 register (Google or Facebook in this example). This use case is based on the following assertions: - Glewlwyd is freshly installed with default configuration - Glewlwyd is installed on a machine and available at an external address, for the rest of this documentation, the following example address will be used: [https://glewlwyd.tld/](https://glewlwyd.tld/). #### Step 1: Register your Glewlwyd instance as an OAuth2 client at Facebook and Google Follow the procedure for each provider to create an OAuth2 client: - Facebook: [https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/](https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/) - Google: [https://developers.google.com/identity/protocols/OAuth2](https://developers.google.com/identity/protocols/OAuth2) For each provider, you must use the callback address [https://glewlwyd.tld/callback.tld](https://glewlwyd.tld/callback.tld) in your registration (replace `https://glewlwyd.tld/` with your glewlwyd instance external address). #### Step 2: Add authentication schemes You must instantiate the schemes [OTP](OTP.md), [WebAuthn](WEBAUTHN.md) and [OAuth2](OAUTH2_SCHEME.md) by using their respective documentation. #### Step 3: Add a new scope Add the [new scope](SCOPE.md) `conference`, disable `Password` in the Authentication paragraph, then add the 3 schemes to the scope `Additional authentication scheme` in the same group, see screenshot below. ![use case register scope](screenshots/usecase-register-scope.png) #### Step 4: Add a register plugin Add a new register plugin, name `register`: - `Password` set to `No` - `scopes to add` set to `conference` only - Add the schemes `OTP`, `oauth2` and `WebAuthn`, for the scope `OTP`, set `mandatory` to `Yes` - Check `verify e-mail` and `username is e-mail` - Enter your SMTP server configuration, at least `SMTP server` and `E-mail sender address` - In `E-mail body`, replace `` with `register` #### Step 5: Update webapp/config.json In the last step, you will have to update your `webapp/config.json` file to adapt your front-end to your configuration. ```javascript "defaultScheme": "otp", // This will be the default authentication scheme "sessionSchemes": [ { "scheme_name": "webauthn", "scheme_display_name": "login.webauthn-title", "scheme_type": "webauthn" }, { "scheme_name": "otp", "scheme_display_name": "login.otp-title", "scheme_type": "otp" }, { "scheme_name": "oauth2", "scheme_display_name": "login.oauth2-title", "scheme_type": "oauth2" } ], "register": [ { "name": "registration", "message": "login.register-link" } ], "register-complete": [ { "name": "registration", "complete-link": "https://www.example.com/", // Replace with the url of your application "complete-link-label": "profile.register-complete-link" } ], ``` ### Use case: Connect Webmail RainLoop with OpenID Connect plugin using Dovecot configured with a master password To connect to a RainLoop Webmail instance with Glewlwyd's OpenID Connect plugin, here is a validated setup. - **Add additional claim `email` in the ID Token** See [OIDC Configuration](OIDC.md#additional-claims-in-the-id-token-or-the-userinfo-endpoint). - **Configure Dovecot to allow connectig via [master password](https://doc.dovecot.org/configuration_manual/authentication/master_users/)** Sample dovecot config to add a master password file: ``` auth_master_user_separator = * passdb { driver = passwd-file args = /etc/dovecot/passwd.masterusers master = yes result_success = continue } ``` Use htpassword to create the file `passwd.masterusers`: ```shell $ htpasswd -b -c -s passwd.masterusers masteruser password ``` Security tip: restrict use of the master password to the ip address hosting Roundcube, append `::::::allow_nets=127.0.0.1/32` to your masterusers password record, the content of the file `passwd.masterusers` should look like this: ``` masteruser:{SHA}xxxyyyzzz=::::::allow_nets=127.0.0.1/32 ``` - **Configure [mod_auth_openidc](https://github.com/zmartzone/mod_auth_openidc) to authenticate via Glewlwyd before accessing the webmail** Sample apache.conf config: ``` Alias /webmail /path/to/rainloop OIDCProviderMetadataURL https://glewlwyd.tld/api/oidc/.well-known/openid-configuration OIDCClientID webmail_client OIDCClientSecret webmail_client_secret OIDCResponseType "code" OIDCScope "openid mail" OIDCOAuthRemoteUserClaim "userid" OIDCRedirectURI /webmail/callback OIDCCryptoPassphrase xyz123 OIDCSessionInactivityTimeout 3600 AuthType openid-connect Require valid-user Require all denied ``` - **Add a new rainloop plugin dedicated to map OIDC connection with Rainloop** Create a directory `oidc-master-password-login/` in the location `/data/_data_/_default_/plugins`, download the files from [this gist](https://gist.github.com/babelouest/9c0ca17224ade7c4e763b3c1c37d21ae) in this new directory. - **Enable and configure the rainloop plugin `oidc-master-password-login/`** Go to [RainLopp admin page](https://www.rainloop.net/docs/configuration/), on the plugin tab, enable `oidc-master-password-login`, then configure on the plugin to map your configuration: - Header username: `OIDC_CLAIM_userid` - Master Username and separator: `*masteruser` (don't forget the separator) - Master Password: your dovecot master password Now, when you open RainLoop url, you should be redirected to Glewlwyd login page first, then if successfull, you will be redirected to the RainLoop login page with the e-mail field filled with your e-mail address and a fony password, click on the login button, you are now successfully logged in RainLoop! ### User profile delegation ![user-list-delegate](screenshots/user-list-delegate.png) An connected administrator can update a user profile with the delegation functionality. A new window will open, allowing the administrator to update the user profile, register or de-register authentication schemes for the user. ![user-delegate](screenshots/user-delegate.png) ### Add or update additional properties for users and clients Glewlwyd is designed to allow administrators to add or update additional properties for users and clients. The following paragraphs will explain how to add a new property for a user. The same process for the client is similar. All additional properties are strings, no other data format is possible. #### Step 1: Add a specific data format Edit the user module parameters. In the User Backend settings modal, deploy `Specific data format` and click on the `+` button. Enter the property name, ex `postal-code`. The checkbox `multiple values` defines whether the new property values will be a single value or an array of multiple values. The checkboxes `Read (admin)` and `Write (admin)` define whether this property is readable and/or writable on the user list in the administration page. The checkboxes `Read (profile)` and `Read (profile)` define whether this property is readable and/or writable on the profile page. #### Step 2: Update webapp/config.json to show the new property Open the file `webapp/config.json` with a text editor. There, you must add a new JSON object in the array `pattern.user`. The user pattern format is the following: ```javascript { "name": "postal-code", // name of the new property, mandatory "type": "text", // values available are "text", "password" (hidden text), "boolean" (checkbox) or "textarea", optional, default "text" "list": true, // set this to true if the new property has `multiple values` checked, optional, default false "listElements": ["value1","value2"] // restrict the values available to this list if the new property has `multiple values` checked, optional "profile": false, // visible on the profile page, optional, default false "edit": true, // Can be updated, optional, default false "label": "admin.user-password", // i18next label name, label files is available in the files `webapp/locales/*/translations.json`, mandatory "placeholder": "admin.user-password-ph", // i18next placeholder value for text or password types, label files is available in the files `webapp/locales/*/translations.json`, optional, default empty "forceShow": true, // show the property even if the user has no value yet, optional, default false "required": true, // is the property mandatory?, optional, default false "profile-read": false, // can the user read the property in profile page, optional, default false "profile-write": false // can the user write the property in profile page, optional, default false } ``` The Postal Code pattern should look like this: ```javascript { "name": "postal-code", "list": false, "forceShow": true, "edit": true, "label": "admin.postal-code", "placeholder": "admin.postal-code-ph" } ``` And the new entries in `webapp/locales/*/translations.json` should look like this: ```javascript { // [...] "admin": { // [...] "postal-code": "Postal Code", "postal-code-ph": "Ex: X1Y 2Z3" } } ``` Then you should see the new property in the user edit modal: ![user-new-property](screenshots/user-new-property.png) ### Non-password authentication Glewlwyd allows non-password authentication. You can use any other scheme installed to authenticate a user. If a required scope has the option `Password` checked, the password will be mandatory to grant access to this scope. One or more schemes must be already installed: E-mail code, WebAuthn, Client certificate, HOTP/TOTP, etc. The scheme must be defined in the file `webapp/config.json` in the `sessionSchemes` array. The pattern is the following: ```javascript { "scheme_type": "webauthn", // name of the module, mandatory "scheme_name": "webauthn", // name of the module instance, mandatory "scheme_display_name": "login.webauthn-title" // i18next label name, label files is available in the files `webapp/locales/*/translations.json`, mandatory "identify": true // Does this scheme requires username first? Available only for schemes OAuth2 and TLS certificate with scheme storage option } ``` Then, in the login page for a new user, the drop-down `Scheme` will be available, allowing to authenticate with the schemes specified. The option `defaultScheme` in the `config.json` file can be used to overwrite password as default scheme to authenticate users. Fill this option with a scheme name present in the `sessionSchemes` array. Example: ```javascript "defaultScheme": "webauthn", ``` ![login-nopassword](screenshots/login-nopassword.png) ### Multiple password authentication This feature is new since Glewlwyd 2.5. If this option is enabled in a user backend module (except for the User Module HTTP Backend), users are allowed to have more than one password to authenticate in Glewlwyd services where password is required. The use-case why this feature was added is when the user backend is LDAP and this LDAP service is used to authenticate to other services than Glewlwyd, such as an IMAP service or network login. All those services can use different passwords, and those different passwords don't have to be remembered by the user if the user uses a password vault. Also, if one password is compromised, you can change only the compromised password without having to change the password in all the services. Be careful that every password will allow the same access level to your Glewlwyd service, so all of them must be strong enough to avoid being guessed. ### Test a client configuration with Idwcc The tool [idwcc](https://github.com/babelouest/iddawc/tree/master/tools/idwcc) is available via the library [iddawc](https://github.com/babelouest/iddawc) to test a client configuration with Glewlwyd (or any other OAuth2/OIDC provider). You can build the tool yourself, install it via a [pre-compiled package](https://github.com/babelouest/iddawc/releases/latest), or use the [docker image](https://github.com/babelouest/iddawc/tree/master/tools/idwcc#docker-image) available. See the [idwcc readme](https://github.com/babelouest/iddawc/blob/master/tools/idwcc/README.md) for more details. ## Troubleshooting ### Impossible to log in as administrator - N-factor issue If for any reason, the n-factor(s) scheme(s) added to the scope `g_admin` can't be used anymore, you can reset the scope `g_admin` to its original setup, i.e. password only, no n-factor authentication. - Step 1 (optional but recommended): to use Glewlwyd in a secured channel, you can disconnect Glewlwyd from the outside world, in order to avoid opportunity attacks when the security will be lowered. i.e.: disable the reverse proxy if you have one and access your Glewlwyd instance directly, or add rules to yuor firewall. - Step 2: Backup your database - Step 3: connect to your database and execute the following SQL queries: ```SQL -- Remove all n-factor authentication to scope DELETE FROM g_scope_group_auth_scheme_module_instance WHERE gsg_id IN (SELECT gsg_id FROM g_scope_group WHERE gs_id IN (SELECT gs_id FROM g_scope WHERE gs_name='g_admin')); DELETE FROM g_scope_group WHERE gs_id IN (SELECT gs_id FROM g_scope WHERE gs_name='g_admin'); -- Set scope settings to password required and 10 minutes session UPDATE g_scope SET gs_password_required=1, gs_password_max_age=600 WHERE gs_name='gs_admin'; ``` ### Impossible to log in as administrator - Password lost If you lost your administrator password, assuming you use the database backend, you can reset it with one of the following SQL queries, depending on your database type: ```SQL -- MariaDB/MySQL UPDATE g_user SET gu_password=PASSWORD('password'), gu_enabled=1 WHERE gu_username='admin'; -- SQlite3 UPDATE g_user SET gu_password='fOfvZC/wR2cUSTWbW6YZueGyyDuFqwkoFlcNlRYWJscxYTVOVFJ3VWFHdVJQT0pU', gu_enabled=1 WHERE gu_username='admin'; -- PostgreSQL UPDATE g_user SET gu_password=crypt('password', gen_salt('bf')), gu_enabled=1 WHERE gu_username='admin'; ``` glewlwyd-2.6.1/docs/HTTP.md000066400000000000000000000043161415646314000154170ustar00rootroot00000000000000# Glewlwyd HTTP Basic Authentication Schema documentation [![License: CC BY 4.0](https://licensebuttons.net/l/by/4.0/80x15.png)](https://creativecommons.org/licenses/by/4.0/) ![scheme-http](screenshots/scheme-http.png) The HTTP Basic Authentication performs a login/password authentication against a specified webservice that requires HTTP Basic Authentication. This scheme is similar to [HTTP authentication backend](#http-authentication) but requires users to be already added to any backend (Database or LDAP). The advantage is that you can specify different scopes for each users and add any other additional data for the users. ## Installation In the administration page, go to `Parameters/Authentication schemes` and add a new scheme by clicking on the `+` button. In the modal, enter a name and a display name (the name must be unique among all authentication scheme instances), and a scheme session expiration in seconds. Select the type `HTTP Basic Authentication` in the Type drop-down button. Below is the definition of all parameters. ### Name Name (identifier) of the scheme, must be unique among all the scheme instances, even of a different type. ### Display name Name of the instance displayed to the user. ### Expiration (seconds) Number of seconds to expire a valid session. ### Max use per session (0: unlimited) Maximum number of times a valid authentication with this scheme is possible. This is an additional parameter used to enforce the security of the session and forbid to reuse this session for other authentications. ### Allow users to register If this option is unchecked, only administrator can register this scheme for every user via the administration page. ### URL URL of the HTTP service to connect to. ### Validate server certificate Check this option if the HTTP service uses TLS and if you want to validate the certificate. ### Username format on HTTP server This option can be used to build the `auth_basic_user` value using the user properties values in a specified format. You can use any user property values as long as it is not a list. The property must be specified surrounded by `{}`. Format examples: - `{username}@glewlwyd.tld` - `{domain}/{username}` - `{specific_property}_{phone}` glewlwyd-2.6.1/docs/INSTALL.md000066400000000000000000001674221415646314000157560ustar00rootroot00000000000000# Installation [![License: CC BY 4.0](https://licensebuttons.net/l/by/4.0/80x15.png)](https://creativecommons.org/licenses/by/4.0/) 1. [Upgrade Glewlwyd](#upgrade-glewlwyd) * [Upgrade to Glewlwyd 2.6.1](#upgrade-to-glewlwyd-261) * [Upgrade to Glewlwyd 2.6.0](#upgrade-to-glewlwyd-260) * [Upgrade to Glewlwyd 2.5.4](#upgrade-to-glewlwyd-254) * [Upgrade to Glewlwyd 2.5.0](#upgrade-to-glewlwyd-250) * [Upgrade to Glewlwyd 2.4.0](#upgrade-to-glewlwyd-240) * [Upgrade to Glewlwyd 2.3.3](#upgrade-to-glewlwyd-233) * [Upgrade to Glewlwyd 2.3.x](#upgrade-to-glewlwyd-23x) * [Upgrade to Glewlwyd 2.2.x](#upgrade-to-glewlwyd-22x) * [Upgrade to Glewlwyd 2.1.x](#upgrade-to-glewlwyd-21x) 2. [Distribution packages](#distribution-packages) 3. [Pre-compiled packages](#pre-compiled-packages) * [Install Glewlwyd on Debian Bullseye](#install-glewlwyd-on-debian-bullseye) * [Install Glewlwyd on Raspbian Bullseye for Raspberry Pi](#install-glewlwyd-on-raspbian-bullseye-for-raspberry-pi) * [Install Glewlwyd on Ubuntu 20.04 LTS Focal](#install-glewlwyd-on-ubuntu-2004-lts-focal) 4. [Docker](#docker) 5. [Manual install from source](#manual-install-from-source) * [Dependencies](#dependencies) * [Build Glewlwyd and its dependencies](#build-glewlwyd-and-its-dependencies) 6. [Configure Glewlwyd](#configure-glewlwyd) * [Port number](#port-number) * [Bind address](#bind-address) * [External URL](#external-url) * [API Prefix](#api-prefix) * [Login URL](#login-url) * [Delete profile](#delete-profile) * [Static files path](#static-files-path) * [Static files mime types](#static-files-mime-types) * [Allow Origin](#allow-origin) * [Logs](#logs) * [Cookies configuration](#cookies-configuration) * [Default scope names](#default-scope-names) * [Modules paths](#modules-paths) * [Digest algorithm](#digest-algorithm) * [SSL/TLS](#ssltls) * [Database back-end initialisation](#database-back-end-initialisation) 7. [Initialise database](#initialise-database) 8. [Install as a service](#install-as-a-service) 9. [Reverse proxy configuration](#reverse-proxy-configuration) 10. [Fail2ban filter](#fail2ban-filter) 11. [Front-end application](#front-end-application) * [webapp/config.json](#webappconfigjson) * [Internationalization](#internationalization) * [Login, Admin and Profile pages](#login-admin-and-profile-pages) * [Customize CSS](#customize-css) * [Customize titles and logos](#customize-titles-and-logos) 12. [Run Glewlwyd](#run-glewlwyd) 13. [Event logs triggered](#event-logs-triggered) 14. [Getting started with the application](#getting-started-with-the-application) 15. [Running Glewlwyd in test mode for integration tests](#running-glewlwyd-in-test-mode-for-integration-tests) ## Upgrade Glewlwyd Glewlwyd upgrades usually come with database changes. It is highly recommended to backup your database before performing the upgrade. You must perform the database upgrades in the correct order. i.e. if you upgrade from Glewlwyd 2.3 to Glewlwyd 2.6, you must first install the 2.4 upgrade, then the 2.5. ### Upgrade to Glewlwyd 2.6.1 This is a security release, please upgrade your Glewlwyd version. ### Upgrade to Glewlwyd 2.6.0 If your current version is prior to 2.5.0, first follow the security instructions in the paragraph [Upgrade to Glewlwyd 2.5.0](#upgrade-to-glewlwyd-250). Some changes were added to the core tables. You must execute the script depending on your database backend: - MariaDB: [upgrade-2.6-core.mariadb.sql](database/upgrade-2.5-core.mariadb.sql) ```shell $ mysql glewlwyd < docs/database/upgrade-2.6-core.mariadb.sql ``` - SQLite3: [upgrade-2.6-core.sqlite3.sql](database/upgrade-2.6-core.sqlite3.sql) ```shell $ sqlite3 /path/to/glewlwyd.db < docs/database/upgrade-2.6-core.sqlite3.sql ``` - PostgreSQL: [upgrade-2.5-core.postgresql.sql](database/upgrade-2.6-core.postgresql.sql) ```shell $ psql glewlwyd < docs/database/upgrade-2.6-core.postgresql.sql ``` ### Upgrade to Glewlwyd 2.5.4 This is a security release, please upgrade your Glewlwyd version. ### Upgrade to Glewlwyd 2.5.0 If your current version is prior to 2.4.0, first follow the security instructions in the paragraph [Upgrade to Glewlwyd 2.4.0](#upgrade-to-glewlwyd-240). Some changes were added to the core tables. You must execute the script depending on your database backend: - MariaDB: [upgrade-2.5-core.mariadb.sql](database/upgrade-2.5-core.mariadb.sql) ```shell $ mysql glewlwyd < docs/database/upgrade-2.5-core.mariadb.sql ``` - SQLite3: [upgrade-2.5-core.sqlite3.sql](database/upgrade-2.5-core.sqlite3.sql) ```shell $ sqlite3 /path/to/glewlwyd.db < docs/database/upgrade-2.5-core.sqlite3.sql ``` - PostgreSQL: [upgrade-2.5-core.postgresql.sql](database/upgrade-2.5-core.postgresql.sql) ```shell $ psql glewlwyd < docs/database/upgrade-2.5-core.postgresql.sql ``` ### Upgrade to Glewlwyd 2.4.0 If your current version is prior to 2.3.3, first follow the security instructions in the paragraph [Upgrade to Glewlwyd 2.3.3](#upgrade-to-glewlwyd-233). #### Mandatory core tables upgrade Small changes were added to the core tables. You must execute the script depending on your database backend: - MariaDB: [upgrade-2.4-core.mariadb.sql](database/upgrade-2.4-core.mariadb.sql) ```shell $ mysql glewlwyd < docs/database/upgrade-2.4-core.mariadb.sql ``` - SQLite3: [upgrade-2.4-core.sqlite3.sql](database/upgrade-2.4-core.sqlite3.sql) ```shell $ sqlite3 /path/to/glewlwyd.db < docs/database/upgrade-2.3-core.sqlite3.sql ``` - PostgreSQL: [upgrade-2.4-core.postgresql.sql](database/upgrade-2.4-core.postgresql.sql) ```shell $ psql glewlwyd < docs/database/upgrade-2.4-core.postgresql.sql ``` ### Upgrade to Glewlwyd 2.3.3 This is a security release, please upgrade your Glewlwyd version. To mitigate server configuration leaks, I recommend the following actions: - If you use the TLS Certificate Scheme with [Allow to emit PKCS#12 certificates for the clients](https://github.com/babelouest/glewlwyd/blob/2.3/docs/CERTIFICATE.md#allow-to-emit-pkcs12-certificates-for-the-clients) enabled, please revoke the issuer certificate and use new ones - If you use the WebAuthn Scheme, it's recommended to regenerate the [Random seed used to mitigate intrusion](https://github.com/babelouest/glewlwyd/blob/2.3/docs/WEBAUTHN.md#random-seed-used-to-mitigate-intrusion) - If you use the Oauth2 Scheme, please change the [clients secrets](https://github.com/babelouest/glewlwyd/blob/2.3/docs/OAUTH2_SCHEME.md#secret) - If you use the Email code scheme and use a [SMTP password](https://github.com/babelouest/glewlwyd/blob/2.3/docs/EMAIL.md#smtp-password-if-required), please to change this password ### Upgrade to Glewlwyd 2.3.x #### Mandatory core tables upgrade Small changes were added to the core tables. You must execute the script depending on your database backend: - MariaDB: [upgrade-2.3-core.mariadb.sql](database/upgrade-2.3-core.mariadb.sql) ```shell $ mysql glewlwyd < docs/database/upgrade-2.3-core.mariadb.sql ``` - SQLite3: [upgrade-2.3-core.sqlite3.sql](database/upgrade-2.3-core.sqlite3.sql) ```shell $ sqlite3 /path/to/glewlwyd.db < docs/database/upgrade-2.3-core.sqlite3.sql ``` - PostgreSQL: [upgrade-2.3-core.postgresql.sql](database/upgrade-2.3-core.postgresql.sql) ```shell $ psql glewlwyd < docs/database/upgrade-2.3-core.postgresql.sql ``` ### Upgrade to Glewlwyd 2.2.x #### Mandatory core tables upgrade Small changes were added to the core tables. You must execute the script depending on your database backend: - MariaDB: [upgrade-2.2-core.mariadb.sql](database/upgrade-2.2-core.mariadb.sql) ```shell $ mysql glewlwyd < docs/database/upgrade-2.2-core.mariadb.sql ``` - SQLite3: [upgrade-2.2-core.sqlite3.sql](database/upgrade-2.2-core.sqlite3.sql) ```shell $ sqlite3 /path/to/glewlwyd.db < docs/database/upgrade-2.2-core.sqlite3.sql ``` - PostgreSQL: [upgrade-2.2-core.postgresql.sql](database/upgrade-2.2-core.postgresql.sql) ```shell $ psql glewlwyd < docs/database/upgrade-2.2-core.postgresql.sql ``` #### Scheme OAuth2/OIDC In Glewlwyd 2.2, the new scheme [OAuth2/OIDC external login](OAUTH2_SCHEME.md) was introduced. To use this module, you must create its required tables by executing the script depending on your database backend: - MariaDB: [oauth2.mariadb.sql](../src/scheme/oauth2.mariadb.sql) ```shell $ mysql glewlwyd < src/scheme/oauth2.mariadb.sql ``` - SQLite3: [oauth2.sqlite3.sql](../src/scheme/oauth2.sqlite3.sql) ```shell $ sqlite3 /path/to/glewlwyd.db < src/scheme/oauth2.sqlite3.sql ``` - PostgreSQL: [oauth2.postgresql.sql](../src/scheme/oauth2.postgresql.sql) ```shell $ psql glewlwyd < src/scheme/oauth2.postgresql.sql ``` ### Upgrade to Glewlwyd 2.1.x In Glewlwyd 2.1, the plugin module [register](REGITSER.md) has appear. In order to use this module, you must add its tables by executing the script depending on your database backend: - MariaDB: [register.mariadb.sql](../src/plugin/register.mariadb.sql) ```shell $ mysql glewlwyd < src/plugin/register.mariadb.sql ``` - SQLite3: [register.sqlite3.sql](../src/plugin/register.sqlite3.sql) ```shell $ sqlite3 /path/to/glewlwyd.db < src/plugin/register.sqlite3.sql ``` - PostgreSQL: [register.postgresql.sql](../src/plugin/register.postgresql.sql) ```shell $ psql glewlwyd < src/plugin/register.postgresql.sql ``` ## Distribution packages [![Packaging status](https://repology.org/badge/vertical-allrepos/glewlwyd.svg)](https://repology.org/metapackage/glewlwyd) Glewlwyd is available in some distributions as official package. Check out your distribution documentation to install the package automatically. ```shell $ # Example to install Glewlwyd 2.5.2 on Debian Buster $ apt install glewlwyd ``` ## Pre-compiled packages You can install Glewlwyd with a pre-compiled package available in the [release pages](https://github.com/babelouest/glewlwyd/releases/). The package files `glewlwyd-full_*` contain the package libraries of `orcania`, `yder`, `ulfius`, `hoel`, `rhonabwy`, `iddawc` pre-compiled for `glewlwyd`, plus `glewlwyd` package. To install a pre-compiled package, you need the following libraries installed: ``` libmicrohttpd libjansson libcurl libldap2 libmariadbclient libsqlite3 libpq libconfig libgnutls liboath libcbor libzlib ``` ### Install Glewlwyd on Debian Bullseye ```shell $ sudo apt install -y sqlite3 liboath0 libconfig9 libjansson4 libcurl3-gnutls libldap-2.4-2 libmicrohttpd12 libsqlite3-0 libpq5 default-mysql-client zlib1g libcbor0 pkg-config $ wget https://github.com/babelouest/glewlwyd/releases/download/v2.6.0/glewlwyd-full_2.6.0_debian_bullseye_x86_64.tar.gz $ tar xf glewlwyd-full_2.6.0_debian_bullseye_x86_64.tar.gz $ sudo dpkg -i liborcania_2.2.1_debian_bullseye_x86_64.deb $ sudo dpkg -i libyder_1.4.14_debian_bullseye_x86_64.deb $ sudo dpkg -i libhoel_1.4.18_debian_bullseye_x86_64.deb $ sudo dpkg -i libulfius_2.7.6_debian_bullseye_x86_64.deb $ sudo dpkg -i librhonabwy_1.1.2_debian_bullseye_x86_64.deb $ sudo dpkg -i libiddawc_1.1.1_debian_bullseye_x86_64.deb $ sudo dpkg -i glewlwyd_2.6.0_debian_bullseye_x86_64.deb ``` ### Install Glewlwyd on Raspbian Bullseye for Raspberry Pi ```shell $ sudo apt install -y sqlite3 liboath0 libconfig9 libjansson4 libcurl3-gnutls libldap-2.4-2 libmicrohttpd12 libsqlite3-0 libpq5 default-mysql-client zlib1g libcbor0 pkg-config $ wget https://github.com/babelouest/glewlwyd/releases/download/v2.6.0/glewlwyd-full_2.6.0_raspbian_bullseye_armv7l.tar.gz $ tar xf glewlwyd-full_2.6.0_raspbian_bullseye_x86_64.tar.gz $ sudo dpkg -i liborcania_2.2.1_raspbian_bullseye_armv7l.deb $ sudo dpkg -i libyder_1.4.14_raspbian_bullseye_armv7l.deb $ sudo dpkg -i libhoel_1.4.18_raspbian_bullseye_armv7l.deb $ sudo dpkg -i libulfius_2.7.6_raspbian_bullseye_armv7l.deb $ sudo dpkg -i librhonabwy_1.1.2_raspbian_bullseye_armv7l.deb $ sudo dpkg -i libiddawc_1.1.1_raspbian_bullseye_armv7l.deb $ sudo dpkg -i glewlwyd_2.6.0_raspbian_bullseye_armv7l.deb ``` ### Install Glewlwyd on Ubuntu 20.04 LTS Focal ```shell $ sudo apt install -y sqlite3 liboath0 libconfig9 libjansson4 libcurl3-gnutls libldap-2.4-2 libmicrohttpd12 libsqlite3-0 libpq5 default-mysql-client zlib1g libcbor0.6 pkg-config $ wget https://github.com/babelouest/glewlwyd/releases/download/v2.6.0/glewlwyd-full_2.6.0_ubuntu_focal_x86_64.tar.gz $ tar xf glewlwyd-full_2.6.0_ubuntu_focal_x86_64.tar.gz $ sudo dpkg -i liborcania_2.2.1_ubuntu_focal_x86_64.deb $ sudo dpkg -i libyder_1.4.14_ubuntu_focal_x86_64.deb $ sudo dpkg -i libhoel_1.4.18_ubuntu_focal_x86_64.deb $ sudo dpkg -i libulfius_2.7.6_ubuntu_focal_x86_64.deb $ sudo dpkg -i librhonabwy_1.1.2_ubuntu_focal_x86_64.deb $ sudo dpkg -i libiddawc_1.1.1_ubuntu_focal_x86_64.deb $ sudo dpkg -i glewlwyd_2.6.0_ubuntu_focal_x86_64.deb ``` If there's no package available for your distribution, you can compile it manually using `CMake` or `Makefile`. ## Docker The docker page is available at the following address: [https://hub.docker.com/r/babelouest/glewlwyd](https://hub.docker.com/r/babelouest/glewlwyd) ### Quick start for tests only Run the official docker image `babelouest/glewlwyd` hosted on docker cloud, example: ```shell docker run --rm -it -p 4593:4593 babelouest/glewlwyd ``` - User: `admin` - Password : `password` This image configuration uses a SQLite3 database hosted inside the docker instance, so all data will be lost when the docker instance will be stopped. Also, this docker instance can be accessible vie the address [http://localhost:4593/](http://localhost:4593). In this instance, both configuration files `glewlwyd.conf` (backend) and `config.json` (front-end) are stored in `/etc/glewlwyd`. If you need to make the docker instance available in a network, you must update the configuration files as explained below by updating at least the configuration variable `external_url`. **Customize configuration without rebuilding the docker image** You can overwrite the configuration files `glewlwyd.conf` and `config.json` by mounting a volume on `/etc/glewlwyd` when you run the docker image. Point this volume to a local directory on the host. You can use the files [docker/config/glewlwyd.conf](docker/config/glewlwyd.conf) and [docker/config/config.json](docker/config/config.json) as a starting point to build your config files for docker. You can also use environment variables to override config file values. See [Configure Glewlwyd](#configure-glewlwyd) for a complete list of configuration variables. ```shell $ # Run docker instance with a new set of config files $ docker run -p 4593:4593 -v /path/to/your/config:/etc/glewlwyd $ # Run docker instance with default config files but override external url and database connection using env variables $ docker run -p 4593:4593 -e GLWD_EXTERNAL_URL=https://glewlwyd.tld -e GLWD_DATABASE_TYPE=postgre -e GLWD_DATABASE_POSTGRE_CONNINFO="host=dbhost port=5432 dbname=glewlwyd user=glewlwyd password=secret" babelouest/glewlwyd $ # Run docker instance with a new set of config files and an overwritten external url using env variables $ docker run -p 4593:4593 -v /path/to/your/config:/etc/glewlwyd -e GLWD_EXTERNAL_URL=https://glewlwyd.tld babelouest/glewlwyd ``` ### Docker image builder The root directory contains a Docker file to build the docker image from the source. To build your own docker image, go to Glewlwyd source root directory and run `make docker`. This will build a docker image called `babelouest/glewlwyd:src`. ```shell $ make docker $ docker run --rm -it -p 4593:4593 -v /path/to/your/config:/etc/glewlwyd babelouest/glewlwyd:src ``` You can use the same options than in the official docker image, including customized configuration. ## Manual install from source Glewlwyd has been successfully compiled for the following distributions: - Fedora 29+ - OpenSuse Leap 15 - OpenSuse Tumbleweed - Alpine Linux - Free BSD And probably more! Let me know if Glewlwyd can be installed and working on your distribution by opening an [issue](https://github.com/babelouest/glewlwyd/issues), if needed, you can describe the non-documented commands required for your case. Download the [latest source tarball](https://github.com/babelouest/glewlwyd/releases/latest) or [git clone](https://github.com/babelouest/glewlwyd.git) from master branch in GitHub. ### Dependencies On a Debian based distribution (Debian, Ubuntu, Raspbian, etc.), you can install those dependencies using the following command: ```shell $ sudo apt-get install libmicrohttpd-dev sqlite3 libsqlite3-dev default-libmysqlclient-dev libpq-dev libgnutls28-dev libconfig-dev libldap2-dev liboath-dev libcbor-dev libsystemd-dev libjansson-dev libcurl4-gnutls-dev cmake ``` #### Libmicrohttpd 0.9.38 minimum required With Libmicrohttpd 0.9.37 and older version, there is a bug when parsing `application/x-www-form-urlencoded` parameters. This is fixed in later version, from the 0.9.38, so if your Libmicrohttpd version is older than that, I suggest getting a newer version of [libmicrohttpd](https://www.gnu.org/software/libmicrohttpd/). #### Libmicrohttpd 0.9.71 minimum recommended A bug has been [fixed](https://git.gnunet.org/libmicrohttpd.git/tree/ChangeLog?h=v0.9.70#n9) in Libmicrohttpd 0.9.70 related to [Jenkins OIDC plugin](https://wiki.jenkins.io/display/JENKINS/Openid+Connect+Authentication+Plugin) (see issue #89), and a security issue has fixed in Libmicrohttpd 0.9.71. It is recommended to install Libmicrohttpd 0.9.71 minimum to avoid these problems. ### Build Glewlwyd and its dependencies #### CMake Download Glewlwyd from GitHub, then use the CMake script to build the application. CMake will automatically download and build Iddawc, Rhonabwy, Ulfius, Hoel, Yder and Orcania if they are not installed on the system. ```shell # Install Glewlwyd $ git clone https://github.com/babelouest/glewlwyd.git $ mkdir glewlwyd/build $ cd glewlwyd/build $ cmake .. $ make $ sudo make install ``` The available options for CMake are: - `-DDOWNLOAD_DEPENDENCIES=[on|off]` (default `on`): Download some dependencies if missing or using an old version: `Orcania`, `Yder`, `Ulfius`, `Rhonabwy`, `Iddawc` and `Hoel` - `-DWITH_JOURNALD=[on|off]` (default `on`): Build with journald (SystemD) support - `-DCMAKE_BUILD_TYPE=[Debug|Release]` (default `Release`): Compile with debugging symbols or not - `-DWITH_SQLITE3=[on|off]` (default `on`): Enable/disable SQLite3 database backend: This option is passed to Hoel library builder - `-DWITH_MARIADB=[on|off]` (default `on`): Enable/disable MariaDB/MySQL database backend: This option is passed to Hoel library builder - `-DWITH_PGSQL=[on|off]` (default `on`): Enable/disable PostgreSQL database backend: This option is passed to Hoel library builder - `-DWITH_JOURNALD=[on|off]` (default `on`): Build with journald (SystemD) support for logging: This option is passed to Yder library builder - `-DBUILD_GLEWLWYD_TESTING=[on|off]` (default `off`): Build testing tree - `-DWITH_MOCK=[on|off]` (default `off`): Build mock modules, for development use only! - `-DWITH_USER_DATABASE=[on|off]` (default `on`): Build user database backend module - `-DWITH_USER_LDAP=[on|off]` (default `on`): Build user LDAP backend module - `-DWITH_USER_HTTP=[on|off]` (default `on`): Build user HTTP auth backend module - `-DWITH_CLIENT_DATABASE=[on|off]` (default `on`): Build client database backend module - `-DWITH_CLIENT_LDAP=[on|off]` (default `on`): Build client LDAP backend module - `-DWITH_SCHEME_RETYPE_PASSWORD=[on|off]` (default `on`): Build authentication scheme `retype password` - `-DWITH_SCHEME_EMAIL=[on|off]` (default `on`): Build authentication scheme `e-mail code` - `-DWITH_SCHEME_OTP=[on|off]` (default `on`): Build authentication scheme `OTP` - `-DWITH_SCHEME_WEBAUTHN=[on|off]` (default `on`): Build authentication scheme `WebAuthn` - `-DWITH_PLUGIN_OAUTH2=[on|off]` (default `on`): Build Plugin `Glewlwyd OAuth2` - `-DWITH_PLUGIN_OIDC=[on|off]` (default `on`): Build Plugin `OpenID Connect` #### Good ol' Makefile Download Glewlwyd and its dependencies hosted on GitHub, compile and install. ```shell # Install Orcania $ git clone https://github.com/babelouest/orcania.git $ cd orcania/src/ $ make $ sudo make install # Install Yder $ git clone https://github.com/babelouest/yder.git $ cd yder/src/ $ make # Or make Y_DISABLE_JOURNALD=1 to disable journald logging $ sudo make install # Install Ulfius $ git clone https://github.com/babelouest/ulfius.git $ cd ulfius/src/ $ make $ sudo make install # Install Hoel $ git clone https://github.com/babelouest/hoel.git $ cd hoel/src/ $ make $ sudo make install # Install Rhonabwy $ git clone https://github.com/babelouest/rhonabwy.git $ cd rhonabwy/src/ $ make $ sudo make install # Install Iddawc $ git clone https://github.com/babelouest/iddawc.git $ cd iddawc/src/ $ make $ sudo make install # Install Glewlwyd $ git clone https://github.com/babelouest/glewlwyd.git $ cd glewlwyd/src/ $ make $ sudo make install ``` ### Note for distribution packaging Although the cmake script automatically download and install the following dependencies: `Orcania`, `Yder`, `Ulfius`, `Rhonabwy`, `Iddawc` and `Hoel`, it's highly recommended to package those dependencies separately and not to include those in Glewlwyd package. Use the cmake option `-DDOWNLOAD_DEPENDENCIES=OFF` when building Glewlwyd for the distribution package. ## Configure glewlwyd Glewlwyd requires several configuration variables to work. You can specify those variables in a configuration file, environment variables, or both. In addition, some variables can be set via command-line arguments. To run Glewlwyd with the config file, copy `glewlwyd.conf.sample` to `glewlwyd.conf`, edit the file `glewlwyd.conf` with your own settings. The following paragraphs describe all the configuration parameters. To enable environment variables in Glewlwyd, you must execute the program with the `-e` command-line argument. You can use environment variable configuration, configuration file and command-line arguments at the same time, just keep in mind the following priority order: command-line arguments have the higher priority, followed by the environment variables, then the configuration file. When you change the configuration file or the environment variables values, you must restart Glewlwyd to use the new configuration. ### Port number - Config file variable: `port` - Environment variable: `GLWD_PORT` Optional, The TCP port the service will listen to incoming connections. The port number must be available to the user running Glewlwyd process. Default value is 4593. ### Bind address - config file variable: `bind_address` - Environment variable: `GLWD_BIND_ADDRESS` Optional, use this address to bind incoming connections, can be use to restrict glewlwyd service to listen to a specific network, or localhost. Must be an IPV4 address. If not set or empty, all addresses will be able to connect to Glewlwyd. Note: this is NOT a `listen` option, this setting means that Glewlwyd will accept connection sent to this address only, not from it. ### External URL - Config file variable: `external_url` - Environment variable: `GLWD_EXTERNAL_URL` Mandatory, exact value of the external URL where this instance will be accessible to users, ex `https://glewlwyd.tld` ### API Prefix - Config file variable: `api_prefix` - Environment variable: `GLWD_API_PREFIX` Optional, the URL prefix where Glewlwyd's APIs will be available. Default value is `/api`. ### Login URL - Config file variable: `login_url` - Environment variable: `GLWD_LOGIN_URL` Optional, name of the login page. Default value is `login.html` ### Delete profile - Config file variable: `delete_profile` - Environment variable: `GLWD_PROFILE_DELETE` Optional, whether the user can remove its own account or not. Values available are: - `no`: The user can't remove its own account - `disable`: If the user removes its own account, the account will be disabled but not removed - `delete`: If the user removes its own account, the account and the schemes registration will be completely removed ### Static files path - Config file variable: `static_files_path` - Environment variable: `GLWD_STATIC_FILES_PATH` Optional, local path to the webapp files. If not set, the front-end application will not be available, only the APIs. ### Static files mime types - Config file variable: `static_files_mime_types` - Environment variable: `GLWD_STATIC_FILES_MIME_TYPES` in JSON array format, example `'[{"extension":".html","mime_type":"text/html","compress":true}{"extension":".css","mime_type":"text/css","compress":true}{"extension":".png","mime_type":"image/png","compress":false}]'` Optional, list of mime types for the webapp files. ### Allow Origin - Config file variable: `allow_origin` - Environment variable: `GLWD_ALLOW_ORIGIN` ### Logs #### Log Mode: - Config file variable: `log_mode` - Environment variable: `GLWD_LOG_MODE` - Command-line argument: `-m` or `--log-mode=` ### Log Level - Config file variable: `log_level` - Environment variable: `GLWD_LOG_LEVEL` - Command-line argument: `-l` or `--log-level=` ### Log File Path - Config file variable: `log_file` - Environment variable: `GLWD_LOG_FILE` - Command-line argument: `-f` or `--log-file=` Optional. Default no logs. Log modes available are `console`, `journald`, `syslog`, `file`. Multiple values must be separated by a comma, example `console,syslog`. Log levels available are `NONE`, `ERROR`, `WARNING`, `INFO`, `DEBUG`. If log mode `file` is set, log file path must be set to a file path where Glewlwyd process has write access. ### Cookies configuration #### Cookie domain - Config file variable: `cookie_domain` - Environment variable: `GLWD_COOKIE_DOMAIN` #### Cookie secure - Config file variable: `cookie_secure` - Environment variable: `GLWD_COOKIE_SECURE` Optional. Default, `cookie_secure` is `false`. The sample config file has the following cookies configuration: ``` # cookie domain cookie_domain="localhost" # cookie_secure, this options SHOULD be set to 1, set this to 0 to test glewlwyd on insecure connection http instead of https cookie_secure=0 ``` You must change the value `cookie_domain` accordingly to the domain name Glewlwyd will be available to. You can disable this option if you need to, but it's highly NOT recommended: ``` #cookie_domain="localhost" ``` The parameter `cookie_secure` is set to 0 by default, but since you should use Glewlwyd in a https connection, you should set this option to 1. ### HTTP header X-Frame-Options - Config file variable: `add_x_frame_option_header_deny` - Environment variable: `GLWD_ADD_X_FRAME_DENY` Optional. Default, is `true`. It's recommended to leave this value to `true`, unless you need to embed glewlwyd in an iFrame. ### Cookie Session values #### Cookie Session expiration (in seconds) - Config file variable: `session_expiration` - Environment variable: `GLWD_SESSION_EXPIRATION` #### Cookie Session key - Config file variable: `session_key` - Environment variable: `GLWD_SESSION_KEY` Optional, default values are: ``` session_expiration = 2419200 session_key = GLEWLWYD2_SESSION_ID ``` ### Default scope names #### Admin scope - Config file variable: `admin_scope` - Environment variable: `GLWD_ADMIN_SCOPE` #### Profile scope - Config file variable: `profile_scope` - Environment variable: `GLWD_PROFILE_SCOPE` Optional, default values are: ``` admin_scope="g_admin" profile_scope="g_profile" ``` ### Modules paths #### User modules path - Config file variable: `user_module_path` - Environment variable: `GLWD_USER_MODULE_PATH` Mandatory, path to user modules. #### Client modules path - Config file variable: `client_module_path` - Environment variable: `GLWD_CLIENT_MODULE_PATH` Mandatory, path to client modules. #### User auth scheme modules path - Config file variable: `user_auth_scheme_module_path` - Environment variable: `GLWD_AUTH_SCHEME_MODULE_PATH` Mandatory, path to authentication scheme modules. #### Plugin modules path - Config file variable: `plugin_module_path` - Environment variable: `GLWD_PLUGIN_MODULE_PATH` Mandatory, path to plugin modules. ### Digest algorithm - Config file variable: `hash_algorithm` - Environment variable: `GLWD_HASH_ALGORITHM` Optional, default value is `SHA256`. Specify in the config file the parameter `hash_algorithm` to store token and secret digests. Algorithms available are `SHA1`, `SHA256`, `SHA512` and `MD5`. Algorithms recommended are `SHA256` or `SHA512`. ### SSL/TLS #### Use secure connection - Config file variable: `use_secure_connection` - Environment variable: `GLWD_USE_SECURE_CONNECTION` #### Secure connection key file - Config file variable: `secure_connection_key_file` - Environment variable: `GLWD_SECURE_CONNECTION_KEY_FILE` #### Secure connection pem file - Config file variable: `secure_connection_pem_file` - Environment variable: `GLWD_SECURE_CONNECTION_PEM_FILE` #### Secure connection ca file This configuration is mandatory only if you want to use TLS Certificate authentication schemes in direct access, it must contain the CA certificate file used to authenticate clients certificates. Otherwise you can skip it. If this option is set, users can still connect to Glewlwyd without TLS certificate. - Config file variable: `secure_connection_ca_file` - Environment variable: `GLWD_SECURE_CONNECTION_CA_FILE` OAuth 2 specifies that a secured connection is mandatory, via SSL or TLS, to avoid data and token to be stolen, or Man-In-The-Middle attacks. Glewlwyd supports starting a secure connection with a private/public key certificate, but it also can be with a classic non-secure HTTP connection, and be available to users behind a HTTPS proxy for example. Glewlwyd won't check that you use it in a secure connection, but you should. These configuration variables are optional. Default is no secure connection. ### Database back-end initialisation Configure your database backend according to the database you will use. ``` # MariaDB/Mysql configuration file variables database = { type = "mariadb" host = "localhost" user = "glewlwyd" password = "glewlwyd" dbname = "glewlwyd" port = 0 } # MariaDB/Mysql environment variables GLWD_DATABASE_TYPE must be set to "mariadb" GLWD_DATABASE_MARIADB_HOST GLWD_DATABASE_MARIADB_USER GLWD_DATABASE_MARIADB_PASSWORD GLWD_DATABASE_MARIADB_DBNAME GLWD_DATABASE_MARIADB_PORT # SQLite database configuration file variables database = { type = "sqlite3" path = "/var/cache/glewlwyd/glewlwyd.db" } # SQLite database environment variables GLWD_DATABASE_TYPE must be set to "sqlite3" GLWD_DATABASE_SQLITE3_PATH # PostgreSQL database configuration file variables database = { type = "postgre" conninfo = "host=localhost port=5432 dbname=glewlwyd user=glewlwyd password=secret" } # PostgreSQL database environment variables GLWD_DATABASE_TYPE must be set to "postgre" GLWD_DATABASE_POSTGRE_CONNINFO ``` Database configuration is mandatory. ## Initialise database Use the script that fit your database back-end in the [database](database) folder: - `docs/database/init.mariadb.sql` - `docs/database/init.sqlite3.sql` - `docs/database/init.postgre.sql` Note: PostgreSQL requires the extension `pgcrypto` enabled to encrypt users and clients passwords. For example, initialise a MariaDB database: ```shell $ mysql mysql> CREATE DATABASE `glewlwyd`; mysql> GRANT ALL PRIVILEGES ON glewlwyd.* TO 'glewlwyd'@'%' identified BY 'glewlwyd'; mysql> FLUSH PRIVILEGES; mysql> USE glewlwyd mysql> SOURCE docs/database/init.mariadb.sql ``` Initialise a SQLite3 database: ```shell $ sqlite3 /var/cache/glewlwyd/glewlwyd.db < docs/database/init.sqlite3.sql ``` Initialize a PostgreSQL database: Check out [PostgreSQL documentation](https://www.postgresql.org/docs) and select your version for more information on the following commands. ```shell $ psql -hlocalhost -Upostgres postgres=# create role glewlwyd login password 'secret'; postgres=# create database glewlwyd owner glewlwyd; postgres=# grant connect on database glewlwyd to glewlwyd; postgres=# \c glewlwyd glewlwyd=# create extension pgcrypto; glewlwyd=# \c glewlwyd glewlwyd glewlwyd=> \i docs/database/init.postgre.sql glewlwyd=> \q ``` ### Security warning! Those scripts create a valid database that allow to use glewlwyd. But to avoid potential security issues, you must change the admin password when you first connect to the application. ### Built-in scope values If you want to use a different name for admin scope (default is `g_admin`), or the profile scope (default is `g_profile`), you must update the init script with your own value before running it, change the lines below accordingly. ### Administrator user An administrator must be present in the back-end to manage the application (manage scopes, users, clients, resources, authorization types). An administrator in the LDAP back-end is a user who has the `admin_scope` (default `g_admin`) in its scope list. ## Install as a service The files `docs/glewlwyd-init` (SysV init) and `docs/glewlwyd.service` (SystemD) can be used to run glewlwyd as a daemon. They are fitted for a Raspbian distribution, but can easily be changed for other systems. It's highly recommended to run Glewlwyd as a user without root access. Glewlwyd requires to be able to open a TCP port connection, a read/write access to the glewlwyd database, read access to the config file `glewlwyd.conf` and the installed `webapp/` folder (typically `/usr/share/glewlwyd/webapp`. ### Install as a SysV init daemon and run ```shell $ sudo cp glewlwyd-init /etc/init.d/glewlwyd $ sudo update-rc.d glewlwyd defaults $ sudo service glewlwyd start ``` ### Install as a SystemD daemon and run ```shell $ sudo cp glewlwyd.service /etc/systemd/system $ sudo systemctl enable glewlwyd $ sudo systemctl start glewlwyd ``` ## Reverse proxy configuration To install Glewlwyd behind a reverse proxy, you must check the following rules: - Forward HTTP methods `GET`, `POST`, `PUT`, `DELETE` and `OPTION` - Forward the entire URL, including query parameters (I'm watching you NGINX!) - Forward the session cookies *-and optionally the registration cookies-*, cookies default keys are `GLEWLWYD2_SESSION_ID` for session cookie and `G_REGISTER_SESSION` for registration cookie - Forward HTTP headers, including `Authorization` You can have glewlwyd available at the root of the domain/subdomain, e.g. `https://glewlwyd.tld/` or host Glewlwyd in a sub-folder of the domain/subdomain, e.g. `https://auth.tld/glewlwyd/`. ### Apache mod_proxy example To use Apache as reverse proxy, you must enable the mods `proxy` and `proxy_http`. If you want to use user or client certificate authentication behind a reverse proxy, you must enable the mods `ssl` and `headers`. The following example is a simple Apache reverse proxy configuration on a virtual host `https://glewlwyd.tld`, the glewlwyd instance is running on localhost. ```config ServerName glewlwyd.tld ProxyPass / http://localhost:4593/ ``` The following example is an Apache reverse proxy configuration on a virtual host `https://glewlwyd.tld`, the glewlwyd instance is running on localhost and the proxy forwards the client TLS certificate to Glewlwyd in the header `SSL_CLIENT_CERT` if the client uses a valid TLS certificate. ```config ServerName glewlwyd.tld SSLEngine on SSLCertificateFile /path/to/your_domain_name.crt SSLCertificateKeyFile /path/to/your_private.key SSLCertificateChainFile /path/to/your_chain_file.crt SSLCACertificateFile /path/to/your_ca.crt SSLVerifyClient optional RequestHeader set SSL_CLIENT_CERT "" ProxyPass / http://localhost:4593/ RequestHeader set SSL_CLIENT_CERT "%{SSL_CLIENT_CERT}s" ``` ## Nginx reverse proxy examples The following example is an Nginx reverse proxy configuration in which the glewlwyd instance is at the root of the domain that Nginx listens to requests for. ``` # Glewlwyd location / { proxy_set_header Host $host; proxy_pass http://127.0.0.1:4593; } ``` Other headers which may be useful to define as `proxy_set_header` options here besides `Host` are `X-Forwarded-For` and/or `X-Real-IP`, to pass along the requesting client's IP rather than the proxy IP for logging purposes. See [the Nginx proxy docs](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header) for further information. It is also possible to use Nginx to serve glewlwyd from a sub-folder, which would allow multiple services to share a single proxy listening for requests on a single domain name. Below is an example configuration for doing this with Nginx. ``` # Glewlwyd location ^~ /authsrv/ { rewrite ^\/authsrv(.*) /$1 break; proxy_set_header Host $host; proxy_pass http://127.0.0.1:4593; } ``` In this example, requests to the sub-folder `/authsrv` will be sent to glewlwyd instance, and requests to the root domain or other folders can be sent to other applications and services on the same domain name. The `rewrite` regex in the second line extracts the part of the URL *after* `/authsrv` and sends that part of the URL on to the glewlwyd instance without the folder name. In this sub-folder example, the `webapp/config.json` will need the main URL changed to account for the sub-folder, while the rest of the config can be unchanged from defaults. The main glewlwyd.conf can also be unchanged from defaults, since the rewrite rule is removing the sub-folder name from requests to `/api` endpoints before passing them to the glewlwyd instance. Below is the URL configuration of `webapp/config.json` with Nginx serving glewlwyd in a sub-folder named `/authsrv`. ```javascript { "GlewlwydUrl": "https://glewlwyd.tld/authsrv/", "ProfileUrl": "profile.html", "AdminUrl": "index.html", "LoginUrl": "login.html", "CallbackPage": "callback.html", ... ``` In both of the above examples, SSL terminates at the proxy, and Nginx passes unencrypted http traffic on to the glewlwyd instance on the internal/private network. This is not suitable for a proxy which passes traffic across the public internet to the eventual server that the glewlwyd instance lives on. For SSL pass through configurations in which SSL is preserved rather than terminated at the proxy, Nginx users should consider using the `ssl_preread` stream module for the simplest configuration. The `ssl_preread` module allows Nginx to read the address from encrypted traffic without decrypting it, and pass along traffic in whatever load balancing scenarios that may be necessary, without having to maintain certificates and encryption settings on the proxy. Documentation on the `ssl_preread` module is [available here](http://nginx.org/en/docs/stream/ngx_stream_ssl_preread_module.html). ## Fail2ban filter You can add specific filter for fail2ban to ban potential attackers. The `glewlwyd.conf` file is available in [fail2ban/glewlwyd.conf](fail2ban/glewlwyd.conf). It will ban the IP addresses using the following rules: - `Authorization invalid` - on a failed auth (user or client) - `Code invalid` - on a invalid code in OAuth2 or OIDC - `Token invalid` - on a invalid token in OAuth2 or OIDC - `Scheme email - code sent` - when an OTP code is sent via e-mail, to avoid spamming users - `Register new user - code sent to email` - when an e-mail verification is sent, to avoid spamming users - `Verify e-mail - code invalid` - when an e-mail verification is invalid - `Update e-mail - token sent to email` - when an e-mail update verification is sent, to avoid spamming users - `Update e-mail - token invalid` - on a invalid update e-mail verification token - `Reset credentials - token invalid` - on a invalid reset credentials e-mail verification token - `Reset credentials - code invalid` - on a invalid reset credentials code The `filter.d/glewlwyd-log.conf` config file has the following content if you log to a user-defined log file: ```config # Fail2Ban filter for Glewlwyd # # Author: Nicolas Mora # [Definition] failregex = ^.* - Glewlwyd WARNING: Security - Authorization invalid for username .* at IP Address ^.* - Glewlwyd WARNING: Security - Authorization invalid for client_id .* at IP Address ^.* - Glewlwyd WARNING: Security - Code invalid at IP Address ^.* - Glewlwyd WARNING: Security - Token invalid at IP Address ^.* - Glewlwyd WARNING: Security - Scheme email - code sent for username .* at IP Address ^.* - Glewlwyd WARNING: Security - Register new user - code sent to email .* at IP Address ^.* - Glewlwyd WARNING: Security - Verify e-mail - code invalid at IP Address ^.* - Glewlwyd WARNING: Security - Update e-mail - token sent to email .* at IP Address ^.* - Glewlwyd WARNING: Security - Update e-mail - token invalid at IP Address ^.* - Glewlwyd WARNING: Security - Reset credentials - token invalid at IP Address ^.* - Glewlwyd WARNING: Security - Reset credentials - code invalid at IP Address ignoreregex = ``` You can download this file [here](fail2ban/glewlwyd-log.conf). The `filter.d/glewlwyd-syslog.conf` config file has the following content if you log to syslog: ```config # Fail2Ban filter for Glewlwyd # # Author: Nicolas Mora, Robert Clayton # [INCLUDES] # # load the 'common.conf' list of fail2ban upstream maintained prefixes # before = common.conf [Definition] # # declare the daemon name so common.conf variables will match # _daemon = Glewlwyd failregex = ^.* %(__prefix_line)sSecurity - Authorization invalid for username .* at IP Address ^.* %(__prefix_line)sSecurity - Authorization invalid for client_id .* at IP Address ^.* %(__prefix_line)sSecurity - Code invalid at IP Address ^.* %(__prefix_line)sSecurity - Token invalid at IP Address ^.* %(__prefix_line)sSecurity - Scheme email - code sent for username .* at IP Address ^.* %(__prefix_line)sSecurity - Register new user - code sent to email .* at IP Address ^.* %(__prefix_line)sSecurity - Verify e-mail - code invalid at IP Address ^.* %(__prefix_line)sSecurity - Update e-mail - token sent to email .* at IP Address ^.* %(__prefix_line)sSecurity - Update e-mail - token invalid at IP Address ^.* %(__prefix_line)sSecurity - Reset credentials - token invalid at IP Address ^.* %(__prefix_line)sSecurity - Reset credentials - code invalid at IP Address ignoreregex = ``` You can download this file [here](fail2ban/glewlwyd-syslog.conf). You must place the file `glewlwyd-log.conf` or `glewlwyd-syslog.conf` under the fail2ban `filter.d` directory (On Debian-based distrib it's located in `/etc/fail2ban/filter.d/`). Then, you must update your `jail.local` file (On Debian-based distrib it's located in `/etc/fail2ban/jail.local`) by adding the following paragraph if you log to a user-defined log file: ```config [glewlwyd] enabled = true filter = glewlwyd-log logpath = /var/log/glewlwyd.log port = https,4593 # the TCP port where Glewlwyd is available from outside ``` ...or the following paragraph if you log to syslog: ```config [glewlwyd] enabled = true filter = glewlwyd-syslog logpath = /var/log/syslog port = https,4593 # the TCP port where Glewlwyd is available from outside ``` You can download this file [here](fail2ban/jail.local). Check out [Fail2ban](https://www.fail2ban.org/) documentation for more information. ## Logrotate configuration You can add a logrotate configuration file to help you managing Glewlwyd's logs. Example of a `/etc/logrotate.d/glewlwyd` file. This file will create a new log file every week, compress the old files and keep 52 files (1 year archive): ``` /var/log/glewlwyd.log { rotate 52 weekly compress copytruncate missingok } ``` ## Front-end application All front-end pages have a minimal design, feel free to modify them for your own need, or create your own application. The source code is available in `/webapp-src` and requires nodejs and npm or yarn to build. The built front-end files are located in the webapp/ directory. ### webapp/config.json The front-end configuration file must be available under `webapp/config.json` you can copy the file `webapp/config.json.sample`. You should update the URLs of the API and the HTML page to match your configuration: Example: ```Javascript { "GlewlwydUrl": "https://glewlwyd.tld/", "ProfileUrl": "https://glewlwyd.tld/profile.html", "AdminUrl": "https://glewlwyd.tld/index.html", "LoginUrl": "https://glewlwyd.tld/login.html" } ``` The front-end application is written in JavaScript using mostly ReactJS and JQuery, ES6 minimum is required. Recent versions of browsers like Firefox, Chrom[e|ium], Edge or Safari work fine. By choice, Glewlwyd isn't available for Internet Explorer or browser with a poor JavaScript engine. If you really need it you can build the front-end application with `babel-polyfill`. Check out the [webapp-src documentation](../webapp-src/README.md). ### Internationalization The languages available in the front-end are English, French and Dutch. If you make a language file for another Lang, you can add it in your Glewlwyd installation by adding the file in `webapp/{lang}/translation.json` where `{lang}` is the translation language in ISO 639-1 format (2 letters). Then, add your new language 2-letters code in the `webapp/config.json` file in the `lang` key, example for adding Korean language: ```json "lang": ["en","fr","nl","ko"], ``` Also, feel free to send your new language file if you want to add it in the official project. The new language file must be under MIT license to be added in the project repository. ### Login, Admin and Profile pages These pages are used when a user requires some access to Glewlwyd. They are simple HTML pages with a small JavaScript/JQuery/ReactJS application in it to provide the expected behavior, and vanilla bootstrap 4 for the visual consistency. Glewlwyd front-end source code is under MIT license. Fell free to update them to fit your needs or to adapt the front-end to your identity. ### Customize CSS If you need to customize the CSS only, you can update the following files: - [webapp/css/glewlwyd-custom.css](../webapp/css/glewlwyd-custom.css): update the css for the 3 applications (admin, login, profile) - [webapp/css/profile-custom.css](../webapp/css/profile-custom.css) : update the css for the profile application only - [webapp/css/admin-custom.css](../webapp/css/admin-custom.css) : update the css for the admin application only - [webapp/css/login-custom.css](../webapp/css/login-custom.css) : update the css for the login application only ### Customize titles and logos In all pages, the navigation bar has the Glewlwyd logo and the title `Glewlwyd`. You can change them in each pages individually. #### Change logo Replace the files in `webapp/img/` with your own. Each file is used in each page of the applications. Feel free to use your own organization logo or event. - [webapp/img/logo-admin.png](../webapp/img/logo-admin.png) : Logo in the admin application - [webapp/img/logo-login.png](../webapp/img/logo-login.png) : Logo in the login application - [webapp/img/logo-profile.png](../webapp/img/logo-profile.png) : Logo in the profile application #### Change navigation menu title Change values in the internationalization files located in `webapp/locales/*/translations.json`: Each title is identified by the key `menu-title` in each block specific to a page of the applications, you can change the value with your own title. - Admin page: ```javascript { "admin": { [...] "menu-title": "Glewlwyd", [...] } ``` - Login page: ```javascript { "login": { [...] "menu-title": "Glewlwyd", [...] } ``` - Profile page: ```javascript { "profile": { [...] "menu-title": "Glewlwyd", [...] } ``` #### Change HTML page title Change the tag content value `` in the following HTML pages: - [webapp/index.html](../webapp/index.html): Admin page - [webapp/login.html](../webapp/login.html): Login page - [webapp/profile.html](../webapp/profile.html): Profile page ## Event logs triggered Glewlwyd now logs event messages. These messages can be parsed and used to trigger external actions such as web-hooks or message broadcasting to other Glewlwyd instances for example. Event log messages have the following format: ``` <date_timestamp> - Glewlwyd INFO: Event - <event message> ``` ### List of events logged #### Core events ``` <date_timestamp> - Glewlwyd INFO: Event - User '<username>' authenticated with password <date_timestamp> - Glewlwyd INFO: Event - User '<username>' authenticated with scheme '<scheme_type>/<scheme_name>' <date_timestamp> - Glewlwyd INFO: Event - User '<username>' registered scheme '<scheme_type>/<scheme_name>' <date_timestamp> - Glewlwyd INFO: Event - User '<username>' registered scheme '<scheme_type>/<scheme_name>' (delegation) <date_timestamp> - Glewlwyd INFO: Event - User backend module '<module_name>' added (<module_type>) <date_timestamp> - Glewlwyd INFO: Event - User backend module '<module_name>' updated <date_timestamp> - Glewlwyd INFO: Event - User backend module '<module_name>' removed <date_timestamp> - Glewlwyd INFO: Event - User auth scheme module '<module_name>' added (<module_type>) <date_timestamp> - Glewlwyd INFO: Event - User auth scheme module '<module_name>' updated <date_timestamp> - Glewlwyd INFO: Event - User auth scheme module '<module_name>' removed <date_timestamp> - Glewlwyd INFO: Event - Client backend module '<module_name>' added (<module_type>) <date_timestamp> - Glewlwyd INFO: Event - Client backend module '<module_name>' updated <date_timestamp> - Glewlwyd INFO: Event - Client backend module '<module_name>' removed <date_timestamp> - Glewlwyd INFO: Event - Plugin module '<plugin_name>' added (<plugin_type>) <date_timestamp> - Glewlwyd INFO: Event - Plugin module '<plugin_name>' updated <date_timestamp> - Glewlwyd INFO: Event - Plugin module '<plugin_name>' removed <date_timestamp> - Glewlwyd INFO: Event - User '<username>' added <date_timestamp> - Glewlwyd INFO: Event - User '<username>' updated <date_timestamp> - Glewlwyd INFO: Event - User '<username>' removed <date_timestamp> - Glewlwyd INFO: Event - Client '<client_id>' added <date_timestamp> - Glewlwyd INFO: Event - Client '<client_id>' updated <date_timestamp> - Glewlwyd INFO: Event - Client '<client_id>' removed <date_timestamp> - Glewlwyd INFO: Event - Scope '<scope>' added <date_timestamp> - Glewlwyd INFO: Event - Scope '<scope>' updated <date_timestamp> - Glewlwyd INFO: Event - Scope '<scope>' removed <date_timestamp> - Glewlwyd INFO: Event - User '<username>' updated (profile) <date_timestamp> - Glewlwyd INFO: Event - User '<username>' removed (profile) ``` #### OAuth2 plugin events ``` <date_timestamp> - Glewlwyd INFO: Event oauth2 - Plugin '<plugin_name>' - Refresh token generated for client '<client_id>' granted by user '<username>' with scope list '<scope_list>' <date_timestamp> - Glewlwyd INFO: Event oauth2 - Plugin '<plugin_name>' - Access token generated for client '<client_id>' granted by user '<username>' with scope list '<scope_list>' <date_timestamp> - Glewlwyd INFO: Event oauth2 - Plugin '<plugin_name>' - Refresh token generated for client '<client_id>' revoked <date_timestamp> - Glewlwyd INFO: Event oauth2 - Plugin '<plugin_name>' - Access token generated for client '<client_id>' revoked ``` #### OIDC plugin events ``` <date_timestamp> - Glewlwyd INFO: Event oidc - Plugin '<plugin_name>' - Refresh token generated for client '<client_id>' granted by user '<username>' with scope list '<scope_list>' <date_timestamp> - Glewlwyd INFO: Event oidc - Plugin '<plugin_name>' - Access token generated for client '<client_id>' granted by user '<username>' with scope list '<scope_list>' <date_timestamp> - Glewlwyd INFO: Event oidc - Plugin '<plugin_name>' - id_token generated for client '<client_id>' granted by user '<username>' with scope list '<scope_list>' <date_timestamp> - Glewlwyd INFO: Event oidc - Plugin '<plugin_name>' - client '<client_id>' registration updated with redirect_uri <redirect_uri_list> <date_timestamp> - Glewlwyd INFO: Event oidc - Plugin '<plugin_name>' - client '<client_id>' registered with redirect_uri <redirect_uri_list> <date_timestamp> - Glewlwyd INFO: Event oidc - Plugin '<plugin_name>' - client '<client_id>' deleted <date_timestamp> - Glewlwyd INFO: Event oidc - Plugin '<plugin_name>' - Refresh token generated for client '<client_id>' revoked <date_timestamp> - Glewlwyd INFO: Event oidc - Plugin '<plugin_name>' - Access token jti '<jti>' generated for client '<client_id>' revoked <date_timestamp> - Glewlwyd INFO: Event oidc - Plugin '<plugin_name>' - id_token generated for client '<client_id>' revoked <date_timestamp> - Glewlwyd INFO: Event oidc - Plugin '<plugin_name>' - Rich Authorization Request consent type '<type>' set to <true|false> by user '<username>' to client '<client_id>' <date_timestamp> - Glewlwyd INFO: Event oidc - Plugin '<plugin_name>' - Rich Authorization Request consent type '<type>' deleted by user '<username>' to client '<client_id>' ``` #### Register plugin events ``` <date_timestamp> - Glewlwyd INFO: Event register - Plugin '<plugin_name>' - user '<username>' registered <date_timestamp> - Glewlwyd INFO: Event register - Plugin '<plugin_name>' - user '<username>' updated its e-mail address to '<e-mail>' <date_timestamp> - Glewlwyd INFO: Event register - Plugin '<plugin_name>' - user '<username>' opened a reset credential session with e-mail token <date_timestamp> - Glewlwyd INFO: Event register - Plugin '<plugin_name>' - user '<username>' opened a reset credential session with code ``` ## Run Glewlwyd Run the application using the service command if you installed the init file: ```shell $ sudo service glewlwyd start ``` You can also manually start the application: ```shell $ # start Glewlwyd using a configuration file $ glewlwyd --config-file=glewlwyd.conf $ # start Glewlwyd using environment variables $ GLWD_PORT=4593 GLWD_EXTERNAL_URL=http://localhost:4593 GLWD_STATIC_FILES_PATH=/usr/share/glewlwyd/webapp [...] glewlwyd --env-variables ``` By default, Glewlwyd is available on TCP port 4593. ## Getting started with the application When your instance is up and running, you can complete its configuration with the [getting started documentation](GETTING_STARTED.md). ## Running Glewlwyd in test mode for integration tests ### Option 1 - Build your own Glewlwyd in test mode You can create a Glewlwyd instance suited for integration tests. You need to build Glewlwyd with Mock modules and use a database initialized with the script [glewlwyd-test.sql](../test/glewlwyd-test.sql) after the script [docs/database/init.*.sql](database/). You can use ```shell $ # example with sqlite3 database $ mkdir build && cd build $ cmake -DWITH_MOCK=on .. $ make && sudo make install $ cd .. $ sqlite3 /tmp/glewlwyd.db < docs/database/init.sqlite3.sql $ sqlite3 /tmp/glewlwyd.db < test/glewlwyd-test.sql $ glewlwyd --config-file=test/glewlwyd-ci.conf ``` ### Option 2 - Glewlwyd in test mode on a Docker image The docker build file [Dockerfile-ci](../Dockerfile-ci) builds and generates a docker image called `babelouest/glewlwyd:ci`. This docker image will execute Glewlwyd in test mode. - Build the Docker image for test mode ```shell $ make docker-ci ``` - Run the Docker image for test mode ```shell $ docker run --rm -it -p 4593:4593 babelouest/glewlwyd:ci ``` ### Glewlwyd in test mode configuration When you run Glewlwyd in test mode, the Glewlwyd instance uses the modules mock for client and user backend. These backends come with built-in users and clients. The instance also comes with pre configured OAuth2 and OIDC plugins installed. - The external URL for this instance is: `http://localhost:4593` - 3 mock schemes are instantiated: - `mock_scheme_42`, expected value to authenticate: `42` - `mock_scheme_88`, expected value to authenticate: `88` - `mock_scheme_95`, expected value to authenticate: `95` - The scopes available are: - `g_admin`: access to administration page, available using password only, session timeout 600 seconds - `g_profile`: access to profile page, available using password only, session timeout 600 seconds - `openid`: available using any authentication, no session timeout - `scope1`: available using password and scheme (`mock_scheme_42` OR `mock_scheme_88`) AND `mock_scheme_95`, no session timeout - `scope2`: available using password and scheme `mock_scheme_95`, no session timeout - `scope3`: available using password and scheme `mock_scheme_88`, no session timeout #### Mock users list You can add, edit or remove any user from this backend instance, but there is no persistence, which means that if you restart Glewlwyd, the user list will be back to its original state. All users use the password `password`. The password is common for all users, which means that if you change the password for a user, the change will apply for all users. - username: `admin` - password: `password` - name: `The Boss` - e-mail: `boss@glewlwyd.domain` - scopes: `g_admin`, `g_profile`, `openid` - username: `user1` - password: `password` - name: `Dave Lopper 1` - e-mail: `dev1@glewlwyd.domain` - scopes: `g_profile`, `openid`, `scope1`, `scope2`, `scope3` - username: `user2` - password: `password` - name: `Dave Lopper 2` - e-mail: `dev2@glewlwyd.domain` - scopes: `g_profile`, `openid`, `scope1` - username: `user3` - password: `password` - name: `Dave Lopper 3` - e-mail: `dev3@glewlwyd.domain` - scopes: `g_profile`, `scope1`, `scope2`, `scope3` #### Mock clients list You can add, edit or remove any client from this backend instance, but there is no persistence, which means that if you restart Glewlwyd, the client list will be back to its original state. - client_id: `client1_id` - name: `client1` - description: `Client mock 1` - confidential: `false` - authorization_type: `code`, `token`, `id_token`, `none`, `refresh_token`, `delete_token` - redirect_uri: - `../../test-oauth2.html?param=client1_cb1` - `../../test-oauth2.html?param=client1_cb2` - `../../test-oidc.html?param=client1_cb1` - sector_identifier_uri: `https://sector1.glewlwyd.tld` - scope: <none> - client_id: `client2_id` - name: `client2` - description: `Client mock 2` - confidential: `false` - authorization_type: `code` - redirect_uri: - `../../test-oauth2.html?param=client2` - sector_identifier_uri: <none> - scope: <none> - client_id: `client3_id` - name: `client3` - description: `Client mock 3` - confidential: `true` - password: `password` - authorization_type: `code`, `token`, `id_token`, `client_credentials`, `none`, `refresh_token`, `delete_token` - redirect_uri: - `../../test-oauth2.html?param=client3` - `../../test-oidc.html?param=client3` - sector_identifier_uri: `https://sector1.glewlwyd.tld` - scope: `scope2`, `scope3` - client_id: `client4_id` - name: `client4` - description: `Client mock 4` - confidential: `true` - client_secret: `secret` - authorization_type: `code`, `token`, `id_token` - redirect_uri: - `../../test-oidc.html?param=client4` - sector_identifier_uri: `https://sector4.glewlwyd.tld` - scope: `scope2`, `scope3` #### OAuth2 plugin instance The non specified configuration values are the default ones - name: `glwd` - Display Name: `OAuth2 Glewlwyd plugin` - URL API Auth for this instance: `http://localhost:4593/api/glwd/auth` - URL API Token for this instance: `http://localhost:4593/api/glwd/token` - URL API Profile for this instance: `http://localhost:4593/api/glwd/profile` - Tokens signature - JWT Type: `SHA` - Key size: `256 bits` - Secret key: `secret` #### OIDC plugin instance The non specified configuration values are the default ones - name: `oidc` - Display Name: `OpenID Connect Glewlwyd plugin` - URL openid-configuration for this instance: `http://localhost:4593/api/oidc/.well-known/openid-configuration` - URL API Auth for this instance: `http://localhost:4593/api/oidc/auth` - URL API Token for this instance: `http://localhost:4593/api/oidc/token` - URL userinfo for this instance: `http://localhost:4593/api/oidc/userinfo` - Issuer: `https://glewlwyd.tld` - Allow passing request parameter as JWT: `true` - Supported scopes: `openid` - Tokens signature - JWT Type: `SHA` - Key size: `256 bits` - Secret key: `secret` - Allow non OIDC but valid OAuth2 requests: `true` - Authentication types enabled: `code`, `token`, `id_token`, `password`, `client_credentials`, `none`, `refresh_token`, `delete_token` ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/OAUTH2.md�����������������������������������������������������������������������0000664�0000000�0000000�00000057417�14156463140�0015614�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Glewlwyd OAuth2 Plugin documentation [![License: CC BY 4.0](https://licensebuttons.net/l/by/4.0/80x15.png)](https://creativecommons.org/licenses/by/4.0/) This plugin is based on the [OAuth 2 RFC document](https://tools.ietf.org/html/rfc6749) and allows Glewlwyd to act as an OAuth2 Provider. **Note** This plugin will be deprecated sooner or later. Until then, bugs will still be fixed but no new functionalities will be added. It's recommened to use the [OIDC plugin](OIDC.md) instead. ## Functionalities summary The following OAuth2 functionalities are supported: - [Authorization Code](https://tools.ietf.org/html/rfc6749#section-1.3.1) - [Implicit](https://tools.ietf.org/html/rfc6749#section-1.3.2) - [Resource Owner Password Credentials](https://tools.ietf.org/html/rfc6749#section-1.3.3) - [Client Credentials](https://tools.ietf.org/html/rfc6749#section-1.3.3) - [Refreshing an Access Token](https://tools.ietf.org/html/rfc6749#section-6) - [Proof Key for Code Exchange by OAuth Public Clients](https://tools.ietf.org/html/rfc7636) - [Token introspection (RFC 7662)](https://tools.ietf.org/html/rfc7662) - [Token revocation (RFC 7009)](https://tools.ietf.org/html/rfc7009) - [OAuth 2.0 Device Authorization Grant](https://tools.ietf.org/html/rfc8628) ## Access token format Glewlwyd OAuth2 plugin uses JWTs as access tokens. Therefore, the access token can be used by the client or the third party web service to identify the user and the scopes available with this access token. An access token payload has the following JSON format: ```Javascript { username: "user1", // Username that was provided this access_token salt: "abcdxyz1234", // Random string to avoid collisions type: "access_token", // Hardcoded iat: 1466556840, // Issued at time in Epoch Unix format expires_in: 3600, // Number of seconds of validity for this token scope: "scope1 g_profile" // scopes granted to this access token in a string separated by spaces } ``` ## Generate a key pair for JWT access tokens signatures To create a key/certificate pair in RSA or ECDSA format, run the following commands on a linux shell with openssl installed: ```shell $ # RSA KEY $ # private key $ openssl genrsa -out private-rsa.key 4096 $ # public key $ openssl rsa -in private-rsa.key -outform PEM -pubout -out public-rsa.pem $ # ECDSA KEY $ # private key $ openssl ecparam -genkey -name secp521r1 -noout -out private-ecdsa.key $ # public key $ openssl ec -in private-ecdsa.key -pubout -out public-ecdsa.pem ``` For more information on keys generation, see [OpenSSL Documentation](https://www.openssl.org/docs/). ## Installation ![plugin-oauth2](screenshots/plugin-oauth2.png) In the administration page, go to `Parameters/Plugins` and add a new plugin by clicking on the `+` button. In the modal, enter a name and a display name (the name must be unique among all user backend instances). Select the type `Glewlwyd OAuth2 plugin` in the Type drop-down button. Below is the definition of all parameters. ### Name Name (identifier) of the plugin instance, must be unique among all the plugin instances, even of a different type. ### Display name Name of the instance displayed to the user. ### JWT Type Algorithm used to sign access tokens and ID Tokens. The algorithm supported are `RSA` and `ECDSA` using a private and a public key, and `SHA` using a shared secret. ### Key size Size of the key to sign the tokens. The sizes supported are 256 bits, 384 bits or 512 bits. ### Secret key Private key file used to sign if the selected algorithm is `RSA` or `ECDSA`. Must be a PEM format file. Shared secret if the selected algorithm is `SHA`. ### Public key Public key file used to validate access tokens if the selected algorithm is `RSA` or `ECDSA`. Must be a PEM format file. ### Access token duration (seconds) Duration of each access tokens. Default value is 3600 (1 hour). ### Refresh token duration (seconds) Duration of validity of each refresh tokens. Default value is 1209600 (14 days). ### Code duration (seconds) Duration of validity of each code sent to the client before requesting a refresh token. Default value is 600 (10 minutes). ### Refresh token rolling If this option is checked, every time an access token is requested using a refresh token, the refresh token issued at time will be reset to the current time. This option allows infinite validity for the refresh tokens if it's not manually disabled, but if a refresh token isn't used for more of the value `Refresh token duration`, it will be disabled. ### Authentication type code enabled Enable response type `code`. ### Revoke all tokens if a client tries to replay a code If this option is set, when a code is replayed to gain a refresh token, all the refresh and access tokens delivered for this code will be revoked. This option can be used to mitigate replay attacks and enforce tokens security. ### Authentication type implicit enabled Enable response type `token`. ### Authentication type password enabled Enable response type `password`. ### Authentication type client enabled Enable response type `client_credential`. ### Authentication type refresh enabled Enable response type `refresh_token`. ### Specific scope parameters This section allows to put specific settings for an scope that will override the plugin settings. The settings that you can override are `Refresh token duration` and/or `Rolling refresh`. Please note that a specific scope parameter has a higher priority than the plugin settings, and if have multiple scopes in a request that have specific settings, the settings will follow the following algorithm: - Refresh token duration: The duration provided will be the lowest duration among all the specific scope parameters. - Rolling refresh: The value `No`has higher priority, therefore rolling refresh provided will be `No` if one scope has the value `No`, `Yes` otherwise ### Additional token values This section allows to add specific values to the access_tokens that will be taken from the user property values. You can add as many additional values as you want. If the property isn't present in the user data, it will be ignored. If the value is multiplier, all values will be present, separated by a comma `,`. ## PCS - Code challenge (RFC 7636) This section is used to configure [Proof Key for Code Exchange by OAuth Public Clients](https://tools.ietf.org/html/rfc7636). ### PUCE allowed Enable this feature if you want to support code challenge. ### Method plain allowed Enable this feature if you want to allow method `plain` in the code challenge feature. It is not recommended to enable this feature unless you know what you do because this feature is slightly less secure than default method `S256`. According to [the specifications](https://tools.ietf.org/html/rfc7636#section-4.2): ``` Clients are permitted to use "plain" only if they cannot support "S256" for some technical reason and know via out-of-band configuration that the server supports "plain". ``` ## Tokens Introspection (RFC 7662) and Revocation (RFC 7009) **IMPORTANT NOTICE!** Glewlwyd access tokens are [JWTs](https://tools.ietf.org/html/rfc7519), the original way for resource services to check if an access token is valid and reliable is to check its signature and its expiration date. Token introspection and revocation have been introduced in Glewlwyd, but if the resource service doesn't use the introspection endpoint, it will miss an inactive token and still consider it valid. The endpoints `/profile`, `/introspect` and `/revoke` when they are given an access token to authenticate will check if the token is revoked or not. ### Allow tokens introspection and invocation Enable this feature if you want your oauth2 instance to enable endpoints `/introspect` and `/revoke`. ### Allow for the token client Enable this feature if your want to allow clients to use endpoints `/introspect` and `/revoke` using their client_id and secret as HTTP Basic Auth. The clients will be allowed to introspect and revoke only the tokens that were issued for them. ### Required scopes in the access token Add on or more scopes if you want to allow to use endpoints `/introspect` and `/revoke` using valid access tokens to authenticate the requests. The access tokens must have the scopes required in their payload to be valid. ## Client secret vs password When you add or edit a client in Glewlwyd, you can set a `client secret` or a `password`. Both can be used to authenticate confidential clients. The primary difference is that a client secret is a string stored 'as is' in the backend (database or LDAP), without hashing, where a client password is stored in a hashed form in the backend, so makes it more difficult for attackers to retrieve it. A client secret has priority over a client password, which means that if a client has set both client secret and client password, the authentication will be executed with client secret only. ## Glewlwyd OAuth 2 endpoints specifications This document is intended to describe Glewlwyd OAuth 2 plugin implementation. OAuth endpoints are used to authenticate the user, and to send tokens or other authentication and identification data. The complete specification is available in the [OAuth 2 RFC document](https://tools.ietf.org/html/rfc6749). If you see an issue or have a question on Glewlwyd OAuth 2 plugin implementation, you can open an issue or send an email to the following address [mail@babelouest.org](mailto:mail@babelouest.org). - [Endpoints authentication](#endpoints-authentication) - [Prefix](#prefix) - [Login and grant URIs](#login-and-grant-uris) - [Scope](#scope) - [OAuth 2 endpoints](#oauth-2-endpoints) - [Authorization endpoint](#authorization-endpoint) - [Token endpoint](#token-endpoint) - [OAuth 2 schemes](#oauth-2-schemes) - [Authorization code grant - Authorization request](#authorization-code-grant---authorization-request) - [Authorization code grant - Authorization Response](#authorization-code-grant---authorization-response) - [Implicit Grant](#implicit-grant) - [Resource Owner Password Credentials Grant](#resource-owner-password-credentials-grant) - [Client Credentials Grant](#client-credentials-grant) - [Refresh token](#refresh-token) - [Invalidate refresh token](#invalidate-refresh-token) - [Manage refresh tokens endpoints](#manage-refresh-tokens-endpoints) - [List refresh tokens](#list-refresh-tokens) - [Disable a refresh token by its signature](#disable-a-refresh-token-by-its-signature) - [Token introspection and revocation](#token-introspection-and-revocation) - [Token introspection](#token-introspection) - [Token revocation](#token-revocation) ### Endpoints authentication Authentication has different faces, and differs with the authorization scheme. ### Prefix All URIs are based on the prefix you will setup. In this document, all API endpoints will assume they use the prefix `/api/glwd`, and all static file endpoints will assume they use the prefix `/`. ### Login and grant URIs In this document, the login URI will be displayed as `http://login.html`, this will be replaced by the values from your environment that you can define in the config file. ### OAuth 2 endpoints #### Authorization endpoint This is a multi-method, multi-parameters, versatile endpoint, used to provide authentication management. It handles the following authorization schemes as describe in the [OAuth 2 RFC document](https://tools.ietf.org/html/rfc6749): - Authorization Code Grant (Authorization part) - Implicit Grant ##### URL `/api/glwd/auth` ##### Method `GET` `POST` #### Token endpoint This endpoint is used to provide tokens to the user. It handles the following authorization schemes as describe in the [OAuth 2 RFC document](https://tools.ietf.org/html/rfc6749): - Authorization Code Grant (Access Token part) - Resource Owner Password Credentials Grant - Client Credentials Grant - Refreshing a token - Deleting a token ##### URL `/api/glwd/token` ##### Method `POST` ### OAuth 2 schemes Each scheme is described in the following chapter. The description may not be as complete as the [OAuth 2 RFC document](https://tools.ietf.org/html/rfc6749), consider the RFC as the authority standard. #### Authorization code grant - Authorization request ##### URL `/api/glwd/auth` ##### Method `GET` `POST` ##### URL (GET) or body (POST) Parameters Required ``` `response_type`: text, must be set to `code` `client_id`: text, client_id that sends the request on behalf of the resource owner, must be a valid client_id `redirect_uri`: text, redirect_uri to send the resource owner to after the connection, must be a valid redirect_uri for the specified client_id `scope`: text, scope list that the resource owner will grant access to the client, multiple scope values must be separated by a space ``` Optional `state`: text, an identifier used to prevent requests collisions and bypass, will be sent back as is to the client ##### Result ###### Resource owner not authenticated Code 302 Resource owner is not authenticated with a valid session token. Redirect to `http://login.html?client_id={client_id}&redirect_uri={redirect_uri}&scope={scope}&additional_parameters` for authentication. See login paragraph for details. ###### Scope not granted to the client Code 302 Redirect to `http://login.html?client_id={client_id}&redirect_uri={redirect_uri}&scope={scope}&additional_parameters` for grant access. ###### Success response Code 302 Redirect to `redirect_uri`?code=`code`&state=`state` with `redirect_uri` specified in the request, a `code` generated for the access, and the state specified in the request if any. ###### Error Scope Scope is not allowed for this user Code 302 Redirect to `redirect_uri`?error=invalid_scope&state=`state` with `redirect_uri` specified in the request, `invalid_scope` as error value, and the state specified in the request if any. ###### Error client Client is invalid, redirect_uri is invalid for this client, or client is not allowed to use this scheme Code 302 Redirect to `redirect_uri`?error=unauthorized_client&state=`state` with `redirect_uri` specified in the request, `unauthorized_client` as error value, and the state specified in the request if any. #### Authorization code grant - Authorization Response ##### URL `/api/glwd/token` ##### Method `POST` ##### Security If `client_id` refers to a confidential client, then client_id and client_password must be sent via Basic HTTP Auth. ##### Data Parameters Request body parameters must be encoded using the `application/x-www-form-urlencoded` format. ``` grant_type: text, must be set to "authorization_code". code: text, required redirect_uri: text, must be same redirect_uri used in the authorization request that sent back this code client_id: text, must be the same client_id used in the authorization request that sent back this code ``` ##### Success response Code 200 Content ```javascript { "access_token":text, jwt token "token_type":text, value is "bearer", "expires_in":number, set by server configuration "refresh_token":text, jwt token } ``` ##### Error Response Code 400 Error input parameters The combination code/redirect_uri/client_id is incorrect. #### Implicit Grant ##### URL `/api/glwd/auth` ##### Method `GET` ##### URL Parameters Required ``` `response_type`: text, must be set to `token` `client_id`: text, client_id that sends the request on behalf of the resource owner, must be a valid client_id `redirect_uri`: text, redirect_uri to send the resource owner to after the connection, must be a valid redirect_uri for the specified client_id `scope`: text, scope list that the resource owner will grant access to the client, multiple scope values must be separated by a space ``` Optional `state`: text, an identifier used to prevent requests collisions and bypass, will be sent back as is to the client ##### Result ###### Resource owner not authenticated Code 302 Resource owner is not authenticated with a valid session token. Redirect to `http://login.html?client_id={client_id}&redirect_uri={redirect_uri}&scope={scope}&additional_parameters` for authentication. See login paragraph for details. ###### Scope not granted to the client Code 302 Redirect to `http://grant.html?client_id={client_id}&redirect_uri={redirect_uri}&scope={scope}&additional_parameters` for grant access. See grant paragraph for details. ###### Success response Code 302 Redirect to `redirect_uri`#token=`token`&state=`state` with `redirect_uri` specified in the request, a `code` generated for the access, and the state specified in the request if any. ###### Error Scope Scope is not allowed for this user Code 302 Redirect to `redirect_uri`#error=invalid_scope&state=`state` with `redirect_uri` specified in the request, `invalid_scope` as error value, and the state specified in the request if any. ###### Error client Client is invalid, redirect_uri is invalid for this client, or client is not allowed to use this scheme Code 302 Redirect to `redirect_uri`#error=unauthorized_client&state=`state` with `redirect_uri` specified in the request, `unauthorized_client` as error value, and the state specified in the request if any. #### Resource Owner Password Credentials Grant ##### URL `/api/glwd/token` ##### Method `POST` ##### Data Parameters Request body parameters must be encoded using the `application/x-www-form-urlencoded` format. ``` grant_type: text, must be set to "password". username: text password: text scope: text ``` ##### Success response Code 200 Content ```javascript { "access_token":text, jwt token "token_type":text, value is "bearer", "expires_in":number, set by server configuration "refresh_token":text, jwt token } ``` ##### Error Response Code 403 username or password invalid. #### Client Credentials Grant ##### URL `/api/glwd/token` ##### Method `POST` ##### Security HTTP Basic authentication with client_id/client_password credentials. Client_id must be set as confidential ##### URL Parameters Required Optional ##### Data Parameters Request body parameters must be encoded using the `application/x-www-form-urlencoded` format. ``` grant_type: text, must be set to "client_credentials". scope: text ``` ##### Success response Code 200 Content ```javascript { "access_token":text, jwt token "token_type":text, value is "bearer", "expires_in":number, set by server configuration } ``` ##### Error Response Code 403 Access denied #### Refresh token Send a new access_token based on a valid refresh_token ##### URL `/api/glwd/token` ##### Method `POST` ##### Data Parameters Request body parameters must be encoded using the `application/x-www-form-urlencoded` format. ``` grant_type: text, must be set to "refresh_token". refresh_token: text, a valid ref refresh_token, mandatory scope: text, must the same scope or a sub scope of the scope used to provide the refresh_token, optional ``` ##### Success response Code 200 Content ```javascript { "access_token":text, jwt token "token_type":text, value is "bearer", "expires_in":number, set by server configuration } ``` ##### Error Response Code 400 Error input parameters #### Invalidate refresh token Mark a refresh_token as invalid, to prevent further access_token to be generated ##### URL `/api/glwd/token` ##### Method `POST` ##### Data Parameters Request body parameters must be encoded using the `application/x-www-form-urlencoded` format. ``` grant_type: text, must be set to "delete_token". refresh_token: text, a valid refresh_token, mandatory ``` ##### Success response Code 200 ##### Error Response Code 400 Error input parameters ### Manage refresh tokens endpoints The following endpoints require a valid session cookie to identify the user. If the user has the scope `g_admin`, it's possible to impersonate a user with the optional query parameter `?username={username}`. #### List refresh tokens ##### URL `/api/glwd/profile/token` ##### Method `GET` ##### URL Parameters Optional ``` `offset`: number, the offset to start the list, default 0 `limit`: number, the number of elements to return, default 100 `pattern`: text, a pattern to filter results, pattern will filter the properties `user_agent` or `issued_for` `sort`: text, the column to order the results, values available are `authorization_type`, `client_id`, `issued_at`, `last_seen`, `expires_at`, `issued_for`, `user_agent`, `enabled` and `rolling_expiration` `desc`: no value, is set, the column specified in the `sort` parameter will be orderd by descending order, otherwise ascending ``` ##### Result ##### Success response Code 200 Content ```javascript [{ "token_hash": text, refresh token hash signature "authorization_type": text, authorization type used to generate this refresh token, value can be "code" or "password" "client_id": text, client_id this refresh token was sent to "issued_at": number, date when this refresh token was issued, epoch time format "expires_at": number, date when this refresh token will expire, epoch time format "last_seen": number, last date when this refresh token was used to generate an access token, epoch time format "rolling_expiration": boolean, wether this refresh token is a rolling token, i.e. its expiration date will be postponed on each use to generate a new access token "issued_for": text, IP address of the device which requested this refresh token "user_agent": text, user-agent of the device which requested this refresh token "enabled": boolean, set to true if this refresh token is enabled, i.e. can be used to generate new access tokens, or not }] ``` ##### Error Response Code 403 Access denied #### Disable a refresh token by its signature ##### URL `/api/glwd/profile/token/{token_hash}` ##### Method `DELETE` ##### URL Parameters Required ``` `token_hash`: text, hash value of the refresh token to disable, must be url-encoded ``` ##### Result ##### Success response Code 200 ##### Error Response Code 403 Access denied Code 404 Refresh token hash not found for this user ### Token introspection and revocation The endpoints `POST` `/introspect` and `POST` `/revoke` are implementations of the corresponding RFCs [Token introspection and revocation](https://tools.ietf.org/html/rfc7662) and [OAuth 2.0 Token Revocation](https://tools.ietf.org/html/rfc7009). Both of them rely on 2 distinct ways to authenticate: - HTTP Basic Auth corresponding to the client credentials whose client the token was submitted - Authorized Access Token that includes the required scopes for those endpoints Both authentication methods are non exclusive and the administrator may enable or disable each of them. #### Token introspection ##### URL `/api/glwd/introspect` ##### Method `POST` ##### Data Parameters Request body parameters must be encoded using the `application/x-www-form-urlencoded` format. ``` token: text, the token to introspect, required token_type_hint: text, optional, values available are 'access_token' or 'refresh_token' ``` ##### Result ##### Success response Code 200 Content Active token ```javascript { "username": text, username the token was issued for, if any "client_id": text, client the token was issued for, if any "iat": number, epoch time when the token was issued "nbf": number, epoch time when the token was issued "exp": number, epoch time when the token will be (or is supposed to be) expired "scope": text, scope list this token was emitted with, separated with spaces "token_type": text, type of the token, values may be 'access_token' or 'refresh_token' } ``` ##### Error Response Code 401 Access denied Code 400 Invalid parameters #### Token revocation ##### URL `/api/glwd/revoke` ##### Method `POST` ##### Data Parameters Request body parameters must be encoded using the `application/x-www-form-urlencoded` format. ``` token: text, the token to introspect, required token_type_hint: text, optional, values available are 'access_token' or 'refresh_token' ``` ##### Result ##### Success response Code 200 ##### Error Response Code 401 Access denied Code 400 Invalid parameters �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/OAUTH2_SCHEME.md����������������������������������������������������������������0000664�0000000�0000000�00000013426�14156463140�0016630�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Glewlwyd OAuth2 Schema documentation [![License: CC BY 4.0](https://licensebuttons.net/l/by/4.0/80x15.png)](https://creativecommons.org/licenses/by/4.0/) ![scheme-oauth2](screenshots/scheme-oauth2.png) The OAuth2 Schema implements authentication based on authentication via external OAuth2/OIDC services. The chain of trust is based on the Glewlwyd's administrator trusting the OAuth2/OIDC service to authenticate Glewlwyd's users. Therefore Glewlwyd's administrator must enter only trustable external providers. It is strongly suggested to test the providers authentication with your configuration before telling your users to do so. ## Installation In the administration page, go to `Parameters/Authentication schemes` and add a new scheme by clicking on the `+` button. In the modal, enter a name and a display name (the name must be unique among all authentication scheme instances), and a scheme session expiration in seconds. Select the type `OAuth2` in the Type drop-down button. Below is the definition of all parameters. ### Name Name (identifier) of the scheme, must be unique among all the scheme instances, even of a different type. ### Display name Name of the instance displayed to the user. ### Expiration (seconds) Number of seconds to expire a valid session. ### Max use per session (0: unlimited) Maximum number of times a valid authentication with this scheme is possible. This is an additional parameter used to enforce the security of the session and forbid to reuse this session for other authentications. ### Allow users to register If this option is unchecked, only administrator can register this scheme for every user via the administration page. ### Redirect URI Callback URI that will be used in the OAuth2/OIDC flow. This option is filled by default with what is supposed to be the expected redirect URI: https://<glewlwyd_url>/callback.html. If you know what you do, you can change this value. ### Session duration for authentication (seconds) Duration of the session for users to authenticate in the external provider, i.e. the maximum time they can spend between their click on the `login with` button and when they get redirected to `callback.html` with a valid authentication. ### Authorized providers list Providers used for external authentication in Glewlwyd. The administrator can add a `mainstream` provider or a personalized provider. If you choose a mainstream providers, some settings will be filled but you must fill at least `client_id` and `client_secret` (if necessary) with your own. You can choose a mainstream provider and then change its settings if you need. The mainstream provider list is here to facilitate the administrator's job but its settings may be obsolete or not fitted to your needs. `mainstream` provider settings are configurable in the `config.json` file. By default, Glewlwyd comes with a list of `mainstream` providers such as Google, Facebook or Microsoft. If you think another `mainstream` provider should be present on this list, feel free to send a pull request. Below is the list of settings for a provider. ### Provider Type Type of the provider. Supported types are OAuth2 or OIDC (OpenID Connect). ### Name Name of the provider. Must be unique among the provider list. This value is mandatory. ### Logo URL URI of the provider logo, this will be used as a graphical identity in the profile or login page. The logo must be an small image (maximum 50x50). The Logo URI has the highest priority. If you have both Logo URI and Logo Font-Awesome for a provider, the Logo URI will be used in the Profile or Login page. ### Fork Awesome icon Name of the [Fork Awesome](https://forkaweso.me/Fork-Awesome/) icon to be used as a graphical identity in the profile or login page. Glewlwyd uses Fork Awesome 1.1.7. For example, if you want to add the [following icon](https://forkaweso.me/Fork-Awesome/icon/debian/) to your provider, you must enter the `fa-*` value specified in the HTML tag: `<i class="fa fa-debian"></i>` => the logo value must be `fa fa-debian`. The Logo URI has the highest priority. If you have both Logo URI and Logo Fork Awesome for a provider, the Logo URI will be used in the Profile or Login page. ### client_id Client identifier as given by the provider. This value is mandatory. ### Secret Client secret (password) given by the provider. This value is mandatory or optional depending on your provider's policy. ### Scope Scope to use with the provider authentication flow. This value is mandatory or optional depending on your provider's policy. If the provider type is OIDC, the scope value will be set to `openid`. ### Response Type OAuth2 response type to use for the authentication flow. Response types supported are `code` (OAuth2/OIDC provider type), `token` (OAuth2 provider type) or `id_token` (OIDC provider type). ### Userid property This is the name of the property that will contain the user identifier necessary to identify it during the authentication flow. ### Config Endpoint URL URL of the provider's [OIDC config endpoint](https://openid.net/specs/openid-connect-discovery-1_0.html), i.e. https://provider.tld/.well-known/openid-configuration. This setting is available for OIDC providers only. ### Auth Endpoint URL URL of the provider's auth endpoint. This setting is mandatory if the setting `Config Endpoint` is not set. ### Token Endpoint URL URL of the provider's token endpoint. This setting is mandatory if the setting `Config Endpoint` is not set and the response type used is `code`. ### Userinfo Endpoint URL URL of the provider's endpoint to fetch the user's information. This setting is mandatory of the provider type is OAuth2. ### Additional parameters Additional parameters to pass to the auth endpoint query string ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/OIDC.md�������������������������������������������������������������������������0000664�0000000�0000000�00000232072�14156463140�0015360�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Glewlwyd OpenID Connect Plugin documentation [![License: CC BY 4.0](https://licensebuttons.net/l/by/4.0/80x15.png)](https://creativecommons.org/licenses/by/4.0/) This plugin is based on the [OpenID Connect Core 1.0 specification](https://openid.net/specs/openid-connect-core-1_0.html) and allows Glewlwyd to act as an OpenID Provider (OP). ## Functionalities summary The following OpenID Connect and OAuth2 functionalities are currently supported: - [Authorization Code Flow](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth) - [Implicit flow](https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth) - [Hybrid flow](https://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth) - [UserInfo Endpoint](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo) - [OAuth 2.0 Multiple Response Types](http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html) - [OpenID Connect Discovery](http://openid.net/specs/openid-connect-discovery-1_0.html) - [Address Claims](https://openid.net/specs/openid-connect-core-1_0.html#AddressClaim) - [Requesting Claims using the "claims" Request Parameter](https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter) - [Requesting Claims using Scope Values](https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims) - [Client authentication](https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication) using HTTP Basic Auth, POST Parameter or JWT - [Passing Request Parameters as JWTs](https://openid.net/specs/openid-connect-core-1_0.html#JWTRequests) - [Subject Types public or pairwise](https://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes) - [Proof Key for Code Exchange by OAuth Public Clients](https://tools.ietf.org/html/rfc7636) - [Token introspection (RFC 7662)](https://tools.ietf.org/html/rfc7662) - [Token revocation (RFC 7009)](https://tools.ietf.org/html/rfc7009) - [OpenID Connect Dynamic Registration](http://openid.net/specs/openid-connect-registration-1_0.html) - [OAuth 2.0 Dynamic Client Registration Protocol](https://tools.ietf.org/html/rfc7591) - [OAuth 2.0 Dynamic Client Registration Management Protocol](https://tools.ietf.org/html/rfc7592) - [OAuth 2.0 Form Post Response Mode](http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html) - [Messages encryption](https://openid.net/specs/openid-connect-core-1_0.html#Encryption) - [Session Management](https://openid.net/specs/openid-connect-session-1_0.html) - [JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens](https://www.rfc-editor.org/rfc/rfc9068.html) - [OAuth 2.0 for Native Apps](https://tools.ietf.org/html/rfc8252), see [Native Apps Guidelines](#native-apps-guidelines) - [OAuth 2.0 Device Authorization Grant](https://tools.ietf.org/html/rfc8628) - [OAuth 2.0 Demonstration of Proof-of-Possession at the Application Layer (DPoP) Draft 04](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-dpop-04) - [JWT Response for OAuth Token Introspection Draft 12](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-jwt-introspection-response-12) - [Resource Indicators for OAuth 2.0](https://tools.ietf.org/html/rfc8707) - [OAuth 2.0 Rich Authorization Requests Draft 03](https://www.ietf.org/archive/id/draft-ietf-oauth-rar-03.html) - [OAuth 2.0 Pushed Authorization Requests](https://datatracker.ietf.org/doc/html/rfc9126) - [OAuth 2.0 JWT Secured Authorization Request (JAR)](https://datatracker.ietf.org/doc/html/rfc9101) - [OpenID Connect Client-Initiated Backchannel Authentication Flow](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html) - [OAuth 2.0 Authorization Server Issuer Identification](https://www.ietf.org/id/draft-ietf-oauth-iss-auth-resp-01.html) - [Financial-grade API Security Profile 1.0 - Part 1: Baseline](https://openid.net/specs/openid-financial-api-part-1-1_0-final.html) - [Financial-grade API Security Profile 1.0 - Part 2: Advanced](https://openid.net/specs/openid-financial-api-part-2-1_0.html) - [Financial-grade API: Client Initiated Backchannel Authentication Profile](https://bitbucket.org/openid/fapi/src/master/Financial_API_WD_CIBA.md) - [OpenID Connect Front-Channel Logout 1.0 - draft 04](https://openid.net/specs/openid-connect-frontchannel-1_0.html) - [OpenID Connect Back-Channel Logout 1.0 - draft 06](https://openid.net/specs/openid-connect-backchannel-1_0.html) The following OpenID Connect functionalities are not supported yet: - [Self-Issued OpenID Provider](https://openid.net/specs/openid-connect-core-1_0.html#SelfIssued) ## Messages encryption Glewlwyd OIDC plugin relies on Rhonabwy library to sign and encrypt tokens. All the JWT signing algorithms are supported but not all the JWT key management encryption algorithms and data encryption algorithms. The discovery endpoint is up-to-date with the supported encryption algorithms. Concerning key management encryption using symmetric keys, the encryption key is based on a hash of the client secret or the server secret depending on the token to encrypt. To generate a symmetric key encryption, you must build a SHA256 hash of the secret for the key management encryption algorithms `A128KW`, `A192KW`, `A256KW`, `A128GCMKW`, `A192GCMKW` and `A256GCMKW`. Depending on the alg value, you must use the n first bytes of the hash as follows: - `A128KW`, `A128GCMKW`: 16 first bytes of the hash - `A192KW`, `A192GCMKW`: 24 first bytes of the hash - `A256KW`, `A256GCMKW`: 32 bytes of the hash If the key management encryption algorithm is `dir`, you must build a SHA512 hash of the secret, then depending on the `enc` value, you must use the n first bytes of the hash as follows: - `A128CBC-HS256`, `A128GCM`, `A192GCM`, `A256GCM`: 32 first bytes of the hash - `A192CBC-HS384`: 48 first bytes of the hash - `A256CBC-HS512`: 64 bytes of the hash ### Multiple keys If the plugin has multiple keys available, the client can choose any of them to encrypt their requests. But the client MUST set the `kid` value in the JWT header to specify the key to use for decryption. ### Access tokens, refresh tokens and code encryption If the client receives an encrypted access tokens, refresh tokens or code, it must decrypt it in order to use it with Glewlwyd. In particular, the encrypted access token must be decrypted and the nested JWS must be extracted to be usable by Glewlwyd for getting access to userinfo, token revocation or introspection, etc. ## Access token format As a heir of [Glewlwyd OAuth2 plugin](OAUTH2.md), Glewlwyd OpenID Connect plugin uses JWTs as access tokens. Therefore, the access token can be used by the client or the third party web service to identify the user and the scopes available with this access token. The access token claims format implements [JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens - draft 05](https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-05). An access token payload has the following JSON format: ```Javascript { "iss": "https://glewlwyd.tld/", // Issuer "sub": "4321zyxdcba", // subject that was provided this access_token "aud": "https://resource.tld/", // The resource this access_token is intended to, or the scope list "client_id": "client1", // client_id the access_token was provided to "jti": "abcdxyz1234", // token identifier "type": "access_token", // Hardcoded "iat": 1466556840, // Issued at time in Epoch Unix format "exp": 1466558840, // Expiration of the token in Epoch Unix format "nbf": 1466558840, // Not before time in Epoch Unix format "scope":"scope1 g_profile", // scopes granted to this access token in a string separated by spaces "claims": {}, // claims asked by the client "cnf": { "x5t#S256": xxx", // identifier of the certificate used to sign this JWT "jkt": "yyy" // thumbprint of the client public key if DPoP is used } } ``` ## Keys for JWT tokens signatures ### Single key pair in PEM format To create a key/certificate pair for RSA, ECDSA, RSA-PSS or EdDSA format, run the following commands on a Linux shell with openssl installed: ```shell $ # RSA/RSA-PSS KEY $ # private key $ openssl genrsa -out private-rsa.key 4096 $ # public key $ openssl rsa -in private-rsa.key -outform PEM -pubout -out public-rsa.pem $ # ECDSA/EdDSA KEY $ # private key $ openssl ecparam -genkey -name secp521r1 -noout -out private-ecdsa.key $ # public key $ openssl ec -in private-ecdsa.key -pubout -out public-ecdsa.pem ``` For more information on keys generation, see [OpenSSL Documentation](https://www.openssl.org/docs/). ### Multiple private keys in JWKS format If you want to use multiple keys for signatures, you need to setup the keys in JWKS format. The JWKS must follow these rules: - All keys in the JWKS must be either private ECC/RSA keys or symmetric keys - All keys must have a `'alg'` property with the value corresponding to the algorithm to use - All keys must have a `'kid'` property If the `'default-kid'` value is empty in the configuration, the first key in the JWKS will be the default signing key. ### Generate JWKS using rnbyc The library [Rhonabwy](https://github.com/babelouest/rhonabwy) comes with the command-line program [rnbyc](https://babelouest.github.io/rhonabwy/md_tools_rnbyc_README.html). You should have the program available if you installed Glewlwyd with the packages or using CMake. This tool can be used to generate a new private key or key pair in JWKS format and/or parse a key in JWKS or PEM format. Check out [rnbyc documentation](https://babelouest.github.io/rhonabwy/md_tools_rnbyc_README.html) for more information about this tool. #### Example 1: Generate a JWKS with a single ECDSA 256 private key This simple example shows how to generate a ECDSA 256 private key. ```shell $ rnbyc -j -g ec256 -k key-1 -a ES256 -p /dev/null { "keys": [ { "kty": "EC", "x": "AN64-jEEs_0zQfuUJI-9Rik6hkYMrIDHzSUfT3jlrA-q", "y": "APmN2Hk4SxihpBzQAZRVHlpxJS6O_0q-k8JgCcN-hj88", "d": "BvC2P98BQsYiMHqPqqfsguXe2Vl92JmZnB6Pj0jTHsM", "crv": "P-256", "kid": "key-1", "alg": "ES256" } ] } ``` #### Example 2: Generate a JWKS with a ECDSA 256 private key and a 2048 bits RSA key ```shell $ rnbyc -j -g ecdsa256 -k key-1 -p /dev/null -g rsa2048 -k key-2 -p /dev/null { "keys": [ { "kty": "EC", "x": "AN64-jEEs_0zQfuUJI-9Rik6hkYMrIDHzSUfT3jlrA-q", "y": "APmN2Hk4SxihpBzQAZRVHlpxJS6O_0q-k8JgCcN-hj88", "d": "BvC2P98BQsYiMHqPqqfsguXe2Vl92JmZnB6Pj0jTHsM", "crv": "P-256", "kid": "key-1", "alg": "ES256" }, { "kty": "RSA", "n": "ANpN63DvHZKWlMEk94xq3vheqfSPMyrkvIDLoTeD_ONmZFfiJ9fjRKa8uZPIJ01woRGTMawVnqSCN8dusR79mv7lQn9jTMPMVUqD2ndGS6t5V2fW1YZO6TFjkFONvFB8U3G9JaB1apYSxZfx8oQwq6rQ3lLBDi07SlMbzl9cQGMceaFMC9mRquIh9svDdTzx9L7otRzET756i0whl7uKEfJ7wTR17LaeCf7eW_s7XVCgPRNSlR5FfQjoCM4AMGRbEn5HwVTds9jBxyFGLTj1sN570NIzg2bNtIdk4EDjAE5ZBrLn02nCH4B75Cx4R9zzZ4rhMV4UR39kpIt7gqjY9zs", "e": "AQAB", "d": "AI032LV-yVeZd3MWUdkRDVoICtN8izIVM-fxUkISbNZB12mOkA21JCTkcvwpf1s2H8u5t2lFtxFed2zYq1WL4uc_MWwstWz30rjYYMvFuo-beDJhJFG03F8ptCSIzKgYNPaf0CZjrmaUkCQBzMs8nOBsK1XHMz-JWkQ2-aJwj-pMQFBjXX02j-rokXVCsEflgFCjXgTGaC8wAOlF1omKU53heW8DzjpQgVHCKumIw2B8eLNOW4LlR_jxg-dqta4G-6Hqou5zdXnEeHbkXV-bnS2l3mIcqAt-Hp0EbpSBFzth3uUpgihwx1uiwCFoULNiZWGbnrUEuZBpJngleWUr-Fk", "p": "APRew0zj8zGv7xoByRi41XqaH2ZfC_GrddAK3gYVdYWDAt3pHbUQofrT-yhF6CsQTz-L7iR4VHNf-j7S3knRUPf6hggB1sb4_6D8a2fH4rI1TrDwXf5Mm9PDfOjkx6ukgzgsbiICsS8kK4Vpy3IvbNqGtCribU5B0-kpHgHjsnvH", "q": "AOSxltWZopKfA74DFSs4fjnq_P4jl58IRmMMtnzfy-xXxVCJ-VPmzABfKlrhqtIckdYLeJE5Xxt76Mcq3-4YBSwWUHSkejGUAaLGfJspIEx7l3xOiCwpP2jVtpYUrhEMTbRZMlc7E5Ko1R7tU_pviMfLZwK5JrakQcYhgbG2wKDt", "qi": "XNkpOGrFesrOkdSteU7Ew-aaB4xxSLEE2xz50nbJ2ck8qR5u6C7r1F4AeaKxGABpT_HKcoZ7pD3NUnR_yzTwQTW8Sn3WWeeRazXXxXxTw6prgLRiA189RLLuzatPd7Lyl_mwWpmWHR6pA-iWIXDlByoNMVPTXKoCU3CBs-s1DS4", "dp": "TFl_M3rU9OU_EyUTq4G0UUXuIZH4rV0gxgtfKw9xVHGGZ8b53SSBN1kb041j6HCEEhqqIQLnf9Sw3wgLI40eexvu3HmWnTwWwjmbZSVykrNNDsNK2rUcyqD9WdaA_AO-a8KV9lJZAZ2Pa3OOePKQVAZaLDvqYtT2XJbYJUb68Ok", "dq": "AKox9APw-4lMqBdP6gApYd8un6tux5cGLIPoYSMb1oKEa4bdt90WBTo9mKLcESmfM3VtQgQqAzrMA1e987sYyzidPrEf-wRMls8SEofSmoPw8rMDKtpatCML8X9N0qFDW8zdGNbMU2uxFDKZAwd82_l8yaPAMSx5n62ZIBEm5cKd", "kid": "key-2" } ] } ``` #### Example 3: Key rotation using rnbyc and reload config automatically using API Keys In this example, you need to generate a new key every week, and want to keep the previous key *alive* for compatibility reasons. In Glewlwyd's OIDC plugin, if you provide several private keys without specifying the default kid, the first one will be the default key to sign the JWTs. So in this example, the new key will be the first in the list, and the previous one will be the second one. Therefore clients and resource providers will be able to verify a token signature for valid tokens. Each private key generated will be stored in a file. First, generate a *week 0* private key: ```shell $ # week 0: generate a first private key ECDSA256 $ rnbyc -j -g ecdsa256 -o $(date +%Y-%V).jwks -p /dev/null -n 0 $ # use the generated file content as the private key for token signature $ cp $(date +%Y-%V).jwks private.jwks ``` Every other week, generate a new private key and concat last week's private key in the generated JWKS ```shell $ # next weeks: generate a new private key ECDSA256 and concat this week and last week's private key in the generated JWKS $ rnbyc -j -g ecdsa256 -o $(date +%Y-%V).jwks -p /dev/null -n 0 $ rnbyc -j -f $(date +%Y-%V).jwks -f $(date --date="last week" +%Y-%V).jwks -o private.jwks -p /dev/null -n 0 ``` Then, after each key generation, you can use the content of the file `private.jwks` to update your OIDC plugin configuration. The following commands show an example on how to automatically update an OIDC plugin with the new private key with an [API key](GETTING_STARTED.md#access-to-administration-api-via-api-keys), and the command-line tools [curl](https://curl.haxx.se/) and [jq](https://stedolan.github.io/jq/). ```shell $ # Get the current configuration for plugin 'oidc' $ curl -s 'http://localhost:4593/api/mod/plugin/oidc' -H 'Authorization: token XJcv1MRnK33EHAedPGELl0yXx2W6vUPu' -o oidc.json $ # Set the content of the file private.jwks to an environment variable $ export private=$(cat private.jwks) $ # update the current configuration with the new private key JWKS file and save to file oidc-2.json $ jq '.parameters ."jwks-private" = env.private' --compact-output oidc.json > oidc-2.json $ # Update the configuration for plugin 'oidc' $ curl -s -H 'Authorization: token XJcv1MRnK33EHAedPGELl0yXx2W6vUPu' -X "PUT" -H "Content-Type: application/json" -d @oidc-2.json 'http://localhost:4593/api/mod/plugin/oidc' $ # Reset the plugin 'oidc' to load the new private keys $ curl -s -H 'Authorization: token XJcv1MRnK33EHAedPGELl0yXx2W6vUPu' -X "PUT" 'http://localhost:4593/api/mod/plugin/oidc/reset' ``` ## Installation ![plugin-oidc](screenshots/plugin-oidc.png) In the administration page, go to `Parameters/Plugins` and add a new plugin by clicking on the `+` button. In the modal, enter a name and a display name (the name must be unique among all user backend instances). Select the type `Glewlwyd OpenID Connect plugin` in the Type drop-down button. Below is the definition of all parameters. ### Name Name (identifier) of the plugin instance, must be unique among all the plugin instances, even of a different type. ### Display name Name of the instance displayed to the user. ### Issuer Issuer that will be added in all ID Tokens, must correspond to your Glewlwyd instance URL. ### Subject type identifier Specify the way to identify subjects. If the selected value is `public`, the `sub` value in access tokens and id tokens will always have the same value. If the selected value is `pairwise`, the `sub` value in access tokens and id tokens will have a different values for clients of different `sector_identifier_uri` or if the `sector_identifier_uri` is empty, for different clients. ### Supported scopes Specify the list of scopes available in the property `scopes_supported` in the discovery endpoint. Important note: This list has no effect on what scopes are actually allowed by users for clients in the plugin instance, because by design, all scopes are available for use. The availability of a scope for a user to a client depends on the configuration of those 3 items, and you can't restrict the use of some scopes in the OpenID Connect plugin. The meaning of existence of this list is to allow the administrator to choose which scopes will be shown on the discovery endpoint. Therefore, the administrator can chose to show in the discovery endpoint all scopes, only `openid` (which is mandatory in the specification) or a subset of all scopes available, including `openid`. ### Client restrict-scope property Specify the client property to store the list of scopes a client is allowed to get grant access from a user. Important note: If this configuration is set, then **all** clients must have the restrict scope list set according to the scopes they are allowed to ask for in a request. ### JWKS URI URI to fetch the private keys JWKS. This uri is loaded each time the plugin is enabled. If you want to update your server keys, you must restart the Glewlwyd server or call the API [Enable or disable an existing plugin module instance](API.md#enable-or-disable-an-existing-plugin-module-instance) with the action value `reset`. ### JWKS to use Enter the JWKS value directly or select the local file tat contains the private keys JWKS. ### Default Key ID (KID) The default KID that will be used to sign tokens if the client does not specify a `sign_kid` value. If not set, the default KID will be the first one in the JWKS. ### Client sign_kid property Client property that will hold the default kid. This option is used to specify a different KID than the default one for a specific client. ### Public JWKS URI URI to fetch the public keys JWKS. This uri is loaded each time the plugin is enabled. If you want to update your server keys, you must restart the Glewlwyd server or call the API [Enable or disable an existing plugin module instance](API.md#enable-or-disable-an-existing-plugin-module-instance) with the action value `reset`. Note: This setting value is optional, if you omit this value, the public key will be extracted from your JWKS private key. Use this setting if you want to specify the public keys available in the public JWKS endpoint: `/api/<plugin_name>/jwks` ### Public JWKS Enter the JWKS value directly or select the local file tat contains the private keys JWKS. Note: This setting value is optional, if you omit this value, the public key will be extracted from your JWKS private key. Use this setting if you want to specify the public keys available in the public JWKS endpoint: `/api/<plugin_name>/jwks` ### JWT Type Algorithm used to sign access tokens and ID Tokens. The algorithm supported are `RSA` and `ECDSA` using a private and a public key, and `SHA` using a shared secret. ### Key size Size of the key to sign the tokens. The sizes supported are 256 bits, 384 bits or 512 bits. ### Secret key Private key file used to sign if the selected algorithm is `RSA` or `ECDSA`. Must be a PEM format file. Shared secret if the selected algorithm is `SHA`. ### Public key Public key file used to validate access tokens if the selected algorithm is `RSA` or `ECDSA`. Must be a PEM format file. ### Access token duration (seconds) Duration of each access tokens. Default value is 3600 (1 hour). ### Refresh token duration (seconds) Duration of validity of each refresh tokens. Default value is 1209600 (14 days). ### Code duration (seconds) Duration of validity of each code sent to the client before requesting a refresh token. Default value is 600 (10 minutes). ### Refresh token rolling If this option is checked, every time an access token is requested using a refresh token, the refresh token issued at time will be reset to the current time. This option allows infinite validity for the refresh tokens if it's not manually disabled, but if a refresh token isn't used for more of the value `Refresh token duration`, it will be disabled. ### One-time use refresh token Updates the one-time use for refresh tokens. Values available are - `Never`: The one-time use is disabled - `Always`: The one-time use is always on - `Client-driven`: The one-time use is enabled only for clients who allow it with a specified property A one-time use refresh token will be disabled after successfully getting an access token from it, but then a new refresh token will be available along with the new access token in the JSON response. A chain of refresh token will be created, adding a new and enabled refresh token and disabling the previous one on each refresh. However, if a refresh token is used twice, the chain will be considered broken (i.e. a refresh token has been stolen), therefore the last refresh token of the chain will be disabled. ### refresh-token-one-use property Enter the client property that will hold the `refresh-token-one-use` flag of the client. This property value will tell if the client allows to encrypt refresh tokens code. ### Allow non OIDC but valid OAuth2 requests If this option is checked, the plugin instance will allow requests that are not allowed in the OIDC standard but valid in the OAuth2 standard, such as response_type: `token` (alone), `password` or `client_credential`. In those cases, the request will be treated as a normal OAuth2 but the response will not have an ID Token. ### Authentication type code enabled Enable response type `code`. ### Revoke all tokens if a client tries to replay a code If this option is set, when a code is replayed to gain a refresh token, all the refresh and access tokens delivered for this code will be revoked. This option can be used to mitigate replay attacks and enforce tokens security. ### Authentication type token enabled Enable response type `token`. ### Authentication type ID Token enabled This option is enabled and can't be disabled. ### Authentication type password enabled Enable response type `password`. ### Authentication type client enabled Enable response type `client_credential`. ### Authentication type device enabled Enable response type `device_code`. ### Authentication type refresh enabled Enable response type `refresh_token`. ### Service documentation URL (optional) `openid-configuration` URL to the service documentation to help users or client to connect to Glewlwyd server, default is Glewlwyd GitHub documentation. ### Service policy URL (optional) `openid-configuration` URL to the service policy. ### Terms of service URL (optional) `openid-configuration` URL to the terms of service. ### JWKS available Enable JWKS available at the address `<plugin_root>/jwks`. Note, JWKS will display public keys for key types `RSA` and `ECDSA` only. ### X5C certificate chain (optional) Add or remove the chain of X5C certificate to help clients and users validate the certificate chain of each `id_token` or `access_token`. Ignored if the keys are specified via JWKS. ### Specific scope parameters This section allows to put specific settings for an scope that will override the plugin settings. The settings that you can override are `Refresh token duration` and/or `Rolling refresh`. Please note that a specific scope parameter has a higher priority than the plugin settings, and if have multiple scopes in a request that have specific settings, the settings will follow the following algorithm: - Refresh token duration: The duration provided will be the lowest duration among all the specific scope parameters. - Rolling refresh: The value `No`has higher priority, therefore rolling refresh provided will be `No` if one scope has the value `No`, `Yes` otherwise ### Additional token values This section allows to add specific values to the access_tokens that will be taken from the user property values. You can add as many additional values as you want. If the property isn't present in the user data, it will be ignored. If the value is multiple, all values will be present, separated by a comma `,`. ### Additional claims in the ID Token or the /userinfo endpoint This section allows to add specific claims in ID Tokens or userinfo results and to specify name, e-mail and granted scope claim handling. `Name claim`, `E-mail claim` and `Scope` properties can have one of the following values: - No: The value will never be available on ID Tokens or userinfo results - On demand: The value will be available if specifically asked in the `claims` parameter - Mandatory: The value will always be available on ID Tokens or userinfo results In addition, you can add these claims as scope claims. If you add additional claims, the options available are the following. If you specify a type `number`, the value will be converted from a string to an integer. If the conversion fails, the value will be ignored. If you specify a type `boolean`, you must specify the values for `true` and `false`. If the value doesn't match, it will be ignored. If you check the option `Mandatory`, the claim will be added in all ID Tokens or userinfo calls, even if the claim isn't requested by the user. Finally, you add scopes to additional claims. ### Address claim properties This section allows to specify how to handle address claim. If the drop-down button `Use claim address` is set to `No`, the claim address isn't available for any user. If the drop-down button `Use claim address` is set to `On demand` or 'Mandatory', you must specify which properties available in the user profile will match the address claim properties. If an address claim property is empty or its corresponding property value in the user profile is empty or unavailable, the value will not be present in the address claim. If the address claim is empty, it will not be present in the result. ## Claims request You can specify claims in your request according to the ["claims" request parameter](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims). The claims request parameter can be present in the URL in JSON format url-encoded if the HTTP method is `GET`, in the post body parameter in JSON format url-encoded if the HTTP method is `POST`, or in the JWT payload if you use JWT request parameter. The supported options for a claim are `null`, `value` and `values`. Option `essential` isn't supported. For example: ```Javascript { "userinfo": { "given_name": null, "nickname": null, "email": {"value": "dev@glewlwyd.tld"}, "picture": null, }, "id_token": { "acr": {"values": ["urn:mace:incommon:iap:silver"] } } } ``` To have a claim available in the claim request, it must be set to `on-demand` in the plugin configuration. ## JWT requests, public keys and JWKS ### Allow passing request parameter as JWT Allow using request parameters as JWT with `request` objects or `request_uri` links. ### Strict compliance with IETF request parameter If this is set, the JWT requests must be strictly compliant with the [IETF definition](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-jwsreq-32) rather than the [OIDC definition](https://openid.net/specs/openid-connect-core-1_0.html#JWTRequests). The main difference are: - The `client_id` value present in the JWT and the url - The JWT `typ` header value must be `oauth-authz-req+jwt` - The response content-type when using a `request_uri` must be `application/oauth-authz-req+jwt` or `application/jwt` ### Allow encrypted request parameter as JWT The client can send a nested JWT by using its secret value or the server public keys to encrypt the claims. ### Allow request_uri and jwks_uri to a unsecured https:// uri If the specified `request_uri` link points to an insecure https:// page with invalid certificate or hostname, allow it anyway. Warning! This may lead to unsecured connections or MITM attacks. ### Maximum expiration time authorized for JWT requests Maximum time a token JWT request is allowed to use in seconds. A JWT request with a higher expiration time will be refused. ### pubkey property Enter the client property that will hold a public key in PEM format. ### JWKS property Enter the client property that will hold the JWKS of the client. ### JWKS_URI property Enter the client property that will hold the JWKS_URI of the client. The JWKS will be downloaded each time the JWKS is requested. ## Encrypt out tokens ### Allow out token encryption Allow to encrypt out tokens for client that have an encryption setup. ### enc property Enter the client property that will hold the `enc` of the client. If not set, the `enc` value will be set to default `A128CBC-HS256`. ### alg property Enter the client property that will hold the `alg` of the client. If not set token encryption will be disabled for all. ### kid_alg property Enter the client property that will hold the `kid_alg` of the client. Mandatory if the client uses jwks, ignored if the client uses a public key in PEM format. ### encrypt_code property Enter the client property that will hold the `encrypt_code` flag of the client. This property value will tell if the client allows to encrypt authorization code. ### encrypt_at property Enter the client property that will hold the `encrypt_at` flag of the client. This property value will tell if the client allows to encrypt access tokens. ### encrypt_userinfo property Enter the client property that will hold the `encrypt_userinfo` flag of the client. This property value will tell if the client allows to encrypt userinfo when sent as JWT. ### encrypt_id_token property Enter the client property that will hold the `encrypt_id_token` flag of the client. This property value will tell if the client allows to encrypt id_tokens. ### encrypt_refresh_token property Enter the client property that will hold the `encrypt_refresh_token` flag of the client. This property value will tell if the client allows to encrypt refresh tokens code. ## PKCE - Code challenge (RFC 7636) This section is used to configure [Proof Key for Code Exchange by OAuth Public Clients](https://tools.ietf.org/html/rfc7636). ### PKCE allowed Allow if you want to support code challenge. ### Method plain allowed Enable this feature if you want to allow method `plain` in the code challenge feature. It is not recommended to enable this feature unless you know what you do because this feature is slightly less secure than default method `S256`. According to [the specifications](https://tools.ietf.org/html/rfc7636#section-4.2): ``` Clients are permitted to use "plain" only if they cannot support "S256" for some technical reason and know via out-of-band configuration that the server supports "plain". ``` #### Scopes where PKCE is mandatory Select the scopes for which PKCE is mandatory when using `auth` or `par` requests. #### PKCE required Allow if you want to require all clients to use PKCE when using `auth` or `par` requests. ## Tokens Introspection (RFC 7662) and Revocation (RFC 7009) **IMPORTANT NOTICE!** Glewlwyd access tokens are [JWTs](https://tools.ietf.org/html/rfc7519), the original way for resource services to check if an access token or an id_token is valid and reliable is to check its signature and its expiration date. Token introspection and revocation have been introduced in Glewlwyd, but if the resource service doesn't use the introspection endpoint, it will miss an inactive token and still consider it valid. The endpoints `/userinfo`, `/introspect` and `/revoke` when they are given an access token to authenticate will check if the token is revoked or not. ### Allow tokens introspection and invocation Enable this feature if you want your oauth2 instance to enable endpoints `/introspect` and `/revoke`. ### Allow for the token client Enable this feature if your want to allow clients to use endpoints `/introspect` and `/revoke` using their client_id and secret as HTTP Basic Auth. The client will be allowed to introspect and revoke tokens if it has the auhtorization type `client_credentials`. ### Required scopes in the access token Add one or more scopes if you want to allow to use endpoints `/introspect` and `/revoke` using valid access tokens to authenticate the requests. The access tokens must have the scopes required in their payload to be valid. ## Clients registration This section is used to parameter client registration as defined in [OpenID Connect Dynamic Registration](http://openid.net/specs/openid-connect-registration-1_0.html). If enabled, the administrator can (should?) require an access token with the proper scope to be able to register a new client. How this `access_token` is provided is out of scope of this documentation. ### Allow client registration via API /register Enable this feature if you want to enable client registration endpoint `/register`. ### Required scopes in the access token Add on or more scopes if you want to allow to use endpoint `/register` using valid access tokens to authenticate the requests. The access tokens must have the scopes required in their payload to be valid. ### Scopes to add to the new clients Default scopes that will be added to the registered clients, can be empty. This scope list is only used in `client_credentials` response type. ### Allow clients to manage their registration Add endpoints for clients to manage their registration as defined in [OAuth 2.0 Dynamic Client Registration Management Protocol](https://tools.ietf.org/html/rfc7592). ### Default properties Add default properties and values to clients during registration. ## Session management ### Allow session management endpoints If this is set to true, a session ID will be used to identify the user's session, the same `sid` parameter will be added to all ID Tokens for the same session. The following session management endpoints will be available: - `GET /api/{plugin_name}/end_session`: Start the end session flow. The query parameter `id_token_hint` referencing the ID Token's session to close. In addition, the optional parameters `post_logout_redirect_uri` and `state` can be passsed. If a valid id_token is used as parameter, the endpoint will redirect to the login page with valid parameters to complete the session ending - `GET /api/{plugin_name}/session/:sid/:client_id`: Get the list of clients which are referrenced by this session and use OpenID Connect Front-Channel Logout (if enabled) - `DELETE /api/{plugin_name}/session/:sid/`: Confirm the suppression of this session and send an end session token to the clients which are referrenced by this session and use OpenID Connect Back-Channel Logout (if enabled) - `GET /api/{plugin_name}/check_session_iframe`: Check the current session as standardized in [Session Management](https://openid.net/specs/openid-connect-session-1_0.html) ## Device Authorization management ### Code expiration (seconds) `device_code` expiration in seconds. ### Suggested interval between code verification (seconds) If the client sends the device code in a shorter period, it will receive a `slow_down` error response. ## Client secret vs password When you add or edit a client in Glewlwyd, you can set a `client secret` or a `password`. Both can be used to authenticate confidential clients. The primary difference is that a client secret is a string stored 'as is' in the backend (database or LDAP), without hashing, where a client password is stored in a hashed form in the backend, so makes it more difficult for attackers to retrieve it. A client secret has priority over a client password, which means that if a client has set both client secret and client password, the authentication will be executed with client secret only. The `client secret` can also be used to authenticate a client using the method `client_secret_jwt`. ## Mutual-TLS Client Authentication (rfc8705) !!!Full disclosure!!! This authentication scheme has been implemented based on the documentation and examples I could find. But there may be other and better ways to implement this type of authentication. If you find bugs, weird behaviours, or wish new features, please [open an issue](https://github.com/babelouest/glewlwyd/issues) in the GitHub repository or send an e-mail. If you intent to use Glewlwyd directly, set this option to `TLS Session`. You must also configure Glewlwyd in secure mode with the proper `secure_connection_ca_file` value. This configuration value must be set to your CA certificate or your CA chain certificate in order to validate clients certificates provided. Self-signed certificates couldn't be tested in TLS sessions. ### Certificate source Where to retrieve the client certificate. Values available are: - `None` to disable Mutual-TLS Client Authentication - `TLS session` to retrieve the client certificate in the TLS session - `HTTP header` to retrieve the client certificate in the HTTP header - `Both` to retrieve the client certificate both in the TLS session and the header Important security warning! If you don't use Glewlwyd behind a reverse proxy to forward the certificate in the header, this option MUST be set to `TLS Session` only, otherwise, an attacker could manually change the header value, to fake any valid user without having to know its certificate key. If you set this value to `HTTP Header` or `both`, it allows to use Glewlwyd behind a reverse proxy such as Apache's mod `proxy`. You must then configure the proxy to validate the clients certificate and key using your CA certificate and if the client certificate is valid, the proxy must forward the X509 certificate to Glewlwyd in a specified header. ### Corresponding header property Header property name to retrieve client certificate. Must be set if certificate source is `header` or `Both`. ### Use alias for client endpoints If this is set to true, new client endpoints will be added: `mtls/token` as well as `mtls/introspect`, `mtls/revoke` and `mtls/device_authorization` if necessary. ### Allow self-signed certificates If this is set to true, clients will be allowed to use self-signed certificates for authentication. **WARNING** This setting must be used with high precaution! Self-signed certificates are less secure than certificates delivered by trusted CA. ## OAuth 2.0 Demonstration of Proof-of-Possession at the Application Layer (DPoP) (Draft 01) Glewlwyd OIDC plugin supports OAuth 2.0 Demonstration of Proof-of-Possession at the Application Layer (DPoP) (Draft 01). The DPoP header can be used during a response_type `code` or `device_code`, also the endpoints `/userinfo` and `/token` will validate the DPoP header if the access token used has a `jkt` property. The endpoints `/register`, `/introspect` or `/revoke` won't validate the DPoP header by design. ### Allow DPoP If this is set to true, the DPoP header will be parsed and validated when enabled. ### DPoP Token iat claim maximum duration (seconds) Maximum duration authorized for a DPoP iat property. ## Resource Indicators (RFC 8707) ### Allow resource indicators If this is set to true, clients will be allowed to use the resource parameter during `/auth` or `/token` requests. If this is set to false, the resource parameter will be ignored, and the `aud` claim will be set to the scope list. If there's no resource parameter, the `aud` claim will be set to the scope list as well. ### Allow to change resource indicator on access token refresh If this is set to true, client will be allowed to change the resource parameter while refreshing an access token. ### Resource list by scope For all scopes, you can specify a list of allowed resources available. When the client is claiming multiple scopes, the given resource parameter must match at least one of the resource of one of the scopes. ### Client resource list property The client property that will store the resources allowed for this client ### And/Or Configure if the claimed resource must be available in one of the clained scopes **and/or** in the client resource list available. ## OAuth 2.0 Rich Authorization Requests (Draft 03) This settings will allow your Glewlwyd instance to provide `authorization_details` objects in the access tokens or the introspection endpoint, based on the `authorization_details` request specified by the client. ### Allow OAuth 2.0 Rich Authorization Requests If this is set to true, client will be allowed to use rich authorization requests. ### Allow unsigned OAuth 2.0 Rich Authorization Requests If this is set to true, clients will be allowed to add a `authorization_details` in the url in JSON format. This setting is disabled by default because then the `authorization_details` value an be changed by the user or a third party, and lead to data leaks or broken process. If you want to enable this feature, you must be aware of the risks. ### Allow unencrypted OAuth 2.0 Rich Authorization Requests If this is set to true, clients will be allowed to add a `authorization_details` in authorization request objects without encryption, signed requests are mandatory though. ### Client authorization_data_types property The client property that will store the authorization requests types allowed for this client ### New type Enter the name of a new type ### Allowed scopes List of scopes allwed to be used with that type. If one of the allowed scopes is included in the request, the `authorization_details` request will be allowed. ### Description Description for the authorization request. Will be displayed to the user when consent is asked. ### Allowed locations (optional, one per line) `locations` values allowed for this authorization request. If a client requests a location not present in this list, the request is invalid. ### Allowed actions (optional, one per line) `actions` values allowed for this authorization request. If a client requests an action not present in this list, the request is invalid. ### Allowed datatypes (optional, one per line) `datatypes` values allowed for this authorization request. If a client requests a datatype not present in this list, the request is invalid. ### Enriched authorization details - user properties (optional, one per line) List of user properties available for enriched authorization details. If a client requests a property not present in this list, the request is invalid. The client must set the enriched properties in a `access` object in the `authorization_details` request. The properties requested are the keys of the object, the values for each keys are ignored. Example of an `authorization_details` request with `access` object: ```JSON [ { "type": "account_information", "access": { "accounts": [], "balances": [], "transactions": [] } } ] ``` ## OAuth 2.0 Pushed Authorization Requests (Draft 05) This settings is used to configure pushed authorization requests to enforce client privacy and security. ### Allow Pushed Authorization Requests If this is set to true, clients will be allowed to use pushed authorization requests via the endpoint `POST /par` to initiate the authentication. ### Require Pushed Authorization Request If this is set to true, pushed authorization requests will be mandatory, no client will be able to initiate an authorization request directly via `GET /auth`. ### request_uri prefix Value of the prefix that will be added to every request_uri. ### request_uri duration (seconds) Duration of a request_uri before user first accesses to the `/auth` using this `request_uri`. ## Client-Initiated Backchannel Authentication Flow This settings is used to configure Client-Initiated Backchannel Authentication Flow (CIBA). ### Allow Client-Initiated Backchannel Authentication Flow (CIBA) If this is set to true, clients will be allowed to initiate CIBA requests via the endpoint `POST /ciba`. ### Ping mode allowed If this is set to true, clients will be allowed to use ping mode on CIBA requests. ### Poll mode allowed If this is set to true, clients will be allowed to use poll mode on CIBA requests. ### Push mode allowed If this is set to true, clients will be allowed to use push mode on CIBA requests. ### Allow uot requests ping and push to a insecure https:// If this is set to true, if a `backchannel_client_notification_endpoint` points to an invalid server certificate, the request will continue. ### user_code parameter allowed If this is set to true, the client can use the `user_code` to verify the user identity. ### user_code property User property to use to match the `user_code`. ### Default expiration time (seconds) Default expiration time for a CIBA request when the client doesn't specify one. ### Maximum duration expiration time (seconds) Maximum duration of a CIBA request if the client specify one. ### Send an e-mail to the targeted user If this is set to true, an e-mail will be sent to the user when a CIBA request is created for this user. ### SMTP Server Address of the SMTP server that will relay the messages to the users, mandatory. ### Port SMTP (0: System default) TCP port the SMTP server is listening to. Must be between 0 and 65535. If 0 is set, Glewlwyd will use the system default port for SMTP, usually 25 or 587, mandatory. ### Use a TLS connection Check this option if the SMTP server requires TLS to connect. ### Check server certificate Check this option if you want Glewlwyd to check the SMTP server certificate before relaying the e-mail. This is highly recommended if TLS connection is checked, useless otherwise. ### SMTP username (if required) username used to authenticate to the SMTP server if required by the SMTP server, optional. ### SMTP password (if required) password used to authenticate to the SMTP server if required by the SMTP server, optional. ### E-mail sender address Address used as sender in the e-mails, required. ### Content-Type Content-Type for the e-mails, default is plain text but you can set an HTML body if youo need to. ### User lang property User property which will contain the default lang value used for the e-mail templates. The lang value must be an exact match of the lang template. If the user lang doens't exist in the templates or if the user has no lang property, the e-mail template will use the default language. ### Lang Dropdown value to select, add or remove lang templates for the e-mails. ### Default lang Checkbox to specify what lang is the default language. In case the user has no language value or its language value doesn't exist in the templates. ### E-mail subject Subject used on the e-mails for the current lang, required. ### E-mail body template, {CONNECT_URL} required, {CANCEL_URL}, {CLIENT} and {BINDING_MESSAGE} optional The pattern for the body on the e-mails for the current lang, You must use at least once the pattern `{CONNECT_URL}` in the template to be replaced by the connection url. The patterns `{CLIENT}`, `{CANCEL_URL}` and `{BINDING_MESSAGE}` are optional. Example, by using the following e-mail pattern: ``` Your authorization is required by the client {CLIENT}. Message from the client: {BINDING_MESSAGE}. Click on the following link to accept: {CONNECT_URL}. Click on the following link to cancel: {CANCEL_URL} ``` ## Financial-grade API additional options (FAPI) ### Select all below If this is set to true, all FAPI related options will be set to true and all encryption algorithms will be allowed, except for `dir` and `RSA1_5`. ### Allow JWT responses in /auth (JARM) If this is set to true, JWT response mode will be allowed. ### Add s_hash in id_token If this is set to true, the state hash will be added in the id_token payload as `s_hash`. ### Verify nbf property If this is set to true, the `nbf` parameter will be mandatory in jwt requests and `exp-nbf` must not succeed 60 minutes. ### Allow restricted algs for encryption If this is set to true, glewlwyd may restrict some algorithms for encryption. ### Allowed algs Dropdown menu to select allowed encryption algorithms. By design, algorithms `dir` and `RSA1_5` are forbidden in FAPI mode. ### Allow clients with multiple similar kid If this is set to true, clients with the same kid in its public key set is allowed. ### CIBA allowed clients must be confidential If this is set to true, clients must be confidential to use CIBA endpoint. ### Push mode forbidden If this is set to true, clients must use CIBA with poll or ping mode. ## Native Apps Guidelines Glewlwyd is conform to [OAuth 2.0 for Native Apps](https://tools.ietf.org/html/rfc8252) best current practice considering the following configuration: - Any `redirect_uri` is allowed if the client is manually added by an admin in the admin app - Any `https://` URL is allowed or unsecured loopback URL (locahost/127.0.0.1/[::1]) when the client uses the client registration endpoint. `redirect_uris` accepted in the client registration endpoint **MUST NOT** contain username attribute (`https://username@domain.tld`) - [PKCE](https://tools.ietf.org/html/rfc7636) extension is supported - A client can be specified as public (i.e. not confidential), without secret or public key ## Glewlwyd OpenID Connect endpoints specifications This document is intended to describe Glewlwyd OpenID Connect plugin implementation. OpenID Connect endpoints are used to authenticate the user, and to send tokens, id_tokens or other authentication and identification data. The complete specification is available in the [OpenID Connect Core](http://openid.net/specs/openid-connect-core-1_0.html). If you see an issue or have a question on Glewlwyd OpenID Connect plugin implementation, you can open an issue or send an email to the following address [mail@babelouest.org](mailto:mail@babelouest.org). - [Endpoints authentication](#endpoints-authentication) - [Prefix](#prefix) - [Login and grant URIs](#login-and-grant-uris) - [Scope](#scope) - [OpenID Connect endpoints](#openid-connect-endpoints) - [Authorization endpoint](#authorization-endpoint) - [Token endpoint](#token-endpoint) - [OpenID Connect schemes](#openid-connect-schemes) - [Authorization code grant - Authorization request](#authorization-code-grant---authorization-request) - [Authorization code grant - Authorization Response](#authorization-code-grant---authorization-response) - [Implicit Grant](#implicit-grant) - [ID Token Grant](#id-token-grant) - [Resource Owner Password Credentials Grant](#resource-owner-password-credentials-grant) - [Client Credentials Grant](#client-credentials-grant) - [Refresh token](#refresh-token) - [Invalidate refresh token](#invalidate-refresh-token) - [Userinfo endpoint](#userinfo-endpoint) - [openid-configuration endpoint](#openid-configuration-endpoint) - [Get JSON Web Key](#get-json-web-key) - [Manage refresh tokens endpoints](#manage-refresh-tokens-endpoints) - [List refresh tokens](#list-refresh-tokens) - [Disable a refresh token by its signature](#disable-a-refresh-token-by-its-signature) - [Token introspection and revocation](#token-introspection-and-revocation) - [Token introspection](#token-introspection) - [Token revocation](#token-revocation) - [Client registration](#client-registration) - [Session Management](#session-management) ### Endpoints authentication Authentication has different faces, and differs with the authorization scheme. ### Prefix All URIs are based on the prefix you will setup. In this document, all API endpoints will assume they use the prefix `/api/oidc`, and all static file endpoints will assume they use the prefix `/`. ### Login and grant URIs In this document, the login URI will be displayed as `http://login.html`, this will be replaced by the values from your environment that you can define in the config file. ### OpenID Connect endpoints #### Authorization endpoint This is a multi-method, multi-parameters, versatile endpoint, used to provide authentication management. It handles the following authorization schemes as describe in the [OpenID Connect Core](http://openid.net/specs/openid-connect-core-1_0.html): - Authorization Code Grant (Authorization part) - Implicit Grant - Hybrid Grant ##### URL `/api/oidc/auth` ##### Method `GET` `POST` #### Token endpoint This endpoint is used to provide tokens to the user. It handles the following authorization schemes as describe in the [OpenID Connect Core](http://openid.net/specs/openid-connect-core-1_0.html): - Authorization Code Grant (Access Token part) - ID Token Grant - Resource Owner Password Credentials Grant (if enabled) - Client Credentials Grant (if enabled) - Refreshing a token - Deleting a token ##### URL `/api/oidc/token` ##### Method `POST` ### OAuth 2 schemes Each scheme is described in the following chapter. The description may not be as complete as the [OAuth 2 RFC document](https://tools.ietf.org/html/rfc6749), consider the RFC as the authority standard. #### Authorization code grant - Authorization request ##### URL `/api/oidc/auth` ##### Method `GET` `POST` ##### URL (GET) or body (POST) Parameters Required ``` `response_type`: text, must be set to `code` `client_id`: text, client_id that sends the request on behalf of the resource owner, must be a valid client_id `redirect_uri`: text, redirect_uri to send the resource owner to after the connection, must be a valid redirect_uri for the specified client_id `scope`: text, scope list that the resource owner will grant access to the client, multiple scope values must be separated by a space, scope `openid` is mandatory in an OpenID Connect request `nonce`: text, recommended for response type code, mandatory for all other response types ``` Optional `state`: text, an identifier used to prevent requests collisions and bypass, will be sent back as is to the client ##### Result ###### Resource owner not authenticated Code 302 Resource owner is not authenticated with a valid session. Redirect to `http://login.html?client_id={client_id}&redirect_uri={redirect_uri}&scope={scope}&additional_parameters` for authentication. See login paragraph for details. ###### Scope not granted to the client Code 302 Redirect to `http://login.html?client_id={client_id}&redirect_uri={redirect_uri}&scope={scope}&additional_parameters` for grant access. ###### Success response Code 302 Redirect to `redirect_uri`?code=`code`&state=`state` with `redirect_uri` specified in the request, a `code` generated for the access, and the state specified in the request if any. ###### Error Scope Scope is not allowed for this user Code 302 Redirect to `redirect_uri`?error=invalid_scope&state=`state` with `redirect_uri` specified in the request, `invalid_scope` as error value, and the state specified in the request if any. ###### Error client Client is invalid, redirect_uri is invalid for this client, or client is not allowed to use this scheme Code 302 Redirect to `redirect_uri`?error=unauthorized_client&state=`state` with `redirect_uri` specified in the request, `unauthorized_client` as error value, and the state specified in the request if any. #### Authorization code grant - Authorization Response ##### URL `/api/oidc/token` ##### Method `POST` ##### Security If `client_id` refers to a confidential client, then client_id and client_password must be sent via Basic HTTP Auth. ##### Data Parameters Request body parameters must be encoded using the `application/x-www-form-urlencoded` format. ``` grant_type: text, must be set to "authorization_code". code: text, required redirect_uri: text, must be same redirect_uri used in the authorization request that sent back this code client_id: text, must be the same client_id used in the authorization request that sent back this code ``` ##### Success response Code 200 Content ```javascript { "access_token": text, jwt token "token_type": text, value is "bearer", "expires_in": number, set by server configuration "refresh_token": text, "id_token": text, jwt token } ``` ##### Error Response Code 400 Error input parameters The combination code/redirect_uri/client_id is incorrect. #### Implicit Grant ##### URL `/api/oidc/auth` ##### Method `GET` ##### URL Parameters Required ``` `response_type`: text, must be set to `token` `client_id`: text, client_id that sends the request on behalf of the resource owner, must be a valid client_id `redirect_uri`: text, redirect_uri to send the resource owner to after the connection, must be a valid redirect_uri for the specified client_id `scope`: text, scope list that the resource owner will grant access to the client, multiple scope values must be separated by a space ``` Optional `state`: text, an identifier used to prevent requests collisions and bypass, will be sent back as is to the client ##### Result ###### Resource owner not authenticated Code 302 Resource owner is not authenticated with a valid session. Redirect to `http://login.html?client_id={client_id}&redirect_uri={redirect_uri}&scope={scope}&additional_parameters` for authentication. See login paragraph for details. ###### Scope not granted to the client Code 302 Redirect to `http://grant.html?client_id={client_id}&redirect_uri={redirect_uri}&scope={scope}&additional_parameters` for grant access. See grant paragraph for details. ###### Success response Code 302 Redirect to `redirect_uri`#token=`token`&state=`state` with `redirect_uri` specified in the request, a `code` generated for the access, and the state specified in the request if any. ###### Error Scope Scope is not allowed for this user Code 302 Redirect to `redirect_uri`#error=invalid_scope&state=`state` with `redirect_uri` specified in the request, `invalid_scope` as error value, and the state specified in the request if any. ###### Error client Client is invalid, redirect_uri is invalid for this client, or client is not allowed to use this scheme Code 302 Redirect to `redirect_uri`#error=unauthorized_client&state=`state` with `redirect_uri` specified in the request, `unauthorized_client` as error value, and the state specified in the request if any. #### ID Token Grant ##### URL `/api/oidc/auth` ##### Method `GET` ##### URL Parameters Required ``` `response_type`: text, must be set to `id_token` `client_id`: text, client_id that sends the request on behalf of the resource owner, must be a valid client_id `redirect_uri`: text, redirect_uri to send the resource owner to after the connection, must be a valid redirect_uri for the specified client_id `scope`: text, scope list that the resource owner will grant access to the client, multiple scope values must be separated by a space `nonce`: text, nonce value generated by the client, mandatory ``` Optional `state`: text, an identifier used to prevent requests collisions and bypass, will be sent back as is to the client ##### Result ###### Resource owner not authenticated Code 302 Resource owner is not authenticated with a valid session. Redirect to `http://login.html?client_id={client_id}&redirect_uri={redirect_uri}&scope={scope}&additional_parameters` for authentication. See login paragraph for details. ###### Scope not granted to the client Code 302 Redirect to `http://login.html?client_id={client_id}&redirect_uri={redirect_uri}&scope={scope}&additional_parameters` for grant access. See grant paragraph for details. ###### Success response Code 302 Redirect to `redirect_uri`#id_token=`token`&state=`state` with `redirect_uri` specified in the request, a `code` generated for the access, and the state specified in the request if any. ###### Error Scope Scope is not allowed for this user Code 302 Redirect to `redirect_uri`#error=invalid_scope&state=`state` with `redirect_uri` specified in the request, `invalid_scope` as error value, and the state specified in the request if any. ###### Error client Client is invalid, redirect_uri is invalid for this client, or client is not allowed to use this scheme Code 302 Redirect to `redirect_uri`#error=unauthorized_client&state=`state` with `redirect_uri` specified in the request, `unauthorized_client` as error value, and the state specified in the request if any. #### Resource Owner Password Credentials Grant ##### URL `/api/oidc/token` ##### Method `POST` ##### Data Parameters Request body parameters must be encoded using the `application/x-www-form-urlencoded` format. ``` grant_type: text, must be set to "password". username: text password: text scope: text ``` ##### Success response Code 200 Content ```javascript { "access_token": text, jwt token "token_type": text, value is "bearer", "expires_in": number, set by server configuration "refresh_token": text, jwt token } ``` ##### Error Response Code 403 username or password invalid. #### Client Credentials Grant ##### URL `/api/oidc/token` ##### Method `POST` ##### Security HTTP Basic authentication with client_id/client_password credentials. Client_id must be set as confidential ##### URL Parameters Required Optional ##### Data Parameters Request body parameters must be encoded using the `application/x-www-form-urlencoded` format. ``` grant_type: text, must be set to "client_credentials". scope: text ``` ##### Success response Code 200 Content ```javascript { "access_token": text, jwt token "token_type": text, value is "bearer", "expires_in": number, set by server configuration } ``` ##### Error Response Code 403 Access denied #### Refresh token Send a new access_token based on a valid refresh_token ##### URL `/api/oidc/token` ##### Method `POST` ##### Data Parameters Request body parameters must be encoded using the `application/x-www-form-urlencoded` format. ``` grant_type: text, must be set to "refresh_token". refresh_token: text, a valid ref refresh_token, mandatory scope: text, must the same scope or a sub scope of the scope used to provide the refresh_token, optional ``` ##### Success response Code 200 Content ```javascript { "access_token": text, jwt token "token_type": text, value is "bearer", "expires_in": number, set by server configuration } ``` ##### Error Response Code 400 Error input parameters #### Invalidate refresh token Mark a refresh_token as invalid, to prevent further access_token to be generated ##### URL `/api/oidc/token` ##### Method `POST` ##### Data Parameters Request body parameters must be encoded using the `application/x-www-form-urlencoded` format. ``` grant_type: text, must be set to "delete_token". refresh_token: text, a valid refresh_token, mandatory ``` ##### Success response Code 200 ##### Error Response Code 400 Error input parameters ### Userinfo endpoint This endpoint is defined in the OpenID Connect core: [Userinfo Endpoint](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo). It's used to get information about a user in JSON format. Default information are displayed, and additional claims can be requested. #### URL `/api/oidc/userinfo` #### Method `GET`, `POST` #### Security A valid access token is required to access this endpoint. The user shown in this endpoint result will be the one the access token was created for. #### URL or POST body Parameters Optional ``` claims: text, list of additional claims separated by space format=jwt: send the result in JSON Web Token (JWT) format ``` #### Header parameters By default, the response format is `Application/JSON`, but the client can request a JWT response. The JWT will be signed with the server's private key, and can be encrypted using the client's secret or public key. ``` Accept: application/jwt - send the result in JSON Web Token (JWT) format ``` ##### Result ##### Success response Code 200 **JSON Format** Content ```javascript { "sub": text, subject of the endpoint (user) "name": text, name of the user "email": text, email of the user } ``` **JWT Format** Content ```javascript eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ``` ##### Error Response Code 403 Access denied ### openid-configuration endpoint This endpoint implements the [OpenID Connect discovery](http://openid.net/specs/openid-connect-discovery-1_0.html) API. #### URL `/api/oidc/.well-known/openid-configuration` #### Method `GET` ##### Result ##### Success response Code 200 Content `openid-configuration` content in JSON format. Example: ```javascript { "issuer":"https://glewlwyd.tld", "authorization_endpoint":"http://localhost:4593/api/oidc/auth", "token_endpoint":"http://localhost:4593/api/oidc/token", "userinfo_endpoint":"http://localhost:4593/api/oidc/userinfo", "jwks_uri":"http://localhost:4593/api/oidc/jwks", "token_endpoint_auth_methods_supported":[ "client_secret_basic" ], "token_endpoint_auth_signing_alg_values_supported":[ "HS256" ], "scopes_supported":[ "openid" ], "response_types_supported":[ "code", "id_token", "token id_token", "code id_token", "code token id_token", "none", "password", "token", "client_credentials", "refresh_token" ], "response_modes_supported":[ "query", "fragment" ], "grant_types_supported":[ "authorization_code", "implicit" ], "display_values_supported":[ "page", "touch", "wap" ], "claim_types_supported":[ "normal" ], "claims_supported":[ ], "ui_locales_supported":[ "en", "fr" ], "claims_parameter_supported":true, "request_parameter_supported":true, "request_uri_parameter_supported":true, "require_request_uri_registration":false } ``` #### Get JSON Web Key ##### URL `/api/oidc/jwks` ##### Method `GET` ##### Result ##### Success response Code 200 Content `jwks` content in JSON format. Example: ```javascript { "keys":[ { "use":"sig", "alg":"RS256", "x5c":[ "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwtpMAM4l1H995oqlqdMh\nuqNuffp4+4aUCwuFE9B5s9MJr63gyf8jW0oDr7Mb1Xb8y9iGkWfhouZqNJbMFry+\niBs+z2TtJF06vbHQZzajDsdux3XVfXv9v6dDIImyU24MsGNkpNt0GISaaiqv51NM\nZQX0miOXXWdkQvWTZFXhmsFCmJLE67oQFSar4hzfAaCulaMD+b3Mcsjlh0yvSq7g\n6swiIasEU3qNLKaJAZEzfywroVYr3BwM1IiVbQeKgIkyPS/85M4Y6Ss/T+OWi1Oe\nK49NdYBvFP+hNVEoeZzJz5K/nd6C35IX0t2bN5CVXchUFmaUMYk2iPdhXdsC720t\nBwIDAQAB\n-----END PUBLIC KEY-----" ], "kid":"h7uJEqXw_h4UXW_wCm3oBuboSGyuxf7XucGDKohPwxo", "kty":"RSA", "e":"AQAB", "n":"AMLaTADOJdR_feaKpanTIbqjbn36ePuGlAsLhRPQebPTCa-t4Mn_I1tKA6-zG9V2_MvYhpFn4aLmajSWzBa8vogbPs9k7SRdOr2x0Gc2ow7Hbsd11X17_b-nQyCJslNuDLBjZKTbdBiEmmoqr-dTTGUF9Jojl11nZEL1k2RV4ZrBQpiSxOu6EBUmq-Ic3wGgrpWjA_m9zHLI5YdMr0qu4OrMIiGrBFN6jSymiQGRM38sK6FWK9wcDNSIlW0HioCJMj0v_OTOGOkrP0_jlotTniuPTXWAbxT_oTVRKHmcyc-Sv53egt-SF9LdmzeQlV3IVBZmlDGJNoj3YV3bAu9tLQc" } ] } ``` ##### Error Response Code 403 JWK unavailable (if denied by parameter or if the algorithm isn't based on public/private key. ### Manage refresh tokens endpoints The following endpoints require a valid session cookie to identify the user. If the user has the scope `g_admin`, it's possible to impersonate a user with the optional query parameter `?username={username}`. #### List refresh tokens ##### URL `/api/oidc/token` ##### Method `GET` ##### URL Parameters Optional ``` `offset`: number, the offset to start the list, default 0 `limit`: number, the number of elements to return, default 100 `pattern`: text, a pattern to filter results, pattern will filter the properties `user_agent` or `issued_for` `sort`: text, the column to order the results, values available are `authorization_type`, `client_id`, `issued_at`, `last_seen`, `expires_at`, `issued_for`, `user_agent`, `enabled` and `rolling_expiration` `desc`: no value, is set, the column specified in the `sort` parameter will be ordered by descending order, otherwise ascending ``` ##### Result ##### Success response Code 200 Content ```javascript [{ "token_hash": text, refresh token hash signature "authorization_type": text, authorization type used to generate this refresh token, value can be "code" or "password" "client_id": text, client_id this refresh token was sent to "issued_at": number, date when this refresh token was issued, epoch time format "expires_at": number, date when this refresh token will expire, epoch time format "last_seen": number, last date when this refresh token was used to generate an access token, epoch time format "rolling_expiration": boolean, weather this refresh token is a rolling token, i.e. its expiration date will be postponed on each use to generate a new access token "issued_for": text, IP address of the device which requested this refresh token "user_agent": text, user-agent of the device which requested this refresh token "enabled": boolean, set to true if this refresh token is enabled, i.e. can be used to generate new access tokens, or not }] ``` ##### Error Response Code 403 Access denied #### Disable a refresh token by its signature ##### URL `/api/oidc/token/{token_hash}` ##### Method `DELETE` ##### URL Parameters Required ``` `token_hash`: text, hash value of the refresh token to disable, must be url-encoded ``` ##### Result ##### Success response Code 200 ##### Error Response Code 403 Access denied Code 404 Refresh token hash not found for this user ### Token introspection and revocation The endpoints `POST` `/introspect` and `POST` `/revoke` are implementations of the corresponding RFCs [Token introspection and revocation](https://tools.ietf.org/html/rfc7662) and [OAuth 2.0 Token Revocation](https://tools.ietf.org/html/rfc7009). Both of them rely on 2 distinct ways to authenticate: - HTTP Basic Auth corresponding to the client credentials whose client the token was submitted - Authorized Access Token that includes the required scopes for those endpoints Both authentication methods are non exclusive and the administrator may enable or disable each of them. #### Token introspection ##### URL `/api/oidc/introspect` ##### Method `POST` ##### Data Parameters Request body parameters must be encoded using the `application/x-www-form-urlencoded` format. ``` token: text, the token to introspect, required token_type_hint: text, optional, values available are 'access_token', 'refresh_token' or 'id_token' ``` #### Header parameters By default, the response format is `Application/JSON`, but the client can request a JWT response, either using the default JSON format as the JWT claim, or using the [JWT Response for OAuth Token Introspection Draft 10](https://tools.ietf.org/html/draft-ietf-oauth-jwt-introspection-response-10) format. The JWT will be signed with the server's private key, and can be encrypted using the client's secret or public key. ``` Accept: application/jwt - send the result in JSON Web Token (JWT) format Accept: Accept: application/token-introspection+jwt - send the result in JWT Response for OAuth Token Introspection format ``` ##### Result ##### Success response Code 200 Content Active token ```javascript { "sub": text, identifier for the user associated to the token, if any "aud": text, identifier for the client associated to the token, if any "username": text, username the token was issued for, if any "client_id": text, client the token was issued for, if any "iat": number, epoch time when the token was issued "nbf": number, epoch time when the token was issued "exp": number, epoch time when the token will be (or is supposed to be) expired "scope": text, scope list this token was emitted with, separated with spaces "token_type": text, type of the token, values may be 'access_token', 'refresh_token' or 'id_token' } ``` ##### Error Response Code 401 Access denied Code 400 Invalid parameters #### Token revocation ##### URL `/api/oidc/revoke` ##### Method `POST` ##### Data Parameters Request body parameters must be encoded using the `application/x-www-form-urlencoded` format. ``` token: text, the token to introspect, required token_type_hint: text, optional, values available are 'access_token', 'refresh_token' or 'id_token' ``` ##### Result ##### Success response Code 200 ##### Error Response Code 401 Access denied Code 400 Invalid parameters ### Client registration ##### URL `/api/oidc/register` ##### Method `POST` ##### Data Parameters Input data must be a JSON object. ```javascript { "client_name": text, name of the new client "redirect_uris": array of strings, each string must be a 'https://' or http://localhot' url, at least one value is mandatory "response_types": array of strings, values available are 'code', 'token', 'id_token', 'password', 'client_credentials', 'refresh_token' or 'delete_token', if empty the client will have the response types 'code' and 'refresh_token' "application_type": text, values available are 'web' or 'native' "contacts": array of strings "client_confidential": boolean, if false then no client_secret will be provided "logo_uri": string, url using the format 'https://' or 'http://' "client_uri": string, url using the format 'https://' or 'http://' "policy_uri": string, url using the format 'https://' or 'http://' "tos_uri": string, url using the format 'https://' or 'http://' "jwks_uri": string, url using the format 'https://' "jwks": JWKS object } ``` Parameters `jwks_uri` and `jwks` can't coexist at the same time. ##### Result ##### Success response Code 200 Content This is a non normative sample response. ```javascript { "client_name": "New Client", "client_id": "i4bmq8izuc8c65p8", "client_secret": "EpurvxmR712c1WPfMUtiXWxsA6ReFw9B", "client_id_issued_at": 1583695374, "client_secret_expires_at": 0, "redirect_uris": ["https://client.tld/callback"], "response_types": ["code", "token", "id_token", "password", "client_credentials", "refresh_token", "delete_token"], "application_type": "web", "contacts": ["contact@client.tld"], "logo_uri": "https://client.tld/logo.png", "client_uri": "https://client.tld/", "policy_uri": "https://client.tld/policy", "tos_uri": "https://client.tld/tos", "jwks": {"keys": [{"kty": "EC", "crv": "P-256", "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "use": "enc", "kid": "1"}]} } ``` ##### Error Response Code 401 Access denied Code 400 Invalid parameters ### Session Management #### Check Session iframe This is the iframe content to be used by the client to verify if the user status has changed. Due to Glewlwyd's design, the iframe only checks if the user associated to the id_token is still connected to Glewlwyd and the active user via its session cookie. No other check is performed. Therefore, the advantage of `check_session_iframe` in Glewlwyd is limited. ##### URL `/api/oidc/check_session_iframe` ##### Method `GET` ##### Result ##### Success response Code 200 Content An HTML page adapted for check_session_iframe, this iframe is intended to indicate to the client web page that the user status has changed. Check [OIDC Session Status Change Notification](https://openid.net/specs/openid-connect-session-1_0.html#ChangeNotification) for more details and an example on how to use it. ### End session ##### URL `/api/oidc/end_session` ##### Method `GET` ##### Data Parameters Optional ``` `post_logout_redirect_uri`: string, the uri to redirect the user after end registration is complete and successful. Must be previously registered by the client and will be used only if a valid `id_token_hint` is passed too. `id_token_hint`: string, the last id_token sent to the client for the user, must be a valid `id_token`. The `id_token` will be invalidated by Glewlwyd on end session. `state`: text, a client-defined string that will be sent back to the client via the `post_logout_redirect_uri` ``` ##### Result ##### Success response Code 302 Redirect the user to the login page with a end session prompt. If the user chooses to end the session, then the session will end and the user will be redirected to `post_logout_redirect_uri` if a valid one is given. ### CIBA endpoints The following 2 endpoints are the 2 additional endpoints required for CIBA requests. The `/ciba` endpoint is not explained here as it is defined in its [Core document](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html). #### Get CIBA requests for the connected user ##### URL `/api/oidc/check_session_iframe` ##### Method `GET` ##### Result ##### Success response Code 200 Content An HTML page adapted for check_session_iframe, this iframe is intended to indicate to the client web page that the user status has changed. Check [OIDC Session Status Change Notification](https://openid.net/specs/openid-connect-session-1_0.html#ChangeNotification) for more details and an example on how to use it. ### End session ##### URL `/api/oidc/ciba_user_list` ##### Method `GET` #### Security User identified with its cookie session. ##### Result ##### Success response Code 200 Content This is a non normative sample response. ```javascript { "client_name": "New Client", "client_id": "i4bmq8izuc8c65p8", "client_description": "Client description", "user_req_id": "abcd1234", "binding_message": "xyz9876", "scopes": ["openid", "scope1"] } ``` #### Check CIBA request for the connected user This endpoint behaves like the `/auth` endpoint. To validate the request, the session cookie used to authenticate must be for the user specified by the `login_hint`, with at least one of the specified scope granted and authorized. ##### URL `/api/oidc/ciba_user_check` ##### Method `GET` ##### URL Parameters `user_req_id`: identifier of the request for the user, mandatory ##### Result ##### Success response Code 302 If the user isn't fully authenticated yet, the endpoint redirects to `login.html` with the required scopes. If the user has no scope required, the endpoint redirects to `login.html` with an error message. If the is fully authenticated and at least one scope is granted to the client, the request is validated, and the endpoint redirects to `login.html` with a message "authentication complete". If the user cancels the request, the endpoint redirects to `login.html` with a message "authentication cancelled". ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/OTP.md��������������������������������������������������������������������������0000664�0000000�0000000�00000004050�14156463140�0015275�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Glewlwyd OTP Schema documentation [![License: CC BY 4.0](https://licensebuttons.net/l/by/4.0/80x15.png)](https://creativecommons.org/licenses/by/4.0/) ![scheme-otp](screenshots/scheme-otp.png) The OTP Schema implements authentication based on One-Time-Password using OATH standard defined in [HOTP](https://tools.ietf.org/html/rfc4226) and [TOTP](https://tools.ietf.org/html/rfc6238). ## Installation In the administration page, go to `Parameters/Authentication schemes` and add a new scheme by clicking on the `+` button. In the modal, enter a name and a display name (the name must be unique among all authentication scheme instances), and a scheme session expiration in seconds. Select the type `HOTP/TOTP` in the Type drop-down button. Below is the definition of all parameters. ### Name Name (identifier) of the scheme, must be unique among all the scheme instances, even of a different type. ### Display name Name of the instance displayed to the user. ### Expiration (seconds) Number of seconds to expire a valid session. ### Max use per session (0: unlimited) Maximum number of times a valid authentication with this scheme is possible. This is an additional parameter used to enforce the security of the session and forbid to reuse this session for other authentications. ### Allow users to register If this option is unchecked, only administrator can register this scheme for every user via the administration page. ### Issuer Address of the issuer of the OTP settings, i.e. the address of the webservice hosting Glewlwyd. ### Secret minimum size Size of the secret shared between the user and the server to authenticate the user. Minimum 16 bytes. ### Code length Length of the code that must be sent by the user, must be between 6 and 10, 6 or 8 is recommended. ### HOTP Allow users to register an HOTP code. ### HOTP window Window validity of the HOTP code. ### TOTP Allow users to register an TOTP code. ### TOTP window Window validity of the TOTP code in seconds. ### Start offset Start offset of the TOTP code related to Unix EPOCH time. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/README.md�����������������������������������������������������������������������0000664�0000000�0000000�00000003047�14156463140�0015575�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Glewlwyd documentation [![License: CC BY 4.0](https://licensebuttons.net/l/by/4.0/80x15.png)](https://creativecommons.org/licenses/by/4.0/) This folder contains full documentation for Glewlwyd and additional resources. ## Documentation ### General - [Install Glewlwyd](INSTALL.md) - [Getting started](GETTING_STARTED.md) - [Scopes](SCOPE.md) - [User documentation](USER.md) ### Modules - [Install User Backend Database](USER_DATABASE.md) - [Install User Backend LDAP](USER_LDAP.md) - [Install User Backend HTTP](USER_HTTP.md) - [Install Client Backend Database](CLIENT_DATABASE.md) - [Install Client Backend LDAP](CLIENT_LDAP.md) - [Install Authentication Scheme HOTP/TOTP](OTP.md) - [Install Authentication Scheme WebAuthn](WEBAUTHN.md) - [Install Authentication Scheme E-mail code](EMAIL.md) - [Install Authentication Scheme TLS certificate](CERTIFICATE.md) - [Install Authentication Scheme HTTP Basic Authentication](HTTP.md) - [Install Authentication Scheme OAuth2/OIDC client](OAUTH2_SCHEME.md) ### Plugins - [Install Plugin OAuth2](OAUTH2.md) - [Install Plugin OpenID Connect](OIDC.md) - [Install Plugin Register new user](REGISTER.md) ### Screenshots - [Screenshots available](screenshots/) ### Database init - [Database folder](database/) ### Development - [How to write user backend modules](../src/user/) - [How to write client backend modules](../src/client/) - [How to write authentication schemes modules](../src/scheme/) - [How to write plugins](../src/plugin/) ### Additional Resources - [Token validation for resource services](resources/) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/REGISTER.md���������������������������������������������������������������������0000664�0000000�0000000�00000063102�14156463140�0016062�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Glewlwyd register new user plugin documentation [![License: CC BY 4.0](https://licensebuttons.net/l/by/4.0/80x15.png)](https://creativecommons.org/licenses/by/4.0/) This plugin has the following functionalities: - Allow to register new users in your Glewlwyd instance - Allow a user to update its e-mail address with a verification phase - Reset a user credentials (lost password, lost scheme properties) ### User registration You have 3 different modes of user registration: - The user can register without e-mail confirmation, it will need to use an available username - The user must confirm an e-mail address to register, but can choose any available username - The user must confirm an e-mail address to register, its username will be its e-mail address used for registration During the registration process, the new user can register schemes and/or set a password. When you setup a registration instance, you need to set at least one authentication scheme to be mandatory for the user registration process. You also need to set at least one scope to the new users, `g_profile` is recommended for the users to be able to connect to their profile page, you can also add any other scheme you may find relevant for your self-registered users. ### Update e-mail With this option on, the user can update its e-mail address in its profile page. ### Reset credentials With this option on, a user that has lost its password or its authentication scheme properties like OTP shared secret or webauthn device can reset its credentials using either a link sent to the user's e-mail or a recovery code previously generated. - [Installation](#installation) - [Registration URL](#registration-url) - [Registration process for new users](#registration-process-for-new-users) - [Update e-mail process](#update-e-mail-process) - [Reset credentials process](#reset-credentials-process) - [Register endpoints specifications](#register-endpoints-specifications) ## Installation ![plugin-register](screenshots/plugin-register.png) In the administration page, go to `Parameters/Plugins` and add a new plugin by clicking on the `+` button. In the modal, enter a name and a display name (the name must be unique among all user backend instances). Select the type `Register new user plugin` in the Type drop-down button. Below is the definition of all parameters. ### Name Name (identifier) of the plugin instance, must be unique among all the plugin instances, even of a different type. ### Display name Name of the instance displayed to the user. ## Register account #### Register account enabled Check this if you want to enable user registration #### Session identifier Identifier for the session cookie used during the registration process #### Session duration in seconds Maximum duration of the registration session in seconds, default is 3600 (1 hour). If the new user didn't complete its registration before this duration, the user won't be available. #### Password Can the user set its password during the registration process? Values available are: - `Mandatory` (default), the new user must set a password to complete the registration - `Yes`, the new user can set a password, but it's not mandatory to complete the registration - `No`, the new user can't set a password during the registration process #### Scopes to add The list of scopes that will be added to all new users that will use this registration process. #### Add a scheme Add an scheme instance that will be available for registration during the registration process. ##### Mandatory A mandatory scheme means that the new user must register this scheme during the registration process to complete the registration. #### Verify e-mail Check this if you want the new users to verify their e-mail address during the registration process. #### Username is e-mail Check this if you want the new users to have their e-mail used as their username. #### Verification code length Length of the code that will be sent to the user's e-mail address. #### Verification code duration (seconds) Maximum lifetime of the code in seconds, default is 600 (10 minutes). If the new user didn't verify its e-mail address before this duration, the user will need to send another code. #### E-mail sender address Address used as sender in the e-mails, required. #### Content-Type Content-type for the e-mail content, default is `text/plain; charset=utf-8` #### Lang Drop-down value to select, add or remove lang templates for the e-mails. #### Default lang Checkbox to specify what lang is the default language. #### E-mail subject Subject used on the e-mails for the current lang, required. #### E-mail body The pattern for the body on the e-mails for the current lang, You must use at least once the string `{CODE}` in the pattern to be replaced by the code. The pattern {TOKEN} will be replaced by the token used to build the URL in the e-mail to get direct access to the validation step without entering the code. Example, by using the following e-mail pattern: ``` The code is {CODE} Or click on the following link: https://hunbaut.babelouest.org/glewlwyd2app/?registration=regis&token={TOKEN} ``` Users will receive the following message: ``` The code is: 123456 Or click on the following link: https://hunbaut.babelouest.org/glewlwyd2app/?registration=regis&token=abcxyz123[...] ``` ### Update e-mail address #### Token duration (seconds) Maximum lifetime of the link send to the new e-mail in seconds, default is 600 (10 minutes). If the new user didn't verify its e-mail address before this duration, the user will need to send another token. #### E-mail sender address Address used as sender in the e-mails, required. #### Content-Type Content-type for the e-mail content, default is `text/plain; charset=utf-8` #### Lang Drop-down value to select, add or remove lang templates for the e-mails. #### Default lang Checkbox to specify what lang is the default language. #### E-mail subject Subject used on the e-mails for the current lang, required. #### E-mail body The pattern for the body on the e-mails for the current lang. The pattern {TOKEN} will be replaced by the token used to build the URL in the e-mail to validate the new e-mail address. Example, by using the following e-mail pattern: ``` Click on the following link: https://hunbaut.babelouest.org/glewlwyd2app/?registration=regis&token={TOKEN} ``` Users will receive the following message: ``` Click on the following link: https://hunbaut.babelouest.org/glewlwyd2app/?registration=regis&token=abcxyz123[...] ``` ### Reset credentials #### Session identifier Identifier for the session cookie used during the reset credentials process #### Session duration in seconds Maximum duration of the reset credentials session in seconds, default is 3600 (1 hour). If the new user didn't complete its reset credentials before this duration, the process must be restarted. #### Use e-mail to reset credentials Check this if you want to allow the user to send an e-mail to its address to receive a link to access the reset credentials page for its profile. #### Token duration (seconds) Maximum lifetime of the link send to the new e-mail in seconds, default is 600 (10 minutes). If the new user didn't verify its e-mail address before this duration, the user will need to send another token. #### E-mail sender address Address used as sender in the e-mails, required. #### Content-Type Content-type for the e-mail content, default is `text/plain; charset=utf-8` #### Lang Drop-down value to select, add or remove lang templates for the e-mails. #### Default lang Checkbox to specify what lang is the default language. #### E-mail subject Subject used on the e-mails for the current lang, required. #### E-mail body The pattern for the body on the e-mails for the current lang. The pattern {TOKEN} will be replaced by the token used to build the URL in the e-mail to send the reset credentials link. Example, by using the following e-mail pattern: ``` Click on the following link: https://hunbaut.babelouest.org/glewlwyd2app/?registration=regis&token={TOKEN} ``` Users will receive the following message: ``` Click on the following link: https://hunbaut.babelouest.org/glewlwyd2app/?registration=regis&token=abcxyz123[...] ``` #### Use recovery code to reset credentials Check this if you want to allow the user to generate secret codes and store hash of them in order to start their reset credentials process. #### Number of recovery codes available Maximum number of recovery codes available. ### SMTP Parameters #### SMTP Server Address of the SMTP server that will relay the messages to the users, mandatory. #### Port SMTP (0: System default) TCP port the SMTP server is listening to. Must be between 0 and 65535. If 0 is set, Glewlwyd will use the system default port for SMTP, usually 25 or 587, mandatory. #### Use a TLS connection Check this option if the SMTP server requires TLS to connect. #### Check server certificate Check this option if you want Glewlwyd to check the SMTP server certificate before relaying the e-mail. This is highly recommended if TLS connection is checked, useless otherwise. #### SMTP username (if required) username used to authenticate to the SMTP server if required by the SMTP server, optional. #### SMTP password (if required) password used to authenticate to the SMTP server if required by the SMTP server, optional. ## Registration process ### Registration URL The URL to access to the registration page has the following format: ``` https://<your_glewlwyd_url>/profile.html?register=<your_registration_instance_name> ``` Example, if your registration [name](#name) is `registration`: [http://localhost:4593/profile.html?register=registration](http://localhost:4593/profile.html?register=registration) You can directly use this link in your network. The parameter `name` corresponds to the register instance name, the parameter `message` will be replaced by an internationalization message in the `webapp/locales/*/translation.json` files. The `register` parameter is a JSON array containing one or multiple JSON objects, each one with a property `name` and a property `message` in it. ### Add a link in the login page You can add a link to the registration page from the login page. You need to add a `register` entry in the `webapp/config.json` page. The entry has the following format: ```json "register": [ { "name": "register", "message": "login.register-link", "reset-credentials-message": "login.reset-credentials-link" } ] ``` ### Add a link at the end of the registration process When the registration process is complete, the user may have different link choices. If the user has clicked on the button `Register new user` on the login page, a button `Back to login page` will be available. If you want to add one or more links on this page to your applications, you must add an entry in the "register-complete" array. The entry has the following format: ```json "register-complete": [ { "name": "register", "complete-link": "https://www.example.com/login/", "complete-link-label": "profile.register-complete-link" } ] ``` - The `name` entry must correspond to your register plugin name. - The `complete-link` entry must correspond to your landing page. - The `complete-link-label` is a locales entry in the `webapp/locales/*/translation.json` files. And finally, if no button `Back to login page` or `register-complete` is available, a button `Back to profile page` will be available. ## Registration process for new users When a new user will register to your Glewlwyd service, depending on the configuration, the 3 following screens will appear. ### No e-mail verification `Verify e-mail` is unchecked in the admin page of the plugin. The new user doesn't have to verify its e-mail address, it must only choose a valid, unused username. ![register-no-verification](screenshots/register-no-verification.png) Note: by using this method, the user can't enter an e-mail address, so the e-mail scheme won't be available for this user. ### E-mail verification and valid username `Verify e-mail` is checked and `Username is e-mail` is unchecked in the admin page of the plugin. The new user must enter a valid username, but also an e-mail address. ![register-verification-username](screenshots/register-verification-username.png) ### Use e-mail as username `Verify e-mail` is checked and `Username is e-mail` is checked in the admin page of the plugin. The new must verify its e-mail address to register, the e-mail will be used as the username, so it must be unused in the users list as username. ![register-verification-no-username](screenshots/register-verification-no-username.png) ### Enter personal data and register schemes if necessary When the first step is complete, the new user must register one or more authentication method, such as choosing a password, registering an OTP, a TLS certificate, or a WebAuthn device. A message below the screen will explain to the user the mandatory schemes the user must achieve to be able to complete its registration. ![register-enter-data](screenshots/register-enter-data.png) ### Cancel registration The new user is allowed to cancel its registration before its completion. The user's data and scheme registrations will be removed. ### Complete registration If all the mandatory steps are achieved, the new user can complete its registration. ![register-complete](screenshots/register-complete.png) Then it should be able to connect to Glewlwyd and the applications using Glewlwyd authentication. ## Update e-mail process If an existing user wants to update its e-mail address, it must validate the new address by clicking on a link send to the new address. **Note for registered users with their e-mail address as their username** This process will update the e-mail property only. If the user has registered through your Glewlwyd instance and the registration option `Username is e-mail` is set, the username will remain as the original e-mail address used to register. ### Change e-mail The change e-mail button is available in the profile page of the user. ![change-e-mail](screenshots/profile-update-email-button.png) ### Enter new e-mail address When the user clicks on the edit button right to the e-mail input in the profile page, an input modal opens up and asks the user to enter the new e-mail address. After clicking on the Ok button, an e-mail will be sent to the new address. In the e-mail, the user must click on the given link to validate its new e-mail address. ![change-e-mail](screenshots/profile-update-email-modal.png) ## Reset credentials process When a user has lost its credentials (password or authentication scheme) that forbids to log in Glewlwyd properly, it can start the reset credentials process to recover its account. To reset its credentials, the user can either receive a link via e-mail or use a previously generated recovery code. ### Start the reset credentials process from the login page In the login page, the user must click on the `Lost credentials?` button. ![reset-credentials-login](screenshots/reset-credentials-login.png) Then, the user must either enter its recovery code or send a link to its e-mail address. Depending on the reset credentials configuration, it can be only one method available to reset a user's credentials. ![reset-credentials-login](screenshots/reset-credentials-login.png) ### Reset recovery codes in the profile page The user can reset its recovery codes in the profile page in the `Password` tab. There, the user must expand the `Reset credentials - Initialize recovery code` accordion and click on the `Reset recovery codes` button. Then a modal window will appear containing new recovery codes. Then, the user must save this new recovery codes in a safe place to be able to recover its account. ![reset-credentials-reset-recovery-codes](screenshots/reset-credentials-reset-recovery-codes.png) Note that when a recovery code has been used to reset the user credentials, it will be disabled to avoid re-using a code more than once. At any time, the user can ask for a new set of recovery codes. ### Reset credentials page Then, when the user has accessed its reset credentials page, it will be able to enter a new password and update or recover its scheme configuration if necessary. ![reset-credentials-page](screenshots/reset-credentials-page.png) ## Register endpoints specifications This documentation is intended to describe all the plugin endpoints behaviour. ### Prefix All URIs are based on the plugin name you will setup. In this document, all API endpoints will assume they use the prefix `/api/register`. ### Get plugin configuration #### URL `/api/register/config` #### Method `GET` #### Success response Code 200 ```javascript { registration: { set-password: string, values possible are 'no', 'yes' or 'always' schemes: [ // Array of schemes available for registration { module: string, module type name: string, module name register: string, values possible are 'yes' or 'always' display_name: string, display name for the module } ], verify-email: boolean email-is-username:boolean }, update-email: boolean reset-credentials: { email: boolean code: boolean } } ``` ### Check username #### URL `/api/register/username` #### Method `POST` #### Body Parameters ```javascript { username: string, mandatory } ``` #### Success response Code 200 The username is available Code 400 Username unavailable ### Register a new user without e-mail validation #### URL `/api/register/register` #### Method `POST` #### Body Parameters ```javascript { username: string, mandatory } ``` #### Success response Code 200 The registration process has started. A session cookie has been sent to the browser Code 400 Username invalid or unavailable Code 403 The new user must verify its e-mail address. ### Send e-mail verification code #### URL `/api/register/verify` #### Method `PUT` #### Body Parameters ```javascript { username: string, mandatory if the username is different from the e-mail in the plugin configuration email: string, mandatory } ``` #### Success response Code 200 The e-mail has been sent Code 400 Error input parameters format Code 403 The new user can't verify its e-mail address. ### Verify e-mail address #### URL `/api/register/verify` #### Method `POST` #### Body Parameters ```javascript { username: string, mandatory if the username is different from the e-mail in the plugin configuration and if token is missing or empty email: string, mandatory if token is missing or empty code: string, code verification, mandatory if token is missing or empty token: string, token verification } ``` #### Success response Code 200 The e-mail is verified. The registration process has started. A session cookie has been sent to the browser Code 400 Error input parameters Code 403 The new user can't verify its e-mail address. ### Get current profile data #### URL `/api/register/profile` #### Method `GET` #### Success response Code 200 ```javascript { username: string, the username used to register name: string or null email: string or null password_set: boolean } ``` Code 401 Invalid registration session ### Update password #### URL `/api/register/profile/password` #### Method `POST` #### Body Parameters ```javascript { password: string, mandatory } ``` #### Success response Code 200 Password updated Code 400 Error input parameters Code 401 Session invalid Code 403 User is not allowed to change its password ### Update user full name #### URL `/api/register/profile` #### Method `PUT` #### Body Parameters ```javascript { name: string or null, mandatory } ``` #### Success response Code 200 Name updated Code 400 Error input parameters Code 401 Session invalid ### Can the user use the specified authentication scheme #### URL `/api/register/profile/scheme/register/canuse` #### Method `PUT` #### Body Parameters ```javascript { scheme_name: name of the scheme to check, mandatory username: string, mandatory } ``` #### Success response Code 200 Scheme is registered for this user Code 400 Error input parameters Code 401 Session invalid Code 402 Scheme is available but not registered for this user Code 403 Scheme is unavailable for this user ### Get scheme registration #### URL `/api/register/profile/scheme/register` #### Method `PUT` #### Body Parameters ```javascript { scheme_name: name of the scheme to check, mandatory username: string, mandatory } ``` #### Success response Code 200 Get the scheme registration data. The response depends on the scheme. See [Authentication Scheme APIs](API.md#authentication-scheme-apis) Code 400 Error input parameters Code 401 Session invalid ### Update scheme registration #### URL `/api/register/profile/scheme/register` #### Method `POST` #### Body Parameters ```javascript { scheme_name: name of the scheme to check, mandatory username: string, mandatory value: object, mandatory } ``` #### Success response Code 200 Scheme registration updated. Depending on the scheme and the command, the response may also contain JSON data. Code 400 Error input parameters Code 401 Session invalid ### Cancel current registration #### URL `/api/register/profile` #### Method `DELETE` #### Success response Code 200 Registration has been canceled Code 401 Session invalid ### Complete registration #### URL `/api/register/profile/complete` #### Method `POST` #### Success response Code 200 User has been successfully created Code 400 Some authentication schemes must be registered to complete the registration Code 401 Session invalid ### Trigger update e-mail #### URL `/api/register/update-email` #### Method `POST` #### Security The user must use a valid profile session cookie #### Body Parameters ```javascript { email: new e-mail to verify, mandatory } ``` #### Success response Code 200 Link sent to the new e-mail address. Code 400 Error input parameters Code 401 Session invalid ### Verify updated e-mail #### URL `/api/register/update-email/:token` #### Method `PUT` #### URL Parameters ``` token: token used to verify the new e-mail, mandatory ``` #### Success response Code 200 User profile updated with the new e-mail address. Code 403 Token invalid ### Get current profile data (reset credentials) #### URL `/api/register/reset-credentials/profile` #### Method `GET` #### Success response Code 200 ```javascript { username: string, the username used to register scheme: [ { module: string, type of scheme name: string, identifier of the scheme } ] } ``` Code 401 Session invalid ### Update password (reset credentials) #### URL `/api/register/reset-credentials/profile/password` #### Method `POST` #### Body Parameters ```javascript { password: string, mandatory } ``` #### Success response Code 200 Password updated Code 400 Error input parameters Code 401 Session invalid Code 403 User is not allowed to change its password ### Can the user use the specified authentication scheme (reset credentials) #### URL `/api/register/reset-credentials/profile/scheme/register/canuse` #### Method `PUT` #### Body Parameters ```javascript { scheme_name: name of the scheme to check, mandatory username: string, mandatory } ``` #### Success response Code 200 Scheme is registered for this user Code 400 Error input parameters Code 401 Session invalid Code 402 Scheme is available but not registered for this user Code 403 Scheme is unavailable for this user ### Get scheme (reset credentials) #### URL `/api/register/reset-credentials/profile/scheme/register` #### Method `PUT` #### Body Parameters ```javascript { scheme_name: name of the scheme to check, mandatory username: string, mandatory } ``` #### Success response Code 200 Get the scheme data. The response depends on the scheme. See [Authentication Scheme APIs](API.md#authentication-scheme-apis) Code 400 Error input parameters Code 401 Session invalid ### Update scheme (reset credentials) #### URL `/api/register/reset-credentials/profile/scheme/register` #### Method `POST` #### Body Parameters ```javascript { scheme_name: name of the scheme to check, mandatory username: string, mandatory data: object, mandatory } ``` #### Success response Code 200 Scheme registration updated. Depending on the scheme and the command, the response may also contain JSON data. Code 400 Error input parameters Code 401 Session invalid ### Trigger reset credentials via e-mail #### URL `/api/register/reset-credentials-email` #### Method `POST` #### Body Parameters ```javascript { username: username to reset credentials } ``` #### Success response Code 200 Link sent to the corresponding e-mail address. Code 400 Error input parameters Code 401 Session invalid ### Verify reset credentials e-mail #### URL `/api/register/reset-credentials-email/:token` #### Method `PUT` #### URL Parameters ``` token: token used to verify the reset credentials, mandatory ``` #### Success response Code 200 A cookie session for the reset credentials process Code 403 Token invalid ### Reset recovery codes #### URL `/api/register/reset-credentials-code` #### Method `PUT` #### Security The user must use a valid profile session cookie #### Success response Code 200 ```javascript [ "code1", "code2", ... ] ``` Code 401 Session invalid ### Start a reset credentials session by verifying a recovery code #### URL `/api/register/reset-credentials-code` #### Method `POST` #### Body Parameters ```javascript { username: username to reset credentials code: valid recovery code } ``` #### Success response Code 200 A cookie session for the reset credentials process Code 403 Code invalid ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/SCOPE.md������������������������������������������������������������������������0000664�0000000�0000000�00000003750�14156463140�0015512�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Glewlwyd Scopes Management [![License: CC BY 4.0](https://licensebuttons.net/l/by/4.0/80x15.png)](https://creativecommons.org/licenses/by/4.0/) ![scope-add](screenshots/scope-add.png) A scope in Glewlwyd is an access level that the user, if it has the scope available, can or not grant access to clients. From the administrator point-of-view, the scope can require more or less authentication factors. The first authentication factor is the password, which is required by default when you create a new scope. You can add n-factor authentication to this scope and setup password session max age to enhance security access. Here is the documentation of the options available when you create or edit a scope: ## Name This option is mandatory, it's the scope unique name and identifier ## Display Name This option is optional, it's the scope long name that will be displayed to users. ## Description This option is optional, it's a short description of the scope. ## Password checkbox Force authentication with the password to allow this scope for the user on the current session. ## Password session duration (0: unlimited) If password is required, set a maximum age for the password in the current session. Setting this option to 0 means unlimited session. ## Additional authentication schemes In this section, you can add one or more authentication schemes to the scope. You can gather authentication schemes in groups to allow multiple authentication factor, you can have multiple groups to force more than one additional authentication factor. You can specify the number of schemes required by group to authenticate, this option is useful if you want to enforce security for a scope but letting users the choice of schemes to use. e.g., a scope can have one group with 4 schemes and 2 schemes required. The authentication group model can be represented as the following schema: Scope 1: password `AND` (mail `OR` webauthn) `AND` (TOTP `OR` certificate) Scope 2: (mail `OR` certificate `OR` webauthn) ������������������������glewlwyd-2.6.1/docs/USER.md�������������������������������������������������������������������������0000664�0000000�0000000�00000022151�14156463140�0015413�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Glewlwyd User Documentation [![License: CC BY 4.0](https://licensebuttons.net/l/by/4.0/80x15.png)](https://creativecommons.org/licenses/by/4.0/) This documentation is intended to help Glewlwyd Users to manage their profile and register authentication schemes, and understand the login workflow. - [Profile page](#profile-page) - [Profile Personal data](#profile-personal-data) - [Sessions and tokens](#sessions-and-tokens) - [Password](#password) - [Schemes](#schemes) - [WebAuthn](#webauthn) - [OTP](#otp) - [TLS Certificates](#tls-certificates) - [External OAuth2/OIDC Login](#external-oauth2oidc-login) - [Login to Glewlwyd](#login-to-glewlwyd) - [Grant access](#grant-access) - [Multiple sessions](#multiple-sessions) - [Authentication complete](#authentication-complete) - [Delete account](#delete-account) ## Profile page ![profile-data](screenshots/profile-data.png) To access its profile page, the user must go to the profile URL, typically `https://glewlwyd_server_url/profile.html`. The user may have to re-login, because for security reasons, the default session timeout is set to 10 minutes. ## Profile Personal data In the first tab of the profile page, the user is allowed to view and update some of the data related to its account. Some data may be updated such as `Name`, some data can't be updated by the user itself, such as `e-mail` or `scope`. Depending the server configuration, the administrator can add more personal data that can be viewed and possibly updated by the user. ## Sessions and tokens ![profile-session](screenshots/profile-session.png) This tab allows the user to manually disable active sessions or refresh tokens provided by Glewlwyd. ## Password ### Single password configuration ![profile-password](screenshots/profile-password.png) If you click on the `Password` tab, you'll be able to change your user password. ### Multiple password configuration ![profile-multiple-password](screenshots/profile-multiple-password.png) If you click on the `Password` tab, you'll be able to change your user passwords. You can have multiple passwords if you need to, or none if your Glewlwyd service allows passwordless authentication. Be careful that every password will allow the same access level to your Glewlwyd service, so all of them must be strong enough to avoid being guessed. ## Schemes In the profile page, the user will be able to manage the schemes available for its account. The following paragraphs will describe how to register the schemes available. By design, the schemes `Retype password` and `E-mail code` can't be registered or configured in the profile page. ### WebAuthn ![profile-webauthn](screenshots/profile-webauthn.png) WebAuthn is an authentication scheme that permits the user to authenticate via a dedicated device without having to retain a password. Currently, the devices available for this scheme are the following: - Yubikeys - Android devices (phone or tablet) if the system version is Nougat (7.0) or higher - Apple devices (iPhone or iPad) if the system is iOS 14 or higher You'll need a browser compatible with WebAuthn API. Recent versions of Firefox, Chrome, Edge and Opera work fine, check [Can I use](https://caniuse.com/#search=webauthn) for more details. #### Registration On the `WebAuthn` tab, click on the button `New Component`. There, follow your browser instructions. Eventually, you should see the new registered component in the component list. You can verify it's working properly by clicking on the `Test` button, there, follow your browser's instructions. You should see a popup message saying `Authentication succeeded`. The component name can be changed in this page, you can also disable/enable, and delete a registered component. ### OTP ![profile-otp](screenshots/profile-otp.png) OTP is an authentication scheme using the OATH standard, it allows the user to authenticate via one-time passwords. Glewlwyd OTP scheme allows HOTP (increment based one-time passwords) and TOTP (time-based one-time passwords). You'll need to synchronize your OTP parameters with another device so you can authenticate via this scheme. You have several mobile apps available, as well as physical devices available to achieve this. #### Registration If you want to register a new OTP account, on the `OTP` tab, select the type: `HOTP` (increment based) or `TOTP` (time-based). Usually, TOTP is preferred over HOTP because it's easier to use, with a similar security level. If you want to disable this scheme for your account, select type `None`. You need to enter a shared secret. This must be a random number, large enough to make it difficult to guess, encoded in Base32 format. If your administrator have provided you a shared secret, type it, or copy/paste it in the text-box. If you don't have one, you can generate a random secret by clicking on the `Generate` button right to the text-box. ##### HOTP parameter Moving factor: This option is the starting offset of the HOTP configuration. Usually the default value is 0. ##### TOTP parameter Step size (seconds): This option is the time window specifying the duration of a one-time password. This duration must be long enough to give time to the user to read and type it, but short enough to give an attacker enough time to reuse an already used password. The typical duration is 30 seconds. ### TLS Certificates ![profile-certificate](screenshots/profile-certificate.png) TLS Certificates is an authentication scheme allowing the user to authenticate to Glewlwyd via the TLS certificate he or she is using to connect to Glewlwyd web service in his or her browser. #### Registration You need a certificate emitted by the certificate of authority that the Glewlwyd service is using. You can either add the certificate file manually by clicking on the `browse` button, select the X509n cert file in PEM format, then click on the `Upload` button. Also, if you're currently browsing using your certificate, you can click on the button `Add current certificate`. #### Test certificate If you already have registered at least one certificate and want to test the authentication with the current certificate you're using while browsing, you can click on the `Test current certificate` button. If the authentication is successful, the certificate used to authenticate will be highlighted in the list. ### External OAuth2/OIDC Login ![profile-oauth2](screenshots/profile-oauth2.png) ![login-oauth2](screenshots/login-oauth2.png) This scheme allow users to connect to Glewlwyd via an account on a trusted external OAuth2/OIDC service. External OAuth2/OIDC services may include mainstream services such as Google, Facebook, GitHub, etc. #### Registration You need a valid login in one of the proposed external services. In the Profile page, click on the `+` button of one of the provider, then proceed to the login workflow in the provider service. You may be asked to accept Glewlwyd service to be authorized to access your personal data. You must accept this authorization to allow Glewlwyd to identify your login. Glewlwyd will never have access to secret data such as passwords in this process, and will only save the minimal data (i.e. an identifier) to be able to identify your connection during further login phases. ## Login to Glewlwyd ![login-new-user](screenshots/login-new-user.png) When you're asked to login to an application that uses Glewlwyd as authentication service, you may need to enter credentials, depending on the access required and the server configuration. During a login process, you may have to use multiple authentication schemes, and choose between them. When this happens, you have a `Scheme` drop-down button available on the bottom-left of the login window. There, you can choose between the schemes available. ### Grant access ![login-grant](screenshots/login-grant.png) When you first login to an application via Glewlwyd, you'll be asked to grant access to the client. The client needs your permission to have access to scopes you're allowed to use. You need to grant at least one scope to the client, otherwise the client won't have access. At any time, you can go back to the login page and change the access granted to this client by clicking on the `Grant` menu available in the `Manage` drop-down button of the login page. ### Multiple sessions ![login-multiple-session](screenshots/login-multiple-session.png) If you have multiple logins available on the Glewlwyd server, you can switch from one to another without having to logout every time. The login available are specific to a browser and a session. Use the drop-down button `Change user` on the bottom-left of the login page. ### Authentication complete ![logged-in](screenshots/logged-in.png) If the authentication process is complete, you'll have a `Continue` button available. By clicking on this button, you'll be redirected to the client with credentials available to the client. ## Delete account If available, you can delete your own account on Glewlwyd. You need to be connected to the profile page. In the `Personal data` tab, you have a `Delete account` button on the bottom left of the screen. By clicking on this button, you will delete your account and all the schemes registration attached to your account. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/USER_DATABASE.md����������������������������������������������������������������0000664�0000000�0000000�00000010402�14156463140�0016633�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Glewlwyd User Module Database Backend documentation [![License: CC BY 4.0](https://licensebuttons.net/l/by/4.0/80x15.png)](https://creativecommons.org/licenses/by/4.0/) ![mod-user-database](screenshots/mod-user-database.png) The database backend uses a database to store information and passwords for users. The database can be the same one as the Glewlwyd database or another database of another supported type. ## Installation In the administration page, go to `Parameters/Users data sources` and add a new user module by clicking on the `+` button. In the modal, enter a name and a display name (the name must be unique among all user backend instances). Select the type `Database backend user module` in the Type drop-down button. Below is the definition of all parameters. ### Name Name (identifier) of the module instance, must be unique among all the user backend module instances, even of a different type. ### Display name Name of the instance displayed to the user. ### Read only Check this option if you want to use this backend as read-only. All user properties such as e-mail, name, password, scopes can't be modifier with Glewlwyd, even administrators. ### PKBDF2 iterations (SQLite3) Number of iterations for the password digest in SQlite3 databases only. Currently (2021), it's recommended to iterate at least 100 000 times. ### Multiple password Check this option if you allow users to manage multiple passwords. More information about multiple passwords use-cases are avaiable in the [Getting Started Dcumentation](GETTING_STARTED.md#multiple-password-authentication). ### Use the same connection as Glewlwyd server Uncheck this option if you want to use a different database that will store the users. The new database must have the structure already present. Use one of the following script to initialize the database: - MariaDB: [database.mariadb.sql](../src/user/database.mariadb.sql) - Postgre SQL: [database.postgre.sql](../src/user/database.postgre.sql) - SQLite 3: [database.sqlite3.sql](../src/user/database.sqlite3.sql) ### Database type This option is available if the option `Use the same connection as Glewlwyd server` is disabled. Select the database backend among the ones available. ### Path to the database (SQLite 3) This option is available if the database type selected is SQLite 3. Enter the path of the SQLite 3 database on the server. ### Host (MariaDB/MySQL) This option is available if the database type selected is MariaDB/MySQL. Hostname of the MariaDB/MySQL server. ### Username (MariaDB/MySQL) This option is available if the database type selected is MariaDB/MySQL. Username to connect to the MariaDB/MySQL server. ### Password (MariaDB/MySQL) This option is available if the database type selected is MariaDB/MySQL. Password to connect to the MariaDB/MySQL server. ### Database name (MariaDB/MySQL) This option is available if the database type selected is MariaDB/MySQL. Name of the database hosting the Glewlwyd user backend tables. ### TCP Connection port (0: system default) (MariaDB/MySQL) This option is available if the database type selected is MariaDB/MySQL. TCP Port used to connect to the MariaDB/MySQL database. Set 0 if you want to use default system port. ### Postgre SQL connection string (PostgreSQL) This option is available if the database type selected is PostgreSQL. SQL Connection string used to connect to the PostgreSQL database. ### Specific data format This section allows to specify new properties for the user. The properties may be available for schemes, plugins, in the admin page or in the profile page. #### Property Property name, ex: `phone`, `address`, `human`, etc. #### Multiple values If this option is checked, the property values will be available as an array of string values, otherwise a single string value. #### Read (admin) If this option is checked, plugins, schemes and administrators can have access to this property in read mode. #### Write (admin) If this option is checked, plugins, schemes and administrators can have access to this property in write mode. #### Read (profile) If this option is checked, the user can have access to this property in read mode in its profile API. #### Write (profile) If this option is checked, the user can have access to this property in write mode in its profile API. ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/USER_HTTP.md��������������������������������������������������������������������0000664�0000000�0000000�00000004231�14156463140�0016251�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Glewlwyd User Module HTTP Backend documentation [![License: CC BY 4.0](https://licensebuttons.net/l/by/4.0/80x15.png)](https://creativecommons.org/licenses/by/4.0/) ![mod-user-http](screenshots/mod-user-http.png) The http backend uses a HTTP service to authenticate users via Basic Auth. By design, this backend module instance is always read-only. Listing users will always return an empty list, getting a single user will always return a JSON object, even if the user doesn't exist in the backend. The HTTP request `GET /api/user/user1?backend=http` may return the following response: ```JSON { "username": "user1", "scope": ["g_profile","scope1"], "enabled": true } ``` The scopes returned are the one specified in the `Default scopes` property. The authentication method used is the Basic HTTP authentication. ## Installation In the administration page, go to `Parameters/Users data sources` and add a new user module by clicking on the `+` button. In the modal, enter a name and a display name (the name must be unique among all user backend instances). Select the type `LDAP backend user module` in the Type drop-down button. Below is the definition of all parameters. ### Name Name (identifier) of the module instance, must be unique among all the user backend module instances, even of a different type. ### Display name Name of the instance displayed to the user. ### Read only This option has no effect on this backend. ### Multiple password This option has no effect on this backend. ### URL URL of the HTTP service to connect to. ### Validate server certificate Check this option if the HTTP service uses TLS and if you want to validate the certificate. ### Default scopes Select all the scopes that will be available for a connected user. At least one scope is mandatory. ### Username format on HTTP server Fill this option if you want the users to enter their username only, without surrounding patterns. For example, if the login format on the HTTP server uses the format `\\domain\username`, then you can fill this option with `\\domain\{username}`. This option is optional, but if you fill it, the pattern `{username}` must be present in the format. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/USER_LDAP.md��������������������������������������������������������������������0000664�0000000�0000000�00000012471�14156463140�0016217�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Glewlwyd User Module LDAP Backend documentation [![License: CC BY 4.0](https://licensebuttons.net/l/by/4.0/80x15.png)](https://creativecommons.org/licenses/by/4.0/) ![mod-user-ldap](screenshots/mod-user-ldap.png) The database backend uses a LDAP service to store information and passwords for users. ## Installation In the administration page, go to `Parameters/Users data sources` and add a new user module by clicking on the `+` button. In the modal, enter a name and a display name (the name must be unique among all user backend instances). Select the type `LDAP backend user module` in the Type drop-down button. Below is the definition of all parameters. ### Name Name (identifier) of the module instance, must be unique among all the user backend module instances, even of a different type. ### Display name Name of the instance displayed to the user. ### Read only Check this option if you want to use this backend as read-only. All user properties such as e-mail, name, password, scopes can't be modifier with Glewlwyd, even administrators. ### Multiple password Check this option if you allow users to manage multiple passwords. More information about multiple passwords use-cases are avaiable in the [Getting Started Dcumentation](GETTING_STARTED.md#multiple-password-authentication). ### Connection URI URI to connect to the LDAP service, ex: ldaps://ldap.example.com/ ### Connection DN DN used to access the LDAP service. The DN must have write access if you want to use this backend in write mode. ### Connection password Password to use with the `Connection DN`. ### Search page size Page size to list users in this backend. This option must be lower than the maximum of results that the LDAP service can send. ### Search base Base DN to look for users. ### Search scope Search scope on the LDAP Base DN. Values available are `one`, `subtree`, `children`. ### Search filter Filter to apply when performing a search of users. ### Username property Username of the user. This property will be used to build the search filter on a user connection. You can specify multiple values by separating them with a comma `,`. On read mode, the first value will be used, on write mode, all values will be used. ### Name property Name of the user. You can specify multiple values by separating them with a comma `,`. On read mode, the first value will be used, on write mode, all values will be used. ### Scope property Scopes available for the user. The LDAP property must store multiple values. You can specify multiple values by separating them with a comma `,`. On read mode, the first value will be used, on write mode, all values will be used. ### E-mail property Property used to store the user e-mail value. You can specify multiple values by separating them with a comma `,`. On read mode, the first value will be used, on write mode, all values will be used. ### Password property. Property used to store the user password. This property is not used if the instance is in read-only mode. You can specify multiple values by separating them with a comma `,`. On read mode, the first value will be used, on write mode, all values will be used. ### Algorithm Algorithm used to hash the user password. This property is not used if the instance is in read-only mode. ### rdn property This property is mandatory to store the rdn property. This property is not used if the instance is in read-only mode. You can specify multiple values by separating them with a comma `,`. ### Object class property for a new user This value will contain all the object class values when Glewlwyd will create new users in the LDAP backend. Values must be separated with a comma `,`. ### Specific data format This section allows to specify new properties for the user. The properties may be available for schemes, plugins, in the admin page or in the profile page. #### Property Property name, ex: `phone`, `address`, `human`, etc. #### LDAP Property Corresponding LDAP property name. #### Multiple values If this option is checked, the property values will be available as an array of string values, otherwise a single string value. #### Convert If this option is set to `base64`, the property content will be converted to base64 for Glewlwyd. On the other hand, if this property is writable by Glewlwyd, the data must be in base64. #### Read (admin) If this option is checked, plugins, schemes and administrators can have access to this property in read mode. #### Write (admin) If this option is checked, plugins, schemes and administrators can have access to this property in write mode. #### Read (profile) If this option is checked, the user can have access to this property in read mode in its profile API. #### Write (profile) If this option is checked, the user can have access to this property in write mode in its profile API. ### Scope field property This section allows to specify a correspondence between a Glewlwyd scope and a value in the scope property. The main goal is to use an existing LDAP service whose users have property that can be related to scopes (group names, etc.). For example, the group name value `accounting` will correspond to the scope `mail`. #### LDAP value LDAP value that must match. #### Corresponding scope Name of the scope that will be returned. This value must be an existing scope name. #### Match How the LDAP value must match. �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/WEBAUTHN.md���������������������������������������������������������������������0000664�0000000�0000000�00000016262�14156463140�0016060�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Glewlwyd WebAuthn Schema Documentation [![License: CC BY 4.0](https://licensebuttons.net/l/by/4.0/80x15.png)](https://creativecommons.org/licenses/by/4.0/) ![scheme-webauthn](screenshots/scheme-webauthn.png) The WebAuthn Schema implements authentication based on the [WebAuthn API](https://w3c.github.io/webauthn/). This allows users to authenticate to Glewlwyd using physical devices: Android or Apple phones with fingerprint or face id, Yubikeys, etc. The user needs to register its device to Glewlwyd first, then when a webauthn authentication is required, the user plugs its device, and the authentication process is validated without the users need to enter a password. As for now, the following attestation formats are supported by Glewlwyd: - Android Safetynet Attestation (Android devices with fingerprint, face id or secret code, depending on the device) - Apple Attestation (Apple devices with fingerprint, face id, depending on the device) - FIDO U2F Attestation (ex: Yubikeys) - Packed ## Installation In the administration page, go to `Parameters/Authentication schemes` and add a new scheme by clicking on the `+` button. In the modal, enter a name and a display name (the name must be unique among all authentication scheme instances), and a scheme session expiration in seconds. Select the type `WebAuthn` in the Type drop-down button. The default settings makes the scheme usable as is. Below is the definition of all parameters. ### Name Name (identifier) of the scheme, must be unique among all the scheme instances, even of a different type. ### Display name Name of the instance displayed to the user. ### Expiration (seconds) Number of seconds to expire a valid session. ### Max use per session (0: unlimited) Maximum number of times a valid authentication with this scheme is possible. This is an additional parameter used to enforce the security of the session and forbid to reuse this session for other authentications. ### Allow users to register If this option is unchecked, only administrator can register this scheme for every user via the administration page. ### Force attestation without validation certificate (much lesser safe) Force all registration to Glewlwyd without a certificate (attestation set to 'none', therefore fmt returned is 'none'). Check this option if you don't want to manage FIDO2 devices chain of trust and want to accept all FIDO2 device a user wants to register. ### User must have a valid session to connect This options allows or forbid users to authenticate via webauthn if they already have a valid session. If you disable this option, you can use webauthn for a no-password authentication. ### Random seed used to mitigate intrusion This setting is required if the option `User must have a valid session to connect` is unchecked. It will be used to generate fake users and authentication ids to mitigate intrusion attacks. So attacker won't be able to make the distinction between a valid user and a fake one. A random string is generated by your browser to fill this option. You can change it as you like, but be sure to enter a long string (~32 characters or more) for a significant entropy. ### Challenge length Length of the challenge that will be sent to the webauthn device. Must be large to have enough entropy. Most webauthn devices require at least 32 bytes long, and recommend 64 or more. ### Maximum duration to register a component (seconds) Maximum duration in seconds between the first step and the last step of the registration process. ### Maximum duration to complete an authentication (seconds) Maximum duration in seconds between the first step and the last step of the authentication process. ### Relying party Value of the relying party that will be used and compared to during the registration and authentication process. It must correspond to the address of the web server hosting the front-end application. It must be an `https://` address unless you're using only under `localhost` (but why would you?). ### Supported webauthn formats Select the formats you want your WebAuthn scheme to support. You must select at least one format. Format TPM and Android Key are not supported yet. **Security warning**: To avoid man in the middle attacks, it's **highly recommended** to disable format `none`, because it's impossible to verify if a trusted device created the credentials without certificate validation. ### Signature algorithm Signature algorithms supported. Currently, only ECDSA signatures are supported. ### Certificate file path on the server These are the certificates provided by the security key manufacturers used to validate the full chain. **Security warning**: If you enter no certificate, then no chain trust will be checked when a FIDO2 or Packed device will register, which can lead to man in the middle attacks. Therefore it's **recommended** to specify the manufacturers you support. ### Android Safetynet integrity: Expected ctsProfileMatch value and Expected basicIntegrity value These options are related to the potential integrity of the android devices used for authentication. This allows or forbid users to use a rooted android device or modified device. More information is available in this [android developer](https://developer.android.com/training/safetynet/attestation#potential-integrity-verdicts) page. ### Server local path to the root certificate 'GlobalSign Root CA - R2' This is used during the registration of an Android device using webauthn. The device certificate will be validated with the 'GlobalSign Root CA certificate - R2'. It is highly recommended to [save](https://pki.goog/) this certificate in the server hosting Glewlwyd and fill this option. The reason why this certificate isn't hard-coded in Glewlwyd source code or isn't shipped with Glewlwyd package is because Google won't allow to redistribute the certificate in terms compatible with [Glewlwyd's license](../LICENSE). Also, the certificate may change and it should be possible to use a new one if required. Please note that if this option is not set, no certificate chain verification will be done during the registration process, the other verification of the registration process will be executed though, but one can forge a fake android safetynet registration if the official Google certificate isn't used. ### Server local path to the root certificate 'Apple WebAuthn Root CA' This is used during the registration of an Apple device using webauthn. The device certificate will be validated with the 'Apple WebAuthn Root CA'. It is highly recommended to [save](https://www.apple.com/certificateauthority/private/) this certificate in the server hosting Glewlwyd and fill this option. The reason why this certificate isn't hard-coded in Glewlwyd source code or isn't shipped with Glewlwyd package is because Apple won't allow to redistribute the certificate in terms compatible with [Glewlwyd's license](../LICENSE). Also, the certificate may change and it should be possible to use a new one if required. Please note that if this option is not set, no certificate chain verification will be done during the registration process, the other verification of the registration process will be executed though, but one can forge a fake apple registration if the official Apple certificate isn't used. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/database/�����������������������������������������������������������������������0000775�0000000�0000000�00000000000�14156463140�0016056�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/database/README.md��������������������������������������������������������������0000664�0000000�0000000�00000011122�14156463140�0017332�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Database init scripts The following scripts are available: ## Initialize Glewlwyd with the default settings and all tables for all schemes and modules The initialized database will contain the user `admin` using the password `password`. - [MariaDB/MySQL initialization](init.mariadb.sql) - [Postgre SQL initialization](init.postgre.sql) - [SQlite 3 initialization](init.sqlite3.sql) ## Upgrade Glewlwyd from 2.0.x to 2.1.x ### Install tables for Registration plugin - [MariaDB/MySQL upgrade](../../src/plugin/register.mariadb.sql) - [Postgre SQL upgrade](../../src/plugin/register.postgre.sql) - [SQlite 3 upgrade](../../src/plugin/register.sqlite3.sql) ## Upgrade Glewlwyd from 2.1.x to 2.2.x ### Upgrade core tables structure - [MariaDB/MySQL upgrade](upgrade-2.2-core.mariadb.sql) - [Postgre SQL upgrade](upgrade-2.2-core.postgre.sql) - [SQlite 3 upgrade](upgrade-2.2-core.sqlite3.sql) ## Upgrade Glewlwyd from 2.2.x to 2.3.x ### Upgrade core tables structure - [MariaDB/MySQL upgrade](upgrade-2.3-core.mariadb.sql) - [Postgre SQL upgrade](upgrade-2.3-core.postgre.sql) - [SQlite 3 upgrade](upgrade-2.3-core.sqlite3.sql) ## Upgrade Glewlwyd from 2.3.x to 2.4.x ### Upgrade core tables structure - [MariaDB/MySQL upgrade](upgrade-2.4-core.mariadb.sql) - [Postgre SQL upgrade](upgrade-2.4-core.postgre.sql) - [SQlite 3 upgrade](upgrade-2.4-core.sqlite3.sql) ## Upgrade Glewlwyd from 2.4.x to 2.5.x ### Upgrade core tables structure - [MariaDB/MySQL upgrade](upgrade-2.5-core.mariadb.sql) - [Postgre SQL upgrade](upgrade-2.5-core.postgre.sql) - [SQlite 3 upgrade](upgrade-2.5-core.sqlite3.sql) ## Upgrade Glewlwyd from 2.5.x to 2.6.x ### Upgrade core tables structure - [MariaDB/MySQL upgrade](upgrade-2.6-core.mariadb.sql) - [Postgre SQL upgrade](upgrade-2.6-core.postgre.sql) - [SQlite 3 upgrade](upgrade-2.6-core.sqlite3.sql) ### Install tables for OAuth2/OIDC scheme - [MariaDB/MySQL upgrade](../../src/scheme/oauth2.mariadb.sql) - [Postgre SQL upgrade](../../src/scheme/oauth2.postgre.sql) - [SQlite 3 upgrade](../../src/scheme/oauth2.sqlite3.sql) ## Initialize only Glewlwyd core tables with no data - [MariaDB/MySQL initialization](init-core.mariadb.sql) - [Postgre SQL initialization](init-core.postgre.sql) - [SQlite 3 initialization](init-core.sqlite3.sql) ## User backend database only - [MariaDB/MySQL initialization](../../src/user/database.mariadb.sql) - [Postgre SQL initialization](../../src/user/database.postgre.sql) - [SQlite 3 initialization](../../src/user/database.sqlite3.sql) ## Client backend database only - [MariaDB/MySQL initialization](../../src/client/database.mariadb.sql) - [Postgre SQL initialization](../../src/client/database.postgre.sql) - [SQlite 3 initialization](../../src/client/database.sqlite3.sql) ## E-mail code scheme only - [MariaDB/MySQL initialization](../../src/scheme/email.mariadb.sql) - [Postgre SQL initialization](../../src/scheme/email.postgre.sql) - [SQlite 3 initialization](../../src/scheme/email.sqlite3.sql) ## HOTP/TOTP scheme only - [MariaDB/MySQL initialization](../../src/scheme/otp.mariadb.sql) - [Postgre SQL initialization](../../src/scheme/otp.postgre.sql) - [SQlite 3 initialization](../../src/scheme/otp.sqlite3.sql) ## Webauthn scheme only - [MariaDB/MySQL initialization](../../src/scheme/webauthn.mariadb.sql) - [Postgre SQL initialization](../../src/scheme/webauthn.postgre.sql) - [SQlite 3 initialization](../../src/scheme/webauthn.sqlite3.sql) ## TLS Certificate scheme only - [MariaDB/MySQL initialization](../../src/scheme/certificate.mariadb.sql) - [Postgre SQL initialization](../../src/scheme/certificate.postgre.sql) - [SQlite 3 initialization](../../src/scheme/certificate.sqlite3.sql) ## OAuth2/OIDC scheme only - [MariaDB/MySQL initialization](../../src/scheme/oauth2.mariadb.sql) - [Postgre SQL initialization](../../src/scheme/oauth2.postgre.sql) - [SQlite 3 initialization](../../src/scheme/oauth2.sqlite3.sql) ## OAuth2 plugin only - [MariaDB/MySQL initialization](../../src/plugin/protocol_oauth2.mariadb.sql) - [Postgre SQL initialization](../../src/plugin/protocol_oauth2.postgre.sql) - [SQlite 3 initialization](../../src/plugin/protocol_oauth2.sqlite3.sql) ## OpenID Connect plugin only - [MariaDB/MySQL initialization](../../src/plugin/protocol_oidc.mariadb.sql) - [Postgre SQL initialization](../../src/plugin/protocol_oidc.postgre.sql) - [SQlite 3 initialization](../../src/plugin/protocol_oidc.sqlite3.sql) ## Registration plugin only - [MariaDB/MySQL initialization](../../src/plugin/register.mariadb.sql) - [Postgre SQL initialization](../../src/plugin/register.postgre.sql) - [SQlite 3 initialization](../../src/plugin/register.sqlite3.sql) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/database/init-core.mariadb.sql��������������������������������������������������0000664�0000000�0000000�00000013506�14156463140�0022073�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-- ----------------------------------------------------- -- -- Mariadb/Mysql Database -- -- Initialize Glewlwyd Database for the backend server -- -- The administration client app -- -- Copyright 2020 Nicolas Mora <mail@babelouest.org> -- -- License: MIT -- -- ----------------------------------------------------- -- DROP TABLE IF EXISTS g_api_key; DROP TABLE IF EXISTS g_client_user_scope; DROP TABLE IF EXISTS g_scope_group_auth_scheme_module_instance; DROP TABLE IF EXISTS g_scope_group; DROP TABLE IF EXISTS g_user_session_scheme; DROP TABLE IF EXISTS g_scope; DROP TABLE IF EXISTS g_plugin_module_instance; DROP TABLE IF EXISTS g_user_module_instance; DROP TABLE IF EXISTS g_user_middleware_module_instance; DROP TABLE IF EXISTS g_user_auth_scheme_module_instance; DROP TABLE IF EXISTS g_client_module_instance; DROP TABLE IF EXISTS g_user_session; CREATE TABLE g_user_module_instance ( gumi_id INT(11) PRIMARY KEY AUTO_INCREMENT, gumi_module VARCHAR(128) NOT NULL, gumi_order INT(11) NOT NULL, gumi_name VARCHAR(128) NOT NULL, gumi_display_name VARCHAR(256) DEFAULT '', gumi_parameters MEDIUMBLOB, gumi_readonly TINYINT(1) DEFAULT 0, gumi_multiple_passwords TINYINT(1) DEFAULT 0, gumi_enabled TINYINT(1) DEFAULT 1 ); CREATE TABLE g_user_middleware_module_instance ( gummi_id INT(11) PRIMARY KEY AUTO_INCREMENT, gummi_module VARCHAR(128) NOT NULL, gummi_order INT(11) NOT NULL, gummi_name VARCHAR(128) NOT NULL, gummi_display_name VARCHAR(256) DEFAULT '', gummi_parameters MEDIUMBLOB, gummi_enabled TINYINT(1) DEFAULT 1 ); CREATE TABLE g_user_auth_scheme_module_instance ( guasmi_id INT(11) PRIMARY KEY AUTO_INCREMENT, guasmi_module VARCHAR(128) NOT NULL, guasmi_expiration INT(11) NOT NULL DEFAULT 0, guasmi_max_use INT(11) DEFAULT 0, -- 0: unlimited guasmi_allow_user_register TINYINT(1) DEFAULT 1, guasmi_name VARCHAR(128) NOT NULL, guasmi_display_name VARCHAR(256) DEFAULT '', guasmi_parameters MEDIUMBLOB, guasmi_enabled TINYINT(1) DEFAULT 1 ); CREATE TABLE g_client_module_instance ( gcmi_id INT(11) PRIMARY KEY AUTO_INCREMENT, gcmi_module VARCHAR(128) NOT NULL, gcmi_order INT(11) NOT NULL, gcmi_name VARCHAR(128) NOT NULL, gcmi_display_name VARCHAR(256) DEFAULT '', gcmi_parameters MEDIUMBLOB, gcmi_readonly TINYINT(1) DEFAULT 0, gcmi_enabled TINYINT(1) DEFAULT 1 ); CREATE TABLE g_plugin_module_instance ( gpmi_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpmi_module VARCHAR(128) NOT NULL, gpmi_name VARCHAR(128) NOT NULL, gpmi_display_name VARCHAR(256) DEFAULT '', gpmi_parameters MEDIUMBLOB, gpmi_enabled TINYINT(1) DEFAULT 1 ); CREATE TABLE g_user_session ( gus_id INT(11) PRIMARY KEY AUTO_INCREMENT, gus_session_hash VARCHAR(128) NOT NULL, gus_user_agent VARCHAR(256), gus_issued_for VARCHAR(256), -- IP address or hostname gus_username VARCHAR(256) NOT NULL, gus_expiration TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gus_last_login TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gus_current TINYINT(1), gus_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_g_user_session_username ON g_user_session(gus_username); CREATE INDEX i_g_user_session_last_login ON g_user_session(gus_last_login); CREATE INDEX i_g_user_session_expiration ON g_user_session(gus_expiration); CREATE TABLE g_user_session_scheme ( guss_id INT(11) PRIMARY KEY AUTO_INCREMENT, gus_id INT(11) NOT NULL, guasmi_id INT(11) DEFAULT NULL, -- NULL means scheme 'password' guss_expiration TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, guss_last_login TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, guss_use_counter INT(11) DEFAULT 0, guss_enabled TINYINT(1) DEFAULT 1, FOREIGN KEY(gus_id) REFERENCES g_user_session(gus_id) ON DELETE CASCADE, FOREIGN KEY(guasmi_id) REFERENCES g_user_auth_scheme_module_instance(guasmi_id) ON DELETE CASCADE ); CREATE INDEX i_g_user_session_scheme_last_login ON g_user_session_scheme(guss_last_login); CREATE INDEX i_g_user_session_scheme_expiration ON g_user_session_scheme(guss_expiration); CREATE TABLE g_scope ( gs_id INT(11) PRIMARY KEY AUTO_INCREMENT, gs_name VARCHAR(128) NOT NULL UNIQUE, gs_display_name VARCHAR(256) DEFAULT '', gs_description VARCHAR(512), gs_password_required TINYINT(1) DEFAULT 1, gs_password_max_age INT(11) DEFAULT 0, gs_enabled TINYINT(1) DEFAULT 1 ); CREATE TABLE g_scope_group ( gsg_id INT(11) PRIMARY KEY AUTO_INCREMENT, gs_id INT(11), gsg_name VARCHAR(128) NOT NULL, gsg_scheme_required INT(11) DEFAULT 1, FOREIGN KEY(gs_id) REFERENCES g_scope(gs_id) ON DELETE CASCADE ); CREATE TABLE g_scope_group_auth_scheme_module_instance ( gsgasmi_id INT(11) PRIMARY KEY AUTO_INCREMENT, gsg_id INT(11) NOT NULL, guasmi_id INT(11) NOT NULL, FOREIGN KEY(gsg_id) REFERENCES g_scope_group(gsg_id) ON DELETE CASCADE, FOREIGN KEY(guasmi_id) REFERENCES g_user_auth_scheme_module_instance(guasmi_id) ON DELETE CASCADE ); CREATE TABLE g_client_user_scope ( gcus_id INT(11) PRIMARY KEY AUTO_INCREMENT, gs_id INT(11) NOT NULL, gcus_username VARCHAR(256) NOT NULL, gcus_client_id VARCHAR(256) NOT NULL, gcus_granted TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gcus_enabled TINYINT(1) DEFAULT 1, FOREIGN KEY(gs_id) REFERENCES g_scope(gs_id) ON DELETE CASCADE ); CREATE INDEX i_g_client_user_scope_username ON g_client_user_scope(gcus_username); CREATE INDEX i_g_client_user_scope_client_id ON g_client_user_scope(gcus_client_id); CREATE TABLE g_api_key ( gak_id INT(11) PRIMARY KEY AUTO_INCREMENT, gak_token_hash VARCHAR(512) NOT NULL, gak_counter INT(11) DEFAULT 0, gak_username VARCHAR(256) NOT NULL, gak_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gak_issued_for VARCHAR(256), -- IP address or hostname gak_user_agent VARCHAR(256), gak_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gak_token_hash ON g_api_key(gak_token_hash); ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/database/init-core.postgre.sql��������������������������������������������������0000664�0000000�0000000�00000013001�14156463140�0022145�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-- ----------------------------------------------------- -- -- PostgreSQL Database -- -- Initialize Glewlwyd Database for the backend server -- -- The administration client app -- -- Copyright 2020 Nicolas Mora <mail@babelouest.org> -- -- License: MIT -- -- ----------------------------------------------------- -- DROP TABLE IF EXISTS g_api_key; DROP TABLE IF EXISTS g_client_user_scope; DROP TABLE IF EXISTS g_scope_group_auth_scheme_module_instance; DROP TABLE IF EXISTS g_scope_group; DROP TABLE IF EXISTS g_user_session_scheme; DROP TABLE IF EXISTS g_scope; DROP TABLE IF EXISTS g_plugin_module_instance; DROP TABLE IF EXISTS g_user_module_instance; DROP TABLE IF EXISTS g_user_middleware_module_instance; DROP TABLE IF EXISTS g_user_auth_scheme_module_instance; DROP TABLE IF EXISTS g_client_module_instance; DROP TABLE IF EXISTS g_user_session; CREATE TABLE g_user_module_instance ( gumi_id SERIAL PRIMARY KEY, gumi_module VARCHAR(128) NOT NULL, gumi_order INTEGER NOT NULL, gumi_name VARCHAR(128) NOT NULL, gumi_display_name VARCHAR(256) DEFAULT '', gumi_parameters TEXT, gumi_readonly SMALLINT DEFAULT 0, gumi_multiple_passwords SMALLINT DEFAULT 0, gumi_enabled SMALLINT DEFAULT 1 ); CREATE TABLE g_user_middleware_module_instance ( gummi_id SERIAL PRIMARY KEY, gummi_module VARCHAR(128) NOT NULL, gummi_order INTEGER NOT NULL, gummi_name VARCHAR(128) NOT NULL, gummi_display_name VARCHAR(256) DEFAULT '', gummi_parameters TEXT, gummi_enabled SMALLINT DEFAULT 1 ); CREATE TABLE g_user_auth_scheme_module_instance ( guasmi_id SERIAL PRIMARY KEY, guasmi_module VARCHAR(128) NOT NULL, guasmi_expiration INTEGER NOT NULL DEFAULT 0, guasmi_max_use INTEGER DEFAULT 0, -- 0: unlimited guasmi_allow_user_register SMALLINT DEFAULT 1, guasmi_name VARCHAR(128) NOT NULL, guasmi_display_name VARCHAR(256) DEFAULT '', guasmi_parameters TEXT, guasmi_enabled SMALLINT DEFAULT 1 ); CREATE TABLE g_client_module_instance ( gcmi_id SERIAL PRIMARY KEY, gcmi_module VARCHAR(128) NOT NULL, gcmi_order INTEGER NOT NULL, gcmi_name VARCHAR(128) NOT NULL, gcmi_display_name VARCHAR(256) DEFAULT '', gcmi_parameters TEXT, gcmi_readonly SMALLINT DEFAULT 0, gcmi_enabled SMALLINT DEFAULT 1 ); CREATE TABLE g_plugin_module_instance ( gpmi_id SERIAL PRIMARY KEY, gpmi_module VARCHAR(128) NOT NULL, gpmi_name VARCHAR(128) NOT NULL, gpmi_display_name VARCHAR(256) DEFAULT '', gpmi_parameters TEXT, gpmi_enabled SMALLINT DEFAULT 1 ); CREATE TABLE g_user_session ( gus_id SERIAL PRIMARY KEY, gus_session_hash VARCHAR(128) NOT NULL, gus_user_agent VARCHAR(256), gus_issued_for VARCHAR(256), -- IP address or hostname gus_username VARCHAR(256) NOT NULL, gus_expiration TIMESTAMP NOT NULL DEFAULT NOW(), gus_last_login TIMESTAMP NOT NULL DEFAULT NOW(), gus_current SMALLINT, gus_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_g_user_session_username ON g_user_session(gus_username); CREATE INDEX i_g_user_session_last_login ON g_user_session(gus_last_login); CREATE INDEX i_g_user_session_expiration ON g_user_session(gus_expiration); CREATE TABLE g_user_session_scheme ( guss_id SERIAL PRIMARY KEY, gus_id INTEGER NOT NULL, guasmi_id INTEGER DEFAULT NULL, -- NULL means scheme 'password' guss_expiration TIMESTAMP NOT NULL DEFAULT NOW(), guss_last_login TIMESTAMP NOT NULL DEFAULT NOW(), guss_use_counter INTEGER DEFAULT 0, guss_enabled SMALLINT DEFAULT 1, FOREIGN KEY(gus_id) REFERENCES g_user_session(gus_id) ON DELETE CASCADE, FOREIGN KEY(guasmi_id) REFERENCES g_user_auth_scheme_module_instance(guasmi_id) ON DELETE CASCADE ); CREATE INDEX i_g_user_session_scheme_last_login ON g_user_session_scheme(guss_last_login); CREATE INDEX i_g_user_session_scheme_expiration ON g_user_session_scheme(guss_expiration); CREATE TABLE g_scope ( gs_id SERIAL PRIMARY KEY, gs_name VARCHAR(128) NOT NULL UNIQUE, gs_display_name VARCHAR(256) DEFAULT '', gs_description VARCHAR(512), gs_password_required SMALLINT DEFAULT 1, gs_password_max_age INTEGER DEFAULT 0, gs_enabled SMALLINT DEFAULT 1 ); CREATE TABLE g_scope_group ( gsg_id SERIAL PRIMARY KEY, gs_id INTEGER, gsg_name VARCHAR(128) NOT NULL, gsg_scheme_required INTEGER DEFAULT 1, FOREIGN KEY(gs_id) REFERENCES g_scope(gs_id) ON DELETE CASCADE ); CREATE TABLE g_scope_group_auth_scheme_module_instance ( gsgasmi_id SERIAL PRIMARY KEY, gsg_id INTEGER NOT NULL, guasmi_id INTEGER NOT NULL, FOREIGN KEY(gsg_id) REFERENCES g_scope_group(gsg_id) ON DELETE CASCADE, FOREIGN KEY(guasmi_id) REFERENCES g_user_auth_scheme_module_instance(guasmi_id) ON DELETE CASCADE ); CREATE TABLE g_client_user_scope ( gcus_id SERIAL PRIMARY KEY, gs_id INTEGER NOT NULL, gcus_username VARCHAR(256) NOT NULL, gcus_client_id VARCHAR(256) NOT NULL, gcus_granted TIMESTAMP NOT NULL DEFAULT NOW(), gcus_enabled SMALLINT DEFAULT 1, FOREIGN KEY(gs_id) REFERENCES g_scope(gs_id) ON DELETE CASCADE ); CREATE INDEX i_g_client_user_scope_username ON g_client_user_scope(gcus_username); CREATE INDEX i_g_client_user_scope_client_id ON g_client_user_scope(gcus_client_id); CREATE TABLE g_api_key ( gak_id SERIAL PRIMARY KEY, gak_token_hash VARCHAR(512) NOT NULL, gak_counter INTEGER DEFAULT 0, gak_username VARCHAR(256) NOT NULL, gak_issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gak_issued_for VARCHAR(256), -- IP address or hostname gak_user_agent VARCHAR(256), gak_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gak_token_hash ON g_api_key(gak_token_hash); �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/database/init-core.sqlite3.sql��������������������������������������������������0000664�0000000�0000000�00000013003�14156463140�0022050�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-- ----------------------------------------------------- -- -- SQlite3 Database -- -- Initialize Glewlwyd Database for the backend server -- -- The administration client app -- -- Copyright 2020 Nicolas Mora <mail@babelouest.org> -- -- License: MIT -- -- ----------------------------------------------------- -- DROP TABLE IF EXISTS g_api_key; DROP TABLE IF EXISTS g_client_user_scope; DROP TABLE IF EXISTS g_scope_group_auth_scheme_module_instance; DROP TABLE IF EXISTS g_scope_group; DROP TABLE IF EXISTS g_user_session_scheme; DROP TABLE IF EXISTS g_scope; DROP TABLE IF EXISTS g_plugin_module_instance; DROP TABLE IF EXISTS g_user_module_instance; DROP TABLE IF EXISTS g_user_middleware_module_instance; DROP TABLE IF EXISTS g_user_auth_scheme_module_instance; DROP TABLE IF EXISTS g_client_module_instance; DROP TABLE IF EXISTS g_user_session; CREATE TABLE g_user_module_instance ( gumi_id INTEGER PRIMARY KEY AUTOINCREMENT, gumi_module TEXT NOT NULL, gumi_order INTEGER NOT NULL, gumi_name TEXT NOT NULL, gumi_display_name TEXT DEFAULT '', gumi_parameters TEXT, gumi_readonly INTEGER DEFAULT 0, gumi_multiple_passwords INTEGER DEFAULT 0, gumi_enabled INTEGER DEFAULT 1 ); CREATE TABLE g_user_middleware_module_instance ( gummi_id INTEGER PRIMARY KEY AUTOINCREMENT, gummi_module TEXT NOT NULL, gummi_order INTEGER NOT NULL, gummi_name TEXT NOT NULL, gummi_display_name TEXT DEFAULT '', gummi_parameters TEXT, gummi_enabled INTEGER DEFAULT 1 ); CREATE TABLE g_user_auth_scheme_module_instance ( guasmi_id INTEGER PRIMARY KEY AUTOINCREMENT, guasmi_module TEXT NOT NULL, guasmi_expiration INTEGER NOT NULL DEFAULT 0, guasmi_max_use INTEGER DEFAULT 0, -- 0: unlimited guasmi_allow_user_register INTEGER DEFAULT 1, guasmi_name TEXT NOT NULL, guasmi_display_name TEXT DEFAULT '', guasmi_parameters TEXT, guasmi_enabled INTEGER DEFAULT 1 ); CREATE TABLE g_client_module_instance ( gcmi_id INTEGER PRIMARY KEY AUTOINCREMENT, gcmi_module TEXT NOT NULL, gcmi_order INTEGER NOT NULL, gcmi_name TEXT NOT NULL, gcmi_display_name TEXT DEFAULT '', gcmi_parameters TEXT, gcmi_readonly INTEGER DEFAULT 0, gcmi_enabled INTEGER DEFAULT 1 ); CREATE TABLE g_plugin_module_instance ( gpmi_id INTEGER PRIMARY KEY AUTOINCREMENT, gpmi_module TEXT NOT NULL, gpmi_name TEXT NOT NULL, gpmi_display_name TEXT DEFAULT '', gpmi_parameters TEXT, gpmi_enabled INTEGER DEFAULT 1 ); CREATE TABLE g_user_session ( gus_id INTEGER PRIMARY KEY AUTOINCREMENT, gus_session_hash TEXT NOT NULL, gus_user_agent TEXT, gus_issued_for TEXT, -- IP address or hostname gus_username TEXT NOT NULL, gus_expiration TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gus_last_login TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gus_current INTEGER, gus_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_g_user_session_username ON g_user_session(gus_username); CREATE INDEX i_g_user_session_last_login ON g_user_session(gus_last_login); CREATE INDEX i_g_user_session_expiration ON g_user_session(gus_expiration); CREATE TABLE g_user_session_scheme ( guss_id INTEGER PRIMARY KEY AUTOINCREMENT, gus_id INTEGER NOT NULL, guasmi_id INTEGER DEFAULT NULL, -- NULL means scheme 'password' guss_expiration TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, guss_last_login TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, guss_use_counter INTEGER DEFAULT 0, guss_enabled INTEGER DEFAULT 1, FOREIGN KEY(gus_id) REFERENCES g_user_session(gus_id) ON DELETE CASCADE, FOREIGN KEY(guasmi_id) REFERENCES g_user_auth_scheme_module_instance(guasmi_id) ON DELETE CASCADE ); CREATE INDEX i_g_user_session_scheme_last_login ON g_user_session_scheme(guss_last_login); CREATE INDEX i_g_user_session_scheme_expiration ON g_user_session_scheme(guss_expiration); CREATE TABLE g_scope ( gs_id INTEGER PRIMARY KEY AUTOINCREMENT, gs_name TEXT NOT NULL UNIQUE, gs_display_name TEXT DEFAULT '', gs_description TEXT, gs_password_required INTEGER DEFAULT 1, gs_password_max_age INTEGER DEFAULT 0, gs_enabled INTEGER DEFAULT 1 ); CREATE TABLE g_scope_group ( gsg_id INTEGER PRIMARY KEY AUTOINCREMENT, gs_id INTEGER, gsg_name TEXT NOT NULL, gsg_scheme_required INTEGER DEFAULT 1, FOREIGN KEY(gs_id) REFERENCES g_scope(gs_id) ON DELETE CASCADE ); CREATE TABLE g_scope_group_auth_scheme_module_instance ( gsgasmi_id INTEGER PRIMARY KEY AUTOINCREMENT, gsg_id INTEGER NOT NULL, guasmi_id INTEGER NOT NULL, FOREIGN KEY(gsg_id) REFERENCES g_scope_group(gsg_id) ON DELETE CASCADE, FOREIGN KEY(guasmi_id) REFERENCES g_user_auth_scheme_module_instance(guasmi_id) ON DELETE CASCADE ); CREATE TABLE g_client_user_scope ( gcus_id INTEGER PRIMARY KEY AUTOINCREMENT, gs_id INTEGER NOT NULL, gcus_username TEXT NOT NULL, gcus_client_id TEXT NOT NULL, gcus_granted TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gcus_enabled INTEGER DEFAULT 1, FOREIGN KEY(gs_id) REFERENCES g_scope(gs_id) ON DELETE CASCADE ); CREATE INDEX i_g_client_user_scope_username ON g_client_user_scope(gcus_username); CREATE INDEX i_g_client_user_scope_client_id ON g_client_user_scope(gcus_client_id); CREATE TABLE g_api_key ( gak_id INTEGER PRIMARY KEY AUTOINCREMENT, gak_token_hash TEXT NOT NULL, gak_counter INTEGER DEFAULT 0, gak_username TEXT NOT NULL, gak_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gak_issued_for TEXT, -- IP address or hostname gak_user_agent TEXT, gak_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gak_token_hash ON g_api_key(gak_token_hash); �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/database/init.mariadb.sql�������������������������������������������������������0000664�0000000�0000000�00000107556�14156463140�0021156�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-- ------------------------------------------------------ -- -- Mariadb/Mysql Database -- -- Initialize Glewlwyd Database for the backend server -- -- The administration client app -- -- Copyright 2020-2021 Nicolas Mora <mail@babelouest.org> -- -- License: MIT -- -- ------------------------------------------------------ -- DROP TABLE IF EXISTS g_api_key; DROP TABLE IF EXISTS g_client_user_scope; DROP TABLE IF EXISTS g_scope_group_auth_scheme_module_instance; DROP TABLE IF EXISTS g_scope_group; DROP TABLE IF EXISTS g_user_session_scheme; DROP TABLE IF EXISTS g_scope; DROP TABLE IF EXISTS g_plugin_module_instance; DROP TABLE IF EXISTS g_user_module_instance; DROP TABLE IF EXISTS g_user_middleware_module_instance; DROP TABLE IF EXISTS g_user_auth_scheme_module_instance; DROP TABLE IF EXISTS g_client_module_instance; DROP TABLE IF EXISTS g_user_session; DROP TABLE IF EXISTS g_client_property; DROP TABLE IF EXISTS g_client_scope_client; DROP TABLE IF EXISTS g_client_scope; DROP TABLE IF EXISTS g_client; DROP TABLE IF EXISTS g_user_property; DROP TABLE IF EXISTS g_user_scope_user; DROP TABLE IF EXISTS g_user_scope; DROP TABLE IF EXISTS g_user_password; DROP TABLE IF EXISTS g_user; DROP TABLE IF EXISTS gpg_device_authorization_scope; DROP TABLE IF EXISTS gpg_device_authorization; DROP TABLE IF EXISTS gpg_access_token_scope; DROP TABLE IF EXISTS gpg_access_token; DROP TABLE IF EXISTS gpg_refresh_token_scope; DROP TABLE IF EXISTS gpg_refresh_token; DROP TABLE IF EXISTS gpg_code_scope; DROP TABLE IF EXISTS gpg_code; DROP TABLE IF EXISTS gpo_ciba_scope; DROP TABLE IF EXISTS gpo_ciba_scheme; DROP TABLE IF EXISTS gpo_ciba; DROP TABLE IF EXISTS gpo_par_scope; DROP TABLE IF EXISTS gpo_par; DROP TABLE IF EXISTS gpo_rar; DROP TABLE IF EXISTS gpo_dpop; DROP TABLE IF EXISTS gpo_device_scheme; DROP TABLE IF EXISTS gpo_device_authorization_scope; DROP TABLE IF EXISTS gpo_device_authorization; DROP TABLE IF EXISTS gpo_client_registration; DROP TABLE IF EXISTS gpo_subject_identifier; DROP TABLE IF EXISTS gpo_id_token; DROP TABLE IF EXISTS gpo_access_token_scope; DROP TABLE IF EXISTS gpo_access_token; DROP TABLE IF EXISTS gpo_refresh_token_scope; DROP TABLE IF EXISTS gpo_refresh_token; DROP TABLE IF EXISTS gpo_code_scheme; DROP TABLE IF EXISTS gpo_code_scope; DROP TABLE IF EXISTS gpo_code; DROP TABLE IF EXISTS gpo_client_token_request; DROP TABLE IF EXISTS gs_code; DROP TABLE IF EXISTS gs_webauthn_assertion; DROP TABLE IF EXISTS gs_webauthn_credential; DROP TABLE IF EXISTS gs_webauthn_user; DROP TABLE IF EXISTS gs_otp; DROP TABLE IF EXISTS gs_user_certificate; DROP TABLE IF EXISTS gpr_reset_credentials_email; DROP TABLE IF EXISTS gpr_reset_credentials_session; DROP TABLE IF EXISTS gpr_update_email; DROP TABLE IF EXISTS gpr_session; DROP TABLE IF EXISTS gs_oauth2_session; DROP TABLE IF EXISTS gs_oauth2_registration; CREATE TABLE g_user_module_instance ( gumi_id INT(11) PRIMARY KEY AUTO_INCREMENT, gumi_module VARCHAR(128) NOT NULL, gumi_order INT(11) NOT NULL, gumi_name VARCHAR(128) NOT NULL, gumi_display_name VARCHAR(256) DEFAULT '', gumi_parameters MEDIUMBLOB, gumi_readonly TINYINT(1) DEFAULT 0, gumi_multiple_passwords TINYINT(1) DEFAULT 0, gumi_enabled TINYINT(1) DEFAULT 1 ); CREATE TABLE g_user_middleware_module_instance ( gummi_id INT(11) PRIMARY KEY AUTO_INCREMENT, gummi_module VARCHAR(128) NOT NULL, gummi_order INT(11) NOT NULL, gummi_name VARCHAR(128) NOT NULL, gummi_display_name VARCHAR(256) DEFAULT '', gummi_parameters MEDIUMBLOB, gummi_enabled TINYINT(1) DEFAULT 1 ); CREATE TABLE g_user_auth_scheme_module_instance ( guasmi_id INT(11) PRIMARY KEY AUTO_INCREMENT, guasmi_module VARCHAR(128) NOT NULL, guasmi_expiration INT(11) NOT NULL DEFAULT 0, guasmi_max_use INT(11) DEFAULT 0, -- 0: unlimited guasmi_allow_user_register TINYINT(1) DEFAULT 1, guasmi_forbid_user_profile TINYINT(1) DEFAULT 0, guasmi_forbid_user_reset_credential TINYINT(1) DEFAULT 0, guasmi_name VARCHAR(128) NOT NULL, guasmi_display_name VARCHAR(256) DEFAULT '', guasmi_parameters MEDIUMBLOB, guasmi_enabled TINYINT(1) DEFAULT 1 ); CREATE TABLE g_client_module_instance ( gcmi_id INT(11) PRIMARY KEY AUTO_INCREMENT, gcmi_module VARCHAR(128) NOT NULL, gcmi_order INT(11) NOT NULL, gcmi_name VARCHAR(128) NOT NULL, gcmi_display_name VARCHAR(256) DEFAULT '', gcmi_parameters MEDIUMBLOB, gcmi_readonly TINYINT(1) DEFAULT 0, gcmi_enabled TINYINT(1) DEFAULT 1 ); CREATE TABLE g_plugin_module_instance ( gpmi_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpmi_module VARCHAR(128) NOT NULL, gpmi_name VARCHAR(128) NOT NULL, gpmi_display_name VARCHAR(256) DEFAULT '', gpmi_parameters MEDIUMBLOB, gpmi_enabled TINYINT(1) DEFAULT 1 ); CREATE TABLE g_user_session ( gus_id INT(11) PRIMARY KEY AUTO_INCREMENT, gus_session_hash VARCHAR(128) NOT NULL, gus_user_agent VARCHAR(256), gus_issued_for VARCHAR(256), -- IP address or hostname gus_username VARCHAR(256) NOT NULL, gus_expiration TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gus_last_login TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gus_current TINYINT(1), gus_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_g_user_session_username ON g_user_session(gus_username); CREATE INDEX i_g_user_session_last_login ON g_user_session(gus_last_login); CREATE INDEX i_g_user_session_expiration ON g_user_session(gus_expiration); CREATE TABLE g_user_session_scheme ( guss_id INT(11) PRIMARY KEY AUTO_INCREMENT, gus_id INT(11) NOT NULL, guasmi_id INT(11) DEFAULT NULL, -- NULL means scheme 'password' guss_expiration TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, guss_last_login TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, guss_use_counter INT(11) DEFAULT 0, guss_enabled TINYINT(1) DEFAULT 1, FOREIGN KEY(gus_id) REFERENCES g_user_session(gus_id) ON DELETE CASCADE, FOREIGN KEY(guasmi_id) REFERENCES g_user_auth_scheme_module_instance(guasmi_id) ON DELETE CASCADE ); CREATE INDEX i_g_user_session_scheme_last_login ON g_user_session_scheme(guss_last_login); CREATE INDEX i_g_user_session_scheme_expiration ON g_user_session_scheme(guss_expiration); CREATE TABLE g_scope ( gs_id INT(11) PRIMARY KEY AUTO_INCREMENT, gs_name VARCHAR(128) NOT NULL UNIQUE, gs_display_name VARCHAR(256) DEFAULT '', gs_description VARCHAR(512), gs_password_required TINYINT(1) DEFAULT 1, gs_password_max_age INT(11) DEFAULT 0, gs_enabled TINYINT(1) DEFAULT 1 ); CREATE TABLE g_scope_group ( gsg_id INT(11) PRIMARY KEY AUTO_INCREMENT, gs_id INT(11), gsg_name VARCHAR(128) NOT NULL, gsg_scheme_required INT(11) DEFAULT 1, FOREIGN KEY(gs_id) REFERENCES g_scope(gs_id) ON DELETE CASCADE ); CREATE TABLE g_scope_group_auth_scheme_module_instance ( gsgasmi_id INT(11) PRIMARY KEY AUTO_INCREMENT, gsg_id INT(11) NOT NULL, guasmi_id INT(11) NOT NULL, FOREIGN KEY(gsg_id) REFERENCES g_scope_group(gsg_id) ON DELETE CASCADE, FOREIGN KEY(guasmi_id) REFERENCES g_user_auth_scheme_module_instance(guasmi_id) ON DELETE CASCADE ); CREATE TABLE g_client_user_scope ( gcus_id INT(11) PRIMARY KEY AUTO_INCREMENT, gs_id INT(11) NOT NULL, gcus_username VARCHAR(256) NOT NULL, gcus_client_id VARCHAR(256) NOT NULL, gcus_granted TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gcus_enabled TINYINT(1) DEFAULT 1, FOREIGN KEY(gs_id) REFERENCES g_scope(gs_id) ON DELETE CASCADE ); CREATE INDEX i_g_client_user_scope_username ON g_client_user_scope(gcus_username); CREATE INDEX i_g_client_user_scope_client_id ON g_client_user_scope(gcus_client_id); CREATE TABLE g_api_key ( gak_id INT(11) PRIMARY KEY AUTO_INCREMENT, gak_token_hash VARCHAR(512) NOT NULL, gak_counter INT(11) DEFAULT 0, gak_username VARCHAR(256) NOT NULL, gak_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gak_issued_for VARCHAR(256), -- IP address or hostname gak_user_agent VARCHAR(256), gak_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gak_token_hash ON g_api_key(gak_token_hash); CREATE TABLE g_client ( gc_id INT(11) PRIMARY KEY AUTO_INCREMENT, gc_client_id VARCHAR(128) NOT NULL UNIQUE, gc_name VARCHAR(256) DEFAULT '', gc_description VARCHAR(512) DEFAULT '', gc_confidential TINYINT(1) DEFAULT 0, gc_password VARCHAR(256), gc_enabled TINYINT(1) DEFAULT 1 ); CREATE TABLE g_client_scope ( gcs_id INT(11) PRIMARY KEY AUTO_INCREMENT, gcs_name VARCHAR(128) NOT NULL UNIQUE ); CREATE TABLE g_client_scope_client ( gcsu_id INT(11) PRIMARY KEY AUTO_INCREMENT, gc_id INT(11), gcs_id INT(11), FOREIGN KEY(gc_id) REFERENCES g_client(gc_id) ON DELETE CASCADE, FOREIGN KEY(gcs_id) REFERENCES g_client_scope(gcs_id) ON DELETE CASCADE ); CREATE TABLE g_client_property ( gcp_id INT(11) PRIMARY KEY AUTO_INCREMENT, gc_id INT(11), gcp_name VARCHAR(128) NOT NULL, gcp_value_tiny VARCHAR(512) DEFAULT NULL, gcp_value_small BLOB DEFAULT NULL, gcp_value_medium MEDIUMBLOB DEFAULT NULL, FOREIGN KEY(gc_id) REFERENCES g_client(gc_id) ON DELETE CASCADE ); CREATE INDEX i_g_client_property_name ON g_client_property(gcp_name); CREATE TABLE g_user ( gu_id INT(11) PRIMARY KEY AUTO_INCREMENT, gu_username VARCHAR(128) NOT NULL UNIQUE, gu_name VARCHAR(256) DEFAULT '', gu_email VARCHAR(512) DEFAULT '', gu_enabled TINYINT(1) DEFAULT 1 ); CREATE TABLE g_user_scope ( gus_id INT(11) PRIMARY KEY AUTO_INCREMENT, gus_name VARCHAR(128) NOT NULL UNIQUE ); CREATE TABLE g_user_scope_user ( gusu_id INT(11) PRIMARY KEY AUTO_INCREMENT, gu_id INT(11), gus_id INT(11), FOREIGN KEY(gu_id) REFERENCES g_user(gu_id) ON DELETE CASCADE, FOREIGN KEY(gus_id) REFERENCES g_user_scope(gus_id) ON DELETE CASCADE ); CREATE TABLE g_user_property ( gup_id INT(11) PRIMARY KEY AUTO_INCREMENT, gu_id INT(11), gup_name VARCHAR(128) NOT NULL, gup_value_tiny VARCHAR(512) DEFAULT NULL, gup_value_small BLOB DEFAULT NULL, gup_value_medium MEDIUMBLOB DEFAULT NULL, FOREIGN KEY(gu_id) REFERENCES g_user(gu_id) ON DELETE CASCADE ); CREATE INDEX i_g_user_property_name ON g_user_property(gup_name); CREATE TABLE g_user_password ( guw_id INT(11) PRIMARY KEY AUTO_INCREMENT, gu_id INT(11), guw_password VARCHAR(256), FOREIGN KEY(gu_id) REFERENCES g_user(gu_id) ON DELETE CASCADE ); CREATE TABLE gpg_code ( gpgc_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpgc_plugin_name VARCHAR(256) NOT NULL, gpgc_username VARCHAR(256) NOT NULL, gpgc_client_id VARCHAR(256) NOT NULL, gpgc_redirect_uri VARCHAR(512) NOT NULL, gpgc_code_hash VARCHAR(512) NOT NULL, gpgc_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpgc_issued_for VARCHAR(256), -- IP address or hostname gpgc_user_agent VARCHAR(256), gpgc_code_challenge VARCHAR(128), gpgc_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gpgc_code_hash ON gpg_code(gpgc_code_hash); CREATE INDEX i_gpgc_code_challenge ON gpg_code(gpgc_code_challenge); CREATE TABLE gpg_code_scope ( gpgcs_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpgc_id INT(11), gpgcs_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpgc_id) REFERENCES gpg_code(gpgc_id) ON DELETE CASCADE ); CREATE TABLE gpg_refresh_token ( gpgr_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpgr_plugin_name VARCHAR(256) NOT NULL, gpgr_authorization_type INT(2) NOT NULL, -- 0: Authorization Code Grant, 1: Implicit Grant, 2: Resource Owner Password Credentials Grant, 3: Client Credentials Grant gpgc_id INT(11) DEFAULT NULL, gpgr_username VARCHAR(256) NOT NULL, gpgr_client_id VARCHAR(256), gpgr_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpgr_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpgr_last_seen TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpgr_duration INT(11), gpgr_rolling_expiration TINYINT(1) DEFAULT 0, gpgr_issued_for VARCHAR(256), -- IP address or hostname gpgr_user_agent VARCHAR(256), gpgr_token_hash VARCHAR(512) NOT NULL, gpgr_enabled TINYINT(1) DEFAULT 1, FOREIGN KEY(gpgc_id) REFERENCES gpg_code(gpgc_id) ON DELETE CASCADE ); CREATE INDEX i_gpgr_token_hash ON gpg_refresh_token(gpgr_token_hash); CREATE TABLE gpg_refresh_token_scope ( gpgrs_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpgr_id INT(11), gpgrs_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpgr_id) REFERENCES gpg_refresh_token(gpgr_id) ON DELETE CASCADE ); -- Access token table, to store meta information on access token sent CREATE TABLE gpg_access_token ( gpga_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpga_plugin_name VARCHAR(256) NOT NULL, gpga_authorization_type INT(2) NOT NULL, -- 0: Authorization Code Grant, 1: Implicit Grant, 2: Resource Owner Password Credentials Grant, 3: Client Credentials Grant gpgr_id INT(11) DEFAULT NULL, gpga_username VARCHAR(256), gpga_client_id VARCHAR(256), gpga_issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpga_issued_for VARCHAR(256), -- IP address or hostname gpga_user_agent VARCHAR(256), gpga_token_hash VARCHAR(512) NOT NULL, gpga_enabled TINYINT(1) DEFAULT 1, FOREIGN KEY(gpgr_id) REFERENCES gpg_refresh_token(gpgr_id) ON DELETE CASCADE ); CREATE INDEX i_gpga_token_hash ON gpg_access_token(gpga_token_hash); CREATE TABLE gpg_access_token_scope ( gpgas_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpga_id INT(11), gpgas_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpga_id) REFERENCES gpg_access_token(gpga_id) ON DELETE CASCADE ); -- store device authorization requests CREATE TABLE gpg_device_authorization ( gpgda_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpgda_plugin_name VARCHAR(256) NOT NULL, gpgda_client_id VARCHAR(256) NOT NULL, gpgda_username VARCHAR(256), gpgda_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpgda_expires_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpgda_issued_for VARCHAR(256), -- IP address or hostname of the deice client gpgda_device_code_hash VARCHAR(512) NOT NULL, gpgda_user_code_hash VARCHAR(512) NOT NULL, gpgda_status TINYINT(1) DEFAULT 0, -- 0: created, 1: user verified, 2 device completed, 3 disabled gpgda_last_check TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX i_gpgda_device_code_hash ON gpg_device_authorization(gpgda_device_code_hash); CREATE INDEX i_gpgda_user_code_hash ON gpg_device_authorization(gpgda_user_code_hash); CREATE TABLE gpg_device_authorization_scope ( gpgdas_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpgda_id INT(11), gpgdas_scope VARCHAR(128) NOT NULL, gpgdas_allowed TINYINT(1) DEFAULT 0, FOREIGN KEY(gpgda_id) REFERENCES gpg_device_authorization(gpgda_id) ON DELETE CASCADE ); CREATE TABLE gpo_code ( gpoc_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoc_plugin_name VARCHAR(256) NOT NULL, gpoc_authorization_type INT(2) NOT NULL, gpoc_username VARCHAR(256) NOT NULL, gpoc_client_id VARCHAR(256) NOT NULL, gpoc_redirect_uri VARCHAR(512) NOT NULL, gpoc_code_hash VARCHAR(512) NOT NULL, gpoc_nonce VARCHAR(512), gpoc_resource VARCHAR(512), gpoc_claims_request BLOB DEFAULT NULL, gpoc_authorization_details BLOB DEFAULT NULL, gpoc_s_hash VARCHAR(512), gpoc_sid VARCHAR(128), gpoc_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpoc_issued_for VARCHAR(256), -- IP address or hostname gpoc_user_agent VARCHAR(256), gpoc_code_challenge VARCHAR(128), gpoc_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gpoc_code_hash ON gpo_code(gpoc_code_hash); CREATE INDEX i_gpoc_code_challenge ON gpo_code(gpoc_code_challenge); CREATE TABLE gpo_code_scope ( gpocs_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoc_id INT(11), gpocs_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE ); CREATE TABLE gpo_code_scheme ( gpoch_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoc_id INT(11), gpoch_scheme_module VARCHAR(128) NOT NULL, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE ); CREATE TABLE gpo_refresh_token ( gpor_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpor_plugin_name VARCHAR(256) NOT NULL, gpor_authorization_type INT(2) NOT NULL, gpoc_id INT(11) DEFAULT NULL, gpor_username VARCHAR(256) NOT NULL, gpor_client_id VARCHAR(256), gpor_resource VARCHAR(512), gpor_claims_request BLOB DEFAULT NULL, gpor_authorization_details BLOB DEFAULT NULL, gpor_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpor_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpor_last_seen TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpor_duration INT(11), gpor_rolling_expiration TINYINT(1) DEFAULT 0, gpor_issued_for VARCHAR(256), -- IP address or hostname gpor_user_agent VARCHAR(256), gpor_token_hash VARCHAR(512) NOT NULL, gpor_jti VARCHAR(128), gpor_dpop_jkt VARCHAR(512), gpor_enabled TINYINT(1) DEFAULT 1, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE ); CREATE INDEX i_gpor_token_hash ON gpo_refresh_token(gpor_token_hash); CREATE INDEX i_gpor_jti ON gpo_refresh_token(gpor_jti); CREATE TABLE gpo_refresh_token_scope ( gpors_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpor_id INT(11), gpors_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpor_id) REFERENCES gpo_refresh_token(gpor_id) ON DELETE CASCADE ); -- Access token table, to store meta information on access token sent CREATE TABLE gpo_access_token ( gpoa_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoa_plugin_name VARCHAR(256) NOT NULL, gpoa_authorization_type INT(2) NOT NULL, gpor_id INT(11) DEFAULT NULL, gpoa_username VARCHAR(256), gpoa_client_id VARCHAR(256), gpoa_resource VARCHAR(512), gpoa_issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoa_issued_for VARCHAR(256), -- IP address or hostname gpoa_user_agent VARCHAR(256), gpoa_token_hash VARCHAR(512) NOT NULL, gpoa_jti VARCHAR(128), gpoa_authorization_details BLOB DEFAULT NULL, gpoa_enabled TINYINT(1) DEFAULT 1, FOREIGN KEY(gpor_id) REFERENCES gpo_refresh_token(gpor_id) ON DELETE CASCADE ); CREATE INDEX i_gpoa_token_hash ON gpo_access_token(gpoa_token_hash); CREATE INDEX i_gpoa_jti ON gpo_access_token(gpoa_jti); CREATE TABLE gpo_access_token_scope ( gpoas_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoa_id INT(11), gpoas_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpoa_id) REFERENCES gpo_access_token(gpoa_id) ON DELETE CASCADE ); -- Id token table, to store meta information on id token sent CREATE TABLE gpo_id_token ( gpoi_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoc_id INT(11), gpor_id INT(11), gpoi_plugin_name VARCHAR(256) NOT NULL, gpoi_authorization_type INT(2) NOT NULL, gpoi_username VARCHAR(256), gpoi_client_id VARCHAR(256), gpoi_issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoi_issued_for VARCHAR(256), -- IP address or hostname gpoi_user_agent VARCHAR(256), gpoi_hash VARCHAR(512), gpoi_sid VARCHAR(128), gpoi_enabled TINYINT(1) DEFAULT 1, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE, FOREIGN KEY(gpor_id) REFERENCES gpo_refresh_token(gpor_id) ON DELETE CASCADE ); CREATE INDEX i_gpoi_hash ON gpo_id_token(gpoi_hash); -- subject identifier table to store subs and their relations to usernames, client_id and sector_identifier CREATE TABLE gpo_subject_identifier ( gposi_id INT(11) PRIMARY KEY AUTO_INCREMENT, gposi_plugin_name VARCHAR(256) NOT NULL, gposi_username VARCHAR(256) NOT NULL, gposi_client_id VARCHAR(256), gposi_sector_identifier_uri VARCHAR(256), gposi_sub VARCHAR(256) NOT NULL ); CREATE INDEX i_gposi_sub ON gpo_subject_identifier(gposi_sub); -- store meta information on client registration CREATE TABLE gpo_client_registration ( gpocr_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpocr_plugin_name VARCHAR(256) NOT NULL, gpocr_cient_id VARCHAR(256) NOT NULL, gpocr_management_at_hash VARCHAR(512), gpocr_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoa_id INT(11), gpocr_issued_for VARCHAR(256), -- IP address or hostname gpocr_user_agent VARCHAR(256), FOREIGN KEY(gpoa_id) REFERENCES gpo_access_token(gpoa_id) ON DELETE CASCADE ); CREATE INDEX i_gpocr_management_at_hash ON gpo_client_registration(gpocr_management_at_hash); -- store meta information about client request on token endpoint CREATE TABLE gpo_client_token_request ( gpoctr_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoctr_plugin_name VARCHAR(256) NOT NULL, gpoctr_cient_id VARCHAR(256) NOT NULL, gpoctr_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoctr_issued_for VARCHAR(256), -- IP address or hostname gpoctr_jti_hash VARCHAR(512) ); -- store device authorization requests CREATE TABLE gpo_device_authorization ( gpoda_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoda_plugin_name VARCHAR(256) NOT NULL, gpoda_client_id VARCHAR(256) NOT NULL, gpoda_resource VARCHAR(512), gpoda_username VARCHAR(256), gpoda_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoda_expires_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoda_issued_for VARCHAR(256), -- IP address or hostname of the deice client gpoda_device_code_hash VARCHAR(512) NOT NULL, gpoda_user_code_hash VARCHAR(512) NOT NULL, gpoda_sid VARCHAR(128), gpoda_status TINYINT(1) DEFAULT 0, -- 0: created, 1: user verified, 2 device completed, 3 disabled gpoda_authorization_details BLOB DEFAULT NULL, gpoda_last_check TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX i_gpoda_device_code_hash ON gpo_device_authorization(gpoda_device_code_hash); CREATE INDEX i_gpoda_user_code_hash ON gpo_device_authorization(gpoda_user_code_hash); CREATE TABLE gpo_device_authorization_scope ( gpodas_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoda_id INT(11), gpodas_scope VARCHAR(128) NOT NULL, gpodas_allowed TINYINT(1) DEFAULT 0, FOREIGN KEY(gpoda_id) REFERENCES gpo_device_authorization(gpoda_id) ON DELETE CASCADE ); CREATE TABLE gpo_device_scheme ( gpodh_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoda_id INT(11), gpodh_scheme_module VARCHAR(128) NOT NULL, FOREIGN KEY(gpoda_id) REFERENCES gpo_device_authorization(gpoda_id) ON DELETE CASCADE ); CREATE TABLE gpo_dpop ( gpod_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpod_plugin_name VARCHAR(256) NOT NULL, gpod_client_id VARCHAR(256) NOT NULL, gpod_jti_hash VARCHAR(512) NOT NULL, gpod_jkt VARCHAR(512) NOT NULL, gpod_htm VARCHAR(128) NOT NULL, gpod_htu VARCHAR(512) NOT NULL, gpod_iat TIMESTAMP NOT NULL, gpod_last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX i_gpod_jti_hash ON gpo_dpop(gpod_jti_hash); CREATE TABLE gpo_rar ( gporar_id INT(11) PRIMARY KEY AUTO_INCREMENT, gporar_plugin_name VARCHAR(256) NOT NULL, gporar_client_id VARCHAR(256) NOT NULL, gporar_type VARCHAR(256) NOT NULL, gporar_username VARCHAR(256), gporar_consent TINYINT(1) DEFAULT 0, gporar_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gporar_client_id ON gpo_rar(gporar_client_id); CREATE INDEX i_gporar_type ON gpo_rar(gporar_type); CREATE INDEX i_gporar_username ON gpo_rar(gporar_username); CREATE TABLE gpo_par ( gpop_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpop_plugin_name VARCHAR(256) NOT NULL, gpop_response_type VARCHAR(128) NOT NULL, gpop_state BLOB, gpop_username VARCHAR(256), gpop_client_id VARCHAR(256) NOT NULL, gpop_redirect_uri VARCHAR(512) NOT NULL, gpop_request_uri_hash VARCHAR(512) NOT NULL, gpop_nonce VARCHAR(512), gpop_code_challenge VARCHAR(128), gpop_resource VARCHAR(512), gpop_claims_request BLOB DEFAULT NULL, gpop_authorization_details BLOB DEFAULT NULL, gpop_additional_parameters BLOB DEFAULT NULL, gpop_status TINYINT(1) DEFAULT 0, -- 0 created, 1 validated, 2 completed gpop_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpop_issued_for VARCHAR(256), -- IP address or hostname gpop_user_agent VARCHAR(256) ); CREATE INDEX i_gpop_client_id ON gpo_par(gpop_client_id); CREATE INDEX i_gpop_request_uri_hash ON gpo_par(gpop_request_uri_hash); CREATE INDEX i_gpop_code_challenge ON gpo_par(gpop_code_challenge); CREATE TABLE gpo_par_scope ( gpops_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpop_id INT(11), gpops_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpop_id) REFERENCES gpo_par(gpop_id) ON DELETE CASCADE ); CREATE TABLE gpo_ciba ( gpob_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpob_plugin_name VARCHAR(256) NOT NULL, gpob_client_id VARCHAR(256) NOT NULL, gpob_x5t_s256 VARCHAR(64), gpob_username VARCHAR(256) NOT NULL, gpob_client_notification_token VARCHAR(1024), gpob_jti_hash VARCHAR(512), gpob_auth_req_id VARCHAR(128), gpob_user_req_id VARCHAR(128), gpob_binding_message VARCHAR(256), gpob_sid VARCHAR(128), gpob_status TINYINT(1) DEFAULT 0, -- 0: created, 1: accepted, 2: error, 3: closed gpob_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpob_issued_for VARCHAR(256), -- IP address or hostname gpob_user_agent VARCHAR(256), gpob_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gpob_client_id ON gpo_ciba(gpob_client_id); CREATE INDEX i_gpob_jti_hash ON gpo_ciba(gpob_jti_hash); CREATE INDEX i_gpob_client_notification_token ON gpo_ciba(gpob_client_notification_token); CREATE INDEX i_gpob_auth_req_id ON gpo_ciba(gpob_auth_req_id); CREATE INDEX i_gpob_user_req_id ON gpo_ciba(gpob_user_req_id); CREATE TABLE gpo_ciba_scope ( gpocs_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpob_id INT(11), gpops_scope VARCHAR(128) NOT NULL, gpobs_granted TINYINT(1) DEFAULT 0, FOREIGN KEY(gpob_id) REFERENCES gpo_ciba(gpob_id) ON DELETE CASCADE ); CREATE TABLE gpo_ciba_scheme ( gpobh_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpob_id INT(11), gpobh_scheme_module VARCHAR(128) NOT NULL, FOREIGN KEY(gpob_id) REFERENCES gpo_ciba(gpob_id) ON DELETE CASCADE ); CREATE TABLE gs_code ( gsc_id INT(11) PRIMARY KEY AUTO_INCREMENT, gsc_mod_name VARCHAR(128) NOT NULL, gsc_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gsc_username VARCHAR(128) NOT NULL, gsc_enabled TINYINT(1) DEFAULT 1, gsc_code_hash VARCHAR(128), gsc_result TINYINT(1) DEFAULT 0 ); CREATE INDEX i_gssc_username ON gs_code(gsc_username); CREATE TABLE gs_webauthn_user ( gswu_id INT(11) PRIMARY KEY AUTO_INCREMENT, gswu_mod_name VARCHAR(128) NOT NULL, gswu_username VARCHAR(128) NOT NULL, gswu_user_id VARCHAR(128) NOT NULL ); CREATE INDEX i_gswu_username ON gs_webauthn_user(gswu_username); CREATE TABLE gs_webauthn_credential ( gswc_id INT(11) PRIMARY KEY AUTO_INCREMENT, gswu_id INT(11) NOT NULL, gswc_session_hash VARCHAR(128) NOT NULL, gswc_name VARCHAR(128), gswc_challenge_hash VARCHAR(128), gswc_credential_id VARCHAR(256), gswc_certificate VARCHAR(128), gswc_public_key TEXT DEFAULT NULL, gswc_counter INT(11) DEFAULT 0, gswc_created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gswc_status TINYINT(1) DEFAULT 0, -- 0 new, 1 registered, 2 error, 3 disabled, 4 removed FOREIGN KEY(gswu_id) REFERENCES gs_webauthn_user(gswu_id) ON DELETE CASCADE ); CREATE INDEX i_gswc_credential_id ON gs_webauthn_credential(gswc_credential_id); CREATE INDEX i_gswc_session_hash ON gs_webauthn_credential(gswc_session_hash); CREATE TABLE gs_webauthn_assertion ( gswa_id INT(11) PRIMARY KEY AUTO_INCREMENT, gswu_id INT(11) NOT NULL, gswc_id INT(11), gswa_session_hash VARCHAR(128) NOT NULL, gswa_challenge_hash VARCHAR(128), gswa_counter INT(11) DEFAULT 0, gswa_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gswa_status TINYINT(1) DEFAULT 0, -- 0 new, 1 verified, 2 not verified, 3 error gswa_mock TINYINT(1) DEFAULT 0, FOREIGN KEY(gswu_id) REFERENCES gs_webauthn_user(gswu_id) ON DELETE CASCADE, FOREIGN KEY(gswc_id) REFERENCES gs_webauthn_credential(gswc_id) ON DELETE CASCADE ); CREATE INDEX i_gswa_session_hash ON gs_webauthn_assertion(gswa_session_hash); CREATE TABLE gs_otp ( gso_id INT(11) PRIMARY KEY AUTO_INCREMENT, gso_mod_name VARCHAR(128) NOT NULL, gso_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gso_last_used TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gso_username VARCHAR(128) NOT NULL, gso_otp_type TINYINT(1) DEFAULT 0, -- 0 HOTP, 1 TOTP gso_secret VARCHAR(128) NOT NULL, gso_hotp_moving_factor INT(11), gso_totp_time_step_size INT(11) ); CREATE INDEX i_gsso_username ON gs_otp(gso_username); CREATE TABLE gs_user_certificate ( gsuc_id INT(11) PRIMARY KEY AUTO_INCREMENT, gsuc_mod_name VARCHAR(128) NOT NULL, gsuc_username VARCHAR(128) NOT NULL, gsuc_enabled TINYINT(1) DEFAULT 1, gsuc_x509_certificate_content BLOB DEFAULT NULL, gsuc_x509_certificate_id VARCHAR(128) NOT NULL, gsuc_x509_certificate_dn VARCHAR(512) NOT NULL, gsuc_x509_certificate_issuer_dn VARCHAR(512) NOT NULL, gsuc_activation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gsuc_expiration TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gsuc_last_used TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gsuc_last_user_agent VARCHAR(512) DEFAULT NULL ); CREATE INDEX i_gsuc_username ON gs_user_certificate(gsuc_username); CREATE INDEX i_gsuc_x509_certificate_id ON gs_user_certificate(gsuc_x509_certificate_id); CREATE TABLE gpr_session ( gprs_id INT(11) PRIMARY KEY AUTO_INCREMENT, gprs_plugin_name VARCHAR(256) NOT NULL, gprs_username VARCHAR(256) NOT NULL, gprs_name VARCHAR(512), gprs_email VARCHAR(512), gprs_code_hash VARCHAR(512), gprs_callback_url BLOB DEFAULT NULL, gprs_password_set TINYINT(1) DEFAULT 0, gprs_session_hash VARCHAR(512), gprs_token_hash VARCHAR(512), gprs_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprs_issued_for VARCHAR(256), -- IP address or hostname gprs_user_agent VARCHAR(256), gprs_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gprs_session_hash ON gpr_session(gprs_session_hash); CREATE INDEX i_gprs_gprs_token_hash ON gpr_session(gprs_token_hash); CREATE INDEX i_gprs_gprs_gprs_code_hash ON gpr_session(gprs_code_hash); CREATE TABLE gpr_update_email ( gprue_id INT(11) PRIMARY KEY AUTO_INCREMENT, gprue_plugin_name VARCHAR(256) NOT NULL, gprue_username VARCHAR(256) NOT NULL, gprue_email VARCHAR(512), gprue_token_hash VARCHAR(512), gprue_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprue_issued_for VARCHAR(256), -- IP address or hostname gprue_user_agent VARCHAR(256), gprue_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gprue_token_hash ON gpr_update_email(gprue_token_hash); CREATE TABLE gpr_reset_credentials_session ( gprrcs_id INT(11) PRIMARY KEY AUTO_INCREMENT, gprrcs_plugin_name VARCHAR(256) NOT NULL, gprrcs_username VARCHAR(256) NOT NULL, gprrcs_session_hash VARCHAR(512), gprrcs_callback_url BLOB DEFAULT NULL, gprrcs_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprrcs_issued_for VARCHAR(256), -- IP address or hostname gprrcs_user_agent VARCHAR(256), gprrcs_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gprrcs_session_hash ON gpr_reset_credentials_session(gprrcs_session_hash); CREATE TABLE gpr_reset_credentials_email ( gprrct_id INT(11) PRIMARY KEY AUTO_INCREMENT, gprrct_plugin_name VARCHAR(256) NOT NULL, gprrct_username VARCHAR(256) NOT NULL, gprrct_token_hash VARCHAR(512), gprrct_callback_url BLOB DEFAULT NULL, gprrct_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprrct_issued_for VARCHAR(256), -- IP address or hostname gprrct_user_agent VARCHAR(256), gprrct_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gprrct_token_hash ON gpr_reset_credentials_email(gprrct_token_hash); CREATE TABLE gs_oauth2_registration ( gsor_id INT(11) PRIMARY KEY AUTO_INCREMENT, gsor_mod_name VARCHAR(128) NOT NULL, gsor_provider VARCHAR(128) NOT NULL, gsor_created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gsor_username VARCHAR(128) NOT NULL, gsor_userinfo_sub VARCHAR(128) ); CREATE INDEX i_gsor_username ON gs_oauth2_registration(gsor_username); CREATE TABLE gs_oauth2_session ( gsos_id INT(11) PRIMARY KEY AUTO_INCREMENT, gsor_id INT(11), gsos_created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gsos_expires_at TIMESTAMP, gsos_state TEXT NOT NULL, gsos_session_export TEXT, gsos_status TINYINT(1) DEFAULT 0, -- 0: registration, 1: authentication, 2: verified, 3: cancelled FOREIGN KEY(gsor_id) REFERENCES gs_oauth2_registration(gsor_id) ON DELETE CASCADE ); INSERT INTO g_scope (gs_name, gs_display_name, gs_description, gs_password_required, gs_password_max_age) VALUES ('g_admin', 'Glewlwyd administration', 'Access to Glewlwyd''s administration API', 1, 600); INSERT INTO g_scope (gs_name, gs_display_name, gs_description, gs_password_required, gs_password_max_age) VALUES ('g_profile', 'Glewlwyd profile', 'Access to the user''s profile API', 1, 600); INSERT INTO g_scope (gs_name, gs_display_name, gs_description, gs_password_required, gs_password_max_age) VALUES ('openid', 'Open ID', 'Open ID Connect scope', 0, 0); INSERT INTO g_user_module_instance (gumi_module, gumi_order, gumi_name, gumi_display_name, gumi_parameters, gumi_readonly) VALUES ('database', 0, 'database', 'Database backend', '{"use-glewlwyd-connection":true,"data-format":{"picture":{"multiple":false,"read":true,"write":true,"profile-read":true,"profile-write":true},"reset-credentials-code":{"multiple":false,"read":true,"write":true,"profile-read":false,"profile-write":false}}}', 0); INSERT INTO g_client_module_instance (gcmi_module, gcmi_order, gcmi_name, gcmi_display_name, gcmi_parameters, gcmi_readonly) VALUES ('database', 0, 'database', 'Database backend', '{"use-glewlwyd-connection":true,"data-format":{"redirect_uri":{"multiple":true,"read":true,"write":true},"authorization_type":{"multiple":true,"read":true,"write":true},"response_mode":{"multiple":false,"read":true,"write":true},"sector_identifier_uri":{"multiple":false,"read":true,"write":true},"token_endpoint_auth_method":{"multiple":true,"read":true,"write":true},"client_secret":{"multiple":false,"read":true,"write":true},"jwks":{"convert":"jwks","multiple":false,"read":true,"write":true},"jwks_uri":{"multiple":false,"read":true,"write":true},"pubkey":{"multiple":false,"read":true,"write":true},"enc":{"multiple":false,"read":true,"write":true},"alg":{"multiple":false,"read":true,"write":true},"alg_kid":{"multiple":false,"read":true,"write":true},"encrypt_code":{"multiple":false,"read":true,"write":true},"encrypt_at":{"multiple":false,"read":true,"write":true},"encrypt_userinfo":{"multiple":false,"read":true,"write":true},"encrypt_id_token":{"multiple":false,"read":true,"write":true},"encrypt_refresh_token":{"multiple":false,"read":true,"write":true},"resource":{"multiple":true,"read":true,"write":true},"authorization_data_types":{"multiple":true,"read":true,"write":true},"tls_client_auth_san_dns":{"multiple":false,"read":true,"write":true},"tls_client_auth_san_uri":{"multiple":false,"read":true,"write":true},"tls_client_auth_san_ip":{"multiple":false,"read":true,"write":true},"tls_client_auth_san_email":{"multiple":false,"read":true,"write":true},"backchannel_token_delivery_mode":{"multiple":false,"read":true,"write":true},"backchannel_client_notification_endpoint":{"multiple":false,"read":true,"write":true},"backchannel_user_code_parameter":{"multiple":false,"read":true,"write":true},"request_object_signing_alg":{"multiple":false,"read":true,"write":true},"token_endpoint_signing_alg":{"multiple":false,"read":true,"write":true},"backchannel_authentication_request_signing_alg":{"multiple":false,"read":true,"write":true},"request_object_encryption_alg":{"multiple":false,"read":true,"write":true},"request_object_encryption_enc":{"multiple":false,"read":true,"write":true},"token_endpoint_encryption_alg":{"multiple":false,"read":true,"write":true},"token_endpoint_encryption_enc":{"multiple":false,"read":true,"write":true},"backchannel_authentication_request_encryption_alg":{"multiple":false,"read":true,"write":true},"backchannel_authentication_request_encryption_enc":{"multiple":false,"read":true,"write":true},"post_logout_redirect_uri":{"multiple":false,"read":true,"write":true},"frontchannel_logout_uri":{"multiple":false,"read":true,"write":true},"frontchannel_logout_session_required":{"multiple":false,"read":true,"write":true},"backchannel_logout_uri":{"multiple":false,"read":true,"write":true},"backchannel_logout_session_required":{"multiple":false,"read":true,"write":true}}}', 0); INSERT INTO g_user (gu_username, gu_name, gu_email, gu_enabled) VALUES ('admin', 'The Administrator', '', 1); INSERT INTO g_user_password (gu_id, guw_password) VALUES ((SELECT gu_id from g_user WHERE gu_username='admin'), PASSWORD('password')); INSERT INTO g_user_scope (gus_name) VALUES ('g_admin'); INSERT INTO g_user_scope (gus_name) VALUES ('g_profile'); INSERT INTO g_user_scope_user (gu_id, gus_id) VALUES ((SELECT gu_id from g_user WHERE gu_username='admin'), (SELECT gus_id FROM g_user_scope WHERE gus_name='g_admin')); INSERT INTO g_user_scope_user (gu_id, gus_id) VALUES ((SELECT gu_id from g_user WHERE gu_username='admin'), (SELECT gus_id FROM g_user_scope WHERE gus_name='g_profile')); ��������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/database/init.postgre.sql�������������������������������������������������������0000664�0000000�0000000�00000104417�14156463140�0021233�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-- ------------------------------------------------------ -- -- PostgreSQL Database -- -- Initialize Glewlwyd Database for the backend server -- -- The administration client app -- -- Copyright 2020-2021 Nicolas Mora <mail@babelouest.org> -- -- License: MIT -- -- ------------------------------------------------------ -- DROP TABLE IF EXISTS g_api_key; DROP TABLE IF EXISTS g_client_user_scope; DROP TABLE IF EXISTS g_scope_group_auth_scheme_module_instance; DROP TABLE IF EXISTS g_scope_group; DROP TABLE IF EXISTS g_user_session_scheme; DROP TABLE IF EXISTS g_scope; DROP TABLE IF EXISTS g_plugin_module_instance; DROP TABLE IF EXISTS g_user_module_instance; DROP TABLE IF EXISTS g_user_middleware_module_instance; DROP TABLE IF EXISTS g_user_auth_scheme_module_instance; DROP TABLE IF EXISTS g_client_module_instance; DROP TABLE IF EXISTS g_user_session; DROP TABLE IF EXISTS g_client_property; DROP TABLE IF EXISTS g_client_scope_client; DROP TABLE IF EXISTS g_client_scope; DROP TABLE IF EXISTS g_client; DROP TABLE IF EXISTS g_user_property; DROP TABLE IF EXISTS g_user_scope_user; DROP TABLE IF EXISTS g_user_scope; DROP TABLE IF EXISTS g_user_password; DROP TABLE IF EXISTS g_user; DROP TABLE IF EXISTS gpg_device_authorization_scope; DROP TABLE IF EXISTS gpg_device_authorization; DROP TABLE IF EXISTS gpg_access_token_scope; DROP TABLE IF EXISTS gpg_access_token; DROP TABLE IF EXISTS gpg_refresh_token_scope; DROP TABLE IF EXISTS gpg_refresh_token; DROP TABLE IF EXISTS gpg_code_scope; DROP TABLE IF EXISTS gpg_code; DROP TABLE IF EXISTS gpo_ciba_scope; DROP TABLE IF EXISTS gpo_ciba_scheme; DROP TABLE IF EXISTS gpo_ciba; DROP TABLE IF EXISTS gpo_par_scope; DROP TABLE IF EXISTS gpo_par; DROP TABLE IF EXISTS gpo_rar; DROP TABLE IF EXISTS gpo_dpop; DROP TABLE IF EXISTS gpo_device_scheme; DROP TABLE IF EXISTS gpo_device_authorization_scope; DROP TABLE IF EXISTS gpo_device_authorization; DROP TABLE IF EXISTS gpo_client_registration; DROP TABLE IF EXISTS gpo_subject_identifier; DROP TABLE IF EXISTS gpo_id_token; DROP TABLE IF EXISTS gpo_access_token_scope; DROP TABLE IF EXISTS gpo_access_token; DROP TABLE IF EXISTS gpo_refresh_token_scope; DROP TABLE IF EXISTS gpo_refresh_token; DROP TABLE IF EXISTS gpo_code_scheme; DROP TABLE IF EXISTS gpo_code_scope; DROP TABLE IF EXISTS gpo_code; DROP TABLE IF EXISTS gpo_client_token_request; DROP TABLE IF EXISTS gs_code; DROP TABLE IF EXISTS gs_webauthn_assertion; DROP TABLE IF EXISTS gs_webauthn_credential; DROP TABLE IF EXISTS gs_webauthn_user; DROP TABLE IF EXISTS gs_otp; DROP TABLE IF EXISTS gs_user_certificate; DROP TABLE IF EXISTS gpr_reset_credentials_email; DROP TABLE IF EXISTS gpr_reset_credentials_session; DROP TABLE IF EXISTS gpr_update_email; DROP TABLE IF EXISTS gpr_session; DROP TABLE IF EXISTS gs_oauth2_session; DROP TABLE IF EXISTS gs_oauth2_registration; CREATE TABLE g_user_module_instance ( gumi_id SERIAL PRIMARY KEY, gumi_module VARCHAR(128) NOT NULL, gumi_order INTEGER NOT NULL, gumi_name VARCHAR(128) NOT NULL, gumi_display_name VARCHAR(256) DEFAULT '', gumi_parameters TEXT, gumi_readonly SMALLINT DEFAULT 0, gumi_multiple_passwords SMALLINT DEFAULT 0, gumi_enabled SMALLINT DEFAULT 1 ); CREATE TABLE g_user_middleware_module_instance ( gummi_id SERIAL PRIMARY KEY, gummi_module VARCHAR(128) NOT NULL, gummi_order INTEGER NOT NULL, gummi_name VARCHAR(128) NOT NULL, gummi_display_name VARCHAR(256) DEFAULT '', gummi_parameters TEXT, gummi_enabled SMALLINT DEFAULT 1 ); CREATE TABLE g_user_auth_scheme_module_instance ( guasmi_id SERIAL PRIMARY KEY, guasmi_module VARCHAR(128) NOT NULL, guasmi_expiration INTEGER NOT NULL DEFAULT 0, guasmi_max_use INTEGER DEFAULT 0, -- 0: unlimited guasmi_allow_user_register SMALLINT DEFAULT 1, guasmi_forbid_user_profile SMALLINT DEFAULT 0, guasmi_forbid_user_reset_credential SMALLINT DEFAULT 0, guasmi_name VARCHAR(128) NOT NULL, guasmi_display_name VARCHAR(256) DEFAULT '', guasmi_parameters TEXT, guasmi_enabled SMALLINT DEFAULT 1 ); CREATE TABLE g_client_module_instance ( gcmi_id SERIAL PRIMARY KEY, gcmi_module VARCHAR(128) NOT NULL, gcmi_order INTEGER NOT NULL, gcmi_name VARCHAR(128) NOT NULL, gcmi_display_name VARCHAR(256) DEFAULT '', gcmi_parameters TEXT, gcmi_readonly SMALLINT DEFAULT 0, gcmi_enabled SMALLINT DEFAULT 1 ); CREATE TABLE g_plugin_module_instance ( gpmi_id SERIAL PRIMARY KEY, gpmi_module VARCHAR(128) NOT NULL, gpmi_name VARCHAR(128) NOT NULL, gpmi_display_name VARCHAR(256) DEFAULT '', gpmi_parameters TEXT, gpmi_enabled SMALLINT DEFAULT 1 ); CREATE TABLE g_user_session ( gus_id SERIAL PRIMARY KEY, gus_session_hash VARCHAR(128) NOT NULL, gus_user_agent VARCHAR(256), gus_issued_for VARCHAR(256), -- IP address or hostname gus_username VARCHAR(256) NOT NULL, gus_expiration TIMESTAMPTZ NOT NULL DEFAULT NOW(), gus_last_login TIMESTAMPTZ NOT NULL DEFAULT NOW(), gus_current SMALLINT, gus_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_g_user_session_username ON g_user_session(gus_username); CREATE INDEX i_g_user_session_last_login ON g_user_session(gus_last_login); CREATE INDEX i_g_user_session_expiration ON g_user_session(gus_expiration); CREATE TABLE g_user_session_scheme ( guss_id SERIAL PRIMARY KEY, gus_id INTEGER NOT NULL, guasmi_id INTEGER DEFAULT NULL, -- NULL means scheme 'password' guss_expiration TIMESTAMPTZ NOT NULL DEFAULT NOW(), guss_last_login TIMESTAMPTZ NOT NULL DEFAULT NOW(), guss_use_counter INTEGER DEFAULT 0, guss_enabled SMALLINT DEFAULT 1, FOREIGN KEY(gus_id) REFERENCES g_user_session(gus_id) ON DELETE CASCADE, FOREIGN KEY(guasmi_id) REFERENCES g_user_auth_scheme_module_instance(guasmi_id) ON DELETE CASCADE ); CREATE INDEX i_g_user_session_scheme_last_login ON g_user_session_scheme(guss_last_login); CREATE INDEX i_g_user_session_scheme_expiration ON g_user_session_scheme(guss_expiration); CREATE TABLE g_scope ( gs_id SERIAL PRIMARY KEY, gs_name VARCHAR(128) NOT NULL UNIQUE, gs_display_name VARCHAR(256) DEFAULT '', gs_description VARCHAR(512), gs_password_required SMALLINT DEFAULT 1, gs_password_max_age INTEGER DEFAULT 0, gs_enabled SMALLINT DEFAULT 1 ); CREATE TABLE g_scope_group ( gsg_id SERIAL PRIMARY KEY, gs_id INTEGER, gsg_name VARCHAR(128) NOT NULL, gsg_scheme_required INTEGER DEFAULT 1, FOREIGN KEY(gs_id) REFERENCES g_scope(gs_id) ON DELETE CASCADE ); CREATE TABLE g_scope_group_auth_scheme_module_instance ( gsgasmi_id SERIAL PRIMARY KEY, gsg_id INTEGER NOT NULL, guasmi_id INTEGER NOT NULL, FOREIGN KEY(gsg_id) REFERENCES g_scope_group(gsg_id) ON DELETE CASCADE, FOREIGN KEY(guasmi_id) REFERENCES g_user_auth_scheme_module_instance(guasmi_id) ON DELETE CASCADE ); CREATE TABLE g_client_user_scope ( gcus_id SERIAL PRIMARY KEY, gs_id INTEGER NOT NULL, gcus_username VARCHAR(256) NOT NULL, gcus_client_id VARCHAR(256) NOT NULL, gcus_granted TIMESTAMPTZ NOT NULL DEFAULT NOW(), gcus_enabled SMALLINT DEFAULT 1, FOREIGN KEY(gs_id) REFERENCES g_scope(gs_id) ON DELETE CASCADE ); CREATE INDEX i_g_client_user_scope_username ON g_client_user_scope(gcus_username); CREATE INDEX i_g_client_user_scope_client_id ON g_client_user_scope(gcus_client_id); CREATE TABLE g_api_key ( gak_id SERIAL PRIMARY KEY, gak_token_hash VARCHAR(512) NOT NULL, gak_counter INTEGER DEFAULT 0, gak_username VARCHAR(256) NOT NULL, gak_issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gak_issued_for VARCHAR(256), -- IP address or hostname gak_user_agent VARCHAR(256), gak_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gak_token_hash ON g_api_key(gak_token_hash); CREATE TABLE g_client ( gc_id SERIAL PRIMARY KEY, gc_client_id VARCHAR(128) NOT NULL UNIQUE, gc_name VARCHAR(256) DEFAULT '', gc_description VARCHAR(512) DEFAULT '', gc_confidential SMALLINT DEFAULT 0, gc_password VARCHAR(256), gc_enabled SMALLINT DEFAULT 1 ); CREATE TABLE g_client_scope ( gcs_id SERIAL PRIMARY KEY, gcs_name VARCHAR(128) NOT NULL UNIQUE ); CREATE TABLE g_client_scope_client ( gcsu_id SERIAL PRIMARY KEY, gc_id SERIAL, gcs_id SERIAL, FOREIGN KEY(gc_id) REFERENCES g_client(gc_id) ON DELETE CASCADE, FOREIGN KEY(gcs_id) REFERENCES g_client_scope(gcs_id) ON DELETE CASCADE ); CREATE TABLE g_client_property ( gcp_id SERIAL PRIMARY KEY, gc_id SERIAL, gcp_name VARCHAR(128) NOT NULL, gcp_value TEXT DEFAULT NULL, FOREIGN KEY(gc_id) REFERENCES g_client(gc_id) ON DELETE CASCADE ); CREATE INDEX i_g_client_property_name ON g_client_property(gcp_name); CREATE TABLE g_user ( gu_id SERIAL PRIMARY KEY, gu_username VARCHAR(128) NOT NULL UNIQUE, gu_name VARCHAR(256) DEFAULT '', gu_email VARCHAR(512) DEFAULT '', gu_enabled SMALLINT DEFAULT 1 ); CREATE TABLE g_user_scope ( gus_id SERIAL PRIMARY KEY, gus_name VARCHAR(128) NOT NULL UNIQUE ); CREATE TABLE g_user_scope_user ( gusu_id SERIAL PRIMARY KEY, gu_id SERIAL, gus_id SERIAL, FOREIGN KEY(gu_id) REFERENCES g_user(gu_id) ON DELETE CASCADE, FOREIGN KEY(gus_id) REFERENCES g_user_scope(gus_id) ON DELETE CASCADE ); CREATE TABLE g_user_property ( gup_id SERIAL PRIMARY KEY, gu_id SERIAL, gup_name VARCHAR(128) NOT NULL, gup_value TEXT DEFAULT NULL, FOREIGN KEY(gu_id) REFERENCES g_user(gu_id) ON DELETE CASCADE ); CREATE INDEX i_g_user_property_name ON g_user_property(gup_name); CREATE TABLE g_user_password ( guw_id SERIAL PRIMARY KEY, gu_id SERIAL, guw_password VARCHAR(256), FOREIGN KEY(gu_id) REFERENCES g_user(gu_id) ON DELETE CASCADE ); CREATE TABLE gpg_code ( gpgc_id SERIAL PRIMARY KEY, gpgc_plugin_name VARCHAR(256) NOT NULL, gpgc_username VARCHAR(256) NOT NULL, gpgc_client_id VARCHAR(256) NOT NULL, gpgc_redirect_uri VARCHAR(512) NOT NULL, gpgc_code_hash VARCHAR(512) NOT NULL, gpgc_expires_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gpgc_issued_for VARCHAR(256), -- IP address or hostname gpgc_user_agent VARCHAR(256), gpgc_code_challenge VARCHAR(128), gpgc_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gpgc_code_hash ON gpg_code(gpgc_code_hash); CREATE INDEX i_gpgc_code_challenge ON gpg_code(gpgc_code_challenge); CREATE TABLE gpg_code_scope ( gpgcs_id SERIAL PRIMARY KEY, gpgc_id INTEGER, gpgcs_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpgc_id) REFERENCES gpg_code(gpgc_id) ON DELETE CASCADE ); CREATE TABLE gpg_refresh_token ( gpgr_id SERIAL PRIMARY KEY, gpgr_plugin_name VARCHAR(256) NOT NULL, gpgr_authorization_type SMALLINT NOT NULL, -- 0: Authorization Code Grant, 1: Implicit Grant, 2: Resource Owner Password Credentials Grant, 3: Client Credentials Grant gpgc_id INTEGER DEFAULT NULL, gpgr_username VARCHAR(256) NOT NULL, gpgr_client_id VARCHAR(256), gpgr_issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gpgr_expires_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gpgr_last_seen TIMESTAMPTZ NOT NULL DEFAULT NOW(), gpgr_duration INTEGER, gpgr_rolling_expiration SMALLINT DEFAULT 0, gpgr_issued_for VARCHAR(256), -- IP address or hostname gpgr_user_agent VARCHAR(256), gpgr_token_hash VARCHAR(512) NOT NULL, gpgr_enabled SMALLINT DEFAULT 1, FOREIGN KEY(gpgc_id) REFERENCES gpg_code(gpgc_id) ON DELETE CASCADE ); CREATE INDEX i_gpgr_token_hash ON gpg_refresh_token(gpgr_token_hash); CREATE TABLE gpg_refresh_token_scope ( gpgrs_id SERIAL PRIMARY KEY, gpgr_id INTEGER, gpgrs_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpgr_id) REFERENCES gpg_refresh_token(gpgr_id) ON DELETE CASCADE ); -- Access token table, to store meta information on access token sent CREATE TABLE gpg_access_token ( gpga_id SERIAL PRIMARY KEY, gpga_plugin_name VARCHAR(256) NOT NULL, gpga_authorization_type SMALLINT NOT NULL, -- 0: Authorization Code Grant, 1: Implicit Grant, 2: Resource Owner Password Credentials Grant, 3: Client Credentials Grant gpgr_id INTEGER DEFAULT NULL, gpga_username VARCHAR(256), gpga_client_id VARCHAR(256), gpga_issued_at TIMESTAMPTZ DEFAULT NOW(), gpga_issued_for VARCHAR(256), -- IP address or hostname gpga_user_agent VARCHAR(256), gpga_token_hash VARCHAR(512) NOT NULL, gpga_enabled SMALLINT DEFAULT 1, FOREIGN KEY(gpgr_id) REFERENCES gpg_refresh_token(gpgr_id) ON DELETE CASCADE ); CREATE INDEX i_gpga_token_hash ON gpg_access_token(gpga_token_hash); CREATE TABLE gpg_access_token_scope ( gpgas_id SERIAL PRIMARY KEY, gpga_id INTEGER, gpgas_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpga_id) REFERENCES gpg_access_token(gpga_id) ON DELETE CASCADE ); -- store device authorization requests CREATE TABLE gpg_device_authorization ( gpgda_id SERIAL PRIMARY KEY, gpgda_plugin_name VARCHAR(256) NOT NULL, gpgda_client_id VARCHAR(256) NOT NULL, gpgda_username VARCHAR(256), gpgda_created_at TIMESTAMPTZ DEFAULT NOW(), gpgda_expires_at TIMESTAMPTZ DEFAULT NOW(), gpgda_issued_for VARCHAR(256), -- IP address or hostname of the deice client gpgda_device_code_hash VARCHAR(512) NOT NULL, gpgda_user_code_hash VARCHAR(512) NOT NULL, gpgda_status SMALLINT DEFAULT 0, -- 0: created, 1: user verified, 2 device completed, 3 disabled gpgda_last_check TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX i_gpgda_device_code_hash ON gpg_device_authorization(gpgda_device_code_hash); CREATE INDEX i_gpgda_user_code_hash ON gpg_device_authorization(gpgda_user_code_hash); CREATE TABLE gpg_device_authorization_scope ( gpgdas_id SERIAL PRIMARY KEY, gpgda_id INTEGER, gpgdas_scope VARCHAR(128) NOT NULL, gpgdas_allowed SMALLINT DEFAULT 0, FOREIGN KEY(gpgda_id) REFERENCES gpg_device_authorization(gpgda_id) ON DELETE CASCADE ); CREATE TABLE gpo_code ( gpoc_id SERIAL PRIMARY KEY, gpoc_plugin_name VARCHAR(256) NOT NULL, gpoc_authorization_type SMALLINT NOT NULL, gpoc_username VARCHAR(256) NOT NULL, gpoc_client_id VARCHAR(256) NOT NULL, gpoc_resource VARCHAR(512), gpoc_redirect_uri VARCHAR(512) NOT NULL, gpoc_code_hash VARCHAR(512) NOT NULL, gpoc_nonce VARCHAR(512), gpoc_claims_request TEXT DEFAULT NULL, gpoc_authorization_details TEXT DEFAULT NULL, gpoc_s_hash VARCHAR(512), gpoc_sid VARCHAR(128), gpoc_expires_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gpoc_issued_for VARCHAR(256), -- IP address or hostname gpoc_user_agent VARCHAR(256), gpoc_code_challenge VARCHAR(128), gpoc_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gpoc_code_hash ON gpo_code(gpoc_code_hash); CREATE INDEX i_gpoc_code_challenge ON gpo_code(gpoc_code_challenge); CREATE TABLE gpo_code_scope ( gpocs_id SERIAL PRIMARY KEY, gpoc_id INTEGER, gpocs_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE ); CREATE TABLE gpo_code_scheme ( gpoch_id SERIAL PRIMARY KEY, gpoc_id INTEGER, gpoch_scheme_module VARCHAR(128) NOT NULL, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE ); CREATE TABLE gpo_refresh_token ( gpor_id SERIAL PRIMARY KEY, gpor_plugin_name VARCHAR(256) NOT NULL, gpor_authorization_type SMALLINT NOT NULL, gpoc_id INTEGER DEFAULT NULL, gpor_username VARCHAR(256) NOT NULL, gpor_client_id VARCHAR(256), gpor_resource VARCHAR(512), gpor_claims_request TEXT DEFAULT NULL, gpor_authorization_details TEXT DEFAULT NULL, gpor_issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gpor_expires_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gpor_last_seen TIMESTAMPTZ NOT NULL DEFAULT NOW(), gpor_duration INTEGER, gpor_rolling_expiration SMALLINT DEFAULT 0, gpor_issued_for VARCHAR(256), -- IP address or hostname gpor_user_agent VARCHAR(256), gpor_token_hash VARCHAR(512) NOT NULL, gpor_jti VARCHAR(128), gpor_dpop_jkt VARCHAR(512), gpor_enabled SMALLINT DEFAULT 1, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE ); CREATE INDEX i_gpor_token_hash ON gpo_refresh_token(gpor_token_hash); CREATE INDEX i_gpor_jti ON gpo_refresh_token(gpor_jti); CREATE TABLE gpo_refresh_token_scope ( gpors_id SERIAL PRIMARY KEY, gpor_id INTEGER, gpors_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpor_id) REFERENCES gpo_refresh_token(gpor_id) ON DELETE CASCADE ); -- Access token table, to store meta information on access token sent CREATE TABLE gpo_access_token ( gpoa_id SERIAL PRIMARY KEY, gpoa_plugin_name VARCHAR(256) NOT NULL, gpoa_authorization_type SMALLINT NOT NULL, gpor_id INTEGER DEFAULT NULL, gpoa_username VARCHAR(256), gpoa_client_id VARCHAR(256), gpoa_resource VARCHAR(512), gpoa_issued_at TIMESTAMPTZ DEFAULT NOW(), gpoa_issued_for VARCHAR(256), -- IP address or hostname gpoa_user_agent VARCHAR(256), gpoa_token_hash VARCHAR(512) NOT NULL, gpoa_jti VARCHAR(128), gpoa_authorization_details TEXT DEFAULT NULL, gpoa_enabled SMALLINT DEFAULT 1, FOREIGN KEY(gpor_id) REFERENCES gpo_refresh_token(gpor_id) ON DELETE CASCADE ); CREATE INDEX i_gpoa_token_hash ON gpo_access_token(gpoa_token_hash); CREATE INDEX i_gpoa_jti ON gpo_access_token(gpoa_jti); CREATE TABLE gpo_access_token_scope ( gpoas_id SERIAL PRIMARY KEY, gpoa_id INTEGER, gpoas_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpoa_id) REFERENCES gpo_access_token(gpoa_id) ON DELETE CASCADE ); -- Id token table, to store meta information on id token sent CREATE TABLE gpo_id_token ( gpoi_id SERIAL PRIMARY KEY, gpoc_id INTEGER, gpor_id INTEGER, gpoi_plugin_name VARCHAR(256) NOT NULL, gpoi_authorization_type SMALLINT NOT NULL, gpoi_username VARCHAR(256), gpoi_client_id VARCHAR(256), gpoi_issued_at TIMESTAMPTZ DEFAULT NOW(), gpoi_issued_for VARCHAR(256), -- IP address or hostname gpoi_user_agent VARCHAR(256), gpoi_hash VARCHAR(512), gpoi_sid VARCHAR(128), gpoi_enabled SMALLINT DEFAULT 1, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE, FOREIGN KEY(gpor_id) REFERENCES gpo_refresh_token(gpor_id) ON DELETE CASCADE ); CREATE INDEX i_gpoi_hash ON gpo_id_token(gpoi_hash); -- subject identifier table to store subs and their relations to usernames, client_id and sector_identifier CREATE TABLE gpo_subject_identifier ( gposi_id SERIAL PRIMARY KEY, gposi_plugin_name VARCHAR(256) NOT NULL, gposi_username VARCHAR(256) NOT NULL, gposi_client_id VARCHAR(256), gposi_sector_identifier_uri VARCHAR(256), gposi_sub VARCHAR(256) NOT NULL ); CREATE INDEX i_gposi_sub ON gpo_subject_identifier(gposi_sub); -- store meta information on client registration CREATE TABLE gpo_client_registration ( gpocr_id SERIAL PRIMARY KEY, gpocr_plugin_name VARCHAR(256) NOT NULL, gpocr_cient_id VARCHAR(256) NOT NULL, gpocr_management_at_hash VARCHAR(512), gpocr_created_at TIMESTAMPTZ DEFAULT NOW(), gpoa_id INTEGER, gpocr_issued_for VARCHAR(256), -- IP address or hostname gpocr_user_agent VARCHAR(256), FOREIGN KEY(gpoa_id) REFERENCES gpo_access_token(gpoa_id) ON DELETE CASCADE ); CREATE INDEX i_gpocr_management_at_hash ON gpo_client_registration(gpocr_management_at_hash); -- store meta information about client request on token endpoint CREATE TABLE gpo_client_token_request ( gpoctr_id SERIAL PRIMARY KEY, gpoctr_plugin_name VARCHAR(256) NOT NULL, gpoctr_cient_id VARCHAR(256) NOT NULL, gpoctr_created_at TIMESTAMPTZ DEFAULT NOW(), gpoctr_issued_for VARCHAR(256), -- IP address or hostname gpoctr_jti_hash VARCHAR(512) ); -- store device authorization requests CREATE TABLE gpo_device_authorization ( gpoda_id SERIAL PRIMARY KEY, gpoda_plugin_name VARCHAR(256) NOT NULL, gpoda_client_id VARCHAR(256) NOT NULL, gpoda_resource VARCHAR(512), gpoda_username VARCHAR(256), gpoda_created_at TIMESTAMPTZ DEFAULT NOW(), gpoda_expires_at TIMESTAMPTZ DEFAULT NOW(), gpoda_issued_for VARCHAR(256), -- IP address or hostname of the deice client gpoda_device_code_hash VARCHAR(512) NOT NULL, gpoda_user_code_hash VARCHAR(512) NOT NULL, gpoda_sid VARCHAR(128), gpoda_status SMALLINT DEFAULT 0, -- 0: created, 1: user verified, 2 device completed, 3 disabled gpoda_authorization_details TEXT DEFAULT NULL, gpoda_last_check TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX i_gpoda_device_code_hash ON gpo_device_authorization(gpoda_device_code_hash); CREATE INDEX i_gpoda_user_code_hash ON gpo_device_authorization(gpoda_user_code_hash); CREATE TABLE gpo_device_authorization_scope ( gpodas_id SERIAL PRIMARY KEY, gpoda_id INTEGER, gpodas_scope VARCHAR(128) NOT NULL, gpodas_allowed SMALLINT DEFAULT 0, FOREIGN KEY(gpoda_id) REFERENCES gpo_device_authorization(gpoda_id) ON DELETE CASCADE ); CREATE TABLE gpo_device_scheme ( gpodh_id SERIAL PRIMARY KEY, gpoda_id INTEGER, gpodh_scheme_module VARCHAR(128) NOT NULL, FOREIGN KEY(gpoda_id) REFERENCES gpo_device_authorization(gpoda_id) ON DELETE CASCADE ); CREATE TABLE gpo_dpop ( gpod_id SERIAL PRIMARY KEY, gpod_plugin_name VARCHAR(256) NOT NULL, gpod_client_id VARCHAR(256) NOT NULL, gpod_jti_hash VARCHAR(512) NOT NULL, gpod_jkt VARCHAR(512) NOT NULL, gpod_htm VARCHAR(128) NOT NULL, gpod_htu VARCHAR(512) NOT NULL, gpod_iat TIMESTAMPTZ NOT NULL, gpod_last_seen TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX i_gpod_jti_hash ON gpo_dpop(gpod_jti_hash); CREATE TABLE gpo_rar ( gporar_id SERIAL PRIMARY KEY, gporar_plugin_name VARCHAR(256) NOT NULL, gporar_client_id VARCHAR(256) NOT NULL, gporar_type VARCHAR(256) NOT NULL, gporar_username VARCHAR(256), gporar_consent SMALLINT DEFAULT 0, gporar_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gporar_client_id ON gpo_rar(gporar_client_id); CREATE INDEX i_gporar_type ON gpo_rar(gporar_type); CREATE INDEX i_gporar_username ON gpo_rar(gporar_username); CREATE TABLE gpo_par ( gpop_id SERIAL PRIMARY KEY, gpop_plugin_name VARCHAR(256) NOT NULL, gpop_response_type VARCHAR(128) NOT NULL, gpop_state TEXT, gpop_username VARCHAR(256), gpop_client_id VARCHAR(256) NOT NULL, gpop_redirect_uri VARCHAR(512) NOT NULL, gpop_request_uri_hash VARCHAR(512) NOT NULL, gpop_nonce VARCHAR(512), gpop_code_challenge VARCHAR(128), gpop_resource VARCHAR(512), gpop_claims_request TEXT DEFAULT NULL, gpop_authorization_details TEXT DEFAULT NULL, gpop_additional_parameters TEXT DEFAULT NULL, gpop_status SMALLINT DEFAULT 0, -- 0 created, 1 validated, 2 completed gpop_expires_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, gpop_issued_for VARCHAR(256), -- IP address or hostname gpop_user_agent VARCHAR(256) ); CREATE INDEX i_gpop_client_id ON gpo_par(gpop_client_id); CREATE INDEX i_gpop_request_uri_hash ON gpo_par(gpop_request_uri_hash); CREATE INDEX i_gpop_code_challenge ON gpo_par(gpop_code_challenge); CREATE TABLE gpo_par_scope ( gpops_id SERIAL PRIMARY KEY, gpop_id INTEGER, gpops_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpop_id) REFERENCES gpo_par(gpop_id) ON DELETE CASCADE ); CREATE TABLE gpo_ciba ( gpob_id SERIAL PRIMARY KEY, gpob_plugin_name VARCHAR(256) NOT NULL, gpob_client_id VARCHAR(256) NOT NULL, gpob_x5t_s256 VARCHAR(64), gpob_username VARCHAR(256) NOT NULL, gpob_client_notification_token VARCHAR(1024), gpob_jti_hash VARCHAR(512), gpob_auth_req_id VARCHAR(128), gpob_user_req_id VARCHAR(128), gpob_binding_message VARCHAR(256), gpob_sid VARCHAR(128), gpob_status SMALLINT DEFAULT 0, -- 0: created, 1: accepted, 2: error, 3: closed gpob_expires_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, gpob_issued_for VARCHAR(256), -- IP address or hostname gpob_user_agent VARCHAR(256), gpob_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gpob_client_id ON gpo_ciba(gpob_client_id); CREATE INDEX i_gpob_jti_hash ON gpo_ciba(gpob_jti_hash); CREATE INDEX i_gpob_client_notification_token ON gpo_ciba(gpob_client_notification_token); CREATE INDEX i_gpob_auth_req_id ON gpo_ciba(gpob_auth_req_id); CREATE INDEX i_gpob_user_req_id ON gpo_ciba(gpob_user_req_id); CREATE TABLE gpo_ciba_scope ( gpocs_id SERIAL PRIMARY KEY, gpob_id INTEGER, gpops_scope VARCHAR(128) NOT NULL, gpobs_granted SMALLINT DEFAULT 0, FOREIGN KEY(gpob_id) REFERENCES gpo_ciba(gpob_id) ON DELETE CASCADE ); CREATE TABLE gpo_ciba_scheme ( gpobh_id SERIAL PRIMARY KEY, gpob_id INTEGER, gpobh_scheme_module VARCHAR(128) NOT NULL, FOREIGN KEY(gpob_id) REFERENCES gpo_ciba(gpob_id) ON DELETE CASCADE ); CREATE TABLE gs_code ( gsc_id SERIAL PRIMARY KEY, gsc_mod_name VARCHAR(128) NOT NULL, gsc_issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gsc_username VARCHAR(128) NOT NULL, gsc_enabled SMALLINT DEFAULT 1, gsc_code_hash VARCHAR(128), gsc_result SMALLINT DEFAULT 0 ); CREATE INDEX i_gsc_username ON gs_code(gsc_username); CREATE TABLE gs_webauthn_user ( gswu_id SERIAL PRIMARY KEY, gswu_mod_name VARCHAR(128) NOT NULL, gswu_username VARCHAR(128) NOT NULL, gswu_user_id VARCHAR(128) NOT NULL ); CREATE INDEX i_gswu_username ON gs_webauthn_user(gswu_username); CREATE TABLE gs_webauthn_credential ( gswc_id SERIAL PRIMARY KEY, gswu_id INTEGER NOT NULL, gswc_session_hash VARCHAR(128) NOT NULL, gswc_name VARCHAR(128), gswc_challenge_hash VARCHAR(128), gswc_credential_id VARCHAR(256), gswc_certificate VARCHAR(128), gswc_public_key TEXT DEFAULT NULL, gswc_counter INTEGER DEFAULT 0, gswc_created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gswc_status SMALLINT DEFAULT 0, -- 0 new, 1 registered, 2 error, 3 disabled, 4 removed FOREIGN KEY(gswu_id) REFERENCES gs_webauthn_user(gswu_id) ON DELETE CASCADE ); CREATE INDEX i_gswc_credential_id ON gs_webauthn_credential(gswc_credential_id); CREATE INDEX i_gswc_session_hash ON gs_webauthn_credential(gswc_session_hash); CREATE TABLE gs_webauthn_assertion ( gswa_id SERIAL PRIMARY KEY, gswu_id INTEGER NOT NULL, gswc_id INTEGER, gswa_session_hash VARCHAR(128) NOT NULL, gswa_challenge_hash VARCHAR(128), gswa_counter INTEGER DEFAULT 0, gswa_issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gswa_status SMALLINT DEFAULT 0, -- 0 new, 1 verified, 2 not verified, 3 error gswa_mock SMALLINT DEFAULT 0, FOREIGN KEY(gswu_id) REFERENCES gs_webauthn_user(gswu_id) ON DELETE CASCADE, FOREIGN KEY(gswc_id) REFERENCES gs_webauthn_credential(gswc_id) ON DELETE CASCADE ); CREATE INDEX i_gswa_session_hash ON gs_webauthn_assertion(gswa_session_hash); CREATE TABLE gs_otp ( gso_id SERIAL PRIMARY KEY, gso_mod_name VARCHAR(128) NOT NULL, gso_issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gso_last_used TIMESTAMPTZ NOT NULL DEFAULT NOW(), gso_username VARCHAR(128) NOT NULL, gso_otp_type SMALLINT DEFAULT 0, -- 0 HOTP, 1 TOTP gso_secret VARCHAR(128) NOT NULL, gso_hotp_moving_factor INTEGER, gso_totp_time_step_size INTEGER ); CREATE INDEX i_gsso_username ON gs_otp(gso_username); CREATE TABLE gs_user_certificate ( gsuc_id SERIAL PRIMARY KEY, gsuc_mod_name VARCHAR(128) NOT NULL, gsuc_username VARCHAR(128) NOT NULL, gsuc_enabled SMALLINT DEFAULT 1, gsuc_x509_certificate_content TEXT DEFAULT NULL, gsuc_x509_certificate_id VARCHAR(128) NOT NULL, gsuc_x509_certificate_dn VARCHAR(512) NOT NULL, gsuc_x509_certificate_issuer_dn VARCHAR(512) NOT NULL, gsuc_activation TIMESTAMPTZ NOT NULL DEFAULT NOW(), gsuc_expiration TIMESTAMPTZ DEFAULT NOW(), gsuc_last_used TIMESTAMPTZ DEFAULT NOW(), gsuc_last_user_agent VARCHAR(512) DEFAULT NULL ); CREATE INDEX i_gsuc_username ON gs_user_certificate(gsuc_username); CREATE INDEX i_gsuc_x509_certificate_id ON gs_user_certificate(gsuc_x509_certificate_id); CREATE TABLE gpr_session ( gprs_id SERIAL PRIMARY KEY, gprs_plugin_name VARCHAR(256) NOT NULL, gprs_username VARCHAR(256) NOT NULL, gprs_name VARCHAR(512), gprs_email VARCHAR(512), gprs_code_hash VARCHAR(512), gprs_callback_url TEXT DEFAULT NULL, gprs_password_set SMALLINT DEFAULT 0, gprs_session_hash VARCHAR(512), gprs_token_hash VARCHAR(512), gprs_expires_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, gprs_issued_for VARCHAR(256), -- IP address or hostname gprs_user_agent VARCHAR(256), gprs_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gprs_session_hash ON gpr_session(gprs_session_hash); CREATE INDEX i_gprs_gprs_token_hash ON gpr_session(gprs_token_hash); CREATE INDEX i_gprs_gprs_gprs_code_hash ON gpr_session(gprs_code_hash); CREATE TABLE gpr_update_email ( gprue_id SERIAL PRIMARY KEY, gprue_plugin_name VARCHAR(256) NOT NULL, gprue_username VARCHAR(256) NOT NULL, gprue_email VARCHAR(512), gprue_token_hash VARCHAR(512), gprue_expires_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, gprue_issued_for VARCHAR(256), -- IP address or hostname gprue_user_agent VARCHAR(256), gprue_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gprue_token_hash ON gpr_update_email(gprue_token_hash); CREATE TABLE gpr_reset_credentials_session ( gprrcs_id SERIAL PRIMARY KEY, gprrcs_plugin_name VARCHAR(256) NOT NULL, gprrcs_username VARCHAR(256) NOT NULL, gprrcs_session_hash VARCHAR(512), gprrcs_callback_url TEXT DEFAULT NULL, gprrcs_expires_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, gprrcs_issued_for VARCHAR(256), -- IP address or hostname gprrcs_user_agent VARCHAR(256), gprrcs_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gprrcs_session_hash ON gpr_reset_credentials_session(gprrcs_session_hash); CREATE TABLE gpr_reset_credentials_email ( gprrct_id SERIAL PRIMARY KEY, gprrct_plugin_name VARCHAR(256) NOT NULL, gprrct_username VARCHAR(256) NOT NULL, gprrct_token_hash VARCHAR(512), gprrct_callback_url TEXT DEFAULT NULL, gprrct_expires_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, gprrct_issued_for VARCHAR(256), -- IP address or hostname gprrct_user_agent VARCHAR(256), gprrct_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gprrct_token_hash ON gpr_reset_credentials_email(gprrct_token_hash); CREATE TABLE gs_oauth2_registration ( gsor_id SERIAL PRIMARY KEY, gsor_mod_name VARCHAR(128) NOT NULL, gsor_provider VARCHAR(128) NOT NULL, gsor_created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gsor_username VARCHAR(128) NOT NULL, gsor_userinfo_sub VARCHAR(128) ); CREATE INDEX i_gsor_username ON gs_oauth2_registration(gsor_username); CREATE TABLE gs_oauth2_session ( gsos_id SERIAL PRIMARY KEY, gsor_id INTEGER, gsos_created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gsos_expires_at TIMESTAMPTZ, gsos_state TEXT NOT NULL, gsos_session_export TEXT, gsos_status SMALLINT DEFAULT 0, -- 0: registration, 1: authentication, 2: verified, 3: cancelled FOREIGN KEY(gsor_id) REFERENCES gs_oauth2_registration(gsor_id) ON DELETE CASCADE ); INSERT INTO g_scope (gs_name, gs_display_name, gs_description, gs_password_required, gs_password_max_age) VALUES ('g_admin', 'Glewlwyd administration', 'Access to Glewlwyd''s administration API', 1, 600); INSERT INTO g_scope (gs_name, gs_display_name, gs_description, gs_password_required, gs_password_max_age) VALUES ('g_profile', 'Glewlwyd profile', 'Access to the user''s profile API', 1, 600); INSERT INTO g_scope (gs_name, gs_display_name, gs_description, gs_password_required, gs_password_max_age) VALUES ('openid', 'Open ID', 'Open ID Connect scope', 0, 0); INSERT INTO g_user_module_instance (gumi_module, gumi_order, gumi_name, gumi_display_name, gumi_parameters, gumi_readonly) VALUES ('database', 0, 'database', 'Database backend', '{"use-glewlwyd-connection":true,"data-format":{"picture":{"multiple":false,"read":true,"write":true,"profile-read":true,"profile-write":true},"reset-credentials-code":{"multiple":false,"read":true,"write":true,"profile-read":false,"profile-write":false}}}', 0); INSERT INTO g_client_module_instance (gcmi_module, gcmi_order, gcmi_name, gcmi_display_name, gcmi_parameters, gcmi_readonly) VALUES ('database', 0, 'database', 'Database backend', '{"use-glewlwyd-connection":true,"data-format":{"redirect_uri":{"multiple":true,"read":true,"write":true},"authorization_type":{"multiple":true,"read":true,"write":true},"response_mode":{"multiple":false,"read":true,"write":true},"sector_identifier_uri":{"multiple":false,"read":true,"write":true},"token_endpoint_auth_method":{"multiple":true,"read":true,"write":true},"client_secret":{"multiple":false,"read":true,"write":true},"jwks":{"convert":"jwks","multiple":false,"read":true,"write":true},"jwks_uri":{"multiple":false,"read":true,"write":true},"pubkey":{"multiple":false,"read":true,"write":true},"enc":{"multiple":false,"read":true,"write":true},"alg":{"multiple":false,"read":true,"write":true},"alg_kid":{"multiple":false,"read":true,"write":true},"encrypt_code":{"multiple":false,"read":true,"write":true},"encrypt_at":{"multiple":false,"read":true,"write":true},"encrypt_userinfo":{"multiple":false,"read":true,"write":true},"encrypt_id_token":{"multiple":false,"read":true,"write":true},"encrypt_refresh_token":{"multiple":false,"read":true,"write":true},"resource":{"multiple":true,"read":true,"write":true},"authorization_data_types":{"multiple":true,"read":true,"write":true},"tls_client_auth_san_dns":{"multiple":false,"read":true,"write":true},"tls_client_auth_san_uri":{"multiple":false,"read":true,"write":true},"tls_client_auth_san_ip":{"multiple":false,"read":true,"write":true},"tls_client_auth_san_email":{"multiple":false,"read":true,"write":true},"backchannel_token_delivery_mode":{"multiple":false,"read":true,"write":true},"backchannel_client_notification_endpoint":{"multiple":false,"read":true,"write":true},"backchannel_user_code_parameter":{"multiple":false,"read":true,"write":true},"request_object_signing_alg":{"multiple":false,"read":true,"write":true},"token_endpoint_signing_alg":{"multiple":false,"read":true,"write":true},"backchannel_authentication_request_signing_alg":{"multiple":false,"read":true,"write":true},"request_object_encryption_alg":{"multiple":false,"read":true,"write":true},"request_object_encryption_enc":{"multiple":false,"read":true,"write":true},"token_endpoint_encryption_alg":{"multiple":false,"read":true,"write":true},"token_endpoint_encryption_enc":{"multiple":false,"read":true,"write":true},"backchannel_authentication_request_encryption_alg":{"multiple":false,"read":true,"write":true},"backchannel_authentication_request_encryption_enc":{"multiple":false,"read":true,"write":true},"post_logout_redirect_uri":{"multiple":false,"read":true,"write":true},"frontchannel_logout_uri":{"multiple":false,"read":true,"write":true},"frontchannel_logout_session_required":{"multiple":false,"read":true,"write":true},"backchannel_logout_uri":{"multiple":false,"read":true,"write":true},"backchannel_logout_session_required":{"multiple":false,"read":true,"write":true}}}', 0); INSERT INTO g_user (gu_username, gu_name, gu_email, gu_enabled) VALUES ('admin', 'The Administrator', '', 1); INSERT INTO g_user_password (gu_id, guw_password) VALUES ((SELECT gu_id from g_user WHERE gu_username='admin'), crypt('password', gen_salt('bf'))); INSERT INTO g_user_scope (gus_name) VALUES ('g_admin'); INSERT INTO g_user_scope (gus_name) VALUES ('g_profile'); INSERT INTO g_user_scope_user (gu_id, gus_id) VALUES ((SELECT gu_id from g_user WHERE gu_username='admin'), (SELECT gus_id FROM g_user_scope WHERE gus_name='g_admin')); INSERT INTO g_user_scope_user (gu_id, gus_id) VALUES ((SELECT gu_id from g_user WHERE gu_username='admin'), (SELECT gus_id FROM g_user_scope WHERE gus_name='g_profile')); �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/database/init.sqlite3.sql�������������������������������������������������������0000664�0000000�0000000�00000103411�14156463140�0021125�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-- ------------------------------------------------------ -- -- SQlite3 Database -- -- Initialize Glewlwyd Database for the backend server -- -- The administration client app -- -- Copyright 2020-2021 Nicolas Mora <mail@babelouest.org> -- -- License: MIT -- -- ------------------------------------------------------ -- DROP TABLE IF EXISTS g_api_key; DROP TABLE IF EXISTS g_client_user_scope; DROP TABLE IF EXISTS g_scope_group_auth_scheme_module_instance; DROP TABLE IF EXISTS g_scope_group; DROP TABLE IF EXISTS g_user_session_scheme; DROP TABLE IF EXISTS g_scope; DROP TABLE IF EXISTS g_plugin_module_instance; DROP TABLE IF EXISTS g_user_module_instance; DROP TABLE IF EXISTS g_user_middleware_module_instance; DROP TABLE IF EXISTS g_user_auth_scheme_module_instance; DROP TABLE IF EXISTS g_client_module_instance; DROP TABLE IF EXISTS g_user_session; DROP TABLE IF EXISTS g_client_property; DROP TABLE IF EXISTS g_client_scope_client; DROP TABLE IF EXISTS g_client_scope; DROP TABLE IF EXISTS g_client; DROP TABLE IF EXISTS g_user_property; DROP TABLE IF EXISTS g_user_scope_user; DROP TABLE IF EXISTS g_user_scope; DROP TABLE IF EXISTS g_user_password; DROP TABLE IF EXISTS g_user; DROP TABLE IF EXISTS gpg_device_authorization_scope; DROP TABLE IF EXISTS gpg_device_authorization; DROP TABLE IF EXISTS gpg_access_token_scope; DROP TABLE IF EXISTS gpg_access_token; DROP TABLE IF EXISTS gpg_refresh_token_scope; DROP TABLE IF EXISTS gpg_refresh_token; DROP TABLE IF EXISTS gpg_code_scope; DROP TABLE IF EXISTS gpg_code; DROP TABLE IF EXISTS gpo_ciba_scope; DROP TABLE IF EXISTS gpo_ciba_scheme; DROP TABLE IF EXISTS gpo_ciba; DROP TABLE IF EXISTS gpo_par_scope; DROP TABLE IF EXISTS gpo_par; DROP TABLE IF EXISTS gpo_rar; DROP TABLE IF EXISTS gpo_dpop; DROP TABLE IF EXISTS gpo_device_scheme; DROP TABLE IF EXISTS gpo_device_authorization_scope; DROP TABLE IF EXISTS gpo_device_authorization; DROP TABLE IF EXISTS gpo_client_registration; DROP TABLE IF EXISTS gpo_subject_identifier; DROP TABLE IF EXISTS gpo_id_token; DROP TABLE IF EXISTS gpo_access_token_scope; DROP TABLE IF EXISTS gpo_access_token; DROP TABLE IF EXISTS gpo_refresh_token_scope; DROP TABLE IF EXISTS gpo_refresh_token; DROP TABLE IF EXISTS gpo_code_scheme; DROP TABLE IF EXISTS gpo_code_scope; DROP TABLE IF EXISTS gpo_code; DROP TABLE IF EXISTS gpo_client_token_request; DROP TABLE IF EXISTS gs_code; DROP TABLE IF EXISTS gs_webauthn_assertion; DROP TABLE IF EXISTS gs_webauthn_credential; DROP TABLE IF EXISTS gs_webauthn_user; DROP TABLE IF EXISTS gs_otp; DROP TABLE IF EXISTS gs_user_certificate; DROP TABLE IF EXISTS gpr_reset_credentials_email; DROP TABLE IF EXISTS gpr_reset_credentials_session; DROP TABLE IF EXISTS gpr_update_email; DROP TABLE IF EXISTS gpr_session; DROP TABLE IF EXISTS gs_oauth2_session; DROP TABLE IF EXISTS gs_oauth2_registration; CREATE TABLE g_user_module_instance ( gumi_id INTEGER PRIMARY KEY AUTOINCREMENT, gumi_module TEXT NOT NULL, gumi_order INTEGER NOT NULL, gumi_name TEXT NOT NULL, gumi_display_name TEXT DEFAULT '', gumi_parameters TEXT, gumi_readonly INTEGER DEFAULT 0, gumi_multiple_passwords INTEGER DEFAULT 0, gumi_enabled INTEGER DEFAULT 1 ); CREATE TABLE g_user_middleware_module_instance ( gummi_id INTEGER PRIMARY KEY AUTOINCREMENT, gummi_module TEXT NOT NULL, gummi_order INTEGER NOT NULL, gummi_name TEXT NOT NULL, gummi_display_name TEXT DEFAULT '', gummi_parameters TEXT, gummi_enabled INTEGER DEFAULT 1 ); CREATE TABLE g_user_auth_scheme_module_instance ( guasmi_id INTEGER PRIMARY KEY AUTOINCREMENT, guasmi_module TEXT NOT NULL, guasmi_expiration INTEGER NOT NULL DEFAULT 0, guasmi_max_use INTEGER DEFAULT 0, -- 0: unlimited guasmi_allow_user_register INTEGER DEFAULT 1, guasmi_forbid_user_profile INTEGER DEFAULT 0, guasmi_forbid_user_reset_credential INTEGER DEFAULT 0, guasmi_name TEXT NOT NULL, guasmi_display_name TEXT DEFAULT '', guasmi_parameters TEXT, guasmi_enabled INTEGER DEFAULT 1 ); CREATE TABLE g_client_module_instance ( gcmi_id INTEGER PRIMARY KEY AUTOINCREMENT, gcmi_module TEXT NOT NULL, gcmi_order INTEGER NOT NULL, gcmi_name TEXT NOT NULL, gcmi_display_name TEXT DEFAULT '', gcmi_parameters TEXT, gcmi_readonly INTEGER DEFAULT 0, gcmi_enabled INTEGER DEFAULT 1 ); CREATE TABLE g_plugin_module_instance ( gpmi_id INTEGER PRIMARY KEY AUTOINCREMENT, gpmi_module TEXT NOT NULL, gpmi_name TEXT NOT NULL, gpmi_display_name TEXT DEFAULT '', gpmi_parameters TEXT, gpmi_enabled INTEGER DEFAULT 1 ); CREATE TABLE g_user_session ( gus_id INTEGER PRIMARY KEY AUTOINCREMENT, gus_session_hash TEXT NOT NULL, gus_user_agent TEXT, gus_issued_for TEXT, -- IP address or hostname gus_username TEXT NOT NULL, gus_expiration TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gus_last_login TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gus_current INTEGER, gus_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_g_user_session_username ON g_user_session(gus_username); CREATE INDEX i_g_user_session_last_login ON g_user_session(gus_last_login); CREATE INDEX i_g_user_session_expiration ON g_user_session(gus_expiration); CREATE TABLE g_user_session_scheme ( guss_id INTEGER PRIMARY KEY AUTOINCREMENT, gus_id INTEGER NOT NULL, guasmi_id INTEGER DEFAULT NULL, -- NULL means scheme 'password' guss_expiration TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, guss_last_login TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, guss_use_counter INTEGER DEFAULT 0, guss_enabled INTEGER DEFAULT 1, FOREIGN KEY(gus_id) REFERENCES g_user_session(gus_id) ON DELETE CASCADE, FOREIGN KEY(guasmi_id) REFERENCES g_user_auth_scheme_module_instance(guasmi_id) ON DELETE CASCADE ); CREATE INDEX i_g_user_session_scheme_last_login ON g_user_session_scheme(guss_last_login); CREATE INDEX i_g_user_session_scheme_expiration ON g_user_session_scheme(guss_expiration); CREATE TABLE g_scope ( gs_id INTEGER PRIMARY KEY AUTOINCREMENT, gs_name TEXT NOT NULL UNIQUE, gs_display_name TEXT DEFAULT '', gs_description TEXT, gs_password_required INTEGER DEFAULT 1, gs_password_max_age INTEGER DEFAULT 0, gs_enabled INTEGER DEFAULT 1 ); CREATE TABLE g_scope_group ( gsg_id INTEGER PRIMARY KEY AUTOINCREMENT, gs_id INTEGER, gsg_name TEXT NOT NULL, gsg_scheme_required INTEGER DEFAULT 1, FOREIGN KEY(gs_id) REFERENCES g_scope(gs_id) ON DELETE CASCADE ); CREATE TABLE g_scope_group_auth_scheme_module_instance ( gsgasmi_id INTEGER PRIMARY KEY AUTOINCREMENT, gsg_id INTEGER NOT NULL, guasmi_id INTEGER NOT NULL, FOREIGN KEY(gsg_id) REFERENCES g_scope_group(gsg_id) ON DELETE CASCADE, FOREIGN KEY(guasmi_id) REFERENCES g_user_auth_scheme_module_instance(guasmi_id) ON DELETE CASCADE ); CREATE TABLE g_client_user_scope ( gcus_id INTEGER PRIMARY KEY AUTOINCREMENT, gs_id INTEGER NOT NULL, gcus_username TEXT NOT NULL, gcus_client_id TEXT NOT NULL, gcus_granted TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gcus_enabled INTEGER DEFAULT 1, FOREIGN KEY(gs_id) REFERENCES g_scope(gs_id) ON DELETE CASCADE ); CREATE INDEX i_g_client_user_scope_username ON g_client_user_scope(gcus_username); CREATE INDEX i_g_client_user_scope_client_id ON g_client_user_scope(gcus_client_id); CREATE TABLE g_api_key ( gak_id INTEGER PRIMARY KEY AUTOINCREMENT, gak_token_hash TEXT NOT NULL, gak_counter INTEGER DEFAULT 0, gak_username TEXT NOT NULL, gak_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gak_issued_for TEXT, -- IP address or hostname gak_user_agent TEXT, gak_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gak_token_hash ON g_api_key(gak_token_hash); CREATE TABLE g_client ( gc_id INTEGER PRIMARY KEY AUTOINCREMENT, gc_client_id TEXT NOT NULL UNIQUE, gc_name TEXT DEFAULT '', gc_description TEXT DEFAULT '', gc_confidential INTEGER DEFAULT 0, gc_password TEXT, gc_enabled INTEGER DEFAULT 1 ); CREATE TABLE g_client_scope ( gcs_id INTEGER PRIMARY KEY AUTOINCREMENT, gcs_name TEXT NOT NULL UNIQUE ); CREATE TABLE g_client_scope_client ( gcsu_id INTEGER PRIMARY KEY AUTOINCREMENT, gc_id INTEGER, gcs_id INTEGER, FOREIGN KEY(gc_id) REFERENCES g_client(gc_id) ON DELETE CASCADE, FOREIGN KEY(gcs_id) REFERENCES g_client_scope(gcs_id) ON DELETE CASCADE ); CREATE TABLE g_client_property ( gcp_id INTEGER PRIMARY KEY AUTOINCREMENT, gc_id INTEGER, gcp_name TEXT NOT NULL, gcp_value TEXT DEFAULT NULL, FOREIGN KEY(gc_id) REFERENCES g_client(gc_id) ON DELETE CASCADE ); CREATE INDEX i_g_client_property_name ON g_client_property(gcp_name); CREATE TABLE g_user ( gu_id INTEGER PRIMARY KEY AUTOINCREMENT, gu_username TEXT NOT NULL UNIQUE, gu_name TEXT DEFAULT '', gu_email TEXT DEFAULT '', gu_enabled INTEGER DEFAULT 1 ); CREATE TABLE g_user_scope ( gus_id INTEGER PRIMARY KEY AUTOINCREMENT, gus_name TEXT NOT NULL UNIQUE ); CREATE TABLE g_user_scope_user ( gusu_id INTEGER PRIMARY KEY AUTOINCREMENT, gu_id INTEGER, gus_id INTEGER, FOREIGN KEY(gu_id) REFERENCES g_user(gu_id) ON DELETE CASCADE, FOREIGN KEY(gus_id) REFERENCES g_user_scope(gus_id) ON DELETE CASCADE ); CREATE TABLE g_user_property ( gup_id INTEGER PRIMARY KEY AUTOINCREMENT, gu_id INTEGER, gup_name TEXT NOT NULL, gup_value TEXT DEFAULT NULL, FOREIGN KEY(gu_id) REFERENCES g_user(gu_id) ON DELETE CASCADE ); CREATE INDEX i_g_user_property_name ON g_user_property(gup_name); CREATE TABLE g_user_password ( guw_id INTEGER PRIMARY KEY AUTOINCREMENT, gu_id INTEGER, guw_password TEXT, FOREIGN KEY(gu_id) REFERENCES g_user(gu_id) ON DELETE CASCADE ); CREATE TABLE gpg_code ( gpgc_id INTEGER PRIMARY KEY AUTOINCREMENT, gpgc_plugin_name TEXT NOT NULL, gpgc_username TEXT NOT NULL, gpgc_client_id TEXT NOT NULL, gpgc_redirect_uri TEXT NOT NULL, gpgc_code_hash TEXT NOT NULL, gpgc_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpgc_issued_for TEXT, -- IP address or hostname gpgc_user_agent TEXT, gpgc_code_challenge TEXT, gpgc_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gpgc_code_hash ON gpg_code(gpgc_code_hash); CREATE INDEX i_gpgc_code_challenge ON gpg_code(gpgc_code_challenge); CREATE TABLE gpg_code_scope ( gpgcs_id INTEGER PRIMARY KEY AUTOINCREMENT, gpgc_id INTEGER, gpgcs_scope TEXT NOT NULL, FOREIGN KEY(gpgc_id) REFERENCES gpg_code(gpgc_id) ON DELETE CASCADE ); CREATE TABLE gpg_refresh_token ( gpgr_id INTEGER PRIMARY KEY AUTOINCREMENT, gpgr_plugin_name TEXT NOT NULL, gpgr_authorization_type INTEGER NOT NULL, -- 0: Authorization Code Grant, 1: Implicit Grant, 2: Resource Owner Password Credentials Grant, 3: Client Credentials Grant gpgc_id INTEGER DEFAULT NULL, gpgr_username TEXT NOT NULL, gpgr_client_id TEXT, gpgr_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpgr_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpgr_last_seen TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpgr_duration INTEGER, gpgr_rolling_expiration INTEGER DEFAULT 0, gpgr_issued_for TEXT, -- IP address or hostname gpgr_user_agent TEXT, gpgr_token_hash TEXT NOT NULL, gpgr_enabled INTEGER DEFAULT 1, FOREIGN KEY(gpgc_id) REFERENCES gpg_code(gpgc_id) ON DELETE CASCADE ); CREATE INDEX i_gpgr_token_hash ON gpg_refresh_token(gpgr_token_hash); CREATE TABLE gpg_refresh_token_scope ( gpgrs_id INTEGER PRIMARY KEY AUTOINCREMENT, gpgr_id INTEGER, gpgrs_scope TEXT NOT NULL, FOREIGN KEY(gpgr_id) REFERENCES gpg_refresh_token(gpgr_id) ON DELETE CASCADE ); -- Access token table, to store meta information on access token sent CREATE TABLE gpg_access_token ( gpga_id INTEGER PRIMARY KEY AUTOINCREMENT, gpga_plugin_name TEXT NOT NULL, gpga_authorization_type INTEGER NOT NULL, -- 0: Authorization Code Grant, 1: Implicit Grant, 2: Resource Owner Password Credentials Grant, 3: Client Credentials Grant gpgr_id INTEGER DEFAULT NULL, gpga_username TEXT, gpga_client_id TEXT, gpga_issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpga_issued_for TEXT, -- IP address or hostname gpga_user_agent TEXT, gpga_token_hash TEXT NOT NULL, gpga_enabled INTEGER DEFAULT 1, FOREIGN KEY(gpgr_id) REFERENCES gpg_refresh_token(gpgr_id) ON DELETE CASCADE ); CREATE INDEX i_gpga_token_hash ON gpg_access_token(gpga_token_hash); CREATE TABLE gpg_access_token_scope ( gpgas_id INTEGER PRIMARY KEY AUTOINCREMENT, gpga_id INT(11), gpgas_scope TEXT NOT NULL, FOREIGN KEY(gpga_id) REFERENCES gpg_access_token(gpga_id) ON DELETE CASCADE ); -- store device authorization requests CREATE TABLE gpg_device_authorization ( gpgda_id INTEGER PRIMARY KEY AUTOINCREMENT, gpgda_plugin_name TEXT NOT NULL, gpgda_client_id TEXT NOT NULL, gpgda_username TEXT, gpgda_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpgda_expires_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpgda_issued_for TEXT, -- IP address or hostname of the deice client gpgda_device_code_hash TEXT NOT NULL, gpgda_user_code_hash TEXT NOT NULL, gpgda_status INTEGER DEFAULT 0, -- 0: created, 1: user verified, 2 device completed, 3 disabled gpgda_last_check TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX i_gpgda_device_code_hash ON gpg_device_authorization(gpgda_device_code_hash); CREATE INDEX i_gpgda_user_code_hash ON gpg_device_authorization(gpgda_user_code_hash); CREATE TABLE gpg_device_authorization_scope ( gpgdas_id INTEGER PRIMARY KEY AUTOINCREMENT, gpgda_id INTEGER, gpgdas_scope TEXT NOT NULL, gpgdas_allowed INTEGER DEFAULT 0, FOREIGN KEY(gpgda_id) REFERENCES gpg_device_authorization(gpgda_id) ON DELETE CASCADE ); CREATE TABLE gpo_code ( gpoc_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoc_plugin_name TEXT NOT NULL, gpoc_authorization_type INTEGER NOT NULL, gpoc_username TEXT NOT NULL, gpoc_client_id TEXT NOT NULL, gpoc_redirect_uri TEXT NOT NULL, gpoc_code_hash TEXT NOT NULL, gpoc_nonce TEXT, gpoc_resource TEXT, gpoc_claims_request TEXT DEFAULT NULL, gpoc_authorization_details TEXT DEFAULT NULL, gpoc_s_hash TEXT, gpoc_sid TEXT, gpoc_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpoc_issued_for TEXT, -- IP address or hostname gpoc_user_agent TEXT, gpoc_code_challenge TEXT, gpoc_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gpoc_code_hash ON gpo_code(gpoc_code_hash); CREATE INDEX i_gpoc_code_challenge ON gpo_code(gpoc_code_challenge); CREATE TABLE gpo_code_scope ( gpocs_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoc_id INTEGER, gpocs_scope TEXT NOT NULL, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE ); CREATE TABLE gpo_code_scheme ( gpoch_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoc_id INTEGER, gpoch_scheme_module TEXT NOT NULL, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE ); CREATE TABLE gpo_refresh_token ( gpor_id INTEGER PRIMARY KEY AUTOINCREMENT, gpor_plugin_name TEXT NOT NULL, gpor_authorization_type INTEGER NOT NULL, gpoc_id INTEGER DEFAULT NULL, gpor_username TEXT NOT NULL, gpor_client_id TEXT, gpor_resource TEXT, gpor_claims_request TEXT DEFAULT NULL, gpor_authorization_details TEXT DEFAULT NULL, gpor_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpor_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpor_last_seen TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpor_duration INTEGER, gpor_rolling_expiration INTEGER DEFAULT 0, gpor_issued_for TEXT, -- IP address or hostname gpor_user_agent TEXT, gpor_token_hash TEXT NOT NULL, gpor_jti TEXT, gpor_dpop_jkt TEXT, gpor_enabled INTEGER DEFAULT 1, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE ); CREATE INDEX i_gpor_token_hash ON gpo_refresh_token(gpor_token_hash); CREATE INDEX i_gpor_jti ON gpo_refresh_token(gpor_jti); CREATE TABLE gpo_refresh_token_scope ( gpors_id INTEGER PRIMARY KEY AUTOINCREMENT, gpor_id INTEGER, gpors_scope TEXT NOT NULL, FOREIGN KEY(gpor_id) REFERENCES gpo_refresh_token(gpor_id) ON DELETE CASCADE ); -- Access token table, to store meta information on access token sent CREATE TABLE gpo_access_token ( gpoa_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoa_plugin_name TEXT NOT NULL, gpoa_authorization_type INTEGER NOT NULL, gpor_id INTEGER DEFAULT NULL, gpoa_username TEXT, gpoa_client_id TEXT, gpoa_resource TEXT, gpoa_issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoa_issued_for TEXT, -- IP address or hostname gpoa_user_agent TEXT, gpoa_token_hash TEXT NOT NULL, gpoa_jti TEXT, gpoa_authorization_details TEXT DEFAULT NULL, gpoa_enabled INTEGER DEFAULT 1, FOREIGN KEY(gpor_id) REFERENCES gpo_refresh_token(gpor_id) ON DELETE CASCADE ); CREATE INDEX i_gpoa_token_hash ON gpo_access_token(gpoa_token_hash); CREATE INDEX i_gpoa_jti ON gpo_access_token(gpoa_jti); CREATE TABLE gpo_access_token_scope ( gpoas_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoa_id INTEGER, gpoas_scope TEXT NOT NULL, FOREIGN KEY(gpoa_id) REFERENCES gpo_access_token(gpoa_id) ON DELETE CASCADE ); -- Id token table, to store meta information on id token sent CREATE TABLE gpo_id_token ( gpoi_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoc_id INTEGER, gpor_id INTEGER, gpoi_plugin_name TEXT NOT NULL, gpoi_authorization_type INTEGER NOT NULL, gpoi_username TEXT, gpoi_client_id TEXT, gpoi_issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoi_issued_for TEXT, -- IP address or hostname gpoi_user_agent TEXT, gpoi_hash TEXT, gpoi_sid TEXT, gpoi_enabled INTEGER DEFAULT 1, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE, FOREIGN KEY(gpor_id) REFERENCES gpo_refresh_token(gpor_id) ON DELETE CASCADE ); CREATE INDEX i_gpoi_hash ON gpo_id_token(gpoi_hash); -- subject identifier table to store subs and their relations to usernames, client_id and sector_identifier CREATE TABLE gpo_subject_identifier ( gposi_id INTEGER PRIMARY KEY AUTOINCREMENT, gposi_plugin_name TEXT NOT NULL, gposi_username TEXT NOT NULL, gposi_client_id TEXT, gposi_sector_identifier_uri TEXT, gposi_sub TEXT NOT NULL ); CREATE INDEX i_gposi_sub ON gpo_subject_identifier(gposi_sub); -- store meta information on client registration CREATE TABLE gpo_client_registration ( gpocr_id INTEGER PRIMARY KEY AUTOINCREMENT, gpocr_plugin_name TEXT NOT NULL, gpocr_cient_id TEXT NOT NULL, gpocr_management_at_hash TEXT, gpocr_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoa_id INTEGER, gpocr_issued_for TEXT, -- IP address or hostname gpocr_user_agent TEXT, FOREIGN KEY(gpoa_id) REFERENCES gpo_access_token(gpoa_id) ON DELETE CASCADE ); CREATE INDEX i_gpocr_management_at_hash ON gpo_client_registration(gpocr_management_at_hash); -- store meta information about client request on token endpoint CREATE TABLE gpo_client_token_request ( gpoctr_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoctr_plugin_name TEXT NOT NULL, gpoctr_cient_id TEXT NOT NULL, gpoctr_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoctr_issued_for TEXT, -- IP address or hostname gpoctr_jti_hash TEXT ); -- store device authorization requests CREATE TABLE gpo_device_authorization ( gpoda_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoda_plugin_name TEXT NOT NULL, gpoda_client_id TEXT NOT NULL, gpoda_resource TEXT, gpoda_username TEXT, gpoda_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoda_expires_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoda_issued_for TEXT, -- IP address or hostname of the deice client gpoda_device_code_hash TEXT NOT NULL, gpoda_user_code_hash TEXT NOT NULL, gpoda_sid TEXT, gpoda_status INTEGER DEFAULT 0, -- 0: created, 1: user verified, 2 device completed, 3 disabled gpoda_authorization_details TEXT DEFAULT NULL, gpoda_last_check TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX i_gpoda_device_code_hash ON gpo_device_authorization(gpoda_device_code_hash); CREATE INDEX i_gpoda_user_code_hash ON gpo_device_authorization(gpoda_user_code_hash); CREATE TABLE gpo_device_authorization_scope ( gpodas_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoda_id INTEGER, gpodas_scope TEXT NOT NULL, gpodas_allowed INTEGER DEFAULT 0, FOREIGN KEY(gpoda_id) REFERENCES gpo_device_authorization(gpoda_id) ON DELETE CASCADE ); CREATE TABLE gpo_device_scheme ( gpodh_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoda_id INTEGER, gpodh_scheme_module TEXT NOT NULL, FOREIGN KEY(gpoda_id) REFERENCES gpo_device_authorization(gpoda_id) ON DELETE CASCADE ); CREATE TABLE gpo_dpop ( gpod_id INTEGER PRIMARY KEY AUTOINCREMENT, gpod_plugin_name TEXT NOT NULL, gpod_client_id TEXT NOT NULL, gpod_jti_hash TEXT NOT NULL, gpod_jkt TEXT NOT NULL, gpod_htm TEXT NOT NULL, gpod_htu TEXT NOT NULL, gpod_iat TIMESTAMP NOT NULL, gpod_last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX i_gpod_jti_hash ON gpo_dpop(gpod_jti_hash); CREATE TABLE gpo_rar ( gporar_id INTEGER PRIMARY KEY AUTOINCREMENT, gporar_plugin_name TEXT NOT NULL, gporar_client_id TEXT NOT NULL, gporar_type TEXT NOT NULL, gporar_username TEXT, gporar_consent INTEGER DEFAULT 0, gporar_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gporar_client_id ON gpo_rar(gporar_client_id); CREATE INDEX i_gporar_type ON gpo_rar(gporar_type); CREATE INDEX i_gporar_username ON gpo_rar(gporar_username); CREATE TABLE gpo_par ( gpop_id INTEGER PRIMARY KEY AUTOINCREMENT, gpop_plugin_name TEXT NOT NULL, gpop_response_type TEXT NOT NULL, gpop_state TEXT, gpop_username TEXT, gpop_client_id TEXT NOT NULL, gpop_redirect_uri TEXT NOT NULL, gpop_request_uri_hash TEXT NOT NULL, gpop_nonce TEXT, gpop_code_challenge TEXT, gpop_resource TEXT, gpop_claims_request TEXT DEFAULT NULL, gpop_authorization_details TEXT DEFAULT NULL, gpop_additional_parameters TEXT DEFAULT NULL, gpop_status INTEGER DEFAULT 0, -- 0 created, 1 validated, 2 completed gpop_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpop_issued_for TEXT, -- IP address or hostname gpop_user_agent TEXT ); CREATE INDEX i_gpop_client_id ON gpo_par(gpop_client_id); CREATE INDEX i_gpop_request_uri_hash ON gpo_par(gpop_request_uri_hash); CREATE INDEX i_gpop_code_challenge ON gpo_par(gpop_code_challenge); CREATE TABLE gpo_par_scope ( gpops_id INTEGER PRIMARY KEY AUTOINCREMENT, gpop_id INTEGER, gpops_scope TEXT NOT NULL, FOREIGN KEY(gpop_id) REFERENCES gpo_par(gpop_id) ON DELETE CASCADE ); CREATE TABLE gpo_ciba ( gpob_id INTEGER PRIMARY KEY AUTOINCREMENT, gpob_plugin_name TEXT NOT NULL, gpob_client_id TEXT NOT NULL, gpob_x5t_s256 TEXT, gpob_username TEXT NOT NULL, gpob_client_notification_token TEXT, gpob_jti_hash TEXT, gpob_auth_req_id TEXT, gpob_user_req_id TEXT, gpob_binding_message TEXT, gpob_sid TEXT, gpob_status INTEGER DEFAULT 0, -- 0: created, 1: accepted, 2: error, 3: closed gpob_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpob_issued_for TEXT, -- IP address or hostname gpob_user_agent TEXT, gpob_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gpob_client_id ON gpo_ciba(gpob_client_id); CREATE INDEX i_gpob_jti_hash ON gpo_ciba(gpob_jti_hash); CREATE INDEX i_gpob_client_notification_token ON gpo_ciba(gpob_client_notification_token); CREATE INDEX i_gpob_auth_req_id ON gpo_ciba(gpob_auth_req_id); CREATE INDEX i_gpob_user_req_id ON gpo_ciba(gpob_user_req_id); CREATE TABLE gpo_ciba_scope ( gpocs_id INTEGER PRIMARY KEY AUTOINCREMENT, gpob_id INTEGER, gpops_scope TEXT NOT NULL, gpobs_granted INTEGER DEFAULT 0, FOREIGN KEY(gpob_id) REFERENCES gpo_ciba(gpob_id) ON DELETE CASCADE ); CREATE TABLE gpo_ciba_scheme ( gpobh_id INTEGER PRIMARY KEY AUTOINCREMENT, gpob_id INTEGER, gpobh_scheme_module TEXT NOT NULL, FOREIGN KEY(gpob_id) REFERENCES gpo_ciba(gpob_id) ON DELETE CASCADE ); CREATE TABLE gs_code ( gsc_id INTEGER PRIMARY KEY AUTOINCREMENT, gsc_mod_name TEXT NOT NULL, gsc_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gsc_username TEXT NOT NULL, gsc_enabled INTEGER DEFAULT 1, gsc_code_hash TEXT, gsc_result INTEGER DEFAULT 0 ); CREATE INDEX i_gsc_username ON gs_code(gsc_username); CREATE TABLE gs_webauthn_user ( gswu_id INTEGER PRIMARY KEY AUTOINCREMENT, gswu_mod_name TEXT NOT NULL, gswu_username TEXT NOT NULL, gswu_user_id TEXT NOT NULL ); CREATE INDEX i_gswu_username ON gs_webauthn_user(gswu_username); CREATE TABLE gs_webauthn_credential ( gswc_id INTEGER PRIMARY KEY AUTOINCREMENT, gswu_id INTEGER NOT NULL, gswc_session_hash TEXT NOT NULL, gswc_name TEXT, gswc_challenge_hash TEXT, gswc_credential_id TEXT, gswc_certificate TEXT DEFAULT NULL, gswc_public_key TEXT DEFAULT NULL, gswc_counter INTEGER DEFAULT 0, gswc_created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gswc_status INTEGER DEFAULT 0, -- 0 new, 1 registered, 2 error, 3 disabled, 4 removed FOREIGN KEY(gswu_id) REFERENCES gs_webauthn_user(gswu_id) ON DELETE CASCADE ); CREATE INDEX i_gswc_credential_id ON gs_webauthn_credential(gswc_credential_id); CREATE INDEX i_gswc_session_hash ON gs_webauthn_credential(gswc_session_hash); CREATE TABLE gs_webauthn_assertion ( gswa_id INTEGER PRIMARY KEY AUTOINCREMENT, gswu_id INTEGER NOT NULL, gswc_id INTEGER, gswa_session_hash TEXT NOT NULL, gswa_challenge_hash TEXT, gswa_counter INTEGER DEFAULT 0, gswa_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gswa_status SMALLINT DEFAULT 0, -- 0 new, 1 verified, 2 not verified, 3 error gswa_mock SMALLINT DEFAULT 0, FOREIGN KEY(gswu_id) REFERENCES gs_webauthn_user(gswu_id) ON DELETE CASCADE, FOREIGN KEY(gswc_id) REFERENCES gs_webauthn_credential(gswc_id) ON DELETE CASCADE ); CREATE INDEX i_gswa_session_hash ON gs_webauthn_assertion(gswa_session_hash); CREATE TABLE gs_otp ( gso_id INTEGER PRIMARY KEY AUTOINCREMENT, gso_mod_name TEXT NOT NULL, gso_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gso_last_used TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gso_username TEXT NOT NULL, gso_otp_type INTEGER DEFAULT 0, -- 0 HOTP, 1 TOTP gso_secret TEXT NOT NULL, gso_hotp_moving_factor INTEGER, gso_totp_time_step_size INTEGER ); CREATE INDEX i_gsso_username ON gs_otp(gso_username); CREATE TABLE gs_user_certificate ( gsuc_id INTEGER PRIMARY KEY AUTOINCREMENT, gsuc_mod_name TEXT NOT NULL, gsuc_username TEXT NOT NULL, gsuc_enabled INTEGER DEFAULT 1, gsuc_x509_certificate_content TEXT DEFAULT NULL, gsuc_x509_certificate_id TEXT NOT NULL, gsuc_x509_certificate_dn TEXT NOT NULL, gsuc_x509_certificate_issuer_dn TEXT NOT NULL, gsuc_activation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gsuc_expiration TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gsuc_last_used TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gsuc_last_user_agent TEXT DEFAULT NULL ); CREATE INDEX i_gsuc_username ON gs_user_certificate(gsuc_username); CREATE INDEX i_gsuc_x509_certificate_id ON gs_user_certificate(gsuc_x509_certificate_id); CREATE TABLE gpr_session ( gprs_id INTEGER PRIMARY KEY AUTOINCREMENT, gprs_plugin_name TEXT NOT NULL, gprs_username TEXT NOT NULL, gprs_name TEXT, gprs_email TEXT, gprs_code_hash TEXT, gprs_callback_url TEXT DEFAULT NULL, gprs_password_set INTEGER DEFAULT 0, gprs_session_hash TEXT, gprs_token_hash TEXT, gprs_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprs_issued_for TEXT, -- IP address or hostname gprs_user_agent TEXT, gprs_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gprs_session_hash ON gpr_session(gprs_session_hash); CREATE INDEX i_gprs_gprs_token_hash ON gpr_session(gprs_token_hash); CREATE INDEX i_gprs_gprs_gprs_code_hash ON gpr_session(gprs_code_hash); CREATE TABLE gpr_update_email ( gprue_id INTEGER PRIMARY KEY AUTOINCREMENT, gprue_plugin_name TEXT NOT NULL, gprue_username TEXT NOT NULL, gprue_email TEXT, gprue_token_hash TEXT, gprue_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprue_issued_for TEXT, -- IP address or hostname gprue_user_agent TEXT, gprue_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gprue_token_hash ON gpr_update_email(gprue_token_hash); CREATE TABLE gpr_reset_credentials_session ( gprrcs_id INTEGER PRIMARY KEY AUTOINCREMENT, gprrcs_plugin_name TEXT NOT NULL, gprrcs_username TEXT NOT NULL, gprrcs_session_hash TEXT, gprrcs_callback_url TEXT DEFAULT NULL, gprrcs_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprrcs_issued_for TEXT, -- IP address or hostname gprrcs_user_agent TEXT, gprrcs_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gprrcs_session_hash ON gpr_reset_credentials_session(gprrcs_session_hash); CREATE TABLE gpr_reset_credentials_email ( gprrct_id INTEGER PRIMARY KEY AUTOINCREMENT, gprrct_plugin_name TEXT NOT NULL, gprrct_username TEXT NOT NULL, gprrct_token_hash TEXT, gprrct_callback_url TEXT DEFAULT NULL, gprrct_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprrct_issued_for TEXT, -- IP address or hostname gprrct_user_agent TEXT, gprrct_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gprrct_token_hash ON gpr_reset_credentials_email(gprrct_token_hash); CREATE TABLE gs_oauth2_registration ( gsor_id INTEGER PRIMARY KEY AUTOINCREMENT, gsor_mod_name TEXT NOT NULL, gsor_provider TEXT NOT NULL, gsor_created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gsor_username TEXT NOT NULL, gsor_userinfo_sub TEXT ); CREATE INDEX i_gsor_username ON gs_oauth2_registration(gsor_username); CREATE TABLE gs_oauth2_session ( gsos_id INTEGER PRIMARY KEY AUTOINCREMENT, gsor_id INTEGER, gsos_created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gsos_expires_at TIMESTAMP, gsos_state TEXT NOT NULL, gsos_session_export TEXT, gsos_status INTEGER DEFAULT 0, -- 0: registration, 1: authentication, 2: verified, 3: cancelled FOREIGN KEY(gsor_id) REFERENCES gs_oauth2_registration(gsor_id) ON DELETE CASCADE ); INSERT INTO g_scope (gs_name, gs_display_name, gs_description, gs_password_required, gs_password_max_age) VALUES ('g_admin', 'Glewlwyd administration', 'Access to Glewlwyd''s administration API', 1, 600); INSERT INTO g_scope (gs_name, gs_display_name, gs_description, gs_password_required, gs_password_max_age) VALUES ('g_profile', 'Glewlwyd profile', 'Access to the user''s profile API', 1, 600); INSERT INTO g_scope (gs_name, gs_display_name, gs_description, gs_password_required, gs_password_max_age) VALUES ('openid', 'Open ID', 'Open ID Connect scope', 0, 0); INSERT INTO g_user_module_instance (gumi_module, gumi_order, gumi_name, gumi_display_name, gumi_parameters, gumi_readonly) VALUES ('database', 0, 'database', 'Database backend', '{"use-glewlwyd-connection":true,"data-format":{"picture":{"multiple":false,"read":true,"write":true,"profile-read":true,"profile-write":true},"reset-credentials-code":{"multiple":false,"read":true,"write":true,"profile-read":false,"profile-write":false}}}', 0); INSERT INTO g_client_module_instance (gcmi_module, gcmi_order, gcmi_name, gcmi_display_name, gcmi_parameters, gcmi_readonly) VALUES ('database', 0, 'database', 'Database backend', '{"use-glewlwyd-connection":true,"data-format":{"redirect_uri":{"multiple":true,"read":true,"write":true},"authorization_type":{"multiple":true,"read":true,"write":true},"response_mode":{"multiple":false,"read":true,"write":true},"sector_identifier_uri":{"multiple":false,"read":true,"write":true},"token_endpoint_auth_method":{"multiple":true,"read":true,"write":true},"client_secret":{"multiple":false,"read":true,"write":true},"jwks":{"convert":"jwks","multiple":false,"read":true,"write":true},"jwks_uri":{"multiple":false,"read":true,"write":true},"pubkey":{"multiple":false,"read":true,"write":true},"enc":{"multiple":false,"read":true,"write":true},"alg":{"multiple":false,"read":true,"write":true},"alg_kid":{"multiple":false,"read":true,"write":true},"encrypt_code":{"multiple":false,"read":true,"write":true},"encrypt_at":{"multiple":false,"read":true,"write":true},"encrypt_userinfo":{"multiple":false,"read":true,"write":true},"encrypt_id_token":{"multiple":false,"read":true,"write":true},"encrypt_refresh_token":{"multiple":false,"read":true,"write":true},"resource":{"multiple":true,"read":true,"write":true},"authorization_data_types":{"multiple":true,"read":true,"write":true},"tls_client_auth_san_dns":{"multiple":false,"read":true,"write":true},"tls_client_auth_san_uri":{"multiple":false,"read":true,"write":true},"tls_client_auth_san_ip":{"multiple":false,"read":true,"write":true},"tls_client_auth_san_email":{"multiple":false,"read":true,"write":true},"backchannel_token_delivery_mode":{"multiple":false,"read":true,"write":true},"backchannel_client_notification_endpoint":{"multiple":false,"read":true,"write":true},"backchannel_user_code_parameter":{"multiple":false,"read":true,"write":true},"request_object_signing_alg":{"multiple":false,"read":true,"write":true},"token_endpoint_signing_alg":{"multiple":false,"read":true,"write":true},"backchannel_authentication_request_signing_alg":{"multiple":false,"read":true,"write":true},"request_object_encryption_alg":{"multiple":false,"read":true,"write":true},"request_object_encryption_enc":{"multiple":false,"read":true,"write":true},"token_endpoint_encryption_alg":{"multiple":false,"read":true,"write":true},"token_endpoint_encryption_enc":{"multiple":false,"read":true,"write":true},"backchannel_authentication_request_encryption_alg":{"multiple":false,"read":true,"write":true},"backchannel_authentication_request_encryption_enc":{"multiple":false,"read":true,"write":true},"post_logout_redirect_uri":{"multiple":false,"read":true,"write":true},"frontchannel_logout_uri":{"multiple":false,"read":true,"write":true},"frontchannel_logout_session_required":{"multiple":false,"read":true,"write":true},"backchannel_logout_uri":{"multiple":false,"read":true,"write":true},"backchannel_logout_session_required":{"multiple":false,"read":true,"write":true}}}', 0); INSERT INTO g_user (gu_username, gu_name, gu_email, gu_enabled) VALUES ('admin', 'The Administrator', '', 1); INSERT INTO g_user_password (gu_id, guw_password) VALUES ((SELECT gu_id from g_user WHERE gu_username='admin'), 'fOfvZC/wR2cUSTWbW6YZueGyyDuFqwkoFlcNlRYWJscxYTVOVFJ3VWFHdVJQT0pU'); INSERT INTO g_user_scope (gus_name) VALUES ('g_admin'); INSERT INTO g_user_scope (gus_name) VALUES ('g_profile'); INSERT INTO g_user_scope_user (gu_id, gus_id) VALUES ((SELECT gu_id from g_user WHERE gu_username='admin'), (SELECT gus_id FROM g_user_scope WHERE gus_name='g_admin')); INSERT INTO g_user_scope_user (gu_id, gus_id) VALUES ((SELECT gu_id from g_user WHERE gu_username='admin'), (SELECT gus_id FROM g_user_scope WHERE gus_name='g_profile')); �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/database/upgrade-2.2-core.mariadb.sql�������������������������������������������0000664�0000000�0000000�00000005726�14156463140�0023063�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-- ----------------------------------------------------- -- -- Upgrade Glewlwyd 2.0.x or 2.1.x to 2.2.0 -- Copyright 2020 Nicolas Mora <mail@babelouest.org> -- -- License: MIT -- -- ----------------------------------------------------- -- ALTER TABLE g_user_module_instance ADD gumi_enabled TINYINT(1) DEFAULT 1; ALTER TABLE g_user_auth_scheme_module_instance ADD guasmi_enabled TINYINT(1) DEFAULT 1; ALTER TABLE g_client_module_instance ADD gcmi_enabled TINYINT(1) DEFAULT 1; ALTER TABLE g_plugin_module_instance ADD gpmi_enabled TINYINT(1) DEFAULT 1; ALTER TABLE gpg_code ADD gpgc_code_challenge VARCHAR(128); CREATE INDEX i_gpgc_code_challenge ON gpg_code(gpgc_code_challenge); ALTER TABLE gpg_access_token ADD gpga_token_hash VARCHAR(512) NOT NULL DEFAULT '', ADD gpga_enabled TINYINT(1) DEFAULT 1; CREATE INDEX i_gpga_token_hash ON gpg_access_token(gpga_token_hash); ALTER TABLE gpo_code ADD gpoc_code_challenge VARCHAR(128); CREATE INDEX i_gpoc_code_challenge ON gpo_code(gpoc_code_challenge); ALTER TABLE gpo_access_token ADD gpoa_token_hash VARCHAR(512) NOT NULL DEFAULT '', ADD gpoa_enabled TINYINT(1) DEFAULT 1; CREATE INDEX i_gpoa_token_hash ON gpo_access_token(gpoa_token_hash); ALTER TABLE gpo_id_token ADD gpoi_enabled TINYINT(1) DEFAULT 1; CREATE INDEX i_gpoi_hash ON gpo_id_token(gpoi_hash); -- store meta information on client registration CREATE TABLE gpo_client_registration ( gpocr_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpocr_plugin_name VARCHAR(256) NOT NULL, gpocr_cient_id VARCHAR(256) NOT NULL, gpocr_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoa_id INT(11), gpocr_issued_for VARCHAR(256), -- IP address or hostname gpocr_user_agent VARCHAR(256), FOREIGN KEY(gpoa_id) REFERENCES gpo_access_token(gpoa_id) ON DELETE CASCADE ); CREATE TABLE gs_oauth2_registration ( gsor_id INT(11) PRIMARY KEY AUTO_INCREMENT, gsor_mod_name VARCHAR(128) NOT NULL, gsor_provider VARCHAR(128) NOT NULL, gsor_created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gsor_username VARCHAR(128) NOT NULL, gsor_userinfo_sub VARCHAR(128) ); CREATE INDEX i_gsor_username ON gs_oauth2_registration(gsor_username); CREATE TABLE gs_oauth2_session ( gsos_id INT(11) PRIMARY KEY AUTO_INCREMENT, gsor_id INT(11), gsos_created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gsos_expires_at TIMESTAMP, gsos_state TEXT NOT NULL, gsos_session_export TEXT, gsos_status TINYINT(1) DEFAULT 0, -- 0: registration, 1: authentication, 2: verified, 3: cancelled FOREIGN KEY(gsor_id) REFERENCES gs_oauth2_registration(gsor_id) ON DELETE CASCADE ); -- store meta information about client request on token endpoint CREATE TABLE gpo_client_token_request ( gpoctr_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoctr_plugin_name VARCHAR(256) NOT NULL, gpoctr_cient_id VARCHAR(256) NOT NULL, gpoctr_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoctr_issued_for VARCHAR(256), -- IP address or hostname gpoctr_jti_hash VARCHAR(512) ); ������������������������������������������glewlwyd-2.6.1/docs/database/upgrade-2.2-core.postgre.sql�������������������������������������������0000664�0000000�0000000�00000005540�14156463140�0023141�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-- ----------------------------------------------------- -- -- Upgrade Glewlwyd 2.0.x or 2.1.x to 2.2.0 -- Copyright 2020 Nicolas Mora <mail@babelouest.org> -- -- License: MIT -- -- ----------------------------------------------------- -- ALTER TABLE g_user_module_instance ADD gumi_enabled SMALLINT DEFAULT 1; ALTER TABLE g_user_auth_scheme_module_instance ADD guasmi_enabled SMALLINT DEFAULT 1; ALTER TABLE g_client_module_instance ADD gcmi_enabled SMALLINT DEFAULT 1; ALTER TABLE g_plugin_module_instance ADD gpmi_enabled SMALLINT DEFAULT 1; ALTER TABLE gpg_code ADD gpgc_code_challenge VARCHAR(128); CREATE INDEX i_gpgc_code_challenge ON gpg_code(gpgc_code_challenge); ALTER TABLE gpg_access_token ADD gpga_token_hash VARCHAR(512) NOT NULL DEFAULT '', ADD gpga_enabled SMALLINT DEFAULT 1; CREATE INDEX i_gpga_token_hash ON gpg_access_token(gpga_token_hash); ALTER TABLE gpo_code ADD gpoc_code_challenge VARCHAR(128); CREATE INDEX i_gpoc_code_challenge ON gpo_code(gpoc_code_challenge); ALTER TABLE gpo_access_token ADD gpoa_token_hash VARCHAR(512) NOT NULL DEFAULT '', ADD gpoa_enabled SMALLINT DEFAULT 1; CREATE INDEX i_gpoa_token_hash ON gpo_access_token(gpoa_token_hash); ALTER TABLE gpo_id_token ADD gpoi_enabled SMALLINT DEFAULT 1; CREATE INDEX i_gpoi_hash ON gpo_id_token(gpoi_hash); -- store meta information on client registration CREATE TABLE gpo_client_registration ( gpocr_id SERIAL PRIMARY KEY, gpocr_plugin_name VARCHAR(256) NOT NULL, gpocr_cient_id VARCHAR(256) NOT NULL, gpocr_created_at TIMESTAMPTZ DEFAULT NOW(), gpoa_id INTEGER, gpocr_issued_for VARCHAR(256), -- IP address or hostname gpocr_user_agent VARCHAR(256), FOREIGN KEY(gpoa_id) REFERENCES gpo_access_token(gpoa_id) ON DELETE CASCADE ); CREATE TABLE gs_oauth2_registration ( gsor_id SERIAL PRIMARY KEY, gsor_mod_name VARCHAR(128) NOT NULL, gsor_provider VARCHAR(128) NOT NULL, gsor_created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gsor_username VARCHAR(128) NOT NULL, gsor_userinfo_sub VARCHAR(128) ); CREATE INDEX i_gsor_username ON gs_oauth2_registration(gsor_username); CREATE TABLE gs_oauth2_session ( gsos_id SERIAL PRIMARY KEY, gsor_id INTEGER, gsos_created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gsos_expires_at TIMESTAMPTZ, gsos_state TEXT NOT NULL, gsos_session_export TEXT, gsos_status SMALLINT DEFAULT 0, -- 0: registration, 1: authentication, 2: verified, 3: cancelled FOREIGN KEY(gsor_id) REFERENCES gs_oauth2_registration(gsor_id) ON DELETE CASCADE ); -- store meta information about client request on token endpoint CREATE TABLE gpo_client_token_request ( gpoctr_id SERIAL PRIMARY KEY, gpoctr_plugin_name VARCHAR(256) NOT NULL, gpoctr_cient_id VARCHAR(256) NOT NULL, gpoctr_created_at TIMESTAMPTZ DEFAULT NOW(), gpoctr_issued_for VARCHAR(256), -- IP address or hostname gpoctr_jti_hash VARCHAR(512) ); ����������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/database/upgrade-2.2-core.sqlite3.sql�������������������������������������������0000664�0000000�0000000�00000005562�14156463140�0023046�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-- ----------------------------------------------------- -- -- Upgrade Glewlwyd 2.0.x or 2.1.x to 2.2.0 -- Copyright 2020 Nicolas Mora <mail@babelouest.org> -- -- License: MIT -- -- ----------------------------------------------------- -- ALTER TABLE g_user_module_instance ADD gumi_enabled INTEGER DEFAULT 1; ALTER TABLE g_user_auth_scheme_module_instance ADD guasmi_enabled INTEGER DEFAULT 1; ALTER TABLE g_client_module_instance ADD gcmi_enabled INTEGER DEFAULT 1; ALTER TABLE g_plugin_module_instance ADD gpmi_enabled INTEGER DEFAULT 1; ALTER TABLE gpg_code ADD gpgc_code_challenge TEXT; CREATE INDEX i_gpgc_code_challenge ON gpg_code(gpgc_code_challenge); ALTER TABLE gpg_access_token ADD gpga_token_hash TEXT NOT NULL DEFAULT ''; ALTER TABLE gpg_access_token ADD gpga_enabled INTEGER DEFAULT 1; CREATE INDEX i_gpga_token_hash ON gpg_access_token(gpga_token_hash); ALTER TABLE gpo_code ADD gpoc_code_challenge TEXT; CREATE INDEX i_gpoc_code_challenge ON gpo_code(gpoc_code_challenge); ALTER TABLE gpo_access_token ADD gpoa_token_hash TEXT NOT NULL DEFAULT ''; ALTER TABLE gpo_access_token ADD gpoa_enabled INTEGER DEFAULT 1; CREATE INDEX i_gpoa_token_hash ON gpo_access_token(gpoa_token_hash); ALTER TABLE gpo_id_token ADD gpoi_enabled INTEGER DEFAULT 1; CREATE INDEX i_gpoi_hash ON gpo_id_token(gpoi_hash); -- store meta information on client registration CREATE TABLE gpo_client_registration ( gpocr_id INTEGER PRIMARY KEY AUTOINCREMENT, gpocr_plugin_name TEXT NOT NULL, gpocr_cient_id TEXT NOT NULL, gpocr_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoa_id INTEGER, gpocr_issued_for TEXT, -- IP address or hostname gpocr_user_agent TEXT, FOREIGN KEY(gpoa_id) REFERENCES gpo_access_token(gpoa_id) ON DELETE CASCADE ); CREATE TABLE gs_oauth2_registration ( gsor_id INTEGER PRIMARY KEY AUTOINCREMENT, gsor_mod_name TEXT NOT NULL, gsor_provider TEXT NOT NULL, gsor_created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gsor_username TEXT NOT NULL, gsor_userinfo_sub TEXT ); CREATE INDEX i_gsor_username ON gs_oauth2_registration(gsor_username); CREATE TABLE gs_oauth2_session ( gsos_id INTEGER PRIMARY KEY AUTOINCREMENT, gsor_id INTEGER, gsos_created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gsos_expires_at TIMESTAMP, gsos_state TEXT NOT NULL, gsos_session_export TEXT, gsos_status INTEGER DEFAULT 0, -- 0: registration, 1: authentication, 2: verified, 3: cancelled FOREIGN KEY(gsor_id) REFERENCES gs_oauth2_registration(gsor_id) ON DELETE CASCADE ); -- store meta information about client request on token endpoint CREATE TABLE gpo_client_token_request ( gpoctr_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoctr_plugin_name TEXT NOT NULL, gpoctr_cient_id TEXT NOT NULL, gpoctr_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoctr_issued_for TEXT, -- IP address or hostname gpoctr_jti_hash TEXT ); ����������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/database/upgrade-2.3-core.mariadb.sql�������������������������������������������0000664�0000000�0000000�00000006203�14156463140�0023053�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-- ----------------------------------------------------- -- -- Upgrade Glewlwyd 2.2.x 2.3.0 -- Copyright 2020 Nicolas Mora <mail@babelouest.org> -- -- License: MIT -- -- ----------------------------------------------------- -- ALTER TABLE gpo_access_token ADD gpoa_jti VARCHAR(128); CREATE INDEX i_gpoa_jti ON gpo_access_token(gpoa_jti); ALTER TABLE gpo_refresh_token ADD gpor_jti VARCHAR(128); CREATE INDEX i_gpor_jti ON gpo_refresh_token(gpor_jti); ALTER TABLE gpo_client_registration ADD gpocr_management_at_hash VARCHAR(512); CREATE INDEX i_gpocr_management_at_hash ON gpo_client_registration(gpocr_management_at_hash); -- store device authorization requests CREATE TABLE gpo_device_authorization ( gpoda_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoda_plugin_name VARCHAR(256) NOT NULL, gpoda_client_id VARCHAR(256) NOT NULL, gpoda_username VARCHAR(256), gpoda_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoda_expires_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoda_issued_for VARCHAR(256), -- IP address or hostname of the deice client gpoda_device_code_hash VARCHAR(512) NOT NULL, gpoda_user_code_hash VARCHAR(512) NOT NULL, gpoda_status TINYINT(1) DEFAULT 0, -- 0: created, 1: user verified, 2 device completed, 3 disabled gpoda_last_check TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX i_gpoda_device_code_hash ON gpo_device_authorization(gpoda_device_code_hash); CREATE INDEX i_gpoda_user_code_hash ON gpo_device_authorization(gpoda_user_code_hash); CREATE TABLE gpo_device_authorization_scope ( gpodas_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoda_id INT(11), gpodas_scope VARCHAR(128) NOT NULL, gpodas_allowed TINYINT(1) DEFAULT 0, FOREIGN KEY(gpoda_id) REFERENCES gpo_device_authorization(gpoda_id) ON DELETE CASCADE ); CREATE TABLE gpo_device_scheme ( gpodh_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoda_id INT(11), gpodh_scheme_module VARCHAR(128) NOT NULL, FOREIGN KEY(gpoda_id) REFERENCES gpo_device_authorization(gpoda_id) ON DELETE CASCADE ); -- store device authorization requests CREATE TABLE gpg_device_authorization ( gpgda_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpgda_plugin_name VARCHAR(256) NOT NULL, gpgda_client_id VARCHAR(256) NOT NULL, gpgda_username VARCHAR(256), gpgda_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpgda_expires_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpgda_issued_for VARCHAR(256), -- IP address or hostname of the deice client gpgda_device_code_hash VARCHAR(512) NOT NULL, gpgda_user_code_hash VARCHAR(512) NOT NULL, gpgda_status TINYINT(1) DEFAULT 0, -- 0: created, 1: user verified, 2 device completed, 3 disabled gpgda_last_check TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX i_gpgda_device_code_hash ON gpg_device_authorization(gpgda_device_code_hash); CREATE INDEX i_gpgda_user_code_hash ON gpg_device_authorization(gpgda_user_code_hash); CREATE TABLE gpg_device_authorization_scope ( gpgdas_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpgda_id INT(11), gpgdas_scope VARCHAR(128) NOT NULL, gpgdas_allowed TINYINT(1) DEFAULT 0, FOREIGN KEY(gpgda_id) REFERENCES gpg_device_authorization(gpgda_id) ON DELETE CASCADE ); ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/database/upgrade-2.3-core.postgre.sql�������������������������������������������0000664�0000000�0000000�00000005757�14156463140�0023154�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-- ----------------------------------------------------- -- -- Upgrade Glewlwyd 2.2.x 2.3.0 -- Copyright 2020 Nicolas Mora <mail@babelouest.org> -- -- License: MIT -- -- ----------------------------------------------------- -- ALTER TABLE gpo_access_token ADD gpoa_jti VARCHAR(128); CREATE INDEX i_gpoa_jti ON gpo_access_token(gpoa_jti); ALTER TABLE gpo_refresh_token ADD gpor_jti VARCHAR(128); CREATE INDEX i_gpor_jti ON gpo_refresh_token(gpor_jti); ALTER TABLE gpo_client_registration ADD gpocr_management_at_hash VARCHAR(512); CREATE INDEX i_gpocr_management_at_hash ON gpo_client_registration(gpocr_management_at_hash); -- store device authorization requests CREATE TABLE gpo_device_authorization ( gpoda_id SERIAL PRIMARY KEY, gpoda_plugin_name VARCHAR(256) NOT NULL, gpoda_client_id VARCHAR(256) NOT NULL, gpoda_username VARCHAR(256), gpoda_created_at TIMESTAMPTZ DEFAULT NOW(), gpoda_expires_at TIMESTAMPTZ DEFAULT NOW(), gpoda_issued_for VARCHAR(256), -- IP address or hostname of the deice client gpoda_device_code_hash VARCHAR(512) NOT NULL, gpoda_user_code_hash VARCHAR(512) NOT NULL, gpoda_status SMALLINT DEFAULT 0, -- 0: created, 1: user verified, 2 device completed, 3 disabled gpoda_last_check TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX i_gpoda_device_code_hash ON gpo_device_authorization(gpoda_device_code_hash); CREATE INDEX i_gpoda_user_code_hash ON gpo_device_authorization(gpoda_user_code_hash); CREATE TABLE gpo_device_authorization_scope ( gpodas_id SERIAL PRIMARY KEY, gpoda_id INTEGER, gpodas_scope VARCHAR(128) NOT NULL, gpodas_allowed SMALLINT DEFAULT 0, FOREIGN KEY(gpoda_id) REFERENCES gpo_device_authorization(gpoda_id) ON DELETE CASCADE ); CREATE TABLE gpo_device_scheme ( gpodh_id SERIAL PRIMARY KEY, gpoda_id INTEGER, gpodh_scheme_module VARCHAR(128) NOT NULL, FOREIGN KEY(gpoda_id) REFERENCES gpo_device_authorization(gpoda_id) ON DELETE CASCADE ); -- store device authorization requests CREATE TABLE gpg_device_authorization ( gpgda_id SERIAL PRIMARY KEY, gpgda_plugin_name VARCHAR(256) NOT NULL, gpgda_client_id VARCHAR(256) NOT NULL, gpgda_username VARCHAR(256), gpgda_created_at TIMESTAMPTZ DEFAULT NOW(), gpgda_expires_at TIMESTAMPTZ DEFAULT NOW(), gpgda_issued_for VARCHAR(256), -- IP address or hostname of the deice client gpgda_device_code_hash VARCHAR(512) NOT NULL, gpgda_user_code_hash VARCHAR(512) NOT NULL, gpgda_status SMALLINT DEFAULT 0, -- 0: created, 1: user verified, 2 device completed, 3 disabled gpgda_last_check TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX i_gpgda_device_code_hash ON gpg_device_authorization(gpgda_device_code_hash); CREATE INDEX i_gpgda_user_code_hash ON gpg_device_authorization(gpgda_user_code_hash); CREATE TABLE gpg_device_authorization_scope ( gpgdas_id SERIAL PRIMARY KEY, gpgda_id INTEGER, gpgdas_scope VARCHAR(128) NOT NULL, gpgdas_allowed SMALLINT DEFAULT 0, FOREIGN KEY(gpgda_id) REFERENCES gpg_device_authorization(gpgda_id) ON DELETE CASCADE ); �����������������glewlwyd-2.6.1/docs/database/upgrade-2.3-core.sqlite3.sql�������������������������������������������0000664�0000000�0000000�00000005742�14156463140�0023047�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-- ----------------------------------------------------- -- -- Upgrade Glewlwyd 2.2.x 2.3.0 -- Copyright 2020 Nicolas Mora <mail@babelouest.org> -- -- License: MIT -- -- ----------------------------------------------------- -- ALTER TABLE gpo_access_token ADD gpoa_jti TEXT; CREATE INDEX i_gpoa_jti ON gpo_access_token(gpoa_jti); ALTER TABLE gpo_refresh_token ADD gpor_jti TEXT; CREATE INDEX i_gpor_jti ON gpo_refresh_token(gpor_jti); ALTER TABLE gpo_client_registration ADD gpocr_management_at_hash TEXT; CREATE INDEX i_gpocr_management_at_hash ON gpo_client_registration(gpocr_management_at_hash); -- store device authorization requests CREATE TABLE gpo_device_authorization ( gpoda_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoda_plugin_name TEXT NOT NULL, gpoda_client_id TEXT NOT NULL, gpoda_username TEXT, gpoda_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoda_expires_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoda_issued_for TEXT, -- IP address or hostname of the deice client gpoda_device_code_hash TEXT NOT NULL, gpoda_user_code_hash TEXT NOT NULL, gpoda_status INTEGER DEFAULT 0, -- 0: created, 1: user verified, 2 device completed, 3 disabled gpoda_last_check TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX i_gpoda_device_code_hash ON gpo_device_authorization(gpoda_device_code_hash); CREATE INDEX i_gpoda_user_code_hash ON gpo_device_authorization(gpoda_user_code_hash); CREATE TABLE gpo_device_authorization_scope ( gpodas_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoda_id INTEGER, gpodas_scope TEXT NOT NULL, gpodas_allowed INTEGER DEFAULT 0, FOREIGN KEY(gpoda_id) REFERENCES gpo_device_authorization(gpoda_id) ON DELETE CASCADE ); CREATE TABLE gpo_device_scheme ( gpodh_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoda_id INTEGER, gpodh_scheme_module TEXT NOT NULL, FOREIGN KEY(gpoda_id) REFERENCES gpo_device_authorization(gpoda_id) ON DELETE CASCADE ); -- store device authorization requests CREATE TABLE gpg_device_authorization ( gpgda_id INTEGER PRIMARY KEY AUTOINCREMENT, gpgda_plugin_name TEXT NOT NULL, gpgda_client_id TEXT NOT NULL, gpgda_username TEXT, gpgda_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpgda_expires_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpgda_issued_for TEXT, -- IP address or hostname of the deice client gpgda_device_code_hash TEXT NOT NULL, gpgda_user_code_hash TEXT NOT NULL, gpgda_status INTEGER DEFAULT 0, -- 0: created, 1: user verified, 2 device completed, 3 disabled gpgda_last_check TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX i_gpgda_device_code_hash ON gpg_device_authorization(gpgda_device_code_hash); CREATE INDEX i_gpgda_user_code_hash ON gpg_device_authorization(gpgda_user_code_hash); CREATE TABLE gpg_device_authorization_scope ( gpgdas_id INTEGER PRIMARY KEY AUTOINCREMENT, gpgda_id INTEGER, gpgdas_scope TEXT NOT NULL, gpgdas_allowed INTEGER DEFAULT 0, FOREIGN KEY(gpgda_id) REFERENCES gpg_device_authorization(gpgda_id) ON DELETE CASCADE ); ������������������������������glewlwyd-2.6.1/docs/database/upgrade-2.4-core.mariadb.sql�������������������������������������������0000664�0000000�0000000�00000004203�14156463140�0023052�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������DROP TABLE IF EXISTS gs_user_pkcs12; ALTER TABLE gpr_session ADD gprs_callback_url BLOB DEFAULT NULL; ALTER TABLE g_scope_group ADD gsg_scheme_required INT(11) DEFAULT 1; CREATE TABLE gpr_update_email ( gprue_id INT(11) PRIMARY KEY AUTO_INCREMENT, gprue_plugin_name VARCHAR(256) NOT NULL, gprue_username VARCHAR(256) NOT NULL, gprue_email VARCHAR(512), gprue_token_hash VARCHAR(512), gprue_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprue_issued_for VARCHAR(256), -- IP address or hostname gprue_user_agent VARCHAR(256), gprue_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gprue_token_hash ON gpr_update_email(gprue_token_hash); CREATE TABLE gpr_reset_credentials_session ( gprrcs_id INT(11) PRIMARY KEY AUTO_INCREMENT, gprrcs_plugin_name VARCHAR(256) NOT NULL, gprrcs_username VARCHAR(256) NOT NULL, gprrcs_session_hash VARCHAR(512), gprrcs_callback_url BLOB DEFAULT NULL, gprrcs_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprrcs_issued_for VARCHAR(256), -- IP address or hostname gprrcs_user_agent VARCHAR(256), gprrcs_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gprrcs_session_hash ON gpr_reset_credentials_session(gprrcs_session_hash); CREATE TABLE gpr_reset_credentials_email ( gprrct_id INT(11) PRIMARY KEY AUTO_INCREMENT, gprrct_plugin_name VARCHAR(256) NOT NULL, gprrct_username VARCHAR(256) NOT NULL, gprrct_token_hash VARCHAR(512), gprrct_callback_url BLOB DEFAULT NULL, gprrct_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprrct_issued_for VARCHAR(256), -- IP address or hostname gprrct_user_agent VARCHAR(256), gprrct_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gprrct_token_hash ON gpr_reset_credentials_email(gprrct_token_hash); CREATE TABLE g_api_key ( gak_id INT(11) PRIMARY KEY AUTO_INCREMENT, gak_token_hash VARCHAR(512) NOT NULL, gak_counter INT(11) DEFAULT 0, gak_username VARCHAR(256) NOT NULL, gak_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gak_issued_for VARCHAR(256), -- IP address or hostname gak_user_agent VARCHAR(256), gak_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gak_token_hash ON g_api_key(gak_token_hash); ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/database/upgrade-2.4-core.postgre.sql�������������������������������������������0000664�0000000�0000000�00000004067�14156463140�0023146�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������DROP TABLE IF EXISTS gs_user_pkcs12; ALTER TABLE gpr_session ADD gprs_callback_url TEXT DEFAULT NULL; ALTER TABLE g_scope_group ADD gsg_scheme_required INTEGER DEFAULT 1; CREATE TABLE gpr_update_email ( gprue_id SERIAL PRIMARY KEY, gprue_plugin_name VARCHAR(256) NOT NULL, gprue_username VARCHAR(256) NOT NULL, gprue_email VARCHAR(512), gprue_token_hash VARCHAR(512), gprue_expires_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, gprue_issued_for VARCHAR(256), -- IP address or hostname gprue_user_agent VARCHAR(256), gprue_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gprue_token_hash ON gpr_update_email(gprue_token_hash); CREATE TABLE gpr_reset_credentials_session ( gprrcs_id SERIAL PRIMARY KEY, gprrcs_plugin_name VARCHAR(256) NOT NULL, gprrcs_username VARCHAR(256) NOT NULL, gprrcs_session_hash VARCHAR(512), gprrcs_callback_url TEXT DEFAULT NULL, gprrcs_expires_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, gprrcs_issued_for VARCHAR(256), -- IP address or hostname gprrcs_user_agent VARCHAR(256), gprrcs_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gprrcs_session_hash ON gpr_reset_credentials_session(gprrcs_session_hash); CREATE TABLE gpr_reset_credentials_email ( gprrct_id SERIAL PRIMARY KEY, gprrct_plugin_name VARCHAR(256) NOT NULL, gprrct_username VARCHAR(256) NOT NULL, gprrct_token_hash VARCHAR(512), gprrct_callback_url TEXT DEFAULT NULL, gprrct_expires_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, gprrct_issued_for VARCHAR(256), -- IP address or hostname gprrct_user_agent VARCHAR(256), gprrct_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gprrct_token_hash ON gpr_reset_credentials_email(gprrct_token_hash); CREATE TABLE g_api_key ( gak_id SERIAL PRIMARY KEY, gak_token_hash VARCHAR(512) NOT NULL, gak_counter INTEGER DEFAULT 0, gak_username VARCHAR(256) NOT NULL, gak_issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gak_issued_for VARCHAR(256), -- IP address or hostname gak_user_agent VARCHAR(256), gak_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gak_token_hash ON g_api_key(gak_token_hash); �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/database/upgrade-2.4-core.sqlite3.sql�������������������������������������������0000664�0000000�0000000�00000003723�14156463140�0023045�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������DROP TABLE IF EXISTS gs_user_pkcs12; ALTER TABLE gpr_session ADD gprs_callback_url TEXT DEFAULT NULL; ALTER TABLE g_scope_group ADD gsg_scheme_required INTEGER DEFAULT 1; CREATE TABLE gpr_update_email ( gprue_id INTEGER PRIMARY KEY AUTOINCREMENT, gprue_plugin_name TEXT NOT NULL, gprue_username TEXT NOT NULL, gprue_email TEXT, gprue_token_hash TEXT, gprue_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprue_issued_for TEXT, -- IP address or hostname gprue_user_agent TEXT, gprue_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gprue_token_hash ON gpr_update_email(gprue_token_hash); CREATE TABLE gpr_reset_credentials_session ( gprrcs_id INTEGER PRIMARY KEY AUTOINCREMENT, gprrcs_plugin_name TEXT NOT NULL, gprrcs_username TEXT NOT NULL, gprrcs_session_hash TEXT, gprrcs_callback_url TEXT DEFAULT NULL, gprrcs_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprrcs_issued_for TEXT, -- IP address or hostname gprrcs_user_agent TEXT, gprrcs_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gprrcs_session_hash ON gpr_reset_credentials_session(gprrcs_session_hash); CREATE TABLE gpr_reset_credentials_email ( gprrct_id INTEGER PRIMARY KEY AUTOINCREMENT, gprrct_plugin_name TEXT NOT NULL, gprrct_username TEXT NOT NULL, gprrct_token_hash TEXT, gprrct_callback_url TEXT DEFAULT NULL, gprrct_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprrct_issued_for TEXT, -- IP address or hostname gprrct_user_agent TEXT, gprrct_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gprrct_token_hash ON gpr_reset_credentials_email(gprrct_token_hash); CREATE TABLE g_api_key ( gak_id INTEGER PRIMARY KEY AUTOINCREMENT, gak_token_hash TEXT NOT NULL, gak_counter INTEGER DEFAULT 0, gak_username TEXT NOT NULL, gak_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gak_issued_for TEXT, -- IP address or hostname gak_user_agent TEXT, gak_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gak_token_hash ON g_api_key(gak_token_hash); ���������������������������������������������glewlwyd-2.6.1/docs/database/upgrade-2.5-core.mariadb.sql�������������������������������������������0000664�0000000�0000000�00000006462�14156463140�0023064�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-- ----------------------------------------------------- -- -- Upgrade Glewlwyd 2.4.0 2.5.0 -- Copyright 2020 Nicolas Mora <mail@babelouest.org> -- -- License: MIT -- -- ----------------------------------------------------- -- ALTER TABLE g_user_module_instance ADD gumi_multiple_passwords TINYINT(1) DEFAULT 0; ALTER TABLE gpo_code ADD gpoc_resource VARCHAR(512); ALTER TABLE gpo_refresh_token ADD gpor_resource VARCHAR(512); ALTER TABLE gpo_access_token ADD gpoa_resource VARCHAR(512); ALTER TABLE gpo_device_authorization ADD gpoda_resource VARCHAR(512); CREATE TABLE gpo_dpop ( gpod_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpod_plugin_name VARCHAR(256) NOT NULL, gpod_client_id VARCHAR(256) NOT NULL, gpod_jti_hash VARCHAR(512) NOT NULL, gpod_jkt VARCHAR(512) NOT NULL, gpod_htm VARCHAR(128) NOT NULL, gpod_htu VARCHAR(512) NOT NULL, gpod_iat TIMESTAMP NOT NULL, gpod_last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX i_gpod_jti_hash ON gpo_dpop(gpod_jti_hash); CREATE TABLE g_user_password ( guw_id INT(11) PRIMARY KEY AUTO_INCREMENT, gu_id INT(11), guw_password VARCHAR(256), FOREIGN KEY(gu_id) REFERENCES g_user(gu_id) ON DELETE CASCADE ); INSERT INTO g_user_password (gu_id, guw_password) SELECT gu_id, gu_password FROM g_user; ALTER TABLE g_user DROP COLUMN gu_password; ALTER TABLE gpo_code ADD gpoc_authorization_details BLOB DEFAULT NULL; ALTER TABLE gpo_refresh_token ADD gpor_authorization_details BLOB DEFAULT NULL; ALTER TABLE gpo_refresh_token ADD gpor_dpop_jkt VARCHAR(512); ALTER TABLE gpo_access_token ADD gpoa_authorization_details BLOB DEFAULT NULL; ALTER TABLE gpo_device_authorization ADD gpoda_authorization_details BLOB DEFAULT NULL; CREATE TABLE gpo_rar ( gporar_id INT(11) PRIMARY KEY AUTO_INCREMENT, gporar_plugin_name VARCHAR(256) NOT NULL, gporar_client_id VARCHAR(256) NOT NULL, gporar_type VARCHAR(256) NOT NULL, gporar_username VARCHAR(256), gporar_consent TINYINT(1) DEFAULT 0, gporar_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gporar_client_id ON gpo_rar(gporar_client_id); CREATE INDEX i_gporar_type ON gpo_rar(gporar_type); CREATE INDEX i_gporar_username ON gpo_rar(gporar_username); CREATE TABLE gpo_par ( gpop_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpop_plugin_name VARCHAR(256) NOT NULL, gpop_response_type VARCHAR(128) NOT NULL, gpop_state BLOB, gpop_username VARCHAR(256), gpop_client_id VARCHAR(256) NOT NULL, gpop_redirect_uri VARCHAR(512) NOT NULL, gpop_request_uri_hash VARCHAR(512) NOT NULL, gpop_nonce VARCHAR(512), gpop_code_challenge VARCHAR(128), gpop_resource VARCHAR(512), gpop_claims_request BLOB DEFAULT NULL, gpop_authorization_details BLOB DEFAULT NULL, gpop_additional_parameters BLOB DEFAULT NULL, gpop_status TINYINT(1) DEFAULT 0, -- 0 created, 1 validated, 2 completed gpop_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpop_issued_for VARCHAR(256), -- IP address or hostname gpop_user_agent VARCHAR(256) ); CREATE INDEX i_gpop_request_uri_hash ON gpo_par(gpop_request_uri_hash); CREATE INDEX i_gpop_code_challenge ON gpo_par(gpop_code_challenge); CREATE TABLE gpo_par_scope ( gpops_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpop_id INT(11), gpops_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpop_id) REFERENCES gpo_par(gpop_id) ON DELETE CASCADE ); ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/database/upgrade-2.5-core.postgre.sql�������������������������������������������0000664�0000000�0000000�00000006337�14156463140�0023151�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-- ----------------------------------------------------- -- -- Upgrade Glewlwyd 2.4.0 2.5.0 -- Copyright 2020 Nicolas Mora <mail@babelouest.org> -- -- License: MIT -- -- ----------------------------------------------------- -- ALTER TABLE g_user_module_instance ADD gumi_multiple_passwords SMALLINT DEFAULT 0; ALTER TABLE gpo_code ADD gpoc_resource VARCHAR(512); ALTER TABLE gpo_refresh_token ADD gpor_resource VARCHAR(512); ALTER TABLE gpo_access_token ADD gpoa_resource VARCHAR(512); ALTER TABLE gpo_device_authorization ADD gpoda_resource VARCHAR(512); CREATE TABLE gpo_dpop ( gpod_id SERIAL PRIMARY KEY, gpod_plugin_name VARCHAR(256) NOT NULL, gpod_client_id VARCHAR(256) NOT NULL, gpod_jti_hash VARCHAR(512) NOT NULL, gpod_jkt VARCHAR(512) NOT NULL, gpod_htm VARCHAR(128) NOT NULL, gpod_htu VARCHAR(512) NOT NULL, gpod_iat TIMESTAMPTZ NOT NULL, gpod_last_seen TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX i_gpod_jti_hash ON gpo_dpop(gpod_jti_hash); CREATE TABLE g_user_password ( guw_id SERIAL PRIMARY KEY, gu_id SERIAL, guw_password VARCHAR(256), FOREIGN KEY(gu_id) REFERENCES g_user(gu_id) ON DELETE CASCADE ); INSERT INTO g_user_password (gu_id, guw_password) SELECT gu_id, gu_password FROM g_user; ALTER TABLE g_user DROP COLUMN gu_password; ALTER TABLE gpo_code ADD gpoc_authorization_details TEXT DEFAULT NULL; ALTER TABLE gpo_refresh_token ADD gpor_authorization_details TEXT DEFAULT NULL; ALTER TABLE gpo_refresh_token ADD gpor_dpop_jkt VARCHAR(512); ALTER TABLE gpo_access_token ADD gpoa_authorization_details TEXT DEFAULT NULL; ALTER TABLE gpo_device_authorization ADD gpoda_authorization_details TEXT DEFAULT NULL; CREATE TABLE gpo_rar ( gporar_id SERIAL PRIMARY KEY, gporar_plugin_name VARCHAR(256) NOT NULL, gporar_client_id VARCHAR(256) NOT NULL, gporar_type VARCHAR(256) NOT NULL, gporar_username VARCHAR(256), gporar_consent SMALLINT DEFAULT 0, gporar_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gporar_client_id ON gpo_rar(gporar_client_id); CREATE INDEX i_gporar_type ON gpo_rar(gporar_type); CREATE INDEX i_gporar_username ON gpo_rar(gporar_username); CREATE TABLE gpo_par ( gpop_id SERIAL PRIMARY KEY, gpop_plugin_name VARCHAR(256) NOT NULL, gpop_response_type VARCHAR(128) NOT NULL, gpop_state TEXT, gpop_username VARCHAR(256), gpop_client_id VARCHAR(256) NOT NULL, gpop_redirect_uri VARCHAR(512) NOT NULL, gpop_request_uri_hash VARCHAR(512) NOT NULL, gpop_nonce VARCHAR(512), gpop_code_challenge VARCHAR(128), gpop_resource VARCHAR(512), gpop_claims_request TEXT DEFAULT NULL, gpop_authorization_details TEXT DEFAULT NULL, gpop_additional_parameters TEXT DEFAULT NULL, gpop_status SMALLINT DEFAULT 0, -- 0 created, 1 validated, 2 completed gpop_expires_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, gpop_issued_for VARCHAR(256), -- IP address or hostname gpop_user_agent VARCHAR(256) ); CREATE INDEX i_gpop_request_uri_hash ON gpo_par(gpop_request_uri_hash); CREATE INDEX i_gpop_code_challenge ON gpo_par(gpop_code_challenge); CREATE TABLE gpo_par_scope ( gpops_id SERIAL PRIMARY KEY, gpop_id INTEGER, gpops_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpop_id) REFERENCES gpo_par(gpop_id) ON DELETE CASCADE ); �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/database/upgrade-2.5-core.sqlite3.sql�������������������������������������������0000664�0000000�0000000�00000006377�14156463140�0023056�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-- ----------------------------------------------------- -- -- Upgrade Glewlwyd 2.4.0 2.5.0 -- Copyright 2020 Nicolas Mora <mail@babelouest.org> -- -- License: MIT -- -- ----------------------------------------------------- -- ALTER TABLE g_user_module_instance ADD gumi_multiple_passwords INTEGER DEFAULT 0; ALTER TABLE gpo_code ADD gpoc_resource TEXT; ALTER TABLE gpo_refresh_token ADD gpor_resource TEXT; ALTER TABLE gpo_access_token ADD gpoa_resource TEXT; ALTER TABLE gpo_device_authorization ADD gpoda_resource TEXT; CREATE TABLE gpo_dpop ( gpod_id INTEGER PRIMARY KEY AUTOINCREMENT, gpod_plugin_name INTEGER NOT NULL, gpod_client_id INTEGER NOT NULL, gpod_jti_hash INTEGER NOT NULL, gpod_jkt INTEGER NOT NULL, gpod_htm INTEGER NOT NULL, gpod_htu INTEGER NOT NULL, gpod_iat TIMESTAMP NOT NULL, gpod_last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX i_gpod_jti_hash ON gpo_dpop(gpod_jti_hash); CREATE TABLE g_user_password ( guw_id INTEGER PRIMARY KEY AUTOINCREMENT, gu_id INTEGER, guw_password TEXT, FOREIGN KEY(gu_id) REFERENCES g_user(gu_id) ON DELETE CASCADE ); INSERT INTO g_user_password (gu_id, guw_password) SELECT gu_id, gu_password FROM g_user; -- SQLite3 doesn't support DROP COLUMN, using a backup table to remove this column is dangerous because of all the foreign keys. -- So instead I'll set the old gu_password to NULL UPDATE g_user SET gu_password=NULL; ALTER TABLE gpo_code ADD gpoc_authorization_details TEXT DEFAULT NULL; ALTER TABLE gpo_refresh_token ADD gpor_authorization_details TEXT DEFAULT NULL; ALTER TABLE gpo_refresh_token ADD gpor_dpop_jkt TEXT; ALTER TABLE gpo_access_token ADD gpoa_authorization_details TEXT DEFAULT NULL; ALTER TABLE gpo_device_authorization ADD gpoda_authorization_details TEXT DEFAULT NULL; CREATE TABLE gpo_rar ( gporar_id INTEGER PRIMARY KEY AUTOINCREMENT, gporar_plugin_name TEXT NOT NULL, gporar_client_id TEXT NOT NULL, gporar_type TEXT NOT NULL, gporar_username TEXT, gporar_consent INTEGER DEFAULT 0, gporar_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gporar_client_id ON gpo_rar(gporar_client_id); CREATE INDEX i_gporar_type ON gpo_rar(gporar_type); CREATE INDEX i_gporar_username ON gpo_rar(gporar_username); CREATE TABLE gpo_par ( gpop_id INTEGER PRIMARY KEY AUTOINCREMENT, gpop_plugin_name TEXT NOT NULL, gpop_response_type TEXT NOT NULL, gpop_state TEXT, gpop_username TEXT, gpop_client_id TEXT NOT NULL, gpop_redirect_uri TEXT NOT NULL, gpop_request_uri_hash TEXT NOT NULL, gpop_nonce TEXT, gpop_code_challenge TEXT, gpop_resource TEXT, gpop_claims_request TEXT DEFAULT NULL, gpop_authorization_details TEXT DEFAULT NULL, gpop_additional_parameters TEXT DEFAULT NULL, gpop_status INTEGER DEFAULT 0, -- 0 created, 1 validated, 2 completed gpop_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpop_issued_for TEXT, -- IP address or hostname gpop_user_agent TEXT ); CREATE INDEX i_gpop_request_uri_hash ON gpo_par(gpop_request_uri_hash); CREATE INDEX i_gpop_code_challenge ON gpo_par(gpop_code_challenge); CREATE TABLE gpo_par_scope ( gpops_id INTEGER PRIMARY KEY AUTOINCREMENT, gpop_id INTEGER, gpops_scope TEXT NOT NULL, FOREIGN KEY(gpop_id) REFERENCES gpo_par(gpop_id) ON DELETE CASCADE ); �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/database/upgrade-2.6-core.mariadb.sql�������������������������������������������0000664�0000000�0000000�00000005240�14156463140�0023056�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-- ----------------------------------------------------- -- -- Upgrade Glewlwyd 2.5.0 2.6.0 -- Copyright 2021 Nicolas Mora <mail@babelouest.org> -- -- License: MIT -- -- ----------------------------------------------------- -- ALTER TABLE g_user_auth_scheme_module_instance ADD guasmi_forbid_user_profile TINYINT(1) DEFAULT 0, ADD guasmi_forbid_user_reset_credential TINYINT(1) DEFAULT 0; CREATE TABLE g_user_middleware_module_instance ( gummi_id INT(11) PRIMARY KEY AUTO_INCREMENT, gummi_module VARCHAR(128) NOT NULL, gummi_order INT(11) NOT NULL, gummi_name VARCHAR(128) NOT NULL, gummi_display_name VARCHAR(256) DEFAULT '', gummi_parameters MEDIUMBLOB, gummi_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gpop_client_id ON gpo_par(gpop_client_id); CREATE TABLE gpo_ciba ( gpob_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpob_plugin_name VARCHAR(256) NOT NULL, gpob_client_id VARCHAR(256) NOT NULL, gpob_x5t_s256 VARCHAR(64), gpob_username VARCHAR(256) NOT NULL, gpob_client_notification_token VARCHAR(1024), gpob_jti_hash VARCHAR(512), gpob_auth_req_id VARCHAR(128), gpob_user_req_id VARCHAR(128), gpob_binding_message VARCHAR(256), gpob_sid VARCHAR(128), gpob_status TINYINT(1) DEFAULT 0, -- 0: created, 1: accepted, 2: error, 3: closed gpob_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpob_issued_for VARCHAR(256), -- IP address or hostname gpob_user_agent VARCHAR(256), gpob_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gpob_client_id ON gpo_ciba(gpob_client_id); CREATE INDEX i_gpob_jti_hash ON gpo_ciba(gpob_jti_hash); CREATE INDEX i_gpob_client_notification_token ON gpo_ciba(gpob_client_notification_token); CREATE INDEX i_gpob_auth_req_id ON gpo_ciba(gpob_auth_req_id); CREATE INDEX i_gpob_user_req_id ON gpo_ciba(gpob_user_req_id); CREATE TABLE gpo_ciba_scope ( gpocs_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpob_id INT(11), gpops_scope VARCHAR(128) NOT NULL, gpobs_granted TINYINT(1) DEFAULT 0, FOREIGN KEY(gpob_id) REFERENCES gpo_ciba(gpob_id) ON DELETE CASCADE ); CREATE TABLE gpo_ciba_scheme ( gpobh_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpob_id INT(11), gpobh_scheme_module VARCHAR(128) NOT NULL, FOREIGN KEY(gpob_id) REFERENCES gpo_ciba(gpob_id) ON DELETE CASCADE ); ALTER TABLE gpo_code ADD gpoc_s_hash VARCHAR(512), ADD gpoc_sid VARCHAR(128); ALTER TABLE gpo_id_token ADD gpoc_id INT(11) DEFAULT NULL, ADD gpor_id INT(11), ADD gpoi_sid VARCHAR(128), ADD FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE, ADD FOREIGN KEY(gpor_id) REFERENCES gpo_refresh_token(gpor_id) ON DELETE CASCADE; ALTER TABLE gpo_device_authorization Add gpoda_sid VARCHAR(128); ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/database/upgrade-2.6-core.postgre.sql�������������������������������������������0000664�0000000�0000000�00000005103�14156463140�0023140�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-- ----------------------------------------------------- -- -- Upgrade Glewlwyd 2.5.0 2.6.0 -- Copyright 2021 Nicolas Mora <mail@babelouest.org> -- -- License: MIT -- -- ----------------------------------------------------- -- ALTER TABLE g_user_auth_scheme_module_instance ADD guasmi_forbid_user_profile SMALLINT DEFAULT 0, ADD guasmi_forbid_user_reset_credential SMALLINT DEFAULT 0; CREATE TABLE g_user_middleware_module_instance ( gummi_id SERIAL PRIMARY KEY, gummi_module VARCHAR(128) NOT NULL, gummi_order INTEGER NOT NULL, gummi_name VARCHAR(128) NOT NULL, gummi_display_name VARCHAR(256) DEFAULT '', gummi_parameters TEXT, gummi_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gpop_client_id ON gpo_par(gpop_client_id); CREATE TABLE gpo_ciba ( gpob_id SERIAL PRIMARY KEY, gpob_plugin_name VARCHAR(256) NOT NULL, gpob_client_id VARCHAR(256) NOT NULL, gpob_x5t_s256 VARCHAR(64), gpob_username VARCHAR(256) NOT NULL, gpob_client_notification_token VARCHAR(1024), gpob_jti_hash VARCHAR(512), gpob_auth_req_id VARCHAR(128), gpob_user_req_id VARCHAR(128), gpob_binding_message VARCHAR(256), gpob_sid VARCHAR(128), gpob_status SMALLINT DEFAULT 0, -- 0: created, 1: accepted, 2: error, 3: closed gpob_expires_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, gpob_issued_for VARCHAR(256), -- IP address or hostname gpob_user_agent VARCHAR(256), gpob_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gpob_client_id ON gpo_ciba(gpob_client_id); CREATE INDEX i_gpob_jti_hash ON gpo_ciba(gpob_jti_hash); CREATE INDEX i_gpob_client_notification_token ON gpo_ciba(gpob_client_notification_token); CREATE INDEX i_gpob_auth_req_id ON gpo_ciba(gpob_auth_req_id); CREATE INDEX i_gpob_user_req_id ON gpo_ciba(gpob_user_req_id); CREATE TABLE gpo_ciba_scope ( gpocs_id SERIAL PRIMARY KEY, gpob_id INTEGER, gpops_scope VARCHAR(128) NOT NULL, gpobs_granted SMALLINT DEFAULT 0, FOREIGN KEY(gpob_id) REFERENCES gpo_ciba(gpob_id) ON DELETE CASCADE ); CREATE TABLE gpo_ciba_scheme ( gpobh_id SERIAL PRIMARY KEY, gpob_id INTEGER, gpobh_scheme_module VARCHAR(128) NOT NULL, FOREIGN KEY(gpob_id) REFERENCES gpo_ciba(gpob_id) ON DELETE CASCADE ); ALTER TABLE gpo_code ADD gpoc_s_hash VARCHAR(512), ADD gpoc_sid VARCHAR(128); ALTER TABLE gpo_id_token ADD gpoc_id INTEGER, ADD gpor_id INTEGER, ADD gpoi_sid VARCHAR(128), ADD FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE, ADD FOREIGN KEY(gpor_id) REFERENCES gpo_refresh_token(gpor_id) ON DELETE CASCADE; ALTER TABLE gpo_device_authorization Add gpoda_sid VARCHAR(128); �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/database/upgrade-2.6-core.sqlite3.sql�������������������������������������������0000664�0000000�0000000�00000006326�14156463140�0023051�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������-- ----------------------------------------------------- -- -- Upgrade Glewlwyd 2.5.0 2.6.0 -- Copyright 2021 Nicolas Mora <mail@babelouest.org> -- -- License: MIT -- -- ----------------------------------------------------- -- ALTER TABLE g_user_auth_scheme_module_instance ADD guasmi_forbid_user_profile INTEGER DEFAULT 0; ALTER TABLE g_user_auth_scheme_module_instance ADD guasmi_forbid_user_reset_credential INTEGER DEFAULT 0; CREATE TABLE g_user_middleware_module_instance ( gummi_id INTEGER PRIMARY KEY AUTOINCREMENT, gummi_module TEXT NOT NULL, gummi_order INTEGER NOT NULL, gummi_name TEXT NOT NULL, gummi_display_name TEXT DEFAULT '', gummi_parameters TEXT, gummi_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gpop_client_id ON gpo_par(gpop_client_id); CREATE TABLE gpo_ciba ( gpob_id INTEGER PRIMARY KEY AUTOINCREMENT, gpob_plugin_name TEXT NOT NULL, gpob_client_id TEXT NOT NULL, gpob_x5t_s256 TEXT, gpob_username TEXT NOT NULL, gpob_client_notification_token TEXT, gpob_jti_hash TEXT, gpob_auth_req_id TEXT, gpob_user_req_id TEXT, gpob_binding_message TEXT, gpob_sid TEXT, gpob_status INTEGER DEFAULT 0, -- 0: created, 1: accepted, 2: error, 3: closed gpob_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpob_issued_for TEXT, -- IP address or hostname gpob_user_agent TEXT, gpob_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gpob_client_id ON gpo_ciba(gpob_client_id); CREATE INDEX i_gpob_jti_hash ON gpo_ciba(gpob_jti_hash); CREATE INDEX i_gpob_client_notification_token ON gpo_ciba(gpob_client_notification_token); CREATE INDEX i_gpob_auth_req_id ON gpo_ciba(gpob_auth_req_id); CREATE INDEX i_gpob_user_req_id ON gpo_ciba(gpob_user_req_id); CREATE TABLE gpo_ciba_scope ( gpocs_id INTEGER PRIMARY KEY AUTOINCREMENT, gpob_id INTEGER, gpops_scope TEXT NOT NULL, gpobs_granted INTEGER DEFAULT 0, FOREIGN KEY(gpob_id) REFERENCES gpo_ciba(gpob_id) ON DELETE CASCADE ); CREATE TABLE gpo_ciba_scheme ( gpobh_id INTEGER PRIMARY KEY AUTOINCREMENT, gpob_id INTEGER, gpobh_scheme_module TEXT NOT NULL, FOREIGN KEY(gpob_id) REFERENCES gpo_ciba(gpob_id) ON DELETE CASCADE ); ALTER TABLE gpo_code ADD gpoc_s_hash TEXT; ALTER TABLE gpo_code ADD gpoc_sid TEXT; ALTER TABLE gpo_id_token ADD gpoc_id INTEGER; ALTER TABLE gpo_id_token ADD gpor_id INTEGER; ALTER TABLE gpo_id_token ADD gpoi_sid TEXT; ALTER TABLE gpo_device_authorization Add gpoda_sid TEXT; PRAGMA foreign_keys=off; BEGIN TRANSACTION; ALTER TABLE gpo_id_token RENAME TO _gpo_id_token_old; CREATE TABLE gpo_id_token ( gpoi_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoc_id INTEGER, gpor_id INTEGER, gpoi_plugin_name TEXT NOT NULL, gpoi_authorization_type INTEGER NOT NULL, gpoi_username TEXT, gpoi_client_id TEXT, gpoi_issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoi_issued_for TEXT, -- IP address or hostname gpoi_user_agent TEXT, gpoi_hash TEXT, gpoi_sid TEXT, gpoi_enabled INTEGER DEFAULT 1, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE, FOREIGN KEY(gpor_id) REFERENCES gpo_refresh_token(gpor_id) ON DELETE CASCADE ); INSERT INTO gpo_id_token SELECT * FROM _gpo_id_token_old; DROP TABLE _gpo_id_token_old; COMMIT; PRAGMA foreign_keys=on; ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/docker/�������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14156463140�0015561�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/docker/.gitignore���������������������������������������������������������������0000664�0000000�0000000�00000000026�14156463140�0017547�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-full*.tar.gz ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/docker/Dockerfile���������������������������������������������������������������0000664�0000000�0000000�00000004207�14156463140�0017556�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������FROM alpine:latest AS builder ARG GLEWLWYD_VERSION ARG ALPINE_VERSION COPY glewlwyd-full_${GLEWLWYD_VERSION}_alpine_${ALPINE_VERSION}_x86_64.tar.gz /opt/glewlwyd.tar.gz # Install required packages RUN apk add --no-cache \ git \ make \ cmake \ wget \ gcc \ g++ \ libmicrohttpd \ jansson \ gnutls \ wget \ cmake \ autoconf \ automake \ libtool && \ (cd /opt && wget https://github.com/PJK/libcbor/archive/v0.7.0.tar.gz -O libcbor.tar.gz && \ tar xf libcbor.tar.gz && cd libcbor-0.7.0 && mkdir build && cd build && \ cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib .. && make && make install) && \ cd /opt && \ tar xf ./glewlwyd.tar.gz && \ tar xf liborcania_*.tar.gz -C /usr/ --strip 1 && \ tar xf libyder_*.tar.gz -C /usr/ --strip 1 && \ tar xf libulfius_*.tar.gz -C /usr/ --strip 1 && \ tar xf libhoel_*.tar.gz -C /usr/ --strip 1 && \ tar xf librhonabwy_*.tar.gz -C /usr/ --strip 1 && \ tar xf libiddawc_*.tar.gz -C /usr/ --strip 1 && \ tar xf glewlwyd_*.tar.gz -C /usr/ --strip 1 FROM alpine:latest AS runner RUN apk add --no-cache \ wget \ sqlite \ libconfig \ jansson \ gnutls \ libcurl \ libldap \ sqlite-libs \ libpq \ oath-toolkit-liboath \ mariadb-connector-c \ libmicrohttpd \ bash COPY --from=builder /usr/lib/libcbor.* /usr/lib/ COPY --from=builder /usr/lib/liborcania* /usr/lib/ COPY --from=builder /usr/lib/libyder* /usr/lib/ COPY --from=builder /usr/lib/libhoel* /usr/lib/ COPY --from=builder /usr/lib/libulfius* /usr/lib/ COPY --from=builder /usr/lib/librhonabwy* /usr/lib/ COPY --from=builder /usr/lib/libiddawc* /usr/lib/ COPY --from=builder /usr/lib/glewlwyd/ /usr/lib/glewlwyd/ COPY --from=builder /usr/bin/glewlwyd /usr/bin COPY --from=builder /usr/share/glewlwyd/ /usr/share/glewlwyd/ COPY --from=builder /usr/share/glewlwyd/webapp/config.json /etc/glewlwyd/ COPY --from=builder /usr/etc/glewlwyd/ /etc/glewlwyd/ RUN rm /usr/share/glewlwyd/webapp/config.json RUN ln -s /etc/glewlwyd/config.json /usr/share/glewlwyd/webapp/config.json COPY ["entrypoint.sh", "/"] CMD ["/entrypoint.sh"] �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/docker/Makefile�����������������������������������������������������������������0000664�0000000�0000000�00000002064�14156463140�0017223�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������GLEWLWYD_VERSION=$(shell curl -s https://api.github.com/repos/babelouest/glewlwyd/releases/latest | grep tag_name | cut -d '"' -f 4 | cut -c 2-) ALPINE_VERSION=3.15.0 glewlwyd-full_$(GLEWLWYD_VERSION)_alpine_$(ALPINE_VERSION)_x86_64.tar.gz: wget https://github.com/babelouest/glewlwyd/releases/download/v${GLEWLWYD_VERSION}/glewlwyd-full_$(GLEWLWYD_VERSION)_alpine_$(ALPINE_VERSION)_x86_64.tar.gz build: glewlwyd-full_$(GLEWLWYD_VERSION)_alpine_$(ALPINE_VERSION)_x86_64.tar.gz docker build -t babelouest/glewlwyd:$(GLEWLWYD_VERSION) --build-arg GLEWLWYD_VERSION=$(GLEWLWYD_VERSION) --build-arg ALPINE_VERSION=$(ALPINE_VERSION) . docker tag babelouest/glewlwyd:$(GLEWLWYD_VERSION) babelouest/glewlwyd:latest run: docker run --rm -it -p 4593:4593 babelouest/glewlwyd:latest my-config: docker run --rm -it -p 4593:4593 -v $(shell pwd)/config:/etc/glewlwyd babelouest/glewlwyd clean: rm -f glewlwyd-full_$(GLEWLWYD_VERSION)_alpine_$(ALPINE_VERSION)_x86_64.tar.gz docker rmi -f babelouest/glewlwyd:latest babelouest/glewlwyd:$(GLEWLWYD_VERSION) docker system prune -f ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/docker/README.md����������������������������������������������������������������0000664�0000000�0000000�00000001677�14156463140�0017053�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Docker image builder for Glewlwyd This docker image is based on Alpine Linux 3.15 and Glewlwyd latest alpine package available on https://github.com/babelouest/glewlwyd/releases/ . ## Build the docker image ```shell $ make build ``` ## Run the docker image with minimal configuration on your local machine, only for testing. ```shell $ make run $ # or $ docker run --rm -it -p 4593:4593 babelouest/glewlwyd ``` Then open the address [http://localhost:4593/](http://localhost:4593/) on your browser. ## Run the docker image using your configuration files This will use the configuration files placed in the subfolder folder [config](config). You can specify a different database, certificate files, `external_url`, UI settings, or any other configuration settings that will make this docker instance suitable for your needs. ```shell $ make my-config $ # or $ docker run --rm -it -p 4593:4593 -v $(pwd)/config:/etc/glewlwyd babelouest/glewlwyd ``` �����������������������������������������������������������������glewlwyd-2.6.1/docs/docker/config/������������������������������������������������������������������0000775�0000000�0000000�00000000000�14156463140�0017026�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/docker/config/config.json�������������������������������������������������������0000664�0000000�0000000�00000023221�14156463140�0021166�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "GlewlwydUrl": "./", "ProfileUrl": "profile.html", "AdminUrl": "index.html", "LoginUrl": "login.html", "PasswordMinLength": 8, "pattern": { "user": [{ "name": "username", "edit": false, "label": "admin.user-username", "placeholder": "admin.user-username-ph", "forceShow": true, "required": true, "profile-read": true, "profile-write": false },{ "name": "password", "type": "password", "profile": false, "edit": true, "label": "admin.user-password", "placeholder": "admin.user-password-ph", "placeholderConfirm": "admin.user-password-confirm-ph", "forceShow": true, "required": true, "profile-read": false, "profile-write": false },{ "name": "name", "profile": true, "edit": true, "forceShow": true, "label": "admin.user-name", "placeholder": "admin.user-name-ph", "profile-read": true, "profile-write": true },{ "name": "email", "edit": true, "forceShow": true, "label": "admin.user-email", "placeholder": "admin.user-email-ph", "profile-read": true, "profile-write": false },{ "name": "picture", "list": false, "type": "image/jpeg", "forceShow": true, "edit": true, "label": "admin.picture", "profile-read": true, "profile-write": false },{ "name": "scope", "list": true, "forceShow": true, "edit": true, "label": "admin.user-scope", "profile-read": true, "profile-write": false },{ "name": "enabled", "type": "boolean", "defaultValue": true, "forceShow": true, "edit": true, "label": "admin.user-enabled" }], "client": [{ "name": "client_id", "edit": false, "forceShow": true, "label": "admin.client-clientid", "placeholder": "admin.client-clientid-ph", "required": true },{ "name": "name", "edit": true, "forceShow": true, "label": "admin.client-name", "placeholder": "admin.client-name-ph" },{ "name": "enabled", "type": "boolean", "defaultValue": true, "edit": true, "forceShow": true, "label": "admin.client-enabled" },{ "name": "scope", "list": true, "edit": true, "forceShow": true, "label": "admin.client-scope" },{ "name": "redirect_uri", "list": true, "edit": true, "forceShow": true, "label": "admin.client-redirect-uri", "placeholder": "admin.client-redirect-uri-ph" },{ "name": "authorization_type", "list": true, "edit": true, "forceShow": true, "label": "admin.client-authorization-type", "listElements": ["code","token","id_token","password","none","client_credentials","refresh_token","delete_token","device_authorization"] },{ "name": "sector_identifier_uri", "edit": true, "forceShow": true, "label": "admin.client-sector-identifier-uri", "placeholder": "admin.client-sector-identifier-uri-ph" },{ "name": "confidential", "edit": true, "defaultValue": false, "forceShow": true, "type": "boolean", "label": "admin.client-confidential" },{ "name": "token_endpoint_auth_method", "list": true, "edit": true, "forceShow": true, "label": "admin.client-token_endpoint_auth_method", "listElements": ["", "none", "client_secret_post", "client_secret_basic", "client_secret_jwt", "private_key_jwt", "tls_client_auth", "self_signed_tls_client_auth"] },{ "name": "client_secret", "edit": true, "forceShow": true, "label": "admin.client-secret", "placeholder": "admin.client-secret-ph" },{ "name": "password", "type": "password", "profile": false, "edit": true, "forceShow": true, "label": "admin.client-password", "placeholder": "admin.client-password-ph", "placeholderConfirm": "admin.client-password-confirm-ph" },{ "name": "jwks", "type": "jwks", "edit": true, "forceShow": true, "label": "admin.client-jwks", "placeholder": "admin.client-jwks-ph" },{ "name": "jwks_uri", "edit": true, "forceShow": true, "label": "admin.client-jwks_uri", "placeholder": "admin.client-jwks_uri-ph" },{ "name": "pubkey", "type": "file", "edit": true, "forceShow": true, "label": "admin.client-pubkey" },{ "name": "enc", "edit": true, "forceShow": true, "label": "admin.client-enc", "placeholder": "admin.client-enc-ph", "listElements": ["A128CBC-HS256","A192CBC-HS384","A256CBC-HS512","A128GCM","A192GCM","A256GCM"] },{ "name": "alg", "edit": true, "forceShow": true, "label": "admin.client-alg", "placeholder": "admin.client-alg-ph", "listElements": ["RSA1_5","RSA-OAEP","RSA-OAEP-256","A128KW","A192KW","A256KW","dir","ECDH-ES","ECDH-ES+A128KW","ECDH-ES+A192KW","ECDH-ES+A256KW","A128GCMKW","A192GCMKW","A256GCMKW","PBES2-HS256+A128KW","PBES2-HS384+A192KW","PBES2-HS512+A256KW"] },{ "name": "alg_kid", "edit": true, "forceShow": true, "label": "admin.client-alg_kid", "placeholder": "admin.client-alg_kid-ph" },{ "name": "encrypt_code", "edit": true, "forceShow": true, "label": "admin.client-encrypt_code", "listElements": ["no","yes"] },{ "name": "encrypt_at", "edit": true, "forceShow": true, "label": "admin.client-encrypt_at", "listElements": ["no","yes"] },{ "name": "encrypt_userinfo", "edit": true, "forceShow": true, "label": "admin.client-encrypt_userinfo", "listElements": ["no","yes"] },{ "name": "encrypt_id_token", "edit": true, "forceShow": true, "label": "admin.client-encrypt_id_token", "listElements": ["no","yes"] },{ "name": "encrypt_refresh_token", "edit": true, "forceShow": true, "label": "admin.client-encrypt_refresh_token", "listElements": ["no","yes"] },{ "name": "resource", "edit": true, "forceShow": true, "type": "textarea", "list": true, "label": "admin.client-resource" },{ "name": "authorization_data_types", "list": true, "edit": true, "forceShow": true, "label": "admin.client-authorization-data-types", "placeholder": "admin.client-authorization-data-types-ph" },{ "name": "tls_client_auth_san_dns", "edit": true, "forceShow": true, "label": "admin.client-tls_client_auth_san_dns", "placeholder": "admin.client-tls_client_auth_san_dns-ph" },{ "name": "tls_client_auth_san_uri", "edit": true, "forceShow": true, "label": "admin.client-tls_client_auth_san_uri", "placeholder": "admin.client-tls_client_auth_san_uri-ph" },{ "name": "tls_client_auth_san_ip", "edit": true, "forceShow": true, "label": "admin.client-tls_client_auth_san_ip", "placeholder": "admin.client-tls_client_auth_san_ip-ph" },{ "name": "tls_client_auth_san_email", "edit": true, "forceShow": true, "label": "admin.client-tls_client_auth_san_email", "placeholder": "admin.client-tls_client_auth_san_email-ph" }] }, "defaultScheme": "", "sessionSchemes": [ ], "profilePicture": { "property": "picture", "type": "image/*" }, "register": [ ], "register-complete": [ ], "providerMainstreamList": [ { "help_url": "https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/", "name": "Facebook", "logo_fa": "fab fa-facebook", "provider_type": "oauth2", "response_type": "code", "userinfo_endpoint": "https://graph.facebook.com/v5.0/me", "auth_endpoint": "https://www.facebook.com/v5.0/dialog/oauth", "token_endpoint": "https://graph.facebook.com/v5.0/oauth/access_token", "userid_property": "id" }, { "help_url": "https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/", "name": "GitHub", "logo_fa": "fab fa-github", "provider_type": "oauth2", "response_type": "code", "userinfo_endpoint": "https://api.github.com/user", "auth_endpoint": "https://github.com/login/oauth/authorize", "token_endpoint": "https://github.com/login/oauth/access_token", "scope": "read:user", "userid_property": "id" }, { "help_url": "https://docs.gitlab.com/ee/integration/oauth_provider.html", "name": "GitLab", "logo_fa": "fab fa-gitlab", "provider_type": "oauth2", "response_type": "code", "userinfo_endpoint": "https://gitlab.com/api/v4/user", "auth_endpoint": "https://gitlab.com/oauth/authorize", "token_endpoint": "https://gitlab.com/oauth/token", "scope": "read_user profile", "userid_property": "id" }, { "help_url": "https://developers.google.com/identity/protocols/OAuth2", "name": "Google", "logo_fa": "fab fa-google", "provider_type": "oidc", "response_type": "code", "config_endpoint": "https://accounts.google.com/.well-known/openid-configuration" }, { "help_url": "https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-protocols-oauth-code#register-your-application-with-your-ad-tenant", "name": "Microsoft", "logo_fa": "fab fa-microsoft", "provider_type": "oidc", "response_type": "code", "config_endpoint": "https://login.microsoftonline.com/{tenantid}/.well-known/openid-configuration" } ] } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/docker/config/glewlwyd.conf�����������������������������������������������������0000664�0000000�0000000�00000007350�14156463140�0021540�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # # Glewlwyd SSO Authorization Server # # Copyright 2016-2019 Nicolas Mora <mail@babelouest.org> # Gnu Public License V3 <http://fsf.org/> # # # port to open for remote commands port=4593 # bind to IPV4 address #bind_address="127.0.0.1" # external url to access to this instance external_url="http://localhost:4593" # login url relative to external url login_url="login.html" # url prefix api_prefix="api" # path to static files for /webapp url static_files_path="/usr/share/glewlwyd/webapp/" # access-control-allow-origin value allow_origin="*" # log mode (console, syslog, journald, file) log_mode="console" # log level: NONE, ERROR, WARNING, INFO, DEBUG log_level="DEBUG" # output to log file (required if log_mode is file) log_file="/var/log/glewlwyd.log" # cookie domain #cookie_domain="localhost" # cookie_secure, this options SHOULD be set to 1, set this to 0 to test glewlwyd on insecure connection http instead of https cookie_secure=0 # session expiration, default is 4 weeks session_expiration=2419200 # session key session_key="GLEWLWYD2_SESSION_ID" # add http header X-Frame-Options: deny, default true add_x_frame_option_header_deny=true # admin scope name admin_scope="g_admin" # profile scope name profile_scope="g_profile" # user_module path user_module_path="/usr/lib/glewlwyd/user" # user_middleware_module path user_middleware_module_path="user_middleware" # client_module path client_module_path="/usr/lib/glewlwyd/client" # user_auth_scheme_module path user_auth_scheme_module_path="/usr/lib/glewlwyd/scheme" # plugin_module path plugin_module_path="/usr/lib/glewlwyd/plugin" # TLS/SSL configuration values use_secure_connection=false secure_connection_key_file="/etc/glewlwyd/server.key" secure_connection_pem_file="/etc/glewlwyd/server.crt" secure_connection_ca_file="/etc/glewlwyd/ca.crt" # Algorithms available are SHA1, SHA256, SHA512, MD5, default is MD5 hash_algorithm = "SHA512" # MariaDB/Mysql database connection #database = #{ # type = "mariadb" # host = "db_host" # user = "glewlwyd" # password = "glewlwyd" # dbname = "glewlwyd" # port = 0 #} # SQLite database connection database = { type = "sqlite3" path = "/var/cache/glewlwyd/glewlwyd.db" # path = "/tmp/glewlwyd.db" } # PostgreSQL database connection #database = #{ # type = "postgre" # conninfo = "host=localhost dbname=glewlwyd user=glewlwyd password=glewlwyd" #}; # Prometheus metrics parameters #metrics_endpoint = false #metrics_bind_address = "127.0.0.1" #metrics_endpoint_port = 4594 #metrics_endpoint_admin_session = false # mime types for webapp files static_files_mime_types = ( { extension = ".html" mime_type = "text/html" compress = 1 }, { extension = ".css" mime_type = "text/css" compress = 1 }, { extension = ".js" mime_type = "application/javascript" compress = 1 }, { extension = ".json" mime_type = "application/json" compress = 1 }, { extension = ".png" mime_type = "image/png" compress = 0 }, { extension = ".jpg" mime_type = "image/jpeg" compress = 0 }, { extension = ".jpeg" mime_type = "image/jpeg" compress = 0 }, { extension = ".ttf" mime_type = "font/ttf" compress = 0 }, { extension = ".woff" mime_type = "font/woff" compress = 0 }, { extension = ".woff2" mime_type = "font/woff2" compress = 0 }, { extension = ".otf" mime_type = "font/otf" compress = 0 }, { extension = ".eot" mime_type = "application/vnd.ms-fontobject" compress = 0 }, { extension = ".map" mime_type = "application/octet-stream" compress = 0 }, { extension = ".ico" mime_type = "image/x-icon" compress = 0 } ) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/docker/entrypoint.sh������������������������������������������������������������0000775�0000000�0000000�00000000401�14156463140�0020326�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/bash if [ ! -f "/var/cache/glewlwyd/glewlwyd.db" ]; then mkdir /var/cache/glewlwyd/ sqlite3 /var/cache/glewlwyd/glewlwyd.db < /usr/share/glewlwyd/docs/database/init.sqlite3.sql fi glewlwyd --config-file=/etc/glewlwyd/glewlwyd.conf -mconsole -e ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/fail2ban/�����������������������������������������������������������������������0000775�0000000�0000000�00000000000�14156463140�0015770�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/fail2ban/README.md��������������������������������������������������������������0000664�0000000�0000000�00000000155�14156463140�0017250�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Fail2ban filter Read [Fail2ban filter](../INSTALL.md#fail2ban-filter) documentation for more information. �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/fail2ban/glewlwyd-log.conf������������������������������������������������������0000664�0000000�0000000�00000002243�14156463140�0021255�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Fail2Ban filter for Glewlwyd # # Author: Nicolas Mora # [Definition] failregex = ^.* - Glewlwyd WARNING: Security - Authorization invalid for username .* at IP Address <HOST> ^.* - Glewlwyd WARNING: Security - Authorization invalid for client_id .* at IP Address <HOST> ^.* - Glewlwyd WARNING: Security - Code invalid at IP Address <HOST> ^.* - Glewlwyd WARNING: Security - Token invalid at IP Address <HOST> ^.* - Glewlwyd WARNING: Security - Scheme email - code sent for username .* at IP Address <HOST> ^.* - Glewlwyd WARNING: Security - Register new user - code sent to email .* at IP Address <HOST> ^.* - Glewlwyd WARNING: Security - Verify e-mail - code invalid at IP Address <HOST> ^.* - Glewlwyd WARNING: Security - Update e-mail - token sent to email .* at IP Address <HOST> ^.* - Glewlwyd WARNING: Security - Update e-mail - token invalid at IP Address <HOST> ^.* - Glewlwyd WARNING: Security - Reset credentials - token invalid at IP Address <HOST> ^.* - Glewlwyd WARNING: Security - Reset credentials - code invalid at IP Address <HOST> ignoreregex = �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/fail2ban/glewlwyd-syslog.conf���������������������������������������������������0000664�0000000�0000000�00000002521�14156463140�0022013�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Fail2Ban filter for Glewlwyd # # Author: Nicolas Mora, Robert Clayton [INCLUDES] # # load the 'common.conf' list of fail2ban upstream maintained prefixes # before = common.conf [Definition] # # declare the daemon name so common.conf variables will match # _daemon = Glewlwyd failregex = ^.* %(__prefix_line)sSecurity - Authorization invalid for username .* at IP Address <HOST> ^.* %(__prefix_line)sSecurity - Authorization invalid for client_id .* at IP Address <HOST> ^.* %(__prefix_line)sSecurity - Code invalid at IP Address <HOST> ^.* %(__prefix_line)sSecurity - Token invalid at IP Address <HOST> ^.* %(__prefix_line)sSecurity - Scheme email - code sent for username .* at IP Address <HOST> ^.* %(__prefix_line)sSecurity - Register new user - code sent to email .* at IP Address <HOST> ^.* %(__prefix_line)sSecurity - Verify e-mail - code invalid at IP Address <HOST> ^.* %(__prefix_line)sSecurity - Update e-mail - token sent to email .* at IP Address <HOST> ^.* %(__prefix_line)sSecurity - Update e-mail - token invalid at IP Address <HOST> ^.* %(__prefix_line)sSecurity - Reset credentials - token invalid at IP Address <HOST> ^.* %(__prefix_line)sSecurity - Reset credentials - code invalid at IP Address <HOST> ignoreregex = �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/fail2ban/jail.local�������������������������������������������������������������0000664�0000000�0000000�00000000450�14156463140�0017722�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[glewlwyd] enabled = true filter = glewlwyd-log # (change to glewlwyd-syslog if you use the syslog filter/logging) logpath = /var/log/glewlwyd.log # (change to /var/log/syslog if you use the syslog filter/logging) port = https,4593 # the TCP port where Glewlwyd is available from outside ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/glewlwyd-init�������������������������������������������������������������������0000775�0000000�0000000�00000001675�14156463140�0017050�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh ### BEGIN INIT INFO # Provides: glewlwyd # Required-Start: $network $time # Required-Stop: $network $time # Should-Start: # Should-Stop: # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Starts the glewlwyd daemon # Description: ### END INIT INFO user=pi group=pi prefix=/usr/bin exec_prefix=${prefix} glewlwyd=${prefix}/glewlwyd glewlwydconf=--config=/etc/glewlwyd.conf pidfile=/var/run/glewlwyd.pid case "$1" in stop) echo -n "Stopping Glewlwyd " start-stop-daemon --stop --pidfile $pidfile echo "OK" ;; start) echo -n "Starting Glewlwyd " touch $pidfile chown $user:$group $pidfile start-stop-daemon --start --quiet --make-pidfile --pidfile $pidfile \ --chuid $user:$group --background --exec $glewlwyd -- $glewlwydconf echo "OK" ;; restart) $0 stop $0 start ;; *) echo "Usage: $0 {start|stop|restart}" exit 1 ;; esac �������������������������������������������������������������������glewlwyd-2.6.1/docs/glewlwyd.8����������������������������������������������������������������������0000664�0000000�0000000�00000003210�14156463140�0016235�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.48.1. .TH GLEWLWYD "8" "November 2021" "glewlwyd 2.6.0" "System Administration Utilities" .SH NAME glewlwyd \- Single-Sign-On (SSO) server with multiple factor authentication .SH DESCRIPTION Glewlwyd Single\-Sign\-On (SSO) server with multiple factor authentication .PP Version 2.6.0 .SH COPYRIGHT Copyright 2016\-2021 Nicolas Mora <mail@babelouest.org> .PP This program is free software; you can redistribute it and/or modify it under the terms of the GNU GENERAL PUBLIC LICENSE License as published by the Free Software Foundation; version 3 of the License. .PP Command\-line options: .PP \fB\-c\fR \fB\-\-config\-file\fR PATH .IP Path to configuration file .PP \fB\-e\fR \fB\-\-env\-variables\fR .IP Use environment variables to configure Glewlwyd .PP \fB\-p\fR \fB\-\-port\fR PORT .IP Port to listen to .PP \fB\-u\fR \fB\-\-url\-prefix\fR PREFIX .IP API URL prefix .PP \fB\-m\fR \fB\-\-log\-mode\fR MODE .IP Log modes available: console, syslog or file If you want multiple modes, separate them with a comma "," default: console .PP \fB\-l\fR \fB\-\-log\-level\fR LEVEL .IP Log levels available: NONE, ERROR, WARNING, INFO, DEBUG default: INFO .PP \fB\-f\fR \fB\-\-log\-file\fR PATH .IP Path for log file if log mode file is specified .PP \fB\-v\fR \fB\-\-version\fR .IP Print Glewlwyd's current version .PP \fB\-h\fR \fB\-\-help\fR .IP Print this message .SH "SEE ALSO" The full documentation for .B glewlwyd is maintained as a Texinfo manual. If the .B info and .B glewlwyd programs are properly installed at your site, the command .IP .B info glewlwyd .PP should give you access to the complete manual. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/glewlwyd.conf.sample������������������������������������������������������������0000664�0000000�0000000�00000007375�14156463140�0020313�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # # Glewlwyd SSO Authorization Server # # Copyright 2016-2019 Nicolas Mora <mail@babelouest.org> # Gnu Public License V3 <http://fsf.org/> # # # port to open for remote commands port=4593 # bind to IPV4 address #bind_address="127.0.0.1" # external url to access to this instance external_url="http://localhost:4593" # login url relative to external url login_url="login.html" # api prefix api_prefix="api" # path to static files for /webapp url static_files_path="/usr/share/glewlwyd/webapp/" # access-control-allow-origin value allow_origin="*" # log mode (console, syslog, journald, file) log_mode="console" # log level: NONE, ERROR, WARNING, INFO, DEBUG log_level="INFO" # output to log file (required if log_mode is file) log_file="/var/log/glewlwyd.log" # cookie domain cookie_domain="localhost" # cookie_secure, this options SHOULD be set to 1, set this to 0 to test glewlwyd on insecure connection http instead of https cookie_secure=0 # session expiration, default is 4 weeks session_expiration=2419200 # session key session_key="GLEWLWYD2_SESSION_ID" # add http header X-Frame-Options: deny, default true add_x_frame_option_header_deny=true # admin scope name admin_scope="g_admin" # profile scope name profile_scope="g_profile" # user_module path user_module_path="/usr/lib/glewlwyd/user" # user_middleware_module path user_middleware_module_path="/usr/lib/glewlwyd/user_middleware" # client_module path client_module_path="/usr/lib/glewlwyd/client" # user_auth_scheme_module path user_auth_scheme_module_path="/usr/lib/glewlwyd/scheme" # plugin_module path plugin_module_path="/usr/lib/glewlwyd/plugin" # can a user delete its account. Values available are "no", "delete" or "disable" #delete_profile="delete" # TLS/SSL configuration values use_secure_connection=false secure_connection_key_file="/etc/ssl/certs/cert.key" secure_connection_pem_file="/etc/ssl/certs/cert.pem" secure_connection_ca_file="/etc/ssl/certs/ca.crt" # Algorithms available are SHA1, SHA256, SHA512, MD5, default is SHA256 hash_algorithm = "SHA512" # MariaDB/Mysql database connection #database = #{ # type = "mariadb" # host = "localhost" # user = "glewlwyd" # password = "glewlwyd" # dbname = "glewlwyd" # port = 0 #} # SQLite database connection database = { type = "sqlite3" path = "/var/cache/glewlwyd/glewlwyd.db" }; # PostgreSQL database connection #database = #{ # type = "postgre" # conninfo = "dbname = glewlwyd" #} # Prometheus metrics parameters #metrics_endpoint = false #metrics_bind_address = "127.0.0.1" #metrics_endpoint_port = 4594 #metrics_endpoint_admin_session = false # mime types for webapp files static_files_mime_types = ( { extension = ".html" mime_type = "text/html" compress = 1 }, { extension = ".css" mime_type = "text/css" compress = 1 }, { extension = ".js" mime_type = "application/javascript" compress = 1 }, { extension = ".json" mime_type = "application/json" compress = 1 }, { extension = ".png" mime_type = "image/png" compress = 0 }, { extension = ".jpg" mime_type = "image/jpeg" compress = 0 }, { extension = ".jpeg" mime_type = "image/jpeg" compress = 0 }, { extension = ".ttf" mime_type = "font/ttf" compress = 0 }, { extension = ".woff" mime_type = "font/woff" compress = 0 }, { extension = ".woff2" mime_type = "font/woff2" compress = 0 }, { extension = ".otf" mime_type = "font/otf" compress = 0 }, { extension = ".eot" mime_type = "application/vnd.ms-fontobject" compress = 0 }, { extension = ".map" mime_type = "application/octet-stream" compress = 0 }, { extension = ".ico" mime_type = "image/x-icon" compress = 0 } ) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/glewlwyd.service����������������������������������������������������������������0000664�0000000�0000000�00000000473�14156463140�0017536�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[Unit] Description=Glewlwyd OAuth2 authentication provider After=network.target [Service] Type=simple EnvironmentFile=-/etc/glewlwyd/glewlwyd.conf ExecStart=/usr/bin/glewlwyd --config-file=/etc/glewlwyd/glewlwyd.conf User=pi KillMode=process Restart=on-failure [Install] WantedBy=multi-user.target Alias=glewlwyd �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/resources/����������������������������������������������������������������������0000775�0000000�0000000�00000000000�14156463140�0016324�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/resources/README.md�������������������������������������������������������������0000664�0000000�0000000�00000000622�14156463140�0017603�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Token validation for resource services This folder contains example of token validation for a resource service. A token is provided in the HTTP request as described in the [RFC 6750](https://tools.ietf.org/html/rfc6750) concerning the Bearer token. If you want to add other examples in different languages or different contexts for a Glewlwyd token, feel free to post a pull request or contact me. ��������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/resources/ulfius/���������������������������������������������������������������0000775�0000000�0000000�00000000000�14156463140�0017633�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/resources/ulfius/README.md������������������������������������������������������0000664�0000000�0000000�00000012753�14156463140�0021122�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Token validation for resource service based on [Ulfius](https://github.com/babelouest/ulfius) framework These files contain an authentication callback for Ulfius framework to validate a Glewlwyd access token or OIDC access tokens with the correct scope. [ulfius](https://github.com/babelouest/ulfius), [rhonabwy](https://github.com/babelouest/rhonabwy) and [jansson](https://github.com/akheron/jansson) are required. ## Validate Glewlwyd OAuth2 access token The provided files are `glewlwyd_resource.h` and `glewlwyd_resource.c`. The Ulfius callback function is: ```C /** * * check if bearer token has some of the specified scope * */ int callback_check_glewlwyd_access_token (const struct _u_request * request, struct _u_response * response, void * user_data); ``` To use this file, you must create a `struct _glewlwyd_resource_config` with your specific parameters: ```C struct _glewlwyd_resource_config { int method; // Values are G_METHOD_HEADER, G_METHOD_BODY or G_METHOD_URL for the access_token location, see https://tools.ietf.org/html/rfc6750 char * oauth_scope; // Scope values required by the resource, multiple values must be separated by a space character jwt_t * jwt; // The jwt used to decode and validate an access token jwa_alg alg; // The algorithm used to encode a token, see https://babelouest.github.io/rhonabwy/ char * realm; // Optional, a realm value that will be sent back to the client unsigned short accept_access_token; // required, accept type access_token unsigned short accept_client_token; // required, accept type client_token }; ``` Then, you use `callback_check_glewlwyd_access_token` as authentication callback for your ulfius endpoints that need to validate a glewlwyd access_token, example: ```C struct _glewlwyd_resource_config g_config; g_config.method = G_METHOD_HEADER; g_config.oauth_scope = "scope1"; r_jwt_init(&g_config.jwt); r_jwt_set_sign_alg(g_config.jwt, R_JWA_ALG_HS256); r_jwt_add_sign_key_symmetric(g_config.jwt, "secret", o_strlen("secret") g_config.alg = R_JWA_ALG_HS256; g_config.realm = "example"; g_config.accept_access_token = 1; g_config.accept_client_token = 0; // Example, add an authentication callback callback_check_glewlwyd_access_token for the endpoint GET "/api/resource/*" ulfius_add_endpoint_by_val(instance, "GET", "/api", "/resource/*", &callback_check_glewlwyd_access_token, (void*)&g_config); ``` On success, the variable `response->shared_data` will be provided with a `json_t *` objet with the following format: ```Javascript { "username": "user", // username whose grant access is granted "scope":["scope1","scope2"] // Scope list the user is granted for this access token } ``` On error, the next callback function will not be called and instead, the client will receive an error 401 with the header `WWW-Authenticate` filled with the error. ## Validate Glewlwyd OpenID Connect access token The provided files are `oidc_resource.h` and `oidc_resource.c`. The Ulfius callback function is: ```C /** * * check if bearer token has some of the specified scope * */ int callback_check_glewlwyd_oidc_access_token (const struct _u_request * request, struct _u_response * response, void * user_data); ``` To use this file, you must create a `struct _oidc_resource_config` with your specific parameters: ```C struct _oidc_resource_config { int method; // Values are G_METHOD_HEADER, G_METHOD_BODY or G_METHOD_URL for the access_token location, see https://tools.ietf.org/html/rfc6750 char * oauth_scope; // Scope values required by the resource, multiple values must be separated by a space character jwt_t * jwt; // The jwt used to decode and validate an access token jwa_alg alg; // The algorithm used to encode a token, see https://babelouest.github.io/rhonabwy/ char * realm; // Optional, a realm value that will be sent back to the client unsigned short accept_access_token; // required, accept type access_token unsigned short accept_client_token; // required, accept type client_token }; ``` Then, you use `callback_check_glewlwyd_oidc_access_token` as authentication callback for your ulfius endpoints that need to validate a glewlwyd access_token, example: ```C struct _oidc_resource_config g_config; g_config.method = G_METHOD_HEADER; g_config.oauth_scope = "scope1"; r_jwt_init(&g_config.jwt); r_jwt_set_sign_alg(g_config.jwt, R_JWA_ALG_HS256); r_jwt_add_sign_key_symmetric(g_config.jwt, "secret", o_strlen("secret") g_config.alg = R_JWA_ALG_HS256; g_config.realm = "example"; g_config.accept_access_token = 1; g_config.accept_client_token = 0; // Example, add an authentication callback callback_check_glewlwyd_oidc_access_token for the endpoint GET "/api/resource/*" ulfius_add_endpoint_by_val(instance, "GET", "/api", "/resource/*", &callback_check_glewlwyd_oidc_access_token, (void*)&g_config); ``` On success, the variable `response->shared_data` will be provided with a `json_t *` objet with the following format: ```Javascript { "sub": "user", // subject this grant access is granted for "aud": "client1", // client this grant access is granted to "scope":["scope1","scope2"] // Scope list the user is granted for this access token } ``` On error, the next callback function will not be called and instead, the client will receive an error 401 with the header `WWW-Authenticate` filled with the error. ���������������������glewlwyd-2.6.1/docs/resources/ulfius/glewlwyd_resource.c��������������������������������������������0000664�0000000�0000000�00000025337�14156463140�0023556�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * * Glewlwyd SSO Access Token token check * * Copyright 2016-2020 Nicolas Mora <mail@babelouest.org> * * Version 20200508 * * The MIT License (MIT) * * 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. * */ #include <string.h> #include <time.h> #include <orcania.h> #include <ulfius.h> #include "glewlwyd_resource.h" /** * Check if the result json object has a "result" element that is equal to value */ static int check_result_value(json_t * result, const int value) { return (result != NULL && json_is_object(result) && json_object_get(result, "result") != NULL && json_is_integer(json_object_get(result, "result")) && json_integer_value(json_object_get(result, "result")) == value); } /** * Validates if an access_token grants has a valid scope * return the final scope list on success */ static json_t * access_token_check_scope(struct _glewlwyd_resource_config * config, json_t * j_access_token) { int i, scope_count_token, scope_count_expected; char ** scope_list_token, ** scope_list_expected; json_t * j_res = NULL, * j_scope_final_list = json_array(); if (j_scope_final_list != NULL) { if (j_access_token != NULL) { scope_count_token = split_string(json_string_value(json_object_get(j_access_token, "scope")), " ", &scope_list_token); if (o_strlen(config->oauth_scope)) { scope_count_expected = split_string(config->oauth_scope, " ", &scope_list_expected); if (scope_count_token > 0 && scope_count_expected > 0) { for (i=0; scope_count_expected > 0 && scope_list_expected[i] != NULL; i++) { if (string_array_has_value((const char **)scope_list_token, scope_list_expected[i])) { json_array_append_new(j_scope_final_list, json_string(scope_list_expected[i])); } } if (json_array_size(j_scope_final_list) > 0) { j_res = json_pack("{sisO}", "result", G_TOKEN_OK, "scope", j_scope_final_list); } else { j_res = json_pack("{si}", "result", G_TOKEN_ERROR_INSUFFICIENT_SCOPE); } } else { j_res = json_pack("{si}", "result", G_TOKEN_ERROR_INTERNAL); } free_string_array(scope_list_expected); } else { j_res = json_pack("{sis[]}", "result", G_TOKEN_OK, "scope"); } free_string_array(scope_list_token); } else { j_res = json_pack("{si}", "result", G_TOKEN_ERROR_INVALID_TOKEN); } } else { j_res = json_pack("{si}", "result", G_TOKEN_ERROR_INTERNAL); } json_decref(j_scope_final_list); return j_res; } /** * Validates if an access_token grants has valid parameters: * - username: non empty string * - type: match "access_token" * - iat + expires_in < now */ static int access_token_check_validity(struct _glewlwyd_resource_config * config, json_t * j_access_token) { time_t now; json_int_t expiration; int res; if (j_access_token != NULL) { // Token is valid, check type and expiration date time(&now); expiration = json_integer_value(json_object_get(j_access_token, "iat")) + json_integer_value(json_object_get(j_access_token, "expires_in")); if (now < expiration && json_object_get(j_access_token, "type") != NULL && json_is_string(json_object_get(j_access_token, "type"))) { if (config->accept_access_token && 0 == o_strcmp("access_token", json_string_value(json_object_get(j_access_token, "type"))) && json_object_get(j_access_token, "username") != NULL && json_is_string(json_object_get(j_access_token, "username")) && json_string_length(json_object_get(j_access_token, "username")) > 0) { res = G_TOKEN_OK; } else if (config->accept_client_token && 0 == o_strcmp("client_token", json_string_value(json_object_get(j_access_token, "type"))) && json_object_get(j_access_token, "client_id") != NULL && json_is_string(json_object_get(j_access_token, "client_id")) && json_string_length(json_object_get(j_access_token, "client_id")) > 0) { res = G_TOKEN_OK; } else { res = G_TOKEN_ERROR_INVALID_REQUEST; } } else { res = G_TOKEN_ERROR_INVALID_REQUEST; } } else { res = G_TOKEN_ERROR_INVALID_TOKEN; } return res; } /** * validates if the token value is a valid jwt and has a valid signature */ static json_t * access_token_check_signature(struct _glewlwyd_resource_config * config, const char * token_value) { json_t * j_return, * j_grants; jwt_t * jwt = r_jwt_copy(config->jwt); if (token_value != NULL) { if (r_jwt_parse(jwt, token_value, 0) == RHN_OK && r_jwt_verify_signature(jwt, NULL, 0) == RHN_OK && r_jwt_get_sign_alg(jwt) == config->alg) { j_grants = r_jwt_get_full_claims_json_t(jwt); if (j_grants != NULL) { j_return = json_pack("{siso}", "result", G_TOKEN_OK, "grants", j_grants); } else { j_return = json_pack("{si}", "result", G_TOKEN_ERROR); } } else { j_return = json_pack("{si}", "result", G_TOKEN_ERROR_INVALID_TOKEN); } } else { j_return = json_pack("{si}", "result", G_TOKEN_ERROR_INVALID_TOKEN); } r_jwt_free(jwt); return j_return; } /** * check if bearer token has some of the specified scope */ int callback_check_glewlwyd_access_token (const struct _u_request * request, struct _u_response * response, void * user_data) { struct _glewlwyd_resource_config * config = (struct _glewlwyd_resource_config *)user_data; json_t * j_access_token = NULL, * j_res_scope; int res = U_CALLBACK_UNAUTHORIZED, res_validity; const char * token_value = NULL; char * response_value = NULL; if (config != NULL) { switch (config->method) { case G_METHOD_HEADER: if (u_map_get_case(request->map_header, HEADER_AUTHORIZATION) != NULL) { if (o_strstr(u_map_get_case(request->map_header, HEADER_AUTHORIZATION), HEADER_PREFIX_BEARER) == u_map_get_case(request->map_header, HEADER_AUTHORIZATION)) { token_value = u_map_get_case(request->map_header, HEADER_AUTHORIZATION) + o_strlen(HEADER_PREFIX_BEARER); } } break; case G_METHOD_BODY: if (o_strstr(u_map_get_case(request->map_header, ULFIUS_HTTP_HEADER_CONTENT), MHD_HTTP_POST_ENCODING_FORM_URLENCODED) != NULL && u_map_get(request->map_post_body, BODY_URL_PARAMETER) != NULL) { token_value = u_map_get(request->map_post_body, BODY_URL_PARAMETER); } break; case G_METHOD_URL: token_value = u_map_get(request->map_url, BODY_URL_PARAMETER); break; } if (token_value != NULL) { j_access_token = access_token_check_signature(config, token_value); if (check_result_value(j_access_token, G_TOKEN_OK)) { res_validity = access_token_check_validity(config, json_object_get(j_access_token, "grants")); if (res_validity == G_TOKEN_OK) { j_res_scope = access_token_check_scope(config, json_object_get(j_access_token, "grants")); if (check_result_value(j_res_scope, G_TOKEN_ERROR_INSUFFICIENT_SCOPE)) { response_value = msprintf(HEADER_PREFIX_BEARER "%s%s%serror=\"insufficient_scope\",error_description=\"The scope is invalid\"", (config->realm!=NULL?"realm=":""), (config->realm!=NULL?config->realm:""), (config->realm!=NULL?",":"")); u_map_put(response->map_header, HEADER_RESPONSE, response_value); o_free(response_value); } else if (!check_result_value(j_res_scope, G_TOKEN_OK)) { response_value = msprintf(HEADER_PREFIX_BEARER "%s%s%serror=\"invalid_request\",error_description=\"Internal server error\"", (config->realm!=NULL?"realm=":""), (config->realm!=NULL?config->realm:""), (config->realm!=NULL?",":"")); u_map_put(response->map_header, HEADER_RESPONSE, response_value); o_free(response_value); } else { if (ulfius_set_response_shared_data(response, json_pack("{sssO}", "username", json_string_value(json_object_get(json_object_get(j_access_token, "grants"), "username")), "scope", json_object_get(j_res_scope, "scope")), (void (*)(void *))&json_decref) != U_OK) { res = U_CALLBACK_ERROR; } else { res = U_CALLBACK_CONTINUE; } } json_decref(j_res_scope); } else if (res_validity == G_TOKEN_ERROR_INVALID_TOKEN) { response_value = msprintf(HEADER_PREFIX_BEARER "%s%s%serror=\"invalid_request\",error_description=\"The access token is invalid\"", (config->realm!=NULL?"realm=":""), (config->realm!=NULL?config->realm:""), (config->realm!=NULL?",":"")); u_map_put(response->map_header, HEADER_RESPONSE, response_value); o_free(response_value); } else { response_value = msprintf(HEADER_PREFIX_BEARER "%s%s%serror=\"invalid_request\",error_description=\"Internal server error\"", (config->realm!=NULL?"realm=":""), (config->realm!=NULL?config->realm:""), (config->realm!=NULL?",":"")); u_map_put(response->map_header, HEADER_RESPONSE, response_value); o_free(response_value); } } else { response_value = msprintf(HEADER_PREFIX_BEARER "%s%s%serror=\"invalid_request\",error_description=\"The access token is invalid\"", (config->realm!=NULL?"realm=":""), (config->realm!=NULL?config->realm:""), (config->realm!=NULL?",":"")); u_map_put(response->map_header, HEADER_RESPONSE, response_value); o_free(response_value); } json_decref(j_access_token); } else { response_value = msprintf(HEADER_PREFIX_BEARER "%s%s%serror=\"invalid_token\",error_description=\"The access token is missing\"", (config->realm!=NULL?"realm=":""), (config->realm!=NULL?config->realm:""), (config->realm!=NULL?",":"")); u_map_put(response->map_header, HEADER_RESPONSE, response_value); o_free(response_value); } } return res; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/resources/ulfius/glewlwyd_resource.h��������������������������������������������0000664�0000000�0000000�00000004251�14156463140�0023553�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * * Glewlwyd SSO Access Token token check * * Copyright 2016-2020 Nicolas Mora <mail@babelouest.org> * * Version 20200508 * * The MIT License (MIT) * * 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. * */ #include <jansson.h> #include <rhonabwy.h> #define G_TOKEN_OK 0 #define G_TOKEN_ERROR 1 #define G_TOKEN_ERROR_INTERNAL 2 #define G_TOKEN_ERROR_INVALID_REQUEST 3 #define G_TOKEN_ERROR_INVALID_TOKEN 4 #define G_TOKEN_ERROR_INSUFFICIENT_SCOPE 5 #define G_METHOD_HEADER 0 #define G_METHOD_BODY 1 #define G_METHOD_URL 2 #define HEADER_PREFIX_BEARER "Bearer " #define HEADER_RESPONSE "WWW-Authenticate" #define HEADER_AUTHORIZATION "Authorization" #define BODY_URL_PARAMETER "access_token" struct _glewlwyd_resource_config { int method; char * oauth_scope; jwt_t * jwt; jwa_alg alg; char * realm; unsigned short accept_access_token; unsigned short accept_client_token; }; /** * * check if bearer token has some of the specified scope * */ int callback_check_glewlwyd_access_token (const struct _u_request * request, struct _u_response * response, void * user_data); �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/resources/ulfius/oidc_resource.c������������������������������������������������0000664�0000000�0000000�00000045110�14156463140�0022625�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * * Glewlwyd SSO Access Token token check * * Copyright 2016-2021 Nicolas Mora <mail@babelouest.org> * * Version 20211029 * * The MIT License (MIT) * * 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. * */ #include <string.h> #include <time.h> #include <orcania.h> #include <ulfius.h> #include <jansson.h> #include "oidc_resource.h" /** * Check if the result json object has a "result" element that is equal to value */ static int check_result_value(json_t * result, const int value) { return (result != NULL && json_is_object(result) && json_object_get(result, "result") != NULL && json_is_integer(json_object_get(result, "result")) && json_integer_value(json_object_get(result, "result")) == value); } /** * Validates if an access_token grants has a valid scope * return the final scope list on success */ static json_t * access_token_check_scope(struct _oidc_resource_config * config, json_t * j_access_token) { int i, scope_count_token, scope_count_expected; char ** scope_list_token, ** scope_list_expected; json_t * j_res = NULL, * j_scope_final_list = json_array(); if (j_scope_final_list != NULL) { if (j_access_token != NULL) { scope_count_token = split_string(json_string_value(json_object_get(j_access_token, "scope")), " ", &scope_list_token); if (o_strlen(config->oauth_scope)) { scope_count_expected = split_string(config->oauth_scope, " ", &scope_list_expected); if (scope_count_token > 0 && scope_count_expected > 0) { for (i=0; scope_count_expected > 0 && scope_list_expected[i] != NULL; i++) { if (string_array_has_value((const char **)scope_list_token, scope_list_expected[i])) { json_array_append_new(j_scope_final_list, json_string(scope_list_expected[i])); } } if (json_array_size(j_scope_final_list) > 0) { j_res = json_pack("{sisO}", "result", G_TOKEN_OK, "scope", j_scope_final_list); } else { j_res = json_pack("{si}", "result", G_TOKEN_ERROR_INSUFFICIENT_SCOPE); } } else { j_res = json_pack("{si}", "result", G_TOKEN_ERROR_INTERNAL); } free_string_array(scope_list_expected); } else { j_res = json_pack("{sisO}", "result", G_TOKEN_OK, "scope", json_object_get(j_access_token, "scope")); } free_string_array(scope_list_token); } else { j_res = json_pack("{si}", "result", G_TOKEN_ERROR_INVALID_TOKEN); } } else { j_res = json_pack("{si}", "result", G_TOKEN_ERROR_INTERNAL); } json_decref(j_scope_final_list); return j_res; } /** * Validates if an access_token grants has valid parameters: * - sub: non empty string * - aud: non empty string * - type: match "access_token" or "client_token" * - exp < now */ static int access_token_check_validity(struct _oidc_resource_config * config, json_t * j_access_token) { time_t now; json_int_t expiration; int res; if (j_access_token != NULL) { // Token is valid, check type and expiration date time(&now); expiration = json_integer_value(json_object_get(j_access_token, "exp")); if (now < expiration && json_object_get(j_access_token, "type") != NULL && json_is_string(json_object_get(j_access_token, "type"))) { if (config->accept_access_token && 0 == o_strcmp("access_token", json_string_value(json_object_get(j_access_token, "type"))) && json_string_length(json_object_get(j_access_token, "sub")) > 0) { res = G_TOKEN_OK; } else if (config->accept_client_token && 0 == o_strcmp("client_token", json_string_value(json_object_get(j_access_token, "type"))) && json_string_length(json_object_get(j_access_token, "aud")) > 0) { res = G_TOKEN_OK; } else { res = G_TOKEN_ERROR_INVALID_REQUEST; } } else { res = G_TOKEN_ERROR_INVALID_REQUEST; } } else { res = G_TOKEN_ERROR_INVALID_TOKEN; } return res; } /** * validates if the token value is a valid jwt and has a valid signature */ static json_t * access_token_check_signature(struct _oidc_resource_config * config, const char * token_value) { json_t * j_return = NULL, * j_grants; jwt_t * jwt = NULL; jwk_t * jwk = NULL; const char * kid; if (token_value != NULL) { if ((jwt = r_jwt_quick_parse(token_value, R_PARSE_NONE, config->x5u_flags)) != NULL) { if ((kid = r_jwt_get_header_str_value(jwt, "kid")) != NULL) { jwk = r_jwks_get_by_kid(config->jwks_public, kid); } else { jwk = r_jwks_get_at(config->jwks_public, 0); } if (jwk != NULL) { if (r_jwt_verify_signature(jwt, jwk, 0) == RHN_OK) { j_grants = r_jwt_get_full_claims_json_t(jwt); if (j_grants != NULL) { j_return = json_pack("{siso}", "result", G_TOKEN_OK, "grants", j_grants); } else { j_return = json_pack("{si}", "result", G_TOKEN_ERROR); } } else { j_return = json_pack("{si}", "result", G_TOKEN_ERROR_INVALID_TOKEN); } r_jwk_free(jwk); } } else { j_return = json_pack("{si}", "result", G_TOKEN_ERROR_INVALID_TOKEN); } r_jwt_free(jwt); } else { j_return = json_pack("{si}", "result", G_TOKEN_ERROR_INVALID_TOKEN); } return j_return; } /** * check if bearer token has some of the specified scope */ int callback_check_glewlwyd_oidc_access_token (const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_resource_config * config = (struct _oidc_resource_config *)user_data; json_t * j_access_token = NULL, * j_res_scope; int res = U_CALLBACK_UNAUTHORIZED, res_validity; const char * token_value = NULL; char * response_value = NULL; if (config != NULL) { switch (config->method) { case G_METHOD_HEADER: if (u_map_get_case(request->map_header, HEADER_AUTHORIZATION) != NULL) { if (o_strstr(u_map_get_case(request->map_header, HEADER_AUTHORIZATION), HEADER_PREFIX_BEARER) == u_map_get_case(request->map_header, HEADER_AUTHORIZATION)) { token_value = u_map_get_case(request->map_header, HEADER_AUTHORIZATION) + o_strlen(HEADER_PREFIX_BEARER); } } break; case G_METHOD_BODY: if (o_strstr(u_map_get(request->map_header, ULFIUS_HTTP_HEADER_CONTENT), MHD_HTTP_POST_ENCODING_FORM_URLENCODED) != NULL && u_map_get(request->map_post_body, BODY_URL_PARAMETER) != NULL) { token_value = u_map_get(request->map_post_body, BODY_URL_PARAMETER); } break; case G_METHOD_URL: token_value = u_map_get(request->map_url, BODY_URL_PARAMETER); break; } if (token_value != NULL) { j_access_token = access_token_check_signature(config, token_value); if (check_result_value(j_access_token, G_TOKEN_OK)) { res_validity = access_token_check_validity(config, json_object_get(j_access_token, "grants")); if (res_validity == G_TOKEN_OK) { j_res_scope = access_token_check_scope(config, json_object_get(j_access_token, "grants")); if (check_result_value(j_res_scope, G_TOKEN_ERROR_INSUFFICIENT_SCOPE)) { response_value = msprintf(HEADER_PREFIX_BEARER "%s%s%serror=\"insufficient_scope\",error_description=\"The scope is invalid\"", (config->realm!=NULL?"realm=":""), (config->realm!=NULL?config->realm:""), (config->realm!=NULL?",":"")); u_map_put(response->map_header, HEADER_RESPONSE, response_value); o_free(response_value); } else if (!check_result_value(j_res_scope, G_TOKEN_OK)) { response_value = msprintf(HEADER_PREFIX_BEARER "%s%s%serror=\"invalid_request\",error_description=\"Internal server error\"", (config->realm!=NULL?"realm=":""), (config->realm!=NULL?config->realm:""), (config->realm!=NULL?",":"")); u_map_put(response->map_header, HEADER_RESPONSE, response_value); o_free(response_value); } else { if (ulfius_set_response_shared_data(response, json_pack("{ss?sOsO*}", "sub", json_string_value(json_object_get(json_object_get(j_access_token, "grants"), "sub")), "scope", json_object_get(j_res_scope, "scope"), "jkt", json_object_get(json_object_get(json_object_get(j_access_token, "grants"), "cnf"), "jkt")), (void (*)(void *))&json_decref) != U_OK) { res = U_CALLBACK_ERROR; } else { res = U_CALLBACK_CONTINUE; if (json_object_get(json_object_get(j_access_token, "grants"), "aud") != NULL) { json_object_set((json_t *)response->shared_data, "aud", json_object_get(json_object_get(j_access_token, "grants"), "aud")); } if (json_object_get(json_object_get(j_access_token, "grants"), "client_id") != NULL) { json_object_set((json_t *)response->shared_data, "client_id", json_object_get(json_object_get(j_access_token, "grants"), "client_id")); } if (json_object_get(json_object_get(j_access_token, "grants"), "claims") != NULL) { json_object_set((json_t *)response->shared_data, "claims", json_object_get(json_object_get(j_access_token, "grants"), "claims")); } } } json_decref(j_res_scope); } else if (res_validity == G_TOKEN_ERROR_INVALID_TOKEN) { response_value = msprintf(HEADER_PREFIX_BEARER "%s%s%serror=\"invalid_request\",error_description=\"The access token is invalid\"", (config->realm!=NULL?"realm=":""), (config->realm!=NULL?config->realm:""), (config->realm!=NULL?",":"")); u_map_put(response->map_header, HEADER_RESPONSE, response_value); o_free(response_value); } else { response_value = msprintf(HEADER_PREFIX_BEARER "%s%s%serror=\"invalid_request\",error_description=\"Internal server error\"", (config->realm!=NULL?"realm=":""), (config->realm!=NULL?config->realm:""), (config->realm!=NULL?",":"")); u_map_put(response->map_header, HEADER_RESPONSE, response_value); o_free(response_value); } } else { response_value = msprintf(HEADER_PREFIX_BEARER "%s%s%serror=\"invalid_request\",error_description=\"The access token is invalid\"", (config->realm!=NULL?"realm=":""), (config->realm!=NULL?config->realm:""), (config->realm!=NULL?",":"")); u_map_put(response->map_header, HEADER_RESPONSE, response_value); o_free(response_value); } json_decref(j_access_token); } else { response_value = msprintf(HEADER_PREFIX_BEARER "%s%s%serror=\"invalid_token\",error_description=\"The access token is missing\"", (config->realm!=NULL?"realm=":""), (config->realm!=NULL?config->realm:""), (config->realm!=NULL?",":"")); u_map_put(response->map_header, HEADER_RESPONSE, response_value); o_free(response_value); } } return res; } /** * Parse the DPoP header and extract its jkt value if the DPoP is valid */ json_t * verify_dpop_proof(const struct _u_request * request, const char * htm, const char * htu, time_t max_iat, const char * jkt, const char * access_token) { json_t * j_return = NULL, * j_header = NULL, * j_claims = NULL; const char * dpop_header; jwt_t * dpop_jwt = NULL; jwa_alg alg; jwk_t * jwk_header = NULL; char * jkt_from_token = NULL; time_t now; unsigned char ath[32] = {0}, ath_enc[64] = {0}; size_t ath_len = 32, ath_enc_len = 64; gnutls_datum_t hash_data; if ((dpop_header = u_map_get_case(request->map_header, HEADER_DPOP)) != NULL) { if (r_jwt_init(&dpop_jwt) == RHN_OK) { if (r_jwt_parse(dpop_jwt, dpop_header, R_FLAG_IGNORE_REMOTE) == RHN_OK) { if (r_jwt_verify_signature(dpop_jwt, NULL, R_FLAG_IGNORE_REMOTE) == RHN_OK) { do { if (0 != o_strcmp("dpop+jwt", r_jwt_get_header_str_value(dpop_jwt, "typ"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_dpop_proof - Invalid typ"); j_return = json_pack("{si}", "result", G_TOKEN_ERROR_INVALID_TOKEN); break; } if ((alg = r_jwt_get_sign_alg(dpop_jwt)) != R_JWA_ALG_RS256 && alg != R_JWA_ALG_RS384 && alg != R_JWA_ALG_RS512 && alg != R_JWA_ALG_ES256 && alg != R_JWA_ALG_ES384 && alg != R_JWA_ALG_ES512 && alg != R_JWA_ALG_PS256 && alg != R_JWA_ALG_PS384 && alg != R_JWA_ALG_PS512 && alg != R_JWA_ALG_EDDSA && alg != R_JWA_ALG_ES256K) { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_dpop_proof - Invalid sign_alg"); j_return = json_pack("{si}", "result", G_TOKEN_ERROR_INVALID_TOKEN); break; } if ((j_header = r_jwt_get_full_header_json_t(dpop_jwt)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "verify_dpop_proof - Error r_jwt_get_full_header_json_t"); j_return = json_pack("{si}", "result", G_TOKEN_ERROR); break; } if ((j_claims = r_jwt_get_full_claims_json_t(dpop_jwt)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "verify_dpop_proof - Error r_jwt_get_full_claims_json_t"); j_return = json_pack("{si}", "result", G_TOKEN_ERROR); break; } if (json_object_get(j_header, "x5c") != NULL || json_object_get(j_header, "x5u") != NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_dpop_proof - Invalid header, x5c or x5u present"); j_return = json_pack("{si}", "result", G_TOKEN_ERROR_INVALID_TOKEN); break; } if (r_jwk_init(&jwk_header) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "verify_dpop_proof - Error r_jwk_init"); j_return = json_pack("{si}", "result", G_TOKEN_ERROR); break; } if (r_jwk_import_from_json_t(jwk_header, json_object_get(j_header, "jwk")) != RHN_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_dpop_proof - Invalid jwk property in header"); j_return = json_pack("{si}", "result", G_TOKEN_ERROR_INVALID_TOKEN); break; } if (!o_strlen(r_jwt_get_claim_str_value(dpop_jwt, "jti"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_dpop_proof - Invalid jti"); j_return = json_pack("{si}", "result", G_TOKEN_ERROR_INVALID_TOKEN); break; } if (0 != o_strcmp(htm, r_jwt_get_claim_str_value(dpop_jwt, "htm"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_dpop_proof - Invalid htm"); j_return = json_pack("{si}", "result", G_TOKEN_ERROR_INVALID_TOKEN); break; } if (0 != o_strcmp(htu, r_jwt_get_claim_str_value(dpop_jwt, "htu"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_dpop_proof - Invalid htu"); j_return = json_pack("{si}", "result", G_TOKEN_ERROR_INVALID_TOKEN); break; } time(&now); if ((time_t)r_jwt_get_claim_int_value(dpop_jwt, "iat") > now || ((time_t)r_jwt_get_claim_int_value(dpop_jwt, "iat"))+max_iat < now) { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_dpop_proof - Invalid iat"); j_return = json_pack("{si}", "result", G_TOKEN_ERROR_INVALID_TOKEN); break; } hash_data.data = (unsigned char*)access_token; hash_data.size = o_strlen(access_token); if (gnutls_fingerprint(GNUTLS_DIG_SHA256, &hash_data, ath, &ath_len) != GNUTLS_E_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "verify_dpop_proof - Error gnutls_fingerprint"); j_return = json_pack("{si}", "result", G_TOKEN_ERROR); break; } if (!o_base64url_encode(ath, ath_len, ath_enc, &ath_enc_len)) { y_log_message(Y_LOG_LEVEL_ERROR, "verify_dpop_proof - Error o_base64url_encode ath"); j_return = json_pack("{si}", "result", G_TOKEN_ERROR); break; } if (0 != o_strcmp((const char *)ath_enc, r_jwt_get_claim_str_value(dpop_jwt, "ath"))) { y_log_message(Y_LOG_LEVEL_ERROR, "verify_dpop_proof - Error ath invalid"); j_return = json_pack("{si}", "result", G_TOKEN_ERROR_INVALID_TOKEN); break; } if ((jkt_from_token = r_jwk_thumbprint(jwk_header, R_JWK_THUMB_SHA256, R_FLAG_IGNORE_REMOTE)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "verify_dpop_proof - Error r_jwk_thumbprint"); j_return = json_pack("{si}", "result", G_TOKEN_ERROR); break; } if (0 != o_strcmp(jkt, jkt_from_token)) { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_dpop_proof - jkt value doesn't match"); j_return = json_pack("{si}", "result", G_TOKEN_ERROR_INVALID_TOKEN); break; } } while (0); if (j_return == NULL) { j_return = json_pack("{sisO*sO*}", "result", G_TOKEN_OK, "header", j_header, "claims", j_claims); } json_decref(j_header); json_decref(j_claims); r_jwk_free(jwk_header); o_free(jkt_from_token); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_dpop_proof - Invalid signature"); j_return = json_pack("{si}", "result", G_TOKEN_ERROR_INVALID_REQUEST); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_dpop_proof - Invalid DPoP token"); j_return = json_pack("{si}", "result", G_TOKEN_ERROR_INVALID_TOKEN); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "verify_dpop_proof - Error r_jwt_init"); j_return = json_pack("{si}", "result", G_TOKEN_ERROR_INTERNAL); } r_jwt_free(dpop_jwt); } else { j_return = json_pack("{si}", "result", G_TOKEN_ERROR_INVALID_TOKEN); } return j_return; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/resources/ulfius/oidc_resource.h������������������������������������������������0000664�0000000�0000000�00000005047�14156463140�0022637�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * * Glewlwyd SSO Access Token token check * * Copyright 2016-2021 Nicolas Mora <mail@babelouest.org> * * Version 20211029 * * The MIT License (MIT) * * 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. * */ #include <jansson.h> #include <rhonabwy.h> #define G_TOKEN_OK 0 #define G_TOKEN_ERROR 1 #define G_TOKEN_ERROR_INTERNAL 2 #define G_TOKEN_ERROR_INVALID_REQUEST 3 #define G_TOKEN_ERROR_INVALID_TOKEN 4 #define G_TOKEN_ERROR_INSUFFICIENT_SCOPE 5 #define G_METHOD_HEADER 0 #define G_METHOD_BODY 1 #define G_METHOD_URL 2 #define HEADER_PREFIX_BEARER "Bearer " #define HEADER_RESPONSE "WWW-Authenticate" #define HEADER_AUTHORIZATION "Authorization" #define BODY_URL_PARAMETER "access_token" #define HEADER_DPOP "DPoP" struct _oidc_resource_config { int method; char * oauth_scope; jwks_t * jwks_public; int x5u_flags; char * realm; unsigned short accept_access_token; unsigned short accept_client_token; }; /** * * check if bearer token has some of the specified scope * Return G_TOKEN_OK on success * or G_TOKEN_ERROR* on any other case * */ int callback_check_glewlwyd_oidc_access_token (const struct _u_request * request, struct _u_response * response, void * user_data); /** * Verifies if a DPoP header exists and if it does, verifies that it's a valid DPoP header */ json_t * verify_dpop_proof(const struct _u_request * request, const char * htm, const char * htu, time_t max_iat, const char * jkt, const char * access_token); �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/��������������������������������������������������������������������0000775�0000000�0000000�00000000000�14156463140�0016652�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/README.md�����������������������������������������������������������0000664�0000000�0000000�00000004566�14156463140�0020144�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Glewlwyd screenshots Screenshots available ## Login page ### Simple login with password ![login-new-user](login-new-user.png) ### Login without password ![login-nopassword](login-nopassword.png) ![login-admin-example](login-admin-example.png) ### Grant client access to scopes for the user ![login-grant](login-grant.png) ### Grant client access to scopes and Rich Authorization Requests for the user ![login-rar-grant](login-rar-grant.png) ### Multiple session selector ![login-multiple-session](login-multiple-session.png) ### Logged in ![logged-in](logged-in.png) ## Admin page ### User list ![user-list](user-list.png) ### Client list ![mod-client-list](mod-client-list.png) ### Scope list ![scope-list](scope-list.png) ### Scope add ![scope-add](scope-add.png) ### List of user modules instanciated ![mod-user-list](mod-user-list.png) ### User database backend module configuration ![mod-user-database](mod-user-database.png) ### User HTTP backend module configuration ![mod-user-http](mod-user-http.png) ### User LDAP backend module configuration ![mod-user-ldap](mod-user-ldap.png) ### Client LDAP backend module configuration ![mod-client-ldap](mod-client-ldap.png) ### Scheme list ![mod-scheme-list](mod-scheme-list.png) ### OTP E-mail scheme configuration ![scheme-email](scheme-email.png) ### OATH HOTP/TOTP scheme configuration ![scheme-otp](scheme-otp.png) ### Webauthn scheme configuration ![scheme-webauthn](scheme-webauthn.png) ## Profile page ### User profile data editor ![profile-data](profile-data.png) ### User profile change password ![profile-password](profile-password.png) ### User profile session and tokens manager ![profile-session](profile-session.png) ### User profile OTP configuration ![profile-otp](profile-otp.png) ### User profile Webauthn configuration ![profile-webauthn](profile-webauthn.png) ### User profile TLS certificate configuration ![profile-certificate](profile-certificate.png) ### List of plugins instanciated ![plugin-list](plugin-list.png) ### OAuth2 plugin configuration ![plugin-oauth2](plugin-oauth2.png) ### OIDC plugin configuration ![plugin-oidc](plugin-oidc.png) ### OIDC plugin configuration for OAuth 2.0 Rich Authorization Requests ![plugin-oidc-rar](plugin-oidc-rar.png) ### List of API keys created ![api-key-list](api-key-list.png) ### Create a new API key ![api-key-add](api-key-add.png) ������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/api-key-add.png�����������������������������������������������������0000664�0000000�0000000�00000035171�14156463140�0021454�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��ø���÷���~kŅI���sBITÛáOā���tEXtSoftware�mate-screenshotȖđJ�� �IDATxœíŨwTY�đ—@0tDŠŪĨ÷ĸ`Ąw,ëÚģX°WŦXw]ÛZ°+ŠJė]Ē"ØP¤7Š Hōũ1~1 D@ŅņūNÎaæÍ›7÷ÍL.“7C Ŧ_ŋ��Ā/b_���āû‚D��8‰��pŽŸ5ÕÔÔÔØØØÖÖևŅ���č ~~~AAÁ˙b?jkkeddĻOŸŽ¨¨ČQ��Ā/ĄŠŠ)///""ĸĸĸB\\œUΏĸP(222ëׯ'}!��€ÔŌŌŌĐĐØž}{]]ëĒˆĸRŠŪŪېå��ˆDĸ§§'•JũZ‚jkkSUUíģ¨���ô&UUUöŽü‘@ ôaL���z™Lf2™—į*áņJ��Ā9Hô��€sč��į Ņ��ÎAĸ��œƒD��8‰��p=��ā$z��Ā9Hô��€sč��į Ņ��ÎAĸ��œƒD��8‰��p=��ā$z��Ā9Hô��ǘLfddä“'OÚ/JII ĨĶé=Ü$úoļhI ’šViYY_�ĀƒØØØ/^$&&räúÔÔÔøøøwīŪ]šrĨ‡›č~ĸŒŽQRĶŌĐÖËËËoŋtä(gw¯îĮÕ-;vîVRĶŌ34mnnæX„EËūRV×61ˇžģ 5-U-7÷Ŗ’šÖŸĶgũØĀ�ŋ/‰„bĪõiiiņņņ!"‘¨§§×ÃMđ÷pũ–ÖÖ A[.œ=ÕÃvzŽĨĩ5<2Š@ 446^OHôöōh_ĮČĐĀØČ›nĻŅ>~ĖģyëvŌÍ[{wwX��ž7%%Ĩ‰'^ŧxą­­-11!Ô¯_ŋ¸¸8&“I$}}}ĩ´´z¸ ūw™éŸJ+ēŊž‰ąŅ“§Ī"Ŗcŧ<Ü{JŨHLĒŠŠ<q…ĐK—¯\í0q[YZ, XČ^’’š6iĘ´-ۃ]]œ~T°��đ–ëCCCą\O °,īããĶķ,"6’RRëöú3§OU—ßŧģŽŽŽKĩĒĒĒA[­GŒRĶndf9{ŪÂĖ—YØ"sëNŽ˙IĘcÜ”Ô´îŪ{Ā*‰‰‹WR͊ŒŽá˛‰K—¯ „ĻMbbl”–žņ!7——øMMŒ--ĖëëëßŧyËKũö Æŧ…ėáqéėø “•ÕĩKJKŲ[¨ŠŠUÕÔõöĐŊ���8 ŦŦ<qâD>>>„“É$>>>:::ŊŌ8Q[}„¨`ˇ×įį'm\WũųķŽŊuV§úķg¯qūŅ1ąnŽÎģ‚ˇÍ˜65;û•ß„ÉÉ)Š!k+Ëw9īYŋ'ĒĒĢ?äæ aK1Ī’SB6V–m"//?9%ÕĐ@_qØPoO„Pؕk<vARB!ÔÔnXŸG;vîNLēĩfÕ ė3 ÷ÎúųŽc2™‘Ņė-ÜHLĸĶéã|<ģ��� 6Íd2{Ģåž>uÃ`2FŲۍe9ėjÆķÖŲ˙ĪĄ˛ōōķgO­Zčåá>îėkWBI¤;w#„Ŧ­,BiéXågĪRøøøœRRĶX-$'§hjjHKKwÆĨ°+!_o„‹‹™LŽˆŠnimí2ūÖÖÖį™™!e%%Ū{ÍrîBčŠ3įf͜>kÆ4^:ëėä *"‘čãn¸š8w#���>dffFGGc#6Øu}BBÂŗgĪzĨqâĢwEĩԞ>¤´q  āē ›ÚÚÚ81™Ėë7nh¨ĢÉÉĘVVVa/?ÉĐĀ +û…Bĩļ´@ąŌúĶädu55 sŗŦėl*•ŠǍ¨ĖË/°ĩļęlë---á‘QũúõsvvD‰ ;9Œ­ŠŠMJēÅ%föö]NĀŌĀÂÂ"7Wį:ũ-Ō™ÛwînŲļÃËĶ}õŠ@;+((čæę’_PĀúÅVũųķŗä‡ąŖEEEŋ5���>ŧ|ų2**ŠÁ``ãōūūūŊ›ëųQunîį†ļ"/'ˇdŅ‚āŨ{O9;{æ öEÕÕÕ55ĩ55ĩfVļíW,)-QUQŅPWKų˙3ŽĪž%°ĩ551ĻĶéĪ3­­,ž&'#„l:Oô ‰I55ĩžînĸ""XÉ8¯Č蘰Ģ×\]œØk8täĀĄ#Ģ˛ˇÛžuķˇv9;ûÕ’Ā•úzģvl#ŧwÖoü¸ĐËaá‘Øķ?7“ öY�đĘĘƲ<@đööÆÆåũüüÂÂÂčtzBBBČÜÜŧ'›ā7˛´ĘÍyĶķX§Mŗ˙Āa''yVy#…‚ŌÔÔX¸´ũZedBÖV–§Īž§RŠ yų+—›((ČËÉĘϤĻZ[Y<KN466ęlĶØmX33Ķü‚ŦDVVVZJęņ“§Ÿ ‡ ĖĒijblnfŠM‰D #CMMnôw؊UMMM99īËĘĘ RāŊŗē:ÚZššņ×olÚ°ŽL&Į_ŋ!'+kiŅŖŖ�øuåææŌétāååĨĢĢ‹ĒĢĢ?>,,ŒÁ`TUUõpüņ ‰Jõ8TÄĪĪŋmKĐ8ŋ‰A[ļ8v„Hü2ú/",ŒMŒ°ĩél]k+ːSg2žgVUW!„LLŒBÆÆ†ØxNrrǙЉ�‰Ôáē?æaÕÖŦÛĐ~é•Ģá˗-a͚›™r<^ŲmúzLš8wAĀ’ĀaĄįąĪYŧt!4Ū×'hËļ;÷î›%§¤ÎŸ;›ĩģ��ŋƒĄ¤¤Äņ‡QžžžšššŽŽŽ=ÜDO˙`ŠĄžßøqaWŽ%&Ũ"ũ?/KKKKJJäæ~Ŧ¯¯cUŽūüYĒlÚÔÄX€DJKO/++WSUé/)‰216Úļ}gaaQ~AÁ”?&vļQė6ŦŸ¯5{9F[ąjíÕđˆ% ųų{ŗ›˜]ÁÛådeį˚qôxČ?/[ĀcgBžîŽÁģöÄĮ'”——3™LoxŪ€ß6bĶá"--­ŪyŽžįM°[Ŋ"°ŋ¤äæ­Û™L&ĢĐŲŅąĨĨåxČ×ŋž­ūüŲÉÕsæœyØ,™L622|ū"ķir˛™Š VhblÜŌÚzōĖY„UĮôØmXiyāRgGö——‡ûØ1Ŗ*+ĢîŪģßģ}dˇtņ"]í#Gŗn&wŲY„˜˜˜ãØ1wī?¸eld8lčĐī!��3s‹jšzĢ9qqņĩĢW–•—ŗ˙ŊŌâ€ōrrGŽ_šz]xDԑŖĮ=ŧ|kkk§NųƒUĮÚĘ2=ãyaa+ŅĢŠĒˆ‹‹_ ”—“SVîøŲĮ„ĤÚÚ:w7Wöëe–?˙˜Œē|åjoõŽ=‰´ߞ~ũú- \Y__xë,BČoü¸æææ7oŪúx˙čo�ünˆüÅų{áf,‹ˇ—ë†'FZJ*ōZؤ ūŸ<]ŊnÃą'555Ž^ē`ÍöPÖV–ØÃ”Ļ&ÆX @064 RŠ6֝ūÔ—ŋ†ũķ—š™š¨ĢŠŪđčģ~ͤĸⰍëז–•­YˇņÖY„š™Šŧœ™Lvqvü~ą��Bˆ°aÃÆŌŌ’'Nôu$ŋ—’ŌŌ‘ŖƏķŲļeS_Į�ĀĄYŗfÉÉ}y�öč;‚w#„fLûŗ¯�ā_ī?ޏČ/(xøčÉÍ[ˇ=~°pžĸâ°>�đ€D˙CŊ}›ŗiķÖū’’Ë—-™7ūŊ �āG€D˙C9:Œų˜ķ睪��ü^`Œ��p=��ā$z��Ā9Hô��€sč��į Ņ��ÎAĸ��œƒD��8‰��p=��ā$z��Ā9Hô��€sč��į Ņ��ÎAĸ��œƒD��8‰��pŽŸÉd0™Ėž��@ob2™L&›†+z��Āš/˙36¯°´oã���đ|IôŠƒåú6���ß Ũ���ÎAĸ��œƒD��8‰��p=��ā$z��Ā9Hô��€sč��įøëęęû: ���ŊŠąąąŽŽ›æ—’’ĸŅh}��€Ū%...%%…MÃĐ ��ā$z��Ā9Hô��€sč��į Ņ��ÎAĸ��œƒD��8‰��p=��āÜwOô‹–*Ši•–•}ī áÕęĩ”Ô´ō ząÍ|PzžšŸđ,ÂBĒŦŦępé÷8jŊŽ{�žt?ŅGFĮ(Šiąŋ”ÕĩMĖ­į.HMKīÅ7˙;ņ“'€ŌŌÔ°ąļ aŗŋÄQã’Ŗ �Įø{¸ž‘Ąą‘!6ŨLŖ}ü˜wķÖí¤›ˇöî ööōčqxŋŠŠĘ=ûūÖŌÔ6th_Į¸™7gÖŧ9ŗ°é_â¨ĩ’Ŋ �ßzšč­,-–,d/IIM›4eږíÁŽ.N=l˙wķ2+̝C�ßė—8jŋDā;éũ1zScK ķúúú7oŪļ_ZUUĩ1hĢõˆQjZÍĖ,gĪ[˜ųō?į_fæË9ķšZ¨i ˇ9zŲōUEEÅŦĨ---ĮCN:ģy70ŅŅ7vrõ8r’Á`đŪ>‡.䏯˙$%5­ōō öKËʔԴü'MéF<3fĪ=o!BhÚĖ9JjZė#`D"ņčņ[û1ęÚzVļö˙Ëd2ģŨk cŪÂ�%5­Čč˜.?a˛˛ēvIi){ 55ĩǚēŪž¸lĨĩĩu{đ.sëęÚzŖœĪ_ŧÄžô›ēP\\˛bõZsëjZà M-f͙Ÿ™ų[dn=ÂÉõ?%Į:š)ŠiŨŊ÷€UĪŪY–oZ—5ĀŨíŖÖ^eeÕęĩĖ­Gh 7tvķ<}ö\[[k)÷ˇÆŒŲs•Ô´ęëëY%mmmJjZ“˙œŽ:9ĩ8ÆčšėU„Đâe˕Դ(ęÎŨûlFŽV×Öŗ´ą;yú,÷ŸDO¯č;$)!jjnæ(¯ūüŲkœ}}ũÄ ~jjĒĨĨe.^ō›0ųėé3S„PVö+ŋIS$$ħũ9e€´ô§ÂÂķC>zœ”'))ZŋqķĩˆHwW—Iũ ƒ‡wîŪW\\˛yĶ^Úo{ƒÜãqwsMĪxž˜tsĘ“X &ÜHByēģu#ž…ķįIˆKDFĮ,Z0O[KSUE…ĩčĐáŖ¯ßŧā7žxöÜÅŋ˙98tčwW—îõšÃŽģ“n­YĩÂËÃŊËũ|ĮĨĨgDDF/œ?—ՍÄ$:>ÎĮ“ËV6oŨŪØH™2yF‹Š‰Ũ´yĢ�‰ä7~ܡvĄ¤´ÔĶg|Ssķ¤‰ūj**eååB/ųMüãüŲS&ÆFÖV–‘ŅuuuâââĄĒęęššBBBÉ)Šv#mąž%§ „lŦ,9ZūĻuīÜŊߓŖÖ^õįĪîŪã¨Ē——‡‚ŧ|rJĘÖí;ßŊ{ŋsĮVÄÃ[ƒ;.Aō˛WB$ !4ŅâÁƒØŋÁ`üsđđöā]bbĸž>Ū]�úXPPĐėŲŗ™ß."*ZQUķīr”ˇ´´ØÚQTÕŦ¨¨d2™ /STÕ,)-e2™ë7nVŅĐÉ|™ÅĒ\\RĸŖoäî5›=1ÔÕÃûéŗdV…ŗį.(Ējž=w›ÕÔ5đöõgßÜÖíÁķ´ĩĩņŌ~{ÜäOUuĩІŽ˙¤)ėĢ{ûúĢk ¯ĢĢë^<GŽWTÕŧw˙Ģd՚õŠĒšžū“ZZZ°’ŦėWŠĒš3įĖĮfģąöƒröüEEUÍģö°–roJĨ70ąíČŪā¤)ĶÔĩõęëëšlnü„Ét:+)**VĶnc7šĮ.°¸rĩĸĒæÄ›ŦĘī?|PV×öįĪd2#ŖcU5oŨžƒ-ŠģŽĸĄŗbÕZl)Æ~ŒŖŗģWû8ŋi],$ė$īÆQkoũÆÍŠĒš>b•LŸ5GQUķ]Î{&o Ŧ2vâaZ[[U5'M™†Íļ’Ŋ Ü÷*ĢGKY >}RT՜1{ng=}köėŲA˙כC74ííģœ€Ĩ……EnŽÎHŗ/e2™×oÜĐPW““•­ŦŦÂ^$~’ĄAVö+ …Šš<qBlT¸š™)B¨ĩĩ•FŖŠ¨(#„ŠŠŋ|D%ņķ—”TUWŗš]ŋvõ‘ƒ˙đņņņŌ~{\ė2Šūũ­,-RĶŌYĢ—–•=‘ig7BLLŦ{ņtfæôŠØ%BH[K“H$VTTđ¸Wš¸}įî–m;ŧ<ŨW¯äņ0 ēšēä¤Ĩg`ĢTūü,9ÅaėhQQQ.ۚ8ÁHürž)(Čcš›÷.0™Ė›7oKKI3ŠU¨ĸŦlh ˙"3ŗĻĻÖÚŌ!”’š†-zšœŦŽĻfan–•MĨRB•yųļÖVí#ėÉēęė¨ĩĮd2ãäde­Ų>glÚ°îâšĶŌŌRˆ‡ˇFOtšWY…>^_?´ <˜L&—–•÷<�đŊõtčæĀĄ#á(eoˇ}ëfŽÂęęꚚښšZ3+Ûö픔–`'#ŖbÂŽ^{ķö]CCki[›XēdŅ–mÁöŖĮŒennjcm%;pā7ĩ΁Kƒîņ¸ģē<xøčæÍÛüĮŖ¯ã6îŨާ3Æ cMa!ĄæfZˇ’ũjIāJ}Ŋ];ļŦ—ũƏ Ŋ‰=pu#1‰Á`tųų]C]}vČāÁĪ’SŠ‹KH$ŪģPYYÕĐØ¨ŖŖÍ Ŗ¤¨˜–ž‘—Ÿoh ¯ĄŽ–ō˙ōgĪ’GØÚššĶéôŒį™ÖVO““B6%kiién¯ÛĄÎŽZ{•ĩĩu:VZėũ2xđÁƒYŗÜOŞčr¯JJęc%ōōrėHüüm­müôzščMMŒąĢ „‘H”012ÔÔÔh_ŗ‘BAijjŦ \Ú~é@„Đž}˙{ė„ŽŽö†uĢ$ ķūÚuXÕĻNųCMUõėų‹7’nb7ĶFްŨ´QAAž—öÛãŌ /ņŒ;ē߯ „Ä$,ŅĮ_OŗŗÁcy×ŲÃÎ=Ųʲ̚ššrrŪ—••¤Ā{ƒē:ÚZššņ×olÚ°ŽL&Į_ŋ!'+kiaÎŊ ""ÂėŗdA2BˆFkųĻ.P›¨!!AAŽjdr?„•Ú„˛ļ˛<}ö<•JmhhĖË/XšÜDAA^NV6%5ÕÚĘâYrŠ   ąąQ‡AödŨöxDŊ™ÖŒâō”Z—§bOđ˛W1üüßåŽøŪzzØĖÍL9¯ėŒˆđ—÷ų[›+Đh´SgÎÉÉʆž?+,,„˛_ŧ`,-Ė--Ė[ZZRRĶŖbb""Ŗ'˙9=1!ļËö;ĶYƒLŖËxD„…íGŽHēuģŽŽŽÚÔôüEĻŋŸ¯�‰ÄK{EOļb ¯÷Į¤‰s, \z­âąÁņž>A[ļŨšwßÄČ(9%uūÜŲŦa™ÎĐū{1ÛÜԌ$S„…„BÔĻ&Žr,‰ˆ!„Ŧ­,CNÉxžYU]…211Bbc2ÉÉŠfĻ&¤ŽSpOÖí‰ŌŌĄúzÎŗÃã[ƒCkk+[įe¯‚_ڏûŽiiiII‰Ü܏ėO€!„Ē?Æ&*+Ģh4šŽŽëTF%§¤vؚ€€€ĩ•ÅŪ]Á“&ø|úôæõ›.ÛįŽ}ƒ<ÆãîæJ§ĶīÜģĪūŧ /ũí=ŲĘŽāícFš3kFÆķ˙<üM zēģöë×/>>!îúu&“éãÍíyˇÜ\öŲ‚OŸBCūĻ.  -..ū!7—ųß§úŪįæ"„”BĻ&Æ$RZzúŗg)jĒ*ũ%%B&ÆF/^dåØÚt:öŌ“u{BHH¨ŋ¤ä‡Ü\öėüņcŪšķsŪāåTäį'!„ZŲĮ,,âuøž—Ŋ ~i?ôK͜[ZZއœb•Tūėäę9sÎ<„v͉ũŅā×oŪDDE#„h-4„Đ홿Ö#""ŖŲÛ$ !~ŠËöÛãŪ`—ņ`ėFڊŠˆÜŋ˙0éæ-yyļĪõßBˆˆęl$ˇCŨØ ģĨ‹éęh9zœu’—ÅÄÄĮŽš{˙Áĩˆ(c#C^ū"ôęĩÖtiYYÆķ*ĘĘØûoę‚ãØŅ••U7oŨf•ŧ~ķ&3ķĨĨ…š˜˜BˆL&>‘ų49™õtωąqKkëÉ3gB6V&ëî­ÛŖÖۘŅöĩĩuėgãūƒ‡‚ļnoiiáåT”0�!”›û‘U‡ãÄæd—{üŌ~čˆÛâ€wīŨ?rôxEEĨ™ŠIyEEčĨ°ÚÚÚŠSū@‘Éd{ģwîŪ_ˇ1ČÜÔôũ‡į.„îߡ{ÖÜwīŪ‰‹ik#!.žfũÆÔôt-MMeeŊēild¨ĨŠŅeûíéęhsi@ pg´Ŋ€€€ÃØ17oßillœ3kûíŦo!4xđ`„ĐŅã' ‹ŠLŒô†ëöp¯v‰D"íߡĮÕĶgiāʄ¸(111ô?.:6î͛ˇÁÛˇō˛!ZK˜ų‹FÚÚ457_ ģŌÚÚēhÁŧntaIĀĸÛwī/[ązę”ÉJŠŠEÅÅį/„ ­_ŗŠUĮÚĘōđŋĮ¨T*+YĢŠĒˆ‹‹_ ”—“SVVâg7ÖíÆQk/`Ņ‚Ûwī¯ß´ųÍÛˇ ōōÉŠŠwîŪ÷ōt×ŅÖBuy*z{y\ŧtyێkV­$ßŧuįų‹ėŸ�¸ÉË^ŋŽzE/-%y-lŌ˙ĮOžŽ^ˇá؉“ššW/]`=Rļ+xģģĢKbâÍõƒŌ3žŸ8zxäÛE æÕ74lÛą‹Fkš|ņü䉞>MŪĩgĪŪŋŗ^Ŋ \ēøtČq,ŊvŲ>~~~î r‡õäŸģ›K]]N÷ôpûĻūļ7ÚŪÎŅaĖÛw9‡Ž-.)é•ŊÚ%EÅaׯ--+[ŗn#ī š›™ĘËɑÉdgGîíĶh4„ĐÁũû)(ė?xhמ}ĄŨ;ˇģš:wŖ ĘD…‡9;:\ Xĩvũų Ąf‘×Â44ž>Õcme‰=ijbŒ•cC*•jcŨÅnéÆēŨ8jíÉÉĘF^Ŋėæâũƞŋöŋ˙ģvõĘŨÁÛąĨ]žŠúz{vîhnĻMŸ5wö܅5ĩĩ!ĮŽ ˇ´´đ$/{üēAAA%%%ĮŽëëHĀ/Ϥ´tä(‡ņã|ļmŲÔ׹��8͙3G^^›†<ēiGđn„ĐŒiöu �€.ĀSąāÛä<|ôäæ­Û? X8_QqX�č $zđmŪžÍŲ´ykIÉåË–Āˇ™đKāīŅaā÷ãč0æcÎ뾎�ЅĖZ6DO/{DLzŠ��đæ]ę‡z„Ēy~7›X”_>��x#ĶđęΛ†úė{ŠÔ!D:Ŋžũ��ĀOÅÜ@ĒôAäĩĮĩƒlGÂã•��€CäĄ*ÃPC˙áJBč��‡Ē33s…åøŪßO.'"š��ÜyöĄeįîf§J{qH¯/ĢnaôuH���zSË#+ž~C­í•¨ü’5ˇīÖõuH���z“‘žl?„ę§lkÁ?eū‚’î~ß��€ŸĶō˙§„4āf,��ā$z��Ā9Hô��€sč��į Ņ��Îũb‰>.>AIMëîŊåkÖmĐ32ĢĒŽÆf“SRgĖžkjiĢĒŠk`b1{Ū™™ŦĘt:=äԙąNnZà G98;qûŸISĻ9ģyļßh^^ž’šÖš ĄĄŲķ*Ši;q’ŖNUuĩĒĻŽ’šV[[VÂd2#Ŗc&ū1Uߨ\CGßÖ~LāĘÕīrŪ÷ŌžøIšZ(ŠiqŧL-l°ĨØŪk˙šŋh1kõCGŽr´špņ2%5­K—¯p”ŗˇĻŠk0ÆÉuێ%ĨĨŨ‹œ÷#‹ōōõĮļëėî…Z¸˛Ã~)Ši]ŧt!4sÎ<ŽōuƒēŒkœGŧŸŊØKMk¸•­ũęĩĘĘ9ŋ֐ŖwŦ¯'Ü`¯VYYĨ¤Ļõ,9…cõææf[û16#y|oŋØ?quqē|åęļÁVV$V˜ũęõ•k›6Ŧ“–’B=KN™2mĻ›‹ķžŨÁ’’’%%%GŸœøĮ´¨đ+jĒ*Ąŋö9ufŲ’�}Ŋá)Šiģ÷ūE$f͜>ÎÛ+påęˇoßqüCäČč‰äîę‚Í’ÉäˆČč9ŗf°×‰‹įããc}C“É\¸"6îēģĢËŋņBB‚ķō/]žâíë:äëŋNãĪŅÃ[ZZYŗõ õ˖¯b˙ßCŪļ%ˆc-ii)lbŨęUęęjė‹ęëëoŨžŖĄŽ5Á<ĮŠCŪšc+BˆJmzũæMØÕkW¯Eœ8v¤{{˜—#‹ Ú°ŽĄĄņāá# ĄysgûxɉËW­VSU=ķK#**JĄÆFĘ({;öģ8PFĻrÁãŲËÚc­­­ī?ä:ōīËŦŦ¸č"ņë5Gī0D"1x×^{ģ‘d2qĩ˙ĀᲲr)ŠūŊÖ7ĐcŋXĸGmŪ¸ŪÉÍķô™sØ’ÉdmŲĻĨŠ1yĸ?Váü…På}{vbŗ:ÚZ––>žRĶŌÔTUZ[[Īžŋ8}ęlu3S“ˇoßÅ]O˜5sēŖÃ˜›ˇDÆÄŽųī[%:6n´Ŋ„„86kldđčņĶėW¯u´BĨ%’��IDATĩXuĸĸc‡ëę¤Ĩg`ŗ—ÃŽÆÆ]ßģ+ØÛË+…Đ˙ņãũ'<üīų3œ—¸Á‘a/[.Õŋ˙æMëY%BÂBÖV­îãÍyM/((¸nÍĒ?ĻÎČ/(6t(ûR!a!s3SlÚŪnÄ´?§L›9{ūÂÅ÷î$‰ kđŧYŒŪp]„PčåËX*TSUÁŽ!Bä~d™}¤P(ē:ÚŦPŋĪ^ö=fcmÅĪĮ´u{AÁ'EÅaŦU8z‡eo÷ėYōņS įs ãíģœ3įÎûxyÜ{đ°WúzÅĪ2tS__Ÿœ’šœ’Z__ĪŊϞ˛ŌôŠ:ōoeeB(*&6ãų‹-AY—$-­­­­­ė̈ '^™4Á!ÄĮĮ>wöLÖRyyšÚē:„   “ƒCLlƒņõ;!Ō3žąŽ×B ĐĐPˆŒf•|ü˜—•ũĘÆÚŠUröüc#CV–g…qõr(–åcââU4t^ŋyÃڊ’šVBbBh¸ÉŅã!+V¯56ŗŌÔ5˜=oáįšŦš‰šõŠ3įÖŦßhhjĄgdļcįîĒęęYsč›[ŲÚ_‹ˆÄĒĨ¤ĻųMüCĪĐTGߨ×RJjBČĖĘöāáą Ø‡î…‹—ąb3ĩ´=r’÷M´´ļîŲ÷ˇ…ÍHm=#_˙IéĪۊظëąq×÷îíđPļ×~č&<"ĘÅÉŅŌÂ\A^>2*†ûęÂÂB;ļmū\S…•™Yž:snÚĖ9Úz &æÖgΝ߱sˇ…ÍČá&3įĖÃÎ" /Gļ{)BBB.ęđ`q1ÎoâŸĶ˙ķ§Íœã3~g/‡~ũú!„DEEē낍¨ČÂķŽ)-+ëŦƒÁXģ~Ķä‰ūĒĒĒ]6~¤Ÿ"Ņŋ~ķÆÅÃ{Âä?'LūĶÅەū:°pž¨ˆčÎ={)ęŽ=ûü|} ôõXKGŲĖÍũ8ŅâĖĖ—ė'=†H$:T\üËN[[ÛŖĮOŒ ąŲq>^ååOŸ%ŗęGEĮJKK°ĩf•0č 'ĮظxÖ mdtŒēšĒ˛˛6[__Ÿķūƒ…šYûȅ…ŋŧÛŨ]]ėFŽØ´•ÉdŌéôM›ˇ9;:89ŒEņ“ø8injšōôa|tÄĢW¯ˇn ÆÖâ'ņ‡œ<=z”}zō“U+–…œ:3mÆėysf=O}ęãíšaĶ–ēē:*•:sö<åđĢ—"¯]ÖPWŸ:cv]]•…ë˛49%UNV65íKNÉË˯ĒĒÂXxŲBhGđް+×Ö¯Yuų⚥C‡ü9}Vaa{OKJK×oÚ<kÆ4ÎũĀdŌÚa2™čÜ܏™/ŗ|ŧ= ‚—§{dtLg5YT”•‡ MNIÅfI$ŌĨ°+ęjjĄÎ bûVUUåáŨ[‰ ąYŲ¯:Âû‘í6 ĨãDßŲÁâŌ”›‹ķĶgÉ ØlCCӧΰ‘^Î^„P[[[[[•JMKĪ8v⤗‡ģ´´t—]`ĐS§L–“¸s÷ŪÎę\ŧVVVļtq@—­ė§Hôû..ūō5 ÅÅ%ûæ^_HHhũēՑQ1K—Ņh-+—˛/õ÷ķ]š|ŲÃGŊ|ũõĖĻΚ{éō•ĻĻĻ›ÚŗīīÂĸâ…ķįaŗ&ÆFƒŠŒūråØÚÚŸāåáÆĮĮĮž–‡›kõįĪ÷>B1™ĖčØ8ˇ˙"„°‹ÄAƒX%mmm •õÂ|ˇmŪôūCîĩˆČ‹—ÂJËJƒØÆ7´ĩ4}ŧ=‰Dĸ’’âÄ ~ ‰IT*•ĩh”ŨH€mŅĐ@ßĐ@Ÿ@ ¸š:Ķh´yų%ĨĨŠ§ģ›Š˛˛ĒŠĘĻ kO‡°˛´xūüö›īYrŠģ› …B-øô !”’šÖ_RRSCƒĮM4R(—¯\ X8ßÅŲIWG{Įļ-ļ6VXS&“š|åy9ŲĀĨ‹9vøÛw9šē¯ŦėWĢᑊŠÃ°ßâ>ŪžEEÅŠiéÖd'//_Yõå:@ ’ÉĢWčķķķ#„T”•}}ŧųųųådeGÚÚfegķ~dģ­‘Byų2ËË×_Gߨn´ãž}777#„:;X\šrrt ĶéwîŨĮfoŪēC§Ķ]œogīÛˇīÔ´†Ģi ×Ņ7?aō!ƒ×¯]ÍK˜L&‰DZģfUlÜuށ,LEEåŪ}oÚ°Žu5~?Eĸŋuû—Ų9;:X[YŪŊ÷`Õōe’’KįΞ™ōäáņy{{~*,\ˇ1hÔXį÷>pTÛĩgßŲsî˙Kqؗ‘_āí鑘t{Ūđ°ļļŽũ'ßAƒ ô##ŖBŠiéEEÅn.ÎŦĨØ R[ëׇ4ÂŽ^Ķ50fŊ°l5p ĖÚÕ+wíŪ÷×ūAÖcw’1ÚlcÄjĒ*---ååØŦ’’"6!*"‚RVúrą)""‚jhhP6LQqØŌĀ•˙;‘ũę5Ÿ™Š‰   ĨĨy#…ōî]B(95ÕÔÄDo¸.IJj𕕁@āq99ī[ZZ†×ÁĘH¤#˙aŋãrōtFÆķŋ÷înŸŗ† ~åĮKĨŖKf:ãåáŽ]*Č˰ĢtĻ­­=ĩčŗ/Õ`ģŲ+..VW÷ŸĄBîGļ{ ‰D*)+›5cÚŲS'üũ|OŸ=ŋfŨF„Pg‹Kk22LMŒ“’naŗ ‰I–æØ%9/g¯â°ĄQáWĸ¯D^ŊüīĄL&ĶÅŨ‹ũ—4wŖėFÚÚXoŪēŊũgåÍ[ˇ›˜9ŒÍcSāGú)=ĮsŗÁNŠąœX‚‚‚ŖGŲmXwëFüÅs§ŠMM;vîa-e0kÖm¸zéTČ1ģ‘ļė+úxyR(Ô¤[ˇBQŅąē:ÚŦûlėÜŨ\oŨšÛĐЧ¯§7xđ Ö"PXôu(cėčŅaĄįÃBĪ:đ7{#n.´@āx{ŗ}ĖBąn]p¤NlŒ•…Édōņņ]štÁÅŲņō•Ģî^ãlFŽÆ†ļåde‡ĨĻgTUWįåå¤Ļ~IôėiēËM`É ŦŊˇoßíûûŸĀeK:<ŽdA˛žĮĢÃa‡WTTūĩ˙�vĒĻ5<=ãųõ„Xã"/?_^^Ž5Ëq‡€ãĄ‘öcA\Žl÷‰ÄĖôäČĢ—Œ æĖš°p~tl\MMmg‹;W§{Ōh´F åáŖĮnŽŦE]žŊũČäáē:Ãuuôô†;Œ}âØ"‘xøČ1Ūģŗ~íǎīrކG˛ŪŊ÷āÁŖG›7nāŊđ#ũ‰~ãē5\fŋUee…Be/ą07s;†}č?hËöěˇ.ž;ciaÎąú A fĻ&1ąqĘ­;w;ģ‘åâėH§Ķo$ŨL¸‘čîöŸ‹>aaĄáÃuãn°†z 616216ŽŖÃ^ķī8PLLt˙Cėå …5ŨØØˆû˙M^HõīŋfՊûˇ“¯ĮXYZŽ\XYX¤gd¤¤¤ĒĢ̉ŠŠšĨĨ§—”–—”XYvú$L{ũûK˛ãĐŌŌ˛tųJCCƒ™Ķ§ōŪ`‡ŽED`—ŸØëĘĨ Í4–Å:“š–^QQŲ“Û§\ŽlIi)ûd´ļ´ võŦa‡45ÔBeee¨ķƒÅ…ŖÃXöđŅãģwīŖ˙^ëđxö˛HĘĘJīrrxīŠ˛ōäIöũĩŸÚôõ]vũÆ …:bÔX  íÁģĘË+T4tΜ;ßåŪ�?ĀO‘čÍÍLãĸÃÎX8?.:ŧ'OĄUUUYÚÚ;Â^Čd2?æå €ÍFDF_ 8sōÄp]ŽÚ@>Ūž?ŊžpƒÉdēw2D+Õŋŋĩ•åŅã!uuõ.NNKgNûŗ°°ččņŽō—YYlĶŲ§ÎœÛļyĶ–MNœ<ÍūöfŨKDeeg ĘËÉvŅķ˙+,,ēų˙T¨Ēĸ˛mË&"‘˜“ķ!dei‘žū<9%{ŌĀ@ŋāSa||‚’’ĸŧœˇF˙KII‘L&ŗ‚d0ū“Ļ`ƒ*ģ÷ūURRēww0k ¨{°Įį==ܰËOėeldheiÁeôĻŽŽnÃĻÍ ōÎNŽŨŪ4—#{<äÔÔs°û=L&ķũ‡\ŽĮ=;ôņcŪÜ9īŋŽf<A$‡Âå`qĐŌÂėÎŊûIˇnÛۍĀFØXx9{YZZZrŪŋ8pā7õnÉĸmôļ!§Y%K'ÄEĮĮDb¯Ų3§KKIÅĮDz¸šq�ü?ËsôZššZšš=oGZZzÆ´Š‡Ž­ŦĒ=Ę^B\ŧĸ˛2<"2-=ãāū}ĄæææŊí9†JĨ˛˙QŸĄĄë/°œ‚ļlßû×?ė ˇįîæ¸bĩĨ…ų€œ-¸8;ĨĻgüĩ˙Ā‹ĖL'' ‰ŠŠŠ›ˇoßš{ßŲÉŅ@_¯ĩĩuõÚõîn.Øo5‡1ŖW­Yq•D"!„Ę+*ö8äíåņáÃĮ /šš8sŒŸpQRZ:oáâU+ąĒQ1ąD"§ļ07-+/ŋuûîēĩĢB"ÂÂęęį.„ŽeĮûF‰ŠˆŒį}äčq9YYåĐËa/ŗ˛woKNI=u期¯Īû÷Ūŋ˙Ī]]ū’’! …rŋŨÖD"‘ã<&.ž­­ÍaėŽš.ΎĢ×n¨¨¨”‘€ĸRžÄÖÖÖˇoߝ9wBĨž=u‚u(ģ§ŗ#ë7Î'ôR؂€Ĩü|īÜŊ÷аpīîā.[4HáŨģœy —.(#“’švėÄÉéS§ eeŋęė`ĄŽö•ēēšė!gįC‡˙­ohØĩcĮ渟ŊŦ=Æd2ĢĢ?‡^Ģų\3îŦoꝸ¸øŌÅ[ˇ]*;p fĀ€|ü|ęjđåĪâgIôŊhõĘ@5U•+×ÂW¯Y_[W'**ĸĢŖsöÔ ,•|ĖË++//K*Oü˙í,LōãŦwĩŖÃ˜ˆČhîŸ|ĮŽM&“;ģY´aĩĨÅš Ą[ļS(”ūũ% ôΜ<nkc:xøßŌ˛ōķgOa•7Ŧ[3ÆŅåČŅã‹-@ųųŽĢ̝÷ôßÜLeo´qīŨ735ŲŗsĮ‰S§˙ūį ?ŸĒĒĘŅÇ!„ÄÄÄt´ĩ^fe›a• Īŋhũ-ã6˜5ĢVˆÄā]{) uõĶ!Į†w!v5<ėj8GũĶ!ĮFØÚ „ ‹ĻÍœÃą”H$~xûŸ§_Â#ŖMMŒŲoPcƎĩvũĻ蘨Y3§#„>Nüc*ÖÂ@™#lĖ›Ŗ /˙­Ũiˇ•ŽŦĻĻÆŋ‡ūų럃‹—­<hĐĄūb=˜Ë…€€Āų3'÷üõ÷æ­ÛkjjååäV­X6eō$Äõ`ĄŽöÕîÛĮy{!„ĮŽŪ°iŗ ™lo7‚csÜĪ^ÖCõ—”ÔÕÕšxūŒžžŪˇönĸ˙øĐK—q˙•¸A *))9vėîÆ€īĘĐÔbúÔ?ΟÛׁô=#ŗŲ3§/˜Įų›��đ­æĖ™#˙˙‹^҃_Q}}ũ‹Ė— öō—Ā��~Š›ą�$Ũē=}Ö\c#‡1đ 6�Ŋ Žč:)Oû:„>0ÎÛk\WĪ�ēŽč��į Ņ��ÎAĸ��œƒD��8‰��p=��ā$z��Ā9Hô��€sč��į Ņ��ÎAĸ��œƒD��8‰��p=��ā$z��Ā9Hô��€sč��į Ņ��ÎAĸ��œƒD��8‰��p=��ā$z��Ā9Hô��€süĩõ j_‡�� 75P¨ĩõØ4ŋ„˜ĩQ¨o��ĐģD……$ÄD°iē��œƒD��8‰��p=��ā$z��Ā9Hô��€sč��į Ņ��Îņc?ō Kû6���ßɗD¯8XŽoã���đĀĐ ��ā_��� ÷U×ÔIdl=��␔¤˜ė€ūØ4 Ũ���ÎAĸ��œƒD��8‰��p=��ā$z��Ā9Hô��€sč��į Ņ��ÎAĸ��œƒD��8‰��pžÔ �€gšųŸNŋÜHĄ2ŒžŽå›‰DaĄéø+ŌŖvz+ ��øŲäæÚ$¤žĄņWĖō!ƒQ_ß°˙HHnū§ž´‰�€['Ī]îëzŒ@@Lf;‰�€[ }Bo (TjO€D��?ģ=Aĸ��œƒD��8‰��p=��ā$z��Ā9Hô��€sč��į Ņ��ÎÁ—š�Ā&†z–ĻF ō˛|||55u/˛^Ũyđ„ÚԄ-Ũ´úî牷ī÷mŨ�‰��BhŠŋąÁđį/ŗ†§ļĩĩ 2ČÖĘĖ`¸ÎūŖ'~í¯R€D��ČÜØĀÄPīRxĖ“ä4ŦäåĢ7ÉéĪ—/šã:ÖūRxL߆×Cč��°6/(,beyLyEÕ?˙ž*¯Ŧj_ŸŸĪÕq´‘žŽ¨ˆp}CcJFæõ¤;Ø7Ō(+us-/7H —–Å$ÜĖÍ+@‰DĮQ# õuûKJÔÖÖŨyøäŅĶÔĶ;Hô�€ß™ÜOAN6é΃ö‹ŠJJ;\eŧˇ›žļfXdė§ĸâaCûyģ ø#boHs§MN{ņōRx4l­ĖæĪœ˛~۞ĻĻfO+s㰈؏ųŸ4T•}<œémô§ŠßšsAĸ��qQQPõš†ĮúBB‚fFú‘q‰™ŲĄĒęY™v6Ņ×oJJŠ“ÉũR32Ë+ĒB×ĸ¯gdfˇĩŅÉũúŲXšŪŧķ %ũBčQõįÁƒäĮØŲū˜DW�~wLÄDŅétë’“%‰ųŸ Y%ŸŠŠHKUTV—WVMč;ÆÎf‚ƒÁøđ1ŋĩĩUA^–ŸīMÎÖ*īsķH÷čŨžtŽč�ŋģēú&“)#-Åc}2šB¨š™Æ*iĻĩ „Čũ˜Læū#!ŖGÚXšģ;ŠŠ­‹Ŋq+5#[%`îtÄdbĢ„˜¨HUõįŪíN{č�ŋ;­Ĩ°¸ÔĖØ ņöũļ˙^×ëëjĩĩŅŗßŧc/ljĻĄ˙§{ š_?„PSs3B¨‘BŠOŒŠO”•`?ÂjŠŋOYy%ö[áÜĨk%ĨåėMÕÖÖ}ˇn}C7��€î>|")!î8ƎŊPv Ė„qēÚ•‹KĘ †Ō°!ŦÅĄƒ›š›+Ģ>KIJčj}Š_VQy9<†Á`ČÉĘ—”ĩĩĩ‰ˆ—WVa/ •Ú@Ą´ņ<^ÔpE��(íųKUeE{ÛÁ ré/˛h--Cäm-ÍĘ**"ãnpTĻ65=MÍko[Yõš¨¤TUYŅÖĘėöŊG CRRbæ˙čëIŲoŪ1™ČÄPÉdæ6Ķh“Ķ\ÆÚS(ԂOEũ%%|ܝkëꏞžđzT]SO$‘ąiHô��€B—ŽEŋ{Ÿkca:ÎُHŦú\“xįūũĮÉ­­­í+_ŠoĻŅüŧŨDE„kjënÜēwķîC„ЇųŽDޞĩrqÅ 3JË+NœŊTYUŠˆŊ=d)&*RßИõúml­ī×)I1ŲũąiHô��đEFf6öÄd‡VídMĶéô¨¸Ä¨¸ÄöÕR32S32ۗ3Œø¤;ņIwz%Ôocô��€sč��į Ņ��ÎAĸ��œƒD��8‰��p=��ā$z��Ā9Hô��đŗÃžę˛Û Ņ�pKTD„õĩĀŋ0&SDX¸' kë(Ôۊ��~3ĻøŖž] ˙„SüŋuĨ ĩļž{Á=��ˇ”‡ Y2ύˆH‡>ú @Y2Ļ2ÛW"wŋ„˜ĩQ¨ˇÂ�€ŸŠō°!;6Žėë(ú€¨°„˜6 Wô��€sč��į Ņ��ÎAĸ��œƒD��8‰��p=��ā$z��Ā9Hô��€sč��į Ņ��Î}Iô Ŗoã���Đ[8R:!D"‘^ŋ~ŨGņ���če¯_ŋ`ÍBBBB111L|=?��üö˜LfTT”Đׯ%æ9r¤€€@EEÅ˗/Ĩ¤¤DEEųųųû0D���ŨĶÜܜ““sâĉŠŠ qqqV9!((›jhh P(0X��ŋ.>>>aaaö¯īĸĸĸĸĸĸ?<*���ß×wĨĄ7ŧHI}ķŠ˛žÚBį•”ĒcnĻ-ûĶüKĢĘ'įBĶøT:*öu,�Įh•oRŸe”U5Pét>!ąCtÍ, ~šwÂß9•EECgzĒVaĶ_ņ ˆJĘĒ[XŠJķõY„ŊŠū>ęXR]HĶ{ÚXV)íutČÍOHHĮs–—ĩ{ 5+âܝb:BjîĶ†öōŽũžĪŅĶĘ_šõämq ĩ  –†Ē‚ė{aWszũŋ‘×g†<–ŨĐÛíĐ *ĶŖÂne~,oā“8Ta ?­ĻøíƒˆČeôžW|Øg~Q!ÔŌPõéųČk/jú:(ĄæŊûr2´äŋ)îõĶâ2*€XÚįßī����IENDŽB`‚�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/api-key-list.png����������������������������������������������������0000664�0000000�0000000�00000163572�14156463140�0021706�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��[��d���A>š���sBITÛáOā���tEXtSoftware�mate-screenshotȖđJ�� �IDATxœėŨ\“õū?ū§äšĢ@H8g0ËC=nÚáGžy”% j f‰ũÄ~iŋ„ã×Ä~Ö)ûĨT*”&&øã봌éšÅf'ųQB`ā85F'¯ Đ}ū?6`cĀ`Đ÷ˇ’k×õz=¯×õzŊv=šŽ]g0ČÄÕĢW˙ûß˙rwõęU�����ø}ųÃūĀ0Œ‡‡Įu×]GDãL3ĸĢW¯ūôĶO<īúë¯7ž �����đ{ríÚĩ+WŽ\šreōäÉ×]wYFÔÔÔtŨu×]ũõŒ�����`¸ũī˙#ĸ›nēÉėB˲&LpPH������#d„ WŽ\!"ŗŒ¨ŊŊ}ܸq �����`„\wŨuíííÔ##�����p*Ȉ�����Āy!#�����į…Œ�����œ2"�����p^Ȉ�����Āy!#�����į…Œ�����œ—‹ËwŨėX�����€u†kW‡XŽ����€ķBF�����Î �����8/dD�����āŧ����€ķBF�����Î �����8/dD�����āŧėų ­����ā´ĒŦûpoŪ¯ŋ]švíšŖc1fܸq˙xũǤeö™<ōĩã���� UõuoīÜĨ˙õH‡` CK˯ÛßųāŌO#_;2"����Ē܏ōŒqãÆ ×ŪĪũxäkFF����CÕōŋ˙9:ûÆĶˇ´Œ|ĩȈ�����`T0 #_)2"�����p^Ȉ�����Āy!#�����į…Œ�����œ2"�����p^Ȉ�����Āy!#2Ŗxę6ņÔ%š cŖØ^ޝģíVņ’œa¯���`°&xúŨâÉëķ%—‰Ū’ŋĖüŗË‡NÎI:[{<gû…ZSŠkf9†Į„Š,!95N"ptl����Nd‚8úą8oũĨšŌ¯Īž)šP˙k;ņ<n™âÍwĄļ–† į›Å.DíŽŗ ĪgÚôY"OúåbiÉj~ut80 œ #b5ŲĢīĪR7ã>U&—zņxÄ6VĒT…9ęÂŧܔ÷r×Ëúū3ąšœĖBŠ[—,1k%*{{‘{â͉bGfÛPŠRĢ*kuÍ,1pĒ,<BâŨą5ŠíšĩŌÔ™ĀüßŖ [ŖR(5ĩÆŪ]ā%”ĘįwíØŖxôļTÛũ;Ãķ˓R×§Dx;.(��p ņüɡ†ÆŨũSiÕ¯Øo”\čxŖ˜čØČĖyüõūUËÄnDD47:zîgīī:ųã ŗĩ[­LøÕæžšlë~KĶīķ;ũÆ+_6 ŽÆáÄsķđ]ˆˆÚZš.˙:z2ØÁí҅ܧ\=Ŋ¨9ņāîAАˇzE–šõŠŨ’Ŋ%Qlr>ÉV~ļnõēŲŦ >ųæüQwB ƒÅVė,аą,<N(`X]mĨZ]đŪw5 )wŠ{&ŪÁō8ĄûŌ V““]žį7”Bz–YķYvn%O!ē ˆtĨĘĸÂÜ6ilėf^qÛŗŨ‰ˆˆĶÕ*sˇfĨ¨jsĪdČ—)]Ū=rÕõļG�đ;×ÖöKŨų Õ×įJĢÆĪIŲ˙Ŧ$ú‡ĘŠo4įJ+œ‰įÄ.îH‡ˆˆhŸîLœWžuĸ~„ęoRä—]u—Ĩ&ü94eĨüV^[Qû•ķ‡6î­h'߸GĸéØž‚jļŋFĮgD/ŋôâĩNW¯^5ū÷žé†ëčšĢ‡ˇ‡,üÚÕk†ž\áėņĖ 5˧įnKšŋÄßųæ{\ZNÔJ6¤Ķäež‘ĢP×6ŗÄ¸ eķ“RĶ%<"MFؒ\ÁzEášÎbk˛ŖŖŗjyąŲßfuM$8U)Û~6ĄãwÛˇúÂëĪ×ũøc`šō“$Ķ3`Ũg)aë”âôĸO’Ŋ‰tE[͞ō”5ÍD^bųÚ´t÷ÁĩŌī Ģ)(а¸5‰÷C ũ$ŋŧė…B"đŧŒ!Ļĩ v÷ĩęĘf¯ˆ¤¸ÎKWoo?wÚŠ¨mÔÉŧĮlö.‹e˛Îž/ Ÿ/ϰ%yšĮĶdķGĪ•¯RU騛Å�ÆīÛæxŋ\¸qcCKįE…˙īæ?}ļ˙ôŲ‹ŠMŠâF:qōŧÎaavpņ›ę;žĮ˛I~~Š~„’”_k4%#SĶ@xG'†šœ~ûé/ޘ-Ÿč5Ų?øūUöäî˙nÔeqÖ9>#ēzõǝ¯/7ŽëŋÃnøË_‰Æqŋū÷jg0\TŲŦ˛PÁ/65YØįëâČ-–ˇÖ_ûhĄN¸>EęEęÂėŧôĒīŪ;™&“„KyšŠ" ģFh<›ĶŠ• <†aÕĘ Š0 RĒ8 Ž–ņHa\`ûV3W>ĖÛúŦ2¯ &i_wDĘ<Į“&Įy‘fsbJN­ģ4a}b¸WĢČI]íˆ­Q)”ĒĘÆfŽw/ą4|žĖΏ‡l­Ē@ĄŽŅéˆŪâpšŧãÆ¯ÅöŧFiâÔÚe­ˇ<5QÜ`Š„ŅiT•œP.īņņ0$.ŋŊsŸwÍYŠļAą=§Qš,Õ)”ß5čXâyIâį yŦ&'ŗ°–ˆr34^á)k¤Ŧ]ö‚íë¤ÜOžféžV)”Ĩ5:ņŧÄRyWEēĘã åwÆ]ƒåō?QMAf^sxbpŖRYŲĀO ”ÉãdũĐâŽ[9˛C'‘JxšĩĩDB"ŌŠ˛Ķ2s‹*9–qËSŌ3ÖogUĨÍ^]›úĻ\š!S%Î8û~kiMMÚėĩŠī%h2ŗ•:r'Ļg§ ¤mÎ.ĒeyBųúŦŦDã(Ķ)wnČĖVÖ˙˜š‘‘Ā#]ۊŲéj"Z-.Ž.<š^Üįj}„ŽŲ™‘™Ŗ¨läˆį%–'­Oīqæ?˙ųÚë˙ügũėņĮü ;5&�ĀX1qüĪ˙ųæģö ž~3§¸Ô_øŲäĩļĒĶûôŸįΟ|۝sž˙×ų’Žî…ŅķDĪëQÖą§Žž85Ŧá^io'2ΉÚÚÛ{Ü 6Ņ/*>:tŠ÷MÆ˙ÖōSųécûŋŦéˆ|â-qKüUä9žÕ–Ÿ>öīîm|—ϝō:•ĢšŊ`š7ßå×ēâcŲ§XYü‚yS<&´7•ž8ôaqCģŲ]sÆMvē!úÎīy¤˙ąd˙GĮĘ{ĨÃßV¯tĨį{¤CÆßēríŲ5ļ’"Į?kîęÕĢd’]ŊzõÚÕĢŦŽŽíˇækm­†kWUvĨZÃ#•ęļUææBxŨ''ßLOŽ›??.9=ģ0+œjs3rˆ¤ōp†+UĒēÖ.ԐT.w×iԝzĢPĒ›IazjdûVaņ‰r†ž;gréX§Ė+å˜đ$š€ˆ-؞WK¤÷>ĘHžS>?1eÛáLqíwÜ`öt¨ØŠ‚E^ō¤”ÔGS’å^Ę–ˆ¨áxNŽR'”'§¤ŽI woTähŒīáq:˛V(OJ’‹Éb Œ¤öģFō‹{_Fá ú˙Žåh‰ˆkP+*ũâRRŸ^Ÿ'lVį)jˆx’Ä”h/b$ ëÖ'‡ ė´ÄŠŊ™ÆĸĒJ ןjį”RplrJJb¸wŖō@žJGDlmA΁RV›œ˛~M’\ČŠsīčY<× TԊãR×?Ŋ>UîÕ¨Č+¨`­ī¸å#k5ĩĩ,ãååED¤+Xˇ:ĢR¸îÃÂ"å‰Ü qéöÕëŽŗķØÚÜíJizî'érž•5‰Įc5Ųo”ÆŊ§üö[e–¤2w]\R/õõˇßĻđ›7興4›—¤l¯•d|¨PžČ]/,ŨœZĐ@$H|¯p˜˜Ø7UĨ‡×‹-­Ö+$:žļz{Ĩ8ũ‰ĶE…ω+ˇ¯~ĒĀüʏ?ūÄc?nēé�8š+WØļ+ƒc˙â×ũĮų+ĨgNĢN}uĄ­õž8uĖü„~Ō!ĸöīŋŽĐ›/ú­úl™Ų[áDŲŌ¤;'5{˙/ŊņNAƒoôōžž(ģgy´gCūŽW7žy Ø-láôļļņ>Ąķ|JöelÜŧ1¯Á+lŅSĢįĩaãK/ŸnŽ]0Ģ×ĮŠÚÚÆûĖ[ücá˛_zōĨCßß0÷Ū…ˇôž¸1’måsĮC¯n}a{ĮĪŨÁÆäqŧgčĘU÷LUŸëĮ¨ČˆÆõŌŪŪzíjÛÕöÖkí­×Ú9ÃĩÁ]#jĐéˆx‚A}NDu  ‘üÂÃŊtŨH+ãQåqĨŽx˛ ąjĨĻcmĨŠķ“%É%TŲyۍŽT]K^ŌpĶËSŲj~Bœ;ÕĐtnŦ+: ayœœgܔ%¯ˆIwá˛ä¸ž/… 7]c3ËJŧĀ[<?9))\Ė#b+”Ĩ^á‰rąˇ@ đ–Ü)æ}§Ōt=ŧ™'–ËŧŊ<K% Ģc‰q Ʝūĸ%/‰Ü˜jņ$BwŽĄQGD<ņˆˆĮãņ,ļÃĀ $‰‰r?ĒTČÎĖČ|#;ī3ĨĻĻû›Õ¨JYīđ8™ŸˇĀÛO.vįt,ą•ĒĘfax\¸Ÿˇ€'đĪ–z5–Ēē“j/Y×.„K…\mi-kuĮíˇG=ą5ʝļWēËŒˇĖ ä…ŠÜmq?oo?ÉüõIbVŠčúË5ēĮm^3_"öãõˇ&I’×EˆH!—KŌ5Ib åroŽR]IÄ3¯vję›ķ%ŪŪŪ’¸m›ãܕÛs*‰ˆ'ā1Æ˙ņŦ­Ö#¤ZÍwœģ,qžØÛÛÛO"ßōáׇ÷ęƒĻIŌ!�pv7Üâqå—v—_ęēŽˇüüŸ3—ĸĻéJĢûJ‰ĖOôG$""~ËŲ~˙ßΈôU_í?Õ4Ū뭐-Ígķû…Ē›~ūĨŠFsęĖĨ ˇˆ}‰ˆ&üu •(Tũ¨kųĨáÛÂcĨWĖ.7ŋtöØwŋļĩT–×ĐĒ:ũåO,Qûåķå?÷đ›ÔG@ã/Í×4ĩҝĒę–ë'{ߨWÜ#ÖVn˜ØķŽÂŽ@=įÜ=–’"Įß5×ŪŪnĖ‚¨ķ]mo5´ˇ_kkŊÚŪJ†qƒÍˆxDD,™_8Ņå-™^jēDšYúQ\ķŧ†ĘZލ2;NšŨĢØÚī‰ÄŌp1ei4 $ņĻ ĨēŲK*“Hxbî …†ä2bUE•ä'0ÛT0€­dɉÂŲ9Ē´m2"Ō)ķ4œģ<q>QCc-G$ 6KÄ!CĩƒjŠ!ņ J́’JÅB?Ą7ĪÛۛˆHWÛČē‹…Ũį†B?oF]ÛȒņzģWįk–J°!œ­÷-ĪŨĢĢt†Ą>îmŗÛ^O(KL‘ą 5•ĩ•5ßÕV* J•JqlBĸ쨥Ą‘å‰ģÃņ–ÅŨIDTĶĐcŧ…^Œ˛ąQGyW÷6/w†mlԑ˜,ī¸=÷ˆ¨2S>5ŗûWFņaFįgˆxŧFåæÍĨ•ĩ:–e9∄Ũ ėeōq/ëkz …]%2 %ÂŽÍ܉cYĸJĨ†ķŠ“ųum" —04šÎVęŒļŸÕēCG„{eį=z›’,—ÉÂÅą…gúweAH‡�ĀŲũRņåš4+ēĨō?] QŖæl‹wđ^ie‰˙Ôįv]gö#‘šøF=t§pÂo—ž=Q˜…\ˆhÂÍ3?8—ßĻ=ŗ'{˙wÆwŸö+me ŦøŗĮM\\\\ÆO ú҈hԟnĸ_˙}ŠëMĒéûKWĸMōœŸĩM7žĩŗímôķĨÎ?~ļˇ˙F..}¤ë/wßĢÖ~Ĩ\&ôŒp[õiŧᜨ°?Vã˜ęÆņQ×5"2ɈÚÚ[¯ĩˇ^mã íŦ—įå †Á=ŅĪK jlĐéˆēOQxÂ¸ÄØÎS¨Fuēą¯MY–%"qōöuá=ΞyĄˆü¤R/ĘS•˛ÉŪlŠē–']'!o’x5+•$ Đ(TŪķ†Ŋl%NLgg)ō$““N‘WĘy%&_bu,ÃãõˆT3 KŨų‡Ÿ<%ÅĢHYĒ.P+8F ”Čãäbą,KÍęėÍjŗ ŊXļcK“č-”0P<ww×ÜĐ@ ¸ ZKė´ŨxŪ~o?‰ŒˆmPP(q˛„G,Kŧ>Ž|rŊ—ķx]™ˆˆ'āõ|‰Ŧī¸]÷H˜¸ũÍ$c‚ÂcŧŊüLŖŅd$ŦČãÅed|( xÔp`uėvĶXŒköĀë$ëX–snÍ5ŽŲl–č5“di…ŸLŨžķ@öē,ã%Kے1߯Īx �tđ đÕ~õŊ[ČnúžˆÚ.žųšfũÅŗąĒîJKM#ũÉËÂv#v~㜠…ˆčúɡŨ›Ųkã=CãįŠ^>VCD.žņŪÚ~nūąī/ŗm4Qļōá…ÆÕ&¸L v}÷9l{Û '´íDDm6|SĪ0Yå°\Ȩåû÷œ¨qd⸌¨Į5"ãÛÚZ¯ļˇ_måÜūøË‚˙Ž˙CߟŨęO@¸”—[XZ¨bãēgœ%oéĘSO)ԅ}m*ˆˆGXüÂI¸Ė=ˇHĄĄp]Q%œ$#"‰TĘËĶ”ęˆ”Ĩ͌,<|H[ų%$Iˇ§) Žŗr™ĸPà “;ī’ãņxDÛuēNDD,ËŲ˙sD<!NGdÖ:ŽxÂŽ'9đŧ%ķ%ķ‰ÕÕTĒŠŧJY+įņxä.NL7ŸÕx}žQ÷YÂ@ķžˇĐNTV6Dôŧ–ÁVĒ4ŒXægųl~@ŅZĀ{AD,˚§ģ<oYøTÕ{ß54’DČãņˆmîũyĻ×rķ‰Õ™žÆ˛Æ,ĪúŽÛkˆˆ'‹úžąS“WPëw`[\G˙nĐ5[(Äö5-đxä%ßūaĒéWiņx^ۃZ­ceIbú[‰éÄÖ¨Ų›ĶRWđœI“ôĩ&��‘‹÷m“šJĪ˙ÚN%_ŨJ4Ū{ÁũIãot?/ÄīŊe?‡YʈFĘÄ@ɟ,]~!"ēņ–ā›Šæ'ĸÉ3fMjQŊyHõcĮ†×O ú…ˆˆŽ´ˇ‘ ŋû\Ûe„ŅķtUû¨˙÷ŠStsgC įŪz“ņŸ-ßüîžiĮĖ—9ūsD]‘Šļ6öZ;wĩĶũü‡šŽÕZŸ´"<1΋XeVĻĒ˙uÍ „BwĸJUiåώJEČĨLsОB­Pq~Æ;e¤˛`ŌŠkJÕĩ$–GôŅņ˛• .9œa•…E•Š< 'NHčŧĪÛKČ5VšŨ#W9,OđŠŊ™FMiƒé2FYÉ ÄWbt •rá ü$ķåbÛØČ’@čÅc›Yž  ¯įuŠÎō,”0`‰LĖ4Ē ‹ĖÂ%Ļ @Ą.ÕY+Ņöh-ą×^čTš™™šĒe›YbîĄ67Öv­Đ ĘÉ)ĐčČÛÛ|95Ô4r<¯Žįu776vŊĻkläx/ÕˇÛqéw—Y–ŧģėʂĩD}Ļ÷ļ¯i‰8\Ę47rĄŸ_Į7éũā W#ĸĸ¨ÆøOžŸ,nËz9Ķ\Y‹‡x�X4ÁŊpžŠëdŲÅŅhüõ7ē'"r›ĩ46x‚ã‚ëāáÛįtē ŧ&šņ\\č×˙vūéŪåæÛ‚'u^qøå§Fšč5šû.÷[˙<ČÚŅĻ­ķā]ųņëüC…û;~ÎÖ?pÕRūá˜J‡hôdDdō šqãÆĩļr×ÚÛŽĩqmlÛĩ.4ČĪÉÖ§ÅēSmNʒ­Ę†¯é4yOe)Xbú<ë•ÅĘŨ‰SfīŦé^Æ­‹ž-xõg§•2š„× É.P7ģw|^A •xs*EvQ%‰å}ÕŅ@ļâÍO”ķ¸ĸíiŲ NŽõë.D*áQ­ĸûš Ä*r>ëķĀ!âIäáBVģŗ@UQYSSŠQ)rr kyÁōˆŽŋ÷ëTŠ9yǚĢĶ5ÔĒÔĩ$zņˆ æ5(픕 :–Õ5h9;ŗû|V™Ĩm\ĸD SælĪ;ŽĒŦ¨­ŠĐ}–]Pë.“[}n´íҚoÆ#â*ktēF;í…@.õŌ)rrĢ*kjk4ĘŧE%oĒ1ƒæČÄîĩĘeeCCCæx˛‘ŧ„â‰eÁîĩĘUNĮę*–ęŧ¤]_DLŗJĄĒŅąŦŽFĨP7ē‹ƒ…<Ģ;nŋãŌ‰LÂTČ>^ĢĶÕĒrŨĐ,åqĩš>Zßö5-áÍOŠs×l~$ķxEƒNWĢų,-!Zū¨qP ÜyÄ}§TVÖ4•ÕĖUæ¤=ēâҜĸĘ]CC…rgŽ’ŧ‚íķ� �€ß§+ŋ莚Üā{›dúLÉô›ĒmÛSpĒäc^1ŪÍķ&ĮgDŧū.ጟāÂ#"ētąžíæ9Ą“&Nœ$šŗ*^PWÕ6ūÆ?ųņ\č— U-EĮūUäqŖ§īĖEŅAãwĶĶhĶđũ%Ahô_nņÜxƒāÆ7ōLn:k)˙đŨĒ1•ŅčškŽL>D4nܸö6öZ{ëÕöļĢíܸq.ם‘@ží“LZ‘V˜ž+”† buĩj•Ļ‘#F—ųæ6yŸ)Ņú-ąĘG ŗb*SäB^Ŗē0;OŲč›1ŋ3eHåB.SĄāxá]ō‹vĪV(9¯DŠ_ß! hĢđÄ8÷ÂŧŌZ^x–éÍJ‚¸y–ē wõ]JĸT ûN‘WĀʤŒÂüĶ vá-K^#(R¨Õ:ŽxîĄ86%BŌõįrŋ;äĮO¨ r͏qw÷“Ä%†{ į''ō EŽJĮĪË[<?I.éŖą-—0p<aܚq‘BĨQ¨9–aŪbiBBD@ŋˇŋŲ­yub™T] ÎÍ­”&ŦĩĶ^đ„ķ““ŧ‹”UAŠ‚c‰áš „Âđ¤îīÆ%'W(9Jš{ų…'ŋĖ•'œŸœ@ eáÎ:bŪâđDšŦ;A°\ĸSænoh&ž—Pž 7~ģŽåˇįqąJ˜ąEõčæuōä,OÛ˛MŪ°ĄôŅė¤:P8kZ&Ûōɛ‚Í™i šÍ,1B‰<=w˝Æ…'% ×å¤,ų,áŊ¯3,Žf.b[î– ۟JČhæž—_xBևëÅŊ×��"ĸņ“ün›0ņz—öß´u嚎GÍ5Ԝ˙:Ÿx“DÁšm–ØĪŅwÍũĒoéúîØ>]Ņīúĩäãü?­Z˜>—ôĩ˙)Č?TęļĀweØÚiÛ?Oükī¯Äq÷¯]ÜŽũžøØÁ¯¤Ls|ļ7dē}tāĻĨ Rģ~<Q[eŪ†÷˙s…šJŋ>]üå‰oqt€7Î`0tũRSS3”gIģîƒØjõũ÷EGG“yFäZōFĐ_į˙O[}­7ÎåŌ寅Īt`DÔ Ę{/7OŠĒlhf9â1^^I„<6)ažŲuÅSˇĨŠĶ”Ÿ$uÜĻÉË|#WĄŽmf‰qĘæ'ĨŽO4}TåÖđØėF’nV”Øą˜UŦžĒdŨc?To“õ]Ŧm[uŌd„-ÉÕɡŸ}ĢGæV{|sFÖgĒÚfbŧÄōĩi[ąŅKrë…kķnŊz|-��€­KÛlÃZ'ŨėōķOēž¯ đüĸx”~]ßîâvƒKË/ŽŧųFIÂÚ{nģÉÂĢúĒ‚×ß˙úōģ2fde¤hũÁ~s)QCCƒŸŸŸã3ĸ{W&/\¸Ė3"RoŊ-tÁ8ĸkímíÜoĩ?”/|^1čĀÆļ†ŧ¤č •qǎ ę‹fˆ�Āpŗ-#ęäâq›Äw<ĢĢĢ­ģükģ Oāõg_¯ 4a[Z|Ąe؂—‰7ēõu;Uû흸ŧč0ųŒČ‘wÍíŲŊĢĸĸĸŊŊũĐĄC׎]ģzõj×—‰~+S)Ū]y׏we †Aß57Öé™[Õ$^—‚t���~/ڛžũwšđ&MöģMčŌ~EWWũŸúŅvÉĨũןĮā `0ŽĖˆVŪģʁĩv ĒeĨē`gš&mIņst<0ļųÉSöį��€a×Î^ūņÂeGGāø'+@ß4yiiÄe)[2×ã›M�����†2ĸŅjūļŌīļ9:����€ß9Į�����€Ŗ #�����į…Œ�����œ2"�����ƍs@ĨȈ����`¨ÜūøG2Œqߍ?ōÕ"#���€ĄJē'Ņ1ۇߓëÆŨŸŧĖՎ|•����đ;#úŗīÃkVšũņãÁ Lüã{põdo¯‘¯zœÁäúfMMˇˇ÷āËēîö �����Ā&†kWŊmCCƒŸŸŽ����€ķBF�����Î �����8/dD�����āŧ����€ķBF�����Î �����8/dD�����āŧ\ėXÖPž �����`äá�����8/dD�����āŧ����€ķBF�����Î �����8/dD�����āŧ����€ķBF�����Î �����8/dD�����āŧ����€ķréņ{mm­Câ������I ÃPīŒH,;"�����€USSC¸k�����œ2"�����p^Ȉ�����Āy!#�����į…Œ�����œ2"�����p^Ȉ�����Āy!#�����į…Œ�����œ2"�����p^ČFmÂG�� �IDATˆ�����Āy!#�����į…Œ�����œ2"�����p^Ȉ�����Āy!#�����į…Œ�����œ2"�����p^Ȉ�����Āy!#�����į…Œ�����œ2"�����p^Ȉ�����Āy!#�����į…Œ�����œ—=3ĸ‚D+ökM–pĒįĨSC–ī3[Vô¸Ltû˙Wl‡ ÕiˇÅė¨JeY1ĸéO),žŽW<.M Yž¯5_Î>"šÔũ3]–đÔ;Ęú% CĖvbÉč‰ęwÉÎM]üpQ\vÕ újö­t ė>$mai_4›įEe•ĻČQlhĖ!mĖÍģo)6‡ÅlĄCū>G__†ŪōÃ{ė>c Bŋ3ühSŖÂˆĻąÍžŅ™?•œ.Öw/Š:­iĸÖs§KšîeJM‹›4r†*ô_ŧaËcáC)‚q#bˆąô˛ūĖŪĶ$ņĪíûĸŽ÷Ģ>wŊžģ{oîîŊšģwŊö¤œ9›šzešŠëŊ"ô ŨˇRúĖiGGá<l)fĢ î™le‡áŲĮ I;´ŒÆæĐ0mŸÁt6K†Ü G­áÃk,Æüû0ȖöŲxđņ Nē`8šØąŦ °Ų;ŋP–są2cŠQ_ŦĒ”ÎŽ+9}Ž"įWĒR×ģÎX'ą˜„ €įŒ˜EC.„!b,Ŗ=šŋ˜‰|ãy×Íī/ŦZúĐķ—ŨüƒdŌÎeԈđ�úÛōö|ū´l!ČaũŽqe%˜ÃF#ÅtĩÁ#Ķ­ė2<{rАzËØhŒ ĶöDgŗdčpÔ–Ņ1ĖÆbĖŋƒkųaŸČ']0œėú9ĸ°9nMÅ';¯Zę5Ęr+éĪ*;¯lk5gĒ( ZÂ'­ú‡—…Í M ’Üŋv‡ēķڒ:íöåģOpTĀô5Ÿč5iˇ‡,ß­QlYuģ,`ē,lÅKEÚŽ5ģ.’ZYH{zëũ1’é!ˇĮ?°[]œđˇWˈˆˆa\]W ûS_¸OÏē+"|‘ÜŗâāĄūŽÆ2!Ž­uõÚ~ÖëĻWŊ5=üŪ}ƒĩE;š]05D2oŲú}åz"ĸúV„ÜȤL}áÃ! 9uDZUöڄ(ÉôÎËĮĒžjļØÎ–qõŠ-kbn ˜"š=ūŦĶuDDš´y&—¤ĩû—O xøķÎ N“v{Hüîzę?*íG+fŦ:ÔŌrčŅԘ­å–ĒȎpõŠŦޤqO} Ō÷ŗŧīĻ&+ÁWSÛ¸yG ¸=恧Mę6v{õ'Ī, ›0=*~ËįZ­ú‡ãĨŗBn_읜#˛y¤t­Öãõ’–BíŊ•I‡ąx”­ŽÜžė>$mėՖZƸZKņŽGŖf…ˆzoq—O¯Ÿd,FÅĪČDq9u}ŪÉb÷°šG¨ƒ ´ŗõ=žėŌ ­…^š6•Åņn¯ŠÃŪŖÃĘhivĩmâ5y“ęqW’ÅĻ0oĘAõ[‹˛7 ŖĪroˇa2ˇ›oXÚG{äQéôø­ås WōRÔÔđĩ'ôDęõŗBâwœîkáQgwŪqĶc|‚بƞq`ÚØŦŗ6Ã[.0}Öh-MėC/yxĮ‘ÅØúŽ´ūŖ„€û™ÄŠ˙äūQ‚qOíqXČ`â‡~0 Iķû$ūą{. ƒ-z2xæ“Į›Ë^™/Y´ˇÎ¸ėøcR˙ų;/.¸Oâ?˙šįĒ.^Ŧúæđs‘ͤk7 ƒĄä…;Cį/]ųîÉoĘǚ %/Ü<wéēÃUŦÁ`¸|rŨÁO+YƒÁ`Pm˜¸đŨĒŽ­,ŦvųÃ{$ūw<ōášĒ‹Tī?´(ōŠėÎ ƒÁ`¸ŧwŠøŽßôš7v.œųÂ9ƒÁ`8ŸšĐ˙ŽĪwŋÆ<$é*¤SÕÛąâûōģq>sĄ˙´'ŋėY¨IĖ>^9S˛0ŗ„5 ö›Œ…ūĶm8\rņbŨ7Ÿ‹œ&]y°Î`04NOKūđrWŸ\3S˛ėāeCķ‘53% ĶOžŋXwņBIAú"ņ´”—{TgĨM[ĪôßÍ_>é?3ų•ĸ˛‹—ëÎ+^\4302Ŗ„5°_>&íÚ;VņHđܰŲ]MwaįÂi _)3ØÛ\övl`đcG.77ŗĢŗ}GدŌ#ÅsSŪ.*ģPĻ:đôBņ´Ĩī_°ļÜRS[ ~¸šÚÆÍ/¸GâĮ“ÎÕ]žXv<#9rnWß+yáŽĀā;’_(ēĖ —?|Ģ44ö‘÷˚ öÂģKÅĶ’\6Ø<RēW3?F=†¤ÅP͡˛ĨSY§ûIÛzĩՖ™=Ņƌ#ß\¨:_´sŲĖŽā­ė˛rŨĖĀEģę炸ęiŠqÚ4/ÜÆîacŽXpgŗ4žėŅ ûéx&š°8Ūí8uØwtXšëŦ”lå%KoRĻ1[n ;ô[+Ō”•Ņgš^›&s+ąŲø†eĨ}.<$/1ž&UŊ+™ũĐÉ˝Í+žšhŨá:Ö``•>t¤šgˏ†ŲØĘ˛1ûY“ö7ÃÛØ?Ņ7lŒÖâÄ>䒇sYŒÍbĨ—÷&‹§Ĩ4w—°ršdŲŪË6īÎhdLė›.îZę?-å@ŗÁ`0|“)ž'˙˛ÁđUz˜øžîšQb0°ÍĢ.\ėhuī/ ~Zi0ŒØŋcžčøĩë”Å`0|õtXįAí9MôŊÚŏŨ*Yy°sÎc•ëævgD“ÅfįUŨÎg.ôŸŋ­ãĨ‹{N ÛPÜpĪÁÉ6W}ųnrđ­aЊæîÍ­dDÍĘ ķ%ĄėĢųČĘiĻ'ĘėWé‘ĩŗ'SgJē†Yŗâ‘ā™4 eÛ"oí8;4îĘųse—{N]VÚŲÂIĖåü•ĶĖFõųĖ…ū3Ÿ<Κ§ˆg>÷%k0 _Ĩ‡EflKģčí ƒÁĐ|0E<wãWŖ2 ÃruļîHsūĘi’eŨĮˇäũĮž|Ĩ¨ŲōrËMm)øajj7ŋlėĀ]]ģė•ųĻ'ŦâŽwMƒĄ9å­Æņe0 † ;#o ÛPl°u¤˜­feHZŲS {mí([‰§§á’6õjĢ-#^ōqWË|ųXį_[Ŧí˛å7NŗÂMõ3aÚЀ#ÖÁŪŲ,ޝ!wÂū:žI&,ÅcĪŠÃŽŖÃŌ\gĨdk•Zx“2Ob-6…™Aõ[kŌ”åŅgĨ^›&s+ąŲö†eŊ}.᝜)]y°îâŪäāšt´¯jÃÜ@ņ=wegÆŗŠ‚fCī1åāŲØÚ˛-[ûŠ~gxûįĀû†­ŅZžØ‡ZōpŽ#‹#ÂÚ[Æ˛i’ΔĖä¯öƒ9ŦŖ…1ũąįለČ7L*zqŋRÅ-‰Ž/V5M‰‘xņeÁô3ᏅugŠ›Üf„1|Ļé“Ŧ—×kĒëô­Įq-D>­]åøJ|MŠõtŨ$Ęđ]ŠĩĨĪģQû^­^SE>÷zvž q=Øy‘˜!ÆÂĮˆ4{Ô.ģ+¨cĮ"žúnž:]ÖŊvÅkŅS_ëŪÂÍ?zĶŽŒhngmŠøāáM…ĪåŋŲVĩēŦurŦLÔūŒ°�׏5į´äzw ˙îüŖU÷ĻL!ŽøĐY {QÎ'bB#|Ūû艕ÜĘĨҞŲs=ƒB<{ÕÔO;÷ĄZSÖ:96ħkÁi€ëΊ’:’KBƒ¸ũĒjŠŦVĒiÆķKeåûkôMaÎ)5Œté "Ų•mՙ}DÄōŽ”kĘZ=bģ¯äžn#"RYX^bšŠ-oĶN ŧŠmÜŧZSGą]ũJ$ p{×ä~1_‘?ŋĢ Wō œÜY$Ã'Žëk¨Ø8 L™ IģvĒ)ļĮ3,C’oK¯ļĘ3$¸ģe<Ũ¨ĸ…ŗa—¨Ÿ6ˇĄG´ƒ™ę?6›'wÂūŽB7š~â)žŠcčŖÃÚhĄäĻūúgī7)ŗ˜­ŧa™Ž7¨~Û_‡4Õ÷čŗR¯ÍÍRl6këíãš(sÃą¨-+—SëœƚÔīĐõ›¯HäÖĒ)¯§Ø^˜ƒgckČÆic˙éÅÆŪZųīVžõhģ ĩäáGG„åJ=˙īī!/m=rVÉ'}Ņ5É6Ę=­ž\ŲØPŽfገĻDÎņyOqZC!õĘj921Ō° .[YMA%šj×Ų…0Äi6'Ũû‘ë]›ž#rc¨é“‡ŋnR Ã7ûlÅG˜ë{5=ĮãáfRļŸęL6quëŊ§Ü¯¨omÚöwŅ6“Ĩ՟qaōŽĩE‰īžļÔ811ŽžS<mül_Õîį3Z[]EõŨwXę[8ē´+1h—ŲŠūz=‘'3gų]ĸ?=Xžō´čĖa#;”!"FšžˇO´3ûāžM{_lqõ™ŊdŨÆô‘Ųžô×Î}Сč͚‹ƍO×B4eößWĪ•hÉãė9mĀâŸ![U.ŪCŠáf¤Îļ5*ĢŗqG¸ŽŪŸŗ´ÜZS[~˜šÚÆÍõœŪlG†oĄ9ãú-6(ŗM熤};•Íņ אôĩĄW[eaÚą­cÛ¨ß ŗß(Gļƒ™mÔ Ú:i ¸öÛņø}}ˆÔR<Ã4u }tXžë,–Ü_Ĩ}ŧI™—lš)LTŋĩŊCZ*ÁJŊļwļĄŒëūÚĮ3fiĖÔÃĖ]Qf§Š|ŗĸ] qpđllå�Ų<0mę?ŊŲ8Ã[)Ÿ?đž1čhģ ąäaG–bŗVЧ<^˛yËŅb.RΝ9Ŧ¤9/DzŌë¨aĸ%n{K4e%eŒäÁ@""ō žãÛTŦĒ>§Ö¸†<7‡OTr¨°ÚcIŪ‹KBŒ[5ĩ îDÁ Ã×Ô]>§oęžä™°g÷‰Üzũ™P_”˙š^˛zī ēĮ›ūĖæûß;xR/é\æę3%0pđe“wŋā˙îŠ ëŗBl2DÄwch˛ü­ŠL×c<‘Ūĩ8 įā‘ō%G‹ø vË:ģĩ§äž oŪŗôUjŞ—7?ą†ņ9™bRBųĀۙīÆ7k.âô-úމ:0\Ę˙HUĒõ8]æ3;ƒOSÂ$´ņlUĮ9mĀßĨÍŌoTļVgێđŨĖ¯-Ë-7ĩĨ⇪ŠmŨA†!ŽŠûīAœ^īĐ+ÚšSŲhø†¤mŊz ˛Ëũ^fjī˛^Âhč`š4ČætĮë3žaš:†>:,ĪuKæúŠ´7Šž5ZnŠ.ƒëˇCīÖëhg3‹ÍļƒÕOûpį˛^UxΖqG7o_:ĮäŅģzŗĸ[ôÄô“Ÿ8d6ļr€l˜6õŸŪlœá­”oũäsß2ŗ‰}(%÷8ę36˛VŠgØ]3¸—̏9-G‹)4#ŠO4„Ã:jØõYsDDˌh‰kõŲŊ§+¸Đƒ90\ƝRī?QNSÂf{‘ž•#ŸÎȕSTŲ~‡Ņ€ˆ|ŠžŦēëŲ;ę%&5ņE3={Î9ú3{Or3â“æuũȖ.–qEųŸÛū(9K|Ŗîš¸(ķ…˙Ķîy>MŠ'"Ig¸6Õĩ¸M™"ęøņdžOįé˜hÉrIŨÉũ¯:ãs—ņ6ŽNŖPVGŠtɆ'"\›ēwŗcGŪ΁’ ×KįJēŸ—RĨŽhu ō%"š6›4§?9YÁ‘N!ĸĀĐ ­ēčș*_é_›ŖęˆĻ˙ęlÚ‘ÔŧÍ;+–­Í¯ˇ˛ÜRS[ ~¸šÚÆÍ}ĻPSU÷#[*Tša*ŊôyĻŪīžöŪĘÆŖlÅpÉ~{uúÍaŦí2Ã0¤oę*ĸžĒÚüũ­wáCŸ0Gg3c}| ĨĒãYŒg˜ĻŽĄKs•’ûĢ´7)ķ­žau\ŋz‡´\ī�ŪĄúdãÁ˛Ú>\ų[ë÷qK6íÜąiĄ~÷Ļ×Ëģģx}IEדëĒĘhuõô!k2[9@6Æcc˙4+åĸoحʼn}¨%į8˛›õJ=C˸â#gŠNœĨđEüūš}Œ°{FD|iä RųaŠ´ûīĢ3dÁœęSE}Į}t( r­8¸įķ*­ļJ•ŗöõAR×Ö:Í9ũ0|„očâ�Rnš°ŧ^[Ĩū≎Î1Ũ Š;ōŌÚgö—™oĄ=šŋ˜$ęq/‘’ęSÅĐS"""ōŒŲ˜ÕzđŲM =?rUŧĮšŦu[O”×iĩU%‡ŌVÄÄ<ŅũĐmΘĨÚŧ]JŸÅ‹;UīO{xÍÚŨ§Ëę´uuåEģs‹ÉgF yßD;ķ#ŒŸ\˛ũųw”ÕuÚú˛/­ßW¸<iŽņ†Yč íģNˇ…ņ%2ŸŠ]{4|idíQ‘ŸĄ–ę3EåÕud­:›v„ų`Ėä’íĪo=Ą)+WōM¯—PˆÄĮĘr‹Mm)øajj7÷\,!åöMŠĒëĒ4…˙xĩˆĀĩ•Á29F=ÎŦ튅­Ŧv*[ ëėŋW›ąÜ2fĄYŲe˙@×jå§įôD¤/ÛũŌÁ&×~ ú„9ę:˜ ‹ãkČĐÆéÅÆx†ięōč°6Z*ŲļJÍŪ¤Ėk´ū†eÃa˛lčŌJŊ6ŊCYfãÁ˛Ō>\ųëĪäčc6>-cøáĪfD5}đĖ[eíáVŋķu•V[§ĘŲŧ§Â#Ēķ\s{:lŗąĩdķĀ´ŌĒö=ŋâÕs6‡Ķ+í?ˆžacoˇ2ąąäaG–bë§RΈŠūô̝ŸĻˆEĄü~›}Ŧčũ°…!Ģz;6ĐßėáƧ^úw>ĶÉ``/|ná\‰˙­’ŲKž;PÆ6mŒœ(ŽŨvŪPō‘™e[öøÕđMF×Ã+z>€ÅÂjÃÅ#–DŠo Ī]šēˇäxz÷ãSžÉˆėõøŠē÷—˜=ōĨ[s×CZú|¤™ūŸžm0šOĻÎ œũБ˃ÁP÷eæ# įJŎŠg.\öôĮߘ?ļā>‰øžMžŒÄž?¸qŲü0ņ­ūͤĄąŧĸ¨ëũ PËílųhlŨņŒ”Čš˙[%Áw,M}WeŌuī/ ôŋĩûiāߤ‡ųß*M-ę~ä— Q.+6FÎ ôŸļĄØzuļėˆÁĀVt–0;ö‘ˇ‹ģžšdašÅĻļü°5ĩ;xņä ÷DO Ī\¸2SųeæBņüm]3éö'×L ė~ļĖÅ=‹n•Ž+2Ø<RĖb39F=6ąĒÉV6v*Ģ#ˇķx į4ØĐĢmlķā-wlöBūēØ0ņ4IđÜÎÚõôKĶĄŅÍö ŗĪ´^‚};Ø :›Åņ5äN8€ŽgÚV–ĮģŊĻ;Žc –æ@ËŗĢŗ,ŊIõ˜ģŦŋaõ{˜Ŧŋe[ęĻŦ”`Ĩ^[&ķAŽks}ˇĪųw‰gv=_ÎøÜ9ÉÂwË:Š,ČH)ņŸųОÎ&ĩ2ĻF~6ļ~€l˜–ûĪųĖH˙iOīYeŋ3ŧũsp}ÖŪnebbÉÃ:Ž,Î]ÖŪ2 ožū3ŸüŌlüØÔPŖ1ũg0瞪šš???ĮegÆĶë‰ßųĄ2}áũáëéEõûcęKŽĩGīũÛ&æå;ly–���Œę´Ûī=ˇōđ‘Dũ¯ �veLėūd…Q¨ūŖ1›[fŧŧzŽWwúŊ­§™ˆ×BĮLbĄ×VU—îŨ˛ŠX´úŌ!�����ģr†ŒČįžˇˇë7žļõūŋ7ĩ¸ēųHä›v§ĮŒ™Ôĸ.?5úÅ É]oŧ–2˜/2�����˜ãŽ9������sÆôĮūΚ�����+����€ķBF�����Î �����8/dD�����āŧ����€ķBF�����Î �����8¯áˈ4›įEe•[ųÃDv{PĖŽjG‡�����#ÁΑvßJé3§í[&�����Ā0ąoFĕ•Tpv-�����`øØ3#úhŌU‡ZZ= šŗĩãvš–âFÍ M—…­xŠHÛĩŽļhĮŖ1ˇËφHæ-[ŋ¯\ßą\“v{ČōŨęOžY6+$`zTü–ĪĩZõ;ĮKg…Üŋv_yīŒKĢĘ^›%™$š. KxęUg5Zõ;/ ›"š$š=~íĩŪd# tŠ˙hEHĀũ‡ēC&}áÃ! 9uCm'�����-ė™-yûāú�r[˜ĨúzßcDDú“/ŋÛ´03/˙đÛĢ}Ës×fæˆˆ¸s[î]ĩũԌ ;O~qdwĒčܖ{׿× a˜Ö˛=o•Įl?ųī’͝Tíy>ūū&uˇúßĒ#+][^.Ԛ×Ē?šöđ[UĪíû˙O(?Ûųtā¯ßŋá-i?yvMfĩčą÷ķ•_ŪŊ! l{ęú#ÆÄĮZ�|b—KIõŠĸĢ:ũŲÃ*šą|¯› �����ʞÃį3 ãæÉį3DD¤w[šąaáŒ)ĸ đäÃÜZĘ+ęˆH˙ųëû~LÍʈ‘øúúˈߘīĻÜšŋŦŗ.pécហ‘gXä j!YĘ}|"fJT¤oë%=žzP_QÕâ1'>2Č×ĮwŠ$ö…ų>Á'"ž|Sū‰÷6. ųúŠfÄ<rw`KąRC6`ďZ*g4tdJzÕĄb Ŋ;ĘĶŽ-�����Ž5ŧOßö îŧĸÂđ=Ũ¨ĩ…#ĸjuYëä92QįZˌ°�×jÍšÎĢ1ž"~įFŒ+ųNî\‘áĮõ¸mNáséŖ'VĻí>ZTŽåČ3($Г1nÜT´=5ūoQŌÛÃ%ŗâˇjˆãZɆ�:‡ŪÃ/É?ZEDÄ:Ka‹ä|ģ4 �����Œ .ÃZ:ÃôĩTßÂŅĨ]‰AģĖ–úëõD=.Ā0Æ˙ôYJ×:Ōôŧ}ĸŲ÷lÚûb‹ĢĪė%ë6ĻĮˆNŗ9éŪ\īĘØôė‘CMŸ<ŧøõĀĖY~—čãO–§<-:sXÅČßĩ �����Œ1ÛõīÆĐdų[;™,dĪA@ĮSrΆ7īŲ@ú*ĩbΟXÃøœL§C…ÕKō^\b\ŠŠĨeāŪĩ8 įā‘ō%G‹ø vː����üŽ Ã]sũ>~[$áÚT×â6eЍãĮ“aø>ƒģĢĶ(”ÕÆ:ųS¤K6<áÚTV­'}+G>nĢ•STĩ4�Ņ’å’ē“û_?tÆ3掃Š�����F-ûfDn|†ZĒĪ•W×õzšu7~äĒxsYëļž(¯ĶjĢJĨ­ˆ‰yâÖōÖTīO{xÍÚŨ§Ëę´uuåEģs‹ÉgF Ÿ%AŽ÷|^ĨÕVŠrÖūŖ>HęÚZ§9§į€gĖŌmŪ.ĨĪâEÆ%Uû_ņęšÁE �����Ŗ‰}īšãGŦL=›ģ*áØŨīoˇ|‡3į…=;ø/mŨxīM-äæ?#ęšŨ î!nLøÆŨ/ŧ´yᆸ›Z]Ũ|Dŗ—ŧļķą@"ē+ãÍÚŦ§ĸ?&ɧ_xQŽ}Šėá÷–¯ ü‚'�?ôī!ŽEÜŌØ) ¸úŠ’fų�����Œ&ã C×/555~~~Ž fTŌŊ÷o›˜—O눯cæ�����~?Œé#žŦ0VčĩUÕĨ{ˇl*­>‚t����ā÷‘EuųŠŅ/VxHîzãĩ”)ũ¯�����c2"‹|īũ¸ú^G�����Ãižž �����0F #�����į…Œ�����œ2"�����p^Ȉ�����Āy!#�����į…Œ�����œ2"�����p^Ȉ�����Āy!#�����į…Œ�����œ2"�����p^Ȉ�����Āy!#�����į…Œ�����œ2"�����p^Ȉ�����Āy!#�����įåbĮ˛~¸ø“K�����°Î˙O7ą{fDC�����`$áŽ9�����p^Ȉ�����Āy!#�����į…Œ�����œ2"�����p^Ȉ�����Āy!#�����į…Œ�����œ2"�����p^Ȉ�����Āy!#�����įåâč��FZŖļéËĶ_ąg¸f™˙­ųvd*‚‘7KrÛ×8bŨiܸqnŧ>eÕ ŋ?ųô~ĩĩ­Ŋî§ËWXŽĩ­}dâčÁuŧËã{ķ¤§6ŧāčX†‹õa�ö‚ŒœKŖļé˜â”ŖŖ� ƒžå×Wˇī|úņ‡|'{›žÔÚÖ^qĄÖ{ŌMž7Or÷pŒÖļö˙ūŌ\qĄÖҁ #+Ã�ėẃs9ĨüĘŅ!�ŒãÆ ×vėú°ĮâēŸ.{OēiŌM¤Cā@Žã]nžt“įMG2Ė, C�°#dDā\X–ut�cʸqÍú–Ë~ũߕ›nā;$€nēÁŨŅ! ŋž†!�Ø2"��°Æ`čų‰ģĢ׎ũá:ŧ}ā$*{C�°#ŧĨ����€ķBF�����ÎËą§-9´õ™5ņ —LM ˜•đhÚîĪËôË~ôGֈωV˛}‡´ûVL M ŠĘ*ī{ îčŊ̓DS{˙„Hį-ģ÷9Euœųõ$‰ĻÅīŽˇ{´�����cšã2"Ŋ惇cÂ7ŧ{čLIuĮxøøđŽŠZķÅŪS˙>/>íD?§īÃáÜ?ÂEŗž/ųŠģU˛īlĢņ_GösVÖtõX‹†�� �IDATtũø‹|}}Šōã—Wũ-~ũ íȄ ����0Ļ9č툜f늕īV´’Ģ˙ßSŸ|0>4ȓ1žPWrtWÖkģÔ{YŠkßŅž#VũšōĻŦŽ/%û÷Vųü_´Û'*ŽíU=7'œą°ĒGėkųéf‹8­úŖg×eœūáāëd§ö,ÉÆ����ƒrˆ+ŪōÔģ­äŧū@ūDvĻCDÄø†,J˙pßëQD—o|­h$īßâ*J,ܧ6bī;VO$Šyäéø�ĸÅĮŸ¨Oé}ooŋۇ¨õėŽüęá ����ā÷‘öĶ×ķ/šĘÖm{(°Ī >ą/oYĩ0qũ†ĨS""}ūŅÔ É3§šĒŖëÂĻ…ũCĶĩļžüčÖĮWFŨ. ˜$š–đčæüō^‰„ž,˙Ĩĩ 1ŌY!ĸŠAĢiēV+z<Dt[ęáVĸ–Oīž$š*[¯¤VQ_´ãŠøy˛€ŠAŗÂcîé“ŪĢX§?ŗëdQĀâESĸ–†¸RĢrá@oc$ķ ĸ*u…ĩ{îlƕgĮĪ MZkr'žÕŅ~´"D45(jG)YŨžeĸŠA û댅W}ūÎ3+cæÉωφHæÅ/&[Qe—Ā����úį€ģæ´'žk%r‹|0ŪĮâJü°ô†uũÆ0ŽŽD\KÅĪžUXī$õ÷šv”vâŠø'ŽÕˇēzHåa“™–Š•_ėzö‹Â“Yųo/ôí(@_ôLüĒC—ČÕ#P:ĮƒĄĻŠU_ėzö …jĮ‘WÂøDž1ÉĢõ'‡J[\ũŖ—‡ú22 ¤ máÃË;ŲDŽ“Cĸ"ƒ<H[~,-ályŧÛ@Z&§¨…HrWė"úŋģe/•œ>ģ7ŋúžDũnkÖx|7ĸĻVŽĶ õžšēŖkī­¤Å#üå=]w0ö× žąËCˇĒŋ¨ūx˙šž›aV\Ŋ"ŋ”ČuÎō˙ķ5æZ ¯•ËYäÁpú犺ĒC¯ŠŽ[`Ÿ…lŲQŽãß|SëOZķ¯wåyÜĖüüSķ5Åc„›×ÍWúÍl™ë“ų-—šÚF(„‰~wĖģÍ˅Ú~:wŦ¸îĘÕ ��068 #*+Šh%r•EΰũŒ×•ˆ¨ĩ$÷ čÉ#§’§tmXˇíĮę[=ĸ_Ū÷FŧOĮbíįëSž|~ũ>Éžå>DDåīm>t‰\LĪŗšĒœåwž\rčåwī {:ĻD?™.zĩčPi #Yĩáš94°*8Õk›O6‘kđú{:ĢāĒöĨ.ßxÆæŦ/Üwļ•\eËøyʗ…n>ũEųĄOËx2ČæRˆ8m]šyx õ+åõę´ÕΟhr yjĮŽŽôՆáG-•{|q°ūØAՓ3d&‡šîķÃ"ˇĐÅQžDúĸío•ˇē†<•ŋīQįJ\Õî5ņ/ž}=ëč’÷šA]wC@X´¸å‹ĪL3"ŪäŲ7|č§fÖō–ä23ųšUĶĮ›.jkŅÖT~UPđuũĒ1áˇ4ũ>ŋĶoŧōĨŖ?A÷{æ. *.:Uk’ņ<g͛éŖ,:Û8I‘Ûôy‹Ãü‰ˆÚ&~_ŧīÛ¨Ō„žęĢ‚gË.éZØvŪD¯Éˇ„.X*œ0ü5_<øRv™äĄô…7]0f¸‰æÄEĪš,āOßvĨĨņŌ…3ĮŽũëĮa™aÍųÆ?ģ&HķvÆą†á¯ �fäīšĶjë[ˆČĶĮĮöķõŽĶå&7ų“tˆčܞlU+šE=›ŲujNDž‘éĪ/pŖV՞ũUÆ% ŌßĘÚúږ{L.;0Sîē;„ˆ~8Wníž4ÛĒāŠķ?o"ōˆyäžî*˜)˟]`ķN–īßĢ!r Ŋ;Ē#ā‡'Ë=ˆĒ?ŨĢČ]dÚĪwn!r-ŌEŽüƒ‡S÷VSāĘíģčŪ+›„‘Ū5™¨ŠpŸÚ4ôē“GKˆÜ–ĘųDÔTU×Jğ"2‰“™roÖžÜ}G6EŽĒtčoŗ&]˙Ķ…ŧÉŗ#ūo*X.cũ|n×ģŧŪņ“ķáŠē ĶâÖŪ?gŌpÔeÎmÎĒ-‰ˇ =Nhĸ04bžđzŗeŽ7Lģ#bļ×x ›üNüV™ŋmĮŠēf/[™ōĖē‡ÖŪ}+]Øŋc׉KŽŽlČô_}đüūī{˙Fŗ S=õā<ß_Î~ŧ'ûåˎ߸čÄ÷t˲VE!kpn#ŸĩrøxSŲáfVĢ.V]"rÚ#šâË"g¸UkÎ“Īˆč…KĸųDÄéĩuõuuõuuMãJDz}Ģå*mŦĸžŦŧ…Č5(ŧG"АMļm߸â}ŸVyDŗ""b¤wĮķŠ36ĨDœļJšmŌķĘr H~:j(—ˆ´…Ī<ĄnņY´m÷ŠIA663cųBQËéũŠîĪRo™ķˆ].eˆˆ|ψ܈šY/™pČ3H&™â;Ôë[vŌ‘™֎th¸.°˛Úēęš ?ž9sčüō6aØŧ?S}]\|˙ėí §O:ƒž’"—ß}RÔvĄø??ß</eéÜā)7ûNēŲ_<cņũ‹ūęŲūCíX˙†€ļēÚûø7Œf.ˇĖ™~ĶĨSŲyÅßV7Ôkjž+É˙Đ­‹ŋĐÃŅą€#üųÃqœ•<ÄķëMÕõDÔZĩoĶÚ=VmĒ#"Ē/¯īø$ WõųëÛŗ O—Öˇ ¨JĢhĒn""žo¯ûÔ<E>D6ü-”;ŗ÷HŅdyGļĐaÆ˛Ĩĸ=¯UŸÜ¯ĐGÆö,ûŌŽ;ƒvõU˜ĢčŽ7Ū~2hđ×/šŠļ<°ūh‰Vīx%rm¸ônÉ{š3{Ojcã=‰ˆĒŽŦ ōY°¸ã>:Fžnãß5ëkr\°ßM$‰††G…FČ=Gˈ‘õåĘĨ‹?Ķ-ü\čGãI×ÄĀ;bãÂüŧÜ\Ú~n(ũ˛0ŋ¸ÁøÉ7Qčâŗ'ßp=]ųīĨ gŽ;Yũ+Ņ-÷lJö:ņęĢgtÆoI|îąÉ§6ūŗøįîJ&ūõ§—M!ĸäíŅžxmûŠ }eĘīžMI^§öũkŌŧĶŧų.ėĪ•_}œwæBĮŊ'–‚ô[šžäuj_Š86NÔ´sފĩ­Ā‰~QņŅĄSŧoš0ūˇ–ŸĘOÛ˙å˙cīÎ㚸öÆņîs!}˜üT‚Ö„ÛBŦleS@YD… *TÜP̍Ŋ¨TÅjÕē u­[ëBĩāRqļÖ Čĸėe­,Jˆ°š >ž xžũũ–°$ÄÍįũâ2™œ9srΙųĖ93´ŪŖõ‘ŋĮxîõ&QYJR éŊâãōûĨŋh&ˇŦÚyēšz¸šv)ķ$^J"/å¯|[Šh}ččâ Ši˛ĶįԆ~<ÁîöĶôš~×ōŸf�hé-¨ž:Zæ5õ %îfēāYC‹úĐ÷Í'xûڏl Šj^,/ŖęM-šZī;yøģęŋ�P}qûšgŽæq7ų,˙ÍķíÔ^ķâķĪ@s¤‘›ˇë¨öYyMS.\LyôŧEmØ–ūs<MēŪŨY}~ËšgŽ㟧$ũö‡¸Ec˜ĄũėYNŖ5äĘ8ąī�΅ä3Ō}Q^#ũŸ5yõĘily{Ô%ÛŽ‚}‘Ī\ēžäŨ(üãE0{ÎęCjīŠ€Z§ŧåŅĨCGe^Ëī4äöBŨēĩ–!fnŪžÖz#HhŦ+įŨŒģËoīé4>š0ÛõŖj-õO /ũ”TÖĨgE „ūˆ˜ē€HXƃÅë DįsSZ:ÜT›“$įĮ\%b�€.;0ww!¤‘ĮB¯qÆē$“A�ĐŲk#‹zūäkn‚ĻÅ�Ā ēĘwͲ„Kˇ(� ˛v.đíô%�INdB­w@—Q0X\ŽNûHÁ €`rômŨf|ęeņˇĻœ~ŋ.G"�~bdÚgûœeŋ%eË€ã`ŗˇ(7;:ŠÆwž.@UrR�×ËŋãY ēžGnM>yív?7žŸņ únKŋÜė¤ÛsúũϧpčŸėąũ€ÚP&4 (éYĨšž÷Âv-—Īũđ¤Éuëģ0¨ųøņüW a:{ë°Â+G.üҍĻĨįč=wņ ę›sŲJn˛ĪĶ\ļÂíųåđØG`ēpYīIĩ´¨ëēē}~ųhų˙;pÉŦW[Ī–6*Č$´47Ģŗs5ÎK:’(zÖ¤d‚ZļūķĻ -=úÚC ˜8͝0÷åÁŠš�´ÆĪ púčâÉč˛F-sŲl-õĻÆfÅe%CųČEo&’Ōâ:ē�¤fŧ”YöF‚ĸ÷}V/qcw}R7^ēw{ۋƇąĮdŊęēN_ŌũąŽÚĩ”ˆŸÔĻššŗÉnjŽžy<KmüŦyK>ĐķSÎĮFFŠ/_a=€ĘētîæKĶ9‹gŒ&Aü$ũüåčķCC—Zj¨ŠĢˇŧČJ)ŗöXéÉĄŲü062˛”åéŋĜlĒNģtúŦ^ę:� ņˇ$žĄĶÜģŠŋ,ų‰•8z§˙č.ŲPSkŠIá=ō Øä?¨˛KßE˙pyČÖfīÉÍiģ`yã‰ãŧá37ų~¤¯îĩũ¯ŠŠ`ēdģŠVŊĨ6%ŠØgƗ>:ęTŲųīĸ{Ėę;MĢiöu]Ør#Ĩ´ėiCˇ‘=†‚^¨KˇĻöҌ… ME‰—~(Ļ4ôŧũ΃C?Ü�h~ėáV‘~ū;^ķP#ŋ9îAžŋēüp0GÆÄ؈q-CRx;U<ŖÛ¸GZ,ĻâŅ‚`ˆXįŌÂmŦ&ŒÛ˙m!ŦÉûŽyʄ 4\cD‚âĄ*%7A€¤uB`§Ŋ •y�wm\tŽ�€â÷´‚¤0:ą*`ɨN {ø…Öž!‘0žÜėt{ĶŽÜ˜/7:ßøÎģŖÔ”,��Éū.d.¯čz\ÕüåŖøˇĘĖgw~n“ëŧË=haYAN./áúÕärہāĩQ ÛĮ ÜĖšžG‡ôĮēöw8¤F~`ęįcĘ|ž•ũDš #;Ū‘KE"�x‘wņŊUŽ6œ|^íБ#4ä—מ€W/Ž{–¯%Vúná–ĻĻ–f€–&ĒŠ F*T]îÍĸW-�ĐPž”ņģĮcŌ_A~&�@ģąäČŨō=Ĩ×s‚MMÅąĮwĀĢį/[�āÅ˔ Į1†ēPô†ŲŽ‚—âî=i�ŨŊĀãl˜?ĸЎ˛ęŧMŲ čM†CRZ\G—ū&ętcšÚЏ'¸üßŨÔ_û,(2B§×ķjÍ#‡�ŧ҈´íV4]ģČKúĄ( Ԇp¸zƖc-ôĩĨšk,OĘqÜBf[ę�€ö0īŲOGRrkŦŨuAÃÜwš> 1L�´‡š:f&UԀeëøŌ Mŗ•ŽÆÚ�ĐXĀËkĐķ]âfHĀߍ-™â— ˆ4mü}Ė´`ø7Ãôãuŧ€Ņ#ēg”mãc9T�HcGŨ뤂˛F3+MšyP×ÔPSPĶ`jj4uüßX":g~€fļ¯ĨŽtģļ\­lyŲC}äEVô1ŗŨ<—ZzBķËž Ŧ° ŖHđB”(ę4ä÷B� Û­iXēYk bHŽl�€įą×4Õė™C5@5æ^Š+}�Â,^…Ķ öûÃāŅķ( „Ŧˆˆ˜NĶ]Č uįDD™ûZ9XĻ‹öÎZp•1iķÁŸŽęq �āŗ�DâšZ1€‚“įęėB �Ëegį‘“ÚB~¯3÷”ÜŠŖ@‰kDtûc ¤Ēʔ˜2WuéB‘6;RÎĖé>¸#N v]Ë+ŋtĄpÉfËŪëFĄŅ§įČņ[•|gSčĨ1įũÛFl”,��`N œĖâ]+Oæ/’\)ƸŪr†~c;/c;¯ų̞ˇúÎģĖ=•ēvœ‚€ų #?úhxךŠ>LŸĢŲ †ížy¯ģĖëæúĒŧˆË<ô€=\_Wũe>ŋũŅp-‚Ę?šíuõ´ VøđÁ 'ĮĀ…ęšÅU‚‡OjŸüÕŲJ'%ŽûŖ}"ę‹įĸ˙¨ëę…_Õåg˛� žŽĻįpH^‚O[›ĩl==æ~ĀŌÖTSSSS×xŠ�0ô_#āÕŊēļėĩŠ7Û �…eÕmoÚŖ 7Iiq¸˙üŋ.ËԆ~ÄVøėY_=Ė]™ĐĒšĨŽMkŽv ÜėHÕđ•ũöđAUyōåÂäďü? pdĢÃķꚖ!VÜöŽO]Īā}ĩŦē¤ú{ę Ų‰IៈęZZš›�†vdx[W[ú_Ũī5-ZVėļŲfę˙rķ¯ö՘´­暤Ô5öX2Löûíũöp–fKMÍK°Ōė%=P´Gŗ-Ũîđ÷Ûī-SĶTƒ–žŗ‡úNĶŖģŅáZîGƏ6eäæ?ÆÍķáÅSŅ÷žļ(ė4ä÷B� Û­ą˙ĨĢېßŅ)ÕÜŊpĨ}5ņ“öŪ¯Ĩ‘jļ& "4 Ä}ÔĖIĢôyÕek7YDísëÔÆ­_ɗ�‡ÔU4wŠcgˆōēŦ„ ą¯gį“g~O¤ci1J‡�Ü0ē<ŒÎ>'ũŅî#;¯Ŋ ŽĨ |ęAZí%{#P/ŊׇĶgų� [īįē1':‘ŧÄē¸čœ0Ëqũq‹ “Ĩ� ãŊ{gÚ´ā˜œoVž´i{@ļ’"E¸xpŽ+ģv=N”Ȇs€GĮ.ŠųŠéšU ›EnF˜ļ“Ɛ—ų”X¨DĖõÆüOîŨL­IöjÉ,kŽËHû_KWķá¯˙L×ķ<7â§Ü��j#œf.4Ŧ‰9“TŌ1]C †ēū{{į_„šš� ‚Øī"žš:Ų:z;úh6ŋ¨ÎNŠ‹)ũ•sŪe“j¤dFŽZZZ@MM]q&�ZšäfĒįÕt}—-rl)¸›ôđyS3hŲ.Xá)]GSMZdƯZŠ&FDŊeŖ‹~‰…��Zū§ęNî˙ZM6—mÎôŗ^NŸ…C�nžŽ}8ŧĩ‡×4pšf:��žfŨĖmŽĄŠļ´ĻĪ6¨˜:Šk8F×pŒĀgFIŠšYhžĖ†ŲÔÔ¯î~ŋån§ĩY�ŋĮœˆĖPŗô÷õ=\C˛ĪO”YIMŖíøÕŌÔjōFoՔ;Ęi’˛ŸQƒ––f€æ^ōĐ{DvÎöëdõą–†ÚĘÂÚĘÂd�Íė‚xúųXŸĖŖtMō{!i’íŨšš†´ˆåtrũq!ôú¤3&LBö¯ËÚWTķš_ÕėĪÖzØĩ>[ŒŽ)L<ąs÷…" FËn°S˜,XbŊ-;}÷ēXŖ#žíqĻn žXM8íL>=C¸Æ\€ĸēÔä˛0ËÖ!)qŲŲ•_æęXĩE”°V =)' � kk„ŋlĒÜ&;/G21I”đíAíŋG$.Ø˙ÍU!OĖŖs.$אîŗģ>Ŋ­Ŋ¸ė&ą¯‹’/ĨŠĮš÷gĀtÚŧ{VÁŧ˅GCöÚÆnļ$@Ųic9Ũ›{î˙ėŽ N2š§sOŦߖM˜Ķ:Ë-;– Ķ’˛(�×d`ŋŨP“r;ĶĩsP$UđRĀíME-/Ÿ=ũC:‰ĸöf’ųē@?Ÿ‚G—ĩŪÚÛÔÔ/ŗÎœãÉNŗhiĄ^ļfû^\ôŊ8ĐÔŅ3wôđ3¯ųåÁØ']ˇ ŽLģW.ŠÎį‘šŌķHP˜I…zN=Æj8•ũŨĩÖŠƒ õž&€4ĩæ–fPcv|H­#Åe5@Zū§ęNō¯u C+™…ôŗ‚ŸSŊė:lôˇļķ‚_xßú‚ŗÖˆ¨ųUqF^ūQs#Õ¨F2e.ƒŋ÷ĄŊ‡izYéĶg�L 5b;ž›ėĶåÕÔČa� ō…ZļŸûÚ}(]ÚđŸF€Ą=mCCC ZÄīwg;‡âMŌPŧNé<tƌœ=Bƒ€š†–fK%–4>ÉJ*u219€RĐi| ŋęĸŠŠԘũđƒ[Ąž3@—§ãå᪘Ąkw$W^Üxqƒdé0´°N$�`p&†ÜĩȲˇ]˙}{rÖ'ņžôuŠggĖ"h҃œœ2‘8÷m“žšsŧƒ&ŊÏō*›dĮ1ŋ(5G4jMԝƒ“‹2DąÛ‚Āqj†OšF&,(ånš5;žK0Ŋvņå(ˇ `ē}6.csNņž™nņ–æēL‰°Ŧ¨P¨ŋ.Äņđ;Č‡'_а&ų9ˍu[oÎõČÚÛ’…îžũ(0m7ė[ZQēÍųÆ.Ļ’eŪÎ8pļų‰]ŔX^Ķ]dwQgúækŠ÷Írž`1΄Ãb‚XX[žUT'öÔ¯BÃũĄ§ ¨š‚ĸvMå7Ž÷÷öÉ;z‰ß�đŧZĐlŠ­Ņô\Ø6ĖĄ1dŧjPĒkŦĶTV)jh ˛ãxÆÖē:đ¤ĨĨŪ#ÛûÃåĢÕ�%Õõ^"íŪ­“@†ŗYęÍĸš—� 7“Ŋę9ÁÔÔ Ąžíķj#͡HOY„O_€)g¸<m�PĶ3˙P¤īä—Õ@i ‡šZuZę „CƒÆ‹Ė#ģ“š=Bžt•í¨g/š€Ę€áúzj…õM#†ˇÍyk|YC߀ϖĐŌnĢĒÍu%ÅB€–k¸žŽZžāÉKøPŦüÎ;‘Tc=sĄõëäôÉõđ/é|ļguĸ5–îP€'ŊåĄĨÛ˙ ö ¸Ąv+7xĒ'vųMj­Ã4€z)…††ü^¨‹įÕ5Í6z 'Ō›ôt'{čæ]‰ĘSģ…úûú˙÷ˆÚÆsŽ%¤_Ūŋl†Ŗ1‡EĐĸÚZ‘˜`ķXļ;:ũįīY*5ĸëu áÆŅužLQŅ­k×c’‹„L‹ŠK÷ĮßøŽũ–¯ąį9s‰šôëboĐúsžŽ6Öõ Ũ<YŸ”Tg%d”‰§°Ũķl9 ImqVa-ũ:›�āĖ9}béDKē*į/윿Nß{%bš%‹ m÷z Œ‹Î�pŧü]�~^ú�’Ŧč¤ūšãŌŽŗv˜j¯¯Ûr[ú"ĘH+]/[�°Ũœ:ī"a|&áԗNFDmQjâõ˜ÄŒ‚ZbŒįg{oÄņ?ĐÚP“r;ķqįVÍĸ ^Jņķ×zü_ķ"?)鱖Ŗ¯›ž4ĸi*OÉkĐķœéķņûô´†`éŋ,dũK�tlf/˜ˇĐņ#ÎP­aCß7v´ ¯uM�"A]ķÃ1z� Áqô°%{ŧUĄŠąŪ>Úx$kG^R]ũg˜ßŊáZZøv~N#Åŋ”5)ĖdozN°î÷Úæ‘vŽFÃĩ´†síúŠŠjVö/= 5xų(ģLÜ<ƎBęčMtÕon;gųŲx:Â!īr8�Ã,}ėXĪ’"ÄfæWũ^S÷ûʂg"/Uj˜šZŽ��M#Wk-Aâ•ĨOë)ęŲる'ŽîųŠ@ �ėqÔūČĘ({FQĪĒ2#c_érÕZ^ü^ŨũFM#7‹!ŪĩĨŋ×ÔUgÅÆ%>ŊåtKãEnLJõ3ŠĒ¯ĘŒI˙ƒüxŒąĻâ<hhĒAãķ‡ę„õ2˙ƒü=BîeáÍLáĪ…_˰ËÕåŒÔũČĀŌgūBÃĻâ”Âį °ĶPĐ uŅTÎ+zŠī6Ãįc]ÎH=ÛŪž€āɛ}„ Bčoā)Ė:–ža–žaŊŽįö]yĨÜ7™Ŗ&-?4išĸĪŖŧ6DymčļØxÎą„9˛ųqŪŌm5Ĩ6@pŨ×~įžļËŌ­9•[åFgÎųÂ9ōßng˛6ßž2áUęŠhíŽ8‹Ž<X¤ÄzL¯žW÷ÅÜEW ģ|\Š‘‹Ä�#˙…=<‚Đužî<_™dLûH‘ĖŖÁšEŧ†›ĢQŦûš(#.ĪņßÖūŽ{îūĐōčZäM>ž 'šĐ(ü–tüf!�•qĮb=ü\gŦņ!՛녂ė įŸ@Cū͸æ¸­Ül×ÜôJ—t3ĩİûũ M2rŸÍ˛_ņo͌Ķå$ÕՋ<^>éēbŽļZĶŗŠÄbËe˛7='ØPx1ö_ =gnļņãŌ›ą×ŠIŨN+—ÁCŧģgŽt›úPŋķ’nPK[į8ũõlôš ‡Ÿ‹ęA_ Y(’÷L‹7Cs´ī’•¤đ˛2/åŊjl5!#>Đķ vs%LÕGû.\ĸ‘t36ōnCh°ô>öXî3† �¤ålßß#¯„gųŠī s*ŠæLƑ°&´ËC6ÕGûÎ ŌLJŒLn�räGž‹Ŋ]_ŗqŗvŗĸRŽíĢyŅĸ1ÜĐc‰¯ņ{ŊäÁŨÄŅzøåŦãߕŽ_æ)ķ˙ly{„^ĶŖk?yâęfgīo=ô=uhn|ųė‰āæ ŪŨÖß[“ßi(ę…Ę:oĨåQėšČFO߅n$ˆë%žŽģûļ˙"1BīēüųįŸí/žžŪĀeŊ{ġVO]–(ļŨ͋îßų~ōDũtĨ÷•ēĶŌuvĐ~pĢHvĻ…:ËlĸŲ˙ĻĻđ{}Āu~QŪ¸Ņoôü7/ŌKī2˙d TĶĐ„ĻÆÖųKc¯› מ:]Ú?ŗãŦ,Ėz[…íėųūŖ;Ã!­&ÛAū_ ‡¤:íø#�€‚ߎųx´ŧ•ß6ÕˇG œēLíCo“fĄŊ�� �IDAT°-…ūĐĨ"„ú„4üÁĮÜ 7‡ŽŠ^ģ)QœYaƒdÜ_ÖP“vģëƒÔ›E%ŋ¤ ÜŧSÔjČøā?ÍŌK—ĶRjà | [Ę~z4€7 uS—ņKŨ˙ëų4đī$˙ŋž{˛B!„ū:ŒˆĐP“¸ibŋ¸ \$a-<¸aĖ€?&áīûŋžN_{\ˆúÕĢ{gĸ5}Ũ|¯dj6˙įEMqldl‘Ō?LÛ/ē†C��!„BƒFDč  Ęŗ’īđäXL_ö՗sz}f z+ .íčۙ*5Á†GÉg%÷iV*ҟŊ'#!„JÈŊÆ_$—~1Й@!„B¨wxB!„BHuaD„B!„R]!„B!„TFD!„B!ՅB!EūņŽKūëŋūë˙đųáhp4ˇ túC÷fˆęC!ÕĸĄĄđį@įĄˇĮŸūLf—eäk>Ŋė ÔEũË˙č,ŧy=5C„PˆŠWg{�ŧԆŌūëÁ‹ævYĻ;røķúW_¨Čåy48Iš[ūžx^˙j 3ōæõÔ B}Š–:,wה´Ė&ē ĮŠRŒÔúīāEķtGžße9C]ÍøŖkž>¯ų?ĄÂPWĶÔ Œ?úp 3ōfÉk†Ą>ô?˙ė8+zzz—„B!„ę'ŌđgÍ!„B!„TFD!„B!ՅB!„BHuaD„B!„R]!„B!„TFD!„B!ՅB!„BHuaD„B!„R]!„B!„TFD!„B!ՅB!„BHuŠõaZÕŋ?íÃÔB!„BH1ũü›)āB!„BHuõåŅߏĪB!„B¨?áB!„BHuaD„B!„R]!„B!„TFD!„B!ՅB!„BHuaD„B!„R]!„B!„TFD!„B!ՅB!„BHuaD„B!„R]!„B!„TFD!„B!ՅB!„BHuaD„B!„R]!„B!„TFD!„B!ՅB!„BHuõ{DD'™šp œ×eË._]lÂ50ˇ%§7õ`˙dށ‰ŅęÛ}˜æ F×ĻžÜ0ÅŲÂԄk`iáę´åRp s%Cåž4`Äbŋ žé5ÎԒk`bdå<yîÆŗSc@!„Đ cDī qŅŪ™^ \Īæ‹( I¨Úō´‹Ûüf­‰ëķķšŗž&žŅĩ}.B}ãÁÉ ß/ĪņŠj Ž…ķ8s]BĖĪš>/hG!=ĐYC!„Đ ƒŅģÎÚŋæDšFã Kŗ‹ō oėôã2 6iĶ׉â>ŨXMrbaŸ&ˆPŸ*ēpĻ\ Ûm é7ĪDŋ˜œŊΈP}5:C"„Bu1X#"aÎņÕŗŦ,š&F^A[Ž=č8Šf\0ÅŲÂÔŌÂuöʨĸŽwÄe?­ögjidå´˙vęœûĐę�Āxíūp/.��˜Æ3öÜāį9oÕ }��ČŲä`Â5đڑv{“­‘ÕÆT�E…Iķãv.õr°ä˜X8øŸĖ�ÔūčcâŧĢ� ŋvã,Ŋ*V˜ˆĘ~#h�Ņ"Ą�Ģu aŧčtBZ^AŅ'ĸmĨĒ„o‚}&[˜špMmfŽ9Ū1§îv°Š ×ĀkoYëëÎŗ={lGâ‚čSœ LŒŧ”ė¯B!48¨ tzÄ?žxéžrā:y.ä2„…wâ/~UPK$ŸöÔ:kgPā™j⨠Ķ.ÅīZPąąA\�ņ­-Á›E@šy™CÚļM"•9÷(Ë) �ĀČo2ˇĶrc˙}‡Ú_�ĸÔ]߈ÁČÎRŸŠ¸0×­J‘F Ŋ +6‰w`АHˆ "M|gŲF\ÎËbēģĨ ßd}[.đĘŠø/ Čęd;c.S‡Ŗ+ŗJUÔß]Ńe9yú(ĒčVzŌžyEüSąûœ™ŊĻŪŊ=ØQ.!ÜfØĐEˇĶ.~Ā—$œ÷×UÔÄB!4X ʈH\”V.Ō#ėØ.w€öw;š!ä˛h�&ŽŽ`/<ąŲ’€FAŽ_ĨEœJ Øå"NŠL°üFīs&€žącZ@¤h ÷ĨEB�`°uuz]•Ēa}‘|Ū_�„×ä&]”Æ'š\}ŋ=–CĢÜí@uaB†0Čß.苚äËŲ"Đõũ<<€Ŗ(UūFĐ@âÎŲĘ[q0[Tą->€ÁâÚN \ēdŽ-‡��qâ۪Őn{bNzé�Ё;}ũÎTĮėŋ´ĖyÉ(Ĩ6!ĶŽÄ‰AQå’öz.ž<e ¯đ‡ČB˙ÍųũŅë&B!ÔOåŦ9‚3Š@%­›ļ`åÎŗWŗÁ.dÉ"ßqē�P–ņ@ĀāŗÄBĄPœQē�ĸĸl>�ŋ聀aîlK��SĮązŲĐ;Ŗ}&ëŽņõhŊXŽ 0™Ná7’>ŗÜ�hĻ �€÷0ăß|Ë%Ņ)ŧ ģC'›sIHDüôËáķ|ƒĸų��…Y�Ãfędé%bŒ—#�Ę3 ”ÅėhG…�†š›´ž3'ŧ_Č/MŪlаu „BhШ1"ēķÉ;Mˇž”ž: ;öĨđËīyüÜx~nü�Ûí̈#\ (�$a]d>^[S4ˆi� f[x@0I�Õ’Đáčđ%u55�Ŋ\âf萭ķ‚hų… Æâ‚¨m;Îd<¨Ĩ$ “SˆJ#hĀ;ß%vžK�haŲí[ļEQŲGĪflCIk&ŗŊfÉbÔ%ô:o s;IS#ē û(lbgßB!ԗú=""X:€„Ēâׂ3§u!]]Č�ĐåļŽ!0-įŸüyž¸Ļ¨ §(-ųŌÕäjŪ΍W.Î!Y€„aŗėāgļ2į:Æ@”1 � M‰éÖĀJ,¤úu××f yŠO•Į'ķ—’šEĄė[¯šˇu|?ßü•g÷@‰_˜tÚļā]I"†QāîĐŠ\&$QÜã–$ĸŌß8´°,+§¸Š6úÔׂ �@č{nū*įÖŦËĩâ:! )­™âöš ”H � R§#jŋLbąĸËIvI‹Å4L&S~ë@!„ĐāŅ˙ŗæŒÜœH�(Œøæj �@×ĻîÜ'`Muâ��Ô¤˙tō›ąeL] ßų›]f ņE�Æ6& � \'g'g#&MŅĀd2Œ-L�@RÎK�ˆĶcrTf8‚pZ �eGCÖ%”I'ūˆËŽ­ =[FUđ鞯zË/Ėǜr�p§/ķu˛ŗÔ§…u��Jv`͊Qéo  ÂS+Cˇ…šq¯ũņqâ‚äb!�ččë�–Žv$€$7>YÚV肄ŒZ�0ždĮ�i\$zĀ�Đ9ņé ë­ąã€¤˜—ŨZĪ×Mąŗīļ#›VÔ:B!4hô˙Ŧ9ĻûÚ/œĶˇĨ‰î„yŒŲÁbXDI�€aōõœÖŒēøˆsŲTRAš‡.ˆųšqå�G7c�Âs•ī÷ËOĖ›]3؈āgÄåԁEh‚ŗčL tú6;]˙e�lAeT @âY_ī “ũ› ƒÂsĒcBũb֓$ASŌųnđŨ3z~ā‚ŽÜÂÔ5æ0 ZR~vĶNŅ¨ÚŒ[B6D|ūíÃû-ÂÖ:2Y$�Uą6˜ī¸v>~#hP!ÜžsĘØœ^ķšKK Äĩĩĩ”€t™?�˜žĢBÎĻî*æ­÷ āĶĨŠnĨWC?pít]��#įqä…D*{gđĘ2(ˍ"Y�"šõVĮsUĀŠŦ3ÕņĄAôd#ēčvšŸ/s&�ä÷W!„4ūšuëÖö¯^Ŋ2dČß&ĶÔÃËü=áŗ?^R/E¯ūī=և–îŸm;´Ķo4Ņļ‚ģ-Kø¸ŧä~jZNq…ačėŋõā.ÃÔ�Ôt]=,O—˙–•YP)&Įx}qp˙|�ūÛĐÁ‚ņ°¨äquÕ3Z×ëëm/§?û'gŌ’iŖå3õú”šÎßéÎŦ˙üņĮË/ëŠÆ’œ]|W9¸Ęq˜tgigb Šx-ķh<å&a0Ö@XTPų¨´ô =:pߥ•vâÔ;ĨÕU°Üƒ&Ø}Č,IĪ|,z&¤†ŽŸé9i*~#hPašOņ´JŊŠ…ü^ûėM 5°t_ļíĀÆ)iÅĶąôžČųOÍãŠŧü_KkÕt­Ļ~ą˙ĀjkéØ 1Ęʂ~XTņøQå‰nĀŽpËōswëūɝ´Äk´ZOíh¤ŗģ5ųŦĒüˇŧœâšŽĩīĒc{æ °ŋB!„ĐĀ“†?˙øķĪ?Û ==ŊËB!„Bõiø3(ŸžB!„Bũ#"„B!„ęˆ!„B!¤ē0"B!„BŠ.ŒˆB!„BĒ #"„B!„ęˆ!„B!¤ē0"B!„BŠ.ŒˆB!„BĒ #"„B!„ęˆ!„B!¤ē0"z7íp5áXŽä tFŪtmęɍSœ-LM¸–ŽžA[.:W2ėŸĖ501Z}{ 3ō6ĘŲä`Â50 Jˇ˙ßņgjëäŗtGB=ĐšėWtbŠ ×Āy]ļėRņÕÅ&\“q[rú%ŸæZr L¸ļ+ĶTĢøB r*՜õ50ņŽč| A@\´wĻ×Â×ŗų" H’Ēļ<íâ6ŋYkâú<(Š780H6‡ÃæpØ, jË3"Cƒĸø)#ŧ_(��*56§ŸB"l€!„” *QMrbá@į tÖū5'Ę%Ā0 <_Xš]”_Xxc§—ĩI›žN÷鯰â vÛcĶS’ĶS’sōy‡=Y�TaÄŲŦÎ•J&'H€ÁÕį�Pé×ŗú%$ˆBH‰ ĸ7Lq6201rđ ØríAĮI(]•đM°Īd SŽŠ­ĶĖ5ĮŗÛ/Úß65áxí-k}ŨyZ‘tnŒ×Žė˛¸-ŗŦ,Ŧ&lš]�PûŖ‰ķŽb�(üڍk°ôĒ�„Y'×Lqļ0ĩ´pŊ2Ǎ- mé¤ŨŪäckdĩ1ĩ? ¤īHĘŽŽī˛ûâ¸ļ\“qëÛ/ĘŌŠëmš&ëĶUqæ q!Ą�Œ×î÷â2�€i<cßÁ ~žķVÍĐ�9Õ@^ ųq;—z9Xr L,|ƒOæ^ŗâˆË~Zí;ÎÔŌČĘ+h˙íEߍ‚fĸ ! �� 8ŪAY� ĒŽLķ$aÎņÕŗŦ,š&F^A:įŋŲg o%I€1fÁ—Ū\�*#&ģsÉÔ˙ô‚¨\Ŗ‰m+õļue{~„B¨ũŅö|}=[Čr™áaĮŦÍžøUŠKŌ3ļǍžĄįx|ɨÉĶũlYâĸ¤}ķÖĨ)s#�DŠ;׎5rŸlĤę˛/ŽY] @šøÎ˛e�°,Ļ.đ0!čŦA’˛i}ī ;˛<~ׂ Ö)4méėúæ–ÄČÎRŸų†ŠáÍ(8r˜ĪcË%¨ēė‹k‚Oō˜îŗ'ą�D鉭kņŌ)�–ˇī8b@s;0Ęr (�0ō›Ėí´ÜØߥ ‹ÜŒ™�=UuF|k}ĐĒ35ŦI xčŌåŧKƒŖ^ˇâ‰om ۜX."¸.^6DÚļM "y{ °™(h¨MÛYļ*ÖEøĮ/Ũ—XNXz.\0˝CĨ]ü* 4Q�Šęŋr}ĻtĘÃbęd§Š“õģMœßúréæÄrp뜌 ųĢ•gĒ[WrëJõüoĻØBŊũÔú{ƒâÛ{ŖĘ%Āō;ŊĪ™�ņíā)kx…?Dúoæ&î=ZLéļ'椗�¸Ķ×īLuĖūK˜—ŒR*uJ¨ûuō1O Ámá5QAr‘8ĀĶ.苚äËŲ"Đõũ<<€ÂkŅÕ�ė…#6[°Ā(ČõĢ´ˆSŠģ\Z™T ë‹äķūēo° Ū‰˜ķyōé:@?Øī;5ĸēėâĨ‚ā cœũŊ9×#k3x…`g P–‘%āxøŲĒä ‚P$�[W§×Ueǁ‚:CĨņI.Wßo΁åÆPÅ*w;P]˜! ōŠ'NŠLA{ģ gė˜ŲcL$VĻ™ôØ8oWx˙ĻĐĩWĪÜ�פ÷: JÄEiå =Žír'�hˇŖB.‹�aâáŋ×gļN™įéŽ:^“¸§øé×ŗh§Ö “"Ķ(�ręÁč#nL s6M ē�� éģŊo]Šž!„’ŖßĮˆ 3 $� s7éé8sŌÉû…üŌä͖�…Y�Ãfędéy 1ÆË‘�åĘÎv`ŒņrÔ� FY˛@"õđҞŒ�ט% …BāŒŌeËÜh=Æ×ãm ‡�€1Æw’t÷M&Û°� ļŧJ �ŗõęR“Ë� *-ƒĀõš>f@ķ:`ÚÂ@ef vTu†é~3!ųį3ˍ€fę°��hņëU<~Ņ �ÃÜYÚ.‹ŠãX=įIŠfĸ\CP%Y[|\';šNgå–,`M]3Īd s5¸œQ,�*iŨ´+wžŊš v!KųŽĶ…ŋßgļM™ķš¨�Æî]&ÎĩÖ 7géí¸ŠN2õŋ÷­c…G!ôˇô÷M‰h� ˜Dˇņ šKßbļŋE˛˜�ĩ@ Å�Ę]ßf2Z×#ēo cC �’Œ°‰.2‹kkj�ŒĨ˙3tȡņz:ÁlĪ6Ádˆ¤'æLååoytwarFÕZVVr9€‘ß ãÍęĀŅáčđ%u55�ŊŒ<vT…uF\ĩmĮ™Œĩ”Dar ĄAZų‰öĘO0I€‰”l&Ę4•"Ąęj)��` Į/–;ĢāŨųJ�Mˇž”ÆáãŽ})üō{?7žŸ€Ávû*âH�^§Ī',š!m ŒÉû‹ŽymO™{4ų ��-„Ö‰sÎN�-–Öj˛ŊV3eęŋ=6Vx„BKGDI�éP:C\,Ķ@0™L’Ųå- Db�`:áIû!ÄbÅ០ōĀ"�$ ›e?“8ĻķÖĮ´˜j+;ąX � &SZtēļßĻ'Ũ*de—XL÷Vnâ;ˆk3†<ŧĘã“ųËGÉÜJTö­×ÜÛ:žŸoūĘŗ{Ų(¨3tÚļā]I"†QāîĐŠ\&$QÜã–$B”I+?Õ^ųÅBJN"Ę4ԕķÁŦ(/. ‚ĨC�H¨*~-8ˇÍŖĢ ų��ēÜÖĻåü“?Ī×äĨ%_ēš\ÍÛšņĒĶÅ9¯ŲgvéšĨSæ�€Ē­–­ÖTÛÄ9éĨ€Nõ_ÜąâģÛc#„,ú}֜ąã€¤˜—-�§¯›bg;ŪmG6 –Žv$€$7>Y:߁.HȨ�ãIvL�žđ‰đĨ7úæÄ§ËŊõ\ZL�ۘ0�$p\œ\œ˜4EC[đđ“$Ü�ĐŌsE� kdŌēS:îŗ'‘Pŗķ\–„aûVÎ ė#„ĶÂ�}�(;˛.ĄLZÕÄe×V†ž-ŖĒ øtĪĩ@~ŠĘ)�wú2_';K}ZX� Ąd¯Å÷^ņŒ-L�@RÎKkm19rĒw/ÍĄš9‘�PņÍÕ*�€ŽMŨš;NĀ0šęÄ�¨I˙éä7;b˘ē.žķ7;ēĖ@"â‹^¯ĪdzEđ+H˙ʏy­Sæ€ģ4Ļ}9ŋä¤)3qÎØb�HŠZë—îũo÷Ø­ !„’ŖßŸŦ ãš*āT֙ęøĐ z˛]t;M ‹Ī—9�žĢBÎĻî*æ­÷ āĶĨŠnĨWC?pít]��#įqä…D*{gđĘ2(ˍ"Y�ĸŽW#{F0Y$�Uą6˜ī¸vū*ßī/–Ÿ˜7ģf˛ÁĪˆËŠ‹Đg‹7ģīoƒ(üÖkîm;Vmjb5�ÃrļûLgoVŌ…ĸr`8úMVÁųBLBöo* ĪŠŽ õ‹YO’MIįģq<ÂwĪčšht<åÕ]cĒ%åg7íĒ͸%dsAÄįß>ŧß"l­Ŗ˛Ogb ĶˇŲéĸø/čd ē(Ŗ H�LJęÍTÜLęĶ}íÎéÛŌDwÂ<Æė`ą@,ĸ$�° ųzNë¨h]|Äšl*Š ÍÃNÄüܸr�ŽŖ›1�!ˇū÷žåÖ)súî^2c:ĸŠNdLbÛÄ9‰NßĻSņĄâÉFtyn M´ Éo}ŊmģKĪŋÄEĨ{>„Brõ˙͎‰1_EEmô°dÖĻ^ģž%dŲÎū:öô|éŲ$čLėîyÎ\IAōõ˜lJgÜŦįŖÃm[3ÆũĢŖËœôIēüVB.íĩëH€ô§ch%Žū.Á_8s@•g唋°Ûšfĸ%Q}ëâå¸23ãëčĶJ>Înp’N&dēo;°Uš\,&õ—=$ķx%ÂÂm �¤ķtw?- ŒŲ6Ë؈MEQ@rĖŨėŒŋqĀ[nÉČ­3L¯¯Ė6į0ęŌĸ¯g1Ļ9}tķl}ĒSr„¯Qņtŧw]į¤OŌÕŠéE0yį‘ú�˛ŗD;(l&ÉĄëōÆÉuž6\I‹D4Á⎛žîTBt°ŅžÂé¯ũƑ5ÉįNDœģš-5ųŗį6Ø  ū÷ĒuĘĮqj§In„¯#ŲņS­:Ÿ<ēÎIŸ„ęŦœrbōÎp/éD>FëĘqë] B!ÔŗüųįŸí/žžŪĀeŊIÂÛÁSBxËīoŸj>w!4HŅÂ*~HDpLt�@ˇØyUē„ģ4>y-ˇ×#„B™4üé÷Ys¨˙Õ$nÚuŊ 0§ŒrÜįĢ0B .âÔ]Aaéƒcã=؈¨ÍˆK—�iŗl6†C!„úFD*€=HË(“\§ųáģßēŸEŊķt>=ûžHΉ9“Ë YŖœf-\ûŧØ[!„ę8k!„B!¤Š¤áO˙?Y!„B!„ ŒˆB!„BĒ #"„B!„ęˆ!„B!¤ē0"B!„BŠ.ŒˆB!„BĒ #"„B!„ęˆ!„B!¤ē0"B!„BŠ.ŒˆB!„BĒ #"„B!„ęˆ!„B!¤ē0"B!„BŠ.ŒˆB!„BĒK­ĶĒūũiφB!„BŠé˙käßLĮˆB!„BĒĢ/ĮˆXC´ú05„B!„zĶpŒ!„B!¤ē0"B!„BŠ.ŒˆB!„BĒ #"„B!„ęˆ!„B!¤ē0"B!„BŠ.ŒˆB!„BĒ #"„B!„ęˆ!„B!¤ē0"B!„BŠ.ŒˆB!„BĒ #"„B!„ęˆ!„B!¤ē0"B!„BŠ.ŒˆB!„BĒ #"„B!„ęˆ!„B!¤ē4"ĸķwLĩĩ˜úmqĮĸú{‘›æNdcåä8uîęČLŅĀåî]•ģËÃ"āLõk|"‡ģíŦHÁ›ĘĐ�PĸšõP9 ‡û¤°€*žēŊ5ŋå;~Đ¯�Ôį]>ŅĘvîÕēnŲ[7ËŨÉÆĘiĸßēC÷ë•z‹üŧkÕ,÷ļ:šY+gįkŲ4Í~zXJŊœ÷ßōЎĶ:Ju8J—mÛú=~õ‹ūy“…Õ¤Õ)T§ÅĸØÅö;ōdQåŋ|ļ`ÖD{' éūî/îōĄŧ‹›—Κčâdaekãâ1kéö+%×�� îl˜daå´8Nųš&ͤíŦ Ũž2Qüb{[ ûu?÷ĐŌū&åûˇöÆĢäqJ‰^ĸ]ŧŽã‚ŖßΡĩX+›ˇ}ĸ•Ķâ̞eHßÛ0ÉÂ}wî_í™_˙đ÷ŧFķéŪféōČšŽßw¯_!Ô§0"ĸ‹#öÜ|ÚyÉūå+"*Y~돝<´ÉodÅwa+"+*¨Ū´5›—9ht6úŠ2ÕŦ{å�€ŠŦ<Ętŧ5QˇiŁ_Ųá§ŖŽoŸĘHŲ¸|s—ÍŪR�QūĄĨsCŠ'zÎŪcÃĨ{OGí]núø§5Ë•ĐŊŊU˙ķÆÅëīJÖė=uh{āȊ ĢWíšíp> ?5[ąé|˙õ;šÅ+KÉį5Ęļ5ŲŋúAĄ!åĀ÷šŠ‚Šú;æÍÛ+`š,ßē÷ØŅđuŸčÕÅ혰éNÛyēøūöyÁ?TŒôŨtāôĨĢįNķš5d†/]ūcEᔍĖ+÷AOOĢøjj/d Fu¯KŨŦŊ[ Œ×JFiJ÷o­”<N)ŅK´ßßŗ>Žąlkˆ§Í‡P’™'ŗVufi=HŠ3Kež´‡÷JH+펪gVēųôÜf Ŗ…áË ë÷gŠû'ŋ!UĨ6`[ޏ~•1eęĮ?įˇ-Ąŗ~ŒyŦxîĐB��k€ä+C�� �IDATSFĨgh<¯zĄū€å€ļų'S:}G™jÖŊr�@m~~ģ5YúĶÉ,ŌīôŽ@S�ĖÂĄrzhTÂ*×ŲeS€Ú¸S)ä’SŅ#t_Ũéz8•úcĖcÃĨ—v|Ē�æf#ŠJ˙Ŗ˛pa*xK”y#\7î]ũ �`f %žĄwyå!F=•ĶpöĄģrŠ ˙rAfĩˇä¯,%;œ×,[y_ũ`@Ú왕$ėģ0ûōBŊWÅmßr‹˛Ūpúø§m+8¸Lķģ<`Oø¯ņߨ@įÅđę >OÜÖVÛõ Ė­´é ƒÅ%0ėHVt÷Zá˛g cߚØ_Ē})Ũ‰“fĻė’„ Vļ/ĢK‰{¨o0ēú\SļkmŧŒŦ}J§”č%ZUūx€Įđ>>SûąÚQŠ÷*č)ÖŌ¸ ./īŠĄÕØē’Ėbpą‘Ž^Ÿ÷”abJ�ųôĖJ7ųmVoæFß+AO|jfÖoųFМ#œßu–ö]d(ŗŒ°ÛsétAûk–6 4ŖåJå˙¸f‘‡‹“…•­ŖûܰČüŽ+jĸĖC˙žåhokã>kudĻĖô‹ŌîN‹/äßøz‘‡‹“ũôšûSEĸü×Ėčâdã>7ėj% ĐyV‰ô#Ĩwö/Ÿæ>ÉÆ~’ĮŌoīŊ]S{¯f=UN��*īūCļõXNu~ŪĶaLÛŽe’ÖL•˛Wv{K€å~Q÷x¤Ž´B2ĖĖZ¯íĩžĢûh*/ĩBņ[ŦŠĮSoú¤#9‚ĀĐęa„¤ŊžLđ˙,hy§zō‘[ŧ˛”ėp”/[�ų_ũā@ē…ŽŽÚsĨį0QpķBeēdS{8$ÅöŨuîōV{�@BŅ�@KdW ė7G_>ÔéSu?_-%'xw˜ęĒũđFŧā52Šíâ ÷ô—¸ŌŽ%Õŧ›•:XË †Đuwö¯’Nutī4K”w&lÁtG{[ ûI 6ũ˜×ÖáÉí!;Ī=ŖëîmMybĀĻķ­ē­ņ*Ymzī%Ú6˜röjŨ虭“Ŋĩ֋ŧģÛļYz¯RÛ,Ā͐úõ^Ûœ¨4ĢFO4#_Ŗg–{W’Ĩ;Ļʔ‰(vą•­ÍšÔļqĒŌîNs/Ô)*ívJ7m–0œ1×āéÍ Š}?e!ôÚčÚ’ĖŸšõķ/ˇ~NÉ,ģÎ�˜ˆ¨öęžõ.aÁVŒNķ &[ÕŅ! ōōŸ’fVzũģˇQũ­Ģ ô–}w.1îŌą5a[~ĄZßÚ)0Xq-)*ܧūėņģ í#’Š §*>Ųw=5=iëhAôöy!KßIMžĀH9pđįnu`H*ĸžM1[ųÖíܸ-Öu—ÖÍ|ĢTŊT39•€ūõ^‰–ĩŊ<}X#õŲī0Ų˛$+ę”N€`i3{Ė�ŲĶ’EBÃĶ:‘¡:6A‹EuÅq{öŪb8ēuģ.¯ žŧSäoįĩ^¯ÃéĨl|õƒ }ŋ/f˛~=~4ĩ‡H˜*ŊW †ėē b�K߀ÕZķ¤gö§Vo¸x§ĸ^nïæŨŦԞâmE€ŠĪ' ~‰-LŽœâūaŨ­øöŲ}åņ ÕznSL;2zo׊Đ8‰ÃÖĶךtjÃXŅÕÕ+ö—Ō�@Ũ _sJ`z*æZâ…ÃĢ ĮCļ߁Ō5ŸÎ=°b}LŲzúō…CĢ *‡Ŧj\ÚŅx•Ģ6Ŋõí[Ėģ•I¸ģJ×$Ǝ7cÔå˙* đ蒴†éxkS3öĶŧé'éâû•=kv—tôĖ ōJrôD3­ęüRqk62+´‡‘™­AYu~ 5Ōڊ-ŋ´{Ō[ķQØfŲŸŒĻîķōŪĒ# Bī$Q\˜˙ŋ÷:ûcÔŲŋÛ>/pûŊwĨaDD$Š?*pX:^Ņ\ēøčĻわ—-uQâĖišn<wũčúifzļžų'ŸÍ4hČģ_ � JŊ’Á맙ąYlƒ‰kC]ÉN—wiÃË´ �–ƒ‹4€Õ‚š†$�Ą?Á…- zØmāģî=�X.>VèĘJˇŽnŨĒ™‚ĘY’™Vã̀ĻhRv-‚Ņ~•X‰iĀfÔWT´_jĨëJžДDá[­ę¯„8;}2cŪŽRÍĮyw;iRPOT]¯N¯eĢdĪ6ĐĶekŧāÖÁ%ŨbĸĮ"`°Ų#'ĀųtīąÛÁŨƒĄžãė§Īú÷öCqųĩ+O¨`ģų˜�ųųÖķŽŧÆÉ,C˙“ŠfīJžô#•7yĒīîŌq-J=ëŠYđæÕÛČõ‹MŸ~(ˆģx¨{XŨ mííbÄfsôM§l<|.âķņ$([ķ)Ūqõæ![9čZMÛ¸eÕļč) ¯ņĘ­6ŊôįV6hšļEĄ¤ĩÃh´%UÜ/Ĩ íĖIƒņVZwĨņIéŊüļŊUyĪ=ŗ‚C€Ü’$Ė&˜BɯŌ:RœYĒí>ÕZRšW � .ũĩš;ŪPAiw§DķQˆcf ŨP™+xŨĪ!„úU—ī~æŊûņ‡Ŗ~%'|ōųǐĪW­Y0xĮĻŪģŸy//ŋ¸î펍ú?"ĸîøžÄ,4ĖUÁY•{tųŠ Ÿûæâ-DJ!˜Œú{'ÃæúMŸčîáč2īp)Đ �€ ´´õ ÚKûC-ŲO˛õôZå ’`�Û°í|ˆ HH:EO­Xz퇂$@Ōđv6‚îÕLQå,Īü•2ŗŗVxĢ~¤Ār™éĀȋØ}Ĩ‚š*˙eOøŨz� ŋÕJÛuÃéS'÷nōY˛kņŠ •ŨžųõDĨ)ĶáôZļĘôlƒĶáķUöԕ]ģ=YC‹�Pĸ-“6 ÷Ũ¸›xéčæUŪĻäĶÔČ­+<ŊW]Šh˙hé•_žNõjŊQ„íėcА÷ĢōŊÁvžf š“%€’Ø”ú}¤'úR‚Ō ÉHsŗŽŗj}+FCeq€žŨø‘O¯lXžã­{õ4h™IGˇ”ĢųĨmCƒļéy„éÜoÂW;Đsãí“ãT}ŊØzÃÚ_sė­ô$Ĩ÷ōh�A^~ŊžŊ) ĀÜĘJ˛Jh€ęŦŧz-3‡Ņ=ĻÕsĪŦā ŋ$™fö†’Ō\�î僚ÌņzOsK)�ēä~)aeo Jģģ^›Oo´õØP_÷Î? ĄAĒōĘ7›Ö‡Ŧ^˛ãĻ@RŋcEČę!ĢW„LŠ—”D‡­YŊz͎Ãŋ<î=ĨAŦŋ#"ņũƒá÷õVmtgÉ]ĨūÎ׋W\•ø=Ŋy°?EgĐ K÷¯8\Š=sÃĄsÎ_9žŧ}† %Ą€Áę˜ÃC0H9z��†tbôŧz¨f +g]^ŪSC‡ąL�‚$  )ŲkŊ ”H’T6…Č)[÷.Ô+ œlá0yÅÆĸ ;C›ÔVüV+–žŠĩËĖĩ‡-™wôûnĀ“_OT—˛Žâ˛Uĸg<´§­™¯/8ģˇËsąY#ĩ’ēJåi„ļ‘ÃÔEÃOĮÜN u€ŦŊ¤Ļčûą)O%ßų[XŲZXŲZXÍØW*Šŋ›đ:3+ØŽŪĻôũØû—*2˜ę*;¨@5PĀ`É\Ø!Z$H$ �„UXÔé° ZöŦôtœÚöĖk%kž¤AŌŠˇl×ŊņöRmô]6II€íRõ]ŦG6äe–‚¨ôž@ÛÚZ�+{Cɯ÷ Ę/0ÆN4ëš îšgVpPP’ėąÖė§%%õ úĩDd`mÆ67ĶŽČ+ĨáņŊ‰™ÃXä—vOzéšzÅ` ‘ĐoįÅ7„ŪzVa1‡ģÜaڅaČąĶ ­1čõ÷ŗæJ~I­oh÷ļ īXöÛ<ĢkÖޟūT€ĘŨĩ|ũ]íU‡æž įŨũ¤2ūgÁ0Ÿ¨-ĶZ'uԋÛ'Š“ $ĸŽ‹Ą4E5tûŧĒ鹚)ǜŸüšR9Ōz#�@Ī€ ™u�mgibÁSc¤J.(—‚b¤Õꈤåĸ:hsXDuä\`Ûˇ^!—÷–¨ôN>Ĩ?Á^ŋmoô HIiyL‘ŊÅ_A=QQJt8ʕmo=Û Ŗ?{“ßĩyGŪ1Ûą4°ŅƒÃ==L”rægÂmĻ›� ŠzŠĄ-;Ā4›Ŋ|™yw+ĢX@Ũ‹KĨLįŸÚčÖqōOeî 9{ķ.5ņeĐXŧĖėšq7•uˇÁpŠs§[›H-$"™ĒKS hĩžŲŗLgŽŨ7s-ˆĢķSĸîÛ¸š`_åj>ŠÅčœr[ūģ4^%Ēü^ĸ3É�‘DöDôD3­Ģ%Ĩå%+Ļ‹ Zwƚ]Ÿ—/(Î/e˜…ZŋÖ0¤‚C€ĸ’4oĨu5ŋT¤YÁkN‚žŊ)|ķkuvqũč)Vm9čą´eį*Ų5õJ"ĄĄMāYBĖlÍĩ=°iú]ߤ õ+KŨZP°ø§Aúƒ¯§ŋĮˆŦCN_ŋzŠũīTĀhĐvÛsáÜöO´ 6nShc†C¯‹’H@›ĶvŠŽāĨ¤7â° ô žē˛ũŠÜÃŧUŸ%%¯š)¨œt^ji:^zguĐ{‘w+ŋí,Ļūū/Ĩ`æbM*‚"õå)ņw*h‚Åæ°€ĘņYönFŠßzzk߯M‡īw\w­TR mũ.§â ę‰JRĒÃQŽl÷lƒaú đGÉŪKŖįhG .n9*ûë7@WĮnŪzėĮ¸Į4�Ô]\<ÁsÅAįÔęĢë@{$ �¨Ė+w%æŪŗm ŒÚ˙Ŧ}}Ŧ$÷âR_ãĄD,+Č;yėe:mBį0ÂĐԐņ´¸¤ãîÅęüJ‰–!čēŌ;÷[‡)˜úVĶÖ|PĘÖ|ŊąS.ũqéĸ°¸ē.WŠj#ŋ—čL[›u‚2Kŗ Ļ Á¯W2+i3ģļŅ ƒņVZ‚üؔ Сûz㐠ōK�Ėí­ $ķæŨ‡¤™•>�ÚŠōī˙’)`[IŸë ˇ´e)Ų5õĒ^PÚėÁؘR) Dû„"ƒņNĖč˙1"‚Ĩ§/Ķ3ô Æ0ļĄ�¨ĖÃGŗëĮSĨšíŋĄNúfoÃD”e`jČH¸ęēÔŧÃGŸZ1uĨŔ9Ûyšé÷á'÷\ų™ƒvCIô÷÷$ZŊ'øSPÍäWÎÜģĨ`öĨyë;s—:_ŨēcŊŪúEfŒēģ§Â3IŸ“^,�P6ZTņ°šĸ*ë¨ĘŌÜŧ§@ú,ņ×W¤ŽÛ¸Āš¤ōĸöüDŲíh{Ô¯ÜˇĖf/˛ēžuÕŪ†%SôHĒ"öđ҇Úöēvi9 ę ųŽti�Š‹ˇúęē͡ôÂ">7WØátŦϰl;VSĐŗ N¤ËĒĨc§“ €azmËXŪ[öä-_ĩ|zŌ9ŪVú$ˆōy?]åUŗũm´g�Ûk•_ėŠī–Ī}ē`ŽģŠ TŊā^Ė؟2ĩ\wM՗ū ˜†MčręJē~26|WBŠhęLe{sŌÕÛ.|ceõY×jLē,ō>õŲÉ?ę­˙ĀA•\ÜķÔ00ܚ�ÝųÕ<dũ˛ $Ô ~š˜#} HĐVPķ;§ė>ōŗ“;ü| ›Žˆ9xŧ„ąjģ8JĻņ*Ymô|hc YQZ íW�Ļ•‹9ü˙Ųģû¨Ļ˛|ođŋģž›sf!§įY 3 â4ÁYŧ ˆĐ >ÚĸbYb5b‹Ĩe ļ"ąļVņĨéÂ* [Ą´ÕB¯B=zEŅÅËŦîT ŠYC˛f*Š%e=Iū¨ųƒˇČÉ!ĸ¨ų~–HÎÎ>;{īŗĪųŗŗSScôH›§įëīc8xŗū™dÕØúûÂđœxj’ˆ Vé?ģĐhôÍxˆHė(˙áÂWŒxÉ'Ï­Õļ9‡ī1KDũí]zg¯Ā)~t��áfîZ'Ō´vęI'ëķWũ2kN >‰:*éŠĖũéř1WHâķ~ōūŦpŊsgę?ļlŖķ_íXw(¯į@AŅÎõG÷€SŌVėžãĀ“Ĩėéf]õ-Īŧ?ũi’Fe—rye™[´FVáˇęXIšÆå ŋ}pËŅŅģķW˛ļ^!ĸßė­>/—,;pDs āLę–ŖäŦđ_‘ûŎeÃãŲ$_wü4S\pĻ8­\od$īŦÍÍß9aŲ4Ū~2ųīž‘xĒ— ÚÚÛYŲč cÉxëÖ,Ų›ĮõÔ W6žĩøŲSÉĸŋœ¯\RZTöÍį.qFF,{/äÃ#ųۏĖzî?}Zõå™ËķĒĩœ‘g™‡Ę/ų‹ņ"ÍíË­¤JÃÍ^ō~ĀÁÜëĩšuqB×›ŊČųĄa堙‰SK ˜üĸ‹ô$–Ŋ˛­0mŗKD!é%û Ž–ål<ū‹‘q–+üV,Lô&"žžl–3¸ŋ0_\pōPâY=Iŧ‚‹÷Å{t1?x…vŖšPŧŋöæŖuŖ#õ[¤06wÉĖÖØfũz?ûvįŋĻ<Q_nũ`Ŋ&‰HėˇČë—~‹†TÉTÎEWŒQK†×u`­ÖļåŪ>|Į,‘ūÁÄ!›Ŧ| �`ü˯ŋū:úGoo¯BĄ°;/Ž›ę—%��Yבĩ[î/<}}Ī›ũĨ䗨ķŗÕ w:Ÿ†`ÆtŽŨxļ—TW )wųßĻ掯ulŋķI[Üđë>œōÂúĶhâÂ5 …?3ķ ­���@äĩ=õ}cuAÕûŗn/™æÂņ#Sļ#€—éuš5��oąöŋ­ūãUͤ‹y0~™WŠV;äéŲ!éų+ˇ$eœSÚ¤Äeŋƒúlæį܊’ã ņ[í�¯c¯ļ‡åˆĶ÷jĩzĻéÕęߖųa˜5��¯„ĶqœaōˆH,•žUë{��ŧEz ׎?Ûkus@ÆŋĪėOMŧøŦ9<#�€W‚KYûOZ��0CËĸSū÷˙œü;œƒWņūÄų�����XĨÜ|ĻaĨF7YH$–Čgŋųø�����V*}dī…a­9�����p\ˆˆ�����Āq!"�����Į…ˆ�����""�����p\ˆˆĀéīŸÍŒ^čŋûlŖÎrkSqb„PüeÍ åŸ9����L'DDāh mĮ“NuI×Ļ—|Q˜šVÖų÷´¤ŗ]Ãu-…ÛâSjôŧ ëŋÔx3���€é†ß#cxxæĘcEÜųÂÍ^DD>L×īRn|ĶŗŲ˃¨ŋļ´^üIéW˛3‘ģ­>ßyŠ9đf����Ķ868įĘ%ŖX1úˇT"&ƒ#""idŪų8Éljœø>uqĖú Ÿ‚Æŧˆ—šoæ����0íŖagËfö6ˇhÅ*˙Ą—XŠÄÚl7ą—_Hˆ—ôĨįĀ—9����L;DDāČ mř'{“|pņl[I]Ŗ˛OFŊâϐ9����Ø++€Ã⚊“*ŒĢŽˇķ;:/5‡Ī����lÃ3"pLúē?'ĻßaÖŸN ŋ~9ŧxæ���� ""p@\ĶÁÄô;’äS…ņŪŧĢdĪL/ž9����…ˆNufJ5ŗĄĖūxãĨæđâ™���€pˆˆĀÁpEÅ™ôE\GSķȋŦØCå%%ƒŽķ‡Î@ÔĨ'âē:ššĩĊ=ŧŧ¤,õ×>zGąũĐGʗš_æ����0ũƒŅ´vęI'ëķWũ2kNŽ“ęoÜr´cäĩ+Y[¯ŅoöVŸ‰—×õ°ūÎŗUDʗšƒž'ķéĒ����ķ/ŋūúëčŊŊŊ …Âîŧ8?# �����¯ŽXl˙2TCáVß�����Į…ˆ�����""�����p\ˆˆ�����Āq!"�����Į…ˆ�����""�����p\ˆˆ�����Āq!"�����Į…ˆ�����""�����p\ˆˆ�����Āq!"�����Į…ˆ�����""�����p\ˆˆ�����Āq!"�����Įõ¯Ķ˜—éÂåiĖ �����†ÄÍ/˜ÁtFD˙Ķ˙ĄšÆÜ������^6˚�����Į…ˆ�����""�����p\ˆˆ�����Āq!"�����Į…ˆ�����""�����p\ˆˆ�����Āq!"�����Į…ˆ�����""�����p\ˆˆ�����Āq!"�����Į…ˆ�����""�����p\ˆˆ�����Āq!"�����Įõ¯3´ß'M•%{nĒ]7¯ˆ˜;ōĸņÖąÄŊí&ķt’đŒģ ķ_}ųā­ö¤áFyI}ģZgbĨ˛ĀđØėh_ŠíMDô}ÎŽĸ§›Nķ7}WWžŗU­3ą2Īå1 éÁrv$‘áņŊüŠĢˇē NŌ~k˛â‚ße&æ0h}GĪy2ˇrėØÜī˜ū‡Ÿm/íöNĖ>æ?įE+ōõgü>'ãP%E–_0ÉVÍ­ŠōŌÖîž+•ÅĻĮúēN’ ¯°Hi­ufšņVQâ^Ũš¯sŖß}š;ŌÃÍËĨšUU^ōHŨ3@bŠ,0<6=ÚZ”Ôļ÷ Xϊ‰KHņjīũ>gץī"_‰–OļÕfÙŋŨÚ˙_Æ[E‰{[M^wÛ}(oĢ|xĢwÜņ+Q–öÉŊÍ{žl"ŋŖ%ģ—SlP"ەÉT鞯)¸V×ЧĶš'7Uėē ëįÍ–@xWXöU3ĩŅĀVÎíšúáŌúÖĻ>~ŠŌĶocLôrwŗĘiEė:~ÂŦ>éIŨæ=W=R‹ŗįŅ“K9ģrEŸ<Č›mžąæÆÚŒĢŽģNŸđ§†’-ÛMė]DD[Oœ sâo›í;V*°!ZČéW˜`ÃLDDOž/8YRŠsß0øÔDbՆ+ÜF^1RŲĢ-L™ŽîāÚîč쟸ÎtA2~W~h{=EÄ$|áéĸëŽ)¨*ú”˛¯Dģķn""ĸĮmMƒž[”L˙ŊĸíÚEë’Ō=tęĢ9'=>6t‚ųųŪŽÃeUėŅ}žŦîaÁš’í$­N˜ĮZä@<;ęŋWb5sĢĮŽ­ũšq ŪQ.ĢÉ,-9/ÛŋņĩēŠ›~ÆīŽ•]ĶM~áöäÖɜŊŨn[â’ŗĨ¤ëŽÉ¯:ļrĢcŨĮgÂß+ÆōŗŪ:ŽC@7ķŧĄôĐ^ĩ[zbîr™H×]“SZ´} ãō†qũv¸ VĮ%gģ‘ēž<˙Ä!ڗ›2o˛ˆßSn8Ųę¸OÉ\Ļgī/44ok˜ååŖ“Įčą õ4<ú)Ęâšĩŋĩî;Yŧcj *¤2ųJzÚv*îxĢ8tMvŒŌՅ ēž[7+sâ˛ōļē J05ëajÕÅLąSŲ¨Ášũ|ĢäП z…'mšīęDœŽíVmíŪÜÖīRŗ÷Í 9ë**›T›í=€|cöœ ú¯öŌɲ:Yė‰ĪĄcV,›Cdäy¯ æŽÆAaC´ĀĶŽ0Á†ˆˆú]­sZs6GZš|LcąÅd$ąŌ3pūŧW_*°—QŨŨĮ7ūŊnŒí§ëĩ‘š'†Îåķ=™ž]É Šv—gõĢÕũnÁŗ~<­]žũרy,ÍKĸžÔä› )ūQŽdlēyĩI{9)ę]"š7ī„‹{ƒÉitįÃ9ˆÚķ­îˆ'sžcĮÆ~Į™íubŸJ=øœh–ĩ4oƒĮĩ9õĸåĄžˇÔ“m}Ō~MM›’S‚gÍķ¤î]É­ÕąîJķdļzÅ(ë­ã@ôp3Fueë€÷ē„ķį‘Ô?>ĨõŅæÖ‡ę ķ,n“?o=]¯õŽ9ü×9-˜'åúöžŲž%ŲßâļŨežrÃÍY6{~ɜäžķį[{N"öô”wßģū8:eėōōįē†>7ˇžžądSkP!•ÉT’ąŠū‘Æ-ļæ“¨áXNîž@ébĖ-oë֐ģ\@‚ŠXSĢŽYÔ_3•NeŖN„vŅūē/˙ôČē5÷DØčC$ßåĄaš9•×mœ1TcbUowÃáÚ(ģlΖĪ~ĢSƒąRˇĐųŸ´jžáj4ĩįĸ…žūp… 6Ė@D$ J*š3›Ú&lԙˆYŊŒ>Ɵŋ­*š|ÂēČCG.˙§Äc=+’cĩWKZût&'WUTöÖčĀá+ᥚ(ęž+u‹X‘á>›ˆčûœ]ĮzV$G´—¨eŲE{cLߗžĢŦT÷iMbˇE‘˛ŖįĪĻ'—íĘUŅ1ŸŲ–œü™ÕbŒĪpĻ.ÅUŪĄÃF§Ņ3#uq"“‰ãßDDôŧŠŊĪU™āĒioŌš„úŪščįɔļ5=rÕ÷mû€2Ōoô*A:?,flß#902Ģ;Ō¨­gÎsėđī×Ė“IÛņ­¤9î†14y‹´rōëŒ9a_œ´¸ŽeEDĸYão›ÚčcŦˇÎkëįK‡öå‹j÷„ÎØŧU´ëOąÕŲž§węY‘¯Ģ,|Ô×?HRĪĐėÄøĐ9¤{X¸ļT“•âΑáĮ æ>ōŪ5ôS@ˇ bDdqWDD4ūÁĻîGĩÉ%T9š›<"Č­đfK'ųN2°XĶŦšzÃM˜)7¨ûļŧŦ°ĄģĮää1<Āū˜“šķ]øHš'u›“ËžķKnLög‰ˆ~ĖŲuHŊö>Öv�� �IDAT"QY{b:ŌވšúM˙PY孆SÜG.5ŽõÉB#]Ė.ņ§Ú 6+ĶÖQI&ÎDD&ƒųkŒovޝāS$¨ĻX]D†)t*›u"°‹j*kÕ䙐fŲ÷”Ŧ“)ŒŲĀæœŖûđZŲĨ ũë_õÜ^!Í7RsÜ ŅBO¸Âœ.ÆūÕmēA""‘“‡§¯ōm™€?++°sæX9?™¸Aģˆ&ß|ž7œ;”Ü` Ũš]]tøė&ĨŽūØöō‡VdRßŦlR%UŸ<Ũx,Áˇ¯rWéçDÃsQŽéÄíŠ>vøķY[ÅĄŊ÷~&""#2õ×_mPn8›ĩ!b֓kĨĮ ĩ˛¤Ôܚc‡?sëŧV”ųđ9Ҝ˜]šģŨH”t§$;ɝ§ã2œŠŠ""fö\štŦ�š&ĩNė9ßÃÆ&"ŖēĄÛ)PåNēĮ’z˜Mo˜-•IMZĩŽčšļs@äáĸ;_ō—ČÄ- wĨZŪŌ?šn4žņdÎsėđīwŒĩv| õו• øĨĮĖgmŽ(FãĶ'?w¯,˙‘hQdЄ{ęŧŊŒõ‘íĩ5wy¸’Ô÷ž}2ōÂsõ-5-r%bD&õÍō:Ī„Ë'>o=öI ŽvoU›Hœ°OĨ;ŽžŸˆHsū\ũSŋ„}Cŗ:…ôpsŒr}T]õ۟DôTSwžuĐ;<B9.™‰ˆDæ—Aŗ]œhP×˙„& ,<cšU/Ūpš†ō ‘—•{yW¨T]šĢ´å)šEx:õ¨ģŸ‘ĄģMíâ"îkžâÕ¨ŋ”*}§)=Wĩ’.’iŨkyįn¸×# ^îi–hĒ jĢ2•ŗũ<™žĢÉ%5ß>~b°'ÁÔLJ)W×:•‘JPnOēÛ´ä᧚äk6ŒEa Ņģáb]Ô%U-O…rúh>ŗjy…wˆzúÃæôĐŨ+úđxYÉÍĨ7o”V}—}ĒáMš&ÄįĩZknĐ`2qíWweíôÛôŋ]韖?üémŠč—ëIëųG:ߘOR|Ũ]įĖUúĮg‡ËzjÆēŠ[DJđ\–ˆãŸéÉĩßģ˙œčykIŊÖ;&);xžëÜš ²CîßŦŊKĨq Ύö_ā.ŸMN›rŋNMˆ™'w+_ŧ&Öm°ŠŊ›ˆØYNŦˆHä$5‹ĩU ŗ _Æī*KJ´žI1§ßLØÔŨÖDĘEžd4H$6ŋĶä$bÉd$xj25T•7šEŨ—}b§ŽžhsŲ÷Ëxvė9ūũšåe­ß6OîåTiÅmĩ{?št|kHrę†sŨŪ›2N„ņ_\ōt˜7ŌlŋˆQ÷õGÃ1ÃSõŊ&RÅú ßņ3¸E ¯ę1Į?FéÂõõõÍYžiC öjÎŊŸûëĘN¨ömōáôpKL`BÆ_Ũē÷îŲęŗé!åũ~É_LœÉ#uw töÆmÆūnŨČÍf"ķÅ֘ö’pԈŧ Á ärĨīúŦn\ûŊûĪ_?OęūžÍHDÔÖŪ- 4u7iˆˆžvß㤠uŸŽ4ÖĘõÜh4,ū™oy…ų>ē¤zõņĩG: ?‹ gĘ ĘKØQé‘üÅ:ĩ–'gīōßúok*¸÷}ŋq ĻH@=L6yuņšÂHeË FG"WŠų×Û,Zß"13/).”•—üøĒ¯ąl7ßøj´5D =ũá ķE<˙éûīÚÚÚîÜT‹ũÖ¤¯‹MY›JJĒZÚÚž˙ūģŸßė Šĩæ&c4DNO)0&i‹T¤SוT•lĐQur°ĩoÂ0íj“tšįØHáĄtcnöĩéhМˆHęæ6Z‡Ž2™ØÔ­ÖŅrĶ÷j“tųØ\ÆWåÆÔw÷„†žĘŨ<Gî61ŗE×ĢĘ3ģĩũƒ&ŖÉd$’NXjÆV1Ė2|<oĒ<ļĢÖŗkĪ„&Ų¤nWsžŅļžŠúÜH&V•p4zKDîێô­ēVĶ7)3i<e˜ÚgáŲ¯aíøÆ{ūmEe›į†j!Ws–nĘ~w`ā§ÖēĶįrļf|å>q9ŠĄl§Šą^'Œj}Ķæá¯Œ›Ô¤údôŽTæ6zÉË8‰Čô|øt7'ė¯qWVÚLĻĀMšËGĻLLŊ‡?o*/ĘQK“v}!sâúîV”|ę´§"Öō+ŅsübUå{¯•]ōüdŊŒÔ­å9­D.ŖûX´6Æ´—Dî96˛Ŋë&›úÔ:ZîéĢ4Õ5i)Ô]Ķ &߸ˆĀžú[ŨΎĘEmíŨŦ2—ˆĻ4DFƒŅd0‘ˆ1ėPÕôŨüđ“›u ũüäļБŋØš~ĢŨ* ë۟úúĪūąŽn@ļ1HΚM›Ö!KøQ9+0zwuäĩēŊĄĩížēõtiÃé*UVjōzwFXk&Ģ%õ 0™°s„}u"Ä,†Č< ĐŨ;ļ¤tôn€KėžĄĨä†ÍöMQíË9WûŌןĮFķM¨F›C´°ĶŽ0_H_åš’ëځቲÚ/ˇ7˜mŦ-Ú^KŒ“tÁŠäŗW[ysŧNãė„˙؟îķ”"íĘs5ˇ~ŪøZ-cûäH$5{°ĀŠœÄd2Œ<Xģ˜m‰2LDƒƒFŌ]ČũËŧdÜ Ņ""Öiäų˛ņĮÇUŠBŗ7m”9ą4píDVÉԋ1–áĖ{ō헇öļŠb-VāáŲôs“Z§ RÎ&"''–¸AŗU  ä$v"Íɕ˛Ņņú]O7ąŠģMGKåf9Xß˓9žũš%؎o¸§må9í˛Ũ9BĪsRų<МįûēdŽĒĒŦ Ũģ|’ëžķFcÃC=ęī]"kŋĨEėRö"ži<Ō ˆEEˇEĄŲ~ŖĄ† náį†ÃĩēĀ][‡Žå돚ē#Ī]ŊĩbÜ÷ g-ߚŦ>Q’›˜K$ņ ߡBõ§*š=r7|l`ą5ĻŊ$–ŦĶđ�;W(­lë~B.ęļˇÕžs}=] ÔŨ†0—†n“oŒ’ĨéKĶVļäxÃĐŊ8tOã'J""YčŅ­a‹ČÅōŅĮÜĨĄžųu÷ŸĢ¤ ­:ˇčˆšDc—øSoPëĻzT3GéĻô ÛJôôĮšŊ'Ęķ+–fDH…'˜hŌZ"˛U$,ŲÄę˛aĘuÂĪÅE*2iút<ÜæRUByÖ ‘î^æÉÖ o˜}>ûFūŊĐ/T–[DDdšpˇ˙š‘hš–wäižIĒŅÆ-đô‡+Ė2ßá=ėžŦĶZĢ)”ë2Ξ^ŋ91e¯SD4ĢĖMLíũDov%ŋ|NNb2éĖ&V9r N¸ŗmĻAŽDŦˆČɉ!éę]{?)I'ÖvßŊ[Z—˜Ŧm1Ãß)ā&Äe̝įMe‡öļē¤ėÛŗqü=E+›žĢëú¤›æÉÜåԎ֍u˧ZN$UJ‰D2šiˉÄãrāŲOæ<\ŦīלĀv|Ãĩ=jÕæîųCîØkŨ6Õn:~6ÂėēøÉßĒ=ü|Gļâ]7ˇáĮ§ã/öx:Ė›Ī=lĩ[íõGˇx>ŧīôų|!Đø]Ue‹2Đô(˙ZDāĐ#=ܜöą†\BecÕ-•JÅĻîŨ„EgÍOÉ(NzōŗŽ\\į0?ŨČ$ŠĘcbŽÂĮ´iÅ ˜=h5 IÄ:‘{¨ŌŠRŨ­siSKįgÍĸwUžtîûžŸį´ ¸-W}ÂiJãšælVÄđG]F>ŧHî=o˙íŠ_؂Оk­­ŌÖAeŒŸÅ3|;Ô:ĄG%‘áųN4Gjļ‡Ųķĸ’ünlh}ÜC$ĀĒIk‰ˆøëA`˛ņÕe›đ:d–2TFųí­?Åʇ}Ž|ÁP6.ŨbšÉ#ŗÃë6T•ëižRœĶl'iˇ¸A§Ķ‘Ëĸ^sŪFķ™WŖĀ!Zāéo\aÚĮ7îøQ*YŲQģiāĶėˇE—sA7ĶŚ¯Ķ÷ˆ4÷rŠN]3[Zō§înŊÍkA "ˇyJ‘Ž­{ė Ä=ę>Ŗ“ÛhÕiēûFŋhøSŸÖ(’)ĨD˛ų DšA§wåōá."Öiî$ˇ¸MFr‘6†Įë´CƒÎ“ bŧ&úī•ėjmœėŧnm“AŨĸvōž¯?W*hz4:MųÉũGŨäé8‹ˆņ\ĒŠĩŽÖļZŨÍ9š)Ĩ–9đ”'sÖ÷kÁf;ž×e}čđčŋŗ‘nät4'7/Čō"C÷0˙dIaûØÂũÚ>Näâ1á”ĪĶaŪ ō˜pĪūÖē’†viP˜•_‡´`x|5ŗŪŗiOŅĻ §ĩe%D‚{¸9) ôkĮnFë´:=šČĮ7ÁuËŊoŲ9s]į0D¯7hĨĒāņ 0ĐTÆ´iĨëëŊøŠOˉdCŋ]ęĢRRwÛĩÖ>ą§ō]"rķU¨ĩõH•#aĶ“fÖ\åŧy æÍ[0ožrîTžŪĖņ[­¤Ļk• ƒžĢũ,ņŲŽĩNčQųsÍæ¤]ŸÖŽ[búIn\æJ„$āÁSKÖëA`˛qÕ%„Đ:jîō~âžĢ™7û ŽīĮÉV "fAˆeô¨đææ/úy2ÚG×,žbôķĩ›íœTĩÔm|ScĢų,ĒQā-đô‡+Ėé""V$"ŅđCF$˛Ŋ|ŅâÕ?#2ę÷õ šˆúôD\_wĶ÷:9y¸šKĨRękČ9aââĸ]¨_]Sx­O´ĮŽ{%g–ßÖPéæk_–Ę–쉏îš?Õëŧ#“FgâŠuuų7dģCeŦöQ~mŸÄoÍĸYDäˇ1ÔåĶĒ’§ ąž.]û…seˇ\ĒĮ–âá6O)j¸^ÛãIÚG…U:oĨ¨G÷ãwĪU f9‰EÄiÛK=¤6ŠņZxŪVPÕΨBģ›žyQää1Ī]j}SOk7y&ŒŦęž1Ư˛ôËŊ˛„-ž"Më՜v§˜}ĄR"ĸY+"]Wî*sÚîf辑_;\mæ9đ”/sëĮcuŋøÚņĩj¤ÂΑŋk6h°2#r‘ģˇnčūTW˜ųH–žą~Áŧ¨-ĘúÜŌc‡×,—9q}uU}ŋäĄg,_c™%ãkW\Ö jÚÚÚĖ. DRˇųĘ9$ Š­*š sÛŊNĀņqIiíĶ äŨķ™Ų´!ÛoßŪŌĢËŗÖsB{¸YĨšGÄ{ÖæV”:Å.•‰žö=ĘŋĻû%-×D= å{ûZŌ7E: 6Ũ,;?¨úëŠÉŠ:Kč˜6Ŋ ĮjërjdģƒdŦnx€:ôXĨ¯ī@åųvĶ‚MnDDŗ<Ĩ}įkEbŋ5ŖáÜtĨą×ŦˆPUÎÉVNšfŠåŠVđ%Ŧ2yĘ1sCSÂë>­:§‹ŪäéáD܀ļĄūÆųv§ˆÄĐw…$bĨĢÛQöTo§Ģ.#•ā.* N8Ē>´Ģ*gĨ:|cč|‘A§mzTwŊ]'öÛ?é’ŗüSb”+Ī5ôĐØŒJiІ¤úœÂã9܊č7'ĐŪo¸QŠvZŊkÍÎæ“6­æk2¯FC´õĶŽE2\a‚-¯>"¸u.'tuĢú’ÍõDä™~ėĪįÎĪŪ—,ޏZzōPū ‰Ĩn‹böė‹ö};ÖtzÉfÆe‰Ę Kŗ FĢnėۇōĐØåWˇgtkLNĒ '6 ­Ä&dœp*/8wčüĀ 9Éømø<nB8DDsBŗ6ũ¸ˇĒdU=I<ƒR6}1PŪyâææÃT‘ģ>42ÜŖ´v{îŖØÔâlŪbŧtßwžĩlŗÅ$eVŅūõÖ6mčQ*G~“›ˆ¤ÁŸœ,ĪšY˛YgbeĘÕģ2öĖ5bį­?›:+§ĸōĶúrq Ų“íÎŌãoÍsā)ÞĖyŽkûĩÄێ/|iõf0ęúÚēE:"ĸšëweŗUå§ĢŠ. ˜ˇĀđ¤Ŗë†—ĪKÆÛXfšņĩÎëBאyÜüû°´(ņäÁŗh–jš§¨Áą\ĀwåÕĩ_ž×Šūš:42ĪZ˜Qö§Zw?Á=Üĸ R3¨ĸōÂÉŦÜã" J*_7üĩ ŗds–oMî/-/=‘“ONĘĐŋî‹]>ųuŒĐ1mÎ`yÄlXŽ`ŊĮXĸYĘPˇûŨĘEžC‡đÜ@O§ÂzĶ2?ŗ;íĶ•Æ^ŗUaĄNí†P•e-MaȚ֪`V`BöYĪ̧ëkrtœ‰'Ї§2e_ėÆųs„%*°•Ž>åz˜Ė˛ēxëÁŦēøØUĢsBr/ĢjKjī•–Öę‡+G•”ë;×ډØ5bÃÆúŦĶæß›bܡîËõ¸Vyēžl¯nœ\<<ũ˛˛b×Ī›ĘãG+MĀÛ|ãĒQØmõ´k™ŒÁ&Øđ/ŋūúëčŊŊŊ …Âîŧ›§ĄD0Í&ü˜ �€š'?Í(cˇ?1=k^�Ā[æqÁžŦĶZōŨtü}Ųv'n`söI%kÚ˛‡_¯˜Ņ•œØũŪĄđįĩ^Y��^ĸįO~ŌvWV”5Éĸ/#��G…ˆ�ĀAõ7­Ēč“x†M|ÅŋI��oƒV×#¤ÁŨĪFÔhuēˇeŅZ˚�����k4ûöņüQāĻöŦ?}^|ÖÜë´ú6�����ŧ^äËC=­Ž5č¤Z­zã—íÃŦ9�����°JũįÆĐŸu“ũ†ĄØeîė×įw&ė…ˆ�����ø°sæŽ˙ ąˇfÍ����€ãBD�����Ž �����8.DD�����ā¸����€ãÂZsā€žWWžŗU­3ą2Īå1 éÁrÖöĻQÆ[Į÷ļ[Ŧ@) Ώ›0č˙†Į÷ō+ŽŪę08Iø­ÉŠ ~w’U)_° ����0=ÃéŋW˛ŊBģh]Rē§“N}5į䥧ĸÃĮügņo23øÔDbՆ+ÜF^1RŲđžˇëp™F{tŸ'Ģ{XpŽd;IĢæ i^¸ ����0=Ŗųņüĩvqxö_ŖæąD4/‰úR“o6¤øGšōm2g2’Xé8Ū„ĖM7¯6Éb/'EŊKDķæpqo09Ŋ„2����ĀôĀ÷ˆĀÁhÔM:—Pŋ҇6ŗũ<™žļĻįŧ›ˆÔ•˙æŗõŗo‰ˆu&bEã"ęûļ}@ä÷îČßŌųa1žÃsŪÆr°ˇ ����0큃Ņ=֐ÔC:öÂlŠLjŌĒuŧ›ˆÄnĘEĒyR""7HbŅ$™?×vˆ<\tįKū™¸eáŽôOË[úG6Žå`o����`ÚaÖ8àÉ@"ąų'K&ã ß&"r ŪöEđĐ̃“‰kŋēĢĄŊĄo€\dAkŌ×ŋË <5™~Ē*įÂ×]!5öÕ>W´Ų”Q0Ÿ5ËÁî2����Ā´CD0EF“Aäôtc’ļHE:u]IUÉU'K鹑LŦ*áhô<–ˆÜˇč[u­Ļ!nūŌI–›���€™‡ˆ ëäÄŌ�7H4ēxÛ⠁œÄNĚŦn˛Āø;á?ö§û<ĨHģō\Í­Ÿƒ7Šæˆ‰äJŲčĘrīzē‰MŨm:Z*ŸÖ2����Ā4Á÷ˆĀÁČÜå¤3˙ZÎS­N'’*Ĩŧ›xšĘÜÄ4Đ?@ä"“;7`6ÅÍDD"ņË/����Ø8˜šĘPŲ@ĶŖī Ã?š˙¨›<ũgņn2§š—Stęšfė…ŸēģõCA ãšT)R?j]MA­îæœÜÆĮ3/^����˜&˙偪<yōdΜ9vįeú4ļ˰9ī9?.ģV˙ŸŦLNŋü_˙ũ|Nƒiå֏—Īũ/ŧ›¨˙áŲ˝˙?ĀyRvđî?+žhÖ8ũ/ī°˙ã—ī}uāŸ˙ˇSPBúĸ˙Ęã&ũˇo~]ûË;īūĪÔĶZūįkŨ˙õŋmŲå;į_Ís°ˇ ����0Žč“ÛNdÅPøķ/ŋūúëčKŊŊŊ …Âî›í~/Ā+ô\]WžsŗU­3ą2åęu ûüįÚܤŽüˇk=‹Jw,%ĸŸ[ *Ž^Skõƒ$–ē- _ŗ/Úwô9îû95 }äâš!;zžtbv•����ÆqZ`÷{‡ÂDD�����đĻzņˆß#�����Į…ˆ�����""�����p\ˆˆ�����Āq!"�����Į…ˆ�����""�����p\ˆˆ�����Āq!"�����Į…ˆ�����""�����p\ˆˆ�����Āq!"�����Į…ˆ�����""�����p\ˆˆ�����Āq!"�����Įõ¯Ķ˜×˙ûŋēNcn������ü<^8‡éŒˆ¤sœ§17�����€— ŗæ�����Āq!"�����Į…ˆ�����""�����p\ˆˆ�����Āq!"�����Į…ˆ�����""�����p\ˆˆ�����Āq!"�����Į…ˆ�����""�����p\ˆˆ�����Āq!"�����Į…ˆ�����""�����p\ˆˆ�����ĀqÍHDÄĩ]Ή^čē61ˇĻ×0…pXM—/øũšž)ŧŖ%72(ölīË*Đ Đß?›9ÜŖãwŸmÔ ÚDD-š‘KĶę ŧ}X˙ėۨȰ@˙°ˆĩ{ čiōxvğšī˛Đ_“šzaLZŊŪĘöˇ„ĄķFîļ˜Đ…a‘ą[ÖöXŠŽIöfš8´äF.MģT´Ú?hÁø‰Uē‰9ŧxī~î0´‹ŨtNÍ͆ŪÛĮ’WG†-)OŋųFģšŌöģ -šŅA ĸ˙Ö&(ŋ)0Ø;ŽD­.ŧ͛ū =ÔgãC˙ˇ6nŌ­Ö>āKũ°oÖÅŠk>KÛą8lÁ°ĐčøŨ˙Ų6I‘Ļ“ŽųbÖĐũƒ/Ũ–SÕÎ N`¸Æ{Š#älnŽ˙Ė>ē[kŖĘzā8–}õģė¯ÎL:ū8dg^šJŦkū2o"ĮT ŋú’€0ŠÕŠY‹’™.Æt1´KLēBáÛŌ“U}ûÅĸŋ§%ŅéĘÍ^ŧ›ˆˆ¨ķa3įķq�Û_fĨį°*õHĻ7u^.8’šH§ÎīVą–9ĪŽŦ |Å~XšFåW\Ė>yAq2ŪãÕÔų+§šą{[ž&dGū)FS[t0+‰ä×öû°¯C˛ˇÆPgūoë>ūßC9ŗ‹Mun^‹ģ÷øŽ7 ŊWx'ú ?ŊšŲ^ļIiĩöšûĶ[ŧö<ŋLÁčÚ/æHKԟŦÜãÒŊMiû]†ļSų_k‰düÍ ŨåÄØö?Ôũe!oĒ7ôtĀ*7įmŋŗ%ũØÂKY8›/åĖ}Ā×÷€ŌÜÎHJ¯åŧ#?HÜī'uwĢžĘßxį›ŊŅņŪ/e„{ú gãÎģâčO2ˇųÉ%dÔvŨ.û,o[WvácoA l°}67gëĖ>ŒwT×ŪĢˆ:Ęŋx(^{:7·%"UuŤ”ŨL˙Čõ•„‘øFEĪtĻáá™+qį ‡âœ�Ļëw)7žéŲėåÁŗ‰ˆˆú[Z4^‘bë}˜ģ{æĘcīm—r?T‘¯JÆu­?Söđãã‹g›įĀ<<juGÖ3į+ŪÔĢŲŪž VsŅ[y'ÂĐTVÚŦØQyč#"RųJŧî_doáÎ,‘ģJäc¯rwĶöÂ3vøŽģ˜†Ū+ŧ“w9ū ŗōä:ž€ßĐZuįīéņr"’†˙[ōoļŪŠíÜããkgS xWįÅŧË˞čßÜną•Ų 0tļwm'{sOŠuû?¨J(øüÅi*žd3ö_ÛĒ˙rnV­!üāų¨‘‚…,^ũáûš ģî˙ˎâ߯—mš¯|ŖņÚņī)Œ‡—¯ŋĐPĐÖŪKŪ  l°}67OmëĖ>RjžQŪ�¯|Ö\OKŗö%Ŗŗ8`‰ĶÕØ<ųŗlL×r&õãå‹Ãø…FƧmy:ļŠąđąĄ ƒ#cwŸm4{ÖۑļĨĸåúŸ?^ž8,paLüąģ:]Ë™ÔøˆÅa‘ņi—ģ D–O{‡ŪŌQw,quäŌĀ…K—oûÛũ—üč|šąÁ9W.Nđũ[*“ÁĀņo""âšü đsåéڎNã;Ē�ÅHŠđČ÷¸æģãrāŲOæöŊkœŅ~˛dũքD‹~ōöøĄūŪ;措×ÃŌ€čÕ!Š gíI6s šēâäáYŋĪŧ0Ü9ŗÅWhFS5ũyé‚ß_ė'"jɍ ÛRŅxá1 “¯īK#ŲrMÅõ˛O’'Ūf~ņŪ+¸“ę˙qYķŪē8–HWŗ7ba|aįđ-wCûßVû/OĢįˆ†!Æĸ„D4ôŠ}Miķ]Ŋūð2…÷öÄ:šĢ343ęãŦË]ö!\ĶŲŊ̇-7JO~ĻĐWmûmŌg܍Ũ üc ;ĮįeYB!§ĢũĘz‹L0üÃ#ãwW´4ĮŽũLmī~‰ˆõ^īĨũēâ.īČ ŗ›žiëŽ%Ž^ļ`áōÕŠįšÆĻe9ČÕUõU+ųīȌ’[ŧ,^˜–úž¸÷æ…ĸ–ŦÅaņgGŽ%,+ĘjæiG#g "ƒEˆÎ.ĖúǞđC…°üœÍÍŲ8ŗâUˆČz_zÃúÛo×ÔŪŽŠŊ]ߨ~ŗŽ�yŊōˆHûƒ†dfGÖlšģÔø¸F�� �IDATScũ-`›ūúŨEŊŠí?˙ī՗JRŊ:OĨe×pÛö§íõÚ{ęę­˛ŧUúœŧķlôm,cėŦ(íŒ:zíîŊ[Ūëũ*gã΋Ėļ“uw˙{åī™úãˇ'ôu–1v–ũ­^•^YûmSuv€æRzqãõu vļ\!;ŋô6ˇhÅ*…MD†ÖûíÎ Ŋøú°‘ˆXķ+'ąTLĪ´e<;â;@ė{—9ž~ōáz;õŒ‡D{!ããå‹ÃB#cŨíM’ÍCĶņ¤ôjZvāteEa˛WWŅÎä 6žMȲŒQsåËûū)§ËR&Ė"íĖf4W‹ĒiÕΓ=™|áŪ+´“šk9¯Čp9‘4j_ZˆļüāÕ~"ĸŪōƒWš%ûŌÂÅÄú­‹’u^)­ĶˆčiĪ?Ëī<ķ^û’ėmJ[īęŋœ˙š~qÚ§ū c5 unh;–˜tJĢJ-ēV}ŠäSEÛņÄôęĄĖ7°w§āŒūũÜ˛ķ—Ž˙AŪ9:J[$ĢŽŸOö"qdîˇwN' ™t4TÜŠŸŦļČxúĒũig{Ũ“OŋöEJ@KA^ŪČ8ŗöˆä!QīqžižĘKS]PNkrËÎ_:-m.I9p÷)‘PšÖæ^ō\(X˙÷˜gí-?Ã;Ë>kÉģv÷^Cõ>ßΒ”ĩCeŊķ´ãPdRē;ãb]§~˛æ˛™€—ŗš9ū3ûXXUˆČj_zÃčĒĶÖ˙1˙dŲ?ΔũãĖßs6ÆåÜŖ.yŧęˆČĀ ĊÍĮg–1ģvqßūZqúj•ÂUŽđÚēÎëYķƒ""ŨŨĒ ų4}ĩJ.•{EėI [ÜU1x¯Ų"a‰¤!‹UôŒü7Å{‹‰X%‹åÆŪļŪIvfđú`o”‚%"éâUūīp]]ol<kh+Î<Ųû›íÛO˜Y>aS{c3ų/Rņöa™—œŅwvŽŪŽ5hÚĩDÎh‘ĪŽ öŊËz?y›čôœŅxŋ¸ ŲkSūŠĶ;}ôWŌļl?hĪH˛™Â}sĻZīģ3ûã/o˙Õûŗ“—ČuZÛÃŽÆ92sķb_oÅød’Îlh*ģØéõŅĮ6ŒŲĶ{wōĮM]Ī$Ū>#‘’e)Ŋ_æUkú/įŸŅ§e,–ąûKrŊ:ŌWūvP؇š%GJ6+ˆėmJūwénä÷†¤Ļ,0MuŦΚģ'¯<öŪ–—åã*—ûŽLĪ\)~PvUMļvÎųƒĖ=‘ž eČG‡8ŒŌVG�VėĖ0DŒX*OéÁæÔOÖZd\-ÜŊŪB!Ÿî[§R¸zøĮÜák´ųė; šĒŧ$Īēšz~8""NöAÎpMîČLx{pãįH”^Ŗ'F"Ÿė‹UŦL.!v$ ņū 9JÎąŌÅÛã~Ã=¸ų€#âéÃCÅ°ŌŽŽ)ųc0Ũ)H‰ûŨ˙š0&ö9…Õ-ũfĄÍ|„œÍÍņŸŲÍjÄę¨BDÖúŌ›ëinš˙ ņūƒEe­â%[ĶvîHŪš#9uĶ"úædņŨûī7ˇ´i^—3ž}f`ex ØŲŒūëâ‚ŦŽĮÎ`4 ΈäF"ĸŪ I–yņî^Îæ3䊑Ą™ŗ ÉŊGžķ˲b2'›W.UxŽæŦ˜%ãŗ7ķ ⚊“S*ŒĢŽMX``’MęÆVNõį&éâu!é§WО×)H}§ īŽžčÖj<e°¯äüŦ÷“ˇĘ3ؐ}ų›}X"ōÎÎ×uŜēx?Õ?‚ņd3¤ŗŖĶ(Yæ5rYÃúÄĘō>ųXhaa’ÎĖ=,¯áöOz?ۜŨŊW Ŋ^GrÅ;c/HŖs÷|s,i+ö_X&)Æą´ŧYâąėp…3×uŗčxf’¸čüNÖÎĻäyWwüŗvUĘĩIž„ ŖÁHD ˰CwĄĮęŧˇĩĶ([66W‡õ]øsĨŖ]GJ[ģT5ÚpŦX"ĻŽĄQZā0IŠŦąįt0y‹XîTĶŅK˛ĮzŦ_¸ŠųÚ,čąķ4$QČéÍTڔÕ$yx+ÄÆ.ĩ†–qŽs@Ņä•kŅs¤^^Ŗ‘­ĢB!6v¨5´ĖhŊK‰øÚQ¸ųčõ8ŊēĨņÁ‡÷[îž=pķlqpfņ‘uÃk9ØL`• ŗšÅgã;ŗ›áUˆŦõ%ÁdgTWÕĄĖ¯{Žāzs“n˜mü*-é+bœež GNoöšôũo„Wąb1Kz‹ot?ãŒ$ ¸sV:Ž|šTÅŦČĖH PˆYŌēņäĐ&ÎČ#›§Á2b+“6†'ŧōMéÂŧWx/J_÷įÄô;ĖēâĶib›4ÍÍZī(ŋŲDÄׇÅËQ§fæÅũˇ<"‰Īš´„āŦbK,s°ž#ˆ}ī""Ū~ō6a%b"ąŋûh?õPy‰mŠđ˜éd3ÅøĖh1ee¸˜Ø™ééƒ÷É/7„(ˇŋ÷ >w9#ą–ƒ”4ōƒci5ėŠĖ%#—°š›GŋŌ;ųņP”âą#ߨąüā—ˇŠVÛהÖß -Č{ Hū*r’'!ō—îŧ9t‘!Ž.lø‹˜Ėëœ{f$myBPšÅ{Ü9ÎöĀ>ų(-p_*ž~cßé`’ˇĶ%F1fŗÂXąÄ™Ė"";OC ÒŅh0 ŸđKĖ–Į`œ™Ą…Žs@Ér2j´"Ëī‘A¯Ņ“T&aÉ@ã+ŠŽ(ž>,%˛ŲŽŦD­ ‰ū˜čiûÅôԂ#Įo†Ÿú@*<Á$žÍÍņœŲÍ3ļ>LjiBôĨ7ƒڕ"fíFžõÃŊw–œūpB'yŖŧōgD /95vš\O{ĩ:FĻ|ŗĢqĻuŨ¸ŨûÎǞėÕÃĪÜõOGįˆ–ŒēąŖÎĀqĪ&ŧßŅpMĶīH’OM\9ÔĘ&ŽĩžK°_NdĢ‹ũwŸē•¨ĶčHâ*e{ÎÆ“|Ą‚ĩˁgG6ûŪ5‚§ŸŧM$îrgŌčÍÎrF"bÆOS™‘d3EėːQ' šĄĮwf"24ßi%īão/Xží…z¯Đs#fHg4ŋ§mh+ūŦ^ę`øæČŠ*‡î×öviHĸ+ŽT&;z4D ģšŌzh¯šĢö,oeŲC„˙Üč5 ãÚ騭Ĩe U9#q'zl‘§Ø™!ŲĒc…+Ė^d‰œHo×Ā.pP+Õ4ü´‘eŋšŦEÆí´÷–Œēą9ENoĪh5ž?b$üŊ,qzŗ,ŒĪŒÄ0ÎDä0”ÔOĨ ōš˙čŋĄå›6ã;‹–ŧGÔAD d|ÆÃ0ŧ}˜—ĶsŒDjöąfĢ>J\rn㝎"Š€V ?››ŗvf7Į3ĒxYíKoUęÕ|ƌšķÁ­ }R\ĮĮĩ›Ú~ŋĨ\;ĶŚ¯|eš_ˆâ—æÚŅ9Ųú5¤ZĖ{؃-œŅHבCËĐųM}īČmš—‚ô=]Ŗ'–šÛߘ›/IufJ5ŗa’pČę&CķŨNąĪĸĄ§Û|}X¯ŽŋQ×i`ĨrW)KÔuũÆcéÂ÷•ãrā)ībßģÆđô“ˇ ëĀtÖüĮč÷ÚÕ-œŗ—ˇü5H6S~یļ­}ô{Į™m§Uk†î™›§5=Ŋļ¯ōÆuf""úĄšũ™ÔˇįBäE{¯Đs‡D"%Mī/cĨí,Íēb\•QX°˙}ŽâđÉĄUÎ$ é5f?ƨëÕęIâ*ąˇ)­ŋ+`įék—/ū+ũũ{$y?ŋâ|N”„ÄrĨĘĮWåãĢōQĘ'œ~žŒ^ķLėáĄū'eą|6Ų;°Û†ūĪ_*AøúÕä-2n§ /9i;{G> Ąĩ^ЙËVÖ÷jhō/ÅXŖëęũū|Og/Į¸ûĘę€RÄ'ŗ_æ]îĩx™k9rüÎėkNšöŽŅÎßĶŲkdܕrŪ>ĖCsq˒ß%UXî‘ô=šg$‘I…$°NđŲÜ"gkgv <ŖĘП“öĨ7C,ÎN(b歘7D4yÅoû-W›^ŅØÖŪrģ83¯QŧjÛ ū 6xųx3?\˙ęnNßĶ|1ũ Ö۟1j:Ú8Éģڇ|‘_ÕÜÛßĶqûāgo÷o¤ØÆ5?dB>XÄu45ˇ ˙kīŌņnjģĶAĒÅ#?)Āׇ{o¤¤f^xĐĄnoŧšVÎ''xҏxĘĀ“š}ī2ĮĶOŪ*âđ„ä]ŸĨŧŅÔŲu˙rfz…ÖûφNÜ=—÷ÆoûŦíÕ&›yâÅGĘÚŋČ-ŦīPwļ\?xød;ųúȉž^Līƒ›mqꊂ¯õļgYDDÄizô$÷Ĩ9V?öö^ŗxîpôrÖwv _œēN¸ČEĻ'°ŗCR2—čË”Ē DŪkâ}Œ5Į3̚{u:M΃sY§ZÅKքKÉŪĻ´ú.V:r-čĄđđPČ Ãŧ#÷V¸ÚŒ5ċ7Ŧ”´gÖwõëô=í7rˇ­_ŸqCGdįĀÎ7ˆÅ,qŊ÷;{û§áÛŪÖû•ĩGŧʋœ*¸ŨŠŅõ´\Ø˙e› Ûé6ús{—ŪŲ+P1…OÂöū3¯ĸĨG§īoŋxäĢ$KĸX‡: Hē2;'RÜ|hKlÆgU5÷ÜŊ^qxËÚ¤Ë\pæÁMŖ3IÅÚ=ÛŌŖĶ÷7WTˆ˜ˇ퐝H^ëŪķ÷Äøƒo7w¨;;šÜ(LMĖktOˆö’`¸ˇ=hŧ?ö¯E­›ÂŲÜŦŽŦžŲ-’ņ*DÖúŧ6f`eiTv)WW–šEkd~ĢŽ•¤ĄSŧ éŠĖũéř1WHâķ~ōūŦpŊsgę?ļlŖķ_íXw(¯į@AŅÎõG÷€SŌVėžķVN–FĶÚŠ'ũü­wĖ_õËŦ9šNomSJOË3īOĮ~vÚz–,;pDs āLę–ŖäŦđ_‘ûŎeR"ęĒ7΁§ Rë™Û÷.sŧũdüíŽ7ĢÚqēØ9īøgIW~!É{‹ļfnöĒƒö‡övV÷j“ŊØĀũ…ųₓ‡ĪęIâœXŧ/ۃˆ$ËRĶ›÷ļ%ō"+–ŠVĻ$¯|ŧû÷nŧegÂũÂé„kÖąúąˇ÷š×°°sšPŧŋöæŖurRWä”k‚s‹Î&"G¤îX›Ÿ]ņūųÍ^ëū~’Ž}va˙Æ<Ŋ‘‘¸Dæžß9üUûšō%t�6pIĄ¸ čPbšū9ģû.I)Ų-%"’Û3°ķŽ�‹~ŋFqāRRÂ7ßĘ xĄrķô+žąŦ+EüņŦžŒ’Ŧ¸5$ųMøļK–ėMiˇŋDD¤pįqČ&•āV1ŧ)ËzK“ÖvhŒÎŪ!)…û‡VuœŠˆ$‡ÎŸ_ō3wOŧ¨FbŲ{‘Yį?ö5‹ęå+w,ĶW”ÂĸĸŦõaâĀũ§OĢž<sųb^ĩ–3ã,ķPų%ą#>@",iofīŧižoČÁĪ=„žÍÍëĘڙ}\29΍bŊ/Áëâ_~ũõ×Ņ?z{{ …ŨyqÜŗŽ ��ŧĨēŽŦŨráéë{ŪāU€ ÜSĪž:ån˙ņwY”õíß#íŋˆėüluÂŨE§Î§ ‰@–ÜȤöß_ĒÜŦ˜é’ĀËĶU¸vãŲ^Re\-¤ÜåwV|›Ēß׹ũÎ'mqï_˜Ņ•^d‰ļĄđįÕΚ��xyŧļ§žoŦ.¨zc( ˆ4UÛ~ˇ4.įz{oŋĻĢér~Q#ŗhå¸§ŽšpüŸÆČ”í‡�`2ø="��xĢĖIĪ_š%)ãœęÔ&åt]�ˇ˙mõ¯j&˙Äøe^)Z¯ÃN'ųēãG¸CŸũqŊū#–ų„gœÜe÷=`ƒúlæį܊’ã/S�{ĩ=,GœžWĢ5Đ3M¯V˙ļĖÃŦ9���[ œŽã&˙F#–JÅxô��o¯ŪÂĩëy~( ãßO8•e§Û‹ĪšÃ3"���[Xą”ÅĪD�€cR,‹ūMųß˙sōeBœƒW…Ėd84-����€UĘÍgVjt“…Db‰|ö›˙”�����đaĨrי.Ã˃ĩæ�����Āq!"�����Į…ˆ�����""�����p\ˆˆ�����Āq!"Äĩ]Ή^čē61ˇĻ×0ôrĪgĢũƒŒ˙—XĨ›Ą÷öÁäØČĨūaĄŅņģ‹û…l˛ ŋ6s¸ ŅņģĪ6šíÄJņ�Ļ_KndPėđ¯îYûŋĩ7.MĢę›úĻâÄ˙ øËëéy:ŧA‡eæû8M—/øũšëEˇĖgčS =H 7rˇÅ„. ŒŒŨr°ļg’t†Û 7ėDlT��˜&X}NufŌņĮ!;ķŌTb]ķ—yû9ĻōH¸˜äkō˂9ŗKMun^‹ģ÷ø_eÔßŪŋ%ŊŨksę‘L9éÚ/-ŪDį¯īôâŨdÎĐv,1é …oKOVIôí‹ūž–D§+7{ņāåRŦNÍZ¤ö+{›9ŸXŌĩîĪŧŦá˙ĩpžoFāácG™§ōŅxŒ|ęūę4AŠæÆîmųšų§|MmŅÁŦ$’_ÛīcųģĪ8#‰Ļ$ŧ7ō ËĘŨ_´¨��0ˆˆĀŅt”ņPŧötnœKDĒ<ęŠI)ģ™ū‘++WĒäc šģiû á;|Įũņz3…ī?˛;JLD¤ōĄößĨÜųFŊĶKÉŗÉ<ÃÃ3W+âÎ]ø0]ŋKšņMĪf/žâŊĖJ ’øFE LÚßŌĸņŠ SuiŊø“Ō¯dg"w[}BÄ×áÍ<|ė)ķ>‘O-đ 54••6+vTúȃˆT>…¯ûFį š ‰ũ}|^ŧ„��`˚ĶĶŌŦ}'dÉčmZqĀĻĢą™—ÎĐT\P/û$y䞯ē8fÁÂĖ:"’FŸŧûmaÔØũ`–!bœYūMæ9°Á9W.NŊķÍJ%b2¸)`*d†ŽŸfüDą S˞iëŽ%Ž^ļ`áōÕŠįšÆ:!×üāy€Ÿ+‘42īüņ”ü0y:ŧ9ŪÃĮ eļLĻk,üclč ĀČØŨgõcoîȍ ÛRŅQw,quäŌĀ…K—oûÛũąé}#ŸZčAúCũŊwÔoGŖ>i@ôęńķLg$†)�Ā̃ˆŒö É<Ė͖ģK;ĮŨßÖ\-ĒĻU;WŒŪô{ų…„øHÍĶ Oušļęü#ĩLHÜûļ6™åĀΖ+¤c~ŊÍ-ZąĘ_!ŧx�SâŸ~Šúęŋũģ\˛Ų‹aŧ†ČmŧIS]PNkrËÎ_:-m.I9p÷éĐCëũvစ^DÄJ%ŗmīŪz‡ŸĪ‘e‹Õ2Ņ_ߟvļ×kīŠĢˇĘōVé˙qōÎŗą‚2ÆÎ˛ŋÕĢŌ+kŋmĒÎĐ\J/nžH;úФ\o§žņh/d|ŧ|qXhdlâąģ“}-ĘČq$–0Sų”��0Í0k‹3ˆĩøŌƒ3ˌŋ_mh*ģØéõQ~ĀØũ\רė“QæiôU;—×BĸGí?™ģRnsĶ„†÷ÕVœy˛÷7ÉĪZ<€)bÅŽōĄ^ePŸM+×ø¤}ĩCÉûđ…ˆ8Ų9{"]‰ČcGfBcĖŠ¸ÅËÄDíÍ䟩˛¯(~2<G–mVË<JwˇĒ…B¤¯V‰‰(bOJ[ãÆŗæåķú`o”‚%"éâUūī|ŨÕĨĄ…4öŠ w„¤:=g4Ū/.āÖ~’Ÿ 3t]<z0mĢąäÚ~ˊįŒ#÷ twuãũŽ_Hâš5mg¤‡­Ö�€i„gD�pËk¸€¸ŧ_Ũ‘„gœ.ũâHæZYûÁ-I]A›ÆīŠŠ81ŠÂ¸ęøŅø)Ũ °ËĶæ‚”Súđyër•Īč!āá­Ģ5DDęÆVN`Ī%ģ/üđ™B™ĮôvhHâá5иzYĖX“*ŧFC5VĖ’ņŲPĻūПČȆėËßŧØ×Û+pevūļ÷4Õīû<Ŗuæ8 ؖwēât~‚ūJÖÆũĩ“¯Å��/žcaÅb–ôG4zEôŒ3’ØüŽīĶ7î“_nˆåŨ¤>R X ‰)ūŦ~eŅč­hžMfôuNLŋÃŦ+> ^<�;éjŗ÷ßdÖžĖ ´ęšXb*0Î F"Ō47kŊŖüL–g’?)a‡Ī”Ęl†3rÄHĮ&ŠąŒØbÆ3yĖcöФŦDL$öwÍĪCå%6v´i(Â<dŠũvėOooĻ7æĐÅÛšČøŠ=��ûá8…—œ´æ3ūŸöjuŒL9vņahžĶJŪ ­^ŗé:ęjÍWÄÃÛKlÔĒ5ŧ›Æãš&Ļߑ$Ÿ˛ŧ:´]<�ģz/¤æ7ËvîņøœƒĶ›ÆgFbg"ŽĩžKā?ÕiĨۛÂá3Å2›3,ucŠ ÷Œl2˙ÔR‰ģܙ8ŊŲ\:#16#IW…—˜~ŅčmĨ�€éƒˆŒÜ/DņKsmËČu—ūAMŠ›]¤ũĐÜūLęešˆ‚9míŅũ™EÆ.tú{ģ8Fâ!áŨdŠŋ:3ĨšŲpĒ0ŪÛōęÔvņ�ė`h+N+Ōúe˙HøôL]WĮčÜ­žÎ^Žq÷•“Ąųn§Øg‘÷ÔvoĩÛ|øLĩĖä^ Ō÷tî凿öqO‘&aņФŦOD�ĶYķŖĢ)¨[:8g/īqå鹑›šsŨlŨŋžö=#ķ–Ų,��LDDāhŧâˇũ–ĢÎM¯hlkoš]œ™×(^ĩmÅXüÃizô$÷Ö_sxwÆE5Š>úØßX ųHuc[{ĮũË9)Å?H–|.åŨdž×XTü ų`×ŅÔÜ2ü¯ŊK'¤x�S§ĢĪŨũˇhį'*ŖĻ_3üī˙oī^ƒĢ,Ž?LŽÎôœ…|0ÉÎÂaD’ĩMh\V`].ëlÔ:ˆâ­Ô—.(rQ$xYŽb(Z•x‹n5ĩčVc]°ĘÍ.Đv*“  ~8Ąę ~8™AöC¸„�iˆ Ξŋߜ/'yßįyĪĖ93ųĪûœ'Íí :ą}kŧū§ŊÍš>úÅŌĒOzũãuWÆÂŸ7ė ßņŊ#‡dš?n{ī>BËîGŪəBØûæŦ;'¯üsčü ßî°N?>]tĘk>AÎđ^~ŋę‰_ũq_ĶŪī.ZyĒ˙Ô҉¯ēŗéņ—âהܚŗ{åôEo˙áãŨžYöāëŸ^>îÖļë9~XÎĨáãę3Ë~ūûģ>Ūųūëe÷W|’3fÂŊj�ž&ß#"rz˙ķ#/´,_PYv÷§­ą>ßŋĄü™Ųí˙hjųKKkVīŽKmBËî­l8xCBÎÍË^ĖZąüĨŗ_;КÕë˛+oš˙ÄÔļŗ:ųUģö˙īĮ OÜŗĄũ ß/ûīgoîũ×.ÎÜŪ ˙s üāą}Đî‡WÎyįÅq§=Ĩ5“Õ§túĩû^˜rĶÎũ­ßš|čô§‘ģ?øĶÁËK­ģ;đîĸģŸÜyôœÕķîYBøģYoŊtgNČ|úÉGŚCžáÖéĮ§+NsÍäÜŧxÁŪĮ–?=uüŌŦŋŊrÜôŲ×/ŋCį į:ŧęÎ>¤í^Nˆ}÷'/ŽøÎ‚e+§ŦūKčuŲ?L~ĒlR˙X‡Ãb?˜Wą4žėų—æüë“CüŌˆN~jö¤ŋ?ķ¯iĐ}>|øØ“}ûöõéͧÛcĩ´Ø!��øæ|¨ÚōĮĒ9�� ē��]Š��ˆ.E��D—"��ĸK��ŅĨˆ��€čRD��@t)"�� ē��]Š��ˆ.E��D—"��ĸK��ŅĨˆ��€čRD��@t)"�� ēzžÅąš??xG��č\<˙š#œÍ"ęû7—žÅŅ���Î5Ģæ��€čRD��@t)"�� ē��]Š��ˆ.E��D—"��ĸK��ŅĨˆ��€čRD��@t)"�� ē��]Š��ˆ.E��D—"��ĸK��ŅĨˆ��€čúæ‹(ķ֔ĸd˙‚öC‹o{đšô9šq{ŲЂâU gc¨tMÕÃc¯2 QáĩËĒ2gcĐBŠĢfųšąĨĢ6§ž‰I� ęzžŸiso|zɍŲGždRõŋ}nÅãˇ5dĒ5Ąß9ž9U5ą¸öžíO ëÆškf–,l1ŗ|^Q"ĩmeŲô’tÖÛ?øÚSdj–LĒ Ŗ§>ZYÔ;UûęŌeSKBUui~×'��ēá<QŧoÁÁĮãgȰŲéaĶ_xšv‚ĸs:qĻŽvWwīąėxyŖÄmUO–ÆBEåĄ~ôŊŋž=zBŪל"ŗåšĒŊɒÕĢJķCaHaŦ~øŊkģ§4ŋ_W'��ēãÛō=ĸĀÂŧN5K‰ÔÆU˙V<´mŲ­ŗĒę-ŠKm{~ÚÍŖ ¯(H^1dØÍ3žŨÖļžlķŦŒ­l:6āÖ‡$oxĩņ„IR¯Ũ9pŌږ–ĩĨÉūÅKëO7T¨+•ŧbÆē—¸gûÖĻŪ#FƎ^ōUŖ ŗęˇlMw6EČ4­[8šxhŅ€ūE…CĮ––on '‰]ũäģoWMÎ?ö<;;2™tW'��ēéÛRD™ĻŊАČîŨö—Ļfaɤûέxīũęʩɚ…%ĶÖ4…Bú˛)+÷äĪŠzwũĻ˙ǘŋ÷éģįžĩ~]��ēIDAT™ętčã˛Į=ŗzր˙—ōmŦē/÷´C%ō^˜ŨáėĻ]!'™{ü‰ÜžŲ­ õMí:qŠüôÆG&Ūģ&3bIÕ{VW=>(UUZ˛pĮIwb‰ŧdöņup [ˇíî×ÕI�€n:OĢæNIÕž3Ņ;ÍÉ 7ĩ-™K˙îéĒŊųSß^Pœ !äå=ē vûíoԍ}  iמ–Ū#Ǝ,Č !äæũûĒ~c›ŗģüšX"‹…‹g'Ąū´Cå/Ē,>é*[2™K´Ÿ+‹…ÖˉˇkN˜"ĩöåęũE3_™=<7„FĪY°cËuU¯nœšlL,œFĻĻ|æĶ ߛŊ|dĸ˓��ŨsžŠh×ōŅũ—ˇ{ž•;xüsKØÖ ÛëZsŽ’<úÛØĀa˛~ąŖ& ’WČ}áĩé3Į2čĒüė‚ĸŽ÷rēę,u: ;ęZsŽ/:~‹§ßāYģjØSī ‘ŪZ>š´23î™Uwë-&��€ķVDÉ[ž[>>/„Z÷TθosáŧgæŒ9v'$Ũ’ û_žĨāåÎé›N‡=xŪ/Ģ’Ν~åņ×ĩdå7ķŅyÅÉĶŪqéDėˆŠÅąĐœN‡pė:[Ō™Otr‡*Ũ’ąŪņvƒÄ≐ɴœōčÔēKĻŊģãÅĒyCŨŸ��č˛ķTDYšũōķÛî‚ĖŗūÚŠķŪxÕÎüŸˆĮBΘ•÷&۝‹eˇm¯–]xĮÜ˙¸cnHīŲžî•%ķ§OŽåž7ī¤ę2]Ųî­kC‘ļÔ7…pt—ˇtÃūTVn~îiŽ!$≐in×?™tK:$Ú7ŌQ魏”L{¯÷ėŸWܕß.Ęē1)��Đe߂#gĪŧ:ŊöņĨێFLrđĀŦæÆ–xŋ~É#ėX,‘›!͏cŨĻ#˙ĸ4Ņođ¸šĶGd5×5¤CˆÅb!Ũ|,ƒšö4œúFL!dBčl¨ĶČ4"ŲŧĩzûŅ9RĢw„ĸ‘WōvMÛAų…Yûkjoƒ°gûŽÖø€‚“vÎn\3ŗtMėŽ9tĻ“��gč[PD!ä}čžÂæ×ũi͑@9ilīšō™K××7ĻR{jזŨY\<}m*„ĐđFŲ”ÉĶ*7×5Ļë7Vūį֐;0?BßĸüŦ†MŋŽI‡Ōu•‹W7gjĒx"ZļlŦohÜyēĄBcõcĨ÷ŋZ×ņÜüIS˙)Ŋæái•›kjˇŋU>ŗls|ÜÔOúîQģ)ÂČ{ĮæÔŽxøŲM ŠĻēõ‹gU5åßöŖĢ:ŦĖKo^Zž%6|üđôŽ­ÛļyÔÖ§Î`R�� ;.8|øđą'ûöíëͧĪ9ž1ķ֔!÷5ūdũo~Ü~ī€Líââ[ŪˆÍ¨Ēnû/ĨĄicųâĨkū°§š%Äû5aöÜņ!„LŨšÅķ+Ū¯ihn͊į&]?uÎ}Ŗsc!döŦ-›žü­†t,‘;pėC“Â’ŌM7V˙æĮũÂö˛Ą%5ߎ.M†RëģíĄ_6dzßūâúۛN=T]ų¨ë* ŸÛšlLĮ‹O×U-)Ģø]]S&–<nÆŖķFŸbųZģ)6-(jZWūøŌęí Í!ž;`Ä­Ė+Üągę:ę‡/4tfĐü_š#ģĢ“��g¤-žų"��8˙Úōį[ąj��āŧPD��@t)"�� ē��]Š��ˆ.E��D—"��ĸK��ŅĨˆ��€čRD��@t)"�� ē��]Š��ˆ.E��D—"��ĸK��ŅĨˆ��€čRD��@t)"�� ē��]Š��ˆ.E��D—"��ĸK��ŅĨˆ��€čRD��@t)"�� ē��]Š��ˆ.E��D—"��ĸK��ŅĨˆ��€čRD��@t)"�� ē��]Š��ˆ.E��D—"��ĸK��ŅĨˆ��€čRD��@t)"�� ē��]Š��ˆ.E��D—"��ĸK��ŅĨˆ��€čRD��@tPD={ö<tčĐųē��€oÆĄC‡zöė:Ņ…^øå—_ž§K��ø†<xđĸ‹. Šč’K.ųâ‹/>˙üķ¯žúę<]��Ā9tčĐĄĪ>ûŦĨĨåâ‹/!\pøđáŋ>pā@&“ą|��ø˙§GąXŦW¯^=zô'��@tØk��ˆ.E��D×˙NG/SM5Í����IENDŽB`‚��������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/logged-in.png�������������������������������������������������������0000664�0000000�0000000�00000151764�14156463140�0021243�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��]��!���œ×ÅŌ���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨy×}æ÷īy÷ģußF7Ѝ…hîĐB´­iÆ1ÛŌ3’CLyÆv ōĶUŽHžĒŒ˜¤&˛“Šč$6éJj¤U™š”‡ÔdfÛ“ek ؑ zc{l I\�4Ŋow{×sōH )›cąÕđ|Ў.ī{ī=īīNU7žúķįœCDDDDDDDDŪRŪf """""""r-Rč""""""""˛‚×¨ĒŠĨĨ%˛,ŖĒĒͨIDDDDDDDämĪ÷}â8fll Ī{}_‹š|O—ĒǏxņ"I’P¯×ßđ """"""""ÖZƒƒÁ€;vŧ.Gš"tY\\Äķ<ęõúwŧP‘ĢQ¯×`ttôŠãWD0išRĢÕžsU‰ˆˆˆˆˆˆˆ\åjĩƒÁāuĮ¯]ʲÄķ+JDDDDDDDäjįyeYžūø&Ô""""""""rÍSč""""""""˛爈ˆˆˆˆˆˆl�….""""""""@Ą‹ˆˆˆˆˆˆˆČPč""""""""˛爈ˆˆˆˆˆˆl�….""""""""@Ą‹ˆˆˆˆˆˆˆČPč""""""""˛爈ˆˆˆˆˆˆl�….""""""""@Ą‹ˆˆˆˆˆˆˆČPč""""""""˛爈ˆˆˆˆˆˆl�….""""""""@Ą‹ˆˆˆˆˆˆˆČ6ģ�ŲX§Īįs˙ūwčõXk7ģœŋĪķhÔk|ø'~Œ›wīÚėrŪu爈ˆˆˆˆˆ\ÃNŸ;Īg>ûnīĒ \�Ŧĩt:]>ķŲG8}îüf—ķĻ(tš†=úī~gŗKxëÎ]5פĐEDDDDDDäÖíõ6섎–1ôƒÍŽâMQč""""""""W•Ģe™”B‘  ĐEDDDDDDDd(tŲ� ]DDDDDDDD6€B‘  ĐEDDDDDDDd(tŲh3spĪöŨ˙äõqŪoaæáƒėŲŗûov%""""""ō_.`Ëî;øÁ{„ŸųČa~éŋ=Ė}?u/˙đ{oe[˛ŲĩŊ}›]ĀÕ%eæčŖ|úČ1ĻOžbu5%M’ö${§Ļ8ôárp_{ŗ‹yË[īāĮ>ôîžl]qŧ( öŪųø'Å'=Îįž|–Á&ÕøvĨĐåÍJOđđ}ŋĀCĶ̐´Ų3u€Šņ˜˜ŒšįĻ™~ėQĻ;ÂįîûMųø](č‘Ģ]íæ{øÅŸ~/7Õ^ûÎ_ūôg8Æ­|˙=āƒü9ūĮŨŋËŋzäĢĖoFĄoS×\čōâŋũˆļNpæėĶė˙…ũ:ˑû>ÂCĶ)ā7?yˆ=¯IUŌSq˙/ÜĪ҇‰û÷ū!Ÿ: ŽšŠÜÉĪŧ6p,đ׏=Ęoũõę+žåy¯ß}˜_:øŖüŦō_PĮËĢŽšĐå×~í×°Ö~ãQUÕ7žî‡‡%›`ėŽũØĘâÎ|í-;wzô7x`:%Ų÷ yđ“ođ™dĪA>õ›Ÿxt†Šá71čę Ž<ôi=6ÍĖj I›ÉŠ{8üŅOph_œāøœŖŨwŲ9gxøĀšI8øđ ÜؘG?ÆžgęSOōđžËOömŒõŅrōĶŋ áÉīzŨeœxāøĐŖ)~ōŌ÷ĶS<öĀ|úņ“Ė¤ĐžœâĐĮ?ÉŊoâDDDDDDDŪ.žįŪđŽ+:\œüüÃüÖĶék>[ráO˙oũEūŲ÷ŨË=ų)ģø,õmėĒ ]ĒĒb׎]�cŽxvî)FŪķũ€!ë.QÎŲˇčĖ)Į?JJÂÁ~ÃĀåöâŪĐĢGš˙Ÿ|ŒĮV'9pčãüüÔ8ĖMķøgđ‰Lsę7ō‰ģöą*áŅŖĮ9‘ŪĮäĢ5ĢĶŸKH’”éã§`˙7Ķ•'O“˛—S Ŧ]~ÂocŦÃ?ÃäãŋËŖG›ŖŪŁ+.䏛…‰C|x?Ā,}ėCÜ&~”î™$ž›æņ_ũ§œØũ&ū\DDDDDDäíaä~đŽ×Ž)Ēą÷§˙ŸzõeąĀÉ?}‚c_ú*gĶ”žxŒ“w~ˆģŪ{+đų(/ûæīų!>pĪŊኞxėKüÁą/mĀElžĢæîEUUW.¯>ĒĒÂVéęyŠūļČqļz‹Î|Šé“)$SÜ3õ֌øäCŋĘcĢ{øøįōŠOæā<ü ūŊŲĪ ūęŖĖS÷ė'IOōÄôe_ž>ÎIĻ8p ÍęÉif/Ģķ‰éUØŗŸŠ7XŲô÷k‡íô(}MšyâĮfaōŪÃė8ņY~ãxJ˛˙“|ūÁû8ôęuũ‡ÂÉYDDDDDDäęPÛ}ģŪčÁ'˙â/øĶ§.˛Ô) ’oörlŲŊ`�C7߯økžöĮžÄß Xš–¸ĘB—˃–Ëe™cĢ‚ĒĖąeŽ-3œ}Ģ:]æX[’6ÃoÉî¸OōۏĪÂä~ö¯˛ēz؃ŊÜ;•ĀŠŖ<ą ÉÔ~ö’2}üÄ7ŋ}|štrŠßŗNMsōÕdõ$Ķ301ĩ˙—?}cM:ĖT’rüŅŖŦ^6æ‰#O0ËēÔ!33=Í, S‡pEî3q/‡÷kka‘ĢEk¤MøšcÅüWøÍ˙û3<ü…/ōų ˙ë˙ų>ķ§){ßs+ÛļßÁÁ}/īÚ´šlyƒu5¯ ^ŽõĀŽĸåEeY~#dŽø˙ĒĖqe‰-rĒ2gŪÂĐ啰 …ėuī­räCwņ‰¯=>Ń'åāå ŗĪ1“§æā]‹sžãÔ°gŠũ{āĄ“'™e¯t LLMąoožôĶ;î‚tú8§hsī={ŪxČöˇ1Vû^O=ĀĮĻ?Įãŗ9<p‚#OĖ’ėûyî|õŌf€6ãã¯Ŋđ„ÉÉ6\؈ˆˆˆˆˆČÕcĀÉ/>Î×VĘ+—;īųŋ|đ5ÍĮ^uyČr­.p….—wēĀ•ĄKņJ‡KUd¸2e|ë</¸oņ7ü_lœá60;Ëę*\Ų‘°ûŪCœüf3;ũĶÛJš,%Øs˜O}|˙ßZ:fr`’ŠŠ 82ÍÉô0éIĻgĻ>ž&`īÄ*ĮŸ8wíáÄąiŌd?û_ŋ×í+žą>@rü1Ž<>Ãáû&_YZ”°÷ŸßËÄĢ—vŠxÚopQÉđ0 ]DDDDDDŽ•U ļ_ŅíRođī땝ō™˙å,?øßũ÷üØĢË.:]–˙–ą¯‡°åUWMčōF.¯>ENU–TyFĢąÂ–öĄ˙VŨ jĪĨMh;ÉãĶ)\ž($Üuø.Ī9ŽŪ”éĮū–á†Û $“ėŨŋ˙Åˇ˛o˙íGsėė_;Ίd/žØĮÔT‘'Yž8ąJ2õ^öoÔXûœ{ۏqäČQfîģŲ#ĮXMösøuˇÆÎH_ģ‘5°:ˇöúƒ""""""ōļ48÷<įy'7}ãHŊ?t7Ožü—ŧĐ}Mø˛õ|ĪŽož\?ũ<sߥ:ß=].]^û(Š[fTEÆę˛ĪŲŗcdųkWŸũũí˙ņ{™ åøCņäˇ;X{7“m.íĄōFīŋ6ąØSÉ*'ž8ÅôąiŌÉũė{%į˜šÚ '3=si–=žEįĖ[2Ö]>4 3G8râ8ŋ}l•äĀ!.Ī &oŸV™™{mę’23Ŗ.‘ĢÆĘS|ųŠ+›ęˇāŸũƝđŠ_˙ˇķø­wŋ—›žņOđž|â…oĩēčēsՅ.ĀëB—<ΰe-2Š´`i!€ˇlOāŽķ?lÃĖŖü‡âø.Zåđûyčh Iûo ?îâŪ{ڐįŗĪ\ųVzœû˙Ņ>öŨ÷Øe qϏgoÂ܉Īōøô*íŊ{ŋąQn{jãé4Į>Î)öpĪŨļč ßŪX{b3<ūĢqtĩ́ŋ˛¯frī^Ú¤LšrÃ]fåsĶoĐū"""""""oS)ķøyæ ‘4iŊr7é`÷Ũ|Oëī,ũÅãģø)đĒpÕ-/^ˇĖ¨,ŌK{ē”U™aL€}+CÚxđķ<ČGøåĮæžx”ÉŠũė›f˜ŒĩšϧO0›B2y?õā ¯u×Į?ÉÁ'>ÆcäCĪ}”C÷LĪMķøgp|v‚ƒŸŧüî?mĻL’>t”ŖiÂūË7m™|/ûÚsôņ㤇˜zŖÛŊæ:ž­ą&qxęĶ|búLæÃ¯Ũ?æŽûøčžĮyāø/ķĄäđū=03ÍãGN0<5 ĮÕ`&"""""rÕXų*ŋõoļō‹?ũ^nĒ]ūF›Ū}#ĩų€ũŠßčrYzęwųW_8Ë[ĩŲĮĩāš]ō"ģ¸ļČ1ÆâÜ[ē�LrđÁ?aęĮđŲĪáøôqŽN§¤$$ãėŨ˜Ÿŋ÷0‡LūíK|�Úxđ÷>ĪÔCŸæŅcŸæĮ.uĮLNŨË˙ōãÚwåSû™Hf–)öO]ūÎ>îŲ›đØņ”öÔ=ė{WņíÕæŪCS<0}œņ{ŊÁg&9üČ#đËŋĘgáŖĐžœâŪO>ÂGĶ_æ.….""""""W•ÁécüËO_äĮ>ôîžüfGËø{Ž_ī+/ŠN{œ˙åŗt6§Ėˇ-ãœs¯ž8{ö,×ÖŽ›ãgögųā?ŧ>taú×ųŽģ?€lYPf}fÎ|üO_ÜĊ¯MO~âøČãģyāåĐÛsLjˆˆˆˆˆČeî˙•˙ũ-%`Ëîw°wßmÜ8Ōd¨ƒÎ*gN}¯?ũ6aG‰˙ˇņ?éßbvv–oŧņŠcoûN—Gy„gŸ}–˛,ųž€ĩ–ĒĒŽxū‰›û<ķäQâ¤A”Ô Ŗx:]„SķĢĪŌ>đIîUā""""""r)Y>÷_>÷ÔfrUyۇ.ųČG6ģ„ë\ĘŠŖG™>ųG}ŒSėįÁūwÜ%IDDDDDDDŪūĄ‹lļ9Žú~:•ĐŪw?ų Õå"""""""ōwRč"‡Iî{ė÷mv"""""""Woŗ š)tŲ� ]DDDDDDDD6€BšĒc6ģ„7EĄ‹ˆˆˆˆˆˆČ5ŦŲh€s›]Æ[Į9õúfWņĻ(tš†ūÉƒĢ¤3äM1æŌ5]爈ˆˆˆˆˆ\ÃnŪŊ‹_üųĐl4ޚe9oÄCŗŅāū#Üŧ{×f—ķĻįžŲctöėY&&&6ŗ‘ĢÎėė,7ŪxãĮÔé""""""""˛爈ˆˆˆˆˆˆl�….""""""""@Ą‹ˆˆˆˆˆˆˆČPč""""""""˛爈ˆˆˆˆˆˆl�….""""""""@Ą‹ˆˆˆˆˆˆˆČPč""""""""˛爈ˆˆˆˆˆˆl�….""""""""@Ą‹ˆˆˆˆˆˆˆČPč""""""""˛爈ˆˆˆˆˆˆl�….""""""""@Ą‹ˆˆˆˆˆˆˆČPč""""""""˛爈ˆˆˆˆˆˆl�….""""""""@Ą‹ˆˆˆˆˆˆˆČ^{`fff3ęšjÅqüēc¯ ]öėŲķ)FDDDDDDDäZqöėŲ×Ķō"‘  ĐEDDDDDDDd(tŲ� ]DDDDDDDD6€B‘  ĐEDDDDDDDd(tŲ� ]DDDDDDDD6€B‘  ĐEDDDDDDDd(tŲ� ]DDDDDDDD6€B‘  ĐEDDDDDDDd(tŲ� ]DDDDDDDD6€B‘  ĐEDDDDDDDd(tŲ�Áf """ßYŋsæŌŗq¯q—ū3gÎŒ1xžÃ\öąĒ0ŧüôΜøKJģLkwĸ¤ˇ> ŋša+‹M ŲRNÜô vT^ÅÚ_­ãåÁdDoĄ‡›ŗ˜†nĸZōĖPŦôą´oIčx=Û¤6ŌÄU%eŋÄVāmĢ‘nŖŠ]Oŋ;āâ Īē5ø~ĖH- dx­„xt÷Ü*ÁĀQÅj—][[øÆŖđBŠcŠ‚ŧę3üŖÛßÃ÷nš•­aˆįĀ9ƒņSVėKĖŋtŽ3§^dav‘ĄŅ&;oÜM¯ĶgæÜYÎ%#œjâoĢ0ũ>k§:ŦŽ6ņĮ›ÔŦÅv Ē2$v>ģk95ˇD}üvīúĨīø‘ī …."""×pp)Lqā<0Ü+¯GøĐ+š3t3‡Áø†|­bîš3tæVđkk‹ĸ´Âz&p4F"’‰˜ŪŽ”ūÅ>é™[8l§‚†ę #ˇ$dãyZ×|ōĖa+¨îđ̐|Š`ítk &Lą”õ:Ļ äy‰™OÉ<Į\#€ēĨ—X˛Ü'îdø•ÃúP8‹?äQm ņKLa௧͝%ō,>Ž,ȸel7īßŊ;Į&¨pցWáy)Ŋō<Oũ؟ņW_~’ķį.ĐĪJŒaāŅžu’Ą÷O‘Ž ĶĒ šaˆ+ Ūf‰p¤ž#--•õđ*ƒí,/–$ADŊķg+Øĩ™ŗADDD6’B‘ëŒ3\ęfųÆ"ãK.”†ļ1ŧsļD—ŪN­ãL×pz2 yQR0€qƒĢƒœÎų>ņxLũæ“XĘ~AļZā2GäBzS=˛^Îđx‹4t` ûb$đ Ž^ŧČg°šSt+âá”ęåeėhE¸}„rwƒtf˙ë=<P6+‚=ŋå{†hPRö2l粖õõ”8 pa‰īą&ôCŸZlI€ŊãßËûnē“‘ß#s‹<÷ô Ž˙§'8õ7ĪS8ˆ“ˆzŖEGÄ˙ÕģXû¯ßÁB@?Į/f>'0ņí㴂’zčŗ´˜ŌšØ‡Ž%Ͳn@dŠÕ‚ÅĖĀŨ›6DDDdƒ)tšÎøžšfpgĀđæ{ĖŦŦŗ´ŖÉމ&CŧkČąŗæ¸ž_°ô‹”úp@X3,wûTĨÁō‰wtÎuééÖCüVDé Lč㠇ØÂą6ß§ĸ ]oŌ]¯¨ĘŠÁrJ\1Ö# Xëô1Ąą+&ÚĶ/ ve@ŲŽ‘zuĘČ#Č TúTųĨŽ’(ōŠ$„=ƒ),Œ…΀fc‰Įę+}ōÕ>ˇŽīāûo}īÜē“v˜bIÁËČËe^:÷"ūĮÎÉ?šŦ_’´š4â�€ÄÔnŋįžëģXö*惌Āķ‰=‡ĩP ōEhO։cƒËJüøË~М H’Îb‰įܡúk‘k€B‘ëŒ÷ĘēĸWsĮĨĐÅēs/1ŋ0Oãtę=72ą{„ßg8p ‡ŽߝĐ?ŋ‹¯œë02Ū† ˛‘ŒpÂ#đI˜…>Ô*2[a Ëđ uōŧ sžO8PŗĪŦШ%ØŦ"ŸÉđG åŸh, X5¤į-õ1G´3$í‡xięĨåÂ€ŌæxÎ#ë„ ŋeH€2r8 QF­Ä„u\â‘g)ė‹oãûļîa[h¨˛3¤6'í,sæųįxú+_įÅgβžÔ§^o’ŒFāy˜ĀD#[ļqᆛč.”„Ë}ĸí!Žæ(#ƒ×ô(BČՊGY¤˜ųŒփíģ Ŗ7Ļtû]ŧ8¤éëW1‘k™~Ō‹ˆˆ\gĖ+›åZ�ķʆē| ų Ĩģ|žlĐ"8YŨ…Ų5ĘHœ`­Ĩ~ōŪ;Ųö˙Áą¯}؆%Žô°Ģ>~ā•Ŋ ŠŠ ô)͜ūŌ€0ö‰|ŸÎ|†íģJ’ĐÕlAô%åēĨŪŽ(úŽl9$o6)=‡éøKüĖRÔ0Ŋs>Įīø$78Ē8Ŗė�‹1¸0§[øÔ[AVpc^ãļx7ˇ‡i˜yú+]ē;|ũÜ9žųĪOķâéķäiE-Žj41a„ ŒŠöÖøķ7Ūą>øË=džaĩ?Ā$Ž8„Îz†ĮŌõųš.mRú–ÁDĖčÍ%õáœāĩˆÃ͜ """˛Ņ爈ˆ\w žÆ[]ÚãÅ8 tÛˇŽžDŅ[ayŪ‘Ė„Ä5ŸÚÄ5/ ˛ŧ˙}ßÍčŽ_xú/ÉgƒŲ”^ĮÃk„ĐôŒÁ厰áamEļXКˆęqÍgĐ­č,„5G6W˜*ŠčŧQ†áw4(ŒG:ˆ¨!Žāy¯ƒįáę~R#é:Š k°`!)1Ûb†[mŌn—|u@HIVAmû{L›ˆąz…W.°žÜ![_aæ™g9õĩįéŽĨDqVŖøā{xž‡ķ}|ã1<>ÆŌûîāt­Aų7+–¸- ú‹)ŪÂ:Uä0šÃkFäA…W‡°îŗŗîĶq†—ŧ€ŗķ)ÉlIÚ&5 ŠÜßėÉ """HĄ‹ˆˆČu&ˆ+‚Č×J˛õ„4 ”ŧ=;ØöŽ žyŽĩÉ JĘ|Āî&¨5ŦĩTļâÎwÜJm4äŗŋ÷Ezũ�ģîH&"âļc}ÁŌ93 ļfiŨÔ >—ŌIIēրuŽ`gx2f¸5 , i§ĀÍC˜DdĨm4 â0Ļīe”ÆAlđâ€( ‰đJKÜjRusŠÅKw˛C/­`}� Ôoæļp˜­/—øŪ€ wRōt…™SĪķü×^Ā–zŊņŧKëŦ?ŒđƒįÍ]Û8÷ßÍüöaÜlJ[Š´ K{بÂ+*’ †ļ aŖ€ūJ…ëx9ĖûkH]LÃVÔķ}?bŅ͝b"""×2ũ¤šÎôĪQ¯Exžķwá{ÖYk-™ÚˇcœĮęWŽ3OIžöČ{]nŪsͰ‰ŗŽ˛ĒØŗu’z÷{ų­ß˙#ƚOkÄᆠv`(sŸîųĪĨÄaDeK˛Y‡ķaāã÷,ÎAmwLŅ­Č*K˛;&‹éŽ8Ē5K}kBŅО�ŦG˜ûX¯„ĒĀ8"? 1‰‡édK%ž['nZ|?&ķ+vÆuÆĪ÷‰¨¨ˇ eŅÁf=ÎŊx†įžú<ÆųDQžÁ BĀÃˇƒ_ÄÛF˜Ûw /:ÃĐr—-qD÷Ļ+/•”:øÍ�¯æĶđZ;Cē…O„qȑo/ØÕ„흌ĨÜÃŗ>ŲĀöSōū�ž{ŗg„ˆˆˆl…."""×W-@UŖŗîãŲQŧ—Ė+›ģxĘŦĸž÷VŒÍY8ų'dy‡õ•UVæ—ØŗīvÆGˇ8ŸĒĒxĪ;ŪMā<~įÉ?ĄŗŪÃsåÅ ŋī¨ĩŒ đë¨ĀV†˛¨}Cqą 7;€ĘŽÖ¨j>ņxŒßō°ÖÉ/äCu2/Ļ4ŽF’æϟ“į9™Ÿã•>f[‚KüØä%qÛŖÄ$-ß°cļ". †ˇ†DQor_ēČ '_ĀĢ<Â(Æ÷ Î÷đü,ø—$[ÛŦ~ßm\ôÁ›['ßZ§ĩŖÆĐp GEXVô Kx $IAjPÅ!Ų°%7>#L- “�ęŽęy–oōl‘¤ĐEDDä:S}:saSÕÎĩ,~ĩ[‚3c<ŒķČ+Kō]īb¤ĖYxęĪX_™Ŗŗ4ÄęâˇĪmÜtË-$~ƒ^Urį;ßÉ-7îä÷ūü8Įūė$Ļ ¨ŸpgHsĸFoąĮ ĒČ]A4ŒØ9‡{š¤Ŧ,ŅP…K3üf $°čČ/öČzÔÁŒ…x5˙ŌŌ?kņüKûƤĄÃ5Áī# Žoqį{Ü`"ZEF{GL}<[ŅYXæëũ,¤–¸–`<ƒī ÎY|Ī'ˆBâVLq׿F›ũŒúHÂZVæģy˜Ā˛åĻ!j…eĨ’ĻŠļfŠ^^Ą,*ōALĪžézŦ´<Fļų4đ ŧ’zŊ ÖÚėŲ """IĄ‹ˆˆČuĻ?(ˆhŒ8Š(Ŗ –‚­˜2xeG]wé†Ō*ciūƒ;Inž…ågžĘęŲŋĻ˙âsŦ-Ī37‘w}×ģjmeÍ4kC|ø‡ūvÄüÛcŒkû4v׉Į|ē˖úHLcԐW–ĘųøÃ õØŅīĻļ¤Z¯(xM‹‹ÜM~á‡e•“uØĐ20ÆwDÎÃæĐŋØÁx`*Ÿ"ŗô—öœŲ‚adPЉ̀Ē?ā…¯>G5'ŠÅxa€įGøžG- ЎjÄI‚s–âļŦßš‹¨Ķ#nP&tĄXĖtģD‰G”‰‡Ų°žÁ�*ŗU…IÁ[Ģz)I+d5ôé­;FŌ’ē—ŗîAŪˇpÛfĪŲ( ]DDDŽ364¸†e­ČH,-1'27`đ0Îa Āb°šeĨˆXž…Z—˛ķ4#KéüŲ+/Īq˝{Ø9y}ĪQ'á}wŋ‡á­5ūŸ¯ŖīrXNHĸLd˜ŋSâĨ…WPx¯īcœÁ|üō…”AĮŖÜR#ßR'÷ n5ƒÕ’*+¨ĸ’ ČĢ’ČųÔ=Ī7ą—Ô ō‰Ŋ’]Ąūãˆ�� �IDATOŊ—3<ÖĀ+‚˛äėŠĶ\<=K’4�Ÿ(ˆh4ę´FZŒlk14Ú$¨ĮĖŽõøĘÎ-䃔 ‘`ũО[ļWõ+l⑖ŲZFéJüĐ#_­RŸhŦEāXĪ1YJ}xĀĀ‹¨Å ˇÜaV2ŋ�ËŨŊHDDäZĻĐEDDä:“­‚0¤(<GčUdŪYRŋĸŨDXx%xq†š3įxæĪž TÔÆˇīŦãž=Ù¯wXœ_bįí¸íÎÛŲ>jņŊ{ŪM}[ĖCG~‡åĨ’­ˇ Ķš˜‘Íj˜ĀŅ`K‡ëBļ^ˇ#jmCoÁa3´ĐkûxĢ9~¯Āõ+ėr…9‚!ĸ€0ņ¨ Įxíˆ,€,ĩøEÆMžĮčzŸF˧Ųō‰Ŋ‚îō2Ī}åEb€F=abįģÚ mi`~âŗē>āâä(Ųî6ŲÅũ,…vĀŽ!4˘_îS††Ē°X?�ßáGŽFėá-9ü<‚zD>Č0ũœ¨–á\†ß­Û6JlZFˇŖž‰3ADDD6šB‘ëŒņB 1ɰ#iZŒįŅ]벴đ,ŖÃ[ŧIl呧ŽÖ¨Áķ Qkŗk÷ģ¨o7äŖ‹ôčPÍoĨqn†Å2ĨŗžÎĘėw|ß^nšå]ÔĢaŪ9r+ûĮ?ĖŋųŌĸōzøÖáfKVį Âá„ÔF|ōå>؜ĨšÅ§ŋ”ŗ6Sâļ6Čkļ[āõrl`°ž%Ŧ ĩzƒ â&>EŋÄ:‡‹ Ļr$͈ņĨœ8+Ųrc“(txEƙ“/Ō_Oi5 ĩ›ėœį†[vŌØÖ†PXGy‹aÅâÎ?¯ˆÂ6/I× ÚcUÃ1ŸÃ`Ŋ$Xɉš ņm1áļ˜:†l)§Ė ÅzFžÖc¸V°u‡aËÖÆi {˝Zž+éģˆå!u爈ˆ\Ë爈ˆ\gŌ2ÃvK>4!ÜQ y´ŠŠÕõsôú>uˇųķ+Œnk&iÕcĮ÷ÜĀ ŸąĐ™%s#íwSë”Ŧ.œĻ, ^>‘õSĘĒâ]ˇī#І¸s÷$ ųÜņ?¤īĸZH÷å›WÔw†„5Cm"ĸ´g-Õˆ‹gōš‚°g,&ô‰F‚Ę'ŲŪÆÖúāvPaz%Cc n¤ÎĪgdi…f3 Ņ‰‚ŠĨŗķœūú9ęqÂÄÖ6;oœ`ËÎ6ĩv‚IbŧĀ`–Ę’™‡¨Ę”jŨāâ!¨EäË+\<ģJ<�“؎ĀĐHęl-Û¤ĢÖ{ôfK<Ά8vŒNnžąA{ȧ[B§(”1fKÄPā‘Vfŗ§ƒˆˆˆl …."""×BéUx‘Oų˜¸$Š=ļÔbŌų>s‹ĪŦް>[ą˛^ŅHAÍs,ŋ¸ÂüųYŦŸŅšxáÎũ„ëķäŲ€Ô•Ė/øĘ[<Īņî[ŋ— jđŽ­{øÉŠ’˙wũË,vŽ°äš… ōPZŒ­ČÖKšíë%„žOå; 6|ÂĀÂ�ĒŦ$_ā÷rĒĐáŽVĢNˆ!ļ0žĐ'ŦJļL S ĻČyņŠĶ„.äĻÛvąsrŒÖh“¨U§Š"œņđŸ4ËYoF ę!ĻpøqˆõsúE~šQ-jAE-ˆ¨|´ō0fĨÃúLN^ųÔļ˛aˆš>ŊNĘÚĸ%Ē…ÄIHá,ĩĻ#I|â˛ĸæŠĶEDDäZĻĐEDDä:S%Q#$÷2ÖMJÃ6(\ĀzˇĸĖrâzJ6€’„ū’Ã9K¸āÃé>i1O` Ķ×ĸÜ@ĩxš*Ģ(Ģ.ķ/_䯎NE!īØ}'ļĒņîŪÍOÜ]ō¯ŋôG„76Y›éâœ%Š“§9ô+Â(ÂkEd„~žÅT%aUᐇ>Æs¸2ƒ,Įu Ąą+axŧFē2˛Đ§QôiĮ´Fĸ°|a×+øžī~Û&ˇ GxaDP¯cÀŌ:Ę´ĸ2>Ŏm4“:=“SĢ…liú †v+fŦ1„g˛Î< ë]–# g)'-QęC!yåŗî;úi†É3ķ–ÜÔčD†f’ÔJj­ WFT+ۓjŗ§ƒˆˆˆl …."""×:ë)Ģ+}šĩ�vF¤!Ë ŧ2§ĖS6 đ|Ÿ<w8ß`â�%ÔÚøc–ŠYC“øŨ 0ČÉŌŠĩ,œwüå>Iô# 7Ŋ[Å|×m{ųÁõ üîßü ~䑾œ‘õ×)ŽČ„øÃi‘öœņ0E…ë§ąÁ>…"´”ÃQ#&Š q#$ܐ×-^FVJB¯btŦIŊ`LFwqwŨq3Ŗ;ļā×Bŧ0‚8Ā„1`̊‹§_fą™Đi6Š\ĖĢĶōi‡Ŧ‡5S1â‡Ŧ­Dˆĩ™Šzˇbë֘Ąxë…Ô†VX[…´_Q&ŽjŨ2ėÆÃ,//°į€Ą]1fĐÄ[ņ‰ļö6{6ˆˆˆČRč"""rņ’„|ĐÃXƒEt‹’Î|FÖ)‰"ˆÃˆ<ËĀy$øU„ ^bĸmøŽNUTäū,… ™[߯V˙‚č4~Q¤)Öu¸pö%ūꏞ¤ūà ļ7wãlwŋ—ã_~†™Ö`Å`]IPķ1GcĸIQ÷ôy eiņŦÁl­ļü~‰múd!äÖŅÜŨĀŽ”œrP1æûÔ(ŽŲ2>LœÖVņaüæqâ--<Ī#ōCˆ|˛ d\üÚs<_D vm§VæäËō Îúđ}[˛Öp”j6ÃĢg° aːØë͑—)a`{)öĸ%ŦĮØv —C­0ŧ-Įú%y’§5z‹~ĖÚ˖ų3nÛė!"""EĄ‹ˆˆČuƄ­z|énBXR[áJuPFFk˜Ũ1ŊG•Y<U^Aa ōX] ]ÆxuЁGTģƒŅ`ĸULé“÷3ŒņxéŲž6~‚úū:Ãl§ž´øĐŧ˙ã‰ĪĶÆ5TƒŠŌ9LáÅ1ø5Ē0†,ĮĻĨAbü�g šī¤ˇ+ĸīDX\ˇdh-" ;GŠÖ0~Fwi•FR§>Üĸ94„ 2>AŖF×8^œū'ÈŗcÛ :Pëæ ŦÅų)ë/ÎŅojCõ–Ą `ā ĒĻaüŽa úĢ} ÂTŦÍå¸Å€Ąí ejÉũ€<­X8•Y‚i†<]#Ģ,v3Ξv›=DDDd)tšÎÄ-‹į•k%En‰#GÜąĨOQxô9Eāđۆ 4T…Ãyg,žīQ•Pdŗâ…!..IŌ6Ávđŧc-Ų`€īGœ>ų"cãŖė}gƒĐļšë{ž›ŠģŋÂĶ‹¨ßĐĸ?›’_ŦX>šŽˇc+f{Ø÷(rđģĄM)ō? }Ÿ¤đë!UUâB‡‰Š ŖįKFFŒŪ0JŖŌYëŌ_éĐŪ2†1>žņ¨y>Q+Á4NūÍŗ<=>ÂęČÁZFu@Ÿ€Ņ-âĐâΝá/š~ƒ0Ęč–%š34[M[=ļLļŠV,+ ËŦŦ/á…ÍZ4"‹™ī˛ä^HēĻ ņĸˆēh•˜8Ĩ1nŲ•x›=DDDdé'ŊˆˆČuĻÛ°2×göųĢ/•d ÛĩkņpXcY)ræ–ú”eQ†+-.3ØŌà š„­qLŊ ĄAN?ŧ@nRŒ38įøŽ0¤ƒ+KËŧøÕš°pëĨā}čũxļbđRŸÄƒ-74HMŒI0ŧAEAc$f¨žP_ƒdžÂŋ˜‘E!Á ¤f<†­Ī-k>5[˛ũÆ-´Į[D‰OwySâZDå …s”Q„?2Ėķ/ŧğw æ†j˜ŪqĢ$Ūę1Ō´ÔŊœ¸fŲ1D{¸†_”t/XžØ'_¯XžqîtËØÎ&ģvŒ3ėš�į[˛õŒV06B?ÃwÍ�nÜãđ†RÎŧėXOĄ5ĄtEDDŽe ]DDDŽ3/ŋØg}ą$]ˇØÜ§´ŽN'ÃC­ĻW8¨,6Ląq†JŦąiE–yX/Â÷p>šĢŗĀ0Eâ‡ņ ž (YŋĪ…™‹<÷ĩXMÁUÜžûF~ō}?@5—Rö ^3"xĮ(n˛E8lUQĢG4‡kGÍøšŖ,J*ßRKā|ëØžāSŋØŖ=VctW¯Îætį–n4ˆ‡›T^�Íg^<Īī?sõv“Ą^J”—„Î#j&TžĄ*rĘA Î#iá|Ÿ´į“-;z+–Õ, St{°Ū/! HÚuĸą:.ö(ũ€pkČp중—K%Å g¨1`´žFÍËņc|­ ˙ō`ŗ§ƒˆˆˆl -/šÎ¤~‹¸î}œĩôK‹_ƒ¸ÕKÆˇd•ÁĨ–ÁŦĄĘÔRüz€ÉcŠž ļgJČB‚r ibYjļÖfņ×Ā÷ 2äũŒn°Æ™§Î26>JëŽa‚ĒÉ˙ã÷Ņ­ ūãO“{Ŗ¤Íˆ˛°x­ˆĀ:|k _Qô \ŨāÅ¸Ļi„ø6 žĪ[Ë)Ų1š•¨a|čÎ.Qõ2Fwn'ÚŌÄÄ^#amq‰'f.0ˇg7ík):†ƒ*  iy¸•Œîė*Ƌ¨Ē�"Csë0Ö38ãcmÉęŌĨúL᱒û”Ã!V@cWB§Ė(Nw *‡i…ÔęP .>“’ŽAk$!u°ž N‘k™B‘ëL{ØāQ {TiNVjaDIÉęRIąâđcƒŗŒø˜ĘģŠÆ¸‡‰ēŦĪØÂŖėĩ°Ģ ^ā‡ŽhÔ'íĮ,[Ø6VÎv[ZŌ~ĨųEžūëS´ĮGšië-TUāũœ[îōsK6ĸLKŧØá§ÖsĘ8Ā/,ÆBĮä ŸŌ•¸ÕJąŦNÜ[ahŧFk,ĐR9 į^f¸Ūĸ5ÖĻō ¨%äiÆ~á,ŋ/qfYyū"ÍTÃ!^XF–õ.,ô0ËPΗ4j[ĮëŦ RÖrŸ(Ž(z֖q@9äáĩbjņ( ΐ­å„C!T>ļô )QRRß°V čĪĸ,ĸ4Ö}Z­ÆfOŲ@ ]DDDŽ3&īĐ_ĪH‚ˆ°æŗ6ģNŲI(Ú!UĮQu<ŧ!nĪRY‹ī„uƒ×Č`[9ABo ¤FÕę`M‡ütŸAĮRîÜNÔ=ífTĒŦ" ēĖŊČŗõ,Ã?ÔbKŧcb~úāû9ũÅnŽ_ũ’¨[á†|P`(‰ę~ÃШ°k9Îw4}¨E†‘­-L䨊ŒŪÂũųunŊaA-ƒõOŸ|ŽgnÛEaCčč.˜VL°Ō~ūZˆHœ!đ‡|ÆBĮö5ú}Ÿ§æ:,U%ąoˆ’�Ķ4,v×! !nŅ}qž~Q0´ŖÛ­YĖú€Î…œ‡‡‰,éĀÄÃãúULDDäZϟô"""×SwÄuGŦu¸ķÔõZĀ…3ë”ŨŠá‘:Ņø\|Ļ^AQÖÉ3Gįå„âØŖ1`F#\Q¤%nÄ×<­Ū cøĪŊŒGHéJōĸ Ûërú™ĶlŲ1ÂŪ;ÔĒļ ˇųŠģŋ˙ë÷ؘ*3TU…sƒ =‚Đ#Šcb?f0W¤Ž–7 Ų đãŠ4íbVsΟŖfÆ&ÚØ$ßãėŗ/r~ûéXģ´FąÜ#īäŦ vPŅ/3 įáLÎđî;Tāˇ|Ė˙ĪŪ}ũHšfw~˙>îõáŌUe™ļĶŨÃqœ!—Ã% ‚"–ƒŨåB!VԅūA€ūŨē¤ŨH(ADAX#r‡C.†;3íĻ]™ŦŦôîuĶEV÷’n‹t>Ÿ@2‰D"â�ņâ—į9ÅÖÔsÁ.’ĄuLę 5bíÎZ‚ÕˆŅā[ņ9)áp‡Ø Œ›÷2ĄØũ–!4†M ņĒCGV§Ką$I’$ų:Kƒt“$I’ä–9ûĀrũ–׎ ‡īí°û͒~ląÅK°ÆQܗLŪQL”LwŒŌĐ ¸Œ¨5EQHEûŲ–Ģ×âÖ jC,<ęģwP'Ri‚‹8;pyqÁ/˙æc?ųœQ­‰ÁķûoŊËoŋ}ŸI|ŒøqDŽŅzBëQ ´ÆÃŦ.y#*Ļ1"ķˆ:úåŠĢGĮœvÄîÁ™)ĘIÉúęŠŖįhoŽ:ß`2Åčë“ ËĮK6OÖØ“‘<bkŲžŽ´O;–§[ŽdäĒČ8ę<v–Õ™Å …Ė LJė<įzš…ļŖšg(ö V9˜dd‹fībqĪ҆œĢ¸‹+§8_Ŧ_u9$I’$IōĨ¯$I’$É-3Ŧ ĸuT.Ō]´ôxuÁvå‘F"$´#ĸ”Tŗ‚×į´—[FĘR0ė•#FŽrĻSMhēČ ĨBā( …gxm—đ|:jlß#<ūä Í´Dɝ;¯S‹˙Á?ü-Ū˙ėcčF+ŠyEôldgÁ{oŋÉŊ7īâúĨ‚ĪŠ ‰0ŪZ믞zvLb:­eÁ@āũGg||gŸņ¤§h#ĩWL¤Ąß+C@ĪËĶ–ņĘ"´"ļ3S8ą&2VcA(Äēe ‚â^AGÆqD÷i=e)3CÔ-ūē§ī {rEŒžr_D¤ˇč!ʊ֎ČKûĒË!I’$I’—(….I’$IrËôŖĨ.y^ŌöÚĢ–>:ÆĩE¸€R†ūš%×›iĘ™Æž[ÖĮ[L)ņ1P?0Ėž_Ņ ĮúŖŨ ĘoÄŠ¤}Ōsqܓv(4õˇŪ˙ĩ`ƒÛb{·?ũ„ŧŦpߗ˜Ų 3Ųå?˙Oū)ۍ%͚"StE Øģ3'C↎Ö_ąž;ĮÜĢ~K?,‰cOŠ<ËGŽI ‘i>˙ė)õíZQÔbOh[ˆæÍŒXI˛™ŗŒĢ/,yЄéē@6ÍFâœ%('9{¯ œč TŒÜĩûá’ü^EąWrŪ:ŧԍ‚§Ö?]ĄCAĮąč;ė*āĪäÅĢ.‡$I’$I^ĸē$I’$É-STŠ{ŋ]‘U‘ņB0ÛĪéĪGbץJƒžH2ŠPQ2žt¨‰b᭜q3Đ/-äĩgČīėÚŗūš%^ÃtgKŽsTŽiģ‘åQOiräëņ`üĶcŒ1øMS=ëË5O>:bžŗGs×0„m&ėMjŠĒÆ49 AúĄĨī;”đ”SI1Y€t0N@ %ønÅæōŠpŊa¨§<yvÆ_­8•të.0´=Ę+\"täEdJ˛‡3ÆL`/:†‹-ņ‰ Ž%LĄī<ĘI6—4Ũ”´ãˆ<nŅĪÖë€m#ÁGœÎ•fPi“;˜[ŗËĒ÷´ũ5^9Žĸ zã^u9$I’$IōĨĐ%I’$InW ÂÔƒŪ ´ÃėįˆL‘/r„Žā#eŽpÎ…AW‘ų›†õĩĻûČŖ+#íeDE1Ī03ÛČöį=ÅAPŠ`1jú K{•QĘvü%šQXZeØÁq~|…Y>Ág-B(TSCDĄˆ&YÁ$+)¸č Ņ3ËÆŽņxF?„g˙ÛßÁ÷#r¯aĶŽøB8úã5uČÆ֎ęnƒ94økGŽz¤ĩÄuGĨsÚÜ!…rŲÃĐ3Cuw—V–\_`Ī-å[ģ„­b< ¸7īĸ_›2 f‚j&pį-“ģš÷F‘3HŰ(O'¸q¤_w`AŊęrH’$I’ä%JĄK’$I’Ü2ƒÔ\22Š#RDŠ@;ĸ”¸Öâ7=;‡U™áĸĻë:.ø!ÜŅŅīŨ툐‘čaņfCŋė9˙p…Œ’āĄÜÉF0ā–‘ŠrĒĪ‘ëœŲ īŧĮūkŠg3šũ=˛ĒAįšU;Ō_há‘XDapUQSĸAhj3Ķ56:z70fsVå”åŲ)Ē,؝64.Y:G°×=U Õũą_˛î{tY"ĩŖÄˇŸ1d†î¨ãá\S båPyEŽËĪ–´{ĸƒ1JöŪ;¤xk—ž÷l6 OVøŪQÖbVáĨ@�AKt[ –5tdEŽŠŌĨX’$I’|ĨOú$I’$šeĒûLCÛ–Īz†‹‘ᤇ�ŌDĖî?ŦYŸnál ˛šŅŦ*O+$ãšÅ­ÕŽ$›døčX^nˆBāZ›H>ՄÔĨF’1<łīūÁ{ŧũÍ÷øĩo}“ŊÅ ųwv)žŸ>=ãâ“#2鐞c:Šā !ĊžĒ(EI …DcȍæfĘš*čâ–J &¯ĪČĨd|ļ†č)ß* îŽœŌŒM†r#u)ĸåŲsĪjÛá*‰‘ԊŲ\ŗî†ÖŖ&†ũŠáįKÂŲ@Ļ1Zί.ĐĒ�ë“-ĘĸĶ ö ‘1Đļ ‚ÚXš{‚qô m:^”$I’$_g)tI’$I’[Æ=Ų˛öĖæ„L3l Ö'#ũįģ9‹%RŒ6¸ŪQF2lƕŠŠ%*BJĐŲžra(” [:ŠJ1eŦˇ#VĀÁ~ÁkođÆū[Üī!õŖ\ˆŽ<h”ŦŧåϏøâĮŅSUšfZ`ëHßäfD2`‹ ^Wädd(2 @b0Ö÷čb‡``ž{Ėkwt§<;Û`”œžŒc š3ÁĪ ‚ŠģsĪéĶ ú‹€$ŲŽCW9gᑞ+xŠØ›¨ÅČåGK¨JBč)ĨĮš‚Äč5Dœ‹ÄļGö!8œĖ§ ʝ‚ašĸīˇĖgŗW]I’$I’ŧD)tI’$I’[F:Åv ãÚQŪ3Ä\Ŗ”ĻŪ¯¸˙ģ3šCÁųGŽ>¸`šlņHâÆáĪ,×'ž>z<Žrž‘ÃúŲ…¤™gˆ˛-Čč Š};ã5fĖ› !tœ>ú˜zYQī6äuIVx§Oxú—ą9šĸY””EMD2ÖŅg¸ž@ŠąĘ0ĻĨ Ĩ$š*ČcFĄ‚Ļ%P𚨸ōköߨ!V†“õĀz%“ ?)YÆ@a\õēFš Ŗ™‹TšcÕNŽc9;õČIIƒā  Œ{š~ˇFVŠŧœ_ŗî ÕnC¨``jrÄYvkʲ`>ŲĄĖ*VęšaČ‹t)–$I’$_gé“>I’$InņZÉđË5ũé;ßZÂā™?¨čÕČđd ß x/ȚœõÖą#ģīÔė=ôŦNíŌDd{aéž:Ô¨đõHķFÆü× Ž?Zŗzym6gĮ6aEģ\QM2fŗÂ%Fb_âë‚,7ØåízIk%›Ö1zI3”dŊ¤]J„”(%‘Z’•9eSQ/Ļl˛)ĻŲ”FTÄ‘"į‡wßã/ūåŸqRB5+qËî]Ę;ĻŒÃ@G×Åk?˜°^õh;2fŖc%Ŋ5ˆ¸Ä=Ø8ÅÜ�ĩB<ČiëČv°8ž‘ĄĖ(2Pƒdw6ĄŦ &ŽaGZĄ1ˆGO¯^u9$I’$IōĨĐ%I’$In™îrC÷|‰ŲDė`č¯ĘIÛQœ|rI8éŠwKšƒŒb×PÍ!´n &b ;wrļĪ;.ZoĪ™ĖJÖį[L™Ũ“Œ'šÉuC ĩ[ĸŗ¨1b­ ē’,nؐ9ųĸažˇƒR‘(^Z\€ļ CÄہ|ÔņÖ#Ŗ$Šˆ)ŗŊnčZSMkäNÄå–*:ʨ¨ŗ†?ūÆoō_ŋ˙¯ŲĈŽ*Š™F•Žā,# +ÅôNę;‘ōĄ!ZEģttWmTIJ`öƌ~éˇđ¤ŠÄR҇=^2ŨËXĖ4ĶŦg4=Âdø¨čĨ@W€(ŖCŦ:ÜŲíšg}áYŸo_u9$I’$IōĨĐ%I’$In™á“ uUĸv5ۃG2 =ĮŲzŦ9=ë¨÷*vžŋ‹×Žö#‹”1õt'ūŦĮô–ÉaÅäaÍ(ēķ–Õ‘b×ÖLŠœ(-ø-^Fâšēėčû ĢK…6šéŨ]‚Ž]cŨˆžˆ&!xúŪáƒD¨ˆs^d'‰!Đn7ԓoD øÅˆ-zĀQ…=~đÆģüņžä<ų€üQ‹ûø9fG ¸l „TĖ´b‚|9˛Ø1´ŖĮw™Š8˙â‚v¸āđû¨CXw—s’ņšĨzjárĀMAfŦ‰˜E*2„ dvd;Z.ļ=ōĸĮ?]ã-Ø\RßK+Ŗ“$I’äë,….I’$IrËäYFūzÃ=~PwJėEĮ´Ôd5ãŌŖĮnq*Č&’ā-UQZąē‘Ē,ĒfŠq‡žģ?ØáüHŗ}6˛X 0zœ xb!62v[VJc2Mī=QÂÖ}!ˆ´H‰>`û€Įĸ¤¤ž=RhƋ+&]ī,ã00ļ-u;%ŸŒĶ5;Ĩ§ üŗú-~"ŋ⋏bšU(ŠpcOí%CīYGgAGMštk2Ķũi4ĪâģnëpFQ ’ūhE9hĻ“ģV 0šfŌäD§ė•DxKs=°ŨX\ÁaE™ ōmxÕå$I’$ÉK”B—$I’$še‚ lŽ× Į-Åb‚øFI°`æ‘R ĸS¸ĩgįí Վbķô™Gļ'ûŧ#.4š‘dĩ Ō5ëON÷ÄīÕlû‘É…G{q¸8ā]Gt#R¤áé Î"B Đ†$>)R@œķÁ!�gˆīO›eäyN~Ĩ™Î&čŗ3ęiÃtwÆxØķp^RÆ˙qū6˙MvA}o‚Ė#ח#âé)=^´—ljÉØ† tÛą]z¯WŪßŖˇ[Æį–rˆpēÂ>鈘âG�� �IDAT‹ Ē”ČLáŖ äj2aK†{..ÖLą”ëéŽ0,rM jĨ^q5$I’$Iō2ĨĐ%I’$In™l7GO Ų*€ÕÄÚŖ í3Oķ §)VHōF1™)ŽŽ;\ ĐIVĪ,Ĩ“Č=IĢÖˇ\^ ğļ˞íj`wPøĖá‚Ãų7ŒDgo!ĀGԋ›đŠĄ‡Õ՝nfģD‹/<>xŦwX7āėˆŧŗxëˆņ&x‘¤ŅĖæ3f‹õdÍåŲ%ÃĀėģjĘožÉ?j6ü›ū9š”ģ3âāpã€ĩ"WŨĀyįČætžõEG7éXÜŠ˜Šī`8ņėV%oJŽ\$‰FŗjCī(.G2•UÁ¸RĐZįŅŖ¤>œP ™°7ÍX4éxQ’$I’|ĨĐ%I’$InČīdˆŨí3OXĖJbˇ–!Dí@‚m=WĮ‰ ,3Ė§ĸ_YÖ×#5ļčąēÅ_)ėÉdRQ™Ž~؃'xw’Œ@J$ =BE˛,CÜļŅ&'ĶĄ<ZBTa$Îŧĩ ]‡,֎7.Î2Ö:@°š\Ņím('5ų¤"Ģ –ÛsæĶ]˛°āG“ˇų›ū)Wˁ; ˛ ŊÛ"œĨëzâĶĩ´ W͍=›>3’bfp{`žYąwí¸>ņŦמ1ä„^°ą-šPW Ŋ :ŗˆąCī-¨^?ĀŅ3n;ĘŠŊ{ÅŐ$I’$É˔B—$I’$še†į=ž „7Jú|D,%b§!šŒŽhŲ9ČY<,8ūôšqtÜųõ]Ļ›]aAæ%Ēt냙gˆQ“_EGë6ÄÁâÆŗVƈ(Ĩ1Ę ´Aë›�ÆEđ7뛋&Ŗ¨2¤ÉU†—3­4ųŽÂ˜ Á:ËõÅ'ĪN¸^]áŧÅ9K3Î9(2ŧĩ\_]˛3]˛ËœXōÃlÁŋč>cũšĮO Ū”�”ā`g†Ú lĪ;Ö×ēļŲž8ÄbJĶTÄ:pąÜKĪĄ_¨Ŧdz¸ ‘aĩÅŦ,nģbVHšũŒūDↁnĩÆT¯<įŨ–îyß}Å‘$I’$ÉK“B—$I’$še\!ÄE€! ú€2āęr œjöö§ä{ >‹ŦžvLī ėŊ=į,.V“C÷ŦŖģöT{Sę;9IąŒ Ī[ÚaÅĐulŽ× ]O’Ļn(*…”-"ZHd„=QD´RdeÆ|wN9Ņ„`ąÛŽåõz‚õhm¨fęyÃd^“™ ÛõœōÉĪ>äøøˆž)KôžĄí c‡äÍV¤?œ|“gĮ<ŋ°0jÂāŠÜ0™ĒÍ8ĪY[â$Įĩ+Ž/6l|Äė;¤)ą[ŸČ<c÷ôks6Î"7†]7ā\Ä79ä‚gŸzėŅŲnpZ“Ušmģayą|Õå$I’$ÉK”B—$I’$šeĖ4ōÚ2™—øû?Œ¸%zA(2NĪëŪqö™cõI ˜÷Üų~¤Ú)™ ›îq š2äyĀį}Ī0œ;ēĶ Ë՚ĢķkŦĩÜ;<äđÎ]ĒēD(‰÷á#DR"‘äyA^åĖ÷æLJ¤đŦO¯X¯×ôÛ-}×1™Lp› Öö4ŗ’ˇŋ÷6ŗŨCŽ.ĪŲ;˜ķW˙įy~~FßõlˇDÁ°mŲ[BeAŗ+įüÎū;ü¯|‚ ëˑ\Œô×y‘!&†ŲÚXNmIĻ …Ōh#QeÆÅ•% Šų› Ē–l/¯1•AM$[ BĀVƛ!ģ÷& [Ë•ėiÃzkˆ¯ē’$I’$y‰Rč’$I’$ˇLū°aÜ y|X2Ä÷t@īdHĨ(‹‚åɆ‹ĪÁû’āčƒsú~Āį s˛…@ŦzD70Ŧ,q–cĸPØÁŌˇc?’g9w˜īíP˜ ač{ļã?ZōP+* ˜JQÍ „ˆ(8=~†ëZÚË+ũôį˜(Š' ŸũÍ/xô7Ëoüūoķß˙§4\Ņu-æ'°ÜnčēíĻÅļ~´¸r$%!F~¯~—OļK>ŨŦxøú.(p12ZG^5Ä:éļ=•Vû5ũŲ%›g-2ĪBŖ-ÔķUVË5ž‹ˆ\ęĀÚÚ(Č÷*Jđ1Đ AåawšSOŋąhŸVF'I’$É×Y ]’$I’ä–ąëÔA+"Ûįz#(öό8ÆŠĻ2Ä`Y}ŧÆIíPBg’ÁG.ŋčÉ׆Éw2,YU”Uiˇ%Ņ™Ąl*TžV`ÁZč-]ģaЎj>ÃY‹-RZ!Ā¸œēÉŅ*đ胏hwŲšwŸ'ū-§Į#ųū‚đ‹øéĖ?ūĶ?á7ūŊ€õœ=ģb=™’¨(ņÎÄÍ1ĸ#…ČøĶđ?åīs6kąÎbWžčW—kÔ`ˆRŖ” Ė ËĢ-c;PJcŗ† &‡s|)0cNæ~5°||ŘiÔÁ”Ę ōÎcŊg[hXôWwlÜøĒË!I’$I’—(….I’$IrËÄå€žĀølM?(ä(‘UÁ؃ ž@ ōÒpąÅ^{œ/‘^˛÷@˛‘åŲpˆ"GEÁ¸ĩ¸ĢȰ”w*Ėm2TĻ1eŽP ­ îjÅŅ'ŸąZ-Ųļ-å¤Ą˜Í ŊEčŖ yYQ6S˛LŅIhvĻ4ĨfÚLxį{ŋÎŋũķ˙…ÕųŊĩÄ˙ëš?úO˙+ú~āūīūūáôxũ×^ggÁdoA]7˜˜Ŗ@æYşčoņįÃ/ųÛõ3Tal=J‚h]¤_+V—:(˛Z ĸ@Õg$ω¨Â–%›/ÎĐ!°Ŋ Œų@­z)Éį9JįdAwÔķŲؚĮŌķKéúē$I’$É×Y ]’$I’ä–Q÷+ÖĪÖ‹’bÂp>BëŊCuģõ  =Yĸ­@- †.‚Ü=˜0}Įķķ¯ąëHĩ[3ÍōqËæ80¸ž{ē.ŅZQ•U͐g†Ķ>åßūœÅƒ|qôŒķ/>cąˇĪ[ßū]×QÔöīRė1+÷Čb 6žæ`‡; v÷æœ_]Đ(Ī•ôœģ;ÃĀ?ū īüđ‡˜r‡Į?ū÷~ûû4ģS&{û˜ē ˜5ėæ{¨X‚ˆD|,Dŏ˛w8w×<ÛŦX4z§ —ž“'kŽ.-ŒSœ;öîÔėŪoب_FÚåĀæķ&ĒEF,*œ–Xī‰cDĒ™EBeģéųĸōôíŠt)–$I’$_gé“>I’$In[ú“žISSO+ėÉČx´Ąi2œ¸ņ&˜đƒCÖ r^ė†qyöÉ õ-A^K6Ī,ųNÅüÛ5ÅŽâčß,Ų~⸖ “rB™eÄēawį€Ëã3>ųÉßđ[ôGdŗ)üô¯đ>ōŅįOXėīrp˙uī>āõwßdoqĀ,6ŒąEɌfwÁģŋ÷(Ĩáũŋ}ûģ|ķގ˜ŒYSáNųŊß˙.ÕWH‘!‹ŠfoŸFN)C BE@D@ ŋėx‘˙Ųō|ĖĪV΍šŠņĒßwä횘)L(/1n¤Î<EĻņMF‹åúhÃæ“ f{%!+)C†¨rú Ø^õœŦ¯a'ãįEĮãûØž¯`p7{Õå$I’$ÉK”B—$I’$šezFęÚfˇbüb…_øÍv ×_hô~Ԙbĸďāę ‘GŽ––ėŠĮKĶ—eX ÍžĄž F+đYÄęŠēšPT’ÉdÂûũ!0ÛYpvy…Ĩ%wîė2›īņāÁ›ŧ÷ũwŲģˇĮBÍA“c˜DK_mČ÷ŧûŋMå×wH­XėN™ßÛŖšģCĩˇĀ§gk„Ę™ŪewįĨ˜P„ „oq3×Eˆ›đ%ÄČD•üčđģ|cį?‰8Īļ,˘īW¸<Ō_¸e Ø­PzdÔP)ŽÃL`ŋ9ŖGüŲ†yeˆv€^▎Nĩ,›‚ëĘR][ĖÅHŲj֋’mžŽ%I’$É×Y ]’$I’ä–i4˜<0n<ˏ7ŒFâž[ã­§š:”˜ģĘ[D;R5ÃTaW-Ē,ą*âēˆīË/ļoŠŠlßāĩg}ŧĨV“Åal.7Lšũrç?˙ oŋķ-Ē,c:ßcŪ}•+¤!�MÍĮSZŽ;‹y÷Ėîm�EÎē*°zÂā&¨Q3?˜2ĢL˛)†ĸ$ ÁÍ]Bđ寏1"�)n‚āíbŸ&üŦ<æßqĆ@‡§h N‚įW‘pĩáۃ‚i­ ģ†ĩtËH,ŖpŒĢ{eČōŒŽQÔĨâG˛áúÚ37ŸĖGĀŊēBH’$I’äĨKĄK’$I’Ü2âŲĀh›ŗÆÔŠ "!ŒŽ„u‡X‹›îXĸk:?™ÅŠu/kBãVžū¸GåŲũ’­ß0~nŲŊˇ`ėļ\žž˛Y¯Ų?|úuÅvģ%åÃ7pÖ3"Ųšw­#a°ŪŅ›ž}3KMÅoí\7V÷:d|‘ÉHĐZj2Ŗ”A#É1˜˜‘/æˇÜd.Aäæ1ˆ›�&FˆÉÍs!&ü.o2‰’ë8ŗfˇdšéXõ–<ÛÕ@ŽsTĖȔŖÎ%]đ\_X´ËŠīîPīM¸cFę 2cx˛g9D‡ķZ˙ę !I’$I’—.….I’$IrËŦ˙â9—PĒ…"Äųų€TšöhK8ęÉīä~MV+üjKQ:ōˇ ėqc 9ĖĐU _˛"2Ž,ÃŗžáųČÛûwÁŽŦ.{V끞ˇ\^ŦYsī<ŅG”ŧũî7P&cč{ €|Ņ=7"1t\°—M˜g#Ž@$2F‡)$ ņ&8ą("ŠÕ¯:[nž⋯á /ÂÄWáK$ō]ņ:Ä.˙zxŸ/ü†ĐŒį[J -öĘQjƒĒo>ØåzŊá‹O¯ąĒBEëXĮŧ(ĐũˇÚPõ#Ö;ÉÆ|ętI’$I’¯ŗē$I’$É-ST˜™ÆV(ŠđËãA=ČY÷#á“Pķ@ˆ#áąGΞĘpŊĮwaĸ0sAæ"õL`ˇ0äų;LŽk–§-ÕdJVUgQá&Đ@" 3ŠŲtFĄeUĸ‹ŒRå义„äæ.fh4Š›î‚€—b�"ž d<‰Cŧ¸}š*úWŅ‹xģDņÅąŖ_=Gŧ˛ģP 8ũ6?‹ĮüÕö„EmĀhéÎ-Û͚ÃohšJą7kp•Ļیl.Ú{†ŅE2'$'Ū3öŨ ¨^A$I’$Iō÷%….I’$Ir˨ī”ųAKI Ų(ZŋĨŊęÉ*ž_ īg¸ĖaO,ŲRŌ@Š’ÍÚŌ„ŒZDú‹žqč™,vÎĐw4Ķ B\$`Œ Dsžž1Ząŗ3įá›÷9|ë.‹‡{Tz‚ˆJɛė…pĶÅ"" Q…'ƈF‚�=›@'ā Hã‹@E#PÄøâ`ҋޗ/Å÷âË.—CvB 4˛æûâ5>—׋kbg)e i >:ÜJНŒsAŧđ\_mđ"2¯gÄmĪæė8%8įo¯4‡UE˜ĘWQI’$I’ü=IĄK’$I’Ü6סvŦĪWÄōiI84ø•`*r¸Ģđ3…0ŽFüÚQ„Ë->jԃ nTX[°5#Ũņ æyhPV‘įŠ~ H!)CS‡ÍfĀÄČN]˛S—d:‚ŗˆŽGf+œėŅēĄk?ã/ūüĪxûמĪīü1ŧˆK„héŲŌ…‰ ˆ/C™Hˆ+4 {˛˜ŖÉoVF# „§Ķ…¯"į†ė 7ÁK3ūYõ-Ę}ÍûëäØcDĪô `zŅ]A{> H§ĩ§¨"â@2FCš_Đe ‚ot5Îvd…ĀšWXI’$I’ŧl)tI’$I’[Æd9õ<§ģŸu°´čÍáũšøzĪŲ1D†õˆ¸đpâéÜHB*č"Ãe˸ôH/‰ļâzÆ\”tlA†›­@ZQEŪ˛L3ÚëQæ‹Šë§ŸÂæ9>„‹S˛‹͎¨_{‹§Ÿū ūėŋũī™ŪyƒßøŊw‰žŒFnf°xžŪ¯Y]\ā{‹TŠēiÆtF!ŗŒB”T8˛X‚xąÍ(J"ęWی^ôžÜüö›ģČ͊é/ƒ—™Ŧø÷~×Oų`ü—DméV0l#†üîœRŦÜé‰Ūƒ ›žb7cįnƒË2ļĶŽåķívxu…$I’$ÉK—B—$I’$še”˜Ra7ŲNÆčúŦ§Ė"íā1Fá%øŖČkâūČxiŠĻD•Ņ>Ũ ÛisėR05ÜŨĶXģA÷UĮHž+„ô ÎŅۖa ] SR(ŽÖž_~ô3š<§1{\_ŠFđ—ų¯øā§īķ˙ūŸōŊßũĮ@FˆáÅL€H­[Ņ/—´'g\ŸâC$Ģk&;;dEŽĘō›™2Ã垚@ „2ˆúÅVŖ/ƒ—_͂ų/" ~S>ä S| >â¸[s~čÖā•Gī ʉ ™WlŽFVgžb’Ҏ |0¸ÍˆTōQ˙*J I’$I’ŋ')tI’$I’[&žŽ,eDKIķVN0 ¯2ēŗŽŦ*˜ŋ+Y][â2âđÁaf„™@Žķ4 ŧÆ,rF“s_ōqsRH UĀ‹s–0Xœĩ*j´Ö„ š÷æģdyÍ_˙í/8?m8<\đüī(ë ˙üŋü/xđÆ ¤äæXŅÍc) ļwŦž_bWįG§œŸ^ uÆto‡ÅŨ&ĶU3ÁÎzŠ…Å5/YČ0hĸ¸Ų$ĸē送¯Ž}9ķåËĩŌ"ūjÅt ōPŪc/NøķūÜę×Y\ôØĩ'đQ`GG/Lr‚tcDõ |ĀŲ›ĩĶE^ŧĸ*H’$I’äīC ]’$I’ä–)1č\áÈ=bRPLŅ'ЉsT{š§ãĀeéģ5ã0ĸkėÔĀųH6Ī)TAˇhrÃŽęčÛ Z ĸžŲLđ7↖~Ü"„B ƒÔ’¨@+‰ÉrŪzīē{{đS֏ĘėŪ}ūđO˙9õkwčâ%! Äâfƒ‘ˆÄHfŌK“ąjΞōøķĮ(c˜_/é6[vö;&;sēļĨn;š0ˇYÄ å‹n˙b¨ŽząY ~uāčËĄē7âÍ¸Ū’ ˙döØ{đ„Ÿ\?å¸ŋĸŊîqKŒ Ę 5ŠҐgSē!b Ãl/§ë3Æå€ëŌĘč$I’$ų:KĄK’$I’Ü2;%L$'ĪŧW”įŽƒƒƒßāĪ:ėe¤+ļŖĀR“ i5ŅJėEĪ`4ö<˛ģëQÅ=BjD D<!:ŦmÚ%Cׁ2dŲ„ÜHîf~Š(#؝îr÷āw8ũÉ_1ãMŽ6kōãĮ,v0R‘a(Äs@uÔ§Ō9÷îķėÃ_pöøˆ,*–Ë5}? œ$Ž‘ā=Ņĸķ#„ˆ^Dúâf-´Ž ¤$Fî ū΀]ņÕq#x1Ä÷Eđ"„ĐBđ;w^įūÁ˙rķ1O7—ôc$ļŊ ø‘a@éHSæh-čŦÅúˆ¸,ŅĮŨĢ)‚$I’$Iū^¤Đ%I’$In™ąōxÛ"2Pķ×F–GÚx6OˏÃF!1”fBÜ\'ˆ[X+ÚõČũũ‚ũyĮ°‘‚pČčŅãÆŽą[ŗŨnX­WĨØ_d=ā‰Ņae Žr–aë‚UßąŨn؜\ ŧ%Ģ tĄiĒž‰\ b €`Ğņ-gĪŽņƒäāÎ]ęaËÉķ.ŸŸŖÁ”9ZeHĨĐšAįYfRA&(‘Č(‘BŖEˆČ——G_1úģÁK$2ƈD …>D^ū¤ųī—güßGO8[ĀĶL ¨ Ŋn ë�sEŋėEąŲĮ˙Īû“$I’$É×G ]’$I’ä–Œĸ;퍘!ˇÛ ޝ$ë_@ØhĸDí*ĸƒēŲŌÜF$€äUI‘•ÜÛŗøą#`Q’›c?>â܀ĩ-mģaŊZq~~ÎújIųŽawoFāf0Žser‚lÜH{}Éîf9Ą$š1Öā†× Lō **ž­ŊâėéÜÖņÆ[oã¨Ļ$DÉrŊd,ÖyœxįqƒĨo;Œ1(Ĩ@(Œ1Ą ŧø{|QB ĐD�ųbÄnD°z< š…’ņÅZiÍoĒ{L뜕=âiXĶ[ü0āĪof¸ˆŠJ{¤‘wÎéŠåĢ+„$I’$I^ēē$I’$É-#Ž$ⴂA!”Ä8A~c(ÄĮqDøĒßCK…’ŠēŽ9˜Dtû֎õâįŧg!Œ í°éi—+Úå’\œķX×#$xEđīĄëĄˇŽūzMģÚā퀑zŦ)ë› HA8´’Äqd}qÉÉ'OųæwžÉč$įΝX_ļ Ûį<2JŒPhŠnvEĐQ }ĀŽŠ{ļRd$ŲÍk#ž|ą@:ž\žÜd O`‹%ÉPHAÜŧRߨŨ!́Ÿ_ūŒÎžŅöŽjŌ!ôH #ByTn‘“ ķÃđ +!I’$I’—-….I’$IrÛ|*1!G�r€āą3%Q° árb1 LDx (@Pä÷äöszÛCt¨p3Å{Oā=ÖywÂdĻ`1ßC(IÛmA n:gT�į,ÃÖáœįŲņo\ŋƒJ"‘™XĖ&dĨAFËĐwH!ˆ}Ī“?%ށûŋņ&—gK„–äYŽwB$+st¨(ĐâEĮJxŅŗn6 ŖØf-Q€Œ…D" b@’__E0AŌĶŗŒ=Fh rr4ęÅĪž>ßįūü×ųî=Ī“ņ—œö+ÚÁŌmG"ënˆˆLü˙ŧAI’$I’|]¤Đ%I’$InåãMpņĸ—Ã; ÁÜ# ŖF؀Ō˜€’ŽY]r8›cÜ5Û~ "|ĀH"$ā>r3„6 ”Ę˜L2ĒēDDAŋŨbr‰ ÄQ#•fč:ŦŲ^\ņčã_rī7đ ØĄĮnzĘZcÛ™kŒVœ>:b\Žųĩßú­s˜"Ŗ˜–ŒŖcwœŖ8Ƙ›îĄÉL†–ë<:DDˆë&ā„àč(ō0"B4ņĢË%q3—"*9VŒx<==m(4ĩĖ01 ØåĩâGÜ)îņ¨ų1ģ#ēē@DOßC×\L3]’$I’äë,….I’$IrËĶ!ģš $^ö¨lQz„÷xŲŗiF”Žd…ĻĖŽy}÷>eœpy}ŒÄãŧQF#âÍfŸGpá#Zh´ÉŅĘ u‰Đ‚ŽīĐ[EüØģ÷0ŋĒ:Ī÷īĩÖŪûWUŠ*o•>}HzFŠ–NŌÚCB÷!0-i ô@@1Ø*7ĨĨĮVQĄĨiD<Ę` ­ÍÅV´G*‰Īá6‡Ëy†„§'‰Īi’ĶĶŠ8cv[å…ǤĒ~{īĩÖųcíĒ\(ŽII>¯į‰„ü~ŋŊ×Ūĩyä÷áģžßVNĀĮšc“üËāOøŲöA~ūĶáÆwđŗáŸŌ7÷ ŧžī ü˛ģ›žŪN::3:rĮÎą1â„gé �]]Tã%Yž“uvŅŅåŠēJæŧĻϊÎeÍÄĄ@žg˜<ÃR‚˜˛"k:(ĸeŌĻ1΃k†aRĨOŒiŖ‘Á`:ÔXc�6Āôk„Eü]~Ûü~ŖsîIž˙%ÖDŒ‹é#"""rĐRč"""rˆé~ÃNr“)˜đÃÔņŸ°u­V7ÕDÍdû§Ėyũ¯}=tåŋAOöŠz.cŋ üjĮ0žžĀˆąÆek&@Jŧ„�!@Œ‘VG…ë 7Æj ŖccTž…1žgžy†áÁŸŌūå8Eeyãáo&Ë øyÉΟųĪüäįd­Œĸåčp†žînæ˙öŋá¨ãūwlwãã.õ†ąyFÖŅĸŖw>ō*įkĀŲ”žøiYG–Ĩū5.‚‹}M;kS˜‚,BMÅ$cȉ¸X€IáÍÔ8Ŗ.r&Ŋeg5†Šĸ…Ī";tŅ"Įá €ĮĐÃkÍ˙Á‚ĸnģžmũOÆō6““õ{DDDdÖ)t9ÄüŪÛ~ŒÉ#!æTc˙ĘøØĪxíkƒVw›PMĐÕ]đúŽ:ËßføŸãūd'åİ“Ė@*,–Ė:"<žöDņuúemFŅĘČ]Žĩ@ŒäENŦ*†Ÿ~šj|[ÂaŲëyŨ`Ngyf1Æbƒ'ú6Ņ× hYzķ5üæĸ~úŽüMČ “m\î@åkŒ7kČ[=‘Pųԑ%3 ÎĻÆÁ˜HŒį ‘@$s”TdP˜�1bņÁ€‰KÆkŠŧņėøÕĪ)Į'čėé!9ĪPaÉé Å6ÍGĸË,äČĸŸÎp?˙X˙W¨Uę"""r0Sč"""rˆ™;˙Įxī)2‡ĩŽr"§ī vėĀdļ4tŲ˙•røwØūO¯cd`„Ŗ;čœcqŅSG !U¸`ˆŪã}ÄŅ`ŦŖČ:¨cķ>ã &âc sNŨĻ;xM_/ŨäYÚĮęĻBÄā\ÆZ¯ë&ŸÛC×oŧ–VwÎÄäļĘ(ŠY(Dęē"V†PGœËčœcđíŠ#80ր1XkpÎcÚUä€ŧ‚?�� �IDAT>֘hé°Ô!R›€iúŌTÔ!š€5‡ÁŅ65Eč bąYFgOÕø$厝sē°yN0q<•iŅEA<Äķ[+yŨoÁ–Ÿ˙đ€> """2ģ爈ˆbÚíš"wäÆ`L`ŧŽąuŽļ´ō^˛ōwøÅ‹§ˇf<3<Ęäø3¸ŒËˆuH!D4ŌpŸhĀBŒĀŲ 2CŪ„.ÖY5Ä@¨!īéÁ9U°fœč –�Ą&d›¸Îlwžˇ Ī<Y{—w‘™clZ‘Pb‚OĄsĶ2øͤ¤,ÃFĀŦ1dYúW .ĪŪSÛ grjüt5 &Ŗ"’Å o".Frcˆ1đŒŲAFAˆžĒĒ€ˆą–É;qEAĢĢãĀGÃ59sLNŪTŊtģEüooxí{DDDdÖ)t9Ä<y–5üü“<ŗÃĶŨšäõūˇāé%ütÛoōŗ˙ņ ;ņ3ÚåxęÍŌՓšäē˜cRÕGŗ=>FB4iB1Xg0dXã06HÛt PW5ufŠ3ËDŦ0ĄĻˆ†ŦČ Ëą­Œĸ•ĶY8:lãlķ‚VGGēŸĻŲ:āĸ#ÖŠ:Å9KŒ‘č.sb K2‡5b¤Ž=EË`2KĖRŒĩ<ÆҤ-D&m8 x bcFmjbŒ8,1V”&"žŦ0•ĮfÚLÆ@Ņ5‡"Ë(ŒŖK~=ŊĻ•Ē^b Ãv ™e ]DDD1ƒ˙cœ9ŨžŪŪ#?ĢÉčä ­ßĻü—ßå'[ēųåOJ9>F(kBđ´^Ķ…Ë ž D@–9L„HHãĸ‰6Ë DČRTA´Í(ę€ÅKhšØf°¸Ė`\Īl1äšŖ( ::;éhĩ†4AČ:œË('Û¸:đЉ.C VlĒR 1@fŌV%k‰6] Ú{ō<Ë!b%!xĸ1„Đl1˛†Ø7)x1´ã$™)Č1ŒMŒ“™B *KŦØq-,†0ŅĻjAé*ZļEf-;ņD-cSk‘ƒ’B‘CLOo&fø9˙öõŋIkr!ŋúīŋÅĶ˙ũÅŋâÛãTŪã}ÄåŽ,ˇPObc žŨcRāC †ØT P™Ht‹%Æ@ęĄkq€ĩ`ÅĨ$c šą8YĖČĸÃĩ,yîčęę¤ĢՅ5–Âr“C°ŲÔ· 9žnF<‡TábĨ6ĻĄäČL ^ŦbĀÄHŦ}ŗvĀ‘ļ$Ų´e)Íu Î8‚ g°ää&§M•¨čß1JnC]–ÄXâ|MĮkzpéū„š2ƒ`"Δ¤JĄŧ[-"""'…."""‡SĒH+˙_Ȟy3˙ōßįņ¯?iĶۉ/ÛÔĩĮûš€Ą( ˆ5>´1€ Ķ֘SķÜĐüÕb1ũÎe†P×DlĒ€‰`2pÆĻĘ<.2cąŅ`ŊÁZ×4ģuttĩčîꤧģ›;ŧ¯É\FžåXãČ ‹1 đuXb¨Sŗ€�Æ9Ŧ18k0|d1ę įr|4c‰œqŠp¨›pÆāņ`K°&H&}ę?YRՑĖZ,2GnõÄ$،VgÎ9ĸIÍyŗ´RÚą&āčPđ"""rĐRč"""rˆ côuŋ÷̎ō“˙ī <ķŗ´wŒ3YNPWe \"ä­ÎYĒĒ$slÄÛ�uMSīBŒqú¸>F"eŪ{HĶ™S¯•,ÃŐ—ĒMœqXÖZ2 ™ÍɋŒŪ×tŌĶÛŜŽNē_3‡ŧģE]ÕØYîpÎ&5ķ5o UYã}$Ërŧ¯°˛<Ã`Rxb!:›v(E‰W;‚ķ¸Ėƒk›íS‘Ą*}ĒŽŒ k1â,@ ŒJī1uEŊŗÍDíiåu ą °-bYQÚ :ē:)l‹�äX2Ō¨k§ĀEDDä ĻĐEDDä“ũrėx+?ŨūZF>J{b'UŲĻŽ*Bđbj.‹‡˛ÂäëRĀC€R—Ų¸gč2ŨÖhbXg "†"&:–Ėr,­,§ŗČéė(čîí`Îk:čėjŅũÚZŊd]EڒäžĒŠ&'0ˆŠKV9Ú>R—Á˛ŦŠĻ‰‘Ŧ#OĢ0) ÉŒ%bRO—,BˆDß.ÍǃPŒÔąÂ0Æâ\:Fđ1Ũ%˛Ž&ėN|9‰Ÿ¨7“-&ʒēŦčęŽXē,ļƒ•‰Íö+›îĨˆˆˆ”爈ˆb˛É7ķ/?ébĮĪI51N]—Ôą‚Xƒ¯Šƒ':C=Æzœąi Q„}JRš‰Eģ/ÖX<S‚!š&ą‘Hå Íä#ãČ2KQätww0§ģEwg=¯m‘wåtuĪĄë5Ũ¯é$ī(h˛}čYüd…÷čĶ´ ›gØvMđš@fŅĻžžŽ´eČƒq–ĖĨëņž&ËŪ{Œ5@ÎB˜ˆžN}aŒ!ؐ&ؐĒ^"”ŪŖ#k9 €÷ø:Pú@ŪŅ"皞,É| ‡ĄŦ&‰y¤e[82jj29j¤+""r°Rč"""rˆŲ1ü:Æ~ą#Ug„ŠXÖDī ž&TOˆ6�mÚĘSĪ–hSoãcĘ^š*’HHS‚bĀ×GęģBLÛu b1X Yn):;čžĶEWwNwg]¯Cįë:iuåtĪéĸčî"›Ķ"ë˧Į@‡Ē†Î¨#ĩo|ĀG0Öb°d@ÕŽ .ĮƒĩК$„ĐŦĮ`,øčąŅcƒ%–5•õdYFŦ=u¨ˆŅã"ī1ÖmÄSSÖ%Y–c2ƒ÷mŒ‹PØ´Ęę*•ÎŦ! _ÕäÍd'›zĐ  Ã*p9¨)t9Äė|f’PWxīŠĒŠĘ×ÔuMíë4iĮœuiQôM5‹%„H4О՚Ōb †][ŒRĄKz!Ķ4ÛMī1Ļū.yFĢŖ ÕŅĸŖĢEgWAŅÛE×ëģéz]79EgN>§“Ŧ3§ČZøPAŠ0Øb^391‘ĒR| sŽÉ°2—MW¯›ēøZgˆBØÜĨ5…UM49žt힒ÜeøvÕ\JÚ2U0Ņcq„Ú36ē#…(šÃû@ŅĘ)M;RM1/+ljIlæ0™ŖČ,EG†5† ‡'¤†7 ^DDDZ ]DDD1ą]ę:5Í­=>bôÄņĻ&–XÅTŲb‡˜ĒD�Ķhh‹K#­!FąŠŨ 1`]Fđž . ÆĮˆŗ–ÜædŽĀåY‘aķœŧģƒbNAžg­lËb[—Ĩ1Ō€qāmE$b|Ā„ˆ‘Ė::ætŅŪ9N–d„ĻLĒ.1ŦāqNJĮûÔÃ%Ļ­R6B¨k|–CđÔ>`s‹ĩ)( ŪãŠSõ1Tã“xåx…#§čę`bĮ8D˛&´ĒË6ėH÷ĪšŒXTT€Á-ōņˆˆˆČ+@Ą‹ˆˆČ!ĻĒjĒvM]Ĩ īkBíŠ}5ŨŸ%DljU\îvõo!5žMۊ CÄN‘Ž!0Õô%í,2Xcą–4:6Ķ€ÄŦqdLĒHq\ vbđÔĻÆ‡TqcB Ö51†4žCå Cgw'd|ĀeÁ¤ĘB$ø€5†ē]ĨĐ́:DČR–Š-He(ąÎB€ ĸ)ɋ‚ĄĄŒÔU7žP{LåŲ96YŗY‡e„0u!ÆHlW”ããØĖŅá: ˜ĻÚå•ũų‹ˆˆČ+GĄ‹ˆˆČ!ĻŽÛøē%>Tøāw…%1ũOí+,†Ė¤1ÉÖf„`0&ŖIۊĻ'ĨĀ$ˆ&iz§ŽbĚÔÆY‹ŗŠ‘.>MCŠĩĄ÷T%UGŽĢ-9R?•āšHŦ<ą]¯qyǐ1Ņâ2KÕNëírTžÆ:Gs)˜hÁBÄÜa°ØĖPã 6bŊŸ=m›ŠL°Æ¤ÉFÎ1YU8ëˆĨ§,kjīĶvŦ*õ¯1ÎPM´Š|I^äøčéėę¤ŊYÄžÂLZ&1Ø^CˇëÆEw�ž�yĨ(t9Ąē"„4é'4€BˆŸ*BŒīk�jlSáb‰Á¤ö#ŅMÚw”‚—Ļĸ%FÂTEJÚQ”F;7MŒk˛TĶlI u ŊŗÄYÃdd-‹ÍÁ•ãšMMtĩDĶ4äuāŒĨÕÕAi&ą.ÃØ4N:sš #‚ŠÂÅ�ø€ąiŅ80!õŖ1ŠŅŽÉėôįđãĄ Bˆ„ÚC´„ēĻjˇņŪSOÖTm¯#™i1”eį"ŽĖčéé!˜Ļ"(Fbí1ĩg|lŒØčČ;ČM÷+û�ˆˆˆČ+FĄ‹ˆˆČ!&„’:TÔĄ&4U.SĄH$Bļ5s=9&:ˆëIIHc ŗÔĩ'„:U­ƒÉ\ 8Œ濨S“‹,Ņzĸ` |¨hĶžŒLŽC^X:rK[œs8g0&õT‰&b3CNFíŽ#ÃԆHj”‹q„āÉcҤ­?ƤiL16ÁG ‹ ą †"웨Ļ1M Ļ*g‰!õމÁPUeĒ⠑rb’ēhOzĸ‡ p.'Ë,ƧûXŚŧhĨ­XÖ6Įķä6'øŠ éą ]DDDV ]DDD1Uđ>%>ÔøđuMđ‚'Ÿļ׸ˆŽÚ×dĄĀ×gX1ā\ęņâCc-}šW ĄįcŖ%šę`ŌöŖ=ĩ78 UÛ39)˛ČdnqšÅĘĒT ¨B…!¤ņĖŅĨíLeĻŐÉōī=Öf`Āx‹ >…(YšČ”ŸH´Š™Ši&EkĀYŒ™ŨŒÃļ†P…*ÅČÄøąŽÄ2R—Ô5Xo‰Y$†šĖ„˜BŠĘר˜‘gÖÚ4šČY éūųøŧ?*y•Sč"""rˆŠëŠ:xŧ÷́ËT3Ũ<Ņú4Úę:âr—Bā›‘ŅŠ4$ÆH]ׄf,4Lĩ͍Xc Á§VˇÆ€ Cšd"Ÿ%‡€`|N{˛d<˜ BŦčęîÄfB…!â,i|Šú$°ÖRļKšŨNŠ75„°ÎâŖkqŠŊ ļ U˛ŦŲ#Æ5uÅæ–X§†ŊŪ×i2Sč—šôgØ96N5Ņfb˛Ä×3l0¸"Ŗōžŧ°ø*ŨS�cŌ^Ģh5¨CË5šHDDä`ĻĐEDDäđMs×469„€bH#ŸąāžnĻ ER¯ Ø4áĮ4-!¤ā‚hšq҆Tī’šįbmęCLÍscjž* 6ŗ€ã UhOæ8g VD.‡ŧĢBÄhut6-W"eUcˆĶŊaLæđÁc­#RpdRÚb˛Ô×Ååo 8‡3i›”ąëÆYL‘ŦĮ„€Šk|UaŦ%F&Bæ¨CĀÃčD›É˛&Ô ä¤íKY–úË.ÃWē]Q¸ŒÎDŒ Ôž$ÖXũۘˆˆČAL˙7/""r¨i‚ī}j$Kęo’úļD‚ņÄ1MX_×øĖamj4ë›i?f¯QĮÆ�Ņ49M�Ķ$#›ß}Ēx hŗ´%)rīˆĄÂ[C{˛&wsBô[ĶU—ĐzøÖe…ĩ–āÁûԜÖׁēĒÉŦIį4`°Ô!¤ßĀĻ‘ÕX‹uL]íMjœkȍIÛĻŦ'•¯1Á¤ 'Fę*Pûš;ĮņMāĸ5_ûtŖŖ,+ē[9!œ¯>§Ž*,‘,ĪhZˤ1ÛQû‹DDDf ]DDD1žŠl‰4[Hķ‡h&Aj–‹ņ] Ibl‚ÆcŒMo5C�Ō4 &—!øēcŪbđ˜Ü@ô„˜ ë!`Šl Öáęôáv3=Š]ˇ1&â'JĻÆRį;'pÎŅQtāĄ,ąŽąEFôgšqÔҧéFƂˈ6’Y0lV|ÚZ­Å8K!…!ÄHOéž™ôTĩ+Ú;'ņ“5ãm|™Ŧ'Č\jöë%VžĒĒ�pĩŖnˇShUäÄą>…\!FöĘ­DDDä ŖĐEDDäC Ä´Å(…ėMj^;]ŲQ{l–ļYk§+3bŒŠâ$xâÔ!Ø5>KķįCSĻ#&@¤&`¨ „hą.pØZdLė ¸ĖŅŅU°ŖŪÁ¸5d.’įyšÔ*đ‘Ŧŗ œ 8ãČ�_û4=ijŌQ3ĒÚL]#ÍčæŠĩY°&#ÄÔ_Æ4#ĻĶ'šJ‰u jORNz|Øņ‹T‡Åš@‘˛]bÃY›z܄æĘĢ@ÖriÚSíņĻ&Ë22—aōŦé‡#"""+…."""‡˜HHĄ1…SAEL“…BĶ`6Ə8o›ž.Y3Č6K ˜ú,Ļif›Îb­!zhFĨęH}`šž/ąŽdšŖ[Glí‰Y†ķ5Ī$g#šËč("Yđį#Ķ4˙u`"žš>)ãŌ¤%O 3‘h}Zc4ĐTęX ÁGB�Ûėû‰ĸ1“VǚĒ]QׁÉņ’āaįÎ bĖ0XZ]-|•‚`BÚBd›ÉK@Y–äŨ Ž*LæČætbLÄŠÖEDDä ĻĐEDDäš@%F°Æm$†`ŌëÍ" RW5žČ§c Ūû´}0ÆĨ6ŋ žhmĒîˆíŽķB ÆŖĄŽ‘ØÁŧ1D›­G†ēŽqS“†l„Ú‘0Y‹0^bŊ§ÕjáŧÅģĒ[0Ōu‰Y „l dŠqMšždŌĩEš-S¤ā%DOđŠ oŒ›A žz˛ĸ*=Uģ¤=Y’šœã5ÎY:˛Œ<˨ëŠĖå` &Ëņ1’9.sTí6yg Ķ8ęĒ,ÉcžzŨˆˆˆČAKĄ‹ˆˆČ!&m Š`MžLÕ Đė6šjđšļáSķØ,ĻēŒ">‚ĩ†Ėf̈́Ŗ€1ĶËDDpÁīšîļ1Ļz›2œLĀ›ˆĖZŧ5i@f™ôWUd•'ŗ†ēp.'ƒ [At–0Á`\l*x"Ū×XgŌ$"<„HYZOMõMĘbĒĒ&ÚÔH8yFYÖė§œđLėŦ0&'kå”e *=]91@°‘,K#ļ]‘gĶy|USŅüÄ$ąŽČ‹â˙ų‹ˆˆČ+GĄ‹ˆˆČ!f×ĜŠ^'Ļ™b7]áš1Ōu]ã}–‚š‰@&ƒĻáŦąMāSõJj¸kĀ€ —™Ļ)oĘØlÆM›ŦĄŽaW8`čé_ZB°´ËšlNAŒēö˜Ė`ąDŸÎmŒĨ ‚Å…TbĀûŠĖæéü¨=Ņ@đ5>Fĸ÷¸hŪSãąiæ5•¯ öL=¨JĄ‚"R9•¯)B$Ī3LŒä65Éķ “Yĸ3)ˆŠÁÔDg1iŸUYžr?xyÅ)t9DĨąĐfW•‡azËPŒfēŦ÷UipÖĨŪ.6’g9&ęXãŒÃ`™ĘHŌÔ"›Â*0‡MísÅšTcKí^Bę…b18CȤÕŅXŽ-ā°Öb¤gĶ6'į,ƒq†@$3ŠWK$|,KŒīšÉ!Pų ë,ĄJ•?>xjŸ&;ÕžJSŒebį;Æ'˜¯ŠÚ!5ũunzú“ËRõLæR(mē™ÁōĖjYĀfŧĮ:C{r‚VgAfķōŗ‘W†B‘CĖԖĸôûXcˆ5Lų‰ĸ ØāđuI'ɜ!ŌLč1&•&bX\ÚNdSx‚M}RŒŠ %xėT…Íôt!›‚“Ūc18Rok-Î:L4é}X"–ÁG¨CĒ–1ŅÁpÖĻ dębĀ„fļą“| ¸´Č{ MŪP|Āׁ@¤jWM—H{t’öx/!ČÚZ™…Îåä™%sYž5AĮd9uíÉō,‡ö>…4Ö`›J _×ĶS•DDDäā¤ĐEDDäsÍWč%ˆˆˆˆÔ2_DDDDDDDd(t™ ]DDDDDDDDfB‘Y ĐEDDiOŨ°‚ū#ąāũë}÷ļ×^Ȃ#Ņ˙‡ÉÆ}=ņāŦ:rũoū÷O˙áfŽųÃEôyąi_Ođ<Úë8ī͋č?ōŲŋŧõ8–˙Ņģ¸čã7ōÍG^đžŧę<z9‹\D˙[/į‘Ŋ–ƒÂ0OÜr!K\D˙‘Ģøëmz="""ŋ^4ŊHDDäUĐ×ßĪÜbęīیŽŒ0<đ#øÜķ5Žé;W~ņ*ŪŊ°÷@.T~ ĩâú_Îm›ĮôRDDD~m)t9dõqę×påÂŊūxtˆ­ãĢ7ŨĖ?äĘwlgāëˇså2/’ >p9}â^ļŒõq܅§ŅžũŦ/ôĒDDD~ũh{‘ˆˆˆėŠwG­ŧ€[žˇ†k–÷Aš•Û>|5÷t{äåiŗņžulé=–ŋøûû¸ũŖĮ2÷@/IDDäהB‘ũdtË:Ž˙į˛â–ĨŪ/o>ŽåīøSŽYŗeŋôFŪôŽųāģXūKR–7/cų_ČĮnŒÁö~8ÁŪZũŧû‹WqJ0ōCžt×Āŗ×´ū;üEŗĻ͚–ūŅģ¸ô†ulÛ}MŖqŅ[_¨_Í0ßũ“tm+oŲũ\mŊ“ŊËßē¤é=ŗ‚•ī˙4ß\?ŧ˙Ž÷ŧ¤ûŋíFVL÷˙eã]—ŗúŽ›žGËßņūúŅĄŊ>ÔæūĻcŸˇf4mßųān×üĢ8īS÷đÔs<L/íų›ęô.ūvp”'nšo]B˙›Īåģ/â–Î_ņIîūŪ­ŧo‰ĒŸDDDžB‘ũ`ø°ōåĢë63Úˇ˜“N'gw8ly˜Û>q+>¸ŽÁ—}ô6Ûî琕īŧšÛÜ ķŽåŒwŊ“3V,Ļ5ô8w_{+Ūņ—<1•(Ŋ'pņĒÃØrĪCėŪ'upͅŦ|ĪÕ|ëÁ­0o)'|§ˇ˜šŖ[šī֏˛ō7˛q*Œč=–ŗ—÷�#ÜŋfķĖį~˜ģ7•Ā[8ce퇪<ņšÕŦ8˙:î~l„Ū%'pÆģN㤅-û{Ž|Ī)Ŧž} ŗ‘9íōrî-€Ņ6Ūp̝zˆÁžÃ9fÅą,™ C›ČįĪ_ÍEkwO8Z´šū:Ŗ÷ré;?Äßn‚šKŽåÄĨ čŨĘŖßž‚U˛Û}mŧôᝠÕ(Z{5—~aíūÅ,[r8Ŋ­ē-ŽZuG)oyAęé"""˛¯ŋÃĨū!Ce'^w_^5éī­Ãņąw~ˆģŧœŨĩ˜ģVĪ{éĮßv'—~îqF8Œ3nž‹ĪŸ¸ÛfŽŅÍ\ķ'įrÛÖođąNæÁĪ,æŋ3ŋD‹VMß­Û|’ŖpD/Đ~Œ/Ũđ8#ô°ėę5{^W{3×ü‡ÕÜļõNŽ_ķîZ=hqĖĒč[w/#ŨÃFsÔ^į~pK(–žÎŠķ›Ë{āj.ēc+eĪŅüÅßŨĘûîēēŅõÉę÷ƒõ×^Áß.[ÃöîMŗŋŧœûßjĨŋŽ=ÄõksŲ÷ŨmíŖ<ōŠÕŧ÷ÛÛyās_ä‰×rĖôeĩ€’§nŋ‘ų+oáąŋZ>Ŋu§Ŋí;\ôÎĢytë\ŗæ,ÖLŨķ—õüMŊcˆ|Ž˙Ú|ū8mŲßTé"""”ë¯fÅĮŌįųĩüS˜ŠWčÆ;ū†õ%ôŦøŸßũ /ĀܸōōˇĶCÉú;žÃ˙¨ģņŽo°Ĩ„žΕ'îõŸw1—}ôz€Ąĩßá‰Ų(ų˜ˇų�åÃĶ…ũœ}Ũ \Ũĩ|vÕ^ARk1gŸž�(Ų¸ūGĶU(­e§q|0ô8w?k‹Ņ0÷¯ŨLIÁQĢŪÖ C|÷Ö2FÁ’^ģGāĐģėĪšrÕaĀVnģkÃūģŪŊėÛũ/9âÜĢöZ{/ĮôNėFįîMģ}¨y[Ų:Ë>ŗ|^)­#Îâ˛Õ‡%›Ö<>]šōrŸŋž`ŒŅ…įsĨ‘YĄĐEDD cddäųÍš đÄú§‚ŖVËL;.z—ĀQ0°™/šÉ�Ol`Ҋ™ßZŌl3ëŸŨve?(š/ōmĻoAkGw2gŽ:#ZéĩŅá!͝QŌ>™rlt×֟ÖRÎXŅ<Í#k÷Úb4ø0wo(ĄXĘŲ+š�`tlXĀ)ËgĒjqԊĨô�#›6ŋŦ@ë…íëũ?œãëö‡z&ũņÛļŒ<ëåbŲ ?CÉŌĸå‹é˜ēŪ}ūŽZ~ôŒŸ‘}§íE"""@ąü6|ũäįũōŲ^{!‹?üø^Õ.# ”lģëj.}`ĻOŽ4U Cl‚—6ęeęø}˟÷Ģëíc~/0ŌTĸėīm6íąĻk}=ģũųčž{ĶÍÜļöqļŒŧ¸yÁĮŦz;ķžũ †\ĮÆ+vm1|đ6=ĮÅņS—9´”<Í}7|„ûīŽmoŪģ•Aāˆ—ve/žŪ˙y,œqGY/sį@ÉđĐ°į›æÎ›7ķ6ąžÃ™laxčŨ×į¯`î<UšˆˆˆĖ…."""û¤Mģ)åÚđCöžGŗ§’Ņ—ŧũgęøSOgŌj^k3ÚnÃ~îęŌ؜ž´ķ˜?Ũ`d×ŧķBn()úŪÂžĖ’ū>æö¤s>x#—ŨŗũŲ[r'Íûˇ =Ä}›>ÉQK�†¸폀Ž×nÕívS%3ÂĻu?œ9tŲíŊŖû˙ŌŲéíĻ�� �IDATįû_<×įZ´ŠÔŋĨŨ~öCŅģGēĩûĮĻ6ĩŽ}}ūZû˙–‰ˆˆČ4…."""û¤EĢéãėo<Ęg—ÍÂņ[@9Æ ßÍS_ŧ[´ž;x™Úl\û$c@ąd)G5‡ßvûuÜ6PÂŧ͏å{×îĒNi Ü 3….,䌕‡sÛ­ÛšífŽ\˛âžÍ@ß œŊlˇõO5Ŗ-ŪÆW˙á?qŌIöũūĪüą6í2Ŋ2ãgƞãdĶ‹˜ zfûų‘}Ąž."""ûd û�Fš™Í}ôĪcpč9Œ18šŪ;˙e Gz^ƒ÷ōĨĩ#@Á1̧ÜļyjCj^Ōŋō=Ī \�ļm™)pI~ !m1ŧ—MĀŧgí6Řw8ķ  |šÁ—Ü gŲĮû_>Í⌺ÂGN[˛æö÷=ëÕáÁ‘™Ãš‘fËUOŗĨi֟?Ų ]DDDöÉ<ŽYvPōÄÚĮ™ųkī�O<°mÃ/g´Ps|ظöɏ?ēū!6–@ßҎ?›šŒnāšū%ëK(œÃe+§÷MŋĨÕ7Câ2ŧŽÛÖ>ģ9ė´#Nā”ĀĐã<°e€û×nã¤U‹÷|_īRŽ[°•û|Ž3ƒ¸˙Ņ-ŧŦ[ûĸėëũāŅ 3„5Ŗ?âŅ€>ÍĐôelķClœášļ=ļ™ X¸¸é_3Ûψˆˆė …."""ûhŅš°Ŧ€ōąëøØšŊ*†yäSâŧKÎcÕ'Öņr 6Žz×9,) |ėFŽy`¯# ?Æ5_x˜1 ž{ĮŧüËØĨ=ĖSkoäŧ˙p!ˇm-ĄįhŽüâ%,š~C/ķ›ęŒmkbBŽá \˙Áė_æ ĖpÍũœēę-Āvšë;ikQ˙ɜądī÷ÍãĖķŪF°éĻËųëM{E Ŗ¸ūÃââķWsŅ]ŗ2ļ Ø×û_˛ņÖëøÁ7i”7ŨĖ#cĀŧˇsÆL‡~Čõ7mŪķYŧ‡ë×l Ž™Ģ=ûψˆˆŧ|ęé"""˛¯æŸÅį˙jĢ?ūCøÄ*–ßž”cöŅjđԆ i˛ĪŧˇņųĢOiƒ‹ĻqŸ˙ĖVâaîžä6.=–Ŗú{`d;OŦ’Ą1č[ūIž|Ū Ŗ‰Ÿ×?øđ*ž(vũI{l„áá‘éŅĐ=ũ§qåÍWqæ{ö9ęÜ Xv×ÕŦß|ĢūxĮ/éKëyôIÚË>Į]—ņąˇ_Įρ¯qéâø•įpŲ‰ģÖ7åé,šáGlúö7�Xxúiģ…:ģôŽŧ–/o:‹îx’ĪŋķDî^z4‹æõÂØ�ũC%ô,ŊŠĪ¯~‰×>ļŽũá†įn"Ûęå¤Ģ×på2öíū÷ŧw/ßĘĮūhˇˇ”E}0ŧåqŲ<BÉaœrųųĶ}rvˇđôĶhŨu.Ë]Ę1 ŖÕŪÎÆĮžd` ŠÅ—ėVuÄė?{ŧ‡K?øŨFt2X pÛ%̏oúy:œ÷~å œ9œTDDäÕIĄ‹ˆˆČ~0åXģādžyĶÜŊa3÷ß3FYôĐ71§Ŧ:‹‹/<™EĪ7úąę?ņ`˙=|éÖīđČĻĮųÁ†ąÔ×Ŗ˙í\ŧú|.^ĩđyĮ]ĪŦdd`+{l* zzgɒŖ9eåYœšō9Ž;˙,nų;¸æ†;šĶÃÜŊĩ ¯1ĮčūėĸåĖg”+?ō—Ūô$[|˜Ö’ŗöüüܡqƒŋd͆x gŦ|ŽĐ¤—㯸‹—‡/Ũq/Olzœû6”Pô1oáÛxīę ¸xÕâ—&”Œ =ũ<¯íąeéeß˙v‹eWÜÎ) ŋČõw<ÎwĄ,z˜ˇøíœũŅOđeĪąōūŗ¸ũī—ōĨ/ü ?xpCc%Eߎ[u>W~ôdöĘĀfũųÛķšļķÔÖ­<ģļhīįŠÍ€v4‰ˆČ!ÎÄãÔßüøĮ?æo|ã\ŽˆˆˆČA`đNVŊí:6oįĢ˙ī8éE~ė‘/ãŊ÷Œąđ#÷ąöĸ—Zš$"""ŌL™Šz爈ˆˆˆˆˆˆĖ….""""""""ŗ@Ą‹ˆˆˆˆˆˆˆČ,Pč""""""""2 ÔHWDDDDDDDdŠ‘ŽˆˆˆˆˆˆˆČ+DĄ‹ˆˆˆˆˆˆˆČ,Pč""""""""2 爈ˆˆˆˆˆˆĖ….""""""""ŗ@Ą‹ˆˆˆˆˆˆˆČ,Pč""""""""2 爈ˆˆˆˆˆˆĖ….""""""""ŗ@Ą‹ˆˆˆˆˆˆˆČ,Pč""""""""2 爈ˆˆˆˆˆˆĖ….""""""""ŗ@Ą‹ˆˆˆˆˆˆˆČ,Pč""""""""2 爈ˆˆˆˆˆˆĖ….""""""""ŗ@Ą‹ˆˆˆˆˆˆˆČ,Pč""""""""2 爈ˆˆˆˆˆˆĖ….""""""""ŗ@Ą‹ˆˆˆˆˆˆˆČ,Pč""""""""2 爈ˆˆˆˆˆˆĖ….""""""""ŗ ›í<ũôĶŗ} ‘—ä°Ã›õsĖzčōJ\„ˆˆˆˆˆˆˆČ¯m/™ ]DDDDDDDDfB‘Y ĐEDDDDDDDd(t™ ]DDDDDDDDfB‘Y ĐEDDDDDDDd(t™ ]DDDDDDDDfB‘Y ĐEDDDDDDDd(t™ ]DDDDDDDDfB‘Y ĐEDDDDDDDd(t™ ]DDDDDDDDfB‘Y ĐEDDDDDDDd(t™ ]DDDDDDDDfBŲ/ųø2ú\D˙˙ Ûôbbíĩ˛āČEôy.ßŪí…Á;Yuä"ú\ÂE°å‰ˆˆČn˛Ŋ�‘—mx3?X{<ø#6 1<2FIAŅĶĮüū~ŽZv§œ~ĮŅ:Đ+=xl{ˆŋ~p€#V\ĀIGčÅė'ã5ũZæ‰ģîe#‹9sõRæ>ī{Û >p#—^õ 6�‹¯bũ>ë>#""ōëKĄ‹ˆˆŧú´‡¸˙†Ëų‹ģžd¤ÜûŒrėi6?ÍĀæĮšûÖYxú'øōgNGŲËžÛvĪ|ūÖ!Nė?‡“öēĄ‹Îģ–¯׆žĖ?@ë{9žīš^UæËe7ßĀ0-æ/9ЋŲMûGÜöš/ōĀüķ9ūųB—ŅÍ|ķS—sÍēí<ëk‘W)….""ōęŌŪÂßž˙<>ģa,ũ}ĪN\u§ŦXĖĸy}´h3:´§6=ġîXĮĻ‘1ļÜsĢļsûßũ9GŊŠŋSxŖ<ĩeč9_ģđNZø .gŋxūkzUiõsˉũzĪļeO=oŠŌfđŅ›ųØ'žÆú(ú߯q<ĖŖ¯ÔEDDfz爈ČĢČ(÷üĸéĀĨoų'¸īŋŦá–+ÎáÔe‹9bū<æĪīgҞ8ķĸkYķ_Öpũɇ0ļųk\úŠĮ=ËÕÛĘú-[ ÂÁxMŋ^†ˇlåyc­ŅuüÅų_cũH ßukŋw-gĖ+^Šå‰ˆˆĖ*U爈ČĢF{ũ\ŗn€bé'¸ëëįđŧ-8ZũœųŪA{d%Wn(ŨüG—s|ī^ĮŪĀwoũ÷=ö$O Ž0V’úÂ,| Į¯<‡÷ŽZĘüŊ+dīdÕÛŽc‡ņŪŋ+— ķÄí_äĢkgãĀceAĪŧ~ŽYņūėC§ŗh¯s>ņņeœ}Ī,ŋM_?ļŦãĢ7ŨÉũ›ļ20R6į?–ŗ/ŧ„÷7īšīÉļĮøæßáž ?bÛ`sŪžyąôXÎ>ī|Î\ō<Ũ0ÚC<ąæNžĩæqžbd zúæąhé œ}ŅųœēpjŅüõŸÂįˇN}°äKŽ"ÕTÍ5˙Īŧ{nj¤ûŪ{Æ`Á‡yāû<ûgĶâ‘ģū†o­}’CŒ¤ÍŧūuÜiŧ÷ÜĶ9ęYËŨÂõt_€%—?šķú|āNŽŋã^žØ’ŽQôÆĸĨ'sņ‡.y ۃ^Ü5M/ũå<#/֋ū9ŧ€ég˛āě7qˉ3œęe</íĩĘâ?LŲķvnû‡/püčfž{ĶßpۃO˛mhŒ˛čĄ¯1'­ž„?[Ŋx×öĄū”—<ŧkĢĐĀ×8åȝĐ÷ŽÛŲđ™ĨģN2īh.žú.;n.0ĘS/ō։ˆˆüēSč"""¯ŖÜĮ›˙bž€?ģú—iķx÷uwpDûpŽ9âŲ_^‡ũ4Ģ?ø÷ 4ß ‹žÃčī…ŅÁ§Øđ0æ›wŧ“[žņiŽßũûhĢEúŽ]Ōā›ī_͕AŅCȮĸclh+ÜqŦaÍž€E­=?cĐcxũ§ščũi EO=EÉØØîåŗgĶÍwķå÷ū2ÜfۚpŪ§f¨Y{OßaôˇJ‡ļŗiŨv6­ģ—o]xˇt)ĪēōÁ‡øØųáîé /č)`ld;ë×}õëîåžĢoįËĢûiŅbn˙[XČĶlÛ:B ôĖ[Āü u8s_LØ0¸ŽKßs9÷íZ,ķú{`tˆĄ­O2´õIîģë;\ü•[šlŲîĢmŅĶüntd„ˇ|„ķžđ#Æ(čéiQ%åČĶlZ÷5.~l3×|īŪũĸĘŧøkzŲĪȋņ’~ûbž—Šĸ“v›ŅÁ‡¸ô=âž!(ŠZ=åØ#[į[W=Î[ngÍgšĪ÷öŗhņ͌le` (účīīŖEÁÜūž]Įī=ĪītæžČlIDDäU%îfûöíQDDä×ŌäƒņCŋˇ0ūĻ…ņđw;ūlķŸīˆ§˙n:æīœzUüū??ŗÛ‹ĪÄžīĒxōÔëīūvüÉîŸ}fM|כÆÃß´8ž|æéņw~˙œøWl‹ĶGxf[üūe'¤õžiiŧđžŨãģryzíߝO˙ũÅņ„ËÖÄ˙öŗÉŠ‹?{âÖøŽŠëũ÷_ˆ˙¸×ԟyä“ņØ7Ĩ׏ũŽwûl:÷˙õŲŗâ[šsŸ{÷ŪwëŠø•S§c˙ūņ+ Æé3˙d}üĘûšĩŊéäø™ģ÷'wÄĶ›kžđūɸˇ˙û˛Ĩés§Ū˙y÷&7ÅĪüûæZ~īŦøWģ/ÆŸyęÛņC˙ŽYĪī]ŋŋĮrˇÅ¯œš>{ô™gÅc÷„xámëãOĻđLüĮo]nîÅŅ—­Ī^ŲķxkÚ§gäŊŧŸÃä}ÄßyĶÂxø›Î‰ˇûŊÚãZö<Ķ>=/÷˙ysž“ãég.o9ķÚøũ§vŨ‡ÉŸ<?3uŋ{VüģŊnz.fxŽgöLüūûšãšŸūYy˔Ѝ§‹ˆˆŧ: læŠĻwn˙’Ŗ÷ÃŲQîŋéf6•@q4Ÿũʧ9uJ˜^ŽXųinšâ-@šáfžēž=ÃqJļl÷}ũV.;ŽW…@o?§^ņINėã‰GˇîųąVS>0ô#—\Ë]u:GM—W´˜ģė>{á‚æÚ7°qp÷oáĢ×ŪËP,ūˇåŦŨ>›Î}Ō7ņŲ=ĀŪt3w[účڛųęÖ8Œ÷~å&>pÜŧé*ŠÖüĨ|ā+ŸãŒ>€í|÷ևöšÎđÚųæ�@§\w+—ív>€Ū…gņ寜“ļöŒ=Ė—î˜šƒęČæ­Ėŋân9o÷­<Ŋ,Z}ļ´hŪķ8ÛöqŊģė¯gä9ŽūŠũöíyŲe;›FŪÎ-_˙ä[žZķ—sŲ§ŅPnåŅÍ/ūˆˆˆė爈ČĢÃđÃ�Ė_ØˇīĮk?É}Ĩ§gÅ9œú[Ræ¯<‡c €î_ģuÆ÷ËÎįâ…3lūč]žf˜ĖØČĐs|i>ŒS?tōŒ!Ō 4[kFŲí…-÷ōƒ€NēđŦįØf5—SĪ}{ú"<ô8÷m™úķQYû8c� Îâė%3Ŧģĩœ‹¯ū0ģü*Ž<ũđūâ ķȚͩ¯ĮŧˇķŪŸcÉÂŗ8{qúíĀŖÍœôĖÅĢfęo3—ŖĻž‰áĄæ9Ųöã3ōl¯āĪaŸž—=-9÷Ž™áGØZ¸”E@ÉāđČŗß ""rˆRč"""¯í6S˙ũŧUė‡šĪģUÎ,Zö–į[p22°uÆ/ôG,]đėž)�ôĐ;uāvɌ˙ũŋX<}ügŸģ§9nI{ˇoššs8Kf {ĻL~š§ļL­|;ë7ĨĻ=ũ žŗ/Î'^ĀÎ;‹3O\ø×öbmgũ˙ĪŪGWYŨûŖÉ h’+Čh!8$Ô¨’ MD„ø+ Vb+āŦÔąUkÕÖņĒĩˇmę<€ˇ‚.ƒĸļ$hI¨%Q!¤˜ ’ۜPČI€ß'� Č!@߯ĩ˛–9įyžŊĪyî{wcŊ’@Zũ[=.‰ūMÁIY1K[ú˛RˇēåwLBc/›='{ŦŸ‘ííŊû°gĪKs ôØJQįÍĪ*ÔÕ9ĶE’¤&Ō•$í ׆QŦƒ=,+Ú|æLbŌŽ+%l)đYžEąíŅņņq´jgŨŒoĖ´QUeĶL‚EüúÔūüēÍį$B]%USnâ÷FåԚęÍí%&%ėđëHLJ�Ē!ÔxÎ6_t >žÕķcÚ!‡ÛN;>#ÛŲ‹÷až—­$´ƒG]’$mĪĐE’´HęK2P,-Y¤īŲõ6ΈˆŲɀ=†˜˜�j}Åøwõô-3 Ä%´D´ØF]uģ×Ä4Օ‰¤]hoËû[ĪėŲ,°Į_õŽiĪg¤ĨkīĨû°GĪ‹$IÚ#†.’¤ũCr*ũã $•…,%Ŋ[FˇbķĖ™:ęvXĄ´Žē-Ŗã}b0ŗ9Hįö?ŋÄweĸDL [ōPzˇûímy?™™+ģ*’ĪČ^ŧ{ôŧH’¤=bMIŌū!&œėÆĩ ĨSxav‰Ąæ=n›x=OĖ(ŲRĖ61ĄqņDˆĒęUá¨ÜRÄ6!ŠvMÚsÉ)M…„Ģ)ĢÜœc’6/…ŠĒŦnŋú'­‰O ším^hļ\§#EōŲ‹÷ažI’´G ]$Iû‰x†^t&á2ž+xũŽû˜ßĻ=t+™~Ķ]ŧ>o\w+¯5m‹“2˜ūÎâ‚E­zĢJ7oל”6`Ÿ]âĶŌÃÛ+SÉÂĸ]Ũ̧/™iáå,Ą’y,n娊wrųU?åō_NŲÃ-˜S؆ö Œ…EÉEZũ÷…™.}FöŪ}ØŗįE’$í CIŌ~#fāOøõØŪá_ĘŪāĸķîäŨŠĖ¨+áĩĢ.äÚŲÕ@€´Ëîā’Ļ5I1œ5"< 8ûeūXŅō%–N}……!€žŒÉMk§O˛‡ŌÎäŦ€O?ÎüVž‚ššˇ}ürö2 7ĪĐÜ �•ŗxrF ƒđē^ô ōfŋ·Ձf!–$¤.ÔÖšņœļšŊ÷y!¯å¤ŦŽh ¯—8z8­ėÎ;øL}Föä>ėĸ=z^ڑģI’ūēH’ö#ņ ũÕŖÜ‡JßāŠīįûˇy-¯ÅK˨¨(cqÁ{üņÁë=ė,nŸŊ€¤Q÷ōÔ é͆Ø1 Ŋú'dÆĄÜ}՝ŧģÕ>Å5,žz —?ZJHņs.ŪG2HáÜ[gũTžÁåį=ŧ}ßgÜɄëŪĸ2¸‚ĨĄ’›Í‰q—¤€ y˙}9ˇÍ(ÛŧėĒŽĸį¯ģš'Ë�zķÃĢGmŲĒ8>Žø�@ˆÅ3ōwöŠÛéX:~ôu\›�ĒyįĻĢųÍÜĘ­fTŊĖ5WŊŪÖ8éL~1Ą•m‰#a‡Ÿ)˛ĪČn߇]ļgĪ˞ÚŧģWåŪ)iü„Íšējjš˙ŠŲ\įĻ†Ēš­ß7ģ‘$íO,¤+IÚŋĤqîso’üā­Üöb>•ĄjŠĻ=KŅ´g[>>Зœî偋Ԏ´&ãŠß–1áēW()}ƒ+FžE\R‰1uÔTŦ ēqܗqOũvø>ą´¨IüÉwđÔ]Õ\tG>ÕÅĪrÅČg $ô&1jĒVlė{\úyņūQ[÷=&_üá^*Îŋ…w*KyũēĶyũē�q†šģ œ|×ĶÜ>°yZ“NÎĀ�s CTĪžĖcn�ä<^ÄS9;ęmWūá!Ę~|=o–-āÉįđ|Bo’ãc¨ĢФ˛ņ‹$Ę¯ŸŊ™!{siŅÎ>S$Ÿ‘ŨŊģķ1÷äyŲCũFd‘đŌTSʓ?“�Š×‘÷öĨôæ˙wįL ļ|rņo9ũ„ßnõŌĀ[ō˜zŅ^ æ$IÚ†.’¤ũP"Coxšy3}ę4Ū™ˇˆÅe•TU  .!ä”t†ŒÅYšÃéŋƒ)ņ'ßˌ?įĩ§§đNA1‹Ë*) …Äõ&-3ƒĶsĪįÜŅi{0Ë Rbč?áiæeŧĮk/MáÍyĨ,­\A%„û>0ƒĄÎįŠÖúž<ŠGūœÎY“į…Š…,,[A0 .Ą/ũ3†sÎå?fLÚļg&qîũQvĶ}üą¨ņø¤Tļe„ž<œū4ƒĶ'ŊĖë3ōYXVIYYˆ@ ¤ôT†Ž>Ÿ‹'dĶo¯×rŲųgŠč3˛[÷awėáķ˛'-gūœ§n rÛĶīQR"—@ŋ´¤}đĪ”$Ií¯ĶĻM›65ũR^^NŸ>}:°;’$I’$IûŸ–2kēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E@T¤XąbE¤›$I’$IÚ%Ŋ{÷Žx]öƇ$I’$IÚ׸ŧH’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$IœĨO&å˜ū¤œņ K[=ĒŽųŋ<šMĮŊûŗLRŽéOęU3ŠČû)ŠĮô'åG/SŅūŨ¸š—…?÷yĶŸGíĒfjã÷;qžßĒ)L8Ļ?)Į]ÆôēöęY%īŪs#Ž~ž‡ŨÉüöētkę*™˙â\tÆŌëOĘ1I=~Ŗ'ŪÂye´ÛG“$ieč"I:āôËĖ"  ô=æWĩvT)y…Õá˙,ËßÁqÅĖ- L|;÷u§ĒĻ1á˜ūäNĒÜÛ-ë�S5éŽy)Ÿ˛` )§24=-˛ĪsM!w˙h4įÜûsKW$ޏ¸Ž dŪ[<đ“\Fülæ~\J’ÔV†.’¤ĪĀá M€p°ŌĘ\ƒĨ…˝„@ �Ąbæļv\I>ķĢRÉÉHŒLw Žd‹÷zĢÚņšOSö…”=7vī‡t;°´¨”0ūQfŧúOũnũ#ÖZĶoēšJCt*ˇŊ’GÉ'\@é?>äÍģF’Q9ķn4P”$Ā ]$I tr2â€ ķ´¸„ĄĒ0Ÿ`ĐčQ$būÜâĢ((¤ u8C“#Ųį–U””ÜûÍjOÅÄttļĶô|'&%ņŪ-}‹'g!0€Ûž}ŒK2“ļ´“Č  ņÔÕŠ@ˆ‚Iī9ÛE’tĀ2t‘$€b”3˜�,œĮÂíŪ¯aūŧb /ƒ& gP ķ[˜QRÃüŲĨ�$efҝ…v Šų/^Oî°Ėp—ãNfÄyˇđZQË3gĒ ĻpÛUãÉ>i`ãņ™d|<×<8“ĨÍSŸĨĪ0ú˜ūä<nŋ莜p6בŠäçļô+õø“=ņ>ūX˛ã*#uķxūŋ/ctĶį9f ÃÆsųOŲÁŦ­-}*7\3äŧ)´zJÉÌ8Ļ?)Į˙”éÍē´kí×1ũĒp}’–—_ÕņîßoŪéđ÷rRKuNĘxâûũˇŽëĶ\Í4.:Ž?)Įũ”wëvPĶĨĻ„é^Ī„īŸÜXߤc}“;y~ne+õM@ysyŗē(ßŋŒÛĻ–´Š&JÕÔ I9Ļ?O Įw%îßņˇđaķãŠĻpwãŗ™ŌølfŸq7ž8Ší*æîaũI9f<ĪWÔ0˙ŠÆZ1Į]ČĢ ĒxqqÄeŽã‡Û˙Á ßɃÃË�+K ]$I,CIŌ)>c8ƒ@u>sKļyŗŽ˜ŧ‚ÄĨ“™6€Ė4 2ŸųÛVĶ­[@^IH`舴퉊áßMāĸ‹!e0§Č"-ž†˛Âˇ¸ũŧËx~›ëULŊŒŅįßÅëŗK!)ƒĶFÉé'§“XSĘ;OßĀč=ĖÂÍĶŌ9į˛ķÉI ˙šq6_x>#*­�� �IDATWäώaÉJ͝šĀÅÍĸ¨*Žū#Î䇪ŗHŽ™Åm?ēˆß´<T¯+z˜ ?¸œ_OΧ"&•ĶƞÍ9cŗčŗŒŧÉwqÎ÷/äų’ķûĮĀ�„ §0Ŋ•ŅôâīQÄeã´øömˇôËbHPŊˆ…Û>U ˜[ūĪPŅöá\]Q> CČΐÖϐÔrÛŲ¸öéY,ŦëÍ grÖØ‘ IŠyođëįrҤ˛O­šz5š?™ÂԘT†Ž8•Ė”ĒËōyũĻ‹¸|ęΓ°ø´3šâÂķÉI �~føYēlxcXĮŌI—1úėģxav)$eqÖøŗ9kD:1•ųŧyī匸Ņ}Ėß*A 4Næ Q9ã.Žy¨ē”t2ö%>sŸĻøãŠw´ÄĒĻ.ÅĮE~æ$I$ĒŖ; IRD$f“Å+øpnŋHKŲō^É{, B ;‹A1‰$g÷…ÂeäVqeŋfu[ŠÂĮ7˜œ-´Qō,ŋO:“įÜːĻĶęJøÍ&đdé"^˜\Ė%ˇĻ7ž>ß?˜O5qdŪ5•I’ļ\§Ž˜ģ0J_æ7SĪgŌ„DˆĪāÜŌI,›B^YˆäŅ—r{ķsv ŽāˇÜ=ģ¸ņ_âĘ´Ļ!mK']̈́;ō[8Иģ¯{–ĸ`€´ ŸfŌ­ÍËUŧûË \1mŋūåË }ûŌfũ4“|*įdŪGŅŧR^Ÿ\Â%7lX•đæŒe@c&d„ÜíŲūnI#'#Ļ•2ˇxëį Ļp ‰#3;‰‚yÅĖ-!Í>ŌâyفŲá>ˇ4—¨jÆãŧ^".ûf<7Žæ+ÕjŠfÂyĪRđāã|˜ûC›'ĄüūŅTŽøSWĻ5}#5Ė˙ī\Ιŧ‚š/žÅŌÜ1icųÅ­đá/ß"¯,DâˆsûåÍū<,}™kîɧšŪœõø$Čiög Ļ˜ģĪģJ_áÆG1ûWéIS'+™>†>›Į'īJÍŖ>œô>Õ@BFVkËH’Ôąœé"I:@%1tD_�–Î[°Õ2—ĨŗP ôo܍¨_fx™ÃâŲ ļ0/ž]H5á ƒZú§ø`?üŸf @LįäĻPYRÖėz)œs˙ƒüæū{ųuî6áIL:ጠ׎XX°hˇŅ­cūÔ÷ƒŲŅ?ᒴæĄß„›¸8ĩ…ŗæžĖôJ aˇßąÍė„DNģá'œ�J§đzŅÎúČi㇔Íxkûå]Eoņn%4’ŗ2c"Đūî4b0qĀâšÍīA‹į."HåôņHbķ‹š?MeĖ/Xô%'ģõPŦĸ,ŧŧ)1m�ۖŠøžzõEŪyãĻfĘÔŅīę{›.�ņ ™0Š€˛Ō­—Ĩ톅/ŊBIâFüœÛsļ NâĶųÅ á{Y9c ķ›ĩ�R“öcnßÅĀeáSWsÍĖjˆËâWg;ĶE’tĀ2t‘$°šļŽÍkļ4ĸ’ –}ˇėF”Žë*z¯Ų ˛˛q0`ȈŦ–—H$e‘ĶÂǪĤ�uÁ-ĄKLƒNÅs‡Ķ/<ĩƒšĒJ**Â?5á3kö0tŠdqIx‹ëū'§ˇ0˜Mahfīí^]<oA .Ŗ•€)1ƒ“Ķ�V°¸¤ KZNĮi @å,^ßf9ĶÂīQ ¤ŒĮ ĩŋ;b2ŗ€`qaŗ%DeĖ-Ŧ†ä,†dfĶ?îëæûZU^z””ŐL7INK"�”Mũ-Ol[ŋ%†äôī—Øâũ:ŊĨ0'!‰D€Pē=z`ʘ_Ū:Ŋ+ĪyĖĀÆĨzÁb ZX5({ļR¯)æų‰šœõĐ‚I§r÷ĢōÃ(P-IŌŪbč"I:p5mZ@^QãČ´Ļ0\ã%iđ–ArL:9\ĀÜĻYU…ä…ˇ-"'Ŗ•!eĶĀw›ÎÛ†kJøã=?eôII9fŋ—Ãɧ†ÎzhŅî~ĘmTSV OrBËũNLŲv_GEe8FHLîŨĘŦƒ’ÃÁPE¸‹ÉāâŅŊjŪ”ßėĢ(æŲ+€TÎß´Ä%íīŽøÆ`§rÁ–ú> ˜_ éô‹ĀɡŽëRW”Īâ$d ßá™ÄŅwp{vTįķĀų9¤Ÿ”ËE?ģįg˛tGĩ $î$ŅØŗŽšđ$œ’“Zi(>äøđąUÛå]“Ú8ËĨjˇ}!ŋžˇ‚¤ė›xķOqnšs\$I6CIŌŦiëčāæ]ˆę ÂEOãŌ›’㔝 T3^¸ęnMŅ{, ŠÃŌ˙_WČŨgOā/ŊĪRR9벛¸ûūyōņGyōņGųÍØžíĐ@u5°ĨĐéöbļ{ŖŽēēPøŊ¸ÖÁ1ďŽkÛ&ÖũĮ# ΞÆģMÁBŅ4Ū­„@Æ8Ælū^#ĶūŽKbHf_ Œ‚âp‡k ķYL€AŲé@"ƒö…ęâÍřÃ3tâ4ĸĨYEÍģŸÂšĪŊÃ;ŋũ1§gô&Ļē”š3_á××]DÎ ™ŒūŲË,lmODs‰ēƙ2­?/Ķø^5ÛM̉ic÷ĒøãuWķzYˆ¤ą2õš Ôæé1’$íŋ ]$I°-[GW˛X8oA ĘŲzœœ™A PV°€ ęX<{! åä–ļŠŪuK_ŧŸĘBt&Oũy2ÜpįæŽâ´œáœ–3œĄÚĄØ2@ĩēė¤ŽnÛŠ1Äćē`kķ&ęhĖEZmZŅoįd ”Ī›ŗĢכyŸJ ™0˛Ų,ĄĩĪŽĪé?b0I„X8ˇ˜:ęX8ˇ˜éœ<0Üf˙ėt6×ui\šH''ŗ-}Ч˙čŸķČĢŗ)ūGī<{7ŽĪ"%.HÉĖû™0ņaGhsĻÖ5=/;ZĻÔĖÄėŌwŋ•ŠYŧ^‚¸SšũÖá-Γ$é@dč"I: mŪ:ē,ŸųUe,,ĒĻÅ%CiÃ’�”˛°ĻŒšÅÕ@CZÚ*z—Õą¸0\ #eôų má_ø—–,k‡v�âHL¨ĄĸēåQôŌ’ÛŧCrR8ôЍXŅJPQMEE8õHNŲž&L˒3!ƒ�!æOÍ§ĒŽ˜7g‡‹§ž“ŨüKh‡ö[<КĒĒPûÚ¨ąžO°8ŸÅuĨĖ-BJ֖bÉiY[ęēT-`niøœ!ģ:k#&‰ū'åĘ_=Íė??Čé *Â ­lį9 „W›ЍleĒMM%5ác“ÛļV ’Čšđ|.žá‚]˙Ž$IڏēH’l[GC)ķ„ ĻßzĮ!�R99#B‹(˜ˇˆ…e@\˅rwŨ–tLKuVĒfōŒÔ)iķX<…Šq@ˆÅs‹[8­˜ŧyÛˇÕ?;8 TøŪV;Ôléc~c›ž Ęhû<…øã8-BEo1}ęŪ­†„ãļ žv¯ũâá™U•-|Uų䕴šĢ—L''3�•Å,,(ÜRĪeķJįä4˛°(\p7mÄāív$ÚZ ‹įNãĩIķh1ÖHĖ"'-�ÔQÕęLŸHIaHcaå…3´¸ŨuMAãRģ„ÁœŧģĶž’‡så­7sû„mwĻ’$éĀfč"I:Ā%1tD*dá‹SX‚¤ĖŒ– Å0äätT3˙Å),&ŧUôö[øîŽx’SÂ39–ÎxŠæoUō›ĢĻ"%5ŧQeuŗų–:&Um.ÐŅYÄÕ3æų’æƒø>xŦ lVæü0¨žÉo,Üfđ]Éô;§ ŒsņŽ ŧc˛8gt„đ›{Ū#HoN›ą]ŨmŋßĀp-œĘŲSø°ųI5%<Ũã,ŽŲūŗîX<C˛ĶRō^zĨ›ëš4IbĐĀŪPŊ€&¤7C2SZģXŖjŪš÷VnŋãVnœTļ}Vņo…€$ú§ėũHbĐø €Đŧ‡š;o›X¨jw?ô>A¤]xCvˇ‘ŠBĻOÆôŧ▃'I’PQŨI’"­_æ`’(Ĩ˛´ˆã´ėԏ‹ĪČĸ?ų—N1¸Ũj˜ēđR2'ŨEAņũäžQŽáRŊŒųsP—y“n rãČû)*{–k~VÉĐŅđ‹œú L…™‹¨œ|5ŖËR‰'•k_ũųŋņ9?įųÜ^¸ˆ~”Ã;ĸǤ˜ĸĒžÜxuŋč}B„ļ�1éüâˇ7ąøŧû)xé"˛įe14Ŗ7ņuÕ,.˧¨2D éLšė.ÖãˆaȄ3I™ü,eĄ¤Œâœ-|ĢģŲ~ōč Čyôō*ßââaÅdfö%žŽšĨE‹¨Jģ‰ÛGŋĖ/&¯ØĨâ.‰Y¤ą€‚y‹€Á›ëš4韝NÜKŗ˜;H8ŗ ŗĄR¸âŽķywâ+ĖŊãt2^Ā Ô$c Ļr ‹JŠHW´ËĖĒ]ÔīøU!nzŸ7r: 3˛”~> P„„ė›yäĸ…K­Ģ+~–oĘ'”p6¯į¤[ĶE’ôÙ.’¤_ĶÖŅ�íŅ›%fČæqe*9Ųí8ë yOŊzgeôĨŽė}ۜü–ÁĢŸbęscé×īLnŋ~0I %ŗßg~e8%HžpwęK\ š’ĸâo/ŧYį>7‰'/;•‰u,-|Ÿŧ‚RęRÎä7˙û4WL/ņØ&ˆˆIģ€I~‘ģĮgҝޘw'ŋÁë3P“Îé×?ƌ?ŨËiģŗ“SڙœÕ˜sĨ=ŗÕ­•wĢũÄQ<ōę=œ“Ũ—*)˜Īü˛ũ&„wČéž×u­W‰Ũ^ŋ,†4Õ.IÍÚn)ZĖĀŦp .c8ƒÚĖÅgŪˌ?=ʍcŗHŽ[ÆüŲŗxsÚ,>,Š!qā™ÜøøTfüĒã–ŪôË}ŒŲoÜÃÅ#úBY>Ķ'ŋÁô‚eĤŒäŠûßdösãčįîΒ$í˛N›6mÚÔôKyy9}úôéĀîH’¤N]!ˇ}˙"^¯ĖŨs^â\§9H’¤PK™Š3]$IRDUÍxœ?VB܈ cā"I’ūƒēH’¤ˆŠ)z†ËīY@(ĘWwįI’ôÅBē’$Š•đÚ/Ÿene)ķ —$ށˇ<Č%ģģŨ°$IŌ~ʙ.’$Š)š7‹ŧÂJHĖ9÷ObŌE)íļ”$IŌūÂBē’$I’$I{ČBē’$I’$I{‰Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRDmû§Ÿ~Úũ$I’$IÚoÅÄÄl÷ÚvĄËˇžõ­ŊŌI’$I’¤Åʕ+ˇ{ÍåE’$I’$I`č"I’$I’†.’$I’$I`č"I’$I’†.’$I’$I`č"I’$I’†.’$I’$I`č"I’$I’†.’$I’$I`č"I’$I’†.’$I’$IÕҐ$i_ĩđëō‹Âß°&ôlØ´ąŖģŖxįáŽîÁ˜N8¨ë‘pßmD§ĶŅŊ‘$iŸåLI’Z°đërÉŧ›ŠŽ[ÆM:ē;Ō>ĨĶÆløz ĢŽ¸‘†Ī—utw$IÚgēH’Ô‚ ˙§Ųo:ŦŌžhS§NtęԉM7R}ķŨŨI’öY†.’$ĩāëēoĖZ¤8ØPŊēŖģ!IŌ>ËĐE’¤ÖlęčHûļM:ÁFëI’ÔCI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’ÚÅĄ ô{ææNfRæhŌ;wt$I’ÔŅ ]$IjéŒ?*Ä@7öŊ–ÉŖžåĄcĐŅŨŌéÔ­Ģ˙ŗ$I’v[TGw@’¤E`Ģ_ŽdÔwdøQáááåUkvķLJ’zä8.=æ$wíIâÁPŗö +Ļōė'yסCĮw$îfŒ>ÂŧņÜõu„ÛÚÛ=ˆŊîē–Nt\ãku°"û^Öp‰˙{/‡|ō{*oœA}¨-&ö7ĶčŅû–Ÿ7…HßI’´oķo$IŠ ˜øīqķŠ¯0#kY‡FīâŲ]~âL>étúŽ˙+ÜÉĨÜË}˙ú‚¤ž72ųûˇ0ŧŊ—1ōcę¨+Hoú}ũ<\đo­mįv:\4¯{€g5 \ļā �|âĩ$=0šč@kĮíīđÖīų¯^ŨI’<Ît‘$)â¤$Oä™^Ŗ™÷É#ÜųéGŦظķŗúŪĀŊߊĨpÁĩ\ļô‹-oŦü+o--æé˙w-w z—yûˆ6MÂhƒ¸ŽH9¸Y[õeŧˇŦŦŽž/I#ĀržšäZžūdĮŠR8xafŧėGzõ#Ļ;Îʑ$) ]$IÚ[îIvúŊĖ<j/˙ũĢüjaÉœyĖ`â̟āÎæK“ĩ3¸%¯֖mžF\÷ąÜ<h,Ãģö$žZĘžū+/˙ũ Ļ|ú=Āėãžāúü/uÂX˛âģú‚÷>y€›—–‘”ö,3Ķ3y|.E ÎgÂĒŅ͖E3<kņ{ÆWĻsĮq'Ō9ÖķĖG÷ōėĒĩĀĄŒ:…ûx€ė?$ØØˇĀ‘÷PxÜųæ­ŧUMߤ‰Üœ~ ņŨ ´œâå¯pįß?dYŠ=ËÁ î Ö}˛ˆ&zčiŌĢŲt–@Ņ1[~m×ā%ЃC~rMxiSwØôõŦį9Ē˙ˆ†ÆC:n,‡ß4Ž.ߊeĶ—ÅÔ<ü .}Ã>ģ…ũĪĸđAŨNā°›&üDĮACy1ĩĪ<Éęŧ/ØpôųÚIŦ=÷žūŧŠņēŊņ$ąģ†åžÆˇž<( ķŸŪĨÛ÷Pū‹ÃįJ’¤=æō"I’ö˛˜CsiösĖz Ã[[rJvW(Šüˆ­\§:XFuS@7–§OHzđ.ûķ8˛˙|'/¯Ā§Ū™‡†ŠÛ‚ĀIü<-Āä/ ķĮ2ļ$ÄđÁ×2ūPXöéĩ\°d%Ŧ}‡ Ū˅˾ÚĻÅzę6@L÷ķųųapũŒņdž9‘ûjŌ¸>ķ|Rwá;ˆKē‘—˛O#ŽâÆO?Ÿąų͍éu#/ežDĢĢ}"j0Ũ}- ?ŋrËĪOOaÛEíŗÔčPēÜô�ŊNđīģŽá‹īOdÅũÅœ{/ŊŽKĨ@ėI$üūJbžžÆ—įM¤ōWpđĨ7đ_Ŋ›‡BŠt{ōNēö.aõĩ)˙Á•Ŧzbīy€ÄĄ‡ļ­+‚Š?`KųúÜąüë6I’Ú“Ą‹$I"@R¯ņ<vęÄ-õSšë| „¨\ģ˛MWK?j,Cpį‚<Šƒk¨.bĘGĪ1ûāÁ\p䛏‹98Äŧ’),XPΞåīRB?Ō‹†k †�B„Ö¯%Ôڌ“ƒņrq㊍_1sy1u‡ö#ĩÍõez06íâW=ÁՋūJéÚ¯Xļjˇü}1ÉãÛÆŧ Ŗ|âĩôžé„p8˛;ēÄa9=Y˙˃Ŧž_FÃę¯}ø$US—8=—.8čč[Ę˙=<…õ˙úŠúOō¨ē˙#6ußr™NßÍ%žĪJūīŽG¨ũä+6|ųë^~5˙čÆĄãOâā6uĻž u! ÄĻÚĩl<ЖNI’ÔÁ ]$IÚ'­ĨˆŲéq�]IíÚž)Ļ´yPR_Fq ¤t?bˌ _PÜ|#Ĩ !‚@\ÛFča5K)kÖN(TOâÛzƒú1¸+”}YJuŗ—ƒ_DéÁũ|ØŽŪÛVS÷ˇ/vFHŸ41+Y˙ÉÖ3‰ę?.cc\ {AÔ1GpPp)ë˙Õė€Ī˙Ęēf;HEŸÂÁÁ2Ö5?†5Ô}˛’ƒú¤ą¯‹’$ũ'°Ļ‹$I"DåĒŠÜWđ Å-ŊŊ~%Õ$v,ßY!ÛXâĸĄ.jļ¨%X1Ú,ŧiŠ Ą=ģĘÁ‡s0¤Ĩ?GI Ķ| ;ĮģģÅöîZMÃ×@īˇöŽkY™ˇíŌĢ]ËA„ØÜúåMĄZ6 StŠ…`í6ÁN-›Ķ)6�ĄZ6…ļŊNbcw&Ž$Ij7†.’$íeukÛPHˇžŒÂ¸>ųR•QÚÂ! Iãĩá&¯Ü:\ŲrͭØļ͚‰Œ˜æíoXKp”|z×/Û6\ \ŋˇ€RĒĪ<¯Íæ‡NĸĮŦ[9tsĮ—{¸�ÃáĘÁÛ¯éËAŦeC6ÕÖBL`›ā$–ƒšŗą6X:Ø*Kë€Úm›æás$IRÄšŧH’¤ŊeÃJæߨˇōđw.ø‚ˇ>[@M|.÷HŨŽ k n4÷fNäĮ} †5”ŽY ‡Ĩ“Ūüoöč2âĄdÕŌvÛRzįB„6@ĖÁą[…<)ŨˇÔ•aãRŠ×„H:4–Ęā,ÛüŗšēĢŠŪ+{ײ!ÄA—ãļ‘ŲĒßōSÛŧ#í¸�|VB¨Ž'ëąÕËŅßMá āR꾄†ō¯ØØŊ1Ŋšpô tiVĶĨáã26ÄĨĐå[Í¯ŌƒÎĮôdãg%á{ZÃĻÆŲ3›ÅA ;Û0…‘$) ]$IЏ•Īqéˉ\VŌX€ļ Ē—=ČõËV’Ō˙AfŊ‚ Ž<‰Ŧž'qæ€[˜œs-k§rũGŠ?›BaānœCúĄ]Iˆˉd‡ūÂËËÛÔÕ¯…Îi >üúvŪĒ õ”}ũt?…Qqáķŋ„Ÿô †ĻcžbZÉ_ŠKž‚‡Ž@ßÎ]é7€q'ūž™#n$k¯#)!øîJāH{~ũ ß ˙Ėģ….ÛģKėtr]ļú@ PûWÖŧŗ’ΗŪĀaCŽ Ē[C¯āđܞÔMĘúlüÛŦ¯Kã°ëFĶĢ+QĮåxĶ`hVĶeĶß'SSŪ“ø›ŽāŖ{ppˇ#črÁ tũÎJ‚/}ĀF€/—RėÆ!§Ÿ.Ŧ8‚ØŸŸN y Wb#=8äÄTĸŋÕÕeI’$ĩ#—I’Au5áááåUģŗdf ųW2ļâ|~žv ?ÉĖ%ūā5k—’ŋäNŽ˙ä¯,k pÖÎā§ī¸sĐų<=ęFâYMŲĒŋōķ÷Ÿ`ÖúlųTfu-ןúŖ_Cî˛]īõ˛Oāžîˇđ“īOãįVSöå4î*ūˆô“ļĖėV>Ā…šų¸[˜öŨnÄlXMŲǏųũįČß+3]ęY˙Û[ųŠkčvZ:Ņ-îSũk?\@ǏPŊĢ5\zŸÆáœļŨË˙žu,_æ­eũooäËĐ5tŋã ēw‡ +–ōīgnáë—ËÂ˂VįąęÆ~ô¸i"Ŋ˙t%?[Ā˙=ünzØĻyKĄ2V_q'ošH⓹DŅhøl57>Čęŋ7~‰ĄøúWīĐãē[8rlür)ĩ?Į7ßē—˙jęÔgīđŸDˇ›!éãßSqå víĶJ’¤VtÚ´iĶæ%ŋåååôėŲŗ#û#IŌ>aĐ[gėâ'ņЏîdTĶ>–3ŗøîûlŅV;ô(2Ūy¸Ŗ{Đū:ÅJ§Pŗmœ'8ë^ΜOåävXæÔŽ’æžŨŅ]$ŠÃ­\š’>}úlõš3]$IjÅLū´„ÁGõ ˛ōî/žAņ.Ė0‘ļ;”ē•Āß~ĪĒg>ĸŽtšp"ąbžūpß \$IRë ]$IjkY°čZN^ÔŅũĐĄöCV]ۍ„ëÆŅķÕk9˜Zę?ûˆ5×>A͗Ũ9I’ÔV†.’$Iû ŸLcÕÄiŨ I’´ÜŊH’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$IjM§Žî€´oë´iäI’Zcč"IR ēĮ›6ut7¤}ÚFāā„îŨ I’öY†.’$ĩ⁌_˛eĒ‹á‹Ô\§M›čtęԉ„ûoīčîH’´Ī2t‘$Šƒē›įŗīŖ{ĖaĖÁŨiŸ˛é ƒčÔõŋ8üЉ:ĒoGwG’¤}V§M›ļĖ.//§gĪžŲI’$I’¤ũÎʕ+éͧĪV¯9ĶE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$)ĸ"ŨĀ—_~é&$I’$I’vI¯^Ŋ"ŪFÄC—Ŋņ!$I’$I’ö5./’$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CõJŪ��>IDATI’$I’¤0t‘$I’$IŠ€¨Žî€$I͕-¯āÕÉo˛ößëØ¸qcGwGŌ>ŽS§NÄŌ…‹ÎĮ‘IŊ:ē;’$mř.’¤}FŲō žxöe‚ĩk \$ĩÉĻM›ÖŽåą§^dÅĘUŨI’ļbč"IÚgŧ2é͎ũQ§NlÚ´‘į_ŌŅ=‘$i+†.’¤}FíÚĩŨIûĢN¨ Övt/$IڊĄ‹$I’›6męč.H’´CI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ€¨Žî€$Ií)î˜,ÎĘLZīŽD¯¯âķOæņöô"*:ēg’$IúOcč"I:`ÄĨOāĻs͈oz!:‘ū'ærôˇyčą<ƒ—J,G2’ŗ˛SIîÜĀ×+Jɛ:‹ŋ|šžŖ;&I’´™Ą‹$éĀՇ3Îh¸4Ũ{ã†,āáüovķâ}8÷ŽKŌe sû-S—ˇĐüˇÄ=~‡Cūõ6ˇ<ūÁŨlImEŸ1sUv"Ņ�DĶũ[ƒ˙Ķĸ÷ TíæUģö!ë”l2MĻGˇ.°.ČęK˜?g|ö M™]Ú9ˇsU×YŪgI’´S†.’¤CbGĮĩöf4ÉĮ&Ķ%˙ÖíAõõą 8žo//gëI3püQD××īÁÕÕĸ¨ž|īœqÆąą?Íã…×?ĸ2ę8Fi \š‰>‚‘ÃŽ"˙%4´tŪf:EõĘâš+N#šasf1sU- ]0äDFũøJĖ|G>X‰“Ĩ$IŌŽ°Ž$ImROÅō âÄŅÛū“EįT2û­§bÅzŒ]ÚW\úHÎ:.‘CĸģĐ㸑œ• ]éž]âvH×ÃčŌÚy­‰JæŒsO#9¸€?<đSō‹øĮgK()žĪ”§žāĢĨī¨ąd%Fä#J’¤˜3]$I†Ē>žH÷gģÔSņiÅÍrX÷éį”™MæąQ”üs˜‡¸oâč†r>¨:ŠäæķØ>ŒČÍ!Ģ_Oēw‰æßÁ/)™7‹)”7ö%™qˇ_L9/0§k?ؓnĄfyS^›EImø2]Ž<Ü1'2 w"‡D×Sŗĸœ‚™ĶxûŗÚÍMu9rįåf“–Ø™ú5åüåí9Ôä\LΊW¸uZyø˜^9ãŒlž{d"‡ä̞"ۜšGɚ=üb"ŠĨp%XÅ×õĐŖ…÷ūŊ&<›ŠK+ĄLKĸŽ=‘ĖÃ|üė,–lWf=KĻŋÂũųëŠlžlŠ!ŠîßÉÅcrtˇ(ūŊj yoLcöōÆ ´ÛŊßų}Ĩs2§äŽä”c{ŌŊ ÔŦ(%īíY|PVKˎâÜģ. GŪÃ[-š;ęė›šļ÷îøŨ|Vq)Yœ52\”úÖņõŠ%äΚÅėÍ׍%í”1œ‘Ũ‡qQÔ¯^ÉĸĻ3uūĘÆĪØ‡qˇŸO9“XtėÎHŠfĘŨ¯P`ŲIŌCIԁĄĄœˇß.Ą˙šÛ×uŠ_1ŸŠķ›×sI sė0ÄÁ˛üéĖ.kã(0XJAŲ0rOĨË??iXƀ!ÉÔ|2‡Ī9ŠS6Kæ¸ķųA×Oxõši|„ø#ŗ9īė œˇæˇ<Snŗž>š¤a#đö4îŸ^MCl*įūô.õ9ˇžą„†ÎЌ›xG/É3•˛ēĄ3ICÆpņ…øúÁgøË s*ã&ŽĸĪōwųÃkŸPĶĨ9cÆōŨnҰĸą;]rŅš$/—÷ _‘ĀwsĮréÄ(ųŨ,Ę[X73*g#s†ĩøUĖʛÃĖŧ9mûŪö@đŖYŧyĖ–eBo×BÃ'˚ŸÍŅŲÛ,1Ē˙‚Ys–ĐĐÚy­H>&™C֕ŗ¨Ŧ•ÅC ßl¸�>˜Ü! É{é&“@Ö¸ņƒs†ąäūY”ˇëŊßÉ}%W\ˍ¨RŪ|i%kĸHĘÃyĪ'ęwO0{7ëÛĐų8Æ_8ŒnE˙Ë#¯¯d]T,}˛ÆpŪÄąī{…‚ÚÆē:CČãžY^K|Ę0ÎËŊ˜‹ęŸā‰ŋ¨¯Ļېa¤}4‹GfVķÕ6Ôö…gL’¤H2t‘$0‚Å“¸]gjÜ2z]åEķxsVã–ŅQąÄEÕŌĐ5•īđúFCRÃ?øûō%ÔwŽ"XģŗđĨ–Å•3îėA čüIø_ėģĻ’Ųģ–EĶ+¨?žųąëY4õ îæV­ æW¯™C~Ö F› ÅK6ŊbS‹ĢÃõBjK)( 2¤wOēą„UëËyûą?@p%Ģģˇ:īo|>d,ŽėĖ_ÖŦ§ËąƒĐåKfžĪ’*€"^›Ú‹ÛŽÛJ$ ÉĻ?˙āé×ō)Yđ ŗ_›C˙›G’ķí9›ƒ€æšŧÛŠ÷ę`¸a%yųQū˛õ‹”O?ĮpVöQ$Į5đõŋw/ĒÚŅy-‰"..‚ß°zW ļDUķÁkųüc=@5ŗæ•3l\OzÄBymûÜû`îkÔ1ŲäôŽĨāąiüeyc[ͧ‘×ī† ëÃol[¨ēöĸG—Z\Jå€oX=ížú8–šõ@įTFI¤"īĻW‡Ûũx:“ėÃĩÓôq•—ęžî<ōA)Ģ[hfŸxÆ$IŠ CIŌ%øY>/~–ßÂ;‡‘yᕜ—˙ŽîÂ!¯vxˇģĸjÉę Ļ.ßņ5øéBJKæĀX æ×rxú ’ƒĨL]Ū@ÔVĄKëęcÉ5’ķŽL {—(ĸĸĸˆîŦŲú¯ßšU+ˇZúÔ°Žĸē4Ŧ×ŗŽK2gŒCŸÃc‰oēNt4åQáëtë@ôē ʛĪjø2ŧÜj��ą$™+æmŊ|ĻļœÅkĸ–ŌŠË[üŧÛŠ÷ę`¸Šîq‰÷kËæŧÂ#ŗ*h –%Lâ>héÄÎ5æb.Íîĩųŧ¯ŠfņôEŦÚîö6@ģūDĢ–QŪėģ\\O=‡5.kjŸ{ŋķû Ũz÷äú ¯hūÁĒ)˙Wøo%ĶrVíâG ęs¯Î&뜋‰Î_ĀĸĨå|ūe-•Ëg õîKrô>.ĢnŪ{Ę?[Iũ‰Éô‰…ĘÆCŋ^QŅbāŌ¤CŸ1I’"ĖĐE’´ßkĒU2āČDâYÃ˛OË)_S Q‡ŅŖßQŨ0û_ƒSŽíĀ!@ÍŌ…|ŧĒ3ũĻŅŖK  ™Y}xûõ%;ž°~ ķ?māâãŖÛür†œĐ‹ÕÅĶ(Žj~\T2šW\BVÃBĻLÅįĢÖSO,™^Ũm.Ų°Ŗ‡påå9Ä˙s¯žļ„¯‚ Ęy7ŸąyļCtįΰnũ6ũ^ĪēÍŖų(ētzįō›ßän×DũĒÎDA̟ģų�xo†›Šá6}Î/Ã”ųQœrÁ• ;ļ]‚_PRVÁWëĸˆ;<™ū)Ŋ貞ŠĪ‹æđæœiäõž˜ô ßÛĮ0ūĶ%<ōņöˌVk!.Qėp‡Ŗ­5lW4yķīítīw~_ᐸÎũ.ģ÷;Û_ ؕxØŊĐĨĄœŠ=ÍWÃ˛ÉĖCÖ]¨_ŊŒ‚YĶyŗ¸š†Î‰ĸ+Ã~ú+ļ_T~Ūŋę†õ;˙R;ę“$)Ō ]$Iûĩ.)#š~â‰ÍŠĒvĨīq]éÛü ú4DŊÂ3o§q×GĀĒŋņ‡§fQ Ė*;Ÿ{Î=ščÕ yfęN�ÖSōņę/üžŨ•‡Wąčĩ•ÛÖ{Į¤āąi,oz1–Cē�ģP¸öđoĸoC)OŋņKš:ģu}Ųúõë!:j›ŋÔ;‡ž�4„ęKgr˙Ô%ۅ ëžŲéįp—¸Ø-ŸŗūsĻžQÁņ^LNīÆWģÁ€nGl}Rt"ũŗĪĻOīˇyčYôŋ!—ŖŖĸ‰ëĖæ$ ™¯>­āßŲGqüˇ;ķ÷–Y•LfV틍lKųŸvē÷;ŋ¯đīāzXWÂĢåąl›ķÖSĶöæˆŪö˙ k+øËôIüe:tIėÀŦ‘äž{>õk~ËÔõëi` ķ_z…ŧmS†‚ģQœŲ°E’t rËhIŌ~Ŧ'#Ī>ąÅ]lļuČv#ĘŨ×đéB­ëÉ)cRéļĒ”ų_ļpPį(ĸ¨åëfŗĸz}‡‡ŗK˙ä×Öl–Št8ˆ>°9yYŊęęãz’ÜĩŲ‰ŊŽæčÍ;9ÕRąŧ ē&ŊϚUUM?ß°Ža=5ĩģUõ#â‚+*øē1!Ē˙t!‹{gķŊŪmۖč~9ŒJ\Bū§MXÃ˛-Ķm(ûĢē0āŒ1|ˇëļīvæ¨1c—s"i-îŒÕ‚vē÷;ŋ¯°zÅJjēFÍīk5Ģ×5°.XÛJ˜Ö@C×ŧ3‡Ņãđ-iNT×džsLÂæîŽĢ*§`z%õą$'v/¯ĒĨ{įõ[ĩģ*Ø@Ãēoöx§0I’Ît‘$íŋŋC˙nm8ŽĒ„ųëréŗ"?‘Ģ.īܸŧččpvŅm—æ–pëëĨ;ŸíŌPNÁ?×3䄮TĖ\Øōō_PY?ˆ!YŠ,ĘĢ€Ãã#Ŗbi==ēAŸÎKļĒ ŌšÕeáY§ߓ7?]O÷c‡‘›ō Ÿ Į‘=‰ûh ÁO˙ÁįõšäŒ9ōéĨÔÄÅČ1GApËu*įĪãķ!c8/÷ ^ŗ„¯bINĪáŧœ>~ęŅÖ˛éë>›ÆˇNÛüûá§doŽÅŗs]čŅģ3ųnūžŗC*xûĩ™$_1Š‹–Ā€ų X´ôęģ$Đȉdö‹ĸdĘ3mß ¨îũē6Ü׆ĪūƜ—’söHVO˙Ÿ¯†øo æŦŗ‡ĐeÎüĪÕ-\ššōõd;ˆ>sō(_ߙ¤Ŧ‘dÆÕošvâ`Æ_؇ōYĶ™ųĪ•Ŧ#–é'r4ßP°b=Ŧ/eÎGĩ\5ęGœą~ų˙Ē%ĒÛQ ËÃwƒĶųõsEÍģ)IŌ,CIŌū+Ž3]v~ôū˜ÄŸfo.¤ßoÃú…ߎ_ˇēÔRß–åE� ”TJMz2˙ŗĨA-P[ÄäŠGpņ¨qû‰Pķ¯Ox{ę4ō$ųÂlŽšú]ÉN[Z÷ĪYŧúˇ œ•{÷¤âĶyLyã#–Ā•Ã&pSũ+Ü:­ˆW_ęÅÅš9\sķHÖ­XœˇgĖŊ„ī6}ĸ5E<ķ$œqF6Wū,—CXGMU9¯ŋĀĖ}0pŲÛžœĪ#Ŧä{9Ų ;!‡ã˙{÷"eĮüģíˆģŽ+Ŧ[šYȖFAō``āB Iˆ‚™„`DP‚"ˆ<„ũ Ã(°@A RP2đXĄ9H؊ė"¸ëŦėîL‡°˜qˇgF?ŸÛ<ī |á™Ëûß;ĪâöŒ–.æÜ™Ÿŗkëw9tæęGNOpö>C“Ø×ôįĀ'Û3ļ|iúÖŊ”îöäŌ`Nėߑí¯ōŨĖP~ÜŊ' V-ÉĻ͋2:r!ÅŖû˛ûčíY˙Ā_3ũē'}ŗ4+?“Wú:3m´”ķÅŲš#{˙H’ąœüv{ļ,Mßōĩy˛ŗ=)Oņøž|ŧ[á�W´T*•Ę•Åb1===õĖĀ-ėÕ7ߙÚæ-ËÛfÖ$ŪzéÔŪŧ˙ųO)e(c]OdÃÆ§rī´dāØÎ|đõdŒn\…ļļLIéĘŊxaAVm^“9ûˇdËÁ uÍvŨÜņhÖ¯[–Gf_ûŖŅÁųęĶ/sd˛Ķ) ę–Ø×ëėŊˇŪ¨w�nQũũũéíí­Z3é@ķ:ûCöŸz0}ķ;s­ÛđķŸÎk//Ėšąßŗwë÷9tôŽ\ėLN.fpl,iĐ˙5™”öõgs÷/ģķÅū“ĖĖÜŋxI+œÎŽã7ŅųųcŲöîąz§¸qn•}€›˜I�Ɣ']ø[ûŧEYšėņ<4ˇ+3RĘĀŲ“9¸o_ü6…Įbh8öuęLē�P/&]�ā&U:s8Ÿ}x¸Ū1¸Îė+�47GF���ԀŌ��� ”.����5 t���¨Ĩ ���@ (]��¸)´´Ô;�TSē�Đ0fvt$•JŊc�ͨRÉŦÎÎz§�€*J�ÆęįVøŠøonkÉķĢWÖ;�TQē�Đ0î›wO6ŧ°&3;:Ōĸ|&ifĮŒlzņųĖsgŊŖ�@•–JåŸ9îbą˜žžžzæ���h:ũũũéíí­Z3é���PJ���€Pē����ԀŌ��� ”.����5 t���¨Ĩ ���@ (]����j Ēt) )—ËõĘ���ĐtĘår …„õĒŌĨ­­-###7,���@ŗ+•JiooŸ°^UēĖž=;ÃÃÃNĨRšaá����šMš\ÎĐĐPJĨRēēē&\oŠüĢ]ĪĀĀ@._žœņņņ��� ™´ļļfúôééîîNkkë„ëJ����ū?§���ԀŌ��� ”.����5đ':´Ēŧģ7����IENDŽB`‚������������glewlwyd-2.6.1/docs/screenshots/login-admin-example.png���������������������������������������������0000664�0000000�0000000�00000063143�14156463140�0023216�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��Z��Ŋ���%G���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨ\ÕõÁ˙˙‡_ē@3X¤‚Š`NpW‚m‚ëŖ¸eéŌ˛´2ĢĨ­•ĩ]Y[e[Ĩĩ–ÖŽ­Ú*mĻļ˜ÚĨ•×Ĩļ Ŋž m\)n‰ZPT M9€žī ōË4;ŠÖã~ģqËsŪ¯÷ëũzŊÎéķŧŊ~´ ƒA$I’$I’ô…ÔÚ $I’$Iú˛Û÷={öđŅGØŗgOkļI’$I’$é¸õĩ¯}ČČHbcc9餯sXÚƒÁāž={øāƒˆŠŠĸ]ģvÍ I’$I’$ŠÎŪŊ{ŲŊ{7ģwīæôĶOo”Ŗ´ ƒÁōōrN:é$Úĩk׊͔$I’$I:qüûß˙āÔSOŨ˙ŪI�UUU´mÛļuZ%I’$I’tjÛļ-ģwīnôŪI�ĩĩĩ´iĶĻU%I’$I’t":餓¨­­mü^+ĩE’$I’$éKĮ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’B$Ŧĩ I’$I’ŽŽâwKxæšEü{×nöîŨÛÚÍ9"'t_oז+Æ\DR—„ÖnÎ!9ŖE’$I’¤/ĄâwKøÃŦšT~úī6dØģw/••Ÿō‡Ys)~ˇ¤ĩ›sH-’$I’$} ͛ŋ¨ĩ›:mÚ@0xBôÉ E’$I’¤/ĄO˙ũīÖnBhĩiÃŽŨģ[ģ‡dĐ"I’$I’N'Â(ƒI’$I’¤1h‘$I’$I ƒI’$I’¤1h‘$I’$I ƒI’$I’¤1h‘$I’$I ƒ–ŖeëLFôęEę­š_įÄ֙#čÕ+•[sZģ%’$I’¤Ī/ŒŽ]úđŨá#šęĘqÜôÃqL¸l8CūãLžÕÚm;>…ĩvN Ul]1GŽ$oŨFvė¨ĸ**Ѝ]雞Îč+ndDj‡Ön¤$I’$I!ׇ‹.9]ŖŊ_SSCß´ūü ĻŒu+—ņĖk[ØŨJm<´JU!3'LdFŪˆę@¯ôĄ¤ŸI$ļ˙3ŧĨķČ[ēg&<ÉÜÉčI’$I’Ntm“˛š~|&‰m›^)ãĩG˙ĀJÎä;Ųį1lØ5üŦËb~?ˇ€[ŖĄĮĄ/MвéOˇ׉Í[Ū&kâS!Ēu '\Ɍŧ*:˜Æ“SGĶĢI’Rĩq)ˇNŧ•3oâÖž˙Ë#CŲ"I’$I:’ÆUMC–Ũeümé<žūێú7ŠX5÷6 ĮM#F1qäZâĖ8‚–_˙ú×ėŨģw˙ߞ={ö˙÷š>ANb/ąˆÍČbīžŊ7˙#dĪŽZņĶōLjJÂÜéŖéÚB™¨^#xäÉ�Sæm%ŊũaT窐…3eŪĘ<ļ¨tMĪf܍S2mĐ%Ėk?™K'4xæVfƌ­QŒ˜YČôŦuޘDę¤ŌÉef¯†ûuŨ8Œu.†sɝžŅŦ…ĶqÉŧ*FĖĖ­ģŋj#K§MãŅeëØZēĻ3zōT†ƐH’$I’ŽQ|køyôn4“e7랟ÉĶoW5)[KéšųĖŒģž›ĪNöÚGXúÁ1lęqę¸ZöėŲCBB�mÚ´iôß`đ-N9û;@Ÿ~Ğš�ÁāŪ=šŠœe+¨"Š7Žk1dŲ¯×hĻM;Œ*wŦāÖLb鎮 =™kĶOƒíy,›ĩ)WæąņÉLÉH%+=Šy+r(Ŧš@×}3hv䑺=Ѝ¨*ōr6BցD%7'*ú24= v6|ā¨kÜUt]ļ˜y+ūˊé mԑB–­ÜFsEĀ6–Nē„[s ëˆ™–Ũ•Číy,ģīR ģƸH’$I’ާôáģ}šŽjKßņwđČž—5eŦ[ŗš•¯°ĨNJw–¯d]Ú%ddžÉËĪŋCmƒ;‡eŸËyŲįļø¨å+_å啯…N´ŽãūÔĄ={ö�C–}{öėaīž=Tí(Ąf×NöÖTÜģ'DOŪHŪē*ˆJ';=45æÎ¸Ĩ;z1ųų<2e#†eĸ)Ėüīédą•y÷ÍcžETÕ:Vį5¸9/‡u¤3thvŦËc[ƒvŽÎÛŊ˛HoaÕŌ‘×ՋqŖ{AÕ –­h’Z.då6č:|Š�…ŗx(§Š¨ŦŠ<?}Ŗ÷õkÁ°n’$I’¤CÛ.=HhéÂî2ÖŊņkŪú€*k ‹:0oŖc—΄톘¤œÖäļ—WžĘō”/kČ'HĐŌ0\iøW[[ÍŪ=5ėŠ­fom5{k÷†jFËvvî�ĸ:Đ>$;ÜæōįeÛ kY§í`ĮŽôexzl\ÁꕞE_ĒČË)<pwNU]Ķš"ģ/lĖcŨžėcĮ:ōļB§ôŦ–—6}ēēŽGzT9ķV°ŖA… Wŗ^Œ]7fk^ۈ"}ôPe=†3.Ëí%I’$éD}J›ŧWķa>Oūטšd9 æūģøXSEßŗĪäû0bT&Ŋ;Ņ'Ķą…u3MÖ/sČ'ĀŌĄÚÚÚũÁ Đčß{jĢ ÖÖ˛ˇĻš=ĩÕl Ĩ> ¨‚@ŗk;XxIS ›žŸÎôÂyŒh)[ØöOļVg2"cæAžų.ˇŊŌÉę3Ö­cŠtǟiŌ)=ÔžQôĒz”•…04ĒōrØH†g÷jšĘ_ ŽÃ—>IyΰlÛÆu(dáęmDĨ^ËđŽûēļčĀi§5íx]ģv€F1$I’$éÄą›u˗ņOjŋ]F|ö•Ü5ĸIņ&ÅöiŦ|™C8‚–†3Z qĐRS?“eOM€`m§Å}Č;Áƒ|ǟÛi´ī�lÛÆŽĐxĒF]†fD×ĖļŧĨä}Ö*™@U�ŊÆņČäŦ–ŽlO׎�]IOī ķXW5ŽNUëČÛEúäTč};í gõFČčEáĘ<Ēĸ˛Čjž_mŊ/RWCĮ %*g) —me܄Žõˆĸč{Ëp:íëZ]ãéĐB§ĸÚˇĮ E’$I’N •Ÿė †ÎfĩÔÔ´đ;û“ūđË-|÷†˙äĸ}Ë+*?åã΍û˰ėsÜ--ÍhŲ÷ߚšjöÔÖ˛§:@ô×?Ąc‡˙Z¨“ęUˇ‘ėŌu,ËĢbÄІ)BãĻŅ0ÛXqë ō–~Fuí;Đ Ē+}ŗ˛ö‡“š•N‡y9Ŧ,„Ŧ9lŒęËé�Ф§Gą°p;€Õ…;ˆJĪ$ëhՕu1Ã;,eáÂl0m W˛#*‹q͎ąPÕtj`ĮöÍߔ$I’$—vŋû/JH!q˙;mé{î@rß_Ë;Ÿ6 \â’ųÖé^V˙‹íĮ¨Įŗã~–†AKĶŋšš*öÖØS`ĮĮ_c˖XÕMW“šŦ‹‡Ķ‰*rfĖ ÷‹VÖĄ ];Pˇ'JKכĻYŲ¤Gí põFōVæQÕ5‹Ôúl#=Ŋ/ŦË!okŨž*Ŋ†d†LHęĘ`Ü莰u! søķĘD MÃÜŠkĪŽĀļnoš´TąuĢŗY$I’$é„ņÉ[ŧöVã íēåæ_ü‚GœÄˆÎŪ?s`&‰û‚—‘ģúƒ­úJ9a‚ YĐR]`om {kÔTÕđQY„l c2wŽč�[į1ņ’ä´¸4h… oeƊ*ˆęđGÃŗ;@Uŗfnm|Š*‡[ŋ—Ję„Ĩ Ų¤“Ũ7Ší…ŗX–ˇƒ}ûîßėļCz*§UåąrféEvKĮ 5ōÅęę5z4ŊØĘ˛ûf°bG†^ÜxūLמ}é@y ošËÖy<“×Â4I’$IŌqNJŋ/[Îú‹œLtũÉĪa]2âėčũW>zc+?8& <î0K‡€fKˆjkĒęöhŠ­aOm€6mÂØĘ … ū<Ķš’ģ–Îd ytMĪ"ĩk{Ú`įö­ä垭 ĸēŽ`ú#ĶÍôh*cōTFŦžÄŌ#¸äŸ72:ģ+‘ÛķX6k!9Û:1bjÃS{:>´+U3V°ĸ*ŠŦ†›°tÍ$ĩÃLV,ËĄĒĶhŌ[:n¨I?žP]]G3.ũQĻäm„Nã¸ĸé~0¸1uĶrîâ’Ië—Õ ļæąla!íĶģBŽ“Į$I’$é„ņIOΉãúņ™$ļmxĄ-‰guŖí‡aŒēlčūŲ,Ŋĩ˜ß/ŲB¨6ō8ŅĐAKuM .dŠ °ˇĻš6mö †2hčĘˆé¯“~ņBf=ŗœŧVäUQEQNŖoÖ8Ž>ŽŅCģ~öō€C™ūßĪ“>ãQæ­|”iKëfÁtMδßMftjã:ĨgŅŠj&ÛH'+Ŋá•T˛ûFą4§ŠéŲ¤F/žX]>:iy9œ6|t eē2nî\¸ë>f­XČ´ĐĄk:çÎåÆĒģČ0h‘$I’¤Ęîâ•üîҏč’ķØõĀĖ•Ķ2¯áÁĖú5eŦ[šŒį^ÛBeë4ķ¸Ô& ˇlŲB§N‡Úžĩu\}õÕ 6 h´÷ ßxm€Ŋĩ5ÔvąuķÎûųōVlņ—Sî”A\šŦ ĶūwŖĪ¯Š$I’$Ё[qj Ŗc—dúĻö Û)'ĶvWî`ķÆ lxûJ[aˇˆé÷ŪqėúļmÛFˇnŨöŋ>ng´Ė;—ĸĸ"jkkY˛d {÷îeĪž=ū;&iësWõu"ĸÚyf´ˆ3šoŲ6: ĘpCI’$Iú Šåãwßâĩwßj톜0ŽÛ åĘ+¯lí&|ÅUąqÅ ōÖ­fáŧĨl$‹éˇât#I’$I’žâŽÛ E­m;9ŪƌQtHÁôŠĶálI’$I’>“A‹ĸ+–ndBk7C’$I’¤ČI­Ũ�I’$I’¤/ ƒI’$I’¤1h‘$I’$I ƒI’$I’tBhĶĻMk7ᐠZ$I’$Iú:ųë_‡`°ĩ›:Á _o׎ĩ[qH-’$I’$} {œ�3@[›6u}:δH’$I’ô%”Ô%ë¯Ŋ’“ŋūõbÉÍÁ´i͆“ŋūuŽŋöJ’ē$´vsŠM0 nŲ˛…N:ĩv[$I’$I’N(ÛļmŖ[ˇnû_;ŖE’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’B$lß?ļnŨښí$I’$I:áDFF6zŊ?héÕĢ×1oŒ$I’$IŌ‰l˖-^ģtH’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’Bä(-eüéŠ4’zö&ũ…ŸQn~ŋ7I={“yßg•+dĘšuåFÍ/;Â62õÜŪ$õĖbJÁVĄĪáØŒwÅâ‰$õėMŌ—Pqô#I’$IŌa9JAKƒ†$Pž÷ ëVŦäMÖ×ũŗ47M+ˇé֖$“Ō–J’$I’$…ĘQ[:”1$€â<֖´\ĻŦ>„‰ˆ�Š^aíA&Ģ”äåQ 4AŨFkuĸŠõ$Å˙ȧøŠ‘Ä´vc$I’$I_yGo–”Á Š(be^K J€üU…T“Čđķû֗kiņGų̊�ˆMLīŖÖ`°"#[ģ’$I’$Gu3Üd˛ĶcjōW­#Đôr •՛ƪ’‰Ĩšü•…/G4ƒ†$7žVVȟīģ‘QßĪ"ĩOo’ú¤‘ūũņÜ4ãe65ĢčŗUlXÂԟŒaȡ3Hî™FōˇĪgėĪfōzIË•,`ęĮųíēŊh’úd9b"ˇ>Ŋšfˇ”ĖeTĪŪ$û+ÖSAūü;ûũ ’{ö&ų?†0ę'sɝΘĘrįrĶč!õũÉ sôüŠā ģ„°˙uĪ^Ā”ú>%×÷)ũûc>ŗžŠ K˜úÃQ¤˙GI=ĶHũūnzb5e+_ŋ§JęĪVC ˜eŋ˜Čo×ßûíQ\7c5uą\ųķīfė÷ŗŒĶĖfŗžZÜŖĨŅxØ´ōaŽq`LĶGLdĘâ Íŋk’$I’$}AG1h‰¤ßūDÕš¯°ļé¯Ú ¯°ļ"RŌ/%“~P™÷ ųMĢ)x…ĩ•@t˛3Ė\Ø´€Ģ~0–Ûæü…õĶ0äB.; …ŧøädÎ˙Áŧ~˜ûænZ|#C~p'ŗ_."ŸĘ !éôŽ)'wÉo¸úûãy° aãlš?‘ķ/š‡Ų̊ ~ š„‹†¤Yē†E÷_ĮŅŋbmÃldߌ‹@%ųOLäĒy’24ŗ/q÷)xųŽúņÖįÜͨ>L>É 2´¸�Ĩ…/p×'ķįĻ} a˙JOäüq÷đėĒ"ˆOgč° š +•¸ŠĸēúF?L~“Ī0°a&cGßÉėÕEâŌš`Ė… MŽdũ““8˙§/ĶŌã###ˆ�•›ųĶĮrëĒ ŌׅråEŦ|rW=ąü'Žbė}!ߗĄCúĮûŧüŽúņ܃īåĶÂx¯Ÿ?‘Q7,`SL2ƒ†ü?2’")/ZÃŗ?ŋŠëéÆĘ’$I’$D0 nŪŧ9xTė|)8ūŦ”`bÁÁ{ķ_zįņ‘ÁÄ)Á‘ŗK‚Áā‡Ág.O &öüõúÆåŪž>,˜Ø#%ØëšÅÁûß]üũđÔ`bÔāāÛr‚6ŧĄĒ$¸đúĖ`b”`ßëW5¸§ xīwS‚‰=2ƒw6lË;s‚#ĪJ &ž52xīڝ .ė ū}úČ`¯)ÁÄī=|{ų'ƒÃęû4yEŖ'ƒ; ‚÷O &öH ŧĢ XĩīũŸ Žé‘Lė‘ėûŊۃ˙ÛāļĒüûƒƒë¯õ?gXã:Ģ ‚÷~/ĨnœžmøŦ#é˙g¨Ę N>§Ž cž-irm_RƒcĩĄ$øÔÅu}íķĒÆmؙŧķ{uך÷ŠęÆôŦôāĀkž žSuāŌ;ŗ/­ģö­Ė`˙īŪ\ø^ËõŸS“īČÎEęžĶđûąoŧĪJö=įĘāSëÔÜ|ãŽÁu÷ Ÿ|īpÆG’$I’¤ƒhšŠÅ-@L˛Ķ"€÷y=ˇ¸Á…2ÖŽ.Ä1 3ØĖÚÜŌåJY›ģ€~Cîßė43Į‹Ē!öBĻŨ›IŖsˆ"ãšøžȈ€Ęœš,;Ĥ…üįæRP ąį˙”Û2n§CŋI?exŌé$E“_?"Î<6TCô˙äŽė&' ŤrÛäÁDĨ/-h>‹‡�ũ&ũœĄ n‹LģĄI�•T¤ŨиÎČT.’@IÁæĩ„°˙u’¸ė<øĀũLßøRd*—LĒÉĪm°Ŧä^,ŦúōŖÉƒˇ!&ģnЇƒĒNäę{.Ĩ{ƒIJŨ‡ Ģۃ§˛‚„‰ˇsqB˃š  ”MŇšč§:@÷‰÷sMJÃ=\b0v0ņ�ŅŦwũ$I’$)„ŽnĐBÜū}UŠW­a˙áCo’S�Äö'+Ĩî­îũ‰Ö¯Ę;°×FE9�’ÉjpŦķúUyTŅ™ƒé×Ō>¨qÉNĒ É-øŦ_ŌÅŦÍ}ˆ wV*ÍNJĖdú˙ŦbÕŌGšŧ{}ų‚r�z7~Ũ’6˜~@e!˛%ęû‘ÚôŽXâë‰Ū™ũ›ÕWą"Pš˙ŊĐõ_Ŗãé—5Œ‹G Ž>T”•RRR÷WA�Օûƒ–†ŧēcģãSĐB•õãp0IéÍī‹‹%. žiMb‰‹‹�ĒŠn:’DvfĶz€Ø$�Ē+9ėĒ$I’$I: aGû é$ąŽâ kX[q%Į@ āÖVCtz&ũöLȀčy,*x…ĩ‘ „@îōĢirŦs€’Ōē°#P8[ōB‹Ī-+¨fSi9Џm�Ę).ˆ!!öpŪW>–„øƒ”‰%!(/§Ŧ Hip-"†¸¸î‰ˆ .ĻÅčĻÉëPöŋŠ üų‘Į˜ũŌ6”Wēxy9Õ�ņI´ÔĨãp ĸc›UûOŠ&>ļų-FâpĶ‘XZlÜæ,’$I’¤P:ęAKŨ1Īŗ(.-$'7ĀÅŲ‘Ŧ_UH%deõ?đã92•ŦŒ­ZĮʂ�C3"É_ũ&•@|ŖcuA@uņ^l6k¤ą@yõ3"ķ„āÃ)Y-P?ķĸaÁΚâqčËÚĒūī+”ĮÔK&2쏚ˆØž\4qiIąÄE×ĩŊbÕÃÜļdsŖ[**ö…1ÍgÆá`"r_Ũĩˆ]U’$I’$ŽŖ´Ôķ<{I9kWBv,kķŪú’•ŪxO”™Š°ęMÖŽ*† ČÉ+ĸĐčXįH"cꖐ$M\ÄĒÉ)šHbb€ōJ*kjC}xđ™KNö…1‘D^zķ9…˛˙u6=ũ�ŗ‹Ģ!ūBžøīûÔdĒIYų\h´DFîK…Ē2+$@ÅANĨ–$I’$éËę(īŅ y./(dSY!9%´¸GG\ú@R€Ō‚u””Ŧcm1=°ņąÎD’PŋޤŦ¤ü .ũˆ') ’’âƒėęŸ{ |éAĘW”RRQW6á0Vė|~Ąė?@€õyuĶb’Î×,dØ´asŗ÷ââbë&ā”—ļxŒ3e›)Šlé‚$I’$I_^Į hi°1jņÖŽ^Ãújˆm´¨^÷tÄVŗ6o5끈´Á h21¤wf:Ņ@eŪōNö°>įÖn:ԔŠxúĨ@ūĒ5-…Lų~?Rž™ÍÔ\€$dԗéMZĒŊ"÷•ē}ebû“ÕŊ…!ēūוŨ'˛Ĩ}jĘ^föKÍ7Z‰LNĨnā<֖4ģLŲęWČ?Œ§K’$I’ôerL‚–ũĮ<WņėĶ…T6[´O2YŠŅPŊŽŲO¯Ŗč7¤ųI<‘Yã¸< ()ŋx…Æŋķ”ŧt×];‰ËÆ=Ā뇘ōŅoĖ•¤E@õęß0eqiƒØĄ‚õOüŠeĨ@üy\”Ņ´üÃL]Ų$š)[ÍԇūB%¤Œŋ’‡14G"”ũ‡’ęfČlzŠI]ey<øã‡)IJŽ›ŊRZ~ Œę>˜ ’ÖņøŒW…T’—™úHŪaî9#I’$IŌ—Į1ØŖö휎Ž E•1ė´–ö/‰¤_v*/¯aC@2Ų™-“Â͏ũ’õãî!gÉ$†Ŧîˀô$â"+()\Gnq9D$rŲ?gĐĄļIé~%Ķī\ÃØ_ŽaåĪĪ'ķÉTzĮGRQ\HAi%D$ķŖßÜpāt¤îW2ũŪ<Æūü/,ēáōĶŌ/)Ę7ŗ6÷MJ+!6ķv~wUŌ¯C a˙~ã'1˙r `Ԉ<ĨÅÖõ'įM÷1˙ŽJn=ī ŠgqĶOJtū•Ü–Äåw^Âĸ>Oņ˓Č,ėˀ”X"+Ū'ŋ ˆČ!?åââßđlŅQI’$I’Ž3Į(hipĖ3@Ę@úėtäôÁôf �I›íã˛Od÷Kyúúōį'gņėĒ7É_ĩŽĘęĸcãÉ9ŽĢ'^ÉĐm÷ąOōRĘ~ûČ^/($§¸šˆØDŌ†]ˏ&5¯§û¨GY•´„ß>š€× Ö°,¯ĸcIH:Ŋ–Ji~tqˆ…˛˙$\ĘĪĀÔsYQđE›”Ę IOpķu™$PÁ]ˇŧÂMŧɆU!2íR�b2îfū3)<8c.¯‘S ŅņÉ šôw]•ĖŠū†›K’$I’ôå×& ˇlŲBˇnŨZģ-’$I’$I'”Ļ™ĘąŲŖE’$I’$é+Ā E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Zžô ™ōíŪ$}˙aÖˇvSb¯pSŸŪ$žKIk7E’$I’@ØŅŦ<đŌDRē†ęC–ėĪÔ˙›ÃåqŸ˙KøSq—J%æÚøeŗéĨ™Ŧ{5Wŧ�� �IDATŊË3ö f4)éɊLv|$I’$I:ʎjвOtęy O‰>xČza °~ūo˜^|-à Z€ ,zä7,2°AВÄå˙õ$—ˇjģ$I’$Iúj8&AKܐ˜v]ŌQ¨š”õĘ!ō(T}"Ē(bŊkH$I’$Ij5ĮŨ-‹Į“Ô3‹[s+XŋønÆ~?ƒäžŊIū!ŒúÉō+ę nø™=ŗ™V>@VĪŪ$]ą„}—)+äOŋ˜ČųßŪwį˙đnūTPŅčy+~’FRŸYV’ĮƒW !šg7åĸ‘bVˏ…QßĪ"šgo’úd9âF|И@Ãr›fr~ĪŪ ™ą˛ÜšÜ4zŠ}ęË_q7ŪhZ1›V>ĖMŖĪ'ŊOo’zĻ‘ūũ1ÜôÄjJšmĸäé1$}'9ÕPúäE$õėÍųOĶŌ-ûĮ8§ŒĩOßÂųßN#ŠgoRĪ틋 PAūĶw0ęÜēąĢ{͚p˜cÜĸ 3¤go2ī+¤$įaŽÚ?–Yœ˙ã‡yŊY`TÁúÅŋâē}cØ3ÔoâĒ_,a}ĶĮ•åņĮŸgH}ģ’úd9ē…ĪįPåę?ŋô_6Šc{ö&ŠĪDūÜäŲk‘ERĪ1üq_ûķģRļ˙{_Ɗ_Œ!ŊO™÷xnYÎÃ\7"‹Ô>õ˙/ülųe‡fI’$IŌąuLf´|.‘‘DPÁúG&qSd*7?0ŸiŅ•lZō0ˇ>y×q:Ģ˙+“ČøK™ūX$ŋũé,r.äÁ[סnųPŲ+ÜzÉ$U$’=ęŽN‹…˛B^œŗ€ģŽx“â§sWFŨ4˜H"Ąēœ•÷ßÃúȁÜ|K<Ũã?́Åüņ‡c™–)ÃŽåŽIņÄJə?‹Į:–ĩeķY|UŌūžDešpUN4Mzˆ›“ĸŠ(šË”Ÿ=Īm×FŌũÕÛé@€õOgėũEDĻãę;o !Ļ’ +𧇮ãõŧûx驑$¤UqCnįņĀcLyh ų9ĶF&“|ŒŸœĖĻøaÜõÔ ÄUōø/īdŅĪ'ȍeSå`nûũĩ$6ķė}ˇ0ûᓉKy‰ÛRęëøcÜĸˆčēąY}Wå&rŲ-3¸-!š˛‚yLŊoWXôߎ͝žŠMOOdÔũëˆLŊË'§’ešK˜ũܝŒ-ŽæĨg.­›@!ŽģŠĮKÉ{?J‰†ŠÍäž´€Į:–õyzTÜa–Kg@<Ė.ČcŠt¯oz `5ųDT’[āâŦ}ũÜ@N^9$]Č „Ī÷]ŠûĻTP6˙WL-Šeø¤HJ‰­Ū¯{í<ŠcûsŲ䟒WMÉĘÜôãh—ËI’$IŌņ& 7oŪ<Ē^œėÕ#%8øņMŸûžÄī=|ģĒá•õÁ_/%˜ø­Ûƒ¯í/'8éŦ”`âÅs‚ī¨!øÆ]ƒƒ‰g ū~}Ŗ ‚Á_ Ž˙VJ0qøō¯Ũ–Lė‘ė{Íâā‡‡ŅžÕĩoā]ÁFĩWīũnJ0ņŦ ÁĨ;ëß{oNpd”`âY—Ÿz¯Qáāˇe{ Ūģ~_Ųį‚cÎJ öŋyU“vTߞ>,˜Ø#=8éõ&ũi*˙ūāĀ)ÁĶ×7xŗ xį9õ㚯Æ}c<üÉā; JîŦī[âwīūŊÁŖĒ^ŋ=ØˇGJpØūĪņķq‹Ūy28ŦGJ0ąĮČāīßiréņ‘ÁÄŠÁņ‹ö dIpém—‡]üËāk;–Ŧ žvsz0ąĮ°ā¯÷uš~ OoōĢZ|ęú+ƒã§įwvšúĪéŦ Á… žûÆ]™Á^ßœüŊÔāā†cũásÁ‘=R‚§ÔŊüߕ}ŸI¯īŪŪ¤;ƒ ¯I &öŧ7ŋa-õŸAĻßI’$IŌąÔ4S9&K‡Š瀤žŊú7ä‰âf÷$ šŪ&DēT–Rö™+S YôŌû4Aą”••ø#• Ō" č^¯_vQ÷ˆŒÆĄ=Ē`íĒ<ĒIæ˛ņŠˇ†‰LåĸķĄ:s›,˛IÉđFSQ"IH9(§Ŧž%Ģ–[̀ėžĐ°ÍeÄe$‰JÖŽj˛„å J9đūY�1ņąÄ�ą™ƒ÷Ī$ˆŒO$(+-¯įķņgJ>ĄŨŋÕ=Ŗ?ņT“Ÿģ¯ŋņ ˙õsŧ´đn5˜Â@BJ<PNÉžgí›ET\DŖĮGĻpÍīįđôäôēY ‡U.’~CúR§nÖJ\څ\Iqî›ûVŽČ[Ízĸ™Ė‘~Wb2/lÔG…äTCü@.JkXK$Æãhė|$I’$I:rĮėÔĄ‹Ķbz=>­ų‰DqIMËGBdP÷û JŠØT Íâ‚īĖ:HĄRŠËá@˛O÷„ÃŲQˇ” ÅÕq:Ũ[XÃĶ=%ž6SR\ ~GÄÆ6q掝/%6•ŧøĶAŧx§G”–S![.×tŒëÆ7.ūāŸp„cܲˆ„¤æËĄâ“ˆJKË)ÛWE ˜<ÆãĢŪdSI9•Î `˙†')—ōŖĖ¸mÕd2ĪËĐĖÁdeĻ3 +•Fņa–‹LLŋˆŋ°vu1dĨ@Ų:ÖGĶī–TúU§ņRųW’ësÖQҟŦ´H øˆž+ )‰ –ŊOI%’Ü|œ’’Iˆ€æ1Ĩ$I’$ŠĩŗS‡îúœ§ņAB@Ũoîäq<>y`ËõDƐĐh–Hĸ#§ōęē`$2†Č*ŽŒ¨ Œ•M“ CW€X˛ī¸Ë2T‘qÉ!Ũ“ã؎ņAÚŅÂũõīöĨ)Mö:š%•„¸ú)‹īāGĪ•6¸7ž‹ŸZD÷Åŗx|ū+ŧūÜoxņ9 "–ŒQ?gڝÃčų9ĘÅĨ“Ķ Ū¤„bōVŗ>"™ËŌ"‰ ¤{õ,r6ĀđŒ ä–‘6˜1p¤ß•˜Ļ…€ˆú8MjņĀ-I’$I:Λá~Q11õKCâ靕yĐÍcLDŨæ@EũčÆWՕ�DÆ~ū8$2&“”É Ŧ/ÜĐŖ+„c¨ŦĻŲP*난ČúĀ%w.įU=äæ˙×āFaĶĻU-ÕGŋQˇķÄ¨ÛĄĸ”üÜWX4g.~n2c+"YĩŋŽÃ)Ī€ŦDx:üŠK‰ËYGuŌ8úŤ2 žœ×WCŌ:ÖCī1éõ“xBô]ŠXĒëíÆãTq ‹’$I’$Žģãŋ°¸DēĮÅyŦoa‰Q â0Ž>¨$R’" ú}65;~6m(Ĩšhē7[öthŨSJō7´´¤‚ŠCī|L…pŒĢKŠi6”ĨuīEĮĮT”žO9Đ=ŗo“=eŦ-(mzwc1ņôËž’iĪĖáæd(_õ2k[ËĪ(×;ŗ?ąÕëČŨPDNa9ąiéõ{Û$“‘AIÁ›lĘ[ÍząoOˆž+qņ$D�四Sq›>ûnI’$IŌ1vâ-‘XĘ@* ‰…Ę5<ūt1~SWŦfʐúÃ%Î>­-=lĐų‰ĻˆgŸ+l\w g_Ú Ņš íķ/čˆ2ŒŒ(žķ+å6==‘ô˙8ŸŠM7Ų=ˆ@ÅŅžæÂ1.zg Ö`íâ5”Á€Ŧd�"ŖŖ‰�ƊËŨZöŌĖŪPWu]ëŸOæˇĮķ§H¨›!yøå�HËd@t9ų‹°ļ8‚~™ÉûKöËHĨē`5ŗWŽŖ:ļ?Ų)j Éw%˛/iPü ‹S+ž^Î!b&I’$IŌ1vL–•­zŒ)ĨÍ7ŧ= ’¤ķ¯åšŒCŸûĶX, q@ŅœIF|"CĮf2`Ō/šhõ$=4–ąÅ×rQV<‘e…ŧ8g9Ĩ§sŅ=ƒã„Ą–Åœ˙ŸÜ6˙Mîš3‘Q×ruV<‘EŦœŋ€KcÉzā?z$ŠÄ]Č]“—0öūåÜôƒJ.iqՔŦ\Āã/™ús.Ë8ďōúMd V=ĖÔ¤ÁÄĮäšė#éåĄD†lŒŖ“ŖyũĮãŠ)Y P–ģ€ß.yŸˆ¤kųҐ灌ĖÆĐčåŧ8˙NnŠŋ– âlĘYĀėŧxîēwS~úkį?Æ˛č ”9˜¸p×%c(5Œ´¤h"åŦZŸ‹"H™x)€ČÃ,W׀ūdĨEđâK/CD*S„#qiŠ$UĪåĪĢlj2˜Ū úšīJÃ'ãˇy/0ûĮãŠ?’´˜ ŠWŊž@_2ĸ˙Bîan’$I’¤Ŗí˜-•…Ëyö'§Ä_zAK Wßq kū+Ÿ|˜üÔ06â3ũųų¤=ōĪŽšÅÔ%•K÷´ ™ú›¸<í‹l)›ÄåO-&yüĨYLYR ą$¤ æÖY7pMÖaė�ÛĸHz_5‡ÅņņÛ'_æĪ3îdvuŅņÉ šø7Oj|s‹âÎãļ‰/sĶĶo2{FIc“š&û(m—Ē1Nš§GņāŒĮ˜˛á}*‰%eČOšëž Ž˜ŽĖ´§~÷ÍbÅũ“y1:–”Œ ™>īÅS–™ĮƒĢį2…X?s%O/Œæˇ,`Åü‡YTY ŅħôįōnāæQ)u3VēfšēÔŲŧz$ĨÖīĪR/Š?ũĸgQ\Á ė&Į8‡čģ“õKæ?Íԇ–ŗėĄ{X}:Ŋ3'đÄo’yņ!ˇŅŒ.I’$IRkj ƒ[ļlĄ[ˇn­Ũ}•lšÉųįũ†’‘OSøëôÖn$I’$IG¤iĻrâīŅ"I’$I’tœ0h‘$I’$I ƒI’$I’¤9&›áJÍtŸĀK˙œĐÚ­$I’$)¤œŅ"I’$I’"-’$I’$I!bĐ"I’$I’"-’$I’$I!bĐ"I’$I’"-’$I’$I!bĐ"I’$I’"-’$I’$I!bĐ"I’$I’"-’$I’$I!bĐ"I’$I’"-’$I’$I!bĐ"I’$I’"-’$I’$I!bĐ"I’$I’"-’$I’$I!v4+¯ TŗĩtęöėŲ{4%I’$I’ÔLDxmŖ"Ičü "j Å Ĩ*PÍÆMīũubĸOæk'}u&Ī”n+ŖßY=Zģ’$I’$}åU×ÔōŅ';)zg+Égv=ęaËQK?ļ–n#:úëœÜŽíW*d‘$I’$IĮˆđ0:ãTâNí@Éõįĩ¤ĒǚvmŖŽVõ’$I’$I‡íÔSÚŗģ*pԟsԂ–ŊÁ 'ĩis´Ē—$I’$I:láaT×Ôõį¸ĻG’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)D Z$I’$I’BÄ E’$I’$)DŽĶ eīæ-á÷˙’›t#W]{#?ūŲ4~÷ÜlÚ‚ęwæđĀĩ7ōģüÔ"Û_ũ=“îø#k+Vâ=ũęÜ6g‡‚W0õļynkhÛxŧŠxãLēãy֍Ęßz–ŸŪöGÖôķ$I’$Šš°Ön@s;ųûō‡7wŅ9-‹ ¯éIlģvnü++^›ÃÔüˇšéŽkųVûÖngh–ڏnËWōo}€sNivŊfS>û$Š”QgŌŽÚw<jÛ=“ËG…ßÚ ‘$I’$ŠŪq´ėüŋ9ĖzsÉWüŒ[žÛéĀ…>i ԓ‡îy–9ĪÃ7¯;‹đÖkfčuėÃw’Vō§ŋņŅ9įpjŖ‹5üë¯ETžÜ‡I_Ē^!áßčÁ€o´v+$I’$I:ā8 ZļąfÕzv%f|ÐeŸØ,ŽŊ#bĪØ˛ėÚô Ī.ø ÷#vŅ–ÎŨĶzéhÎí˛oŪG īūßŗĖYô&īî §}—4.ŧ(ĄIÅ5|ŋ„gũ•ĸmĐö4ē§_Āø1ũé|Ėrhú诂gķųû‡įŨ0@¨y‡Üˇ?ĨãŲũčģļžÉâeo°îũrv׆ŨšÃFōƒ^Ņ-Ôģ›ŋ͚Î3Œäžkŋš6LMÁ\n{.ŊįJ´¨dũĢËXēz Û?­%ü”NôũîpFĶų 3h*Ūø#ŋ\ËŖ`åķė>ûjîqė~W/įĩÛø¸ ĸ;'“=â<Îí~ }ģļžÁ3‹Wŗáƒ*ÂOI ãŧsé¸f6ËãŽæÁKųhõ“ÜŊ2–ëīEī}7}ü=đ*xW÷Ē{ūËNfÂũ—Đ—Í<wī<ļŸ;–ž—ą´8–Kīē’mkØūÖJ-‹•} QątK;—1ÃŋÉiû?ÛOXˇx ‹ J¨¨â´ŗ2š¨×’$I’¤¯°ã+hŲĩ™ŧ]FEėAŠ´ī|ƁŧÂCĶ—°+ũrnšē7ąlįīKį0gú,ÂycĄfĶķünvíĪģž{u‚Ūæ…E/ō.ŧīąųsxāąˇ‰=o<w :ƒđŪfáįđĀÃųÕuiĮlŠNģ^ũé5Ÿĩũ€ėa÷ŋŋkc>Ēbų΀3`÷<ĩŒu? 7&Ķ1ŦŠŌĩ˘=g>§NžČĀŽGōä6/ÍֆņKÆ1ĄËÉTŋĘ3‹gķtøõüøėæK™�ĒwXšļß?„Sb2V>>›—k“šhüHRNŠĨtÍ2žyjĩ?ųq]€´{ žZΖ.įņãËûĐąvy9+?„°Ķ¤ũ�a„‡×ōņÚWŲpöyÜ4,–ĶÚÂŽˇ–đÛšīpęwGrˀN„ō˙ũü~ģ;Œ)—§ĐØūę|f˙2.™@v÷(*6žĘŌ•[¨Ĩ…°O’$I’¤Īp|m†ģs; #6öÔC—6Ŋū6ĩëĪøqįĐŊs{ÚwîÉšã.ä[5ëY‘ˇ­žL!姞ÃeEįØSéÜ+‹ņCŠŲ_ËGŦYūWvõē„›.JŖK}™kĮœEMū˙˛Ļü¨ô´emĪ$ãŦ“ų°0ŸÍûßÜ͆ŋŊÃîÎũø  m7FÜx=ˇ\r=N?…SŋŅ™žŲįЃmŦ{w÷‘=wwËזŸ=š1igpjĮSH<{8cΎbÃĢoRrĐè­­%!s8ēw&Ąc85Wŗōƒ“ɸd$ģĮqjĮÎô1’ė¸mŧúęfj¨ ŽÖUuâÜįĐãҜzz˛GõãÔĒ#k~Cˇũ&—ž›Bâéq´ãr_{‹ŨIį1aX Oá´îũšbø™ÔžŊšÜ>`í_ˇ~V6ĨuæÔčēžK:ž2HI’$IŌ‰á8û5Ų–ph‚|–ŧûŪGpFOē4\ŪĶî ēw†oŖ†ļ|°­‚đN‰tnX$Š'yģîEÍ{Ŋ G$ŌžQ™ŗčRû<EīÕ04öX­ §ĮŲÉtü[š[‡‘ب,"w#œ9ŧOũž-miģû-–._Ɩ?Ĩbw-ĩĩĩÔÔÖŌ­ļöČûáfJj;đIqÚŌ­g'Â֖°ĨZZ•@,g6ø�>~ģÃč}zÃ1‹Ŗ[דŠÜZÂĮ$Rķ~9ĩQ Ä7\Õ1n§ĀGGփ՜žp`›šmüë}8-;˜eÚuíA|írūõ~ įF—ŗũ8m@lƒ=‰īŪ ūv„ã)I’$IúĘ:ž‚–ö§Ō>Ŧ–ō÷ļAú‡(ŧ›]ģ üÔļM6ÅmKģvPSŗ›vŗĢˆ oŧü'ŧÁëšŨÔÔÂģ‹īæĒÅ͟’ŧspėŽ8 ī۟ž§ü•ŋ­ŨĖE]ŲũV>˙ĸ—öŠO:>|ƒ?<ą’ŠŗÎãŠËĪä´č0¨)â™–f@ՂĒ*jŲÁkũ‚ך]Œe÷nā`AKXá žEģ*Ģ ö-fŪųVķ˛'BVUaaM>ˇ(ÂBđm ‹jPImĩĩPēü&-o^öĖĘ*¨­ew-„5yxxTa´H’$I’>Ÿã+hiwɝaaū_y÷ĸ3čŌB‘ų˙CnxÎíĶ0PĄÁö†L8ááPSSÃ.8ŽėÚuāux[څA—ė˙äúoĮ4yZ8íÚës¤;3p@'^[Īŋj:đņßJī5šžõAĮöˇķŲ–Ė„KúĶc_§+wĐ5p FˆŠ"Œd\9Žņ&ŧ�aaDŽ}_ÚEGAT2—ߘMbŗĒĸˆļGEAmm“6WÕ:ĄEÛ0ˆx5WŸ}rĶ‹´ŽŽ |Â`w“Ų@5UUÆ,’$I’¤ĪíøÚŖ…N Ō›vÛ^aÖĸâfáAÍ9Ėúã ŧôÛ¨Ą=]Î8Ūû'›ÜõE@—^gN ;ĩ…m›ų A‘Å˙<đ:ü ēw Ŗŧ|ą;Ņy˙_ áá1´?V;á6pZj?ēUŊÃß ‹øÛûa¤ 8ķĀiA•UEtƒé äŗ’ļ„ĩĩƒƒíī6Ø|æ‰t û”Ēĸ8íqūĸÃk{ĘįÚ ¸ã靈ŽÚA%qęęØļ.Ø:žŪ°Ēm”~ØāÆˇ°åĶ/ÃÃÂęf¤4čSMŲ{l˙m!ŧŨNããOĒ訰_ß8™°°“‰i „ĮrZtŨxxT [6nû<O’$I’$ 8î‚h˙ņ\˙íSų`ųÃÜū›įY‘WĀ?Ū*`ÍĸYLŊ˙YŠb˙׏;‹v@÷!ß#yכ<;ī 6•īdį˙äÕ?žĀ?ÚĨ24ũT œäī¤ŅūŖ7xöšŪ-˙ˆwßz…9¯ooœĘĀķŌĪ_ČVū“vî¤üƒōęŋæöûįđ]­0ûŅĨŠu/ŋʖ¨> L:ĒtLJ í§īđÚ_? ĸō6˙u1Ow ĮÉđņģÛ¨hļ„sZ—Xx÷üíÃē‹›V°|cíųLm“9÷ė“ŲōōBūû­ø¨˛’í[ķyîņGøõŸōŠøMīuįv.gåķ/펭ŸPQų %o­ā÷ĶáwkĘ�h×Ģ)QÛxuéjÖŋ_Æö­˙`Ņâ|*ĸÔsz':֖°öíO�¨ųx‹^=øļŧ-;…Œī&öörf¯ŪĖöĘJ>úp3kū4“i.aũn€Îd¤Æ˛ûíå,*xí—ņ¯7–°üŨ°ãlē—$I’$éDđĩģīžûî;vĐĄC‡Vŧ­ėcbNūúÜÅiũŌ˙ŒZJ7äŗæĩÕäü}N÷īŒáúk“¸īyģŽ|+%’-š˙Ë K–˛ėõˇųđ”o2îēqdÄ~ €¯ÅöäŦ˜íüõ//ķÂ˙ū˙ØÖŽs/=‡ĀÚĀŲŲdtūáŋIÚ7>Ąpŋ,\ü˙ŗúm>lŸÆe׍á[ŋöš{PųéŽ˙ŋŊûŽĒŧ÷<ūŽžHbM0@ĐÁJ%UüQc/jh/ė"üZ UoĄxÛ^éØéjg¤wėHģęûŽãG¯‚e]‚â`T” ­Ņ 5G L$ŅDshŽdūØ“@Ũ!‰ŧ_kš$gīũ<ßŊ÷!kíĪķlöOîÍIëCoŗnķģœņ•o2õË­Ķ—Rûb@Ã_Øø˙ÖķŪ`×Á šū†¯3ôoaãÆWxųŖA|}Đ~^xššŗžōw\”yy¤ī-§ä?žeÍķĸ"öE&>ō­QpÍ(ōROåŦ‚a ú0ĘK˙¯”’g_`ÃÖZRŋ8†›¯ŋ”ÜNÖnŪŗ…g+Naä˜áœũÉeĘä‹#Ī#}§×­į?ÖŋÂkģâä™ĖŒkÎĄ@ę�†ŠŨü"ëÖŋĖĻ‘7æë Ē~ŨYŖøzA6då‘×ŧ‹MëžaÕē—Ø…Ëŋ9ŠæÍÛ8uä5ŒĘU›Y˙—͏ôë2€¨Øđ5š—sÍĐÖeRû‰‹Îú€7_(劒RÖŋ˛“šĖaLŊé›ŒČ Š>#˙<úÖídãúRž~ųĪTĨįúĢOãõ7c Ŋjƒú|†[)I’$Ię1ĒköÆįõ#žŠ¤´´´´DŖQōķķCíhķ›;8'ˇßąwüÚ]]è ‡vwŊL ëîũ5Ī žÅ˙šrøę.’$I’$}v›ßÜúķúá™J›:$I’$I’Ô[´H’$I’$…Äõ>ÕCôcėū™ąŨ]†$I’$IŸ#Z$I’$I’BbĐ"I’$I’ƒI’$I’¤´H’$I’$…Ä E’$I’$)$-’$I’$I!鲠%%%…ƒ--]Õŧ$I’$IRŌūÖį´ÔH—÷ĶeAKzÚi46~ÔUÍK’$I’$%íŊē÷IOëĶåũtYĐ2øœ\?jĸáÏøøāÁŽęF’$I’$ŠSkŽS]ŗŸwßĢ'o`˙.ī¯ËÆĖ¤õ9a_ÄÛģĢŠy¯Ž?>šÂ–Íoîčî$I’$I:靖!=­į>!S‡ē´‡´>§qÁyƒē˛ I’$I’¤ÃˇI’$I’$…Ä E’$I’$)$-’$I’$I!1h‘$I’$I ‰A‹$I’$IRH Z$I’$I’BbĐ"I’$I’ƒI’$I’¤´H’$I’$…Ä E’$I’$)$-’$I’$I!1h‘$I’$I ‰A‹$I’$IRH Z$I’$I’BbĐ"I’$I’’HW6ž! 7,ƒš!ŪŌ•=…#’ũž�O‡ŅųŨ]$I’$IęmēlDˆ(\ũŋaocīY ¨soCP÷†hwW#I’$I’z›. ZŽ_Ŋ$_i/ZZ‚ú%I’$I’ŽG—-ûēĒå jģģI’$I’ÔÛtYĐŌ+Gŗ´Ņ[Ļ;I’$I’¤žÃˇI’$I’$…Ä E’$I#š‹ø�� �IDAT’$)$-’$I’$I!1h‘$I’$I ‰A‹$I’$IRH Z$I’$I’BbĐ"I’$I’’´ŒÍ†Ŋ?†–;ĄîvXY Wdļc.T܏öyî˜MˇÁĩi'ĒjI’$I’t2ë‘AË´ | k€OÁ¸GaŪ+[�e߅ÛrŨƌ`aĖ|žŽu}͒$I’$I‘î.āpƒ/‡ĨÃĒå0yk› ;āĄWá÷s`Ņ4(û-lŠwÜÆĩŰxĖ{�–ן˛%I’$I’zۈ–š_*aîÖ6Æ`Ū¨īķ†v|üˆĢ`Ų(Xü(ÜWŨ••J’$I’$ĩ×ŗ‚–,(ęe¯ÃžNv9P ĨÍP8äČmƒ‡ÃÚ¯ÁÚđƒĘ.­T’$I’$é=+hɀ\ úhĶ}âm„Ü,čĶæãÜĄ°v ä6ÂŌ]\§$I’$IRzVĐrȧX9Ļč+°m#ŦĀŌ)0 üĒ$I’$I’ŽĒg-õōŗŽ˛Oō3‚Q/Ú|\ú$L~ϝ€Ø—aÕUíGŧH’$I’$uĩž´4BY Œŧw˛KŸ!P˜ Ĩí?¯n ū˙ū˜ôŒŧu°Ž‹$I’$IRWéYA °čˆ ‹/ī`c,ē˛öÂÂŖ,vģå˜W so€G#I’$I’ĸOąJ×Úˇf…eaķX\ŅäæĀĖĢĄ˜ų�l?F;K–CŅwaņ4(�ļÄO@ņ’$I’$é¤Öã‚€'‡Â ˜˙UXP šéP_Ĩ[Ąp=lŠ%ŅH#Ė}FΆUÅ0rŧßå•K’$I’¤“Y Z�ļl…omMbĮj(øIĮ›Ū¯ė|›$I’$IRØzÜ-’$I’$IŊ•A‹$I’$IRH Z$I’$I’BbĐ"I’$I’ƒI’$I’¤´H’$I’$…Ä E’$I’$)$-’$I’$I!1h‘$I’$I I—-)]Õđ 1‚’$I’$IĮŠËâ„™@KWĩŪÅZ į Ũ]„$I’$Ięmē,hųÃ4HéĨÃZRR‚ú%I’$I’ŽG—-Ŗķá…ī@nf9%¨÷…īõK’$I’$HW6>:öŪŅ•=H’$I’$õŊdŦ‰$I’$IRĪgĐ"I’$I’ƒI’$I’¤´H’$I’$…Ä E’$I’$)$-’$I’$I!1h‘$I’$I ‰A‹$I’$IRH Z$I’$I’BbĐ"I’$I’ƒI’$I’¤´H’$I’$…Ä E’$I’$)$-’$I’$I!1h‘$I’$I ‰A‹$I’$IRH Z$I’$I’BbĐ"I’$I’ƒI’$I’¤´H’$I’$…Ä E’$I’$)$-’$I’$I!1h‘$I’$I I¤ģ $I’$}zoWíaÉŋ=Lã‡ŅŌŌŌŨåH=NJJ ™_8[fŨLūšįtyŽh‘$I’¤^Ējo5÷üj ˛Hhii჆Fūå×˙J՞ę.īĪ E’$I’zŠ%˙ö-°HĮ”’BKËA–<øH—weĐ"I’$IŊÔûī7tw Rī‘’Âûtũ߃I’$IęĨÍ"Ÿ1ÅΠE’$I’$)$-’$I’$I!1h‘$I’$I ‰A‹$I’$IRH Z$I’$I’BbĐ"I’$I’ƒI’$I’¤´H’$IŌIĻoá-üú˙ĖŋĖIz‡{äđ÷ķū™_˙âŋrĶy'¸8x—ņOŋøīÜrawōų`Đ"I’$I'Ŗæf8o—dt°mā( û5Ķ|‹Ōqé7š;~8žüîŽCí´H’$IŌI¨šĄŠhCWŒĖ:b[ūeŅwO5ûēĄ.%/ũė! ˆtw:œˇD’$I’NJĩŧž=‹âË.ĸ˙† ŧûÉįy\qaŅ-oĀŲ#ÛėĄ˙Ĩã™>fųũ3Imnbߎ7Xŗĸ„×jâ�dÎâ§ckyđĄ.r%ũ2HÕōúē•<RVM<Év ‹á“'3udgDbTmYĮō7‡đ7fąæîyŠ1Ņ΅c™:ū"†öĪ„ĻĸåĪąlõŧ§Séƒ.cƄ̏øėlRãG“>¨)Žäâŗŗ9&öíÚÆsO•đŌŪX’įaøˇÄ,VsĪ›C˜6ö|dĻA]”u+ūĀŗoÅZ¯į1ëĐÄxĻF~˙4š÷WķzéjV”U“Yô}ūûuũ€ĄÜū‹+Š\ņ/üKY=éGR\|— ęĮé4°ī­ržXąŽŠēÖ6ĪštĶŽģˆŧ´8ėŲFIÉŪŖUŽgÁ†ņúŊ÷˛â“]s)ūá?rÉöeÁę* üÂņL)F^ßtR›¨z̜5+ÖąõPßiyMOŅšœ•ėŲÆē§J(}Ģ1ØŪo4?™7Š×}‘žã'pqĶ:îúmÍįfęøË)Hܓ÷öėdCI Ī:އ1h‘$I’¤“RœĒ-ÛøāĘQÜĀS‰čČy—sqfkĘë¸tt›Ũå§]ÄūįūĀ=ÕŌÉá’ë&3kFŒ}ŋ\Įn Ū‡´aL,ĒcŲ˙ž—ĨúžÅü)Ŋũ~Jë’k§Ҏ˜uY„?=~?%ģ`ˆąÜ<!‡ôÔúOĘIŋp2ˇÍ8Ÿ÷ž[É=T͜}>o˜ĖméqîztMröeÜ2g<é\Íũ+ĒhĘFņ´¸-5Æ]ī¤Š_!ˇÎËé[VķûĮwō9\<v2Ķį^O|ŅÃlĒKæãÄãúÅ1×­fé/W˛Ÿ,ޏņVnža  K؝dũ™#ŽįörŠXŊ’{ūÚHúĮpķ”YÜÜôkîßp?÷eßĘmėäžßŦ#‹AöHfΝBŪŽgXēč ö‘Ã%S&sËėŋZTB4‘AãšeÚ0>xî1–ÕBöų\7~ į�;?Ãˇ)rŪxn™’ĪŽåķČ[ÄĶr]<™Y3šX¸hī’ÃßĪÅu‘m<ņĐJ*ę"œ3z7Īū"‹~Īŗ5A;ÍŠi\<v8^÷ ÷ėŠįƒ´‹˜5c }Ë˙­Ļ)’Aūč Ü<{2 w?ĖĻ˜ĩ8uH’$I’NRņ]›y}?.š,/ņI„Ą—Oú[›yŊᰝ÷ŧČīîŊŸûKv˛ģĻžũ{wōė‹;ų¨>Cŗ[wKMķįŌ ėlˆķnųfĒČ%˙ėH’íäRxŲ@šß,aų–jö×USQúÖÕdúI/Y\Q4œôŋ–pÉ6v×Õķî[ä‘Õ;‰\xW´Š§­üŅW1´á,_YÎÎŊĩėūË–?õ*Q2Čō ¯dHė –­('ZĶHCM”—VŦãõČPŠFä$Ž�‘(Ĩ%;ŲâõüiK”æžšäe$[—Ž.€7Kx¤l'ģkĒŲYļ’åĪEiÎĖ"Ņ ú7ƈĮáœÂĢø2[YöŠgÍNžũ÷įˆf_ÆØ Ķ‚kP8Œŗö—ŗĸd'ī&ú]žĄĒÍĩũtÎ8;‡3šĒØ´ĨŠwëęŲŋwO=t?ŋZūûČ—ŽbėŲlz|%/ŊUËūējļŽ^É皁Œ“ßfH&l_ĮŠ-UėŽi$ž=éėøSpö×TņÚʇšgÉ:ū봜nåˆI’$I:YÅĢŲ´Ĩ†1—âü’*vr>W\˜ÆŽ§ļŅĀE‡íƒūW2­ø|ō˛3HO‰D8Z"mŸ,›k‰îi{\œ& =’d;‘dÞ ‡ĻÄØņf\ø1’ËĐŗaßē*ÚæAMoī`węx†žĄ´îđųCœsv6Í5ī´[{f˙–ÕÜŋ%ØūwggÞįØŨöĐX5•5P0(‡ĩɝ#Āģ{Ų×ĻxSœf"¤§&YCbŸ-í¯CEÉcTБ ōõƒ=/˛ŗm�ŅåĪuЌ9/ļÔ2 _æ× iW%û8ŋÃV“ĩ˙¯T˙7Ήķ\ŲV*ۊ˛ģŽ–hb¤T˙ŗs9ŊšŠ?īi{qk‰žŨĀƒķčK41}­™Ē]ĩ­ģÔėāĪû¯bôŗHŨđ*¯˙5ʎŊėŪÕ‡˛$´H’$IŌIl÷7ŗoĖ•\q^ ģĶGQ‰˛üÍØO‹™—~‹Û§å]ŗ’ˇTķA30x<?™‘sX‹GY %™v"AŅÔÜžĻ†ͤ%öI#’ y×ũŋžîČ>vdχ?ˆGHMöĮ;žVDééĐŧ?vØÛ–b45AjjZ›QG?ĮCûtúÖĻdęOė'Ķ@„ôtāė)üâSŽØÚünŌ‚ §á°kĐÜŲ59{7đĢßÔ3vĖ囉éÍ|đv°öÎK{cœž™ŠÃųĪ˙kø‘Į6ds$‚–ÍMmĒ(+~ķ¯ėsWŒžĀčâtš÷W˛Šd5OlŠMęNœh-’$I’t2ĢŲFŲžo0vä0vĻÛWRÚŊö9ƒ/_6”Ķ˙ē‚GJw~2 #Bä8§œ$ŅN<Ns3¤Ļļ\MĪltÄc45CՆãÁ?¨Äijčh´Cü“Ā$:Ú*­đí˜Ī:Å&éúûœIöą=8?ū熅+vōěꉓA<qmÛ]ƒôÎŽÉŅD8ŧ´ĻŊođÔŖođúFŅø L˙N„ũw˙÷bĐTÁ#ŋYGåáĩÅc|p´ŽĢxiõcŧ´ŌûåsņčņLšéhŽģ—ģŽĢčÂ5Z$I’$é¤VËkÜKęcAœÚŲÁw„ô47ÄÚlËāâKķ3xHĸx=û`Ā Ü6#Ōzank3ņjĸ{šé›ÆūšZŪũäŋFâņF:\ģŖ‘Ē=upöōÛ™#&ķ_ž;–ķ#ėî`;iš íUoU‡7z"™úãÕD÷Ā€ ōH˙äĀnáŋLÖæŗ6įˇĢ˛û‘Z×ļÍzšâ1>hŒėĢi‚~į2 Í‘™ƒ†´ûųČzˆtHZ2Û´1pÃĻ:€ũģŪ`EI9īeæpN6ėßSÍéYdŌļļZö7ÁRg×6’Įđ/å|ō]hlj˛iõ:*š3Čë—ÖÉQŨË E’$I’NrûßÜLUf6gÅwRļŊŖ”ĸžĒ= ¤^p9Ŗe™Ë%Žgt<Ę>˛rvVûõI:•L;ÕŧöfgŒKņ…9dfįPPt=EŲąvíl*ŨFüÂņĖO˙Œ úöËįīnŧ…Ÿ|o2<īŪ°‘Ę´‹˜zÃeœ?0—ü Gssņ(ÎĒĢdwĸ^dGÚEL2’üė 2ûåķw7ŒåËą JËë;nôSIĻūzū´Ą‚æ Æ3sôųœ30—‚ÂÉL-ĖĄiWM@sS 2ķ8˙ŧúgDØ]ö";ŌGr픑ä÷ ŽoAҎ˜˙Ã[(âėøã6>č;’)†qNvį|Аé…9GÍRWMUS&—O&@$‡KŠ/'¯M:rÆČąĖúÎõüũ…šôÍNœOŅ0Îj¨"Zņŋläš=9ŒŊa<— Ę"3#‹s.Ë?ū×īķOŖŸ~ÖFŋ˙>ã˜5ú|ÎÉΠov.Ŗ¯d(õD÷ôĖÕp:$I’$I'ģē7Øô׹ hØLE'C v–üu}'3ū{˙âĻv”•đāãõ\œ9‹Š7ŨĘÍ˙~/$ŅU2íÜ_ōË2'sŨ˙Ęx;Ę׹|Ũpnŋ)CK–4Ŋš’ûËÔą×3ŋ8“ÔæöŊõ,^L}ęđ<Ëøũ˜6á*nų^6Šņ:ĸå+¸oubOŨšI„iÆpë§p: ė{k..áĩ×^MĻū†-+ųUúxĻ]ĪíÅéĐđ¯¯~˜å ŠywËF^/œĀÄšˇrés÷ķķ’rî_ ÅÅWqëŧ)œNÔDŲôčƒŦŲ\¸ø[%ünE„›Į^ĪüÂ8īíŲFÉęį`΂ˆ ƒ/@|'O<ū*ŗ&\ĪOMuÕüŠdëúũE‰]v¯{˜#¸nĘ-LĖL…æāÚ=˛¸„q€jž]ü ņ)ã)žũ}ÎJ‡öWSąîaÜP{dŸ‡ēūËj~ˇb<SĮLæöâLR››x¯&ĘĻGfÍېnFČRZZZZĸŅ(ųųųŨ]‹$I’$é8|˙ŋũî.Ą‹DHOƒĻXëCßҎđĶąĩüîŽė4 ’’ņë_üs¨ížŠ8ĸE’$I’ÔŖœ3ūVæÆxîņՔŪī"ŠĮœË{åëØaČĸΠE’$I’ÔŖė^÷0˙™ĀÄ)ˇ0&3•æ†vŧņ8ŋ[푯ķ•Ú2h‘$I’$õ,ņzļŽ~˜­ĢģģéøųÖ!I’$I’¤´H’$I’$…Ä E’$I’$)$-’$I’$I!1h‘$I’$I ‰A‹$I’$IRH Z$I’$ЎJéî¤Ū%åü1h‘$I’¤^ęĖĖ3 ĨĨģːz‡–Î<ãŒ.E’$I’zŠīĖøœâ°))§¤0į?ŨÜõŨty’$I’¤.‘î9ÜūŊ9dfdtw)R–™ņ~øũšä ĖíōžRZZZZĸŅ(ųųų]Ū™$I’$IŌįÉᙊ#Z$I’$I’BbĐ"I’$I’ƒI’$I’¤´H’$I’$…Ä E’$I’$)$-’$I’$I!1h‘$I’$I ‰A‹$I’$IRH Z$I’$I’BbĐ"I’$I’ƒI’$I’¤´H’$I’$…Ä E’$I’$)$-’$I’$I!1h‘$I’$I ‰A‹$I’$IRH Z$I’$I’BbĐ"I’$I’ƒI’$I’¤´H’$I’$…Ä E’$I’$)$-’$I’$I!1h‘$I’$I ‰A‹$I’$IRH Z$I’$I’BbĐ"I’$I’ƒI’$I’¤´H’$I’$…Ä E’$I’$)$-’$I’$I!1h‘$I’$I ‰A‹$I’$IRH Z$I’$I’BbĐ"I’$I’ƒI’$I’¤DũaûöíŨY‡$I’$IR¯Ķ§OŸv?´ <ø„#I’$I’Ô›UWWˇûŲŠC’$I’$I!1h‘$I’$I ‰A‹$I’$IRH Z$I’$I’BbĐ"I’$I’ƒI’$I’¤´H’$I’$…Ä E’$I’$)$-’$I’$I!1h‘$I’$I ‰A‹$I’$IRH"]ŲøËģNáĻ'RŠũ(…xKWö$IĮį”čwz ˙wZ3—}°ģˑ$I’ô9Ņe#Z^Ūu ˙NŖúCCI=ĪÁØ×˜Â5žÆëûÜ'I’$)]ötqãŠTĖW$õh)pđ L]žÚŨ•H’$Iúœč˛ åŨƔŽjZ’“{ü}%I’$)]´8šERoņąŋ°$I’$…Ä… $I’$I’BbĐ"I’$I’ƒI’$I’¤´H’$I’$…Ä E’$I’$)$=:hé“wß{ī„–;aķ01§ģĢ’$I’$IęXĪ Zr`í˜˙eČMRaä—aÕ˜cØ"I’$I’z HwЙi߀ĸô6¤ÃÂožGáũĪĐū€!°đk0.rĶ!ÖÛĒaņͰ¤*ÉFr`ķ<(�fU~†bN2ūfvpoëë`íË0o#ė šĪ‰7ÂĒtČzāŗ}o$I’$I:š´ŒŌųļŦ!P<ũ)Û>s(”Í€ú×`îzˆ6@Z6LŋΆŦ%đķęOŲ¸’Rũg˜žąÍV� ž ĨŲ0r ąŋō03ą$÷Ÿx#ĖŨ ãˇ†X„ēNlž æŪ›ēģI’$I'ĩ´$- î 3ķ ž æ=OÖũĸË!/䯀ˇ}X ›*!mž ´tŠX=<Ø( įwĀļ”Ž‚qĪĀ“ņđú{ģ:Žũ‹†,ŊƙaXī˙m&I’$és ĮŽŅ˛ö(Sqę+Ą,ņį9Ķ`^.ŦŨ õš°l >FÛY‰˛´Ã7ÄáÖßÂäWÛ|–wÜ�•?ä­ø6\›Õū°XĖš!ąhīacqûÎĖ…ßΆē;;ncĀåĐtL%ˇAĶPwĖɅ †ÃÆÛƒĪ*gÃĩmNƒÛÕvėũ.Üv”‘@ŊAy%šmÎķ‚‚Äuš š~ ĪÇ=TO,ŽCĶ°q Œ(îĮœD;o„–Ųpfb˙+.O\×ģ -&&îÉĘ;a^6Œ›ÜĪk’Ŧã÷wĀsWÂmßę˜qÄŒ`äŝp÷¸ãÛ°÷ĮA[§Ām÷;ĘŊ0*8fb›ž¯)öģŖÍúE‡öģļ“�â‚QđÜm­× b6L;lũŖ3ķāÁ9ÁwˇŖs>ží‡ú˜“Ûē}đ•Á5žļm§Y°ņ.xlhđãÄĄéF1*¸gu‰ŋw$ŽĮWAũ´`TZŲ]°ņōŽĪW’$I’N„´,J›:ØĐķŸi]gŖ(ļŊ ßz fž iš0ōm—V@l Ŧē&æBŸÎvŒĀŨ3`~&Ė{†=�kĶaÕ ŅæarÜ× pŒû-=ų_ÅŖŗ`Ųl˜Ô Ķ ųŋ…ĨŠAW$ڈŁ4Xp9Ė_é?ƒÅqX<I÷@ú=P–‹¯ní÷ŽŲ°p ,|ō s+aÁˇÛ?lˇĩākÁÃxG˙-øÚ1.Ú ’?h†ęÆāį3  ô&ČĒ€Â_ÂČGĄ~(”Ni M.¸ –‚Ō§`Ø}° –^YŠ÷ŅgŦšŅõ0ō—ŋÖϲ›‚°cú}°(}˛îį“Ŧ#îũ¸J(ZĢ:›§” 3¯ƒúg`āĪ ÷!ČējŨåh÷v_e0ō§¨M`1nT7s[?+ TBYG#ƒō`íT  ×`isp FÚ' V͆‘{aŌ(|bPZœ8įcm΁ĩŗĄ°&øģ‘û[XÔLĪ›‘ÕAMˆÅ!íÜāzLŋ˛ķj`ᔠÖíĄč ŠBŅæäۖ$I’¤°õØ …ZˇũĒ›f(˙Kđ@ˇ¤ļuˇ4€D ë(˜éĀۯ¸§Xõ=ˆ%F4Ü= ˇ Pú šaá“đdl¯‚<ËöB~›ą0k3lŠ…į_…eu02ņĀ;â+0˜û8<] o×ÂĪ—CYĖ/hsаöØâ°Ē28šĨĪ$†m„eīAÄ� ĪP˜?–>K*áízxr ,Ŧ…yWw-X?]äį?]l;Ą"‡Õ FĄ,ŊǎBi"˜y5dU¤g`K=l¯„™k ­�f&Ög^ąŠ`M•ˇëáéaaM#–rs!ˇ –n…íõđv5üčßĄč ˆfˆÅāũXōu�äĮ`Šúč‹îF_ƒ%‰éiīWÁǚÖīĖ1īm=”6&Ϗd@a,Ũ #‡ļ^×ĸsĄlG'uTøß$Χ6¸?ßõũ (q>W|ŠaîSđ|5lŲs×ŖÉr“Ųū(ŒÛ7ÕžZXōŦŠĀŧáGš8‰Āĸgāí8‡e[!– #3‚Ÿë÷)ÖBœr&I’$IĮĢG¯jp ~đā]Đöķ/BÁF1Æ#æO…ų߀šÁųšÕåm‚jaÖã‰?'FŽ”6Í)ƒ´Ä•™XīĨ´íč†FX[ķ†ĐēHsû~ęã@ ļĩ9.2ƒ�!?˛šaíakɔŊšįB>°Ŋƒķ>¨Ü™ÁŌ-! ÁȏØWû°ĘļBŅS‰p 1jcÛúöo!z˙(O ÖQš¯†eÁļ—Û/ž[Z|ŠãžßŽ„ō, ‹^ ĻŠmŠ‘%SGb] čŪ6ëūÅļÚö?WĮ[ŋ3ÉÜÛU•0wœšbįÂČZ˜ģĻO Ftmʂĸ XÖŲŧ8ĐFfĶéŌ"E›īî@ˆÕĀļ6‡ŊŊ&'žŗs’ØÎ^(o|Ä Ŧ6yĶéH˛ŽÔÂļ6íˆ gõčß`’$I’NF=ī1%nûĖ+€ütˆVÂÚŊPO0šc\ĖŋŌĻÂĸCë‘\ -myXuW°ŽKᇠŸˆ˙ ŋeüœ`=‰e7ÁĸoÂĒ +3Øį˜oĒ9Ęŋ gĨĄūŽ#ˇÅjŽō°ŲtdģmëČĘRaՂŽmFtvîmƒ•îY�Ē_‡I/´žĶĖŠ03ĶÛ.P FúŒ<ėūRšė“•šĻÚ¨o<Ę}ĢĻöĖŋ:˜Âŗ0Ē߁OļŽ2i'™:bÉžÖčhߙ$îmųˆ]—˜&7ę߁íī@yfÁļsaX#”ÖvĐÁú-eSĄėi˜ž5z8ļŨÔēOZĐÔųȜcmĪJ F™~IęAdg#Ž:“ėĨ•$I’¤îÔŗ‚– xlLĪnũ(Hđ/÷ŸHŦß13īčMeåÁ0:ú¤AVöö°û~,¨€˛‚āØę‚‡ėG}0>šúP #Ÿ<ōA1 Faī'@}Đ3—´. üIģņcŋ4Šģ–CbíGĖ_“fÃĸá­#"ˆC}sđjæé¯ŲF}c°OŦšuÆ!YGŋŽīWχƒķ`ŪĩÁš8Ņ{:xmx2u„(™{{ 2Ēä�įBŲú Îĩ‰Ņ"ۆacy'}Œģ$˜ 5ķÅÖQ:}hÍ"gŌq˜’ėö4ڏ6ę,€‘$I’¤ĪƒĩFËĩßh˛$­ –ŊåÉŦŅ’Ĩ?ĩWvŧyXĐ<ĖFĢĄ>ŠÚ..›+įĀmš¸ōw‚>Ķęa{mëõņÖ_?h5K:a”��ŅIDATT§'FŽ´i7 üŗ…žå@%Ė{&]=íĮĄŦōŗ ZÛū<cqؗXĪf[# ;l*JQA$ Č A>äí*øÁĶÍ ĻŅ|âPx“L!JęŪ6ĄJQŒËĩīĮ–Ŋ,‚;.ą>Kg߃ŦÄ‚‘b‡LÕ>h)ß „Â6!րá°q\ųtÛI ū>•WĩÅⴆ™ }úA§$I’$õF=*h™4ôĶŨŧuhn2oЇų¯Ā°kƒ×0O#rᚡp÷°øK°ę…`$ˁJX\ķĻ´<‘wO„qiÁ:ÉØō ”ĻÁŌb¸"dÁĩWAųm°đŖrŽæĀX´7XWfZ Č“]{”v"õtOށĩX|]ë›|–žŧÉfŲ•pA Î ^ĨŊm\›H–U@îpXXĐz}į%°ËŊ–Í€; `pVĸÍĢ!ŋĘÉC=0lŒČ jIĻŽ°${oK+ĄđĢÁú,e‰ĐnÛH Ķŗ *wĸ|o°ßÜŧāšM쿯ƒõV ™Øō2”ĨÁĸ)pM.\QŧÍ)ŋ.XwåXÛ7Ŋ|÷ÃY0 æLq1X”ĩ´o/DSaf"ë“‹Žî´ėNŚ€ (ÜI’$Ię.=jęPî§|`͏E`X’o2yū)(zæ'^ܕ<¨m{æ?�÷z@Ã�ŠaŅė`„ÁļJ˜ūlJv*Q=Lz�~Ö~7Xl´ē–> ĒŽûTÛųų+†…3‚õlęë`íz˜žņŗĩÛmaŪz(˙&,Ü ˇVÂûP´}-ø<­9¸3€§#I6=s3`Á 0/Ĩ[aîz(›–1q˜-ëazÃÂL M›Ī'ö_ŧÆ] eCaæ}°<‰:”ĖŊ-ß|5xãŌĄ)röSŠŠÚ„/yūX˜ æÂÂ&(}5XgR&,šK—S¸Æ=,˜ģj¤Å‹¯ILĒ?ööI‰íko žûÛ*aú°üPmU0ķX\ MS ēüäÎ>žëĩ}+Ŧú ,œ Ķ_€QĪßņ’$I’–”––––h4Jnn’sa’”~×ņ§&Ũ ĶSąS3LúĖü1L:ÚžÍ0é§đäqWĄŪčĖ´ÖW1 žĸ_ƒq?‡§}Ũ¯’ĐôW‘$I’tüĒĢĢÉĪĪ˙äį5uhņĢí׌čP*äf@é1FƒÔWĩíŦ>ŋF|ęo‡{SF …ÅWCt+”˛H’$I’N 5učų5Ŋ&ɝ€ûē´õ[Öä,,†y™Á"¯Ĩ0nMī[X’$I’Ôģõ¨ EúTâÁBēO&ŌI’$I’ÔEzÔÔ!I’$I’¤ŪĖ E’$I’$)$-’$I’$I!1h‘$I’$I ‰A‹$I’$IRH Z$I’$I’BŌeAKJW5,I!;Õ_X’$I’BŌeAK˙ŒhéĒÖ%)$-00Ķ_V’$I’ÂŅeAËŖSšIņ_‰%õp§¤ĀŠiÍŨ]†$I’¤Ī‰. Zž:č Ī~ûo Čh!âJ0’z˜SR`ĀZxaöß>ā`w—#I’$és"Ō•uĐAĸķte’$I’$I=†cM$I’$I’BbĐ"I’$I’ƒI’$I’¤´H’$I’$…Ä E’$I’$)$-’$I’$I!1h‘$I’$I ‰A‹$I’$IRHNˆD"<x°ģk‘$I’$Ię5<H$i÷Ų)�iiiÄbąn)J’$I’$Š7jjj"==ŊŨg§�ôíۗ?ü?ü–––n)N’$I’$Š78x𠍍455‘Ũn[JK"YųøãyīŊ÷8pā�üqˇ*I’$I’ÔĶzęŠôé͇ŗÎ:‹SO=ĩŨļO‚I’$I’$}6žuH’$I’$)$˙ĩ!OŨ����IENDŽB`‚�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/login-grant.png�����������������������������������������������������0000664�0000000�0000000�00000140052�14156463140�0021603�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��Z��Į���ž&4���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨ{tTõŊ˙˙ĪL´MäôLžrH–1Ã2)_&ÔᇠU’|•…`\Ŧ\ŧakT­Ņ ęąØŠ åĻ""AĀˇ xJÂZ%ã:ŋ\ž- įk´‹ô'í13\öī\ČL&ËCđųX+ ™ėųė÷gĪžŊÜ/>ŸĪ`†!������ôÚUŅ.������āJAĐ�����`’Ģ[˙ãėŲŗúķŸ˙Ŧ@  ŗgĪFŗ&�����€ËÖßũŨßÉjĩĘfŗéĒĢBĮ° 0 Ã8{öŦūøĮ?*&&F×^{m‡������ĐėÜšsúę̝ôÕW_iđāÁ!9Ę�Ã0Œ††]uÕUēöÚkŖX&�����@˙ņˇŋũM’ô˙øm¯]%IMMM翚kĸS�����@?tÍ5×č̝ž yí*I:sæŒ •ĸ������úŖĢŽēJgΜ }-Jĩ������\qZ������LBĐ�����`‚������“´������˜„ �����Ā$-������&!h�����0 A �����€IZ������LBĐ�����`‚������“´������˜„ �����Ā$-������&!h�����0 A �����€IŽŽv����� oÔũž^īŧŋCûī¯tîÜšh—Ķ#W]u•žqí5ē÷ŽŠ˛ßír.Š-�����\ę~_¯7ŪÚŦÆŋū­ß†,’tîÜ956ūUoŧĩYuŋ¯v9EĐ����ĀhËÖŅ.Á<H†Ņ/úDĐ����Āč¯û[´K0׀úī¯žŠvEĐ�����ú…ū0Š �����Ā$-������&!h�����0 A �����€IZ������LBĐ�����`‚–žrbŊ& .įĸ˛¯Į~;qbũ$ îÔĸ’hW����čžĢõ­Fčģ§höĖzôūš{÷DeŪôĪú1ŅŽíōtu´ čštĸh‹^ß^,OÕQ:դϘÅ LŌH—Ky÷>ĸI΁Ņ.�����Ķ\?BS§ŨŽąIą!¯Ÿ>}Z#ĶFë{§}Ē*Ū­w~}\_EŠÆËAËÅ4UjũÜyZí9%Å ÔpWļ\×[eU@_ü§Gž][äŲĩ]īĖ]§Í ĶE ����čīŽągéÁYJž&ü7>ũúõ7TŦÖŋfŨŽ ž¯'n(Đ/6WčOŅ(ô2tÅ-ĩī.–%~Ž˙ÜķŪ6ŠÕ“Ú>wĻV{š4hŌ ­[ž§áaIJĶŅ]Z4o‘ŠÖ?ĒE#Ĩ5،l����ôc˙ĻŲá!ËW>ũĮŽ-Úø§Z^¨Ņū͟Šzė =:)WķϜŌË;Ų"õƒ å§?ũŠÎ;×ösöėŲļ?ŋ?ÂĐU:'‹mlén;{NÆą˙kÚž›Š^Ö O“bœKĩyUž’"l3|’ÖŦ hé–r]ׅFOUjûę×ĩĨØŖ§š¤˜JreiÆ#K•įŒ‘TŠãĻiËu U´knģ}žĐúėl­>ŖIë+ĩĘŨŽÍĸr.(‘kM™Öoŋŗ^´õČUŊ^ MÚŦ˛UéēQšbœĻmiŌ¤õeÍīo:Ē]+VčõŨU:Ņ$ Lr)oárMėÂ!����\.bô‰ˇ+5d$ËWĒú`Ŋ6ūŽ)lÛ3ōÚĒõņę˙kĸ˛¯ŅŽ?^ÂR/S—}ĐröėY%&&J’ ō§aüV˙đ/˙*i€ũŗÎžČ0Ι´į&•ė.R“b4é‘C–6Ãķ´bEš<U¤Eß[ ]§’”ˇPs\×K_x´û­íZ:ĶŖŖëŠ´4Ũ)ˇ+F[ŠJTŲ4WI­#hNyTōEŒbbšä)9*šĪ'*e%5i¤˛]1Ō—íw؋ļfĖVŌîm)úPEĢŌ•Ō‘Jí.>) ĘĶŊnI:Š] ĻiQ‰”4é­ČJ’õ v??]•7tá¸�����.˙0Bß>_čœĩDkZ˙zÚ§ĒCĨ*ū÷ ojŌgûŠU•6Mé˙ŦŊ|Ļ3íŪ9!ëŨžuKÄ]í+ūwí-ū÷>čDt]öO:{öŦ¤ĐĨõįėŲŗ:wöŦšNÕëôŠs§ƒ2Ν5iĪGåŠj’b\Ęr™ĶbŲęįĩëÔp-ü Hk–ÎФėlMšąTë?^%ˇNhËķ[tR’+Ë­˜Ļ*•zÚŊŲSĸ*š”=P§Ē<:ŲŽÎRĪ)i¸[Žŗ–zŪÖpÍČ.5iwQXjYš]Å'Ĩ¤‰3䔤ʷôrI“bÜËõÁĒšĘkí×ļG¤Ē“����ô×Ü0T‰‘~ņ•OUŋųũöúsã]s~ÜÆˇnø']ũ•gĒëÃŪßĩ/B˜rĨ†,R? Zڇ+íΜ ęÜŲĶ:{&¨sg‚:w& ãœY#ZžĐ—§$Å ÔuĻŦp[ĻwŸ”’Ür_J§NĩûŅHMtÅHG‹TzJŠqš5RMō”Tžw‰GMI.Ũ›5R:ęQUköqĒJžŌ —;ōÔĻ^´•”7Cޘ&•l)ŌŠvmVn/ÕI W^^ķH˜N*FŽŧl…d=ƒ&j†›å��� ŋˆũ‡úû°×N˙Š\ëūí ­ßšOÛ6ŋĄŋø†Ū8Ô¤‘˙ōĪú˙4B“r3”ú-IąßÔˇ"Ė› [ŽäEęS‡Îœ9ĶŦH ųīŗg‚2ÎœŅšĶA=”Œ&--A“čđģSÚ>-]K+Ã_wiUåMŠ”-œüOh’ttŊ&Ĩ¯īdŸŋ×Ņ/$ wÉ=\Z]UĨ“rjPËH“A.—œ#c4ŧéuWJŲéR“§DG5Pŗ†Gnr`/Ú8Q3\+´ĀķŽvŸœ¤ƒ$ŠRÛKO*Æ9G“ZģvBŌ@]}xĮc””4P ‰i�����ũĮWĒÚˇ[˙÷/gB_>sĩ˛fjŲ¤°ÍÃ6kÕ>Xš’CŠ-íG´HĄAË閑,gOdœiŌõņŌgF'Ÿjˇ]¯ëJ:yR§NIĄC5btÃÄ<MJ:Áœôė’įBŗdMj’¤á3´fĄ;ōc ­×))I’’är ’ļ{TÕ4CƒšĒä9#×B§4H9č”JJJéÃUYėQSŒ[îŽëÕļčM[1ʞ‘­˜’]Úžû„fĖMj™6Ŗ‘OÔ ÖŽ5¯:sŨu"h���€ūĄņ/§tZ˙2Ēåôé÷ŲŠĐĪ×wū‘ĻļN¯hüĢūëm_éKĢË>h‰4ĸĨõĪͧƒ:{æŒÎŠũÆ_ô­Öß˙Y“ŪŧėŽ*íö4iRvû!Fé3V¨}ļQ´¨Hž]hîēēN’b’4Ōín ):ãtģ4pK‰Š+%÷—%:3R÷ē$É)—+FÛ+ĢtJRiå)Ÿ2äîĢļÜwjâĀ]ÚžŊH'æÎÕÉíÅ:ã֌ą¨)|jI§žø˛ã‹����€ËŌWŋ˙Ē—CÉm¯\Ŗ‘ˇŒUŲį‡õŲ_×ø}gđųŋúëūŸž¸Du^Î.û5ZÚ-á?§O7éܙ€ÎžčÔũŽˇ) ŸMÖsî;'jšT˛zĩĘzÛØĀ”4PÍkĸDú}xJáΒ+æ”*KĘSėQS’[ΖlÃå)U•ČsĸyM•áŲŒ1Ĩ­tÍČK’Nl×öĘ}X|J1ŲyjŸ;% K’tJ'žOZštâŖY���� ßøËoõë߆`¸vHļ~đĖ3ZķŌMú§ķ¯˙ķØ %ˇŨ‚ûTVúYg3‡žVúMĐ"ŠCĐ tîĖi;ĐéĻĶúŗījÉ´5Z$Ĩ/ÔĶ“J'ļhŪ´Õ*‰85č”*ˇ/Ōęĸ&)fātMĖ(5•č­õ'BÕTĸE˙Û)įÜ]í&Ų¸”52F_TžĨŨžS8rdÛbˇ]N]ßäQņúÕpeEzÜPˆŪĩ5</OÃuBģŸ_­ĸS•}gčø™¤‘#5PMōl]4W'ļčO„a.����€ËT“ūŋŨût$âd‘o*ļåÉĪWß0V“ū%ļí7ūÍn˙ņ’xŲë7S‡$u˜BtætSķ-gNë뙀 ¸ZįĖ Z4PŲĢ>Đ*ÍÔ˛]ë5wÜ%šÜr&]§ëЗ_œĮSŠ“MRLŌ$­Zŗ*d¤G¸ô…Ë5Štv­ž¤i˙ųˆō˛’dũÂŖŨomWÉÉAš´ŧũS{ʕ¤ĻÕE*jŠ‘ģũ",Ir\¯ĸŨ%j”'W¤Į …õŖWm%åi†ëu-õ•ÍĐŊáëÁ¤ĪÕ#ÎŨZQ˛LĶTi†{¸tÂŖŨÛ+u+I*ađ����ôŠĐÆMņzpV†’¯i˙‹k”üíu͟ŽVîŨŲmŖYūüÛũbįq™ĩGׯƒ–āé@sČr: s§ƒ0✠ÃĖ E’’4iÕAšîÜŽˇŪŲŽO‰Š<MjRŒb]¯‘îš3q†ō˛“.<}G’fkÕĮČĩúum)~]+v5‚IrMԊŸ-Tž3´…A.ˇ5­×Išävĩ˙SY#c´Ģ¤I]YrvĄŊkk &æš´ÂSĸë'æEØ&I36o––=¯ˇŠļkE‘40ÉĨ‰Ë7ë‘ĻeJ'h���€~åĢēbũėõ?ję´Û56éüȕë3ž¯—2ZūrÚ§ĒâŨz˙×Į՝2/K Ã0Ž?ŽAƒ.ļ<ktÜwß}š0a‚¤ŽA‹</éŽŊ]$;sZg˙­ĮĒuû“ûĸXņ•Šlé8ÍÜ}ƒVüj‹ō.ĪS����ĐÎĸgVšĐĘÕúÖ )éĒ˙ᛊģFúĒņ”Ž­Võī>“7 ĢEŦúɒKŋĶ 8yō¤nŧņÆļŋ_ļ#Z6oŪŦšš9sF;wîÔšsįtöėؐ?ī˛˙ˇŽ”Éķ YbŽÕß[Ŧ}0ĸ:ē^Īī>ЁŲË5‘����žFÎčŋ~˙[ũú÷ŋv!ũÆe´Ėœ93Ú%|Í5éhQ‘<UĨÚže—ŽĘ­U_äéF�����|Í]ļA ĸí •ŧžHĢÆh s’V-_ĨIŒf����ā‚ZЉ$ÍŨuTsŖ]�����ũČUŅ.������āJAĐ�����`‚������“´�����€~aĀ€Ņ.áĸZ�����¸}ķߐ #Úe˜Į0ôk¯vEĐ����ĀhFūTŠŒ�鲚ût™#h����ā dŋ!QΙŠo~ãũbĘMg  o~ãzpÎLŲoHŒv95Ā0 ãøņã4hP´k�����čWNž<ŠoŧąíīŒh�����0 A �����€IZ������LBĐ�����`‚������“´������˜„ �����Ā$-������&!h�����0 A �����€IZ������LBĐ�����`‚������“´������˜„ �����Ā$-������&!h�����0 A �����€IZ������LBĐ�����`’Ģ[˙ãĉŅŦ����� ßąZ­!o Z†~ɋ�����čĪŽ?ōwĻ�����˜„ �����Ā$-������&!h�����0 A �����€IZ������LBĐ�����`‚������“´������˜„ �����Ā$-������&!h�����0 A �����€IZ������LBĐ�����`‚������“´������˜„ �����Ā$-������&!h�����0 A �����€IZ������LBĐ�����`‚������“´������˜„ �����Ā$-������&!h�����0 A �����€IZ������Lru´ ���hĪ_īQQá^—VéHWž†FeQŦÍĻD{ŠFeŒ×9ã5&1.ÚĨ��t0Ā0 ãøņãēņÆŖ] ��ø ԗę—+_Ôkû)xŅ­-˛g>ŦeKfj\ĸõT��YxĻÂÔ!��eÕ.QÎmķĩĒK!‹$Uˇ˙UŨw[Žzčã ��犠��DQ@G6ÎSÎcŠŽk K¨ā1íx,Wŗ7Vļ��€ËA ��ˆ_áãšŊōS5öĒ•F•­œ¯G }&U��Đs-�� :ęˇéŅ'>Qƒ)5¨ø‰…zˇŪ”Æ���zŒ ��D_ģWžĸ˛žLęLđSŊ´r¯ü&6 ��Đ]-��āŌĢŨĻĩû{7a(’ÆũoéŨZĶ›��č2‚��pÉÕî˙HÕ}Ōröė¯ë“–��ē‚ ��\b>.=Ög­W—~*–Å��ŅBĐ��.ąĪUŨ—ƒNęĒŚ¸�� ZZ��Ā%æ—ŋ/WŦõ7˛ .��ˆ‚������“´��€K,Nqq}Ų|Ŧú˛y��€ !h��—Ø`9ė}ØŧŨĄÄ>l��āBZ��Ā%¯1É}Öē=c´âûŦu��€ #h��—ܐĖÉrôIË)ššŲ—Ãe���.Œ ��\zCĻëĖXĶ›ÍœŖ{†˜Ū,��@—´��€(ˆĶÄ%?RēÅÄ&-ŖĩxÉÂ��QEĐ��ĸ#qēVũôVŲLiĖĻŦŸŽÔ=Ŧ‚ ��ĸŒ ��DMbÎËÚ¸d´z7‰(VéKŪÔĪrLĒ �� įZ��@Y•:{ ^,{OĻY’5õÕ­Ú8Û!Ģéĩ��tA ��ˆ2̆äŦTá˙YŖ2“ÕĩŧÅ"{æ­ũ?Z•c'd��—†aĮ׍7ŪíZ���ä¯õ¨h˙^—VéH]| Ae‘ÅfĶ{ŠFeŒ×9ã5&‘eo�@ô…g*-������=žŠ0u�����Ā$-������&š:Ú��€¯‘€OGJöiĮūC*¯ŦSm}ƒƒAI’%a´&ΚŽģ3ĮjÔEÖ_ņ×WęāūmzoĶ^•y[Ūo‰U|b˛†8rgNÖDˇCņŦ’ ��.1Öh��—€Oå[_Õō5Šĸáâ[[lÉJu$kHB‚ââŦ˛* €ŋQõŪ:ÕVרŽ!xņFl#5uÁSZœīT|ī;���‹á�€KËWĒå-Ô†ĘÆ¨ė>Ö9C?ûÅSGÚ��ú�‹á�€K&PģMŗŋ7?j!‹$5VnŅ}ߛ§wkQĢ��|}´��€žQŋWÎxN%]˜*Ôįi،Įĩģ>څ��€+A ��čuúåcKT|9„,­>ŅĸĮ6Ģ6Úu��€+A ��0]ũÖ%zО Ö^bÁĘW´tĢ7Úe��€+A ��0WĀŖ×ÖTéō‹Y$)¨˛5?×a–k��}„ ��˜Ę_˛YE—Ķ”Ąp ´ĄÄí*��ĀŠ ��˜( ōÂOŊg uEŖī¯ƒZ��@_ h��&ōęHŨåŗHRcux���č -��ĀD^Õõ‡ĩfŊĮZ��@Ÿ h��& ĘßæäüL��}‚ �����Ā$-������&!h�����0 A ��0•5ÚtI˙¨��ô?-��ĀDƒíē ŪĻÄh×���ŽH-��ĀDMÍIŽveĪ™ŦÔh��ŽH-��ĀTŠ Vj‘36Úet*Öų˜V-pDģ ��p…"h��æ˛:õā;[ĩvŪ­r$ÄĘíz$IÅ&¤(kŪŧ3WŖXĸ��ô‘†aĮ׍7ŪíZ������ú•đL…-������&!h�����0 A �����€IZ������LBĐ�����`‚������“´������˜„ �����Ā$-������&!h�����0 A �����€IZ������LBĐ�����`‚������“´������˜„ �����Ā$-������&!h�|-Y)û°TĨÜŋWū‹l(œ§”aОßō‚Ę{ģãúÍĘ–*ûˆĮUÔöbĨ–ß’*û0ˇ–VôvØĢŲ#ReÖņ'å&ˇ2nģKķŸxEī–Ô]ô˜ô;%Kä–*ûMKt0Úĩ\|:üæ<š†ĨĘ>,WoÔFģ��.WGģ���pŠYdŗÛoiũ{@ū†ųęĒT\WĨâoišũv-{õYŨ㈋fĄ¸ ęčĨĮ–hCec´K�ā˛DĐ�Ā׎M_-Đ2GØË~¯ĘK÷jíšŸĢ¸nŸ–åSŨÛĩ,°Íꋗhū“ŠēŅ&÷ŧÉ lÜĸ˛`´Ģ�āōÂÔ!��Đ,.AŖræę͏ ´<Ã&k´áąįTtÅÍ#BĪTžs¯ĒãÆjé{´qáXÅGģ$��.C-��ô’ŋz¯^úá,eۜŪŧ–ˡ2ōŅō‚jSÖ:ņUlĶō‡îRÆÍiÍknjHWƤyZ´ąTõvÎj×=¯>Ģ;l’öéĩ­uk*ÛĻĨ-5Ĩ´Ôäēí.=ēz¯jÛ×ä? ų7]lũŸ>ŧˇšo9oļßW@õ%›ĩčū\eܔֲ–LĻrî˙ąŪ-ķ™×ߋčÖņ¯}E™mëųøUžu‰ōosˇŖŒŧĮõF‰7ėM=ÔÜöėķԜ‡Úõųæ\Í~f§Žtr2uīük]č.ũ˛Ū¯ÃoÎSæMi˛˜ĨģpH3ŸŌŽ×éûiŒr� 3-��ô‚¯øqåä-ÔÚŊ•ōÛœĘž2MSŨÉRõ'ÚđäTe>´Wõ=n= Ú­ķ”3í9mØ_#%ŒÕÔģĻijĻSVī!íX9_™y/čp_Œ8‰¯r“%IÕ;¨ũZ§õķ”3ã9ŊˇŋFJp){ÂdŨáv*Ū_Ŗ=ë*'qcuwFŦ¤TFŪ—īí¨JŠŠ9ö–ũ:ü|ž2įŧ¨Ĩ ŠK¯ŠwMVļÃĒúŌ´lÆĘßX­žČ™ÎëÉņ•U’ü *_=[ųĪP}l˛ÆdŽUZŧä­Ü§Usō5ŋ°}Ēa•ĩeŊŨGztÚũ˛BŠOĢ,WŠâü5*y˙iåŪÛî¸ļčūųg‘Õ*IAy ŸĶŖ/{°;•ž–Ŧ8ëŎ‡UŖr§k ��Ä-��ôTũ6=úØ>yƒ6eŊ¸U?ËMPÛŊĒī€M[ û—hŅV§ļæ'tŋũÚÍzôųCjĐ`MũųV­Ęj7QÃ_Šå÷ÎŌ†š-Z´z‚ö˙ÄŠ‹Ū'wSjæhŲÖSCũ§*÷ĪՐ8IRŊļúĢôį Bû¨ÔōīåkCÍfŊT0C[ķã%Y5&wŧl{?RCéN•ËŠQaûņíßĢō dqMŅÄĖî?§ų›jŒ­ĨīŦĶ÷į{į/{Aų÷oQŲʧõËô=žÖŒYzrü­Öæ?čĨ§\ŌŽvŋ>“¯ûŪ?Ļâį_ÕáĖ•ĶÖ-̤ Žl|E‰9oĒô§mĶrĩÛ4Ús*ŠŲŦåĶUĐzĖ{tūĩnáÕî÷Ĩqok•› @��˜‰-�€¯ĩ`ŲsĘŧŲ-×~2žņ(ŌzŸå›ÖĢ,(Åf>ŠUíor%)~ŧ–-š]ą ĒlĶ6õäéˇå›ļ¨:(ÅfūH˲Ân†ãœZŧpŧb%y ˇép_ íHp(Q’‚ ōĩ °ëîWëĨWjEnXxduęî))’‚*/ĢjmbMŸŦq6IŪCÚŅaúOE…• ĘĸQšˇļ„ ^}¸nŸeQÚ•!!‹$ÅĨ˙HËrKĒ҆­ķúĻwĮ?¨!ŗž Ģ=Nã>ŦŦXI ‡´ŖĸŨ›Z6 ZĮkņO2BÖ>ą™ŽÅųɒ‚Ē(8Ô6BĨ§į_ŦE’åwĖŅ2B��LGĐ�øz 6ĒĄĄáÂ?‘b–:.û\’EŖrÆ*ŌlЏôņe‘TWŠōn/)R§Ã ’¤ÔĖČí[ĶZÚoŦTYĮeTL`išy¨íX4Ę=AwæŽ×kķīü>¯ęë›üjžlôŸŸÖcuijĻMŌį:X6}¨ūíđ%‹KwgļÜôû=*Ž”¤Ũ‘i$UŖ2]Š•ÔPQŲŖëâz{ü“5ÎmīøĻ¸Ņj~šAĩÕ ~mI¯q†&Ĩf8+Iu­ũíũų7*ctÄ÷�€Ūaę�āk͒ąZžˇ'\đ†3P8OÎĮ…jiPW’‚ĒŨúœ-ŽôΆ–Ņ^U{Ĩî=ĸĨĩ}›:Š.ÎĻÄ8I -#NĖžBhlYL5VļØv¯ûĢõ᚟kCá!U7tíŲžcroWÂû[äŨŋWåOŸŸ>Tŋ§*$Åē§k\k7ŊĮԜ |Ž=ĢWÄ5tĩl[ŖzICē×ŗ.číņO#âlą8ÅĮ[$åķ6H Ũ(>!!ō0[˛%U76Čį—×ÛķĪĸøFŗ��ĐZ��葀-C6ŧž} ŽL¨ üŨžÚĶÚ~ëâĨ‘X[~?L^Ĩ%PWŲ|ŖnIPbۂ!-Ÿ6OꂲØFjęŧ JŗÛÛŧo˙ūW´xįąŽĨMVvÂmđОŠ§4*M’ŧ**Ŧ’Ģqwĩ•´Œ†iPÅŪ}‘ƒ–vÛúÍīēz}ü-ŊĪ*ĢĨx7‰Ú�� �IDATy=–@ ãI’hĩ[kc­uõöüŗšČ��€$‚��zČ*kœ¤›îŪRĸé}ĐžUR°QîĮ[´Ūl[eí< čĄ€Ę ?UŖ$KšKŖZš¯Ũøĸ6ÔĨ„Ézķã•įGĄ´đ5l–"-rhjN˛6Ŧ;ĻĸÂJ-KsJõ´§R’mŧîNoW낲–[ĩö?^WvTŪ˙Čo (lūMÄ÷4v˛ŗļ"ZÝž>˙��@OąF ��=’ ‡M’üĒ÷öÅķ•m˛'HRŖęŊ,đâ÷ĒŪßŧmbjtAõéĩÂIÉo]¤6 #žæÅHė93:„,’T[)di–:e˛Rķô!Iõû?R…¤„Ėéힾ#)!Y‰IÁĪUßíĩmĖŌËãü\õŸëí—Ī×<Ũ*Ūnëđ[_}Cä€ĻĄe:UlËtĨ>?˙��@O´��Đ# “>XRP‡ )ō­n{TëëÉ#ė-íK兟Flß_v@åAIļŅr›šH‰ßŖåŊ ˛ dI™ŠÅ9mķ†Ú6ąÚ"¤,žŊÚPØq×6CÆëŽIŪC*ŽŽSQa¤ÁĘÎu†nį’Û!I5Úŗŋ“I1õ•TĢG‡ļKz{üëTâ‰ĐøĢTR'I6ĨFXÄĨąō€Ę#ôŠļ´R ’,gËz4}}ū�€ž"h� ‡RgÍUēE –ž¨Eua#|:øĖÍ~xļrŸÜĢž Ėu×LĨY¤`é+Z^ւ¯TË_ūD˛Č1kĻÆôŧį|:RøŠfož6ÔĨØŅZöęÃJmÛ N‰-Ŗ0j (d†Ϊ—zEõö”æįy"ôŲŽ‰š#%ĶÁ­Ûš§ Ų'hjZøv ēsö­Š•Tąf‰Ū¨‹üŊôØ=0'_ķˇöÉã–$õöøUžîEí9H~•¯ųš6JJ¸]S#-^ėŨ§—ÖT†žKõ;õRÁ1Ii{vߟ�� gXŖ�€žJœŽU?õ(˙‰}*~2W]ã°ÉhЏ§ų‰< ˇjÕsSē÷ĀĄVCfjÕO<Ęōíxø•ģÆj”=Vj8ĻÃeŸĘÛ(Ų2žŌĪfGxŒđ5h÷cš:l9˙J ąA>_CÛcœcí“ĩėįĪęÎ!ĄëˆŒš5Wé[ŸSYå‹Ęäҏ4[s=%Ÿ*ūŧļ.iÔĸÛ_TEŨ[zô‡^Ë™ŠÅYįëKĖ™ĸ´ÕUĒx‹$É1erģ įŧ¸œ•úYÅlÍßôŠVMËŌ×hĨ&ÄIu*/Š’7(ÅēžÕĒünöŊq¯Ũâé|!Xkœ˛Ÿ+вtõîøĮŪĒ{2j´čļLmpģ”j“|Շt°˛AA ÖKæ´­{ĶžcĘdYˇÎRF‰Kcƒe Syé§Ēk”,·ۍ.Rߟáęwę҇ļ´{œļ_õAIĒ͆‡sĩ§í|JÖ}ŋxYw&šąS��ú‚��z!1įeĻLĐģk6k‡§RE;´ÄʖčÔšĶõĀŧ JŊĐŗŖ/bHîëÚoߊ×ÖmĶÁŠCÚíil^§Ã~ģȟŖr|4udA5ÔÕ(d’ÅĸظdĨĨÖ9ĶugN'í&NכīHËWoVQÅ'ÚQc‘ÍîÔ¸oęķ3”(ŋ–=~@ŽųTÕû?‘5mzčûãoÕÔ´Tá JŠŠ9%q÷ôVíĪØĻ×6}¤Ã‡´Į”,6%8nÕ}ųsõ@ŽŗBP ŪĪ/đûƐéH=>ūĢԟۍ;¯ęĨM‡ôáū-ąJpŪŽģ>ŠĶ;ŠÜ>]?péĩ—×k÷~ŧAYl)rįÎҞ…–{õųųÚ§c:RSŖŽcˆÂΧ€ę˜­�ø`†qüøqŨxãŅŽ�� ĢßŦÜ[_T…åv­ũíËĘîâÛ>‘Žûv6ĘņøÎīî%��Mᙠk´������˜„ �����Ā$-������&!h�����0 ‹á�����ô‹á�����ô‚������“´������˜„ �����Ā$-������&!h�����0 A �����€IZ������LBĐ�����`‚������“´������˜„ �����Ā$-������&!h�����0 A �����€IZ������LBĐ�����`‚������“´������˜„ �����Ā$-������&!h�����0 A �����€IZ������LBĐ�����`’ĢûĒáĪ?˙ŧ¯š�����č‘Áƒ÷iû}´ôuá������—Ļ�����˜„ �����Ā$-������&!h�����0 A �����€IZ������LBĐ�����`‚������“´������˜„ �����Ā$-������&!h�����0 A �����€IZ������LBĐ�����`‚������“´������˜„ �����Ā$-������&š,‚–ĸĻÉ>ė.ũ˛žå…ÚõĘ–*×3ž¨Öu%ķĖ’}Xšæĸ] .ÕzéļTŲoyAå—j—~^ē?GÎОß4Oúē÷öÖķxvaËyÜríp>q‰¯\ŗ����´¸,‚–â4*cŦÆ8b/Éîj ×ëŨ˛Nîđ|•zãūLĨ KUÆķ•—¤žKÁjsĘír)-ŪíRpA^ܸYë/žew]đŧŋDĘ×,ŅÚŌcŠsĪŅŌ…Ķ•Õrzî_ŗ����\žŽŽvÅOЊˇ'\ĸUkĮšWĩ;sŦîIųM}É+Zôä[*÷[.Q-—NœûGÚčŽv¸(ß!ŊļōįbŸŠq‰f6ÜųyéøU[÷š¤Ņzāšéžh•a†KzÍ���p9ģ<G´\Jū‰4Z l‰ræl–Īõŧ ~1]ũųũW ÚŖÚžh¸ŗķūR H˛X×_G˛����@˜žZ|ũō‰YĘŧ9])ÃŌäŧå.=úfŠ.8aĄŗõ|•z÷™yĘš9])ÃR•r“[9÷˙XīVøC6+úašė#Ņnv??O97§É>,UΛs5˙MOÛžë7Ū%ûŋ<­’ ä]7UöaŠĘyŗŽåˇÉšøj ˙mJĻ34¯áÖĸ2¯ŠžšKŽiĄSēØI ÔîÕōûsä‘*ûˆteŪûc}XíoYÛf–>ôˇëΰ4=ZÜĄŊ›—*ûˆĮUR_û5Z*ĩôæTŲķļŠļbŗfߒ.ûˆymmKÕŋĸGķZę–&×m͟e}Ø2/ūęZzŽ2nj>î)7e*įūæšģvđJ۝3Оp+3īqŊQéŦņęā›+˙6ˇRF¤*åæå?ąMåvՍí6.iÛÎ>"]yhyaÂWŗéj?{v<üÚ}šsöŠQÚ1'Uöan-­8˙û#/hū¤ĖæõMÚ}ˇÂ?p>īۊÖģOĖjŠ9MŽÛæiyąˇc•Õ;ĩüĄÜļ횏ëfžØŒ¤Š”1lŒ{$é˙3Uöaŗônۗ3ôhũ~tųęĐé‹ĩPŅĶe1ĢÃ:1‡ŸqËię o›ō‡ĨĘõDŠŽY]ŊĩjūžįĘ5"M)#ÜĘyčÕûuđ‡i˛k˙]ėDČĩöüšûŌÅÎŨéʘôˆ^*¯b›–ŪŸ+×?߀v?”&ûˆyÚ]_­wŸ¸Ģí|pŪr—m­V‡Ōģ|ũ ¨ļđÍĪkwžwõ���ĸ¤o§ųöjū÷Ē8ŦŦ܇uŸ]Ē+ŨĻw_ž¯ÃÕkTøoãģ>RÄw@‹Ļ-ĐK[i6ÉWŠ=›ļiŲŊŸĒîí-Ko^oÄjĩJÁczīĄ 8æhŲÛO*^^\ķ´Vŧŧ@ø=ژ¯øĖ§´6đs-}ų”ų¤VLIVœ#Ąyésĩĸ]ˇĘ*É/ßÖ´ŧÆĻ‰ –Ũaëv_ä? E3j°ÜŗŸÕTGŦüÕ{ĩöĄŠ ļíÉ V̤@Š^{ĻF×t-˛§(Õ*IŲ8Kų+kduMĐ}O?ŦĸFU7–=ĪĢđí)J”¤Ú͚÷ĸŽÄÕ= ĻËgQĀWŠ=›>Đâŧų?~_ßr"üĨZ4mžvø˝˙°îsÄJū•ŧŋMĢæTĘģĨP+ZęôîŊųZæ‘Ō&L×âY6ĒčŊ‚į”_Y§<Ĩ1qŨŲÎĢīĪ×âRÉ1a˛–ÍKQœŧ*ÛēEËUyũVmīh>Ū]ígGœÆ,\ŖåąĪiŲŪĨĪ{Y÷šb5ÄŪüy”¯ž­üu5ŠsNĶ?q)ŅÚ¨ęâÍÍß-Īķ*hũ<"čüŧo [ŧÚp˙ÕÆ×} 'H^ŪÛēO[ ÛĮz°ĨŪ@Å Ęŋw‹jãĮęÎ3”/ųĢhÃÆuwiĨÖ~ü˛˛;û‚Û§kÕĪÚŗfĄŪĢIŅ}¯>ŦôX[s¨éÛĢųĶĒØŸŦŦŲ?Ōą ø<Úąî-ÎûTÕīhYZ7Îú.ļ7Æí”eoĨJ*ē3ëü9VâiÅb‘ˇĸRõrļ׀§Tå˛h\ĻKVÕtØmW¯C’Bŋįų?ŌĢęJvjéŒ:˛%KË÷ŗ3JŊ4cļÖÖ'++˙a=āˆ•üĮTV¸MkËבĀų}ųK~Ŧ܇>P}üXŨ3oēėV¯Ę ļiíÃų:ōb6æ&´lˇDšs>’/aŦîY0Ciņ’ŋbŸÖn}Qw{j´öã•ĘŽ“ÚŽAÁ:­}hĄâŌįhÅÛ)Š×įÚŗú9­}vļ|Öķívįúį+|\š}"Ģs˛xz´â­AųĒčŊ­/ęîJ¯v|ü”FąĖ���.7†aĮŽ3Ė×düzq†‘<tŧņôáĻv¯˙ÉØõ ËH:ŪøÉ‘æW~õ§‘<tēņöZ6ųl1a¨ÃŊŦŦ­­ß,o${Šņ‹#íÛ2 ãO…ÆŦī8Œä‰›ŒÖˇ˙fąËHę0F/.1žlŋí6S†:Œáß/<˙zųJcėP‡1vՑÎģŌē͊ŠŽ÷~Ī\cøP‡1üģOŋ)ĸ{}ųÆéFōP§1eCmČĻ_=lŒę0’ŋ=רÕēí‚ĸđjūdŧs§ÃHūöŒ_ĩž˛cĻ‘<ÔiĖ+j­áˆņĶ˙툸/ãīw}ÛaŒūÁ~ãOĄŊ4~ˇj‚‘<Ôe,8ØÔކŒ°ĪÜ0Œ? îœk<]ÚB¸ĻōuÆŦ;§ŗ:԰ɘ0Ôa pŋŅÚō—{6FuV1ÚīíīÍ4†uS6Ôwoģĸ‡‘C]Æ]īՇUUkŧ}§ĶHūö\c{Kų]ígoĮ6L1’‡ēŒ…CÅ”ĄcøÄuÆg![iüęFōĐ cáÁ°ũ…‹x۟?&Ŧ =Vj9V6ԇnûŨgß„œß†ņåÁ§ŒŅ]úž|ilŋ'ô>ŨČ0…5üŲ:c¡Fōįŋ­įņŦ=Mįˇę0F..ë~{-Į5¤î?ŊoÜõm—ą`ņÃÆđoĪ5vĩkâ7‹3šĪ‰/×Ŧî]‡>[;%Âw¯ÉøŨĒ)ÆđĄáĮ(‚–ĪsüǰīMĶãígŗV•ĩėĢåsûÎÃÆŽö§^S™ąđģ#ų=eüēÉ0 ŖÂøÉwFōwfÛ˙ÚäŸvĖ5Fuã۝;Í×p‡1ōûCûúå~cŪwFōwWŋkéSׯ_Ûŋī4’ŋũ°ņĢđM‹ž5îēįGÆöĐ/����á™JNĒԞŌ)aŧîNo˙OŽņĘ^˛QīmyYw'tŊ­…ŸKöągķËįķ˙‘Sw¤Y¤š:2”ÜĻėÜ …ĖúIL֐X)čķ^xꒉâ2&k\HŨé‹_‡Kk$ĨčŽL{hģî™g닊ššē¯úũ;UŒÕ˜Ŧ‘Rûz}~ÅgŒ•]:ŧŋũ´ ŋjĢŊĄĶ§čgÛ×iEօĮ0YĶæjãömœŨކ@@ø ąHÁúÖĪίƒ…‡Ô¨‘šz—#dTObîJmŨ˛U+2cģˇŨû‡Ô¨Ũ‘a ũ\|ą“™"=*ŽhßĢŽöŗįĮ#’úũ{U!iTūd…†‰SvūXŲÔ ƒû{ņ„,‹K÷å‡ĢxGŠâ%ųŊ Í/TīSQ”1^CžãpŒ×8›ä-ëÉú2•ÚŗŋA˛ŨĒģŗÂæė ™Ŧģ’*Ãŋë&ĩ—8VcėjšŌŦyԊSî|—Rƒ5*ŠnũĢURŲ 9ÆļŒ†ęLWŽC~•—ÖIJ ûîY•:oŽÆue-nkķ¨_]MčĩÍęĐ÷ąIēškhųÜb3§‡Ž6˛ē´øõŪ/æ(U’Ēč WŠMŸŠ‰aCŖâs&kŒEĒ+9öųÆj\îØĐžÆnžĻy+u¸^ęîĩÜj•ü\ÕĄ“šâŗ~Ŧ­īŧŦ;/4:���ˆ’ž›:ä;ĻÚIŽ”SŦ‰éÎTękTÛ(Šæ-Ũņ¯ou˛‘Wu ŌųšH6%t".ũķDGrč Ũę‹WuŪ dIPbøũ¸5AŽŠãâŊdKА°}ÕW“Ô¨=ĶžNŪfņ6Č/)1gŽîØ´@{Væ*Ŗ`Ŧ˛ŨrgŽÕ˜´uuŠ_Å6Ŋļf›VÔÉÛ ûm %°đĒēŽų؄×+k‚FĨˇĻxÕ]ßΔôŠ–Ũ:NË:ĢÍÛ )ĄËũ4ãx„̝ū\’MCBš„æī[…×+ŋÔŗ}Ä Öđ7ļÜČûÍG?PW§zIÁ÷į+ũũNÚ SŊ¤nŨ ûŽ5?Žī‹Wĸ=VĒôĒÎ+uiŪaˇÚŗ+Ë5Xk ŠÜ?S‰qRyI•‚öå­ę„u°´NJwHž*Ž“ėķFw:EĢYWŽC^Õ5ĨX{ĮīyœSévŠ8l Ķõ@ÆGZŧĄ2nŲŦėŒņrg¸4ÆíTbģŨĩ~nCė Ljw¸ÚiÛvÎäŽWMk˛†$JÅu5aŸo‚ á[Į)Ū'éķækšēsũ‹SöŦ鲗lŅÚin˝qcåÎĢ1NP���āōŅwA‹ßßō@Ö ´Ü\§ĖĐڅc#ˇgSb—GČ\:qá +tĢ/AjžÉí°ĄåÂk6ô”5ļCM@@’MYKž×ŨöHo’Ŧņ)Í7õņãõŗw(kŨ[zo˙!ŊˇîŊˇN’-ES<ĢeųÎ ŪüˇŽ áĩÖ} WĘmOPœU’*õÚŊ/ǤmËķĮæÂ'X7ˇŗŒÖĸÖÕīØKÅŲ[îšģÚĪ^Hŋ$[Ëq /ąåķķ;,lÚe]8ąF%%Ly^+r"­˛Z:9ŽĐvŨˆx\šŋOAųģÚšnļ—šéTėû‡TRĐÄô:•T6ČæriˆÕ.ˇ3Vī–UÉ'‡ŦžRŅ`Ũ™éčn#h=G-NŅØ.>‘)AwžŊCC ŪŌÚ­tđũWĩį}I›ŌsŸÔЧ'hˆõüįfŊČg/°UąI 4ˇļM"_“Ŧ-ë<ęöĩܚū” ?vé—ëļiOé^­-ũHk%ÅÚoÕĪ=ĢŖöhr��� s}´ÄÅ)NR0Đ(ŋēöĪkKÖĨē3.ō/ȗšnõĨåÆ%Đr“rWT ËŖYzqĶ-É') 8{†Æšģđ†8‡&.|YJúj,ũH;6mĶŽgįŠ^ښßY"æĶîuÉĢ-Ú˛ŠmŅUIR !ėĻŦõØø#›žlÛrChUbZFØt¯^öŗĮĮ#˛æĪŖ“°!ĐØüYĮEēi756VIŠKŅ8ˇaC‹‹\7šGÔÄF™LhΚ6^Ŗ,ûš§ō$Tép]ŦÆ<ž"ÉĒQîé™R•&+ޤJÁØąĘ2Ĩë-sƒ‘ž§]•¯QšOéÍܧ$ŋWåe´cĶf}øūBåû­Ú˙oãÛ>ˇ@āZc-Ø. †FI˛†}-Q˜c܃kšuČx=øĶņzPųĒ=:Xø‘6lŨ§U÷.m/Ѓ&ž~���€ún–ød ąIĒĢî¸NC}Š>ÜēMEĩ]ŧƒhkËŖ#Ūđ›=ĻuĢ/6Ųm’‚^Õwxl*žļÛú¯Éˇk j]ZŖ'†8’%5Ēŧ:Ōü˙o­‰eį?Ĩ7ˇ<+ˇĨQe…k{ŪįÍS8l)6Ī#PQĒ#!ŗˆėrØ-Rđ˜*ęļqŠļn͇%Ūnl— 4ģE Ö¨Ŧ:Bi˙giuĩŸŨ;‘ q –Ô ÚęŽī ÔÕ¨VR‚ŨŪãŠI]aĩە(ÉW]Ąū€ü]OBÅ'+ąŗë†ŧĒ­k”, rt2˛Ē×íōVVšTWQĨÚĘ:bIQzËŽâœc5$XĨ’Š•T6Ȓ>Ö¤'ŪØdOÔč•/ü`úkTvąiC‘Ä%hTÖL­xg“~"5ėßĢÃɚØüšÕVëø(į˛zwë^ņŸßŽžēãvōS­O’=|Z¨WÕuZUŊˇņ|{u-ˇ*Ū‘Ą;žŦ‚WoWl°F{Jzrp���€žÕ‡‹á:uG†Mjا …íīü:¸æi-~vŊĘģz—âÔ™6ŠņÖnŦ ũŠ–~oŒœ÷īėÕˇøZ }Ĩ;}‰×¨ŒI5ÚQzCQ_°YEĄ-ĮŲl˛(¨úēĐ&PļSEaĄLwÄgNPēEĒÛôs…ÜTģqž\7åhyY@’W>”)WŪ+oĸZ>ę O%‹•-V’ŋAžöû Tęĩ՟Čo‘lą!ĢÆåŒUŦŽi÷&OHâ+|Q‹ž}NĒŨÚnĖ”ąŠUƒv¯ÛŠĐLƧʒëæĮ[úßÕ~ööx´€‘ņ”f‘ʡ~øTôū5j°Æå¤\´UŠįũÉĘļKAĪf­­í\ âåۜŽÜ7{rėÔÔ ›Ôđ‰Ū+ŊéToĶŽJɒ6Ąk#ŽzÔ^ŧÆd¤HÕĨÚP\Š`âXi3ÄŠQļ.ØŠō:‹FeŽ6)ˊ׍´Á’*ĩŖ´ũU, Ú­ëu°ąŗ÷wdã,eÜ<K‰j9ÕZ§:noūÜJ6ëÃöÛ*ĩöŲ§ĩlõÕˇÛŽąt›v‡]Xë ˇépPräŒ[÷ĻQßßzŽ˙D;*$Ų]/uëúį? Eˇš•ųŒ§Ķy›���DCßM’Uc>ŠŦŌ…*~"_ŗËĻËm—ęJˇéÃŌŲ2W끴n´ĩāYM-] /į+ŋnŽĻēdõUjĪĻm*ņÖÔįūöî?<ĒōĀû˙ž֙U›ŅĘLģ0SéĖ”5“ķŖ>IX$á)D,ÄE EÄJQ@ä‡!‚€đ�‹jųá�JĀ@—\ö* ĩIX7É~ģ ŦNhídąĪŒßš ųÂ÷ É$$$ÁAeũŧŽ+—ΜsŸsŸûœp]÷'÷}ŸáW6=ÉjĮ”^ÃRûpŦÖd~žnĨ2o#{.žéÂw"ÔA/ŲÁԜĐz†5™IŲ)WpΞ]‹#ķQŌˇĖĻāĨl˛ęæn—A]y!ųĨIN˙ƒŽ‘8ŠĄĻ#l_Á+ŽiŒ°FãĢŪĮē §°9Ąî’ŋ6w“e ‹fī%ë…ƒĖøY€‡&ŽÄciÄ[°‹MĒ1bį5ŋYĘJRšāŧ×ČēŋŽû2p™Ŗ úĢ)ÚšĸÆūÜ;1ų2S;#2œŦ{éKŸXC0+ÃWʞņ]ĖãLgUE![scy0e8qO17÷‹v>Ffũ8îM°¨*äŨũ'X`QVh˜‚šģûĨ?Ëĸ”“Ė-^@æ„ ĪLĀB%yođvi×c4wČģ{æ/Ų`ļô'šjŽnYÂ+uNl‰ãíĮ˛éûČ|i-Yęšë üđŪ:Ā:öfzēč€vøÜ÷䥰3sÉÚü['dâË~„TW4ÁĒBļn;B­ų.ÖetwØI¸ĐŋwĪæũYYŗán—‰ ÷ooßG•év.̓ßģžĪ‘õĨ]ŧ{¸‘˜Ėذ0ÁIĸ'šˇ÷ī˝š ‘[Ä=~ ‰šK(y.›žGHˇˇh/[+ŦŒˆ=Éû˛ /Ÿ2Ëę,z`<噪đØMÁzĘīâŨęh\# �Ī€üGwˇėë2ÕQ˛ĩ&—Lc„Ņŧߒ84y7 xŒĒĮÆā1ņ•ėcëÎ`˜EŲíīī@ūdMŽfRf,f5īoßAyc éĶn^¯§˙ū™ãIwž‘Q?†{SbąøkKŲ“{@Ė]LĘø.Ė%""""ßzW1h,ŖØŧÛÄ+Ģ7ōöūĀdu2âéÅĖÚÐÂ2œUģsņlØČۇ_céŪ�˜bpxưtí4ō\áߖ-#™ûØfl;ÁÖÕÕØŗœü<=†Ęũ¯ąĩ¸ŨžÕŲZŨü˙VHŋĸ Ĩ‡×bÅú7ƒ,Íyüŧĩ”`ÂęÎĖ—§ANzØâ°€yĢ^ŽcNÎlz4‹UŅ&ėž‘<žqæ Š”ÔŌÅ:%1pgo'Īē‘u[đîęlmŒÆdu2ôą ˜Ūú—m[æōLYˇĨüÕŲÚŅĻū8<#Yēd%^ū>9Ļn`}ũVæí`î,0Ųc=q3sŗ\íđūŧŨėÉY/'gŗķĐëšX6Ŧa]Ū>VmH\Æ<6Ī~„¸–Suw?+÷Ŋž‹eÛF6í<ÂĘįv͈ Ģ=žI+Ļ13ŗõ•ĮŨŊÎ/Ûæ´i,JŠfeņ>VÕöį^ëFģ‚Â)�� �IDATˏ§n#Īļ‘u› Y÷Ün˜ˆąĮrߒ˜™ÛõsŲásoęĒTFâŗäŊãdŨ†78”ģœ÷DĮ„ÚuŅôq ŊŌŔ,ŖX˙^ žÕy;o9sęÁÔwĘŖlš>Ž>Ā==ž+™$Ķė D—ļÁL\‚Ÿ¤ŅžÜŗ7§uÅ6ŽÍ¯Y˜ŗƒŖ–đ>1¸RfÕcđÍ;ČûtņŽgĮ#l{ĮÄē ģ8”ģ†=Fˆ6auÅķPģgלú+ōvģXšz‡ļ­`&bėņ<žņ)fώ†'æÄ_‘÷Ļ‹•v‘ŋz>[`˛ÚIš˜ÃĖécq_rLÜŊb5æíkŲ”ŗ o=ÖX\ģ˜EaOdˇ˙ũ33â˙ä˛Õĩ‘M{°Šh7ÆhL1VÜiŗØ:ũ†j-\ųęuá… §OŸfĀ€_w]¤Güŧ;!‰šåÉŦûp Ŗ5‚^äŋĄ:^š'UucØúá ũēĢ͉COzxü€“…Gvōķkzĩr‘žkŸŠ\Å5ZDD¤;‚å;˜1y"3ōÚ-ĻTs€÷Ģ!ÚOÜ×S5éĄĢ;uHDDēdØ­Pu‚÷K&â¯j^GÆWÁžíģŠŠvōøôQWõ-R"""""9 ZDDžnæáŦßŊ×ęŧˇ†šFĸM18<°túS<ÔÕâÆ"""""ōĄ5ZDDDDDDDDސÖhšJ´ˆˆˆˆˆˆˆˆDˆ‚‘QĐ""""""""! ZDDDDDDDD"DA‹ˆˆˆˆˆˆˆH„(h‰-""""""""u5ŪÔÔÄŲŗgill¤ŠŠéjžJDDDDDDDäQQQDGGsĶM7uUcĐųŽÖ›šš8sæ 7ÜpÃWv1""""""""ᚚšøüķĪ9sæ ũû÷ŋęųÄU›:töėYn¸áĖfŗBųZDEEĩägĪžŊęįģjAKCC×_ũÕ:ŧˆˆˆˆˆˆˆHˇ]ũõ466^õķ\ĩ åüųķôî­ĩvEDDDDDDäëõ•ŦĢ$DDDDDDDD$B´ˆˆˆˆˆˆˆˆDˆ‚‘QĐ""""""""! ZDDDDDDDD"DA‹ˆˆˆˆˆˆˆH„(h‰-""""""""ĸ EDDDDDDD$B´ˆˆˆˆˆˆˆˆDˆ‚‘QĐ""""""""! ZDDDDDDDD"ä[´œbįķĪąâ_įģN°nîķlųč*Wå·X:w%;˙ķ*ŸGDDDDDDDޚ¨¯ģ=õyã˙GŅ'ūô˙žãŊēUĻøŪwúú}3×G‡gK1$Ū3÷Í×Gļ’.fÅö�ãæŒb`d,"""""""ß`×\ĐRôI�ü]ĖzqĄ[e.ЋSmäŸūŸOšįG1ôéͧy‹‰ž¸ˆ×ņ¯gNņiSLď+"""""""ßl×\Đōé_›øģīv?dĐˆ–×]ā?ūÅĮ‚ÃaoŪrŠĪoåtĘtæ ŗ�įđū.Ÿ]>ÂÛ…šŋ“‘#oíYũ>XΞƒõ°fîqŒÅĶ?š  ¯€c5õœm�“e�CŌGrĮBŸtī[Y˙¤?>‰ôū}ā‹Oø ī ˙üī ãV'é÷Œd˜Ã€˙ˇŋfqA “&Zø0īˇTų>į\ßnO˄ŸÜÚÉyĀ_SĖžƒ'¨:ķž /7õ˙!É#G’Ū|\Î}ÆÉ‚|üî4Ÿ~ũnu’~Īh†9úĩn?˜ĪōĐö>×ĮđÃ;ŌšwÔm|āĖ!–ūC5î‡qîXŋ˙øsÎE]Ī€;F2éæ‹éâúDDDDDDDžéŽš åüZB–Ēī^õ˙wLŊ lihhčtŋs˙yWwWcūû,æ%ÅĀg˙ÁƒPü°›õģ%y 3>{…õ˙ūCfü2}ûÁšOxoS.˙åaÂä,n35á­8țooÅ5Iƒû]r˙G{yĩ Û'N …,ø(Ø´•MNî8׍MÔËįÍ×ß éÉ'HŋĸúDAC5īũķŒô)&™ÎņiņVVėÍįØcØMTø‹eįö8빟~~MŸsúX>ožžĶŗd:Įōˇ˛õŖF›ÂíĻNįŗëõ7āÉĮvķTæmåÕ쁴‡Ļ0Ĩ_žøĪ߲k÷ŧÜ4…y÷|Ÿ>Q@Ķ_(9ø¯ÜķĐ/ßŋ­9Āú×ßáUËôPՍëųĻûÖ-†{áBį#aN¯æė2GŨÆ-7ŨČ-ŽxÆ%Û8דôéGŋž�QD™úҧœĢũ-˙âģŋwCūöFĖ7Yp˨|ÎÉcÕøÛâÜâåˇOsKæÃLøQ(„9÷īÅüņzK˛ÃÂwoē•ÛīKēåO|đÁŠ–:65Eáūûn3ôáO6ūÄé3\Ågä͆ëšmˆ ÛM7ōŨ›ŋΐˇyzj:îžĀQđģĪ0j,é?ē•[ú$)s,÷üøüŸ}jūšâ/ HËĪ~t+ß5Ũˆmđ(Æ%Åđįßũ–Ē–Ķ6qĶé$÷]ĪuŽa¤Ûáô‡ÕüWŽODDDDDDä›ė[´t.§žĪ‰˛|Ÿ[žŊîÛ|žŸÖü‰/ú~Û,áߚ°ūāšūü Ÿ†í+cëö㐜Ŕ;nlųúė™?ņE” w˙đ @üíõÎx9{ņ̍ôÛ%*Š~ĀMTÎrî˙ÂąˇÍÎâĨōL€s˜°ũí­˜û�g>ÁÛt=ļūaĶwú|Ÿa=ĀĪ~Ôūü ŪĻđƒÛöĮ÷ˆjø§>ģøM_lũàˇÜ|=œũ#g{r}"""""""ß`×ÜÔĄĢ§/ÎĻ(Ž ˙ēO(¨ø2Î54AT_ú´[$ĨOT44„Öh $/ŸĻĻ&n „žŋX䯁húˆWtđžéë?ÄĸŪŌ>š÷—S¸åƒbJŽåķ/ų DŨ8€Ä‘Ŗš×cĄOSMDaîė°_4đQ˜Û5RŸ¨žôŖ‰Ļ/ ԀQDĩ?FT44qŽ›×÷e/‘ĢMAK‹(ĸú@Ķš&ū ­aË ´dWčēžQĐÔĀšđ䄿�Ļoß6‹ÔŪtG“•ņōŽŊėúqëú-יúB_'ũ2ũ’WFGEõÅ W>ŊÆô}’īyä{ā¯>ÅÉcÉ{û úÜø÷öíKMøŋč¤lŋP Ō~ûš/ø‚žaLĶ%ŖjÎ5ĩ^wŽODDDDDDä›NS‡Z\Ī-–žāk;•Į˙ņŠļS{ŽĀMŽīҝáOüÁūígœ>ķĸúkËw}šmČm؏f(~Ÿˇ—ÍĮč˙=L !€…[nnũšŠ_ũLĻNß(ԕsg?áäŋûZBšënHŌčt\QŸãõ}7ÄõNüYXŠO(Ø´…­ŋû úŋƒíđiíŸhęû=Ŧ-3ŠđÖü1l�ug>‡›nå–Ģx}"""""""_%--úpÛNLŸ•“÷UxĪ~†÷ßËÎãõ=ÍŌ§__xųŸÎŅĮžÂ˙˛ü…É;ĀÉ3Ÿáø¨ü`/?žÄaƒÛNU î{ÆōŋúTŗkW˙ôųŅOvk=ģđá~†?đŪņōĒ Ŧ?æģ´Ũå;ÁÎío°ĩøxĪø¯ŗ¤ōØoų70 ?čį$=öNėåŊ>Á{æĮķō9đ1ĄuYú9Iŋ#´Ŋāß}üWā3ŧāÍãÁšônkIHĸøâß Øķ‘ā3Nũî ĩđÃ$'æĢy}"""""""_!M ĶĮ1’'Æ6ņfÁ;ŧx<Š›ú;9zlÎÚÍûšŒ[bÂßĪį7ŋ‡?…yŖnåggq]^;7'Đ…éÖōŋ™ÄĪŗß@î}0‰?l<țŋĀ˟ÜJú㓈Ę;Č?žžŗ ĐīÆīáJ˜I)–ŽŅkūŅhžČ<ȞöōRūį4Eõå&Ë�|˜Qũúp[æÃd÷;ȁŧ­ūLˇūQ“G3ėf€~Ü6zSĸōyo×+üãį­õēwØ­a-v=ˇ§˙„¨ãšŦxģž/ĸb¸ípĮÅEv¯Îõ‰ˆˆˆˆˆˆ|•z]¸páÂéͧ0`@D|5Ž đúÉzūwLhąĒī^NžbīO<øĮ¯—\Ɵątu9ˇM›Ëøŋũē+#""""""ßV_EūĄŠC""""""""rÍMęŨ .Ћ^\hŠŌįų˛ŠR€ã¯m`WíåÎÅMIŗčžīŠ3‰ˆˆˆˆˆˆČĩéš Znš.ŠS=ĪĀë ēUæŊ8õW0÷:G¯^ŊŽđĖ&†<4ÛÚŋŖ¸(úô3]fûˇÔÍ#X´rÄ×] ‘Ģîš ZRŋoĻāæ?ūú?¸@÷B“^\ā†˙ŅÄĀŪ~úöí{ÅįîĶĪÄw{ú "ųָ悖ëŖ{s÷ īōņĮŸĐĐĐĀ… ŨÕŌ‹^ôíۗü@ĶzDDDDDDDä揿‚€>}úāpØŋîjˆˆˆˆˆˆˆˆ´Ąˇ‰ˆˆˆˆˆˆˆDˆ‚‘QĐ""""""""! ZDDDDDDDD"DA‹ˆˆˆˆˆˆˆH„(h‰-""""""""ĸ EDDDDDDD$B´ˆˆˆˆˆˆˆˆDˆ‚‘QĐ""""""""! ZDDDDDDDD"DA‹ˆˆˆˆˆˆˆH„(h‰-""""""""ĸ EDDDDDDD$B´ˆˆˆˆˆˆˆˆDˆ‚‘QĐ""""""""! ZDDDDDDDD"DA‹ˆˆˆˆˆˆˆH„(h‰-""""""""ĸ EDDDDDDD$B´ˆˆˆˆˆˆˆˆDˆ‚‘QĐ""""""""! ZDDDDDDDD"$ęjüôéĶWķđ""""""""ß(W5h0`ĀÕ<ŧˆˆˆˆˆˆˆHˇ}B4uHDDDDDDD$B´ˆˆˆˆˆˆˆˆDˆ‚‘QĐ""""""""! ZDDDDDDDD"DA‹ˆˆˆˆˆˆˆH„(h‰-""""""""ĸ EDDDDDDD$B´ˆˆˆˆˆˆˆˆDˆ‚‘QĐ""""""""! ZDDDDDDDD"DA‹ˆˆˆˆˆˆˆH„(h‰-""""""""ĸ �?•û×0cB C<ØypŪ™AækȝōˇŲĶ—;ûā§9t•jrĩé w‘5ČÃԂ¯ę„ŨäĐėöâīzįo™ŽÛæč“ė÷īÂ÷•Öëŋ‚_â4‘ˇÔp"""""r…ĸžî ôTCC•UÕü_ŋŸķįĪwĢLīŪŊųŗˇËIßž}Ûm­#˙ɉĖ9Ā1j3ŸĮfŨ1Ūßžƒ™÷Rūz.‹͑ŋ‘+bāž¸˜•ÁX ŧ¤ōvFÛ2ôœŠˆˆˆˆˆ|Žš åßĒĒųÎwŽcĀ€ôîŨŊ9įΟįĶO˙ĖīËʉŋc}úôiŲæÍĪœ ]‘ĮæLkXŠFgŽaåũYŧĩá “Įa‹đĩˆ\)[âXîkųTKIU2žÆ ‰ˆˆˆˆˆp NōûũÜlššÛ! „F´ÜrËÍ4ƒ|üņ'a[jy{û ˆÆĸ6!K3ÃÅÜwJ¨xķr!‹Ŗ›IƝ‰8yˆ6ž9šUÍS:*X8ĖMÆæÚ°Ũw‘5ȍķ‰B‚-_V°đN™ÛęÂŽÛŗ˛c–Ė!aPKËÛU¯j iƒR™S‚TæÍ'ķNÎÁ‰¤Ü?Ÿwkƒ\^čøYÛJy÷™ņ¤ ņāœFfN!>_)¯<‘IÂÎ;3™‘[V¯ •yŋ"ë§Š8šqI#ķÉo3%#HÍū‹û´oģŽšēÃ<¤<YØņ”˜`-ųĪM$íNöAnœwfõÜjÂ.Ņ_ž‹9÷§;؍sHGÛw´lˇN$mÂ|Ū ›>æÍķÎųä­!û§ŠÄnŽ÷ūZÂ[Ō_ĩ—…2š“JÚä5õvr]5¯’1(•…a÷.XđKœƒÜdåúÚî78ĨåáS‡ ™:ø^ļÖ5R4+ ûųŋ¸ŋÄ[°œŦa‰Øyˆũé/yĨü2ąĒ֐68ƒĨX8!ØÁœCŌČĘ oī 5a÷5ÔFŋ"?ŧģē—ŲsČĶ ;ÜņįRąĘā•šÖīBûÍįhÖq(į12îô„žŖ;3™ēē˜Öæ.m~†‹ųõä4œƒã]?@‡ž›ØüL§’ņäŽ:o‘î¸æ‚–ķįĪ÷(dš¨wīŪ\¸p†††Ö/}”Õ‚#-šķ Å0.sÔ e9ŲLÚp†¸[8|d?ÛĻÛ)ËÉfF^ā$=ÖDMiEKx,/Ļ2&sU1•SSJ™ßJRbxØĶŗ˛w>đsFÛΐŸWÚĻÃ_šŋZë]<˜j,_ÎÔy…šũŋÉcÛėXŠVŋÖzŦN› ‘ĘíŠĘØĀáË)~ŅIÍöųdNہ1}Ĩ–°b4‡rVßÜ#÷íšŦyĮ0_Íū#G9üúS8Ēא=ųU.ö—}ûŸ&ķ™cX&æw0—͏Åp|q6söwŖĢxå‰ųĩ>Åļ‡c頞•Ļ3ķ°Áƒkķ(:R@ۊq‡g“Ŋē"´ƒwS',§Ō5ÍīŧOŪÚ1‡g“õ\q¨kv=a eöil{ī(%īm`RL)‹&ĖæŨæ^ģAc}!+ˇ›˜šģˆŠŠČhphÖtÖU5WÄģ—pČÃú÷ (z/‡{ƒû˜účrĘ:Ęĩ $Yũ”•´†jeÅ'1ĮÄPYr˛å~ú+ŽQcN Ũ^x8ëŗ˜T qIå,&éâĻÚ],Ũä—s)xī%î‹>ÆĒY)ëėFGuŧûŌ\ ō¨ø¨œŌ—‡ãĪ}šŠšÍ!`ų˛į„´ōPôÎjî5Ž0sÚÆ–į¨Ģûpší–„á8‚”\lKĒ(*…˜˜zŠ*.>AƊĒ!1™8ÃĪŅį&ōx^Ą+r9ü/ûÉ]/w*Ų9Ímg`xwn¤(áYrߙĮ3ÔlžÎŒŧ�CŸĪåđš,K­fŨ†4vÖ>""""""ŨpÍ-_օ Z?øOá#›-ĻŨ^A‚Áļ?ō˛.÷ŽéĢY–‹Íf%.s1Ë2MmŲE%qéąP~ĸĨƒ]V|KÆ’‚'9ۜ8ø+NPcŽ'ĩMē§e]<8ÖIũá}oŠn-īŽÃž1Ž8 ,÷uÖ1,š‚Ãfő8ŽeŲˇĶ՘€ k3S-€%e8q q ?w™GÚpl§(¯ ÷ŨÍGĻ=ÅĒė6 6Ī(–-…šú ļ–ԑŋí¤=ËĒŦÜIY/°ėąx ߙvuō‘?k*ë‚cØüō#8:Ėž‚ÔTՁk$÷%ÚąŲŦ¸Saũî\6Ojŋí¯RbĮ˛įĮ’ä˛ãNÂ˛G=> lįĘÍŖXöüXâ,Žzū)ԃĮØē?ldC§O!Î `Ɲõ(#L§ČĪ Ĩ•;_Ŗˆ‘,[;…Ą+6G ŋX;8ī.6îhD‰“ÔƒšŌęæP­–ĸRHšxæŠŌæ�#.žáÄĩģ~Ãb‚h0Ė&Ėæ°ÁLZ;….;×pff'@]55—]čĩÛØ§xČZëŜ8ĮĄ<¯04BÄõ(ÛŪËeķėÜ+6W ŋ˜˜ŒŠļ”ãŪî܇.ļÛâI˛ÔSVŪėøNRæsr_æ@*‹Ē›Ÿ‹jJ*‚ÄĨÄcö˛u˙<Ķ_`nĒ ›ÅŠ;ũY–e ¤6oGhÄKŗ:Ķ(–MNœËŽ™*öė­ÆH›ÅĸŒPš¸ĖÅĖL¸\°*"""""Ōĩo]ĐŌ– . |yáú쏰Ÿt–tPŧļ”ĘÆū$%ÚÞ4ˆKq][A™ĖąÉ¸ƒ”4EĨ—2ŽT{%~Bč Œ„âÚž§e™ãHô˛§¨ųŠj 9äĩsīXāŖĻļžh{,Ž6įHhķš36û@Z–Y5ĖŅ`sõģl3A‚A  \ ą„/Íj¸âqSOM•‚Õ”TÃã¤ĩkkfčė`}vlØwŽįLeNųíŦzũY’:]ëÕ .#Sņ ˛ž\Ã[xũ`ļÅâUg.š~[ƯØüâXÍÛqÅãīk›xėPSq*ė9qŌö–ÛqŲÁWwŠ >*ËO+šm]-ņ¤Ú)+Šî¸îŠņPu,ĒøNPæs’š‘Bœŋ‚2/@5EAÜiˇĶí(ĀžĐ5_JŒ‰h‚ø/›Ŧ™pģÂ/ΌÃuÕĄ Å0Cí>NČ$mX* w&;ë�C÷žËûĐÕv;é f*›GrË‹Š´'soŠsEsûx+8îŗ“š`Ú *ûįi;õĪ‘ā$:PMyØt-Ģ+ļuäZđ5^pxėaíiāNtvˇuEDDDDD:ôíZ,1XĸņVÕĩũ:e1{vį†~Ö>€Ŋ“âø9ÃÖÜØĩū¸ĻĄ‘zü~BĄˇÕQVîké@'zŦÄyb¨,Š H-EAâRã/í@÷´Ŧe$&ÂŅŊ…øšÃņēÆ0Úu° ŖMøqÉįî0.ū§“.ЏŋĖFtģrĄ€Æl„ Ÿ`#í÷i¯| 3ļWĶč¯Į×Å{žm™[ČÛø(îúBÖÍĘ"õŽDŌžxĩy]˜F‚~.sŊü~ˆ6ĖíŽĘ„Ų AkĐŅæ Ņ4ƒi Ũ÷Ōx…?wŗĒüõG™BĄJQMs¸`M&Év;‰ŽSUøÁ[A™ĪNzBG“Ļ:séę:¤1.-gā|yĶɜĩĘ,Öŋą‡ũīŊĪáÃ1…í~ųûĐÕvwęíåĄ‘<eÅ'1{bqxRp7‡NūŠcÔXâIr�ū�~ bLíĢl akŽæ°g-ÄßÁķg˜ĸé≚Ŧkî­CeN Õˊ Š™moé`ąwą?S™#”7aПˇđxû4Æ0°Ø�\¤&˜yĢä$ž˜b*­ņ,3ƒ#%Ÿ ÆC™ĪÉŨ u˙{ZÖˈ‰É,|â�GũNŧûk‰ËŲüW|Ŗ% đCkØā÷ˇũ †s4xũ_M%<€iŪ§&ØÅŠæŊ< ÎDVÎ{•¤7§´qŌöÄ8Ō§°*} }Tícå kɞCņëÉ-IĮ×Û6P Qã÷C´ÍÜ:úŠą9L1w´O4f30÷—$_t˜­‡–xRmË)ĒđQV~sâlXˆķİލĮ¨´%ŗėĒŋúęŌ/Á`°ųY÷q4VMMišŗA‚íÂŖËŨ‡ąXēڞ‚Ûŋ‹˛š*(7Ũ F=ŠöSUøpUcNx7€9¨Ôˇ[Ä6čāĮ|I�ĶZEŖ5ô /hųRžŨ#Z°2zę]˜Ē72gsU‡# |Utö˛ė ÄE×㠘p8ė­?Ãlmé‡ĮĨÄCE1īŽÆėižĒãJÆí+åčūcÔØHę¤Ũ͞Fâ8F[N°gÃöÔ%ķ`ZKb„Ãn‚Ú Â^Ū‚¯âD›Īa„ĻÖ´Ž9,?A ũqģ,`8ņ¸ Ļ¸"lŸ GsƓų\aëwö‘ŒöÄō‹OáŽÚȜ ß'đSYTHŲÅõG îô),ī¤ąļ/ÜŽūPuĸ͂´žũķÉŧ ĮƒoĮ_MI-¸§8U‡^§ÜRíS”ׁÍŪ nĪ@¨Ģ%h {&Vˆ ‹Ĩŗ”ČNRB •Eû(*—šÂâHˆ…ŠBō‹Ē1'$‡Â…Îtgą.¨,­ ûėŖ˛ĒŦ.4âBtŒ9,¨ōqhoø˛]Ũ‡Žļ ėĩ”>FQ­“T„Fr•lÅÕ<ŊĖ‹;úLëš.ÍjJĢi49qwLqX Ļ<ümQÍ‹ėŠˆˆˆˆˆ| ßō ,‹Y?ŪJåKY¤M^ί÷r´¤˜Cy;X:9ƒ´i…i͘äé °y8“2c([=›•Ux}>jĘC¯õ͘ĩˇå•¸Fb2qž#l-āNi^ÂKĸĩš­Û+0' ī´Ũã˛FfÄp|ûn|)ãŅē° I™Ã‰ŠÛĮԜB*ŊuTí`anmdGŗ�,Ŗx srKŠņųđ–īeá &LinK+ŖŗīÂ(^ÁŒmÅTVUq4w>KsOaöÜ~i°~A,Ū-ŗYYŪQĸāø†§É~b9ųåĩx}uԔīeĶŪZLŽ€{â#xüXúĖ.ŽWUQVđ*sröáĩ†Öe‰›8…D˙–>ˇ—2¯_M)oÍ[C‘ų.&e´]äø†åäWÕáķU‘Ÿŗ†ŖAgķZ8ā˙(‰ū}Ėyn/e5>|Ū*ŽnžNÆOŗ:Š{ˆ;-JßāŨZ'‰žP cxâq{˛Š8HRJl'%CĶĄjJŽQYS×ųëąģ%˙áĩ,-¨ÅįĢŖ,o›J!1ë.,Xqģbh,ŪÅ[åĄëĘĪ™ÍÛŅņØŠŖŧē°ĢûEpŅ6�� �IDATĐõ};I‰1”mƒJ{rËč2GŠ“`ņōŊNŌ/Žâ2įņĖū”o˜Ī+EĄãU,gNnŽŦ‡Iętô“‹{38ŧ‚Ĩû+¨ņÖr<w>ë* M‘/åÛ=u� CŸĪcĘÖmßĮĻgŪ žĸMũqx’™ųÚJídēIĪogŗy9+gķëú�˜—ö,ÛŒm4cN ÕUOQE|ķ_įŦ$yĖŦÚäî´Ë,ĀyeŨÉXļš•ĐĻŪFâ<ļ- 2gÃĶܝk`u gæ‚i0a ´›0ķeY2^"7¸œĨ[Ļ“ą8�ĻūÍí2ŽeARKÆ äúW°tËl2_@ĖíŒX°…e™:žaËzE‡3™;k9éīũĒŨ¸V~ūō‚‹×˛rō.ęDĮ $.åYļ- nl°íMX˜ķ*Sī?CĐčO\Fš Ršˇcķ›Aæl$û§ ƒ=ņ.ÖŊņ,ŖÃ—F‰Žgæt;Īd1§Úaåž_āįžŲÆ˛ųMX™ķŲ?[@�1öxî[ģ™žÎÛØđ 'ΐ"Û’.žĪKĸŊžĸÚdR;)k$ķ`öíŨ2›ĖâģXõ›øáY #Ļ?Œąs:ŗNá7’ôØVe†*”4{5×ÍgŨCYiHRÖ<ÖŋhåP}6Kge2gm›ģ¸]Ū'Ā’�Û÷a¤ĩ.^l¸’qûRb×:ĩ3I ļŗÉXÂĘy™ŦĒ“ÕÉĐé[X4ÕuŲ'Ú=}+}ķY÷Loƒ+íQ–Í7˜:Ģî2ĨDDDDDD.¯×… .œ>}šDôĀWã˜�…ü3Cūį˙ŧĸ˛ūū÷Übą0xđ#\Ģo’ •9™d'ī7O]~ljô˜/w"‰91lúč%F|Ũ•‰´š5¤ÜGŌî"–u4‚KDDDDDä÷UäŅōßFŸˇŽšâ5ĖÉ 0bíŖ YDDDDDDDžb×\ĐŌģwoΟ?OīŪ=[^æüųķôęÕë*Õꛠ”•?›Ę’>{ËŌ#ŋōŠˆˆˆˆˆˆˆ\Ū57uč÷eåôëwˇÜrsˇÃ–ķįĪķéŸ?åėŲŗÜ`ū~ücõųļŅÔĄ¸]N~_^Î˙ôG.\¸Đ­2ŊzõÂ0 Lßšžž}û^劈ˆˆˆˆˆČˇÕ5´ôíۗø!CøøãOhhhčQØŌˇo_~đƒī_劈ˆˆˆˆˆČˇÕ5´�ôé͇ÃūuWCDDDDDDD¤ž­(+""""""""RĐ""""""""! ZDDDDDDDD"DA‹ˆˆˆˆˆˆˆH„(h‰-""""""""ĸ EDDDDDDD$B´ˆˆˆˆˆˆˆˆDˆ‚‘QĐ""""""""! ZDDDDDDDD"DA‹ˆˆˆˆˆˆˆH„(h‰-""""""""ĸ EDDDDDDD$B´ˆˆˆˆˆˆˆˆDˆ‚‘QĐ""""""""! ZDDDDDDDD"DA‹ˆˆˆˆˆˆˆH„(h‰-""""""""ĸ EDDDDDDD$B´ˆˆˆˆˆˆˆˆDˆ‚‘QĐ""""""""! ZDDDDDDDD"DA‹ˆˆˆˆˆˆˆH„D]̓Ÿ>}új^DDDDDDDäåĒ- ¸š‡éļ¯b@ˆĻ‰ˆˆˆˆˆˆˆDˆ‚‘QĐ""""""""! ZDDDDDDDD"DA‹ˆˆˆˆˆˆˆH„(h‰-""""""""ĸ EDDDDDDD$B´ˆˆˆˆˆˆˆˆDˆ‚‘QĐ""""""""! ZDDDDDDDD"DA‹ˆˆˆˆˆˆˆH„(h‰-""""""""ĸ EDDDDDDD$B´�ā§r˙fLČ aˆû Î;3Č|b ųUū6{úr'bü4‡ŽRMŽöņ/=á.˛y˜ZđU°ģ‚zƒ}Â^ü]īü-ĶuÛ}ԃũū]øžŌzI§žąŋg"""""iQ_wzĒĄĄĘĒjū¯ßĪųķįģUĻwīŪüŲŒÛå¤oßžíļ֑˙äDæā5Ž™ĪĮc3ŋîīoßÁĖû )=—E‰æČ_ŒČ1pO\ĖĘ`, ^Ry;Ŗ€m_ũsę˝HFųŖ”ž˜ō•Ÿ[DDDDD䛿š Zū­Ēšī|į: @īŪŨsūüy>ũôĪüžŦœø;†Đ§OŸ–mŪÜųĖ9ĐČĐyl䆕JatæV۟Å[2)qļ_‹Č•˛%Žåž–Oĩ”T!ãë¨IĘōj‚_ĮŠEDDDDDžŽšŠC~ŋŸ›-7w;dĐˆ–[nš™†`?ū$lK-oo?ąĶXÔ&dif¸˜ûN o^.dņqtķ/ɸ3į ąÃÆ3'ˇĒyJG ‡šÉØ\ļû.˛šq>QÖ9­`á2ˇÕ…ˇgeĮ,™C 4––ˇĢ^ÕŌĨ2§(ŠĖ›O杜ƒIš>īÖvÕE?k[)ī>3ž”!œƒĶČĖ)Äį+å•'2IâÁyg&3rĢÂę¤2īWdũ4į 7Î!id>šƒãmæ˛ŠŲqŸöm×QS2c˜‡”' ;žŦ%˙š‰¤ŨéÁ>ȍķÎ ˛ž;@MØ%úËw1įū4bģqéhûŽ–íöÁ‰¤M˜Ī[aĶĮŧšãqŪ9Ÿüĸ5d˙4•ØÁÍõŪ_Û&lđWíeᄌæã¤’6y GŊ\WÍĢd JeaØŊ üį 7Yšžļû NciyøÔĄBĻž—­uÍJÂ>d>Į/îoņ,'kX"öAbúK^)oŨŽîQ1s†¸Û=—pü™Dė÷ėĀ‹ˇ&Ä1io€ĀŪŠØe°˛ĒŖ ,eΙ›‹ÃžĄTŌžx•ãaÕ Ũ›æ6ä!ážĮXY~§ũ”åÎ'sX"ÎAĄv͘ŧ†CŪnlīqw§}B÷ÁųÄ^ŽŽOÂāDf],×Ķß3ųī⚠ZΟ?ßŖåĸŪŊ{sáÂZŋôUPV Ž´ä΃øĖQƒ”åd3iÃâláđ‘ũl›n§,'›yu€“ôX5Ĩ-áA°ŧ˜Ę˜ĖUÅT^<LM)e~+I‰áaOĪĘŪųĀĪm?C~^i›åūBj­wņ`ĒA°|9SįBæö˙&mŗc)ZũZëą:m‚F*ˇo¤*c‡?,§øE'5Ûį“9yÆôm”~XÂū‰ŅĘYA~s'Ôˇ˙i˛æÃ<~5ûåđëOá¨^CöäWŠĄuŸĖgŽa™˜CŪÁ\6?ÃņÅŲĖŲßAŒŦâ•'æsÔúÛ^ŽĨƒzVn˜ÎĖÃŽÍŖčHy+ÆažMöęŠĐŪ]L°œJ×46ŋķ>ykĮ`žMÖsÅĄ6ŽŲAö„5”Ų§ąíŊŖ”ŧˇI1Ĩ,š0›w›;ķõ…ŦÜnbæî"*>*"wĸÁĄYĶYw1dđîeƄ2ưūŊŠŪËáŪā>Ļ>眞Žúێ’Ŧ~ĘJZCĩ˛â“˜cb¨,9Ųr?ũĮ¨1'î /<œõŋYL*¸¤€ō“tqSí.–îŒaŌËšŧ÷÷EcÕŦ”…ĩW÷čō,Ü÷ōæ8Á4j5%ŋËeĻĢã= Ŗ‘Ę-k)I}‰Ã–SņÁbâĒÖ2uہPÛû Y8y G-ãØüNEsY–čį×OLį­æļ–Ŧ`ęâRl͎°˙HEī,fhp3žÕ÷˛Û¯ ģn#Ģ^c“wĢŪÜÆ\Wü{&"""""˙=\sA˗uá…ÖūSøˆÆf‹iˇW`°íO‡ü…ŦË=…kúj–eÄbŗY‰Ë\˞LE[vQ‰A\z,”Ÿhé`—ŸÄ’1†¤āIŽ7÷fũ'¨1Į“ÚĻ“Ú͞.ë¤ūđ>ގTˇ–÷×aĪGP–{„:ëÍNÁaŗâHĮ˛ėÛģ5í#čĮĖT `INHœÂĪ]fĀĀ‘6[ã)ĘkCį}wķ‚iOą*;‡Í‚Í3ŠeKFaŽ~ƒ­å�uäo;iΞ*+ˇÃERÖ ,{,ÃwĻ]|äΚĘēā6ŋüŽŗ¯ 5UuāÉ}‰vl6+îÔGXŋ;—͆ÚoûĢ”XÆąėųą$šė¸S§°lÁÄQ(Ûšƒrķ(–=?–8‡‹#‡žŠôā1ļîY„ÁĐéSˆ3˜qg=ĘĶ)ōķBIKåÎ×(b$ËÖNa¨ÃŠÍ‘Â/ÖN#Îģ‹M‡;¯ã$5Á Ļ´ē9TĢĨ¨’&Ū…šĸ´šƒ¤Ŧ¨Ã3œ¸v×oXL †Ų„Ųļ18Ik§0ÂeĮáÎĖė¨ĢĻÆ×Ũ{Ô5Ãle‘† ‹ŲĖåbI\㘛aÅ� ËpfNŧ@Ņ>Žús<swī!oÅ#$šŦØ.FL„$Ē)¨ĩНǖzS,÷fÄâ°Yąš†3÷å\r_…­Ëí=m㴏ĪʃĪ?ÂP ›ųËũž‰ˆˆˆˆČĩī[´´e€K:@žŧĮpũ]\ØO: K:(^[Jec’ía_ÄĨ8‰Ž­ ĖæØdÜÁ JšˆĸRˆKGĒŊŽ’ ?ĄÎ]FB qíß͞ŽĖq$ú ŲSÔ|E5…ōÚšwŦ đQS[O´=G›s$´ųÜ›} -ËŦfŒh°šú‡] Á  .Ž„X—f5\ņ¸Š§ĻĘÁjJĒĀáq†uÎÍ ũŦĪŽ û.ĀņœŠĖ)ŋU¯?KR§kŊÄeÄb*^A֓kxĢ ¯ĖļXÜ63āŖ˛ęĖ%×oËø›_‹Ŗy;ŽxÜáiŲ‰Į5§Âž'moš—|u§âŖ˛ü¸’ÛÖÕOĒ­‘˛’ęŽëžUĮB~ß Ę|NR3RˆķWPæ¨Ļ¨"ˆ;íöˇáė ÍaPķĨʈ&ˆŋģ÷(Â,.g›‘H6ģScUu�fˁ“lš7žŒŸĻ‘pg*ąÃ–pŧąą%č´%Į<Ȝ ķy%¯˜JoĖvâ\ĄđæōÛ{ØÆ=i{<žÜī™ˆˆˆˆˆ\ûžŨA‹%Kt#ŪĒļkPXRŗgwnčgíØ;)Ž?@3l}Ā}PëkÚŠĮīlņ$Ųę(+÷ĩtî=Vâ<1T–T¤–ĸŠ qŠņ—v {ZÖ2’áčŪBü@Íáƒx]cí�„:؆ŅĻãxÉįî0.ū§“.ЏŋĖFtģrĄ€Æl„ Ÿ`#í÷i¯| 3ļWĶč¯Į×Å{žm™[ČÛø(îúBÖÍĘ"õŽÄĐ: >€F‚~.sŊü~ˆ6ڏĘ0a6CcĐß´DGc´9H4†Á AC÷ŊtžAáĪÅŨŦĒ} Ã‘ æ„P‡ŋ¨ĻyŠ˜5™$Ûí$ēNQTáoe>;é MšęĖĨw¨åswîQ„™cLí΍q1œĢŲAö„åËĸĩÛŲ˙Ūŋ÷Të4(�×rßYÍ}Ö:öäLåîģI¸?l .ļ÷¨{Ō>†9ė™Šāī™ˆˆˆˆˆ\“ŽšˇE”9T;,+*¤fļŊõ/Î;qûŗ1˜9ŌIyũąq ˇOc ‹ ĀEj‚™ˇJNâ‹)ĻŌĪ238Rbaņ jŧ1”ųœÜĐQ7Ŧ§e͌˜˜ĖÂ'pÔīÄģŋ–¸ė‘ÍëĪ-a€Z;}~ÛĪ‘`˜1Gƒ×€đ1 á×æ}jēęЛXôō4ü9Y9īU’ŪœŌvÄIÛãHŸÂĒô)ôQY´•/Ŧ%{^ ů'ˇ&_oÛ@%|DßŅ6sëč§Ææ0ÅÜŅ>ҘÍ@Â<Ū_’|iĐaļvOYâIĩ-§¨ÂGYųIˉS°a!ÎÃēĸj|ŖŌ–˞HŊúĒ;÷¨Í¤ëŠŋ>Đî@üÍĪeÍá}”ÃŲôâ8’.6īŌŅff×(æūŸQĖ%ˆˇŧ­Ģ—°hrÛ/1Ôčb{OÚ8xĨíķūž‰ˆˆˆˆČ7Ōˇ{D VFOŊ SõFælŽęp¤¯Ē‚Î^ƒ=¸čzŧ‡ŊõĮb`˜­-Ē¸”x¨(æŨÃ՘=ÍS\ɸ}ĨŨŒ[It {ZÖHĮhË ölØÁžēdLkIŒpØMP[ŅfĄS_ʼnn.|ÚFhjMëz!ÁōÔСˆ jŠ+Âö r4g<™Īļ~gÉhO,ŋXņîĒĖŲĐņ}?•E…”]œŅaXp§OaŅx'ĩxąāvõ‡Ēm¤õíŸOæũk8ėx;ūjJjÁ>ÅŠ:ô:å–jŸĸŧlöūXp{B]-A[Ø3á°b6LX,ĨDv’b¨,ÚGQy¸'šēBE!ųE՘’q_ŽŨ{€tį5‡ūúđ×QSÛ.0éæšëĘĢÛü.ÕTĸ1z .+ëũĄu^šĮģoháŪæcûĒ 9Ôō(›g‹fÁZŠJo×Û{ÔÆŨjŸŽ|…ŋg"""""ōô-ZĀ’ą˜õã­Tž”EÚäåüz!GKŠ9”ˇƒĨ“3H›Vˆ‘6Iž ›‡3)3†˛ÕŗYYP…×įŖĻ<ôZߌY{[^Al$&į;ÂÖâ�îgsŲX­ÕlŨ^9ax§č—5x0#†ãÛwãKĮˆÖ…UHĘNLŨ>–æRé­Ŗ˛h sk¯Â_ŲC–Qŧ†9šĨÔø|xË÷˛đ…Ļ4ˇĨ•ŅŲwa¯`Æļb*ĢĒ8š;ŸĨš§0{nŋ´NŽGXŋ ī–ŲŦ,ī¨Wāø†§É~b9ųåĩx}uԔīeĶŪZLŽ€{â#xüXúĖ.ŽWUQVđ*sröáĩ†Öe‰›8…D˙–>ˇ—2¯_M)oÍ[C‘ų.&e´}ũ÷ņ ËɯĒÃįĢ"?g GƒÎæĩpĀ=ūQũû˜ķÜ^Ęj|øŧUŨ<ŒŸfuR÷wZ,”žÁģĩN=ĄÄÁđÄãödSq¤”ØNJ†ĻĨԔŖ˛ĻŽķ×cˇŅ{4+šÚĸ}”…^ DåļåėŠÍaÂl@ öGĢjņ^æäĻē],ŨÜ|Ž’,Ũ^MLÚX†šÁ–‹Šū[ķĒđųę(Ë›ĪŒR+I1ā-¯Æßū5˘<›W Ēđz}xkJykK!u1ˇgëz{ĪÚ¸;íĶņŊøę~ĪDDDDDä›čÛ=u� CŸĪcĘÖmßĮĻgŪ žĸMũqx’™ųÚJídēIĪogŗy9+gķëú�˜—ö,ÛŒmp`N ÕUOQE<Šž‹G˛’ä1ŗjgģ͜Wī Ęē3’ąl 04+ĄMŊÄyl[dΆ§š;×ĀęÎĖĶ`Âh7aæË˛dŧDnp9KˇL'cq�Lũ›Ûe\ËĢ´-/ë_ÁŌ-ŗÉ|!�1ˇ3bÁ–eZčhˆ„-ëÎdîŦå¤ŋ÷Ģv ãZųųË.^ËĘÉģ¨ 43¸”gŲļ`x¨“k{„moœW™z˙‚Fâ2rČ]Ōŧ}›ß ˛0g#Ų?]@€ė‰wąîg>€!:ž™Ķí<“Åœj?†5–û^|_\œ{fËæ7á˙gīÎÃŖĒ>Ž#!*I“˛$*—- ZōP+ Vb+ā–ĒX̏ŅĸØĸU”ˇā^+.T\Ē`ËR°€Ęö*‹-ÄZƒUQ1š ËŧLB2Ā„ũ~ž'ĪÃĖ=÷ž3w#÷—sÎünôĶ ūŅHJH )­#?~đY†eí{˛zĐĄx R/¤sE}‰™d§ą  +Ũ˛÷ąn +— nĪü'o%wá،}ũ÷=Ô×|Œ’9˙öQ,šéAvyŽ@b rGđËÜÕ\ŗ X~„é>čbŌF<Ī•?™Å%pOvõõĨæŪÄyE1øÜ< ƒ‰¤uÁŋ-?6ŊF0vĀõÜũ›‹Č&‰Œn?ãž1ũáÉÕ ~ōzúždéČ'y$xČØĸ2ˆ Ÿß=[x¸Ņõ5,?Ā}É9\íá8Œ×™$I’¤ú'& …ÖŦYCëÖ­ŖēáÚØ&ĀoÍãôĶN;¨u˙ūĐ<9™SO=%Ę­ĒO‚|0:—Ü…=˜úúÍûjĸļųĨAdNbüû÷sN]7æˆą”;ē fų ×˜qÍ>§––$I’¤Zw8ō{´|cŲŧސU `øK%œķāĪ Y$I’$I:ĖŽ¸ å¨ŖŽb׎]uԁM/ŗk×.bbbjŠUõÁR~÷Ŗk˜BzŨú(÷ôrFI’$I’ˇ#.hųnb"Ÿ~牿ÍŋqØ˛k×.>Ũô)@Ü78lÉaėß?`l]7ã.yāŗ ŦëVi:qĪÛÔu#$I’$é°8ₖvéücÅ 6lÜ@(Šh˜˜� G7&>>ž–[(I’$I’ž­Ž¸ %>>žŽ§ŸÎ'ŸŦĨ´´ô€Â–øøxŽ;îØZnĄ$I’$Iúļ:â‚€† Ōļ­ß^"I’$I’ę—›QV’$I’$IûdĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQRkAËQGÅŽ]ģjkķ’$I’$IÛącąąąĩ^O­-ņņņ×Öæ%I’$I’"ļuëVâââjŊžZ Zš5kFqq1[ļlaĮŽĩU$I’$IŌ>íØąƒ-[ļP\\LŗfÍjŊžZë3KĢV­øüķĪŲ¸qŖa‹$I’$I:ėbcc‰‹‹ŖUĢV‡ečP­ÖË÷ž÷ŊÚŦB’$I’$ŠŪ¨Õ eY!\đ<lŪ;CĩY“j[ƒH>ūztJ­ëÖH’$I’T?ÕÚ-y ķxظՐå›`g6–ĀOŠ uŨI’$I’ę§Z ZÎv°|ŗÄĀŽ]pÁ uŨI’$I’ę§Z ZÖ}Y[[VŠõ~kˇ$I’$IÕĒĩ ÅÎ,ß\“$I’$Šzĩ´H’$I’$}Û´H’$I’$E‰A‹$I’$IR”´H’$I’$E‰A‹$I’$IR”´H’$I’$E‰A‹$I’$IR”ÄÖuö%ķû0ĸœŲZ4‚ŌXąz&oŦëÖI’$I’$}]ŊėŅŌ˙bXÜŌKāÎWāÜgađ›°1&]ļ9|myüÖäđÕ'I’$I’Ž\õŽGËņab{x} ô[žį˛ÉËážk`čŲđĐøOm7Ļ d5†•ĩ]$I’$IúF¨wAËĐ.ĀZ¸qy5 wĀmOĀm;*ßjŪ֜ ƒ_šĐd9´™ ßM…‡ú…- ICظ&ΆÛ>*_1 –_¯?[rāÆcĄ °"O…Û†!Đč|+Œų�šŧÁjšurß :'CüvXšîü+L.Ē,Sĩ=ņ;`ņ?ačLøpG„Ë[˜ž0 5ÜΕĢáÆWāo[Ę+h÷]�ƒ ĩÚōL_�7.ƒ/#Y.I’$I’Yũ:Ô8VŦü`?ŊUvėų˛t#:ÂC/…o‡_į–Ā…O@ë‡`čj¸ņ¸Ļę0 †0¸l™ -GC‹gĄÉiđP`5dũ ļ�#~-^Ž>d!^ŋȇÎAë'`âv˜t)dV”i͇@ֆp{:ŋ Ĩ0īøn„Ë'  ˇÃ€Į õc0ą!LŲåQŲ°ū0´! ž�­ĮÁ…  sbdË%I’$IŌĄĢ_=ZâÃ=HVnųúĸĀ^- V \âŠwāŲՕīxØ ˙) ŋūĪ›0¯#\x,<QeûkūO”OŽûå:˜ž9ÜëƒåđiyĨĨđå^ĪnáÜßÃÆ•=Cū÷1Îly[ ģ œš:ŋKĘ×::1üyĶkXŪēœ \ø2ü­üķüīd8÷‘ũū YI°æMø[ųgųĪ28w-ė֚–K’$I’¤CWŋ‚ ˆßëŊæ`ãE{ž7~\[%X™ˇvĪå[âaĖ9á2-â!>6Č,Ūë¯,ÚķõÆá˛Û$ÃøžÕšÄ†×oBåv˛ZBéæ=įzųĪ?à Ā55,t,°æ•V)°^ß7ļū Ķ?‚ÁįĀ´Ļ0)^_ ˙ŠōíL5-—$I’$I‡Ž~-[aãvhŨøgå۟~Į—ŋh ûīĩŪöō!D’āõ!Đ"O†•[X˜xë×CœŊ‡"¨æ`ņE°øo0āŸá †caåĨ•eâはö=JM˛Ä-aË=__VēĀ_§Â™aÄi0ą į/ƒĄŗÃðjZ.I’$I’]ũ ZJáõĸ𐙓gÇīo…%[+˙]Ķh—“3 ķ¸p*˝RW˛DÁš§A“Õ0x!|Zū^€=ëÚRîŨō]ĒS"YÎjČúk¸ĮOUĨĨåsĮė€ųī„ņpfxč Hë‹�� �IDAT˜^Ū‰`š$I’$I:dõk2\`ŌØŌ&æ„‹Ŋ5o ­kØF|°ŖŧwIšãŋ!ęŅR“X(-Ų3üš°ÃžAˊ @Kč\Ĩîæß‡wށîą,_ 4 ΧōaQåĪ–°q+Ũ3āøōuƒĨđˇw`ĖjH?6‚å’$I’$)*ę]Đōé?aĀRČú!Ŧŧ†}~Ø~Ôŧ"<$§ôxh힡ąf5lI€;@ķÆŨ&ĩy%ĐúXhiØōU8@9÷D89Šúāgň?†ĻBķ&Đŋ ŨžoĨsKøn,äŊ ‹ãáĄ\čŪ˛3`bhũŦØÁōĨ0/&^�ŲIáz~˜+†Á˜T nĖ…×sĄ{ųōė z,,^ÁrI’$I’õkčPšŋŊYÁ]`D.´hĨ_ÁĘĩpįŗ0ūŖ}|Õrš/ķađRxčØŦø†NēÁëŨÂáEˡ#hČFxh5ŒųQ8ČšđõųLæĪ†1MāÎĄ0æ+˜ˇ L… āĄū0qrxRÛs'„'Ė~ Äī€Å˙„3g–ÚRķō '˜žđúuá‰v7ÁėáÎuáv ~ļ|ũë ICØōŧž�n\ŲrI’$I’tčbBĄPh͚5´nŨ:ēž#Ē›S=Ēfb^I’$I’žmöÎTęŨĐ!I’$I’¤#•A‹$I’$IR”´H’$I’$E‰A‹$I’$IR”´H’$I’$E‰A‹$I’$IR”´H’$I’$E‰A‹$I’$IR”´H’$I’$EI­-1ĩĩaÕš\I’$I’ĒUkAKJ"Ē­­Ģ΄ Ub]7B’$I’¤úŠÖ‚–ŋ „ŖėųđsT ŧzY]ˇB’$I’¤úŠÖ‚–ėcᝡĐĸąË7ÁQ1Đŧ1,ž2[Öuk$I’$IǟbksãRaÈÚŦA’$I’$Šūđ[‡$I’$I’ĸÄ E’$I’$)J Z$I’$I’ĸÄ E’$I’$)J Z$I’$I’ĸÄ E’$I’$)J Z$I’$I’ĸÄ E’$I’$)J Z$I’$I’ĸÄ E’$I’$)J Z$I’$I’ĸÄ E’$I’$)J Z$I’$I’ĸÄ E’$I’$)J Z$I’$I’ĸÄ E’$I’$)J Z$I’$I’ĸÄ E’$I’$)J Z$I’$I’ĸÄ E’$I’$)Jj1hYĘ]ڑvŌ^?§f“sÁÕÜ=#Ÿ`EŅuĪ‘{R;ŌNÄ_Šk¯E‡WwŸÕŽ´“˛¸aÎa¨.Šûpū¯˛I;Š9ŖķĸĶļ*Šg\>.xŠUQßzũđÁ¸ž¤ÔŽôß8„­|ũü9¸}w˜ĪÃˆä—ˇŠ'wįW}3‹_ē“k~Ō“ĖSÃmÎėŌ—7>ĀĢĢž17†#BÅ= wba]7夐?ū$ü˙āĀŠ›ëē1’$IĒE‡ĨGK\B+RRÂ?I”P¸rĪÜ4˜ Gõڟō€&÷%¨ęģ@RGzåtĨ[v‰uŨ˜C ) ‘¤¸ōˇ‚y<ū“ķ¸dÔËĖÉ[O $$@IŅj–Ė|ša?Č wØdņ¯ē‘vúí,>Ė5×ĩÄ@�€@bÜ>ËĪøé'õåwųû,"I’$}+– Ĩķo§˛đ­š,|k.K˙>‡‡û$%ŦxōšoŨL}ŗnîLVÔu#‘@öU<1áI&ŽėAr]7æ$�ˆ#1 ČōҎ06¯âÚpۘ)Ŧx y_Aū›Opef”­æÅc˜Ü˙–Ŗ*¸”) ‹c…õG 1ˆ#1°¯H¯˜ųSQv8%I’$!˙-Î|6I�EĢYĩĪԛYüÄ- <ˇ™§f‘yÖ�n˜˜ĮĶđęčĢéÛ%‹´“Ú‘Ų%—kžXĘ›Üŧ”Įo@Îéá2é]ú2ø7Ķø ę†ŠķøĶ¯Ņ÷ŦlŌOÍĸĶŋāw jîÚŊyÉSÜP1ĖáÔnôrŗWU÷$XČüqWĶķô,ŌOīÉĀßŧÁēĒ‹#ŠŋĘgM?)ŧ?†īŊ?önߜ[čtR;Ōēü‚Wŋöq ųãíčvī{�ŦՋ´“ŽŪkØQU3î$÷ŦlŌOÍĻį˜ŋįέųí¯} žâšsģíūĖ\ąįšÅ+&sĮ\:žEÚIYt:wÃ_ĒÜūēŠW“yR;ŌÎē“ÅģߜÆĀĶÃÃR†īŗD1Ë_ēįv#ũ¤v¤ŸŪ“Ü_MfųîâAf˙<Ģ|čD>Ë_ē%ŧßĢÛÅųüéÆ\:šEúé}<î ÖED~ū”WUíĐĄŊ>K—ž ÜûüŪÛēÉ >Ŋi'õŨo/‘šöåđĀžÜŊ$ŸWžÎĒ=Į÷ üüžH �ņˌõ�d\˙(äfėîąHÍá×Å%=/føČū´ĢØw/ _ĪCĻąüĨĢÉ95k÷0—āē7øŨĪs˯ų,:{5wĪŠėąUšîLÖ-Šrž[å\p ™ßŋ†)E@Ét.ŲīPēšÎ§jĘT{œ"(ÁŊĸxÅd†_֗NïÎôĩ{EMe“á㔨æãNcđIļ° XÍøĩ#í‚įvīšĪ›ŊU^o}Į- ß+ģd‘vj6=‡<ÅâÍ_/—;q)¯ŪؗĖS{r÷Š̎æë.’ûZÍe‚ĢfrĮOzî^~ĮŒgN(I’¤ēS7“áVųmŗš_だ‹Gæ’ûgą$؆ķ÷§sÂJ^ģwƒw7*fö¯3ėŲEŦKęÁ•ƒz“\ɜû¯æšŨķ đøĢ;s%Ŧ>\9čbÎI)aÁ¤‘ ŧify “ĪãCņëiËX—ÔƒŸîCjᛌ˙ų`î^ąŸ‡Ū%w’{ųƒŧ–WFÛnr^VĢ>ĪĐËoŲ+Ô°îŲëšaj‰É‰P˛ž%“naøîĄ:‘Ô_Ÿ† dØŗ‹X•؉súu%ĩø=ĻÜ;ˆãōĢũå=˜˙×Ü4‹ĸ¸t†N¸ŸķŋÖ"vš“~•”y!— ęMģ*$¸â1˙fÁ¤$e%,|šFŊQū0É1ڏĸéÜ1b:ÁôŽtH†ĸ•orĪĪībvqEŨ÷1đ˛ģxqaŒ\Ô'“ĀēeL5ˆÜŅyÔÜQü:' _æŽG힙WīÒHp/ŋîVũ_ã×Ŋt=GMgÉæVœ?črē'ąbÚ] QqNąwÄēI#šáĨ zö m`ī}PĖėß\ïgޤ(F÷ž ,¸‹;fÔÜ "ōķg‚|0npųgIĸ{ŋŪtN,dɤ‘ üųäꃎâĨÜũŗûXP’@ö]rĪ>öQ$û᠄"揞•‡ Ķ9§g:‰_;Į÷–Čųc^cÎŦҜŸä/ey @:įõLûzņä>Üķ‡;šļo&Éåįg |hKYÁķ W@rV'Ú&ÁĨÜ}ųõŒŸ[�Yreŋt(XÄ3×]Ŋ{ˆKåēOs͈7t:›îŠPTđ&÷üü”ü¸g:q�qmč5ār.ÉIĒöĶÔ|>Erœ")ÁŊbķ4nrSViÛ÷bŽԇvÁ<ĻTŊ.#(“šû(sfMå×ŲÕ|ā@:į ęJ � dôš˜+sĶIŒøŧųÚ ”PZõŌHîXҊ‹Žž‚^ÉA >Č5#ĻUs]ŪÅŨK´ËÎ$-yŊ5_w‘Ü×"(\ĘŨ?ģ•ķÖLíÄų9)|0n$Ī8ZV’$éÛ! …V¯^Šž%Ą‘gd„Úœ˜ôڗ•o—Ž ũųڜP›3Bm~xč_ĄP(´öŲPŋ3BmNŧ"ôį/CĄĐĻŠĄ§d„ÚœØ#ôÛåĨáõ6M :%#ÔæŒÛBķJCĄĐ— B#ĪīęņÃ+Bø \äãņ}ÂÛũņ¤ĐĻP(úrjh‰Ą6§Ũú[iEũ„^ûdh”%ĄĩĄP¨töÍĄöeʛY:?üŪÉ?ĒŌō*օ&ü8üŲzŒũ TZūŪ —f†N>%3ÔgüĒP(´"ôÛ3ÃeNūņ“Ą•†BĄĐĻПš~¯|ۑÔ_:˙ļPĮ3BmNģ*ôĘĻō2‹G…ēž’:ųŒōĪVu~ŧ t뙥6'愎ŸŊi?ĮčËП/ ˇąß‹ëvŋ;ī—Âûņ´ĢB/Ŧ-ߡĪô/īļĐŧP„Į¨ē_ģ*ŧûTYoFhĐiĄ6'f†Mų2ŧ/ŪO]š`÷1ø˛b?œrEčĪkĶÔđē§ô sxų™ŖBīėŖūPhSčĪÃú…zü°Očę)åŸyņ¨ōí^zĨt¯}pÆmĄy_Vļũä=öÁ¤ō}ēu~ųŠĨ+Bŋũaųq6wm8ķ'3tũėŊöŨųO†>…BĄ/g„÷wÕúŋœēúŒĖP›Sz„~ģ<´×vV…^š6'ÔæÄĖP_/ŲĮš:€ũ_yގŋvFøš •†æũ2gsŧ&Ĩŗ¯ ī×SŽĢŧNkPyõ\\eĨå÷‡úũ°O¨Įī-?6…^¸´r_īšnŸĐ˙~Pžîîë§GčˇTlĢüŧ8íļĐ;ûlIįS$Į)‚2ŨĢfßŪ——N­Ü÷kį†ū0öÉĐ ķW…ĪĩHĘÔhnčúS*öaÅ{pŨîe÷õVõÚ]~o¨ë‰Ą6'öMXģWšĶŽ ũm÷ļ"­7‚ë.’ûZeJg_>V§\Uų™?~6Ôī”pũĻėīž,I’¤#ÍŪ™Jėás˙&—œqá7¯§¨ ‰ķnš|÷P€=ä/âƒ2 .Œ¤b6oHĄm*,(ČcItĪČážWrĒŦ$19 X ÁbЁä@ m“`IŅ,†˙¨ˆ×rzĐ-§#Ũ¯ŋj÷_Ɨ/ÍŖ -Ôāæp]ə´e+V,b9}čžwûŠķXū s2Ę{å¤pé +¸twĄĘaö/ī)’LįœtXøeÅE›#¨?yáRŠ�2zĐšŧgJ ûNžg5;¯gnē•üBHč3ŠąŊ~6„œūü85üīļŲ™¤đ…Á"ŠƒDxŒöŗņ”Ž\”U~’;Ō+,-cUūj(.dN~@įܜŨÃHŗ{Ķ9a:¯•äą`E÷ @r?Æūv=oZċ÷¯ÚpɘÛč\}W) ™?4•Wy'˜”B2PTVBq1T�%!§7Ũː˜–N*‹(¨Øyåû =Ũ˛+ēZdr^§$ž)ØO¯–<öiÅ"–—×ßĢĸūÄ<ņvõŗî|đčõĖ^Y)—3vd§}O¨[œáū¯X!Ž}ģ–īļ�mŗZÁ´ĸŨįxM÷Vô0=:ģœķ 2¯{sy@˛nŸÃÔÁ)•o$uåĸė*;ëfĻž~s•$’œ”Q\\˛gÅ)]9/Ŗ|ŨÔtÚ&Š’ōs bœOų§5—Y>:‚{UZ Š@ÁŌģčû“EtīŲ•nŲ¸ôÖ•Į!’2#âķfŸ' úT^ģč÷<…e+YQ�¤V–‹ËéG÷Šë4Ōzŗ#¸î<Vķ}msÍ÷žäŧÕác•ŅŖ˛mģŌ+ VŦ<˜+I’¤#Éa ZĘJÖSXņŒ—@JfW.šūfŽíV},) ?p•-â—gīs˛nQĖō‰wq÷ŗ‹ø °¤úIøåF°yÄcĖ)XÆkËxíY ŽŊF>É#ĶV<|å=Čy˙ķāžë—˛šē'Åâ"ŠË�Ę'ķܟ8’* ÷|Ȉ¤ū@y™¸@BBëÉ/˙Eždád^Ũ܃d֒˜˜T9´+.‰D b0HdĮh?OJŠōYILŦx.Ūc˙&WũÄđ$Ē%e—Šx–Üŗ?ŨņZ ÖƒK˛÷ũ á9î=™Åë)Ša6ĪÄĒ8.aĄnÁââđ>¨zXÉî/h9ķg߂%Eåõ—Īu˛_eŦ\ūgá,^\rö1l(ōũ_)1ŽĘ9^scö”& ËևΛļMŖ]úz‚@qáĘĘ{HU{œG@°€ŋŒž‹ņsķ((Ēáā&T9ŋ+†A•N¤QĶųÉqЍL$÷Ēļ×ņĘ"n=üŧYŧ˜7‹¸¤Ž ûÃ8ŽÍJŒŦĖÁ8Āëļ:‰{\H áš|ŠøÚz{ܛ"­7‚ë.’ûZ°Ŧæ2ģCŊĸ*­N ņ�/ I’$™KĐŌíÁÅLėų/īō ˛¸Ž }đgėũܜœÁwqÍŊá9H.sįĨ%ÂÜû¸äÉ÷ö(›˜uOŧ~ÅëōXž4s'ķ—šĢ™3úvū’3‰v‰ @ ¤_ÎÃˇvŨ+ČH¤muŋ'&ÎJÂ7‚Å!ņt ‚ú7——)+.Ą˛ÃEââb‚HLLŦōË|+Î{ėQzM˰š‹øŨ¸…œķŋ9÷—ęũ<DrŒö̤¨ĘđŇŸ€ÂÁFbŌž÷o Ą|˛N€ ËĮ=Āk%GYÁsÜ1ņBĻŽfŽ€ÍĶ>äA”%=hCûϐ¸yï{™>!ī÷˛`I¸]åM*Ū\]"PE”Ο@BByũÅ{Ô,ŋ$&îņ`—1h"÷¤=ÆĀQ˘rīc\’}Ē=ŋd˙GAZG:$<MaÉJĻLË᧎†{˛ofę+áŪ)ŗoĖbč˚ŋãfų¸Ģųå¤õŌ›ģëOģä�ËÄ= kéûq"8Ÿ":N”‰ä^ÚæŪˌÜQlÎ_Ęâ%‹˜3u:¯­\ÄØOĶũõ›iQ™ƒ…ķϏjwĸ`Eīĸ�‰{MĘģGiŊ‘\wÜ×ųÜûōĘÃÖō{vøXUJ’$é§n&íIFGڕ˙BLZŨģåĐŊ[:‰Á‚$’˜Ģ–Ž ÷HģĄš9tÎjCpsø›K¨ø‹ãē…üé‰û¸{j>‰Š™tĪŊ‚_˙áQ†ĻeEAģN™$�”IÎ.¯++‰`IöņČÄL˛Ķ�ŠX>ˇbĸÅÍüåįŨČúŸîô}4?âIũģËäŋąûo‚+ īē“ŨåÖŨȆĨĐ-;ƒķoŋŽŦ8(š6†‡÷3Šo…`ņū?‚c´_‹x-ŋbĪeĖɈŖ]VHˤWFPÂâKĢLd9Å%@\&Ũʇ|W<ĀđgWCRoyáf˛âĘX1îvū¸ęëU†ë-îC&]߇îY™¤–­'âųg÷Ø™áŌ˛•ĖŠøöžâ…LYZÃd¸Ņ:2ēŌ!({9K*ë~ng˛˙§w/ŠzLãhÛŠrGđĶ4 `2wŋ´hé�öT:qen� žŧžĒ~SLp3Ģ<Į”ŧH‚’Í|ž¤ôŧ‚K{uĸCFE…˛�‚AöyyDr>Erœ"(ÉŊbķŠi<>î>ū´’3r8đm<ō²6˛™ČĘDޤrßDáŧ)Y8‹ųb/y#<œŠ6dí#7= z#šî"š¯EP&5ŗMx"åüEģŋ5)˜?‹Ų{]rÁü™<ūÄS<>5ōok“$IRũwXz´°ä> Ë}ŒK&­düåX×3@Á"^]ē2obFˇLR3Rˆc5e+ŸãŽŅE´-\ÄėÍ­HŖˆ‚‚7xx\&ŋPÄkO>Ī’’Y,_ЛΩP\°ŒWW)]镁ŒŸ14ũ ÆŽ|™k~RÂųŲÖ-œÉ‚‚2ŌŊČęžuƒ4.Ŋž7Ī\7‹‚g¯gāæŽ´-ZĘĢKË Ą+ÃeŅ@ [Íõz]ĮĐĖ7›ˇˆ;.ŋš%™q|°đM ‰#māuœ“_û-=ĩ?ŋø=ģš?žĖ%žb÷ˆŒ*ĩ“˜ū+yū“ˇrMAo.šõǍŖjU<%ņꐁŦĘI'˜7“%@R.é™$ōã‘ũyæ˛į)˜t=} {Đ!ĄˆåsQDY×ßūĨ`ŋņ<$ĐmäÎÉJ&õúéäŪ˙ŋņŨĢûĖ)i¤ÆAQŲRƏ¸‚¤ÕĖ^XLÛXQ˜Į‹ŖŸ#õÖ+"ÜgsIÎ,YXÄk#œ›I0oĢ(īy°Īįû(?É}6đi?ģš×nL°g:Áŧ7XPq™×1´[5ĩ †ŪŌ›ŋ\7‹Žá/}ŸŦfhYJdû?jt¸uwäæžĨë™3j YŖHJ„âĸĘa ™—3ŦoĘ~ļ“HjJPDጸ#Nņ’7X•N+)šû4wg'04Ōf%„‡%–ŊÁŨCngAîåܓģWW­ˆÎ§ū§še ÁŊjķ"žyrE/-cAߎ´ YˇâM–� Ų=č@„ej”Db2PXÄĢ#Ž&˜s!ÃFö9äķ&E ŋøjēgÆņÁÜ7)z^ÁųŠû[+Ōķ5‚ë.9­æûZ÷žÄnũ9?éMĻ-⎋¯fI§8>X¸’`øôÜ},ÎŸĖØû—AÚĪ螛yp=‰$I’TīÔĪ-čüۗxæ–ŗÉ Ŧfö¤—y5:ôÅKŽĸ-Øw hOJÜzŧ4ÅqōȄGųõ€6$°šų3–˛9ĩ?OLÅEX7÷yÆ?ų<YRL۞?cüķåĻ2¸ö…gšŖ_{ ßāÅgg˛<˜ÎyˇLäĨ‘™û=“Øë~Ļ>ũ3zĨĮąjîtĻŦ(ŖmÎå<üōŖģ'ėŖFR×Nx‰ģt$ĩx)SĻ-b] =įŨūä~Ú Ãõ7Ķ+ĘōãîŠÕũ:@÷knĻ[J”ŦdņŌ•đW՚Qu‚Áō'ŒŒëx䮎/}ƒÅᯗž{Â(ēWĖ)›uS_ÅE’(^2)sķ(Nëʕc^bâ5˛|Üí<S�qnæžžá'¸vƒGqi ”å=Āđęžf:ĩ?cۛŒ$(X0Wķ“úô“<rëؤĕ‘ŋp&DÜŊ?™ķĮ<Ęđœ6$W3aôÍ#ƒÚT|Ø}N÷ķ'@‡‘™x{o˛ ™?m:‹7'‘=`S'TŦUÔ}3Ã:ÅAIxhYuĮŧæũe ~úÂkŧx×ÅtKoE%•@R+˛r.føcSXøįÛ*'­~#tŋu4CsZ‘PŧŒŋL]D°įŊLœ0ŠŸf&@ŅRfĪYŋīükom/ä—ړWFáŠE,/¨f͈ΧHŽSe"¸W$öē——ŧœn)E,žú<㟝ÎüÂ$ē ĮÔûa™še2ôÖŪ¤%@IÁRæį‡į˜9Ôķ&ąį(Æö‹cųÜEä“Čč9‚‰cúPSŽiŊ5_w‘Ü×"(Čá×Fq^z.eöŌ:ßõ(ÃĘsē`°–†˛I’$Š^ˆ …BĄ5kÖĐēuëēn‹$é[hū¯˛šrZ )ƒ^báČ}ô†“$I’ęŠŊ3•zÚŖE’$I’$éČcĐ"I’$I’%’$I’$I:H’$I’$IĒ%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%ą˙øđÃ벒$I’$IGœ@ °ĮëŨAËņĮØ#I’$I’t$Û¸qã¯:$I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%ąuŨ�I’ę›åŸũ‹_.ũ_”}ÉÎĐŽēnŽöãĩęēß211Õôģ$Ũw 3NĒëÖH’T/ŲŖE’¤*–ö/~ēđ6Š‚_°3´ŗŽ›#Õ+1ģvąķŗ/Ø4t8;>Z]×͑$Š^2h‘$ЊáK˙ˇĘ̘:k‡T…bbˆ‰‰!´kEˇŨ]×͑$Š^2h‘$ŠŠĪ‚[ĖW¤ė,úŧŽ›!IRŊdĐ"IŌŪBuŨ�Š~ ÅÄĀ.į/’$Š:-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"IŌ!9šŽfAî$^ĘîKf|]ˇG’$IuÉ E’¤C’É€2HŽkFV›aLęķ4÷ŸÔž¤ēn–ILŗĻū’$I’Jl]7@’¤#]Ü/ŽŖĪiãčqÂ˙ņĀģđÜĻ/rĢG“~\Ž:Š ›ļ šo[ËŌuSyúũ9ämBÃ÷'á§Ėč{Kį āŽĪjšŽÃ-Ž9oē•fįdŌ0ĄüŊā<ÖįÜËW�ü€ä?ßËwŪ˜Âá3Ø^ÉFŌøwĶhŪęy>šl2ĩ}x$IRũåk$IǁÄ˙áﺟgF×ūt=ēáŽŨ”g<Τ.įŅĻôm[r'WÍģ—ûūŗ–”6ÙtîíôˆöĨ„ūLí3”ĖŠ×Ĩķx`ÉãLßåzę\CâoKķ‹Ē„,_ĮQqĐāŒa¤ŒíKø}•;Ōĩ'iúÃ|ˇe]ˇC’¤o{´H’TkâHKÂS-û˛đũG¸ķÃwYŋĢæĩ’ÚÜĘŊĮ7fé˛a\Ŋjm傍o3}UOūpŖ:Ėfá;īQg‹$4mOZƒ*um/āÕQÚz}’AÂ9-€OØōĶa|öūū“¤pØÂôl9‚´lKāė}#IR”ÔUœk��€IDAT´H’TÛ´ 'ķ^fž°Œįūņŋ/üt?Éą\xRG‹įÎĒ!K…m3¸}N>l+ØŊ„cúq[‡~ôhڂDļRđŲÛ<÷Į™ŧ%"$ĩËÜS×rËĸĩôųA?ē&6#Pļ–7ŪËmĢ HÉxš™™Į™4 —Ë.gāĻžU†5¤G×iÜĪà (ĖdÔŠ? -ž1lËãŠwīåéMۀŖéŨ}2÷1–œųķ))o[ÜqŖYÚîœ2’éÛŌ&eˇežI§ÄfPö yŸ<Ī˙˜ĪęB¨Cטq@p-_Ŋŋ hHÃîįđ–Uē­ÄeĐ0Pų2ĒaK\sžsŨ áaKĮ@čŗĩ”ž6ĸĮŪeGy‘ŖNíĮ÷Fô§Ņņ mČŖøįŲyÕ8šüûvūķŋī… 5ûMF !ņôci˜�;ÖäąõŠņ|>g-!€‡rܟē°íŌ+øėŖŠĘĶhöōxŋsŸĖ?‡ãĮŸG,˙×Ų4›7š5ŋœ^W’$‡I’t˜ŽîČU9˜Ųũ§ôØ×pĸ†éä4…üÂwYŋí•PTJ$ôãÉŗ‡Yō<Wŋ۟œ×ī䚯ÚsįŲˇsáŅá"Á]eׅ›3â˜4˙ ˛˙܏~ųeôč8ŒGÃę‡qÅĮaÛk\1ŊƒVēWÛ î„Ā1—ss“yÜ2c�ŲS†p_qˇd_Núėƒ„”á<›s ëaĀĢ—ĶoŅ4Š[įŲė.ės$O­ęČ1÷ #éæk+~q&{ŠÎ0ĸŖi4b,-Ī‹ãŋwŨĀÚs‡°~L rīĨåMéÄ�4îBŌÃ×øl.BáoįŅāĒ[ųnĢĒAP:ÍÆßIĶVų|>lk~t-›^ƒÆŖĮ’ÜũčȚōĮY7|;YÅg—öã?w˛H’--’$Vq¤´ĀīĪR9JUņĮ’D…Û6F´ĩĖú‘U6;—Í!¯ä ŠJŪcōģ˜Û #Wwėîre,˟˞R€íŦūd6ų´%ŗICØĩ’2€2ĘJˇQļ¯ž% ŪãšŧōáOģ>eæ'ynKzÄķÅ4§_ƙ$nzœëß{›•Û>eõĻÜūeRûĶ/Œ Ž48c­Fü ˆŒf]hŌĢĨOãķÅėøüSĘægķÔOˆ;/—FqpÔŊ8:a_>0™Ō˙|Ęö÷į°yĖģ„ŽŠÜLĖiš$ļŪȗw=ÂÖ÷?eį†ĩ|õÜ8žøg3ŽЅ5f;;ƒe@Ą­ÛØõM%IR2h‘$Š^ŲFÔX )éM[Ā–<VV GļW iĮ[Ų3cįZōĒ~ŌÎ2J€„ČžĘÊWQPĨž˛˛í‰#1ŌmՖŽMĄ`ÃJŠĒŧ]ōŲģŦlЖŽMtŌāÃís‚īŦ=øž­3ˆ l¤ôũ={ m˙{ģԈo ą'ËQ%Ģ(ũO•ŊÍWUžųŠáéi4()āĢĒeø‚āû9Ēuõ}/J’ôMį-’$VenšĘ}Kž'¯ēÅĨ)ÚGJ“cᓚ&ŖmLBC–…ÙJ[)؁GW lĸŅeĄėĐļŌāh #sųÕtįYß8دÃ>XŸŗã3 UÍåļŨ5ŒsöVu�seė,ŲķíPŲVvGLÄ4n %[÷ sļ˛ĢĘ:1ã l+Ą˛ŊˇS|I’-’$&ÁmL†ģŊ€ĨÅpKꙤŋWĀĘjŠ$Ĩô§ĪÎyLÚ¸g RšÍ=˜ČzĮԎ@ÕúwnŖd'äx+ˇŦŪ;P)Ŗ¤ôp‡,�+)ēđ>‹ĢŌ$Ž ÍgäčŨ /YfBČPTė5MLBcŽb;K ´u+âö KsT•uvm-ƒ¸ÆÄÄąG~Ķ8ļîŌT^G’$Õ*‡I’TÛvndaŪíô™1’öûC�k™ūīe'æroûô¯MĘ—Đ—{ŗ‡đŗ6Įā V~ąšd’Yõô†itJ„üMĢĸöõĪ5+Ŗl'4Ū#ØI;Ļržv­"ī‹2RŽnLaÉZVīūųœāŽĪ):,ß3ŧ•e@āXZ9)L¨l{åĪÖĒ ‰RČđī|ʂ-ˆ?ĩųo7<-ŖJVÜ�;Ö|ĘŽcÚhYĨ‰? Q•9ZvüŊ€ i4:žęVšR vũ;?|ĖËž TŪKfˇÆĮw {1y‘$)Ú Z$IĒ5eŽ›ĀU3‡pu~ų$˛(Z=Ž[Vo$­Ũ8fvĘĮuĄk‹.\Øūv&õF§mSšåŨw)ōū=™Ĩqgr[Į^dŨ”¤„öôĪBNŲ˙ņÜ'‘‡ÁíÛ >ƒŽß;–6ņ3ËĮv >[ ĮœIŸ„đúIßû)×ĩŒƒe>eZūÛS‡r˙IíiߔV íéÆÃĖė9œŽ‡er‘|JfoުɧŅvéėđĪÂÛiôĩ˛˛4>–øÎ? Ņ?í‰kl}›/^ÛHüUˇŌ¤ķąÄ6kN\÷Ą|/ˇÁŠS)-ƒ]īĖŖ4˜A“›úhŲ”ØS{‘<ĸ#T™Ŗ%ôI¯iA∥|įÄæ4hv,Ž¸•ĻßßHÉŗķØ°aÁ’f|įŧ„'Į;–Æ7ŸG\ÕämkģhÎwÎH§áņMr$IR”8tH’¤Z,ū?x÷žÛt0Ãaž`Ņ’kéˇîrnÎ8“ë˛sIlPFņļU,úøNny˙mVW„6Ûfđ‹7ã¸ŗÃå<Ųg8‰|NÁώšųÍĮ™UyĢ?™Ę܆qËŲĶįƒČ]}ā­^ũáXî;ævŽ;w7īüœ‚ ͏+ī]2ģTöā()Ë %C¸íÔۙvZ3;?§`Ķ<n{s‹K–í”>8’OšfįdŌ°Úī”^ËļųËØ1˙ŠtN–VįđŊGÎųÚÛ˙Ų sļQúāp6”ŨĀ1Ŗį˜c`įúUü÷ŠÛųėš‚đŸĪį°ix[šBĢŋ^ËŽ/ãËgįˆq4ŽčŸTVĀįCīd׈!$Ī%6ĄŒ˙^Fņđq|ūōXö.Ÿũö5šßt;Į-„]Vąõą l9ū^ž[ҍŋƗīBŗō÷‡Ywí vا•$IՈ …BĄ5kÖĐĸE‹ēn‹$IuŽÃô p.Ü˙“;éSņÍ;;?afŪ#Ü÷ī÷öøfՎרëD_LãŖ‰)Ģō•Ëq? yÖŊÄ=u9…“ĸ0„)ŠRŧR×M$ŠÎmܸ‘Ö­[ī~mI’I“>˧ã Í),|ž1y3Č;€ž$Ōw§ų_G÷ÎÃlzę]vДFƒ†Đ8.Īæ×¯E’$UĪ E’¤C˛eī ŖÛ{uŨ}#lĪĻaÍHēŠ?-^Fļ˛ũßīōŰĮ)ŪP׍“$I‘0h‘$IĒGvŊ?MCĻÕu3$IŌAō[‡$I’$I’ĸÄ E’$I’$)J Z$I’$I’ĸÄ E’$I’$)J Z$I’$I’ĸÄ E’$I’$)J Z$IÚ[L]7@ĒßbB!8Ę E’¤ę´H’TÅ1& Õu3¤zmĐ é˜ēn†$Iõ’A‹$IUŒíô+*ģ´¸HUńBÄ�111$ųu]7G’¤zÉ E’¤*:s ĖšcMh@ƒēnŽT¯„Ž:Š˜Ļßå{OŒ#ö„6uŨI’ęĨ˜P(Zŗf -Z´¨ëļH’$I’$Q6nÜHëÖ­wŋļG‹$I’$IR”´H’$I’$E‰A‹$I’$IR”´H’$I’$E‰A‹$I’$IR”´H’$I’$E‰A‹$I’$IR”´H’$I’$E‰A‹$I’$IR”´H’$I’$E‰A‹$I’$IR”´H’$I’$E‰A‹$I’$IR”´H’$I’$E‰A‹$I’$IR”ÄÖֆ7lØP[›–$I’$I:(-[ļŦÕí×ZĐRÛ —$I’$IĒo:$I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%-’$I’$IQbĐ"I’$I’%ąuŨ�IŌ7_Á'ëxaŌļũ÷+víÚU×͑ô-Cãī4bđeũ9.Ĩe]7G’ô-aIR­*ød?ũ%[ˇ˛H:ŦBĄ%[ˇņû'&˛~ãĻēnŽ$é[ E’TĢžiJ]7AŌˇYL ĄĐ.ūøÂäēn‰$é[ E’TĢļnÛV×MômCqÉÖēn…$é[ E’$IßxĄP¨Ž› Iú–0h‘$I’$IŠƒI’$I’¤(1h‘$I’$IŠƒI’$I’¤(1h‘$I’$IŠ’Øēn€$I5I8Š+õęHFĢĻ4,ŨĖGī/ä•WWP¸ŖŽ[&I’$íÉ E’T¯%ddÄĨ$VŧŅ0™vgärâņÉÜ˙û9†-ĒFcN8ŗ7夓ŋƒĪÖ¯dÎÔYü߆Ōēn˜$Iú0h‘$Õ_ą­šā‚*!K [uĻįe<°hËAnŧ5—ŪõS:7ú‚ˇ~˙ S?ŠĻúS~ÂčAßį;˙y…Û{—’ƒŦéˆ ;ž ŠU,­Īŋ’Ÿį$Ķ€†s|ü"‰†=ÅŧÍšÕĻ­ézfŲ'§ŌŧY#øĒ„Ī×ĖâˇŪbŪŋˇŪsŠäޏšvyāžYŖõ$IŌÆ E’T%gpbž6$õäT-ÚÂW‡PÅöíizk^ųd {Æ ņ´?ũnß~[?2Ğ2Ņƒ2høūˌ|îũCڟ‡Ul ūį’ŸpÁɍ)ųpĪŧø.…ą§ŌģsEČREÃcé}Ö ,zųcvTˇŪ~2ĻØ–]šač9¤îX͒ˇf1sĶVv4Jĸ}į3čķŗki?홎‘oBL%I’“áJ’žÅļŗî“u$žÚ÷ūĶC|:ŲmKYˇž”ozÔŌ06–†@ÃFGÖß_2{sŅŠÉ|§a#šŸÚ›‹2CĶdŽųZĘöĻMh´¯õö%6• .=‡Ô’eüaė3L^´‚ūûcōķ3ų‰ĮųÃ;[iͧ]“kå#J’¤#Бõ•$éÛes>•œÁ1ÕöjŲÎē×r>üˆ5Įå}r,ų˙Ē듐pJNÜą†y›O ĩęCtãÖôĖíE×ļ-8ĻQCū[˛ü…ŗ˜<oMy[Ré˙ë+iūÖ3ŧÕ´?ĘjAŗx(ūd“˙4‹ü­‘nיËrsČHŽgûkøŋWŪĸ¸×•ôZ˙<#§­ ŠOåĖÜۜyr ŽiÅëW2į•YĖ+ØĘž4;Ĩ3ÛW˛¤|ČËöŠžUēdÄ6mMö)ņ|´h%›e×Ļę•’Í|ļšWŗėŋ_„{?5ÚGSØ“Ī û{%üũéY|üĩ)^JųøÕįŗ¨”Â=†$Ås™pÖ 4ŨÁgzė“ģōĢ;?a˙=Ģ7gׄFleŨŋæđÂËīī>ĩs~œĀĨw]Aķ9ė1,ī„‹ocX̎õĐb>ŌērQīđÕßá+>[˙1‹fÍbîîí6&ãĖķš §5ÍbŲūųFŪ›÷*SoÜũī¸ąīŊ¸fŊΧũWs¸įąđļ%I:Ō´H’ę¯kxå•|Ú]úõyZļ¯_ĖÔÅUįgI"ģßY´O€Õ‹^enA„Ÿ–ŦdIÁY䞞NŖU ›iBûΊŋ˙qgî.ܘėū—ķŖĻīķ„i|T‰ĮåpŲÅšė‹y*/\įöí I9Ģ7í_™Æ˜W‹ØŅ8Kq ƒû|Äȗ?fG$ۉO§˙>´ūd6øĶû7jM¯ķûqZŗ†°žō3÷z%}bW2åŲiäKJ×ķšlČåÄ>ô8sĢ›$ūTú_܇vzŅëÃˇ˜<u1•,ÛŲÁöØÆdt=ŸūŊ28Ļáv>øęA˙û×Ęûô:‹ŪŊÎĒv—Κķ3įŧŲū?%īÎbĘI•C€Ļäm…ī3kq'æė5|hûZfŊõ1;öĩŪ>¤ž”ĘwžZÃ{û´cË^! 4:Ĩ7Ŋ>\Č ŋŸÃöĻé\té9vė6!û‚˜ü#7”Ōč¸ŪÜō‹~\´ęc˙{-ž‘ˆ?•ƒÎĸ؊?ķȋų*ļ1­ģžĪeCúQrßķ,ŲZ>ONį,zųyžúd+‰igqYî• Ūū8˙=|ŨnoOû^ßįƒ9Īp˙ú-īUM}8Į$I:-’¤z­$ī%Æ|Օ‹ú”ŊķW›Yŗb!Sf•Ŋslcbˇ˛Ŗi:˙ßŪŨÆDyæ{˙ZīIįFÆĘcG‹ׇ*ÕĒARHä›`M1ĢģëZíņlkēļÍö$ûԓsvĪnvĪĻ{N<nģ]ŗév­ļz"$Č š`˛!Š"Ģ 2Å‘GÌŪĶĖ­œ Â`APŸ„s?\÷5Ė•0ן˙uũ_ZˇŠÅxÆū§[ : |ūņ.~.œō°uËRį¨ �QËHKôS[â%¸6ôÚ�ĩ…ņ+nĐŪĶ?ņîî)§"c 9KŨpļqđJGëI Īvö'‰øëŠēėcCb<Ņ4ŌF;æŌ5¤š×8Z\Ac@  øÅģ÷Æˇ2ÉNôSĩˇˆã-í”Q–ŧ›ŦŦ$Žžī `ˇQUswúBæ/ũ6ģŪ[Ī…ģŋͨõė|įVÄö?áVë9NˇŒūûģ;É>~¨`ģãû_Ž=ˆ§ä>ôå˛93ˇËĻë́ĒCcŨ7—+|7čžĀ,Ļu’C%įúŗ3:*)kČd×>û~ēN• VJ˛ZÎP۞NÚĸPíēņލâL?ĒëšÚpƒîĸũ\¯Ž¤7�8—‘ŗ!oŲíėnu _,JâíŦõ<S]ÆU�\аŸÂŗŖo<#ƘˆˆČP EDDf<ßÅ ūząb”3ķHÛņ&?xn9L"ŽÎ_ũ}~ąÔÃOÅĮQØ2ötŌ×p†:^!mu$U•~b_XƒÛWOa‹1$Đbc#I۔Ã-`ži`čú'ĩˇŊmȲ&Û˛Á0&Áãˇ¸�‡åÅšup­)UęĀËčÄx"‚^.´†žŋN<_ų˜ûŦ›h<#—ũ؝œ.ÚGmÅ26åå°qi+’ÎÅ.fėžÄą’RĘÎwŽš4køDøĄN€īnhģ2fāsˇh.ßĪžR/6~}ÎīŽvŖ“”Üvf& ŪwŊĻ”?ŽĄ}dTĒ9Õŋ-õļxC–ĀØXž�$†˙Ųsˇ_íĄ[zm0S<>ÂŅq‰ Ũ™d|ŋ�GÅIj›<\ēæįjË@fPâb܎Ē/w†ÜdãšØF0ŨMR$– Ū–Î‘í‡˜Ö1&""ō€h‘ÉLXM^^&Š‹b˜KÍ <=~0æ—œÂûīüö�l\jô6ĄēŨɊÕˉ3MĀ$-#‰âƒc˙į>ĐHeƒMÁڕDWzذ.îŗEx€”Đë 7ųoŧF†}†C…Ĩ\j$’´ģØ4ŦÉ1+%‡ŅŽÃé+0ŦßŦČG„Ë ŽUüËoV|†/Ššp߉´ŨQOņ?Ŋ;ļŗ9ŲŧwÂjæčÁ˙ãËûd˛ :é}˜āģÚŪÍŪ¸ÕTÎĄJƒ¯žIÖŌLßę.{šn¸bŨŦx.3ĐÁĨšrŽ”Q–XĀËÉũc$nu.ßmhdĪ(K¤ē}~p- Î`ĖĘDĄžég?Đ c ÚŠc˛=îũ3×ŗ2IËČ%#Ī$ØŨLUi GÎvb;D‘õÖ/šđ§sp¸Zã˙R§kŒ‰ˆˆ<(ZDDdÆ1ŸËáĮ¯§‡lhÅâ•Q,Ŋ(¸œTc?ûŠ—ķy Ąũ~\ĘU ôōv~Ŋm Žî3ė+'Č@€ēęF‚;V‘ú|ŠąÔe9CâÖÆú¨Ú[DUËŨƒ‘D˜@ĪŪ`ípÃūP;C&ŠpË�ĢŽĪö–Ņ<ėļąįÅ=‘¤dä°9{nsØ)s1/ŋõ.kkĘ9RzŠÆž‰M„Ķyo–ā% {YģŖ€ėāŖŅ I^8ô&G +2ˇ”XˇKYņ^>K�æēœĀČ@Ëõ/ˇ2SXûŧ“ĶgG >nŌ2ā­Ŧáj8ąŠICS;>Fr ˙Æč÷rŧäsŽ—€“DjFųÛļėųo lz¨üt?eÃ#9ļ¯˜`•&XDDäQĸōÎ""2Ãēŗ%}ÔĒ1ÃEŒ˜ũ=8ģá ĩV<s—Ũ^OåĩQ.røé É0V‘ËÄūuF;Ũí7ēâqG…Ü—°„%!˜ē[Ûč5įáĸ“öŽ{?Ũ–åķ`r.cÛOŪåíŧū Ë­Ö3|QŪÜ_Âēé_œ¸Â-LÜĢ7ņö{?⟞Ŗôņ4ōĩzé¨ģl8ÅÄL^J ¯œPDr6›bŠh¸Û@Í­Ŗoˆk_>AUģIj^./F ?ë$%÷ļf§ŗ|ÔĘXŖ˜¤14eãۆWhgæ{/‚cDšYõ­ƒŨĩ:<T•”QŒÄã„öf<ÁHæ;CžÛîŗą­߸R˜ˆˆČL§Œ™YbVą":Œë:ꨴVŗ3o k!6]?t,ZԟíŊ†ųuüü`ũøY-ļ‡Ēķ6Ŧ‹Â{ôĖčK*Z¯p5¸† ˨-ķBėJ^Ι‡ˇ)H\ôB’œxÂÉj§†p)˜Ovî:<%õôēRČÉM_H—/ž ŧu'Ų[rč.9ÁĨn˜ûėz6oŲ€Yūŋ;6Ęūļ Xר,.ĸ°ēāķßc3´¯PUTĘéĘÕäåįņ,XV˜Õ›2ëb˙ūķĸÁ׹3÷čŸI\ĸ“Ķû§ĮģÔöR|ā(î76QđÎR+ORÛtƒ š€ŌIK6¨;´/ü >á|öa4cMÕø OkŒĨkH*/ÃpōLFiŽāŊļcÖķŨIxJK8zž ‹Hâ^Hg 7¨j @ žōS~vmúyR*žōcD§•ŸË‹žūķ/5ĄŨyė(Đ"""3‹ËÉđÕ,ŖJ\E*ŸsŦ!sp3ÜšÉkČØØ5hY`úŠĒg逍įT=Ŋ/¸Š>Ÿ :ũ5|Q¸‚MßáũtčũęŅEÔērpīČd÷đÁ˙ԍ˙¨°Ú)ãŗO(ČĪf÷Os°Z)/.ŗ˙/žŖ6žüĶ'Øų9äŊū#æ›pĢģē˛ũ|RqŸ÷`{)Ūģ‡cv'Ũ1Ãč˙:āĀĀX×j8ôĮzĘb z;¨.ÍcÅžVɞ߷ņRv&Yë˛Y›e´|\oŠįČŪrގÜŋ<ôá|öÂČųđ×LÍøĀOuq )Û˛Ųũū‚xN•R|j;—ö/U˛/–đaa›ŗ^áĮy.A‹ŽU÷sô€McŅ'ė 䐗_ĀF— VžķĨ|TŦ ‹ˆˆ<ūfõõõõy<âãã§ģ/""ōú×ûÍÄnX”˯ßZĪÜ0.ŊÕt”>=‡…;*ƒ7ßú6‹ĐUs=‡Ã-ī<sN';Āā~ĄF ÛŪ•¸˛?đ‡ŠcŪ;æÚíü×Ö%ĐTČĪ>~D'Â1ĢŲųz.ŠŅã/ v×qč/ŸSnĘ õ°ÆĮãä÷ŋüŲtwADDCmmm$%% žVF‹ˆˆĖ,­')kZF^˛‹ņĻĖɛøÉ;ëšn{9ē÷ī?•€Ī͕ēmüp6†s%?ŨÂ3 Å|VÖH7‘,ÉĘf­ŅĖ‘ķ“;‰v\ĸļÉĀhģœķŒÖQÞßÖLw/ž‡8>DDDdb”Ņ"""SjÂ-2Č\´­šé,OŒ"‹ŽÖF*JKųōō–ĒČcKãcâ”Ņ"""SA-"""ĢĨ’ŋūąrēģ!3”ƇˆˆČˤōÎ""""""""“D‘Iĸ@‹ˆˆˆˆˆˆˆČ$Q EDDDDDDDd’(Đ""""""""2Ih™$ ´ˆˆˆˆČcoÖŦéˆ<)h‘)9gôõMw7DäIÖ×Į\—kē{!""OZDDdJm˙Ūfũ+YDĻ×SŗxmûÖéˆ<!h‘)õÜ"7oūķĢDΙÃ,\Dä!‹œÁîžFb\ėtwEDDžŗúúúú<ņņņĶŨ‘GJ[[IIIƒ¯•Ņ""""""""2Ih™$ ´ˆˆˆˆˆˆˆˆLZDDDDDDDD&‰-""""""""“D‘Iĸ@‹ˆˆˆˆˆˆˆČ$Q EDDDDDDDd’<`wîܙˆˆˆˆˆˆˆ<2îÜšƒaCŽ=āt: ĶŌ)‘G‘eY˜Ļ9äØS�ŅŅŅÜŧy“›7oŌ××7-yÜšsŋߏeYDEE 97Ģo ˛rûömēēēøú믚}ûö´tTDDDDDDDdĻ›={6O?ũ4ķįĪgöėŲCÎ ZDDDDDDDDä›QÕ!‘Iō˙(ږŨ`ß ����IENDŽB`‚��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/login-multiple-session.png������������������������������������������0000664�0000000�0000000�00000062700�14156463140�0024007�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��X��ĸ���Ķ2r}���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨy|TÕũ˙ņׄ$3E‚ĸA%`k°­ÄĄĸ…*-.­āR×V[ëVÛjĩuųĒ`[Ģ­ Ú*h]¨Ú‚B Ôj°ŋBŦB¨˛(„щ$3Ųæ÷G„6‡"¯įã‘™šįŪû9wnū˜7įœˆFŖQ$I’$I’ô™ÅoũĨŽŽŽ?ū˜H$B]]][Ö$I’$I’´ßęĐĄÁ`´´4âââ�DŖŅh]]~ø!ĄPˆŽ;nÛ(I’$I’¤Õ××SUUEUU={ö$..Ž!`)++#..ŽŽ;ļu’$I’$IíÂ'Ÿ|ĀAD@8&))ŠM‹’$I’$IjO’’’¨ĒĒhXjkk mZ”$I’$IR{GmmmÃīm\‹$I’$IRģgĀ"I’$I’#I’$I’¤°H’$I’$ÅȀE’$I’$)F,’$I’$I12`‘$I’$IŠ‘‹$I’$IRŒ X$I’$I’bdĀ"I’$I’#I’$I’¤°H’$I’$ÅȀE’$I’$)F,’$I’$I12`‘$I’$IŠ‘‹$I’$IRŒ X$I’$I’bßÖH’$I’¤ÖQ¸ļˆ§ž}OļTQ__ßÖå|&qqqtę˜Äųį~‹ĖŪŊÚēœr‹$I’$I_@…k‹xčą)Tnū¤Ũ†+�õõõTVnæĄĮĻP¸ļ¨­ËŲ)I’$I’ž€Ļ>ķB[—°īî×}2`‘$I’$é hķ'Ÿ´u ûV Ā–ĒĒļŽb§ X$I’$IRģ°?Ou2`‘$I’$IŠ‘‹$I’$IRŒ X$I’$I’bdĀ"I’$I’#I’$I’¤°H’$I’$ÅȀĨĩŦ™Ė˜ūũÉžaÁqŪX3y ũûgsC^[W"I’$IÚ{ņtë}4'>‹ 'Œį‡ßĪĨßÍŠ_:‚ƒCm]Ûū%ž­ hÂŦ™=•§Í!ņrĘËÄC!BŠ ĘÉaÜųW1&;ĩ­‹”$I’$iŸ‰ī~4ß:{'d$īđ~MM ƒĮ7kJY<gOŊļšĒ6Ēqbž;á&_z“ōË!”J˙œ‘ä$H„õīå“?}*ųͧņÔĨ2åúĄāI’$I’Úģ¤Ė\yÁ0ú&5ßRĘk>ÄŽāk#Fqúéķ“Ū/ō‡)‹ø¨- Ũ|a–•OßHb÷ŦZũ.š—=žŽZ´K'0)?L1wđčíãčß,A /ŸÎ —ŨĀėÉ?ä†A˙⁑Žd‘$I’$ĩc]saķpĨĒ”ˇĻO剎ĘßXÆÜ)+XzÂx~8f,—UÎ}/Ø#Yöû€åž{ž~ÛO]]Ũļ/>:Jõ$Ļõ mh.õuõDWũoŸ;<û>îČĘž™)Į‘ŅB›P˙1<đh„›§Ž!§Ë´ŧ€i“dęœ|֔‡!”JFÎÆ_u3ã˛C@wœx6Sģ\Īėé—69į&ɤ5!ÆL.`bn“cΚėĢķČy`“û7=Y Įēęt?ø"Œ™Â‚‰C?Ս‚;NäėŠaÆL^аx9Ķg,fMR3rwũíŒŪƒK"I’$IÚ_„8vô(î0rĨŠÅĪOæ‰wÃÍÚÖRüÆ3Lî~%?úĘhFĖ€é~ŽĨîgöû€ĨŽŽŽ^Ŋzvø7}‡Ž_ū ˛ųcęj"DŖõûčĖaōfĖ&Lˆ1Wo1\ŲĻ˙8î¸cY>›žy5ĶË39îz.É9Öį3ãąiÜ<!ŸåÎææĄŲäæ„˜:;‚đĨdl1SžOŪúĄP˜üŧåģ=IY—O˜AŒĖ ÁĻĻ'ŒáXã/$cƋLũfOĘČ:RŒ9%Đcįį”0ũęŗš!2Æ\Å#2ŽĪgƝįPĐ{Ž‹$I’$i˙ĐõhN:ēųŧ $]đ3Øú˛Ļ”ÅoĖcÎŋą:fÅŦ9,|6C‡ÁĖįWPÛdĪĶGœĖ¨'ˇxĒYsūÅĖ9˙j…N´ũū)BuuuĀŽáĘ֟ēē:ęëę—Qŗeõ5ÕDëëöŅ™—“ŋ8 ĄFäė›#.˜t'ĶËûsũķŗyāæņŒ9’1ãofōß&’ËĻŪ9• gD.Ąđbæå7Ų9?Åä0rd*å‹ķ)iRįŧürčŸKN ŗ“>ûąú3~\ĪfÆėf)eÁ4æ”@Æčņd<Æ}yaBšˇķüÄKˇĩ_Ī]‹K$I’$ĩIŊûŅĢĨ UĨ,ūĪxãų¸˛–øĐöņŨzJ|¤döãfģ͜ķ/fĩĸ|ŅÂh'KĶPĨéOmm5õu5ÔÕVS_[M}m„hũžÁ˛žMå@(•.ûdåÚüeF dä’{H9ååM~Äčœ,ŸÍŧråä2ˆ0ųyÛ÷ÎË'œ‘Ãų#Áō|oÍ<Ę“ŋzääļ<…)†ceŒON(LŪÔŲ”79fÁ´y”ĐŸqãFžŦÉΧ„9ãF˛CÆĶc4ãs]öW’$I’ڋ䎊$4{¯æŖ…<úۇ˜üŌ,ž›ōˇŨũŊfЗāāCfĖØa ė$wĻ[ ķdš‡,_ÄpÚÁĄÚÚÚm °ÃīuĩÕDkkНŠĻŽļĸ}°4aˆ|j[9ĶÎĘÍÍßĪabÁTÆ´”)”ŧĮš0°|2c†NŪÉ9ײ|=Đ?‡Üū0iņbJČĻGãȒ99d Ņ?ü s `äPįįąœTFčßō!Sc8VęhÆįÜÁÕųO1Ŗd ã{�0m^ ĄėKąĩkk€T9¤yĮCdd¤ÂņŒ$I’$Šũ¨bņŦüocíŽoׯ“>bˇŒiÖŧYŗ­š*_ÄpÚAĀŌt ė°Ô4Ž\ŠĢ‰­ sH÷XŨɧš×ĄK*PRBy9ė84#DīŅã“ą=z)ɟNūŽfÃD„úįës[~œs° �ääô€ių,§Gx1ųkBä\Ÿ =`Prōæ-‡Ąũ)˜“O8”Kî§×ĄmËąBŒ?’PŪtĻÍXÃøK3§…tŨhzlíZCņ¤ļĐŠP—.°H’$IRûPšąœŨaKMM ßŗ7.âĄ[WsŌŽå[[§STnfÃ.ŽũE VļÚī––F°lũˇĻϚēÚZęĒ#$wÚHˇÔIč°¯ ÕŋaØé‹™‘fĖČĻéAˆĄãī iĻ1û†ŲäOßÅáē¤Ō ”Á ÜÜmáÄÎdῐ:59ģ)åĄAœŸMNNˆi‹)æ”ĘFnk+÷یNÎ´iŗYséĨ”L›Cy(—ņŸzu„pķĨōõ›>ũĻ$I’$iŋTĩö}ŠČĸīļw’tō ,X7Ÿ››-ŨplĪí/+ ßgũįTįūhŋ_ƒĨiĀŌü§Ļ&L}m„ēšå:°zu‘ęæŗÅ>ģÜoĻaō&MbAŦKíMF* kž´´Ŋy:‘;‚œP9ķ–“?'ŸpF.Ų™FNÎ XœGūš†5SúÜɈ˜}rŦĄŒ—kĻ1­ ŋĖ)'4rMķό#3€rÖŦož°„YŗÆŅ+’$I’Ônl|‡×ŪŲqāBĮÃGōŖ_ü‚îŊš1‡n˙ˆ†ŅwÛWđRĖ[ąŗB„v°�Ÿ XĒĢ#Ô×ÖP_Ą&\ÃĮĨņ°ĪÖ`†^ĪĪĮ¤ÂšŠ\vö$ōZœTNÁ´˜4; ĄÔ]C="Ây<6y͎›ÂyÜđõl˛/Ūd2M#…X_đ3ōËI4hÛ"ļŠ9ŲÎgÎä<–ĶŸ-=>hąĢ˙¸qôg 3îœÄėōTF~{Įņ2ƒ‘J˜üi;.†ËšŠ<•ß°I’$IŌ~*ĖÛ3fą¤ÅÉ!In|‚s|īķåäm[>ūĪ æ|øš¸ßj7S„€OMĒ­ 7ŦÁR[C]m„@ žú}°ĘȉĪ3‘ Ü2}2—ž8•Œœ\˛3ēЅ›Ö¯!?ŋ€’0„2Æ0ņ‰;Œėhnčõˇ3fŪÕLŸ4†ŗßģŠq#2ŽĪgÆcĶČ+éÁ˜Û›>…'•œ‘„'Ífv8DnĶEV2†‘:™Ų3ō÷GNKj֏˜Ž•1Žņ9rsūrč1žķ›¯÷2ôRŽĘžÁyˇpöՋŸÛÖä3cZ]r2 ī@$&I’$IíĖÆE<ņdwŽŧ`}“šnHĸīQ}Hú(žąßšmôĘĮīŧČ^ZÍžZ°ŖŊj×KuM¤!\ЉP_SM PO4ē/€ ÆL|œoOãą§Ļ‘—ŸĮėü0aB„z Üņ\2z<ãFfėzš@ęH&ūíyr&=ČÔ9rĮô†Q/9Ŗšãw×3.{Į#ôČÉĨGx2%䐛ĶtK6#…˜ž&5gŲ{Đ‹ØŽ•Ęčq9ܑŸĮ!ŖĮĩĐ&ƒņSĻĀ-wōØėiÜ1R3r}ûŽ ßÂPI’$IjWĒ įđģ?ä[gâ„Œí#Uv1÷k|QSĘâ93xöĩÕTļM™û•@4Ž^Ŋš=vˇėjÛ¸čĸ‹8ũốO,äßË1'Œ"�Ô×ÖPŲšUKuĶŦ6Ŧø‹iÁÍ'2aFoîøĮTÆíŸˇŠ$I’$Љ~q×>8J<Ũz`Pv?útíLJTU–ŗjųR–žģ‚â6Xbâ¯~öųŸtJJJčͧĪū;‚eʔ),[ļŒÚÚZ^zé%ęëëŠĢĢÛáßs3ˇ°dÁl‚ĄN$†:’l…,bųdîœQBęČÛm¸"I’$IZ6Ŧ}‡×ÖžĶօė÷öۀe„ m]Â.ĖōŲŗÉ_<iS§ŗœ\&^ˇ›§I’$I’t€Úoĩĩõä=x“–‡HÍÃÄÛ'2ÆŅ+’$I’$ĩȀE;‘ÁĨĶ—si[—!I’$IR;×ÖH’$I’$ĩw,’$I’$I12`‘$I’$IŠ‘‹$I’$Ij@[—°S,’$I’$}uîÔ ĸŅļ.c߉FéÔąc[WąS,’$I’$}?ī[°øØk@CŸöS,’$I’$}eöîŕ—L s§NûõԚŨ tîԉ+/™@fī^m]ÎNĸŅhtõęÕôčŅŖ­k‘$I’$IjWJJJčͧ#X$I’$I’beĀ"I’$I’#I’$I’¤°H’$I’$ÅȀE’$I’$)F,’$I’$I12`‘$I’$IŠ‘‹$I’$IRŒ X$I’$I’bdĀ"I’$I’#I’$I’¤°H’$I’$ÅȀE’$I’$)F,’$I’$I12`‘$I’$IŠ‘‹$I’$IRŒ X$I’$I’bdĀ"I’$I’#I’$I’¤°H’$I’$Å(~ë/kÖŦiË:$I’$I’ڝ`04 Xú÷īßfÅH’$I’$ĩGĢW¯œ"$I’$I’3I’$I’¤°H’$I’$ÅȀE’$I’$)F,’$I’$I12`‘$I’$IŠ‘‹$I’$IRŒ X$I’$I’bdĀ"I’$I’#I’$I’¤°H’$I’$ÅȀE’$I’$)F,’$I’$I12`‘$I’$IŠ‘‹$I’$IRŒ X$I’$I’bdĀ"I’$I’#I’$I’¤°H’$I’$ÅȀE’$I’$)F,’$I’$I12`‘$I’$IŠ‘‹$I’$IRŒ X$I’$I’bô9,Ĩ,|e27|ī\NũęP=Ė#2āKš s?œôķK#ûæTES{ä@2žŽŲûæˆmgÎU 8r ™ãĻPÔÖĩH’$I’¤jũ€Ĩt7Áˇ~ü^˜ˇŒŌ`:Ÿ@nÎq ėĨËŪäåGÎwN;ÛT´z9­má/rÉüŌΘŋûDæ\Į€#ĪāŪĨ­V–>w…<4f ž?“Ŋ‰Ŋ$I’$Š}ŠoŨ×2ãÖëųķ˛jœÍÄß\ËčÃSvh)Íį?žž‰ųËøĶIîßīãĔnŋWĖÂĨe{Ŋ×ĘüĒ ļB=j3ËXPôÚģŨŧ$I’$Š}jŨ,ođB^%З‹īšíSá @°{WūáVž‘”ŊÉ íyKd‹özäA)K–ŽkjԖ ķYRŊˇ;y/H’$IR{ÕĘK%Õ�idĻíĸ]ĘpîøÛë,}/ßøtSąt&÷^sAÃú-G$ķč\†ģŠÛ_\ĘŪÅ1ŠōĻpÃ÷Æ2ėKƒׁ9•3žwO/(Ũy7–žÄí×4Žsä`|õ ÎûÉd^/Ú>ųãõk“yĖÕŧ\ Tū•ī9Ė#‡rCŪÎĢŠxņ22<‘ķVņđ7ÖĻ6ŠyJJ™˙ÄuŒ=yû58õüŸņôĸ]Ræ?s—;•œŖ“yä`˛[¨{OEŠæņĮŸ\Æ[ĪŋGĮ‹°ō•_ķÃķĪ įK5œ|.?œ4“•-í+X˛Cû†u}N=˙:î}ĨđS÷FŅ3į6´ŲÉ´Ōg.ha{ˇŸ<Ė#ĪåE ÷ÂÍßKΗ¯ÁÉcš|Ō?Ų~ ۟ũ<e@õÜëÉ:r ™G_ƌ]\ö=ģöŽŋ;aÆ÷öûLq‹Ûgīl{ÅRfLēŽķNË%{ÛZJ CĖ+ny:Tišķ*ÆnŨįčÁäœvÁ.>WI’$IjZwŠP÷tē'•ŧđJ!Ŗ/ĖÜé䇔îŨ[|ŋtÎuŒũņ,ŠĢIÃČa= VŽb~ŪĢüéĻW™1w/ūáô=˜‰QÁü;/äÂ'—QMYÆs|zHa¯Ī{ž[æÍâåŸ=ÁfíPãƝâŧ›^ĨŒDŌŗs8q0”°āĨß°ā•rÅSOrãā ŊΘĀEÁ|ūōŌb*û2âŧčLchæÎ+ fâŠ ’yũ™Y,­NfđYg2$%HzNŗ4*XÁëלĮísaāĐã™UÍĘEų,Í˙+ˇœ_HäoĪrņáMÚG xčü˘XP É}š{:#“#-}“ŧ—~ÂWfqÅSOpãā=›‹Y:…ķÎŋ›E•œ~'žžF Š–ŊŲpæū“›ŸoVĨĖūÉyüđĨuT'Ļ‘58‡!ÁEK xųŅë™=7ŸGžŋ­Ét°Ŋm_ˌk.āG3×Abƒs‡32-™ŠâÅĖ_0‹‡ķ˙Ɍŧûxņžá´|gíŠD‚A€jĘōÍyŋ˜Bif6C†‚˛eĖĪ_ƜG¯faé#ĖŊg)ô$÷‚KˆĖũ+Î/ƒô¸čÔžĖæđ]ĖüŲũŊđyõw*ōšųėËøsa5‰éƒ8ūÔčŒPZXĀÂyĪsĮŧYĖųå3<s^“›~ås\8ū—ä•Abú N<õē+X2ī ^~ôMfĪ}ƒGĻŪʼn­V´$I’$}NĸŅhtÕĒUŅÖŽūį–áŅžũ˛ĸ}ûåD‡_y_tÚü•ŅMá=ÜũƒgŖį•íÛoXô˛Šĸ;ėöŅÜčõ'eEûöˎžûįĸ&û<=Ģ_V´īQ×F˙ҤųĻŲ×Fõˊö=vBôņ%;°iū]ŅĶĘŠöíwVôKšlXņdôŦŖ˛ĸ}:+úĢų›šî}{âYŅūũ˛ĸ}ŋ~_ôŨmíī‹ī—í{ėOŖ˙ŲÃ.FŖ ĸ?˙JV´oŋĶŖ÷,iļiöÎqlNô¸ŅwE˙ķQ“má%Ņ{FgGûöˊžpĮĸĻļ]ķūŖī‹ū§iŲŅpôŨ?Mh¸'ŨũĪ}›ĸĶ/n8Īq?šũ¨Ųļ×nl8× +įF›žęŖ.m<ĪOŖ˙ØĄî•ŅĮŋ›ĶpŧlûL÷žũ„†kķ•KŖĶV먑đ’ÆĪ­ß°čÕŗˇWõÁŸĪi¸.WžmŠëũyB ÛWF˙đõ†ûlĐąÃŖ—Ŋ°r‡}?zųŅãúeEûuitÚĻŨkwv~/|–ūî\8:ũʆĪôŦĻ;Mļ˙Ŗ…í[û4čâgŖ4ÛcĶÂûū†ŽŊ6úÚļō–D˙0:;Úˇ_vtøy;Ū;áĸč´+‡ĩxīH’$IR{˛5Siå§9ūįOr˙YHĻ’Âšqãøo0ø˜Ą w7LšÂŒ;ŸÚ°đÉÉ,¨†äSobâØôGŋtÎ-?E2Õ,xō9Vbūōč,*Idđõwqq֎C R†^Ë-c{ËøĶ3ųÛĪ˙ėUCÚ?æÆĄMG{¤0äę3:ŗ'™‰…,ÜõÉcW™Æˇīų)Į7ũ_ū`ß;�€âĨMŽaéL~q ëŪ\Ëņ; R 2đÂ[šb�P<‹?åíɤ’ C.ģûīž“‰×7‘‰į '¨\4¯ÉgPČ_žxƒJÉŊú&FîPw&_Yé=I).hÜįŗ´“j|õ­|ģŲА`Ön9¯'PÆėg˙š—ĶČZP C¯eâØGau?õNLĒ—ą¨0ÖíĖįÜߝ(*l˜.Ô=kЧFŒĨ ū<õ/?Į7–ɛĘÃËĒ!íLîøÕ°ī`:ßūåš•yS˜ąķz’$I’Ô.´ūcšƒéŒžįEæÍz€›Ī=…ÁéÉ@%ÅođÂŖwķŖņß`đ—ÎāÂ;_bÉß ™ŋ`Č3N ĨÉ,)C‡3$(,`᎞ Uä3§�`�ß–ŪR‘ 95‡d lŅö/ņ[Ī?07ûĶS›‚Øø÷šĖū ß=ŧųÆ},ũFd}úíîéi ßũ#•ÛžTGŊÁÂęī™œ˜Û¨dáŧU{pō Ŋ†gôØŗ8ą@„ŠŠRŠŠŠ~*VˆiZä 7§…Onđĩŧō¯šĖ}ęR~ÖöËú2"§ĨΆ kø<Ģ—°dzš'†œÚÂ}LŖav[„Hk%mÔßæzeĨ“žøZĐ|Ŋ• Ŋį0đđîÛūV–ĖͧH6œ!-MęŪxV°`‘‹ąH’$IjßZų1ÍÛĨ>œ‹5œ‹Hi! ŊÉÂyķ˜3ī ¯"īɟ3Ū›<ōü]ël”ŅđæÕŦ|æ—üpNKG-Ŗ€b–ÃNŸ(^ECū˛Ž—']Įĸ–ÚT6† ÅË(ßvūzĨĩņsŖĶŌ[ėÚļīŦMž›–S Pņ&_s]‹kŪDŠoSV\HŲ-†W;*eá3ŋįū'_e~a->§é÷ãâU”V‰iôړĩ5>K{€ÄžôÚŲâ;éit*+ÖQZ{ĐÉŨHĻ{‹÷AäĆßZ-"h“ū~Z÷3nå–W.ä–yo0qüîOĀņ9Į‘;b8'ËaĮ‡„E(*nxdy¤`*7\ķ׏YZPÍĘâ2 åđH’$I’ÚƒĪ-`i*Ø=“ãGdrüˆs¸’Es~ÍošĘĸÂŋrķ¤s˜ûĢl‚Dˆ4~c-ΟEKĪ:ŲŽšŠ]}ģDŋü–ąhæŦ––&m+"@pëųˇ.rÚ>D"ņGå2æĖ\ļëÆÕ{ ”2ûšķ¸bæ:HėÉĐŗ.aÄāžtOKioŠgrû]Í>Ÿm×;¸g׎5ÚĶęĢŽėúŪhö—ū3ųîã/3ä•Įxø™™ŧžŋŒŧ™Ëț9•;H&ëôpĮĪ'0d눞Æ{ąēđ ^ŪÍôŠHY,’$I’Úŗ6 Xv¤×ˆŸōģârīZLqū?YI6 LĘŌøÎÔ<îË)‚ _>Oááˇdä&ARR€˛Ęvõ=%Ĩq8Eέ,x木Ÿ(ŗč1nŸšpÅ´g¸ąŲú5Ŧ,āŪæûlŊŪl ĢvsŽÚGvÖ>RļwĄMãņöK­ÖߝÛų•Haā×ōģ3Ž…H1KäķúÜYŧđĘ,y7į–ņâ´k LIĒÉŧėæ^ßâ|5I’$IúÂhÅ5X"-˜ÉĶüš§÷`}…íë‰lũßút˛Ō�*(*Žqq‹ôžôJĒ×Q´Į‹iĻ“™PIQáNvŠDˆlĢw˙’žN2@qaãôŠØ-*h’u5W€Ha įI°ũzHãĩûlíûŌ}w틋ĻÕ¤õ¤WãԕĻSĒZúĖJ§´ėw>c÷H‹7oĨĨ-NÛQ0šgqå¯eîß'ņ4¨^öZАõJoxÄtiQŲ~õ7"I’$I­ĄU–%Oū’[î{ŒÛīÜŨS~",œģ˜j 1s� kÆĻsüО@5ķ_yc'OF)dūœ|V–îæë[JšY�ËxyîN&å3;o)Û•ΐÁ=X8÷ >ąpķiCČ:fˇ/Øõé?OÁÁ ˙ŋÁËK[nSēčŸĖ^TŧwO›IIlaāD1yōO¯É’2€Ą™�Ëxy^ ×{ådÆ3„Ŧ/]ĮėČgiŸMncû9-ĩ'šo6,°:xXø@0ؐ<T—ļÔ÷B^Ī[×RĪcŗ/’…ĪØß ’’Øđiļ*•žÁœOŨ;,É{‰§Ÿ™×Âߍ Ö&J+:=°qáŨĘüYĖoņ:DX’÷Oæ¯l­Õ%I’$éķ͊K #¯ž„ŦD¨.¸›ķž÷kfˇôEĒĸŲ“.ã‡/•iŒž`øļõ9^p)CĄzŪŨÜđbaŗīĒĨŧū‹Ģšđ2öĻ™-éÛ&o_x ÉĀĸ~ÆC‹šÕQ‘ĪŊ?žš+.9ËŸŲžXĐs'08Ēįũ†›_lúԔ –<ōĖ(ŌGņ­mĶ—’Ÿ¨Sŧ#eļNé(Ŗ¨h|OÎc{Ģxú'˙ĮüfuD–Naô˙c�� �IDATá†ī_Íg_Āũģ\ŒĻA÷Ėˆ1‹ūÉë;̘Ųŋ¸š‡#Ȩ.kŌįLž}á $Kø\Ú¤_‘Bžžk*K´3ÎáÄ`lí=úKūŌlTGÅĸßsû‹e@OF_ļũÉ?)Y}VųX6‹?7=˟ô3ūTœŧû ˛‡ļNĶŠ.-Ū‹‘D;ģ>[wåđÁ}(žû¯7ũs¨XĘü{–›íQÆËwũœ[nũ97<Ķüo(ú'/,ĒŌ˜ŲPA0w<ßÍĘūĘÍŋøgŗëĄč•Ÿqų%WķņwķēC\$I’$ĩsh4]Ŋz5}úôi•”æŨƅ?~žĨ• ¯“ĶúŌ+Ŋ!ˆˆ”­ceqãSi{2âįđģķ˛v)QôĘuœ÷“YW'’6 ‡ãŗŌFĘX’ŸĪŌ˛jH?…û§>Čč­OW)šÂØSîfQâ(~į>Fn;R¯ßy!—?šŒj’ÉĖ9ށé)PYČÂŧÅWCrέŧøø9Ū¤€•Ī\ÆyˇžA‰¤ef30=HEa‹Š+Ö%yę nÜø•62NžœĘ 1}ĮgI9ã.~7vW‹wV0ã{šüh^5$÷ečātH?‡G~5œ”9W1ā¯R}yĶ&đŠČlŨ>ā&ōĻ7Ų)āĄķ/cbA%$ödpn6‡'CEņ2æį¯ĸ’d_ö(Ī\ßÂ㧛‹,åŪožĮÅÕ$Ļ bäŠH‰”ądŪ, įáŠX0ū<ūT iŲŖyę™üčōatoē8.ÉdfgĶ+%BŅĸ7)Ŧ„ÄĖ3ydę]œ¸m‘˜ĪĐū'ō×VQØ“Áš9 LƒŠâ^Ÿ×ĐĮĄ×=Ę—7íc!7–; Ē!1ŦĄƒčŦĻti>‹"'pīÕAnžu ›DÁã§7îWČCcžÁÄeÉ|ëąLĖm~ļo˙Æc øŨÖíKÍŠß|ŒBIÍĀ”drū īr)’]Ü ŸŠŋģP:“ËOģž9•4œkh_R"eŦ\´˜ŌŦ›¸%s 7>ģŽÁ?›Ã‹6Üŋ ūąß›Ja5$gbȀtēĄĸx -ŖŦ:‘ĖsåÅ_ål y"+Ÿãōņŋ$¯ Ķq|N&Ũƒ,fAa$öå;x†;rÛøI]’$I’ômÍT:ÜvÛmˇ•——“ššÚ*'ę”qß{ ™]ĄnË*Ɗ)ZģŽâõ늠#Ŋ˛Žå”o^Î-w߯•_ëņŠUwSŽü:ßūú�’7ާđũwYøöbŪYŗēÍ)ãŽåד~Ä MWr­(āš'ß ¤C?žņũ¯7N7Ō'÷LÎĖîJÅÆuŦy§€ˇ ŪåŊĸ:R˛ŽįÛ?ž‹ßũ|8ŊšĐí˜Ņœ9,-%ë(*\Æ;ī¯bC0cNēˆÛ~s;vÚŪ8>ƒ!ũ*øß[ËX[ŧŽ7B¯ÜŗĩĢ/AúŠ7E‹ŪfåÚuŦ-ŲB0ķ$ΑI°p&Î\E]¸čė§ŧu{Ú \t^“íņ=8îė3šļ…Оĩŧ÷ÖģŧõîrŠ*:Ōgč(ŽøÅŊüōŧ~{öE<ž;_ûúą$–RX¸ŒˇŪ^Ėę2čsęUüzŌĩœphfVđß˙ˇŒÂUĢØĐ)‡3ĪČ"…N~ڙŒHPZZĖ{KßåũUeÔv?ŠĮŪĀo&]ÁqŨšžč3´1šQ™°ąl-KŊ͂ˇßgõ– ũŋ:Šüōˇüō›™Íî§Ž 9í+¤•ŽeÍúĩŧŋlEĨĩtĪšˆ˙ûÍ-œ˙:S^ZFeštėÖ o#˙}æĪüŋ˛ Į\ĘȌæhûö#Į\ʨ­ÛģÅ1‰Ëøī;Ģ(^WÆērüg5>aggvq/|ĻūîB§~ŒĖíÉÆâU|XRĖûËÖRZ— ąˇōģ;Īĸ[Ásüų­r翜ÃwŋÜĩĄē^Ã8įëč¸Ĩ’’ĩËøß;īōÎģ+(ĒH¤×1Účg÷0é’ŖiōA|ˇŖ9sÜ)¤SIIņr–,z›EīSŅ!!_ŋˆ[&ŨÅĨC:ĩ\Ŗ$I’$ĩ[3•VÁ"I’$I’ôEĩ5SiÅ5X$I’$I’ ,’$I’$I12`‘$I’$IŠ‘‹$I’$IRŒ X$I’$I’bdĀ"I’$I’#I’$I’¤°H’$I’$ÅȀE’$I’$)F,’$I’$I12`‘$I’$IŠ‘‹$I’$IRŒ X$I’$I’bdĀ"I’$I’ŖøÖ<ø›Å0f*”~uŅÖ<“$I’$IŌ§u@÷Nđˇķ!§W띧ÕF°|Į? %› W$I’$IRÛ¨‹BI%|åXôa띧Õ–ŅOAŊÁŠ$I’$Ijk¨¯‡1OĩŪ)Z-`)ÚÔZG–$I’$IÚKXWŅz‡oĩ€ÅÁ+’$I’$iԚK˜ø!I’$I’¤°H’$I’$ÅȀE’$I’$)F,’$I’$I12`‘$I’$IŠ‘‹$I’$IRŒ X$I’$I’bdĀ"I’$I’Ŗøļ.@’$I’¤MÖ2ĒúY’ØBõm]Î~Ŗž8ĒčČŦÄs)Ąw[—ŗWÁ"I’$IŌį¨kųVä1:ąŲpĨ™8ęé­ä[‘ĮčÁÚļ.g¯°H’$I’ô9y–@ ­Ģ؏ˆ2*ōl[W˛Wœ"$I’$IŌį¨#›Ûē„ũ_ @[ÚēŠŊr@,ˇ]DНn{/ˆë@B´âG\|‰Á‰ ņÄÅōB]m-uÕŅzâIčG‡@ĩuQjëĸDŖ4ūDųŅŧÎmÖWI’$IŌūÉŅ+{ĻŊMŸ: –¸@€(ââ–�P Đ-žŠäČ 2&Ä% õ5t¨üō÷ Ųŧņ#j"UÄ%I=Ŧ?]ëO°SĸuÔÕ€� ™M”()ūÁH’$I’tĀ8 h™ J4%@€úh;Ôpbé+tM8œõ̍ŲRÎąYüöBJ>XKmu„6ldíú2ęčĀĄG2úÛčÕ7‹÷’_WC”Û•íņ$I’$I:PK4.@\}„@‡:$@}=‰ØTĸôŖrÛđ"_íםøÎņŦ^UÉĄĄ�iĮΖę:ĒĒkYąūc –,'°yGÅŊĮa ÂõÉŦ!�õÛ–@�ĸQI’$I’TĀrTįyíŗØ°9Ė|Ũú@]-5Ą.”$ÆqĮŠI¨įÁ§ūÎēōJžÔģ;Įõ;ŒuNDÂ[čÕ5HĮA‡Ķ%)Dį”NôȈgdÕb^*?ˆōúŽÄÕGН4ΧûėË×ΆŋÚu›×^€“?€…×ĀĸĮáĸUŸųt-:į;đėĀß WÁĸ÷áļŲđō}{>I’$I’Úŗ*`9¨ļ˜O*KųdÃ:–ŋKĘa})ŠĐĄļžōŖGŋų9~õÛįÉ[QB ņĖYš‰3V•0aøąÔ&w$ŧŠ’Ū%Ņ))DYŅJԍĩ˙) ĩ[GĒ2ž‘ęĸq4ŦĘōŲÍNzsûë+Ά3ËāĖW!Üø^y)Šá${b#œû"”4žLMƒkNŋ_�ƒī‡‚V>Ŋ$I’$IíŰTDjН¯%˜Īæ-›é—æã tˆÖĐĄkOŪYú ë7VqÚIš7æ”UFųËß^åÁŧĨäNōA](Zŋ^%ĶšK5Ĩ+J¸ōÎ8yBOŽxŸÔ6NŠF‰‹aYčMeđzŲö×§…!ŧūŅ|”Jk,aX´ –o}Ŋ ^Ģ…ōoÁiiPPļĢ%I’$I:pPËGE%$t ¯§äÃuT…ĢIŠO PWKB}€5E›øęāŖšôū{čxPŠ‹×ą`I9¯#īŋ˙%Ĩg’:vĻ{Į0yīÕąĻ°”AŖG“Ö˙ËjëHÔ4<ĒšC� áséW8—Ÿ ˇeAZ˜˙?8w:ŦŲÚ ?×ôƒ>IPō!Ü=î˙,͊jFŅ”×6y¯3üd \ҎņøĨđėĢpĶ˙ įœŪP߀dWÂüĨpÅLX^ģÛ%I’$IÚĪÅĩuŸ§ē-QŽŲÄÁÕe„>ZÁÂW_$%Ą–¤`ęëëØ˛)Ė1ŲYt<(™M›‹ØTUÎ1Gt$-°…‘{“ĪÛīžĪúõ¨čԛÂΜp}åP_ĩ…Ш'1PO|´îsë×i§ĀņīÃiŋ‡“fCŸxxČöí?ųÜ}(Üũgč3 ŽXˇM€Ÿ¤íŨyéŋ=ĘÃ_ˇŽÁ˙÷=¸­+Üô$ô˜ž įžOd54É>žÍ‚‡Ÿo8˙ņ/B8 ū>rĪļK’$I’â9vÂ-<pÍ ÜÖĨ¨EÔ–ä/â¨žYT´’nĄ�ožú2ķYˉį]MJÚÁ$'n!yC„מ|†ÅkK(\VHč“2žÖÖUVQTö ņAŪú`#CģŦåđŽ]XûA!Ŋû  6.D|ÔFĄž.Úø¨æĪGø}¸haã‹2x6Î= XÁ~pĶĄđÄÃđHQC“53áîžpM.üöÅÆQ&-9–Ũąã[%Āš3a}ãë`_¸ĸ;üöAxŽqą–˃ÛÂoŋ ‡,…=€á‰U° XSį>}ąģí’$I’¤ö¨įÜ4‚õLæĩm]Kë; F°$j Ü‡ŽƒŋNuß\N3ŽIõŧúČ-D׿“Ņ=‰ ,cņßfđŪĢ'iķZīOfj<ë+>aÃ'Uqča =b EĢ×Ōmũ{,îY>ȝ¤Ž‰ÄEkˆ§ŽÔøüæļĖo6ÕguBŅYŸZ/iļĪĐãĐŨ„Ĩpæƒ0¸ņįø'á‰*øûāōÆŅ/úBjŧÖl=–E@( Đ0eŠüHxíl¸< 2B°Š GÁėnģ$I’$Šęڃôäļ.âķs@`é”G4Z uÕ$ã t?œožüe>\÷¯=ķ0CBŸН'.­Į–BM¤ŠšČčBŋôŽlŒFY˛rŸ”LŸCCtMI Šv3ũo=I\ J€z:tø<Į¯�ģČrR“øëm-lŦ„4YÄļ…ã.+i˛Ŋŧüî>žxBĄ†váf5„kPÃ:ŧkÂņa¸éĢpÛŲđp,[WL‡×7ī~ģ$I’$iŧrˍҜ>¸‡$CMe+ū;‡f­`Cc“¤ŪĮsūØaduQŗq5˙žū/*F\ĈuSųųKĢu>‚SĮŽā„ÃĶ8( *>ZÍ[sf1Ŋ Ŧá+čĄŖ¸íĮXü›ßđâ‡[Oۃ1ןc—?ĘmīáÎ+Ž#čûĶ_1æįšqĘ;ģúúÚîPKRB�č@4G\�Ēk;’Ú'“c÷âČ#zPüÆlÖŽ˙ˆŽĄ�Õ|\[K°SG~;¯˜¸ú¤z'Ÿv"ׯ'ŨL4PO]M˜ūG ¤C´–Ôׁ@ Ķcš÷ĨōJ  .|æ7ÛŽŨūæŊąh#¤voũRâGĖ4ųK …€đöĮJ/_ -mh›Ũî>ū>úLi˜n´ģí’$I’¤Ũ ‘5ö".;ǜ9OOfōē0I_áœŗ'đũøGš{FĩĄœķŊĶéŗv6xú*’ú0bôYÛ-Ö5&žcŽ8“jņÔãĪđ~e<Ŋ˛Gqūw/"Ĩöžx7ŧË*�(œÅŨO†¸ų‚4æüæOüģ4ü…Wā�›"Ô)>JR‡z:Æ×Ķ)!ĘA]:sP˙/íy(ũžšÅ ßŋ’#ONĮPO:tĻļSŅújŽí}W™Ŋ¸ˆwJĘ)­JāãĘ(¯Ŋˇ-38ė„qŌ%‰ƒRBtNŒŌ)Ž–n‰ûGIJēJ’GĒ”m˙Y†ōÍģXeŽīŪđØč`õ*(O‚“š-˜{üĄūŅ˜ íܸĄ –Â5ųj ivˇ]’$I’´:ā¤ėŽŦšķĶß+aÃærŠßÅsķK9äË_!+’úaPŌ‡ükúŦ(-įŖĩ‹xúÅÔ6™ĘŸųžvp9¯=7ƒˇ×–SšąŒĨ¯ŊÄĖ5É :a�{6맖ʚZ –Úp˜Ē/zēÂ6‚Ĩc‡zq°qãF-y—O6WŌģW†ŸĐ‡NŨ’ t‹rüŗ1díRÖ,.bū˙Örč–$›ÖķĶŪéĖYū!īĪ[ÆĀŪŨųËŌĄSwŪ߸šwq_v ‡öčɀūYtMëÂ'īljë.y~û!Üô-Xũ ŧV=ƒßŽ…Ô<2o;‡`p߆pFĨœt\Ķ~ąaAZVÁÃĨpÅ7aūß`ūf<n; žxŧĄÍI§ĀmĀürĨÂMĮBų‡° ¸p7Û%I’$I{āāÃ蕰‘ˇÖåú•%Ԝ܃ž]aCĪ4NJX]Ú¤Á‡Kyŋō+ j|yČá=čXUÂûMÛ°™âĩI|‡°ˆĘVîJ{t@,ĩUå,~w ˙ūĪ˙ãíˇßb˖Í$…’Č’Å-ˇ^A—ž‡S[[Jđ°>ęD÷Ô.k",Ē……EĢč܆ Éd҇Ķ­ûÁœ’s,ų˙[Ɵ˙ō,/˙í/t ųō—žĖ™G÷A'z[w€{‡đ¸ûč“åáī¯Âš˙Ų͎]áŲīmŽjsÍãp˙օuk᧏Cųxø{Đ# J>„‡Ÿ†Ÿ6ļš˙i59¸æŋ§Íl`vˇ]’$I’´’B$QKEՎo×ֆŠ"žø$H… Ēųt0UMöIÅCm˜šfŖNjjk!"Ą•Ęoåé§§°āÍ|ĘË7 žz6Wnāå9¯Ū\Î=ŋģ—ŽõĄŽîH Ōõ°Î|û´cH{­žWÂUTË) ÔĶ5­+‡§tĸ[į�gŸv2UÕõT”¯§K(žÖ­áāŠ|køüzÕũĶûá§-m(ƒ!7úíû;D;a¸˙ųfīíÆs†įö´ņf¸įĪpOkm—$I’$í^UC’’´ãÛņI!’SQ5á0$Ä7 B$5ŲgK¸âC$4[k3!á05;- žø*eØŅÕõ˙Ė˙7h)ã ÄÅŸ@EÅfĸõĩŧ:ŋ€ģoų%ŋ¸÷>:t:˜hŨzj+>ĻC\-ŊIáāŽJËëŲ´%ÂĻO"TWןâ´#˛¸āŒŸđĩÜS8ôŽŦ˜3—äÂîFˆH’$I’´¯Ŧû€ĸšãčĶ;šL:$ŗ UEo„ •S“܃^]aÅÆÆ‡öŖ_“…U6Ŧ,aËÉ=č×–n{BP*}zvĨfŨÔVRK< MÜP‡|j–'v8 šMHH„ējˆÖC4JĮPˆƒēĻĐ!.Žēē0¯üsOũáwÄE;@RO}3ĄįĄ¤”Ėq‡@Ę+ĢCÔĪæH€ĸ6rHZÎņ eC÷nÉ =íd:§umëîJ’$I’žhBŨé{ädíđ͇ôÎ@xsūģ‘ž#ÎâÔ#ĶčÖ9•ôŖFqūņŠÍ˙ī×BÕō˙ņ~ÍaŒũeútíLˇŪƒųîØ#hē¨Jmá<ūũQ*_;ŠcM%šsY'Å¨Œ,ø×;Tl,Ą¨*™Ŧ/Ұčm|ĮŽ9Ž^M§…kŠ"•ŦūŊ8¸{į/|ÔōEīß‚‘Œ=5—>ũŽâoŗįRüQ ‰ABÁD*+6QŨ!Â3/Íåđ>äž=ŽēāfĸÉ:8‚/;RXOÕ˙VJJ"!ĸCB"ĩõQÔQôÁK?ęĢ$阖´û‚$I’$IÚŨ†pū%C>õö’§ī⡂0+fü‰GkGķÍsŽä›É°eC KįLå…×Jfûl^ÄSOĘEcGđߎĸjŨ ū5}•c/æØ­ķjK˜ūđ3l;‚s¯ø )I5TŦ[ÁŋŸüĶ ˇļYÁ ĪŋÉEŖĮņË;Ąjc o͚Üîã9ikQëŪäĩ•3ö2Ž[9{ų/Z˙ ĩ™*`5|—]v)O(-“ÉOMáã’5ÄĮ'ŸHuu„¸„ū6ãUúôīĮaŲ¨¯ĢĄCŸLR‚éķŪĮôĢŦĄˆOH .1H ’‚ PfÃĻ0’BÄÕ׈ļuw%I’$I_ĩŧ=åvŪŪmŗrū7c*˙›ąķ&Uk˙ÅäIŗļ?:9ūž› Ĩ›ˇ7Úŧ‚šSV0w§Ē|wŋ{ˇŲ‰ŪŊ×ļÕRÂkLÜūú î€ XŽä^Ž|tëĢžpÄŠpĎmŪūüz0 āāÆ-Y‰Úė õ@QãīË÷}͒$I’$í3ĄŖšč§g“ž|:OÍYÁ:Ķīä|)~/ŧ[žûũĩSTĀ"I’$IŌ-üO=Ū™sFãŌkÆĐ‘*>^ˇ‚™Īâßwŋģv΀E’$I’¤HÕÚų<ņûųm]ÆÎõ!I’$I’¤Ö`Ā"I’$I’#I’$I’¤°H’$I’$ÅȀE’$I’$)F,’$I’$I12`‘$I’$ésmëډēh ­KØ+,’$I’$}ŽļD;CԘe—ĸQÂtjë*öŠ‹$I’$IŸŖYÁs‰Ú×čŒĪ[4`VđÜļ.c¯°H’$I’ô9*Ą7/$^Â'ŅÎínLkĢ‹ø$ڙ/Ą„Ūm]Î^‰oë$I’$I:ДЛ?olë2´9‚E’$I’$)F,’$I’$I12`‘$I’$IŠ‘‹$I’$IRŒ X$I’$I’bdĀ"I’$I’#I’$I’¤°H’$I’$ÅȀE’$I’$)F,’$I’$I12`‘$I’$IŠ‘‹$I’$IRŒâ[ëĀĢ.YMŸ>}Zëđ’$I’$I{eõęÕ@ŸV9ļ#X$I’$I’bdĀ"I’$I’#I’¤˙ßŪŊGGYŨûO2™™@BHˇp$­ ZjÅZŠhOĄõZ´G°Ģĸ-ZĒũYۊĩ⩗‹ļ刎Šöī—jÁ֊ļ*TšÉ-HB’™d2ķû#A‚"CH”÷k­Y‹yf?{÷,t­ų°÷~$I’RdĀ"I’$I’”"I’$I’¤°H’$I’$ĨȀE’$I’$)E,’$I’$I)2`‘$I’$IJ‘‹$I’$IRŠ X$I’$I’Rlí$I’$I:ÔŦXũ˙ķĀ,ǎאH$Zģœ6#--ŦöíÁ8úõîŲÚå|*Ž`‘$I’$é Ząún˙ŨŠŦĒ6\ŲM"‘ ˛˛ŠÛ÷GVŦū ĩËųT X$I’$I:ˆfÜ?ĢĩKhÛH&?sߓ‹$I’$IQUuuk—Đöl¯Šií*>•Cú –ŋŦ‚Ņ*zõČæ‡į°1ū6‰x’†@=õuI*ËâÔöīACûÁüYŅ�ŖzÆWō:1īO÷ŗ¤0‡šĄ}Ųēq;í+ëøãŲ—ˇö”$I’$Iú\øŦmŸ:¤W°deGŠŪ^KlŲb&Œ>†Žy¨ŨĨĄ,AŧĒž@¨q’Dļ¤ZįK Ųt|õUfūøZ–fĀĒ.Ŋ(ų÷6˛Ķę‰tÉhíéH’$I’¤VrH,Õ¯î›ĮĸX¯ŋú2—r‡õíIŒ8É8DAØTGC0@Όv­|å¯žFޤâ˃Šo_Gŧ}€hUœÜôĘ֞Ž$I’$Ij%‡tž=QÁŠ^aÉŌíŧđ~sy”‹OJŧ|ęËhHˀ@€Hu’ÁÉzÚ§Õ0rĖŠÔDZ @‡51âą�‘ė’YŽ`‘$I’$éPuH,[ĘŖl¯/eÃĒÕԔ÷gŅû ŧōø_ø“OĻ!"ÜŠĄÜ�…C|!7Äq§ få€BŪ‰ČĖ RtT&YÕ5lۜN|ہŦ,ÆS—Ąī€#vž åø3ÆqŲÔGXPz ĮJÅë\ũÅ#8yęĸÖ.¤…”˛ā֋8~ĀŒyx}k#I’$IjÃé€Ĩts 5Õ5dļ™x‡åŽėƛKV’žm=ŖF…Ú5ÛČĒ 0p{Ũķ`Q(ĖKåõÄõÔl˛5!÷đδĉnmS ģŸËĖäOĖäOŋģ‰kF÷Ĩō…_ņ3ÆqËknIjQĨ¯sËųßā˛gļníZ$I’$ImŪ!ũĄúô8eU„ Dk*H´g{ī"žúßg8ûō tŊā ;„ nZÎēmĢøku áHUĩI:g[%#œA}įNlŦjĮGe÷ሥĮĶoĮûSžÂ7/ž€éį_Ė´Ģ~ΐį˙‹Ķsü°‚uĪÜŜœīķđīēq÷‰—ąŽĩ ’$I’$ĩi‡ô –ŠŠ4ęęĶ f$IˌP_##ŲŽ!§ŸAvûLڗ¯Ĩ0˛#?–ÃN:‹h(ƒv‡w"?36Æ”AMV&Ášdäu:8E‡‹™xķĨoyŽģgīØļcÅėøö§0°i;ŅiįßĀS+bŸžv-Į8_.Ü­¯…ŋâä§põˍí*—<ÆOĪÅāAGĐwĐ)œvÉo˜ ’…Ōŋ1ũō1œüÅ!ô0„ãĪøO~ųĖJbM¯{xOŧ–§^ū Ÿq ƒ ađ—Įqõ§lķÉsˆ1įō! ŧü1æß:Žã 劗÷\rÁ¨[™ũģąa€%I’$IÚ‡tžeCœĒŌzjĒHÄD"ē'ŦYō¯žôŧķÚ?xūŅ˙eæ=˙EtķržT|8kHä…HDԈFbǎ“Ŧ‹Īh8x…÷;‰á}áŨ×5† ÃÅSžƒĶĻ2ûššŧüųTr˙�� �IDATį[ųFø%&}˙.ŪÂCÎåôü ĖyfÉ.ŨŧûĖ‹ŦĪ?•o ÃēĮ¸âüë˜>—;Ÿ˜ËËOLåąĮšėŌ_ņVlOEėŖØ"nšāÜąî(Ž™ņ ¯Ŋ4›iŖáŠĢ.æęšÛœÂ„ŠÛō"ˇÜ—ͤG_fŅâ—yøĸ0sŽúw4•ŧ/m>yaÂ!¨[ōGî^w&ĶœÉ5Cö\v¸ �ŗI’$ImGc.üüöʓčÜÚĨhé€%=’F}4†J¨ŨÖ@l}5+ß}‹ÖŦfķēÍtéĶ‘Ę­Û¨ĢŒņđŒ{Ú>‹nÉvÔ"Ŗ0B‡žŲ$Ûe‹5Đš]ü VŪžųPWē…R€âK™ųÄÃÜ3ųdŽčםÅ'3ņĸ“Č^ų: ÖáÁ|ũ´|Ö˙í9Ūũ°%<ũˇ äŸ6šaaxwÖy™¯qãmßcxŋîôčw2oû>G¯{„ģ_Ø˙ķ^b¯ŨĪC+ģ3ū×7p֐îôčËđËĻ2iđæÜ÷";Īë 3üßã耎øöĨœžŊЧf7…>žÍ>ĪĄ´;ßųŅ RLSI’$Ij!Ŋ;å{ŒčØÚu‡tĀĖĘ &aãú ÚE2ˆ‡Ô' |íFj*+ųŌČáTnXA‡Œ8ÃG$/Ãøî_¤W‡4”Ĩ‘••EZU”úĖ4^\Sv+5m‹ 7Ā΁•ķĶķĮpڗOáø‡2øĒgŠĸŽXĶʍaŖN%å‹<ŊĸŠ‹%Ī1ge7Î3(åŨ…Ģ ø$†5 Žã”uŧõÚŌũŽtÅëKŠĘČĐžÍ¯pĐnÔ­\Ċ¯íÖ&ܗâžPē~Uŗ-@׿SĖĄīqnũ‘$I’¤–Öą Ũŗ[숃į>äļ!Ņ�éIB=ŗi A(™ ’Ų“ō†=ėēįdâ ŋ$™‘Nˇ‚ÃH#ƒ~I˜ĐéxM›ÕŅR23Ԉ¯ORMÄĘ7°r=dÎ'(ũÆLYĘŅ?šĘŖRŪĖiß_ĩķ–ĄŖ9ŊûŖĖa%×ôëËģĪŧČĘî_áÎ!�uTVK¯cȀë>2Z¨o1د§éÄ*cÎ&ŧÛÍáp*Ģv†'Ąá]Bá0ÔÅbûØfßæĐ8xŽÛ$I’$}>s9ōkgqæŪfC}ÕŪ˙×\ūōÜûėXŲsį9™â‚õåĢyåÉŋR9ōģŒÜđ�×=ļēąQÖaœ6f$'õ˧S&Tn^ÍsŸãÉE[ˆtũ7\5ßvŗ7îŧ gOžœcŪû=7ŧs4S'GĐį'ŋāėŏrÍũ‹9˜{?ļC:`É §Ķ.Ņ-QĒ‚íX!˙°jģ‰VSēn1Ũ{ buÉB–}đOúôDYä§gsQîyĒė_ŧ’ÜLV(ÁĐN]ZŨą…Ī2g}ˆŖ'G˜RæĪū;UĮOeÚe'S°Ŗ 1v=:e0ß8š˙ûĖ‹Ŧ¸,ÆĶ/Ŧ§ī¨s9€99ĀņSxúį'}$H įtßīGįä„!VÕ¸’ĻY'ąĘädīŧTא|˜|TQY Ą9„Ąq.ÛĻåæ I’$IŸ ŠĮ|—˙üBsúØ%ŗ× Œũօ\ü=7?ĩŽxd c/9“ŪĖáw-Ļ2ŗ7#ĪÍ1y°ĄŠ›`ΞđmFÄōāŒ‡Y^¤Įā¯qūyß%'ū[fžũäRV>ĮÍ÷EøéEųĖŊí^^)~ŽÃ8ġEâíÉĢëBzEyåí鐨eíÚE‚QŪ_ų>ëĘ×Ō@‹įŋÄ˙ūæ÷,~įÔąD˛öéažYpgu?‚v™ ĶęNŅ•¯sËõ°žûšL8-¨Ŗ2ĄüæĢ2J™ķØ?ŲŊĸŖĮ|…‚•/2˙åį˜ŋŽ;§.nú¤€#†ôõ+‰õčKŋ~;^ŨÉ gSP°˙ŅDã’]ĩ”×V6ŋēžˇ–l T<¸)āXĘkKšEBąU,\=úvkŒ|\›–›ƒ$I’$}&d dÄāŽŦšûO.+ĄŦē‚õī<Į# J)<öŠƒyøŅ•š‘ŋ>ųwŪ/­`ķ yhöûěmå ö=/uŽ`Ū#OņæT•oaÉŧĮxvM6G4}Ûõ§Ē>ĉGŖÔ~ŪĶņ,šåqâąõ^؁ĩqęĢĘ)/)'” |{O˙~Ĩ_]BĪˈmKRģyÛún&;3A$™K,YĪđÂÃÔūYöį_`Õ*Ū}íõσ`c”.y‘?ũūq^‹ äę°0@wŽ(Χî™GxháQœ•ŋ…÷Ũ˟BĮŅ—˛péz*{t'' 9“Ķ áO7ma]ßąÜŲoįPGŒģ”Ą˙œĢ¯Ė´˙<‰á-ŧûĖm\ũەœõā3üŋ!{(*×/būËģ„ΥߐÁôz)įõũ6]˙+†üâBŽÎņîėŸsĮĸn|ķ3ˇ85Ũ˛āˇŋ⩜ī1,ŋ’ŋũ ķc™ôaôÉmR™ÃŽb”.YƊʰ”R rÉ"ŧļÂŲô+.ÆŧF’$IR›ĶšˆåŧņAÅ.—7­(ĄūË]čĶĘēå“QģŽÕĨÍl\ÂōĒ8Ēémaŋ.´Ģ-ayķ6Tŗūƒr2†QČBĒZx*ŸE‡tĀōՋž"ēæ6ŧڎXĸ’h}yŨ¨‹UĶ! 7”IC $AzôīA¤C{ÉĸÉm4â44„(ŠØ@AĮŪ|5ûŠ_āúĮ™tÁãMoBdį÷åč“ȟ&eXŗ_øÃ&ßʄõ×rĮˇ†sKv†}{ wūē;sļ\Ė/¯ÃÕˇÍåžQ9@ãĶ„îŊoÅ?ú ũšÕc4÷<ˇLũ#ŸsUd“ß÷8žyÛ}Lú„`ĸꅛųî ģ_=ŠŸž4‹ņ=Ššæß’ũŗÛøéų°Ĩ*DūĀ“8īŽû¸fhŗ~CĮ1é}™ûãosõŌJÂŨķÍ_ßÄÄæE~R›æ°Ģ-<uũˇšqQŗKŗ&ķYÍįõ)ē“$I’¤ƒ!3B&q*kwŊGŠ%H02"¨Ũ}ģN”Úf÷dD‚RŋÛĒ“úx"2Z¨üĪēC:`Š|c(Ģ^Î%‘QEff@áˁdÜ™|á‹ũ(,* Ļ´Šx´ŽŒü’ u$ë’Ô&jˆaûÚͤ“Fįŧ^°˛0gũn!gíkķœãšfÆ \ŗÛåķ|™ķvģvôu/°ōŖgĀ6vS<š͍û\įņL{ã]Ļ}Rŗ‚“™øģ“™ø Ír†^ȝO^˜R›OšÃđÛ˛r/Ÿíԝņ~—ņŸØN’$I’ÚÚÆ %'s×ËÁĖ™DŠŦ…úh2‚ģ…2›ŨSC0BFš'1‘ DŖÔīĩ€ ÁC8e8¤Ī`YķJÛĒ7’ l§!‘$--É42Cí)>æ yęqä÷."Ė -€d’hÅ6jˇ–̍¤ēt5åÔn)ŖŽĄēĩ§#I’$I:”mXËēúŽôî™ģËåž]Ȩ-a}9”mŽ >ģ =:6kĐĩ?ũ›ŦRļĸ„šĖ.ô/hŪK.Ŋģu¤~ÃZÖÄ̈$Ŗy˜ɧđ#´:‰Ë!°TUo¤ļϊšę*’‰ =˜FF(ĘĘmT•—‘–§}įlrģu‚úõU5ÄŖQęëbl^ą–ĘōJņĩÛË[{:’$I’¤ĪģH}Fņ.¯ŪtĪĸK™û¯rúŒÍiōÉËĘĨûžÆųÃrYˇāU–ĮĄöŊˇY^_ÄČŗŽĨwĮ,ōzáŧ1‡ŅüP•øĘŋņĘæ\ž4ækŲ5—ėŦ|ŠGŒækŊĘyí¯‹Š(/a]m6ÅĮÖxčm0ŸcÎ>Ž͡EãԒKņá=č\õšZ>īķûXŅX”úúąh=õõ’@F(ƒx˛†’õĢø÷ümt-*$Ę&=$’&Ŗ]Ō`Ûæ26~PJ™ÔGŖ$öžHJ¯āÛ÷ąōÛŠˇ‘$I’¤ĪŊŧŖ9˙ŌŖ?rųŨ‡nbúĸ(ī?u/ŋŸÅ9c'rN6Ԕ•°dîüe^IãnŸę…<x_Wž;f$WüäkÔnxŸŋ>ųUcÆsĖŽũ@ņžŧûajƌd܄ČÉŦ§rÃûŧrßŊ<šrG›÷ųËŖ˙äģgũ?Ÿ ĩå%ŧņÜ\æ\ˆEmø'ķV äė1˙ɏV<ɯīųe-˙ ĩšC:`IĐ@Zz555DÚg ĩ#L¯o`[uņUõDĢt鞤}‡ÁHz¨‹Įذē„meQ {䒌7Pŋ};tüä1%I’$Iúôâŧy˙/yķ›UđöSđöS{oRûÁ_ųíĪí|trđ0ΈĘŌfG_TŋĪ ÷ŋĪGžgŌLÕ;Oqį;ģ ôÎ/™÷a-%ĖģgÚÎ÷Ÿs‡tĀrëo~žß÷1ā�"I’$IŌÁÄwō-ēŋ÷$Î}Ÿ2˛č˙å‘|1¸ŠŋŧSņÉ÷k¯é€E’$I’¤CJt1ÎČbėY'ķŊ+ĪĻĩlŨđ>ĪÎxŽW<Z4%,’$I’$Bj?XĀĖģ´vŸ;‡ôS„$I’$I’I’$I’¤°H’$I’$ĨȀE’$I’$)E,’$I’$I)2`‘$I’$IJ‘‹$I’$Ijs@k—đаH’$I’tegeA2ŲÚe´mÉ$YíÛˇvŸŠ‹$I’$IŅ%ŽƒĪØęŒƒ.hüž>C X$I’$I:ˆúõîɕ—_JvVÖgnLK dgeqåå—Ō¯wĪÖ.įS ļv’$I’$júõîÉM×_ĶÚeč�r‹$I’$IRŠ X$I’$I’RdĀ"I’$I’”"I’$I’¤°H’$I’$ĨȀE’$I’$)E,’$I’$I)2`‘$I’$IJQ‹,iii$‰–ę^’$I’$iŸÅãq‚Á`‹õßbK$Ą˛˛˛Ĩē—$I’$IÚgÕÕՄBĄëŋÅ–ŧŧ<*++Ѝ¨ ˇÔ0’$I’$I{ĮЍ¨ ˛˛’ŧŧŧ§ÅÖÆƒAēuëFYY%%%†,’$I’$é  ƒ„B!ēuëÖĸ[„ZŽg'Ņšsį–B’$I’$ŠÕų!I’$I’¤°H’$I’$ĨȀE’$I’$)E,’$I’$I)2`‘$I’$IJ‘‹$I’$IRŠ X$I’$I’RdĀ"I’$I’”"I’$I’¤°H’$I’$ĨȀE’$I’$)E,’$I’$I)2`‘$I’$IJ‘‹$I’$IRŠ X$I’$I’RlŠŽ7lØĐR]K’$I’$í—nŨēĩHŋ-°´TÁ’$I’$Im[„$I’$I’RdĀ"I’$I’”"I’$I’¤°H’$I’$ĨȀE’$I’$)E,’$I’$I)2`‘$I’$IJ‘‹$I’$IRŠ X$I’$I’RdĀ"I’$I’”"I’$I’¤°H’$I’$ĨȀE’$I’$)E,’$I’$I)2`‘$I’$IJQ°%;˙ûjøÖ,(ŨņdKޤCEz� ÚÃįÃņ=ZģI’$ImIŨģKŲđßŅPQ „ĒIZ€ôŽštģõį„ž0°å†iŠŽ˙žNų#lŦ6\ҁ͐„’*8áX¸ąĩĢ‘$I’ÔVÔ-[ÁÚīN"QVN !ŅÚå¨ 4$ˆo)cíøIÔŊˇĸÅÆią€å?sĩˆ�$pöƒ­]ˆ$I’¤ļb㏎'L’’@k—Ŗ6$H&lœ|}‹Ķb[„6UĩTĪ€ •­]„$I’¤ļ"žŠ´ĩKP—ÄKˇļh˙-ÂÕ+ji ū%“$I’$íŖd i;D ņ)B’$I’$I)2`‘$I’$IJ‘‹$I’$IRŠ X$I’$I’RdĀ"I’$I’”"I’$I’¤°H’$I’$Ĩ¨Í,cŋÉw}Õ^¯~ žš{`Į*<’?ƒslˇŸNXr#$ŋƒ÷đņđŗĄö(<č…I’$IŌį]Yˇ<MŋĮ’ŅÚĨT´ģåiúMŲ ôOŅëŗČÔÚu}ļĩš€€r7F4ŊÆÍhxūĸ=‡Ÿ ]áöãZģI’$IRÛvųßA‡Ž­]‡vlíö( WÁ{;Ū¯‚yq¨øœ‘‹ļ´fq- f-sO…sŪ†'ĸ­]$I’$ŠMęڏp'¨oí:ôms˞Ä! TÄw^ęĐîŊ ĘÖ¸•hã÷áWũwŊ­y›Úëā¯gÃá+]t!ÔN‚á‘™Å^-xfˇŸáOhûՓá­CíPū#¸÷8čĐôŲôÁ['7kœŊjŋŗkŋĶ ¯ž°įūīŊîŖŸ ͏ŠWĶûÂ>đđŽīūg°ę2øqŸ}¯“|Xō3øU1<< j/ÛŲˇ$I’$ĩ)ĄBÚ]5•Ī=MŸ×ŸĻ÷sĶéōũcwYą6h4]œEŸŋ=MīG§’7l f<M¯ĩŗQŪąäŪ2ž/>MŋןĻ×ŖSé4˛ˆ@ĶĮÁqwĐīÅÉd6ģëhēŋ>›ÎÀcޠ׉„‹Éb}nūáŊģĘ cÔt{t}^ŸCŋŋÍĻhúdõÚĪPũ'Đķõûé´Ëīížä=:‡žW lzߞđ˜Ét|v͘ŗčqĮxÚ5_i“57Ūąsū^K‡c:îüŧ×XŠūöGō†¤ķŖOĶgÆh‚@ú1cé<ã~z˙­q.=g\KnķûڈĪDĀRØn?*ū W4]ŒĀŨÁUpî=Đûv˜° Žü\ļãŦ–\xü˛ąąÍ°G!Z ķÎnöCŋ™ágÃŨ]áâû`ūA^E‰Ã”g!÷h¸2ī톞 Ο ž…ˇÂ/Á°3aÖŅŸ?žöŲ9ŋp Š‚ŠŽ0dG'ų0,ķVíoąp÷w`āFqôž Ļl„.„‹˛ö­N€hFãĒ’—`Ø_ d?ˑ$I’¤–ĶžĖ)Ķčúõ5?ŋ‚ĩg\†›‘>æ&ē^5°1āČ:‘ü;&ŪúĪŋ„õŋ˜Gú÷&ĶĄ[hg7ĄäŨ}ģ-ĄlŌ%Ŧ>g"›Ÿ†ŦŠĶ(Ū~ßJys:뮞G+ØzŪhÖüt>É=ĩt ]6æßĘú˙¸€5ãoĸĒîD §]@hOí€Ā1é2å(ę˙p-ëΚ€5ãī¤&ôu §í8ßψÜģo%oĀ&*ŽžČšsŽ ôB:ŪqšÍūĩ=Îĸũ÷FĐđ‡ÉŦ˙Å<˛†S0íBËîfÃˇ.`Íų×RļŦˆŽwL&;¯…&ŗŸÚæĄŽ°ôÆ]/•Ŧ…qĪÂĻĸ0å Ö4…!k^‚yĮÁšEpO =FTð'á5€˜đ,ÜŪē�;˛‚pøŅđø‘pà x¤‚Vąém¸áD¸áL˜yŗšîŽƒ…/Áġ/­y &ÁŧS`đ[°` p6 Â˙ÅaX(y #ōáĩ-ĐĄFáĘũM4ra`&<˙&,júŽÖ< K߄’čžÕšhG_Ëáǎ÷ŗI’$Ijiy'’;˛ ŅģŽĻlA͝´ųwS:ûXŠÆŒ!ķŽ›ˆž0’öŲ+(˙Í#D×�ĖĨôæ~=ÔķÃ&cƐĶģ„mįŨIõōÆkĩ÷ßJųđGč4îDŌįĪŨ‡bęiˆÕu$̎“¨ÛKŗe°ņŧghXž–�›¨xø_äŪ9˜ĖŽPˇqŋŋŊJPD°j)Us—R_×8fŲՓØŪĩŽ86–ÜeTŸFåâÆ NņÛnĨâ‹3čpŅQlûÅŋ›Âĸ<xõZļÎ]ŲØq˙~dd—QķĖ?šęŪDõ¯¯Ĩ4Tøy¤ĸmŽ`)…s˙†4Ŋ†Ũ3káųīÃeÍVwTD`Ęxklüqã6”iŠ†t…h),mÖõšˇaôėfįģ�]úÃķgÂŦGá×­ŧŒâîg d�Ü^ŧ‡ķaHÆGWž,XŅ–ÛÖÂÂ`c˜pFŸÆĪŸß#Н ëŅU°`‹ÜĪ—Ã„ą0ũøj—ÆíG‹ÖÁĻøžÕšÃÂĩû[„$I’$Ŋ‹ …Kˆ.ŪõŸĀëßXI"ģ/‘ŽPDZՊĻpĨÉōPģuįی/ö%Ŋj%ĩÍÛPNlq iŊ‹ėSŒęĒĄ÷Hō§O§čņYôzn6Ŋ§Ž ZÂãÄB#č<}2šŖŽ%Ô5Ē×[ž‰$ЏôØRj–5?=f-ҎË(nļúŖŽØâf?×ü‹š ]Čšqų㆓Ųŋ#ĘŠ[ŧ’†ŊL­¤mŽ`‰ÃŌ’f!H ŧļ˜7Ÿ 3…X>< tY?Ḱ ˜ ;ŽO‰D€ZØöqceĀí߂Ht9ČįŽėIlLų7<~:Üž|ˇ3įvå¸r÷æF€-0¯†ՍaÆĖĩ°`-ÜÜÂoÁE°ā%ˆío‘q¸ęXz \|"LŅr˜9Ž|bûRg“¨úJ’$Ij˲ŗHŖŽ†Ē]/'ëĒI" Ŧ,¨ĒŪmģN5‰f÷˛BPWM˛n÷~ę +k/gŠėŸôQ7Đũgũˆũ÷­lšģ‚†päDŠĻĀQvŗü6Œ/!÷ĸ¯“ũÛč”]Güíy”ß<ĘåÛIëÔÂ#čú÷Ŋwk!éė8¸ˇšdUŗĻîßlõ%{Ü$:ü0‹Ä†ETŨu'[įŽŨķŠVŌ6–ŊXXšĐ †ÅáÜŲ0ĮÁˇY;ÀŠhãj–||Čōøl¸ģ¨ņl–‹ÖÂ}­´Eh‡'ž…y“Û<ĨųĩũÎ|n.ŨíĻ8”4Õũü*˜Đ ĢaH,ˆÂ{ˁŗa`ncč2ëSžŋŲũoJ5ÜķlãĢC>œ{"Ü>ļ1hšj_ęü˜sf$I’$ŠÍ¨j RŌŗwŊČÎ"í4TA˛ēÂĄŨB’,ԚŨ“¨ŽƒPV㠒f!K +Õģ‡3ŠčHģ¯GúĶØ|˙ŋhØ14ÄaĢaËįSöĶų”‘ApЉtøū$ î QÎMġn‡ĒWØ<ūØũßŲ“uÕÖšGeKŠŧíįTŪiŊŽĸũ¸‰tš:•äÆ Ųēø€N*%ms‹Đ^ +hü_D˛iüąŪėŠBŊŽ„ađal´p#Đĩņ<’ „W/ƒá;ŽÕÃŦˇaū¸šn˙~0&ķqĒaÂË0äT¸¸yb´ÔCīŧˇĨŲĢēq%ȎiÁr ..†ŠĩM+6ÂÂ,8ãHXҏĘeoĸqČŨí vū9œ įôßųTĸm[āžgáųzR°īuJ’$IR›ˇl uą.Dîr9㘞¤U­ ļâĢ7‘čԏpķ'æô?–ĖN;߯ßXICv_2wy|j!‘]H,[BMĢYBíw .Ŋö´}čãöų„H AbëöĻķW�:Ō~Ôāũ�ęĘI6­ÖųPVĄfķKī"íúī8ŦˇžøâųlŊkõŠw…ø˛ÄŗģÎZę×ė|ÅĢęHl-ßkĀč:vÃv>i)ąæßTŨ6ƒšXá^ûx8đAŌ6– éÛ^_-†_]Wv„™¯6ū@_Ŋ *˛áĘŖĄ0 † ŗúĀŧ*č]…AXôXÛĮĀđ.0´fž ŊËaa|ˇ1ãpÃ#°ēĖ<yOE\īŊ 3ŖpņQÍVåDáöˇ`Øé7÷€é—ĀÂoÁŽ˙ÜcĢ`A\Ų.ßyīŧ ¸ōD¨XÕėŲ=X°ö‡ĄM>.nvn Ošu ΅^šđÕ`DĶŊûZ§$I’$ĩ YED†Kæ.¯ŖåÕ˙ üé"ߛLî°"‚y…„†O ķ˜.ÄfĪ&Z‰Wį“{Õ(Â];4’‚)ĮAŗ3X’oÎĸrurĻL ]˙BŌķŠČŧp2,Ąęžy$€†e+¨“3ŧņWS ë‰tēhāŽĩVב v' $ŖWĮ=ŦJŲDlYi'Œ"gPGŌģö%ëĒkÉŠ[D…D’öiĪaŲ¸‚XUíž~,é�Ą"˛~øuBÍV⤟>žÂ;¯%wx_‚];ėu9žHÆÖ%D7BrÁlļ-+"÷ú d *$=¯Đđņtũķ ēû˜­KŊžNÁ´ŠŽ;–P׎ģö%sÜh2)!ēlû§œHËj›[„:ÂŦKvžÖÂę¸rÜŅ´ĩeÛ¸øu¸ũėÆ- —ÄŲĀ)đü)J×'áŒp÷(xü˛ÆG!/xF<ÛŌė~äJŦÆŊ O…_-‡Ÿ´æˇMmwä6ģ<˙I87 7Ÿ WfĩO:cˇ',=_ŌxÖĘķÍΚˇn>f-ųøĄg= #ÆÂŧ7Ž8YđLy īßøÅ–ÃOÂí§Ā‚QЇÕ[ĪÆšĄäSÔ)I’$ImAˇĶé|įéš\sŨh6ÎŨNôļĢŲXw~6N aà jūp-[ī_Ų¸ōĸl.›¯îGá”KčöÄDËūÉļßL§aĘ­díØTˇ’˛ 7˜r w!˜]G|Ų?ŠŧúVĘŪl:sdņlž¯ˆ‚)3čs} ËūAųmœ>igQ˞fÛ'’7åNēŋqë&>ÃîëĸwŨDyˇÉtüŸGČĢú€čėģŲô‹M´ī4NS§ĶųēņT~šī§î_lũÅĶ^u-=˙‰+¨žkŊnĸÎ&¸ŽMĄ+țr':… VFŨ›˙`ķ„éDë�VR1a2É)Éģãëdd7ûgíũé'Éw°ņæ‰ä_4™î?Ė#-VMũšETũô:Ęw?ˇ´•’É>‚5­��.IDATdrõęÕôîŨûĀvüĶڝ´GÉ?š$I’¤ĪŋĮ4 9˜Yí Ô5{trčX žģ‰Đ.`ũ,˙™š-é÷úœÚߎLĨmn’$I’$éŗ"k8…O<FëGéUH°×@˛§\BVhUķ Wms‹$I’$IŸÕķŲ<)üĢÆŌåÁI¤SMũ˛Q>i:•[ģ8,,’$I’$Ĩ(ąø16_ōXk—ĄVä!I’$I’¤°H’$I’$ĨȀE’$I’$)E,’$I’$I)2`‘$I’$IJ‘‹$I’$IRŠZ,` ´TĮR“t˙’I’$I’öQ ™„´–û!ŲbKa6lŠŪuČKBˇœÖ.B’$IR[ėœO2éPí]äˇX˙-°üy,\a ’€§Îoí*$I’$ĩ]ožž@ @€Ļ• R“@2Ųø÷" ëũĸÅÆią€å¤ŪđōĨĐ%‚žôĸ$-�…Y°`" îÚÚÕH’$Ij+BƒRtī¤åu$™æPí”LK#-/—ĸ™wĐ¯ÅÆ $“ÉäęÕĢéŨģw‹ "I’$I’ôy´#S1֓$I’$IJ‘‹$I’$IRŠ X$I’$I’RdĀ"I’$I’”"I’$I’¤°H’$I’$ĨȀE’$I’$)E,’$I’$I)2`‘$I’$IJ‘‹$I’$IRŠ X$I’$I’RdĀ"I’$I’”"I’$I’¤°H’$I’$ĨȀE’$I’$)EÁ–ęxÆ -Õĩ$I’$IŌ~éÖ­[‹ôÛbKK,I’$I’ÔÖ¸EH’$I’$)E,’$I’$I)2`‘$I’$IJ‘‹$I’$IRŠ X$I’$I’RdĀ"I’$I’”"I’$I’¤°H’$I’$ĨȀE’$I’$)E,’$I’$I)2`‘$I’$IJ‘‹$I’$IRŠ X$I’$I’RdĀ"I’$I’”"I’$I’¤°H’$I’$ĨȀE’$I’$)E,’$I’$I)2`‘$I’$IJ‘‹$I’$IRŠ X$I’$I’RdĀ"I’$I’”"I’$I’¤°H’$I’$ĨȀE’$I’$)E,’$I’$I)2`‘$I’$IJ‘‹$I’$IRŠ X$I’$I’RdĀ"I’$I’”"I’$I’¤°H’$I’$ĨȀE’$I’$)E,’$I’$I)2`‘$I’$IJQpĮŪ{īŊÖŦC’$I’$é3'Í–^ŊzĩZ1’$I’$IŸE%%%€[„$I’$I’RfĀ"I’$I’”"I’$I’¤°H’$I’$ĨȀE’$I’$)E,’$I’$I)2`‘$I’$IJ‘‹$I’$IRŠ X$I’$I’RdĀ"I’$I’”"I’$I’¤°H’$I’$ĨȀE’$I’$)E,’$I’$I)2`‘$I’$IJ‘‹$I’$IRŠ X$I’$I’RdĀ"I’$I’”"I’$I’¤°H’$I’$Ĩ(  ’H$ZģI’$I’¤ĪŒD"A0š–H$B4mÕĸ$I’$I’>KjkkÉĖĖš–ŧŧ<*++ŠŽŽ&™Lļjq’$I’$ImY"‘ ĒNJĒĒ*:vė@ Ų”¨444°uëVbą ­Z¨$I’$IR[•žžN8ĻS§N¤§§ÍI’$I’$íŸ˙Kķō3Eį_����IENDŽB`‚����������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/login-new-user.png��������������������������������������������������0000664�0000000�0000000�00000065535�14156463140�0022251�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��_��Œ��� H B���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨ}\TeÂ˙ņ¯­Ė RB™ Ŧæļˇƒí ŽĢ°Ģ&wZøTjjšięZiŨĨ•ĨŲŊŲö –ŠĨ™‰Ļĸw í:đģ“ņŪÜÜ Ô‚27„Rf�į÷Į Ęਠˆ~Ū¯¯ôĖ9×Ã9gxuž^×uÚ9‡������āWĩv������.g„/������Dø�����āA„/������Dø�����āAíŨmŦŽŽÖącĮdŗŲT]]ŨŌm�����h~öŗŸÉh4Ęßß_W]å~ŒKģú¯šŽŽŽÖwß}'ooouėØąŅ�����Žt§NŌɓ'uōäIuëÖÍmŽŌ |)))ŅUW]ĨŽ;ļXC�����Ú˛Ÿ~úI’ÔĨK—Ÿ5ˆc***ÔĄCΎ �����ā2ŅĄC<yŌíg —ĒĒ*ĩk×Îã�����¸\\uÕUĒĒĒr˙Y ˇ�����āŠBø�����āA„/������Dø�����āA„/������Dø�����āA„/������Dø�����āA„/������Dø�����āA„/������Dø�����āA„/������Dø�����āA„/������Dø�����āA„/������Ôžĩ������<¯đë"Ŋ˙áFũtâ¤N:ÕÚÍš W]u•:uė ģÆŪŽĐA­Ũœ&cä �����—šÂ¯‹ôæŠÕ*˙ņ§6ŧHŌŠS§T^ūŖŪ\ąZ…_ĩvsšŒđ����€Ëܚu[ģ ͧ];ÉáhS}"|����ā2÷ãO?ĩvšWģv:qōdkˇĸÉ_�����@›Ķ–ĻOž������xá �����€ž������xá �����€ž������xá �����€ž´„Ã˕ŽčYYWFŊ8ŧ<IááҚení–�����Î_{]ÛŖ~3b¤îž8AŨ7ASÆĐ_ū\×yˇvÛ.mí[ģmO…§­ŅëŌeŲ@ĨĨĒđö–ˇ_°ĸL&%ß5]IŅ~­ŨH�����šMû€>ē}ôP öŠŗŊ˛˛RQ1ũôŸ•VíOßĒ÷˙zH'[Š—2—ķQ‘ŖåSĻjąĨTōöS¸)QĻë2ĘĻī˙i‘eËYļlĐûS–iõĖXü����ÚēĄ š6)N!ębÕ__Séúš~0TÆŨĢ?öHŅĢŗu´5z ģ,ׂfËĐU}ŠøŠī4SŠG´aĘD-ļT¨kŌ-›ŸŦđzéJŁ-š5u–Ō–?¤YQŸji"#`�����mØ51ēģ~đrŌĒŋmYŖ÷ūVęڐ¯ĢŋRŪ€ z(i”ĻŽ,Õ+›S[› _^zé%:uęôOuuõé˙ŪÛĮĄĢtJ˙Žō×ŠęSrüGŗÕ]‘öŠX*ä=WĢ%+ØÍ>ŪáIZēĖĻškËÔš …–æhÃâ×ĩ&ŨĸÃĨ’ˇŸ‚M š0}Ž’ŖŊ%åhÁĀŅZĶyĻŌļLŠUįa-OLÔâÃŪJZžŖEņĩĘL›Ąčf™–fiyxíĘ.ĸŦéô˙õ)iĩ˛Å6čF΂ŊĻBIËŗœĮWЖ ôúÖũ:\!ų›”<sžF4á”�����.ŪēyÄPõŽ3âå¤ö´\ī}YQoß*gŽĶō€izø–JØŗT[žkÁĻ^âÚTøR]]­   IRģvíęü×áøB×üĮ¯%ĩ“íĮcĒŽ´Éá8ÕL5WČŧ5MōVŌô nƒ—Ķ“ĩ`AŠ,MĶŦ˙œĄ-ĨÁJLžŠÉĻëĨī-ÚēbƒæN´čž4͍VŧÉ[kŌĖĘŠ˜ĸāš‘6Ĩ™ŋ÷–ˇw…,æRü™”%ËlQ…ĸ”hō–Ž×Žđ"ƚpˇ‚ˇĻhMÚĮJ[ĢÄ:ÉŅÖô#R×dŨ/IG´eÆhÍ2KÁIĶĩ !XÆī-ÚúüåôhÂy����\ŽéŖßôŠ?רƒĸ&=ŠĨ5­´jf†Ō˙’­CújGēöĮŒVlÜĪĩũŖ¯TUëČa ƒ44aÛĒv¤˙EÛĶ˙âN\ÚÔێĒĢĢ%Õ ^j~ĒĢĢuĒēZĨEĒ<q\§*írœĒnϚȲŋBō6)ÁÔ<%f-~^[JÃ5ķŖ4-;AI‰‰Jš0WË?Y¤x֚į×čˆ$SBŧŧ+ö+ÃRë`‹YûeRbĸŸJ÷[t¤V;3,ĨRxŧLnf<]xYᚐ.U¤ikZŊt3gƒŌHÁ#&(Z’rVčs…ŧãįëŖES”\͝õĶĨũG����h:ôčŠ wœ´j˙įŸ+ķ‹ītŦŧJíŊΌë¸ļĮ jRō íŠëëļ=ũ/Úá&`šÜƒŠ †/ĩ—Ú?UUvĒŽTu•]§Ēė:Ue“ãTs|ų^ĮK%yûŠsŗŦĸ›Ĩˇ‘‚ã}ŠJKkũ(J#LŪԁ4e”JŪĻxEŠBsΙŖÍU›tWB”tĀĸũ5yHé~YK]Mņî§E]DYÁÉdōސyMšJk•™ŗ!CGŽädįˆ™Ã‹ŽČ[ĻäDÕÉēŽĐ„x– ���€ļÂį?yÕÛVytŸ–ũ÷›Zži‡Ö¯~S˙ĩđMŊ™YĄ¨˙øšŽģĄ’FÅŠ÷ĩ’|ŽÖĩnæÚÔ`Ž„āEjcĶŽĒĒĒN‡-’ęüšēĘ.GU•NUÚU]e—íš1|q…’­ÁgĨÚ0:Vssęo7iQÎ%šËŽüS‡+$XޤØåÔųĩ|/)ܤøpiņūũ:ĸhuuHéj2):Ę[á¯+=GJŒ•*,fŸF$„ģ/Ōī"ĘōĄ Ļšay_[$iBWIĘ҆Œ#ōŽžŦÁ5];,ÉO×__ŋãŪ ö“ęD7����€ļã¤öīØĒüPUwsU{&LÔŧ¤zģ×Û­Fí°åJ^¤6žÔų"Õ _*]#^Ē+mrTUčú€ŖúĘŅȕ>o×ĢŗŸ¤#GTZ*ÕŌá­#’•|&–9bŲ"ËŲfØØ*T!Iá´tfŧûWR;+8X’‚e2u•6X´ŋb‚ēVė—å°ˇL3ŖĨŽRT×R™3HąáĘIˇ¨Â;^ņ ×Äuš˜˛ŧ•8!QŪæ-Ú°õ°&L vM9ōVÔã#ÔĩĻkÎÆËĪM§ŧ;wá ����´ å?”ĒR7ÔũRYéæ9û‡lŊųĖ!ũæÁĮt{Í4Œōõīŗ”}Ĩ„.5ÚTøânäKÍ++íĒŽĒRĩŨ&ŸN?čZŋcōúYsŊØ*ÜšXí–ũÚjŠPRbídÁ[ą¨vŪ‘6+M–-g)ŽŗŸ:K’w°ĸâãO‰Ž7ÉoYé9RüqŗxGé.“$EËdōֆœũ*•”‘S*oSœâ=UVüáˇE6¤éđ”):˛!]ĨŪņšĐā•Ú6UÔ_øZRé÷Įn����\’N~ũ/)R!§ˇtPÔ Ęúvžúą^Ą›ģųkYáŋô} ĩŗ-hSkžÔ_ę˙TVVčT•MՕ6•ūûg:tČ_6{ũŲi.ūŽęĒ ™/VÖÅæ×CÁ~rŽąâîķúÉE|‚LŪĨĘÉ8 KēEÁņŠvå&S”´ß,Ëaį-ቍŒ¤i–˛b5!9X:ŧArĖú8ŊTŪ‰ÉĒE÷ –TĒÃß×O_*tø0Ŗ^���� Íøá ũõ‹ēƒ:†%ę᧟ÖŌ—g(é†3Û> N!§Á­ĘĘøĒąYGW¤6žHjžØí6ĒĒÔŠJ›*+*uĖÚ^jļ5_$ÅÎÔSI~Ōá5š:ząĖn§•*gÃ,-N̐ŧũ΂ÄjD‚ŸTa֊å‡ë~TaÖŦßE+zʖZtLJˆōÖ÷9+´ÕR*ŋ¨¨Ķ ęú™ĸu}…EéËÍ: p%¸{ÍQWVxr˛ÂuX[Ÿ_Ŧ´R?%ŪQwœMpT”üT!ˆē ķęđŊoq3����p‰ĒĐߡîPŽÛI%WËĮõęö=(é?|Nrėķ­J˙ŽEØf´ÉiG’L?ĒĒŦpŽųRUŠę*›Úĩk¯SÍžČO‰‹>Ō"MÔŧ-Ë5eā›âÜYeĶņīËbÉŅ‘ É;8I‹–.Ē3"¤žØ™ķ•”1C['iô?§+9!XÆī-ÚēbƒĖGē*i~íˇųɔŦŠÅiJĢđV|íE]‚ãíˇ\i[ÍĒčš,“ģ×ÕëĮE•œŦ Ļ×5×r@ę:AwÕ__&vŠĻGoÕķ<žą_âÃĨÃmŨŖÎĻ`ÉĖĀ3����h3~ČÖ{Ģ4mRœB:Ôū ƒBnēQŽļרņ‰§GŊû"Eol:¤æZärqŲ„/öJ›3xŠ´éTĨ]íڝ’ÃҜá‹$+iŅn™îØ īoŲbVšĨBō–w×ë?A“GLPrbđŲ§ūH’_ĸ}ō‘L‹_ךô×ĩ`‹s´L°i„ŧ6SÉŅuKčjŠW׊å:"“âMĩ?‰VB”ˇļ˜+ägJPtzqqeųiD˛I ,f]?"ŲÍ>Áš°zĩ4īy­HÛ i’_°I#æ¯ÖôŠyŠ%|���€6ådaē^{ũ;Ũ>z¨Ÿár}ÜŊz9Îõ—JĢö§oՇ=¤ōÖiæ%­ÃápÔŪpčĐ!uízŽ%`[Į=÷ÜŖaÆIjžČō˛~1`¨ÚI:UUŠ*Û >˜§Ąsv´b‹/OYsjâÖZđé%_šˇ ���� –YOŋĐ Ĩ´×ĩ="ŨS7^sĩ|;H'ËKuđ@žōžüJÅ­°ŌÄĸįžlųJĪâȑ#ēņÆlo#_V¯^­üü|UUUiĶĻM:uę”ĒĢĢëüwlč åfĨÉčŨIīŽō2=0ō:°\Īo="ŋÄųAđ����W*ũûë/ôׯŋh톴9m"|™8qbk7á WĄii˛ėĪІ5[t@ņZôø9ŪĒ�����$ĩ‘đ­í{™_ŸĨÅŧ坤Eķ)‰Q/�����4 á š XSļДÖn�����mĐU­Ũ������€Ëá �����€ž������xá �����hsÚĩk×ÚMh2Â�����.sWwę$9­ŨŒæãp¨SĮŽ­ŨŠ&#|����ā27aÜíR)rNíÚ9ûÔFž�����p™ í¤i“'ęęNÚÔtúÚĩk§Ģ;uŌ´ÉÚ#¨ĩ›ĶdíŽēãŽ:¤Ž]ģļV{������Ú¤#GŽčÆol°‘/������Dø�����āA„/������Dø�����āA„/������Dø�����āA„/������Dø�����āA„/������Dø�����āA„/������Dø�����āA„/������Dø�����āA„/������Dø�����āA„/������Dø�����āA„/������Dø�����āAíŨm<|øpKˇ����� M3nˇģ _ÂÃÃ=Ú�����€ËÍĄC‡ÜngÚ�����€ž������xá �����€ž������xá �����€ž������xá �����€ž������xá �����€ž������xá �����€ž������xá �����€ž������xá �����€ž������xá �����€ž������xá �����€ž������xá �����€ž������xá �����€ž������xP˅/ļíēģOo…öjė'FĻAcu÷ĶĢĩģČÖāđ˛ÔŠÎũîÚ¤˛k4Đüv˙1VĄŊzkøÛ…­S˙#1Îúß+n•ú[ŋ;Ú ĸÕÕ̎Bû<Ž´Ön ���Đ Zaä‹AūĄŠŒ¨ũĸĐ@ŖĘŠ÷ËüáBŨķûQš•nmųĻÁĨPo&õVÄļĢa �����ÎGû–¯Ō_#^MŅŧȆŸØŦ}0gĻdÔÆGg*ö/ĢtG@ˡđŠW–¯ŦBIA­ŨxÂĀ˙ÎVáK’Œ­Ũ����¸2\RkžLē÷Ĩ(ÉžW+SZgZƯĐĸ\{k7Eđ����-æ’ _$IÆhŨį/I*°ä7mڋ5G??]Ŗ~¯č>ŊÚ'FĻßOŌC‹ˇĢ ąl…J{ûqĢ9ĻWoEürˆ†ß÷_z7Ëũ”'[Á.ŊųĮI>(VŽujĸŌ¸?.WZ#•åm×ˏLԐ_šŽé¯¸ä隟’wAëOœ_y9š?¨ˇB{ÕģERYŪ&ÍŊo”Lŋ<ĶöûīŌ™%v\ûūH%’ė;g*˛Wo…ö™Ē­uēg՞u˙Ĩû“‡ČÔ'ÆYÖ¯†kÜ—ģ]¯ĮšÆHŒîN-“ÕüĸÆ ŠUD¯=”ŪøÕĩeũ—âzõVč/§+­ąeŗhÖ¯z+´WŧfeÕ*ËV¨´ˇŸÔ¸¤škëlߨ?ŧ¨ŗëfĶÖ?8×@ĩÎŨ(6Ĩšûŧ`š†÷ę­Đß/QnYž>xd”L}z+"iš íÕšŲŠ2ôîĶSëÜcĻAcu˙Ķëĩ§‘™xey›4˙>gũĄŊœ÷ūܔ<•ÉĒîę­Đ^ąš•ufˇkžÔŦą1čEåĘĻ‚ô%ē?iˆëû+SŌTÍMÉ;īihÖŦõšû‡ąŠûUŒë~•é÷cųnžīũZ[ąvŋũ¸FšÎ[Ä/ã5üžõqŪųËŦë&)´WoE?’!›­P[ŸŸĒáŋĒ)wˆ†˙aIŖß÷ķë¯$YĩoŨ‹ē?y¸LŋŒqũŠ×äéšŋ.G /yĶöˇĻLrļ÷ģ^3ëzs­ĩ5j]ÃlæĮŨ̎"îZ_§~közÍwõ-ÔÕˇ¸¤Ššõ^FÃkŌ¤ī‡ë>K>sŸÅ%O×Ëé…Lw��Āe§Ļ›¯¯¤Ųm6•I:ëĖŖ‚õē{Âŗ2—H†Ā( 2@Æ2åfdjÛ˛ŊJÛ™Šˇ×ŧ ĩ ąåh~ō$­ĖˇK>!Šī§ ƒTVœŖŨiAÆgĘzuŪxæŧå•üĒōėųG˜”8Ō_F[™Šō÷*kĶĢĘJŨĄYÖiZä™!ÖôĮ5ęŅ*Ž9&Ž›ŒåĩĮü™VÎųL[w.VĘÚ<ģįüË3Čh”$ģJ,K4îéÕ˛†FĢoÜPŠ$_{,ųJ_6CûŦokįKqōU7ÅOš,ÛÎÍZk)‘čž!!’1Za5Ũ˛åčÍģĻjQNšëÜ SĸMEy{ev‡ŪOŗc|OˇÂh4J*—-oŊf­[ŖÜ€(õ5äĢFcĮhDčGzĢ0SkwZ•8Ēá]`ËÚŦŨ%’‡j|Ŧ́eš;z†ÖÚ%˙Å  _ɚgŅîk4{įĨ/\§ˇG6(īŧÎÁ#e%ÚŊx†í´+2ϟÂüũ/xP‰-{‰ÆŨˇBŲå’Oh?%Ž ‘¯JTŗWé>ĢôÔíšûū2Ũ[ëŗe/ҏģV(Īî<æÖč@Š$_[įŒĶžė'4°L’Œįn“Ņĩ‡­\šëĻjū3ų 0õĶĀ!˛îWV~ĻÖÎÉQ‘ļé=7םĸ”Š5'S%2(0Ú¤D“ŋd/QAļEۖÍTš9_ë6<Ļž§wž÷k ĢļūaœŪY"ē)fČ`õö—Ŧy;47y¯ōFų4ŠŊgN…Ņu*ōõÁ}3ĩ ÛG‘1ŅcSAÖ^åí\Ą˛r4˙ŖUē3ėbúkUÚ#ˇëí%ÎīRė` ô‘ĘJŠ•›õ™V>ķ™ļf,VęÃ\ŋĪcĶ`õÖ^egghŸĢ­ūŲ˛3´Īõį܌Ŋ*7LĩŋŠš9*—7ĀU¯MëfhÜ35} Û‡øKeßj_FĻ6žŠ­)ôŪûO¨MAMø~Ŧ›ĒQĪėUš|hĒÄPŖlÅųúøŅqÚ7n0 ���./Žz<XSķ¨HuLē)ŌŌs°ãšÜŗîčøëÃ&GHĪHGÔÃfG…këņmS!=#!wĻ8ŽŸŪ7×ņƈhGHĪhĮāŲfĮŅ:Å96L‹s–3mg­cŽoVŽq„÷Œt„üîĮįĩ?p8ßlœâˆęéšåĮį5•;Ž;>æŦgä[§ÛTĶŪ¯VNtDõŒt„ß›rĻ ß|č{S¤#¤gœcęÆĸēĮŨé˜ų›HGHĪhĮØĩEg;ĩv!å8Ūøs{Ô̓S7ÖmûŅm:úõŒt„Ü4ÅąĄÖy8ēvĸ#¤g¤#|Zjƒž~>o°ķŗ¯Ô;wŽ/]į!ä7ĩĪÃņų<įučwË`ĮČEŲŽz§ŧņ.¯ãŦëÎë^[W}í,wđĸšę¸ã͇]×üŽeŽ/+ꕷqŠĢŋŽž)gË´hGHĪHĮHˇ×ĸÂuíë}~ôCĮ؞‘ސ›âũ~÷ cË7Mė”Ãáøëlįũ=ė­‚ZÕd;žr]Ãa ˛ęŖŖŽOg;Ī{ȈeޝNo/pŧs‡ŗmũŪYįߡĖ1ōæhG¸ëžyj_­úv3lĨģū˜QˇLtŧ“[ûä?}ŨCFŦr4ŠĢfĮĖ["!=M īņŠlĮsŋĢš_kˇúÂî׊=O¸ļqŧQ§ŨޝÖēŽyƒß;ũģæĻhGԈW¯}ĐņlĮKŽs5-õLyŌßÜWƒ{F:BnyĖņiũü¨ŲņÔī\ŋ/÷]ČūšŽ—~éé9ŌņÆWuwũ|vœ#äωŽIwF;BnyÂņyO oŒˆt„ôæxŠækõÕ2Į0×īî™iõ*>žíxn„ķ| ˜—}æzëûq<Õ1õfįųš´ąîųĒČ]æyŗëšŨô˜ãS���Đv4–Š\zĶŽŦģ´2Ŗ\’A}ĸĪú/ö6ķŊ•o—üoĶ‚įâꎐ1ęŽgTŦA*7¯ÖÖZãį}ãÔk¯>¯?=ûā™Šu >F}$•ėמĸš­%*(˛KōUXLhŊ6v÷b­[ŗNŠĪ>Ũ†}Ģ–+Ë.ų ™ŖEŖë0Xķž*Ų•ĩj}“ĻŠ\pyI˛KąiҍēmâęĢ=_ŲMY^Įē]oĨ|+)D÷žôXŊsgTīģŸŅ’ŠwhĨšát™4{F´Î2āĨŽ áckė–õú¸ūI˛Y´mg‰¤ëZŊŲēK+w–H ŅĪMQīz7OĐ(Wûė{ĩ6ĩ™^ŗl/QĐØ'4â"'ļ™Wkką$˙aš7ĶTī(q惊7HĘ_¯ĩŲŽÍE™Ú˜c—Ĩ‡Ÿ\įū÷™ĸESCe?ßĩ{ė6…M}ĄÎčÉWũĮ V $æ(ˇICB5~ábŊŧđ-¨?ĘČ­ņ##$Ųĩ/kŨį}ŋÚ´'e—J$ų°^ģ 7G÷D4ĨŊnØũuĮsĒoí‹á­‡g“ŋ¤rķví>}›_@­Å*’¤@“úÖL§Ų^§Ÿ,Ķ‘˛¤âMū’ eΊ=y(Oæœ)t€ÆĮ…J%92įÕú¸,ĮšØļ?ÅģęŨˇjsdՐĮ4/Ą^ÅžŅš=s°|$§Ž×žú÷F#ߏ˛ŒÍÚ].)ô6ÍŽwžŒ‘5ox7���—“K'|ąYU`^¯‡&<)sšdˆ˜¨ŲCÎū˜žģĶĸrI>qƒk å¯%`€"%Ųs”•}æŠĀ7,N‰ÃGjDŦkP}™UÖĸb̍ȿZŒ´L%å5G*,Ô9*mņ‹nÖ{PīØh…žGS¨=YßJ2¨īđnÃߨÁękT˜Ŗ}į|Ģöŗ×wˆ›ãŒū $›lMX֝Š}vIŽķÚ@¨Æw“TŽ}|jˆiä:5&`°ÆĮûHĘׯMyu>˛emVZšd09ũ`wîöĒŦķĄ.ˇŠë SˆúĮ^ä&9§”Kō15v/›\Ãß*7ĪymygĐęæa\RØđÛsŪ- UBœ›ūø‡:§ŗŲËekʉ3Ēoü0Ũ1j°kƚMe֚īXąĘœ)‹ėåen¯CĶī×båæ9ÃÚŪņîÂÚP ŒŊĀy“b–hŒāüŽŲķ•]“á]H#&I9Ë5×Íú.žaŅęĒ�ã…íßwH?ųČŽ\s­Ā§h¯öJū1Ņ%Ԟė3%Ų˛œß!Ķ`õ•$jOv‰$Ўģk"ÉãúŨSî nęp˙ũČÍʗ]’Oô�õnXĸú1éü&‹���—ļVXķå[­üĪŪZy–= Ąˇéĩ7k0rĄ.›ŠŠļœ5šõČfˇ{YK$ÉŽ‚âIgŠĖËõ§e›•–}Påį`TâĖgtkÎLmËYŖ†Ž—Oh´š(~Č� Œ<ķ€$I*QaąĢŪuĪęĄtwe–8˙[ÅĘ+Ö9ļšØō|āīöąI>ÎgÂ&Öâb•KRŲ^ŊõČãnG%؊'ŗ¤¸PeĒ;Ę% đ|×CņUâØÁōßšY…Š›ĩof¤ëĐĻŨ)™*—AņŖ†žîĒĩ¸XvéLPāF@h ¤oeˇËĒæx›ļŋB/:{ąŠ¨Ø™&ukäų+(Ā ÉŽĸÂI˛–”¸úâžA ķ—\ĪÎM䯠s,éŌäĐĒ,O/ũŗVĻf*¯ä|†āœĪũZĸÂIōUÛcÎ\ķķáūŧũā+ФDVĀæBW�� �IDAT̤š ī|û6Q ĻîŌŨËö+ũ™qJž›bbMę?d°âúŠoũE‘Îsg(˛Cæ‹rå SĘr2•+ƒÆEËSŽž†´ģÖē/Îõ^ J8=ę°æwŋ‚ Ã}ũë7Õ?r×đûa“ĩÄuŋøģ/30D’ĘŨ ���´9­žäĒ�C­MFƒŒ2Ę70Dą #uĮđčŗg’$›l6įŽŊ0SÛÎ1mÆVRϚđĨ(edžĪÉTš|:d´ˆ‹V€¯¯| ’T¨ĩs^•šū˙õ ĶkŸDčÖu+´rĶ.í+ÜĢm…{ĩíÃW%Ÿ%LŖy÷ĮšÖl§G[včė\ė*;įĶls—wajΡĘķ•ž=˙ė;—Ų>¤ îö<+cė%nÖÚâZk~L}㍒-SÛ2Ę%Ÿßj|­ŅQ§Ûg44ōÔ,ĻĒr[ķŒ|1O¯U{áÎÜËFŸF[~úôŲlΛĶVfw5Ąąuō=ßļÔ<¯ĄļY4ôT­,´ËāĨÛ§SL¨ŋ\ũ+ÛšDŗ75ušFÁŊÆ Ŋ@g9¯§×'ļŲ$/°ŋFõųĄvÆ­×[Ģ6)Íŧ_Ų›•ąYoÉ ˙čÛ4ûšĮtG¤ī…íīÛO 1’Ų˛WûФžA6íKĪ‘]Ҋ1JÆ(ÅĮHéŲ™ĘÕ0õWĄöd+ú)ŪTSgÍīžÆĪī™ķaSYÍų¨áöûŅ„ûŊŅs���´M­žøkÄĢ)šįvZČų0Ęčë :uŖvÎlbļ ŊüŧsÔDė“ë´îîĐz;øČüŒÜ˙“Ģo¨īA‰÷K6kžöYö*=uŗ>Ū™¯ôWîWnņ{J}Î$_eô•Tâ¯ņkĖZ{1ũ”Ôėå]__×ĶŋéeŊ?Ļ Y30Fküđ­]vPi)™Z?X6ķví.—üGŽŅĀZ˙oŦiŸÍMđãbĢIą M¸ķü[WÎÜËļōF[ŽĶŲ’ëiÖčJcėrI ûckÚ!(xoĄVÚĨĀÛôö'/ÔšN’d-Y-5Køâz¸ˇÛíĢ­)sęÜØØĩ¯9¯ÆĶ×âbú;FķbĮhžĘT”ŊWģwîŌļÔíĘĘųHŗ“Ęö?ĢtgЅė ūq’ĨPfK™î :(sNš:@ũœŸ÷ ‘,Îu_úûįČ\()tp­ˇÃ՜ߺM7kx>ÎÎ(Ŗjî÷Fv)/ámG���¸Ŧ\:kžœ7Ŗ‚CÖ­Eįņ?ę…{•[.É0@ãĮÕ^$YĒ  Ķ4Œ‘ę?|ĸæŊ‘ĸŒ5Ŗ*Š8e…kÎ@EúKRŲéé$§šËģ0žÎuŠ UtŽ›Qīąc#Š<cģv—•i÷ĻL•Ë_‰ŖLud=ŨžŧFÛWäœC!CP`ÃđČíMT"Ģõ|W­=_gîåĸĸoš—KTTälGP¨s “€�_įJ"֒ëH’Ŧ•{^SŽš‹MšįP´Đá’T×Á‹$ų¸Ö)SQ‰û3WwSŽ$ФĐũyĩ•ČZ&™ĸÕ\ũõUPĖ`Ũ9ķ­ûKĒæ›|$û^ŊõaŪīÛO˛k_VŽlí)–üMŅĒyCvX\´|\ëžØ˛3•k—Bãû՚nU3m¨\Eō,NUVŦĸšķҤ)xÆĶĶĘŦV÷ãølE-û;���đ´6žHŊ㜋2–[v4|ˆ$ÉĻ\ķ.í)(ĢŗÍų´F7‹Gڔģjĩ˛ęo.+ÔîÔõz7ŨũÜ&ߨÁęë#É^æz(ĢYØÕŽ=Š™r—jOēEÖĻÄFÍ]^Õ+ęôšřÚÖČķ 5{—Ō˛‹iã Ēņ&ƒTžŠŠ›ĩŅ\.ÕøØzīŠt-„Z˛WénÛW¨ô ×ÂÅqQŽāÆ(_ƒķOÖb7I…5ŗ‘˛šWī¸hųH˛[všŋ—­™rŪ~!ękrÆFÆPך$…{ĩĪÍ /JŨ¤\ĩølÎtĀčnëv­LmŽT(T1>’ėĘ5ᏠŽr”žquīÕ7¯"ŗe[œ ;ûD(&Tē°ūÚdÍŪĨßÛäöÚI8$D’Tf-š€ũ]"ĢŋŋTžŗW{,{U ƒúÆEŸi¯ë;“›ąW{2rT.õR{ačéEĒ÷ĨîuûŊ.ËÚå<ūũæf7ÂbCä\Ŗw¯›{ÔĻ=ŠûåéČ���hIm:|1ÆOНĄ’J6kîĶģęũKŠMEŠOęūÉ34~ÂBíŽy> ŒV˜ëÍÛ˛ë†2)OęĄTÅJR‰Šj‚ Û^ŊõĮgĩ`Γz3ģáã‡ÕŧC{Ę%ų‡Ēˇk8EīISœ¯IÎX¨Y)…õ ­Úũô ŨũāŨ5gģû]¯§šË;›š™öšWÛÖđŦFu“tPüņEíŠW‘-oĩfũa†=IĘV3 Pâ¤ōQšv?ŋDfģ9vLÃˇ¤ Ö=Ãũí{zyŊW"ÛTđŪŗú P’˙`Ũ3üˏ—°įCkņÎõĩ^,Š,Oī>úgå^ĀZ5įË;Qw„J*ŲŽ—[ę=äkë3V–]2˜&랚ÜČĄ(ÉžŠˇ–Ö=Ɩˇ\ŗ–ŧevš¯‚B#y Rë}/­Ŋü‡%* pŽÚ)ndÔN“Õø�ųH*I]ĸwķj_ô2í[üĸ>ļ^čIČ×ĘÖĢÎËÍl…ú`é•Hō‰ĻūFéBû[ōŦfŋđ”jđģËYOZjž$ƒÂœ Īyī/I2F+!Æ Z´2ĩÖz/5|Ŗ)Ųŗ7keöˇ’Oŋo ë;vĸb ’=c‰æ§×ģZÖ Íå3•Ë ČIÕŋŅsYW@Ü0×ÛŲÖëåuuŸYÍ/j~FÍûĄ���€ËC+ŦųԜ"õđŸŸQî„geŪ4CC2ĸÔßĒ�c™Šrö+̰D2„hüÂ9Xķŧá;@Œę&ķ‡ßjã}ŖT4d€Â|ËU”ŊW{ }tį;ī)>åVŨŗŠ\ģ_˜ĒY–a?ã6͛ąIã^Ų¯EŖãĩ6Ú¤ŪūōU™ŦÅųړķ­ėęĻ[ŸzÔõ0&)hŒŊdҏ?îPúœQŠ{Τū‘ū2ÚJ”ką8߆ø[-zvdĶÖNiîōÎÂ72ZĄÚĢÂü?k\RĻzûú(ūŠ×uo¤QũŸzEŗōĻjQ΍´K1ņŅ ķ‘ĘŠķĩĮrPåōQĖÔW4ûüßq|ö6ŏQĸ˙gÚXb—Ĩۇģ™2&_ |jąîɛǕ9¯jÔ Ĩ�Ŗëúæ—ČnˆĐ=¯>ŖÄZ‚†OTÂŌ™J/ŪŦ{å(66Džļdī—5rŽæ _­Ų~ëŲÅ_ŒŅšũęåŪĩPYĢîV\Æ� 4u“¯­Dš–LeÛeŧM¯-Ŧ}}#õĀ“CĩõÁ*\uˇ†d Đ˜nRÉ~í6+hƍØô”Öžc1jOč;iŠb×=ĢŦœ…•dŅĀŠä ö˜÷ĘûŧÖ=YŽYC*ģp…z¤X‡OÔė„ ĢË7á1Í6ejžeŋ%'h[L”‚|í˛æå(ÛĸY3čO¯|&ģ_ Č-Ķh°ž¨áƒ6i`\„TĸKϞŠí’Ī�͛9øôčš ęīŒgtkÆ mÛ>CC,!ęĄ ŖlåÅĘÍÎQa‰]>ŅjŪ(į\žūįšŋ“Qũ‡˜dؙ)ŗERDÍz/5Õ7Ļ›”“)s‰dâæUįaĩč9‹ÆÍųLŧUûLÔ7ÔĮŲŋŦŊ*.—üãžĐk ÖĪ:‹€Û4oÆzz%_ægF)nI}ƒ ˛Yķĩ'§\ũŸ¨€WV(‹•_���p™hĶ#_$É6FīũĪFŊ<u¨zû~Ģ};7kãĻLå–ų(väŖzë“-ˆ¯=ĀWũŸZĨˇĻūV‘žåÚˇũ#}œš#kĀP-ذNķb4pÆ3ē=ÂG*ÎQÚÎYeTīûW)uō‹ą8GģˇoÖÆí™ÚWlTßa“õō')zmxŨØ#hø+JũdŠf ‹–oIŽŌ6mÖÆ9˛úFëÖŠ‹ĩí“×5â<ŪuÜÜå5*r˛=>@Ą>v•äįh_q­U1Ņšļa›Ö>;Z ‘R‘y—6nÚĄŨy6ōÖÜ)Z73ēųßTbŒĶøáÎé͘ÆûékŌŧ ŠZųämęī_Ļ=Šií‡ģ´¯Ü_ũGÎŅĘ˙Y§yąõφ Ķkī?¯ņq!ōWą˛vfjOĄ]aã–*坉ęízeÍÃĢ×#'jŨ˙ŧ§ųc(Ė–Ŗ´?ŌÚÔŊ*2FëÖĮ—*õ“”X¯ß ¯(eÅŖē5ē›l…™Ú˜˛C{Šē鎗ÖiŨũŽp ņˇ?yLĐŊũū3ēŨ"[ágÚøáfí.”úĪx[)īŒTXØmš÷x?ʕˇķ3í)ž˜s¨;ßY§ˇĻūV16X>SzVžlĄˇéå Ë4-ÆßyÎģŠh=üū:-îĢĸŒúxĶgÚWæ¯Č!“õÖ'KuGíkq!ũ Ŧ×>qūîęëoSŽy‡6nÚŦ4ķAŲüM˙äÛJ}ʙ0ä|÷wņ5 8=JĖ?æĖz/5jĻŧIõŌĪÍtL)lÔëÚųŅķēgHˆT˜Š­~¤­Ye ĒnÔÎwÆ(ėŧn2Ŗzß˙žRNP|„¯l…™J7īU-B÷ū9Eoß­Ķëg“ŋ���ā2ĐÎáp8jo8tčnŧņÆVjИ2Ĩ=’ ļKˇū9]¯%¸{DD]ÍũÕŨZ[ĸv¤jv×ã¸Ō•ĨNUĖŖ™’éyeŋ?Ōm����î4–Š´ų‘/¸BŦןļ;ÚŊ'žĮa'›Š˛3´5eSƒõw$I9ÚW"Éĸ˜æ����¸ m|Í\Ŧšû蟕'ƒbg4œVqå*ĶžĨ34;Ã.˙!RĘĢ#Tsnl…úā…ʓä3dä™ĩˆ�����-Žđ—(›öŧũ¤VZŠ•›Ŋßš¨įįĩ¨ÎbĸWē�ŨņäÚ8úYeí|JC­Q˙˜ųĒDŲ9ÎE˜ũĢO fę ����´"Â\ĸl˛æėRz†]˙ŏ{L f ŗgę Ŗ÷>éĻ–ŽŅFKŽöėĖ—]ųF(aŌH=0uŒú^ėë¯�����…w�����š î�����´Â������"|�����đ Â������"|�����đ Â������"|�����đ Â������"|�����đ Â������"|�����đ Â������"|�����đ Â������"|�����đ Â������"|�����đ ö-UQ…ÍŽÃÅGdŗWĒēúTKU ����� I2xĩWoŖ‚n¸N¯‹DZ&|аŲu ākųøt’¯ĪÕúŲUWր›â#VõŊŠgk7����€+šŊ˛JĮ~8Žü¯+âįÁ-Ā´H r¸øˆ||:éęŽޏā�����\ ^íuÃu]ĐÅOEßmąz[$ Ѝ°Ģcī–¨ �����āŦē\ĶY'+l-V_‹„/§]ÕŽ]KT�����pV¯ö˛WVĩX}Ė�����đ Â������"|�����đ Â������"|�����đ Â������"|�����đ K8|ŠTÖëĶu÷ŗ˙Ŗī<]Õw›ôÄäŲZUāéŠÚģžĪZĄéwŽÕžøŋ˛ˇvs�����hƒÚˇv. ˙CÉ÷Šŗk7äbˇęķwkIFĨŽ1´vc�����hģ.á‘/-¨cwŨüë~ ëÜÚ š„o×nÔôWŸVrHk7����€ļĢí|Š<Ļŋoü@›-˙Ôwe’—oWEüz¤Æß~“j˛œ(ØĨīĒ|wB^ūŊ4hė­ęŧe‰Rģ?¤?Mčåœv4īsE<ņ˛&…Uęī¯?Ē7u§æõũ§VmųRß?!ų÷Ōđ “5<ŧc uė'í~î-Ņ4}đô¯tĩkĢ=ãE%/‘Ļŋ˙„:I˙ūâ-[ŗS;hÕOę¨ëBĸ5lÂ$îãį:ĸT˙ˇq…ŪۖĢoJ+eÖ-Ŗ&kęīot•™ĢĨ÷ŧ¨ĸQë–}īhåŨ4}åJ¸î?õÜK~ēÖđ“Ō[¨Į�����\ŽÚxørB˙XŗD¯íëĸá÷˙Quģ´ęŨĨzĨrļž*¯ŲZõßTz›ŸÚOOüSŠëW)̤JęîŽL/yyI•ļiƒ˙xM[0Iū:ĻˎhÅģÛô‹—FĢGKwŗ1?ũ¯^ņc{H ŋQWÛK•ŋm…–<÷ĻŽyû %\cWū;ĪééOŊ4tÆšî§žøXKŪzN/^ŌüßHō’ÁĢRßúąū6hĸ^šĐMA$ÉO×ļr÷�����¸´íiGĮs”f9ϰ¤IJîĶ]ūģ¨GßҚô›ëõŨ˙ûL˙¨”N|ņšū~2H‰c¯ˆē膰[4ųŽ›äUvޞ+{)ņö›äī%ÉĢ‹bMŊäuŦH_o‰Ž5ŅŅC*:á§_ ę§°ët}PO |ā -™?Nŋė$é§˙ĶŸ~ĢĐ1iF\O]]€"~{ŸĻ˙ļ“ū–’ŽÚë íô+MŋŊŸ"BO˛�����¯m‡/G õuU……vŠŗų†đîō:ų J¤’oލ˛Cw…ŨPk‡1Šđ=GŲ7é¯3õęč%/UęDeŗĩūâÆč—Vmå9-ŨúŋúŋƒĨ˛ËOaá7ęZƒ¤ĸ\Vč}kdPDß`yĨ?œŲz]HO]ßŌí����ā Đļ§8Šj¯Îõ–aņōꠎĒRå ŠōÄIŠcyÕŲŖƒ:žsé¯zĮ\‚ Ŋu˙KķÕ=åĨo]ĄīžW@o ™pŸî ”á§Ÿd—UŸĖĢOÜM?ū$éWQ.ųŪ����Đ&ĩíđĨŖ3d9~ĸîæĘ'uBÔšŖäÕąƒTYŠēVNęDŊcÚ ģTˇ/×ôÔ°ûfjØ}ŌEšú|Û*-[˛P†ë–ęūNdP€n™3GɁõ 2tÍu-×n�����ŽTm{ÚQ÷PõhL…ĮęlūîĀ7ĒėĐ]=ü%˙Ž]äUV¤¯Kjíđõ—Ę?ך/­ÎK/ŠŌū“ėĩļũķÛĶļũ—>˙{ņéΝꭄ{Įé—^Ĩ*,ūI ę­p¯R}˙S'u <ķsA†NŦí����@ ¸ôGžœø^_|Š’:ŊÔš{/õč­áŋîĸ…[V)õ†ņŠíîĨ…ģ´â¯ĮÔ#a˛"ŧ$¯>ũŅ~•R?4+ll´:ĪÕæõ_JįZķĨÕÔĢ›´ūs™‹ęļ ƒūũÅZ}°ĪŽĶķĄŠvęõ…š ŸpŸîŒŊQWĢTßdėĐ? !!¤N˙ĄÛ~ë§yk^ĶĘN5Ŧ—ŸėGs´ų­2_3Yo?=°Ņ7ŲRáŅ˛ËŽÂ¤JÃ!í˙ÂOy隐žęŪŠĨÎ�����mÛĨžû\+ūûķ›1u‰7uTÄØĮô×Úđîm(“:véŽ_$=¤ņÃē;3ŠÎˇhōôoôæšMZ8į#uė~“ĮŽVį5K”u‰wŋûˆišúĪ×ôÁc÷iĨ—Ÿēß<TĶ'Ä(˙Ģ$Épķ}zîÕZ–ōĻ{÷¸*Ŋ:ęēĀŪōøē3D’ ŠzāiÍë´JīŊõœ6—ž:vSDėDÍŋ¯ņāE’žųô=–b­ĩe‹žžˇE’ôËĮŪŅü8Ō�����šĸÃápÔŪpčĐ!ŨxãÍZÉž/˙ĨĀŽÍZæų¨<qB•^ÕąfÄHå—ZņØR}—ôŧæ%t9ëąÍĄøˆU}oęéņz�����@Ķėûō_ÍūŦŪXĻriũh'öęÍ?žŖ¯ûŒ×䤛ä¯ãĘßąIY•Ŋ4ž¯įƒ�����peģü׎ũ4ų‘2­Z˙Š^{v­N¨ƒü{ܤÛMÖ ˙Ön�����¸Ü]ūዤŽaƒ5íÉÁ­Ũ �����pjÛ¯š�����¸Äž������xá �����€ž������xá �����€ž������xP‹„/íÚĩĶ)‡Ŗ%Ē�����8+{e• ^í[Ŧž _:xôã'Zĸ*�����€ŗ:öÃquđ6ļX}-žvՏ'NĒü§Ē>uĒ%Ē�����¨Ã^YĨ#ÖëčąRŨp]‹ÕÛ"clŧE„õĐáâ#˛ûAÕÕW^�ŗīËĩv�����¸ĸŧÚ̃ˇQ‘?nŅiG-V“ˇŅ đĐ-U�����Ā%ˇ�����xá �����€ž������xá �����€ž������xá �����€ž������xá �����€ž������xá �����€ž������xá �����€ž������xá �����€ž������xPû–ǍÂf×áâ#˛Ų+U]}ĒĨĒ�����$ŧÚ̃ˇQA7\'ƒW‹E"-žTØė:Pđĩ||:É×įjũė*Ü´”â#VõŊŠgk7����€Vg¯ŦŌąŽ+˙ĢÊøyp‹0-’‚.>"ŸNēēc‚�����Đ* ^íuÃu]ĐÅOEßmąz[$ Ѝ°Ģcī–¨ �����āŦē\ĶY'+l-V_‹„/§]ÕŽ]KT�����pV¯ö˛WVĩX}Ė�����đ Â������"|�����đ Â������"|�����đ Â������"|�����đ ö­Ũ€ÆU*ëõGõfvŊ÷nˇī ˙7iPŌH īĶĨušÖŌöŊŠ)>Šņ¯<ĻA[ žī՟oUY˙)š“Ô]^õ>.Ë^Ģk(ęžiē+ŧC 4’¤ōŊúĶüę0ņiMíĶڍ����4Õ%ž¸tšEĶîŊE5™Cå‰#úĮÎmÚđßKT2ëŋ4)ŧ~4€‹vm?MØĢ…é;”ŲĒ]WëŗĘiˎ|éĻd%ŧ�����pN—ū´ŖŽ×ĢGx/E¸~~Ņ7^ãy@‰]Ž)3=['Zģ}—ŠëŒĐ€kŠ´cË>•ŪZŠĸôĘ:yŖFø…|[ą}�����´—ūČwŧē+Ŧ{{Ĩ;Ļã’:J:Q`ÖÚõģô÷¯ŋ׉ĒöęÜŊ—Ü>IÉ}\cf*(ëÃĩÚŧ¯PߕUÉË÷z…õŊU“ÆöĶ ^g˙ŧŖe‰f~čĢiK&ëf×@›ü5ŗĩpw%ĪVÃopn;ū˙–hæ‡]ôВIú…Žéī?ĐfË?õ]™äåÛUŋŠņˇß$IŌ?ĩęą×ôŨĐiēų‹ĩÚpāzMZōt<Ļŋ¯YĨĩ–B¯ė¨úūNã[cЉWw MŠŅßŪIזÎéE˙ū?­Ī,ՍCĮŠ˙ĩÎŨNū\)[?×ūoKuRŪēŽG„% Հn$ÔßV,ŌûŠį'˙B]EWf¯ÖėĩԘg'LJJí_õĸVj„ĻėÕû™%ę9éIŨ^ŋAõūĶkôũ qúõŅŋhĮ—GTVå­kÃoŅØŅqęY3§ü ŌSŌ•Ypä˙ˇw÷ŅU•žĮŋ´į”“L $’@Āđ’$ Ö0z‡ tņ~jmŅĄ8mĮÎHg:S§×{ĩŊĩÅž cß�G pK´t$­´@Ģ%J(/‰ā` $åįÉũã  ('ûũŦÅZf?{īįŲĪ9YËũËķ‘hœ”´úË´Q=ëPžĄ˜å%eTÖD‰ŌčÖ̐qS>Ã5.P…‡ŋģü/˙#͝n|–mĪō˙QFūäāža鉃oŋÂÃķ~OÁŦŋgęÕęŖs=ŒĘWWŗtÍ6*Ŗ:tíĮØąšIú°%I’$IÉte†/Ļúpœ`‡Ž‰éHĮKY<īYĘúŪĖŨ˙THVđ8{õ,ũh>YŪ΍,Øģę1Ûܙ[g}ƒĄƒ—ōü“OđH°3ߝŪëüå=˜ÜØË”í…ĄŊöąutėpŒ­{ŽōŲ܎@ŒŨÛöAßôgë“ßã›;ķŲY÷sw÷TŽīų/?ųC‰ũ#ßšŪ‹ ‚Á8Õŋúlũ˛™;5‡ÜT8¸æ1û ˙›ûųlßTŽnû<ŋęÄčŪęŊœÚw4S”ąhÕ:>5gGV¯Ŗ2ŗˆûО'ŧŊĮŦåøāņÜuK:SÍëkW°tūķæ|ņŊ€æü‚ßû k×sÛŦ|ē\Õü™@œĘukŲ5ås|cZ&Ômg鏞ãņe<0cŠÔąqéSŦĒČįgNĻ Ží}…§—=ĮĶ™÷ōˇ…)ÄvķøŠ nųˇõJ# ŗ~Õ .NaîŊ#čtŪō| 2׹sO\čƒŠ?V’’–Æū=•Ćõ'ßģ“CĄ|ĻvmI5˙üąˇŠy|YF~ŽšEYPŗ‹5ÅëØôųā­$I’$Š]qáKėx˜˛’gųųž ũōāÄh†Ôsķ?ũ3tėNV*@g˛&|š~%‹ųÞãŒĘ rpīač>†á}s×dũîú§žT“ÄÎ_ž¤_ĮėŪszw†Ŗåė>šĮđ‘ÖmĢ$öŠŽŲGŲžŊĮ~œÔŖ[øÅīĶ{ęßsķÉE‡ÜŒ‘ođ¯%/ŗuj¯÷FĐT§^ĮÜq…ŖaöņķßVōUnũdbĄÛŦOŨʤođđo[˜V›�� �IDATĩ›ĨsíøŅŦ˙n1K‡9öĮ7ĖE^cÛË7n "4ģ§ Ąg0qūđ)Ŗyķ=Oɖ*ŠFĨĩŧĒē †OÆ'.´ŒL×ë™P˜™X8Ŋ?c‡įąŠx3Û#ƒ¸6%Ä5Sîĸ'té”hdįNŖžž”â•PXĀąÕԅō¸apwē2™8#‹kjt‚ ”§ŅŋW€õģÕM*Uŧš FôŖbc9•ô§'1*v„ ^=Œü`Kú(ģŲįßšąŒ#™…Ü>Ž€.�ŽgÚđüķŗģZŪ§’$I’¤6Ąí‡/ûVōõ/­<ũXJ†ŪöUf 99‘%•Ôã¯ōü Ξ;|ŒŖĮcÄb1bņ8Ŋc1 •ŪŸėEęÂe<ŧ ˍOfPß^deõĸGãÎ_dhßTÖíØÃņŅ îyƒŊš˜10ĖĻ'ß`/č]]NŲŅnčÛÂ{ØīĖ ŊNߍ)ˇow‚ÅûØ] CgduīŲŧ�ą0Ģ!÷¯ršė0¤Gß<øíģ>ĩ–“‹īW2øsLč}˛euė?P ]{Ō­éšĮ)9ôˆí{̉qáKvOōZ°~o‡Ž9§­5ĶųĒ,Râ•TÖĀĩ)ARƒõlZSĖĶ{Ģ9‰Į‰EĖD˙uîŨŸnÅkyzA€QEƒčß+ŸŧNŲôlŦûBåų}ķaÕNö3ˆ‚ē *ęr¸apŦ{…Š#ĐŗS˜í{ãtG°E}”ŨĖķ×q¨Ēž@v÷DđŌ(ĩGOē`ø"I’$IWšļžäŒāîŋŊņŊ€"ė@Į܎ī­ĀÁ˙â‘˙삪CnáK;€ÜŽˆmá§sŸ%ÖxJÖ§žĘŋ¤ū?˙åV.x‰Åņr obÆÆĐ¯ã…Ęƒôؓā’?˛—ëa[9ŠŊn"ˇWgz‰ŨՐģį vü8ũrp„ãčxZ#!L!•8ą&ĢS›ŧ•ĮâC0xúĮL ä2…/@—ƒčR\M§Áũ›ô{”HBglE"%âąč{}ß"Á-Ų;)%=tú@€�qâ1 ļæ/d} iSÆRpUˆ õlZükNžßuw%ƒĩë~ĪÆUOą* ŊĮ@ÆMi\ƒååŠŊ ȏnā͡!˙íėĪ,  SņŽÕŧž7Â(*ЍËbh¯t Ēå}tÚķG‰Ä€ôĀéßķ` E}$I’$Ij[Ú~øėLnîœoŠŅƒ›7°;XČŨs#ũNžå北˙ šCÆđĨ!c v”ŊÛ6đü’•<ōdGž{ī0:^¨ŧī�z˙5ģîƒ=1zOčÁc Ę ŗuĪQrˇU’Ú÷$Fʤ&B–ŖglÅ;á8)g…2§š €ãąøY×]TŅ*NN… į eN‰ÁûŽ’"uŅĶÄŖD6ķZU7|y EWŸ<Ąžã ķÔ%Š]1ņ탘HŒÃo•QRŧšĨ?Ķéëˇđ‰āĘĶķųDf1oî­Ŗbo%)ŊGЙtō{¤ąfG˜cė¤2ŗĶ:}>J<O<į8œ `"Q"`�#I’$IW˜ļŋÕt ĎE ˜BĮ&o˛ÕŋÛĀnhL`Žŗw[)ģ6;ŌcČnũĢ<bá=T_°čøqå†)Ûü[vgP¯ ЙŪŊ:°{ÛĢlŨŖ÷Āž‰—éîŊč8œX#ωƒ;öKéN,šĖ!ˇ#Ünļ4.äÛæ¤Ķ­k(§ĸi2 ŗŗ ōzå4.* ņxô´°åĐŪę÷]ë‘ŊašöęĄÕÄYäeŅ8qŌčÜ$ˆØĘëUŧ—ö;°×DKƒtžzSĮŌŠžšũ5.‡lúôNc˙ŽRŪ|+N~¯�ēô΃ŊÛymG˜”Ūäĩ¸š“F—ėTíãP“ŖĮö–Ÿöŗ$I’$éĘđĄ_˛úö$õ؛üâ7û8zô0ģŗ˜ĮvtĻ_¨ŪSÉŅX„˛U?å‘/cĶî0ÕGsp÷~ūÛ0ŠŨË…Ęrčס#ģų2{s?AīÆŦsv'ļíe6Uwgh߯1 Ѓųė§:ŗ{Õb~ž-qŋŊ›—ņĶ’ÃôųéSŖsÎŌáŸėÂņÍËxöw{8XĻŦd1+÷Ī9Šärę9|}ĸÛxaųfʏÔqėírÖ/[ËöP?FfAēôȂŊ[yííDúpl÷/(ŪßcŽRŽüž֕s¨ŽŽÃģ7đÂ+aŌ Ą Đĩ;Ũa6ŽßÎĄē:íŪĀÂåĩäõ ?˛ōHŒcĨkYøĶįYģí ‡Ôqøír֗”q$-üL.X7 vo`SUW'>™`žäÕlcíŽ8Īģˆ>jN‚ëú‘^SĘōUÛŠ<RCåŽ ,ŲXí¨I’$IēĩũiG-:äfžôWķyöŠ˙Í=t ĮĀ›˜ņ77Bq˜GŠã_ƒwķč—ī"öÔ žŸ÷kĒ#q‚ēĐ{ā-Ü7ŊTā3(č1đãđË ‡ôzoT°û�z•˛Üī2JŋéĪŨÁgxūÉyū¤vîΠ wsë¸îį RzL¸‹/]ĖĘ'ŋÃ::ĐcČM˘ä ŸįĒˤĶõÜ9+ÎŌÕëxėģ+ˆÆUŊúqûėą\›ž8ĨËđÉLŲû<ÅķbU .}‡1ml*ž­}U^7škëÖņ“˙[ɑxˆĢúŽåÎ)kҤ2}Ę>Žyž7BzL˜2™kꊩ\ŧžˇûžōfÍōĮYU‡@ĸ͟Ÿ=–‚ 0úå@°GōŖÛ؞YHAãs’’GAv=ÛĢúĐŋw“O¸}Ԝ`īąüŨä8O¯}žoo ĐŠk?Ǝ VÓ˜$I’$Im[솆††Ļ***ČĪĪŋ¤•l~c'Ũr˛/é=Õ2ûÃU Ppš›q ”ŗä[ ŠņUæŽōģ$I’$Iú`6ŋąķ’ŋ/Ÿ+SųPL;’$I’$IjĢ _$I’$I’’čCąæ‹ūôdúŋ~ër7B’$I’¤‹æČI’$I’¤$2|‘$I’$IJ"ÃI’$I’¤$2|‘$I’$IJ"ÃI’$I’¤$2|‘$I’$IJĸV _ÚĩkĮ‰††Ö¨J’$I’$éŧū;įcÁ@ĢÕ×*áKJčcÔ×oĒ$I’$I’ÎëpÍQRBí[­žV _Žî–Cũņu:Îģ'N´F•’$I’$I§ųīXœpÕŪ>\K^îU­Vǒą ĩ˙ũz÷ā­ũaĒ×đîģ0­iķ;/w$I’$Iēė> jO˙>Wˇę´ŖVĢ)ÔūcôíÕŖĩĒ“$I’$IjÜíH’$I’$)‰ _$I’$I’’ČđE’$I’$)‰ _$I’$I’’ČđE’$I’$)‰ _$I’$I’’ČđE’$I’$)‰ _$I’$I’’ČđE’$I’$)‰ _$I’$I’’ČđE’$I’$)‰ _$I’$I’’ČđE’$I’$)‰ _$I’$I’’ČđE’$I’$)‰­YŲėŸÁâ?@ôŨÖŦUŌú(Ė ķ'^î–H’$IŌ•§Õ—vi­Ú$]JŅwaÁīĄd”Ũ{š[#I’$IW–V™v4ãƒéÃ`ĮáÄīŗ$I’$ŠåZ%|ųĪ­­Q‹¤Öāīŗ$I’$]œ¤‡/ņwáx<ŲĩHj-ū>K’$IŌÅIzøøh˛k$I’$IjģÜjZ’$I’$)‰ _$I’$I’’¨Õļš–$I’$%×[•XđäSÔ˙é8 —ģ9R›ĶŽ];Ō˙"•;ī¸üîŨZ­^GžH’$IŌ‡@åÁ0ü`uõ2x‘ÎĄĄĄcuõ|ī‡˙NåpĢÕkø"I’$I ž|š ]¤ j׎††,XøtĢUiø"I’$IGÖ]î&HWŽví8zŦõ~g _$I’$éCĀQ/ŌÅiÍéy†/’$I’$IIdø"I’$I’”D†/’$I’$IIdø"I’$I’”DËŨ�ĩq¸éz˜} eAN DcP†’×aŪfØ=㚠Øđ5(*‡œ'āĐÅíŗ`åL€Ų `Auk=Œ$I’$I­ĪđEįÔ>V΀1é­ƒ’P…P {ÂėĪÂėaî3đíĘŪ4–4/sž0x‘$I’$}øž¨yi!I ŧøs¸}ÃŲ#Xn+? Ī€đaqí…īųÜL˜€‡ÃŖád5^’$I’¤ļÃ5_ÔŦ›>“R ô×0Š™ā`ĶķDS`Ūg ãųn‚ĮfÂô4˜÷,|ŊĨ#e$I’$%U§ĸ;ųáwžÅ÷f’ŌėYüõœoņÃīüŸīÕʍSëKģŽģŋķ/Ü9ār7äÃÅđEg Āíũ<đkxį<§nŲ�+#ŅƄÎqRš ŗ3`ūĀŊåIhŗ$I’¤÷/ƒ^CšÖLYîОcÄZŊQē(ŲÚ˙kcÉŋÜíPŗ _tļ,(LÂPræbēgŠÃĘ}@Ff5S€ûgĀÜ,Xô,Üeđ"I’$ĩ9ąēJ*ęō¸Ą0ãŦ˛üëŌé@¸ŲŅđj;Rēö¤‹ ‹´Y~4:[:ä�ĩ5p´§‡×zÉI?Ŗ �sgœî@6V]ÚfJ’$IēTĒy}GŽČUë×ķö{Įķ¸a@[ļA×Â&į¸ęÚąLՏüĢŌ Æ"Úģ5ˋųCU€ôĸ;øˇŅÕ,\\ÅĩS†Ņ?;`´š×׎āéaâ-ŧd0hōdĻæŅ!ĨrËZ–žŅ“ŋģ5ƒ5-ä7õ÷0šŠcRpU:DǍ(]Į’ÕÛx;Î9Ĩô¸Ž)ãGpM×L‚ņŗ¯IéQĔņøĻk&ŠD8´ˇŒůųÍÁh Ÿ1Ā /~;XÍ#oôdÚč>tIAMk—?Ī/÷œükwKÚāĒÁc™>ēųW…ˆ ķzÉj–o “>ōĢü˸l €ûž3Œōåßã{kIÉ-d„ í‘M*uÚSĘ ËײŊæÔ=ģ];žiã’Šsė@ÅÅĪ˙UÉË÷öãõīŸåīšÃ„¯ũCwü;ŦŽBäeĘČ~äuJ!ĢŖrO)k–¯eëÉēCyŒœ2–‘}sčœĮ”ąvU1%{ęåŲÃųƜ!ŧūė+t;žk"kyđĮ‰õÎÔą×Ķŋņ39|`닋ųåÉëÚ(Ýíä/÷E~;Î$ĶæD`ū¯aä0o”-€_]h4$I’¤V§rKĮ† Ą(w=Ģ_ĒŊŽįšôJ֔Öpíđ&§÷ÍßMȑuĪķČâj",†Ž›Ė3ĸúîZöņXBũ˜8˛†%?ũ>‹ę\5üæNĪđSR͞û\5ōsÜq]€×–=Nņ^č2x4ˇĪ"%xjĮ”“šgF¯[Á#O„‰eöaâ-“š'%ÎƒĪ–iî‘3¯ãÎYcIyu5/¯$’Ū Ķnáž`”—í"’]Ä]ŗF“ēe5-ÛÅa˛¸fôdĻĪž™øŧ§ØTĶ’gŒC°÷(&ÔŦfŅwWp„ n¸õ.nģeÛ.f ۟>øfîģ%‡íĢWđČîzRzâļ)wp[ä‡<žūqÍŧ‹{úîâŅ­Ĩ"…ĖBnŸ=…ŧŊŋ`Ņŧm"‹ĄS&sįĖ�?˜WLE=Ærį´~[÷oŦ†Ė>Œ;ŠnĀŽđm ô˝SōŲšô)žŪSO<”Ã𠓹cF„‡į­įm˛øëŲw0.PÆ ‹W°Ŋ&@ˇáãšmæĖ{Œ_6ūá> qÍčAŧšv!¨åXh wĖE§ŌįųÁŗa"4ō‡įŠ{č)6ĩáüÅiG:[„ŒLčŌ‚ĶsG&†ëÎ(¨é?†ģ~“~ŅlXy ôŊÄ͕$I’ôÁÅ÷næõ#Ų Ŋ.¯ņH€‚ëú˛g3¯Ÿų˙ú^á'ßœĮ‹wąŋĒ–#wņËWvqüĒ| 2O Æyŗd=ģęâŧ]ē™JrČīhá}r(ē.—ØÅ,ŨæHM˜í%Īŗļ*ā{ĩdpÃČA¤ė.æņâ2ö×ÔōöžWyzõ.FpC“ö4•?|u¯˛tE)ģVŗ˙ëYēę÷TF:_4ŒžŅm,Y^JEU=uUüfųZ^0rđŠ5.øŒ� JŠwq$ÄkymKąN9äĨĩ´ũ\;ŧ?ŧQĖĶwąŋ*ĖŽ+XēŽ‚Xzx”H4Qŧ>J<ŨŠFđ ļ˛ä™õl¯ĒåHÕ.~ųĖ:*2¯cô€Ä‚ųEũč|¤”åÅģxģąŪĨë+›ôíûĶĄk"•lÚRÉÛ5ĩ9XÆĒŏķƒĨÛ8>>‚Ņ]ëŲ´lŋŲS͑š0[W¯`mU.ŖFå7;Ö˛|K%ûĢę‰gæŌ%Ĩž¯%úéHU%Xņ,X˛müüŽ|ŅŲjacôˁ1i°ø|éa�&5N+*9sëčZ(i ŖwüĻw‡‡ÂĘ P´ĒeSš$I’$ĩ’x˜M[ĒuŨúW˛‹>Ü0 ÄÎUeÔ1đŒsŖpÕ0ĻMčC^f)Á�@€TĒ 4}ˌUSq éuq"@J …÷ dŅ%­sjöP”oTžúĢn ‡‚Žphm%M3ĸČ[;ŲKA×�%5gÎ=JŖ[×LbUûN[ËæČ–Õ<ž%QūŠŽ™p`û›^ S^ũ{d ēeĪđöA5šO<'F€”` Û_×xΖĶûa{ņsl§9iäõȆ¯°Ģi(Q_Á›5AFõʁ-ÕtÉN?Ģ"{Ë9DŸfīÚRGvo§rėg¸mVœuˇ˛}OûkĒŠhQuU×Rc•ŧy iįVSņVŽÎŖSßbTî­>uJÕNŪ<2‚áˇŪApũīy}w;ÖŗoōŌČđEg‹Ãüípû'áOÒUįŪņ¨ī°Ä–ÔáßÁ‹į™K đŌ*˜› V„Qŋŋä-—$I’ôėu3‡F ã†^ÅėOB˙@K߈žõæ˜~íį¸oZkV°pK˜c1āęą|cƙģpœ˙%á‚÷ $ŠHėôûDęĸÄhÜn5"„ŧqwķÃqg׹3=œųr ˜‰7?%‰));=c—§(‘ƒĄ&ŖC.đ"ÔxÎ9w‹jIûΉĮ[R@€” ëžķ)g•ÆŪ ”ęÎ胨šúä"\Ī~TËčQ×S4á‹LL‰qė­ÄZ>ŋ9%5=ÁAüí˙töĩu™t€Æđ%J,Ō4ĩĒ`ųūCŖFpÃđņ ŸBėH9›ŠWķ–ę}—‹á‹šĩéeXŌĻ2ąãҤ—9-Š|=Ŧŧ Bu0ûåķoI @žũ ~ĻO„Įj᎝ÉzI’$I­ĒŒ>ÃčÂ~ė õ+ØNۂ:O\W@ęîå<]˛ëŊŅ9]Ĩ÷‰Į‰Å <ũÕ5%ŊIø‰Aåú'Yøę™!KœH]sŖ"âī…()ĐLØpzČręUčôPæƒNĪiqûĪéhé+|âųØŊ†‡—ī:+ø‰Gj‰“FŧąoO냔sõÉų8ŗi‘ƒÛXõė6V S~Œ;žé_ päĄį9\…ČvžūŅZÎÜ7rė|UÕWō›ÕĪņ›Õ’Ī5ÃĮ2åķ_ Vķ}–īŊ¨Fˇ*×|Qķęáö'`e Ũ÷Ãē[āąqđØØp”N„œ˜ũüŦĨŖŧęáög 4ŗoYÍmO-I’$é2Šæ¯$Øw4cûÆŲõÚŽf^¤ VmR–Æ5׿_dŅ‚ûÄk9T]zä49ĸ`@ΊÛÄÃTˆŅ)3đĒjŪ~ī_=ņx=uÍŽROåčړü&ĄAúāÉüũ—GĶ'PĪūfĘ åP •{—n”EKÚSq�ēôÍ#åŊ ô'?š_“cMžoodfŦizĪZ"ņ(Įęã@=‡Ē"Ũũ´ĩ>Ķ{ô<˙ڟņ:â'GĘĸK“ŨoĶsû1(7tōŽėŨÆōâR§gŅ-Žs,%ƒtšļ­š#‘DØtŽž dæ1čãYī}"UlZŊ–íą4ō˛Cį¸Ēm0|Ņ9ŊS “…1?‡ĢĄ_˜ũ—pû Ȩ‡ų?‡~Â‚ę ßë´ûV¤åNų3āĻļũ;"I’$ũY9ōÆf*Ķ3éßÅÆÍ%ĩT¨#Ø÷z†÷H#=3‡Ąãofxŧ‚CdĐŗkÆé띜SKîæoÔĐađh& Č"=3‹ū#ofdfô´ûl*)#>`,w ĪįĒ´4:eįķŠ[īä_™L˙sŧoė_ŋōĐ@ĻŪr}rsČ0œÛ& ĄsM9ûãPąūv†2uJ!ų™i¤gįķŠ[Fķ‰čvJJk›ŋéûŌ’ö×ōÚúíÄúŽåöá}薛C˙ĸÉL-Ę"˛ˇ’‹D!=>Ŋ˛¸*-Āū¯°3ĨÛĻ’Ÿčßū#?ĮܯŨɄ ÎÎWË8ÖŠ)ãûŅ-3ƒn/bzQÖųGŊÔ„ŠŒ¤Ķ˙ē>¤˛:ázōš$& GsĮ—næ¯äĐ)ŗņyFöŖs]%5˙ãÖČbô-cÚ#ƒô´ ē ÍßũÃWš{øyūBŸ}=Ķg|;†÷Ą[f2sč?|ÔRq m¯¸ë´#_^ڐø×bĩ0ėį?å­­ģõĩL’$IR2ÔlcĶîŅtŠÛĖös AØUü<k;MfėWū‘ ‘*vn,fá˛ZŽIŋƒŠŸŋ‹Ûžų>Oˇ Ē–ÜįņâįX’>™qˇŪͨx ;Kײtí îû|ˆ“K DŪXÁŖËF3uôÍĖN0VĮĄ=ÛxzūÚÄ´ŠfŸs#-€iãGpįW2 Æk¨(]ÎŖĢGûÔŧĘã L?Šģž6…Tę8´§Œ…ķ‹ųÃ%^ßĩ%í¯Û˛‚¤ŒeęțšoB ÔíãõÕOąôĩDcŪŪ˛×‹Æ3qö]\ģîqž]\Ęãķa„Ü5g ŠD8VUÁĻg˛foĸãâ{ŠųÉō�ˇž™šEq(Ŗxõ:˜5žD\ĐĖ ž‹–ũž;ÆßĖŋũoˆÔ„y­x-kŗŋĀČÆSö¯}Š…ņŒ›r'̓KôŨĶķ‹ŲķËų ‰O˄™_Ĩs ?fûÚ§X¸ūÜŨ˙q5?Y>–ŠŖ&s߄t‚ą‡Ģ*ØôėSŦ9x‰>Œ$i×ĐĐĐĐô@EEųųų—ļ’ ŧˆKē˛4<xš[ I’¤3}õ˙õr7!I¤„ =t~'˙6皟ü¯į ˆ¤–øáwžuIīwŽLő/’$I’¤6ĢÛØģ˜[eŨ˛Õ”ˆ’’= Ŗēs¸t-; ^t…0|‘$I’$ĩYû×>ÅŋÆ3qʝŒJĢĢbįļeüduE›ŪZXjĘđE’$I’ÔvÅkŲēú)ļŽžÜ ‘Ū?w;’$I’$IJ"ÃI’$I’¤$2|‘$I’$IJ"ÃI’$I’¤$2|‘$I’$IJ"ÃI’$I’¤$2|‘$I’¤ƒv—ģŌ•Ĩ]+ūΞH’$IŌ‡@ĮôĐĐpš›!]čØĄCĢU×*áKj°5j‘Ôü}–$Ij›ž4ãsđ‡ŋH-ō‘vĖú›ÛZ¯ēÖ¨ä `Ĩ+_Cãīŗ$I’ÚœüîŨ¸ī+ŗHOKģÜM‘Ú´ô´ŋāk_M^nNĢÕŲŽĄáôqiäįį_ōŠr‚Cõ8QēR5@n8p˙ånˆ$I’$ĩMįĘTZm͗đ×aę�žHWœ@ģÄī¯Á‹$I’$]ŧ@kVöŸˇļfm’$I’$I—ŸģI’$I’$%‘á‹$I’$IRžH’$I’$%‘á‹$I’$IRžH’$I’$%‘á‹$I’$IRžH’$I’$%‘á‹$I’$IRžH’$I’$%‘á‹$I’$IRžH’$I’$%‘á‹$I’$IRžH’$I’$%‘á‹$I’$IRžH’$I’$%‘á‹$I’$IRžH’$I’$%‘á‹$I’$IRžH’$I’$%Q šƒ;vėhívH’$I’$]ŅÚˇoßėņfÃ—Ģ¯ž:Š‘$I’$Iú° ‡ÃÍwڑ$I’$IRžH’$I’$%‘á‹$I’$IRžH’$I’$%‘á‹$I’$IRžH’$I’$%‘á‹$I’$IRžH’$I’$%‘á‹$I’$IRžH’$I’$%‘á‹$I’$IRZĢĸ×~„ŠK‚ToĮ‰†ÖĒUúā>Ō˛SøĪi1Žëzâr7G’$I’t…i•‘/[}„Ÿü‡ūdđĸ+Ή8TߎŋZø1^?ä`1I’$IŌÅi•7É)Kƒ†.ē˛ĩƒ'`ęŌāån‰$I’$é Ķ*áËūcíZŖ)šÚÁÁ:ŋ˒$I’¤‹Ķ*ዃ^ôaņŽ_fI’$IŌEr I’$I’¤$2|‘$I’$IJ"ÃI’$I’¤$2|‘$I’$IJ"ÃI’$I’¤$2|‘$I’$IJ"ÃI’$I’¤$ \îœĪ´[aÉ'Î>­ƒ;aîØŊ´uNŧVĻ@ÆpôŌŪZ’$I’$ũjĶá �50}9„›ĘÉšŸ†’l(\�;.auĨāö�´4Ķér=”v‡Ü嗰JĒĮ'āŅÚËŨI’$IԟƒļžDĄ´üŒ€Ĩ^Ŧ‡Ši0'îĒŧtÕŊU‹/âüÂîētÕ+Ų2 0 Ę.w;$I’$I6ŽØ5_Ž„ 'ķÔąŽ9đØL¨ų&4|ļnĘ8ũēÁC`Ã}ų&”Ī„‰9¨ņšŠ�� ËIDAT°â›°ĸĸ|â­Đ0:6žßĨ'<7ëÔ=ËgÁũ=eŗf‹C!c(4<å$Ž÷íÅ÷@äAˆü3Ŧ›�}›Ä\o…Č­pĶgāā7ṂæŸņąûaŨ0˜8ļßĒÍ7Ĩ~ŪM#`ķũ‰új×7ļ? 6?å:ˇ}˙ÄyëŽorƒ,ØüMø~Íę˜ OöÁƒpđËđЙmNƒûoō>Gߡ¤üÖÆōáā=đũAĐūdy”?ßĪ9Ŋڇîōq‰˙îr=DyđܗíÜĪ]ßxŸžpđkP„y_K|í‘$I’$)šŽØđĨ}&ä�áúưd&LŠÁôCūaQV΀ƒö=aåT`;= ĶsĻ&^ƛ‚ųˇBŋƒ0ōŅÄ=į„ž3Ō`Ņ3‰Ÿk_‡œá0tė%Ÿ‡ŒíPô](|j  dĘŠ@'‡P.Ė̀۟€šûš¯>‡ÂaŌ>(ü6¤<%°ä3§BƒÆÁ‹Ÿ†k ßwaĖËP4– ĒĄ¤ŠzžēgQÔÖAaĪS÷čØúEae˜ŗ…`ū S“@ū<˜]sn…Y'Ó�<4æĻÜÅĐī x1%Ņ÷ƒ-,Ÿ dÂÜŐķ]¸ũ0},ęūīÁ™ũEžæ/†ĖƒÂ_ä‰0;(‡Âg ˜û#ČYī´üö’$I’$Ŋ/WdøŌ%ƒœ*XÔ\ ū$Œf/ƒ—Ēá­jøöRؘs_⋆@~ ĖY[jaĶÖÄK~Κ*ʀ~)Pō‡ÄųoUÃŌUPôŧ…wĸ/üq8MŧČß~#d”ä_$ŽŲQˇ¯P¸ũŒ‘ķWÁK•đÖy˜ „9[C‚zXTš‚Ž‡Ō—ᎭđV-lÚ ŗ7Øa0đb9ôëy*øĶJ~ ĩŨĄ°ņXQD÷ÁÆx3 ˆÂÜP´~NôÁĪ^†`R÷Ä)í{Âė\xøgđŗJØQ ÷ž�KB~Z ËŗaŪ °´ÕÂK¯Āû`Ō_B—swĪŲũ„ ŋj åvl…R (7ņķĄÆgŒFáhsĪ+I’$IŌ%Öö×|Ʌ˛Ī>\Q“^€M/ЅŨ0”4 2ęáÅZ˜ĶØ ũ˛!z0ņ2~Ō[;ĪŗūG5ŧXŗ§A衰˛J°å\kĖ`d”Ŋ ‡š>ēJƒ02ˇÉ"¯ÕPڂU}ÃÕ§īē&ę dAa–”Ÿ~ÍÆũ$ĨÁ’Ā¸DĐōĢ´ÄąE[Ą(6Õ˜î‰�į\Ŗ@jCđđg (rB $BŽßžüȈ@iõé}wĮ˛ÄötūōÁŸL”—4-J÷Ahôãôū<¯ll:‚'žé’Ņöŋé’$I’¤Šļ˙JZ“–&Öwč÷—°¤�æ<?k^d„€\¨m&¨‰V%ĻØd#UN ĸ§ī¤tš8Üģ�Ęn„Û˙fĸ5°čMFŖ4ÕJŪ 7}ģ’Ļkĩ4†=ß茔D3g6ĖiĻ8#GËacÆdÁÆl(Ŧ…’Z…#WĨ1¤)oæ�YđâLČŲˇ/…˛úÄs.úÚŠ…†3ŌĪsŽ,éBåĄPcųĪzr‘ K’$I’Ždm?|‰CYøÔnG[Ö¤{`Ū8(Y~jTHm”Äš?;û%?Ú8%(OŒÚhO“ā$œ'Їk˙:f%ĻÁĖ›–aî=sLjc‰íǧ˙áė[Õ֟}ė‰$žuŅ3đpÕŲm 7>ԋŖ[ŠēCm9ŧlÜ$ĻôÖÂėst@ßūP‡IËáW'Ñ´Ķ‘Ú:ÁS Qī™.Xmž<ĸįßö;ÔöŋÁ’$I’¤?sWۚ/Q˜û‹ÄC7YHļtĄZØQ}ę_müÔĸŧe5ĘNLc9éę‚ĶnĒ}L,8ĩ0íŅjXŧ^ŒAav“O�ņĔ—ü ¨¨>ŊŅÆua.Šj؃üĐéuí¨o\ͤņ´’r(,H0G¸”•ŨazA"ŲrŽ*BŖVÂMB‘ĢAQ“įŽCm ŒĖjraŦ˜÷ä´ ŧŧ™rë´ŧ7MŦqäL(Ĩiã ß;?I’$I’ÔÖ\yá đÖæÄbŦŗ'œÚÉhËī $‹&Ā YĐ%#ąsé=đpãĘ7C8捀ĢĶ`đ X4ô<ĶŽ˛ģ-ƒ3āę ¸iŒ6LœR‡Œl¸)ŽÁĸ_C´⚾ipuĖēĘfÁM—zūLæm†ĸĪĀCũĪÔ7/ąŨvé-§Ē-Ũô„Ûŗ ¤qâwöAiĖ-€’įŽĸĸjĶaÎč’7 %=Ą¤ōģC—�ŧSķĢ;GM˃ÁyđĐD‚Õ-/Ÿ=1ąõw—´Äg÷@wXôJcˆT Ĩ3¤ņš0m^ėĸš‘Ä(§1Đ7Ë­Ļ%I’$IÉwÅNژ˙s˜=æƒ!¯�ĩ0é xøŗđâ—S‰ÂÕ°h<Đ8=ččN˜ô,ē*>ũ1įg0wvķS[ŪŲ cVÁŧaãg!KŒhY´,ą­4Ā‹ŋ…˛)đâ,˜˙p×všæ}J¯)+Ol)ũŌĨųüjLŠÂÃ`N:Ûa˚S‹Ôžŗ¯qŨ—Z(99õ)šXāvLVbG¤s9ēn˙Ė›ŠJwÂėåĀđâP‡ÜUđõ'€ 0ofb÷¨˛r˜žøÔ‚Č-)¯�ķgBN „Âügāë'Û‡9ËaÉ8¨ø&ÔÖ’_ĀÃŲͯwsNa˜WOLŒ*|"1 K’$I’¤di×ĐĐĐĐô@EE99įÜ|ų}Iy°í,™Úžq‘÷Ö|ɃōŲ°ōGpī9‡ĀH§Dž‘„M’$I’tÅ ‡ÃäįįŸuüŠųō~´ī 3Ąô%˜ģĸi‰ŒrĒ`‘Á‹$I’$IJ‚?ĢđårķBã4ĸ›S‚JwÂôĨį^pV’$I’$郸ŗ _�ļl†Q›/w+$I’$Iԟ‹+rˇ#I’$I’¤+…á‹$I’$IRžH’$I’$%‘á‹$I’$IRžH’$I’$%‘á‹$I’$IRĩJøŌŽ5*‘ZÁGũ2K’$I’.RĢ„/];4@CkÔ$%QäĻûE–$I’$]œV _ž›ã#ŽĐî#í`ų´Øån†$I’$é Ķ*áËõŨNđĢŋųoēüEƒ!ŒŽ8i]ūĸ_Īüou9qš›#I’$IēÂZĢĸëēž âŪwZĢ:I’$I’¤6ÁŨŽ$I’$I’’ČđE’$I’$)‰ _$I’$I’’ČđE’$I’$)‰ _$I’$I’’ČđE’$I’$)‰ _$I’$I’’ČđE’$I’$)‰Î _�'Nœ¸m‘$I’$Iē"8q‚@ ĐlŲYáK("&ŊQ’$I’$I‘H„”””fËÎ _:uęğūô'ūô§?ŅĐАôÆI’$I’$]ŠNœ8A}}=‘H„ĖĖĖfĪi×ĐLÂōîģīrøđaŪyįŪ}÷Ũ¤7T’$I’$éJôŅ~”öíÛĶšsg>úŅ6{Nŗá‹$I’$I’. w;’$I’$IJ"ÃI’$I’¤$ú˙D&ū”[/����IENDŽB`‚�������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/login-nopassword.png������������������������������������������������0000664�0000000�0000000�00000114132�14156463140�0022667�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��B��ĩ���ĢRö���sBITÛáOā���tEXtSoftware�mate-screenshotȖđJ�� �IDATxœėŨy\TõŪđ¯9à s@dU˜!–Ae Ž`\…B‚›Š ’Ĩf‹–ĨfĨuK+ËĨÅJMËw D 4xa¸÷˛¨, ::ƒÂŒˆ> û& ?īW¯ûēž9Ë÷œ9œĪü~įwf†Üž}›���˜éĄū.��� ÷c��Ā`ˆ1��`0Ä��0b �� 1�� †��CŒ��ƒ!Æ��€Ác��Ā`ˆ1��`0Ä��0b �� 1�� †��CŒ��ƒąúp] ÕÕÕZ­ļĄĄĄW ��ĮĐĄC9Ž@ xčĄŅŌWŋūÜĐĐpåĘ.—ËãņČž�@ŸģuëV}}}}}ŊŨ@¸Ú÷YŒ)•ʇzˆĮãõÉÚ��` ûŋ˙û?"˛´´ėīBúîۘFŖ155íĢĩ�Ā@fjjZ__ßßUõaŒ †!C†ôÕÚ��` {衇 CWA„‘Š��Āhˆ1��`0Ä��0b �� 1�� †��CŒ��ƒ!Æ��€Ác��Ā`ˆ1��`0Ä��0b �� 1�� †��CŒ��ƒ!Æ��€ÁXũ]���3444¨ÕjŊ^?@~.˛‹ÅbŗŲ|>čĐĄũ]K?@Œ�ÜYCCƒRŠ6lØ�L‹†††ēē:ĨR)Zm÷:�îL­V6ėoûÛ�ˉĄC‡ōų|§VĢûģ–~€�¸3­VËãņúģŠîđx<Ŋ^ßßUôÄ�ĀŨēukȐ!ũ]Ew†:ĐnÚŨˆ1��`0Ä��0b �� 1�� †��CŒ��ƒá[<��ú^\\\EEÅ_\‰ŖŖctttŸÔ3ˆĄ5ÖG*ļ‡ģšI–g3iÍ]m*ũ>l � o 5vGšŠÔ¸¯ãĶd%55 —Ëĩpô”J#įŧ.ąčīÚ�`€B+ęžAŒuK“ŋ}Á ˛âZ¸IƒĨÖi¯ū!“%ÆÉãw-øî§e>ÜūŽ�āA6˜cŦt÷ +›ō į~ßĢČãĖŨ ĶØ„¯ũnM¤[ĢŧŌ”$._¸<uû’åžŋmF› � ß0;Æ>ũôĶ[MŒ˙ûüØÛŅ-Ā'āVíÛågzˇrMęÆĩ2 W˛ę§õ‘Žm_âē…oūNģ*ŽBjŪõō5ųņžŽK“UÔhˆká( Š~uU¤„K”ŋvâô8ķeЉ šV[ą=8xC7|{ūú€ĻÅSK§K7gowk^ãŨ,øę”‚¯(ü§ėõ>­‹Ę_;qzœ&|{öú�"MIâÚĩ_'ThČÂQšlMXīŽ�@˙avŒ544888‘ņ+;˙{ûöŲáœhˆöfuƒ^{ûö­^­[“ž”Ē!nøĢŅŽžîšvm×Kפ.˙įâÄĮāČeķĨÖtU–´#~Õ\YÉwŠĢ|$Rn\jzžfŖą…W#KŋĘår5˛ô hL­ėt™†<ƒĨ\Ēm^éŨ,ũœcRB\ęĪŠë}‚[ĘĘOJ““Mäœ�"’'.žž<Ã_]äČš*KúhFūč^*�€ūÃė‘Š Ô*Ìn54hj*õuĩˇôēÛˇzĩîY†¸Ō ioÎŪđQbÛ˛Š›WE‡‡G¯ÚūËú�Ljû(NN$ āj 2dMsËŌ HlQS “7m=CVCnŌļ–wŗ [t¤iR“R5-ËįĮ§ÉÉ1,ZBDų;6Ļk¸kŦ_iŦp˙ĢT '��Fa|kĖ]ÔcDd0čn5č ē[ŅÛˇz×ģZ[CÄĩ0īÍŽėŸ“ää`]SSĶ<Ņ3LĘMOOͨ‰Ž”xRĒ,=Ÿ$dl?9ΟDŠËešh.QMŦ‚lĸÚwfŪՂ‘ŅŌ¯WĨĮĨÖ‡Ķ0?>CNnË"Ũˆ¨B&“7 ˛Õ=›°č€ĩ˛Ô^ė/�´‡įÆîfˇÆ Ð ēÛÃ-ŊŽA¯kĐézc\"" iÛLŦ‰ŸîÖVtĸĻÃĸō?*4D%ÛÃ}Z›¸<]Ctąä*‘…4ĀäÆļOI†ŦÆÆS*ņ pĶČŌō‰ˆ4˛ô˛đrkŋæģZĐ",ZĘÕėJjlbåĮgČš’Č0ĮƉ,Ŧ­[§4×ŅÃU�€aakLoĐŨ2čôÚۍĩÕĩ?o÷îwäŦÍ-ˆäōšĸ–k;wtXd¸cc´Ée‰˛N;á´ šEo^Đž-Į1wt$"GŠÔ†âešhMŦ‚+]&!ō´ŠIĪ(!ˇü4™†āĶqÕwĩ 78:˜›žŸTŊĀ‘ōãĶä\Ī7ÃlŒ5q,ÚÖĮ57'Ē!�øËЊēo˜cÍ­1ju‡L¯×5 :-˙o7FXTŗ‡Ö÷jŨnRn\bA’LÜ|ąįúD¯m—ÔåŠ˛ÄÎ5ˇ0'"ŽŖg@€Mk—H-âŌĶō) 6Ŋ„ë9GJDŠ”Ÿ_PC”‘_Õúüõž ŗHŒO­X°@ŸVà ˆnķx€1o[Ô\­%��F„ŠzŊæ–AÛ ×Ö\zá‚@Ģc÷nåΆؐ&}Æģū(‹ŅŽD%˛‚vĶ[‡F@”[“ŸQ"K“iŒß"•zRAēŦĸ@VAnÁZrŊYĐ':Ō‘*âãķĶNĢáG6'˛ŖĢ#QMÅÕÖ9ĻŠ¨@S �f0ÄĩĻ8dȝN{Ë ŋĨ×ę5új‹zyoŒČgŲģáTˇpú†ôö‡5ųņË7¤jˆkŅYØø„Y&}ĮöV7x5é˟”H$6…4ȓ{5G’ŦÆÂĶĶ8”ÃB*ąÖČŌļ§—[´ĢÛTwˇ [d¤U$}´!ĩÆ"øŲ–všŖ§§idņŠ-ÁUˇKÖņF�Ā€6:ŠÕą!C†ôš[]ƒAß`ĐÂēÕë#‹āõÖĶÜՉÛLŒs”HÍÍI[{ĩB&˗kˆëž~ķúāNslؚđŒÅ‰§˙ņj¤ņŠŦņér›đ5͝zŌ`G͆ÔÔÖ7Áũ%ÛS“Ō56‘ŌΟVģû#ŖĨ_¯’•MôœÖ7Û|ŧ*IZ›žzúâ‚č�7ǐ%Åį›K)ũj/�@„1ĻĶk úŊö–^7dČ­Ū>ūläžū”ôŲøģâĶeéŠ2†¸\kĪ€čųaŅ‘ÁŽ]ŽÆˇ^˙Ë醯ãŌž^›hü°ĩ_-‹”´,a# °Ņl—“4 åŅ4I'71]c! ’t]Ķ].h)]+Kˇ‹lû’côO?Ņęv¤Æ¯M% GiؚŸ^ÕŦöAŒ�Ŗ š}ûvŸŦč… 66] h¸WæÍ›7eĘjc$ûlœ_Čĸ[ŊA[WQ^˛2å>6 d¯š87iôÚßâ"ī÷û0x\šrÅÖÖļįķ÷Ëscw[ä_$—˝œœîÛæēÂÔÖØO?ũT\\l0:Ôú oŨē%Ē+ĖNåp˙fÂåąM8­5Æ|%Û?J’[¯ C†Ā`ÄÔ›;wn—0ĀiJRSeņq‰%°ūÍ.Æ=ĀŊįÆîĻÆÜÉÕô¯—o(áZHÂׯYŽĻ� RˆąÁĘqAbɂū.�ā^cösc��đ€CŒ��ƒ!Æ��€Ác��Ā`ˆ1�€;{衇úęË"†ëAĩ‡�¸3‡sķæÍūŽĸ;uuulv/ЃŅc��wÆįķëęęÔjuCCC×Ō^CCƒZ­ŽĢĢãķųũ]K?x[ ��wkčĐĄ@­V_ŋ~Ũ`čŨoĘß+,‹Íf ‚ĄC‡öw-ũ�1�Đ#C‡ĩ°čę‡�Ąß S�� 1�� †��CŒ��ƒ!Æ��€Ác��Ā`ˆ1��`0Ä��0b �� 1�� †��CŒ��ƒ!Æ��€Ác��Ā`ˆ1��`0Ä��0b �� 1�� †��cõáē***úpm��0q8œū.¨ocĖÍÍ­×��Ų… úģ"t*��Ŗ!Æ��€Ác��Ā`ˆ1��`0Ä��0b �� 1�� †��CŒ��ƒ!Æ��€Ác��Ā`ˆ1��`0Ä��0b �� 1�� †��CŒ��ƒ!Æ��€Ác��Ā`ˆ1��`0Ä��0b �� 1�� †��H1Ļ=úÜX‘kĮ˙ŧ¤QĪŊ÷ĶŠJmëŲUÉ EŽĸ9‡TũU0 H§Ūōšz„n+ģWëŨKäęúcÕ=Z˙Ŋ€?–žQųS„̇hė›Šũ]´6bŦ‘‰@ä.voūO(˛į¨Ē Ō÷­›÷TÄō4E—7•m ÷pų¨öÎs� Ŧū. #AØį ĢÅm&i˛Ũ+—­Í(?¸t™Ī‰Øg­úŠ´NUœ]FäĐße vŋČ+û”ˆĶßu�� ČÖX'8VŌįŋŨ<˞H—ŗ3á^u1^™ŦP×ß5< a�3bŒˆˆ#yÚ_@DĨ˛âî:Íų?ôjÄS’ąĸą^Ō§b–l8ZÚqmYęļ7ggsõptrč ˙ú!ģ}Ĩļôø–ˇbB}Ü]=DŽ^’Āˆ™omOí°:UŅŅĪ^™ü˜ģ̇hl€äĢkŠzxĸËæ¯ ôšFũPIĒĸCĢ^ˆ>ÚXĖ‹Ž7Ũ.Ė_č!š~@I¤;ļLėę!ģ0ŠĨLEÖŪŊ9Y:ÖKäę%y,tæ[ÛÛŨh<õē—ČÕëšd•"ũ“™>îŽ^KŌ:9ĖÚėųģzˆ}5ĩãîieËķš,ĪnZP[–ēí™áÆƒė%y,4âåO~ÎkŊ¤6ée/‘ĢGÄŪv÷™´ŠíĻ—nuõ=ĩŠPU´ûõéX÷đíĨ=9žÍkŦĖøáŊ…ÍoĨ40ęÅ÷ögučĸVZķB„tŦ‡ČÕKúTĖĒ„")vĪņšú,Īn}ŦZŨ3Ū/ ü¤´Ĩi›^ Ÿ,ë!ë# _¸*Ą¨'ŧŠėũĢ^ŽōĖËŨÕC4ÖGúTT‡“ļ'į@ŗĒSÛیôqwõp4 ô…O~îŲɨØ#rõŧžĄÕ–%}´0ô1ã&‡žŧŠã9߃š‰H‘ģ÷“#CĨz‰\=Ü ˜ųęšŊųŠĪŖHˆqwõpųx›+öĪtõšzDėmŗ&mú›W÷9û›§*ōö¯y9Ę˙1/‘̇hŦøÂå?f´9\ŨWÚŌ´M/F6ž›ū‘¯~–V†îúi�v*vÉˌO¤Ôiĩ*ĸÎģK÷?ũAē’Lė='Nöŗâ¨ 32|—“z,s[ÜĮ›—Ņ毉ŒŲYŦ#žĐ'`ŧƒ ŠĒōOeX›ņ{öį{ˇ…Ú7ÎU´="ōķ"‰Ā]<UĀŅĒ*‹s˛}žœ˛<~īKâÆ䊴7#–ĻTgķˇã¨ËŗŌßšō÷¤cžŌ}_Ī–5ápˆH§”mšųŪO ‘ÄÛ?„”ÅY˛â´īį*ļûÔߌėbækŪ#S’ŊßŧÉBâHœ9;ģeÎÂõųjâ }Ļķĩ•E9é‡>ĪNNY´ëĮ^fÆmp8"ĩļh˙ōŊq…VžŪRŽƒY's|f„‰l-ËÜsLŅæMĐf>Ĩ$˛™åÃ!"ReŦšžxO™Žî“ũĖHQ$;u,nÅą”´u{ˇEØw{`:n˜Ã!"•ōԆÅëéÄ^㝂žˇˆ´y›fž°#OM|ŅøāŠB3R–æį¤íû -ųčĒ]ß=ßôVjķ6͜ŗŖHG|Ņø§%ö¤,NZ93+īí‰*"âtš9ã+ZuáŪ…kŪ/ļ’ŽŸ8Ų]QV]œšge~%ų1ĸģNđʄ…+3•db/‘K¤S–æÉŽ|ˇ,5ŊxoüŪ[íÉ9`¤HzyækĮ”dbį5y’‡€E)Ģ"sŠ"øw<JÆcŦUī~aŲÚ<žØK2ŅK[šStlĮĸėü5bg;ßU͊Ô×§-:Ē$žĐĮgŌD>Š”U…Ųŋī|˙÷¤Œ ÉßNąęÉ<ŌI”“——‘K“|[Ū͌\""*ĖČQ͜Ō|žfä̉Äū~VDDÚŌŊ‹gžo,ŌoÚdŠ.įfdü83)!úĮ]oûšĩÚįÎÎĢŌŊ #ŪĪQß^,âh̊^:3wæ$$Ų@tģ”——˙ÕUh’cƈ….“>,ėü哯I….bĪ×Ō5ˇoßž}ģöČĄ‹X8;Ąļq†ÂoÃ$Bɤé×ZnjÉ_č"ö|éXĶlˇ/íœáæ">ųūéږĩ_:¸ĀĶE,œđūiãÚo×ūö’Dč"™ēĩTĶdž?wÎõtģ=ŸĐ¸‰Kûĸƈ….ū VļĖvíØ˛'ÄBIԞĘîöˇ§Ë–~û¤Xč"ņ|dŌƒ-Å\;ōĘxąpĖ‚øĻŊ¸ļgŽĐEėöRrë‚O¯ž$tģ…mlĩŗšs;įzēˆ…O4īėíĶĢũ….âņ&M]Ÿ×ę¨tVõÎBąÛė}×ÚL֜\á/tOZo|ķj{Í_č"ö|öģs­ĒštpÁxąpĖÜøÆ…5‰/I„.âŠí”æˇv͝í‹r Įøō•ÄKŨÖwûöÉRĄ‹xĘÖŌĻ•åŊû„Xč"™˛6ģÕŽ]ûmÅ$Ą‹XöŨŸSJŋV"tíXķŽÕæ~7õ‰ÛąĐÅ˙ŨÜĻõŋ&爧ėlW›ÔsÂÜī ›÷ļÖxä…aąŨÕĢI_6A,t‘ļ9U4y>i<š éé9 Éz{ŧ‹X8fÆˇ-•hūÜŗ`ŧ‹¸íK'˙ ÆH<Ã6ūˇyžÚŧOŸ•]Ğ/%×ŪUͅ'šˆ…Ūø­õ‰r-ũŨ'ÅB—IæöpžÂOŸ ]Ļ~ûgËë§Wø ĮĖ™-NxûtËäŌoÃÄB—)ŸOĀ?ŋ›2F,t™´,ĩÕĒkķ> “]Ä~ĢķNWįUmōÂGÄBiĖÁ–}Ô~7õąĐE,ķÆo]ÆJ\öûs:Įwf¨‰Lŧƒ$~.ÖĻĮm-֑ā™ĩúˇ|úåØ?ûÁ+>&¤N˙)ŠŠ¯ÁĖ˙•¯>˙čË^ņmÕāp1‘O¤,Č­4NP–VęˆĖœŊD­6Įq~nÃŪ¸ŊÉL2n"7v{ļŽø“WŽ°o™ÍjŌęwBø¤ËŽŨßMĮ×],kBD:ōyc}DK1V“gLäéŠķēšW¨8ē5á2‘đųOßhĩŗįŪ_äNT•˛3ŊMw“’¤+K:k†ĩpácB:ŲūŸ[ī›Vv䘒Č}V”˜ˆHq|į1%‘pö‡ <Z>‡ˆ÷šérö$÷j´ēNéõvØ]ŽaŅĻ˙”TE$˜˛z™´ÕŽY/{%Ā„¨x˙ž<""ĒĖ<˜¯#ō|íŨIÍį™×‚õ EēžÜqÔi~Üܰ#2ķ9ɞˆĘō ģû�/šĩnÃgë>^ÛēyĘ‘ĖšęN¤ËÍ.hY´Gį€6+ḒHúJĢJ8Î3WÎsīÁ.4îˆāŲ_ņn>Rf’×–MŠĶžRŨM͊ĒJ"˛—zˇn‹Zų¯øfīÁ_ž[$îá<â�Š€¨,ŊĨ'˛(=_I"ŋYū"Ræ§5MVåg— ƈ‰ˆrcãŠtğüÆę VĢ6“ŦX6‰OT•ŧ?Ģõ›ŌáŧRe>Ĩ&=ŗĸÕ>rÄsW‡Úõė Â}ńĶ*JĶ÷/‰~']M&îsWLîü2[xLĻ&âûOōn—rV~Ab"]~v^ã™kæė:5ĖĮŠˆ´*…ĸ˛Ē˛˛Ē˛RKÆūĩq.{gŸH™ēá“ļ7Ŧ<|$΍=neYŲ—‰LŧCũÚÕdæ3Éۄ¨,?ˇËîzYīÉmįäŦŦˆHĢíúƇ6/3WGdī$n÷Šhb€‘:7ŖŧõT¯G¯#ĢIŗøDÅ5_BH›}8UM&ŌÆkA×Ûĩ÷õą#ĸÂîopvIčës—Ŋ‘D…9j"ž´ã‰! ŅåÂ"i‹dĨD$j{I%r}ÆĢGÛųˇ­M r "ZÛÍŽrėŊĻ<1əCDZ•Âx*VЌŠĨVĩ[ôNį@Ua‘šČÄ# ŨG=ŅDŸ_Ō ¯6KsŧüŧMˆtÅyUwSŗŊģ3åo_Õöf˜™ŗÄ[,˛âôtīÉãų¤+LoJĮƜŦ2xI&zy ¨<+¯q9mvfŽŽøŌIŪDDeYyJ"ō˜Üū‹ã5ÉۄHŸŨæÃ_ûķĒ0ģXGėøy´]Ú{˛ôΝŗpß Ā{c—wūĶcgg/˜ˆžųęÛ7<:ŋÎj+Ģ”D¤Í[ūúáv¯)”D¤+­R5žŦ•éÛŋüîpj^šēËڜāeī?ŋėH~Üĸũ|‘dĸÔ/`˛ßDąUKʲ*"Ō•îũ`IZģŕ•DDUEU]ŨĮģÛeųV‚ö’|"ĸn.’ŠĒ*5ŠrļžūfģÃĻ­Ō‘˛ĒLE-Í/+ûžÜp2 Žš$8v¸,ųpî2ą7‘öTBϚL"BŦšļĢŖĻëx[V"{ĸË:E•ĸ7DwbÚĘ*Y9ØuØ5ƒ• ‘ޞLIdĨP*uD$ļ¯ĘÁŨY@yĘ;׿ĐÅ-°;ļĒčįÍßėLÎ,RŪąŅwĮs@YĻ$"3‡öŗ5ö;­ŸˆˆėŨÛŽĀƌHŠT(ˆÄ=ŽŲyîڅĮŸûŽ íũ™iŲyųH}'O ōīŨúĻkæáxMō6IIĪ—Ō$o"U~f!™Lô—pŧÔŪ&N5Ũ+ĖČW“IPcWņKā`ßá#¯™ĀĄŨžu8¯´ ĨŠˆŦŦŽĐŠHŨÍ҃ū0�cĖD Y™4ũ‹cÂ!Ž™ŊĐ'hęŗĄ’Žī•kĩZéĘ2tŅÉĻUnj1V™°0teϚøĸÉĶųKŦĖĖĖLˆ¨lĪĘĪĶ[ŸĄSžúÅũéŊ;v:ž[–s¤,įČžĪ‰/ Z¸rõ‹ū%"Ē’ĨtŅGĻk˙qēMÁŊ^琉Į„ÔÅiG‹;ŸCĨkŗŽIįŗĩÅņ™lxOUʞô7ŧ8¤Í<’Ą&ū?f55”ˇË1éŠÆ{ęÔmĨK&œŽZtĨņÄāđ;ĢŘZ5iUē.ļĀ1ëÉFMz5 _+[3}áÎ2‰ĀsÚÂ)^"ŸCDĒc›V*ŋãŌW§UQĶx6îâĀuvGąhĩDœ×Ėņ^ļī˜˙ū­ą‡RĶ ō2įeŪJ&É3+>|ãYąYOį1äE鞜ÜJōvĐæĻåëHāÅ!Žg€ĨåeŌ_*ËĘģL&ãģ\‡æ,*ãžĩ×ģ>gzqÂŊ7�cŦ“ĮŸ{€Ã13!Ō‰<ļŦۅĩŸ}”Š&Ÿwöî}NÔę~úû>h™‰‚_ü8øEŌ*Šre9iɇ>VœļņÅÂĒ“?”š‡cF¤ˊK_ëĶ‹‚{ŊlO홤īgīšŅ—Œs$ŗB…{ž+OMČ\0I›~ô”šSgLlúėË1nWĢë˜UéŨéĻõl}WĢņÄĐĒ;ĢŘļ5ešŽZ_ۚfģgŖĶJ\ˇŗLGöĪlûå㉭Z åOԛãp8D:]Į‚ģëzn?kĮƒo<ãē̚­|fŦö™ąšT•y9§Ž?’|4;˙ŠČrí¯ąŗz8•¯ŋ;ÉĘŌeĒįĘĶķÕ$ōķĩ""+o/!ÉōĶ‹ČWŸ^F$šÔ4Ųx:ũŦÔf_ēĀáņœéđŠZ‰‘ŠîõĮÁ^@DŠĘ;ge9…j"ŋY3EmĻ+ĘKģî8âX‰}CįŽū6!#n爍*aĮ)Ų‹D¤2v[ŨĨŋ˛lO™ŲÛ퉍ĒŦ˛¯×ė5ËHqô”JuęPϚÁŌ–ĄÛ-ę¸Ũʲ*"2q°o;ZŋŨ\J…ĸ¯än<1*+/w81”••:"rŲ‘••™ )”íīf*Ę īÜŖØ;ÚBY‰BŖ'ļíũ*-ęE†ßƊˆT˙J‹zÖŖHDʲO**ję5í]Íf^“f/ûxī‰ä5R>érļî+ęų<Î>ãíI—›¯-•eU‘@*1Žüwö—đŠ<+OĄÍË,ԑ(`|S2; ՕU ŦĒ4îKwŊĶcį­BŅžŖD[Ų÷Jđ× š#)ŸH-KÉj˙'Ŧ-L?žUڜŸÆĖÚÍûSvë ǞSÉûHkßAiæ3ɛO¤S)TÔ4`A—•œŲ!‹Ę˛ŌdĨŠn"õ¯,Û­VË5ŪĐŽĘ<ŌáĸĄČ;žš÷"Ô!d–Ô„Ô™“LWˇ<.fÜŽØĪۄH™“Ö~ģei—‰Lŧũ=9DD3)ĒÚ…"ŗÃ‚Ŋįá/áédĮ۟ŠĖ´2"zK­ˆˆ#rw ĸ˛œÜļĨ2ųPaŸÕŌNcAœvˇ˛Gw&÷.9E^î|"]az~Û}ÍOËčņ Ģr˛ÚޝտÉruD|w/QĪkÖ*ōŽ˙üãĄÜög˜ũÄÉB"R)”=›‡ˆˆÄ“|¤ÎĪɒ唒‰ˇŋ¤ąąŸˇ fädeäĢIā;ššFdI”›œĶnŨĒėãš:"Áø�g憺Đ„HŸĶö­×f%ā{r Ácœ€čŲ""åáUīoõ‰I[™ü΋ķΊ^wĘøh/q6!Rįių: miÂ;K’ų^öD¤Ŧ4æ‡6gë[Ŧ]ųΖ6ß:AŠô”,5‘@äaEDäŗĀĮ„të–'´~Â_qęŊÅĪŊō\ÄĘŖŨ|“`{Ēu�� �IDATņ_Yļķ#Ā!"Ō1™MZaGTžû­OZc…ļč§å//^4=æËŧģÜF Ģā?>ŠO}´)]Gâ¨mÆtYMš* *ßũŪöVÃÍĩĨ?~°ģŒH0i^hccĖŲKHDUĮöŸj>ĖĒĸ–~SØŗģt=Áņ™ûŦˆHyôŗ ˛VīeUŌûßdëČD:žņŠ&™hO¤ËÜēše6mŅöå›ËûŽ–vĖD"*MnuÆ*dŸŊŧŠRänBDUš†wĀņ õã)“7ũĐōũ!ĒÜ ŸüŦčų>īüxËā\mŲîÍ)J"ž˙_Î]Ô\šđÁŠß]Ōæ‘H[–š\Ldâė%ęá<DDI— •Év&7Ũ32“ˆI—wxgŪeâo=,Ö;jŽ— é26­iũe⊌5W“‰8fŽ/uĮĘЎ QŲūĪöļüm*Ō?Y“ĄēWįüđŪX¯‰_ûæũÂčŌ-žœáé+YqT•ųŲeJ2ÎZˇrĸņä7ķ[a—žīōÁ"*'û9›Š+ķr˛Ęøŗŋ˙1 áéy‡Ô§>^¸\6eÖâgV/>4scÁúé{$R{ŠUÅYų—ud÷ôģK}ks˜ąūSŲˎRŌVFø˙(õ 8ZeĄLV¤Ô‘ũ?Ö0ĩģ;ReŲΘ‰%"Ę)+ūffxχ?āŨ¯Ÿs|ßŨ¸ŧháúü¸YĮŊ$Î|RUgÉĘÕÄ÷Z¸qEĪÆ’wžš€Á‚ß*uDžĶBEí^œøî†yE wæ˜2Ņß͊ŖŽĖËÉ*VęLÜį}ū~pĶGy‡ĐšA›—ĨUž˜īã#4Ķ*Kķ â•ĢCZąīrßÜ"ãHV|ž˛pÎēėØįü3ü&JíĖ´ĘBYf^•ÎÄū™¯Ö5gņĸwB’^I)‹}nrļßD/;RœJ¯rXŧ2ėĐģ{îÍyzĮ,đŲûAvūēˆpŲD/)ËŗŌs´>í}GŊ<d]^ŲŽ%¯WM ģ"¨§+4 zc…4sĩŦ`}dĐ/O3ĸ(?O!\žØīˍŋ먓ģ•íI§‡)> <4ŅßŨŠ”Ĩ˛Ėė*ņũV/›dvW5/~˙éŒÅGŽ.ž,z{š;đ9ZuUa^~™RĮ—,]aODž=˜‡ˆˆ8ž“Ĩ&Į2ĶeDîÆcFöŪ^v”Ÿ™Ž$“Ém¨pžģūCŲĖ•ŋ|åé\ПˇˆOĘōŦėœ*5 üßūęšv§kVĪŦ^ŧ?bcqúûū{ĨŪ&ZEqVžÚ÷ÍšVwd÷á­[č ƒ§5FDį?ūzđŗ…!f—s>x(ŗPÅ÷™ētë/ kZžįô}7vëˆÍÔšGüœœ¯° YŋwĩÕÄÅīOsįSU~ęą|q<^ŒMŪąr–ŋ;§*˙ÔŅÏfæVqŧ§Ė˙ė—„¯B[ū’B7&˙˛yų‰™2?õĐáƒĮōf’§n8ōË×w|P÷¯,Û ņüõoú‰ø:eq~nUĶíiŽäĨø#{>˜$ĻĘôãĨœ*Ō:øO_ĩ#aī˛Î$ī)Ž˙ŦP;ĸ–ĮÅÚ0“ŽŽOŪųÎ3žUVō=ûŽįĒžSWîüuījŸÖO!Oųj×Gŗü…ĒĘ>–™UĻsžš9áûšfęģÁņÜŊŋū¸&ĘĪY›ŸēīĀžäœJŽäé77'˙ōqpĢĘ­‚6&ėXú´ÄN[–y0!%ĢŌîŲO÷î}ŅŨŒˆ¨“Q—}ĀaÆļ]īO“ ĩeŋÜwøTų.Ū–đũTgįgVŋ9ŪŪD]tė÷ŦĒģ:öŗŋßģuá?ŧŦ´Ĩ˛ß͞‹ĩĸg>‹˙î%/õpäŒäĩ]{׆šUf¤ü|č÷\•@<yūÖ_6?Û| zXŗÕ¤¯~9øŲÂoļ0=åāĄÃŠéåZtÖ;ےw-hŒœžĖCDDfŌÆG¸^’ÖŨÆc"īÉãÛ ŽwŽøú؁æMRYfŌžIŲåQČĸu}?ÃųÎī%ĮãÅÖE¸›iË2ĶŌsJĩîĪ“°í9Iãč%Ų@2äöíÛ}˛ĸ .899õÉĒ€!TН-:JO“öUP÷_ũÁh˛U=ˇG)\”’ŧĸÛ*L§J^čĩ4“¤åíš:ˆßNčCä˛?¨Zcp_•î˙ō¨šėCæ Ž‹žļ2/#)áPû¯Ŋ/ÍĪU™ŊđCn�Ō`ē7÷‘"cÕŌoŠČÄgņ‚;3¨˛6/^‘ĄLτΧ:4ū>@Ųîwņ'Oõ$ģ 0Ø ÆāŽhŗļŊŗSVU˜WPĨ&ÁäÖßíOŽ \VĪžķöÁéd{wr`œ¯—ĐŒ”ĨyųEJ ūąöŨIƒŖÉ 0ø ÆāŽhųĮĶ2t&aĀĖ7Ö.ž4¨zڜgüø‹ŨîÍqeųYĮŠud¡wŠ™ēhá īžü�čKâ��Ŋ1@.ûâ�� †��CŒ��ƒ!Æ��€Ác��Ā`ˆ1��`0Ä��0b �� 1�� †��CŒ��ƒ!Æ��€Ác��Ā`ˆ1��`0Ä��0b �� 1�� ÆęīZh´ēŠ*šV§oh¸Õßĩ�� Z&l–)—ã`;Ō„=€" ×Ę>h´ē’Ō‹|ūßĖøÃ†>Äŧ6b•\á=ÆĨŋĢ��¸3ŪP}ŖļøĪ ÷‡A’ ”¨’ķųÆ3eb†�0ˆ ›e;ŌŌĘŌĸōĘĩūŽĨ ”ĖĐht<SnW�đ °n^¯Ņöw}` ÄØ­Ûˇ2¤ŋĢ��xP˜°Y:ŊĄŋĢč%Æ���z1�� †��CŒ��ƒ!Æ��€Ác��Ā`ˆ1��`0ÆÅ˜>ûëWŸûā×+}ĩž+‡Ūžŋ"ļ´¯Vw'×sž|įŊ5‰—ôm'ĢōöŦXąiWIũũĒ�``\Œõ5ķŋG΋ôܯ͍ds=+%ŗõWĀčĪ'Ļ͘ p7ĶûU�Ā ņĀĮoÔ#w6ŋ´ö ķ^™’˜Ģjœ ¯LKÉŽwŠgv˙Ē��$˙ŨÆmčĢ˙{p÷aŲWTÄ6ŗq|ęŦicŒ ­ēŌã;vũvæJ[āõ´yâĻäQKžŒvĨ+‡Ū^}ÚũíĪbœõ˙ũzéšŊÚûØÄsWjëHā=?ԍ×ĮE˛G…„{ũįû´Ä÷9nĻtũßû3kœBfúŽ "}åŋSž(ž ¸I\ ˇ áá\øDT˙ŸëwŅԏæ3VŖĪûiÅšņÁ\_S}Aė';)lUÎŽLĨKĖ;ķÜZoėüŽ÷âŽ-}Ķxãŋ÷üÕåĀ-`Iõå§SNWŪĐXÃėE^S"‚=GQũĨ )'Kä×5ġu  tæ]ËXķEŽį,˙ë)IĻAĢ^uHûpįÕĀy7Ō~ɓ_אŲh¯ŗ§xđ‰ˆH]ž––Y*ŋŽ1˜ŗû‡ĖōˆčZÆē/rÅ1A†)Ųo깂GÃ##F^؟Qtų& w šähl•ę¯žM;˜röŧâ&qN^QaãŦŲ}ün��Ķ ĻĢ;ˇéĢ\ËĐßZ2ŠWWv<ö‡Íõ+>Œąëōbŋˆ/=ķæÂņæu$īÍVhTģÅŲl6éKŽÄ fŊ´6F@ՙÛÖîøáȸO§îëBynAĶÆ˙˜xâņ×ũ¯'¨îûϝŠōâŋ: 8,råK}!åĀĄowV.õˇînel‹ 3ŌXãįŧčd=˛§5čKSļēā2=zŽhK#ĪL<´3ÖtåRkR¤mŨyÔā>-fĒx¸Ą*3i×÷q†×_IDÄ6h ŌÎxÍ{ĶÎŒjØlCՉ”‚đŠ+íØęĸ]_īũņ¨ËG3\ؤÎÚ—xcėėĻēđIu1cׁŊģ†/]čeJDd¨ÉN;?mÖâi# …ûˇl9W.õÂķLodíØ˛;áߞKũ­‰ęÎúō§?-Ÿ˜úϝ ûƟŋ8ôe=kÕlq_Ŧ��fDŠĩųОjįđ˜ČąŖæ–ŖŊ§Į<a}å~?Ŗ§ēŗ§˙[īõ”ģ­Ĩ­ķ„ųsưU]ŦDī<mŒ€MÄļô‘ē˛Ģ+/ÖŪ‹Zų†=|ãßûcãžãúD:°‰H‘}ĸX?&hŽŋĐzßŌq܌ˆąĻWNŸ¨čÁúÔ~<m-{|sMuYŠæ:øHFYni'ūgĖ‚%3Ǝ Ō—d¤]æ3}ǟŗ•å[ĪđŠAVō'Ę›Æ¤Ü$ˇ i^ŖFōí"ŊŨø/+6ņÅ>ĸaõ—å׉ˆ¸ž/­z!Ė×ŅĘr„•Đ+ĐĪNķgIeĶÆ –>:‚MdęáåĀ2hü]L‰h¸ĮR\ēĒ'ĸŲ'Ī֋BL;Œní<~NØÃ†sŲ×{ēƒ�đ€D­1yŲEƒĨČ˛y‚­Û(vĘĨR% .ÉõĻ"gÛĻF{š›ũūßNWbë`ÛÔmÅæąŲ¤ĒĶw:ß_6b|TPÎē” Ļ’™áÎl""Ŋ˛\AÖwhnm°í„”wõ˛šīô6Y îrtˆĨŗØ>%m×6V ī8ąČÉa„•Đ”ˆčęey=ËÁÃŽšķÎĘÉq˜ēĸō: ­‰ˆXŖÛŒ‡1iĶ\0˔E†z=›Įž™}4e×EeuŊÁ`0č5DÛŋK›k=‚ß´ ה,†7nÎԔÅ"ƒžˆôōķ—É:ČĄų~!ĪŅÅېrū˛>p: Å Šąēú:b™ˇęrbŗMydĐבžŽžxĻ­.~Ļŧ.{ĻØ÷íi=fœuŠr„¤Š—Ė Š7)ĢÕ;Ââ˛X¤2îü6ąšw=ÆŅÎÉĢi'r˛ã5,ūčąS"BüėLëÔ2œŨūîŲ63ģĄ"˛&"â˛MÛ!V§Ĩé/Üē3“å5#"Äe$—M7ŗcˇmĩНĪ;ƒÆ` Ē”Í‹SÚL~X­!BŒ@‹Ac<SjëZ&čëęëČԜGlž)éõ­šUõuu—īo,Ž)‹Žk4DM-•ŽÁÖDOÔģŸ jũëB<ģq˙œ=¯Ž(>™’´‡aÄÛĶ-ų\âēĪ~5HØē4÷îFQ^Îũb˜Ī+žŽÆßŦĢ'~7k`qMYdī7oŪ߇ĩžjĘįwš�<ŅŊąQĸŅŦęŌ˛ęæ WJ.éMGĀƒ­Ēŧ¨lzáâšâŽîõ#ļ‹]-•7'ŦūbųU˛p°ãąØl24ÍtĩegēĮbą¨NŨŧ܍Ģ×4Æ˙§ē\TpŲø´5ÛŌqÜ´¯7•U7h„ _SŖ&+둍˙0e™ōųw×Ō 4ŦųFūō™Å]/ÛÆÉŽuũ†fÄČæJ†ąXÃĖđd�´ÅĖĢģZzöܙ–˙ū¸XKē„>nYš›|VŽŦ­ž˜{`ĮÉęŅOüÝMŧąãŨYåÉûŌK•ĩĘŌĶ;vŖøˆÖđGŨŲ¤í:]~U­ŽŽČŨŸxÖ ō‘ˆØÖŖtņĖŽé‰HUššRbčYCZādĮēV’[^ODõ•)ŲęÆÅTyi;wħŊR}]]}­<ķdņõaNÉí6!ĐV™vāč*n¨Ô7*ĪĻ~ģ~ķW™ŠģÛģQö,yVfŅUĩújéé 5"–áúĨōúžßiîķ„;ë\ĘΌōĢjuõĩōĖŨÛ×~}¨ßs�m1ŗSąúôŽ/Nˇž0náĻ7Ĩ<÷¨7–°wĮ˙°6^E<ËQãÂ—Ėš2ŠMDææŋziKÜĄu+đF Žšnˇ){āíģ™WäCĘÁ{×Ō×ÂiLČKáãCVŦũĻF\ŒOųâ“DÖ0kˇ 3Bž°§ĻĢä?öįî´¯ÖdąšN ˙ģr{‰Á@ä=ΐt4a{âMą†šĪ^âÂ&"Û EķX )‰ßožŽ!Ķá6â čyūVwˇ'|¯¨ˆK;Æ¯Í"ūčąáS=Õ)•ą™_mĨ7g÷´=Å;õĩéiĶâ×%Ũ4°†ŗ(Č­1�hkČíÛˇûdE.\prręõâšįÎÛÛÜåĩōnčëęôl¯qøšolžūŅę Ë;,ÖcUr…÷—žZ�Ā}{îü_špũÅË~_p-’{ĸ.gË[ß_;k~øÕ§ĘÖģÎōîŗ �€ūō`Äoüü×Uąûûęƒ=ud*=æ™Ĩ‘÷íë€�āžy0bŒˆį<éĨw&õw��ĐĮ˜9R��€ˆc��Āhˆ1��`0Ä��0b �� 1�� 6PblȐ!ˇúčûD��āŽtzƒ {0<s5PbĖ”krķæ�üõ�€ÁŠúF­)—ĶßUôcŽö67ëęÕ˙W×pëV×�0˜éôšâúĩęۑũ]K(-J.ĮÄŨytE•\Q}ŖĄ‘I–{î|—��pg&l–)—#~Øqpt* }ārLÜDŖûģ ��`’ŌŠ��Đ ˆ1��`0Ä��0b �� 1�� †��CŒ��ƒ!Æ��€Ác��Ā`ˆ1��`0Ä��0b �� 1�� †��CŒ��ƒ  ßĶhuUr­NĪПÍ�`ãĪf:؎ÄĪfö%VWRz‘Ī˙›ØĐ‡ĐFlŖJŽđãŌßU�Ā ĄĶĒoÔ˙Yá>(~�z FE•œĪ˙Û0ž)2 �āž2aŗlGZZYZT^šÖßĩô’ŽgĘíī*��–ÃÍë5ÚūŽĸ ”ģuûöCC†ôw�� 6K§7ôw}` Ä��@/ Æ��€Ác��Ā`ˆ1��`0Ä��0b �� 1�� Ƹ¯!ŅgŊtK^ĢgXĻ‚Ņc熎ĩėŋĒî^î–ßÔĪÚøF yß­ķzΗ’Tž V†bˇšŦĘÛŗvÜķ…—æ¸™öŨÆuΗkRLįžˇplW�ŊŸ#""Ë /=?Áxũ××ÉĪ;˙Å&åōŸąī°āā6b|TPÎē´”L߅#›&ęĪ'Ļ͘Čpd� FĖėTäYvsuwsuwsį0ëõEÁ–Õ™iyuũ]Wŋŗö ķ^™’˜Ģjœ ¯LKÉŽwŠgÖ¯…�Ü#ĖlĩÃå<Š•Z]]KÄ#Ē+Mßŗ˙ø/^­3°ĖGšúM‹‰kND¤—gīÛs8ˇėŠĘĀ6ŗvö~:&jŧ-ģķé<ŲĻeûĖ^Ú4˙6Qq܊u§L#×|jKDTû?›–íŗ\˛)fU˙÷āîÃ˛?ލˆmfãūøÔYĶÆˆˆūˆ}ãĢ+!/=rvO|‰uĖĻ%~ŧę˙ÆÅÕęyļŪOÎēGŊXėQ!á^˙ų>-ąÄ}Ž›)]˙÷ū˧™ž#ˆˆę*N'$.¸\SOܑŖŨÃCüėL‰ę˙ŗcũ.šúŅüq<""Ōįũ´bÍø`Ž¯Šž ö“ļĀ*gWĻŌ%æyn­7VžëŊ¸Ģ3ŋv"åœ\eāŽp›5ŨßÅØęS—§%¤e–Ɲk ĻÃlÄū!3…<"ĸúōĶ) '‹+oh Ŧaö"¯)Áž#ē˜nČXˇá´Ķ++ĸ‰ˆôg÷ŦøŠØięō×&đ‰ˆŽeŦû"ĮåÅ7Ļ9vĩkë×Wū;i˙Ņŗ•–™{HˆíŊy�āū1FÕĘjÛĖܜˆęōbŋØSėšä/ģîâŠ=[žŪ*XûV €.&nŲ’k9ëÅUX˛ëäyņ?|ŋ‘mš!JÔųôÉ[ũīÅég"ētĻ„ĖÍTgĘjCm͉ôĨg/‘›ŋ3ģîĖ›žĘĩ }ņ­%ŖxueĮcØŧQŋâÃ(›XlļAyęșĮ"WNŗąåŅ•Ŗ[ļüų=˙V¨¯ö둸Ä?ô4ę^ ž[Đ´1Å?&žxüu˙ëI'*‡ûžékEDtíô–miu’°—Ļ?lIʂ´CûˇÆŗ^ŸkL¸.°Y,2\ĖHcŸķĸ“õČö/ŗX†ĘiFĖ\5c8Š‹öŊwû‹Ōã‘:k\⍱ŗ_˜ęÂ'ÕŌ]öîžtĄ—Šž4eûĄ .ĶŖįˆ†ą4ōĖÄC;cMW.õŅųt'—á'Η)ČҊˆ.üQi:lXUYĨ~‚˜MTwņüUŽĶ4ģnv­}ũúŠ”íŠÍž˜šŌW@7ū<šrĸŠčá{ņ6�ĀũÂøĶ×ɋOîIždöČ+ņ\#ßy—ĖG xDd)˙‡ûÉØ˙–Õ ØW.V͍§üÜlxD$˜ôŌ;B%Ųé;Ÿ.`ģ›*-Ģ&gKĒ-/­uđ{ĸūÄŲJũãælēT\ĻwqåÕæ§ĘĒ§Ŋi]â==æ‰sīüũĖ4‘ą §äũ}å/ŅĨä˙­d{/ž%Å&<>뙒sëū÷ūŖaA™RöĮĘUp}^ t`•gžĀģ$Â[Č&"ž_DPášø“ų ßĀawXŸÚÂ/b‚GWwÖ믇{ g_âį’[T?îQSŽgÄKB˛°Á&"ˁ~™y)%•äåĸēŦTs|$ŖŦŲD4üŸ1ĪŦD]L&ą2KåuV<R–’‹ŋû…ŦōJ IĄDÎvœāÄîf×ŦÚÕ>Ģøúp¯įϏX҈ņ3üÎŋģįĪ>8ä�Đ˜c—ŋ=˙pË?M­™ŗ8ÆÛØ%ÆãÕũ;ūāžRšĒļN¯×ëõƒŗ^OÄs–Šx;ŦÛ&”JÆš‰Ņh""ęb:û7Ū‰’˛ē KvŲš‹ļcbÆĘŗ8w‘Æ8+ˋkm|ÜĖI^vŅ`é#j!ië6ŠrŠTIØ F Æôō+J˛hĶ4…=Ú́ū÷ž}ˇ´qŦGĘSÉĖpgã6ÕU—kČNhß<ÆÔFhEE•zēSŒY ēbfgĶ|×Ír¤ĀÔPYyƒ5eķØ7ŗĻėē¨ŦŽ7 Ŋ†h¸ˆ,Åö)iģļą}Į‰EN#Ŧ„ĻÔÍt'7'J<_Eã\Ô.¨m|$.t"ãÂuސ]4Ø9°ģÛ5Ģļõ̝*n˛ŦFY7ÍČ-´&Ä�ŗ13Ælü—, 0&›mfnkÎk~éĘņëÕzOŸŋpŒ­9‹ôų;VîŅ‘āņÅĢyĮ“>ŧíˇXƒŠ­×“1ŅOš›w5=zŦŊī‹4žÎ–ķDOڊ,G×ũVĒ$Û˛sWĖ]Ũm‰äõuÄjĩmbŗMydĐ7 5aķšŽŦzCØ­~e•ÍcŗéūD‚õ˜qÖ)ĘqSušúzbāļĘÉ55%ƒ^ŖŋãēØÜnÆ8šō[ũJ‹Å"ƒAO¤ŋtpëÎL–׌ˆ—‘\6ŨĖŽŨrÔ8˙’W-ŌNäd%Æ%jXüŅc§D„øŲ™v5įėâ¤9]xœŽ¯îâ2ÂÁ`§,¸XH•ԂGD|"Åv­Ĩ~MŊžˆĪjyĮØ, ß`:fÆÛŌvô¨NīÎ_É=]ĘöZō|€ģņĒVK­.Ķl[ī§æ{?EúڋgOĮī;ŧņķ K'˜w5ŨmĖčēôŌ+—¨Lī>ŠØĒqļō3eĩļg+yn“FĪ”G†ÚVã#õuõudÚ:ؚļĖbŗ¨ŽÕOûčëęīœ}ŠåĘŪtšīlMĩŅ]lŊZĶōƒĻžX,6ŅåÜ˙(†ųŧáëh|áf]=ŅđÆšxvãū9{Ü?I_]Q|2%i˙Èˇ§{°ģ˜ÎwōžRxQ}ábĨŠŗŋ%ņF;Z"WŅųĘáĪqWģÆbąÉ 7Ô5žEõšz"$�Ŗ1sĀ}×ôĒzb›š7]Ā”˛ĶĨD¤'ĸē‹gķJk‰ˆˆm>ÚûŠYôō2e—͉Ė]ĮŲʋsĪš2jœˆMdé,2+=ûī3ezįąB6fU—–U7oũJÉ%Ŋé¨Ņ‚eąmlÍéJ™ŧ)ēôĨg/ŨĢCĐ9žŊ].ŋĐžõōķ rŲ°‰Åf“Á iŽŽĢ•=_īõ‹ōæũŋzYi` †i fŲ”úËg Ų¨ē\TpšžˆˆØ–ŽãĻ…x¸ŠŦēŅåt"̇‡U•äVœD6Ddíė@‹ūS"7uvq¸ÃŽĩ3ĖڊKŠKW›ū­ēX~ĩũ<�Ā0ƒ-ÆnBžĒ0õ.ÕÖV—ūOė–Kw3R–UÖęë‹wlüæ@vŠ\Y[}Ĩôtō˙ĘyŖÆØRW͉ȯŨÍŧôØīm=œÍ‰ˆlĮŽŌŸũ=[9ę7Oú¸eiblōYš˛ļúbî'ĢG?ņ÷NÂå'ĩŽË=°GVvE)/>{¸Œ}ŸÕúų?Ŧ9{0!ˇüēZu­<ķ@Z×ũ ¯áDlëŅēxæ?×ôD¤*MM)1ôŧ•nz=įā‰ōĢjuuééƒrūoą)‘Ũ({–<+ŗčĒZ}ĩôô΄ËpũRyŊ^•—ļsG|ÚŲ+Õ×ÕÕ×Ę3O_æā4œēšNDc¨ôtļÂÆÅ‘MDėŅB‡gĶJ .ŽwÚĩvØ.wįßČKH,Ēŧ~Ŗ˛äôž,%šb�LĮĖNÅŽņŧ#įOÜē'îŖ×ČlôØ'cž ųƔ-īą—|ųĘKú¸Cņ_¤+ë l3kįąĶߌōâw1ˆFuĨc§ŲŪ"c&{ԘŅu˙.ļõwnü)ž{ÔKØģãX¯"žå¨qáKfMÕi>i~mėá>;AfŖŊŸŒ‰bĩ­ēŗī™ãŧh؟tbˆCõ4l¤ČũšE!ō‰ˆŦũĻF\ŒOųâ“DÖ0kˇ 3Bž°§Ļ§kũ{ĐŖęߎ¯ŧnāŽt Y!æßĐMÖ�� �IDATß+*âŌÎŖņkŗˆ?zlxÄTOuJelæW[éÍWŖį’Ž&lOŧi Ö°‘"÷Ų‹B\ØDA]L'b;iÎ ÷rᑊƒ‹ÕÍ"ÅÃbįĻ#ŨõŽĩÃvyyĒaWZü§YŦvî!a´-‰¨U$�0ÍÛˇo÷Ɋ.\¸āääÔëÅsĪˇˇąę“JŸ*šÂ{ŒKWŅQųžw^đ_ŧ2o�#åž;˙WŽ-ņ˛ßW[§"��<Pc��Ā`ƒíŪÜG¨÷>ėī�āA‡Ö��0b �� 1�� †��CŒ��ƒ!Æ��€ÁJŒ 2äV}Ÿ��ܑNo0a†gŽJŒ™rMnŪŦģķ|��ĐĒoԚr9ũ]E(1æhosŗŽ^ũu ˇnõw-��ƒ™No+Ž_ĢŽq°ŲßĩôŌĸärLܝGWTÉÕ7díåž;ßß%�Ā aÂf™r9â‡G§â�Ú.ĮÄM4ēŋĢ���&(Š���Ŋ€��CŒ��ƒ!Æ��€Ác��Ā`ˆ1��`0Ä��0b �� 1�� †��CŒ��ƒ!Æ��€Ác��Ā`ˆ1��`0Ä��0Ø�úŊ1"Zô Åū—4 ũ]�ĀāÅJ1ĐÖöw}d�Řë&:Ŋŋ‹��ė4 ´-‡N–QņŌū.Ĩ/ ”N؃Č0�€û§¤šböw}a ÄØĪgúģ�€Ėā¸đˆ3Üĸ:C�đ€ŪcŦQ��0�� 1�� †��CŒ��ƒ!Æ��€Ác��Ā`ˆ1��`0Ä��0b �� 1�� †��@ŋ700q,čšĮ(ʅŧ,ȂMšz*žDûriëĒm=Û8Ē™A~#qF›ÅĮŅÉds‰žˆĨlÍũ-ā€莛7ũNNlĒQĐÉ3$7…ų i+­|ŒĸvĶo7ģ[Üڅ~ ›+ô2 āŪ@ŒtÉ܅~FNõ´nũë<i›_āRL8mõ¤ÃŗÉ÷{Ęīâ×.Ė…ôë,rĒĄgž§SČ0€{÷Æ�ē´r 9mŨCoˇÎ0"ŌPėZôqGŅã:_–ã@ŋÎ%÷›õ=ũ† ¸gc�]p (+Ō”ĶŋĘ;=öwē@ô„”Ŧ;ŧÄąĄ_cČ÷&=÷=ũŌm¯#�üEˆ1€ÎYےQņtĩĢ9ät˛žČ†|ÛöÍst8†ž0PÔ÷´ŋæ^— đ CŒtÎf8‘ŧ›2Pq ›l¸­&ÚŌžzŠOš*F; āŪCŒtĢûQP,""MĢ!îžô”ÖɈFҝô7@ßÂHE€ÎÉo9Yu=‹Ü‡ՓŧÕŽš?Čw•(oíķ¤ÃW艌ļÃC� OĄ5ĐšĢ—¨˜ČŨ…ÜēšÃ†ž0%œōZM“—S‰ˆh˙úâ ų>I?v1”�úb   rúņ ‘-ũKÜųë1˙ 'ĸ_e]Œ1ĐŌŨôk=EEĐ'÷ŽJ€b  K_Ĩbĸ¨é´ELœÖ/°hFmuĨšrzũL×Ë×PÔ*fĶĘŲ4cØŊ.ā…{c�]Ō–ĶSģé×é´h6E)čä%’kˆkAžBr7%y9=ŗ›*ē]Cm9=ķ eũ“~ŒĄ Û(ģ‹īû�€^Ck  ;Eäĩ‘žû_Ę#zb-zŒĸ„¤šD+w“û÷=úšÄ’Šú/qméđtrŧ÷<hиíMŠ=Jąwœí ™vŅÁø[ Ičë˛�€ˆĐ��FCŒ��ƒ!Æ��€Ác��Ā`ˆ1��`0Ä��0b �� 1�� †��CŒ��ƒ!Æ��€Ác��Ā`%ÆxėūŽ��ā38.ŧ%ƞKtģŋ‹��xpÜĻgĮöw }a ÄXė4˛†$�¸/n“­ÅNëī2úÂ@‰1"’ŋMĶÆkH×�0¨ą†Đ´1tų­ūŽŖ ŦŸÍüyVW�˙ĪŪ‡5q­�˙˛eID%‚ÃDj Ô[Đ úŖbĩR[ĄuÁĨŠĩŽ×…ĒUŦĸ¸W­U´­¨­ËmE­bŊ ^‘Ū X%X ˆ .E„LXōû#€ ¸ÔĒ8ø~ž<>äĖĖ™7ørΜ$„Â)/ĐhŒBųĢ(Æ!„pÅ!„Ŗ#„Âac„B8ŒbŒB‡QŒBá0Š1B!F1F!„Ã(Æ!„pÅ!„Ŗ#„Âac„B8ŒbŒB‡QŒBá0Š1B!F1F!„Ã(Æ!„pÅ!„Ŗ#„Âac„B8ŒbŒB‡QŒBá0Š1B!F1F!„Ã(Æ!„pÅ!„ŗ|Š}]¸pá)öF!äEÆ0LS—�<ŨëØąãSėBȋŦ°°°ŠK�hR‘B§QŒBá0Š1B!F1F!„Ã(Æ!„pÅ!„Ŗ#„Âac„B8ŒbŒB‡=Íwņø;4ef—‹ÍîT˜5u!\Ԋ1ĘĢ›ē BiJ/DŒ)o›h)Ā!„üeMcÅz3S†å^+]ŧ÷Â-Ą˛šFö}éqĢUi˛šēō¸‚FŗM]y˜™™ˇąnĩļĪŠPŌÔĩp[ĶĮX]†MŲzÎhÎĀ–42{ƒŽŠ+ OÂĖ’ŽF“FŖąˆ-–0íß×KÚtnęr8Ŧé¨ô•�°øĮ FsV0Ŗ #„4ffffæf0Į¤ã ›ēnkú+5�€ZĮÂĸ釆clę!“ÔeEM]ˇ5}Œ™F€Æa„—‹™™Y5­ø{^”#„Bž�Å!„Ŗ#„Âac„B8ŒbŒB‡QŒBá0Š1B!Ö^qėĐĨ]Ė@aĐ+<Gs}YEÎՒM{ bķĒvŒŖCF´ƒbųšQžW•MŖģđØĘ‡&_]“ĶԕüU.vß}Ķę÷W–{ uíōCŋōŅĄšķŲÉĘfųžöüĩ—&$=ėU<mēˇúdxĢ^+>ĒŽ)ËvS´ûÜCŋ՞懸ŧ–uwË+õW´ĒŨŲwe˙dü/zO~`Ûī?~ĨØņ4j$äEÁųkåŲ1uš]ņ˙ŽGü¤ģ\k{›Đˇœ6Ͳą‹Î^–ßÔÅ57Ũí÷Š˜0ĨäzSōXęU[tĸhĄ˛ęÚßî’'nģi‰?ģxÊō"Xž: õô%ÚĪ(X“ķÔßPå?—NîČÚgúÚÖĘÆĪŲ;ęŸSíÁß Ą˜Ô¯.ÜĘ{ø>ëĸN\Mŋxä‰ĪBūaįē%(æÍŨĄM]Č˂ë1fÖģW×Ģ*×oo^15–§åTXĪuņ1ȧ7ĒÚģ1mQŅÔU<ŽúՖæ•%<â×÷ã0ī5\āš_ôŪĖbĶw[ō¯,žq0ĀzMNųßīŊĄÂ;ęôëŠēģĮ¯ūÚŌĘfŧėƒÍ™;+O8üÛwņ?Ü§ĢŊû‰ĢŠOÖ?1ņē7u /ŽĮ˜š�3ëúm•Ú ˙¸{W`;;Ė)ÂĶÆÕĒ*'§hęÕjßĀLoĶrüøWĸŧl+ØÔtUčŽÛĻßN­\ÚÆ„:†ēYÛĄĸū!ŊÜ/Ō‡Æ˛ÃzÛ[č5ÚČØË'â†´–ĩBĄ˛("ļā?Z�@‹–SF8Oõ´qĩAáÕâ˜Ũųk/T>ûgã/jķš]äA+>*/g–nŲt+)߀įb;mrëŪ^[^uŅUöÄõšä ĪÉ7 °Ŧ$ļ9}yVō=ãsĪāļ͆ļtw43\ew¨×$W`˛â•ōÕ1°›ßĪüЌĢkr`+n9ĄÕënV|TæūZŧzcÉŲÛĩ= n9´…ģ­ņrļnÃ#¯^įûļ™>ÔÖķ Ü2œMÔÄ|[~@M˙7fæÛFļ騯Ŧ4˙Ζęø<ãĢ ĢŨāæ|wRŅĘ*x‚pdOÆĩ9[j8âvĖÆŌ+÷¤sK&d‚ũˆžŧļæė-öl⭘oËŽÃŦčÄ­…7J¯ÔíVQ‘›~k [ ôü5¤¸™õNį~vÖ­4åˇ�‹úŒîūž›]Į˛ŠōCĘcĢNoaĢ �,Ė,æøN|ûVæ–G.%Ŋōŋ}û~7ø–ž¸ū¤â{]‚Ã=‡¸ڗW˛é׋O}yãŽ:wėÄôš=÷õOzl`afņą×đ`ˇ>Nļ×īÜÜúûģ˛2“6l˙WŠīŪpzíõ¯ú~7¸´âN]nv¯ü<dÛđ„i#=‡ôpėV]]u8īDtę—ÕÆj�­­[}ęķąOYkëVŠōVœŪœ~]*~{îëŸŧē-¸ĸēĀį˙œ*yû­G*‹¯• œ%įŊ}PUÃüāÖgl÷ЎgCuEƍķҧ6äëjFÜRĄd–OD7a—bŊöōØšßž­¨ŽxHûĢž˙ōū¨›°KĩŅxîföŠôØß5�Œé6tRpYÜ[ĻnZ SÂū=ū?sū›ę‹>ķÍĖĖ’ķĶÆK?hgcš$?ę×ĩ™7ŗ&Ŋ>éՑ�rĮū7:uÃļķ?>ģo bÂõ%U'2îč_é°|ģA.–ĖũÛ-[,Ö9RP:uMŽxų•#6öû§9Ik˛Û,h “īīųAQYŊ,wííēŠ§�´mģ{–ë;šĐ¨ß]Ŗ.ĮYĩŨ?ÍŲĮ�ôÕ°ą‹ę]}ŽÅ'š›*ė6Moŋ3;ŗÅŦËŠí6õ7]Ī`fĪrq1Ä|™å:+'â/jZįŲŽ÷–5¨ƒņ›÷ÜĸuxfĪUC<q›MQ­;dߚ2æō  7Á6z…°WK�ŧ8ô6hįN¸2h¸ja‚ą÷ÜöÅ8ģą`æ‰*(‹>|eŪŊ†öÁŽ'0š; GQÍM2öžÛ!ō5sĀČļ¯Ų…đJfN.Ü­\ė6­lã~Ŗxæ„+ƒ&ĢĪ8´^ģÄŽ#�Āļ§ũŠ LŅžëī /˜š§*$‚ßļŽķž›fļ4$Ũ6üʰ•:^?Įĩ �ŒŦļ=ێāi'¸ė7¸`›ĄeäA{<ŦZĪ1íôFōJÕ{ïŒ^ĸ3ôtX1œ×āÁĀėÕ Ž‘Ũ+ļĖ)4üĘčueŧ+†ZUg“Ję_ kiũē.+ Ī>Ã�tjåĸ¯boé‹tüįš>ŸũĒ:30~ldrLP§^‹ũf˜vÕmH¨äí•é›CöGÜ(+šíš÷,Ŋë5Įn‹ũūĩíüÁ?ŽūčČė6Ö­Ö,�āˇķ=�Ÿ˙ēŽĪž0�ŗ}"ÆvēIņ]đŪŅßūūÃŧ×?ŌĨŋŠCuE¨x@îíŧa SË+õõ;¯¨Ž0įõO6gîôŲ1húī:ø˙\ũ˜Áė› å^]g'Į Ū7îœ:į› e[wúŸę cÁķ°¯Äxˇ—^ŋsķ5ĮîĻģrĮîŠ×÷dX7û.Ģۜw"?íŨũcžecŲâËžŸ›69Ų:Æõ_™¯Ŋ6<aúĸ_×ŊëūÖ§ž?¤ŨUāŧ­˙Ēw4īøxčOīT”m^íĐRøđ˙ŽĘęĒŨdBwöķũîÛú’˙Y�ļdîÚūĮŪëwnúėx§.õÉ3ÅõÕdeЏZxšė’ę7yfLí¸´§mĮÚA&#nņŠ>f‡ę@^ų…ŧ’ißĒvį›šļ2m´ÔŸĪõkifayrránšĖÍ€´ˇcnEÄŪøOĄáJĄvYėĩT{a¤—…ékĢĒ#‡5™å@åũXذq?jo�Đjw+Ģ]]Z8�Œ§cä+qß^ŽŊĀ^)*?°ûrĖu›ŠũmīIŲ¨×ūÔā*ĶŸŽGøûp‹ŲĢ!×’…ëJĪߨēž_ž}åíßÛ´ íi+žģ#rOčÎæW^ŋaHŨwcôäÂŨų@Ek� ÆŌ;Ն{{cB‡ļ(:¤^–¤ŋ˜oHŨŖ^}ˆEk �0˜3<v×ZŨŲ<Ãõ 3ߥvîˇtŸ¯ĐžÍ¯ŧžWļfeņˇV#웿¯÷mŲVY˛zŸūĘíĘ+ŋĮėĢä×tÎ jƒęš{Ɲ܍ŧō[ņÜlû­{ÕŦ€0g eÛ÷čo¨¨HL4ĀqˇzXĩš{Ž8ĨpÃo†+7*/ūVŧëˇ*Wi‹ö vąpwąÔåč’r*Žß¨ŧøë­™SŽ-<qßhē%3"Ē]/ƒvõžûž§ĀĖ fĻ›€g;đ}C%oīŊđŗiL3^úAúõĖU§ˇ\ÕĒN¤¯Hß<đĻßŧƒ;ŊōËŋ/$\*É˙âˇoŽ•Ūŧŋķέ;ąU†øÜ#ųēkįÔ9“-Œ>õ%€ÛŦ@YEy ĢkieķĄĮ ¯ĪíŲ1ņĒVĩ;û§}ĮK?¨éˆōJvEúfÅÍŦĒÆ&9äPÜĖpęÚŲ|ŨĩnÂ.�ūéüš§Ŋûܔ•Š×2”ÅWŸZ¯*Ŋ1ŌķŨ|Ũĩ]ákŨ�´mŅēc+§Ŋ~Ž‹ą×ģ˙OõÛ=ũį•ä‡ėX&.¯äęīš [Ī˙ iãÖÆÚĀûâ`}%;'eEæÍŦ¤+ŋ,IŨĀŗ°zHûƒĘ*ĘgXráVŪ…[y˙úo´ĨšeHį˙{ä˙‘•utę—å•zļĘpāĪ$7ģŽÖŒžŠÕWĒÆÛl‰ĄęY|k{q}R@UōĪJI’ĨTÜ*ČKÔÅ.rŒ}䐒ˆ5ĘØ|ŖĢ‹]Y™ĸ°vßBͨX�€#€ĘÔ uW4*.—ÁÚĘ °”‰ŦqĩđDŨ­îˆÆ|ǏN—@̍žâ #Ę ĩWEŒúōj´2ˇ\]lė*îšZ÷÷/›šWá(jéŠŌ{ÖEšBkÁĀöxžĀLJČB—W~w~ė6{žĐŧŸÄ IúÄėĒ:,vŅ&ũZv&§ōzÎŖ~[3îŽÕW˛ë&æ*“Ö&5%åëĪ×lątwŗ`sĘrëvĖ×˙~Ģm7‰%Ιšģ˜ërØēzŽ+õ×`kęÜķäîŅ׍xn+ģÎkõĒ’Î�›ĪæÖn2Ē‹{ÆV÷0”Âu°ũtoĻ}ks†gÆãY ßŦá!•g~e'ŽiˇŅPĸėlĻáz>{ņž^l¯pxũvɔ)ˇÎŪÁ30ĸëģ#ēž[wˇŦR˙īœ„˜´¯�˜ÁĖSØeŨ™­u[͝+Ė`&iãvãŽÚĩ•ķžœCu›’.§ôėđę=§^Ë0;ß^÷ã…Ã˙SQ••ßžgļ˙°2ˇúĨāt]KÚ5Åû]‚m,[”U–ȸņ,į–˛îëļTĀđH…CUEŨ5?#Œŋž“´ũ€S×ΞęčųÍī{äŽŌ,ÍÅ_¯YáĀ…ßŪąĨđ÷ÆØŠ2g~ûŪŊ"pjaimen ĀŽÜŌwŗīōGŅESŪ8đgԁ?“�<¨ŊĢŊûyMnŨĮ˛Ęōŧ’|SUwĨDÅÖ•–-ЊáëËč’üķÖ b �PY™yž(ķ|Ņ2 •¨ŨîO\žmģ…ÆŽ•*ŒúƏ16ļ\ÁÜÎxÅĩø×ú­úëLŨQuʀŠģkŗëÎb×ĘVmöĮļiĐq ãÜŋŧŋ.ēžg†0ŗåÁPjŦPFƒļļf@eÂUiˆ]hß֋C…LŠáÄuŒi¸ķ <3>p­ĸņuęėŨŗ˜ˇm Ļwû_z7ØĄČÁ0ÚÚÂ`¨÷äŪŠ=ŠgÎÜgvJŸY˙ ęËļĩŸëķ×ūāĩŽrZāÆ~šōF’˛ŠēMpYárīN÷\]h7r�ú’ļ|Cåī'nĮlÔ^ŧW!3zŨž=zFņÅgĩäåōØˇŋ˙`úzŠ˙,MųíE§Ö™îļ°´ļ4ŗ˜ÜcÔ'^#ë"´ikkÕŌĘÜōNEY]cą^{į—Jōß˙iâGŨÃfČĮ-f™7ŗZwNŨāUļV-|7`MŨ|¤™™�{›6Wĩ*�ēŠ‡å7[Ųā˙Å f�ly-yVįG%Öĩ[˜[hĘnøUuæŗž“�ČÛKOž;§žĐÎĻcKĄˇŖôZéËڂ{úī/zķ‹>ķŋĘØņų¯ët†;¯9v[eÚ$`ø×ƒ>¨Ũ–×ŌtšąÎCYK+›‡<:ũũƒ-úÔßĻĀųcZXÚUTŪ¨7ãS’w3*Ã1ÕËF –TÁĘÂÚxÜÕÅe@NžlGIũđĶ—XĀúG5P\R…˛âđč‚úëŊôU…Ø˙9˜IuŠ<[3ŪŨ0ŗåĄ´Ô�É{ÔÉ{ĀkÍ{Ŋ_ÛicÚ/¸uuJԃ—ĮĒu0gZš˙ܤęĸ;Đũīæčoë?ˇFCi`UZ ¯ŪæüģãĖÆü˜ßę_Ū1ę ŸčęZÛ đļ8ŗâæößLĮ VvR}1ųÖŧä[°˛ôėŲjâá:^Å %å5ĪUKÆĶĄōĐē’g–a�ŠĘ‹Īkjūėųü×ußøâmˇĀƒĘŖ�Ę+õՕÛÎīũņÂáú‡hĘoĒ+�´°ŧû­ÚŠáŖ1nåÍ8mnfŪÃĄÛtīą[ū/捝īÕßAWQ ā_˙ÎŊuŠ~{ŖIđ˜t†RļĘ0(ūŖúĻ9ÉS×Îļļnå*pöé [uúkC•áŧ&÷5ĮnŪíģ˙OuæūŽŪ8uíėgž5ŨĩŽweü–žØ–×H=¨ŊÔPjĘė:ļ<››e�÷ŧJĪú#}Ō48~mŦmģ_JôŊįĒ“Ĩ¸­JØBār~YąMīē+ŽöûæücĘ}|×SŠČĶÃŪÚZÃ^(4Ũ ÅU…Úŋđē Ëųe…6<GÔõĀ^.3—TžPs •gōĒøĸëŦŨĢsŗ hÉķíɘF’†Û†ä=šxĨyGIíß;ūßfs á.ej7ZôáôÍhëûö­ĖUVō-_qĨæVŠ3TŨ6•š7ĒųnL]=í%Ömk;?í]ĖŽ×UXm(­ēõ8rĩ<sĒ‹îÔÆmë–ÁŌû,:÷´élúŊVQy>šhCbE[æîõŗ;eŸČ_öÛsûđíĶ…™‡”Įæž>Ņ”IF˙Đä:ķķJޚnųēkÕZCŠĄĘpũÎÍîBqŨą}]ũîīP*”ČÚy�¨6VŸ.ĖüâˇoZ[ˇÚÔΘ@v‘ŌPUŅļEëēŗÜfKné‹MKûžĖ9ucÁ373¯ëS_ÅŪQ¸Ĩ/žp+/Đõ QĢWÎūāLáīŪŽŌF/Œ`,xÅėŨæ@ˇ@Ԏ…˛Šū”ļ“đj#gĐ?ú~?`­ĖÔūģú‚§°‹iZ�Ÿgëf÷Ęīę �J wZX2–f5—ÆÅ1ĶhBƒ˛į‰ã1VTyB/ŌåÔđvCģ´”ē´ėåŲvéÄ.›ēUí?\t`snnēΛ:ú•ĄĸRQëĨ݂lôŠ}õnæ‰Â6mãF´õq´thÛâ˙ŪrSD‹cDáے=ķ‹Ģ֑Ŗ‡ŠxžÔËéHt×÷fíķcŪ^ŌÂ÷ĩģˇWŖ<ĪîÖ^vDFØtv°hãŌbČÖŨ uÛNTÃļÅGsÚ¯ˆ°õtąlã`åŲ×n€KUîo�J Õp°y]lÕąõ=O‡a÷>ŊmoûųÁ֝EL¯ĄíĻ÷ŗŧ~Îpߌ‹15ž$×Ån~„­§ƒE^¯ŅíøĻC¨ €ęS‰å:ˇV͇ļčč`Õšg›ųũĖuĩĮī)į÷kŲ—éØÚĸŊ¨Åˆ(į¯mãiõˆ‡Ũxĩ7ØÜ[æ¯ <[[´ŲN›#0dā`íî`^/ō,úvX7ĮޗȞ}kˎŨ#zZeëī~Û´ļ™8G*~žŋĒbŌ62ŧŲōĶŨ¯Īíîįę7NúĢĀYŌæ+zĪŲ=đ˖V-�üœwâ-Qīūĸ7]øí'ŊîØŌūūŪü\ä›úE˙ŸĢŋ ŋŊ¤Í?Fxž[ +ŧVzÃPeĐWąōö2I›°•ė“{Œę/zĶ™ī(o/‹ëŋjYīO˙ÎŖøUu&ĢčâĒ7įz;Jl¸õ90xË0ÁĻ­§ŽŪu°˛øĒ)Ÿ~+üŊ—‹ ŋũ)ÕŲûģĘŧ™õO§×¤BI[‡…˙œvŗŦ€§}Ƃˇ;û'KsËUoÎõj×5 ã?gųD(‹¯a|Pû÷YZX2Kügš œŨ[wZũæŧRC™éāį5šf03­ĪėÔĘe˜Į;ķ0KXĐĻÍkŽŨ:Ø:ü§‹<&ŽO*V%īČé­lŲģŨĻ2vVЗą9J]ärUÍë´*Ë?]ŽÄp§/f Q™“SēF•öđ Æĸĸw–#&ÔņH”Ģ* ¯—ÆÅæFåũĨŋŧ˗-ĪՏpŽ™ÖÕÕڞ#.†m˛Á˜Eī z×oČÖ šRr=īVDTuäáĻKžĄ2÷´væĘ[g+€%3ŖĖ"Į´Y;ŠĪĢ.ēʞÚx}õ¯Õ�r•œéŲ&rĶ™ĩ<‹×÷]Ÿ�ûiCŋu43˛‰+Ž­ų­ē‘ŋ’ōŠ#f#'´Y; æÍœļĖ)ڝ�ĨÉꙎí"‡:ū0Üx-[ˇfcɈ%v5'F ÍôĄíŋ›iŌĘ+™ēy3oŸԘ ~ĩ1w›õ–Üî0Ŗõˇ{ÚčŽęã7ŨøüFËļ+ÚFol7wtŨßö†-sođ&ˇ‰\×ļ-ė-ÃŲ_oFlÔßMe[ŪëŊm‹ūW´ûéŋyĮƒÜ,+úōėöŲ>û˙LJŋŽHŧœ2ãDô8éSz„ë wÎŪøcøĄiw*ĘŦ=ŗUhĶf‰˙,ļ’ũéĪŖ߯ė=ĮĐpŊæÆŒīŦĖ-gûLphŲÖtøG˙™mÚĢØ9N‰ĸ°�� �IDATöæ+¯îųpięW:Cé,ųxĄM[Mų­cW~]uzËßyÕÆęŅ?Ίô™đeßĪm,­ t×7dlĢ}eÕ¯Ē3Ŗ<ßÛyõ€éîŲįÛÛļË.úķ6[rWßŊ"pŠëŋęNEŲÎnwhií7ŗĘXuHy|ĖĪŗfųDl^]Ŧ×Vū×TváuŖíųēká‡gĖ”?øî7•ÕUgnüū᥊ˇõ%�˛Š.Ž:Ŋå“WGÎōáÖĨĪ˙ˇö@ČŗG]�;¤<â´­˙ĒXÅÎugãūÎ3F‡Ų=/(yb—/_vtŧīĩQáÄes33ŗ Å˙¯ņI|ŌƒîûŌã|ŊÖŖ(ĢŠK!+h4kfųĖį?,Í,øŒ­éˇ0€Ŋ†ėúŽĪw5Œ MÂXYũ{øŖß`åTXXčęęÚÔUp}R‘ŌĐxŲ‡Į‡î ęÔ˅ß>°ã#ģžģ7—Ū ‘4g\ŸT$„4°Iņ=cÁ‹ô™`ßĸMáõžœC_floęĸy†(ÆiVnjUĢûzõo_7u!„<'4ŠH!„Ã(Æ!„pÅ!„Ŗ#„Âac„B8ŒbŒB‡5}Œ™ŪŲÅ �žÛ;û4ôūŖ„pœŅh47kúßÜÖôO_Kž€P` ú¤TBČËÆaËļM]ˇ5}Œĩļ€yCē˜UPeĀSzGBy‘FcĩF|Ų'ĒŠkáļĻ'~uaŠ…{ÛĩŖĨ‹ŧpĢôNe5%ižŒ•˙`Qō133kcmˇ. JÜæq?ƌ4Ēéßá@Š™7,čü¯jÅeŽôŦBšÆ ō÷M?`˃ŧCՍ;fÅz3 ŗĮĮo˛Oâ$„Å c�Ŧ,ĐžeĨyé Ŋ^_UUÕÔåpCĩ5M]yŽ,,,Ŧ­­,,,šēB^/JŒUUU]ŊzĩM›6;v¤QBUUUĨŅhōķķ_yåsķĻ_ŸEȋāE‰1ĩZ- ííí›ēB^\fffĻ]ģvM]!/„åēōōōÖ­[7u„p@ÛļmËĘƚē B^/JŒUVVŌ\"!ÃÂÂĸĸĸĸŠĢ äEņĸÄ!„ō(Æ!„pØS‹1KKËęjzÍ!„ŧĒĢĢ--_ˆE‚O-ÆŦ­­õzũĶęBȋŦŧŧŧE‹M]đܡiĶFĨRFĶg¯üUfķžV-„4sį†6uä%V]]]VVĻ×ë;tčĐÔĩ�O1ÆĖÍÍ;tčPTT¤Ņhžām8ÔjõĶĒ„fO­VķųüĻŽ‚ŧ¤,,,†ißžũ ōü§9ŗiaaņÄ/É4čÃÆy\ÎÎÎ/Â[˛ō"x!˛”By2c„B8ŒbŒB‡QŒBá0Š1B!F1F!„Ã(Æ!„pØ ņŽXOŨĐ°ÛŖŪũ \.ÄĻãXvąÉJz”ˆŠ1ˆ-męJ!äEÕ<c �n#4…��kkõDĖH¸~ƒ —š¸.B!OQķ1=—pĄöŪ.Sņ:"/Ą¤)Ë"„ō4Ŋ4ׯ*‘zÖ­á�h匭ãq{Œ‹q}"–vŽŨÍC"{6Œ‹Q>˙ˆ.–lwđBų\ ĒũK ×@cļ}Í]ĶÖ˙ŗ,1¨?2fŖ|1nĪÆž~čX{ļŗņß×1eĘ`¤5� ˆKsQž�īŖ—õķxb!„Ķ^š\[C¯C!�kl‰ Ū‰…눸„Š`ŧ�Hû`ˇ›ū וđ‡^‚#ũØ~ãr,ŅÛąĻ˙ N(Ô!ČĨæŽogāR+ņąß Gâ!ŽAīŸāč#ũÁ��ô•p•#čzĮbŋ]ü°Û G~‚x"."Ē(Č!äášī¤b=Œ5z{#˛=öG �="cR\ŅĀ•ã8áw\[ ą#pq—P\)Fh,\āíÅ8Q _ �°…¯-âN#ĸ3˜ °@o¤žD‰-ĻvCj">Ŋ�W˛q o%â@%�¸ęŅ;W��‘¯BŸŠŋƒŽd ĒN´~ūĪ!„pIķąöČY\īn9ö@DvÍŊbkÄôƒ¯ŽÖ°ļ„ĩR- õwŋ‹īcĶī8r WŠ‘‰‡ĩīŋ„ˆNhu zČ4ˆ¸ˆĐȀ4;ôļÅîK€2+ėÎŋ[HÎ%čũák‡�¸|Ŋ&Ã` ąrŌÁÖŧúŦžBišoŒŠņÎ\�č+Q¨Šˇ˛ÃGÆĀ1á{S X"nFÍôŨ• øęŲQīc“r˛ņ’KØŽ¸}Č�tFq>.äCa _;ä¸@\Š@; °ŪĮbŗ•(Ŧk§ ī~bļ%ėŦ ¯ŧģ§^ú8mBy¸æc•Č)ŧģRąž.øVâx$›2ÃļÁ%¨ Ų• XBÚ1ũq$ŽÛqãA헠°†¯=ā‚Ôã@%Žh䂜Î(ž�=ŠĮz'`Ŧī ļē‚õ°Ģ÷bmMׯ!ä^ĸ%uŦų@% kĮ=ģÁ5.í [�@%2ŗ15ÖB¸>¸Ĩ8ĸAo ‚ėq$�RķáÛA.HŊĀu(*āër÷ėâN°.‡ĸøž˛*‘S ąKÍę˜‰By¨—1Æ._B1SŊā` /ėî„:¸ēĀÁŊûāȇꌎļčâŒČWQ|9x`;€—āÛ2 RK į"Ŧ;#ÔûM¯ŗÖ#&ž}0ģ3:ÚB*Aœ7§qĸ˛‘Ââ~‡_tC;ôōF” M*BČ#4ßIÅ+ÉFx:žˆB@qņ€?ŽøCQ‰ößÃz bFÂĩô:¤^DĐa”�kĐ@qč }vÍ&{ kôŽM5�ɇņN%bBÃGņm9ŽŠ)w×qԗyáļˆ A ČFÄaė˙š<#„ÂYfFŖąŠk�€ÜÜÜ.ÛŨ›ē B¸áˆ\wwúy!x9' !„4c„B8ŒbŒB‡QŒBá0Š1B!F1F!„Ã(Æ!„pÅ!„Ŗ#„Âa/ЛQ?zB€ÜÜĻŽ€Æ!„pÅ!„Ŗ#„Âac„B8ŒbŒB‡QŒBá0Š1B!öŊnėfQqS—@!„c^ k×ÖŽŠK „Š‹n6u „ŧ(hR‘B‡QŒBá0Š1B!F1F!„Ã(Æ!„pÅ!„Ŗ#„Âac„B8ŒbŒB‡QŒBá0Š1B!F1F!„Ã(Æ!„pÅ!„Ŗ#„Âac„B8ŒbŒB‡5ŋS}ûž‡hØuŊ&6mŽÜ]ļĢA[ōTQĪ¨Ô‡ô¤\čî?OņlĘTn v÷Ÿ™öl:'„—Fķ‹1'_ŸNP¤¤jī6)S250d¤œcīļåœĖÔņå^Īĩļôyo†~[đ\OI!Í[ķ‹1xøyÛÎĖŽË,UjšJ"÷f)u;)ĶSU<¯žRæyVV“Ĩ~ô^„B_3Œ1Čü|ųšÔŖy5wĩ™'ŗíŊFö÷О>™]ĶĻÎüE q_ŠĀ´ÃˇSCũzČDî2ųĀ}›Ö0jÔ)ĮË==Ä=‚ÃŖSjˇąĘø¨° ąģ‡ČĶ'pXÔAeMjąŠzĖIŽ;ŧ`{ˆģĪä“@Z”ŧOŒÂpnqņĮ‡M{3ÚĖČ==D=C控)ˇ{/OKŲ8&XŪC&î2û°ōY<Q„Â}Í1ÆoO•vڔ ŦâX#õ÷éîåŦJU¨Lm's ĸ�_g�y‡\žãņUüÉãģû¨ÖŽŋņnh°‰Ģ6¨‚ÄØ7ŠSÖļI“wŠ�@ą:<ōgFĮ˙œtō‡•ī2Į§LܐõđĒ|>MØđâ™RĶ×ôg�€M^ŋZáw`īÎâ‚ŨsæÅ×΄T?FīĖØ•~F‘ūM€vߜEņڇôM!/­æcøú‰‘÷‹éōXVJ&+yÃK ņ— ˛Žfj ķdēÎÉOî°'ŋŪ”c?$fɇ>"ggIŋšK"D9[7§×ÎHęŋO‡Č=Ü$žá ĻȑŦ�€dl܁]ą3ü<ܜœ%~FžÁĪKO}ÄE/FČ0�á Lmįū‘+L‡}ôļ“!KqŠvgƒsČô%�Ų;ũDõ7BšĢYÆœũä"CæÉ4ČKM͏ųI…€—Ow(~É`å/Šž—Ÿ@AvŽŽ'õ—Ô]#yÉė5Ų™ĩ‘Tŗ�@č!ą‡*§��#@ŪūyÃBßô—÷ô‘N;ŦƒeņņŊä’Ú¯íø`īvÁw9Õ~-°įÕßD!ä.ËĻ.āŲp đuú:1%2ÕÉ<{_�FîįÁn9™EfĪ{ŠŒ Õhaø9ĸÛĪ ˇWŠ7�`ĖŨU Ã@˲€:~RHdŽ×ŋĸ׋…  ˆ œøŖ%\aōM„BîjĻ1q_)§"3K‘“ÅH#Lcaw_gMjZ^Fz&OöН��öđÅË˙=ŊūĘ{†á kžl0 bY>urü/:yôŠņ~ĻŨXĐX‰BšFķœT¯žR^Ūé)9Ŧė ¯š‘Ä_.PĻīIʆ›Ÿˇ)œ%b{Ũ55Dnn57g# kĮBēŦėڏP+ŗ5IÜ`вāŲ ĩí‰ûNęNĖ0`ĩu௿e6\dh�!„§§šÆō�/¤L¸ä&—׿ ŧ|ēŗiûU5͌�˙áŖÄy›"—T¨ÔjUVŌęđ ā°¸ēčâŠ÷Å,?™§V̞âcÖĻÃ?¤N{CʞījuAöÁč;yŪ"¨9*- ĄDėd8ˇ÷¨ �[plŅæĖz5ņPLÉT*Õ4z#„§ĸŲÆ„Ūū"ƒN×Á×§n­šŸ‡N§ãËũë–V@2áģmSœ2— öųgpؒtá¤ÍqãE�`�xNCæ ĮļIoö ‰Îqˇ~Eˆ€īŒ•˛ŧĩī÷ō4c/3vŨ˛éŖäLō´™GĩM\1NŦœ,öô œvL6cĸ/¯6ŗ$CGəÔa! ŽĶË  !äŠ03M]�äææēģģ7u„pũŧR§ųŽÆ!„ŧ(Æ!„pÅ!„Ŗ#„Âac„B8ŒbŒB‡QŒBá0Š1B!F1F!„Ã(Æ!„pÅ!„Ŗ#„Âac„B8ŒbŒB‡QŒBá0Š1B!F1F!„Ã(Æ!„pÅ!„kÆ1ĻÍJX=yX°ŧ‡Lä)“ž2~ūž õŗ;›øąL4lŸöŲËRįû‹nQ6u„æ§šÆ˜ęāԐĶö(íĻ|ž~ëWĢ> Ģ.|wĐČoŗŲ§w6ņcŸđSr1#,'ež^ī„BɞРx& v͙yØĐwM|l°SM“°ˇæŊ?~ņ´Õ^>õz:i“—–Í"¸æŽŗĪā!OĨWB!­YŽÆ˛wn; ų´Åuf"đûlÎ[üŧũ[O˛�ÔģFŠ{F%*ļO(õ”‰{†LŪU7Rc•IKÃüÅîâÁaķ+ÂīųîV•áä4_Q9Šõ'•[‚=ƒ—Ÿ<ļhXMĪ3ō´Ų{&ŋ(õ”IƒÆmTÔM=Ē“c? îé#v—Iß š+ģŅ9ɂ]Ąâžsž\ä/õ”Iß ™W[‘*1z\pOąģ‡¸G`Čė=YuĨ[>&DŪÃCTĶyĻöílâTņ°}ĩsŽŲ˃<D=ŖRkŸÄŠ>â1‡ĩ�Ô)?ņë!šËäAãՕĄÜčŧ<ißä ™øŊí�Ô)ËĮK==Ä=ƒĮĮĻ<ÃŲ\BČKÎøb¸páÂSë+ÛāΒН7Ų¤?:ŽĢäÅ ŖŅX˛÷Ŗ.]ũ&l>uĶh4ę˙Ü:´KįĄßäFcIâtīÎōÁ+Žū‘_đgęîqŊĨŪSŽ–ÜßYū%Ą; JJôFŖū?¤>Œ/1nîßYę=dÁOųzŖąäÄŦ€N]F¯:Ub4 ~-īôöæ?FŖQvq˙N]Ī=¤ČĪ/8ģ÷Ķ€Žō‘{ î¯úæÎ:ËßŊųl‰Ņh,ųcëˆîû/Ë2Æ?7 îŌuđÜDş5ĨJŧgĨéFŖąā›!ŌîŽ:‘UŸ¯<ĩsz@WŋIÉúĩ—ėũ¨ËĢĶOčMįÛúǟ÷ëƒŋúĶt~ÅįŊĨƒˇõŠe˙'íōö‚Ÿ2 næ+OlúČģŗß¤ÄcÍCö xûŖĪ)ūøķĻŪxķ‡ĨzO˙!Ŗāf~֏x]Zû¨ÉSđ4^á¸æ8͍Ôā „lb:8 ĄViLcƒ×kÜGžB�Œ[đ`ä(˛Y@õãæŸĩōČØÎNn>CWĖyƒ=ēåĮ‚û:ōÁ#ā ÷ĪQœC&žíĖ�‚^ÁRžAį1rĸ¯�€S¯@1ō2•, =ļv×%ɤ•‹ƒĨÎÎN^! ‡đOnŪ“ÕøŖbzMúČK�@ā6ļ˙ŌÁøl�Î!ëãÄ~ÖWęæėäæ34"°ƒ&ũ˜�TŲyįĀĄŊ$NÎÎ"ß°%q?ŦŸ"cÔ.ŋáÁfžĖ�V‘’%zgˆD•fŋ¤§Ē|ũœØ´íßį9^õļĖIč,ę5>zŠT“¸íXíHK˙iŸK=܄ŒúøÎtøOZ0Dæ$t–ô›ŲO`øk˙‰„ōxšcŒ™4ž’Ŗá/SžH&Šũšá �­`sŌ˛áæ'­‹AĖĪГö×ֆđŨęr”á ā$qlj:€áÁĀČKĪ2tđõÕUāå'æåe>`9Ĩ¸ŪŽ"‰jÕ%`(8>0د§ŋŧ‡OøŽk`Y�Ä}ũėŗWŽŊũ`ZžŒŗDę&xpģŗˇ¯ŗ&CĄ‘rN  wĘJËdmúiĨĐ읔é9:~Ŋ2 ôu0äeÖŽ?äyHkˇåeĀŪM"¨ŨSä#æ˙•gBWsŒ1'‘3 *U#›XMB'ûÚҝą}´ŦŲĢˆÜ=jn˙\˜ƒVķ—ÖŌ3xä*­ŽÅĩ­ī{ԝH2ņ¸mŖįáņÁŨ; ˲Đ&Ī‹ØŦōŋ$îß{ŽŠ ëPģ ß˛];gxŗi_Ī>@Ö#0|eJÁÃÚ%ūr2å‹ŧ ëå'öuGæi%،´LFā°Z ŸŠ÷¸†VWīĻnTĒeĩ`ėyõvldĀJ!OAs\Š(ôöáۄcá#œnaĶgė{Šv8#đ [7ØžA̰ąYĘŋCĀgС߆Íĸz #tnlgƒAĢj’L§Õ‚į,`ØĶ{4’IqŸ×tĄdë 7'ßđ(ßđ(V›—ŋaŅĘIãíãÂEj÷ōëŽ%)Yš“yâwe ÷RoĪ(Pe˛^“¤ 0`u,‹ē„fĩ,üFJĀ0`5wkaĩ‡3!„ü]Íq4ҍqo0™æíĘkĐŦM_´äg­xx„ĪCGŒX&áčœŨDn57{†ą °˙ŋM$÷âi t|ˇē Fāô€ķԛÕd/)Tpu`XV „ĩķulĘŪŖššŠ´yŠIéĻËyŒ@Ô+|A„AЏôĀv€‘x¨Ī%%¤d‰Ūđ�ŠčŌɄôÔi_š�€ŗ\Ė×å¤Ũ}RUŲ×xŠĮũÅ:‰Ũ QŪ]w™“–Iׯ!ĪDŗŒ1CĸWôį§. žēúû„”ä“Į~Œ‹ ߊ}cņšÜq´ĶqĖŅĨ“ãŌ•ju2ũûŠaīĪInd8Á�eÚ/YJՓŒ5ŖBė3VÎXž”] V+ûæ žļīAkĶS×/=˜­RĢŗF¯NfÅī–@ ö!c׎ÔĩZ™˛üã¯Yywh/e)ĩ,›ŗ5r\øė}ŠJ•ē /#iÃNĪËGŒĩĘûŠō~ÜvŽ‘IŨ�ĀÉKÆĪØļ#Kô†¯�ŸąŠTßĪ_š˜­RĢķ’cįŦÍė0d\˙Fr×9ā])NŽ_ø}Z^2ķāüÕÉ,]#„<ÍqR�„ũžˆīûõϏckįo×čĀwûGī4ØëAƒĒz}—ėZļzŅúÁK4žŊČį­;Ļ÷ē˙@æÂģ'ož’ŌgőUũâãûųļXÁŌå ÂŋÕčĀīäøiÜÜÁĪ]ōŧ§L%Í›™Ŗeœ¤C–-™ā@4zY¤bچđ>_3NŪoĪX˛BžÉfÎYüū$æĀļßhæEo?hŽÎĀã;‰{ÛüY˜āô€v�Nž>ö‹ˇiúúIMįôKŲm? ƒå5ÁĪHfíXĪ_°fŪ°Ī^üƇļÍj|hëôášUy‘K—°ˆéäųYHĖøŖēŋü BČŖ˜ÆĻŽ�rssŨŨŨ›ēŠ”z×HŸhûMįWõkęJČ ‚~^ŠĶ<' !„ŧ$(Æ!„pXsŊ6ÖŦÃļå…5u„ōBĸŅ!„Ŗ#„Âac„B8ŒbŒB‡QŒBá0Š1B!F1F!„Ã(Æ!„pÅ!„Ŗ#„Âac„B8ŒbŒB‡QŒBá0Š1B!F1F!„Ã(Æ!„pX3ŒąäŠ2‘ģGŖˇ°x5>ŗ‡‡_tæŽÖfėŠ (õôšË¤=ƒÃĻnIV?ødŦ*1vNXŋÔĶCäé#8n^\úCv<éķzzĮæÕÔ;ŌĪĶCÔsNrƒvB!@ŗüôg¯I›wÖ|VōĨĶ&ŠĻÅN’2��H\zđĄlÆĘ°°8ôš4=Ö§“1¨ķŽmZšfÔûĒGĸ|™ûv×Ļ/6nkžŊØØÅ~Ŧ&ë螭KÂSėúf¨Û_Ģ:}Ū›ĢE;vvĐéŨšŅū"{�`OoZÁ+ķvuíOĸū)!¤™h†1&p“ûÖd˙¤�Œ“¸—üņÍÜIļ+vŧÔtßM"õrBXdfF6ë+ģ'Į´ÉŅ3￉gū°m‚¤fS¯žƒ‡ū+xâŌEņ}âB„Ąč‚œ,5D5w„^ÁƒkOĸŅxn €ģíO Á)!¤™h†“ŠĢeīmbdĶãl›po†ęc›4ĸ°uf"ėģ`ׁ„u5Æ*“–†ų‹Ũ=Ä=‚ÃæVÖôĪ&~,ŧ/ye¨ÜĶgō–(yŸ…áÜâ>âŗu“‡isä˙\˜CŌD/‘įŋî™TÔ*öĖ|/PęyoĪĘø¨° ąģ‡ČĶ'pXÔA͆´{N@ûIpOąģLúfčĖ]ŲÚ§ö4BČķC1VŸt€_ĩë_á+g<âˇ:Ģø%ÃĐÁ7Xr߁›ÄI��Đ&Í ›¸_¸ ūxRÂW#)sÂfĶ�Ãđ`ČūzSA˙ßÅÍúi†ˇøĪ<šžĻ˙ŨTôYpôįHx}c’ŋ.éU˙${Æ[š%™ûÃĄø5ī0Gg„ÍOŅPŦüŅņ?'üaåģĖņ)7dđšįlFtø¨õ×ŧæn>z<!n’(#:|rŧęo>}„ōü5ÃIÅŋAĐīķ¸E˜ģ|ķŒw7ƒg/ö’{úļŋHpߎZÆ�{ÉÃ.TŠ~ÜüŗV;#@ĀyčŠ9)~ĶļüXPsuJíôÁį#z™ēf€Įđ‚C;F `�|Ą aÛļ¤ ‡îũ|°�‰hņ\Õĸ“5 Œ;0T(1ė4a䛯ϧĀÙÖ?…öØÚ]—$“-pv^°X‘ūÁæ=Y!Ķ=žøÉ#„Ļ@Ŗą†’ŋˆĪüíĐÖ5‘Ęíĩé{>;ĀīŊÕ÷Í�Ŧ‡ÍIˆ›Ÿ´î™@æįaČIËŽ=Häíq<>uVö5žHZˇŠÄ98*vŲ`7�Œ�yûį |Ķ_ŪĶG:í°öū*ķŌŗ |}ꮔ1^~b^^fÆß^dI!ĪÆ#õ õ h•ņsÂ#ŋžˇë„ņ –G0öN|ü’­´đÕ˛d¯ ZÕ ŲGŖ„�Āž0Å``ĩ€3s˙áęøI!‘9^˙Š^,2€"&pbc+3ĩ:×ļžīąĩAk'mmi„Âc h Ԍŗ°ŪĞĀ-dúqĮ×f^b!Ē?áĮHŧŊxû“ãSXŋ†Ë?´Šq{Ô~Cßvx„Į­\æ‘˙~PđX­h˜dęäø_tōčãũjV˜ ‘‘�ø :ôÛ°9ĸ~43ŒÖâB¸†&īŌ&Œ“÷ [¤hø›_{)[Ą“ũŊK…Ŗ‚í5ûbl0á¨NZ8yÉ×{3Y0b™„WP svšÕÜėÆ^øĀ!˜áą+zH: ûtFmĨę„9!ī­Ne Z<ûēAž:qß醝ÖŪÉŊxšß­Ž6!ÜžttH!Mæ%iU™Éõㇸɤ΁ĶψÃVŒ cĮāã$ÔĒôCą_bŊ”Ūׇ ×ܕy“65xøb!4YG÷lŨ—à ^ĩ"D`ȸ€MĶ–NŽãĪ îÄh/%¯_¸(]{dU¯ûãBĀ@u2%ŗ—ŧƒķcŧpÚcäŲŽÕ‹fK™ņŨ™‚_ÖFī/Įz0NØö|¯čūļŊ&u[ĖNžˇ§9*­ŗ“ Á)F…؇¯œąœŲŗ ��žIDATųĖžUũ˛uūƒÂGŋLsŠ„nyIcLw4fÔŅú Ũįß=ÚY2á‡xįõļŧ^Ŗ3đøöNōą›ÖŒč×čl›@>ëģxŸ¸ ›ö혡OcāņDŪo¯Ų5%XbĘ)Aß%ģ–­^´~Fđg/ōykŎéd�ÉĐQōãË„…Č$|×éŅĀyDÜw˜Ŋeü{×XσWpôŽš~ĀwÆĘ՜ĩī÷ZÎīäšn™Sĸ&|Ņ´™k’bëŸb¨īįÛbK—/˙VŖŋ“Wā§qs)Ã!Ücf4›ē�ČÍÍuwwoę*áúy!¤]#„Âac„B8ŒbŒB‡QŒBá0Š1B!F1F!„Ã(Æ!„pÅ!„Ŗ#„Âac„B8ŒbŒB‡QŒBá0Š1B!F1F!„Ã(Æ!„pÅ!„Ŗ#„Âac„B8ŒbŒB‡5Ëc~,š{Ü|Œ}6gLüX&ļO @š:ĐŨžâYœ%oã@ųüôgŅ5!„p”eSđĖ8Ŋŗ<æį{íÅĖ39ã1rÁrVúl:'„ō@Í7Æøŧ|änĪëlÎ>ƒ‡<¯sBŠĶ,'˙ĒĖy=eaqé?Îõë!{†DSĢĶ7~"ī!÷ ™ŧ+ģv*R•=.¸§ØŨCÜ#0döžŦš õ&Bžqj¨_™ČĶ?pĖęä‚ēö”‡øõ‰Üeō q‹ōęf>ĩŠíãú‹ŨeŌ ‘‹’T:ÃŨδŲûæ –zzÜÛ!„ŧLšsŒą÷xđž cČÚļ!;xũŅ3Š”ebåļ9!cļ3“âŌΤ%Œä%FĮT€2vŌä]¯…›Ž'%|õ‘0}aøüôĮŊØÆf/3n“FūŲ7ģ’ž[ĐKģgüØÕY,Āf.>imA÷Yß$¤_1§…ĪLŌ€öØŧc2„cã~NJØ0Ö~wĖęÚŪ öM67‘ygŨ¤“ĸße÷ģ4ãŲ\÷#„Yķąœ5ēyIęßÜe?pwV2tŠŋ„~^ĐÁįŖŅ8.)ō�Ā9d}üØĪúJŨœÜ|†FvФS>^9lڎīsD §÷“IÜdŸÅL[ˍÁĻm˙>Īiô˛¨ˇeNBgQ¯ņŅS¤šÄmĮԀ6e_ĸF<jÎ_7Ąŗ›ß„…#œu5Ŋeíūú$ŪZŧæŖ^nNÎn~ÖLô*Øŗéč#Fƒ„Ōü4ßkcĸ÷ׯ n°Äƒá;‹�ÔĶ�€aŒi]†ŗ¨“ f7Ãŗ¤CíQŒ�ĩ{ PŗhZNFĮ˛,Ëę xØ ¯ž‚ė_ėQwąÎmčŠ/� kwŽŽ/öÕí(ôu0$d*1X}ÉĀ—Ū=ÄYėå„�Pg).A2ÖWPwˇŋŗakZ‚åW!„4Í7ÆxN2iŖK<˛ÖØ|Íôĩ˙šÔ¸ā†›Ķ?÷¯:Ô&Ī‹8ÚaÔįKK;(7‡}đ¸åh5Z0ŧû;eĩ,>SoÃ0ĐęX€ÕęRˇ›AĢræĘÜįÖīŠ'Ōąh¤nBiƚoŒ=˜ÛČõ{kKD‚‡ī|{zo‚F2)îŗāšĄ“’5<üˆúö°:öžœ˜{ÚY- ŸŦĄŪhO§­™5ä €<ōĐÂ7ęwÆœ(Ã!/›æ{mėÁĄÄK&5ŨÜ;ÅžZ@ ä×ŪMŲ{Tā1'%bž.'-¯öžrĪø÷Æ}› gyÃv¨2˛¯ņ$RĀYâÄĶådÕ]|+ČĖИžzČ:A•Į:‹ÜÜL7'à )Å!/æcēK'S’ŪRĶōž|„@ė#BÆŽŠjĩ2eųĮ_ŗōîĐ^ĘRj'ÉŸáŠTßΎ:¨ČÎR^ž`u˛Ž“—ŒĪØEĒīį/MĖVŠÕyÉąsÖfv2Žŋ�øŊĶ‹ŸŗuɖdĨJ™}lyä~mm†z„ŽõŅîŸ9_†R­.ČNŽļ\AK !/æ;ЍÚ?kėū{yom:ŋĒßö(Ŋ,R1mCxŸ¯'īˇg,Y!Īd3į,~s`ŗđ‘G3’Y;ÖcÁšEÃū­_äķÎ睧{1�$ŗvŦį/X3o؍Žg/~ãà Ûfų0� čŋø+ÕĖ_kã$2'rÔæq›LŊ9ŽũËŖŋ4WžŊČ{ȚmSd4#„ŧtˌFcS×��šššîîîM]!Ü@?/„Ôiž“Š„B^c„B8ŒbŒB‡QŒBá0Š1B!F1F!„Ã(Æ!„pÅ!„Ŗ#„Âac„B8ŒbŒB‡QŒBá0Š1B!F1F!„Ã(Æ!„pÅ!„Ŗ#„Âac„B8ŒbŒB‡5ãĶf%Ŧžü^°ÜS&r—Iß Ŋ/CÛÔE‘ŋAŊk¤Čķ_‰/ÎI“>ģü^ũ| "„4Ô\cL85$dÚĨ0`ʲõ[ŋ^õYp§‚øšīúWâ‹ķKGš%8hiFc[ÔģFĘg§<īzž‡J}2ųØåŸõhę2!bŲÔ<ęøš3ë|îŠ Õ4ų •‡Z8/ú^_ø1MZž‰6į´’íÔØ6K‘Ã>īrž ‡J}BŒ›ßˇĻ.‚ō`Ír4–÷cÜ/:éÄÅufâ<tÅŋ÷&,ĢÉ0­bûĖ÷Ĩž"OŸĀasžĪŽ™pTī)Ø>y` ÔS&î2yW6 �lâĮ2ņĮû˛âį„ŧé/õ”IƒÆmLĢ›Ļd•IKÃüÅîâÁaķ+īūvg• QaAūbw™ôÍЙģ˛ĩ€26X6íƒjĮģî!ģTõĒT?ĖkÔ>nßx‘{đō뇕z­bĪĖ÷‚Ĩž"w™|ā¸å'kžĘ-ÁžÁËĶR6Ž –÷‰{†Ė>ŦlüŠS'Į~Üͧ~Š�Ô ŸČ=C–g×<$Vą4ĐŨrŌĨFJÍŪ7oX°ÔĶCäé8furAÍϧ.ye¨ÜĶgōɇ—Ä*ãMO—éņFT>"( v…Š{Î9xruxŋÔS&}3tfB[īķ`RTp™_t&�¨S6~â×C&r—ɃÆ-JČc6mŽÜ=p‘ĸ^§ŠĨ~îū3O˛ 'U‰ķGúõ‰=ũƒ§nOÕ=úŠŌįõ”…ÅĨ|;&Pė9îGšÖ&äŠ3ž.\¸đÔú*‰í,éŋIų°}ūÜ6¸Ģ4`VüŲ?oŪü3íģ)^ũč‡|ŖŅh,ŲûQ—Ž~6Ÿēi4õnÚĨķĐoōFŖņÄi§×F.>™¯7õ?LwęŊäĶ9§{w–^qôü‚?Swë-õžr´Äh47MėŪ5`ŌΓü™ujįÄ7:ËĮēiԗœú, Sī§n–čõ JĶ—d}õļ¤û”„›%%ú‡–ÚđQôĒÄ{ÂļSYųfũgņĐ.]‡~gÚíĪÍũ;KŊߞū]V‰Ņh,ÉXĐY:roÉ}]čĪ.îߊëāš‡ųųg÷~Đõ˙ÛģߘĻÎ=āŋÅ­'ÛŌķĒ'K,oZRáЄ"š´¸PĮV§ģ嚈Åë.uŪéŽ#Ė;‡NeęEÁ?Ã?Wwˇ0÷ĮáĻnwE—1\—.ĄešļÄĐú‚˛d”eé雞Fsî‹ļ‡ō¯¨ĶÁƒßO|!į9į9ŋ>Gúí9üˆVי`üEœ[oÍ*kęWEņŋˇØR°žmplŠũÍŽ|ą`ÍąË}ÁūžŽ÷VŲŗíéŽ&—ŽØYžĄé˛ÛÛN[’{O‘ÉZŪĐqŊ/ØīíxoŨ°č`|‘OUĖŋS÷āŠ ƒÉZ´æXwXQ”đõãš&į~oüjVd™Î5[ŋėô^ī*QĪūE–ŦÅ;Īšƒƒũūˍk LöĒÖ°íĒ™'ÕyÕ9¯×9 ķv^Ž8i_ci–š´ĻÅÛ?ė>ŗĩŧØj0U|6˜~é<ģŠÅĸE+\mŨ^˙ØEŋ7÷ķû€q31Æú:L–u-Ņ4ģt×9 ķļ^Uw _XgN$_øĖZƒÉąËüŧÔdŠj*ņ÷âü­—“GE[*ŗL_*Šü¨LĖZÕ<¨Î×Z™kއ_đŖ21wC[ō đå†ĘĒ㞨ĸ\opŠ÷tS]đŖ21wsĮ¤ĨŽîīķö‡‡ws™-Ž–°ĸÄ3C,=LŽy÷/ ļ{ÆLpÁ5bæčÕí5B”ÁfWžÕu&ØĒ"w^åšÁqJŊŪā4äoüV­ađķrŗe]K8ątæĩįÔĄ4%EûRßîÛˇæ&?F¤1{zÉĸÕų‰L ŸYk0ŲĢÛËmߨ&\üĐĪ.zuûp^Ļ֓rRīūEbî†õú_ŨlOÄXēĨķė* ‰O�÷ b @5#*j9ĸ´ĪĄB^ßĪ$ä¨?"ãŗķŒäīš™8JcĖ“Cœ†'’bÉ/Ų™ÉŖ8-Į‘,ÉDro—2íA/ĪžëíōɉĄŧėäAü3Õ˙9ēÚrĮ?œ›ŦÔa<ųŠqKyÉ ŦOΎ<[Û‹É˛ē—6͍W÷Ôi(e()pÍ›]hSŸÄrsíؚ@;ūlR(m¨ą¸ë]+Ü,ŦŲšX}4QČëšIbQ!ŸÜ ĖΈšģz_ røÔũ'(‰ã)đõ[/.]đė|ëĶ6Ëë#[ėŲ)…E#…Ô%2ØōËįŋÖŅĻėIBNŪėX ĮO\aÉsēĀĨ–ø“Mß7­Ų‹—ZFœAžéRfžQŊū9ļėÄ_Ķ/‘^´dLú �āžĖÄA/hbA_€JÄ öˆHi2ø”,Ņō<Åd)ųƧ™xvnœ’%9Fžƒ1ąŲ6$Ň8.̈́éĨ+uD%ūĢ_<Z°ĩápQĻNCō÷›žĢMyķįhŌä”"2ũ||yÎņ[ ’D$ %+žŠ¯já–Ô-'Ĉb’DÔ[“7§&uĢÆI”Áņ#Rl‚’BÍUKˇôÎ}ŖūhIļĀyö-¨ŧ9YéD 7<ģ†ãh8Ã5ZõšÉ’˙ô1\Į‘‘‰ČVēP˙ßöļĀæLŖ÷ÂĨ€Ūq4oä)dYy)9­F˙ŧ4ŲŌqü=˙�€IĖÄãŗmFęhûÆ[-Žę“}÷ÁyΚjūØ$N‹{éģãx^CâęOŽ–ęFl„ø_ŽMxė$Ō•šĘßöĩ‡s4î_QŨũ áĩÍ^øîą¤vÆpœ¸Ũĩ 6ųâîwVVŊĄÔđ<‘uKKmQęĮëīĻ/4ÔŪüCÄZßđŠ=•2ŨÁÅb’D”H˛ ¯&Īs$Gä”Õ”%™x-GDdYfŸũՅKūWä–ļcɒŅMöĮiHJš”r$’øb’Ĩ€hF>T4–­.ŌNl:Гú.&ûŋØ´åpcs@&!GœMžŨę°ÔÛ kö=6âsŲyĸ&Œdd3t§øøų¯ô$;ÔäöúōĨ;.MŪ°–¨íNK•‡$â´BrkđÂY7Ũe&­s5CÁˆ6S}Įņúx4Čžw7–ËjŊ_ë”>Š=âK™Z-5Ī@9C]=ĪiáŽ5&É¤ŅŠ÷mĄÖŗ?ŪŲG€Ū.ĩ$ųĻg€2ŒŗĮž8Ú­ôvÔ nßĪŅOŦšKBāR{Į7íAũÂŌ1ˇōœ!S ŋ' žÆŨĄ>/Mˇt�đ@ÍČ#aiũŅRŊ˙˜kÁß÷~|áR{ĮĨ¯ŧątym§ūoGwŲyĸšŽĩ6éâîgŨÁPČíä–Cüs/•č'Ÿz|ú˛uŽmīkŸ\ķ‡BA˙ĩ“˙\š`ųļv‰ˆô‹W?Į]Ų÷Ú'Wŧ>_ûémģOßäķry"NËS¨§ŗ+āJ-ĪQ$đCģ/”î´Ô ĢE;ôÃņf_(4ānŪöÚ5}ĄŽ‚žŪŅs§Á;^ZĒs¨~û;_0ō{ΞõbIÉëgCD$ûŽŧyB*ŲšŲÆņķˇÔ-úøÍwŊōčRsĘ_ļI_oÚqÖí…‚žö÷ĢJ^XųļįŽ˛TŸ#ębWž8é …‚žķõÕ§4FđôH“MĶųÎŪķžPČwžūPģœŊllqļ—WNîØÛę…íīo;Ō3ģl3‘7y΅BīŠ=ũÆ%ËÆų]1qY‰!Ōļo÷…0ĐyzۑžäÆ4K�ÚT÷˜$<€ÎĢh_ëÁueŽ\ŗh0Yr‹WT5´õĨt/†ŨMUņQŗŨąfįšäXøĖZƒšōÜpg`ŗËdqĩ$;Ë>WÛ•öjĸDûZö¸ŲŗLņ ÷œ>YøúŠ­åÅÖ,“˜5oEÕ)Oĸ ¯ŋy]ąÅ`˛8†;įâ[w:ōEƒŲ^Ķ™ŽÔQ}ģ}E‘Y4˜íÎõMŨáhwCiŽÉR°ŊKé;æ4ŲĢ;Õ=ƒ-s7w7IđrCĨsž5Ë$få;Ë7ŪVEšŪXš•¯v'Æģ-ÎFotlŠŪæšUÎ\ŗh0[ Wîo ÆkŊtiJ wí_ãČ5‰YųNWCĮ`Ô˙Ų*{–ÉēŽ%œŽSŅŧņËÎĻĒÅö,“%ˇ¸ĸĻ%Ņ9új*Š2ØņŪúŌ‚|Ņ`˛Ä+LĒģÎaųÛ#Nõš9šČšē[ˇ˜ã ÷.]ŧSŅ1æ˙NčTP=ĸ(ĘT')Ņ7æĖ™3ÕU�“B§]ļz]ã˙.œęJū0ø~P͡Š��đ@Œ��ÃfbÃ=<d„•M•S]�L܍��Ãc��Ā0Ä��0 1�� CŒ��Ãc��Ā0Ä��0 1�� CŒ��Ãc��Ā0Ä��0 1�� CŒ��Ãc��Ā0Ä��0 1�� ›‘˙mĻ|ūUۆ`åwįÖfĻnöüËēüû…ŸvÔé>(ųķa߸‡ę–Ÿ¨ūšbËãjœīôūģčüĢļ mą”­ZŊXô×ĒëįëīßK��€;2#cl2K>ĩHDD$_9ôŌąČ˛Ã;—鈈ˆĶįčĨSŸžtĢnđė>\‘ˆC]vbũ’#û–ņ¤›Mī6ŧėøôBûC_�ĀCīĄŒ1NČąÅ3ˆä!^C$ZŦ…ę°žPHūĩ™ˆĶįØŦs‡–‰ˆ´†›5yĢg}f~6Ŋ°ōdĶĨÍ6'˙āË��~6v?pŲyĸ&Mu!��›|7‘%IJŨ M´įī7 Æ4‚N˜|O��¸ŸfnŒõ~ø—?}8fĢG–§k{u Ģxĸ�đ›š1–Ō…‘8ņĘΟîĪäŊ‡ŸŸsxøK­áųÚ÷ëžGŠ�üŅfnŒč ""îâ}ë#4.o<ŧ"ŪÂitBĻ€�˜37Æ(>S3'ß��,t*��Ãc��Ā0Ä��0ėEQĻē"ĸ7nĖ™3gĒĢ�`ž_�T¸��†!Æ��€aˆ1��`b ��†��†!Æ��€aˆ1��`b ��†��†!Æ��€aˆ1��`b ��†��†!Æ��€aĶ%Æ}ôŅÛˇoOu� ¸}ûöc=6ÕU�LĶ%ƞ|ōII’Ļē �„Ãá'žxbĒĢ�˜.ĻKŒétēß~ûí×_Å=ĀDnŨē544‡A˜ęZ�Ļ‹éōŋ?Ņ­[ˇ~ųå—h4Š$×ŦYŗüņ§žzjÖŦYS] Āt1b ��ānM—‡Š���÷�1�� CŒ��Ãc��Ā0Ä��0 1�� CŒ��Ãc��Ā0Ä��0 1�� CŒ��Ãc��Ā0Ä��0 1�� CŒ��Ãc��Ā0Ä��0 1�� CŒ��Ãc��Ā0Ä��0 1�� CŒ��Ãc��Ā0Ä��0 1�� CŒ��Ãc��Ā0Ä��0 1�� CŒ��Ãc��°˙xŋd=Äú����IENDŽB`‚��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/login-oauth2.png����������������������������������������������������0000664�0000000�0000000�00000111140�14156463140�0021666�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��X��ˆ���DíN°���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨ{tTõŊ˙˙;S/3ë´NÎų™Éé7 $9„B$TŽB0Ū¤V@D)ŠK)Uđ‚ŠPČÕrH@HāKz ô”„_g8=ĄÅ.ķũc&÷I Ų }>ÖĘ"3ŗgīĪūė kíŸĪûĶ&�����€&ģŠĩ������pŖ#`�����0čæK—.éėŲŗōų|ētéRkˇ�����āēôO˙ôO2›Í˛ŲlēéϚcVÚüéO |÷ģßÕ­ˇŪZįC������]ž|YįΟ×ųķįY#GisöėŲĀ­ˇŪڊÍ�����¸qüũī—$ŨqĮ•īŨtË-ˇ´V{������n8ˇÜr‹ÎŸ?_ãŊ›Ú´iĶJÍ�����¸ņÜtĶMēxņbÍ÷ZŠ-������ß,������°������DĀ�����` �����€A,������°������DĀ�����`ĐÍ­Ũ������p})ųc™>ú4[˙Įy]ž|šĩ›Ķ$7Ũt“nģõ=üĀp9~ÕōĮkņ#�����€FÉËôö{+å=÷÷6\‘¤Ë—/Ëë=§ˇß[Š’?–ĩøņX�����@ĨUĢŗ[ģ ͧM)¸&įDĀ�����*ûûß[ģ ÍĢMũãüų?  �����øFģSX������ "`�����0ˆ€ĨÉNiá)>áGŠī:U9­ŨœFģŅÛ5ŽčÕŪĄsŧwž [ģ9����€oŦ›[ģגĪ]¤ÜĪwh÷ã*,-—Ûí•×ī—d’åŽ;dŒVlBweôŋGi 6™[ģÁ�����ā†đíX<EZ3oŽnúŧa7đË{ö+ŸũJÅĮŋÔÆČÖi¸žŸ1IÃb‰Y�����@Þņ‹¯øcMxfrĪVŊg‰ėĸä”-ĢIōųŊr–žRqūAåŸ9'IrĪÖËUáĪ—éÕ4[ë4�����ÜžŲKé:}bōCÃVLŅwë'3&iTrũĶ\E›ôÖėųÚxęœä?Ŗĩ/ž¤¸ĩīj”ũšĩ����€ÜÍúūb•ØNŅßģ]ÖīHįĪ˙¯JOTŅũ^ųēĩÛ×üžÁKŠVNŸ_ŽXēNЊˇSÜfüD$ Ҝ:(ö™§4÷Č9É{T _Û¤>oQDË7����€ÚÍ5|dĨũK÷/\¸ „¤dŨwÁĨĸ›õŅŪ3:ßJml ߨU„<{–jÉqđÅŊ4sŪ•Ã•JæztŪËÚĩ—FN|Uķ'v—ĩÉ-ņęäöåzuâSØģ§’ēūHņ Ũ”Ú;SLœ¯5šåōÕūJére&Wŋé9ũHŨĪC|{Ļ*5´]|™ĘŠwÃ#šVąšÎCËYo[‹ĒVŨō Ŧo3÷&îÚ.a Ū(Ž˙ܡMėZŠhŧ–ũęĐw~¤??Uo+ĒÚ}°ĒŨ=~ĸŨžZ;ęŨéã•yoO%%üHņ]{ĒᐧôÂĪ7é¤'ü.����-įG?=÷Ÿ#ë„+’K{ų†Ļŧõ‰>;uŗâ<ŽMŌ÷[Ĩ•-ã°¸ĩû×{+ ÚÆ>1I[FÅvæ|°P¯>1DéąM\QČų…^1PÃ_\Ŧĩ_UéŲsōûĨ`QŨ3*úâÍzvú=ķą Ģ1]”:“ĸƒ*Šg÷…ŽVíõQni=–îU^¨MLrWÕ?ÛŠƒ2’oūz樊ęIb|E{Uė¯xõ• ”×ŗáqå m˜Đ[ÃFŪ¯dS¨IŸ¯Sa}PÅ×ķ7iw¨ŨļŪ÷+­ZĘåĘ}]™÷ũ§ŪÚôĨŠËĪ)Ø­įä>sTÛ?xUÃī¯ U����´´ī%iĖčtÅÜRíŊķ.]ŗPĻ,Öϝ.ęüWÅÚĩrąl*Õ-3õė°hŨRīo,­°œūxŠū´cĄö/{ĸųvę;ŽŨųם4ĒwdķíûjyęÕ'^ĐÚSį$™Ų{ŒæŋŋZ;÷ėĶĄ=[”ũĢWõH×;$Iî 4öĮëĒ,頌Đgõ§”›V’I‹I Î#ĮüäNĨõéĐ@ŖÍJėŨUĻĐūsŠÂ¯šTxā¸ŧ’,wØâGvĀČŠƒĘ í"ļGEØîҍŠ„Ĩ|¯Öä7”°ø”ģé`(@ēS}îëRr97iŌ‹Ų*õKŌíŠ2MK×nŅĄÜ}Úˇ}ĩ–NŽ}ŠYS–ŠŒ����Ž…īęGƒû+žFZr^EkßՊŖ˙[kۋræŽÖģ_ūU˙úƒÕīÎkØĖÔb5X~öŗŸéōå˕?—.]ĒüķņŽŨ¤Ë2Ųū?ŲR{ęōĨË ”ožƒ—­z°Žîĸ„k^ Ö§ŧE¯kmš$™ûĐ2­x1ĄÚ4#‹ŦiC—Ö]‰ŗ4鋺ōų…f}ÖKËîŗI2+ąOY6í7tŒ˛×^å<ĸŧ3’L4jˆ_ī}|<t<<¤Öt&¯ō„ĻãÜŅU å+’ŦÉŊ”hÚĢ|ŋ_ENIũģÖÚĸ"Øš]É÷ŨŖŌ˛UZtP…ž!J¯5ĖĮYt$ėD+­GŒ$)cD/YėWgĩû×{åIģ'üô+ĪAm8\ŅIŅ÷hTrÅÎ}Ęû`iemČ ĩbF×Ē}X;(bäËJIŽÖØ‡Ģ ����ZĐ÷:ĒWĮÚcQnQÂčiZ\ņō‚KEš9ÚųEÎ|ũĩ~ŋ}§Š’F*5ũ‡Úļö÷ēXí›úõV˙~ŊÃjûÎ/´mį-pÆ´Ø–K—.Énˇëßūíß­ļmÛĒ}ûöŠ‹‹S pYßëŌCˇũŸĘwîŦžöüYĀåf;ļ¯Ü-wÅ {tSbZˆg¯ŪûėĢāīŅ÷kæęŠábĶ€)ãBĶfÎ)wío*Gą˜ģvWĸI’BAGíCTą$EvR˙>íe“ä/:Xwڍī¸r‹‚i“%š—¯4×ÉV¸‹Ö­ÃRė(Fé÷uWŦE’÷¸rë4ŅĢŧg‚ŋŪŅEąĄķęqŋú‡įxŦĶnwíī…ÎīĀæĘp$öžÁŠĢüä¸ļí õ­Š‹žzĻkØž5Į<¤Š#ĸ:S����@3šåíîƒķ.}ųĨrû•Îz/ęæīVķøūîÔÍį%ĢŖūĩÖ×ļíüBÛÄ(×k¸"ĩpĀ"ImÚ´ŠüŗâįŌĨKē|钾ūß2]øĮßtų‚_Ë—šíØ>ŸWXL‹ĩM<ū‘ß(/Ԁ˜>ƒ5ėŊ4 bTÉŠƒĘ̘kcíŪ@ĐáSáĮå—dIč.GBw%˜>č8õE(¨0)ąw×̍%Šääčā¯gŽĒ¨V�â9 vĸģ(!Ļ“R:HŌWĘ/Ē5=Éw¤*ØéŅ[ąī›ģjÔ}ĄũûjÍįáĻ5y•ûyÅô NzoLÕGĨĮUĒËĸ˜îJn ļN\Ÿîj…Éa����đ­cųŪ?ë;ĩŪģđ—cZöķˇõî†íZŗōmŊ:÷mŊûĩēūP˙rgG ÉLWü÷%Yn×÷Ã˝Š˛\ĪáŠÔ‚S„.]ēT¨HĒņûŋ~]žtA—.úuųĸ_R.7ßI2IÁÅ'ų¤‚…RŊ;b¸ŪēÂĸ61ŗĩõ‰˜†7 )):SđØl’ĶYOØ‹ũvéø9ÉFÅg$%H’MÉ}ÚKĮWtÄU† §”[Ŧí’ØŖƒĖfŸŌ¤=GžR~~š”P+”äæé¤ŒäÚUœÃ‹ëĶEļÎČ­SĘÉ÷iT˙Ēé9…*‚ŽrČ&%DKGΨ¤öô¤ĸƒ•ÁNrŸ.5ú?îžÁŠũ`ąŠ%­]§’‡Ÿ“Ŗz<ĩņ@°M]‡Ģõ!HÎS•Ŗ|L1WŨI“TN����¸ÆÎĢhûf˙ëŚo_ŧYö~jƐZ›×ÚŦBõ@åzW¤ÁrņâÅŖVjŒ`ščWāâE]žā×Ĩ ~]ōû›5`ąZ,•ô~īŲđX[×]5ė#^–úõÔāΤĪCĩFtVNwÕGŽĄÁ ŖRéQå—KRĨ%X$Ų”-I*9RŊāŦ[ų~üĩC/Ĩ]íJJz+Í"I~Š^į”vįƒäd–×ŖSØéI%ŽƒSet­oÅ Ö¨ŽĄbˇg~Ŗ5E5?v}ą)4Ȥ´ŊQí3×[šrRõë–Õ"K“–����4†÷¯˙Ģ ĩŪģp!Ljō×Ŋ=sĄ˛˙PũËįô? ė{Ûu>rĨB‹NĒ/`ššré‚O įõ/ß;Ŗ@ ž¸Ē)ėĒF6”žR}ĢŲ”ōđ z~â„ē?C:…VÔiŸūę_ŽíĨ”;¤ÚA‡+ŋjšNrčDĢ•YŒį¨rBáEL†–gŽÅÜIiĄ�ĝ_mzRé^埕¤JO†énz’[ųųgBŸ÷ŽąŧrMũGt õíWÚžö Ē˛ˇr?;déĨa}jēņWõÉtĨĢcjÚōÚ����€F9˙Į˙VYwnQBī4ũđö0g"bõŖjõ<%˙­˙ŋ…Ûw-´ØĄę#X¤šĩX.\đëŌŋēä÷ÉrÛ_õũ>ĢīüĶųæ;xdÅŪ!Ÿ•tö¨r‹Ĩ”Øú6ļ(ņž‡”îŖür­Út\õÔa­—Ųd–éŅįįûôË>M}ĖäÛĩņķsrįT‰ēĘ!¯ōÃ[B÷Ēâ¯ē+Á´Y{ŧĮ•wJĘH|§*XåJË3×fQrŸN2}qTū3Į+§'šōÃĒę+3Y;)Ŋƒ´įøWĘĪ/•b‚ÁN(l‰íĶĨÆ” Ö>)ÃōĨļ{%÷›”ëëŽ ŗ$÷^m Õnąõž_iuēŽ*Tņû¯”dųåkh%h����@ķøëoĩ÷ˇũSm%Ą[ÛŪ­‰¯Ü-ÉĨo-ÖĻĐz%?LKWLeÁ—įüžžB7”V™"táÂ×ē|ҧK|úß˙ų'9c“Ī_ģŽæ.ęŸ|{čÅmü舎åsļ-&´LŽürģŦlVrīĐ(š3Į•ī–ä;*kRBNU›ZĢœÍŽŲ)9 ‡îčĒ´Æä+’"ēö Ļ=ޜ"Ÿ$¯ōCĢŲ’Ģ;ŠTB¨(nɁãrIōTž_Ēž<sŨSëĸQ÷V,'tPûÉųųæĐwīT˙‘u‹ōšī°TF,¯ˇáëę.—›ú+����p |­˙ģyģN„;qģ,ĄÜ忤iH×Ē™ gŋÜŦ_]“ļ¸X$Õ Xü~Ÿ._ŧ Ë|ēđõuŨ,5k‘[ŗRRš‚ŒûķųZR|í"{‡hUÜ.ĨG~g(Üą&÷Žt”îUŪYIę¤ôäęņCÕę?Á ŖŧršŽ%š—R;ˆÆŪU)ŅRp™čãÁ垏„ v$9ēv’E’˙ÔAų¤’G‚uRîčĸ´zG™•2b°‚ņË9å~vP•jÛgĄŠPŅ÷hhB˜oŲ"+§:ųKĪTŧ ĮWzü ĶÃ�����Íæ¯ZņaŽJë„,ˇ(æßŖuËí?Ô¨īŽŊröˇëõĢ gԌķYZǓ`šxáë` –‹téb(liæU„Ė côRÅ ˙īôŪgj[COãu¸•ķųņ&Č5'ôVrh˜…÷Ā:ínp'>ülšÖė)’3Üvļ.ĄåšƒAGɑ#*—¤čNu–(Žt¸+jĸ˜”Øãj–gŽ­ƒ2’ī”$š‹Žë䊃ĘķJR'Ĩ'Ôܛ9ĄģM’ŧG•STŽüĸ`ühéŅ;üÔĢ ąƒ544˛Æ`“vįoÖÆŠŠE#ī¯6JϚ˜ŽŠ­L¯Gõ„åSŪg=Ŋ ����ĐtįKvęŋ\ĢÜ?ԜÍņ¯ékŪ+ĒÛ÷%]pŠhÛZ°˛@ifļˆV Xü|Ápå‚O—/øuų‚_@ķ,’MĶgidÅ0–ōš4ōŊúŲŠ+„&^Üŗ\/ ÉÔ¸_˙.XlÕ­äWģ$k/=zߝĄŨíÕÂמĢžM=ųķ5iöbÍúņ |qS˜íb”Ö5tīÕÆ=g‚g—ÜŊæŌÆĒtWÎgUė—¤NĘčquË3×Ûģkp$Némß v:t¯ėČÚ)4éŦŠüFyĄ`'šO§+;1ZQėÖTKflŽ81uҍ{#ÃÅÜEC{„ĻųęŨŸ {==ų‹5wĶŲĢ:O����@ķščú­Ö,yK3šVŲ9Įtôˇ˙­˙>ũß**Č×gkVjîėÅzwī)¨q=jņ"ˇ’ęģŊp!8jåÖīũ›._ŧ ‹žH?7#ŦŨõęG ¤g^ŌÚS~Éû;­‘Ĩ?o¯”äNŠMč ģÅ$ųŧō”ĨŌSĮ•Wt\åÕŽ˛)˛—~2o–MhLHaVĘė5ôĀjcšTūų îŽņĪ Vrl¤,ōË[~JųŸ}ĸ%ŋ-gl餟Lļ l\Ÿî˛}œ-÷ŠMZ#ŋ¤Û•\kšNđ|ƒAGîņ¯´ũŖ/‚7k¸@äjĪ"Ą—’M›ĩĮ\7—’ļ%tŠėH‘JN¸S:ū•Šũ‰œ~…–gžrŸEÜ{ŋŌæÕŋ_ååÁ@Ĕ<X}ęmŗYéĪ<ĻØ=‹Uė—Ę7ũDYūq?ĸģbm&ųŧ_ŠxĪĮZōŅ—rwčĨ4ī^å–7íü����MuQ˙ķĮßjīÛÚ šfš=`YšrĨŠ‹‹uņâEmذA—/_ÖĨK—jüų€ã:qx‡ĖßŊMĻīŪĒī˜Ė-0‚%ÄÖ[¯~´^éoŋŽ7>úRå~ÉöwĘũüwĘũŧū¯™îč¤OÜíMYČÚ]sŪ_ ĶgjíŠsrĪÖŦgŗÃo{G=ŋhĄ­§Ŧē+Ų’­íŪsÁĐÄÔAi áŠÄPĐá= DbzÔérõįĐI &í9rNÁZŊˇ+šGøjšŽ]eųxŗŧŪŗĄ`§ˇRę,ĪîŊ4ĒĪíÚķųšĐˇ+mDī°ASĨ˜Į´xÆqeÍØ+ˇü*ũ|ą&}ž¸æ6wôŌëķÆÉ=e¯r%ÉįģĻ…Ž����ß.Ͱ<účŖÍŊKãˑƸņe<^Ēŧ/öj۞ƒ**=#gšW^ŋ_2™dąÜ!{d´bē+­OwĨ%ĮčjōŲ{ëÕ_wÕ¨íë´jĶ^—Ęyöœü’L–;åčĐIÉ÷ŅŖ÷]!Ä1wUF‚IÛ„–ĉéŽÄzFxÄöč$ËĮ_…†ZŨŠäF-Ī\›M)}:HGB…gë v$sB%š6+7ÔÄØ>]*‹Ņ6ĖŦ”÷ČöyvhÅŖŪuSšė÷-ԖŋŅ{ŦĶîüSÁ~5Ũ.[d%÷Ŧ'ĸ8Ģ[k*vå÷°�����ZL›ķįĪZģø–s¯Ķč>s”/)rÄ m‘Đ„ĸŧ����€æ0ų•9­Ũ„ņæO§5ëūūüį?+::ēōu‹šŽVÉgë”/IŠÖ€‡ W�����7´.ĪZøÁī$IĻcôH}uh�����¸Ž° •kËs´Į+IŅõĖ= ˇ����ā:ÕbË4áxœĨrûũr—Қˇ—jûŠāęA‘#Ļi|=t����0ĸM›6-~ \C^åž–ĨIĢ!…ØzL͞)]¯Ú����0ėöÛnĶšsį¤kJ\€nģíļ? S„p ™dļ˜*ˇEwŅĐé˔ũöũr0x����Ž d ˙æ„+’ÔĻMđœZú0,Ķ �����Ē+ųc™V­ÎÖß˙ņ7flĐĻMŨvë­z$k¸?ˆjöũ×^Ļ™€����� ‘j,L�����0ˆ€�����Ā ������ƒX������ "`�����0ˆ€�����Ā ������ƒX������ "`�����0ˆ€�����Ā ������ƒX������ "`�����0ˆ€�����Ā ������ƒX������ "`�����0ˆ€�����Ā ������ƒnūÃūĐÚm������¸Ą˜Íæ¯Û@+ĩ�����ā†tæĖEGGWžfŠ�����€A,������°������DĀ�����` �����€A,������°������DĀ�����` �����€A,������°������DĀ�����` �����€A,������°������DĀ�����` �����€A,������°������DĀ�����` �����€A,������°������DĀ�����` �����€A,�€o•ķûĘŅ>^ąOl“į ÛúļŽUlûx9zŋĄcF\ļR™íãåčø‚vTžY¨ŲŊãåhßSĶ Œ žmĶ1^ŽöubģôTúŊh܋ õņū’+öÉ g˙4%ļ—ŖË4íkíļ|#¸théXĨ´—Ŗ}ĻŪ>ŨÚí�āúqsk7���\+&ŲE˜*^ûäqģå*)ŌΒ"íÜđžf;úkÆ[3õPœĩ5Šë¯lˇæ=?MË Ŋ­Ũ��ŽK,��|kØ4ø­õšWëmSĮrļéÅK´ŗdģfŒ(UÉû+4#•Ae;§iÜԍ:éĩŠįØĄō­XĨÃūÖn��×Ļ�đmgĩĢķ§´ôŗõšn“üÅZūü,íøÆÍBĶøtlÃ6´ĻiúÚ-Z1)M­Ũ$��ŽC,��4‘įä6ÍûņhõížŦÕŌą§ŌGü§f¯?Ų,ĩL\k4ûŲ”Ū=)X3ĨcĒŌ‡ŒÕä9*ķ5Ãj3;ôĐ[35Č&ÉŊ]‹V—ÔmĶá5šjSl¨M)÷> įæoĶéęmōėÖ¸.WĒ/ãŌē‡ƒį6piõcųTļĨ&?‘Šô.IĄZ1}5đ‰WõņaWķī4Ē˙O/TßĘz=[=MY÷öŦėŖô/číũÎZ_ōiĮŗÁ}Yī NÁyļÚ9wĪԘW6čD=7SãîŋŠz?čƒ2-Ģž]’äč8ZëŽĸKŖúž¤ėĪ–éņ$F5�P��šĀĩķ 1Iīl+”Į–¨ģ‡Ôđž1ŌÉ=Z>u¸ú>ģMeMŪģO§WÕĀ‘ŗ´|WądOĶđFjxßD™šĘž3N}GŧĄC-1ÂĚĄ§3c$I'7ėVõĻeëĮjā#ŗôÉŽbɞĸģ Õ ž‰Šđk˲I8bĄŽUÖ4=˜n‘ä֎õ…áåÚŖėŋ¤ čŊéŅĄ×ŗÔ÷ÉšĘÎq˚”Ąá ÕŨqf•åŦՌG)kÅIĩDžTĨ)ũo‘Y’<n›?FY3wĢĖŖn}Ķ”!9 ˇëÍ'ŗ4nkõ4Ã,s¨ާdŖž9AHIię—+̧Xû?}Y™WëאÆß&™Í’ä—së,=ˇ O>GĸR“bd5_Š?Ėęœ9JÉV��h5X��hŦ˛5zîųírúmę7wĩ~‘iWå3Ēkˇ&œ ė]Ķ4yuĸVgŲŋ˙Ķ+õÜëšr+R×Ŧ֛ũĒMČđjöÃŖĩŧx•&Ī ]?MÔŸ)žo˛lËJå.Ë×1ĪSjk•äËŅĸųšrËĸÔYëkž—¯PŗīËŌō╚ˇū­ÎАdVˇĖ Ųļm”;gƒŽ)QkĮĩk›Žų%SĘ0 Ž ŪÎY÷aąü–dM˙h™Ģ:;Īá7”õÄ*žķ˛>H]¯gjגi.Méŗ9ø§wˇæ­OԔĪöWkģGû^ÉŌcŸ–jįëoéPß9ęVyZfI~XąPQ—*įgé•Ķo|§×hÜČYÚ_ŧRŗ×ŌúŠ>oŌũWą…S›?•îzo§ŪėÉD��š#X��ßJūÃŗÔˇ{OĨ4đ“ūJžÂÕņ<öáģ:ė—,}§ęÍꡒ‘ĄĶúË"ŋ¸FMYÅö؇ĢtŌ/YúūD3úÕzļ&jʤ Y$9ˇŽŅĄ–ĘaS”$ųŨrU¸pčÁšķ5oîŊ–Y+42'ęÁaą’ü:v¸¨rt‰9u¨î˛Iræ*ģÎ4!—vl-”_&uÎė œZˇlģŧ2)iԜáŠ$YSĸ™‘’Šĩ|u^ķo-Æú߯ļŖgÖjģUwM¯~Iî\eTûRh3ŋ9CS~š^Ŗļ‰ší(MɊ‘äWÁúÜĘ)MŊ˙,&IōĘ÷¤fŽ��ĐėX��ßN~¯ÜnwÃ?ŪpņJ‰.—dRįi 7kššĄÎ&I%…:Öč’!%:Tā–$Å÷ ŋsRh˙ŪBŽ[&Ĩ˜Bí>UvŲŽÎ=čūĖ ĩ5?폜*+ ūxœëâ÷zĒĻī˜S4ŧ¯MRšöm­5M¨l˛ķü’)Eö =ė{ō´ŗP’b5(=ÜČŗ:÷M‘E’ģ °IáՕí˙ŨÕĶQ÷KÖdßvëôIwMŠē+ĖP¤øôDY$Фâ|ßĶ“Ã~��Ã!�Ž’)}žōŪĐāƒĻoëX%>Ÿ[k‹[%NIōëôęYzng¸oēCŖ œ:锡äJÅūmО×Ķ:ĢMQVIîĐ“æž*ãķ†Š¤ZdŗT{ßsRë/Ņō­š:éžē5zģeö—ũĶUrîÚĻc/WM*ÛĩA’,=G鮊Ķt–*˜”kËüļųc�� �IDAT6ގ4´mąĘ$ĩmܙ]ŖũoW\ØYaVED˜$ųårē%ÕÜ(Ân?ÕËŖ(I'Ŋnš<’ŦFī?“"ėŒ^� %°��Đ(>ųBC4œyÛU{]˜šüō4z OÅū+Š’†c}æ“Įį“šš ‹¯¤0ø€n˛+Ǟ HžfĢå%~™l >v€’6EX‚ĮöėZ¨)Jëî,i¨îļ¯Ōrįnm)xI“$ÉŠ[‹$Yt×ÕFaø|ĄŅ/nlÛ>`Šļ­§ųO]†ûßTß÷Ė2›‚õV|žē7…ĩF’Uũk;Ģh—ŅûĪÜü]��$°��ĐHf™­’Ü6=¸jŋ^Kmũ›%ųŊ ķRņm–šū ‰|:ļ5_^IϤuíūôŠšZ^â—ėCĩôŗ9UŖNB\î•R¸€Eq>0F˗•jĮÖBÍHJ”ĘvkKĄ$[†L­ÖūŠBąĻ>zįč/uwĢ$Æû?ü×|ōųƒŸ„ũގžƒU6ĸ"Ôiéû��45X��hģâl’äQ™ŗ%ÖIļÉa—$¯Ęœõpņ8Uæ nՄEŠTļQ‹ļē%™Ô-ĢĸøŦO'ō‚ÅFŠŽHŌé“á• øaC'§ I*ÛĩQ’ė}GU[MG’=FQ&Iūr•5ēvMs1Ø˙ūr•…]ŸÛ#—+8­*ÂaĢķŠĢĖ>˜q‡ĻMYBĶ’Züū��MEĀ�@ŖØÕ-5R’_‡ļæ*ü#n‰íĖĶiWS–øq„ö/ۚv˙žÃģuĖ/ɖŦžÍY„Ä“§ŲĪžĄÃ~Éû¨Ļ ŦœTš‰Ų&]qmĶō­u ˇVj›ĄAą’œšÚy˛D;ļKŠÔŨ™‰5ˇŗĻ¨gœ$kËŽz&ŋ”åiĮū“jR×^Ŗũ_ĸũya‚O‘ö—H’MņaŠ´x wëX˜s:S(ˇ$S\b¨ŪLKß�� ŠX��h¤øŅO)Õ$ųsæjōú’Z#\Ú÷ʍ?F™SˇŠ)1:?đ¨’L’?gĄfīŦĩWŽf/Ø#¯LŠũ¨ē5ũ4Ēø\:ąuĄÆÜ7Vˋũ’%Y3Ū¯øĘ ŦŠ ē8Ŋuˇj ĐpåiŪŗ Uæˆ Ž#ät‡9g‡g&H*ÕžÕk‚Ķƒ4<ŠövvŨ?Ļ,’ OĶÛĩâOžæ=?AO?™ĨqĢ[dų$IFû߯cËæjsNōčØâ%Úį•dī¯ááŠ;ˇkŪâš÷RŲÍ[_*ɤn•KYˇüũ��š†,��4VÔ(Ŋųŗ<eŊ¸];§f*}EŠēÅŲdöšu"//¸ÂŽŊŪœ5Ŧq Uhû¨Ūüiž˛ĻîQöøA:–’Ļ΋ä.ÕĄÃųrz%[úKúŘ0Ë7Č­ÍĪgꐊęŸ×-—Ë]šŗÅ1T3–ĖÔũmkÖ é<ú)ĨŽžĨÅs•9$Ow%Ų‚í؟/_ęëZ=ÍĢÉũįĒ ä==÷c§îø¨ĻôĢj_ÔĀaJš_¤‚OWI’↠­āTąœŖ_ŒŅ¸ķõæČ~ĘNIVŧŨ*yKtl‘œ~ɒ2Sof5ōÜŊÛ4šw^ũ^ÍVŨ=kŊf¤ĘX˙[účĄôbMžˇ¯–÷LQŧMrĖÕžBˇüŠÔ iOVÖĩŠ.nØP™WVúūu‹‹”ŲWĒc9ų*ņJĻÄņÕFŠåīŋÚĘ6čšgWU[ÛŖ2ŋ$•hųøLmŠŧŸbôدčū¨æ8(��7��š jām ¯Tv^ĄvlđĘo˛Č•¨A™ŖôôØŠoh č+h›ųKírlĐĸek´¯ W›ķŧÁ:Žūz:ëI=×āĶáųå.)VÉ<&“,Ö%%%kĐĀQē`=ûĨĨIŗį¯ÔŽ‚=Ę.6ÉæHÔ]–jâ¸tEÉŖ/ėÖs‹ķur×™“FÕü~D OzCy~I >°ž€ÄĒģ^^­]ék´čÍ:TĢ-y~Éd“=ŽËzJOg&6!8đËí,oāsoiGMîŸYŠ/¯Đ ¸ˇ4īÃ\­Ûå–ßd‘=ąŋœ4UΤÖĶrĮ(­X›ĸE ŪÕæ]yrzũ2ŲbÕ3ķI͘4@ĩōŽŋ˙jžSŠNĢÚ÷“O%ĖJ�|‹ĩ Ön��Ā ­lĨ2ûĖUŠŋŪųíŨ}•_Û÷bĒÛāUÜ [´u\cG$�€ÖtæĖEGGWžĻ �����€A,������°������DĀ�����`En�����‰"ˇ������͌€�����Ā ������ƒX������ "`�����0ˆ€�����Ā ������ƒX������ "`�����0ˆ€�����Ā ������ƒX������ "`�����0ˆ€�����Ā ������ƒX������ "`�����0ˆ€�����Ā ������ƒX������ "`�����0ˆ€�����Ā ������ƒX������ ēššwX^^ŪÜģ�����0$22˛E÷ßėKK7�����āzÃ!������ƒX������ "`�����0ˆ€�����Ā ������ƒX������ "`�����0ˆ€�����Ā ������ƒX������ "`�����0ˆ€�����Ā ������ƒX������ "`�����0ˆ€�����Ā ������ƒX������ "`�����0ˆ€hq…šŪ=^Ž{ęDk7ĨŠNŋ́íã•ōJŪUlŧ[ĪuŒ—cÄJ•ĩxÃ����āúĐ‹G'vžĢÉOd*ŊK’íãåč˜Ēô{GëšųtĖÕ˛Gŋž8ĩoÅJíģĒ'ÎÆlûíszëģúøđˇęæi}Vģ:§§Š[œĨĩ[����×Ĩ– X<…zûáA4ū-ev˚”ĄáŒÔƒ“ĨbmYö˛†ß›ŠŲ‡=-քëŠ+W‹æ,Ņ–’fŪö[ᤞŋĨwrÜ­Ũo—ˆzíũeúEV\kˇ����ŽK7ˇĖnZ÷üXŊ™į•­īT-û¨:[ĢîĶéõĶ4î•íZūė9>ûPEĩLKŽž“y:-Šm3oû­ã)Ö Fö�����Ž3-2‚ŎĄæåxeŠ}^+ŪĒŽH’Ym3hé¤4ÅÅŲåsWÅâ҉õohܐžJė/Gû$%ö~@Ī-ÍQ™¯ÚfĄš}įŸ”ëđJ=7"´}ĮTĨ?üĒÖŽžqĄf÷Ž—cÄ••åhŪŗ™JīÜwĘŊc5{§ŗÎ9xNnĐėg+Ļ6%)ļû@eŊ¸R‡ÂÍLqåéƒGĢo÷TÅVk¯+t>›ŸHRܓÛå•WŲOÆËŅž§Ļ„ëš+m{•}ĶWNĩļÆËŅą§úŽxAoī¯ybžõŖåhßS“{tbũĢĘē7¸}l—žĘüņģځGŽB}üĘX ė^ņũžøÄĢú¸ Ú\Û4Ž{ŧŊ_ÕąZįqzÅŠmŸ¤ĖĨ%*[ņ€]_Ö~ŋä\6\Žöņ¸´j˜ĪÕ]ŗP=”ktē`ĨÆôN•ŖãX­ķ¨‘÷”$_‰vĖA™÷öTlĮx9:&)åŪҚŧēPM—•§ÉŨƒĩKNŸŪĻé ]įTĨyAԘÕĀy{ãŠ÷Šīđ4Ĩ´WʋyĒ{ûøtčŞÁûī°¯Ū,Žũ 5nHO%v Ũ/Ži`ęŸSûVLSVeĨ*}Äjö֒ĮwUŪ{NíxåĨtLRúë…MęQ����¸VZ`‹Oû>͕[ š0Jņæúˇl;f™ļŽŠųŨcķĮ(kYąŦ‰#õôOSeöęäΕúxÁ8Ę{]ëßĻ(I2›e–ä:<Wcö[4|ÂMtXä)^Šé/ŽÕ”'ÍjûÅKę,I2wīŪĻÉĪz•õŧ–ž`—Üųzgæ,-ūŲ?ûT‡†Œø ŪPÖÃĢt:"M÷OxDŠ’įän-_1WæęĪčîˆP“]Û4îžIÚé‹QŋĖņzĖ!•äŦ ļ÷äbmũy†ēMZŦŲ–YšąÍ­Ôą ôXŠEmázÄÚĀļč›úxr4yä8eģ"Õ3kŧ‹ŗHžbí˙tŪ|˛PÎU[õZj肙Í2ÉŖ‹'č9sĸ&Î]­×,^Ū°P“—ÍŌ8E*įįéjāōJŽŨš<r‚˛=ĄžI˛IŽBmųpf<œ¯’÷×kFĒ98ũäåm:öüZM^<J['Å÷[ļAĶI‰ĪëÍqE”Ŋ¤w|K4}AŽÔwĒ^#kœŊŅ×Ėl–äËŅĸWŠåKĨɎØā}Ú¨{ĘĨÍĪfibŽ7`”ĻŒŽ‘Unl]ŖOfŽÖ Ījmרé4ÁãËšQĪ7ŠķčŠZņ˛Mrå띙sõÚĨō}ļ^Ī´ŊÂy\íŊ’4TwÛ6ꓜm:æKQˇęĶW¨ėˇdDÃSÍŌéē­õŧĄŦ'WŠÄ–Ŧ'=¯ÔŋĘvŽŅsĪZT'S•SëžČŌ”)nĀPÍ+̜:ŧz•–?ŸŠceĢĩz\đē{Á#×ę74ģØĻÁÆËgkd_���Ā5hv'?íˆų÷§ŋū[#ŋú§ÃÚÅ: ^ø}ūøÍÄô@Lģô¤}_ר6æßGŪ˙Sõmŋ|9%=Ķ.#đĶUmúŲ=q˜v)Ņ[ūRsĪ[Æ:´‹ [^VsÛ^3_Öj˙ßöŊHnH{­ ōX{CĮzųĐ×ÕļüK`Ķ3)5Úđ§åÃ1íR“ö]E7„Ûļ1}S¯- ŒžX`ôōĶĩøa`@ģ¸@‡gv*öđõ–§ÚÅbîYø¯ģ õĪ^ ėmøh/gdbū}XāW'jĩë/[Ŗˆüa ęŌũ%°é™ôāõü}Åë”j¯CŽÍ ¤ĩ‹ ¤Ŋyĸڛšf÷Bb`X˜~¸ę{ęoû?}hX`ĀÄõšwTAđū˙™/ĢŊ÷ō„ú˛Á> m×.1đ@vÍŊ~}hf ­]\ aĘūĐ5ēōy\ÍŊō_ËÄ´Kŧ|¨æ.ž>TŅoĄūũ˛Ā€vqä‡+÷õëĮ1í~zŦúõ ]÷vq˜ûĢŽīßvŒ$´K <đIY Ļ́÷īO ū{:åŠ{¯C¯—{ûo����\#ĨĨĨ5^ˇĀ!ˇ\If›ĸęū7vƒĘvmS¤ÎYCkÕąęîŦ4ŲäÖž]ĩĻ $ ĶāÃ6ˊŠ‹ ļŖöTS˛ėQã-ĢŨŽI.g¨hęÉíÚQ"ŲĶ3ÔÖį’ËUõã‹ËĐ]6Éy8/ôú…Ú’ã–ėz0ĩú˙Gčîi+ôÉĒzĐŪ¸>¨O“úĻsŌSZņëõZ1ĻÚđŸOžģښ$™SĩģĖŅwh­QHvÅŲ%yÁë\¯Beo-—iēËæŠŅ.%jP’I*Ū­}•ŒĐāY3ÕĪZ¤y3×čÄūˇ4{—OI“æTŽ,ĒWŖŽY…D vŅÕŨSÖtÍøhŊļū|˜Ēî(Ÿ|žH9ė’Üå*kjũfSĸ†×ēOÍIęf‘ŧyšW<ÆÜ+fČ.ˇvl­~īøtl}ŽÜŠŅāĖzFáø ĩŋĀ/ŲĶ4<Šú bVˇŦĒŲ"ö}š+¯b5(ŨTķ^pYÔ­oŦäĪĶ΂š•ŦéCuW#˙ ���€ÖŌBEnƒŽļ,H…˛“å’ljQ÷C{Ŧĸ$8ōH•SL6›ęlzŪķÕn€ÕψÚsZBĶB<Ą}%%*“ä˙tœR?­§ĄžR•Ijë*Õiˇ¤”Ø:SsĖQqę֌…{›Ō7ḠÖhŅâ5ÚWP"§×_ëS_ká¨=5Ã,™ƒSŽęôoë´WRņ{ÔãŊz6rĒÄ-U^ˆ Ŋ6ĢŋŽC™~)qĒŪSORŊՍšf¯mvĩ Ķ•ŌÕßSž˛}°ø=eįĢĖíUÍŪlėŨ_5Ļn8iļ)"BR™[e’â+ŪsēW’†j°c•ŪÉŲ cJ Nǘûˆ†×7ËÉUŽ2¯¤¸ē÷žąŠ2IUÕqœ:éôKĘ׌>wiF}ģtē%U%’Qq1õl ����ןXlŠŠTâT™KĒû¤Z?ŸĪ#É&k¸ÂfKđ×ã¯õčjēú˜ŦjCđAŲ>ėuŊ60|ŨŗŲ|Āõxä“d …4-Ši}S“g˙4e>šQN[˛›4G=öĐū ĩčášÚn×Mop°-ąčIiá÷cļ*ĒÖŸˆžCu—mģ˛Ũ&ĨföŋēU—sÍ*ß°4pnWqOšļ鹑“´ĶŖ~cĻjJŠ]Vŗ$Ÿvž>FīŠÉj6‡ É˞˜¤ā(U]˜0įҏ{%NƒúÆčešÚR uN’|ĩĪ-%P˙ûē÷kŋįļŲ”ŦÉŋz˛æu¨ök­0Īz_���āzŅ‹CŠąŊSR¨9.=”ŲPÂâŌéĶfĩm|œ4[­’üō„K |ŪāĄÕÔĸa†Ųb >^[cuWĪ+)ĩZe•ä÷yåQŖ˛¤ÆˇËp߸´yŲF9ĢÉĢ>Ŧ,”üžģųû4Ô72Ûß3ŊáâģU Ņąųs•íļÉa÷čđüYÚÜ÷—|…ŽmÔ5k&'>\ĸn“Rg­ĐŌŦę té„ŅÎôųÂŦBäSpĀ‘9|pRMcaCåXö–vl-Ԍ¤Øāô S‚&öm`~[(Xņ‡‚´MōyäĢ1œĮĘ6͊JJgÚ���€o¤¨ÁbVˇŦū˛É¯ũ‹—čPu(ĘÖŋŦĖū=•š"¸LrÛP‹Ķ'ëŽķę+)ÖiIv‡ŖÁ)0F™EIr,ĒSDōÉSũŠ5"Fmm’JNÖ]dĨ,GëV¯ŅŽÚKû6‘ņž)W‰S’-Vk KđäčDíŲBFUöMžN„éŸ'L„P°D“?,•ũųZ˙ŪO”äÛŖé3ˇ…š55ęš5W™S’]“jĨ?ž"í?ipįîŌāč¯ę|å:]&)Ę~Å ¯Ņ÷JÛ •œģļéDhz)i˜în(‹°+ĘjkíĪBĮ¨bW’Ã$ų‹u8\ßø<M\Ö����Ž-°HæÔņšŅ×&9×jÜohGYí\NŦ~AY¯äĘkËĐĶƒ˙S‘>@I&éØęĩĐ\ÚņényŠģÆļD“Ģ´Ēģ’?oĨŪŠUtĶW°P™ŨS•š´ĸēDĸĨÛ$÷v-ßZũaÖŖ}‹_֔™īę°ˇÆÚˇō5X¸¤ÆŅjlkŧo,˛Y$yÜ5‹Ķú ĩhūyL’ü^#•CjIÔ ž6ɛĢwV”Ôܯ'GĶīëĻÄ'6T"žB-šúžJlCõÚ¤YÛ>Ē×&Äʡk–Ļo xĒ%BēfÍÃjĩJōĘåĒ~<Í_ĸC>Šr*O“ꓭ5ÛëŲĩA‡ü’-1íŠĶϝ8483VrækËúÚį6Š[fŸ†ƒs‚R“LRÉneÔėƒ+ļËYscu–&‹ÜÚŧlC­@ÆĨuĪöSJ÷´ƒ”���Ā Ŧ…ŠÜFhđ[KåyvœfäŦŌĶ}6ʑ’ŦÎv›ĖrëD^Ž œ~ÉŪGŗ5GwW<ÉEŌk6*sÁ[ĘzØŠ§3S!¯ v­ÔĮģŧ˛›Ŗ‰I-]—ÁĄ‰ŗŅŽ'ViųÙryT=ãLōÜ­å+ö¨ÄÚG‹*Wm1ĢÛ¤Šę—3I;_ĖŌ˜ÃŖÔĶ!•äŦŅēˇl}įëé¤ā–ÖˆH™TŦ}ËfémgŦĸRGip\øs ŋ­ŅžqčZ´ WŗŸ](_VĸĖŽ<eē]ža3õ´&čÍÂŨZž:QĻgÔS'Ŗ1Ėę6aφįLPö‚,e•<Šá=í2ģ ĩåÃ5ÚīŒÔđYĄ‡xŸŽ-žĻwJl´djå’ø13õІ,-e–6§„Ļ ŲАT°kĄf;2dˇ§éņ~šfÍ#>ŗėŸŽÕæ™/Č1a˜ĸäÔáõ+ĩÃü”^ËZЉ+{ŲEeĻéŽÆÎZ˛ĮJŽUVÉŖžj“Ęvkų˛=ōš4qlʕ§s5áīQTߥJš3WĪwĘkJĢŗŠQ]<v€åmÔōgGË3z˜’Ŧ•ėڨ͞ĨZöčpĩ­­ũ^Ԍô"MÉyY™†ÚäÔáõĢôIžOqceę���€ZË­"dŽĶCīīTˇkôΧu¨ WŲy~Éd‘=.EŽ~DeĨĢmį<ŗâĮ­Đú¨%Z´tˇŊ˛V^Yds$ęūYs41+ąEëœTļ"õ%­˙uŦ-^ĨĢßĐ¯_&[Œ:œĒFéŽęS'"héZ‹ŪžŋDŸl]ĸũ^ÉbÕŨ/ĖԔqé•íĩö¯)éÅZ”ŗQo–Dj¸}hũKØm­†ûĻí¸Åú…{–æ­_Š)ĪKGĸ^Ē)Yqō9FjËÔĩĘ~ũ éWiz­9:2"CoŽ]­¤ÅKôÉŽ÷4{ƒW˛ØÔ6i¨fŋ5^%Ÿ¨}K4yYŠ,}įkFŋjOŲæDM™5R;YĢé3ˇŠÛ¯("ĸŋόŨĻįVäkųüb9˛bõx?GãŽY30'Ŋ¤sũšžxˇMŨ#ŲbÕmāO´zŌ�ĩu™´3gŽļ|8WŗÍ˰˜S4cIĸv.XĸE¯”Čé5˞8Tŗ_žĒ‡ŽĻęoSūEehxâ\Í(ôĘŌwØU…֞3ĩz‰Eŗl׿ŗ´ŲŠøô§´ô­XmšoûǝJe×ũī¯VĊ%zįĶ=š÷ĘZųe‘Ũ‘ŦĮæŽ×Äˏ/ ����-ŠM ´v#�HRĄĻwĪŌ'Ö'ĩåķŸ4Ã("����@K9sæŒĸŖŖ+_ˇH �����€o������ƒX������ ĸ �����@#Qƒ����� ™°������DĀ�����` �����€A,������°������DĀ�����` �����€A,������°������DĀ�����` �����€A,������°������DĀ�����` �����€A77÷Ë˲{—������†DFFļčū›=`ié�����\o˜"�����` �����€A,������°������DĀ�����` �����€A,������°������DĀ�����` �����€A,������°������DĀ�����` �����€A,������Ũ\ß(+×ŌVéÜß˙Ą@ p-Û|#´iĶF–ÛnÕS=Ŧčŗˇvs�����-(ė–˛¯ūŦŋX*īšŋŽ�MäņžĶÂÅËTVūįÖn���� … X–~đ‘"X kĶFĀe-]ūQkˇ����ЂÂ,û›÷ZˇøæjĶFķđw ����žÉÂ,Œ^šSí����ā›U„������ "`�����0ˆ€�����Ā ������ƒX������ "`�����0čæ–Ûķ?뇉ÕåßÛÉūũÛuëÍõ¯[ÎS'uøHąÎœģØb‡�����¸–Z$`ą´ī¯ĮFũ‡ÚYj}đ/w*Ļm'ĨõûĢNėÜ öž‘ˇ%������p 5sĀrŗūĨ[–&fļ“ĩĄÍžķ=Åx\/ü`Ŋ~ą˛@˙sÅũ~WöÄtõK‹UģČ–õ;Ō?ūĮ­ß˙חÚų˙Øģ÷ā¨Žß÷_rģë¨5A˛Ü<do|×p Ļĸ9#W!ĸ ąCbąącßą;ĮI<ÉdĮĮdb’`Ođ Wā TYsGžUČQ€-^XĨB"’‚4îŽę>ÃũC´ŒėāĮ÷SE•{īÕk¯ĩWãĒũc­ĩĢj‰&ŦŸjá’åüxaŋ~ė ö¤9šųUžŦHąúņWy§ßˆ\˙ĩī°|J°ķĮøˇŸ=Ī[­¯ÍÕÅú8pLũÚwøFh#ß^YKü’^K’$I’ôi7 KhlßLŽ$[Ų]ĩ‰MNf1rfËįŒâĒ) ųæÂ<ą1FßĪûŲLŊmËgdq|īŪ\{˜ļx€!ãĻ2ŋ¤‚'ņë_žÁžŽėÉe’;—Gnķú?W€ę⡰f]ŠŖ—ĸūļwYŗö]ūtÁ‰ĮÛ3­\’$I’¤Oˇ Xō(­¸Ģ.8g÷ÚÕŦÚu6iËí‚9�AF–,dnÍķlîcDxæ"–ÍČĸ~Ũ*ž¯9qöÄûûŲVs˜{(įËåīR÷JDŽ4ŸLĄü1Œ œ¸xÁ~Jĩ6°­×}Đú­>Ĩe`j“$I’$é3eĀ–ĀØ˜="͉xÕûēÕ@îĘsU~ŪŲķÁQĖ/ÉcķÆæ4_ÎėyE >ō&¯×¤ Úw°æ—1ho>Ž„ K¨X8‡ių9 &ÎņÆũlÚPÉÛĮē×…K–ķƒŌŦ~Š•™s(ÎÍ&˜8ÁîĒõŦŠi&Õŗ4d9yjß––Žgd8 ÚŖT­{ƒˇ^`ÄäR—MĄhDâ­Dk7ņÚÆŊ´œIzŒ˜^ƗK'‘E˛­™Ũ›7˛ŽĻ™đüûøŪMš@>9‡Ãë~ÎĪk:zuŽ€Ĩ~ƒHÍsütsOßŗgņ­ī—Ųû ßū×ũ=}EåûÍ�� �IDAT.`é÷–3zĶ/X•ZtfųĖņ4õ?ŸėšåšSXZQĘėkr ķX¯žg&T8ĢįŪį28˜ädS”moŽgÃû]į•™Į´ü‚Š īYhÔ ĘËįq}a.ƒéäøĄZū°ŽŠēķfÉ [Âåķ˜–›E˛ŗ™w*×ķ‡]'úũ;€�Ŗg–ąxÁD"#Âo'v`+6ÔĐĐ×l¨ė‰Üqī-DßāŠWöģ$I’$éŒ{Mķ° ‘ôûŽÄģˆ÷<õFæ–ą`ÖTĻåŸģûíU…†ĨûnV“ō!ļ¯ĄĪ}Z:[›é<ũT[Â=w•2ĻuĪ?ķ$˙ķ™WŲÔáËwßÂėœî"Šd ˛&rķü�ÕŋyšGû OlJ1­b!ss�R¤Rŧfåų{xņŸÆ#˙‚u-ÜŧdŖ{.šŧˆûoŸAhßFžúÉĪyâĨ­Ä',âū% õ” Oŋ…—ŒįdõzžúŲ*Vmîĸ¨b9ËĻgĶRŊŠgˇļCÛvžũáynGįjfwcœ‘ã ÎÔ¸ĻˆŅÄķ‹(8],7B$̃†ƒį~ŋĪúƒy”–sŧęUžøŲ¯yũ@€™gúžŦ‰,ŊŗœIÛYõ˟ķøĪVņÚÁ,æß~+7žŽ;g+î*Ŗ iĢ~ųOŊü.É)K¸ŋb|wsfpĮŨLKŊˋĪüœĮŸYOM`+î,#Ō; F(+ģšú7ĪĪŦfŨĄ,fåVnuúž\üwž~ ßZ:žxÍ<ņ“'ųŅoĢ8ž_Ę7˙v.érBy|éöEˇWņĢĩ†+’$I’¤s XĀrU8û⅂}L˜–›>œ į2„$míį‡éEJæ0&ą—×ÖÕmíĸŗ5ĘÛëĒØ(būôág›LņŪæęž™ )Zjß%F‘ü^í DŲ\Ų@[ Hu°sW”ä°< ˛†2{ūTB+YUšŸŖí´ÚÁš &ĪëyˆĘĖšÅ°¯’55 mmĻĄf=¯oŠ’ %JOt_?Օ uÁô‘Ņ}1(s&\ˆ\[@gm-õÁÆįv Žad"Ę{ĮÎ˙z_õ8\ĩžÍī7ĶŌc[ÕvŸß÷´Büé9ˆ˛á—ŋâŠĩ54ë ­ĩ™=U[Š'i…YŨíŸ;ĸÎŧžž–†c'8ú~5¯oØN”lÂĀč’yLb¯Ŋ\M]km­ ŧõō&ĸ9ŗ(œuNkęĢÖķöé>ŦĢĸ.žË´yũü gö‚b‚{ĢXSĨĨŊ‹ļÆŊŧžn/ņü9,(<ŋīŲ\ÛWš)XËĒ—jzÍP’$I’$ŠÛ%yM3�Ûyö7ŌĐëPÃڟpßZ]v.Č={"Ų×kœdŋ™ÍčühÚÄŅŪÕ%š9Ü Å…Ã ĐŗÔ&y‚hS¯2Šq ÔûB-Į8ŪĢžT<E’�Ą ČŖ(ŽWÅΙÉ?RĪŅ`Eų6wö”ŲÕ{éM‚ēĘWŠëW ŪXO,pEšPwl8“ÆÁá ÛiȟÅõ…Yŧ՚"2Ą€äÁíDáĖL—•lĻŽŠWĮ≠ûžNū<ũņŧ4|—_=žž:ÄC”—-$2"›!Ąî�&  8=>ÉÖ?rŧ××ÛvmdÕ.€lfæBĶzŋĒ+Ę{íAŒÍƒ]ŅžƒÍ44öœfęÛĄ8w8ē.ū;˜\8ž#vÎہRM‡‰q#ķŗĄņt#˛ŋđĢ,ģ&ƚ§+Īm›$I’$I=,`ųSKũ{Ä02w蹇:[Ķŧčėād2ČUųÃaWē=ZzË"‚d[‚ä9ĮÄã fqöEÃũ™‚:¯ž^Y‚Pp͎øÅMžŽg)“ēpjJ˙ĩGih/%rM6tFˆ„›ŲÖØÁáÆ.ĘĮØŲEqa€hUô#ėŸ’ę_÷Ī×ō.Ģ×nŋpŠV˛Ģ;0éY–3d_%k^nāxg ‚Yöōžû ÚR}ŧō8@(äWđ䓜Mļdũą&S|pNБ"‡`0@°?ŋƒ�„‚?lR RIčõ×ĸ°”åã‚“)†„€OãÛĒ$I’$I—Ü€,-‡ĸü‰Qgß"žČ—˙.“íīōÚ+;hFĪũ*‹§g3,?xÎwī‹ĻßĶ"ŅL}+Lš<•Ņ•Íg_?ÜKxō\f&÷RũūšAĘŲGįs¸ƒięøČR âIˆU˙ŽÕ;ÎâNīė:S朇õŦ™÷&¸qláÎ"FˇæpZÄ b #s:‰„›yįĐe˜V‘j%Öëķ-B#&_Į˜Ô~~Ŋv §o~6į„Z§Į'iB–îķ|“'Ö5\nĨâŊÆ4@0 Hôú|fŒûņ;HA< Ã˛˛8'1 d]ŧ$ĸüaå&B —Sžd.õ+ĢĪ#I’$I¸ ÛŲ|¤÷cq˜‘×\MQa.ƒ{Ž„ō (ēfWõN9’‡Ų´Ŗ¯W Ÿ`ۖz>Q²˛‚ Ō @î,–-ų+Jg 'HG›Ú!ĖšĸfåQ” ąC™ŋ%įŒT3ŅĻ$Ãr˛hk=A˙?]¤R]t&N—‘ zÍë Pŧpŋhb˙–ķ�Ņ÷ŖPXÄėÉyďôŧ&šŠžX8Bņô"FļGŠoŋH%—A0œŠÄŲ ‡a3Ž#=)Ką4ãžžˆŋ˙ģRÆēˆ5ļBN.ÁöŪ÷´ƒx*ÁÉŽ^ķ(:gŋœ<"9ĐÖÚAĒ?ŋƒTwp7r\Ū9ã(ÃHډ5õ ]ZöđNcŒˇÖVË_žŌŧK¸ŽN’$I’ôi5p '¨^WCŦĪu5é$9\š‘ˇ?dŲEįÎõŧ¸Ŗƒ‘ –ķŨ;˘?}"Å×NdvŲ-<xo9Eí[YŊށ8­ŪB}ÖWĖ ’“M87KJ™”¨csm˙6ĘíŸļmŪOjrËįF‘Í°Ü7Ūļ‚īŪģˆâŦî2;ĢëHN(㎚ã=*â’E,.Nŧą{īd<áƏΈėôíЃõDÃY0!‹Ŗī÷,“JĨoĪcÁŧâëŌÎėĄŸõ”ļC1>gūĖ<ÂŲC‰Ė\Äc;¨ī„a…y„p´z+‡ŗĻ°xÉ,ƏĘ#2y.ËƝãĒöÃMÁҚ-ԇf°Ŧb‘ÜlÂ9yĪŋ•GZAyáší/.-ãúQC ggęÂL ŖfG÷ũšøī ƒ›ęHN(eYIĪøÎ`iųˇ°š1M[kxqcŒa nš -’$I’$ č“bęXŋz%›ûoģŽ‘]‹“$ļõUž¯îköĘi]Ô­}ž'ö-āæųS([2‡ÁÁ$´5SWķ ĢĢöŸ}ĢKûV­ °táîy¨‚ÁtrüĐ~VŋPÉ;ŧwF|ßzž][ĘâŌ[x´<L0ŲÉņC{YķBu=KW:w­įšP‹į߃å!čü#ģ7ūž×wv7Ļe×Vv—,äæģīaæĻUü´2Í>3‰(uMa&]s˜÷Žœîh GÜ<'ĀÎ}}īMs~ũĪõĩžg�Ä÷U˛fë­,Žø&?Ļ“Ø-ŧžv,Î= nåŅäīyl} Ν„Ĩ įąâŪ‚Švĸĩëxvcw@F{-Ģ^€ōōyÜķ@ƒ‰s˛5ĘļWVķfĪĻļÁ@€dgĒZ™štËōŗHļÅØöĘzŪjíiL?~ģŪāš@‹ÜĘŖ!ˆˇŨWÉķvôųJđļšõŦ›|Ën+c÷3ŨđV’$I’tÆ S§N:˙ā}ßū~F•† gątI)3G„ m+O=QIŋä;Ü?+ņcÔlXĪēÍ}lx*}öüâÉ^é&H’$I’H4%‰œų|IÖ:Äwđâ?īåÍą™V˜ādĪņ“ļđ‡1vī‹ŌæFĄ’$I’$é3ân&‘ åP-o:{¤eWuŸoĄ‘$I’$Iú´ĀMn%I’$I’>Ÿ X$I’$I’2dĀ"I’$I’”!I’$I’¤ °H’$I’$eȀE’$I’$)Cé–A—šŌgÜ ˙NI’$IŌgZڀå‹á!pęÔån‹ôŲtę_2äJˇB’$I’t Ĩ Xūöö[á ū“ģ4 ž0ˆģžžėJˇB’$I’t Ĩ X"WæÁ{ī"œ}šÛ#}Ļ„ŗ˙‚‡î웂QyWē)’$I’¤KhĐŠSŽ’$I’$Iú(ĸŅ(‘HäĖgß"$I’$I’”!I’$I’¤ °H’$I’$eȀE’$I’$)C,’$I’$I2`‘$I’$Iʐ‹$I’$IR† X$I’$I’2dĀ"I’$I’”!I’$I’¤ °H’$I’$eȀE’$I’$)C,’$I’$I2`‘$I’$Iʐ‹$I’$IR†]aSSĶ@W)I’$I’”‘üüüKZ˙€,—ēÁ’$I’$IŸ4.’$I’$Iʐ‹$I’$IR† X$I’$I’2dĀ"I’$I’”!I’$I’¤ °H’$I’$eȀE’$I’$)C,’$I’$I2`‘$I’$Iʐ‹$I’$IR† X$I’$I’2dĀ"I’$I’”!I’$I’¤ °H’$I’$eȀE’$I’$)CžN‰5ąōwŋ§ë??āÔŠS—ŗMŌ3hĐ Â1˜˗šzô•nŽ$I’$éS"í –Øąfžzn%]˙i¸ĸĪ•S§Nq˛ŗ‹Ÿ˙â×̚¯ts$I’$IŸi–•ŋ[Ã) Vô95h§Nũ+W¯šŌ-‘$I’$}J¤ XūüįÎËŨé“eĐ ū|Ōŋ’$I’¤ūI°8{EÂåq’$I’¤~ķ-B’$I’$I2`‘$I’$Iʐ‹$I’$IR† X$I’$I’2dĀ"I’$I’”!I’$I’¤ .]Í،;žâ W32œE0•ādËŠ;Đ@ũą.R—ė’$I’$I—×%XŒ˜^ʗËgQžwîܔää‘Zū°ļ’wZY$I’$IŌ§ß�,Ų/ü*wĖÅā>ËrÍ ,ûJ;ągĒiéG­á”Îŋiãō¸*$ŲŲJėā~6Wm2¤9-ˇ”ī><ƒú_ūŒ×ĶŸË#Ī!öÂĪxųĐG¯ūĖ\3œ!áÄ;ikŠRSŊ‰ÍûN\IĻ~í;|#´‘o¯Ŧ%~Ų¯/I’$IŌY°=˙VVĖÅéy+4ÕąmĮęšēHāĒŠÜ8ī: âÛYõ›ū…+#f~•û—Á‘wŲ´a G;S„†1sn ˘HŅoWņúĄÄĀuã˛‰°ôŅRޝ\Åæö¨Žs˙öú1:Ûēū�#JnåūŠž1xŗ{ ááLš5‡›nŋ‡ioŽâšÍÍ.û’$I’$}n \Ā’;‹ĨĨW÷„+Ib[^åWčėU¤áPÛjļ0‚´ô'É-aYEÉÚWxę•ũŊęjāīōĨģVpķ’l{ĸ’č€uä2ÉÉctŽT}‰föėløúG•°ŧĸˆäŽåŠĩ įŒÁžĩė\´‚oÎ[Ā´¯ōNWĻ“$I’$éĶiĀ–ČÜ9Œé™ēōÁ„+g$Nôkæ @¤dc¨gõēũ֕jæ­WVQ—<ÁŅĶĮ˛ĮķĨŠRæŽÎU!8ŲegU%võ,aɝË#\GŨo+ų`A ‡ĸ‹Øž*ÖŦŨK .YÎJO°úĨVfVĖĄ87›`âģĢÖŗĻæė,Đ¨”—ĪãúÂ\ĶÉņCĩüa]uŊf‹„ gQąpĶōsĻZ‰Önâĩ{i)\Č?Ũ}C€1ßų!å{×ōíŨÛkH€Šˇ=Ėōp%¯Ŧíé{å}“ŌĐvžũĮ4ô.ÚČcFņāé%B4õWöÜļP7Ū&bHĒÃĩUŧ¸~/m¤7ždņ:VoH7ž Ö?Īˇ×§ÎŊrąq�B…%T,œÃ´üįxã~6m¨äíc§“ˇ�Ŗg.dié Â)N6Öō‡ Į(šw!ŧōVíK×ÚlŠį/¤|^„‘á�ÉļfvoŪČēšf—I’$I’.Š Xō˜6.§įŋÛŲVU{ÁÃx Đ÷ĨRŠt‹K†3~\4nĸޝŲ.íÍgÕ@åwßĘüT-k~û*õ Ļ—ąė+Ë’ú/îëŠ$8”Ųå×ņæëĢxėX‚PaŪģˆÅx~g‚T2Yšy~;¯ũæi^ė 0bîr­XČÜ=ËmrfpĮŨ4ū/>ŗ—ã įúŠEŦ¸3ĀsĪTM9ŗXqWĄYĩ.F<<‘ōĨK¸?˜āGk+yâĨ,ž{ûpĒž^ÍÛ­‰ķ–פh8ƒō" ĩÔĨ€ė"áNNĻ ˆäBCk÷}/* ĢŽgÔŲ¯JSÎ\ @qi)-•üĒ*AhÜ–W,bicwßĶAäš0ÉÆēžĮ€ķ•ūŒCn ÷ÜUĘā]y~mb8ĶJņåģo!õĖīŲցąeŦX:‘“[ŪāŠęf‚…s(_瀑Aˆ’âŸn€ČÂå|ŗ$EõÚßŗĒą‹!c°Ŧb9w$Ÿįų}u@’$I’¤Œ PĀ’Í°ĶųJ2F}Ķų§gņÍī—S”öģIŪ{ųg<ŋëü'ølŽ ÃMũš};‡Gt°ųéŧsŦûXŨæõŧ9ųÛ,ž;‘đžĶĄO?í¨:3S"Ūø.ģ[æ0ģp8ėŒu—Ļxos5 ]�)Zjß%V^F$?�í)F—Ėc{øõËÕ=ÁCoŊŧ‰Iß)Ŗtō&VíJ™;ĸÎ<ĩžļ{ųŌąj^ߐÃâ Ų„IŅ’L)R‰ņ4ųRüP=ąĀŠsĄîŽ)btK-o'fQT˜Å[­ ȉPî îĀųáAŠÎ´õ‡H¨äåĶK‰Zˇ°s~13{õ=Ũ$͌ÁYĒ;héĪ8\U2‡1‰Ŋ<ģŽļ;Œĸ‹ˇ×U1é{K˜?}8Û6Ÿ 2k"WĩÕōâÆũŨ!Z{%kÂü <;M;Ŧ‰”•äĢzŽ×w� mįF^+Œp˙‚Ŋŗęl'I’$IŌ�ø×4§ 턔> ĻŨrē’ū5qä¸<Į›Šoí}´‹Ŗíg\ÍHN,qގô$œLA Øë:ÉD{‡DŠq �ČĻ 0šļĐĐ;ęŠō^{cķ`× Fįįlũã9{ ´íÚČĒ]ũę´Gih/eü5ŲpŦ‹Čĩġ°ŗs<ŗĮØŲ@pėFvFyŊČíOĨqėĩO ]´%Îë{ZįĪËƒ˙Ŋķ9~´ųD?ÆĄ‘ų9Đ´‰ŖŊ'‰fˇBqáp$™&ŲôGzĮ>mĸ'/}GŒĄ ØÎÎC'zL}ŋ™äœ"ŲpÔ=b$I’$I—Č�,]´ĩ#€ĐpFįB]īėÔ ŪĢŨÃÉ^—6ޘ1a€$ņDēõ']oƒÁšC Cúũ\z f • y^¸“LĨ +ëĖ›ē ‹õįà …€ü ž|˛â‚ŗÉ–,†€ļT{4ķŪÁ7^[@ æ‘kDßlæh*ŗÆ0’(CƐlÜJēš'éõ§īŊupŧįœ7íĩŦųe´įžgūíg–‹CĄ$Û$Ī)‘ ‡`°§L’Šķ–NÅÛ{ũ†Î“•E€ÜûC\p˛ĩ{Ė X$I’$I—Č�,'x¯ą“Ōa`sDx{môl¸ˆōÖ+ŅŗÅŗ&˛â;Å=šŠoJ÷ÔßA´ąJŽcfΎô¯Ν—Æuą­&ʉ˛8'Df qūÃ|&RÄãĀÁ7yb]ÃõĻâ¤Č:„āc‡,Ņ÷c°°ˆ‚œl&å6ŗ­1&žC$'Ģ DĢb—đõČDļCÉTĻeīāíĶEĒ‹Ŗg͊“ŊnÂÅĮĄwŌģHīā%E* Á@€sĒ }ČũL$HŅNÍKŋ§ęü]”S):âUؒ$I’$õá SMІ-ĩg–Ã\5ëî(ŪGz3”ë—,dZ¨ûSōČv÷ņđ­ŲĘaŽĻlÉ,F\°§i_Z˛ˆ›įsU�Ú6ķA(ĸs–Ę %’ŸC˛é¸˙FąÆVČÉ%Ø~‚–ÖĶ:ˆ§œėJu—ij‡ü1Dzĩ;<}˙wĨŒ?sėÃķ­Ô‘:ކ ˜6ŊˆŅ­õN�‰õ-Ù4=Âøœģ~ØûŽ3ĪĪĸÕ[8ĖnZ:ƒaé ää12töãÅĮĄ‹Ŗiî YŨ߉j&EĮÛãG\ÍČ^E†M(>įķ9ZMfsUVĸטœ Ĩ3E*Ūŋ}|$I’$Iú¸(`Žmâĩ-§×…™Tq~­”Ų׿1,;›pN㧗đ•ŋģ‡åSÂ=åZŠŪ¸ŖĪWĶZËëꈏ+įŅnĨŧd S¯Īõ%eŦx`7ˆņ‡—ĢˆĻ uh oˇ 寊2ĻŽJ8{8ÅķQvM;Û6íĐėŖ5[¨Í`YÅ "šŨ}+ž+>´‚ōÂîÔāhõVgMaņ’YŒ•Gdō\–•_ĮUí‡ģ÷I¤ˆ3”â ŒČÍN…tEŲŨ:œŲķ H‰õŧŪēƒhc‚Čŧ9´ÖSßײ—ūÔßí;xqí’*xäĄî1(ž6Bņä|iŅrūáĄrŠ:ßå͞eû3Ņę-ÔgMaqÅ "9Ų„s#ܸ¤”I‰:6×v�)ĸ;89b‹įG–=”ŅĶËX6+Ģī%B‰ũlÚŅEäĻ[(ŸÜũ›Q8ƒĨwßĮ#_™A¸¯īI’$I’4�p“Û Īęđr–ÍČ!H‘SæąlĘŧ>ĘwōŪēWŲĐøá \ÚvžĘO›fPV:‡ëËQ ’ėl%z ŠgĢjh8=û%Õˆ^僊Rž|÷†„’œljāí—VŗáĐ�/ĸi¯eÕ P^>{¨`0qNļFŲöĘjŪ<ŨŸöž_ KÎcÅŊ9SíDk×ņėÆ†î°§i;›N¤ŧâ<xp?]™.hę ū`‹įeŗûũŗ;­ÄÅĖ›ĘÉÚ(ᝆ9ãüú×}üîļízƒŸļÔQZ:‡ëKQB˛“?5ÅØŊņwTíˆŌyú÷gÚw°je€Ĩ pĪC Ļ“ã‡öŗú…JŪé Œâīod՛‹XļāĢü 4ÅņC;øÃē­”Ū[ÖĮr¯ ëWŗ*QFyÅræ‡Co%睒į7\øÚpI’$I’Ō S§N:˙ā}ßū~Uf™ģˆeĨÅį,é-Ųy˜ęuëYˇīü× Kg˛˛zöVéQXÆ?Ü;‘ŨO?Íēc—§ ŋxō‡—įB’$I’¤O•h4J$9ķyā_ĶL‚hõĢühĮpŠ'O¤xÜՌ g$ÁÉļÔŋŋ‡ŨšĪÎxŌŒ]ÄîOėÍõlØu‚d8š g0¤eÛ.S¸"I’$IR]‚€ĨGâu;ĢŠÛyÉŽ Ī°ÔĄüęõ2/¸…o Lv;°ƒÕ¯T ā†Å’$I’$ ŒK°HIqtįFžÛšņJ7D’$I’¤‹¸ˇI’$I’$}N°H’$I’$eȀE’$I’$)C,’$I’$I2`‘$I’$IĘPú€eĐen…ô 4Čŋ’$I’¤~J°|1<NēÜm‘>9Nâ‹C†\éVH’$I’>%Ō,{û­đ˙ų^Ÿc_Ä]__vĨ[!I’$Iú”H°DŽ̓÷ŪE8;ûrˇGēâÂŲÁC÷ŨMÁ¨ŧ+ŨI’$IŌ§Ä S§\ $I’$I’ôQDŖQ"‘Č™ĪžEH’$I’$)C,’$I’$I2`‘$I’$Iʐ‹$I’$IR† X$I’$I’2dĀ"I’$I’”!I’$I’¤ °H’$I’$eȀE’$I’$)C,’$I’$I2`‘$I’$Iʐ‹$I’$IR† X$I’$I’2dĀ"I’$I’”!I’$I’¤ ēÂĻĻρŽR’$I’$)#ųųų—´ūX.uƒ%I’$I’>i\"$I’$I’”!I’$I’¤ °H’$I’$eȀE’$I’$)C,’$I’$I2`‘$I’$Iʐ‹$I’$IR† X$I’$I’2dĀ"I’$I’”!I’$I’¤ °H’$I’$eȀE’$I’$)C,’$I’$I2`‘$I’$Iʐ‹$I’$IR†}8kbåī~O×~ĀŠS§.g›$}Ę 4ˆđ_ fÅōeDŽ}Ĩ›#I’$I—\Ú,ącÍ<õÜJ:ģūĶpEŌGvęÔ)Nvvņķ_üšXSķ•nŽ$I’$]ri–•ŋ[Ã) V$e`Đ Nú/VŽ^sĨ["I’$I—\ڀåĪîŧÜíôY4h>é˙O$I’$}öĨ XœŊ"i ¸ĖP’$IŌįo’$I’$Iʐ‹$I’$IR† X$I’$I’2dĀ"I’$I’”!I’$I’¤ °H’$I’$e(pÉ/GŅ„ÅŠ™4Ž€pKO­ŦĄåR_X’$I’$é2š$K¸p JŠ?6˜aĄsOfa$,’$I’$éŗã,ã)ŋŗ‚’Ї LdÅncZ°įs˛“ÃÕođ|e”xŋŽ‘Åčéķ(;‘ĸüĄ Âm'hØˇ•ĒĒZĸ‰Œ;ņ™.YΏvņëĮŪ`OšķĄ™_åɊĢ•wRį YLŊí˙æ3Ôŋū4ĪíėʰÅũ”;—GžCė…ŸņōĄËsI€@v„ŲĨķ˜;Ą€‘ÃB“qūÔŖ~Į6TGé<S2ÂŌī}Č–įøéæ— %—ēū _ô0÷_ŗ•|!aƒã�� �IDATĻÚpT’$I’Ō¸{°üQc›`˜1so`|ŋ g3õļ{xđ+ŗŲšŸ7×žĘ¯~ķëv`ØŦ |āĻfôV"äÎå‘‡Ęˆ Puņƒ[Xŗn;G/Qũdgî8Ū’ R2‘aUī",}tķs.Ų.*P8—o=üuO­Ždõoū•_ŊRIuS“ĘŋÎwīœu û/I’$Iú¤ģä{°¤•ÚĪĒĮžßķa"+ūé6Ļõŗ)ᙋX6#‹úuĢxžĻ×ŋŪŋŋŸm5‡šįrž\ū.u¯4đą&e\AĄü1Œ ܌„TkÛZ/]ũáÉ7P”ÚĪę )ß~×įîā­Ö‹ī#ËÉctŽ_‚Ēû%aņmEAûVž{ĄōœRuûjŲvāV]2Ōąĩŧ~čĶöĢ“$I’$ „+°|lÙ=¯ˆÁGŪäõš4AAûÖü2íÍg•Pa  į0-?‡ÁÄ9Ū¸ŸM*yûX÷Sr¸d9?(=Áę—Z™Y1‡âÜl‚‰ėŽZʚfR˜úĩ<ĩo KKĮ32œíQĒÖŊÁ[‡N?m1š”ÅeS(†x+ŅÚMŧļq/-gžšŒ˜^ƗK'‘E˛­™Ũ›7˛ŽĻ™đüûøŪMš@>9‡Ãë~ÎĪk:zuŽ€Ĩ~ƒHM¯e!ŲŗøÖ÷ˉė}…o˙ëūž>°ô{ËŊéŦJ-:ŗDčxšúŸOvWĖÂԊRf_“Įzõũà efIņŊ›¨{?ÅîÎ(™5œˇ*ĪŽËÅīm?ÆhėBūéîŒųÎ)ßģ–oWv/*āÆÛrĶäQ Iĩs¸ļŠ×īĨít˛ ˜_QÆü y\‚“MûŠÚPÉæC=K™rįōŨŽc÷+[Vļiņ*~ô/5gŋē}æpũ°NļŊP•vųYįŽ7x|_ŠÔ‡Ü°Đ¨”—ĪãúÂ\ĶÉņCĩüa]uíĻŪö0ËÕ<ž˛ļg™Qå}“ŌĐvžũĮ4�ĐS.´‘Į~{v)Vxær~PŪÅę|ƒ==×ŋčaāß~ö‹3Wwšø˙Ž]Ë˙™ÜČãŋ­íĩ¤)‹ëŋö0ËÂ=ũĪOųŌ2n›K0ŅJŨ–JvöŨ5I’$IŸļ×4g0)bû.x>­ŗĩ™ÎĶēš%ÜsW)cZ7ņü3Oō?Ÿy•Mž|÷-ĖîYn’JĻ k"7ĪPũ›§y䱟đÄĻĶ*27 ûÁ9xÍĘķ÷đâ?˙ŒG˙ëZ ¸yÉF÷\*4y÷ß>ƒĐž<õ“ŸķÄK[‰OXÄũK&rz;šđô[xpÉxNV¯įП­bÕæ.Š*–ŗlz6-ÕĢxvk;´mįŲū˜įvtpŽfv7Æ9ŽāL}kŠŨŲI<ŋˆ‚ĶÅr#D˛:h8xî÷ûŦ?˜Giy1ĮĢ^剟ũš×˜yĻī"w ŗķģØš3JŠÛvĩ2rú gîG˙îm?ÆčP%Oŧ´‡8Æŋ=ũc{eoO0 ¸´”ȁJ~õ˝x̿,béĖŦžŠ‡ķĨģ—SžßAÕKĪķøOVņÚÁĄ”ŨųUž”{ļÉ`ĶJ§r˛j5O­ŨËÉ4]ymƒãQv7ö• |x¸BÎ î¸ģ‚iŠwyņ™Ÿķø3ëŠ Ė`ŝeD)Ä ŋˆ‚Ķqgv‘p'') rĻ­yˆˆŗOQįĄ:Ž (Ę?[nŌ88Ų™Í¤ÂĶkåD&äÁÁ}üŋ[Ŗ0ö:Ļõ^F—aæ8ˆÖėĨlfåVJs›Yˇōį<ūË7¨ ĪãĻ)YH’$I’úvY–öŽåņ§ˇË´ĸp.CHŌÖ~~ø^¤dc{ym]-ŅÖ.:[ŖŧŊފŨ"æO~Ļ\0˜âŊÍÕ4t¤hŠ}—yDō{Mđ DŲ\Ų@[ Hu°sW”ä°< ˛†2{ūTB+YUšŸŖí´ÚÁš &Īë s†2sn1ėĢdMMG[›i¨YĪë›ĸ$ÃC ¤ÄŨ×Ou%Ō<°§ˆî‹Aá"=͊\[@gm-õÁÆ÷<ˆ‡ Į02åŊcįŊ¯úŽZĪæ÷›iią­j;‡Īī{Ŗg]GAû^ļ5v>ZŗXx sĮžûŊ‹ŨۋQŠÎdĒģŨ‰ņ3í‘:PÉË;Ŗ=÷r ;ۂvkāÚy”æwąmízŪ>t‚ļöföl\OUë(,ˆôšē†UŦÛãhkWšY;Âá,čėāäų'įüéã^•Ėc{xíåjęZ;hkmā­—7͙Eéä,â‡ę‰ (îÃĀ5EŒnŠe[ĶPŠ {‚œEáœ÷ÛoRߙM¤ph÷įė"áfļí8Áč y=ũėgĸīGiߡŨŠfĪzĻŠĐ¸ëOÕûē {"7Žƒ÷Ē6˛­ąƒÎöfölŦdw<ˆ$I’$ŠoŸŽ,ÄIŌßuM،Î΁ĻÃíũ`œhæp+Œ,~ļžä ĸMŊʤRāPī ĩãx¯zRņI„‚@ ĸ|8~ ÖkŲďÔs4˜GQ~āl™ÆŪKoÔUžĘ‹Õą~íoė~/ĘΤqpøĀvęZN?ˆˆL( y°žh?ęëî{3uMŊŽO\Ø÷ 0wúPb;Ū=ģnû~ļ5e1mVäÜņųĐ{ûÆčqlîõš‹ļ‚Ũß–ŸĮādŒ÷z÷Dt2$ŋ ×†´Ibg_š–~įû<ũã^îœEø‚rŲæBS= Ŋ—uEy¯=Hdl´GihĪ&rM÷´’ČĩÄëØy°ƒ‚q€ĐØ1ŒėŒ˛û‚=nN°ûP‚‚ąŨ3›ēÙzj4/ė™Ų”S@Qøīė‚TÕĩ ÆĖšÂ�ŒŸī˛;Œ¸šĢčâxSâœkÔ7õīũ^’$I’ôyuYö`<e ?˜2�uvp2äĒüá°Ģų"…ŗ… Ų– yÎņņ8ƒYœũ7ųūÄŠķęé%E 7}‹_Ütáéúp֙2Š]KríQÚKģÄ;#Ũ3;8ÜØEų¸;ģ(. ­Š~„ ~Sũë~/ko`Ú° CŌõwÄuÎėōá•÷oŒŌ×đáí΂āTžņ㊞ėĖaôŧn8A2ūamLŅŲŪ†3,@¯ ¨™ĒßüšmA€�‘Ō¯˛8íßĻ�Ą_Á“OV\p6Ų’E€(īLpãĩjNš&@ôÍfŽĻb0k #‰2d\ÉÆ­ifĨ8z F˛| ŖŲ ×oÜBKcGCķˆäĀņÂ"FvFihí.ßPSËņ9×Q2ǚ ­ã™9.Åî—z6…‘:oļNę"÷H’$I’ôéÚä6ŅL}+Lš<•Ņ•ÍggOôž<—™ÉŊTŋß×Cúšõ˛đ!• ž„XõīXŊŖëü“Ä;ģΔŌ×:’~iî~[@¸ŗˆŅí‡9œ€–1¨ÃȜN"áfŪ9”f'Ö“EņŦ‰„ŽláŲu{ÎŲ„Đxß9’ÉYėŲ՟6ôoŒ>Ž:¯cÍ/Ģ8|ŪšT*‘v¯•žÄÄø`ŪxJ&dągßé~Ĩh;;ŗP¨3i÷­Ißä‰u ô'ī DߏÁÂ" r˛™”ÛĖļÆp˜hx‘œ<Ž* ­J?Ķ)~°žŖY7É̓kD̚!•Í{-ÙT˜Íņ yÄõ gŽŊKMS %3ō¨jœJq|/Ο~ûQĪĖŦ!įüL„BîÁ"I’$IæSļDčÛļÔķÁˆ–•\rgąlÉ_Q:k8Aē8ÚÔųg÷, +ĸ\ˆēØ[r>‚T3ŅĻ$Ãr˛hk=A˙?]¤R]t&N—‘ÎnR ŠŽāīMėuėÃEߏBaŗ'į?힅ŅTO,Ąxz#ÛŖÔˇTĮŌČĪÜÉĸ;ˇŌpŦ™ŖŊ˙ÚÁļCŠgMLŗT&2F-˜jkjædh(azĮ ÚâŨ×GûÔû[xģ%Ä´… ™šĻ@ Ņš}ĩ¯‹Xc+äälīŨ–âŠ'ģē[’:RĮŅpĶĻ1ēĩžÃ Ŗže8“ĻGŸc÷Á>BĢŽ(īĩ§hōx&å6Sw$tmė"2aJ÷ŦĻŊÙl̉1lō ”ÍOįŽwĪ.)k?Æq˛™ß;PÉŖ¨Đ=X$I’$éÃ|ĘčÜšžwt0rÁrž{gķ§O¤øÚ‰Ė.ģ…ī-§¨}+Ģ×5ĸÕ[¨ĪšÂâŠDr˛ įF¸qI)“ulŽíßFšũĶÁļÍûIM.cųÜ#˛ŗ–áÆÛVđŨ{QœÕ]fguÉ eÜ1w<ŖGåQ\˛ˆÅ%É7vŋ&O@¸€ņc‡3";ũ{ę`=ŅđDLČâčû=ˤ1ęÛķX0¯€øÁē´3{čgũž|EÄØš÷ü™:� v×F/|K͇č×%RÄJņ„Fäf÷+jIŊŋ•MMÃ)]RÆõ…C geôäRžųđ}|kîđ‹WpŽfŪ|iī§ō‡Wđ•ų3˜zíxНÂe‹øûīŦāæÍlÚ´˙œ=xN;Zŗ…úĐ –UĖ ’›M8'âųˇōčC+(/ėéMW”Ũ­Ã™=¯€Ô‘XĪōĨĸ "ķæPĐZO}ē[Ā v™7§;œé)wü@3 s˜™Ķ|A8ĶYģēđ ,˜ĐA͎^ËíÚ÷ŗíL*]ȍc‡3,ˇ€ë•2)č,’$I’ôa>]K„�čĸníķ<ąo7ΟBŲ’9 &ų ­™ēšWX]ĩŸ–Ķ˙TßžƒU+,]¸€{Ē`0?´ŸÕ/TōNŸĢO|ßzž][ĘâŌ[x´<L0ŲÉņC{YķBu=ĪļģÖķ\¨ŒÅķoáÁōtū‘ŨĪë;ģ͞k+ģKrķŨ÷0sĶ*~Z™fŸ™D”ēĻ0“Ž9Ė{GNw´ƒ†# nž`įžž÷Ļ9ŋūįZ>j/‡2ŗd 4n`w÷/žoõĖž1”wúķL۟1jÚÎæƒ)¯øÜĀO×õ§­ÍŧõÂjRe”ßyW…čūTũžÕÕ}SÛTëžē™Ų æ1ŋ¤Œ™ÃB“qNļ7Sŋc#ë6ÕíkUT{-Ģ^€ōōyÜķ@ƒ‰s˛5ĘļWVķfãŲ1Ŧ?ØÅâyŲė~˙ėN+ąC1ķĻr˛ļgļRbĸ0ī:R{cgĘĨšę9š5•ĸÖígB—3 ėlLRØÎ;įlœÛÁÛ¯ŧÁČ%e”ßų-§ZНŠä;ĘX1šŋķŦ$I’$éķgĐŠS§Nđžo˙#Vá+?ø:%ũyūJÖņëĮ^eĪ™YņOˇ1­÷ „øģ<ûøz>b+$õSöîyx!ŠĩOŗjßĨÜŗ§Û/žüá%ŋ†$I’$]NŅh”H$ræķ§p‹¤-+›# ˜ģp!E-[xâ2„+’$I’ôy0@K”—˙>/ŦīîgÕcuƌ¤cØŦ[ų^y'Ô˛ú•ę]v$I’$Ię?g°HŸ#mÕ̏¯úJˇB’$I’>{>uo’$I’$Iú¤1`‘$I’$Iʐ‹$I’$IR† X$I’$I’2dĀ"I’$I’”ĄôË ËÜ IŸYƒü˙‰$I’¤Ī´ËÃCāÔŠËŨIŸ5§NņÅ!CŽt+$I’$é’K°üííˇÂüggIú îúú˛+Ũ I’$IēäŌ,‘ĢGķāŊwÎΞÜí‘ôÎū ēīn Få]éĻH’$IŌ%7čÔ)×I’$I’$}Ņh”H$ræŗo’$I’$Iʐ‹$I’$IR† X$I’$I’2dĀ"I’$I’”!I’$I’¤ °H’$I’$eȀE’$I’$)C,’$I’$I2`‘$I’$Iʐ‹$I’$IR† X$I’$I’2dĀ"I’$I’”!I’$I’¤ °H’$I’$eȀE’$I’$)C,’$I’$I2`‘$I’$Iʐ‹$I’$IR† X$I’$I’2dĀ"I’$I’”!I’$I’¤ °H’$I’$eȀE’$I’$)C,’$I’$I2`‘$I’$Iʐ‹$I’$IR† X$I’$I’2dĀ"I’$I’”!I’$I’¤ °H’$I’$eȀE’$I’$)CŽ°ŠŠi Ģ”$I’$IĘH~~ū%­Ā–KŨ`I’$I’¤O—I’$I’$eȀE’$I’$)C,’$I’$I2`‘$I’$Iʐ‹$I’$IR† X$I’$I’2dĀ"I’$I’”!I’$I’¤ °H’$I’$eȀE’$I’$)C,’$I’$I2`‘$I’$Iʐ‹$I’$IR† X$I’$I’2dĀ"I’$I’”ĄĀĨ¨tûQ(˙=´ū'üīS—â ēÜūAûđoËāŋ\éÖH’$I’ôÉ2ā3Xvƒ’ šËpåŗäŸ‚æN˜ŗj]éÖH’$I’ôÉ2āËÂ5đ_+ŸMƒāŋū Ę×\é†H’$I’ôÉ2āKėĪ]Ŗ>QAĶÉ+ŨI’$I’>Y<`qōĘgŸKŋ$I’$I:—o’$I’$Iʐ‹$I’$IR† X$I’$I’2dĀ"I’$I’”!I’$I’¤ °H’$I’$eč°üˇáđ“ÛāØãpęqx÷6¸yø•n•$I’$IRzŸŧ€e8üû]đč$Č A˜1 ū×]p—!‹$I’$IúúÄ,K˙ æ‡ŌœÁ_˰ū‘c`õpė18õ#ˆ?īŪ w|„J†Ãģ?‚Õc2lŒ$I’$IúLøÄ,ũ!ĄÅĐ1P’AŨ_,‚š;aF;Üũ Ėx毅ÍĀ wÂ#yT.I’$I’>ˇWēÛPxz1ÜQ�1xāđoū•ų7@äDÖÁ‘ĶOĀļÐu”\ 4_âvK’$I’¤ĪœOÜ –?Ü÷šŽÃPĶķßw-…ōāß߅Ž<xm)\s‘ē‡öÄIYįŸHÁ=˙‹ļ÷:– ,Ãuo´[÷5øCĪũZ" îZŌŗīc°ĩüÜ6|1žŋÚO_ĮČ ūÜ<*ãĐ~?ܕĻÂÖģžūGv¯ŠŗāūĶmûû;¸ßåJ’$I’$]1Ÿ¸€åõ˙€Íņ4'âđčŸ{>Î΃ũÛáÖ pĮvČʃŠ{s$FÁ˙Z7įÁëĢ`�~r;<†^‚‰ŋ…Á˙ēĻ÷šķķ× %õđ×˙ķ˙"˙^¸ŽįäPxíNø›$|ų_ ō/đb°ģŽŲ=u$R@üà đčJũŧ‚n‡ÆĀß<Ą§ f8ŧđŊî#wÂŖā‰W ōĪp÷aø‡¯Á#}lüŲĤûķy‘›&I’$I’.ę°pūz%<ķ>4'$ÔžŗVž8[,  'ˆI¤ dŌ8˛ūú˙Šų˙Ûģߨ¨Îßã_ģļĻT¸˜.\†ģ‹‰6‰)„‹ŽĀ—HĀ)¸d•ē—8ĘJ8J%\%Ž”*ŽÔÕéŽj^pëTjbdž:\•uĸ+×$RpmqŠ´6â…Í•‡‹+†‹ŗęŠ}_œ!93†īGÁœķœį<gÎØŌųųųCĮ?@ō0œ}ŪŪk2‚“ŌĩPŋš?„/ÃųËđÚĐ>ąŒž$É đƒŗđå0œūÚ¯AÕę`߯'Ą¨˙|4 ‡áãĐ…ÆĘŒë(ÎOāË$‚ށāâÚ>†+�#Đ~ b+aPēWBÛpt�.&āÃSĐ< ›gšēāĮ]ˇo˙qW°O’$I’$…Ss°Ü†×~¯ŨƒēO •g`ãZ¨Š &Õm|ˇAũģp4ąrˆŽBoF Ã0üāWé˙§{ŠôLÎ4˜„Hú­JĪįŌĖ(0 hX œKo›zžD HBÆqÉ$°$•båƒÎisÅô\‚ōÕÎĪpŨˇ‚”Ãé+†+’$I’$åNá,epp4TBl1 @į$zoÔŦ‚Æ#yZnÍ7˛&ļOVŅņV0OKõą™C†¯ĨāË ÁëāŅUĐū´<Į ē$(“ŧSézf�+!ņÖíû’Wī0<iôöz3Û]”@GĶ ĮŪ€rfŋöĖ@ÅpE’$I’¤Ü)Œ€Ĩ Ū?�ĩK'7ÅÖB}æÄ­c V ē“č*¨`æĄ4Ņ\™`\ŋ M}ĐSŋ§{ŖÜ!Dš“D€Ēoj’I¸É “ífSī `ęŽNNøûuŊŠšA2X‘$I’$)÷ b–íÛφ+Y…öĪĄ7›9XĸĐũ&t>=ķîŠ(0ƒqH,†-™“Æ.ƒß€ƒåŲ5­÷RpÎHÎOžŠ (ē[ƒqˆ/N÷Tɨw0 ‰‘ ¸‘$I’$I÷WA,ģ×ŨŨqƒgƒU„ęĪfQ8ŸCÅö`9åŊkac9<ˇŪŪ­AĮ'AĪ—›Đz^€ŊĢ`ã*x{ÔD gxÎ3đåįЁļđÔ2X…íĪBīAhžŖΝÜŧ�-CÁŧ1{WÁŠ2ØX Ą{–đH’$I’$Ũ[1D¨ünÆĘ�ąMđ~1TlČŽü铰å4Ļ—SŽ–+õ_‚ÆcpäÖ¤ĩ)øŅ1`'´ėz‹ô@íģđûl‡ %`÷1h~:_…(†ļ_AĶåy_ęīƒäNhū~0_MâtvAí™põJ’$I’¤ģS4111‘Ķ ߜ˙1ī†Ú’9 ÁîŸ@Ũ°ûNeĮ`÷áÃų7Cķ01ÃäŊ’$I’$=,‰Åb_ŋ/ˆ!B­_ĢŨQ ”—A÷Ŋ?—Ą?W “$I’$IĘBA :} –žĘ˛đ18rO[#I’$I’4?҃E’$I’$i!3`‘$I’$I ɀE’$I’$)$I’$I’¤ X$I’$I’B2`‘$I’$I )įKQŽ+TÁyě,I’$IŌ9Xūú[ĀDŽkUÁ˜€ī|+ߍ$I’$аä<`ųõ÷`‘=X‹Šāũ}ž[!I’$IRaÉyĀōÔj8Såe-’EE°ĸ z^+ķŨI’$I’ KŅÄĄz$I’$I’æappX,öõ{W’$I’$I ɀE’$I’$)$I’$I’¤ X$I’$I’B2`‘$I’$I ɀE’$I’$)$I’$I’¤ X$I’$I’B2`‘$I’$I ɀE’$I’$)$I’$I’¤ X$I’$I’B2`‘$I’$I ɀE’$I’$)$I’$I’¤ X$I’$I’B2`‘$I’$I ɀE’$I’$)$I’$I’¤ŠĪŸ?Ÿī6H’$I’$-(ĨĨĨSŪ¯Yŗ&OM‘$I’$IZ˜âņø”÷’$I’$I ɀE’$I’$)$I’$I’¤ X$I’$I’B2`‘$I’$I ɀE’$I’$)$I’$I’¤ X$I’$I’B2`‘$I’$I ɀE’$I’$)$I’$I’¤ŠīEĨ˙:´ˆÚK¸úEŒO܋3č–EE°ü¯&øõŪ1ūö;ãųnŽ$I’$IĨœ÷`9we›˙į7¸ō•áĘũ0>WFŠxîįßāWė$I’$IR>äü‰|Īņƒ•û­ÆĮá…ã%ųn‰$I’$IĨœ,˙į˙åēJeŖ†nøŲK’$I’”9Xėŧ’?ņ×$I’$)/œ´C’$I’$)$I’$I’¤ X$I’$I’B2`‘$I’$I ɀE’$I’$)$I’$I’¤ X$I’$I’BZ0Ki<šīFH’$I’$Í 0–bØģŪ€‰ˇŌ¯Ã°=ŖHÛA|Ög_íŪ}0ņ*ŦĪyƒ%I’$I’&DĀōÜhb‹g/)†čcĐŗo~!ËBķŗ×á`4ß­$I’$IķQQEŨ†āߖVxíōËŪ YĒ߃ķŠ{ßļû* UeПīvH’$I’¤y)ˆ€%Z ŒAw:\Y_ 5™Ŋ8ŠĄĸ$Ŗ|.C–bØĩ š6@ÅHŪ€îŗĐđ1\Lyt´í‚šeL@ë)ˆo…Æ!Xy2]¨ í„úĩAOœøUhspXķ4 n…šŸĀG__œų! ž ßKÁĐ~(ĒÍ˙Ņ÷‚c%I’$IRa+ˆ€eēæawɝËä*dŲž:*ĄųW°{ĸĢĄutCÕ)¸ÖīCõ%¨9ņ4>ĩQ`(]I1ŧŊRP÷.t@ÕhÛ å)ø^_ €Ē_B˙KĐü? uØpE’$I’¤…ĸ æ`š[ŅĮ {'”ŪmeаzēāGāâ|Ųõ_@Å&¨)†G×ÁîÅĐr NÃųËđƒ!šd˛šŌĩPŋZ>€ã—áJ>úš.Áîg`E–Íš’Š’I¸ū  ’$I’$éļ €ž !zz,‡Ēčš4us˙�$Cub+!2 =ÃâĐ}cōmÅZˆŽB÷đÔzz/AdTÜmû$I’$IŌ‚PK|$ģrĀߝ qĸDxręæ›)H�‘Hđ" ͊Č؉�)HNëu’Lįˆ„hĸ$I’$I*|9Ë+˙ ™-+†öCSįeéø�ūîlČ%ƒ Ĩ|ZRšŧ$“Áų§‡$Ҍ ‰[eЁŒeļpF’$I’$=X ¨ˆn3��ÂIDATĸK"”Ā–U“ÛnĻ2^ĶŠœ„+�CĐ;Õ̧nŽX ęMĀāÕ`ž•ĒĖUĘaKÆ,ƒX [–M­§z%$‡ —to–[!LZér‡I’$I’ô (ˆ€Ĩ-=˧Ą&ŪJŋÃöĘÎ;\‰@õ:Ø>íĩą HBķY¨Ū ‡ÖÁš2ØX mO@īН‚ë { wĀSQXŗ ~ž Șƒåæ�´^…ú]°ĢV”ÁögĄi5´} ׁ+C0Xu•Á1ĨQhŲ<­­ŖAššu°~YˆÉ{%I’$IŌ}UC„NŸ‚Z yÄĪ\ĻŖâ}đĘ|į\Y mßŋ}sįqøoį‚sīNAķh^‰kĐŲ ŸĻ'΁ē÷ }'t˙CÁŠBņA›HÁŽAb'´î‡ōÅ‚Ö_Ōe.CŨ'ĐēF÷@<Mŋ…ōũŠCË�4ī‚ž¨:įyš’$I’$éū+Če…‹ßzđĻt-@$•ątr1üüTtÁĶgōڴیžéŒ/’$I’$ŨkņxœX,öõû‚čÁRĐ"Đū:T]€ē.ļl†ÚbhčËwã$I’$IR!0`™KęŪ…ÖįĄãÕ`uĄÁ84ũŽ&ōŨ8I’$I’T X˛pũ2|īhž[!I’$I’ UAŦ"$I’$I’´°H’$I’$…dĀ"I’$I’’‹$I’$IRH,’$I’$I!å<`)Ęu…ĘÚ#~ø’$I’$åEΖī|k&r]Ģæ4+—øÁK’$I’”9XŪaŒEö¤¸īÁ‰Ŋcųn†$I’$IĨœ,Oüõ8§˙ûŸYņÍ ƒ–û`QŦøæŸė˙3VŒįģ9’$I’$=”ŠFGGW"I’$I’4ņxœX,öõ{W’$I’$I ɀE’$I’$)$I’$I’¤ X$I’$I’B2`‘$I’$I ɀE’$I’$)$I’$I’¤ X$I’$I’B2`‘$I’$I ɀE’$I’$)$I’$I’¤ X$I’$I’B2`‘$I’$I ɀE’$I’$)$I’$I’¤Šs]áĐĐPŽĢ”$I’$I eåʕ÷´ūœ,÷ēÁ’$I’$I…Æ!B’$I’$I!°H’$I’$…dĀ"I’$I’’‹$I’$IRH,’$I’$I!°H’$I’$…dĀ"I’$I’’‹$I’$IRH,’$I’$I!°H’$I’$…dĀ"I’$I’’‹$I’$IRH,’$I’$I!°H’$I’$…dĀ"I’$I’RņŊ¨ôŗ_ÄK”0üE¤&îÅ$=hÁōŋšā×{ĮøÛīŒįģ9’$I’4/9īÁōŲŋ/âŋūâÄŋ2\‘”Ŋņ ¸2RÄs?˙ŧbį:I’$I KΟbö(Á\EŌ])‚ņqxáxIž["I’$Iķ’ķ€å˙ŽåēJI“"ēáīI’$I KÎ{¯H ë/ū"‘$I’´Ā8ҁ$I’$IRH,’$I’$I!°H’$I’$…dĀ"I’$I’’‹$I’$IRH,’$I’$I!dĀRē ŪŪC‡aâ0œŨģ–åģU’$I’$I3+ŧ€et€ÆīBy PU߅ŽpåSē ~ļŽ†ŅCđ›m°&ߍ’$I’$iž .`Ųģ ļ,žaĮbhŪ†Š{LŧgŸžĨ@ü.Ũkfoqˆ);Ņ 8Ģ_ Ņˆ,Ũ›Ą{O¸ûŧqŧ Ũę-¸ö:üf<™,ķč&īŗ$I’$)w .`ŠY;ûžčZ¨{‚1¨xÖΰkMeę׌VŦƒß‚k‡āŸÖ۞zjfĶbC]töãîd×‹Đŗ*n@Ķ ¨yÎAlôŧ ģĘrwM’$I’$ŨRpËŊ–¸ ũË Žüö}ģ‡ū8$īŗxM{`ˈ.†=đģЯŠĨŗ7›› ío ûCØô=]€w?†M˙ hŨĨšŧ0I’$I’(Ā€Ĩs`ö}‰č {‚Đ9 ĩO۞]f:˙ŽpöŒžŖoĀ™=°1chÉŽ}0ē6n‚3¯ķ‰\;‡ÖίĘāí—3æ#yžÛŖacFąíĪNÖsíuøųá†ÔäËāđėûú¯Íŋžēg z ęŋ˜agęŽBÕ ¸™ą9˛ ~v }OÁûOL `rvīwÂĀ0z8]Ge0„é@FšųŪ×5OÃİ=scÎŧ|˙öË0ôÆäpŠéõޝ„98yŋÛ ëg¸Æíۂ6ŋŸEO"I’$IzØ\ĀrücčaĮ(4~ ×36m|~ŗ/x`ĖļWBh˙#Ä6Ās‘‡Šahŋ4ĩüúgĄũ č9 U?…Ē÷ ž:wNž3™‚Čjh^ ĩG`éO á*4ī™ F˛ŠįĀ^h\Į ât¯…Ö IMöĒyjtn…žSPņS¨é‚ęĐžiæëmÚ<XĪôjښ凖M' û$o@Ë ø=đûĪ s†{=øhKĖ~܌"Pŗz˙ .ÎRäĘ0\Iel(ÆįĄŋ ĒZ ūÔî‚úôđ¤œŪûMĐ}*Ž@ĶUhÛĖ;sË|īkļî…ú¨;ąŸÂîO‚z[Ķ!ÉŖ•ĐũDû :}‰uSįÁIĻ ˛ŖA=—n?OĄ|Ī$I’$)_ oŠĪa¨9 Í; v-”ŊĐt >L÷xXQWz,ė^,‡ļž wKddj3“ūsĐŗ5˜\õô…`[Ũč˙ú§•< Õ}Đ?œîų€æ>詄 āË[‹Ąåc¸˜~€o?­ß…Ē2ør$‹zĸPˇ:?€Ŗ—ƒ:Žü Ē^öĻ' ˇ ^9lēxęWC÷fØx6Ŗ=iM]Áŋ‡§=äū¸krßũpåü—wĻmLĀîŖĐ˛ jWA$ ŨgĄ>#H›ņ¸™”ߕūÄüÚÕĶGŌŊ–ÎwAũãPŊŽ$rwīë‡dԟ ęšøi0ŸP{zÔŨÜ×lU-ƒÁ.ø(žŽ÷ ¨š‘ôįTˇĸ°ûãāgŠԝ‚ÁƒypŽÜú<Ë õ$|4ËøšBųžI’$IRž^Ā܆×~¯Í°oũŗĐģ’c=�–C÷ë(ūßÂĶgæ¨?mqh~Ŋ�×WÁî2h97WN+› &Hmųˆ•A¤8x‘ zÃ|múSSKŅâ,ëY<´ˇeԙ‚ŽKPwëA|T•@û´aL= ų$T§觛ūđ{ŋzWŦƒöô\*�ÉkAOˆã‰ā^ŋr ^™éĀeđ/ûĄ&ã¸Ö“đڅ™Ī“dÚ=™Ëtf~ŪIH0{v˝î}1TDĄ˙ŗŠC“ēû€Į&¯ķnîk6:.@Ũ6øÍRhī †Ā]L‡-Öō Ī•ŒcŽ_‚ŪØ˛2#`†Ū9&'Ę÷÷L’$I’ōŠp–28¸ *!ß[ 5Ģ áĄęÉāá6R<pˇõAŦj–B¨~ž:s‡Ą$i€–PøãP‡Ž0-`Ųž:*Ąå$Ô_‚D *6C÷†ÛëŧĶķįœõD‚ëŠOĢ$1Ü XeęĄa†sD#Ā,â™ē÷ûĄˇ)#\h?ũĢáwûƒđ`p�ē‡!Y Uë`K ƃ^!õ'ƒ!,1 ˛4˜čļũîīÄĮ‚ī įæŅ¸Ôėģrrī‹ƒ 01í<‰‘ŒcBÜך|xļ<CÛ3…Î/‚^B‹ƒŸŖĒí0ąũöcģ3W\J?‹sÉį÷L’$I’ōŠ0–2x˙�Ô.Ü[ áÉT[ ĩĮĄĻ>číŅx,ũöĪāĖƒ%–›9w¸pĨzv@]%Ä+ƒáˇÍŨQ ęí‚×2Ú+æûŠeSOzž•ōi]0ĸ™šŖA™ļ_BķÕiįHA|Ž'ā|=đf^ĶāgĐT,Ĩ|k!§ŠĮ‚WĻØjhŲīBŨįĐũdzGdō¸)’AHĶ\ ?žyH͊J¨MAë,=`ĻČÕŊOŊ­"ĶŽ‹–eô‚ y_į:˙é3ÁĢ4[6AË6踛ž€ÄôžÚ?Ü~hâ.CƒI’$IŖ‚˜ävûļŠáĘ}1m°ekЋĨ­o†2ÅA¯˜)šÅA(ķŽ’M=Wa¨Z>ĩĖîÕī‡Ąg b8?œņdrîšgōĨûōä˙Û˙�ĩ›g IfPŋ âsã$.ß>OÎ-mŸAb9´mģ}ŌãŌeĐļ7eyßruīSĐ?̧ļiKeƛģŧ¯ÉTĐĻĖđĻtųÔ9{žĢ„5éˇ7“đŅhÚC zâ‹+:ež;™‚+ŽW.I’$IY+ˆ€ew–Ëžļ÷AķŪÉČæũđŗÁ<Õém/ÁSYžˇã,°ĸ—ĄcĻŋÖ§{ET? ĪEaÅ2x{D€H0hVĢeSOچ f+ė-Ę|ĒSSëi9 ÕÛāíJXSëWÁĪöCī‹°"ËëžßŽƒĸ7ƒ×âÁõfmTÄĄ2}üŌcp~–ĸWÎ=1*6C˙Ëppl_ßß=`KjOfDåęŪ|oË7ŊkVDƒå˜2Åģŧ¯W†`°d2ô)BËæŒÅÁĒÎ=đܲāÜOU“įö¤į{iû’•Đū4Ŧ/ƒ5ËāĀ‹Đ�ļĪkBI’$Iz¸ÄĄéÃbfSˇ>‡ē[“Ü.…úgŌ;ĮŌ“Ü~–Ũ!€ë‚e‚#˜:ÉgĻ– z/tū0˜ķĨũc¨–æm9�ÉzžĖ%›zŽü*öBÛ?�7 Ŗ G #ŖË铰; Í;Ąa 0 =}Psjökx˜|tĒú ys0÷K´ׂyGjģāüæ\™.W÷ū÷C}4Ŋ )č>õ]Á0Šdē=wu_/CŨ'ĐēF÷@<Mŋ…ōũéũ#P÷.´>¯f|Ÿ@ÃA‘ë}°å´l…Ūį!2ũÁ$Äŗ­$I’$Iē]ŅčččD.+\üÖü˙ėũūa¨-™ģ7 ö(t§‚‡ÎÚ ËĢ°åXöË4Ŧbx¸žė}ڀō_,āëšæšĐņL0g.=Ÿ!ÃBŋöG#p=#°Xķ4 n…šwāŖy„>“Ņ7Mx$I’$Žx<N,ûú}Aô`iũj˛yā^í¯BītŸ„öĪ ļ g$X÷æ\u°ûĄ%õ@÷Hpm͏Aįņ…0d:} –žĘw+۠÷ h9-C]ô°<„…’$I’¤…¯ z°(­ 턆ĩPž8ÎŅ‘Îņ ,bØĩ š7@ÅHŪ€î>h85ŋ!K{°H’$I*dĶ{°°H*H,’$I’ Ųô€Ĩ V’$I’$IZČ X$I’$I’B2`‘$I’$I ɀE’$I’$)$I’$I’¤r°åēBIGüE"I’$iÉyĀōŸĘ& § ?Kz¨LĀĘ%ū‘$I’´°ä<`yoĪEūõYŌ]ZT'öŽåģ’$I’4/9XžųĪãüī—˙ˊ˛ ŠáER–ÁŠoNđÉū?ŗaÅxž›#I’$IķR4::j_|I’$I’¤yˆĮãÄbą¯ßÛĮD’$I’$)$I’$I’¤ X$I’$I’B2`‘$I’$I ɀE’$I’$)$I’$I’¤ X$I’$I’B2`‘$I’$I iŅøøxžÛ I’$I’´`ŒS\\<eÛĸd2™§æH’$I’$-<ŖŖŖ,^ŧxĘļE_}õ_}õyj–$I’$IRágdd„ŅŅQ–.]:e_Q*•šøĶŸūÄ͛7ųË_ū’§&J’$I’$ļGy„ŌŌRžũíoķČ#LŲW4a×I’$I’¤P\EH’$I’$)$I’$I’¤ X$I’$I’Bú˙¸āuZ÷����IENDŽB`‚��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/login-rar-grant.png�������������������������������������������������0000664�0000000�0000000�00000215722�14156463140�0022374�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR�� ��5���šŅ¯€���sBITÛáOā���tEXtSoftware�mate-screenshotȖđJ�� �IDATxœėŨXSįũ?ū—ŋ¸?íįÃųl]Ōm&[%КDß&ԑо Ô΂ļmjĩ‚•ĸSņ'ZĢ(ĩŠ:ĩZA[ņ'Z­ځŽŧ6Ām ž7ÂÖ&ÚOŋ›'íæI¯–­ųūÁ΃€H-Ööų¸ŧŽ‘sÎ}Ÿ×šŊōÜ}Ÿ„~@€ˆ._ž<dČ���øžjCũīu%����ß.ˆG����ˆG����ˆG����ˆG����ˆG����ˆG����ˆG����ˆG����īu��$¸NW NŽŅ)š{]�|ß!Ā=$ØJ6oØ~˛ÆãoŨŦŠ˜œšt^ŧ ) �î,ŽĀ=":Í?ié;íŗų\į÷ΡÄ˙ēÔ)ŪĢĘ�āûņ�î Īņ9Š+Ģŧ]ėõ{JŗR—œãû´$�€fˆG�pđ%ĢsĒ|Ũã)]S)ôM=��í!@ßs/<›pDDä-+xH�Đ÷� Īņ*zt ß^eÃH�Đį� Īy]îé÷ē1}�}ņ�úcßÄĄ��w â�ô9…FÔŗ#ƒUø’H�č{ˆG�ĐᏰņ†åŖ`st8f� Ī!@ߓOČLPÜö¨ Íėôh¤#�č{ˆG�p0͞mišng‚MYy3´}U�@;ˆG�Đį­ĻŽĨä-uz@ĐāØõEų1žęĘ:7ž�úū$-�ô!Ņqü•ÕŦŊ~"YØĸ-ygßģx¨āč™ĒZ‡×OŦP…šϧ%¨;RŸ9`÷‘Ė”˛jcV´Ël�ĐWú"ē|ųō!Cîu1�đ&:ŪžšēļŽũ×e)Ė Ķ“&F4j9#EŪcĢ=W\tāt­ˇũĒ•Åä•ėŒSöuÅ�đũŌ‡� ¸ “br/úo`§‚cˇœÉ—ßՊ��$Zãž=€>!Öî*ču6""_yūIįŨ+� ˆG�ĐDûÉ2ī×ëĸád1ō�ô Ä#�č ÎĒÚ¯™Žˆ.U×zîF-��ˇx�}@¨w\ųúŊ8k\â×ī�āv� x\wcŪĮįšÂ߅n��nņ�ú€ŋ+šÆįÁ—D@@<€>āīĘǘ `q �ú�â�Ü?đÅŲ�Đ'�  VŪ•oŧ–ŠđŊ�Џ� hc̓īB/1øģ"�Џ� /„Ļ-‹•}Ŋ.Ī­LUŨj��ē…x�}BŊíĀēņĒāŪĩÖ?ˇëĀĢáxö�úū$-�ô%Qp{Üž;ûü V¨•Ü7T�@ĢÖ84đ^W�ß+ŒSĒt÷ē�€îaq ���@ņ���@ņ���@ņ���@ņ���@ņ���@ņ���@ņ���@ņ���@ņ���@ņ���@ņ���@ņ���@ņ���@ņā~Q—3F§™S*vØĖMĻ‹Īwõ¤šņ•Z""÷~Ë0CzyĮžnÃŊß2L§jų§i2?ļ(ŋ´^¸ŗnžÁšĻŋÁŅwgüz„ú’…1#;ģk�đí6đ^��÷URŪÚx‰‚×Y[Z\5~ßŅE; ^2°{]ÚˇŒP÷öŌ…ėLÉŨëJ�āÎaö�î�§Õ‡›Œá&cTl܌ė7Ŧīį?ĪÕmœŗēĸįî ÂŲ{…¸Âw žWÜëR�āÎ!|ˇŽã¯¤Å‰Ô 3čĮ$Í-ŦģmnĨ+fÆGTà Æq)+Šwuäæ•ĢŪŌ­VOSgîōÍéOĮčGęTà Æqi9呈ˆ?4Õ zvŋģ]Sž(E3Ėōļ›ˆˆ¯Ü=÷ŲxãhƒjddĖĖ×ƜŊYŒęōBœģ㇙æ–Ôž9ĮbmЌ4ÅĖ|­ĸĨ”Š_TĪžš<ƤiЏIšÛnDįšœ9I1OTà Ƨ_~ŗ’oŪáŪofZd­Ęy6R32íô-Cƙ×Y.—÷â:�āŪC<¸ŸøEQčHl%<‡æ¤.>+F-Ī?ûģ’ÂLU}^Jr~ˇę¸O¤?—UF Yk~W˛-‰•­JM/ōôŧ$fŠ‹’ųëĢęD"rėHÍØīÔ.(|ˇŧōŊĸĩ1ÂņŒ´­"’ĩčƒęJËÚō_f­#}âX% 5¯Z^ÜQ¯˜ļíkåąu“čŊŲ/,/ģĶé¨n.„1FžŠŧŪ¤üĒ?Ûë>Č ÷HŸŋßŲ|Œ{rŦڕīÖ4üĩ˛(…UŦJĪŠl uĨs_Č<îÖĖÛi­ü]ŅZ“wלä5bs+mųxK^ÉąUQˇŽ ÉåXU¸!ÜWǞMŋ7´˙÷Ôz{ë^ûî­ĩ›Ŋ}qŦVŠT…Zr7&Ë{*ēž‹Š?˛§RŒXŧeV”V!WĒÂSķ›Åš}Gëī &…JF~ŪË‘ę…üwKŠÖąĒJĩvlæŦđ KÕ5"’Į$„]<sļ%xšWl§Đä_*‰?Ŋũ¤G5mÛoÃÕ ĨÖüŌ–ų‘Â{[‹nû°ų]3ŋ¸8RΈ˜Üŧ8-‚ęN–9[öųCž_ž¨ãˆˆĶĨ.˜Ŧ𖕜‰œ%{ʅQķvž:Á P*ĩcŗ7ÍĶ^9^PÚ4ƒÄČĪĢ7&uZ’Āw âĀ}E˙âŪ…‡Û˙Ûņ‚ļe§ÛŪā%M¤ąíÍ:Ôl öÕUv™4„zĮ%R……ļĩāB r78ī`ōFlN!ƑûdÎLK˘Hũh“ū‰…å~jžŪâĸ'EحᚿÜgOԓņų9QƒŨá—#tm%#ĩäŦm¸“ųŖÛ_ˆÚ i}zœS…ČÉåh#“iB•­ C´ ōš\< õĩ.RŖÚv)ÂM!~û…ÖČĨ6iđD:Āw>šp? ’kÂMFÉ[2‰#jĘ�‚×GtqåëVJņ<‘–:ã"Žk?ųÁX0ųEĄį˙ˆ‡‡‚̓9"Ą|yrÆyeŌŌĩŲFĨ,ˆ‰įËnYOãĸ&Fgœ,sO›Ąô”YXäĻ(ŽHô "y¤ĒŽHģÕxĸĪĘÜöB‚$;cDĸO$bDDĮ$û‚ČīH?šöÄÛ#=—‚o‰\ îi�p_A<øîādÁDa+Ū]Ôn+c\—s/D­‡ˆ^IōB÷„Ę“žāđX=#ĄėČ9¯jZҚ‰ęæ}$úە91*8ŗøŦgFĖš3•Á æ)âˇĻ…´ī–1™’zŽÛ ‰Č/´ß)1ĖZ_ļKƒ‚ ú)HÆ㸠Ō$Ū’(?,įˆx€ī0,Ž|w( ]r ĩZÕüOĖ8™ŧËŦÅjCČuÁÖöfĪÛėR…éz8oã.]ąę=Ÿ*q^ G$ ĸŸdm⸭'mDäkyÍ"&ÅȜÖsÕ֓õ\ôķĻϞ4&mīņr­5Ģcܝ}_Đí/¤žĻĄ5š] ^RT-¯ŊĢ[ŸC/Ų]Ŧ ‘§3ĒČãä­…Š”ãđĖ5Ā÷�âĀwˆaÖlŖx|éōC5.7Ī;íĨ93ĮĮLŨßÍsÖę”#Ųų K÷W8=<īĒČĪŪPËÅĻ'¨ģ8^pÔU×ÔV×ÔVW–Ę{9ū™Ŧ3‘ŗ#CĮˆHĒLöŖģ*=<īĒ.Z>÷Ŧ"JAŧĢÎŨ<;ÃÂ-œãhÎ —<&1ŧ9´É'd&Čk7§¯+ĩšyŪí¨(\h—ŧĸŧĢG|n{UEeģ5žÂęv,*Ēsķŧģf˙ŠíƒŒ‰c[§§‚ŊÅĢ6—9xžw”­[Ú;xB˛‘Š-ąėw+æīŽpxxŪS_ž9õ™xËēڞ,<ōÎρĒsųČĪ74ũ\gܰ¸đ]ĸ˜ōV![ˇe×|ËJ¯?(xphLFaÖ4]7-äķߥœÜ=‹žYīõÉ4Æ [ Įwšį:’õ|ķCBA2…&Ô˛ncæÄÖ]æĻžå[įÄî%™6æÅĩ[Y‰Į–—?Įo=˜¨$b†„ąÜÉÃŽŲ[ô­}2ĶĢ%{9Û7§]ņQ°LĨŸđzáâØŽæhŧgVĨŸiŋ!č—[˙üƄÛ\HPhæ2SÍkÉy ‘ŠL/æ¯Ol[ŧ“'ŦLĄŊķ'Íuy™bԄõÛW6ÍlÉŖˇØžuĶŽESwx}ŦЄ[ļe{°đ(Tä&/Žj}šgú {ˆHĩđĖŲtU×­�āÛĸ_  ĸ˗/2ä^�đ pīˇürŗ|GM~l'ÁĻb‰iz]â™÷t"āûĄ5aq ���@ņ���@‹k����DX\���č â���€â���€â���€â���€â���€â���€â���€â���€â���€â���€â���€â���€â���€â���€â���€â���€ÄĀ^´šråĘ]¯���ā›0xđā;mŌ›xԋĶ����Ü/°¸��� x��� x��� x��� x��� x��� x��� x��� x��� x��� x��� x��� x��� ņMĮŖÚEŖuĒg÷ģ‰ˆČ]”¤š¨æ>'���Ā×đõâ‘ā8ŋ<ųéũHj˜AķDLüĖWß.w‰]Î)ô‘æ0÷ĩÎŲŽXoŨ}Ü.Ü­î =Á~âMk—ˇ��ā;Ŧ÷ņH°īN7iŪĻ“6/“đ|RÜX-Į×ŧŗ6ÃķëîΚp‘Ë ßÚ4EÛësvĐPœˇã°ŨwˇēƒvD[áú­% ˆG��đ=4°—íøŌE3ˇÔøĮŽŪž1YÛ6Ä×n˜ŸšĢtuēRcÍēk9¨‹l˙7{Šī/WMƒ÷ē ��€{ĄwŗGbõöõåž íÂíųíŗɍ‹wæNŌ¨”äåoivËŗGbŊõĩôĻĩš‘&ãĶi9%ŽļĨ2Įæ˜a:KĄË]š9ũéHÍ0jtŒå×ûm‘ģ0IõßĢíDöÜXÕ0CĒĩķižf˙ĸŠņÆŅÕ0ƒæ‰øä%GmÖâÜUoū:É<Ú f0ŽK[Qâom>Ō iŠ™ēüd!¯ÛâI¨/y-ĩeŲQ?&)=¯Ô)öpī-Ãí<ąbjŧq¤A3:Æ˛ä¨wŊų´NõôngS‘EIĒaņėŽãŋŽ×4$5 ŧ`+zĩõÆqIsķkÛîHˇcKŽ×ĖÃ&ír‘ŋ*Ë0L§_RÛee���ßEŊš=k‹­^ ūåŧäÎæ‡¸č§ĸ{Ō‹-/9šĀ%7'ÎKÕËÉkˇî?´4šš/*I×2" fDŧuu:)Ļgį/–‘ģjËĸÜõФ¨úM´<fŲ.qķĸMäW-ŽŦ4°NNPķĒå…wxEĔĖiZŽ{éŪ#Ģ“]ĸõØ4uĶîŠĪdW˛Q“’dĸŨzāđŌÔzĄ¨$UED|ųBKÆ{‚æŠ)YĶĸŖüČɕSë<KØm‹w—d&/­ãˉķ’5ķš*Kd%ģČē3N~ģŊņĨsŸË.CbS—ÅĒDģuwęœĒPQ0kšfFtÅY¸ü´G3%3AĨeDTŸŸšŧЁ3>7;[/g>ĮŲŖ‡6ĨÖ{‹ŦŲúێ-§Hܸƒ6d°kžÛšÍ)5=¸›���ß!@ \ēt)ĐsÄ Õ†L)švûCk˛͆LŪ÷I >9œ2ԜUŨôbßÄڈ•5í:šzl†1dÄ´cW۝åąYÍ/@ĀũÖdmČc ~߁ęeaCĩ÷ēģ:÷ßöΚ8aÖ[ĩnhüËĘ营ŅklÍ/;Ī2tâëõ-ûë_˙•6äąŋm ö5OjCž\õĮ–MŸRrõöÅ_=8Eōø˛ļŋíÍHzŠā/ˇŨÛŅGãB†ęSŠ[Gá꩗Ė!Cĩ!šGõZņŦĄÚáŋĘm×Üyl^bܔMíˇėœ  ylYķĐŨvl­i#´ÃgX{p‹��ž#ZãP¯fŧž(X!û:AsŸ=i÷Ë&ńˆ<ßēčŖ ĒĒ+¯'Į7Ό™Æļͨ(Ԋ ĒķēĸNĻY:ŌĨ”¤ļŊE’kDun^$b$֖WųH“đŧļuF;{Ga¤OĻ&"{i™‡)Ã[§Ĩ” jÂõ xƂHpÕģÅpuS{ĻK}Ŗ¨š˜î÷vĀWÛ/QPĤ˜Ö –OČLØzvKzœ2&1´mM5ų7G&ˇÛ+Š2Ĩ2ˆ<í‡îëŒ-��ĀwXoÍ&ĸ¯÷Ą&§ÃCä+~1Ēø–]nŪK-Os2ic,ĸuM°mŪZtŪæēâk{†;¨ųųKN)íÃ�§6†7ĩô\â‰BUƒ%; Æ?6%akÕ;kŸ‰=ųËXŗ9ÜĒl­œëvoW\"yˆ˛ũ(¨ôē ’ÆŖ ĨVúĩā8ž}Įáŗŧ××Åķë_ol��žŗzd 9‘Ãs…˙ ĸO$<iũĒņ˛{§ę¸Šwg°­KNŪwIn|aņ–ĩœcD‚uõô}-šBE"Ôi(ŅOÄēH ˇ-ž‹|ÕúŽqWūҞĒw6ž}‡(HfLXšzŲ5ģíŪ§E"NZ æn™¸c’ëpŊ=3ym3$eŦŅ(9FDÕy)ņŒ5��@ô*ŠõĄ2r8J+øÄɝå#Ņíä*yˇ“\0#"NeŽ2ôĻ„ÛÎí*ēäWŊ˜pŽe›ÛÔv�cŒHôûDĸ[+å8D$ˆO‘õ¤xNˇø7q‹ItÛĪW”ėßUōÎŧüÜûšQÜí÷ļÈ5g¤6ĸ(DŨdȚ=[ëü˛¤‚ĸ5ƖKŨ˜��č™Ū}°_?)~0ų/lŨ^ÛÉWV ĩ9/Ž7?ŗŲÖmJmŅ›CúņQ¸k߁Í{Ü~ ևéÚ6 ļÚKm¯ä!ę`ōģœí öŌCEĨ6˜*DNäv´;žDgųŅC%ĩüĪ”†č)köfjČ{îŒãŽö‘L%#â=|û|äēPßí÷=ņ‚t&}["ëjøŽ(��€é]<bĄiËÆËČs$3u]•[ōMAUffvé’BģíBg"ûž=ļļæ|Ų’ņÆ'–õ4"Ņ-+m8Ž#ŊŪÖ΄šÍģjD"ŋč‰ˆ˜qŧ9˜\Ĩ‡kZ{p_ˇ|e^Š›ˆ´qcä=ģŋŦ5ņĨ9KWį]o[ŧpnҏȸuuí+cDDÁģŨŪŽĄÚÁäŋP\Ųveų']ˇØūԃƒƒČĪģŊ-ÄúÂÍebP7Ŗu FDūž��đŌÛGŗåŅßZ%Ė\]š/=˛$ÄdŌĢe$xĒkŧū`CZAaĒę6=(WĻžL.8úŦwJr´–yíÖũ‡ĒeJbx?×ôԑ͘QĨ}j˛IēÎ'o ĒŠÚ’žŽž72žöč^+›Ŋ&q×üļĸ=§e Q&UTÖŌČĒėÃs,ŧ%Á$ķŲĪž<SG†å &pD¤ŸŊüŠĶīÍ}.mrR„Ę×Pn-­CĻg'(o_ü¨X īKŗx&™5ķķös‡K‚4ķ'nģˇŖĐ”i†’õåKSy^ˆä|öŗG˄°HŲ{•] 3ÅE˙Žŧ`á nZ$įŗ[÷įÖĻųgoj(.8ĄNŽˆ ęēqs2ĨœČž''ßgPę'ĮëīÚĘ��øöëđA˙;ĶčüíŽe)ĸGĐ† Տz<.iŪĻcļöߕĶõ÷ĀĩŋÉMkj>Â6aVöa{[㏠â†jÃVÖ´?áīC†N;Øüm=Wŋ61l„6d„9i¯ŗ“ōŽVž>#nÔmČsôŒÜS5Îc/EĒ5åHĶ×!5~röõ—&†=Ļ ĒûÕŦėâúöß=tĩĸ srsyS–”\ZˇÅ7ēŋkÁÄ_™‡Ն Տz21mmÉߎõlī­×Q]6Á<|¨vøcŅI+Kūv­&ûņļQŊV<+d¨>­LŌäZû&‹üåZ põlÖ¯Œ!Cqí=ÛĀ'g–Å5 ËKÖĢ��€īžÖ8Ô/Ņå˗‡ r¯ŖôŒxnîčĖ2ĶēÚˇ&bR��āniCŊ{öúŽXŗ;}jƊōĘŌj?ŠXđ��øF|¯…„>ÁT r]8<?™OĢ ]įöūÎ+{j­åvOw��@¯ }ëÉãļ`[7í8]ôZšĪ$Ŧ‹™ŋ7kVūô��Ā7Ī���áŲ#���€Ž ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ėE›+WŽÜõ:����ž ƒžĶ&Ŋ‰GŊ8 ���Āũ‹k����ˆG����ˆG����ˆG����ˆG����ˆG����ˆG����ˆG����ˆG����ˆG����ˆG����ˆG����ˆG����w/•/Ô<ņjuËļģÖoē+˙F‰ĨŠ#ã78zÕÖ]:wœI52émw'’GÆopļûĄw„É#-oöē9��@;ßĀė‘)Ŗhį,]/ŠŽĶ%uBëKžvÃÔHÕȅew¯´Ûčuå÷…Ã{;îĘŖ]ešžwrv%˙aß å×íę6¸ˆĩ×MūĻĪ��ßß@<âTĄëECûœĸæ÷oŅąÛōÜj§,$øîÖÖŊ^W~_h7ŧ=āŠØžšØõuĪ)x}¤ĐĢš>TšÚ •go��ôŠ^Æ#žrsę8“f˜A?îå7kxÉžöKTîs93ã# šŅ1–%Gë›Ūœ+ęĮŊvēdyōĶņæ'"Í3wÛĸšW3OzëÖĮŒLzÛIĸ {~gI~jH—īw‰ԑIJ^‹mH.â‰[ŅBË“f¤Á8.mCĨ§ų0wéŠg#5# Æq/ŋmŨl™rˆ'âļ_ˆq%ižŪīn_yĪ:į+7§Ž‹Ô ĶiFĮX–œpЎSՆ™ņƑ:ÕH“yę̧[&cDw銩1ú‘:Í–š­‰°ŗ č:ž$É<Ú f0ŽKÛĐ4ūnl%^"Ą:˙åø'Lš‘ũ¸”V´vĪĄŠņ+ë|gæôŋŽjŽŲubŅĶ‘MwüŖųpžfwúĶ‘ú‘ũ–ôüÚŲĢ>/ŪRpÉ_ĩÜ8:ém7‘ā8´$É<Ú i2?ģü¸ãÖņjgĶP4Ŗ#ãŊß&šwĮL;Ū|ųįæŽÖ_ŠkĄB‹fæ Ąuq­éŪ•īŸûl|˘Hc[ĩ‚­đå˜ŅÍč˜äuįN/‰4žRÛņÄîs9Sãõ#uĒ‘Ļ˜Š¯•5ß/ÁVørü†;@כORķOä˴ȋ4ŽIÉ)įÛzîá}�€oƒ@ .]ēčšOöM|,.ĢĖ}­ņÚßĪ {lÖŠĢ@Ų‚á¯úc ų‡ŋ@ũëŋ2Æ­­ü¤1¸V˙Ö sØâĘk@ bŲ¨Ƥ]öÆ@ Đh_ķ+ũÄŊî@ đÉáÄáö}ŌūDļUa#üļĶŽYSF#flúãÕkĢg2ÂĪ8X-hŧZą*zDâ[ĢĮĻčGÍ8ōQc pÍūúķđĶŽ] ŽI1qįG-WĶzŪÖĘ{ŌyceÖãæ´bwc ĐxĩfÍcŌaw‡û’qԌ}ģ4ēOŊd>ŖäjÛ°Ô|ŌØxÕV0ņ1sVEcˇ;ā'{‡OØô—k@āÚߊ3"_õĮÆÎ7JnZģáŊZ<kÔãŗZŽhYôˆ¸×ë;ŒoMÖcÆĖŠ@ 4ZSFÃĻäūöŖk×œg‡Ī°^ ŸIz,.ëŒŗ1hüȚų¤1Ĩøj‡^ūļ1ŽųāĀĩßÎ3šÜ\á7NūäĒ?6×J’FÄŊūQģíkžÔG¯ŦŧÚtÕSŒŖ^˛^ Ø×<Ų4&€-7âWqŅ“›ŽåÚŠú¸ŊîĀĩ’æ{z͚2BöŌ‘ĀĩßĪ37Đh[1"nMõÕ@ãÕ?nœņ˜>bĨŊÃũ:õ’1bqåÕÆ@ ŅũÛÅqŖ^˛^ ŽžÉõxÆąúk×œ§æEŸŧī“.Đšs‚vø“ N] š>–qęZ—˙!Üö–�@kCŊ™=rZOÖĢĻ-ŽUpŒĶ%¯Úļ&QŨé$ũčq>b^–YɈ8íŒĖ_˛ŗ'Ģ›§ ôĪ'ë1ICn—§ŗöŨbD$ę,árŽ1žĸčŧ<eÁ-GÄ䑺 Åg]$\8cįÆĻ%ǧŸ—ÖĶĩ—žtN~A Ĉ˜Ü¸ōTMQ˛BÚ 7vKyÕÎi:Žˆ)ÆNEŽ:wͰ¸Ãfg•ŒÉ Ķ6nY:^!všąŗŊ>bÁ#"NgyŖę¯†ŗÎ7v/+Šåâ3ZŽhūt­§Ėz›G¯ÃSŒUsŒSŅ§ÁMä´ĩi_\¯bDL7/IQ]ržË ņBņYqlfF(GD\xڋQüīŠíi?qš7ÎÎ2˧’Í*K+M¤‘lĩ."rÖÔqæÄPīy›@$6T:áĻv#ΈˆE%%¨›†"2¤ĨÚķŧaÚl“œ˜<<+c,į§Žã# ‚Č8Ž1"Ļûô5‹D�� �IDATēĩngG|EŅyš%c˛–cœjBöĻmizÖí�ræiäDDō˜‰QtĄÜ~g÷��ž öĸÛá!Å`yķ+ExŧĸĶÃ×ÁWģč Ķĸæ ĸΧq7Ŋ2™œkÚČcb—Ë,Ũ“)UMī'Wŋc{˛ž eč'…‡xO2“ŦyS*ääŊkŗˆyY!Šķc*cTäSã“âĸ”ˇŧßēÎmXw ÂåD"ũ,Alyˆ˛åōՑqj"Ą¤‹ ⨔Î.Œ}â¤É›0!^+'Rwļą W\RZBZ^ʕ æv_!Ōv7-WĮZ$r;<~ûú˜Ņë[ĢķËxĸÎĪëvšI1žõÉ.NĄ” õáÖs žK‚<DŨ<Ä9tķlJ¤†/ēȓÂVëÕĨ<erí¯tĐîŧôkUD’_!™ŧå)$ÆZĒåŊLÖúKĢ1¨‚nyä_>!3ąxN˛ņė¨(stl|Ü“‚҇‡ä––ßpš~l,Õu1€!DAJUë/Ü`9'Ö{EÁ{÷��ž zˆˆČ/Ō-˙÷ģF$‹ÛøîüĐļ-Œãˆ\ˇmŲCŦ]7A†Ŧĸüøāļ]Gî†uĶy8ģ]įDēÔ}UņŽęĒķåÖ=sĮíģŗdc$×ÖHŦ͙ŗÚfŪ^ô–YɈĘjVĩîę딎nėj�)zãû5ķėįËŦĨgÖ%o-\PrlšZŪŲÆ]ķ5ŪÁą-åE.=ģ:ĸcuwĐÁlæôęWjë…Á5U¤AŽsČöV9܊:Ū0-”uuģî¸ŗ“pĻe%xąžōwgΖnšco꾒ŦtÛėÖôˇĢį÷ąį'�€oLoהZš\-Ë(žŠÂũ~œS„ČOōfcėúĶ`­‚Ü.¯ŧíTÄ1"šLN^W˄‘ārĩ+Sôĩü$xēŸRęĸs"A™\e™ĩö­’B >r^ōÍ7ØxÕøsĶ´K}]CĶģ%§,Ž8[cv–ī?TÃwžąĢAdJCôŒėM%īŽŌ9Ž;稨ÅŠäv\jyéqzDĨ˛ķ)Ān(U r{…Öqa-ãŌÅŅJæq´>.xœ<§Tt’Ļ8Å`Žo ŅÕāĻÁj%‘2,\ŪPi=oS„…r¤6hû…ŠĒyTO~Šär™čņļôę˛;üˇ# ‚ČäēØÄůī;ģ%Ú]rÔÖt÷[ĮН=TXZ/v3€ūļ•bŅëæ™RÆŨŲ}�€oŪÄ#uL‚ÎutC‰Køúĸ×m?ĪwúeH˜ ŦÛĩŽÔ) ŽĶK,æ9'ēųp#Fž§ ŠDĸĀķ<īæũD>ÁÍķ</t7= JŽ­›ß´ D"_ŗ;uŒ%Į.gŒ5eGëQtWm-ŧĐrxˆ’ķÔ×ņDDîŌŊUŪNŪ*oÛšcŗeLʆ^$"Áesųä2é—p29yję<Dĸģōĩ­5,xyH;qŦ˛vWŪ9ˇ đ5ģW,ŨcšÎ7v>€ÂéųąņKN8"ŨŽ <ÉT˛N7v5ŧōąÉFÁēãm‡@$ē­›÷:4ĪOTuŧLä]Ąë…Ou|ĸÎu §¨Š—ĒSc“ķģū&�ņ| ĢØžŖš'"žbûžjåS“ iJœ ŦŨ•W剄玎Ÿc1‰áŒˆ´‘F_ŲžsĸÖ¨$"­Qį:ēץˆ4öhIJg6röģėoËßQvë7ˆĩ+ƍO/ŦˆHämu—DYˆœäQŖP˛ãm;/ŽĶyËsŦ^Æē@Ąj˙!§H$Öî¯fą†ŽūC¸ũ-�€{ĨWėWOËß-$›ŧĪ?yįēɝŋCiīŲ4ÖˇÃ2Ú y"u—?nÛú‰Ũŧ•ÉÍ Qbéô'â7ØÅ˛ĨąĻ˙ŽŠĖ8éõŸ_üË(ĶĮ.ĒėŽ"y|naĻĸ|NŦf˜)fé9eööÅF¤˜ŧzÕX~‡åĄÆĘ’ZV.Œķ˛ãøŧņÆ1KnCl’1¨ķåŽn;×flËVU/¯Ļ͌É<#[°-K/iÆE/Îs¯‹×ŒŒM/QÍÛšj‚ōüĸqË+HŋxGn¸ãĩø'ÂÍķߓgn_Ɉuļąķä&ŦΛāۓü„N5ĖdÉõFmɝ,ītcWÃKōøŧ´ â™‘š‘&Ëv߄Ûgt\ÔŅˇ¨ęscÍsJģL´ĘÄüˇ^`E鯑:ũ3ëĻÜüԎĢĩ&ąĒvŅ8ƒfäøĮ¨;„v>íŖ]ŧ#7Üĩ:~´A3fašjAáëæĻYĻPķ(Ūåՙ5DDœŪ¤¸äĸ°đž-GąČųSƒOOÔŒNŨJ‰ŗMˇŦ…1ãʝ/r%™Æa:Õčņ+ėŖÖn™Ļ&’[ōōĶ‚Ή5ü"yƒ'z[ĶÆ.0(4>ړk1Ž4%ĄÉ[–ŽåčNîcŽ��žiũ�]ž|yȐ!÷ē˜o˜swü3į§˙aßä;z@ž3Dąå‰2ūøÔØŊækz7yŽ\o>mŠL./JFĖ�¸/ĩÆ!üIZø~plŽmYQɋ$ō•{öÚá‘w7�ĀwG¯?šp_ŅflËö,ZĢõR°B3vMŪânžĮ���žßžO‹k����]Ãâ���@į����$����$����$����$zķÁū›7o~öŲg_|ņÅ͛7īzA����__˙ūũ|đÁ‡z¨˙;ž ęM<úėŗĪˆHŠTöâ|����} i6įŗĪ>“ÉîøOZö&ß|ņÅŊËb����}Ŗ˙ū=ôĐ_|Ņ›ļŊhsķæMd#���ø–ëßŋīBĘ���@<���@<���@<���@<���@<���@<���@<���čÍéĪ>ûėʕ+ĸ(ģžúęĢk׎=ōČ#Çīŗz����:Õwņčʕ+JĨ’ã¸~ũúuØuéŌĨ˙ûßũũ‰÷¯ĻĪ3˙°mĮßK–W ]ņâ=xKoׯ:¤õp''ú×;÷ŌŗKĮČījų���đ}Ņw‹kĸ(r×ŋ˙~RŸ~úé'Ÿ|ō“Ÿüä'#͒lÔ­ũéƒ?_ũFë��€īŠž›= æĀ§Ÿ~ÚĐĐĀqܰaÃĒKŪXīļtĖÜUEĢš‡đãāÆÎûú¸ęčŸūųéß÷ŋ;čŲg~îũā÷j| ÆAǧϯ…´õéŸīļžöcß˙|įŸxŖ‘ONLųÃ}°ķāÕG‡Đį‚īŸ˙ôøô”С6ķ}X|čƒË4žl|Ā0!uĖĪ]ĩ-ųãŋnĐuRÆ>˙ôč‡čĶŋž:XūOz€žŧņƒ'Ÿ›ūÏ ^ûテ¯ņĶßĐMx6ö‘čŗŽ'ũfÆ���‹GMžüōË@ đāƒ‘ĪįģtéR˙ūũ}ôŅx ųß_‹?ģ(mô×/Ú~žĶ^1ĮĒĢĒõĶžNîŌį8qiĘĪ]įËß8P:|Á""ū~ĒđO?KœöãA˙>˙NÕO͚÷Č úō ~S~Qũœb ũË'ŸõâXŽøō-Įǝ„>3¸šoá¯U~jĩågD˙žøĮ~Ižŋ”TQėK Ճ„ŋž:X÷ɨđ˙—üĮ4?-<˜žøë;¯—Øuŗ˙5ūgāˆ’ÔƒŽ;O­=mėeã–“Žz Ķ‹��€o—>G~ŋ˙Ã?$"F3`Ā€?üđË/ŋ>|xpppÛķÚW?ųėĄ!Ņ åpå ÛŦ ų.˙ŋƐ1?DDƒä>B¯ü›ˆŽ;?Ø]ÕøßķŸDtũŸõ˙ī?˙:Ŋ÷2]8đS)hāCƒÂũ¯hüôz[œúŋ~|ūØĻCšĮFüר_htŨQåŖ>ˆˆ¸‘OĪ!"§ÍķĐĐIÁDD>ōPÉ'nŌŌŸ ũų "ôđĪúėŌŋŽ˙ā֓â��Āũ OãQPPL&ûĮ?ūņüã̝žúâ‹/†úđÝ=_Ũäz—{:uŖåŸo î‘˙üžę˙÷ĶADƒ*cgN o—N>ũ;Ņ]\ûÃasü—ķŖúēōmå?™šŌÜõ ;Ģâ֓��Ā}Ą¯ŋ÷č§?ũéOúĶĢW¯zŊ۟˙üį?üpĮ˛=ü͇>ģtųK"ē~ųîîŌëDÁC~ūŋ.ũũ“ëDDüåiČĪHDĖ“žŸ0äoĮŠ_Ō Ÿ üĪŋüÍGDôåĨōS˙ķiˇ~ú×ĒķüąúŋÆXžzl ÛŨøŨ`īEį—DôÅ_K6úđúā…īÃ}DD_|üágƒC”DtÃíp]'ĸ/Ž\úėĄŸūøO ���ß}ũėQŋ~ũyä‘ÆÆÆ*•Ęt<"Øđ´ŲqđįøåÃMë_P WzŪÛŊ÷FōôØgĮŧszĶ]ŋlxvę#äsŅ IS4›žøōs–§.—X_=pĐõŠ1DÔMXáū?žŗû< Dô€z˘‡~ø€eˇīė^_N×)ãž:čJšđÉŪ=5Зô“§Ÿ åČAeô÷U˙ųėß#ž3üˆ¸õ¤���p_č×ôĐĪå˗‡ ŌÃ6wtpĢ?˙ųĪĄĄĄũû÷'ĸ7nôīßŋéį&7oŪ´ŲlŖGžĶnŋ¯üOøšįFŨë:��� ŊŪ%œž›=bŒ ‚ĐôĩM“F­cAcĩãĪzī◒M܈§Ļ>Žo}��€oDßÅŖÁƒģŨîN˙¨Hŋ~ũcƒîŦ<bĘ´ˆ>¨īkŅĻ­ŅŪë���āîčģxôĐC=ôĐC}v:���€ŪéëOŽ���|Ë!���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ė]ŗË—/ßÕ2����ž-z† rWË����¸ûz7ĄƒÅ5���� Ä#���� Ä#���� Ä#���� Ä#���� Ä#���� Ä#���� Ä#���� Ä#���� Ä#���� Ä#����‰^ūÍĩ^øôĶO=(Ё@ ÃŽ~ũú1Æ ŏ~ôŖ>Ģ��� S}7{ôņĮ0€ã¸˙{ Žã đņĮ‹ÎԜ™ķ˜ķ˜H폴œrHDt.}ô²;:YÍĢæŠ'„ŪZšÜ8ŗ´—m;ÁšŗÂ~×ēģKĒY^ADB]Y‡ˆÜ…IņųŽ{]U÷<o?Ÿã¸×U��Ā÷@ßÍ}ūųį<đĀõë×;Ũ;`Ā€Ī?˙|îÂÜō‚ŗą F$8ŽÎ™ļUi]Ŧíŗŋ?Ė?09Nlĩƍ5)îu=���ß"}ü~˙͛7ŋúęĢN÷öë×Īī÷یKĢbŒˆˆ8mbū Œĩ;HtZ˛žØË˜(0Ķ‚Yl׸ÍĒûĻČŊ%ąéžĨĩ;ã8ņ\ú¸sĶs‚ˆČW’fągœ]Ŗ'÷~˸ŖáĮŦ‹ĩäĖŗ,÷˙üJũãE•DäÜŸáÛø~f.:"ČU ĩĖ'-?>5ĩ\-\õohj"ŗ_pģÜĒų…k˜P÷öŌ׊yƑ™–nĖ2ĘI¨ÎĪÚ`Dō+-Ģ6Ļní§lIú.ųĒĸ,}Ë5yN/Yž×EŒ|ĸ"qíë‰:rZ˛ú°‡HdQYy‹#åĸķÄĸĨÜ,˜Ōe掍UÔįYšÃB™OđēܲiÛ^SvŖŧeŧ*–ÄĮXˇÅ2Áú˛qWø‡ÜpâMM÷dg8§–Nú ĄfŨ{N_Cjū˛ĩŒČqrŜēzˇGPfän€šWyūÉ*âEĶ2oËĢQrąžháŠ"/1ŋČFÍÛōęXšX_´|E‰‡1DÕė-šXUÎü6b$ø¸øUÛâëRįˆOÍR“cøäę”ō’d9ÕŧŗĪX˛^qŧÃÖŧjŪîË\î˜Üü˜K+ælļ1…RÂD’��Ā7ŽīâŅ7nŪŧyķæÍN÷ŪŧyķÆjƒž}b’pDõÛŗ‹ĩy%é*"ĪÛSSļÖXĮëŊ‡íâ”Ø†&œjë).Ü^åÔG‡Ē"ĸ`c´ŧ°ÖIzy]-3Éęí<iŠÚNOŽN÷edŸv&ž¤&įŲ÷ØÄ<_š\4ûũ#8Ņļ.ū¤4FĖÃ+ķŗBIŠąāRÉûš:ņÜÜ1GĢŗÍʂ凚֝z&ēŪ|6mĢųėJÚŧ¨*ēčTĸRtŧ=˙@Eü|""ly™ģØŌÂŦvč<ēËąíÔ,5‰õ֓‚@õû˛+וüFËÜ'ææūÎQątË,)‰äČ}4ųš×ĘLo¨ƒE§WUx0QNüĄŠ“ļVFO¯í0,gךš+ÔäT5PŦÆVé ×ú*ŽēPé5[KN"âĖŗS5eöŒÂtŊģĐīöČļÛ§&Į†qŲÅΉ‹ÕíÆĀ%Ūzc,GÎ|Kz#*›ãÅč•'†6mŲį›å;\ā}ū#“å$ØO”yŪą§LĩŦjžČSVÔ (ÃÂi‹ŸĨ/Ô+ŒŦꂐįŽē¨ŒÉp¤vÃĩŒ‘ÃkøāČJŽl¯¤U›ļ[ŗ´Œ?‘:îâ��ôž‹Gׯ_ŋqãÆ7ē9 õįŠWâWœõ‰$˛ø‚ĒėĻmŧÍîr{—[ΑÉ=BhŒ&§ļAÔÖŲd ĶšŖ•‘Ģē¨Ž]ŠˆHÉÖÛøiōJ_dJt͑ B<U aķÔZ.)(ũ„ãĨ,*ļOzKEŽũn…1”#"Ļ3ëYĮ‡pd:­‚ˆ¸`SéÕDÄdķ "oŗûBĶôŒˆ˜*Ü@9[ŧČ´‰J"bÚ;s‰øCäwfĻ{⊎šöŊ*ŸĪeĻOŊ46>z|LB¸œ?d÷ęŌ´Œˆ”ˇí$ĪmpŠÆ›¸æk‘í¯ņš˜Ō0JND$׊‚Š]˙¸eXxj™dáŒfea­S¤Jfz˛g¯…Z§>ZGū[†?Hi SÉ2Á!AcZshSĒÁ|í%ĸå\Ũ†9'‰1âŊ‚ÁG¤ã[43Í˙TlLôd5GÁqę} -ŋŽ7Ö­$Š4xÛÅąžZŠy!ÔzŽ^ sڃ"“ČVŌq É@LÕt;øz—?´iLäz“‚yģúí��¸{ú45%¤N÷ëׯ;k. ÉŅQÔkÕ­/ĮԈ­Į0báË÷m‹l7Ĩ$DČ jmĒ ,r]8+=\S'ŗGĻpԜoTáī^{­ÜŖo ķĩŲũnC‚Žˆâå/œŦŽaÕĘÄB9Qû<$Rw‚ˆuą§ëv‚Û¯ î­IXkj˜öĨƒÖɎڊĘ÷V<ˇ;vįvŽËZZtvÆN†Ĩ•<"’VWÛŠ^f\Ŧ÷ėÍŊ`ķ\TĮ,`tĄ“ƒƒģē˛[8÷/Úî_üîž(Žøĸ‹ƒˆ¸đė’ŗŧŖēęŊâų–ŊŠû -‰…ī?åŦ9_aŨœŧ]ŗņŨeĄ1úœĒÚjŪgĘ4†:öTÚ/8Eã<%Ųnš"ĸ[f��úPß}ríúõë7oŪ 7oŅ´ņúõëáu¯-*q5ŋG ŽĶå Ôö.)×d6k­@DėåmŽā‰8c$;ŋëŦhŌË9ŊQŦÚS.F„ˇ[€ŅÅhÜGØFS™ {÷šÔf=‘<nēū†uĩę¤hŽˆTZĨ§Ö&‘hĢĒë> ĩ#5ˇī˛Ų)Ô P4ĸũŧ“ˆDĮ›3_>ä&"YTÚ˛¯'Ô¯Z]Æˇ5'Ū´z8­yrúĒÅFoƒĩõæ>‘>uw=ĶDj]å•‘ûBĨOcP‘莚ā&"ōÔ;üjí°N†Ĩ"Ü$”œg‘aLĻãOî˛Ëb m1Œ{pĩĸŖĒš'"ąžö’\Âų<‚,D͉ŽĶÖQ$ęåŸãåÚ(˂éšúJ—ģ|÷!;S›âfŦY:!¨Žž'fˆPÚöhÂÕLg ļœtĸuaûV̍žÖŅ<Žß��€¯ĄOŸ= ´¸uo ¸qãÆÆËļæfÅlˆˆ˜"<)ˇ(UOtŽé]fŪė%Ģ“ŸŨÁD?3Íß&'"E”ŲˇáHØ<5…é\›+âįˇf†ĸåö…bv#ÉĨ/Ą[šō ˇŒZ´”­lšt‘',N9ˇč9ËaÅ`JĶķ\t™yĶ—Ŧļ<˘(Ęã7m4ŖųkMYsĮŊGDō‰Ģļ)étĶų´ŗļĨ¤$/=Ą{kĸ˛i‹BÅļgYōƒķ“|ÚĘNGšĪ/]˙4‘?8jyžŽäęÕöUŠ–}A$rĄĢ׍eä$Ļ”ģļÎIsģ=‚viž‰) ˇKģ ͚úÂ‹Ķ—sDˤĒ{ەąļŨœ6B™ˇÜōʲ\Uw—ÉTĖžîåbīˇąr§–¸Äé\Vęŗį•rÍķi/*W­ĪąoŠôíHz7c$’bŪzŖœŧ5K“S#âL•DɖoŊ #bzŖ¸d.EĶéRģīAO[Ē›“S#S*ôJ-ģÍÜ��ĀŨĐ¯)Ŧ\ž|yȐ!=lsGˇÚˇoŸZ­îę“k p:)))wÚmo‰ļuÉۋRīŗĪ´;ķ-s)ΚŪmœšģj^5o×[NŧíÚ��ŽMīNßÍ=øāƒ>Ÿī˙ī˙Ũé^ŸĪ÷āƒöQ)|éĸ9;ę/nËžĪ˛���ôž›=účŖūô§?ũį?˙éôŠüā?øÅ/~ņčŖŪiˇ����]ųļĪ=účŖH?���đí×wŸ\���¸/ ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ���H ė]ŗË—/ßÕ2����ž-z† rWË����¸ûz7ĄƒÅ5���� Ä#���� Ä#���� Ä#���� Ä#���� Ä#���� Ä#���� Ä#���� Ä#���� Ä#���� Ä#����‰^ūÍĩ^øôĶO=(Ё@ ÃŽ~ũú1Æ ŏ~ôŖ>Ģ��� S}7{ôņĮ0€ã¸˙{ Žã đņĮ‹ÎԜ™ķ˜ķ˜H폴œrHDt.}ô²;:YÍĢæŠ'„ŪZšÜ8ŗ´—m;ÁšŗÂ~×ēģKĒY^ADB]Y‡ˆÜ…IņųŽ^t$:Ī•9īrqwĮ]ž��đ=ŌwņčķĪ?īßŋ˙õ.ôīß˙ķĪ?Ÿûâ!iûŲÎV}PiŨí\•ļÕŅg~¯˜7~EDŽ[­ž¯Ķ‘ķĎ3Ŋ‰U���ß^}ˇ¸æ÷ûoŪŧųÕW_uēˇ_ŋ~~ŋßf\ZĢ`DDÄiķ?H`ŦŨAĸãВõÅ^ÆD™lĖbģÆmVØ7E.V,‰M÷-­ŨĮ‰įŌĮ›žDDž’4‹=ãė=š÷[Æ ?f]Ŧ%gžeš˙įWę/:˜¨$"įîø ßÆ÷3¨0sŅAŽR¨e>iiüņŠŠåÚhšāĒwxCS™ũ‚ÛÕāVÍ/\cæ„玗žVĖ3Ž|Ė´tc–QNBu~ÖĢ ’_iYĩ1upk?eKŌwÉWeé[ŽÉszÉōŊ.bä‰k_OԑãĐ’Õ‡=D"‹ĘĘ[)'-=āfÁ$.3wmŦĸ>ΞČĘ|‚×å–MÛözœ˛ã°å-ãUą$ž8Æē-– ֗ë¸Â?ä†hjē';Ã9ĩtŌ 5ëŪsúRķ—­eDŽ“+æÔÕģ=‚2#įD5ņyŲ[kDF>Qž°rũ´PVš:æüô?äFQåBã‘čĒ4Ί’weÚ–ˇ8’k:k'ōU9ķˇT‹ALôË-ëļĨj_•3‡ >.~Õļt=ŲwĪ]wNd$,<k]¤5ĩ‹ĘKžwŋēh{q$ˆŠéës'Ģ…CSS+UŪ!›wpŧ¨Ģû��ĐS}nܸqķæÍ›7ovē÷æÍ›7nÜPôíã“„#Ēߞ]ŦÍ+IWyŪžš˛ĩÆ:^ī=l§Ä6Ôx4áT[Oqáö*§>:tPŖå…ĩNŌËëj™IVoįIKÕvzruē/#û´3ņ%59ĪžĮ&æéøŌ䂠Ųī™Ā‰ļuņ‡$Ĩ1b^‘˜Ÿ­JRŒ—JŪĪÕ‰įæŽ9ZmV,?ŦČĩîÔ3Ņõæŗi[ÍgWŌæEUŅE§•ĸãíų*âį‘`ËËÜŖfĩģ@įŅ]Žˆm§fŠIŦˇžĒߗ}XšŽä7Zæ>17÷wÎȈŠĨ;XfII$GîŖÉĪŊVfzC,:ŊĒƒ‰râM´ĩ2zzm‡a9ģÖÔ\yh¤&§Ēb5ļJ_¸ÖWé pՅJī¨ŲZrgžĒ)ŗgĻëŨ…~ˇGļíØ>596ŒË.vNœįŲ˛ĸ&ĸčØ4%‰Õ¯XVDX3ošm†Yŗ{Šã Gļ¯[*\+÷*Rļ[c$œKˇŖ,ųđŗ{ĘTËĒÖč‰<eE ‰õ¸´ōmąŒøÚãĩ>]W•+Jsf^™ôî‘ÉrĘƯ:uđ)Æ<n.ĪzLKü‰Žī#��@OõŨâÚõë×otëúõë­ ß×�� �IDATWŧo~"Ōø„ÉŧŽŽeoŗģÜ'–[žM˛<ģđŒ—ņ!4FãŦmŨu6YÂķĒK•ąžęĸ:6Ŧ9‚(Ã"Ų/Ú*}‘)ŅTuA.T aQjíķIAgN8ˆÅÖāIšęÜ c(GDLg–D4""’é´ "â‚eLĨW“qĖ/ˆŧÍî m:žŠÂ Tīđ¸í™v”’ˆ˜vÆÎÜ r"ō; 3Ķkâ6Ž1rí{U>5ž;š>uų†ĸķdL—ķ6ģWgÔ2"RNÜļ3Q-6Ô¸T‘&ŽųZd—j<DĔ†Qr""šNätũã–aá[ĪĀÍJG­Sl¨ôhĻ[‚ëíŧh¯uęŖuÜŸ Ĩ)LMD$SČÁGnG3…)‰ˆX¨9Äíhčņs<*ô2™L´ŽNž™–:wŊč’ãÔĩ -ŋ~ímĢWg‰V QŸ›œžn˙q—blŧVŪUåŽÚzED¸œˆˆ3•Žēz""NgÔŅíî#��@ôi<ē-gÍÅĻ÷ā¨5ÖĒ?TVe‡‘(ļöˆ…/ßWrėHÉą#%ī[ķ-rfАÛkmUįYdX¸)¸žĻŽÚŲBTáoĨŊļŌŖ 7…é„Z›ũŧÛ­#RÆ'ĘĪžŦļŋW­Lœ —*Rw‚¨Ģ7ŨŽÛ n˙˙ĪŪũG5qî‹Â˙jĪÉÜÕ}2īžîdŨw™ŧg5Ą`&pHĐM‚=t­é.Š"V¨VP)Ú*ūD¤(ZÔ­n-R­€?ĐĒčVąģ€ŊåĮ9ܧ =ׄĩ^ģ/‰īē §=Löíeĸ%īü€$ü`ŋŸåZ-ÉĖ3ßį™gfž<3Ī ‰a/žÖų&ĩúlmuūbæööw2>ÅSVC6Í|8´Y<KđcãānĢĄÍČSÄČĐ|Wßü]HB€ŧ;ėcß*ųŨzp,�€Ŗnw~“ŧ°úTyÅŠõ1ŽRBR+ū\Sĸ•î ö÷Ÿ´˛ĀŸˇ§áOGW)SyVŌ‡ˇ™1EbøŖ‰!„ōįŠĻGũũũN§ŗׇ>Œi˙dceā篘nÔw€įēĮ—ĘyúÚ6�€Ž+=ØHŠ8ĸĨŦUĘø¤LÁ6ŸŦg†\¤ ë…3zBJˆ•‚ŽĶ•–• �€ŸøžėîūŨm!ij�ĔĐÖĻg�€Õ7ˇúÂʏ’s—ˇč %åÖĐb�ÖôéŠÎY€Ÿĩĩdß[ÆÂĸ:ĪȰĻ̟ÖÚHJĩ(ģp“ĸGg"<ĨY¯f/ũĖHHâ(K}�`ŊÛd—ČÅ�ĀZuw­��6ŖÉB…ųiAŒ’Š/o!âĸ a´”žVfā͓{ư Ø�ĩĘ#*ŦžųžP!!€K˛=4�`lëđŦįRĐ9LđÅ<� ë¯ĩ˛��ÖúĪΈeâō[’9íFšnŦ¨jAÔŧԂŊ˄mæ@‘S iOK+ �Āš­T´ĪHØ8÷#B!äãŠ>{ä4ü[§ĶųčŅŖ’3[īÉK8Ę��‚˜´=ՙ2€ÛŽe¤šĨĢ6i#XĄüč�ņ*ûū ŅëB� Zj9ؘôQˆWą„\Í7l`ķK ĸâČėÍPrȕo1šČ[ˆ‚8�€˙ÖόÛßҜL—Š%C†“‚æ–žˇšHŗ˜ X–Ÿt D|TŦĖ[ģāK�ā§ ×ö¨•G22´[ŽJOĨ]ŸÄÄŅ<Í A8€ŋŦ ”ž%[Š’Ūppãˇ•JR”c(ĖÔTr€%ŖŠvĪ'Ā „o9ŧ&Ëjĩ1ԖJB(Ū,^Ē$ƊīŪÛFJqûį–œb¯H*VXēMŗcëņĐĒĘõ% yßl!;+XV’/BđžöäöčÄ<>)r#“í*ÕŽeŅÜĢúF( ØßŪŖÕVō„ īnJ(ÚŋåâĖm<Ũíyā�¤2§DČg–K5û °„4wO�ø<ąx[ûÚiWH`@˛io ŧÆäÆģB!oS\ÉĘ÷ß˙Ę+¯Œr1-ėVYYhæÚK/Ŋd6›322ÆZėxąúŨÚũ‚ŖÕ™‚‘—}ž˜OhÖBimö°tæšņüGˆBč—c|ÎĶ=zųå—ívû¯~õ+ŋßÚíö—_~ų)…BßÚ¸æ˜Qđū‘üI–!„Bč)xzŖGųË_ūķ?˙Ķīųõ¯ũÛßūöÕW_ką!„B<īŖG¯žú*f?!„zū=Ŋ™k!„B“ĻG!„B>0=B!„ōéB!„LB!„|`z„B!äĶ#„B!˜!„BųĀô!„BČĻG!„B>0=B!„ōéB!„LB!„|`z„B!äĶ#„B!˜!„BųĀô!„BČĻG!„B>ƓM:ĩŋŋÂCA!„š@ũũũS§Ž+ÕĮ:/ŋüō?ü€B!„ž[ũũũ?üđÃË/ŋ<Žu˙nëL›6í‡~°Z­˜!!„Bčų4uęԗ_~yÚ´iãXw<éŅÔŠSy<Ū8VD!„zūáŖŲ!„B>0=B!„ōéB!„LB!„|`z„B!äĶ#„B!˜!„BųĀô!„BČĻG!„B>0=B!„ō1ž?*ōāÁƒ !„BčI˜>}úXWOz4ŽÍ „BMxs !„BČĻG!„B>0=B!„ōéB!„LB!„|`z„B!äĶ#„B!˜!„BųĀô!„BČĻG!„B>0=B!„ōéB!„LB!„|LūôČt0!LšPjšĐBÛļŋ&ŋYeĐB2–&Uėh›ĐBÛ6Š_¤'´Pkuš8,nŖî �]!SŽmzBÅ{ˇ{c\‘uƒ}BÛB!4 üŨøVküPūŪ-Į9\žŠž¯}UE>vdŖÅ•Ĩbá)np"°ÆÚ*Ŗ u‘üɅ͕ĢbiūtbB Ŗ›?¯…ų™*!��YœJđclŒáę9›ly’xt՟ĐļB!4ų3=��Ž<å­(¯k$Kw´6YöŅí:]yÍNÅSēz ‹O%>MMœŽ+ĨĮô‰O2=ĸŌ˙Pž>Ae1Í'÷„¸ĶŖ¸­qTôÁę+öļĻ6=šČļB!ôxœôˆ“ĩuSˆīgLÛöw2Ī_Ø{.Ŗfuˆ˙ÕĐzÛĐąˇį™Qwß‚gÅčYtöÉ/BĄįĖD?{D*ŪK‘�tčÚ÷gŒáâöÅ,š8L.››–]zÛėķ`Ũx⃤×ä’0šlnÆÆ]û$Lž]Ī�>V„INXŧ–gnŦ‹#>x:ÄįŲŖļŗ¤’5ˇŦ烚×äâ×>nu­ÁÚęJ7hæ*%aRqD\ÂŌmį >Īä°ĻĢۗ&É"¤âˆ¸¤5ŸĩŽøĖkšąûƒÁ•ĒÅ띎šëd>Ą‡Åm÷y4§Ųũt‹ĩ"MüĪE�Þyâ0yfíĀzŒéâÆÅ ˛0Š8".éÃ*=ã]Ŗ¯ū8ķÍ8Y„TĄTŊųÁŽ“û{ē:M–´ß`šüa’,BŽ­Ļ}Ÿ§ißūšT6ėßŦmîtU—&šö‘äĩ$íæ‹ƒ[oŪ8KēäjدŊ&/8hôķėQ°Ø\;HSaą6Ė~3N&ĪJĐ ­Ũ>]bģwiƒ;ĀXûIö› Ž-*ŪĖōlŅô‰*ėí2 8šķäaRŲæ6¯dRq˜\ą mí‰6¯ä9-ÆXķ‰{EŲÜ´ėŌ[f|, !„^t3zīj0M'­ų‚ū.=ī]ŠĢîâéō\Ą°ölĒ�€5–fg—wŠw6å)ølĮÍōÜĩ<Â@ãx„ pСwv 9Å!�€åÜ mPjŪ/V ĻãfeUÁ;mĻ35ÅJ�€žĩvi~=+Z˜ģ{žhŨĩknķƒ]mįVh Ú@žō~’G°÷›Ē/–åhé“7KâFžYÆOØZÆÜxā.?ĨpSÂtĄœ�ׅŊ§*sÍ}aŌ˛M°4Tģĩ7ÍP“��Lëmæ_•ē.SƇ]ÍÉĶ[´­tuM6E���ĖÛnØ$éšo‰Š!M7ũíüŨr¯ą–k‡Ëī2‘ëfĢûXķî´ 6=wEc¸uúB‘ÖÂÖ^Z˛÷í†=ųWŦŅĢ-S’"!€o˛2Rl.@×eƒāŊü›x`m>´qwí†`Ĩ™Ųå÷ŊģÄFžwX}ŠV[nqoŅP[uÎŊEAjÉ1؟sÆ yįpޚJ�Āx"S{ ƒTŧŗ*_Æ'ėφ‹įd{ĒkķeÁ;™ĩ&WģĨTĨŽĶJHÂniēuš<OkÚã‰ü÷4BĄÉËét:Îû÷ī;Įâ›u2QhâžÎa_t7åÍĄDኧē\?vÎĄDÉîõyé<:#T‘[×įt:Ŋ Y3)Ҝ=ßēčŽÍšI‰BšN§ĶéÔF‡Rę2ŗ×6z¯/—‰ÂsŽģV1P‡RęŖgsĄŠŦ+ŨžˆŽŦœĒĘēŲíU@SîlJ”\î ŋŗ$Q*ËđZå^YŠ(”%Wvų­|wC~zJJAS¯į“ iáԌå5ŽO:ËRDĄĒüVīušōfRĸEļŅē5:”J9ml‘ōÄPJž˛Īčnãž×)ŅĖõßô , Ĩ"Ķkŧâ1OĻDáË.u;Ngī••ĸPjÆë^ÍčÔųlŅ[Ÿņx˛L4sŲŲÁŨwīôʔ䕧<{ŗīÛĩ(TŊS?PŊŗ‹(ŅĖ­ß ~Ũu>UĒĘkUlĩ›šō’'ëŠE^ĩbx—p}âî]•)áTlÎĶūÎîKËž-öÕf…S3–×.`ž´.51ũ€W㘏'Sĸ™[đnĢžëĢeĸđ•×ûœNg÷ŲtJ4{ëΊ}÷N᤭.˙Öoä!„&9w:ô8ŖG=­å÷{~‰fú~kC‹ÅΕo(LwKnÕŲ€ĘRķÚ}į‚TĒCāPks;ĖS€ŠYo&1Ęũ[<_ŊDÅ­ŋõqqĸßNr‡Å4Öļ98ą @{BĖSpoŪēŨJ¯ áĶ­†û‰]˜āЉ4é-ųŊ†@åķÕÅgÕ^?ŗ,) !AgëĄÆũŦ5Gūî*΍X.æ€ĨĮĘ�đÁÜđĨ8ķ2…žÅÅɚȒ=íõmėĸ¤ĩ„ ŠQ#¸1ģsK:ȅĮJĶ“f–×dz×ø”� ŨJŗ�#”8ĘØ8ʡæ{X"ā@û@í†Ū%ČØ% ÜúĢ?YŽŧˇDŦ׍Ipš}ļčEŧčyũ˞<Ą6˙xÁÆb´˛1!Žb iæĢ3ƒŦ‚BčEđ8é‘Ũpõ¤oÁ(ŪŲžĩ2=Nāē˜0ļû6�[šVY>lm›�ۃ�Ĩxē×Dˆ\�ˇlãK(z.‘6“Åޝ×ũķ×Ã–ëąØ�ø,6�rēĐ;¯Š„\˜°æÛ‡~V×vßÚc÷<bMÚįĄR,đ  ¸ž:XM6�AˆĐįÂĪ8đÕj��GHü42]ģmã…⌊’yŪyŖ¯>x¸ēEoyāUÎhÂ]l@ōx>‰cā;§Œß.A āę@—0›l�ö+īĮ_ Ũ~ÉfL—;ßđ™öŪa#"įgŧu¸ų‹âßĪģ÷ģy*UŒ*6JˆĶ˙BčÅ÷8é‘h՗ĩî™ktí } âDwn�,Ã�•qt“jčĩ–āKH�šeaØcF1Ē s@¯Ėė, ĀûŨöŊŠC'Ō„P �,ËĐ)čƒOÖĢŲīä7da֖MrŸ �:ĘÖÕ?VĐA°Ŧƒ x\ßI‚�`z<)ÁáĘÍšĢ˛7ÍČļTį)ŧeõģĩÚĘû|Åģ›ņđI€Š-z¯Ō¸¤1Į6zėH]‚ĩŗ�ĶßŪ[¸7dU‚ũ��,Ÿ¯Đˇō´œâ‰$� ĩ4Ŗd¯á$ã>Žũ“ĸėÄÅēæ/Jž�āđomMÁ$ !„^döh6?iëēŠ–â E‡ĩ5›o‘$—�I|œ˙ ×EĐuEtc˜~Ágaô#�\’ ‰8Uŧ˙‚kg�ŋ—Z�ㅓMvîŧCG’Ü",9R&đKŽ‘$ļĮî[ Ã˛�$oÔ×iļmÎ^ųģÃĮ—ų\ܙÛeÕ÷â÷Oœ]/üĖje†:Aąų”´K\�Hą*^>ēu'ˇ;xiåÕ;ŨI!kuh$•¸é‰›€ĩZkĒĘjžX÷ރüķžøįø­˜!„ĶNė¤ī\&†ûŸī¨2~DˆÅB�s[ģī\'× ��)pŦ–ŪߚÛŊĮ-�|n[õĐcøKJĖ{‡Î÷Ž°Œ;ž€Ā<°z‡hĩ˜'`VĢ @¤”y]M-z¯ü€ �Ā÷ōN÷Đž ʄP�›Ųâ͊VË}p„âQžŪ‡ŽÛœwÚ2}ÉĄ=ÉCžļĄmVpeŅRĪGŒžíūSŒÍ‡˙.aōÜlR"€z“o'`™@/  m6;p¤J¯IjlģÎ4Ö÷NBš:}geEŽznߜØŋaƒBč93‘ī="¨÷ RxŽöcÛĢ/fÔņbp4<įõŽkMŽjVÂv �@ÅFqĀÖpKīūŪzít“×Ĩ‹'āĐÖžõM׎tŒ>(2>IÁû—OÜö\>Ųļ]īÄ)–^´�bäĶÁŅvŗÁ}šeõŽšü‘$ Đcëq`9Wz‹æxF<H>vĢÅsũļ6\Ķû”Áa$AUoPāhŦūŌķ>&Ötåjpc*G5 bŽÎÛx‹Ą6-Pô I€íéqˇŖ;XĻcŦc0B×�›ŋx?ļĄüt‰[į›=ŲĨ0!QÎCåIĪ@×m^¨xmCãîŨÁårĀA[Ũ;Œ5VŦc9#īæöÆqIģÛŊ—"��¸$Ū[CĄÚÄž÷ˆŒĪÛ2¯!¯žô“ Læ�ĩnÛ;ukž(y'ҕ'ĢîÚé wYŲGKä��Š^Ĩ™ŪtáLæRû*Œ°ĩ××ļR̓ –0vž„ch8¸Ŋ†ûžŒĮZn—mãË8–Q˙úN&m)¨ÖÜڐäH]• #؎úĘĒz špÃī\“­¤+•5EM;2ŗMŠqb°éŽ]ļâx÷ũũ͍¤ßņ.|qnËAn"ŸąÔWŸ1Ę 7)74ˇœŽž ę(Å1œ–Ļōĸs✞ÃÚ\ĩŋ¤<¯gŊy>€éÂÁũ„BLŊą(Ā]<eÅ×´•ŸhWXVid$kkĒ>sÅÂU­OÍ-sÕÚŨwíŧØx˛ãFwjIU‰1ü؅ ŽŽųPönXĸ čļ‹§k‰U;SË>:Ŗ¯>yƒ÷VŧRĀįqÁŅRļã3+%Ž×Ē}rƒĮŒm¸Á.‘ŊÔūžFF2Ļ›ZššĮ…Š™×´åg2÷¤kÕŅc¨­:×Ė3Rc\[$xB>€áäŽvšPļH•Īũēž|ÃvrYi7ÔV]Ļß*ÎrŦ:ĐqĨüjˆ66ž Iä< \ŠĖŌØŪz[%! m¸}žĻƒ#ųčíQŪ×C!4I ™č?Jß{ätvNJE¯kpŋ–ĻW!yJôL™(T9'%̏ļĶįĩ1Ö¯ŠWĒgģžMÍģbė<ŋĖķ’§ŗ¯Ģvgzbd8% WÄ.ÚzVß}§@% _yŨĩáī=|Ą‘GŸõĢ’õ)s3B)Q¸JžõTĢĪû€zõōŠ#Ã)Q¸*quųnãņdå wīü֔92Q(9'5ˇL×ítöļîIœ)…ĢķûœNgwがd•ks‰ĢËītŋN‰š_¤ÔũMqjt8% WĨ6ģŪ ] ķiáM Q螺ž0{ŋ=_˜ņēj°ÖīĢŗz^ teĨ(T–Uį]€×ģ|ˇF†R"?˙dšŽUē›ö-wĩ°JŊ|ĪõÎ>§Ķ|iĩzF¨,2ũB§ĶŲ×y!kŽBJEÎ)üĻĪ÷ŊG#Å6ēÚ Û_ž.ą,˙Ššˇ.gF¨,ĢÎSã{7÷d%ģv™":yeūyƒ×kœ]7ˇ&ΤDĄ˛čÕĩŨNgokyV˛jF(5cĻ:mĶ…o{Îî†ŧ×ĸPEb‰!đ{œÎ>ë7eëS^WÍĨ\ũ3̏æ^¯!„Đ ɝMq:�đũ÷ßŋōĘ+Ī:U@Wg( ;žÔyŽ˙î)B!„^(îthĸ˙æB!„Đ$‡éB!„LB!„|LėĖĩ‰Á×VZ´Ī:„BũRáčB!„LB!„|`z„B!äĶ#„B!˜!„BųĀô!„BČĮx&ö÷÷÷˙đÃ?ũôS˙„„B!ôøĻNúōË/O›6męÔ1'=úá‡ĻL™"^zéĨqŦŽB!ô¤ũüķĪ?ūøã?ūø›ßüfŦëŽįæÚO?ũ4mÚ4ĖB!ôÜzéĨ—ĻM›öĶO?cŨņ¤GũũũSĻLĮŠ!„BOÍÔŠSūųįņŦ8ᥠ„BMj˜!„BųĀô!„BČĻG!„B>0=B!„ō1ž÷ĄįĐŋĩ˙{Uõ•qŧ¨sęÔŠË´oĪ’ũĶ“ˆ !„šŒpôč1žÜ�úûûĪ~quÂãA!„&/L^ķ^=|4‘ „B“ĻG!„B>žŪŗG˙ņ˙aŗŲX–u:Cžš2e A`!„Bhb=Ŋôč¯ũ+A$IúũöŅŖGũë_1=B!„Đ3÷ônŽũío›:uęÃ�ĻNúˇŋŲ%q ’$%,ČØXŅFOÄvÍ'Ō´6ŋ_ąæÛuf� /¯Đė2ŒšdV÷IŌÜ$m…i´+0íu:˙‘Œļ�ĶÅĩ äĒŨíŖ^ãī¸˙1įäԔ7f š8O!„…§wÁt8ũũũū2ܔ)Sއ„rKÍŠD�˜öĪ?Ú ĩėŠŨŠ oģ!ŲĒ|ežzėĻL=?„ŋčTÍ8Jļ6߅´ŖÕ™âŅŽ`ēz¸6qžR0Žm�X/n,ĩÄŠ$ÆŅŽđëzįŨ÷~Ë˙{×Oŗg'Îû÷ŗ'.}ûã8ˇBũB<ŊôčŅŖGũũũ&Xõ÷÷?zä5Š”-?´žiÁÉyŠE„éÜæŊWz‚eåú’<ŸnŪõŅ1=¨ɤÂ#Ų2Â|u{á3 ,DŽ:ôq˛ícÕQĮ|ÂbMØŗ‰É[ Ĩĩ™÷ŗįV‘I ĢÍ—_ē.n¯é°6eí'ļ ĘWšōŠåtciūaK€åŋU°wYy{íkųĶKŲÔ•_^ ŧ9høl{­ÅJ­%ˇIr|žå“+4A‚Pn)ÉSđuž�NhÅ��LķŽŨ_ší™‡ã‰k÷ūéÉ$�´oŸû‰øĖQb‹ļ^Ŧæŗ=ŒÅšÂ#ZŠ`‡Õš˙֑S]qÚ2ĒŸŗØ“�ĀßOû§Ĩ˙Ÿõ-ŨãØ!„Đ/ÆĶģšöđáÃGA=|øĐgR'°,`<š…*Ŧ9[^}é@œaÛaĐ 'ëÄ[kÎVÖ\?ē„ėa€ž\xrĢk.ÕTdÁ•Úv– ĀÄČ]HM�€�`lDBaÉūX‘7ũüîkVųĘU nTnųϏp-Â6ÚŽ‹=rОúRõ&ŪÅíå�°Pn-Ų÷Įę\ŪĘÖž|eA’8$mĪ-e,ßv^°ĩæReõŲReĶļÃ:€áĒU™žj}ÅēåKWh�`uW[ÅŠÉB�čą RKö8qj%qôPã§Ö@cHûŋcc˙Ÿ‡æ[Į+īXĀÃ˙uį‹ã×˙ß˙3=:öĮˇBĄ_Ч7zäzÆČgˆČ‹Ķé|øđ!ĀK^Ÿą, ĐzƒÅÚŗMĶ���v‚oŖųŠÄĘ šÕ į%Îר…ėí&“`Ąœ��ūŧ+�Āp‹+ĸ†<NH”r�ąLhģkƒÕÔA(ß�Q*‘õB\�‰’"�€ y˲�ÃrZo°GeÉ\EĮČa—ÉrđĀ`ņ™ę];ž´jR­ĩwĨiëųQr1��)ĸx6ƒf†Õ€¸u‡ûõ´˙ ēûū×Ŋ/œz”~˙ʍN§Â?üˇ˙úwđ?ņEG!„P@O5=ęīīw:~'ö÷÷÷?|øāŋx>Ĩī6õH–ˆ"f[å‘8ī´$ĩâĪo˜u-ĩĩG%%_(��†d.ÁÆZŋa}~"|ū3ôÛ �ĩl l¸aŲ ŅKōI�?Ī ~j=&cė@FhÖ.{t¤ĒžÂÜđ” ˙ô°›ė˜!„BÁ<Ŋ›k=rå3°Ä´žå´r>ɗĘyúÚ6�€Ž+=ØHƒĩūŗs"D™¸|į–dNģ‘‘ÄQ–z�Lũ6Íîf˙i û]“Ž�ÆÔfHB\:<Ë å‘ŦĄÅ �Āę›ī ’Ņå&ü(9WßÜÎ�Xôˆ’ûøš�‚ؚ YKŪÜqĖ—3° Fßlb€n×õˆä?ĩ#ĢŽ€ßDŧŗvŲoŠY‹7dDũ�~øē˙9ÖĸBĄ_–§úhöĪ?zv°�� �IDAT˙hæ�<zôÕíÕ,8Ŧé1iĨÕŲ2@š[ējs‘vņ1‚uʏŽđķt[´įC�ĘœĄ€,ĘŅmŅ&–­;¤"zZül€AķļėJ›ÕF,Ųû–�âdģJĩkŲ"Ĩë{åú’„ŧoļ„,+ÉŒjž4ˇôŊÍEšÅÁ˛ü¤%r�¯ ŠXaé6͎­;Õü„ˇ„ģOĘ÷Rƒ_ō„ĖÅĩkЖ~né|ˆaĩfęˇi´3ŒÍÆnHh&Ŗ6T”Ėķ÷ÎÅVéĘ?žûv÷7oމ��€˙Ķõ§Ęúīqđ!„ jŠëV×÷ß˙Ę+¯Œr1-ėvéŌĨø‡øÕ¯~å÷Û˙ũŋ˙÷ßūöˇÅ‹ĩØQģ=ëÖÛ˙v`ūÛ˜0MÛ´b+Ž'ō�čsKĩĻŧ†bųø ĖŨ´ÃßĮ˙E ûmŦL4íī1L˙ŌløūoūW?ēįøˇB=¯Æ—á<ŊŅŖ¨¨¨ŋüå/ũë_ũ>{ôë_˙úˇŋũíS æ™˛œ[“wšŽÜ4=Q}ļö–‹íūÆŌB!ĀĶK^}õÕW_}õŠmnõ‰S?ģ­{§¯I÷ų„Ÿ~ļáƒBĄĄžŪŖŲ!„B“ĻG!„B>0=B!„ōéB!„L^SĻLy&ë"„B/L^K/˙2eJƒEB!4y=Ŋ‰ũ#ęīī˙á‡~úé§ūūūgËäķ›˙‹ģ~õ{ã^ũûīŋŸ¸XBĄ'bęÔŠ/ŋüō´iĶĻN}˛ã;ĪQzôÃ?L™2E ŧôŌKĪ:„B=w~ūųįüņĮüÍo~ķD7ôŨ\û駟ĻM›†šB!„üzéĨ—ĻM›öĶO?=é =GéQ?>#ŒBĄ ĻNäĪÛOØVžôB!„&LB!„|`z„B!äĶ#„B!˜!„BųĀô!„BČĻG!„B>0=B!„ōéB!„LB!„|`z„B!äĶ#„B!˜!„BųĀô!„BČĻG!„B>&mzd­Ō„IŞrŲ\M掋zæY6˛ö]sĨŠmĪ:Œį}Q&ÍŦņģGŸUšö/J>l~ü‚7+Å ŋ 'Pšė­ĖŠĻÚ6aZĢ4aōėz�˜Ú,qXÆ9zb fë?„i>5OLiÃy5õsp hÆ@ũab›Ú/ĪĻŊb{Ē&ŧ¯ž ŧēë(wM°3đ/¤M��@œVzūLÅų3įĪ(H›n­)ŌŽ¨˛>ë¨ü /j#>¨øAôöļŨŅ3 č ķŠī°ĩȖ^ÅŲû)ļáxëœT[¸ÃÂņ`Úŋ nģnĸJ›0­;â’NXF^Žģnoá{1!uˇ™XŅ ŗwMh3žøžĖąü"yF‡Ōsęīžu�…¤d1JÁĀJuHOÂەˇéeéügÖ0ŦĄÍë79R:/Eúė‚y |ë;FC ĒQ,øôÚpÜu Ž/O\ô8ë3z+„LTiÆĸ70Ŧ`äå€ĮkÄĩÕQw›‰5đq{ׄ6ã ī Ë/’gt(=§&÷čŅA�‡ �ævö,Šjwģחlã‡JņÜOôÃÖbMW7.NEČ%¯iÖV´ëOhÄŗ64�˜v͕&ėž}cs’,Bšąi`áí+4ŠYrq˜\öš&ûDķ`ĸmÚŋ@š°ģšĩbCŌ\Ĩ$B.[ĩ̉�ķ‰$*įKģãëUaŽû2C9•ëM—7§Š^SJ"”ĒÅßp1ퟨQDČ%ŗ4›¯uŸ¨Âvü՜i?ˇ9#á5š8L*™• ų°Ęë&#k­?˜ũfœ,B*™•¤ŨqÕčReL—wd%͍“„ÉesĶÖV´ģWb 7.M’E¸îZz}Eˇ}úašj–\&•ŧ–¤ŨqÕ8läuX}€n=ąA3W)q…ˇæ`ŖŸ!>úÜRéەmEJŸ][ŨYrąOãxß×`Í5g.ˆ“„IÅJ՛ėoō˙ËOā}מũ5ßŪRŋÁuƒÆ_]€�‡ąf›fŽR&—-ČōÚœĢŠdRq„RĩxÃį灝˜Ú,IDÖeŨgÚ×ä’ÅUVī{ēm ŸÛÄRq˜4ŠÂ6¤ŨÄʄĨ_6ą��ēŋÍ×9zÎŋ+uuiߛ)[ģqŗRŧ¸Jßt0ķÍ8W‡Ī>Ņ`čœn,ÍJxM.“Ģoģl˛û´¤ųöŽ5iŽoo~đé@ ÜΎXXŌá°X(¸ŊåÕ arłŦ]õļŪhxlČ ūģŊųĶ5IŠŠ8".é˞>Œžb›vA‚,B*™•´|ēĩbÃĀ^ž›ļązxCųŪ\ x4ūĖāÅüYR˜rmĶ`ÜÖ*M˜T˛âę`™lŨšdéEz<7°XÖrk×Ō¤Á¸eÅ ahĨĀ[5ßÚž"I!‡É 2ļW›�WzzĻũ ¤âYCĸĢ3Äŗ>¨c!čŽ ÖWŨÆ{^ąžO°ŦųÖ.W•#âV|Rgõ4tĀēę˙Ŧ­Žôƒ¤×\ĄÆ%­øä†Ų˙NĮŽqo{,gā ĩxa8N§Ķy˙ū}į¨iá'UlWeJ(•rÚ:đc_oWkyÆl™ēX×ët:}w TĸŲ[īô .ßא;“R—˜‡–Ķ§ËŸCÍH.ŧn´vu6ZĸžŖÍÜzĮét:û^§"_OI+¨šc4wõ:}ēüŲTäĸ=_éÍ]]æoo&†ËRÎģb0î{š1;1ĢĖĐít:ÖëëTĸ™ëŋęs:ûzī•ĨˆÂsŽ÷öõõ9NÃÎ9TtÎét:ģ/¤…Rҝ¯ÜŲhís:ŨMų¯S3–×t;NgīõÕ ŅĖÔ}ÆŽ.ãWe+_WĪMÜgŪŊ×W+DŗWo4vvY;[/äÎĄ"W×öēžkÜĒJ+iøļĶ|¯ą<c6šēÖáŲt…hö˛}uÆŽ.ķˇWļĒÃe‰eF§ĶéėmȝIÅŽģp§ĶÚÕeŧs~Ģ:\‘qŗÛéėž”.›‘\x]oîę˛ŪkŦ˚CEojęÎĐúö}[œ( OÉģbčėîîŌ׿'ËDs ī ]ÍéėŗžM§D‹*;{ûú'öõeYežāĮĢ õ…ąĄĒŒ˛Ļ{]ÖŽNÃõâÔČđÔS]à ˛ī ųŗŠØbƒgáēõ3BSŽw¯‹qßëԌ9)ië*ŋ1š; ;)ö˛ĶŲÛ¸>:T‘R\{¯ĢģģSwv]âŒđ”ãF§Ķéėģ™3#T­NĪ9Ūhŧ×ŲŨįt~ŗI!zũĀ=§ĶŲ×ÛŨmíęrũ3U8#<õ¸ąĪétv_6#T{^w¯ËÚel:ĩÜŨĨûz[ cCUyayJ ÚÚw Tĸ™ĒÄuîõ:ÎžÎķË"CÕ;õÃ{”kĶĒŦĶēÎîîÎÖ š‹T‘ĄÔ@su×fÍĻ"“ ¯ë­]]ƯŠS#ÃÕų­Ž°jŗÂ)u‰ÁĩÆęP™zSíˇÖŽNãW%Š‘îÜU™*ËĒës:Ŋ7WŠB—ívŽļƒí6}u93BUęE+wŪ4tvīœ_J%–šüž{%)3BÕY§›\GJÖYdzÍđÎâjÆ3SōoęîuīœĪ‰ •šÎ6^MíÕ ƒMc83x3îœãé“Ŋ7WΘ­Šžšū›ÅtųŗŠÄĶÖ�Íč¤Ū›+EĄĒØäeƒ“JŠKŒ#UaHÛ9”ŧ6í›ŗĢ&c&ŊŧüŖĩģË|įôĘčPEÚyĢĶŲ÷Í&…(šr`tUĻ„ĢĸgģûdīõÕ˛Ëkzƒî¸`}Õ'ėqŸ—‚Õ7ā ļģ&c&ģēōŖšS_ģs‘B4gë7ŊН0`˙ī,K™13u_ąĢËÚŠoؗŽÍŲķíØvWwõŪ5>Ģū <úCéIyrI‹{áɝ‰|ūÉbW_¸įŪãÆęPEî`čĢ[žzvØŪëĢ[ĒrÜÎlÉ;=Í)ôę…}ŨÆÎn÷ŨgĶŠËkzŨ ŋ~ ĶŊhãz÷Å ëtŠ(<į́o†ĨG:wÔßĢļŪ]“NŠËÜų\īõÕ ‘˙ôČŲÛež×Õëūņ^Iĸhæúo"”‰]p‡Ü]ˇ'k]åˇ}N§ž0:T‘uĶŗÖˇÅj‘ëüÛYžčÕtNg_ˇŅĐŲŨ7<čë2~Ûé)ÁͧžŊĩYážÉ‡~Ol¨Ė{Ķîz\ZN‰ŌŖížJEŽkp/w§@5¸k<mØ}~™(tŲ%OaŨzc—Ÿ+j}8=ēī\]bΎîūR—3˜ŗZĪ.’yNôN§ŗ¯)w&ŊI7¸˜,íŠgķ~/`Ŋ[cCUY7ëëĩvvZ=Ui-Œvg3ú=ąĄĒüÖaĨmí;*÷Ôét:{k2ŧĮđ0OĻf¤{ēM_Ũz÷%§ŗ,E䓀ZO-rŸ4›rŊ;m_w§ŅÜíõ+JVøāæü_×GÛÁ†4›wˇq5u†§ŠÍĮ_§f,¯ísüŽŊÉs¸õ5nö› ö6d…ģķ§ĶŲûMINnISw ô(ČŅ4†3ƒ;*Ņ"WwęûfJ]\žįÎŒÔá‰ûŒš1xz$K;īé×WË.´ÁĒ0DCÉzt¯$QžŌë íŊ´\æZŦˇ.gÆāWŊWVÎXtāørEâ@ŸÔå͖ĨœˇŨqÁúęã=/ŠoĀėŊ’DŅ˜ëîÂ:kōVo=k Ū õ˙ž¯VËDÉåî.äė5k´ú;0‚åFNFũĄôÄ<…ôhr?{Deœ(Ņđ��ĀÁXÛo–Ô,h+9s Y@Ĩ.‘Uíŋz›™—HĶxĩ…Un™?ėáUĢ垝#QĘŸm$dķÜķ ž8”Âëņ‚pÜ?_¸ˇÕbŖi; ĀÚÁĄp¸G99™Đŗ(—�;3ŠaīšČũh%Iž �ØÚÍn<å~°€ŒO’qüOĮ IģžôāvC‡ĩĮÎē �ĐĄ39‰ûY,ūŧ­'æ�X = ‰SîBĸT ne[“âÅąņâce[´Đžē0!:F.æS2>�€hžjúųę\-ķîÛIŠšL(¤ĸFŦžĨŨčāD)$žO(…”sÆh°AxĩRe¤{!>Éh/|Eĸ’[´ëL‰ķŅQ!üšßGĪFØwŖĮĄRw!y$|Į°�`Ņ™§�!QRpĶŌa�@E}&Î|1ûŖ[DFå‘$ū`áēæĐŽ-íf[ðŦH6xÄ#ļ6O$uGAØá%˛÷Māk=Ũ†+¤đ% �ĀÛ, \īŠ§ F)rTß5BJürŦgvín1Úzh†`íĢ:˜ĨĖ;—䁃eY�ÂÔώsĸdîÍPÆJ9×ôČ}˜˛´Ü(5ø3Ÿ÷ĮĄĩķėhĸ�ÆufˆREskZôĖ2!ŲŅÔQEoÅŲN–l XumV2z0žy(‚(…ģqHŠL ÷Í đGĒ‚—ąJŒŅtÄoEyĘ&Ŗ8Úaf@*WGÁ^]úĻvž<'YĐv¸šÉL!M-zFüļB�ĻĒ€;Ž ŌWƒÃy)H}`™ËĻû HõT9$Ĩäx �€!H' Ø˙Ŗ’byË\a{OŖŽQ(¤|q”˙sį„åÜüŸG(Mf“;="Äb)5¸3(YŒR@ĪÍŨUž:§‚�ÁĸLÅáÍoЉéDËĖߛ8üęÄö0�"Ō3ķƒ IŽĪŽįKëÅėĨEfęũâŊ…R— znŦyģØwéqĖ!!Āk%÷Æ \Ōë šÜ�…›>]šQbŨ^t ^<$Ā\žš¤��X;ËAp†¯ÃôØž+øgioü4 @Q›ž¨ĻĘOžo8ļļŧĮÁ§Y_—Bņûǝ(O–U_Ûõū!;pÅ ŠÛÖĮŸ1ÅØ I¯&‚Cžū‚ ü5ގÔŠ/xŸ—_ŧytÛ釙œ[X Ĩ†ž=FŪwŖÆņˇ—Y;@ŪMM’zz�>��Áõŗ1mģr>1R[kķŨgļu‡vI ą0Ë …˜OrXÃ!MNËąØÚŪí°ŗ˛ øv‚K�°Ž¯XNÎ ;éģŠ€f�|ŠßĻÍiĻm)ÎWy‚mŲ¸ ?č}\Ė�cí,8ęsĸ†<Ė,°õ�†,É�AŽú`z4 ü˙XĪ „REM&6™×ÖJKVÉųQ‘ššÉTë›;UÎxŸ į ŧģ�Á`Xv4U4æCÉÎ0�¤÷É ‚ –aøŠy{žÍqŽĻv"ĻH"ä˄G[ôlJˆŽÍʏŽĐŲqAújPŖ?/ŠoĀ,Ë2Ž_v‡Ŧę˙ü¤5ŧ‹e•ˇNīøĸØÎáIÔĢōˇ,Wģ MāYn˙3đčĨÉlr§GC‘"š�ęM÷PđȄwį“šWjmÉü[­ä'âüôU‚G�° ë9Ŗ2LĀËļĩáĒÎŊëĐúųŨŌβAŽ1‡ā ų“ąÛũķĻkį;`Ūą=ËãÎ?V÷Ɂā’0ŒcøJ$ ŊũO…ņŪ‡6AŽĒ‘Trہä<`iSkíÉ]Ĩy™ hΗđŖ4[Oh�X›žéÖá=‡˛×đj¯/ žĪ–¸$0Œwŗ†‚đ›ėŒĸ^ŊOŊ€1ˇŨ¨Üģŋ0›ÔņŨŨcÚwė(ΎÂā’0¤Š†u�—7ōĐ7ļäcOOõĘÚŽÔ>āi*J´ b0Ē‘Ãš˜Ö&�–õĒ Ķ38hG$$ŠįĨúœ› .h=™Æ ˇ{ÄËĒwĻ tX?=qˆąw°Ņ#špįí­Ū$ķ āķ†/9´ƒühg´Ņķ(ötŗ…ˇ˜)u BĨŒ´EĪđš DĖ^ŲxO<īŗ Ë:�x1†*Œũ4Č%I�šü=�€íąĮ•œb”‚ũēīŦfĻ•–Ŧ’@ÄFEM&šiî UëĨ|ĮõîĢAúH V߀'X‚$9CĒėŪnĐN¨˙Bå˛bå2�ÆĒk9}toņŠ|ōŋ—/ō-ũé]ĄF(Mf/ÔĖ5`: 6ā /H„jIĪXSUVŨÂOJņ×K„BĮŅa0 ūĖļÕˇLØ;pŧ.Ļk7;ÆŪ˜ŽšbJvŗÉ}7i­m÷qaXHžûw3ļíJÃÁÍIä§ĮĐæ~IS˙ąfņļ:„r‰�î[AHˆxāŸK<>ŦĩŊŽŪ5¯>Ÿšg’c3YhÖÖZß<0a‚DÍ[Yœé°´˜B1X_ąBĘučÛ<-ښn”bb&$ĶĻæ:ÃĀä 2D‘žķŖųܯvÜh°}Į!`ĪIÕÜ~hSŧī$JšOSķÎ<J6ŌØk<‘ģąiúēã…ņŪë`X <wŪ^wĩeäˇ imB$mępĪyaÚŨõ"Ĩ 1Øl _ėî9B’ ų^ŲŅ@”,Ã:€'pn­ŊĻ÷|ëĪX;ØX‰Q\ģŲž>Â#Žpø0‘Ģ›;ܓZKĶ’Ö\ 4ôäh˙äĮ$ˆ­ē– By´�ÄąQĐ^_ĶŌĘJæÉG‘uûgŅuxύÛ- IÉ1Taė§A2ЁåŽŪ3‡ŠÖl Ž–’��Ō„hžŠšŽĄÅLÅF‘�„LIõ´6|Yo bT2€ ;.X_ `ŒįĨ õ t‚%Ĩ 1XZ<Uļ^]ûfÚ.Ŧ.û?cnēÕ:ĐųHĄ2ą ˙-ąŖÃ0ėQ‹ĮŊBŪčĨÉlr§GŒŠŊU׿ú×X[ĩ}EQ=+IĪŽuīĸ¨´ÔŽ3eɒ´Ą÷Ī]UJ<÷ÁåŨÍ4mn;ˇų`Ģ˙[8��B…Œį¸{ēēŨJĶæĻĒė6Њ ļv==¯õÁĮũú&“ŲJöTĪ}[Á1”}ŽŗŅVKã‰ŧ2K€›3”BĘéŠĢŧj¤iÚt{˙šƒŒ,÷u&š~rV"¯ãäÚˇô&“žžjíž/Ėdt _šJÁ^Ū˛íœÎbĨiŗáÖŽ –VĀvuWNöÚˇõfmĩ돝6�Ĩ’đŲŽĶ[r3?Ēj4ŲhÚf6Ü*ĢéāČUŌaG„O}ÉØU]]´ŊļŨJĶVÃÕí[ŽŅ’ÔU~ķ’ā€ĨšŅ`1ēčĻCkWäîĒm7[iÚjjŦ8ĶhŸ5ėūwĐ}'PR\{ķĩ:�€Ö}ļŋÁînęQīģĻŪXzÛhĨisÛš-ŸÜ„čuY˛`+0MEŲîKs×'s{ŦVÛĀ?šBĸ¤8–Ģ'ëĖ4mmŋą#÷<7–Æh˛0,�—G�Ŗon3šm>ŋ+ĄĩƒĪO“@ÛąímfĢÍØTĩąĸÃŊ~ˆ&gņõö>sucũÁĖß'ivˇą��ImhŅ›-V†EMÃŞ&M[Z̎­mÄ €ļ´[=wŦƒąģĶ4+ĒüŊˇfÔŨ†ˆ]•%ąVämŦi7Ķ´ÕÜvnŗ6á÷šį†g=Dė*ˆŽŲļŊēÍh65VoÛ^ŅA(gēAŽĻ ‚÷Že4ßtĻLGDŠÄ��„,NŪSWų%MŠcÆ?.EOlû\gĄi›ąvīá& ŌRĨcŠÂ8Nƒ!īĮ-ûˇT5šm4mi<‘ŋŋœ—ũÖĀ  ĨŽ!ž+ĢlȁŒRˆÍ5U­Ŧlž’�žã‚õÕĄ5×y)h}ž`C4īĮww}t°Î`2ní߲÷f8Ž"‚Õ%`˙ˇˇ–oË|ÛeÅęZåÄ- 72nد<ãžBš[hâĨIíI?ū¤Š6smÆLuâō=—ŒCį7OvOĢņ¯ˇĩ<ëuՌPjÆėÔŧķ†;ÅjŅėB÷Ėĩëšŧ–íūĻdeėL™(Tģhë%c_oëžÄ™”hæú¯z‡-ÜZĒĘw=ÉßŨ˙ēB*‹\^Ķ=læZÆOĖe‰ĸđõs+ē›v.OŒ §D3Õiĩ÷ęļF†&îķĖ^đjŒē”92Q¨,rβuÖžî†ŧ×eĸpu~ĢĶéėëĒۓáĒāLuĘ&פn§Ķétö/ŦTĪ–šZ/mSåÁ)S7÷d$Ģ"Ã)Q¨,rNjnI“kĸR¯ą&?=1:œ…R3f'z­â˧žN§ŗûNŲú”9ŠÁŦåtöļH™-…*RN›ƒ6ŽĪœęoJr\…‹ÂąÉ+wŪ4û› dß9Ũ ;ĶՑá˛3Չë*ī4î‰u7ĩO]‚îeg_WŨ,Wģ…ĢÔé…îŪØW—ãž įâžās§@%:“rͅéëŦÉMvõĖȒĻn§õŌjõŒP×tk×˙S3foũĻoČLĨ€­}§`Č|ā†Ŧp÷¤÷!Ŧ_/SĪ”‰Â]ÍU›;Ķ3Į­¯ŗaßę”虎.—šUŌ48U°īŪé•Ņá”(<q§Ūéė5œZčjÄu•ßööŨ;Ŋ,:œŠLŋĐ`ĘUāÖ{}šL4ģĐĪdfßn3ŦŠģĪĻSĸôšÁnÔûíų­i¯ģz‹J^xvčÃŗâ7e9‰ŗ3BŠČŲŠš§uŨž;Ρ9šFfĻO—7›…¯tO€ę,K…zÍ ëĖĩ+ËDĄ+ĪękķŠ#]q7xÍĩ T…a-øPō?ąßéėëŦÉ_îÚŗ˛čĄiīõÕ2Q¨"¯qđ3ũžØPŸųhAw\°žęøøÎKAOO°}ƚütW˙W¨Ķ ¯wēk°.ûˇîÔēÔØ™2‘k•å…ÃŽt#†:š™kŖ?ß#OÃS˜š6ÅétĀ÷ß˙Ę+¯Œ2ŖĶÂŖ÷DŠ5–ôû3Ōãõ%{fš%øƒƒ‚lŨš¸UÖ÷믯œ˜Į‡odtu†r7ė˙×ĘEãVGhōbŽfŽč)¸ô˜ĄgíÉ%-î…'÷Íĩ XÚfÔ]\›sĖĒüh]; LķÆņĒë ĢÕŌZŊmĢÔ&>§`úōšyĘšŸ6™ŪåÆĨÆcn„~‘Ŧ5×heėsp`"„~^Ŧ™kl]aŌēBŦĘ9ą7%Øŗą¤ĒäÔî]ģOn_z˛ĮÁá $ņy•ÅÚįaj"ŅĄrf÷'§?Ō–Øž(*awužŸw ôK ĖŦŦ}Ö1 „~9^ô›k!„ząāÍ5„BĄ§ Ķ#„B!˜!„BųĀô!„BČĮäMXcõ ŗäâͧAß!ĪÔf‰Ã2ÎŅ��›•âG|•íhx‹žk•&Lž]?æŋ:ÁÖ ĄįøĶžkŽTąŖÍõŖ;¨+‡É3k° „Đ eōNėo;]ú5-/ŧ˛-6dĖĐ{˛÷÷īeŖ&Û[ˆØÚÕdžŗŠø‚€ĮÆ4–Wé@}øO91lN„zĄLÚôˆĩ3,„¨bŖBžŅ;Š„ŠEĪ"-{LFC ĒgŋeˆÕņ”x˛%É!„F09oŽéļ)ū)¯Ū†=ķÄaIûÍ�ĀZëfŋ™ ‹Š#”ĒÅ>׍xŋƒn=ąA3W) “Jf%hÖl´�[÷ĄRüægƒw]ØÆÍJqXÂ.ÃāJσ aqÛuÃîŲ-ŽŌ7Ė|3N!—ŧĻÉ>ŅîūCĄÖúO´ ”’šbÁŸ6Y.¯ŠW\eĀ€KZĢ4aʍĩÍģĮI"˛n0>Á‹#” K?žlŧÁäZ¸Ūtysšę5Ĩ$BŠZüņ +�Đį–Jߎ|āh+R†I3k†‡° ŲÚ,I+$É�� �IDATDÖeĶí]K“ŗä’ˆ¸¤/‡ŨŅbu+”k›ŧŋhß5W*ûđ6°Á�ę>”{ĩ9�sU&ÕÖĐ�6­ûLûš\˛¸Ę˙_<´w\ۜϚ%G(VlôėyF_ąMģ A!•ĖJĐî¸jRi{ķ§k’RąoXĶÕí+4ŠYrq˜\öš&ûDķĐÎÄŪʎˆ/hhΓ‡IĩÕxs !„^(“3=R6˙Ëî8PŽūŊf]0MÛ49iåúę?7ęūtt• c˙ŠėOMAŠ`õģ3—Ŋ’[^û/ §ÖK­UīŊûq+KÄĖ“q,wõ×QKS;Áãõ´lޟiC›•=OîSA`9šŊF°élSû˙ĐÕær[l8ėʨL3sÎX%[Ē˙\[]¤ļÍ=lAøų+'A–$�Vâ ­)­šTO‚ĩ:/ķ@ģ0ëhÍ×õM—J—_oZQÔĘēŖą78dJ:Đđ¯ēŽ˙^o˙bcáUøé§ęw)�d[ę˙ĸ?Ą:äŦ 9p´Ū}[^TĶöo†ļŗoACŅÆ ːeę"ąŊņÂmOb¸Zgã%kc‰€ d�‡Ĩėh{ÜŪęšŊoøģ‰åĐũäĻxå‘/jn_h:™ŊƕEąÆŌLíž62­°æĪõĩĮW’ÍEÚ5WŊŦž+ģĪô$íŠūĶ•ķųŅĖ­ÁąmģVäß`ĸ‹OÕ4}]S‘i=š›]mķ +ņČŋŪÜ.Pęūĸ¯ĐâÍ5„zĄLÎô‚$�‚ I‚ ĀvãčíIΑüDАĪQ¤īÛ2Ÿč8]Ұ�ævYõ}ļ°D# áķ…ōÄ❊ÛĩĶ ŠPGAģÎĀ�XīęiÉ{‰šš�`Z›;@ŠŽžŨØEKōSĨ$�!IoEÁŊÁ�úę[îī vĻD !ʔ’jĸĮDÁ—$ĀA‹SK´ )% øI{jŋŦ,Ņ*¤BR-Īú¯§­Ū;Tŧŋ)N@��_õļjēÃp× �×ĶjCˇ?rJ3ļ$‡�@ĘS߃š­cXnC-Ɉd›.Ö Ļ´Ö|mŋĩDIiđ€ûȅ�=|Máę8JÂ÷ûĮķXjå‘luTˆXˇ˛87ŌŅ~톀m)Ģî SöÉT…!ĘÔ#E‰DÛąĶî@`„ÚŨI˛*F›ŗÄ]#BļîĖ•šã[įËÅBĄ8*)g‰Üalh(Ar �p ?‰Bhr›¤éŅÉ!Ë<Ī%=–˙÷b�ĀŌntpĸĪ'”BĘq 6āGĮ‰ŲÖf �0m-Fqėü$…ĐĐĸg؎&D%Dûyք'’ēG’ā�˲�ŒŲō�Ä Š{Jī˙YŠ‘— QJܗa‚äĐÍĮ˛'Š^SĘf)e+Žõ€ƒõĘVBä"÷Â$A�ˎ”†ŒØ†šįA‚ƒe‡ũ“–Åíįkm��lÛÍFž–*…  >2AlxFNJtG&¤$<°˜l�Ļ6Ŋ• ķ4š2VĘH[]Å*eîbš$Ī]#‚pÜ?_˜‘´ A1K)›ĩpW8XĮ˜gĮ!„š´&íŖŲŪX;@¯H’ā@Oā˙ąYÆÎ�I’^ŋö’�–ĩPņJŪaÃ]+ˆÍMí|ųG!âž(âV“ â‰=#YĸđwŠöAđü¯aŽW <×o@#/IîŸŲÖÚ%5ÄÂü-'b>Éa ‡49->áxūˇč+hŽáÖŠ^’@žWs˜šRh¸VĮČ6% �‚7øˆ.'ȡ’ôj6‚$X; „G}N”Øwi­Ā•xôą^Ė^ZdĻŪ/Ū[(åq ĸᯚˇ‹G%BĄÆ ‘\€a^1 ë�./ā”"’KÃ0ŦįÉ:‚ �ŌYŨĻgdēv"ĻH „ Žę)3XŦĐf*bÆ0aC�ŦÃ+KclūķŅ/ �mWjđ4%Z…+zü äŒÍ8Ú0@Aņ™o~íŠiY\M W8ߕ[mđĄX‡Ÿr0Ŧwą Á%€äĀˇˇz“Ė'<‚Ī ^œĩáĒÎŊëĐúČÁî]<BĄ_‚ãæšD)įôÚŧĻ>}§3’ĖdÄ )ץoëpššn”B � WĮß5ÕÜÖĶ’89@FŠÄææ–ēæžJ-C`ü,ífwōbniõ7iôK$¤€7xÉfęŽļŒf&¨ąˇa Ô˛÷$ļÕĮÎ7ÃüŒØė*hƒvģģęŦĨ}Līk4ëÚŨëŌĻŽÉÅ�bE×nļ@Hˆxđ ¸Br„L‡íą‡Ëwg…Ļk7;‚-BčÅķb¤GüäŦD^ĮɍĨˇVš6ˇÛōÉMˆ^—% ¸ģJ#ĸĢ‹ļ×ļ[iÚj¸ē}Ë5Z’ē*Ž�� ĸãäLcå53ëzņŖPÍ7T61*IĀ2ũ‰ŌüN`ŋĩ÷mŖÕĩ•/Ų�ƒŖ_‰’âXŽžŦ3Ķ´ĩũƎÜķÜX ŖÉŒ0ˆD,͍‹™˛čØÛ0 ArĻ‚žpō&ņÆå`.´ÁĨr ĮvûŧŽ�ÖÚŧŋ<đ3õ~ĩÛ^ã*öâöŖßq)ķ…�DėĒ,‰ĩ"ocMģ™Ļ­æļs›ĩ ŋĪ=đy´B…Œį¸{ēēŨJĶæĻĒė6Њ ļv=ũØCt!„&‰#=2ްæX*ŋŠHģ ^ųûŧĶLėūŗG̓{Qųš"ciVÂ?Į'Ŧ8f–­¯8ĩ^:p5'c$=ļž2z  Jllä<åØî˛Ę­'ŠÔаaáīæivÜä.€ß[5Ŗ_@žˇp!§eíņĒwļ]!ß?˛oũ{ <ãļ"ØË �ˆ˜ŒerhŲôŽvcíĐąŠąˇa@ü„7b8@ĨĨFym:Hƒķ5…Å)Üēq’Yq }I孏á�Œ&aY¸1y[åMEššņqK™Š÷+Ĩ �ivEuū˙ĪŪũÆ6qåûã˙,ZåHHĖäy‚}%°õíÚŪ¨vÕÎfcįļqōĨ1°`hC(…´@BĄá_Ãŋ4”¤@! %iR é‡m0åæOûMÂí]íÆžÚŸ]­ ŌŽyĐą:‘*&đīA`hH äßûĨH53gÎ|æ¤RŪ:įØÖ uyÎ?ĨŲ˙\øyôĨŊŖx"fßv$Ī$T­ą˙ianUoƇå{ķ–¤+uüŌūv��˜~Į‰čÖ­[sįÎå5cj<z΍Ûņ&KĸÄøûīH÷§äv8ēŪ˙ųŦĖč[Ntĸ{­Œš^ˇŸ��OÛŗ -÷O‰­Ų™˙¨ķõFnÅžŊk,Šõœ9tIš÷ļk¸Ä3ú–—, QÁw︃€ŽĀl��“âŅ3fŪŪPMÅUĮō/ÆúfŠ /mŠŲķŽá×ĩœ¸‡_ĪŊ,ÍąæüåHžö—›��LHX\��€Éä9,ŽM‘­Ų����O â���€â���€â���€â���€â���€â���€â���€â���€ÂäŒGĄŖŽŒÚŸũč×^{†_Š.‡šŨ§Ûŋä?›˙˛YģĒ ß��0qLÎī\3Ŧkø¯lY&ĸŪã¯ŋ^ĶPé䈈q*îŲŨÔ˙Åū}’Ëô´nvįåVÉÉZU‚ü”z��€§arÎĮķjF­Ņ¨8FŒWi4jF-Öšô¯†ÉŪËüw›Ĩȧ‹ÍšĩMû×ē ė–—×ėo[íû×:-‰fũ|‡kWcpp§ksĸ9×ŖĖ,ŪËÚ+ąĀ!GâŠēÆ"Ëüw›īOøHíųķí;ŧrĪvĶÖŗ—v­q.pXRœšĩžÁ&r¤ųƒ5ļųf}ĸÕļǤ9<ØŗDé•=ŗÁō ���<I†g\ēDēŌ2”z<ßČÖĨiõw_ŧa>änģŪé)œuiwiŗDDĄÃëŠēĩģŨķ÷~[ĩ0V[Ö%ilZĻgŠŽ­%î÷_LĐīnûĮÅŧ%K2ŲËmCáĮÛÔÍŊ˛Ėˈ%ôĩ5ú]Užëmž/˛åĒÂũ^™Hî.Ë+ޤV^÷÷ūŖĩŌÜ[ŧéhLD”äĘNB4��˜xĻT<"]ÖJCä˛'BDDĢ]”ļ"u pļՋx""Ūą4žoõËäoŧ$Ļn)´igxģāÖvĨ[&"ufūúEēĮ߅Y–9UŨîv‘ˆHîđ|Ī9–&¤)풷Ŧ‘nÉ2ŗÔŨÖKō žūE…ë“x"⒠֧‰í—CĪl���āW›œ{KŊČe:|æJ0ģŅ­CN=`ˆ- ­j° ›Ãsr0&KąÛRŸoGŠuĮā š¯O/ˆDš_žMԊ,͟¯´ˆKßān\íR-:g8žĀ̇޿x5ʼnQĄ/\kmēVŖ2™Ų0ũ��Ā0ÅâņŽ%ÉeŸ] m’=íä(O&„ôū—ОŽüu[Ōũ3ŒqŖ\íŌ-YĻ={Ų]ŽŊÖ­No0 ׿ūæĨ„yoœĒß ~ø6ČF���×ÔZ\#">}™=Öâžrš2]–ĄŌ/Dĸƒ/å˜ 2ŠãÔķx)*?ˆcc‰-ÚE9ú įÚ%÷÷ŧsÉũ…¸ūhthã“(ÄdžŸCŧZÃb‚Č=Ųm���āų›rņˆ¸ĖĨŠbÃÁf–ĩĖüā¨Ôuö|X&’ƒõgģYj†™Čŧd‘&pĸėZX&’"Íģ\ļM"Q´ĨödsøŅ~1ęë KōĀŦÆ‘ĒŪß5g™Sû ‘på„'*IŪĪ.øUi-ąÔ•ŽYU;D™H 68ĩHDD’(Šĸ(Æd’ûQE ođ��˜Ļ^<"f_’ÉõķŽ%ÆĮ’œéŅr—%Қs‘–۝É‘aįgÉėĢvÍ7ëSrOôgUZĘEŽVU_î}4Ģđļ%iōĩˇRœ‡ũ˙N_i%2,]ôĐ^Ĩ{ļŨûžcžŲ˛ņ_đ—VFĒ?Ŧ;`žYŧĀĒMĖČm˜ĩĄf_&GDžũ ŌŦJ[yæfāĐÂ?ĨY˙TpI$���wŋ‰ĮãDtëÖ­šsįŽōš15Ŋ§Ö­Ø”ģ Ņū׋o—ȧ‹]9­ 9üSčüčé×\š­õÎÁũJ=eŽœčž@M:–Î���žgZî7žrŗGRäŌžę mũōQŧí×ÜϧžčDߒ-|r��ĀT3ĩŪšÖYdZwYVWÖ¤?ÃØ"]ËM)ėæSwÖlOÂL��Ā”3×���`ęÂâ���Āķ†x��� €x��� €x��� €x��� €x��� €x��� €x4 RSNĸë͟}IíđZßͧ”ô<ۂž•ž2‡~kûčÚF>]lÎq˙Ęo‰“{j×XĻ]]ŋŽ��€§i˛Æ#Ų“§Á¨øI´Ú^{ˇ¸Á÷ŦžŅ•K=pŽläo):;""˛nj¨YoŠíØtī˛j‡žT?ßá\[tē3úč÷å>%Æ5U /ÔB5ģ‘zųGgØ~åרų.Ôõ?lõ}h{‚‹ŒųĶ"ú¯˛kßkyĒŊ�¤3YãŅŦWŽĶáũ¯ŽÎŋÖpŠÃUyÎĩŖœâ#^g6đ#}H´ŖęčåqÚ$ŗúé~×H‚e_ëux˙ĢÕsnß[†Øį]9ĩg‘˜Æ¤ņëXü_ėoˆGŒ7˜tŋ2É}’<KgPŗ'¯‡ÆüiC']¯—†Uķf=ĩ.�`˛šĖņˆ8NÃķ<¯Ņ™Ōrö4|šGį?XÜ8'zOæ/ļ›ÍĻW~­O8*´ī_å4%ĩ‰VĮǃ-ƒsROũģÎķÃ{Ę–]į7:ô)%Ũ×"Ÿ.6įÖ6í_ër,°[^^ŗŋU$Šž_åÜčģēŅlÚÚõđâšžVŧĘaJ4ëį۝[Īö ŅųžiÁÁfwQÎb§-Ån[{rđ¸˙ -qÅéa§CØ,žįy^­3ؖÖšĨ UĨ§’ iū`mžYŸhĩ­*iËCOôž3ÅŦ}Á¨OqmŽ4$ õõ)ŽÍ)GjĘM\qØ}Đ9ßœĶ Ū_\“[ßÕŋ\ržū=×b§-ÅîÜØ”‰ŧ%–ĩWbCŽÄ§Ã/ŽIŨĩī:SŦúDŗiÁšbĪĀäVôôbsn}ûၹJqíđD%]ËO)jíŋũųkfËŽ.":æ.°ëÍĻgnE×@׊_ă‹ķMGsįÛwxīĮEņü*ŗŖ"4ü# NÃ{Ž—­úDŗeAŪáÎÁĒdIĩ˛Æ]›;_Ŗ��ĮãņøÍ›7ãŖ6ϯ΍ÛģW×˙î{ūŸōØī´ũ?Äãņ]\ņ‡ŦÂĢáģņøŨz ūŨ˛æōņø_ŊcIŨŲųÃŨxüŽđŸ;ŗ^|Įķc<ūÃÕM/ūqĶŧûcøĢ-éŋ[~æ_ņø˙w ũw\úūåđ?Ū˙č^ņûĨ5˙ŒĮãášE†ßũûö¯~ˆĮ.üÃϝ~ŒĮãŪÂ?X :âņx<Ū˛ũwÜ÷÷x<~×˙áŋ›Ō÷vūp7˙1xę ËĀíâ{^üŊeÅ ˙Ũ6˙×´ôs!Įđž;áūû>éī´üîmōpđŖ˙kJ?ŒĮīū÷ŪôߨûûņxüĮŋÉ~ņ˙–˙ũn<üKúVŸ ūßũĄ§nÅŗ> \eÉ:āũ×Ũģ?ôÔ-ũƒ­°ãnüGΚß[RßūË˙đãŨģņŋH˙Ũ–ļÁ§ø?ĻĨG‚wãņøŨāG‹n˙ׅėß-:흸ĀP˜V\ū!˙pyũ‹\nāv{Ō?p;áÔrÃīū}ĪW˙ŠĩŲķ˙î>ō[ôäũ>ũÃ`<Į˙Y—õûô‚ĢÂŨxüî?Ũy4­¸ ÄųE(Üķ˙ķËK;;O˙pqÅīŗjūųØGøáęĻ—ū¸i¨Ú}éŋĪ>õ·zíŲ÷Ōīˇ˙įc˙ŋ�€ņ÷ėBËũƓzöčLgžC҈@ö4öÖítjĶemYĄîvßI–$™qcDLų‘'P“Å‘ØŅpƒwmZnā§]ôū_*ķLŒˆõsŠ+]ZŽ{t6ŗ­^ÄņŽĨiô}Ģ˙1Ë\ūĻfҞĄĐÆ3"ÎđFA:ëŧÖ18‹eZ™3pŊUOB$JDÄ[ŪČ_š4âŌÖ­AK‚#ųÆO˙ĸÂõI<qIëĶÄöË!"I’ˆqŗ8"ƛ×7|įŲi ō7^^ÚP`Ņ0ƛW9ļ{ĄZ&FD˛Ņĩ)™į~ļÂeZļÆĀˆˆ–9ĩBį÷Ųį#ļ¸}œsĶŽˆņömoĸ-ž%đŽ7iˆˆxĶK)*H}¤`Ķ•°aõN§š1ŨŌ-.uû†@#ũ"†pi+ŌŠíJ‡LD$v]ëŅ.ÉÔ=îφüšíCÕnÚ`îŊÜöô–č��`Jøíxđ4É2#BŅ~˙!ĮüCC'úúų^‘–.*Čžŧ1ĮŌöbš-=ÙĩČĒft;%ŪĨlɛ23ˆˆDĸĩ~¸ŨØ ­jđ%›Ãsr06|<’ĸ7%~Ūũ<L­įéŠ0°bÄTüāqÆ“ĮŧH&™Ø,"1*ôłk­Í÷OôÉÉQ™œĢwZķŠØO˜_Jsf­tĻë8’"ˇ%~žfčž:{–Žˆd"Ri´Ã%YjÍĐÖ"^ŖĸXD$n¯ŅíH”4Žytŋ­š Âm"ŽˆxõũįgŒäžTĸ¤VßpVMžHÄû‹x€Y—d˛‚ËmRĻSîđô]åēĮ>ÂíP´?T•cĒģ?dũ¤ŽiGŧ��L/S)ÉA˙MŌfkˆDF öŨmĨŠ÷Ī1ÆqDdŨãūn]°ķ›Ģm׎¯­ū<÷Œģđņũ1úĨm(ũ#ž}üÕŋv{ËMo„46-ŅMJ˜÷ÆŠú ęû§ã‘vyM[fØ×ŅÕ~ĩĄÔYÕtāËO2i(?ūŦš_.GkÍ C}éĒG°û˙ũ…n˜e™sVŽû†d‘¯úõËЇiķāĖ… ĩÎû;°˙×���¸o -Ž…O´Qš+˜{b?„1âŅĀęoĖČŪųŅ™ļcé‚ģą‡æÔ$„nv"úÎ×_ Ž4Ķ?¸FDrL™F5üWN=‡o‡‡–“äH¯@st#OƒŒŽāŽnõËjâÕDnč996ŽH–$ât–Eš{jŋĒßĸēqĄKä´sxéA=áÖŗįŊ#~B_T:/Dĸ¤Ō>æmjs´ EÃQYŖ. ŒHcPS$zũNEH­ũh%š˛x˙•Kž¯{ĖK2ī:Ė#Ė1¨IˆÄøCF]ĩ�€éjRĮ#IDQE!Ôí>˜ķæQÁž{¯“#"3ÛųbCH"’…ŽâU9ĩ’}Å æŧK{7eÕ<žø4—ErWŸö‹’iŽ(Úī‰<"u=–‰ä`ũŲn–ša&"bL#Qéáékö"īDE— IĶUíĖ‘<BĪĸī|mSΰģsä>qāAÞæŠŧœÆÂ}ohˆXęJĮŦŽĒƒĸL$  ŠZ$ÖØ^/id"’ÂŒiT–fj|'*ÚIŊ'‹wæ—Gœ5IčŊP×%‘Øūš'Ēs¤jæqúzÊįä3s,’§úôĀ`{Ž~Ō¯\:æĩ*#Û9{Ø‘‰äpãaO_˛ë•‘?7@1æ†%ËԞÁ$WúƒĢ†y>-'UöũÔ/Éĸ÷dîËŽũ~™ˆdIEQû‰úūŋ’žŅ§K�Ā„7™×úžŲōĘ7DD” ŌšŌōę*s-ƒ5Ųĩ§¤â˛|KYŒØŧ$WymŽ–˜voÍēâ ,åąū„YZķĢŽ­Ö‘ĢĸV,*ۘq ÆÔ–%•ĮV눂ŊkB’3=Zî˛xŖ2oZ~lw&GDĻ….m~y†­ĢŦmÅũ–†ÕåōžRįü˜ĖTFĮöúm#å‘hû‰ĒĀ[Îavg÷ûJ3ūTJD”0KmH]Yã~Û>đŅJ,ųÃēeĨÅ ŦQ™Š´ŠjöerD99*Ú˙g놾ū•>mMÕŪ FdÚY].ī>čLš-súĖ‚ĒŊvF#$�.}ĨöJnJaXb:Įž#šZ"âmKŌĒJßJņŊuŽnhņΊzą¨x­ũ°DœÆ˛ŧĻęmŨĪųēÕĩ5ąå9Ļ]2ã´iyU\#§Ŗ‡ÆüÔRž´‹\ú#UĒ•ņ°ā,¯K÷oĖ8“™ZŸų~ÕN3#’[vgli\-ŨųJQBFĩŋ6cė��“ßoâņ8ŨēukîÜšŖŧfLGīuûTE>]ėęĖimČų•Ÿ‡8áĩž§ß7Ģūģ’äņ.dôzƜųŌlƒ3t“đ��`4ž]hšßx2Ī ’EīŅb7{ëKö�7Š÷QôôkVëÆē÷+ždQ��āg°¸���“ÉsX\Ãė���€â���€â���€â���€â���€â���€â���€â҈Zßͧ”ôŒwŖ"5å$ē> ?ŲŁâķæÖ§[��Ād5Yã‘ėÉĶĪ/ęx&}‹ŨîkÁījĩnj¨Yo|&wyÚ¸ÔįʖkÆģ ��€Éo˛ÆŖgHúūķ˛+ƒņˆĶ&™Õ“äkŧxŲĀO’Z��&˛)¤Đų]+lķÍúDĢíĩĸKĄ˜C˛p­x•ÔhÔ§¸67¤ļūŗų‹íúŒÚDģkWSP&’¯åŋ\ØÚwŖ8Śë–^\:æ.°ëÍĻgnE—HDD=eËÖĻ–˛5ÎË|‡Ģlđ¸8ĐøŖ~žÃĩĢ),?ReäĶÅæÜúĻ Ėú­í2‘č=™ŋØnJ4›R\ųĩ>i°čČĨ­.KĸYŸâÚÜĐTü˛9ŋU& ^`Îu6ĄĐQGbŪ%éáÅĩąunJqmnč}´@��€ilŠÅ#ŠåƒüÃKåˇūŪ´ąöo<Ø-QčøēŌCšįo=]5¯ û;e’}‡7íUž˙ Fž-3JwÔGˆe՞[§MH=đˇŪÅ=č8|2ã5Ž !đŋī‹uĖ]°š!JDŒ(Öö…×QåšŪæûr55ú<D$wŪ}…å5ū7¸^ž:TėŽ*ëdŒQđâ ÃĄVßGéLhÜŧņ —_īčübÁfˇHDáúÂâĀŧßú{ŋĢ]úĸY$FŖ™}įúßúßÕ.‹4ļHŋØ-��Āt1ĩâ‘üũå69ŗ`SGD\rŪē4ņ›Ë~"ã%áĨ  cŧyõ‘cģĒeb–Ŋ×; Mņļ….¸ų¸I”`Ķ•°aõN§š1ŨŌ-.uû†@DŒ KŪ˛rDD:“‘‹†™¨_’ˆÍJ`DŒˇėũĘېŖĻSKöÛfžcö4öÖítjĶemYĄîvß)ÚŅŅ8×eōDħŧ9ļ-PŖęüÍÁÎķÆØ9��Ā”öÛņ.āŠ"ŠŪß,ÄŠ5ŧŒJŨ–øyšÁÉ Ļŗg鈈dŅ[]\ÕŒöÉD$÷‘ũą‹B”Ôęûûž5Z5yB"#"^ÅgŒä~"–ēĨp^îļ ‹Ö’fuáŠŦ4Í0ŗ>í`fBŅ~˙!ĮüCƒ'äž~žW$m$FüPâÕ:ŽF?Å3šÎ5OÚ9��ĀÔ6ĩâŅpƒ‰üŗ‰ĄđŲümíēęēœZFÔķ='ö${ʘ{ĻËęîēŅęųlķ‚ęĖ3š'!�� �IDAT÷;÷ŗV^$Øwˇ•Ļ>8Ã8ŽzIYōΟ`TÕ=žķ‡û[į���SÚÔZ\Ķh5,ŠũŠ—ĸa‘͍9N;‡—n‡‡ĻG­gĪ{E)đ}˜OËŠeDDb04Ō뉯 ĻHTú§АZ;›č%Ifŧ!ÍĩūĀ)wŊ‹š/Ū!~h´jb?„1â‘JĢ"12´iIŧîxÅf%<gd1&ŽP÷˜;��€É$Qˆ C?ĸ(K]é`UÕŨ"‰UŸuk^]f&2,ÍÔøNT´ ’$zOīūĖ/sLĨâÄ@OX&ģk]•9ˆ ŗÅ"Qéá #Û9{Ø‘‰äpãaO_˛ëūqu…Žē^^sØ+ĘD$Ez"}ŧj¤OĐ9ŗ‘/ö7„$"Yč*^•‘S!R'ÛՂįŗfA–ĨHsEc8a šJŖĄ°÷$"’#—F ^Ŗéœ��¤ŽG}ßė|%Ã>ôc]Û(Kû°v§ÖˇcYŸ¸pčÅ#Õۓ3íŦ.OtĻ$Ûļ}ÍTíĩ3fß´×ŲâĪV}Jîį´ēōŖ7‘ƒÎ×D]ę2sėğí9õ‘÷Ō­Ž­É’ĒrL‰f˛\^UĨëąéˆ ›*ß×vī^hzÁ¨šāĒj{eĄi¤Ņdמz“5ä[Ļ? [ËksĩDd,¨Økč-^dzš°Õœ9¸:ĮeėIę-ĩĨ8ĢĒåĨŋ´Ģú—:×/(l5¯[Žydĩ ��`úúM<'ĸ[ˇn͝;w”׌Šņč=Ŗn§Š@qĘŠÔ_™1Ū…���ŒĢgZî7žĖŗG����Ī�â���€ÂÔc˙Ta:đŧk���˜0{��� €x��� €x��� €x��� €x��� 0YßšvīŪŊ;wîüôĶO÷îŨīZ���ā ͘1cæĖ™ŗgĪž1cMŲLÖxtįÎ"Ōh4j4��`Læ;îÜšŖRŠÆģ–&kļøé§Ÿ&ZŌ��€ąš1cÆėŲŗúé§ņ.Da˛Æ‹{÷î!��L3f˘h[e0����������������������������������������ĻC<’.­u{9hņF/Æ*|ŌĩņšDD(^\Ô!˙Ú*Ÿ9ė)qΡnîãŲĄA×ēœĩÅŠÎ"ËځĮ$Ņ—S˙Dc��0ŠL‡x4œPĶqOTņbŒDß f{‰#ĸP{P“šÄžn}OBō”{Mi†ąŸÕ HŨmR’Uũëj��˜~;Ū<'bÛÁüē^!ãWTÔēb‡Ëž÷õæĪŌĩîëÍ­ŨSon˛Tô/ג(GÃąy[Ž•¤ņŅæ]EŸGˆQŸŦÎ>đQļ1Tbû@]˙ÕzIm˛Ŋˆ'"ÁëãorŪ[U&‹ŽōZ—|~ץË1Æd‰Yˇ)´đb×ūmÕ=ÄHęãœû*ķM,Ü´c÷›E Ęd¨ƒÎbU;WM=ũZ^ėCĪNé‘>K/D‰d–VXąĶ΋wÔõ‘Dę•Ĩû–ëįØ×⤿Į„áFāągĨŽũeƒƒ°wđŦ/ØqQâĩjĒočX S0­|Lö��˜JĻI<Š…Y–įÔ&œuŊŪØ“[˛!WßâßTŋÅ"ū[{‹S}ž‰ŧM‘˧>Éä(\ëƝ Ĩ­øúD(ĩōĢõ:’ƒž+’DdŪî>Åx"’ŋīMtD$vt1{)O1FĄ˜ųۋ{9 V¸.*ÜųZĸčéUkŽ{ÛļD>kŅîéúĐDmič•(Ú˛ģš¸ŨvŽ„Æœ×ļX?ŅüŧjĻčķ‚ĻĖũą M›Ëŋ ›g.­üōL&G˛ˇČšīJÚšlž1ĸĮ/ō=î,g|S¸–ˆˆÄkûë6\ŋ¸ˆ“{Ɯįšųۃ†tã¯ūM���L|Ķ$Š’m&FDŧš—Ōc1ƒ-‰#"Ōhįˆž›’æÕ…\AūĒ›™Îô…Ž%É<q<OD$ûÛÃæ%F"’žī”L[4D1bZKGDb?"Ċ\mDDÔĮø¨Č[˛tgŪsmM_˜‘•éJ×ČíۈvĄuāf/ŲUgŊQ&ŅÃ}ƌyFDšĨ•5DŪ’1 Ž]q‚ˆ¨Ÿd•@Ä?­ŅŠõĀ}™Ņfb"ĸ`×˙hėÛ'Ā"��Ā37MâҐ1ũyg†wÎy–‡|_ŋ~2Ŗæâ;CKKÁļ^ÍDD˛˙†`ΜSaŒ Ū„%Š´?|ŗėú믆Ŋ7:<GsĒôGž´Œ\׃IžĄ>ū Lģēō?VdžHÍ[]û}ũŒ{õČõ=Éŋđ´1XJ´ÛĢĘXÃ=Y���“Ë4ŨšÍˆÉ˛âÉĄŽn‘ˆä ī&o˜ĮBMŸzĸœÁļ<ßNKĖ’ˆ$Q”‰"Ũū9VFD=mŊF›^Ų7o4Ģz<>‰ˆHlŠ8Ú!’ĐzōŧŸéŦYo¸{QB (éí†Hk§DD$|ßŲ§7k‰ÍbR4FD$üĘwņIæY=]™ˆ„ĻüU'ƒZ›1z­%LD$û÷7„†]6[ôq›īģÎŽĮe#Y ’Ŧ""­AõõHD$ŪTŧŅĘR“ŸÚô��Ā„6Íf†p†TME‘ëƒ=õ9C/œÄ´Ė_öîåØmANŨ[c`ŦŸUējë'~õ^Gū×ęúÖÉl•…:ũsė…Îī *6ė*Íy­šÉũĖē­’'NĢōîΚ@ Œˆŗn:ĸQsĨ›üûr]gHæ’JË2‘sqciÎVŊ†ŸĮi-ؘWžrwŠs1Q˙Ŧ´ĸ #Į8äÛŧÍu•%Ȥ^ųáFÔ]æ*î’%!&ûnŪ†Ī>YūĐäŌ0gcÕš;͇FcßĀûŌø%;×´īxŨuA=Į¨ÕķD}ž.Ųŧię*��€Šã7ņxœˆnŨē5wîÜQ^3ĻÆŖ7Î5xKlU&ĪšĨX@��xΞ]x˛ÆĶtq ���āqĻéâÚ0Ŧ%]ÖņŽ���&�Ė���( ���( ���( ���( ���( ���( ���( ���( ���( ���(Lâ/šuëÖx—����SĐ$ŽGŖ˙^���˜Č&ڔ×��������������������������������������������ĻC<’.­u{9hņF/Æ*|ŌĩņšDD(^\Ô!˙Ú*Ÿ9ė)qΡnîî¤p­ø5§mĶļ`ÍūNQqjhÂĩ.gmDqǺȞvā1ItįåÔ?ŅX��L*Ķ! 'ÔtÜUŧ#ŅwƒŲ^âˆ(ÔÔ¤&ą§[ߓ<ĨÅ^SšaؓâĨ}GŜúŽëžļcĻîŨÕŨŸÕ HŨmR’Uũ”Š��˜¸~;Ū<'bÛÁüē^!ãWTÔēb‡Ëž÷õæĪŌĩîëÍ­ŨSon˛Tô/ג(GÃąy[Ž•¤ņŅæ]EŸGˆQŸŦÎ>đQļ1Tbû@]˙ÕzIm˛Ŋˆ'"ÁëãorŪ[U&‹ŽōZ—|~ץË1Æd‰Yˇ)´đb×ūmÕ=ÄHęãœû*ķM,Ü´c÷›E Ęd¨ƒÎbU;WM=ũZ^ėCĪNé‘>K/D‰d–VXąĶ΋wÔõ‘Dę•Ĩû–ëįØ×⤿Į„a€_tĖŊˆãˆˆŠõŧÜ.ÉD‘NęÚ_68{ËÁú‚%^ĢÖŠú†Ž:ĶĘáŗ��Ā”2MâQ,˞<§ö0áŦëõƞܒ šú˙Ļú-ņßÚ[ü›ęķMämĸˆd>õI&GáZW~](mÅ×'BŠ•_­×‘ô\‘$"ķv÷)Æ‘ü}§hÚ #"ąŖ‹ŲKyŠ1 ÅĖß^ÜËQ°ÂuŲPáÎ×EO¯ZsÜÛļ%ōY‹vOׇ&ĸhKC¯DŅ–ŨÕŦĀíļs$4æŧ~°Åú‰æįU3EŸ4eî LhÚ\ūMØ<ëpylå—g29’ŊEÎ}WŌÎeķŒ=v‘qÉ=ugE×ö´ûĶ]œmp4ōMáZ""¯í¯KØpũâ"Nî)sžhæoŌOīW��0aM“x¤Jļ™ņj^HiÄ ļ$ŽˆHŖ#únJšWrųĢnf:Ķ:–$ķDÄņ<‘ėo›—‰HúžS2mŅňi-I‰=ūˆ+rĩQãŖ"oÉŌyĪĩ5}aFVĻ+]#ˇ{#څց›ŊdWõFi˜xD÷3æi–VÖyKzvÅ "ĸ~’U˙Ëã v”W×~li10Ô÷eF›‰Eˆˆ‚]˙ŖąoŸ�Kˆ���ĪÜ4‰GCÆôįŪ9įYōut~]üúɌš‹ī --Ûzu6Éū‚9{pN…16x–\tĻŌūđͲë¯ŋöŪčđÍŠŌųŌ2r]Ļ€†úüųƒ0íęĘ˙X=l¨"5ouí÷õ3îÕ#×÷$“Ø˛+˙ˇ­ūcÛ(‚Ô#ĨDģŊnj5Üč¯��˜ŧĻéÖlFL–/ˆHuu‹D$}7yÃ<júÔå ļåųûvZbېD$‰ĸLéöĪɰ2"ęië5ÚôĘžyŖYÕãņIDDbKÅŅ‘„Ö“įũLgÍzûÃŨ‹AIo7DZ;%""áûÎ>ŊYKl“ĸ1"")āWž{ŒˆO2Īęé ČD$4å¯:ÔڌŅk-a""Ų߸ŋ!4Üĸˇčã6ßw]×÷$…k Žsģëß(ÉbX؃ô`ˆˆ´MÔ×#‘<xSņF+KMCĒ��˜ÄĻŲėŅÎĒŠ(r}°§>g腓˜–ųËŪŊģ-ČŠ{k ŒõŗĒBWmcũįŪëāČ_âú@]_Ã:™­’#ĸP§ŽŊđŅųcAņ]Ĩ9¯U3šŸYˇUōÄiUŪŨ9(qÖMG4jŽt“_ŽëLÉ\RiY&#rŽ3n,ÍŲĒ×đķ8íŖķĘWî.u.&ꟕVTaäø‡|›ˇšŽ˛™Ô+?\ˆēË\Å]˛$ÄdŋĶÁÍÛđŲ'ËL.ų>?Ķ+°RW×Ā?įmøė“åąę܁æCŖąoā}iü’kÚwŧîē žcÔęyĸ>_—lŪ4âL��ĀÔņ›x<NDˇnŨš;wî(¯SãŅįŧ%ļ*“įÜR, ��<gĪ.<Yãiē¸���đ8Ķtqm֒.ëx×�����f����������������������������������������&ņ—ŠÜēukŧK���€)hĮŖŅ/���LdmĘ‹k���� ˆG���� ˆG���� ˆG���� ˆG���� ˆG���� ˆG���� ˆG���� ˆG���� ˆG���� ˆG���� ˆG���� Ķ!I—Ö:ŠŊ ´xŖŠc>éÚxM"" /.ęm•Oƒö”8į[7wsNōŸŨüšĶąØéX°ĻØŖ|äĄA×ēœĩÅŠÎ"ËځĮ$Ņ—S˙Dc��0ŠL‡x4œPĶņˆp˙ʼnžĖöGDĄö &5‰=Ũúž„ä)-öšŌ ތ6WĩkŪwˇ}åiĢNí)Ģî~øä¨Aęn“’Ŧę§T,��ĀÄõÛņ.ā9Ûæ×õ ŅŋĸĸÖ;\öu¸¯7÷x–Žũëp_onížzs“Ĩĸš–D9ŽÍÛrŦ$6ī*ú<BŒúduö˛ĄÛęú¯ÖëHęh“íE< ^įx“ķ–ØĒú3YDp”×ēäķģ]Ž1&KĖēũHĄ…ģöoĢî!FRįÜW™obáĻģŋØ,’ČXP~ CŦpĢęÜšjĸčé×ōbzvJôYz!J$ŗ´ÂŠv^ė<¸ŖŽ—ˆ$R¯,Ũˇ\Į8Įž'5o<& 3�ę7NŠX”Tķøûg¤Žũeƒƒ°wđŦ/ØqQâĩjĒočX S0­>{��L)Ķ$ÅÂ,ËsjÎē^oėÉ-ؐĢoņoĒßb˙­ŊÅŋŠ>ßDŪ&ŠHæSŸdrŽuåׅŌV|}"”ZųÕzÉAĪI"2owŸb<ÉßwŠĻ :";ē˜Ŋ”§ŖPĖüíÅŊ+\— î|-QôôĒ5ĮŊm["Ÿĩh÷t}h"Šļ4ôJmŲ]Í Ün;GBcÎë[ŦŸh~^5SôyASæūØĀ„ĻÍåß„Íŗ—ĮV~y&“#Ų[äÜw%í\6Ī҈‹|Ą“ŽĩŸIŋķÔŨũƒœmp4ōMáZ""¯í¯KØpũâ"Nî)sžhæoŌOë��0M“x¤Jļ™ņj^HiÄ ļ$ŽˆHŖ#únJšWrųĢnf:Ķ:–$ķDÄņ<‘ėo›—‰HúžS2mŅňi-I‰=ūˆ+rĩQãŖ"oÉŌyĪĩ5}aFVĻ+]#ˇ{#څց›ŊdWõFi˜xD÷3æi–VÖyKzvÅ "ĸ~’U?\ †õîīÖ‹ŪƒšÛJ-O~܂`$ ¨îˌ6‹ģūGcß>–��žši†ŒéĪ;3ŧsÎŗ<äëčüēøõ“5ßZZ ļõęl&"’ũ7sö✠clđ&,ščLĨũá›e×_5ėŊŅá9šSĨ?ōĨeäēL õųķaÚՕ˙ązØP5DjŪęÚīëgÜĢGޝc­ˇ5&žˆˇf/L(čŒPōhVĘK‰v{Uk¸Q\���0éMͭ،˜,+^‘ęę‰Húnō†y,ÔôŠ'ĘlËķ÷í´Äŧ!‰HE™(ŌퟓaeDÔĶÖk´é•}ķFŗĒĮ㓈ˆÄ–ŠŖ" ­'Īû™Îšõö‡ģ%‚’Ūnˆ´vJDDÂ÷}zŗ–Ø,&EcDDR|÷ŸdžÕ͐‰HhĘ_u2¨ĩŖ×ZÂDD˛ŋqCh¸E5nŅĮmžī:ģŽīIĻØÕŋŧwøūcjƒšHÂ$+ˆHkĐD}=Ƀ7o´˛Ôä_žž��˜ ĻŲėŅÎĒŠ(r}°§>g腓˜–ųËŪŊģ-ČŠ{k ŒõŗĒBWmcũįŪëāČ_âú@]_Ã:™­’#ĸP§ŽŊđŅųcAņ]Ĩ9¯U3šŸYˇUōÄiUŪŨ9(qÖMG4jŽt“_ŽëLÉ\RiY&#rŽ3n,ÍŲĒ×đķ8íŖķĘWî.u.&ꟕVTaäø‡|›ˇšŽ˛™Ô+?\ˆēË\Å]˛$ÄdŋĶÁÍÛđŲ'ËL.ļ[ŊcWŽŖœdš•öū_qDūę܁æCŖąoā}iü’kÚwŧîē žcÔęyĸ>_—lŪ4âL��ĀÔņ›x<NDˇnŨš;wî(¯SãŅįŧ%ļ*“įÜR, ��<gĪ.<Yãiē¸���đ8Ķtqm֒.ëx×�����f��������������������������������&k<š1cÆŊ{÷Æģ ���øĩîŨģ7cÆÄ $ĢšŅ›9sæ;w���&ĩ{÷îŨšsgæĖ™ã]ˆÂdũR‘ŲŗgßšsG$$��€Ékƌ3gΜ={öxĸ0YãҌ3T*ÕxW���SĐd]\���xFĻe<j}OŸRŌ3ŪUŒNäĶÅæˇødˇl5ÛĘOˇ ��€)o˛Į#ņü*ŗö…§…_n*t6v 4ŗnj¨Yo|ƕ=%ęå9`ãĮģ ��€id’ĮŖđ• !S†åæeO䗚F;ĒŽ^hÅi“ĖjöĖ‹{*o0鐎���žŖÉ‚¯ÖÕ{×ŧ$\l|xąLޝr˜ú׿†€DŅķ̜{}W7šM[ģ^\:æ.°ëÍĻgnE×Ā"VO™Ã˛ĩŠĨlsÃ2ßá*<.4~Á¨Ÿīpíj ˏ”ųtą9ˇžiĮŗ~kģL$zOæ/ļ›ÍĻW~­Ol&õÔŋë˜oÖĪw¸>¸v~ĢÕōˆē?°›ļv uåÛ1ßēš“Z\{´sÚ÷¯uZÍúų׎Ơ4LįB˙Sr��€Šo2Į#ŲwÁĶ—ļ"UcĪN“ŋžāŊŸVBĮוöĘ=ëéĒyU¨(ØßŠzã\Ũ˛YŗÖøÛô>™ŋņWĐø‡ß÷Å:æ.ØÜ%"FkûÂë¨ō\oķ}šš}"’ģīžÂō˙ \/O *vG•1Æ(xņ†áPĢīŖt&4nŪx…˯÷ t~ą`ķĀ"õ抛i5ŊķTZo|ŪÖĮh43YĘÎ)tx]QˇvˇûoūŪoĢÆĒsËē¤ÁÎoßīüB'ō��˜Mâx$u66SęJ+#fYiŖ–†ƒ(ūÆKÂK ,Æxķę#Įv/T?:Ī3 Øt%lXŊĶŠfDLˇt‹KŨãž!ŖÒˇŦ‘ÎdäĸaA&ę—$bŗã-{ŋō6䨇éԒũļ™į…==†u;ZFÄtY[V¨ģŨ7Dĸ §]4ŦŪ`刘ƚn™–F•Ž”“ŋņ’˜ēĨĐĻaDœáí‚WXەn™‚žvҜ­č���Æh˛~î‘Ørņį¨JbDĒs^áÖ6ļHéË9’"ˇ%~ž†hÆtö,Ũãēĸ¤Vk†ūŠŅĒÉâ ¯ēŋá‡1’û‰Xę–ÂyšÛ2,ZKšũՅ+˛Ō4Ãäv03 Ąhŋ˙cūĄÁr_?ß+ bŒÔs†:Wë4 ŖæûK‘ÛRŸoGŠuĮũŪûô‚H$ÆHõ„��Ā€I„o.xûĸ”grčī§ žčōyøéĸQa&‡sĪt9CŨ]7Z=Ÿm^PYã>bįÛŖûîļŌÔgĮ=ōģąúPį¤Ę:ō×mIĘŪ;~Mį���@D“wq-ėi jŪŧpŨĶ6ôsa:Øp-LÄiįđŌíđĐFčpëŲķŪá?7HcPS$z?¯ĄŠĩša›‘$Ɍ7¤šÖ8åŽwQķÅ#äVMBLâ‡0F#"žWQôöPA‘PäÁö šŋ°C)&Žm8õ<^ŠŠ4Ô9ĮãØˆ��Ā(MŌx¸p1b\ą:YŖÖ ũ$¯É6FŽ\–fj|'*ÚIŊ'‹wæ—9"bL#Q顉%#Û9{Ø‘‰äpãaO_˛ë•Įž‰>tÔõōšÃ^Q&")ŌéãU#}:€Î™mŒ|ąŋ!$ÉBWņnjœÚŠ\čėņNQ–Ĩ`ÃgÍCYFc˜CĄŽ LDROCã/|jĨyÉ"MāDŲĩ°L$Ešwšl›ÄŸuŪ"&` ��`Ŧ&e<’ŊÍĸiĨSš3ZķęJs´šÁ'3ĶÎęōäĐAgJ˛mÛ×|AÕ^;#2-tiƒåļ×L%éV×ÖdIU9ĻDŗåÍF.¯ĒŌõø2lĒ|_ÛŊ{ĄéŖūå‚ĢĒí•…Ļ‘ĒÔdמz“5ä[Ļ? [ËksĩDÄŦÛ+ ÔÛŌ ķ‡Ō߲nŌ8÷lŅŪČ}ŲáX\pĪ^>Â,‘aįgÉėĢvÍ7ëSrOôgUZĘ?č<c ķ NF>��ŖßÄãq"ēuëÖÜšsGy͘Ū3ęv‚kŲjŪΟézĤ���Cž]hšßxRÎ���<;ˆG���� “öũSEæĮūĖņŽ���†Ų#����Ä#����Ä#����Ä#����Ä#����…ÉúÎĩ{÷îŨšs᧟~ēwīŪx×���Ohƌ3gΜ={öŒhĘf˛ÆŖ;wî‘FŖ™PŖ ���c20ßqįΕJ5Ūĩ<0YŗÅO?ũ4Ņ’&���ŒÕŒ3fĪžũĶO?w! “5^ÜģwŲ��` ˜1cÆDÛ*ƒ„��� €x��� €x��� €x��� €x��� €x��� €x��� €x��� €x��� €x��� 0Mã‘ĐŲ”ŸZ3���˜JĻg<;ę{¤§Õ ���Ļ”ßŽw΁l(*vG#IÖn8Vnėz˙„?Āļ•p‡öčēŪ+nˆë—Ų‹[Ž•dōâųUšZŊRŊžøæPŗ’Ešņ~���x^ĻC< \¨‹­üōârž$SKLŌålJĢ;j8V˛ˆvČé{Ī-Mâ(\ëĘ?Ę,T3¸ Īˆ˙8=ĐlŧŸ����žŖéô };Öæų¯f8Ō—ë¸‡NŠx.pxãbŒÄ˜dî#""Îh1ŒOĨ���0L‡ŊG\ōûîļSÛ2ø›—ˇšrŨŅgÂgwTõŋUsĻūTŨ‘œylđhcÃö���ĶÂ4˜=’įbiųéi.C2‹ØZ#äš@ę‹JĒy:ŽH5{zeõ¸Ö ���Ã4˜=âæhúķ¯pŊļÂUĪļXˆæŲ-ŅãoæūMö[ܕÜ×ÖänģĸÉ[§ņÚßŲ÷ЕCÍBãV;���<ŋ‰ĮãDtëÖ­šsįŽōš15Ŋ‰P���<Ī.<Yãi0{���0ˆG���� ˆG���� ˆG÷)�� �IDAT���� ˆG���� ˆG���� ˆG���� ˆG���� ˆG���� ˆG���� ˆG���� ˆG���� ŋīžÜ­[ˇÆģ���˜‚&q<ũđ��ĀD6ŅĻ<°¸��� €x��� €x��� €x��� €x��� €x��� €x��� €x��� €x��� €x��� €x��� 0Mã‘ĐŲ”Įģ���˜Ļg<;ę{¤ņŽ���&¤ßŽw΁l(*vG#IÖn8Vnėz˙„?Āļ•p‡J’B%;Ēz‰#IVŋu¨|šN:ŋ*§U›ÎË1)%מĘī���€įi:ÄŖĀ…ēØĘ//.įIō7ĩÄ$]ÎĻ´ēŖ†c%‹Øĩü}ˇ—ũõârž¤Ö÷œûޤ{…(&¨ŗëķĩ$ĩo^pŦÅYˇˆī'���€įh:,Žé:úޝÍ+ŽmꙕžÜüPØ ų‚ęÔdžˆˆ3[4‘@ˆˆK2k‰ˆ¸yUԏ’��`üL‡xÄ%ŋīn;ĩ-ƒŋyy›+׍ŧ���#™ņH œ¯myCškû‘|}°3ōā”ÁbŒŨč‰ˆ$—`xÉHD$õt…d"ŪØ<ŗz\Š��€q3 öqs4}Õų‹O2F2Ў˛ÉvK´øÍ<ųXŨĸĀæĩ+.s$‘~įĄĨ<‰D*Ô¸yãm1ã *2ąņ��`š™ņˆø´Âē´Â‡°ĖÚ2^ö4d<zÆUrĀü|j��€ g,Ž���ŒÅt˜=ūsmã]���Œ'Ė���( ���( ���( ���( ���( ���( ���( ���( ���( ���(Lâ/šuëÖx—����SĐ$ŽGsįÎī���ā)˜hSX\���P@<���P@<���P@<���P@<���P@<���P@<���P@<���P@<���P@<���P@<���P@<���P@<���P@<���PøíxđˆįWå´jĶy9&EĸäÚW™c`b×ūmĮēå&÷ķŽ˛Ę\ë,˛œIm;•Åõ”9kër¨yWŅįbÔ'Ģŗ|”m¤Đų]‡.Į“%fŨ~¤Đ÷ŗ��ĀS7âÅuv}ž–¤öÍ Žĩ8ë2c1õš*O†š¤öüÕ-9Ÿ,úųEáÆĄÔƝÖëHzŽHĪŧŲPáÎ×EO¯ZsÜÛvĀúüŸ���ž­i¸$ŗ–ˆˆ›gPEũQZ¤RÉĩĨ9‰‘–™]î"ÍĢ š‚üU73é K’yņŧ?"Ċ\mDDÔĮø¨H„ų#��€ŠfšÄŖGČ-eīwšrÕDž)ÕÃˇb†wÎy–‡|_ŋ~2ŖĻŠ'–\tĻŌΞoĩ���đ\M“­ŲROWH&"1āÍ3Ģ%)FŧVEDbë•n™ˆˆ¸YLŠIDDŅ ?*ÉĄĻO=QÎ`[žŋo§%æ 1ŖYÕãņIDDbKÅŅqܞ���ži2{¤ŌH›7Ū#1ž "“ãĨŧW>/ĪÉ9ŖŌ8ŪÜé(=ŧģ1šfÉ[Üû›×tj¯V“LL­eU…ŽÚÆú‰_Ŋ×ÁYņ]Ĩ9¯U3šŸYˇUba ��`*š&ņˆ4Ž’æ˙äí%ûĐ?rÚöeëNšßV\¤~ģæ‘#†7>žøÆ3,���Æß4Y\���­é0{ÄŋqŽmŧk���€IŗG���� ˆG���� ˆG���� ˆG���� ˆG���� ˆG���� ˆG���� “5͘1ãŪŊ{ã]���üZ÷îŨ›1cb’‰UÍč͜9ķΝ;HH���“ÚŊ{÷îÜš3sæĖņ.Da˛~jöėŲŗīÜš#��Āä5cƌ™3gΞ={ŧ Q˜Ŧņhƌ*•jŧĢ���€)h˛.Ž���<#ˆG���� ˆGSšÔ”“čú4üdŠSĖ›[ŸnA���“ĀdG˛'O˙‚Q;đ“hĩŊönqƒOüåËBÍî€4ęģ¯)sŧqŠΕ-׌w���“ĘdGDDŗ^9ūM‡÷ŋ::˙ZwĀĨWå9×6ūÂD‰˙‹ũ ŖGŅŽĒŖ—#ŋļĖqÅëĖžw���“ĘdŽGÄqžįyÎ”–ŗ§áË=:˙Áâ†(ÉÁ†"×ËVũ F}Š+ŋ> ‘ˇÄ˛öJ,pȑ¸ât˜Hė:ŧÖiI4j­ļU%͏ÎEΝrî ô]Ũh6m:š;ßžÃ+Ν2;*Brëģú—KÎ×ŋįZė´Ĩ؝ƒƒM¤ž†÷\/[õ‰f˂ŧÝŅÁË:æ.°ë_0ęį;\ģšÂō#wŒ|稜[ß´cYŋĩ]&Ŋ'ķÛM‰fSŠ+ŋÖ7˜ęäČĨ­.KĸYŸâÚÜĐTü˛9ŋU& ^`ÎuŋĐQGbŪ%éáÅĩąunJqmnč}´@��€éaRĮ#%͒-ŽĮsC$ĸPõæ˛€ąôjāƒžšTĄâŊã~"k‰ûũôģÛūqņmÔ˛¯đ<e׌üÍŊ“ûfĮž&åڜúsuËfÍZXãT¯[i•;ÜžÁ¸ ~sÕ¯^ļÔˆQôĘåØē†¯<]ßÖĻ 7W…ˆHôåWÉËjZ{˙áõÍiŲøŪé0‘Üux÷–×øß`āzyRčPą;Ē|�Æ/Ū0jõ}”΄ÆÍ¯pųõžø}_Ŧc 6ģE" ׿øÖßû]í˛ĐÍ"1ÍėĐč;×øÖøŽvY¤ąeôː���SČŠGÄtæ9DdØäūŽa¯gDœyÉBm,zdc—yŦĩĢfĩ‘#bęĖĨ/R(đø]F\ڊtjģŌ!‰]˙?{÷ÕԙīūSfû{ė—}Ī8Éēw5šgI5‰tLÔC°`R;u�ĩBĩ‚ÕĸUŅ*`ŠÅß =ÕŅ ­ŦŠļŠ*ö āLųq§„ž)ĄßĶ„õŊ$zŽIīúēs˜q3Ë/;uČũ#üÚüˆųŠī×b­˛÷ŗŸįŲĪŪyŗŸŊĶ› ŠĨ‹Â|oi–%Ģ"bTˌ gõ×NâĒJjĨÉÛVŠX"FĩiƒļéjĨƒČÃķĄ3DŒTˇįssI’ŦŸMéÖjĨ,Cö˛Ë ĒWw ˇ%QVWZˑĢĒŌ!7žēHJDŌé/ĢÕCūrGኃ,��āI1Qŋ˛_‚@Džk)|CÉŪ÷Ž}kį"Z=}vÜ:˛˙ã*‡‹ˆHđ0KũĖ%1úĨ‹˜ôĢ•ü"ŖPUÖ¤6čHG!2š´cŠ\BnGßÛ\ۉ$Maז<$sš%#4ekŦNĄ[õËãČûšę#Wtd&§ÍåąŠ™}¨ŗV´‰#…ÃMŌÎeH* c)đK<.já���OŒ') VËmR$ȉœ%iÅĖž37VŠX"Įû/™Ē{/[ŸģqoƒáDɃœ!ĒØŽĖņ[6Ŗ[f I)­åu ‹rŲĄū.üÔ9ÍŦÍ()0†t­Ė°,ŠSŠkŒļēšÚО7/>ščTi^ÛgCŨ˙ŽÚUš7’DĨ4‘8Ä ƒē?(€Â{–7¸Â��žOĐäšũōéJZ`Šf‰ėæ&F÷ō*KDÄ7Y\žŪ sM œâÅdƒī ŽĩąŠĪŊE˜â¤–ĪŽ”}Ņ ]ē¨ķŠĩēœŗvN‡‹$ )=§’‘Ķá–vbb}W´x‘Ē˜Öī;SZdĸë—jũÄšBFN7ßU ã+Eĸįčŧi‰ģmoõũ‹ &Ą3ΜÛ˙w ˛p��€§Ë„ŽG<īä8ŽãœļēԃI/sFíÚcd‰H*—Mĩ <oģđÖgN6˜sš‰ˆ!†Z›ėŧ °)šĖ."ÁY}đ=3…đnŽĪLÜÃÅûb‡jé2Yũ‘üÆStW:ĸāĻ‹…5qˇÎ–šÂb"å$])”{ß œųƒ”…Ļ\‹@ļcĻ…ÉG˜@DŧŖÁŅ*•ČüÜSfLP;>Î-ąņD‚ŗ&{ulRƒH67Jæ,ûđēSxĮõüËö`ßâšœėæoy"Wü¯@ 'Qá���O—‰Zŋå ô˙ŧ@˙‹¤Í…MōÔ²Sņžo@T'īZōEŌ|­æW‡\ĻũĮSu|QŠŠČ!5,] Ü|ežņˆ#zgÖį~Ŗ2<6­TąåTÎyíŽÅ™UĸXĄyҤ°ˆ5lŧÉ)–˜”fÎJC16zĨâŗ”ųzåÂŊVÍîŧIŠŌec•Ķô1ģnÉŗNėÔ2¤Út<KQˇëEÍ4ĩraú Éļã­“'œy™)IĶ…Ģ5ŋ:d×(HQ‘:=Ē){q„faF…6aQG]ØEéģ#šöæĢO ņēĢúQ…+gTh_].ī5Û��đTxÆëõŅ;wĻL™ā:ƒZ8p#Tė0jØoLãwÕ6t\õŠØŽĖ )úãÛsĮŦFŲķ“ųŊ–ãącV��€Q6rĄĨká'éÖė%pæcŲĨĖ+ŸđÔ���Oļ‰<š6z\­Đë7ֆeå¯ ëē���ĀÃÕŖ@ČÖ~jYÛ÷׹G›ÆxVKŗī–ą­��Ā“W����D����D����D����D����D&ę“kííí---<hooëē���ĀMš4iōäÉAAãč’ÍDG---D$—ËĮUo��Ā øŽw´´´H$’ąŽKˇ‰š-<x0Ū’&��� VPPĐäɓ<x0Ö™¨ņĸŊŊŲ��ā 4Ūn•AÂ����A<���A<���A<���A<���A<���A<���A<���A<���A<���A<ōš•6{{ųXW���ÆÄ#����‘uFœPS¨)=Ÿ %"û1ã&Ęû×mlÅÛ;N4Kŧ {åЁåa [ķŲ’ÂŌ‘ëŖŠîwĘv oōiĨÂcs5Ų™Ĩ Žj‹Ûnķ,{ˇpUqÕw6O˛•{s–‡1cØR���OūÕ#FŋtŽëf9GDdŊv‹â—Ēų›š9ß/;sŠäüĨ’Tz/į3Îoäđ¨ŗ?“3×vōģ-īđoōŒî‹×lÄßĖ>ā^yǏč|qI:~D9���01<ųņˆÍ2ƒûF%GdģQ˛Ė¨ [ŊU9WJDÄjurGŖÕ š!ĸ‰DĻU˛DÄJCxÁCļúŽņôēD͊Ĥü&â]ÎQh���Œ°'rˆ™kŠĖÎ˙ŊSå(—Äɉ\­&<ē`bkŽēFū¸5��€qä)¸zDDÚĨ‹ø[§Kž–›~!'"•N펭㈈xKS5GŨš Âđ.7ßhq<ĒX…AíēYn'",—sKlNT���0î= WˆHĩ,Ęm,y./Ë7Ŗˇ/ŗqķēÄĢ,ņ¤Üy(^Jˇ|˅_Uoܛô†R. e*•Ūw¨~ķVĶ &X ŲĘw–âÆl��€'Ā3^¯—ˆîÜš3eʔ�×ԁu���€Ņ7r`h ?“k����C<���A<���A<���A<���A<���A<���A<���A<���A<���A<���™Ā˙KÚ;wîŒu���ā 4ãū—´���O†ņvÉ“k����"ˆG����"ˆG����"ˆG����"ˆG����"ˆG����"ˆG����"ˆG����"ˆG����"ˆG����"ˆG����"ˆG����"OC<â.ŦŽÉļ<j)ķۆÕ׸ҍ���ŒkOC<���„uF WúvJ~įn•'æOQ1\MîÖwë„`FđHMû§¨˜Î%yķ›÷Á3Á‚Ā.ČĘߊgíĻŽČrsî&;ķōņSņa‚í›{/ēˆfAFūÎ()9oîØuÎÉ0OęÔûbecŲZ���x OÉÕ#ˇ‰/8ŠėãW…ÂwËyÜnYō‰˛O/•žßD…'˅îE9ž–.)ũôRiÖs×O|Á1 ŲŨĄ8~&˙EįĮWmd=‘uQžSúéĨ˛SK—~o'ׅ]˛Å%gŠKĪ$pŽ•c–��`ÂzJŽą1†ˆ¤ĩäC‹‹–H$BÁŪ¤KÄo˜¨iF*eĒķS¯C‚‹įe<CÁr•RJD²ŧKā,nuNJ!"yüņSD­#—}r ã䈨1h'���<ž§$Q×ÜY‘@BųūŦjmIIŠŒ¨~Įü“=l<˛õ3驒<Cļc1ov­Æ_ ŖŲpĻp9"��ĀÄ÷”LŽņu56ˆ¸Fŗ;T+ãy7I"â*>Ģë1ŗF‚›į% CÄוÕržVĄŸŌ¤ڐ†šFˆœ×ŌV`e”zUĶJŽˆˆ¯˙Mį(´ ���FÆSrõH&\Ūŧņ{Îá–Ļį/bĨ|ę/ÎHJ*–Čc^Ūŗ÷ČŽËsS‰ˆˆ‰|%é܎_'ېÉôɛ–WĖ.ˆÎīsåHz`åŽŊƗˆ<! 2ķÕ$UīŨeÉI3–3ŖN; õ��ĀpyÆëõŅ;wĻL™ā:ƒZ8pãĄ���0úF. má§dr ��� PˆG����"ˆG����"ˆG����"ˆG����"ˆG����"ˆG����"ˆG����"ˆG����"ˆG����"5ĩˇˇu-���āqĩˇˇ¯@2žj¸I“&ĩ´´ !��Lhííí---“&M늈üxŦ+0D“'Oniiq:HH���WPPФI“&Ož<Ö™¨ņ(((H"‘Œu-���ā 4Q'×����FČSīŋ¤M*åzۚŖÜxSRŅ\i˛ōĨėūiĖž¯Ũ\1¤ŌģTlWÎģĄķuWØ0TcėÓ¯đÃ[!��€Į5Q'ׄ˛TÍÖZO¯ßJ~}ņoĪ ¨�ŲōÃŋ$Ōá¯Ų0qV_ļ+Č^BŋŠäŖŊīj��� Ÿ‰ˆˆB~qäˇģ{†!† a]™‘Ē4ã7‘ĢęÄ1sēßxÄ*"´ŖWĄ“j��� Ÿ =šÆJå2yŠ”eˆˆŋ–žx¤âÜæƘ…Qēů_° DDÕÛ5‹^(Hօ›Ūˇ÷œ\ãŠ^7Î×*Âõ1Ģ–;;‹ÜUųІŲZExLŌūšŽy8Áqũ­dÃl­2\oXũöu{ĮüW},iĄ^ŗîX•ŖŋĘ Ž+o˜táZÍ|Ķæ’&ĄĮīûčē°Ú¸§ąõÆF­æ"â-įŌ^ŠRNS+ÂŖLo^ŗúVîgV‹o(Únœ¯ULS+į›65öžļõ�ņ %ÛM õĘp­nqę‘jWĮbΛŲ+ĸ”áZŨâ×?*;f OžĀq—“:Ö""r–$*_:įėY@ ŋ•Ŋ"JŽ×-~ũũFw`;��`TMčx4†¨éĘ5fËų˛Ę?ÜČS~››‹÷ũžûâ†kMéKֆu/͕eĻŌ+gĒm,Ų"šĩyë9'ãą\ž!ÛTúGŗųL$W˛÷´ˆ„ēũŠŲŽČã˙jiú÷ŠãÚĻėMĮ"ūVîÖËBbQũŋW—eČ*Ę{ĪúŲ‹2˛•ûū`iücÁ2ĮåōŽäŌo˛Uį —…„ŧxĘŌø/ęl<ÆE¨˙ŸVĮöĢ÷î(ę7Ų>ÜqÂŊėLĩã6Ԝú%W˜yÚÖ§gzô�W–™vBXvĒĸéßÍe™Ī•oÜū‘ˆ¸+ģ2¯ŗ›ĘūdŠ˙dŊģä3+ÃÖížŊœŨZö'sũ'ëŠô ŋwh��Œ‰Z?{ešZŅũŖ5؈ˆ"b$. cˆˆUG…’ĢŠã’Pkč‹éŅrļįĮ=WUR+5mZŽbVą$ëčņT CD$˖nIŌHFĒ›ËēN„Ú‹ež%ë#¤DÄF¤¯_ĀŨēj#˛ÜĒælHQąDŒ*aƒĄī7¸Ē*rãˋ¤D$]úrĮÍ:ØŖÛķ¯Õ%–ˆ¤†uŦŊņv˙÷Œķ<O Â1Ríú’?–íTõí´ŽāĒJjĨÉÛVŠX"FĩiƒļéjĨƒø¯oXØEŠ a ĢŲ’<' h4¸Âã}…¯Tá���Ŗe‚ß{ôÉļˆî× #•uū["•2ŋíņ"“÷žáč{›‹¤ĻÎĨšEąDDIewū0 C‚‡ˆs9[ŨÖuúë+ ­Â\—Ā .AĒėÜ ÉU˛`K¯­¸n’+ēļ" c‰§ $ŅŨ<g>™}â–ÕÕ*‘ĐJQtˆ~ÍN}jöâ¨ĶÚ9 Œq+Ņa}oÅęîīm.íD’ϰk;’šˆsq$Ņw<F.“RĀS`#Z8��Āh™ČņˆXi˜"ėŅ‹õĀĐ .WôģhpčĒ3EdŨ 1,CeŊęī⎠ú­Đõĸß{˛ŸKÛz+ėpaQÁ5ŧ•4`ĸP,?UšČ^_UsëFÉ^ã‰kû>ųÍō^7w‹z X›QR` éąi–œM•ŪĢ9ũŽÂ��ÆÔDž\ĪŠdä´ŨîxÅÕ_(ēičûޤ29ãvrŦ´Ë0 Ë#‘0œËŲą–`ˇ}ßįŪ#‰BBœŖķödîļŊÕ_=ņ_ÛĨҝ gĩųųš į‰ Ķ-IŲ]đyŅIíŚŋØŠŖáˇ´{ÛÄ2DR‰”ÜŽÎÆ;ÎîU„ÖŽZšü_õņW¸Ģ˙Â��Ƌ xÎéröüá¸ÁÅ tIĮ—žüČÂņŧãz~fn™{Ā;‘™Č•1!U'Vqo-IYœYÎŖ›ËԞ-j䁺œ;]Ķ7^ÉæFɜe^w Ä;Žį_ļû+ˆFā.^‰„åėWWpč†Āįî7õ8K’ ŋ~ģÜ)oo´¸šÄĪH$E eĮŪˇđDgū eĄ)×"Ģ‹Õō兗­ŧ 8kŪ+úēsņP9ë˛6rDDΛgkÜ}o?°đ녗í< Κ÷ŠžõW��Ā™Čņ¨õ÷;ÕķįŸ“ŪëũŦÖŖIMųŠÁ7ÆjžtÄ}üŨ5OØ1sß)ܧŊŊX¯M) Ųp*gKÄFī;”Ā\JÖΎ2åˇŽLžėiíĩĻ:=Ē){q„rqF…öÕårßlÛ�’æE“Âz Ö°ņ&ĩiŅsúWzåü”ŗ´æøá—ÕŽƒÆ7û&$yŌŅ}ēÛšŋŌ+ĻŠu/LÉ'öÄú›K”ĨË*6Æ*§écvŨ’gØŠeˆdË÷æ,âNš~Ą{õ˛$iigočļdÅqų/ęšLšbuÁ=fSøEÜIãĪĩēW/ËR_V“ßR���ÆÂ3^¯—ˆîÜš3eʔ�×ԁĄbáąØ?0ūĒö•?/ø 7��FÔȅ–Ž…'ōÕ#���€€x��� 2Ą뇑ļžėß׏u%���FŽ���ˆ ���ˆ ���ˆ ���ˆ ���ˆLÔ'×ÚÛÛ[ZZ<xĐŪŪ>Öu��€! š4iŌäɓƒ‚ÆŅ%›‰ZZZˆH.—ĢŪ��€Aņ]īhii‘H$c]—n5[<xđ`ŧ%M���Ŧ   É“'?xđ`Ŧ+"2QãE{{;˛��Ā ((hŧŨ*ƒ„��� ‚x��� ‚x��� ‚x��� ‚x��� ‚x��� ‚x��� ‚x��� ‚x��� ‚x��� ōTÄŖēˇĸ ߎ:_Wo×ŊQ3–��€qėŠˆGDÁ æëÜÂŖ—��€§ŨĮēŖ$,u—´đāGņ—^ ëū%oū`ķū/x&XØYų;õŦŊĀ´Ų9—ÜNG“ķjŦģÖârXųč}gÖG0|]Aæ{5†xžÛshM;ví��€ķ”\=" ŅíÜ.ģšsŲŲãwOË—”~zŠ4ëšë'žāˆ†œîĐ ‡ú%wâ%Č;uâē|ÕB‚ųXv}ôņķ…Eį‹÷)>Ë.qŒY[���`$=-WˆˆŨĩáRRnŲ/އtüF*eĒķS¯C‚‹įe<CÁr•FJDR‰TĒ “‘”•´ō9mß:mßĻ­¸LD$‚Ę%‚ŗÖ���ĀHyŠâ‘ty暋›Ū­Jg"ĸÆ#[?“ž*ÉS1d;ķfįR!Ũ+t§ˆHjĖ)ÍԌZu��`L<5“k>a ûbšŽ4 nž—(d _WVËyZũ߸-×Ԛ› ‘Ŋôāûf~ę ���Ŗī)‹GĨĶwÍmm∈‰|%I8ũëĔu{t›–ĶÍė‚FŸ5õÛōâog¯H4­0mŽ”ĖUáÆl��€'Ķ3^¯—ˆîÜš3eʔ�×ԁu���€Ņ7r`h ?mW����ņ���@ņ���@ņ���@ņ���@ņ���@ņ���@ņ���@ņ���@ņ���@ņ���@ņ���@äĮc]ĄģsįÎXW���ž@d(ŖÁ�� �IDAT8ū?ā��€ņlŧ]ōĀä���€â���€â���€â���€â���€â���€â���€â���€â���€â���€â���€ČSęۊRΎ1,ėüYœüžíqĘĢŲą0ŗŠČ^`28]I¸™2?ŗJô+×G+ŒšU���~øI;ÁęôâŌŲ0•fČûƒˆėÃT���Œ+OI<z{iŗ#r.šŽ&!æÕXw­Åå°ōŅûÎŦ`\×ßÜ~ÚAŒā!ÅËy˙FˇŌfß\ö§ŖaŊ‹Ŧ%ÛŗKÜÄxfæ–wß^$%Ū|,-§–dĪIU,ī[Šģ•ŊņX#“ËB¤Dd~ÛpÂŗˆq8c˜„ oēęfgôÛō2tŒåƒÍûo ņ<37#Ok-ÉĖ.u1 ņ‚bÃģ–ČGšˇ���žpˆGDD CNwč†3Û¤öŒŋ:G˙z)OÎ]YtÕ˛>Báâĩ›Š¤Ä]XŊė´9>O?P1nNˆŪs>>‚%{)­Øļ(ƒš°˙Ļ|oYžžá+ļĮ”0DÔpâ`ūDY†ŠáŽĨ,ž%õmŪæÖūáŌ–ŦųĻĢĒüŌ4‘ëŖÕÉī™ËĸŠ?fS+ŽĮ2ÄÕ_Šw tûbĄ{å'—–K‰ˇ\+wķ$gG¯§���žOI<ōX Sc.uŧ`d ygÖ¨E ËU)I%RŠ2LNDRVŌĘ DR™Ôqpķę†8/įũlE"elüŒ†87¯m%á{‹KņĸŠ!"VŠfž&âŦODNJ!"ŠF/cÜž*)t,q ‡ĶiĒ$"ĸVFęâ#â#HJĢ_ŊČ(cˆ1ĻuĮēT‹ņ—ą1ŅËЍ���†ŲS‚ÕŠ…Ŋî=˛ĨĻ6 Ės¯œē´„ˆBēßbēū%Wš™íJ(;'%áúƨ~6b?ˇã„gįo‹°Ä•$›úÜs-øY—a˜ŽM3s3‹G1=Ū;PŠw5˜koĻžW˛­ė_ĸįf•Vrļēš/Žn5M).2 ×=U���@ô”<šÖ¯°”š?V×˙áŌk*‹ņ\+#“I‰Čyķ†EđrZ]ŧ$4Œ%l×˚ˆ “9Ė6ˆøš[Vˆ¤a ˛Öۈˆœ_W;z•&Uk% eõ<WžŦŠãNJÎՑ,"6aĪĄ5ōĻz;ßxĄā'U-0mËKSZĢ~t���ķ”\=ōXO$Šģ^2ęô‚Ā.ē„_x5͏Z&Würeē.ûhæuU\˙‹Ē^a3RVÔĘĨƕНĘsåVmȈNË1™$2šö95ĶJDsSwŠ7fĘ%r™FŽbzÅ-uzū†7÷&­8ÉFŋõ¸T*Č;V›Ž0Á$0ęôl0ßz2íĨ†’m9¤r§���@ŋžņzŊDtįΝ)SĻ¸Î Üx¨���Œž‘Ë�C[øé\���čâ���€â���€â���€â���€â���€â���€â���€â���€â���€ČDGAAAíííc] ���x\íííAAã+Œ¯ÚnŌ¤I---HH���Z{{{KKˤI“Æē""?ë Ņäɓ[ZZœN'��ĀÄ4iԤɓ'uED&j< ’H$c] ���xMÔÉ5���€‚x��� ‚x��� ‚x��� ‚x��� ‚x��� ‚x��� ‚x��� ‚x��� ‚x��� ‚x��� 2ŽâQPP×ëëZ���Āøåõzƒ‚F<ŊŒŖxôėŗĪūå/ëZ���Āøõį?˙ųŲgŸé­üx¤7¸Ÿüä'---˙ųŸ˙ŲŪŪ>Öu��€q'((čŲgŸũÉO~2ŌGņ(((H"‘Œu-���āi7Ž&×����ÆÄ#����Ä#����Ä#����Ä#����Ä#����Ä#����Ä#����Ä#����Ä#����Ä#����Ä#����Ä#����Ä#����Ä#����Ä#����Ä#����Ä#����Ä#����‘mĩ;wî k5����Ƌ!ÆŖ)SĻ k5����†ßĐ.č`r ���@ņ���@ņ���@ņ���@ņ���@ņ���@ņ���@dˆß{ã͟˙Įš’Ģíííƒ]1((hMԞؚ‰Z��LD¸zô„Z6"ĸöööķŸ\öú���L\ˆGOˆĄe#Ÿ‡?<ƚ���LtŖ7šö_˙õ_.—K¯×Ûë­gžy†a™Lö͟ūtÔę���Đ¯Ņ‹G˙ņ˙Á0 ˲ũžûđáÃ˙ø˙@<��€17zņč¯ũëß˙ũß˙đÃũžûŖũč¯ũë¨U���` Ŗwī‘Įãioo˙Û�ÚÛÛ=2<*fą1fą1fqōŽĸzn8ļk/HL*rõû–`ŋUn'"îĘ:SŽeĐ% æƒÆ…Ƥ"[ +đåæūkØölŪHŒYh4,4Ĩ4ō­ķ㐠ū—Kâ3[‚į��0z˜>loočâööö‡2ú]ĨgâX"â?Úē=Éq ėķxÛ KģT2Ā[ök'ohĸ…I—Ÿ)BÉΚ¯)ņDIŠ"Đl×Ū+‹[¤— a[Dd/Ę:Íl-ûƒåkvüjīé˜Ōaū×ø‡úõ˯ü\úwžWķæÅÅūķŸ~ķįĄm��āi1zņč‡~xøđáÇ>$õÃ?ũ¨ãĢYûîļęÅ^ĪĐ-glŪ<tÕÍ0Īčˇåeč¤\Mî֓ ÄßĘsާiûĩėœí 4sÃģo/qŊm8áYÄ8œ1vō›)ŋ,åvÚÂsŦQFītyĸ˛ō_ŖËŲĨMÎęÔ#ĖnYáz[Få>-W•ŸõžY`¨U.ŨshM{kķüËŌäįx‡ÛnsGdîŅwŪ;eų ģĖádönfw7z>Úuđ*Į°ÔĘčwåe褿î $)ˆˆøšÜũ_Ø[›RŪ[Ā|öŨ‹ŋũÍ–ˆŗT||‚Ų•TĄˆ– nŪá"SÎņ$#ônuXJAIY"b* īh}D‡Ožģĸ;ŅßMū§ÕÉ˙Ÿķ_jī u'��< Forí‡�ˆV`5Q2‡ÅAÖYWU9Ĩį K>=eÉ|ĪL\å‡åŠŨĨį‹K??ą’uķÄ]ÉųŌKJ?--JĨĢeЍמ{Š#šCÄ옘œŧųMQÆs÷æÔŽß  ‰H/Üõß}‹Õīf›#Z\ōiÉNÉåėBQ0 ŌīÎ;ü›’tÉõâZĄĢzÚõ{ŒŠ°ÄĮ“TÖÂĖ‹˛ŨĨŸ—œĪ×Wgžg&ę[Ö°!E)5l+Ú˛vĨŽéj%GD‚ųZ"a‰œˆÜNYBŪáŖgÖ3'Ū-įûi51R–!"âĢO^Ĩ„•Z˙ũũEFūß?Øož*ūĘųũđŋžúäÔį˙ī˙~nNä?>öž��xĸęÕŖööv¯×Ûīƒũííí?üđŅëņkAˆ!ŽÁâpē3M•DDÔĘH]œTVŧŨôFô‹ąq‹LŅráVĩMöĸ–!"iėÛEDdšÉ(tŊ’c”z-CDŒB#w}mīSC§­‰Ņŋ,'"b" ĄÎKM<…)õ*†ˆV‚@Ôg˛k°´F¤j|EĪÕRŽÍEZ꧝õXûÖNS‚ŗėkuâ6) Dl„VADĆĒ$.‹‹ãû´šHJDÎ˛Ė´‚āgÖĢŅß˙0ų'ôđ^Û˙úî‹ãg&ˏ}õz3ÍPũ@˙ũ˙üɏé?ņEG���Õ{ŧúžëõz{Īģq_Wģ•+$37ŗøxTĪX’Pô¯ŋ´›kĢʎ%Pæ}ĸ#"ę•\fā›–<ž#ˆ^1ĸ˙ô~÷øŠ€jÍJÚ~Ũę˛ĖY™Åõs:ĶOĢÉ^úúæRÍžķë^=ũ•o%6Ü´yÍÃãį*Š2#~{ō?ũ”Zm­ČF���ūŒŪäÚÇģRëÅ÷û‡{LŽņí:É×/bĨj­¤ĄŦž'"âĘķUqäŦøā‚… ĶĮ­}gגāF+¯ŒR9*Ė<ņ™Ļũ5ũĮáÛjŗ@Dŧ­Ū)SvÜÖėé^VŽ)XjíDDBCÍmšNØ]áŌmHCMŖ@Däh°P„ļ˙›¯b„Ž­É–$ą7Ū:iJ˜Ûą žĄÆ&×hv‡jeũ´Z0Ü\ĸÉ;H6""§š‘#ĸŸ†˙z횟Ģf¯ØžņS"jųwķÔ*��€§Öč]=š4iRkkëŗĪ>Ûīģ­­­“Ú˙"˜?2->IB+OĪÍMĖ/IĶ0Dęôü oîMZq’<Œ~ëq)ą ‰yWŌE fˆXũĻ<šŒŨģÉŧ+Éx‚Fŗå]ãŽígL(ÕdĻģœ.fåĄĨr"ŠŌäæ'möę}īëˇåÅdėxŠ–aZؚŧ,Q@ĪáĢĶķ_ys¯iÂÔx4OKÔß×°ĒHy~Ļé­ŨEīDKc–Ę÷¨=¤ę|S"į/oŪø=įpKĶķąÄônĩP•sŲî’lūÕgžĘFl/ʋõ”\Ÿ^ũĮ——……ü4üĨáDDôŋīūļ¸â.��øõŒoĒëΝ;SĻL pA-ÜĨššųßūíßūō—ŋô{īŅ?üÃ?üüį?˙ŲĪ~6Øbv+möÍe:ēhÄ60(|ufŌĨČĸSqR""îÂę$[FåžGÜmíOúΡúûõ“i~Š üwųīm˙OåÎ�_ŊyâČ;Cß6��Āx5´„3zW~öŗŸdú™@6fœåfîėČF#ĒÍÕX{šąŋki���0€§į{”Ŗ ū=ÖuđQŦ:UēJôéĒķ•cT���čmônÍ���˜����D����DžĪ<ķ˘Ŧ ��đäA<zB$¯\>´”ķĖ3Ī$¯\>ėõ��˜¸ÆŅ“kííí---<hooëēL<?ũ?BļŊöʐWŋsįÎđÕ��`DMš4iōäÉAA#{}gÅŖ–––gžyF&“ũčG?ëē���ķˇŋũíĪūķŸ˙üįŸūô§#ēĄq4šöāÁƒÉ“'#��@ŋ~ôŖMž<ųÁƒ#ŊĄqÚÛÛq0���øôˇŋũmġ2Ō����˜X����D����D����D����D����D����D����D����D����D����D����D����D����D����D&l<rž3MS+ē´š…Ļ”ˇ.7đc]ąGkĖ]¨ÖŊU?ÖÕ÷¸ËIĶÔ)ĨũîŅąęCۑÅjå5_P՛zÅâcÖĮ/hJë‡p3%\m*q [ÎsĻiÚ´ ˆø˛TÅ´ä Üđ,Tŧޜfzß><ĨõÕŖĢĮÁ<@74†ˇĢûÕŊéuUÃ>VŸ=†k€ģÆßøŠ0aã)ķ/~\tņãĸ‹Ũ“øœŗtoŌēsÎąŽU?¸ËIᯗwŧ]–š)tL+4ÂDíĄėuÍęËœŊGą‡Ú˙ÔI9Gļ˙R>ôlGGe›‡Ģ´aS÷V”ąĀņč夑[åŧĸb†eŖ›á5ƒđ1G×°vã“odŽå'ÉJãԏĮē…Uiæęe/ôŅaî˜eŎǏ5̤cZ­>K}ŋäXulŧzė*3 ÄíĢĨI C� Ž^š-ūIĩqËg}žŠÁIaÃUÚ°q4XxAöčåˆQ,0)†kĢ›á5 ƒđqG×°vão„Žå'ÉJãÔÄžzÔ Ã0˰ +mļÚ°ŋąĮ›BÕzŃ }Öl×vŦˆŅ„k•ķM›‹ LŠŲÛ̈ˆlš Õ1ûo]͍ ×ī¨îX8{I7[̘ĻÕĖ7ĨÔtmۑÅę˜ũ5uEۍ õĘp­fqjn5GDöŖjĶ­žßo˜æ›—éu‘SŋŖÂvåÍDÃ|Ŋ2\oXņöõŽË_|ãGo˜táZåėĶ›×Ŧ惆i1š–ūZÎ7^x39fžV1M­œczã\IFÁYq,íĨ(M¸Z9ۘôÖ5k×%UŪvå­TãÂ(å4­faâæĸÆŽ•xËåĢšpßŦeˇ¸ú÷ßH4ĖÖ*ĻŠ•ķIo]ŗöšōÚ§ŊDÄÕl7-Ô+}ÕÛxŦǟK|܅ÕęeÅß{ę÷ęEWt]åû“cfkĸÎé9¯!ØKßNYĨœĻV„ë /Ŋ~¤ē˙?~ŪwŲķÅŖĨbģo‚ĻŋļCkiĻiĄ^9MĢYœÚcsžŽŽŅ„ĢázÊí™;ŪâËR•áŠWĖ$Í×*Wœsöœƒ0gęDĶÄjÅ4ĩąČÕĢßáú˜Õo_ą DDæˇu?Ī2{Ü_Vû†´x2eĀŪŽzS¯XqŽĄúXĘKQžŸVĐ8ĀĨsŽ*?5fžV1MkX‘yÅÖ*ęIû­Ü‰žwu/Ŋū~GÜJ 1¯Éã8úĸĸczĢG‡LĶę§æV¸:Fß@—÷`ŊV訴Öŧŋҍ W+ÂŖŒo\îķÄ7e&-ŽŅ„Ģ•ŗcü–ĪÕmīØË w”ôí(ņäڀGSāg†ė§é7WwÖÛyÎ4M­\w­ŗLĄ|ŖVšú27” ,ApÜĖ]mė끛ö�NŊ‹đPxĢö›ŲëŒēp­bšVˇ89ģÄÆųú°ĮĄg;˛X­˜ŊŊĒŗJ\I˛böëåųŨqūÆj—Ąž—ŲŪO°‚ũfޝÉáQ1ë–;ģ;zĀļ 4ūWyūëÆųžĒF×ŧnī§a×tm{0g`ŋ­xbxŊ^¯×{ûömoĀĩđH{ˇ8~Ē*ūŦŗãeÛũģu…Éķ4ŅûĖ÷Ŋ^¯ˇíĢ=†Đyģŋjë\ž­2}–*:ĪŪģœ6sÖķĒéKr>ˇ:ī6WŸy->úy]čŦŨ_yŊ^¯õđ Ē™/Ä'î)ũĘjŋ{ßëm3gÍSÍ\~āw öģwíßÜȉ›Ą‰ŋč̃õđ ĒéķâRO[îyŊ^¯ķķ-†ĐYÛ~׿õļŨ˙ît|čŒMŸßokkķzŊ–wžWÍŲcözŊŪ{—§Ēæŧ°ū*g›×ëŊWõ‚júÚŌ{^¯×{˙ķ×tĄŗWYīŪĩūîôú¸ĸ§O;líÛ÷?M:oũŠ*kķ]gsŨĨôįU3_+ģī{¯j÷œŠ†ÄŧĘošíßU&ĪSÍ|­ĖWÃķĢtĄķÖ.ˇŪŊk˙æęî蚸ĶV¯×ëŊ_™>KšåŌWÍÎģw­_]Ü=C—|ãž×{īĶUšéKr>o°ßŊëüŽĒ8õy՜ÕmŊĒĶģŊmßė‹ ŸqÕŌ|īŪŨ†˛Ŧ%šĐįsžęŊš×Ûæ<ŋJēŧ¸ų~[[gįDž°&õtwå;;§G6äDN5$ŸŽūîŽķnŗåķ} 3g$œšÛˇp?ûΒ5OšĪŌŊpųļéSãO5÷m‹õđ ĒéĪĮ'n)ūŌjoļVžŗ\׹—ŊŪûUÛæLÕÅī+ûîîŊ{Íæķ[âĻΈ?eõzŊŪļ›ĻOŽ^ĩéT•õģæ{m^ī—;uĄ/ũÎëõļŨŋwĪy÷ŽīĮūģ=qĶg$œ˛ļyŊŪģ×LŸ~ŅüŨ]į]kõ™ĩ]Cēí~]NäTCFUGĩēKķÛÛ_í1„Î2ÄmšôŨ}¯×ÛÖ|qÍĖŠŅī4ôQžMRΚ›īŨkŽģ”žÜ0sĒĒŖģî•ĨÎSÍ\’ķyƒķî]ëīö%˜UįĢVYę UtžÅˇŊÖŖŅS5Ņ;ËživŪmļū./af×�ž[?U“ZŪæõzīßX:uÍų{Ū@˜ßaĶVžiúTCôōõīܰ47[ŋē¸-rĒ*î´īĀoû./~úÔčÔŗÕž#%õyÍĖUĨ}‹¯§ĪŠĪēaūŽŲúÕÅM‘S5žŗMŽî1ũMƒ83ôd}įųî1y˙Æúéķ sfmû˛c1sÖ<UÜYį�ŨØŗ’"÷oŦjˆ\˛Ļŗs6ENUEįYՄ^}ãįPęąéuķŪ-MžĨšŗļđKĢķŪ]ûWg×Ī™ĒKŧčôz۞܊ ]RÜą îĮĪ0Ė™×5&īūšfúÚŌû~wœŋą*ĒöĪKūÚ;ā ö^iō,UäkÅ_ZíÍ eī,ׅ>ŋûËû^ŋƒpĀņß|:~úŦ„ÃåÖģwÍ •‡WBŸ?đÍāvMáÚs׈Vü øĄ4RF.´t-<ąãQ¨čGųÚĨīēö¸õhôT]zįh+ß6sFÂų>{¯­|ÛĖŠßÉŨëíHK=ãQčķ9=FaÛŊfkķŊŽ—÷ίRM_[zŋkáŽ6w-Zĩ­ëÃāîŲøĐ›~×ņNŸx´ĮÜUëoöEwlũ^iâ UôéŽ<w˙ķ×tĄũĮ#īũģöīîŪīzų]^\čŦm_vÔPēüRW•ī•HŨRüM›×ې3gĒ.õF÷Zßė‹õ› ãzt×ÛvĪjiž×Ö7F´Ũĩ~ĶÜ]BQ{ī—Ĩ·†‘S5=7ŨՎO×ĒBWuÖöŪĨÄŠĒ™[*ģ–ûjĄs×t÷áŊ‹kB§Žų´ģ°{Í Öģũ|ĸúŲwĮŖŪûÎ7$|Ķ5^Ę7ufVįųåšîŊ×ëmĢNŸĨšŗĶÜš˜&ņj÷æûũ�ģ_ĩ;rĒ!õFĮbm÷ÍÍÎîĻÔåĖéJ3 "§˛ęú”æˇˇŋÚcčúõzŊŪûĨÉ=˙Æčf?ĩD5}U÷°i+ßÖõ‘Ķ|:>T@g–w4ĢĶ{Úļ{ÍVûŊĸ¤ÎčÜ\˙Ÿë°^ŨÖsØøē:šģĢí§^PM_[ÖæíøiÎÎîí­j÷œ~âũĘÔ]šÁëõŪ˙2oSz^õŊâ‘ŸŖig‘¯öB—û†Sۗ[ Ņû 3ērƒõhôŒ¸ÃցēŅ<Ō$^ėŸŋĻéø õׄ^üJũĮŖīōâBgŦīqŪ˙t­ÆˇØũōMĶ;ßēuũôåGO­ÕÅuŒIsÆ<MüE§ßįoŦö2Ôķ’Ÿöx‚ũ./.tÖĻĪģ k.Íxm÷yĢ˙A8ĐøoûŨkšĐ%…]CČ{ßūÕŲ߁áī,÷čxø8đCiČB<šØ÷Š’ ōL""ōđÎÆ…ĮL‹ëķ>>ēDN¤JXŠ9wäÚ->6Ž%žęZ­ ßĩ¨ĪÍĢNĮíÖ`Ĩ^Ûyo#Ŗ‰Õ…\Ŧė^ XĨëq{Ãxn_Ė9Tįpq\Ģ@$´’GįéēĘŦÔČģ a¨•ā˛w˜6´ëÖJ–aHx"r5Ú=! T]7° ŒšāĘūĮ`ŲֆücŲ–&§ģUđU‹B"ĸ&ŗÍ#3)ģîŒÆî.ˆ%"rZšÜ¤ŒŌą]…Dt!ÅõÕZ ˆ\ 8yzW5&ŧ3gŽV!Ui¤DDĄą†į.–¤'ņ//3ęæj5rš*â‘Ís4Z=Á:e÷oT:uđĮV‹‹ŒėĀĢuPëgv-$eC::§Š.N˛7÷ׯےãbus"¤aÚ~o={Äž \°J§î/Ŧ„Ĩoyˆf›Gfę�Ä(õ*ēáhr’NJD$‹PųŊ'Î~9mëM&šø¸QÚYx0WúnîŽFģËÍ D‚ĐJŦāŋƏėmI¨ēĢ Ë“СDáļÍAŌ¤îaÃhujúB "â­õ’¯YĐŨNŲ\}¨§äk+Å/čUÒķãÜũĩV—›ã"ĄÕC  Ō�ë‡L¯éĒ{+! DŒ­žĄ58"FĶu¸1úHuđg iÅ7L9ꭞĒķ5ģ ã7Ŋ[׃ŋŖIE4¤3C„aNHimŋFÎ6U7RÄŪĨQŽO[\¤•9ÍõNvNŦŠh(ĪĄČ"t]ÃĒ5 ĒŧmįIú¨&ô0ØC‰ˇÚn“biDwŲl„NF'šė<ŠĩŅtČl–GQCuŖTģi‰ŦūŊšz>%žĩÕ6đŠe:ŲÎ ¸ãT~ÆĒ_ƒ8/ųiī@'XūŠí6Éē›Ÿw*žˆČâg8ū#Œ‘’­'SÖš^1EĪÕéÔRED˙įÎa;Ëué˙ øĄ4‘MėxÄ(jUįÎPiæęeÜÂôÜ„Eīč’-OŅŊ÷æåë\Ü*ĻöĒ™Šëûé$¸yĸPļûɆeCDK‡tŋéŧœļz¯]õęžC9jIø¯o\ļOŧôž!a¨ĮJ]į=…°=Ž6$d€Âmī¯NÎkĖŪ{tâ9–!{aĘĘ""Z&¸ī:ŧģ•čÛ=˙ŦŪ#Ž?ĮŠT;?)Q~xąōäæBˇ'äš(Ķļ=qa ģāpÉUũ‡§K>Ë}õŨV QÄ$ėÉÜļĀ˙S|+O,ÛŖ‹‰ f„ūoč…aú뜞Š>‘|TxųÆ‰ĖŗnO°dæ’ôœ=IĒŪgGīģ€÷ˇ—…Vžˆa{v5Ë2ÁävķDR""&¤ŸũЉ¯ĪŨtĐĒÚ]–ÕuÆęŪJZYĘŧ˜ĩĢ@§˛Á‚å]ĶĻÚGÔí‘ŊŨŗ?ŦOâaÄ0D‚ī-ŪCŽc§}(^EÆņDâNį+2“6ÕĘwíËŌÉ%ÁŒPģcq–ßô! °~ Đ0ĄU OÅψ^73Ë\n"Y¯%yb؀fŋGSĮŋ{f`ôŅ´ˇÚ&,‘Ô×qĘ Zi„#Ô^SΧD7Ô41†MCŊ+<DÖs0ÁDŧ I2��IDATŌ„Nƒ>”Zyžˆíy2#† !Ā DR]ŦJ¸Xī (Ou#3w¯R.ÕČOÔ6ņaæz§t΂0"ŗŸįgŦúøyÉO{<Á īûcˇī;ūá@ã_j<Z*š|ēøæŲˇ>Ų×,QFoČÚĩVßįmĪrú?~(Md;õƆjeTaģ͓NJÄÆŧŧˆMŋZæZ"ŊYĮū˛ ǟąĘH"ē΍<?āĮļŗōššuNîģÛu ËVAđķķx˜ā^bō­­ũķļĪ.6QėÉkŖ:Î?ÎŽ“Â2Äķžž+ą’ĸ9ŲŋÍYĐķĐfÖ×4Vĩ$ãč’ 8[]Ų‡šų)$ĢÉŌI#Lģ LD‚ĢĄúæ{ŪMÛ()û|MXß to)„%žīŲł‡ˆaú ;CÁ„Eŋv8ú5"Ū^ŊøĐ‘œ4AVq\ŧģĩī„�ÎŽ}*ÂR¯ŽæyÁC!’G_"#îúތ B\ÁŠ„I ūjŲ÷SQ^’ŽéŦÕŖĢ5<ŊÍ0D‚ĐŖ-ŧģķĸÃ˛Á¤L¸øn‚čÜĖ„H{ˇ“¯ētË­XSōN|ĮđāIčg$ö2ø86„ĄØC%;5ĸĒ3RIß%{wŖ˙‚ũMCŦíœX•pļÆÁ)jíĒč–äzôDm/Šļ0si†zâņô<Ģ‚‡HÂ0ƒhÂāOƒ!,KÄņ' îV ö…Ų\Ŋėˆų[§¯ã”´ 1‘ĖŪjĮ×4ą†mjōŋã܏Uŋ>RüĩwĀ,Ã˛ÁŊšÜĩ]ŋƒp ņĪČõköé×ņNsíŲ‡ö­Ëb˙P¸\\úč}B~(MdOԓkÄ7Y\"ëü@b +kéšĶ%ĩRcÂÜūF‰\ėi˛Ø:_ õõÆ#ÁŨJÁ=>lŸŨhLõõ™ĢPÉŠÕnëšMãëĘû˙páXi×ßfBũÕĘī;7§ÔĒ‚Ũ–úŽ/Éã+Ū6­Č,įIŽUĘčļƒ—……):~¤! +‘2$8Ë+|Ī•#U-H9°Eė˛98ÁUWQĶņĀ#‹ˆ]ŋ/yĻĮQ?Ā#íUčÔ!ž†úîžl5VOH„nxHæl5喎‡3Ø0ŨĒwļ. q÷čˇÎúÛwÁ C<ß}Rĩ7ŪîŨՏŪwJŊVÔÕÄkļ‘DĨyÔĩÁZžŖúš-§rôL‚‡ˆ•Iēr{ųĩÚG_p–ŪfBĩrâlM]Īŧđ]íbÕ:š\ŧTŅ5rä,ÃJ{¤ŖŽZ ŧā!‰Ŧë÷βĪēßíĪ`Ø`)t!­vuų0 ÄČû^&ōucMS×CHuų‰Æ×ēôåįh H˙ ”΍Q8Íĩ×+›äÚ9r"RDFPcEim ŒÕēûį07u?ĸjmt,TÍĸ ƒ? ˛ĒPr|ŨĐũ ×`q‘bŽš%"RĮĖ‘ÚjĘ+kíĒČ–ˆŅčUîēĘ/*,Ė\ƒ†ČīŽķ7V0Čķ’ßöt‚eÕ:9jģ›ėŧļųĨÄ\ŗā¯-ŽŪ^}ŗŽcđąr}ܞŦĨ O“ĨĪ­û ¸ĀĨ‰lbĮ#ŪÖXgŽ÷ũT•Ë^ˇˇBPŽJ‹ėÚE‰ aMŸļ(W&öž?÷a ņ Bžŋ˛˙X•ãėõŪ<V×˙‘\§‘xž>[Ōčä8{õš´ˇ\jCš¸GüYĪ0!äš]Qmŗ;š@OõŌČeē`KáŪĖ.Îé¨*Č8í`rFĨSģˋ¯Y9ŽŗŨ:˛ņ¯™IžÛf'tIjœ¤éÃÍoŨl°Ų*Îm>đ‰Ái×oĐ Wve^0;œgˇÜĖ]÷bĖęsV"r]ËŨ”ļšāVƒŨÅ9]֊“g-¤2(ĨBĶŲ]é)[ĪUŲ\į˛[nž.m ÖÔ}ŽQ{ŲČ ĻPŽdovYŖ“㜖kŲģ>㔠úš˜Į°L09jĒ,{ĀũÄUŋģy]znYŖŨÉqN[UŅĮU­ĪEô™˙öģīdzUHkÍgågūāHekWWŧī:ēzGū-Ģ“ãėõvŧAsļ¤jü­DÄWīM;z[žmIˆÛétuüp1JŊ*ØqíÃr;Į9¯ŋ•~1$REŧÕæāĸ C|CMŊÕîũ­<ˆŪöCą(QIõ'ŗ‹ęíN—ĩú܎ĸĻŽõÃL›b™ßgoũĀ7 ŦĮR~e4í¯ˆˆ–%ÎRÛ`w8yi„ę9˛\>]íâ8G]IææJŲqŽFį@÷Ũø`BÕūDĶēsũ}oMĀÆ‰ÜĒteė(m´sœĶ^áͤ˜_Ĩ_č›z˜Č ĻPŽ43ģ¤ŪjˇU•df51ē“ŽŸŖÉ/˙Ŗ+L?Gjûø´™‰0(ˆˆM”Ö]^ü§Šž;ôëRŒĩ ķ#ŗƒã\Ö˛CīU“*1A=˜& á4–üjS{dך*ģ‹ãUYGęŲØ´ĨUŅs™oO7v¤@b#t {éš:AĢgˆüī8cĩwˇt^ōÛŪO°aĻWŖ˜¯sˇ+ˇØŦ–›GvēáVDŠmpüˇÖfĻŧšyÅėpúV)¸é™ÕįOž!BuõĐđJÚHß>RÅöyrmúŦč¸ĩ>ĩöēß~jI×c5ũģ_W˜ú‚aúTÕôy -_틝—ĶõäÚô-Õ=–Ŋ÷eŪúČYšĐŠēČåģ?ĩļŨ¯;7K:kÛīî÷Y¸.gÎTC–īNū{•Y/čB§jfŽ-Ŋ×įÉĩäĢŨun>:c[Įŗ÷ĒßY7s†*tVt➲īĘwΜw¸û酝Qž˙ŧ&tĒfæķkŪ)wļŨĢĖxA:#:ĢÎëõļŨ-?ėkāŦčøž‡ēŊ^¯×ÛfũtĪúčy_ī%î,ūĒķ‘Šæ’—fÎP…NÕĖ|>!=¯Ú÷ Ō}kiÖǏ93TĄSUĶįÅõXELÔ^¯×{īĢĶÛâŸ×uÖa€ĩŧŪûuGãįiB§ęâĪÚũvŽč™ę/ķ6ų Ą‹\˛ūöū÷ŗīŧŪ{•īŦŠž9C3}Vtܖâ¯ĒDvuĩ¨-~÷˛ˇínųŅT_ŋÍ0D¯Ęémå›ē…ķézĀįĢ=†ĐŪĪ`Ē|Ī´5—Ļ/ņĖ¸äŧę{^᧝EOŸę{ÜÚ÷oÕôyģŋlëõ¤Ō€Ŋũ՞^ĪWĻÎčzčŊįīö­‰žĨ á뎲ôYŨΏĩ5W~-~Î,ߐKHÍĢî|T°íģŗëįĖP…Έ{§ÁëŊo9ķZœ¯7âļsŋíģŗkæĖPÍ\uéî�\ <ĀîžV:/§Ÿ‡™ÅÃĻOWß;ŋJēĒ´sŨ˙æâîÄ|ŖÅŊ*į|ī3F÷Š_žŪ7O7}Ējæŧ„ôŗæ{â'„~ŽĻĀĪ }´™3æŠBgŦīz�Ēųt|čÔĪöÉĩĢkB§Ž?ßP–ĩ<zϝžû*{<k9PúôˇR˙ö{ŊmÍĨYk}{V3§÷Az˙ķ×4ĄSuUŋk89Uô<šßįoŦŠ+>´ķ’ßSĮĀ'Ø6kiÖ*ßø×E¯ĘųŧšĢÅļeĀņĪ|fKBä,M¨o•ĩ9}>éYÕ@ž\ ü ėŒ†Qxrí¯×KDwîܙ2eJ€‰jP nDŠĩ`üÕĮęSy˙õ,đœĀH;/ åŖ68_­ø|ũđÜîđ8Ä5ãJ’õûéČ‹—ų˛:ĀÄÅ_KYįŪķé880`Ŧ\héZxbOŽų!p.ĢųōæM'ú­[üĖ,đ5;/0Ŧ;Vnq8Žē’Ė#Ղ>)nœ‚š+cõ‹Ķ߯ļu\4>ņuHTÂd#x*9K?ãô‘ãāĀ€§Â“õäZ7Ą<Į¸Ĩ’Q6Š÷wo,kČ;ŗ?w˙‡ŲĢ?t{‚CdĘÅû’ÆÃŖ‰ŌåīōûžŨš”×ę –„FÄė/Éčįģ �žō”ⲹŽ�<=žôÉ5���x˛`r ���`´!���ˆ ���ˆ ���ˆLÜx$XK^™­U„›Ū÷ûō|YĒbZōŽˆ¨ęMŊbņąG~•m z cÆyÎ4M›V1č˙ë„PņērÚ#FNsĒuoÕû^đæcI ĩŠiڔRŒ�€'ĘÄ}°ŋūlūī9mÎÕĖȰA˙Ŋ‡ĢyõČ!!bĸ} ‘PöēŽÄPy>_đØøĒÂsfŠ~æĘН��O” „V^ 0CdDØ}G‘\ˇ|,bŲc˛Zš2Œu-ž Ī{HŊ@Ĩ˜h!��abNŽ™3u˙”Qá!ˁXÅ4ã; Ίci/ÅhÂՊpŊaÅöĖœīāę ļ›ę•ĶÔĘŲ1ĻĮǜD$”ŋĄWŧôAįŦ‹PõĻ^1-&×Ōš’íXĖ´¨lsŸ9ģįĒĨŧĨ ×*į›Ō ģūGĄÎŠƒI‹õĘp­nņëīW;ŽŦS+Ö]ãŠ.é<gšĻßQV“ģ"JžzU^ŽYũö[į“oá ە7 ķõĘpŊaÅÛםDÄ]X­^VüŊ§~¯~š:Ĩ´oėCĄ,UžzÅv+wĩQ7[Ģ 2žqŲÚgFK0ŋ­›Ļß\ŨķÆÜ…j͡„;œˆ¨ü m>'â¯%MS'•rDÄû6mū ižVšâ˙oīū‚šĘî8€˙úrĪSîSîËæ>ŦÉĖN’aL2 ã’d*‘Q#âV n+ĨVDAD,˙TwWĢ#⮑V;ÖáÛ ą$Û’—d÷!ņåæéæéäeû�‚!@Ģ­Đßg˜Qn~÷ÜsOœš_Ī=ÉíËüÄÃDčŲ§‡ŒšZyŽÁRusjé}Ŋėģ-šĩ2×boœ[qŌ ÷į'­úĩ<ũŒhp°šĘĻĪÕĘ?ĐjvØjîēWūcĸŖ59æ‹�wƒöĩŨ7×BhK؜ņČĐęūļÃĀęĖ€˙ŸÎS §/ØjûCŊãųÔė×='dĄkU5Ÿŗ4A}•å=?(ęîšžšxP¯ŽöũÕ%%ųģ4LäĨoá:™Š4îņĮæüŪ¨$o—6­-BDî7;eįžœ|?ëĒ“xnœš=Ÿ¨‚7+kG•Žį.G[a¤§îvB2<å$K%!�Ôw÷ą`ģîüKĢ™…¨ŖĄōF€¯îqžŸūËõrōâ\U›‡Ļz“˜ēq+hŊ1ņˇŲĐ_¯›>Û:(�wøÁxģ@Ķ8ūwß]ÛĘ)lcČ0 Üî˜Ôļ9Ŋ˙đ{ŋ,‰ļŗŊ‘-CŲGōÄÔĶÉĨâ‹I‹ídÕĪō�` ųĸ'`ęt8;÷d牕ôõü~D~ŧûĪÎ‘ĪŽņÁû5'įSģ^iŋęeĩ:Ÿģ>;ÎēÛė'—Ŧø@Įã¸õĒãë'MyâčâQo{UͰ˜wåsú…ŗˇi{´§ŽÆKīÖŪîŋ4k�Œ­ŗ÷õÚņæBm)›3!,!�„°,!bÃ=“qemwĶ^5Īq ũá?4‘ĐÃ^īĒ ˆ“_8~Ų[ģlĮņÚŊW.—ÉbC'DV_¨ƒĀŦŸ�D_úåQ›2ėöŠ��ĸĮCĄîõt“ØVŪTĻf€(Ŧ%:xåķĮ�ĀįHv^ŧ\Ēãe Ci×åBĪÜŖė•’‚ŧŦËŽWĢd,�gŊęúæQ—]¯æeŧĘøIõNiÜ;ž<ꏝ3É�pÆÆ÷’ū—a� ’ĨQ[yüĩĮP]ŅXŦ �ĀjËČ!ė Ŋ–mTåÛét˙ØâdŠĮų"&/)7,žę{4€8gkũĩIĨVpžGUĮģk u šÚtüJŨöd`h8 @gžp„ØŌĢŨ•F/SĘēÛöī‡Š‰@y{ĮEĢFĄPåÛkËSgD4§8?;_¤•ķŧ\g­-×&į&ŧ+:JX `@B2 &BĄÍm“ÆŖ"ŗÁ¤LĢYZ D”Ä#ĄĖ÷b� ˜K2:Ŋri‹J¯f’sūpy&9õ¸#� zgæäEV=īŸņQ�šöƒÎ’—a­‰t›:5ƒ@XÂ�Ĩ@ G^\¯Ní *4g^+ĩvĨ L]† Ëî;5ŋ´w4šMÕP’tYZQhˇĨŠYB€ŌĩbȚc(Ķ.­ąa IJ_Ÿúá­GĖ$đÄ� Ū‘ Q{¨L Y|m2*ÛôŒÚ¸=Õ3^Ĩ”B$z} FgŅ, šĄ@Í,ÄÖųf šTŗVš:#B’?<i­°îļčs šÜ}í^HŌä†?‡BhĶÚ´Kŗ—Ŗ €°Ė˛M,KˆĮE€ĖËfń,Ë.ûo?aX”&�Tfƒôļ˙eäáé�§=­Įudt:f2ã•åúL—ęå3KMˆ€H–õA*“dėĐڕ,IũN=-ör'Ų×ÔxW/įX†úoŲjgŌēŗŦųˆé˛Žáną…åö¨s4\yœ÷‰šsV@ö_‘0Y^eXvŲ°–  $A!9^Ģ“§WËbq€ųāšĘ¤O´ŋæãļ°ęؕÎVĩTBH|øä+ëč%BĄ-cKÄ#"aD1šl“(Ō$H¤Ģ~¤ˆ•° Š"]ēFŌ¤H �¨-zÖáõ‰šŲ�Éo“‘™Tņ/ü‘(xŖŧ>Xc Ée)MŒeÎë¯�ī€ë•ÔÖÛe×Ī÷žB†‰œų7Æp•†Ė•{dû‡‚GLÎjj-šĪVY|%šĖ°qUI‘.oV VB@˛ĢĶqN“Ö=ÂIŗ7œMäĩßĒ_č9$–7Bč˙ÁÖ¸šĻ4h™¸ßģėŖOßÍAĒŌŦšdäzĩ$éķ†RhĐ=—”čôr��ma>ųnÚ9é”&-`uFyØ=3æqÆBõ:Æ)dRˆŠđžņdž›´ūʅ`Áʤ‹—lqlpf=“0Ym| WŖ:rTvÜyâ†ĸŠ‚…t•uĀ !H¤NFúžÆđl ĩ¯ Åa›V ×ë$‰p ų⏔ ĪŽ‘th<Œ„KĨÂāĐH([=BĄ­gkÄ#ޏz¯4t˙ėõÉš¨ „Ŋ_5ū~ōNUkVŨƒ-8aÛ&8ښ]¨ DũƒÍC‚˛ė„‰��<“Vœz4VĖņ#¯Íãü}ũlžQšj›™čl;e‰Ņk“sŅųŖ|CW™ŧX%ĨAÅDī…!nŠ{")P8ŒˆkL"–0qOų#aaEéÆĮpU˛âJŊđôūŲSnXĖ"Y\­U2ąÉ'ŗ"�ШûÚŊÕ×ÔgäŊĶėœoļŋšį;F_ZĐ‚ÕĘhoÃYg ,Ұ÷ĢOí–ũu_­ēm¯×H“/:QAO÷Õ´ÄÔF Ä>á?žĸC!´IlxŦŠÕy§Œ›nŗī6ö7< Ž}Ųs8ÛŧŅ5õöÖm›ģ^mųĐlŠēÖÔ÷>¨W/\ÍŲ|‹2‹s†ŧ…6TzŧŠŅíģ ģËB įīļÂę};wŲZ&e ­å2ČxĢfũ•�˛Ã­û˜™ßî1^`u˙Ąū¨E:wÕfīÍöe�$ŋâˆfδŸu­œ›ÚøފŗėÉg@u¨LˇėĐYœŗĩ^)•ŒU™”š&ËéoT õų ĀzÂĨ$ų įĩĶmļŸ›Mß ĢŽõŪ*ã�ˆēĻ×ҤŒŪĢļ~h6íoxËģ¸Ž3"ĻĶ]՚hO…éÃ}•=Ą]—¯^Ŧ.Q‰C'v_˜Zk};BĄ­ág?ũô�üøãīŋ˙ū:÷ŲPņúŊĨf˙ר(ˆ„K}"ŨÛŧŖrĘęp7Ŋ>+ŗūĘwāŦ0vŽį÷Šņû€BŊio/´¤ŠˇÄŌėw™˙Ļõ`?{¨õb…ž‡¸īQį3qÛ'ļL‰gũ•ī.*DcQo_sK@QįÄl„Bh“Âxô–iëw šįVÍĶx‚‘ČTy§>;˙kÕVųî \;X9 žg°ßčĒ‘¯]ŽBŊ“đæB!„6“˙ÂÍĩ-˛4!„BčMÁx„B!”ãB!„PŒG!„Bi0!„BĨÁx„B!”ãB!„PŒG!„Bi0!„BĨ؜ņ(xĶōZūڏ˛jô->R‡7Ø>NļWY59jyŽÉzōO|<B!ôn؜Ī\Ss|[F)�„n<Žpt[Y� Ŧ”}{õ?nw(u6͛9 \ûՙ1ÕųģĪw*čwˇOŸŠi‘ģ˙XøûBĄõ؜ŗGĀrœŒįe</e NĘķ2ž— ÷lĘ_öE‹čė%}îo†ÅČįŋĐVŪl¯˛Yv›ô?¯h*ĸ“íUV}ŽV™ką}Ú?ˇ0ãūmŽļŌEĶ8{I_5tZrŨëŋ ĪũÍpj˛GœŦÉ5Ĩž“æw}Ī>­°îļčwX+īzJhd¸Ĩ˜ĢUæŒ_S��!š#W:ËōyŽSžĢÔЀ7ü6‡ !„Bë´IãQfęŌ>84ļ˜|ŽÔPjf éy:ŖítN<Ÿv5Hž5ļ ‹�ŧvė‚GŪčü‡?ôמ}ņ;•n�@ž¯Žö€’¤5m¸älÚÎ('žZ]RRDf&ÃĪė ‡Ũy@K€0‰‰~ŋ­Įõ|Âû¸Œö4´ĪR�ęé¨nŽt?÷‡žīֆškoú(�_xņõæÅÉĸh$29˙_%„BeˇĨâ(ö–Ģ"Ž��FÜ`>T0Ÿ@Xã‘b�€ŗ”šá帟‚ŋ˙™PpĒÁČ�VõIŨN21äĄ� +Ē9^ŦXũ(DĀ*õ8'��:åzÉZJķįĶ”ŧ䨁�P”Њž‰Й'ŽdqÃq�ŦŽî¸Y˜ĻĩGƒ}Íø¨Ž„{ŗŖBĄËæ\{´*YąMsíŅĐ\MŊÚ?:E Žæc ÃËĨ %ä=ŽĨsq*Æ_‰ īŲ†ŗ /ĐDB�Ö1‡Ŗ;´—ß?4&”fgFÜŌâ/5ķÛNļ¸7ËɈ ÄAˆEņš*Ãđâž4Aķc´ ŗSâėÍĘĶŖüåG‹]E!„Đ˙Ø‹GĀYJō;îkŠk,Wķ3DŽäŸ@ēˇëëĶēÔ+„°ë\­(9 īpÅ>’zd…UϚÔâ%fÛáŊ'dËŗĐ­¨ëŒŊ#VÔé¸h™#„Bč]ņ/ž}AĒļËũ����IENDŽB`‚����������������������������������������������glewlwyd-2.6.1/docs/screenshots/mod-client-ldap.png�������������������������������������������������0000664�0000000�0000000�00000211143�14156463140�0022333�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR����0���š„R���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨwtTÕŪÆņīˤgB:$—’B ŠXéM°#Š ĸĸˆbÁŠõÕkoX¸ (Š"bP¯R¤IoIhzô6Š3ķū $!$!įųŦu×bÎŲ{Ÿß>¸ĪŨ§{ė1;""""âT\�JJJČËËŖ´´›ÍÖØ5‰ˆˆˆH=2¸ēēâãホ›�.%%%X,† FˇnŨđöönä2EDDD¤>°fÍ-Z„Á`ĀÕÕc^^¤oßž €""""˙@ŪŪŪôíۗ’——€ąŦŦŒ=z4ri""""rĻuīŪŌŌR�ŒVĢU+€""""NĀĮĮĢÕ €ą‘k‘F ("""â„EDDDœB ˆˆˆˆRqB """"NH!PDDDÄ )Šˆˆˆ8!ãąO‡ˆˆˆˆČ?_^^ŲŲŲŊŧŧģi ^^^˜Íf]qF """"NH!PDDDÄ )Šˆˆˆ8Ąz ˇOšL›¨Xæ}<ŋÆ}}|:—ö\į6gŗÚÔߊ[¯ŋųöĒHDDDÎvYYYüđÃØlļ“ö­^Ŋš”””:Ŗ^B`nn.Kü‰vŅQ,XøE} )"""â”ŦV+|đ+WŽdūüų‚āŠ+øöÛoųøãIOO¯Ķqę%~ųõ7xzzōĐSYŋa#ûūøŖ>†q:&“‰Aƒáęęʎ;˜7oVĢ•_~ų…Å‹Сo_‚‚‚ętœz  >˙‚—_FΤ„…†˛đ‹/OjsøđƎ›@ģöé–t/ŋúz­Úœ(=#ƒ{îŊŸŊΧ]ûŽ\xņåĖú`Nĩ}ēöčÍĖYđĀ´GčÔ-‰„ÎŨyúŲ˙#=#ƒ›o™HĮ.=čÕįB>û|ĄŖOII Ī<÷<=ĪëKTl<ŊĪīĮ /žLYYŲiÕ—Đ™wߛYaÛũ>Ėāa#ĢŦˇŦŦŒ—_}~—öwĖqîGķNynDDDäÜÔĻMŽģî:ÜÜÜHIIáõ×_᧟~ā’K.áŧķÎĢķ1ęwīŪÃĻÍ[1|(ƒaCŗpŅ—Øíö íîšī~RRR™ųîÛ|4wYYY|÷ÒĶnsĸŠ<Äīë7đęK˙æģ¯žā– ãxōéįXŧdi•}\\]xīũ˙pQŋ ų}õJĻŪ;™÷fÎbėMãšuÂÍlXû#†åáGŸ ''€‡}‚ųŸ}΃÷ßĮ’īŋfĘ=w1{Î\žũŋęTM<ķÜ ŧķŪL&Ū2īŋYÄM7ŽaúSĪōɧ ę<ˆœZļlÉõ×_Ņh$33€‹/ž˜^ŊzÕËøFƒÁP§>]°Ö­[‘Ø1€Dz˙ÖŽûŨŅæĐáÃŦümˇL¸™žI=hÁcLÃĮė}Zm*ķđC0gÖûtëڅ֭[1jäbÚEŗlųĘjûÅÅÆĐ¯ī  �@§ÄŽtJėxt[Š‹‹ŲŗwYYŲ|ūÅ"î˜x+\NË-:x7\ķ>ų”’ŌŌZ×*yųųĖũhãĮŨČđaChÕ˛%×\u%ÇæíīÖil9ģmŲ˛ĨÂ=)))×iLƒÁ€Á`ŦÛJ Õjå‹/ŋdؐÁ”••QVVFXh(;%ōųÂEŽvģví !žC…âãOĢMeŧŊŧųĪė9ô4”îŊúĐ-é<’SRÉ>ē‚W•6mZ;ūėc6ŅĻc›ųčļŧŧ<vė܉ÕjuŨc:´Ŗ°°}ûū¨uũ§˛cĮNJKK9¯wÅÔßŊ{7ūøķO ,u_DDDÎN_ũ5ĢW¯Æh4Ō¯_?ŧŊŊųã?˜={6EEEußĨ.—-_Á‘#iŧøōĢŧøōĢö%'§đØ#áááAAA�Ú˙Ũ⚴9Qii)cnGY™•GĻ=HDDk\L.Œŋuâ)kwss;i›ģģûIÛėv;ųųų�˜OXÕķöövÔ^›úk"īčą¯žîŽ_ŗĩŊܞ–ž†ˇwË:CDDDÎvģ/ŋü’õë×c2™9r$ąąąÄÄÄ0{öl8ĀĖ™3šá†ę”3ę?ûü :wJäᇨ°Ŋ¤¤„̝ģÅKdđĀxyyåĢjĮËÍËuüš&mN´qĶfv&§đņ‡Đ­kĮöŒĖLÂÃÃk7ŠJøøø�Ÿ_Paûąß>>fGP<Uũ•]~¯.Í[Ĩ|é…įˆŽŠ:ihķæ§*_DDDÎ!6› ‹Å‚ÉdbôčŅDGGĖØąc™5kFŖąŌLq:jŊđá‡î'žCû“ö÷ę™Äį 1xā�Ú´.ŋôē}ĮN:wJĘWņV­^‹ŋŸ@Úœ¨¸¸�˙ŋ÷¯ß°‘ũûߥCĨ}j#Ļ];L&ë~__á’đúņ1›iÕ˛%žž5Ēßlö&÷„ ¸39šŌ•I€˜˜v¸šē’ž‘I˙ˆŋ/Wgdfb4Ģė'"""į&“ÉĨQŖ8|ø0ĄĄĄörĶM7áááqŌÕĮĶUëøå×ßPVVÆĨ—\\éūũ/ãūæČ‘4ÂÂBIė˜Ā[oŋCĢ–- ā?ŗįāæęęh_“6'Љ‰ÆÍ͍YĖeŌíINIá˙^x‘ŪŊz˛gī^Ō32 Ŧíüü|šbäpۚQ^[ll ĢW¯eÎ܏?îF\\\j\‡¸8/ų‘oƒˇŲ›÷Ū˙YŲŲ„4mZéą}ĖfŽŧr/ŋúūū$ÄwāĀÁƒ<ņÔ34oÂûīčË""""˙4&“é¤�xŒ_‹c§ĢÖ†,X¸ˆn]ģT˛.š¨FŖ‘E_~Ā+/ž@›6­šyÂmŒ{3ĄÍ›3lč`löŋŸxŠI›ãđüŗOķë˛åœßī^ķmžîn;†ũ˙ÛĪ5׍­íôNōØ#Ķ9|?öôģ”—_{Ûoģ…Iwü}˙aMęčÁŠøú6Ą÷ũ¸āÂK(--eİĄØlöĘ Ā´ĻríÕWņė˙Ŋ@ŋKûsĪŊ÷Ķĩs'^~ņ…*ûˆˆˆˆTĮ0nÜ8ûģīęU#""""Π}ĮÎx›}ęį‹!""""rnQqB """"NH!PDDDÄ )Šˆˆˆ8!…@'¤("""â„EDDDœB ˆˆˆˆRqB """"NH!PDDDÄ )Šˆˆˆ8!…@'¤("""â„EDDDœŅfˇ7v """"ŌŒ›7mjėDDDD¤;v먨5ˆˆˆˆHs8’‘ŨØuˆˆˆˆHrhčרuˆˆˆˆHŌĶÁ""""NH!PDDDÄ )Šˆˆˆ8!…@'¤("""â„EDDDœB ˆˆˆˆRqB.]€ˆˆˆˆ4œ¤ŽhŪ<T+""""ÎH!PDDDÄ ÕérđÁÃé¤gå`ĩÚęĢžķûīëØ˛=›íÜĢ]¤ŽŒF#bŖwũU]Šˆˆ4’Z‡Ā‡3ČĘÎ%8Đ“ņÜZPœķņöíû ]‰Hã°ŲllÚ˛ƒ™sæsãuŖģiĩNoYŲøsā˙ũŠ�(b€MÛļ7v""ŌHjāŦVÛ9�-–Bė6{c—!rV°Ųl”””4v""ŌŒvģs"//ĪÆ.AäŦâææÖØ%ˆˆH˛ÛíØí6=,"""âŒEDDDœB ˆˆˆˆRqB """"NH!PDDDÄ Õéŗq5ņâ+¯ŗuÛ6�lGßĪg4–ŋŠyЀū 2čL—Pg˙~roΜKęž}•î3™LØívŠKJHĪČbã–müôëʓ>IwÅĐtí”Ā‹¯ŋËĄ#iUސ_`aũĻ­|õŨĘŦ֓ŽĀÃ÷ŪɤûĢ—9Ö÷x•yüÉĖũäķJĪãŠjǝúē$ÆŗyÛÎ7^MæŪ"""Įœņ8yŌíŽ?ŋōú[´hΰÁgđ;oŧ;›Ô=ûpwwŖMĢ xmZĩäY:Ú¸ē¸ÍwKūKĪîųüĢīĢ 8(€ņcŽĻ°¨ˆī—ūˇfröĘČĖbړĪ×yœA—]DōŽ=zA˛ˆˆ8Ŋ3ĢķÄSĪŌ3Š]x�%%ĨÜy÷ŊŒ{=īĪū€—_ÆÎ䲲s蜘ĀđĄƒHIMåãų (°X0Œ\sÕ(ÚĮÅ6ŪDŽ*..aGō.žÃƒ÷ÜNLT[v¤ė SĮ¤¤îfõē <0y"_~[ų ß1ié™üēr Ũ:wŦ6öNęJŋ>ŊqwseåÚõ|ũũR�ÚĮD3đ˛~xzxPVfeҎ‹Ųŧm�1Ņm>đ2ŧŊŧ8’žÁĮ ž<Š–ËúO\L¯Î˜Åŋš3|ĐexyzbŗŲųėËoؙ˛›@?ĻŪu_}ŋ”˜čH‚ƒؙ˛›_~ @Īî]¸äÂ>”––˛aķ6Ē{-ųŠj đ¯°JÖĻU‹ĶŽéæ1WāīĮŨˇŽãŗ/ŋaûÎÔ 5ëûÍ⟈Ž¤yŗĻ|ũÏ4kĖŋÂB đ÷cîüĪŲûĮ˙�ˆnۆ!.ÁŨ͝2k?ū˛‚5ŋoŦvîÍCšrߤ[¸ûÁ'ĮūĐūķá|rķō+ÔSÕEDDęƒK~~ūŠ[!Ŋ{%ąlŲJGÜŧe+üë_áá濯”ģĀÂũ?J\l,aĄÍyíÜ8özâŲŋ?O>ķĪ=ũžžMm.ĮËĘÎagĘnĸÚļq„Ā^Ũģ°đëīą˛{īġeũĻ-Վc0Nē¤|âū�??î%ũũ¸˙î‰ėHNåĪũ{í(f˜KĘîŊÄĮÅ0æĒÜûČĶxyzpãĩŖyãŨØ÷į˙¸ wÆ^;ŠwgĪsŒÛĩS;vāĨˇŪĮÕŅ›Į\͇ķ˛uG2ĄÍB¸{â8žxîeŦ6îØąķÎŦņđpįé‡īcŲoĢ)--ãŠĄxū•ˇ9xč0IŨ:ã_ÅߏŲÛë”5ĪËĶŗV5ũįÃųŧôô#ŧôÖ{äåüßũąžۚ9‡.‰ņ\7z8¯ŋ;›/ŋ[…}zrqß>ŧ3ëCĖŪ^Ü<æjŪ™ũ)ģöĀũwŨÆūQXTTãšWĨē9æå8Úuī’ČĩŖ†UčģzŨæÎ_xZĮį‘ŸŸONNF__ßF+ĸ{ˇŽė?xũ�°fŨ:’ztsėīŨŗ�ŪŪ^´‹Šbgr2›ˇnÅl6“˜@xx8QQmŲ°qSÃO …EExyz�åĢ?^žžŽ¤ßÖŽ§W÷ÎÕö āŧžŨظe{•m ?/˙ €ŒŦlRvīĨm떔––2múķ¤ėŪ ĀŽ”]¸ššáÛćčČŌŌ3Ø÷gy-ŋŽ\Ã+oĪtŒҚū_Čī}€ÅRH\ģ( ,lŨ‘ ĀÁC‡Ųŗ÷O:ÄÅ8jXŋq+�EEÅdįäáīįGdDk:ÂÁC‡Xĩv}•öT5¨ļ5ՄÁ``ķö�¤ĨgPVVFęŅķ˜–žŸ¯Īß5gd˛kĪŅ}™ėHŲMlģČ͚{mįxˉOPDDNÅ×חĀĀĀÆŊėíåEbĮx–¯XŰ!Ų´y+Ŗ¯AYiPžrŒ§§ ^Ō32˜2õAĮžâŌR"#"ŧūę°kī�ôęŅ•€�?ūī‰ŋkvws#(ПôŒ,Įļ[nŧĢ͆yųųŦYŋ‰˙ yUÉ?nU¨ĀbÁËË €î]:Ōšc Į>m00{{a),rôąŲlX,…xyzb0¸ūĒ‘S`)ĀË˃€�?`˛ŖĢĢ+{öũáø]T\ü÷xvFŖo/O ÛívģcĖUWSej[SMģ_ĐnˇS\ü÷Ŋƒ6ģŖĄüzŗ™ü|K…~ fŗ7VĢĩÆs¯JMæxĖęu�ˆlĶJPDDjŦQC @¯¤Ėž3V-[ĐēUK8|ø�yyų4iâãøsP` ū~~„‡…ōč´ŗėj5 ¤uËąčÛ%¸ē¸Đ51žĮŸ}‰œÜ<G›Ą.Ĩgˇ.|ųŨĮļĒž@ŽŽ§§–ŖÃėíEZząŅ‘\tÁy<˙ęÛääæáááÎķO<@n^>foGŖŅHHpĨeåÁû•ˇßgč€K9¤?ķ>[DvN.˙:Ė ¯Í8éØ~Õ\â´áéáQá8fo¯J۞ĒĻÕļĻú”›—WĄf�ŗˇ7‡¤U;w›Ũ†ÁP1zx¸Ÿ4~usŦĖęuaPDD¤&ũ=ÚĮaĩYųláôėŅŊž_–- 3+‹ÉÉÄÆDMZz:;’S�((°0ãŊ™9á•+ÁÅÅ…ČˆÖL¸áZÖoÚĘž?˙GbB{IĢ�ÖoÚJ÷.‰ĩ˙+°ÛíôîŅ�ß&>DF´&e×^|›ø——On^>ƒ~į÷ÆfŗáîæÆÎÔ]úûŨļ �=ģuæÆkG9ÆKKĪäÃOŋ Ž]‰ņq¤ėÚKP ?‘mZåĄķú+Gā_mmģ÷î#ŦyĄÍB�8/Š[•mĢĢŠ2ĩ­ÉfŗaˇÛ—éëbgęnü5„4 ĸ]dļîHŽvî9šyFG­ícĸqws̎9ŠˆˆÔTŖ¯FzöčÎԟ~Ļk—NŽí&“ ~üI ō-\zņE´=zÉwŌÄ[™7˙3ō 0 ôî™DĶĻÁg´Î;&ŒuŧÃ`Ëļŧ7įc�&Ū<Ļ|ŸÁ@Ff+×Ŧã§_Wå„Ŧß´õ¤ņūÜ€ŌŌRÚĮD;žÚ=&Ŗ‘ĸâbŠ‹‹š˙îÛđōôäĮ_VđĮ˙ös8-.‰ņ<rß]X øfņOlŨŅ”ÛÆ]Ī3/ŊÁ;ŗ?âĘáƒđôô =#“™sįWÛb)dÎ'ŸsãĩŖxöĨ7ygÖG TūäŽŨngÕē ddfUģꖞ‘ůŋį֛ŽÃjĩ˛vÃf§Ĩc¨$ô˛Ļ 틊jU“ÍfcÃæmÜ{Į-,üú{VŦ^Wƒ3]9‹ĨwgĪcčĀKOĪŋ#i�UÎŊ¨¨˜o˙Ä­7]GFfûūø>rŌę`usІņãĮÛgˍŲ%§ãmؖJXŗú ^K~ü™={ö2áæ8|ø<ü(3ßyĢ^Æ?Ņôį^<#㊜‹^ûŋ'NŨHDDū1&L˜@hhhã_ÎĖĘäûsŲ%5v)""""NŖQ//\ô?˙˛ŒA.§eˍYŠˆˆˆˆSiÔ8lČ JŋŌôŒ] ‘ŗāé`ix """"NH!PDDDÄ )Šˆˆˆ8ĄZ‡@“ɈÕfĢĪZˉ/æqV§ķMeųgŠu ô÷##3ᜠ‚-[´ĀŽũÔ EūÁėØIˆ‹kė2DD¤‘Ôú1a!�¤edaĩž[Ađ ú°îw/ļnۉ͎0(ÎĮh0Đ>.†¯ĢúÍ""ōĪæ’W`Šuᰐ@G<×$ÆE6v """"ÆčãíÕØ5ˆˆˆˆHĶĶÁ""""NH!PDDDÄ )Šˆˆˆ8!…@'¤("""â„EDDDœB ˆˆˆˆRqB """"NH!PDDDÄ )Šˆˆˆ8!…@'¤("""â„EDDDœK]:<œNzVVĢ­žę‘JŒ‹ŦußZ‡Ā‡3ČĘÎ%8Đ“ņŸŗ xāPZN¨ˆˆˆHCذ-ĩNũkŪ2˛˛ đûG@gQëgĩÚ�EDDDÎQJq""""NH!PDDDÄ )Šˆˆˆ8!…@'¤("""â„EDDDœB ˆˆˆˆĒĶgãjjüÄID´nÅÔ)wWØ>eęƒLŧu­[ĩlˆ2Έ{šNiYŲIÛŖ#Û0qܘz=Ö#O˙›ëFĮĪחéĪŋÂĢĪ=^¯ã‹ˆˆˆķpąX, r ´ôtVüļŠ^I=äx éŽņ7ŅēąËŠ1—¯ŋūļA4zäpæ|ô ããņöö:i˙†M›Y°pE……¸¸¸rňĄtî”Č‘´t›ūCbëļí8p€aCķ×_‡Ø÷ĮŸdddrĶØë‰l@Jj*Ī_@Å‚Ņ`äšĢFŅ>.ļAæX™e+×°jŨzĻÜ1ƒÁĀŋŽ`ãæíLž8ާ_|ƒžį%Ņŗ[įJûŽXŊŽÅ?ũŠĢĢ ‰ņí1 Ž}ƒeŋ­áĮ_VPRRBRˇÎ ēėĸ†š–ˆˆˆœãŒŖFlED´!ĄC{æ/XxŌžââbŪžņWž‚ž{š+F eÆûŗ°Z­˜LF,–B||ŧ™<évFÆĖYsˆ‹‹åŪɓ¸ Oožųö{�ōķ xí ؟įžz‚‰ˇŒãõ7ß!''ˇAæX™ŪI]ņđđāוĢÉÎÉeéĪËšūĘá F ēœčȈJûeeįđéß0aė5L›r'ū~ždeį8öÛl62ŗ˛yėūģšįöņüēb5ģ÷ūŅPĶ‘s\ƒÜXÎÎ#‡3í‘ĮčĶ+‰ˆˆ6Ž=îîîŧøÂŗx{•¯ļ‹Ĩ¤¸˜ŦėlŒÆōկĄ�š†ãâęJlģh�BBšōۚĩ�lŪēŗŲLbB<�áááDEĩeÃÆM\pūyglf¯ŋ;ãqĢt�׌F—Žņ Ž5Œŋū›ˇíäŌ~į@ģ¨Ę @ęîŊ4iJhŗ�’ēvâĶ…_WhĶ÷ŧž�øҚ]{öŅúÜŊŋRDDDN†@hâcfÄđĄĖšûM{ Âžå+WąfÍZėv;P¨ėöŋ÷{xx�`4ņpw˙{‡ÁˆÍf  ĀBzFSĻ>čØ]\ZJdDÕaĢ>Ü~ķ˜jī ô÷ķ%:2‚›ˇ1~ĖÕ5ŗĀbÁËËĶņÛ`0Tø ācövüŲÛۋKáiV."""ÎĒAC @ŸŪŊXžâ7–,ũ ŽŽžmŪ˛•īž[Ė#ĶĻāīĨĐÂmwL>íąũũü åŅfcûs˙ARwī%1Ą=_~ˇ„+†8eOOO ‹ŋ­V+ųâą:VO  ¨ßÂEDDäĢÁßh0síU|ų͡”””•¯¯~ž~Øl6žûa FŖĸâĸSŒVQLL4iééėHNĘWgŧ7“#GŌę}5Ufĩ2į“Ī9¤?#‡ôgķļ¤ėŪ @ĘŽ=dfeWÚ¯më–øëū:žß֞ÔfųĒu�dį䒲{/QmۜÔFDDD¤2 žå÷ęõéŨ›ī/ k—NŦZŊ–Š=ŒŲĖĐÁé˜Ī‹/ŊÆŨwŨ^ãqŊŊŧ˜4ņVæÍ˙Œü‚ Ŋ{&Ņ´i𙚠�¯Ŋ3ë¤m&Ŗ‘—žy”o˙DHp ņq1�Œ2€šŸ|΃÷ÜΧ‹ž­ōéā Ā�†猎۟ƒ‹ÉD—N „aŗÛąŲlxx¸ãáîÆ3/žĨ°~į÷ĻU‹đ3:Oųį0Œ?Ū>cƌĶî¸a[*aÍÎl¸j Ĩ‘ŲØeˆˆˆˆTkÃļÔZe– &ĒĪÆ‰ˆˆˆ8#…@'¤("""â„EDDDœB ˆˆˆˆRqB """"N¨Ö!Đd2b=úÍ^9ˇÔ:úû‘‘™Ŗ ("""rĒõgãÂBHËČÂjũgÁ ÛRģ‘3ĒNß t„A9wčÁ'¤("""â„EDDDœ1¯ĀŌØ5ˆˆˆˆH3úx{5v """"ŌĀt9XDDDÄ )Šˆˆˆ8!…@'¤("""â„EDDDœB ˆˆˆˆRqB """"NH!PDDDÄ )Šˆˆˆ8!—ēt>x8ôŦŦV[}Õ#""""ÕpsuĄi ?Á~u§Ö!đĀá ˛˛s ôĮdԂbc:p(Ä¸ČÆ.CDDD@Ii{ū<€hZ‡ Xëô–‘•M`€Ÿ ˆˆˆHrsuĄM‹0Ō2˛ę4N­œÕjS�inŽ.””–Õi Ĩ8'¤("""â„EDDDœB ˆˆˆˆRqB """"NH!PDDDÄ Õéŗq55~â$Ŧee Ü=Ü  ĸK—N\zŅE¸¸˜8|ø<ü(3ßyĢVã×ĩU5G´nÅÔ)wWØ>eęƒLŧu­[ĩŦˇc5´{šNiŲÉīŠŽlÃÄqcę<v™ÕŠŅ`ĀÍŨ €�ããč{^&“é”ũ×nØD|l îînÕļKKĪdúķ¯đęs×Š^gÕ !`ĘäIÄDGQXTHjęn>ųôsRSvqם âĨįŸk¨Rj,-=ŋ­ĸWRÆ.ĨŪŨ1ū"#ZŸ‘ąoŋy ‘­)*.fĪž?YøõėŪûÆ^sĘž_}ˇ”čļ§ """R7 ņôđ$žC{BC›3í‘élŲēĻÁÁŽ•<›ÍÆÜ>fËļí |Ėfns-&Ŗ‘éO?Į€Ë/cgr YŲ9tNL`øĐÁ'cÃĻÍ,X¸ˆĸÂB\\\šbÄP:wJ䉧žĨgR.ēđ�JJJšķî{yāžÉ´lŲâ¤qF̏>Ąc|<ŪŪ^5>Α´t›ūCm’YĮ�� �IDATbëļí8p€aCķ×_‡Ø÷ĮŸdddrĶØë‰l@Jj*Ī_@Å‚Ņ`äšĢFŅ>.ļ^ĪûéXļr ĢÖ­gĘ0 üøë 6nŪÎä‰ãxúÅ7č{^=ģu>å8îîÄFGŌ<¤)Oũû5v$ī"&ē-[ļ'ķõ÷K),*ÂÅŅ!ũ/!Ą} īĖúˆŦė^zķ=FéO\ģ¨*Û –ũļ†YAII IŨ:3貋�ĒėgŗŲølҎlONÅ`�ŗˇ7WBhŗvīũƒ…_Ĩ°ŖÁȈ!ũ‰‰j{FĪĩˆˆHci´{ƒiß>†í;’+l߲uÉ)ģxfúãüßĶĶš¸ß…Ŧ_ŋ“ŅDaaŽnnLšûNŧī~ūuÉ)ģ*ô/..æíīqõč+xáš§šbÄPfŧ? ĢÕJī^IŦXņ›Ŗíæ-[ ¨4�DD´!ĄC{æ/XxŌžęŽc2ąX ņņņfō¤Û9|3gÍ!..–{'Oâ‚>ŊųæÛīČĪ/āĩ7f0h`ž{ę &Ū2Ž×ß|‡œœÜēžâZëÔ~]ššėœ\–ūŧœë¯ŽÁ``ĠˉŽŒ8­ņüũ|iՖä]ģ))-å?ÎgÄāËyâÁ{Ō˙>˜÷6›ą×ŽāîÛÆ×.ĒÚļ�6›ĖŦlģ˙nîš}<ŋŽXÍîŊTÛoGĘ.R÷ėãá{īäŅŠwsAī$6oŨÅRČģŗįqiŋ xäžģ¸ņēŅŧ?įcōōōëũüŠˆˆœ |%đx^žžX -ļųųú’•ÅĒ5kHˆī@î]ōûū�z÷,ŋ4ëííEģ¨(v&'ĶŖ[WGwww^|áYŧŊĘWîÚĮÅRR\LVv6ŨģueŪ'Ÿą˙ĀÂÃÂXŗnI=ēUSĄ+FgÚ#Ņ§WmjtŖŅ�@bB�MC‚qqu%ļ]4�!!MųmÍZ�6oŨŠŲl&1!€đđpĸĸÚ˛aã&.8˙ŧĶ?Š5ôúģŗ1 ļ]3z]:Æc0¸vÔ0ūũú;lŪļ“KûOpP �íĸN/�ãéáAaanŽŽ<5í^<==�ˆ‰nKIi)9šy˜ÍŪúT×ö˜žįõ 0ŸȈÖėÚŗˆÖ-ĢėįëãCNN.ë6lĻ}l4;v�`íúM˜ŊŊč[ūwÚ,„ˆV-Ųŧ}'ŊēwŠÕœEDDÎf¤ĨYņr[˖-¸mÂÍüøķ/ĖũđZļ įęŅŖđđ(˙ŨËĶĶŅÖĶ̓KÅ °|å*ÖŦY‹ŨnʃŽŨŪ^^$vŒgųŠU 2M›ˇ2úŠÕÖØÄĮˈáC™5÷#›ö@ŽsĖąš#îîī0ĢYŌ32˜2õAĮîâŌR"#jļjęØ}{Uņ÷ķ%:2‚›ˇ1~ĖÕu>^zF&­ËĻYũû~߸ĨÂyŗâŽsĒļ>ĮGoo/ ,…Õö k΍׎æ×•ĢųtŅ7„‡6gäāūX ÉČĘæ‘§˙í¯´´Œ6­*_%9×5ZüëĐ!RwíaäđĄ'ík[ž˛VRĘ7ß}Īģ˙™ÍˇN� //Ÿ&M| ŦĐwķ–­|÷Ũb™6•�,…nģc˛c¯¤Ėž3V-[ĐēUKNYkŸŪŊXžâ7–,ũ ŽŽžę85åīįGxX(ž0۟û’ē{/‰ íųōģ%\1t@­Į:œ–Ξ}2øō‹Ųž3•Ĩ˙]Δ;&āįۄÂĸ"î{äéJûÕ¤­Ĩ°Đą[PP@p`Ā)ûĩ‹Š ]TĨee,ūéWæ|ō9ũ/îKhŗîŊsB­į)""r.iđ{KJJŲž3™W^{‹n];Ķö„¯_~YÆÜ>ÆfŗáææJhhķ Ëkŋ,[@fV;““‰‰ŽĐ?+;__ü|ũ°Ųl|÷ÃŒFEÅE�th‡Õfåŗ…_ĐŗG÷Õl0síU|ų͡”””Öč85MZz:;’S€ō•ÁīÍäȑ´Ķ§>•Y­ĖųäsFéĪČ!ũŲŧm)ģ÷˛k™YŲ5§´ŦŒ”Ũ{™ņŸéœĐÖ-˙ENn.>f3žM|°ŲlüøË ÅÅ،å˙9Z ËWķĒk{ĖōUë�ČÎÉ%e÷^ĸÚļŠļßĘÕëølŅ7Øl6\]\hŌ€¨ļmHĪĖ$õč<-……|0oéYõsREDDÎ2 ļøÂ‹¯`4°c'(0ˆ>įõâ˛K.:Š]—ŽŲž3™)SÂäb:útđu�˜L&<<<xøņ')ȡpéÅŅ6"Âqŋ @×.Xĩz-Szŗ™ĄƒŌ1!ž_z'›†ŲLĪŨYúĶĪtíŌŠÆõ‡‡‡Ķ§wož_ŧä”ĮšûŽÛk<ގ—“&ŪĘŧųŸ‘_P€Á` wĪ$š6 ŽņĩņÚ;ŗNÚf2yé™GųvņO„WūîČ!˜ûÉį<xĪí|ēčÛS>|ė~C;āGĪnš°OųŊ{ããXˇa3?÷2fo/ú_r!bŖyķŊx`ōDãÛķüĢ36đR:wėPeۛŽģw<ÜŨxæÅ7°ŌīüŪ´jNHĶ *ûŨuëM$īÚÃŖĪŧˆÉdÂėíÅU#‡āééÁ„Žáķ¯žŖĀbÁ`0ĐŊK'‚ũĪÄéit†ņãĮÛg˘qÚ7lK%ŦŲ™ *ĮĢĪB/ųņgöėŲ˄›oŦ‡Ę߁Ci$ÆE6v"""Ō€6lK­Õŋ˙&L 44Ôų>—™•É÷?,ŽtRDDDÄY4ęĶÁ máĸ¯øų—e py•īqįL iZįKÁÆ bؐAõT‘ˆˆˆČšËé.‹ˆˆˆˆB ˆˆˆˆSRqBÆŧ‚“?ģ&""""˙lF7×Zu4™ŒX~˙VDDDDNIinŽu{žˇÖ—ƒũũČČĖQi@%Ĩeėųķ ÁuüĒU­#dXH �iYX­ ‚mÃļÔÆ.ADDD€›Ģ Áū4 ôĢĶ8uZG t„A9wčé`'¤("""â„EDDDœB ˆˆˆˆRqB """"NH!PDDDÄ )Šˆˆˆ8!…@'¤("""â„EDDDœB ˆˆˆˆrŠKįƒ‡ĶIĪĘÁjĩÕW=˙x!Aū~ÎL&#Aūž„†5Z """Ō¸\/YZĢŽg•Kp ?&Ŗk"=3‡ĖŗāœYm622ŗąc ,$°Ņ摯cėÜšS­:fdeā§�xĘĘĘΊsf2 đ##+ģQë‘Æc ¨UGĢÕÖčaæ\cĩ=įĖd4ę2žˆˆˆ;;‰ˆˆˆˆ4(…@'d<|$­ąk‘æ˛aÃÆÆŽA*QVVFÆ_’“™FYiqú¸ēēĶ$ ˜Āæ-pqŠĶÛDDDäÎå˛K/nė¤ũ ļRēöėƒģ‡gú’ŧmũĩ>ʈˆČšL÷žĨr3͈Š¯q�p÷đ$:.ÜĖ#g°2ų'hk†ã'NÂZV†Ņ`�Āh2ڜaCŅĄ}\ŊkĘÔšéÆˆ‰ŽĒ—ņ>Â?ĘĖwŪĒ—ņjĒ´´ø´ā1îž”Öđōąˆˆˆ8¯ģqlĘäIŽ`VZVÆoĢÖđęoķÂ3OâëįÛPeœSėv{c— """˙Pōô€Ģ‹ }z÷äÏ>æāáÃøúų’’šĘĮķP`ą`4šæĒQ´‹`ÃĻÍ,X¸ˆĸÂB\\\šbÄP:wJāŋŋ,ãëīžĮÍՍŽ]:ÁŅÕÆĘlÛžƒų R\TŒÉŅū—^L¯ž=8’–Îc͟bİĄlŲ˛•#iéÄÅļãšĢFWč˙ÄSĪŌ3Š]x�%%ĨÜy÷Ŋ<pßdZļlQīįI!PDDDΔF ĨĨĨü˛lF“‘……‘Ÿ_ĀkoĖāÆą×“˜Īūũûyō™xîé'đđpįíī1éŽÛˆiĮīë70ãũYŧ•OVv6sį}ĖcĶ <<œ˙ūēœĖĖĖJ™››ĮĢoŧͤÛo%6χæ‘éO͞E8ž^^X,… pםąZ¸ëžŠ\Ø÷|Œ†ŋo›ėŨ+‰eËV:Bāæ-[ 8#�…@9C,žôę˜ŒåĢt……EÄDGqīäģ0›ŊYšj5fŗ™Ä„x�ÂÃɊjˆ›¸āüķxņ…gņöō }\,%ÅÅdeg“œœJXh(ááá�ôéŨ‹?ú¸ŌãoÛąƒ`bcÚĐŦYíccØ´uIŨģĐ­K�ŧ<Ŋđķķ###“ā  ĮŨģueŪ'Ÿą˙ĀÂÃÂXŗnI=ēŗUN+"""rĻ4XŧûΉŽ{˙ũōk4oی6­[PP`!=#ƒ)St´/.-%2"€å+WąfÍÚŖĄ¨<HÚ퐟Ÿ—ˇˇŖŅh¨đûxyšyø˜}*l3›Íäåæ9~{zzü=–ÁˆÍVņÛēŪ^^$vŒgųŠU 2M›ˇ2úЧw"NƒB ˆˆˆœ)r9øĘQ#xlú3ôéŨ“đ°0üũü åŅiœÔvķ–­|÷Ũb™6•�,…nģc2�^Ū^Z,ŽļeeVōķķ+=ϝorķō*lËËË#´YČiÕŪ+ŠŗįĖŖUË´nÕ’Ā€€Ķę:EDDäLi”÷†…†r^¯$>üč�bbĸIKOgGr Pž28ãŊ™9’FVv6žž>øųúaŗŲøî‡%ŠŠ‹ˆŽŒäûđŋ˙íā§˙ūRå1cccH?îūú‹m;v’Đá´jīĐ>ĢÍĘg ŋ gîĩ™~ŲíļZũODDDäTíÛbC‡ fęƒĶXŊf-ŨģueŌÄ[™7˙3ō 0 ôî™DĶĻÁ˜}ŧYĩz-Szŗ™ĄƒŌ1!ž_z'›Æ•ŖFōâ+¯cr1‘ÔŖ͚5Ãn;9ų˜ÍÜ1ņVæĪ_@Qq1&ãn¸žæÍš‘™UųÃ$•1ôėŅĨ?ũ\ū4ō¤•@9S cĮŽĩΜ9ķ´;nؖJXŗā3PŌŲoɏ?ŗgĪ^&Ü|ãiõ;p(­Æįlûē_éŅįĸڔĮĒ_—ÛĨOęIŒ‹ŦÕ1DDDäÜ4aÂBCCõ؏͕™•É÷?,æ˛KjĐjĘÕ՝âĸBėvûiũ¯¸¨W7÷3Z›ˆˆˆœûírđšháĸ¯øų—e pų™{7āQžMüŲ“ē“6‘íps÷8u ¤¸ˆ=Š;ņôņ?Ŗĩ‰ˆˆČšO!đ4 2ˆaC5Čą<|üąäd°uÃē ØÕÍwŗ/^ """r gŠ  `rÎ{.EDDäĖĶ=""""NH!PDDDÄ )Šˆˆˆ8ĄZ‡@“Ɉĩ’—2KÕLÆŗįœYm6L&ũ�gUëčīGFfÎYjÎ...gÅ9ŗÚlddæčīרuˆˆˆHãŠõĶÁa!�¤edaĩ*ÖTĶ €F?g&“‘@?Įߥˆˆˆ8Ÿ:Ŋ"&,$PAĸtÎDDD¤ąéĻ0'¤("""â„EDDDœŅÕÕĩąk‘Ļ•@'¤("""â„EDDDœB ˆˆˆˆRqB """"NH!PDDDÄ )Šˆˆˆ8!—ēt>x8ôŦŦV[}Õ#""""ÕpsuĄi ?Á~u§Ö!đĀá ˛˛s ôĮdԂâšâĀĄ4ã"ģ ŠĨ’Ō2öüy�;Đ´A°Öé-#+›Ā�?@‘äæęB›a¤edÕiœZ'8ĢÕĻ�("""ŌÜ\]()-ĢĶJq""""NH!PDDDÄ )Šˆˆˆ8!…@'¤("""â„EDDDœB ˆˆˆˆĒĶgãNĮoĢ×°xɏ8x“ÉDË-4ārâbcĒ„ųmÕ;ÆãááÁáÃGxāáG™ųÎ[õ6žÕjåĻ yü‘‡hŲâ_öŨzĮ]ÜyûmÄDG1~â$Ŧee �Œ&aĄÍ6dÚĮđđãOrÉEr^¯žõV߉îyh:Ĩe'ŋ‡(:˛ ĮŠõ¸ié™Lū^}îņJ÷?ōôŋšnôp"#Z×ú§cū¯iLŸžŨ+üމŠŦļÎē8ūdįäōđS/đĘŗaŦ§÷oŽŨ°‰øØÜŨŨÛNœ§ˆˆ8¯ ?,^Ęĸ¯ŋášĢF“ĐĄŖUĢ×ōĘëor׉i×eÔȂ…_Û.‚ƒƒxéųį­–)“'@iYŋ­ZÃĢoŧÍ Ī<‰¯ŸoƒÕqĮø,Œ5–íÉŠ\اįIŋíöF,ĒŽžún)Ņm#*„Āį)""Î댇Āĸĸ">_ô7\5IŨ˙^}č×÷|üņ1›Øļ}ķ,¤¸¨“‹ ũ/Ŋ˜^={p$-ĮĻ?ňaCŲ˛e+GŌŌ‰‹mĮ5WŽv@Jj*Ī_@Å‚Ņ`äšĢFŅ>.€Í[ļōÉüäĐ,¤)cŽģšĪ>_DzF&Oũß \så(š…„TX ŦMõÅÕŅ>Ŋ{ōáGsđđá ÕYļr ĢÖ­gĘ0 üøë 6nŪÎä‰ãxúÅ7č{^=ģu>å8+V¯cņOŋâęęBb|{ GW@ļlOæëī—RXT„‹‹ Cú_BBûIį…×gpIß>¤îŪKvN. ícpi?l6Ÿ-ú–íÉŠ `ööæĒ‘CmÂS˙~ŊB]GŌ20 œô;-=ŗBÉŠģųâÛÅ—āb2qŅŊéÖš#P°~õ= Mƒƒ¸rø š…4­˛ūĘüži ß/ũ/K!];%0|ĐåÕˇĒy~ũũdeįđŌ›ī1rHâÚE4Oqng<îŲ÷ĨĨĨtëŌå¤}‰ ņ�äææņęo3éö[‰iĮĄC‡ydúĶ´lާ—K!ÜuįD,…îēg*ö=77ˇ*÷ų˜}xíÜ8özâŲŋ?O>ķĪ=ũƒ7ß~)“ī mD?,ũ‰ˇfŧĮ#=ĀøÛîāĄûĻāëįËáÃGĩÖļÆæÍšÕËy,--å—e+0šŒü+,Ŧ^ÆŦŊ“ē˛qëv~]šš„öą,ũy9“'ŽÃ`00bĐåžrŒŦė>ũâî›t ĄÍBXšæw˛˛s�()-å?Îį–ą×Õļ ›ļîāƒyŸņüô‡0ãęęÂmãŽĮRXČô˙{•vQm)*.&uĪ>ž÷NL&ŋoÜÂæ­;mrR]ۓSˆŽŦō÷1ųŧ3{ão¸ščļmHKĪāŲ—Ū$<Ŧ9M|Ė˜û ĮĄuËņßåŋņŸ?åž;ÆWY˙‰ GŌ2˜6åN˛˛sxâ˙^ĄW÷Žx{{VyÜŦėœJį9öÚQL~đ îžmM|ĖÕÎKDDœĶ4ņi‚ÉdǞÍļ; v\nÖ,„öą1lÚē¤î]!ŌËĶ ???222 mVåžŊûūĀl6;‚fxx8QQmŲ°qîî„4kJۈ�.ę{>={tĢvĩ­ą.!đĨWßĀd,_+,,"&:Š{'߅Ųė]ë1kãõwg;îM<æšŅÃčŌ1ƒÁĀĩŖ†ņī×ßaķļ\Úī|‚ƒĘV썈Ÿē{/ÍCšÚ,€¤Žøtá×�¸šēōÔ´{ņôô� &ē-%ĨĨääæ9úwīœ€—§'‘­HŨŊ—ö1Ņäää˛nÃfÚĮFĶšcGûëÚž3• z'Uųû˜Šģ  ēm�‚ƒiՖí;SņķkBpP ­[–ßįŲ§gwēvJ¨QũĮØívÎīÕƒÁ@€ŋū~MČĘÎáVyÜv‘•Îŗ˛û8̚—ˆˆ8§3}Ėfrķr(--ÅÕÕĩŌ6yšyø˜}*l3›Íä÷åąDŒ#6›­Ú}Ō32˜2õAĮžâŌR"#"(-+ÃËËËąŨd2ác6SRRZå<ęZã1CųM˙•íŗYm ?p÷÷ūûå×hŪŧmZˇĒ˛Æ3åö›ĮT{O ŋŸ/Ņ‘lÜŧņcŽ>íņ ,ŧŧ<ŋ C…ßĢßĀīˇ`ˇÛō0j;îf=÷ŋ˙ėîŽĨ°đ°æÜxíh~]ššO}CxhsFîOxXķ Į.--eߟû‰ŒhUéīãåå`öŽĀŊŊŊČËĪĮÕÕ/Īŋk6xũoėTõīÄ1lv[ĩĮ­jž!!ÁÕÎSDD䌇ĀÖ­ZâîæÁō•ŋŅ÷ü>öũēlŽnŽøú6!7¯âĘH^^žce¨6üũü åŅiœ´oõÚuäåæ:~[­V:DĶ ā“ÚS_5|šøpčđ!ZˇjéØ~čĐaŠKJ ô¯´ß•ŖFđØôgčĶģ'ágŅå`€?÷$u÷^ÚķåwK¸bč€ĶęīééIaa‘ãˇÕj%ŋĀ”¯^-ũīrĻÜ1?ß&qß#OWč_`ą8BR~…€€ōsØ.*‚vQåĄņOŋ2į“Īy`ōÄ }SvīĨU‹pĮ˙A9ņ÷ņ|›˜ÉĪ/¨°-?ŋ€ĻAø˜+îŗŲl:’FVVÎ)ë?•ęŽ[Õ<§Ü9ĄÚyŠˆˆœņ÷ēģģ3bøæÍ_ĀO˙ũ…œė ,,ųņgæÍ˙”āā`bccHOOgGr �ūú‹m;v’ĐáŖW-&&š´ãÆ,(°0ãŊ™9’FûØXŌŌ3Øļ}�ŋ,[ÎÛoŋ‡‹Kų%ëüBËIãÕg—\ԏ…‹žfīž?(+ŗrā¯ŋxö$uīFP`å÷Ѕ…†r^¯$>üč“Ķ>Ū™Tfĩ2į“Ī9¤?#‡ôgķļ¤ėŪ @ĘŽ=dfeŸrŒļ­[rā¯Cøë�Ë~[ëØ—“›‹ŲŒol6?ū˛ƒÁ@qqąŖÍŠÕŋ“KęžŊDˇmÃĘÕëølŅ7Øl6\]\hŌÔŅūøēļīL%æøûOø}ŧčČ2˛˛H=:ŋCGŌHNŨMû˜hÚEEž™Eręn�VŽū˙ĖOn^Ū)ë?•ęŽ[Õ<MG_3c),<åŧDDÄ95Č+b.ēđšąđ‹¯˜÷Ég¸šē҆ûîšÛąvĮÄ[™?EÅŘ\LŒģázš7kFfVfõƒWÁÛˋIoeŪüĪČ/(Ā`0ĐģgM›–¯öMēũVfĪũˆB‹…āā`nše&“‰n];ķēĪrå¨ÄļûûÕ5>fsŊÕ8đōKq5š0ãŨ™dfeŌ¤IztëÂĀūũĢí7tČ`Ļ>8ÕkÖŌŊ[×Ķ?)ĩôÚ;ŗNÚf2yé™GųvņO„Wū´ëČ!˜ûÉį<xĪí|ēčÛ=ĀđA—ņÖûsp1™čŌ)ā lv;ããXˇa3?÷2fo/ú_r!bŖyķŊ¸yĖՍF<ÜŨxöĨ7)°X¸đŧž´nų/š5 &y×}æEL&fo/Ž9 B]ۓS¸eėuŽZNü}<o//ns5_|ķÅÅ%˜L&Ž=Œāōšņ7\Í'ŸIaaAŒŊvū~žÕÖ_Õˇ‰Ų\é<F#‰ņíyūÕ xiĩķįd¸ęĒĢė}ôŅiwܰ-•°fU_>•ŗĶCi$Æũ3V„NõÂi‘˛ ÛRkõoúÕW_MHHFŸSˇ‘üüüôí`g¤(įŦā �] Š%…@'¤("""â„EDDDœB ˆˆˆˆĒu4™ŒX+ųū­ˆˆˆˆœY%Ĩe¸šÖí›ĩū~ddæ(Šˆˆˆ4 ’Ō2öüyā@˙:SëRūÛ´Œ,ŦVÁsɆmŠ]‚ˆˆˆÔ’›Ģ Áū4 ôĢĶ8uZG t„A‘˙gīžÃŖ*ķ6Žg&R&ŊBMHH‰ĄƒX+ęRD\EwTDQVQYAW_+‚XA@DTÊ Ō „„"HK$3é“÷ĀH$ †:÷įēŧ6sĘs~Īsf“›SEDäĪC7†ˆˆˆˆ8!…@'¤("""â„EDDDœB ˆˆˆˆRqB """"NH!PDDDÄ )Šˆˆˆ8!…@'¤("""â„EDDDœKCV>”‘MvŪQ**ėUˆˆˆˆÔÁÕėBp€Až j§Ū!đ`FyųĮ đÃdÔÅ?ǃG˛HėÜņ\—!"""¤´Ŧœ=ûR 7 Ö;ŊåäåāīĢ�("""Ԍ\Í.th͊ŦœŧĩSīWQaW�9\Í.”–•7¨ Ĩ8'¤("""â„EDDDœB ˆˆˆˆRqB """"NH!PDDDÄ 5čĩqgãĮõøâËU<t“ÉDÛ6m¸öęĢčĶ\%œ‘×m ņŧxÜÜÜČČČäÁGĻņÆÜŲēąãīĄĸŧŖÁ@ ˇŌŊ{WޏôR\\LŽe"ÚˇcĘä{Ģ­;yĘCŒŋcíÛĩmԚN¸oꔕŸúÜĄčŽ?ft“lķĪj㖟‰ĄE ×Fm÷Ņ˙åφŌ1ĸ}8kâ�� �IDATŖļ{.eeįōÄŦįyáéĮÜÖ_q|DD΅f ŸņË?YÁČo ĄK FëÖoäų—^aâ„ņÄÆtjŽ2ÎČŌeÛ)777‚‚ynÖĶM˛É“î!&:Šĸâ"ŌĶwŗhɤ§íbâŨãËdegķũë8ŋOī&ŠĄ6ÆŪĸ?°gāã•_Ņč!PDD¤94y,..æƒåsËÍ#čĶĢ—cú€ūáīÅ@ōŽ/]FIq &^qį÷íMfV6˙~âIŽ2˜mÛļ“™•MįØNŒŧņ†:į¤Ĩ§ķŪâĨXm6Œ##oN\įX�’ļmgŅâĨX­„†3úĻŧ˙Ár˛sryō?Ī0ōīà Šv$°>5žŽģ›;ņ]â kÉÏ>ÁļíÉt‰ë Ā Ã†ōŋ‹8/>OOFŨ/õĩæ‡ ŦÛ´™ÉÆa0XõŨ÷lMÚÁ¤ņc˜ņėËôŋ }{vĢqŨŠé,ûø3Ŧ6ÁAü}čĩ˜ÍfžūŋŲ\{åĨėHM'+'‡N#6hāikIMß͇Ÿ~AII).&—^܏žŨÎ##3›g^šÃåũ/$}÷^ō#!.†Ģ¯�ĀîŊûXöÉg؊Š0Œ\7h 1Q‘dįä2ë…9\ܯ7Ģŋ_ĪãM"u×^>ųė+ŠŠ‹qqqaĐĀËIˆ‹aî[ ČË?Ęs¯ĖcØ tîUk=5ĩëęú[pü~ũ&žøú;Ėfãã0 uŽYhHpãR[˙ęÚwÛSŌjėgNnO˙ßl^~ )ŠéÎČäęË/!#+›ũ‘›—΍áCčĐŽÍiĮũLjŦIcĪŲė§M[’øōÛĩzuKä§­Ûr͕tŠŠ¨s""M÷ü˛˛˛2zvī~ĘŧĄx�Ž+ā…—_åžģî 6ĻGŽdđč3hÛ&wlļ" ˜x÷xlE6&Ū7…Kú_„ĢĢk­ķŧ,^ŧøōūqëÍ$&ÄsāĀĻĪ|†§g<ŽÁ`ā•Wį1yŌ"#"øü̝™=gN}ąwN`ę“ņņõ!##ĶQk}klzFã@\\ ;RR!0"ĸ ]âXŧtˇŪ<˛öFÃõë̓­ÛwđŨëIˆ‹åĢoÖ2iü ×]{AA5ŽWhĩōÆüEŒ3šöm[ķíÚyķŨ%ÜūQƒÆŨ:’ĸâbĻ>ū.čۓ_bҞOi+80;ū9Ššo/dė-#ˆŽė@VvO=÷ á­Zbv1S\\‚ŲėcnÆVTÄ˙yNQ‘´ æĩˇ2røēÄFsčHĪžüĶ˜ˆÉdĸ¸¤ŗŲĖĖiS(+/įÍwsû­#‰ŠėĀĪÛSxgáûĖzb*ˇŽΤ‡įŪ;ĮāíeĄĐj­ĩw7ˇjížbōō˛äÃ<pĪ턅†đƟČË?Zį˜]Ö˙‚ZĮeü˜›kí_mûŽŽ~FŠŠ‹ąxzpĮ?ob㖟™ŋhãoÍߎēŒUß}Ī—ßŦaÜ­#1ĩŽģˇ——ŖN›­¨ÖŊŧ,ÕúÔØãS×÷æ÷û)˙č1-û„)ī ,4„Īžú–ĖŦlL&]J-" M­V+Ū^ۘLĻZ—INI!$$ČqZ844„¸Ø~ŪžLŸ^=�!ŌÃŨ___rrr ­uŪŪ_öaąXA3<<œ¨¨Hļlũ™n- &2ĸę_ķ—öŋˆžŊ{ÖŲúÖxĻ!°j=wlEļ“ĻTrũ°Ą<üčŋšđü>DDt8ãļâĨ×ŪÆxRPyÃēŸÁ``Ôđ!ü÷Ĩš$%īäŠXüę::˛3}7A´oÛ€ ûöĸG×ʎŋ÷°[B�ÜŨÜđņņ&/ī(Ũãéž_c{›ļ&čOtd՘Đ)*’;ĶIˆĢ:ÚÛĢ["P5Ž#ڑž{/šyųX<=č @Xhíڒ´c';EaˇÛéŅ5ƒÁ€ĢŲĖ“ߏģģ�1Ņ‘”–•qôX‹g ũĢšžî‰ņÕÚ=YúîŊ´ &,4€>=ē˛dŲ'uŽ™§‡G­ã˛qķĪĩöīü^ŨkŨwĩõķDŊ]bĢž÷Á¸˜LDŋ\ 80€M[’ĒÕP͏w;īˇz“wĻÕYcSŽO]ߛßī§ôŨ{  ulûŌ‹ûąōĢoklWDä΍ÉC —ÅÂą‚Ŗ”••a6›k\ĻāX^¯jĶ, Į ŸOü0ŒØíö:įY­6˛sr˜<å!Įŧ’˛2:FDPV^އĮo§WM&^ ĨĨeĩöŖĄ5ž‰ĖŦ,:vŦ~JĖÛËÂuCķÖüüûáĪĒŊúēëļŅu^čįëCtĮļ&%3vôˆ3jĶjĩááîîøl4ņôđ ˙č1�ÜÜZœ4Ī€Ŋ˛îą+(°bņŦÄ<==((,t|>šMˇ-°á^äFN^>Îø¯c^YY9Úĩq|ļœtę}ũO[øië6*++Ē@d¯ŦŦW=–Né[m6<<~ƒÁāø\ۘÕÅVTTg˙jÛwĩõĶt<ž¸îŅ`0TģŌ`0Pi¯>5ûŲÔx˛ÆŸŗŲOV›­Z{...xũ.ü‹ˆü™5ylߎ--\ŨXûÏôŋčÂjķž[ķ=fW3>>Ū+(¨6¯  Āņ/đúđķõ%ŧUĶjNë7nĸāØ1Įኊ 9Bp`P­í5E';|äéģö0lčāSæ]Øī|Ö~˙#_~õ5üîHŌš°˙Ā!Ōwī%1!ŽV~Éõƒ¯>í:^ ……VĮgģŨΑĖ,ÜŨÜj]gĶ–¤ZOë ¸¨oĩö� ­„:>[m6ĮüBĢ ?|ŊŊ áūģĮŌî‰@zâč׎é|õíZ&O‡¯7EÅÅ<đčŒkõņļœļžßpww§¨¨Øņšĸĸ‚BkÕŅāÚÆėāĄ#,ūđ“ĮåōūÔÚ?¨yߝM?ĪDMã~˛ēöÁī5öøœÉ÷æÄ~rwsÃVËļEDū šüâ–-ZpŨĐA,\ŧ”¯ŋ]ÍŅüŖX­6ž\õ /!((ˆØØ˛ŗŗIIMāāáÃ$§ė$áø)Âúˆ‰‰&ë¤6­VsæŊAffqąądeįŧ#€ÕkÖōęĢķg),:õ}SÔPZZƎŠ<˙âlzöčæ8E}2ƒÁĀčQ7ōҊOë<ZŲĘ+*øßĸ6h à $)9…´Ũ{HÛĩ‡Üŧü×ëAvnŠéģøaũOŧ9qÁč„î‰ņĖz|ę)˙Ũ÷8ĸ;F“—GúņmÉĖ"5}7q1ŅŽõŋ_˙PîŌ÷ė%:˛Q‘ČÎÍuŦg+*❅KÉÎÉ;eûGÃËbÁĮÛ ģŨÎĒÕßc0())Ád4:Ö؞~ŪžBFf6�‘íÛrđđ>š7žvĖztM¨u\ęę_mûŽŽ~ÖGMã~˛ĶíƒĻŸŗŲOíÛĩá׃‡ČĖĘpŒ‹ˆČ_Eŗ<"æŌK.&8(e~ĖÂEīãj6ҁîģ×ņŧģ ãī`ņâĨ—”`r11斛iJn^nŊļééáÁ=ãī`áâ÷)´Z1 ôëۇāāĒŖ}÷ÜuoĪ_@‘ÍFPPˇß>“ÉDĪŨx|úSü}øuÄvúíŅ5^KŖÖøĖŗĪc4¨¤’Ā€@.ŧā|ŽŧüŌZ—įÂ~ũøė‹/Ī~0ÎŌ‹sß:ešÉhäš™Ķøô‹¯ žsÕķ‡ ēšų‹>āĄûîbÉōOkŊ;ØÃŨąˇŒ`ŅQTTL`€?ˇŽ^ī==<¸mô>\ņ9%%Ĩ˜L&FŨ0„ @˛˛s1¸ĩpåŠį^ÁjŗqÉ}׎ģe$|ŧĢ͆Á` W÷Žø9Žžp^|g6mIâą§˙‹§/ŋ„.ąŅŧ2īœ4žÄø8fŊ0‡!×\AŋŪ=j­į÷íŽüō.čۓā@üzí•Ė~ũ¸˜Ltīš@HP öĘĘz™ģģ[­ũûhå—5îģ{ĮŠĩŸwŽšųŦöKm㞕ũÛ˙GęĒąŠĮ§ŽīÍī÷SHP ×\1€įž‰§‡}zvÅ×ĮKAPDū2 cĮŽ­œ3gÎY¯¸%9VĄĩŸ>•?†ƒG˛HėÜņ\—ŅŦķÁÄMáĮ›ßņĮy>fchŦq˙#OeeeĩĐ÷Ā´Lŧ㟍vˆˆHClIN¯×ßøqãÆĻׯ‰47Wŗ™Nĩ<Oū8ãSRRʔi3Ųĩį ę4ĩÉhrÜQ-"ōg×l¯‘*ŨÎkØu¤u”ņiŅ•‘×fÁûË)//ĮÃÝŒŽŲEŋ6Eä¯AŋÍä/'(Đ˙{*ø¯ė¯8î ]bIč{ŽËi:,"""â„EDDDœB ˆˆˆˆRqBõ&“‘Šŗ|7ވˆˆˆ4\iY9Žæ†Ũß[īāįKNîQA‘fTZVΞũ‡ đ;ũÂu¨w„lRõĀÔŦœ<**˙Čļ$§ŸëDDD¤‘¸š] đ#8ŽAí4č8b̐�G‘?Ũ""""â„EDDDœB ˆˆˆˆRqB """"NH!PDDDÄ )Šˆˆˆ8!…@'¤("""â„EDDDœB ˆˆˆˆjĐģƒed“w”Š {cÕ#""""up5ģāGP€oƒÚŠw<˜‘C^ū1‚ü0u@QNīā‘,;w<×eˆˆˆüŠ•–•ŗg˙A*āÁz§ˇœŧ|ü}�EDDDš‘ĢŲ…mZ‘•“× vęā**ė €""""į€ĢŲ…Ō˛ōĩĄ'"""â„EDDDœB ˆˆˆˆRqB """"NH!PDDDÄ )Šˆˆˆ8ĄŊ6îlü¸~_|šŠƒ‡a2™hÛĻ ×^}ccšĢ„3ōãē $ž››™<øČ4ۘ;û\—õ‡4yĘCüķˇÕ$íß7õ ĘĘO}RtĮŒ3ēÁí¯ū~=ŽøœQÇĐíŧ.Žéģöü²O>#+;OOwŽp1Ŋģ'°=%•W~E^ūQŧŊ,\yŲÅt?/žZģ…V+ĶgŊȅ}{2đōK�¸÷ÁĮNŲūŒiSpwskp?DDDęŖYBāį_|ÅōOV0ōÆHčŌƒŅĀēõyūĨW˜8a<ą1šŖŒ3˛tŲ‡ÄvŠÆÍ͍  @ž›õôš.ÉŠM{ #Ú7zģ –|ˆŊ˛’āĀ€jĶ­6sŪ|—×"1>Ž]{~áå×Ū&ĸ]Ėf3ožģ„qˇŽ$*ĸ=iģö0ûõ˙ŅžMküũm,úācZ´pu|.++Ŗŧĸ‚YOÅÍ­EŖ÷EDD¤>š<ķÁōšåæôéÕË1}@˙‹đ÷÷ÃËb yG ‹—.Ŗ¤¸“‹ ¯¸Œķûö&3+›?ņ$× ĖļmÛÉĖĘĻsl'FŪxCķ�ŌŌĶyoņRŦ6Fƒ‘‘7'Žs,�IÛļŗhņR ŦVBC‚}ĶŪ˙`9Ų9š<ųŸgų÷ᄆ„T;XŸīĐáÃ<1ãiŽžęJvĻĻ‘—”n‰ ü7�ļüœÄŌeË).*ÂÅÅĖõ× Ļ[×Dėv;ķŧĮļä ŧ,n=ŠV-[Ö8ũ˙ž‰;īKDûöäįį3qōŋwÛ­ôéÕ ģŨÎøģīãņ?LfffÍ}ĘĖâą'grŲ€KXõõˇĖzęIÖ­ßĀ'+?ÃÕėJî]Á`hŌīÎéŦųaë6mfō„q V}÷=[“v0iüf<û2ũ/čCߞŨj\ˇ_Ÿ´ oÅ3/έ6Ũ^aįúÁW“@d‡vøx{‘™•C̰PFßxQĮCiTd<==ČĘÎu„Ā-IÉØlEÄwūí6ļĸb Cĩ`(""rŽ5yÜķË>ĘĘĘčŲŊû)ķĒNŖ;VĀ /ŋĘ=wŨAlL'ŽÉāŅ'fĐļM8îØlE 0ņîņ؊lLŧo —ôŋWW×ZįyYŧxņå9üã֛ILˆįLŸų OĪxƒÁĀ+¯Îcō¤ DFDđųW_3{Î<ú cīœĀÔ&ããëCFFĻŖÖúÖØ24´ZŸMFEEŘ]]™|īŨX­6ūõČ4:ĮÆŌŽmk^3{&ÜIlL'~Úŧ…9¯ŋÅė„xļ'ī 5m3Ÿx ëÖodķæ­äļÍ­qzįÎą¤§í"ĸ}{RvĻŅ12‚ŠģčĶĢûö˙Š7-\]ëėSUf^xn9ššĖ_ø˙~øAÂÃÃųöģĩäææ6í—į4úõéÁÖí;øî‡õ$ÄÅōÕ7k™4~ ƒëŽŊŠ  €Z×mŪĒÆé^^zv;ĪņųpF&VmÛ´Ââ鉯7�lڒDee%mÛTĩUhĩą|ÅįLw+ߎũŅŅFQQ&“‰7æ/â—ũ°xzréÅũĒ‚inM­V+Ū^ۘLĻZ—INI!$$ČqZ844„¸Ø~ŪžLŸ^=�!ŌÃŨ___rrr ­uŪŪ_öaąXA3<<œ¨¨Hļlũ™n- &2"€Kû_DßŪ=ëėG}kü}<Ą_ßŪ�xzzĐ)*ŠŠŠDGEōė3Oáéá@\įXJKJČËĪĮ×Į‡ŧü<ÖmØ@B|zßæž}ûkœž~ÃFÖmØÄ•W\FJj*— ¸„å¯�`gj*qcNÛ'ģŨÎųŊ{a0HMM§UXááá�\Øī|Ū]đ^cÖ^zímŒŋ;â8ō†!t?/ƒÁáCøīKsIJŪÉ."čøéŨNQ ŪvvN¯ŊŊ€‡ũ ‹§§cú~âŊĨáíeaôˆë×õ-ųđú_x~ĩSÃ�..fēŸ×…~}zŌ&<ŒÔô=Ė}{~´mŪā:EDDęŖÉC —ÅÂą‚Ŗ”••a6›k\ĻāX^¯jĶ, Į ŸŨŨģ€Ūh0bˇÛëœgĩÚČÎÉaō”‡ķJĘĘčAYy9Įƒ€ÉdÂËbĄ´´ŦÖ~4´Æßķpw¯ļžÕf`íëØ°a#•••@UøŠŦ„ļmÛpį¸ÛXõÍjæŋģˆļmÃqÃđZ§ĮÄÄđîÂETVV’–ļ‹7\ן}ÎŅcĮؙšF˙‹.$33ë´}˛Ÿ_XXˆĮIAČh4TûÜTîēmt×úųúŨ1‚­IɌ=ĸŅļģ{ī>ŪZ°„!×\Iׄ¸jķúöėFīî‰ėÚķ o-X˜›ūNAĄ•ŖĮ ¸°īŠ˙˜ đcäđ!ŽĪĸ"ˆ‹‰f{JšB ˆˆœ3MÛˇkK W7Öūđ#ũ/ē°ÚŧīÖ|ŲՌ7Į ĒÍ+(( ,4¤ŪÛõķõ%ŧUĶ~đ”yë7nĸāØ1Įኊ 9Bp`P­í5v…x{{9~  iÛvVŽü‚Gž‚ŋŸļ"wN˜äX'ŽslÕŅÁŌ2VŦüŒ×Ū|›é˙~¤Öé~ūūüœ´ wÜÜÜčØ1’;SŲķË>îMIIÉiût⠜‡§EĮƒ*@yy………õę{cÚāéģ÷’˜ĮG+ŋäúÁW7¸Í=ŋėį­KøįM7ĐŽMkĮôCG2ČÉͧKl4FŖ‘¨ČDļoËδ]dde““Ë´™Ī`+*Â`0•“ËĐkŽ$˙Ø1Zˇ s´U^^ŽKGĮEDDšZ“?'°E‹\7t /åëoWs4˙(VĢ/W}ÃÂÅK "66†ėėlRRĶ�8xø0É);IH¨˙5S11ŅdÔĻÕjcÎŧ7ČĖĖ".6–Ŧė’w¤�°zÍZ^}u..U” ‹l§´×Ø5Ž^ŗ€Üŧ<vĻĻM^~>>>^øúøbˇÛYųų—ŠKŠYŊz ķŧ‡ŨnĮÕÕLXXK¨ŦŦu:@\L úŅQUp‰ęÉĒoV†›[‹ŗęStĮŽüzā ŋūz�€¯ŋ]]¯~7ĻōŠ ūˇč† ȰAIJN!m÷^�Ōví!7/˙ŦÛ,--å­K¸uäđjĀf+❅īķËū_ČČĘf×Ū}´ã֑Ùūđũ<ūĐ}<ūĐ}ôéŅ•ūũú0úÆadfįđÂĢo˛˙ĀAGm;ĶwĶĨķįŽxq>Íōˆ˜K/š˜ā @–}ø1 ŊĢŲLDD¸ī^Úˇk Ā„ņw°xņRŠKJ0š˜sËÍ´ %7¯~7xzxpĪø;X¸ø} ­V ũúö!8¸ęhß=wŨÁÛķPdŗÄíˇÁd2ŅŗG7Ÿū~ą~û#íeą4Z&“ 777yl:ÖBW\v)‘„…ĩdŨúL™ú^ ƒ˙v į%Äķės/ō¯)“Øą3•ÉSĻbr1ŋ ø&Z††Ô8 sįV|ö9×ŧ€¨ČH^ž=—ë¯|Ö} âīÇņėķ/ar1ҧwOBCCŠŦã”wcxqî[§ŽŸŅČs3§ņé_@|įĒgMt5ķ}ĀC÷ŨŒåŸÖzwpyy9÷?ōdÕĪŧķŪRæ/ú€—_‚ŋŸ/yųGyqΛÕÖšæĘKpŅų šöJŪyī qwsãĸžŊč[w˜‹hߖÁ×\ÁķcĩŲđ÷õå–Ãt¤[DD¤Ą cĮŽ­œ3gÎY¯¸%9VĄĩŸ>•š9ķ¨É"ąsĮs]†ˆˆČ_–äôzũ]7naaazmœˆˆˆˆ3RqB Í,$$Ø)O‹ˆˆČ‹B ˆˆˆˆRqB """"NH!PDDDÄ Õ;šLF*šøAÁ""""rĒŌ˛r\Í {įGŊC`€Ÿ/9šGEDDDšQiY9{ö"(AíÔ;Bļ � +'Š A93[’ĶĪu """jŽf‚üđmP; :ŽØ*$ĀEDDDäĪC7†ˆˆˆˆ8!…@'¤("""â„EDDDœB ˆˆˆˆRqB """"NH!PDDDÄ )Šˆˆˆ8!…@'¤("""â„EDDDœKCV>”‘MvŪQ**ėUˆˆˆˆÔÁÕėBp€Až j§Ū!đ`FyųĮ đÃdÔÅŋŠƒG˛HėÜņ\—!"""ĩ(-+gĪūƒTÁ ‚õ9yų €""""gāh^.™‡`-,Än¯¨6Īh4áiņ"¸e+|üüOۖĢŲ…mZ‘ž÷×s+*ė €""""§qč×_ČÉʤuģtđņÃd2U›_QQAÁŅ|ėۃĩđa­ÛļMWŗ Ĩeå ĒĢA׊ˆˆˆHíŽæå’“•IL|W\\jŽ]&“ _˙�,Ū>¤$mÆĶâ}FGJ‡ōDDDDšHæá´nץÖ�x2ÂÛv ķđÁf¨L!PDDD¤ÉX­…xųøņōŪž~ØŦMXŅoEDDDšˆŊĸâ”k�ĻOŸÎôéĶO™n2™¨¨¨8ezSĐ5""""Í衇bŪŧy�Øl6f˘qNęPi&S§NeŪŧyŦZĩ €`4k<*ØÔt:XDDD¤‰wz7--UĢVŅĨKētéÂĒUĢHMMu˝¨(¯ņôqSh–#Ų99,~˙RĶvQQQŽ››Ŋēwgčŋ5KGsķr™t˙Cŧ>įåfØÆ–‘‘ɃL㍚ŗĪu)ÕÜ7õ ĘĘO}NQtĮŒ3ēAmßûāc§L›1m înnėŨ÷+ī/_AvNŪŪ^ žúr:wŠjĐöDDD›§§Gķđõ`ɒ%ÕæwéŌĨÚ´‚Ŗųxxz5KmÍgĪ}Ž˚9WW3™™Y<˙Ōl\[¸2čÚĢ›Ŗ„?Ŋ  @ž›õôš.ŖFÆŪBĮˆöÚfYYåĖz|*nn-ĒĪ+/įĩw2äę+čžĪÎôŨŧ1>0/‹gŖÖ!""ŌÁ-[q`ß,Ūž§}LLyy9÷˙BxģÍR[ŗ„Ā2üēĄ¸ēšâūIwc6ģ:–IKOįŊÅKąÚl FFŪ8œ¸Îą�lų9‰Ĩ˖S\T„‹‹™ë¯LˇŽ‰dffņؓ3šlĀ%Ŧúú[f=õ$iéé,Zŧ”Ģ•Đ`Fß4ww7�6lü‰å¯ĀfŗŌģw/FÜpũ)ĩ:|˜'f<ÍÕW]ÉÎÔ4ōōŌ-1Ąƒ˙Vg-�ĢW¯aÅį_`49˙ü>Ŧ_ŋŋF\įØ:ûw2ģŨÎüīą-yƒ/‹…[FÂÕÅė8¸zÍZ>\ū‰c›ÍFL§h&Ū=ūŒˇĶ\Öü°u›63yÂ8 Ģžûž­I;˜4~ 3ž}™ūôĄoĪn§Ŧg+*Æ`0Đĸ…ë)ķŌvíÁÃ͍]�ˆ‰Š¤mx+ļ%§ĐˇW÷&ī“ˆˆČ™ōņķĮZxŒ”¤Í„ˇí€ˇomo Éãāū_đ ÂĮˇé Í{õėÎkoŧɕ—_FLL4aĄĄøúūöŽģÂB+/ž<‡Üz3‰ ņ8p€é3ŸáéãæÖ‚WįĖ㞠w͉Ÿ6oaÎëo1;!“‹‰ĸĸbĖŽf^xn…ŧōę<&Oš@dDŸõ5ŗįĖcŌÄģ0 ÉČ`æô““›ËŋĻ>ĘÅö#ŦeËjĩšŒ'ÚteōŊwcĩÚø×#ĶčKģļ­k­%˙čQŪYđ=ō ááôæļ�� �IDATá,˙x™˜\\ęėŸwĩío۞LjÚ.f>ņ..&Ö­ßČæÍ[éŨŗ‡c™‹.čĮEôĒNĩ?1ã?\}ÕgĩæŌ¯OļnßÁw?Ŧ'!.–¯žYˤņc0 \wíUÔ¸^QQ&“‰7æ/â—ũ°xzréÅũčv^22ŗ Ŧļ|pp G2ŗ›ŖK"""g%Ŧu;<-Ūd>Čž=iØ÷ŖÉ„§§áí:4[�„f ˇÜ4ŠׯgãĻÍ,[ū&Ŗ‘ž=ĒŽ ôp÷ iûv, ‰ ņ�„‡‡ɖ­?sņEđė3Oáéá@\įXJKJČËĪĮh4`ˇÛ9ŋw/ É))„„ĀĨũ/ĸoīž”••RYYɀūc0 ĀßߟÜÜŧSBā ũúöĀĶ̓NQQėLM%:*˛ÖZRSĶiNxx8�¯ŧœ>ųā´ũ;™¯yųyŦÛ°„ø.ôîUū222OŠąŦŦŒ—_™ËĩW_EĮŽ‘ü°nũo§1ŊôÚÛ †jĶFŪ0„îįÅc05|˙}i.IÉ;šbĀEVŋNQĩļéâbĻûy]č×§'mÂÃHMßÃܡāGii)fŗšÚōŽf3ÅÅ%ß9‘FS •5OŽöŋͤYB Ņhāü>Ŋ9ŋOo*++ŲûË>.^ÂÜ×ŪdâŨãąZmdįä0yĘCŽuJĘĘčx<Ė­ũa6l¤˛˛¨ •' ”ÅRueaĄã Ǐčeą›— T:GM#vģŊ֚=ÜŨ?ģģģaĩŲęŦĨ°°Ī“ŽG3›Íx{UÕuēūŦmÛ6Ü9î6V}ŗšųī.ĸmÛpFÜ077ˇS–ũ߂÷ kÉĨ—\|ÖÛiLwŨ6ēÎkũ|}ˆîÁÖ¤dƎqFmø1røĮįNQÄÅDŗ=% w7JKËĒ-_RZZãŠc‘síĐ¯ŋ“•IëvčāSÛéā|ėۃĩđa­Û5K]MķōķIOßMĪU×} :´oĮĀ+.į­˙Ŋ €Ÿ¯/á­Â˜öđƒ§ŦŸ´m;+W~ÁŖOÁßĪ[‘;'LĒļˉƒPŪŪ^;æ˜^QQÁĄ#Gđp?5@NAA!ŪŪ^ŽŸęŦÅŨÛ­Čą~yy…§í_Mâ:ĮVe,-cÅĘĪxí͡™pĮ¸jË|ģz ûöíįĄŨī˜vļÛi.û"}÷^âøhå—\?øô7’ė­[…9Ļ•——ãb2˚7V[ūđ‘LzuOlôÚEDDâh^.9Y™ÄÄw­õƓɄ¯�oR’6ãiņÆĮ¯éO 7ųsív;ķŪz‡/W}CIi)�šyy|ŗz 1ŅŅ�ÄÄD“•MJjPuDkÎŧ7ČĖĖ"/?/|}|ąÛíŦüüKŒFÅ%ŧl+.6–Ŧė’w¤�°zÍZ^}u†ßĒ<Ģ×ŦuÔē35•ؘč:ké؁}û÷säH�+ŋøƒÁxÚū˛ŨÕk˜ŋā=ėv;ŽŽfÂÂZV?ė ėŪģ—e}Â]wŪN ×ߎ~ÍvšKyE˙[ôà dØ $%§ļ{/PuƒGn^~ëefįđÂĢo˛˙ĀAĮ˛;ĶwĶĨs':Fļ§ŦŦŒ7nϞ˛’m;R9t$ƒ„Î1ÍÖ/‘3‘yø�­ÛupĀë¯ŋžmÛļ9æoÛļë¯¯ēQÕÅŅđļČ<|°Yjkō#ūūL™<‘—ÂG¯ ŦŦwzôčÆā㏇ņôđāžņw°pņûZ­ úõíCpp/OÖ­ßČ”Šāeą0øo×p^B<Ī>÷"÷NŧĢÚļ<==¸įŽ;x{ūŠl6‚‚‚¸ũö1g]ŗÉdÂÍ͍G›ŽĩĐÆ—]JdDaa-k­åņ?ĖĐÁ×ōŸ˙>§Åƒ ûŸŸ/†Ķôī÷ē÷čÆŽŠLž2“‹éøŨÁ7U[fŧŸS\\ĖĶŗūë˜æææÆôĮ=ãí4ĻįžuĘ4“ŅČs3§ņé_@üņ€6lĐÕĖ_ôŨwK–ZëŨÁíÛ2øš+xcūbŦ6ūžžÜ2baĄ!�Ü6úF/û„e†ŋŸ/cnú;î§´#""r.Y­…tđņs|ŽŠŠbĀ€ÕŪ2vėXĮ|o_?öīIk–Ú cĮŽ­œ3gÎY¯¸%9VĄM.΅†<”Ųn¯Ähüí¨ãøģ'ņā“7‹ü<’EbįŽįē ‘ŋ„ÍëÖĐĩwõ›3O~wđ˜1cNywpMëÔdKrzŊūf7ް°0Ŋ;¸ą3遚{ütŠîČO›ˇ`t1rŽK‘?3f8nd}øá‡ĪY ÄÍ͍Œž™ˇŪ™OyyžÜ9î6Į˛EDDDN8—áī…Āß Ž÷ûyģwK¤{7ŨĄ*"""UŒ&§<Ļ6ågŧlC5ųŨÁ""""ÎĘĶĶ‹‚Ŗygŧ|ÁŅ|<<Ŋš°ĸß(Šˆˆˆ4‘ā–­8¸˙ĘËËOģlyy9÷˙BpXĢf¨L!PDDD¤ÉøøųãHJŌfōr˛ŠøŨ{ƒĄęåųšŲ¤nߊ_@PŗŊ?X׊ˆˆˆ4Ą°Öíđ´x“yø ûö¤a˙]4šLxzzŪŽCŗ@h@4™ŒTØí˜Œ:˜("""R?˙F}\iY9Žæ†ËĢw‚ đķ%'÷(v{ƒ ‘3WZVΞũ‡ đ;ũÂu¨w„l�@VN ‚%[’ĶĪu """R Wŗ A~ø6¨Glāƒ""""ōįĄ úDDDDœB ˆˆˆˆRqB """"NH!PDDDÄ )Šˆˆˆ8!…@'¤("""â„EDDDœB ˆˆˆˆRqB """"NČĨ!+ĘČ&;ī(öÆĒGDDDDęājv!8Ā �ßĩSīx0#‡ŧücøa2ę€bS:x$‹ÄÎĪu"""ōPZVΞũŠ‚ëŪrōō đ÷U�iFŽf:´iEVN^ƒÚŠw‚̍°+�ŠˆˆˆœŽfJËĘԆRœˆˆˆˆRqB """"NH!PDDDÄ )Šˆˆˆ8!…@'Ô 7†œŠėœŋ˙Ši썍(ĮÍ͍^Ũģ3tČß0™LMžũÜŧ\&Ũ˙¯Īyų´ÛËČČäÁGĻņÆÜŲM^WSšoꔕŸzÛxtĮŒ3úTôĮõčŒ˙rĶ CéŅū\—Ōh˛˛sybÖķŧđôc në¯8>""RĨYBāėš¯Ķ1"‚Y3§ãęj&33‹į_šk W]{us”āt&ŒŊE¸EDD¤VÍ8Čđë†âęj 88ˆû'ŨŲėęX&-=÷/Åjŗa4yãpâ:Į°åį$–.[NqQ..fŽŋn0Ũē&’™™ÅcOÎ䲗°ęëo™õԓ¤Ĩ§ŗhņR ŦVBC‚}ĶÜŨŨ�ذņ'–ŧ›ÍJīŪŊqÃõĩTlāëoWŗōķ/)))åÂ~}6tpĩØívæ/xmÉ;0 xY,Ü2z­[ĩĒŗoįš6°nĶf&O‡Á``Õwßŗ5i“ƏaÆŗ/Ķ˙‚>ôíŲ­ÆuSĶwķá§_PRRŠ‹ÉÄĨ÷Ŗgˇķ8t$ƒYĪŋĘs3§9–}xú,n9œˆömk­ĨŽõÚˇmÍûË?eGj:X<=šqØ ÂBCØŊwË>ų [QFƒ‘ë $&*’ėœ\fŊ0‡‹ûõfõ÷ëyüĄI¸ēūö=û~ũ&žøú;Ėfãã0 Žy;RĶYöņgXm6‚ƒųûĐk Žs,kĢŖŽ1Ūž’Æ'Ÿ}EQq1... x9 q1ääæņô˙Ífā嗐’šÎáŒLŽžü2˛˛ŲāšyųŒ>„íڐ‘™Í3/Íáōū’ž{/ųG‘ÃÕW 8ãkŌØãSÛ÷ĨĻũ´iK_~ģŖŅ@¯n‰ü´uCŽš’NQunCDDę§YB`¯žŨyí7šōōˈ‰‰&,4_ßßŪuWXhåŗįđ[o&1!ž0}æ3<=ãqÜÜZđęœyÜ3áNbc:ņĶæ-Ėyũ-f'Äcr1QTTŒŲÕĖ ĪÍĸ  W^ĮäIˆŒˆāķ¯žföœyLšxƒ#˜ūorrsų×ÔGšøÂ~„ĩlyJŊvģœė\f͜NfV6Ķ{’.qq´kÛēÖZļ'ī 5m3Ÿx ëÖodķæ­øųøÖÚ7īæūSôë̓­ÛwđŨëIˆ‹åĢoÖ2iü ×]{AA5ŽWhĩ2÷테ŊeŅ‘ČĘÎáŠį^!ŧÕŠcx˛M[’X´ėãSĻ2røāZ×KIÛEúž_xäūģ1™Lü´uIÛSđõöæĩˇ2røēÄFsčHĪžüĶ˜ˆÉdĸ¸¤ŗŲĖĖiSĒ…˜ŧüŖ,ųpÜs;aĄ!ü°á'ōō:úöÆüEŒ3šöm[ķíÚyķŨ%\Ö˙‚Zk?ææZë¨mŒËĘËyķŨÅÜ~ëHĸ";đķöŪYø>ŗž˜ŠŅh¤¨¸‹§wüķ&6nų™ų‹–1ūļŅüíĒËXõŨ÷|ųÍÆŨ:ŖŅHqq fŗ wŽš[QOüį:EEâíåå¨Ķf+ĒĩF//Kĩ>5öøÜņĪQĩ~_ÜŨÜĒí§üŖĮX´ėĻLŧƒ°Đ>ûę[2ŗ˛1™tŲ˛ˆHSi–xËMŖøqũz6nÚ˞åa2éŲŖęš@w’ļoĮbą˜@xx8QQ‘lŲú3_tĪ>ķž�ÄuŽĨ´¤„ŧü|ŒFvģķ{÷Â`0œ’BHh0‘UG.í}{÷¤ŦŦ”ĘĘJôŋƒÁ@`@�ūūūäææÕ.ŋô�‚ƒéÔ)Š´´tĸŖ"k­Å×Į‡ŧü<ÖmØ@B|z÷ęĀëÖ×ŲˇĻōŌkoc<)�ŒŧaŨĪ‹Į`00jøūûŌ\’’wrŀ‹ Ŧ ~uuŲ™ž› @ĸ#;�@§¨HvėL'ļSĮZ×ëžO÷Äøį:’Qëz>^^=zŒM[’ˆ‹ĻÛy]�ظųg,žt‰ ,4„ˆvmIÚą“Νĸ°ÛíôčšP-�¤īŪKː`ÂBC�čĶŖ+K–}rRßhßļ5�öíEŽ xzxÔZ{]uœßĢ{­cüäÃ÷;ŽNĮDGRZVÆŅcŽzģÄv 80�“‰¨ã§õƒØ´%ŠZ Ŋē%āáîNĮˆv¤īŪKˇķ~Ģ7ygZ56åølښTë÷Ĩ{b|ĩũ”ž{/áaĄŽm_zq?V~õmíŠˆHãh–h48ŋOoÎīĶ›ĘĘJöū˛…‹—0÷ĩ7™x÷xŦVŲ99Lžōc’˛2:skXĮ† ŠŦŦĒūPVVūÖžÅRuäŖ°ĐŠĮņ€`2™đ˛XČÍËĀĶķˇyFƒģŨ^kÍ^ŪŋĨķ˛xRhŗÖYKÛļm¸sÜmŦúf5ķß]DÛļጸaøiûÖTîēmt×úųúŨ1‚­IɌ=âŒÚ,(°bņôŦ6ÍĶ̓‚ÂÂÕZ›đV-ųĮ¨øî‡õ,Yž‚đ°– ûÛ@lEEääåķčŒ˙:–-++§Cģ6ŽĪ–“öõ V› wĮgƒÁāølĩÚđp˙mžŅht„ũÚœŽŽÚÆxũO[øiëļjß!{e%Ļã!°E WG}'~>ņšŌ~ŌpskņÛĪ-Z`+*:ĢOÖØãs&ߗûÉjŗUkĪÅÅ/KõuED¤q5yĖËĪ'=}7={T]cf0čĐž¯¸œˇū÷.�~žž„ˇ cÚÞ˛~ŌļíŦ\ų><??lE6îœ0ŠÚ2'øx{{Qpė˜czEE‡ŽÁãøQ—ŗaĩYņ˛T.;VPHPpđik‰ë[ut°´Œ+?ãĩ7ßfđĩ×ÔÚˇsi˙C¤īŪKbB­ü’ëŸūo ……ÖjĶ ­„b4ąWV(Å%%@Ũ§ƒoúûĐZ׃Ē#“ĸ"(+/ዝŋã‹>`āeũ áūģĮŌfūŅĒũ˙ûŖ€�îîî;>WTTPhĩāeŠŪ7ģŨΑĖ,:Ââ?ŠąöËû_PkPķÎWߎeō„qøúxST\ĖÎ¨qũ3aĩŲAĢĐjÃß߯Ú|_oī:k<YcĪ€‹úÖú}9áÄ~rwsÃVËļED¤i4ų7vģyoŊÃ—ĢžĄ¤´€Üŧ<žYŊ†˜čĒST11Ņdeg“’šTu˜3ī 23ŗČËĪĮĮĮ __ėv;+?˙ŖŅ@qIņ)ۊ‹%+;‡ä)�Ŧ^ŗ–W_Wc 8oW¯qÔēsg1ĸëŦeõę5Ė_đvģWW3aa-Ą˛˛Îž+åüoŅ 4aƒ’”œBÚîŊ�¤íÚCn^~ëEwŒ '/ôãËÉĖ"5}7q1Ņøx{QYYIvN�Ûv¤RZZTžõøÔSūģ˙îquŽ÷ÃúMŧŋ|vģŗ‹ -ß„؁ėÜ\Gļĸ"ŪY¸ÔŅÆÉ~ŪžBFf6�‘íÛrđđ>š7:–ëAvnŠéģoû'ۜŋ˜]j­ŊŽ:jãŖĮŽáeąāãí…ŨngÕęī1 ”œ|ĪÆ÷ëĒÂoúžŊŽS¯'œnŦšr|ęúžü^ûvmøõā!2ŗr�ã"""M§Éøû3eōD>\ū }ŧ‚˛˛r<<ÜéŅŖƒ?ÆĶÃ{ÆßÁÂÅīShĩb0čסÁÁAXŧ<Yˇ~#SĻ>‚—ÅÂāŋ]Ãy ņ<û܋Ü;ņŽjÛōôôāžģîāíų (˛Ų âöÛĮœUŊö ÜŨŨpssãá?ÍZÄUW\Fd‡ö„ĩ Šĩ–M™ÄŽŠLž2“‹éøŨÁ7ÕŲˇĻôâܡN™f2ynæ4>ũâkB‚ˆī°AW3Ņ<tß],Yūi­w{zxpÛč|¸âsJJJ1™LŒēa!AUGv^~ ŗßx‡�?ÚˇiMčôŅŨÍ­ÖõããHŨĩ‡i3ŸÅd2aņôāÆaƒpwwcÜ-#ųāã•Xm6 Ŋēw%0ĀĪq$đ„•_~Ã}{H`€?C¯Ŋ’Ų¯˙“‰î] Ä^Y‰‡ģ;coÁĸ>ĸ¨¨˜Ā�n5ŧîÚë¨ãŖ•_Ö8Æ÷ŽÃĻ-I<öô˙aņô`āå—Đ%6šWæŊÝcn>íxĖh4âÖ•§ž{ĢÍÆ%ôĨ}ÛÖdeįžQM=>u}_~ŋŸB‚šæŠŧ8÷M<=<čĶŗ+ž>^ ‚""MČ0vėØĘ9sæœõŠ[’ĶiÚ´AFĒ<’EbįÚoŧÚũ¸qķņ#:ëRUc=ú4>•••ÕBßĶf0ņŽ:n‘ęļ$§×+Œ7ް°0Ŋ6NūÚ\Íf:ÕōL<ųãŒOII)SĻÍdמ_€ĒĶÔ&ŖÉqGĩˆˆ4žfš;Xä\9ņXŠŲe|Z´peäõƒYđūrĘËĢ.ųĮ¨á˜]ô+JD¤Šč7ŦȟPP Ŗŧø$ĄK, ]ÎŨ›tDDœN‹ˆˆˆ8!…@'¤("""â„EDDDœPŊC Éd¤ĸŽwˆˆHĶ(-+ĮÕܰû{ëü|ÉÉ=Ē ("""ԌJËĘŲŗ˙A~§_¸õސ­BĒ⚕“GE…‚`Sےœ~ŽK‘?�Wŗ A~ø6¨Glāƒ""""ōįĄCDDDDœB ˆˆˆˆRqB """"NH!PDDDÄ )Šˆˆˆ8!…@'¤("""â„EDDDœB ˆˆˆˆRqB """"NČĨ!+ĘČ&;ī(öÆĒGÎŌO?mbێTėö?į>0t‰fĖÍ7žëRDDDœJŊCāÁŒōōā‡É¨ŠįÂ; ßgßžũ`8וԟŨnįįm)ŧööBn­ (""Ō\ęŪrōō đ÷U�<‡ö˙úëŸ:�:`{JꚎBDDÄŠÔ;ÁUTØ�ĪąĘĘĘs]BŖųŗžÎųŗRŠqB """"NH!PDDDÄ )Šˆˆˆ8!…@'¤("""â„EDDDœPƒ^wϞsrXüū¤Ļíĸĸĸ777zuīÎĐ!Ãd25ųösķr™t˙Cŧ>įåfŲžˆˆˆČ]ŗ„ĀŲs_§cDŗfNĮÕÕLffĪŋ4׎ ēöęæ(ADDDDNŌ,!đƒ ŋn(ŽŽf�‚ƒƒ¸ŌŨ˜ÍŽŽeŌŌĶyoņRŦ6Fƒ‘‘7'Žs,�[~Nbé˛åáâbæúëĶ­k"™™Y<öäL.p Ģžū–YO=IZz:‹/ĨĀj%4$˜Ņ7ĀŨŨ € bųĮ+°ŲŦôîŨ‹7\J­vģų Ūc[ō ^ ˇŒEëV­HÚļũ”ļ[……‘ŧ#…ÅK—QR\‚ÉҁW\Æų}{sčđaž˜ņ4W_u%;SĶČË?JˇÄ†ūÛiûÜĸ#;0čęËiáڂōŠrV­ūž ?m%ĀĪ—)īäãĪž"&ē#AūėLÛÍŌ> Cģ6 ŊöJ<ÜŨąÛ+y˙ŖėLÛŨduŠˆˆHĶk–ØĢgw^{ãMŽŧü2bbĸ Å×××1ŋ°Đʋ/ĪáˇŪLbB<`úĖgxzÆã¸šĩāÕ9ķ¸gÂÄÆtâ§Í[˜ķú[ĖNˆĮäbĸ¨¨ŗĢ™ž›EAA!¯ŧ:É“&Áį_}Íė9ķ˜4ņ. G22˜9ũßääæō¯Šrņ…ũkŲ˛Z­Ûļ'“šļ‹™O<†‹‹‰uë7˛yķV|ŧŧklûûî兗_åžģî 6ĻGŽdđč3hÛ&ŗŲõx}ŽLž÷nŦV˙zdcciÖ˛Ö>ûøx7ú>°xzpÛčĖ}{iģöčĪŋ&ŪɁƒ‡ąáæÖ‚J*™ûÖģ¸šĩ`Æ#°æĮõÚ¸môŪ]ŧŒí)Š„…†pīø1<ūô˙QPhũmwOdÔđ!Õļš~Ķæ/^Öč}‘†k–xËMŖøqũz6nÚ˞åa2éŲŖęš@w’ļoĮbą˜@xx8QQ‘lŲú3_tĪ>ķž�ÄuŽĨ´¤„ŧü|ŒFvģķ{÷Â`0œ’BHh0‘�\Ú˙"úöîIYY)••• č1ƒĀ€�üũũÉÍÍ;%úúø—ŸĮē HˆīBī^=�øqũ†ÛŪžŧƒ bc:B\l ?oOĻ{b"�ũúöĀĶ̓NQQėLM%'7§Î>7ļčŽdåäļk�YŲš¤¤í&ļSG6mIÂ`0°yëv�Š‹KČ?Z€Ÿ¯/mÃÃąZmlOIāБ öėŨO—Î1ü°~“Ŗũõ›ļ�8‚  ˆˆČ[ŗ„@ŖŅĀų}zs~ŸŪTVV˛÷—},\ŧ„š¯ŊÉÄģĮcĩÚČÎÉaō”‡디•ŅņxāZûÃ:6lØHee%`� ˛ōˇö-/øöî<,Ęz˙˙øsf�Y†uXPT$÷ĨŦ´,Ë\ĶŌĘę˜Vf‹i}ÛO{ÎŠ_ĢižŌÍ]3ŗĖ%s7K%Qˇ:nėČ2Č6üū@QdIA š×ãēÎu ÷ōūŧ?įōÕįžīĘV]O‡E�“É„ģŲLFfPÂĘ{2ąŲl•z iÁũãîa՚ĩ|ūÅBB‚9bxĩĩs˛sp?=ūŲ~ĘļŸáęâRūÚÅř<Ģ×?˜ķĨæn6“›k­°-ĪjÅlv+˙ųTAAųk[Š ŖŅ€ĢĢ3>>^<˙ÄÄō}ŽŽŽ<ü[Ĩ1ÎÁ°Ö-�EDDūäę=ffe‘”t€.;`0hŨĒ%ũû]ËŒĪž�ĀÛˋā @ž{ú‰JįĮ˙ē‹åËWđėĶããí5ßĘũ&V8ÆP– ņđp'';ģ|{II ĮNœĀõô=*ē]T؊ca˖ËGŸĖdĀ ×WYÛĶ̓뜜 įįääØ4✟sņđp/íkąÔ8įú“ƒû9ĀėæFrJjįeĖæØņdūũîÔ gËļíåaPDDDūŧęũsm6Ķg|Ę÷ĢÖPPX@Ff&kÖŽ#2"€ČČRĶŌØ“¸€ŧ<+S§LJJ*™YYxzēãåé…Ífcųwßc48UpĒŌXŅQQ¤ĻĨ“°{�k×­įçc8“/ĀÚĩëø|֗Øl6œœ lĨĨÕ֎ŠŠ$íœŪ?NžŊÄÆļ?[sŨúōyīML$*2ĸÆ9ׇŊIđņņ"ŦuK�ü}iÖēü2ouöí?„¯Åģü<gî¸e(īzéSDDDFŊ¯Z||x|ŌÃ,^ō5_-]FQQ1ŽŽ.tîܑA§?ÆÍՕ‡ÆßĮėšķÉÍËÃ`0ĐĢGwüũũ0ģģąyËO<ūÔ3¸›Í ēéF:ÄÆđæ[īōČÃTËÍ͕‡¸™ŸĪ"ßjÅĪĪ{īsQũvęܑŨ{™ôøS˜L§ŸžŊÚÚîf3ÆßĮÜš 8UP€ÉÁĘ;ī YĶĻ$'§`2™pvvæ™į_"/×Jŋkú–b¨�� �IDATßWXŨœëƒÕšĪG3g3čÆ~åO>w)ŠéxÕđ JūŠSL›1‹!ŽÃÍՕŌŌR6oÛNzFfŊô)""" Ã0vėØŌŠS/ėRßšļ'$Ô´~ËßErr O<ķO›R/õ_|ũÍzŠÛXŪũ× Ũ‚ˆˆČßŪ¸qã Ôׯ‰ˆˆˆØ#…@;¤XüëíR°ˆˆˆH](ŠˆˆˆØ!…@;¤("""b‡EDDDėP­C Éd¤¤ŠīŪ•tá_„ō§w1ßę""""uWëhņö"=㤂`# iŪ‚RJģ:+Ĩ”öQmģ ģR믍 °�šžII‰‚`cčsUoļmÛÆ¯ģ÷RZú× ƒƒ˜¨Hî}kcˇ"""bWęôŨÁA–ō0(#Ž]Xcˇ """Az0DDDDÄ)ŠˆˆˆØ!…@;¤("""b‡EDDDėB ˆˆˆˆRąC """"vH!PDDDÄ)ŠˆˆˆØ!…@;¤("""b‡ęrōąä4Ō2ORRbģTũˆˆˆˆH œđˇxãgņĒSZ‡ĀŖÉédfeãgņÆdԂb}8z"•¸vaŨ†ˆˆˆü‰sđ÷Ŗ”ūu‚ĩNoé™YX|ŧ�EDDD“Ŗ­[‘šžY§:ĩNp%%6@‘Fāäč@aQqj(ʼnˆˆˆØ!…@;¤("""b‡EDDDėB ˆˆˆˆRąC """"v¨N_wĄŌŌĶ™;!‰ûöSRRŒŗŗ3];ubČā›0™Lõ>~Ff'?ɧžAã-Xŧ„ÕkÖŌˇĪUôčڕ'žyŽ§M!99Ĩü5ĀĻÍ[‰ëƒŗŗs}Oá‚<úԋWūĖ ˆ°ÖŒ3ē^ÆLMËāÅ7Ūæ×Ÿŋ¨}ę§í;‰‰Š¤I§ōmÛvÄŗæĮM?‘ŒÉÁDķĀfôëĶ›ˆ°ĐK2fmŊöÖüß#÷7ø¸"""ĩŅ !pĘ´˙Ɲž„““#))ŠŧũŪœš81pĀ ŅÂEŲŧå'Æūã.bcÚcŗŲxë×Ģ<nÁĸÅDĩøĶ„@€ cī$,´Ucˇ€ÅĮ‹—Ÿž\§K—¯$ĸMhy\ŗn#Ëŋ˙aûÁ``ÛöxĻ~ōãîž ¯ē}bmŦüa=nŽ.8::˛qëĪpÕå=ŧ‘‹Ņ !đȑŖ :''G�üũũ˜<ņAĪŽîėKJâËš ČŗZ1ŒŒēu8Ņíĸ�Øž3ž‹–p*?Gn:ˆŽ—Å‘’’Ęķ/ŋĘ5}ŽfÕęxãĩ—Ų—”Äœš ČÉËŖi€?Ŗo‰‹KYHÛúĶĪ,Yē Ģ5nŨē2rÄ͕z}įũ)¤§g0ķŗŲ\ŲûtíÜŠÂęßoŋ7…´ô ^ū×ŋuËpbcÚW;‡ĒútvnR_owĩÖmÜĘæmŋ0iÂ8 Ģ~ÜĀŽøŨL?†WŪ|ŸĢ.īN.Ģ<71é�‹ŋYAAA!&}¯ėE—Ž�0 ŦÛ´•Uk7PXXH÷.p]_Ō3˛*ŦĘ8ô‹žūk~>Fƒ‘ĄûŪ€Ũ‰I,Zú-yV+ū~žÜ2d�_-_IfÖIŪú`:Ãö§MĢ–|ũŨjnzâbĘ{ģŧGŧŧ<1ģšVęû×Ũ‰|ũíJōOÂÁÁũ¯%6:›ÍÆü%ß°;1 ƒĖnnÜ:l MũũĒÜØ4 Ú÷õ˛Øh6ũô ‡˙Ņ‘áåī‹ˆˆČŸYƒ„ĀŽ]:ņŅĮŸpŨĩ×A`ĶĻxŗb“››ĮģīOåîģî .6†#GŽđŌĢ˙æõW^ĀŲš NÎCî'*˛-?˙˛Š˙Á”ØL&ōķOáčäČ;oŊANN.|8I'Đ&4”īVŽfĘÔéL|ø '’“yõĨ’ž‘Á˙=õ,W^Ņ‹ĀfÍ*ôúāøû˜ôø“üãî;‰Œ'99ĨĘ9Ũ7v cīŸĀSMÂĶËŗÆ9œß§Á`¨ĪˇģZŊēwfĮŽŨü¸q ąŅQŦ\ŗž‰ãĮ`0:āzüü,Už—›—Į´™ŗ{įH"Ú´&5-×Ūú€ā f8:8bŗŲČČĖâŸ˙÷é™ŧöÖDE„ááî^^ÃjÍįŖ™ŗ5|0íŖ"8v"™7ß˙ˆį{ƒŅĀĮŸĪaü˜Ņ´ iÎë7ņÉķ˜ôā8&>ųÜ?w3ûĸ¸¸˜ËbŖ+õØ>*(ģ}FaQŸ|1—{īEx›ÖėÜĩ‡OgĪįŸbĪžũ$<Ė3“Äd2ņķŽ_‰ßĩ‡Ėā“Un?v<™9‹–V×ßחG¸‡‡~côČalũy×^}E]U"""õŽABāˇßÆĻ-[øiÛ/,Zō&Ŗ‘.Ëî tuq%~×.Ėf3qąeĢ;ÁÁÁ„‡ˇaûŽ\ŲûrŪü÷k¸š–­ōDˇ‹ĸ° €ĖŦ,ŒF6›žŨēb0HØŗ‡€Ļū´  īUŊéŅ­ EE…”––ŌįĒ+1 øZ,øøø‘‘Y)ÖVMsˆ‰iWĄĪúôŪG31ž7ƨƒéÔ!ƒÁĀmÃķŸ÷ĻŸ°—~}zãį[üچ‡V[soŌü|}ˆhĶ�?_ mÃÛ°{oąŅeĢĩg.Z|ŧ mÅūƒ‡š,ļ}y„Ŋû0ģš–‡ĩĀĻ„ļ !~÷^š4qÂĪ×B̐æ�\ŅŖ+/‹­Ô‡ÕšģŲ ã~gĩ“Ŗ#/?=š|%82ĸ …EEœĖÎÁĶŨ“'ŗŲļ=žč¨:v(ëõČŅãUn*Ŧ>žĢ¨¸˜>Ŋ{ŌŽm8Mœœ°Ųl r¯ĢˆˆH]4H4 ôėŪžŨģQZZĘĄÃŋ1{î<Ļ}ô ?8žŧ<+iééLzüÉōs ŠŠ;æÖoÜĖÖ­?QZZ ”œŌŌŗõÍæ˛§ÜÜ<\]Ī^4™L¸›Ídd–­šsšĐh0bŗŲ.Ų˙hįöYŸ¸gt÷z{yʎøƎyA5srō0ģšUØæææJNnnųĪîfˇ ûōŦųގæį“ž™Åŗ¯ü§|[QQ1­[ļ ¸¸W—ōíFŖ7W×Jš˜Ũ\ÉÎÉĨ¸¸‡ ûĶŨōķv~Ūņk…ŋ[i)ÁA͸ûļü¸q ķ–,#8°Ãnę_ãöę8:8ĐŽm8�íŖÚ^P_"""­ŪC`fVIIčŌšė^3ƒÁ@ëV-éßīZf|ö�Ū^^ōÜĶOT:?ū×],_ž‚gŸ~ooŦųVîŸ0ąÂ1gž<<ÜÉÉÎ.ß^RRÂą'puŠ˙7jšÃ™ÚHW+øũČ1’".6š¯–Ī̓ūøÁO3ššyļåææāī[ūŗ5?ŋ|ĩ6//?‹O…ãŊ<<lĀäĮUĒ˙ËÎ]ęÛl6N¤¤â{^̓hâäÄæmÛéÕ­s…}›~úGGB‚ƒËˇíŪ›ÄĘÖ3iÂ8ŧ<=Č?uŠĮž}Ĩ|ÛđPچ‡RT\ˊÕ?ō؜…<1q|•Û¯šęōj/W5'‘?ģz˙œ@›ÍÆôŸōũĒ5‘™Éšĩ눌(ģ4AjZ{÷eĢjS§LJJ*™YYxzēãåé…Ífcųwßc48UpĒŌXŅQQ¤ĻĨ“°{�k×­įç×Ë%X“Šė­Ë͡ūáū,ŠKJølÎB† ėΰũ‰OØÃž‡�Øˇ˙ ™YUžJzf&I§=‘’JbŌĸ##ʏYŋy�Y'ŗŲwāá§/ŸŪĻ5iå5Ŧųų|:{i陴 %-#“Ĥ�lÜō3Ÿ|>‡Ķ—T­ųe̊NŽŽÜx]_-ũ–õ›~";'k~>k7lfáŌåøúT 'ŗŗq7›ņôpĮfŗąjí lܲųK–aŗŲptp Y€˙éąĢŪŪ).†7^xĒŌ˙�EDä¯ĒŪW->><>éa/ųš¯–.Ŗ¨¨WW:wîČ ĶãæęĘCãīcöÜųäæåa0čÕŖ;ūū~˜ŨŨØŧå'ęÜÍfŨt#bcxķ­wyäá*ŒåææĘCÜĮĖĪg‘oĩâįįĮŊ÷ŽŠ—y™L&ētîČ /ŊÆ-ÇrUī+ĒÃ™•†đî´•{5yëÕįøfÅjü,Ä´‹`ØĀø|ÎBž|ôæ-ųĻÚ§ƒŨ\]šgôH/ûŽ‚‚BL&ˇL€Ÿ/É)i8;7Áš‰¯žų>Öü|úôîEËÁŌpqqfܝŖX¸t9yV+ƒŽ.Ã×â ĀØ;G2gáWäįŸÂ×âÃ]ˇ Įh4ÍīLeđũčÕ­3Ŋ{vÅĪׇeß­bÁŌå8::ĐĒEs&ŒŊ‹ÁÆėĶŽmÛãyūõ˙‡Ų͕ū×^Mû¨>˜ū)ß÷÷äšWßÄd2avsåÖa đķ­r숈ČߍaėØąĨS§NŊčˇ'$ÔÔ¯Z’3ŽžH%Ž]XcˇQkųÁÍ"""wÛ’j•ƍG`` ž6NęOn^ÎMūķEDDä5ČĶÁbö$îį“YsšĸG—ÆnEDDDĒ (õ"2ĸ ˙zūÉ?>PDDD….‹ˆˆˆØ!…@;¤("""b‡EDDDėP­C Éd¤ä~÷ވˆˆˆ\˜Âĸbœëö|o­C ÅۋôŒ“ ‚"""" ¨°¨˜ƒŋÃīôˇnÕV­#dP€€ÔôLJJëËö„¤ÆnADDDūDœđŗxãoņĒS:­#XĘàˆˆˆˆüučÁ;¤("""b‡EDDDėB ˆˆˆˆRąC """"vH!PDDDÄ)ŠˆˆˆØ!…@;¤("""b‡EDDDėPž;øXri™'))ą]Ē~DDDD¤NŽø[ŧņŗxÕŠN­CāŅät2ŗ˛ņŗxc2jAņīæč‰Tâڅ5v"""ržÂĸbū~”RĀŋA°Öé-=3 ‹— ˆˆˆHrrt u‹ RĶ3ëT§Ö ޤÄĻ�("""Ōœ(,*ŽS Ĩ8;¤("""b‡EDDDėB ˆˆˆˆRąC """"vH!PDDDÄÕékã.ÆĻ-[Yņũ*Ž;†Éd"¤E Üp=íĸ"INNá‰gžããiS*ŧŽĶx›ˇ×!ggį*÷?ķüK\Û÷j.īŲ€ąãĸ¤¸ŖÁ@į&øųúŌŠĶeôëÛSzi(4įúôčS/RT\ųķŠ"ÂZ3~ĖčK>^jZ/žņ6īŧū|…×uņĶöÄDEŌ¤‰Ķ%ęōŦĩļ°xŲwÜ6|0;´/ßž˙āa}ũ-Ši¸ššĐ¯Ī•tëĀŽ=‰,]ž’ĖŦ“x¸›šîš+éÔ!ĻBŨÜŧ<^zã]ŽčŅ…ū×^}Éû‘ŋ¯ ß­Xɒ¯—1ęÖÄļoÁh`ķ–ŸxûŊxxÂx,>>åĮúųųōÖ¯×yĖ‹Õ6âĸҤ‰NūŠ|’’0gŪB’öíįáĮךŸ†P›9_JÆŪIXhĢ×âãÅËOOŽsĨËWŅ&ô’‡ĀYķc+-Åß×Ra{žÕĘÔOž`ä͉‹‰f˙ÁÃŧ˙ŅLB[ļĀŅŅ‘Ož˜Į¸ģFڊ}û2åŋŸŅĒEs,>Ūå5æ,\Z/ĄUDDūūę=ž:uŠ…K–rį#éŪĩkųö>WõÆĮĮwŗšÂņŠŠiV÷%%ņåÜäY­ FFŨ:œčvQ¤¤ĻņĪ_fčāAüúë.RRĶhՖQˇŽāí÷ϐ–žÁË˙ú7ŖnNlL{.†‹ŗ 1íŖ lÆĶĪžČ¯ģhŨŽÂ1ĮŽįÅW^į†ë¯coâ>2ŗNŌ1.–!ƒn"%%•į_~•kú\ÍĒÕ?đÆk/sāāAæ.XDÁŠLôīw ={tĢąNķ?oŒÖ­[U˜ķ’ĨËčŅŊ}¯ž€ÂÂ"|d2O<6‘ų[Ŧģuˇ˛yÛ/Lš0ƒÁĀĒ7°#~7Įá•7ßįĒËģĶŖKĮ*ĪŨ˜ÄĸĨߒgĩâīįË-C`2ũĶMĪČǰxāĐo,úú[Ŧųų F†ėOdxŌ32yũ˙MaĀu}Ų˜Djz:mÃÚ0l`Ļ͘EfÖIŪú`:Ãö'##ĢĘ~KKmt騁+z”ũ-ķÄķ¯qu¯lüéįōžŦų§ˆh͚qwĸW÷δâßīNĢ0/[‰›Ũ@\L4�mZˇÄĶݔÔt‚›2úÖĄ„ŸÕámZãææJjZFyÜŸ€ÕšOLģļ—čˇ$""ö¤Ūī <xø7ŠŠŠčŌŠSĨ}qą14o\íšššyŧûūTÜØŸ×_~ņ÷ŽáŊĻqōd6&“Ģ5ƒ~p<O?9™ĩëÖsüÄ î;€§›tŅđ\ž ŅŅ‘ėŪ“XiŸÉh"?˙ŽNNLzäAž|ėQÖü¸ŽÄ}û19œŲįČ;oŊAaa!īŧ˙!#† áĩ—ŸgÂ}c™ųÅlŽ9RcįŪãī[aÎŊzvgÆMåũÆ˙ē ‹Å§Q @¯îqvvæĮ[Č:™ÍĘ5ëšã–! †¸žˆ°Đ*ĪËÍËããĪį0ōæAŧōėãthÅ'_ĖĢvĢ5ŸfÎĻ_Ÿ+yöą‡šûöü÷ŗ/ÉÉÉÅh4’ę`Ü]ŖxôąlܲäÔ4îēm8�Ü?†vmÃĢíˇk§8ļlÛ^>Ūî=ûđöōĸŋĢyééÉŧôôd}`,.ÎM¸æĒËhTe¯îîfētėPūķņärōŦ„´ÂË̓˜v‘�”””°eÛvJKK itú}ą˛dŲwŒŧyĐEüDDDÎĒ÷•Āŧŧ<<Ü=0™.ūžēø]ģ0›ÍÄŖŨLxxļīØILLŲĘܙpéę⊗—ééX|,ÕÖŧXŽ..Xķ­ÕîīÕŖ�nnŽ´ gob"—ûuĮfŗŅŗ[W  {öāGTd؊MĶĻDGE˛sWââĒ­“ž‘^ãüĪã|]ģtföœų9z”ā  ļnÛF÷n].ŲûR•÷>š‰ņŧ^FL§1 n>˜˙ŧ7ø„ŊôëĶŋĶ—GۆW�ö&ĀĪ×B̐æ�\ŅŖ+/‹Åj=Uåņ {÷avsĨ}T�MmBüîŊ´k@ĮØ˛˙0pqvÆĶ̓ĖĖ“øx{U¨S]ŋf77.ũ–ãÉ)4 đįįģčrYlųyÅÅÅL˙t6ũúôĻuË Üié™|4sˇģ ŗ›[ųö[æË_áánfôțq9}ŠŪâ¯šęŠž. ‹ˆˆ\Œzîf3Ų9')**ÂŅŅņĸÎÍËŗ’–žÎ¤ĮŸ,ßVPTDXčŲĐāâröū7ŖÁˆÍfĢ{ĶįHIM%,ŦMĩû]]\*ô’g=Ífw�r˛sp?ũúė>39Ų95ÖqŊ€ų›ĪĢ{†›Ģ+qbXŋa3ƒŪČÎø]Œ¸yčMˇN¸gt÷z{yʎøƎyA5ķōŦŪŖŅˆ›Ģkĩ!КŸOzfĪžōŸōmEEÅ™ŗs“sę°•Vũ7SUŋ..δj˖ŸļĶŋßÕ$ėŨĮāû•Ÿ3wŅ×4 đ/ŋ\|!úŗæ1øÆë¸,6ēž]:Ō­SûfÆŦyŒšũrrķ8™Ã=ę7ԋˆČß[Ŋ‡ĀV-ChâäĖú›¸Ē÷öũ¸nŽNŽ´nŲ˛ĘsŊŊŧ äš§Ÿ¨´/#3Ŗ>Ú­āø‰$í?Ȱ!Õ_rËÉÉÅÃÃŊüĩ¯åė*ä™E1OO˛srÎ;/‡ĀĻ5ÖšųWąXŽg÷nĖül6-CZĐĒeH…pÃīGŽ‘tāqąŅ|ĩü{ntÞãn6“››WūŗÍfãDJ*ĻĒ˙tŊ<<lĀäĮUڗu2û’ôÛĩSž\đ̓ ÄÛË€ [ļņŋŖĮ™8~Ėqđđī˘5Ü>‚–-š—o?v"™ôŒ,ÚGE`4 o͚6­BØģo?ÉŠi¤ĨgđÜĢoeÁ×`0šžÁč[‡]ÔEDÄ~Õû=Mš4ačĖžģ€Õ?ŦådÖIōōŦ|ŋj ŗįÎÃĪΝÚs###HMKcOâ> lUhęôIII­qL“ŠlZš5\Æ­Iaaģ÷&ōöģSčŌš#mBĢŋ\švŨz�223Ų›˜HTdDĨcĸĸ"I;gG'aĪ^bcÛ×Xįbæ_՜ÛGˇŖÄVÂüE‹éŅíÂWĻęCqI ŸÍYȰũ6°?ņ {Øwā�ûö$#3ĢĘķچ‡’–‘IbŌ�6nų™O>Ÿ[å%p({€"-#ƒ¤Ķĩ­ųų|:{ié™5ög2ˏ˙Ŗ~#ÃÛ`ŗŲXēü{:Ÿž§īˇ˙á›Ģ3úÖ ^ņ.,,dÆŦyÜ5jx…�e÷6~:{>‡˙�ÉŠiė?ô̓škÔp^zz2/<ų(/<ų(Ũ;_ÆUŊē+�ŠˆČEiˆé{õ•øûų˛hņRfĪ™“Ŗ#ĄĄ­yėŅGhÕ2„ää”*ĪssuåĄņ÷1{î|rķō0 ôęŅŋWM&]:w䅗^ã–áC+­@VįßožŅ` ”R|-ž\qyOŽģļoã8;;ķĖķ/‘—kĨß5}iZŠ7wŗ™ ãīcîÜœ*(Āä`bĖwĐŦiS’“SĒ­\đü̚ŗŅh¤GˇŽŦ\Ŋ†Î.ģ ÷ .Ū6ŖŌ6“ŅČ[¯>Į7+Vāg)ØaØĀø|ÎBž|ôæ-ųĻÚ§ƒ]]\{įHæ,üŠüüSøZ|Ę⨊‹‹3ãîÅÂĨËÉŗZ1 títžīWF#q1ŅŧņÎTߨŒĖŦjûunŌ„Î—Å˛vÃâڗŨ›ēbõ:Nōö”˙žíÅŲ™ÉŽcō3/eÁōĶ/đųœ…ôŋöj|ŧŊČĖ:ÉģS?ŠĐˍ×õĨOīž pŸ~šœÜ\\œéŨŖ+íŖô$°ˆˆ\†ącĮ–N:õĸOܞDPĶęWņūî.ՇZ_Ē:Õų~Õ<ĸ{îž¨ķŽžH%Ž]XŊôôw°vÃf˙~DĢo""Ōhļ'$ÕęßęqãÆ¨¯û;ËČĖāÛīVÔ¸š)/ëd6ĢÖn Ī=ģ‘Zk°¯“†ĩhÉRÖŦ]Į€Žo´Īü;Zļb56˙Dŋ>Ŋ jÖØíˆˆˆÔšB`-ø_’K¸—ĒÎųĀā.y]{wÃĩWsƒžŖWDDūt9XDDDÄ)ŠˆˆˆØ!…@;¤("""b‡jM&#%—ø{zEDDDäãäXˇį{k-Ū^¤gœTi@…EÅüũ~ī:ÕŠu„ °�šžII‰‚āßŅö„¤ÆnADDDÎãä耟Å‹WęÔi1(ĀREDDDä¯C†ˆˆˆˆØ!…@;¤("""b‡EDDDėB ˆˆˆˆRąC """"vH!PDDDÄ)ŠˆˆˆØ!…@;¤("""b‡EDDDėC]N>–œFZæIJJl—ĒЁ“Ŗūoü,^uĒSëx49ĖŦlü,ۘŒZPüŗ9z"•¸vaŨ†ˆˆˆ\b…EÅüũ(Ĩ€‚`­Ķ[zf/@‘ääč@ëA¤ĻgÖŠN­\I‰MPDDD¤89:PXT\§Jq""""vH!PDDDÄ)ŠˆˆˆØ!…@;¤("""b‡EDDDėB ˆˆˆˆĒĶׯ]¨ąã"´UKŸôH…í“’ņ÷ŖUː†hãOoĶæ­ÄuˆÁŲŲšÁĮ~ôŠ)*ŽüyCa­?ftƒ÷#"""õĢAB @jZ6mĻg÷n 5ä_΂E‹‰jŅ(!`ÂØ; mÕ(c‹ˆˆHÃj°8bØ>›5‡11¸ššVÚŋ}g< -áT~>ŽÜ<t/‹#%5žø2ƒnĀŽ„Ũ=z”ÁƒnâøņūíwŌĶ3øĮ]wÖ&€}II|9wyV+Fƒ‘Qˇ'ē]TĨņŽ?΋¯ŧÎ ×_ĮŪÄ}df¤c\,CŨDJJ*Īŋü*×ôššUĢā×^æĀÁƒĖ]°ˆ‚S˜čßīzöčVcšú9ŒÖ­[‘–žÁË˙ú7ŖnΒĨËčŅŊ}¯ž€ÂÂ"|d2O<6‘õö{ĒÎē[Ųŧí&M‡Á``ՏØŋ›‰ãĮđʛīsÕåŨéŅĨcĨķl6ķ—|ÃîÄ$ 0ģšq밁4qrâõ˙7…×õewbŠéé´ kðũ|n"""ö¨Áî mMlûhæ.XTi_AANÎČ7ķī×_áæĄƒ˜úß”””`2ąZķqwwcâC0lČ`>žņíÚE1yâC\yE/–}ķ-�ššyŧûūTÜØŸ×_~ņ÷ŽáŊĻqōdvĨ1MFųų§ptrbŌ#ōäc˛æĮu$îۏÉáĖ>GŪyë yįũ1l¯Ŋü<îËĖ/fsäȑëÔÔĪųcŒŋw,�O=6‰Ø˜öôęŲ 6•÷˙ë.,ŸF €�ŊēwÆŲŲ™7n!ëÛ Đ�� �IDATd6+×ŦįŽ[†`0:āz"ÂBĢ<oĪžũ$<Ė3“äšĮáĘ^Ũ‰ßĩŖŅHūŠS`€qwâŅÆ˛qË6’SĶxf"""öŠÁVĄ”›‡ áég˙É=ģÚē|O“&Mxķ߯áæZļBŨ.ŠÂ‚2ŗ˛0 �ÄÅÆāā‡ƒŖ#Qm#�đgĶ֟�ˆßĩ ŗŲL\l �ÁÁÁ„‡ˇaûŽ\Ųûō*ģęÕŖėō´››+mÃÃŲ›˜Čå~ŨąŲlôėÖƒÁ@ž=øŲ€ĻMˆŽŠdįŽ:ÅÅU['=#ŊÚ~bbÚUã|]ģtföœų9z”ā  ļnÛF÷n]jûæ_÷>š‰ņŧ^FL§1 n>˜˙ŧ7ø„ŊôëĶ?_ �mÃĢ€�žîîœ<™ÍļíņDGEĐąC{�˛NķŽąe?ģ8;ãééAfæIü|ëcz"""rŽ āánfčAĖø|˙|ú‰ ûÖoÜĖÖ­?QZZ ”‘ŌŌŗûĪÜ'g4qnŌäėƒ›Í@^ž•´ôt&=ūdųî‚ĸ"ÂBĢ)Ž..å¯]\œÉŗZË6›ŨČÉÎÁũôëŗûĖädįÔXĮõú1ŸW÷ 7WWâ:İ~Ãfŧ‘ņģqķĐjįq)<pĪčī ôöō$",”ņ Œ=ō‚j5ãîÛFđãÆ-Ė[˛ŒāĀf ģŠ?fŗ�ÎÎg—FŖ[Š­n“‘ Ō !āŠ^=Yŋa߯\ §WâŨÅōå+xöéĮņņöƚoåū /ēÁA<w^ĀŦINN.îå¯}-–ō}gÅ<==ČÎÉ9īŧ›ÔX§Ļ~223*ŒQ•žŨģ1ķŗŲ´ iAĢ–!X||.x^õá÷#ĮH:pˆ¸ØhžZū=7ēá‚ÎkJÛđPŠŠ‹YąúG>›ŗûūq{=w+"""5iđĪ 4 ŒžíVžZö ……E�dfeáé鎗§6›åß}ŅhāTÁŠ‹ĒAjZ{÷e+ƒS§LJJjĩįŦ]ˇ€ŒĖLö&&Q阨¨HŌΊ{ôøqöė%öôĨĖęę\L?&SŲ¯"7˙ėJdûčv”ØJ˜ŋh1=ēuŊ¨÷âR+.)áŗ9 6°?Ãö'>aû`ßūƒddfUyŪÆ-ۘŋd6› Gšø7dÛ"""R_ „˛{ãŽčՋoW|@įN—ąyËO<ūÔ3¸›Í ēéF:ÄÆđæ[īōČÃ\p]7WWŗįÎ'7/ƒÁ@¯Ũņ÷÷Ģōx“É„ŗŗ3Ī<˙yšVú]Ķ—6ĄĄåĢtg¸›ÍLsį.āTA&cîŧƒfM›’œœRm Ú~ÎÃd2ŅĨsG^xé5n>”Ģz_Ņh¤GˇŽŦ\Ŋ†Î.옎¸VŪ6ŖŌ6“ŅČ[¯>Į7+Vāg!Ļ]$�ÃŪĀįsōäŖ0oÉ7Õ>Mâūƒ<÷ꛘL&ĖnŽÜ:l`}OEDDDū€aėØąĨS§NŊčˇ'$Ô´ępõWœœÂĪ<ĮĮĶĻü)ęTįûUk8xđãîšûĸÎ;z"•¸vaõŌ“ˆˆˆ4ží Iĩúˇ~ܸqękãūĖ223øöģ\wm߯nEDDDūfår°üąEK–˛fí:Üp}Ŗ}6 ˆˆˆü}Ųm đŋ$—p/Uķ 8€Á\ōē""""ĐO‹ˆˆˆHãSąC """"vH!PDDDÄÕ:šLFJlúžW‘†VXTŒ“cŨžī­u´x{‘žqRAPDDD¤sđ÷cøYŧëT§Ö2(Ā@jz&%% ‚FÛ’ģšÄœđŗxãoņĒS:­#XĘàˆˆˆˆüučÁ;¤("""b‡EDDDėB ˆˆˆˆRąC """"vH!PDDDÄ)ŠˆˆˆØ!…@;¤("""b‡EDDDėB ˆˆˆˆr¨ËÉĮ’ĶHË<II‰íRõ#""""5prtĀß⍟ÅĢNuj&§“™•ŸÅ“Q ŠVGO¤×.ŦąÛ‘K¤°¨˜ƒŋĨđ¯CŦuzKĪĖÂâãĨ�("""Ō€œhŨ"ˆÔôĖ:ÕŠu‚+)ą)�Šˆˆˆ4'G ‹ŠëTC)NDDDÄ)ŠˆˆˆØ!…@;¤("""b‡EDDDėB ˆˆˆˆĒĶ7†\¨´ôtæÎ_Hâžũ””ãėėL×N2ø&L&SC´đ—˛iķVâ:ÄāėėÜāc?úԋW~ä<"Ŧ5ãĮŒnđ~DDD¤~4Hœ2íŋ„…†ōÆĢ/áääHJJ*oŋ7§&N pCC´đ—˛`ŅbĸÚF4J˜0öNÂB[5ĘØ"""Ō0$9r”áC‡āää€ŋŋ“'>ˆŖŖSų1ņŋîbÎÜääåŅ4ŸҎ$(0„Ũ{˜ģ`§ 098Đŋß5ôėŅcĮķâ+¯sÃõ׹7q™Y'éːA7°/)‰/į. ĪjÅh02ęÖáDˇ‹ĒÔ[MuRRRyūåWšĻĪÕŦZũoŧö2ŧ¤ũœ?FëÖ­HKĪāåũ›Qˇ gÉŌeôčŪžW_ @aa>2™'›HHH‹úũÅUaŨÆ­lŪö “&ŒÃ`0°ęĮ ėˆßÍÄņcxåÍ÷šęōîôčŌąĘs“°ø›â`2Ņ÷Ę^téØĄg """Đ@!°k—N|ôņ'\wí5DFFØ´)^^gŋë.;;‡>œÎ¤‰hĘw+W3eęt{ôŪy˙Czā>ĸ"ÛrâD2Īžø !-‚qtt"?˙ŽNNLzäAōōŦüß3ĪŅ.*Š ĀfŧûūTîžëâbc8rä/Ŋúo^å<==*ôf2šĒ­ãëëszŸ#īŧõ99š—ŧ“ƒŠÂEEŌŊO=6 O/OŌ32XˇncyŒ˙u‹OŖ@€^Ũ;ŗc×n~ܸ…Øč(VŽYĪÄņc0 p=~~–*ĪËÍËcÚĖ،Ŋs$mZ“š–Îko}@pP3›4đ,DDD¤ABāˇßÆĻ-[øiÛ/,Zō&Ŗ‘.Ëî tuq%aĪšúĶ&4€žWõĻGˇ.ėJØM@€Q‘mhÚ4€č¨HvîJ S\�ŊztĀÍ͕ļááėML$=#ŗŲL\l �ÁÁÁ„‡ˇaûŽ\Ųûō*{ŦĒÎå~ŨąŲlôėÖƒÁPÖį%î'&Ļ]…1Î×ĩKgfΙΑŖG  bëļmtīÖĨîŋ”ŧ÷ŅLŒįõ2jÄ`:uˆÁ`0pÛđÁüįŊiÄ'ėĨ_ŸŪøų–ŋļáĄÕÖܛt�?_"Ú´ĀĪ×BÛđ6ėŪ›¤(""Ō$zvīFĪîŨ(--åĐáߘ=wĶ>ú„‡OnnŽŽŽåĮ›L&ÜÍfr˛sp7ģW¨e>Ŋũ W—ō×..ÎäY­¸æYIKOgŌãO–ī+(*",´úRUŗc–õPŸũ˜ĪĢ{†›Ģ+qbXŋa3ƒŪČÎø]Œ¸yhĩ폏gt÷z{yʎøƎyA5srō0ģšUØæææJNnnz‘ÚŠ÷˜™•ERŌēt.ģOĖ`0ĐēUKú÷ģ–Ÿ}€‡‡;9ŲŲåį”””pėÄ <==ČÎÉŠP/''§ÂĘQNN.îå¯}-ŧŊŧ äš§Ÿ¸ā>ĢĒsƙEąúč'#3ŖÂUéŲŊ3?›Mː´j‚ÅĮį‚įU~?rŒ¤‡ˆ‹æĢåßsķ ?~¸ĮĶÃLnn^…mššyøûÖW›"""Rƒz˙œ@›ÍÆôŸōũĒ5‘™Éšĩ눌ˆ� :*ŠÔ´tvī`íēõ|øátĸĸ"IKKcOâ>�Ž?NžŊÄÆļ/¯ŋvŨúōš{‰ŠŒ 22‚ÔsÎËËŗ2uúĮ¤¤¤VÛgUuÎ×ũ˜Leŋ’Üüŗ+‘íŖÛQb+aūĸÅôčÖĩÚ94„â’>›ŗaû3l`âö°īĀ!�öí?HFfV•įE„…’ž™IŌécO¤¤’˜t€č*ŪgŠõžhņņáņIŗxÉ×|ĩtEEŸēēĐšsGūx77Wzā>f~>‹|Ģ???îŊw îf3ÆßĮÜš 8UP€ÉÁĘ;ī YĶĻ$'§`2™pvvæ™į_"/×Jŋkú–ßWøĐøû˜=w>šyy zõčŽŋŋ_•=VWįĖ*ŨõŅĪųc˜L&ētîČ /ŊÆ-ÇrUī+0ôč֕•Ģ×ĐšĶe—úWTÉģĶfTÚf2yëÕįøfÅjü,Ä´‹`ØĀø|ÎBž|ôæ-ųĻÚ§ƒŨ\]šgôH/ûŽ‚‚BL&ˇL€ŸVEDDƒaėØąĨS§NŊčˇ'$Ô´ęPÕ’“Sxâ™įøxڔŋUę|ŋj bÜ=w_ÔyGO¤×.Ŧ^z‘Æŗ=!ŠV˙Ə7ŽĀĀ@}mÜ_AFfß~ˇ‚ëŽíÛØ­ˆˆˆČßDƒ<,ĩˇhÉRÖŦ]Į€Žo´Ī‘ŋŸŋl đŋ$—^˙luÎ7xā�pÉ늈ˆˆ}Ķå`;¤("""b‡EDDDėB ˆˆˆˆĒu4™Œ”Øl—˛š�…EÅ89ÖíųŪZ‡@‹ˇé'EDDDPaQ1?†ŸÅģNuj!ƒ,�¤ĻgRRĸ øgļ=!Šą[‘KÄÉŅ?‹7ū¯:ÕŠĶ:bP€Ĩ< ŠˆˆˆČ_‡ ąC """"vH!PDDDÄ)ŠˆˆˆØ!…@;¤("""b‡EDDDėB ˆˆˆˆRąC """"vH!PDDDÄ)ŠˆˆˆØ!‡ēœ|,9´Ė“””Ø.U?""""R'Gü-ŪøYŧęT§Ö!đhr:™YŲøYŧ1ĩ (pôD*qíÂģ ‘ŋĩÂĸbū~”RĀŋA°Öé-=3 ‹— ˆˆˆHrrt u‹ RĶ3ëT§Ö ޤÄĻ�("""Ōœ(,*ŽS Ĩ8;¤("""b‡EDDDėB ˆˆˆˆRąC """"vH!PDDDÄÕékã.ÔØņQR\ŒŅ` ‰sü|}éÔé2úõ틃ƒŠ^ĮNNNá‰gžããiSūđØM›ˇ×!ggį‹:ī¯ėÜ97´GŸz‘ĸâƟq֚ņcFךūļņŦųqĮO$cr0Ņ<°ũúô&",”Ô´ ^|ãmŪyũų ¯ëâ§í;‰‰Š¤I§JûRĶŌų×;2~Ė´lŅŧ|û'_ĖŖ´´”ģoΆ-Ûø~Í:žzôˏŲŧm;ßŦXÍSN '7—ūõvĨúĨĨĨŧđäŖx{yVŲÛÚ [Xŧė;n>˜Žڗoßđ0‹žū–Ô´ ÜÜ\č×įJēuŠ`מD–._IfÖI<ÜÍ\w͕tęSĄnn^/Ŋņ.WôčB˙k¯ž¸7LDDUƒ„@€I"2"œüSų$%`Îŧ…$íÛĪÃޝ×qũü|yë×/訋Õ6ggį‹:ī¯ėÜ97† cī$,´Õ%¯ģfŨF–˙Ãö':2ƒÁĀļíņLũä ÆŨ}>^gŋfĮâãÅËOOŽķ˜K—¯$ĸMh•!ĐĪא×ķé— øŋ‡īĮÉɉŋîæāáßxbbŲ˙ztéČĪÛãųæû5 ė-�yV+K–}Įíˇ ĨI'š4ņŠV?›ŗââ’jāŦy‹ą•–âīkа=Ījeę'_0ōæÄÅDŗ˙āaŪ˙h&Ą-[āččČ'_ĖcÜ]ŖmÅžũ™ōßĪhÕĸ9īōs.­rž""ōįgęØąã? pŅ'žHÍĀÃėvAĮ.ũæ[ēw킟¯GGü‰‰fîüE„„4'Āߟ}IIŧ˙áG|ķŨ V¯YKĶ�üũũ°Ųl|öÅl>Ÿ=‡•Ģ×°yËO´nŨ OâŨÅ;īMaÉ×ß°}ĮNZˇnÉŠüS<öä3ņū”i´o׎'žy–näØņã<öä3�|õõ7,ûv'Ož$˛moŋ7…ƒ‡ķˎøûųa4™üÄS p#� ģ÷đū‡ņŨŠ•Ŧųq=ÎMšĐĸy0)ŠiL~â)œ]øjé2žúz9'’“‰i]é}¨iü””Ô }_}eo÷%U9fMu€jßËķĮHܗġÃŋ•ĪyÚôO(*.ĻuĢ–�1ūÁGi…W5ã\9šVšų[ūđ¸3VŦū‘Nq1BÅšÖmÜĘÜEKéŅĨ#ƒU?n`ŅŌīčŪų2^yķ}L4 Ŧt^AA!S>ūœ[†  ķeą8::âčč@Hķ ›5ÅĶÓÉďˇpũ5W‘–žÉs¯ŊÉõ×\CŋņņįsXšv=ë6nÅĪĪ‚ŸÅ‡ôŒLūųę[87i¡+āÛU?’šNTÛ0Ļ͘Åo˙;J|Âü|}*.€æAÍØđ0ũFːæ|øņgŒžuMü0 ´nŲ‚Yķ—Đ.2w3ķ/ÃâíMß+{UųmŪļŸwÄsīŨˇáāPõĶyzēsy÷.lŲļƒV!Í lPö>*ĀßĪ—NqeĢ{>Ū^lūéZļhއģ™V!ÁDE”}´ÅĮ›õ›ĸMĢ–øZ|�ØŸĀCŋŅĻuKę%Ė‹ˆHõN¤f\Ôŋģg|ũõ׸ģģ7Ū=ž ŅŅ‘ėŪ“Hnnīž?•7öįõ—_`üŊcxīƒiœ<™Í¯ģHܡŸW_|žŊō"×ôšš_~ŲAvv|8ģîŧwŪü;^ƔŠĶ19˜ČĪ?…Ŗ“#īŧõF…U “ņĖ>'&=ō O>ö(k~\GâžũÜ7v �O=6‰Ø˜özÍÎÎá÷?dİ!ŧöōķL¸o,3ŋ˜Í‘#G0™ŒX­ų đđƒãyúÉÉŦ]ˇžã'NTšsMãŸßwaaaõcÖP§Ļ÷ōü1Æß;ļœ{õėΆ ›Ęû˙u‹!!-ęãOāõęŪggg~ܸ…Ŧ“ŲŦ\ŗž;n‚Á``č€ë‰ ­ōŧߎĨ¸¸˜Ëb+ņöQ5kZí˜Vk>͜Mŋ>Wōėcs÷í#øīg_’““‹Ņh$˙Ô)0ģFņčcŲ¸eÉŠiÜuÛp�š íچW[˙ÖaIØģ÷>šAįËb oĶēÂ~?_ ũŽî͗ žb˙ÁÃėڝȐ›Ž¯˛Öņä-ũ–Üv ÎMšT;f‹ā *ˇģģ›éŌąC…z9yVBZáåéALģH�JJJØ˛m;ĨĨĨ„´(Ģ•›WļB9ōæAՎ+""n v9¸*Ž..Xķ­ÄīڅŲl&.ļlE"88˜đđ6lßą“V-CČĖĘdķÖ­ÄÆ´§[×Î�lÚ˛•€Ļū´ - }¯ęMn](**ÄfŗŅŗ[W C•ãöęŅ �77Wچ‡ŗ71‘V-CĒí3aĪüˆŠl @ĶĻDGE˛sWŨO÷ĶĨS§ĶsrÅËˋôô š5­:lT5ūå~Ũ+ô]͘ââĒ­“ž‘^í{ĶŽÆ÷Ļk—ÎĖž3Ÿ#GÄÖmÛčŪ­KĩīËĨđŪG31ž×˨ƒéÔ!ƒÁĀmÃķŸ÷ĻŸ°—~}zãwz…­mxÕʂœģŲ c-žÛ:aī>ĖnŽ´*[U l@hËâwī-wcËū#ÁÅŲOO23OâãíUmÍsšē¸ĐĄ};~Xŋ‰‘ÃVyLŸŪ=ųeį.Ļ|ü9ˇģ ŗ›kĨc ųīg_2āúžVj/TZz&͜uzŧŗ+üˇūĖ— žÂÃŨĖč‘7ãrúļy‹ŋæĒ+zVģŠ+""~SRS kC^ž•´ôt&=ūdųž‚ĸ"ÂBC iÁũãîa՚ĩ|ūÅBB‚9b8ššy¸ēžũĮŅd2án6“‘™€Ųė^í¸Ž..å¯]\œÉŗZkė3';÷ķę™Ífr˛s*Ô9Ãh0bŗŲj5ū™ž/dĖĒę¸Öđ^ž?ÆųÜ\]‰ëÃú ›<đFvÆībÄÍCĢĮĨđĀ=ŖkŧŒčíåIDX(;â;zäÕ4ģš’“Kqqqĩ—HĢcÍĪ'=3‹g_ųOųļĸĸbZˇ<ģęė|vÕÍh4`+­ūw}žÃŋáįŋrCŋ>|1w1“ēSŇŖŒF#}ŽėÉWß|_éAŒ3ž\°” fÍčÕ­ķ]‡~cÆŦy žņēJ̧=ēt¤[§8ö<ˌYķsû-ääæq2;‡+zÔī ˆˆHũj´xüÄ ’ödؐAdeeČsO?QåąŅíĸˆnEaa˖ËGŸĖd ד“]~LII ĮNœĀõtĢf€œœ\<<ÜË_ûZjžžîééAvNN…m999å÷V]ŦšÆ?Ķ÷…ŒYUo/¯jßË3šĻ÷Ļg÷nĖül6-CZĐĒeŸZÍņRųũČ1’".6š¯–Ī̓nøÃsZ4ĸ‰“›ˇm¯’6ũôKŲũÁÁUžëåáA`Ķ�&?8ŽŌžŦ“ŲUœqáN0cÖ<†ē‘íŖ8pč0Ëž[UūČšLF&SÕ+™ˇūĖá˙᱇î­S?�˙ΌYķøĮí#*<ĩ|ėD2éY´ŠĀh4ŪĻ5mZ…°wß~’SĶHKĪāšWßʂŗÁ` 5=ƒŅˇĢsO""Ō0üžĀÂÂ"vīMäíw§ĐĨsGڄ†AjZ{÷—geęôIIIeíÚu|>ëKl6NNŽ6ƒŌRĸŖĸHMK'a÷�ÖŽ[·N¯öđšÖŽ[@Ff&{‰ŠŒ(˙77ŋōĒ`TT$iįôwôøqöė%6ļ}Ĩc/DUã×fĖĒęÔô^ž¯Ē9ˇnG‰­„ų‹ĶŖ[×ZÍīR).)áŗ9 6°?Ãö'>aû`ßūƒddfUyž“Ŗ#7^חEKŋeũϟČÎÉŚŸĪÚ ›Y¸t9ž5Ûđ6­IËČ éô8Öü|>Ŋ€´ôĖ{5žôlÍΝö˜9 —Ō:¤ÚG0rØ 6lŲÆCŋÕXû\GŸ`ņ˛īøĮm#jŧđB2cÖ<î5ŧB�„˛KęŸÎžĪáß˙@rjûũFķā@î5œ—žžü˙Ųģīø¨Ēü˙㯙ILz%‰„HB3ôĻą‹ˆ(ēę×ļ ˆuQąwũéÚVAײĸ(+ĸĸR¤ JĨ“B“ =d2“>ķû#0R€„pŪĪĮƒĮ#sī=į|ÎɐŧsīĖ\ž~äž~äúöėÆā}�EDÎ2§íLā̝ŊŅ`‰“°Đ0Î?¯?—\4¨ē yĪøąLŸņE6ƒũúŽŸÅ-ŠiL|øQLU—|oŊå˙đķ3sĪ]cųdÚįÛ턇‡ķÜqÜ:L&>>>L~ęYlEv.žp¨ëu…Ŋzvįég_äúQ#IJHpĩņˇX˜0~,3fˤ¤´“‡‰;nŊ™QQŽŗk'ĒŽņí§ž133ŗęG]kyė&“ŠÚœ<ŖŅHŋ>ŊY°h1={t;Šš5Ä[S?ŽąÍd4ōú OđãüED†‡ēŪ pÍđ˙öå×<ōĀ]üīÛ|^_úõę^kŋû÷&<,„æ-dæė9xzzĐēå9L}-cŖÉΊũûæëëØ[oäëŲs°Ųí z÷čFXhpŊgF#É]:ņʛSqÅÅ5Î@ūļv=ÛvüÉ#÷ßåÚȈ+.áĶ/fōĪûĮŸP¨[žâ7Š‹Kx鍚Ÿ_yíđË9ī˜K´<8ųšĒ¯++ųī3™öå×\vŅB‚ƒČ/8Č[S>ĒÖæŠK†rÁĀūŒv ˙ũâkŦEEøúø0°_o:'% "" †ŅŖG;§L™rŌ ×nŪJLTx”Ôtšû OÕøM=Ÿ.fĮŽŒšķö“j—q ›äŽņMR“ˆˆˆTˇvķÖũŪ3f ŅŅŅēmœT——ŸĮÜyķ]giEDD䯊Yß,g–YßÎfņŌe ģüŌfûl@9=Ü*FFF4ëŊ€OÕøM5Ç1bøÉß=FDDDÎ>ē,"""â†EDDDܐB ˆˆˆˆRqC &“‘Ęzî+""""MŖŦŧ/ĪÆŊŋˇÁ!048ˆÜŧƒ ‚""""§QYy;vī#<4¸Qũ48BÆD†›OeĨ‚ TYģyks— ""ō—æåéAxh0ĄAę§Qįc"C]aPDDDDÎzcˆˆˆˆˆRqC """"nH!PDDDÄ )Šˆˆˆ¸!…@7¤("""â†EDDDܐB ˆˆˆˆRqC """"nH!PDDDÄ y4ĻņžĖrōRYé8UõČ_Đīŋ¯aã–4Ž3˙yb4éœÔ;nū[s—"""Ō¤32sÉ/($<4“Q'Ĩv˙ūģvíCsWrbë7Ļđū'ĶšķAųëjpzËÍ/ 4$HPęĩ{Īžŗ&�ē`SJZsW!""Ō¤œā*+ €r\N§ŗšKhŗáŌĩˆˆHc…‘s#�� �IDAT(ʼnˆˆˆ¸!…@7¤("""â†EDDDܐB ˆˆˆˆRqC """"n贅ĀĢVķÔŗ/0zÜÆN¸—_yÍ[RŨīĖožeü=÷3ëģŲdffqûčąu;ņáGHIKoÔx+VŽĻ¤¤ā¸ãvĸĮ)Žžãéö˙ž}Œ Ŗo­ąũŠI÷Ķ26úô$""ōuZBāŧų øôŗé Ŋ`0¯Ŋü"¯žô<={tįˇ˙͖”ÔFõŊrÕoŒūûmŒ¸rááaŧūĘK§¨ęÚ͜õ Ĩ%Ĩ�§eŧæpô›Chh=ģumļņEDDÜAƒī|ĸJJJøúÛŲÜzķ ôíŨÛĩũ‚Á Æßb`ķ–f˜EiI)&.ģøBú÷ëCVvO>ķ#G\ÅÆ›ČĘÎĄcR7ūí:Ū|į]rsķøäĶé ¸‡Ū={0iō|8õ]�–,]Æ÷sæâåéEĪŨĀpäūeé[ˇōŌ™ØėvŒ#7ūm:&Õ;ŪoŋKNnĪŊü*7^?ЍČČjã­]ŋ™ŗžĨ¤¸OŽyŨģ%×ģ>ûöīį™į_âōK/!5-ü‚ƒtOîĘÕW]IVV6O=÷^0„…‹–đĘ‹Īą}ĮŽZ׊ž~ęī1c´iĶēÚŋũũúöačA�”••s÷}2éĄû‰‹kyĒž&Õ|ķũ\F]u›RŌ(.ŽyF˛Cģ6 ŋü"ŧŊŧЍŦ`áŌ_XũûējĮôî‘ĖMŖFTÛļjÍZĻ͘Õ$5‹ˆˆœmš<îøsåååôęŅŖÆžäŽ]�(,´ōæ;īqĪ]cIJLāLæyâZÆâk6cˇc0ĀŊwĮ^lįŪfČāÜ=~,~„ŋß~+‰ړ™™åę;'7—iĶŋāÉĮ&˒Ÿ—“——@Q‘ˇŪ™ÂíˇŨLr×.ėŨģ—g_x•—ž“ÉXįxcGßÁčqxôĄ‰V¯´´”÷Ļ|Ā=Ƒ”˜ĀīŦeĘ>æŨCsŦ‹Éhĸ¸¸O//&Ūw76›N~‚ŽII„……Úįɛ¯ŋ‚ÕZTį:yzzÕŲOLt‹ēįëaĒ6FyyEĩ9ææåąl؝ޏaã&BCCš,�ėÜĩ‡ÍŠé ŋė"ž˜ų]ĩ}?3wŪrS?ųœôm; áŸ÷ŽcoÆ~öČtˇjÍZ�WT�ŠŽÉ/Ûl6ü0™Luŗ9%…ČČp’�ˆŠŠ¤SR"ë7mvŧ;"Ížf‚‚‚ČÍÍĢwÜ´´­ÄDG Āųúc2VÕ°aĶ&,‹+„ÆÆÆŌž};ÖŽ[ßāņŧŊŊyíÕ]sčÔ1‰˛ŌRō ęmw؀~}�đķ3“Đž=Šii U÷°íß§7ƒĄŪuǝŸãÍ÷č1ŽÕģWOöîÛĮی �V¯YCß>ŊNhN e0øæ‡ųtí”HĢ–ąÕöuˆoKvn.éÛv�“GJúv’âkôs8ø)�ŠˆˆÔÔägũ- ­)//ĮĶĶŗÖcŦ…Vü-ūÕļY,Ŧ…V×c__××Fƒ‡ÃQī¸EEE˜ũüŽ´1\m6;9ššL|ø×ūŌōrâÛļmđx�Ë]ÉęÕŋát:Ē@åtˇ�f_ßjcÛėv×cËĄĩ9‘uĒ­ķ Ė×rLŋ‡ų™Í$ŸÛ…åŋŦdÄđ+Xŋa×];ōÄ&Õ6ģī~ü‰ë¯ž’—ß|ĪĩŨßbĄ¨Č^ãX‹ÅīØ.€Ē xøŦ ˆˆˆŅä!°uĢ8ŧŊ|Xūë <ŋÚžŸ—ũ‚§—'Z­ÕöY­VĸŖ"<ŽŲĪLņQAĒĸĸ’ĸĸ"�‚ƒ‚ˆ‰æ‰Į&Õh——_˙ŋēlظ‰9sæķøcŒŊØÎ¸ ÷Ÿp{Ģĩˆ€�××aĄĄŽ}‡OНČ:ÕÖΉˎ–“€.ũûöá“O§Ķ*Ž%­[ÅrÂķjŒŋũAŸžŨ4 ĪĄ` …V+ūĮ>‹Ÿ™YŲ§Ĩ&‘ŋŠ&ŋėííÍČ̇3}ÆL-YĘÁ‚ƒØlv~Z¸˜é3ūGxx8II‰äää¸>ž%c˙~6§¤ŌĩkįÛ!>ž={3Øŗg/�‹–,uíKLė@öQãŲlvĻ|đ!YĮ &SÕrÛkėË/( 0П Ā sæũ„Ņh ¤ôÄ>jeé˛å�äåį“š–FRb‡ĮœČ:ÕÖĪÉˎļ9vîԑJG%_Íú†~}z×hĶ”žüz6—\0¯Cg‘Sˇn'$$ˆø6­�ˆŒ#!ž ›RŌNk]"""gģ&?0tČ "ÂØõÍlĻų^žž´mۆ‡¸Ö­â�˜0~,3fˤ¤´“‡‰;nŊ™QQ >3ÎõŖŽáĩ7ŪÆäaĸoŸ^DEEát8đ3›šgüXĻĪøŠ"› ƒÁĀ€~}‰ˆ¯w<“ÉD¯žŨyúŲš~ÔH’\ûzöčÆĘUŋņđŖ“ņˇX¸ęĘ+8ˇk^{ũ-&Œ˙GŊĩšL&|||˜üÔŗØŠė\|áPÚĩm[Ŗ‹ĨÎuĘĖĖĒŗā„į{ė<ŖŅHŋ>ŊY°hqÕģŦOŖ}2ųuõī\0°?�v{1ī2ĢޏØõîāi3f‘•{Zë9ÛFíœ2eĘI7\ģy+1QáMP’{ÉĖĖĒö13ÍŨO]~Z¸˜;v2æÎÛOĒŨ3/ŊÖ$õœoŊüts— ""rʍ3†ččhŨ6NŽ//?šķæsÉEC›ģ9ENËå`9{Íúv6‹—.cØå—6égŠˆˆČéĨØĖ"##NÉ%ÜSÕĪąF ƈáÃNyŋ"""Ōŧt9XDDDÄ )Šˆˆˆ¸!…@7¤("""â†M&#•'p?]qsõ܎îLf¨ī>z""" ĄÁAäæT”zŝĶ'Îæ.ã¤8qŌ9)áøŠˆˆœÅü11‘Ą�dįæSYŠ (ĩģ`đ@ÖŦYÃÆ-Š8g~4 tIJäÎ[ūÖÜĨˆˆˆ4ŠF}N`Ld¨+ ŠÔ%šc|s— """ĮĐCDDDDܐB ˆˆˆˆRqC """"nH!PDDDÄ )Šˆˆˆ¸!…@7¤("""â†EDDDܐB ˆˆˆˆRqCēwđžĖrōRYé8UõˆˆˆˆH=ŧ<=ˆ &<4¨Qũ48fdæ’_PHxh0&ŖN(烌ŲÄD…7w"""nÍ`0“—ˆhDlpzËÍ/ 4$HPDDDä4r:„‘›ß¨~œā*+ €""""ͤŦŧĸQí•âDDDDܐB ˆˆˆˆRqC """"nH!PDDDÄ )Šˆˆˆ¸!…@7Ô¨ÛÆ¨ŅãīĄ˛ĸŖÁ�€ŋŋ?={vįšÃņôô<%œR~„ŋß~+‰Ú7w)Įĩbåj’Ī킏ĪiûÅ×ŪĸĸĸægĩnՒG<íõœŒ_{‹ĘĘJ×sÖl6“˜Đž!į÷ĮÃã´üˇ9ĨŪ|÷}ŽŧüZĩ<§šK‘3Äiûm6ņū{\ĄéLŪzį=Ė>ž ŋōōĶU‚[š9ë’:4K¸éúkÎÚāqãu#]ĩįåå3ãëīđņöæüū}šš2‘Æk–SQQ‘ 2ˆ_~]é k×o`æŦo)).ÆÃÓkG^E÷nÉ8Ļ}ū7oÁ`0āoąpë-7ĶĸE­Û˙õÆÛŒ;šļ­[SPPĀŊ˙ɘ;oŖoīŪ8Æßũ�O?ųYYY˘9‹Ō’RL\vņ…ôīׇŦŦlžzî.ŧ` -ᕟcåĒÕ|?g.^ž^ôėŅ :Öžũûyæų—¸üŌKHMK'ŋā Ũ“ģrõUWÖÚīö;j­Ąž~�Ōˇnå‹3ąŲí FnüÛ(:uLĒ1F›6­ÉÉÍãš—_åÆëGņíėčסC‡  ŦŦœģī{IŨO\\ËĻ˙ÆcÍëYŋqˇß|ƒĢא’ļÛnēŽ)~J¯ÉtëÚšFģĖėl>üäs&MŧĮĩí_īLåę+/#6&šy ŗmįŸ `ö5sų%C‰ g÷žŊ,Xü3ÅĨ%1rŅĐA´mŨŠüüūķßĪéÕ=™ßūXĮ„üŊƘ!!Áôčv.ë7mq…Ā´m;XōķrJKK1™<¸`Đy$´o‡Ãá¨ĩ†đĐĐZˇųÕ7Œŧę bZ´Āj-â_˙žĘˆa—Ō))‡ÃÁĢoŧ˝ˇ˙ųų,X猞ŌrL&ũz÷ K§¤Zëß´%•_VŽÆÃÃĄöęxΊˆˆûjļëZN§ãĄÛΕ––ōŪ”¸gÂ8’øũĩLųĪĮŧÛĩ ›6o!-}/<ķ&VŽú?ūXG^\^­Û;vLbkú6ÚļnMJj:ņíڒšļžŊ{ŗk÷đöōâÍwŪãžģƒ”˜Ā™<ūĖķÄĩŒÅ×lϏ¸O/OŪ|ũrķō˜6ũ ž|ląąą,ųy9yyyĩÎÉd4jëÅÄûîÆfŗķĪÉOĐ1)‰°°jũZ­EuÖāééUg?1Ņ-xë)Ü~ÛÍ$wíÂŪŊ{yö…Wyéų§1y˜ĒQ^^ÁčqxôĄ‰’›—Į˛eŋēBā†› i–�Đ=š )é[ųíu$´oĮ¯ĢÖpۍ×a0¸hČ@‚C‚OēĪí;˙dמ ÆŨq+&“‰M[RIOßN€ÅŸ˙͚Í—]L‡vmČĘÎæŖi_rםˇa4)-+ÃÃ̓û'üŖÎĀäp:1Ģö••—3ëģ¸näpZĮĩ$5}ß|?‡‡î_g …QÖZˇˇiĮî=Ä´hÁŸ{öpNl îΠSR"˛˛đķ3ãåéÁ—_Īæē‘WŌ:Ž%yyųLũxQ‘áx{{WĢŋĐjeî‚ÅÜqË D„‡ķĮú,´6ę{%""=ÍōƐĖĖ,-^JnÉ�x{{ķÚĢ/’”˜�@§ŽI”•–’_P@P` ųųŦ\ŊkQ}z÷dø°ËëÜŪ11´­Û�HIK㠆°mÛv�RĶŌčÔ1‘Í))DF†ģƋŠŠ¤SR"ë7mÆ`�‡ÃA˙>Ŋ1 ¤Ĩm%&:šØØX�ÎĐ“ŅTīüôĢ:Säįg&Ą}{RĶŌjô[_ õõŗaĶ&, É]ģ�KûöíXģn}1ŽÕģWOöîÛĮی �V¯YCß>ŊNöÛwR>ûr&/ŧúFĩˇ¤�`0¸ō˛‹øuåo|ûÃ<ôíMČĄā×ĻuÁ'=žŋŸĢÕĘĻ-ŠØ‹‹é””Āyũû°mĮNĖžžth×€ˆđpZÆÆļm;ƒ‡ÃAį¤Ä:`^^ŋ¯]Gbûx�ŧ<=šwÜhZ ĐmZĮQ^^Žĩ¨¨ÎęÚŪēUKvīŠúžėÚĩ‡^ŨĪeoÆžĒĮģ÷ŌŽM;ūÜEHH kŧ`Ú´Šcێ?kÔŋk÷^ÂÃB‰ šK'Ũį[DDj8mg_ķLF8 ú͝wo.ēpˆk˙ō_W˛zõo8N ęąĶ qq-7æN.^Ę´Īž$..–ŽUįöÄÄD>›ū%N§“ôômÜpŨĩü8w IMKgđĀķÉĘĘÆßâ_­>‹Å‚õ¨ŗ%–Cû‹ŠŠ0ûųšļ†jkcöõu}íëëƒÍn¯Ņ¯ĩĐzÜjëĮlŗ““›Ëćqí+-/'žmÛcËĪl&ųÜ.,˙e%#†_Áú ›¸îÚĻ}ƒÆŅ¯ĢĢM`@�­[ĩ$%m+׍¸˛ŅãEEE0røüöĮ:æ.XLTd8LqI ……ŧųîûŽcË+*9'&ÚõØlö­Ö×_}SuæĪiĀĪâKį¤$zõHví_ŋi3›SŌĀéÄyč9ëpÖ]C]Û[ÅÅ1oÁbœN'ģödpҐAüēr E6;ģvīĄûš]ÉË/ĀėkŽVŸ¯Ų›íČsëpũÅÅÅÕ^j0ší5Ą""ræ:m!đžģĮ×ųnÚ 71gÎ|ėaB‚ƒąÛ7á~×ūN“ĒΖ•ķÜšŧ˙Ņ'<ûää:ˇ‡„°~ÃFĖf_|||ˆoGjj;ūÜÅØŅ(--ĨĐZũō˜Õj%:*Ōõøđ !ŗŸ™âŖB\EE%EEEõÎÕj-" ĀßõuXhh~Ž[Cmũ͏MĒ1n^~^ĩ1jĶŋo>ųt:­âZŌēUĄ!!õÎĨŠíÛŸÉŽŨ{HJhĪĸŸ—sÉQÔÅh4âp:Ģm+-+s}ŨĻu\ՙšŠ ~YššoœËĀū}‰ ãīˇÜPŖŋÃ߇cĪ^ÍUuØmÛw˛bÕnŋųoøûSRZÂ+˙úwŊ5Œšũæ:ˇûøŗmûN|}ŧņōöâœsĸŲĩ{7û3šúĘXĘĘ˰õ<°Ûė„uÉüpũŪ>>”–”ēļWVVb/.ŽwMEDÄũœ׈ō  ô'(0‡ÃÁœy?a4()-aéŌeLûü ^^žDGˇ�§ŗÎí�™ũã\:´¯ íãÛąpņRbŖŖņņņ&))‘œœRŌŌČØŋŸÍ)Št­åMâãŲŗ7ƒ={ö°hÉŌãÎgé˛å�äåį“š–FRb‡ĮœH ĩõ“˜ØėŖÚŲlvĻ|đ!YYŲ5Æ0™ĒžŊEÅGÂCįNŠtTōÕŦoč×§÷qįŌ”**+ųîĮy\<t0DÚÖíėÜŊ€ģvSp°°Övū?œN'ųųĒ7h”——°vŨæ-X„ÃáĀĶÃđCŧU\KōđįĄū‹KJøföW'ËZT„ŸŸ‹‡ÃÁŠÕŋc0(++­ŗ†ēļ´‰‹c؊մ<§ęe-ccYŗv=aĄxyyŅēUēęĪÎÍeįŽŨÄēŧ}´¸Ø2ŗŗÉ<ôœXŗvCƒæ(""mgÄžõėŅ•Ģ~ãáG'ãoąpՕWpn×.ŧöú[üķáûŲ’šÆÄ‡Åäa:ô.ā˙ŖETd­Û:vL䇹ķvŲ%�´o׎wŪĘĩ#¯ĀßbaÂøąĖ˜1“’ŌRL&î¸õfZDEšÎĻÎõŖŽáĩ7ŪÆäaĸoŸ^DEEát8j‹ÉdÂĮĮ‡ÉO=‹­ČÎÅĨ]Ûļ5ú­¯†ĖĖŦ:û¸güXĻĪøŠ"› ƒÁĀ€~}‰ˆ¯1†ÉdĸWĪî<ũė‹\?j$ƒžŅh¤_ŸŪ,X´¸ęÎMlÚ_ÕØf2™4ņ~^ū+Ą!AtˆoĀÅC3û‡šŒųûÍĖ[°¤Îwûxû0p@?Ļõ5AAÄļhqčĺĄėœŋ‡ˇŪûF“?__.ŋäB||ŧšîęáü´h)ö’b ]:u"88°ÆŲ‘˜ĪĻ-Šŧ=õCü|}9@_ÚˇkÃôŗ¸å†QėÜ]ŗ†°ZkƒĒ3‡ŋŽúķúVŊFŗeL4_}3›!�U/ ¸öęá,\ü3eeåMFŽŧė"ÂBBjÔąC2ũŗ0™Œtę˜HhHČĄ—ZˆˆˆT1Œ=Ú9eʔ“n¸vķVbĸ› ¤ŗWff“&?Á‡Sß=#úŠËO ŗcĮNÆÜyûIĩË8­īšˆˆČ"ã@6ÉãOēŨ˜1cˆŽŽ>3.Ë铗ŸĮÜyķšäĸĄÍ]Šˆˆˆ4Ŗ3âr°œŗžÍâĨËvųĨÍöŲ€"""rfP<…"##NÉ%ÜSÕĪąF ƈáÃNyŋ"""röŅå`7¤("""â†EDDDܐB ˆˆˆˆjp4™ŒTÖņÉ""""Ōt ^ž{oƒC`hpšyEDDDN#ƒÁ@N^áĄÁĮ?¸ ސ1‘U÷=ÍÎͧ˛RAĐ]d¨yb9}ŧ<= &"4¨Qũ4ę<bLd¨+ ŠˆˆˆČŲCo qC """"nH!PDDDÄ )Šˆˆˆ¸!…@7¤("""â†EDDDܐB ˆˆˆˆRqC """"nH!PDDDÄ )Šˆˆˆ¸!Æ4Ū—™CNūA*+§ĒžŋŦß_ÃÆ-i8Z+iŖŅHį¤Üqķߚģų hpĖČĖ%ŋ đĐ`LFPŦĪ§ÅŽ]ģÁĐܕČŲĖáp°~c ī2;oQ‘ÆipzËÍ/ 4$HđėŪŗGPN lJIkî*DDä/ Á Ž˛ŌĄ�x‚œNgs— !zIˆˆœ Jq""""nH!PDDDÄ )Šˆˆˆ¸!…@7¤("""â†EDDDܐB ˆˆˆˆ:­!đĶĪĻ3vÂŊdėÛWm{ffˇ{JĮ:ļĪ+WSRR@^~ˇŪņ*++O阧R÷s;3qÂ^{n2/?ũwšíÚ�Ā[/?Ņ ?§1,4„7^|˛Iûė‘Ü///ĀŊ×ZDDūÚNÛoļŌ˛2ÖoÜÄđ+Ž`é˛_š|ŧđđ0^å%×ã™ŗžĄ´¤´ÉĮ=Ÿ×—ëF cé/+yėŲWyâų×Xģa3cnģ‘ö‡‚ œ:šyų<öė+ŽĮÃ.ЎˇW3V$""Ōô|īā“ĩúˇ5$%&0 _&?ų ×^}žžžĩģté2~˜7ŖÁH˙ū}Yĩj5׏ē†N“Øŧ%…3gQZRŠÉÃË.žūũú••ÍSĪŊĀ… aáĸ%<x˙Ŋ<ųės|8õ]Ūxû]rrķxîåWšņúQœsNĖĄš~įÛŲ?`ˇÛčͧ77\w-YŲ9<ųĖs\uå06mŪBFF#Žē’ũûđįŽŨäææņ÷Ûn&ž]Û&Y'///.ŋčž˜ųkÖmpm_ļb5ų)*˛Uw!éÖĩ— „ŸŲ—ßūXĪ×ŗįĐĄ]†_~Ū^ŪTTV°pé/Ŧū}]Ŋû ×ŋœÄņ€“"›Ī˙÷-û3ŗhĶĒ%Wģŗ¯/‡“¯žûÔôíĩÖß)ąW\rž>>TTTōíķŲ°9…Đā žwŗį. ąC<áa!¤Ļogæw?žT?Įę×Ģ;C‡Ãé`՚ĩôHîÂ×ŗį’ļu{s æÁ ˙`Éōœßŋož÷!˙ŧo÷üķIîŧåB‚ƒ¸oė|õŨė۟YįZžĶķ‘Ô!žQ|?o!QáœMHpĶf|ÍÎ]{ķ”i§-.ųy9×_;‹ÅöíãųũuôéŨŗÆqšyyü÷ķ/xjō$bccųvödffaōđ °ĐʛīŧĮ=w%)12yü™į‰k‹¯ŲLqq ž^žŧųú+deeģú;úF›ĀŖM$0(ŧü< 23yáŲ'ÉÍË㟏>ΠķāííŨ^Œŋŋ÷ßs+VŽæũ?fâũ÷píČĖ÷?ü8—{īß$ëwN žžüąaS}‡ī�@DxĪžō&ÁA<ūđŊü˛r 6ģ;ošŠŸ|Nú…đĪ{Įą7c?…Vkû‚‚h×ļĪŧō‡ƒn];ĶĩS" ĢÚ|6c›RԈŽŠäžņwđôK˙Âz(æééÉm7bʇĶHßž“.šåo#yđņįŠt8đņņƉ“І7ĪO~ˆe+V‘•{Âũ-(0€Q#Žāå7Ūc߁L.š` aĄ8*+ąø™ëœĢŊ¸oĘ+*˜ôԋ„…†¸úüčŗŧūüãŧūîX­EõŽuiY>>ŪØlvŪũđSz$wá˙Žģšˇß˙„īæüĐķûqáāķ™úņgÕęîŨ#™›F¨ļm՚ĩL›1ëdž*"""rZ.īÉČĀnŗģΝ×ŋ/K—-¯õØÔÔtZÆÆ Āe—\„ãĐŊw7§¤NRb�QQ‘tJJdũĻÍ U÷Tíß§7ƒá¸59N.<ƒÁ@Xh(!!!äåås¸ir׎�DD†ãáéIRB�"##Č?x°á‹qfŗ/Ö"Ûqīk0øų—•�䤠ā AAtˆoKvn.éÛv�“GJúv’âëŨWXXDP@�=’ģ`6ûōĮúĖ]¸”Ž íąŲėŽ�ēī@&;vîĻsĮÄ5•——ķØ3¯ž}'�)éÛđōō"0ĀßUķëĒÂmII)­t?‡ĩo׆ŒũØw €K–ģž÷õÍĀh4ēΎO]k}x߆-ЇÆČĨĸĸ‚­‡ęÎÎÉ%(ĐŋFĮ>@i§åLāŌĨËČÉÍeė„{]ÛJKKÉĘĘ&""ŧÚąEEEøYü\=== đ¯úEj-´âoŠūKÕbą`-´õ¸æ/Ũēøų™]_ ÆjÁËĮĮĮĩŨĮÛûHŖcŽ;Õl6;ū?<L&*ŽķÆ{q‰ëëJ‡ŖŅ€ŋÅBQ‘ŊzŸv;‹•uîÛģo?˙™ö%į÷ëÍĩW]ÎیũĖünfŗ!!A<5é~WOOOvüšĢ֚z÷8—îįvÆ`0p(ģúēęAIé‘×e:œU5ŸL?Gķ3ûbŗģWTVēÎNÖˇ‡ŲĒī¯Omk}XYYPõ‡EiiŲQķsb4ÔūwÖĒ5kˆoĶJPDDšE“‡ĀŌ˛2VŦZÍKĪ?MHp°kû3f˛tŲrŽYũ˛˜¯ŲŒŨ~Ô/öŠJŦEU!/00€BĢĩÚņVĢ•č¨H×ã8 xFÛĩ'ƒ˛˛rz÷Hæ—UkĒíëĶŗęĪ<˜�� �IDATååålßY{�(´Zņ?*č�XüüČĖĘŽw@ÚÖí¤mŨއ‡ 9Ÿ›ŽÁœŸŗo&¯ž5å¸ĩ'uˆgč ķxåÍ÷8XhÅĮĮ›Wž~ôD§~Ōũ—āëëãzl4]!īxs=ŦZŗÖEDDNˇ&ŋüۚ߉nŅĸZ�čÕŗ;Ë]QãcZâÛĩa×îŨ8t‰oÎüŸ0:›’””HNN)ié�dėßĪæ”Tēví\o &SUûĸâ?ķĶ\ĘË˙=wWģ„}zâīoÁ×ׇũ{sõ°KČÉͯˇ}ęÖ턄ßĻ�‘a$ġaSJZŊûúõęÎ5Ã/Ã`0PQQÁūY Ōˇí$,4ØÕÆ×ׇ›¯IhHpąüąZ‹(´a0¸`ā�Ū^'÷NÛígĮŽŨœĶ‚đ°P�.ØßuÆąžšÖĮápāt:1.EDDūŠšüLā’ĨËéÕŗ{ímZˇÂĶĶ“uë7ãÚŪ"*ŠĢ¯ÆË˙ī ü,fÎПāā T]â›0~,3fˤ¤´“‡‰;nŊ™QQäåįÕYƒÉdĸWĪî<ũė‹\?j$]ģtj‚™ž:?˙ēŠėÜ\ޏøŽžōRĘËËųs×^ۚō1{2öšŪŦPģŊ˜÷?™ÎUW\ėzWė´ŗ\ož¨kŸÕjŖC|ž~ä***ąŲĢŪ\\RÂԏ?įęa—āg6ãt:Yšf-šy5Ãčڍ›é‘Ü…Įē›ŨÆķą)%‚qwÜĖŋ˙ķßž}ũLũäs×qYŲš|?o!FߊŨ^ˊß~įāÁBœĮY‡úÖĪáp°vÃfœđf}?—ÍŠé'\ˇˆˆČŲÄ0zôhį”)ĮŋÔwŦĩ›ˇ~üĀápV{ÍÕøģīgŌC÷ģŪ,rļyæĨךģ„ŋŦŖ_ođŌS“xãŨ]oųĢzë姛ģ9K3†ččč3īļq%%%Üuīũ¤Ļmā÷?Öbô0yœ–ânŧŧŧxéÉI´m@—މTV:ČĘÉ=NK9mŸxĸ|||¸ũ–›ųøŋ͍¨(Įėgfܘ;ņōĒũƒĨÅ}•••ņŲ˙fqÃ5WááaÂ^\ĖGĶž¤ĸĸĸšK9ãq! G÷dztOnî2ä,°~S ë7Õŧ“ˆˆˆˆÔīŒģ,""""MO!PDDDÄ )Šˆˆˆ¸!…@7Ôāh2ŠlÂ{čūĨœåˇ˛“3Ëą÷Piˆ‡ĀĐā rķ*ž€¸sZâÄyüEŽÃ‰“ÎI Í]†ˆˆü4ø#bb"Ģîך›OeĨ‚`}.<5kÖ°qKjĩģ[ˆœ ƒÁ@—¤DîŧåoÍ]Šˆˆü4ęsc"C]aPę—Ü1žšKqŅCDDDDܐB ˆˆˆˆRqC """"nH!PDDDÄ )Šˆˆˆ¸!…@7¤("""â†EDDDܐB ˆˆˆˆRqC """"nČŖ1÷e搓ĘJĮŠĒGDDDDęáåéADh0áĄAę§Á!0#3—ü‚BÂCƒ1uBņTË8MrĮøæ.CDDDÎ0eåėØˆhDlpzËÍ/ 4$HPDDDä4ōōô M˲sķÕOƒ\eĨCPDDD¤xyzPV^ҍ>”âDDDDܐB ˆˆˆˆRqC """"nH!PDDDÄ )Šˆˆˆ¸ĄFŨ1äT™øđ#üũö[IėĐžšKi”+W“|n|||NûØ<ú å5ß*Ū!ž ãī¸å´×#"""gļ3"ūU˜õ I š%L}+ņm[7ËØ"""rv1uīŪũÉaƝtÃŲyXüŽ{\VV6=2™ōōrŪyw*C dų/+xûŊŠ,YēœBĢ•={3č–|.N§“'=Š/ßÍūīžŸÃĖLētîTŖß}û÷ķĐ#“øîûųaî|<HbB‡ZĮLKßĘ;īŊĪŧų Xüķr|ŧŊiyNlŊũ�¤o­j÷ãŧų,Zŧ”¨Č""ÂkŒ‘–ž•îâuë‰gęQ^QA›Ö­�(++güŨĐšSAAĮ]7k‘Ą'øŨ€ų‹~ĻGrBC‚kŨŋė×Õ˘5›~Ŋēc0Xøķ/˚=ž=ģņükīāáa✘čZÛnIÛĘԏ?gî‚%lܒFĢ–ą”–•ņä ¯ããíÍÜK˜ģp YŲš$%čVw"""§Ãėŧ“Ę ‡}˙ũ÷øûû7ũ™@“‡‰ââ<Ŋ<yķõWČÍËcÚô/xōąIÄÆÆ˛äįåäååUk2bˇc0ĀŊwĮ^lįŪfČ⁴ˆŠĒŪ¯ņpŋ^Lŧīnl6;˙œü“’ Š6ĻÕZěīŧĮ=w%)12yü™į‰k‹§§WũÄDˇā­wĻpûm7“Üĩ {÷îåŲ^åĨįŸŽ1¯ōō F›ĀŖM$0(Üŧ<–-û•ĄC°aã&BCCˆ‹kŲÔK^Ģ}{˛nĶ~ūu];%ą`ņr‘Ã.%<ŧö'Q‘ÍÆ‡ĶždüˇĐ:î–,_ÁGŸũÜ~Å%%`€1ˇŨHqI >ũ2įõëEdxØižˆˆˆœŦ&8ú÷éÁ` -m+1ŅŅÄÆÆpū€ū|öųŽczõč€Ų×LPPššy5BāaúõĀĪĪLBûö¤ĻĨq^xßjcnNI!22œ¤Ä�ĸĸ"锔ČúM›é‘œ\g?šyšX,’ģv 66–öíÛąvŨzētéXmŒcõîՓé_~Åی bcbXŊf }ûô:KZ§ˇß˙ã1ĩÜxŨzœÛƒÁĀMŖFđ˙ŪžĘ†ÍŠ\|Á@ÂÃĒ‚_Bûļuö™ēu;áaĄ´Ž;€ķûõĻgˇŽ”ēUM÷Žđõņ!00€üüƒ """gĶöš@‹Å€ĸĸ"Ė~G.#†j|}ŧĻÎh0âp8ęė×ėë[­Ín¯1ĻĩЊ˙Ą¯ėŗ`-´ÖۏŲf''7—‰?âÚWZ^N|ÛļGõSŊßÃüĖf’ĪíÂō_V2bøŦß°‰ëŽYį<N…ģîŧĨŪ×Ō!ž-ë6lfô-7œPŸ6›ŊÚڍFüĖf āãã}Ô>gŨß+9sœļxø•ŲĪLņQA­ĸĸ’ĸĸĸ÷kĩāīú:,ôČeÍÃcPhĩĶÎJtTdŊũ͏MĒ1n^~^ĩ1jĶŋo>ųt:­âZŌēUĄ!! šãО{ī>ļnßIr×N|7į'ŽŊęōãļņˇX(*˛š;deãÛLo~‘Sã´N`‡øxöėÍ`ĪžŊ�,Z˛´Qũ-]ļ€ŧü|RĶŌHJėP㘤¤DrrrHIK c˙~6§¤ŌõĐĨĖēúILė@öQíl6;S>øŦŦėc˜LUKYT|$āvîԑJG%_Íú†~}z7jžUQYɧ_~Í5Ã/ãšá—ąas éÛwžmyųĩļKhߖœŧ|Ōļnā×UŋķŅ´ĩ^‘ŗĮi˙ˆ˜ˆˆpŽu ¯Ŋņ6&}ûô"** g=—|ëb2™đņņaōSĪb+˛sņ…Ci×ļ­ë,Ũaū Əeƌ™””–bō0qĮ­7Ķ"*ŠĖĖŦ:û¸güXĻĪøŠ"› ƒÁĀ€~}‰ˆ¯1†ÉdĸWĪî<ũė‹\?j$ƒžŅh¤_ŸŪ,X´˜ž=ē5|ŅNĐ[S?ŽąÍd4ōú OđãüED†‡ŌĨc"�× ŋœi_~Í#ÜÅ˙žũ‘ÁįõĨ_¯î5ڛ}}}ë |ųõw—Âm7jꊈˆˆH3Œ=Ú9eʔ“n¸vķVbĸ› ¤“™™Å¤ÉOđáÔwΈ~ęōĶÂÅėØą“1wŪ~Rí2d“ÜQˇ""""ĩ[ģykƒ˛Â˜1cˆŽŽÖmãšR^~sįÍį’‹†6w)""""ÕčŽ!MdÖˇŗYŧtÃ.ŋ´Ų>PDDD¤.gmŒŒŒ8%—pOU?Į1|#†ŸüXDDDDN]qC """"nH!PDDDÄ )Šˆˆˆ¸Ą‡@“ÉHe>āYDDDD§Ŧŧ/ĪÆŊŋˇÁ!048ˆÜŧƒ ‚""""§QYy;vī#<4¸Qũ48BÆD†›OeĨ‚`SXģyks— """g/OÂCƒ‰ jT?:ę ƒ""""röĐCDDDDܐB ˆˆˆˆRqC """"nH!PDDDÄ )Šˆˆˆ¸!…@7¤("""â†EDDDܐB ˆˆˆˆRqC """"nČŖ1÷e搓ĘJĮŠĒGDDDDęáåéADh0áĄAę§Á!0#3—ü‚BÂCƒ1uBQäL•q ›äŽņÍ]†ˆˆœ"eåėØˆhDlpzËÍ/ 4$HPDDDä4ōōô M˲sķÕOƒ\eĨCPDDD¤xyzPV^ҍ>”âDDDDܐB ˆˆˆˆRqC """"nH!PDDDÄ )Šˆˆˆ¸!…@7Ô¨ÛÆŒ+W3ÁBöíÛ@ė9ą\zŅ…ôčž @ff“&?Á‡Sß­Ņļž}'3~ōš]đņņ9á6§bÜŗUCÖ `æ7߲hņR†^0˜WkĸęÜÛćáīˇßJb‡öMŌ˙>CyEÍĪžę߆ņwÜŌ¨ž[ģž.I‰x{{5ǟŗQvNĪŧōožôTŖúŠm [ģž%ËV˛?3 €˜Q ØŸŽ“4Ə>CEe%Fƒ�‹Åä.¸ōŌĄxxœļ_"ŌÄNË˙æų 1ûûšņo×ŅŠc‡ƒuë7đŸūKĄĩ!ƒÖÛ><<Œ×_yŠQ5˜õ I N:Ô¸Ģ†Ž×ĘUŋ1úīˇŅĩKį&ĒLN‡ Ŗo%žmëSŪīė9 čĐŽ­[†ĀА ž{ėÁF÷sė.Yž’š –píU—“Øž§ƒ[Ԙ6cÖ"úölĐ8wŨy‹ë9“Ëԏ?Į×ׇK‡jôDäĖ`ęŪŊû“Ɲü›ŲyXüŽ{\Q‘7Ūz—;˙~ ={tÃËË oooââZŒ¯žfčC())aáâĨøû[ø÷”øūĮšØl6’ČĘĘæÁI2|Ø�¤oŨĘ;īŊĪķæŗhņRĸ"#ˆˆ`ÃÆMŧųöģ|ûũŦ]ˇž6mZņŅ'ĶØąķOūXˇžˆđpĸ"#kÔY[;ŖŅČÂÅK\ãŽ]ŋwŪ›ĘsæąhÉĪŨĸ‡ƒO?›Î´é_˛`ŅbVŽú6mZãoąÔē=0  ÚØûöīįĄG&đŨ÷?ōÃÜų<xÄ„deeķĐ#“)//įw§2dĐ@ŌŌĢæ?oū˙ŧooZž[o?õ­Ûąc¤ĨoeįŸģ\ë5õƒ(¯¨ MëV�”••3ūîčÜ)‰  @×<Ū|į]vîÜEZÚ6JËJéĐ>žÍ[Rj­ĩļy{†ĄŽõ>VVvNz_ž›ũß}?‡™™téÜŠŪy?đĐ$ÚĩkKHp0üãŽ{‰Œ ᜨXã&ÜOī^=ņ3›ŒUKŨ;vî<Šįc€ŋ˙I­Ëō_Vđö{SY˛t9…V+{öfĐ-ų\ÂÃBû˙ĀZd§Eĉ 0ŅĪôHîBhHp­û—ũēšŗf͝Ww  ū…YŗįҎg7ží<<Lœ]ŖŨԏ?gמ 6lN!<,„ˆ°PŌļnį?ĶždŅĪŋōËĘ5x{{EfVOŊô: Ė[¸”Ÿ/ŖĐjĨ}ģ65ú=ŪąÛwîâÃi_˛`ér–ũēšđđPÂCCČÉÍ㊗ūEEE˙ųôKôéÁ×ŗįōŋo`é/+ų}ŨFZĩŒÅßbŠŗÎÜŧ|ž|áu|ŧŊ™ģ` s.!+;—¤„š÷jÎÉÍį‰_ãŌ ˇ]]5솺™)Æ˙]5įv§'^^^œĶ‚� ßü0ŸAú_p°Ú\Īī׋UkÖōág3XļbĨĨe˘õ=ᥥ„…†Ôxø™Í0°vÃfúõî~F­Ģˆ;;wR?ßûūûīņ÷÷oú3[ˇo ųÜŽ5öõíĶ‹>ų”;˙$8(‡ÃAnN¯ŧđ,YŲ9<ņÔstîԉ Ā#ĄŠ¨ČÆ[īLáöÛn&šköîŨËŗ/ŧĘKĪ?Á`āßī}ĀÄû'ĐŽm[æ-XÄģS>āņG'1zÜ}h"G…–à ­ĩļ›0îŽcJKKyoĘÜ3aI‰ üūĮZĻüįcŪíڅM›ˇ–žžy +WũÆŦ#/.¯ÖíįÄÄTßd4Q\\‚§—īģ›ÍÎ?'?AĮ¤$ÂÂBíķäÍ×_Áj-âÍwŪãžģƒ”˜Ā™<ūĖķÄĩŒÅĶĶĢÎ~bĸ[Ôšn&Sĩ1ĘË+Ē­Wn^˖ũĘĐ!ƒ€Ē`B\\Ëjķ¸{üØj—* ­uÖęk6WĶpč˛Ķ‰ŦˇÉdĒž~&#v{1Ü{÷xėÅvî}āa† ˆŋÅŋÎywė˜ÄÖôm´mŨš”ÔtâÛĩ%5m}{÷f×î=ÔZĮŽ•Íf?éįãCÜwÂ뒛—Į´é_đäc“ˆeÉĪËÉËËĢãÛé1 oOÖmÚÂĪŋŽĸk§$,^ÎũãīĀ`00rØĨ„‡×ūéļ›Fq˙#Os߸;đˇPdŗ1õ“錞õ:´kCvN./žūobcZāéáIII)žžŒģãfėÅÅ<ķō›$´oGÛÖqÕú5uÛ"2‚÷?™ÎŖFĐ9ŠûdōÚ;īķÄC÷b2™()-ÅĶĶ“žx˜-i[ŲēãO&?x7&“‰ß×mdÃĻü-uÖéëãCqI `Ėm7R\RÂŖOŋĖyũzVįÆ:ÛųûųÕYķąk¸qK�“jŒŅŖ[W>˙ę[víÉ 4$¸Ú\ ōåŦīyøŪąDGE2wÁ˛˛s0™ę~™¸ÃéÄhŦújˇŸ‘ë*"'§ÉßbŗŲĒ-:𧇠EE6× "<Œ„„ö¤§o­ÖfÃĻMX,’ģv 66–öíÛąvŨz6§¤Aģļm:x ?x˙qk<‘vŪŪŪŧöę‹$%Vũ°íÔ1‰˛ŌRō  $ŋ Ÿ•ĢWc-*ĸOīž vyÛë2 _�üüĖ$´oOjZ8ú÷éÁ`¨Ē52ÜUGTT$’YŋisŊũÔˇnĮŽqŦŪŊz˛wß>öfd�°zÍúöéubëZG­Įŗžõ>ÖáæŊzô�Āėk&((ˆÜÜŧzįŨ11´­Û�HIK㠆°m[Õ-Šiitę˜XëXG×ŨįãÉŦKZÚVbĸŖ‰āüũ1M5ę:ÕŪ~˙î›ôTĩkÖm8´n5‚Ÿ/ãĶ/ŋæâ ēÂrBûļ„ĐŠ[ˇB‡CgėÂÃBIhߎ-ŠGūĪ÷>ôšaŗ¯/ņm[ąuûÎ:ûĢíØÍŠéXüĖtNĒ:IÛVqlؒŠÁ`ĀápĐŗ[W ūū<XȚĩ°Ųít?ˇ3— To‡ŸģŨģVŊüÁ×Į‡ĀĀ�ōķÖ;÷úÚÕWķąėv;ūūĩŽáa2ág6cŗŲkĖuëöÄFGUuUdč 8œÎ:ëÍÎÉc؊UœÛš#ĀģŽ"rršüL ÅĪŧ‚œNg_öåY‹°¸ļųuŠÔßâG‘ŨV­Íf''7—‰?âÚVZ^N|Ûļ”WT`>ęŌÉdÂßbĄŦŦŧŪ‹ŠlĩļŗÛėՎ[ūëJV¯ū §Ķ TÍÅ鄸¸–Œs' /eÚg_Ë ×Ēw{mĖžžŽ¯}}}°ŲŒoąTũ ˇZņˇT˙ĄoąX°ZëíĮ\Īē;ÆąüĖf’ĪíÂō_V2bøŦß°‰ëŽYëąG;‘Zëę^īēøúyũĸŅ`ÄápÔû|éÖ-™ĪωĶé$=}7\w-?ΝĮÁÂBRĶŌ<đü:Į:\wCž'ŗ.EEE˜ũŽŧėÂh4T{ÜTŽ~=Xm‚ƒéߖu63ú–4†ÕjÃrĖ\üüĖX‹Š\}|ŧ|ííŊ¸¸Îūj;ÖˇØ‡Üüū˙šö•—WĐĻՑ˙ƒŋĒīQlL nŋé:~ūu˙ûöbŖ[p͕—tFŖ‡ĶqÜų×ÕÎ^\|ܚÔîĮÁƒ…ĩū|­¨ŦÄfˇc9ęe;‡įjŗÛĢŊĖÁÃÃ˙c^ŪķŪ‡Ķ0šŒā?zv;—AĒūĀ<‘›s]EäÄ4yŒo‡ÁédÍkéŲŊ[ĩ}+W­ÆĪĪLëV­ČËËĀfˇáoŠ ……Ö"Â#"Ēĩ  "6&š'›TcŦUŋ­ÁZXčz\YYÉžˆ ¯ˇÆ€�˙ZÛyšŽ,Ά›˜3g>?ö0!ÁÁ؋팛päla§ŽIUgĢĘĘųaÎ\Ū˙čž}rrÛkcĩāīú:,ôČeĩÃ?ß(´ZiguũE_W?õ­[^~^ĩ1jĶŋo>ųt:­âZŌēUĄ!!uˉ×ZטĮ[īUßŧ‚CBXŋa#fŗ/>>>ÄĮˇ#55îbėčuö{¸î†<Of]Ė~fŠúc ĸĸ’ĸŖ~I6—Ũ{÷ąuûN’ģvâģ9?qíUuŸáŽK`@õĢ�PõYdđË}6ģŨŠlvBęxb]ĮɃwŠq|ÁÁĒīÍŅá)Ą}[ÚWøų‹~>tĻķüãÖy*ÕWķąīÚnĶĒ%N`ũĻÎ=æĀkÖnĀ×ׇ¸sb(:ôíášúúø`/.q[YYé:æ°Ü~Sœë*"55ųå`?ŗ™Çņņ'ĶXąr5EE6JËĘøeÅJ>Ÿū?Žģv$žžžŽã—,]@^~>ŠŠéŽ75–˜ØėœRŌԁĒ31S>øŦŦl:%%‘“Ëæ-)�,]ļœ÷Ūû�ĒËgEÅÕČVWģŖˆåčOP`ÕkįĖû ŖŅ@Ii K—.cÚį_āp8đōō$:ē8un¯ËŌeˏĖ=-¤Äš!$))‘œŖæŸą?›SRéú˙Ûģķđ*ę{ãŸ93gI€Ha'A07ÜX-**.€Š¨ŊjAmŠ×ļOąUk­Z}nŨîĨ.čƒÔåiEp_dQŦÖ=ĩĸu‚ ČB!Éə9s˙H $!ÉIqŪ¯ôĖĖī7ߙœG?Īī7ŋ9ųG4ÚOc÷­ŽŨĪíyŋŽČ;\NÂŅâįž× #G4x Í­ĩ!Ũßuį Ē—^}MC{¯[|hŽVŦzS}zõÚkĸ%ũ7ôŊjÎ}rčĄÚ¸é;mܸI’´ō7›uũmÁv=ąđYM™|†ĻL>C˙úėßúj×4íWßŦSIéžSö’dŧīÕîŅŧ!‡RqiiÍī÷[ˇé˯×*oīũÛī}$É _¯[_3uøÉškËÖĸŊú¯īØÁšUTRRsŽŠĘJ=ūˇgTT\ēO}īŧ÷ĄŋđŠ‰„‚–Ĩž=ē7šÎ†ÔWįū4VsŨ{˜’Ҥ‰ãô×ÅĪ냂OTQQŠx<Ž÷?ú§žyáU{æiõžŌ%g@?münŗļn+–$­xķízËhIuĩÅ}Đ:Č+bN›xĒézˆ–-[Ą?!C†úõëĢ™?š\GīúŸ“p”’Q$ŅŋŋU;+uúÄS•;0G[vŊûJōBåĩ?ŊZ{ząĘwî”a:é„Q5Ģ1¯ũŲÕzėÉŋǞĸBYYYēęĒ+ešĻŽ~Ŧūp۝ēpÚųûLķuę”Zoģ= ?îŊûŪšsÃMęŌšŗÎ9ûL•¤îšwŽŽŸķ }ūŗú՜dZŪ”ß/ģD=ŗ{ÔģŊ>Ļi*‰čĻ[nĶÎō M<u‚r ĒĨÛ­KįΚũĶĢõôĶ΍*“i™ēōĮ—ĒgvļļlŲÚ`?’ŧouĪQßũ :aä-_šJÏÛ{Dˇ!ÕZ÷œu5vŋ˙đûkF‹÷gߗÃĒW^[ĒŗÎ8M’487W÷?ø°ĻžNĢô_ß÷Ē9÷Ĩ{÷,]8mŠîųß?Ë´Lyŧ˛ŗŗå&ÚvZlîÃŲg›čŪ;nÖĢËVĒGVĻŽÜõĖä”É“ôäÂgõÛ_ūL‹^xUcOĨŽ?vŸö@@G™§?ũß<{æD4r¸~rŲt=˙ĘRÅbÕ2MS3.8W=˛ēi[Q‰€"áîŧ÷íŦ¨Đ¸“OPN˙ž’¤%¯¯ō ė5jėØY?žXĪž´D;+ŧgãFwŒēeĻ׌Xívô‘yúō›uēųŽ{dšĻ:wJÕES&ĢSjjƒuÖíŖŽēu6EJJ¤Ášwךį=ʉ:$­‹V­~G[ô‚dęÛģ§.›>ĨÁ@Õ#Ģ›Îœ8^s^ NŠŠuü1ęzH—&ÁÆj<÷@ë0fΜéΛ7¯Ų >ûZŊŗŸfm-?ô—6ˇÖõĩõ}z}Å*­[ˇ^ŗ~ry›ôļņŨ÷ÛtôáëÕû{ąō?>øxׄÃZí%ĖmaĪ:6uŸ#üõÍÔ_}Å^%�8¸|öu‹ūû>kÖ,õęÕĢcül܎ōŧ䚝•ƒ{ ��•IDAT”–čĩĨËtڏ&´w)€BÁ œÛŪeė×ÁZg,V­97ߥoÖJōĻ­Í€ŲäwOøa8č˙įĶ5ŸéĄ‡įkÜØ1í]Šo=÷ÂKZõæ[:kŌé Žl¤cęŋHs°Ö‡tņÔsô×Å/ČļmĨĻĻčōĶä'á�_éĶÁ�ZŽ#N�öĪĶÁ���h]„@���"��ø!��‡ZM3 §_X ��€}UĮm…‚É­čoqĖLīĒâ’2‚ ��ĀTˇĩîÛÍĘĘlø÷ԛĸŞwīĨĸۊKå8Aā`VđŲ×í]� •„‚–˛2ĶÕ=ŗkRũ$5ŽØģGfM��@ĮÁÂ���"��ø!��‡���>D��đ!B ��€��|ˆ��āC„@���"��ø!��‡���>d%Ķ8*ĘļíÖĒ���°,KiiiJKKKޟ–6ŒFŖÚąc‡233eFRE��� i\×UII‰$%[<F•žžN���8€ ÃPFF†ĸŅhRũ´8Úļ-Ķ4“:9���š/$ũ8^R C\×Męä���hžÖČ`Ŧ��đ!B ��€��|ˆ��āC„@���"��ø!��‡���>D��đ!B ��€��|ˆ��āC„@���"��ø!��‡���>D��đ!B ��€u˜¸ąĖЌg‚x_D}îŽč°šaŨ¸Â’h›ķ­-1ÔųöHÍį[Ū°Ôûîˆn{ĶÚg_K ™Öę û˙<ĩÆTyuËÎągŨí%™ú�@ÛhŋdĐL—<Ô¨ž }>šJKZ_jhĘĶ!u Iŋ9Ųnõķ čęjíĩU5ŸŽ15rĩNËMČIh¯}míæU–Æ pÔ9Ôüļ{ÖŨ^’Š��´?ÛĐíãmEvUœ“îęå鱚Ī_Ŋ Ŧ_hkua@›w:{ˆŖßņâ;ē~yPĨ•’iHwMŒkÂ@/-[Đõ¯URi(7#ĄšgÄ2ĨüÃ*ŋĄJ, icÔĐėWƒēâGS‡95ûj?4ËŨįælęOo[J Jį udėąī•¯ē占ĘbRؔn×äÚļ(¤oË Mx,Ŧģ&ÆuZnĸÁcëĒ[÷õ'ŲZš> WU^-…LéēQļ.>ŌŅēRC'?Ö5Ãm=ôĄĨĨ3b˙xX7ž×ëkM}žÍĐú˛ČPÁúļĖĐÃgĮ5˛OĸŲõ�€ö×aσ§ stå‹A=øŠ/Š šŽÔŗ‹”žâíˇRYLŠ˜Ž^œ^­×/i~Ĩˇŋ ¨¤Ō DsNŒëĶkbzōüjM_Ō–riÛNiÆ3!=xf\…×UéœÃ]úÜŪCV §VĢWWķ'ĮuũI{:6ĨŊäMg˙biP‹/¨VÁU1õIsĩ)ęÅŠ¸tés!ũĪâúrvLˇŽ‹ëŠB˛Ōãįzķ¨Ë/‹é´ÜDŖĮÖUˇîm;Ŋûpûø¸ūuMLOM­ÖĩK‚ZŗÕPȔĸ1)%(}{]•Ō"RY•Ô-Uzūĸjũaœ­Ģ_jÂĀ„^ž¸ZWãčŽwŦf×��&Ū?)Ž›FÛZšŪÔøĮÂęoDŋ\TYYŲųŽ$/žÔĪŅę -ũÆTfŠĢIƒŊ’×ŨÕ }zų+SĢ Måf¸ąkDëĒáŽ^›kr]MmŋzC@Ã˛\åu÷F/;ʑĩëî§Ŋéå1ŧ>N”ĐθôŸÆ>ũ4įØúj”îjlŽ×67ÃՄA -[kJŽ+;!]”gË0$šŽ\I“{÷sPzBaSŊëŧƒŌÚŧ+Ä&S��hf:8`HĶp4ũGŽ+}´ŲМåA]ņbH‹§yŖM†¤CÂĩmŌÂRiĨˇmC™Ą!skwVŲ†FõM(fģ꩝ēĩRfĒ´Ŋ‰üíTŊíë*ސŌ÷8.`H)ĩûŸüÄÔĸĪMš2jωûÎ(7ûØ=m)—ēĨî}`FÄÕÖōÚĪŨęÔžû9ž€ĄŊžé ’ŗGW-­ ��´7GĨl čüaŪH“aHĮõvuŨH[ŗ—Ô&WRQ…”ÕÉû\\i¨WWŊēx#po]žīŨ3Ÿ›ÚVQ;be'ŧį ÃfĶjëŅYõļ–åz#j줧HeąÚ qĮĢU’–~ĐŊīĩúŋĒÔ;͛†ÍžĢūÕĮÍ9ļŽž]öŽU’Š* éV›ÖŒ Ū%S��hb:8!Cŗ^ é÷MUÆŊmßEĨų–F÷wjŽ3$-ø§Uŗua@c$4&'ĄõĨFÍëXJ+Ĩ˟j}ŠĄq9Ž ˇZšŪÛˇ ĀÔ%Άš†šÚūÄ~ }ēÅЧ[ŧT›2˙Sn¨{'W=ģHNBēī]KĻ!•W{#‹†¤íUÆ~ŨŸąmØ^{ž(2´r]@§ęė§eãšS?��88tˆ‘Ā>iŽ–ĖˆéļÕAŨņ÷ bļ”vuū0G7œRģPà HƒŽF<VIĨ4{„]ķŦŪĸi՚ŗ<¨â o*ķ’|G9éŪØĶSĢõķ%A•UĘIOč‰ķšūRģô”Ļĩ˜îęÎ qģ0Ŧ`ĀՅyŽgērŌš‡9Z¸ÆTŪũauë$ŨxJ\“'4ųаŪģ˛Jį utōŖaŨ1>ŽŠ‡7~l}SŅģeĻz‹E~ģ"¨ō˜ˇ:xŪYÕœéjSY“/yÍŠ˙Ęc“ œ�� u3gÎtį͛×놅……ĘÎÎnƒ’Zfm‰ą×k[���~Čž˙ū{ 0 Ųíf͚Ĩ^ŊzuŒé`���´.B ��€ũ`Bā  —Š`��€&úÁ„@���4!��‡���>D��đ!B ��€��|ˆ��āC„@���"��ø!��‡���>D��đ!B ��€��|ˆ��āC„@���jq´,Kļmˇf-���hÛļeYVR}´¸uZZš ”™™)Ķ4“*���Mã8ŽŠ‹‹•ŸŸŸT?I…Āüü|EŖQF��˲ԧOĨĨĨ%ÕĪ��øP‹GŖŅ¨vėØĄĖĖL†Ņš5�� ŽëǤ¤D’’ lņH`4Uzz:��ā�2 CŠFŖIõĶâhÛ6 B���ÚA HzMFRĪēŽ›ÔÉ��Đ|­‘ÁX��āC„@���"��ø!��‡���>D��đ!B ��€��|ˆ��āC„@���"��ø!��‡���>D��đ!B ��€��|ˆ��āC„@���ę0!pc™ĄĪ5đžˆúÜŅasÃēq…%;ŅŪ•ynyÃRīģ#ēíMKkK už=˛ß6û;nČܰVočĸ§Ö˜*¯nī*��@SYí]@S]ōlPŖú&ôųä*E,i}ŠĄ)O‡Ô)$ũædģŊËĶÂ5ĻæOŽÖiš 9 iíĩUí]Ōuķ*Kc8ęjīJ��@St˜øŲļ€no+˛ĢâœtW/OÕ|–¤ekēūõ J* åf$4÷Œ¸†fšZš> WU^-…LéēQļ.>ŌŅ—E†F/ëW'ÚZ]Đæ†ÎâčwcŧPųÎÆ€Ž_TiĨdŌ]ãš0pߥĮ …´1jhöĢA]qŒŖŠÃå?Vų UÍęgūĮĻūôļĨ” tŪPGF÷ĸąēו:ųҰŽnëĄ-}ņŗ*Ŋ÷]ë^Ũs īĐˇe†&<Ö]ãúã[AMĪŗuÕpG’TeKũî‰hŲĨ1•íļđ���ZS‡ S†9ēōÅ ~>ÂÖØœ„†dēęŲĨv˙ļŌŒgBzizĩFôIčĪī™ēôš^Ŋ8Ļ …ôôÔjÍIč›C# +?;ĄK*‹IĶՋĶĢUZ)õPDã&44+Ą …ôĐ™Õš48Ą5[ ûKXŸ^SĨ÷ŽmáÔj ™Ö#gĮuJ˙„Ö–ÔÆˇ’J5ØĪž6–úÅŌ Ūž"Ļŧڭ?Z†ëîßÕU4&ĨĨo¯ĢRQ…ZũúCĻö:GĖ‘ŌīŒhųe1ew–žŨnëņY5!đĩ¯ę{ˆK��ā Ō18“t˙¤¸nmkåzSã Ģ˙ŊũriPeģ˛ÔĒBSšŽFôņFØŽîčĩ1­*45(ŨÕØo{n†Ģ ƒZļÖŦé{FžVŌS¤“ú9ZŊ! ĨߘĘLq5i°×.¯ģĢú&ôōWϚŖŠũŦŪа,WyŨŊ tŲQŽŦũüuęĢ[Ž+;!]”gË0Ô6×_įuMËsôŲVCŸoķv.ūÜÔEG8Íēo�� mu˜‘Ā€!M?ÂŅô#šŽôŅfCs–uŋ!-žV­ĸR×HíH“2SĨ-åRˇÔŊG 2"Žļ–{˙nH:$\ģ/-,•VzÛ6”2ˇvg•mhTßæ­D)­lZ?ÅRúõ )#Ĩá~Ē{ˇnŠŪ?ÛōúwŸŖŽŽiŌĄŽžøÄÔMŖm-]kęÎSc _ ��8ā:DÜ•ūą) ķ‡yÄ0¤ãzģēn¤­ŲKŧ•=:KÛ*j‡Ĩė„÷ė\ví’TTihH7/š’Š*¤ŦNŪžâJCũģēęÕÅ•{ëōäÂKcũė9mœž"•Åj?Į¯Ž†4T÷nģGčzviũëßTļ÷9ę3#ßŅėWƒ:ϧĢcz&Ô'Š`��&b:8!Cŗ^ é÷MUÆŊmßEĨų–F÷÷ĻĮå8*Ünhåzī’˜ēäؐÆå8Ú°Ũ¨yÕĘE†VŽ čôCŊv†¤˙´jú\]ĐØ ÉIh}imģŌJéōįƒZ_ÚHōŠGSû9ą_BŸn1ôéoûÃ5>íÜPŨuĐö×oŧ~ļWÕî;u`BNÂĐīVYšÎT0��1Ø'ÍՒ1Ũļ:¨;ūTĖ–ŌÂŽÎæč†Sŧ•Ŧé)ŌĶSĢõķ%A•UĘIOč‰ķĒ•™ę-ÜøíŠ ĘcŪęØygUkpĻĢĩ%†Ŧ€Ô9čjÄ#a•TJŗGØ5Ī.šV­9˃*ŽđĻg/Éw”“Ūŧ­Ž‘†ûŲs$p`ēĢ;'ÄuWæ9œéĘi`öšĄēwŌíÖ×_÷VĀ[Í|ōŖaŨ1>Ž+ud¤ ķl=øĄĨs‡�8Ø3gÎtį͛×놅……ĘÎÎnƒ’œĩ%Æ^¯ré(ZĢîļžūŪ7õÁ怜o“ū�đŗīŋ˙^ hvģYŗfŠW¯^c:ĪĻ2éžw-];˛ũ_ä ��öÕ!ĻƒŅąÜúĻĨų[úõ‰qŪ �ĀAĘ×!pP†ÛáĻ‚ĨÖĢģ­Ž˙ĻŅļnÍ ��3σ��|ˆ��āC„@���"��ø!��‡���>D��đ!B ��€��|ˆ��āC„@���"��ø!��‡���>D��đ!B ��€ĩ8Z–%Ûļ[ŗ���4mÛ˛,+Š>ZÜ:--MĘĖĖ”išI��€ĻqGÅÅÅĘĪĪOǟ¤B`~~žĸŅ(#‚���ˆeYęͧŌŌŌ’ë'™ÆiiiI���€…!���>D��đ!B ��€��|ˆ��āC„@���"��ø!��‡���>D��đ!B ��€��|Ȓ¤­ÅÛÛģ���@–$uĪėÚŪu���ā�b:��‡���>D��đĄ˙i~Z5 `Rb����IENDŽB`‚�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/mod-client-list.png�������������������������������������������������0000664�0000000�0000000�00000054076�14156463140�0022400�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��[��C���åY���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨ|“åĄ˙˙›člˇ˛v´[KŲH˔Ô9ΞĐ'„#HÕjĢ8)ēQô¨Ũt:ũ8ŠsüĐ)Î9ĒCZ( ŗu œī!åœévlëFiwZÖ~ڝ…֍¤ūāķGúģĄMKJ ŧŸGHîûĘu_÷;šßšŽësęÔŠSˆˆˆˆˆˆˆˆHP\ę ˆˆˆˆˆˆˆˆœOÆöüן~Ę_˙úWŧ^/Ÿ~úi¨ę$"""""""2Ē}îsŸÃ`0ÅEõîË2ĻsҧŸ~Ę_ūōŒF#—^ziŋEDDDDDDDÄįŗĪ>ãäɓœ<y’ņãĮ÷ĘQē–––.ēč".ŊôԐUTDDDDDDDä\ō÷ŋ˙€/~ņ‹]uÅ.‡K.šäė×JDDDDDDDäuÉ%—pōäÉ^u…-Ÿ|ō cƌ9ë•9W]tŅE|ōÉ'Ŋ Q]DDDDDDDDÎK [DDDDDDDD‚Ha‹ˆˆˆˆˆˆˆH)l "…-""""""""A¤°EDDDDDDD$ˆˆˆˆˆˆˆ‘‘ ;’…ščs#YŧˆˆˆˆˆˆˆČúėĶ-_=[DDDDDDDD‚Ha‹ˆˆˆˆˆˆˆH)l "…-""""""""A¤°EDDDDDDD$ˆˆˆˆˆˆˆ‘‘ RØ""""""""DcC]9?Õüšž×Ū(æŖœäŗĪ> uuä6fĖ.ûüĨÜ=˙vž;>ÔÕ”zˆˆˆˆHĐÕüšž7l¤õŖŋ+h‘3vęÔ)ÚÚ>bũ/Íņŋ4…ē:ƒRØ"""""""AWôzq¨Ģ į›1c8uę3^)Úęš Ja‹ˆˆˆˆˆˆ]Ûß˙ę*ČųhĖZÛÚB]‹A)l‘sÆŠS§B]…A)l "…-""""""""A¤°EDDDDDDD$ˆˆˆˆˆˆˆ‘‘ RØ""""""""DcC]‘føJōJÍ,wŧÉü˜ķ÷5ũÛÃâ+ Ôŧ‚˛7siUDDDDDD†ĘEō7’˜2ņ+|!ÜDëßū‡JWvķI¨ë7 cĮ%1ûúkąšŋLmÔWa×î÷ų}ķš¸5r:įPØâĄnO!ëˇŲ)wUã>áÁk0b0ÅcIĩ’•“G†ÅęJŠˆˆˆˆˆČ3’0ífîŧ>™č‹{<üņĮ||ņ•L>›ÖēCŧUŧ›˙:—BŠqßâž2øú%„wÅtMŒcË ųˇæPV.xÆ^vô'C]‘:7‹‚…ßgmų 0D2Éj#5ڈMÕNœĨ…”—S”û2EKŦC]ß –WáJÉ`qŽĨ˙~p;)X_FdöR˛ÍĄ¨_°yhŦvRîŦĻŽÉÍ L&â'YIKˇĶÕ�ĩØ×Q—šGŽÕäį˙į6O­ģÃÕŅ^ ÆHLŅņ¤Úfa‰š0ߍöŽ$Īîéũ ÁH´ŲÆüŧ%äĻ̟™ˆˆˆČ陸æwsgĘå\Üį™Ģw°ėzbŋ5Œë¯áîbˆÛTDIĮoIŖ‹‘o^?ŗGĐŌÃ%ȸū ūĢđŖ? kâ›so&#e_ėܖĪ‹ËļQ Ā׸sIÉ.aŨĻ˙ā˙žCYX0aK#Å īdmš‡čšĢ(X•šĪõ›§úm/\ĖŪ‚ûY<e/Ė:÷/`e´ķP]˛—“ŲJZF<&ƒw]5åå%ŧüa-Yš7ö;V}b˜bË >2øA„ĮUHA]y A/ÛīëÕžMAQ5FK:ļ´x"M€ģ‰ GĨE…xæ/ÂzĄæ ŅŦ_›MdĮŊî:EĢY›ë¤Žč�ų֐Ö‹ŋ‹Íšˆōu饎ŠˆˆˆœwÆōĩšķš;åō}Lë҃lÜ´—?ud*:XĘ3ø#ßŊ7‹™ æņןoäßū’ AÉũ%->—Nü: üĄ#°­Æōĩšws÷?]>ā2…KÍ<ŧ€ 6põäzöŦ!ŋ܃aĘ ŠÖõZ�ŒæyáåUdeec $gqģ(^ö}æ^s%S&})W~‹š —QėęLC]äO˙:暨ëĩb-3ŋŽyŌ•,.ëSæžû™2éJr÷ôLT‡[N23g|ķ¤[)jôW˙ˇÉŊōë˜o-¤ķiwŲj˜û-Ļ\ųuĻ\9Ŧ‡‹qšh Ģ„—‡øŒEäf§c1'Ÿ„%ũFrs3˜äŠĀn¯=ÍÚFbĖĖ#ĐëŖŠŽ‘ŗ™éוWs":Ŧ +æ„bL1Ä$X°åd uMđAh2cļZąvüĨÍĘfųk¯0?牒ĸ=gu?[…ŗâœŽŋˆˆˆŒb_ž–Û§ëũØņ÷yîWŨAK—Ēxũ•Ũü‘ dĖŊ‚đŗVÉ Y֔ˁfŪá)ō–>îûëęÕŌ›/pų_:ēyÛ(ßdŽR;^ŒĖÍË!~ EÍŲä¯  H÷Ī}€Rw<3ŗ—› M唺âN'žŧåV iŠFŠėe¸<‹ˆīŧ&v—ãh4b0x(wTAzRWąN‡/S˜i5‚ŊķŅá–ķMÜgdõŖŠKj™ŋ(Ą÷&8Šqz¤ædø&Ŋu­$;ˇēČT˛–d“íĨÎ^HŪÂČŽ_ÕG+ß'ÕM'đb 2ÚLjÚ,Ŧ åĄÎY‚Ŋŧˇ0Å`NŗaŗÄt SĒÅžž˜ĻÔl&Օ⍋Á–—šq°rĪ„—ŗoŧ ›ŋy‚L2rŖÁtē.ū† ļØ×Ō”šCĒێãÃFÜ0F[ČȞEŧącW@ųŽhŌr‘ęÉvđ pÁ€mŅĸ>šŠ.ŗã¨đmŖ1ÚLĒ­g]ÜTīąãø°ŗ LÄOąaKOĀÔŅn%kŠ9‘–Í”&ŽęF<1Å[ąeXģß_ƒļe ĮŨHąj1RTWGøÎin'Ë×PTVM“׃!Ԍ-wų‹:‡D:Y~ÍBęō^ĀæXƧ™üC¯ál=˯š“ēŧ—Ér­a­Ŋ7‘˜ŗWPgbÛō•”Õá1Æc[˛–ĩŲį!7Ž ËXSā îmƖ—O~vFÜßy +Ęb.gaé>–˜ZįôېæÚ@ūšBėÕMxéfĩd…ßáu?˙ųŗüüŲgũļęƒ=ă>Äũ$"""Ą1–¯MO!ēīÃãmŦXmëúoëŅÃŧ˙ūûėûĐ ûŪ:øO<zí?qõåāw}zˇĖžy-×ĪŧÖīĢíŪû>ģöžÜMP=•GO2õ ˙Ŋ[ūqôÔRBčˇĮČĨc[øãŸ=ĀXžô&_Ū#Zû•^ķė\¨=\FyĪ–jĘ]0¤b R—{įš•”ēÍ,~s/ŦČ!cÖ,2rVPPē–4ę(Ę÷õIĩĨađVāpö\š ŠØl‘¸]åtw:ŠÂQ~ĖéôŊFn9Ķ3ŗąāÃmÅT÷*ŅŖ¸¯!ų6āĄd}1uÄ3˙å×ÉĪšÛŦlr×ŊÃszƒĶn#ÂSEÉļ2šĸmĖĪÍãÜlŅM8ļ•ĐŲɨqO!E7ņļrķ1?-’&{!%]ŨvŒ€ˇËA]ŧųķm˜ŧÜ3Ģw6A´ŲĖé:RM1Cš;hđíđŌXn§:!ƒÜŧĨ,ÉË ūD9ÅöZˆ%;—™Ņ`°dąxIiĻnŒÄ›c04•ą­ÄIuãĀ…Öí)¤¤ĻĖÍ!77›´˜&ۊqē<ԕ˛­ÂƒynšK1ߏˇŧˆm{ēß!FŖ—F‡:syK–˛$ĪFt“â’ĒŽāgĐļ ā¸9ĩÔÕy0DGw|‰pS˛x!kĢãYüZ)eŽŊ囊XŋÅ]Ŋ䌍ęŠÖãH]AŅ›+°YŒFŽ‚įŠČxĮīc­…ęĸÅdĖ/Ę÷&åŋ˙=ĨšFė+WRŌŅ<Ž•ˇ’ģžKūkØ{)ZOÅĘ,ōJŲ/—˛Ø †š/āŦx‡%æÁÖ9Í6°‡å ×Sm^ÁļŊû)+}åæjÖ/|¸Ģ.==øāC<øP˙@EA‹ˆˆČų$ŠÉ_õ×?åc_Āōīŋį˙ˇ“ŒåŌ΋ycqáŸđ11$ĩ˙7đ]{ßgˇŸ�âė-�ūk÷^ūčoR–“Į(Ų=ø|-Ŗk{�žÆwdpKÆėîŋŲWö Ė.Ä.Ŗ<liôũ2m4œé-œl+i‚„4ŌĸŨ¸Ũ=ū°0×j„ę=8Ü`´ĻcÁCšÃÕŊļÉ7ÁĘ|›ĒTt^͏+(¯ƒčÔ´~Ŋo†_Î,˛2"ĄŽ„mŽēËØæō`°e`3úļÉáô@t:Y–Ū¯mÍɸ7P¨š›8á1oI"ÆdÂdŠÁ<+‡ųķĶ|ÃÅ<U8*šˆNËÆfŽÁd2cšÛ#:]=B*8a4cŗ&cÂ8XšgĘãÆƒHSæÂvmÁfîx]c–øHŧM¸ŒF_ö„ŖŅ8xûÉ’Mļ-Ēíl+XCūšį)(~‡Ģ–^×ËÎ 1iXb0Å$`ÉČ Í‰×íO5Îęħe–ƒÉh"Æ<‹™ŠŅ4U8{ŽŅÖ^m–ˇŽ‚:O€myÚÅ/O-Ž ËX_‰-kVGg–_ŠŊh–bb°ĖZÂ|ŗ‡ŨŲkõĻČ V.š…Åœ€qëaÉaqē¯ŊLé6Ŧx uķ;66Ūf#Æ[My5āŲà ÅuLĘ{üYbbb°dŦceF$Žõ…žũ`4a4�1­ãoę\|čÄš= sLĮđŗU¯ąíĩ%¤æ­Õ7pQĐ"""ržšŒ/ô› ä$Üĩ‘ü_í`ûŽm<ŋv ųŋ*åƒË¯ĀúÕ(Ļ^Īmßú2s1—_æˇÔžEč‚  ų?xņ…7xŋú/´~ |ÜF}õŋŗá…ĸ€īD4Ēļg.5gđÃ˝öá5A3ʡŗãĘĮũ;h¸)žõVTô}<•5¯“áīĸŠąš:/P]@FjÁi^Ķ×ks*ifXër҈…˜Ž^'ŅŠV,#fīķØ]`ŗ‚ĮYF5‘dؒúg~9֜lâˇPRčdų:_סŖ—7[öŦŽmjōmSü”ūÁŠŲBŧ>ķŌ"1ņě¸ļBj*æøâcŒÄÄt ŋq×Ņä‰ÄßûĘ+>!CyMēîøßŨËd°rĪX¯Č‡°ÆČč^¯n0Āi‡ķŒx;�‰ˇf“kõĐX[M]u-ÖUã(ŠĀá037+K ĐØH“Įˆ9ēgíc°fÜčûgmŖß6ˆ‰ÆāhĸÉ ]ųJtī60EGbđt,C�myVÚ¨^ƒmŌšŪ♙˙ųŗēˇĀhlÂąr%%ÕÔš=x<^ß /ž÷^6[čYÃ@׋‰īn/Ŗƒâ-ņ= "/PíĀå&ÚĐĢ kšÃ6ŽûĄ{;_§×6˜ĶI‹. øīâÉÍÁfĩ’f6aö74¯‡žáŠ‚‘ Āņũlų]}ŸŸĀåWq[†­ßŨŠN§gę`â“æ*ļŋRÅö3(c4mOāÚ¨­náBI4ĘÖhL& ŠŖ‡K¯īāFâ3˛™ÛãÂĸŠŧ„ōĻŠķt\”šsXŋ8Í˙%ŗŅD|<@ŠŠŅPė¤Â“CŒ§‚ō:#Š‹-–č8U`MÂewâ5¤“æw¨Ķ”cÎ&Ë\ĀZ{1vŦØpc/ŽĀÍüÎe<n<€Áhôģ=†šãėķíĢîz&`ËÍ%ēĖAEy åv/S<[†¯į‚Įƒ‡”Ŧ¤ŧ_YŅž‹ÃÎĸׯRî™2FiôrĸąĖA¸@övf„ÛĄ#1 b,XOŖ“’mvėvæ F<x0žž‡š÷tĪûnņîé‘M}ęXlËŗÔ.ņŲŦa~Wj4ĝ`ęķ>u‘Ÿu'ÅÆ ōķ_#ÕlÂH#ÛÎe}ß­4†ĩž?ūĪ€Ûƒ‡&ОžNQ˙ âDŋķđĐÖéŊ V–—žÉ¤õØV°˜mų ŅŠd,_EūŦ„믐EDDä|õ˙û7āK=úø?įŸđ§Ōõ,­ÎbÕÂ+šÔˇ ­ûhĀŌCJ\–Ā?ĪžÉ?#†/^ō =ú'*ģ9‰‘/Œ˙É/įâ“Ŗūŋ˙íģR;đĻ�Ŗ%dųˆÖ6⠃-×FÅÖ . ģ¸Œō°%É7ÁliĨNŗz^ ąæŦĸgžaØNyé�řLžīü†xϤ§3Øe˛%ÍJdQv¤šË¨6Lé9,¤Ļ)vUā'0XĶH z9 dÍOeũr;%{<ØŦvJ]âs˛č1Ô˛x;‚¤^—PŪŗ:g‹ŖŅ�n7nčßžn7nŒÄ÷œĩ׃eV6–Yāq×Rí´c/.„Ü`31‰9;‡´~ŗdq ëâĘ=Ķ|ÄC|$ė­ŽĻ1=ÆīqäŠvâ2˜ą&pņ~&Û@]GŦĀnø úŒ1VŌ&9yųÃFš°oô"'N7'Šá4Īû a<îž ų–ņ  °-Gē]�Œņ˜ÍIåsSRIÆļudtŊŠqŸ¤ėáŽ7“#ŅØÖŋFžšĪsF#ŅūÚf8ët­k!{Å/Č^Ņ1iqÁJ–įŨ‰qÛ–[XODDDÎS-|đį6f~ŠĮŧ-ņ˙D†åOluĩôéŨbäë)qA @#•Ĩ÷Kŧ,‰ģ¸ƒĢ쉋ųâÄ+™6ąĪr—\΄oÍæáį°á…mü>€Ā%ôęŲúôãŧ5ļįšIÜŊ"›)]ŨŽ:‚–˙ŧp‚õsļ@ZvŅxpŦ]ƒsđÅfŠ÷]čW;é7účõķ9@ēTà *U”۝xŌéėážjŽ2Ęk}ķŦ˜mé§\rå˜2rH3xp”–Qm/Æå5“•Õc¸RL4ņ Šē˙pĄęŠ>“ëŽ<߄Š.*úŨ˛ÚËQ×dîî ân¤ēļû g4%`™eÃlôĐÔäS<ŅF'<FL&S÷Ÿßŧ§ĢÄ`åž1ĢC““Ō2?÷ævģ()ąS^áėö¸ÃŨÎÁŒt;¸­YC‘ĶßIĶCĶ Lž;b™â‰6ž ŠŽį˛8 ;&Ž‰ņķ<4Ö6á5FĶ#p:ŅÔÔk>wS^Ŗ‰hĩåˆCāöā!Ļw�Y]ÂŪ:ü<ķõcN#Õp‚&¯‰ø„„îŋ#†ĶMú<œu�]ØËjģūkL°’ąj 6à ĒëFé%aŸđ§ũGč=XárŽūîXŊú'<“s]÷ņšė f~Ģ{‚—üáßųĪ>w"ƒ<{n %�_¸’ÛgÖšM<üãāâ(žŪcBâO>ų¤ûĪĶŗ/Ō…´Ā9ļ`]Âōš‘PWHî­Ģqøšļ7Žâ‡Yk÷]ܝūâÔĘ\[$xl¨íũ”§ŒÅ3¯dĘ¡{\ČYąYŒ4ē ()?AäKׯÔĻT 1^'ö‚2Ē1cK¨ûÁ”cœEļ͈ˇl=Ë \0%‡š ŊËNĩĄÎŪ{"]<Ø ßf QU#Áhą‘īĄŧh%Î*ĒkkŠv9ąRZgdŠ-Ŋû—~ˇûļBŠĩ4ē=¸ŨÔ9ËŠÃD|´ŒIX§itã¨nÄíņānta/Ü@A‰ëôAÆ`åe;3Čļ˜p; Y_ŧguuĩU¸ĘŪĻ  „ēČT2l %ÃŨÎūųz95VS×čÆŨ4Âí`˛–Û^HŅ'Õĩ466RWëÂQ\ˆŊÚȤt‹¯7™1 Ģ9’:G ŽęFkqí)ÁŅŅņ&0šąNņ=īŦuãö¸iŦŪCi…›čTk¯Ū!†NėÎZ_;Õ:ą—7ižâģõs myŽ€YŦX Õl+ØCÛMŗ–51%ՈˇÎuúģ# wŊÁg1?#×ĘûYŗ§ŠFˇ›:×Û,Κ‰íÎsŖoÂrī‡Õĩ4Č:~T˛ü;y °ŒęF7U86⠚)#:SąˆˆˆŒjyŸ-ûũĪ{q¸ą#€¸Œof\Ë×;Ÿ8yŒ’Ō?Đvvj8DqL1ûģÃŌĀ"ĖÉ$ŋ2APķČ߀q\ûĀcŦ_ũßßĒ,’û-{á-0ę‡˜°­{“5ÜÉōŌr͊ˆOMcJŧ ÜMu”;]4yÁŸÁšÖuÜĨĮ?ë’UĖu<@éÚšdUį‘e‹ĮØTNiA1ŽĻhææĪę1%‰T[<Ū5vė^#i='eIHgJdöŪčlRŪ†3)'-;ƒČŌb*挤­ĩõļb"#ׯÚōŠۊ;7›T“›íŔxŦ¤ė~æŽI1Xsa*ŗS^^B‰Û ÆHLņfææĻc‰éąsn$Ëļ‡ŊΊė'đ DF&`ÉČ&­c#ãgåm´cˇât{ÁMŒyķm–Ķ”{æŒÄg,"×\†ŨéÂQRŽ×cĀ`ŠÁœšEVzŌio íΰļĶOĖÖTĘKĘ)*Ē&5ë#ÜFâgå0?Ļ ‡ËII…¯ ÆHLņņ¤ÍŸ…5Ąģöņ9díąã°âpCdtiYˇK÷••…GéöēņĩeZ66kŸ#~Š ‹ÛAŅúFN`$:ŪF–-ŠĢmËŗr|ȔMū*'Ŧ\ŒmDNąą|Õ:lË¨x €ųY°­tVđÖ €u՛ŧ`ZɚåYđ€!‹mEĢnė8Ļ¤ÍĪ&~q!šˇžMÖË˙Aū ëø‘žŽĸUËČ_˙0Yų'đŒD'¤‘ĩö5–ôŽ$"""OøSiÃīæÎ”Ë{O€ûĨd&_ūęŋ‘Åm)ÆÉŋ°wĶfūmTöj01œß‘Œ—u÷âU:ösÉH™ĀũV˛…Š˙ū#­˙]ĘÖ hŽ–žÆœ:uę@mmmĐīÆ1æĸĪĩŧFg1/ãpVĶxƒ#†čh,–tlsį“5ĢOûÃW’WjfšãMæw _qQŧæyŠėåԝđ€!’xë,æį-!ģī0ĒW“6ˇ€&RYYū:Ų]O{°/ŧ†<‡‡Čš¯QžÎ:đkŖœn.ō§ßJ‘ÛÆúCŋđ&ÕíYIūÚˇq֝�C4fÛXžĘLéĖ[)2-Á^ēhtßZdPĩØ×Q—šGŽ5ØüŠˆˆˆH°-^žō Ö6ōĩi7sûõÉDŸæ–C­u‡xĢx7˙Õ<šīo“Ä]Ģîāę@o›ÔéãßŗaŲ6~?"u:?ŦÍ_qFëŸúėĶ ÕÄ§ąą‘„„„Ž˙ŸSaËĢą˜ų3—QņåĢüŪōHä °EDDDä\rfaKcÉßHbŠų+|!Ü'?ĸõo˙CĨ̊Š?ģ΁ÛųÚÜųäN˙JÉ|s’cû7ķËŌÚ>“KOŖ=l9†]čÜØ×ŦĻ3‹s´ˆˆˆˆˆČÄĶBå ō?C]‘áōđ§Ō莚r^RØ2Z5:)qTS^˛’r/ņķW‘›ęJ‰„Rļŧ3K¯EDDDDDÎ…-Ŗ•Ģ˜åËKĀ5wk–XB]# €Â–ŅjÖ:*>\ęZˆˆˆˆˆˆˆČ]ę ˆˆˆˆˆˆˆˆœOˆˆˆˆˆˆ‘‘ RØ""""""""įŒ1cB]ƒÁ)l‘  ˙üįáÔŠPWCÎ7§NęZ Ja‹ˆˆˆˆˆˆŨüīfŸ]äÜrŅžŸs{¨k1(…-""""""t‰_ãžEwūųĪ3FĄ‹ÁeŸ˙<?ŧw!ãcĸC]•A9uĘׯĢļļ–˜˜˜ā~Ņį‚ZžˆˆˆˆˆˆˆČ™:õŲ§A-¯ąą‘„„„Ž˙Ģg‹ˆˆˆˆˆˆˆH)l "…-""""""""A¤°EDDDDDDD$ˆˆˆˆˆˆˆ‘‘ RØ""""""""D [DDDDDDDD‚hėH~ęŗOG˛x‘QG=[DDDDDDDD‚Ha‹ˆˆˆˆˆˆˆH)l "…-""""""""A¤°EDDDDDDD$ˆˆˆˆˆˆˆ‘‘ RØ""""""""D [DDDDDDDD‚Ha‹ˆˆˆˆˆˆˆH)l ĸą=˙SWWĒzˆˆˆœ÷.ēč">ûėŗPWCDDDD‚Ė`0ôú¯°Ål6ŸÕʈˆˆˆˆˆˆˆœëjkk{ũ_ÈDDDDDDDD‚Ha‹ˆˆˆˆˆˆˆH)l "…-""""""""A¤°EDDDDDDD$ˆˆˆˆˆˆˆ‘‘ RØ""""""""D [DDDDDDDD‚Ha‹ˆˆˆˆˆˆˆH)l "…-""""""""A¤°EDDDDDDD$ˆˆˆˆˆˆˆ‘‘ RØ""""""""D [DDDDDDDD‚Ha‹ˆˆˆˆˆˆˆH)l ĸą#Yøą˙ųËH/"""""gŅ„¯|9ÔU9'ŒhØĸ“ąˆˆˆˆˆˆˆ\h4ŒHDDDDDDD$ˆˆˆˆˆˆˆ‘‘ RØ""""""""D [DDDDDDDD‚Ha‹ˆˆˆˆˆˆˆH)l "…-""""""""A¤°EDDDDDDD$ˆˆˆˆˆˆˆ‘Âsāã�� �IDAT‘ ę ˆˆˆˆˆ„ÚŅÚ?ķëĸ-|ô÷đŲgŸ…´.]t—}ūRž7˙v&&|5¤u‘áQĪš ­ũ3?ņeZÛ> yĐđŲgŸŅÚÚÆĪ_|™ŖĩuuDDdˆˆČí•Â-ĄŽBcÆĀŠSŖŗn""2(…-""""rAkûčŖPWÁŋ1cøû?ūęZˆˆČ0(lĨFð&:…-""""""""A¤°EDDDDDDD$ˆFyØâĨųČV?˛ˆĖIÃrÅd'Ĩtu3˛`ųĢīōAk˙ĩZw."qŌdīÜAįĶ­Û;û~÷cræüĩu(Ë ĩŅļ´ēøõ}s˜žŊŒ—vāHM ^Cąąŧ-Ô¸Ū㍧ō¸áÚL–īmumuøņ4¯~ŒƒĄŽˆˆˆˆˆˆˆˆŒ¨ąĄŽ€_^Ģī\ĀKUí6ō~ÄŊ™Ķ˜<ÎĐš�õGvąqíŗl,¯âûĐú‹Í<?sÜi‹ŒČÜ@Í/ §]fä4p¸˛%¯+"""""""gÛ(ėŲâåāLJ}AKø–lÛÎķ÷\×#h0—r3+^ÛĖs3ĸ€ãŧķÄŗ” 6ū$$A ā­âHeh^ZDDDDDDDÎŽŅļ4˙†įļ°.^Įŋ&Ä2÷éUÜ=;›%Ëncâ�‹4gKkå.V?¸€ßļ’4i2‰W¤1=ëVn¯ėŋlG9–Gö ”ũęa2¯õ­—tE3î|Œ×t¯Uö` ‰WæņN;Đöî˜4™ÄIV–8oŠ^¯å­ĄôņEĖøv ‰“R°|;“{Öî§Ųˇ$‡7˙æũKš¯WĪ ķÁ6û/ˇųČVVŪw;Ķŋâk“+ŦLĪXĒW÷SīõˇFŸíŧ:9ß˙)oVúOˇę7ßNâ¤É$Ũˇ Å5o^0āķũÚaûĀ{ô]~ųČætÔ7qR –k3™÷HöŖŧĸˆˆˆˆˆˆČđēaDÍûvq¸ŋŽ{3c_!b:+~>}ø¯ˇ÷a2ÚMC{QIŠØĻĮĐvŒƒŽ÷Øøč{”î[Ëög׹ŧÁFām;NéƒËøáž0RŦͰ%ˇqôH9•åŋaŝ5xßŪÂ÷&Bܜî6”ķæŽ ÚÂ&0sŪ4â QX¯[÷kãõûŗ˛rSS¯cb‹‹ƒåUėŨG}øfōYÆŧõ-LļNÁ–ØÆaį!Žėz–ģ ėܖÃÄŽŊŨœĮŧ'ĐBą–iÜ2# Zsx˙Ūzę�ĨÛįķęk?fjDW Qzß<~¸¯ÂÆ“2ã:&GAsån–gĸ23|ØmˆĄîoe™YĪRŲšüÍQŧ­ÔWšãYœ;wŗdÛæAB<‘ĄģdŌÍ<˛ā*žČß8¸é—ŧūĄ'ÔU‘uaËGĒhÂŦ×qÕH_×oåíĻĄ=Š™OoæųĖXē^˛ų]–dįņÖžĮX˛ŲÂæyžāĮæ{ēŨų +oâ˙ŋG0á­duÖ<^ĒĒ`ãß[faâĖą"ņĘvTĐf°p÷˛35ĀęuŊ–ãŧdũ;ßīîŊsôÕۙķT•îឈ)ä˙v3ˇÆu>YHæOsÄĩ•ˇ*sXšL×ã?Xu€ÆsË/6ŗĻį7­.VŪš€UE,Y;›}?ą`�ŧÎgYš¯ÂϰdÛĻ!Ewp3b†ŧZ)[˙ *ÛÃHyx;›īIė^/G_]DæS‡xní.n}åfN?ψˆˆČЅã‹\΄ņ—Â‘ Ö(FÔLsC�ãbc‰dé3uxSÎvŸņ(kz^ČŒģŽ]O8í87måhߕÛĸ¸õ'={€�†dîČL Ą˛&xˇ0nŸĀŨOö&5qÆl&´ĩˇčĮŨA ĀÄë¸! Ŗ5ŨÃfo*ĸ˛Âgüˆ}'ް°tņu„ ;ˇrĐ āåāöwiĸæÜĪ÷’{Ī›3qŪŖÜŦėočû§…Ŗõí@S{/‰w­esŅfv>y‚1Ŗ,liĮۑ :ģuŒ˜:}sÃ\5gšß`'ÂzW…5.÷˙$v3“û¯3.6Š0�o[đ–ÄTĻÆõyl\ãÂ�b™šŌw¸UãÆ…í´v6(5<âģ#Ōäūˇ×ŌąŊm.œ5� |PŲ„19ÍB˙ŽF‰¤[Į{ŗ6œũËÄÄp ûڟú™Ÿe“­&ƍtŒ'""""""˛Q6Œ(ŧë†A^oûŋV 5 �íŨü$?Øë™z�¨l€^Ũ!ĸbũöŽč $‚9kxT˙°ĄëÎJáÄF PŽŠtnoqą§ "ĸˆ‹�ZZhn’[¨iˆ .Ę˙:ãcãmÆĐ g˙°-~‚\‹yĮUÄŊ×o%<ŅBzę4ŌfL#ŨšĖ8MÕ"""""""#l”…-ÄÅF-4WÖЊe‡yģzŅ4”īĻaĀeÛi éMl ~z•t?˜Îí ā؆Žįŧ=bŧx[^Į0bˇĶæū‰›Íķo'qÃæ—Ų¸ã]×❚CŧŗåYŸĀĖE˛âžéôí($"""2_˜ô-ž9ūr.Ŋ¤ûąKžzY×ŋÃÍ˙DÆ%=æl9y’ŋ˙˙öĄû,ÖRDDBe”…-09-‰°h?ō.e­737€´ÅÛÚ ãŽ| "€–(î(ro^}ĪAJ{[WˆŅ_gĀačQ:×i?í:^īpK’\Áū‰HÄvĪSØîos%‡Ëąwįoxs_{×Ũà ¯˛ķ'Š#>'ˆˆˆœ¯žFƂ ŽžøôK\:ņfNėķāĮ_æ—mŖrDë&""ŖÁ(›ŗ"ĻßDz8Đū/m¨ü˛ÜëbuöLR3æÍ~ŗØ$–ä(€Vę‚6ģĘ(Eb,@õ }' éĐÚ@}ĢoŲ¸X€pƍhĨžÅ˙ž8ZŲQĪĄTūÖjnh  žÁŲ?†qÉL“Êˇŗŋ(›D aû˔]ģ\DDDDDDBbԅ-D\ĮįM� rÃb–ī=M0�@Ĩ<ĖÆšvÚÚ‰ŌØXĻZĮíÜyā4“ŲÖppo9G›C:†(H;ļī<äw{[īr¸ˆē†´‰žuR’v>p¸ü'.öîīœ ž>#íÍ ~^§†2G sŧ c˙´ÖPļs+ŋŪ[ãwéëu\´ˇŌŦ°EDDD†íO”l*áí]ûŲû~÷߁Ŗ'ģ–øĮŅCŊžÛģËΖM{ÕĢEDä1ú LÎ[ËK8pŒˇîŋ…ĖĮˇr°žįÕą—ú#;Xž•Éw‡°$î}öĮLâô!“äb ƒöũOŗd{MŸ0Ą™˛Įķ¸ëūģČ|tE>ƒ ÷õöđ6Pf‘ĢnĪ!% Ú÷?ÃĘž!Vķ~VŽ{6ÂH^ÃT� L3p eį3üē˛g ĩrxíOyŗš˙]Ŗ"’' Pĩ›7úŦspícll¨žCŪ?ŪCŧôȓä?úŋ<Ō?Mivėæ`•ČdŨûYDDDÎĀ˙~øėûŨ^Jvw˙Ŋ_ũQ×ķmÕ˙Ū빒ßĐ|-""Q7g �†dūõĩW‰xh1+÷ãȖ'šc˓„…G1." oķqZ:nVû–>ûßKÆD­qˇąægåĖ{d7{ÍdúĢŠLMŽÂāmáƒōr*[Ú!ö;Ŧyōfŋw üu’˜•-‡Xž};ī$ˆ˜ķĪgöŊeķ›˜ÃšŸ”3īŅ÷xëū8œ:ĢÃĄå‡hhƒ¨é?æųģģV‰˜ų#–Ļ`Eyk˛fōNĘâ"ÚiŽtq¤yKōĻņÜē÷h§Ŋ; Ižģ-[ÉwUņRÖLĘŦSˆ3´Ķ\YÎī4V/ŽeųģŸēe¨ûgÜMŦČÛÁŧuŦÉNã K*“cŖˆ •æ†*ēŽĶÎxnXöАƒ9‘@Î°Ėw_܉íČ.6nū eåUÔ7ˇĐĐFXøx’S,¤gæp÷ËŨÎ7nÎ:v&Íæõõ…ŧUîžŖö°pĸâ,ܐy÷.šÍä3IÕ0ĨOΧū‰­8*8Ø:žô9gXæ0MĖ|}‰;xnÃVʎ ´ŧ ÂŖˆKŧž{į-äŪĖä>ĮÆōŨW63nũ3ŧ´ķ”ŋGexÉ)7ąúÅûšĩõ6Ŋ§tIä{¯l°ę6îwQš˙=ކgōôûŲ¸8—ô†ĮX ´xŊxø~JCÛ?&ßŗ‰É[yiĶ.Vē(sĩŅNáQą\5{!ˇÜŗ[“55ވˆˆˆˆˆŒœ1§N:P[[KBBBˆĢ#""""rvå-}<(å|éŸķX1Û×ēi×ķä˙.› nũęŸĨ9}3•Q8g‹ˆˆˆˆČš§íx=ũøøo;ūŅ Ë‹ˆČųkô#9‡œüp˙gŲŽPWCDDFõl "…-""""""""A¤°EDDDDDDD$ˆˆˆˆˆˆˆ‘‘ RØ""""""""D [DDDDDDDD‚Ha‹ˆˆˆˆČ(5f˘PWADD†Aa‹ˆˆˆˆ\ĐÂ/ģ N u5ú;uŠË>˙ųP×BDD†Aa‹ˆˆˆˆ\ĐžŸs;ŒÆ$cÆøę&""į…-""""rA›˜đUŧo!á—]6*†íŒ3†đË.ãÁû21áĢĄŽŽˆˆ ØS§|}&kkkIHHquDDDDDDDDÎ-}3õl "…-""""""""A¤°EDDDDDDD$ˆˆˆˆˆˆˆ‘‘ RØ""""""""D [DDDDDDDD‚Ha‹ˆˆˆˆˆˆˆH)l "…-""""""""A¤°EDDDDDDD$ˆÆŽdáĮūį/#Yŧˆˆˆˆˆˆœã&|åËĄŽ‚HЍhØ""""""į]‹ˆfDÝŒEDDDDDDäBŖ9[DDDDDDDD‚Ha‹ˆˆˆˆˆˆˆH)l "…-""""""""A¤°EDDDDDDD$ˆˆˆˆˆˆˆ‘‘ RØ""""""""D [DDDDDDDD‚Ha‹ˆˆˆˆˆˆˆH)l "…-""""""""A¤°EDDDDDDD$ˆˆˆˆˆˆˆ‘‘ RØ""""""""D [DDDDDDDD‚h”†-ÍŪūSîɚCęÕ)$^‘‚åÚLîz|+›ŊÃ.õƒĩ3Hœ4™¤ß b]GB ŋ˘L⤁˙,”‡ēĸ"2bĘYūíŽ÷ûˇŖŦĩ÷ŗ‡WųÎgs~Ušę7z´sį_įgÎ#…”íû™ãbåĩ“Iœ”Âöģ.#YļŒ.~ŽģŽŋ¤Ģgyß3Øë‡˙}g¨Îū÷ŖŅxŦŸ~ŸtũŨšƒÖÁ ĸ@Û"xmvî|9ˇ uúņÖđú}wąb‹ī˙aá„ŧ´5TáØō$Ž;XōĘū5%"´õQ"âÆÛÖņ_o -í@áąQtnyÄ8Cˆę'"gUËoXŊy!é÷$†ē&įĩ°đņŒ‹�¯ˇ…–†*;žÆąs+ˇ<û*kfŽëX*ŠäÔi¤ĩ„‘2nĀâDŌyÜāmŖšå8GöŊĖŊΞûí ĖÕqvÖõÚ'=ĸÎįīž""lŖ.lų`}ž/h ›Ā ?YK~f2@ëŅ]Ŧ~č1Ū¨Ē`Í}O3ųˇO‘~Ū~æÅōŨ÷ņŨÎ˙:#uūoh!•ooāÖķvģEät*7<Céŧ˜Ģ÷˙ˆ™ú“íŧ:Į×ĀŪúũ<÷čb^*?Æ[-&厛øn@,ˇūlˇ†´Ļr>éyÜxrWöĶ8ÛŪãší5ĖUČzÖõŨ'"""Ã1ē†y÷ķŌæc�$Ūĩ–į;‚€ˆ‰ŗÉņG¤„-ģxi_3Ũ]>į°Ōņ.Ë3Ŧ$]ũe�­•ŧū`&ŠW¤tõîZû.ū{ä6sđW3ī_Ō°\‘‚åÚÛųÁĢŽŨDxjĨô>+‰“&“úH9Ũ›æĨėkĮ0Ŗũxģ柯ōŊåüēĢMf0oÕģÔ÷*s°ļ‘ŗo 3§GAÛ{<ˇž’xk(]ĩˆ9ßNņžÉ=ŋ*§škį3§Ģã9™¤oߝ x›÷ŗúÎī˙EüōHĪw˙…u~0ÄMgé‹OqCĐ~ˆ[*;žņĶ•ŋšœ_>x;͝öĩ{Ōˇįp×ã;ø Ģq†rîcĀ}čį@_=ƒJJ÷Õ=éęĖ{ŧO}tLu†‰ˇq‡5 €z׹û/€öt1„īG}y9ēķ§Ü“1Ë“IŧÂĘôŦ‡ųĨŗgéC8ļē+4Ėã8”‚ųęĄŊ’7 ¤Í:pL {‹ˆČ™]aKåˇ$qËÍÉũŸģž[R�Ú9ė¨Ā‹ƒ …˛§~ŠŊ=‰Š)ˆ ûã÷°bW-†DŌį\ƒÁņ$Ëwļô)ĐËÁUwqĮēŨ8Ŋ˜{×mL ¯â§p×Ģķ œî5B-Ûí×´ėßÅáŽĮ]ėŨßD173CWũ[)}â1Ū1\ĪŊw]Gœ÷8ÎMsO×|´…ˆ„‚uŅBR fķŗ”úũ†Њũ‘ģøáĻÔG]ĮŨ Ž'Î[ÅŪu‹¸įՆŽezœĪžx’ˇ˜ÂÔäpÚ[*xë‘<îēīiÆN#=Ņ@[ÃÖ<ôLĮšå=?D\ĮĶŖ�¨qVœæĸ§†_~kvUaH™ÍŨ ˛ąÅļáØ˛Œyí긠 ô<Ü×`û4Đ΁žzĢķ\CļID´Įšåa–lnđõû”Ĩc*HŧxŊí�ÂÃ:ö_ íČū ôûQG_]@æCEė­ig⌛¸ÅEĢk7kæĪc‰Ŗķō>ĐçáĮĄĖ÷PˇÃëķxŽ&ŠĢŦ‰:Ę:ũų!Ācb˜û[DDÎܨFämnđ}1 ĪÄ8KŒcbl8ĐF{C ­Du<ŪF}ԏØ÷ÚmÄ4oeŪž Š[žŨ˚4xofåķØØķķĨyĪm>Œįîg7°"Å� ’¸ëÚe86ŧLŲŧ§HīútīķŖ€!í6æÆū† Ø{ĻĻ�•8ØÄ^Ī-֞_MÚ!õĮŧúŗëˆ�lQĮH{NJĘ-[ųāž3yHm!"gUėM,Í,äŽ-xn}9s’Ú™VŽšp'pËĪÖņ¯Ép4NJ™ëŽqdįšīēîŠÚhMzˆí?ŸNDs2Í˙ߓ8ÛĢ8šø*û–ŠĄ>–Ŗßy–ʆ …Ģ".ÜķC\rėh–˙=.Z]8ĒÚ!üz–žø6āŊ™ëМÕį×øAÎÃūĘlŸés ¯6šãž`ߋŗ‡—4fr÷ŽīsŅ:/–S!āĨ~ī3ŧä'}†Å÷p ŸŅŪ�öWķn6ōũ¨¯Ö]Ŧ^_AáĖüŲ[üjÎ8ĀËĢ2šeĶ1ŪZģ•{Ķr™Øĩ ĮVŸâ‡ö}fä|<“ékû>ÅÜgˇ°4ĨįcÁ|ĩĶ{?û^š™qxų`m&7l8F喭žįĮ\Õˇ:­ÃÜß""ŖĒg‹ĄĮīƒöpėķš{UæõŨ!H‹ځ°)¤u~@,ܐÕ{ĨĘË%’ÕJss3ÍÄú‚žÎ>?&ôzQÁ¡O�ŽSļĪ×Åũ¨ã�5@✛ú|0‡1uæ´Ž/8q)× ĐpĖ×Õ}ˆm!"gSSåb ƒ†íĪđĻŋ>āĶÉ/ŲÉžßnâ_“ŧDŒë8įy[ûaLž™ę;Œ›@\”īąĢfX|§Ö¸$&†´ŌŌÆ}~đv6ĩá4{†X&FmģYrã~°Ē705/—īeĻöųĖä<ÜW@ût(Ÿ}…q՜i{&ό ŊĩÅWļŽŠŗÂņĐÔwŧšŠ´û‹Ši‡ØŲO°tfĮŅH{˛ŋũ~ÔבlŽᆝņ€ĢæLķÃU8Üë€äØęįLŽãāko;NCC˙ŋÖ~§Ūāž‡ŽĘŧŽĢŦÉ3Žņũ¤ØPÅQČ11Üũ-""A1ĒzļÅ8 Ąũ•õ0wbßš9ÚāģEOxÔøŋŒ„1.ŧĮär­­ž°Æ` ĸĮ÷cCD8Đå{ÛÚ|Ëĩ`éwŌûŧVõõ@×hĻŪ¯1ZLœs)ëŸæČž]ÅÁ}Uø†eĀŪŗ1:Ŗ­VZ[‡Ú"rÖÅŨÄŌyܞЂįÖ`MŋĶQ+‡_}’•›đACíƒÖy>č<ˆ0ô}Ŧ/ōųÁK}eGW˙¨XüŪƐĘŌĨųŅ_°ˇæīÔâM@Øxf.ÛĀķķ{ü60đy¸˙øÔĀöiāŸũE„uŋ¨Ą_ ¤cęlčžķM;­ -´‰ 6ŗs™ĨëØ ŦŊß_~?ęˇ^[įzŊÖ#Üw‡ÄÚhîs |lõw&Įq°Ĩ={0ā rƒ÷2Ņķ{Ļ!‚ ¯ßķC Į„—áío ŽŅļ$NcjToĩÞÅÅ{|Ņ� ~7oãĒ™ ķ[ŒÁh÷ļų~…č(¤ĩš­÷ráQžåÂŽáŪgŌˇ—ę¸sá‹^ÜõÜa}†#ûwc?…ŗ°Üä'¨ōŌÚÖãĶēĩķ•""Γļ9¯¸jŅHÛžĮŽ_đƌŪoR¯ãIîyj7-aIÜņôCܐû~Ę*Îü•/ÔķCķģŧąß÷š‘<ũ˙a ‘’ï~›CkŊ‹Ãå.ûļōæžcė]õoNßŌq#ė<ÜWĀû4ā΁ĄŅ1uvtßųĻ{čHÍÎBæ­ëēëb íČū ôûQ_†đÎõZ{­G[GްpŋˇJ’:ŽCihī!/­m=wJĮų!Ė˙ų!cÂP9ŧũ-""Á1dža˜ÎŨ ’j6=Ė6Wvuąl=ē‹å÷=Ñv ö&î1§z˛Å7öŊŊŠŊ“ļĩîį­ō>)~ō5LÚÛ q:éiĶIOK"Âۆ÷4_~GŸqØnŋŽpĒxkUÛðúîÔÎá:&kôrxß!ßo‰ILŽā<i ‘ķܸŲ,7¨bīžŪaķŅōĒŽ÷ôMܛ9Š)đ6÷=ŲŪvfwō¸�ĪŪæũŦžīIöļáßá‡ķNsûŨúũŧū̟˛r{%qŌ3sXņâzîMÚ[¨éõą3Čy¸Ā÷i ŸCŖcęl309ī îˆZvŗ|Õūîa&´W@û+ĐīG}ĨLcj8Đ~ˆwöuÖĘËáh�HžŽŠgŧĪFæ8ĨĄŊ‡Ú9ŧķŨŽ}îåƒũį‡8˙ᇀŪCÃŨß""ŖĢg 0ųžĩäģîbéžãė}âRV…nđŌÖÖŅų2ü–ŋøcĻÔ#uÜw¸cú38÷ˇđÎŖķđîŗāuā(žÉuģúqŽ›Í3Á[ĒxiūíÔĪHÂPs€Ōōã`yˆi–ŪÚāˆHģšQģyÃUa͏e†ŋß_Ã0yšĖ;ßejTeģŽa¤Ü~›īƒø<i ‘ķŨäE?bææ<_ĐC\r,aŖŊǐåĢZ˜Øp�{ķxiĄĻæ]ž[kaéâaŽĶŋ@Î]“bzÛhnéčō6;ž} Ûi/$ķΆ"œmģ9ė¸žŠqĐZsˆŌ* v3{õĐä<ÜG`ûô:&čįĀĐč˜ C*KŸŧ ûÂßаãIVgn'ßP{ļŋü~ÔWÄl~˜WHŲSė}$“y{S‰ksaßĖ÷Y|SPB‘‘8އÃ˙š€!•owL„€ĄŊ‡Â0y†9žzú}XDDFÄčęŲ@"ˇžøī<=Ÿ™–ņ„ĶF[„ĮNaæ‚'x뷛ø^ō`Ÿrã˜ûôz–LŸ@¸÷eû]0cĪ/˜ā{Úëíø5ÁĀԟlfãÃß!Åp û–bJ+áĒ›˙_{÷U}ī{üÃ1ˈfxljė'A“]I´‡ā~H¨$rˇ\”› Š "—r &šÍm“l,"šĘÅrŗH�šJDDZ)-ēIZ¤íθ7LöÁÄZ&fæü1rĪ YsIō~=03kå;ŋõ[ŋYķ™ßZk‰vlĒyUũ g~H˙Ø"I˛ô­Á›ôYĨ™áÅ:ū›3ēl‰R˙YÚđ\×ë+im´uSƌę?üäũbbOu5ŲõûtĘ4ZŋؔĨEŖdQąŽúw5z×čfĩņáÆE1KRx”~"Iëßß§ŒūMüdīmØ´DclŅ…ßlĶúˇˇiĪ/ë‡˙ü‚ÖoĢ˙Ã@Ķãpm^mS?ŧAŸ ŒÎũ“”ņĪá’ėĘ~åMr_tŖŲöōl{yz|Tߏž{Gû~>Eũ˙ŅĨ?˙æ€öūŅĄ{~<^éīîpBFđA?ž] ÷â”6p‘ÜÆyļMœÕâîŦÁ˙ēJĶ<<ۇn}{�ZŽCUUU•$Ųl6Y­Ö�—ƒ[ōÕoõŌĐš:ęרmGĩ˛Ö‰ģ˙Gé>Ŗ-MūÖgúÅãĢ�Ú)?ŒÃM~�­ũ�ЊÕÍT‚î4"xá‡Z¸ü€ūüŲŋëœC˛üxŽæq`�íŸh čĮ�€6(O#‚ĮœĨúŋŋ?ĄsĨfũcŋ´!sBĢž�ĀK| - �Ú N#�����hē™ 3[������ DØ�����` Ûĸ7��¸IDATÂ������ļ������ˆ°�����Ā@„-������"l�����0a �����€[������ DØ�����` Â������ļ������ˆ°�����Ā@„-������ ņåʋ˙ĢėĢ����´rQ÷EēĀpĖl�����0Ogļ„˙ ԗĢ�����:Ėl�����0a �����€[������ DØ�����` Â������ļ������ˆ°�����Ā@„-������"l�����0a �����€[������ č����´]ļ víØŊ_ß~WĄīŋ¯ t9đ:čÎ;:iʄ1ē¯kD Ë‚3[����ø„í‚]ooŲ.Gųw-mXUU•ĘËŋÕúÍÛUō˙ž t9@P l���āŲīt đ—TUõŊŪŲą7Е�A°���€O”ûm K€?uč Gyy Ģ�‚a ����ĀUUœ.H„-������† Â°ÅĄ3{–iōđĮÔ;ޟúŽĨô#69]–ß9õQr?=÷đÍ iĐØéš˙Æ>.õWųJü°ÆoąųëzÁ_ĩļ N/χžyGŎ´tŨڃy{Įû6k=íŌ˛ūāŠÖĶ-ĮXÛ<ÆZã]īwiū1Gũ§K÷éų„aJĪķK1TzäeõĻEŸ6°=äÔé%ŖõЀ—õ‘߯�@ktaËŜ…šŊęOŠœ”ĄM[×iŲp“ŽĨÍŌĸ†Fڃˆ'ĩbÃZmܰVW-ÖŧáV9~—ŠÆN×ę<īÛ¤tĪ, Z’ëƒBŅ:X5*y‘f&†žfúVđbÛx€ąQŽcĢŪŌéö÷‹RĐ ’¤yq.\õ+Šŗ=œ…•ņA™âį&ihx`ę�´>Aļhû†S˛ŒÍPú¤õŒíĄAĶ2´p°KGļŌÅ@—–nЉSīø8õN QĶkĶŪMšgũR[ŌVč¯ž8UxļH._ՊV L=‡ × ‹ÁëĨo/ļGk�–„Į•č8¤•Ųļ@—EjÜĢ/*Öž_¯īĩÕxÜŽŨËwĘũ‚>¨â��­Pp…-ÅųĘ+š[‰{Č|ãA‹âöŠ(WˇđãbÛdŽÖôĨ?QLŲQmÎąß|ŧ4_›“§kØ�÷”øžƒ'+eKž.K’Ę´{FÍū \ŽæëĄ¸ņZ]ØÜ25”—č“7fiԀ~z(a˜F%ŋŖĶ5ˇ‡ë)Í{G)SGĢo‚{šū°Š ĩ9¯ŦÆ+ĘtrËË?¸ú˛áĶĩhOQũZ|P›œv}’5Oã÷Sī¸~ôĖBŊÛD‡ģœ÷ĻF% ĶŦ=6k/Púā~z>ģĀ]ëāĮÔ;á1 ›ņĻN֜’\šĢÕ?¯ž Ģ÷āņšŋ%W5[¨A^ÕŪĐ4ũ–ÖŪHßĒåú:ōõū÷ļč0Z“ß8ŽŌŌ|mNžŦAúŠ÷āÉJŲStķ´A§]ŸŧqķŊõ<Yķŗrk¯ÍļYŽ xX“ŗíĩ=Ŋä1=ôĖÎ&BÜ[ėFō¤?´×ũŪk ¯­Uĩž`y\)/uWņÖÚmoæĩžŒ‡h™¨§”2)Bg7dęũęūRz$Së "4.mĸĸnŧ°ų}¨ųũ�ĐÖWØRōšėŠPT:GvS¸ëK6w ŌžD%(Ņ*æT1-ĶûKįkÍĒ™ŋÜĻsvimr´ ßNŅâ#Iašj›æEK–Áéúíī6iVLsËÜdĪÉÔv=Ĩô­Û´kÕp…į­UŌŌã7ž\4ģĮĮĘHŪ([L’6îŨ¯ŗ×h^ŒMëæ.Ģ> qęĖŗ4ûíÅ&¯Ņœ]Zû’UgVÍRjNĶžÅĩÉŠĶĢf+5Gēt“ŪË^­yŅEZ3wžŪmčäũâ}JMŪ/ķ¤•Zũ´ÕãÚÍ&— ˇžŠcąŠzīãßętÎbÅÛw)5+÷æ6LKŅ[´^~{ŋoÍĐȲ_kŨīšē}ž—ĩ7°|Ëko¨oÕ˙Kf“K…ŲU8dĨ˙ƒ/í.ێeš2w§L3Öé“ãŋŅ{ΘtlUfõ9ņ\>[I9.%.Ũ¤GviãĢŊTēgžfŋQЂ63Ž]|˓÷Ö~÷{ŋaŦ5ŽļV=Öú†S&Eũ™Æ…˙I벎7xy2ĸåĖę9#U#-§´&+W—šZŗę÷˛ OŌĖØë?zĐ›Ũ�íAH  ¨ÉépĘ)ŗ,uĪp5Ë$§œĖlŠ!BQa’Ģôk•JęĒP=šļMĄ¨H÷A×Č4.ûÖZ IŲ*“I’Éĸp‹E’ŗŲeŽsDŒŅ˛ƒÕU’ĸæhásšũöúÔ1@C-Í˙mŲ?Wqy˜1@DJR¤ēĻ­‘uD™Â-’Įĩnī—Š™ąK‹†XĢבDžg˙¤ļî×ųsô@#-ŅâÚGĩ9§L=Ķ6izõĩLĸŌËáÚŠŌ‡jü”%9r•ž SļÄ m›[=ˋڝŅcôōĢ{šđwˇÉŽE•×î|)qiĒFÅēw‚A ’t&wŠļ4Ö ŧŠŊÁå ĒŊ^ßj˜3æ)ÍL “Y’9q€bõ{ŲâĻjrõiMQ(ō—;uÆ&ŌqŊûq‰bįŽÕüÄęöҟiaÁ)MØŗS'įfhãÚĖ-菆ņ¨?´ßũŪk Ģ­5ĩždÉOęŖš™Z?ŠRbÍõ_SęÁxØĀb¸æ8Ŋœü¸N.XĄÔ2“ōÔ_+$¨ķõį=é‡Íí‡@H¸?Ec~XŽ?îÚĄ}Eū{%ŠKßõĘČûÔņÆ#•*>¸Nož ŒTa ŧáĒž€Šú”+ŗ:›Ęt0+S‹ ž”Ũá”Ëé’ŗ\RdcWđ|™ČØîėjQ1VY\E:o—†Æx°k=ņkí~u–œ“žŌ ¸8ÅĮ„遨ę ĩũI…Ž ˇÖǝgBw™öčlŠô@#Ĩkqm…*t…iht‹Æš{hōkĩ˙Py‘ŪM^Ą#aIÚļt€n”cķŧöpkôÍ6If‹Yr•ģ•´ČŽ0 Žy$ÖMŊŖCÕčÍ9<­Ŋ1FÕîĄHĢõæ:L™MRdLD•še‘K.—$[ģļØÚSŨĸâĸeÚZ¤3viPŲ-´™'ŧhŸņ¨?´ßũŪk Ģ­5ĩ>Ö9qŽæ%ŒWÆōˇcjũœÜ“ņ°špëüh’æ%Œ×â\)~éj ĒŲU<é‡Íí‡hįn׃cĻiō?Y$ŨĨG§NSĮwļh—ĪĢžũ×éęĶŠŠ×tTÔČ˙­Ŧ‘5ĒøŗÖ,Ų¯/|\ĐUØbļXdV™I5?ØĘrÉRÆKģVĸbģd‰ s7•ŗ@¯ŋ4[ģMOjáĢIЎēÛō`ō­kl^,c ­ũ€)TĻë_Š=Y9N)[7)jë;z?{…öŦ*—)ĸ—FÎMUĘĢĖŽršTĸíĪ=Ŧíõ íæî|hqmŽršdR¸Šą†r+ÎN×J—K&k‰jM˛ōĸvSSŋ<:\rÔĢÃ,“Ĩ‰Â<ŦŊņŋiPíˇĸz}&5RŧŖÜŨu6¯ŲędĘukmæ‰ôGÃxōŪÚņ~ī?Œĩ†Õ֚ĮZŸ ͍äŸhû¤_ëõœáZ—PįiOÆC(L ‰–rĨĄ uÎm÷¨6ŗúįM´ !?Ѓ}úi`üũ˛Ūs—îčØČë*+Tö՝ËËÕŅS_čëĢū*đvŨ?bŠ^ėwŸîhæ•ß}–­Ĩī•Ęĸ øƒ>˙_O¨{'IīQßgW>ĐæUØ"k´"•ëž6KĪļËļ•š"ǧbB’œg?Öą“bįörh} lwkäÖÅ{ũUeēÜÔ˜Ë8Ęęüb[}Đl õb=á=4nÁJ[ ].Î׹™Z™6_æČJą„ʤ|cĩĻ[ë,g6)Ŧ‰mßâÚ,î/ ĨÍŦšŖ'jmZ7mž‘ŽEY}ôŪ‚8wÛˇ öZ,&™åRi­ˇã”ÃŅDaÖŪôōÔî ÷—ˆēīÍé(—CĄî/Î[hŗjŽĻn íâIhĮûŊŋ0ÖX[kkũ!jĸŽŨ¯)Y™ú¤G¯ÚĪy2Â?<í‡M퇱‚'BЄÆŠodc K ;),˛ģúŽėއãOkËÆõË.mUß>-ũXë–Ē×ÔišĸâˇéWë?Ô´™îĀåģ˙,Ö×>¯ÕĻíK7đ•ā ["{)ҚŠ#įË_}pĨ2}z¤@ŠŖxfļ¸9ōĩfų~Ų#žÔ˛ÕâpÉĨ0u­qĀå,<Ēc6IÖ:Ë_?Āôb™Ōĸ•*úƞŅ69LŨÔ3RR~ķëqÚ tŌĒGŨŋčtŽŠĶ¨ä9:™“ĸB›CØK=M‡d/ˇ(*ĒÆ4[‡]Yk:x]-­MÖ^Š1íיŗvéÆímžņĻΏČĐë#܏D|RŊcŦŠJ;ŽŅiéĘHÜĻôD‹dŊõÚk‰Œ–UGU\änܚųsåu՞éU“‡ĩ7Ę¨Ú¯3ō^ˇ1=xoRq~‘\Ą=)ÉėI›™d2×ũĸhWą­‰Ŗ0ŖÛåVxŌÚņ~īŒĩ†ÖÖĒĮZŋ0ĢįKIōqŠÖl5՞`äÉx˙đ 6ģÆr@ÛŦ{5fæ8õíŌxĐRyéœ>ĻüÂ˙–CĄęĶCO {\=#{kÚĖĢúÅęÃ˛ųa†KGIĒ<§ˇ˙e‡Î6öĸp=2uš&^ŋFŨSôĸ܁ËČ~Wutßg~[$é~=÷o?Qœų•$ŠâŦÖ.Ų­s>­ hģ‚ënDŠÖäũåČIWjvŽÎœÍ×GY •‘kŅČO~6y 8žTa^žNįåët^Ž>Ęūšž;_ÛíŨ5oU’z_Ÿ‹ŨC1ĻĪõūŽã*.-SqŪNĨ./QLœI.{Î8œ’,˛˜%‡-W' mēØÕ“eÜĖļ}ĘČÎWqi™.žŨŠ×w|ްÃoöđoÛö)#yžRŗsuŪ^Ļ‹ö"ĖŪŠ<E(6Ú"YčŲa:“ĩPĢébi™ŠĪ~ ô4áÕÔÔeēZ\›e€ĻŽĐŲ éZ}Ŧ@į ķõūōŸkŨYŠgúG°áC^ҞN\ēBŸ8ÔĸÚk‰ė¯Q=¤O7ŦĐî<›.čŖåoé¤Ģ‰Ÿ,ŊŦŊĄå ŠŊnß2âb֖š>ÂũŪ6jĶÅRģÎ{S‹÷–(æé‰îíëQ›YÕ3Ú$Û§‡tÆ!IĪÎÔÁ˛&N0Ŧ]ZĀ“÷֎÷{Ã1Ö2Ö65Öú‹e€æÍč%û‡”įĒũxŗã!üÓ~ØÜ~ˆfué3B6´čŌi­ũåũî/˙-ĮUIWËuņ/§ôĢ_n͉KRĮ.ņšĐ'Hž9Ô ZÜ:)ę‰)šØå3m/O—üvÚĶU\ņâåW¯¨Âgĩ�m_pÍl‘>dą6:2•ąuĄž/qÉlíĨ‘oŦUJ{=’(9¤Ô—U˙Į$KX7Å&ĖŅÆšOŠwx6 R Ķ ”šĩPŖ÷Ja=×ŧ´Ez´,T…ÉŋÖķ3¤m;æč‘gž’ué.Í~Î:āÁ2}äršd})ICm5{lėŽPÅ$&iuÚ�÷/‰ũíT­MËÔĘ­Ë4eÕ×r™BiíĨ‘ËWWß&ØŦŪikĩڒŠ5¯ÍŌö˛r)´›zLŌÚà ڌŠmŽz§­Ö KĻÖŊ6K[ʤ°č>š•õŠ&7xÁÁ0 ZúІŒMQÆŌūęšjđ-Õ^_¤ÆŊ–ĄâĨ™Z3w‚^7uSüĶIJ‘ŠųŪ’Ôėeí -oDí–:}ë°Å{ŧpŖëėŧV™ĻZŗtŠÖ”I–ˆîJœąZ)ĶĸĢgžyŌfašœĒŧ´ˇôüā2[";"IķF|ŠųŸ:™ŒcTģ´„ī­î÷>ÁXËXÛäXë?]ŸNŌŗ{§hKQÍG=áėC‰Íí‡hZ¸zÆG4ņ|…ōÖ …WlÚ{øŦzMÕŊņ¨Ë‰ēäĢ2=Ņ`Đâvšđ¨Ū˙‹7ɇŽĒĸBžĪâ̏ĸJ_–´qĒĒĒĒ$ÉfŗÉjĩēr‡ƒ{5���íUZúJ/—hîT—Īõîâmúcc9Åí˙¤YËÆčG•gĩö_|} Ėzņß&Ч8¨É å ÖŧãĪ-×ũ@įüLcģyøō/?ԒˇNŨŌ)NËŊėÕë-Ü m@ŨL%ČN#���€V,(ƒÉë͈ŽpĐ„-����‚Ä7ēđUSĪßĢØø•B~ØŨ}]î¯JTfla ҃cšZv]ЏÆô­wMu˙¸Ēī*=?1¨˛’Ķˆ€– l���$Ju&¯¤‰į;)nØ0Ũ{OŨnÕØaąēC•ēw>@×k Õ?D66ŖÅĻîĪNĶØ‡î ĐÍĪŽĒâŠįS[*Ž\Q@&ā�ma ���€ qéTŽŽ]jbNE—ŪšũĶq.Kˆ¤Pu‰îŖ:E}ģH•—ō´ë”_īiWÃôu’”›§…ëG˙ŗS`ĘĒVQáy|RéÕ9G�ę ēģ���hĮŽ^ĐžõģÕqæ8õmäĐģÄjė ą[įņĘK§ĩeũaŲ:%ŖR•WôõßžQ™ũ´öîû,@×hНŌáÅĖG ´R„-����‚KųyíZĨ3}úi`üũ˛Ūs—îhėE•*ûę‚ÎååęčŠ/ôu@3›vĨ§kW Kh‚ˇ§¸u„-����‚ĪÕotîDŽÎt!Ģ”¤ŽjÆëËŧX(piP…Ã}ŅÛFīŦ}CĨ*ŧ˜ >ŽŲ����^ŗéÄŠ˙Ōw^-SĄâS§õ…*jÎÕŋæę„Ŋų:gĪĶąŋrĐĖl����¯]Ņ9ŋRjN ëđ›ö­~Mû]Đ0ŗ�����Ā@„-������"l�����0a �����€[������ DØ�����` Â����€!:tt@p l���āĄwŪ)UUē øKU•,Ą–@WÂ����>1iühĻ:´'˙ŖƒĻNč*€ @Ø���Ā'Ŧ÷FjÆ´gzįę@čŌĻŨyįš5}Š"ēÜčR€ č����´]Ö{#•öŗŲ.�üŠ™-������"l�����0a �����€[������ DØ�����` Â������ļ������ˆ°�����Ā@„-������"l�����0a �����€B|šr‹ÅâËÕ�����fļ������ˆ°�����Ā@„-������"l�����0a �����€[������ DØ�����` Â������ļ������ˆ°�����Ā@„-������"l�����0a �����€[������ DØ�����` Â������ļ������ˆ°�����Ā@„-������"l�����0a �����€[������ DØ�����` Â������ļ������ˆ°�����Ā@„-������"l�����0a �����€[������ DØ�����` Â������Ũ[BBBtíÚĩ@Ö�����ĐĒ\ģvM!!!ĩģļÜ~ûíúöÛoũ^�����@kU^^ŽN:ÕzėFØr÷Ũwëī˙ģžųæ}˙ũ÷~/����� ĩ¸víšūöˇŋÉápčŽģîĒõ\‡ĒĒĒǚ/,++“Ķéä”"�����€FÜvÛm2›Í ĶmˇŨVëšZa ������Z†ģ�����ˆ°�����Ā@„-������"l�����0Đ˙ęl;C™tģ����IENDŽB`‚������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/mod-scheme-list.png�������������������������������������������������0000664�0000000�0000000�00000045463�14156463140�0022366�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��°��S���Á|?���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨ{|Tõ˙ņũŲĖÔ5—]Â/‘ĐM[&°%ƒE2h!ŠCMŧ”€ˇT(7/PĢ€Uã°@ĸ\‚‚$ĸe@Û X™`&ú0„ß’]‚É#ņ÷۝ÉnËīIBŽdrá„÷ķņČã3į|Ī÷û9ß9į3ßķ=gœ={ö,"""""ņŊžŽ€ˆˆˆˆHg(CQ+""""†ĸVDDDD å˛Î,üˇŋũoŋũŸĪĮßūöˇžĒ“ˆˆˆˆHģú‚ŋũío|ķÍ7˜Íf.ŋürž÷= ۊˆˆˆHī 8­ŽŽæ{ßû—_~yO×IDDDD¤]ŖzŊ^~đƒôd]DDDDD:pû?˙ķ? 0 'ë"""""Ō!MdCQ+""""†ĸVDDDD E ŦˆˆˆˆŠX1%°""""b(J`EDDDÄP”ĀŠˆˆˆˆĄ\֗đŊ˙՗›ŌŦˆˆˆˆŠX1%°""""b(J`EDDDÄP”ĀŠˆˆˆˆĄ(CQ+""""†ĸVDDDD E ŦˆˆˆˆŠX1%°""""b(J`EDDDÄP”ĀŠˆˆˆˆĄ(CQ+""""†ĸVDDDD E ŦˆˆˆˆŠX1%°""""b(—|ë˜? ËđÛÉŽčßÛlÛ^Œú–ÛŗčķLjˆˆˆ貞Ž@÷ņRļ7‹Ė ÜÅxÎxņ™Ė˜Âĸą&ØH™AŠ5Ŧ¯+)""""¨$°^7ëgũ‚UgĀĘp›„3fŧTģpåeQ—Cvú˛Ú0÷u}/Y^ÜY+É#…3­­÷ƒĮÅúĖ|BĶ‘fé‹úu7/Å. \ŔUz8ãSXŅÃm$&Y‰l @)ŽĖlĘ2Hˇ…ĩņcķ–ēp8Ũõ1đa2‡M‚} ÖČKķĶčxtoķMf",vfd,$=)˛o*&"bũ ­ gÖ=Ŧ*đ‘ŧ‚õ+Ō°´8'z‹wŗ`Öö­„ņûyuŠņ“šØy)Î]GŽÛK˜ÅFbJ4a&/ž˛b rŲpŧ”Ôô›[õUŋHâí)D‡vrįugąž,‘Œ”˜n/ģÍí•îf}v1fköÄhBÃ�O%…Î|ō˛ŗđȍíRÍÕ"RČ\•Fhũ}ž2œŲ/˛*ŨEYöA–Ûú´vēs7v×l V'õuUD¤Ÿ2üXīŪ•,/đbŠ_JöęÖÉ+€Ųr3¯nXAjjö@rW›œ§~AōØQÄ˙ņŖŽ&yÖSä¸FLÜ,Ÿđ#,Éë(kļb)ë'˙ËđQ,ČoQæŪGˆ>ŠôŊMG]ēZÎ&Oú×öįŅzv“ŪbnĢ'˙EMžšøQ?"~ÔxRįįāö é¯;—\ˇ—č”Ų¤§%aĩć5éfŌĶSî-Äá(mgm3‘+–Ŧ,ĢĀÛņbŨĻŦ ˜3I¤ĻذÄDIdŒûĖBĄŦōî„a,6ļúŋÄ)i,yįmfDT’›ŊˇW÷Sw+tēū"rņ3øŦgžf’3f}žE-i,_@‘žŊ,H~”<O4“Ķ’ž•ä­Īaé=.ŽoØĪ›•Ä3ŲŽ|ÜŪŲD7äžœfL&/Îc×XŦËéÂG<“mfp4ŧÚÕr~ÂŊ›yņq'9šĨ˘Ķŧ Î\>3 3Sˆp/#-=‹˛ĐRĻ‘áŖĖ‘EÆŦĐÆŅŸ‹•˙ōŗ‹âĘ3ø0a!!q ļ˜†`y)såâ((ÅãÂ"ą$Úą[#ë§(”âČĖĄ2!áeš8Ë"ąg¤aŠč¨Ü áÁí*ÆmĮŪÖŧë0+)éÖŪĐc[S:jgŽĖ,*f’āqā<^Į æ+)iSˆ6×Oß(Čfš;‚ÄôŲ$x{2Ūķ$11ØgĪnņš‡â|ÎBÍėMëâĄx¯įņ†„oĮžCX}ÜrWæp&1øJ'Îâ ŧ˜ ‹ļaOąû|uË@ú]Oą’`5“]VF%øië—Ŧ$;ŋ˜JŸS¨{úR–Īn˜åbÉØY”eŧŠŨų+]–~›oGëšY2öĘ26ę^É*G1Bą¤-e}F;–,c}~^s4ö…ĢX•Öpōā\÷+×;);DX°g,gyZf<äÜ3–Ĩ�ŗ°äE3+o? -į[§ũ6$ē׹|eŽâJ|ÔOąX¸´ÍŠ5/ŋŧ†ĩ/ŋÜfT›;—šsįuã~‘žfđØb Ü^0%`īĻËmŽ•ËČķXXđŪ~^]:“”)SH™š”õyĢH¤ŒėåūQÍ{"&_!NWĶ•ķq“€ŨŠĮ]ĐäÎūc8 ΀%‰–ĮŨŽ–3aZvߑCqŗ=8s ņ™™aŧäfæPF436ŧËō™7cŸ’FúęYi)㸯{âÖ#ŧĮČŨ‘Oe„é<š>{D%Κ4 †WėÍ"Ûé!Ú>“ôŒŲĖH ĨŌ‘Enãđ˛đáq;)‹ļ3c† —{aõ.ãx%DX,´7āo‹ėÔ\ėŽÛ āŖĸĀAqL é‹X˜‘Bô™rĨ€kZ:“#ĀdMeÁ™$†õp0m‰ÄT™ĪŽ\Åį/´loš…Ÿ<“ôô4#+qîČÁåđR–›ÅŽB/–䙤/œÍ {4ž‚lvė=÷ 1›}T8”YRČX¸ˆ…v"*ääkLĻ;Œe�ũŽį”RVæÅA�rĖbUq4 ŪÉ#ßšėå 3gą ņjŽŗŲKYv&΄Ĩdŋˇģ9õĀlöâ^˙ …)p~ų%ÎUVŠŗ2# sÆ{|ų%yéf˖‘[÷˛ÛIĪ,ÃēüÎ}d/ŒĻpY*š@iōX`Sō̏ ?dĄĨŖuÚi{Y2+“bËRvė;@~Ū;,ą“9k~c]šš;wÍÛęu%¯"ũ“ÁØ ˙Š9Œî™.čbGn%Ä$’áÁãiō‡•d›Š÷âô€Ų–„/N÷šĩ.|16fØ­Pėĸ°á<á)¤  "[wŊœ)¤Ļ„BY.;ÜM ôäŗÃíÅdOÁnöˇÉéōBDŠÖæÛļÍL9˙¨u_ķTrÆk&ÚGdXaa‘XĻĖdƌD˙Tī1œ…•D$ĻaˇDF¤õfėņfŽģÜÍ vÆlÁn‹#22 sGå^(¯/&BÃēiŽu'ÚI„ģĨ~ģæ8ŦŅĄø**ņ�˜Íū|3fŗšãøvƒ0kiö(v°cũJ–¯|…õ9ģqēKi–ƒxŨ¸ ŊD&Ļ`‹‰$,2kJ ‰–P|/x‹qŸ!:1…ĘHÂĖaDZĻ09!‚ĘBWķ/qļf1HLˆÆWVH™7ĀXöB\Úä-Åšî)2‹Cą§NŠ˙‚†}yŽėÕ¤XcˆŒŒÁ:e!3,^œWŗÕ+CSX6{ VK æNŦ‡u& ’üņ K˛cà ŗ™QߨhģH_1ŀw/¯æ”1<ãU–Oą‰5e5ËRBqffų÷ƒ9 ŗ ĀL˜ŲØ:mĩĄĖÍq_(ļ´)X"맞Ŧx‡ī,$ąVË$VÉĢH˙eđ)õg/´HôsûX–ļ|=•…ī’Ō։¨ĸ˜2Pŧž”„õílĶ?ē†%D ŦrģŠĀJdũčhD‚ ĢՌÅ÷ 7ØmāuåSL()ö¸ÖŅuŊÛĖ4ĸwŦ'7ËŒÕū!h3ˇ/{ڔú6UúÛß:YĩX‰6ŅbūíE$2šč0'îY€%:†čH3‘‘õ—Ū=eTzCąD7?›EĮDb*(ŖŌKãūĄŅįFC;*÷‚us–͉všC#šmŨd‚v/å÷x�ĖDÛŌHˇyŠ(-ĻŦ¸”ãeÅ8s q:-$§Ļa**¨ôšąD4­}$ļ”›ũ˙,­h3‘Ņ˜œ•Tz 1ghƒ°ˆPLŪúe –Ŋ x%öá+›ŋfŠfōōwX>å\ ĖæJœË–‘[XL™Į‹×ëķđĸ›īÕ‹•Ļ5 tŊČččsņ2›1™ ÚŨ´ BņáõÅNÜžRl1Íʰ%Z1ípãn˛Îĩ3đušĩÁ’DbÄzrŊoúLė6‰–0,<ąiÂĒäU¤˙2xAXPY?Ûė¸f&:%ä&ëʂ\ *ĪSœˇūDo™Iæ‚ÄļĶsŅŅ�1$$D@Ž‹BīL"Ŋ…”™IX`…H°FœÁé<ļ8Ü>S‰mNs¸€r,i¤ZÖŗĘ‘ƒv<8r ņE¤1Ŗa¯/`2›Ûlé<áč}ū}uŽž1ØĶ͉ČwRXKÃįޝ=Å?Âæõâå ë—QĐĒŦ˙ ˇ!ąkÖøĘŊPæPBÍ>ÎTT€Ĩ’ž.ˇŗ#=‡fĖDÆX‰Œąbŧ.rw8p8ÜXfZ1ãŋšũ+)žöŪ÷?.ĪÛ$'3‡ĩ\¨~0–Ŋ—č42_ŅøÅŌlŠ$"&ŦÅįÔÍōÔ{Č1§°|ų;$XÂ0SÁŽYÉdļle˜ŠKëĩĨíŖāņâĨ’ėÔ‘ŨēAœiuîÜ:ÍÛ`cIŪ{ Ī\ĮŽõ ØąÜ‹)"”%+X>%æŧõWâ*Ō˙<ķß•WHžËKʔĻ]3ļ™+hš3:æ;(Č;Oqaaūã¨)šø¤$:J=Ŧ‰6Bŗķq¸!Ņ“Oą)ž>q´’`&Į]ˆpžÁdK$ąÛˉ!uF™Käîõbˇ9Čs{‰ž™JãlúÄÕWŸœ7;-yŊøzuŦŗŲh_fĸ›ŪYfŽÄ:% ëđzJ)v9pädAú/ą›Í˜ Œ6“Ĉ6ļvž\ã|å^hÎiŽ$:öS‘Ųf?ōģp›,ØbHˆ.¤ÔĩĮâ�ū„ą/OæH‰Ã]l8^A%VĸÍū$ķL{sLMíŧßFbëõ´\ČŋŒöD€ąėé¸�˜ŖąXâÎ?ĮCnY();V“ŌøĄŽĀsσ˛ģē^G¢‰Āžų-ŸÕl6ŅVlē˛NãēVŌ–žFÚŌúëÖ/cIÆ=˜wd‰õ<ë‰Hŋgđ9°˜–B^œĢVâęxņķ ‹ö'OÅ.ZÍ<�š ķ�$ŲI0ĄĐyŒ‡ _L WˇlņāΧ Ô?oÕbOj˙Âō”–2“D“g^>ŎÜ> ŠŠMĻ*DFm*‹[O(.lqXĪķßÔãϰÕãŋ<¸ÅøÂ,į-=—ž›)i‹Á:ŎÅėĨ˛Ō aŅD˜Ŋœņš ;÷gÆ?¯ŊJtTî Ãjŗ`Ēt‘—߯sÎ<nrsz{ÔPWÛŲ‘žŽƒĮEöʕdģÚzT–—J˙/;øŸ„M„ų •eM—­Ā•UsUddīCEi%>s‘M’ø3••Íæ×z*+ņ™Ãˆ#°Xöx˙č/‘ÍŋÔឝŒļæM]øzą$’`:CĨ/Œč˜˜s‘fLíŨ˜Ø•u�*Ü8ōK˙kŽą‘˛b!vĶŠËô.‘KáXl Y’ eY¤ßū"Îļž‹ŠwÎ|V9ü'ĖöOø6’íĄās˛~]iķˇŧų,˜<ŠøYģ›œmØ­f*ÜëÉ-8Chŧĩq4%,ÁJ¤Ī…c}>ÅX°'œo˜ėĘ1O!ÍnƗŸÉ’õnˆŸIrLķ˛Ŧf(s4ŋŲ /ŽŦŨœoFEO0[í$F{)Č^GŽëÅĨĨģ]8˛˛Č+3oO:7"åqáØ‘EŽĢ” §‚2We„asļx3ΜÅxŧ^<nYëXŸën?9ė¨Ünig iÖ0<Î,2söâ*>FYé1ÜųģYŋ>—˛ĐRė1%Ÿ]mgë‚üŖņŔUxđTöpÂŦ$&Dāqd‘Ŋ×Eqi”•ēqædá(63<ÉęŋęaŽÃf ĨĖ™‹ŗ¸‚ŠŠRÜ{sqVBDt˜-ØâũīģJ=xŧ*Š÷’Wč!"ÁÖlĶtƅÃUęSŠ GA%Ą–x˙c´‰e/ô€YmXMÅėXŋ—2‡2W>UI|‚_™ģũ§"tuŊŽ˜§0#%÷˛GXš÷eîŨ,IŒũ҆cŖ˙ĻZßq'ÎâR*d6gąäŅ{x4+Ÿâ ĮpŽËÂIņ=z7ˆÁ§�„a_ũ+š‡%yëIOĖ&:!‘øč0ÂđâŠ,ŖĀåĻŌĻčVžēēūîüļŲŽ Ųų(yĢ’I-ΠՍšū9°ÎĘ’—Oi2Å+Œ{4ž•>3‰M'šÆ$ēGŽ_D 1įoŔ“˜–Bh^…efWŲ[\˛#%ŨÎĒ‚\˛gŨŽ'=„0Į9äzm$˜mĖėI‘ØfÎ&,ßAAA.š˜C ‹ļœžÔü§Ecn&Õž—}Ž\˛gđ™M„†Æ`MI#ąž‘ŅSf’fvāpdáōøĀA¤e 3ėmüTm'ĘŊpf˙Xōq¸Ü8s đyM˜Â"ą$¤’š×î#ļÚŌĨvļQ'‹-‚ܲŗ‹IHũeĮÁLô”™ĖˆĖĮév‘[čĀįÅ˙S˛ŅŅ$Îhū\Õ蔙¤îuātdáô@hD ‰Š)õžķ—•ŠgŪ:öyđĮ21 {‹Ÿō ‹ˇcõ8ÉÎŦā f"ĸí¤ÚããÔa,{Ĩ(,å+\<ēlöogɊը+žĸđŅõĖH…ySēoŊ�ØVŧĮĢaËXš$•ė3^0Ecĩ/%{ÅÍõ}ÚLâŒ4ĸd‘~ûnR7|Áō×iCŌj˛W<ÅōĖų¤.?ƒĪd&"&‘ÔUī°°_üÔ´ˆ\ˆgĪž=ȂĨĨĨŨ~î€īũ¯n-¯Â•ÆėœŽb*ÎxņaƁ՚„=yŠSZx9æ"#ĪÂį{Ėhŧtí&gå+d; (;ãS(Ņļ)ĖČXHZË;`‹_$1y=•$°Ŧā]Ōßöâ˜5– §—Đäw(Xm;˙6ģPÎ9n–O¸lĖïĩ™ —í]ÆōUģq•Sû/Y˛ÂBŪäÛÉ[ˆ#oöÅũH-‘ĩõ""ŌõĢö’U‘ÃŒÉOQœō+ üę"D ŦˆČĨÂøs`/y+_¤� ééJ^EDD¤˙ĶŦQU¸ČuSģŽÜ‚3DÎxĮR=WFDDDúŋ~p×%ĘÃ’%š`ŠÆ–ž‚• •ŧŠˆˆČĨA#°""""b(š+""""†ĸVDDDD E ŦˆˆˆˆŠX1%°""""b(J`EDDDÄP”ĀŠˆˆˆˆĄ(CQ+""""†ĸVDDDD E ŦˆˆˆˆŠX1%°""""b(J`EDDDÄP”ĀŠˆˆˆˆĄ(CšŦ/7~öīëË͋ˆˆˆˆiVDDDD E ŦˆˆˆˆŠX1%°""""b(J`EDDDÄP”ĀŠˆˆˆˆĄ(CQ+""""†ĸVDDDD E ŦˆˆˆˆŠX1”Ë:ŗpYYYOÕCDDDD$ J`-KOÕCDDDD$ šB """"†ĸVDDDD E ŦˆˆˆˆŠX1%°""""b(J`EDDDÄP”ĀŠˆˆˆˆĄ(CQ+""""†ĸVDDDD E ŦˆˆˆˆŠX1%°""""b(J`EDDDÄP”ĀŠˆˆˆˆĄ(CQ+""""†ĸVDDDD E ŦˆˆˆˆĘe}šņ“˙ųM_n^DDä’0ô‡WöuDēUŸ&°ú@‰ˆˆˆHgi ˆˆˆˆŠX1%°""""b(J`EDDDÄP”ĀŠˆˆˆˆĄ(CQ+""""†ĸVDDDD E ŦˆˆˆˆŠX1%°""""b(—õuDDDēęDéđģėm|÷—ŋō÷ŋ˙ŊOëōŊī}+ūár˜q'ÃbūšOë"ŌßiVDD éDéđō먊ũŽĪ“W€ŋ˙ũīÔÔÔōōë8Qú}]‘~M ŦˆˆŌÛYÛúē ­ �gĪ^œuéG”ĀŠˆˆ!Õ~÷]_WĄmđ—ŋūĩ¯k!Ō¯)éfÔ‘ūL ŦˆˆˆˆŠX1”~‘ĀVmŊ—¸á#‰>’I̊zwãûõo;5‹SŊģå Rŗs6ąÃGû‹]Ôôueę]ŒuęíCŌąš=õ}úž ëĶŨUŽˆHŅØŪÛz˜ē†˙íŲÎ!_÷oåȝ‰ķ$‡ēŋču1ÖûbŦ“ˆˆˆ‡ņČāčvļĸŽgrđ§ė;ö1[\O0.Ņԍ)įHQu7–×[Ú¯wČ´u”LõŠ;ãdÔ:‰ˆˆˆ‘|ÖĮĄ­SÄN}„EĶâ€ZÛ>éŪËlžcíå™ ŨĸŖz÷Eĸx1ÖIDDD ÅØ lÍA6î¯â¸íÖ ›tŖƒ ÎšŧĒļW9ĩõNb‡$îáhkĻAÕÖ{›ŊŸ?w4ąŖ2ø°¨ũ€ģ†$v¸…Ζkš€*mšĪ´ëlū9?NdŌ=OōîŅļĶéšĸxqîŊLēöÜōReŲÎĸV xÃüPëâ@9ųoÛN\Ûé¨Ūį›oZS´‹esīŦ¯×h⎝ĘôÅëÉ?ÕVÄjøzįoųeęTƌöĮŽąîfe_HĒŽngŲÃw2áZ˙6blcBĘln:@Ëju6Vđø„7ßËԆ};|4ÖëĻ1}ņz'ڞŗŌš8B¯ö!_ yŋžÍ¤kGûÛrí4æŦ:€˙cSÑ­ŋaúĪũ13‰is×s¨ĪTgęĐÕXļšŨjOgúš_‹>6&‘Šŋø-īĩŊĪ:{ ę0}‘ždč)UûŗČ¯Ŧˇ< āzî˛ũ–Ŗŗeg wΉŊām ™:“ûMŧˇĢÚ ĄLž>ž!Ļpl-‹6Ր?w:ËöÃHÛXė#ę8q´€ĸ‚XzO žŨÛx`X“ēī›Ī´yS^Dx\ö ƒ1՞äķS6>ū)yûWąķõŌPŧ)ˆ ĀW{šŧšOņØū FÛÆcQÛævŽw 'v>ĘôĮ?Ĩš ĸŦ $†Ē7Ž]kpíų„ßŲĖĸŅ Ŗ¤5ä/žÆũģNCP8#lãn‚ę“rųÛápŊŞ&Ō™X6ããÄÖ Ļ?}°žNãšmR8ԜæČƒŧ˙ÜAōvÎ`Ķ;O0.¤kąęˆ¯h=ĶR×PÔ°¯n ĮäĢáÔąÃõqų˜…;ļōЈsŖĮ‹cÃNî­>t’w^žĸĄŒK˜Č°j7‡ Žąo]§‚ˇ˛œ§˜žYÍH[<öØZޏsôŖ5ÜWnbĪŽ™4 YgëЕXļ§ûÛĶųžUä=<ĮöWCĐ`FOšČČp¨*ú˜%Ї)šÜqģ�}˙ūäÃoeņŊW1˙ĮĄÍođîqo_WID:`āļœŧ­‡Š#ÛôęЃ°ß9že>Ĩh×|=įWŒŧĀ­ ›ü+–ÆžDūŽBjMVîę Æĩĩ`ŅÖFŨÂĻ?<Á¸Aõ¯ųŠx1u:o+dã67<eõŋ~j;ŋœ÷1åuáL~~+¯L‹ĸņtQõ Ķ2x˙“,Üjeëô(�LAūˇë\/ą,öļüŠÉI´í\īĻNdąđןRĮũoobŠ­a5YuĶ×ōæã¯qĶīëãZ´eģNCP\Ģ“žīDĶo~žŖģžįÍû&°hD'bŲĸNŋ\qjsÛk[Y9yĐš÷jÜ,ģį^6ËfáĒŲ˙ŦSbu~5ägžFQ]ŖįīdëœXš´’›f3íšÃŦ]õˇŋ}+ƒēĮŊՇœ¯ņĻíWėųà Ģ_éÄĻ;™ú\!Eëæ0'$žåŋßĘíCŪĖbÚÍĪsÔŊ÷‹f˛h]ŦCbyŨŪž.ô5Ÿk ËöWCP< wlnō8— ÷˜>Ž<xŋđ |(ščw AŅvāņÜ5éÜĄ6$q&öp äļ¸zņrXm8ˇŋĐ$ņ�0āŽiq�”•4^Î;˛y=Ž:žô8+›žt�Mdé“7LŽÍÛ9ŅÖvžm:Ôūv:ëČļ,ŽÖAøÔy,˛5Ũ@WeĖ#9v0ąA%i¨Tø ,}m/ŽYÁŨ-FlLÃnáŽŅ�'9RÔÎĩÚ@ę´9›ĸ:žô+–NnqJ ą˛hÁD‚ōļž>Ņ-ąĒæÄŠ: „aŖciŅJ†ŨˇŠ­Ų[ŲķĖÄÆ~§ãØ´žŊ҇ę†r˙3į’=€a“nô'Ķĩ5 ™ũÄšd`ØDnPΉ’sAî|:ˀt[{:Û×|Úų Õ@øÔGx ŲgĀİés\gŌ9MüEDú€AX‡ļ~@ >éėÍ”îš:¨&oëÁ€æu‹¨ņLŅúåAQáøj듏šNA\5u<!­W!Ä6‘Ģ‚€7GZæ~o§ŗÎÕkdĸ•VMXųûũėĪ}•ģŽšAŌäš}ō;|5T*įÔŠrNĒÆW?DVSSײ´ĀëtÔ˙ѓڎ•it}ŦjŨ¸JZŧŲ-ąŠbXl0PcÕoۘ#8ˆ‘6+Æ4ÔŽ qėt}/°Å&0nH‹×…3(ČßŪqŖŖZŧΠAA@5ž†öwĨe€ēĢ=îkå|]TKģûšX’lƒ;ז€]DņéƜBā;Ȗ=ÕĀ`NhI�� �IDATėĶZ8Žēķb7¯Ąd˙v5IîãqxT›Ŗuk<WTSRPĮ‰­ĪđË}mV]˙@ûrŠĘĄYÁo§ŗęđĀæ;ņ k3דw ōÚŽnģŖ:…3$Ē:…„3$¨ŽĻĒ hš�vKŦLØ<ÍMî|čÎæÁļk%)a<‰“Æ“dÁ f°kqė\}/°‡ˇNxŸūLTøyęĐX‰ŽÔĄŗą Pˇļ§3}­š’j8ßžœ¨sÅ_D¤2­ÚŗG-@-‡VÜË´VKԏTÕ°qO9ÉĶ[ŽĀô% ƒ>åūG€ĩ¯Žš^Bn¨WPĀO˛ōe1ũžį9Z Áq7p˙ÔF &$Čøp­[ĀFwO×ÉT˙ž¯~4­ÎĀCnä•ŨqÜ´uw}‘’Ã|Xr˜ˇ­āĄLžũ8KįL¨Ÿ‡Ũų8vŪ…ö!ĶyĸhĨģX‡NÅ2PŨŲžÎô5žšķ¯cęąNp1ÅßXūiøÕüdđ?rųÎŊöƒžĸņßÁ–kHųA“9°˙õ_|{ú+>;îéÅZŠHG ˜Ā–“ˇĩ ū—ˇj)qžgŲ:ŽnũˆĶĶ āfķz=1š0…�Õáܕídš­‡70!!@um€IsyĢ^âh-„OZŞ×ol1zčƒ]Al¤ĢĶüu2™€ēÚÆ“uk 'rS& @H,ö9ĪaŸžĒ"Žfߞxo˙1ö­žÃ×囨ķl!ŽcW\ }čęp,{SWúZÃ:uíŽãķueBO §ŋÅŋˇü )÷Ļ0æûí/qų°ąLnyÂøī+ųŋO툏鯌7öÄvļ¸ë h,Ë>ûš’ãíü}ąŠÉÁĀąíl9znõĻ—cÛ|cyO˙âV#Âj8U~1ũĒyąQ�ĩœ*iįĻ+ŸŸĪWˇ“¸ŽÖá$ŨÛ2y(įhɅ$¯�áįęTŪŪCHË9Uã_vH/ ´›`Üԙ,}}'˛ĶˆĘwn ŋ:ĮޏúP÷ÔáüąėM]ékÁ PÊęļ÷æ‰ĸÖĶēįÔßâ/"Ō9K`}Ųü%@í’ĪwĢlČDîš œ&okAã‰Âdō+ÔU•ˇqO ųΘ¯ÖTãlƒ:í9ØÎMD%ÚW‰ĒŪ|¨xWößprd˙AZŸÂŨ,ųųUŒ5™e.8wę "¤OŸ+‹ŧ†›ĒÚŌę@l}ŦāȞÃmÆĒÆõ Gę€đą$>ˏšō÷lįwûZŪ!æb›ČUÁ@] Uõ lįâØCęB:ËŪԕžËč¸` Ž¯î6’Q7û´NFģįÔßâß[ūÜÍšėūč�ûūpîīā‰˙j\â¯'7{oßGļmŪ§ŅW‘‹ŒąX_[öŸ‚ąßŲöˇį˜7}"á@õūí# !#†pėcļ5=åÔphՓl,oëÁãÁūQ_9§ēūD¨F#īMĮužgáΒ'ž*ōÁ}ÜĮ´Į?j#ęŒÎÕûĒ;gúÉėĀ–ė,oR¯ž~ëˇä•Q7p› –ą�§Éß_ÔŦ 5EYĖyü0ƒŦūX6Qęj^bŲž+T`ŲęOŠ%ˆ÷Îėø™˛]á;Ė›‹ŸaųãOōFŋ†Uåü˜Cĩ@x,#ĩŦs qėšŪëCŨX‡.IJ7už¯™7u<Á@õž—ø]‹ãɑUŋåŊĒ VÛéÚ1¨ĩū˙ŪōÁū?î#÷ãs(ūŽņũÚâĪ›Ŋ—ûĮƒš˙*r2ÔؚũÛÉĢÂ'r[bĮ3´Lļ;HŽú€åŸ°eöiƒ`ÄÜoŨÎr÷1ŪLLž-ž!Ļ:NJ 8ęĪ‹ ĸXōôĮÍ¯í ‰cd8UfIڝ|k"dęsŧ2­‹×Ŧ‡ÜÁĘ ˜žøcö=> ›7"“¯š¯ (ĒŽƒ¨ëYųĖ>Lŧŗõ6“•OdúĶŲ÷øT&Ŧŗ22ĘDM‰›ŖåĩĮƒká*�ĸHžīzÖÎû”’u÷1ĩh"ãĸ ĻÄM~A5Ãæoâ•Ak˜ä>HõÎg¸ņÜ4ũ níBž-`úãŸōū#7q$a<WÅ×˙Ú×aĘk!|Âŧr߅˙ęZ›ŨÂԌ]L_]ČĘ´DļXN5T•ãû4u æĻ§æ1Ža$ēSqėĸŪęCŨY‡ŽÄ˛7uĄ¯…Lū‹˛´ •Š“ųpt<CBę¨*rs´j( 3Æŗvõ§ÔQwîŌ•cP[ú[üED:Á@#°Uäm=H5õ’:ĀZšmęP ŽC[?ޤL,ŧŊ™eˇŽ%6¤†ĸŸ’ī*ë#lĖy•ÛcM„�uMį(š&°čųØĸ‚¨+/äĐŅō žÕkČÔÕėŲÉ­„Tģqėú€÷÷ģŠ ąrĶėU|¸ûU’/ôVā.Ô{ØôuėÉyšģ&ÄbĒrã<p¯}ጾqoîŪĘĸŅįž8 šēškfkâԁØ˛ķŽø†r÷k[Ų:gCĻÎc餥םäОƒÕtąNĶ^eÎ îŸ4J’ˇ-‡<×ILą7đāķīŗ˙íæąī^&FÎŲĖž sׄ8Lånō?ú€÷?:ȑrWŨ8‹wī䕩ÍĶÄÎÄąĢzĨukēËŪÔųžÅŨooåÍŲ×3zŸ˛Īu _ė-ŧ¸cŽÄWŗNŪ…cP;ú[üED5āėŲŗgY°´´”˜˜˜ŽŽˆˆH`2ũē[Ęųß?Ë`éūÄŊōŖWXūĮ7ķÅgģĨiÍ@#°"""Ũ¯öô)žũoāŋ˙'O×áō"Ō÷ 5VDD¤ģũ×ņ]üæŠ]}] éĀŠˆˆˆˆĄ(CQ+""""†ĸVDDDD E ŦˆˆˆˆŠX1%°""""b(J`EDDēŲ€úē "ũšX1¤ā+Ž€Ā~ Ŋw=Ë˙đ}] ‘~M ŦˆˆŌ/fŪ ãHį€ūē‰HQ+""†4,柙ûđ,‚¯¸âĸ¸d?`Ā�‚¯¸‚šĪbXĖ?÷uuDúĩgĪvũĨ´´”˜˜˜ŽŽˆˆˆˆČųiVDDDD E ŦˆˆˆˆŠX1%°""""b(J`EDDDÄP”ĀŠˆˆˆˆĄ(CQ+""""†ĸVDDDD E ŦˆˆˆˆŠX1”Ëúrã'˙ķ›žÜŧˆˆˆˆtŅĐ^ŲgÛîĶVDDDz^_&"=ĄOX} DDDD¤ŗ4VDDDD E ŦˆˆˆˆŠX1%°""""b(J`EDDDÄP”ĀŠˆˆˆˆĄ(CQ+""""†ĸVDDDD E ŦˆˆˆˆŠX1%°""""b(J`EDDDÄP”ĀŠˆˆˆˆĄ(CQ+""""†ĸVDDDD Å@ l o¤Œ$vøų˙Ŧ‹ úēĸ"=Ŧ€%×Ö÷ųkŸ$ŋĻųģGVL"vøHĻžUŌ7ÕëuMâŅđ÷ãŅX¯›Æ}‹ŗČ?ákąŧ›e׍$vøh~š¯ģëŌ“eKkmėû–÷ėĸĻã‚:)ĐũÜ}ũáëUūĪuÜÜO.Ŧ ‘~ⲞŽ@āL„ LTmũ}ՔW×AG…R˙rČ SÕO¤TĀ‹[g‘4'ļ¯krQ Ė đųĒŠ.?†s×ķ8÷lįļ5›X9yPũRáŒHObuŖˇ81†}ߒ)ŧEÄđ ”ĀFq÷ëûšģáŋŽ'I˜ņÕ$°t÷:n×1J.QEë^"oúĢ$ë3gw˛iĒ?žSXûøŪ,8Éûķ0ú÷›š{@ˇŋ°ŽÛû´ĻŌŨšî{é˙ 4… 5ä=l#vøHpîÂĄüÅļú)đ5^vJdÉž~7w ?MܘIL_ņ §š•YÅĄˇæ3ũį‰X<ëuwōËMî¸$%ŌYņLžĩŸ˛6ŗˆ–ʛņ•ˇb6S¯í˙\;9oPÕ¸@Ãgb*Ë\nŪ;„$îÚ;Y¸§_Õ^ŧgRũg`6omú ¸8?#Ļ!XôúsÜÔfãļĸúwÚ¸Ŧ[UĀsīdÂ|⎝Ę}ŋŪÅ׍čĖ1Ŗ…ķÆ>ĐcVKM÷Wyŋö×=nĖ$Ļ˙ēE}.Á}ßļîŒYuEŧˇø<eĩ@ĖjŠę÷ÃhâÆLåžUŸpęŧp‘KO?K`C°ß9‘p úĀGi|ŨÍžĩ@8ÉĶ0aÂd¨!īé'ųĐtŪ7‘!žĶ¸6ĪgNãÜA‡VÜĮ]Ģ?ÆåJō}w0.ø>w/÷mēTæĘÅĖ6{Ŗƒ dëōÚ<ģÔāX|m>ČŠđ‰Üī ņcßęŲĖŲT^ŋLÃgĸšü§Ÿá}â7"˜ēęBŪ_œÁ}?ĪĄ¨ņ$ڍ-?ČĘy/Õž.ōĪHČDîš@‰Ģ°Äĸ„7~1›•Ã4úFîŋ7 {T-ÎmO1}ŪGõIK ĮŒ–:Š} ĮŦ–šė¯ X[‡}R!ĩ§qm›Ī­ånŋEYũißˇŌ1;įHfkKšĘ‹ŠžŦöûC 1ĢÁņë9,ũčÕĻX’ĻŽÅä|†%{Ēģ?$"f )1%ŪArÔl,?ČžŖ0n4PtCÕ@Ô Üfkz*¨ƒ„'ØôÂDB�{øIŸ;FŅļí|=į FV}ÄÚ­'ÁÜŋfKG›āŪ8îģî)œë6?ũ9’4åVúRÔ-,š–Å]Û˛6ŗ€ägZ/SãÆYLlėPn{a5€áĮ˜ŧú$G÷¤ęž;87´–š¸yė|y!U#¨úé3¸ęŽq"v^HĀt*ŠׯĄ¨ŧC'āǐ‹˙32dD8ėdžę’ļGkÜ8ÕAđ ,zũ9ė&Āw“3RŪbôŗƒcF[ewûNŗZĒĨjČĶėũFá#‘ÉÜŋ̚#ûŨÔL"äÚ÷‡~= ĢZžNōšm,ŨôĩîŒY5Q°˙í[„¯WMãĻu')Úļ#sžāĒ–Õ äœRķ1÷WáÜļf++Māģ•e7OgŖrX‘FũlĀĘ]wN“ŋßÉđ„ķ %@ėÔ[ZP‚7y|ã `CF%  ü¤˙ŌaŅAžŽ‚b^CUUUD1lPíÆu12Č%&„qŗĶąAųΗx¯­ëŒ!Xžģ‡ũŋßĖC#�|„ ōJâĢi‘Ô1rr‚˙31h(CÂũ¯]5ÉęĮ°`€Ēk1ÄgÄ×S;Ų”)Šaá@íĮ,ŧų^~š"‹÷\0.#Ļ%0¤ŲÂ3Z (ö9fĩÄUSĮ×'T&† @]MĩŋėKhß×՞Ļŧŧõ_MĢD÷ÆėĒiË9i,á�åĮ8ŅV$f%îúeâIløōb˛rSBøÅG¤ŋéw#°�ÃĻŪÁčĖį9ē˙ '„sh˙1 ŽÛnŅbIĻā&'5“ŠūR] 55āĢ­õžÔdŅõI-Ö-įÔ) e‘"ŊmČ-,šžžÛ6˛6ķ +[ŨĮRÑMΰlķAž.¯ĨރâB‚> Ÿ!Ļ–¯Õáß§Šę/û†GŅæCL ,zũqĒ}%‡ų°ä0n‚3ųŠuŧ2=ļÉ%üķ3čbė?fĩtnŖĻVIúĨŗī× ø&Žî‹™‰ā&Û4…Tãkŗ?35ūeL&Bšvĩ`@C°" úeː¸ËöG|Œãh8Ž"Āz ÉÃZ.čŖĻļÉQĻĻáÛu!!` ĮԍåÁ5ŗhy%o’Wš(˜¸jö¯HÜš�įŽ×Ø2ŠyGõ9ŸaÎsSĮ]ĪĪãĻØØ˙[îZWxá[žØ?#UŸ°å€˙Ų{#&Œm;BFĪä­ßĪ¤æ”›#nœûˇķŪū“ė[ņ$īMØV˙ôčč˜ŅRĀąø˜Õ9—ôžīĸÎÅĖGM­žâ4ô‡ ļûC 13…ø—ņÕúGŠŽĒEDÎé‡S�aŋs"ÁãũŲĒ Â6톗ę8˛į`ũM>Žė?ė˙~ĮČ`ÄXFuĩ;¤Ä $%ÆâĢÅ×Î K¤O ē‘E͇ĮØˇ˙dŗˇNĢīסđā´ Œ=_Õi˙›uĩįzAG.âΈ¯ę�/>ü ûjāëylz;ĪĘ=u€wßú-Ëv2ÄJŌ´™,}=“ã€ējJš zupĖh!đØzĖęœKuß_ˆÎÅŦŽ#{>Š˙ããëõũaHÛũ! ˜°úįR×cŸŗ~BÍŪ/ĐčĢHSũsIŧƒäđŲâ>AãšmR[c/A˜Ž>Ī´{>a\x9ų‚}įūČ ylÚkÜĩíoθ“S“â0•$¯ā4Xįą'ŅÚģ9‘ŗÅä­ū„­‰!#ĸâ$uĮ˛X˛ĸšaåqT &–jJJ>aí*+‹tq~ŨEöiŧ‘ĮWKUuũåß ĄÜĩæ9ėí&T§ųp]6ŽÚ9âŧqC Ļä0yĮ€¨ņLn6’ØÁ1Ŗ…Āb?‘azĖęœKrߡdJ`éîú›ķĐš˜a:úSėÅlĐõÜ5á%\Ēųđņéøö[ņšr‚` –၈\"úé,`˛29!€āÄ[°ˇs.ˇ “ÃO’ŋŋšāĄ$ÎÎä­ûĸ aÜŗ[Ų8˙zF›NâØ–C^\uëĶl}; ŧē'ŌŊB&˛hv\뗧>Í+wÆtįÖ8t ¯ŧÉŌ;‡ĖIō÷´ķ|ˀ\\Ÿ‘ÆyĒk!|(ļįņæî,O<Īpā;xëí§š-!˜Sûŗys]6īšj6iof?Á¸‰ĪųÍu*öŗ:ã’Ü÷-˙JĒÛ¸‘Ģ}ÅĖWs`ögVs€ũ!°˜ "ųųLNJ°ī$ųÜ0i¯Ü;Ô_„Īwa#į"ũĀŗgĪž dÁŌŌRbbbz¸:Ũ¨ęæü<ƒ}ĩáܖŊ•Í&šYvŨt6–qĶkGyerŸÕRD ĄŽį=f‰ˆHSũo ÁŠXōÜ9Z@Q-'<Âc:ˆČÅJĮ,‘NëS|Õ|í<HQĩ‰Ø ŗxkÍ|#„ˆHŅ1KD¤Ķúīé—úßŦˆˆˆˆôkJ`EDDDÄP”ĀŠˆˆˆˆĄ(CQ+""""†ĸVDDDD E ŦˆˆˆˆŠX1%°""""b(J`EDDDÄP”ĀŠˆˆˆˆĄ(CQ+""""†ĸVDDDD å˛žÜøÉ˙üĻ/7/""""]4ô‡WöŲļ5+""""†Ō§#°áaWôåæEDDDĀ4+""""†ĸVDDDD E ŦˆˆˆˆŠX1%°""""b(J`EDDDÄP”ĀŠˆˆˆˆĄ(CQ+""""†ĸVDDDD E ŦˆˆˆˆĘe}]šø”ž:ÍÖģøË_˙‹ŋ˙ũl_WGzĀ€ø‡ËŒ;Ļņè+ûē:ĸXiĻôÔiÖm|—ÚīūĒäĩ;{ö,ß}÷ŪüŨģ|SYÕ×Õé%°"""ŌĖ–œúē Ō[ āėŲŋŗyëû}]“NQ+"""Í|÷—ŋôu¤7 @íwßõu-:E ŦˆˆˆČ%îėYcMQ+""""†bČļú‹m,ÆõI°Žą16éŌf?ˎ/k›,õ/ŪdãæĖã}VĪžõg–Ųm¤m,íëŠ\?wÖé›9Ų×éfÆh—ßĪ÷ šũ]3‘î]Âīūtē¯+ØĢŒąĪēGõŪ…ŒsK˙TÛÆģ>?} Ö¤…üžē×Ģ&"=ĖpŅĒųĶŗĖČČ'øĻt–Ėū ƒBŨ7ĮųũĻ×X>û8ĩ›ŪáKĪ×ŖúŊ‡Hûr&Ÿ>sMĪoŦKb¸yūR~3°¯+"Ō;ŽœĘ ŋ™JC¯Ģ-ãŗ­ëY›ņ0åomgéÕĻ>­žtŋđ)ķxėũ,_ŊžÔ1ŋ"žÉ.öo`ų‡ßrõ¯ķķđžĢŖˆô ƒ%°>žx§‡?ÂGĪÜITÃËC‡?f žûÖPøe)XbzŧÅ_§Ž‡ˇra?åĻžŽ„Hī ŽÆrõ†6ž0†Ÿ^;nû;ļæķØÕvBú°zŌ“úD:ģīzߟÆ;wÅÔŋ~šĪmãôđYŧ|ûāžŦ ˆôƒM!¨ŖÖāk<šŽaéÖ^ž=ĻÅĩŪ¸›“&ø/)Î~‰Īš^NōæĶU‘fŸĀØ1oŋ‡š™ŸSۏŸYfŸĀ/ļ|Î;ŪÂØkbÉ/yøÃī¨ũp.Ö1iŧ\ÜēĻÕ_lfŅŊˇ0ūš&—2ŋøļÉßōŲÆ…¤Ų'úˇ{Ķ,}ī85(ãüīˇ1… Ãļ~UßÖ¯øtÕCÜlŸČØļbÖ˟Yš4{6~Îî§↤ ŒŊænžŋ™ÃM¯ęU˙™ßÍ÷ŋoccŧũmüs7ĩ÷+–ŨÔĸ­Õ;ųÅcįįãkŅž{ļœ0-÷ũcėŽĒ?įåGĶąö4ænüœĻ{ļįuķ.ÆŖĪÛÕL?"ŪDŨéoÎĩE}ąz+‹îē’/ßZÃîúãTõŪ5ŧųՕ¤>yg“/4~Ė‘‹‡ÁØ`ŽūŲ :žšOlãĶâo›œÚVûĮ5üîÛÉ,۔ÍöÕ3\ŧÅ™Ÿ×¯WËgĪ=Ėŧŧ:ŽũÍÛ|°w;žø ÕīÍåáU_Õ/cÂTĮé÷×ķؘyŧŊi1ŊœÍcÃ!ØžŒOūø6ĩœ˛Pë`ųü ”Zæąáũ]|´e-YJy#ãŲúŦÂUņđēo5-ämįõ91Ž~ˆÅy§+ŖÃm´ŠD�mSPś^âŖ“ãø„ÃyŋæęĶMcÖZŠŽâM¯ņÅĩËų ˙�ķ'žøuæũÆQrø–Ũŋ™ËÚŌ|5›ōļķúüá¯[į÷ÖvC{ÄõŖŽāäŸŋj<ųžüœâ˙Dpņį4~ŋ8ųgžŦŊ’ĢĮ î⾟ĮuÁ߲ûÉEl,ÎÂuģøxĶrRžÍâ?öîãGÎķŽÄãâhW÷û†Ķ§ë8°~júb˙c"~öbR‚ą6ķsjj?gíj'Á7ÍãÁQ s ēá˜+"ƒM!€¨Û_äõÚgYžn ķk čJ,c~ÂĩSĻ’jCT‹inĩWLcÉģēÁĐ+yāÚ,>~œĶ\ÃĐę|Ūq|èŒ×™{mũeĻë~Œ¯qĮ{Ûø,c9×חwú ;îOǟļpš  (˜đāā֕<ũ8ųŨ@~šœDÜ`€ÁD=š–˜äo jķyãũ2,ŗˇŗtJŒŋ]ƒŗäËcÖĻ]K~„¸ŽĘ(íāũ–:ŅVßđi,œƒ <‰”1˙DnCĖÚÛ1–i<6epã:Ūõ¯äfîáOĩv~|×=™Í\ÉĐÁĻúöÎ"uËŪüĶW0嚎cvŪöšúŲášãKßMüÔ…ŸÅ@ûMXŸķÅIˆ 5_ũ'ƒÂBKįâŅlßWīdĮŸáÚß,ææQū@_ŋ`…ŸĪ`c{ąé)į‰ųĩ]ˆĮEĶŽnâĢ-å‹÷^āwĮ˙‰ëV%ÕOP_ė—LcX82Ÿ-xÅßņ‰ŧ°āšsSFēã˜+rY8ļ´LöŽí[Ųyŧož,ūīņé<žōCžßøĘs2÷ ^:xi}Ķ2Ø,@0cī_Éî?~ÄöĖĨ<–üc‚ŋÉgãoæÆäĮØQÜ|œ0|ԏĪ͕ÅDđĀ`¨ûÎ?ĒQúÅuW?ĒųŠĄc†ôŨq ›Üŧ<ØŌ´œČã§W~Î'bŲŸ‹ÄN¸ (ũ7ŠëŽäęĢcšŦd"ūšTú_VPFGīˇÔ‰ļ†Į o6WĐl:ŗv„NĶû$ĸbbŽû†c§ũm ú–ĪŪZÄ=ˇŨÂõöŸ4ƒĩ_¯Ž.°˜uđ~Ȩk°Ô}ÅáR€R>û3Ä_{+?ų†Ã_Õ>žüĶW˜Æ\C|'ãŅlߗ~Åi2txĶ3Z4c‡_qžčôŒķÅŧ+ņ¸XÚÕeĮ_į–&O!HøŲ<ŧé[Ž~b-KŽkh—úbrŨ<ģĻ–?}ū-ņķ¸žiXēã˜+—83#ĻŨĪ=Ŗ˙‘˃Ču÷ŪĪŊōY‹áîgž%ķÅsK›%¯�ßghĘ/›-“ųĖ­üK/ÔŽ/nļ‘i q×ŪDÜĩ7ņ�Pķå6Ī_ËĢ÷pŨēi'ö ķxjŋŖ– Â[ôASĐSG]“/WAÁA¨Ûmz›Ą›6ŗ{Ë ŧˇú;‚Žü )‹Y4%SíwÔņ īŪgãŨV+GS[ „wPFGÛ¸ļvá`<°EÁAAQG]āûŠį<ĖŽ Š,ybWĮcâ[rįĪā@cÖŅûƒÂՃ_ãË/ŋ…˙ƗÕÃI5˜øQYûÅWø’ōŲ—uŒšķlēēīkëüë5ëĻÎõnrۘw:Oģē,æVÖ<7†4Đ4C6ŋqK}ąČO§ ‡Īáį×´¸qĢ;ŽšŊ͈ūá˛0FŒ›ĀĪŽūbũ#—ŋåūûŋøļęE_|ΞC˙Î˙ũŸŪĒ ™IžAú„ryKūõč~“SMŒå2N8Ā˙ų×ųŅ€ībüŨ“ųķĶģø÷Ū¨˛´b¸ÖWû-ĩA[}#u'ũl33ūxœ“@@OŖų>đ�� ­IDATM öŸ Ē[\đÕ~G-W´:ĄtJøI]°’ÔPsōĪüaëV>9ĶāX|A\IĘĒ—y ĻÅzĻ  ŒQŧß[mjŋmQp˙¤ü˙öî68ĒęŽãø/@ö:Ąq˛Ō˛M˜$í$aŠ@+ž°´ Šã#`G-­LŖ #ŖŽ%ˆĻX|jITŌ‘P°ō‚:Ąã8ĐŲĩ%‹ŗ]d“ŨāöE@!MöėĶ1ßĪËÜģģ˙sfY~{îĪ=ēCīēGjæk+uË7uyõeīĢ/o&—ÚôöÁud7ĒÕ^ĸbKĘģļPZû/š<Ų:äÍ׍ĨÖĀæÃĘŌPÔqҝģäķ%˙RRØ9WŦķ‘>ãēdYšĘsôßæ"ņ^Ŧâõ™‹Člc5÷ž9šbī/ĩ^`ČpeÛķ5efž~Rv@¯žT¯æ¤ŧ}š21ŠđúÉ{úĶö•,ŦĐ<gĻ\;7kĶÆĒ¸¯'ĞųˇK'^Ģ[[[ŲĮ/˜`=5ē{Æ:î¯Õ›Ž^ŊryNKŲšŅ…WIrʙõ7jöH\žs<Ē€­PÎHģ¯ôŗV—§E{Ũ6MžÔķ­ũōŧRŨ˛lŠöÖ?ŦVˇOšZĸâŦŋËsÚR^Ūû´ú<j“]—Gņ]؟†ŪļkžæŖj͎—7]­n˛Æôô’5PļŽēā?âŽÖŨÚã–äˆnÎ"×RņĩĨŌŗÚžuLVŅž ãœ(gGöũÒÛ^ǞķãŧÔų°ČĄŨrõIÎķ×(ŠŠ9 %šO.ėœKąÍG+Ą|ŧ%ĮĀ?sUĤF”9Zŗî›Ŗ)9ũ‡×`ûamßĩG[?“O6]å,ÔMĶnPą}‚*îëÖsUģäNÂJėI ֟ŲĒæūNĘĨÉ +4īÜŋ¯ŧ›č^õ„Ø™?íÖîē“`%éŨųä¯UÅwI’ŋYՏŊĨà ­)õĖęĩßŦĘĮČõübŨąĻFī6ĩčHk‹ėÛĄĒe‹ĩēŅĻëīœ~æBÖuēkFޚ_\ĨWöšÕÖáŅ‘=ÔĘm'äœ=Oũī{nÉ*ų܍ÚÛęV[ī›Ā¸ë´z؃ZūFŖŽxŧjķÕŪ7jÔ¤\X’uæĪČÖĄ ĒjĪQĩuxåjŪĄU‹æjîv¨#šįˆt<ncr*OÔé™WĘÕáU[SžŪzLŲS§k’%Š PÎŦczgëûruxåjĒŅō5'ä,ÍRĀĶĸCžŽ¸ŒwhŲDyß×ë>9¯Í?WXĄ&؏éõ­-˛J¯Ķ؁·ũgēĨPÚ÷âSzĢÉ­6W‹Ū]ķ‚ö’ßwvÎã|¤Ņ¸Š÷âāĪ\D”3q†Ž^Õ~@ÕĪoÕ??úLžnIŨ§ÕöŅ~mz~ŗÚĨ!9eš;1Mî:Ņ+ŧöŽŧ›h^·Úōf“Ú“ÖōĐ)g §wwƟ°ZŌ‡Y+°˛4ĄōeŊ\´I¯ŧ]ŖÕõ'ä HYļ\å•聗ꎲXî<ei²j­ËzJë_ õ^ÉĘÍפEUz¸ĸ LĪ“ĨɡũRŽĮkĩäÎŨšŊa—V”}{tč¤åĒŽ\§g^{B ž=Š@–MvG‰fŽŠ:ˇåÖPM¨ŦV•ĩNë×.ÖīiÉ6FÅSRõīĻ÷Ŧ GxŽĄŠôņktė3–ęFīKZRŪ"OĀ&Į¤‡TUyî—ßŖnÖŖ•-ZžáQŨēMĘ.ŧATŽĐõ^›Z—ũUw/’6oÃx­M.8Š}-%šüÍö9v•Ų´~[@ŋ˜š‡ų°kÎÚÕr=žNë3WOgQŲė‡ôđŒuz0ÉÛ…s)ÆųHŸq%īÅAjāŸšˆd”ŠËrÃ÷ëāŽ]ú¸¯ ÖéÖļ]Í*YX¤Ņec•ĶĐ öD•>Ãk/[w띏bI“ņĐ-ŋ_Ņ_Yņw*˜ČrŌDF( EsĸÛí–Ãáˆë‹û|}Ũŋf9¨U?_ĸæÛújë@b0į�ĢrÕ31>"Ōeîcz}åf}Đ_ööc-~b–Æ›UũHĸ/ÕŊOŪŽbõŅB6ŧn×úŋ$såõŧ+4uéoU>&ĘĶ?ŨŠĮ^ØIí kVü>Ļķ­žļMŗZ���!-ÃĢs Aįāh! Ā�€:Ĩ㟇;>ZãŽîŋk1ķęüžßQ~~"EˇCÎÔ¸YaÂkíqŸ5åüo=“Ŧ[g‚Ņ7ƒƒŖ…€�‹*Պ÷>āRvR1į�ŌM‡5s|¸J§MĶ5Ãú84ĖĄōiEēLAo:’ĸūW›Ž´÷ˇōęVūü •˙č)ÚāŖ[ūÎč—`ũJÉBq’`�Ā€ĩī¯×žö0k9´äū9šZ0JVϤL›r &ęŪûhJŽloRíūTŨõ ]Ų+~Û60Jã8<5eã÷GIƒ1õ˜Ë°]��@Zę>ŽēoiH˜Ŋ`‡äŠüž"•÷ú{°ũ€^Ũ˜œ=`ûTĐߊ“˙=%¯į€ļÕ}˜ĸž×˙ôŰëK“ĸŒ� ��âãôÕVmĐĄ´ž•l_ÜĒ]ĩJĩŠ,!ŒX[,��ˆŸîS:ÜP¯Ã Š.¤AI2N‹ž~"†Ĩ.aû}=?ˊ|3Ž ü1Ŧ֚ŒX��0ˆ¸Õ°˙?:Ķcürí? TQ$ŨŸ4ĒÁysŦ3ž&íų„��€ī˜N}\ŋIËëS]G :ŨĒĢZĢēTבFX��€Q°���0 ���F!Ā��Ā(X���…� ���Ŗ`��šŒŒTW,��¸ˆmÄ)JuH–PH–ÍJu1!Ā�€‹ÜūĢ[Í[’ÃĨû^†Ū^žę*bB€��qŒļkQÅ|ŲFŒPAö;mĈ˴øŽĘÍų~ĒK‰IF(Ũ5ˇÛ-‡Ã×÷ų|q}>���$‡eĨŽí€X���…� ���Ŗ`��`,���ŒB€��€Q°���0 ���F!Ā��Ā(X���…� ���Ŗ`��`”ĖTžx*īĄ ���3ą ���Ŗ`��`,���ŒB€��€Q°���0 ���F!Ā��Ā(X���…� ���Ŗ`��`,���ŒB€��€Q°���0 ���F!Ā��Ā(X���…� ���Ŗ`��`,���ŒB€��€Q°���0 ���F!Ā��Ā(X���…� ���Ŗ`��`,���ŒB€��€Q°���0 ���F!Ā��Ā(X���%ę�›™™ŠŗgĪ&˛��� ĸ¨ė°aÃôÕW_%˛��� ĸ¨ėȑ#õÅ_čÔŠSúúë¯Y���Đ¯ŒP(ŠöäŗgĪĘëõĒĢĢ‹v���¤DL���H5v!���€Q°���0 ���F!Ā��Ā(X���…� ���Ŗ`��`,���ŒB€��€Q°���0 ���F!Ā��Ā(X���…� ���Ŗ`��`,���ŒB€��€QūԎČËĸj *����IENDŽB`‚�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/mod-user-database.png�����������������������������������������������0000664�0000000�0000000�00000070377�14156463140�0022673�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��%�� ���ģ’f���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨw|uūĮņ÷n6Ŋ‘B!”PCīDĒô"UDłŊ{žõŧĸwū<īÔŗgĨw¤‹‘^‘^CBIo›˛ģŋ? !Ų0ÁŧžGfæ;ßīgfÃã1īĖ|gM/žøĸC����`ŗŅ����¨Ú%���� eštEnnޞ˛˛TXXhD=����~‡,‹üüüäíí]rÛÅ iii ͤI“T§NRw����€ōČÍÍÕáÇ5oŪ<>}ZÅļ;CIvvļÂÂÂôâ‹/Ęd2]÷B���ü>y{{+..N5Ō̝žĒôôôb7@œsJrrr4bÄ ���€ a6›5lØ0åää_ū6›Mõë×ŋî…���¨:ęׯ_būēÅdēđ.ë]���€*ÄËËK‡Cį^ ���ĀP„����†"”����0Ą���€Ą%���� E(���`(B ����CJ����Ębt����Ē– ›ˇÉ×oŋs™;%���� E(���`(B ����CJ����ŠP���ĀP„����†"”����0Ą���€Ą%���@æp84ū|ũđ×mŗiĶ&M›6M6›­Bj¨ôĄ¤^ŖĻ>jŦËÛūž\Ëq?ōøSĒÛ N‰II.Ž ���7‚Å‹kĮŽZąbEŠÁdķæÍZ˛d‰öîŨĢYŗfUH J^ûĮ˙Šnƒ8ĩhŨ^VĢĩĸ‡���PM›6•ģģģ$•&[ļlŅ’%K$IfŗY-Z´¨*4”ähîü2™LĘĖĘŌŌe+*r8����åTˇn]?^‹EŌ…`˛uëV}ũõ×r82›Í5j”âââ*¤ŗŦ'´5~ŊĻšžķå+V*55Mˇ+z´hÆŦŲŽ���Ā5)-˜,^ŧØHFŽYaDĒā;%Ķg=svםÕŽmmŲēM,ĩíšīÖjđ°‘jÔ´ĨÚvčĸ?>˙’222ŽšíåėÜšK“|D­ÛwRƒ¸æęvSo=ųôŗ:q"ĄLû?öäĶĒÛ NzūĨ?Š]ĮŽjÜŦ•†Ģ;w)77WųÛßÕąk5iŅF#GĶĪģ)ŅOBÂI=ķĮįÕąk5ˆkŽÖí;éŪÉjįÎ]ŋų¸īžī~gm+,,TŨqšpĮ¤+ÛŲŗgõō+U×ŊÔ ŽšÚtčŦûxX;wũTĻs��€OllŦƏ/777IEāM&“FŽŠĻM›V訖Šęøđá#Ú¸iŗZˇjŠ:ĩkiİĄÚŧeĢfΚŖž{ļXÛÍ[ļęŪûThhˆ}øA…kãĻÍē÷ū‡d6›sÛËųéįŨsÛDU̍ģꥥ:vü¸Ļ~5M߯[¯•ËžVPPĩ+öqūšģ‡}BíÚļŅgŸ|¨_ŨĢ^~E=ú„5j úõęéÃ˙ž¯ úãķ/iŌ=“ĩ~íˇÎ}O&&jØČŅĘĩZuÛøąjP¯ž’NŌ—ĶĻkĖøÛ5õķOÔŽm—wY$§¤hø­c•‘‘ĄņãÆ¨AƒúJLLŌ—_Mטqôų§ŠCûv.���•GffĻėvģsŲáp(++ĢÂĮ­°P2}fŅ]’Q#GH’ŧEūÛkšˇ`ĄžyúIyœģ0—¤÷>˜"ģŨŽ)īŋĢ͛I’ÆŒžU/ŋōWmŪ˛ĩXŋåi{9;wíRũzązášgÕąC{įúˆđpŊō×Wĩøë%šxûmWėÃâVtęj×ĒĨG~P’Ô$ŽąÖįÕŌeËÕĸy3=ûĖS’¤fM›hķæ­úė‹ŠÚõĶĪjĶē•$é_oŊŖä”}đî;ęסˇŗī~}{Ģ˙ĀĄúûë˙ÔŧŲĶ]vÜeņÖÛī*éÔ)͝5]͛]HÄÆVŋƒõÚ?ūO įņ��ĀīÍΝ;ĩpáBį#[&“I6›M˖-“$uėØŅecÅDGŠCģļÎå y|+??_sį/§§§ č/IōķõÕ-ũú*55M+W~ãlkˇÛĩqĶfÅÔŦéŧØ>oė˜[‹-—§í•L?N‹Ėu’‚‚åååŠ^ŊXIŌ‰„˛=Â%Iũúö)ļ\§v-IRīŪ7[_ˇnmIŌé3g$ĨÎUĢV+4$D}ûô*Öļ^lŦZˇjŠ;w*55ÍeĮ}5‡CK—/WŖ† Ą3gÎ:Ü-îjŨĒ•~úyˇ˛ŗs\6&���Œˇk×.-X°@vģŨ9‡dėØąÎGš–-[ĻüŅeã;qRq.Wȝ’eį&¸2Xū~~ÎõˇŽŽų iæė94đIEéyyyЉ‰.ŅOlŨēŖËĶöjæ/(ĒcΝ{•™™Yl[aaŲŋ&""ŦØōų."<ŧØzwKŅĄÂ‚BIŌ™3g•™•ĨĻM›Čd2•čˇn:Ú˛u›9ĸ¨¨H—÷•$''+55MŠŠięĐĨûe۝L<Šúõęšl\���᧟~r“ɤ#F8ᐌ3F3gÎŦ;&û–$ÕĢ[ģbBÉų î:´×‘ŖGë#""ĸõ?lĐąãĮSŗĻrs‹žģÄĶÃŗD?žžÅו§í•ŧņæŋõÁ”ÕŦiŊôÂU3:ZÚˇ˙€ž{áĨ2÷#Éų†‚˛Ž?/'ˇčnƒˇwŠÛŊŧŠŽ'''×eĮ}5YŲŲ’¤ÆéO=qŲváaa—Ũ��€ËÁƒeŗŲd2™4|øp5kváɜ† jôčҚ9sĻėvģΞ=ëŌąĪ‹Tô—{7u|čĐamÚŧE’Žx?kö\=ũäã΋īŧüŧm.}L¨<m/'//OŸ|ö…"#"4męįōõõqnģôŽIEōõņ•$åäæ–ē='§hŊŸŸKŽ[*zLíJü|}˙îŅŊ[™û��ĀkčĐĄ˛ÛíĒ[ˇnŠ_ŽØ¨Q#5JÔ Aƒ\>ūžƒ‡e‘Õ*Ģ,ōđpM§į'¸5RŨēu-ą=//OĪ<ûŧfĪ§Į}XÕCCåîîŽãĮKÎãøuīŪbËåi{9gΜU^^žš5kZ,HŌÆM›Ëԇ+T¯ĒĀĀ@8xĐųēĩ‹í?÷ęäēuęČĮĮ§\Įm9÷¨XAaaąõĮ¯ōēãĐĐPUĶÁƒ‡”‘‘Ą€€€bۓSR|õƒ��Ā ãü#[WWąßS’t4Y…Ū! ôšzãĢ9?ÁŨÃŨ]O?õ„ôīWâgøĐ!ęÛ§—Μ9Ģ5ßÅËbą¨uĢ–:zėX‰īÁ˜úå´bËåi{9ĄĄ!’TâûH~ŲŗGķ,”Tú‰ŠĐŋoo9sVĢžY]ĸ–;wŠs§Ž (÷q‡U¯.I:xđPąõķæ/ŧjMú÷W~~žū÷Ņ'ÅÖ'§¤č–AÃtĪäĘtl���@YYöĻYŅ´ļüķO_sgËVŦTZZēn1üŠQŋãö Zžb•f˚­>Ŋ{iōŊwkãĻÍēįž4ęÖ ĒVM7oVnŽĩØDyIåj[///ŨÜŗ‡ž]¯^~EÛˇ×ūôŗĶô֛˙§{īHkÖÄkŅ×KÔûæžōņqAZģŒĮ}DĢ×ÄëÉgū¨;'NPŨ:ut"!ASŋœ&__ŊxŅ÷š”į¸G ǝĻĪĐß^û‡ž{öy{{iÕ7ßjûŽ%î]ęąGԚīâõū˙§Ķ§Ī¨Cûv:uú´ĻMŸŠ´´4Ũ9ņö 9���¨ēĖ Û´QÃ×Ėww~ƒûWžpíĐž6¨¯øĩ딘”¤›zt×;˙~SĄĄ!úøĶĪ5åÏĸŪ}[~~~*Čŋ0ĸ<m/įõŋŋĒ!ƒjŊUzņåW´uÛv}øß÷tSîz䥔‘™ŠŋŊöz…ŋú6<<L æÎԀūũ4{î<=ûü‹šúå4uęÔAķįĖTŖF mËsÜ­ZļĐ˙xMVkž&Ũ{ŋîģ˙aĨĻĨéŖ)īË××Wųųų—­)4$DķįĖÔmãÆjũôĮ^Ō”?VãÆ4{ú—ęÚĨs…ž���T=Ļ—^zŲ!I‰‰'õá‡]���€ßšĻ-ÛČ×Īßš\!_ž����eE(���`(B ����CJ����ŠP���ĀP„����†"”����0Ą���€Ą%���� E(���`(B ����CJ����Ębt����Ē–NíZ+22ĘšĖ����†"”����0Ą���€Ą%���� E(���`(B ����CJ����ŠP���ĀP‡Ã.Ir8—��� *p8:ŸC$î”����0˜åâ…ĶÉiFÕ��� Š*JÂBĒU���€*ŠĮˇ����ŠP���ĀP„����†"”����0Ą���€Ą%���� E(���`(B ����CYŌĶĶ%IYYY—��� *ČĘĘŌų"I–IR^^žQ5���¨Bu>‡H<ž���Ā`„����†"”����0Ą���€Ą%���� equ‡Öŧ|MHR^~l6ģĢģ¯t‚}5哊ĘĘΑÃá0ē�1™Lō÷õŅŊwMPíš5Œ.�€ŠKC‰5/_{“ŋŋ¯üũäfū}߈Ųĩgŋ>ũüK9DĒ:‡ÃĄŒĖ,ũë?˙Ķ@ŅQF—�Ā ÃĨŠáhB’üũ}åįãũģ$’´ę›Õ�˜Lr8ėšōé—FW�Ā ÅĨÉÁj͗ˇ—+ģŦÔrrrŒ.@ec2)=#Ķč*��¸Ą¸4”Ø™M&Wv �7æ—�P>ŋ˙gŦ����Tj„����†"”����0Ą���€Ą%���� E(���`(B ����CJ����ĘbtS>üD6nēb›‰Æéæ›z\§Š*ŪŊwŒWķ&œËJNMĶžŊûõíÚ”–žq]ęøĮ+Ԛī7hÅęøë2^y\¯ÚŒ<Ŗ† TũØ:zíÍw¯ûؕŲo9/•ųw��\%-#K’”™cHôS×.œË~üšĸŖŖtKŋ>Îu‘‘F”VĄÎ&§hÚė’$EGE¨S‡ļęØŽĩĻ|ú•>ZŽūēun¯ZŅ5ôåŦųQ.���Pa,Õü$I9Y>†]Ŗ†ĸkÔp.{xē+00@MâRĪõ’——¯ũ‡Ž8—w˙ēOkÖũ¨īž]÷LĢWūņoååå—šŋ˜QP%���Pņ |Ģ,^}ũ y¸{č™'+ļū÷>Pzz†^zūY=đđ4°Ÿ“Ni׎ŸdÍËS“¸8ŨuĮí đ/ ^6›M‹–,ÕÆ[”œ’ĸāā õëĶĢŌ<–ŸŸ¯ésęŧU‡6­´ö‡’$?__ ÔO ëĮĘĮĮ[iiéŠ_ŋQņë”$=v˙$ÕĢ[[’ÔĄm+ũã­÷•žžyÅ}Îs3›5bđ-jßĻ…ÜŨŨõëžújöåää–ilIŠ­SKƒû÷VTd¸Ė&ŗ“´hŲ*įŨŗŲŦūŊz¨uËf ĒĻ´´t}ûũZˇaķ·+jsssĶ€>=ÕžMKųx{éÄÉ$-X˛B‡/uĖzukëĄ{īĐėKôÃÆ-eĒũĩ—ŸÕŠÕņ T›Íäå銇hڜ…ĘĖ,ēā¯ņˇSũzudÍĩjŨW>vIzķo/jéĘ5ZŊvŊsŨø[‡ĒFT¤Ūxįŋ.;÷˙Ķĩbuŧ7¨§õęčšŋŧ.Ģ5¯X-¯ŊüŦV~ģV‘áÕÕĸYœĖ&ŗ~ØŧUßŦY§ņŖ†*ļN-åååkÉĘoĩqËvI’ÅÍMƒú÷V›–Íäīį̌Ė,mÚļSKW~+ģŨ^æķR–ķpŠōūÎ čĶSˇôéYęļeĢÖhéĒ5Ĩn��ŽqC„’ŨēęãOŋPjZš‚ĒU“$Y­yúy÷/7f”$ÉÍbÖŌå+5nĖ(MēcĸN>Ĩ7Ū|GĶgĖŌä{'I’fÎ™Ģøøuš8aŧęÅÖÕî={4mÆ,šššŠGˇŽ†ßÅN>ĢĶg’U¯nmg(™0z¸ÂÃBõéWŗ”‘™ĨØ:ĩ4näĨĻĨi×î_5åŗ¯ôČ}wéĖŲdÍ^°D9ššš|įmWÜįŧŽíZk×î=z˙Ŗ/ŦņˇÕ؃õɗŗĘ4ļ‡ģģîŋk‚ļėØĨésĘ$“ēwé ˙ö†rs­6°ŸētlĢ™ķëБcjT?V#‡­ĐĻ ›ˇ]ö\\km’4|PĩiŲTŗæ/ŅŲäõčŌAŨs‡ūū¯w•œšVlŧęĄÁēwâ8­ūn~ظE’ĘTģÍfSī›ējɊÕúĶŌ)ĀßOO?2YˇôžIŗæ-Iē}ėH……†čŋOUzfĻēw͚(;įˇ?6éĒsoŗŲÔĨc[ũüË^-ûæ;åį”ËfŗŠW.š1o‘ĻĪ]¤.ÚjėČ!j[Gŗæ/Ņ‘cĶ4°_/>HģvīQnŽUŖG V‹&5sūb;‘ Ú155fÄ`y¸[4oņō ;/RŲ>ˇ‹— ��×Į JÚĩmŖ¯ĻĪԏ7é–~}%I;wí’Ã!uhßÖŲ.&&F];ÍO‰ŒˆPĪ›ēiŅ×Ku‡5O‡]ߎ‰×ĀũÕĨsGIRxx˜Ž=Ž%ËVTšP"IŠiiÎģ;’4gŅR9ėvįEô™ŗÉęŪšŊ5¨§]ģ•Õš'ģŨŽÂÂBįÅÜÕö9/#3Ks.•$;qRŅQęÕŖ‹ÜŨŨUPPpÕ~‚‚åååŠÍÛvęÔéŗEc/\Ēm;VaĄM^žžęÖšŊV}ģV›ļî$­KNQÍč(õéŲũŠĄäZkķôôPįm´āëÚžëgIŌôš‹äééŠĐАbĄÄĮĮ[Lē]?īŲ̝WŦ–¤rÕ~ęôũxîAZz†~Ųģ_1ŅE%øĢaŊēš5˙kí;xX’4{Á5Ē_īęŋ WāĒsīp8”Ÿ_ …KW^qŧ㠉ÚŊgŸ$i뎟4vä>z\GŽŨuÚē}—ú÷ęĄđęĄ:}6YÚ´Ôü¯WhÛÎĸs69UaÕÕŗ['-\ēJ~ž>r^~ëīÜĨÁ„@�ĀõsC„Oulß^ë7lt†’-[ˇĢuĢ–ōņž0ĻvLLąũjԈRAAŌŌŌ”–žĄÂB›šÆÅk͏a­ũ~ŦVĢŧŧŧ*ū`ĘĀl6;o‘¤ŧü|õíŲMõcëČĪ×Wf“I>>Ū:s6ų˛}”uŸK'Ô>z\nnnĒŦ“I§ŽÚĪé3É:uæŦî?Jßoؤ=ûčDBĸœ›/S§–,nnÚŗī@ąqö<ŦÎíÛČÃÃCųųĨĪšÖÚ"ÃÃånąččņg6›MOQŦ_‹››î8NŠéúęÜË$ŠFTD™kOH<UŦMNnŽ|ŧŊ%IaÕ%IGŸ(ÖæčņŠŽY걗…+Īũåg+>ŪYįŋ­yEwC¯ķöōRtd„Ėfŗ3°œwėD‚<<<T=4Dį‚ˇĢĪKy>ˇK]B$��\?7D(‘¤îŨēhMüZ;~BááaÚųķn=úāäbmŧ<=‹-{zxH’˛ss”kĩJ’^˙įŋd’ÉŲÆîpH’ŌĶ3*M( ĢĒŊûJ* (Ũ3Qnfŗæ,\ĒSgÎĘfˇkōã/ģyö9^Î;ąæáá^Ļ~‡Ūz˙#õžŠ›:ˇoĢ!ˇôQjZē/˙F›ˇí”—WŅgōčũ“¤sįZ’LĻĸĪ ĀßOg“SJ=Žk­ÍĮĮĢØ~—sS×NōôôPâŠĶÅayj/((ųČĶšfō<÷{™_PXl{y^dPWž{ë%įē4………%Ö”˛NĻ įîŌš)ÖsĮėåéQaįåZ~į$Â��F¸aBIÚĩT+ĻĻ6mŪĸZĩbäįã̏ÆÅßĐuéElnnҞ¯ˇ l’¤ûčč’oĒ Ē Ę˧níø;˙Ę[;&Z5"#ôÖģsāįëĢä”ÔRû(Ī>įƒ›sŲŗh9//ŋĖũdeįhÁ’Z°d…"ÂĒëæ]4qėH%:ãŧ(ũbúŧän‚$ĨĨĨ_ö\\kmYYE˛ŋHŊœ¤Ķ§5cŪb=~˙Ũ: æ.Z&I×TûÅō Š.˛Ŋ/ŠÃÛûĘ!øĸëi'ww÷bËuî¯Uîšą/=÷į˙pkĩ:ˇ]íŧ”å<\ĖČã��ŋÍ õîŨēvŅĻ-[ĩiķVuîÜAfŗŠØöŊûö[>rôˆ<<=Ŧ˜š5äną(#3SQ‘‘Î?_?ųûû_ņ"įzņööԘƒ•œšĻíģvK’Ü-Eš1ûĸī‘ŠSS!ÁA’Š˙ųŋ—gŸēĩ‹?ōS3Z………:›œRĻ~B‚ĒŠY܅/‚L:}F3æ.’ŨnWdD˜N&а°P~~ž:uæŦķ';'G™ŲŲ*´Ų.{>ŽĩļĶgÎ*ŋ Āųf˛ķįčąû'Š}›–Îu?īŲ§„“IšŊp‰zté¨F b%éšjŋØųGœĸŖ.<’d6›U?ļÎ÷ŗæå•¸@Š wūģ"ĪũĩJ8™$ģŨ^â3ŦSĢĻr­V9›RæķrĩķPÚØF7��ømn¨PŌŠc{ĨĨĨiÛöęÖšS‰íiiéšŋhąNŸ>Ŗ;wiõšxulßVîōööVî]ĩ`ŅbmÜ´Y§ĪœÕžŊûôÆŋßÖGŸ|vŨÅĶĶCõëÖVũēĩÕ¨AŦzuīĸįžxHÕôÉԙ˛ģpJHLRAaĄztí¨�?5jĢŅÃjĪž ¯*?__IEsĸkDĒFT„RŌŌË´$…Š_¯ RÃúąęÚąļ˙´[……e;(¨šî™8V7wīŦ°ę!ĒĸūŊo’ÃáĐáŖĮeÍËĶú[4°īÍjŨĸŠB‚ĒŠ~ŨÚzøŪ;5qĖČ+žŖk­Íš—§ ›ļŠßÍ=ÔŽu ÕŦĨą#+Ļf :RōË)7mŨĄ?ũĸ ŖGČĮĮûšjŋXjZē=Žž7wWŖąĒĄqˇu~ƗsėÄI5oÚX>>ŪrssSŸžŨäësaUEžûk•“›Ģ ›ˇŠīÍŨÕ,Ž‘‚ĒĒ}›–ęŪĨƒžû~ƒėv{™ĪËÕÎÃĨŒ<n��đÛÜ0oI’¯5l(ĢÕĒđđ°Ûģwīĸœėũåĩ¨ ŋ@-[4×mãÆ8ˇ3J>>>š5gžŌŌ3¨V-›éÖÃŽįaH’BC‚‹žy—dˇÛ•ž‘Š_öî׊ÕņJŊčņ’Ŧė}9sž† čŖmZ樉“úræ|úkŌmcôčũwéĩ7ßUüē5qÜH=ųā=úč‹eÚĮÍėĻņk¤?<zŋ,îíŪŗOŗį/)ר_Κ¯^Ũģh`ŋ^˛ÛėJ<uZ~>Ũ9á|ŪâåÎ×Ķøû)#3K?ũōĢ/ûæ˛įĮUĩ-X˛B‡CÃö“§—§N&&郏§ęlr鏾͘ˇHĪ?ų°ÆĒĻÎøMĩ—æŗiŗ5~Ô0Mžķ6åZķ´îĮÍÚ´u‡Z4‹ģė>ķ/Ķ„ŅÃõ×įŸRNŽU?lÜĸM[w¨qƒĸˇS8t¤BÎŊĢĖ^°DÖŧ<1Xū~žJMK×ōožĶĒ5ß;۔åŧ\í<”ÆČã��ågzå•W’tōäIM™2åš:Ûž{ŋjDTwIaĨÉČČÔ3ĪŊ¨ģöíÚÛöđãOŠoī^2h@…Šŋžū¯ë6€Ëūī/F—��@Ĩ5yōdEE]˜į}CÜ)ÉĘĘVŌé͚>sļjDEĒm›ÖF—���ĀEnˆP˛nũš3Ô¯¯ģīšXb‚;���€× Jú÷ëŖūũú\ąÍģoŊyĒ���āJ7ÔÛˇ����üūJ����ŠP���ĀP„����†"”����0”9Ī…™L&Ųö�7o-� \\z§ÄÛËCYY9Žė˛Rķņö–a�.æp(0 Āč*��¸Ą¸4”ÔĒĄŦœ\efįČfˇģ˛ëJŠ×Í=%žČĀÅĖ&Mž4Áč*��¸Ą¸ôËŊ<=Ô(6FG’t&9U6Ûī;˜T ŅSOÖ˙>ũJ™YYF—Ā`ū~žš<évEGF] ��7—Ŗģ—§‡֍quˇ•Úk/˙Áč���€oß���`¨ Ą$ûe����¨ĒœĄ$yß#ë����PE %Š:u:ÛØJ����TIįBIŽ2Č$���� p.”Ødˇ[���€Ēé\(q“ŲÃØB����TMįBI€‚ŧŒ-���@Õt.”ø+,ĖרJ����TIÎW5hld����Ǎ _žč[×Ā2����TUæĢ7���€Šcö4ē����UwJ����ŠP���ĀP„����†"”����0Ą���€Ą%���� equ‡Öŧ|MHR^~l6ģĢģ���`°VMęģ´?—†k^žö<&_øûÉÍ\ĩoÄ$$qų���iûîũ.īĶĨĄähB’üũ}åįãíĘn���üŽšôV†Õš/o/Wv ���āwÎĨĄÄîpČl2š˛K����ŋsU{Ō����ÃJ����ŠP���ĀP„����†"”����0Ą���€Ą%���� E(���`('P;�� �IDAT‹ŅHŌÛī~ í;vęÁÉ÷Ē}ģ6ÅļĨ§Ĩëą§ŸÕŗĪ<ŠÆ TáõõŋĪĻé§_~ŊėöÍâtĪíc+lüY žÖūƒGôÂSK’ž}åīęŲ­ŗú÷ęQac�� ęĒĄD’Ėf“f͙Ģ-šÉĶÃÃčr ¤ą#†”ēÍßßī:W���TœJJZļhŽ=ŋîÕōåĢ4tČ@ŖË1œ§‡§֏5ē ��� ÂUšPâíí­ĄƒjūÂÅęÖ­“‚ƒ‚/Û6=#C3gÍÕ/{ö*;'[ÁÁAęÕķ&õí}ŗŗÍ#O>ŖÁˇôWBbĸļnÛ.ģŨŽî]ģč–ū}õéį_j˙ūōôôŌđaƒÔ­KgI’ÍfĶĸ%Kĩqã%§¤(88HũúôŌÍ7UÎĮ–ļėØĨ/ĻĪÕģ_ŅQ‘’¤CGŽéßī¤ģoĢ–Íâ´péJ­Ž_¯w^˙sŠ}¤gdjÚėÚwđ°ŧŊ<ÕĩcģRÛŲm6Í]´L›ļíPAAĄ7ˆÕøQÃäëãSaĮ��€ĒĄŌLtˇÛíęŨëfiæėųWlûÉg_čĀĄCē˙žģõ×?Ŋ¨ũûiÆŦŲÚļ}‡ŗÅėĻå+ŋQË-ôŸ˙SŖF×ō•ßč_oŋĢAúéŨˇßT—.4õĢéĘÎΑ$͜3W˗¯Ō⁎čo¯ŧ¤~}ziڌYŠ˙~]…{i‡ Kũq8’¤ļ-›Ģiãš5˙k9ŲívÍ^°D­š7QËfq’¤ˆ°ęjÚøōsqž˜1W'“NéIôčũ“”•“Ŗ?ũRĸŨ†ÍÛäpØõĐ=5aôpí;pX3į-ޘƒ��@•Riî”Č!Y,n3úVŊũŸ÷ÔģgÕ¯_¯ÔĻãĮŒ–ÉlVXõPIRDD¸V¯Ÿw˙ĸÖ­Z:ÛÅԌVĢÍ$IÛˇĶįS§Š^Ũ:Ēë\ˇøëeJJJRTT¤ž]¯úĢKįŽ’¤đđ09z\K–­Pn]+ōčK8™tJO>˙—Rˇ=ķčdÅDא$1D¯ūķ?Ú¸eģō ”š–Žī™člÛĄm+uhÛĒÔ~ŌŌ3´īĀ!6H ęՕ$:Pŋî;Xĸm€ŋŋnZôX]Lt 8™¨Õņë•_P w÷k:V���Tm–´Œ,IRæšģFkÕĸ™š5mĸ/gĖԟ^xŽÔ6^žžúzų íųu¯23ŗäp8”­ˆ°°bí"""œ˙ööö–$EFF^´ÎK’”“›ĢŖĮN¨°ĐĻĻqqÅúhܰÖ~ŋNVĢU^^^.9Æ˛¨Ŧ‰cG–ē-"Ŧēķ߁ū>¨Ÿ.])›ŨŽ1ÃËßΎLcœ:}F’T+φsÉdRíšŅ:~2ąXÛØ:ĩŠ-׊#ģũ{MNQTDx™Æ���JSyî”\dė˜[õԟūĒīׯWËf͊m+,´éˇŪ‘Ãf×øqŖ.7“›Ū~īƒũXÜKžÅRrÃáPŽÕ*IzũŸ˙’I&į6ûšGĨŌĶ3Žk(ņp÷P혚ejÛļUsÍ]ŧLnf7ĩhÚ¸ĖcXķō%Iî—Üéđô,ųöŗķÎŲÆŖhŸüüü2���”ÆR- čõ˛9Y•gÂrČHõęŲCsį/Rㆠ‹m;tøNœHĐsxZ \xŧ+#3SÕCCķ˜>įî¤Üw÷$EGG•Øô›ûŽhKV|Ģj*´Ų´tÕ šĨO™öķ8,rs­ÅÖį\˛,• yį–=x}3���ŽQĨ™č~ŠaCËnˇi؊UÅÖJ’üü.„¨ęėŲd9äøÍãÅÔŦ!w‹E™™ŠŠŒtūøųúÉßßŋÄŨ„ĘâØ‰­YˇAcF Öčჴ:~ŊŽ8YĻ}ÃĪÍÉI8™ä\gŗŲ´˙Đám>ZlųčņYÜÜT=äōoI���ĘĸR>ž%Ižž>>tˆĻ͘Ul}ÍčhšģģkÕ7k4lČ@H8ŠŲķ¨i“8%&RzF†Ę=žˇˇˇztīĒ‹ËßĪWuęÔQrJŠĻ͘Ĩā jzâ҇]uhebÍËĶ/{÷—ēÍd2Šqƒz˛ŲlújÖĩmÕ\õcëH’Z4mŦ¯fÍ×ģ_nnnÚ´u‡víŪŖ{&Ž+ŅOpP5ÕŽŠŠ•kÖ*44Xū~žúnŨ˛¸š•h›œ’ĒåĢãÕļesMNŅē ›Õ˛y“JÖ���p㨴ĄD’zöčĻ5ßÅëD…ŋüøëî;'jÎüúáĮUģV-Ũ{×DĨ¤Ļéƒ)ë˙ūų–^ũËËŋiŧqcFÉĮĮGŗæĖSZz†ÕĒe3Ũ:b˜ĢŠĖ’SRõÁĮSKŨf2™ôÎëÖĒ5ß+-=CLžĶšmäúÛīhåˇkuKŸžJ<uZģv˙zŲqîŧm”ĻÍ^ ˙}ú•ŧŧŧÔĩS;ĩoŨB;~žđZ`ģÍŽ^7÷PrJĒŪxįŋ*(,T“Fõ5zØ —/���Ē.Ķ+¯ŧ␤“'Ojʔ)×ÔŲöŨûU#ĸúÕV IgÔĒI}ŖË����\fûîũ×|;yōdEE]˜Į]iį”����¨%���� E(���`(B ����CJ����ŠP���ĀP„����†"”����0”KC‰Éd’Ũápe—����~į\JŧŊ<”••ãĘ.���üΚ4”ÔĒĄŦœ\efįČfˇģ˛k����ŋSWvæåéĄFą1:š¤3ÉŠ˛Ų&Ûwī7ē��� Rsi(‘Š‚IÃē1Žî���Āīoß���`(B ����CJ����ŠP���ĀP„����†"”����0Ą���€Ą%���� E(���`(B ����CJ����ŠP���ĀP„����†"”����0Ą���€Ą%���� E(���`(B ����CJ����Ęâę­yų:š¤ŧüŲlvWw���ā:ķpˇČÛËSŅ‘aōpwy„pm(ąæåkīÁcō÷÷U€ŋŸÜĖ܈Р’ΨU“úF—��€ ’_P¨äÔtũzā¨ÕĢåō`âŌÔp4!Iūūžōķņ&����ŋîE†…¨zH5H<íōū]šŦÖ|ųx{š˛K����•DHP r­y.ī×ĨĄÄîpČl2š˛K����•„‡ģEų….ī—gŦ����ŠP���ĀP„����†"”����0Ą���€Ą%���� eIËȒ$efį\ ���€Ēˆ;%���� eņņō$yy¸VÄÛī~ í;v:—=<ÜĒĻMãÔ¯O/…k˙đãOŠoī^2h€Ëj¨ˆ>/uū8œ|¯ÚˇkSl[zZē{úY=ûĖ“jܰA…ÕP™üīŗiúé—_/ģŊEŗ8ŨsûØ ËŨbQpp5Å5Ŧ¯žŨ:+¨Z KÆššg_ųģzvëŦūŊz\—ņ���nŖ 8/,ŦēîēãvIRžÕĒãĮŋnž_ˇ^?ō°6¨įl;vô­ŠŽĒaTŠ×Äl6i֜šjŅĸ™<=<Œ.ĮpĄ!A;bHŠÛüũũ\>Öø[‡I’ōķ tâdĸ~Ø´U6oĶũwMPlZåęoíuôx‚n3ÂĨu��T5•&”xzzģCвEsõéŨSožũŊ÷Áõúß˙*o/oIR×͌*ķšĩlŅ\{~ŨĢåËWi萁F—c8OO5Ŧ{ŨÆĒ[ĮšÜ¤qŨÔĩ“Ū˙ø }ôÅtũéOČËĶŗĖũ?q˛"Ę��¨r*M()———îœ8A/ŧôg­˙aŖzß|“¤’ZíŨw@sį/Đņ'dˇ;S3Z#‡SŖ†õ%I<ü„ ė§Ä¤SÚĩë'YķōÔ$.NwŨqģ.ķ×øôŒ ͜5WŋėŲĢėœlŠWĪ›Ôˇ÷͒¤W_CîzæÉĮŠí÷Î{(==C/=˙lŠũz{{k蠁šŋpąēuë¤ā āRەĨIzäÉg4ø–ūJHLÔÖmÛeˇÛÕŊkŨŌŋ¯>ũüKíß@žž^>lēué,I˛ŲlZ´dŠ6nÜĸ䔊_Ÿ^ēųĻĘųHŅ–ģôÅôšúÃc÷+:*R’tčČ1ũûũt÷ícÕ˛Yœ.]ŠÕņëõÎë.WߞžwëPŊúĪ˙hã–ęŅĨƒ$)3+[ķŋ^Ž});'WAÕÔŊsGŨÔĩŖ$éí˙~ĸ‡ŽH’6mŨĄg@WÜį<ģÍĻš‹–iĶļ*((Tãą?j˜|}|Ę4ļ$<|T‹—Ŗ„Ä$ŲíEGEhp˙ŪĒWˇvŅvģ–ķļîüI)Šé Ē žŨ:Ģ[§öå>˙���­R‡IĒЈđpíŨģĪJ.fĩæé­wŪS‡ömuĮÄÛ$‡CĢŋũN˙~û?ú×˙¯¯Ü,f-]žRãÆŒŌ¤;&ęÔéSzãÍw4}Æ,MžwRŠã~ōŲJL:Ĩûīģ[Õ´o˙}6õK…†ĢuĢ–ęŅ­Ģ>ūô ĨĻĨ)¨Z5g-?īūEãÆŒēėņØívõîuŗž[ģN3gĪ×÷Ũ}ŲļWĢA’,f7-_ųnŸ0^wŪ~›ž[ûŊ>Ÿ:M{öîĶíãĮ(ļp‘Ļ~5]­[¯fÎ™Ģøøuš8aŧęÅÖÕî={4mÆ,šššŠGˇŽe˙p\Āáp¨ °°Ôm77™L&ĩmŲ\Ûvü¤YķŋÖŪ#‡ÃĄŲ –¨Uķ&jŲ,N’V]M˙ļš8aÕU=4Dv†’¯fÍ׊3gtįøQ đ÷×Á#G5}ÎBĒy“ÆēīÎņúĪ”ĪT=4DŖ† ”ˇ—ū÷Ų´+îsŪ†ÍÛÔĸic=tĪDMNÕô9 5sŪbMš0ĻLcįįįëŋŸ|Š6-›iėČ!’ÃĄø6ęũ§ęo/>-ooÍ˙z…ÖoÜĸą#ĢN­íŨPs-•ÅÍMÚˇ)õ<���ĨŌ‡I RZFzŠÛ’SS”kÍUįNíU#˛č¯čˇŖöíÚĘbqs‰q>öĄž7uĶĸ¯—ękžŧŧJ>˛3~Ėh™Ėf…U•$ED„kõwņúy÷/jŨĒĨÚĩmŖ¯ĻĪԏ7é–~}%I;wí’Ã!uhßöōã,7}ĢŪūĪ{ęŨŗ‡ęׯWjĶĢÕā<ļšŅjÕĸ™$Šcûvú|ę4ÕĢ[GõbcëŊLIIIŠŠŠÔˇkâ5p@ué\ô—÷đđ09z\K–­¸îĄädŌ)=ųü_JŨöĖŖ“]4whˈ!įîflW~ARĶŌõā=m;´mĨm[ũæ:‚Ģ*#3Ëš<rČ�™Í&…I’ÂLJhíĩgß5oŌXŪ^^2›Í˛XÜäįëSĻ}Î đ÷×­C‹Ũ‹‰ŽĄ'ĩ:~Ŋō äáî~Õ~RŌŌeÍËSģÖ-V]’4jč@ĩiŅL‹EVkžžß°I}{vSû6Eŋ'ÕCƒu,á¤VŽųžP��*"”Øívš™ŨJŨވđpMųđõŧЇšÆÅŠV­šjtÉŦjĮÄ[ŽQ#JJKKSDDx‰~Ŋ<=õõōÚķë^effÉáp(;;[aa’$Oulß^ë7lt†’-[ˇĢuĢ–ōņöšę1ĩjŅL͚6Ņ—3féO/<Wj›ĢÕp^DD„ķßŪŪEķn"Ī´ĸu^’¤œÜ\=vB……65‹+ÖGㆠ´öûu˛Z­ōōōējũŽR=4XĮŽ,uÛų nI đ×đAũ´péJŲėv>Xū~ž.ĢŖčwė²===´jÍZí;pXYŲŲEį>'WaĄ!—íŖŦû\:ĄžN­ŲíßëlrŠĸ"¯ÚOXhˆÂLJčķésÔ­S{5jĢč¨HįŖ[Ž‘ÍfSŖÅÃnũØÚÚ°iĢōōōåéÉK��@åqC„’¤¤ĶjרÔmfŗYĪ?û´–._Šøī×iÎŧ ԈáCÕĨĶ…gđ/Ā|ūÍWŲš%ŋ4˛°ĐĻ7ŪzG›]ãĮVdd¸ÜLnzûŊŠĩëŪ­‹ÖÄ¯Õąã'Ļ?īÖŖN.ķqsĢ^úĶ_õũúõjŲŦŲoĒA’,î%?F‹Ĩä:‡ÃĄ\ĢU’ôú?˙%“LÎmv‡C’”žžq]C‰‡ģ‡jĮÔ,SÛﭚkîâer3ģŠEĶÆWߥNŸMvN¸ˇŲlzīÃĪeˇÛuëĐ Ģ.ŗŲŦ˙}öÕe÷/Ī>įCâyžį^ĮŸŸ_Ļ~Ėfŗžxđ}ŗfÖoÜĸEËV)¨Z õëĨömZƚ—'Izgʧ}Â>ãŒĖ,U÷ŧü\&��€ë­Ō‡’}û÷+-=MM›Ä]ļM@€ŋƎŠąŖG*áäI­Xų>üø3EEFĒNíĸŋJŸŋ?/7ˇhŲˇ”ģ‡Ō‰ zîO{qFfĻLJ†:—ëÔŽĨZ15ĩiķÕĒ#?_Å5.ûÅrČHõęŲCsį/RㆠS ååsîNĘ}wORttT‰íÁįnj–ŦøVÕThŗiéĒ5rK—ô{đđQĨgdĒQƒĸPräØ L:ĨĮ¸ģØ]ŦŦlį#U—*Ī>ųųųŖķÎ-{xx”š?__ ÔOÃõSŌŠĶZŊöM9Oáaō>*ī7RQŨE;/¨Z@™Î ��ĀõRŠŋŅ=;;GŸ9CĄĄ!jÛļuŠmNŸ9Ģm}ņb¨(Mœp›Ėf“N^xeëŪ}ûŠíwäčyxz*8¸ä_Œ Š&^ûų],ÔŲŗÉrČQŦmˇŽ]´iËVmÚŧU;wŲlRy 2XvģMËVŦúÍ5”GLÍrˇX”‘™Š¨ČHįŸ¯Ÿüũũåînܗh^Éą ZŗnƒÆŒŦŅÃiuüzsÁ+ysrs5sŪbUSĢæM%I…į&Ūûúx;Û>z\ÉŠir\æÔ—gŸƒ‡[>z<A77U .S?É)ŠÚĩûÂAF„‡iėˆÁ2™LJL:Ĩ‘˛¸š)3+[áaĄÎ__oųųų”z ��ĀH•æę$//O{ö[aĄŽŸ8ĄUß|§ŧ<ĢžzâQš_æB*%%Eīž˙_9B-Z4“I&mظI&“YõęÖuļKKK×üE‹ÕĨcGLLÔę5ņęØž­<Jų&ûšŅŅrww×ĒoÖhؐ:‘pRŗį-PĶ&qJL:ĨôŒ ũĩšSĮöš5gŽ’“SôÚ_ūTîãöõõŅđĄC4mÆŦß\Cyx{{ĢG÷ŽZ°hąüũ|U§N%§¤hڌY ĒĻ'}¸Ü}^ k^ž~ŲģŋÔm&“IԓÍfĶWŗ¨mĢæÎīiŅ´ąžš5_xė~šššiĶÖÚĩ{î™8î˛cååįi˙ÁÒ¤B›M'Oéģu”—Ÿ¯‡îšCˇĸyK5ĸ"dąXôŨēuKŸž:™tJ‹–­RŖą:}æŦ2ŗ˛åīį+/HHԉ“‰ĒV- LûHEĄbųęxĩmŲ\g“S´nÃfĩlŪDîîîe;5-]}1]CöUĶÆ e’I›ˇī”ÉdRZ1ōōōT—ŽmĩdåˇōķõQ­šŅJIMĶÜEËT-0@÷Ošāʏ��āšUšPrúôŊūÆŋ$}ëyĩĀ 5kÖDƒöWhČå'7jØ@wßu‡–¯üFķ.–Ųėύ‘zäÁÉÅ&°wīŪE9Ų9úËk˙PA~ZļhŽÛƍ)ĩĪ€�Ũ}įD͙ŋ@?üøŖj×ĒĨ{”Ô4}0åcũß?ßŌĢyY’äëãŖF Ęjĩ*<<ŦÔūŽĻgnZķ]ŧN$\øËyj(¯qcFÉĮĮGŗæĖSZz†ÕĒe3Ũ:bØoęīZ$§¤ęƒ§–ēÍd2é×˙ŦUkžWZz†™|§sÛČ!ôˇ7ŪŅĘo×ę–>=•xętąģĨ9›œĒwĻ|ęė;0Ā_q¨¯ ĒčlįįëĢ Ŗ‡kҞUÚ´u‡bjÖĐícF(-=CŸ~9KīLųT/<õ°nęŌIŸĪ˜Ŗŋ÷‘î™8ŽLûØmvõēš‡’SRõÆ;˙UAaĄš4ǝŅÕkė Ŗ‡ëÛĩ?hɊoåf6+"<L÷Ũ1^aՋū¯Œ|‹ŧŊŧ´`ÉJĨgd*ĀßO͚4ԐūŽyä ��Ā•LĪ?˙ŧC’õÉ'Ÿ\SgÛwīWˆęWox]úe‹Ž”‘‘Šgž{Qwß9QíÛUÍW­&$QĢ&õ.���×ÁöŨû¯ųÚoōäɊŠē0ŋšŌÜ)šŅdee+éôiMŸ9[5ĸ"ÕļMés^����\Ąä7Zˇū͙ŋ@ ę××ŨwM,÷w����EĒD(y÷­7]Ūg˙~}ÔŋĪį���×ĒRŋ���ĀīĄ���€Ą%���� E(���`(B ����CYrNîĐē{t()UēÆ/O4™L˛;2›x=.���đ{“_P(w×ŋĀ×ŋb›2coVæļkîĖÛËCYY9 đ÷uAi����*“äÔty{yēŧ_sZžˇĸÖV5ŸkîŦVeåä*3;G6ģŨå���0Z~AĄ’Τčtršĸ#Ã\ŪŋĨĻ_–~ųækĨĮ„_sg^žjŖŖ I:“œ*›`RUlßŊßč���PA<Ü-ōöōTãzĩ*æņ­ŽÃ‡*hûíØģÃ%zyz¨aŨ—ô���ā÷Ī"Ÿ05éŌWÁuŽ���@d>|<M…FW��� Ę2Į¯XĻU?WZŽÕčZ����TA–AŨĸĩ~Gŧ֜Í0ē����U%´a7 mØM‰‰Ė)���pũ™.����@ÕF(���`(B ����CJ����ŠP���ĀP„����†"”����0Ą���€Ą%���� E(���`(‹Ģ;´æåëhB’ōō dŗŲ]Ũ=ʨqŊZ•æsps3ËĶÃ]ĩjDČËĶÃĐZ���Pų¸4”Xķōĩ÷ā1ųûû*ĀßOnfnÄáÔŲ”Jõ9ØėvegįjīÁcjC0��@1.ŊZ=š$_ųųx~!\•™dĒTŸƒ›ŲŦ�_ųúxëhB’Ņå��� ’qéĢ՚/o/Wv‰ß ĐfĢ”Ÿƒ¯¯ˇōō Œ.���•ŒKC‰ŨáŲdre—ø •ôsp3› Ÿß��€ĘĮøg{����Ti„����†"”����0Ą���€Ą%���� E(���`(B ����CYŒ.�•‹ÍfSA~žėv쇇$Éd29<<=åææfpĨ���øŊ¨Ąäíw?Đö;‹­3›Í QËÍ5lČ@ųxû\—ZĻ~5]ŋîŨ¯W˙ōr…ŽķđãOŠoī^2h@…ŽSV‡C™:•tRYY™’Ã!›Í&ģŊčËÍfsQ1™äë마¨ō÷Š~I#���n,•"”HRXXuŨuĮíÎå‚9zLK—­Ô‰ zæÉĮ¸�Ž@vģM§’NĘËĶCĩk5‘ģģģÜÜÜd6›ĪmˇËnˇĢ  @‰‰‰:u2A>õ|eąTš_!���Ü *ÍĨ§§§7lPl]ŗĻM O>ŸĒĒ~ũzU÷ûg+,Tž5Wõbë* āŠmŨÜÜ´{÷nŲ %���¸f•ūŠ26ļŽ$)9%UõĪ­ŗŲlZ´dŠ6nÜĸ䔊_Ÿ^ēųĻÎũŌ324sÖ\ũ˛g¯˛s˛¤^=oRßŪ7;Û¤ĻĨéĶĪŋԞ_÷ĘÛÛ[=oęvÕz 4wū"mÚŧEéĒ N;høĐÁÎy<ųŒ¸E)))Ú¸i‹ŦÖ<5lPOwMœ Āj%ú|õõ7äáîĄgž|ŦØúwŪû@éézéųgË{ÚĘíüüOOOįܑËņôôtî���\ĢJJN:-I vŽ›9gŽâã×iâ„ņĒû˙íŨw|eūū˙kNĪIƒ�)$@čE*Ũ† b—*ĸ+ē¸îĒëZļü>ģ?vWwmëęvÖĩĸ`AEE¤KoŌkč ž“ä”ä$đõ|<b8SîyĪĖņdŽ3sĪ´Öæ­[5íŊdĩZ5dĐ@IŌëož­#ézđĮÕ(:J;vîŌ›SßQĶ&1ęqywIŌ_{SéGõØ#?UtŖh͟ŋ@Ģ׎SDxÄ9ë™úît­Yˇ^îĢV))ÚŊg¯Ūš:M%ĨĨ7z¤$Éfąę‹š_éļ[o֋Ī=ŖœÜũūéį4ëŗ9š0~ÜYm4P¯ŊņļNdgĢqŖF’$§X›6oŅØ“mÖ=CVĢUVĢĩĘËäĒ;���P ę–Ā>Ÿ¯ė§¸¸XÛļīÔôf(11QmR[K’ŠŠŠ4˙ۅ>|¨ôŋBqqąēúĘ!ę߯Ÿæ|ņeY[ãFŌ=ĒíÛ*>>Nƒ P‹-´iķIR։Úēu›nŧa˜:uė Ä„7Faްs֗—Ÿ¯%˖ë–7ĒoŸŪŠmĻ~WôŅĩ×^Ĩ‹–¨Ôë-›6!!^ƒö—ÕjULãuëÚY{ĶöUÚnī^=år9ĩ|Åʲa6nT õíĶĢVÛ´ē #x‡­Saã|?���„Rƒ9SrāĀAMœôĶ Ã ÃP×.uī„ģĘ‚÷í?(¯×§.:U˜ļcûvZ´x‰<\.—\N§>›ûĨļnÛŽŧŧ|đ4xû�� �IDAT(>6V’täHē$ŠuĢ” ËkÕ*Yû÷<g~ŋ_Š­[UŪ:%Y%ÅÅĘČČPRbĸ$ŠE‹¤ ͏ŨnVÚŽĶáĐ}úhéw+tũ°ë$IĢ×ŦSËģ×Û]Į ÐÍfĢōŌ­SĶžŪ ���¨Jâãâ4éûĘ^ķímø~“&ŨŸÂÃËĖ‹<IŌs/ž$CåĪū“ũrrreŗŲõÂËSĀį׏ઔ'ĢaÕ+˙üwŲôž“íØí7Ëé:g§æq…UœÆå ž.ö— ŗÛíÕXërƒ С i˙ƒŠ‹‹Õ†M›õČC“.¨Ú¸ĐPrjZ��� ļl‡CŌ…D‡šŨaWĢ”ä˛×cFŪŠõ6ęũëž{Ɨ w‡/¯úņÄû””ÔüŦvbbkĪŪ=:xđ~ũÔjߎüŽ]šyyjִФōÎÚEEž ķŗF×Ée{Θ§čä<.÷š/ũĒJĢ”d%ˇlĄ•ĢV+9šĨ"ÜáęÔącÛģP‹Q၈ÕéWB&��@(4Øëo""ÂuįmˇjŅâ%ÚžcWŲđ–-eˇŲ”›—§æ e?ኌŒ”ŨnWiŠ÷dågXvíŪ­cĮŽ+ ā•øø8IŌūå—jyŊ>mÛžũœ5ĩLJ’ÅbŅÎ]ģ+ ßĩ{¯ÂÂ\e—†ÕÔ ´rõ­\ĩFũû÷•ÅRGũ‹E6›M~ŋŋĘ>%~ŋ_6›§ē�� $l(‘¤ÁƒĒUr˛Ū|û˛Näaaa2x f~:[+VŽŌŅĖcÚē}‡^øë+úßëoJ’Z$%Énˇëëyß*;;[›6oŅÔiīĢKįN:’žĄœÜ\5mŌDŠŠ­5įķšÚ´y‹öí? 7§ž#Ûy´#"Â5hā�}öÅ\­]ŋAĮŽ×ŌeË5˙Ûēnč5ĩ>HīwEeggkíēõÔŋ_­ÚēP‹U‡SYYY*..–×ë•Īį+ģU°Īį“×ë•ĮãQVV–œN—,B ���j¯Áô)ЌÅbhüø1úã3ĪiÎįsuëÍ#$IcG”ÛíÖ3>VvNŽĸŖĸuy÷Žēķö[%IQQ‘šxīÍødĻ–-_Ž”äd=đŖ Ę:‘­OyMĪŋø˛žųÃīôāõú[SõĘß˙%—;LWŦ~ũŽĐÚĩëĪYĶøqŖæręíŠĶ•›—Ģ˜˜Æ1⍸~X­×7ÜíV‡öíåņxWģŗ.ʐĮ4QNÎ åææ–…‘S—qúˇa˛ÚljÔ8F’xV ���jĮ˜<yr@’>Ŧ)SĻÔĒąu›w*1žYH û!ĘÍÍĶ“ŋū?Mŧw‚úôîYãvĨgÖx?X-†~˙ɨ(ĪÆÉ˙˛Úlōų5zxâĄôL]ŪšmÕ��ā’5iŌ$5o^Ū?ŧAŸ)ųĄČĪ/PúŅŖšūū‡Jlž ^={˜V‹ĪĘîjfHgvk1 y}ūzŽ ���—2BI°dé2ÍødĻÚĩm̉?šP¯Ü���ŗJ€áÆjø°Ąf—���˜ĸAß} ���ĀĨP���ĀT„����Ļ"”����0Ą���€ŠBJ ÐŋÔCh5Ôũāķûeĩ’ƒ��PQHÃ\åᆞIԀÍjmûĄ  HN‡Ũė2���ĐĀ„4”$'Æ+ŋ°Hy…ōųyęˇY 4¨ũāķû•WP¨üÂ"%'ƛ]���˜><Ņåt¨CjKí;”ŽĖã'äķ™@üCÕąMrƒŲVĢEN‡]R[Ęåt˜Z ���ž?ŅŨåt¨}떡n5Ā~���Āŀ^Į����LE(���`*B ����SJ����˜ŠP���ĀT„����Ļ"”����0Ą���€Š%����LE(���`*B ����SJ����˜ŠP���ĀT„����Ļ"”����0•-Ô zŠK´īPēŠKJåķųCŨ<�ā¤Žm’Ėį­Õj‘ĶaWrbŧ\N‡Šĩ��.>! %žâmßŊ_‘‘ኊŒÕ‰�¨ Į˛Ôį­ĪīWAA‘ļīŪ¯öŠ- &�€ ŌŋbûĨ+22\î0Ķ˙@ĀĨːҠ>o­‹ĸ"ÃîĶžCéf—�¸ČØ˛ŗŗ%IyyyĩnĖã)QtTd­Û�œŸ×į“;Ėevg Sæņf—�hāōōōt*‡H’­QŖF’¤ÂÂÂZ7îd1ŒZˇ�8ŋ@ũŧĩZ,Ļ÷o�4|‘‘‘:•C$îž���Ād„����Ļ"”����0Ą���€Š%����LE(���`*B ����SŲĖ.��P€JōsUœ›- ĻāoCÁįžX,†œ‘äˆˆ’Ņ�Ÿ…�¸ô4ˆPōĖŗ/Čfˇë—˙üŦq‡Ņo~û{ũôÁÔģWĪzĢéą'~ĨúéÎÛnЎeĸ~ũėįëēk¯ŅÍ#n0ģ” únÅJ-^ŧTûTIiŠĸŖŖÔžm[ >T-ËĻ­u5c{N}wēļmߊgūđģz[æÅĖīķéøŽÚ˙Å4e­[¨@iŠü~ŸNf†dąXeą;Ô¸û ĩŧ~œš´ī&‹Õjná�€K^ƒ% Áŧų ´7-MÜw¯ŲĨ ŽœšĮŒēSIÍĪ?SôŸW_׊•ĢtEŸŪē{đ 9]Ne¤gčÛE‹õĮ?=¯Į}XíÛĩ1ģT4 ĨšÚ÷É˙äÛēT­~Y’Å0d5‚ŠÄ0äøäS‘ޝŸ§4OĄ"ö´\Ņ1&W�¸ÔJNÚˇoŋŲ% ŽšöīgR%ĩˇpŅ­XšJÜw¯ôŋĸ|D7iȐzæŲ5kögzĒ’ŗøá*ÎËUÁÎuJˇ)Âvî˲šÜ2ImÚôŌÂ?<¤âü\B � Î]”ĄdûŽ]ú蓙:pđ üū€ZļHŌˇŨĒíÛJ’|>Ÿ>ķšVŦX­ãYYЉiŦaC¯ŅÕWŠ´Ŋg_ø‹ļmß)IZēlš~˙ģ˙O’dĩX4ëĶ9úfÁBĒcĮšøŖ ŠŽŠĒŅrĒS{NnŽŪ˙ā#mŲē]…ЉiŦkŽēR×]{uY˙âIŨtũp:rDkÖŽ“ßī×ātũđëôÆ[īhįÎ]r:]ēíÖ4 k-õz5ëĶŲZēl… ŠŠ”Ü"IŖîŧ]mÛ¤Į—–ęŖO>ÕĘUĢ•“›ĢFŅQęwE_ŨvËM˛žŧÜãá_<Š›n¸^YYYZąrĩ<žbĩo×F?š0^ŅĸĢ=Muę?_Ŋ•íã^zšÂåFĄZŸ3UgŸVõž8Ķ×ķ¨mÛԊä¤0W˜ūīWOĘårsßVĩ=}ü—ēúĘÁēåĻƒëŖGŸøĨúô&=PÖÎŖ?ĨaC¯Õ ïĢĐū…ÎŋnÃ9ė=ų‹G+´ķˇū[99šúío~ŠŲŲzã­w´uÛv………éĒ+sũN ÅgÅÃ?B7ŨxŊ6mŲĒ­[ˇŠE‹$šŨn=ņØ#–õŌ+˙PAaĄ~ûë§jÔî+}^î0w•ëT[ät8eŠ$”XÃŖsũŊ ŋr¤6üíW2Nö7� .]tĄÄã)ÖËû§úöéĨ{&Ü%úfūũõ•ŋëĨžUx¸[īĪøH .Ņ„ņãÔ&ĩĩ6oŨĒiī} ĢÕĒ!ƒžÕæ#?û‰žņeÅÅÅ鎹Ŗ<0Xąrĩētî¨ĮųŠŽĪŌkožĨ™ŗ>Ķ=w“¤ ^Nujũ͡u$=CūxĸEGiĮÎ]zsę;jÚ$F=.ī.I˛YŦšûÕ<Ũ=~œîŊû.-X´XoMĻ­ÛwčîqŖ•Úú'úxÖ§šúîtõčŪŊFÛD’Ū˙`†VŽZ­ģƍQlŗfš7˙[Ŋø×ŋ鏓ĢØfM5õŨéZŗnŊ&Ü5V­RR´{Ī^Ŋ5ušJJK5nôȲZŋ˜û•nģõfŊøÜ3ĘÉÍŅīŸ~Nŗ>›Ŗ ãĮU{šęÔžzĪĩOĒõ9SUû´:ī‹ĶęĐĄCeü•9_ ŠÎöėÔąŊvîÚ]6ũļ;Ķ8FÛwė*–žžĄœœ\uîÔņŦö/tū¨¨HŊöÆÛ:‘­ÆI ū˙˛iķ=ší˙ûڛĘH?ĒĮųŠĸEkūüZŊv"Â#*]ĮP}VX­6-X´DŨģuÕÍ#Ž×ž=izÆĮ*,*, …E…Ú˛uĢFŧŗZÛˇ˛vįy÷Y(Åb•ÕaWTßëäIÛĸŌĖCÁzÂŖÔäæI ëqÖŊúg[—Ëją* ”��ęŪEJŽŸČR‘§HũûõQbB‚$鎹ŖÕ§w/ŲlViūˇ uã ÃËžEŽ‹‹UÚžšķŗ•€ģÃܲXŦ˛ŲŦŠŠ,?ĀqģÃ4~ÜIRĢ”d­YˇN{ö-§ĒÚ%iÜčQ2,Å6k*IŠĶ7 jĶæ-eĄD’ZļHŌåŨēJ’ŽčĶ[oMĻ6­[ŠMjjذ؟}Ąôôt5ožpÁĩyŠ´pņRy‡úöî%IēwÂxëčŅŖ siɲå3ōõíĶ[’ÛL‡ŽŅWķækäˇÉn žŊâ5x`đŒMLãuëÚY{ĶöUXŪųĻŠÎļŽĒŪØf*ŨĮ§äåį‡t}NWÕ>­Îûât999’¤&MšTîķųTZZZa˜ÃáÅRņÎßÕŲž;uÔģĶ>ßÅbhÛöęסˇæ}ģ@Gf*6ļ™ļíØŠ¨Čĩl‘tV:||œŪūž–¯XŠë‡ĪēlظQ€ÔˇO/e8Ą­[ˇéîģÆĒSĮ’¤ņãÆhķ–mįÜîĄúŦ0 Éá°kԝˇK’š6iĸéĖІßĢ_ßž’¤uë6Ęī÷ĢO¯ž5nˇ>’ŦVĢĸûW܄ߨpį:eN{^ž‚<ÅNø­Âģô͆ṵ̄­‹ätģt´¨¨ėŽ\��ÔĨ‹.”$ÄÅ)>.NS^}]W]9D]:uRrr uhßN’´mûNyŊ>uéÔŠÂ|ÛˇĶĸÅKäņxĒüų”6mZWxĨŨ{‚ĄdßūƒŧœĒj—$—ĶŠĪæ~Š­Ûļ+//_@@Š­ĐV|||ŲŋÂíŸ<đ  .ģ°¨¨Fĩ:tXĨĨĨJII.fˇŲôŗŸL’$mŲēM~ŋ_Š­[U˜¯uJ˛JŠ‹•‘‘Ą¤“wjqÆAĢÛíVAaa…aį›Ļ:õWUoU8Ōõ9]Uû´:ī‹ĶYŒ`Čđû|†/\ŧDoŋ3Ŋ°_>ų u<ŖęlĪNÛĢČS¤ƒ‡Še‹$mßąSŖGŪŽ=iiÚžs§bc›iĮŽęØącĨˇŒŊĐų‡ŽčĶGKŋ[QJV¯Y§—w—;Ė­Ŋ{ƒĄ¯uĢ”˛e†ĄV­’ĩ˙ÁJˇS(?+Ú¤–4jÔHíÛĩÕÚĩĘBÉĒ5kÕŠcGEGGÕ¸Ũú`Ųl6åû ųRD×Ōm?“ĩq3šÚv×ūĨ_)°æKEēō˛ŲJš%0� ^4ˆPbą’ß_é8ßÉá֓ßR[,ũæ—Očķš_iáâ%šņņL5‰iŦÛoģEú]Ą"G’ô܋/Uø†īÔũøsrrĢJg\NqúŸæš,§ĒÚŊ^Ÿ^xųo øü7v”âd5ŦzåŸ˙>Ģ6›ũė]gŗ=,Ԩւ‚āAļĶé8ĢMIōœlĶVqžSí{ŠË†ŲíöJÛ8ŨųĻŠNũUÕ[•P¯Ī)ÕŲ§UŊ/ÎÔ¨Q´ ÃPæąc†÷ėŪ]I‰Í%ˇÉ?˙ķjĨ5Ug{ÆÅÅ*>.N;vîRtt”Ō3ŌÕļmĒvīŲĢ;wiЀūÚąs§nžiDĨˈisÁķ4@ß.\¤ũ*..V6mÖ#CåŠũc?ã}īržû˙åP~V¸N˙SúôîŠ÷>üX%%ĨōųŊÚŧekŲeĩiˇŽ’l6ģ 6-Ķú7^Pįq+ōŠá’¤Ėß+ķĢiŠ6ŧ2ėNū€ėö‘I��õĄA„’ČČH<x¸ŌqĮ2ƒ^ĸË;GEEjˍ;4fÔ:tø°žüjž^}íM5OHûäųOŧOIIÍĪj/&ĻqHjŽérÎW{ii‰<¤_?õD…[šææåŠYĶĻõZkdd¤$ÉS䊴ÍSSgŽ/** Žw‡î`Ģ:õŸ:SqŽzĢRWëŗgīžjíĶķŊ/ZvöG ĨV))Zšznŋõæ˛NøŅĸË:Ûg;~ΚĒû~8Õ/$*2RII‰r‡šÕŽmŊ3í}ĪĘŌąãYęÜŠÃ9—sĄķˇJIVrËZšjĩ’“[*ÂŽNƒũUœÎāEgėŸÂ“ûį\ęęŗĸWzgÚ{Úŧe‹Š‹K$I=O^^Y_ŸA5aÁP.ĪîUÚöŅĢę<ú'ō–kûģ/+ļč¸ė§žŒņdŗÛ9S�¨–Ē'Š{]:wVzF†6mŪRa¸ßĐgŸĪULãÆJIn)I:šyLk×o(›&ąysM—,C‡Vˉ˛ÛlĘÍËSķ„„˛ŸˆđEFFž˙[î čĪY“åTU{iŠW’QŪąy×îŨ:vėxŲ—kĸ&ĩ&ÄĮÉéphێeÃüū€ūüü‹ZēlšZ&%ÉbąTčĖŦw¯ÂÂ\g]nVÕŠŋĒz˜c3ÖÕúTgŸVõž¨Ė°ëŽQfæ1}ūÅW•Žßģ7íœ5U÷ũĐšSGíÚĩKÛwėPûļÁģUĨĻļŌŅĖŖZšjĩâãÕ$æÜˇŠ­ÉüƒĐĘÕk´rÕõīß7xUÁ~8’´˙@ųĨZ^¯OÛļo?įōëōŗ"**R;tĐú›´nũuģŦKŲe”ĩú ĒcVĢMvW˜dĩ+Ōí–}Į mžų––ũíwjî=!§+L‡S‡S˛Úew†ÉjáÁ‰�€ē× Î” ėßO‹/Õ?ūõ_ vRZļTn^ž.Zĸ´}ûôđC–uÖÍĘĘŌ?ūõēãvuëÖU† }ˇbĨ Ãĸ6­[+,,LCÔĖOg+2"\­ZĩŌņŦ,M{īÅ4n¤ĮųYĨ5„‡ģĩo˙~íÛ ZßdÖd9UÕîvģeˇÛõõŧouëÍ7ęāĄÃúđã™ęŌš“ޤg('7ˇėvÄĸ&ĩ†……iĐĀūúlΊiÜHÍ´`ŅbĨĨí×Ä{SŽAčŗ/æ*6.6Øo`ûNÍ˙v†Zöí}(T§ūĒę•Îŋëj}Z$%UšOĢz_TĻoī^Úšs—>š9Kģ÷ėQīŪ=ŽŲ9Zŋ~ƒÖoü^}z÷TjĢVgÍ[Ũ÷C‡íu";[ëÖ¯ąŖīÎë S‹¤$}3ēwģėŧë^“ųû]ŅGĖøHĮgéOø˙ˆ7mŌDŠŠ­5įķšŠ‹mĻČČH}ũÍ|ŲÎŗ_ęęŗâ”>Ŋ{jögŸĢ°¨H÷Ũ3Ⴗ¯Ŧîp…ĩîĸÂôŠ°ÛæŌą ÔÂi—ÕáT P ßPai‰\­:ËæŽ4­^�ĀGƒ%6›UO=ūs}öųįZŊfŊæÎũZV›UmRSõ맞({.†$uhßNtæ~5OŸĖš-‹ÅĒæ‰ zøĄIeßĻŽ=Rnˇ[ĖøXŲ9šŠŽŠÖåŨģęÎÛo=g C¯šJ˙}í =ķ܋zø'?ŽVŨēœęÔ>ņŪ šņÉL-[ž\)ÉÉzāG”u"[˙žōšžņe=ķ‡ßUwŗÖĒVIuį2 CīøąŠ=%%%ęąGĻØØf’¤ņãF+ĖåÔÛS§+7/W115bÄ qũ°ÕXÛúĢǎĒ}\ëY­}ZÕûĸ2ãĮQįNõ͡ 5íŊäņ+2"RmÚ´Ōã?X]ģtŽÕö wģ•ܲĨöĻíSģļåĪKiÛ&Uķæ/¨ôV§ĢÉüánˇ:´o/ĮŖ¸¸Šg§|`ĸ^kĒ^ųûŋär‡éę!ƒÕ¯ßZģv}Ĩ˯ĢΊSzõ衷ߙ.‡ÃŽn—uŠ0Ž6íÖ%[x¤_=RšË>×ņ=eøŊōûũĘõx”+I†üČj“-Ĩ›búß ë9nš �@(“'OHŌáÇ5eʔZ5ļnķN%Æ7 Ia�~xrsķôä¯˙Oī >Ŋ{š]Nƒv(=ŗÆŸˇžĸyķsđûĪē4Ԑ!Ãb‘-"ZÖ°đ×vyįĘü �€$Mš4I͛—÷ŊlgJ�ü°åį(ũčQM˙C%6OP¯ž=Ė.é’f ¯qā�� .J�˜nÉŌešņÉLĩkÛV4ĄŦƒ;��øa ”�0ŨđaC5|ØPŗË���&iˇ���đÃE(���`*B ����SJ����˜ŠP���ĀT! %†aČT=!� Vęį­Īī—ÕĘ÷]�€ Ōŋa.‡ōķ CŲ$� 6ĢĩA~ŪÉ鰛]�ā"ŌP’œ¯üÂ"åĘį÷‡˛i�Āi 4¨Ī[Ÿß¯ŧ‚Bå)91Ūėr��™><Ņåt¨CjKí;”ŽĖã'äķ™˙‡�.UÛ$7˜Ī[ĢÕ"§ÃŽŠ-år:L­�pņ ųŨ]N‡Úˇnęf�•āķ�p) 7"����SJ����˜ŠP���ĀT„����Ļ"”����0Ą���€Š%����LE(���`*B ����SJ����˜ŠP���ĀT„����Ļ"”����0Ą���€Š%����Le uƒ^¯WYYY*))‘×ë uķ����ę™Íf“ÃáPLLŒlļGˆĐ†¯×ĢÇ+""Bááá˛Z­Ąl���€ |>Ÿ<>ŦæÍ›‡<˜„ôō­ŦŦ,EDDČívH���€K„ÕjUxx¸Ünˇ˛˛˛BŪ~HC‰ĮãQXXX(›���Đ@„……Ф¤$äí†4”øũ~†Ę&���4VĢĩNús÷-����Ļ"”����0Ą���€Š%����LE(���`*B ����SJ����˜ŠP���ĀT„����Ļ"”����0Ą���€Š%����LE(���`*B ����SJ����˜ŠP���ĀT„����Ļ"”����0Ą���€Š%����LE(���`*B ����SJ����˜ę’%‹÷Ytû{ĩzŲĨČ?𔸗F}čĐʃ†)õ$ũÅĨg—ØĘ^īĪ14ø §ũŲĨŋ¯°ž5>›kWĪ)ΐļYgŽ���p.Ą=n@íŗhÄģėėĶĢ7—¨IX@r ũå;›Ž׊Å÷ĢSŗ@ŊÖôėĐRunæ/{ũÖzĢļfúėŽĩņĢq˜*Œ7ËVYĩæˆE¯Ū\Zã6Î\ˇ†&ë��€Đ¸dCɔÕ6ulĐkˇ”t^žĐU­Jtå›N-ŨoQ§fžz­iüe—wÂc¨et@[ú+o–uéĩ?væē54ĄXG���„Æ%JJ|ÁŸ3E:Ĩ5“Š+ ‹{ÁĨ§xĩũ˜ĄšģŦĘ/‘ŽMõë_7–¨Š;8×/=ģØĻˇXĩ?ĮPRT@÷ņęĮŊĘRⓞ^hĶ´M6eI—Åųõ§kŊē")x`žô—~Ö×Ģ_ ôęę7úî`đĀ8ėi—ūxĩW/g+_öÎt8WzhŽC ÷Yí”îīá=kšŖŌ¯įŲĩ ÍĒŦ"))* {yõĶ>Áõ¸îm‡īÖõÎFĢ–ß_Ŧ„ČĀyį9Seëöp¯&/°éÃÍV-0ĐØŽ>ũvˆWļ“ų ÅK.=5 TßėąjAšEûķč˛ģôD˙Rm;fŅŦmVųŌ=ŨŧúE¯~:ĮĄĨû- wôģ!^ŨŨÍWãuė_ŋgÍ���Pî’ũēøúļ>m;fhÜG­:dČžcNģEzé;›§ø•ö˜GË(Öē#†žüĘ^6Íožąë¯Ëmzj€WĢ\ŦGúzõä×vŊšžŧŋįæŲõæz›žģļT_Ũ]ŦԘ€nšæPZöŲ}X>Sĸ{ģûÔžI@~áŅO{Ÿ .¤=Iē˙S‡6gúdL‰æŪ]ŦãE†fnĢØŸãÁŲ-?hŅ[ˇ•håÅzĸŋWŋüÚŽOˇß Ž*Q„€FvōéĀ/<ę¨ržęŦÛŖ_Øõö›žęÕúŸëW{õīU6ũf^y.vXĨ××ŲÔ9Ö¯/ī.Q¸C˛[ĨW–ÛtcÛ`=ŧĒT¯Ŧ°éÖéN=ŅßĢC{4ū2ŸũÂŽE5_G���˜į’=Srßå>(2ôÜ›>ŲęT”Sę߯í|ÛÕ'ˇŊâôŨâüe—OĩkĐ=}úķb›ūQR*_@š˛ÚĒ'û{u×ÉiRc|ZwÄĸ—ÚtowŸōŠĨ7ÖYõ§kJuG§ā4˙ŧąTĨ†vgJiTņĀ7Ú%šl’Õĸ˛ŗ1§ģĐöåJßĻYôōđR]™<“ōŌ°R}ŗ§bpxáēRY-*›ŋmŸĻŦļé›ŊVŨÜÁ¯h—dŗHN[y]UÍsĻ3×íxĄôî÷Áušķäē´nėĶļLC˙XiĶĶ×xå°J†!šíŌ3×T hŨēĄ]p9ŖēøôđvõMōĢīÉ3FŖ:ûôė›v7Ô7)PŖu��€y.ŲP"I÷÷ę'ŊŊšŋĮĸoĶŦúfE?ûÜŽ?/ļiö¸u<­Ŗ{÷øŠ×›úåņJ‡ķ e*ņ/é:ŨāŋŪXŧÜkKhŌ¤ą��mIDATĻE¯Ô3Ą|‡UšvGIjŋĐöļ†^Í˧7Œāë åÁ$ÜĐ_–Ųĩ0Íĸc…Á3HYERjĖškŠÉ<§ûū¨E^ŋÔ'ąâöë‘āWAŠ´+Ë(ģé@ßJ.M;ŊŖ|ÔɉĩkR>,Ōœ7ˇØ¨uŊ���¨_—t(‘‚ßŧhī׈öÁƒØ…iáĐ¯įŲ5slų~ÄwÍ wg{¤\OđßÃĻ:tú…S§. ËČ7tÂSqžÚēĐöōNv“ ;ã ĐéëUę“nžæ”×/Ŋ8ŦTíbü˛YĨQžû–Á5™įL§ļ_ÔŗD:+Ö.IQŽŗ/ĨrVrGaW%īÜ@ˆę��@ũēdCIzžáūœnHŠ_ˇtđéË]tķ*ö}/;nVŪaūõ[J*í(ëΐWüļžļš†.¨ŊSáåÔŲ‚S˛‹Ę§YuØĸīš7ĄDNģ+Vf”]yģ5™įLŅŽSĩU~ęõŠņĄŠz��Pŋ.ɎîųRÛŋšôŌ˛ŗ3W í8n(.ĸâū’ũCĘÚ#…ÛƒŖkœ_NĢ”Y`¨}Ķ@ŲOLX@MŨ9mRģĻšíÁ6žâHCßvčŨūđĀ m¯]“āúlH/?—Sę“Ÿļ^ž“]5bÂĘ×}ÅA‹Ō˛ ÎČ=§^_Č<įŌ5Î/›EZ~°bŨ+īÖ&&tÍk˛Ž���0×%yĻ$.Bz¤¯WĪ.ą)ŖĀЍm}ŠqKGō¤w6ÚôŨ‹ĻŪ^ņĄyGō =ŊĐĻģ. ŪĩëŋklŲŲ'—-xŠĐÄ>=ŊČŽĻáÁ~ûs‚wįJŒ čã1%ŠrJ÷t÷éųĨ6%FÔĄ‰_¯­ŗií‹ĻÜtáčģĐöZFÔ7ҝ–Ú”P3w@˙\i“ŨR~äŨ5Ö/—Mú×*›~3¨T›3-úŨ|ģŽmí×Î,‹ŽHąáR#W@2,ڐn()˛zķœOL˜tO7Ÿ^XjSëÆ~uhŅ>‹ĻŦąéą+Ęo  5ZĮ¨€šĐá��Ā4—d(‘‚wpęÔ, 7×[õŲ‡˛Š¤hg°sõ§ãJtmëŠĒīíæUvąĄA¯;Uä•nhëĶKÃĘūŸZĒhg@ŋ™gSz~đLˈv~ũūĒōiūtMŠ,†ô›yvå•H]bũš9ĻD­×ė+ų mī­ÛJõ“Īėēķ}‡ĸ]Áį”ŒëęĶŦíÁ3ÍÂĨ˙ŪTĒßΡéŨ.õHđëŋ7—čpžĄģ?včúwœZ3ŠXõöjâ,‡ŽyËŠéw–TkžĒŧ4ŧTŽ€~>ץŖÁ3PŋčÕũĪžrmÔt‡Ļ6ˇ<��ü“'OHŌáÇ5eʔZ5–––ĻøøøVŸN¨!���€sKOOWJJJ­Ú˜4i’š7o^öú’ėS���āâA(���`ĒKļOɅ8ø¸Įė���€,Δ����0Ą���€Š%����LE(���`*B ����SJ����˜ŠP���ĀT„����Ļ"”����0Ą���€Š%����LE(���`*B ����SJ����˜ŠP���ĀT„����Ļ"”����0Ą���€Š%����LE(���`ǐ†‹Å"ŋßĘ&���4^¯W6›-äí†4”¸\.ååå…˛I���� DAAGČÛ iˉ‰‰Ņ’%K!ˇÛ-ĢÕĘæ���˜ĀįķŠ°°Pųųų8p`ČÛi(ąŲl8p ˛˛˛TRR"¯×Ęæ���˜ĀfŗŠiĶϊ‰‰Š“ˡBŪĸÍfSlll¨›���p‰âî[����LE(���`*B ����SJ����˜ŠP���ĀT„����Ļ"”����0Ą���€Š%����LE(���`*B ����SJ����˜ŠP���ĀT„����Ļ"”����0•íôGg›U���€¨ Ą$ļI#ŗę����đÅå[����LE(���`*B ����SJ����˜ŠP���ĀT„����Ļ"”����0Ą���€Š%����LE(���`*B ����SJ����˜ŠP���ĀT„����Ļ"”����0•Íė���pņ۝ļ_¯O}Oų…ōûũf—S¯,‹"ÂŨēīî1JMiY­y–¤IŖŪ“2 $o në ›!5 —># L }ûœ)��@­ėNÛ¯—˙õ?åæå˙ā‰$ųũ~åææéåũOģĶöW9ũ’4iđ˙¤#ųG ‘‚uÉ ÖŊ$-ôíJ���P+¯ŊũžŲ%˜Ī0¤@ ZÛbäûŌE’E* ŽĸFžúĻ %���¨•ŧü|ŗKh C……UN–‘WĩÔC:Vģ›P���„Hu._ģ(Ī’œĻ..9#”����0Ą���€Š%����LE(���`*B ����SJ����˜ŠP���ĀT6ŗ ���Ā[īŨÔŋOO%6—ÕjՉ9Z˙ũfÍ_´L…EEeĶ=;ųWúvņwúō›…&Vk.CŌŨŨĨ‰Ŋ¤ËâĨ0›t0Wšŗ]zn‘tø´3ÎŧKJi,u˙GÅ6Fu‘Ū-=2GúĮōz-˙œ%���0̈́1w¨×å—iŨÆMZüŅ*yŊ^Ĩ´LŌā}uųe]ôō^S^OŒ—$‹!M%ė"Mß(ũ{…”W '÷“Æ^&]ûē´1ãÜm J–ŪžSznqà $Ą���&šĸ×åęŨŖ›ĻôŠ–­X]6|ãæ­Ząfžxx’F\wĩĻôЉU6õ•Fu•îú@šļą|øœŌĢĢĨĨ?–>+u~EōUōÔõŽÍ¤YãĨ÷ž—~ũUũÕ]„���˜bČĀ+´īĀÁ ä”ŒŖĮôĘŋ_WFæąsÎoŗZ5bøĩęŲŊĢ"#•›—¯•k7č흿Ëī÷K’R[%ëĻá×ĒyBœ,†E‡Ž¤ëĶ/žÖîŊû$I‹EïĸŨģ*Ļq#egįhūâeZōŨĒēYéZx´Ÿôõފä”c…Ō“sƒĄã†öŌėmĮĮGH_Ü#-? Ũ˙IũÔ{!%���¨w.—S‰ ņújūĸsNsđđ‘ķļ1ęö›Ô­sGŊ˙Élí?xH)-[hôí7ÉaˇéãŲså°ÛõāÆkõúšūŅ,24x@_=t˙ũßĶ/¨¨ČŖ[oĻWôŌûĪ֞´ũęĐ6UwÜrƒ|^Ÿž[ĩ6ÔĢ]c R›&ŌĪ“•žŪü=$Ĩb(‰pHs&HųŌČé’×_§ĨÖĄ���õ.:2R†ačX։Íīv‡ŠoĪîúäŗ/ĩvÃ&IŌąã'ÛLW ę§YŸ­ÆŖår9ĩjíe žq™1ës­Ũ°I^¯O.§Sƒú÷Ņ×ķiåšõ’¤%ĮŗÔ"Šš†^5¸A…’Äčāī´ėsOSä•ŌķĨĨōa6‹ôáXŠGséņ/¤‚Ō筺Ϗ%0���ę]@ÁN>Ÿ¯Fķ'%ÄËbą(m˙ Ã÷<$‡ÃĄfM›čhæqedĶŊãFjčUƒ””˜ ŋ߯]{ŌTZZĒÄæņ˛Y­ÚēcW…6vîŪĢfMcäp8jļruāäÕh˛Wqôn1$˙iũI:ĮJMŨÁNņžNę›Tw5ÖgJ���PīrrķÛ´Iæwšœ’$§¸ÂpOqIpŧĶĄ@  —˙õ?]{å õīĶK7_?T'˛s4{î<­ZģĄŦGŧO ”Ɇ!IŠŠŒĐąãY5Ē/Ôäˇj|îiÂlRl¸´?§|XÚ iđĢR‰Oę'Í+õø§”YXˇõ^(B ���ę]qq‰:ĸžŊ.חß,”ˇ’3&Ũģv’×ëĶĻ­ÛĪWt2Œœ §¸œÎ“ã=’¤ü‚B͜ķĨfÎųRņąÍtõš0æĨgd–šˇ§ĪĐá#gßG7;;įŦafÉ,”6•Æ\&ũiĄTÉÍĩtm›āīov—Ë)^Ö%IcŪ“Ö?,Ŋ7FēîĘīĐe.ß��€)ž]ŧLEkøĐĢÎĢąwŪĸŽ;T:īĄÃéōûũjŌ˛ÂđVÉ-Täņ(ķX–š4n¤ŽĘįO?šŠ÷>úT~ŋ_ ņą:t8]^¯WáĘČ<VöSPX¨ŧ‚‚Jƒ’™ūēTę'ũ¤īŲ㚄I/ —ÖŠJNw$_÷žteĢāĨ\ gJ���`ŠÕë6Ēmj+ ģz°Z$&hÍúīU\Rĸ–‰Í5¸_Ĩ=ĒO>›[éŧ…EEúnÕZ]wõ`eËŌÁÃGÔ6ĩ•čĢo,‘ßīWãÆt˙„1šõųWÚ´uģāĶ぀öî; Oqą–ŽX­¯ģZ…Úˇ˙ b7Ō7ß ėœ\ũįwęy‹œßëk¤!­¤Ū|âĖ­R~q0¨<Ü/ø´÷[ŪŠü,Ę)ßė‘ū0_š|´â ôŅæúĒūü%���0Íôŗ´}įn ę×GwŪrƒŦ‹ŽeĐ—ķjáŌ*-=÷íĸ>œ9Gžâbžũ&EF„ëDvŽæÎ[ ¯ŋ],IÚĩ'Mī|đ‰Ž<@7ģF~Ÿ_G2ŽęÕˇĻ+ķØqIŌĮŗį–Ũ8*2Bšyųú~Ë6Íūb^ŊŦ˙…Hēg†4w‡ôãŪŌ”[$—-ØßäũīĨgV¯¯ČH’Ĩ7n—6gHÛÎũ(˜zcLž<9 I‡֔)SĖŽ���™‡ŸúŲ%4(ūįoü_=R‡O×nūI“&Šyķæe¯éS���ĀT„����Ļ"”����0Ą���€Š%����LE(���`*B ����SJ����˜ŠP���ĀT„��� D èzšz¨Ŗ.Ųę AJ���P+‘R `væ ^ådq‘’.Ö͐šVŊŠŒP��€Z™8aŒT3—<Ãn‹*|8úâŨ\†Ŧ?Ô%���¨•Ô”–úųC÷+2"ĸZ—/]j ÃPdD„~ūĐũJMiYåôS¤E÷Kņ‘us)T]°Y‚õ.ē?XČÛ}“���øĄIMiŠ?ũî)ŗË¸h L‘ŽüŌė*Ž‹$›���¸TJ����˜ŠP���ĀT„����Ļ"”����0Ą���€Š%����LE(���`*B ����SJ����˜ŠP���ĀTB‰ßī7Ģ����?�•eޞPbˇÛĩe˖z-���Ā˖-[äp8* + %nˇ[ŗgĪV ¨÷Â����\ú€f͚%ˇÛ]a¸õĘ+¯œ,I‡CÚ¸qŖš4iĸČČHŲl63j���p ņx<Úąc‡^}õUedd(::ēÂxcōäÉNäå加 €ū%����BÆjĩ*<<\g;ëTHdd¤"##ëĨ0����ā–Ā����LE(���`*B ����Sũ?Í´¯šœœ����IENDŽB`‚�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/mod-user-http.png���������������������������������������������������0000664�0000000�0000000�00000116650�14156463140�0022101�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR�� ��c���ôËu���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨw|uūĮņ÷nz'…T% ´ĐBīUzo"*°—ĶķÎrw?īôÎķŧSÎzvAJčŊ‰R¤IQDz „@z۔ŨũũX Í&âëųxäņ`fžķũ~f6>œwæ;ŗ†_|Ņ*����p�įÜÜ\eeeа°°Ēk���p›pvv–ˇˇˇ<<<Н÷ķķĶ”)ST¯^Ŋ��� ŧrssuâÄ -\¸PIIIōķķŗm3X,ĢÁ`¨Âō����܎,‹^yåĨ§§Ûnv ����*ƒŅhÔđáՓ“su]Ö���ā6×°aÃbĪ›;Wa-����nsîîî˛Z­2Šî}p���€Ã@����8 ���€Ã@����8 ���€Ã@����8 ���€Ã@����8ŒÁjĩZĢē����ˇ¯fąmäåí#‰; ����ˆ����Āa ����†����Āa ����†����Āa ����†����Āa ���ĀoˆÕjÕĸE‹´uëÖëļŲąc‡f͚%ŗŲl÷ņĢ]�iи™FŒo÷Š÷cOūNõŖct>1ŅÎU��ā×`Ų˛eÚģw¯ÖŦYSjŲšs§VŦXĄC‡)>>ŪîãÛ=€ŧúĒ~tŒZļŽ“Éd˛w÷����* Yŗfrqq‘¤!d׎]Ząb…$Éh4Ēe˖vߎ$ŋ @ -–Á`PfV–VŽZcĪî���TPũúõ5qâD9;;KēBvīŪ­å˗ËjĩĘh4j˘1Љ‰ąûøv Ģ×ŦUjjšîœP4=hNü<{v���ĀJ !˖-ŗ…QŖFUJøė@fĪ)š#vī=“ÕŽmíÚũŊŽ;VjÛ ßlԐáŖÔ¸YŦÚļīŦ?<˙’222*ÜözöíÛ¯Š?ĻÖqĶB]{ôŅĶĪ<§ŗgĘ´˙O?ŖúŅ1ĘČČĐķ/ũYí:tQ“æ­4bĖxíÛˇ_šššzųoW‡.ŨÕ´e;A?øŠD? įôėžW‡.ŨĶB­ã:ꁊkßžũˇ|Ü÷=8ÍVÛ/Ē~tŒ&Ũ=å†ĮvéŌ%ũé/U—îŊĶBmÚwԃ=Ē}û(Ķš��OTT”&Nœ('''IE§ 5J͚5Ģ´qíÕ҉'ĩ}ĮNĩnĢzuëhäđaÚškˇæÆĪ× |ŽX۝ģvëi+((P?ú°´}ĮN=0íÆ[n{=?üx@ãîœŦ5ütīŨ“U3(H§ĪœŅŒ/géÛÍ[´vÕrųû׸aWæÉ=ōøSj×ļ>ûäCũüķ!Ŋđ§ŋč‘ĮŸRãÆŅjØ >|˙]MHĐžISîŸĒ-›žļí{îüy 5Vš&“îœ8^Ņ (ņÂ͜5[ã&ŪĨŸĸvmÛØí¸Ë"9%E#FWFF†&N§čč†:>Q3ŋœ­q&éķO?Rû¸vv���ÕGffĻ,‹mŲjĩ*++ĢRĮ´[�™=ˇčîĮ˜Q#%IƒŨĄ˙ûÛĢZ¸x‰ž}æiš^ž—¤wŪû@‹EŧûļZļh.I7v´ūô—ŋjįŽŨÅú-OÛëŲˇŋ6ˆŌ |NÚĮŲև†„č/}E˖¯ĐäģîŧaÎNE§Ēn:zü҇%IMcšhÃÆMZšjĩZļhŽįžũ$ŠyŗĻÚšsˇ>ûb†ö˙đŖÚ´n%Iú÷›Ķ•œ’ĸ÷ŪžŽūũúØúî߯ Ļŋŋö/-œ7ÛnĮ]ožõļ/\ЂøŲjŅüjŌ>lˆúĸW˙ņO-YČT:��€ÛÍž}û´dÉÛ´+ƒÁ ŗŲŦUĢVI’:tč`×ņĸŖęŠAũēö™‚•ŸŸ¯‹ËÍÍM$y{yéŽūũ”ššĻĩkŋ˛ĩĩX,Úžc§"k×ļ]X_1~ÜčbËåi{#“&NĐ˛Å láŖ  @yyyjĐ J’t6ĄlͰ$ŠŋžÅ–ëÕ­#IęͧWąõõëו$%]ŧ(Š(MŽ[ˇ^Aęסwąļ ĸĸÔēUŦöîÛ§ÔÔ4ģ÷ÍX­V­\ŊZE+,4T/^˛ũ¸8ģ¨uĢVúáĮĘÎÎąÛ˜���¨zû÷ī×âŋeąXlĪ|Œ?Ū6kÕĒUúîģīė:æác'tôøIûÜYuųáķáC‡ČĮÛÛļ~ô¨Z´dŠæÎ›¯ÁƒîTtAž——§ČČZ%ú‰Ē_ŋØryÚŪĖĸÅEuüų233‹m+,,ûŦ„†[žō!…†„[īâ\tĮ§° P’tņâ%efeŠYŗĻ2 %ú­_¯žvíū^'NžTxx˜ŨŽûF’““•ššĻÔÔ4ĩīÜíēíΝ?§† Øm\���T~øÁ> ƒFŽi{æcܸqš;wnĨŨ 9|ė„}ȕ‡ĪÛˇĶÉS§lëCCC¨-[ˇéô™3ŠŦ][ššEß âæęVĸ7ˇâëĘĶöF^ã?zīƒÕŧYSŊôÂTģV-šēēęđ‘Ŗúã /•šIļ7”uũ9šEw<=<JŨîî^t<99šv;î›ÉĘΖ$5iŌXŋ˙ŨS×m|Ũm���øu9vė˜Ėfŗ ƒFŒĄæÍ¯Î¸iÔ¨‘ÆŽĢšsįĘbąčŌĨKvŋÂäøņÚąs—$Ũđb>~Ū=ķô“ļ íŧüŧmŽęSžļד——§O>ûBaĄĄš5ãsyyyÚļ]{'¤2yyzI’rrsKŨž“S´ŪÛÛĶ.Į-M5ģo//ÛŋģwëZæ~��đë5lØ0Y,Õ¯_ŋÔ/l܏ௌŖcĮŽiđāÁvŋÂäĘÃįãÆŒR׎]JlĪËËĶŗĪ=¯y ęÉĮUÍ  š¸¸čĖ™’Ī]ü|čPąåō´Ŋž‹/)//O͛7+>$iûŽeęÃjÖ ’ŸŸŸŽ;f{ÅŲ/šüēâúõęÉĶĶŗ\Įí|yēWAaaąõgnōŠá   ųû×ĐącĮ•‘‘!__ßbۓSRpķƒ��Ā¯Æ•iW7S=ŋäĘÃįŽ..zæwOiā€ū%~F Ē~}{ëâÅKÚđÍF9;;ĢuĢX:}ēÄ÷L˘9ĢØryÚ^OPP $•øžŸÔÂÅK$•~§Ą2 č×G/^Ōē¯Ö—¨eßžũęÔąƒ|}}Ë}ÜÁ5kJ’Ž;^lũÂEKnZĶĀ”ŸŸ¯˙}ôIąõÉ))ēcđpŨ?õĄ2���Pē˛jÍZĨĨĨkôČ7üKųŨwMŌę5ë4'~žúöé­Šܧí;vęūԘŅ#å_Ŗ†ļīÜŠÜ\Są‡Ø%•ĢmiÜŨŨÕĢgw}ŊaŖ^øĶ_Ô!.NGŽÕ3géÍ7ūŠĻ=ĸ 6jéōęĶ̧<==oÚį­zōņĮ´~ÃF=ũėtĪäIĒ_¯žÎ&$hÆĖYōōōԋŋøž”ō÷ČÃôåė9úÛĢ˙ĐŸ{VîZ÷Õ×Úŗwo‰ģ>×zâņG´á›z÷ũ˙))éĸÚĮĩĶ…¤$͚=Wiiiēgō]•r.���đÛTĄ; ļo>ŋûÆŠíãÚŠQtCmÜ´YįÕŖ{7M˙Ī ÔĮŸ~Ž>üXzīíˇäíí­‚üĢĪ.”§íõŧö÷W4tđ ­YŗN/ūé/Úũũ}øū;ęŅŊ›{ä!edfęo¯žV鯛 Öâs5p@Í[°PĪ=˙ĸf˜ĨŽÛkŅüšjܸ‘­myŽģUlKŊūWe2åiĘĶôā´G•š–Ļ>xW^^^ĘĪĪŋnMAZ4Žîœ0^[ļnĶ^xI|øąš4iŦyŗgĒKįN•zN���đÛb°Z­ÖĒ.���ĀíĢYlyyûHĒā����(����‡!€����p����‡!€����p����‡!€����p����‡!€����p����‡!€����p����‡qŽę����ÜŪ:ļk­°°pIÜ���ā@����C����ā0����C����ā0����C����ā0����Ã���¨TVĢUVĢEw@����8sRrZU×����ā7Â98°FU×����ā7‚)X����†����Āa ����†����Āa ����†����Āa ����†����ĀaœĢē�����ˇˇŦŦ,Ĩ§§K"€����¨d~~~ ”Ä,����D����ā0����C����ā0����Sáˇ`™ōōu*!Qyų2›-ö¨ŠZ đķŌŸĖPVvŽŦVkU— 3 ōņōÔ÷NRŨÚU]��ՂÁZĢhS^ž;-/y¸ģÉÉx{ßPŲđˆ–.].Ģ�ĘČj•Á`ÔīŸ|HĩÂCĢē��ĒÄÔŠS.Š‚S°N%$ĘĮĮKŪžˇ}ø¤u_­'|�(ƒAVĢE|:ŗĒ+� Z¨Pj0™ōåéán¯ZĒŊœœœĒ.‘Á ôŒĖĒŽ�€jĄBÄbĩĘh0ØĢ�¸mņĖ��En˙yS����Ē ����‡!€����p����‡!€����p����‡!€����p����‡qvô€|ø‰ļmßqÃ6“'MP¯ŨTQå{āî‰ ô¯Ąŧųn‰mõëFꩇī×ô÷?Ņ‘ã'5yÂ(ĩkÕō†ũÍ]¸LõëE–ŠŨæīvęģ'ĒEĶÆÅļeeįčÜųD­\ˇAĮNœ*˙AUĐ__xFÛwíŅō5ëËŧĪ?ūōmøv›ÖŦßX‰•9nœŌŒ>H ŖęéÕ7ŪvøØÕŲ­œ—Ēü�Āõ9<€ Ø_]:w´-øņįĒU+\wôīk[ęč˛Ēĩ_Ģí;÷ؖī?Jį/hũ7›më“.ęč‰SejwÅĨä͚ˇØļėëëŖ.Úé‰iSô¯˙ūO§Ī&TÖ!I’ēvŠSZšŋ¨RĮ��@õæđ�R+"Bĩ""lËŽn.ōķķU͘&Ž.ĨZJŧ¤Ä Iļ傂eddęĐŅãÅÚĨgd–ŠŨyyų:rüdąuû~<¨??÷¤ztí /f/°ßA”"2"ŧRû��ƒÃHYŧōÚëruqÕŗO?QlũôwŪSzz†^zū9=ôčS<¨ŋÎ'^Đūũ?Ȕ—§Ļ11ē÷îģäëã-I2›ÍZēbĨļoßĨ䔸ĢßŪˇÕôފ(,,ÔšķT30đēmŧŊŧ4bp5j%OOĨĨĨkã–íÚ¸å;[›7ūöĸVŽŨ õ›ļØÖM=Láaz}úûzbÚ5¨_W’Ôžm+ÛT4‹ÕĒ}z¨[Į8yx¸ëđŅš1wĄ˛˛ŗ¯[“ҍ‘CîP\›–rqqŅ·ęËy‹•““[æzœœ4°oOÅĩ‰•§‡ģΞKÔâktâԙRĮlPŋŽyānÍ[ŧB[ˇī’ŅhԀŪŨÕ:ļšük(--]_ģU›ˇí´íķęŸžĶšõåīī§6-›ËŨÍMGOœÔŦųK”™™%IōķõŅÄŅÃÕ°A=™rMÚüŨÎRĮ˙Ĩ›kIŠĒWGCôQxXˆŒŖÎ'jéĒuļŠveŠ˙īūƒÖŦߨ&Ņ Ũ žūøōk2™ōŠÕō꟞ĶÚ¯7),¤ĻZ6‘Ņ`Ô֝ģõՆ͚8f˜ĸęÕQ^^žVŦũZÛwŨ­svrŌā}Ô&ļš|ŧŊ”‘™ĨßīĶĘĩ_Ëbą”ųŧ”å<\Ģ,ĮũKûöÔ}{–ēmÕē ZšnCŠÛ��ĀUË�ŌŊk}üéJMK“’$“)O?øIƍ‘$99ĩrõZM7FSîžŦ IôúĶ5{NŧĻ>0E’4wūmܸY“'MTƒ¨ú:pđ f͉—“““ēwíâĐc2 ōpw/ąŪÍÍÕĄu\+0Ā_įq'åZ“ÆŽPHp>ũ2^™YŠĒWGF UjZšöøšLc|đŲ—zėÁ{uņR˛æ-^ĄœÜĸ°Đēe3ũ|ø˜ŪûdĻüũtįØÔ¯—æ.ZvŨž:´k­ũęŨžP`@€&ŽĻņ#‡č“™ņeŽwÄājÛLņ‹VčRrŠēwn¯Gîŋ[˙÷ÛJNM+6^Í �=0y‚ÖŗY[ˇī’$ Ô_;´Õ܅Ëtüäi5nĨQÃĘ\hÖļßK* ŋ}ztҊ5ëõį•˙–¯ˇžylĒîčĶCņ‹–K*š6¨÷?žĄôĖLuëÔ^ąÍ›*;'§Lįĩ4Ž..švī$íÚģ_ŗ,‘AuëÜ^ß?Y/ūíu忚Ę\įmõãO‡´ęĢo”Ÿ_Pb,ŗŲŦŪŨ;kÎÂĨšŊ`Š:ˇoĢņŖ†*:Ēžâ­ĐÉĶŗ4¨o1XûTnŽIcGQËĻM4wŅ2>› ē‘ĩ5näšē8ká˛Õ•v^¤˛}nŋt%`\B��TLĩ íÚļŅ—ŗįęģí;tG˙~’¤}û÷Ëj•ÚĮĩĩĩ‹ŒŒT—NEĪ“„…†ĒgŽZē|Ĩî6åÉjĩčë 5hā�uîÔA’Ŧ“§ÎhÅĒ5 áĄ!úįËĪ;tĖkW_zæãíĨî;($8Hķ—Ž¸î>ķ—Ž”Õbą]˜_ŧ”ŦnâÔ8ēA™ˆÉ”'‹ÅĸÂÂÂb‘&“Iķ—}&áœZ6‹QŨČZ7ė+#3Kķ—Ŧ”$>{NĩÂCÕģ{gš¸¸¨  āĻõēššĒSû6Zŧ|öė˙Q’4{ÁRššš)((°X�ņôôĐCSîŌŲ–wwsS×NqZ÷õ&íØŊW’´99Eĩk…ĢoĪnÅ.d/$]Ôw—˙ōŸ–žĄŸQd­ĸé‡~ž>jÔ žâ-×ác'$Iķ¯PㆠĘtN¯ĮßßOîînÚųũ>]Hē$IšŋdĨžß÷Ŗ ÍeŽßjĩ*?ŋ@KVŽŊáxgÎëĀÁÒ¤Ũ{ĐøQCuâԝ<]t7i÷žũĐģģBj)éR˛Úˇ‰Õĸåkôũžĸs)9UĄÁ5ÕŗkG-YšNŪ^ž•r^ĘķšũŌĩ!„đ�@ÅUË�âæęĒqqÚ˛mģ-€ėÚŊG­[ÅĘĶÃĶÖŽnddąũ""ÂUPP ´´4ĨĨg¨°ĐŦf11ÅÚ4i­Mßn–Éd’{)w$*Ëĩ_ĸŅÃUúøáĄzë)ļ.'7W_Æ/Ō·]wŋŧü|õëŲU ŖęÉÛËKFƒAžžēx)šÂ5?Y|ĘSfVļęFēŨpŸkߨuâÔ999Šf`€Î%^¸iŊa!!rqvÖŠ3Wē7›ÍúxƜbũ:;9éÉ”šžĄ/ņšE„‡ĘŲÉI-ÖūČąę×FŽŽŽĘĪĪ—$%œŋPŦMNnŽ<=<$IĄÁ5%I§Îœ-ÖæÔ™ŗĒvÃsp#I“uáâ%Ũ3qŒžŨļCÕŲ„ķ:zųųŸČzuĘ\˙õϤī’íßĻŧĸ)ZW‚Ī/×y¸ģĢVX¨ŒFŖ-œ\qúl‚\]]U3(P~—§OÚûŧ”įsģÖ/á�€ŠĢ–D’ēuíŦ 7éô™ŗ Öžčņ‡§kãîVübÕÍĩh:SvnŽrM&IŌk˙úˇ 2ØÚXŦVIRzz†CHiK’ųōŧ÷ʖt1YŸĪžg[ÎĪ/PŌĨdÛŧûŌF=r˙d9šŋdĨ.\ŧ$ŗÅĸŠ÷L´KM×^đYe•ÁpÆ—]ų\¯íÃÕÕĨLõzzē—:öĩzté(77Wŋ$ŖŅh;OîîEŋsO›"]ū]’$ÃåÂ}}ŧu)9ERŅ‹ŽuåøÜ.˙îæ۞—wãēnÆjĩęÍw?RŸ]Õ)Ž­†ŪŅWŠiéZļú+íü~_šę7]sŽKSXXXb]A)ëd¸zîŽ}–Ätų˜ŨŨ\+íŧ”į¸KCđ��Ā~Ēm�ŠWˇŽęDÖ֎ģT§N¤ŧ=Ŋͤø›˛ŽŊÍÍ-ZöōđTaY’ôā}STĢVÉ70øWRåÕSAANŸ=WŽ}ęFÖRDX¨Ū|īãbwŧŊŧ”œ’j[ūÅõœ‹‹Ë-×z#WBĻmųō34yyųeĒ7+Ģh ؕ ŌëILJԜ…Ëôä´û4l`_-XēJŌՋį/fĪךkîpHRZZz™Ž#ŋ č‚Úãš:<<nŠËrŽŗ˛s´xÅ-^ąFĄÁ5ÕĢ{gM?J‰.Ú­ū[‘{yėkĪũ•?$äšLļm7;/åũĢĘã��ÅUëoBīÚĨŗvėÚ­;wĢS§ö2‹˙yüĐáÃŖOž:)W77(˛v„\œ•‘™Šđ°0ۏˇ—ˇ|||*íųvââ\”Oŗŗ¯>ˇQ7˛ļüĨ_ÜU2å啸@  )ҟáfˇ7Ę ~ŨâĶî"k×RaaĄ.%§”ŠŪ¤‹—”_P`{+×•ēž˜6Eqmbmë~<xX į5oÉ uīÜAŖŖ$I įUXX(oo/]¸xÉö““ŖĖėlšÍe:Ž+Ķ”j…_Vd4Õ0ĒŪ ÷ģŲšô¯Ąæ1Wŋt21éĸæ,X*‹Åĸ°Đ`ģÕ+Î%Ębą”ø ëÕŠ­\“I/Ĩ”ųŧ”õwî—cWÕq�€âĒu�éØ!Niiiú~Ī^uíÔąÄö´´t-ZēLIIĩwß~­ß°QâÚĘÕÕEęŪ­‹/]Ļí;v*éâ%<tX¯˙į-}ôÉgŽ?˜_Ą„ķ‰*(,T÷.äëã­ÆŅQ;b>ǐšAōöō’Tô0x‹fMäéé!'''õíŲU^žžÅúĘÉÍU­ˆ0E„‡ĘĶĶã–k đW˙ŪŨā¯F ŖÔĨC;íųဠËT¯)/OÛv|¯ūŊēĢ]ë–ĒŽņ#‡(˛v„ŽŸ,ųđ;vīÕŪ~Ō¤ą#åéé!S^žļlßĨAũzŠuËf ô¯Ą†õëęŅîŅäqŖĘ|Šié:qęŒúõęĻÆŅQŠՄŅÃdžÉ…đÍÎĩŋ Ũ?yŧzuë¤āšĒ¨}zČjĩęÄŠ3vĢ˙VääæjÛÎīÕ¯W75i,˙~ŠkĢnÛë›oˇÉbą”ųŧ”åwî—Ēō¸�@qÕv –$yyzĒqŖF2™L .ąŊ[ˇÎĘÉÎŅ˯ūCųŠmŲBwNgÛ>aÜyzz*~ūBĨĨgČĪ×O­b›kôČáŽ<Œ_­Ŧė͜ģHCöUû6ą:}öœfÎ]$??MšsœŸv¯^}ãm-\ļJ“ÆŽĐ_Ÿ˙rrMÚē}—vėŪĢ&ŅWß\´qķwš<a”ž~ø~}ôŜŒz}NF'­Ų¸IūúũãĶäėâŦkŪĸåĒwņŠ5˛Z­1¨ŋÜÜŨtî|ĸŪûx†.%§–:KõüĶjâ¨aúhÆ-\ļÚö:[_oedf釟~Ö˛U_•ëx>›5OĮ ×Ô{îTŽ)O›ŋÛŠģ÷Ēeķ˜ëîsŗs}ôøI͌_¤ŪŨ:kP˙Ū˛˜-:!I~>Ûö žŊęŋ흐)/OãF‘ˇ—RĶŌĩúĢo´nÃˇļ6e9/eųģVU7��¸Ę`ĩ–6›ēlö8ĸˆĐšöŦ§˜ŒŒL=ûĮuß=“׎Mąm>ų;õëĶ[CŦ´ņ¯õ××ūí°ą�Ü~ūûĪ—Ģē��ĒÄÔŠS^ô\vĩŧ’••­Ä¤$͞;OáajÛĻuU—���ĀĒe�ŲŧeĢæ/ZŦč† ußŊ“K<|���ā׊Zũûj@˙ž7lķö›o8¨����öR­ß‚���āöB����ā0����C����ā0����SĄ�b0dšõī1€ß o�@Rˆ‡ģ̞˛rėUKĩįéá!¸�”—Õ*?_ßĒŽ�€jĄB¤ND¨˛rr•™#ŗÅb¯šĒ­ŪŊzJ|)"€ō24uĘ¤ĒŽ�€jÁ`ĩVėOúĻŧ|JHT^~ĖæÛ?„øûzęŸ~ŠĖŦŦĒ.€ˇ—ĻNšKuj…Wu)��T™ŠS§*<ŧč˙… ����p#ŋ ŧ ���€Ã@����8 ���€Ã@����8 ���€Ã@����8 ���€Ã@����8 ���€Ã@����8 ���€Ã@����8 ���€Ã@����8 ���€Ã@����8 ���€Ã@����8 ���€Ã8W´S^žN%$*/ŋ@fŗÅ5���¨FZ5mhˇž*@Lyų:tė´||ŧäëã-'ãoû†JBâEģ~8���@UÛsāˆ]ûĢP�9•(/y{zØĢ����ˇą Ũ˛0™ōåéán¯Z����Üæ*@,V̌ƒŊj���p›ûm?´���ĀĄ ����†����Āa ����†����Āa ����†����Āa ����Æš*}ëí÷´gī>=<õÅĩkSl[zZēžxæ9=÷ėĶjŌ(ē*Ęs¸˙}6K?üôķuˇˇlŖûī_iãĮ/^Ž#ĮNę…ß=*Izî/WĪŽ4 w÷J���ŋMU@$Éh4(~ūĩlŲ\nŽŽUUFĩč¯ņ#‡–ēÍĮĮÛÁœ7F�� �IDATÕ����•ŖĘHlË:øķ!­^ŊNÆĒĒ2Ē 7W75jUÕe����•ĒĘˆ‡‡‡† ¤EK–Šk׎ đ¸nÛôŒ ͍_ ŸRvNļüÕģgõëĶËֿয়Ր;(áüyíū~,‹ēuéŦ;ôͧŸĪԑ#GåææŽÃĢkįN’$ŗŲŦĨ+Vjûö]JNIQ@€ŋú÷í­^=ĒįÔŖ]{÷ë‹Ų ôû'ĻŠVx˜$éøÉĶúĪģéžģÆ+ļyŒ–Ŧ\Ģõˇhúk˙Wj陚5oą;!w7uéĐŽÔvŗY –ŽŌŽī÷Ē  PMĸŖ4qĖpyyzVÚņ��āöWeĄ[,õéŨKūūūš;oŅ Û~ōŲ:zü¸Ļ=xŸūúį5h@͉Ÿ§ī÷ėĩĩq6:iõÚ¯Û˛ĨūûŸiˍZŊö+ũû­ˇ5x`ŊũÖęÜšŊf|9[ŲŲ9’¤šķhõęu2čũí//Šßۚ5'^ŋŨ\ŠĮ^ĢÕĒ‚ÂÂRŦVĢ$Šml 5k­øEËeĩZeąX4oņ ĩjŅTąÍc$IĄÁ5ÕŦÉõŸųbΝKŧ ‡ĻLŌãĶĻ(+'G{øŠDģm;ŋ—ÕjŅ#÷OÖ¤ą#tøč Í]¸Ŧr���ŋUvDVÉŲŲIãÆŽÖ[˙}G}zvWÆ Jm:qÜXŒF× ’$…††hũ7õたÔēUŦ­]díZjÕ˛š$ŠC\;}>c–Ô¯§QQļu˖¯RbbĸÂÃÃôõ†4p€:wę I ÖÉSg´bÕuīÚĨ2ž„s‰ôôķ/—ēíŲĮ§*˛V„$iÜČĄzå_˙Õö]{”_P Ô´t=|˙d[Ûöm[Š}ÛVĨö“–žĄÃGkėđÁŠnP_’4fØ ũ|øX‰ļž>>=Ŧhj\d­=w^ë7nQ~A\]\*tŦ���øíĒē�rYĢ–ÍÕŧYS͜¯?ŋđĮRÛ¸ģšiųę5:øķ!effÉjĩ*;;[ĄÁÁÅڅ††Úūíáá!I ûÅ:wIRNnŽN>ĢÂBŗšÅÄëŖIŖhmúvŗL&“ÜŨŨírŒeQ3(@“Į*u[hpMÛŋũ|}4bp-YšVf‹EãF ‘ˇW™Æ¸tQ’T'2ÂļÎ`0¨níZ:sî|ąļQõę[ŽW'RËˇē”œĸđА2���\ĢĘˆ$7Z/ũų¯úvËÅ6o^l[aĄY¯ŋ9]VŗE'ŒUXXˆœ Nzë÷JôãėRōpœKŽŗZ­Ę5™$I¯ũëß2Č`Ûfš<Ũ)==ÃĄÄÕÅUu#k—ŠmÛV-´`Ų*9Ô˛Y“2aĘ˗$š\sÃÍ­ä[ČŽ„5[×ĸ}ōķķË<���p­j@"ÂÂÔģgw-X´TM5*ļíø‰ã:{6Aüũ3j}uŠVFfĻjŨō˜ž—ī<xßÕĒ^b{@€˙-÷]ŲVŦųZ5|}Uh6kåē zGß2íįz9D俚Š­ĪšfY*4ō./ģōĘd���T@ĩų&ôáC‡Čb1k՚uÅÖJ’ŧŊ¯ž}éčącēt)YVYoyŧČÚrqvVFfĻÂÃÂl?Ū^Ūōņņ)q— ē8}6A6o͏‘C4vÄ`­ß¸E§Īž+Ķž!—ŸĄI8—h[g6›uäø‰m8Ulųԙ9;9ŠfāõßV���ÜLĩ¸"I^^ž1l¨f͉/ļžv­ZrqqŅē¯6høĐA:›pNķ.VŗĻ1:ŸxAéōķõ-÷xęŪ­‹/]&o/ÕĢWOÉ))š5'^ū5ôÔãÚëĐĘĔ—§Ÿ)u›Á`P“č2›Íú2~ąÚļjĄ†Qõ$I-›5Ņ—ņ‹ôû'ĻÉÉÉI;vīÕūu˙ä %ú đ¯Ąē‘ĩĩvÃ&ČĮÛKßlūNÎNN%Ú&§¤jõújÛB—’S´yÛNÅļhZmƒ���~ĒM�‘¤žŨģjÃ7u6áę_ô}}}tß=“5Ņbmũî;Õ­SGÜ;Y)ŠizīƒõĪŊŠW^ūĶ-7aÜyzz*~ūBĨĨgČĪ×O­b›kôČáö:¤2KNIÕ{Ī(u›Á`Đô×ūOë6|Ģ´ô =6õÛļQCęo¯O×Ú¯7鎾=uūB’öøųēãÜsį͚ˇX˙ûôKšģģĢKĮvŠkŨR{ŧú*^‹ŲĸŪŊē+9%U¯O_……jÚ¸ĄÆlˇã��Āo“ÁzåK&nÁžGZķæ #/ĒU͆U]���`7{Šđ5îÔŠS^ôÜuĩy���Āí����Āa ����†����Āa ����†����Āa ����†����Āa*@ ƒ,ˇū=†����~c*@<Ü]•••c¯Z����Üæ*@ęD„*+'W™Ų92[,öĒ ���ĀmĘš";ģģšĒqT¤N%$ębrĒĖfBȞGĒē��� ÚĒP�‘ŠBHŖú‘ö¨���Āmގ`���p����‡!€����p����‡!€����p����‡!€����p����‡!€����p����‡!€����p����‡!€����p����‡!€����p����‡!€����p����‡!€����pįŠv`ĘË׊„DååČl飯&����UČÕÅYînĒ,W— G†b*ԛ)/_‡Ž–—|}ŧådä†ĘoABâEĩjÚ°ĒË���@%É/(Trjē~>zJÔąkŠPb8•(/y{z>���€Û„Ģ‹ŗÂ‚U3°†ÎžO˛kßJ &Sž<=ÜíU ���€j$ĐßOšĻ<ģöYĄ�bąZe4ėU ���€jÄÕÅYų…ví“yS����†����Āa ����†����Āa ����†����Āa ����†����ĀaœĢbСŪ~O{öîŗ-ģēē((0H͚ŨßŪ (ÖūŅ'§~}zkčāvĢĄ2úŧ֕ã|xęŠk×ĻØļô´t=ņĖszîŲ§Õ¤QtĨÕPüīŗYú᧟¯ģŊeķŨ×øJËÅŲY5͍Ązví$˙~vįfžûËßÕŗk' čŨŨ!ã��TwU@$)8¸ĻîŊû.IRžÉ¤3g´qķf}ģy‹ž|ėQ5Šn`k;~ėhÕ ¨ĒR+Äh4(~ūĩlŲ\nŽŽU]N• ô×ø‘CKŨæããm÷ą&Ž.IĘĪ/ĐŲsįĩuĮnmÛųŊĻŨ;IQõę”ĢŋM[ˇëԙŨ5n¤]ë��ø-О�âææVė/˙ą-[¨oŸžzã­˙ę÷Ū×k˙Ģ<Ü=$I]:uŦĒ2+,ļe üųV¯^§aCUu9UÎÍÕMF9lŦ†QõlËM›DĢG—Žz÷ã/ôŅŗõį?<%w7ˇ2÷wæėšĘ(��ā7ĨĘHiÜŨŨuĪäIzáĨ˙Ķ–­ÛÕ§WI%§K:|T -֙ŗgeąXYģ–FŽÆJ’zô) Ô_į/h˙ūdĘËS͘Ũ{÷]ōŊÎ_ŲĶ3247~~:xHŲ9Ų đWīž=Ô¯O/IŌ+¯Ŋ.WW=ûôÅö›ūÎ{JOĪĐKĪ?Wjŋ6x-YĻŽ];*Ā? ÔveŠA’{úY šc€Ο×îī÷Čbą¨[—Îēc@?}úųL9rTnnî1|°ēvî$I2›ÍZēbĨļoßĨ䔸ĢßŪęÕŖzN Úĩwŋž˜Ŋ@ŋbšj…‡I’ŽŸ<­˙ŧû‘îģkŧb›ĮhÉĘĩZŋq‹ĻŋöåęÛÍÍUFĶ+˙ú¯ļīÚĢîÛK’2ŗ˛ĩhųj>z\Ų9šō¯áĢn:¨G—’¤ˇŪ˙DGŸ”$íØŊWĪ=ųü|}o¸ĪŗY –ŽŌŽī÷Ē  PMĸŖ4qĖpyyz–ilI:v┖­ūJ įeąXU+<TCôQƒúu‹Æ°X´úĢo´{ßJIM— _õėÚI];ƕûü��TĻj@$)",LĄ!!:tč°-€ü’É”§7§ŋŖöqmu÷ä;%ĢUëŋūF˙yëŋú÷ë˙——§œœZšz­&ŒŖ)wOօ¤ zũéš='^S˜R揟|ö…Î'^Đ´īS ?_>rTŸÍ˜Š Ā�ĩnĢî]ģčãOŋPjZšükÔ°Õōた4aܘëÅbQŸŪŊôÍĻ͚;o‘zđžëļŊY ’ältŌęĩ_éŽIuĪ]wę›MßęķŗtđĐaŨ5qœĸę?¤…K–jƗŗÕ:6V^^žš;6nÜŦɓ&ĒAT}8xPŗæÄËÉÉIŨģv)û‡cVĢU……Ĩnsvr’Á`PÛØú~īŠ_´\O=|ŋŦVĢæ-^ĄV-š*ļyŒ$)4¸Ļš5šĩggBƒkĒfP Ž?a _Æ/Ō…‹uĪÄ1ōõņŅą“§4{ūøûŠEĶ&zđž‰úīŸŠfP Æ $OwũīŗY7ÜįŠm;ŋWËfMôČũ“u)9Uŗį/Ņ܅Ë4eŌ¸2ŸŸ¯÷?™Š6ąÍ5~ÔPÉjÕÆ­ÛõîĮ3ôˇŸ‘§‡‡-_Ŗ-ÛwiüČ!ĒW'R‡ŽĶüĨ+åė䤎qmJ=���UĄÚI đWZFzŠÛ’SS”kĘU§ŽqŠ+úëøÆ)Ž][9;;ŲÚEFFÚĻn……†ĒgŽZē|Ĩî6åÉŨŊ䴛‰ãÆĘ`4*¸f$)44DëŋŲ¨ü¤Ö­bÕŽm}9{ŽžÛžCwôī'IÚˇŋŦVŠ}\ÛëŒUrvvŌ¸ąŖõÖßQŸžŨÕ°aƒR›ŪŦÛąÕŽĨV-›K’:ÄĩĶį3fŠAũzje[ˇlų*%&&*<<L_oبA¨s§ĸŋ¨‡„ëäŠ3ZąjÃČšÄ zúų—KŨöėãSYĢčYŸq#‡^žKąGųJMK×Ã÷Oļĩmßļ•ÚˇmuËuÔđSFf–myÔЁ2 đ—$× ÔĻ­ÛuđđQĩhÚDîî2rvv’ˇ—g™öšÂ×ĮGŖ‡Mŋ‹ŦĄŗįÎkũÆ-Ę/(Ģ‹ËMûIIK—)/OíZˇThpMIԘaƒÔĻes9;;ËdĘ͎Ûv¨_ĪŽŠkSô{R3(@§Îií†o �� ZŠ–ÄbąČÉčTęﰐ…†„čƒ?QĪŨÕ,&FuęÔVãkŪ$U72˛ØrDD¸ ”––ĻĐАũēģšiųę5:øķ!effÉjĩ*;;[ĄÁÁ’$7WWuˆ‹Ķ–mÛmd×î=jŨ*Vžž7=ĻV-›ĢyŗĻš9'^~áĨļšY W„††ÚūíáQôœLØå0V´Î]’”“›ĢS§ĪǰĐŦf11ÅúhŌ(Z›žŨ,“É$ww÷›Öo/5ƒ4yü¨Rˇ]š¸–$?_Ü_KVŽ•ŲbҏCäãíeˇ:Š~ĮŽž…ÚÍÍUë6lŌáŖ'”•]tîsrxŨ>ĘēĪĩģ×Ģ)‹å[]JNQxhČMû  TpÍ@}>{žēvŒSãč(Õ ŗMŋ:zæ¤ĖfŗGļ ŖęjێŨĘË˗›/@���ÕCĩ ‰‰Ij͏ÔmFŖQĪ?÷ŒVŽ^ĢßnÖü…‹❑#†ŠsĮĢsæ¯}¸øĘ¨˛ssJôYXhÖëoN—ÕlŅÄ c"'ƒ“ŪzįŊbíēuíŦ 7éô™ŗ Öžčņ‡§–ų¸Æ­—ūüW}ģe‹b›7ŋĨ$ÉŲĨäĮæė\rÕjUŽÉ$Izí_˙–AÛ6‹Õ*IJOĪph�quqUŨČÚejÛļU -XļJNF'ĩlÖäæ;”CŌĨdÛÃđfŗYī|øš,‹F¨āš2úßg_^w˙ōės%^áæę"IĘĪĪ/S?FŖQO=|ŋžÚ°Y[ļīŌŌUëä_ÃOƒû÷V\›X™ōō$IĶ?øôŸđÕĪ8#3K5ŨŽ˙ė��€#Uģ�røČĨĨ§ŠY͘ëļņõõŅøąŖ4~ė(%œ;§5kŋŌ‡Ļđ°0ÕĢ[ô׿+ŪWäæ-{•rˇâø‰ã:{6Aüũ3Å^˙›‘™ŠšAAļåzuë¨NdmíØšKuęDĘÛĶK1MĘ~aĻŪ=ģkÁĸĨjŌ¨Ņ-ÕP^ž—ī<xßÕĒ^b{Āåi?ÕҊ5_̆¯¯ Íf­\ˇACīčk—~8ĨôŒL5Ž. 'OŸÕšÄ zōĄûŠŨ­ČĘĘļM‹ēVyöÉĪĪ/ļœwyŲÕÕĩĖũx{yiøāū>¸ŋ/$iũĻ­š1wĄBC‚åq9@Ū=a”Âqwė ˙že:/���ŽP­ž =;;GŸĪœŖ  @ĩmÛēÔ6I/éû_|‰aDx¸&OēSFŖA įŽž&õĐáÃÅö;yę¤\ŨÜPō/ÁEE{{_ 'GĶĨKɲĘZŦm×.ĩc×níØš[:ĩ—ŅhPy :D‹YĢÖŦģåĘ#˛v„\œ•‘™Šđ°0ۏˇ—ˇ|||äâârË}WĻĶg´aķ69DcG Öú[tÚ¯ÁÍÉÍÕ܅Ëā_C­Z4“$^~(ŪËĶÃÖîÄŠ3JNM“õ:§ž<û;qĒØōŠ3 rvrRÍĀ€2õ“œ’ĒũŽ~ŠbhH°Æ"ƒÁ ķ‰*g''efe+$8Čöãåå!ooĪRīŽ��T•*ģ2ÉËËĶÁCE!Á\X¨3gĪjŨWß(/Τß=õ¸\ŽsŅ”’’ĸˇß}_cGT˖ÍeAÛļīÁ`TƒúõmíŌŌŌĩhé2uîĐAįΟ×ú Õ!Ž­\]K^p׎UK...Z÷Õ :HgÎiŪÂÅjÖ4Fį/(=#C~žEEîØ!Nņķ(99E¯žüįrˇ——§F ĒYsâoš†ōđđđP÷n]´xé2ųx{Š^ŊzJNIŅŦ9ņ đ¯Ą§´Ü}V„)/O?:Rę6ƒÁ &Ņ d6›õeübĩmÕÂö=-›5Ņ—ņ‹ôû'ĻÉÉÉI;vīÕūu˙ä ×+/?OGŽ$šÍ:wū‚žŲŧMyųųzäūģåėTôœQDx¨œõÍæītGߞ:—xAKW­Sãč(%]ŧ¤ĖŦlųx{ÉĶĶ]gÎëėšķĒQÃˇLûHEbõújÛB—’S´yÛNÅļh*—2š–Žž˜­aƒúŠY“F2Č {öÉ`0¨^HšģģŠs‡ļZąöky{yĒNíZJIMĶ‚ĨĢTÃĪWĶĻL˛įG��P!U@’’.ęĩ×˙-ŠčÛÂkøųĢyķĻ2h€‚¯˙āoãFŅēīŪģĩzíWZ´d™ŒF'…G„鹇§{¸ŧ[ˇÎĘÉÎŅ˯ūCųŠmŲBwNWjŸžž>ēīžÉšŋhąļ~÷ęÖŠŖîŦ”Ô4Ŋ÷ÁĮúįŋŪÔ+/˙I’äåéŠÆÉd2)$$¸ÔūnĻg÷ŽÚđÍFM¸úũōÔP^ƍ‘§§§âį/TZz†ü|ũÔ*ļšF~KũUDrJĒŪûxFŠÛ ƒĻŋöZˇá[ĨĨg蹊÷Øļ:P{}ēÖ~ŊIwôíŠķ’ŠŨ(ÍĨäTM˙āS[ß~ž>Ši­ŊģËŋ†Ÿ­ˇ——&ĄĨĢÖiĮîŊŠŦĄģƍTZz†>¯é|Ē~÷¨ztî¨ĪįĖ×ŪųH÷OžPĻ},f‹z÷ęŽä”TŊ>ũ}Ēiã†;|pšÆž4v„žŪ´U+Ö|-'ŖQĄ!Ázđî‰ ŽYôßĘČ!wČÃŨ]‹WŦUzFĻ|}ŧÕŧic `Ÿik���öb°Z¯7Éäæö8ĸˆĐš7oč`×~qĄ=eddęŲ?ž¨ûî™Ŧ¸vŋÍכ&$^TĢĻ Ģē ���8ĀžG*|í7uęT…‡=Ėäđ2ĘĘĘVbR’fĪ§ˆđ0ĩmSú3*����Ž�RF›ˇlÕüE‹Ũ°Ąîģwrš>���p›ˇß|Ãî}čßWú3Ÿ���¨ˆjõ^����ˇ7����‡!€����p����‡!€����p˜ ƒÁ Ë­!���€j,ŋ PŽ.ö}qn…ˆ‡ģ̞˛rėU ���€j$95]învíŗB¤ND¨˛rr•™#ŗÅb¯š����TĄü‚B%^LQRršj…Ûĩī ŨOqwsUã¨HJHÔÅäT™Í„ßŠ=ŽTu ���¨$Ž.ÎōpwS“uė>ĢÂŊšģšĒQũH{Ô���ā6Į[°����8 ���€Ã@����8 ���€Ã@����8 ���€Ã@����8 ���€Ã@����8 ���€Ã@����8 ���€Ã@����8 ���€Ã@����8ŒsE;0ååëTBĸōō d6[ėQnA“uĒŨįāäd”›Ģ‹ęD„ĘŨÍĩĒË��@5PĄ�bĘËץc§åãã%_o9šĄR.\JŠ–ŸƒŲbQvvŽ;­FQ‘„���Tl ÖŠ„DųøxÉÛĶŖÚ\ôūd¨–Ÿƒ“Ņ(_/yyzčTBbU—��€j BWĢ&Sž<=ÜíU nQĄŲ\­?//ååTu���¨*@,V̌ƒŊjÁ-˛VķĪÁÉhŦ6ĪĨ��� jUŸų:����n{����C����ā0����C����ā0����SĄoBĮ¯WĨPsĪ,ÔĻ‹[•ZVĻ}\ũÕ-¨“ÆÖ!#ŋ:���(?Ž"ŖâĪ,Ō’s+ËĩOJ~ĒŸ[!ƒAš9Ļ’*��ĀíŦJČ[oŋ§={÷[g4¨Ø–-4|č yzx:¤–_ÎÖ·Žč•—˙TŠã<úäīÔ¯Oo <°RĮ)ĢM—ļŪŌ~SMÔâĢ ���¸%Uv$8¸ĻîŊû.ÛraANž:­•ĢÖęėŲ=ûô2Tão÷ūĩKÉO-÷>÷7ēSw5ŖOÍĒ„Š���đ[PeÄÍÍMME[×ŧYSųųúę“ĪgččŅcjذAU‡_2Č GšNҘzCĒē���üĘUģg@ĸĸęI’’SRÕđō:ŗŲŦĨ+Vjûö]JNIQ@€ŋú÷í­^=ēÛöKĪČĐÜøúéā!eįd+ Ā_Ŋ{öPŋ>ŊlmRĶŌôéį3uđįCōđđPĪ]oZOAA,ZĒ;w)=#C5ü|ÕąC{6DNNN’¤Įž~VCŪĄ””mßąK&SžE7ĐŊ“'ɯ†_‰>_yíušē¸ęŲ§Ÿ(ļ~ú;ī)==C/=˙\yO›]t‰Ķļ¤˛X­ļuôxŗû5˛î *Š ���ˇ—j@.\H’$ØÖ͝ŋ@7nÖäIÕ Ēž<¨Ysâåää¤î]ģH’>ųė Oŧ iŪ§~ž:|ä¨>›1SAjŨ*V’ôŋ?Ķ…Ä$=õø#ōĢ᧯ŋūFģžß#o/īëÖ3ãËŲÚŊg¯&ß9AõęÖÕąã'ôųŒYĘ/(ĐÄqEĪA8´jõZ>T˙zíĨg¤ë˙ūöš–,_ĄÉ“&–čŗ{×.úøĶ/”š–&˙5$I&Sž~<đ“&ŒĢšg+{ĸĮkՙõúįūˇeąZK s/Š’ú���p{¨Ōī1›ÍļŸŧŧ<ũ|čˆfĮĪWDD„D՗$åææęë 5`@_uîÔA!!ÁęÕŖģ:uė¨ĢÖØúš8nŦžyę 5nÔPĄĄ!ęÖĩŗj׎­ü$IJIMÕÁƒ?kĐĀūŠiŌXaaš4qŧ<Ü=Ž[_fV–6oũNÃRû¸v ŽŠŽâÔ§OO}ŗiŗ mmÃÂBÕ­K'999)Ā?ā˙Ųģīđ¨ĘŧãߙIf&$¤7zīŊWŠ"RDtŅÕĩŦîúĒëžęŽīĒØ×ēŽŊ#" Mz—.=´„B ¤NÚĖûG` $˜Ę$Âũš.ŽËĶžķ{ÎÃāšį”Ąmë–=Wjģ;uÄjĩ°qĶfįŧģváp@×.ĒtL+ŖWhWĻ4™�°¨üĨÍũ˜ &ks_ąđņõá9ŧŗ÷c—×'""""׎ģrüxĶĻß_lžÁ` uĢ–L™|ģķô¸ø iÕĸEąu›7mÂę5kąŲlX­VŦ ķ-fßūdddâp8ČĘĘ"48€“'“�hPŋ^ąũÕ¯C||Âk´Ûí4lPŋØüõbČËÍ%99™Čˆ�ĸĸ"‹­ãééIVvvŠíZĖfēuéÂē ›6d0�[ļn§Cûv.{û×ĨÖ'ofáņe ē€áQ7Đ6 %^aÎuf™Į{û>uym""""rmŠą�Âô{Ļ:§^ą’ŋîfúŨSņōēxžcŗđâ˯bāâ[ą.<§pî\:nnîĖxũM…v&Ūv+aa!˜ &Ūxû]įúļķí¸ģī˛ÕbŊbļąz_Įj-šÎĩå:įšģģ—Ŗ×õéŨ“ĢV<`vîŪÜ^Ą6Ē‹ŨáāĨo“o/⿘Ą�ÅÂĮüø%ēō!""""ÕĸƈģŲúõbœĶÆŨŽģe˜č-�� �IDATøvÖ÷LŊs’sž§GŅ-R˜6•ČČđíøsäčyâ¯Ņ´ÉÅ7gĨgdTˇ.PôÖ-€œ[ąíŗsrŽXŖõüžm—m“s~Ģį•oß*Kũz1ÄDGąų—-ÄÄDãíéE‹æÍ+Ũ^U9pđÚ¯ī8CĀüøĨŧŧë]8޴ЈˆˆˆHšÕč3 —ōööâ–ŅŖXŊf-rΏŽŠĀŨ͍ôŒ ÂÜŧŊŧņņņÁŨŨüü‚ķm\ŧrrčđaNŸNuž8‡††�üâíV…ė?pāŠ5EGFb4‰=t¸ØüC‡âáauŪŪUYŊ{õdķ–­lūe+=ztÅhŦŲß=šB~ˆ[ĀÂãËxå×w>DDDD¤ÚԚ�Чw/ęĮÄđÉg_8đöđđ oŸ^Ė÷#›6˙Š”Ķė;p¯ŊÁ?ú€¨ČHÜŨŨYēliiiėŪŗ—Īŋú–V-[p2)™sééÔ ¤aÃ,X¸ˆŨ{öœO>˙ˇķ¯Ō-ˇˇŊ{õdūO‹Øļc'§SSYˇ~#ËWŦdđ Î×đVV÷n]HKKcÛöôîŅŊJmU^ßũ>˙Úņ&3vŊ]ė•ŧ"""""UUĢ^Ãk4˜4i˙xūE,\Ĩ‘#�¸mü8<==™9ë{ŌÎĨãįëGûv­šeĖ(�|}}˜6e2ŗæĖeũƍԋ‰ážģ&sælīž˙!/Ŋü:Ī?÷wîŊg}ú9oüûŦž čۇîŨģąmێ+Ö4iâx<Ŧ>ûükŌ3Ō đgĈáŒ6¤Ęũõōô¤YĶĻØl6BBĒv5ĨĸĖūWü5tģÃÁĸ„åŋąmĀ—‰ˆˆˆˆüƒÃQų¯¸ˇī‰%"4¨:뚎¤§gđ—'ūÆ´)“éŌšcĨÛILJŠđ8|˙sTjc"Fp[ô-Ú&1)…ö-—Ŋĸˆˆˆˆ\sĻOŸNxxŅķÜĩę Čõ"33‹¤S§øúÛīˆŖSĮ.¯a\äh�VĨŦŋ═˘čԃ["G]ÍŌDDDDäĻ�RÖŽ[ĪŦ9siŌ¸1Ķîš\#ŸģŨ˜=މŅ5ķËë""""r}R�ŠC‡ bčA5]†ˆˆˆˆˆËÕǎ`‰ˆˆˆˆČĩMDDDDDD\FDDDDDD\FDDDDDD\FDDDDDD\ĻJÄ`0`¯üīJ5ŠíãPhˇc2)늈ˆˆHˆ‡ÕLffvuÕ"•äf2ÕęqČĘĘÁbv¯é2DDDD¤¨R�‰‰%3;‡ŒŦl íöęĒI*ȁŖVŽCĄŨNFV6™Ų9ÄD„Öt9""""R T釭3ÍF—˜DJęY kĪÉīõĻyŖ˜Z7&“‹Ųf ŖąZĖ5]ŽˆˆˆˆÔUū%tĢÅLĶŅÕQ‹T‘ÆADDDDj;=,""""".Ŗ�"""""".Ŗ�"""""".Ŗ�"""""".Ŗ�"""""".Ŗ�"""""".Ŗ�"""""".Ŗ�"""""".Ŗ�"""""".Ŗ�"""""".Ŗ�"""""".Ŗ�"""""".Ŗ�"""""".Ŗ�"""""".Ŗ�"""""".ãVÕlšyÄ%&‘›—OaĄŊ:j‘dvwÃÃj!2,ŗ{•#C1Uj͖›ĮÃņøøxáëãÉxm^PILJĄ}ËÆ5]†ˆˆˆˆˆKäåzöûÅŅŦQLĩ†*%†¸Ä$||ŧđöô¸fLjˆˆˆČõÆėîFXp AuH8yĒZÛŽRj°Ųōđô°VW-"""""R‹úû‘cË­r;¤ĨĨ‘––Vĩ[°ėFƒĄĘ‰ˆˆˆˆHícvw#/ŋ ĘíøøøP§N@oÁR�—Q�—Q�—Q�—Q�—Q�—Q�—ŠŌVÖķ/ĖĀÍŨĮ}¸Ä˛'OōäĶĪr˙Ŋ÷ĐšSG�Ūxë]ļīØYl=ŖŅHŨ灴kۆQ#oÄĶÃĶšėˇŪåtj*˙øßŋ]ŨŽ”á?Ÿ|ů{÷_qyÛÖ-¸ûŽ Õ˛¯ŋũsŨ:ĩgÄĐĒĨŊŠp8<ųÜKL;’ļ­š—˜ū={ü™Ņŋw†ė[ĶĨTĘĩ4"""rm¨‘�RÁÁAÜuįÎé‚ü|ŽÅÅŗđ§%$$$ō—??„Ąū*{Ũ@&ŒYę2o—Ö˛zũ&âŽ'rĮø1ĨNW։“ÉdegͤaũR§Ĩæh,DDD¤ļųŨ‹ÅBķĻMŠÍkŨĒ%~žž|ôéį:t˜ÆÕPuWf1[hÚ¸aM—Āņ„ŋ9]Yûcއ‡ĩÔiŠ9 Šm~7äJž˙f7õĖY×p-•ĩeĮ.>ûz6}č^"ÃÃ�8r,ž×Ūų/Ķî˜@ģÖ-øaá~^ĩŽ7_|öŠíŒF~Zļ’5ë7‘“cŖIŖL?o/Ūxī#9Āæ­;ˆŠ wÍ[wđøÃ÷ņÆģ1x@’SNŗgßrsķhÖ¤ĮÂÛĢčˇŌęØ{¨XČētÚnˇŗhŲJļîü•3gĪá_Į—ūŊ{Đģ{—r÷=#3‹9ķqđв˛sđ¯ãKŸŨč×Ģ›sŸO<û"ƒô!)ų;vīÅawĐŊKnčכ¯gÍåĐŅ8,f37HˇNíøËĶĪ—Ųß˕՟Ō˛pÉr6oŨANŽČˆ0n>˜õĸ‹–0ŅĪlŨų+陸ųúĐšC[n<�ŖŅXéū•66""""5íw˙zrō)�j¸’Ō9ō Jũãp8�čÔŽ ­š7aæœų8ėv;ßÍ]@û6-i×ē�ĄÁA´jŪäˇvÅļŋ’™™ÅŊSī`ĘÄq‰‹gá’å�üaĘDĸ"ÂéĐļ5˙úß˙áūģ'› Áh2˛låZ7¨Į˙ũũqäO<Áėy û¸ŧŽ‚‚wžä^>=gūb–­ZĮ}yōĪ0 wOfĪû‰ ›ˇ–ģī_ΜÃҏxĻLĮÜĪ ū}øūĮŸØĩgŸŗ“ÉČōÕëhŨ˛/üī˙0rø –¯^Ī;~Æ ū}xņ™'čÚŠ=3ŋ˙‘ėœ€rõ÷reõ§Ôm~\ĆÍ[sĶ0ēouxûŋŸ‘zæ,�ßÎ™Ī†_ļ1zÄžū˃Ü4ėV­ÛČÜ‹ĢÔŋËĮBDDD¤6ø]]),,tūwAAGÅķõĖYDDDШaƒŦėĘN$%ķį'Ÿ+uŲ_œNtd�ãĮŒäų—˙ÍĻ-ÛÉËĪįlÚ9ūx÷dįē];ĩ§ë%ßl—ÆÃjeܨˆŽ gįî}‹Op.3¸š™œßî_> æÜOHP]zuīÂĸe+šmlfŗšDGŽÅƒÃAƒ˜¨Ķ6[.k6lfp˙Ūt騀 ēÄ'ž`Ɋ5tīŌą\};r8FŖĀ��‚ƒYŊ~ûĸMˋVG†‡ŅĒyS (Ø|ûũÔ‰ĸūųÚ:ĩkÍâŸWq*å4õĸŖĘÕßK•ˇ?ÅļÉÍeũ歌ēqÚļāļ[n&7/”Ķg°Z-lŪēãüōÖ�Ô 99…k60rø`ÜLĻJõīōąŠ ~7äøņĻMŋŋØ<ƒÁ@ëV-™2ųöZų�: Nž0ļÔeĄÁAÎ˙öķõaôˆ!ü°p …v;ãG߄ˇW…öU?&稴ˇˇļøÜ ĩVl:,$˜‚‚ŌÎeXbũũą‡iØ 77ˇĶĮâ(,,¤Y“âĪæ4nX ›ˇ’››‡Åb.ŗī‹™Ĩ+VsđĐQ2ŗ˛p8deį\ˇx=!Au˙mĩZÎĪ ēd^Ņs99IEú›pâdšúsŠ“I§((( &*Â9ĪÍdržũėāĄ#ØívęĮDÛ.:*‚ŧü|RN§\Šū]>6""""ĩAœ™°ÛK]Vx~žé˛“ĻАĻß3Õ9ũķŠ•ėüu7ĶīžŠ×îׯ ĖîfįˇíeéÔž ŗü “ŅTŠWĻZĖîÅĻ‹2™ŖbmX,Ĩļ™cË)uũą‡ißĻUŠĶļÜĸá7ß˙˜KãĄũü­gé™YŠnģRß yûƒOąÛíÜrķpB‚ƒ0üį“/KÔâæf*1ĪŨŊä_qĮ%Į¤"ũ­H.¸p;”ų˛ąšŧMëåuœŸÎÍŊ–*ÚŋËĮFDDD¤6¨‘�âããCÂŪĀt:å4�uüüŠÍw7ģSŋ^Œsz¸[ØąsßÎúžŠwNēzÅēЂÅËŠãë[ôĐōŌŒ6Čå5äڊ_1ąŸöôđ(ąnvvĮOrÛØ›Kö8˙üˇ%<4´Äöūu|˙}Ĩž‹OāDR2ß7†õ/Žff–ķ–ŦǍH+ԟ ŧŊŧŠĩ{š W-lšĨ×qayE]>""""ĩE<„ŪĒeK’’“ŲŊgoąųvģƒų āīOŊËn'眎ˇˇŒÅę5k9pđĐÕ,×%âYąvãĮÜÄ­ŖGđķĒuÄWĶkr+"öü›˛.ˆKHÄėîN:~%Ö=pč0žD„‡–:Š›ÉDFf!Áuŧŧ<đöötŪô[}/((�ĀËķb 8wœÔŗi8*vq§Ęũ-o.TwwwįČ čůŋû!›ˇî 2,ŖŅČácņÅļ;Õj)q›Yy]>""""ĩE\éÕŖ;Ģ×Ŧã­wūÐ!ŠMzFĢV¯åX\úãŊÎ׏ū–>Ŋ{ąjõZ>ųė ž{æo¸_rhŗŲØõëîÛDFFā_õoÎË˖›ËŪąĨ.3 4oԈÂÂBžœ9—NíÛĐøük…ÛļjΗ3įđׇîÅd2ąyëvíŲĮŨ“oĢt-žžVO’pâ$ūuüJLœKOgá’åtéØžäS)ŦŲ°™ŽíZ;íĨuė=B“F œĪß\>mĩZčŲ­ –,ĮÛ˓˜¨HΜMcöŧŸ¨ãįËŊS'•Ų÷ˆđPÜÜÜXšv#ÃõįDR2ķ~ZJŗ& 9•ršŒĖŦ ?+sОú{Šōô`ÍúÍlŲą‹Gūx7VĢ…î;°xųjęøųĖēŋŸp‚IˇŽÆĶ̓n;°dųj‚‰Š#öđQV¯ßĖ }{–ësPšËĮBDDD¤ļ¨‘�âæf⯏>Ėü… Ų˛u‹-ÅäfĸQÆ<ņ×Įhܨ|¯ 5 Lš4<˙" .bÔČÎe))§yõˇJl3íŽÉôîŲŖÚúR–Ô3gy÷ÃĪK]f0xķÅgYēb iįŌųĶô)ÎecGįŸ3ŪdÉōÕ ԟ“ɧØĩg•jé×ŗ;Ÿ~3‹×Ūū/wOž­Ä4@.ÉÎą1ãßī‘Ÿ_@ëMoÖŠÕqāā!čã\vų4˜›†áaĩ2wÁÎĨgāëãMë–Í9´čĢōô}Ō­Ŗ™÷ĶR6oŨAtTwŒCÚšt>ūb&ož˙1O=ú@ĨIYũŊ\Yũ8“–ÆŅ¸ãÎéQ#†`0˜ģ`1ļÜ<"ÂBøã´;¨{ūÕŅãFŨˆÕbfæœÉČĖÂßĪ—Ąû2¨īJ÷Ģ´ąŠ GåodŲž'–ˆĐ ˛WüKLJĄ}ËßëĪ–ßãĪü‹ūŊ{0t`ߚ.Å%ގūŠˆˆˆTÆö=ąU>ž>}:áááĀ5đC„"""""ōûĄ�"""""".Ŗ_(§ŸyĸĻKpŠë­ŋ""""ĩŽ€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆËT)€ ė•˙CŠÅōō 0ģWī‹sĢ@<Ŧf23ŗĢĢŠERĪžÃÃjŠÖ6Ģ@b"BÉĖÎ!#+›BģŊēj‘”—_@RĘNĨĻ\­mWézŠÕbĻYÃhâ“HI=KaáĩBļéDDDDD\ÂėÕBķF1Õ~ V•[ŗZĖ4m]ĩˆˆˆˆˆČ5NoÁ—Q�—Q�—Q�—Q�—Q�—Q�—Q�—Q�—Q�—Q�—Q�—Q�—Q�—Q�—qĢjļÜ<â“ČÍ˧°Đ^5‰\“š7ŠŠuŸ“ɈÅėNLD(V‹šĻË‘ë@•ˆ-7‡ãņņņÂ×Į“QTDJ“|úL­üŦÚídeåpāp<MF+„ˆˆˆČUWĨŗ ¸Ä$||ŧđöô¨5'T"ĩ‘C­üŦ˜ŒF|}ŧđōô .1ŠĻË‘ë@•΄lļ<<=ŦÕU‹Č5Ģ °°VVŧŧ<ČÍ˯é2DDDä:PĨ�bw80 ÕU‹Č5ËQË?+&ŖąÖ<—""""×ļÚs/ˆˆˆˆˆˆ\ķ@DDDDDÄe@DDDDDÄe@DDDDDÄe@DDDDDÄe@DDDDDÄeĒôKč"rõØíN$súĖYōōË÷fwwęú‚ŅX{_û+"""×/‘Z*ád296-›5Äb6—k›Üŧ<ŽÆŸ ņdQaWšB‘Šsų-X/žü{æW\ž””Ė”ģīeŲō•åjeŪü…Wœ.Íį_~ÍSŽ\í‹ë]>†§SSyîų¸įŪX˛ôįrņĩātęYęE‡—;|�XĖfęG‡“’šv+Š<—^=쓐Čņã Ĩ._ŋqnn&ēué\Šö'Üz mZĩĒJ‰ĨZļ|%|ôIĩˇ+%íåc¸fízOœäŅGĸk—ÎWeŒkãøæåįW(|\`1›Ë}˖ˆˆˆˆĢš<€tęĐĢÅĘú›K]žaĶfÚˇm‹ˇˇWĨÚīÕŖ;õęEWĨÄRÅÅÅW{›Räōc{ųfeeQ70fMãįį{UÆXã+"""â.ÄbąĐšS6nÚˏઋ=({č0))§š}­�œKOįۙŗŲģī�YŲYø3°?ß0āŠí?đđŖ ža #G ālZúûöĀÃÃūũz—ØĻŦũŧ0ãöˆ`Ũú<û÷§ˆŒgŪ‚…lÚ´…Ô3gđgČ  č×÷Šĩ8xˆŲsær<!ģŨAtT$cGĸYĶÆ�–ŲæŸ~Œ›nÆîŊûØˇo?QQ‘xzzōØ#Û×ĢoŧEVv6O?ņ×JĩûÆk/áéáYŦÍü‚~˜÷#ëÖo"+'‡˜¨HnŊe 5ŦtũõęÅ{čpąc;ãÕםcøü 3œË§Ü}/ˇŒÅĸ%K‹qYušj|EDDD¤l5ōzĪŨYŗn=û EķfÎų6nÂĪĪ—6­‹n¯ųč“Ī8™”ĖŊ˜F?_Æâ“Īŋ n`�Úˇ+מūķá'$'â‘īĮ¯Ž˗¯dËļíx{y;×)k?>p/Ŋü:!!!Ü~ÛxŧŊ<ųæģYŦZĩ–É“&Ō¨aöėÛĮWßĖÄd2Ҏw¯uØlšŧūæÛtíŌ‰;'ß?/_Ékoü›Wgŧ€——'ßΚ]f›&“+W¯Ĩ]Û֌1Œ#GŽņíŦīÉÎÉv†ėœlöîÛĮøqˇ�TĒ]‹ŲRĸßÎœÅæ_ļpûÄ ąlų ^~íMūņĖĶÕ­Ô~BBBxåĩ7‹ÛK=ōĐũ|3s6ąą‡yâņĮ°XĖ,Z˛´Bušb|EDDD¤|j$€4mԈ  ēŦÛ°Ņ@ Ųŧe+ŊzôĀh,ē3lâø[1Õ 44„ŸWŽb÷žŊå gΞeßžũÜqûmÎũLš8={÷[¯Ŧũxzxb4šps3áëãMNNËWŦâÆáCéŲŖ�!!Á‹;΂Ÿ—z‚šzö 9ļztīBDXŅۉnŋm<]:wÂÍÍTî6 0›Ũšõ–1�Ô äë™ŗØšëWēwí Āöíģ°ÛítéÔąŌí^.Į–ÃĒ5ë?n,];w`ĘäIäææręÔ)|ŧŊ*ŊŸKíå<=<qwsÃh4–ēŧŦē‚ƒęēd|EDDD¤|j$€ zvīÆĸÅ˘<)‹Ų˝ģw“™™E¯žŨœëY-æ/ZĖžũČČČÄáp••Ehppšösōd� ę×+ļīúõcˆŋø|E÷Ÿ@AA!­Z´(6ŋyĶ&Ŧ^ŗ›Í†Õj-ļ,,$„АŪ˙ā#ú÷ëKĢ-ˆ‰‰ĸYĶ&�ė?[î65lā\^§Nš6iĖļm;䗭ÛhŅŧ9~~ž•n÷r‰‰'ČĪΧ^Ŋį<w77¸oz•ę¯Ē˛ę׌¯ˆˆˆˆ”OũHĪŨ™;o>Ûļī {×.Ŧ߸™ú11DFD�EWDfŧū&ŽB;oģ•°°Loŧũnš÷aŗŲ�pw/ŪMĢåâÉceö“sžŨ_~Ÿaą;�œ;—^âÕh4ōäãąpŅV­YËŦīįāΘŅ7Ķŗ{ˇ ĩiõđ(Öv—ÎųæģīÉË˧Đ^ĀžŊû¸ķމŽõōv/••• €ÅRú[™Ēk?UV]Ž_)Ÿ AuiÖ´ ›6ũBģļ­ŲąsãĮu.?rô ‰<ņ×ĮhÚ¤‘s~zFAuë–kKŅs 99ļbķŗsrĒ´Īķ'Đ˜6•ČČđËüKŨÎ×ׇ ˇŽe­cI<q‚ÅK–ņÁ‡ŸVé6:učĀ_}ÞŊ{ÉÍÍ ãų[ÔĒŌîĨ|||�°]v,/¨ŽũTTYušr|EDDD¤l. īĨzöčÆž}ûųå—m8öbŋũ‘Ÿ_�€ˇ÷Ғ>ĖéĶŠ8p”ĢũĐĐ�â/ų͑‚‚Bö8PšũœŸŒŽŠĀŨ͍ôŒ ÂÜŧŊŧņņņÁŨŨŊD-§RNŗmĮNįtDx8“'ŨŽŅh ņĉJĩy¯¯͛5cĮŽŨlßą“ļmZáqū$ē*í^*,4‹ŲĖūƒąÎyvģƒŊô2ëÖoŦú~Ę7¤ŽËUã+""""åScW@�:wėČ_}Ëėš?”øí¨ČHÜŨŨYēlŖFŪHBâ žû~.­ZļādR2įŌĶņķõũÍöëŌ°a,\DHp>>>,ũy9n&S…÷ãååI\|<qņĮ đ§oŸ^Ė÷#>Ū^Ô¯_ŸÔ3gøę›™ø×á‘(Q˙3gxë÷¸uėÚļm6mÆ`0Ō¨A<<<*ÜæĨētîȏķ’“ÃÔ;';įWĩŨKÛéŨĢķüD€ÂÃÂXšz ĮŽÅ3mJÃ*íįōc[eÕeĩZ]2ž""""R>5@ŦV :ļgŨúÎˇ ]āëëô)“™5g.ë7n¤^L ÷Ü5™3gĶx÷ũyéå×yūšŋ—š{GŸ~Î˙~̧úöĄ{÷nlÛļŖBû4°?˙ųđcžņeūt߸mü8<==™9ë{ŌÎĨãįëGûv­šeˍRëhÖ´ Ķîē“EK–1į‡1M„G„ņ§?Nw^ŠŠh›—ęÔĄŸ}ņ5fŗ;mÛ˙•đĒ´{Š[o‹Á`āÛīž'×f#22‚Gz€āā *íįōc[QeÕåŠņŊĖîîäæåUø×Đsķō0ģWüÔEDDD\Áāp8*yķ lßKDhPuÖ#rMJLJŠđgåxâI˛rrŠ^î’›—ĮŅøxyXˆŠĢpí[6ŽĐ6""""å1}útÂ˞­­Ņ+ "reaĄ$žLbĪūÃäåį—kŗģ™ @?"ÂB¯ru""""•Ŗ�"RKĸ"Â*|%CDDD¤6ĢҎ`‰ˆˆˆˆČõEDDDDDD\FDDDDDD\FDDDDDD\FDDDDDD\FDDDDDD\ĻJÄ`0`¯üīŠ\7jûgĨĐnĮdŌ÷"""rõUéŒÃÃj&33ģējšfš™Lĩúŗ’••ƒÅė^ĶeˆˆˆČu J$&"”Ėė2˛˛)´ÛĢĢ&‘kŽG­üŦÚídde“™CL„~=]DDDŽž*ũēÕbĻYÃhâ“HI=Kaaí9ąŠmš7ŠŠuŸ“ɈÅėNŗ†ŅX-æš.GDDDŽU PBš6ˆŽŽZDŽyúŦˆˆˆČõNOŠˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË¸Uĩ[nq‰IäæåSXh¯Žšäw*ĀĪ‹÷?úœĖŦlGM—#ĩ˜Á`ĀĮ˓{îšDŊ¨ˆš.GDDD\Čā¨Â™ĸ-7‡ãņņņÂÃjÁdÔ•ëÕŽ}ąĖ›7 RNƒ‘ŋ>|‘áĄ5]ˆˆˆ\EͧO'<<¨â-Xq‰IøøxáíéĄđq[ēėg…Šƒ‡ÃÎûQĶ•ˆˆˆˆ U)5ØlyxzXĢĢųËÎÎŽéä÷Č`ā\zFMW!""".TĨ�bw80 ÕU‹ˆ\‡ôŧˆˆČõE÷M‰ˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆGáŅÄ�� �IDATˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË¸ÕÄNßxë]ļīØéœ6›ŨŠX—V­Z0dĐ@*ÜæéÔTŪyīŽO`ÜØŅ 4°Zj}āáG|Ã@FŽ^-íIq÷Ü9‘6-›9§ķķķI=›Æžą,_Ŋž´sénĶŋŽĶîODX(?,\ĘĘĩĒĨÖžųVŦŲĀâŸWUK{""""×Ŗ �ÁÁAÜuį�äÚl?žČĒĩkYŗv˙éš6iTĄöÖŦ]O≓<úČC„…†\’Xļ|%G㞊SŽÚ>Ž7§SĪđÕws0›ÍD†‡ŌŊk'ēuîĀûÉáŖqj¯{—Ž„ķÖŸr*åôÕ(€Ū=ēÁ3į\ĩ}ˆˆˆˆ\kjė,‹ÅBķĻMhŪ´ íÚļáĻÃøį3OÁÛīžGŽ-§BíeeeQ70fMãįį{•dž¸¸øĢÖöõ*77Ø#Įˆ=rŒ=û˛xųjū8q2™ģ'OĀb1W¨=OΤĨqøh™YWŠjˆŽŋjm‹ˆˆˆ\Ģjė HiŦV+S&O⊧ŸeŨúMÜ0 �………Ė[°M›ļzæ ū 4ũúđü 3ˆ=t€)wßË-cFŅģWž9›Ŋû•E@€?û÷cđ œûûÃũ1zä 2Ø9īŖO?'>ū8Ī<ũd‰ú^˜ņ ûİnũFžũûSÄDG[įĀÁCĖž3—ã ØíĸŖ";z͚6 ŋ €æũČēõ›ČĘÉ!&*’[oCãF ‹–įį3{Î<6˙˛…sééÔņķĨ{ˇŽŒžų&L&�÷=đ#nÂɤdvíú[n.-[´āŽ;īĀ×Įģ\ĮŦ<ĩÖ¤ŧŧ<žžũ{ėAēvlĪęõ›�0 ؗíZā_‡´´s,_ŗžĩ~ā‘?ŪMƒzŅ�üûĨį˜÷ĶR6lŪÆčChÚ¸!žž¤ĨcÕēMŦZˇŅšŋWūų7.YÁĪĢ×9įMŧåf"ÂØņæ{%ę{čŪŠ4jP€ŽÚķÂëīx"ŠØ: ëĮpĶĐ Áh0’x2‰y?-u^Ņ1™L ԟ.Ûáéa%áDs,æhÜq�ÜL&F ŊŽíZããíEzF&›ˇídá’åØív�f<÷‹—¯"$8ˆV͛`1›Ųwđ0_}7—ŦėėrŗōÔzŠáƒú3lP˙RĮí§Ĩ+X¸tŕ†UDDD¤v€ˆ°0BCB8pā 3€|;k6ĢV­eō¤‰4j؀=ûöņÕ731™LôíŨ‹GēŸofÎ&6ö0O<ū‹™wŪû'“’š÷͍ãįËÁØC|ōųÔ  Cûv•ĒíÁîãĨ—_'$$„Ûoˇ—gąå6[.¯ŋų6]ģtâÎɡƒÃÁĪËWōÚ˙æÕ/āååɡ3gąų—-Ü>qÁAA,[ž‚—_{“<ķ4ÁAuųü˯Ųē}“oŋúõęqøČQ>ũü+ōōķ™8~�&7# -áļņã˜zįd’O%3ã•7ųú›™LŋgjšŽYyj­iɧNs*%•F ę9Ȩ‡Đŗ['žũūGŽ‹§YㆌŊy8……løeī~ô9cF ĨAŊ^{įŋäåå1íŽ „×åã/g’ž‘IÃú1Ü6v$gĶŌØĩgĨj{˙“/ųĶî"åt*ßÍ]@vNņ+vfwwîŊk[vėâëŲ?`Ā@Ÿž]ųãŨ“ųÛ?g“ccôˆĄtl׊™sp:õ }{våūģīä_¯žEęŲ4nsm[6įÛ9?ŸHŊč(Əš ŗģß˙¸€B{!ƒúõfö?ņÕws ĒĀ÷LaėÍÃøėëŲå:fåŠõRÆå!DáCDDDĘŖÖ€€�ŌŌΐ““Ãō̏qøPzöč@HH0ĮâŽŗā§ÅôíŨ OOÜŨÜ0Î+�ĮߊÁh$8¨.�ĄĄ!üŧrģ÷ė­t�ņôđÄh4áæfrîįRŠgΐcËĄG÷.D„…pûmãéŌšnn&rl9ŦZŗŽņãÆŌĩs'�ĻLžDnn.§NÂÃÃĘÚõ™0n,]ģtŠž•I<y’%˖3nėhÜŨІ,::š^=ēJ˙~Ŋ™7!wÚrq8ėeŗ˛j­-ÎĻĨ9ĩÕbĄw.,]žšÍ[w�°6õ Q‘á ę߇ ŋlÃfË%ŋ �ģÃîŧ0kŪBv;ŠgĶ�H9JŸ]hÖ¤QĨˆÍ–‹Ũn§  ĀšŸKųûûaĩZøeÛN’O=‡2뇅lÛš›‚‚B,3=ēvdîüÅlßĩ€¯gĪÃbąPˇn 9šštíØŽ9ķŗmgŅōĶŠg ĸīîü°p)………�?qŌy<NĨ¤˛vã/ ŊĄß˚‡Ņh,ķ˜•Uki.! """R^ĩ2€ØívLÆĸ“⏸ iÕĸEąuš7mÂę5kąŲlX­ÖmX-æ/ZĖžũČČČÄáp••EhpđUĢ;,$„АŪ˙ā#ú÷ëKĢ-ˆ‰‰ĸYĶ&�:|˜üü|ęՋqnãîæÆ÷M`īžũØív6¨_ŦŨõbČËÍ%99™Čˆ�ęEG['""œüü|ŌŌŌH;—^æ1+ĢÖÚÂh4:o7ŠÅÍdbßÁCÅ։=|”]:b6›ÉËË+ŅFn^ƒû÷ĻqÃúx{ya4đôô åtęUĢûTJ*É)§™2qk6lfßÁC$$žäБc�ԋŽÂŨ͍¸ã‰Îm ųđķo�hŌ°>FŖ‘cņĮ‹ĩŸˆŲl&¨n Iɧ�8žpĸØ:'“Náîæ†ŸŸ/ž>Ūeŗ˛jŊ’K‡Â‡ˆˆˆ”W­ II§hŲĸčÕŦ9ļĸÛ?^|ųU œëØ�ΝK/@ ™ņú›8 íLŧíVÂÂB0LŧņöģWĩnŖŅȓ?ÆÂEKXĩf-ŗžŸK`€?cFßLĪîŨČĘ*úĻüJUÛÎ÷ÕęQŧ?ú—kËŊ8Īb)ļŽÅ\ÔfVNvšŽYHHđoÖZ[Õå@lŅķ=VkQŸŧw*œī €ÁPÔG_oN§ž)ļŊŅhäūģ'c2™õÃB’SNShˇ3}ĘÄĢZˇÃáāõwūË ũzĶŖK'FÄŲ´sü¸hŋlۉ§gҘ–˜āb_m—Œ9€-ˇh}ë%‡r/kãB›žÖrŗßĒõˇ(xˆˆˆHEÕē�r06–´si´jYôíŊ§‡�˜6•ČȒo đ/1īČŅ#$$$ōÄ_+ö:ßôŒ ‚ęÖuNJl yšĨŸ–—¯¯n˄[Į’xâ‹—,ãƒ?!<, �l—ŨSõ|_/_žsūų̧ĮÅyļË×)šöōđ¤ ŋčŽŲoÕZ˙’Ģ45ĨAŊhü|}œßŪ_8˙ėëYœ8™\bũ´´s%æÕ‹Ž$",”×ßũ°ØÕŪ^^¤ž9뜾äÜÜÉŨŨŊJõgfe3wÁbæ.XLhpúödō„ą$%§™YF/„ËåœīëåË/ĪKĮ˙ō0za›ėœÜÎ߲WÖ1û­Z'ž(ąˆˆˆHeÕĒ_BĪĘĘæĶ/žĄnŨ@:uę�@tTînn¤gdæüãí协OŠ'‰ųų�x{_|úĐáÜ>Šƒ‹gšV˛˛‹?<\Ž“­RNVNĨœfÛ%?°ÎäIˇc4H<q‚°Đ,f3ûÆ:׹ÛüëĨ—Yˇ~#Ņ‘‘FįŊ.Ö~kąÛĮ<XlcqĮ0[,”똕UkMķđ°2~ĖM¤žMcûŽ=�$žHĸ  �oo/’SN;˙deg“‘•EAaÉį.<3sáęŨūāĪĨԖ›‹ĮeWžÂÃĘū=™ W.č_‡Ö-.ūĀbŌŠž™=ģŨNXh0§RN“—Ÿī|“Ö…ļēw*]:ļ#ņDvģŨųF¯ ęĮD‘cŗ‘rú╞KÛ�ˆŽŒ //ŗiéå:feÕ*"""Rjė Hnn.ûDp<!ĨËV’›kãŅGtž8zxxСO/æÎûo/ęׯOę™3|õÍLüëđȃ”h;*2www–.[Á¨‘7’x‚īžŸKĢ–-8™”Ėšôtü|}ŠÅöí;2h VĢ•EK–‘™™‰ŋ+ÖíååI\|<qņĮ đĮĮûâÃčgΜá­wŪãÖąchÛļ5 lØ´ƒÁHŖ đđđ w¯Ė_đūu cåę5;Ī´) ņööĸw¯žĖ˙iÁ!ÁDGErā@,ËWŦdčĐAÎ×đBŅ7×sæũHĪnŨ8qō$?¯XEˇ.0›Ũ÷2YYĩē’ÅbĻņų“h“›‰ˆĐPúöę†Åbæí>s>lmËÍeŨĻ-Ü8x�YYŲÄÅ'ā_‡ą#‡“v.÷>ūĸDۉ'“Č/( o¯nü´táa!Œ6ˆ}To//2ŗ˛ˆO8A›VÍYžf=ššy čĶ/OOÎĨg\ąîėœ"#ˆålÚ9˛/ ŗūūu¸{ō~X¸„Ũûāp@įmq8;Ž-7— ›ˇ1d@_ŌÎĨ“”œB¯nˆŽŠāË“Æ_ļ1x@RNŸ!áÄI7ŦOŸž]ųyåZįs1�~ž> ԟM[wDī]ØēãW ((((ķ˜•ÜˆˆHu28ĨŨ|R>Û÷ÄTáíŪxë]ļ_ōíģŅh ŽŸ?­[ˇäχR70°Øú………üđãÖ­ß@Úštü|ũhߎ5ˇŒ…Įųۖ>˙ōköˆåųįūĀÆMŋ0kÎ\ŌĶĶŠÃäÛ'pælīž˙!ū<˙ÜßIN>Å?ų”¸øŧ==éĶģ'ųųųėŪŗ—g˙ū�<ü(ƒoČČÃØõënūķáĮäåđ§ûū@ëV-‹ÕēnÃF-YÆŠäS&Â#¸iøPÚĩm@^^>ßÍūžMŋl%×f#22‚qcG;ūÎ/(`ö÷sŲ¸i ééøĶˇOoF âüļũ‡eā€~dgeŗaĶfōķōi×ļ Sīšė|¤<ĮŦŦZ+â/žZám�îšs"mZ^üöŨnˇs.=ƒŊbYüķ*Î^v[•ŅhdØ ũčÚŠ=ž>Ū¤gdōëŪũüøĶ2lšEˇ-u#Öį˙^y €m[3rø |ŊŊˆO8ÁĖ9ķņķķaęíã9{î˙÷Ę[Ô `Ō­Ŗ‰Š#;ĮÆúM[pww§y“FŧøFŅŗC/<ķ?ŦXŗÅ?¯ EĶÆLžm,înnü÷ŗoJ<čŨšC[öéIPP öB;'“OąøįUėŲ_ŧŨÜܸyø`:ļm…ÅjáÄÉ$~X¸ÔųđˇÉdâĻa7ĐŠ]|ŧŊ8›vŽu›ļ°tÅį>^xæXŊntîĐwwwvīŨĪßÎ!ŋ  ÜĮŦŦZ¯ļŋôœKö#"""5cúô鄇=P#DĒæōPTT6€HÕ\Š~¯@DDDŽm—Zõ ˆˆˆˆˆˆ\Û@DDDDDÄejŨkxĨloŊūJM— ĩÄ˙<ķBM— """R!ē"""""".Ŗ�"""""".Ŗ�"""""".Ŗ�"""""".Ŗ�"""""".SĨ�b0°Wūw ED0jēqĨ*Ģ™ĖĖėęĒE~Į<=<@aT*ĘáĀĪסĻĢĒR�‰‰%3;‡ŒŦl íöęĒI~‡čF}•-d40}椚ŽBDDD\ČāpTíkk[nq‰IäæåSX¨r=ķ÷õä?IFffM—"ŋ>Ū^LŸz1‘á5]Šˆˆˆ\eͧO'<ŧč˙ųU """"""ŋåŌ�ĸˇ`‰ˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË¸Uĩ[nq‰IäæåSXh¯ŽšDDDDD¤™ŨŨđ°Zˆ Æė^åČPL•Zŗåæqāp<>>^øúxc2ꂊˆILJĄ}ËÆ5]†ˆˆˆTB^~ŠgĪą˙PÍÅTkŠRbˆKLÂĮĮ oO…‘k„ŲŨ°ā@‚ëpōTĩļ]ĨÔ`ŗåáéa­ŽZDDDDD¤ ô÷#Į–[­mV)€ØŒCuÕ""""""ĩˆŲŨŧü‚jmS÷M‰ˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË(€ˆˆˆˆˆˆË¸ÕÄNŸanîî<ūčÃ%–8y’'Ÿ~–ûīŊ‡Î:Ö@uĩ×éÔTŪyīŽO`ÜØŅ 4°ĻKĒŪxë]N§Ļō˙ũ[‰eccųŋ_áņŋü™æM›đūąaĶæßloō¤Ûˆ=\ŽõôëËoŊËö;‹-ķņö&22‚›oAŗĻ+ŪŠëØ>ųŠ_÷îŋâōļ­[p÷ŽÚūgΝOėác<õčWmåõø3˙ĸī ØˇBÛ9ž|î%&ŒIÛVÍŲēãWfĪ[HFf/=û$ī}ünnnüéSJl›|ę4˙|ųMĻNOû6-ķ9Æ˛•k‰O<AVV6V‹…†õc< õĸ#ëUdüJ[×h4ā_‡Ö-š2lP<ŦÖmOt+íÛ´*ļ]zF&Oũã%œ~Ö/ąßž˜Éö]ģ™0v$=ģv*ąüBÛ7Ė ũz•Xž‘™Åßū9ģŨÎ/<ƒŅh,w_˙ũŸO8wœNíÛ0ņ–›¯¸~i4vŋą‹;žČęõ›ˆ=|”ôŒLÜŨÜ ô§u‹fôīŨO+nû{TŲ›D.¨‘�"•ŗfízOœäŅG",4¤ĻË)aŲō•=vŒ{ĻNŠéRŽčÆáCčÕŗģsúƒ?%22œaC9į………Ō¤qŖr­wAppwŨy‡s:--•ĢVķâ˯ōô“Ķ ~ŊĢĶĄkTŨ@&ŒYę2oWSsƌJxXÅ?ë'N&“•M“ķ'sŗ~X@ŗ&3b(VĢĨÂíÅ>Ę[|JĮv­šcüŧŊ<9›vŽĨ+Öđæûķ—§ė\ŋ"ãW7П‰ˇŒrNŸx‚e+Öpâd2÷ßs'ƒÁšÜ`00gūbZ5oŠģģ{šęĪÉąņëŪũ„‡†°iËöRObÜŨŨŲ´uGŠ'ą[wėÂh4bˇÛKÔ_V_īšķ6víŪĮįß~O÷ΨUŽēAcWÛĮ.ŋ €¯ŋûƒ‡Đˇg7õëE??ō HH<Éڍ›yūåķĮi“‰-mW"×%ß‘ŦŦ,ęÖÚoÔãââkē„2EFDáœ6[ÜņķķĨe‹æÅÖ đ÷/×zX,š7mRl^ĮöíyüɧYēl9Ķī™ZŊ¸öYĖš6nXĶeÔ¸ŽÚWjģũą‡‰Ž ĮÃÃJ^~>™YŲtj×ĻŌámõúM„…3yÂXįŧ¨ˆpš4jĀ+o}Āá#qÅNb+2~ŗĨġŪ͛6Â×Į›¯ž›Ëҏã4¨í\ÖēES>ʲUëvCŋrícˎ]˜ŨŨ3roũįRNŸ!¨n@‰õԋæ@ėaŽ'ž *"ŧØ˛_ļí$:2œ#ĮŠ˙;WžžZ-ētlĮ×ŗį‘zælšˆÆŽvŨnįí>ÅËĶ“ŋ?ū0æKB• ͛6ĸyĶF,_Ŋž÷>ū‚§ûVKÅC¤Čĩčw@<Äė9s9ž€Ũî :*’ąŖG9OÄ ™ˇ`!›6m!õĖü2h ú]ŧ4ø§‡ãχą{ī>öíÛΝŊÄãOũ/7 ƙ3gØ´y 6[.M›4âŽÉ“đĢãĀšôtž9›Ŋû•E@€?û÷cđ .ļũįŋpͰĄ$ž<ÉÖmÛąÛíôéՓaCķņ§_{‹ÅĘčQ#čŨŗGškžÔķ/Ė öĐa�ĻÜ}/ˇŒŐA™=g›ŲšôtęøųŌŊ[WFß|&“éˇû]ÁzË:/Ėx…ũbXˇ~#Īūũ)bĸ/ûĻ(?ŋėz˙ü—2Įä÷Âlv'**’äS)W\įjüŨŽŠŠÄĶĶ“Įy°Øž^}ã-˛˛ŗyú‰ŋVú3ãéáYÍGŠōļėØÅg_Īæ¯ŨKdx�GŽÅķÚ;˙eÚh×ē?,\ÂĪĢÖņæ‹Ī–ÚÆšô žún.ÅÃjĄWˇÎ%Ö)((`ūĸŸŲēķWŌ32ņķõĄs‡ļÜ8x�FcŅctO<û"ƒô!)ų;vīÅawĐŊKnčכ¯gÍåĐŅ8,f37HˇķĄ"#3‹9ķqđв˛sđ¯ãKŸŨč×Ģ›sß—ßæđÄŗ/2d`_ÎĻĨąuĮ¯äææŅ°A o…ī%'¨ûc9OŦv�nnĻJëÂÂB JˎZ,WíVĩúįo :›vŽø>­V†ŪЏ‹—ĶŊsęøų–ŲÖĻ-ÛéĐļMÖĮŋŽ›ˇíāÆÁJŦįįëCDX(›ˇî(v›œršø„Ü8x@‰“ØŠps3áp8ĘŊžÆŽvŨOKW`0¸{ō ųaá6mŲNn^>íÛ´¤CÛV|øŲ7ĖøĮSėؐ ›ˇŅŋwŅ•uģŨÎĸe+ŲēķWΜ=‡_ú÷îAīî]€ōũûVžC*ķoĶ_ž~žÁúœrš=û››Gŗ&˜8nŪ^Ĩ˙? Ŧū”Ļ °…K–ŗyërrlDF„qķđÁÎĐzĩūí•ÚĄÖ›-—×ß|›Ž]:qįäÛÁáāįå+yíķęŒđōōäÛYŗYĩj-“'M¤QÃėŲˇ¯ž™‰ÉdĸoīĸËą&“+W¯Ĩ]Û֌1 ‹Ų‚›ŅÄO‹–0zÔH^~ņyÎĨŸãŲžČķ0yŌD�>úä3N&%sīĻQĮĪ—ƒą‡øäķ/¨@‡öí�p3šX´dwLšČ”;ngåę5|úųWė;p;&ާaƒûøū‡y|ūå×th׎Ü5_ꑇîį›™ŗ‰=Ė?†Åbæķ/ŋfëöLžũ6ę×ĢĮá#Gųôķ¯ČËĪgâøqŋŲīŠÖ[Öqxđûxéå× áöÛÆ—úTyę-Θ”Ænw•]ęߟš”’ršˆˆ°R—]­ŋÛGŽãÛYߓ“í Ų9ŲėŨˇņãn¨ôgÆUų%Oš�ÜL& Úĩaێ_™9g>üņnßÍ]@û6-i×ē�ĄÁA´jŪ¤Ôv�>ûf6§RNsßÔIøúú°zũ&vüē/Ī‹ŋ3Ÿ]ģ÷1~Ėĸ##8œoŋ˙‘üü|ÆÜ4 �“ÉČōÕë?æ&&ŒÉēM[øöû9xø(ˇŽÁ=wF˛`Érf~˙#mZ6ÃĶÃ/gÎ!9%…)ĮáëãÃácq|=ëüũh͞ô+m&“‘e+×0bČ@ž{ōŅ˙oīžÃŖ¸Ŋw%­: õ ÂôŽ�Smlƒ1ĻLsÁŽá?ļ'ņ›äŊ7oŽorīuė'×qÜ덃éŊ÷Ū{! 5@Ŋk÷ũcĨ•I¨ dû÷y=°ŗ3Ŗ33ģGķ›9į YŲ9ŧņöŦŪ°™iė´/^Žgäˆá Ú÷ÕéŪĨķ-ã“/į3ōūĄDE„;5­š]]Ž_mRĶo�āwۅ›ÕÆũCîeįž,Yš–gŸ˜rĮõ¤¤ĻsõZ"OƒÉdb@ŋŪė?x„1#GT)‡ÕjĨO¯îlŲž›ĮƎvœäė?x„°`‚ƒ›d[›’Ž]Ķģ‚‚B6mÛÅī~5“Éėß.&!)‰šŗfŌēu+6nŨÁŧ…K‰Ž Įl6Ķŋo/=î ‹WŦeįŪLŸ4ŽvŅöģ7 —­ÂÕŅAúÕŠ~ĢKԐēÉėbfÖL7š'§L$5ũī|ôŋ,ZļŠgf<^íū¨m{Ē]fų=Δ‰ciāĪ֝{x÷“/øÃ+/āī×du¯´ ->€Ü¸u“ü‚|@x¨ũDîÉĶĐ?WWōķķŲ´y+Ž͐ÁöÔĕĢ×Xšz­ãdĘd˛_‘žúø$§õ‡††0|¨ũ*ŋŋŸ?Ŋztãō•ĢŽ÷Ÿ˜6“ŲLP`[�BB‚Ų¸e+'Nžr€¨ČúôęĀŊúķĪ/ŋĄCûvtˆuL[žb5ÉÉɄ……ÖŠĖ•yyzáæęŠŲlĻ•¯Ų99ėØĩ‡éS&3p€ũĒmPP ‰×¯ŗnÃ&ĻL~ 7Wסģ>卍m_ë~đōôÂlvÁÕÕÅéJlšē–ˇ.Į¤:‰‰‰ŧđŌ+wœ§Š•––:ūŸ™•Åƍ[¸žœĖ“3ĻV;S}ļÛ0oÁBŽ;Ё8|øVĢ•qũîú;c„¤ä^ųÃkÕž÷ۗæao7mŌxūōæ?Ø{ā0EÅÅÜĘČdîė™ŽyÆõŠąSFfį.\bęÄątėĐ€)åĖš‹ŽyrķōØwđ}˜žeߗļū¤¤¤ąyûnƏ…kŲŨ숰Pēwé@\īž|ûũrÚEG:šlÄõîÁڍ[IMK'&*’ÉãĮ`6›đ÷ (0€mģörú^‘ŪI�� IDAT܅ØCÕŊũûĐĻu+ēvîHüĩ$Įû—ŽÄƒÍFû˛ß[TTā¸Ë؃Ƒ›—ĪÚM[9züîî´oEĪn]čߡ—S͍ûņ+Wšm~Ii)ņ×ų~ųBƒƒĒ4W˛aÃÅŅIãFķáį_3|đ@§f>ˇÛsāAĔŨ‘Ŋ7Žk7nåâåĢthSeū¸>=Yąf§Îž§{—NØl6>ÆāúÔg[Íf3ÅÅÅ5–õv:v-÷Ø9~’ˆđPÛpõZ‡āß~û˛Ŗy؄1ŖØšį�1eû ¨m�ˇ2˛�{xŲž{ŖF c@?ûyD`[â“Xˇyģã„Ŋļú­ŽuH}ë&€ˆđPGŨؖĄƒ°fÃfL.Âbą8íĢēnĶ2……ėÚw°ŦnĩJ0ãņ ‘–~÷&Ģ{Ĩehņ$48˜ā`>üø3FÜŨģv%::’ÎeííΜ=OII)ŨģvuZŽK§ŽlÛžƒ‚‚<ĘFâčÛžĘú###œ^{yy9]I÷pwgŚĩœ>s–ėėl6ššš„9-RŅšĖŗ,a‡††Všf/C^~>Wãę\æš\ģ–€Õj%ļŊsûÛö1Ņ’’’âčÃPŨv×§ŧõŲQŪڎIunī^.!!‘¯į}[§2Ūkט5į§iŪ^^<÷ėLēwëZí2MõŲnĶĻ :ŪÃĄCGd˙ÁCtíŌ…Ö­[ŨõwƁmũÚŦWRéJfëVž<6öa–ŽZGŠÕĘ´ĮÆáëã]§ßQŪ4.:Ēâ$Ãd2Áĩ¤ë�$&%cĩZiíü™ŒŠ §¨¸˜´ôŽöķÁeápt Ŧ4ÍžOķķíwåÜŨ-Ŧßŧs.““›k˙NååÔ6āŽå uîČęåéáøž‚Ŋ˙Glûh\ËũÎŊ°X,„ŨåĀ#G ãžĄ÷röüEΞŋșs™ŋhk6lá…Ų3 ŠÔ ŽĮ ņz2/˙îONĶL&]:u`Æä 5^…îŪĨ]:uāģĨ+yõĨįĢĮjĩ˛˙ĐQ†ā8Qö÷kCû˜(ö<RíIl€_ÚEG˛īāēwéÄĨ+ņܸ•AŋŪ=ˆOHĒ2}ļĩ]t$Ŗ_īž¸ģ[Ē]Ļ2ģ–{ėâ“ũ_vī;DĮØvUúĻXm6Į oaQæ˛ũ‘tŌŌR:wėā4˙=ą1ėŪwÂÂ"ÜŨ-ĩÖou­Cę[7DŪvį>48ˆ’’22ŗ t^]ˇ§˛ëÉŠ”””YQ˙ēē¸8FY;wáR“ÕŊŌ24K�1›MpÛhåJËĻģ”ũņ4›Íüá˙ü†UkÖąuû~ŋ„�?&=6!ƒî%ŋ �€×ßü&**;kY[ÍĖĖ,Į‡ĪŖš[ow‰Ŗ¤¤”7Ūz[Š•'fL%44“ ÷ũ*ķēēUŨ•å'�•Ųlļz•š&eëđđtž¯|šÂJMĒÛîú”ˇ>ûĄ1Ę[×ŅQ*ĢŽ8€‹Ų˜æ!ÁÁNÍ-7‚ƒ‚īØvģ)?Ûú÷cūwßSTTLŠĩ„“§NķĖĶö&lwû1‚ÅÍRį+Uq}z˛hųj\Ė.ôę^ķƒÛÚ¯.ßūyĢü‡˛ ĐūšŧŊã¨{ŲëŠĪmuĮÚ­šī™ ĨĨĨŧûņ?ąZ­<>a ÁAeCƒ~]kš-ÕŦ*Úϟ=Ņ1ĖéûŸ~Éš —˜=s^^ĮŌd2ÕØĄÔjŋ“WŨUw‹›=ēvĻG×Î�œģx™Ož˜ĮâkųÅŦ§+ÍW÷ãØ6€gŸ¨hÖąm×>Nž>Į33¯ĩšÄ¤qđ_{—=ûŅ­ė hegÎ]$3+›•k7˛ríF§÷’ާ0eâŖÕÖ7q}z˛xÅZō Øč(1QøûU{[Ÿm}ōņ‰üíŊOxõ˙ũ'¯˙é÷wÕJĮŽeģĖĖ,G0LģqĶŠ#?ĀåĢ×(**rœ`§ß¸I`Ų‰{yŊōö‡ŸSų/Tyœ•C ģŋc{ĒĢßęS‡Ô§n*į~{gąīëü‚|nWŸí)W~ŅÄbŠūī}SÕŊŌr4K�ņõõ%ĄšĘ� =-€6­+ڎļjåËôŠ“™>u2‰IIŦ]ˇ?ũ_ÂBC•ÜĪg=GDDX•õų—ŨšlˆK—/‘Čī_ũ *%ûŦėlÛļŊÒwÖe.?1,Č/pšž_~ĮÂĢņNc?YŪæāfqŖ]LtŊ—kĒĪv\ßž|õÍ|Nž:EaŲ‰vŋ˛&ƒMųi+×nĸMĢVöë73ū‘‘ĩ/DÅžüÛ>“y•^—ą‚Bį+gå}‹jģPP“+ņ $%§đË_Ė"ļ]Åį&''×Ҝĸ!ōōōš–x“íĪ+˜6i+ÖndŪĸĨüņ՗M'||ŧIēžRí:nÜŧØī.•ËĘÎÁŨbŠrŗcl;zwīĘÉ3į\f‹››SS—IcGsüÔ–Ŧ\Wë33B‚6h�Ë×l¨öų{Ļ}L“Į?â4Ŋ¤¤”ŋđGOž&ŽwĪ*Ëõí՝EËVsôø)?É#hāÖ9[˛jŽ..üjîŦZī€čØĩėcgŗŲwxÜ-nU†ø]ģi+žžŽcqđČq†ļߑ.>Ę33&Ruh^ŋ6ķkĒߚĒ)WXP}W]°ŦĪö”ķņövZīíšĒŖYž„ŪŊ[7’SR8qō”ĶtĢÕÆŠUkđ÷ķ#&ÚŪ.45-C•ōÆĖ§žÄl6‘˜”DTd8nŽŽdegęøņņöÁ×סAWĶËÛ;§ųøTtHŊpņ"éé7î*I7F™Ŗ""0›ÍŽ‘ą*ĘwOO:7Ē‹zí‡v‹‘åũĄhĘĪvĢVžtéܙ#ĮNpøČQzõėîhjהßŖÅ'$˛yĮnĻMĮÔĮÆ˛qëÎj¯tV§üļ}bR˛cZii)į/]vŧŽ Ál6sņļŅs._ĮÃÃŊÖæR5))ëøę])x_žzˇ2¨Į IUœŊp/OOĮķüũÚ0nôCdfes-ņēcžŽî!-ũ†S°ßņ\ˇimZˇr4ÁČÎÎáßūō&ļl¯ōûl6)ié´ōõ­ō^Cyyy2ū‘‘ėŪw‹—īÜ÷ `ˍ”Z­lÜēĶizųķ#ú÷éETD¸ĶOû˜(:ßËžƒGĒ]§ˇ7ī‰eũ–íäį8ڨ߭SgÎ1lđ�bĸ"kíĖ­cײ]€ŋ?É)öfœ;ÄrôÄ)RĶnp+#“o.ÅĮÛĢÕJn^k7m#'7×Ņy<<4W˛sr jëøņööÄĮĮËŅáNõ[SÕ!åÎ_ēâôújB"77ÚT3e]ˇ§˛āĀļ¸ššqĄŌīąŲlŧõū§ė;x¤Éę^i9šåČĐÁƒØļ}'īŧ÷?ü 1QQdegŗuÛŽ\ŊʋsŸwŒ`qķæMŪyīĻNžD¯^=0ab÷Ū}˜Lf:´o§§'÷ ʒeËņõņĻ]ģvܸy“oæ/Ā߯ ŋzŠáà FFDāææÆú ›™8ūQ“øîû%tī֕ëÉ)dfeŅēUíÃŪŽ1ĘėããͰĄCXąz AÁADEFpöėy6mŪÂčŅ#īĒĶâíęēŧŊŊ¸ĪÕøkøûûáëSŅŨČōūP4õg{@˙~,_ąŠŧü|ž{ĻĸãbS~gKAa!§Ę†užÉdĸKĮ”––ōõ‚%Äõé鸂ÚĢ{ž^°˜W_~ö<Âą“§™=sF•õøûĩ!&*’u›ˇŅļ­?ž>ŪlŲąĮŅąė'T÷öīËēMÛ  2<”ķ/ŗm×>ēoˆŖžĒ¯đ°\]]Ų˛cŒARr ËV¯§sĮXRĶŌÉÎÉ­s_–ĘΜŋDĮíNn˯X–Tíg`ŋŪėÚ{OžœĮÃFNN.;÷ >!‰yf†cÛ|}}x`ø`ÖlÜJVv=ĘF’ÉĘÎa΁C\ēĪĪžthĄ.ĮīNčĮŽŊ˜ˇp)ŋ{å§cr;/OOƎz…ËV9M?päĨĨĨôęQ}Ŧ>ŊēķÍwKČĖĘvēcP.ŽOOž˜ŋˆŽÚW;°FšúlkIiŠĶĶÁwėŲĪžũ‡yå…ŲÕ~–tėZîąëÖš#_-øž cF2tPާ¤ōæ;âáîΨÃéߡ)ŠéüëŸßäžØ^˜ũŒã¸xx¸3äŪ8VŽÛ„ˇŅ‘Üŧ•ÁĸeĢiĶēĪ?÷T­õ[SÕ!å2ŗ˛Xĩnúõ!%5íģ÷ҝwĮ`1•Õe{�ļīÚĮ#ĮøÕÜŲxx¸3¨_֖…æā vîŲO|BOM}ŦÉę^i9š%€¸ēēđę¯ɊUĢ8pđkÖŦĮÅՅąąüūÕßpO‡ŠuîԑY?{†5ë6°xérĖfÂÂCyqîBĘÚ_Θ6///,üžŒĖ,ZˇjMŸŪ=x|Ō̊P'­Zų2ëŲ™,\ŧ„]{öÍŋül&7oeđū‡Ÿō×7ßâ/¯ũąAënŒ2?õÄ4<=ÜųâËydegáīīĮØącûČà *SMęēF>8‚>ũœŋŧū&/ūâįôčŪ­YĘûCŅÔŸí¸ž}ųâĢyX,nôęé|°Šž3åÆÍ[ŧ˙é—Õžg2™xûõgũæíddfņâœgīM?†?ŋņ6ë6m㑑#¸ž’Ęą“gjü=Ī>9…ož[ÂGŸ‡‡Cõg@ß^9QqwvĘÄGņpˇ°`ņr˛srņkŨŠŅŪĮČÃŧ}>ŪŪ<5õ1–­^ĪžƒGˆŠ įéi“ČČĖâķ¯đö‡Ÿ7č gĪ]`ÔĩŋëââÂKsžeÍÆ­9~Š [wâęâBģ˜H~ų‹YUF&š0fĄÁAėÚwc –—Ÿ‡‡;ŅáĖ=ŗĘIi]Žß˜L&Ļ>6–7˙ņëËŽå š7Ž{ö“”\Ņ4iī{GåšNÂzuëÂŧ…K9pøŪ7¤Ęû=ģuÁÍ͍~Ŋ{ÜņwßÍļfddrõZBŊ†ęÕąkĮŽsĮXüũX´|5S'ŽeÆäņ˘ėüTõßŧøsŦV+%ĨĨUF›4î<=<X˛r™YŲ´ōõĄGˇÎŒmobU—ú­)ęrƒô#/ŋ€7ūņÅÅ%ôčډ)­qūÚļāfF—¯^sŧž8öaL&KVŽĨ °ˆđĐ`æÎzšļöū"MQ÷JËa˛ÕįŠHˇ9|ō<á!UĮÖILNŖOˇ{šģ?yEÅÅüú˙ūĪ?÷Ũ:×üLiz¯üá5ž˜2‘¸>}^ûëßųãĢ/W;ŋŽ]ËQŨąËČĖâŨ˙‰_›Ö<üā}´‹ŽÄl6cĩZINMãôŲ ėÜģŸ‘÷ĢņY-Ņí@û9˙ŨūMŸ3gaaöž§-~^i8‹›^^ž:z‚˜¨§‡,Š1ŠŠ‹9vâ4Å%%N„Ož9GômĪÕ¨LĮŽųÕtėĀūžßž4‡MÛvņõwKHŋqw‹…‚ÂBüÚ´Ļķ=ą<5uŌŸu"ōSĨ�""ō#7eâŖ|ŋl5ŋ;x„ŋūûĪųc|øų×\žz!ãœÎ×­sĮZīlčØ5¯šŽ]9‹ÅÂč‡îgôC÷S\RBA~žžÕvŧ‘ j‚%"MBM°DDD~ģ –†Ã(€ˆˆˆˆˆˆa@DDDDDÄ0 """""b1Ė]“É„ĩáƒh‰ˆˆˆˆH VT\‚Å­q‡–žĢ�âéa!''¯ąĘ""""""-ȍ[™xz¸7ę:ī*€D‡‡“—OvnĨVkc•IDDDDDšQQq Éi7IŊ‘ADhPŖŽûŽî§x¸[čÅÕÄdŌnÜĸ´T!DD*>yžš‹ """ `qsÅĶÝ.ĸŊ Ö]¯ÍÃŨB§öQQų‘Ķ(X"""""b1ŒˆˆˆˆˆˆFDDDDDD Ŗ�""""""†Q�Ã(€ˆˆˆˆˆˆa@DDDDDÄ0 """""b1ŒˆˆˆˆˆˆFDDDDDD Ŗ�""""""†Q�Ã(€ˆˆˆˆˆˆa@DDDDDÄ0 """""b1ŒˆˆˆˆˆˆFDDDDDD Ŗ�""""""†Q�Ã(€ˆˆˆˆˆˆa@DDDDDÄ0 """""b1ŒˆˆˆˆˆˆFDDDDDD Ŗ�""""""†Q�Ã(€ˆˆˆˆˆˆa\Sod4wDDDDDä'Â5( Ms—ADDDDD~"ÔKDDDDD Ŗ�""""""†Q�Ã(€ˆˆˆˆˆˆa@DDDDDÄ0 """""b1ŒˆˆˆˆˆˆFDDDDDD Ŗ�""""""†Q�Ã(€ˆˆˆˆˆˆa@DDDDDÄ0 """""b1Œks@DDDD~x.^‰įŗ/į““›‡ÕjmîâĘl6ããíÅsOO'6&ĒNËė¸SįCZ.”Øšļ|ÁՁ۰`: iÜu눈ˆˆˆÔËÅ+ņŧõŪ'deįüä€Õj%++›ˇŪû„‹WâkĮū \Īųa„°—ķzļŊÜ;Ž4îē@DDDD¤^>ũb~sĄų™L`ŗÕi_Lų~ šÃ™}™ōmãŽVDDDDDę%;'§š‹Đ2˜LäæåÕ:[Jļei*&Hoäí�"""""Ō@ui‚öƒŧûQIc7S�Ã(€ˆˆˆˆˆˆa@DDDDDÄ0 """""b1ŒˆˆˆˆˆˆFDDDDDD Ŗ�""""""†qmˆˆČOK˙žŊ< áa!¸¸¸pëV&GŽŸdĶļ]äåį;æûī?ũŽÍÛwŗvãÖf,mķ2O÷†YqĐ3<]!! Vž…סARĨ§Ŧ/ybü ÷;Îë˜ÚæOƒ—VÂ;{ -~ĩ@DDDDÄ03§O&ŽOO;ÁöEû)))!&*‚áCŌ§gwŪúāS˛ŗsšģ˜-‚ŲķĻ”î0īŧŋ˛ íAäÅA0Ŗ'<ôKŠyÃĸá‹Įáõí-#|€ˆˆˆˆˆäŪ¸>ôīۋy‹–ąkīĮôc'Oŗ÷āa~ķâƎz€y‹–5c)[Žšajxr|sŦbúĘsđņØųsX0ēũJmU—īKŸ‚ųĮá÷ëŒ+wm@DDDDÄ÷ Ŋ—ĢלÂGš”Ôtūūūg¤¤Ĩ׸ŧĢ‹ cG?DŋŪ=đõņ&+;‡}‡Ž˛jŨ&ŦV+�ąíĸ7ú!ÂBƒ1›Ė$^OfŲęõ\ŧ|�ŗŲĖčīŖoīøûĩ!##“MÛwąc÷ūĻŲčģđō XÁ9|”KĪƒßŽąŒ1`ųį÷C|`õ3°įĖ^lLyëJDDDDDšœ‡‡;áĄ!ŦÛ´­Æy’ŽßqS'ŖWˇ.|ģx9ņ ‰ÄDE2mŌ8,nŽ|ŋ| 77ž˙ŲS8rŒy‹–bÂÄđ!™;{&˙úį7ČĪ/`âŖ3äŪ8žũ~9—ŽÄĶųžX&OCiI)ģ÷jėÍn°Pč�Ũ!­ŋ`˙÷žį�âc•3!%Ļ˃k“ĩŪ@DDDD¤ÉĩöõÅd2‘~ķVƒ–÷ōōd`ŋŪ,^ą–CGO�~ã!AŒ6ˆĨĢÖãį×wö:JJĒũNĘÂĨĢ8tô%%Ĩx¸ģ3lđ�ÖoÚÆžƒG�Øqã&‘aŒ1ŧEđÖö¯dÔ<O~ $į@x̊iŽfønô ƒ_¯†ÜâĻ-gCh^ir6ėJKK´|Dhfŗ™+ņלĻĮ'$bąXl@jÚ RŌŌyö‰)Œ1ŒˆđPŦV+.]Ą¸¸˜đ°\]\8}î‚Ķ:Î_ŧL`[,KÃ6Ž ”ĩ(í–ŗuŗ Ŧ•út ‚ļ^öë˙5 F4]Jw@DDDD¤ÉefecŗŲjĐ å=<Ü(((tš^PXdßŨ‚Ífã­÷>áĄû‡1x@ãÉ­ŒL–¯ŲĀūCGëxéųįĀVqÖn2™�håëCú› *_cģ–i˙ˇ_ÍķxēB7ÄgVLģr † EĨĐ-΀žīBZ^Ķ–ˇ>@DDDD¤Éq-ņ:ãú°vãVJĒšŌģGWJJJ9qúl•÷ō˂Gyˆ(įáî^ö~�9šy,Yš–%+×Č÷ aæôÉ$§¤9ÂËķ’tŊęØĩ™UĻ5—´<8™ Ķ{Ân…jšâĄö7^Ŧ˜–Yhoš0}>yæO‡QŸW?RVsP,1Äæíģđk͚Ņ#GTy/$8ˆO GˇÎÕ.›˜”ŒÕjĨ}L”Ķôvґ䐖~“�ŋ6ôčZą|rjķ-ÃjĩDbR2%%%øøx“’–îøÉÍË#;7ˇÚPԜūg't†_ Ŧú^€'ŧ1_w •]΁'ž…ûÛŲ›cĩē"""""†8pø÷Äļãá†ĘÁ#Į),*"*<Œáƒ’œšĘâkĒ]6/?ŸŨû1ęá¤Ĩß$!é:÷ÄļcølܲĢՊŸ_fΜÎŌUë8qú,6›ũŠë6›ËW¯QPXČÎŊxtÔäææq5>ŋ6L?†ŒĖ,>øü+ƒ÷ȝ}vîkīŽŗ?PpÉiČ)´‡’؟’>áĢęīŽ”Ûx ^Ûzö&Āĸ“F•žf """"b˜y —röüE† ĀãÆāb6“~ķk7meëÎŊ×<lĶwKVRPXČ´IãđõņæVF&k6laũæí�\¸t…¯,æÁáCxôáą–Zšž’ĘĮ˙œGZú �ž_žÆ1o+_˛˛s8~ę ËWo0dûëÃ<ŗ֜ƒŸ÷‡'€‡ĢŊȡĮáŋˇÖ­oĮl!Ņđų$8™gj~Ԋ!L6›­…´‘‚_ũcsĄEųĮ__ģãûĻ5¨ MČöįģ[~Μ9„……ę""""""R�Ã(€ˆˆˆˆˆˆa@DDDDDÄ0 """""b1ŒˆˆˆˆˆˆFDDDDDD Ŗ�"""""Ō@&“Šöy (GSrmäÄ �"""""õâëã6[sŖųŲløx{×:[°/đCŨ]6h[û&֋ˆˆˆˆˆÔËŦ™ĶĄWūôL&ûž¨ÅwĶ~¸ģËd˛—ŋ1)€ˆˆˆˆHŊÄÆDņËšŗņõņŠS¤“É„¯ŋœ;›Ø˜¨ZįÛfCˆoã7gj*Žf{yˇÍļ—ŋ1™l6Ũ?‘Ļ3gÎÂÂÂ�Ũ)€ˆˆˆˆˆˆa@DDDDDÄ0 """""b1ŒˆˆˆˆˆˆFDDDDDD Ŗ�""""""†Q�Ã(€ˆˆˆˆˆˆa@DDDDDÄ0 """""ŌdŦVĢĶki2§NÂbą8^›m6[3GDDDDD~Ŧl6K–,ÁËËË1ÍÅÃÃãOøúúâęęڌÅ‘ƒ‚‚Ν;ĮĮLjj*­[ˇvŧgúõ¯mËÍÍ­Ō6KDDDDD¤Ą\\\đööÆĮĮĮiē̝¯/žžžÍT,ų)Q't1Ė˙BˇÕļL~ēô����IENDŽB`‚����������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/mod-user-ldap.png���������������������������������������������������0000664�0000000�0000000�00000127453�14156463140�0022045�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��â��:���]@Č���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨwxTį÷˙÷¨ÎHŖ>*¨€„ŊƒÅtLĶ ¸&ŽãŨMÖ{í>Éo÷Ųdy’ÍfŨlâl›8€ŠÆ˜blbc0ŨĻ÷jē@ŊF#Ą6’æ÷‡@F–� H åķē.ėŅ™sß÷÷>Ō5Ÿ9eÎūõ_˙Չˆˆˆ¸…@MM vģŠŠ jjjÜ]“ˆˆČcËÃÃ___‚‚‚0 ĩAlˇÛißž=ŗgĪ&<<ÜŨ5Šˆˆ<ļrrrøāƒ¸|ų2AAAx�TTTđüķĪ+„EDDZXdd$ķæÍŖĸĸ�ƒÁ§ĶIpp°›Ky2X,jjj0<j÷ˆEDDÄ=Ä"""n¤ q#ąˆˆˆyšģ�‘'ŅۃGđ7_Đąˆˆˆ;)ˆEDDÜHA,""âF b7R‹ˆˆ¸‘‚XDDÄE§NĸŦŦŦŪ˛cĮŽÕŨ?ē)ŧ ĩ˙ŋ¯ ž>kã&Nšã:gΞåéc\~Î]šZĶÃX숈´<›ÍÆēuëX˛d ååå�ėÛˇõë×ŗmÛļ&÷͝O†õMē÷ >á"DGˇâČŅc÷ڍˆˆČ#%88˜‰'’Í˛eËØģw/Ÿū9qqqŒ1ĸÉũœ9~’}Wjî=ˆ×Ž[ΏgÆ0iâÖ­ßPīš?ŊûgƸISŲļ}g“ŸģՇĢ×0|Ô3 6’Ųs_ 3+Ģá$ΞeܤŠüú7˙Íėš/0ú™‰ėŲģ×˙îĮŒ›8…ųŋø÷ēu?û|cÆMbäØņĖžû—.]žkMGgĘqˇũųV[ˇīā™ “:b4ķ^|…Ģõļs‘G[¯^Ŋ?~<ééélÚ´‰ččhžūy|||šÜ‡Ÿ‘Ē‚K÷ÄÕÕÕlÚŧ…ącF3jÄpvėÜEee%�/]bÁ{ ųxíj6~ŧ–‹/ÕĩģĶsˇ*°Zųˇ˙÷K–.ū ģˇI|›6ŧũ΂ëyzzqîÜyFÁĒKéÜŠ#˙ī—˙Á[˙ķßŦ_ķ!kÖ}L^^>™™ü˙ō3Ū}û|šé¯Œ{f,˙ô˙ũ‹K5ŨIvN˙ô“æw˙ũvnŨĖĐ!ƒų—ũ7—û‘G‡ŅhÄ`0Ô=öôôtŠŊ%"œČ€{üÄ]ģŋĻ{ˇn˜Í˜L&ú÷īĮÖí;�Øā OõëKDD8L›:šŽŨžģUXh(Įī'66€ääū¤ĨĨ5ēnPP }z÷ uë8žę×|}} ˇXČÍËã̝÷Ō˙Š~$$Ä0kÆŗœ8q’â’’&×t'Ûˇī¤[×Ît먀9ŗgą}ĮN‡Ë}‰ˆČÃī›ožaũúõX, Äå˗YĩjÕÕÕMîãė…<|“zŨÛŊĻ׎[ĪöģčŅû)�ĒĒĢąŲyfĖhlļ"‚‚ëÖ Ē{|§įnåt:yoábļnێÁ` ¨ČNttĢF×õ÷ķ¯{ėááÉdüögOĒĢĢąZ­„„„Ô-÷õõÅd2QPPĐäšîÄ^\Ėá#ĮüôČēe&“‰ÂBá.÷'""¯üü|>úč#‚ƒƒyéĨ—0›Íxxx°k×.ļlŲÂØąc›ØS)—NešÄvģ}ûpôĐ>|ŧŊ¨ĒĒbāāaX­R\\ōmÁuīôÜ­6mŪÂgŸobõË `Ũú ŦûxCŖë6…ÅVŠŠ ĘĘʰX,wŦÉÃÃgŗîį’’o×ģUDD8)“ųķ;ŧįEDäŅ`ąXxúé§éŪŊ;fŗ€#F`2™čŪŊģK}ļ‰sũĐô'˙Jrō€ēđōōbđā>ŨøWúôęÅžũČÉÉĨĒNJ5k×Õ­w§įn•—›GLL4Øív>ū䎗–ēZjA)9pđŠW¯°âƒéÛ§7f˙;ÖNNn.Ĩ7ÆŪ´yKŖũNČŅŖĮ¸r%€ã'NÖģPLDD/ƒnpuāuÁÜūæ(ēļös=ˆ×ŽßĀč‘ /Ī3j$ëÖo S§Žŧøü\&NÎØ “éÛ§Õ5ĩĮĖīôܭƏ†k!#ƌãoüŧņã‘‘žÁ¯ßü/WË UTŋųõ¯xũoˈ1ãØžc'˙õæÜĩĻVQQĖž9ƒgg>Į+¯ū„øxp:ôoąXxķ×ŋâo~ô÷ 9–ŸũÛ˙cü¸gîŠVy2ôėAMö ?ûŲĪYY™ŧ÷Ū{îŽIDDä‰ŅĩgüÍēÅĨˆˆˆ;)ˆEDDÜHA,""âF b7R‹ˆˆ¸‘‚XDDčÄ"""ntO÷š‘û“ܯ7­ZEkXDDĝÄ"""n¤ q#ąˆˆˆy858ųF!i9N§§ŗæÛĢĻs lîŦGDDä‰TÄaÁîŦCDD䉤sÄ"""n¤ q#ąˆˆˆ)ˆEDDÜČŖ¨¨ˆ’’w×!""ōD)))Ą¨¨¯°°0***Ü]ˆˆČ%((ˆ°°0šq'ąˆˆˆ)ˆEDDÜČëîĢÔWRZFZf.å•-QK.§Ļ˛˙ĘĘuŽ[g2ú2ëŲIôéŅÍŨĨˆˆ4Ęå NËĖÅĪäKXHPKÔã’å+WQép¸ģ yˆ••WđáÚOÄ"ōĐrųĐtyE%&Ŗą%jq™BXšBGLDäaĻsÄ"""n¤ q#ąˆˆˆ)ˆEDDÜHA,""âF6{ Å×KŨ]ĮcĄ_¯Œ1ôˇŊUd¸…W_œsßũ´´;ÕŲ\ÛBDäQāhϴĝŲ:\ųáj23ŗÉÉÍÅĮĮ—ā :węȏછm WġŽeÔ°!ŧˇdeŊe?xq×Ō31 �|ąm'—S¯Õ­3jØ`BƒƒYûéįTUUÕkƒ|}|ølķ6.\N}ĐS‘ĮˆË7ô¸›9ŗf°fũ"#œ2_ūĮ›<Õ¯–°0ŽŸ8Éņ'ILlËū‡hKfV6ƒS’éÜš‹Ū_†ŅčKqq S'M ..ļšKāĘĩôēp äõī=Ī‚Å+°Úđöō"ĀlæĀ‘côëŨƒŊ7hÄ^šÃ›ŋ§^ŋ‰ņm˜5u"Q‘álÛõ5.§ōüŦgš~ũ:Ą!ÁŦßøÖBæÎœŠĶéÄčë˲ÕëęÚ{xxđŊįgŗķĢŊ\𚯜™S¨Ŧ¨ÄßߟĪ6o#*2‚>=ģ’‘™MlL4ûåŌåT^˜=ÜüĒĢĢĖĩß^˜Ų˛}7­"#7z8_lŨÉø1Ã)š^ŠŋŸ+V¯§˛˛˛Áx­ĸ"čÛĢ;ÕUÕ,Ząššš�z÷čFŸžŨČČĘĻm›8Ns“ÉDb|k¯øˆJ‡ƒš3&SZVNP` _îØMn^~ƒ:ĢíÄŠŗ�x{y5¨'3;§ŽŨŧ™SØč(ËW¯oļŋ ‘ŠŲƒ¸1Cb×6e{÷`ėčQdfgáīīĮôiS()šÎoūįwäX‰aâ¸ąäææąlå*ūņĩx}ļ";'ΜŖK§öėŪs€ūũzqâôYŽ\McHJ˙ē ž•ŲߟōFnQ^QÁ‡ë?% ĀĖßŧō< —¯b÷Ūũ|sū=ģuĻß^؊Џxų ;ŋŪOÛøÖ�`0˜;c _ī?ČųKWšŌŸĖŦļlߍ%,„S&°÷ĀaĘĘĘųlËvĸŖ"?f8–ĐN=ĮŽ=ûéŪĨ#–°ĐģΚcûD.]šĘ—;ž"4$§ĶÉĀū}¯ŧĸ‚%+×Ôk_SSMqI Ÿmۯ̓’ 0ķÉį[7j‰mÛHęĩ ļīŪCpP ¯Ė›ÅņSg\ĒŗązŪY¸ ¨ _€¤ļņ ay¤= î˙T_~ņĢ7yfĖ(Ŧ……ÄĮˇ&3;‹p‹�ŗŲŸ’âRōōķHKË ;; î°ņƒāéáAyYƒä~ŊÉ/°28š–°0:$%RQQA|ëX^}qTUUąjí' úÉÉÍ ¸¸?**ôčŌ™Ž:Hņõë„sîÂ%€ēÃá­""4 %%×YũņF�ÂBC‰iIdxívr:@í¨Ŋŗ˜ˇ—7ÁAA|sá"�V[“æģ{īF Âßũā%ŦÖBÖmÜtÛņn×§Ũ^ Ôn {q �ŽĒ*|ŧŧ áüÅ+uõē\įíęši˙ĄŖu,"ō¨z AėëëK‡öíXąj5ũŸęWˇüfhŲl6ÍD„G` cô¨8 Ŧĸ<‚ƒéÚšÛwīĄk§;y†-Ûwāg21wæTļlßEę-‡Ļoįæ^^@€™â’ë ”ĖÕ´tö:Ę ũˆ‹iEÕJd„…SgĪŅž]Ûē€Ų{đ×ŌŌ™3}2‹W|D~kĄ_íÅËĶ“ bZE5ŗČn',$€ˆĄu+‡ÃˇWí¯:äÆ=Â#ÃÃųrĮnĘË+;ōiz÷čvûņž€M‘_PHd„…“g ,4kĄ­Ņ:ĢíÛ>Ö#"ō¸y A 0lč~ųë7yn֌ēe‡ƒĢV“žžÁÔIčØĄ‹—.gáâĨŲí œBTTä}Đ:–ž<¯îį/ļíŧeīր—§'Ë?\Ŋ¸„áC˛ėÃoĪŲ––•ááa 48øŽã< xzzōė¤ghÉ_7o'Œ1”Öq1äæå“Ø6ž=û3âé^ž;ŖŅ—åĢ×HUU'Μ#ąm<CS°÷Āaž›1™š3ĻĀ×û5:îūÃĮxyÎ âbZQ^QÁw$\¸t…”ũ˜0v$8�?ßá9lļ"L&}ŧ‘ŌŌ˛&×7knúdĖūūŦût…EE ęlŦļ›ö<Ō žŧüķæLDäA1˟?ߙ™™É‚ šÔāčé ÄD…ģ<ЕÔĢlßš‹W^|€=ûö“Ÿ_¤ ã\îëĻ_žų?÷ÜVž,˙û›_¸ģ‘z^{í5ĸŖŖĖņæ/ˇqâä)^}ųÅ1œˆˆČ#ãņč‘Ã=rxŊeôC‹ˆˆ<Ôt‹K7R‹ˆˆ¸‘‚XDDč\bŖeåå-Q‹ËŧŊŊŨ]‚<ŒFŖģKš-—/֊‹‰ -#Ģ­¸%ęqIĘĀėېō‡ä<|ŒF#ŗŸčî2DDnĢ6ˆsŪKųvĖ~&:%ĩiŠz\ŌĢKĪŽåî2DDDî™Î‹ˆˆ¸‘‚XDDčÄ"""n¤ q#€J]u,""âTįsņbŽģëy"yŦØpcģw×!""ōDōš;m™™™îŽCDD䉤‹ĩDDDÜHA,""âF b7rųKJJËHËĖĨŧĸ˛%ęy¤õę’äŌú.qZf.~&_ÂB‚\mú@ddįšŧDDDšÃŅĶ\nãōĄéōŠJLú~W‘fĄsÄ"""n¤ q#ąˆˆˆ)ˆEDDÜHA,""âF.|IĘÉÍgÃg›éÕŊ …6Ǝęî’DDäŅėA|éŌe~ũ_ŋåg˙üSÚ´i Ā; ū´)“ˆŒŒhîáîIęĩ4,^I\LĢēeáaLŸ<ŪU‰ˆČ“¨Eöˆ{öčβ•ō“|īēåĨeĨŧˇp fōķ ˜=s:™™Yė?xˆÖqą\¸x‰ŨēRZVÆų ųជŅhdŅûË0})..aę¤ ÄÅÅŪw mâøÁKsę-ûrĮWxxx0|Č@.[ÅД„…†°äƒ5ŧņú÷ę­[rũ:K?XK¸% OOĪē嗯\åÃuŸ’•“ˈĄ)tëÜņžk‘ĮW‹qpP}zõbÍúõĖ™5ŗnyIņuF J×.9xč0_íŲCRb"L<‘Í[ļRTdgÆôŠŦ˙äSÎ_¸ˆÍVDll Į%77e+WņoüčžkLMKįĪī¯ŦûšwŽŒšÂ‚Å+p:D†[h×6 Aė;x”.:04Ĩ?'NŸ%ŋĀ €¯¯ŗĻMÄ^\Â; —)ˆEDäŽŧlöН—6k§N§“äOqôøqΜũĻnš¯‡ãØņڊ0›Ŧ-ÆÛ‹  ÚĮŪ^Ū8ōōķHKË ;;�ƒÁĐ,5ÆĮÅ6Ø#xzP2 /įW?˙ÉÛۊėtlŸ@XHHŨō¨ˆp�Ė”\ŋŪ,ĩŠˆČãË+8ĐLi‰_‹tūüÜįøŨ[oP¸›ˇn#!!ž!ƒ˛mĮNŽ^KģkáXBÃ=j‡ƒ‚{ž-ĄĒēš-Ûw1wÖTÖoü‚š3ĻÜvŨ Ā� Ŧ6�rōōë–į`/.Š{Ŗ!""r;-zÕt€ŲĖ”IãųŨŪæšY3čĐ>‰O6~Njj*‘QQœ;wVQ‘wėcĐĀd/]ÎÂÅK)˛Û:8…¨ģ´iŠÔkiŧģhyŨĪ"Â-$÷īCߞŨš|åGOœ"žuËV­åĮ?|Ĩ^ûū}{ņūŠÕ¤gdb4úNœN'UÕUŦũä32ŗs?fø}×)""7Ãüųķ™™™,X° I Žžž@LTx —uīôíK""â.GO_hrŊöÚkDGGë†"""î¤ q#ąˆˆˆ)ˆEDDÜHA,""âF b7r9ˆ>>”•—ˇD-"""O—očAZF.V[qKÔĶ,ŽžžāîDDDšÄå 6û™č”ÔĻ%jyâčąˆˆˆ)ˆEDDÜHA,""âF b7R‹ˆˆ¸‘‚XDDčÄ"""n¤ q#ąˆˆˆ)ˆEDDÜHA,""âF.ßk礴Œ´Ė\Ę+*[ĸ‘G’ŲßDlT8&Ŗ¯Kí\â´Ė\üLž„…šÚô‰’‘G¯.Iî.CDDĢÍNzvIņą.ĩsųĐtyE%&ŖŅÕf"""ĩĐā@JŽ—šÜNįˆEDDÜHA,""âF b7ō°ŲK(ž^ęî:DDDžH~FŒ>ŪîŽCDDä‰ô@MŸ<ušŨ_īiōú{öíį“Ÿĩ`Eō]gĶ֝î.CDä‰áōįˆīæŌĨËŧõöģ$ġÆéŦ]6qÜX’’ÚŅ­k—æîŽuüúŋ~ËĪūų§´iĶ€wü…iS&ņĀ긓Ôki,Xŧ’¸˜VuË"ÂØ>yŧK}üųũ•´ŽÁycƒ1”ļņ­]ßVdgËöŨ˘Ōô1DD¤å4{$ļMāī˙îu�Ŧ…V~ûûˇyãGŋÉĪ/ GˇŽŦßđ)æ�3%Å%ŧōō‹œ<uŠũŅ:.–ĖŦl§$×õWZVĘ{ —`&?ŋ€Ų3§ŗdŲ ^íûXÂÂ8~â$ĮOœä…ysęÕŅŗGw–­üŸüãøÜrøŊąū23ŗØ°vü /ŅŖ[WJËĘ8á"?üÁ÷0,zFŖ/ÅÅ%L4¸8×>´Ũ˜„6qüāĨúušã+<<<>d —­bhĘ�ÂBCXōÁŪxũ{ úˆoũmļ";úËR^{e~&#ËV­Ãėī‡ĩĐÆÔ cŲļ{×Ō28qú,I‰ ž¸|å*Žû”Ŧœ\F MĄ]Ûøë9N6nۊŲߟë×K™;s*ž>ŪŦøčc|}|(š^ʸŅÈiußÛHDäqæ9xđāų%%%Lž<šI ˛ķŦšũoû|aa!į/^bĀSũ�0™LRTdĮÛۛŌŌ2ō ō ŗ„1kú4ÚĩKÄĮۛÜŧ< m6^˜û]:wbáûKh—˜HiiááDF„3fÔH||ŧ9vâŨģvåė7įčÔąoØČˆaÃĒWG~~}z÷bĪž}tëڅC‡ĐŠc•ŽũYÂÂ(˛ķÜŦé\ŋ~ģŊ˜g§M&/?‡ŖŠsį/āįīĮŦéĶHˆoÃĢ×0p@˙Ûn‡â’RZE„Ũq[ڊėlŨĩ‡KWŽrøØI;‰Ķé$e@_6oÛE‘Ŋ_ôëÉhd@ŋŪöqéĘ5úôė€Ņč‹ÍnĮ^\Lp` K(Ç ÄÛۛ“gÎŅĢ{ÕU ’BQ‘ŊÁķAX y~öŗtîØžÖl k§ Ö+.)!,$„ÉãF“ß//ö>ŠŸÉĔņchÃēO?§_īwÜ""“ė<ë]_ûoÚ¸q#-ŗGü]ÕÕ5øų™ę~>l(ũė ~ķ?on cöĖgˇX�0›ũ))ūöJn_9Æąã'(´`6Ķ˙ŠžüâWoō˘QX ‰oäPŦĶé$yĀS=~œ3gŋšc�ÁÁ�xy{TûØÛˇÃA^~iidgg`0šeÛÄĮÅ6Ø#xzP2 /įW?˙‰Ë}VWWc2ņņņáÄŠŗœ>{[‘ŗũ7Pˇ{>*"€Ā�3%ׯ7ēŪāOąeûnūøŪÂB‚™:q,ų…dfe““—4ß6yœĩxX­?q’1Ŗ†sæ›s�deå0î™Ņø™üØđÉ_9pā0>ž>ääæ`ŗŲ4×õąyë6â2h Ûvėäęĩ4|}}éĐž+V­Ļ˙ŊīÛy~îsüî­ˇ 0ßļŋģ‰ĀÆčQ#p8Xīes4IUu5[ļībîŦŠŦßøsgLirÛB[§ÎžcØāėøj/mâbĐ¯7_í=HZF& uį’}Č/(Ā^\B€ŲÜčz9šųŒ|z&Ŗ‘ĪŋÜÁ‘ã§°„…˰ÁÉTUUQh+jū#"ō˜i‘ ž|å oũņœ55TUWķĘË/\÷üõë×ųß?ũ™Đ`JK˘7gįÎ_Āáp°bÕjŌĶ3˜:iĨeĩ÷ėėĐ>‰O6~Njj*‘QQœ;w´Œ † Â/ũ&Ī͚qĮzĖfĻLĪīūđ6Ī͚Ņh­ĸ"īØĮ É,^ēœ…‹—Rdˇ3tp QwiĶŠ×ŌxwŅōēŸ=< D„[Hî߇ž=ģsųĘ5Žž8E|ë8–­ZˏøJÃ>ŌŌųķû+q:TWW3oæ4‚h—ĪĻ­;¸šžAd¸…‹WRéÕŊ į/^æĀác>LUuk?ųŒĖė\ƏŽCƒõâbĸųäķ-RVVΌ)ãņ÷3ąrÍVŦ^Ŋ¸„ũûniÚ!‘'•á_ūå_œYYY,Z´¨I Žžž@LTxŗ˛gß~ōķ ˜4a\“Û\IŊĘöģxåÅį›ŊžûĨo_yō=}ĄÉ¯ũ¯ŊöŅŅŅæqKØüå6Nœ<ÅĢ/ŋčîRDDDîŲCÄwēš1ŖGgôČá-TˆˆČƒĄ/}q#¯}ũœŒ*‡ģëy"y÷Jƒ>f"""â^epžD_ƒ(""â^ŲŅÉôķoúÍ)Œ>>”•—c2[°,‘G‹ÕfĮ|Ë]$›ĘĢôäNúV5šA\LišXmÅ.ö¤9zú‚ģK‘Äėg"ļ•ë÷Ųđzvęh˛˛˛\¨SR—‘†ôņ%7R‹ˆˆ¸‘‚XDDčÄ"""n¤ q#ąˆˆˆ)ˆEDDÜHA,""âF.qIii™š”WTļD=//Oŧŧ<)/ps4}ˆ 'Āßī)""îįr§eæâgō%,$¨%ęy(äæbōõ%,øÁÍą´ŦœôŦ<:ĩĶ]ËDDž$.š.¯¨|ėŋđÁQU…ŸéÁÎŅĪd|Ŧ2ˆˆHãtŽXDDčÄ"""näō9bНēēGe%5558NœN'�ƒĄîŸ¯/žžžnŽTDDF â{ät:)ļÛÉÉΤ¤¤œNĒĢĢŠŠŠĀÃÃŖ6| üũÍDEĮˆÁ`pså""ō0QßŖššjr˛31úúßĻ ŪŪŪxzzâááqãųjjjp8dee‘“™_;ŧŧ´ÉEDä[ũ9âĖŦ,Ūúã;÷ÔvĪžũ|˛ņŗfލVuUåeÄÆÆJ@@�~~~FŒF#~~~˜ÍfBBBˆĨĸĸœę*G‹Ô"""Žfß=ģté2oŊũ. ņ­p8Ē áåæ=VįIožöõõ­;|;žžžumDDDnÕ"ĮIÛ&đ÷÷:P>ŋø÷˙$7/ĐĐPŊŋ ŖŅ—ââĻNš@˜%”÷.! ĀL~~ŗgN'8(ˆ?/\LTdžĘ-//gņ’å˜L&l6ãŸCĩ‡ËÕÔk¤¤$×­˙Ë˙x“×_û>–°0ŽŸ8Éņ'yaۜûœĨOOO<==īzŪˇŠë‰ˆČ“§E‚øō•+ŧõĮwp8؊ŠxzČ`ZEEąeëvbcc˜8n,ššy,[šŠįįĖfİĄtíŌ™ƒ‡ķ՞=ĶŗGwFš#G‘››W¯˙_í!!>žącFR`ĩō§aė¨˜L&ĻNšHZZ:ë?ų”ž}z0tđ víūšiS&ąwßƎußs4j¯Œnj+„ED¤1^>>>x{{7k§mj÷ˆķ˙ũ?éw#ķōķHKË ;;¨ 2_9Æąã'(´`6SXXHįΝ°X, úĪĪΧSĮ�„…†Rhĩ€••ßží˙T_~ņĢ7yfĖ(Ŧ……Äß8l~? ^^^w=,}sŨ[/䚊E/áõööfÜØQŦY÷1ß{ų"Â#°„†1zÔV6oŨFP *í�� �IDATBB<C dێ\Ŋ–Fpp�dŨí[…[,7–÷ 770KČëđõõĨCûvŦXĩšūOõk–ššÄ7ךU‹īĸ%÷īĪĩôtޤ^eĐĀd.]IeáâĨüīŸ‘™I‡öIėÚũ5K—¯ÄQUÍšsHHHāĐá#,\ŧ”‹—.7¸ČiČ Ž]KgŅ’å|°z Ī͜q×:† ƒ‡Đŋy‚ØÃÃPīâŗ[oāņŨpķđtŗ -""Ãüųķ™™™,X° I Žžž@LTx —Õüޤ^eûÎ]ŧōâķw]7#;īîstV“™žFûöí뮜žōōrΟ?OtlnåxFvŊē$Ũĩ>yôŊöÚkDGG?7ôØüå6Nœ<ÅĢ/ŋØl}zxxâãã‹Õj%44´îĐķ­7ôp:TUUaĩZņõ5âááI>Á$""ˇx"‚xôČáŒ9ŧYûŦqBHhEE…ØíöēĪßÜ3žųØ`0āéåEpH(N €’XDDžõDqKņôö!<" gM͍xu~›ŗ†˙q:ņôōĸēÆŠzˆˆH âûT]ãäFęÖū˙짊 ĒĒkpU""ō¨Đ[EDDÜHA,""âF.ąŅĮ‡˛ōō–¨åĄáíåEi؃ciY9F_Ÿ:ψˆ¸ŸËįˆãb"HËČÅj+n‰z ^^ž”•WPXôāæhôõ!ļÕŖ÷ųlš?.ąŲĪD§¤6-Q‹ˆˆČGįˆEDDÜHA,""âF b7R‹ˆˆ¸‘‚XDDčÄ"""n¤ q#ąˆˆˆyŲl6Š‹›~Š’Ō2Ō2s)¯¨lÁ˛DDD-fąQᘌžMZŋ¸¸›Í†Wpp0ĨĨĨM(-3?“/a!A÷ZĢ�Ųyôę’äî2DD¤™XmvŌŗķHŠmŌúģ~hēŧĸ“Ņčr"""ŗĐā@JŽ—šÜNįˆEDDÜHA,""âF b7R‹ˆˆ¸‘‚XDDčë >yę4ģŋŪãî2Üęā‘ãlÚēŗŪ˛œÜ|ūüūĘf'õZ>Û\īqccģęfGOœfãĻ/›ŖÔzĩŠˆ¸›WKtzōÔi6mūƒÁ@EE‰m˜>m*^^ž-1\=ÖB+Ÿ}ž™ysfĶ­k—{îįô™ŗ|õõ^^{õ•ēeŋûÙ6e2UoŊũ. ņ­p8Ē áåæQd/âzŸ˙ߟŪ÷\R¯Ĩą`ņJâbZÕ-‹cúäņ÷Ũws;{ūÛˇĢ÷ØnoúbZ’­ČΖíģ™1ĨvģŨZ̈ˆģ5{gegŗęŖuüôŸūĀ�3�ŸmÚLZz:­ĸ"Yŧd9&“ ›ÍÆøgÆP`-äĀĄCÄÅÅr5õ))ÉTWU7XÖŊ[WŊŋ ŖŅ—ââĻNš€ÅÆÂ÷—bĀ@Yy9¯žü"k×Bę•Ģ9zŒōŠ ōķ =rx“Æí×§w“ᙨ6ŋ˙ģ×p:üâß˙“Üŧ<|}}šu{&´‰ã/ÍŠˇėË_áááÁđ!Y¸lCS’ÖđÆëßk´Ÿ’ë×YúÁZÂ-axzÖž!*+/gŲĒu˜ũũ°ژ:a,YŲ>v’ؘVdįä1 _/B‚‚ظy+fŽ_/eîĖŠ8Žzã]NŊʨ§Õ{|ôÄi�***YšæcLF#EöbF LtT$Ë?ZáÆķķfMÃÛÛĢA=ˇJĪĘæƒ5ČÎÍcø:&%6č×ĮÛģA­Ÿnú’kiœ8}–î]:ÕĢUDÄŨš=ˆOž<Mr˙~u! 0nėh�žør ņņŒ3’Ģ•?-ø cGĀd21uŌDŌŌŌY˙ɧ$÷ĒÁ2›­ˆØØ&ŽKnnËVŽĸK§ŽthߞQ#†qū mE ĐŖ¯/Ŋ{õdĪžũ�ėüjO“Æu%ˆ/_šÂ[|‡Ã­¨ˆ§‡ ĻUTÖBkŗnĪÔ´ôz‡‘{÷čʈĄ),Xŧ§ĶId¸…vmãnÂ�ûĨK§ MéΉĶgÉ/°rũz)ƒ>E§öí8zâ4ûĨul4ū~~L;’ŌŌ2ūđįÅôéŅv ņŒš‚ÕV„‡Á@HpPŨxååxzzâååUīņM{ĻM\,Ç ¤ĐVÄĸåĢéŲŊ3Imãš2€KWŽRdˇc2­į&_ž›>™ôĖ,>ÛŧB›­AŋŨģtlPëSŊ{āëëC÷.­ODĝš˙ÕČ`Āét6úT~~>:v� ,4”BĢ €�|||¨Ŧt4ē,/?´´ ˛ŗŗo c ĀjĨsįN�´OĒŊ]äé3gīkÜo§a æ;ķ¨ŽŽÁĶÃ@Đ6ĄvØáp0˙ß˙ĶĨwE|\lƒ=b€§%ŗ`ņr~õķŸ4Š[‘ŽíģeŪ'NåôŲs؊ė˜ũũkŸ ĀĪĪTÖ[ļīæī-!,$˜ŠëīŠžģx™ö‰m<žŠĀZHŌe!ÁA؊Š(,,ĸCRm=‰ m�°—4ZĪMáaĄ�øúøRUUÕhŋŽÔ*"ō0hö‹ĩzvīÆŪ}°ŲluË>Ũø9§NŸ!Üb!ëFæææf irŋáôęŅW_y‰—žŸËœY3°„‡“•UÛߙŗßpæ›sy#p/ãļjEZZ:Gm@—”\';'‡ˆˆČzëy{{3nė(ÖŦû¸Ésš_UÕÕlŲž‹šŗĻ˛~ãMj@Á7 9yų�ėøj/mâb˜>y<“ÚÕmˇÜüÚŊú"{1fŗ?9šųŒ|z?úÁK„„säøŠz}sūb]ČßúøĻ°ĐProŒ™_PHhH0–°rrķ€Úp<éĘm뚝Æúm´Ö[ū&ĢODĝš}8""œysfņî{‹đōōĸēēší“čÜŠ#íYŧt9‹–,§¸¸˜įfΠ   Iũ˜ĖâĨËY¸x)Ev;C§0dP2‹Ū_ΟŧGYY9ßéÕUœ9{ޝ÷ėÃāa�`Č —Į fÂøąü÷īŪÂh4RYQÉKĪĪÅĮĮģÁēÉũûŗųËm\IŊJPP€ëíR¯Ĩņîĸåu?{xˆˇÜŋ}{vįō•k=qŠøÖq,[ĩ–˙đ•Fûéߡī¯XMzF&FŖ/ā¤]B<›ļîājz‘á.^I%,4„ĒĒ*Ö~ō™Y9L=‚ŌŌ2ū˛tÁA”••3cĘx mEuãefįUûåÖĮ7 |Ē7+×l`åš ””\gÚÄgˆŒ°°ōŖY´|5ååĖ›9*GUŖõÜNcũ6V+Āų‹—9pøXŖõ‰ˆ¸“aūüųÎĖĖL,XФGO_ &*ŧ…Ëzü=ŦßžtđČq mŒ1ÔŨĨˆˆ<rŽžžĐä×ö×^{čččĮûsÄ""";]:*õôëŨÃŨ%ˆˆ<Q´G,""âF b7R‹ˆˆ¸‘‚XDDč\bŖeåå-Q‹ˆˆČ#Ëjŗcö3šÜÎåĢĻãb"HËČÅj{8žYįQvôôw— ""ÍÄėg"ļ•ë÷Ųp9ˆÍ~&:%ĩqy iHįˆEDDÜHA,""âF b7R‹ˆˆ¸‘‚XDDčÄ"""n¤ q#ąˆˆˆ)ˆEDDÜČå;k•”–‘–™KyEeKÔ#""ōH2û›ˆ ĮdôuŠËAœ–™‹ŸÉ—° W›>ņ2˛ķčÕ%ÉŨeˆˆH °Úė¤gį‘ëR;—M—WTb2]m&""ōX ¤äz™ËítŽXDDčÄ"""n¤ q#ąˆˆˆ=ÖA|ōÔivŊĮŨeH3ĘÉÍįĪī¯twwtđČq6mŨéR›Ga^"Ō2\ūøRSœ<ušM›ŋÄ`0PQQAbÛĻO›Š——gK WĩĐĘgŸofۜŲtëÚåžûšté2oŊũ. ņ­q:k—M7–¤¤v\ēt™_˙×oųŲ?˙”6mZđ΂ŋ0mĘ$"##îĢūÔki,Xŧ’¸˜VuË"ÂØ>yü}õû°°ŲŲ˛}73Ļ<ķš_ÍÄYŲŲŦúh?ũ§ 0Ā Āg›6“–žN̍H/YŽÉdÂfŗ1ū™1X 9pčqqą\MŊFJJ2ÕUÕ –uī֕Eī/ÃhôĨ¸¸„Š“&`ą„ąđũĨ0PV^ÎĢ/ŋČÚõŸzå*GŽŖŧĸ‚üüFŪ¤qûõé]o.‰møûŋ{¨ øßūūmŪøŅß�ĐŗGw–­üŸüãøøx7ë6LhĮ^šSoŲ—;žÂÃÃáC˛pŲ*†Ļ ,4„%Ŧá×ŋWoŨōō –´PQQÉŧY͏pé GŽŸ$ĻUiYôīۋžŨ:7:~EE%+×|ŒÉh¤Č^ˍaƒÉ/°røØIbcZ‘“Į€~Ŋ蘔Ȋ>Æ×Į‡’ëĨŒ=ŒŦœ\=—§'ŗŸÄʏ6`ö÷ÃZhcꄱlÛŊ‡kiœ8}–íŒcĩŲęÚŋ<w&žžž”\ŋÎŌÖn ÃĶĶķļsŧų÷ö]‡ŖAgĪ]l°=cĸŖXļj]Ŋzŗsķ8|ė$1ŅQ\IŊF—Ní)-+įō•Ģŧ4w&gĪ]h°]î4nL̍ēįīw^ũžnŨ~Ķ'įƒ5 ÆßĪĖŦl~đŌÜ;ũ鉈x>ũôĶķ‹‹‹™8qb“dįY 4ûßöųŊ{÷Ũ*Šn]ŋ}‘Oj—HHp0[wė"8(ˆéĪN!)Š‹—­ }ģD ŦVæÎžELt4[ļnŖu\lƒeĨĨeøųû1kú4âÛđÁę5”––Îė™ĶąXBq8LjŠŒĀQå`ėčQ¤ĨgPZZFzfV“Æí˙Tŋēš 9ņn,3™LRTd'$8ˆüüúôîŞ}ûčÖĩ ‡ĄSĮ˜ī°mŠKJivĮík+˛ŗu×.]šĘác'9|ė$N§“”}ŲŧmEöb|}|Đ¯7&Ŗ‘ũz7čcįžũ„‡…2uÂXBC‚ŠĒĒĸŦŧĢ­ˆé“ĮŅ**‚_íʼn“/ļîäØÉĶu˙’ã9pøAL|f$‰ mø`Í'ġŽĨČ^ĖĖŠčÔžËW¯ĀĪdbĘø1´‰‹aŨ§ŸĶ:6†k!¯Ė›…Ŋ¸‹%”áCâííÍÉ3įčÕŊ Žę*†IáĢ}Œ“ßĻŽŊ‡G홓]{Á¸ŅÃpT9HĪĖϤ´´Á/\žrÛų|ˇÎ™S'4؞EEöõ††S\riĮrŊ´ {q ĮޤĀZHUU5555 ļKÛ6­)+/';'ˇÁ¸ũz÷¨û=5Įŧî´ũžÚwVQŒ=[‘ėÜ|úôėvĮŋ?š?ŲyÖģžÎß´qãFZāĐ´Á€ķæąÜīČĪΧSĮ�„…†Rhĩ€••ŽF—ååį‘––Avvöa X­tîÜ €öIĩwŦ:}æė}{'ÕÕ5øų™�p:$xŠŖĮsæė7wmëŠø¸Ø{Ä�OJfÁâåüęį?šcûÂÂ":$%˜Đ€ü‚BB‚đņöÁá¨ĸoĪîôíŲŊAûk!I‰m ÂVT@Xh0�~~&Ž_/%ŋ ĖŦlrōōÚßIízßn×§Îrúė9lEvĖūūM'¤Ūzļ";Û×Î'ėÆīŦą9ÆÅD7:ŸÆę4 ļįíę ēąGęíåE`@@ícooGŖÛåNã6įŧîļũlEEtl߀č¨HŽ<Ķ qŋfâžŨģņû˙ũƒS’ Ž}útãį$$´!Üb!+;čAnna–;wv‹ˆđ,ĄaŒ5‡ÃAA•ã§N“••M¯ŨkÃđÆ ėwßÜΏ7X­?q’1Ŗ†SP`­[ūüÜįøŨ[oßöđasŠĒŽfËö]Ė5•õŋ`îŒ)ˇ]×BNnŨ:wāÜÅË āσGsüTũ7.ŗ§M$,4”Üá‘_PHhHíī17ŋvŪEöbĖf,aĄ„†3lp2UUUڊHĪĖææh;žÚK›¸ôëÍW{’–‘YīÚíÆųnĩAÜxķt3Ô›c‘ŨŪč|ĢŗąíŲhŊMđŨíōíīĄá¸Í9¯ģm?ŗŲL‘Ŋ€Ėėœ&ÍEDŧf∈pæÍ™Åģī-ÂËˋęęj:´Oĸs§Ž´KLdņŌå,Z˛œââbž›9ƒ‚‚‚&õ;h`2‹—.gáâĨŲí œÂAÉ,z9Zđeeå|˙ĨpTWqæė9žŪŗƒGíKԐA)÷4îå+WxëīāŦŠĄĒēšW^~āāāzA`63eŌx~÷‡ˇyn֌{Ûhߑz-w-¯ûŲÃÃ@D¸…äū}čÛŗ;—¯\ãč‰SġŽcŲĒĩüø‡¯Ôk? _oV~ô1‹–¯Ļŧĸœy3§qéĘÕãôëՃ~Ŋz4X>đŠŪŦ\ŗ•k6PRriŸ!7/ŸĒĒ*Ö~ō™Y9L=‚¤ÄxVŽŲŠÕëą—0°ßzũ´KˆgĶÖ\MĪ 2ÜÂÅ+ŠôęŪ…ķ/sāđąFĮąÖ“­ČÎ{KVō~üCú÷íÅû+V“ž‘‰Ņč 8cûĄFį“ܯWƒ:ŋŪ¨ÁölŦۈË]_ßŨ.Ĩeeˇ×ÛÛģŲæu§í0 o/–­ZKzFŪŪ^Üæũ˜ˆ¸™aūüųÎĖĖL,XФGO_ &*ŧ…Ëz<=Ę_úpđČq mŒ1ôšáŗÍL7úw/îeģ<¨yŲ‹ąŅ&.–ŗį/râôYfMmÚĩ "roŽžžĐä×ų×^{ččč–ųø’ČũĒŠŠaøāî.ŖŲ=Čyy{yąņ‹­ø™ü(¯(gú¤qd\q‚XšäÖĢ}Zøŧ{spuģ<Čyųų™øÛīŋø@Æ‘{÷XßYKDDäa§ q#ąˆˆˆ)ˆEDDÜČå 6úøPV^Ūĩˆˆˆ<˛Ŧ6;æw_t…ËWMĮÅD–‘‹ÕVėō`Rû3yü˜ũLÄļrũ>.ąŲĪD§¤6.$""" éąˆˆˆ)ˆEDDÜHA,""âF b7R‹ˆˆ¸‘‚XDDčÄ"""n¤ q#ąˆˆˆš|g­’Ō2Ō2s)¯¨l‰zDDDIfąQᘌž.ĩs9ˆĶ2sņ3ųäjSy@2˛ķčÕ%ÉŨeˆˆ<QŦ6;éŲy$ÅĮēÔÎåCĶ啘ŒFW›‰ˆˆ<ÖBƒ)š^ær;#q#ąˆˆˆ)ˆEDDÜHA,""âF b7zŦƒøäŠĶėūzģËhV™YYŧõĮwÜ]†ˆˆ4—?GÜ'OfĶæ/1 TTTØ6éĶĻâååŲÃÕc-´ōŲį›™7g6ŨēviņņUŠ×ŌX°x%q1­ę–E„‡1}ōø&÷át:Ųwđ›ļîdÎôÉtHJ¤ŦŧœĢ×PZVÎØC ˇ„ņÁÚ xyzRQQÉĐAÉtīŌ€ėÜ<~ķÖģüßú6[Ë>\GTD8�=ēu&š_īfœĩˆČÃ§Ųƒ8+;›U­ã§˙ô˜ølĶfŌŌĶiÉâ%Ë1™LØl6Æ?3†k!"..–ĢŠ×HIIĻēĒēÁ˛îŨē˛čũež—0uŌ,–0žŋĘĘËyõåYģūR¯\åČŅc”WTŸ_Āč‘Û4nŋ>ßžčŊwûĸu\,™YŲ NIĻC‡$Ū[¸„€�3ųųĖž9gM ë7|Š9ĀLIq ¯ŧü"…Vkƒeŋũũøˇ˙ûĪX ­üķŋÎį÷ŋ}///~ķÛßķūáĮ ęˡZŲŋ˙ ž^^Ė›=‹…K–§WķũĘÚÄņƒ—æÔ[ö厯đđđ`ø,\ļŠĄ) aÉkxãõīÕ[ˇĸ˛???:ĩoWˇ,3+‡žŊzĐŗ[gŌ3˛ølË6&Ãŗ“ÆnáüÅËė9p˜î]:RUUÅĮŋ ĄM�ĨeeôëŨƒņŖ‡7ÛEDvžO?ũôüââb&NœØ¤ŲyVÍūˇ}~īŪũDˇŠĸ[×Îu˒Ú%ĖÖģ búŗSHJjĮâe+hß.‘Ģ•šŗg͖­ÛhÛ`Yii~ū~˚>„ø6|°z ĨĨĨ„‡‡3{æt,–PŽ*ĸ"#pT9;zié”––‘ž™Õ¤qû?՝޿ôŒ m6^˜û]:wbáûKčŲŊ;‘áŒ5oŽ8Ũn'ĖÆŦéĶh×.oo>Ü`YvvĄĄ!\¸xo/oŒF#Eļ" Y9šÖ—›—ĪßūđUļīÜELL4S&MĀQYÉĩ´tÜRëw—”Ō*"ėŽŋG[‘­ģöpéĘU;Éác'q:¤ čËæmģ(˛ãëãÀ~Ŋ1 hdĪÔËˋ¨ˆpN=GĢČ,aĄ„†YģGûÕžƒ´Ž‹ĨSûvøúú°`ņr;ÁŦŠ“0ûûņņ_7Ķŋ_/2ŗsčÔž9šy?u–ķ—Žpčč "#,uoæDDŲyÖģžūŪ´qãFZāĐ´Á€ĶélôŠüü|:uė�@Xh(…V�!!!�øøøPYéhtY^~iidgg߯@ÕJįΝ�hŸT{KĮĶgÎŪ׸ˇ ˇX�0›ũ)).Å×Į‡ÃGŽqėø mE˜Í 6”ŋ~öŋųŸˇˇ„1{æŗ.ëŪ­ ß|sŽÔkט8ávė܍ˇŊzug˙ÁÍÖnŠũeŌšsíˍššC|\lƒ=b€§%ŗ`ņr~õķŸÜSŋ•ë7n":*’OõĀÛˋŋųŪ ¤gfņÁÚ Œ>OOÚ'&°kĪ~�:uH"1!KX9šųŧŋō#~úÆë÷<?‘GAŗ_ŦÕŗ{7öî;€ÍfĢ[öéÆĪ9uú á Y7‚4770KH“û WîŧúĘKŧôü\æĖš%<œŦŦÚūΜũ†3ߜÃĐȁ{7'7�›ÍF@ ™Í[ˇ‘Īŧ9ŗéŌš#Nœdeå0î™Ņüôß ,$”7ēŦSĮœŋx‰ŠŠ bĸŖÉË/āZz:mÚŪž>ƒ€āā �ęÖk)UÕÕlŲž‹šŗĻ˛~ã.ˇw8ŧŋb5Éũú08ų)�ļîúšCĮN�HYY9GŽŸÄVdgŲ‡ë¸––Áú›ČĘÎĄĸ˛�?“‘šššæ›˜ˆČCĒŲ÷ˆ#"™7gīžˇ///ĒĢĢéĐ>‰Î:Ō.1‘ÅK—ŗhÉrŠ‹‹ynæŒē€š›A“Yŧt9 /ĨČngč↠JfŅûËųĶ‚÷(++įû/Ŋ€ŖēŠ3gĪņõž}<jƒlČ ”{×áp°bÕjŌĶ3˜:i|˛ņsRSS‰ŒŠâÜš ´i͚Ö}LhH0ĨĨeĖ›3‹ĖĖ,ū÷OގĖh4RYYIۄx�‚‚ŠrTááa¸k}ƒ’“yįŊ…\Ŋz ŖÉxÛ#ŽJŊ–Æģ‹–×ũėáa "ÜBr˙>ôíŲËWŽqôÄ)â[ĮąlÕZ~üÃWęĩĪČĘæ¯_l##+›ĖėŽ<ƒ%,”œŧ|6}šŖvž<3j+?ú˜ŖĮOQZVÎÔ céØ>ąŽŸŋ,[ÅÔ cŠŠŽaõĮŸbôõĨ´Ŧœg'=Ķ,ķy˜æĪŸīĖĖĖdÁ‚MjpôôbĸÂ[¸,÷Ûŗo?ųųLš0ÎŨĨ¸Lßž$"âGO_hōëīk¯ŊFttôãũ9b‘‡]‹|Žøq0p@w— ""O�틈ˆ¸‘‚XDDčÄ"""när}|(+/o‰ZDDDYV›ŗŸÉåv._ŦAZF.V[ąËƒÉƒsôôw— "ōD1û™ˆmåúĮ{]bŗŸ‰NIm\HDDDŌ9b7R‹ˆˆ¸‘‚XDDčÄ"""n¤ q#ąˆˆˆ)ˆEDDÜHA,""âFúDyb9Nl6ÅÅÅTVV’››KUUŅŅŅxxxāéé‰ŋŋ?AAAx˙˙ėŨwtT÷Ŋ÷û÷¨L‘FB$�!ŠM3U4Ķ1BŗÁŊ`''9~žÜ“•gŨœ\žsœuNrī“œœâÅĄJƊƒƒ .`SŒ#zĮX؍Ōh4ԌęÍũCxmŒäĄ|^ky-͞ũũũž{ãĨöė={‡†vH~ąģŽ[I9 MŅˆˆČ÷ÂëõRQVBĩŗ ŖÉŒ!Č@uU^¯—*W=ā/47{Āë%!ą ÖȨëŽg ˇœÅlōĢŋƒØVRN˜ÅDlt'Kå&ŠË*Ü?=ĐmˆˆÜÜn7õՌ͜†ÉdÂ`0`0|ī{Ŋ^ŧ^/—.]ĸ´´”¯ŋūšôûbĩZ¯9žÃYCQYéŠÉ~õá÷9â†Æ&,fŗŋe"""wģŨN||<fŗ™āā`‚‚‚|al0|MFbbb0›Í4Üāéƒ1Q‘¸këũîCk‰ˆČ}Éãņ`6›¯: žŖŅHHH---íŪ‡‚XDDîKÕÕÕÔ×ס â?˙ųĪüųĪžj™Á` ˛˛ˇÛŨî}čĒiš/Y,Ėß:Õē~ũzļnŨ @cc#O<ņĐÄ„„´l*ˆED䞁ŅhôŊÎÎÎfëÖ­ŧōĘ+�üâŋĀ`0đøãĐŠS'ÂÃÃÛŊ}4-""÷ĨoŨķĘ+¯ššJjj*¯ŧō Åįõį�� �IDATÅÅ@ëÔß\ĀÕŪîē >pđ7Ŋč6|Nœ<Åg{öē ņ“Ņh¤ļļ¯× ĀĪūsRSS}ī§ĻĻōķŸ˙hŊ°ĢšššCnęŅîMˇ´´ŗ!gu Ą!!¸ÜnæÎžEFī^í=ÕáÁú¯ķÚČZŗ”¤.že ņą,œ;ķ–Įøęü˛˙üWâø`?† HΟ˙Jss3MM˟5Ž‰ÛŊ‘;Edd$EEE8ŦVĢīų›‹ˇžųqCC„††ļ9§ÜÚ=ˆmļ"UN^ūņ°WVRPPHcSĢ×fc6›pšÜ˟3‹Ø¸^[ĩŽˆ+v{%K/¤¸¤„ŧŧ‡„đÔãËY—“ƒõ <˙ô“�؊Xŗ>‡’ŌRĻO™ĖCCûæßķų>ōö¤[J2%ĨeŒ=ŠŒŒô6ķtęÉĒ5뉋!ÜŽÍVĖK+žkĶcJĘßž˜}áB!›Ūyk„ˇËÍ3O?ÉÉS§°Û+‰‰áäŠĶ�;q‚˙ų÷GJJĘ ĮûŽŌē§đÂS]ĩė“ģ bâ¸LVeį2~ôHbcĸY÷ÆF^~éŲĢ̝֭gؐĖœ:ҎloŪA;'0}Ōx*ė•üåí÷ųásOÜv¯""w*ŖŅHrr2‡ƒŌŌRßí./]ēDll,ĐĘ!!!„……ŅĨK—ģãb­ŽI] eõēlzöHŖoFCĖĮÛvœœÄėĶ)/¯ {C.?ļ”IÆķ@˙~8xˆŨ{÷’Ūŗ'&ŗ™—^x–ļ|LFīŪL™4/ķķŠrV`2™xú‰å\(´ņö웝 bƒÁ@xx ĖÃíŽå7ŋũ÷âŠ6ķtęԉ>Ā䉺gī>ŠŠJØõŲž6=ūôåûÆ>yú4éŊ{1cÚTė•‚ޏä}tæHFgŽd×g{ˆėI¯ž=¯šÍWŽ÷]؊øãÚ ž×C>¤ņŖÉZķ:^¯—Îņqôę‘ Đ&„ęęęÉ˙ę<ësßÂãņ0}ōÕ0đÁ~�ÄĮÅRQé¸í>EDîtqqqDEEŅÜÜLKK —.]ĸĨĨ…ĐĐPߍ=‚ƒƒ šĨīíÄĄ!!üčÅįqšŨœ?_Āûm%8Č@Hh6[1eee@k`šŒF>ĘŅcĮŠrVqųļaņq­‰T:ôë×€Ūé­ˇ~Ŧ°Wų#UŗÉ„ĮãiĶC|\�Vk8nWŨ5įŠrTŅŋëĮĘÉI]9xøöŠ6=^iâ„ņŧ˙Á~ķÛßËŌÅ?¸ęũüüs9vœ˙p…¯×÷]ĨĻ$ˇ9"xxĖ(˛ÖäđĢ_ūė†õ}3Ō陖J\l4ËíŦŨđē§$}k-oģô*"r'ķzŊ¸\ŽģëĄ7súˏ\.F ƀ =Ŋ'˙¸ōĻOB\L,S§LÂãņPYé`ëļí¤ĨĨ2nL&Ûw~ʅB[ë —+.>žŌŌ2Āé3_ø–ßĖÅō �œN'‘ÖkÎIĩĶ @QI � ņ mzŧRiéEf<2•0Kīŧû>û÷ÂhjŊôŨ^YÉ[oŋÃß˙Ũ}WÕŨlŧöÔ|éīØÅ˛%ķŲ´y ËÍģîēåv,–Öķa3---tī–LAa}{÷ĸėb9‰ ÖĢˆČĀëõræĖĘĘʈŒŒ$88˜ęęjZZZpģŨxŊ^ZZZ(**ĸšš™ŪŊ{ĶĨK—›ė§vâÔÔndįä˛w_ĄĄĄ466ąlébúöéÚõ9ŦZŗžęšƏMFītŪŨü!tNLäėŲ|ē\qĐ¸1ŖXŊ6‡Wŗ^Ŗžžįžz‚/Īģi‡×sߤ¨¨˜ųsfÔfžå˖ōūqĄĐF¨1ƒÆdŽjĶcâũÔÖÖō¯ū‘˜č(ęęęYūØÎ~™@Î빀œ7ZīÆ2bØĐ›Ž÷]ÚøÃęßë   ņqŒņC āëķ…9~’Ôn)dįžÅO^|æĒúN‘‘ŧųö{˜M&ęęøÁœGč™Ö×ß|›?­§™sší>EDîdĩĩĩTUU1räHßC ä{˙Û}øâ‹/ˆˆˆ¸îCž+Ãʕ+Ŋ%%%deeŨRÁ‘Sų$%Æˇkíiīž<ėöJæĖšqÃõœN'•UUôLKãäŠĶ>r”'–ˇũ¸÷û¤§/‰ˆ| hjjĸW¯^ņ¯˙ú¯,]ēÔ÷Ļ‚‚rssųųĪŽËåâäɓ¤§§wųôįĩ9•ËŋĮWŦXA׎]īß;k…„„˛éí÷§žžžĮ–, tK""ō=úöC’’’øÅ/~q՝ĩĻM›tėCîš Î9â–ÖŗZÃųŋūĮO:¸šSUWWá âĮ¯×Ë/~ņ �ĻNęģŊå7}ˆ%Ą¯Ąšį‚XDDäV\ëĄO<ņ&“ €%K–ø–ëĄ"""íėÛ}øÆ•|%=ôADD¤ųsṫ>ˆˆˆ´ŗo?ôáF:ōĄ~ąŲh¤žĄĄŨų>EFFâršp8466réŌ%ß-.ŋšŨeSS555”••ŨôĄg Ö0‹ß}ø}Ž8%)[q9§ËīÉäæŽœĘt ""÷…āā ‚ōŋ* ĄĄ¯×KSSŪ–Læ0āo}Įhļp*ŋ€––kA[Ã,$wņ˙>~ą5ĖBßôî~O$""r'jnnžˇú ""r7 鐯%Ũ*]Ŧ%""@ b‘�R‹ˆˆ‚XDD$€Ä"""¤  ąˆˆH�ųũÅ)w]=ļ’r›:ĸ‘ģ’5ÜBrb<ŗÉ¯:ŋƒØVRN˜ÅDlt'Kī9Åe îŸč6DDäāpÖPTVAzj˛_u~4ŨĐØ„å7ŊšÅDE⎭÷ģNįˆEDDHA,""@ b‘�R‹ˆˆ‚XDD$€îē >pđ7ŊsŨ÷÷îËãŨÍ|Ũß.–ÛųãÚ nã†>ÆGÛ>õĢænØ.š7´û“[ZZČؐ‹ŗē†Đ\n7sgĪ"Ŗw¯öžęŽPPh#kÍR’ēø–%ÄĮ˛pîĖ�v%""wŠvb›­G•“—üC�ė••ŌØÔÄęĩؘÍ&\.7ķįĖ"6.†×V­#"ŠŨ^ÉŌÅ ).)!/ī�Á!!<õørÖåä`Ā@}CĪ?ũ$�Eļ"ÖŦĪĄ¤´”éS&ķАÁWõđeū9Öeo ¤´„éS§‘‘ŪfoK ›Ūyk„ˇËÍ3O?‰ÉdlĶcJŠ_Ėž–´î)ŧđÔcW-ûdįn‚‚‚˜8.“UŲšŒ=’ؘhÖŊą‘—_zöĒu›Ø°ņm,f3Õ5.ĻLKšŊ—Ë͔ c))ģČīāšĮ—^sū}´YwúÄņlŪē kx8ĩĩu,[<“1”×˙ō6&Ŗwm3ĻN ôb9'$8˜§—-&88wm-ëßx‹ø¸X‚ƒƒhhh$į/›0\îwų’DFX¯ŲĮãi3Ī™ŗįÚė¤Ž‰dįūkxŽ*'ķgM§Ŧŧ‚CGOÔ5‘ķ…ôīۛēúž>§–-æĖŲ|=ArRĘ.V0rØāΛÔ%Ņ÷ūín×ĩūN§o˙-œ;“76žCLLáaa””–ņÂSËnø˙ŽˆÜû‚~øá•.—‹ŲŗgßRAY…ƒHkøuߡ†sôØqNž:MËE\L,éé=Ųąsaáa,Y¸€´ÔîŧņæFúfdĐ9!žiS&c4†rôøqâcc)¯°ķŖŸgĮÎO‰géâ…ÄÅÅāņ4SWWGyyĪ?ûŨģucë'Û9|˜o~[Q1v{%Ī?ûú÷gÍú ĐfžššbãbY˛pŊzõÄʞŊûÚô˜9rÄuˇÕåŽŖKBė ÷—ŗē†mģöōÕų :z‚CGOāõz=r([ˇīĸ篅Éhdä°!XĖfFŌfŒŨûöĶ)2’ŲLĻgZwŪØø.Ũ’ģŌÔÔDĪ´î¸Üĩä]@‹ˇ…-Û>åč‰Sž˙Ō{ĻRn¯lŗŽĮĶLlt4sgL%-ĩƐō!ĖbaŪĖitOIâ¯ī}Hˇä$*U<ŗ| AA­g2víŨObįfL€§ŲCQIîē:âcc˜?k:1ŅQ477“˙õųköŗ˙ĐŅ6ķ,ž?ĢÍū¨ŽŽ!..†‰ã2 åÄéŗÄDGárײ`ötjëęŠqš™=}2•Ž*š›/ŅŌŌBu‹ÅķgҎw/rŪÜDîŨ¨oh ėby›y‡ čÛĪíą]ßūwę™ÚŨˇ˙vī;@—ÄfNˆŗē†˛r; zđ†˙˙ˆČŨĨŦÂqĶ\øÆæÍ›‰ˆˆh˙#âА~ôâķ¸ÜnΟ/āũļd $4›­˜˛˛2� &Ŗ‘C‡rôØqǜÕDX[4âãZ7ĸŌá _ŋž�ôNoŊ•d…Ŋ‚ø„x�Ė&§M].åtŠę„ËU{Íy&NĪûlá7ŋũ=ņqą,]ü*ėmzlŠ)ÉmŽˆ3ŠŦ59üę—?ģa}ĨŖŠôž=�ˆŽę„ŗēúšë 4€ĄƒÜROc3‡ķņŽĪøĪ×ÖÅüŲĶąWVQRZÆÅ ;đˇí‰žĒÖY]CŸŪ=[ߋn}¯ĒǚŒôÖe=Ķ琒Ôõšũ\kƒÁĐfFŽŸ<Ê3gqV×` oũ°Ķå#ŌА"#"Z õũŋ@X˜…ÚÚēÎ۞ÛuŊ§oöŸŗēš>—OŅtMėĖŅ§ÛŒ!"÷ŸvâĶgžĀår1bø0<ø�éé=ųĮ•¯0}ęâbb™:e‡ĘJ[ˇm'--•qc2ŲžķS.ÚZšü 2.>žŌŌ2Āé3_ø–ßLyy�ÕÎj"#­×œ§´ô"3™J˜%ŒwŪ}Ÿũû‘ŸĐĻĮŽŌ|éīØÅ˛%ķŲ´y ËÍģîēą11”_{e1ŅQCC|ÁSålũ…āČ1Ž<sUíŌŗ¯šîÅr;“ƒÅlæÃOvrøØIâbcˆ‰ŽbÂØQ477SåŦύ¤ŒoīõN‘T:œ­ã\î+.6š‹å<Ø/ƒŗįžÆ`0P]SsÍ~Ž5ĪĩöĮÎŨŸĶ=%‰‘ưûķ؊KniߖÛ[˙ŨĒk\X¯øôæZķļįv]ëß đí?ĢÕJu €’˛‹ˇ´-"rīk÷ NMíFvN.{÷åJcc˖.ĻoŸ>ŦYŸÃĒ5늎ŠaüØŅdôNįŨÍRPP@įÄDΞͧKbgßXãÆŒbõÚ^Ízúúž{ę ž<wî†ķ{[ŧ\ēt‰×sߤ¨¸„ųsgc0ÚĖĶŊ{7ūōס‰‰ŽĸŽŽžå-!<,ŧM‰Wôķ]ÚøÃęßë   ņqŒņC āëķ…9~’Ôn)dįžÅO^|æĒúĖáCذņ6l|ˇģ–ŗ!&Ēģ÷äŊ>iũEīõ2lđ@† ȡĨ÷Hkŗn]]=ZŸKT§HęëX4o&áa6l|‡×ßÜDËMæˆĄž1œÕ5ŧļn˙đ“1t0k_oŨŋfŗ đ2rØ6üåmVįŧICcË/ wĪ´kö3jØā6ķėÉ;ØfôJKåŖm;šPTLįø8Ν/ !!îĻûģšš™ˇŪũ€’Ō‹Ėš:‰ēúúëÎÚnÛu­'G•Ķ÷ūČĄƒÉÎ}‹ĸâRBCCnõīJšĮVŽ\é-))!++ë– ŽœĘ')1žƒÛē;ÜoO_zįƒ­Ė15ĐmÜЁÃĮ¨Ŧr2}Ōø[ŽųžļĢ篅ŗēšî)ɜųōĮOaÉü[ģ6CDîGNåßr.ŦXą‚Ž]ģļ˙ąÜ›ZZZ˜863Đm´ģīsģBCBØŧea–0X8gÆ÷2¯ˆÜŲÄrK‚‚‚ˆ¸Î×vî$W^}+žĪí ŗđŖįžü^æ‘ģĮ]wg-‘{‰‚XDD$€Ä"""¤  ŋƒØl4RßĐĐŊˆˆˆÜĩÎŦaŋëüžj:%)[q9§ËīÉîEGNåēšXÃ,$wņ˙>~ą5ĖBßôî~O$"""méąˆˆH�)ˆEDDHA,""@ b‘�R‹ˆˆ‚XDD$€Ä"""¤  ŋočáŽĢĮVRNCcSGôs×ûē €ŧŧũÔ74ē�,fK~0‡‡>čVDDäüb[I9aąŅ:ĸŸģ^Ά\š<ž@ˇáSßĐȟßzWA,"r‡ōûŖé†Æ&,fsGôrO¸“BøwĘŅšˆˆ´ĨsÄ"""¤  ąˆˆH�)ˆEDDHA,""@w]8xˆ›Ū t"""íÂīīßLKK 9rqVׂËífîėYdôîÕŪS‰ˆˆÜõÚ=ˆmļ"UN^ūņ°WVRPPHcSĢ×fc6›pšÜ˟3‹Ø¸^[ĩŽˆ+v{%K/¤¸¤„ŧŧ‡„đÔãËY—“ƒõ <˙ô“�؊Xŗ>‡’ŌRĻO™ĖCCûæŋpĄMīŧ‡5ŠÛåæ™§Ÿ$4$˜Uk×_5ŽÉlbÍē, N§“™LŖÜn'o˙AēĨ$SRZÆØŅŖčׯo›žSR’okF–-šK]}"#ųdįgDuęÄCƒ ¸¤Œä¤Žä:Âé3_ōØây456Î[ˇSRvņļæ‘;Kģqפބ††˛z]6={¤Ņ7#ƒĄ æãm;HNNböŒé”—WŊ!—Į[ʤ ãy ?<ÄîŊ{IīŲ“ŲĖK/<ËG[>&ŖwoĻLšĀ—ųųT9Ģ0™L<ũÄr.ÚxûŨÍWņÉͧIīŨ‹ĶĻb¯td0đéŽŨmÆ9{îiŠŠLŸ6™J‡ƒWŗūĤ ã cá‚y¸ŨĩüæˇŋÃ^éhĶ÷O_ūņmíŖĖáC((,fĮg{‰ęÉ3˗°}×ęëøāãtMėĖĖi‰ŠŒ ¤ô"¸Øh͛ůĘ`ÄĐÁ,_<€ŧƒGČysĶmõ$""ŅîA^|—ÛÍųķŧ˙ŅV‚ƒ „„†`ŗSVV€Á`Ād4rčđQŽ;N•ŗšĢ€ø¸X�*úõë @īôt�*ėÄ'Ä`6™đ|ëNV'Œįũļđ›ßūžø¸X–.ūÁ5ĮŲŗo}ûd�C•Ãyyî8�ŦÖpÜŽ:*ėmúž]ą1Ņ|yî<�Îęĸ:Eú~†Öģs…†„CR—ÎtŽoíÉëõúÆČ;x€ôŠ a‘ģXģņé3_ārš1||€ôôžüãĘW˜>u q1ąL2 ĮCeĨƒ­Ûļ“––ʸ1™lßų) m­ƒ\쏸xJKË<p�§Ī|á[~#ĨĨ™ņČTÂ,aŧķîûėßčšãÄĮÅQZV ¤ŧŧ‚ظh�.–W�āt:‰ˆ´’ŸĐĻīÛe¯ŦĸsB'Nˇ†˛ŖĘyõ8ǜėÜũ9!ÁÁDëūŪyøYDDîNíÄŠŠŨČÎÉeīž<BCCillbŲŌÅôí͇5ësXĩf=Õ55Œ;šŒŪéŧģųC 蜘ČŲŗųtIėėkܘQŦ^›ÃĢY¯Q_ßĀsO=Á—įÎŨpūÚÚZūãÕ?E]]=Ë[BX˜ĨÍ8=RSYŗ>‡Õërpš\<ēxe/âņxx=÷MŠŠŠ™?g}22ÚôxEßÅįûņčĸš<ēp.ÖđpūúŪGÄ\ã!ûæŅEsYļh‘ėÉ;H…ũö˙‘;‡aåƕے’˛˛˛nŠāČŠ|’ã;¸­ĀØģ/ģŊ’9ŗf|į1ūų×ŋmĮŽÚĪüæŸŨ‚ˆˆ\aŊtíÚõîûąˆˆČŊ¤Ũ?šž›eŽčDDä>Ŗ#b‘�R‹ˆˆ‚XDD$€Ä"""äw›Fę:ĸ—{Bhhh [hÃl6ēšŋ¯šNIJĀV\ŽÃéęˆ~îzŖ3G˛/ī� wČ+fŗ™Ĩ?˜č6DDä:übk˜…žéŨ;ĸ—{Âāūéü`æ”@ˇ!""w # ąˆˆH�)ˆEDDHA,""@ b‘�R‹ˆˆ‚XDD$€Ä"""¤  ŋīŦåŽĢĮVRNCcSGô#""rW˛†[HNŒĮb6ųUįwÛJĘ ŗ˜ˆîäoé=Ŗ¸Ŧ‚ÁũĶŨ†ˆˆÜAΊĘ*HOMöĢÎīĻ›°či>"""W‰‰ŠÄ][īwÎ‹ˆˆ‚XDD$€Ä"""¤  ŋ¯š´qÁVÄÂųs¯ųžĮãá_˙ßß2bØPētIÄY]Mpp0v{%sfÍøžģmŖ˛ĘÉôIã}ËΜ=GĩËÅČĄƒ;|.i_íÄ---älČÅY]ChH.ˇ›šŗg‘ŅģW{OuM••"ŦVĻN™ä[ļw_�Ž*|¸•å-íđ> md­Ų@JR߲„øXΝŲîsõÍđoß:ĢkøxĮg,š×Ú˙ŗįØļkAMM¤vKfÎ#SÚŊĪ+]ēt‰Ũû’Ū3•Îņqwč|""wĒvb›­G•“—üC�ė••ŌØÔÄęĩؘÍ&\.7ķįĖ"6.†×V­#"ŠŨ^ÉŌÅ ).)!/ī�Á!!<õørÖåä`Ā@}CĪ?ũ$�Eļ"ÖŦĪĄ¤´”éS&ķАŋ nũdEÅÅlßų)fŗģŊ’¸¸X�ŪÚô.į/pøČQú÷ī×ϟĸ+æ~é…į šŊpHëžÂ O=vÕ˛Ovî&((ˆ‰ã2Y•ËøŅ#‰‰fŨyųĨg¯Zˇąą‰ ߯b6S]ãbʄą�|}ūūë{”^,gŌøŅ444RYådŌ¸L^˙ËۘŒFÜĩu˘:Øčhrū˛ Ãåņ–/YĀ{}BĄ­˜ã§ÎĐ9!žM›?â'/>ƒ5<Ė×cqi™¯ú†˛s˙Š5< G•“ųŗĻãõzŲŧuÖđpjkëXļx>Ngu›eߌųmģöîįØÉĶ|]PȈĄƒč—Ąīe‹ČũŠŨƒ¸kRWBCCYŊ.›ž=Ō蛑ÁЇķņļ$''1{ÆtĘË+ČېËã-eŌ„ņ<Đŋb÷ŪŊ¤÷ė‰ÉlæĨžåŖ-“Ņģ7S&MāËü|ǜÕ�˜L&ž~b9 mŧũîæĢ‚xō¤ T9Ģ™øđxߑđ72GŽĀl21dđ kö3nLĻoîöP`+âk7ø^ø�“Ə&kÍëxŊ^:ĮĮŅĢG*@›øüĀ!ē§$3q\&UÎjVįŧɸĖá˜LF–,˜MËͯĘfâ¸L�öî?D×ÄÎL8{Ĩƒ7ßŪLFzOŌ{¤2~ôHž:ęš†ˆÉdd@˙žėøės† xU`N~x �Ëí�ÔÖÖ16s8}{÷âČņSė;x„Č+ŊŌR™4~4g5Agž<×fŲÁŖĮ9qę‹ĢļkŅŧ™<دöJMMôIīŲ.û[DänÔîA^|—ÛÍųķŧ˙ŅV‚ƒ „„†`ŗSVÖz¤e00:|”ŖĮŽSåŦ&Âj ūōlĨÃAŋ~}čŪzÄTa¯ >!�ŗÉ„ĮãųN}VØ+ÚôsåÜí!5%šÍ1ĀÃcF‘ĩ&‡_ũōg7Ŧ¯tT‘Ūŗ�ŅQpVˇū!’xyû##Ŧ¸kk}ëÛ+Ģ()-ãbEk€ ĒĒĒɸt=Ķēp6˙+_Á` ÅëŊaFŖ‘ã'ĪpęĖYœÕ5XÃÛ9œw|ÆžļŽØč(æĪž~ÍeC `č mÆ4›LĖ~d2Í͗ Ō5ƒ"r˙j÷ >}æ \.#†cƒžŪ“\ų ͧN!.&–ŠS&áņx¨Ŧt°uÛvŌŌR7&“í;?åBĄ­uËĄOiiƒāô™/|Ëŋ+ƒÁ€÷rč$Ä'´éĮVTtÛsÜLķĨK|ŧc˖ĖgĶæ-,[4īēëÆÆÄP~9Tí•UÄDGų~¨qš}ŧ�ÄÅÆńąŖhnnĻĘYÍŠ/žäbyöËāėš¯[˙ā¸b?<С7X“ÃČĄƒéĀ–mŸŌ-%É7îÎŨŸĶ=%‰‘ưûķ؊K¸XngōÃc°˜Í|øÉN;Ir×.m–Ą;yæĒíZē`6ÖđpBBBĀŋ[˛ŠˆÜsÚ=ˆSSģ‘“ËŪ}y„††ŌØØÄ˛Ĩ‹éÛ§kÖį°jÍzĒkj?v4ŊĶywķ‡Đ91‘ŗgķé’ØŲ7Ö¸1ŖXŊ6‡Wŗ^Ŗžžįžz‚/ĪûÎŊ%$ÄsúĖYöėŨĮ˜ĖQmúio…6ū°:Į÷:(Č@B|ŖF<ÄĐAøú|!GŽŸ$ĩ[ Ųšoņ“ŸšĒ>sø6l|‡ ßÁíŽeÁėG¸X^AķĨfŪz÷JĘʙ9m"uu­ˇT5l06žÃëonĸÆå&sÄPF†ŋŧÍęœ7ihl`ųâ\ēt‰/Ī}ÍūCGūĐ ͛Åē !8$˜–K-ôę‘JF¯:z€^iŠ|´m'ŠŠéĮšķ¤$uåŨ?&ĒS$õõ ,š7“˛‹üi}îUËĸŖ:1lđĀvߡ""÷ Ãʕ+Ŋ%%%deeŨRÁ‘Sų$%Æwp[wŖŪ �� �IDATļ;íĄûĨĒē†iĮē‘ûڑSųˇœ+VŦ k׎ēĄĮŨŽŧĸ’O÷ä‘ÖÍŋ§}ˆˆČáŽģĄ‡\-!>–øÉŠ@ˇ!""ߑŽˆEDDHA,""@ b‘�R‹ˆˆßAl6Šohčˆ^DDDîZg Ö0‹ßu~_5’”€­¸‡Ķå÷d÷’#§ōŨ‚ˆˆÜAŦa’ģøŸ ŋƒØfĄozwŋ'‘ļtŽXDD$€Ä"""¤  ąˆˆH�)ˆEDDHA,""@ b‘�R‹ˆˆ‚XDD$€üžŗ–ģŽ[I9 MŅˆˆČ]Én!91‹ŲäWßAl+)'Ėb"6ē“ŋĨr›ŠË*Ü?=ĐmˆˆČ58œ5•UžšėWßM746a1›ũ-š§ÅDE⎭÷ģNįˆEDDHA,""@ b‘�R‹ˆˆ‚XDD$€Ä"""ä÷÷ˆoæÔé3ėŪķ9+žÆˇėw˙ūŸ,˜7—îŨRÚ{ēģŽŖĘÁneųcK;dü‚BYk6’ÔŎ,!>–…sgvČ|""r{Ú=ˆoä…B6ŊķÖ+n—›gž~“ÉČęĩؘÍ&\.7ķįĖĸ¨¤„ŧŧ‡„đĐāA<|˜””d.2zô(ú÷ëÃkĢÖaÅn¯dé⅔””’wā ŨR’É?÷|€ēúzžĖ?Į‹/<‹Ųln3OJĘßžtŊįķ}äío­/)-cėčQ446úúxúÉådįŧÅbÁét2ķ‘i”Ûímjúõë{Ãí1šŒrøČQŪ˙p /­xޏØXŽ?Áąã'xbųcˇŊŸĶē§đÂSWķÉÎŨ1q\&̞s?z$ą1ŅŦ{c#/ŋôėUë644’ķ—M€ÆÆ&–/Y@ūWį9|ėI]ą—2bč`=Øīļ{šß}¯A|ōôiŌ{÷bÆ´ŠØ+ ėúlÉÉIĖž1ōō ˛7ä2nL&&ŗ™—^x–aąX˜?g66[›Ū}î))Lš0žú÷ãĀÁCėŪģ—ôž=‰ŒŒdūÜŲlũxÕÕ5,Z8ŸMīžĮ—ųįp:ĢÛĖķĶ—ėëÍ`0ÆÂķpģkųÍoĮėøúØōÉvŌRS™>m2•¯fũ‰IÆˇŠąW:n¸=§NŸá°ŲːÁƒpģkŲõŲĖ›Ãįûö3}ę”vŲĪļ"ū¸vƒīõ0iüh˛ÖŧŽ×ëĨs|Ŋz¤´ a€=û’Ū#•ņŖGōÕų T×Ô„ŲlfÆÔ‰—–ņÁÖí b‘vĐîAl0hņz¯ZvéR ÁA&NĪûlá7ŋũ=ņqą,]ü*ėØlŔ••ųęâãb}õŅŅŅ�Fšš<˜ŒF>ĘŅcĮŠrVaĩŲēQĄ!tęÔúshH(įēķ\)>.�Ģ5ˇĢîĒ>ėv;}ûd�C•Ãy͚[ŲžoŒ>”úÕ¯ydÚUU¤ĻvģĨ}|3Š)ÉmŽˆ3ŠŦ59üę—?ģa}UU5é=č™Ö�{eŅ—÷Š1ԈĮĶÜ.ŊŠˆÜīÚ=ˆģtIÄf+ÂãņŠÛ]KŲŋ$$tύ¸˜L%ĖÆ;īžĪūũ‡HˆO .&–ŠS&áņx¨Ŧt`+*‚kå7ļnÛNZZ*ãÆd˛}į§\(´Ũ´¯kÍķmË+�p:DDļ†;ž Ŗ´Ŧ HyyąqŅ×ŦšŲö ŧ—˙P1™LdôîÅëšo2bø°[ŲŊßYķĨK|ŧc˖ĖgĶæ-,[4īēëÆÅFsąŧ‚ûepöÜ××üŖEDDÚGģqtTŗfNį˙ûŨī1›Í456ņÔãË0CŠ­­å?^ũ#1ŅQÔÕÕŗüą%„‡…ŗf}ĢÖŦ§ēφņcGßtŽŒŪéŧģųC 蜘ČŲŗųtIė|Ú1™ŖÚĖ“ø­ĮÃëšoRTTĖü9ŗđx<ž÷ƍ͚õ9Ŧ^—ƒËåâŅŋ(ģxąMMŸŒŒnOBB<§ĪœeĪŪ}ŒÎɄņãøįų5.YäĮ^žą‚BXã{d !>ŽQ#bč |}ž#ĮO’Ú-…ėܡøÉ‹Ī\U?rØ6üåmVįŧICcË/āĢķÚ­?ųÃʕ+Ŋ%%%deeŨRÁ‘Sų$%Æwp[ßŋŊûō°Û+™3kF‡Ö|Ûų‚ ėøtĪ<ųøM×ÕĶ—DDîlGNåßōīé+VĐĩk×ī÷b-šÚÖOļsüÄIžúÉ@ˇ"""ĸ ž,säˆīĨæJS'Odę䉷5†ˆˆÜŨtg-‘�R‹ˆˆ‚XDD$€übŗŅH}CCGô"""r×r8k°†YüŽķûb­”¤lÅå8œ.ŋ'“ÛwäT~ [‘k°†YHîâ˙×{ũbk˜…žéŨũžHDDDÚŌ9b‘�R‹ˆˆ‚XDD$€Ä"""¤  ąˆˆH�)ˆEDDHA,""@ b‘�ōûÎZîēzl%å446uD?"""w%k¸…äÄx,f“_u~ą­¤œ0‹‰ØčNū–ŪˇŠË*Ü?=ĐmˆˆHr8k(*Ģ =5Ų¯:ŋ?šnhlÂb6û[&""rO‹‰ŠÄ][īwÎ‹ˆˆ‚XDD$€Ä"""¤  ąˆˆH�)ˆEDD¨ŨƒøĢ¯žæšą …že˙õ'.^,oīŠžWŽ*9r;lü‚B˙ë˙šW˙´Ū÷߯wŪī°ųDDäÎā÷ =nÅ ČŪđg~öĶ—1C}ËëęëxmÕ:""ŦØí•,]ŧ’’Rō¤[J2ųįžbāƒPW_Ī—ųįxņ…g1›ÍŦ^›ŲlÂår3Î,RRūöeé=Ÿī#ok}IicGĸĄą‘ŧŧ‡„đô“ËÉÎy‹Å‚Ķédæ#Ķ(ˇÛÛÔôëסÍ<E%%žqŒ&#……>r”÷?ÜÂK+ž#.6–cĮOpėø žXūØmî)ŧđÔÕã|˛s7AAAL—ÉĒė\ƏIlL4ëŪØČË/={ÕēEÅĨlŪē kx8ĩĩu,[<Ÿŗų_qøØ ’ē$b+.eÄĐÁ z°ßm÷*""íŖC‚8ĒS'<˜›6ņØ’ÅžånW-“&Œįūũ8pđģ÷î%ŊgO"##™?w6[?ŪFuu ‹ÎgĶģīņeū9œÎj’““˜=c:åådoČå§/˙Ø7ĻÁ` <<Œ… æáv×ō›ßūŽŲ3Ád6ķŌ Ī˛å“í¤ĨĻ2}Úd*^Íú“&ŒoSc¯t´™gܘLß8§NŸá°ŲːÁƒpģkŲõŲĖ›Ãįûö3}ę”vŲoļ"ū¸vƒīõ0iüh˛ÖŧŽ×ëĨs|Ŋz¤´ a€3_žŖWZ*“ƏÆáŦ&Č` ((ŗŲˌŠ).-ロÛÄ""w b¯×˨‘Ã9rė§Ī|á[n29tø(G§ĘYM„Õ @TTdk3Ą!tęÔúshH(‡ {6[1eee@kđ~[|\�Vk8nWŨåeą�ØívúöÉ� 6&†*‡ķš5כį›qŽ4bøPūéWŋæ‘iSpTU‘šÚí;īĢ+ĨĻ$ˇ9"xxĖ(˛ÖäđĢ_ūė†õc3‡ķņŽĪøĪ×ÖÅüŲĶˆžŧOĄF<žævéUDDÚG‡ņ7_ö(ŋûũҏ[ˇm'--•qc2ŲžķS.Ún:FB|q1ąL2 ĮCeĨŖÍ:Ë+�p:DDļ΅/Hã(-+R^^Al\ô5kŽ5­¨Č7ŽÁ`Āëõ`2™ČčŨ‹×sßdÄđaßu÷ܒæK—øxĮ.–-™ĪĻÍ[XļhŪu×ŊXngōÃc°˜Í|øÉN;I˜ÅŌĄũ‰ˆČíéĐ Ž°Z™7g&ŋû÷˙âŅ%‹ČčÎģ›?¤  €Î‰‰œ=›O—ÄÎ7cLæ(ÖŦĪa՚õT×Ô0~ėhŋUãņxx=÷MŠŠŠ™?gĮ÷Ū¸1ŖYŗ>‡Õërpš\<ēxe/ļŠé“‘Ņfž+%$ÄsúĖYöėŨĮčĖ‘L?Žū—_ķč’Eíļŋ müauŽīuP„ø8FxˆĄƒđõųBŽ?Ijˇ˛sßâ'/>sU}]]=ZŸKT§HęëX4o&…Eí֟ˆˆ´?Ãʕ+Ŋ%%%deeŨRÁ‘Sų$%Æwp[ˇnīž<ėöJæĖšŅĄ5ßvžā;>ŨÅ3O>~Ķuõô%‘ûÑSųˇüû~ŊtíÚĩcˆīU[?ŲÎņ'yūé'ŨŠˆˆÜåîú Î9â{ŠšŌÔÉ™:yâm!""ēŗ–ˆˆH@)ˆEDDHA,""@ b‘�ō;ˆÍF#õ Ņ‹ˆˆČ]ËáŦÁæ˙M”üžj:%)[q9§ËīÉîgGNåēé@Ö0 É]üŋΆßAl ŗĐ7Ŋģ߉ˆˆH[:G,""@ b‘�R‹ˆˆ‚XDD$€Ä"""¤  ąˆˆH�)ˆEDDČīz¸ë걕”ĶĐØÔũˆˆˆÜ•Ŧá’ãą˜M~ÕųÄļ’rÂ,&bŖ;ų[z_+.Ģ`p˙ô@ˇ!""ÄáŦĄ¨Ŧ‚ôÔdŋęüūhēĄą ‹Ųėo™ˆˆČ=-&*wmŊßu:G,""@ b‘�R‹ˆˆ‚XDD$€Ä"""¤  ŋŋG|3---älČÅY]ChH.ˇ›šŗg‘ŅģW{OõŊsT9øāí,li‡Œ_Ph#kÍR’ēø–%ÄĮ˛pîĖ™ODD¯ŨƒØf+ÂQåäå˙�{e%…Ô××ŗjíz ¨ohāų§ŸÄd6ąf]‹§ĶÉĖGĻQnˇ“ˇ˙ ŨR’))-cėčQôëחÕkŗ1›M¸\næĪ™EJĘßž0Ŋįķ}mjÉË;@pHO?šœėœ7žĶ<E%%žqŒ&#……>r”÷?ÜÂK+ž#.6–cĮOpėø žXūØmīŋ´î)ŧđÔÕã|˛s7AAAL—ÉĒė\ƏIlL4ëŪØČË/={ÕēMlØø6ŗ™ęS&ŒĨGjˇÛîKDD:Fģqפބ††˛z]6={¤Ņ7#ƒĄ æŖ-“Ņģ7S&MāËü|ǜ՜=wŽ´ÔTĻO›LĨÃÁĢYbŌ„ņ„‡‡ąpÁ<ÜîZ~ķÛßa¯tœœÄėĶ)/¯ {C.?}ųĮž9 C›šŲ3Ád6ķŌ Ī˛å“íßyžqc2}ãœ:}†Ãf3CÂíŽe×g{X0oŸīÛĪôŠSÚe˙؊øãÚ ž×C>¤ņŖÉZķ:^¯—Îņqôę‘ Đ&„>?pˆî)ÉL—I•ŗšÕ9oōĶŋ{ž]z‘ö×îA^|—ÛÍųķŧ˙ŅV‚ƒ ͝__�z§ˇŪęqĪž}ôí“@lL U'�ņqq�X­á¸]uTØ+°ŲŠ)++Zƒ÷Ûž]Ķē,�ģŨ~[ķ|3ΕF Ę?ũę×<2m ŽĒ*RÛé¨35%šÍ1ĀÃcF‘ĩ&‡_ũōg7Ŧ¯tT‘Ūŗ�ŅQpVWˇK_""Ō1Ú=ˆOŸų—ËňáÃđ⤧÷äWžÂ”É“(--cđĀœ>ķ ÄĮÅQZV ¤ŧŧ‚ظh�.–W�āt:‰ˆ´’Ÿ@\L,S§LÂãņPYéh3īˇk�đéwŸĮVTäĮ`0āõz0™LdôîÅëšo2bø°öŪWižt‰wėbŲ’ųlÚŧ…e‹æ]wŨؘĘ+ė�Ø+̈‰ŽęĐŪDDäö´{§Ļv#;'—Ŋûō Ĩąą‰eKͧOoV¯ÍáÕŦר¯oāš§ž Gj*kÖį°z].—‹G/ĸėâE<¯įžIQQ1ķįĖĸOFkÖį°jÍzĒkj?v4‰‰¯š÷Û5Į÷Ū¸1Ŗŋķ<WJHˆįô™ŗėŲģŅ™#™0~˙ü/ŋæŅ%‹Úm˙ÚøÃęßë   ņqŒņC āëķ…9~’Ôn)dįžÅO^|æĒúĖáCذņ6l|ˇģ–ŗiˇŪDD¤ũVŽ\é-))!++ë– ŽœĘ')1žÃÚģ/ģŊ’9ŗfÜq5ßvžā;>ŨÅ3O>~Ķuõô%‘{ߑSųˇüģ~ŊtíÚĩũˆī[?ŲÎņ'yūé'ŨŠˆˆÜÅî¸ Î9⎭šŌÔÉ™:yâm!""ĸ;k‰ˆˆ‚XDD$€Ä"""¤  ŋƒØl4RßĐĐŊˆˆˆÜĩÎŦaŋëüžj:%)[q9§ËīÉîwGNåēé Ö0 É]üŋΆßAl ŗĐ7Ŋģ߉ˆˆH[:G,""@ b‘�R‹ˆˆ‚XDD$€Ä"""¤  ąˆˆH�)ˆEDDČīz¸ë걕”ĶĐØÔũˆˆˆÜ•Ŧá’ãą˜M~ÕųÄļ’rÂ,&bŖ;ų[z_+.Ģ`p˙ô@ˇ!""ÄáŦĄ¨Ŧ‚ôÔdŋęüūhēĄą ‹Ųėo™ˆˆČ=-&*wmŊßu:G,""@ b‘�R‹ˆˆ‚XDD$€Ä"""ä÷חåÄÉS8ĢĢ Æn¯dÎŦnéžqæė9Ē].‚ƒ‚¨Ŧr2}Ōø@ˇ$"rßh÷ ūę̝ųˇ˙úo秤ø–%vN`ųcKokÜčĀŪ}yˇ5ÎípT9øāíˇŊ-×SPh#kÍR’ēø–%ÄĮ˛pîĖ™ī}3zpāđąGDDÚę#â^=zđ÷÷Ōußߗw€ŧ閒Lūš¯øāÔÕ×ķeū9^|áYBCCxmÕ:""ŦØí•,]ŧĸâbėöJââbیˇįķ}äío¯¤´ŒąŖGŅĐØH^Ū‚CBxúÉådįŧÅbÁét2ķ‘i”ÛímjúõëËęĩؘÍ&\.7ķįĖĸ¨¤Ä7ŽŅd¤° ÃGŽōū‡[xiÅsÄÅÆrėø Ž?ÁËģí}—Ö=…žēzœOvî&((ˆ‰ã2Y•ËøŅ#‰‰fŨyųĨg¯Z÷ĐŅ:z‚¤Ž‰œ/(¤ßŪÔÕ7đõų <ĩl1!!ÁdįūkxŽ*'ķgM§¤ė"•UNbŖŖnģņOđÃ?ŧŌår1{öė[*(Ģpi ŋîûUUU|ôņ'|™ŽŧũÉÛ¯ˇ…”äŋŨi¤´ŦŒę.YHmm-55.~°`.v;O3af â™6e2Fc(G'**Šēēz¨ĢĢ'Ŗ÷ßîRUT\L•ĶÉËĨŋžŦZģŽžŊ)¯°ķŖŸgĮŽŨDuęÄÂĖ#=Ŋk˛_§GZj›ŧƒ… HKíÎon$-ĩģo‹ŲŒ§ŲÃôŠS0`āĖgéÛ'ƒˇßŲˤ ˆŠēūŨÆ\î:ē$´ũ#âJÎęļíÚËWį/øÕëõ2zäPļnßEu “ŅČČaC°˜ÍŒ6¤ÍËí¸Üĩ,˜=Úēzj\nfOŸLĨŖŠææKXĖ&ââb˜8.“ĐĐPNœ>K§Čęŗ˜Šoh WÔö)""×VVá¸éīúolŪŧ™ˆˆˆŽ9"î‘–ÖæˆxێO9rôÉI]éŲ#¨¨H�BBCčÔŠõįАP<&Ŗ‘C‡rôØqǜÕDX­73>.�Ģ5ˇĢîō˛ÖaˇÛéÛ'€Ø˜ĒÎkÖTØ+°ŲŠ)++Ā`0\5ΕF Ę?ũę×<2m ŽĒ*RSģųą‡Ž/5%šÍ1ĀÃcF‘ĩ&‡_ũōg7ŖSDëū !2"ĸõįĐÖ}k49~ō §ÎœÅY]ƒ5üúT‰ˆHĮûŪ.֚4a<“&´^tāāĄŽģuÛvŌŌR7&“í;?åBĄíĻã_,¯�Āéty9¸}AGiY0ōō bãĸ¯Y“Ÿ@\L,S§LÂãņPYéĀVTäĮ`0āõz0™LdôîÅëšo2bø0v…ßš/]âãģXļd>›6oaŲĸyßyŦģ?§{J#‡ a÷į°—´c§""⯠⯞>Īīūũ?}¯ † ^ūņoš>Ŗw:īnū‚‚:'&rölū5Ī _Éãņđzî›3Î,<īŊqcFŗf}Ģ×åāršxtņ"Ę.^lSĶ'#ƒ5ësXĩf=Õ55Œ;úĒ9â9}æ,{öîctæH&ŒĮ?˙˯ytÉĸ[Ūļ›)(´ņ‡Õ9ž×AAâã5â!†Ā×į 9rü$ŠŨRČÎ}‹ŸŧøŒ_ã÷JKåŖm;šPTLįø8Ν/ 6&ēŨú˙VŽ\é-))!++ë– ŽœĘ')1žƒÛōĪŪ}y~ĨéģÔ|Ûų‚ ėøtĪ<ųøM×ÕĶ—DDî}GNåßōīú+VĐĩk×ģį{Äwš­Ÿlįø‰“<˙ô“nEDDîb÷DgŽņŊÔ\ięä‰L<ņļÆŅ-.EDDHA,""@ b‘�R‹ˆˆßAl6Šohčˆ^DDDîZg Ö0‹ßu~_5’”€­¸‡Ķå÷d÷ģ#§ōŨ‚ˆˆtk˜…ä.ūßgÃī ļ†Yč›ŪŨī‰DDD¤-# ąˆˆH�)ˆEDDHA,""@ b‘�R‹ˆˆ‚XDD$€Ä"""¤  ŋīŦåŽĢĮVRNCcSGô#""rW˛†[HNŒĮb6ųUįwÛJĘ ŗ˜ˆîäoŠ|GÅe îŸč6DDäΊĘ*HOMöĢÎīĻ›°˜Íū–‰ˆˆÜĶbĸ"q×Öû]§sÄ"""¤  ąˆˆH�)ˆEDDčž â’ŌR~˙Ÿ˙č6$€.–ÛųãÚ nCDÄĮī¯/ŨĖW_}Íī˙ë¤ĨvÃë…ÆÆæÍCߌŪí=Õ]ÅQåāƒˇ˛üąĨ2~AĄŦ5HIęâ[–Ëš3;dž›õōĮĩ薜„/M˜:‘ôžiß{/""wēvb€ž=Ōøûŋ{ �{e%˙ū_˙ÍĪ˙áōÚĒuDDXąÛ+Yēx!Ū–6ŊķÖ+n—›gž~’*‡ŖÍ˛˙ķo˙Î˙ķ˙/Uū×/Vōo˙įׄ„„đ›˙ķoüÃ˙ø kÖå`ąXp:Ė|dv‡ƒŧŧ‡„°|éV­[Obį‚CÚnîžĪ÷‘ˇ˙ ŨR’))-cėčQ446úęŸ~r9Ų9o\5~šŨŪĻĻ_ŋžŦ^›ŲlÂår3Î,ŠJJ|ãMF 9|ä(—V<G\l,ĮŽŸāØņ<ąüąÛŪīiŨSxáŠĢĮųdįn‚‚‚˜8.“UŲšŒũ˙ˇwßņQŨwž˙_3’FŖ‚ē„PŅ›é`zīĻ›flb;vėd7ɖäîŪŊŲ›ürwûˆo˛Iė`ĮÆTclƒ16ŀ QE5`°‘P/SÔÛHs˙ ˆe@â�~?–Îų–Ī9#ë=§čĖĸŖ"YņÎûüėĨįĩM=œFiiĮŽ$'/Ÿ-Ûw1eÜh6oûŒĐĘË+xjÁ-ŦyīC-ĘĘ+xlŌXrķ 8œv??ƎJJÛĢĩØN^_ą–ŸžøĢÖũĐ`ė'sĻOÁív{īt{-ûķ_Wđ‹Ÿŧˆŗ¸„_ũöüī_ū?ū{ŲÛüŨ ΰöũ ˛Z).)eâØ‘ØNO=ķgOcÍú‰‰ÆĪĪīŽ÷ŗˆHsj‘ žViiAV+eĨåŒ;šGzöāĐá#ėŨˇđ°0:wéÄc“'Qdŗc6™8õÕW^Ë:¤¤™•EzFúõåÜ×įąĐŊ[W>ßģö))L™<›ŨÎĢËūʔ‰ã ´Zyé‡ĪŗåĶmôíĶ› ãÆp4텍ę3™L„„3īņŲ”••ķÛWū/3›ęéŋuĮN¯ņĮíÕ§Čf'))‘MĄ  Uk×1jÄ0Ī8§ŋ:ÃQĢ•ūũúRVVÎ{žäņŲ3؟z)“&6ËžNĪĖjtÚĩŸG?z8˖¯ÁívĶ:6†NR�ŧBøFÎ|}NíS?z8vg1f“‰}ߚIãFQdŗŗūÃÍ 4�k` Ī>9ŸôK™Æ(+¯ĀHyy#‡=J÷.H;qšÔÃi„ĩ õŋŠ9S’“ČÎÍ#3+‡žôāüˇéXčÚŠ=ûĄ]rãF Ãá,æ­Õë7j˜§žģ÷Ōŗ{WFˉĶg(˛Ų›e‹ˆ4‡ âo/^äz ˇÛM@@�Ī|ī)-Ž=Æąã'p8‹iʸąŖųxËV~ûĘˆ‰æ‰s›\ÖģWOΞ=GúĨK˘>•ŨŸīÁ`Ą_ŋŪ8t„îŨē…Ãî 6&�‡ÃA=�ˆ‰‰i˛ŪØËËCCC(+­hÔŋ¨¨čã7îSXTHff6yyy@CĀ_;Îĩ?:_ũæeĻNžˆŨá %ĨíŨėn”ä$¯#b€1#†˛lųj~ķīŋđyĖ‘ÃeûŽ=üéDGF0gÆŠlrrķČ/,ŽnktT¤§ßÕ7nüũũY4w‹…§ÎpúĖ9œÅ%„†„49~SËztëĖųo.’™•ËäņŖØ›z˜€�z÷ėÎŅã'éÜą�‘á8‹‹Õã,.Ą[—Ž Ë"¯Ö("r?h‘ îĐūęŠé+Ūû`íÛ§0jÄ0vîūœŒK{ƒ¸ŋ�� �IDAT™äææķØÔIŗqĶĮ<x„ļm“Ŋ– <ˆŊûRqģëILH °ČF]]sŸÍˇ3ČÍËúPPPHtĖå_´—Ã!""›ÍpšˇüËGÉN§“VaĄúĮÆÄ49ūõ}âb㈉ŠfŌÄņÔÖÖbŗŲÉĖĘōŒc2™pģŨ�ŌĩK'ÖŦ[ĪāGŨíîž)W]Ûw}ÁS į°aķVžš?û†m-ūÔÖÖāp6„Y~Aƌ Čjå“ģ9zü1ŅQDEF0väP\.g1Y9y˜ŽĢŠ7›>ŲNģäD† ęĪŪũ‡ČĖÎirü¤„6^Ëöí́Ãi¸Ũnâ[Įaw8pÕÕ1sęD22ŗ)¸üĻ Čæ *2ĀSOxX+l—ß@]yķ "rŋhņSĶWtíŌ™M›?!==Öņņœ;wžvíÚōŪß>$*2‚ŠŠJ?𐜜\ūøę덖Y­VjjjčĐ>€đđ0\ĩ.ĖfŖF gųĘÕŧĩb5ĨĨĨ,Z0ßŧ�#†åĩ7Ū$#ãÖ Ģ' ¯U[[˚uëÉĘĘfÎĖéž0š?/?ßĢOˇŽ]Yžr5o._IqI ŖGo4G\\,_9Į—ûR>lcGâ×˙ų2‹Îoļ}œ~)“ŋŧĩÚķŊŲl".6†Ąƒ0°oožŊx‰´§Hi›ĖĒuđ“ŸkÔŋs‡öėM=ĖGŸîh1ˇ›ŠŠJūēráaTVV1ö4B‚ƒXûūFÖŦß@IiÃŧ­ú:ĩOáĶĪv“‘•MëØ.\L'91MŸlo4~^~Ą×œjjki—Üđ ×VĄĄ¸ęę0™L {´?kßßČÚ÷7RVVÎã3Ļbw8=ķ؏ˇ×Ŧ'+;Ģ5đū1ŠiéŌĨ–-[v[ŌNŸ'1>ļ…ËēwöĨ ¨ČÆĖéĩhŸë]LĪ`×į_đÜ3ßģe[}胈ȃ!íôųÛū}ŊdÉîŨą\ĩmĮNNœ<Š߯čRDDÄ`ßų 6dđ=és­IÆ1i¸ģCDDߊ'k‰ˆˆÜoÄ"""R‹ˆˆHA,""b ŸƒØjąPYUÕĩˆˆˆ<°ėÎBƒƒ|îįķ]Ķɉqdf`w–ú<™Üš´Ķį.ADDn"48ˆ¤6ž?gÃį  ĸ{įv>O$"""ŪtXDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@>?YĢŦĸ’ĖœĒĒkZĸÃ|›žÎŠŦĒ6ēš… k įÎd@Ÿ^F—""r×|☂ƒ‰Ž o‰z ŗzí:jjk.CnCeU5ī~°IA,"ŸOMWU×dĩļD-†R?XtæBDēF,""b ąˆˆˆÄ"""R‹ˆˆHA,""b {ÄûR°iķ–;ę{ōÔiö|š¯Ņ˛C‡đū†ÍQZŗčÛĢĶ'7ē Ÿ ęׇÉãGßQßî]:1x`ŋFËÔũ "bŸ˙Žøv}´åSė6;‹ÎĮb ¸Ģąz=Ō�ģÃΖOļąøÉ'šŖÄ;ŌŊK'ƍŽÛí&0ĐÂÅK™lÚ˛Ũ°zŒtæë �D„‡1aĖHŪßøąÁ‰ˆ<xZ$ˆĢkj(--aØĐÁėK=˜Q#<ëŠKJxsųJbĸŖ !33›_xŽå+V„ĶédÚÔÉŲí8p?úöéÃá ŋ €ô‹M;@VfËWŽ&'7—)'P[ëâĀĄÃ´MNâü…očĶë**+ųúü^üá턇…ŨÕvÅÅF3{údūû/Ë)¯¨�`üčá$%Đ˜Īĸš3iËÎ/öqöü7<5•U„‡…ąc÷˛rōŧ–ÕÔÔ2mō8ĘĘ+ fÍú ÔÔÔđä‚ŲÔT×–m;ÉÉË÷ÔbĩōŊ…s)//'*2‚ ›ˇŌ:.–}!;'¤ÄIãøÉ¯nÚįŠĐžˇđqlå•$ļiÍÛkß÷Ē52"œũzSįĒãÄWg‰Œ'6:ŠvɉôîŲ­ÉũāīīĪ€žŊČÎÍŖCģdNŸũš   :Ļ´eųš÷(+/`đĀ~,^0€‡ĶXŊ~Ã]Ŋ^""ŋ1cÆ,---eƌˇÕ!¯ĐNXhČMÛ|ąg/]:wĻk—Î|˛u;ƒô'3+›ŠŠJ˛˛ŗILH`ÎŦ8ėNrķō(¯¨$"<œysgĶšs'–¯ZC—N)(,âĮ/ž@vN•ôéõĩŽZĻLšHNN.…ŧđüŗ´kۖm;vŌŽmÅ%Ĩ,Z8ōōrJJJ™ûø, ‹Š¨­u‘˜ĐæÆ5š˙–Û>¨_ō 9{ūΞ‹™—”ß:–¸˜hVŊģĖœ\ƎFH°•â’26oũŒo.f°hŪ,üũũŧ–ÕÕ×ápŗņãm\ĖČÄår1dP?**ĢØ¸e;—˛˛y|æT§đĖF‘ÍÎŽ=ûŠŠ­ĨWnØĸ""øāŖOČÍ+`ėČĄ=~ęĻ}JJJ ˛’ØĻ5šų|˛}‘ᴎ‹m˛ūoĶ3ˆ‰Žbųšõ$ġ&(ČĘWgŋÆ?Ÿ]{ö7š˛sr kʆ͟LXĢP6oũŒ˜¨HüüÉ/( ;'ģÃIeeÕm…đcĮŪ˛ˆČũjķæÍ´jÕĒųˆëëŨėŲŗ¸¸Xvíū‚ü‚NžzTæ°;čŲŗáTsRb‡ĻQTTD÷n]ˆŽŠÂaw}Ķšbãb°R{ųÉX GŊūū„‡7|āāY7Ün7&“é†ë ‹ė�TW×āīOtT$__¸€ŗ¸„ˆđ°&—íؐ‰cGņw?|ģŨÁß6JtT‰mZĶ:6Æ3÷ĩĒkjéĶŗtīJDXĨ—*Å%@ÓÂünĢ4„ô•SÍ9šųôy¤G“ĩØ.ŋ>ˇģ�JJJpš\””–PërašŽÆ‡Ķ8p8íĻ㋈<Lš=ˆ8Á€ũ˜ūØ�ĘĘĘyķí• Ø€°°0Š ŋČŗrr�ˆ‰!7/čCAA!Ņ1‘ ƒ]z&“É+îĨĶgĪąäû‹I=tÔ&“Į"#+ģÉöE6­ãb8ųDGEbw8›\Ö:6–ģ÷PUU͔ cčß§E6;v‡“Ũ{÷ãīįGäuĪö;b(™Y¤NcĐA$'Ūøh˙vú”–•{NŨˇ‰ģaũ�\÷¸áĻoPDD䯚=ˆˇnÛΞûžįûĐĐÜõõ8.‡īˆáCyã͡ɸ”I€%�“ FÎō•ĢykÅjJKKY´`>6›Ík돏Xž:sŽ/÷ĨŪõ `wĸČæāŊ?æŲ'įãĒĢÃĪlæÂˇéœ;˙-}éîÕ~˙Á#,š?‹EķfÂß>ú”ŧü¯e!ÁAüāéE8ÅņŪ‡›Š¨¨dŅüY<56a­ZņåÃž#M€ ßĻ3yühÚ&'RPXDĮ)ä]>Å{#Mõ)˛9�H=t”§Ÿ˜KrBj]ĩ€ģÉúŖšø°Â"]:uāŅ}ŠŠy¸>•KD¤Ĩ™–.]ęÎÉÉaŲ˛eˇÕ!íôyãcīxB§Ķ‰Íá cûöœ:ũGĶŽņôâ'īxŧæōë—_1ēC…ĩ %2"œŒĖlēuéHīžŨYŋaŗŅeŨÔû+ŖKšcK–,!!!Ąåū|éFüũØđáG„„„PYYɓ įßë¤ .WĶ&§ĸĸĢ56ŨŲß|‹ˆˆoîy‡††đĪ˙đ“{=­ÜBEe%¯ūuĨŅeˆˆ|įč—"""R‹ˆˆHA,""b ŸƒØjąPYUÕĩ* āŪ˙9”Ü9ĢÕjt ""ÍÂį›ĩ’ãČĖ.Āî,m‰z 3|ØRĸę!|“ņ°ąZ­<1÷öÉ*"rŋķ9ˆCƒƒčŪš]KÔb¨~=;3wÚDŖË‘ī]#1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1ĪOÖ*̍$3§€ĒꚖ¨GDDäDR|,AÖ@Ÿúųę9îkׇFv^!ũzv6ē šØ%dåŌ9%ɧ~>ŸšŽĒŽ!HŸ|#""ŌHTDeå•>÷Ķ5b)ˆEDD ¤ 1‚XDDÄ@ bŨŗ ÎÉÍåzí^M'""ō@đųīˆv‡-Ÿlcņ“O´Čøé—2Yļ|-ɉm<ËâbŖ™7kZ‹Ė'""ĻfâũrāĀ!üüũYüÄBŪ\ą’øÖqøų7L•zā&99‰ŒôK >”Aú{úš?•Ķ69‰œÜ<FJUuĩgĖī?ŗ˜UĢß!((§ĶÉ´Š“)(*ōęĶŖGwŪz{Vk ĨĨeĖ™9ŦœĪ8–@ —Ō/q4í˛•—–ü€˜čhŽŸ8Éņ'yzņ“wŊ/ÚˇKæ‡Ī6gĮîŊ˜ÍfƍƛĢÖ1zøĸŖ"YņÎûüėĨįĩ­ĒĒfõ{0ÕÕ5,^ø8įŋšČŅã'IlOfv.ƒöŖo¯w]̈ˆŖŲƒØßĪ@Ģ•—~ø<[>ŨFß>Ŋ™0n GĶŽQPPˆŸŸ™   æĖœAff6}Ô(ˆM&!!ÁĖ{|6eeåüö•˙ˌĮĻzÆÜēc'íSR˜2y6ģW—ũ•ņcG{õ)˛ŲIJJdÆcS(((dÕÚuŒ1Ė3Îé¯ÎpÔjĨŋž”••ķŞ/y|öLö§dʤ‰Í˛/Ō3ŗxũíĩžīû÷y„ņŖ‡ŗlųÜn7­ccčÔ!Ā+„ž<x˜ÎR=|ß\Ė ¸¤ŗŲŒÕjåąIãČÎÍcËļ b‘X‹œšŽ‰ĀápĐŖGCHÄÄÄxÖGFF`ąX¨ŠŠmĸCÛĐĐĘJ+YTTD÷n]ˆŽŠÂaw6Ų§°¨ĖĖlōōō€†€ŋvœk ~t ŋúÍËL<ģÃAJJÛģŲ|”ä$¯#b€1#†˛lųj~ķīŋ¸i‡Ŗ˜Ž;Đą};�Šl"ÃÃ�°X¨­u5K­""bŒ–šF|9ô""ÂąŲl�ä^Äۑ_P€Ķé¤UXhŖ1ccb.Õ‡‚‚Bĸc"›ėGLT4“&ާļļ›ÍNfV–g“É„Ûí 00Ž]:ąfŨz?:čîļũ\uulßõO-œÃ†Í[yjūėļ‰Ž$ŋ ^=ēr7""ōđhŅ›ĩF ĘkoŧIFÆ%ŦAVOđŨJmm-kÖ­'++›93§S[{õ¨yԈá,_ššˇVŦĻ´´”E 擗ŸīÕ§[׎,_šš7—¯¤¸¤„Ņ#‡7š#..–¯ÎœãË}Š 6„ąŖGņë˙|™E į7Ûö§_Ęä/o­ö|o6›ˆ‹ačā ėۛo/^"íÄ)RÚ&ŗjŨüäÅįõ2¨?kßûˇV¯§ĒēŠÅ ᛋÍVŸˆˆĪ´téRwNN˖-ģ­i§Ī“ÛbíK=@Q‘™ĶkŅ>×ģ˜žÁŽĪŋāšgžwËļúô%iJÚéķˇK–,!!!áģûįK×Úļc''Nžâ…ī?ct)""ōsßņ°!ƒīIŸkMš0ŽIÆŨÕ"""wB¸1‚XDDÄ@ b)ˆEDD äs[-*ĢĒZĸ‘–ŨYBhpĪũ|žk:91ŽĖėėÎRŸ'{˜¤>ot ""r "ŠīĪŲđ9ˆCƒƒčŪšĪ‰ˆˆˆ7]#1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD äķ=Ę**ÉĖ) ĒēĻ%ęy …†‘K5Ч~>qfNÁADG†ûÚUÄPŲy…ôëŲŲč2Dä!ew–•WHį”$Ÿúų|jēĒē† ĢÕ×n"""ĩ¨ˆ0ĘĘ+}î§kÄ"""R‹ˆˆHA,""b ąˆˆˆÄ"""ē§Aŧ/õ�›6oi´ėäŠĶėųrß=›īaS[[˯˙÷ËlÛū™ŅĨ*'7—?üé5ŖËxā9wÔÃiF—!ōâķßߎ'OąmĮNL&õõõ$&ļáņY3›lÛ둞>mwØŲōÉ6?ųDs”z_ōem6;­BC™4qü=¨ėá—~)“eËגœØÆŗ,.6šyŗĻŨöÎâļīÚÃüŲˇßį~ŅŊk'ŸÚ_ŋ­_=ĪŽŊû.˙ŋīĻMë8ĻOO` åĻã¤_Ęäõˇ×Ō6)7nĒĢk˜6i;ļŋãmyP4{gegķÁ‡›øų?ü”ĐĐ�>ūt+īŦ˙€Ž]:ņõų ŦXĩ–œÜĻLšHeUEE6&OšĀ[o¯Âj ¤´´Œ93§͛o¯Ä„‰ĘĒ*^øū3|°aé38švŒūũúzæ­ŦŦôj{EEeoŧš‚V­B)*˛ņĂy¸ëëŲ°ņ#B[…RVZÆs߇Ũîĩ,ŦU¨gœ/÷§rāāaÚ&'‘“›ĮČáCŠĒ޿Cøųûķũgŗjõ;át:™6u2EE^}zôčîĩ­Y99žq,.Ĩ_âhÚ1>ūd+/-ų1ŅŅ?q’ã'Nōôâ'=5mÛņYŲŲėÜũ9Æ fųŠÕæ/˛Û=ãžôÃāīīwÃ}Ō6ųꥧ8ÄÁÇINN"#ũÇĨw¯GŧęūëÛ+ų˙ūí_ą;ėüë˙ZĘī÷2ūūūüöwŋį—˙ú ĪxûôÔņÜŗOŗrõÚ[žÖÖĀ›nĪâ'ōæŠ•ÄˇŽÃĪŋų~”ÛˇKæ‡Ī>ŲhŲŽŨ{1›ÍŒ5Œ7W­côđ!DGE˛â÷ųŲKĪ7jûҧ;¸”™Í‰ĶgčÚŠ#kß˙ Ģ•â’R&ŽI‘ÍΑc'IJlC^~!CõŖWnžūķZß­sGÖŧ÷! eå<6i,šųN;ŋŸĮŒä“ĪvByyO-˜C€ŋŋ×Üg1GŸ$ąM<™Ųš ؏žŊzxæ>tô86‡“Øč(¯v=ģuöĒaįû<ÛÍG[wđ÷/<Kppgŋũmķ§tî˜âŠuŪŦiŧķūFĸĸ" &'7IãF‘Ōöę~ˇ;œŧžb-˙ôãîÉv‹Éo˘1KKKK™1cÆmuČ+´v9`›˛gß~RÚĩŖ{ˇŽže:t`õšwéŅŖEE6^xūYz÷ėÉō•ĢéÔą•äääĖÂyĶ>ĨīŦŸŠŠ bccybÁ<bbĸ¨­uß:ŽZW-S&Ml4īg;w{ĩ--+Ŗĸĸ’¸ØXZĮÅ2yâ,–�Ž8AII Ņ1Ņ,œ÷8:uÄĀĄ#Gŧ–Y,WßÉgegãp:yúŠEôėŅ7ß^A÷Ž]((,âĮ/žĀŽ/öÎŧšŗéÜšËW­ĄCû¯>¸ņÚÖö)í<ãY­žm4aâĖŲstī֕7nfüØąDD\}ĒYll YŲš,Z0Īvá5—N=ãšÍW¯D8N¯}ríŲ‰Üŧ<lv;O=ąÄ„ļļ“ŠŠJ¯ēˆŠŠäü… ø`ĩZ)vc2™čŅũj¸äååyęøü‹ŊˇõZ9vüĻÛŗëķ/HLL`öĖéÔÖÔp)3‹!ēáĪfiYmâĸoúķí,.áŗ/öņÍÅ Ž;ɑc'qģŨ 2m;ŋ ¸¤”@‹…!ƒúdĩ2dP¯1Ŧjë\Œ5œŊŠ  cÆÔ tlߎwŪßDJÛ$ŠKJY0g:Ũģtbõú Œrĩîœŧ|¯õ�ÁAAĖž6™vɉüíŖOh›”ˆÍîāšÅ 9tô8Ņ‘‘ĖzlíSÚbņ÷įĀ‘4¯š;¤´Åî,fŪŦĮhĮį{÷3 o¯ĢsįæSYUEĢĐ¯v•U^5Œ2Čŗ­§Ņ6)ąŅQlJÛ$Ūûp3=ģuņÔē7õmâã˜6iÎâō ŠčØžß\ŧäŠĨĐfįü7qš\÷dģEšK^Ąũ–ŋgŽØŧy3­Zĩj™SĶõuõ^ËŽd@›6ņ�„G„SZZîY_XTHff6yyy�˜L&lv;=zt Kį†GžūęL“s6Õ6'/€@‹…#Gqėø ÎbZ…†2nėh>Ū˛•ßžōbcĸybÁÜ&—]/6&€ĐĐĘJ+./kØéEEEž7 ŅQQ8ėÎ&û4ĩ­×Žs­ÁäWŋy™Š“'bw8HIiÛäöß|~īq›Ú'׋ŒŒĀbąPSSÛdŨŊ{õäėŲs¤_ēČéSŲũų,úõëŨÄžk¨ãv_ë/SSoē=‡ƒ=Žjb.īãæ’œäuD 0fÄP–-_ÍoūũMôjšÍî sĮ�DF„ã,. :*€āā ĘË+ŧú]ŋžČæ '7üÂ"āęĪLtTÃk4rØŖlßĩ‡?Ŋą‚čČæĖ˜rÚ#ÃÃ�°X¨­uŨ°öëÛŨ¨†kÕ×{˙ŋ}­Îâbēui8žߚc'ŋ =3‹×ß^ ¸ņ÷÷gŅÜYėM=tΎ[ä^kö îÛ§7¯ũå † â9­ûéļôîõ�…�;‹ ģúË?.6Ž˜¨h&MOmm-6›ã§N“››Gŋ>ŊųęĖY0™0™L¸Ũn¯ycbcŊÚ^ąíŗ´oŸÂ¨ÃØšûs2.e’››ĪcS'ĖÆMsđāÚļMöZ6fôČFķä_ŽßétŌęJũž !7/čCAA!Ņ1‘Möij[3ŗ˛<ã\ģtíŌ‰5ëÖ3ø&G{7›Ÿ&~a6ĩOnĨŠē#"ÂŲģ/ˇģžÄ„ ‹lÔÕÕ1÷ņŲŪ\Žãv_ë[mODD86› ārģ–ãĒĢcûŽ/xjá6lŪĘS훨ž+ŽyũĸŖĸ(¸"E6Q‘ [Pd ¸¤Ôs įZׯ‰Ž"*2‚ą#‡âršp8‹ÉĘÉãĘ+›_PĄ1#˛ZųdĮnŽ?uÚīTS5؝Şm}¤GW–¯^ĪŖú ĀÎ/öņH÷†7SWj Ĩ¸¤h8úŋĸŠ7@÷Ãv‹´´fâÄ6mX8.¯ŊūWüũŠĢĢ#!Ą æÍåČŅ4ęęęXŗn=YŲ9Ė™5ƒōІŖÆ˛|åjŪ\ž’â’FΨCyëíÕŧēė *+ĢøÁŗOS[įâĢ3įør_*Ç ņĖÛT[§ŗáĒk—ÎlÚü éé鴎įÜšķ´kז÷ūö!Q‘TTT˛øÉ…äääōĮW_o´ėzĩĩĩ õge3gætjkk¯Ša8ËWŽæ­Ģ)--eŅ‚ųäåį{õéÖĩĢ×ļ^+..ļŅ6Ž=Š_˙įË,Z8˙ĻûžŠų¯ÕõšÚ'™ŲŲ$'&ŪpüĻ^ŖøøÖÔÔÔĐĄ} �ááa¸j]˜ÍŪáŗqšzũ:¤¤Üt{F ĘkoŧIFÆ%ŦAÖ&ß Ũ‰ôK™üå­ÕžīÍfqą1 <€}{ķíÅK¤8EJÛdV­û€Ÿŧø\Ŗūq1Ņ|}á[9ưGûŗöũŦ}#eeå<>c*…E¸\.>Ø´…œÜ|ĻOŗ¸„7VŦåį?yĀk}įŽ)Ŧ}#kÖo ¤´Œaƒ6šŗĸĸ’ŋŽ\GDx••U˟=ā ¯ší§×ö^?÷ ÔĪ̆ļI žm}t@_fO›Ėō5ëņ÷÷ŖŽŽž6­c™=m§Īž÷Œ3d`?V­û€Ŧė\ü›zŸxĶ9›kģEîĻĨK—ēsrrXļlŲmuH;}žÄøØf+āË}ŠØfL›Úlcļ”}Š(*˛1súc-ÚįzĶ3Øõų<÷Ė÷îx š>}éĘ QSƏn´|ã–mĖzlŌ ×ˇ¤+sß Å%Ĩ8‹‹i—œÄ™¯/pâôΚŊ{TDîwi§Īßöī™%K–Đ2׈oW^^>;vîbūÜ9F–q_Ûļc''Nžjt¸<|ęëë7rØwbî�6oũŒā `ĒĒ̘7ķÎߤŠ< ?"šWî—#byxŨÉąq)""b ąˆˆˆÄ"""R‹ˆˆČį ļZ,TVUĩD-""",ģŗ„ĐËĪY÷…Īž”œGfvvgŠĪ“‰-íôų[7šĄÁA$ĩņũ¯Š|âĐā ēwnįķD"""âM׈EDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ bųü@ššėv;UzĖĨˆˆˆ‡Õj%** ‹ÅâS?ŸƒØnˇDdd$nˇÛ×î"""“ÉDUUvģøøxŸúú|jēĒĒ ĢÕĒšĖívcĩZīčlņ]#V‹ˆˆ4v§Ų¨›ĩDDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1ĐCÄÛŋ1ŗę¸Õ.ŊÜÂøy–ųâ\‘‰īYîēžkëšÂßč�ęęᡐ_n"ĀėÆ^iâŽt1ŧmũ9ącCßķ61Áđ÷ƒëšĢ\Ŧbx%5€W&×Ū˛íĨâ–ĢÃ×ZDDäūq_ņÉ9Ĩ&Ū[P@fą‰#9&֞ôcũ)?úÄ×sÎffq/ã;Ôķâæ�B-`Ģ4ņo#kiáæÅÍL@i5ŧ>ŗ†]ũqđŸv��IDATČ(6‘WfâT‰7ŽøjŒb?äōjzĩžÂrx~Ŗ…NŅnĖ ô\\/|d!:ČÍĨb˙9Ą–?đįHŽ™Mg͌NŠ÷ZßģõÕ‰ūķ!O‹zÕņŌæ�"Ŧ[f⟆ēČ,1ņî)?,~0ŗkÎúŅ+ŽžÔ,3“;ÖQ\mb_Ļ™sjôģy-3ģŨųšˇüƌŗ´´´”3fÜV§ĶIhhč­ú *6mfĮˇ~U˜Hw34ŲÍŠ3šĨ&~?ÕŸöu,ųČ‚Û ‘Ađ›ņ.úˇŠįl ¸ĘDûˆzūkĸ‹vnĒ\PTaĸ¸ÚÄ÷z×ņU‘™˙šāâT™âj§ Ė^íÛ´ēZĪëGüé[Ī˙åĸ˛N˜Ūļž‘n~2¸Ž 7[.ø3§{Õu&~:¤ŽœR“×ú+Gå�í#ܞ:^?ėGBüĮXÒëųņĮ MĒįĸĶÄęšĩœˇ™((7ņōDöJ((7ņc]¤;LTš ĖĘMkc”••q[m7oŪLĢV­î#b‹Ŧ™[‹­ŽäšyeŸ?~f’TOJDÃQeTØ*!ŊØĖÉ|_Û�0› ŗÄÄØ”†Đ–ÜđßŗE7ž¯Šö×Ę.51îōú”ȆųƒÜl:įĪ'įũČ)5ėnÔįV믕QlfTģ†ĀL s“]jj˜+âjŸøĐ†¯­ūĐúĘ×På2PÛs‰ˆČũížâ]ÍØ*MĖëQĮ¤Žõ MĒįŅ7’TĪ{CHå–Bl0´¨'9ŦáZkĩĢ!T?=oæk›™iÔŗëĸŗéæķĨ„×{ĩr5Û„ēÉ(näŧ­á~ļ?ôg`B=O÷Šã#~Ë3cę/g`Sëo8D=įíf ž‹mÚEŲˇĒEDD÷Å]ĶũÛÔķņ×f_gáÉ,,ū›…˙3ąáĻŖšz?ßĀs-ürt-O÷ŠãpŽ™%°đ= § L<͎ŽÃ9&ĀīSũésķk¤ˇj˙TožõcÉG¤f™qģaDÛz–§ųņĶO¨vÁŪKfęëŨėN7ŗæ„_“ëO4­Īö­ãxž‰māėāå žŨ`uĢZDDäÁaZētŠ;''‡e˖ŨV‡ôôtâãã[¸Ŧīœl¸áę_F¸îÉ|"""w#//”””ÛjģdÉî#b‘īĒûâņ,ęĨ;€EDäáĻ#b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ bųüéKVĢ•3gÎÜõˆˆˆ<***hĶĻĪũ|⨨(�ĒĒĒ|žLDDäaîÉH_čÔ´ˆˆˆ|>"ļÛí‰Ûín‰šDDD(&“‰ĒĒ*ėv;ņņņ>õõųˆ¸ĒĒ ĢÕĒšĖívcĩZīč˛íšV‹ˆˆ4v§Ų¨kÄ"""R‹ˆˆHA,""b ąˆˆˆÄ"""R‹ˆˆHA,""b ąˆˆˆÄ"""R‹ˆˆHA,""b ąˆˆˆÄ"""úÎqĩ F/ˇđĮ~l˙ÆĖĒã~Mļ{į¤˙ĩˇņĮ6Ÿ+2ąā=ËŊ(SDDrūˇnŌōęęᡐ_n"ĀėÆ^iâŽt1ŧm}‹ÍyŠØDL0üũā盪%dÃ+Šŧ2šÖčRDD¤ÜA|˛ĀDNЉ÷Ô�YlâHމ’jxqŗPZ ¯ĪŦ!Ô/m  še&ūi¨‹‹NëOųŅ'žžs63‹{šߥž7j[Ĩ‰YK¯ÖW?+ō·ü9U`â#~„Z ŖØÄOģŧú\QXĪo´Đ)ÚM€Ųû3'מôķĒĄÂeâŨS~XüāÕi5üä“;ĢûL‘Ų3Np€›´\3›ÎšųŨ~V=^KÛp7Ÿ^0ķé?~?E-"ō š/‚¸GŦ‹Ÿ›—608ŠžQíę™ŨŊž?¤ú3"šŽ=ZĮžL3šĨ&žŧdfPĸ›Ÿ v‘Ubbņ,XGT›˙ëÂ^ ­äRą‹GâÜü|¸‹o&ūáĶ�6.ĒņĖųŌ@9Ĩŧ0 ŽwN6œ–^žæįÕį‰GŽ˜WŸđgjį:^TĮĻŗf.:ŸÕ7W ŋ^K¨Å͊9ĩüé€ß×ũL—gœ̈́ÂĖnõ8Ēęx;͏ãbŨI?~:ÄuĪ^3i÷E[ü`ÍÜZlp$×Ė+ûüņ3C€ŒMi8==,šáŋkOšÕŽ!“ÂÜd—š�H‰h8J [%¤›9™oâk[��fĶ­ë¸YŸėRã.גé}DÜT ×.Ë(žģ睴ģÖüžuŒ|+Ÿ m÷~mšŽKDDî_÷EīēhÆVib^:&uŦghR=žȏšøÚffõėēhÆl‚”ˆzÎÛÍ@=&چ7„Ī{Cbå–Bl0´¨'9Ŧápĩ 2KnÄMõ9”ŨpäÛ&ÔMFqÃįmMßãv} Đp¤ wW÷‰<“gP9oƒ`dģzūyk�ķ{>X×ēED¤Á}ÄũÛÔķŗOX{Âk�”×Ā˙™XË蔆ëĨ‹? ´ÆÄ˛5 L€}Ā6PTiâå ĩœˇ›ŠŠ7ņķmœ*0ņËŅĩŒjWĪ>`ÉGfōËL|ŋŸ‹NQ7?b|ēOWŸ+žęí♠Žå5œv71Ôõ5T^sšöŲžuw\÷ĩ:EšŲnfÍ ?žę]Įķũ\Œ^Čo'éÚ°ˆČƒČ´téRwNN˖-ģ­éééÄĮĮˇpYžyį¤Å&ūe„q×Hæ¨ûhމ7ŽúķÚtąˆˆŅōōōHIIš­ļK–,!!!áū8"–;ķįƒ~lŊāĮë3knŨXDDîKE/ęeüõŅ;ŠánëūņŖuüøQãˇ]DDîÜwöÉZ"""÷ąˆˆˆÄ"""R‹ˆˆHA,""b ąˆˆˆÄ"""R‹ˆˆHA,""b ąˆˆˆÄ"""R‹ˆˆHA,""b Ÿ?}ÉjĩræĖ‚ƒƒ[ĸ‘REEmÚ´ņšŸĪA@UU•Ī“‰ˆˆ<ŦÂÃÃ=é ŸƒØbąīķD"""âM׈EDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ byčQ`sY‡ˆˆČw’'ˆãĸ#ŒŦCDDä;I§ĻEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD äë&"""päøIŪũ`•UÕF—ŌH5…sg2 O/¯uīž€7ŗĒå눰Â_fÂÂŪžõĶąˆˆÜ–û1„*ĢĒy÷ƒMMŽ[˛œ•÷ĻgeCčûJA,""ˇå~ á+nT[q5`ēGE˜îėČ[A,""b ąˆˆˆÄ"""R‹ˆˆHA,""b ąˆˆ´¸Aũú0yühŖË¸/é""ŌlēwéĸQÃqģŨZ¸x)“M[ļ]ÖMMí ?õnđ3Áéøåx¤5üË(˜ŊüͰüqøü"üõHķί ‘fÍėé“ųīŋ,§ŧĸ€ņŖ‡“”īicąXxjū,**Ģ cĮî=ÔÔÔ2mō8ĘĘ+ fÍú ÔÔÔđä‚ŲÔT×–m;ÉÉËoöš{ÆÁ˙?&.ûåüb$ün*,?Úđ} Ŧžīž„÷O7{ bi=ēvápÚ O|öų—�´Ž`ØŖũIŋ”ÍŽ=ûˆãšÅ 9qú ß\Ė`ĮîŊDEFāvģ6x�9šųlßĩ‡˜čHæĪžÎkoŽjöš§u…iWCāw{áâ?7qˆ>~Öo™]#‘fâvģ1™nūĢč¨HŠlv�œÅ%D„‡ąg˙AŦV+÷Ãg™2~4õîzĸŖĸčÖš#‹ĖaĘø1¸ŨîĢÛŋ‰$Ŧŋ<]§(xí ,­CZhū–VDDžkNŸ=Į’ī/&õĐQJJË�˜<nYŲž6E6­ãb8ųUC(ÛNZĮÆ˛c÷ĒĒĒ™2a ũûôĸČfĮîp˛{ī~üũüˆŒ o‘š?: ī>+Ķ čōü?‡Ī5|}<>8 9%đábxl8šų$Ä""Ō,ŠlŪûđcž}r>Žē:üĖf.|›Îšķß2°oÃGí?x„EķgąhŪ,BCBøÛGŸÄž^„ĶYLPPī}¸™ŠŠJ͟ÅSķgÖĒ_8La‘ŊŲk>S?˙Ö-„ę:0ÃWđ‹OĄ×ÕKÛėĪ„_ī‚‹aę (¯mžÄ""Ōlžžđ-__øÖkųĄ´ãž¯WŦ}ßkũ™¯/x-kĒ]KØzĄáßõR3bË× ˙š›Ž‹ˆˆHA,""b ąˆˆˆÄ"""R‹ˆˆHA,""b ąˆˆÜĢÕjt 7tŖÚ–{(WcîËķųHA,""ˇå‰š3îË0ļZ­<1wF“ë–Í‚đ{Tr¸ĩa>_é""r[ôéŀ>ŊŒ.Ã' {7üģŸéˆXDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b™üüüp8F×"""ō`ŗŲđķķ.qHH¯žú*………†&""ō°ËĪĪįÕW_%$$�ĶŌĨKŨ�%%%TTTP__oh"""3ŗŲLHH­ZĩŽy˛VXXaaa†&""ō]¤›ĩDDD ô˙�ĩ�Hã–4••����IENDŽB`‚���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/mod-user-list.png���������������������������������������������������0000664�0000000�0000000�00000053073�14156463140�0022074�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��]��D���õAĻ���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨxTå÷˙7ŽÍŒÖ„؆MhBk€Uˇ5aû•°ŽV,ŠE‰buĐĩŌjĩÚĮÖ jk+hUb‹‚?j"RÖ%°×’¸ģ8iWˆ-t‰Mž¤OCbëL´ōũ#?Č/H€ đ~]WŽ ÎĖšĪ}îsæĖ9ŸšĪ}8pā�’$I’$IęS§õw$I’$I’NF†.’$I’$I1`č"I’$I’§wžđ—ŋü…?üáDŖQūō—ŋôG$I’$I’ŧŋúĢŋ"””Äi§uí×2¨ũ@ēųË_øũīO0äĖ3ĪėvI’$I’$ÁG}ÄûīŋĪûīŋĪĐĄCģä(B—ēē:N;í4Î<ķĖã^QI’$I’¤ҟūô'�>ųÉOv˜Ū!‚‰D"œqÆĮ¯V’$I’$I'¸3Î8ƒ÷ßŋËôĄË‡~Č AƒŽ[Ĩ$I’$I’Nt§v~øa×éũPI’$I’¤“žĄ‹$I’$IR ēH’$I’$ŀĄ‹$I’$IR ēH’$I’$ŀĄ‹$I’$IR ēH’$I’$ŀĄ‹$I’$IR œë :í¯bŊI’$I’¤#rāŖŋÄ|öt‘$I’$IŠCI’$I’¤0t‘$I’$IŠCI’$I’¤0t‘$I’$IŠCI’$I’¤0t‘$I’$IŠCI’$I’¤8Ŋŋ+ I’$I:yíųŨ>žųŲ:Ūûķû|ôŅGũ]Ā ÄY?“ëg]ŧ‡ íīęôŠ=]$I’$I1ąįwûxtՓ4ŧ÷'ŗĐØø+ôŪũ}MW§W ]$I’$I1‘˙Ķuũ]l ⁏x"˙ŲūŽI¯ēH’$I’bĸņOęī*čd4h ũ]‹^1t‘$I’$I'”ôwzÅĐE’$I’$) ]$I’$I’bĀĐE’$I’$) ]$I’$I’bĀĐE’$I’$) ]$I’$I’bāôūŽĀņPtÛyĖ/Ė`QņķĖJ9y—ŲŊ×XpŪÍf,fËķŗé×ĒH’$IŌ‘ &1úoG1vä§øD|>x†?ū/;Ãģ(û]=öwũŽÂéCF1õ’‹ČĘøhd_ų[ŧēņ ~U{"Žį ]"Tžļ†•ë‹( —Sŋ?B4$˜J(3‹ŗį“JėīJJ’$I’ŽY´ ŋĖ5—Œ&ųcí&đ|ė<ÆO˜JCå›ŧ°n#˙}"…C>ĪM7įōŲ3Z'Ä3üÜ Ė9œgy’¯íĪĘõĶĪ:‹Ŋ÷ī÷wEúŲ‰ēDÂŦžķ/Ŧ(ŨÁœ“•Cfr jĘK()\Ciá:ōį>NūÂ,‚ũ]ßSV„đšå’Ë‚ŲĄŽÛĄž„Õ+ˇ08īvō2úŖ~}-Buy Ĩ%åTÖÔŗ?ÄDRĪÉ"{bˆ”he>•™ķ™›•ØÍ˙Ol‘ŠŠŠÃ-m%Lbr*™9SĨœšŸÆĸ›Īc~Q¤ãÄ@äŒfÍ_Č܉ö;“$I:´DūîęëšfÜŲ|ŦĶ+”ŋÄ]?ÛĮ°ĪO ÷’ ¸ūæ†?OÁžHˇ% ,Aūî’Éí—vÎAî%įōßk~=đƒŠĶųģi_&wÜ>Ųē.üŠGīZĪN�>Ã5 g3úwÜ˙ôōO LŦ¯ ĄK5ëæ\ÊŌÉĶ–ązYŽã"å/ŗ`Î6­ū* Ænæ‘)'ū…ŦēåĢ(GHĖČ";7•Ä@„úĘrJK xü fĖũR—}ĩY csrIÜ÷D$ŧ†Õ•ŲĖĪMëķ˛ģ]^ÅËŦÎ/'šHNv*ƒúƊˇP˜ŋ†ČŦydĒųBr.+Wä1¸åŋŅúJŠķīcÅÜ*ķˇą4Ģ_kwLę×}…œ’y”Ū?ąŋĢ"I’N:§ķ™iŗ¸~ÜŲíĻ}@Ãîí<ųô&~ے­üv{!üú7|åÆLžv&øÁ“üûûĨÂG`8ŖGv—¸4;sägIã×-ÁÅ@u:Ÿ™v=×˙ũŲ‡}ĪĮN‡33ršíZNéāå„H7ōÚr––FŒ]Lūũ]€`Ɨxäņe˘‘GNoō–ú0ëîúĻ]pcĪų,cĪû<ĶæÜÅēpk:fé„Ī’1m•fŦ`õäĪ’qÎy,ØŌŠĖ×žĘØsÎcîkíÖŖ-g4“'ũ-į\A~uwõ™šį}–Œ+ÖĐúrũ–û¸yÚį{Ūg{ۅˏmáú^´…ŽJ$\@A8Bjî<ææM$”‘FjÚ(BŋÄÜššœ)Ŗ¨¨âsIÉ‘ƒ^ 5•ÕΌŋ˛´œũÉ™‘›EFZ )‰)¤¤…Č™Kæ`¨Ŧ9…wÂÄ 2˛˛Čjų˞’Įĸgž`Vr ų¯×íÔ×ĘJĘNčúK’¤ėo.âĒ C:N{÷ zė`āŌæŊ]üô‰ü†äN;—øãVÉSŲp˛Æ ÔōÆ#÷0˙öo7˙ĩõré¨9xų<}‚tųčk'ĀjG(.,"Jiķg“z¸ˇfäątY/ŠŦĶnϰ>•Éy ™›™ 5ĨŽ^ĮâkJxįņÍ,Ę ‘$ŋh áČ<R[¯ëK)ŽD(-ŪGĩ[R\B”ąLÎ BQëÔŖ-įī¸öĻ ÷ŨYĖē‚ fÍKë¸ Åë(‰ɜÛ<8nx ys×P98“ ķČNŽRY´†ųsˇũĘ>P5ߚRByÍ~ĸœœAfö˛ŌZ+BeIEĨÔ׉)ddįJiš}Š‚ĸ•ë¨ÉĖãœĘŠ+SșŸGFuOå‹zÂ%åDSsČénĄÄšs“!ņP]<ēģŊ¨§õŦĻhåj2g“Y_Dņ;ÕÔG ˜"7o ŠÁ–[ģ*ōYN&{î<2#ąl‡Ča.ŧĶș7¯Ķ´zʡQ\ÖŧŽÁä 2sÚ×Ĩžō׊(~§ĩ I›CÎÄ4[Ú­`ų:ögį1ļϘâōj"ILÍ"'7ëāįĢĮļėÍ~+!2CAō++ЁæcZ} Ģ-'K95ҁÁäĖ]ĖŌy­ˇJ–°č‚9T΄œâģX^’ÁŌ7Ÿ 7ŌĶ|a]p •ķgFx9+ŠĘŠg0y‹Y=?‘õ‹–°zK%‘`*9 W°"¯õ8TOņĒģXžē˜Ęũ@r9ķ—˛4oAęYwÍ,.˜CFa*s 7ŗ0ãpķz˛ÃĢXē| Eå5DišũjáânoģûÁäĄü ÛVũú-ˇpË-ˇöáv’$Iũãt>3aɝ'Íaņ}9m˙mØŊƒ7ŪxƒÍīÔÃ˙“ļ˙=w^ô÷|îė_ķozģL|—Lž¨ÛĨmÜô¯nzŖoWá°öąs÷ûŒ?ˇûŪ.Ūũ*z(Ą˙×'ș§Ôņ›ßE€ĶųëŋĮ˜ŗÛÅ §ĒÃ8<§r— §K9Ĩá2ÉéŖŽø%˗PXŸÁ‚į7ķČâŲäN™BîėÅŦ.\A6•ä/mî=’™“M ZFqIû™ˇ&“œœÁԇK9Ø eÅĨû!c"¯Žļœ ĶķČ Ā;ë×QŪĄÄzŠ×• d3+'ˆP°r•¤2ëņŸ˛tö—Č™’ĮÜû_ayF%īDûĻŨb"˛‹‚õ[¨IÎaÖÜųÜ<w69É5¯/ ĩĶQõkkČ/Ž'5g6sįĪcVö`jŠÖPĐ֍'DŠS™šÃŦY9dĐsšĮVīJŪŠäŒ Õą*˜˜rDc õŧž�QĒK‹(OËeîüÛY8?—ÔũĨŦ+Ē�‚„ōæ29Ą,X8›ėġAR3RÔla}A åՇ/´ōĩ5”ÁØiŗ™;7ė”Š×¯Ŗ¤ BeÁ֗EȘ6›š į1+'•hi>ë_;ø ŖTQ™‘Ëü…ˇŗp~É5EŦ+ØÕ�õؖŊØīb§‚Ęʁä䖓‰z ĖaEy* ž)dKņ&ō—fPļr ÚzÍ #T毤8s1ųĪ/&'؛ų Œ^ũ0ešSüĢ_Qŧ"Dyūrg­!8˙yJõ+ į)Z˛„‚–æ /š‚š++ -}†ĸâMä/LĨlÉ æT‰ä=^Ȃ L{„’˛WX˜ŅĶ<‡X^cҜ•”g,fũĻ­l)|†EåŦœs[[]Úģå–[ųú-ˇt™nā"IŌÉ$‰1ŸîŽŋĘÍAËüŠßüßFŪįtÎlŊ¨gxü‡|@ Ŗ?Ũõ üÕMo°ą› âø.�ū{ã&~ĶŨ -īīĨ`cĪãš Ŧõø _ē:—Ës§ü›z^—āėTíņr„.ÕÍŋTé›á/JX_PiŲd'×S_ßî͞‚PūÅõ˚HˆĨÅáƒs—MËbVNĘK(kŊļŠ/Ŗ´’3ŗģôÆ9úrĻ0#w0T°>ÜŽĀú-ŦGää’l^§â’$OdF¨ã˛ŗfįžwP̝a$Hjh)‰‰$&ϐ1e6ŗfe7ßFŲEqY ÉŲyäd¤˜˜HJčKäŒ ōNI¸]Xûƒäd"%%‘`OåĢH= NėŖąƒŽ`=I‘“ҞÜā(BЃ‰V×P 6gP ƒ=ˇoH 呗“åEŦ_ŊœĨËfõē—)WĐáē9Ϥ,BJv.Yi)$ϤĘÍ%;c0ŅúDĘ))ßOjv.Ųi)$IɘÂäĖdjĘJ:ÉYÚ ;3•he•‘^ļåqh—nE*(^u+Ë“3cJK(—HÎŌBŠōī'7”FJJĄ) ™•Ą¸¨¤Ãė5ƒsY2o ĄŒ4‚G0ĄŲ,˜ØÜ^‰sČ"™ķ˜Õ˛˛Š99¤DË)-"¯ņČēJΙ˙K§„HII!”{?KrSŧrMķv& �I {7OwëPæč`˛ōϐ‘Ōr[Ú˛gX˙ĖB˛ņŅęŧ¸H’t˛9‹Ot*ä}~ķę“,}ė%^|i=¯XÎŌĮ yûėsÉútiã/áĘĪ˙ ãc$œ}VˇĨv*ú/ �j˙“Gųo”˙ž†€ŲWūŦz$ŋ×O.PësÎĖČåëĶ?s"ÜrĶgN€umšŠ@×õŦģâ—užžÉō˛Ÿ’ÛŨÅSu9•Q |5š™ĢąĖæ^ dd’+ÂaĒ ‘ŌŌ %93‹P(HFôaŠÂ“‘’-”3˜ÜœQ]‹K<úr˛fį‘ē~5kJXtsWŸúâu„ŖƒÉɛԞN5Íë”:ļkĀ’"5@§ņd”TR‹ ¯_™™d¤Ļ‘š$%ĨåļœúJj"ƒÉHíx–š–B ´’šmOœœz°×IOåŗ>ž2?‚õ Nî°ô@�y›OĖÛ HjVsŗ"TW”SY^Á;•å”Q\œÁ´y„R€ęjj"A2’Û×>…ŦÜ/5˙ŗĸēÛ6HIM&P\CM=´å,ÉÛ 1y0HË{čE[—vʗ“sÎōŽĶŠL^ú K§\ƒ`°†â%K((+§˛>B$m>āĨvÜĒÉ!Ú×°ˇķĨ¤Ļl¯`@�RCŠí b0Q" ŧ˜p4™ÜŦ´ede‡ŦnˇŽgīįé°ÉN^Íē›ŋBdîlr˛˛ČÎH$Ŗģ[öÚi˛¸H’t xw+ĪūÛžN=@>„ŗĪįĘܜ.O7:”öĄDÖîâÅ'vņâ1”1Ö§÷Š(¯ãTēÃč]’ILjZzŧt8’š›Į´v5Ĩ”ÖϏHËÅiÆlV.ČîūŌ9˜Hj*@™™É°Ž„˛ČlR"e”VÉ\‚%xd"\TB40‘ėno:†r2ō˜‘ąšEë("‹ę)ZWF49Y­ī‰ÔÁ`ˇë8LsÍÛę`=Ķș;—ä-Ŕ•PZ%˜J('ˇš'C$B„ũ”Ž^Bi—˛’›/[È+ßCšĮ*8˜ÁÁ(ûĢĢ!Ŗ.ԏz={ãvč HJZˆ”´Y@¤ē„‚õE…ɘ"H„ÁC÷X‹ęõæGÃGÚåÁÄÎojyô˛-S줿ąō‘Ymah0BrZb§Īi˜Ĩ3Ža]0—ĨKŸ!3#‘ ÕŦŸ3•×21pTķu§ûŖP!B ų3>K~×b—ãđ‘ÍĶq˛XTø<įŦ\ÅúÕ Xŋ4B 9“ÜEËX:%í°õ7l‘$édõ˙īĀ_ˇ›ôÁ‡Ũ\¤Čo Wr{ų –Í93›ßHÃß;léũNœ•Æ?LĖ?üm Ÿ<ãCū°ûˇė|ˇž÷ ō‰ĄŸaôČŗųØûdß˙ü/žēŠÃ¯ 0P–÷hh>ŅĶû){n5ĢOą§Ŋœ�ĄË¨æh Ë(,‰;Ĩũ…BŦŲËhŸsŨVDiáaŠKLl>÷¤2vâDzē\eg18 EaČŽßBy`lKØ"33Čēpõ@qŲ~YŲd÷y9i˘•ÉĘEEŧ!'̈Âp„ÔŲ3h쓍%l‰ļJ.Ĩ"ĸĮuL— Á`�ę멇Ží[_O=ARۏîL!4%ĐˆÔWP^RDŅē50÷käƒLFŪl˛ģŒĻÁÃ]ŽÜcÍI‚)¤†MååTOLév?Š”—d•Ö‹‹øcYĪ^Ô5fí�Í!G7_0%‹ėsJxüjj‘lFöjĖ”Ā!^ī&Œ‰Ôw~Sķ{šīŦęe[Æē]�‚ŠddŒ:ü-~áuT&wũũäļ}¨ĢŠßßCŲG;_OƒI&gå3ĖĪčôZ0Hrwms4ķ´Í"oņÉ[Ü2¸ņę%,š ÁõÛX:Ė|’$é$UĮÛŋkdō_ˇ×%õīÉ ũ–įÂuzģųė¸á- @5;7@Ÿ¯xÖ(Žģųj>×L|ŒOŽ< Gvzßg3âķSš-ũSŦzd=ŋęEđŌ˙öņÜŊßæ…ĶÛ¤;Šëį1ļ­RKāō_§Vā'Ę.—K2ŠW,§¤įˇ^bjķy ]îJ:üœ01‡ĖĀ~ƊwQZTB4m"­=ß3ŗÆBx ĨÍã°däL<ôM'ĮPNbîl˛Š ˇP^´Žp4ƒ3ÚŨƔ’Lj�¨)īzQyY§Axc¯y`Õ0e]u]O¸¸œhbÆÁÎ!õՔWüāĶMÉ!#ĄĻ&‰Š$#ėILL<ø¤y\‰CUĸ§rY"ĄŦ 5%néæ™Ūõa Š(-ĢīŨcuv={ëv¨/!ųrōKē;xF¨Ų@bķ´SIî§Ļ˛ũ{Ģ)YĶ2ĀmJJ7¯CuE Ņ`2)킧ũ55Æ‹Š¯Š!L$9‘ŪĩeĖ÷#P!BJĮ ˛ŧ€M•twOåąĪדŒl2ûЉ&’š–vđ/%HāPƒCÍ<�ÕaŠļT´ũ7˜–EäöS^9@O˜$IRŒ}ČoˇžEĮ›Îæs_ų÷Ũ÷]˜}.mĪũ9ë\&ūā�0ūõđ_ž\40œÎčŠĶÚ.Ŋđ‰ķ¸ję@û$Ÿ?>–ÄgÛ \üá‡ü‹´ī›tę.p‚„.d-dŅ´ÁPš†šWÜGq7׸POxŨmŦ(jžČ;ôEjĶrC´˜ÕĢ*:žŲ‚Éį1vÎËí.č˛Č НϠt?ƒĮ†Ú~ĩNĖ ‘-ĄhõĘÉ 'ķpŨŽĄœāōr‚DˇŦdŅę0ŒÍ´´Žeg†‚PYÔqĀ]"­y™ÃŨm ÁPŲŠJķWQP˛‹ōŠ ĘÃ%­YCaeą9ūō__BŅú5Ŧ+Š ē>B}}5•%ĨT’Hjr‚ŖČ¤ēxÅåÕÔG"ÔW‡)ZŗŠÕáC=•Û'ë™K^(‘úâ5Ŧ\÷%åģ¨ŦØExËËŦ^]@åāLrsŌz˜ízv-¨š×Su9•ÕõÔ×ĸCdg&S_´†ü×J(¯¨ĻēēšĘŠ0ÅëÖPT䜉ĄæŪeÁQde Ϟ¸€âōjĒĢ+ŋV@q $§&B0ƒŦąÍ¯—TÔSЧēü5 ËęIÎĖęĐ[$°ŋ„ĸ’ŠævĒ(Ą¨´†Ác›Ũ›ļ<ûG¯…˛ĘYŋú5*ëëŠ,YÃÍwÕ063H´2|č§)í|= NaVî`ÂKžĘō×vQ]_Oeøe͘LÎÍ­ĮÆæÍŖīS\^A5Ŋ™§åkXtķ5Üŧf åÕõTWīĸxÕŠIflLG4–$IÚīßāŲ­Ũ(ûąø`Kq—{Ÿm}áũŊūšÆãSÃ#4œąŨ=‘éđ2F“Ö÷•éû(yëĀ.ēų[ŦŧīģÍËf0ēË{OíĀNˆÛ‹�Éš˙y–s ‹ W37;ŸÔĖlÆĻ&’H„úšJJKÂÔD!šËōGîoyĒO÷˛.cZņÍŽ˜ÆŒōųĖČI%XSJáęu×$3mé”vC$’™“JtyEŅ ŲímI›ČØÁĢ)*(&šœGfÚá×áXĘÉÎËepá:Ę*ƒd¯Čét;K"šssXQZ@ūœ+¨Ÿ›Gfb=ī­Ŗ ’Ef ¨›ą-b)…ŦŲķHÜRDiiõQ&15ƒis'JiˇqŌžÄŒœ×ØTR@~Ņ~ĸÁ�ƒ§ĘÍ#ģe%S§Ė&/XDQŅJęŖL&%c ŗrB‡4zQîą ’š;š[(* S\PJ4 ˜BFæ fLuČĮIwį¨Öŗ›:edeRZPJ~~9™3žãv’:e6ŗRļP.Ą ŦˆhÁÁ$ĻĻ’=k YikŸš;›¯Q\´†âzœœFöŒÜ–ĮŦ7—5ƒ"Š WąŠžæļĖÎ#'ĢĶ?6‡P}1ų+ĢŲOäÔfäŒjk§Ûō¸ėŊ”˜ĮŌe%Üŧd9ëađØ-쟜ęģ(ģy5ŗfĀúÂ)}7_/d-{žG—°|Ņ ō÷G J(g1ųËžÔ˛Oɞ•Gę‚5ĖŊâef<ūŸ,íqžnLŧŸüewątåmĖXēŸh HrZ63V<ÃÂΡ)I’¤Sȇüļ0Ÿ'ã¯įšqgw(÷¯G3æė_ŗīogp帖 ãũßŗééĩüû€ėå$áh~O žu°WĪ€Ō˛}˜Fî¸|˛ÛJÖQö?ŋĄá yîÃĨŗA8ĐúŸŠŠŠ>zĮ ĶūĒOËĢ.YĮãųë(.)§z„(AÉɄBə6‹Sēö,(ēí<æf°¨øyfĩŨÖfŨō‡É/*ĨrƒI͚ÂŦų ÉëüäŒōûȞļš2YRúSōÚ^ŽP4įæG<íJīĪ:ü2ĸœƒÂ,pųõ9Ŧ|ķ‡Ũ†J•¯-a銗)ŠÜd2ržÆĸeNž‚üąÎ؏–zTAŅĘ|*3į37̝–$IR_[°hÉ1Ėä3~™Ģ.Mō!QÔPų&/ŦÛČ×äįáŒâēeWķšŪ>fŠÕŋbÕ]ëųULętrXątņ1ÍāŖŋôQMšUWW“––ÖaÚ 眞Ē×1kō]”į>Cé˛n‘$ ]$I’N$Įē´&1úoG16ãS|">īŋGÃ˙—á]”ũŽūxüpĪL›ÅÜ Ÿj7čoOŪgīÖĩü¨°ĸĶāÁjīD]NÛ‹Nuõ-ŋR2X0×ĀE’$IŌ)$RĮÎ˙ÚÆÎ˙ęīŠ­ŋ-\͇{ĘŽNZ†.Yu Åå”Ŧĸ 4JęŦeĖMëīJIũ)œųĮ–fK’$IŌņbč2…׹hQRɚģŒå Cũ]#I’$I’ÔK†.Ų”û){įūūŽ…$I’$I: §õw$I’$I’NF†.’$I’$I1`č"I’$I’†.’$I’$é„2hP× w ]$I’$I1˙ņÃũ] l !>ĄŋkŅ+†.’$I’¤˜˜õ•ŧ§K‚N§ â_f_ÕßĩčCI’$IRL¤z87ÍģžøœA†/ęg}üã|ũÆ9 MIîīĒôĘ öõǍ¨ %%ĨopÚ_õiy’$I’$IĮęĀGéĶōĒĢĢIKKë0͞.’$I’$I1`č"I’$I’†.’$I’$I1`č"I’$I’†.’$I’$I1`č"I’$I’†.’$I’$I1`č"I’$I’§Įz>úKŦ!I’$I’4āØĶE’$I’$) ]$I’$I’bĀĐE’$I’$) ]$I’$I’bĀĐE’$I’$) ]$I’$I’bĀĐE’$I’$) ]$I’$I’bĀĐE’$I’$) ]$I’$I’bĀĐE’$I’$)Nī<Ą˛˛˛?ę!I’$I’t ]Ļu ]222ŽKe$I’$I’N]Ļy{‘$I’$IR ēH’$I’$ŀĄ‹$I’$IR ēH’$I’$ŀĄ‹$I’$IR ēH’$I’$ŀĄ‹$I’$IR ēH’$I’$ŀĄ‹$I’$IR ēH’$I’$ŀĄ‹$I’$IR ēH’$I’$ŀĄ‹$I’$IR ēH’$I’$ŀĄ‹$I’$IR ēH’$I’$ŀĄ‹$I’$IR ēH’$I’$ŀĄ‹$I’$IR œëėũßßĮz’$I’úЈOũMWA’N 1]<`K’$I’¤S‘ˇI’$I’$ŀĄ‹$I’$IR ēH’$I’$ŀĄ‹$I’$IR ēH’$I’$ŀĄ‹$I’$IR ēH’$I’$ŀĄ‹$I’$IR ēH’$I’$ŀĄ‹$I’$IR ēH(fĐÔ�� �IDAT’$I’$ÅĀéũ]I’$éxŲ]ņ;~’˙,īũéĪ|ôŅGũZ—ĶN;ŗ>~&˙<ë*FĻ}ē_ë"IŠ {ēH’$锰ģâwüāŅĮih|¯ß€>úˆ††F~đčãėŽø]WG’†.’$I:%<ąæŲūŽBWƒÁŗn’¤cfč"I’¤SBã{īõwē7húķŸûģ’¤0t‘$I’úŲ@¸ŨI’Ô÷ ]$I’$I’bĀĐE’$I’$)fč˛{5—ž3†ôsŽå§ĩ=Ŋš–Ÿ^3†ôsÆpéc{ŽGíN æ‘~ÎŌ¯y‰†PŽ$I’$I'’ēH’$I’$ā ]$I’$I’bĀĐE’$I’$)NęĐ%ēûu~tĮĩ\zQŖÎCú9ã]4™wŦĻhw´ÛyvžĘ}ˇ\ˤ/´Ėsn6fÜĖ’wv¤áÅæąJBwl%ēûUÎČfÔ9c˜đípuk?/Ņ=~{“ž0ŽšŽ_˜Î +ļŌ<œM;ÖūfūSsŲŖ>7‰éˇŦfû!ÆēŠ}ë9–Ütž0Žy•sŗ˜;…Ome_ˇĢ\ŖĮnczKú\6—ūË÷x~g÷Ŗ¯ė[{éįŒaÔM¯Ō]qĩk¯=ėë]ÚáÚŽn›J’$I’ÔNīī ÄJtįjĻĪxMq$Ę$įËIĸ ėÛõ&%/=HɆ,\ŋ–h›§vĶmLŋu#U­ķLJ q/ۋɓwū’ÂÍ+xņŅŠ oy GmÜÅOîü!…UÓ9‚!éq=Öīāŧ{ųéM X˛sã3/fd]˜íĨ쨴j>ûâײ”ģ˜š˛Ž1YcÉIodGɛŧõęƒ\W`Ãú،<¸Æė^;Ÿ™ßŲFq ]Č哒 á]vlŨÆ ÷lŖđÅY<õĖ7ŸĐļÆŪ4“¯oރ¸ĄŒ›t1c’ vįFÍx“ĶãûjstëHÛûhļŠ$IR_;ãœ/sĮĩįķIūČö§ÄO߉ôw•$IÔIē4°eåŲŲĮ¸Û^dí éŧ ˛ûŠyLŋįMZņ*W<ņe†�ė{ޝŨē‘ĒĻ$&ßģ–‡§;8Oíë,˛Π›ŋÅÂĩ!ÖÎÖ<Ŋ%[iz+ŸŌŋÁ†7f3˛—×ûÖy‹ȏŗžÁ†7Žl›w÷SWqé=eė\u7$Œeé/ÖrÅđÖ×0ũK÷ōVø9^Ø9›ÛGĶ6ũkËļQĮP.˙áZ–OŌŽ9Â,šæZžÜ•ĪÂSŲüŨ Zō K6×AÜXŽē]Xq0‰™#nīŖØĻ’$I1?t8ŸüĀ،zēH’á$ŊŊ¨ŽŨûš€FŽkq`äu+X›ŋ– w_ÜvqžãéՔ4Aü¤;YŪ>��r1‹ŋu ņ4Qōôsėn+ŠuqņäÜÕûĀĨƒĻ\÷•æ9i*c�>‘ķÅŅ�UėŪsđvšOįŗŗ â'}ƒÅ“;E !n_p1ņ@ՆįØˆ˛ũÅ׊’.ũ*˙ÜĄwH€‘3īäúQGą>Ŋtäí}äÛT’$I’¤ūt’†.ÙÔQ´â{ŨŒõ1„1Y!FoŊĪfÛKŪâ8˙Ō I Ģ„Ŧ‹9?ØfGįņT’. {t73õFz&ã‡wš6$‰!qÍë1~ܰΠcȐ8 ‰†hëzíaû[u�Œ™Ô}ũãZęßĻd@oīlâ“ĸk^”ÎÄŦĄGšR=9šö>Ōm*I’$IR˙:Io/ ŗā;|1ŧ€WÂųÜxÉsħ‡˜˜y!Ų“.dbÖh†tHęØSĐÄîĩwķĩMŨ•YĮ>�ĒØYēS$ ;úŪņI]C‡@kåâ–Ôu–öˇÕ´Ö­šūI vˆĐ!!‰á @]ĩĩĀč:öÔ$0<Šûy†¤ŪíÕj™Ŗiī#ŨĻ’$I’$õ¯ē´]<ÃĶh†Oåá—GņÅĩķäK¯ŗcĪ›ŧ˛įM^yöAˆÁäyw˛ø† -ƒ´Fií4RUē‘ĒÃÜDCįjŨôé­ÃÍÛÛR[ëw0¯én9æ÷6÷‰m8ü<CvŒŽ˛Ŋh›J’$ģOœķyūnč؜yÆÁig|úŦļĮgü=šg´Ķåũ÷ųÃģŋæßߊ?Žĩ”$ T4tIhéũŅHUžIUÍw֐Đéi; éäÜp97@´v';Jßd͆Ÿķüæ]lē˙ŪŽzŠ ßÍ$� .‰Ģķ‹YšÕ÷Ģ[-JSc[˜ŅUkĐh SZįi:ä<Ņh÷>ŧŪeĮĐŪŊŪĻ’$IĮę3ä^›Ëį>včwœ9ō&ė4ņƒŋá˙Ũĩž1­›$éD00Įt2‚áI�Uėw@Ĩ“}elß0”1ŖÎ†ŒfüĨŗYüč‹lÍĪ#¨zņqļ4� ct@ûĒŽ&hčoI¤hd_Õ!ÚĢĄŠ} Íī> ž!C�ØW×}P˛{g×[‹ÚwBęnŽÚÖė°úĻŊŋM%I’$Ię_3t!Ä哒€&JV=ΎCvž¨ĨhÅyĢ HŸĘåãZ&7ėaˆįøÉĻ=ŨŐu1įĮM Ôļ„.ãŗ†Mlß°î¯Ų÷°}S)ģká–§˜IoŠ?ėØđfˇõo(yM4ú;˛yžqŖâ&Ū.w „Ų´ĩk€4÷!iĒ­ęf9{ØRܛ1`ŽĸŊx›J’$ĢßRđt/ŋē•MoüÛļûũļwüy÷›^ÛôjĪ>ŊÉ^.’$`†.ÆĪŋ“ÉIĀž|Žģæ{îėx%ŨWĘOnšŽ¯ŊZ åōoÍi~Ė2@ôM~|ĮŨ,Ŋķ[üč­ŽWāĩÅŲŪ$Ĩ3ĻĨs˘kį’M[īeá‹{:…ĩlųö|ŽûęuLŋķUzč{Ķ/Îŋj6ãâ ië,ŲÔŠ†ĩ[Yr˙/i$ŽŅ×Îf<�Æ_z!ņ@Ũ†øÉÎökÜĀŽßãųÚ¸.ËI=‚a�ģ6ōŗNķl_ņ-žŦŠī2OwޏŊb›J’$Ģ˙÷βųß6Q°ņāßåīĩŊŪXū^+øˇmŽį"Ij30Įt2•‡Ÿ¨ã†› 8œĪ×ŋ”ĪÂø$†$ÄACUMÍī‹Áåß_ÅŌėvŖx šŒÅķ_bæũe,ĪËægĄLÆ K"jĢvą=ü.M å‹wŨĘøÖûe†_Éōī—2ķŽlēs:žĘdüč$Ņ:Ū.-eg] ûG–ßũåŖRQ,œÍōī–2ķÎ_ōÂWŋČŽĖ 9?=ęö˛ŊäMĒ!iÂ7yøēôļY&ƒÛ3ˇą¸´Œå3&ķʸą Ohĸvg˜ˇjG°pū…<t˙/iĸé`(2úJŽ=ĮŌđ.~<c2[˛Æ2<ĐDíÎRۊ^Č} †ąč;{ÚåHÛûhļŠ$I’$Iũhā†.@`ôlžúÅÅlY욟mx“{ǍĒj".>‰aĄQŒŸtWOŸĘų]R�cnxš ŖŸãĮOŋĘöaļ„i"Žø¤aœ?u—ß0‡+FwnuøĨ÷ŗaÔT~ēr /”†)zБϏx’†‡øâô+šqŪTÆ āZGN„Íé/ņĐĒįØōÖ6 K!>‰áé—pãĖ9Ü8}t§f‡ņ•'Ö2dåüxÛŧ]úKvÆ'1zÜeÜ÷čWšĸáž:ų’Î??ņ4eđäÖ0;ˇū’ŨņC3áĢ<š`.ĢžÅ}@]4J”Ã?éČÚûčļŠ$I’$IũeЁ´ū§ĸĸ‚´´´~ŦŽ$I’ķo˙vŸ”ķ×˙0ŸÅS›õĢyõa–ū[o$Đŗ•÷}ˇOʑ$õî2•:Ļ‹$I’405žģ?|�|đGöžû^ī—$ēôíE’$IŌ@ķū;/ņîzŠŋĢ!I:ØĶE’$I’$) ]$I’$I’bĀĐE’$I’$) ]$I’$I’bĀĐE’$I’$) ]$I’$I’bĀĐE’$I’$) ]$I’¤~6hĐ ūŽ‚$) ]$I’tJˆ?ë,8p ŋĢŅՁœõņ÷w-$I1`č"I’¤SÂŋĖž b’Aƒšë&I:éēH’$é”02íĶÜrĶâĪ:k@ÜÎ3hĐ âĪ:‹[nšÃČ´O÷wu$I10聃},+**HKKëĮęH’$I’$xēËTėé"I’$I’†.’$I’$I1`č"I’$I’†.’$I’$I1`č"I’$I’†.’$I’$I1`č"I’$I’†.’$I’$I1`č"I’$I’†.’$I’$I1`č"I’$I’§Įz{˙÷÷ą^„$I’$i€ņŠŋéī*HĮ]ĖCI’$I'/Ž%ŠoÄ<tņ€-I’$I’NEŽé"I’$I’†.’$I’$I1`č"I’$I’†.’$I’$I1`č"I’$I’†.’$I’$I1`č"I’$I’†.’$I’$I1`č"I’$I’†.’$I’$I1`č"I’$I’†.’$I’$I1`č"I’$I’†.’$I’$I1`č"I’$I’†.’$I’$I1`č"I’$I’8tŠeĮ‹ß㆗’ųšq¤Ÿ;ŽĐEĶšîÛĪąŊ6zÔĨžŊbéįŒaÔ-¯÷a]ca?ĘCú9‡˙ ŨQÚß•tLJYô…–ĪôžÅ–†Ž¯îXÖ|Ėēôą=ũSŊ“Fģvnũkũ^šc [vwū^ ŗäĸ1¤Ÿ3ޝmęëēIJl ,Ũėw-Ŗ>7‰é7=@ŅžŖ?§9RĮ˙h îë‡Ū&m×ŧDCĪĄŪļEßĩŲ‰sÎ+I'ˇĶûģŨŠîá§7]Įâ­uÍ˙‹'>ĨąjÅĪŪMņ†—XøÄ*ūu\B˙Ö3Ļ$ Ę°Æ–˙Fë¨Ēk∖Dëš' ôSũ$õšēŸsßÚ9Lŧ!ŊŋkrR‹‹ʐˆFë¨ĢÚEņK÷Rŧá9.đ)–OŌōŽ$Fg^Hv]ã†ļ8ŠWZ÷;�ĸÔÖŊË[›ᯒ=<ô‹G˜æ~vÜuØ&í’NæķKIŌņ6 C—ˇWÎo\âFđÅīŽ`éôŅ$� ģ_åž[ŋÅĪv•ąüĻ{ķ‹{˜xŌ~/ã+næ+­˙-ų™ŗ~N™,~yWœ´ë-ÚvŽz€Â™0ÍĪxˌ˙î‹<uisG÷måĄ;đãŌŊŧpëÆũâiž2`W|WôkMu2iŋßDw¯áēŧ{)iü%Ŋ¸‡i†­Į]įm"IR, ŧۋĸ[ųņÚŊ�¤_ˇ‚‡[€„‘SYúč7ÔŊʏ7×r°›čĨ,)~EšYŒúܡØа“ŸŪ2ĖsĮ1ęs—rŨŠ×éžo-Ûģ™˙”MčÜq„.犝=n×ĩô0ËčW Ū”Eú9cČŧŖ”ƒĢeËY-ˇm%ÚV˙lm*å'mm2‰™Ë^g_‡2{j Ią1–É’ ņ—<´r'‡Ŋá ē‡Âeķ¸ô ãš?į_˜Î •RÛö†vĮŦ’pËqp Ŗžp 7T­ŨĘ}×LjųŒĪãGoĩ˙„ŸZĮ€Āđ Üūč=|1 hz“'ŸŨŲōJ7]ükKųŅ-W1ásÍí>ę —rŨˇ_âíļÆ9’cm'‡ŨĻŊ=ÖwÖ~?ØIᷛë>ęs“˜ųíNõqŸ:î#¯äęŦ8�ö…÷ļÛ~Ŋh¯ˇGpÔY”ŨžĮ š“;†ôsŗ˜0ã6~TŌžô#ØˇVč(÷ãūԗŸĄvšvōüŊiŗVŊØ'Žz{K’bmā….;ˇąŖ`—yt×ׇ_Âåã�šØQ\F”��@[îųEMŖ?n 4PôíXüę.ęéLŧôÅwŗhC]§Ŗl_vWßŋ‘’čĻ]w%ããwņĘ=×rŨS­c(jũ-œĢ.& ¨Ûú*;ÚχشĩHbÚôLmõo đ;ßâ•Ā%ÜxŨÅ žKÉ͎qCÛXŊi Ią’5oãâ`ĪÚ)ėöL ĸ;ŽãëOoc_ŌÅ\í% îbĶũķ¸áŠĒ–÷´;f}įn^`,ãGĮĶTWÆ wĖįē›îeû° ™˜ ąjËo} åøqŠ.æę I�ė));ÄÅĪ~ô/ķXūę.ãĻrũĩyä k¤øŲģ˜yëĢ-VŊ=ÖvÖĶ6ííąžŗvûÁ˛<T5ŠœIŖHh|—’gocáÚĒ^.ŋSYîS}$J4Ú@ >ŽeûõĻŊzŗŊz{ÔÕî§Žeú­ųlÚĶÄČI—qyV á,Ÿ5“…Å­—ųŊŨˇÚ;Úũ¸?õågč +įķО$ÎĪJ'ĐRÖĄŊÜ'Žr{K’boĀŨ^­­j>yĘČáŨŊc#‡Å4UÕŅ@RËôFö%}ƒÍĪ\Ép€Úį˜ššHâōײ<;�Ņ/ŗäK3y˛ũwPíĢ<´v/0”ë\Åâq¸v×]tÅĢgËĖ{˜ØvĐi@ ûJĻ û9OVmcĶ[0~°sÛë€a—pyVûĶ—&Čü&O}˙b€œ¤Ŋdßŗ‹Ī>ĮÛ7|“1GÔ’úܰ˸}úŽ~v­,eÚw3ģž§!LņžxŌĶGpų÷īį_GÃî¤]Lž/omØFíuWrphˆFFŨʋ?˜@Bíhj˙ŋģ)iÚÅîô§ØúũLû†ąûdgUÛwÃų §î1`øč$xŠęötßŖ!LņŽ&ˆŋ„ÛŊ‡œ�Ŋ’É+ˇQ›žÔé×ųŽĩŨ•ŨĶ6=ĸc}gÔ˙›Ęĸd3™ë_ĒcĮæ0 3‡‘ā>ÕĸėÛô�?.ˆgâ¤PķäŪ|G{ąŊj7ōdo΁:kx•ûV–ŅH<“ŋ˙]:ˆrõ˛é\ūô^^Xņ7fĪedÛ =ė[Š?˛s–ØÛūíéLXŅyjĶ|–ÛĮĩŸÖ—ŸĄ&†}•ÍO|™!Dy{Åtž¸j/;Ÿ}Ž7|“ķ;W§7ûDÃQnoIŌq1āzēÚũÆŅc¯ČNßÍįOŋä`˛'ĖÛM@ÜX˛[ŋÄ!ž˜™ÔqĻÛZŪ—Îč¤jkkŠeXsāSϤĶ–1 „¸úĒĀģlŲÜÜ-~wņ6ö�é—^ÖéË;Žņ“/l; >î†Tímî„m!Н%0~Ū\˛â ęÅxžģžá XZ°Íŋxš %aHËq-ÚĐ)0ˆcĖäĖæĪü Ojžvū¤Pķásø(FÆ4P×Č)} ˆļ6uā}aŒL7˛đK×ōĩekxžÆĪŸË?OĪėôŊĐÃąļŗ^mĶ#9ÖwĮų—^ØrŅ`严�45Ô5—í>u\ß:žŨrÎ'ûĢëØĶÃĻ~‡Û'ˇė-Ŋi¯Ūl¯ŪžuöÖ6ļ7qđÅI­1A€ķ/Ŋ°yŪĩvˆö­.Že?î{MīRUÕõ¯ĄËĄˇo?CįOŋ¸­Ŧ1“.hūų°jģģk´ŪėGģŊ%IĮŀëéÂđ$†�UM{ŲšĻėü†ZvW5?Ō'>ihģ_Qâßn€ē††æĐ& ĄŨ9t !8ûG›ß×´Û˙qb§eUąoĐv—SĮe #/Ŋ’q+īå­ÍÛØŊ ‰í›wŅũíYņíŖ5âj ĄáHÛBRL ŋŒÛgŽæō§Ëxhå6–w9ä4°ãŠģYōô6ŪŽj¤Š‡ââZ?ķ­Ÿ÷� Î͚ˆr*ĸėÛŲr @Ō0ē}ˆL “ÛŊ“Ú;ČĻ=oōʞ7yåi n(“īZÅÃ3ĶÛũpøcm×{S{ˇM{Ŧī*!îāB]‚%÷Šãáā“ršh¨ĒŖHŋv-î ĩí;Ŋk¯žˇWo΁ēĖר:_B‡ųˆo~jbÔvڇŋouu,ûq_Ë~p{¯ŌíģĪP€„öᒁ€:ĸŨzŗOD9ēí-I:>^č’~!ã“ōyĄn/EΆųzģ“�öm䅷�â8rˆ�{ģ-&H �4E›ąh)¤Ąļąãûⓚßw7>8‡Î=[‡œ'ƒÃ/áęŦxkëFŠŪJĸd'ēŦ›Ā*JCcģoô†Ö__HH8IÚB:á8Ū7Č~qÅ/ũŸMęøAŒßÍ ÷l¤.nWß{+_LO€ÍßãęUeĮžäSõPû:?ÛÚüŨ0zÂŨ‡.@¸Ų<ö‹Ų4ė ŗŖ4Lņæįx~ķ^6-ûĪOxļåŠGĐĶąļŗ^oĶ^돌ûÔņqđI9o)Ųŗa Ûįßßö$ÆŪ´WoļWo΁: ġÎ×Đa>[ztÄÅwûˆå#Ŗũ¸?Ųg(JCcûŌr|ˆëūøĐ›}"°ķčļˇ$éøpˇ˜Āõ׎"Øķôm|míÎļn™ ģ_eŅMđV0ė2nœt˜oūŅĄæûæ›vąŠu⎆­ŧPÚ)ņ}câ€ĻFHŸĀÄė LĖEB´‘č!Nž!ä\u1ņėâ…eųloŠ#ĢÛÛ šØąa[ˀQvl~ŗų÷ôQŒIā$i é$0d*ˇĪėbĶæŽÁōîŌ]-ŸÛ˸qúƏA´öŨ曏íɧā1 Zģ•ûnē›M@ü?ōõ™‡xlīž­üôąīąäŝ$ 1qúl?ē’GMuėéđÕŌÃąļ“ŪoĶŪ돌ûÔņ`Ėüīpõ0 n#‹–m=xûI/ÚĢWÛ̎į@ģņņ@Ķ›ŧ˛šĩVQvlØFĀč‹ĖÛ,6ûq:˛ĪP;6ŧŪ˛ÍŖŧŊĩåø0ŧûãC¯>CGģŊ%IĮÅĀë錚aKÃ×qûæwŲôËˇ,žø@”ÆÆ–›ņ°čŅo2ūpŊX‡ü#WOx€’­uŧrįLĸ›CDÃÛØMķ ŧm}?‡LåëĶČÕĪîâĮŗŽbߤQölŖ°ô]ŨʆėPŒ×ļo$d_É´¤ü,ŧ â.äōIŨũVGā­{™~ÍëŒOĒbËĢ{8Æ]ueķ—õIŌŌÉ`Ėŧo0yíüæ0 áŖ‡Į^šv­aҞ:FVmŖ¨v(éÔągĪë<´"Äí Žō>ūSäĐ6xf´‘Úē–[âFpõƒ÷sČ ĘwyeU>%ŲQ| ã‡CÞ7)Ü ģÉzlôpŦí¤wÛôbFŌÛcũ‘qŸęLnŋû2ŠæüœĒ—îæžé/˛4+ĄWíÕģíÕËs ÎĻōõųkØrO›î˜ÎĖM™ o S´uoķgdÁe}ŽÄb?>Ũ¤ 2YürˀŲŊpdŸĄ8o=ĀĨŊ<>ôę3ÔÛs^IRŋx=]�HįŠG_á•{g194”xil„øac™|íwxáOķĪŖ{ú&´{W˛pÂâŖ{Ų˛5 “–ņđĩ#š_ŽF[~y0ūģkyōļd\`/EĪŽŖp'œ˙åī°ö‰ö#ôp“3ãˆĪžŒœCœŋŒ_°’“ö˛es ņ#Ȟˇ’ĮŽÖZČÉŅŌÉ ábnŸ7ĒëäKŋÃÃWeXÜģ¯ũ9Ûã.ãá'V˛øĒÄŗ—-J9äĶĻ{tjÚĪŦk„¤dMŊ•ŋü"Kŗķūđ+yė‰īpyf<û6įķãUų<_ŌĀČIsøq~×Ŧíčˆļi/õGÂ}Ē$dßĘŌIIĀģüėÎØŪ<(GíÕģíÕÛs ŽÆ\÷4/Ū;‹ėô&vlū9/”42$3%ĪŦm†úB öãŖq¨tĢöÔu3˜îĄõn›D[ėN įîûšž—Į‡Ū}†Ž~{K’boЁ´ū§ĸĸ‚´´´~ŦŽŽZíëÜđOķŲԘÄåų›XŪáĻß0K.šÉ“Uq|ņ‡oņđä~ĢĨ$ÄŽÃąö°Įzéá~,I:Iu—Š ČۋtöŊĘĸ{~ΎˇJŲŲņ™_åëžŧHŌÉÅcŊNîĮ’¤SĐ�ŊŊHŊ­ãíâmėŦ >a=xå =$Šëu2p?–$‚ŧŊH’$I’$éu—ŠØĶE’$I’$) ]$I’$I’bĀĐE’$I’$) ]$I’$I’bĀĐå˙oīîŖĸĒ÷=ŽŧáŒĶĒ Īë:Ŧ“Pį‚%°Ō0[Ą–OiZ™ĶĘ|ČĢ„Aq}ēÂącjāĩ´ŸĘDͧÍĖ{2ĪQņ,ĄÛÎJ¨ŗ˜îŅá\cĒĶP8ƒÆũcPy„=ÃÃûĩ–¸göėīūíŊėųĖoī �����`�B������ē������€Đ�����Ā�„.������ t�����0�Ą �����€]������ @č�����`�B������Ŋ€’ŋ—Ŋ����@v{ˆŋK�|Ž‘.������0|¤KđMF/����� Ía¤ �����€]������ @č�����`�B������ē������€Đ�����Ā�„.������ t�����0�Ą �����€]������ @č�����`€����� ũŗąkËöŨúéį ũōK•ŋˁētéĸŽīމÖí=Cü]Đ.0Ō���@‹ØÎØõö†Ír–˙LāԁUUUŠŧü'Ŋš~ŗJ˙ī[—´ „.����Z$ûƒũ]|ĨKUUũĸwˇėôw%@ģ@č��� EĘúÉß%Ā—ēt‘ŗŧÜßU�íĄ ���� YĒǏŒ đĄ �����€ÚhčâÔŠ‹5aØŠ ūcĻ+ũ M.—åsígXŪ��ĩIDAT.}œ<@wGßså_Ü4fŠæ,ßĨ“ežĒ#_é‰÷hÜ›¯Ø žĒÍŋmprÉPŨũÄģ*ņËŌÛŽæˇK[Ū—kķÍ6o?íŅrô§MŖ?m}—öģ4į°ŗūËeģôtÜPĨįų¤H*;øĸúGÕüãWŲréäÂQē;áE}ėŗ>�ĐŅĩÉĐålÎ<ÍXņšBĮghŨÆ5Z<ˤÃiĶ5˙j',AČÃZúÖj­}kĩÖŽX ŲÃŦr~–ŠgÆLQV^ķÛ¤lĮt Z˜k@Ą�ü‰cÛ ô§đ‹r^ņ†Nvž_ÚœāÁIšíÖŪīčTíá*Ž}ÅĖJԐ`˙Ô�čxÚ`čR¨ÍoeL†ŌĮĮŠOT¤MÎĐŧDˇnܯŗū.Ī,Ŋ­Ø˜hÅÆ'hääZˇsf[ŋ҆´Ĩú´Yß\**(–Û¨Zø ĮļWčOá–¸īܯeŲ6—…jėËĪ*Ęž[¯î´Õ˜n×ö%[eFķ õWq�€¨í….%ųĘ+ŊEņ#ež<Ņĸ˜‘2įę~ˆė˜ĖᚲčwŠpŌúû•éeųZŸ<EC<Ãčû'NPʆ|ũ(IrhûÔû4c_šœûæčîčqĘ*jjžĘKõéōé™0@wĮ ÕČäwu˛æöđâsĘōŪUʤQęįÚ?tŌ<­ĪsÔx‡CĮ6ŧ¨q‰Õ—– ›ĸų;Šë×b@mrŲõéĒŲ—8@ąŅ4č‰yzŋ‘îĮŧ×42n¨Ļī°yY{ĄŌčéėBO­‰(6î úšŽÕÆ\–ĢŦįĮŠÜ=ŠM§9rUŗ…ęËÕü„{4!Û^kęɅčî'ļ^*[ŪöųÕõįęũįG)6nļöÔkžK똯= =í7J–QYYžÖ'OĐ „ŠMœ ”ÅW.tŲõéō+mß?q‚æŦĘ­˛6Ų.ŪĩC}׸Īĩ&oļyg=ļ}ū´ÕkkŋũŠA,*åšŪ*Ų¸TÛíMŧכū-öˆRƇ¨ā­LíŠŪ_Ęfę͍M{\a—ߨô1Ôôq�čėÚ^čRú•ė QXn íĨ`÷7*jędĨ3 ‹SŧU*Ę/ŦūōęОEs´ŌfÕ´×7骜mZŽĸˇS´ā SRFŦؤŲá’%1]ülĻG45ĪöœLmÖ#J߸IÛV SpŪj%-:rų H“ŸãüDÉke‹HŌڝģõQöJ͎°iÍŦÅÕ'=.Z>]3Ū.UTōJ}˜ŗM̟ŗęԊéJÍi|Ãˇ¸6štrÅ ĨæHC­ĶŲYš^Ŧ•ŗfëũĢ]ø_˛KŠÉģeŋLYZŊŽŨlrĢhãk:•Ē>ųŖNæ,PŒ}›RWå^Ų†i)Ú` ׋oīցáxOk>ká#ųZĨíÍ2›Ü˛ī|GĮĸ“´nc’îˇÔ_”ŲäVQöZ ^ĻüYõ–mËbMœĩUĻŠkôé‘˙ÖO˜txEfõ5ķN[2CI9nÅ/Z§nĶڗûĒlĮÍX^hlģ´`Ÿk=ŪŦ[į=ļ}†ū´õjëčũé5pɤ°1/hlđįZŗęH#Á—7ũ!ZÎŦ>SS5ÂrB+WåęGgŽVŽø“,Ò4-ęŌO~^ė‡M‡��Hū. .—Ķ%—˞Ôũ2h–I.šéRCˆÂ‚$wŲw*“ÔSē?m“>TˆÂB=' =CŸŅØėũzķxĄ48NfK L&I&‹‚-IŽ&įšÄ2Z‹į&ǧ$…ÍÔŧ§r5ęí}:îLĐKĶ˖ũ+•”éŪá ē3T’BÕ3mĨŦà ļHrҚß(bę6Íl­ūŒTÍ+ø\ĪlÜ­ĶÃgęÎZĸÅĩ9i}ŽC}ŌÖiJ|$),mœî­*+uĒÆĪ^’3Wés3e‹ĪĐĻYÕ#˛šQģ+|´^lõĖœ ҎhoqąėŠSXŲmĪ—âĨjd”į 47I§r'jCswšZąí큉Z;9ÁĶÖ pE<ĸiņA2K2Į'(J’-z’&DxÖ)l`‚B_ßĒS6i¤ŽčũOJ5kĩæÄW§­÷ŋ y…'ô؎­:6+CƒœĩK öšVãÕ6īŧĮļīПļZmŊ?ŊVæHMK~XĪĘԛãû)%Ę\˙=e^ô‡W™ ×Ā­“ÔąšK•ę0)O÷iéÜ8ŨxéuoöÃĻŽC X÷Œ›¨Ņŋ.×_ļmŅŽb߇ž’ÔŖ˙ŗziÄíęzyJĨJöŽŅkGI_hsĄ šÃ]}/SõĨXfŨhrhīĒLÍ/üFv§Kn—[ŽrIĄ ŨuĀûyBŖ"k}Ņ‹°Ęâ.Öiģ4$Â‹ĪąöĶŊ!īiûËĶå˙ˆEG+&"HwFyNĘUüšŠÜ!c­U_Ÿ¸Ū2í,TA™tg7ļkqmE…*riHxPEGjÂ+ĩT^Ŧ÷“—ę`P’6-JĐårlŪ×l ŋrR'Él1KîrĪ/˜ļBŲ¤!á5ĪÖz)6<P-zĐG+ļ}hDdŖ‹$…Z­WÖŅd‘Ų$…F„Ôøhŗ,rËí–d+ô,;Ēöđļ°čp™6ë”]ä0¨]šąŨ ãÕ6īŧĮļīПļZmŊ?mãgjvÜ8e,ŲĒą[&ÕƟ$y×֛ ×ęÆû“4;nœäJ1‹˛4¨æŽâÍ~ØÔqˆNŽ›î=Y~k‘tŗîŸ4Y]ßŨ m†/V=ųŸSÔ¯{cīéǰ˙ŽU#jLĒø­\¸[_\ĐĩšĐÅląČ,‡œNI5˙ø•;å–Ĩū˜N­T%vÉäi*WĄ^}n†ļ›Öŧ—“cõ´åŪä‰ZĶĐG4cKP`í Ļ@™.}qöæsĖŅJŲ¸Naß՞ėĨÚąĸ\ϐž1+U)ƒ­2;ËåVŠ6?u6×+´—gŸhāKB‹ks—Ë-“‚M 5”GIvē–šŨ2YKUkĐU3j75ö+ĨĶ-gŊ:Ė2Yš(Ŧ)­ØöÍŽĨz}Mj`>gšgëlBŗ)ĐĖ”Ë¸viÁ>×jŧYˇN|lûũiĢÕÖŅûĶ ŌČäßiķø÷ôjÎ0­‰Ģķ˛7ũ!ZQî.åJCâę\×îÕ~ØÄq蛕čnŌ]ũh`˞ŪzŗŽīÚĀû*+äøöŒžĖËÕĄ_ëģ ž*°›î>QΏ]×7ņΟŋČÖĸĘdЙ?˙Y_ũÛCęŨ]R×[Õ˙É•O°t:m.t‘5\ĄĘõÜģĨÆßŋmĨ*3…T߄$š >ŅáR“ĸfõõüa/Ū§mˇhÄÆué]ũØØIZ3æq:ęüē[}bm lÆįGjėÜe;Wúą$_‡ˇdjYÚ™C?TŠ%P&…hÄō,MąÖ™ĪlRP#ÛžÅĩY<_*ƚ8Ą5‡?ŽÕiŊ´~j翝ę§æF{Úžĩ×b1É,ˇĘj­ŽKNgķĪ´ŨuŒ7¨í[Ėâų2Qˇí]Îr9čųōáēövŠ×u–íˇõž\ƒë։m_Ą?mÅÚ:`ÚĒÂ×ŧ1ģ5qUĻ>ė[û5oúCø†ˇûacĮa”āĀ;õØ3cÕ?´Ą¤Ĩ†ŽŨÚ[ũGôÖ=1'ĩamŽ |rH[ÕŋŸËß>ҚŊeę;i˛PÉG›ôΛiō4Ođōķ˙–č;ÃkĩiķÂW øKÛ ]Bû*ۚЃŸäËS}&‡Ž,”ĸf*†‘.Î|­\˛[ö‡ĩx`uŖ8Ũr+H=kœ”šŠé°M’ĩÎü—NB›1OYqĄĘ~ųĮŅ’"›œĻ^ę*)ŋéĪqŲ uˍ{ã=ŋūÜ­‘É3u,'EE6§4°¯ú˜öË^nQXXĄšNģÎ*´ÖōēZZ›Ŧ}aÚ­Svéō°îB­ŸúšNĪĐĢÃ=SB>ŦØĢÂŌŽhTZē2â7)=Ū"Y¯ŊöZBÃeÕ!•;ĨˆK;ûWĘ+p×ųU‹I&sŨ/Jv•ØŽœ…Ųö-y•ļ—Jō‹åŒTD¨$ŗ7íŌt;ÔĶZÛ­%ŧŲæøØö úĶV­­}÷§ž`VŸį’4ø“­ÜhĒ=āțūžáÅ~ØäqÅIk“nĶčicÕŋGÁKåš/ĩ÷ĀaåũCNĒgD¤ú ú„Æjō´ ú¯Ŧ˛ų`ÄKWIĒüRo˙Į4ôĻ€`Ũ;i˛ŋtģ‡&ęYy‚—.čĐŽ/|ēHŌzę÷ŋS´9–$Šĸ@Ģnח†Öt^mīéE ׄŠ÷ə“ŽÔė\*Č×ĮĢæ)#×ĸSö˙tp~Ŗĸŧ|ĖË×Éŧ\}œũ==fŽ6Û{köŠ$Å^ŋŠĶWÚŗåˆJĘ*ÉÛĒÔ%ĨŠˆ6Ém/Ô)§K’Eŗä´åęX‘Mg{z3‡ŲļKŲų*)sčlÁVŊēå+ Ļŗ—ËļíRFōĨfįę´ŨĄŗöbËŪĒ<…(*Ü"Yôäđ Z5OY‡‹uļĖĄ’‚}JŸú˜{yŸģÕW‹kŗ$hJbˆ ŪJWÖáB.Ęמ%КŠOdũŗÜāÁ/iņ@—ö.ZĒOjQíĩ„Ū§‘‘Ōņˇ–j{žMgK õņ’7tĖŨØĪ›Võ 7Év|ŋN9%ÉŠĶŲ™Úë¨1„ŪĀļo1K‚Ļ ÷´ũúã6-ŗëôá×´`gŠ"}Üŗ Ŋj/Úá*ËöÛz_âÍēuâcģÕҟԟ6ڟúˆ%Aŗ§ö•}ß~åškOo˛?„oxŗ6uĸI=ú ×ũ.:wRĢ_ßĸĪūú9/HēPŽŗ=Ąw^ߤŖį¤Ž=bôXŋ6ōí NāâŅ]aMÔã=žĐæōtÎg—CWÅųfŧũÂyUV €ļ7ŌERđāZëĖTÆÆyzēÔ-ŗĩ¯F,_­”ÎzļQē_ŠĪí¯ūI– ^ŠŠ›ŠĩŗQlp6 ~XķŌ •ējžF픂"Ôė´ųēߨĸä÷ôôTiĶ–™ē÷‰Gd]´M3ž:¤GW}čÅ<ũäv™d}.IClk5cLĄėî@EÄ')+-ÁķĢŖWËNÕę´L-Û¸XW|'ˇ)PĄÖžą$KĶ#$ÉŦØ´Õʲdjå+ĶĩŲQ.öRŸIZ=wXƒ[ëÔ6SąiYZjÉԚWĻkƒC ī§éĢ^Ō„ĢŪ´0HƒŊ¤ÁcR”ąč>õY‘xMĩ×Ēą¯d¨dQĻVÎzL¯šz)æŅ$Ĩ ĪԜs¤!ÉŠĘK{CO'n•Ųĸ¨áIš=üÍ9î’[’9ۘļoÅ&¯VĻiŠV.š¨•ÉŌ[ņSŗ”29ŧz´›7íŌt;ÔįĪõžÄ‹uë¤Įļ!čOéOíO}§įŖIzrįDm(Ž9՛ūžáÅ1ÔäßV4.X}bByŊBųčëĢ…įmÚy @}'Eéļ˜;ÕãčQ3ĒLo\5pņøąčöüĩ9 Hk¸ Š y?ǝâŧ*,čäēTUUU]úÍf“ÕjmÕ8<ã���čČŌŌ—5sŽĻ.ųJī/ؤŋ4”Wtû­Ļ/­ßThõ}iĖzö÷ãÕGWšŧ¨ŅĀe¯VžëË.—ܤ3_И^^žũ›´đ×téĶ’ų/6ëũžŠ‚îj™Jŧŧ����Ú¸6¸HÍžŧč<—F"t���āc?čˎŊ~›îúuÃwBøuoĪũģŋ-•Ŗu ķR€îŨHā˛íŒ~3ēŊ{¯ûÆũ\éũC••\^‰Đ���€•éT^i#¯wWôĐĄēŖÛU^ęf՘ĄQē^•:“wÚO÷s Ô¯BábSī''kĖŨˇûéaiTqŪûĄ.įĪË/r€N‚Đ���€Ī;‘ŖÃįcŅ#V3ž́áÁ˛H Tđ~zöų‰ęßCĒ<—§m'|ú ŧnŌ¯ę$*W.) ÖoūĩģĘĒVQá}ŒRŲŦk‘�4W›|z���€îÂízsģēNĢū <:ēk(y&JcęL¯<wRŪ< ›_‡hTǞâŧžûū9ė'ĩs×~ē‡K}•ÎfŒtqļ‘ĸŠĐ���€”ŸÖļŦU:Õo€ÆÜ!ë­7ëú†žhTY!Įˇgôe^ŽøZßų5+°i[zēļųŗ„F4÷ō"�Æ!t���ā?~ЗGsôåQŌ°JIęz—Ļžē¸3ų/ĒpznŽÛāš/ĢTE3FÅ�h>îé���� ˛éč‰ŋëįfÍSĄ’'õĩA5åÂßruÔŪôƒ ļįéđ߸ŧ0#]���� AįõuÎ;JÍņwÍpŪĻ]Y¯h—ŋë�ĀH������#ē������€Đ�����Ā�„.������ t�����0�Ą �����€]�����ÍŌĨ‹ŋ+�ÚB����-xà RU•ŋˀ¯TUÉhņw@ģ@č��� EƏÅЇÎä_ēhŌø1ūŽh]����´ˆõļPMü¤o¸A]_:´n¸^ͧLTH[ũ] Đ.øģ�����íŸõļPĨŊ0Ãße�@›ÂH������ē������€Đ�����Ā�„.������ t�����0�Ą �����€]������ @č�����`�B������ē������€Đ�����Ā�F/ĀbąŊ�����€6‡‘.������ t�����0�Ą �����€]������ @č�����`�B������ē������€Đ�����Ā�„.������ t�����0�Ą �����€]������ @č�����`�B������ē������€Đ�����Ā�„.������ t�����0�Ą �����€]������ @č�����`�B������ē������€Đ�����Ā�„.������ t�����0�Ą �����€]������ @č�����`�B������ē������€Đ�����Ā�ĩB—€€�]ŧxŅ_ĩ������´;/^T@@@ŊéĩB—nŨē駟~ōYQ������í]yyšēwī^oz­Đå–[nŅ?˙ųOũđÃúå—_|V�����@{sņâE}˙ũ÷r:ēųæ›ëŊŪĨĒĒĒĒî ‡C.—‹K������pŨu×Él6+((H×]w]Ŋ×ë….������h9ž^�����`�B������ē������ā˙ķûŗÜPÔy����IENDŽB`‚���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/plugin-list.png�����������������������������������������������������0000664�0000000�0000000�00000042512�14156463140�0021633�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��Ä��O���JĘØ���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨ{xSUĸ÷ņ/žN“ņĐË9”i)ĖiĀcS8Ō @‹ ­7RZhUđFņĘÃMÆQĀšˆZEĒ`A”TÎØ€b ^R8Cà=/­íĄĖg&é\x˙HļôNoßįyú<ėŊ˛öÚ;k˙ēöÚģŨN:u ‘�uIgW@DDDD¤3)‹ˆˆˆH@S ‘€Ļ@,""""MXDDDDšąˆˆˆˆ´KO˙ã˙ø'NœĀįķņüŖ3ë$""""Ōaē:uęÔ?ūņž˙ū{ŒF#—]v—\ĸc ŨN:uĒĸĸ‚K.š„Ë.ģŦŗë#""""ŌĄ.đzŊüøĮ?îė爈ˆˆˆt¸K�ūū÷ŋĶ­[ˇÎŽ‹ˆˆˆˆH‡Ķda h Ä""""ĐˆEDDD$ )‹ˆˆˆH@S ‘€Ļ@,""""MXDDDDšąˆˆˆˆ´KÛĢān—üŸö*ZDDDD¤Íh„XDDDDšąˆˆˆˆ4b h Ä""""ĐˆEDDD$ )‹ˆˆˆH@S ‘€Ļ@,""""MXDDDDšąˆˆˆˆ4b h Ä""""ĐˆEDDD$ )‹ˆˆˆH@S ‘€Ļ@,""""MXDDDDšąˆˆˆˆ4b hu ļ͈9fYe÷gÖo;3^yÜ::Ŋ*""""]ØĨ]æņRŧ}™mä;]¸OzņŒÂĸąÄ'6i*Š–°Îޤˆˆˆˆ\€ē~ ö:Y5ųA–æŸC(1 Vâ#ŒņRîrāČ]G~n6YĢɚ•€ąŗë°ŧ8×-!—TfN˛œģÜVeæš>›tsgÔ¯­y)s9Čw¸(.wsŌ †°0ĸcHL˛yĻаefQ?•Œ„°zūaķ9°ŲÕmāÃ` %,"šxk2–ČĀü6ÚČT›ˇö‹#f+§Î"#)˛s*&"" ę⁸ŒėÉXšī%"eĢĨcŽsŽõēļ0sōLvŦzŒ™q;y5ųÂŌÕyqåŦ$Įé%˜@bj4a/îbųų9Ŧ>TDZÆíįĢ~‘ÄYS‰mû°čuŽcUq"SSMm^vŊŸW´…UY.Œ–$Ŧ‰Ņ„†îr ėyäf­Ã;q šũ"RÉ\šNhõ}îbėY‹Yšá 8k7 :ĩvįŝũ3ŦŽ)ä/KęėLjˆ´™.=‡Øģ} ķŊâæ“ĩėÜ0 `4ßÎĢĢ‘––Žĩ9YØí$ûéI:¸˜+ˆ8„”ÉO“í<=ĸãdáˆ+0§Ŧ¤¸ÖŠEŦy昁ĖĖĢSæöĮˆ‹HÆöšŖB­-§?#oūΆį!쎐Qgn°;o1§ !nāÄ Nڌlœîf´…´Š×™CŽĶKtę2Ō“°˜MD›bą$ŨNFF*1ŪlļĸÖ6iļ`n‡ŅĶōâ2ŧM/ÖfŠķ]œŒH"-5ŗ)’ȰH"MŦ“R‰…âō�>Ã˘H¨ūILNgŪ;o11ĸœœŦíēŸÚZŖā‚ŽŋˆH}ēđą{Ž FRĻN"ēąEÍé,\Ԍ"Ũۙ™ō8šîhFĻĪ"#>ĘķÉ]•Íü ­ŪÉŧ ‰ņF˛ly8ŊSˆ>[ÜųØËŒ ^ō텐{ĻX‡Ũ8F&ÁvúÕ֖s5÷>jdņ“v˛sŠ˜8ÅT{ėŲ8|Fâ'Ĩ ā\@zÆ:ŠCãI›•Nb„bÛ:ĻN=3:ÕUų/ˇ;p•ŸÄ‡Đ3ņ‰É$˜N7–—bGļü"Ün ,sĸĢ%˛zJFļĖlĘã͉)ÎÁ^‰uj:æ˛ĻĘ=nœžh+Öú歇YH͈€°††Fë›2ŅÔv–aË\Gyü$âŨ6ė‡Ęp{Áa!5=™hcõt•b€,:#H˘Bŧˇ=ÛÁÛH(2a2ĨÎkn\y6ėūm4F˜‰ˇÖŦ‹×vöC§Û Œč8+Ö$aÕí–ŗ$›“‰éÄ•ÛąģĘđb$,:kjÂŲīW“m؜ãŽŊXˆˇÉ*.Ļü}šÛÁĒyKČĘsQîķb5c͘ĪÂ)§§9˜7t2ÅS_Åjš%3 ÷žEǎŠõœĖ:âŠĢIs.aŠÍ…›PĖéķY55Œķ°*¯¯1ëŦĨ,M?Ũšą¯|š%ĢėŸ"ĖX§.daz,FÜdOĘü|€É˜sŖ™œģ“YæÆÖix+Y¸d6W9>ǧ”Ėš_īTĸ—^ZÎË/ŊToĢ>1mĶĻMoÃũ$"¨ ģČwzÁĩ./:–, ×mfæĻŧ:ŠÉɤNšĪĒÜĨ$RLÖB˙¨kŧ5ƒ¯�ģŖæĘy8‰Įj Åí˝ņä†Bėų'ÁœDŨ~ŧĩ匓ŽÕ�‡6fãĒUĸ{v>C"­a€—œĖlЉfâęwY8évŦÉéd,ÛĘs1‡|mĶníÂ[HÎÆ<Ę#ŦL˘Ęã“°F”cߘÃéÁú˛íëȲģ‰ļN"cę&&†Rn[GΙáo#āÃí´SmeâD+fš.÷üę]ĖĄrˆ0›i肄1,˛EsŲ›ŪN�eų6\ĻT2ĻÎfÖÔTĸOæ“m+ŒXŌ3K3gM"1ŦÛ#ŅæH åylĖqā*kŧĐâíëČ)€¸”Idd¤“YŽ}c67€—âœul,đbN™DÆŦ)L´FãËĪbãöŗßŖŅG™ŨFą9•Šŗf3kĒ•ˆrŲ9…gÂy“m،ãŽũQ\ėÅA�nrfNfŠ+š™īä’gßAÖB3™“™yæj“ŖŅKqV&öøųdmšÕ؜õĀhôâ\õ ŠĢąīߏ}ŠWÖLR'ŽÃ8uųû÷“›aÄļ`9ÕÍã\0ŽŒĖb, ßÁfßAÖŦh ¤15§ #}u.3Í`HyGÁVf™›Z§m`;ķ&gâ2ĪgãŽ]äåžÃ<ŗ‹ĖÉ3ÎÔĨĻiĶĻķÄ´iįŧŽ0,"mĨ â2˙1Œļ™né`cN9˜IŒpãv×øÁBJ‚\ÛąģÁ˜„/ųvįŲĩí|Ļ&Z-ārPpúŧã. ŋ"âĪÅn}9ɤĨ†Bq5 tįąŅéÅ`MÅjôo“Ũᅈ$Ō,ĩ?;aRjãŖęÍ]ÎI¯‘hK,‘aa„…EbNžÄĉ‰ūŠ1ŪBėåD$Ļc5GF¤åvŦqF9œĩ%wŌhƚKddÆĻĘ=_^7^ „†ĩŅ\õl'ŦæęĪ5Æb‰ÅWVŽĀhô˙~€ŖŅØtûļ0K:éV¸ll\ĩ„…K^aUöėÎ"je¯G—ČÄTL‘„Eš°¤Ļ’hÅįö‚×…Ãu’čÄTM‘„È4'32>‚ōGí_ #jĩAb|4žâŠŊÍlËh—zy‹°¯|šLW(Ö´äę_˜Â°.ĖŖĩŒT‹‰ČH–äYL4{ąÛĩV/MeÁ”d,fÆŦ‡e3“üí–d%/ÄOabõÆF[­Dú\äģ�īv^Í.&fęĢ,Lļ‰%u RCągŽķīcF€‘0ŖąyëÔˇ ÅNųBIHOÆY=ÕfŅ;l|g‰ |ĩę†b…aiK]xĘDõŲÉ įtēÉ7”ųu_gIÁģ¤Öwb+sQė\ĢH_ÕĀgúG˙0Į“h†ĨN'eXˆŦŊˆOĀb1böŊ‚Í Öđ:ōpJĒ5öÜâÂZ_N¤tĸ7Ž"gƒyËüCän{6N_(Öôäęm*÷oStÜšá×l!Ú@ųË]Hd4Ņavœ×A|<æhŅ‘F"#̧¸‹)÷†bŽŽ}vŒ6EbČ/ĻÜ˙'9„FDŸ­mĒÜķÖÆŠŠÛi ¨õé48uĄŨÛĀHtB: ^Ɗ\ģŠ8TėžS€Ũn&%-K$PVFš×ˆ9ĸfí#IHŊŨ˙Īĸ˛zÛ 2:ƒŊœr7œÉĀĩÛ ,"ƒˇzšŅ–Ō.€k ֘%ĩ_3D3rá;,L>ģFc9ö Č)pQėöâõúü^tíŊaļPŗ†Í]/2:úl{ m‰ŽYĄøđz—§/‚ÔS­2-6:qÖØgˇŗųëÔÚs‰ĢČ~ügx3&aMH ҆š‰ĮgÖ Ā Ã"Ō–ēp Ž , (¯)ŽÕO‰NM'ĨFį_žŸC~y#Åỹƒy™3ë5Æ0ĸŖLÄĮG@ļƒī$"Ŋ䉟iH°DœÄn/„„Xœ6>C‰õNë8rĖ餙WąÔ–Ŧ¸ąeā‹Hgâéeŧnŧ€ÁhŦw{ 4GĮķīĢŗõ4aÍČ "ĪNA~ų6Ÿ˙šŌÖT˙ ×‹—“ä¯Z@ū9eEøO⧃b­oĸÜķe %ÔčãdY˜Û Dĩz;›ŌÎíP‹‘H“…H“…Ā[æ gŖ ›Í‰y’#^ŧžŌãkč}˙ãŊ52ž1ŦîBÕË@3Û˛ƒÚ%:ĖW'žųEÕhˆ$ÂVį{ędaÚ˛Š,\øņæ0Œ”ąqr ™uˇ2ĖĐĒõęSo¸Ŋx)'+í ˛ÎŨ NžĶˇlÚېĀŧÜMÄdŽdãĒ™l\čÅOęŧE,L65Zai]8ĮúoJË- ×á%5šf'n$aŌ"jfPÛ ųšæī— ŅÄ%%ŅT”ą$&š•‡Í ‰î<\†¸ę j!>ŪHļŗ�7`/8‰!!‘Ä6/ĮDÚÄx2įŲČŲîŚ`#×é%zRgfGTa_uدušķzņučb#FŖÜnÜpnûēŨ¸1]ķN?c$–ät,Éāuárذe¯ƒŒŸc51Š9}‰õ|ZcŲĨąrĪ7Ã#‰….eI‘õG^—§ÁL‚Šë|ļŗumˇv��­į—1cd‰1V*Ŗ ŅFh=ŲĐ]Cī×”Ŋîē ų—ņĪif[ļwģ�Ŗ1›cŸļäĖ&§8”ԍËH=ķĨ.Ã}˛‰˛[ģ^SŒ‰ĀšųSë>+Üh$ĸžļiÍ:gÖĩ>˙5ŌįWßč¸jķĻNq7ķ,Ŧ'"ŌēđbHLO%/öĨKp4ŊxãÂĸũaĖåāœ™@­a(€$+ņ†“Ø Éˇ9đ™’8}5/>!œyäųįũš­I _H?rÂR'‘hđbĪÍÃeËÆé3“–VcjFdŅ ÜuîÔWAōڟ˙&+'į<.΍ĶîÂf>;¨ę.ÃUtvĻŠ1Ė„%؊ŲčĨŧÜ aŅDŊœô ;ûcÄ?ąĄJ4Uîy Ò`ÆPî 7¯žį⚝ääØČ/p7īŅT­ŨÎĻ´w;¸d-YB–ŖžGĢy)÷˙Ĩ˙“NÂĸ‰0ž¤ŧ¸æ˛e8ÖUßėYĪûPVTŽĪAd_ N–—ךŸė./Įg #"Œæĩeģ-āöâ%˛ö/‰ŽvSß<ąķ_¯)æDâ ')÷…m2ũ‰4bhčFŅÖŦPæÄ–WtæŋFSŠ‹fa5œÄUŦ‡ē‰HĮëԁ˜„YĖK …âudŒ[ŒŊžįōâÆ™=ƒĨ6˙ ¸á�‘@Š5|vV­,Ēũ–7™#7yK“mV‹‘2į*rōOg93Úo!ŌįĀļ*fŦņ ãG9ÆdŌ­F|y™Ė[儸I¤˜j—o1Bą­öÍwxą­ÛBc3HڃŅb%1ÚK~ÖJr…¸ŠŠp9ØÖ­#ˇØHœ5éėˆ™Ûmã:˛E”šŊ¸Ũe;ō)&Œč#cIˆ3RfĪÆî*Ãíõâ.sb[ˇ’U9ΆÃfSåļÉvĻ’n Ãm_GfövŽBŠ‹ qæmaÕĒŠCãIĩššf[ģįäŋZPæĸ¸ĖģŧÛ!ĖBb|nÛ:˛ļ;p•QVVFq‘{ö:l.#1I˙Uc, æPŠí9Ø]e”•áܞƒŊ"ĸÃĀh&!Î˙žŖČÛëĻĖĩÜ7ņ ĩFY 'ØEūv*r`Ë/'ÔįėZsÚ˛Žfŗ$`1¸Ø¸j;Ån7Ŏu<ūt9qņF|ÅΆŸzŅÚõšbLfbj(Ώąd{!en7ÅÎ-ĖK‰õņĶ}Ŗ˙&gß!;vWe4gz¸Ö1īņ <ž.W™›˛˛Bė+×a'‚¸vŊģQD¤~]xĘ@Öe›XÂæåŽ"#1‹čøDâĸÃËģŧ˜|‡“rĸSYōę˛ę§/Ô/aÖ"Rė“ģ4…4×TŌŦŅ̟Cl/ ear)raÄ[Ŗņ-ąaķIŦ9IؔD\č*l9v|éě߆ķ)'1=•ĐÜl Š$.ĩÖšDFj†•Ĩų9dM‡;#ø07‡lŲäxˆ7Øę™KŲž"I˜4…°<ųų9ä¸}` %,ÚLJFRí?åkē4ëvv8rȲÄg4j’šNbõFF'O"ŨhÃf[‡Ãíc‘æd&ZëųĶĐ-(÷üũ”܇ÍáĞ“Īk‰9>´¤ØÉVŸVmg=u2'ēŸ“OV–‹ø´Ÿˇs;‰NžÄÄČ<ėN96|^üē9:šÄ‰ĩŸë:‰´í6ėļuØŨa"1-ĩúQ…ū˛Ō°aĪ]É7ūļLLĮZįOŨ…ÅYą¸íde–q#ŅVŌŦągÚŠÉļėãŖ™ÂŌY¸ČÁã fbŨĄqVæ-Z†ĩėi _ÅÄ4ؘ›Üvë5CÂĸMŧļ€%ķŌČ:éC4ë|˛Ũ^}LIœ˜NôĖudŒÛBÚę},lrz$-#kŅĶ,˜AÚ“ø F"L‰¤-}‡Yşv‘ MˇS§N***jķģŦģ]ōÚ´ŧ2G6Ģŗ˛ą;\”ôâÈ!"‹% kĘDŌ’Ī‘ŗÍČÔ\3ķė›˜xæRŊ“ė%¯e˧ø¤ ĄD'$3qę,ŌëŪáėZLbĘ*ʉgAū줟yۋmōPĻÚŊ„ĻŧCū˛„Æ?ŗåœådáˆqdš­dî}­ŪĀ_ŧ} —nÁQ| ˜­?gŪ"3š#Į‘6 [ũ6‘&Õ÷MDDDÚÆˆVY6G>+õōĩŅ_(šā(‹ˆHûéÚsˆžےÅäc&#CaXDDD¤=h„¸+*scw‘Ÿŗ’œü“DN|Û|=‡HDDD¤=tņ›ę”3›yķrĀMBÆ"–ĖRi/!‘€Ļ9Ä""""ĐˆEDDD$ )‹ˆˆˆH@S ‘€Ļ@,""""MXDDDDšąˆˆˆˆ4b h Ä""""ĐˆEDDD$ )‹ˆˆˆH@S ‘€Ļ@,""""MXDDDDšąˆˆˆˆ4b h—ļWÁ§ūųö*ZDDDD¤Íh„XDDDDšąˆˆˆˆ4b h Ä""""ĐˆEDDD$ )‹ˆˆˆH@S ‘€Ļ@,""""MXDDDDšąˆˆˆˆ4b h—žūGqqqgÖCDDDD¤Sœ Äfŗš3ë!""""Ō)4eBDDDDšąˆˆˆˆ4b h Ä""""ĐˆEDDD$ )‹ˆˆˆH@S ‘€Ļ@,""""MXDDDDšąˆˆˆˆ4b h Ä""""ĐˆEDDD$ )‹ˆˆˆH@S ‘€Ļ@,""""MXDDDDšąˆˆˆˆ4b h—ļWÁG˙ī÷íU´ˆˆˆt}ÚĢŗĢ Ō&Ú-ëK"""""M™‘€Ļ@,""""MXDDDDšąˆˆˆˆ4b h Ä""""ĐˆEDDD$ )‹ˆˆˆH@S ‘€Ļ@,""""MXDDDDÚĨ]‘ŽæpŅ˙đÛŦ üđįŋđĪūŗSërÉ%—Đũ_.ぉwsšéß;ĩ."+‹ˆˆÔp¸čxiÅj*=?tzøį?˙IeĨ‡—VŦæpŅ˙tvuD.J Ä"""5ŧĩnCgWá\ŨēÁŠS]ŗn"b‘<?üĐŲU¨_ˇnüų/éėZˆ\”ˆEDD.]a ‡ČÅHXDDDDšąˆˆˆˆ´ŽõØ5ßĮÜ7x&ödž"ŧw,FÜÆũSî"ŠĄÖģ•ÛĻ0húnˆ_ÄwīÜIHģW¸ÆgožÂ 'wÈE|÷VĮ~ˆˆ´^× ÄgŪ¯=ƒjžæÃįŠāXIö Ø7ŋĪØåkY2˛ggURDDDD.]4‡“˛|3ķûŸûŽīx>ī>9“…ģŽōÁô™$üūmÆuL2f%GFųĀ`hzaé2.¸9ƞņ<°"“ņŊĒŊŦŲ|¤ŗĢt–°ˆˆˆČį‚ Ä�,ŒĀáüB|,zlũŨô‹@ėŖ×ģÜņõ÷6đūqōۜÁ˜ˆ@ėā›3myĮ|āøņ1č—ļŽcÕKWnžBŋ˜ô{đC*ëŧf™ŗ (Š]Ū•‰Ü<a.ī~WI]žÃŸņúœ{UŊlŋ˜AXnÃ=sVa;ܨ֊ˆˆˆHKuŅ)M *¨ōų¨ÚvÖÄqrË;+ (œA#†sy¸cų¯q˙í{Y05Čz 6‚|žRr§=Í;ƒ”0k‡ŋËį`ūG˟pߖ <pšßÁUŒI[ÎÁĒ ÂcãąŪŽÁWÉąÂŊ8>\ŽcÛ'ĖÚ¸žGúk4ZD¤+ûqĖĖš÷*zđ˙øęí×y÷ˇŗĢ$" ¸@ąãĮ*�oķ':øėËY°ŗ‚â˜ĩņíáŗÛ´{™ĩ´˙ƒ0‚ÄÕ7V9^dAŋ;xī˧vē˛žƒ,Nģ‡7 XŗÁÉO[€Jō2_ã`UƒflfũCũj”īãđÚ)Œy~///ũ˜qoŨŲÆŋˆˆH[ ŽęCü+}ŖēƒąH—uaN™8ūkvy€ Žii4”ļœ¯6F>ę<Pk$ļ7Öįž!)¤ÁįÂÕĪθįj„a�CƏ‰ äā‘ęi>V„pų ~uļËĀå÷-e}Özļ={“°ˆˆˆHš°ąī8‡íīķķ‰są{ (vŗonëņáūPXļë Û!ņŒܲ"{gd=OĖčŲ;œ �Ÿ§:÷æō~ūŠ ļĨŋŽgžpO$X¸ŧžr,"""ŌVē蔉RÖÜ>€5,Ôī^Yņ ´ųTÚ Ž§Oīú 70`P_ø° ųE†÷ŽwD÷Léžŗ¯Xg>ÃhįLļ:ŗxøÖ÷ îg!)~8‰7')Ą?=5uXDDD¤MuŅ@\Īæ0aĀ@Hīž$Œŧ“qŖ,í4m‡Ī`hđ)j†vĄís¯l‰eôúÕŦųđ3ž=˛—­Gö˛uÃrîËČ)O2˙ĄôiŋˆˆH ũ[ĖŽŽúW.ûņŲ×~üīŨĪü;Ø| Š?Ž1‡ø¯åDéž8äîĀZŠHCēh nøs´Ŋscf0�U§ƒq=kTļķŖĪBúa}čyŦīøAžÍßˎmąig!;–=ÄJÖ˛íšxũyh‘.á?HŊ7•Á?jx‰Ë.ĘČËëŧøˇ^üéél×ē‰Hs\Xsˆ[Ąæ´„zŸC\RQį•pzö¨āøņúƒīáƒGÛŦ~M1ôėΰQ“˜ŋb3ģ˛Ōé”l^MŪš/‘Včĸ#ÄmĮ`đŖV/ĄęŒĒ!Ī^ZgŪôīGĒø6ŋFZjŋíÛÅ;ë†č6Ry„ŧ]{94”Fö;į퐄›¸*8›#žJŽŸģ1""Ō)ū›œˇs8vΔ‰! ŋÜ˙Â_īå‹˙9wʄF‡E熋>‡ôīKovSRø īœÄė3QĢäĢĨsYS xjŽÁ°›ã Úš›’ͯąéŪ•Œ;3a÷8ļ_>‹ÍWw6âÛËsžÅaˆÃ×s% ĒxÛ?á+ŪzH—ņ§CûØy¨ök?š>öL ö¸öķy; ψČyģč1ũīâ~Ëû,tōFÚHōâčc¨âøÁ|žķ gņĖŪĖ{æ“Zķ)zŽšÎëķYâÜÍė[n惛ãšÜāãpūg8¸‰ÅS=Ė{~7-|qĶzŪÁüŠrΞ–¤'ōž%žŊà Ą’ã%…|å,ĨŠ(F?=azڄˆˆˆH›¸čįC?xëmÜ9”~!•Üõ;ōGĀōk˛_e\?!@•ĪWãégũyä­õ,ž{(ũB*p|ü›vBü/x/{)í6:k`ĀCoŗmõ“Œ‹ĄÄIŪĮņÁĮģųļÄĀUˇMfņ–Íŧ2JÃÃ""""mĨÛŠS§Na2™:ģ.Œãëī%á™ŊŨœ‰sÅMmü—ōDD¤3MũË6)į'×OeūmūŒō_aaM™Č\ü\›”#"gĀq+?ČW;>äŨëy2E%ß: č͝¯Â°ˆˆÔËSzŒūö˙8ZúCgWGDqņĪ!n’˜õX%Aąyg-ķkÜÜVéx‘Å;=@,cī<÷I"""�=ô!ŋzúÃÎŽ†ˆ4ƒq}=ÆÂģwķІBÖ¤$/~( T–8ųÖYЇ úOy†ę>d]DDDD.8 Äõ !éšõlNXÍËk?ãÛīvŗĩNJ ā$ŋĢ�� �IDATpúÄßĘÃ÷>Æ#ûiē„ˆˆˆČE@¸A! õ Ūõ‹Îވˆˆˆˆ´#ŨT'""""MXDDDDšąˆˆˆˆ4b h Ä"""ˆnŨēuvD.J Ä"""5wī§Nuv5ÎuęŨ˙å_:ģ"%b‘œt7tőØnŨüu‘6§@,""RÃåĻgÚŖ“ îŪŊKLQčÖ­ÁŨģ3íŅÉ\nú÷ÎŽŽČEŠÛŠS§Na2™:ģ."""""N#Ä""""ĐˆEDDD$ )‹ˆˆˆH@S ‘€Ļ@,""""MXDDDDšąˆˆˆˆ4b h Ä""""ĐˆEDDD$ )‹ˆˆˆH@ģ´Ŋ >úŋo¯ĸEDDDä"Õ÷§Ŋ:ü3Û-‹ˆˆČÅ­3‚‹H{hˇ@Ŧ/‰ˆˆˆˆ\4‡XDDDDšąˆˆˆˆ4b h Ä""""ĐˆEDDD$ )‹ˆˆˆH@S ‘€Ļ@,""""MXDDDDšąˆˆˆˆ4b h Ä""""ĐˆEDDD$ )‹ˆˆˆH@S ‘€Ļ@,""""MXDDDDZ ÄGx=u�ũb˙ąĖÉī늊t’|æ][ũ]¸v.y•ĩßũvŅÍô‹7tNõ:\ö8ũså ,7Œáž9ëČ;ėĢŗŧ“7  _Ė ~žŖ­ëŌžeKį¨įøĒû3áC*›.¨…š{,ĩŨ1÷‡Ĩūž#vÚgįWČîŌÎŽ€Ÿ>QôöT˙×WAIEDpīpBĒ_éič¤ú‰t!ąxũd’ę×Ų5é‚‚Ŗč>_%…Ø?ü ömī3vųZ–ŒėYŊT8ũ㇓XÄ ž'RËéãĢ.Cx=/ŠČĢ‹âŪülÅN~vúŋŽšÄOüˆ â™ŋe%ãÔīˆÔrpå‹äŪķ*)ún0ėšÍŦåoßą]ŧüäLŪČ?ĘĶg2č͎ųY€ŪŒ{a%ã:ĩĻr!Ēy|‰ČÅĢ‹L™hŽJrM _Ė�âįäsö‚¨ŧ9 ÕS*vá;sŠ+‘y;ōųí´1Ä_9ˆØÁ7sĪĸĪ8VĢĖã|õæ îš%˕ƒ°Üp7?_ël‡Ë`"m%Ž‘#ÂÁķ;^Î<HŨ‰ĩøŽģh ŖŽä˙~\;†‡ŪĖįø™NWFąĀáäŨicˆŋr�ą×ŪÍŦm%øŽībņ„›ĢŋSxũģšßŒŽųŨ1ôÁėĪ3:¨Ú˚ Ģߊįķņ|^Ÿv7#ûÛ'öÚQÜ÷ËųٍhI_RGŖmßÜžŦޚûë šŋô×=vđÍÜķË:õ Ā}ßyÚrŋÔPuMs)ëÍØ/•Ģ÷õ bâžĨŸqŦŅND$p\@8ëŨ7Tėú˜oĪŧîdĮ.Nʘx 0�*É}f.[ ˇōđ}7ŅĮWŠãí<tfŽĨ¯ŨĮøeŸāđõ%åžģ\ČÖįīåžĩ2S.D S&3(ŽŦ_NnŊgR€Jlsîã‰ˇws,ü&îŋ÷Vúø Ųąl ­-Š^æôwĨ‚ŧgžåâÖ?˜ĒŠ>˜3•ûũ _õNR?ž’Ũ,™ūbõ÷Ž‹wBnbüˆp�Ž8 GxũÁ),ų¸à۸˙ŪtŦŊ=Ø7<Í=Ķ?Ž(ÍíKęjĒí›Û—ÕUc-šÉË%ąXoŽ%ÄSŠcà f­/iæį×)ëbÚ÷ĸ-÷ËYßfNåå#á\•ĐCuY sÍŲ/•Ø~ųķ?.¤ÂЏ¤QC1؟eŪļŠļo‘ P™2Ņ<†ÄģHéũkJvŗã;68¸›¯*€Ūˇ26Ąæ)¤ âŸbí 7Xϒø|!7ŧĪzŠĮ?æåõG(î_ž’ųƒ po,÷Ũđ4ö•ĢÉģįy’4eYēĸŪw0{Ė:ÆoØÍ˙ų¤<î2•NėG‚éׯ/c_XÆ#ũápx!#—åģmģ9~ß]œJëĄ2v:›_AČņūŋîYU…]/Äc8֛Ã7.į`I_†ĢBēūw§O˙pø°*ŽÔ?rYéÄ^XÁˇ2{ÅķX €ī.Ffîæxŋđ:ŖŗMô%õ•ŨTÛˇ¨/ĢËÃņ>ΰsÅmôÄG"#š˙à žŨé¤ōžŪ„øžok_ũr #–Ö}5œ”å˜=¨ækmš_Ǎėũ;ßē“žøøÃŌ1Œ^y”ƒŪįۇžâĒēÕiÎųŦōÖėŦ�Âģ|=K ā쓎ßÃeb‘ i„ĀÂøģûĨäíô_ =lß͠ߨ;ętA 9üĖ y} Ĩ7@ÉQ˙%҃ģųCԏūá•?~œãôæō>@…G vČ „aS2H‚’Í/˛Šžkž!#X˜ŗŸžÍ#ũ|„ôôšâĢŦƒ02Ū˙]éŲ—>áū׎ēŲâĨėËåÁ�•Tx¸ ž;žĶMbh zsy8āų„YˇßËĪ­c“†MÍā1ņôŠĩp}I]Íjû–ôeuqÕ¨áÕáÉĀåƒĸ�¨ĒŦđ—āûž­UyJ))9÷§ōœ¯]Ûî—ĢÆÜtĻŦ7% ¤ÃõsÍŲ/GœÕËđxú.ƒ…ŅņáįÕ>"‹ j„āōQw1(ķ7|ˇs7‡g†ķÕÎB –ąwö¯ŗ¤Cp“ĄÁP} ˛’ĘJđy<ūQ ĒŨĖž1ŠÎē%;Ô-R¤ĢčsŗīYÅØˇ x9s7KÎšį§’o×>˂ˇwķ‡UMtúģrú{b ÄP÷ĩ*|\ßĮV_‚īMŊ•0Ä3{œō5vŲËÖ#{Ųú6ÅȧWōĘ=ũjLYhŧ/Ą•mßüžė\!Ag?ÔpNčä}ßö—Õė›ęÚnŋ Žņ™†B€ |õsÍŲ/>*ũË „Ô<œC‚ ‹\p˜>ˇ2>áEžÛõ ļīÂq,wryŨ}Tzjô•§!$ Áá€Ē Ą<ŧ|2u¯PöŧČ:ušØ¸jĘ/HÜ<û‡¯ņŪÍĩ`ŸũYzū*‚b˙›éŒî;Íø•į˙É]ũģsü3ŪÛå†c˙CëÄ@Č Iŧųé$*9ų6߉}įûlÚy”‹æ˛iĆę§S@S}I]Ínûf÷e-Đûž kŲ~ņQéņÁé_ËNsAõsÍŲ/†ƒ!ūe|˙čöéĸ{‘ nĘ@OŦwßD0…|°(‹¯Ē‚HskKœ�U|ģmwõÍ1>žŨš×˙;pŋX„�ũ‡2 ¨ō@ŋ$%Ž )1–Ÿ_':‘.ĨįmĖž§/PȎGkŊu8ŋ°úxŋƒ‡ĮŒ`Ø žøŽ—úßŦō4ūtŠĻtáīŽīø.?ú,;<@đ<qOĪj>ļ‹wßü5 6$¤…¤1“˜ŋ"“‡cĒ ŽÔ0kĸ/ŠŖųmßÜžŦeußwu-Û/U|ģíŗę_ŧ|üaWõ1×§ūcŽYûĨŋÅ?ßŊǐöęy•ģø _ŖÃ"p!Ž!‰w‘ū ī9 !h8coŽo (ÃwŋaĖ„Ī^BŪĮG Ũ}—ŋSčyOŒyņ ycâŨģ9ÑŨäæ—‚e:Û-ģQ"­0`Ę/š~Ē?�ÖЧo‚8JUá:æ-Ēāō’Ũ؎GŅ ŽųŒ——Z˜=ŗ•sģØwįĖMO>Į+Ē/Eõeüōįą6ĐJŲē2 ‡įžĩßʰ>Pyd/š…@īáŒŦ5ŌŲD_RGķÚū&.§š}YËŌžīõßTâ™ŋĨú†ĖfhŲ~ ÂđŨ‹Œjæ1×ŦũŌķFƏxĮŽ ļ>yž|ÎŨ&đĐäÜ‘‹Ü8B ,ŒŒ 8ņŦ œC†ÍĖäáđŖäí, 2¸/‰S2yķžŪ§ aØsëY3ãFŽbېMîA¸ęÎgX˙VįyÕR¤c„ÜÄė)ąįž<ę^š;ŽŪAĨØ×ÄWAwđĘ[™Ėŋģ/Á%o[Ī>m–ŽõŨ9sĶS…Âû’pÛtŪØ˛™…‰ Wöš‹7ßz†ąņÁۙÅ+ŗØä¨äō›'ķFÖS ĢrīKjkQÛ7ŗ/k‰@Ú÷ĄĄ›ęJŽTÔsc]Úˇ_|Õ7„†`}v÷7ķ˜kŪ~éIĘo2™5ĸ/ÁžŖäírÂ͋xåŪžū"|žķģz rëvęÔŠSEEE˜LĻÎŽKķ˙Œ‡n™ĘO8cŗv°¤Ö„)' n¸‡5%AŒ~í;^Ųiĩ‘ Zô%öe""ŌQ.Ŧ)Į>fŪķņíwųô@püc<Ąˆˆ\hԗ‰ˆt)֔ _°īæ`…~#&ķæōģÎû‘§žLD¤Kš0§Lˆˆˆˆˆ´‘ k„XDDDD¤)‹ˆˆˆH@S ‘€Ļ@,""""MXDDDDšąˆˆˆˆ4b h Ä""""ĐˆEDDD$ )‹ˆˆˆH@S ‘€Ļ@,""""MXDDDDšąˆˆˆˆ´KÛĢāŖ˙÷ûö*ZDDDD.R}ÚĢÃ?S#Ä""""ĐÚm„8<Ŧ{{-""""Ōf4B,""""MXDDDDšąˆˆˆˆ4b h Ä""""ĐˆEDDD$ )‹ˆˆˆH@S ‘€Ļ@,""""MXDDDDšąˆˆˆˆ´K;ģ"""8ŠŽ•˛~ã‡üų/åŸ˙<ÕŲՑvĐ­[7ūå˛3ņŽ1ü´w¯ÎŽNŗh„XDDD:DŅąRVŽyĪQžˆ:uŠ~ø3oüö]ž/?ŪŲÕibéīeÔŲUŽŌ­§Nũ“ˇ×ĐŲ5ibé?üųĪ]éHŨēáųá‡ÎŽEŗ(‹ˆˆˆHģ8uę˜Ŗ@,""""­ b…Û_cö”tnLeđ†ZĶ™0ã5>uyj,÷5 Ŧ ¤¯)jĮētÄgœkīķˇbšįmŽ6ēTvēfÃGO`Úķ›)¨hxß=u–Á#x0÷DÕö�‹G'p{æĄ&–;Ákæ1aôM \]×5{h°ĒÍŌ^û§söûųkŸz7īxė,ę/Ô_´ŊŠíŗ>øVæéŠį]{ŸšKŌ,>í¨ ‰Hģëb¸”OŸšČÄš›)ę‘Ä#sŗ"s1 Ļ #ŧhsî{Åûę렍ŋîĒŲNË2kl ŸŋĀÄņđŽËwîjž=lüLĻîlĘŖ¤•Ÿ^ąén|fO ÖđQ°ô]yˆđąsXņæKĖÛ ×ĢŗytMS'ÆÎ`âöķyøÚ]i”ú‹æQŅRáÉĶybp9ËVQP§i|ŽÕ,Üz‚!S§sKx‡TGD:@—zqÉĻˎų¸îWYŧ”Uãk¸%e4/Ũ÷ ›VîāgCÆĐģĶjŲųNˇĶ ĪgņRrvē6‰ÛĮdÁ}ĶX2÷5âŪûq†ŗoW|ū!û Iŧ0#ˆ%36ŗũččÛŌO÷áÚˆĒ­ōŋũ Ķø,^ē?Æ˙ڐ+ :t͎îāčũ1´¸íĒqÉŖ;ģŌõÍŖūĸ5ĸH{*ƒ-ã_cņcxgŧŠúõR6>ŋŌ˜Éŧ4.ĒąDäĶ…Fˆ‹Ø¸ū¸2ƒY)õt4†Ļ­ŨÉÜNđŚY¤[O_f{€ų›Q ĀŒŽs9ŗb3N`čŒ<Î`uŪ+­SvK×˙ŽSF0ôņ­u.ķyøtƆŪģÁ?âRą‡—Ogø5 ĩĻ3mÍŋ8yČßNƒc^r=í| ŗgŒ$¸hī|YshŖ”O7 øúQ\wíhnčņGļl-Ēŗōæ'%œŗí{Ÿš Ë=(á§$ōčÖđl†ep:/šÎn×Ū5ŗ¸=i–knâÖ)/ōÅé 7 ãšŪį­ûbj”j ŧG0ø|Ô?†÷5ķ“F0aÍļ<ķ�ˇ&`č5ˇrûŒˇŲÛā _Sõ¯vĻÍG0Ô:iī}ÍŪĖt†Ž}ÂęĪŽ}éÛŋO|ī�ŋ[úˇ[obhŨmėMÖģĻVī->;‹ú õíŦīĖߋũo.gKuŨ*ļ/įŊH›{wPŪØqTŊŪžˇ™}ī ŋ&ÁŋŊ÷Îãˇûēæ7K$Pu@\q€ũEĐ÷úa ŸĀ ††Ūáėeļī8ãe>Ę}Ÿ™(XösrK+¸q`wŽ~}āLGåÛŋW#Øĩ‡3ũôҝŲīéŐÁuO-]Éc¯†¯ˇņûšg8Ī7|ēâÆ¤7'Ø2w6kŠb˜ĩōC>YģÔëxũķFQRú ûŠ Îz ]­3 ɐ Ø˙õĪžxt9‡zpKĘ` \Ijr4EÛ7פ̓ÔeY<ÁÖ|öų[<bŽŪŦĪ—ķÛ#Y°6‹÷—M"Ęõ>s2÷TŸø „D™ŽYVûžūžāƒ15điA†*\k_cßĩ ų(oģsŸ$Îĩ‚éŋ˛Õ:Ų´Ė 6Ν͚ĸhžX™ÅGoNgČ×ËY¸ũUAŨič3UáZû"ŋ8‡lÛgėÍũ%CJknc{kiŊ[sŧˇâxė,ę/Ô_´;qSæü/gîĄŌŗ‡——Ų =‡ž>ļš:Ž�…3VSdžÎę>äã÷^æ s¯O}îLĐ‘Î×u৘ ‚ˆŠĒ;o͇ĪWû§ūõķxũƒbĖS2?ųJzGE—2‡y)Á|šöC 10đú+a˙7ė¯.ĸ`ĪzXG3¤ę�ûĒīHŠ<đ Gƒ¯æ:sŨhųú!׏ᆠląAŠÜˇ•} #íúP‘ĮƯáڇæpûĀ(ÂŖb¸qætnnäã‰RNDsÚŠfU{Õ*ž?q&ŦnŨ†+j$Šũ˙;ķ‰lÜײ8gîNPLxpđ™ æé>†y3­Äõ5{íŨ<pmw<‡QwÜĖĪGAæ<^/úOž’DHchÃÉQ�Cx˙O<_nŖŪ{]šŖ4-_Ãĩ=IÚ@ŊûfÂķWÕtčķŌaV˛ÉŋÍáI¤ūˇFļąĩ¸Ū­8Ū[s<võę/:‚a0ŗfŒ„­/0įŠå|J"ŗf^sļMG@é9úC†¤$EīžWrËܗÉZų×7üŅ@. 'aüt^˜ŸÁ˜˜îVŸ ĪāÅÅĪ‘yæg>ŋ8åģN Æ?ĘUˇģ­ČFüĩ‰5~î`ÁžzV/úWU/† 1ÕxŅ@Ü5WTt€ũ2đĖUØ[PÄ_CÜĩwré{öđ�>öy�ÃākˆĢį#ZŧžaiÉŨ؟ģŖúpûļ~׎æ†` č�Ĩô oLÍ^1šĄÍųB4z^Ē{‚<ĀÆíßc=ŠØĶ/E%’ķŋĪũĻMF8Ã^Yc¤Î@p`¨úĄž˛=ėÍ|„GßĢ"uŲ&410<&ĻÖČVo“‰āĒī)lm -=@Ŋ0ĮÔ†Ģša`P“̆›bjŒ Á†ļą´ĸŪ->^Īįxėpę/üÔ_´ˇĻķÄ5žÜs‚¸ŠĶšąfķ7ã8Â4Œëz}ĪÆ§aÁ{6žpĀGbÆŪØE FúšŸ ƒū•Ë‚Ę ÷ŪĪ]Ōᚸغ5ÃīsĖOũ)?ĒĩĖč›úķZËd>{'˙Ņĩë ]įĻēđôĒĸôĐ÷l:ûō5sČZ[= Vē•ųsķę_ßķU|Īģ÷%đî9oFãņ�}¯fHÔkėßz|ÃūŠRF7°/ī;€/Ĩ_ė¯bāCW×é<ĒĨë:v4ĻļąÅu/ĶL_ņéž nX6Ė˙ž§ A„×Ę4‚‚ gŊLDQEé÷Ĩ@7uøNPzÂ{õđ‡†/7ķûīĢ8ņę]X^­ŗlŅ6žđ]ÍįŲ15kũüî™G˜ķyi™o1{HĶÃ#Á=ęt AAQEUk-=UøĒs9Öā˙œ&Bvķļą´ĻŪ-=^[s<võÕÔ_´ŋ\—{ā–kę´asŽŖđÁĖ^û}׾͖÷^`͞ęu5ŠSį0ûô'ižKÃč?l×ųL=˙•Ë~ÔĀrû+'Žãāž=ėøęŋųĶß;Ē‚Fū#e"#~ĘeM,ų—īŪãWŲ˜Ė—rl×.ūøŸˇqŏõdøĪFōõ3ōßQe9ŖëâāĢšÎKžĖãčTĶŲÂMĝ"ėq€`8Áw'ˆ^¤.}‰LuŪ3Ņ# †ëwgĶרčąWÔÕÄCßkŽ„_ÃŅŌœ¸‚[7Ôņļb}ķ(nŲĀÛ!¸r_dŐę.08UTÔ w><žF.߇_Í@ŧģŨNÉøģë?éûzU˙Æu×_xø"7Ī•“X=w$ĩļĖŗ‡%S×‘ķš‡“>Ų´:|Öâaīķ0įķ<ąō%&˜›wđœ¨ĶU?ā!Čļ™jÕßPŨæĩĻ\øÎũœŽĻUõnáņÚšãąŗ¨ŋ¨ĻūĸS5ë8¯$mæŌfBåҝųũúå,™; CÔGĖØÁužPuåŽÉi j(×đŖĶ#ę †§^AŊŦYËūéÆL ֌0|ØÆë9\}īũÜmž”Ŗgąęš˙a(ūË˙åOí^×"Ū}æ—õü"¸ēN &Š[îKäšĢ™ŋæŪē?æœßœ+\(ΙŽ`皏 m”ūLßž5.+{J)!ęĖĨî¸kÃ˛=äũ‘ā÷úO¤æa˜+6đåö`Šĸ3¤‘§é´|}Šc¯äõõ›yŖh=’_?{y5*;8zČæĶ'˜?˛o4xž11ážalúÕ*nÆëãLĩßö|Íâe;đÄ<ĘC āÉcãįUÄ͸›Ąæēķ{‘:x5 sķ¨HM8AĀsĸæ­”ŖEõô$-<é•äÎczn?[Û˛“[éūC”pöōęQWUAŅÄÖģšQS QėĀUTŖÍ}ßđûũUЕ;ÜĘzˇčxmÕņØYÔ_øŠŋčTÍ8Ž|Ĩøĸ¨;×]ë é;˜Ûg<Æšŗũßį]îËÕõ\ڇ1§1ü' ‡áŋũīAr>ų=_ģĘđĐŪæ+ší֑ÄE åū‡˙Î+/}BQŒ˙āoYųôzö7´ĐĨá\wīũÜ]ũ=î{ÛD2đ‡âÔgĮæī: ü÷-šÄāfüŽĀ_÷ŗâ™l×:už.4‡“Ÿä…ąŊpŊú w<ū"īlĪã‹}{ø]î?žÎ3ķ0\ŸÁ„ú~ŖNâg)=(ȜĮKŋ?DIÅ Žîßʂ)wq×Sged2Œ'ōxgķ5WT¯{%CŖūČ;ë<8éėŧšz´fũđä;šŽâCŪũ˛ˇŽņ(Ą¨Dnŋž|ķ6î+ĸäč>}ū5ž¨j|ūPxĘ/yÎĖž_?HúS¯ąqûžø2-īũ†Į>Ę&Ī0æ=ī?ųV|ū!û¸’[ޝ/5sCrÍ;ÛMÄÅQôå6 <� ß[NΉ ZëĀS´‡/\E”4įæ6Ī^ÎüŠ kĮpį�{÷}}ög˙ĄF˙úTđ÷›Y˛ækŽVœ d߯˙#=ŽÍĩõžCšQ˙¨a¤ÆĀ—+—ķŠĢ”ŠŖ_ķÎÜUuÅy˛5´˛Ū-:^[y<võę/:]sŽŖĸÍ,œ19īíĄ°ô%Ĩ‡øâŊ ėŖc†›ã'ÃR¸Ą‘0Ė˙îeÅĢëųüŋĘđüøû”ü×WŦz5‹Ũ˙ ?úÉîÖEn̆ũ~LßÛ&r÷OžãŨė}üo‡MņđōWo ˙ģ—ŋļ[]:_!čÁusŗČžf¯¯ßÆoõ>'Ē ¨{/úÆÃ™“Hģ6ǁ9W†Î]ÁKÁËyų׏đ{4q×OgÅĖŅgoĖ žšëbūėŽæē3Î‰bČĀîŧüAÉ×_Ņx[ŗ~đ5Ü20ˆ/ĒÆ\ëϐ(Ō~ŊŖŋZÎËSībqP4CÆMgvĘrĻ5ú¨ĢÜøë,˛Ž_ĮoßËãõį7pâîuCŦķÉzh4qÁā–č70p74Đ„\?’!Ī/`‹­”´ņQÜ2cûæžÆƒÖ ‚{10e:O¤3íK_õ O0×Ũs'Ļ_ŊĪŖ÷í`\æ’ĻįĀ•~ƒëœøü&^÷ÍĢ™ˇũuŌ¨_TĘcÜrb5Ž=@iUwL×NįĨš ŨiŪŖõ71aŲ|Ž>ĩ‚ųãī„˙É S~Á=f1ŊÁ_įģ‚VÖģEĮkkĮÎĸūBũEgkÆqtíVĖ]ΒĩĪ1q؟¨ ęN”éjRŸéĖcč¤1áÄ éÕČûåëO>áŋë vŪ">ød?Wß;>CbųÉîŨüo{Uŗ9ę Ã~•Žlų¯–¤ĶļđwūúWšđ¯^ū֞ÕédŨN:uǍ¨“ÉÔĻ{<ú“ŠgTØxdė ~õ/Ũ æųšÖGŲĪûdßojÛĸ}* &äĖ™ŲçßÆ|æķŲĢ֎}ŦSK\¨õ––Q!ąš –´pĻ.ë˙‘w~™…ŖĄ,iÄ#ĪaĀßöŗâéöžÜKÆĸņÄQĪ”‰FÃp/ŋŨ‘#ç…ũ˙öî 4Î:ãđ7e*ĩI@j´ŠDĄbOē`҃<”…U *TTŦkaĩ'ĄČ–•-X,*"–Vđ–{ xđâ%h¤9¨( Zą›BŨhJÚNē“töPŅ2Ķ’ģ��ÄIDATÅļéÄ6ķNōžc˜yųŪ|øķ›÷Í}ĪŋĄ›ÛüøwīåīoŒũŽuŽ—wîXĐįûû;īë˛âefz2ßÖ?ĪÁW_Éøā“9āŸ[˜ČÁmeĪÉōˇ—žĖ]×ū7ž“×>ē*÷ŧŧŠ‹ŖrŠÎMÛÜ/`yęĘNŧ21ce‚ßéß#;ōĐŪ¯síĖ+ģŸJĐä<ëķČŪ=™ŪũF^ÛūX&O^•ūuwäūŋž™ķüržzKunÚå~2•Ŗ?$įŊ ō7fÃ-ĩ|üŅ‹˛vË­įŪnøÃąŠ^C_ˆ‡į‰ááŖšũá{ŗúĀhęŸm6§›Í$íũĒŽŲ\Ū+‚xŨ°å_ųtKÕS,Uwfįû/ÎĨ×nĘÖŊ›˛uqŽžx–ęÜ´Åũ.ä?ųlüXüĶÅöˆ¯Î›7gôČČų{ÄĢ3´ycV§™Ŗã_U´?ܗë×_ėd¸ž[Ÿy6C7ÕķĪŒ–Ų4ffŌn7ffRÉAv‡tÕS&��ūßņą‘|p|žŗÉ?äšíäžÛÖĻŋ–¤Ö—ÛîÎŗÛŸČŊIķøx†ĮĒz>É5šū7=üëšÄÚÜ~ĶÕՌõŗFŖũÄm.hŋbéqB �t¯ŲŖ9´˙`VÎķ,â•3ôįúÍߛĮįíũyņÅ5ĶlĖäĮŸĻ29q8īú¤ĸáķ5§ÛÜÆt— ŊH1�ĐŨN~•á}¯įŗŽ~uķ…Ô3ŧkW†ĢaįV&Žüg—"A �tŋŲŠ|9:’/GĢäâšI˛rCļíųĮžT]ą7ĻĪũPîŌ[ÄÍ4pšŧŲ!�¸lõŒŽ}ŸĶ úN#ߎÎ7‹4ŅĨĖų(Ŗ—~˜Úé‰ņ|pÄĘ��ķšÉ7#oåőĒįX€™zí۝CUĪŅœ�P4A �@Ņ1��EÄ��M�P4A �@Ņ1��‹ĸ§§ę Ú#ˆ€ŽčëíMZ­ĒĮ SZ­ô÷õW=E[1�Đ[}hérųVôäŠ-CUOŅA �tÄāëŗíéĮĶ×ۛaŧŦõöŽÎ_ļ>‘u×U=J[zZ­VĢ^¯gppđŠ^xzzúŠ^�€å¯ŋŋķkNˆ�(š � h‚�€ĸ b��Š&ˆ�(š � h‚�€ĸ b��Š&ˆ�(š � h‚�€ĸÕëÂUŧ‡��Ę 1��EÄ��M�P4A �@Ņ1��EÄ��M�P4A �@Ņ1��EÄ��M�P4A �@Ņ1��EÄ��M�P4A �@Ņ1��EÄ��M�P4A �@Ņ1��EÄ��M�P4A �@Ņ1��EÄ��M�P4A �@Ņ1��EÄ��M�P4A �@ŅV$I­VËÜÜ\Õŗ��@Į­H’UĢVåÔŠSUĪ��ˇ"IÖŦY“'Ndjj*gĪž­z&��蘞VĢÕJ’šššLNNæĖ™3Ö'��(Æ/A ��%ō” ��Š&ˆ�(š � h‚�€ĸ b��Š&ˆ�(š � h‚�€ĸ b��Š&ˆ�(š � h˙pR*Ĩ„ہ����IENDŽB`‚��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/plugin-oauth2.png���������������������������������������������������0000664�0000000�0000000�00000171302�14156463140�0022062�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��Ę��“���Ļ`%Ë���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨetTW߆ņk&î$ —ˇā.Å­´Ô…ĘEZę-mĄ-ԕPÜŨĩ¸ģâ™y?Ϥ Ą}īßZY0įlųŸI>ÜŲŲįŒaãö=fDDDDD$ƒŲlVPšfčĐĄøûûc,îBDDDDDōjßž},[ļĖęšøøx~úé'ŠdŽBeŗŲLË6¨Tĩ&3˙œ¯žo}—JUkYģŖÎŨzr˙ƒįĢĪÕĢÉTĒZ“ }r‡Ēš÷œ>}š °}ûvV­Z•ã\bb"ĶĻM#44”iĶĻ‘••Učų ”ˇlŨÆÅđpÜÜÜøsöœB$""""bMšrå([ļ,�›7ofŨēu�$''3mÚ4ĸŖŖhÚ´)666…ž¯ĐAyæŦ9øx{3ôŠ'Øģo?gΜ-tQ"""""˙dooΐ!C(SĻ �ëׯg͚5L›6ČČH�ētéBŖFŠdžB常xV­ZMįNéŨĢƒ?gĪĩÚvūÂE´īԕęĩ‚iߊ+sæÎ/T;kvîÚÍC>Aíē Š܀n=z3kŽõznÔģß@žú,6nĸë}Ŋ¨^+˜ļē°tŲrbcã6b4Á šĐ°IoŊķ.9ú¯\ĩšžS3¨>5ęÔŖwŋ,]ž"G›ŦŦ,&~ü)›ˇĸFzô8˜}û÷įĒĨ}įnVˇb´iߙGę–×ņ˯ŋĶŠkĒÕ Ļ^Ŗf<7ėΆžģíõ‹ˆˆˆü[888đĐCQēti�6nÜČå˗hßž=͚5+˛š ”į/\HzF}ûôÂŋtiš5mÂŧ rí Ųū×FŊø2eĘøķŔÉŧ4zŋūūíØY v֜:}š‡{’ääd&~đŸOū„J•*ņōĢo°dŠõ ß×ŲŲŲqâä)~øiŸOū„ËáîîÆ‹/ŋÆķÃGĐž]V/_Âđ˙=ĮoLgîŧ–ž‹/å™į‡ã_ڏ)“?aęį“(WŽ˙>’…‹—XÚ}ņÕ7|ũí÷tëڙoŋšĘĀũyņå×HH,šÍæ?ū”wŪOëV-øņģ¯yûÍ×8~â$=Ā•+‘E2‡ˆˆˆČŊĀÉɉûīŋ?ĮöŠjÕĒŅĒUĢ"Įļ0˙œ5‡Š¨_¯.�ũúöfôK¯°~ã&Úˇmci7í×ßprrâ‹)Ÿáæę @HH3Z´j—cŧŧļŗæÔŠ3Պ͇ãßĨbÅ��Zĩaëļm,X´„îŨēŪ˛˙ŋáL˙ueĘøđĀũƒxõˇ¨Zĩ Ŋ{ö�āĄ!đégSØ´y ƒöā“IŸQ90Ī'‚Ņ˜ũ{GÛÖ­8rô(SžøŠž÷uĮd2ņ˯ŋͰA}Ūyķu˜*p˙ƒSĄ|ųÛ^ß­DFFņũ?ķđxũ՗-ĮëÕ Ļ}§nüü˯ŒyqTĄæšWdff˛ā‹ŗ'Nœ`˙ūũŲ<^Q>pđĮŽŸ ŋž–c]:uÄÅřŲ˙Øî°˙AęÔŽe ŋ�nŽŽ4nÔ°@íŦéŌš#3˙øÕ’!{Ĩ¸l™2„‡_ēm˙Ō~~– Pĸd �Ö¯o9f0đņņļŦ_ į|XíÛĩą„d�ŖŅHË!œ>}†˜ØXÂ.\ &6–fM›ä˜ŗQøē¸ÜļļÛŲö×_dffŌĄ};ŌŌŌ,_~žžTŠȞŊû =‡ˆˆˆČŊĀd21sæLΜ9@ŊzõđōōÂl6ŗ`Á:TdsxEųĪYŲO¸hĶĒ%1ąą–ãmÛ´fųŠUDĮÄāãí @Tt4uëåŖ”oНķÚΓÉÄô™2Á"BCĪ‘˜”f3éT¯^íļũŨ=ÜsŧļŊļ”īééųãļ˜L؟ŅqČŲšj.Y€+W"šzõ*€åũ¸Î`0P˛TÉÛÖv;—#�xøą'­ž¨PĄĐsˆˆˆˆ7ŗŲĖŦYŗ8qâ�õë×§gĪžÄÅÅņã?’ĀÜšsąąąĄF…ž¯@A955•E×ößvīÕ×j› ķøŖŲ7ĨŨėÃ˙L˙ØËœ×vÖLüxß~˙ƒöįÅQ#đööÂh02läčÛö-(ƒÁ�X¯ûú1ŖŅxĶë0e™ŠŦž>|ŸJ•*æ:nggWdsˆˆˆˆ—ƒräČ�ęÔŠCĪž=1 xyyņčŖōã?’””ÄōåËŠZĩjĄW  ŧlųJ“’5b8u­ėyũ­ˇ™=gŽ%({{{•ĢŨ…‹á9^įĩ5sįÍ'¨Nm>xo\ŽãąąąxyyŨļAøųųpéڊ¯6û–*‰möÛ•ķÚ233šī +æFŖ“)wxŽŽ‰ĻBë{™¯ßõéææJŊēEˇ/GDDDä^ÄųķįILL¤oßž–EK�~øafΜÉāÁƒ‹ī9Ę3gÍÆÍ͍§žxŒ!Ír}õīۇcĮOpđĐa�ęÔŽÅū‰ŗŒÅŽŨ{rŒ›×vÖ¤gdā÷-K—-'"âJžV¤ ĸ´Ÿ+°j͚›É333Yŋq#ĩjÖĀÃÃōåËáææÆēõsôß°qiii9Žy¸ģįzJÅūũ¸z5ųĻu4mŌæÎ_˜ãxff&¯Ŋų6[ˇm/ØŠˆˆˆÜcēwī΁sÜv¯¯/Æ ŖdÉÂom…åĐsįØąsŊz܇ƒƒƒÕ6}ûô`Öĩg*?8x0™™™<ņôŗ,^˛ŒYsær˙G¨^­jŽ~ymgMŖ† Xŋa#sį-`īžũLũōkžúæ;:´oĮšķalŲēä䛇͂zqÔΞ e؈QŦ[ŋ‘5ëÖķüđ‘\ŧÎč‘/�`ccÃāA9rô(ŖĮŧŠ•ĢųxįŨņšžxŅļMk·…1}ƟDEGŗk÷Ūyī}ü¯­[SĸD ž|üQVŽZÍč1¯°fí:–­XÉÏ=ÉÜšķq)‚EDDDîƒá–ĢÅ7Ž2VžƒōõđÛŋ_Ÿ›ļ)ãīOķfMY¸x iii´m͊÷ßKtL4Ŗ^z™¯žūŽĄO=A›ÖŲĪēģūymgÍØˇß ¤ySŪ÷?õ 'Nžä§īŋáÉĮÅÍÕ•į† üŌíŸ~‘_];wâ›/§péŌežũßp†M\\?˙đ­Ĩn€Ņ#‡ķčÃąaÃ&^õ"K—¯`ĘäOđķķ%33ĶŌîņGfđũ™ôųTZļé‡?æí7^ĨlŲ2ˇŧū1/Žâ­7^åāÁÃ<7lc^y {{{Ļ˙ū ÁAuŠüēEDDDūë æ[Ũi&""""ō˙ĖĐĄCņ÷÷/Ü'ķ‰ˆˆˆˆüW)(‹ˆˆˆˆXĄ ,""""rƒÄÄDâââ”EDDDDnäėėŒĢĢĢ‚˛ˆˆˆˆˆ5 Ę"""""V((‹ˆˆˆˆXĄ ,""""b…‚˛ˆˆˆˆˆ Ę"""""V((‹ˆˆˆˆXĄ ,""""b…‚˛ˆˆˆˆˆ Ę"""""V((‹ˆˆˆˆÜĀ`0`0”EDDDDŦQPąBAYDDDDÄ e+”EDDDDŦPPąÂöJt\q× """"rĪ1˜Ífsq!""""r¯¨]ˇ.ŽnÚz!""""b‚˛ˆˆˆˆˆ Ę"""""V((‹ˆˆˆˆXĄ ,""""b…‚˛ˆˆˆˆˆ Ę"""""V((‹ˆˆˆˆXĄ ,""""b…‚˛ˆˆˆˆˆ Ę"""""7pēö¯‚˛ˆˆˆˆČ *–´”EDDDDrđōķ °-Hįø„$¯D“š–^Äe¯˜¨–¯ZOjúëēDūÉŅŪžÎíÛĐĄm‹â.EDDäŪã艗mJūƒr|Bg/\ÆÛĶ /;QZąXšv#ÛwîĀĄ¸KšãRĶĶ™ŋl66FÚļj^Ü刈ˆÜ[Œ68Ø`ëExD4Ūžn8:8܉˛ŠÍŪ}û’å˙–­^_ÜeˆˆˆÜƒLdd (§Ļ§˙įBrff&éÅ]†Č]—’šĒŸ}‘JKájĻnæĀÖļ@[ĩEūėíėŠģ‘{JJtą((‹ˆˆˆˆäp6<PPÉ!6+û_e+”EDDDDŦPPąBAų.°ĩĩeĘÄq”/[¤ãŽūßĶtlÛ˛HĮü§*•˜2qܝŖ0îv}=ģväš'.’ąjՍƤ÷ß*’ąDDD¤čŨņįĸMũęvíŪ{ĶķC¸ŸíÚÜé2î˜*•h×:„ŠĘáāā@RŌUNœ:Ã˛Õ뉊Ž)îō%ŧéÔŽ5ÕĢâæęBJj*'O‡˛rŨF.\ŧ”ĢŊÁ``ÜkŖqvräĩw'’–ĪBoŪ¤!ģö =5^) <ŊēuĸLi?RĶŌ8xäķ-/öįŸ=ÆÔīĻk """rsw<(?üā Đ€ŖGņã´_ųāŊw°ĩÍ~vĢĢĢķ.áŽiÖ¸ƒûõdįžũ|7m:W¯&ãããEûV!ŒūßĶLüė+bãâ‹ģĖ;Ē|Y† }ŒđKLŸŊ€čØX<ÜÜhwí=ønÚ9v2GŸÚ5Ē’™™ÉĨˆHÕ fķöyžĪÆÆ†ū=ģrđČą<e/Ož{âa6nũ‹iĶgãåéÁ#ƒû“––Îü%+ō}ŊE)99…ĶgĪk """rsw<(ģģģYūÁŨ�oėíŗƒō¸ņ¨\š `iˇmû~›>ƒÉŸLäŨ÷?¤^p0į·~éiiiôëĶ›V-šžžÁėyķØŗw? ‰I”öķeP˙žÔŦQũŽ^—ŗ“ũ{ucõúÍ,\ļĘrüō•HŽ?Eīîņpwˇ”==ÜéßĢ{ö*´Ŋ=§Î†2kŪbĸcã÷Úhæ-^ÎŪ‡2°õ‚kķŌ›ã1™LF&Ž}/øÅ2^ŗFõi×Ē9ã?™ d‡Ãq¯fŅōÕŦ\ģ€Žm[R­J ööœ=wžš‹–[ú7ŦĀ^ŨyíŨ‰”)íËũũzRĒD .ED°}įÍ˙�0¸_/Â.„3å۟1›Í�D\‰âÄéŗ<ō@čߛw>ø”ĖŦ,KŸĻØ{ā0‰II4oŌ GPĒUG÷gôīYŽõėڑ˛ūĨųöįߙ0î5ėėėxį•‘ŦXŗŗĄį1™LTĢ\‰A}{âááÆų°‹L›>›¸øŧŊ<9pä¨å{Įö]{ŠY­˛ÕëéÛŖ+ŪžÄÆ'P­J%<ÜŨØšg?s.ŗ\_^jŊūũiÚ°=ēvÄÁŪžŊCÍęU™ôå÷ÔĒQ•'ēŸ‘¯Ŗfĩ*<t_fĖ]DĮ6-pws#&6ŽiĶg˙įŲšWûåV-šŗũ¯˜L&ËąģwͤQCėlmąĩąeŨú čכ‰īŋË3O?ɏ?˙ÂÅKŲŌ˙söNž8ÍĢcFķÕįŸŌļUK&OũФ¤Ģw´îZÕĢbgkk ĸ72™LĖ]´ŒĐķaVû>ķØ’““;a2¯›@bb>8€ã§ÎXą‚ĨmåŠÄÄÆQތ?�åĘøcÆLčų –6ĮNžÆˇTIœ�¨X‘ËWrŽS)€c'NąmįÖ Â`0XÎÕ ĒÅî}1™L<ųđ`N9Į+ī|Ā/3æŌ*¤ÉM߃’%|([Ļ4+ÖlČ"–­Z‡‡ģ•,Įŧ<=¨Y­ ;öėg×Ū”)ígšļÛÉĖĘbâä¯�xįÃI–÷Ūl6ĶŧI>ûúÆM˜ŒŗŗÚĩāôŲsü2}NŽq<=܉މĩ:GVVĩjTå\ØŪ˙d*ã?žBŊ Ú„4i˜§oäīį˃û0ņ ^û!Ąį/ĐŽUHŽŸõë223qvrĸj`E>™úī|8 ŗŲluzˇŽm™2qœÕ¯nÛæģNąŽØƒr“Æ IMMáāĄėÔÔÔT:BHHŗk- ÕĄŒv˜ĒQ­*~žžėÛˇ“ÉÄĻ-ÛčŲŗ;>ŪŪØØØĐĻuKJz{ŗc׎;Zw /ââHMKËWŋ åĘâ_Ú—š‹—“žžNzF –Ž$ |Y|K–āÄÉ3d\/OŒŖōĩĐ[%0€“§Cs„­Ø¸xĸcb (­ME6oßE@š˛  •Ęsėäiöė;ˆŖŖ5Ž­¨ÚÛÛSŖZūÚŊ—ŠĘáåéÁĘĩČĖĘ"2*šm;öÜôZJ•đ <âŠÕķ‘Q1ddfZÚAöūâķšq…ĤĢ9~’æMäë=ü'–Ŧ\K|B"ņ ‰:rœŌžĨŦļ­Y­ õ‚ją|õĢįÍf3ņ‰IėÚ{�€„Ä$>JZų˙ EPí\ЏÂÎŊûÉĖĖdË_ģ¸t“÷ ŗŖŅČęõ›1›Í˜L&NŸ=‡_Š’šš.]ĩŽeĢÖå:žlÕ:–Z9."""sĮˇ^܎““ ę×cëļŋĒÃŪũ(áãC`Ŋ–6%K”ČŅĮÛۋ¸¸xââãHKKãŗ)_æ7**úŽÖmlmlrkX7ˆ‡îīky}éō>œœŗļR%|0 |4îõ\cúx{qėÔi† 惪ƒU+ręL(§CĪŌ¤k6nĄJĨŠ9~"Wßc'OS) §JĨ�VŦYOãu)ãī‡ŅHVVÃ/c6›ŲwđMÔåČą“ÔŠY˜˜8΅]¤~pmŌ32Hēšl7ōīŖ™ėUdã ĢĶ9ΛÍnlg4ŌŦQ}VŦų;¤nßĩ—!ûúæēȨŋoœLĪČĀÖ6÷vãuéÛŖ ßM›~ķĀ D˙ã&ˏ¸x*V(—īš<=܉ŠÎšrvņeũũnÚįÆm™™ØŲŲYmw=wŊļ‚Ŧ,""RôŠ=(´l¤ĪĻ’’’ÂÎ]{hŪŧiŽķY7ėo…ė­  ØŲŲđú+/QĨrā]Ģ âJ$nnŽ¸šš’˜˜ĀĄcĮųpRv0Ž\›ā:5sõKĪĖ 3+‹‘¯ŽŊéØW"Ŗ¨XĄU*pęL(gCÃxhP_ŒF#•Ę3wҞ\}Nœ:CģVÍņôpĮÖ֖¨čX΄ž§rÅ ØØØpüäiËöˆm;÷đėáčā@Ŋ ÚüuíŠ$ļļļđ-FãÍ˙č €i_âs/YÂ[[["ŽDŲ7ņy¸ģŅĢ['ztí�€Ž4¨Äļģ­Îc¸Iŋ‘ĩ­7ęŪš=ëķų×?~9â–mmūņ Ņh„[oqc­r˙ėŪŽÎü¸1+$‹ˆˆŊ{"(רVwļlû‹C‡3䁁9ÎG\‰Ėņ:**Šā Ú¸šēââėL؅ 9‚rdT4%oøs˙päøIRRRš¯S;ĻĪY@jj—RŗW*o˛G:22[|K– "2;@ <=ūžņīØÉ3TĒX*Yĩ~É))$&&ҏ~0Šii–~7:~ę ßߏZÕĢręL(�gBĪŅ ¸ļļļė?tÄŌöäéŗ$$$Ō¸A]jVĢĖėK�ˆ‹OĀŪŪggŽ&g¯*ûųæūĶ˙uŅąq„žŋ@įv­9vâtŽØš]+bãâ-Ov¸~ß?Ÿ6Ņš]+Bš4`ێŨddfæZ ööō¸i yŅš}kęÖŽÁĮSŋĩüRs+>Ū^9^—đņ&.>!WģÛ՚t•ōeËä8ĢÕä‚P@šsŠ}2dŖ͛3gŪ|*âíåãüŅcĮ9rô™™lÜ´…¨čęĐŽ]k–,]Îų° ˜L&vîŪÃëoŊÃÅđđ;ZsZZ:3æ.¤iŖú<ņĐũVŦ@ÉŪVŦĀ}]:ĐŖkŽ?•Ģ_øåNŸ=Gŋ^ŨpssÅÎ֖]:0ōš'-̎'NĻ^ZØÛŲq%2{ëÙĐķ´oÂąšĮ„ėG]Žˆ¤mËæœ: dßČVŠb*”åØÉĶ9Úoßĩ—ûē´įLčyK<{.Œä”ēvlƒŖŖʕĄIƒzˇ|fĖYˆi_F<ûÕĢR˛„7•ĘķøÔŽÍo3įb2™đöō¤FÕĘlÚú1ąq9ž6lų‹ åĘRĻ´‘Q1–=Õ�eüũ¨Våī_‚ŽoĪđ÷ķÅÁÁūvß&ĘøûŅĨ}kfĖ]„Ņˆ§‡ģåëfhß:[ʗ-CZÕŲøhŽvˇĢõđŅ”-Sš ZÕąąąĄYãV÷‹ˆˆČŊéžXQ iĘŧ…‹haš‰īo­Z4cõÚu|6åKyę‰Gņķķ g÷n¤ĻĻņҤĪHOMÃĪĪ—g‡>mšųīNÚŗ˙qņ tlے§ŒŖƒW“S8{.Œī§MįøŠ3VûũüĮ,ôžˇĮŒĀd2zū_|?ÍrƒŪÉĶĄ”,áÍūC‡ŗĶĄįiŪ¤!+Öä~ĘÆuĮNžĻC›œ:“Ŋ‚›˜Dzz:&“)×#ÆūÚĩ—îÚņ׎}–c|ķĶī ėsīŋõ2Ã/ąlõ:}`�FŖŅęĶ.^ēĖGŸM×m2°/Ž.Î$§¤rōô>žō­e›Cķ& ‰ŠŽáäĩÕî…_Ž ô|!MōįüÅ,Yš–Į‡ "%%…ķÂŲ°y;•¯=9#6.žƒGŽķĖãCØ´u­Ø]_QņėšÎ cũSņ΄žĮÅŲ™÷Ū| [[ļîØÅöšojŒŠŽše­ĄįÃX°t%zßĮƒövėÜŗŸ­;÷hŋŗˆˆˆÜ=ÍÕ§ti æ|nšÜ{ø$eüŠ~UėäÉSLžú%Ÿ~ô!ö¯Ž?úõ‚š¯[—"ŸķFīNøôŽŽ¯ŠPžĄ>Čī}DFffq—sĪøįs ËÎΎŒnR|`@olmløeƜ[ôēģîå()O=õĨKû˙Ö “ÉDDÄ~ūm:]:wĘ’Ĩč Jøx3¨oVoØŦ|•,áÍ'īŊAÃzAØÚÚR) <õęÔâĐŅãÅ]šˆˆˆäAąoŊ˜ģ`ĢV¯ĨY“ÆtíÔą¸ËųĪëŪŠmZ4cįŪũŦŲ°Ĩ¸ËųO‹ŒŠá—sčŌž  čM||"+Ön`ĪūCÅ]šˆˆˆäÁ=ŗõĸ¸ũÛz!rļ^ˆˆˆätĪlŊš)(‹ˆˆˆˆXĄ ,""""b…‚˛ˆˆˆˆČ Ėf3fŗIAYDDDDÄe+ō”ííIMKģĩ+;;ģâ.AäŽspp(îDDDîYųĘūž>ÄÆ%’š–~'ę)6AĩkCžž(-ō/g†NíZw"""÷Ŧ|2Ÿ‡ģ+eũˆ&:6ūNÔT,‚ƒjSÖ¯$Ë׎'í?öK€Č?98ØĶĩCÚˇnQÜĨˆˆˆÜŗ ôÖîŽx¸ģu-ůV:´UpŨĖ'""""b•‚˛ˆˆˆˆˆ Ę"""""V((‹ˆˆˆˆXĄ ,""""rƒ¤¤$âãã”EDDDDnäá၏‚˛ˆˆˆˆˆ5 Ę"""""V((‹ˆˆˆˆXĄ ,""""b…‚˛ˆˆˆˆˆ Ę"""""V((‹ˆˆˆˆXa[Nņ I„_‰&5-Ŋ¨ë)0W'J—ôÁÕÅŠĐcå;(Į'$qöÂeŧ=Ũđņō(t˙/GR¯V•â.CDDDDn#>1‰Ķįà ,ī_谜ī­áŅx{ēáčāP¨‰EDDDDŠš‡›+e}št%ēĐcå;(§Ļ§+$‹ˆˆˆČ=ËÃ͕¤ä”BŖ›ųDDDDDŦPPąBAYDDDDÄ e+”EDDDDŦPPąBAYDDDDÄ å˙Īžū‘Ĩ+×°`éJžüū—bŽHDDDäß+ßa]¯ŋ5Ž˜˜X>xīm<==-ĮĶĶ3xúšaŧ;öMʕ)s7JškÆ2•ËWŦžĢ\›Įx—+‘ü¸+AĀÆÖČī3fņü3OŨ­)‹]ĢæMh×:$×q{ģb¨FDDDDōãŽåî];3oá<DPÚVۘÍfæ/\Ė–­ÛˆOHÄĮۛ^=ģĶŦIc�ūœ=—Ȩh|ŧŊ8xø(‰ ņtīڅ’%K°dŲ ĸĸc¨Vĩ2CŸ|ŌĶ3˜=o{öî'!1‰Ō~ž ęߗš5ĒߕkvrtĀĮËĶ까Œ >˜ôMÖ§SģV�ėØŊ9‹–ņƋÉOH`Âä¯xuÔķøûųæęo2™˜ˇxģöîĮ`0вy“Ü“`ņŠ5lÚļŖÁ@ƒēAôíŅŖQ;nDDDDnįŽ%&OOz÷čÎ/ŋO'-=Ũj›Í[ˇąrõF žožøŒŪ=īãģ~&2*�;;;8HęÕ?öMúöéÍŦšķ8pđ0oŧ:†ņcßâāÁ#ė?x€?gĪáä‰Ķŧ:f4_}ū)m[ĩdōÔ¯HJēzˇ.ûĻėėėxp@VŦŨHLl)ŠŠĖ_˛‚Ŋģãæę‚ģ›];´ÁÍÕÕj˙m;÷°sĪ~† }Œq¯ŋˆÁ`ā\ØÅmΆ†a�Ūyy$O?ú íŪËÆ­Ũ…Ģų÷ģk+ĘfĖtîØž­ÛļŗpŅôë“ĢMĶ& ŽSwwˇk¯ņĶ/ŋFÉ>`�///‚ƒę�PĩJ ™™Y´hŅƒÁ€ĢĢ ĨKûÉdbĶ–m<ķôøx{ĐĻuKV­^ˎ]ģh×ĻõŋæUë7ŗnĶļ\Į˙÷ôŖTŦPŽĀŠhŪ¸>ŗ,ÅۊŽ–1�� �IDAT˃€ōåhX7�w7WēujwĶą÷î?DũāÚ–ÕæNm[˛nĶÖmėėléÚą-FŖ‘ŠĘTĢ‡Žž M‹fEx•""""˙Mw-(ØØØđČC2áãOiÖ´1ĨJ–Ęq>+3‹…‹—°īĀAŌĶŌĀ`$--ôô K7Ë˙íėėpwûû˜ŊŊ-™ÄÅĮ‘––ÆgSžĖUGÔĩę;-¤ICÚļlžë¸§§ģå˙=ēväũ§pęL(¯ŋ8,ĪcĮÅ'PŊjeËkŖŅH o¯mJ–đÉąÍĸ„ˇįÂ.äįDDDDūßēĢA Jå@Bš7cÚođŌČ9ÎũúûtB·1fôHJ•,ĀsÃGæhc0riíØõũú+/QĨr`Q•Ÿ/ÎNŽ”,á}Ë6))Ф¤Ļa2›ˆ‹OĀÃŨí–í¯ËĖĖĖuŨY&SŽ×ļļ9ŋŊƒ;[ŨH("""’ÅrW×Ā~}‰¸ÁÖmÛs?yú4!͚XBōåË$'§h7WW\œ ģs5ō.­&įՌ9 iX/ˆŽÚō۟ķČĖĘĘS?wĸcc-¯333‰ŽŽÍŅ&2*ŗŲly‹× ĢŲ""""rsÅ”]\œ4 ?ŗæÎÃÆÆÆrÜĮĮ‡3gĪ’••ÅĨ˗™1kžžÄÅÅhžvíZŗdér·]Ād2ąs÷^ë.†‡ÕĨÜRrJ*‘Q1šžĸc˛íÎ=û ģN.hÛ˛66F–­Z@BbKWŽ%ņ&7ÖŽQ=ûv1œ””T.[•ĢÍÕäÖlØBff&aÃŲwčuëÔēs,"""ōrס^\Ōŧ)›ˇlåčņ–cƒúõåûŸ~æŲáŖđ÷ķ呇äĀÁCĖ]°�'G‡|ĪŅŗ{7RSĶøhŌg¤§ĻáįįËŗCŸĻŒŋQ^ĘMmÚļƒMÛvä:îččĀ[cF0{áR÷ë…ãĩkܯ'“žüāÚ51 ,[ŊžēAĩpsuÉ5FÛV͉‰‹ã‹ī~ÁŒ™ļ-›XŠĻkÛ/23ŗ¨Q­2W““yũŨ0„4iHŖúÁwöĸEDDDū# æ˙6Ÿ{Ÿ¤Œ_É;UĪ=éâåHęÕĒRÜeˆˆˆˆHí=|˛ĀųmčĐĄøûûĪÖ ‘{‚˛ˆˆˆˆˆ Ę"""""V((‹ˆˆˆˆXĄ ,""""b…‚˛ˆˆˆˆˆ Ę"""""V((‹ˆˆˆˆXĄ ,""""bEžƒ˛ŖŊ=Šiiwĸ‘B‹OLÂÕŲŠĐãä;(ûûú—HjZzĄ')Jņ‰W9w!‚ŌĨ| =–m~;x¸ģP֏đˆhĸcã ]ĀŋÅŪÃ'‹ģš Wg'*•÷ĮÕĨđ+Ęųʐ–=Ü] =šˆˆˆˆČŊJ7ķ‰ˆˆˆˆXĄ ,""""b…‚˛ˆˆˆˆˆ Ę"""""V((‹ˆˆˆˆXĄ ,""""b…‚˛ˆˆˆˆˆ Ę"""""V((‹ˆˆˆˆXĄ ,""""b…‚˛ˆˆˆˆˆ Ę"""""V((‹ˆˆˆˆXa[Nņ I„_‰&5-Ŋ¨ë)0W'J—ôÁÕÅŠĐcå;(Į'$qöÂeŧ=Ũđņō(trw]ŧIŊZUŠģ ‘;">1‰Ķįà ,ī_谜ī­áŅx{ēáčāP¨‰EDDDDŠš‡›+e}št%ēĐcå;(§Ļ§+$‹ˆˆˆČ=ËÃ͕¤ä”BŖ›ųDDDDDŦPPąBAYDDDDÄ e+”EDDDDŦPPąBAYDDDDĊ{*(Īøs6OúŧĐã8xˆ§žų_T$˙eGŽŸdäĢc‹ģ šGåû#Ŧ âõˇÆq1<�ƒÁ€›Ģ •*V¤}ģ6ÔŠ]ËŌŽ]›Ö¤ĨĨŨ’ ėõˇÆËīŊ§§§åxzzO?7ŒwĮžIš2eŠąÂĸ•t5™WĮ~xĶķƒúô EŗF…šcīÃüøÛLËk;[[||ŧŠSŗÚ´ĀŲ)ī?šå¯]4Ŧ„ƒƒ}Ąjš+A cûļtíŌ‘ĖŒ,ĸĸŖŲwā “>˙‚>Ŋ{ŌŖ[�J•*yˇĘ)[#ŋΘÅķĪ<UÜĨÜ5îGĨ€ šŽģ8î3Ôo4öÕQF’’Ž~9‚5ļ°{ßAF=˙înˇíŸ™•ÅœKŠSŗē‚˛ˆˆˆÚ] ĘŽŽx{y؁¸fę”+[†ŸĻũJƒzÁø—.͌?gsáb8/ŽNLl,ŋü6“'O‘™•E…ōåōĀũ”/W–=ûöķĶĪŋ2 _V¯]K\le˖噧ŸÄŨÍ5×Üį·1cæ,BΟĮh4Rŗzuō�înތ?Ę•+ņĀ –öÛļīāˇé3˜üÉDėlsŋEŨģvfŪÂ%8xˆ :ĩ­^oRŌU~ûc‡#--ōeË2dđ Ę0nü‡Ô æėšP.†_"++‹Į}˜'N˛wß~ââčÜą=Ũēt˛WŦgĪ›ĮžŊûIHLĸ´Ÿ/ƒú÷Ĩfę…ūŪä…ģĢ+>^žVĪeddđÁ¤/hÚ°>Úĩ`Įî}ĖY´Œ7^N|B&ÅĢŖžĮßĪ÷Ļsxz¸c4ņôp§l™ŌÔ­S“Ļ|ËüÅ+xäū�\ŋÄÜEË ģŽŅ`¤jåŠ ęÛG{Æŧũ™™ŧķá$:ˇoMįv­Øŗ˙+Öl 2:g'GÕĻg׎ †ėI ö<ÂüÅËIŧšLåJ2°/nŽ.�ˇėo2™˜ģh9{"%%•Ū^tíØ†úÁu�ˆ‹gö‚Ĩœ=FZZ:•+U``īûđņö*Ēo‹ˆˆˆÜAÅēGšeHsŧ<=Ųĩ{oŽs3gÍÅÎ֖'ŒįķO'RŊZUžũá'�lml¸š|•sįÎņί3éã �üūĮŒ\ã˜ÍfĻ|ų5ūūĨųė“øāŨąÄÄÅ1{î|�ZĩhÎöŋvb2™,}vîŪM“F ­†d�OOz÷čÎ/ŋO'-=Ũj›éÎâJdãĮžÍÔIPĄ_~ûåŧ-ë7näÁÁƒ˜0~U*2勯đöōbė[¯ķô2{î|“’�øsöNž8ÍĢcFķÕįŸŌļUK&OũФ¤Ģyx§ī,;;;Їk7GJj*ķ—Ŧ`@īēāîæF×mpsÍũKĖ­ØÛÛĶŽUsö:BfVfŗ™īĻMĮΎ$īŋõ2oŽN\|" —­ÂÖ֖—_x€w^Iįv­ˆŠŽáį?fŅąmK>yī ž{ōaļlßÅŽ},s˜L&ví;ĀČįŸâ‡‘ÄŒ9 �nÛÛÎ=>v‚—† å“÷Ū w÷Îü6s ‰I˜Ífžųéw\œx{Ė>xk üôûŦ"z×EDDäNģk+Ę7Sēti"#ŖrOIIÁÍÕGGG }zõ O¯–ķ&“™Ž]:a4�ZĩáĮiŋäĮ`0đίago‡ũĩ¯†õë˛uû�š4nČ3˙äāĄÃÕ!55•ƒ‡ŽđʘQ7­ŲŒ™ÎÛŗuÛv.Z€~}rĩyčÁd™˛pqv ¤y3V¯]ĪÕäd\œ1 ÔŠ]īėUö*•Ųŗg-Bš_{]“ÉDTT4.ÎÎlÚ˛gž~ÂŌžMë–ŦZŊ–ģvŅŽMë<žÛ÷õOŋcŧž {ŅÆČGã^ °bš7ŽĪėKņöō  |9Ö ĀŨ͕nÚhŪŌž%ÉČĖ$11 /O^~áYėėląŗÍūĒ[§&;÷ėˇÚ×Įۋ÷Ūx 7W ū~žT (Īų°‹4Ē dåîÚYļv´nŅ”™sa6›oÛ?%%#ÎNNFjÕ¨Ę'ãßÄ`0zūá—#xáŲĮ-Û@zuīÄĢc'q% ßR% ô~ˆˆˆČŨSėA9ËdÂ`ĖŊ°ŨķžŽLųōFŊô*ujפ^ŊzÔ ú{›ƒÁ` „åĩˇéé\ŊšœkŦ3gΞxér.G\ =-OO�œœœhPŋ[ˇũEpPöî?@ +VŧeŨ666<ōЃLøøSš5mLŠ’Ĩrœ‹‹cÖÜųœ9s–LSæk+֙鐝ņđpˇ´ˇŗŗÃÕÕåZđ{{;�22Ō‰‹#--ĪĻ|™Ģލ¨č[ÖYT÷ëIÅ åsûGnĻG׎ŧ˙ņN åõ‡ÉŧYYĻkseOv•k7råÚ/Wé7Ũŋl0ØŊī Ûwî!!1ƒÁ@JJ*ž7ŧī�ĨJüũsTÂۋĖĖL’Ž&ãæęrËūMÕcīÃŧūŪDĒU¤VĒ4Ŧ[{{{"Ŗĸ1›ÍŒyëũ\uEĮÄ*(‹ˆˆü kPÎČČāüųķÔĩ˛Īˇr` }0ž#Grāā!ž˙ņ'ĒU­Âđįŗ˙ŧn6›ÉĘĘÂÆÆČÜ�ü#ŧ…_ēÄį_|ŀūũh×ĻvļļŦXŊ–uë6XÚ´l¤ĪĻ’’’ÂÎ]{hŪŧižę¯R9æÍ˜öÛŧ4r„å¸ÉdâãɟS%0÷Æž…‹‹3įÃ.đÖØ÷rđZ ˙Lž×ØŲe¯HžūĘKTŠ˜§ÚŠš‡ģ%Kx߲MJJ*)Ši˜Ė&ââōtŪ턞ŋ€““#înŽD\‰â۟˙ w÷N´hÖ[ÖoŪÆĻm;ŦöŨžs‹WŦačcR50ûŸīĻũ‘ŖÁ`°ü ] ŲOŪ¸]W^ö4įÂ.pøč V¯ßÄĘ53âYėėė°ąąaōoú=‘âQŦ{”—,[AZZÕĪu.>![[[ęņđxqä ėŲ쟸øK›ˇl\‰ŒÂÁÁÁ˛ÕáēĐsį1ÚØŌŠ}[˞ã3gÎæhSŖZU<<ÜŲ˛í/>LHŗÆyž†ũúq9‚­Ûļ[ŽÅÅĮMĮípqqļ:g~¸šēââėL؅ 9ŽGŪĨÕäŧš1g! ëŅĩC[~ûs™YY…/éj2ĢÖmĸAŨ:FÎ_¸ˆŅHëĻØ^ ˇĄį/Ü´˙™ĐķTŽTÁrM&a/åhc6›‰ŠŽąŧŽŽ‰ÅŅÁGG‡ÛöOOO'==ƒ€ōåčŪš=¯Žú)ii;qŠ’%ŧÉĘĘ"âJTŽšbãâ õžˆˆˆČŨsׂrZj*1ą1DĮÄpėø ~úå7æ/\ĖāA-OøÎd23îŊXŧtŠŠidddpâÔiœpŊö4ŖŅČÂÅKššœL|\<kÖ­§~Ũā\ķ–đņ&#=sįÂHKKcíú \šr…ĢW¯’‘™ d¯"ļlۜ9ķæS900W=ˇâââĖ ũ™5wžeeŌÍÕ {{N:MVV‡eĪžė}´ JíÚĩfÉŌåœģ€Édbįî=ŧūÖ;–įSßiņ ‰DFÅäúJHĖžŲpįžũ„] §G—´mŲ #ËV­ !1‰Ĩ+גx›ã∋Oāō•HvėŪĮÄĪžÂÕř]:�āíåIFf&Â/‘ž‘ÁĻm;ˆŒŠáęÕ2ŗ˛,ÛUÂ/Eš–†ˇˇ‘$'§t5™™sáėėD|Bbö„æėŸŖĨ+ב’’JbŌUÖoŪNŨ ėg{ߎ˙ô9 ųeÆl¯Ũŧv!œôô JøxSĻ´•Ę3{á“ČČČ`ŅōÕLúōû7ŽŠˆˆČŊëŽmŊXĩfĢÖd''G'*VŦĀ‹#‡SģVÍ\mFÞ{–ßgÎdņ˛1PŽ|F {ÎFmmmŽÃÛãÆOĩjUōĀũšÆĒZĨ Úĩa⧓°ąĩ%¤iS†=?”÷'|Â+¯ŊÉ'?� $¤)ķ.ĸEHŗ|_[HķĻlŪ˛•ŖĮO�Ųû~čf͞Īŧ‹ŠV­ O>ößūđ~ô)oŧúbžįčŲŊŠŠi|4é3ŌSĶđķķåŲĄOSÆß?ßcį3įZ=^§fu÷īÅė…KܯŽŽ@öžæI_ū@p획–­^OŨ Z–ĮŽYķöŸŲáÕË̓úÁĩéÜž5NŽŽ@ö ƒ­š7aĘ7?cccC“uyę‘ÁLúō{ÆM˜ĖØWGQģF5žūé7Z6kL×m8söož˙ n.ÎtëԎēAĩøņיü2cuëÔÂÉŅ‘Ú5ĢōÁ¤/HNIĄj`%útī @ĢfoŲŋ¯n˜ģ˜w?ūœŒŒL|ŧ=Øģ;åËfāĖŖ `Öü%Œ0Ŗ‘€ōeyîɇ1ZŲ“/"""÷ƒŲl6į§ÃŪÃ')ãWŧ rāā!Ļ|ņ5ß}=ĩČÆ<yō“§~ɧ}ˆƒũ÷Ã*.^ޤ^­*Å]†ˆˆˆČĩ÷đÉgžĄC‡âīī_üOŊ(n&“‰ČČ(~ūm:]:wúO‡dÉģ˙÷Ayî‚EŦZŊ–fMĶĩSĮâ.GDDDDî˙Ę T§v‘mģčß§ũûô*’ąDDDDäŋCw‰ˆˆˆˆXĄ ,""""b…‚˛ˆˆˆˆˆ Ę"""""V((‹ˆˆˆˆX‘ī ėhoOjZڝ¨EDDDD¤Đâ“puv*ô8ųĘūž>ÄÆ%’š–^čÉEDDDDŠR|âUÎ]ˆ t)ŸB•īį({¸ģP֏đˆhĸcã ]€Ü}{Ÿ,îDDDDîWg'*•÷ĮÕĨđ+ĘúĀwW<Ü] =šˆˆˆˆČŊJ7ķ‰ˆˆˆˆXĄ ,""""b…‚˛ˆˆˆˆˆ Ę"""""V((‹ˆˆˆˆXĄ ,""""b…‚˛ˆˆˆˆˆ Ę"""""V((‹ˆˆˆˆXĄ ,""""b…‚˛ˆˆˆˆˆ Ę"""""V((‹ˆˆˆˆXĄ ,""""b…mA:Å%$~%š´´ôĸŽGDDDD¤Āėņ/僧ģkĄĮĘwPŽKH"ôÂeŧ=Ũ(áåQčDDDDDŠJjZĄ.PÖ¯Đa9ß[/.EDãí醪ƒCĄ&)jŽx{ēq)"ēĐcå+(›ÍfRĶĶ’EDDDäžåčā@jz:fŗšPãä+( †BM&""""rˇ6ģęŠ"""""V((‹ˆˆˆˆXĄ ,""""b…‚˛ˆˆˆˆˆ Ę"""""V((˙K|øŅ'Ė[¸(Īíã7~O=ķ?Nž<uGjzôÉg8zü„ÕsŠŠŠ<úä3œ>}&ßãžķîû,^ēŧ°å‰ˆˆˆJ>Â:ŋFžø ];w¤SĮö9ŽĮĮÅķ‹/ķÚËŖŠZĨ ;wíæ‹¯ŋËŅÆÎΎRĨJŅžm+Úĩi @BbÃGžČKŖ^ VÍųĒeíú üōÛtzŪ׍žŊ{æ8÷ëīĶYŗnƒåĩŅhĀĮĮ‡õęŅģgwøßˆŅôíŨĶROAæēË—#¸q…zÁuōÕīēģöÍĮßĮÕÅĨ@cüĶĄÃGđđô \™2ˇmkooĪË/ÂßŋtžįyäĄqs+üĮNfdf˛aãf:´kSčąDDDä˙Ÿģ”ķëã ã1ŗŸ{—––΁CGøí8Ø;ŌŧiĄÆŪ°q3M7bĶæ­ôîyFcÎEõŠöüP�23M\¸p_ŸArōUôá"ëVvėÚMLLlƒrJr >Ū>x¸ģ¨ŋ5K–¯¤u‹<eŖŅHjU 4Oŀ ę÷OgĪžeõšu Ę"""R ÷dPöōôÄÆÆÆō紟gΜeįŽŨ… ĘgΆ~‰Ņ#_āĩ7ßf˙ÁCÔ ĘŅÆÖÖo/oËëR%KŸĀĖ?įōØ#åųÁÕˇ›ëÍąīŅŧicēvî@zzO?7Œ7_ÁC‡Y´d�[˙ÚÁˇ_|€Édâģf׎=899Ķĩs:wękîŲsįŗ|å*˛˛L<ũü Œ~a•*V`Îŧ…ėÚŗ‡äädü|ũčy_Wę^Ģiė{Pŋn]víŨ‹ƒŊ=¯ŊübŽ1'|ô)GŸāÔÉSlŲļŅ#†Ëø zž>><4d05ĢW#55•gū7‚7_C``%6lØÄŌ•Ģˆ‰‰ÅÕŅ–-šĶ§WĢīį;īžOÃõš¯[ūœ=—ȨhJûų˛˙ĀAââ¨YŊO>ū666ÄÄÆōËoĶ9yō™YYT(_Ž!ÜObb"“?˙‚ŒĖLË{PĩJ ķ.fËÖmÄ'$âãíM¯žŨiÖ¤1ĀmįXĩf+VŽ"11‰€€ō<p˙ *”/Āąã'™3o>ÃÃą1ÚаA}€ŊŊ]ž~fDDDäŪō¯ŲŖl4ČĖĘ*Ôë7nĸnŨ`ÜŨ\iÚ¸6lĘS?{;{Läī# :@Ÿ^=hÔ >­Z„XB2ĀæÍÛ¨ÄgŸ~DĪ]™9{qqqšú÷īۛž÷u§b@�ß~ņÕĒVföŧ<|„1ŖFđų§͏a}Ļ~õ W"Ŗ€ė-.6oĻ_īžŒū\Ž1_~iîîî<ņØ#– °rÕZ4ˆĪ?HĨŠüöûŒ\}/_Ž`ÚīđäcđÍŸķâ¨álŨļŨ{öŨöŊ°ŗŗãƒøú–bė[¯ķîÛo°īĀvíŲ ĀĖYsąŗĩåã ãųü͉T¯V•oø‰Z5k0øūøųúZۃÍ[ˇąrõF žožøŒŪ=īãģ~&2*:OsíÜĩ›ų ņÄã2铊\)O?›JFf&Ņ11Lú|*͛5aęäOyį­×8ʂE‹o{"""roēįƒrVVb÷žŊ4Ē_¯Ā㤤¤°}Į.Z\[‘iی‡{Ë~WŽD˛bõjęÕÎķjrAįēĒUĢШA}hŲ"“ÉĖĨˆ+yęģaÃfēwíDŠR%ąĩĩĄs§xzx°˙ĀA û‘Š*T§6ÎNÎyŽŠUËĘãääDŗĻštų2&“)G›Ģ)É�¸šēb4(ãīĪįͰAžŸpsw#¤Yö{éîîFŲ2e¸té2ũ^ÛÛÛãč舃ƒ}zõāŨˇß°:TĶ&™0ū]ʖ)ƒŅh¤i“FØŲŲ–§š6mŲF“Æ ŠQ­*ÎNÎô¸¯ûõ!==ííĀĪĪ—ļ­[eīm÷öĻ{—ÎlÚ˛-ĪīĨˆˆˆÜ[îÉ­ĪeųfF.ÎÎtīڅ֭[xĖ­ÛwāäčHÚĩ€ė}°ūĨKŗiĶVzõėniwęôž~ū�ĖYYd™LÔ bČāû‹|Žüō-UŌō;[[ŒFééˇí—HjZ*ū~ßXg0đķķ#*:ÚrŦÔ ãįŊĻR–˙ÛÛÛa6›ÉĖĖšō_ąB�!͚ņúÛã¨R9ÚĩjŌŧYŧŊŧō4‡wÎvļvvd¤g�ĐķžŽLųōFŊô*ujפ^ŊzÔ ĒmuœŦĖ,.^žIOKƒ‘´´4Ō¯uģšŽ\‰¤Fõŋ÷];:ūŊg>âJ$įΝįŅ'ŸÉ5oZZyēVšwܕ lckCrJJŽã‰W“�°ŗŗĪq|뛝b4d/vΘ5‡ÔÔTzŪ×­P5Ŧ߸‰ÄÄDžáīž‘‘ÁÆÍ›éq_7Ë̓ʗãŲ§ŸĀ`4âå剝mūŪĻŧÎu#ŗŲ”ëØ?Ŧô+ “ÉDfÆß!ŅÖ.˙?†<Ü h4xⱇš¯[ö8ČîŊ{Y¸x cF r`āíį¸ÅJ~åĀ@>ú`<GŽåĀÁC|˙ãOTĢZ…áĪ?›Ģí¯ŋO'ô|cF¤TÉ�<7|džį2 ˜ĖÖˇāØÛŲQĢf ^õÂm¯GDDDūîJPö/]šã'Næ:~äČ1ėėė(íį›ãxŠ’%-7O=úЃŧöÖ;ŦXš†.s߸–§Îœ%,ė¯Ŋ<¯nÔKIIaė{pčđa‚ęd¯BÚŲŲáë[ęfCŲ\vļvdddZÎGĮÄxÎÛqwwÃŅÁ‘ á (€Édæōå‚˙q3㝐••ErJ žžĨčÔą=:ļgŌįSŲ˛õ¯<å[‰OHĀÍՍēÁAÔ ĸe‹Æž÷ņņ šÚž<}š˙cīžŖŖĒÚ˙īŋ§OzH#ԀTA)*HGEą Ŏ‚DQTTôÖۊ€b/€+‚ŌoĨØčJz›dZfæųcp dHõûü>¯ĩ˛Væ”}]įœÍâʞ}Î9§WĪP‘|čP%%å˙€;žē)ÉĄiüãįŗ/žĸw¯¤¤¤°qĶøũūĐĶMŠ‹K0ŠÖTų÷ø[æ(_>h ;wîb֜šėŲģôôtžZēŒ…‹>æĘÁƒBĪ''..–C¯fáĸŤīß_f]~~™e~ÜaĻ"Ŧ\ššÖ­ZвE ’“C?5¤CûvŦXUõí*SÕXuë&ķûŽøũü~?_.]^ĻĢÕJVv6EžZŪÄĐĩKg>˙b)™YŲÁīķ/).)ĄķYgTš ĢՁƒ).)ŠVėÕk˙ĮãO>Íūđûdeį•Sf*IMøũ{â)>ũü \.7^¯—ßwî"22‚čč(lVEŽ"rķōp{<$&&˛{Ī|>bÄĮœŸ_PĨxŊzõ`ÆMlūi+E/ų”+×Eˇ.gãvģųhņ'¸\. ō xåĩ7x÷ũjuŒ"""ōĪų[F”ĶŌ3aü8>ūä3^xée<žRęĨÖåúkGĐĩKįJ÷īÚĨ3ßmØČkožÍ¤‰BË_ërێŋīž2Īī-q–đũ†\f˛Ē)�� �IDATŨđ°mŸĶĢ/LöéÕUXƒ\ÂĖ7Ūæž ‰eā€KX˙ũJ}Á)]ģtfú̝q˙„I<ųÄ#ĩÎíęĢ.gÎŧųĪ3Īát9iܨ!îģ‡:ņņUnãœ^=ųxÉgü´u“'MŦō~Ŋzt';;›gŸŸJ‘ŖˆØ˜8:u:ƒžįõŠÉĄ„îŧíVfĪ›Į§_|…7`Ėˇa2™8­ÍŠÄDĮ0ūIÜrķ š|0oŧũˇŪuõSërŨ5ÃŲ˛u}ü1öĘįŸŅĄ=C‡\Éûŗæā(v–Ö˜ąwŨ†ÅbÁbą0æŽÛ™ˇāCžúz9th:C¯ēĸVĮ("""˙C pœI—Įąyû¤Ön$PDDDDädÚ(‹Žm[ÔhßQŖFQŋ~ũ˙ãáDDDDDū *”EDDDDÂPĄ,""""† e‘0T(‹ˆˆˆˆ„ĄBYDDDD$ Ę"""""a¨P C…˛ˆˆˆˆHÕ.”mV+.ˇûdä"""""Rk.ˇ›ÕZëvĒ](×KI$/ŋ—ÛSëā"""""'’Ëí!/ŋˆz)‰ĩnË\ŨęÄEc0¤r #‡œŧ‚Z' """"rĸØŦVŌĻ]ëļĒ](ÄĮFŸā"""""˙Vē™ODDDD$ Ę"""""a¨P C…˛ˆˆˆˆH*”EDDDD¨QĄ\đä—ŋˇžÂmrGĪ&āô†]įųnE3VÖ$tĩŊü-Ž™ĢÂŽķg;Čēėüy%�¸WūNÖ WČ8˙%o¯;éšũĨĸ¸Î/ļ“7vÁI‹}"ŽCŅ´dœû|™eŧDæ%ĶÉüÉē|fh]ĀéĨđšed]>“ĖĶqĖ\ ŖrÚ¸œ‘ī“ŲŲמƒ{ÅīĮí^Ŋ“Ü[įÔ*˙ce˜Ž{õÎ ˇ9ļīĢĸ~W]W)šŖf‡]—uÕëxˇė?!qDDD¤ŧ=ŽR(Ũ›sÜÕŪŨŲ'%ėŅ|éų¸žũ¤÷nģŪņÆ"ŸąN$�ļŪ-IîŨ’Âįž>éš퟊ ĩŋĨŋÂũŨžcúÁ]Jōâ[1D–#Žã˙áKĪ#i֍nrĮĖܖ€Ŋ_[….ō'}BüäX;7Áŗay‘Ōą†¸ˆ˛ ųüÍ\E܄~ĩ:†c%<{Ļúqnslß)“V%ũŽēJ÷fđ*ßPDDDN¸Ęū'y÷, tw6φuˆl ƄHüųNrn|—€ĶKöđ7Á`Āvvbīŋ�ĮĖՔ,Ú €kų¯�Ôyî ĖM‚oOÉē|&QÃ;ã\˛~ ļ-ˆĶLGŋ‹gãíuÔ]6æ¸ųÍ\EԈŗ1DÛĘ­+Ũ™…û‡?IÛˇĘĮ[˛čĮ`Ūū�ĻähbĮŠaāšČrP8õŧÛöƒÅLÄmˆžŠ;‚û:ŪX‹kŲ/ŧ>,­S‰{đĸ°EdX(|nîuģ0ØĖÄ=ĐËé ‚q3 )x~žô|0‰č׆¨aCģz6ėĨčÕUŧ>Ā@ôĩ]°÷mĖŠ’ë�āŨ~ÜÛæ0c–ļõĘæåķSøârbī<—ŧņ…ûn0{|žī÷}sO  "/ëˆsų¯ÁBŲ]JĖmŊąvn€ĩSŗ _fæc å’Å?anš„å´úĄeî•ŋSôæZN/Ƅ(âÆ_ˆų”$� ˙SJ4۟QúG.æĻ‰ÄMę*vķ^‚wûüy%ÄO€­gķ°ųWÖwÂõģŠŽÖU¯˙ĐÅXÚ¯iūÄÅXĪJ#rpGÜkwQđŸ/ {BŖōŅ7v#ĸ˙éGŽŅ¯‡(|i9ž,öާ;Ą_¨ß‰ˆˆHíÔ¸Pv¯ŲI‡`J‰!īE”|ôŅ#{`Œ éŊČė?¤Ų7aˆ°”Ų/zTOžR0‰š­wų†MFÜkw‘8s8¯Ÿœ[ŪĮĩrö>­B›"­˜’˙ÂīÖũ”îÉ&ūŅKÂŽ/šą‚˜›ē­j‡īųáŠßû‰¯_ƒ1)šâ9ߓ?ų3_@ūŖK°žŅ˜øÉ8ÜäÜņĻúqD\|ž {q~Y7b°™É;Ÿ’65âėĒÅŪz€øĢÎ"v\_Šg}GŅĢĢH˜>4÷‰ĪąvhD§˙@ų>æ&IØē@áŗ_˙ÄĨ˜[¤āĪ-Ļhú ėįĩCŽ`°š0&Ec°šĘ­+žŗ[æ˜%”Yp¸Āl$âbŧÛ`LŽ!úĻîØēžrd#Ÿ?ôĢ1֎/=/ø{r4ÚÛq•â\ŧSŊ8ĖM“މáĻxÖw$ŧ|õ‘e%ō˙œÄiWcnŠcæ*ŠfŦ ÎŗW70q~ų3‰3‡cLŽĄāąOqŧĩŽØqÁ‚7ūą�äÜ<ĢÂëQQß ×ījsũmŨ›w˙…8Ū[ękĮroÚGÂôaā-%ûšˇņlڇõŦ´JÛ‘ĘÕøf>[Ī˜ęłɀĩcŖā¨æ Ņ˙t°š1DYąum†į§ô2ë#/ë@Ō#ÃīˍŨĢĖ(ô_Üë÷ā/taŋ m•ķq¯ÚŊīŠ“ĸĮīHéŽ ü9ÅøsŠņn;@Ô՝ĀhĀk'qÆ0"ÜZĪL IQV0ąļk* Ģœ–€­KĶ`[gĨáÛ<΁'Ū-ûƒqc|}[ã^ˇ+´¯!ԊķËíøöįGX'õ¯ÖhŖšE É Gan‘Rfš/=÷ڝD íT~'Ŗ{ˇSˆ֙än%úēŽä?˛ßÁāëÎm]šRŧ`‡n1%Ÿn%ā+;ĩĀųņOd^øÅ 77ūB0—ŊŽŽ÷ŋÃŪ§φņeŽ5eņ­˜[§ĪÕiåÎŗ­GsŒ)1`�{ßSËõĢĘTØwŽĶīj{ũ+9°=ģCŒsĢēĄū!"""ĩWãecŦ=ôģÁl$pÔ(am™ĸĘÄŠhžķą\ËĢ[0_ûÍXIė˜ķĒU0úrŠąœšúlˆ°`°[‚7s�ŗ1Xũĩū¨¯ŨN/E¯ŽÄûË! &#žlÖ3Ģ>âgŒ9ę<›Žœg_N1XMeâëDRúį‘",~Ę Šg­'÷Žš"-D_×ûmĒ~āá‚SAbĮœöSũ8âúlëŲk›zx6î#b@;ĸŽë†ę7d_÷Ƥhė=›ã^ģĢL—ļ'ĸ_[Üëw“{ßB§]ŠqpäÚw°×Ōí$Ŋ{Ėā�”|ōŽ•;‚ĮtISâQũ*ÆN ČUõ㎤ī¯ßÕöúWæxũCDDDjīäÜĖWKūBįQŋģĘåōøpŧąšøŖ ĩŖ9?Ũ‚š~Ö3U+Sbūü#9œ^./ĻäčāVĨ~w¨@öį`LŒÂņÖZ|™E$ÎVŽWWáË ˙´„ęæ„ĮG ÄšīęĪ/)3%ÅT?.47ÜŗųOōX„ĩ}CŒuckןU„wG&y/ .đųÁ ëĒ×Iœ:L|EeæŧžĐô ƒŨLė¸ķÁŦ6‹ßũæÃEpéÎ,Jwebŋ°-ØĖØzˇÄōņO¸7î#ōđ6Ž™Ģˆēē†cú„{íNJn&ņõĸđŦßCÁķËĘæ^pTŋ*ĒFŋĸ’žSAŋĢėúŒQ{ǜ“ˆˆˆœ\'å9Ę› ā;pxš€Ã]v}„5øU|�đĘ=FÎųÕĪĄåîuģ°v,[œ”,ú‘ėĄo”‹[üáXÚÖ}ũ~´@‰Į{뉹ĩWĩĮÖĢ%Žåŋ†V˛`ÖöÁ'1ëDbiאâyCĮš7nžMû€`Ņli– VūÜbÜëwpÖž2ÄE`iא’…?ãä–ā\ú ļŪ-ƒyšČ;?tî--R‚ķōG†C+ģĨ;2Éē|&Ĩ;2Cˌ)1¤|z;Éķo&yūÍ$L FÉķoƘ‹?ˇ„ŧû†öq¯ŲIéîlŦš�āxs-O|>?Ĩ{r(Yüch^2@á Ëņn;Œŋ+ īoXNIĀûķAŧŋ"rpĮrįßSŒŠn Ƅ(đúp~ũKšķė^ˇ‹ĀábŲĩė—rũęx*ë;õģĘŽŋ1%†Ō}š@p´ÜûÛĄ2û"­øs‹ ¸JƒšķoIDDDNž“3ĸl15âlrīœ fĻÔX_;r3RDŋ6ä?ô1™ŋŒ!ĘJˍ^ØĪ?õHR͓É=^1ļnͰõhQĻų@‰_–Ŗė˛'%ķ7’øęđ°)Īū[ˇf˜ŌË­+Ũ“CŪØų�ø‹=ŒÁy˛ĻÆ $L‚õŒFD ë<Āܰq]Ú?ū‘ūüw)Y—Ŋ‚Áf!ĸ_ÛЇČ!gQ0ås<?ĻcĒGĖčŪä?ū%ķ6bíܴ¸•‰đ" ž]Šķ̎0˜ŒD_×%TübėØē7'gôl01!úĻ“cĒ|ūl¯Ō\ūbnU—˜ÛĪ%˙‘%ø œÁ'ĸ<9(XĀQCÎ"˙ąĪČ8C„…č‘=CŖĪææÉÄŪwĪ,şãĀk'úÆîX:4 hú ĸoé –ō7Úû´Âųõ/äŒ|c|$Ņ×wÅģm?ų~Lü”K°vjBū#K(ŨŸšQā4 ĀõÍoMũ&x üįK Ī™°ŸĶ’˜1įUØw*ëw]˙Č!g}}W _XŽëÛß0ĨÆįŖ5eÛzzĖ͒É4ƒŨ‚ũÜVÄÜŨ§Ę×CDDDjÎūUi=öqY"'BÁ“_bnOÔĩ]ūéTDDDä_nÔ¨QÔ¯__¯° G…˛ˆˆˆˆH˙ēŠ"""""˙$MŊŠ€ e‘0T(‹ˆˆˆˆ„ĄBYDDDD$ Ę"""""a¨P C…˛ˆˆˆˆH*”EDDDDÂPĄ,""""† e‘0T(‹ˆˆˆˆ„ĄBYDDDD$ Ę"""""a¨P C…˛ˆˆˆˆH*”EDDDDÂPĄ,""""†š&;å:8™ƒÛí9ŅųˆˆˆˆˆÔ˜ÍfĨ~J"ņąŅĩnĢڅr~ĄƒŊé‡Hˆ!ŠN\­9Q\n7{ĶҤaj­‹åjOŊ8˜‘CB| v›­VEDDDDN4ģÍFB| 3rjŨVĩ å@ €ËãQ‘,""""˙Zv› —ĮC ¨U;Õ*” C­‚‰ˆˆˆˆü]j[ģęŠ"""""a¨P C…˛ˆˆˆˆH*”EDDDDÂPĄ,""""† åĮËõ#Gŗk×îŋ%ŪŽ;šwƒÜr۝'Ĩũ_~ûëGŽ>îúÍ?måæŅwTģ]—Ëõˇž'ųS^a]]cīĀEžĪįŸWfyA~wß;ž‰ãĮҞE 6lÜÄôW_/ŗÅb!%%…ķÎíEŸszPXäāŽą÷rß=wĶļÍŠÕĘå›+yoÖ ŧäbXfŨûŗ?`ųˇ+CŸF‰‰‰œŲą#ƒöĮnˇpĮ˜q 40”ĪŅî3‡Ŗø¸ņŸ<‰F T+į“eé˛ohPŋ?úĐ ksÅĘÕt9ģSč\U¤yŗĻŒ{wĩcX­VÆßwõë×ĢIŠe:”ÁÁŒL:ļ?ŊÖm‰ˆˆČ˙ŋü-…ru=ûôŒÆāsīÜn[ļũĖŦ9sąYmtīÖĨVm¯\ĩ†ŗ;wbõšu x FcŲAõĻMŌ¸ķöQ�”–úIOOįũŲs)))æÆë¯­´ũÉ?ˆß|¸õ§ŸÁžŊ{šķļ[CëëÄĮ×*˙Џ¤„ĻiiDØ#NH{ŪŌRæĖ›OĮöíĒT(ĮDGĶēU‹jĮ1œÚĒeMR,įû›ČÍÍSĄ,"""åü+ å:ņņ˜LĻĐįzŠŠėŪŊ‡ 7ÕĒPŪŊg/dÜØģ™8é~ÚēŽíÛ•ŲÆl6“P'!ô9%9‰‚ÂBæÍ˙ˆŽģĻŌW'&Ų7ÂnĮl˛œ”Z–—ŸĪėæņûŽ]�4IkĖĐ!WP/5ĩ\[N§“'ūķ,­[6įšáCÉÉÍeöķŲĩ{7.—›V-[0bØÕ¤$'áöxuÛ]Ü>úf–ģ’üü¸nÄ0ڜÚē\ÛO>ũ,;wíâˇßwđõˇ+xmúKæævģuûŨÜxũĩ,\ô1={tãŠË. ĩįõzšãîqx<^îŸ8‰Kú_DķæÍ0l˙ųŪ5‡‚üš4iĖ-#o$1!Í?meÆ+3yũÕiø|>æÎ˙ī7nĸ¤ÄIJR\ĖŲ;•ËŨår1úŽ1Lzā~š5;…ĮĻü‡3:vāĪôũ¤§ī§¤¤„ ĪīKŋ ûđûŽĖŋôą˜MœÖļ-×ĘŌeËYōŲ�Ŧûî{^›ūG1ŗæĖeû¯ŋâv{hܰ!#†ĄI“Æ�•Ær{<Ė›˙!ßmØ8í´ļŒ6„˜čāģæŋ]šŠeËW“CLL4ô= úöаO‰ˆˆČ?ã˙ĖeŖŅ@ŠĪWĢ6VŦZM‡퉍‰ĻKįNŦ\šēJûY-VüÔúō+˜Lfžž2™§§LÆfŗņŌ˯”{ÅĸĪįcú̝“Z7…áC¯&đâË3ˆŽŽâé)ņŌsĪË̝Ŋy8G �_/˙–;oÍĻLĻĶ™™5g^Ø<&Žŋ—V­ZŅīüžŧ6ũĨJsŗXŦ�ŦYģŽûīÃ%õ+ĶžÅbá‘I�đĖ“3 ˙E@đ8ĢV¯eÂ}÷đԔÉ—đŲį_–Ëgõšuü´u?8™Ķ_âĘ+.ãÍwŪŖ ŋ Ōsją˜ųzų7 ŧä"Ļ<ö07Ũp-ķ.$??€¯žIįŗÎdÆKĪķÔã“q:,ūd —]:€NgžA¯ŨCįāƒų ČĖĘfĘäG˜öÂŗ4IkČ×^¯rŦų ˛kĪ^&=0ž'{„ĸĸ"Ū}6�7mæÃ…‹šáēáŧ:íEnŊų&-^ÂÖmÛ+=Fųûũë eŸĪĮ–­ÛØôÃf:ŅąÆí8NÖŋ‘‡G¤ģwëʖmÛČÍËĢpŋĖĖ,žZļŒíNĢõk÷ėŨĮž}ûråeDDDÁ•ƒq(#ƒũû÷—ŲöŊŲsņx<Œy#FŖ]{ö’žžŸĢ¯ēģŨŽŨnãĘË/c÷ž=8x0”[¯žŨ‰ŠŒāÔÖ­9”q(4¤6šũ5Ļs§ŗhP¯vģ­JĮėķųté%$ÔŠCB:th:,ˇ]ąŗŗÉLTd$FŖ‘íÛņę´ŠÄÅĮUÃ`0púimiPŋ>�­[ĩÂīp(#ŋ?€Ķå$"2ŗŲDll cîŧáC‡„mëšaC7öNbcĸąZ-tī֕ĖĖ,ŠKJĒËĪÚußqqŋ HM­K\|׍FįŗÎ`åę5ôčŅæÍša0hÖėēvéĚĩ˙ĢŌš‘ŋ×ŋręÅ­wŨúŊÔë%*2’ūõŖwīž5nsŨúÛ9ũ´ļ@p.rũzõXŊz—ėÚnįŽŨÜr{đŗ€Ī‡Īī§CûvŒzuc˙%++ģÍ^vjGJ2fŗ‰Ŧœ\RRęđÅŌ¯Ų¸i3O=ņ(Vkp¤833“@ ĀmwŪSŽŨėėę× ŪØ–pÔԋŌߠ´´4ÔNMskذ!�uS’Ģ}Üu€ÕjÃã)-ˇMĪnŨظņƌ›@›6­iwúitéÜŠĘųŅS^ĖfFŖĮƒŅh`ȕƒ™5g_/û†ĶÚļĨKįNĄŠĮĘĪĪgÁG‹ŲŊ{Ĩ~ŋ€R"+ŽUTX„Ëí"%ųČ9Ē[7…ēuS�ČČĖdëļí|ĩtY™˜§4mRĨc‘ŋ×ßR(›Ė&JœÎrˋŠ�Ą¯õ˙2yŌ ÁÁîš âršxÉÅĩĘaÅĒÕqûŨG M¯×ËĒ5kpÉÅĄĶ´Æ¸õ–‘�ŒFęԉĮb>š§)ūAđ—Ņĸy3æÍ_Șģn‚SĖfoŧ:ŊÂļj;ę]YnfKÅw8ÛŠÄÆÆđđƒØŊ{?mŨÆ_-åĶĪŋ`ō¤‰ŠŠŦ<H!úœĶ›ŗÎ<“­[ˇņ㖭<ūÔĶ šbpš§°øũ~ž}q*-š5ã‰ÉÉĻķđä'Ēëđš÷üaW[,V.ŋėŌĐ´ųwû[Ļ^Ô¯Wß~ßQnųĪ?˙ŠÅbĄ^jŨ2ËS’“C#q×_3œ?ūü“¯–.¯qüģ÷đįŸéL¸o,=2)ôķđƒ_PČļíGæˆZ,–Pė”ä¤Z$§$'ãrģČÎÉ -;t(ŸĪGŨēGÎÁ ×gô-7ņûΝĄĮÕĨϤPZę+3mÁī”iëīČídqšÜ¸Ũnš5;…Áƒōø#“pš\eŽMMM÷n]¸}ôÍ\1øRž 3?=ŋ ŸėėÎīÛ'TœīŪŊ§Ęqbcc°Ûė<x(´ėāĄCĄëĻ$“ž^vŠMn^ĨĨĩ›{/"""'ĮßR(_>h ;wîb֜šėŲģôôtžZēŒ…‹>æĘÁƒ*|”X\\,#†^ÍÂE‹I?fo~~™e~ÜOš6VŽ\MëV-hŲĸÉI‰ĄŸÆŌĄ};VŦĒÚM}ĩ•–Öˆõę1˙Ïpēœ8ÅĖûđ#ŌŌ͏QÃĐvFŖ‘Ä„Ž>”y >äĀÁƒ4jԐ-š1gî ō p{<,\´˜§ž~_-orŦNnąZƒß üš?NWųo*ōîûŗyí͡)((Äī°oß>Ün)))Õ>–Ŗ:”ÁŊdËÖmø|>ŠKJØˇīĪĐĢÕJVv6E‘‘ØŦVvî܅ĪįcûĪŋđÏ?W…› ŊzuãĶĪžäĪ?ĶÉÍ >Ĩdß�СĪ9lÚŧ™ ›~ ´ÔĮĻ3åŠgXŗv]­ŽQDDDNŽŋeęEZZc&ŒĮĮŸ|Æ /ŊŒĮSJŊÔē\íēvé\éū]ģtæģ yí͡™4qBhųëoŊSnÛņ÷ŨSæģ%Ξ߰‘ë¯ļíszõā…ŠĶCO-8™ wßyŗ>˜Įũ&a09ĩu+îš+üÛéēžŨ™ÚĘĖ×ß⡉ã}ķM˚3û|“ÉČ)M›rĪØģĘ<JīīĘ-œÄ„:´;_žNŸs{ĶąC‡*ī;lčUŧ?û&Nz×KrR#† Ąi“´šNHjj]nŧ~sį/$++ ģŨNĢV-¸öša@°oMõ5îŸ0‰'Ÿx„k¯ƂŗčãOiÕĒ#o¸Ž×Ū|›˙ü÷yzāŪJã]yų`ü>?Oũ÷9�NoۖáÂķÛۜښáC‡°`á"^{ũ-ââãčsnoΊÅÜ{9y cŸKV‰ÍÛwĐ ĩú7t‰ˆˆˆˆü]öĘĸcÛęŋØ `Ô¨QÔ¯_˙ß˙x8‘‚ e‘0T(‹ˆˆˆˆ„ĄBYDDDD$ Ę"""""a¨P C…˛ˆˆˆˆH*”EDDDDÂPĄ,""""Fĩ e›ÕŠËí>šˆˆˆˆˆÔšËíÆfĩÖējĘõRÉË/ÂåöÔ:¸ˆˆˆˆČ‰är{ČË/ĸ^Jb­Û2Ww‡:qŅ ŠČČ!'¯ Ö ˆˆˆˆˆœ(6Ģ•´†ŠÄĮF×ē­jĘ�ņąŅ'$¸ˆˆˆˆČŋ•næ C…˛ˆˆˆˆH*”EDDDDÂPĄ,""""† e‘0jT(<ų%Åī­¯p›ÜŅŗ 8Ŋa×yžÛCҌ•5 ]-E/‹cæĒ°ëüŲ˛.{^ �î•ŋ“5č2Î ĮÛëNznŠ(Žķ‹íä]pŌbŸˆëP4mį>_fYÆ/‘yÉt2˛.ŸZpz)|nY—Ī$sĀt3WC⍜6î#gäûdöŸFöĩīā^ņûqcģWī$÷Ö9ĩĘ˙X™Ļã^ŊŗÂmŽí;Į:ļß\Ĩ䎚]í\7ŊŸ;îŋ#9šjôx¸J toÎqW{wgŸ”°GķĨįãúö7’Ūģ!ėzĮkˆ|Æ:‘�Øzˇ$šwK Ÿûú¤įv´*.Ôū:”ūz÷w{ŽYčw)ɋoÅYū8Žwū‡/=¤Y7p¸É3sZö~m ēȟô ņ“`íÜΆ}äM\DJĮFâ"Ę6äķS4sqúÕꎕđė˜ęĮU¸Íą}§LZaú]éŪlū@šmEDDä߭ƅ˛ŋĀIŪ= (ŨŠaâˆ1!ž“œß%āô’=üM0°Ũ„Øû/Ā1s5%‹6āZū+�užģs“āÛS˛.ŸIÔđÎ8—lÁŸ_‚­G bĮôĶ‘ÁīâŲßãx{u—9n~E3W5âl ŅļrëJwfáūáO’Įö­ōņ–,ú1˜ˇ?€)9šØqįcjX'x.˛Nũīļũ`1qAĸoę†āžŽ7ÖâZö ¯KëTâŧ(l– Ÿ[†{Ũ. 63qôÃrzƒ`܌B ž_†/=LF"úĩ!jXįĐŽž {)zu¯0}mė}[sĒä:�xˇ$÷ļ9$ˆĨmŊ˛yųüž¸œØ;Ī%oüGĄÅ~‡LÆãŸįû=DßÜC„C„…ČË:â\ūk°Pv—s[oŦ›�`픆Ál—Y„ų˜BšdņO˜›&a9­~h™{åīŊš–€Ķ‹1!Џņb>% €‚Į?Į”wŠ�� �IDAT÷įC”ū‘‹ši"q“ú‡ŠŨü‡—āŨ~�^ ņ“`ëŲ<lū•õcû{í. ūķ%bOhd=úÆnDô?=xž*š†G+~˙;Üëw“đ•`5ãĪ,ĸđ…e”ū‘ ž�Q#Î&â’`ģÎOˇâūnĻÄ(J˙ČŗYDė¸ķąvlļm)¯Æ…˛{ÍNęŧ8SJ y,ĸäŖˆŲc|IīŨ@f˙i$Íž C„ĨĖ~ŅŖzđ”‚ÉHĖmŊË7l2â^ģ‹Ä™Ã xũäÜō>Ž•;°÷iÚÄiŔ|üžxˇî§tO6ņ^v}ҌÄÜÔ lU;|ĪPüŪ˙H|ũŒIŅĪųžüɟ‘øú�ō]‚õŒÆÄO@Āá&įŽ0Տ#ââĶđlØ‹ķŗ­$ÍēƒÍLîØų”|´™¨gW-öÖÄ_uąãúR<ë;Š^]EÂôĄÁ¸O|ŽĩC#ę<=8øĘČ÷17IÂÖí� Ÿũšø'.ÅÜ"n1EĶW`?¯5Ēp�ƒÕ„1)ƒÕTn]ņœ Øz4ĮÔ(ĄĖō€Ãf#ųãŨ~�cr Ņ7uĮÖõ”#ųüĄ_ąv|éyÁߓŖ‰Đ.؎Ģįâ͘ęÅanštL 7Åŗž#áåĢ,+ņ˙øį$NģsëT3WQ4cužŊ"¸Éˆķ˟Iœ9cr }Šã­uĎ ŧņ � įæY^ŠúN¸~gëی¸û/ÄņŪúP9Ze×đ/ŽoÃųõ/$NģŦÁØSžĀŌĻņO]†?ˇ˜œ›ŪĮÜ,ËŠŠÁGëw“8sæS’p.Ų‚ãĩÕ$ŧ2ŦÂã‘#j|3Ÿ­g LõbÁdĀÚąQpDė‰č:XÍĸŦØē6ÃķSz™õ‘—u 郑áwįÍÆŒîUfú/îõ{đē°_ĐļĘų¸WíĀŪ÷TŒI҇ãw¤tGūœbü9Åxˇ ęęN`4`ˆĩ“8c‡Gn­gĻ‹ä(+˜XÛ5 †UaNKĀÖĨi°­ŗŌđížį@ī–ũÁ¸€1>‚ˆž­q¯ÛÚ×iÅųåv|ûķƒ#Ŧ“ú‡FšĢģE É Gan‘Rfš/=÷ڝD íT~'Ŗ{ˇSˆ֙än%úēŽä?˛ßÁāëÎm]šRŧ`‡n1%Ÿn%ā+;-ÁųņOd^øÅ 77ūB0—ŊŽŽ÷ŋÃŪ§φņeŽ5eņ­˜[§ĪÕiåÎŗ­GsŒ)1`�{ßSËõĢĘTØw*éwáTåxˇĀ1s5 Ī ÆkŸSŒįĮ?‰ēúŦāž QØĪkëÛßBû™OI¨[ZÖÅwāÄũųAG”‡˙Ã0˜Ž%Ŧ-SBT™8Íw>–kų¯`5cëæĢs_€ĸ+‰s^ĩ F_Nqp”î0C„ƒŨŧ™Ë�˜ÁBø¯õGM÷8ŊŊēī/‡0˜Œø˛XĪLĢrlcĖQįŲtä<ûrŠÁj*×X'’Ō?‡ņSQ<k=šwĖÅi!úēŽØ/hSõ'œ ;æŧ°ĄŠ~q}ļõlŽĩM=<÷1 Q×uÃ?õ˛¯{cR4öžÍq¯-[F\ڞˆ~mq¯ßMî} Iœv5ĻÆÁ‘kßÁB\Kˇ“ôî1sĪPōÉO¸Vî~tzĶ%M‰GõĢ;"WÕģ’žSaŋ;^“U¸†�Ī|…Áb*3O;āæž}ô¸ˇ´ĖČŊņčé/FƒæI‹ˆˆTĶÉš™¯–ü…ÎŖ~w•)Ę+äņáxc5ņGjGs~ēsũ8ŦgTožĻ)1 ū‘œN/—Srt°ø(õp¸C˛?§�cbގÖâË,"qÆ0°špŧē _nø§%T7'<>%žĐ|`~I™))ĻúqĄšážÍ’˙Ā"ŦíbŦ[ã¸ūŦ"ŧ;2É{xIpĪū�YWŊNâÔ!`2āË(*3w8āõ…ĻoėfbĮĻ`ĩYüî˙0.‚KwfQē+û…mÁfÆÖģ%–ÂŊq‘‡ˇqĖ\EÔ՝B#Ģq¯ŨIÉÂÍ$ž>cBžõ{(x~YŲÜ ŽęWEÕčWTŌw*éwĮS•kđü•8Ū[OŅÔoˆŧžÆä�’ŪŊžÜô&91NĘs” 63}Õp¸ËŽ°ŋŠ�ž@šĮ_9ŋú9´ÜŊnWšJũHöĐ7ĘÅ-ūđ,m뇾~?Z ÄƒãŊõÄÜÚĢÚĮcëÕ×ō_C+Y° kûā“Œu"ą´kHņŧĄc͡�ĪĻ}@°hļ4KĢ n1îõģ 8=ÕÎáX†¸,íR˛đ‡`œÜœKÁÖģe0"ycį‡ÎŊĨEJ° 2­ė:”îČ$ëō™”îČ -3ĻĐōéí$Īŋ™äų7“0m( $Īŋcj,ūÜōî_ÚĮŊf'Ĩģŗąvj€ãÍĩ<ņøü”îÉĄdņĄyÉ�…/,Įģí@0ūŽ,ŧŋe`9%�īĪņūzˆČÁ˝N1Ļē1ĸĀëÃųõ/åÎŗ{Ũ.‡‹eײ_Ē|c[e}§ĸ~Ái!ūÜbŽŌ`{‡¯Ie×đ/Ƥhbīîƒ{ã>Ü+ĩi=+â67ōø(šú Ū-ûĢtL"""Rš“3ĸl15âlrīœ fĻÔX_;r#SDŋ6ä?ô1™ŋŒ!ĘJˍ^ØĪ?õHR͓É=^1ļnͰõhQĻų@‰_–Ŗė˛'%ķ7’øęđ°)Īū[ˇf˜ŌË­+Ũ“CŪØų�ø‹=ŒÁy˛ĻÆ $L‚õŒFD ë<Āܰq]Ú?ū‘ūüw)Y—Ŋ‚Áf!ĸ_ÛЇČ!gQ0ås<?ĻcĒGĖčŪä?ū%ķ6bíܴ¸•‰đ" ž]Šķ̎0˜ŒD_×%TübėØē7'gôl01!úĻĄ‘ČĒ\‡€Į‡?ÛAĀãĢ4—ŋ˜[Õ%æösÉd ūgđ‰(O °@ԐŗČė32ÎĀa!zdĪĐčŗšy2ą÷]@Á3Kņį80ÆÚ‰žą;– (šž‚č[z‚Ĩüͅö>­p~ũ 9#ßĮIôõ]ņnÛOūƒ?åR�Ŧš˙ČJ÷įcnT'8p}ķESŋ ^‡B'˙ųÃs&ėį´$fĖyöĘú€õô˜›%“5hģûš­ˆšģPņ5<š!ÚFÜũČôS’ÚÔĮ˜M܄~>˙5ŲWŋFļ.Mą´ŠWn_ŠC øWM\ĖēęuâēKģ˙t*ō˙#O~‰ša<Q×vų§S‘šQŖFQŋ~}ŊÂZDDDD$Ę"""""aüëĻ^ˆˆˆˆˆü“4õBDDDD¤*”EDDDDÂPĄ,""""† e‘0T(‹ˆˆˆˆ„ĄBYDDDD$ Ę"""""a¨P C…˛ˆˆˆˆH*”EDDDDÂPĄ,""""† e‘0T(‹ˆˆˆˆ„ĄBYDDDD$ Ę"""""a¨P C…˛ˆˆˆˆHæšė”_čā@fnˇįDį#""""Rc6›•ú)‰ÄĮF×ē­jĘų…öĻ"!>†¤:qĩN@DDDDäDqšŨėM?D“†Šĩ.–Ģ=õâ`F ņ1ØmļZ9Ņė6 ņ1ĖČŠu[Õ*”�.GE˛ˆˆˆˆükŲm6\@ VíTĢP6 ĩ &""""ōwŠmíǧ^ˆˆˆˆˆ„ĄBYDDDD$ Ę"""""a¨P C…˛ˆˆˆˆH*”˙Ųąc'÷Nx[nģ—ËÅõ#Gŗk×n�›ō4Ÿ~ūeÛžųú[ŒēíŽZĩQ‘ëGŽæ—ß~ģîØcŠŽšķ?äŲĻÖ6=‘rjô ëę{ī.ēđ|.8˙ŧ2Ë ō ¸ûŪņL?Ž–-Z°aã&Ļŋúz™m, )))œwn/úœĶ€Â"wŊ—ûļmN­V.ßŦXÉ{ŗ>`ā%3xĐĀ2ë۟ũËŋ]úl4HLLäĖŽ4°?vģ€;ƌc𠁥|ŽöWnG‹ŒŒ aƒ 4Ö­ZV+ßŖ-]ö ę×ãņGÂjĩ2ūž{¨_ŋ^ÛûKúūũüīģīypüŊ4iÚ¤ÖílÛū3qņq4jĐ Ōmks,}ÎéÛíŽIŠåŦXšš.gw ]gųÛßR(W×ŗOOÁh >÷Îíö°eÛĪ˚3›ÕF÷n]jÕöĘUk8ģs'V¯YĮ —`4–ToÚ$;o@iПôôt۟=—’’bnŧūÚ*Į}ķM´lŲ �‡Ŗ„åߎäš_æņG"5ĩnr/.)ĄiZö�N­EŅ}4g‰€´´4,æĶ%>ûr)Ŋ{t¯RĄl4k|,))É5ÚīXŪŌRæĖ›OĮöíT(‹ˆˆđ/-”ëÄĮc2™BŸëĨϞ{÷6lÜTĢBy÷žŊ8pqcīfâ¤Gøië6:ļoWfŗŲLB„Đį”ä$ ™7˙#n¸îš*?¸:&&:ÔNBŽŋf?ū´…ÚBŋÔķ™üÄSœŅĄ7oÆfĩ2qüŊäåį3ûƒyüžc�MŌ3tČÔKMåɧŸeįŽ]üöûžūvSŸ{šŅwŒaŌ÷ĶŦŲ)åâģr˖¯ ;'‡˜˜h.č{ôíSnģ-[ˇ1학�Ü1ö^\܏~ôeáĸOØøÃ”””Z7•—\D‡Ãį*\îG{úŋĪķËoŋŗsĮNÖūo=ãÆÜ @~~Sžū/{÷ūARb"׌J›Ö­pš\eŽå÷;˜;!éb1›8­m[Ž>”¨¨ČrųĪ˙!éûpīØģ˜˙áGdeįP/ĩ.?mŲJ~A!mZˇbä×a2™ČÍËãŊY°cĮNJ}>Ō7bÄ°ĢŠ—Z—;î‡Įãåū‰“¸¤˙E čßmØČ’O?'++›ˆˆHēw=›+.„Á``ËÖmŧņÖ;\3b_|𔂂5ōF’øų—_™ģāCĖ 9)‰KöįėNgđëo;X¸h1ûĀd4q֙g0tȕX­–*õ/9ųūĪĖQ6 ”ú|ĩjcÅĒÕtčĐžØ˜hētîÄʕĢĢ´ŸÕbÅOí^h0°XĖø~ 8Ĩdåš5\>h cîē €Š/ŋ‚Édæé)“yzĘdl6/Ŋü @€‰ãīĨUĢVô;ŋ/¯MŠÂX7mæÃ…‹šáēáŧ:íEnŊų&-^ÂÖmÛËmÛîô͏wė]�L{áYôŋˆ}ĖÖí?s˙=c˜úüŗt>ë ĻŊ2“ĖŦėãæ~´ņ÷ŨCll,7Ũp]¨HXúõ7 2„ŠĪ?Ã)M›0köܰųĪxõM:Ÿu&3^zž§ŸŒĶédņ'K*=Į‹…-[ļRˇn “~Įyˆˇla㛘ˇā#,f3Ī>=…ŠĪ?CëV-yí͡ąX,<2é�žyōqôŋˆĖĖ,fžū&ũ/žWĻŊĸ{îäÛUĢX˙Ũ�Ė Žâ~ũõ7z`<Ī<õFƒĪŋø €ėœ^zyįôęɋĪ=Íå— äĩ7Ūfß’“›Ë S§Ņ­ëŲL{ņy}x"ģ÷îåã%ŸVzŒ"""ō÷ų×Ę>Ÿ-[ˇąé‡Ít:ŖcÛq:Ŧ˙~#=Hwī֕-Ûļ‘›—Wá~™™Y|ĩlڝVã× zKKųzųˇäææŌū´ļ@°đoš–FģĶO#2"’={÷ągß>†\yDDDpåāAĘČ`˙ūũՊˇrõzôčFķfÍ0 4kv ]ģtbÍÚ˙Um˙•kčҤ¤$c6›¸đ‚žÄĮÅņĶ–­as¯Ē^=ģͤIc"""čÚĨ3Âī÷—ŲÆīāt9‰ˆŒĀl6Ø;ogøĐ!•0@Ll Ũģ¯qll 4āāÁC@°X­Vėv;6›Ë.Āã<ļŠ¤¤$^xæ?téÜŖŅ@Ŗ hŅŧģ÷î †2đûũ\ÔīŒF&“‰-ZpāpŦī7l"11‘>įô&*2’3ĪčČč[nÂfĩ˛ūģīIM­ËšŊ{įÁ'$Đŋ߅ŦŽâõ‘ŋĮŋręÅ­wŨúŊÔë%*2’ūõŖwīž5nsŨúÛ9ũpĄÚ´IõëÕcõęu\:°hģģvsËíwđųđųũthߎC¯ŽVŧ_žáđüg¯ĮCll,7ßt 6 msôüÚŦŦlė6{Ųi‡ ÕŦœÜ2ûU“­ÛļķÕŌee–ŸŌ´IĨûár썟zäÆ:ƒÁ@jj*Ų99as¯Ēē))Ąß­V @€ŌŌ˛ß†\9˜Ysæņõ˛o8­m[ētîD“&Ģ#1ĄN™Īf‹¯Į ĀĀK.âå3šįž8ũ´6t먑íN ێŅhāģ Yĩf-………`0â,)ĄN|ŲöŽ\/ĢłįpŦĖĖ,’“ËlÛéĖ3�ČČĖbßž?¸~äčrqŨn76›­JĮ*"""'×ßR(›Ė&JœÎrˋŠ�X,Ö2Ë'Oz�Ŗ!XdÎ]°—ËÅĀK.ŽU+V­Ļ¨¨ˆÛī>R„{Ŋ^V­YÀK.Ũ<˜Ö¸ˇŪ2�ƒŅH:ņ5ēÁíúk†ĶüđÜለbccĘmcļTŪn ücĄ:,+—_v)ú_T­ũ*â÷ûËäQ•܏e0Ví Œ>įôæŦ3ĪdëÖmü¸e+?õ4CŽ\îŠ)acT0ęßŧY3ūûÔ~ūå—ÃsŒßĻUËÜuû­åļ]ĩf ÂŨwŪF›Ö­�˜:ũ•*Į3 Ž3[ĮjąĐļÍŠÜwĪŨ•ˆˆˆüsū–Bš~ŊzüöûŽrËūųW, õŽy DJrrčfžë¯ÎćåĢĨËéwaßÅßš{ū™ÎÄņã¨sԈ­ĶédōOąmûvڝY´X,Ô­›rŧĻĒ,>>ŽZí¤$'ãrģČÎÉ Ũ včP>ŸēuĢ÷”Œē)ɤ§—Ž‘›—GlL,fŗé8{ÅÆÆ`ˇŲI?°?4Šë÷8t(ƒöĮÜøx˛K÷n]čŪ­ _|ĩ”oWŽŽRĄ\aģ……ÄDĮĐĄ};:´oGĪŨ™üÄS–ÛvįŽ]´jŲ"T$û|>öíûƒĶO ?}Ŧē)ÉlŨ^vNøˇ+WҰARRRظéGü~čŠ+ÅÅ%ŒTk*‹ˆˆˆœEEEäįį˙=s”/4;w1kÎ\öėŨGzz:_-]ÆÂEsåāA>Ž+..–C¯fáĸŤ3W7?ŋ€ŒŒĖ2?n§\+WŽĻuĢ´lŅ‚ä¤ÄĐOãF éĐž+VUíĻž“)-­ ęÕcū‡át9q8Š™÷áG¤Ĩ5ĻqŖĒOģ�čÛį6mŪˆM?PZęã?Ķ™ōÔ3ŦYģŽJûwíŌ™ĪŋXJfV6^¯—Ī>˙’â’:ŸuF•s°Z-8xâ’’jå~čP÷Nx-[ˇáķų(.)aßž?Š[ËĮĀųũ{â)>ũü \.7^¯—ßwî"22‚čč(ŦÖāˇîߏĶå$)1‘âpSXäāŊŲE~~~•âu=ģ3…,ųüK ‹Ø¸i3s>˜ÕjĨ[—ŗqģŨ|´ø\.ųŧōÚŧûūĩ:F91bbbˆ˙{F”ĶŌ3aü8>ūä3^xée<žRęĨÖåúkGĐĩKįJ÷īÚĨ3ßmØČkožÍ¤‰BË_ërێŋīž2Īä-q–đũ†\Ũđ°mŸĶĢ/L^åčd1 Ü}įmĖú`÷O˜„ÁhäÔÖ­¸įŽ;ĒŨV›S[3|č,\Äk¯ŋE\|}ÎíÍ9Uœã}õU—3gŪ‡üį™įpēœ4nԐ ÷ŨCøø*įpN¯ž|ŧä3~ÚēÉ“&VyŋÔÔēÜxũæÎ_HVVvģV­Zpí5ÃĒÜF8FŖ;oģ•Ųķæņé_aÄ@ŖÆ sįm˜L&čĐît^|y:}ÎíÍĀūũų}ĮNƍŸHLL4—]:€NgžÁôW^įĩ7ŪĻgĪîƋ‹cüŊcxįŊŲ|ōɧ$%%1ōÆëHkÜ€1wŨÎŧōÕ×ˉˆˆ CûĶzÕĩ:F9ą ĀņfR†ˇyû¤ž˜—<ˆˆˆˆˆœ ûeŅąm‹í;jÔ(ęׯ˙ī<œˆˆˆˆČ?A…˛ˆˆˆˆH*”EDDDDÂPĄ,""""† e‘0T(‹ˆˆˆˆ„ĄBYDDDD$ Ę"""""a¨P ŖÚ…˛ÍjÅåvŸŒ\DDDDDjÍåvcŗZkŨNĩ åz)‰äåár{j\DDDDäDrš=äåQ/%ąÖm™ĢģC¸h †Td䐓WPëDDDDDN›ÕJZÃTâcŖkŨVĩ e€øØč\DDDDäßJ7ķ‰ˆˆˆˆ„ĄBYDDDD$ Ę"""""a¨P C…˛ˆˆˆˆH5*” žü’â÷ÖW¸MîčŲœŪ°ë<ßíĄhÆĘš„Ž–ĸ—ŋÅ1sUØuūlY—Ŋ‚?¯�÷ĘßÉô įŋ„ãíu'=ˇŋT×ųÅvōÆ.8iąOÄu(šļ‚ŒsŸ/ŗ,゗Čŧd:™‚?Y—Ī ­ 8Ŋ>ˇŒŦËg’9`:Ž™Ģ!pTN÷‘3ō}2ûO#ûÚwp¯øũ¸ąŨĢw’{ëœZåŦĖĶq¯ŪYá6ĮöcUÔīN4VšwÎ#ķâ—Éė7•‚Į>+ŗžtw6ŊŸ#ā(û’ Ēŋ‹ˆˆü_RŖĮÃU*�Ĩ{sŽģÚģ;û¤„=š/=סŋ‘ôŪ a×;ŪXCäā30։ĀÖģ%ÉŊ[RøÜ×'=ˇŖũSqĄöץô×C¸ŋÛsĖB?¸KI^|+†ČōoÄqŧķ?|éy$Íē‘€ÃMî˜y˜Ķ°÷kK ĐEū¤OˆŸ<�kį&x6ė#oâ"R:6ÂQļ!ŸŸĸ™Ģˆ›Đ¯VĮpŦ„g¯ĀT?ŽÂmŽí;eŌǤßUI�0TmSįįÛ0ØÍ¤,ž ŦĻrëÍ âI˜1 CDŲkņOö;‘˙+j\(û œäŨŗ€ŌŨؘÖ!ūą"ņį;Éšņ]N/ŲÃ߃ÛŲMˆŊ˙B�3WS˛h3�ŽåŋPįš+07 ž=%ëō™D īŒsÉüų%Øz´ vL0ü.žũ=ގ×QwؘãæW4sQ#ÎÆm+ˇŽtgîū$ylß*oÉĸƒyû˜’Ŗ‰w>φu‚į"ËAáÔođnÛ3´!úĻîĄbĮņÆZ\Ë~!āõaiJ܃…-"Ã2@ásËp¯Û…Áf&î~XNoŒ›QHÁķËđĨįƒÉHDŋ6D ëÚÕŗa/E¯Ž"āõĸ¯í‚Ŋoë`N•\�īöƒäŪ6‡„ð´­W6/ŸŸÂ—{įšä˙(´ØīpƒÉxÜãķ|ŋ‡č›{bˆ°`ˆ°yYGœ˙{wE}˙qü=ĮîNä $‚œrˆ€āâA<đ>ĀŖęĪZŧjĩŪ=lkĄUëQmĩZTŧë**ā­"ˇ@ ÷É^ŗķûc !d„„%ôõ|<|˜Ũ™ų~ŋķŨņá{ŋųĖäƒų~PާÔčēž ÕZ’>˛• ےģv‹ė‚r雺dˇiĸĐĄÍËߋ˛P[žūB^YRfãLeßqŠėļM$I›ū2NVAC%į­VęûbŲmō”ũ‡ĶËÃnôœģRéĨĘųķ@EŽk8ūŨ];;_weoĪQüķE27PüĢ"™™eŨ~Jų|–ŽšĄäÜUŠÕZ[†.¯,ĄF×õUÆé]ũí?pŨš+ĸ*žņUy[ũ•âuƒŸ’$58Ģģ2ŪÛīŧ'%ĪSzÃVŧsCā ?¤lėlm}}š”teĩČQöoO“Ų¸ę��ög{”ãŸ/Rî?.’UĐHī­ŌQĶÕđĮĘĖÉP“į¯ÔÚĶ˙Ĩ&/]%##T鸆CŽ“—HI–ŠF×õ­Ú°e*ūÅbå=y‰ŧdZ~ų‚bŸ|+§_Įō]ŒaYų?üO’sV(ĩdŊrūtFāö-ŦFWõ‘"Õ;ũÄôīĩõų/•7ü2™MjëË_)úįw”7üRIRôOo)Üķ åüy ŧ’¸6Üđ_YÍŗ•qÚĄJL-RŲ;sÔäÅ˙“ąU|ķk*5C™—öĒ^ßsV*įÂ#”uˉÚúâmyâS5~l°ßīĐq ÖRš÷ëAųÅ ˛[7Q¤O[IŌæ&(gčY˛.PēxĢļ<öąœū$ŖŸƒ$#lÉlŌPFĀJå֗§*rl{Y-Wzß+‰IļŠčoßTrîJ™ųÔđĒc9ēmÅNnēüG3ˑģ|Ŗ˙s~Ce 뿎KŠėͲšeËnĶd§>âÚúâ5ū᠊÷JŠūeœōū5Hv§Ļ*yōSmyücå>pžŋƒeĒėŊyĘ{ō™ų´éžˇUōĖ$eŨâۜ{J’6\ũâ.?]];×e*>ĩH9ÃÎVÖí§¨lĖ,mē÷=5yÁ_qļ[æjë‹S”ŪS“§/Ģ´ržĢëÎ:0Gų#‡”—Î}†ų#‡Č+Kjí€GwyNUÎcær•<ũšōūsŠĖ˛´õŲ/ĩéŪ÷”{˙š5j�€únoæ‹w°ŦfY’e(ÜŖĨŋĒYG2Nī*…m™aEŽn§ÄŦå•ļ7Ø÷™Ũ�� �IDAT8į05ųī/‚öüēŲF×_izģøä%JoŽÉ9šKĩĮ˙ô[9'v–Ų¤áļū{(õíĨ7lUzÃV%ŋYŠĖAGJĻ!#ËQŪã+cÛĘmøđV~HÎ KļŠpˇåÁ°:ėVéŨÆoëˆVrWøķėm*Srö ŋ_IfN†2N뤸¤ÅåĮ Â*{oŽÜQ…õ§WûWú’d\ ü‘Cd\Pé}wųFÅŋX¤ĖÁGV=Č4åôiĢĖ‹Rū¨kÕđōŖũã[rWųî<Ōģļž>M^I\éâ­*}{Ž<×ĢÔDؘYZ{Ę#Ú:r†˛ī8E˛+Ž%/L‘͝ŖŦ9•ÎĩāÍkewjęĪUĪVUæ9rl{™$CrNė\åēڝ]^;ģ¸îŦæ9åŸĄsbgšß+-ķĮŨČQzÃVeŨÔŋJyÉŽŽģŊŠlbĄœ:Ę< Ëī÷üžJL-’WšØĢũ�°¯Ųãe3Ë)˙Ų°My;Ŧ֖Õ8ŗR?ģĒwŪYėƒųRØV䨀_ģžļ<ū‰˛nę_ŖĀčnØĒPįĻ寍Œ 'äßĖeH˛M?oߞï¸Ŋ˛¤ļ<ņ‰’…ĢeXĻÜõ% ŪĒÚ}›v˜gĢbžŨ [Ĩ°UŠ_3ˇRË*ÂaΰŗĩõÅÉ*žá BjxųŅrN>¤ú'ÄķKA˛nęøEÄjž­ė? ,9ŽŊ‡4SâëĨĘØM™—÷QúŅĩūōge6i(į¸öŠą¸RguWƀ.ŠOūNŎTŪŋÉ:Č_švWmVlü\5yn§`O*;KąOžõ_–%äítIZy;\Wy[bÕ?īŨ\;ģēîvŧžĖ°d~ß92ļŊTŊĢëÎÜá\ęšWSlęRÅ&}WŅwƒ°ŌÅĨ˛Ē[2�Ā~`īÜĖWKéÍe;üĢĘw)áĒäŠĪ”ŗCPÛQŲÛŗe7ĪV¸gˍĮĘË,_”üđëŒ˛ōĘK{R*-¯$^ˇ¯ø™y™*yæ škˇ(īņ‹Ĩ°Ĩ’'>•[ü´„šŽI W^iĸŧ8-­T’b5Ī.¯ OĖXĻč]ŖîŪĸ|ĨpO¤×mQōÛĩÚx÷[ūnZJ{Zwápå=z‘dr×lŠT;ė%Ũōō Ãą•uËI’å§Í­Ī}){[N-Z§ÔâĩrNé"ElEúvPhĖ,Åŋ^ĒÛö)yōSe:RÆN×Dü‹E*9CyÃ/•Ų8S‰ÉK´éĄ‰•Įži‡ëjK Ž+íæÚŲÍuˇãõėmMHi¯ōÍpđ†ģēîö&3ŋ‘2ÎčĻF×ŋWû�`_ˇWžŖlDlɐܕÛĘvz4•‘öīIrŊ*‘+{^ųûņI‹îQ9œ”ŽžŠõÛn^ÚŅÖ7Ļ+ÔĨyų¯ßwä•&Tōüä=úŸäøŠ}0ŋüq`Ĩ¯OS¸ģ˙$3ˇBŨZhëĢ_—ŸëÆ[^WbÚRI~hĩ˗–ŌÅ[Ÿüŧ˛Ú˙ ÛČÎP¨[ •Žœî÷S\Ǟņ…ŠôíācKLo~­|îCøõâfÅrčî>‡Ôˇkĩîŧ'•úvmų{fA#ŧ}Ŋō_ģZų¯]­Æ˙,™†ō_ģZfĶ,Ĩ‹Kĩņö‘åĮÄ?_¤Ôwë>˛ĩ$Šäé/´ič;’›VjÉ•ž9ŗŧ.Y’6?ü’ßŦôû_ŧNÉkj›/IJÎ[ĨäüÕjpn*ķ‘Ū°U֍d6Δ’ŽĘ&V™įø¤Åōļ…åØÄÂ*×ÕŲŨĩŗĢëN’RK7(9oUyŋv›ŧĒOņ°ĢënorúvPlbĄŌëļH’’ßŦÔæŋßĢ}�°/Ú;+Ę!K™—öRņ¯^‘lKVĶ,åũįŌōÍQô÷c´ö´ĘČ ĢҐãåœÔšbPíķĩᚗ”Ū¸U‘>í9öāJÍ{Ĩ šëJ*ŋˇŠLĨ¯}­ŧ'. Ō֗žR¤O;Y­ōĒlK-Ų 7ŋ&IJoMČ0ũ:Yë ÆjüčE ÷lŠĖ‹ōĪG’Ũ"WŲŋ?ĩüøœ?žŽM¯uįü[F$¤Œ]ĘK\t„6 §ÄĖ垚eĢŅ5}ũË;*}õk…jŗË~w'įw§jĶãUöū32,S /ī]ūŒFŽ"Į´×†k^’LS†)5ŧęX™ųĒũ9x Wéõ%ōînĮ˛Ũņ�5ēūE˙ø–Ō›Ęü'ĸüõl?ĀJĘŧčEīyGkĪ|\FFH q\ųęŗŨ>_YˇŦM÷WzC‰Ė,G ˙ī…k!IÚōØĮjøËã¤P՛ ~U6ĄP~ņ‚ĖœjxÅŅJ~ŗBŅߍQΰŗ$Iá#[+úĮˇ”Z•Ũ2×/Ŗûpļ<úĄ˙9l.ĶĻ{ߓņ %įgÔčĻūģŧvvwŨIRčĐU:zĻ’CĮÉYĘĒæ#ívwŨíʖG?TėÃ’į×oMŖ~&ģ]Á.¯ģĐĄÍÕđĘ>*žųuÉķd6tÔčúā>�؟žįyģßíĮŗîÂáĘųũi u;đ§ ö#›ūúžė9åNûą”Ŋ;Wąņķ”ûđ?jŋ��`Ī 2D͛7įOX���AĘ���@€}Žô���ø)Qz���ėA���@P���”��€�e��� �A���@P���”��€�e��� �A���@P���”��€�e��� �A���@P���”��€�e��� €]ĶVŽ\š7Æ���ÔŠæÍ›×ęøåÚv���Ô”^����Ę���@�‚2���€  ��� (���Ę���@�‚2���€  ��� (���Ę���@�‚2���€  ��� (���ė=9(‹)*‹Õõx���€=æ8Žrrrä8N­ÛĒqPŽÅbZģv­˛ŗŗ•››+Īķj=��� ļ Ã(ĪĒĩË5.ŊˆFŖĘÎÎV$!$��`Ÿáyž"‘ˆ˛ŗŗFkŨ^ƒr,Ģ“Ĩl���`op§NJ„÷čf>V’��°¯ĒĢŦĘS/���€�e��� �A���@P���”��€�e��� �A���@P���”��€�e��� �A���@P���”��€�e��� �A���@P���”��€�e��� �A���đ?”c))c¨Ŗ)ˍŸz(ģtį[gžūЇ��đ?ĪūŠP ktágû‡NIęÚ#ŨqDuī—G¸ÚšøņÎáéé–.:ÔUC˛9��@%õ*(?2 Ąŋõ÷W„?.2uÍÛ!Íē6ވåooœáũ„ŖĢmsŧsH¸ŌmãC:ŊA��`gõ*(įgJ’$ķø˙>(ۓŗí,â)鮉ļFZÚ7Ô!ĪĶĮ&uęÁé*mmŽKũžč¸Vi=< ŠXJēûŖÆÎ7ĩžÔ?vX˙¤Nhã{üˆˆÎėčjöjCsיÚ7tc¯”nė•’$MZfęΉ!Í[k(bKũÛ¸zäÔ¤r3ĒžĮŽöŊs‚­yëLŊ8!Iz~–Ĩ?~RIB:ˇŗĢļšžŪ[dęŖ+z‘ŠĢĮ†õČŠ =<9¤Õ[¤–Ųžž=;Š–ŲūüŒœgéŪĪm}ˇŅPŽ# >4ĨŋôK)áJÍtT–’ē<æčŽcSZˇÕĐâbCŖ%ĘĮÚwDD;ēēĩOJŋhëûM†Â–4zžĨÕˇÆäi×ķ��P_íW5ĘwdkÂbKī^šĐ÷7ĮtngWƒŪĢ(Zš.9•–.ÖÁyž<%)IúŨ!MZfjâå ­ž-ĻĢzĻtÁka—ųĮD,O}eëŽãRš6$Ž'ÎHčˇm­ÚâoŋlTXįuvĩę֘f^Ķæ¸ĄaŸ…ĮYŨ}ŋYkhČ[!ũĩRË~Ķ‘ÍĶztŠ­ĐļOÍąĨ1éĶĨ–>ž"Žy7ÄeŌ“üoßm4tųční“ŌēÛc;8ާgØzuŽĨˆ-Mē*.Iš{}Lˇ“Úíü:ļôų÷–ēxZøĢ˜"öîį �� žÚ¯‚ōˆļné“R›\O!KēąWJMzˇ°ōiŪônHeIiÄŲ ™†äĻũ•ÛģŽMĒE–'۔Žęéę lO#įųu†¤“Úēęœī¯Ôöm•–ëI‹ŠMĨ=…:ÛņûÍĪ”F J聓“UÆX“}Į.°tHž§Á]]9ļô‹Ã]ujRąRk~čŋųč”LC˛M阖iÍ_ī1hãiҝcē°‹+͐ēx:ēEZĶVîŲĮnūzūoŽN)?ŗzķ��P_ÕĢŌ‹]YˇUڒP• Ų!ĪĶŌM+ʏL itĄŠYׯËK6Vm‘JŌų¯U-ÔŨq5z{9ƒ$…,É2¤˛” ˙Ú?Š›ß éą¯lØÖՅ]\õhVĩŪ¸&ûŽÜl¨ÍN5Ë=šyšŊēō yËŦŠ}œKå}Ŋ>×Ōķŗl­)1dR4&˜ĩį7 ļÎIËØÖ}uį �� >Úo‚ōI{R­m…ë Ũ"­ģ&†4ō"ŋ7c[ÕÃGW$ÔģÅ×Öî*ú]}¸Ģs:ģz‘Ĩqߚę;"ĸaũ“úU¯ĒĄ´ēûz’Â;-˚ƒ0~``ĪΞtĪ'!ŊqaB}[ûįuŅë5ģk/ŊS~ė0žęÎ��@}´ß”^ägJÂŌÜu§”ö¤ë ĩË­qŸžÔķį&5i™Š'ŋöS_^)בæŦМ8—Ö`UtM‰Ô¤tI7W/—Ô=ũRzzzđ÷ęî[éiÉÆĘc˜ĩēúcúr™ŠcJ—‡äTZšąę‡Xžb;”*{ž´|ķī_ķ��°¯Úo‚˛$ ęęęÁIļŠĸ†â)éī_ØÚ7tnįŠ•ZĶđÔ"ËĶ#§&u×ĐlĢįrDJ˙ÂÖė5†Ü´4ēĐT'"*\ˇûС¨ØP§9z‘ŠTÚ/o˜šĘPģÆUË)j˛ī€öiÍ\mhė|S W1ÃŌ‚õÕ˙ČZåxšŋŪPq™´žTúõģ!5ΐV•øįÔ`ۊđÜĩĻļÄĨöyžæŦ5ųī?5ŨŌ–øŽû¨Íŧ��ėËöĢŌ‹{OLęöņ!ø\X% C]HëũKãjžĨJ+Ĩ’taWī,4uśa}re\w›RIÂĐĀ—#*IøĩÍ/ž›(ŋyoWÚ7öôÄIŨ91¤% 5ŠHĮĩJëŅSĩÚˇW‹´†õOé7ãÃēæmipWW—–Ō”åÕ Ëŋ<<ĨIߛęøOGMxúCߔÎéė꒑a]5&¤§ÎLęôiûjXCŽpuĪ IMXlĒįŽō3=ÛŲÕņ­ŌJîĸ¤š6ķ��°/3<ĪĢQĸ)**RĶĻM÷Öx°“ŌdÅƝ$]ķVH WzæėĒOÉ���€oõęÕjŨēõ;dČ5oŪ|˙*ŊØß,*6”ŋŖWžąKų5ĮŖį[:­7Î��ėmûUéÅūĻ}cOOŸ•ÔŊŸŲēö퐚5ôtû1)Ȟ?Ū ���ÕCPŪĮ :ÔÕ C Æ���?6J/���€�e��� �A���@P���”��€�e��� �A���@P���”��€�e��� �A���@P���”��€�e��� �A���@P���”��€�e��� @ƒ˛ã8*--Ũc���j­´´TŽãÔēģĻääähōäÉĘÉÉQFFF­����ԕ˛˛2EŖQõîŨģÖmÕ8(;ŽŖŪŊ{+*‹Õz����@]ÉÎÎV§NędE™e��� @W”cą˜ÖŽ]ĢėėlåææĘķŧŊ1.��� F Ã(ĪĒĩ^UŽņŠr4Uvvļ"‘!���û Īķ‰D”­h4Zëöj”cąXÔ|����{ƒã8ur/ŨÕ(ŗ’ ��€}U]eUnæ���”��€�e��� �A���@P���”��€�e��� �A���@P���”��€�e��� �A���@P���”��€�e��� �A���@P���”��€�e��� Ā˙LPŽĨ¤ŒĄŽĻ,7~ęĄ��� °ęÔÄÅ#Ã]øÃŲūĄS’ēöH÷GŅ˙މߙjÖĐS—ī§ ��Ā^Q¯‚ō#ú[Eøã"S×ŧŌŦkãŠXūöÆ„ļËC“l]ŲÃU—ž˜��€ũSŊ Ęų™’ä‡áüūŋĘöäl;‹xJēkĸ­Ņ…–6Å uČķtįąIzpēJ[›ãRŋg#:ŽUZH*–’îū(¤ąķM­/õÖ?ŠÚøĮ?"ĸ3;ēšŊÚĐÜuĻ6Å ŨØ+Ĩ{Ĩ$I“–™ēsbHķÖŠØR˙6Ž95ŠÜŒĒįŅ{xD§wp5{ŠÂu†ļ& ũų„¤~ŪŨÅeŌoŪ éŖ"K[R×Ōz蔤z4ķĪų¸g":ŖƒĢ1ķ-5yšxyBŗVēcBH3W›˛Lég­ĶzäԄš4Ę’Rãû={vRçYZ5u`–§§ÎLčoŸŲššÚT,%=vzRũļīō͆nŌ”åĻJŌąųķÔ:ĮĶ€ÂútŠŠIËLŊ8ÛԘÁ‰]î_š”ōîsôä¤ūôQH?īžŌŸNHÕÕe��°WėW5ĘwdkÂbKī^šĐ÷7ĮtngWƒŪĢ(Zš.9•–.ÖÁyž<%)IúŨ!MZfjâå ­ž-ĻĢzĻtÁka—ųĮD,O}eëŽãRš6$Ž'ÎHčˇm­ÚâoŋlTXįuvĩę֘f^Ķæ¸ĄaŸ…ĮļĨáĶlŨsBRß\×sį$tÍ[!Í_īķŽ !-‰šš6$ĻåˇÄÔŖ™§ËF…ˏXžF˰ô§’yQBž']ôzXķ=Ũė÷ŋbŗt÷‡~˙ÛŋHŧ<ĮŌ;—$4÷ú˜ŠKĨ“ŸčöcRšqM\uqõۉūūž'÷jXšŽ§9×ÅTtSL™ž.íáŊË*Ȕ†Ÿ™Ô˜Á‰ŨîŋŊ˙įgZwi\ˇCH��ûžũ*(˜aë–>)ĩÉõ˛¤{ĨÔ´Ą§q +ŸæMī†T–”FœiHnZz~–ĨģŽMĒE–'۔Žęéę lO#įųu†¤“Úēęœī¯ęöm•–ëI‹ŠMĨ=…:ÛņûÍĪ”F J聓“ã4$ h_ŅÖņ­Ō:8ĪĶ; ũžū1 Š1ƒãjŌĀ™—tMiņFCҘŧiH‡7÷trģ´˛É0¤Iŋˆë¯ũ“rlŋ˙ŗ;§5m•ŪÆļī —vsąũ6{4KĢ{Ķtųú”ÖĸbĮŠ+ }ŗÖĐŊ'&Õ0,e†ĨĄũ’új…ĄëĢŪ šģũÍm‡œˆĢNM<e†Ģ4��°ĪŠWĨģ˛nĢ´%!ujRQfaR‡<OK7U„ģGĻ„4ēĐÔŦkãå+ĢļH% éü×Ē&¸WŖ[fWÔ@‡,É2¤˛”\˙Ú?Š›ß éą¯lØÖՅ]ÜōR‰ mr+ok‘åiU‰ß×ĒCø0¤Š+M%]?ČKū“;ļk›[šœäë•Ļî˙ÜÖĸb?—&Ĩf*÷QYņÚąũ•é_ooņF?ü7}Ā œŽM*ˇ[ŨũÛ5φ��ÔûMPū!iOJ¸aˇpĄŖ[¤u×ĐF^”$el̐øčŠ„zˇ¨ZĪŧŨŽ,wõáŽÎéėęũE–Æ}kĒ†õOęWŊ‚ovKîôv*íˇīĻĨ/‡ÕģEZS¯Ž)7CšŊÆP¯á‘JûoŋQ’Ŧ7tÁka ë—Ô/H(lI˙šbiøôƝąĶ ėüz;Įöļ¤MwÅvqÆ5ß?˛ß_m��`˛ß”^ägJÂŌÜu§”öüŲn‡Õ×ĮOOęųs“š´Ėԓ_ûi3¯”ëHsÖTNŽKŖÕæōšŠIé’nŽ^:/Š{úĨôôôN†‹7Vnģ(jčĀ,OĢļø?_dĒüFĀŠ+vũ1M_e*dJ×å*ŧ-@O]šįmûƞŽ*•Y¤=iŲĻāų¨éū���õÁ~”%iPWWN˛U5OI˙ÂÖϏĄs;W,ߚ†§Yž95Šģ&†ĘÃŨ#Rúûļf¯1äĻĨŅ…Ļz<QáēŨ‡ŊEņ:ũËŅû‹LĨŌR4&Í\eė˛Ôā“"K-1•pĨįfZZļÉĐéģĘĪ”„¤/—ûm}¸ÄÔ[ üôģjKđXZåx*KI3W*MJ˙ųÚŌwM—JėÁĶÛēāŠOË´nŌę˙ŠüČV˙įÂJmûΑōT¸Î¯›ŽÎū���õÍ~”ī=1ŠcZĻuâsaĩzØŅÄīLŊi\ÍŗĒî{aW;ēēâͰŽtįą)Õ)­/GTđwG÷Ō‹į&Ęovەö==qFRwN ŠÉ}Žē>î(‘6ô詉<æįŨSzâk[Ípô§CzęŦ¤ÎķąĨž–Ô?&‡tāƒŽū=ÕÖđ3:ŠmZ§ŧŅ7k̆å>-ĶēöHW§ŊQį9Z5ôßķâĘ y:ôąH@īģ÷Ü9Ie„¤Ž;jûˆŖYĢMŊ8!{ÛsUOWOļuڋ‘jí��PßžįÕčĢĸĸ"5mÚtoįBß ėčęÖ><& ��`oXŊzĩZˇnŊGĮ2D͛7ßŋV”��€ēBP���đĀŽŸĀ'WÆę!���`7XQ���”��€�e��� �A���@P���”��€�e��� �A���@P���”��€�e��� �A���@P���”��€�e��� �A���@P���”��€�e��� @ƒ˛ã8*--Ũc���j­´´TŽãÔēģĻääähōäÉĘÉÉQFFF­����ԕ˛˛2EŖQõîŨģÖmÕ8(;ŽŖŪŊ{+*‹Õz����@]ÉÎÎV§NędE™e��� @W”cą˜ÖŽ]ĢėėlåææĘķŧŊ1.��� F Ã(ĪĒĩ^UŽņŠr4Uvvļ"‘!���û Īķ‰D”­h4Zëöj”cąXÔ|����{ƒã8ur/ŨÕ(ŗ’ ��€}U]eUnæ���”��€�e��� �A���@P���”��€�e��� �A���@P���”��€�e��� �A���@P���”��€�e��� �A���@P���”��€�e��� �AYŌĐOlü|øGëīÎ ļÎ|šîûˇĐTÎߜ:o��āQŊ Ę ÖĘęčÄįj8ŽôÄTk/ŒĒzũũōWÃú'ë¤í§§[*Iø?÷j‘Öۗ$ę¤ŨŨõ��°ŋ̎Aųéé–Îî”ÖԕĻŦ7jtė×+Mũûk{/l÷ũĩÍõÔõ�¯Öí&\éļņĄōđš×@:ö t­Û­N_���ûģ/-ÖĄXJziŽ­gĪN(•ļõĖ K÷”*ß~Ûø5¨"ÕõŅĀŽŽz6KëÜWÂJ¸R“ûėG§Øzā [É´tFWžTxÛBđSĶ,ũûk[ßo2Ô¤§ŽJéúŖ\IŌī>°U5Ô1ĪĶģ‹,­)1ÔˇĩĢág&õéRŗJo-05oŠąû}?ū•ĨGĻØZ_j¨gŗ´89ŠîMũ =ržĨ{?ˇõŨFC9Ž4øĐ”ūŌ/Ĩ„+5ĐQYJęō˜Ŗ;ŽMéĐü´.Vôޘ$iĘrCwM ŠpŊŠŦˆ§~mŌúûÉI5 Kī/2uõذ95Ą‡'‡´z‹Ô2ÛĶŗg'Õ2ģrˆ§*÷ucī”ūņĨ­ÎI茎ÁüēˇCZ]bhÄŲ 5}ĀŅ““zfēĨ[ e†¤§ĪJčđæ~ÛË7ēu|HS–›*Iø˙áIĩÎņˇ?3ÃŌÃ_ÚZžŲP^éįŨRúCߔŒš}'��ØcõrEytĄĨˆ%õk“ÖÅ]Sziļ­xj÷ĮI*‹įyZGLĮl[ŗÆÔ†RiÎu1Ŋ}q\oĖŗôÖ?%ŋYhęîBzüô¤ÖŪĶŗg'tĪ'!MXėOŸcKī-˛Ô>ĪĶ—ŋˆkĘÕ1Ŋû­Ĩ1ķ­ėoģQķL ũ4¤ág&õŨ¯cęu`ZįŧQ•žÛhčōŅ!ŨÚ'ĨuˇĮ4vp\Oΰõę\K[štU\’4÷ú˜n?ĻōŦ)‘N{)ĸs:ģZzsLī]šĐäåĻî˜*ķÆ˜ôéRK_×ŧâ2 éIUŋ;íÜ×ûĻtfGW/Są¯›–ŪZhé’nŠō/ĪÍ´ôæā„žŊ1ŽķquáëšiÉķ¤ķ^ +×ņ4į瘊nŠŠ ĶĶåŖũ2šo7ēqœ?'îˆé­ÁqŊ4ĮԘųõōr��õTŊLOMˇ4čД,S:Ŋƒ<Į.¨]ÍqȒūøŗ”˛éđæžēVá:ųrÄL[—uOŠW‹´ CęÕÂĶāŽŽžŸå÷iHĘoāéâŽū s~ĻtH~ZķĢQōÂl[tqu|Ģ´˛éŽãRúKŋ¤J“RëO‹~Ķ…]\™†ÔĨĀĶŅ-Ԛļr÷Û+ߨjÖĐ͝z“÷ČĀ�� �IDATš [R›\O7õNéÍųÛÆlHŠ´tķŅ)™†d›Ō1-Ģ7fIēü0WãšÚXæŋūdŠŠTZ:ŖCē|Õ÷ĘŽrļŨ[ø‹ÃSZšEšļŌĐÔ†žYkčŪũÕí˰4´_R_­0´`ŊĄ¨ŋ Žŧ OĻ!uÎ÷TxC\gwŪ;e%���Aę]éÅüõ†&-3õ¯Ķü›á–t᥎žžné‚.îˇÛ&'-s‡ŒØ ä—xHŌĸbCãÛztJåé:ōŠ…Ë[*ĢÆũzßm4ÔˇuE�Ė K—tĢ8×įZz~–­5%†LCŠÆ¤ŗvžKĸ†:åWS§&žŠË¤-ņŠ÷ZfUėㄤXĒzAųg­Ķ: Ą§7æYēúpWŖæųķą+æ­MNEÛMúa|U‰ĄŌ¤Ą´'5} ę:Šĸ†Nj—Ö%Ũ\ūdDGˇLë¤vi >4ĨŗĒ54��€:Qī‚ōĶĶũ!Ÿđl¤üŊdÚĨ‹Š ĩo|“\z7÷ΙģȇļôįRUĘv´§Ĩŗ†~xlĪΞtĪ'!Ŋqaĸ<L_ôúž?Vn{?ņröžÖüš†ti7W/ÍļteWcXzũÂĘwú%wX�ö<ŋC’c{ [ŌĻmĩÔAž˜ÔíĮ¤4î[ScXú많ŪŊ4Ą^-XU��?ŽzUzKI/Íļtī‰)Mš:^ūĪô!qu)đ4b†_VąŧōUMÉiË7īų]`íķ<ÍYSųø›Ĩäž/`—kרĶüumĮSŌ°Om­Ü,}šĖÔąĨËCr*-ÍXUŊķh›ëiŪZCŪ!|Ū:CšŽ”—QûqKŌeŨ]M]i깙–˛¯Jˆ]\\1ÖĨ›üUäŗ<ĩoė)áĒŌĶJŌž´l“˙:•–6”úsķĢ^Ž&ü<ĄŸĩNëĨ9?Ū#ũ���ęUP9ĪR2-]Õ3ĨV9^ĨŽ<,ĨgÛJēۂíZŗŧÖõŠéVĨrƒ!i}ŠĄ›ĢW1äđ”Æ.°4ēĐTŌ•f¯1tÂŗ‘ōåŨŲUW–ŌČBKãú7ûÔÖ33lå5ZåxšŋŪPq™´žTúõģ!5ÎđËļˇ+Isך•ÎO’ÎîäjU‰ĄNą”tĨ… =üĨ_kŊ'ĢČA}ĩÎņtÜAiũ.íVõ[Ãsŗl-.6´5!Ũ˙š­Yžz4ķ×§eZˇŽiu‰?'üČV˙įÂJĨĨfYę;"ĸÂu~¸^5T5Ô.ˇöÔ��¨Žz”ŸšnéÂ.ŽT\ÜÍՖ¸4vŠA‡ē:ž•ĢžO8ę5<ĸâ2CĮˇJ—¯�÷oëĒIO]sôŪĸŨOÁ mŌzđä¤~˙aHų÷;ēčõ°†áęĒžÕ[RŪU;Ļu˙IIŨønH?ęč‹eĻF^WĖ~yxJ7öÔņŸŽŽ{&ĸcJkh˙¤>[jęĒ1!ĩČōtz‡´Î}5Ŧŋ|ĒÔîAŲž^ŋ0Ą7 mø ŖŗūÖų‡¸Ú¯šŲÉõuYwW›ã*ŋ‘qGW–ŌĪG‡uāƒŽ&/7õƅ‰ō—įÎI*#$u}ÜQÛGÍZí?.Ī6ũĪ;ÄÕĀ˙F”{¯ŖŸë¤vŽŽ;rĪÆ��°' Īķj´LWTT¤ĻM›î­ņ žšīs[_.3õæāŠúäXJĘŊ×ŅĮWÄÕĢĢĀ��āĮˇzõjĩnŨzŽ2dˆš7o^˙næÃž!éJ_,3õā$ģüļ���ėOĘØ#§ŧ֒¨ŠĄũ’:ē%Oĸ���û‚2öȇWüđ*˛cKeŋ˙áGŋ��Ôõęf>���āĮBP���”��€�e��� �A���@P���”��€�e��� �A���@P���”��€�e��� �A���@P���”��€�e��� �A���@P���Ô8(;ŽŖŌŌŌŊ1��� ÖJKKå8N­Ûąkz@NNŽ&OžŦœœeddÔz����@])++S4UīŪŊkŨVƒ˛ã8ęŨģˇĸҍbąX­����ԕėėluęÔŠNV”ŠQ���ÔxE9‹iíÚĩĘÎÎVnnŽ<ĪÛã���jÄ0ŒōŦZPPPëUå¯(GŖQegg+‰’��°Īđ<O‘HDŲŲ؊FŖĩn¯ÆA9‹ÕIÍ���°78ŽS'÷ŌíQ2+É���ØWÕUVåf>��� �A���@P���”��€�e��� �A���@P���”��€�e��� �A���@P���”��€�e��� �A���@P���”��€�e��� �A���@P���”khÜBS9sĘ_?ū•ĨttÖÃUļ�� ū˛ęÔTŌ•îûÜÖČBKßo22Ĩv=ŨpTJƒģē{Ŋ˙^-Ōzû’Dųëŋ}ŌMŊSúU¯”ļ&TiöMŋ3ÕŦĄ§.ŪO=��°ĢwAų–ņ!}đŠGOMĒëi%RŌ¨BKW ŠaØĶĀŽéŊÚ^騃*úˆÆ¤îMĶrlÉą+oÞéĄIļŽėáĒKÁŪ˙b��ę¯z”?üÎÔ˙õpÕŋmE ŊąˇĢæYԁüÂÛ'„´l“Ąŗ<}´ÄÔĒ-†wuu˙IIYۊMžšféß_Ûú~“Ą& üéëĒNeé‘)ļ֗ęŲ,­NNĒ{SOãšēxdX+n‰ŠÕÎRiéĸ×Ã:žUZCOéâ‘aEīŠI’–F Ũô^HŸ™jÜ@ēŦ[Jw÷MÉ0Ēž×¤eĻîœŌŧĩ†"ļÔŋĢGNM*7C*KJw}Ōës-yžtR;W’T^˙ØįfZztŠ­å› ØČĶ%Ũ\ŨŌ'UgsąŖŪÃ#:ŊƒĢŲkLŽ3´5ičĪ'$õķîūūÅeŌoŪ éŖ"K[R×Ōz蔤z4ķ?›ãž‰čŒŽÆĖˇÔ äiâå ÍZmčŽ !Í\mĘ2ĨŸĩNë‘SjŌĀ?÷Æ÷9zö뤆Oŗ´$jęĀ,OO™Đß>ŗ5sĩŠXJzėô¤úĩņ¯‰å› Ũ:>¤)ËM•$ü//HĒuާ/„õéRS“–™zqļĨ1ƒģÜŋ4)åŨįčɁIũ骐~Ū=Ĩß÷MéΉ!œg)“Úäxúíņ)Á�€ũIŊĢQîÖÔĶ+ßX*\W9mžˆĢžÍũ02=Ŋû­Š#š§5mH\͆Ä4ĒĐŌ33,IŌ›…Ļîū(¤ĮOOjím1={vB÷|Ō„ÅūtŒšgjč§! ?3Šī~S¯Ķ:į•ˆ;ä Ė°´ūŽ˜,Czõ‚„Æ Ž\ráyŌy¯†u@ϧųŋŠéÍAq=?ËÖ_[įuŲ¨°ÎëėjÕ­1Íŧ&ĻÍqCÃ> I’~ûAH_-7õɕqMŋ&ĻõĨ†~õnX’ôÎBSˇŧŌÃ’ZukL˙85ŠaŸÚzõĢNæbga[>ÍÖ='$õÍõq=wNB×ŧŌüõūįqĮ„–DMMĶō[bęŅĖĶeŖÂåĮG,O#fXúĶ Iŧ(!ĪķŋhtÎ÷Ttŗî+6KwčŸģŗíĢÜËs,ŊsIBs¯Š¸T:ųųˆn?&Ĩ×ÄuQWŋĒ4īšŽ§9×ÅTtSL™ž.íáŊË*Ȕ†Ÿ™Ô˜Á‰ŨîŋŊ˙įgZwi\ˇ“Ōs3-Ŋ÷­ŠĪū/ŽõˇĮ4ŦRŋŌę’Ā)��õTŊ Ę˙P›\OGü'ĸî˙ŽčÚˇCzåõr;CR͆žę'Ûf¤3:¸z{ĄGĖ´uY÷”zĩHË0¤^-< îęęųYūöfÛē ‹Ģã[Ĩ•íHw—Ō_ú%UšŦū8§­44wĄ{ú%U)Zāé…sęŪ´j]lړ6ĮĨlĮSȒō3ĨQƒzāä¤Ü´ôŌlKˇôIŠ}cOMJžšÔyũãgfTŒÕ4¤ã[ĨuNgWoΡęd.vfHĐŪUį|˙<Žo•ÖÁyžŪŲÖŪ?$5fp\Mø!ķ’Ž)-Ūh(ę/˛Ë4¤Ã›{:š?ˇ†!MúE\ퟔcûį~vį´Ļ­ō/ÍíĢī—vsŲVŪŌŖYZŨ›ĻËĮĐį ´û;N]ač›ĩ†î=1Іa˙ ÍĐ~I}ĩÂЂõU—ōwˇŋšíķqÕŠ‰§Ė°´)n(dI9Ž'˔N=8­õwÄÔ´au¯��PÔģŌ‹‚Léĩ ZS"}öŊĨ/ž7õ‡mŨ6>¤‘ÆuT ?<ĩÉ­H[dyš˛Ü_‹Š _lëŅ)•O˙ČũcžÛh¨oëŠŌŽĖ°tIˇšũZ}IÔTfČīv}Z×/›†ô×ūIŨü^H}eëÄļŽ.ėâĒG3OëJĨ- ŠuNÅąí{jר뒝Æ*Išx]Xņ¨6s$¨ŊU%~ĸ\Ubč†4uĨФ+šÛ†KUėß6ˇōxŋ^ięūĪm-*öĮTš”š5ĒÜGAfÅkĮöWĻw|ŊŊũÅMĨ=ŠéUŸ>R5ÔąIåvĢģ˙öų–üšŅ…–ÚūÃQŋļiŌÎ˙ŧ2ÃUš���õXŊ ĘÛĐĐ_å;˙W÷Ÿ$]đZXwN éÃ+üĨåäNš6•ŽXĖ°Ĩ?ŸŌíĮ¤ĐŋĘ[†Qŗ6Ž>ÜÕ9]ŊŋČŌ¸oMõҰūI]ØÅ?‘š´•ö¤øį_›š؞üP<đå°zˇHkęÕ1åfHŗ×ę5<Ri˙Č‹Õ Öēāĩ°†õKę—G$ļ¤Mą4|zåKsįēî :oIrlOaKÚ´­N|wĒģd‡áägJŸ^×W+ ŊŋČŌC_Úē˙ [“ŽŠ+7ŖZŨ�€z ^•^E ]22Ŧĩ[+ŋ˛¤žÍŌÚ¯HOE›Ly;„Ë%ō›ũÚįyšŗĻrŌZąš"�ļkėiū5Đņ”4ėS[+7WŦmsŌ*KIßoĒhgÂbŗŧvxgkJ¤& ü•ë—ÎKęž~)==ŨVAĻÔ(,-ØPņQ-Ü`čū/üäÖļą§ov:—yë ĩßa´6sdņÆĘûEũ›WmņžūČTy`œēbחØôUĻBĻtũQŽÂÛĻfęĘ=ŋ,Û7ö”pUŠĖ"íIË6'ëšî/I[ŌÖ¤_Ļr÷ĪRšú˸ļÄ }°$øŗ��õSŊ Ę-˛<Í[gčŧW#š°ØÔō͆Šĸ†^šméņŠļÎęX‘î6Į¤LļOI_¯0ôöBKˇmrxJcX]č—Ė^cč„g#åušW–ŌČBKãšÚPę‡ägfØåO™¨ŽžÍ=ÖÔĶCZžŲĐŦՆŽ{'¤â˛Ēû.*6Ôé_ŽŪ_d*•ö97s•Ąv=†tEW˙Â֜5†–o’n}ßB„$ :ÔÕķ,}ļÔ?vâwĻۜoé˛n+Äĩ™‹ ŸYúh‰Š„ë?qcŲ&C§ė*?Sj’ž\îåÃ%ĻŪZ⎺jKpđl•ãŠ,%Í\m¨4)ũįkKßm4U\fTēy˛ēēāŠOË´nīß\W–”ūø‘­ūĪ…•ÚVņ‘ōT¸Î¯›ŽÎū;ģa\HW kM‰ĒgŦ2ĩ5)ĩÉáŅ€��ėOęUé…mJī_×ŊŸ‡ôëwCZUbȐÔ!ĪĶoKę†^Éęč–i—IíqwĨ+KéōÃüí'´IëÁ““úũ‡!]ųĻĄf< 9ÂÕU=ũí;Ļu˙IIŨønHÅe†z4KkäEņJŋ~¯ŽQÅuÍÛau{<ĸÜ éŌn) 9ĸjúkߨĶg$uįЖl4Ô("×*­GOõËH†öK*•éäü†Ûēzx€gáų‡¸ZšYúÕ¸Vl1Ô:ĮĶ&uÆĪ“ŽÍ\ųy÷”žøÚÖų¯™ĘŠHO•ÔÁyū õ?OKę†4ô͐Žo•Öđ3ējLX§ŧ҇WÄĢ´Õ§eZ×é괗" [ŌÅ]Súīyqø|D‡>ŅÂĢŗ;Ī“ÔoŪŠëãŽlS:˛yZc/NČŪöĩđĒžŽ†}jëŊE–&ũ"žÛũwöĀÉIũúŨz<á¨,%ĩÎņôđ€¤oÎ0�`bxžWŖ˙ģŠiĶĻ{k<uâ÷ؚŊÆÔ؋ų+yu=}GD4°ŖĢ[ûTŋĻ��āĮļzõjĩnŨzŽ2dˆš7o^ŋJ/���€ A���P¯j”ĢkhĘļĢëšøäĘš× ��ÔGŦ(���Ę���@�‚2���€  ��� (���Ę���@�‚2���€  ��� (���Ę���@�‚2���€  ��� (���Ę���@�‚2���€  ��� (���Ę���@€eĮqTZZē7Æ���ÔZiiŠĮŠu;vMČÉÉŅäɓ•““ŖŒŒŒZ����¨+eeeŠFŖęŨģw­ÛĒqPvGŊ{÷V4U,Ģõ����€ē’­N:ÕɊ25Ę���@€¯(Įb1­]ģVŲŲŲĘÍ͕įy{c\���@†QžU jŊĒ\ãåh4ĒėėlE"B2���öžį)‰(;;[Ņh´ÖíÕ8(Įbą:Šų����öĮqęä^ē=ĒQf%���ûĒēĘĒÜĖ��� (���Ę���@�‚2���€  ��� (���Ę���@�‚2���€  ��� (���Ę���@�‚2���€  ��� (���Ę���@�‚2���€  ��� (���Ę���@€˙™ |ōķa ũÄŽÖö;'Ø:ķåpā~ãšĘų›ŗWƸŗņ‹ëޝēlkoŠĨ¤ŒĄŽĻ,7~ęĄ��€˙qõ6(/Xo(c¨ŖŸ ´ĩņË#\ ëŸ,ũôtK% ˙į^-Ōzû’D÷š'&~gjîZ%��ĀŪPoƒōĶĶ-Ũ)­Š+M-X_ˇaąmާŽx’¤„+Ũ6>T”ķHĮ”ŽĶūöÔC“lÍ[Wo?B��€}ZŊLYą”ôŌ[˙×#Ĩ“ÛĨõĖ ĢŌv7-Ũ>!¤ƒrÔúaGũĖŽŅöíĨņ”ÔėGe)ŠËcŽî˙ÂŽRz1ešĄ~ΆÕėG˙ŅĩoW„ę÷™:č!GŖ M?"ĸFÔ˙š°–mĒö#įY:ō?åŨį¨Ũ#Ž~˙-ĪÛũ x!Ŧ‹L]=6¤ŗūë¯Ē¯Ü,]<2ŦÖûįuÎ+a-Üü%bÎCüŨŅķüš[žŲĐ 7ÂjķGų÷ûĮEũc˒~9ĨyĻNy!Ŧî˙ލëã}´$øō9~DDwN¨<§ĪδtĐCŽRiiÖjC^Ģéßø ŖKF†ĩž4ø<{ č“+ÚÚš4#–ō?ËN˙Œ¨É}Žú<Uy\“–ųsßä>ŋ¯Ÿ icŲîį�� ^åŅ…–"–Ô¯MZwMéĨŲļ⩊íĪδôß9–ŪŊ4Ž…7ÆdŌ´UfĩˇoąĨIWÅ%Is¯éöcR•ļ¯)‘N{)ĸs:ģZzsLī]šĐäåĻ$9ļ´1&}ēÔŌĮWÄ5LCz`’üžÛhčōŅ!ŨÚ'ĨuˇĮ4vp\OΰõęÜĘÁ?Č{—%T) ?3Š1ƒũd~áŲ†§Ų×Å4ûē˜„<]đZ¸Jđ^šY:į•°~|RįâĘķ¤ķ^ +×ņ4į瘊nŠŠ ĶĶåŖÃåį!IOĩõę Íē6Žs:šúÍûĄĀąß9Ĩ1 *ŸÃčBKįwqeŌE¯‡Õ9ßSŅÍ1Íŧ&Ļ›Ĩģ? nkw~÷AH“–™šxyBĢo‹éĒž)]đZXÅÛÂđeŖÂ:¯ŗĢUˇú}mŽöŲžõ��ūˇÔË üÔtKƒMÉ2ĨĶ;øecwfŖ -wˆĢ.žÂ–tkŸ”2vXāÜŨöęzå[ÍzúU/WaKj“ëéĻŪ)Ŋ9ß‹aHŠ´tķŅ)™†d›Ō1-͚ŋ­T¤uާEŋŽéÂ.ŽLCęRāéčiM[YķeúJCĶVúk˙¤˛"RVDÚ/Ĩ… Íũ˙öî;>Š:˙ãøkfgKBIBB‹HīMD=PAņŠ]î,XOl§¨įũôÔ;ËéŲõėØ+‚éŠŌ HPzoĄ%Ûfæ÷Į„2i¤ø~>yđ`ggö;ewßķĪ|w}a¯ōŽ8œũ^˜sÛŲ\ßÅā앺×üûÄÕCP-÷õJđíJƒ_6ųŗ:Ü&=ŋ3ũø& 78>ŊßgˇĩYļÅā§5ی9Q˜¸Ød`;À)—Įxā„ jWƒ3Ú8ž'*åą63ĀĐn ÔtąL¸ŦŗMÃ4—įp\؃´ˆK0āŊÖGįÅy¤Oĸü…‹ˆˆČŪnÄÃ}kūƒ)ËMžęį…P�ÎmoķŌÎiįŋ•[ NlZXGl™Đ8Ŗ0Ņ•7Ŋĸį´Ž]|žÖY.›ō`[ŦđąCk>'„hŌ ĻīĪ 0lĻÅÚíĻá…ĘCjÚģՖ!hVøX“ īD`iŽA0�.péĮ!Öí€ö*ė˙uŗ‰ãBŊGJŽŠą$Į U–[r=,°]¯†;˛ËQ”]Ž9ÔaÄü�ę%ųtA€CjētiāmķĢLúÆbŅ&/į& ~Ęo˙ÕÛ`{Î~¯ä KrŧíųĀ nü"ČĶßZœØÔæÜv6‡×¯ük‰ˆˆČĪ”_úÁkrĪWÏ%¯ŽvŅ&ƒæĩ\b6Ŋ ;%‹dĪōĻī‰=Ŧą"ËÛõĩv63ĀŊ_ųāÜ8={!rāûU;ЇëB, Á€j á<ų­ÅŽ^XŽX^ Ū24Zæ˛J[?į´ĩyv†Å?ŽOōŅÜ�ķOb~Ų`pÎ{!îī•āĘ#ã„đÔô�/üPąCąhvJ~ÅÄAqē6đŋÁōŠ#lÎlc3zQ€Īšôx%Ėũ'$ zĶEDDDJs@•^D“đæŦ�˙>1Éô+b? ŽŅŽŽË+ų7õe×pYšS˜ębI¯Įu§ōĻWTĶ —šëŒb5Ās×dD 3Ĩüų§.7éÖĐ)ÉI~\Ŋ{#x4ÍpŲ‡eEn\¸Ņ á@ķL¯Ą�ŧ{NœgN‰ķ-fæ—F4¯åˇ)6zˆãRėĻÃĘ:Ģ­ÍĸM?Ž6ŋØd`{/˜ū°Ú$hÂĩōĘU�ž+ŖÔ$byû}§ĸmĘL…ŒˆwcbQE÷íÚ퐕 v´ys@‚{{% NļDDDDĘr@åįH8pYį$ŌŨbî”äY úļpø`n€WäDáīŠŖōĻ•šßk9gYŦœāŒÖ6̎<9=@† ›jqņaÉ õž6Jw™ŋÁ`SlȅFŠ•̎W, Ļ]æ­÷ÖĄS=—6Y.wŒŗØƒMypį„ ‡×sé˜?ԝWîqJK‡‹:Ú ú8D^:Ôu9æP‡[ÆYŗŨë˙ĮD‹^ ‘Ü͑đ˛RáøÆˇ Ō&ËĨM~‰JŖt—ŧ$ü´Æ 7˙›āˇÍ&›ō â>ŧÍ2&/ķJClŸfQtë >2ÉÓ-f­5°>ĪäđįÂĖ[o°h“Aë§"Œ^d’tŧ˛–ŸV4ĢĨŌ )ß”_ü!ĀšílĒûT'\ĐŅf[ Fūbō×.IÎjcsÚÛaÚ?ĄVūØĮ;C_yĶ‹jPĶ唖gŊ⟓Š–Đ0Íåũsã|0Īâ˙D8ũígˇĩš¯W˛ä‚|\yD’ĩ\Z=ĄûËaŽmčpß ž^jr؈ōGf¸ŦŗÍcĶ,úŊÆ0āÁqv$ Ú>Ąķs"—áįÅ|į}°w‚¤CĮ{¯ķڙ R‚Đá™M0sÉČ âX{p„œÛÎfŌŌÂŪdđj—¯>ĘĻߛaÚ<aqŽÁÛbT ē´:\bw—dkĖ õ“aŽ5ĖŠ-mRƒtŧ¸|{ˇ$§ˇvč˙V˜:Gxhr7ΊĶĻļKķZ.ĪšāöqA˛ŒĐá™qĮā‰žûĮƈˆˆČūÍpŨŠŒÚ[hɒ%ÔĢWooĩGDDDDd­Yŗ†ÆīÖŧƒ&;;ûĀęQųŊ((‹ˆˆˆˆøPPņĄ ,""""âCAYDDDD悲ˆˆˆˆˆe Ę""""">”EDDDD|((‹ˆˆˆˆøPPņĄ ,""""âCAYDDDD悲ˆˆˆˆˆe Ę""""">”EDDDD|((‹ˆˆˆˆøPPņĄ ,""""âCAYDDDD悲ˆˆˆˆˆJåH$BnnîŪh‹ˆˆˆˆČËÍÍ%‰ėņrŦĘΐžžÎ´iĶHOO'%%e """"RUōōōČÉÉĄk׎{ŧŦJåH$B׎]ÉÉÉ!îqDDDDDĒJZZ­[ˇŽ’eÕ(‹ˆˆˆˆø¨tr4eŨēu¤ĨĨ‘‘‘ëē{Ŗ]"""""•bFAV­S§Î÷*WēG9''‡´´4Âá°B˛ˆˆˆˆė7\×%“––FNNÎ/¯ŌA9VI͇ˆˆˆˆČŪ‰DĒä^ēŨĒQVO˛ˆˆˆˆė¯Ē*Ģęf> Ę""""">”EDDDD|((‹ˆˆˆˆøPPņĄ ,""""âCAYDDDD悲ˆˆˆˆˆeōui?�� �IDAT Ę""""">”EDDDD|((‹ˆˆˆˆøPPņĄ ,""""âCAYDDDD悲ˆˆˆˆˆe Ę""""">”R“–š¤ÜŲ+Ëî3,Ä}_Yģ5o4 )÷E˜žÂ� Į+a™˛{ËúŊ\ōQ›F÷u3ö #更:ĖÖØžn‰ˆˆČŪˇ'G<fîzŖā˙ še¸\ud’ˏ°øhŽÉ……ØvG+˙T éĀų„XĩÍāķ c¤ågČś ūũÅ¸ßlȅÚÕāøF6ˇwOŌŧ–[lyĨųéĒ­˛ÜŨ^§¯—š<:Õbú “ŧ$4¨érz+›Ûē%ŠŪíÅJxcV€ŸÖ˜|{Ĩ— w=ū�ęU‡cu¸¯W‚&å3gļqxk@€m1xtĒÅGķ,Í1¨†Nõn<:I¯&ŽīüŊ^ 1uEÉsÜî Æ\â-wŅ&ƒF™ēܤF.9,É==“˜¸.<8ŲbØOVo78¤†Ëemn<:Y°ŦŌæ?ŊĩÃį nü"ČK§'*š5EDD,\P¸öO6Cēx_ŌšIƒņŋ™Ü8:Hj.č`—xžãÂå#‚,Í1øââ<k­AŸaaZdē<Õ/NŖt—e[ ž˜fŅũå0_*€į\#`”X<Ų5v?$›āęOƒœÛÎæũsã¤E\f­5y`’ÅØßLúsŒđš—|Iūų•Åß{$‰ŲE? ƒ%9wO°8ãß]#(|Žß1“ôŽ—íqčõZ˜ š÷ôLpdļCNÔāŨŲúŋâåĶ l_ōx~÷œ8ądņĮ.ü(Dīf^°ÎM@ŋ7BœŅÚæŠ~ Vl5üIŖ˛NkíđÄt‹įgXŧpZœÖ™“—¸bdÆégļqʝ˙ŽîI:>æÖc“´ŪƒD‘ũŨÁĒ]¤íüŸKËL›ŠËM>œđ Ęũ<ČĖĩ&Ŗ/ŽQ+Ĩđņ!Ŗ‚´Ėr™piŦ įšmm—ŪMã\6"Čėu&­˛ —×0Í-x^UȉÂÍŖƒ\{T’‡ú&Ÿöulz5ąšbdˆ…› Ú×qYĩnbĘ2¯‡×wx°w‚–™^Pų~•ÁõŸ{Īo[ÛáâŽÅˇÃŠ­ˇŒ 2}…Éö8tkčđØÉ §ûĨ9CžōÕ“ZŠpqĮ$w÷Hbä‡>ۅ+Fųh^€ôÜĐ%Á_ģz¯9sÁmcƒü´Æ$`Âņī'+ĩümōâ÷žaąl‹AVĒËuJríŸŧåŪ9ŪbIŽAĢL—Q‹ŦŨnĐŖąÍ §%°LXŋū2"Ä7ËLĻš<vr‚ī‡xįė8'4uˆ&áî‰AFÎ7ؐkĐ2Ķåūô,ĨįvÔB“-QƒíŠoË]ŋ5]žëŸā°gÃĖ^kĐ9ģp›–uĖ<2Ų[ŸŸŽŠrHÍÂås¨Cv —yë}ÎĘđŽzõÎė�ÛãCēzĮĐûsTQpL5Ép™{]a­DĶ ‡—OŦ÷Ā4›§žĩ˜ĩÖäĖ6Nšķ7JwéŨĖá…ī-ūs’z•EDäāuĐÔ(§ÁöÉ;ˇĩør‰ÉįƨS$`ŦŲSW˜ÜrtĸD ˜đę™ ´-ēĢŌØ_ėˆÃĐîÉĶęU‡O.ˆĶžŽēÎũ Œe¸Ėē&ĘŦkĸ¤]Îy/„ëzë}Ū!ē5rXqS”OKđü÷…į@Ž Ū ‘qųųš(K†DŠSÍåŌáūĨ;Ÿ_ˇšËüëŖ||^Œa3-ž›QØUúú,‹~-l–Ũåön îdõ6oہī‡hSÛeɍQ~ē*ĘĘ­p÷„ōk|?žgr÷Ä Īœ’`Ũ­Q^=#ÎŊ_û̎ƒ"|ą(@ķL—Š—Į˜~E”Q Œ˜īĩë†/BlÁŧëĸ|zAœG&[ėˆC0ŋŲwŽ2ešÉ¸KãŦš5Ęe“œķ^ˆMyū홰8@÷FNÁüeŲy %˙pëgøü�ƒ:ŲEBrĄÛē%ųŋž%‹]íˆÃícƒ<Ō'QĐÎÉËLē6p¸kŧEû§Ãôx%Ėŗ Wĸ+§ $Û|žĀdūƒ>ų=ŌåÍp\C›ņŋ4"""žøo:Į…o–™ Ÿ _‹âÁöūIOˇ¸­[’ú5ŠĪˇxŗˇęíęTüŌqŊ‡#d=XüoĀģĨ×.—įˇÍŲ5!#Ĩėįũ°ĘāûUœ fj†áž^Il4˜ŗŪāە&+ļÜzL‚°-2].9Ŧ0d}ˇŌ`ö:ƒŸ˜ zĒ…āž^ ž]iđˆ’ÁîûUŪrī할N5h_ĮåõŗâV¯p[ĶĀæĖ6ÕBpi'ۅ…›L Ļ\ãD,¯÷ķŒ6߯.˙P{å'‹‹KŌĨƒa@—.įw°6Ķ iP;Õ-¸jPģ´­í0ƒAԁĪ˜ÜĐ%IŊęphšË­Į&ŲŲbÛņĘ\†vKĐ Ļ×Ë{Yg›†i.ÎõOÂsÖ´¯ãßÛ\ÔēpĮø Ķ\:×/˙ų;ũļŲëũßĪͰh^Ë)Ö+žr›ÁđųęVwyAœË;'šîŗ ŸūR|Ü4:HÍE¸úŗ/ŸįčC ĪߥŽË/ {÷\RDDdŸ: K/jņÔˇ^Ķc6„pÍQ…7ķíôå“ĄŨ’Ü2&ČŲmk— ÅÉJä”)——Ŧ7Mąüƒöģŗ\6ĸ°uÖ51šføŧ~‚Æâƒ!Š\î÷.‡‡^‰D^Ō 5Xü’üÎ~Ũlâ¸Pī‘’Ŗ`,É1J܈¸8Į¤Zb=đĮZ|C]~(�ÃŅ`Æ*“‡žąX´É Vš ¨_:îE› ÆüjņÄôâ‡åQ‡Î{hZņåD,ČKxeq›‚›éĀģ)n§ÕÛŧšāŗß+ybŗ$ĮŋxSžAF¤dģ‹nūúukč0âüx‰ŪįŦKnķ[MrįqI *wüí*aÃãĶ,žī/ö¸ëzë~}īājša3ešÉk3-NmUøÜێMpéaIž\āō‘!^=#NßN…æĪLõļˆ\Jœ„Šˆˆ,Č ü—Î6×˙ÉKea ŠáÔÎ5ęĸ8ËĢĪđnˆ¯˙+¨“m‘é%”™k‹×!ī”°)zšfTŧFųäæ6͝(LA j– \-j9ŦŨá…8ŋ°á׆ĸ\bIīo×Õ/Ā"–ǎ V¨í†áõԗÅ,ĨÂā— įŧâū^ Ž<2N(�OMđÂåj)ÜĶ3Éߎ-Ŋä ´Â7ŋŊEo¤+ÚÆ”üs–‰ƒâtmPņtęw\=ūįxĮÖ]Įųߨæwrĩ3dļČt™šÆüo@Ũuv5~ąI›_ŸēÕŨeHÍjí|­ĸĪķž{XŊ$ŋn6xlĒEßņ Í_ņ‘×Yz‘viVËûkPĶ?$CaŨč“ũÔŠæ2đũņüL’• =9üëk‹ŧ]îG˛8įŊ÷|šûįi¯Ŧcį_Č'đön搆L,Yŋģ~tx&˘_Mšf¸l‹Ã˛-…+ēpŖAæ™.Ų5\v$ŧŪŊæo(ÜĩÍkšÄmŠ•Y8.,ßâŋášĻ;ä%‹ŋŪØ_MŪ]~ąîĢM‚Ļ72ÄÎuūnUÅŗæ™.?¯-ŪĻ•[ŠĐåũŦj^¨\ŧšpūYk _732"”XūŌRz“jĨ¸lĘ+9ŊčņwbS‡!]“\>2ČŸķ–™.­˛Š˙í<YĐÖæYl,ųLąč3Ŧė˛žķœØÔ.q2ux}—V›'ā/ōoÜ<í­O[|Ļp đ¤ ŧų6äzOŽČ š"""Ē2(WVØō†ÔZēÅäÚĪ Céc''XģŨ ÛËaFÎ7™ˇŪ`˝&§žböz“K;Ohŋm6øuSÉ?ŋ€T5Âđxßoūāœ÷BLZj2gÁÛ?8ūÕ0 Ķ\z6včTĪĨM–Ëã,ļÅ`SÜ9!Čáõ\:ÖuéŌĀ!#˙úÚ kß­4xŗČÍWęz#)Ü2&Țí^ŠÂ?&ZœđZČ÷Ōįl—Nõ\ndÅVƒ™k Žų,XęMoE5JwÉKÂOk rđŋ~Ûl˛)Ī(8I)Íā#’Œü%Āđy& Ûž¯įĢá‚垄ĐĢ‰Ã“ßZlʃ[āąŠÅOt™äáÉŗÖØ ŸgrøsáRG—hWĮeÎúōß"w—¤f†|Qš%Ō5I›Ú.Įŋæ3ĖYg0m…É Ŗ‚<0ÉâĻŖËž™īģ•&Í|Ęy.î˜dŨƒĄã,VlņÆt~N€Aŧåuiāđīo‚ŒZh˛b |ō‹É°™NkeWh~€ŲëŧŅG*rŖŖˆˆČę€,ŊØõĒÃ;bô~=LĢ,—[ŽņBĘäËbÜ?ÉbČ!6äz—ĸOnîđâiŅŖöŦ˙¯<Ô;QPĪYYÛÛÔ¯áōđd‹ī‡ˆ&ĄQšË%‡Ų éš,"ŒsĶč mŸŽ0ā¸F6ÃĪķęESƒŪôFiüßë:Üq\’K‡I:^Īúkg&¸itĪD°L8*ÛaäņRKI>ãĒOCt|&LF \Ô1Éā#Ë_Įcu¸ú(›~o† ā‚IŪãÄaaÚ?fÖ5Ĩ˙¤[Ī&˙é“āŽ AūüąAũ.ƒ´šŦsÅļíSũ\>2HË'"4ĢåōߓŒZ*(_¸Ŋ[’íqƒūo…Ų÷z{ß8+NŸÚu€^MlŽü$T° K Ā˙úĮéņJ˜S[:-%5c.Žņđd‹'ŋĩ¸uŦAZŽ:Äaė%ąbĩŲ~Öl7¨[Ŋäs2R`äqnäŲuĒyCåÔÜ;+úÛąŪ0׏ ąn;RĶ寪“\}”]Ąų&- pBĶ=ģQDDdg¸Ž[ū]VE,Y˛„zõęí­öˆė6×Í˙‰ėüŽŨ[ Z<fÎĩū7R–'é@ģ§Ãüãø¤īøÜTˡ´&Ė´ËcĨždˆˆˆėkkÖŦĄqãÆģ5īāÁƒÉÎÎūc”^ČÃå#ƒô{3ÄĒ­ŪšÜķĨEÛÚnŠ?ĒR˄ŋ÷HōĐ7VÁˆ|mqv[[!YDDz ĘrĐx°w‚ė.Gū/Bë'#ŦŪfđրx™#G”įĸŽļWĘ2žrõĮĢO~1™´Ô䱓õ‹|""rđûÃÔ(ËÁ/+ې�Ē6Ä ;KĄp§ū­úˇ*ŊÎ\DDä`ĸe Ę""""">”EDDDD|((‹ˆˆˆˆøPPņĄ ,""""âCAYDDDD悲ˆˆˆˆˆe Ę""""">”EDDDD|((‹ˆˆˆˆøPPņĄ ,""""âCAYDDDD悲ˆˆˆˆˆe Ę""""">”EDDDD|T:(G"rss÷F[DDDDDöXnn.‘Hd—cUv†ôôtĻM›Fzz:))){Ü�‘Ē’——GNN]ģvŨãeU:(G"ēvíJNNŅht """"RUŌŌŌhŨēu•ô(ĢFYDDDDÄGĨ{”ŖŅ(ëÖ­#--ŒŒ \×ŨíŠÃ0 ˛j:uö¸WšŌ=Ę999¤ĨĨ‡’EDDDdŋáē.áp˜´´4rrröxy•ĘŅh´Jj>DDDDDö†H$R%÷ŌíV˛z’EDDDdUUYU7ķ‰ˆˆˆˆøPPņĄ ,""""âCAYDDDD悲ˆˆˆˆˆe Ę""""">”EDDDD|((‹ˆˆˆˆøPPņĄ ,""""âCAYDDDD悲ˆˆˆˆˆe Ę""""">”EDDDD|((‹ˆˆˆˆøPPņĄ ,""""âã”ŖIHš/ÂôÆžnĘÚą/…ydŠĩW_ã…īũb˜¸ŊW_怰4Į Ņc~Zķû÷)÷E˜´ôņķų“ôEöękĖZëíËśõp˘ ~Ü×Í‘ƒÜŪM,Uė‚C ŸWúīŖ'%¸ú¨ũ?=û]€ŋ ˛äÆh…žŋpŖÁũZ:{šeUo×ļ?Ų7Níj{īõ~Ų`pׄ _˙%F(�7~䚁bĪI Cû:Gc¯]Í5šđŖī2Īlãđր8�ÛbđčT‹æXšcP# ę9Üxt’^Mü÷O¯WCL]Qō¸íŪĐaĖ%Ūrm2¸aTŠËMj„á’Ã’ÜĶ3‰™Ÿ‰Ę›ūʏŸfąl‹Aũ.W™äú.6Ō]8!ÁE†ø~pŒđ~ōޝč~؟ŊôC€ímLJ K‡O/Œīĩ׊ÛpņG!î홠I†ë{ŧF,h–á2øČ$WQø9xÄķaæŽ÷×3ŽŒŅŽŽ Ā×KMj1}…I^Ôt9Ŋ•ÍmŨ’Ô—œwÂb“SŪôΌž8ÎqÖī€!_ųf™ˇ¯{4˛yŧo‚ŒīyåM_°Ņ`č¸ ĶV˜8.œÕÆæ‘> R‚p¯]_ ķÚO.í´˙î‹Či?ųÚŦ˜ĮOŽķ¯ŧü/—˜\õi™WĮįßÖJq÷aëöžįXšõĀ ĘģļŊsöŪŨG˙úÚâôV6-3 _įČl—ˇÄ ūŋ1ĪāųgŧbúąbĪsmŒĀ.™"5čM߇^¯…ؐkpOĪGf;äD Ū ˙[!^>=ÁĀö%ŋ°ß='N,Yüą ? Ņ왎MrĐīg´ļyĒ_‚[ ä¨l‡ĶZ;åN˙xžéĪū Ž=Ôaōr“+Fi^ËĨo ‡ķ;Ø<øÅ°™bj_ĢĖ~ŲßÄm¸uLSZzA93ē5Ü{īĪ7fH:pQĮâûī—ëc˜xÛ)7i0f‘w,¤áÂ"ĪŊú(›ēėrŲ5ŧy‡Í põ§AÎmgķūšqŌ".ŗÖš<0Ébėo&ũšäIÖą‡:,ŧžøÉūô•&7ŅąŽˇ-}Â4ā‹‹ŧũ|è W}âŨsâåNĪKĀéo‡8*ÛáëŋÄØœ×~âļqAžč› lÁߎMr÷D‹ :Ø‹Ÿw‰ˆT‰*({=‘Ū{íTī߆i.‘üĩˆ%ač8‹áķl‰´ĖtšŊ[‚ž-J~mA¯WÃtoäđØÉ ĸI¸{b‘ķM6äzķŪB‚žųŊ„ĮŊæ´V6ŗÖĖYo˛%fđ×.Iūš˙å3ešÉíã‚Ė]gļā„&Å{FĘRÖ˛˙ų•ÅC“Ŋ|ûį�n‹Ōũå0§ļ´1?@jĐeÜĨqVm…[Ɔ˜˛Ėëš<ŧžÃƒŊaãå<6ÕbÅVƒĖT¸¤c’ŋ÷HbĐõ…0§´´™ĩÖdŪzƒ /^r˜÷E[ŪļÉKĀĐņA۟ĀuĄw3›GOJđĖw%Û~ėKaÎlcsË1Ūv{í§�OL÷ÚuH — ;Úܜ?íÎņKr ZeēŒZ`ívƒm^8-åsaac.|07Ā—ƒbÅš. Ō ˙ß ÍåéS|ą(…Zfˆ†iŽī˛™ėĩį§ĢĸRsįŖ.Įę]Ãe^)Ŋvģö ŋ3;Āö¸ÁŽŪëž?'@õ<ÔĮû“ —š×ŽCyĶ×í0¸ķ¸$įå‡ôķŌl^ú!Ā7ËLúļp0 ¸ü›˙}o•”7åÁM_™¸$ĀŽ8t¨ëđčI ¯ī?å˙ëwĀUŸ†øf™IíT—˜đ߈ETtŋŧø}€ggxŊåYŠ.×ũ)Éĩ˛ŊČäė÷B,ģ1Zė}Öō‰07tõž3}…×#9oƒIͰK¯&÷IP=ŋ#ô™o<>ŨbCŽAįúôIpX=o?œāßßXüļŲ =įˇOōĪ^Iâ6d˙'B^Ú=áļnIÚ×v¸āÃ9CŊāXÖûqô"“+F†xŧoœĮĻYŗ MsyõŒ‡ĻųŸüo†Ååmģ›Ų5Н.-3mž]iōŅŧ@ą \3äŌ(ŨŲ9Q¸ytkJc�íëØôjbsÅČ 7´¯S|ū°Eąũˇá¯ƒ<Ø;Az~Ũd0aąÉėkb4ĢåÍûPŸĮŧfÕVČK–=}É“Ĩ9͝HP3 dxĶO;ÄŋOL„sÚŲÜ<:Čg LÎhsāu$ˆČūī * ŧ{ĸÅØ_Œē(βŖœÕÆæŧB,É)`’\øaˆ™.˙9ÉûBŋs|)ËMÆ]gÍ­Q.ëœäœ÷BlĘķæ \žūÖbh÷$ߎņÜŠqîgąz›7ũâB hcŗú–(?]ekĖāū¯+V?WÖ˛˙Ū#É­mu˛Ųp[´āų¯üā˙z&øp ×3sîa,ÃeÖ5Qf]%5črÎ{!\×+øëįA^8-ÁÆÛĸ|r~Œ70bžˇûCŧđŊÅŊ=Ėž6ÆkgÆšę“ ķ7Ú6wŒōí “¯ūã‡ĢĸlČ5¸~TȡíE}ļĀäæŅA;9Áę[ĸüˇo‚û'Yŧ;ÛëŠXđÅĸ�Í3]Ļ^cúQF- 0bž×ŅWKLj†+Ökm0ŨJÕ1Ÿ`P'ģHH.t[ˇ$˙×ŗdŨŽvÄáöąAé“(č›ŧˤk‡ģÆ[´:LWÂŧ1Ģp˛~å‘6ˇ[øÚŽ Ģˇ4.ŒŽkd3{ÁÚíūíēmlÅ9&ßޞâæ(‡×wš¸ČĨũōŽ˙[Æ؜ķŽ‹ōÍe1Ū›ŗ{Ũ{ģį™Ü=1Č3§$Xwk”WΈsīWAÆūjrBS‡ô|ļ°đĩž]a°j›ÁŲmmÖn‡~oz'fKoŒōÅEqĻ­0šmŦ÷žühŽÉ}“ŧ÷Åo7DérˆÃ™īxĩíŋm6¸tx[ŽI˛ūoQFžãĨ-Ū lÁ”Ëŧ•9×FųÛą%÷{YīĮˆ›Ŗ0iŠwR7÷ēĻAŠĩûrŊúäžM*v°Z&•:ŽĮūę í^r=ęU‡O.ˆ—É~žųÎ"#â\UųqIV*!ā°ē^ĮƏkĖr§'līxų–:¤†K4IÁIŠeÂą &,Qw˛ˆėUP~åG‹›IŌ$Ã%€ŋvIR¯ēËį Š¯æQAōđĘqLlĮģô8´[‚5ŊšË:Û4LsųpŽ÷l�Ŋ›Ú´Ší}¨÷hä`ģ°h“W;ˇ5iīukWƒÎ‹ķHŸō{ÕĘ[ļĶ€#˛]ú4sH‹ĀĢ ž_eđĀ ^ĪKÍ0Ü×+ɂsÖäägÔĖĶ€6ĩ]æ]+č1€“›žūqZdē|ļ PîļąxsV€›IŌŧ–KŊęđDßڔ_ūŅâœv6Į5ōz=käpf›įnķÚŠ.tđžxkWƒļĩ‚�ŋĢšëMÚÔv ęvK“›€‡&[ŦŪfĐŋUÅÅo› ÚÖŪŗ^ĢįfX4¯åôÆŦÜf0|~€ēÕ]F^įōÎIŽû,ȧŋ˜šžĢL´˜Å/͎¯ãb�sÖûĪķߓŒ8?FVĒä.ėä×ͅĮNyĮ˙ˆųŽũS’Z)ĄāŠAeøí—W~˛¸ø°$]8tiār~›a3X&œŪĘ.vâ4|~€žMęV‡wf[Ô¯îr}›PĀë‰Ō5Yp|Ŋ>ĢđøK‹xAņŸŊä& qēËĸĸœÛÎÆ4 ]—Ŗ8|ŋĒüĖōŪ†áŦßx´Wcn™^CYĮĩ å†Õ¤Ŗ™|<?Ā™m*w\gפBWŋJŗ#Ną¸ĢGá~ߘ[˛Î0 VĒWfSŪôNõŌÂ^9•íx¯ņŸ)Ļ›ō ˇUû:ŗ×ęGŲ;¨Ō‹˛ŦßÛâĐ:Ģ0€´ĖtYēĨđCôņéA†Ī3™yuŦ dcõ6¯ūôė÷JۘR´7ēčeŅ`�ä%ŊāúĀ nü"ČĶßZœØÔæÜvvÁeëŠ(mŲĨišQ¸ž‹s j„Š_m’á xŖômápaG›#žsôĄŊ›9œß>YŦg´IFņļ6¨é˛zģQîļYŸëm÷Æé…íiVË-ÖKTšÅ›7mĩÎr‹Ũ°šëĨčˆå•zøŲ5¨åķe?}ĨIփ…#ä& y-—ˇÄiU|ųEŸˇĶ­Į&šķ¸$^Ų] Ÿfņ|˙â7}šŽwCāõ]ŧpĶ4ÃfĘr“×fZœÚ*^îôlnäËÅ&Ÿ^'ĨČ ˄´lČõ̎ü}BīVy=yvūzF‹ƒĨŖëw@ˆFEĻ7¯Āū¯Č~Y´É`˝OL/ūQuÔ!ŪôsÚ؜ņNˆíq¨‚į¸ë8¯Ņ‹s Z×.ŪŽÖY.›ōŧ›2ÛåøĢ*^×ûūœ�ÃfZŦŨn`^‰Â!5Ë åŊwŪwhÍÂļE‚Múī›š^Øö+ Ē÷páö‹ŲŪIĘ­Į&ųËáÅÛųČ‹˙N+ųqŋ1˙JOrK×_ū1@Ãt—ãwy?û­‘ë>^Öô´ ;+Î5Ÿy|ēEõüßņ ^›(Vœ™â–z\‹ˆėЃ&(—Æq!n~ˆÎ[opt‡Ąã‚e ;ÅÄAqē6(= •õQ|Å6gļąŊ(Āį MzŧæūáĻ<•ũ˜WāJŖëzuÛĻĪ÷Ođˇc“|žĐdä/˜aÔEqēä¯ob—f&¯Måm›—ō*ēīĘqŊ/ü*ģ] Ÿ:Õsvώ¯œöVˆímNk]r}Ļ\^ōfžĖüzø™.3ט@É}ēsũøčô”���IDATËęÍŋØ$á‰M‹ŋnŨęnA0ŨŠY­¯UūtđBæĀ÷C$˜8(æÛ;XZĶlúŋĸk‡īŽđę}g­5čōB¸Bķī|Ũö9Ą¨Č~IąāžžIßō€î2"0zQ€ÆékˇœVÆU‚û)f{ëSÚq;lf€{ŋ ōÁšņ‚0=đ}˙*jįûqgPö;VKSÚS‹¯CĮŲƒÛģ•ÜVélsíQ%˜Đĸ–ÃÚ^‡Aũ%_#aSîrīÍą°K/vVjÉ�ë¸^đĪJuÉM”<q+:ŧ÷ʂŋz7ōUÁō­Žë•`ėT™í("RYMéEíjP#TüŌ˛ãzÅ5+ŌûúĖ) †•`Ęr“į퇧ĘL…ŒüŧËåģĨ9˙^ģ˛RŊŠ7$¸ˇW’—~ø}ÎCšf¸l‹Ã˛"=į 7$hžé’tŧ/ŸfĩŧËĐc/‰s|c‡7.üöûu—ąY—äRĶ-wÛÔÉßîŋl,Üî 67ņ•ŲîZn‰KĻs×ęôS+â˛1ˇäãá€[ĐËŨ:ËåŲS<øÅw+Kîߖ™.­˛Š˙eĨzĶ´ĩycV€KÎ÷Č‹>ÃĘR#æ8ąiÉģķ¯īōÃjˇČj/ÜhÜ|UŪtĮõB\zÄeäųqߐœtŧŅLŸ‘aVoķö÷ĩG% æũneÅ?ęTķJrŠž_æo(ūŠė—æ™n‰coåÖÂ;Ķ€ŗÚÚ|ļĀdøŧ�ũZØA´i†ËÜuFąí6wŊAF2Sŧ÷Ãü"7`ƒp˙$‹U[aęr“n ‚œtāĮÕû<(īũXY™Š°%æōŅ4Ŗp>Ų7ÎŦĩ&O~[ōŊW+Rō¸n•ßkßģ™Wâđ‰%īŠXŋ:<f˝ĨīĪ[ fŦ2č×ĸxP>"ÛecÅŪ/3V$8"Û)wzÂözõ7æze!Á�ŒZhRŋzņēæ šFÁÉŦˆHU;h‚2Āylū3Å™ –„‡'[l‰œU¤§Ã4\Ôtyŧo‚Ąã‚ü’_8øČ$Oļ˜ĩÖĀv`ø<“ß —:’AQ‹6´~*ÂčEfA ųiĩQĄōƒŠH ze sũŋ,;Õsi“årĮ8‹m1oƒ;'9ŧžKĮē.¯Ī Đão]× 4Kr š)ˇøjI€‰‹Mâļ7Åō-§äņ•ĩm nķđd‹Ÿ×ŦØˇŒōS~ogYm?¯ŊÍs|ŊÔÛnã~ķę+/îXųúVđę—į­7ËíŨ>ĄŠÃEm."ˇbeä� 隤Mm—ã_ ķŋæŦ3˜ļÂä†QA˜dqĶŅeˇûģ•fąmžĶÅ“ŦÛa0tœÅŠ-ŪMfīĪ 0¨S˛B͟ûÎ ī˙ė•dívXąÅû[ŋŖđ5æŦ3pvuJ@ĩĢyûię o?LXlōÉ/^š_Ŋ­üã?lAĪÆO~ëŨܡbĢwĸTŲŽ>ŋũ2øˆ$# 0|žW2k­AĪWà ›YxļqN[› ‹|ļ0Plxž3ZÛŦŪnđäô� Û dMõjž uJōáŧ�Ÿ/0ؘë…ä—´ČL…Fé.ķ7lĘķnĻģaTZ)^‰ xÛËÛŽ&ۊ˛Rîûą˛Úd9^}ų瞎hŨęđŸ“Ü=Á*uÜd?5Âđxßoūāœ÷BLZj2gÁÛ?8ūÕ0 Ķ\z–1Žõw+ J–o5Nw9Š™Ã5Ÿz7ĪYgpãčgˇĩŠS­üéÁ�<øÅ­c‚ŦŪæ…ä{ŋ rĶ҉bWnæŦ7é°ÛUD¤"ĒŌ‹Ÿ˜āoc‚œøZˆíqƒuF_#ģfņZK€sÛyŊPƒ>ņ՟cÜŪ-Éö¸A˙ˇÂl{=‹oœ/¸yŠ,Íkš<wj‚ÛĮYŧŲĢAėŪČቾUķįw°šđÃmŸŽđãā’ŖG|80ÎMŖƒ´}:BĀđF9~ž÷ú—v˛Y’cĐ˙í0ëwx=€gĩąšĻČĨØKKōÜ ‹ŗßķFŽxņô-ō{ŋĘÛ6÷õJt‚ôyŨëĘ;ąŠÍc''ĘmûŲmmVm…ë?˛2”†˙õOpjĢŨ+îŅØakĖëų;ĸœ‘/ūŨ;Á·:.Čã}+––Sƒ0æâOļxō[‹[Į¤…á¨CÆ^+¨›-͚íuĢ—|NF Œŧ Î-c‚<;ÃĸN5—ĮNNpRs§BĶߝ`Ųƒ6O/•(úƒ&_-1i_ĮģŲrWa žė—āī‚Ü7)Čq^8-Îe#Bœôz˜ ģ ˇįįšSã ū4D‡g"ÔŠæōpŸ“–†*]ĶŊë~éŲÄá?}Ü5!ȟ?ö~Leđ‘6—u. Ä]8D,—ĩۍ‚mŪPīŸë’qߤ ™Š.ÛŲܙ_ÃÜŋ•ÃCŊüuTMy‡×wøp 7^đ•G$™˛Ė¤Õ“˛R]ūŪ#əmŧcų˛A^<-Á)-Îz7Äā#mŽoT؞ōŪ•UģtŦë2qI€Ãę•}2v^{›įøËĮ!&å˙čNE loSŋ†ËÓ-ž"šôjÎ/9ĖfH×d™Ĩkļ{÷øŊÖK§{Ûá¤×؆wCčŖ''*<ũÍqŽųĖێ™Špķ1IŽ+RÎf;đÍR“gNŲ{?ö""l†ëē•:_˛d õęÕÛ[í‘} Į+aúˇ˛wk¤‚ũÍ áA"<×ŋ]Å9ׅß sퟒûÕŽHÅí}æÕąrGuų#ywv€ģ&XĖŊ6Ļ‘ÖŦYCãÆwkŪÁƒ“}p•^ˆ íždøü� }ęˆ˙¨Ū™íũĒÛΐ‘ĪEŊaęŪúYip§¸í•fÜu\Ų=Ū""{BAY*­˛\îī•`ĐĮĄJũčÂÁjŲī—é^?+^â'ˆåĀ ĀëgÅšs|°Ä(ũQŨ5!H›Ú—vŌ]Dö•^ˆˆˆˆČAGĨ"""""{‰‚˛ˆˆˆˆˆe Ę""""">”EDDDD|((‹ˆˆˆˆøPPņĄ ,""""âCAYDDDD悲ˆˆˆˆˆe Ę""""">”EDDDD|((‹ˆˆˆˆøPPņĄ ,""""âCAYDDDD悲ˆˆˆˆˆJåH$BnnîŪh‹ˆˆˆˆČËÍÍ%‰ėņrŦĘΐžžÎ´iĶHOO'%%e """"RUōōōČÉÉĄk׎{ŧŦJåH$B׎]ÉÉÉ!îqDDDDDĒJZZ­[ˇŪ7=Ęā…åzõęíņ‹‹ˆˆˆˆė¯t3Ÿˆˆˆˆˆe Ę""""">”EDDDD|((‹ˆˆˆˆøPPņĄ ,""""âCAYDDDD悲ˆˆˆˆˆe Ę""""">”EDDDD|((‹ˆˆˆˆø°ÖmĖŲ×mŲīXu2Ķ÷uDDDDDö;*ŊņĄ ,""""âCAYDDDD悲ˆˆˆˆˆe Ę""""">”EDDDD|((‹ˆˆˆˆøPPņĄ ,""""âCAYDDDD悲ˆˆˆˆˆe Ę""""">Ŧ}Ũ�‘ũŨ—_OåķąȋÆöuSöŠ”H˜~Ŋ{q|÷ŖË}î§Â=ã!'ú;4l7¤Gā'ōWĨ\ Ę""""eøōëŠ|đÉįûē){M^4ÆŸ|PfXūīT¸ņS؟7EN4ŋėyXV酈ˆˆH>;ņ É;|6vb™ĪšgûuH.`äˇu)(‹ˆˆˆ”!ŨOk ö‚ōÖ5į�Ē<Њļ*(‹ˆˆˆˆøPPņĄ ,""""âCAYDDDD悲ˆˆˆˆˆe Ę""""û@›VÍyėģ÷u3¤ úe>‘*‡8ņøîŪą™éDcq–¯\Åø/ŋá—EŋíëæũîNh 7uƒŽ‡BŦÜ ŸĖ‡}̎{ĪÉJ…õw@īW`ܯ…ķŪÚ nī=_„Ykßv+(‹ˆˆˆTĄP(ÄM×^AõjÕødÔX–,_IjJ„#īČĩW\ʰˇ?dÆOŗöu37Wũ žé¯ū§ŋ›rĄM¸ŋ7 hG?Ëļ”>īĮÉ/˙ū!”EDDDĒTŸžŨÉĖČāŸ?Ζ­Û ˙mÉ2ļlŲJŊēĩ}įKĢYƒŗO?…f°lå*>údëÖoÄ4MÎ<õd:֞””7nbÔ¸/ųaæl�ŌĶjröé§Đ¤ŅĄ„C!-^ÂûÃ?eãæœŊŋÂe¨[ ë÷} w/||îzĩ�~žū{ œõVÉy/: < N~ž_õ{ĩ¸8Õ(‹ˆˆˆTĄNÛ1õģī‹…äFO˜Ä§ŖĮûĖWē�Ûļš÷ĄĮš÷ĄĮ‰Įã\9čB�ēՙv­[ōđĪqķ˙äãĪÆpŅšgRŖFu�ŽúķEäæær΃˙åŽ{dÛļí ēđÜŊˇ’tz0 ø÷¤’ĶrđĐ×pj+H ŸvFxú4č˙:L]ūû´Õ‚˛ˆˆˆHĒY‹UkÖUjžCÉĻaƒCøøŗŅDc1ĸą#>KŨÚYd×ĢKjJÛąÉ͋âē.sæ/āæģîcÛļí4:´Ųõëōҧ_Į‰'Œø| 6 níŦŊ´–Ķ" oöBąŸYk €Æé…õiī „ásaŌ’ßĨ™ĨR酈ˆˆHr]—€YšžČŦĖ ĸą9[ļ<ļqĶf’ļM­ŒtĻ}÷:´ãūŋßĘ/ cÎü|˙ã,â‰u˛21 ƒ‡īŊŗÄr3ke°vũ†=^§Ũåē^riŒüin‘Į.< n˙é ãÁ3÷jˤ ,"""R…ÖmØHƒCęûN3ō“ĄëēžĶK<°Ŧ�ÛwäōȓĪ͏aÚĩnIīãģqR¯ãxđņg‰'$m›‡ŪSUĢPe歇!éP3 [c%§ˇ¯ą¤×ë\=ä=vŲpøb!ėˆÃ‹gÂo›aʲߡŨ;ŠôBDDD¤ ũ0s6]ŽčDÚ™%ĻõîŲŽúK‰Į×oÜD$&#=­āąÚY™ÖoØD($ ądŲ >3}šH$L›–ÍYŋ~#V PŦĖÂ0ŒbËÚWF΃„C{”œąŧ!ã†Ī…h˛đņ¤ãũûڏđĖtøøÂâĨŋ'e‘*4ūĢÉŦYģž›¯ģ’nGEũēuhŌčPÎ=ķTúžx<cŋüēÄ<+VŽfÍÚuœqĘI„Ã!RSR8㔓Xžr+W¯áŧŗOãŌķPŖz5 àaƒlBĄ6nb՚ĩüēx)NīGÕ ZũO>‘¯šŗ’% Umc\˙ ÜÖ^>ËGšE&œŌĻ]•–?/}ū[ŋ€ŸVÃ'{ã/˙ŪTz!"""R…‰˙}î%úô<Ž^ŨaĀiũˆFŖ,Yļ‚˙>ûK—¯ôīšWŪäœ3Ná˙nŋ ×qXđëbž}éu�>ņ9ĪėĪ]ˇū•`0ČÆM›yø§,[፛öę[īsΧōŋ Áq–,[ÁĶ/ž†ã8ŋÛz—æåŧq’˙ÖF] Ղ°b+Œ˜÷ rKŸ×vaā;đíÕđÎyŪ(NÅĒVĒ„áV´HFDDDäčúŋũą~fúɇî-ušq×*āŪˇ{ķ <˜ėėl•^ˆˆˆˆˆøQPņĄ ,""""âCAYDDDD悲ˆˆˆˆˆe Ę""""">”EDDDD|((‹ˆˆˆˆøPP)C$Ų×MøŨ”ˇŽéáߊ!U *ÚĒ ,"""R†Sz÷w_ˇâw⿝kūqâīԖ*Pmĩö|""""¯ãģ Āgc'F÷qköŽH$Â)Ŋ{Ŧki†äOžgäÄ~‡†í†ô°’‡”Ŋ*b¸ŽûG8GАÁƒ“­Ō ? Ę""""">Œûī@Ĩ"""""ģ°Úĩoˇ¯Û """"˛ßų€ˇÛÆW9nú����IENDŽB`‚������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/plugin-oidc-rar.png�������������������������������������������������0000664�0000000�0000000�00000170004�14156463140�0022356�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��Ķ��ū���ß1b;���sBITÛáOā���tEXtSoftware�mate-screenshotȖđJ�� �IDATxœėŨu\éđīlīÂŌ KHڅĸ؉"bwŖba'bqØb  v`w׊؊z‚ˆ€t,ĩ ,[ķûcŧuoAÜ;ī÷}˙á gžyæ;3ģ†gã!-,,�!„PÍÉĖĖ´ˇˇ˙ÖZš+A!€É‹BڇɋBچɋBچɋBچɋBچɋBچɋBچɋBچɋBچɋBچɋBچɋBچɋBÚöĪ'īĖ+Ė[‰?njuo˛…MÃŲreĩē=õ;mŲmÆß. F˙K,¸Î¸ųŠĘ—oŌæØŗĖ*m"U@ŋcŦ QUw…ĐŋQĨâã;Hļ>a\‰§1éP&‡>ĩÜÔĒ;I´ũ¯čÍåMŦČōĻÂÆhæF/Ųˇz~“EŪ`ęs Pá=ĨÖz_–'æ3¯0šL ķ‘ęąŋ,_˙q8†no@@QôrSLõPĖm%¯ŌáJ`Ņ-Ļŗ‘rZ E… ž` ­|‡•)@u*ĒZ­éZN[%�(I+!ĖGjŖ_ĨūÚ÷÷y:‘[BtĒĨT|šŒy䊎rŊ .ēƃδ˜ÉeGŪĐŲt˛o¯ŋ×>`<JĄÉ0ēąĸÅĻGŒë 4=Đ ØŨKÔFļđ&soīo>fúYT7y÷žĸĮdį†J4(“ÃČĶ,C. ŦĢ�€Ķīé[ŧe‹o1›XÉ�`ü9æ¸&rrĮS:�ŧČ =O§‹ĨĀŠßéĮŪŌãr‰=Ŋeĩ ŋ<cŸgĐÖv•Õ1%ˇDĶOūNWEá/wķZË=ĘŊ/é;Ÿ3æx~MĢ1åS=�_ Í#ØS=“.0G4TđYäĸ[LôŲdXO€āûŒ„<BXJœ,eĐÔ‡1Ŋ…|ãC†BŠ ĶāĀkzV11ĮSū.›XuŸŲŨIņ[2mÃC†ƒōM6mĘEfl.1ÍCŪËMšø#SL”Ę ­Ōß]p‘)U€š.™[BŒh¨HĖ'n|ĸĀ•ô3ƒĨ,:šü.ĶL‡Ė.&ö÷‘ūr—IŠËé#*˜+']`p CLĖn)w0Tö;Æîę¨Č.&ôØdpį¯ĮĢÄSƒŋÄâüëŒ+éãš*VŪe$ä2ôtUǧØö˜~;‰.Đ'ßdëēČŪåĐԏčp?ŠFûš×˜Â(•PĮÛŠ–bÅ]Ļ1—Ė“a>RŪ—"ž3Æ6Qhœü;I´ÁQŦ{cĘZīfģ[+=JWcĨúņžŖgĶ.3Ÿ¤ŌŒ¸d3™› �ņBâ^íÂ0Š\ ­vŗ}\NFʙ-å�Đu?ës!ŅĊü˜G+€§š[„ūaÕũ3˙\,}z 9^lLo!?K€Ø\ÂB—Ôe‹NĻU°áúŠvöJ_7%�4´P†÷”õĢŖ¸øák=Ŗ)ꘒ2ÜûLīXëëmŅËLšģ•�šZ)_düŠūČWŒ'X÷ąž`…ų|Ŋ3Zv‡šĒ“ėØ�i§ZJa (HXWą¯ŒË„wŲ_˙ē—)āMádDvuTœ‰­āĖ Š¯°ä“ŗ=å�Āc’Ą=dŊd‡Ū0Ūdīsiž˛}eaĪĸ2`ŌÁÃFšĸ㗔Ö@ąˇˇŦŊŊrdCš‡R$%Vw–…÷”ؐw’hę§�öŊ¤7ŗ&ˇzËBēÉo04–Ā’vōвŗątõzŠe0đkā VŊP60¤žâm6ņ$ļˇˇ,˛lÃC†D;ž1N ”†t“‰ĘzšcŌh_&‡k ´5]dGúK›Y+UĮ{ķŊ•­2ŧ§lCWMm8äyƗkĄŽŊŊ’I#cs‰2Lm.\OĄqŧÃ(ĖuÉ-ŨeŨœãšČŠØ€W™´&–J�`ĐĀŪ€LČ#zē*ŧĻ쇺Ũ­”.Æ$�4˛PžĖøį‡ČĒĻęŪķ€ú˜)I��Øö˜‘!&FœbŠĘˆđįŒ_:|īīhG#�tY ,ũĶ(gr1˙svKy=ŗ¯ēĒZ(I āOŌŽj$ŸęĄx—M\bvtøZVJ!a§OĀ z õ=ę0I‰Z]QŋĶōˆ§˜J’(”@ŋ:ßUp4üRŗD‰ųuĢN`É'3Ä�Pã*RhãéGúI€M‡MēlxžAķ(uXj™\Hkk§��=2MD�€Ÿ$ úW§Ãj(`øIf{Ĩ ķ‰Ô"bü9&�đŲdR>p€ĘJà ūü×hŸW !Ũd3ް $0Ąé×ĶîßTžū!ŖĮ!–šēķ×_iR0éå{š’Đc‘Ē“ qŧß9Ģ*JōKŲ#*†5P =Éē“Dko¯4䐒Ęt€ĐŋZu“ˇwmÅÆGŒ=ŊdL:H°å1c`]yv1d—'}IŽž‡YĨ2ā2ĄLA�)…„@Ÿ$�”ß „¸\bņmf¨ˇÔTįO˛Z)Ÿ¤ŅZÛ*§Ō<l*čĸŽéjL~CÖāKÎ:’qš„ģ5šë9Ŋ›ķ7Ÿųû^ҝސ˛�� Ž3žĻ\&Hd��)E�|ĢfCrī+�”$¤Vü/)Ŧ’RH,Ŋ͈$Ĩî:į]gDö‘ؐ#N1•¤fˇöĘø<€21Ÿ°Õ˙î9úÃōŽōA'XĘj’NÆd„¯ �Ūe.&W %ĐˆÍ%�@ãˆ4ÚķŲĀc’‡úIÅRhĩ›ŨÃĨŒ*,.—˜ÕRŽĪ_ī3ĸ~§ĢF˜4• ߟh I8S'AãxUȝŋIĄ‰Ĩrß+&�H𚐰ä“Ũ°. “2h`ĄKæ—�”8ԀūĒ›ŧŖ*Š$Đķ0‹Ë„Rô­­čWGšüŖ§Ë×W¨:ÕRŠĄ÷­­Øøq7I™!&lôH'#ōNíPLEˇL��xƒY )—X�ĐŅ^ŅŨY9ô$ëÁØ˛ĨíåĶ/3ˇ?:A†÷ŦøÅ–Åíd]°{š}Šai{ŲÂLôŲ¤_“Š_:ģ“DŗÕ'Ųœu›ŖëēĘv=g-ŊÍ ¨Ûyčąaūu†‡õŸâģž9ŲČB9ö,S"'f{ĘuYšĪ¸Â$˜}•Iõė夜y…)Đ'k›’aĪčá>2õS1ē‘bōEæä ĖÜRbMįJŊšTːlk§ÜņŒ1ÍC^×T9æ ŗLA¸+—ļ—OjĻđ=²Ⓠ�ÚØ*ԏ¨ŽŠŪ>¨­|ĮSƖĮƒFŽn¤PoįZĘ!QLk=˛°ŒPQÔŨJų<ƒÖÂF �‘¯÷?Ķå Đe‘‡úūéĪãíéúå44WŽ}Ā´ÕW6ŗ&Ā҈ė\KŲį(KŽ„Emeč_Gá{˜Åa€>‡ôqQĀĢLÚōø úé8÷đ˙‰§˜s<å -*u]IĪԈĪ;}ĩ…¯2‰M‘}0yŅO�įF?Šģ5iČ%ÜÛąÕɰâ.3¸Æ.ú/Ā{^„ĒyxĪ‹B˙.˜ŧ!¤m˜ŧ!¤m˜ŧ!¤m˜ŧ!¤m˜ŧ!¤m˜ŧ!¤m˜ŧ!¤m˜ŧ!¤m˜ŧ!¤m˜ŧ!¤m˜ŧ!¤m˜ŧ!¤m �HNNū§Ë@Ą˙6›ũĩ �puuÕV1!ô!))é;kq´!„´ “!„´ “!„´ “!„´ “!„´ “!„´ “!„´ “!„´ “!„´ “!„´ “!„´ “!„´ “!„´­ĘÉ+ cbbž>}ú¤œ}G§,~įņuUãĶgĪmÛ–‘™9|”_–ũ×.]šēaĶæÔųŊûŋE:ũ#öčåí+—Ë˙˛™0/o؊U•ėŗJBZęÉÉÉl6[OOOcy–0õ^Ė9aQfÔå} ŋ§3ǚ•}ú”¸nãĻŌR I’|]ŨysgŲ ÔĒÕk7HĨŌ%‹+ÜpÁÂÅķæÎúVˇ"‘h^`�LžčŲ˛ĩ\*“͝(•J‹‹K.˜įæęB-ųęõ„Iõë×�ŠTfaaŧryÛ6­Ģz8ÛÃvϧg,ZPáˇvž>{ŽÍf{wķĒdoÆFFËũe3ęTT˛ąĘŽđˆĶgÎ Ö� {uíâ7zdå7¯ŒĒ/B˙1UN^ąXĖåre2™úÂ|QîĨ‡GDeBk+AĢēcž~T(t:]cÛĩë7fįäH$e-<šq8‘H4fÔČĀEKôøüĀųs7†liPŋ^įN@*•N ˜˛amíÚn�đėų‹‰SĻ?E§Ķ_Įŧqp° …‰‰IöQ§NįääNš0>îCüæ­ÛƏõģuûŽT*íØąũûØØĀEK>3ÚĢkgĒ†Ø¸ž=}ē{u}÷ûû-ÛTÉ{ęÔg'§€É“’—-_š'BUvŖF wîØ�$Iöî7đķį”×oŪ¤ĨĨLž¸*xMjZZaaҌi�Pá@"‘…y}zûž9{~ĐĀūŠŠiAK–Qģčäå}ęø‘]{ö¸8;Ņé´­Ą;’“?įåįī‰û”˜´~cˆŠŠ‰P˜ˇbŲ’GŸ;ÉdΟģpŅŌđíÛ-Y�™™,k˙Ū]ËVŦ‰ōōķ}{ö°ˇŗŖNÅėYĶį.:šGũü:$hÉ26‹­ĢĢķüÅ˕˗9ØÛŠ <h�•ļ………Ū=ûøyũÆÍ¨Sg ôôø ĖËÍ͝9gž5ËKūœŧlņ"õ#ēyõ’Fûwŋŋß´y‹ĄĄaA~AĐÂųĒã=|äxAAAФ´u+ĪaCWõ҈ĐOĒĘÉ+•J•JĨBĄČÎO×åōy~8÷æË¨ôÂx[]7Ą:„Ša[›Ģ׎{wīĻžall\|üĮˆđí$Izyûî ^ŗnˍ‘………E…E@ÅĢ˙8Ēņû÷ąöTė€{Ķ&F††ŸœŽ=ž8(°´´dGøŽÅA 4ĘkܨĄíĸ ŅŸpšŧā•Ë˙>dK¨*›š7Ĩ~¸~ãfįŽTžy÷Ž[×.�ā`o÷ųsŠzŸ¯^ĮøO H$šššƒ¨UËáõ›7�đæíģÔ´´Ûļ¤gdÄÄŧ€ ÷�Q§NwíŌŠi“Æû4°ŋFÍúúúÛˇĢ]ÛÍÉŅQĄPúúôptŦå?)āC|üæ­Û§LŽWˇÎé3įöí?PˇnÍ×gdf€Žo͆ĩâââqū“V._*•ÉęÔvØŋ_^~ūˆŅc/ž=EŠ˛˛˛ōįŋw/_^§Ž[˙ž}öí?pįî]û¯7ļĮODE?~’ŸŸO§Ķׯ€_W¯ģ|á ‡Ã™5gūĢׯ?yÖĨs§‘Ç^šz=%5ĩüãDŖ}ôã§ÍÜŨũĮųĨĨĨëęęRĮëXĢÖŨû÷9hldôî÷÷?āú/ĒrōĘårĨR™œņáŌŖÃŽ‚ÚÍkwz5S” oČo߸—­‰[RbŌų —Eb‘Fō~NIąĩĩ�‚ ĖÍ͔ …X,ū˙ŅÎÖ6?ŋ 55Ëåđx<U{ş‡;I’¤ĶčiiéOŸ?_´�ŪĮÆN+œôRííl€ĮãQŅŖRZZúëęuŽŽÎåCPĩ/õ˙6jØ`įŽmRŠÔˇOīî_˙@NOΰąļ�+KK+KËKWŽV¸GĨRy"ꔭ­íÁÃG“““{đĐŪΞÍÎÎ�txŧ˛2iJjĒ­��›ÛwīÖ­[G`cŖŪXŠTΝ8súT[@&“ĨĻĻ-^ēœÁdH$n5ÎvV6�X˜[��—ÃÍËĪWo<p@ŋŅ#ã>Ä-^ÚĘŗĨH$*(,\´ô�ČËĪ ķ2ŗ˛Zˇō�Ē< åÛ6$,<bÄčą6ÖÖAķ¨fA,_ēxÉŌåE"҈aCęÖŠũĶ‚ĐI•“W&“É垔œ"YN|fI‘,ĢP’ĨĢĮmRĢŖƒi}IŠD*•†n ųuõÚׯcÔ7´Ž�ĨR™‘™innŪ¸QÃ]ģ÷úöôÉÉÉŲŧ-”z&Sj×vûœ’ķæmƒúõ�āų‹—EEEöövĢ‚×Ž_ܸQC�¸yëöŅãQÖVVTĖĨ§gPÛĄT*ŋUŋD"™6sδ€ÉõëÕU_Ū°Aũ—¯^ˇiŨ*ūãGg'Įō˛XŦ ãĮ­]ŋiíęU‘ÍąQ�––~˙ˇzúšcߔ›ˇnwķę:iÂx�(((œôëŠeTÍ"ąX$S5“Õl+|ú”ذaƒO‰‰T^ĄŪ`Ũ†MíÚ´ņhŪ �E?NKOß°vuB§ģwīkœŠōį˙[§HÅÕÅŲąV­ĶgÎõíĶËÄÄ8xår&“™”œliaņ!ūcjJ*�Ä'$��‡ÃV?">Ÿ¯Ņ>6îÄņcų|ū–mÛ/\ŧLoqq Į ŨR\\âÛˇ×.˙ĸ „ū+ū^ō*Ú6ōaōāCæŗ"W߈_ĮÚĶÕ¤Ĩ¨HL§ĶŠ!`ŸŪģöėíĐžjC77×zuëĖžģ@"‘Lô§ŖÃk×ļ͘q–/[œ_P0áĸÉ'¨ŗXŦ°Đ-Ģ×mÉd$Iō¸Üí[7?|ôH5ÂĐĻMë ›6ī‰;|ôØú!AP÷Ē ęכ=w¯¯O…õ8xøSbâÖĐ�`fjērųŌ9ķģwķęĶËw~āĸIĶ$’˛Å 51(Ŋ|}öė‹|ķöõßÚĩŨ\œ§L‘_P0cZ@ŽPXáVģ÷FŽ_Lũl` ¯T* ‹ŒŒ WūēZ__ßĐĀ��ęÔŽē#ŒēƒV7gÖôuBŒŒ ‹ ‹V._öčņcõĩ1oŪ9vĸs§Ī^ŧ`ŗŲ“'~JL Zėā`Īįëžŋp‰:‹‚Txū+ŦVÃĖSéÕĩ˜™3ĻĪâqšr…bmđĘ~}zOŸ5įåĢ×|>�LLL4ŽHŖ}aaᤀéæEEĸeK‚bbŪ†îŗ07?pčČŽŨ{LÆ ũ*SB˙ DbbĸŊŊ}å78vė˜ŊŊ=Ng°ˆ÷™ōŸŲÖq3n­JĨ’N§'''4č‡Œū]>­Yŋ‘z!¤’””ôhũ›ãŧ2™ŒKpëXzčšsėe —ËŠģÎĘŧ!„ūŸũäU( …ĸ¸¸˜ŖāXōÜD"‘ú¸*&ī˙GĮZxËPUUų3l<O$Q¯ķHĨŌââb’$‰?ˆD"õ÷' „*¯Ę÷ŧ7~öėYrr˛Æ[¯�€ ww÷Ē !„ū›ĒœŧNNNNNN?ĸ„ú?ßU†BچɋBچɋBچɋBچɋBچɋBچɋBچɋBچɋBچɋBچɋBÚVåä …111OŸ>}RÎͧOcbb„jS3œ>{nÛö°ŒĖĖáŖüj´lMÛÃvÆÆÆũíÍ9ēgßūŦgé/+~{đ¨;TWáÁzyûVķû9/]šēaͿƴŧw˙ˇ¨S§+Ųm5/ B˙IUūƜääd6›­§Wņœcršüņã'YŲcĮŒĒvmU0yĸ öF’äîŊ‘wîŪc0RŠ´{7¯ÆPĢ>ŠŽ:yzĖč‘͏Q223ÃwîZļdҎzŽûuÍZž._$­]ũĢÅ“Ą}NIYúËJ‡MĚāU|]]jųŽđˆĶgÎ Ö� {uíâ7zdUV$­^ˇĄ–ƒÃˇ.Š˙¤€*}ĮnÛ6­+͌ęļĒÕÖkäîŅÜ�”JRĄ¯ ^eiaQĨūŌ‚…‹į͝edhXŗŨ"TyUN^ąXĖårŠÉÖĘŖĶéēē:|T(t:]cíÚõŗsr$’˛Í8ŽH$3jdāĸ%z|~āüšCļ4¨_¯s§Ž�šš´dYäž�čäå}ķęĨŊúöčŪM$Ŋ‹ ũ˙qĶæ-†††ųkV¯ZģncŋžŊétúĻÍ[k9”–J\\œ}}zŒõŸØž]ۜÜ\j×o܌:uÆĐĀ@OŋpÁŧį/^n Ųâæęš“CÍĒI9v<ę}lėžŨ;Šä:cļžžž¯O�¸|åڊ_–Ž]ŋJŪ9ķ‡ ܸQÃũ@ĖÛˇ11o¯]ŋ�—Ž\9wūB§OׯąŗĩĨz~ũæíĸ….ÎNģ÷F^ŧtE…7m™<Ņŋ™{ĶcĮŖ:BM—I<h€ßč‘�PXXčŨŗßč‘ .îס7_Wwõē l6[Oŋæ×•t:mkčŽääĪyųų{"ÂŒ¯WöčąãüÆėŒØE]”¨S§srr'M÷!~ķÖm;´ōôYØÎ]ļļ‚÷ąą‹–$$|;f´W×Îę×køĐ! /•ÉdĻ&&ÎÎNiiéV––÷{��ˇîÜŨģ+œÅbm Ųbblœ+n Ųpåę5ĒÛ¤¤ä~}{׊]{AĐb==~vVö˙qvļļ—FU-ËŨŊ3ŒúyUđšÛwî<(dËļääĪ2šŧKįŽŊzúėÜ˙āá#+KĢØ¸¸ÅAq>¨Ņö­›5Ú/_\PPP*)mŨĘŗNíÚˇnߑJĨcFÜŧu›ę!„AŒ´ŠĘÉ+•J•JĨBĄ¨p-ARŠ´]ÛļW¯]טõ=66.>ūcDøv’$Ŋŧ}w†…¯Y7fÔČÂÂÂĸÂ"�xöüÅD˙qßÚ¯\&÷õéacc=qĘ´î˙ö ™ģģ˙8ŋ´´tņeĖdkčŽÅA.ÎNķAgĐķ fNŸJDģN]įĪũuõēËÎp8œYsæŋzũ:tGø’E ]]œ—¯ Vß×Õë7æĪ™E…‹Åį7:r˙A_ŸĖĖLutx,+++ÛÜÜLŖČŪžž:<^×.<|TˇNíaCīŲˇ˙æ­;Tt�5ĪŖL&‹Ž~<{Ö Õ†oŪŊkذ�4¨_oëöę}?ũøI~~>N_ŧRĩ|ũϐķæ¸šēœ=!ŋ @ĄPúúôptŦå?)āC||Ú_fP—ÉdąqƏķĢđĸ�@oߞģöNôwéĘU.—ŧrųīī߇l ĩŗ¨_¯ŪŊ|™ Fũēu‡ xúė9�čÛ§Wß>ŊŽG411nŌ¸Qôã'A æ9:ÖZúˊGĸUŨ.X¸�ŽGE5lPœßčôŒŒŠĶgE„o׸4ĒzJJKũ'�@|üĮ.;ööí÷áåĢב{" EĪŪũŧēv‰ÜčÖõË4Íģgo:]sÄLŖ}7¯Žwīß?~ä ą‘Ņģßß×­SÛÎÎvQЂã'N–!¤sļoMĢŽT*årųų‹WDb‘Æ“üsJŠ­­-�annĻT(Äbņ‡øvļļųųŠŠi\.įûķYXX˜�—Ñ”• 6$,<bÄčą6ÖÖAķ¨™™™ÖVV�āęęōessjú ‘HTPX¸hé/�—Ÿ/æeffRĶũ Ößķ.WûÕB’$N€=ûögggO1[,8txŽZt–ggk�:<^^~žúōÔÔ´•Ák&øsûŖHP›Ë]I’)0p@ŋŅ#ã>Ä-^ÚĘŗĨjyzz†5�ôęųe–e;;[jeeRUŗ —.'%'O1[ŠP”ŋ(ėíl€Į㕕•i\¯ėŦl�lÔÛ?{ūâÆÍÛ;ļm�‹ĩsמ/æÍ[÷ĻM5zNIIkŲĸ9�XYZfffÁŸ/zK—K }LŸÕ˛E —’’’‘‘9g~ �čęęĻϤęéņi4�‚ōGĄŅž° pųŌÅK–./‰F RˇÎ—ßI>„ŌŽŋ7ëģü;/æČd˛Đ­!ŋŽ^ûúuŒúr[āčņ(�P*•™™æææ5Üĩ{¯oOŸœœœÍÛB[ˇōT5æpØeee� ‹E"qųŊ$$|š0~,ŸĪ߲mû…‹—Š…ÆÆÆŲŲŲö>ÄģĒå…Īį›˜¯\Îd2“’“--,:’š–æęâüéS’ƒƒŊĒe7¯Žáģ7Ž[Íd2Ĩ2ŲîŊ‘ŊzöČ ss…ģÂŋܐŽ3N"‘pØĒδŒ kKK‚�ĨRsĒ˛×Ŧßŧjšą‘‘úō†õëŋzõēy3÷/_5R÷PquqvŦUëô™s}ûôĸ–‚„„O Ô?|ôXûvmŋĩĮã'N9Ébą�€ē(ǚĶĶ3�€ %YÁīŅō× �@-%ĶŌĶ×o ‰ßN +­üuÍæë›Š3f+IĨFˇļļ6Ÿ“�āsJŠĩĩÕwN‘ĘÜŲ3&NžÚēUK[[[{{ģõk‚ îC|­Z…… …‚FŖ}LH��#Ōh¯ŖŖÃãņBˇ†—øöíßĩKg‚ ”JĨÆChČ⁕РĄņw’— ß ×’$I ûôđŪĩgo‡öíTĢÜÜ\ëÕ­3{î‰D2ŅœŽ¯]Û6cÆMXžlq~AÁü…‹&Oœ jlbbbdd¸ō×Õúúú†åwTXX8)`ē……yQ‘hŲ’ ×1o�`Ō„ņAK–9;9—”Dų`ÎĖĶgņ¸\šBą6xå¤ ã-YæėėTT$˛WģéØŋ¯X,å7žÃáH$īî^ŪŨģm ŲŌĩK'U›6­<O9ëŨŨ+<b÷ŖčĮŲYŲVöövŖŖO>û­ŗ÷ëšĩEEĸĀ %�Đē•gĮöí&O~îtÔŦĶ/[žo˙:ąVmHAŨĖSéÕĩ õßŲ3§¯YĮfŗõøüÁT¸ÉÃGŅÖÖVTėÂeIPāáGm�� �IDATŖĮÖo ĄæŠĻĶéēēēĢ‚×4nÜH}Ûō×KŖķĨŋŦ â—Ģ� gīöíÚ,]žŌĘŌŌŲÉņĀÁÃ];wĸēũã”ö \´$0hą0/?(pūˇÎ:;[[æ‘ķíæę2söŧ2i™“ŖãŦĶF6zŦŋšš™ž>�´đhĻ~DŽ.Îęí§LŽÜp×îŊ &ƒíiPŋŪėš † ¸nÃ&ÕC¨2%!TSˆÄÄÄīL _ŪącĮėėė E…ķ°ŅéôäääAƒÕd•öū}ŦŽŽŽ@`ŗnÃĻ:ĩŨzxw˙GĘ@Z3uÆėIÆŠĩú÷HJJúN´ūq^ōåג$ųÎú.•JįĖ´˛´”ÉdͧNų§Ę@Ąīû;ÉĢP(žõŪĒAõJúû6lpėđjīHûļ†lø§K@čī¨ō›ix<žH$"žA$}˙ũ !„Ē|ĪÛ¸qãgĪž%''W8Îk``āîî^Cĩ!„ĐS•“×ÉÉÉÉÉéG”‚B˙'đŖ;!¤m˜ŧ!¤m˜ŧ!¤m˜ŧ!¤m˜ŧ!¤m˜ŧ!¤m˜ŧ!¤m˜ŧ!¤m˜ŧ!¤m˜ŧ!¤m˜ŧ!¤mUN^ĄPķôéĶ'å<}ú4&&F(ĒŸ>{nÛö°ŒĖĖáŖüj´ėĖĄ#G÷ėÛ_ƒzyûū¸ī՜:cöØãĨ+W7lÚ\™–ÛÃvÆÆÆU˛Û*5Fč§VåoĖINNfŗŲzzzŽ•Ëå?ÉČĘVÍgūs9}ö›ÍöîæUÉö$IîŪyįî=j~øîŨŧF B­zø(:ęäé1ŖGRķÃkČČĖ ßškŲ’Eßęųü…KĮŖNr8=>MđJÕ,îwîŪÛŊ7’ÃáÔr°WŸ˛w”ßx‰DĸĢĢ�"‘xÆ´�Ī–-Ēúõĩ"‘hõē ĩžuųü'PĶSVŌä‰ūŲFu**ĶX]ŊFîÍŨ@Š$ ųšāU–Uęá/UõxǤ*'¯X,æršÔdkåŅét]]>* jbDuk×oĖÎɑHĘZx4ãp8"‘hˍ‘‹–čņųķįn ŲŌ ~ŊΝ:@jjZВe‘{"� “—÷ÍĢ—zôęÛŖ{7‘Hô>6.",TĄPĖ_¸ˆĮãåį˘:ÅØØxŦ˙ÄöíÚæäæRŊ=áĖŲķ …ĸc‡vŪŨēMœ2ÍÜÜ´ĨGķwŋĮRs§÷8déâ  ĩmĶĻHTdbl<vˍ]{ö¸8;]¸x99ųŗL.īŌšc¯ž>Ī_ŧܲÅÍÕ5;'§ąÚ •ĮŽGŊŨˇ{'•ŧSgĖÖ××ķõé�—¯\[ņËŌĩë7PÉK�Lž:}úԀˆŨ{bbŪ^ģ~ƒN§m Ũ‘œü9/?OD˜*aßĮÆîŪšƒÅbŸ8%îC<5c.I’Ák֟Ž:ĘãņfΞ÷ōÕkõb–.^H͋sķÖíŖĮOxļlAí466nõē l6[Oŋæ×•ßÚ#�=vbœß˜ģ¨ËuętNNî¤ ãã>ÄoŪē­c‡öOž> ÛšËÖVđ>66pŅ’„„OcĮŒöęÚYũĘ:¤gī~ 4pwoũ¤_ßŪ))Š÷{��ˇîÜŨģ+œÅbm Ųbblœ+n Ų°aĶfęTÜē}ˇ_ßŪúúúë7†˜šš…y+–-�+ĢĒ–ĮåîŪFũŧ*xÍí;wûöîĨū¨pssŨš˙ÁÃGV–Vąqq‹ƒ#6dpãF ÷8�#G Ų˛MũB/_\PPP*)mŨʓÍfSĮÛĻuĢM›ˇäŦYŊĘČаĒĪ„4T9yĨRŠRŠü֜AHĨŌvmÛ^Ŋv]c‚ņØØ¸øøáÛI’ôōöŨŧfŨ˜Q# ‹ ‹�āŲķũĮ}kŋr™Ü×§‡õÄ)Ķ>~Lxōô™Ģ‹Ëä‰ūɟ?/ũeåÆõkō fNŸJDģN]į͙ĩekčÕKį ‚8tø(ƒÉČĖˌ:v�ēûôįä䙚š‰æĪED^}ũFęØž]íÚnršâåĢב{" EĪŪũēwķ ŨždŅBWįå+ƒÕĢēzũÆü9ŗ¨đbąXãüFGî?čëĶãcB‚™™ŠŽÅbeee›››iNo__¯k—Î6mņõéáčXËR‡øxՔbķæĖ€ŦŦl‘HäXˁZ˜›+ÔÕÕĄž{žAƒzoŪŧUOŪĢVķųüÔ´4;ģysgŠ–¯ß˛`Ū7W—ŗį/ä(Ę ÷(“Ébã>ŒįWáå€Ūž=#víč?îŌ•Ģ\./xåōßßŋŲjg+Pŋ˛Ŋ{ųJĨ˛ącF98ØGG?€ž}zõíĶëxÔIã&E?~´`žŖc­ĨŋŦxô(Zu*nŨž �6mž0š^Ũ:§ĪœÛˇ˙ĀXŋŅęWV=yKJKũ'�@|üĮ.;ööíyėx”úŖbODXäūCˇŽ_ĻŅhŪ={ĶéšckąqÔ/t7¯Žwīß?~ä ą‘Ņģßßģē8SĮļsW3ww˙q~iié4_A5āīˤT*•Ę æ �ĨR)—ËĪ_ŧ"‹4žēŸSRlmm€ ss3ĨB!‹?Ä´ŗĩÍĪ/HMMãr9ߟĪÂÂÂ�¸ޤŦėsJjl\ܧÄD� Ņh�`anN�@ˆÅbGâČÃōōķ­Ŧ,Šf=}ŧ/\¸”™•EMCkmmEmeld”+ĖĨv”’’’‘‘9g~ �čęęädffÚX[€@`­ņđrĩ_B$IŌč4�ØŗovvöÔŗÅbņC‡į˚ņã˛ŗŗ�¯ŦLĒžüáŖč‡‡l\Įáp*:Õ$ƒņ§X´ NíÚIIMŗŗĩU-OOΰąą€^=}žŗĮ —.'%'O1[ŠP”ŋ|ėíl€Į㕕•i\ŲėŦl� ö¨ōėų‹7oīØļ�X,ÖÎ]{x:ŧ˜7oŨ›6år˙tŅSRSíl� ØÜž{ū|eÕ[ō¸\j( `úŦ–-Zđx<G…H$ŌĶãS×] ”?  ]XP¸|éâ%K—‰D#† quqϚ 6$,<bÄčą6ÖÖAķžsZǤŋ3ëģ\.˙ÎK42™,tkȝĢמ~ŖžÜV 8z< �”JeFfĻššyãF wíŪëÛĶ'''gķļĐÖ­<U9vYY�ˆÄb‘H\~/vļkk+ŋŅ#ĨRiZz†ÆZ===ą¸X*•Ōéô­Ą;FŽFü1 üĀūũĻΚŖT*&OĖÉÍũœ’ĸT*i4Zff–Љ A¤Rikkkooˇ~M0�Ä}ˆ733533KMKsuqūô)ÉÁÁ^ĩŖn^]Ã#vo\ˇšÉdJe˛Ũ{#{õė‘+ææ w…ī ÚŒ3N"‘0č ęOøôô � P*+˜BTåŌå+Ÿ<Ũ˛Q}4ĀÔÔ¤¤¤D\\ŦĢŖķōå̉ƗßpđĀ={÷2h€ŊĩD $$|jØ ūáŖĮÚˇkû­=?qōČÁHjŠxęōqØę*üQ3Ą$+ø[ūĘRU ŌŌĶ×o ‰ßN @­üuÍæë›Š3f+IĨÆŠ°>}JlذÁ§ÄDÕ!|ßÜŲ3&NžÚēUKGŸĪ/(,T(4ícB�¨Ž(-#ÃÚŌRãBëččđxŧĐ­!ÅÅ%ž}ûw樁:Ū„„OƏåķų[ļmŋpņōÁ+SBßņw’— ß ×’$I ûôđŪĩgo‡öíTĢÜÜ\ëÕ­3{î‰D2ŅœŽ¯]Û6cÆMXžlq~AÁü…‹&Oœ jlbbbdd¸ō×Õúúú†åwÔŋ_ŸĀEKæ-ĘÉÍ<p@ŗfMÕ×1wöŒ‰SĻÉåōÎ;¨¯233Õ×ׯå`O%š‰ąņęĩë?§¤zw÷Ō×ׯSģv莰•ŋ,usu™9{^™´ĖÉŅŅÕÅyŌ„ņ‹–,svv**ŲĢŨôėßW,ōĪáp$‰ww/īîŨ6†léÚĨ“ĒM›Vž§ÎœíĶÛwîü… ‹Å"IŌŪŪîatôŠĶg+<JĨ20hIķfÎ�€ącFY[YMž:ũÜ騠Āų“ĻLãp8ĩŨ\ëÕ­S~[ƒ1}jĀĒ_×D„o§–Ėž9=xÍ:6›­Įį8 Â=>|mmmEÅŽęō- <|ôØú!A$I§ĶuuuW¯iܸ‘úļå¯ŦFįKYAÄ/+V@ĪŪíÛĩYē|Ĩ•ĨĨŗ“ぃ‡×¯T?sfM_ˇ!ÄČȰ¨°håōe$|ī÷ÅÎÖÖÃŖyäCCTT8ØÛ>lôXss3}}�đîîąûQôãėŦl+ Wgõ =-`räūƒģvīe0ƒôSo›Ö­&Lˇ°0/*-[ô—õ ô—ˆÄÄÄīL _ŪącĮėėė E…ķ°ŅéôäääAƒÕd5mâ”iA æ 6™™sį/<šįŸŽũpSgĖž4aœjPĄ-))é;ŅZå— är9ų]˙āŦī)#3ĶR€{Ķ&Í?] Bč˙×ßy…MĄP|ëŊ Tƒę•ôYZX¨ŋ=ĶŌÂox˙OTõ­ÍũPUžįåņx"‘ˆø‘Hôũ÷' „Ēō=oãÆŸ={–œœ\á8¯ģģ{ ՆB˙MUN^'''''§Q BũŸĀä „ļaō"„ļaō"„ļaō"„ļaō"„ļaō"„ļaō"„ļaō"„ļUö“â’Ō”ôlɟŋē!„ū¯čępm,Lšv5ûŠlōϤgķ¸lcCũjîũŸHËĖ€Æu˙éBĒIyEŠ™9ÎöÕũ˛Ãʎ6HƤ܊æ¤AĄ˙FzââŌę÷ƒãŧ!¤m˜ŧ!¤m˜ŧ!¤m˜ŧ!¤m˜ŧč_ǰH4'pIĨZíˆØ[ŲnĢŌ¸Bã§ĖŦÎæÁJŪ=‘>ÄĮŸŋt%%%õGô�E"ņĒÕë*Õ˛HtđđŅĘv[•ÆZ´´:›#�¸u÷ūoŖ+ß^__oŌø1Ų,$4ŧ°HTÉÆU•+V2З¯¯ņŊŖŸN•į¤(īÚõ›/_ŊĻ3IYÛ֞mÛ´ĸ–÷ôîV™ÍF?f2ÍܛVrwT{WW×Jļ×Ķã:ø/›í‰<ĐŋoŸJ6ŽĒŧüŧK—¯UĻįÍÛvL˜TãԔĒ^Ŧ —”lØŧ]OŸ3nĖo뎈•KĀø)37­Yyęė>Ÿohd$)+ ߝ“+´Xû–ü9%ōĐ1Cũ‚Âĸ€ c_ŋ}wįŪƒ1nôđÍÛwŽ>äė…Ë�đácBįŽíõëŊ#boqqI‘HÔžm++ ‹'O_Čd˛QÃoÚüËĸŊŽäåįKĨŌõęöčÖ%`Öü6­Z–—|JJ^ēpƒA×({Ãæí4ÍÄØHŠTĀĮ„Äũ‡ä,˜3=ōĐąøø„GŸšš˜¨/įqšę\ŋuįíģ÷'N}ôøé‚9ĶÍLMŸ>ųäŲ ™LFŖĶõųüOIÉƎ˛˛´8xôDFF–\!oŅÜŊCÛÖáģ#‹Dĸ˛2iã†õ{tëRķū Ē›ŧ÷~{øņͧŲ3§3ô2ŠôDÔŠŌŌ/ovÛy ĩg ;;ģ=ûp8l‘HÜĮ×GĪß´5´~ũēEE".‡ÛŗG÷+׎ëččZY[Y[Z@IiIÄîH>_77W8x`.—yāМ™Ķ�`AĐŌÅ įSíõ ʤeûÎËˡ˛˛ØŋojZÚÉĶįôõôŠDĸQÆü÷øņS:ƒ1h@ŋŊûčÛûú[�˜”ÜĒU˞ŪŨ>ZRR*..náŅĖÜÔôÕë72™Ŧ_Ÿ^ģ÷˜?{Ɖ“§ e2™›ĢKĮöíöí?Äd1šNüĮ„QÇZX˜kœ‡ˆŨûh4šĄĄ’T@RŌįSgĪéëéMš0öäésI‰É/^ž224R_Îåüéiy˙ÁÃâ/^žōâåëIƙŋŽyķ:æ\.'htžŽNJjęĐAÍÍÍNŸ;Ÿ•ŖP*7jĐŌÃãĐŅãbąX*•Õ­ãÖą};ǎĸ?yf+°IĪČlĶĒĨƒŊŨÖíáž>ŪŲŲŲŅOžéčđ�ˆ1#‡í<đũū—Žøĩ–ŊŊ­āÖŨģ::ēâââÄÄ$ŋŅ#`EđÚŠ“ü *˙˜)*ųtīÚ¤QƒßF߸u§—ˇúZ]]fM›8:ØŲXYM?† ˆ1§ųļ˙đąĄû99:ÜŧsīėÅËNĩ¸\ÎüYĶr…B�pqvœ;3 )ųsÄŪ}}{ČärGûŽ;‰‚–­Üļq•Ĩšŋß(™LJ= >NY4$ÉIĶæth×Z.W´oĶĘÜĖtÅę ŸSSkŲÛŠWõ!>AR& š7ĢH$:ų*�””–Ž3B`cŊ}įž×oŪul׆Ëá´ôhķöwõå-›˙iZŽíڜ<sa@ß^úzzW¯ß1tāíģŋõíåsáĘ5WgG¯Î<zráōÕŽ:įŝ\ēPŠTĖZĐĒĨĮŗ¯Ö˙ú‹žž^§ÄƟjô¯UŨ҆—¯^{uîDŨ °YŦáCs˙üKūŪũ66ÖcF< ßņ“§ :]\\ܡ—ī˜‘ÃŸŋ|ŠŖÃkPŋ~Įvm¨Ø�ą¨¸S‡v~ŖFth׿ˇ‡5v§join.*:xúÔÉOž=€SgÎõōé1zä°ĻM]ŋu‹A§ŗ9œŠ“'04�¨å`?aŧ_î]ēué$“Ëm˙qcÆųžríēŖc-s3Ķ!ƒŌh�¤¤¤ĻĨĨ÷=ÉÜ͛wJ%Ĩt:ÍV`͎ˇoãF _ŋ}§QէĤ2iŲØ1#ģtîX\\ �Ĩe’Áû3ŌÔÄø}ėĪnn.M7ŌXŽŅg ƒŨģĩkĶúŪũ�đ(úIÛÖ­ j9ØõīÛģMĢV7īÜIMMMHHœ0Ūoâøą/_“ĘdoŪŧ2pĀÔÉTŊĄŖÃëߡˇß¨§Ī§ŅųųS&úÛŲ ĸNŸč?ÖoÔ •ī~˙ũūerš\&÷ęŌŠ}ģ6ÔÉo×ĻM§¤RIiff_WˇJą �6ûaô“{īÜXöŨĪŖ››™Ōh4‚ č4�dfåXZš€ĨšyfV�X˜™Šˇ/,, ß3gF�“É$€ČĘÎ ß}äøÉō{ÉĖĘĻē"ÂČČ0//�LŒ¨ō¤åÚį …æfĻ� ĮįSŗk3™Œ“g·íÚ÷ác‚zûo-×ĐŽįƒč'%%Ĩ9šB'GÕąķō3˛˛ss…ļlß´-ŒĮãŠÅâ)Ɔ†ī^¸tevNî÷O/ú)T÷ž— ešIˆÕå俤¤¤effRĀĐĀ€úúW›ÅzūâÕĢ×1ų…|]ŨīôlbbLŖŅ�€ú7'Whfn�f&&1oŪŲŲښš̎/*í?ttĘD&“)—+r…ÂũĶéti™Ŧ\ÍšfæĻT…úú…�`dh�,K,k´ĪĪĪ716�žŽ.õ‹‡É _šzÍf'&%;;9ąY,ĒĨÆōošGs÷åĢÖt÷꒗Ÿooo wÁÔÄ� ô ŗs…ųyų{ö�—Ë)).9|ȁCGŠKJ:wloggĢę‡ÚJWWG,*�###(.)aŗYL�LMLsrsU-+ėŋX,�Uˇ4áŅÜũÉãgy…íū\Ēŧ3.ģ8;uíÔūâ•ë ŸY,–L&€’’Ō’’� ¨đAean–š–îęė”š–neiTĶ?ČåŠõ›C'Žmh �¯ßŧÍÎɝ=}rJjÚŗ¯¨Æ$ŠTuuųÚM�P*É\ažąąŅ÷k622ĘĘ΀ü‚Â’â�ˆØŗŪŦiæfĢ×oV’J‚ ¨Ų¸5–kôŖjÆfŗë×­ļ{_ģ6žÔĒ´ŒĖ† ęeeį˜˜[YYΞ6�’>§pš\›ŊpŪĖR‰dÚėĀ–ÍĒtÂŅŋPu“ˇYĶ&—Ž\›<a“É”Jeûö4 Ÿz3S3#ãŽ]:Éd2Ą0¯|Oŗk7o98ØˇmíyëÎŨäĪ),&S.—@iiiФ´|{S“ŒĖ,G‡ŒĖ,33“/]˙A.WDėŲ7lČ }}=�x+æ;:=##†ē‡%@õ´451šsī7�P*ÉüüÃŋē§Ķ70 ūā-,,*-)€#Įĸ&Œkfj˛#| _Ÿ–Ë˝Š¯OKW§CG{4˙ōËĘÎŽSÛ-'Whhhhjjbfn6Ūo4�¤¤Ĩq86‹5e’ŋD"Yļ"¸IãFĒа(((āëéĒ·'‘”Éd2&“™‘™ŲÚąebRōwú§niŠmU'ŋmkΰˆ=JĨŌˇĮŸÆ *Ŗ~ŨÚGŽŸŠ˙˜`mmõæŨûÂĸ"==ūÎ=ûuuuø|>�8:8:­Ããil8jøāȃGôõôÄââ€IãbŪü鏏s/geįœ9 �ė[{z¤ĻĨoŨimeŠÃãŨŊ˙ĐÅÉq}H脱Ŗ�ĀÁŪÎÉŅa}H¨T*Đ×÷/?īęėH§3‚ׇ�€{“ÆÛwî135ąØ\¸tmFĀ„W1ooŪž§ąÜĶŖ›ũõÛUh4ËØ{`ü˜Ū^gÎ_äī7’Z•ü9eûÎ=)ŠiͧL°07ŗˇŦÛ´M*“ lŦ‡ ęîâ•Sg/Đt¯ÎĢzÂŅŋPu“ˇe‹æ"ąxŨÆÍl6[.—ˇo׆Š6•Öž-÷î?¸{īūÂĸĸvmZ9;k~…ŠĀöüĨË&&ÆÔ_ĘŽ.Îį.\NJJ2ˇ°ˆ‹‹/‹tuu;ĄÃãéęčĒÚsy\~úõíuōÔ>Ÿ_R\2rÄĐØØ8õĩ7nŪĘÍ^ģq�ėw÷Æ™Y{"Z˜›ņ8ÜčĮOėí#vī:¸?�6vv‚ģöĘd2īî]9õ´tt°§ĶÛÃ# ôõõõ AŊz562˛˛´ŧyëžßčáŋŋ{đ0Zcy“F4ž–įČņ¨!ûwh×vEđš!ƒPĢŌŌŌ:’ž‘9fÔ3SĢđˆ=2šĖŌŌĸwOŸë7o_švƒF§ˇũķ¨L&;tôxjjZ_õåûõŨąs7Įåņ¸ujģE?yúūÖÖ^,giaN§kžõ—Ü›4roōå×CoŸî�4oõߥû@Ģ–Í[ĩl�M7¤–īŪą�lmŦ/˜Ŗę§ĩg‹Öž-�ĀÄØ8ø—E�зןsĶڕÔũûô�ÕŨ%ÕxäĐAęÃļ|yËÁܙåk&bÁėięK†ę§ņsDč&­Ô۔¯JŽP´oÛZWG‡úoÛÖ-ëÖvS5Ķ(oŪŦŠåģB?/"11ŅŪŪū/ÛŊ|omaúãëA_$&%ßž{ĪoÔøãĩJ—rŋ´žīaôãÜ\Ą¯Ī_ߓūŊūˇ„† ØßĖÔ¤Âĩ?õw•‰=Ąž¤ĩg‹õęÔ`?g/\~úüåėi“Š;čĐđ.ÛŠ'/ú×zų.ž2뤤¤īDk ŧĢė˙M‘H|öÜyõ%îîMkģēÔ`?×n܊yķvü˜QÕ,õÉËĪ;pč˜Ģŗķˇb÷g§¯ĮŸėī÷CûéåĶŊ—OwÕgL™PũŨĄŸŪķĸâ§žįEč;jäž?=ŒBچɋBچɋBچɋB••WP¤[î-­CeßÛĀaąJ%œŠ UÉËwņ˙t Õ$]ׯ˛ŪkPŲäX›Ĩ¤eįˆĒŋKôߨ€ĐˇT6yuyÜÚÎvŨ!„Đ_Áq^„Ō6L^„Ō6L^„Ō6L^„Ō6L^„Ō6L^„Ō6L^„Ō6L^„ŌļĘ~’"==ũ‡ÖB? ++ĢjöPŲä­ūžBQp´!„´ “!„´ “!„´ “!„´ “!„´ “!„´ “!„´ “!„´ “!„´­˛Ÿa+•”Ĩf戋Kh5!ôãÜšwßP_XlfO•e5+ŠlōĻfæč9ÛÛTs!ôOšsī~5{(+“Ũ{đx؀ŪÕ맞Ŗ ââR#Ŋjî !„~j€L*Ģ~?8΋BچɋBچɋBچɋBچɋB_4iTßÄØđGī¨ēÉ{ũöũeĢ7IĘʨ˙ŽŨVÕžžxũ2æ]5Ë@ĄęŗŗĩnP×ÍŪNđŖwT÷ŧ.Ž'Ī]V_’•“ģsߥc§Îī=tĸ´T˛fķ’$ķ g.\^Z*‘ÉdļEP-KJKoŪ{đ[ôĶÃQgž<�ĨĨ’5!;ō ƒ7†žŊtíČÉsŽŪ€˜wą‘‡Ÿ8sú•ę׌B˙ Ę~’â;긚üķ.ļA]7jÉųË×ģthë`'xôäųoŅOí6i™)ŠéęՉ˙”Äb2]¨–<.ˇž›‹ĩ•ĨĀÚōЉ3͛6zķļY“†A”J$žŨģą:d{Į6ž§/\Y8k “ÉÜädŌįT{[üLB¨fđų:m==‚Æãr�ĀŅŪÎÂˌåũ‡OŠŠÄ?b5ŧ$IöëŲ}ëÎ}Üĸįæåß{đøˇč§RŠÔÂÜŦŽ›s|BbJj†W§ļŋE?c2 ęÖÖčÄÄ7ĄM¤�� �IDAT؈NŖeį_žy7jČ�š\ndh@�đuu˛…Â’’ŌŖ§Î€¸¤X$.Ž~Ų!Dár8Ļ&Æ_˙Ëåpš�ĐáņūŊÉ �l6Ģ×Ņ“įh4�˜ujßĘÆĘR$4“Áxüė%I’æfyųųr…¡{—¯I’�ĐĒ…ûÕ[wy\ŽŽ¯ °(W˜G’$AEf&Æ|žÎĐūŊčtzNnž~š!Tsr„yWnÜ!ÂÉŅŪŅŪ.)9%îã'’$ŗrrĐk&yĀŅÁîíû¸ĪŠi�āãÕéÜĨë\.G,.îëÛŨÜÔD*“Ų l�€¯Ģ+W(¨›YŠĀĘōĘ­ģÆF ęÖ>~úâČÁũ¨å|žî™‹Ws…ųÔåqš=ģuŲsđ‹ÅR*•ÃöŠŠ˛BˆT’™Y9�`an �⒒ŒĖėēĮę&o—mT?÷ōîÚËģ+�X˜™úĒŪlōؑÔCú÷ŌčĄaũ: ë×�ŠTjnfâæâH-įqš}|ē}mV¯vÃzšc!Tƒ$eR�(ûãÍZ?NŨķVS\|Âõ;ŋųxuRŋF!mz˙>6^ ;úˇ$¯ĢŗŖĢŗŖęŋúzSũG˙så „Đ„ŸaC!mÃäE!mÃäE!mÃäE!mĢlōęō¸yE?´„ú—#˜,fõûŠė{l,MS3r’͞ĒŋK„úIąŲŦļžÕÉË尝đKjB?ąÆu˙éžĀq^„Ō6L^„Ō6L^„Ō6L^„Ō6L^„Ō6L^„Ō6L^„Ō6L^„Ō6L^„ŌļĘ~†-==ũ‡ÖB? ++ĢjöPŲä­ūžBQp´!„´ “!„´ “!„´ “!„´ “!„´ “!„´ “!„´ “!„´ “!„´ “!„´ “!„´ “!„´­˛É+•É"÷ŦņŨ_ŊvŖŠ‡įĖŲķjŧg„ú×Ēlōž~ŗ7ō@Íî{Ųō•›ˇnsrtŦŲnBč_ŽRÉûۃG#Fų}NIŠÛ°ixÄnįÚõs…BÕÚn=|ˇmģyëvÃϧNŸõéÕĪŨŖÕđQ~Ē6OŸ=4tDÃ&͛zx.\ŧ´¤¤„ZnccsæäqĀĻÆ !„ūÍ*•ŧ­[ĩ\ŧ(ĐV x÷úųøącĖLMĪŋH­JHøô!ūcŸ^ž C,ŋxųęėŠãŅî2™ĖĀ %�‘™9f܄ŪŨŸ=~pîtÔû÷ąk×o¤ļį7šÅbũ CĄ­*ŋÂFŖŅúõí}ęôYęŋ/_iáŅÜÚÚ �H’?n Ng0C ¸s÷žT*=wūĸĀÆzäđĄL&ĶÚĘ*`ōĨSgjø Bč§RŲ9)Ôõī×gÛö°¸ņŽ.Î/]ņį§Z%°ų2t`iiŠP(r…ÂÄĤ¸ņĩ\ę¨÷ ĖË362ĒNŨ!ôķú;Ék+x4ovúĖšūũz§ĻĨuķęĒZ%—ËŠ…B�Ap8lĪ–-FŠBčg÷7ßĪ;°ŋ‹—.Ÿ;ą[×.::<ÕōäΟŠ>§¤2™Lccc{{ģâår9ĩŧ¨¨¨°°°šE#„ĐO­˛ÉËåpķķķs…B‰D�Ũģu-,,ŒÜ°oŸ^_ûĸŅv„EˆDĸ\Ąpo䁎Úą˜ĖŪžž‰dÃĻÍÅÅ%šBáŦšķį.�š\ž‘™™‘™))•”J$ÔĪ$IūˆƒDĄ•Ę&o›Ö­LLMZˇíxãæm�āp8=zt×ÕÕmŲÂãk_4ZįÎ}z÷kŨļŖˇjÅ/�`` ŋkįŽč'O›zxzûôÖ××_ŧ �’“?ˇjÛąUێ—Ž\ŊvũõŗH$úĮˆB˙.•į555šyõ’ú’ŒŒĖAûĶh_ŗ[ŠTzwķōîæĨąmķfî§OÕXččXë͇ßĢ^0BũôūÎ8¯D"‰ÜđÍÛˇ#† ­ņ‚Bč?¯ĘīmJĨõšÛÛÛíØļÅĐĐāGԄB˙mDbbĸŊŊũ?]Bũ§$%%}'Zņ["BHÛ0yBHÛ0yBHÛ0yBHÛ0yBHÛ*ûŽ2qIiJú˙Øģī¸ĻÎũāß,2„ f*Š(nĢˆ7¨(.DqÖÖÚuo÷ôļŋŽ[ë¨ŖŠbUÜ ĸÕē‚ĸ¸”*a˛dä÷GZnŖ@p|ßđ‚“į<Ī÷œ„‡“œįHjjëÚĩšįÍ#‘(3ķĒŽĻļŖ iFŪŋw@G‚z:Ö&oq™„A§r9ŽíZÍķ&aĪž:ŊžŖĢx,]Míū¤#˜ŧŊpŦMۚÚēW-vāyŽ]ķãņ›bx÷8¤Ø´€a>°z<ôqĩé Ŋčđ<īËÃöą �Šđîņ'7C™{–™ŅŅķŠ]cˇˇ‹ņŋa CŊ˙7įáß§HˇĘ ļ{„^txĖ‹,Eö�ņG°|đ?ZÄ.� õ6ū7ŦÁĻ•!ô˛ĀcŪ—ĐFÁk@'CL" ī‹�‘�ŅaŪ|sjęaũ$8÷úģÁˆxˆî ãģĶ‚Ē8xĮ˛ĪĄŪFŊ^˙ûąã“#=6z$NˇČb„•Úū˜WQŠøaÕĶ×VvU&¯]ŋŠMĒzĨŧæų2X{Ä*X9f€EÉ��?Œ˜Døđ$|=Œ Žƒ7Bu- ÷/G@M=4`˜œy†æĩĒĒjí/ë§NŸņũ?Ęd2oB/“ļ9æ=xH__?gfT›ôöBØŗ{ˇÚÚZ:~;'÷Ō•k]Ņ˙ŧyzšĀģA`G"`Ø™øWž Đ`� ��¨d0ž< #TÕ<ļ[7,4t˙ƒcÃF{ü}“i„Đ3hƒä}T rvTĢÔåå..Ώ^Ëēžq5‹Éd�&ģ÷ĀÁo.ģ”~å|ÚÅĪ?ūā^nŪÍÛw†‡†ė?xˆÃfkuē˜čY õ [ļmwqČ˙(¯°°(9å(˞ĨVŠÄÎŖIÛ~ÛI�‚ŽĻfqė<•F”|ÄŅÁĄZĨš7gÖŨÜÜĖĢYžB2qyHP`ß>Ŋ“•TH }ûô 4črzƍ›ˇė(;Ē]lLôĶnõ¨a!_ŋÚh4‰ÄāÁĀÍÅ9bBX}}ŊVWŗįāáŪ=ģčĶKĢĶö&ĻĖž>Ų`0¨5ZW—Ä#ĮerÅøŅÃy<'‘t''/ëæíČđqLÎBÉËx)ŖU9ūq(8ŗĀh„{øō,ėbáŖ“0Œ�Ÿ�°ˇƒ”9@%ÁųGđŸķ°sÔ5Āė°u Œķ…šz `Ũ•˙u[ZV–š–ļzՏ?­Zķ ˙Īn]}[S$B¯˛6HŪÔ gΘ^[[wėĉŲ3,{“Sžųú ™˙ÛÎ ‰¤ĒZe0ķî?āqšjĩæ^n^ī€ž‰‡R&ŽĶĨsį´‹—RĶ.Ä>Ŋ{1ėFöM‰DÚØÕŨ{÷|ģv?&L&W „´ —ēuí:zäđųų•ĘĒ#ĮŽELœāííyųJÆésįÜÜܘLÆ´Š“Õj͏?¯æķ¸üëŨƒá‹•ßčß?ãęĩˆđ‰]:ų— ķÛY#ãڍE13ķŠ ‹.¤g¤qŖRŽ*+¯Ч“A7úÛUëfOŸė×ĩ3…E%WŽŨčŨŗûĐ!ƒ2ŽŨđönÜļ“@ |øÎ˛ė;9ŨģųŽŲ¸U­Ņz¸ũīÃąƒôŽš�™YŲ ’­ŦmĄYÃ[å°õú_ßߗÁžÛ}?Ė *!b÷_?nž›˙NûE͍sADęívūĖ)0&,Œ@ ˜ZYBČ\k“W&—?xđįö �PT\ĸ™¨5TŖÕRŠv2�ø<žT&ëÜɧ¨¨XĢÕöīÛ7īūƒGĸˆđ‰û“ΜM=ŸvąŽļÎÍÍUĢÕúûû�Į3īmÄđĐcĮOūøķZ>;3*RŽPøûw€Žžž� •ÉÎ<�đxˇī业šņy<�`ą˜j•V"“W**ãâ�:ĻQĢŖgÍ8vâTRR˛÷îBáS˙ī|âL*…Lz¸ųųvž2qėú¸ļŖŧ˛�˛nŪĻĶhĩuu � —+¸�Č•�PU­vt°į:q8lSĒÖÔÔ2ôÉŋGM™Ä Ķ.¤g–”‰MŖdfe€o'oëcˇüûéŋa CŊ�c÷ß§H[B/¨Ö&īŠ3g/˜×šs'�Čžu;õâÅĀAe255ĩzŊžBĄˆË˃;ōyŧŗįSŨÜÜüüē&Ja22YĀįæå)ŦĒĒ&‰/]’Ëå� ./7K,Ž?.ŒAg¤9võęuŸ/—÷íŨë^n|O\^ŅŲĮG\^!đ� B"�ĨRiīĀâķygÁâķ ¸´”ÍfįÜË]0/Úh4ū¸jÍĀũšž'iŖƒ}āĀ~'ÎĻ==õô÷c;ÚË•Î|~QIiĐ 9y¨vT2‰TßĐ āķ Šnz Ũų<\'ļ˛ĒZ&WHd Sžē: jjëęęęâöÛŲŲ}đöëˇsōĮĘĖĘ6åī…zCšČÚMH-xēĪ˙Ū*'„ílūÕęũũ „ •ÉĢÕiīŨËk<ÃĐŗ‡RrĘkú›ˇ‰ŠœēiË6ƒÎ`Đũģûéõú›ã–ŋņ:×ÉŠ@T8vôH�˜29<1é0ƒAWŠÔŗfL  ܡ­°°ˆF§˙{—]ŖŅü˛q‹‡­ÕęĸgĪ`0čņŋ%lܧĶÕ,š95"éĐa{{{­F3wöģ9zŊ~÷ž%%ĨSÂ' ŨŨ=<Ü6ĮÅëëõŽŽ.Bw÷QáÉĶgííY\ŽŸ˙ƒë'ĒĒVI¤7ÅÔÕéÉdŌŨ{y2yåŅg§LŖ¯¯×étéW¯§?9N”ŽĻFWSûāĪGú¸ē8OŸ<ÁEĀßsđ°ŧRYV^3+’BĻ”K¤œ>4xÄPRƒĄáĘÕëOŽ 9k&Ā;Įž"|ÛD¨7Ŧ™`Ķz X{ļėœ|w~û×ĶfŌ32e2yøÄņ­éä??üÜVõĖž‘‘•ũHTÔV6úåĮ•mŪ'B¨•đ>l!ô|yi¯a2xPG—đ{S:ē„ĐķyBČÖ0yBČÖ0yBČÖ0yBČÖŦM^šŽæņ“Šŧ¤(JG—đ4­ŖK@=5k?Û t—JJUģVķŧ 28#ķZÍķú'‡FŖÍŒœÔŅU „žšĩWR „˛^IBĪL^„˛5L^„˛5L^„˛5L^„˛5L^„˛5L^„˛5L^„˛5L^„˛5k¯VkuÅe’šÚēv­!„žg,&ŨÅO§Q[ŲĩÉ[\&aĐŠ\Žc+ĮëĨåŌž=|;ē „Đ OĄŦ.)—úz{´˛kĪ6ÔÔÖŅqZ,„ĐĢ͉í ÖčZßžįE![ÃäE![ÃäE![ÃäE![Ãäm3û“ˆ ŸíŅĻÖlØü͚ mQBčšcí§Ęlāčņ}z …Í\ãÎŨeUUHАļôŗ¯˙¯ļļŽÁ ×ÔÖúuõž9í™īŊ6#rrŗËW~÷Ķ˙ëq>NÎŊŧ¸ Ģ­iię˙Š:€œÜŧmŋ%ôî0oΌ§]!ÔJ­MŪߏŸ¸r%“Įį64ėYŦi‘“šNNĪÖÕ¤ņc›]žvũώ—/ čŲŖeļäõÅķ;ûxÆ}‰É[KXļ8öĘÕŦ3įRíííYLÆĸųs>*Øĩ÷ ƒƒŊĒZõÎ[Ë EüÎ=vv&“ųΛ¯ŋũīOēųvîŅŨīÖŨœŅ#BOŸM%’HŽööD…KÎËÉÍ웓{đPJЏ|ôˆĐN>Ūk×of2 EåôŠŽ.Î_~ûÀ~}”Ę*ƒąh~´Š¤3įĶ*•Ę-ņ;æÎžaŪŪÅEđÍ÷̜œœfEMíėã �§ĪĨŪÍÉMØ{đrÆÕk~$ÛvėîÚĨķõ뛿epšNkÖ˙J§ŅĒĒUŅ3§ųx{Ieō‰ãĮ—”ĩĶ^Eĩ  ŽyC‡‡ �÷rķÖoØüų§UH*ö<Äaŗĩ:]Lô,™T–œr”eĪRĢÔ bįU)•’’)2ƒÁX0/fGž††zG•Z<d°T&Īŧšå)ô(—‡Ē5šōũq‚ÃáČdōđ‰ã&%+ĢĒôzŊ_ˇŽ#†…~ąō›ÔhuE%%o/ƒL&=ÛV„ČˆIąK—/[ģuûŽk˙Kĩŗûīšõyūŧs7§‡ŋߴɓ$R‘@Øą{˙ÂysŧŊ<Ī_¸¤RŠôzũ”đ înŽˇîæ˜:ęæÛyˍ—¯\ũũ“ įE'ū}úԈ56ĀÉ3įģúv™1A*“÷͚/?ų ZĨž;+Š@ ,x}EcōŽē?ņđ’ķ˙ū‡E{™\ņĶw˙! Ļ–#BC’˙=kzy…äöœ^=nŪē3gÆõ›ˇĖËpqvööōœ9Y\^ąqKüžøxXHPÚÅôÖ?ûĄgЖgüģû‘Čä2qYrĘīĮéŌšsÚÅKŠi�Žk—ņcÂdr‘@HJN™1mЇ‡Į•ĖLĩZM&}ŧ: ‰ßą �“ɘ6u˛Z­ųņįÕ_~úņ‰“g&Œ›ž‘ �ÅÅ%ĨĨeīŦxĶh4~öÅĘĀÁ¯5Ôŋ6ĪãŽÛđk™Xėų˜“Öh04ĐétVĢRĢ×˙ē�ĒĢUUUUƅLJųäĢoüÅąs%R™@Ā€áCƒM+: æũ¸�āäĖ+*-†¨¨ô č�|W&S��ΉC LŪ´¤Ļíų<^cėš7fäą?NˆÄ€žū2šiĸĸ’Ō˛Į „˛Ĩ6>ĪÛĐPĪ`ĐĨ2険ŠįĶ.ÖÕÖšššŽ3ęØņ“?ūŧ–ĪãΌŠ”)\� 2­ÅãqÍ;áķx�Āb1Õ*­E˙R™LāĖ�āČvT*Ģ�‰Ã�ĒžŽUĶJHJ Äd08löÛo,%“Ieâr—+*,š65œÉ`ė؟táŌWAIIYWßÎĮOžØŋ/�XDYЏŧw¯ž)Ë%FŖąņ!g)ūÄåī‰%5mo1Vc˙=ēûmŲļķø‰ĶŗgD6-ÃÅY āķ'O¯×ë%RYköB¨õÚ2yīæÜ#‘ČN'Ÿ?nl˜—§°Ēǚ@$ŠÅãĮ…1茔#ĮŽ^Ŋ.āķÄâŠN>ŪįĶ.ô č MŽÂ*$R�P*•ö,‹äâķxŠ.€Á`ŦŦTrØė֗Ŋ%~'“Á¨ŠŠņíŌ9zÖt�˜7gÆw?­ĻQi īžõēJ­ū懟y\'FģlqŦŋ_×m;vS(“9vôČĻoÜ_\Rúö›K‰D"ƒNÛžËôИQ#ÖnÜŧvÖĒęę%ą1OŦí‰íû_;wø° —ŌúÍË`;:ŽŨ¸yõ/ŋV*•cGpuqųæ‡UršBĢĶŋŗ|ЃŊũŗīA„ĐS"´pSøFŲ9ųî.üĻË?~âJæ5Ÿg04Đhô™Q‘6ģT,NL:Ė`ĐU*õŦĶd2ųņ“§8l­V={†N§;˜L&“™ Fėŧšģ÷îëסO˙îņ;v,“+˛ŽgsšN%%ĨcFėĶģ×Ęođõíėå)4įM:œ"—)ôz}¯^=C‚†|úÅʕ_~J"‘6Įŏ1ŦsįNM‹´åŒ9k6l="´Gw?Û gîPĘ1GGû‘ÆvlŊܲsō­É‘HÔB´ļ6yÛ\zFĻ)aÛ°ĪW!y×mÜRW§oÅϺ˜ŧĩ“6IŪįčķŧ/‡wŪ\Ú!ãŽxcÉķPBČĪ]ō<¨ŖK@Ąö…W#„­aō"„­aō"„­Y›ŧ4;;]MMģ–‚BĪ9…˛šÅ ˇžkßaē ŠK% ĨĒõCvˆėœüŽ.!ôÂc1čŽmđųZk“—Å w÷õjũx!„đ</BŲ&/BŲ&/BŲ&/BŲ&/BŲ&/BŲ&/BŲ&/BŲ&/Bؚĩ×°Šĩēâ2IMmĢn1‰B/4“îáÂ§Ķ¨­ėĮÚä-.“0čT.Įą•ãĄWAiš�lv&„lFĄŦ.)—úz{´˛kĪ6ÔÔÖŅi´V†B/4'ļƒZŖk}?xž!„l “!„l “!„l “!„l “!„l “ŲÔĨôŒģ÷ˇIW_}ûŖV§ËģŸŋ}įž6é° ­Ų°9'7¯ņĮâ’Ō•ßũ�õõ Ÿ~õmĮՅž˜ŧ¨ƒ™"I&—oŠÛnũZzŊžžžžA§ß¸yģoŸ^íV]ķŒFãÉ3įæ/}+ûÖķåO܊ÜûēuíŌÎÕĄ€ĩWR ÔV D…ë6n)*)>AŖÕŪÍÉ=x(Ĩ¨¤4?˙á•Ėk;ö\S[+‘H˙ũîōĸâ’]{:8ØĢĒUīŧĩėFö­ĸâ’yŅ3īŪËķīŪ �îåŨŸ>5|ŨϏúúz›={FäšõŋŌi´ĒjUôĖiÎÁĒu™L†RY5;*ŌËK¸vũf&“ĄPTNŸáßŊÛâ7ߍ۰�žũņį93§9vÂÔΌČÉĢ×o"�AĢĶŊŋ⠍VģmĮnŽG­Ņ.}!‘H´gąú7Iüģ÷›ļÂôcUUõ×Ŧwws%“˙úEģqķv˙žŊm¸ŗŅs “ŲJ]ņƒ‡ĸŨû?ũāŨ¤ÃŋOŸ‘}ëF 4pëo #‡uuqŪˇ=+ûVIIiŋi“'I¤2"0<4ØÔɍ›ˇƒiĩ:2™LĄPČ$’oįNãÂF9vÂÛËsFädqyÅÆ-ņsfN0._ēH_¯¯ŽV<sžĢo—Ф2ųw?­ųųû˙XÔÖØĪĄ”ßzø‡O›“›'WTîKLŽŠŒčŪ­ëÉ3įū8yfúԈ!ƒ_ģž}ËbõĄ!Ļ­ČĖē�§ĪĨ ØŌø1W2¯‰Ë+� īūƒ93Ļĩ˙>FĪ;L^dkŽŽ.�@§Ņę暟ÄYĀ�''ŽBQ9a\ØÁ¤”OžúÖYĀ_;ˇąÍƒ?ÆÎ•q5ĢWĪæk•WTˆŠJJË�€@ øuíŌ¯Oīo~XE&“į͙QQ!éĐ�ø<ŽLĻhat‰TÖˇw��ôčîgęö豓ĮOžŠ­ŠõZ{å¨L.7 qv�€JĨf0čd2ÉĘÕŅK “u$`4Íŋ€2qš‡ģ[…Dę=@XRR6mj8“ÁØŗ?éÂĨ+ãÂF@Ĩ˛ĘŅÁH$Ū¸y{âø1]€Ģ‹‹€ĪŸ<iŧ^¯—HeŠ´O¯žƎž““ģ/1؝̝)”Åå�H$’Á` ‰ŠĖŧį⒲Aûßŧ}—@ ¸ē8GN™ÔŲĮģRYE$>öŨķ­��ŽG"‘€iĐė[ˇûô hķ}ˆ^D˜ŧ¨#‰Dˇ}WÄÄq7oß={ū‰Dē|%S\^ĄRkúõí}ûNÎ7?üĖã:i4Úe‹cĪ_¸T\Ręîæj: {{ Í;=bØÚ›W˙ōkĨR9vôˆ.}6Åmw°ˇ¯­Ģ06Ė߯Ûڍ›×nØRU]Ŋ$6�F YĩnŖĢŗ3…BŗĐ 9lÍúÍßũ´FĢÕŊ÷Ö˛šŗgüļk/“ɨĒĒ^˛ FĄPėÚ{đ‘¨PTX|ųJæō×­Zˇ1xČ`/OĶV˜:5<ôûŸ×ũų°€Á Æ7oOŸaÃŊ‹ž_„‚‚ooī'ļËÎÉwwáˇ=čeКšĘßōBčų”“oÍk[$ĩ­øŠ2„˛5L^ô|Á^ô*ĀäE![ÃäE![ÃäE![ÃäE![kãäUT*~XĩÆôĩ•]•‰Åk×ojü1=#ķČīĮŸ­ĢęjUž}­)f߁Äģ9÷Ŧl|įnÎÅËéO;D›ė´Ö0ía+‹ßŗ˙@ÎŊÜgį*ÚĘŧôĮŧé™×˛Ž;8ØGĪžųĖˆ U*uĪūOlŋcWĩJĐŗGHАgŽcYY|ä”ÉIÉ)õõ ­ņEœĢLŖÕ~ķÃĪßüđķGŸ¯ŧuûnãrœĢ YŠĩ×°‰DE‡RŽ8:8TUW/[ēĐâŅkY×3Žf1™ �¤ņc÷8¸âÍe—Ō¯œOģøųĮÜËÍģyûÎđАũqØl­N=ĢĄžaËļí.ΚųÚ&%+ĢĒôzŊ_ˇŽ#†…^ÉĖŧ’qÍ`0ô č6jDž}Z­N­Ņ 4°w@‰S§™L“Å<zėćīŋc^Ol˜ģöPė(t-˙·ķĸg×ÖÖ&§eŲŗÔ*õ‚Øyö,͈įS͆ €’ŌŌ¤ä#ŽÕ*Õŧ9ŗîææf^Íōz”‰ËC‚ėíoŪēŖ×ë{øû+Šđ‰ã-Jũbå7 ¨ŅęŠJJŪ^ūFž6nÛ{{–L&Ÿ5Åb4ģŊƒæŪp5+K(ô(ėßīÆÍ[—.§ŗX,>3ęŗą|úų×ôĢ­­“ËåKÅĘärķũ|ûÎŨĖĖk$2yŲ’Eæ3 ¤gdĘdōĐā ÕŋlčQ]­ĸĶč3Ŗ"-FĄÚŲųúvš“sĪt-ŲSy æ*+,*64(8pĐÃG û{÷ęiÚ4œĢ YŠĩÉĢĢ­™éJ˜I�� �IDATæęēk÷ŪÜŧŪ^˙¸”319囯ŋ ÉņŋíŦHĒĒUƒ1īū—ĢVkîåæõč™x(eâø1]:wNģx)5í@ėĶģרÃndß4]ķnޏ¸¤´´ėoÆĪžX9čĩ)GŽģō p.õ‚žžŪS(T­R˙´z͐Áƒzx =\œMëÉš—K"=…!ACN9wënNŊžÎˇk—ņcÂdr‘@hôQ(&z�:|$bâooĪËW2NŸ;įææÆd2ĻMŦVk~üyõĘ/>sđgÍˆē›“Ķ´ÔĀÁ¯5Ôŋ6ĪãŽÛđk™XLŖRGíŲÃ˙ZÖõKéécÃF5ģ‡-õōô¤ĶéSÂ'—$9:°ŋ}˙ķÕįT;ģÍ[ã˙|TĐĨ“iE}C}Pā`€Ÿ°gߝœ{—Ķ3Ė÷ŗ€Ī§Ōh˖Xūą4!HjfjD8@ø÷ĮŸÍŒŠl:Š—×Ŗ‚Gΐŧ/Į\eĻöWŽf ØŋquœĢ YŠĩÉK!“Nœ<MĨR D…ž]ūņo”FĢĨRí(d2�đy|ŠLÖš“OQQąVĢíߡoŪũ Dá÷'&9›z>íb]m››ĢVĢõ÷÷�×t8ŠL&pæ�@pd;V”K¨4*‰D€Q#†Õ×7Čäō {H$R]­ŪbŨĻõ�€‡�vvvjĩzäˆĐcĮOūøķZ>;3*˛qÅúúĶQĄT&8ķ�@ĀãŨž“ãææÆįņ�€ÅbĒUږKU*Ģ�‰Ã�ĒžŽÎŅŪūú›7oŨŽTVŲŗXÛÃzyzrūŽšŽN¯Õi5Í΄=� VkT*•ųē<�ŲlĨR)•IÍ÷3�đyÜĮ �6Û4w @hv‹ŠĶęZčáq^ŽšĘjkkˇū–āíå9vôˆĮm)ÎU†§ĩÉģwâŌÅ |ŪĻÍ[`0ˆÉ`ÔÔÔęõz …"./îČįņΞOussķķëšt(…É`PČdŸ?nl˜—§°Ēǚ@$^ŧtI.—€¸ŧŧép|/õÂ%�0Œ••JWWN§×ëI$RĘīĮētę$—+/œ_&ßž›�ūžĨi=%%Ĩ杋ÅãĮ…1茔#ĮŽ^Ŋ>,4Ä´œD&›Â—Īã‰Ë+:ûø4ÎtU!‘€RŠ´w`�Āh44[*‡Í猪SgĪųøx r.5­°¨øq{¸é æt†ŖƒclĖ\2™TQ!1…rŖ ‰ÄÕÅE&“{ô°ØĪ÷īßŗãú–5;ŠFŖĄ3čVöđ8/č\eĩuu߯Z7{F¤oįNÍnŽ ÎU†§ĩÉÛĢgĪ„=û¸NNnŽŽgĪ]đņō24*ręĻ-Û :ƒA÷īî§×ë7nŽ[ūÆë\'§QáØŅ#`ĘäđĤà ]ĨRΚ1-80pSÜļÂÂ"fū"6 =ŧŧ„[ļn×ëõãĮ…ŅéôiS&˙˛qŗÁ`čĶģ—ģģ̏ŧ"~G‚‹ŗ€AŖgd^ķz=ūĮäđ ÍÖsũFļyįæ—[œ8l­V={FãōÎ>^"‘o—ΑS#’×j´1sgßš›Ŗ×ëwī;PRR:%|"�øx{Įmûm@˙žMKĨŅhÛŌ­Ģī‘ß˙‰DÎ..÷īį— üë/MüŽ]ũúôîĶû¯w,ÍËģoŅOä”đ›ˇPí¨ƒaÁü˙’ˆ¤ŦëŲRŠT­Ņöėá/đÍ÷ŗõOņãF)vëÖõiûąđ‚ÎUvæ\jIiŲŪ‡�Ā‰ÃÆšĘĐ͹ʞėQč\jÚĸØyËMoF…OßļÃ]ģ~ƒëäÔÉĮģ•ũ|ôé—ßûuԜÚēēūûķ'ūģŲ˙qŽ2ôÚĘl¤“7“É|ļ¯>ƒÎ|Zģí-)9eęäp<e‰ĐŗÁc^ÔöZsĖ‹ĐsyB腄ɋBļ†É‹Bļ†É‹Bļ†É‹Bļ†É‹Bļ†É‹BVIMMmĢŽZ{õ0BŊ v@Ķ) ž ķ"„Đ˜bwöėYĢį™j&/Bĩ¤1vËĘĘÚĒOL^„zŦÆØŊv-k÷î=xļ!„Ú—yėĻĻĻ._ū&žm@ĄvÔ4v™Lf[uŽÉ‹B–šÆneeåW_}gB¨]4ģ[ˇn[¸pžm@Ąļ×Bė …Â'Žn%L^„úK ąËårãâļļÕ@˜ŧ!đ÷ÅÁĻĪí6žĨÖx´ģ{÷ž€€€ļ:ĪkíŨ€rķ  *ŊÉ tj ī„^PFŖŅt&ˇé7Ļī+ĢTōĘj_'vÕōŨ€ŦˇAč.(.•(”*+Û#”“ßŅ% ÔÆX ē‡kܑŌÚäe1čŨ}ŊZ?B!<Ī‹Bļ†É‹Bļ†É‹Bļ†É‹Bļ†É‹Bļ†É‹Bļ†É‹Bļ†É‹Bļ†É‹Bļfí5lj­Ž¸LRS[×ŽÕ „ĐķŒÅ¤{¸đé4j+ûą6y‹Ë$ :•Ëqlåxč%SZ.řqĐĢCĄŦ.)—úz?yƜ–Y{ļĄĻļ'*CŊâœØjŽõũāy^„˛5L^„˛5L^„˛5L^„˛5L^„˛5L^„˛5k?Ī‹ĐĶzëũģųvîŅŨo΁¤¸ ĢāÛž3szaaņ…Ëé>Ū^Œ8¨Ŗ+EČÖđ˜ĩŊ^?%|ÂđĐ`‹å$‘Á`DΜ3gÆų´‹RB “ĩ#g Ųå<.�¨Tj-^Ž^I˜ŧ¨��$É`0�€D*ëā‚z>āy^ÔîF YĩnŖĢŗ3…BŖąŖËA¨ã ŧŊŊŸØ.;'ßŨ…ßūõ  Θƒ^5Ų9ųÖŧæE"Q ҊgBČÖ0yBČÖ0yBČÖ0yBČÖ0yBČÖ0yBČÖ0yBČÖÚ8y•ŠV­1}meWebņÚõ›žv­ŖĮO—<ķ R™|Ũ†_M\ĩßpņ;v=ČĪÚĩÚĐGŸ~ Ö/‘Ęžj‡ „ŦņōķšbzŌøąBáŗßtĪžQ‘SˆÄ'ėEĨ"aĪžÖ׹Ŧ)^ĀįuíŌåâÅË­nåw?ĩž„^­ŊzX$*:”rÄŅÁĄĒēzŲŌ…^Ëēžq5‹Éd�&ģ÷ĀÁo.ģ”~å|ÚÅĪ?ūā^nŪÍÛw†‡†ė?xˆÃfkuē˜čY õ [ļmwqČ˙¨MĢĶÆmÛaoĪ’Éä3ŖĻy =öH”ÉäZ&"|’T&}đ ˙Ø'*$Ōā!ƒ=…Âí;čtēRМ0nŒ€Ī_ũˆ€€ÕÕ*:>3*ōrzƍ›ˇė(;Ē]lL´išBQ[[ëââ �“’•UUzŊŪ¯[×ÃBãÛI ’ėYĖ⒒Ų3ĸŽ;.*(ŧ‘}ķæí;ÁC3Œ¤ä#ŽÕ*Õŧ9ŗrī?¸š•%zŠŠ‚‚öīw)ũʍė›2ÅÁÁ~ÎŦÛ™ƒ~ąō›ÔhuE%%o/ŖÁĐ˙Û.ĒRЧ„Ol ÍËW22¯fy =ĘÄå!A}ûôN>rTR!m04ôíĶ+pĐ í;w74Ô;:8Lœb>\üŽ]ÁC§_šJąŖĐi´ü?΋žÍqâXŒ2dÕęuĄĄ!OûÚØŧmGĩJU[[סw€ånNîÁC)ĮYģ~3“ÉP(*§Oāķy_~ķÀ~ŊÕ-ÛŅ!föcwB/“ÖķęjkfFE.Œáķ¸šy,MLNy}ÉÂķæ‚ŅP!‘TUĢ cŪũ<.W­ÖÜËÍëĐ3ņPĘÄņcæÍíßŊ[jڅKéWúôî={f7ß.æ]ŠUš‘ÃCĖ›;<4äRzz¨P&“ŋõÆŌE įĢÕę!ƒąŲė ãÆš§]J÷ņöž3gnôŦ}‰‡$’ZŖ™}=;�2Ž^76léâŖ†küWúQ¨S'�(..)--[ŧ`ū˛%‹ÎžMÕÕč€@čäã5mę䐠 ŗŠŠCōķëÚ¯o͊‡‰˜8a~˜ūũúœ>wŽD"Ōéô)á“"§D\ÉČ�–.Z°léĸ{š÷ĩ:mŗ{˛é  õ†Á¯ œ>m …B)‹/\ŧėáá=sz䁤äÆ “ɘ6uō‚ys“-))yø°`éâ¯/^xėSúúz2‰ØĨS'‹ØmD"=…S'‡÷íĶûÖŨœĻŖ0ŒššÚúú†§zaÆŦ7—ÄÆ|öá{~]ģŒ qrâLŸqōĖųŽž]ŪZļøĨ ˇîH @ŖŅÄΝŊbŲâĢY7T*õS‚Đ ĒĩĮŧ2éÄÉĶT*ĩ@TčÛåYŠŅjŠT; ™ �|_*“uîäSTTŦÕjû÷í›w˙ÁŖQDøÄũ‰IgÎĻžOģXW[įææĒÕjũũũ€Įã™÷Fĩŗģ~ãæÍ[ˇ+•Uö,–BĄāņ¸�Āurâ:954ü#d2YwŋnĻG+J�ā°Ų�L_ŖgÍ8vâTRR˛÷Z­–É €T&8ķMŲŽJe�đy<�`ŗL?š“Ęäg�xŧÛwrŧ<=9�ØŲŲÕÕéMmvī;@ĨR5­žVßėžlvP'Û´íúē:ŠLZ\\Z^^Ū¸LĩąXLĩJ+‘É+•qņŋ�N͍Õ�`ÚWãôwĩjĩēZUŨt:ƒŽĢŅŲŗX-tb@ ŧštá†ÍÛÔͤņcŧŊ<MË+*$Ŋz��ŸĮ•É� āķMą+ĢĒėíŸb„^P­MŪŊû—.^(āķ6mŪj„ŧc:VŌëõ E\^Ü9Īã=Ÿęæææį×5éP “Á É>ÜØ0/OaUU5Hŧxé’\.�qyšyo§Îžķņņ<ä\jZaQ1ŸĪOģx�dryÎŨ{!!AFŗI°ø<ž¸ŧ ˇD"åō8M˖Éå æEÆW­8 Ÿé ƒN—ĘäĻÕS/\�ƒÁXYŠä°Ų�P!‘øw÷“Ęä‡@ 4Žĸŗ¸ŧB āYŒU[W—ōûąīŋYŲ`h¸q#Û�ÍOÖÕė æ|Ή6z¤^¯—ËæUH¤� T*íX|>Oā,Xŧ`>�—–˛Ųlh’Ô-hvVG§Ņ­ėá¯UjjhTę'ŧĢĢŠYņūĮƒö7í1gAIi�4îĢōŠ ƒÁH$d2šS“­FčĨÔÚäíÕŗgž}\''7W×ŗį.øxy™?9uĶ–m Á ûw÷Ķëõ7Į-ãuŽ“S¨pėč‘�0erxbŌaƒŽRŠg͘¸)n[aaN3Oˇn]}üū‡H$rvqš?ÔČáîn6mQĢՓ#‰D"FÛ{ ŅÔxhpĐö ņ;T*ÕŦ¨éMË.ž<}ÖŪžÅå:ņųeĨˇ)û„B//ᖭÛõzũøqa4 �JKËvíŪ[&.7—H$ÜËŊ9=ôbäԈ¤C‡íííĩmĖÜŲyy÷ÍĮ˛ŖP\œãâˇŗXŦ=ü?aZލTü˛~ķ—ŸlúąŲAÍ Üž3aÛöUÕÕĄ!AĻŋ&zŊ~÷ž%%ĨSÂ' ŨŨ=<Ü6ĮÅëëõŽŽ.Bw÷§x:›EĢĶŌhT2™ôTũØQ(GŽ8”ō;‰L3j‘HdĐéqÛw͝ĩvãæĩļTUW/‰�6›ŋ3A\. Ėb1Ÿj„^P8Kä?ŦųeãŦ¨iÎΖwR0ŊÕÕˇ§C<˜˜<}Zķ§_­—ž‘)“ÉÃ'Žo“’š:yęŒÕnxčĐfmå,‘2š|ÕēMß}ũŲ3÷€á,‘moöŒé%ÛæãĢ aŖGÚ` ÖĘä÷ķķCC,īĨ†j <æE­‚3ŖŖW ķ"„Đ “!„l “!„l “!„l “!„l “!„l­ĩ×°!ô8¯ŋõ~đÁ5ĩĩ‰ôßī./,*Ųšg?‡ÍŽT*?ú×ۗ¯dVVVEEFˆŠŠwī;øéī™Īm6aėč„}ÅâŠú†úÁ¯ >?PŒ:^jję°aÃÚ¤+L^Ô^ôõõ#‡uuqŪˇ=+û“ÁX;WčážqKü­;9Ms›ũô_;::<|T *,ĘģŸ˙͗Ÿ †åī}<d0…Œ¯Uԑv€ų”­¯fԎœ|�prâ(•lG‡¤ÃGi4ڃ?öčîgŅŌbn3 d2ųĒu€Á ĢT*Ķ„juSėΞ=ËúɧZ†É‹ÚQ™¸ÜÃŨ­B"õ Œ‹ßųÁ{+\œß˙´Ö`4Píėęôu� •Ę ÉÜfŸ~øž››ëû+Ū��QQ1Æ.ę@ą[VVæáŅ6÷ ÁäEí…D"]ž’).¯PŠ5ũúö~øH´qKŧ€Īķzü~üÔŋß]~üä™ û€@0-į6ķöz{ ˙ģz}žNčáî÷Ē@¤1v¯]ËJMMũāƒˇIˇ8oj•æmXüæģqVÛ¸„ڐEė._ū&“ÉÄyB¨Ŋ4ģmÕ9&/j/xĀ‹^\Mcˇ˛˛ō̝žnĢĪ6`ō"„Đ?4ģ[ˇn[¸pA[}ļ“!„ū§…Ø …m5 &/BũĨ…Øåršqq[Ûj L^„�HMM…ŋ?ˇÛø–ZãŅîîŨ{Úę<¯ĩŸ*ËÍ/d0¨ô&7ÄE¯8ŧz™FĶ™ÜĻߘž¯ŦRÉ+Ģ}}ž|=E˟*ŗöJ Ąģ ¸TĸPĒŦl^Ų9ų]B6ÂbĐ=\ÛāĘk“—Å w÷õjũx!„đ</BŲ&/BŲ&/BŲ&/BŲ&/BŲ&/BŲ&/BŲ&/BؚĩWR¨ĩēâ2IMm]ģVƒBĪ3“îáÂ§Ķ¨­ėĮÚä-.“0čT.Įą•ãĄW Nė€^& euIšÔ×ģĩ÷Á´ölCMmN—ƒzÅ9ąÔ]ëûÁķŧ!dk˜ŧ!dk˜ŧ!dk˜ŧ!dk˜ŧ!dk˜ŧ¨Ãˆ ‹Vüë㋗¯lŠÛ�‹ß|ˇŖ+BČFŦũ</Bm.÷~ū°  Ā ĀŽŽ!›ÂäEC­Öœ=Ÿf0x<§?NûîëĪL˯\Í:s.ÕŪŪžÅd,š?÷áŖ‚]{:8ØĢĒUīŧĩĖŅÁžcËF¨M`ōĸŽÁb1‡‡†čõzŋnœ:׸|ëö]×ū—jg÷ß5ëķüyįnNŋi“'I¤2âߡ€EčE‡É‹ž#­VĨV¯˙u+�TWĢĒĒĒ&Œ ;˜”ōÉWß: ø‹cįvtĩ L^ôa26ûí7–’ɤ2q9ËM›Îd0öėOēpéʸ°‘]#Bm�“=_æÍ™ņŨOĢiTZCCÃģoŊŽRĢŋųág×IŖŅ.[ÛŅÕ!Ô6ŪŪŪOl—“īîÂo˙zĐËį*C/™ėœ|k^Ō"‘¨…hÅĪķ"„­aō"„­aō"„­aō"„­aō"„­aō"„­ĩqō**?ŦZcúÚĘŽĘÄâĩë7YŋÜ6ôõõkÖmP*•Vļ?züDqqÉĶŽ’ž‘yä÷ãOģVŠßąëA~ž5Å?íy×ŗo>—z.íâۃ‡Úo„léå9æMĪČŧ–uŨúåĪæėŲķũúöaŗŲOliúķ0iüXĄ°ĩˇ)í(ÖO!“#Â'îoĪLėߡ÷čÃÚ¯„l¯ĩ×°‰DE‡RŽ8:8TUW/[ēĐâŅkY×3Žf1™ �¤ņc÷8¸âÍe—Ō¯œOģøųĮÜËÍģyûÎđАũqØl­N=ĢĄžaËļí.ÎųĩUWĢ,–'ėŲ§ÕęÔÍāA{œ8ušÉdššģ?ŸÖtšZŖ)(-˜�˙ųîĮčYQÛ~ÛÕĢgVįāŠœqãæ­K—ĶY,ƒNŸ5írzƍ›ˇė(;Ē]lLtc—Ō¯|ųų'›3gûŽ]"ɞÅ,.)™=#ęÁŸ>xėiđÁžBáö t:]ŠTN7FĀį¯ūeC@@ęjFŸŲōnlvÆÆĖŲąkŎB§Ņō˙|8/zļ‹‹sō‘Ŗ’ iƒĄĄoŸ^ƒ™Ö’+Ģ×mhacˇīÜŨĐPīčā0=rŠųpņ;vŦP(¯fe ……ĸĸ  ĀũûYŒâãíU^QĄŅj™ ÆSŊx~ŲgggGĨډ ‹{õėĄŦRŠË+>ũāŊ?ėÜŗŸÃfW*•ũëíŒĢYŠŗ€�Z­nÕēL&CŠŦšé× ¯Ņ@/¤ÖķęjkfFE.Œáķ¸šy,MLNy}ÉÂķæ‚ŅP!‘TUĢ cŪũ<.W­ÖÜËÍëĐ3ņPĘÄņcæÍíßŊ[jڅKéWúôî={f7ß.æ]Y,×××{ …KÅ.Z0˙ÄŠĶL&ŖW@ˆĐŸßėōА‡Dē]yy…=‹åčč Ķj§GN‰™sëöĩZŗī@âŌÅ Ė›[U]ũįŖ‚ŒĢׯ [ēxÁ¨áà ƒŠ•ZM§Ķ)d˛ÅĻåÜËĄ“×´Š“C‚‚ÎĻĻ<ˆÍfO7Ö´bÚĨtoīų1sæFĪڗxˆ@"Š5šŠáą1Ņ×ŗŗŸ¸›ŨŸ9÷rI$ĸ§Đcęäđž}zßē›SRRōđaÁŌÅ ^_ŧđاôõõĻĩhycÉ$b—N,bˇ‰D¤ĶéSÂ'EN‰¸’‘Ųė(^žž……EOûâ!‘HŨēv™=‹H$z ŨÄĖŠŽVUĢTZnqėÜwßzŨÅYpëNŽų*E%Ĩ�ÆåK}øū GG‡§ĄįDky)dŌ‰“§ŠTj¨ĐˇË?˛RŖÕRŠvϜâķøR™Ŧs'Ÿĸĸb­VÛŋoßŧûˆ"Â'îOL:s6õ|ÚÅēÚ:77W­Vëīī�<ĪŧˇĘĘJķå Čäō {H$R]­žąŲã–‰„A¯ ¸š™Ĩ¨R††�—Ë%�ā`īP.•h4š {�@­Ö¨TĒčY3Ž8•””ėßŊ{ãÜ:­ŽÁ 7ģi�Āįņ�€ÍvP*Ģ,ö’L&ëî× �¸NN• %�pØlĶčϝ-ėÆö'�8q8�`gg§VĢ%2yĨĸ2.ū7� ĶiĩēņŦHË �<ˇ…g™ķ÷(uuúfGaą­ļ…į¯ú)‡ � ĨŽŽŽB!'>JŖŅüų°Gw?ķö~]ģôëĶû›V‘Éäysf<È=Z›ŧ{÷'.]ŧPĀįmÚŧÕķ‡˜ FMM­^¯§P(âōōā΁|īėųT777?ŋŽI‡R˜ …LđųãÆ†yy ĢĒĒ DâÅK—är9�ˆËËÍ{cŗ͗įæåÉåŠÅ ᗉŎīæ���ŖņqË`hđ_ãâ Cø„ņUÕURŠĖ`0‰„JEĨĢĀŲŅÁ16f.™LǍp8œü?˙\0/Úh4ū¸jÍĀũ\\œ€F§kĩēf7­@TX!‘øw÷“Ęä‡@ ÆÆâų<ž¸ŧ ˇD"åō8Oĩ[؟%%Ĩæ ø|žĀY°xÁ|�(.-5?ŨōÆŪŊ›C°zęÛfGŅhtO{ĒĄqņ;?xo…‹ŗāûŸÖŒ˙ØŠ´O¯žƎž““ģ/1ųÃ÷V´Õ ŲRk“ˇWĪž {öqœÜ\]ĪžģāãåeūhTäÔM[ļ1tƒîßŨO¯×oܡü×šNNĸÂąŖGĀ”Éá‰I‡ ēJĨž5cZp`āϏm……E4:Í<ŧ,–ģģģŠË+âw$¸8 4zFæ5/ĄįŅãLŸĐėrÛŲĮ‡Á`¸ē8“H$�ppt8˜$‘Ę ėĮd2"§„oÜŧ…jG5 æĪ-ž<}ÖŪžÅå:ņų};Øŗt55úúz ™ląiW¯•––íÚŊˇL\;o.‘H¤Ņh{$šV´}gBüŽ•J5+júwcHđ_wĮųā“ĪW~ņFmv^ŋ‘mŪ‰ĐŨŨÃÃms\ŧž^īęę"two|¨å}ĒgŧŲQ ‹ŠĻEN~Ē~Z0 _ߍ[â|ž§Đã÷ã§ÂF 3tSÜv{ûÚēÚ cÃÚjD„lė՚ĢlŨ†_gFMđyŠJE\üÎßįi{8~â”Ŋ=+$hˆÅrĶ›Q]}Ûø ŸÄCÉS'O&[u/†gŪX+?yzŲ’æßÄšĘĐKį*{ ŠJÅÚõ›ēvé"āķžÜúņFqũÆÍvũøĒšÇĩ2vÛ[}}CrĘŅ™Q‘]B/’Wë˜Ųķĸ— ķ"„Đ “!„l “!„l “!„l “!„l “!„l “!„Ŧ’ššÚV]ĩöęa„z$$ė�ķ) ZyBč Lą;{ö,ëį–j&/Bĩ¤1vËĘĘÚĒOL^„zŦÆØŊv-k÷î=xļ!„Ú—yėĻĻĻ._ū&žm@ĄvÔ4v™Lf[uŽÉ‹B–šÆneeåW_}gB¨]4ģ[ˇn[¸pžm@Ąļ×Bė …Âļ“!„ūŌBėršÜ¸¸­m5&/Bü}q°ésģoŠ5íîŪŊ'  ­ÎķZ{7 ÜüBƒJ§ŅÚdTôęĀģĄˆŅh4ÉmúéûĘ*•ŧ˛Ú×Įã‰]ĩ|7 kįmē ŠK% ĨĘĘö5ĘÎÉīčj,ŨÃĩ îHimō˛ôîs�¤h�� �IDATž^­!„žįE![ÃäE![ÃäE![ÃäE![ÃäE![ÃäE![ÃäE![ÃäE![ÃäE![ŗö6ŽĻ¨´ĸĻļŽ]ĢAĄį‹I÷páĶiÔVöcmō•V0čT.Įą•ã!„ÚÎOÔŪĘę’rН÷“gĖi™ĩgjjëpĸ2„Đ+Ήí ÖčZßžįE![ÃäE![ÃäE![ÃäE![ÃäE![ÃäE![ÃäE![ŗöJ „Đ‹Ģ˛Rųåˇ? č×GŠŦb0‹æGŸ9Ÿv%ķ…Laŗ__4?íbzÚĨôNŪ^÷ōîėßWŖŅæäæ}øū :žfũ¯t­ĒZ=sš7یąmā1/B/?"‰T­RĪĩâ%éW€�„Ū}ëŖŊ}ķö]VK"ŲlĮčYĶ_ĐOŠŦŠž5Ŋ‡ŋßŨ{y'OŸķöō\ņƒEķŖãwîéčíxyā1/B¯ž‡@ �€é+�lÚúFSĢ5uĩu�āÄa�…BápØ�`GąĢĢĢ+¯¨(•”–™¯ˆZ“ĄWNm]ŨžIqÖ4Ō3ŽŒÆĮĩtuqđų“'×ëõŠĖ–EžÜ0yzåØQ(înŽĢÖmp°ˇīׁۧ¤Ã=ē7Ûrôˆak7n^ũ˯•JåØŅ#ÜŨ\m\ęˊPPPāííũÄvŲ9ųî.üö¯!ôėpŽ2ČÎɡf'‹DĸĸßaC![ÃäE![ÃäE![ÃäE![ÃäE![ÃäE![ŗiō=~ĸ¸¸ÄbáŽŨ{sîåZŗúGŸ~ų ƒZß˙ĻÍ[­īöŠ7*‹×Žßd}{Ķ‹ßąëA~ū3 ÷ŧšs7įâåtëÛKeōu~5 Vļļ'Ĩcw¯ĸRņÃĒ5`]ņY×ŗ“SŽļQ˙s=ûÖésŠįŌ.î=xȖãžôZ{%ÅīĮO\NĪārL?čßoØĐāĮ5ž4~l+‡ŗžĸRqüSŅŗgZŋʲĨ‹žØ&=#“B&ĐߚÆÖkėÖbš-÷XģZģ~ĶÛ˗ôėņTkíŲw`Æ´ŠDâŽÚéIą1kŠĐŋīå+ebą›ĢŽhčߡ7�œKģh›á^mp ÛđА°Ņ#ŦRV­ūeC@@ęjFŸųåū¯“ˇwWß.š÷īL§Ņ$%S(dƒą`^ �\Íē‘‘y­Ŧŧ|éÂXGļcüoģh4ĒJĨž>Q(ôˆÛö‘HäpØã?Ž}öėĶjujfđ CŠ‹˙mDčĐΝ;9— �"‘¨ đFöM‹ūėˇīH ĶéJĨr¸1žž]ËK9zėûoŋ>˜”ŦPTÖ××ßš{oũÚUW¯ŪČžI!Sė'‡O:qę4“Érswûeũ¯ßûuIiiRōG‡j•jۜYš÷\ÍĘ = EEAAû÷3•Z]­Ú˛m싺€Dūk‡'9*А6úöéÕĢg@cˇįΧ™oTüŽ]ÁC›V),,JN9ʲgŠUęąķėYÍ>R™|ĮŽŨ˙zw�|ôé—ßûõî}Ôju]ž‡ŋ߈aĄ7nŪēt9Åb1čô™QĶļīÜŨĐPīčā0=rŠŠ‡O?˙zĀ€~ĩĩurš|ÉĸØŦŲ™™×Hdō˛%‹’SŽ(ĢĒôzŊ_ˇŽ#†…Z´”Éåûâ°ŲZ.&z–žŽî—›ŲlļĐÃũÁƒücœāp82™<|âxķÍ4ČĸBSr…ĸļļÖÅÅ�&%?nÜ9ŗf<í“bņĘiv7ÖÔÔXŧNžXųÍĀj´ēĸ’’ˇ—ŋ!•IÍ7–É`˜VŒ˙m'H˛g1‹KJfΈrvXllãë-pđkæ#šž,‹Q ŋCC‚.\ŧ<3jšŋš˙°ī X\QßP?øĩÇ/īР@™LN ėY,™\áîæa1‡Ųš´‹Šŗ�¯ĸjcmŧi—Ōsī?0}?5b’#›­ÖhĻF„„üŲˍČz}ũ˜Ņ#]\œsī߀¤ä”ĶĻxxx\ÉĖTĢÕ�āåé1bXčŠĶgoŪēM =<Ü'+‘HwíŲ7%bRm]íōeKUjõŲķŠƒęëë=…ÂĄ!AÕ*õOĢ×4ũũ2xJíסOÎŊ\ķū‚ˇ÷Ø1Ŗä ÅÆÍ[?˙øƒÆōRŽ�S�mŨžcÉĸųd2‰�„Ĩ‹PŠÔOŋX9eō¤^^B÷ŋ8>1q‚ˇˇįå+§Īķōô¤ĶéSÂ'—$9ژŧ—Ō¯ôéŨkԈa7˛oJ$Ō’’’‡ ūõî ƒÁđÅĘoôīoęVĀᎰQwīŨķíÚeü˜0™\A´zîŖŅxįNÎ'ūËÁÁž°°�öHüĪWŸSíė6o˙ķQ™Dôņę4,4äûļĄ>(p°@ĀOØŗīNÎ=2‰DĨŅ–-YX\\RZZöΊ7Fãg_Ŧ üšEËËéĮéŌšsÚÅKŠiB‚ƒ++•Ÿ~ôŅh¸–uc¸ąé™�`ąųũûõŗ¨ĐäQ¨S'�hyÜG"ŅS=)}z÷jų•ĶøĒļx4Ôŋ6ĪãŽÛđk™Xœrô˜ųÆN÷÷'B'¯ĐāŦëŲgSS‡ ąxŽ_o͎k1ĘũųæŋīŋķV'/¯ã'NZųė7åŨĪ˙æËO Ãō÷> 2¸žžaôˆP'gFĖĸßļŦ§ÚŲŊõūĮQ‘Ļ9ˍTęë+ūĨŅjŸv dĨ6HŪĐā!æĮŧÕ*5‡Íļ˜‰Įã56)\�ũõĸw�ĀŽJUĢÕÕĒęââŌōōrĶꕕ•<.�ėY,:ŪØ 2š|g‰TWĢošBķūĢĒĢģûu�Ž“SĨBŲ´<�8~â”ŗŗsŋž}L?îŪw€JĨj4Z}“¤2šĀ™�īö/OO‡�vvvuu˙k\YYéīīß8D&¯TTÆÅ˙�t:MŖV[ŗQ#†‡;~ōĮŸ×ōyܙQ‘Ļ…:nïq�0=rŠ—§°éļ„˜čYģvīÕhĩŖF ã xfgÂ�PĢ5*• �x<ŽÅZĻ%ŽlļRŠ´gąø<.�He23ßÔ§#ÛQŠŦ˛h)•IΜM=ŸvąŽļÎÍÍ�œœœˆDBCÃ?:ˇØ|­Fc^Ą——§Š™VĢe2č֌ûTOŠ•¯™LÖôubšÍ‹jg§¯Ģkēąø<�°ŲJeUŗĪĩÅë͂Å(æŋ�Ā`1ĩښVo–¸B"“ÉW­Û� Ũôŧ;q8�ā`oO§Ņ� áīįÉb3Ôl4cŽų!š€Ī‹+:ųxŸOģĐ+ §EK_Ās↍Š×ëår…ZĢ•Éå�PUU­Ķę›åæåÉåŠÅ ᗉŎiRģúz�Ë\ށ@067ŸĮ——ô–H¤\§iyˇnß)))]˛(�jëęR~?öũ7+ 7ndĀH €ųÄN|O\^ŅŲĮG\^!<ö׉Ív”Ëå� ./�>Ÿ'p,^0�ŠKKŲlļŠÛĻeN,Ž?.ŒAg¤9võęuĶQ*N7XhdGĄÔ×ë@§Ķéjt555T;ģ7—-ŠŠŠųę?ß}˙í׎Žą1sÉdRE…„ÃáÜŊ›Ķtöŋ ‰ÄÕÅE&“{ô¨×ëM;ˆĪãĨ^¸�ƒą˛RÉaŗ-Z øüqcÃŧ<…UUÕ"ąqĮZ<›OŖŅĖ+lüƒĮ ĶĨ2š5ãV*•Ö?)-īävbų:1×tcÍ÷žw?ŠLÎápš>×đĪ×[Ë,~�@ĢŅ24k×˙›Ģ‹ŗ››ëû+Ū��QQą)s›˛~3ÔJmqļáÂĨœÜŋ><  ÃFlšũÔÉá“Éd2“Á ąx4xHāö ÛļīŦĒŽ ęס‰DŪ¸9ŽÍvtttllæîî*.¯ˆß‘āâ,`Đč™×č÷Į‰Syy÷•JĨ“G āßËŊ9=Ãĸ˙ĄÁAÛw&ÄīHPŠTŗĸĻ[<j0ÛÖĢgOĶAJĤ‰.ÎÎqņÛY,VūĮŽŸđëÖíčņ?#§F$:loo¯ÕhcæÎÎËģßė&nŠÛVXXDŖĶŒFŖĐŨŨÃÃms\ŧž^īęę"tw÷z=ūĮäđ eŪ‰FŖųeã'[ĢÕEĪžņ¸ŨëččĀbąöė?Čd0XL…B9}öü‰Sgˆ$ŌА �ˆœžqķĒÕ`0,˜?ˇi$")ëzļT*Uk´={øßŧyË´\(ôđōnŲē]¯×FŖŅ,Z üĤà ]ĨRΚ1ÉüëL4‘H¤Ņh{$šŽĘ-6ō¤‰šøøx›÷‰ã6Ô7X˙¤4}嘖§gd•4ū3Ņōë�ĻL7ßXķĶîĨĨeģvī-ûöî;,Š,ßøéiēénBCEEDADĀ�%ˆ€YĮÉģŗ÷ŊwgīŪŨ™Ũ™Ũ}ßŨŲ;i'¸cˆ‰ 8( ‚‚‰JNMG:į÷vP[õ÷yæņęĒs~Uô|)NWęîŲ˛iƒ#—3ägũ¸܈†üīāėėô°ĨÕÛËë™Ayēķ==ø˙üō[^Įwsõ|ĖûgøfS&OzÖž€5`Ž20”åŖžą]ķų|õÍ÷i)ÉNNŽ6čˇ_ޏVZŗ|Ų(Ûą|(:Ųwŧ& û×w˙NJ\åú˜k`Ž2€šĘĀ+.}íš9šÖ_Ī;8váüų6čh4nUUģšōģā%įŧ�ŧ:āœ×āœ��^Jŧ��`kŧ��`kŧ��`kŧ��`kŧ��`kŧ��`•âââąjĘFķ6��ĀK-#ã0BhÄŲ`žœķ�ĀSXb7==møôRĪ’��žd vģēēÆĒMH^��xŦØ­¨¨<|8F��`| ŽŨâââ÷ßF��` ]6VCō�ĀPÃcW"‘üųΟĀh��Œ‹cw÷î=Ûļm…Ņ��{Oˆ]>„‡Ė>H^��xä ąËfŗwíÚ=VAō��B?Ũlšnwā#ĩŗŨÇ3Įjœ×Ú§Õ6ļRŠ$ ų™7 �°xĐ(™ÍfËHîđ/,_Kdr‘¤ß×ËíŠM=ųi@ÖÎÛāîæÔÖŅ+–Ę­\�đBTÕ4žč^et*ÅÍe žHimōŌ(d_Ņ÷���Æy�ĀÖ y�ĀÖ y�ĀÖ y�ĀÖ y�ĀÖ y�ĀÖ y�ĀÖ y�ĀÖŦŊ“âæíģĮ˛ķÕís÷D!“Ö&ÅĪ |î��āÕ`í9ī(c!¤ÖheįĻ��x5X›ŧŖŒŨ1l��^v0Î ��ļÉ ��ļÉ ��ļÉ ��ļfíUeOļ0*܎NW(•—Ž\̧e��ĀĢjTįŧx<ŪÅÉ1<tĻĪĨĨ­Ũ‰Ë‰œęâäH ÆĒ>��xõŒęœ×ŽN[—˛Úƒī†š=k†ea{g×یcB‘d Ē�€Wҍ’W"•5ˇļ“ÉäæÖvAŸįėÄsqjm�€'ƒOØ´ZLÖß+č“Éå¸W��žŽm���[ƒä��[íUefŗŲžaįãåÁfŗ˜LĶžĢģwL*�€WÕhĪykšBN\Îo.Û‹ÅZ–���xœŅžķÖÖ7ũņ¯˙“R��ā5ãŧ��`kŧ��`kŧ��`kŧ��`kŧ��`kÖ&/™L}gcŌ��ŧėŦMŪÔ¤•ŖĖM2™œš´r4-��ĀĢÁÚëygÎ ×R��ā5ãŧ��`kŧ��`kŧ��`kŧ��`kŧ��`kŧ��`kŧ��`kŧ��`kŧ��`kÖŪÃÖÕÕ5Žu��Ā˂Įã˛k“wô=��°€Ņ��°5H^��°5H^��°5H^��°5H^��°5H^��°5H^��°5H^��°5H^��°5H^��°5H^��°5H^��°5k“W§×8˜1ļ}ßĒĒNI[2'rū?ūDĄTŽmû��01Y›ŧˇoßŲwāĐvÜĶÛģyëöāAgNå}ķÕ%—¯üīį_Žaû��0aY•ŧW¯]ß°ik[{ûÔ Y?ėÚãë(‰^^˙í÷˙.ēx)hVXNn^ÜǤ°ˆõ›ļŦSQysmú† ™ŗg…ÍũŸū¤RŠBmmí Î˙ũī~ëææ2+uíš•ãą‡��0ŅX•ŧ‘áũņ÷î|~Íí›ÛˇmqärķOX^zđāaCcSÂĒx<¯P(nUUįå/ģVB ~˙‡BŨ==[ŪxkElLeųĩüÜŦÚÚēĪū÷ „ĐėА¯ŋøß.į2;��Î3†Åb“WįäæYž-8S8'lļĢ+!d6›ˇŋą‡Ãáņø´ĩkŠK.ëtēüS|7׍ëĶ ‚+÷ūģogåœŌæĩŌë9'ķ>øÕûŖß��˜øŦ}&Å`ÉI ß~˙īú†Æ)“} NžųÆÖ—ønn–/\\\ŒFŖP$jnnŠohôž0¸‘XĖvp°|}2˙Ô'ųŋß|õEā´ŠĪģ��đ2yžäuįķÃf‡æžĖONZŨŅŲŊ|ŲĀKƒH$"„ŒFBƒÁÉ¤šás2ėąŠ¯ūõíņŲ‡öī™65`Ä��āÕķœ×ķĻ$'œ>“Ē zŲR:°ŧĩ­ÍōE[{@`ŗŲžž ƒÁ˛ŧŋŋ_&“Yžūöû˙xúLnÖ1ˆ]�ĀkÅÚäĨ)‰D(i4„PLô2™Lvā`FbÂǟÛÂbwü{—\.ŠDûZ´p>‘@X¯Ņh>˙ōkĨR%‰ūĪo÷Û˙ūBč~mí7ßíøÛ§6™MŨ==–˙Æc�`ĸą6yŖ"#8\NäŧEŠ.!„ČdōŠ1t:=|NØĪmaąK–,Š[9oJũÛ_>A1™öģwî(ģQ1+lnlÜj{{ûĪūßßB?œŅëõië7EĖ[4đŸV̇}�€‰ĶÜÜėééų[nŪöæĖāŋ~˙]ˡ%—¯l{ķĻē{cY��ŧœZZZž­Ī3ÎĢŅhˏ{īŪ†uéĪ_��ŧŽžųڝN7mFˆ§§ĮŽo˙Åb1ĮŖ&��xĩ=˙h��€ĮûŅ���ŖÉ ��ļÉ ��ļÉ ��ļfíĩ •ēŊK ŅęÆĩš‰æaKKyų ĩf‚ŪßA!“Ö&ÅĪ |Ņ…��žĩÉÛŪ% RHl–ũ¸V3ŅddÕéõ/ēŠĮRk´Į˛ķ!yxéX;Ú Ņę(dō¸–2MäØĩ˜°įã�€'€q^��°5H^��°5H^��°5H^��°5H^��°ĩąO^ąDüĪŋ˛ü;ĘĻēēģŋūvĮ˜T��Įķ<s¸ãY9zƒa]jʘ´öRˆ æ?EĢÕR(”;5ĩW¯WŧčŠ��/1HŪ‡Í-NNŽũrEOO¯ŗŗĶW+*o–Ũ¨¤Ņ¨aVÆF9~â×īŊsĩôúĨ’+ũūÃûĩuÕwî.œuėD‹ÉTŠÕ×§ Ɲ{ö9;9âđŋ(¯ĩĩ-7īŨŽŽ+ļnŲDĀãöė?ˆAĩFŗ}Ë&šR‘›oĪ`ôËå›ÖĨŨĢ­-ŋQéÎwëęž”›JĐÛg4ƒgL ģVZvĢú6‘@ ’ˆ[6ŽÖŊ^˛ ę“ŋi6›ąXläœP„ĪŲiՊeƒAĨÖdž84Í?dÆt•ZmFčHV^úšÕ&“IĄTšš8gåŸŠÄąKr88,înM]eõ¤ø•J$ę\-ƒāU6É[|ųJúÚ5Z­Ž °0}íĐĶŪŦÜŧŋ~ō1ßģ˙`¯@ 뗛Læēú›­P(ī×ÖNËĘɋ‹]>ÉĮ§äĘÕâ’Ë vFĐô%‹ÜĒĒúšēw˙žīäIąË— Eb,Srųę”É“—.^ØĐØ(‘Ęō VÅ­đôtŋvŊėüŋ<FŖ&'ŽV(”Ÿ}ņ%—Ã~đ ųŋūã×&“éãO˙2kVŲŠUņq“ŧŊÚÛ;L&ûl/eˇŪØ˜Úø Ĩšĩíri9BheĖ’ŧ‚s]=Ŋ!3Ļ͍”ø˜Ĩûü[ŖŅ˜žfĩßdd6ˇļu\¯¸4ÍŪܰ˛Š[žîüī÷Ä`0ŋûÍ;Uwkü§ø~õũn…RåÆsč%,$x}JB¨ŧ˛*ãxîķ˙��Éh“W(544í;jkīPÆŠŋĒTŠH$"Gq9Ü>ĄĐĮÛĢ­­]ĨRÍ ŽĢoxØÜ˛*>îXVö…ĸâK%WtZįĸRŠBgpk‹Î/8}öŗ/žærØŠ)I"ą8 Ā!4Ų×!Ô'9:qBŽΝģ5<Ëá „čtšBŽEąd×Ūũ! …ŦT(Ö§­-(<—āīĪįģ=ëŽ^(&āņ|7žŸ¯OB\ôˇģ°˜ö"‰!TY}‡B&ku:ŖŅˆ‰Äl !$KB˛~…=ÎíĀb1–TÕh´4*åxî) +ŠōåŌōŽŽnK/å•U!_oOˆ]�^%ŖMŪsŠļoŨäããĒē}§øĘ•đ°ĐWiTĒFŖÕëõĄģ§'Ō'œËá]*æņx~~“ŗsōhT*wärcĸ—y¸ķe˛~ {åęU‘H„ōøîîŪØ˜eT 5/ŋāÆ›.ˇģģ'8húũÚ:„Áp9œîž^/¯îž^GGB¨WЇ’JĨv :—ËqtrÜžu3B¨Ŋŗ“ÉdÖܯŨēiŊŲlūėķ¯BCf'y{†]xčĖÂĸ’‡-m[ÚĻø1ííDb‰—ÛÖŅRS×@"’đ8œÁhtäršÛĒ=øŽ\ģáA3ہ)•õ EbPlÉS'GV§Ķéöf#‰~đöšēžĘ+Ģ,ų �xeŒ*yUjÕũûu# ĶĻdįæÍ™5x”¤Ä;÷PŠ*•āī§×ëŋ˙a×ûīžÍvphni^ē!”°:>+û$•J‘Ëik“#ÃÃwėÚĶÚÚFϐÍfķ@SJĨō›īw:°˜*•z}úZ*•˛wÆ÷?ėRĢ5olۘ”¸*;į¤JŠÚ¸!ũîŊŊ^øčņŽŽÎ„ø8žĢĢ›ī‡]{õŊ‹‹3ßÕĩšĨõėų";;:›íĀåūâäúŠdũr,÷îu:=ģwŋN(’œ*,JXą\o0¨ÕęŌ7ķNŸŨŧ.E­Ņ¨5Ú†Ļ‡!3]œÖŦ^áėČÍ<qR$‘võônLK"ā =‚ž3į/͏˜ŗhÎh2^ŋqs4?�ĀÄgísØĒj]šã_Θ)-+ EņqąŖiä/˙øbŦęIO^UVYõ°ĨmŦđÍgŸŽy›�€Q‚į°�ĀÄ26×ķN@sį„Ŋč~!3+īE—��˜(āœ��l ’��l ’��l ’��lÍÚä%‰jf\K™€‹.á)ȝßÃņ�xX{mßÕąŊS –ĘĮĩš‰&bō ÍDũ•C&“S“Vžč*��ĪĖÚ;)���Xî¤��€‰’��l ’��l ’��l ’��l ’��l ’��l ’��l ’��lÍÚģ‡*u{—@ŖÕk5��0‘Ņi7g.…Le;Ö&o{—€J!ąYöŖėī…čėé žęûĸĢ��ŧôÄŌūŽž>_OˇQļcíhƒFĢŖĀ´X�€×›“ĄPĒGߌķ�€­Aō�€­Aō�€­Aō�€­M ä=uē°ŊŊãq¯ŪŊWsåZŠ-ë™PÎ]ŧÜŲŨ3|y打fU/ Ĩ2+¯`ŦZ�<ŽĩW•=Ώ§ ¯_/įpŲFŖÉŽNONZÍvpxžĻVÆF¸üëow|đū;ĶĻŽĸĖqŅĐô°ŦĸjAT¸ģoĖßš?ķÍÍéß.[4oÄ՚š[?ūđƒŅwWqë6ž>5yՊҎ�x˛Ņ&/BhūŧČeK#„î×Ö}ûŨũáŋ{ŊĮN䰘L•ZŊq}š°O˜›wŠnGWČ[ˇl’IĨĮŗs <•JŨēiがLŖŅ`Ī`ČŠČšsú„ĸō•î|ˇŽîž¨ˆp…RŲĐĐXpϐÅb …ĸø¸ØŲšR™L¯×ûM™ŧhÁü?ũkhHˆFĨnëčøāũwņxÜ3§ĻŽŧōJĨPČ q?G˙åŌōÚú&›Õ+ÆĮ,usužmÕŨšĩ‰+ķΜŗ$ogwOŪés<žBĄŦ[ŗúfõŨŠ[ˇMfĶTŋ) "įäŸ9/ë—ëõ_΍đŲ‡ŽåD…‡zēķKŽ•#„č4ę­Ûw]]œÛ;ģÃB‚5ZmSsËųKW–.Œ˛ô•yâdXHpÅ­ÛžL"=liKMŠØŌ&W(˛ķĪĖ ›uęˆ]ŽPŽM\i6›w<bΰ‹ ŸWp.0`JW¯ĀÕÅŲd2ĩut.ž9ŲĮ++¯@ĨÖ(•ǐāéSũ']žFŖRi4ęŲĸ’_ŊššęNÍÍę;T a0iIņĮrN î—Ëa?÷ģ�€Æv´!Ā߇ĮwuweåäÅÅ.ß´!=ĀJqÉå{÷īûNž´mķÆuéŠX &;7omr¯ß{gj€ŸBĄĀ㰓ŧŊ×$%XÁ`0459qõÖh˛¨2�� �IDATMrķO͝Æd2WÄ<ĘÄööŽÎÎŽí[7ŋķæEEÅjÚh0͙ē&9@ tuw?kÍš?nN_“žfĩ\Žli{4Öa2™.])}sszâʘ>‘aFذGĐgogG"ņ8ŧŦ_Ž:UxaõŠåÛ7ĨûųúČŠĶį/ŊĩeŨ;[7`0˜ÎîžîÁ†ĩ‰[Ö­š|­\ŖÕi ‹Å’ÉäØe‹âĸWÜĒž=3ȞÁˆŨŸWÃa]y.+–/œę_S×˰Ŗ'ÅĮüXx!fé‚Ô¤ø iūÅW¯ãp8ŠŦۆT7ž‹V§‹]ļ(zņüšÚ†Ë-™yûî}ƒŅčĘsؘš´!5Ščō5*…2ÍoräœPĮŸ"õTáųÍëRŌ×Ŧ6›Íu†ôûŦ�0Äœķf4¨TJŸ°īBQņĨ’+:­ŽĮs‰^ž¤āôŲĪžøšËa§Ļ$ Åb6‡  ŗlÅųå9—ÃAŅé4…\5¤ũ>ĄĐ҉‹Â`0öL{ŠT†r`1B$"Q¯{ļ››ÕJĨ>šs !¤P)å ĨešFŖĨ) !äâÄqÛKWŽËúû÷>ŽŅh/—–¯Œ^"‘Č,•„OWŠÕ$‹Å"„æG„ŨŠŠå°•Ͱŗ$õ,{BˆH ęõ†'Ôüh5"AŠüųāˆÄRKûļCM]#BˆÅ´ˇÔoΰCđ†!D āõz=!ąXr,÷‹ĶëõC‹ZC$ņ8Bˆã‰%ë�đ|Æ2yīÕÜĮáđ,G.7&z™‡;_&ëĮ`ąŨŨŊą1˨j^~Á7šœîî^o/ĪK%—§NCY2b@¯ !$•Jít c6›^âr8ŗ¯"„L&ŗD"e1™Ŗ)˜B&ÛŲŅŌ“Wáp¸>Ą˜ÉdX–“É$•Zméˇģˇoø†r…R.WŧŊeŊåÛīvĐëõ–Ą žë՞Ѐ)žÖ`0`ąØÂ ÅA×Ę+BfŗY*ëg2DÁ’°éČ{a6›ŦßļKĐ'ôāģõö šœG˙„õššÅRŲ†ĩ‰Ŋáũú„tœ)˛VĢ3 x<žˇOæÜÕĶk}1�€§ƒä-šZZ[ß`2ÉdĘ{īlG%ŦŽĪĘ>IĨRärEÚÚdĨRųÍ÷;XL•JŊ>}í¤IŪĮŗrņx<J5ôj„^¯?|ôxGGgB|œåĪđ#Įŗ<Üų!>ßÍÃŋs÷>Ŋ^ŗŒ<ęšWF/Ũ›qŒH$šLĻu) "‰tĪÁŖ~đvDXČŽG˜LΎA˜å–­._+›>Í ?ßIå7Ģãĸ—œüņ,§RČa!ņ1Kv<b2™Ļųûšē8ķ]yfëõú% ŖH$bđôŠН6<h–õËYĖĄŗa`ąX2‰œsĒ0qåȟ:Ũ‹˜%§Îœ§Ķh*ĩ:5)~Đ¯Ē‘š8;ö „™YyŽ6…LžY}—Īs)ŧX˛bŲ"Ë Ģc—í;|‚B!SČä)“ŧoßģoåņ�XĶÜÜü„‡Â¨ĒituųīîąUZVnų$m Û|Žsjë›&y{„/žÛĩ}cš} ë�ŧŧĒj­É“–––'Dëķž2$2ŲŽŊtÕĪ×b�0ļ&\ōΝöĸK@Ąšŗg͝=ëEW�x5M {Ø��ā5É ��ļÉ ��ļÉ ��ÖKûéTĘčÛąö62‘¨Öh^ŪUÕ4žč��/=:•âæ2××Z›ŧ|WĮöNX:Âm¯/x&�`â°6yéTŠŋ¯Į¸–��¯ į��[ƒä��[ƒä��[ƒä��[ƒä��[ƒä��[ƒä��[ƒä��[ƒä��[ŗö6…JŨŪ%ĐhŸíáž��đ*ĄĶ(nÎ\ ™4ĘvŦMŪö.•Bbŗ†>Ģ€á:{úĖ•^EbiGOŸ¯§Û(Ûąv´AŖÕŊŧ•�˜p`2Jõčہq^��°5H^��°5H^��°5H^��°5H^��°5H^đrøÛįߚLĻҎĶŌ֞wúBčüĨ+÷ëĮåé|RY˙7;÷GË˙Ū—ĄŅjĮŖe`cŧāĨ$•õŸ8Y€Ę<qRĄTYŋamÃŋɓBM[&y{ŽSyãÁ`0F2i´×đ?ŲÎũ™ãÚ>°°öN �Ƅ^¯?|â$‰HT(UąËēē8[–Ëƌc9L•ÚÕŨķææuC6Äb0g. …b…JųÎÖ § /´ĩw^(žr¯ļŪ`0ÆE/ūaßá�?_•ZcG§Å-_œZŠTëô:?ßI‘áĄGŗķ§Oķ˜âû°Ĩué‚H^"wjęĘ+oҍT …œũÞÃŅK8;rŋŲš˙í­ëkjëīÔÔâņx;:=yUėÍęģ7Ģīēōœ›[ÚĻúOVŠ5›[7¯KŠ­oŧY}×ÍÕĨ§ˇoNh0ߕgŠųôš‹}"ąŅhš>Õ/$xúĀžäœ*K$*ĩ&véÂIŪžųgÎËúåzŊÁ×Į3*|ößŋú~æôiRY? …"•õ;r9KF=hnõņô@eå¨ÔĨR<=tfPe՝Š[ˇMfĶTŋ) "į nœīĘËĖ:I!“eũōĨ Ŗŧ=Ũ˙ōΝ?úíĄŨ‡ŽÆ.]xųZ9€'“H[ÚR“â´´55ˇœŋtŞaw§Ļ–@  „´äU6xcŧnāœØT鍛<g§´äU qË-õ[”WVMõŸŧ6aĨ#‡fø†&ŗ9dÆôMéÉ<ĄĢ§wöĖ ßI^KDq9ėÄø,ĢÖhâc–Ļ%ÅßĢ­W*U÷ëãŖßؘæéî†JMŠ˜âĢŅhq8o|Đl9áÍũąpsúšô5ĢåreK[GzōĒü3įķNŸ[ą|FÅ`0›Ō×lY—RßôP­Ņ`ąX;;úŠe‹ĻúOé—+V,[äãåņ šƒÁШԕŅKÖ­Y]pîĸĨāŽžŪ–ļŽMiɛ͓Ī_ēb0-ËÛ:ēÄÉĶ6ŦMT*UŨ=Ũ=‚ kˇŦ[sųZšFĢ5Ma!Á) q7Ģī.Y•–ŧęfõ]„PmC“ßäIŖŅ•į˛15iCjRŅåk&“éôųKomY÷ÎÖ  fHã×+nzđŨR“âSâr<;ü¨bqXWžËŠå‹§ú×Ô5ĖždĪ`,]UY}gɂ¨MiÉķ"ÂÆd įŧĀĻ„"IWwOoŸ!„Áüœ°R™Ė2ĀsvĒž{Ämš„‰D4 Ã_u`1- ÚŅirĨ2%aåąœSjĩf^D˜›Ģ‹eúχ“}ŧBĩõMa!3ԍJĨ>šs !¤P)å Ĩ§ģ›§ģ[}ピ„8Ë&Y' H$ĸZ­Öë !{;:Bˆ€Į3ėėBA¯×#„ØL„•JQū4ô!I$RŲĄc9!2™¤TĒėv!‰TæĀb!„XL{ĶūNM-‡í`9ö ;Yŋ!dY“NŖ’HD„ŅdBĩĩwÆĮ,5›ÍbąäXî)§×ë5Z-‰DÄbąĄųaˇīÕn<;˙´¯ˇå[ŠL6âQeŲ3BD"A9hĐfÍǏķÅWō̟2É{āī0† yMqØ,æÂ¨pƒÁ ‘ūœt:Ũ:]=ŊV5„Á˜Íf˗–/„"ąŲlÆ`0Ri?‰H$ Û6¤jĩēĪūĩcúT˚u MQsg#„:ēē“x1 ÆÎŽ–žŧ ‡Ãõ ÅL&C(wtvûOžT^Y53hڙ —>ūđ7F“éöŊځîF$ŠB˛~9NûiOY\{ÃÚDËNYÂ!Äq`•Ū¨D‰%Ōē†&wˇk啖ŊĘú™ ƈíËå :†Åbkë›ÄRŲ†ĩ‰Ŋáũú ™ŦŅh ‹-ŧP4-`pãlAŸ!$IXL„‹3™LX,V"9ˆÍfBH,‘¤'¯2›ÍßîÜ<}š#—mÕX ’ØTxhpfVŪáãšũrÅܰ.‡ũņ˙ũü˙ųĢ9!Á‡ŽfwtvxËŠ°ešå¤o8GģĄé፛Õ|×CGŗWÆØŲŅOœŠ$ÁͧÚ3ėōOŸ/2^ÃápáĄŗBĮrOMđīęéå9;I¤2Ļ=Ãr‚ŧ2zéیcD"Ņd2Ĩ%¯:’Ÿšīž˙fį~oOGįĐŅâįësūŌe/ĪĮí—Á`ČÎ?ŨÕŨˇląe ĪŲ‰įâtāH–Ū`pväōœĘ*Ģē{zâĸyÎN{UĒÔ+–-ruqæģōÍÖëõKF=nkšĻøú „\œ{ÂĖŦ<G›B&ßē}/>fÉŽƒGL&Ķ4?WžķāÆųŽ.™Yy™Yy …2qe BhöĖ Œcšl6 Į ī‹Å’IäœS…vtÚÅ+ĨtÅbrØ,ëžĀJ˜ææfOOΧŽWUĶčęĖ˙zĀĢā9æ*“õËĨ2™ß­ļĄéNMíڄ•ĪÔŖTÖčXίŪÜüL[•Š[ˇEiôâų/¤w`cU5Öŧˇ[ZZž­pÎ &˙ãŲ"*…ĒŅj’ãc_t9�Œ/H^0!PŠ”÷ŪØôܛ3í/ę„!:3čEu ^RpU��Ø$/��Ø$/��Ø$/��ØÚ'¯X"ūĮį_YūeS]ŨŨ_ģcāÛŌ˛ōüO?_SũũōŒĖŖŖ)æčņŦ{5#ßX5ÜŨ{5WŽ•>kcrĐFÃr„­,>ķØņšûĩĪŅK՝š /<Į†ÃY&î˜{ėÅúË?ŋüížÃĮ;ēēŅ ŠŅ�ė?į--+¯¨ŧÉ`Ø­OO}îFš[ZårÅ´ŠO]sīCũrEā´ŠQsŸģģËĘâ“Vgįæ ÆŅ÷h™k`î1+ LÜ50÷Ø Qqëv՝š'ŦđbËÖh¯*kiiËÉˡg0dũũīŧĩmČĢ•7ËnTŌhT„0+cŖ?ņë÷ŪšZzũRɕ~˙áũÚēę;wΏ:v"‡ÅdĒÔęë͌ãÎ=ûœqø‘k;‘+•ÉôzŊߔɋĖŋ^^~ŊŦÂd2MœļlÉĸŒĖŖ*•ZĄTÎ ,<wžFŖĶč´S…ŋûĪß ŽgËÆueˆ ™ÜØô`Ķút­V››wŠnGWČ[ˇlbØŅ-=^*.Y0/!ÔŅŲ™›oĪ`ôËå›ÖĨŨĢ­-ŋQéÎwëęgØŲUßžĢ×마Åâø¸Ø!Ĩ~üé_CCB4*u[GĮīŋĢĶkwí9`gG EŠ)Ét:uÄũŌim}ÍĘJ>ß­ĩĨ-""<tÖĖ[Õˇ¯^+ĨĶéT %5%y`Ã?|ôIHČL­V'‰Ū|c‹P$|œīÜŊW^^ÃãßyķÁˇ3•–• …ĸų‘_~ķ]`āÔū~9…LIMIŌ ‰Hôõtˇæ~pPāŗžg:ē{Ždåõú͋Pk4–Éąz}míwjjOš0cúTŊN/’H6Ĩ%÷ôöũxވNŖ)•Ēu) ĩ =Ŋ}+Ŗ— LÜe™{ėHvžŅhdØŅc–,<›Å:t,‡B!Ë劘Ĩ \œœž<wWÉĩrK;ËÎË8‘‹AHĢÕ­_›¨Öhō ÎÚ3*ĩ&5q%…BFŠÔęĸË×hTĒ‹ŗŖe×JŽ•×76qØlŠŦ߲ÄRŪŗ"đĘmōĒĩšÔ”$ž‹ËĄÃGjë<=øƒ_ÍĘÍûë'đøŊûö ˛~šÉdŽĢoā°Ų …ō~m]Pā´Ŧœŧ¸Øå“||JŽ\-.šŒÁ`gM_˛hÁ­Ēj oHwí흝]ŋųõ{fŗų6;$/˙ôß>ũƒÁ\,žŦ7ÜųüyQũrÅ˙~ųÕÜ9aĶ=ønÎNŽÃ늚_‹ÃaŨųnQsĪ]¸xû^A¯ķ<)vų2ĄHŒ4™ËÃæ–ë×!„rN毊[áéé~ízŲų‹y<FMN\­P(?ûâËO?ūŖ“#7mmĘŊššáĨ†Ī™m4˜æĖårØ˙úîß]ŨŨdiņÂųĶĻTTŪŧZZŊlɈGxH§îî %!~e{{GnūŠĐY3Īú˟?"‰?ėŪÛô°y’ˇ—eCŊŅ>ĮŅ‘›‘yônÍũkĨeƒŗ#—K"“ßysč/K §P*WÅc0˜ßūūŠ)IÃ{ņōđxØüđ9’—D$Ļ%¯ęčę>}îŌS‹JŽ-]Ußø€D"NŸęŸûcaØŦļCV^ÁũúĻ^Aß$/ĪÅķ#ÄRƒ ~tålmCĶŒĀŠsápXw7^DXHÉĩ2žŗĶ˛Eķ„"ņņ“?Æ.]„ymâJƒÁ TĒ-sw-š7W"•íÍ8ūŸīoRÛ@;E—¯ųz{Ώ˜ķ šUÖßļ¨déÂy^üë7n^-ĢXē0 !DĨPĻųMvåš8;rBfŗųŌ•Ō?ũî7 æī_~(īYxåö=AĀã Īž'‘HÍ-­ž“~ņW•RĨ"‘ˆ<!Äåpû„Bo¯ļļv•J5+8¸ŽžáasËĒø¸cYŲŠŠ/•\Ņiu<ž‹JĨ @q8œáŨõ …ŽN\d™Ø‰ißÛ# ‘I8!´dŅƒÁ(‰fdâp8V?dÛáõ „,;‰D…BąxŅü‚Ķg?ûâk.‡š’4°ĄÁ`´œö EŽN„#‡sįn Įãr8!:Ļœ{HŠRŠ !d™ĩ„D$ęu:{;웎ĒĢoߑHevtúãŽđN=ÜŨY?ÕŦĶéUj•RŠ<˜‘‰R(”rš|đļ!dĪdJĨŌ>aßāãŒârž4 ‹ųhę/ 3b/t:M­R?Ą…Įá˛B$"iÄ)ĮÂXŽÃÎNÖß5wöųKWžŨu€Íb&ŦŒXÉ2q×Ũûõ–šĮBl › ÍĶŨͲīîƒGpXÜʘ%"ąäŠswYڑHd–y|ŧ<BBąäōĩōĢe:nāųj†JĄXše겁ŠŅ�b´É{äXÖ[Ûˇ9r9;~ØmFŋ˜Į“FĨj4ZŊ^O ē{z"}šNŅĨbįį79;'FĨđxG.7&z™‡;_&ëĮ`ąWŽ^‰DĄîžžáŨq9œâËWB&“Y"‘ē¸8ĢÕjŊ^Ãáō~,˜äí-‰ˇoÛÜÕŨ}į^ BƒAϟϘ^OGGįāÆģģ{cc–Q)Ôŧü‚7n.˜eYŽÃã-áËåpē{z}ŧŧē{z9Ą^ABH*•Ú1č!„y4ÕĶđRYLæ}9WtŅËËs^ä܋Å%­mí;ÂÃ;ŒJĄÚ3ėˇl܀Įãz{–PĐ+¸8; …"ˇ Ā!ĮšžžaF˜wD#öĸT*)TŠ•-<ÁŖ#öķÜcæ>‘؉ËI¤Ķœ§ô „KDRČä3ŠoŨž‚MÜ50÷BöfČ\hb‰tНwdxhĶÖŗE%žîü§ÎŨõS;Ŧ^A_`Ā”úχ †ÃvXŧ į"—+0ØAŸŽ š2B&ĢÔjKk‚>45�CŒ6y§O›–‘y”íāĀsq)ēxŲËÃcđĢ)I‰;vîĄR)T*%ĀßO¯×˙ÃŽ÷ß}›íāĐÜŌŊt1B(au|VöI*•"—+ŌÖ&G†‡īØĩ§ĩĩL!Ÿ—ĪwķđāīÜŊO¯×ĮÆ,ŖP(É Ģŋųū“É4#hēĢĢKwOīŪÎNŽT2ĨŦŧƒī~ęô™Õņ+FŦįæ­ĒÁ+•ĘožßéĀbĒTęõék–ûxy4ˇ´øNōIJ\•sŌÎÎNĨTmܐ~÷^^¯?|ôxGGgB|BČËĶsמũ!ŗ‚‡—J&“‡ė˔ɾų?žiiiqrvޝoė }ô›fīC3gÍzôƒ!ÖÕÕi')!ūûv’ˆ$“É´u톁å8,ŽōfU__ŸBК65ĀŅ‘;ø8[˙#~\/Í-­SĻL~Öv†˜kaT¸eî1W}÷žP$VŠÔūS&565ī>x”iĪPĢ5kV¯¨ŦēĶ#čsä°-'¤–šĮ78d.4žĢˉ“4*U§×E…‡y{ō­œģkNčĖĖ'÷f×h5ëSã–/Î?}žB!+ĘÄø­NˇįāŅ?x›Īs)ŧXb9ÃÅ`0ķ#žßsˆíĀĸҍČ<By�XĀ\eO÷°šåbqÉ[†Î*`ų0*>nŒ§wЏy‹íāāíå9Ęvūûúûß>ƒ‚FĸÕéūņĪ/ūįwŋq˛Áį˜ĢlĀĀG^�LLc2WŲ+~Uؘđöō¤ŅhĪwųęsđņö}ėŽˇėÜŧÄÕņ#Æ.�āŠāœŒŊҜķ0ÁÁ9/��ŧ” y�ĀÖ y�ĀÖ y�ĀÖ y�ĀÖ y�ĀÖ y�Ā*ÅÅÅcÕĖĸ��O—‘q!4|Jƒįįŧ��đ–ØMOOÃX=ĪԓAō�Ā“ ÄnWW×Xĩ É ��5앇gÂh��Œ¯Áą[\\üūûīÁh��ŒŖáąKŖŅÆĒqH^��jxėJ$’?˙ųm��€q1bėîŪŊgÛļ­0Ú���cī ąËįķŸēš• y�ā‘'Ä.›ÍŪĩk÷XuÉ ��ũts°åēŨÔÎvÎ Ģq^kŸTÛØJĨ’(à Āpđ4 đ’2›Í–‘Üá_Xž–Čä"Iŋ¯—ÛS›zōĶ€ŦˇīęØŪ)KåVŽ@UMã‹.€1F§RÜ\Æā‰”Ö&/Jņ÷õ}���`œ��l ’��l ’��l ’��l ’��l ’��l ’��l ’��lÍÚ;)*u{—@ŖÕk5��0‘Ņi7g.…Le;Ö&o{—€J!ąYöŖėŧb:{ú`~đúKû;zú|=Ÿ>oÓY;Ú Ņę`ē�Āk΁ÉP(ÕŖoÆy�ĀÖ y�ĀÖ y�ĀÖ y�ĀÖ y�ĀÖ yÁÄR[ßTVY5|ųžÃĮŸđ-�/k¯įĀ6ü§Lqų–u)ĄŠ[ˇņx|đôŠ–o‡xu|K`Ô yÁxšSSW^y‹FĨR(䄸čå—KËkë›8lV¯@ŗÔÍÕeđVˇn‹$Ōšŗgũ{oF€Ÿ¯\Ž ÉĢã–˙åŸ_˙įûo]žFŖR]œwČü衔WVŨŠŠÅãņvtz랅¯ úD#v ĀŖ `ŧäūX¸9}MúšÕrš˛Ĩ­Ã˛Đd2]ēRúææôĕ1}"1Œŧ-‹UĒT+–-JK^U}īže!•B™æ79rN¨ŗãŖGb0˜MékļŦKŠozˆÁ`^ąk�&8įãB­Ņ¨TęŖ9§B •RŽPZ–k4Z ™byŒļ‹Ķ“žáĘ´gXVÃ<&-˛NHDĩZ­×žÜ5�$/2ŲΎ–žŧ ‡Ãõ ÅL&Ã˛œL&ŠÔjŗŲŒęîí{æv1Ëļ!Ŋ^æÂĨ?üŅdē}¯Öl6[^}\×�Lŧ`ŧŦŒ^ē7ã‘H4™LëRĘ*Ģē{zâĸ#ÂBv8Âd2vt  ,ˇĻM>ĪĨđb ہ‰ÂãņŽÎĄŖ94ÅĪ×įüĨËžŪ^–W‡t=Î; Ā3Ã477{zz>uŊǚFWį'ũm^OĪ1WYm}Ķ$ođÅwģļoLŗŗŖSm�Œ‡ĒšFkŪķ---OˆV8įļ&‘ÉvėÍ Ķ¨~ž>ģāõÉ lmîėYsgĪzŅU�đ"ÁUe��`kŧ��`kŧ��`kŧ��`kcœŧb‰øŸeųw”MuuwũíŽgŨęÔéÂööįŋ[´O(ú×w˙6™LãÚŨŪ‡ŸuĢ1ôßø˛ŽxAŸđ™�Î9¯%ĻWÆFķųĪ˙XĐĖŖĮS’°Ø§ąDœ‘ytôŨŊXÖīČåLž4éʕkļ) €×Äh¯*kiiËÉˡg0dũũīŧĩmČĢ•7ËnTŌhT„0+cŖ?ņë÷ŪšZzũRɕ~˙áũÚēę;wΏ:v"‡ÅdĒÔęë͌ãÎ=ûœqø_ÔĻRĢví9`gG EŠ)Éî|ˇ#Įŗ„B‘JŠ\ŋ˛OØ×ĐĐXpϰWĐ9wŽ;Ÿŋī@…B‘JĨ+b–;rš_~ķ]`āÔū~9…LIMIēVZvĢú6‘@ ’ˆ[6ގt!‹ĩZ­ŗŗBčDvŽT&Ķëõ~S&/Z0īūƒ,ΎNkīčH_›’_pēĨšõVUuõģ‘sįPŠÔėÜ|{Ŗ_.ß´.­ļžáFe%ŸīÖÚŌ:kæÕŌëˇĒĒ xƒaˇ.míãæN?ūô¯Ą!!•ē­Ŗãƒ÷ß5šŒ{÷"“Irš"!>n 4¯]/+ŋQéÎwëęž”›JĐÛg4ƒgL Ûwđ°Ņh°g0Ö$ũânŽŊEΝSzũH ÉM6­Og9°†ô5÷ķ/˙5~Ô(ß*;÷gžš9Ũúå�ŧÂF›ŧj­&5%‰įârčđ‘ÚēOūāWŗrķūúÉĮ<~īūƒŊŦ_n2™ëę8lļBĄŧ_[8-+'/.vų$Ÿ’+W‹K.c0ØAĶ—,ZpĢĒZ øųž~…\šxáüiS**o^--   Eŋz÷-‘XÜÜÜ:wNXáŲ +bĸ÷8„*šZęåéŊ|‰H,ūū‡Ũŧ˙ŽBŠL\ũ:õ÷�� �IDATÁ`~ûû?ĻĻ$•Ũ¨X7ÉÛĢŊŊÃd2YNr6ˇx{{!„ÚÛ;:;ģ~ķë÷Ėfķ?ū4|Îl„Áx{yĖŠŦŧYUT\<wN™Dš<ŖúÎ]„PÎÉüUq+<=Ũ¯]/;ņĸ‡ģ;…BIˆ_ŲŪŪ‘›*tÖL ÂŧõÆV‰ô‡?UŠU#ÉᝠĻ9ŗCšöŋžûwWww}CŖ››ëĘØh īPæŅ˙üͯ,b0šœ¸ZĄP~öŗ\ûÁƒæ˙ú_›LĻ?ũ[ČŦYxÖËÃ{Ácĸ‡ÃēķŨĸ"æžģpņöŊ,3¤•ĒŅh #{Ļ÷FvūiĨR­Ķëü|'á øĻæ–ķ—ŽD†‡:šC§QÅiB\t{Wˇeš].W,]ÕÕĶ{úüĨõkËĄPČrš"féOwūĶûāå1Úä%āq…gĪ“H¤æ–VßIŋ˜ĶZŠR‘HDâr¸}BĄˇW[[ģJĨš\Wßđ°šeU|ÜąŦė EŗJŽč´:ĪEĨR „8ÎāÖHDâÍ[ÕÕˇīH¤2;:],s8l„ÛÁíā`4¯, ũũĻX^•ˆĨ!“ųĶÄW„Đú´ĩ…ឺsüũNU*JAõ …ŽN\ËĘöL{ŠT†âr8!&“aųv°>ĄČ҉ƒräpîÜ­ņpwgąX!"‘¨Ķé-ë>zœD")•*ŊV?⑹S͞īzŽOØ×ŪŪŲĶĶ3°,ĩŅé4…\%Š$bÉŽŊûB YŠP „,Įęq~ĒVĄPôËû‡÷BĄRÔĩũî73›Í÷ë˙ãŨ7č4ZGg7ĪÅŠ¨äÚŌ…QB‘8jîl˙ɓĒîԔUV%Ä-ˇ,ōŠABæĩ‰+ ƒRŠļž_�^ ŖMŪ#Į˛Ūھ͑ËŲņÃn3úÅį0–s%Ŋ^O ē{z"}šNŅĨbįį79;'FĨđxG.7&z™‡;_&ëĮ`ąWŽ^‰DĄîžžÁ­+ēčåå9/rîÅâ’Öļv.—[råBH(ÕÜģ10…BˆËát÷ô $ôą9Ŧáe Eĸ­›Ö›ÍæĪ>˙*4dĻe„JĄô E–Í‹/_E™Lf‰DĘb2BŊA€ŋ_ŸPÄbą0ƒfĖúŠģ^/¯îž^GGΐž´:]Ū˙ë§F“ņÖ­*2Ŗ‘ŒØé`Ž\GŽ{ŲŌÅzŊ^$~ŠWЇ’JĨv :—ËqtrÜžu3B¨Ŋŗ“ÉdĸaIũ#öĸVŠ)dŠ•-X`0˜”„•ĮrNŠÕšya<'Ër"‘xį^mMmŊTÖO§Ņˇš§ģ›˙dßŨā°¸•1KžŠk�&žŅ&īôiĶ22˛x..E/{yx ~5%)qĮÎ=T*…JĨøûéõúīØõūģoŗš[ZŖ—.F%ŦŽĪĘ>IĨRärEÚÚäČđđģö´ļļ‘)äÁé6e˛oūgZZZœœëë—,^čæĘûnĮN…BązU<‹%“ÉGŽgYVžąī`ÆŪrš<-eÍđ˛›[ZĪž/˛ŗŖŗŲ\îŖŦôōō´dŸīæáÁßš{Ÿ^¯YF&“B]‡éęîŲ˛i‹š_[­´Ė˛aRâĒėœ“vvv*Ĩjã†ôēēúÁ} g'§]{÷ŅéôŠS NZ–‹%âožũáOũŪō툝97|ßÁŒ=ûĘúûįGEX~[XčõúÃGwtt&ÄĮņ]]ŨÜx?ėÚĢ7č]\œųŽŽĪđ㊕ZE&“žu¨AĢՑˆ„mRĩZŨg˙Ú1ÍŠŲlB_ŊîÁw:ķęõŠöÎ.„e9‘€×ëõ!‰T†K¤S|Ŋ#ÃC›ļœ-*؜>€—ĖUö _}ķ}ZJ˛““ãå–Ŗ&û>Û¤\Ou"+wMōhį0,-+ EņqącRŌpgĪ] ’ˆ įĪņÕĮÍUf2™Í1 8΍į˛dAä˙~ŗĶÛĶŨĪ×§°¨Ø•įėÄå\š~cۆÔĖyŪžîKDîË<áåÁĮ Ô#čK\s4;ŸFĨęôē¨đ0ŋÉ>ã´w�<+˜ĢlėĨ¯]s<;įŨˇļ?õ²Ņ3Ë–.ī^FŠO(Ēol|˙ˇžuC,ģ)=yđ’˙ú՛–/üŊkD†^ūëˇļ ^˙Ũ76>GÁ�ŧāœŒĘsĖĪ ĀKmLÎy_;)��āeÉ ��ļÉ ��ļÉ ��ļÉ ��ļÉ ^ˇn•ŧč*�3ŧ��`•âââąj î¤�ãĨ˛ęNÅ­Û&ŗiĒߔ‘s˛ō TjRŠ ž:3čī_}?sú4ĩFĶŲŨķÖæuzŊ!ãD.!­Vˇ~mĸZŖÉ+8kĪ`¨ÔšÔĕ/zW�@‡Bƒ§4 H^0.L&Ķéķ—ūø_ŋB]š^a0]y.áĄ3JÕˇģö‡Î 2Mŗ‚§ŗYĖ]ŽôôöÕ5=đõöœ1įAsĢŦŋ˙lQÉŌ…ķŧ<ø×oÜŧZVÁ´gŧč¯5Kėϧ§Y?ųԓAō‚qĄŅjI$ĸå&ėųaFŖQ,–Ë=…Ãâ,3㠄Xö „‘HĐô‰lНBČĮË!$K._+ŋZVĄĶ震MŖ€- ÄnWW—›ÛØ<ƒ’Œ ™ŦŅh ‹-ŧPėåá.–Ę6ŦMėī×7 _ŸÃfõ úĻÔ7=Ä`0ļÃân<š\Ábkë_äcëĀël v+**‹‹‹?üđˇcŌ,$/ &>fÉŽƒGL&Ķ4?gĮ^03+Ī‘ÃϐÉ7ĢīYNčĖĖ'÷f×h5ëSã–/Î?}žB!+ĘÄø˜˛ � ‰Ũ÷ßoŦF`Æ0*0cxU ]†`Æ��'ÃcW"‘üųόÕĩ ŧ��đ #ÆîîŨ{ļmÛ:VŖ ŧ��đŗ'Ä.Ÿ?fΆä�€Gžģl6{׎ŨcÕ$/�� ôĶÍÁ–ëv>R8Û=|8300pŦÆy­ŊļĄļą•J%Q†=ŧæāÚđ*1›Í–‘Üá_Xž–Čä"Iŋ¯×Ķī§›'`ō]Û;bŠÜĘõÁëŖĒns�¯ :•âæ2××Z›ŧt*Åß×côũ��€q^��°5H^��°5H^��°5H^��°5H^��°5H^��°5H^��°5H^��°5H^��°5kīaS¨Ôí]V7ŽÕ��ĀDF§Qܜš2i”íX›ŧí]*…ÄfŲ˛?đē)uĀĢD,íīčéķõ툭mĐhu0Q�ā5įĀd(”ęҎãŧ��`kŧ��`kŧ��`kŧ��`kŧ��`kŧ��`kŧ�üÂÎũ™/ēđęŗöN �Æ\oŸ0¯āŦ=ƒĄRkRWR(.onm/8[ääČÕéõ<gĮ…Qs‡l˜•W Rk”JUHđôЙAŨ=y§Īđx …˛nÍę›Õw+nŨ6™MSũĻ,ˆœ“æŧŦ_Ž×|}<ŖÂg˙åŸ_ôÛBģ]ē°ģGpëö]WįöÎ`VÛÔÜrūŌ•Ĩ Ŗl}8ĀëÎyÁ sęĖųĨ į­M\éįë}ĩŦb`yá…â¤UąkV¯@f3B˜![ŒFWžËÆÔ¤ ŠIE—¯!„N^XŊbųöMé~ž>r…âôųKomY÷ÎÖ  Ļŗģ§ģG°amâ–uk._+×hĩCZÃbąd29vŲĸ¸čŎĒgĪ ˛g0 vÁxƒs^đÂŒË×Ɲ–Učt:g'ĮåRYŋ“‰âš8 ß ƒX,9–{ ‡Åéõz„D"s`1B!ÁĶUj5‰DÄbąĄųawjj9l„ƒągØÉúåÃdŲ3BDQ¯7ŒË~0 $/xa8l‡Å "Üx.ršƒũųĪ/:&ë—;rŲ]=žķĐđmhjKeÖ&ö „÷ëB6ĢW ôāģ^-Ģ˜âĢŅh ‹-ŧPp­ŧ!d6›Ĩ˛~&ƒÅâL&‹•Hd#Ve6›Æmx’ŧ0qËįŸ>OĄ eb|ŒV§Ûsđč‡ŧŊlŅŧŖ9ųÎN\­V‡Á ‘DjYnŲĘÅŲąW ĖĖĘsä°)dōÍęģqŅKNūxĮS)䈰ø˜%ģ1™LĶüũ\]œųŽŧƒGŗõzũ’…Q$qöĖ Œcšl6 Į / ‹Å’IäœS…‰+Ŗm{0ĀëĶÜÜėééųÔõĒj]šã_xÕ<Į\e]=d2‰íĀĘ?sŪį23hÚ8ÕĀs¨Ēi´æ-ŨŌŌō„h…s^0ጆCĮ XL{ŖŅģtá‹.€ąÉ &žÛoŪŲöĸĢ�`ÁUe��`kŧ��`kŧ��`kŧ��`kcœŧb‰øŸeųw”Muuwũíë—Û†Ū`øę_ßIĨR+×?uē°ŊŊãY{)-+Ī˙ņôŗn5†ö8ÔĐØhMņĪz@Ë<qōAsëã^•ĘúŋŲš˙9š`â{uÎyKËĘ+*oZŋüų]š<ƒÉd>uM˯‡•ąŅ|ūhSúĸXS<_wėDŽmJāÕ0ÚĢĘZZÚrōōí Y˙;o Ŋ¨ĸōfŲJŠfelô‘ã'~ũŪ;WK¯_*šōŅī?ŧ_[W}įîÂųQĮN䰘L•ZŊq}šŅ`ÜšgŸŗ“#˙‹ÚúûåC–gdUŠÔ ĨrNXhP``ášķ4įĘģtŠdør…RŲÜܲuķF„Đ_ūßgëĶRöė?4}ÚTĨJÍ`ГV¯ēU}ûęĩR:NĨPRS’¯•–ŨĒžM$ˆ$▍ëʸZzũOũΐ]Û˛qŨž‡0XœÖŪŅ‘ž6ĨĄŠŠĄĄąāLa¯ /rîw>߁ …"•JWÄ,wärŋüæģĀĀŠũũr ™’š’ôäÃ8âņܲqŨC™"B&76=Ø´>ŨŲŲ)7˙” ˇĪh2ΘfŲJ$ų¯īž°ŗû6 ö ƚ¤„ÁŨí=p(rîąXzŖ˛’Īwkmi‹ˆ5sH/^ž=ŊŊJ•ŠFĨ>ëû§ôÆÍˇn÷ úÖ­I ĶЇŽæĐiTąDšMŖ=j­ŧ˛ęNM-ˇŖĶ“WÅÍÎ'đdéaK[jR<—ÃÎ9U(–HTjMėŌ…|×Ã'N’ˆD…RģlĄĢ‹ķŗ–€ ŒöœW­Õ¤Ļ$mÛ˛‘Ëa×Ö5 y5+7īí7ˇmŨ´™MŊŦ_n2™ëę8lļBĄŧ_[8-+'/.vųĻ éūSŠK._-Ŋ>#húúôÔ)ž“75dšŪ`pįķß|cË[7ž;OŖQ§.šåČ厸|~Tԃ‡-jē§§×ŽNˇˇg¨UĒ5I [6Žģ}įŽBĄ<z<ë­íÛļnÚ ëīozØ\vŖ"&zŲ[Ûˇ.Y¸Ādzt#ŋ\Ą P(<~ČŽÕܯEŒˇ—Grâꨈˆĸââšs˜L折Gw –\-õōôÜŧqŨ†õiGŗr08œBŠL\ŋeãú›UUO=Œ#Κûĩ8Öī–¸:>xFĐí{54ŋĩ}ëÛÛˇœ9§7<š˙ƒAOŪY<;ÉÛ{HėĀá° %!~eRÂĒëeå#öâáîŪÚÚöėoäÁwMKŠ Ÿ}š´\ŠTE͝žfuĜвĘǁu0ĖĻô5[ÖĨÔ7=Tk4X֕į˛bųâĀŠū5u m]b‰äiÖ&*•ĒŌ7yÎNiÉĢâ–į>÷%`Ŗ=į%āq…gĪ“H¤æ–VßIŋČJĨJE"-9Ååpû„Bo¯ļļv•J5+8¸ŽžáasËĒø¸cYŲŠŠ/•\Ņiu<ž‹JĨ @q8œÁ­I$’ÁË1#‰fdâp8V?°Úã–cą˜°Ų!7Ę+Å2éü¨„›ÍÆ`0!†Ŗ§O T*fd"„ Ĩ\._Ÿļļ đ\vvn€ŋ˙Ā_Üj•šJĨŒ¸k!.‡ƒb2RéĐŠX„BĄŋß„ÛÁA"–"„XLĻĨwËŋO8ŒO8ž! !D$ …@(’ˆ%ģöîGQ(dĨB10*ōäEq8ė'ü”Y?õĸĶéGė…N§*UĒ'´đ8Ύ\„‹y§Ļ–H$ŪšW[S[/•õĶi´ÁĢe, ‘ˆjĩÚ2ØŖŲňĨR%‘Ę,Å´g1íŗķĪtu÷ôö Ž-�Đh“÷ČąŦˇļosärvü°ÛŒ~1ɍJÕh´zŊž@ t÷ôDú„s9œĸKÅ<ĪĪorvNJ%āņŽ\nLô2wžL֏Áb¯\Ŋ*‰BŨ==ƒ[c2í/¯­Ģ‰ÄÛˇmîęîžs¯!„Á “Ųü¸åĄy‘s˙Ŋk¯ÉdŠ_+ë—õõ M&3‹‘ˆ%.ŽNö û-7āņ¸Ū^‹ÅjljÚēiŊŲlūėķ¯BCf:;;!„ČŠJĨqך[Z{‚�ŋ>Ąč˙ˇwßQ]ųĀSĻQÅKT°a{‰]ą`DM4j\ŗŲ˜ŧŧˇģŲėK6›d7û˛ûŪž7‰%Xĸ‚…ˆˆ(KĎĸXKzŸSa`æũ1†āĐ. ßĪ_pįĖ9į–ųr¸sīšbą˜ÅbŲlļēÎˤŌâ’ĸ!eeåЏU›ą™íYPPXŋ€L&•+äŋZŗŠˆō 럌n~eīŪÍt>¤mÅ`0ĩáT)Uę~ĪõVi4"īÔ —C‚G~árz~a‘Ŋ€Åb9vęˇīžSkĩŪē›UĢÚI}ŗŽ^#"ĩF{˙áR‰¯¯X4y|DMMĻÁŸ@€§D{“7lđāØ=û$žžūū§ŋ?×+$¤ūĢKĸmúzŸĪãķyŸ`ąX6n‰Y˙›_K|}ŗsrgN›JD DH8Äįķt:ũ˛čÅã""6ÅlËÍÍãō¸õ?fËũ‹KJˇīŒõSČų\^ڕôāÉ)ĮDÎitšT*éĶĢŸĪ÷÷S¸ģ쑎wü„˛reøČá?jaäÆ-_s<9VĢuÍĒŲ9šß<íå%”H|e˛ĮŖoo/ĄÉlļÔÔx°ŲĢ–v5Ŋ°°hwÜŪĸâ’՝ްΎŊ7ū€ũƍŨą+vûÎXNˇlÉK-nÆņã"ėËßũĪ?}ōá\.§Ņíy=ãFũJ‚ƒ‚ļÄlˇÔXüũũ‚ë^j~e[ĩĮm%7/oqԂVÕCD6›­¸´,ūБŌŌōå/-(-S?š[P¨IČÎ))+'"6›-—Jwī;(đôísōĖ9‡JüüÛvī3MsĻO ôßs ).>ąR§3*\ÖėX ŗt¯šĘūõÕæĨKËeRĩFŗ}×~ûNkkH9~ÂËK8~Ŧãķiė_FõëÛēIšZtā`âĸ ÜÜÚõ_s›WÖIššy)ߝ|c]ã_ ļaŽ2€§™Kæ*ë:W•5O­QoørSŋįž“Ë¤-—nÚ´§\ΏŲļËWÛ`ĘäIíŒŨŽVSS›˜”ŧtITgwāYŌŊÆŧĀ<ŒyĄ‹Á˜�♄ä�`’�€iH^��Ļ!y�˜†ä�`’�Ā)ŠŠŠŽĒ Ī�hYll5œ9¤m0æ�h=v—/_æĒ đŧ��ÍŠ‹Ũĸĸ"WՉä�hR]ėϧ_‹‹Ûƒŗ ��Ģ~ėĻĻĻŽ_˙&Î6��t †ą+xōQ)íä�pÔ0v5ÍG}Œŗ ��ĸŅØŨēuÛk¯­ÁŲ��×k&vƒƒƒ]Õ ’�āąfbW"‘ÄÄluUCH^��ĸŸoļ_ˇ[÷•ZŨh7.nOhh¨ĢÎķ:û4 ŦGš|>‡Įåē¤Uč>đ4 x†Øl6û™Ü†?ØÖTčTšĘžŊ‚ZŦĒų§9;oCp <ŋ°L­Õ9Y ÎĖGŨ�×ōyAū.x"ĨŗÉ+äķžīŌūö���įy�˜†ä�`’�€iH^��Ļ!y�˜†ä�`’�€iH^��Ļ!y�˜æė=l“9¯°Ô\UŨĄŊ�xš ŧ ?Ëig=Î&o^a)ŸĮ‘ˆ}ÚŲ�tĖOÔŅÔÚʂ’ōž=[ž1§yΞm0WUcĸ2�čæ|EŪzƒŠũõā</��͐ŧ��LCō�0 É �Ā4$/��͐ŧ��Lsöz^�xĻĨœøž\ĨŽ­ĩ† >,Ŧnyl|ĸÍfķ‹nŪÎ|˙wo9ŧëúÍ;ˇîúûå 64t⁤ŖF“Ų`0† 9|Čž„Ãlĸ’Ō~}zëôúr•zíŠĨĨåʤŖßųx{MæĨ‹æņx¸&õ ķt}E%Ĩ9y¯.[ŧjųâ“gÎ×ÔÖڗįæTUU­ˆ^4æ…•<ßÖÍ͍ËåΞ>eîĖŠé7kjküW.Zą4ęôš‹DäæîŌ#(rÖ47–›ŸB6ötƒÁh0“œ6yBôĸyúöž–ÎčÚ> 0æčú”*F[ą{˙A"âr9ƒŅĮۋˆ´ÚJ_ąˆˆÄ"nwĊ}ŧ‰ČĶÃĶbŠaŠÕšũ‰Éînî‹Å^ĀĮˋˆ<<ØŪ^B"ō`ŗ-–ĨZsî╠iéÕÕÕ~ 9#kų,Aōt}R‰X&•Ŧˆ^DDE%ĨöØ%"ĄP`ęj´fsU‹õ<ü![­­XŊ¨´LyīÁÃf[ô:ilP€ŋN§gšákGH^€Ž/ĀO❨š÷€ĨĻÆO. đSėŪpXØ AúŊ˜Ÿč%|"JģvŖ¸¤táܙÖãī'/-Sî9$—Jx\îõ›wšjq‡SNōx\ŊŪ°(r–PĀī¨u{6ą˛ŗŗ{öėŲbš™ũdß�hģÖΘSS[ûāŅƒôĢĒĒū߯būøov\ßēŒ™œŲČ999ÍD+Æŧ�ŨÛŨũöŨŦ‹i×ŦVëܙS;ģ;Ũ’ [[ļx~gwĄ;™o��Ļ!y�˜†ä�`’�€iH^��Ļ1šŧÉ)Įķķ îŽÛ›y/˙ˇŋ÷ūŸÛШķõoÚ˛Õųj[U¸NQqņ†/79_ŪžÅļīÜũđŅŖ64÷´šs7ķüÅKΗ/WĒūõÕfĢÕędųļí”ÎŨŧjúī˙üœœëüĩë7“’;žSŋČzđCÚĩ顎Ÿ>Ëdģ]^{¯*;’rüâĨ4‰Ä×ūkøˆá“&ŒkĒđŧŲßĶÔuĘą¯,_ęü[Ūx}m‹e.Ĩ]ņ`ŗG†pϰķęĒuXÎäëPžÜôoëß<¨UīÚŗ/>zņ"ˇ–î=í Â0g:>bØÅËiEÅÅūū t‰ˆžī˙ĨgÜbĻšîÃ×ķNž8~ú´_ŽÁŽĐVüß_…†ĒŦÔņ¸ŧĨKĸūü—ŋõîŲŗ_ßį˛<7f4Ë‹OHôđ`ķųü5¯Ž$ĸĢ×2ŌŽ¤•”ŧūÚj‘ĪöovsšNŋ0rnppPĖļoÜÜÜÄb‘ÕöÄØ'vĪ>ŖŅ¤7F9fô¨˜íßL™8ĄOŸŪ§žO%ĸ뜜œė܌7ę÷ööÚą3–ĮãiĩÚ9ŗfôíû\]÷’’~öéĮß&$Ē՚ššš;wī}šáŸiW¯fܸéÁöđööZ9īø‰“0 0ā‹/7öéĮ…… ‰‡}ŧŊ+uēW_^–õāáÕkׂƒƒrsōƎ9b¸ŊĢ••睎íđSČŨŲ7xâáä˛ŌōZkí°ĄaaƒCëĒ=sælũ•Úžs÷¸1ŖíoÉÍÍKLJz õ:ũšÕ¯Úg'i¨\ŠÚš;îw˙ū6Ŋ÷ūŸ?ûôã¸}ņzŊžēÚ2hā€)“&fÜŧuáâ%ĄPČįņ–.YŧcW\mmˇ÷KQ í5ŧ˙§ÃÇWUUĢTĒukW_˸qåJē;›ũÆēĩ‰I‡ĩ‹e@˙~S&Mt(ŠTŠö{P,MĻ•¯,ŗTWąq‹H$  |øđŅŅcĮÅbąRЊœ;ģūęGŒåĐC{7TjuUU•ŸŸ‚ˆžMHlĒŨ——Eˇv§89nFŗŲėpœ|øÉ_G†‡›Ļŧ‚‚[˙›reyũ•đßģũ›],7w/Ą ŋ `yô…Bî°˛uĮ[ÄčęˇhßY­ÔZk>Ə=wūâŌ%‹øh>Áa–ČĪ>ß8<l°ļĸ’X$āņ´•r™tÚäņWŽŨ¸™ÅfŗŊ„ÂÅķg§gÜRi´ą¨ĩÍAķ\ŧg/\ĘúyîŒEķįųˆDzƒaŅüH‹õû?~°tITĨfÆ´Š~~ŠŦˆ(!1)zņ   ËWŽčõz" é4eŌÄ'Oßŧu›åæ8oö˞˛ōŨ{ö-œ?¯Ēējũ¯ëôúĶgRëĩÔÔôž0~lĨN˙?˙÷yÃĪΘҪ¸ÎđaC3īe՝߯bõęŲsæŒUjõÆ-[˙ôĮwëē—”|”ˆė´uĮÎukWąŲî,bŊžv ‡Ãy˙ÃO.˜øķˆãāĄÃķįÎéŲŗĮÅËi'ŋ˙>¤Gˇ0r^~~AâáäēäŊpéōĐ!a/N™”qãfYYyAAÁ?f˙îßßļZ­~ōiøˆöjå2Y3+u÷ŪŊžũž›=cēRĨvcąœÜ;6›íΝĖ˙üÃīŧŊŊrsķˆh_üŋ|ô'ާᖭÛø)›íîÖ+¤÷¤‰ãŲļĩ5c#FËå Éí,��DIDAT˛Ø=ûîdŪcģģs¸Ü7ÖŊ–Ÿ_PXXôÎÛoÚlļ>ü$bô %/^J›;{Æs}úœ=!õėšņãÆi4Ú÷ß{×fŗĻ_˘3kæĨ´+Dä°ú#†wčĄŨOŲ9Ŋ{÷"ĸæÛũ)'§U;eč°æœēŖÚá8Š­ąŽ~a¤L*ų×W›‹Š‹“’Ö_Ų9ŗ~ūī„ÅęŨ+dâøq׎ß8š:aė‡}]wŧ5ÚŽC+>Ē˙qøí;oõ I9ū“{ŋŽ}–ČßŦ]iĩZ˙ūųĻĄaƒjk­ŖÂ‡ųx{ũáŖ˙úäŋõđ`˙cÃæi“ĮŗXŦW—ŋäéáņé?ŋ4™Í­mœä‚ä8nLũ1oĨN/‰X,ą~ŠTZW@ŠVK¤"Šõø WČåDäÉáčõúJ]e~~aII‰ũíF*‘‘—PČãņę*aKŠRíŠŨãîî^]eiž‡õ믨Ŧ|~@"’øújÔچŨ#ĸ”ã' ÅđaCíŋÆí‹įp8ƒŅŌ ĄrĨJސ‘\*Ŋ}'3¤GąXLDžžžÕÕŋÖh4Ŧk¨LŠŌ¨51Ûŋ!"kĐëYŠ)“'Mųî˙ģA&•,]e_h2™žÚCD/E- éÜpŨY,ÖĘW–íŽÛk0_œ2I&— †]ą{ˆH¯7čt:"’J%ī˛/ņ‰´Z­—P(“Jˆ¨\Š”+dö:}D>Zm…CÉreųŠĶŠgΞ¯ŽĒđ'"___77ÖĪ“Á>æ°úFƒĄ~CBz؋FŸįLģ­Ú)N9JĨ˛áqbŸP‘ãéiŠŽn¸˛udR)‰DŪZmEŖûÚáxsāĐJũņ…ŖąÕØp–H"˛ĪX&đ9O"Ēũų|úCG9O“ÉdąÔ´ļ!pCw×ĸÉeŌââŌŪŊzž9{.,t°CIšL.õ•LŸ6Õbą¨TjŊҍTЈ¨ĸĸŌd4ÕËē_ĨR˙ęĩUEÅŎīf‘§‡guM ŠTj‰Ä—ÅbŲlļ†=‘IĨÅ%%DCĘĘĘ%RqÃîŨē}§  pŨÚÕDTU]täčgũ¤ÖZ›‘qÃJ6‹ŦõĒ•IĨÅ%Ĩ}zõ*.)•Ë›ü8‰D>*•ŠˆŠKJˆH&“Ęō_­YEDų……"‘Č^mÃ•Ē¯¸¸töŦé|?éđŅĢW¯ÛGŠ<Ī~bĄŽ§‡GM…ˆL&“Él2›ÍOĪ7ßXg6›?úË}öéĮ>Ū>ĢWŽ`ŗŨKKËÄbņŨģ™Ŧ#čŌ˛2??ĨR4$´Æbąo ™TšzîY­6F+‰JĘe˛Y3§‡ôލ¨´O h¯Øa_8Ŧ>—Ë­ßÃē?x|¯\ŠrĻ]VëüNi~#?Y‰ãqR_Õ­ŋõ>? \ЋŠ÷5=yŧ5Īáã@DFƒ‘Īoõķšš%ԁÅb9vęˇīžSkĩŪē›Õč'\ÂgÎ]ČĖz|ņ@ppđô[˜wc҂ȸ‰l6[ĀįO?ŪáÕqc"vėŠŨļcWEeåÄņc‡ęîÎŪ¸%F$ōņņņŠ+č_\Rē}gŦŸBÎįōŌŽ¤ ~ėø‰û÷hĩZ__ą\.ģ—õāâĨ4‡ú'ŒģcWėöą:nŲ’—^ĩZm›cļ… l¤ĖŸ7×OĄˆŲžC(4đhĘņũû'§Ģ$F-šŸp𐗗—Ņ`\šbųũû]åq›bļåææqy\›Í°%fģĨÆâīīÜ#9娂Č9+UŋƒÁđÅÆ¯}Å"ŖŅôĘōčĻ6¯ˇP(Üŗ˙[Ÿ/=<<Nž>süÄ)7w÷ ãĮQÔÂȍ[žæxrŦVëšU+Öāîæ~íúōōrŊÁ8xĐĀ›7ģüõÖ‹eöŦé\.ץ¤\.;pˆĪįétúeŅ‹‚ĮgĸíĪ5ØĀ>*wXũķæ:ôĐŽW¯žöĀmąŨښZįwJÃ#ĮžüRڕŧŧ‚ē&š?Nˆhá‚Čú+[˙´{aaŅî¸ŊEÅ%Ģ_]!—IöuS;ŽQ??ÅO9šŊ{õjU%ÔØ,‘cŗŲrŠt÷žƒo@ß>'Μ jm[ā Ė Žė_õ¸ļdÛ|ūÅÆeK+<Ņ #Ú­Ôé/^ē4kÆôvÖc˙R´_ßVLÕØ*˙újsÔĸųM\ÛĐÚY"Ą \2K$§×ō藾=˜čüõŧíáîî6yâDjŒ7ƒšŠ]x†`Ė Đu`ĖË�Œy�žIH^��Ļ!y�˜†ä�`’ ëĶ ’ŽŅ_ū{Cg÷ˆŧ�ŨP X<Ng÷~gt}ڊĘŨûžĩn%Ĩœ¨Ôé FãڕˊKJ|wÚ[(ŦÔëWŋŧ$3ëaÆ­;ū~ų…ÅŖÂ‡ ØŲē¤ĻĻNš4É%U!yēĢÕúˆĄū ųž„Ã÷î?âq9‹æÎRČĨņ‡Ž<ü!Û~Ÿ÷ėéS ‹KRN|ä­/66Žˆ\5—’ [a)dR"ō‹*+u^BÁŠŗ8žžyų…}z†¸šš‰}ŧ‰ČĶÕÕgŨå˗5œ^Ēmpž [ąŠÔ"Ri´"‘÷Áäc3§N\<ļÄWŒ™ÉšRģEEEŽĒc^€n„íΞ~ëŽZ­1Mû÷Í/,Ž?tÄW$ōSČĪ]ē2>⅖Ģčfęb7=ũZjjęģīūŪ%ÕbŪ€Žķ6¸–CėŽ_˙Ļ@ Āŧ ��ĨŅØuUåH^��G cWŖŅ|ôŅĮŽ:Žä�xBŖąģuëļ×^[ƒk��\¯™Ø nä!ŗmƒä�xŦ™Ø•H$11[]Õ’�€ˆ(55•~žnˇî+ĩēŅn\ܞĐĐPWįuöǞŦGš|>‡ĮmõãĻ€1¸ĒŦl6›ũLnÃė?k*t*Meß^-?’šųĢƜŊ“ĸG"¯ T­Õ9Y�:ōĖGŨ…ŽLČįųģāÎg“WĀã>ß7¤ũí��Îķ�0 É �Ā4$/��͐ŧ��LCō�0 É �Ā4$/��͐ŧ��LsöNŠëˇnīOH6™ĢÚÜË‰Žš7bHX›k��čœķîO8blGė‘Ņ\ĩ?áH{j��čœM^“ŲÜÎ YD&ŗš}u��t8Ī �Ā4$/��͐ŧ��LCō�0ÍŲĢƚ7y|„—P¨7Μŋėǧe��tUíķ˛Ųl…<bäđ �˙œŧ|…L:nôH…ÜÃÃÃUũ�čzÚ5æõ ^^˛ $8ˆˆ^1Ôž0ŋ°h{ė~ĨJã‚Ū�tEíJ^ļ";7ŸËåfįæ—•+üūŠÜüBÄ.�@3\đ [UUuEEeiYy…Ngnß}n��ŨŽm��`’�€iíŊĒĖfŗųx{õé"‘ˆ}E"ąČ§¨¸Ô%=�čĒÚ;æÍzø)dŌū}zË$žnnnö%��ДöŽyŗüđÁ_˙Û%]�č&pž�€iH^��Ļ!y�˜†ä�`’�€iÎ&/Ëmįä6"—Ûž:��ēg“7:j.ŋ}šÉįrŖŖæļ§�€ŽÁŲëyG 1$ŦCģ�ĐMā</��͐ŧ��LCō�0 É �Ā4$/��͐ŧ��LCō�0 É �Ā4$/��͐ŧ��LCō�0 É �Ā4$/��͐ŧ��LCō�0 É �Ā4$/��͐ŧ��LCō�¸˜ÍÖƒŨ¸\ŽFŖaĻ7��]žÍfĶjĩ<¯™2,‹ÅĸRŠL&cŨ�čÚx<žT*uwwoĒ�ĢÅQ1��¸Îķ�0 É �Ā4$/��͐ŧ��LCō�0 É �Ā4$/��͐ŧ��LCō�0_ÜŲ}��č^p÷0��Ķpļ�€iH^��Ļ!y�˜†ä�`’�€iH^��Ļ!y�˜†ä�`’�€iėÎî��tG×oŨ؟pØdŽb˛Q—9bHhũ…ûo͝“ÖėʆD\ÚIŅaM˜�:ķąKD&sÕū„à _O"­ÉÅ iMôkĮvž€ä€NĀ|ė6ÕnEËÕͰZD#y�˜†ä�`’�€iH^��Ļ!yāđÁīßîė.¸Žį€§ —Éœ5M*õeËŨŨíđą“9yŌ“ūRúÛ4â{‹¨˛ŠŪûŽ~ŌĐ{Č\CŸ_"?!íĻwSzaĢkFōĀĶåĨsō Šâ!"_ąh|Ä uÉë§Ī›ųbĨNį%îOLöņōš3cŠŪ`đųqņ‰oÁœ:ģ˙`˛ÉÜŽģ#8œ^ާ[%DDc{ĐÁ—iÄW_íáC;Ķ›‡é^y[*GōĀĶĨß>ąņ‰öŸÕmRĘ‰ē—æÍœzėԙ‚Ââ‘ÇL;Úd6˙˜{*õ‚¯XdŗŲ"gN;ņũųœŧüˆ‘ÃĮŽ?•zĄ=ŨâO•c—ˆ.æ‘Ō@ũĨDDũ$tæ5šĩ“ĒÚX9ÎķĀ3C"+Uj"RŠ5_ņųËWš\îúuĢfNhĩY%ņÄąŖ^Y˛đųū}===Ûßûɀdą¨ÖFDÔGB˙sū6ÜÛz Æŧ�đtÉŧ˙đÅIãNž9ODb‘ĪÜSwī?hIŠÖ(dŌÜüBšLZŽR+d˛SŠįÍæĒ™/N>$TŠŌœ:{Ą°¨ÄK(°ZÛûTõ[ÅÔۗÂéZ!Ҙ$âŌ#Ņą‡´é*yēĶöE´*ÚĐ’�ž. ‡-˜=ũ­×W×Xjˆčđą“u/%?9kš^oāņxû \ģr™V[Áãņž=täĮėœČYĶM&“—P˜œb0ÛĶĒZZGŸAžlbĒiņ^ǟį.ͧĶhÃzûhĢ+gŲlíũË��ĐZoŊûag5ũÅ?>Š˙+냎jČö×&_Ây^��Ļ!y�˜†ä�`’�€iH^��Ļ!y�˜†ä€NĀårŸ’v}8Ԗ{!šg#Ns¯#y ,šĮ|øršÜĨQķn™O>Žîˆ—ļĖoŽ�î¤��`Æŧ��LCō�0 É �Ā4$/��͐ŧ��LCō�0 É �Ā4$/��ĶūgŸ Ø[˜Ú����IENDŽB`‚����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/plugin-oidc.png�����������������������������������������������������0000664�0000000�0000000�00000143132�14156463140�0021576�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��Ž��7���īų‰f��„iCCPICC profile��(‘}‘=HÃ@Å_?¤RZė ␥ēhATÄQĢP„ ĄVhÕÁäŌ/hԐ¤¸8 Ž?Ģ.Îē:¸ ‚āˆ››“ĸ‹”øŋ¤Đ"ƃã~ŧģ÷¸{ø›UϚÁq@Õ,#“J šüĒzEQ�AŒJĖÔįD1 Īņu_ī<Ëûܟ#ĒLøâYĻņņôĻĨsŪ'Žą˛¤ŸtAâGŽË.ŋq.9ėį™1#›™'Ž Ĩ.–ģ˜• •xŠ8ލåûs.+œˇ8ĢÕ:kߓŋ0RĐV–šNs),b "ȨŖ‚*,$hÕH1‘Ąũ¤‡Đņ‹ä’ÉU#ĮjP!9~đ?øŨ­Yœœp“"I įÅļ?†Đ.ĐjØö÷ąmˇN€Ā3pĨuüĩ&0ķIzŖŖÅ€žmāâēŖÉ{Āå0đ¤K†äHšūbx?ŖoĘũˇ@xÍí­ŊĶ K]Ĩo€ƒC`¤DŲëīîíîíß3íū~�zr„‹éyŲ���bKGD������ųCģ��� pHYs��.#��.#xĨ?v���tIMEå �%ôĀī���tEXtComment�Created with GIMPW�� �IDATxÚėŨ{TTgžčũoIí*ØU ÄP*x(K0Ū|I ƌ—Ž:\ļ9Æ7tÖJú’LÎIVz:'™ŽŊf4ŗ4ŗl˛œL;šWãˆVÔĄĐē x!" 1‚ „’*¨ĸĒvUņū"rUĶI›øûüÃĒ}yžũüöŽũÛĪŗ7ĩA!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!ÄíHWQQŅ9{öl‰„Bˆ2BB „B‡BIB!núáĐ´�M'-WZ C1!„ø 3(zƏ#Ōqë‰ÃŲîĄåJ+1Ŗ,(zũß´A_Û/36î.ŲŗBņ}ô$ÂFn¤ŽūR&Otša‡ĒBĄÁ`čož4„B|ŋÁáFüZ`Čåä‡Bˆ›"‰C!„$!„’8„BHâBqį%Žō­üŋŋ}ŸķZ×ĮķÛˇr ų6iYķIT9{MhĄø­BÎË>Bˆīä;?c;mĻBņu<ģjŌĩsö_ ŲŨ bŅZ m-+"öōÆdĖtq振´)ņxΜE]ŧž,ž čŗ,J Ė įÉÔ[Éå…ėŽ‹Áʡ8§,%Į|–SõqdEe륒'A ’ņpj{e˜ŅœfŦË&Q‘A!~˜¤e,5DŅ­g’‡ÅlÆĸz8UŪ�€šBÖÃO0ÍÕAėÙ,Jƒãu5‡>ĸž<¨(§{z/7ĮÎáĪ kU6‹VĨã9TÁÕžÆųĪĒI~r æß‡Uœ;ĶYŋj%ëWŨOŦBņÃö8�_FėÛEÔ¨ `įĀžæŧ´’„ķvJ\ũGe€5@!ņĄeŦ°‚ĮéDũž¯ū•ĢõvũÕ´k›&„â‡čq�ĮĸUã8u˜IŽŗSüâ…؆/¨ņžfōŖ™8÷˛c{>ųû/ášÅú<ĒpxģâíGą,ŧK÷œÄ‡&Rŗũ}Š÷UĐ ŪĮ"KÛļŋĪļí'qJŌBˆ›2ė‹œšZZo›ßˆ’ßĒBˆī×蘑œ8}Ž™)÷|ß=!„w IB!$q!„Ä!„â{ĻA‡×‡AúÛaĮÕé@§ĶĄˇÅ;9žļ_–Ŋ+„ߓĢo�üN‰cdT$~-xÛŧ:v¨;ũB!~€žÉp (ŠžąwĮ0öBšĮ!„B‡BIB!$q!„Ä!„B‡B!‰C!Ä­ö˙84-@“ÃyÛü BˆīĪÕ˙4EÜzâpļ{hšŌJĖ(Ëmņ“#?¤Ģ?o"˙­.„¸Stx}ÔÕCĘ䉃.3ėPU(" ŨqIC!îDáFüZ`Čeä‡Bˆ›"‰C!„$!„’8„BHâBņcôŨ•*ßƝ>S™š Ļ<ÁÚã8°e~& ÃŦŪ|p3Å ŋdí”­ĐΎßū‘Æ…˙‡_>hí(›ß5ķËܔŋAčü4|ō_ž�“ Ün{ö4O4ÜRiį ~Ëž´âųĻeõG&ūŸq*ø[!u9ĪĪÛkÉV* ū/‡Ž(˜ps…{Xũ‹ĮI4|?­l(ØFÍĪÖ3ßÔ§~ˇ‚ ˇ[åĄukIoÍįKå5coŦā 7š|Ëŧ˙ûš÷wëId‰o˙ûM۟ø Χ_ZÍDųģų$Ęš@ˆ(q�ÖûW°öaĐ8•ˇ…âIĪcŠ::"§ō>ƒĮifÁē8Š[BÂŖ)PwåŅõĖēš@ūRČî‹Öik™sb Ĩŧ@N‚FŲÛÛ`ŨŗĖQģŽģkUĨSž%ÃŌ=M;ˎ‚/ NÅicéētÎŋũOÔ¤fb­;Íų¸’ÕN5ĖāŲUfJ?8ŠĮŦĐh kM6‰Ę-4üŌ~6˜Î_ŧ€ŋšM˙¸“)œĘŽį1é‰épļÃßũŠĨĘō>ēĖ(Ãe.qĪŦ1đÎoö1éąéøģ—™ÖĢčq?įųlā§bã۟ü;VŽëš×ūÉ6vŒ\Ë×ÜՕž.USį†oËļQxö.Æņ WR˙šŖ÷ņ›˜Ÿ6’š­<ūbßnüß›ų(É—ĢŠ›Ã nö|FûH…K_›xüKUļ˙<­`rˇ’øŗ%4¯æÛģΑž}‘Ũõx×zŪX3˛kƒÜ­|{]\ŽoëSņŽáxyâ>~ķÛVrßÉA)øWŽMŊ øšŧ7+XúĘū”˞ɛø]F%ūä!šé4ã~÷+î3Tŗé÷_ąōī5ŪŨëæŪÉpę ÜÛSáI6ô‰wz׆ąīˇ[õO˙@ēû3ūņO*oüB!oķQFMĪ•ŒúÅˌ:QIäߍåÃ>ûcéD99ņ U)$Nķö̟;pz 6>…EĢéę}¨™ķp&‹Žãø‘ēkkÆÆa1›ą¨N•7đčTęFķTPϤ3Kí]Jƚtjļ—ĐÜ3ÍLBœŠĒ˜Ąá(5@3“üp& 4ã$…Ŧ‡ŗIl>McÕ^4+x:ĀÂjęoąš_ŸÃr=ú†$Ļ™.rɘîáĄėųŦüŲŽ~rŽ/÷~Hnܨ(—*Šé^&={>+3 =Ķ:H%’S æë^įå ­LJŊëÚãĻ’<ękö}?Ŋ„•O?„ûŋā� cīįņėĮY2ē–ŖM€fbÚŧų<ū÷÷sĨėœØÉî&ˇFqŽ//|ÍžVüb-Ī?ŋ–ŲŖ'pī¸ģHŸÛ•4ēę÷0iJWŌ8˙É6ļä“g;×ŗ=}ÛZ—z?œ¨ĻŊę’įh|yé2Į𯐠0–‡F}Å1÷9Žņ�w_¨Ä_U‰aN&Îķ°¯Ô˙øg´§=€ãŖJî]“ÃŌŋ{€q}{W}â=hņø!.e<ÍĘ'gö(ŋ2†ßBˆŋJã5g ņÉ̟'“ķÜ8šëĪr8o+Öu™Ŋ–í¸nøéĀžæŧ´’„ķvJ 6Y-ųøÔIŋ:ƒœ‡ķŲv0Hģ(ĩŦåÅáđÉJ4@AéYOé)@C,ŠķXņ°ÍéDŗÜbs'ŪŸTĶžũ@×IÕ_Ë)÷xVāhĪBšüX˙s´_i%Ōđ %†kۇ6X%nN†ä5Ŋz#“GRWu&v÷8.|Á1eĖĀiĮĐ+rZ×v €˙ZŖf>Î˙ˉ˙J+ūQnvtĮ4ünŋ2ĮMVŠéŽ?qŪzžŸsü“›žîcßļŽ‚KūŲqVᡟŨÅÁĸ|€x> 9ÃÄž˙īĘäå¤_ØIa9Ė^g ’‡ā÷˛{”›‡~1ÎŪČNq_w!Ŗ\M0~?~ÔĄWŊĄũ!„øĢ$ŽÆ/vQdWĐ<.˜™ÃÚX(đœĻ¨ bÍh–‰ÄĒ€v˛ƒ6œu-ĖZ< Ēēz ÉqvŠ?°‘ŖÛđ5žÕd<�ŋú †—7 \¯ššC։˙ÃVR ~2lßEą3b ĻÜNÆ`œúŗŪŨAQ‹gŗ™ënq¨jôžËØÆÛĢ1™@sôįN<Õā?Įg˙ũ!WÎ^&ũīīá^æsčĪÛøĪ‘ߒÎsO}CâRé˙%īkŋģŌžæųŅ׿EÎ]Ί˙Čį÷›MŒ2h¸™Ęęuc™ōw īũĮ‡|É7ŒúŲ˙ š}ÃˇaærŌˇä“wy$WšF˛ôų%<ūüįŸōģ†ĒūūWL› īl?Éė§g}ĩū‚|6üɄ‰Žv§˙ė €{ŸčÛÖ¤=Į ——ķ?GähŲFüĪį@wâ õ>ظ‡øM&îUZųãáŠü§āĩ?ō,…&āą$v˙G>ūÉ —úu%Ž7'ē{k3Ą°ā WFVãæ ŗeôæ˙ā?›ÆĐ~ÅĀŨōũâ–č***:gĪž=čM-­|mŋĖØ¸ģžcU§ŲúV k“9Üĩ_wF˛ąņH />˙7 έũVÕI6üū2Īũn~Īđޏ5ÛŋōŪ¸ā…ÔŋRŧũ—9ߤ’8Îė›ß¤nÍ+,%qĸ¯§Ī yŪģ-€ĘsūcŠšX°&^öāęü'ÛØņÕ<“ũ×,ÕÍą‚|ö‰ÛđĪHŌâvīqüøČ¯ã !¤Įҟü Bˆ›"‰C!D¯ƒ2ô]Œaīqčt ĶéЁ;ö'NŸ“ŖIqG¸úĀī”8FFEâׂwėĢcåū†BÜdâP=cīŽaėŨ1-!„rC!„$!„’8„BHâB!‰C!„$!„B‡Bˆ[7ė˙qhZ€&‡ķŽũ@!„¸“(ú0&ŒŗiЏõÄál÷ĐrĨ•˜Q–;ö'G„âN †¸øĩ”É]fØĄĒP(D0’¤!„w€°°øĩËČ=!„7E‡BIB!$q!„Ä!„âŽKÚ_6ŗą\ëų\S°‰âF¨y÷UŪČ+$ŋ üwˇ˛y_vvüĄķßcc<USÖÜ{˛ņí“ßšÜæOˇ˛Ąā$ }ËŋERę`{oDũ.6ŧĩ‹SΊ‡ l4eomĻėēFä@Õ… 7€:„w†īé[3ŗ¯f‘ Žü?TĐøx樎â‚ĪņęņØÍd­Ë¤ū­ĸ&õ]g9ˇŒõipYĄŅA֚IūÃĮ$<”‚VwåŅõĖĒ?MMÄ#$Tmc‡}É1—hæž>gíļíˇŖŌ‚–ē’ĩņ§ŲļߎÕĸŅq?kSOŗáȘiæ|•‹Ŧ5ĶŠ9b‡I ŊĘßmGbĖ%JĪÜĮīžfÙGxųņ8ÎlĻfáŗXļ˙‘SÖæ<4íæ”r7ŠĶEōâtjĒÎŌSĮxOWy‰u…ėŽ‹Áʡ8§,%'æŪčŊ šËHT�œÔ|v§’ŽĸÕQünŸ˜Ŋ}ĩÎeˊRzϚæ3é$(-ßgŖŪ~–æ)Ģy6î,§ęã˜Ŗí%˙„ŠUuĄMYJÎLKWœœŨķķŲÜ2ƒŒ;§ė“Yŋ&†ÃyግÁã4ŗ`!=ud5īewƒŠEk´ĩ,¨ßÚgŨɜß^Df4§™ë2Ņ>ŨË)͌f˙ëÂĩdYåK)Ä:Tåáøū÷)Úž ˙ōąO>FÂP‹W}D͔Ŧx|Kã+9P`!ųáL˛žœ‡rä šĢör YÁĶ.PS¨™õp&‹ŌŽ×]Ŋ:ļSú…™Ĩ̞Y6ĩOU5û+HXŧ’œukY4)‚šCX¯dғ̙Vgãx�ët˛~„ą8î™Dō¤Ļ=”Bt¯ō­ĘfÁĖÁūAFAŅTĻ-\ÆŦX•Øx3ĒYÁâ9MYC<Éq1ĖJ›DDwy‡?ƒŦUŲ,Z•ŽįPNúlCĪåŧ…ä™qÄĻĻ“ÜĐ7fŊëė^Ü:‘„˜ŠdMą€faÚülVŦIĮsĸîZÎöt Š1$Ū˙+Ž&ëˆMÍ$kūc$Û+Ч§bãSX´ęzÕĄÄÆa1›ą¨N•7ô_×YAą3õĢV˛~ÕũÄz*Ø}Ä(ĒFÍģ|#…øŠ÷8‹­ŅÄ�Îf= €ĘŦ…+Ydõp<of Đq‹ĩ¸:fIĮЇ-hN'šå[Šô JĪpˆÍųŠŽ4 Ļf3Mšļ¨Ļ ŧ’ĸ(ŊVîWÚ�U]Û­įgô(*ā<JQÕd^ÎMĄQ+šáĄēĄˇađŨЍCĖSēËęU^ėƒkųĨĶÎųĒŊl<2——×LxŊ“ÉynÍõg9œˇëēĮzö=-Ėyi% įí” ô_WéŨ MbĻŗôÉûQ4'N,ōâ'?T•ú'vą­ Eë@›šŒĩ*ŊNŽ*ŗÖ¤ŗųßm4ž4´ŗv&e˛öÁøîrž`ZÁ.v8UœöûX4ÎôPķéĮ8ígᡕÄĻĻ0ëŨĩ˜q6›Y°nŌ GÆ ; v‘ׂF Jb&k¯ÎžŌ )R;ĐĻ,cíŖé”íŲEąÅCã¤'X̝¤tČFĮ“1c/ģˇÛH6_ęJšÖb÷ˁOãináú͟e‰ž(Ú׀ĩ#gųI"'Áá=§cîÚŪ*ėØnŖ†oą,\Š…On öũcV°īB1$ĒÕė.ŸÁ´AŠŠ?˜Oą=†XŦ“nāÄí9MQAĚŅ,‰Uc@­fwy2sâė`#!F!ļá Îõ}MŊz‹,ElÛ~ļg¨jŠĩˆmÛ/Ą:]$,–Ą*!~ tŗgĪtĻ–Vžļ_flÜ]?āfi”žĩ õ7Ī2ë6 š§ž-!‹ŗ„Äđâē9’„?_Û/33åžīŠĮq‡ŌZ>!ą¸H~t­DqGšM‡BÆožŊmƒf™š–_ΔƒGqg’�B!‰C!Äß0qčt ĶéЉ–Büă! ĘĐw1†ŊĮ12*ŋ”WĮ !ÄāęĢcŋSâP=cīŽaėŨ1Q!„rC!„$!„’8„BühGįūŊ¸N\?-ôa ž–^.–Đöņ-ÔŪR…̰Š×`Y9­OīÅ{ËMķâ˙ІãĨchĸíõJnööž7¯œáž' •Ų¸üj¸qŋôg\y%´ÖB'Jpn<Lëë%ø}@cÎWm¸^ßM[™wĀļ߈Îå¸Ī Īëųw”wmCīi[lt´ŨĖ>ē kh{ĩįÕãĮ×DĮ–?ãČŧ<ī›6üßËĄŸwoÛ_¯Ä[ŠįÍđ] íUm[Ęņ5ąßŋ“ÚōúėķZÜû1˜úĪņ€­œ6Û7tÎËÆ<ÃE‡­_=tĻÍÆ4' ˙ίđ9€ö$LKô¸uĻZÚÍDž6=āßō_x3~Žyæĩ/˛ˇč2ÆgR{ę î?‹į0' õ[÷‰>€ŋđŪv•N‡Ķ+ĶQVB[i]ģÃ3Ų„ûĢpít ÃGØōų¨ãÃ1,ICŠžĐëÜ\‹ģĐA°ÚLä?O'Ô§Ė°˛\å0ÂĘS™„ˇWá)­% /ÆėLÂĮž*ŋ2rëôŽŦÛvwm4†H�Ąčd"sģį cˆ×æc(ŗŅZ@IŠF}9Å_Ik‘æŒéĶv/žŧOņG ĩO *×Ô/žaįĒhßYChqÚu'ÆkņtāŲxŒ`$„ĸS°ŦCāķ<6ÆÅ]IÕˇåžh+T{Q€PßxZŪG=>ļáŠÎîڟž*\īÄžPƒ¯ŨD§Ũˆúâtô㓉š÷=§ ãh"r’ņīî„ų m›.ūÂŧ˙̜ķŦN‚éĩŲ]ûŊ<œíN”gfŖŊĶĐu<�ū- -ŽÆûļCFŋ=‰‘šŨ?¤yąotæ¨ūĮRđÍ":Æ1ĸڃō\6ÔŌļĶÁƒ“ĐŒšD%œĻuĶõeĪ~kh{¯pÃŧG‰šãéŗô´ũĒw['xģŋ#C‚]$töŨī•8ßqĸŸęÂW?čœ&Z÷LddŽ÷›Į0ž’Ihãn:âLŒpŒÆøĖ:ßë}lM¤ķķJ|å�NŸJEņƒ§¨阰 p,õŨÎŲōģEŌãøJ-l^Q/$ŅYę�ãh SGž“‰iŽ Į1ũŧLĸ–Œé*Đ057‹¨Št\ė*ÅđLQŊĻãÜqüIi×ŽŽ<ĩQü]­¯đÔ&aÎÍ$2ЁŽs^ŧ6/fa~y.J$i"Ė�#¸Œ˙ä ũ”Č ¨ĢŗPã.Ŗĩõ-Bí: Ŗ1.O#<?cŌ8LšŨI˜ƍKéœßÆåãĐuî´_Āw˜ļŧZ‚čŅ‚tú Ķ ĶáC7~ úúJZ[ƒ’=f€ļëgLtžŦíęíô‰§îžTĸ˛G_ßļŪņ<} Ō\ĸrŗ0T‰ĐĪÍD~õ,F‡#ķęŲ„'tmwŋx´zKPÁū î_ŲđV; ɍgÚŊčüMhˇzHēņløåš4#m%|ÉlÔčËhmŨÛų|QËõx?1ĸ>?ˆcG!ŋž0”Œē$ ÅŅØŨƒķŌąĶMø Ë�Įā7b\œ‰ų)#žr7ÁO*Ņđ‚'/wmVŸ2‡Žį�ßĸOjņTQ¯<Šš¤p]ßÖhÂs’ĐgĖ&*w:†¨ūû=XÚˆ§21͋ô‹<"Ō‡nÆ|ĸžŸŽÁØ˙Ø KKBŸ4¨ÜÔŽxGA}jJ×ÅÜĀvĘITĮ ájį¤{ ÅĐg‘á>÷ãÄŗÔ…Ļž)ÚÎ3„-Oę>Ŗwųū�~ėŦ„噘2ĸélĀĮ Gč€ÎĘ {$ķb3Ą=6ÚNßĀv´]Â_īÂ÷Ū—hĩgđ]éwO•›…]ƒį„įLĀ˙N žraq&Bį&MgäÖ4E5´ũ+Ü'ã‰Z‚ŧåx^6ôWŋņ—ŽxVf§¯{ā.a4ēŌãqÁt a—Ddn&‘Ī=ĐŨcšË’ãÚxīÅkõ‡%ŋÚ{íøl[ ĀS‘ĶxšĢûęWß3\Ų tž8Š6#mā0ēcd¸>fJv&QšYDåXûtÎŊũˇc xļ5āŪxWaCŋáŅÎöĀ ûhˆļ8VÖį l {ũ�ׯûL„E_ģ�ëwloætq‹Û)îĖĄĒ~‡ĪT=mo&˜‘BÔ#Ŗ!Î E6Új“PsÍāhĀSč%Xk%rE÷1ūNQĪPUčķr‚ķ˛ŽUŪR…§ũ^,='›&ŧy5xk› ¯˛@zWá7āHÂ4>]ļ‘ļ%xÛ}ž›aĒ ­čîНl ˜ĻĮģ§­Ö‰'ĪHøS}ŽĐ™Đ§Lî?ŒģŪD˜ß‚Ũõ­RĸißxCv:jJ8øĒ¸ru¨**‰¨W’€op9ĮģņnüÍ`¤ŗŨŠē‚卄üĐIę\qŽ ×†FÜč2˛h{4Š˙înB7žsÁ>ņ āß_iJp3ŸxĻĖÆ`ûœļ<čœ1xķĒđV7ŅųN ēÅiDDÛp:ŅŲõč 'ŧO<GôŨGp}ۍŅP ėw îų }B<ÆŠ6\[ÁéÅ)„>,ĮWŪDĀP‚Û?›pjđ”רn¤íÃ{1-ƒū‘,Ėô>ųY0ŽžKđÍOņNíûŖúŨÛšĨŸ?ŒˆgLčKŨę§cIj¤ÅŽž]›Ķ+áƒî÷€Á…og9ZÆgL„’é|û m‘AČx˜¨~Ŋˆáâ DÅcz1ūÚÚŧ$:Ū;L[ĪPUß}žáîŅôŲīĻ +Ú{%¸“„ˆ†+#ėĮphAk('ô=ļî‰Fq|ŽkKá9ŗ ̝ÄSZK ÚDۇALKnv;ŝ`Ø÷qÜŧ&Ú^oÄôÚtydKâųŖā{}/¯=AønÃŨÛM„*qžgÅōühŲ™âöíq!n~žMŸCd�ũōéņcîq!„ø)“Ņ!„’8„BHâB!‰C!„$!„’8„BIB!$q!„Ä!„â64ėOŽhZ€&‡“–+­ƒ!‰˜Bü„)ú0&ŒŗiЏõÄál÷ĐrĨ•˜QŊü´•Bü”ƒ!.~m'eōÄA—v¨*  †$i!Ä ,l~mčfË=!„7E‡BIB!$q!„Ä!„âŽKÚ_6ŗą\ëų\S°‰âF¨y÷UŪČ+$ŋ üwˇ˛y_vvüĄķƒ–æĄôíßđgģ?ÛŲņúkl.č*gëۅwgōycŸ}˜ur’üíuPŋ‹4 ŧHũ.6ŧĩ‹SÎiŊ‡Ã6š˛ˇ6SÖ{VķIT QČpķ¨C!nßĶ3ļff-^Í"+@ų¨ ņņĖĄWi,á°ēŒ,ûĮ”y&3G”É,ZŗšDĀķé&ļŅ˜eš‘u=ū—|,/=Ë,ĪQ6D°vf 5u1ŸŦâŠ;JņG9_‹ž[Iĸ⤿ŗĶ8•t­Žâw?ĮgÆc7“ĩ.“úˇ˙Č)k sZÆŦX ņ(ĨgĒi>“N‚ŌÂņ}6ęígiž˛šgãÎrĒ>Ž9Ú^ōO¨XUڔĨäĖėŪxg÷üÆ|6ˇĖ #ÆÎ)ûdÖ¯‰ápŪG8ccđ8Í,XHOYÍ{ŲŨ bŅZ m-+"öōƐ1ĶĖų*Yšáų ˆ2ĖhN3 Öeĸ}ē—SšÍū-օkɲĘ/„ø÷8†ēR>ž˙}ŠļocÃŋ|Dė“‘0˧öWøh&YÁŋ´twi.p`ûûmŸ-sY1Sšņuûˆ4™Øøtfł‹ž\ÉRë%Ž÷\Î[HžGlj:É Q3e+_ÆŌøJT)(šĘ´…ŨIĀ:‘„˜ŠdMą€faÚülVŦIĮsĸîZĖ́ϯx˙Ŧ˜9PÆ ›šIÖüĮHļWRONÄÆ§°hÕ#$ôĒC‰Ãb6cQ=œ*īę1)Öéd=ü b/püBÅÎtÖ¯ZÉúU÷ëŠ`÷tt ¨5gėr´ !ūö=ÅbAkt1€†ŗYÅ 2káJY=ĪÛFŖŲt ‘gŽr⌊E}ŸŨ(hU6ÎĪ”‰,Xĩ’Ä!sÔ@ëŽDšÚ2MCãúW”^ HģņPš"AG�� �IDAT)ęķ”î˛z•ûāZ~é´sžj/Ėåå5“^¯ĮdržGsũYįmÅēaģ{Z˜ķŌJÎÛ)=čߎëŖĄi@Ėt–>y?пĉEŽv!Äm0T•ú'vą­ Eë@›šŒĩ*Ŋîc¨ĖZ“ÎæˇŅøŌ ĐÎR\PØu ›”ÉÚãģFšö—`YķëSģN„Í7QTŪÁŒŦ ŧŽÆŠTØũA NķY<Ü–x,öO(ĩŧv=Á´‚]ėpĒ8í÷ąh>ÔėģP ‰j5ģËg0mbęæSl!Vë¤8q{NSTPąf4ËDbÕPĢŲ]žĖœ8;ÅØHˆQˆmø‚oŸ,~‹,ElÛ~ļg¨jŠĩˆmÛ/Ą:]$,–Ą*!Ä_‡Žĸĸĸsöė؃.ĐÔŌĘ×öˌģKĸ%„w€¯í—™™rĪ ķåq\!„7E‡BIB!$q!„øą$t:Z ŅBˆŸ¸`0„AúÛaĮ‰_ ĘĢc…âpõÕąß)q(ŠžąwĮ0öBšĮ!„B‡BIB!~4‰Ŗs˙^\'ޟú°OīĄŊXBÛĮˇP{KŽÂĻ^¸7V‚eå´>Ŋī-7͋˙CŽ—Ž ‰ļ×+šŲÛûŪŧr†{ž,TfãōĢUtâÆũԟqå•ĐVXK(Ášņ0­¯—ā÷U8_ĩáz}7meŪÛ~#:O”ã>7\<¯įßQŪĩ Ŋ§mąŅŅv3ûh}Žßëße˙ !nW7ô#‡[9mļo蜗y†‹[-žzčL›iNū_ás�íI˜–čq˙ęLĩ´›‰|m6zĀŋåŋđfüķĖžRņ]ÆøLjO=ÁũĮ`ņ|Â�æ¤Ą~bë>ŅđÂÛŽŌé°`ze:#ĘJh+ ĸk÷ax&›pŽtø[>u|8†%i(Õz›kq:V›‰üįé„ú”VV‚ĢFøAy*“đö*<Ĩĩ„á؝IøxĀW…ã×AFnŪ•uÛ.āŽÆ ā#Ldn÷<tėņņÚ| e6ZK(Iҍ/§ĸø+i-rœ1}Úîŗ÷)ūH#Ąö DåšúÅ3ė\í;k-NëŊ—zǺgã1‚‘ŠNÁ˛b ĪKđØw%UߖCøĸ­PíEB}ãií[fî7+!:H(i.Q¸i{ŊĶkĶ nąĄ=õ0ú뎃1`pá-,Į_í@y!›yžBˆ;e¨*@Øŧ4ĸ^HĸŗÔÆŅĻŽ&<'ĶŽ!c4úy™D-ĶU a jnQS鏨UŠá™ĸfö*öÜqüIiW/O/āŠˆ:āīj}…§6 sn&‘I tœķâĩųˆx1 ķËsQ"HaÁeü'šÎœ€ē: 5î2Z[ß2!ÔîĄĶ0ãō4Âc€ņ1&Ã”۝4�ŒŠŒúˇ”îĀyņíq`\>]÷įNûÜy‡iËĢ%ˆ!H§:ũ:>tãĮ ¯¯¤õˇ5(ŲchģžqFĀDįÉÚŽŪNŸxęîI%*{ôõmëĪĶĮđ'Í%*7 Cõ—øũÜLÔŠáŨ 7ŌáHÆŧz6á ]ÛŨ/žũö‘ž°h=ĄHđ—6ÜŽŋJøō4Ė‹Á_-_6!î ÄĄGg¸Ú9éH1ôYd¸Īũ8ņėuĄŠgŠļķ a˓ēOĀÃč]ž?@§;+ay&όh:đ1čŅ: s€2ÃÉÆŧØLhļĶ7°m—đ×ģđŊ÷%Zí|GcúŨDåfĄF×ā9á9đŋS‚§ÜGXœ‰Đš‚IĶš5@QÍ�m˙ ÷ÉxĸV§ Do9ž×„ =äÕo<Ģ+žũĘ<q oB:QË'0¸nģ:û|žŽCk”/™w`âāT4U÷íô}Ü=ög›­û*p4ā),Ą­ÚJD÷•ē˙"Úēī•„>/'8oÖĩq˛–*<í÷ĸöüĪIŪŧ<ĩ]Ŋ' N­ÅUX‚Ûž„:>œđl#Kpmęē1bĒ ­čîzЕ7lŦŝWŽV[‹'¯ Íס}˄āūôīšDĐoA‰0ĸD7Ōžņ0žĶŨŊ_W~}ēk-*‰¨W˛ˆĘŊ%i ÆņnüīŲpm9H{­•đÖ7ō ’„:FĐDû†ƒ¸ŪŦD—a íŅ(ūÜ;j ÜøÎûÄ3€ mļ&{Jp—9ûĮ3e6†ÚĪiË;Œ6c:]ņŦnÂûN Ū‹V"ĸĢpÃo×ũãŲ¯Ė+”ÅŊĮ…Î˙ūļh ‘ ¸?ŦÄWø8Bü$ û>Ž›×Ô3ö-lI<…ŌãB!=ŽŋvC!„ô8„BIB!$q!„Ä!„B‡BIB!$q!„’8„BHâBņƒö}š Éá¤åJ+Á`H"&„?aŠ>Œ ãŦDš"n=q8Û=´\i%f”E¯—¨ !ÄOX0ââ×vR&Ot™a‡ĒBĄÁ`H’†BÜÂÂFā׆~aļÜãBqS$q!„Ä!„B‡BIB!î¸ÄĄũe3ËĩžĪ5›(n„šw_åŧBō Éw+›÷ÕĄagĮ 9?hiJßū ˙Ppļûŗ¯ŋÆæ‚Žrļž]Čq'p&Ÿ7öŲ{Öj>ą‹ü‚B6ūī˙ņ‚BōˇAãõ[Ié[[9~ĢŦßņˇvqĘy# {8\`Ŗ({k3eŊg5Ÿä@Õ… 7€:„âoá{zÆÖĖŦÅĢYd¨#˙4>ž9ô*%V—‘e˙˜2Īd樀2™EkV“x>ŨÄļ3ŗ,ׯ;skgBŲÛßĸ=šš ĨŽâwķņęņØÍd­ģZ¯“ã;h|`-ŽŊ×E hßĸMz‚œ¸Ūø�2fš9_å"+w‰J×:5ŸÆŠ¤Ŗhuŋûy¯rã(~ũc,Sî'cU: �G)=SMķ™t”ŽīŗQo?Kķ”Õ<w–SõqĖŅö’BÅĒēĐĻ,%gfwƒœŨķķŲÜ2ƒŒ;§ė“Yŋ&†ÃyግÁã4ŗ`!Ũu¤’xâcN)wŖ8]$?š’ÄōÍ}֝ĖųíE”aFsšY°.íĶŊœŌĖhöoą.\K–UžBˆÛ"qx8ž˙}œĒ‹úiOæ@ĮõWā}œÚ_AâŖŋ%Ë~šiaÎ|@ģíīc4e.+T n˜ĒĢ>ĸfĘZ^|PÅyp;Ē2Iσãų¨<KNb ;ūpĪ”ÉX=Î3u8ã@ąN'ëáI$Ú7QÖ ‰V� É3ãˆmN'š!ŸâëʍGQ&ŗ`U:ąWëļN$!F#kŠ…ķû,L›ŸM1lĖ̃…ŨũOšOâũ™LK´ Ѐ�ąŠ™dĨ:Ņūe7õ¤ãô@l| ĶNÆĒÖu×q7žf3ĒGAm<MY$ö]×éĸؙÎËš)hÎ<ž ļq‘˜Ē ¨5gėdYãä[ „øá‡bą 5ē€@ÃŲŦĮbP™ĩp%‹ŦŽįmŖŅl:†Č3G9pFÅĸžĪn´*įį?ĘDŦZIâwnĻ%~2_\@K4qd<šŒd<8 į%Eé=ēuc"”!BĢ(Ũeõ*/öÁĩüŌiį|Õ^6™ËËk& ŧ^Éä<7ŽæúŗÎۊuŨcŨM:JQÕd^ÎMĄQ+éėŗŽŌģ1šÄLgé“÷ŖhNœXä „ø{Š‘qbÛ "P´´™ËXĢŌë>†ĘŦ5élūw/Í�í,Å…]§ĢI™Ŧ}0žk”j –5/°>ĩëŦ×|pEåÜŌ(JęL+ØÅ§ŠĶ~‹æÃųƒqL{x5փ[ŲVžCÎB=EīžĪq\hŠ+XwkåėģP ‰j5ģËg0mbęæSl!Vë¤8q{NSTPąf4ËDbÕPĢŲ}&“DO Eû°vÄã,?‰ĢoRīc‘ĨˆmÛĪö U-ĩąmû%T§‹„Å2T%„¸y犊ŠÎŲŗgē@SK+_Û/36î.‰–BÜžļ_ffĘ=ƒÎ—Įq…BÜIB!$q!„Ä!„âĮ’8t:Đéth€DK!~â‚Áečn‡}wdT$~-(¯ŽBˆ;ĀÕWĮ~§ÄĄ(zÆŪÃØģc$ĸB!ä‡BIB!$q!„øŅ$ŽÎũ{q¸~ZčÃ<-Ŋ&\,Ąíã[¨ŊĨ WaS¯ Ü+ Á˛rZŸŪ‹÷–›æÅ˙Ą ĮKĮĐDÛë•Üėí}o^9Ã=O*ŗqųÕ*:qã~éΏōJh+Ŧ%„N”āÜx˜Ö×Kđû€Æ*œ¯ÚpŊž›ļ2ī€mŋ'ĘqŸ.ž×ķī(īچŪĶļØčhģą}Ôoŋ÷Õ›6üōâ'ī†~ä0`+§Íö ķ˛1ĪpŅaĢÅWiŗ1Í Ãŋķ+| = Ķ=î_Š‚v3‘¯ÍFøˇüیŸcžŲS*ŪĸËŸIíŠ'¸˙,žOœ4ÔOlŨ'ú�ūÂCxÛU:L¯LgDY mĨAtí> Īdî¯Âĩ́aËįŖŽĮ°$ ĨúB¯ss-îBÁj3‘˙<PŸ2ÃĘJp•Ã?(OeŪ^…§´–0ŧŗ3 øĒpü:ČČ­Ķģ˛nÛÜĩŅ"|„ĸ“‰ĖížG€Ž=>"^›ĄĖFki�%)õåT%­E˜3ĻOÛŊøō>Åi$Ô>¨\Sŋx†Ģĸ}g ĄÅiŊ÷R¯x:đl<F0BŅ)XVŒ!đy ›ã⎤ęÛr_´ĒŊ(@¨o<­}Ęô5õŲīÆ~û�ß7´mēDø i„<†ûdÚŨ(9YOîåJm<ᆂS%jn¸|…øiU›—FÔ It–:Ā8ÃÔŅ„įdbšcÂ1dŒF?/“¨%cē 4ŒAÍÍ"jj#ģJ1<“CÔĖ^Ş;Ž?) ŖąûŗīžÚ‰¨ūŽÖWxj“0įf™Ô@Į9/^›ˆŗ0ŋ<%ˆ4f€\Ær~JäÔÕY¨q—ŅÚú– Ąv†Ņ—§ŒŸˆ1iĻÜî¤`LeÔŋĨt΋oãōqčē?wÚ/āÎ;L[^-Aôč A:}ĐéĐéđĄ?}}%­ŋ­AÉ3@ÛõŒˆ3&:OÖvõvúÄSwO*QŲŖ¯o[īxž>†?i.QšYĒŋÄčįfĸNŊzĸn¤Ã‘ŒyõlÂēļģ_<û–Ųoŋ÷¸ņløåš4c�ߞ3„đ‚ÁVÛĩOÂ2ĻŖ>•D¨Ü!ß>!~ē‰CÎpĩsŌ=bčŗČpŸûqâŲ ęBSĪmį–'uŸ€‡Ņģ|€N?vVÂōLLŅt6ācĐŖt@į�e†=’yą™Đm§o`;Ú.á¯wá{īK´Ú3ø.ŽÆôģ'ˆĘÍBŽÁsÂs&ā§Oš°8Ąs “Ļ3rkĸšÚūî“ņD­NA‰ Ūr<¯ zČĢßxVW<,Ķ0Ė>ņKõÕÄMDn&QĪ<ŠšÖ•´Žíß |û„øé&ŽNESõxß>LÛĮŨãéqV°Ų睞GžÂÚĒ­Dt_Šûß)ĸ­û^Ičķr‚ķf]'kŠÂĶ~/jĪ˙œ4áÍ+ÁSÛõ×{qęÔZ\…%¸íI¨ãà Ī6Ōąą×ĻŽ{#ĻšĐŠŽáŽ]yÁÆZÜyåhĩĩxōĒĐ|}[ҎLî?LûžKũ”h�#Jt#íã9Ũ}2ôUqå×§ģ†Đĸ’ˆz%‹¨Ü{Q’Ļ`īÆ˙ž זƒ´×Z O€`}#! I¨saM´o8ˆëÍJtÖڍâ¯ÁŊŖ–Áī\°O<ø÷—Đfk"°§w™ŗ<Sfc¨ũœļŧÃh3ĻcĀŅĪę&ŧī”āŊh%"ē Wá1üv=Đ?žũĘėˇßûĮƒãęš(Ÿ|ЎMqąĪ›%´mú­]žlBüT û>Ž›×DÛ던^›.lI<…ŌãB!=ŽŋzC!„ô8„BIB!$q!„Ä!„B‡BIB!$q!„’8„B|WÃūŦēĻhr8išŌJ0’ˆ !ÄO˜ĸcÂ8+‘Ļˆ[OÎv-WZ‰eAŅë%ĒBņ †¸øĩ”É]fØĄĒP(D0’¤!„w€°°øĩĄß{*÷8„BÜIB!$q!„Ä!„B‡Bˆ;.qhŲĖÆr­įsMÁ&ŠĄæŨWy#¯ü‚BōßŨĘæ}uhØŲņ‡B΍8°„úfÔībÃ[ģ8åbåYχ‡Ã6š˛ˇ6SÖ{VķIT QČpķ¨C!nGßĶ3ļff-^Í"+@ų¨ ņņĖ!Îü…l8ķ/?Įų‚ÍÔ,|–ÄĪļQJĒĶEÂâĩLkØEq]Šö-Ú¤'ȉû˜ ÖH¸˙~ÆTÆ<?ޝLÂC)hugQ]AÂg§q*é(ZÅī~Ž'ΌĮn&kŨ#$�ā¤fĐeâ(~ũc,Sî'cUz×ōG)=SMķ™t”ŽīŗQo?Kķ”Õ<w–SõqĖŅö’BÅĒēĐĻ,%gĻĨĢÎîųųln™AFŒSöÉŦ_ÃáŧpÆÆāqšY°î:RI<ņ1§”ģQœ.’Ÿ\Ibųæ>ëNæüö"Ę0Ŗ9Í,X—‰öé^Nif4ûˇXŽ%Ë*šâ6ęq uÕ||˙ûm߯†ųˆØ'ë>Qßp_SC‰™ČŦ…+Ȉĩs`˙Y<t€ĸĮyĻ' Z\:9Oŧ–ũԉĖz8“Ei Įë yfąŠé$7|D͔Ŧx|Kã+9PuuËË((Ęd\M�։$ÄL%kŠ4 ĶægŗbM:žu×ļǗϯx˙Ŧ¸š4Ž 65“Ŧų‘l¯¤žœˆOaŅĒGHčŠãnbãͨf‹į4euŦëŦ Ø™ÎúU+Yŋę~b=ė>₎UŖæŒ]Žp!ÄíÕãP,´Fh8›õX,�*ŗŽd‘ÕÃņŧm4š-@ĮP%Ą\=ņjŽõ×=KrãNúÛRWb!ŽŒ'—‘Œ§SÁâŧ„Ē*}Zs­´ŋBt"z•7@čĨģž^uÅ>¸–_:휯ÚËÆ#syyͤ×ë1™œįÆŅ\–Ãy[ąŽ{Ŧģ‡r”ĸĒÉŧœ›BŖVŌ=Ä×g]ĨwC54 ˆ™ÎŌ'īGҜ8ąČ.„¸Í†ĒR#ãÄ.ļD hh3—ąVĨ×} •YkŌŲüī6_šÚYŠ ģNg“2Yû`|÷•| ąû?æĀ§ņ4ˇ€…JßŨÅyK ĒCBĖ8æ,ÔSôîûĮ…–ē‚ĩq7ŗO0­`;œ*Nû},šcËėģP ‰j5ģËg0mĒęæSl!Vë¤8q{NSTPąf4ËDbÕPĢŲ}&“DO Eû°vÄã,?‰ĢoRīc‘ĨˆmÛĪö U-ĩąmûĨž!>ĒBüĩé***:gĪž=čM-­|mŋĖØ¸ģ$ZBqøÚ~™™)÷ :_ĮBqS$q!„Ä!„B‡BˆKâĐé@§ĶĄ-!„ø‰ C”Ą¸öqܑQ‘øĩ ŧ:V!î�W_û‡ĸč{w cīŽ‘ˆ !„{B!$q!„Ä!„âG“8:÷īÅuâúiĄKđ´ôšpą„ļoĄö–*\…MŊ&8poŦ$ËĘi}z/Ū[nš˙‡6/#@m¯Wrŗˇ÷Ŋyå ÷<Y¨ĖÆåWĢččûĨ?ãĘ+Ą­°– :Q‚sãaZ_/ÁīĢpžjÃõúnÚĘŧļũFtž(Į}n¸x^ĪŋŖŧkzOÛbŖŖíföŅwÔR‹{˙ī%ņ] íUm[Ęņ5ĘTˆÛŅ ũČaĀVN›í:įecžáĸÃV‹¯:Ķfcš†įWø@{Ļ%zÜŋ:S-íf"_›đoų/ŧ?Į<ŗ§TŧE—1>“ÚSOp˙1X<Ÿ0€9i¨ŸØēOôü…‡đļĢt:,˜^™Îˆ˛ÚJƒčÚ}žÉ&Ü_…k§>–ĪGŽaIJõ…^įæZ܅‚Õf"˙y:Ą>e†••ā*‡~PžĘ$ŧŊ Oi-ax1fg>đUáøu‘[§weŨļ ¸kŖ1DøE'™Û=�{|Dŧ6C™ÖŌ�JR4ęËŠ(ūJZ‹0gLŸļ{ņå}Š?ŌH¨}QšĻ~ņ ;WEûÎB‹ĶzīĨ^ņtāŲxŒ`$„ĸS°ŦCāķ<6ÆÅ]IÕˇåžh+T{Q€PßxZû”ŲxŒÖ=™kÄũæ1Œ¯dÚaÃëPÁéųd‚~ޝŨD§ŨˆúâtôŸäJš‘0CCváqßā)ĒÄG:&,]enrcČâˇ'12ׄ÷írüŽh Ft‘ũ÷ģ^žŗBü†Ē„ÍK#ę…$:K`aęhÂs21Í1á2FŖŸ—IԒ1]Æ æf5ĩ‘Ž‹]ĨžÉ!jf¯bĪĮŸ”†ŅxíJĶS;uĀßÕú OmæÜL"“č8įÅkķņbæ—įĸD‘& 0‚ËøOŌO‰œ€ē: 5î2Z[ß2!ÔîĄĶ0ãō4Âc€ņ1&Ã”۝4�ŒŠŒúˇ”îĀyņíq`\>]÷įNûÜy‡iËĢ%ˆ!H§:ũ:>tãĮ ¯¯¤õˇ5(ŲchģžqFĀDįÉÚŽŪNŸxęîI%*{ôõmëĪĶĮđ'Í%*7 Cõ—øũÜLÔŠáŨ 7ŌáHÆŧz6á ]ÛŨ/žíŖ>ĮE§Ũ‹.!žˆ§R ŖĪ7´{Ņų›ĐHčŒKÅü|á÷�QcPŸšŌuapUR2ę’4G#Aĸ ĪIBŸ1›¨Üéĸúī#!ď"qčŅŽvNēR }îs?N<;A]hę™ĸí<CØō¤îđ0z—īĐé‡ĀÎJXž‰)#šÎÁ| zt€č Ė°G˛1/6ÚcŖíô lGÛ%üõ.|ī}‰V{ßÅҘ~÷QšY¨Ņ5xÎAxÎüī”ā)÷g"tށ`ŌtFnM#PT3@ÛŋÂ}2ž¨Õ)(‘Á[Žį5˙?{÷Gšßųũ]?ēYģĸŨÄvãČ1é)\°7âēhnzr †g aXŗ° .@ô]�q�#b€`)ÁČĀ!ä`S€ŗÃEXîéÉāH€gS1ëšŽ4ÖĀ-sjßĐb3¤¯éíqšŨUõäĻ$ŠúÉÕüįķŖî~ęyĒž*ՇߧÅnįéK^Ŧg5æķ‘>ãėė_�ĩÆõĐ4ō_ņíīBí_\Ŗē ´}—æ–Ąų܉ŠėīüúsŊÕG÷…ũ^W"ōĩYĒzäVtÄ嗗ūœđwķk˙å?€ļWāōŸđËOžËˇ˙Ų¯Ãßūūū_V ?y…æS;÷Ŗ÷/ß_NJ>Ėö˙“ƒoū_9JâūīœŦSũņ_Rũd~œƒß:ÁˇüīüŨŋüøÛīrČ÷°~̉_ž—ŖZŲ&~îMâGQŋ|“Ī^ë˙ū„}.ՙO¨˛Åß˙¸ īŋŲķ:‡÷ô áŋūs>ûëC8ĩąī�4ûÎ]*īũ9ņßúĪųvˇÛÁŧˇTõkßå×ūĮīÃßũíßŌäFõŊŠĮ›0•WøvÂü]ĸžËˇ_û¯Öųģņ—Øņΰūņ?yĖą‡Xí:Ÿ]ũŒ(ūÛî™Ī€ÚŋžÎöŋ]' Įgôđ­Úžųėî%ū'ō˃ų^ßâoŠūø/¨~ŧŽy?‡•íã[ßųūî_na­š¸xx{æĶŪ{Ž’¯`¯Ũäŗ• ^&ĒTüPoū|įoúMž}äOøģÉģĨĢ˙ūŅeĨđß-ņ÷˙ö‚ņËr¨īYWÚŖįHDžzĪü>Žũ[į—˙ķ]ũO=ú'[šOųf.U‰ˆˆ|Ą‡ˆˆ¨âQpˆˆˆ‚CDD""ĸā‡ˆˆ(8DDD""ĸā‘/Õs}ČáßüÍßhĻDDžA~ã7~ãłãiˆˆČ7‹–ĒDDDÁ!"" Qpˆˆˆ‚CDD""" ų•=×īqTĢU666‚@3&"rCÁuIĨRxž÷bÁq÷î]’É$ņx\ŗ*"r@Ųvcj}}ŽŽŽ'ˇ{žÎjĩšBCD䀋ĸˆx<ūĖÕ%ŊĮ!""ûĢL4""ĸā‡ˆˆ(8DDDÁ!"" ‡ˆˆ(8DDDÁ!"" Qpˆˆˆ‚CDDä Ü/s°ų…ĶUC åĒáÔ@@ļųķgúrŒÄé:Y_‘—78ĘK1.%ęĖôī<Q…"@`1ĩā°u ›†ŗ'Cūúƒ8ķ!}[6ųdĀT7{Ú.ũԡ͏8Ķo¸:gCJ›†ąl¨ŗ*"ōúŌ–Ē %‹Ŗí?/-šŒ,ÄŊiÁŠËĨ2”ˇĄ›׀�2=!ِRáņm<7âÜ@H†ž$´¸JšĒNLjȁ¨8Ōí† E Ú ==“i‡ėœ)hí ¸Ø Õ T›áĪm;ß'ōp›Ņ&h(¸L7י適ÛUĀĶyyųƒŖĨģÎų…Cs† Z…Á!¤ ƒ31Fˇ keÚĮ-5u{ژ¯ĩF0ī2Q1€Ü-‹˜öé�� �IDATS:¯""_ëÆώˇ÷Š …žīkļDD8Īķ¸sį‡~bũs\Ų‡ˆˆ(8DDDÁ!"" Qpˆˆˆ‚CDDDÁ!"" Qpˆˆˆ‚CDDžÁáē.ĩZMŗ%"rÁļŠÕj¸îĶ?˙öš>ˇŊŊ ‚ Đˊˆ`Žë’JĨ^ŧâŲWÅq÷î]’É$ņx\3&"r@ŲvŖ–X__§ŖŖãÅ*ŽZ­ĻĐ9āĸ("?ķm -U‰ˆČū*Mˆˆ(8DDDÁ!"" Qpˆˆˆ‚CDDDÁ!"" Qpˆˆˆ‚CDD""ĸāųǃ#ˇč22ŖįGq†į\F–ŦGveč˛Cų ŨbzŅĻú+n]Xp™Žębįü>ŽĪCæx@›áM‹ą“!ū†ÃđŒEē M“¯í4ŦZŒÍ9 œ `Ée>0loÚt¨3¸ãû[g×6#Ū?a1˛`áˇ@‘ˆŠūo'(&>p)ĩĘÚŦa~Åb°ĮæÂŒCk{Dé˙qhũíūBœųΐž-›|2`ĒÛblΆ$”6 c'#~˛bSLXü'Õ?ķkLvXL\vxũtÄĨ)‡6?âĖ1ÃĪ?˛hu- DŒ÷G´čúUŸc’wHgÎ÷ Ŧ:ĖĀļͅ\úNdp¸¸lÁļE“šĸXŧÚ2|<āõM‡å*löCÆ3÷BŖĨ*ŧÚq~ ¤į^&­8ēëœ-äÍC;Oé ΄” `čIB‹k ä ¯' ƒŨ†_ĖqxnÄš­ŧËeĀÛ°Éë[vEDĮ—  ]í0[„Ŧ$BÆú#ŧ�Ö�nŧŨ{Ür% …U‡wŽĀģ§CŌ�DŒŋQ\ŗ™ú F:>eН>ƒ˙ .ĶÍufz`ęļCHÜ Ėũ­Ę÷ēl‚Ļ?ö ¸˜‚rZ\]\"ĸŠãs•é )æ\&\æÛ] 14P's+Æd%d,å0<ī22cö1o~Ô>užsšZ´H¤ ­÷Čft&ÆĨÛ6[ÍūN)âu†øˇbŒũÂaöŗ'ėXkˇ\&~ᰕ€Ų[év¸šŗiō ĨĻnÚ,O)į26cäēũŊW#"ōÕŗnܸaz{{ŸÚ¨P(āûūË´ÅRŲГ‚ÜLŒĨū:#ÍēDDîņ<;wîpøđá'ļųÆ-¨\[ˆņ“CŲ WhˆˆėÛ7+8\Ãųˇë:ë""/@ŋ�("" Qpˆˆˆ‚CDD""ĸāQpˆˆˆ‚CDD""ō˛‡ëēÔj5͖ˆČAÛĻVĢáēO˙P‘įúȑööv666}ɄˆČAæē.ŠTęÅ+‘}UwīŪ%™LĮ5c""”m7j‰õõu:::^Ŧâ¨Õj ‘.Š"âņø3ߖĐR•ˆˆė¯2Ņˆˆˆ‚CDD""ĸā‡ˆˆ(8DDD""ĸā‡ˆˆ(8DDDÁ!"" ‘¯:8r‹.#s1z~gxÎedÉzdW†.;”_ÂI,,¸LWu1‰Č7ƒûe ”9ÁfxĶbėdˆŋá0<c‘n†BSÄäk; Ģcs'Xr™ ۛ6]'ę ŽÄøūVę„Åĩ͈÷OXŒ,Xø-P$bĒ?ÂģBäĐĶÁĒMS_†ķéf‹ÂĄ€ŠãCSm~ę~ÃÕ9’PÚ4Œe §Ž‹¸Ŋb“čŒđ7mōíuĻŌS [‡ °i8;ņĮ+6ńŠoņY´ē"ÆûaäŪ!=ēŪDDĮ T y‡t&ā|ĀĀĒÃl�lÛ\øĀĨīd@‡‹Ël[4y+Z€ÅĢ!ÃĮ^ßtXŽÂV�i?d<s/4vx§zCψ˜]ļĀ3¤› M‡ ˇ?ļ)žqn ¤COZ\%‡\8ÔØū\Ębģ5d8Q*ذârŠ åmhÅæÃ ÃëIÃ`ˇa9Eđ6lōÁî1DDTq|ūĒĐÕŗEČú@"dŦ? ` āĻÁÛŊĮm!W˛PXuxį ŧ{:$ũH§%ą\ÎĨ)Sc¤ŲæÃe›*š   ā2Ũ\gĻĻn;Ü[uō€-Ė#ŗÔÚpąĒ¨6C.˙āĩžcSPŽ@‹ksõŪ"" Ž“é ššs™h†B{¤k1“ˆ™ũ Æd˛ÎX*ÆđŧMKÅâhĻÎāž>jŸ6–ŸüH¤"ZwŋØ\]„ŌĒÅ`ÆĐU2Lä\ÖC6ŗkŅŽ$ˆ`Ūeĸb š[<Ü×nƒ31Fˇ keÚlHēFs6ī÷…Lį\ƚ-Š„Lč‘ƒĮēqã†éíí}jŖBĄ€īû/ŅaŲ ]ļ˜<Ōĸs,"ōÜ<ĪãΝ;>|ø)wX‘}pæaELŸÖÉų"¨â‡ˆˆ(8DDDÁ!"" Qpˆˆˆ(8DDDÁ!"" yYƒÃu]jĩšfKDä ‚mSĢÕpŨ§¨Čs}äH{{;A ™9Ā\×%•JŊxÅ!""˛¯ŠãîŨģ$“Iâņ¸fLD䀲íF-ąžžNGGĮ‹UĩZMĄ!"rĀEQD<æÛZĒ‘ũU&šQpˆˆˆ‚CDD""ĸā‡ˆˆˆ‚CDD""ĸā‡ˆˆ(8DDDÁ!""ōĩŽĀføGM žÜ¤°ā2]…ĩÅÃÅũÎČŧXL/ÚTŸc‹{ã•WŽ–÷9\Ņe¤ ‹HDžYÜ/s°ĩ%‡­äÖŌ!mŸē Cæ_3,ÎÅø°/ ´bSLXn/9LlrAČĪNF,/ēˆíM›ŽuWb|+âLÂâÚfÄß!ˇj˜Ųˆ˜_ąėĩ¸4įRō HČûũ01gCJ›†ą“?Ųī{U›\SHßĒËÅUC›’_g<áōÆœIō+gß 8ž3kķK6ųĀæęŋwɯÆx\Ŋƒd¸ôoāĖCžh1” āæž}ßty§�]žÅļ_g<­‹QDTqėaņŪ2œí 9—pxoíq1fx=iė6ü:¯v‡œ¨ķæĻÃrÕáâ˛ÛM䊍ĘâÕΐáã¯o:üĮöŋ5$ģķ­‡Õ‚Kί3>PįũžCOZ\%‡\°{ŧÆ>N}gB΄”ķk@S*d¸7ä\‹ÍėŽĒ¤Ī7tuGœúG!Ky›jÕáĒ2Zیû6SõčžoW-ĒžĄīHĀģ QÅņ¨ęŠË ĀBcșœÍģ'ĀÛy};°ölađöî]"dŦ? ` āæcÚėâšĀÎ÷‘T .ĶÍufz`ęļCH<Įž?4Æãžß¤%dp+ÆĨ›čŠãíĘãęöŊÍ­sĨb‘_qÉ.˟4ēEDÁąÛôuûŨ:C;IŅw%ÎôˇüU‡É›†ŋŪ‚V ŨŖ9›īíŊŖ{!cŠÃķ6-‹Ŗ™:ƒ{i6´nēL¯…Į™™Ŗ%X#dōXķ. ČŨ˛ßī?=ÔĢs}paŪáClZOÔi{Ęĩ$ Ĩë‹~ČĐQ8ŧ`øĶß§‘ “‹/Fœ{+dûîÃûŪˇcbĶāģcyYX7nÜ0ŊŊŊOmT(đ}_ŗõ4Ų则~k.Ų•™×TEˆČËÅķ<îÜšÃáǟØF˙÷sPūÔa8gqîÄŽ ØÖŧˆČÁäj ^\KGČTĮŽ'ÚfÚ4/"r0Šâ‡ˆˆ(8DDDÁ!"" Qpˆˆˆ(8DDDÁ!"" yYƒÃu]jĩšfKDä ‚mSĢÕpŨ§¨Čs}äH{{;A ™9Ā\×%•JŊxÅ!""˛¯ŠãîŨģ$“Iâņ¸fLD䀲íF-ąžžNGGĮ‹UĩZMĄ!"rĀEQD<æÛZĒ‘ũU&šQpˆˆˆ‚CDD""ĸā‡ˆˆˆ‚CDD""ĸā‡ˆˆ(8DDDÁ!""ōÕ‡ÅÄOcĖī<ššnbxĨņįōRŒˇo=ēÅôå3ŸãøĶ‹6Õ/ō×\˛ ÅÅ#ҧxč˛Cų‹œÛ‡âįÔÛâĸÃŌî'VbdožxŋW?ˆ3´d=Ú˙¯hjŽq˟W_”‚ËtU79XÜ/gÊ.‹‹EhˇÉ%"ŧ‚ ŗ8•…ÜĸË|`ØŪ´é:QĮÃâęĸCždSôĻ}›Ą ŋŠDLe Ķ [‡ °i8{2äöqæS§’?ŠĖ7cL´…”W,{-.Íš”<(ō~ŋáęž>ŽģߔG¯Øœ{;¤0×ÄOÚˇšŌm34Ķ'`ô#‹V×ĸ@Äø‘gLAÕblÎaādÄô´ÃŅcÛĢ6M}uF\‡áëéf(4EŒˇ8Œ¸uĻ[ۘļø_Fŧ…×~Ķ0ûÁ™´!ŋbqö­€ã›B2Âßx¸ÉN›z Î1ä‹C؀ޗ÷V MÍv{ĀxŌፅ‡ûģŊb“8r{&F.ŅĩiAûÇ2?ã*†jÅp.˛•‹1K„Wą8š xŗãû[g×6#Ū.Ÿæ1ũ;\k¯1^sɯ1Ųa1qŲáõĶ—ĻÚüˆ3GáRŪ"ŨlQ80ÕiķķĸCą’Ū鯴đŒ}8Ņr˙‡’8šÎžĪlōɀŠV›áŨũˇē7vŋáęœ I(mÆ˛!ų+íģVmōɈŒgq­ņŗ=×ä@į؃žÅėž^úadgŒßö-ū×tyÛ~ņ´nLĸŠ�?RŧeS-8xŨuúĒ6‹Í,oâpqŲ‚m‹&rE €ž‹ŲoŲĻX…­�Ō~Čx&Â[qšT†ō6´bķáx œČöø+6E,ŽŽÎú;÷î‚Kί3>PįũžĮ÷ą7đ~§ŲfžjqŋdS-:xéˆ\Ūe ‹2āmØäçüļͅ\úNd<Ā‹Čö†œī†ŲUČåŌ™€ķũĢ×üVlĘE‹ž4|¸aq­lü5hJ… ÷†œką™-ÃZÁĻ5méc6ZmĮ}›ŠĸÅĨë6e,p kE‹5퀊ÃT2ųZČp§y¸RĢ8LTBĻĻ"üĒÃĨrČdČxLį-ĀâÕΐáã¯o:,§"2‰ˆ3ŨæA˙ėôī›'V‚žqn ¤Į3¤› M‡ ˇ?ļ)Ļ"Ž&B†ũ{Ąüû°§īLoČpиļööŋ{l =Ihq ”rU 0dzCFz %"†{CúĘ6…Ŋ×͆áõ¤a°Û°ŧ÷z ŒŅYĩ¨z†ž#ī*4DĮ.Јž˛ÃÅ�OB_ŲâŊœé:-8ëđXŽöė][ȕ,VŪšīöBkgĀÅ^¨V Ú ×0x^ã†ŽÍæę-‹ŧrkįFė|?I5h<ˇˇŊŽw~rŨÁk,š\,ĀĀ�°}Į.Ļ \–ŠËĨŨKˇ\.!ĶAēÚaļŲ4ā@ĶŊ†ûž/¤/p™X…ß9aøųGÕæķ8cØĩíÕUÊã°õ´bgWõd€ĩ ´UėGú{â’ËŊãé1gbđø ĶÛį•åņ ßōŊnjĖS.įŌ”Š1Ōlķáōķ.9>Ī>XTü#ũ›ûcSp™nŽ3ĶSˇĒ€Įƒķˇ{Œęcާ\ūÁë]/ŽÍ՝1Úzę\ŠXäW\˛Ëķ'îLĸā¸ĶjŗyŖ0æ‚ב˙#›ŗŋ߸QŽĨb ĪÛ´T,Žfęx@îĻCi͆Ŗ­Ÿ: įKU‰TDkgĀāLŒŅ-ÃZšąd˛[úXÄȔͩ˙ÎĀNpА™‰1Z‚5B&ûíãūR՚Ãp)bĒ'‚\ē†!ãZü`)â/]hé ™ÎšŒ5[ ™<úđŅĻģ&ģEŨL"bh döƒ“ÉGK“L_Č՜ËD3Ú&](%mŪØ ¸Øb˜-Äņs‡ū{‡|sČĀŪ>‡K“‹/Fœ{ËāŸ0ŒÎ¸ĖbQíŦ3™|Šjvc /X­Xxíģ͍suoŠę\KŒ‘_Xx›0œ1PxÆĨĐ1¸ŒüÂĸkŗŅŋīJ9L•`9€Á]Íģ: 9—í¤ĄõÍėZ@ŸįpņVDf'h÷ģ÷¯­c¯cķ{õ=hØÁŧËDÅ@rˇ,†žÔécŽÉt;ŒælŪß{Ŋ <Øli1ÆÄĻÁw!ŨŽ›’|ũY7nÜ0ŊŊŊOmT(đ}_ŗõ2YsÉŽ„Ėŧöõüéĩ°fáˇŧ[1ŪĻΕî/oėéË1§ëdu•ˆ<Zũ{wîÜáđáÃ_‡ŠCžtÛ_ß]+.ģ\\6xړ:U"ZĒ’¯^[ĀLÛ×w÷ę |EcŽëúyú@Qpˆˆˆ‚CDD""ĸā‡ˆˆˆ‚CDD""ĸā‡ˆˆŧėžë#G\×å“O>!‹iÆDD(Īķxå•Wp]÷ŃŖŊŊ ‚ ĐˊˆPÆÖ××IĨROm§Ĩ*Ų—įĒ8îŪŊK2™$kÆDD(ÛnÔëëëtttŧXÅQĢÕ""\EÄãņgž-ĄĨ*Ų_eĸ)‡ˆˆ(8DDDÁ!"" Qpˆˆˆ(8DDDÁ!"" Qpˆˆˆ‚CDDžIÜ/m¤ŠÍčŧ-*e?`ĒĮėŋŸ˛ÍäfÄČ!—8˙_Dūŋˆ‘ÎoĀŲ*ēŒT&ĶēpEä›%‡|KŸö< XÖ\Ū¸gŽōE‹Ąl@׊Ë{̆ĻĀfģ=`< s.%Š„ŧÄæÚšĄgËĻä†xŸŲ\[7Œ$l†¯[´`Qí ˜ėŪ Ĩ˛ÃЂ…ßE"Ļ2†Kģûë7üdÎĨœ4lmÎfC–/ĮÉu†ô}f“OLuÃԂÃÖ!(lΞ 9ž3sĶ´ĩ›ŗ! ĨMÃX6$w%Î|*âÔą�ō1Žš^Åâõū€íšÆö]Ģ6ųdDÆŗ¸VŠøŲɈüĸË|`ØŪ´é:QĮ_˛É6Kž!ŸÛŊ?†K?uhķ#ÎôÁÄîãíđt‹ČKuū°âpaÎĸ tuŒ4­!ÃŊ†SÄ)Zø×mĘ~DĢkX+ZqČųufz \¯ŌX]ˤ ~9$ķ-— wŨáhĻÆHËN(ŨS…­�Ō~Č9ß@!öp+¯-Æš°’2Ŋ!CnÄō´MŅŗšT6 ēЊ͇k!Įw}TũCmģ#z’Pt Ĩ’CŽâ0p" ëÁRĢ!Q…– ›Ģ̐ ™ŪS)‹O#†{aë˛CĄjqqŲĸ¯š<Č-&}C—ŅSŒņƒ=ûãšįBü5įĄãUhˆČKå /2Ū`1úS—Åß~čūžÃ0ԐÖ*ĐļęĀÎGÃWëŠG˛}¯¯Ũ%ßr% …U‡wŽĀģŊ<_4öķŪ~ĩv\ė…jĒÍOi[p™nnŅÔm‡*āađ< â0ē1˙VD!pČīlŲ´ķĪŨ3‰ąū/€5€[^xlF›vúŲ{ŧ§C´˛%"/epxÅÄ\ ¯Ųāå#=8°á0šųbÄšˇ ū ÃčŒË,ÕÎ:“é€ĖLŒŅŦ2yäņũgúBĻ\F=‹Ē0™j,UU?uÎ7–nЈÖÎ=ũõŧ9į2VŌfČųã_„ÜM‡ŌĻ ĮüΈÁ™Ŗ[†ĩ˛á\öÁRėiÛ ĖģLT $ wËbč^ÃæˆžĒËč/,ēļ Ĩ[6e õą2–Š1<oĶRą8šŠ3œ4”Ž;,ž œßŊ?Ū+zäxu‹ČįĖēqã†éíí}jŖBĄ€īûŸ˙čk.Ų•™×Ė×jRĻ/ĮHœŽ“ũœÛŠˆ|ŨyžĮ;w8|øđÛ|õ˙w['JDäeâ~ĨŖˇĖ´}ũ&ečtũ i+"rč�EDDÁ!"" Qpˆˆˆ‚CDD""" Qpˆˆˆ‚CDD""ō˛{Žq]—O>ų„X,Ļ9 <Īã•W^ÁuŨŽööv666‚@3+"r@cX__'•J=ĩ–ĒDDd_žĢâ¸{÷.Éd’x<Ž9 lģQKŦ¯¯ĶŅŅņbG­VShˆˆpQĮŸųļ„–ĒDDd•‰Ļ@DD""ĸā‡ˆˆ(8DDDÁ!""ĸā‡ˆˆ(8DDDÁ!"" Qp|Q›á51TxôĨÅųģÛ:d¯ØPļ™\Š.#<ūUŦšdŦ‡dŪzlĶâbŒ‘â7íC—ĘēūDä%ä~™ƒ­-9l ī°–iĢڌÎ84ų[% (܌qa3âõ„M‘*6×Ö éM›|`ŗä5$l†¯[¤›ĄĐ1ųŒL9=ąŊjĶÔWg‡áŧEēŲĸp(`Ęߡr̆™�o9Æ,^Åâh& s¯ÍRŒą `ÚˇũČĸÕĩ(1ŪŖ{Įkklŗ4įįGkŒˇÁô•^6diŪ†$”6 cYs?@Ūžļx(¤Ĩ#[­ķžįōŪĒĄ)°ŲnoˇZ°đ[ HÄT„§ëVDž‡Å{Ëpļ7ä\ÂáŊ5`ÅĄŽsņxČ`ĸŅfúcÃBFē#ZvmŨįēē#zœÆã\Ū! 8ß0°ę0�^Dļ7ä|7ĖŽž!Ũlh:d¸ũąÍŪÂođ[C˛Í—Ę!“ũ!ã}0ˇv‚%Æh%`ēאËģ,aQŧ ›üãÆÛŅĶRXļ ę0녜r =Ihq ”rÕ'ĪŅĨë6e,p kE‹ĩ*löCÆ3 ųUÕ—€…Ɛ39›wģ?įAhē÷į�r9—ĻL‘f›—mĒû ē-/¤uÕf‰°^Į.Ļ \׿ęžņîk yŗėruÉÂ?V‡BŒéæ:3=0uÛyh?šœŊÛ†úĪZښáJ Ģī\wO‡¤uŨŠČ7!8Ļ¯ÃØīÖÚų‘šīJœéļ�˙ĪbŒU"ÃP—Å…9—ž¤Euį† Đ’4”Ž;,ūÃÆãL_Č՜ËD3Ú&]‹ų=cvu&r.ÛICë!›ŲÍčá͆ÖM—érs-1F~aámÂpÆ@Áp´3dŧËå훟™ÎšŒ5[ ™xúņĨ-ž÷‘áO7*æ]&*ģ �"šcüÁMč*Yxí†s' Ŗ3.ŗXT;ëŒ'Ëm~ $R­ēfEä+fŨ¸qÃôöö>ĩQĄPĀ÷}͖ˆČįywîÜáđáÃOlŖŽ+""ûĸā‡ˆˆ(8DDDÁ!"" Qpˆˆˆ(8DDDÁ!"" Qpˆˆˆ‚CDDžQžëĶq]×å“O>!‹iÆDD(Īķxå•Wp]÷ŃŖŊŊ ‚ ĐˊˆPÆÖ××IĨROm§Ĩ*Ų—įĒ8îŪŊK2™$kÆDD(ÛnÔëëëtttŧXÅQĢÕ""\EÄãņgž-ĄĨ*Ų_eĸ)‡ˆˆ(8DDDÁ!"" Qpˆˆˆ(8DDDÁ!"" Qpˆˆˆ‚CDD_”ĀføGM }iq>ÆÄÆîļŲ+6”m&W€ĸËHk.ŲëWxŨbzŅĻú̌ųû[Xp™ŽÂÚbŒáâsö[ŒņÆ/,¨8ŧ1cdÎet.Æđ‚Íî.Š—‘9—ėds.#ķ6{§úrŒ™/ø2™ˇ�›áËkĪģYÕaxŪząĄŸ2˙W?ˆ3´d}nGųČ5ü´ļ‹K=¯;ךČטûeļļä°u"€ŧÃZ:¤­j3:ãĐäGl•,| p3ƅ͈×6EB¨Ø\[3¤7mōÍ’×x<’°žn‘n†BSÄäk02åpôXÄöĒMS_†ķéf‹ÂĄ€)oYLĖš”<(ōūŅ76gCJ›†ąŦa~ÅbĐwų§ †3i‹kExĶ7lm™:ƒ›.ī ËŗØö댧wúÚŲ˙S.ßߊ8“°¸ļņūɈ–}øÉŠM1aq¸Ŋä0Q°É!?;ąŧč2ļ7mēNÔ~Ü÷Ģ´†LžŒvæ8ÆīŨŒ˜émŧä§&ĶpõŠMĩ?`Čĩ˜˜‰14lmÎfÃûŨĖĖŸ}´Î™Ī\Ū[546ÛíãI‡7āLڐ_ą8ûVĀņ+§xËåâĒ!Mɯ3žp¸=GBŪí5x@ņ–CnÕ0ŗÁg6—~aąĩjŅ•ŠsjkĪXŨæÁ=ŋ`Ķr$dj:FëPlÕ!;g˜éˇZ°đ[ HÄTÆ0Ŋā°u ›†ŗ'Cng>qę7āÚæcæŋĪbē~ú1Į~0ĩš=ķ?¸{¸Ÿ~øƒ=×đôå8šÎžĪlōɀŠn˜˜s)īšķÛ+6‰ã†K{ŽŲôŌÎuŪŅãę%ßøŠÃâŊe8Ûr.áđŪ°âPHךx<d0ąķ“ũĮ†„ŒtīÜXwôų†Žîˆgį/tŪ! 8ß0°ę0�^Dļ7ä|7ĖŽž!Ũlh:d¸ũņÃ?‰T .9ŋÎø@÷û"ŧû¯z’Đâ(9äv•‰ļˆáŪ€7?ŗđwĩ]ĩ¨z†ž#īĻü¯v† x}Ķaų~t^Oģ ŋŽÅĢŨ!įęŧšé°\u¸¸lÁļE“šâŗ:nk¨–žrZWĮ|ņĩ€ļ:\ZiėÛė\ŒÖ9ßaqéēM \ÃZŅb hJ… ÷†œką™-?8ĻŠāė@ČųržQMl'CÆwBĀođ[C ×eĄ�� �IDAT˛ŠÆ9~-`˛fW?Ö=׊†ÁĮ}%@ļHû!ã™oÅåRĘÛЊ͇kā0p" Ûü„ųOEdgēyė1ÜĢx˙=ũ<r 7dzC†ûŧe›âcį|ĮžköūuŽĐUP]qË! !gr6īv΃8Đtŋš€\ÎĨ)Sc¤ŲæÃåG—š<ˇŅ ėē)\Ļ›ëĖôĀÔmįņKTÎÃũž:W*ų—ėrĀüIŗgĶīŠĶ&2Öá<×ĪZŅĻĨŊžīŠķZ …mĒ0 õd€ĩ ´Uė‡÷ëß œđž˙=2ÖŽeÍy7b (:ÚWÚBŽdĄ°ęđÎxˇZ;.öBĩÕf¸†Áķö;˙;˜=ķs?ũXĪ^ŪÜ; Qp<0}Æ~ˇÎĐÎ_æž+qĻÛü?‹1V‰ØŪ‚V C]æ\ú’U,Ŗ´$ Ĩë‹˙pį'ēžĢ9—‰f(´Lēķ{Æėę4Lä\ÖC6ŗ›Ņà :231FK°FČäŊĨĒÖæ]&*ģ‰gßŌbŒ‰MƒīBē}s“n‡ŅœÍ÷ö⅌Ĩb ĪÛ´T,Žfž°TUrˇ!°XsCūpā)ƒuŧ9į2VŌfČųã_4ŧؐ^Œ1|ĢÎø ÃčŒË,ÕÎ:“É'Ũš>¸0īđ!6­'ę´=î’j6´nēL¯…nŋwŦî{?h8xéF�öuÂŇŌ!›-Bǟ6– ũH¤"Z;gbŒnÖʆsŲpŗ˙¸cxōü>2Ÿ!ūĖîkx§"žéPÚ´áX€ß=fÎŋ7÷¯s?丧”|=Y7nÜ0ŊŊŊOmT(đ}_ŗ%ō<?$]Ž‘8]'ĢŠ—įyÜšs‡Ã‡?ąū9ވˆė‹VTE>gC§ëš9ĐTqˆˆˆ‚CDD""ĸā‡ˆˆ(8DDD""ĸā‡ˆˆ(8DDäe÷\9âē.Ÿ|ō ąXL3&"r@yžĮ+¯ŧ‚ëē/ííílllfVDä€2ưžžN*•zj;-U‰ˆČž<WÅq÷î]’É$ņx\3&"r@ŲvŖ–X__§ŖŖãÅ*ŽZ­ĻĐ9āĸ("?ķm -U‰ˆČū*Mˆˆ(8DDDÁ!"" Qpˆˆˆ‚CDDDÁ!"" Qpˆˆˆ‚CDD""ĸāøĸ6Ã?jb¨đčK‹ķ1&6vˇuČ^ąŋ°ÃēėP~Ė+‹‹K_ƒ35įPü폈Č=î—9ØÚ’Ã։�ōkéļĒÍčŒC“ąU˛đÂÍ6#^OØ aÍeāô yס¸đ‘EĢkQ bü˜ÅȂ…ßE"ĻŽY ī~ÜáíŒ=?ã*†jÅp.kîŲØœ I(mÆ˛!ˇWlĮ —ĻŽ‹;#üM›|{Šî]áķG=}ŦÚ4õÕÉcü døî€t1Æ,^Åâh& SˆķƒÍ3I‹ÜfÄäɈåE—ų°ŊiĶuĸÎĀĘÎöŋiøãĸCą’^ąIŲÚĶvpĶåtyÛ~ņ´.f9p‡Å{Ëpļ7ä\ÂáŊ5`ÅĄŽsņxČ`ĸŅfúcÃBFē#ZvļÜN†Œ÷ōy—%,ʀˇa“˙ ļHû!㙯ēįņŊĄ+•Š€Š˙ū>z’Đâ(9äĒģv÷PÄŠŪs)‹í֐áLDаgēŧF›ķ'"f—-š°x50ŌîpŠ2Ų2ŪĶy €WĶ!ÃĮNUf+—-Øļhō WÜĩũq42|oGĢļŨŽZT=Cߑ€w"r+ŽęŠË ĀBcșœÍģŨΎmÂ{đįžcSPŽ@K3d˛PXuxį ŧ{:äʞĮéĮåöŊš/¸L7י適ÛUĀ۝ Āæ9fÉēDž÷ėãŲž`!cũ^�k�7Ÿ˛ũžļmn+‹üŠKv9`ū¤ŅÕ,"+8Ļ¯ÃØīÖÚš1ö]‰3Ũā˙YŒąJÄö´b겸0įŌ—´¨>ÔGĻ/d:į2ÖlQ$düˆÅhžą4•HE$>uŪõ¸õ~„œoŽ1<o?ŧTÕÁŧËDÅ@rˇ,Ní᠛̋PZĩĖXy0Ūš–#ŋ°đ6a8c �Ĩ‚ÃäšÍ‡Í!ī7GøŠÆ>ĩT,Žfę îĒ„ú<‡‹ˇ"2;ũíiÛˇcbĶāģnׅ,"_âúŅ7LooīS |ß×l=Äfč˛Åäéđū’ÚĶcL´×™ėĐˉČחįyÜšs‡Ã‡?åî'_žPS "/?WSđ̊˜>ũü­ũãu&5i"r�¨â‡ˆˆ(8DDDÁ!"" Qpˆˆˆ(8DDDÁ!"" QpˆˆČËîš>rÄu]>ųäbą˜fLDä€ō<W^y×u_<8ÚÛÛŲØØ ÍŦˆČeŒa}}T*õÔvZĒ‘}yފãîŨģ$“Iâņ¸fLD䀲íF-ąžžNGGĮ‹UĩZMĄ!"rĀEQD<æÛZĒ‘ũU&šQpˆˆˆ‚CDD""ĸā‡ˆˆˆ‚CDD""ĸā‡ˆˆ(8DDDÁ!""ōuŽę§#—›¸i1ņĶķ;ĪOM71ŧŌøsy)ÆÛ˙§ÍčœËđt=—]Fææ+_Ė[Ÿ[oĶ—cĖė~bÍ%ģ`Q^q¸Z~ōvÅÅ#ůāĒX‰‘Ŋš÷äŲLÎÄ9|Åfq>ÎčÚÎ>ū"NzagŽĘoĖØäfâLl�XLĪĘø¨ØŒ~ctÁet&Æđ’Ĩŋ}"/)÷ËĖë™ėĩ¨NuY\,Â@ģM.ál茘-ĀŠlÄ)/bíĻÍhsĀdzĪøJœųTÄŠŖ!ĨļAaĶpödHËRŒ‹›=‡lĻWC–NØ Cæ_3,ÎÅøđD3%—÷V MÍv{ĀxģÍЂ…ßE".´:äV 3˙Ūpõß=x~Ē?Â{$âä:Cú>ŗÉ'Ļ’Î#ãĩbquŅ!_˛)úĶ­mˇÖlrM!}Ģ.cEhŠZÍÔŪķå[ÅĨcAĀ´o1ú‘EĢkQ â]ßáķĮ k‹1.¤ŌK.ĨCšb8— éŲécf>Æ57ÂĢXŧŪĐw+Æ÷ˇ"Î$,ŽmFŧ2âÚLŒ\2ĸkĶ‚öŊ'/bd dvމø>ļ Í0S2dp(āēēkp ,&?piÉÔ9ŸVō-Úođ€bYųDTqė“Ÿ)Ū˛Šŧî:}U›ÅĀf–ˆ7ŊgP�'˛›.—ĘPŪ†Vl>\ŗ˜ūØp~ ä|ODËcˇļ¸tŨόŽa­hąV…­�Ō~Čx&ĸĢ=Âo É~ëá៴[™Ūáū�oŲæIÂ@OČÅlø˜6SÁų““Ų:Í{BãVŒŅJĀt¯!—wYÂĸ x6Ë~@úc‡%,~žb8ÛiQĒÂĢ­į„€ßjH‚ÖĒÍÕÕÆ¸¯v† x}Ķašâ0„Lž2Üi¨>ít„]u(TōÍį[-fË0ŋb8ÕšSi,ÄøcBŪŧ‚uū0isaÎedÎefKųDTq<CĄh‘đ -x.Šč+;\ `đ$ô•-ŪË9Ž?á†ŋ›ÁÛš‹ˇv\ė…jĒÍ÷šÜŋųŨģáoÖũí‡ú2ĀZښáJ Ģī\wOî4k ~ūtHú‰ûd=eŧgĪtõ^Uķ ,ļŧÖU›%B�úŽ\LAš-.øžÍÄ"”R!į1;ĸ¸f3õAŒtļÎP3Pq]‰˜+ĸ8äīÍásžũōšMą%ĸ'€mˇąíФÅOō-éô!Ãŏ\ļŊ+@ÃĐ@3[1~o~v2‚ /2ŪĶ8ŽŅŸē,ūˇĮõwPDÁņD.ŋwŧĒá\vįæŨfķF9`˝3"˙G6g}v ÎÄŨ2Ŧ•K3CG,.ĖštyUBHEø×&oūz Z1œ;aq™ÅĸÚYg<á0œo,I%R­ÍĐēéōŋ-Güų'ģžĮftŪÍ>\}än:”6m8ā§Ŧ=ãíis4ĀĮy(‡Á؜ËĪw–ĒŌģ^;Ú2ŪåōöŒÍĪN„Lį\ƚ-Š„LD¤…,_r8õĪ¨ÚŒÎ9Đb¨6Gø÷v˛9ĸ¯ę2ú ‹ŽmCé–Í˙ģw)Ē9d؍1ŧ`q´báĩīŽđ,ū`&FxũxŊQeu†?€Š~€ļBœr˙ÃßÖÖYg˛ã †Ÿą˜˜‹á5ŧĀĸ|$x¨"‘—‡uãÆ ĶÛÛûôjĄPĀ÷ũ—ëȇ놙ˇŖĪsē˜ži1Ôû ĪéË1§ëdŋēSČČ›žŌĻëYD^įyÜšs‡Ã‡ *ŽÁ0Ôkž>ģSą™˜wđOÔ"ōĨ9¸Áá†ĖŧũÅ3tēūÕcsÄųˇ"]Å"ōĨŌ/�Šˆˆ‚CDD""ĸā‡ˆˆ(8DDD""ĸā‡ˆˆŧŦÁáē.ĩZMŗ%"rÁļŠÕj¸îĶ?Täš>r¤ŊŊ ‚ Đˊˆ`Žë’JĨ^ŧâŲWÅq÷î]’É$ņx\3&"r@ŲvŖ–X__§ŖŖãÅ*ŽZ­ĻĐ9āĸ("?ķm -U‰ˆČū*Mˆˆ(8DDDÁ!"" Qpˆˆˆ‚CDDDÁ!"" Qpˆˆˆ‚CDD""ĸāųʃãVŒÃĶ1FæÜÆ‹6åĪ{ŒĸËHa˙S02o=ôLaÁeēúøļC—ĪŋED^"î—9Xב:“ŊģžXsycΤ ų‹ŗo´\ƊĐRĩ8šŠã-ęOEü×I‹÷ĢķĮ k‹1.¤BĒķ=}ŦÚ4õÕI/Ųä›ĨΈ,&æ\ĘIÃÖĻál6$?Ŗu¨Nļꐝ3LĻr̆™€lĒąÍOVlŠ ‹AßfôēEē M“¯íėwÕblÎaād�K.ķa{ĶĻëD–…8ķ!}[6ųdĀ”o3´`áˇ@‘ˆŠūOםˆ(8žĪícŒ” �‰Î‹ hJ… ÷ú6ã\-[đœ č  @>€ŋãYü›Ÿ:,šļb8{.y§zC|>˛ō ]ŪNh�Ŧ¸äü:3=°ļįÂJHߞ}ōÛ#üÖ¨�Žáõ¤áhˇayÁ!ŠqžŽ^‰1„°msá›7ßĒ“Áa`Ųĸ¯š<Č-˛dzB†0Ė~`A+löCÎųFĄ!" ŽĢ8ĀÛŊ;ßŌX%˛¨V `đ<�Ã9ßfbJАķX\ēŋĄõÜûā9ÆĒū*Q…Žv˜-BÖ!cũ^�k‡Ķ8Ļ`įŋļ+Y(Ŧ:ŧsŪ=’Öu'" ŽũW`8wlo Ãđ1›sųųÎRUËŽWĶĮB–/9œúįA#,›Ģ‹PZĩĖZdžŌu‡E?ä¸tŧ9į2VŌfČųãĀ\\p(˛Ų"„fCëĻËôZĄļqÚa4gķ~_Č՜ËD3Ú&]‹™DÄĐ@Čė1&“uÆR1†įmZ*î/@õS‡á|cŠ*‘ŠhÕ5'"/9ëÆώˇ÷Š …žīv—‘+6?|;¤ ›ĄË“§ÃGnÖ""ōĢņ<;wîpøđá¯GÅņB*6ķū‰:m:ˇ""_™—'8š#Îŋíz"bú´N ˆČ—Mŋ�("" Qpˆˆˆ‚CDD""ĸāQpˆˆˆ‚CDD""ō˛‡ëēÔj5͖ˆČAÛĻVĢáēO˙P‘įúȑööv666‚@3+"r€šŽK*•zņŠCDDd_ĮŨģwI&“Äãq͘ˆČeۍZb}}ŽŽŽĢ8jĩšBCD䀋ĸˆx<ūˎ%´T%""ûĢL4""ĸā‡Č˙ĪŪũ†ļu-zŸ˙ŽŊˇä=ƒ5Ø wŽ2x°y°‡x/ UÁC=s Ņå)OryJ}āBta >0œxŪÔ}÷ÍŽ‡ؗ<Œžš ŽJÂc“”ą!ejˆõD 6Č 2˛ĩ˙Ė )ņ߸I˙Ôũ} ´Š—Ö^kíŊ×o¯ĨT‡ˆˆ(8DDDÁ!""ĸā‡ˆˆ(8DDDÁ!"" Qpˆˆˆŧ‚ķŗéq„3_ÂĨö<C%î1sÎâ\ų×+•ĸÃ?>ø§‘€Îša*įPpek­>ĶC!S3žtěUĻ’—˙}#ä#ĖVCbĘ|<âĶ˙Ę^Ļ>ˇy÷CÁ×íWÉ!ũ•Ov(üÎ2ͧĻ:ëL'~ėÁ5ĖfmâÃ>ųŲ=cT3¸ũunÆmŪû“E_wŗí7–›bÁaj­Q÷ę|<V§ˇā0U׃äK5›LŪĸŗJŽĪĖP@y_ŸRŪ&9â1ččfQp|=gëL4&Ŗą?ŲĪ5&˛ŌšÃøZĀGb@n!BåÂ3ÍIŦhąāü~äõ'õĘJ„ŠSšæ¤^Û°XŠAąč0šŌ‹E9QįÆ)›ß.Ūí‚üˇđ._rČy!ۛ=ęd^”g˜Zp(ģPÄį}°ĩn3ĩË%øũÎĮ“eˆ{Îՙl9#‡?Ŧ‡´xÛ]7ÛmŪ[„zC–× W?đøËB„|{@ĪĻÍŊޞ ‹ņ/ qĮP āæPcŦ�*‡\ĸÎŦkČīŖŠÍ{‹ qŸé‘ā;Į†ÖÆĪŊĶŊPYsīōÆbė‘Ą/đä[čqĄæL¤}z]ÃøŦMøl_™ËI¸˛d˜ģęnQpŧš'_9Œo6CdČŖĘŋˇôü틉аē’ęÜ}_"�<‹?,8ÍGČGÃūąO˛…˛ĄŋowÂr;1L| WG} ÉĖÚü_m6īí03üå+j6“̆d7´¸/2Í ļāOÔÉöCĨnÍĸ%îs}(d%ež×b!ņ*ġ ˇ&Īī_Üz`QIĝRŅPj‡–ŸĖ@Hr3Ęü˙kŗŒOîb@mÅâŽųe‡' ¸Ë^ĀpŗīųĮ0<ō"Ø,nåūšfXŽ…ü~Øl(ی-4w&ģ|>:rl¯d>}2qĨąz)|đņEŸÎĸCú‘!{1$V˛É,ڔ{ęô×Ŧ#ĘøÄ-Š}ēßDožâđ¸9°įĒ@Ücé;f†ęd:BúÚ ÷J0übÅąfQI„ĮŽ8 n÷o_õv…Ŧ t6'ũ’EÎyÍ'ß6Ÿ‰Ą�×ƒŌž?v ų;NjžŲũ3vW$ˇō†ËŖũßn¯š#*ōHĨ*tV­uŨ¤äyÉ¨T!ļ§üV âî‹3pmØcĐ3Œũī6ņV vĊãņQcŪģõˇæPė­7&|7$î6ûr ؆Ō7۝>3úĖ~a~Ā?T Ą ĮŲАą´Įøķ#u.×Y]ˆqC\ ´{ÜčæĀŠzz=Æē›!qÎcúÜūjcįę\ĪE¸’ ‰;† ÃÉ$|’ŗų‹ø…:˙S‹áˇ9‡í.(�¸>29‹XÕĐ—ÚŗUÕí‘ĘF/C ŸéžÃĄj7L-ÚôˇAĸl‘ßŲ˙ķkBÆŗw1ÔēëLˇ¨âŋ õÆîz6 n¤’>ŗy‡‰VCŸéáŨ­Ē6Ę5ĀŨ?Ļ7SÎ˛ŠÃC~ôØãĸ!qöÅĢ€‰ŗÆ n F‡BÜ-›ņŦE§k(Æ|ūÉ č;P EB.ë^91ĖÇÁc   =/ūœ %Cĸ3Ä}á uæÎŊēlĨaŦVgļ˙-ėHÉ!Ŋæ“Õg"ŋŽëōôéSΜ9ķĘ2úë¸oŠâĒC&į)„\ë>žlŦ×c¸čĢŊuĪ%L/ÃĮƒ ‘“DIō-5<\gøĩK‡ŒĻŊˇ°-H9Y´â‡ˆˆ(8DDDÁ!"" Qpˆˆˆ(8DDDÁ!"" ųĨ‡ã8ėėėh´DDNr X;;;8Îņ_*ōZ_9ŌÕÕÅÆÆž§¯9ÉĮĄŖŖã‡¯8DDDŪhÅņėŲ3ÚÛۉFŖ1‘ʲk‰įΟsúôéļâØŲŲQhˆˆœpAFŋķc mU‰ˆČ›­L4""ĸā‡ˆˆ(8DDDÁ!"" ‡ˆˆ(8DDDÁ!"" Qpˆˆˆ‚CDD䜟ë@ų%‡ųMCž`Hö¸]>Ķũáž ũÜ0ũĄOėG8ŪôŸ"$ūĄNúˆŦ˅L‡?yŸgl†G|JK6î O˙›ŧšf1–3|<˙'‹žîF{+UCz¤Îåb„3_ÂĨöƟˇuûLö†ä#ĖVCbĘ|<âĶß<ËłÃÔ€au >ĢĶ[p˜*‚ëAō‚ĮeĪf|Ų�ĐŌë1™0LeŠ­!åjČĮäŗ6ɏÁī{õŦEHWędš¯Ģ6īÕĮ֟æYŠqūavÉpy0ĀũŠĪKĻr'ĪPkõ™ ^^įšûwˇmËW|ūõÀųŦÃĒRŠ…LĻ}ZÖÆC'†xëq‹ņb!/āF>Y†›ÃÁr˙ˆŧÁ‘ôHa‘Ų4LŒø$6l2YCo+ZĻ/ž¸1  6Ã#Ŧ8äŧíM‹ž u.­Eøû­€Ú ÷6ūxÁ0ļhHÄ HĀĖĐáI`~.JŽÛ'šeąÜî1áØä×C˛ķ˙ÉĻ3đŅPĀŊ‡J{ČÖfČÕtĀŊ;åXHĨr-2õmú“Ŧ[´$ëŒ9ã_⎡@ĀÍĄ€å\„yBjՐkávŅĻXôé]ŗhô)/F¸K€[5ôĨ<Ū/čĪČîMŸ_´éIÕéƸĪôHИ V"üv .;ĐsļÎôĀn_++ĻNy䆓VmÃbĨ4'āD¯Įt/TÖÆģ<†ą{dčK<ųz\˜ĪYĐвičm<HĻę\ī€üB”{ŸëI¸˛d˜ģ¸žÅ‡É2Ä=įę\+îÛŊl„|{@ĪρŽ'ę¨>vã„Ío7<˛ƒ!ÅĨSkbī˜Žz6™††ZˇĮtģEfŲĐÛj(œō˜hŨ=˙š5ÃĨí}įŪįɁkffĪÎ÷9/š;Q*v˜élŽUŅb ^žëá‹ÃĀŌb„žaŸØZ„ģ]`-.E˜ú& ą×>ôÄ"3gSéˇXIÔÉ ĀĘb„[ĩ:m>ų&`ú´&69!Áqh˛lĶ›Úáz æį"Üõ|ØļøäŽÅûÔIa3ŧjHvC‹ ųĸá†ßtûdēĄ6aĩ°åAoÂįZ"<úÉ҃TŋĪ(!wīÉx@ēÃp× ¸6ė“(Dø$Q'ÛĨĨ(ŸŦ…Äjđ›xĀÕ ŊXā\đI$`øKCŋį°â$wÃbšb˜Ēúä>¨U úÚ “€ü} fsĢâ“ũ €’Ãđ˛áũļũ! EcE0_ ¸ĒĀĻÍxÎĸVą(ļzÜ đäĢc寤ÖsÎ#Y6ô÷íNrnGĀāĄ1|ú(dâJ#¤ ß||ҧŗč~dH•!9âs‹+wl.]ņIšPZs˜q=Ļc�>ņE‹âEŸDŗÖ–XHŧ ņmÃíĮ†kíúV5Ėx>š‹ĩĮwkšuDķšcœōH,Yæ×BŽöÛ|ōh˙˜&j6}ŠÆbPŦ�„ôļBË)xō•˙v÷üĪŦ9ä÷{Ÿá× /ƒãûœŸí­TįnW‰āđuZąųƒį1ו"´5S%Ņ å­F?.ĶøwÜŗ(w{Œ.:L܉U åo!ŅP\6p:ÔĖ&'38ŽŪ€ž.¸[„thķ™ p=(< q÷ļ¸Ķg. …u›ßÍÁ}zUÚ|×üg¯h9˛!7¯K3w"ôĻũ}ī Éķ“PŠBĖą™ÚSjûĩ~=ûūė9FˆŋxŲîss8€ ›ôˆ7ßshÅQ Y)člŽ8J9' Ŋ÷ׯ9{ë ß ‰ģÍūœļ!~j÷Ęhķ ,Ũp;æ13´Î %h‡áVŪpyÔŖ˙ÃíUsLß^áˆ>–1$:-æ–>׏úˆÎí—“ų-ŠÆZ-žXĩ¨žūy9tÍ|ŋķ’k7Ü+Áđ‹ĮšE%ŧÜBXYļH&×Y,[•fŲ Ä{ĮÛPtBâ@ĪyŅČgmhoŽ,Ģą9‘Á‘JúĖįĻZĄĐå1í˛mŖÃ>wīD˜n¯3Ņ!“ŗˆU }Š:—ÔąķMfšąUÕÖėŪĐĮi ‰o:Ė–ö„AˇĮû U(oú\īˇĪ6ök­ đ,无ŧn¸” Iá3›w˜h5ņ™öšŪÚho­r-í“tm&7WŽĪĩX„ąûw2Š ¯ž¸ŸQđō‰€Ÿ­QÆ ;ĖņŽØš:×sŽdC⎡BĀÄđū2…ĸ!qv7 'ÎF_0¸5 šT Čd–C|°Nį7ī}é„Í'ëđîyt_>7ڛj7L-ÚôˇAĸlņŸÛŽģOƉY4ôU n×+ēžˇÉƒcĐ{>`lÆâō˙9Ļ)ĪgvŅaÜ5Ôw‡LåļÛCâ§,îVŊũį˙āš„ÕĨ÷ŧ ×Y]ˆqC\ ´{ÜčŪ_fš }ąŨëņR6ÂØfHÅķ™<Ō6cY‡ÆĐĄN ËnˇBĨĩšŦ4Žo‘Ÿšyøđa800plĄ“÷›|�� �IDATBĄ@"‘øĶûÁũkmå-DXšPgėmü¤ŗä^ķÉ^üõMRoķy).E˜ęĒë3ųA\×åéͧœ9sæ˜QŪJŠ!Ÿ'yģąE÷v=k0Ŋ †:/o“ĒÅäĻĪ …†hÅ!""ZqˆˆČ/š‚CDD""ĸā‡ˆˆ(8DDDÁ!""ĸā‡ˆˆŧEÁá8;;;-‘“–ÅÎÎŽsü÷ßžÖˇãvuuąąąįyY‘Ėq:::~øŠCDDäVĪž=ŖŊŊh4Ē9Ą,Ģą–xūü9§OŸūa+Ž…†ˆČ Ņhô;?–ĐV•ˆˆŧŲĘDC "" Qpˆˆˆ‚CDD""ĸāQpˆˆˆ‚CDD""ĸā‡ˆˆ(8DDDŪ†āČ-FÍ:Œe#\ÉÚŦx0ûy„,†Ų%‹Úk6y,g~ŧFU,Ļ׎øķĸÃXáuÛō&íųes~ŽUV"LōČ …�Ô6,V^δ†ÜšáŌ€avŅfë6CŽŽøüåN”\ˇOrËbšŨcÂąÉ¯‡d7|žäĘąJ5äZÚ§ŋYÛė\”\GĀå>Ÿō—û닭D˜Ü HļYÜ.ú,]°¸W šėŲüŽ�=Ža;Qįũ‚ŞgąŌj1žũg}ntYŒ-z[ …S­/Úŧl˙­‡J{ČÖfČÕ´Ī“šũíŸéuՉˆV¯ŖP6ô'v'Mˇ#`°õ@Ą5‡[¨lC‹/J€Š~ŸLʧ\0$ēqŸt‡Ą\ƒßÄŽī†€ëÁđôæÁú ŗ_…\öë özģf¨š!Éŗ7z!™é9ĐoÃvģĪ́× ém i9ōä+ ^ļeˇũųDÉ‹Įmn­nŋˆˆV¯Šˇ+dĨh ŗšâ(YäœāPšxˇĮä�ÔĒPk…{„¸Ncfßī ¸y% X˛˜šĄ7]gôe…¸îQõ>}EûũuæĒ†å5‡ôĒĮ\×îĪښuåķ-ŠÆZ-žXĩ¨ņ:̇Wĩ_DDÁqŦØš:×sŽdC⎡BĀÄ0,ī-Ôíq)a|+¤Til?Ōßt˜-úŦ>˛!Rk H¸Gôˆú>ę1|šŗy÷”ėÖŋ˛aj3$á@oÄÚCĘl–ūfˇēžîŠŧÃv{Hü”ÅŨĒ×hKÉyŧ÷&ĒPŪôš>ĢKēČDäd1> Ž-T(H$'ĸՒĄÖŌYĩI/†d́Ž‘&×uyúô)gΜųë¯8Ūĩ-‡ß=I`H%ëēJDDŪĐ¯.8:{ëĖõęċˆ|_ú�EDDÁ!"" Qpˆˆˆ‚CDD""" Qpˆˆˆ‚CDD~ŠÁá8;;;-‘“–ÅÎÎŽsü—ŠŧÖWŽtuuąąąįé{ÁEDN2Įqčččøá+‘7Zq<{öŒöövĸҍFLD䄲ŦÆZâųķįœ>}ú‡­8vvv""'\DŖŅīüXB[U""ōf+ ˆˆ(8DDDÁ!"" Qpˆˆˆ‚CDDDÁ!"" Qpˆˆˆ‚CDD""ĸāyįg;RÕb<gC,„ĒĄ’đ˜é߸šĨ%wЧ˙„įųX.dz8Ô*"ŋâā(Û,Į<ūe(ÄŠĀ3Ė,Úl‚ÂfČÕŸí|„YBbUC_ĒNâËũ¯Yŗhô)/F¸K€[5ôĨ<Ū/Døû­€Ú ÷6ū8kz~.JžÛ§gŨbš= åî•ū< “ ´Cy3d"íŗ<%×í“ܲXn÷˜9g˜8PĻö(Âäf@˛ÍâvŅgéƒđ@?B>û“Mßų€'kmŨ‰M‹åŽ:3Ŋ‡ûü—;û9áØä×C˛é]¤"ōļ=Úū\ēëüSģÅ' c Ų-`ÍáV*ÛĮâ‹TĒāļ\ēPg4vø5�5›[Ÿé!Ÿ›I˜]6€á7Ũ>™Aw7mV÷Û I øŒõ‡” Č ø$+BúÛ!æ„PļÉ×�Rũ>™”Oš`āPÃėW!ׇ}ÆzƒFũGôƒS—|Žuļã>™T@š`]öĀ1]‰¸¯Đ‘_÷ŠŖ˛aáöúÜė0Œ˙ÉaéÄģ=& V…Z+ÄN×ImXÜ[Ž’éŪa&Ŋ˙uę•GqéMKķß{ËÔ ŗ­u˛ũ0ķÄϏ/ęņš˙Qfûˆú÷÷Ãbp-ÂCŖ|°Ī÷SDDÁŽg˜ZˆāžĄrÖŖŋ.e#Œo…”*!×ŌO–[CÚŧž6Ãlvīë•ų\‹Eģop7!“ Ąđ= į0U Ą ō ŖßYnô>ÍŲŧ{Ę|čöôã˜Ī&•õ—i ‰o:Ė–ęŒvę"‘ˇ‹yøđa800plĄBĄ@"‘ĐhŊX=• ĩΐÎĒMz1$›4("r"¸ŽËͧO9sæĖ_Åq’Ôļ~÷ $!•Ŧk@DäWEÁņ=tö֙ëÕ8ˆČ¯“ū@Qpˆˆˆ‚CDD""ĸā‡ˆˆˆ‚CDD""ĸā‡ˆˆüŌŊÖWŽ8ŽÃ×_M$҈‰ˆœPŽëōÎ;īā8ÎŽŽŽ.666đ<ũ˛‘“* Cž?NGĮņŋEN[U""ōF^kÅņėŲ3ÚÛۉFŖ1‘ʲk‰įΟsúôéļâØŲŲQhˆˆœpAFŋķc mU‰ˆČ›­L4""ĸā‡ˆˆ(8DDDÁ!"" ‡ˆˆ(8DDDÁ!"" Qpˆˆˆ‚ã§đ8™Ųc cŲc+æõŪWą˜^ûņ›3ŗ`Sæ?0˙ēozƒļŧ¨iÉfåWpåŒî&‘_ įį<XĪŲ:Ķ˙ÎŨ‰2ÕĩÃG›Xiņ,ļģ<>Zpģo‡›0;Á šW kŗČ<0Ä0Ôē=Ļã†ņ/ qĮP āæP@ŦyœâŠÃd➁su&m‡áĸOîbČŌB„/ūMĀŨĸMąčĶī˛÷mV6-Š Ųs0ĩāPiŲÚ ššöY‹’ë¸ü_ÃŊ͐Ė)ÃäWÔ,nW|ūī+!S ´Cy3dâÜnÖßģfŅ6čS^Œp—�ˇjčKyŧ_ˆđ÷[ĩîmüqdˇũķsQōŨ>=ëËí)×p¯đį‘€\.Â=§QĪģCÛ QrŨ>É-‹åv™s†‰ŊmIûÔE˜Ü HļYÜ.ú,}2ŗhŗu ›!WG|ūrįĮ™=ĸėŪcN86ųõė†GēC7•ˆV?‘d–7 ˇXT0℔ІxŌ§°j fs×õšl7ĘįØôĨ<nĻë\ī É/;Ŧ`¨�î†Åōžon‰…ÄO…Ä]¸÷øˆ'á˙* ¯Í'“�<ô™ņŲ*XsČ'ęL^ôø8nsk \†/x¤[ow;&S>ąjĀ?đ‰Ōß1'„˛MžuOũ�5›[Ÿé!Ÿ›I˜]6€á7Ũ>™Aw7mV÷ļĪ I øŒõ‡” Č ø$+ i;ņšÅüzŖũŠ~ŸLʧ\0p°-5ÃėW!ׇ}ÆzƒFũkˇ*PŲ†8_”Ž9æ‘e÷3Ґˆû ­8~Zų"$‡ BČčG (UĄŗŪ¯8˝įëģŲæĀö‹y¸Éķ“PŠBėeO ˇō†ËŖũßn¯6‚ÃmūtÛ;$!ŽĶ˜ yåWЇ¸îū?™_phĒ3ėf[ëdûaæ‰MíĩF yÜWhiū{o™ZÕæ“ĩ€ÜĪfų¨öˇeûˆúãŨ“PĢB­îŊę˜G–}1ĮāÉWã›PĢč­3 )]Ī:ÜÅPëŽ3}F{ ˙Ũ—!˙2|Ķxo*é3ģč0îj é¤ĪlŪaĸÕPÄgzøÅVOHĒŨ0ĩhĶ߉˛Ež5 ąn3ũ(ä/['$éÚL>>ØČn÷&ĒPŪôš>ĢKû‹—ĸLl\^u˜h Čôs˜Ē†ĐųĮpšY Āõš‹0vßānB&Bá{ `k@˛æ0~ßĐŗR~lQ—[\D8ܖ=†Os6īž˛�ē=.e#Œo…”*!×ŌūĢų:e[Câ›ŗĨ:ŖēŠDN:ķđáÃp``āØB…BD"ĄŅú…Ē” ĩΐÎĒMz1$›4("r$×uyúô)gΜy;Vō×QÛrøŨƒ†T˛Ž‘DÁņ+ĐŲ[gŽWã "?ũ€""ĸā‡ˆˆ(8DDDÁ!"" ‡ˆˆ(8DDDÁ!"" ųĨ{­¯q‡¯ŋūšH$ĸ9Ą\×åwŪÁqœ]]]lllāyúå ""'U†<ūœŽŽã+›ļĒDDäŧ֊ãŲŗg´ˇˇF5b""'”e5ÖΟ?įôéĶ?lÅąŗŗŖĐ9Ⴀ~įĮÚĒ‘7[™hDDDÁ!"" Qpˆˆˆ‚CDD""" Qpˆˆˆ‚CDD""ĸāĮOáq„˙â?:^ŧŪ°œŒ0TŲĸÃXá5ë-9¤ÍîkĪ0ŗā0v'ʙ™c ĶEÃğ–öž¯j1~'ÂøĸÃx6BfÅėĢļV˛ÉÜiŧ?3aŧ°įX‹éĩ×k^aŅaļvøĪgįZč]8fø_ŒÁÁūŊĻ™›"°´dŗō:oxqŧ7čÛžņúÆ!ŗ´ŋķwĸŒŽn{ņąC&g3ąđb\mÆÆFgĸŒĨ5‡ŅlãÜLĄö8B˙į2cK•ŠE&gQŅ=,ōŗs~΃ŊÛf¸U„é,}iO4'šŲņŅ:éšMz!äšcąėYŦ¸ãßøä.†,-DøâBËE‡É2Ä=įęLļėQHf㠇²ĪôH&6Ļlŗķø—Ą( ŸüŸŖŖuRÍZ*j{Bį^)dŦÍ0ūĨ!î Ü<kņw‹đQoČōšájÚįŸ×,Šm†K!ą=Ąų™ë‘Ų´™¯\v-ŽĖū8ę+DH×ę\+6Į`ļÖmĻašŋŋâ‘XsøÃzH‹gąŨåqŗŨæŊŊĮ}/ävŅĻXôé]ŗhô)į"ĖR̆\Kû”#ÜsÜĒáŨ!wewĖ}ŗČ<0ôļBĄ%`ú"ŒÍØôØ^ˇhIÖëܯÉ<\û0dv.JŽ#āō2[†Do#(&ŠĢúRuÜĮ†KW<Ō5Hg-¸â3=Tm2yO>ûŽ}č1ˆEfÎæ˙IÜ֐6€XH,2Ņá“oĻOëF9ąÁë÷Š­XÔēāļį3ÚbY.™éqúíÃOä-ąxâۆۏ “įŋgcēëüSÕæ“…F ôœķ{1ŗ{+-7öŒÎ`"„Ōū*ōË+N@p7,–}héđÉ „$7ŖĖW=Ūmé;ˇ'4€Ü›dr‡Ģ›ˇb¸<xü´Ä}Ž…ŦäĸĖW <°¨$âNHŠh(ĩ8Ž]§¯ 2 ČßoLČSUŸÜĩĒĄ¸ņļÄ6,æ×aú˜į—mzS;\Áü\„ģžn@zĀ'ŗ^g78*Ģ1ŸIā‰Ã<ŌŽáI›áŨs03 ×G=ú=(zĐÖ÷šÃ],ƒ;ģã’ˇHĨęÄ0 š @HÜŗĪÕų?B:˜ží­“î (.8ęN9‘[U�NĀU׿Ö#‹ļž€‹÷E~xppWĮmū{Û3€áVŪpųĸĪXOc%ņrKčqcc~ãõšRŲ°p{}nŽxLøüeqĪV–Đŋm‘ßķ•ôųĮæČm‘äyÉ!#Ã6¸{Ŗø¨¯´¯ŲÜ*BeÕáĶu¨}iŗ´Ø¯~Īá:CF‡ĮŊ™ čä;Ž{āņ`{Ëf|-`rĐįRlŋîųŗyyÎöŖjh,msŨ#ēŨ\™Ôj†Šå˙đĄĮˇĩv#kŸÕ|.ˇ6Ę'0͜6ˇbØzņ�bÃļ´6-"'wÅ0˜ ø_˙üy—{īÉn˜\´)Ÿ˛ØÂ'ÖR~`ŗ4Xˇ™~ō—-ˆ’j7L-ÚôˇAĸl‘o>°öžķ˜>wˁ=‹?,8Äē|nÆ S ÜÖ×3TÎzôŗ;ųŨøÛ€ą;îļ†āįę¤T™JúĖæ&Z E|Ļûļˇ Æķī7&øÂ›øČ3ŨŸ—ĸŒ?ö¸ÔáĶGĐS6¸]ėŽÁßŦ1äڅņŦÃ] ĩî:Ķí‡Ë$]›ÉĮAŖÍŽĪõÖ™œÕÜĒ HÖÆīzļCʏ-jÉæņūûŨžÍįĻZĄĐå1ír¯Û֐—ŗ:‡Û’9 ˇ›[U—z Ÿ-Ú|áYpÖŖ lQéō› !Wa,ëđĪú/Ôų 6™ŦC§k(u{Ė8@ĨylųY™‡†Į* $ –ŧę2bâs›Ëî ߟ^q)ÂTW]Ÿqˆüˆ\×åéͧœ9sæ-ŲĒ’*d"ˇ–~ÆmŖĒÅäĻĪ …†ČĪÎŅȏō”rÚcæįœÄ[fF4î" Zqˆˆˆ‚CDD""ĸā‡ˆˆ(8DDD""ĸā‡ˆˆ(8DDä—îĩžrÄqžūúk"‘ˆFLDä„r]—wŪyĮq~xptuuąąąįyY‘* Cž?NGGĮąå´U%""oäĩVĪž=ŖŊŊh4Ē9Ą,Ģą–xūü9§OŸūa+Ž…†ˆČ Ņhô;?–ĐV•ˆˆŧŲĘDC "" Qpˆˆˆ‚CDD""ĸāQpˆˆˆ‚CDD""ĸā‡ˆˆ(8~†l.Âč‚ÃØB„ŅŦÍRm÷§ŗs-ô.͜ĸÃX(9¤ÍŅe6l2w"Œ/D¸˛hą§z–îGČä2wrÕ=?¨Úŧ7alÁa|!BfŅĸøs Į‹ū|‡™›"†ŲĨũũų^Jßīíŋab&ʕ‡ą‡Ņ9‡lõÍ/Ąą?E˜ųæ;ēû(ÂđÂŪąũ1Žũũ,-ŲŦüˇÎXÎqŽ`ūķķoR•g‘ųßZ-ŧŲšL/*k6ķ•7iī÷¸–Ē6īÍũDSEÍb,kSzÕ}ø8Â™ŲÆŸe#Œ.™#۞ĪļđŪÆĩ”™‹0Q„ĨÅĶ‡ËŽ,E˜øf÷=ã9‡Ėīž†æīD]1fkû᎙l„ąœÃhÖŪŊÎkc˙1ĘDéÕíų%r~Ž•9|Ö^';°{áŋ—ŗø×t�6Ÿš™M›ųZĀe×âĘŦáŖ>ąB„t­ÎĩĸŞgą2[ë6S‹°\‚ß_ņlöĸFČĩ‘:ũŽabÖf…€A€šÍ§eŸėAã†[1 _ w÷™ ÍZ‰đ˛ ‹ņ/ qĮP āæŒĪØôx˛fŅ֐شXîĒ3Ķk˜Zp¨´‡lm†\M‡Üj–Ũ^ˇhIÖšŧåđģô¸†íD÷ Íū´ZŒ/B˙YŸ]cˆŪVCá”ĮLˇÅíĸMąR\3\„üb„ģ¸UC_ĘÃÍEÉuû$ˇ,–Û=fúwû•;P6ņĨEŲņq÷]ŋņ*ĸücļGÉu\>īá~šŋŽÔēÃäzH/åDkØäkÜ7á€{Ģē^}‡ŽÛdz[ĄĐ0}ΐY4$bP$`æü×АŲE›­SPØ š:âŗ0KHŦjčKÕI|š˙5kmƒ>åãķ~!Âßo|Ôf¸ˇđĮ‘€Ø‹ā[q˜,CÜ3pŽNĻj“_Énx¤;-såĶī˛÷mV6-Š Ųs0s ƒ{îŧԊÍÖ–mJŊ>%‡÷îÁGgC–‹†Ņ´Gq.JžÛ'ųmķ<ĮīŨ*Yä[|’ëEˆÕ}^U{rk†K†[ßq͎uķ p`<.ĸÜîÛáf'ĖĪE`ÄŖō`ŸŸÜŲŊŽṒÍ/Úô¤ętb}ēĐsļÎô@crû“MqĀįîžļû@ČĨ”ĮõæšxīÅäˆĪgwlJW|:÷„ā§eŸ?Âō—{ŪãÆg–ū!ā֌Mg"āŖ>¸uā~œ-Câŋ5|ļfQl3\×ȚÃŨŽÆüV\Š0õĪôi˜ÍYôt‡”ÁÔíQpīIŲСwvé øÍĻĄŦ>°I&w¸ēiņw+†Ëƒ‡ßŸL„ô¸ũļEKÜįúPČJ.Ę|›ŋŦĘíč–î;l%ëŧŦĻ ´6'ÔÖ6 ŲÎÎŽ€Ú˛Ežė°â$wÃbŲ āTĀ埭-›Īâ>™spwÁĮ&Ÿ¨“í‡ŌR”OÖ<pŌ>‰˜Åđ:¤CÍ Hžõ> ĩÚnļÛ}n„P ém…–Sđä+‹â`@_d0qŋ€ˇ*ģ8ŧlČxę÷%äî/‚ãˆ˛šŪDÅ'Õē˙i÷VÎេšãqŖVW`ø‚G›ôž:, e¸:ę3HHfÖĻåß$VBF÷~u˙šŗ<Ö}Žv…p> ÁņĮÎįlzS;\5& ģgļ<čMø\K„¸öū×knUB.9Į⋒OOÜŽ€K}Šdŧž}ÅøŧßføMˇOĻjŗV H5›Ú ‰W!žm¸ũØ0y! ĄĐą{Ž–īÃđ`㜤īpíCm<Ŋû¤ú‡ÕƘēå((ųÜl>ĖdB.eŦÃ@jĀgÔ Xĩ(žāi÷K¸>ęŅīAŅ;ŽŊĻą:xŽŽ¸f ŽÃõûŒį-jÆyĮįĪëƒúœđš×•ģÛîųJĀõXķ=â>$OžrßlüyΐGīZ„ņ}m÷î>p(ģ�!§pā’ážį3ڜé*k-ŊõũO�NČģ§,VkŽpmØ'QąŨŠ6Ãģ}å¯Cú΅/,*hkžH´By VÖ#”Īך\Œ0Ķ|P:ØĮwčé ™*/n´’Å_Ú:k6˙X„¸ëđ)P[ŗY hą_L.‡ër÷ļÚÛ˙¤;ŗā@˛ÎôŪ‹žÜj3,*ڃW¯ŒŠąŽ:ŦCōŧĮdTĒs,æØ"üî‘ŗĄeOuæĒ†å5‡ôĒĮܞ§īļæUœĪ;´¤vkĩøbõuˇÂÆxxGÕw_׆=öfõ*!Ž o´ŸQŗ˜Z´(ļÜ8æˇN;Ü*B*éyėōÁ7Ä}æŌPXˇųŨÜøđĀëˆw{L@­ ĩVˆŽ“Ú°¸ˇ%ĶŊÃLz˙ëÔwåÛŦˇō†ËŖũßn¯š7>'ÛørØÖ˛�‹gķ7ö4°vD[^uZj/~^ƒ™7jīŅ×ėqÛ·Æ#æsi+­GĐÖ_ĮõėC}ž÷âēÚSOˆwbĶsÖãæĀŪ‹ččV]ēĐ\=ėŊ|ZáI^Ėđ[Učl?jģĐpīۀ\XniŒÃ›Üąl5ˇ ‹ˆŸļ˜ |ë0ĩnXŨ˛(Ž‡ÚŖāøũuŽæ"Œ.4Rēâ…ü‡á€Âƒ(ņ‘fē›ƒžeüąĮĨÖŸ>‚ž˛Áí‚X{HųÍŌß3)-E¸U ^u_ ųh8ävn¤}~ß!“ŗ  ×FŦ6Ę6ã9 <CÉņų§ačėō™Í;L´ŠøLĶšn÷&ĒPŪôš>SK‡÷U§6Côžĸ?=Ũ!Sy‡íöø)‹ģ%¤k3ų¸yģ>×bÆîÜMȤBXxE›Ž*ģų†'íPī–->ÉŲ|EüBs‹Ā ¸ŪÜfĀ Ge­ņŖŪsĶᓯQŸ3¤’>ķy‡ŠV(tyÜ,Ųd–mmßėīö¸”0žRĒ„\K<YpXn iķBúÚ ŗŲŊ¯ŸẪ Õn˜Z´éoƒDŲ"īÄ7fKuF;eį*`øˆëcwˇĒfĀÄŋ¯3ڜP“sQf˙ŋÆöíô,Ž}�O– ˙ČĻŧiÁyÄ‹qoļ/s&n7ˇĒ^Ũ^˙ĩ¯Ųƒ÷ČXķ3Čž>īpũĩ€Ņ>8ŗō/˙ āîķQãÃP†ÆJôˆûĮ¯wŋ•ŋĸŲÕFxŧ|Hk…ŌˇģÁõb•RŠ’[g‹Ī^y?îöĄˇ Æķīm§æ9Û Šx>“‰ÆũPŧaĻģąÚ^9О_*ķđáÃp``āØB…BD"ĄŋJ ōŗ| č^ķÉîųnöķmÖIŋÍíŪ°I¯d‡Â×~K~!Âʅ:c?Ÿg‘šc¸qā3Ž+‹!N‡ˇĢ~jGĩį-äē.OŸ>åĖ™3¯,ŖŋŽ+ō6Úūe5ˇōM&o¸v!|Ŗ÷Ĩ†|žämJ?A›–ō6}C&éVŸã6“ßüüctd{´â‘_:­8DDäG§ā‡ˆˆ(8DDDÁ!"" Qpˆˆˆ(8DDDÁ!"" y ŊÖˇã:ŽÃ×_M$҈‰ˆœPŽëōÎ;īā8ÎŽŽŽ.666đ<O#+"rB…aČķįĪéčč8ļœļĒDDäŧ֊ãŲŗg´ˇˇF5b""'”e5ÖΟ?įôéĶ?lÅąŗŗŖĐ9Ⴀ~įĮÚĒ‘7[™hDDDÁ!"" Qpˆˆˆ‚CDD""" Qpˆˆˆ‚CDD""ĸā‘_įg;Ōãgž„Kí!`(×n|āĶûWėüôŸ"$ūĄNú˜2Kš_œ÷ˆ¯.¸ßQgaŅaų‚Į¨û=UŗË>îļø›#ÆĢœmáΧ¯Y*éqšÕ0•s(8!x†ZĢĪôP@Ø {–�� �IDATŦYeņąÃäzHÜŗØîŽsŗÍfėK@eŨĸķowøwÅŸ}B.{ôŽ;L­V×āãQŸųe¸9ŧ[¯ˆ(8~r=gëL4ū;{'JŽâĶVvøÃzH‹gąŨåqŗËbtҐˆA‘€™TČ­‡J{ČÖfČÕ´Īōl„øhtÍ&Ŋ’Ŋ`1|úĪúd\›EˆÕ }Š:—6Ô.<ÔŽųš(šnŸä–ÅrģĮL¯a<kĶ’Ø*rk†K†[ eŠøüqĻ,h‡ōfČÄHĀgkÅ6ÃĨ„ÅøCo+ZĻ/BfÆĻ3đQĻööqh7ō‹6=Š:ë֑ãÕOČĨ”Įõ=ŋg%w'BåÂ3Í (ZlÁË >˙ØpéŠGēéŦW|ĻG€ĒM&īņq‡Ío—}˛PrH¯˛=Ļ{Ą˛æ0Ūå1ƒŪļŸ|0}Z7Žˆ‚ãgōä+‡ņM(•-ÜŗunÆ S˙Éĸ’ˆ;!ĨĸĄÔ[ô&|Ž%BÜĩųDl?”–ĸ|˛æ“<ĸîívŸ›01 ×G=ú=(z†[wÔ.¤ķā›=HõûŒr÷ŽĮĻĐÛ8fŽlŗúb1Pp^ļĨRC;rŲ&īųŧÛŌw.duŅĻ7ĩÃõĖĪE¸ëų¸NĀĩaŸDÉŪßĮ— 1ĖWŽĮ€õŖÆ V1ÜÍ;›oē”ōYŨ IíéT"ėëŪĨ~øģĪîb‘ÜŲ œŧE*U'VĩĄĩ¨­!l ąŌųôQČĕfŊŊÅe§CŨ9" ŽŸkÅáqs�Š"Œ;!1ÔčG (UĄŗæŌPXˇųŨÜ8\kīNøĩæļíŲĒ5'áZÃõŲ˛×iÔĮ1_C˙˛ Pķ f[A2ķÄĻ´7�-ĐĐéīīã‡/ļė  ūĘņj´õ⊃vÃŊ ŋXqŦ5²ßiÔ9ĩōF=ąČĖڔē}:kŸÕ|ūÜÚ¸ Üj3,*ڛÁŗæPė­“xqœVāE9Qpüœī~aļģÎĩ !ãY‡ģjÍũ÷Ėrc§­# Ūíņū‚ÃDʛ>ׁ ˜\´)Ÿ˛ØÂß�™ķ0ąāpģšUu°ūés¯ŅĀnŸD6ÂD5`{kw"§Û#•0^†>ĶįČ9LUChkl Ũė‚ņŧÅ“>ķy‡ŠV(tyL;†Ü‹`ûæ@÷´?†Ą ģ“õņJŅÜáá:Ģ 2nˆ‹vŨģu^ę1|ļhķ…gÁY¯že‹J—ßXí¸>ŋÉYP…k#`( ‰ŗ{Tewe""ŋZæáÇáĀĀĀą… …‰DBŖõ3Č/DXšPgė-üē¸aĒĢŽĪ8DN0×uyúô)gΜyeũuܡLjČįIŪĻôļ5Ŧj1šésCĄ!ōĢįhŪļ¸˜Nŋ…íj ˜Ņé­8DDDÁ!"" Qpˆˆˆ‚CDD""" Qpˆˆˆ‚CDD""ōË÷Z_9â8_ũ5‘HD#&"rBšŽË;īŧƒã8?<8ēē稨ØĀķ<ŦˆČ †!Ο?§ŖŖãØrÚĒ‘7ōZ+ŽgĪžŅŪŪN4Ո‰ˆœP–ÕXK<ūœĶ§O˙°ĮÎΎBCDä„ ‚€h4úKhĢJDDŪleĸ!‡ˆˆ(8DDDÁ!"" Qpˆˆˆ(8DDDÁ!"" Qpˆˆˆ‚CDD"""ũā0dsFÆ"Œfm–jģ?kĄwá˜æÆ @É!ŊhŽ.ŗa“ša|!•E‹=Õŗt?B&įšãĢîųAÕæŊéc c ŖsæĢPYŗ™¯>Äüįæéeq)JlÆĄpːå `‘ųÜĻôšŖ÷Ēöŧē~ÃėŌū1xs41åJs,ÆîD_û /‰įô85‹ąŦMéq„3ŗÍķ”0ļbžģū’Ãđįšę‹1ŲķŖ5‡ŅŦÃx6ÂTj%›ŅšÆĩ3ēhQĢXdrÍ"‡8?ׁJ>k¯“ØŊŠßËYük:€ ›Ī\ĖĻÍ|-ā˛kqeÖđĮQŸX!BēVįZŅbŲŗX„­u›ŠEX.Áī¯x 6{Q#äÚH~×01kŗBĀ @ÍæĶ˛Oöƒ <+†á‹ánãâ>Ķ#Ac‚^‰đÛ5HV-ō->Éu‡‰"Äj†žT˜cČŪˇYŲ´(&<fûÃ}īÔWĶŨSE˜IĀĖl„øhtÍ&Ŋ2Ũk“_Énđ­Å­û†­uCOĒΘc“y`čm…BKĀôEČĖØt&ū‡Sö´ōß“‚Mâßns­ė0Y†¸gā\LuˇūܚáŌ€áւCĨ=dk3äjÚįÉ\”\ˇOrËbšŨcfoœ€ßxqĢØŧˇhA"dfŅfë6CŽŽøÄV"LnôŸ˛˜]÷™ër¸•Øaú´aęs›w?ôØ^rČy!ۛ=ę\Útø]z\ÃvĸÎû…æ9í0LũĢ!ƒ"3Cnŗ9ųE›žTÎu‹žŗuĻ›×OîN”ŠŽ.¯Eøm9äߝķHŦE¸į¸UÃģCą/-ʎOĨđbL<Ōķt{ Ž}č1ˆEfÎĻ<0‘öéu ãŗ6…!‰ļŸ|0}Z…Č_eÅņ¤lčKėųƒÎ€ßlJ@îM2ésõ<Üzœd2Ōs. ß†–¸Īõ!;,îîy$t;ú]Xēī°•lN~�U ĩ99yā›6ã9‡ąš(ŋ]÷šyn7fž„ë#Ķé:í€Ã>“#>[…ũõT;ŦžõMúīÛG>­&ēqŋ1š™‹ĶpwōË6Ŋ)ëCÃë6w=p€kÃ>˙ĻųūáAk]†–ķ;Üė„–XHüTH܅{ÍūúÖō‰:“=>ŽÛü˙íŨ_h[įēįņīģÖ+k1Į›#ƒBÆ`qÆ>Ä Rĸ€Ąē0DԓfSoˆö•Uj_ÕŊŠ{ŗíÍĀąa .ØÆŪd@_(āĄ 1Ȥ娐€fĸSŠĮų ‚,­?s!9ūĮiŌv'}>ˆ­Ĩ÷]īŗ–Ūßz—„<ŗŅCŧĪ!w(ļÁLļy%~-ĢødЅ ÍL*ģÂā~I1÷Øc4á0Úį<qĨ`2ąŽ`WᡠWTėÖ5Ë#vŅævôĀ1mĀŽ ҈Ãd|?4@ąPqIO:`uü(Ū‹Ú¤ģ!ōh?ĄšÁÂ&ÄŖ‘n‡›ŨGjņčĀ#d+jҊIę+…‡> u)>V2KņkGOØcŊxp bđ]‡GWÍdĻ•uÍį›PûÖ$øÍŊ‰ėx[ÖÁuŌÁĮmÅlÆĮzwƒéčßĀĒļ&€Š‚īpƒ“ ›éAlY‡ÕZ“X­Öœhž÷ožÍķåĒAûļÉØŠÂÚ6ųr Ŧã8vÛČ?/'�ūÛ�ĩgš‹Ķ ˜É)nŧīîiū|6/CsÅ1’°™˛éŨ…P ĩ(ëļ™´lpëüáú×�‹ũšVœÖÚÆm&LöyDúĖ_q nk’KöĩËa>iĶ‹ÁĮķæÛ|Š :aš"ÄB­ąXÍ[Žc.×ðû’ DP­Û„ŠĸöPĪ ŠŗŋĢscCŗ`7ĪĒBüjˇĒēúÜĘú^ōÛ㏠—ÂJĄĄ:ŗŨ­+Á|clŽ||ū ô”V‚å“ü?ŧ¸BŪĮLÕ%ąŽ[÷ø(áq'ˇ“Ÿ„}¤˛TadČ;šN‡Û6Ō…:­ 6uƗ4wönUŊ¨ķĸÉb¸Î׉VÛŊšk+_tÃIJIųœÁ<Bۚš’sŦ‰xĖa!§™ @!l3­؃8Šģū>˜Z6ˆöØÄ;SË&}í)ä´{¸ũn›–4ãU(o;ŒĀzū,g†Įd’YE&as=ãclĮŖTņI: _T|ļ¤éą5"ōˇ&ŗeXˇáēå0ŪŲŦy°ÚŦ]lÃĮÔļGDCôĀ1Ŋīƒ/ūĩyĢĒŊĶ=Ae <yŦۆZMA´Átũ÷ˆ.ąšfėĸg×ŖüČ ÔĮķUfŗ& †ģšíŪ€tFķ}Wü­ŖIg ē,E1čđ΍XŠ !ö/é>|čõ÷÷ŸēQĄP ‰HĩÄqļIōŽGæĻûŗ4Ÿ[ōąvĨA:øË­˜÷1nČ{â7Ş,ž>}ʅ ~ũ[UBüņA‡'šŗúėŠLl;ܖĐâø )xŊ3Č!sķįŧüq™Nū ã ¸ĖÉáBVB!$8„BHp!„āB!Á!„B‚C!„āB!Á!„B‚C!ÄģZkęõēTK!Ūå@0 ęõ:ZŸūĨ"gúʑp8ĖÖÖļmKe…âĻĩĻŗŗķõWB!Ä+­8ž˙ū{:::hkk“Š !Ä;Ę0šk‰~øķįĪŋۊŖ^¯Kh!Ä;Îu]ÚÚÚ^úļ„ÜĒBņj+)B !„B!$8„BHp!„āB!$8„BHp!„āB!Á!„B‚C!„‡Bņë‡"“õ1ŧ¤I/ųΘäkûÎÍû‰.˛;EMē�”4Éeuō6[&Šģ>Ɩ|Ü\68Đ<ų>RYMęŽ&[=đ@ÕäÚ´tkŋnfLŠgM)ī#U<ûاžŌä)ßFpVS8噺KÍũYøĘĮÂYģĢLoœmĶŊöķy“ĩW<žãŗmÜ\ŌÍÚŨõ1ļņ3ž>{Į˙45ƒtƤtā˜Ž-ųH-g8Ļéņ1ûĖ =|~•64ÃÍXÆĮTąyÎ$f÷Γ5ĶM^ūΙøĐŋTGĨo4_v4ČôīũBs-kđuŌ…-“/-›ÔļÉBÍå†epsNņŰC°ā#Yk0R4Xĩ Ö`gĶdjVKđÉM›Ö(jxŒ 5čŗãs&k¸ �ÔL>/;d>t›ÁŗĻHŧīíī\ČazČ 3ßFļęqī˙î˙ Kķqz,Ån¤Á'Ā“5“Š‚AÎvøķËz^“ĩ=vˇ zŽ4Ž™ü~Uq5 šáęŅ0yė2Ũm0U„Ų´1Š3}^1õ•ÉՄĮĸIąčЧ™&kÛňÍ\Ø ĩĸˆ āw™~Rŗ&]—.*î•<ļÁÜĻbˇd˛Ú]įëˆAjU ( įlfģįíG7 ÚĘË>qąĒŠŪ¸•m#ÛíÛ1Xí°™í;P7íōɐŨŦqÅäÚ˛Ųe“sPØö¸5ä\ķ1ąíŌwÎ`nĶaíŠAĸč}ß#ŋäãū••5ÚôđÛģa›O´y¨ŪZĮŋS1õĩ"„".ŗƒ.VkwrË&=ņ]˜‡ŽiiÍĮžq™ļ}üžėņ—l’UÍDBļ‚K R5“\ ūã˙3Émzdļl’Íãt'#ŋŗĀ 5oR‰+v,v vü<Ō1¸™WĖ<¯„ĮëyRVôFüĸËåŊmE ČŽ˜Äbˇ.ÃĖÚÉ̉XÄŖį’KŸ ūÃč Í§‹•ũmŦN—> ō4;ąÖ„P­tƒí#}l›Œe5cYÍũîÁãũīÖ5Ë#vŅæv´9ĄŧwÉa4Ņāƒm“õšÉÄē‚]…ß‚\Qąú­ÉÕA›ô€CôHDWiÖ/: ĮŠL*' ēĶĨˇŨ!lH 8L 9ėšU“hÜftĐ&ąi˛hƒĨ]F}­§GŖ—=J›˙=āå xøĪy<ylP<Ø~+`g*̓“1˜[U`CŧĪ!w(ŽÔÍ6˜É6¯Ä¯eŸ ē°Ą™Š@eBÜ/)æ{Œ&Fû\‚/XŊĖŦTP =JEÅŗ#õ~~ü°cC4â0ß P,T\’'tĐvŠ• ü(Ū‹Ú¤ģÁôķYpī‘"Ōíépøī˙É%rZĄŅZėāŅ€GČV”;mæ˙‹ÍÄ Í­]ÍgĪ€.‡PŅ (sАĮ›ĶöšËüŊdÉāģ—ŽšÉО4Ÿĩ “ü€‹ßܛœŽˇeÜ냏ۊŲ% ąĶ]~�ĢĒ�* :Üà v8L&ÜCŲŅū#} æĢŠÕ MrŨæĪŪáũ�hwtąl(O6_|›įËUƒöÉØ XÛ_n9Ü`˙jĩâ}NĢ?ûäš�ā˙Ņ[7YũĐĄ Čå4ūxtĀāūúá[y/vJŋÚe$a3`+Ō˙Ë$�Ęęļ™č‡ZjÅԁcĩ×įŪ„ŋkĢįũ ÚāRēîĄz·÷RĀa> …M“įáöīĸ­šV€ĐIĢŨĸA0܀š‡eĩ‚*§¸1lĶ÷Lqg]ZÍ œŠĸöU Öq‰�ÁsPsŽn'„ĮŅÕ×āVÖĮđ’G¨ØL¸VÚ ՙín]ŨåÛ{ds=āãķo §Ŧ°Âėđ(¯˜ä˙áÅ}ō>fĒ.‰uÍØēĮG ;¸tø$ė#•5  #C/ģā’8Ō˙ZŪĮÔļGDC4 Įf]ËaŧŗŲG°Ēč7žė2“Õė†9ü>FŅd1\įëDk?z5×V F/{”ŋ5™-Ãē ×ņˆY&\Gē‹ĮršŠ�Â6ĶZ‘=˛ÍÜ]ëæv !‡‘nŠœfˇÃ#tÎ`ąd?o?ŪÃHĐGúÂچT܃ĨŗœE“qHf™„ÍõŒąRÅc$é0|QņŲ’ĻĮRÔp Ķ%˛b2ũĮw;ÂcäŠĮXFŗˆĸÖŨāVõpŊ÷Ž˙}|ņ¯Í[Uíî đĸ(īMÜe“ąŦļĸ¤ū9ûo2yÄ;SË&}í)äjû+ŌĐļfŽÔ`¸Ģší­Hg4AŅwĨAĐ2XĖøX z”jãû+“2§ˆß�õđáC¯ŋŋ˙ԍ …‘ˆ\G‰×d›$īzdnē?Kķš%kW¤ƒŋÂØJšä†CFŪão9˲xúô).\xá6ōq\ņΈ:<ə”~ųë/ĻWáĶ ņÛ Ĩâ—;Û27ÎK%—éä¯10tR>‹+~;dÅ!„B‚C!„‡B !„B!$8„B !„B!$8„Bŧ­ÁĄĩĻ^¯Kĩ„â]à^¯Ŗõé_*rϝ ‡ÃlmmaÛōĩ Bņ.ĶZĶŲŲųú+!„â•Vß˙=´ĩĩIńâeÍĩÄ?üĀųķį_oÅQ¯×%4„âįē.mmm/}[BnU !„xĩ•‰”@!„‡B !„B!$8„BHp!„B!$8„BHp!„āB!Á!„B‚C!„økŽė˛áŒ&ņq3c˛fÃÜW>2(æōĩ3îr:Ģ˙ĒčãÚuâÖųŧÉ0ģdR<đķ1EMēđ’Žm“äü’UMŽMûHg›cÎĢŗĸ¤I.̟ūøkîá¯L*G‡žo#8Ģ)ŧ´æНLJ¯ØkaY3WƒĘ†ÉBåUŽņ̜GŽÉ’nūËT^§ž5ƒtĻ9æ“ÎßįįŪ#æ|Œe5ŠySĪ^T_ÅtF“—ŋ‰&Ūbú—ꨲæcęœMvĐkžˇ ֞ĪŠė†âzŋbnŲdįļ=n 9|wˇlˇClĮ`ĩÃf\›ä6=2[6ÉÎãîĩeø(ęąēĄ¸õĄÍ“ ƒön¸S4)ĸíÅŦ{ÚÅĒ*ŽÚXkĢļÁZÄc5wx‚k>>ÛvšÚnPÄ9ÜgČa:á6'å9ƒŌ€Ãîšfĸ ![ÁĨaÅԒĻlA‡/z[sRÉ$ĩę19äqįČã;›&S˰Z‚Onڔ—īoė‘Úqų¨]qoÛå‹!›5éŊė˛ģiā5Hkƒąo!­(ā29xŌ‘QL=v™î6˜*Âlfį|„†$k&É%éč^Í]øŅ`æbgSŅopcGķ§Mŋm°ļ™ė0ƒ¤Ã_6 ŠíŠŋ¯äüąMÍx‚5EoŧAbķpŊRÕũūö΋™%MĨÃcgÛãVŌáÉüáķbļĪ;|L†ÜįE+ėˇLR+Šh� ~—É IZ7˜ ™\›Sü1mc-û¸wšÁh°ų”ܲIOŧuęųÛÔsąÁd?đL“xĸ=īP_t næķī{2 YqœzÕYVôEö_(V§Ë@āČFš™ Tv!„Áũ`CŧĪ!w(‘°K$䍧CĒßa$h°¸wuÛéŌÛėo y´ŸƒPÍ`abžK.}ÅŖû ˜{ėņiÂ!}É%x´Ãm“ąŦ&=¯ŲŊØ|ÜôķYpī‘ĸVĐä" & žˆšX@mËä÷+0‘t žđ¸?ä0:hķigsG÷īu;¤lŽn›ŦX.É~‡ŅK°¸ šUÍŠ `mŦžp•[y¤Yŋč0s(>8ž×ÜrIŊo3Ũ‹›Š™ƒ ´GЍ(=UĢ×/yüM+¨fŋ…Ņ!›édƒDāxŊŽãf}&Ūˇų4d2ŗqüŧ8¤lî¯8ÖöË­šDã6Ŗƒ6‰M“{6 *EE, ÷ˇ÷*׃ûĄēPqIĪvū>yŦËjnū_EŦĮ{q}ģBEƒĸĖ?BV§‹†=֊ ēZWl%ƒŦvmęļ™č‡Zj¸‡‡Ĩ›öá[SËÅv—ÛūP•upDö‹ogŒm¸d?t)Ø&̧îƒb괁u8L&šãX˜oc.Ú ˜SÜļé{ϏŗŽö÷¨Ų͉lĮöHhƒ\Åaø„ĮãßOÚ_īđ6�&øŒ=vŲfĸ*Ujƒš#Ģ/W ÚC&c+`m|šå2÷Û8v›čXÃ6q T…ŽĒqĻcPkõ_ĢÁė‘zÍ ΋W/hÂrˆŲšŠMøĮ+wž5ŠFÔ§„€öœŋÉC+ģšâĀ õ/šÂsX<ĄžŖĄ˛™ƒ„Į‹/5Íú¸™ņiE—ņ‡'ín›ëc;ĨŠĮHŌ9ŪPĀ#´­™Ģ4Ũ›ŠæK'™˜e2ņČ%p‰Õ4c=ģåGĩ˜GyÅ$?ds={x†{Ÿ-ibŠÚŅ[Ue“ąŦ(Š–ÃdĀŖĐĄ˜Z6ék‡HŲ ˇ‰g}Œ•Ą„Ãt/„ē\F¯x¤îjz>´‰/~ü˙p|˙-üōšĮcs9Íx@QÄa:qdƒĸÉb¸Î׉֕t¯æÚŠÁŨ0ąlR>g°ƒŗ_ķ’sŦŽ#W<Æ2šEĩîĶ']4ĀXÎāīÎ5Ÿ“ē ãKš;­[UņŖõŌîáūēm>XԌWĄŧí0:�ëųW?ã1‡…œf*�…°Í´†r‡Áĩ›‰ ĮbĄ ëē}h|Ae ō‚ķ—Ŧ8ļ[QģhyA}G“E<nČü#ŪRęáÇ^˙鷙 "š6ŋ=š%kW¤ƒo°Ņ’&šá‘÷8Ä_!˲xúô).\xá6ōq\!N[Ĩ :<ÉŊú'ÉNšVcz>Đo/-%â´Ë/—éä›lĐ#”Ī⊷›Ŧ8„BHp!„āB!Á!„B‚C!„‡B!Á!„B‚C!„‡Bˆˇ58´ÖÔëuŠ–Bŧˁ`Ôëu´>ũKEÎô•#áp˜­­-l[ž*A!ŪeZk:;;_Å!„BŧԊãûīŋ§ŖŖƒļļ6ИBŧŖ Ŗš–øá‡8ūüë­8ęõ焆Bŧã\×Ĩ­­íĨoKČ­*!„¯ļ2‘!„āB!Á!„B‚C!„‡B !„B‚C!„‡B !„B!$8„BHp!„ŋzp<ōqaÎGzI“^Ԍ• “…Ę›iū…m•4ÉeõFúČg}LmŊ|ģšy?ŅĨũŌæ2~ŽŨmŽ;5īcŧ}\{pÖũRL}ĨÉüՆä7Ī›ŦŗK&Eöū)ŠyéâĢ=§°Ŧ™ĢņƎåZŪĮøŗÃ5K/iļ�Û õ?ü öë2>ÛÆÍÖ6ÃķšL֖ÛH?ƒü˛é-y‘ ņĻé_˛ŗž‹ ĻûLT r~˛mdģb;Ģ6ŗ!ƒÔĒ"PÎŲĖ(Rŗ&Ŋ—]v7 üąim’ZQQÔēmF+ÍļbEÍDBļ‚K &ü'Lvßø˜Øv‰ĩÜ):äã‰{ĐwŅ!ĨM&>?d0–1ņG\vƊP*hū´éᡠvÃ6“—ŧũÆˇLž´lRÛ& 5—€Įõ¸Íhgķņk+ô߯LÖĮ=íbUWm“߯*ކ!÷#\2>r.=Û ÂđdàŊîMŠE‡č†Aû€CyŲĮ"Íļzã6|üĶŽËGíŠ{Û._ šäŽôwl—ļš5Ž āw™~R˙ͤ/æÂρ˙˛ÍwÅvÅß×ZõßÔLlzD1(GLļkŽ-ÃGQÕ Å­mvs>æđVŊņÃÁVU“ĪË€ÕoÔlīā“+6Ŧš”ĸ]�Úå“!› ōM(§­íâ_Ū5)Ũlm+„xû‚ãÉcérs’íšd“Ü{†xŸÃ0‹wD<ĸđŸƒ' ŠX.É~‡HĐ ą }›&Ŋņ:é +@ëj×ôU!´Ģ¸ķH1qųøÕûÜcŅa‡ž Üi]aīv8Lö{”ŠGž_5)Ddú [6YG1ŗbP‰¸„´GЍ(]ōžOLŲ“XŦέmƒ˙ŧϏ1ĐīâŠĻÜ ‘Oã.ė˜Į%ē’@��ęIDATę y´× ¸e°° Á‚ÉÕÁ:é âģĮ&TMfm‡ėû.ĩG‹ĩÖ;]zÛ!Ü f2SqČ|čBI“XU|ĐŽx¯Û!Õ ĩ9ë¸Įú;šU“hŧÎhæ},ÚX.7ú"H| #Ŋ—<ūæ›fmgŋ…[Ãx¤æLJ€ŋĶ!ÕīÛncĄą*Xa—ëŊ.ņāÁUŖ?ÚĀj§ÅœĻØüëq‡{ëÍļ­r*9Lv5W!3YÍ_€šļšŨĩōŪŲír]û¸g; kyą ņnŦ8žíũĪÃŌÍ�Á†\Nã×I î¯ÔđĀ˙ AÃnëĮšÍķÉf&§¸1lĶ÷Lqg}˙VPá‘fĻņ˜ķüyĩ[§?˙0áA›8PǞ5[3™)BČŌ|Ô6LōÍ?ˆrũĘáĢgvŽ4Y5ÛpÉ~čR°MVßxõ[5~cũũöߥ~mH&ġ î­ļ‘ęŽ3m•¤ ]ûû{pÅQÛđ‘Xn6–ÉÜžévI4WĪowø(�OĒ@P^ėBŧ•Áqæ€éö˜Ęiv;<Bį Kîąmâ1‡še͘Ĩ¨ElF[“MŧC1ĩlŌב˛AŽŪÜ>zÉfúRĢũÅįY“Ģį Ā94š{ū‡HÖĮxÕewBxŒ\ņËhQÔēĪÛ-Ŧ˜„†ęĖvˇ‚1߯Ø#9Ë .ąšfėĸg×ŖüČ s™ÉjvÃP�8¤´Ô˛ĸˇĒ°ÂûûŗL&šÄ,‡‘ ô…ĩ Џ×jāôūū-|ŧÆ 9ÍT� a›i­ČØ y(o*ŽĮ=ĸ0–3øģsÍũ‰ÁgY“û„Ž4č:vŠ)æ2šÕ€GģíŅÛ~ ŧPúņäōĖ­Āøm0ÜZÄæÛ˜Ģ6^ZÖrĩBˆ7G=|øĐëīī?uŖBĄ@$yg]))j]]U“ä˛G&éʙp&Ã_)Ļįü<đU“›ËNē­äk˛ Rwˇå=!Î~‡Ā˛xúô).\xģV?ˇÚŽæãŠxŦ!gĘ_‹€Ã§!Ī\&Îŋ~sųœIī`CBCˆ7ė7]ŅķQ9ø¯Îeîw?o}“>pö“ 6Ŋ÷!„xS÷„B !„B!$8„BHp!„āB!$8„BHp!„āBņv‡ÖšzŊ.ÕBˆw9 ƒzŊŽÖ§ŠČ™žr$ŗĩĩ…mÛRY!„x‡i­éėė|ũā°,‹ķįĪKE…BČ{B!$8„BHp!„āB!Á!„B‚C!„āBņĶũčãN‚>!a����IENDŽB`‚��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/plugin-register.png�������������������������������������������������0000664�0000000�0000000�00000064417�14156463140�0022514�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��#��Y���5}���sBITÛáOā���tEXtSoftware�mate-screenshotȖđJ�� �IDATxœėŨwXįÚđg{Ĩ÷"HEąaETėŊbėQ“˜žœôœ|9ŠĮ“fL1Å{oąW늨bCDEzīlßųūX\WĘ‚ģŒ‹äū]\WfgŪyßgf–p;3;Ëųāƒ����XĀĩu����Í’����[ø†˙(Šōōr­VkÛj����žR|>_.—K$’GfQqqąģģûŦYŗĒ-���€z)ŠģwīnŲ˛%77×ÁÁÁ8Ÿ×ŠS'WW×>øĀŨŨ] ذD���€§”@ pssëÕĢWbbbyyš1Sq+++ĮŽËápl[���ĀĶŽËåŽ=瞞ōá­VlÚ����šāā`ĶßųG(Ú° ���€fC,3 ÃáT=ŪOy����` ’����[´����؂¤���Ā$-����ļ i���°I ���€-HZ����láÛē����€fåt™<Ų0sZ����lAŌ���` ’����[´����؂¤���Ā$-����ļ i���°I ���€-HZ���đÅ0ĖÖ­[O:UsŅšsįÖŦYŖĶéŦÂIĢUXÛ1&[Ķ ÉzŦĘ_zõĀđŦėlVK��€ēėÜšķŌĨKûöíĢļvíڕ””´aÃ+‡h„¤õų—˙ oß1JŠTZß���Ā“Ņļm[@@DĻaëüųķģví"".—Ûž}{+‡°6iŠ5šÍ[ˇq8œ˛ōōŨ{öYŲ���ĀĮįķéAØJLLüë¯ŋ†árš&Lˇrk“ÖŪ}û‹ŠŠŸ™2™ˆÖmØheo����ORĩ°ĩsįNCĖ7nœÅ1ËWū0_Y›´ÖŽÛ@D3gLëŌšĶųÄ ˇSRĒ58rô؈ŅãÂÚFvîÚķ÷>,--}Ü5]ž|eŪ /uŒęŪ.ēO˙×ß|;==ÃLûW^30$ŧ´´ôŊ˙ŨĨ[¯ÖÆL˜|ųō…Bņɧ_tëĶĻ}§q§\ŊvŨt­ŒŒĖˇŪy¯[¯˜đvŖē?7ī…Ë—¯4ŧōŲsį5ÎŅjĩ!áS§ĪĒĩČüüü>ūO¯˜Øđvēö˜ûü‚ËWūŽwW���€•‚‚‚âââx<1 Ãápƍ×ļm[‹;ôôļ0ÍˇĻ˛ģwī=—ĐąCd@K˙ąŖG%œO\ŋaĶûīžmlp>ņšų/¸ēēŧŧāgįŗįž›˙"—ËmxƒšūžzmŌ3ĶfNŸææęz?-måę5ĮOœÜŋį/''ĮZW1\‚}ņå×ētî´|éo7o&Ŋ˙ŅĮ/žüZXXHpĢVŋũōSzFÆ;ī}8kÎŧ“Įgfe7QĄT>79¤UĢ뜜UkÖNŠ{våŠĨ]:w˛Ŧr3 ĮŒŸ\ZZ7eRHHpVVöĒÕk'M™ēbŲī]ŖēXÖ'���4PYY™^¯7L3 S^^nUw;GaIКČʤĩvũ"š0n, 6ä˙>ũ|ËļíoŊųēP 04øņį%zŊ~ÉO‹Ûˇ‹ ĸIĮôņÎ'{¨ˇAM—¯\ nôūģowëe˜ãéáņņ>Ûų׎iĪ>Së*|ŸˆZúûŋŧā"jŪúHüąŨ{öļoņö[oQDÛ6 ‰Ë˙\yåīĢ:v ĸož[TPXøķâEƒö7t2h`˙ÁÃF}ņÕ˙ļl\kYåf|÷ũâ뜜ÍÖJĐŖG4tÄį_ūwû\“��`Ņå˗ˇoßn¸hČápt:Ũž={ˆ¨[ˇnö¨7Æ6+ŽĒÕęÍ[ˇ‰DĸĄC‘\&2h`QQņūũŖœ=—ā×ĸ…!‹Lž4Ū´ ķ j55nĘÎm› 1KŖŅ¨TĒV­‚ˆ(=ÃÜD"4p€q: Ĩ?õīßĪ8'0°%åæåÃ0ruq8 ÖØ UPPĮ‘—._.**ļŦōē0 ŗ{īŪ°Đ/OĪŧŧ|π/čØĄÃßW¯UTTZÖ-���Ôëʕ+ÛļmĶëõ†{ŗ&Ožl¸Œ¸gĪž3gÎXÖ§ļ,/O[5mų9­=ûö9ÂN.7Ė?nĖÖí;ÖoÜ4|Ø"ĘÍËSŠT~~žĻk§ëmP—­ÛvŦ߸éÆÍ¤˛˛2ãL­ļžg‹yzē§ ;ŅĶÃÃ8GĀ‘VŖ%ĸŧŧü˛ōōļmÛp8ĶÎ'^¸{īžˇˇ—e•×Ē   ¨¨¸¨¨¸kĪŪ5—ffeˇjeYĪ���`Æß˙mˆYgėØą†{ŗ&Mš´~ũzkÎlŨÉPrų<ŌY“´ ÷Âwíu/5Õ0ĮĶĶĶÕÅåäŠĶ÷ĶŌüZ´P(”D$ŠL׉žŦˇA­~ũíĪK~‹hÛæÃ÷ßiáë+ o%ß~÷ũë-Øđąķs *•D$•HĒÍ‹EDTYаŦōē”WTQëÖa˙zãĩšK=ÜŨkÎ���ëĨ¤¤čt:‡3f˘ˆˆĒëTĄĄĄ'N\ŋ~Ŋ^¯ĪĪΎ Û–áá-=ī_JŧZ ĩ0iŨšs÷\Ây"Ē5âlظųÍ×_5ä•ZeēČôZXŊ jRŠTK—˙éåéšfå ™Lj˜izfĢQȤ2"ĒT(ĒͯŦT‘\.ĩ r"Ōh4ĩΗËd†‰˜ŪŅÕ ���–5j”^¯ Ŧö„Ō°°° &¤¤¤ >܂n…DĕúĩČ8‘laŌ2Ü ?i¸čč^ĻķU*Õ[oŋˇqķ–W_^āæę*ŌŌš}ęfR’qēŪ5åååĢTLjˆļƘEDgĪ%Xļuqssupp¸’bø¨§q~rJ HĨŌz+įķD¤ŅjsŌęx…ĢĢĢ““cJʝŌŌR{{ãĮBŠ °ĐÅŲš16���ja¸hXëĸđđpk[*qö°ŗčŽxÃŊđBāÍ7^:xéΘQ#ˆÍËË?r4žĪįwė™z˙žésĄVŽZcœŽˇAMŽŽ.DdúôŦë7nlŲļjœa˛Ōāũķōō<d:ĐåËWztīfooߐĘŨŨ܈(%åŽqΖ­ÛënčāÁjĩú×ߗį>zÎŧįes���ā‰Ëd\KÎiíŲˇŋ¸¸düØ1ĩžq™ūėÔŊûŦÛ°q@˙ØyĪÍ>{.aÎÜį'Œëäčx6!AĄPī 'ĸzT/Y,î×7æđ‘ø÷?ú¸[TTōíÛŽZķŨ×˙}nū‹GŽÄīøkW˙~}ĨRi]Ģ7ÜĢ/ŋtčHüëoŊ3cÚÔĀ€€ôŒŒ•ĢÖČdŌ<-ŦŪĘĮŽĩzíēO?˙ōŨˇß’HÄžxé’éŠ8S¯ŧüâ‘Ŗņ?ũōknn^ר.9šškÖŽ/..ž1íYëˇ���l+āō-9§Uõ\øéĩ‡€ŽQ]BC‚㏝ČĘÎîĶ{Ҏ_ģēēüąlŒßūpqvųyņ÷rš\ŖŽēcŠŪ5}õÅg#‡ÛˇīĀ}œxáâoŋüØ'Ļ÷K/>_ZVöéį_5Ö3<<ܡm^?t𠍛ˇŧũŪ+W­éŪŊëÖMëÃÂBXy‡Čö ŋü\ŠTÍznūÜų ŠŠ‹_ō“L&SĢÕ5‡suqŲēiũ3S&Ÿ<uú÷?\ōÛ­[‡m\ģĒWĪ˛9���đÄiUZ-gΜ9ŋũö›­K���h&æÍ›įíí­*J:{!ĶĒgÄ���@5į/]ąKNQ¨ĩdåˇņ����@5*•š/¨zĘĻå߯����æ!i���°I ���€-HZ����lAŌ���` ’����[´����؂¤���Ā$-����ļ i���°߯���Иēwéčååm˜Æ9-����ļ i���°I ���€-HZ����lAŌ���` ’����[´����؂¤���Ā>Ã0ļŽ��� ų`†aô†iœĶ���` ŸˆîĻeŲē ���€fˆOD-ŧl]���@3„̇����lAŌ���` ’����[´����؂¤���Ā$-����ļ i���°I ���€-üōōr[×����Đ|”———””Ļųļ­��� 9qpppqq1Lãę!����[´����؂¤���Ā$-����ļ i���°…oŲjJ•:5#[ĨÖčtúÆ-ˆ=β%KW–WT2 cëZ� Yáp8v2és3§ļlácëZ� ią$i)Uꤔûvv2{;9ûtœģr#y؊U !c@ãcĻ´Ŧü›~ũ×ĢĪûz{Úē�hB,ÉIŠŲvv2šTō´Ä,":pđb�°ˆÃaũ’eĢl]�4-–D%ĨR-•ˆŊVUVVÚē�hî8œ’Ō2[�M‹%IKĪ0\§ŅK�xÚá6P�¨æŠšü���đÔAŌ���` ’����[´����؂¤���Ā$-����ļ i���°I ���€-~ÃtC,ųmééŗįj]4mę”~}bØÚĪMk×&ĖøR¯×_Ŋqs÷#JĨƚžŋüø#ĮOī;ouĩûüŖˇ×mŪĶSŖÕ.ūušé"7×Ūzų•ë.ũ}ũąúœ0zXpPĀį_/nĖBŸ8Ķ=_ķ(ö[×ÎX:î6׸ąoãF|ˇ[ßÛŋz��fđË*ØúššaCõęŲŨ0ũÛ+|}Ŋ‡ `xéåÕ¤ŋ5ŋ pÍÆm†i>ŸßÂ×{@ŸhoOÅŋ­°ĻÛ-;÷ffį˜oŨ#Ęß×gՆ­Ûš—§L*INšĶĶŌ-gqŲO†™=oÜo];w`é¸×Ĩ‰ī4SĻĨ6ämÜÔ<5@ŗÁˇ“IYęÚ×ĮĮ×ĮĮ0- ėۄˇfiŦÆĨRŠ“īÜ3žŧqëviYų3FļôģsīžÅŨžKŧTo?oË: JËČT(•–­n%‹Ë~2ĖėyĶũÆŌq¯KßiĻLKmČÛ¸Šyk€fƒÅ̇f|öÕBĄ@øÖë¯į,úņį’’Ōß{ûų¯ 6(+;įʕŋ•*U›đđ™ĶŸĩˇ“‘N§Ûąk÷Ųŗį  ˆ}b— īĨĻ‘Ŗƒƒá%—ËĶ12ÂŲÉą¸¸äđņS'N'9ØÛM7*¤U@ĨByäø)‰XÜ>"üŗ˙ũ@^ đ1¸ŋˇ——ÃÍČĘŪąį@ĘŨÔWæĪjؒˆēvîđåw?eeįÖ5Ę˙~gߥøÖ!­BZŧûÉWJĨ*4$čfōnÎÂOŪßw8ŪÃŨ­më‘PxãVƚÛ**+ õĮÜ*@ŠPž8“`ē–\&3|PhpT*)..‰?y6ūä"jxŲĻĖėÃj>˙číũ‡yy¸ĩįr¸§97aTP€ŋJĨŪĩ˙đŲķ͔Gf/™ßo ?îĩP3ĢTÛi™ŲÆAy<ŪĐ}Ŗ:EJ%âôĖėmģöŨMMŖ]­Ö˜yÖuÍlÂįŊŊīPŧ““C§öb‘čöŨ{k6m/++¯VęKsgwĻ™}nĒŽcĘĖ{ŌÔן~°{˙‘CĮN^ƍåãíĩpŅ/Ôā_Ŋē6Ķt”ĄúСÚĐ{Ų}āHÍ­�0Ī6I+&ē×Ëū,*.vrt$"ĨRuõÚõ)“&ĪŨŊw˙”IfMŸ–“›ŗđëEk×m˜÷Ü,"Zŋis|ü‰iSãZ^ģqcÍē </&ē×(ØŨͅˆŠŠ‹ /GÔŗ[įõ[vŪšw?,8hܨĄ:­îtÂ"š2~”¯ˇ×’åkĘĘĘG éīáæĻÕiĢõ&æĪœzūŌ•ĩ›ˇsˆĶģg׿LûāĶ…K–¯~iîĖŧü‚ÛvU*c†ŽkN×ŗ[įĢדö<ĒVkø<^P€˙#Į¸9:Ŋn@ŸčÍ;÷ŦŲ¸ÍÍÕyÁs3ƍōįÚÍDôėäqîŽ.ŋüą˛¤ŦŦwŽ‘mŒíĻNãáîēlõ†Ō˛ō �˙)ãF_švŗáe›2ŗĢWĢĶÅÆô\ˇeĮÚÍ;zví<yÜȐ € [wŨģŋfØ ØIc†_švCĄPÖUž™ũPī~kāq¯ë€*ĘēVŠļĶL3|p§ČļļîĘ/(ŒéŲõÅ9ĶŋøfqAQqĩƒnfš9ˆfÖŌétũûôÚĩīĐŋwco'ķĨyCú÷Ų°õ/3Ĩ6dŸ›Ų9åēߓ Ԑ_=3›iÚÆ¨LÃb�XĖ6Ÿ=ėŌš“X,:ķā~ųËWŽ0 uęlxéįį×ĢGw.—ãåéŲˇOôų •J•BĄ8|$~đā={tķđpī×'ĻG÷îģöėcŠBî (ĀĖđ!™Ų9†S b‘(ēGÔĄŖ'Î%^Ę/(<q&á\âĨ}{‘\ŧīP|RrJfvÎō5åĩ]œurr‹E .įäægįæmÚžûį?Vjĩ:ĨRĨ×ëĩZmEeĨH(Ŧk"bF­ÖlßŊ˙Ūũ4Ŋ^ØŌCt÷qŽpĨefKŧÄ0Ln^Á‰3 ‘m„ƒŊ]hĢĀGŽßJ𛓛ŋqÛ.Ķ›Á7íØŊø×å)wSķō Î$\ČČĘ iED /ÛČĖ>ŦŊڌŦk7nQâĨŋ‰čnjÚŊûiD”xņŠ@ đps5Sž5÷›eĮŊŽjfĶÆ0Œą�‘HØŖk§=Ž^ŧr5-#síæ7nŨvuuĄGēP ¨Ģg3ąŪŨž“›wæüEŊ^_\Rz=)ŲĪ×ĮLŠ ÜįuíœZŽrmīIķGШŋzf6ŗšŨŽėy­ŗ�Āļ9§% ģEE<}vČ Dt>ņbĮ‘RIÕ˙[úų[úøxk4šââââ’R­V×6<ܸ¨uhČąã'”JĨX,nÜō|ŧ=ŋ˙ōcãK†aŽ'%¯Ũ´Ũ¸”ĪãŨ¸uÛØ 9ån¨NBĄĐÍՅÃáīéQŠÔ7“S<=ÜĒõŸ›W“—?#nÂņĶįnÜēž‘uÛäö zGQĢÕDdøķoŌęöŨT­Ž–ŋ^uIKĪ4Ngeį ø|{gG"JMK7.JMK÷õņĒÚĩz`ßčā �šLÆåp¤RI^~Áã–]o3­V+ 3ĩ:FŖ!ĸÜŧ|ÃĨJED9šŧ”ˆÅ ,¯šjûÍâã^×õ đoČŪ0ååá!āķSĶ2 /u:Ũ+×—ē™b<ŨŨ¨ŽƒXīŅÉČzxÛxĨB!•HĖī†ėķ†ŧÛ j}OÖ{ øĢgĐĀÍ4Ļ+Ä,�°†m’õŽîy$ūØũ´t÷ËW¯ŊüÂ<ã"ąHdœ6üŅ­PTîYūęßpˆcX¤g"*))mô¤•›W°bíFÃttŽmÂBVŦŨdŧØ!‹ˆčåųŗčÁŋī9ŲÛÉeR)=øķoPķF"bæģŸ~īß'ēGTį‘C—ėÜ{0áÂeĶ6fFÉ/($"ĨÉÍīĄÁ._5LëõzCKS\.—ˆt:ŊqŽĘä/Ŋá¯ŦT"‰DD¤Ö<ŧæĸRŠ=ŧ8gËŨ´}wN^žN¯Ÿ7#ŽæĻÕ[vŊÍÜ\_˜=Í0ķėų‹†Īģiĩ\Ō<ú’8 -¯ĶũFV÷ü‚ÂZh÷†)ŠTLŽHMƃnĻg3ąŪz šÖ¨Æûč Üį yˇWÕYÛ{Ō\&øĢgĐđÍDÆ�ëŲ,i´ô÷÷kq.áŧŋŋŸ\* oũđc‰Ļ 3ü“I¤ZŽˆæÎžåëûČ'ļœŊ6Fs˙Á?¯ˇîÜ:zØ ãš Ãĩ˜?×nĘĖzäsãÅÅ%n.ÎDdzÉC&­ũŸËå•ÛvíÛļkŸ§ģ[ŋ˜žĶ&ËÎÉKËxøoz3ŖTëJ*‘´đņ^ģi‡ągoOjm\]œˆ¨¤´Ė8Į4Îū�W*‰˜ˆ$⇋$ūÔĩôķõņōüîį?Œ÷2Ëe˛‚Âĸj5°l3Í***ŋũéwÃ˲ōGîS6Ŗå™ĒļßȊãNuІDŖōōJzpDĖ0ĶŗáX×z-¨ĮŒ†īķzßíĩž'Ģĩyôę% üޞxC~õ��ž0[>#>ēWĪsįĪ%$öčŅ•Ë}øīʤ[ˇŒĶ÷Rī E"gggŋ>>ŋ´ŦĖÛËËđ#—Éíėė ž“Ã2• Ŏ=ģwéāo˜“‘™­ÕjårYN^žá§ĸ˛˛ŦĸBĢĶåæ‘ŋŸ¯ĄĨH$ ĒŲ§‹“cDxÕC2ŗsķÖmŪĄ×ëŊ<Ũ s §ˌR­ˇĐāŠJEzf–áåõ¤dw7Ķq9ÎĀ~Ŋ‹KJM˙ļ>MfāįëŖVĢ‹ŠK Wå|ŊĢ.ršÜā �ô€Ī'ĸŠO_ké×ÂÅŲ‰čáQ{Ŧ˛Í4S(•wîŨ7üäå×~â§ĻzËĢŠÚ~ĢæąŽ{]´ŪŊQķėcn^žZŖ1‡ķĘüYQ"Ģ53Ķŗ™ƒØđ7UM5Kmā>7˙n7Uë{˛ZĨJ%19ŅåíUõŠūę�<yļLZŨģE_¸x)ēGwĶųÅÅ%[wėĖÍÍģtųĘĄ#ņŨĸ: …‰DĶģ×ļ;ĪžKČÍËŋ‘tkáˇß˙žtų¨ķôšÄ´ŒĖ)ãFņx<"RĒT'Īž6°_Įöm]œƒ[.xnÆ´I㈨ °(-#sPŋ˜–~-<Ü\§M_ZVËY''Į9Ķ&÷ëŨÃŨÍÅÍÕep˙> ÃnÁŠT(|}ŧ|ŧ=š<n]ŖTtëöÃįœ=ņŪũô9Ķ&Đ7"<´{—Žo,xÎĪ×gÃÖĻˇ3;ØÛ Đ×ÅŲŠMXHt¨ÄKkĩÚĸâ’ģŠiûõ ōņöœ2~”îÁßāŒŦlVĶĢ›Ŋ<,$hâ˜a7nŨöps•Ëd”mfZÆ|yĩĒļßjjøq¯ë€šßLãN“šœ}QĒT§Ī]Ô/ĻKĮö-|ŧ'á×ÂįÎŊęĪD0Ķŗ™ƒhņn¯ĩÔîs3īöjj}OVks?=ŗ]ÛÖRŠ„Įã čm¸hH ūÕ�xōlvõˆdRiXh¨RŠôđxä¸Ŋ{÷ŦŦ¨üäķ/5jMdûvĪL™d˜?eŌŠTēaĶ–â’R{‡‘ãĮŽ~u2 ŗaë_o,˜;°oôžƒG‰hËÎŊ†đÛÛÉKËĘ˙ž~sįžƒ†ÆËVo|fÂčWæĪ,)-Ûw(ŪĪ×Į¯EõO6ŨžsoՆ­ąŊ{Ģ×éŗrr[ąÖpįoü‰3Ķό{ũ…9ŋ˙šÎĖ(Ļ‚ƒö>f|Š×ëX˛lPlLûˆđØ>ŊtZŨÔûßũüGĩŋm§Ī%J$’ˇ^ž'Ž^ŋšqÛ.Ãüåk6ÆM=oÆ3 ĨĘđŲ´öáDT^Qšjũ֑Ctíy?=sÕú­vŗž™ôōü™ŸŊ؂˛ØŦĖ—Wë*Õö[M ?îf¨™Í4ŨiĻwŠoÛĩa˜1ÉÄĸĖŦėŸ˙X™_PË%93=×uͯe†iŠģĪÍėœjęzO>ēÕ{ĻNķŸ÷Ū¨T(O=.ņRëŸvlȝ�Ā“Į™;wî’%Kk‹×’}<k˙PĪc)--{ëŨfΘÕĨ“qæ‚Wߨ?väđĄÖ÷oę?_}͏ÖE đy<ã­f/͝QQŠXējũ“Ŋđ5pĐÔX˙žl:ŋz?ü÷“'?(�4)ķæÍķöŽē­Ü6į´ĘË+˛ss׎ßčãíÕšSG›ÔĀ’ųŗĻÚËek7ī(+/oÛ:4¤Uā/KWŲē(€æŋz�Đ4Ų&i8yjĶÖm!ÁÁŗgN3ŊžXžzÃØ‘Cž›>E(æåŦ\ŋåÚÍ[õ¯�ÖÁ¯�4Mļŧzø$=ąĢ‡�đ‡Ģ‡�`zõЖŸ=���hې´����؂¤���Ā$-����ļ i���°Å’¤ÅápôÕžč��ˆj|?$�üĶY’´$bayyeŖ—Â*ŠDBH‡�Ā*†q°ˇˇu�Đ´X’´ü}<Ë+e•:ŊžŅ bIlŋžÔŧ‘ �M—3oÖT[�M‹%Έ‹„aA~ŠŲyE:ŨĶļÜ\]ŪX0ī×eĢËĘËm] �4CvrŲŧYĪúzyÚē�hZ,ü6ąHč׸Ĩ<Ÿô/[—����˙ øė!����[´����؂¤���Ā$-����ļ i���°I ���€-HZ����lAŌ���` ’����[´����؂¤���Ā$-����ļ i���°I ���€-HZ����lá[ļšRĨNÍČVŠ5:žq ���°9Ą€/‹|Ŋ܅ ҁ%++Uꤔûvv2{;9ûtŸËČÎëĐ&ØÖU���@ĶĸÖh ŠJnŪN kåoMØ˛$'ĨfdÛŲÉäRÉĶŗ����j%đŊÜ]Ü\Ķŗr­éĮ’¨¤TĒĨą5Ŗ���4}.N Ĩƚ,IZz†ár8֌ ���Đô |ĩFkM¸ü���Ā$-����ļ i���°I ���€-HZ����lAŌ���` ’����[´����ØÂnŌú~ņĪ3æĖ?—h:ŗ¤¸dƜų7’ną:ôĶkÃļŋ>ûząaúíŋØ{(Ūļõ���€ÅX?§Åår6lÚŦRĢŲ��� Ša=iEļoWQYšwīļ���hz+[ �� �IDATjøl ‘HF ļuûÎččîÎNÎ5””–Žß°ųú¤ŠĘ gg§Øž}öīgXôŌëo28#++ņÂEŊ^ßģWĪ!ƒ.[ą*9ųļH$3zxtĪD¤ĶévėÚ}öėų‚ÂBgg§Abûõ‰iô Ņëõ{MŧüwaQ‰“Ŗ}ßčŅŨŖjۜ˛ĩ›ļßē}G"÷îĄT*/]ŊūÁ›/›éš¤´lÍÆmˇRîJÄĸ^ŨēTW§ÛŧcĪš —4m될¸ ŖeRicn���°†õ¤Ĩ×ëûĮö;zėÄú[ŸŸ;ģfƒĨË˙ĖĘΙ?w廙ũ­äÛËWŽruqîØ!’ˆø\ŪŪũŸ7ãŲgŽ;žbåšIˇž›øü–í;VŽ^Û12R&“Žß´9>ūÄ´Šq­‚¯Ũ¸ąfŨŨĢq7dë_ûNž=?yėˆ�ŋ¤ä”M;vķyŧîQĒ5[ŗi[FföÜĪØŲÉvî=˜“›Īį×ŗ“˙\ˇ97/˙ųYSíí펝:{éīëĻYęt…öm[ŋ8gZ~AŅÚMÛ×oŲ9k椯Ũ4���` ûŸ=dˆĪįMš8ūėš„ääÛ5—ĮMšøæk¯„…{zzôŽîŲĸE‹Ģ׎—úĩđíĐ>‚Ãát‹ęBD­Z^Ē՚ėėl…BqøHüāÁzöčæááŪ¯OLîŨwíŲ׸ĄTĒŽŸ>×?ĻgT§H7Wį^ŨģDuŠÜäxĩfeeå7’nęäãå9#nBEEĨųž‹KJoŨž3°oīVžînF ‹DĻ ėíėÆæįëĶą}ÛčQ—¯ŪPk4ģu���Ā’'ô”‡í#"ÚļYĩnƒ^ĪT[$‰:üÁĮ˙yåˇ_~ũ_ééĻéÄĶĶĶ0!‘HˆČËËëÁK1U*Š÷ĶĩZ]Ûđpã*­CCrsķ”Je#֟ž™ĨĶéÂBZįĩĖ/(TŠšĶ?7ŋ€a˜Ā–~ÆM 4ßsNnųûų^r8œ–-|Mø§üũôz}~AĄ›���OëW&O˙áŋ˙süäÉȈãL­VˇđģEŒN7eĸ——ÃûūĮŸŠOđH…ÕŽÄ1 ŖP*‰čĢ˙}Ã!ŽaĻžaˆ¨¤¤T,7VņJ•Šˆ-YÆy0Į0JiYš›čáÍg• "™œ”Ē÷ž*ĨJMDĀ8G$š60dĘĒEBŠņAN��€§Ä“KZ>^^ą}c6oŨŅ:4Ô8ķÎŨ;ééīūëÍЧ‹JËĘÜ\]Ū­T"!ĸšŗgųúz›ÎwvvjŒĒĢHÄb"š>eœ÷ƒslNŽöĻ/|>Š5“PĨBažgĄP@D ÅÃ3p•ŠGÎÆ™æ*ÃÃ2„ÂGĸ���4YOôņŖGŽĐëu{ö=|âƒFŖ%"šŧęÄĪ픔üü†Ē_a4€Ī/-+ķöō2üČer;;;ĶŗDÖķņōäķxeå™L"—KĢcssu!ĸÔ´ ÃKĨJu39Å|ĪnŽD”‘™mxŠĶé’īÜ5mr7Õ8š–ÁįņÜ\jų'���4AܲúnŲnD2™tˍ‘ĮNœ4Îiáë+<R\\|õÚõ•kÖˇmž•SRZÚĀ>%ILī^Ûvė<{.!7/˙FŌ­…ß~˙ûŌå[šX,ęŲ­ķŽũ‡/\ūģ °(9åîâ_WŦ\ˇÅ°ôøŠsßūô;šē8ųúxí;75-'7˙Īĩ›ííäÆNŒÍL9;9ļôką˙Čąˇn§gf­ŨŧƒĪã™6((,Ú{(>ŋ č歔§"ÛĩiÜ ���ėyrW úÆD9Ÿž‘ixioo7{Æ´M[ˇ:sĻĨŋ˙s3§˙ŧä˙ūīģĪ>ų¨}N™4A*•nØ´Ĩ¸¤ÔÁŪĄCdÄøąŖŊōą#†HÄâmģö—”–ŲÛÉ#ڄ<°¨°¸ønjšazfÜÄÕˇ.úeЃŊŨ Ø˜ûévÆS\ĻÍLÍxfšÛ~]ļZ,÷ęŪ%ĒcûKWĢ>}Š×écûÅ-\ô‹FĢm<qôđFß4���` gîÜšK–,yŦu.^KöņtcŠ ',#;¯C›āFėP­Ņč´:ãmė?,Y&•Jf?;š‡���€'æâĩäĮ ķæÍķöŽē}üIŸĶjö~YēĒŦŦ|ō¸‘övōĢ7’nĨܝ?sĒ­‹���Û@Ōjd3ã&lŪšį÷?×ĒÔ7įŠĮ´ibëĸ���Ā6´™|FÜ[W���MÂ}Ę���Ā? ’����[´����؂¤���Ā$-����ļX’´8ŽžyŒ¯&���xŠ5ZĄĀĒ5X’´$bayų“ûļD����›((*‘ˆEÖô`IŌō÷ņ,¯T”UTęôzkÆ���hšÔmv^anAą¯—ģ5ũXrBL,†ųĨfdįétO}Øēx-ŲÖ%���@Ķ"đ%bQëVūV^=´peąHčgÍĀ����Í>{���Ā$-����ļ i���°I ���€-HZ����lAŌ���` ’����[´����؂¤���Ā$-����ļ i���°I ���€-HZ����lAŌ���` ’����[ø–­ĻTŠS3˛UjN§o܂����lN(āKÄ"_/wĄĀ°d`ÉĘJ•:)垝ĖŪNÎãâŦؓ“‘×ĄM°­Ģ���hūÔmAQÉÍÛŠa­ü­ [–ä¤ÔŒl;;™\*AĖ��€fI(ā{šģ¸š8ĻgåZĶ%QIŠTK%bkF���hú\œJ•5=X’´ô ÃåpŦ��� é øjÖšpų���€-HZ����lAŌ���` ’����[´����؂¤���Ā$-����ļ i���°ÅĒ/MŦ×÷‹žxé˛aZ(¸ē¸ļm>h@Ŧ‹ŗŗąÍ‚Wߨ?vä𡖠aåę5Ģ}aŪsQ]:g–—ŧōæÛoŋõzëĐë‡h~]žæīë7 Ķ>ßŲŲ1<4¸ot'G‡FåíŋčŨcplL#ö ��đ䱛´ˆČŨŨmæôg‰HĨTĻĨeğ8qüÄÉW_ZŌĘĐ`ōÄņžŪ>l—Ņ@\.gÃĻÍíÛGˆ„B[×Ōtšē8ōMDjĩ&=3ëÔšÄĶ æĪœā_īēĮNMMËxvŌXöË��°=Ö¯ŠDĸÖĄ!­CC"Ûˇ1|ȧčëëķãĪŋ(” Cƒ^=ēˇléĮv Ųž]EeåŪŊl]H“&Š‚ƒ‚ƒÚ´ķŪë ŧ==~˙s­RU˙7CĨĨg> ��šÖĪiU#‹gL›úū‡˙wōÔŲūũúĐŖ—˙’nŨŪŧu[Zzē^Īøĩđ7ftXh0=ŋāĩáÃeeį\šōˇRĨj>súŗövōj—”–Žß°ųú¤ŠĘ gg§Øž}öīGDŸ}ĩP(žõú+Ɩ‹~üš¤¤ôÃ÷ŪŽÖƒD"5|ØÖí;ŖŖģ;;9S u AD/Ŋūֈ!ƒ3˛˛/\ÔëõŊ{õ2xā˛Ģ’“o‹Dâ1Ŗ‡G÷ėAD:nĮŽŨgĪž/(,tvv4 ļ_ŸÆŋFĻ×ë÷<šxųīÂĸ'GûžŅ=ĸģGÕļ9ek7mŋuûŽD"îŨCŠT^ēzũƒ7_~ŦąD"á”ņŖ>ûßgĪ_ŠéŲ•ˆĘĘ+ļūĩ÷Öí;• 'GûŪ=ēõéՍˆž˙eéí;÷ˆč\âĨˇ_}ŪÁŪžÖfU› ĶmŪąį܅KļuHP܄Ņ2ŠÔLįD”r7uįۃYŲz=ãëí9bp˙V-ž7���Ũ“NZDäãååéᑔtː´Œ”JÕw‹~ėÕyú´gˆa>úí÷?|ŗđK™LĘãswīŨ?eŌ„Yͧåäæ,üzŅÚuæ=7ĢZĪK—˙™•3îlGû[ɡ—¯\åęâÜąCdLt¯?–ũYT\ėäčhčęĩëS&M¨Y›^¯īÛīčąë7n}~îėš ę‚ˆø\ŪŪũŸ7ãŲgŽ;žbåšIˇž›øü–í;VŽ^Û12R&“Žß´9>ūÄ´Šq­‚¯Ũ¸ąfŨŨĢ‘ön•­í;yöüäą#üũ’’S6íØÍįņēGuĒÖlÍĻm™Ųsg<cg'Ûš÷`Nn>ŸoÉ[ÂĶŨÍÍÕåöģ†¤ĩzÃ֜ŧŧqėíėRîĨŽŨ´ŨŲÉĄ]›ÖsgÄũ°dš›Ģ˄Ņäņ¯Ë×ÔÚĖĐįé„ íÛļ~qδü‚ĸĩ›ļ¯ß˛sÖÔIf:WĢÕŋ,]Õ)2bō¸‘Ä0ņ§ÎūôĮĘO?xS*‘4po���4:$-"rvv*.-Š6ŗ ¨PĄTôčåãåEDĪL™ÕĨ3ŸĪ3,õķķëÕŖ;yyzöíŊã¯ŨĶ•*ąXdÚCܤ‰.×Ũ͕ˆ<==ŋzízĮ‘]:wZŊvũ™ŗį† HD—¯\aęÕš–Ęâķy“&Ž˙ū‡û÷ nUmy]CTŲ¡Cû"ęÕeÅĘ5­Z^îükOvv×á#ņÆîŲŖyx¸ßKMÛĩg_ã&-ĨRuüôš}ŖŖ:E‘›ĢķũŒĖũGŽWËeeå7’nO5,,$ˆˆfÄMøčŗ¯ė-ÔŲŅĄ´ŦÜ0=näP.—ãâėDDîn.ĮNŊqëvģ6­%b1—Ëåķyr™ÔL3C'övvãG #"?_ŸôĖŦCņ'ՍP ¨k­ÂâĨJÕĨc{Ow7"š0jX§ö|>ŋ{��€ ü˛ŠĘ'?Ē^¯įqyÕfzyxxzx,ųmiß>1mÃÃũũ[„™|ܯĨßÃ{š||ŧ5Mqqą§§‡ib‘č¯ŊûnÜL*++gĻĸĸÂĶŨˆDBaˇ¨¨“§Ī’Öųċ;DJ%ŌēĘëĐ>"ĸm›Uë6üûũwĢ-ĒkOOOÄD"!"//¯/ÅDTŠP¤ŪO×jumÃÃĢ´ 9vü„RŠ‹Åõíļ†JĪĖŌéta!cbpPËĶįU*ĩHôđN˙Üü†aÜ$'‰Bƒŗsķ-T¯×ķ¸Uˇũ‰DÂGŽŨē}ˇŧĸ‚a˜ŠJ…ģĢKÍUĖ73Ŋŋ>ĀßO¯?ž_PčíéQ×ZîŽ.în.+ÖnŠîäëíe¸tx;í^Cö���lsN+;;ˇMxXĩ™\.÷ŊˇßÜŊwüņ›ļlsqv;fTĪîUˇāˆEO_>XĄx$#jĩē…ß-btú¸)Ŋŧ<xŪ÷?ūl\Ú;ēᑸc÷ĶŌ=<Ü/_Ŋöō ķĖW8yŌø˙ũŸã'OFFD4p"â ŲŸÕŽÄ1 ŖP*‰čĢ˙}Ã!ŽaĻžaˆ¨¤¤´“–áÎôEK–qĖ1ŒRZVî&zxķYEĨ‚ˆD&;Öp#”erķ BƒƒˆH§ĶũøÛ Ŋ^?~ÔPw7.—ûëōÕ5Û×ÛĖO DBŠÕj3kqšÜ×^˜sđȉ“gĪīØsĀÉŅaø Ø¨N‘ Ü���lā‹…‚'<ä­ääâ’âļmÂk.˛ˇˇ›<qÜä‰ã223÷í?øÛËŊŊŧZú‘!Ŗ(J"’=zRęÎŨ;ééīūëMãķ#JËĘÜ\] Ķ-ũũũZœK8īīī'—ĘÂ[ˇ6_¤—WlߘÍ[w´ mā !•HˆhîėYžžŪĻķŪIŊ$b1MŸ2ÎûÁ96'ĮGŽ ø|"RkÔÆ9• …e#ĻÜM-)-3\…ŧw?=3;įÕįgOJ•—W¸ÔØĀz›ŠÕ SŠÕD$ ͯ%—ÉF4zø ėœÜCĮN­\ŋÅĶÃŊ{��€ Oúņ•+V­suuéÜšcĩEšyų<æÔĮÛ{ÚÔg¸\NFfÕC’nŨ2ļŧ—zO(9;?rBBŖŅ‘\^ŋn§¤äį0ÄD÷ęyî|⹄Ä=ēršĒĪč‘#ôzŨž}ŸøPīõōká#āķKËĘŧŊŧ ?r™ÜÎÎN hĖŧëãåÉįņĘĘ+<Ü] ?2™D.—V;ĮææęBDŠi†—J•ęfrŠÃU*ëˇėtvrėĐŽ-iĩZ"’I%†ĨwSĶ ŠŠ™;ŠŪf)wSĶŠi|ĪÍÅŲĖZ…EWŽU=RÕĶÃ}ōØ'+;§{��€ Ŧ˙ąQŠT7’n‘NĢMKO?pđ¨JĨ|ãĩ—5ūÎ.ū闉ãÆļoÁ!Îéŗį8nĢĀ@ÃŌâ⒭;vöėÖ-3+ëБønQ…žkáë+<2zä°ôŒĖ[ļĩmž•SRZę`oODŨģEmØ´š  đķOūŨĘe2é˜Q#×ŦÛĐđ!ę%‘Hbz÷Úļc§\PPX¸fŨg'Į×^^АÕH,õėÖy×ūÃr™Ôŋ…oaQņæ{ėįΚJDĮO;éĘk/ĖquqōõņÚw(ŪĶŨM*‘lßŊßôÁÆf5ûWŠUÉ)w‰HĢĶefå=qZĨVŋ8g:ŸĮ#"oO>Ÿôę!úffįėØs ,$(7/ŋŦŧÂN.“JÅéYé™YŽŽöfšQAaŅŪCņ#Ûåž8ŲŽ@ 0ĶyQqÉīŽ5l`ÛÖĄâ$\ŧĖápüũĖī ���Vąž´rsķžZø qšG§ˆˆ6#† vuŠåūč°ĐŲ3§īŨpëö\.ĪÛĮëĨæīyīŨģgeEå'ŸŠQk"Ûˇ{fʤjĢÛÛÛ͞1mĶÖm§Îœiéī˙ÜĖi…EÅ?/ųãŋ˙ûîŗO>""™TĒT*=<ÜĢ]‡ž1ŅGŽÆ§gd6pˆ†˜2i‚T*Ũ°iKqIЃŊC‡ČˆņcG7pŨ†;bˆD,ŪļkIi™Ŋ<ĸMØČÁ ‹ ‹‹īĻĻĻgÆM\Ŋqëĸ_–:ØÛ ŠšŸng<ÅeÚŦšü‚ĸEK–‡Ãq°ˇ  cü6šL6uâ˜{œKŧä×ÂįŲIc‹KJ—­Ú°hɲ÷ßXЧg÷ë6}ûãīsĻM1ĶL¯ĶĮö‹)(,Z¸čVÛ&,xâčáõv>uâ˜ÃĮNíÚw˜Įåzz¸Īįîæb~o���°Š3sæĖĨK—>Ö:¯%ûxēąTP­åË KKËŪz÷ƒŲ3Ļ™~­áĶ%#;¯C›āFėP­Ņč´:ãŊį?,Y&•Jf?;š‡���xĒ]ŧ–ü¸|į͛įí]uCö?âV•ōōŠėÜÜĩë7úx{uîTũū°˛_–Ž*++Ÿ<n¤Ŋüꍤ[)wįĪÄ55��€FķHZ'NžÚ´u[Hpđė™Ķr/ü?Įˏ ›wîųũĪĩ*ĩÆÍÅyęÄ1mZ‡Ôŋ���4ĖĶ‘´÷ĩ5Ģ4`đ ܗS ;;ųŒ¸Zž•���œ~Ę���Ā?’����[´����؂¤���Ā$-����ļX’´8Žžæ÷Ø���4/jV(°ęA –$-‰XX^^iͨ����M_AQ‰D,˛ĻK’–ŋgyĨĸŦĸR§×[36���@ͤÖhŗķ s Š}ŊúuÉĩ˛ä„˜X$ ōKÍČÎ+(Ōéļž¨‹×’m]��@ķ'đ%bQëVūV^=´peąHčgÍĀ����Í÷đžŋl]���@ķÄFÚē���€æ‰Û#Ô×Ö5����4Oxr)����[´����؂¤���Ā.‘ÆÖ5����4OÜĖķGm]���@ķÄ=›%ļu ����͡Ut[×����Đ<q=5…ļŽ��� yâÆ?aë����š'îøącl]���@ķ„įi���°I ���€-|ËVĶjĩ………jĩZĢÕ6nA����6Įįķ…BĄŗŗ3ŸoaXĒęĮ‚u´ZmffĻ\.—Éd<Κá���š N§T*333ŊŊŊ­ [–\=,,,”ËåRŠ1 ���š%'“ɤRiaĄUĪÃ˛$i)•J‰Dbͨ����MŸD"QĢÕÖô`IŌŌëõĮšQ���š>gå-éøė!����[´����؂¤���Ā$-����ļ i���°I ���€-HZ����lAŌ���` ’����[´����؂¤���Ā$-����ļ i���°I ���€-HZ����lAŌ���` ’����[´����؂¤���Ā$-����ļ i���°I ���€-HZ����láÛē�s&nîLz˜ų\ōw`†ëŪī­u[Õŗī×â]ĩīôŌZ["���@ŨøJĩÆÖ5˜čÄü<ŧĒB•–.fsŋ>Åŋ–Įũ+NÍáXŪí—4mÜôæÛü’ĀKĖâū6˛Iī‹5ī­��h"øv2Š­k0G.¤Ūū#Ņ€ Ŋ‡Œ™˙—āL:ˇ{‹zĸ’SÛéęms1ģ9_ZmŪ[��ĐD4é̇ĩŠōŅQziÕ-­žž<Îßxwŋ„ãkĪŧĨÛš*Ee–Ō‚ŨÂŖ÷¸ŽbzŠĢļDIۓxįĢčŅ̇'īs˙}„5—Ģc(Â]˙I?m/?ũĀ?…Įīs‰hÕŪ™9Ē6îL]Ŗ´øFü¯žšCwxGīqS_Sš^ÖĖ­ w ŽŪã*Čמ™ßYûbTÕZj}Ī_s•_Ŧ vúĪûkģųęÍĖWiéãŖü×xšO93%B÷aŒ–Ī%"rũJüAŒöÕnUWBŸ˙Kp%‡{rüŋ˙̧&­„ŗéŋLEŊüô?S{ĘŠÚÖĩ÷dØ<b���˙\O_ŌJ.āQ ‡ĒpđŪ!Áx‹†hēųęßåžš_ ä͌HŊ¸[x%‡ģqĸÚ]Æ||T”ĪÕØÜ 5]/œØFˇx˜Šač—ķüQk…ˇ_Vnœ¨ēZä¤˙f°ÆILīÖ=АGK/ō‡ëŪÖʄt>§0Š€ŗbŒÚCƜJ㞸KĐžĻ'ĸw 6]ã};Xā¨˙ų<ÄaÂ\UKGĻŽų¯ėėŧÅû~ˆĻŖ—ū\÷åŨ…†ū;°žûĖ<úöŒāŖÍÍXevõ^&ūâ¸āû!šj[���,y ’–öÁEBĩŽ.dqß9(hãÆtõŅQЊ–œįŊÕCûL;9ë.fq˙w’?#R—SNûS¸ß ŌÄę‰hųhuČboģę'oŌJ9Ĩ*šĄ seˆčëAšņá:Ÿ¤âsIÄ'WŠšQˆˆÃ!Š€>‹­%ô,¨áqŠĨ#CDÁ.ē%įų‡îōF†éËT´ė"īķX͸pũ8LSĄá¤r\$L­ķí„ĖęŋyŸĮjƇëˆ(ĐIw3ŗø˙ĶX­WĪŪ uŅOk¯#"_¤ģÅ%"ņí���ö4õ¤u%‡c÷ųÃŗ.\ Ō/Zu;ü•ŽZGũƒŪ°ÕģĨ~Ų%^ššRЏz†Œ÷rŲ‰¨_€ūf~õģ胝™fÆVÁÜÎēØ�]¤'í_ũö/3ŖČ…DD]}kŋcL&dž>%ˆŋĮͯäč*TP3Ņõ<ŽRKŧĒÖōhÍ85M¯}ūŅ{\­žę˛ŠAG/}…†nrÂŨęšđ×ÖũáZŽbĻHaž9���4ĻĻž´‚™eŖÕ†é%įųûRxKGŠ$UKK•DDƒV JĪå”s *‰ˆėL.į9KĸęI‹ĮĨƒĶTߜæ/ŊĀûđ0ŋ…=ķq_m\Ä#÷˛EîĖ‘Ŋ°–¸ŖŅŅČ5"­žū7HâŦįķhâF‘aQ‘’ˆ¨ÚĨF3ķ ؋Îą•Šj[äŅ#Œ˛���ž¤Ļž´$ęä]ž ŲĖ{˙ā§Ī}0Ü~žt”ē­û#ÂמšSÄ!ĸJ“‡Öq:ĮMF_ô×~Ņ_{#ķũūėí‚0}Gī‡šÅLå ™Üŋs9§Š{úUUĘĢ ""W CDejNĩäS×|CĨ&šĘ0m˜_íi <ˇ�� Éxš>ęī,Ą˙ëĢY~‰wō~UŲzō*8ĄŽŒáĮY¸JŸZ93Dt>ŗĒe™ŠŽÜ­åžĻ{ÅãÃQ[ģ1? Õđ8t=ŋjÃÔ3ŠJ­ĄæĒĖt6{¯˜cč0ĕ‘ čxjÕ(z†ü)\}…W×ü=ŸKgŌÖ6ë ĒÚF{›„ČĢš =Ļ Np��°ėiJZD4ŗƒŽŖ7ķâ.ZGDd/ĸŲuŸlēÎģWĖ9–ĘžFôÜ!81<™¯NđĪĻs“ō9ŗw=äĩ$‹´ΔMÂīĪđop’ 8_žās9d¸ŨŪQĖ\Îá^ÎæhtuŽbF„ģ^˧ŸøYetđ÷ĩŊ‚ūúäBnnŲ‹hz¤îŋ'ųkūæ]Čäŧ´[p!‹ÛŊ…žŽųΚŪ^ˇđ$g7­„ŗú oI"ATÕS:xéwŪâT’ZG Oō  zĸĢqë —Y��€ Mũęa5\};HŗL´đ$˙ũŪZ"új€ÆAÄŧwŸ]Îņ3ÃCô˙סęú؊1ęų ­zŲ1o÷Ō^s3Ģ'Ëhũ¯#5ߟáĪįsŠĩĢ~ũu° CD/tŅÎŪ.Œ]!Z;^mf”ē¸ÉčךķW_wôŌ˙:RYÆyv†aÆ˛�� �IDAT‹pČ*Qâ<Õįą.‡Ū;((SS[wũļÉę@'†ˆęš˙Í`\ČŧēW˜[AžöĖ;Ŋ´oö¨ú´ãWũ5sw B;Š™™tĪ´ĶŧSß'ŨēA–?���ĖāĖ;wɒ%ĩÎŊ{÷<==Y*¨UjH­#ĮŸ\˛Jč,ĄÕãÔ6- ���ž&ŲŲŲ-[ļ|ŦUæÍ›įíím˜~ĘÎi=–ąë„šœÅÃ4î2f÷-îŅ{Ü-“ŗ���āÉiÎIkÅõŋ&oVh(ȉųm¤fH0.“��Ā“Ķœ“–‡œVŒŅáą���`OŲg���ž"HZ����lAŌ���` ’����[¸k7nĩu ����Í7ļWW[×����Đ<qŨŊŧm]���@ķ„û´����؂¤���Ā$-����ļ i���°I ���€-HZ����lAŌ���` 777ÛÖ5����4OÜCGNÚē���€æ‰;eŌ8[×����Đ<á>-����ļ i���°Å’¤Åår†iôR����šNĮįķ­éÁ’¤%‹+**Ŧ��� éS(BĄĐš,IZÎÎΕ••:Îšą���š&NWQQQYYéėėlM?–œãķųŪŪŪ………EEEZ­Öšá���š >Ÿ/ ŊŊŊmpõ����’˜ĻÕj333åršL&ãņx^���€mét:ĨR™™™iåi-KÎiĘårŠTŠ˜���͏Į“ÉdRŠ´°°Đš~,IZJĨR"‘X3*���@Ķ'‘HÔjĩ5=X’´ôz=‡ÃąfT���€ĻĮãYųá?Ü���Ā$-����ļ i���°I ���€-HZ����lAŌ���` ’����[´����؂¤���Ā$-����ļ i���°I ���€-HZ����lAŌ���` ’����[´����؂¤���Ā$-����ļ i���°I ���€-ܲŠJ[×����Đ<qÅB­k����hžpõ���€-M:iõ[.ēJXmfR>GōŠxËõĮŽüĩŊ‚NKDTšÍø~-ūōŋæ´eĖ듿ąģ���lĢI'-›ø%÷܎Ļ{EõËšAA:[W��� bÕ‘fébv“NŸSÛ!f��<5žú¤åąPü¯žÚ¤|ÎŪÛŧr5õŌ˙4Lí*%"Ę,Ĩv ãSš"šĶQkēVnŊ{Ppô¯PAžöĖüÎÚŖtD4đOáņû\"Zu…wfŽĒ;ķåqūÆëŧû%_{æĨ(íÜÎĩ­žԌˆüŋŋŲCs3Ÿģũ&OĮĐôöÚ×{h_Ü%<yŸ+2ÅhŸm¯3Sų~-^ĐUûN/m­ũ›ß:ķûÄĖĸ߈˙ÕSsčīč=nękJ™°Îí=yŸûī#üĢš\CîúOúi{ųéÍĖ��hŪžú¤%āŌ7§ų_ Đü2Bsģ3lĩđ­ũ‚eŖ5D4g‡0šŗu˛ÚSÎ,9Īßv“į,a kÍß)L*āŦŖö1§Ō¸/î´°gF†é7NT]- rŌ3Xã$Ļw ū¸Ā[4DĶÍWø.÷Íũ!ŸfDVOQī5Ŧ xôũūĸ!šÅC5\āŊ´Gp,•÷í`͆ úOâų¯ė Ņ9Ię,¯ûÄĖęfö‰™EB-ŊČŦ{7Z+ÖšŊjģ^8ąnņ0ÃĐ/įųŖÖ oŋŦōjŸī$yüã ��đTyę“ĩ÷ĐŽŠ…¸0ĪuŌ}ņ˙íŨy|TõŊ˙ņĪYfI2I !ĻAvŅ(ŲÚ .T+‚ĩ×ŪÛVŊÕŪz­×õē•ÛĒE[¨­ Ѐ쀞ŠeGÁ-f#$!É$“™ŗüū˜@L†˜äå÷z>ōĮä{žßīųœ“<ķ~|Ī™3ô?CĨų GũãeĄfX"ōäÄК¯ę. >6!¤Š’ŅÅ‘ŦDsū6}Í×Úä~VŧWtU<ē$EKYĩĖßĻŨ9¸nˆ)"™ æÎ<õņMzŊÕÄnĩ†vˇ'õĩDdú ķöˇ]ŲéVvē%"ĶšlÔS˛ĶíÆĘkâ iløˇež“›DDQ$Ú%Ž5"īá2ĨŦZf6û%Ų"ōÄÄĐÔĻG—CĮn� Ķë owCSë"H˙$+`Hnšr¸L‘ái5›E†§Yģ jŌCŒÛ~bŗk]ŽZTŠXļWIfBũi÷¨ASÆeÖM>:ÃZ¸KĢŠĪŨ¤n^]*‚5nMĸ]""Y 5=ã<""}k~uÛ"RV­ˆØM)/‚Ɔ8Ļ6vN"l ĮÁČĮ›•`÷M´o\îš3ÜÛĮšjęm‰4Ú�@§×Ž“–ĻŠe×o4,—V×â;éY1n‘Ō€”W‹ˆDšč2eōËÒĮ'†ú&Xē&Ķ—6đ8ƒ˛€ˆČÄē•-áb *_‚Ũ”n_–(SՄ˛ë‡˜ĪO‰ˆį¤ĘEÄ{ę_Ānry‰0<Â9‰°),ÎmŸöx3ėպǟüH_°CûũZŊgœ}ßãÚÁĻĻJƒíM?(��:¨v´’cdoR¯ņëEDēûę˛N8%„…s@×(9^-rb‰(ŧŠ´ĒĻĪÖ\õ“BeõŦāČ7eõKīøú{÷Šˆ,˜”rJÜKŗ›Ø-1Ú^ķ͚E­”˜īdÆF4ąŧf ĮĐĪI„MõD>-É1ōđ8ãáqÆūŖĘŧ-úÍ+\ũ­aivcíM=*��:ĻvũDƒņg™Ÿ+'ß0dŲōØfŊGŦ}n÷ē7鍇ꖉväŠ1.Iŗû&Ú"˛;ŋ&¨…LŲpĸ[ĀŠŊŨûã#jNŠbŸôĻ~=¸›åŅä¨_9;É˙$DŲIŅvŊŒ"tëâ•=­đĪšš*N[^ŗ‡G8'6ÕáxsJ•UjūXũ“í?M iŠė+Rkoę!�Đaĩë5­ë‡˜ßĨĪ\æūeļ14Õ*ĒRėÔwä*‹§ĩ“ŪĻķ*”Öé× 1?+RžÛŽOhzuéog÷°Û¤g&ØÉŅöĶ˙Ō]jMøœbyuyfĢ~Ī¨Đ§GÕ{×ēÆe}^Ŧú%%FēxíŨęî|%=Ξy˜ųĀzWRŒ OŗWî|ĪÕ#Ö~mFđä"ã<Ō”nMšŧ– pN"lĒ'Âņ>ŽĖ\æ~pŦqy–Šˆ,ŪĢŠŠd÷°koŪų� i×IËĨÉ[×U?ēQũ3íŠ-ē[“ Ķ­Õ? ]˜~ʛôįĨÕʨž*C&e™ON …Û_øqčįo¸Ļ.qĮ{eö0ãÚÁ抚ˆ$ĮČsW…~ŋViwXwëšÉÁÜrå†×ܗŋčŲ>ˇúļķ›W¸ĮžāY45øčøPŧĮžgĩž_ĄtķŲWöĩū0&ôŨ:›Ø­‰"—×Â፝“§ĢéĮ;ǎõÜäĐŧ-úũët]•ūI֒iÁŦD;+Ņn°ŊŲ§�€ŽBšéĻ›,XđŊÆäää¤ĻĻ:TĐ÷Ք'y��4O~~~FFÆ÷2wîÜ´´´đkî•��p I ��Ā)íú>­Ļ8ō›@[—���Đ0Ö´���œBŌ��p I ��Ā)jū-m]��@į¤nÜ_ŲÖ5���tNj|˙ķÛē��€ÎIMNŽmë���:'õ‹—ˇu ���“ÚûÂqm]��@į¤V†\m]��@į¤īŨÚÖ5���tNęEŊYĶ��p„š:čÂļŽ�� sâÛx���œBŌ��p I ��Ā)$-���§´���œŌœ¤ĨĒĒmÛ­^ ��@ģbšĻŽë-™Ą9IËëõúũū–ė�� ũĢĒĒrģŨ-™Ą9I+!!Ą˛˛Ōī÷›ĻŲ’}��´OĻiúũūĘĘʄ„„–Ė͜1]×ĶŌԊ‹‹KJJ ÃhÉî��Ú!]×ŨnwZZZ\=��@S4'φ‘››ëķųbbb4Mkõš���ږiš@ 77ˇ…ËZÍYĶ*..öų|ŅŅŅÄ,��Đ)iš]\\ܒy𓴁@TTTKö ��ĐūEEEƒÁ–ĖМ¤eY–ĸ(-Ų+��@û§iZ ?üĮņ���N!i��8…¤��ā’��€SHZ���N!i��8…¤��ā’��€SHZ���N!i��8…¤��ā’��€SHZ���N!i��8…¤��ā’��€SHZ���NQ].W[×���Đ9ąĻ��ā’��€SHZ���N!i��8…¤��ā’��€Sôļ. ’éKŨĢÔeA]•Ūņö¤,ķwŖxoÖÕ ~õŽkũ7ęöšÕ­2[úŪË6~{ąŅ*Ũ��@ki×IKDÎęj?{e(üēڐųę›õOĒo\T”ÖßŨ_ļjÛķÔį'‡ZęÖvrŠŒ LļÚē"��P_{OZ>ˇŒî]—!ÆgZŨbė[ßpm9ĸ^ÔŗõŗÅÎüs9õäR¯bļa%�� 1í=i}×=,9RVŗĸeXōČ}é>íĐq%=ΞũcÎđšØąéúßč{ UĶ–Á)Öũ—÷˛" ™đ÷†CLjŧ¸GÛ2ģúœTģv§ASX§ŋŧW/­’!ŨŦ‡ÆĻ["ŌķIī]#CkžŌ>ĖQŋųU ÆŨh1šerۛîuߨņ™=ė”ëwĄ÷SŪģF†W–í×ËĢåâ^ÖĶWS}õKŊâ%OíeÁBŋÜŊÚõaŽV\%éqö­Ã_\@� mtŧ¤õų1EDzÆ×Ä {Ö¸ūļCûŋËCĻ[kŋVīxĪåÖåÆĄĻ?(?Yâž>ĐüķÕļ-ŲĻOYäū◁ŽQY:=8é%OfWëÉËB]OŊėˇĢ]Ë>՞ē,Ô§‹õė6ũĒ—Ũ[įTgtąŨš,ØŠOĘ2īeĸYDf¯t^Ŧ,ŸLõŲķˇé¯Ļ%DæDÄĨÉS[\÷^úll ŋ\F/ô>ŧÁ5īōP„Ro]å>pLyáĮÁn1öæÃę/ŪtõŒŗ'÷ãÚ"��m $-ãDHš˛#Oũíj×Ād;ģ‡%"eÕ2›vįãē!ψd&˜;ķÔĮ7é75—)eÕ2s°Ų/É‘'&†Ļ0=z¤!ņ^ŅUņč’}JåÕ˛p§öĐØĐÕLyúА?¤|YŦdtąEĸ]ōāX#r1ߖÉ9ę/ ũ0Ñ''†Ö|Usí/¨p‡ŗ­Yį˜"’/2ÍyLj4VLj<6!¤Š’ŅÅ‘ŦDsū6}Í×I �€6ŅŪ“Öž%öĄēEU‘ ™ÖŸ'ÕÜŋ§@ š2.ŗ.FŒÎ°îŌ*‚’•`÷M´o\îš3ÜÛĮšjęm‰ČÆCņšŽaßQ5`ČyŨk†¸5yųę`íÖėtëDŠÎ|ā˜*"ÃĶj6)Š Oŗv¨‘G…ë”RˇŠ‹×.Š:Í‹qÛOlv­ËQ‹*˖â*ÉL8Í��āöž´˛ė…?lj5ķˇéī~Š-˜ėUŗĩ, "2ņŸîڏ!ZļˆHA…’™`¯žUũäGú‚Úī×ę=ãėûÆ×6# ņ%ÔŨ˜u˛’€ˆHL#9,ÎmŸļ˜ōj‘(WŨ(Ÿįô‡Ž'ęÔ?QÃ%ž2eōËÒĮ'†ú&Xē&Ķ—z"Ž���jīI+Ę%įĨÕ¤‹GƇŪú\ûŨ×3'žû~Ēւ)ÁA)§$ô8[D’cäáqÆÃãŒũG•y[ô›W¸ú%Z‘‡4()Ę‘ō 9įD˜ųëREDĘĒëf(­:ũ¨ûjĖÖ\õ“BeõŦāČ^5+aGũŌ;ž3�€VĐaj " Qō‡1ĄŋīŌ6Ē){p7ËŖÉQŋrv’ūIˆ˛“ĸm.9ĨJíSOû'ÛšŌŲW¤Ff'áôM˛Ŗ]˛á›šŲ,[Æ˙ÃũŌ­^ˇ3÷M´Edw~ÍēUȔ ‡´ĶŽ:­ī–0Â'ĒfÃĮGԜRåģŨ��Ā™ŅŪ×´ęšé\sá.ũoēū5§Ú­IœGnf>°Ū•#ÃĶŦCĮ•;ßsõˆĩ_›<|\™šĖũāXãō,SYŧWSÉîaE""]ŧöîuwž’g'ž¸Ų<Î#?jūī&ŊGœŨ/ŅúÛN}Gž:˙ĒúO70s¯x;ģ‡õØ&=3ÁNŽļŸū—îRíĶŽŠ|*N.ĩļqpŠåÕ噭ú=ŖBŸUī]ëw–õyąZ藔˜Öú#��€Ļę`IKU䩉ÁKzÛ¤˙n´!"ŽÅ{ė{VëųJ7Ÿ}e_ëcB"2ǎõÜäĐŧ-úũët]•ūI֒iÁŦD;ší|ãæîą/xM Ž?é.õ‡Æ†TEîYí*Ę ëõÁŗē6°Raæ~úųŽŠKÜņ^™=ˏv°šâ€vÚQœ\jmcrŒ<wUč÷kõ—öx‡uˇž›Ė-WnxÍ}ų‹žÖúæ��ĐtƜ9sæĪŸ˙ŊÆäää¤ĻĻ:T��@û‘ŸŸŸ‘‘ņŊ†Ė;7---üē#Ũ§��Đą´���œBŌ��p I ��Ā)$-���§´���œBŌ��p I ��Ā)$-���§´���œBŌ��p I ��Ā)$-���§´���œBŌ��p I ��Ā)zyyy[×���Đy”———––†_ëąąąm[ ��@gÛĨK—đkŽ��8…¤��ā’��€Sš“´TUĩmģÕK��hWLĶÔuŊ%34'iyŊ^ŋßߒŊ��´UUUnˇģ%34'i%$$TVVúũ~Ķ4[˛o��€öÉ4Mŋß_YY™Đ’yšŗ ĻëzZZZqqqII‰a-Ų=��@;¤ëēÛíNKKkƒĢ‡���hŠæÄ4Ã0rss}>_LLŒĻi­^��@Û2M3äææļpYĢ9kZÅÅÅ>Ÿ/::š˜��:%MĶbbbĸŖŖ‹‹‹[2Os’V ˆŠŠjÉ^��Úŋ¨¨¨`0ؒš“´,ËRĨ%{��h˙4Mká‡˙¸#��Ā)$-���§´���œBŌ��p I ��Ā)$-���§´���œBŌ��p I ��Ā)$-���§´���œBŌ��p I ��Ā)$-���§´���œBŌ��p I ��Ā)$-���§´���œBŌ��p I ��Ā)$-���§´���œĸˇu§ąáõŠôųjQĨÄydd/뎋B¤Ûá­Ķ—ēWPküú"ãäQ…~ɜį5,)ŋ'pËJ×âŊZƒ“˙ßåĄ[Î3ÓÔ6&EË ëwŖ‹{YÎ��ø˙AģNZëŋQ¯|É=m ųüä`b”}ø¸ōÄGúå/y6üŦz@rM؊vÉK{´zIë•ŊšŽŠa‰ˆÜ9Ō¸á3Ü~ËJ÷€dëW':ŸX“ĨÎęj?{e(ü:ŋBy~ģ6ņŸîu7Vīa;”­ė/[ĩíyęķ“Cm]��hßIkū6Ŋ˛ũˇ)5ĄáÜîö˜>ÁūŨŗé: š&<]”n­ųZŨ™§œÛŊ.-ūTÖŨÚ|X‘Évm,ķęvĒĪž´OũÅ*Ÿ[F÷Žkœ|ļ9čīĶ[õ…=:^^Ų™Īa��ڋv´‚ĻÍSZb=˛}nõÉ-Š>{H7ûåOôsģפĸƒĮ”íšĘŊ—˜á¤Õ ^]%[_•40ŧÛcŪģFŠ”wžĐ*‚2.ĶzæŠ`R´ˆHĄ_î^íú0G+Ž’ô8ûÖáÆ/.¨Šžį“ŪģF†Ö|Ĩ}˜Ŗ~ķĢ@ĩŲhĪŪOyīúŦH]ņ™fÚōĶsŒ_0~ņĻ{Ķ!5Æmß{IÍúœaÉ#ôĨû´CĮ•ô8ûö Œ9ÃM™đ÷†CLjŧ¸GÛ2ģz`ŠŨ`ˇī–´ˇPũīôŊ…ĒiËāëūKšx �@+h×ë—g™Ÿ)מęŪú­b5rĪ´åęæ+ŸjƉ`đōmPŠ]{e°yrJ•q ėŌĨʓéŖ3Ŧœ_ļÜRŊ3Ošķ=WxĶ­ĢÜ[ލ/ü8ø¯[ĒīaüįûŽ•ŸÕœ^ˇ& vęSŦwoƸ#õti2o‹~E–yø×˙š÷ąūŖEž;Fßū&pũķßßv•T‰ˆÜŗÆõÔũŽ‘Æļ9ÕŋĖ6î|ßõ÷]šˆ,ÖŨž6Ā<üëĀ ģąnõJ‘Ÿ,q÷Oļ?ŧŠzũMՃģŲSšÃ;��-ŅŽ×´~vŽYRĨ<ēQ_žßį‘=­+ûš3›ŅŽSē]3Đüīô÷žP'õĩl[–|Ēũė\ŗ‘)UÔ *äŲ­úcĘãžtxN7ëú!ψôM´o9Ī|xƒūį`(Æ-MiĒdtąE$+ҜŋM_ķĩ6šŸ%"Š"Ņ.yplÍũazŠČĐîö¤ž–ˆLdŪūļ+;ŨĘNˇDdú@ķ‘úÁcJ˙d{ū6íÎÆuCLÉL0w抏oŌojÆ{EWÅŖKR´”UKcŨę•ôY‘RV-3›ũ’lybbhę�ĶĶŽ˙5��čÚûÛéoF??ßXû•úAŽļæ+õßŪr=ŧA_um°rŨ‚Sī.ö…éÖKŸh“úZ›Ģ9ĨĘ´æŽ\Ĩé{ŲS Ä>ä­ũĩĢWæ_wVÃĢbCSëÚû'YCr˕ŦD;Æm?ąŲĩ.G-ĒT,[ŠĢ$3ĄnT8-…EPĶ3Î#"Ō÷Äâ\ŦÛ‘˛jeO4e\fŨ„Ŗ3Ŧ…ģ´Š øÜ'”š[mIY vßDûÆåŽ9ÃÍą}ĖĄŠö¨Ū\:� ´÷¤%"Ņ.šōlëĘŗ-Y—ŖÎ\æž{ĩëõ™Á“û\3Đŧ{ëx ´x¯vA+Ŗ‹ũŊ’VV‚ŊđGÁÚŨũ Áv5ü\Ÿ§îuŒ[D¤4 !S&ŋė1,y|b¨o‚Ĩk2}ŠįäQqîšhxڞžSwí=õOd‹”DD&ūĶ]{„áKĢŠ/Ą.€žļ[mIš*ĢgU?ų‘ž`‡öûĩzĪ8ûž1Æĩƒŋ÷ē ��¨§]'­ü ņšOY§š$ÚŌĪ|÷‹ú9čęæīģVЖī×îõŊ?0å’ķԚú@‡ō“îȧ™ŽQ˛5Wũ¤PY=+8ōčäGũŌ;žáMīؘx¯ˆČ‚)ÁA)§Ôœ~ęeMė–#3gė?ĒĖÛĸßŧÂÕ/ŅÖäs��Ô~īˆ/¨Ŧ˙ķ>šų”,hÛrđ˜ŌÍW?$ĮČØ>Ö›õ’€\=ĀŲ؍‡ęrŪŽ<5Æ%éqvĀIˆĒ)ėã#jNŠb7TšŪŗ1ƒģYMŽú•ŗ“ėđOB”m×ŪYží´Ũjå”*ĩĪníŸl˙iRHSd_Qûũß�� ŖhŋīĻŨ|ōËl㑍ú/ŪtŊuPŨrD]ž_úŠûŖÃę#Œīöŋfyđ˜rIo+ÕįlayĘëô¯K”ˇ?WŸÛŽOhzuœbyuyfĢžW.ĢŋRõŽkÜYÖįÅjĄŋūđĻ÷lLœGnf>°ŪĩlŸ–SĒŦ˙FŊōeĪ-+k–ūēxíŨęî|%dFęv˛ÃĮ•™ËÜķļč)ŸS؍̊d÷āV-��ZĒ]_=|pŦ1 Ųūû.퍃îâ*‰÷ȰîÖĘkƒ ŪĢ~ÕŲf´Ë5m ãwŨxŽQZ­ŒZāŠ2dR–ųäЈ$ĮČsW…~ŋViwXwëšÉÁÜrå†×ܗŋčŠ÷�°Ļ÷ŒāŅņĄx}Īj=ŋBéæŗ¯ėkũaLÍ5ĶÛÎ7n^áû‚gŅÔ`„n'ÕÛznrhŪũūuēŽJ˙$kÉ´`V"—�h)eΜ9ķįĪ˙^crrrRSS*¨KÂûoŲÆo/n`Q ��t>ųųųßkČÜšsĶŌŌ¯ÛīÕC��€ŽŽ¤��ā”v}ŸV;tä7ļ.��tŦi��8…¤��ā’��€SHZ���N!i��8…¤��ā’��€SHZ���N!i��8…¤��ā’��€SHZ���N!i��8…¤��ā’��€SHZ���N!i��8…¤��ā’��€Sš“´TUĩmģÕK��hWLĶÔuŊ%34'iyŊ^ŋßߒŊ��´UUUnˇģ%34'i%$$TVVúũ~Ķ4[˛o��€öÉ4Mŋß_YY™Đ’yšŗ ĻëzZZZqqqII‰a-Ų=��@;¤ëēÛíNKKkáÕÃfÖu=%%Ĩ%;��čôøė!��€SHZ���N!i��8…¤��ā’��€SHZ���N!i��8…¤��ā’��€SHZ���N!i��8…¤��ā’��€SHZ���N!i��8E‘¯įĩu���."}zvoë2���:!Ž��8…¤��ā’��€SHZ���N!i��8…¤��ā’��€SHZ���N!i��8…¤��ā’��€SHZ���N!i��8…¤��ā’��€Sôļ.���|_æZđĪÅūJ˲Úē–īMUU_LôĪn˜‘™Ņ+Bˇ92}ąõ‹aŸąŌęĶIŽ‘WfČÅ-š‡5-��:Œ/sũņ™ŋ–•WtĘ%"–e•••˙ņ™ŋ~™s¨ą>sdô_%¯ĸ-c–ˆļä•ËčŋĘÆœ˙T¯��IDATÍCŌ� ÃøÛ?ˇu -Ļ(bÛdÚi͈uEl[Ļ-iŅ$-��:ŒōŠŠļ.Ą5(Šŋ˛˛ąåg˛”ĶQ¤¨e§œ¤��δW?ÛË‚Ö -ŧˆIŌ��p I ��Ā)$-���§´���œBŌ��p I ��Ā)$-���§đŊ‡��tZį;gÄįõHKÕ4­¤äøŽO>]ģ~seUUxë#÷ũöƒ Ŋģf]ÛŲŠČ Cåæá2$Uĸt9R&oG×Kî‰ĮŸž~dt•ĄŽ2},žF~ųĻüy˙¨¤�@į4kÆÕÃΞsĪŪ ¯n5 #ŖWúč‘ŲįôĮŋü­ŧŧ3<k^UdŅt™6Hí‘g?–ōj’*ˇ_$3‡Č¸˛§ !ŖzË?ĻĘŖÎPĖ’��Ō…ÃĪ=Ø9‹^]šųãmá–=Ÿî˙xûÎ;nŸ{å„KŊē˛mËkˇeËôÁrŨ+ōōžš–7ĘķÛdĶyeĻ œ'抏wīŸ,+Ž—ÅŸČŨīš"IZ��tB—\|á7‡ÔÆŦ°‚ÂĸyĪ.(8ZôŨūēĻ]yŲ¸ķ†ŽõŔ•WükĮîˇŪ[ūΜĖ>Ŋ¯ēl\Z÷nĒĸ~›—ŋōí÷ŋüúQUõ˛ą— :8Ąk—ŌŌãk7lŪøŅÖ3sta˙~‘ŧ˙E]Ė +Ē”;ߑ×ˤŗeÕguíŠ>yû§˛å°Ė^~&k$i�ĐéxŊžŨSß[ģūģ›Žäæ58dúOŽ:g`˙%ËW:ōmF¯ž×üä*ˇKmÕ;n—ë֛ŽßļkĪĸWW(ĸŒ™}ÛėY˙õĀcUU]1qä…×ŧļę̜Cũ˛2¯ž2É4Ėļîpøājt÷É嚆ĸŨû_ˆˆ\’Q—´|nys–TČ´Eb4ú‹Ž i�ĐŲÄĮÆ*ŠRT\ŌÄūŅŅQŲį ]ūÆģ;vī‘ĸc%Š)ÉcF]´â­÷ģv÷z=[wė.(,‘e+ŪÚą{¯a˜^gԈ Ū_ģū_Ûw‰ČÆcÅ=ĶĶÆ}ƒVx‘œŌ6U’_!=âj~ÕUY:S†ĨÉoŪčĖTW‡§<��ĐŲØb‹ˆišMėŸŪ=UU՜C‡k[ųÖív''%=Vp´čÆk§3*ŊGw˲žø*' õHKÕ5m˙Á/j‡|ūå×ÉI nˇģuĨ1–%"âj$ȨŠX'nŌ˜"IŅōėĮōđÉN?3ÕÕaM �€ÎæxYšmÛ)I‰MėīõzD$¨Žm TEÄëqÛļũĮgū:î‡ŖF\0|ōåãKJ¯zgõÖģÃC~yëĪÄŽI4ŠĸˆH\Ŧ¯čXqëNƒéĶĩMQē¤ÄČĄã5ŋæ”Ččį%hĘĀn˛lĻ {ZŽVžk´��člĒ̃‡ŋÍË~îģkÖ§Žl <Ā0ĖŊûœÜX¨–y+ĖëņˆHU "ūĘ×ß|÷õ7ßMMIžô’‘ŗf\_p4ËūąhYnŪ)OS(-=.gÄŅJų´Pf ‘‡ÖÉŠ1”q?YķeͯĮĢĨĘ™ąXvŨ.‹gȄ…õ?–čŽ�Đ }°as×.ņ—srcjˇ”™S§ د^įosķ-Ë:+ŖWmKŸŪ=́ŖEʼn]ģ PĶ?ŋđčâWWZ–Õ=5åÛÜ|Ã0|ž˜‚ŖEáeešßo4ų’eË=ĩIu“ŸgŸŌ˜%]&;ķę’V­ŧ šv‰ü°<<áŒÕȚ��Ņļ{˛2ûLŧttĪŨˇīú¤:ėÕ#môˆėüÂÂåoŧS¯seUÕG[wL¸tôŅĸâ#šyY™}FĖ^ķáF˲ēví2{֌oŊˇw˙Û–ķ‡cÛö×ßTWoúxÛ.õû+ŋ9t$Ąk—Ģ'O*=^ö—…/žąc\°].é#O_%ŖzËëûĨĸZu“Û/EdʋõēÂÖ|%÷¯•ûÆĘĮGäÕOĪD‘$-��:§EËVøüËQ]0uĘ$MU‹ŠKŪ]ģnŨĻCĄ>€ˇôõ7ÕÕ×üäĒX_LIéņwVøūD䋯r^|eųØŅ#¯˜8Ö2­ŧ‚Âį_Xt´č˜ˆŧļęđŗâb}eåŸėûlÕÛĢĪäÚ"?]&ī”9įËü)âÕåđqYō‰<˛.ŌX˙ķĄŒė- "ŸČg <YŦ•)sæĖ™?žãû��-vû]÷ļu ­æO˙{ƒíʝáBNĪ~āûõŸ;wnZZZø5÷i��8…¤��ā’��€SHZ���N!i��8…¤��ā’��€SHZ���N!i��8…¤��Î4EQŨt&ëhŊeY‰¤�@‡ëķ‰ŨāW'w(ļ틉ilcˇXiøÛĄÛ„-IVÚ$$-��:Œ›g͐ÆWƒ: Ešy֌Æ6.ŊĻĸĸČŌkZ4I �€#3Ŗ×Ü6;Öį‹põ­=S%ÖįûÛfgfôjŦĪŞ~勞ļô˛] éǤÆĘúŲrqFËæir��Ā‘™ŅëĄ{ījë*œuq†äũg[ŅJXĶ��p I ��Ā)$-���§´���œBŌ��p I ��Ā)$-���§´���œBŌ��p I ��Ā)$-���§¨"bYV[—��ĐԋUĒËåÚˇo_[U��ЙėÛˇĪív×ūĒFGG¯\šŌļí6Ŧ �� °mûõ×_ŽŽŽmŅ&L˜PXX¸gĪžÄÄÄØØX]×Û°>��€Ž(<xđųįŸ/,,Œ¯mWîģī>)//÷ûũܰ��Đ<šĻÅÄÄø|ž“kV°bcccccÛĸ*��€Në˙Į˛Úu1ÜÖ����IENDŽB`‚�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/profile-certificate.png���������������������������������������������0000664�0000000�0000000�00000131545�14156463140�0023311�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��X��á���Zū ŋ���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨw\UåđĪŊŒËŪ ād‰Š‚¨¸Å-ŠĨĸĻĨš2-[– +K-Ë´Ŧ~™eš÷^šp páb("SöæŪķûšrå2äėķ~Ŋ|į<į9ß휪œûŊĪ ‚ €ˆˆˆˆˆˆˆˆjLŦî�ˆˆˆˆˆˆˆˆ;M�JĨHMMEAA¤RŠēc""""""""j444 ‘H`aaąøiŋQqqąđčŅ#ččč@OOOa'=%“ɐ——‡ŧŧ<ØÚÚĘķ(ĸäädA,COOOÍ!5999��sss�€8??ēēēꌉˆˆˆˆˆˆˆ¨QŅÕÕE^^žügqqq1D"‘C""""""""j\Äb1Š‹‹ŸūŦÆXˆˆˆˆˆˆˆˆ^L°Ј """"""""1ÁBDDDDDDD¤"&XˆˆˆˆˆˆˆˆTÄ ‘Š˜`!"""""""R,DDDDDDDD*ŌŦ‹JEbē¨–ˆˆˆˆˆˆˆ¨Æ™´Îęf""""""""1ÁBDDDDDDD¤"&XˆˆˆˆˆˆˆˆTÄ ‘Š˜`!"""""""R,DDDDDDDD*b‚…ˆˆˆˆˆˆˆHEL°ŠHSŨŅ‹'ę~Öo܊ėÜ<Čd2u‡C”H$‚ž&M@‹Ļļę§RėÁBDDDDDDĩ*ę~~ûãodfį0šB*YYŲXņŋՈ”¨îp*Å ÕĒuļĒ;z‘ˆDūZˇYŨ‘TŠ """"""ĒUY99ę^4"2ŗ˛ÔEĨ˜`!""""""ĸOu‡P)&XˆˆˆˆˆˆˆˆTÄ ‘Š˜`!"""""""R,DDDDDDDD*b‚…ˆˆˆˆˆˆˆHEL°ŠHSŨÔ•Ųŗga˙ž}8w>ļļļ/ė9+Ōē•ÚļõĀŽŨģÕQMčë顝ģ ZŲˇ„Ą ‹Šœ’Šk7oá^tŦēÃĢ+Ksŧ4x�œZÛ�nߍƞƒ‡‘”œĒæČ¨64ø‹ 8|ø_ėŪŊááH}ü�`ei‰Ž]ģ!`ėXtčĐAÍQQmé×Ëûö‚Dĸ-ßV\\ gôë勨˜ûظ}7“RÔåķą˛4ĮŗĻCWWGžÍÃÍŽvøá—•L˛ŧ�t‚%==ŗŪš‰sįÎAOO]ģvAĶĻ͐““ƒ;wî`ëÖ-Øļm+>üp.ۜ9SŨáū§mŨēˇoŨRØĻ̧ +Ģ&čŅŖZļlŠĻČęÆŌĨ?ĀÛģ3ēwī^ícūũ÷bcb1}ƌ:ŒŦvÄÆÄ (8Å#77‰-Z´D׎]ŅŦYŗzĨ&mMÕWúwˇOßžčÚĩkšũ999Xž|™€O>ũbqíŒ,UåžaåĘß!•JņÎ;säÛe2BBBpåōe¤g¤ÃØČíÚˇ‡O­ÅŨXÚ̍¨įΞÅõב••cx´k§ĐÕ)ŖĒÆŌ^‚ ((aaĄČÎΆ…šz÷é GG'y™Í›7ãî;åŽõėč‰Áƒ‡ÔJÜDDˆD"ŧ>n<Ûš—Ûw=ō6N<0d@_|0k:~˙{}ŖéÍ2|Č�…äJ)]]ŧ4x�V­Ų¨†¨Tđ˛?ŽßŧĢ7"åÛ~\4Zš%ІĖŦl„\ŧ‚ũ‡C*•Ē+ĖzĶ`,‚ `öŦˇpîÜ9 6 | SSS…2ááá˜ųæ ,YōŅĪĪOMŅ�˜š™b营ōŸŗ˛ŗqéâEŦ[ˇ“&MFĶĻMÕ]ãŠGâáī˙RŊ36&6n€››^ō ēzzČĖĖĀųsįą~ũ:LžüŦŦŦę-ž~ũüęõ| ŨÚ5k–.­ĩ:5ĩ4q5"Béāk׎A,C*k8ŋOŸ:…ĖĖLčëë+l?uō$‚‚ƒĐĢWo4ĩĩEėũû <‘H„.]ēÔÚųC{9rˇo߯°aÃ`nnøøxėÛˇÅÅÅčŅŖGĩËԆÆĐ^§OÂų ķčŨģlmmqņbļnŨŠI“&ˇüÂŅÉ >;+k`h¨Ž‰ˆ^XūƒüĘ%WîFÅ`՚MČÍË�œ<Œˆë‘˜=mĻž6K~úĶŌÕîsqjåPņž'C†›n;Ą‹—'Vo؊đĢ7Ęí724@ŋ^ž�€=ÔwxõŽÁNr{üØ1œ={žžņĶO?—KŽ�@ģvíđŋßW" `lĩ^p’““1˙ķĪŅ­k89ļ†g‡ö˜:u ÂÃÃåe|:{cāĀåŽíī×öv-q"0PaûŪŊ{`o×ģvíTØ^Ķz|ģuE+{ÄĮĮ—;6-- Ž­[aäˆōm'1tč8;ˇAGĪøhî\dddTŲuA[K-íėäÜŨŨ1nüx"44D-15v ęũœĄaa°´°ÄK/ ‡CĢV°ąąA›6Î7~<LMLņāÁƒz§]ģv°ąąŠ×s6d׎]Ģõ:›7oääd<zTūyģvõ*lmÔ;§TY‰‰‰ ‡‡‡ÂvŠTАĐøtöA׎]ŅŌÎ=zô€ŗŗ n\ŋ^Ģ14ôö׎]ƒ——7Zˇv„ŠŠ)ÜÜÜāææ†kW¯VģLmičíU\\Œ ā tņé´hŅǏ€…šΟ?//WPP�3SS…ßs-íė`nnŽÆč‰ˆ^,ÖV–čĶC1!Ÿ˜„ŸWū-OŽ”zœ–Ž•˙l€–Ļ&†é_ŸaŌ3Äb1&vm]�ī}˛�ŗįÎĮėšķąėˇ?�ŪÛĢ3ÄzĶ`{°ėÚŊ �đæ›oVÚeØÃÃŖÜ‹ļ2ŠŠŠ1ü%dffaÜøqhĶĻ Å?Âúõë0zô(ŦYŗ>>>đõ펝;w ==&&&�€””Üšszzú¸rŊûô‘× ‘H_ßî,“4Ši=“ߘ‚¯|…;w`ÖŦŲ ×pčĐ!ãåW^PŌģaęÔ)°°°ĀÛŗß†™š9.\ÆÔ)Sj­›ĩĒ455aeÕiĶäÛd2Ξ9ƒëׯËģņ{wîŒN:ÉË,]ú|}ģ#** 11Ņx÷Ũ÷˜˜ˆS'O"1)2™ ÖMŦŅĢwoųđŖââbœ<q×o\GNN ĐÖŊ-zöę%o\ _ßîČĖČĀõ×QPP€-ZbčĐĄ000�PŌeũØąŖˆŽŽF^^ŒŒŅÉË ŪŪŪÕžîŦŦ,ėßŋ11ŅĐŅŅAGĪŽåĘTužuk×"6ļ¤ģcDxĻL CCC•c̊T*UÚ}O"‘”ŪT{[é}Ģj˙ŗ]ũkë>Wu^UbųōeØĩs'223áæę†>ūXŪ6ÅÅÅøå—Øŋoâ>„­ &M~&L×áŲĄ=f͚3gNã|PÜŨŨqéâE�ĀŽÛą˙ĀA¸ššŠ́>š4i‚Ģ ÉŦ””<zô=zöTHŦUįHĨRœ>u W#ŸŸë&Öč͎/š7oŽ4†ŌžS‚§§§Ō2‚ āāčÔąŒŒqīŪ=ų>ąXŒŠS§AWWWác#ŖZOT6†öĄÜīMMMˆDĸį*Sz{=~üÅEŰŗŗ“o‰DpvvFH™/  ­­ ""Ē;ž]ŧËũn˛nb…ŸŋûJūķ{Ņ8qæ<ŽŪ¸…„Ä$‡]F÷.Ū044@VVļÂąƒũzc_oĨį:tô=QûQ‰Û÷ĸĐÖÕYųžģŅ•ÛĐŽåYĨI–g{˛DÅÜPŌ“åŋ a| WâĘåË%Ũē•t)މå˖!11ë7lĀĮĪÈ#1ķ­ˇ°}ĮNhijaŅĸ…��ßîžaĄĄōcƒ‚ÎCSSC† AČŞ.ÃÅÕ––– ÛkZĪØącahh„;v”ģ†ƒöCĸŖƒaÆ�~ũe¤R)Vūą 3ßz Xļl9œQTT¤ZƒÕĸô´4É>vô(ΝG7__LŸ>}|päČa\ž|Y^FCC—/]‚•Ĩ%^}ĩäį–-›aai×_ŸT2LĨ‰6mڈŧ'ŲėC‡âJøôëį‡3ŪDīŪ}ŠãĮŽ)Ôt––˜=ûm˘ņ&áĖ™Ķō2ûöíC\\FŒ‰iĶĻŖkˇn8zônŨz:ް*{öėArrÆŒÅ̝N@n^.nŪŧŠPĻĒķŒ=666pusÅ{īŋ++ĢZ‰­*ŽŽŽHIIÁöíÛņđáC‚PaŲĒîeaaaĨ÷­ĒũĘÔÆ}ŽÉyŸ×ĸ… ąeķf|úŲgØ˛e+Zļl‰×&NÄũû%ŋd/Z„•+Wâ͙3ņīĄ1ų)øæëØ˛e‹ŧmmmlÚ´mœąqã&ŦZõ'ÜÛļÅ0\ŧtÎÎĘA?/AāâęŠkׯA&“ɡ_ģzVVV°´´P(_{pôč\žr~~ũ1aÂD˜š™bãÆ HKKÃŗRSSąmû6tíŌĩÂä �\ŧx™Y™čŲĢWš}"‘fff ™L†ččč ?t×TCo/‘HĪž¸té"’’’��ņņņ¸yķ&:<)_2ĩĨĄˇWiLb …íēzz(Č/˙›PXX-&Xˆˆę”ŗc+ĨÛoߋƉ3AHHJVØŽŖ#ļļ6D"Ú(~sđč R’xPWBb΁#ČËË/ˇ=//{Žô؆p-Ÿ~0+–,PøSÖŗ=Yū‹l–”ÔT•c_‚ āũhãė kkkųË$Pōm]ĮŽž8}ú4rrrĐ­›/D"BBCäsēŖŗ3|ēø`׎ČÍͅžžĨtŌԚ֪ĢĢ lذaaaōoģSSSŒÁC†ĀČČ2™ BBвeK´k×NáÜc°nŨZ•Û­&Ęž<ggg#,4ŠŠŠ0 d¸TAAÂ.†Áˇ›¯ŧį‘™™=ŠĮųķįä+B‰D"hiiĄoŋ~�J†wÂŨŊ­<™5`Ā@¸ē¸BSSšššˆˆˆ@ŋ~~ōoôÍĖːš’‚ !Чo_h<yy67ˇ@ûö%]ԌŒŒĐĒU+<Šú w˙ūũ!‰äÃŌĖÍÍЍ¨(´iSõÚĖĖLÄDGcā A°ŗˇ—Įu/JĄ\UįŅŅҁX,†ĻĻ&ôôôj%ļęđôôD^^Ν;‹Č›7Ą-ŅFķæ-ĐĻM´mÛZZZ�Ēw/322*Ŋoééé•îVmŨįĒâRUvv66o،Oæ}‚ĄCKĸ‹/FNnbccajjŠõë×á͙3ņōË%=ŌėėíqíęUüūŋß0fĖ�%ttuņņĮķäukjhB[Kfff*ĮY–ģģ;Nž8{÷îÂŅŅŠdÉõkhß^q•ļę܃ââb\ž|ũúųÁÕĩäė!CQXX„´Į†|æææbËæÍpttD¯ŪĘŋ•Jz…8ˆaÆUģAāņãHKO“÷úĢM ŊŊúųų!'7Ŧ\ ą†2Š >>>đņņyŽ2ĩĨ!ˇ—ŠŠ)DbĐĸE ųöää’w…‚‚čęęĸ  �ņņņø{õj$%'ÁĀĀ�Ž.ŽđíŪ]ūī"ŠÆÔĨÜļũ‡ãđņS%?ė+ųO/_Øĩh§VđéTōģÄÔÔXiĨɇŌŪęė푘œ‚Ĩŋü—†ô—ĪĮrû^v¨Ū2Íęžk+Ë*˔&Yū\ģ WoÜǞü‹ĻÁ&XÄbąÂ‡õ˛FŊō ÂÂBËmŋcãōąRRR––†´´4tööĒđœņņáčč„6ÎÎ yZppzöėoīÎ(..ÆĨK—āëë‹āā �@÷îå'´´´Ŧq=c°aÃzlßļMž`9tč¤R)F  d‚üŧ<ĨßĖļjĨ<ķ[׹háB…m:ē::lžÄ”�™T{Å s˖v¸rų åžš6{:)Žšš9ĖÍÍą{÷.tėØ °ļļFË']ēÆÅA å&ŌĩąĩAQaRSSåĨ6yfÂT]]äå?íĩ ­­ķįÎ!&&šyšyyy03¯ŪÚԔ’ĨâJ'FJ>(Û6ĩEbBĸJįQ5ļęęÖ­ŧŊŊ…č¨hDEEáā8sæ4ƍKKËjŨËĒî[UûŸ•”˜X+÷ųyĪûŧnŨē…‚ü|´-3|Q[[˙ûßī�€ . °°žžŠ+œøtņÁ–-›‘““#O.+^VLLLĐŦY3DDDĀŅŅ ÷īßGzZ:ÜÜ܆ØTį@Z,UĸĄĄWžItȤRlßļ †FFōDTE>ŒæÍ[ĀŲŲĨZ×xü8BÃB1jÔ¨:™#ŖĄˇ×É'ƒ#GÂÜ܉‰‰8vė(tõôĐ­[ˇj—Š- šŊ$ ÜÜÜpîÜYX[[ÃÆÆ‘‘‘¸õde< ‚� def§‹ ņāÁœ>} ™>|D…õQÍ%$&=MŽ”ald„÷g VØVY¯ë˛IuĨILNÁ˙Ô|ĩ †t-‹Åpwuf‚Ĩ!ąnŌ111HKK+7Á­ŸŸœœž.xîÜYų\Ęäd—ŒÅsusÃÜįVXÎĘĒ �Ā××˙üũ7rss‘™™‰¨¨(Ėûš5k[[[\¸\’ †Žžŧŧ”'mjZOÛļmáæîŽā˯ž‚ŽŽ8�[[[ųKoū“.Ë::å—ųŌŅŅŠõ1ôÕaff†áe&āÕŌŌ‚™™™ŧG���Ö­[[n.� ä›˙Ōoæu$O¯M,câk¯!čüy\ž| 'adl„^ŊzÃÃÃOę•H$ 1iki?9oĄB\‘JĨظad2 �s ˆÅbl-3lŖ*Ĩą<Ûĸėˇî59OmÄö<´´´āäÔNNm��1ŅŅØžc;Ž;†ącĮVû^VvßĒē¯ĪĒ­ûüŧį}^™O&šÖĶĶUē?;+ �0~ÜX lÛ=I*''%A˙Iī'ƒz¯ęæîŽcĮŽ"??ׯ_CĶĻMajjĒđ¸:÷   ¤ëkU=M.„”$š,-,+})ē{÷ĸĸîaÚ´éU^CiÅ7n`lĀXy/˛ēĐPÛ+##įƒÎcøđō^ ÖÖÖ(,,ÄącGáåå…ŧŧŧ*ËÔö|# ĩŊ€’lģvíÂÚ5k��͚5ƒ¯ow9rēēē‰Døđ™÷‡æÍ›Cœ D˙ūä= ‰ˆ¨æŌŌ3ŅÄęéĐŅÂĸbĨåö<‚¨˜û˜öú8ųļôôĖJëVg2ÂĐĐ�ūûÁŨÕzē:¸ƒ¸ø�@3[k´v°Cn^>ŽŨˆÄŪ•›KæYęē™LV­š>īEĮbĮŪCõQÃĶ`,:y!&&'OžĀˆ#öM›Žø’={öŦJ,úO? (ģ˙ŦîžŨņįĒU¸téRR’!‰āõdŅŽ:ÉįOšp!>;Wø’§J=ŖGÆ_ĖG`āqtęä… ‚ņæĖ™ōZō$ą’Ÿ_~ _NNN•/“uAKKKĄ×†2ÚÚ%/ÍǏPēôŽ‘QųnĨôõõŅĪĪũüüœœŒāā ėŨŗ<Ў  @ᘊ^Ö+ōđáC$%%aÂĉ “ææåÂÄÔ¤Zu”~x6–˛÷Ē&įŠØĒ#;;ÚÚÚåžk;{{8;;ãîŨģ�Ē/+ģoļļļUî/ĢļîsuâR…Ų“ũr,“čĮeËāŦdh—Šį¯)WWW9r‘‘‘¸yķĻŌŪyÕšĨ ˇgË<ËŌƒÆÚĩkxü8ú(ŋō�ܸq………øõ×_äÛA�`áÂoāį×_>Ņķŋ˙­[ˇđęĢTžUi¨í•öø1 �Šs›˜™šBZ,EFFFÉU”yvn1U5Ôö�]]]Œ7™™%/įFFF8uō$ĖÍĖ+6hŨ¤ä‹™ĖĖL&XˆˆjAä{ –æMmāåŲ¯\-7ēĄ­ÛĶw(Apë™áø …ĄĄ>œ=Ļ&OGZ8ĩv€SkÅ^āúzđņō„SküđËU&YÔaíæhōĖÜiĪNŧ{/:ŋũĩNá‹Ī˙’;É혀��ПVyŌIKKK˜ššâŪŊ{J—0NMUīæŨš3´%„……"č|œäŊ*ŧŧŧq%ü îßŋččhôčŅŗÂķĒRĪKÇCGWû÷īĮũû!“ÉđĘËOģ?[YYA[[qqqåÎûėdĒ ‰ĩĩ5445““ ų]]]čééUø"›––Ļ0‘ĢĨĨ%‘X„ääd4ąļ†H,*ˇ„đø‡čHĒ=D@Z\’%/ûĸüāÁ¤§ĨW;iUúá:1ņép ŠTǐ|žķ”ū\ąU%;;?ũ´Ae–&-GjJ* ôK–Õš—UŨˇĒö?Ģļîķķž÷y988@GW!.ȡÉd2Œ=;vl‡‹‹ ´%¤Ļ¤ĸUëÖō?&ĻĻ037¯2QTW T}}}88´BĐųķČË˓ĪoQVu…4ĩ4žyA°vÍDDDȡĩvt„ĩĩ5„ĐD•Y¨Ŧ^ŊzcÚ´é˜:ušüO×.]Ąo ŠS§ÁŨŨ�đđpŒ;ŽÎ“+@Ãm¯ŌŪŗŋÛRžülddT­2ĩ­Ąļ�\ŋ~ņņņ022’ĪsvõÚU´iSԃ/%%ÛļmS˜Ã �âââ ‹j}N$"ĸ˙ĒŗÁĄ ‰‘H„‰/ã§oŋĔ ōí†účâõtâō+W¯7Ȅ�øōSHŽTÅĖÔūûÕaD5wņĘU|2īËA%ķŋ(KŽ8ؕĖo–Ų@īOmkĀ=X:aĈ‘Øĩk'^›8?.[†f͚)”)((ĀĻqėØ1čëTÚ]xȐĄXŋ~ūøcĨB7ßÔÔT 8�øķ¯Õ�J†Øt樗/]BLL zõzš•ķööBaAV˙õ�ȗŽUF•zŒ1`Ā�>|ąąąčÔÉKĄĢģĻĻ&<=;"88ááá ŨĒk‚ÛęH$đėā‰Ķ§OAOOļļļČĖČĀ‘#G`hd„€€�ĨĮefd`ûöíčͧ/!‰píęUˆD"4kÖ ēēēhßž=Ο?33SX[Û 66ĄaĄčÚĨkĩ—­ļjŌš A÷î=œ”„ĀĀ@Ø;8āqęc…š1*R:×Āšsgafj =}}„„\P*UŨķHtt˜ˆ„„Ē[U āĶŲgΞAvN6œœœ ŖŖ‹ėė,„‡GāAÜŒų2€ęŨËĒî[UûŸU[÷ųyĪûŧ 1fôüúë¯°ļą†ŖŖ6n؀ˆĢX˛d 16`,–/_S3S´k×>Ä×_/€ĩ5ūZũw…uáúë¸~ũ:lmmË ĄT•ģģ;öėŪ ;{{ų’ÖeUįH$´oßįΝ…‘‘,,,pųō%Ä?ŠĮĐaåįÂđđđĀíÛˇąwß^L›6Ŋ\O€ŌŊeé@,Ë{Oáĉ@´jÕE……ˆ‰Q(ßŦys…ŋƒĩĨ!ļ—šš9ZĩB`āqH$Ú07ˇ@RR"Ν;‹ļm!‘H ‘HĒ,Sb{@däMÄĮĮcāĐŅҕΓäŨš3€’ד’ącûvôęŨ †¸˙>ΝGgīŠ{˛ŅķIHLBāéķč×ˡÜ>“2āŽ6Hū˙ššyØ}āHŊÄWm]Û<÷1î,åܐ•MŽü¸h>´žųâ<äâ5EVŋl‚�û-ØŊkúöé oooØÛ;@&“!>ū!BBB‘““ 7ww,]ēTayÎgŊ3gãˇ_ErR2ŧ;wFRb"6lXôôtŧöú$…ōžžŨņË/ŋ 77ŪŊå۝œÚĀÄÄÛļmƒ­­-Zĩn]é5¨RΘ1Øŗ{7n\ŋŽoŋ[Rn˙ôéĶqáB0ۘ< ŖF†Š‰)BB. //††ĩ˙ícmņëß:::8~ü˛ŗŗa``�'''ôîŨ§ÂcZÚŲaØ0\ÆŠS'K>XYZ)L`9pā H´%8tčrrr`ll _ßîĪ5YŖžž>üũ_B`āqDDDĀÖÆÃüũ‘•…;w`ũēuJWzÖđ#°˙~lŲē‰=;ÂŖ­"##Ÿë<ŪŪ^ØŗgÖŦų¯ŧüJ­ÄV•žũúÁŌĘ W._Æž[û——ØØØbÜØqō ‹ĒīeU÷ÍÜÜŧĘûúŦÚ¸ĪÕyžTõņŧy‰DXŧh˛srāâė‚ŋ˙ūG>‘îgŸ#c#|ģx1’““aii‰~ũüđÁ‡VZīë¯OÂ{īŊ‹QŖ^Áī˙û=zV܋Ž&Ú´iM-MųŧĘTįôëįD8~ü ĐÄĒ ÆŒ­đ›ūÁƒã?Vâ5jÔsĮššŠŦĖ,ÜƌĭČō˖Īy÷]ĨčUÕPÛkäȑ8uō$öíۇÜÜ\ĀŨÍ]a%ę”Šm ĩŊ† ŠC‡bĪž=(..F‹ĄĢ4e�� �IDAT-1qâkōgFSSãĮŋŠ'qøđaäææÂØØ}úô­p6""Ē™Ŋ‡ŽÂĖÔžíÜļÛÚXÃĖÔÚēĄS‡’9ķōķ đĮšxœ–ŽŽPĢEŋCH ôΰĶĸĸ"܏‹¯pXPVV6B.…c˙áãjˆŽū‰ĸŖŖkkëÚ­T\ģßaëÖ- ErJ 4ÄhŌÄ íÛwĀāÁƒŅˇ_ŋr“ēΞ= û÷íÚķAōŽâIIIXņķĪ <ޤ¤$čééÃģŗ7Ūzk–|9×RW¯^…˙°Ą�€Đ0…ąčS§ŧcĮŽa˘�|ûŨw•žŗ&õ”Õ­k<NKChh˜ŌûöíůŋūŠč¨( _??|ōé§<h LÍ˰˙jˇ3Qmøāŗ¯U:Ū¯ww č͉ō^‚Ņą°aÛ.$&Ĩ¨tžēļ|ņĪŨ‹ļX*ÅģķžĒŖˆj—…š)Ō3˛P\Ŧ|BâēđÃ7ŸĢtŧ “ÖR$%`÷äKÔF‘`ų¯ŠG¯ž=0zô|ķĖōĮDDDDDD •Ē  ¤÷GûļŽp°kCC!9å1ŽŨŧ…ģQ1ĒYüųĄOŽÕN˛HĨRž>Ŋ‡ŽÖqdWCN°4č!B˙u ~�˜<y˛š#!"""""Ē_9šš8w! į.„Š;”Û{č(“%˙!L°401ŅŅ8sæ Ž=‚3gÎā9sæŧ """"""ĸ†‡ –&ōV$žøb>LMMņá‡sņæĖ™ę‰ˆˆˆˆˆˆˆĒĀK3pā DEĮ¨; """""""zbu@DDDDDDDÔØ1ÁBDDDDDDD¤"&XˆˆˆˆˆˆˆˆTÄ 5x"‘ē#¨,DDDDDDTĢ õõAPwô"Š;ŠJ1ÁBDDDDDDĩjÂøŅ ŋģ5.bۘ î(*Å Õ*‡Í0sÚ$ęëCÄD ŠČ@_ī˘[ë&ęĨRĸččhÁÚÚēv+kÔj}DDDDDDDDĒdŌZ­/!!vvv�؃…ˆˆˆˆˆˆˆHeL°Ј """"""""1ÁBDDDDDDD¤"&XˆˆˆˆˆˆˆˆTÄ ‘Š˜`!"""""""R,DDDDDDDD*ŌŦ‹J™´.Ē%"""""""jØƒ…ˆˆˆˆˆˆˆHEL°Ј """"""""1ÁBDDDDDDD¤"&XˆˆˆˆˆˆˆˆTÄ ‘Š˜`!"""""""R,DDDDDDDD*b‚…ˆˆˆˆˆˆˆHEL°Ј """"""""i@llŦēã """""""jT$‰ü˙5 M›6j †ˆˆˆˆˆˆˆ¨1Љ‰‘˙?‡Ј """"""""1ÁBDDDDDDD¤"&XˆˆˆˆˆˆˆˆTÄ ‘Š˜`!"""""""R,DDDDDDDD*b‚…ˆˆˆˆˆˆˆHEL°Ј """"""""1ÁBDDDDDDD¤"&XˆˆˆˆˆˆˆˆTÄ ‘Š˜`!"""""""R,DDDDDDDD*b‚…ˆˆˆˆˆˆˆHEL°Ј """"""""1ÁBDDDDDDD¤"&XˆˆˆˆˆˆˆˆTÄ ‘Š˜`!"""""""R,DDDDDDDD*b‚…ˆˆˆˆˆˆˆHEu–`yîĮpprÅāaÃQXTTa9™L†—G…ƒ“+fŧõļ|{aa!œ\Ģüŗ}įŽruÆÅ=ħķŋD>~hãÖŧēāõ7Ļáô™ŗÆˇß}>ž=áäę/_ŧ5{Ž]ŋĄR;\ē|Ÿą�ũ CģŽáčŌ;wŘqđŋ•̐––ŽRũĩaËÖípprÅk“§*lOIIÁė9īÃŖƒZ;ģãŨ÷įVZžj×ÁÃÁÉ#F¨;”įō"<O 1&"""""jØę,ÁōÕŸŖyķfˆŧu˖˙\ašŋV˙ƒËWÂaaaEß|%ߞ‘‘ �ĐĐЀģ›k…LMMęģzí:†ŧ4›6oEnN.<;´‡•Ĩ%NŸ9‹×ߘ†UŽ.ÆM›1rôXė?pb‘:zB"‘āĐá#ņĘ>rėš¯?;'3gŋƒWƌÆM›q÷Ū=hijÂĘŌéé ģˆī—.Coŋ8{.čšë¯‰/|ƒvžŪÕ.ŋxÉ8pđ¤2zõė{{ģē ­Úž÷ƒÆzM|žˆˆˆˆˆˆžEGG vvvuRųĨËW0zėĢ[6ŽC§Žž ûŖĸĸ1Ø ņ÷Ÿ+ŅŗGwųž{÷ĸā7h(šÚÚâĖÉę%8ŠŠŠ0hØpDEEcâ„ņølŪGĐÔÔ�œ=„)ĶßDqq1öėÜ7W�Āí;w1ĤR)f͜9oĪ‚X,† XöĶ üōÛīĐĶĶÃÉã‡aan^­8 0*`<Ž]ŋ]]]ŧ9}*FŊ<MšX� ‹Š|?­ø—¯„CCCŽüMáúëˆQˆē…đK! ÛĨR)Š‹‹!‹ĄĨĨ%ßŪoāDEEcųßÃč*ËׇŠŽĄ1Ģčšū{ŗŪ~íÚy`×ļÍjŠŽb/ōķ¤Î˜ˆˆˆˆˆ¨ņˆ‰‰AiNĨNį`ņėĐŗßz‚ āƒšķ››+ß'“ÉđÁGŸ °°Ə-—\ČĘÎ�Tû|'N!**ööv˜˙é<yr�|ģuÁ´)“!“Éđ×ęäÛ7lÜŠT ŸÎŪxoÎÛ‹KšD$áŊ9oŖGw_äææbíē ՎãģīÄĩë7 §§‡ëūÁŦ™3äÉ�ĐÖŌBîžØ´a-úöîŠTŠų_}‚‚‚jŸãyáæ›J÷ihh@"‘”û Y_ĩuĩĘ×ĩĘŽĄąjŦ×ôĸ?Oꊉˆˆˆˆˆ¯:ŸävÖĖđėĐ÷<Ā7‹ŋ“o˙sõ߸{ĖûčÃrĮef–&X Ģ}Ž#GKzēŧ4l¨<QRֈ—üKĘ;ŠT �¸�4p€Ō:_W2˙ÅąãÕŠ!õņclÜŧ�0÷ũwŅÎŖm…eĩĩ´°xá ā‡9ŗgA…ũ7oFâũ?Fˇ}äsÉŒŸˆ;w—+ģyË688šbŪ§Ÿ#öū}Œ›đ:\=<ąā›Åx÷ũšhãÖ…EEČĘΖĪ_sęô�åį›˜=į}88šâa|<�`ˏ pprÅĶf(-_֕đpĖžķt.ߞ˜÷Ų|$&&•++“ɰeÛŒŸˆ^]āčŌŧ0rÔXlÜŧ2™L^ļĒk¨I›Uåyâ€Õ˙Ŧ…ƒ“+ĻŊ9Ki}k×mPØ_ŨkŌ‹!“ɰęĪÕč?hœŨÛÃŖƒ^}m2.^ēŦô\ĪĶÛvė„ƒ“+>š÷¤R)~˙ãO ė—ļāŅÁ ^Cá<˙…įΘ.]ž‚Yoŋ+Š§7FŒ ĀĒŋūF~~žJmKDDDDD—fÕETŖĄĄe?,Á˙Øŧeú÷ë‹æÍšáĮå+ ŠŠ‰e?,ŽŽNšãž&XĒ߃ĨtBڊ’ööv040@Vv6bbbŅĒ•ƒü<ļ6ÖJq°ˇ�Üš{EEEU~Ŗ}ėø ÂĐĀ�ŖGŊ\eĖømÅOåļ8xī}ø1ŠŠŠĐŽztäd‡ $4 §ÎœÁōĨßËI � ;'s۟‹‡qŅŠŖ'Ŧ­­āŲĄ=$ ļnßm--Lz}"� EķæJcōë×͚ÚbÃÆÍČÎÉÁKÆÂÚē ZˇjUéĩėØš}ōA@ûvđhÛwîÜŖ­ÛqāĀ!lßē NŽ­åå?úä3ėØšZZZčėíK ¤¤Ļ"(øŽ„‡#"âž]ô5� oŸŪU^ÃķļYUž'žš¨Î5€ŽŽ.>üč>z ]|:ÃÉÉ!Ąa8Œ°‹—°Ī…{ķÜĪŽ¤äŲÉÉÍÅ{~„Ā'ŅÅĮ-[ļĀ•đœ;„а‹8¸wėĢwŠÆú<)ŗaĶf|ūÅ�@‡öíĐĢG¤gd 4, ‹ŋû†ĩ˙@__¯FmKDDDDDXtt´PvîÚ#Ø;ēŪ]ģ ū#^ė]„_~ûŊÂō7mė]„9ī}(\Ŋv]X¸ø;aĘ´7…)Ķg '\š^îg÷ö‚ŊŖ‹pûΝ ë8Ä_°wtŽAĮ˛qĶĨå<ˆė]{G!öūũ*¯ķŖyŸ öŽ.¤)ĶĢ,[‘¸‡åײs×…}1ąąBĪ>ũ{GaĶæ­ōíûė]„.žŊ„q^ōōōŽ‹ŧu[°wt<:x•;ßæ-Û{Gaâ¤) Û}{öė]„Đ°*ËĮÄÆ Îîí'Wá|P°|ģT*žYô­`īč" <LžũÎŨģ‚ŊŖ‹āāä*„G\U¨˙Úõ‚ŖK[ÁŪŅEˆŧuģZ×P“6ĢLMâûëī5‚ŊŖ‹0uÆ[Jë\ŗv}šũ•]́C˙–ėķôņââĘ÷eddũ ė]„¯.VŠ<$Ø;ēí:v .<Œ—īËĖĘú,Ø;ē W­¸_„įIYLwīŪ“×søČQ…ōō[ĘļSMږˆˆˆˆˆ˛9•:"TjÄp :ÉÉ)¸zí::zvŒiS*,Ÿ™UŌŗäôŲŗđņ ū\ũŽŸ8‰ã'đįę0bT�æ}6_Ūíŋ¸¸X>‡‰AÅŊ^ôõõ”Ŧō�mKzģ>Ē|"ŨŖĮË˙?';§ĘëLHL�ØĢ0qđÚõQPP€ü0b¸ŋž–-ZāŖšī—”Û°Qž]ô¤7BBb">xoŽŌ^AuiÛö(((€˙°!čâĶYž],ãŊ9oŖYŗĻ‹Åxø°d˜ˆĄ!V,_ŠĨß ļî uššē Cûv�€‹—.Uëü5iŗĘÔv|ĒČĘƝŋBĶĻļōmFFFō댉•o¯Q;ˆD�€ĖĖL|ũå|ØÚØČw`ÔË#�7#oÕî…UBŨĪ“26mAqq1úûõCŋ~ ûŒŒŒđūģī��ļn߉âââ’ °m‰ˆˆˆˆ¨nÔųĄ˛<ÛˇĮžũ�MŦŦ ĄĄQaŲĖĖ’ešs˛s0kæ Œņlmm‘œœŒõ6áĪÕ˙`ËÖí°˛´ÄģīĖF~™ b+ÆŖ­­ �ČĪ+™+!`Ė(lØ´§ĪœÅĒŋūÆÔ7&ÉË;ˆī—.‡––ŠŠŠPüdŪ–Ę”Nä̧§[e؊�úöî­tĪDˆŒŧ…ŒŒ Ë÷Ŗ};ŸģĻOž�tíâSnŸŽŽ.NUØÖ¤‰† $˙9''?†TVŌÆĨ‰°Ō!\UQĨ͔ŠíøTabb Īí•Æ�ŠŠåÛTi###tôėPî˜Ō¤@VVŨ_k)u?Oʄ„†�z÷ėĄtŸ'įČÄŊ¨h´qr”īkHmKDDDDDuŖŪ,÷îEáģ~„D"Ąū{{÷PXŽĩŦ×'N€˙°Ą016VX§Š­->úđ}˜ššāÛ%Kąę¯ŋ1}ęĐ-ĶcŖ¨¨¨Â8 �:ē%åŨ\]đÎėˇđ͊_ąøģīąyë6ØÛĩDėũ¸w/ 3gLÃĻ-[‘––ƒ'Ō*SÚ{&+;ģęFŠ@ÜÇ�€Ũ{÷!čÂĨe´45QXT„˜˜X´+“Pą´°€čɡæõéa\IĖĪŽS™ģ÷îá§ŋáô™ŗ~ĀĒ91­*mVņŠĸyŗfJˇ—Îī!-“øSĨš5mĒ´ŧ†fI"ôŲI}ë’ēŸ'eJÛļY3åí¤¯¯SS¤ĨĨ#>ū‘B‚Ĩ!ĩ-ՍzI°aÎû"??Ÿ}ō1ZļlŠĶgbū—_Ŗŗ——BĨ”ĨĨ,--*Ŧķĩ ¯bÉːŸŸđˆĢčâĶ:::ČĪĪGVV6*˜ŗöéäšOW'zgö[prrÄęŋ×āÆÍH$'%ÃÕÕīž= ~ũúbåĒŋ��ææfU^kSےaˇoߊ˛lErsJzÁœ;TeŲŦg†-éTĒ ĨÃŗĘ.]™›7#1jėĢČÍͅs' 4 ļ66ōÄ×ß˙Ŧ}ŽÕUTiŗúˆOâJzz=K•vĐŌn8KĢûyR&?/�*~'Ņ.Izå(Ž&ԐږˆˆˆˆˆęFŊ$X–ū¸×o܄ˇW'LzmD"Fŧä]{öbîŧOąfõĒįŽS"‘ĀÆÆÆ##Ŗd8‘ŊŊnیÄÇņ Ģ‹”AžTlĢVŠ+v Đƒô/wĖ­Ûw •JaŨ¤ ŒŒŒĒŒËĶŗ=6lڌK—¯ ==&&•E€¤¤dXZ>íyĸ§¯‡Âô Ŧ^õ;zU0ĄĄŅÕĶCaF222ĒUūû—#77ú÷ï?//ˇ˛Īî=ûžëüĩŨfĩP’hŦkņŲQFŨĪSe1åååWX&īÉ2Íúzz*Ÿˆˆˆˆˆ—:Ÿäö|P0Vũõ7ôôôđũˇ‹äI„ųŸÍƒĨĨΜ=‡õ7•;N&“!11ŠÂá>RŠiié��33S�€‡ģ�āō•+Jšq3yyy066Žr9ÖR§OŸ�øtöŽVų~}û@__………Xõ×ę*ËįååaÔØņâ?QQŅ�€–-[€<Ô´h^2Œ%îÉЎgåįį#''W>ųįå+á�€ņcĮ*]6ųÖíÛĪuūÚnŗšÄ'—<Û2™ōšzâââj%ļĘ4ÆgGu?O•ÅôāÁĨûŗ˛˛ä ĄŠ†uŅ‹ĢN,ééxîĮ�€Īæ}„æÍŸ~č066Æĸ¯ŋ�,ūîÄŪŋ¯plŋCĐĨ{/ų¤¸ĪÚŗw?rss!‘Hä̆ 8�@ÉüōU<ĘØļ}�`đ ōDĪ…PĖzû],ųáĮrå °îIōgøKÃĒu͆˜ôÚD�ĀĘUáHĢĨC§æâÁƒ8ÉįvčúdՔŠŽŊ°°ģöėErrJĩb*ĢŽæ ņyķĄÃGĘí“JĨčÖŗÚvč„7#öI$ÚåĘ˙{ø(<(IF(‹WŲļējŗį‰¯t"Մ„¤rĮáÔéŗž§ļîK]>;Ęŧ¨Ī“Ō˜ž$YOœTēŋôūZYYÂÎŽeĩę$""""ĸG&X>ų|>“ĐŗGwŒUnß>Ŋ1â%äååáũ?V˜čqØĐÁ�€ —›O"đÄ)|ŗø[�%są”ΉāÛ­+Úēģ!.î!>›˙•ŒaũÆÍĐÖŌ´)“åÛMMMqđßÃøsõ?8õ¤ˇ P˛ĐģĖE\ÜCx{uBîžÕžîŲoŊ‰N=!“É0söĖ˙ōkDGĮČ÷—|Ø>ƒQcÆáčąã022ß'_áh\Āččč $4 ĢūTėSTT„ų_~÷?üŸÎ˙˛Ú1”Yžē´įOm0ZZZ ÃÖ'‰, ¤'Ō˛ŸV ---[´@Û'ŊŒ[ˇ� <ĄPĪÕk×ņÕ7 áíÕ �X­k¨í6ĢI|mœœ��7nŪDhØE…ķŊđ[¤+îRÛ÷Ĩ.že^ôįIiLcĮ@[K ĮOœÄŅcĮö%'§ā‡—(™ [M‘zÕŲ,[ļíĀŋ‡ÂČČß.üēÂrķ?›‡ŗįĪãŌå+Xšę/ŧ9}*�āÍéSvÁB0áõ7`kckë&ˆ{øIIÉ�€žŊ{áŊwߖ×%‹ąė‡%xõ5lŨž‡ƒcëVHJJÆũ ‰°č›hŲĸ…ü'ĮÖx÷ŲXöĶ Lš2ÎÎm`jb‚k×o ++ -[´ĀŠåå{ˇTFKK kV¯Â'Ÿ}=ûöcũÆMXŋqŒa ¯„ÄDųĘ/öv-ņûo+āØúéœ1M›ÚâûoâŨ>Ââ%?`÷žũpuqFnN.BÃ."%5-[´Ā‚/?¯vLÖÖM`aa””ø|­Đ߯/ÆŒyŽkĢHI<ķ1īĶĪņņ'ŸãŸ5ë`cc;wī!.î!tuuņũwO‡ˆMŸúÂ.^ÂĒ?W#2ōėíZ"*:įƒ‚ņŅīÁŌĘRūáZ*“bˍWāæęRá5Ôv›Õ$>ļîhߎŽ„‡cüÄIđęÔĻ&&ˆ¸v EEExoÎÛøâ̝zLÔö}Š‹gG™ũyĒ0ϝæcŪ§ķ1}ælx{u‚ŊŊR’S†ŦėlôíŨ S&ŋ^+m@DDDDDKô`‰‰Å‚o�žœ˙ŠŌU‚J•*´üį_y @ÉJëūų ‹žū ^:"+;áWQXXˆn]ģ`ŲŌ%øã÷_Ą­Ĩ¸:‡ƒƒ=îŨ‰‰¯ŽƒĄĄÂ#Ž";;ú÷Ãέ›0rÄKåb˜ũ֛Xą|):uôDBB.^ē sŧõætėÛŗŖŌՌ*ĸĢĢ‹eK—`įļM˜0~,Zˇj™TŠ„ÄD˜Ŗģo7ŦXžGíWHŽ”2xöíہÃũ‘‘žŊûāÄŠĶ077ÃĖͰkûX7iRíx444đã÷ßÁŪŪIIɸqãfĩWhŠŽ1Ŗ^Æö-1 ?¤¤¤âĖŲķČĪ/Āp˙aØˇk;:uô”—íÛ§7ž˙vœÛ8!øBöî?€ĸĸ"ŦümĻN™ŒÁƒbČāAĐĐĐĀŋ‡ ''§Ęk¨Í6ĢI|�đ×˙è—GÂÄÄĄaqņōetéÜ;ˇm†ũ“a#Ĩ+ä�us_jûŲQæŋđ<)3ú•—ącëF ĐŅ11Øąs7BÂ.ÂÅÅß-ūŋ˙ļĸÖہˆˆˆˆˆQtt´`gg§î8ˆˆˆˆˆˆˆˆ•˜˜”æTę|!"""""""ĸ,DDDDDDDD*b‚…ˆˆˆˆˆˆˆHEL°Ј """"""""1ÁBDDDDDDD¤"&XˆˆˆˆˆˆˆˆTÄ ‘Š˜`!"""""""R,DDDDDDDD*b‚…ˆˆˆˆˆˆˆHEL°Ј """"""""1ÁBDDDDDDD¤"ÍēĒ85-;÷D\|§Ĩ×Õi^(“_› îˆˆˆj…ļ–&tu$hfcm­:{Ũ¨ž“:˜™š ™­5Fú†šŠ‰ēÃ!"ĸzP'o<ŠiéønŲoäכŋTĒiöÜųč⿍î0ˆˆ¨—¯ßASkKu‡Ņ(ˆD"hhˆQXP„ČģąpnŨRmI–†øN2{î|ŦX˛@ŨaTĒ1ÄXĒĄÆšš–Ž Ą—đŨ˛ßđŅģ3ÄŗGDDuĢN†íØsƒüzŖw÷.üeBDDô#Š‹ĨĐÔŌ€•… â%Š-ž“ē˜›š`p˙>čåëƒ{Ē;""Ēu’`šŸNęĸj"""j$d2‰yųj‹ī$¤nŊ<ņđQ‚ēà "ĸzP' –ŧü|čęęÔEÕDDDԈH‹Ĩ(,*VÛųųNBęfnjšˆˆū#¸ŠÕ™ ¨;"""ĸzÁ ‘Š˜`!"""""""R,DDDDDDDD*b‚…ˆˆˆˆˆˆˆHEšę€ˆˆˆ¨!ų懟‘˜”ĸtߊ% đÍ÷?c攉035ŠįȞ’Éd¸u' ˇîÜCZF�ĖLŅĻu+8ĩļ‡XÜđŋC ŊŽu›wB(3˛ĩ•%>ũ`vŊĮ’Ÿ_€ /ã֝{xœ–�°ibw×6hįîMMMÄ'$bņŋBKS?.š_ī1QÃĮ Q%Wäû“SđĶīĢņΌÉjI˛Üž…­ģö#1š|œĮNž…M+Œ9 ­ė[Ö{lĪÃŽysč#++[ž-!)šŪãŊŽmģ /?_aûÃG ģScŧōŌXY˜�ŠŠÕˇė85l *Á˛ėˇ?ĄŠŠ‰ŲĶ^/ˇ/1)ßüđ3&ŋ:<ÜĒ]įG_.Fīî]1°oĪZŒTš­ģ÷ãÎŊ|úūŦ:?WŠ‹@pH˜üg ˜š˜Āŗƒ^  sŗz‹…ˆˆĘûé—˙áō•pųĪ062†ģģ †û…™ŠŠŖŖĘ,Yđ tut——š_,RØ÷8-]-I– ‹Ø´c¯B¯‰DššČÉÍ�<JLÂĪ+˙ÆĢŖGĀËŗ]ŊÅļ÷ĐQž>ŠTZi9‘H„ #áÕĄõ텭ģ÷×S„å>~ û�hjh }[7´hŪÅÅňŠšë‘ˇ‘–žUk6§SĩÅIDDCƒJ°PÍØX7Á;oM�ãūƒ8lÚļˇīÜÃ/?~ąX¤æ‰ˆūÛŦŦ,1éĩ ��iq1âãaˆŠŽÁ‚ųŸņßéjîüE•Žßū\‹Ī>|ģ^â‰ŧ}O!šbeiŽ€‘ūple�HJNŎŊqãÖČd2Ŧßē Ļ&Æhí`W/ņ?u2™ŦĘrf&ưkŪ7nŨÁÎ}‡ę!2å¯Ũ”'WZŲˇÄkc_Š‰ąB™Ä¤ŦŨŧ÷ãâvYaQ#ÂË @GGîŽōŸ=Û{@CC*ļf�� �IDAT˙[õ7%$ Š­Ŗ#""‰D—6NōŸŨŨ\!ÖĐĀú›‘””kë&jŒŽ*ĸŦKBbV,YPīąHĨRlŲõ4šbfj‚÷ߚ==]y+Ks˘ü*~˙{=nD–$Y6ī܋OŪ›U/s˛”&WĒĶ>7"ī`՚(–JĄŠĄ‘ÃáĐņ“ČÎΊë0�ÅR)ļī9� $šōÖÔ× ĨŠøZŸąXŒ×ƎŸk7áQbRŊÄFDDWŖN°|øųBôī͉É)¸~ķ áėÔãF ‡žžŌcŠ‹‹ą˙ßã¸~™YŲ062„—g; éßGūō‘•ƒ]û˙ÅíģQČÉÍƒŠ‰ztõA/_y=™Yظm7nߋ†ŽŽž>^õrÍÕĨĨĨ�ĐĶ{Ú§"`Ô\ēđˆkظæhkia͆-8}ö<ŌŌ3`fj‚>=ģãÕąŖ ĄĄņ“g`Č@?Œũ2� --ã'Ī@÷n>˜÷ÁyŨã'MĮ˙ĄxeÄ0\ģ‰5ë7#:62™ ˙gīŽÃĸĘŪ8€gčiĨUT”20ąģģģŨÕ]×XŨĩ×îîîînŒŸģv“* Š€€ ķûcddČÁôûyž‡šsîšīŊįĖܙwÎ=×ÎÆúö‚cåJ�Ä÷>†ˇî ėc8Œ ŅŠ}´iŲė;!""ÅRūōeNCãëä1c' ]›Vxáé//oŦ\ž*Ę*8züîŨ€čΟĄ¯§‹ÚĩjĸS‡vPRRÂīã'ĄqÃúčĐŽ � :*ŋO˜ˇÕđˈa’ē˙'Z4kŠÖ-›ÃĮ÷Ž? wīž.‚•Ĩēt戊ĘŋOŸ:{wī>@Dd$J•2@‹fMиaŅ_n[œä4‚eîŌ5Ų–}„Ë O„G|’<675Ēš*Â#"qøäY¤ĻĻĄW—024@c÷:đôö áíûËyŒ˛zéí‹-;÷K’+Ãö†C…ō¨h_AAß%†į/ŊũBĄ}ģwʖ\€ųËÖ~—XˆˆčĮQĸ,B%!ŽxÜFįv-ҧ[G„…G`ÍĻ8zęôęšã:ŸÁŗ^čŅš-Ŧ,ĘĀ˙m;””tn× �°÷Đq„~üˆŊģAWG¯ą˙ČI”2ĐƒĶ—$ÁŽGö1Ŗ÷…ŽŽnŪš‹'Ī=ĄĨ™sb§¨e\īœ’’Š7ū8rü7p—ęĒŦŦŒķ—ŽĸfĒčÕŊ3ÔÕÔ°zũfÜš{ŖG Aųreáíã‡5ļ )9Ã÷‡‹SxzųHęxūŌF††xáé-Yö>ø>EEÃÕʼn‰I˜9g!¸×Á˜_†"NŸģˆé˙ĖĮî­ëĄ­­…­;÷āüÅĢ=r(*U´Įã§Īąqë(++ĄEĶÆßī }G™ß§ƒ‚‚pūâ%ÔŠUzzē’2JJĘđ¸y.ΎhßļÔTÕ°s÷^<|üũûô‚­ ^ŋņĮÎŨûœ’‚Ū=ēÁĄRøŊz-ŠÃÛ×Ĩ JÁĮ÷•dYHH(ĸŖ?Ŗ˛C%$&&aÅĒĩ¨éVú÷D"\Ŋæå+WcŲâĐŌŌÄÁ#GqãÆmôīÛåĘÚáĨ—ö8%%%4p¯÷ũIxejO@œ¤X˛j#âđ)J|כ°đp@UU5ÛēÅ%ÁōŌË[veJŽ 'W�ĀØ¨ŒžĪÜqAī��•ėËå:_ŠŠ RRRr\NDD”“`�‹2æ¨ųeŌ1Sc#ÔĢí† W<ĐĢKrļqņņ¸÷đ :ļiĒΎ��#ÃR ũˆëˇūEûÖÍĄŦ¤„.í[C(°”xâAcCÜŧs^ž¯āTšĸĸ?Ã÷ÕtīØöåė��Ũ:´ˇīk(‚@ Úuí#ĩŦšĢ3†é/ĩL O„7¸ŋ¸ėį˜\š~CöEũzu�ˆįsyûî=Nœ>‡Aũ{ÃÕŲ6ī@zēBĄ�Ī^xĸQũē8}ū">„„ÂÜĖĪ_zAOWv6ÖzŒø„4nč+‹2�€‘Cĸ~ŊÚPQQF||Μŋ„î]:ĸIŖú�€ŌæfxõÚ‡Žžd‚…ˆ~HAAī0dįRËĢTF¯žŨ¤– €ĒĒ ēwí �ˆ‰Åí;˙Ągˇ.¨é&)ibbŒ÷>āŌ•kčÖĨ*;TÂŪ}‡$īĶŪ>~¨]ŗŽ\÷@XØG˜˜ÃÛ×ē:Ú°˛´@pHP§ļʘ‹/!íĶĢÜjT‡˛˛píú ´iŨuëˆGnššš 0gĪ_d‚EA"Ŗĸ˛-{˙!€x4Kīn`ce �¸˙čŠô矞¯Ģ/ŧ|°eפ}IŽ ĐKa‰ŸŽmZ c›y–Y6wÚwІˆˆ~%>ÁbYFz~sS¤ĻĻ"*:&ƆRĪŊAzz:l­-¤–[Y–ArJ >†GĀÜÔjjǏ|ũ&|_ų#6."‘qņ 01×úå‚ÖVe$uØXZ (øCQėfžĘ”6ĮŸŒ�¤‹Ō‰s¯`ô“°pÎ ˜gēļ?ķ�ūūâKx*ÚK¸ą/g‡¤¤$€‹SÄ'$ āí[ØŲXãųK/ Øž¯^ㅧĖÍLņâĨ\œ!`QÚeJ›cҞÕh͞9Ēē8ĄŦ +‹įˆyūŌ ŠŠi¨ę"}W§*¸xåĄĄŽ^T‡ŠˆH!ĖLM1bØ`�â÷é¨OQ¸~ãf˚‹ÉĮÁÄÄXRļ\Y;É˙AAīžžŽ˛vļRõŲŲX#9) ĄĄĄp¨T ‰ x÷ū=Ŧ,-āãë‡Ũ:ãM@�|üü`bb __?TĒT �æĻĻ035ÅÆÍÛШaTqp€ĩĩ%*~9?xûø!55 U¤ļYŠ‚=nŪēÄÄD¨ķ}úģKOå¸\]M ŋm--$''ãĖÅ̏õī=Š2"QūĪĩĖÉ%%% íß iiiØwøzwë¨čđˆˆˆ EąJ°ŠÛf––.Z­¤¤$ĩ\MMMúąĒxØfBbBļ:“’�ˆ?ŒäTGRRŌŌŌ°vķN¤§§Ŗk‡Ö051†P(ÄĻ{3Փ ûQ55é3ߋĒĒ*ʗûúåÕĢbč¨ßqāđqü1f¤äŠĖs˛Ä'$|Yöõúāë|� ‰°ļ˛D™ŌæxééRúx ‡Jāí㇗žŪhÖ¸!^xzĄwņ%YBĄKæÍÂáã§páōU먺ÆF†čß§š4Ŧ/Ųæäiŗ Ā×ģf¤i÷OŸĸ anVx‡ˆ¨PQU­õ×“#&˙= §ĪžĮA_GĒgš“%11ņË2é„FF‚#)1 eĘĀĖÔž~¯ §§‹Đ”/_¯ßøÃĪīÜëց¯ŸÚˇk @ü>ũ÷¤ 8wánÜē#ĮN°”:wę€ēĩk!áË6.Y–ãûttôįŸ*Á" Qŗš î>|"ĶrŠŠ~ĻKÉ2316‚ššRRSąiį~øøeMĢ—ËēßËsOlŨ9šŌ•+ŲcüÔ9HNNVH‚eÜ”Ųš^ū“Ûȕõ[wÃĶĮÍ×Gģ–M‹:D""*ŠU‚E[[ ÁBs|."R<ą›žŽŽÔō¤Ä$ŠĮ‰_kjH' €¯H3-Y×QWWGĀÛw ÅØQCPÖöë‡áØØ8É%CĒIœ„DŠzâŗ<V$eeXZ”A@āÛ\Ëd$[âãĨ“QņņņâįŋLėâTžŪ>Đ×̓ĩ´45QŲĄ"ÖoŪŽáû×/—\ârCöÅЁ}ņ6讝<ƒĨ+×ÁĘŌB2GÍÄąc`cm™-&c#Ãlˈˆ~D*ĘĘ077Įģwīs-“‘lIĖr~Iø’ŦV˙’ Θ‡EWGe ŠĄ ûōå°gßADDF"<"•*JÖ×ÕÕAĪî]Đŗ{ŧÆÅKW°yë”67—œ?‡ ‹ŌŲb*õå\øŗ¨æâˆŪŨ:"5--ÛĨ7ßSy;ÜÍá6ÁoßŊĮŦË!‰đ9&6ĮuíËÚæ¸ü{9xė”TrĨJĨ �€ääd…Å”Sr%¯åa#$ķād\ŠEDD”UŅßŗ¯�*”ĮĮđˆls™ˆD"\ēvúzēŲ. ō{ õ8đŨ{¨Ē¨@?ĶäŽ,ĖÍ  ņ:@:éāøęęj012Djj*�@+͍˙Ā D|ŠBÆāSc#�âKŽ2¤ĨĨÁīÁv¸Ĩ¤¤ đm s™¸ 1 …đôö‘ZîåãMM ”ų2’ÄÕŲž^>xūŌUēüVŦPBBpķöX”)-IŒ„„†áß{$uYYZ`ôČa |;k¨(+#*:–e$ē::ĐĶĶåÄqDôĶHIIÁû÷ÁĐ7ĐĪĩŒ•…„BĄÔ$ļ�đęĩ?44Ôafb�¨ėP ¯^Ŋ‚¯/*”_öYļŦ-Â>†áŪũ073ƒa)ņų ėc8=ųš((Sē4ú÷íĄP€÷ÁÁ°˛,ee|މAissɟļ–6ttt~ē÷éUáéã‡ö­šaÖ_ãĐ n­üW*Ύ9N¤oce‰YÃŦŋÆÁ"Ëg$@üÃTå/ EéØĻôtu0¤ß×äJqRÚĖĶ&ūŽ)ãGã¯qŋf{>%%ģ…H$‚ž*“ ƒ‰ˆ¨ø)V#XjVsÁģąe÷~4v¯K‹ŌˆÃ˙î>ĀÛwÁ6 —äVĘĸ?ÆšK×āVÍĄaqëß{¨æâ˜ãíö455PĢFU\ēvƆ†°,cŋ×ū¸yįš6¨ ĄPˆ2ĨÍ ŦŦ Û˙ĄUŗF ÅŠķ—QŅž,Â>†#&6Ĩ ôace‰K×oÂȨt´ĩāqû?(gš|é{ILLÄŗž�ÄɨččhœģxŸ?Į {—܇ŨęčhŖyĶF8xôĖÍÍPÖÖĪ_xâĖš‹čŌŠär,'ĮʈˆŒÄ÷bøā~�Ä#„l­­qúÜEÔŦQMRįĮđpĖ]¸ƒû÷[õǏ~ã6!*U°‡ĻĻZ6o‚=COWöåË!ėc86nŨ #ÃR˜5uRŅ(""IJJ‚—/�ņûtĖįĪđ¸q ąąąhĶēeŽëikkÁŊ^]œ9&Ļ&â9V|üpíēZļl&yŸŽXą>EEáņ“įčÕŖ �@C]–¸zÍ.ÎN’:###ąfŨtīŌÎΎ@€īۃ@ D9;;hhh Aũz8qę4t´ĩ`kk‹ˆČHė;pĨ ôņĮoŖ‹đH/Ķ˙‹OŅŅØžį &ū6ģE÷NmŅĩCk| Ä?‹V|ˇXÔÕÔĐĄMsė;|Bjš‘Ą¤–2Āģ÷ŌsÁulÛĒ NŠUwuBuW§ü *HpH(v8Šū=ģĀ,Ëü}Ÿĸ°cīaÉ]‡ēvh“íŗ(Q†b•`QRRÂo#âÂÕxōÜWnüĘJJ°ĩąÄØQC`gc•m:nÕŸˆÅĢ7 %%ŽĐ­c›\ˇŅ­c¨ĢŠâĐņ͈‰ƒž.Z6i€fÜ�ÚZZčÛŊNŋŒ{ŸĀʲ úõ茍čĪØžįVm܎)ãGc`ŸnØwø6mß uuuÔĢ]nUņäKĸã{úŠÉĶū‘<ÖĶÕAųre1ötT´/—įēŖ† ‚φ:Ön؊¨čhĸg÷ÎčŪšƒ¤Œļ–ĘÚŲÂīÕTū2‚I?}îĸÔåAŽ•đĮ˜Q8vō,vīßŌĶĘĘĶ&G™Ōâ_ֆîm--lŨš‘Ÿĸ` ¯ZnÕ0°o¯Â:$DDÅJXØG,\ŧLōXWG666øsÂ(g—÷å}{÷€†ēvíŪĪ1ŸQĒ”Úļmļ­žŪEKSÖVVđ„}ų¯ŋŽ—/WWŽyHŊwWŦ`!ƒāÂĨ+8~ō4„B%”.cŽ1ŋŒ€Ų—IŅ{õčMMM:r QҟĄ§ĢWGtíüsMFš92cž¸ũæ/[̍pPģFU„„†áÚÍ;’eŸ<‡ēēDé"<}.ũ¤E“¨îR|ÅAšÕqįîŊÜ%ĢPĨ’=ėlŦĄŦŦ„€ˇīđäš§äëm[6…SåŠųÔHDD?3ŋŋŋČÆÆĻP+ķįtŦ^ôOūå4iæ|4r¯ƒ–MųļŠÚ÷:fDDômŋôC3ãü R6īC>Âĩ˛b.Ģø–ķë˜?§¨üˇÔ/Ī9˙Öŋ÷pęÜålsĘeĐPWG§v-QģFÕoŪÆˇÄøû䙞XII +æĪ(Đ:YÉĶÆS&ŒAØĮ;ušÜÎZC]Ũ:ĩA WįŸ/ljˆ¨d@FNĨX`!"""R4S#„†…Ë\ö{s¯íW§*¸˙č)|ü^ãST4ôõuQą|9¸UsÎq˛˙ĸÖ¤A]\Ŋņ?™“,BĄë×)â¨rĻŦŦŒÔÔT¤§§ÃŠrE8T,ž>xúÜĄі–†Rú¨h_5ĢšB]]-˙J‰ˆč§Į Q&S'üĻčōĨ­Ĩ‰FîĩŅČŊļĸC‘hßĒÚˇjĻč0d˛|žô(%e%%¸8:ĀÅŅAAŅ D'XÎüKŅ!¯Û4•DL°ɉ """"""""91ÁBDDDDDDD$'&XˆˆˆˆˆˆˆˆäÄ Ą@ čˆˆˆˆž‹"I°¨ĢĢ!!!ą(Ē&""ĸDIY Ē*Ę Û>?“ĸE|Š‚žžĸà "ĸī H,åílqũ֝ĸ¨šˆˆˆJĄP€¤¤$h¨Ģ),~&!Eģ{˙,J›+: ""úŠ$ÁŌĨCk\ŋũ/Î_žŽˆOQEą """*Ļ”•”‘š–ŽĐđ(X˜›(,~&!E‰ø…ķ—¯ãúíŅĨCkE‡CDD߁Āßß_dccSčG|ŠÂŅ“įđ.ø>EEzũ?ĸÁú):""ĸBĄĒĸ u5X˜›(ô!€ŸIH1 ôõ`QÚ]:´†ĄžĸÃ!"ĸ"€ŒœJ‘%Xˆˆˆˆˆˆˆˆ~d™,ŧ‹‘œ˜`!"""""""’,DDDDDDDDrb‚…ˆˆˆˆˆˆˆHNL°ɉ """"""""91ÁBDDDDDDD$'&XˆˆˆˆˆˆˆˆäÄ ‘œ˜`!"""""""’,DDDDDDDDrb‚…ˆˆˆˆˆˆˆHNL°ɉ """"""""91ÁBDDDDDDD$'åĸ¨Ô?čCQTKDDDDTėŲZš+:""R€"I°đ¤BDDDDDDD?^"DDDDDDDD$'&XˆˆˆˆˆˆˆˆäÄ ‘œ˜`!"""""""’,DDDDDDDDrb‚…ˆˆˆˆˆˆˆHNL°ɉ """"""""91ÁBDDDDDDD$'&XˆˆˆˆˆˆˆˆäÄ ‘œ˜`!"""""""’,DDDDDDDDrb‚…ˆˆˆˆˆˆˆHNL°ɉ """"""""9›KUˇÚ°ŗwĀ‘cĮs-ŗpņRØŲ;`ĖØņß-.÷†Magī€K—¯��’““1vÜD¸T¯…JŽŽ¸pņrļ2$M–ļĨ’!Ŗ-sûë;`p‘l—¯1*j|ŸĘ™"ÎģDDDD%UąI°Á>ĀÎŪÛvė’,ĢSģ5Ŧccc�ĀŠ3gqęĖYˆD"ü>æW”-k›­ ŅÎ°T)XYZfû355)’íņ5F?šœÎ7DDDDT˛)+:€âäėŲķŲ–-œ?GęqHH(� ZUWŒ>4Į2D?ēIŽG×ΝžÛöøŖMNį""""*؊í–˜˜Œ›0 U\ĒŖZÍ:X°h)ŌEĸlåŌŌŌ°nÃ&´lĶ•]áŪ°)6mŲ*UĻzÍē°ŗwƒ‡0eúL8WĢ 'טŋh ŌŌŌ��í;uÅüEK��sæ-€ŊâââĨ.M0x–­X�đ¸qvö8~ōTļË ĶÍ[ˇŅŠ[O8ēV/ôcX\}üŽŋĻLƒ{ÃύXŲõ4Áėš P 2—ĨxzyI–å4œíņũí?pvö¨[ŋ1âããˆą¯ė\ vö¸ũŋ;�€j5ëH^;cƎ‡“k 8W̉9ķH^›@öK„rk/YÚZ–žE”›„„,Z˛ šļD%GW4iŪ ›ˇn‡(Ķų)ŋ>–Ûų&+Yßãdé͞ŧ6d=īQΊm‚eÆŦ98qę4”””вEsÜūß?q*[šy cɲˆÃ°!ƒ ŖŖ‹–bķ–m’2ĒĒĒ�€™˙ĖEXhš6i„ظ8lŪ˛ ĮNœ�´o׿ff��ˇÕ1°?¨¨ĒHmĢEķĻpuq�XX”ÁĀūũPŽlYšbZ¸dbbbāęâ"Īá*Q~ķ;>ŠōåËĄ_ßŪ033Åöģ0qō”•‘ÛãûëÕŗ;ÜëÕҐŦXĩ�0kö<$$$ w΍Wˇ�@MU �0eÚLhkib@˙žHJJÂļģ°}įî\ëĪ­ŊdiëÂė[ôķ™:c6lÚM-M ėßᑘŋp1<,)“_“å|S˛ôiY^˛žw‰ˆˆˆ(gÅōĄČOŸpæœxøôâsŅŧYS$§¤ qĶ–Rå""#ąkĪ^�ĀĘåKP­Ē+úõéē cŨÆÍ<h�””” ¤¤�055Ææë��éié8yú Ž{Ü@ˇ.1tđ@xܸ‰!!hŪŦ)ėŸ-ŽŪ={ 2ō?yŠōåĘbúÔŋ˛•)hLš8yô”•‹eSē„„<|ôĘĘĘØ°n5TUTššŠ•Ģ×JæīĨŒŦØEcáĸĨXŗvCļå+–-‚‹ŗ8 špū´hŨÛwž>._š KK ü5iĸ¤|Æq¯UĢ&æĪ @œ<Yžr5ö<„Ąƒæ¸ũœÚK–ļNNN.´žE?Ÿ¤¤$xûøĸ|šrX8oĒTv€ēē:VŽ^‹ —.ŖWĪî2ŊÉrž‘•,ۓåĩũųŗLį]""""Ę]ąüųęÕk¤ĻĻ�ęģ×�¨Ē¨ i“FØĩgŸ¤Ü“§Ī––@�S| �XZ”@ ^Ŋ~ƒ öå%å[4o&ųßŅą Nž>ƒ°°…{Acj×ļÍOõe^CCeJ—Æûā`´jĶM›4†[ę9|´´4e.#+ļG҈ˆŒDDddļ剉I’˙ÍLM1cęߘ0é/,Yļ�‹ĖËą ›6i$ųß­†ør˙�$§¤@U%÷_ö3ˇ—Ŧm]X}‹~>jjj8{ō� ==III066�„†Šįį*Ė÷/YȲ=Y^ŅŅŅ2w‰ˆˆˆ(wÅō›ä§OQ��¨ĢĢK–ëęęJ•ûüų3�@$Ą~ãfČ*44TęËŗžžžä55ņ%iéé…ø7Ĕņáüg˛võ Lž2 ŪŪ>Øŧu;6oŨmmLŸú7ētî(sY°=ŠÆĸsešäļUËæ˜5{.bbcaccęÕĒæXNWįëk[KKKōtTtžm’ų9YÛē°úũœö8„-Ûw 0đ-Ō3?2OUōŊûX~ۓåĩ‘ ˙ķ.åŽX&X ô�)))HHH€††� <"BǜžŽ8aĸ¤¤„ ëVģ‚Ŋ}Gš]AcRÛipŠŒ“cœ;u¸w˙._š†Ģ׎cŌßSQˇnm˜™šĘT&CRR˛ä˙ČČORÛb{(ÖĸĨË uuuøû`ëö6dPļr‘™FÃdü/ ¯¯—­lf™ÛKÖļ.Hß"ĘėēĮML™>Ē**˜ûĪL”+W׎{`ũÆÍRå ģåõ'Ëödymž} ˙ķ.åŽX~›,[ÖN2ĮÂĩë�€Ø¸8\ŧtYǜ“S())!-- ĨÍĖФQC4p¯'ūĩN$‚ŽŽvļ+�@rדoQØ1ũhߞŚupđĐØX[Ŗ{×.Øŧa-lmŦ‘žžŽ™Ę�€‘Ą!�āÅKO�@bb"nÜē%ĩ=ļ‡âÜđģvī…‘Ą!íßUUU,[ą ūūŲʞ>{NōÆkŪž|9¨äqyPV˛´ĩŦ}‹('OŸ=�”-W=ēwEĩĒŽøđAÜgŌŌÅwŊ’ĩÉrž‘å=N–íÉōڐõŧKDDDDš+–#X K•BŗĻqáâeLž2 ×oÜē§Ī ĨĨ…OŸĸ$ˇÃ424DīžŨą{ī~ :͛6…—ˇ7=~‚ĒŽ.hÔ°Aļ›1!āŽģņîŨ{Œ˙ã÷Į^Ø1ũh´´´°aĶ$&&âۃ051Á˙�øÂĘŌ•*VDLllže� aƒú8rė8/Y†7ūūxđā! ĨæÕa{Ü&š€ķgN@ āĪÉS ‰đפ‰¨RŲ#‡ÅĒ5ë0qōß8´„™FŸŧzõ}ú‚žžÎ_ŧ�čߡOb’Ĩ­eéôsË­o/Z0åˉīįã㋚ķ"8øƒd„G`ā[,Yļô“ŠåtžÉz9œ,īq˛ôi55ĩ|_ššš2w‰ˆˆˆ(wÅréL:�� �IDAT �ü3s:7j€””TÜŧy­[ļ@īž=��ÉÉ_‡KOŸú7~ķ+TUUqāĐaø O¯žØēiŊÔ8Y 2åʖÅį˜üīÎŋR××DaÆôŖ124āŊģā^¯.Ž^ķĀ–m;đôŲ3tęØ{vnƒšššLe�āīÉĸEķĻ8á"ÚĩmŨē(ē>Bb‘‘x”ã_zē‹—Ž@āÛˇpĢQ:ļ�Œ1 ––xôø ļíØ%U߸ąŋÁČČ×o܄>ƍũ Ŋzv/p\ųĩĩŦ}‹~^šõ턄´nÕƒö‡žž.: eelZŋũúô‚’Pˆ“§ĪČÜĮd9ßČō'ëödy”õŧKDDDD9øûû‹lllũ„Ü6Åûā`lXģ ͛5Ut8DDDDDD€Œœ ž'"""""""’,DDDDDDDDr*–“ÜŅĪá–ĮE‡@DDDDDT(8‚…ˆˆˆˆˆˆˆHNL°ɉ """"""""91ÁBDDDDDDD$'&XˆˆˆˆˆˆˆˆäÄ ‘œ˜`!"""""""’,DDDDDDDDrb‚…ˆˆˆˆˆˆˆHNL°ɉ """"""""91ÁBDDDDDDD$'&XˆˆˆˆˆˆˆˆäÄ ‘œ˜`!"""""""’“rQTęôĄ(Ē%""""*öl-Í)�G°ÉŠHF°ékEĩDDDDDDDDÅG°ɉ """"""""91ÁBDDDDDDD$'&XˆˆˆˆˆˆˆˆäÄ ‘œ˜`!"""""""’,DDDDDDDDrb‚…ˆˆˆˆˆˆˆHNL°ɉ """"""""91ÁBDDDDDDD$'eE@DDDDE#ā]0ö>ޏø¤§‹N‰! ĨЁ~=:Ã˛ŒšĸÃ!"ĸ‚#Xˆˆˆˆ~@ī‚ąiû^ÄÄÆ3šR@"‘ąąqذm/>„~Tt8DDTB0ÁBDDDôÚwč„ĸC(ŲˆDéØš˙¨ĸ#!"ĸ‚ """ĸPl\œĸC(ųÄÄÆ*: ""*!˜`!""""ʅHÄË̈ˆH6Å&Áōû¸‰pŽVSōįV§>:víÅK—ãCHHŽëˆD"œ9{ƒ‡DŊMPŗntčŌkÖm@TT´Ü1ÕoÜ ›ļlËŗĖü…‹Ņš[¯\Ÿ/č~ĩíĐERöŌ•ĢЎUێ’u¯]÷(ĐēE­ m•qĖ.]ž’­žđđ8W̉ûzŒ˛´waoŖ$ļwZZÖŦÛ�įj5ąg߁lĪ;NĒĪgü͞ˇ@ǜH$BŖĻ-qõš�`߁ƒhĶĄ3Ē×LJöģáĖŲķRåĶĶĶąc×n´hŨÕkÕCˇž}pķÖ˙˛m?sŊiiiØĩg:u퉚u }įnØžs7ŌŌŌ¤ÖÉoۅUONÎ]¸ˆĩŨqåęõ|ËfŽ'¯6Č,11­ÛwBŗVmåŽ711kÖm@Û]PŗntėÚÛwîFjjĒLqį‹ŦĮˇ¨Ér\}||1dø(¸ÕŠÆÍ[cņŌåHII‘*S’úļ,û-k|Yąo‹—/_š-Ût@õZõĐĒmGlŨžSæz‰ˆˆ¨d)Vw˛´°ĀŒi�áíãƒã'OáäŠ3Xš| ĒUu•*?eÚLœ=-š7Cˇ.ĄĒNJ/_âĀÁøtų ļmŪ##C™ˇŋ˙āaŧôôĜY3ē_ƒôÃāũĄĄĄQ í:°ņņņhŪĒ]ĄÅ^X ÚVBĄËWŽA}÷zPWWW`äE¯$ĩ÷Įá˜ô÷TD~ú%%ĨËÄÅÅŖA}wôë#x416–zėë뇨čhÔ¨^GŽĮŌåĢ0æ×‘pŦR÷î?Ā”é3Ą­­…† ę�6lڂí;vaĖč_āXĨ2>‚ąã'b×ö-¨RŲ!Įz׎߈]{öá×Q#āXĨ2>zŒ•Ģ×B(`@˙ž� Ķļ Ģžœ´nŲ–˜>s6ėėl`gk+wdļ~ㄆ†ĄT)šë]¸dnÜŧYͧÂÖÆĪ_ŧÄĖŲs‘””„‘Ç~s,˛ßĸ&Ëū ÁБŋĸ^Úظn5ŪŋƂEK ŦŦ‚?~-)W’úļ,û-k|Yąo3ū™ƒ{÷āˇ1ŋĀĘŌ?Áęĩ둚šŠÆä[/•,Å*ÁĸĄŠÕĢI×w¯‹>Ŋzâ×߯büÄÉ8sę´ĩ´��'NÆŲķ0íīÉčÚĨ“d&ĸ]ÛÖč;`0Ön؈S˙–yû^^Ū…ļ/™dŋ�@MM ēēēŪŽŽļ6„‚b3(Iâ[ÚĒA}w<xđ;ví‘éÃmIV’ÚûÜų (e`€Õ+–Ąa“æ9–‰‡CĨŠR}>'˙ŨŊ‡JĄŖŖƒ­ÛwĸGˇŽØŋ� ZUWŧņĀæ­ÛҰA}$''cĮŽ=8 ú÷í �pvr„¯ī+ėØšKÍĪV¯††ö<ŒžŊ{bĐ�qŊÕĢU…Ÿß+\ŧ|ú÷…H$ĘwÛ)))…RO^ĢTÆö-•_ČÔ|ũ^a߁ƒhßļ5nßųWŽzĶĶĶqūÂ% 4�îõę��,,Ęāß˙îâÜų ųžNs‹E–ãû=Čr\wėÜ K‹2˜7g\]œahh˜m4BIéÛ˛ėwAâËÉĪܡ?ūŒ˙Ũų“&ŒCģļ­ˆÛĀĮĮW¯{0ÁBDDô*~߯ŗĐŌŌÄôŠáSTNŸ>+YžīĀ!TŠė õ…=ƒ­-ļnڀ?Į˙!Y–šššë_zz:‰“§Īāô™spŽVŪ>>Ųę ûˆ_ƌEÚîhÜŦÖoÜ\čûõ#*h[€ļļ† Œí;w#$44Īúķk[@ÜvŖ˙nuęŖI‹ÖØąk7ÖŦۀŽ]{äYīú›Ņžs7Ô¨íŽvģâāá#’į›´h›ˇJg\ž4qōŠz7oīGĐĸE3,Y4ZZšš–‰ƒĻfîĪgø÷î=ÔĒ醷oƒüá5”NB4¨_/^z"&6oƒŪ!))I*i# ҤqCüwī~Žõ*))áĀŪ’/ ĖĖLũų3�Č´íÂĒ''‘‘˜2m&šļlƒf­ÚbĖØņØwā`žĮM–6�Ä_˙™3ŨģvFŲ˛eŗ=ŋbÕ¸Ö¨-sŊ�"‘**Ōyy555@ ČĩŪüb‘åø~˛×Ģ×=ĐēUK2íoíZn’/åJJߖeŋ _fėÛ€ŽŽ.n{\‘$W2()+AY†:DDDTōû ūnme‰‡�bbbāãã‹ÚĩjæēNĨŠ$—\<{ūÕjÖÍõoņŌXšl1*UDËÍāqõ"ʗ+—­ÎŠ3fáõë7XŗrļlZ‡¨¨(\.Āĩåųí׏¨ m•!=-Ŋ{v‡Š‰1VŦZ“ë瞴-�˚3Ū>žXšl1Ö¯^‰‡ãÂÅËyŽ�Yļb5vėڃaƒáČÁŊčס/]c'N�jÖ¨'OŸIĘ?xøfĻĻxôøk{žEDDjÕtËû@•fĻĻų–‰Ī÷r§ääd<~ō5Ũj āí[�â_Œ3ŗ´°� ūĸ˜š*žãBEEEnjž>bbb$ķødŽW(ÂÚĘ zzz’ōŠŠŠøīî=¸8;€LÛ.Ŧzr2cÖl<}ö įÍÁ‘{1xP,^ē"Ī9udi�8täÂÂÂđËČ9>ogk ÷zueŽW  K§Ž8|ô8^Ŋ~�xéé…ËWŽĸ[—Κ֛_,˛ßī!ŋũŠŠÆĮáĐ××Ã_SĻŖ~ãfhÖĒ-ÖoÜ,5īIIęÛ˛ėˇŦņeÅž--11áá8|ôŽ{ÜDŋ>Ŋķ,ODDD%SąēD(/æææˆ� )��eʔ–iŨreí°w×ö\Ÿ74,•” ĸĸ}ũleBÃÂp÷Ū}ü5i"jēÕ��LūsūũīnAwEJæũú´­2ˆMTÁøqcņÛØņčŅ­+\]œŗ•“Ĩm#""đŋ;˙bōŸ$‰žųsgŖe›ŲæÉ‹CGŽbČ ’_­­ŦāååmÛwĸsĮö¨UĶ /Ezz:„B!<|„V-›ãāáŖx+KqōĖ@_ėí ´˙%Y\|<^žôDŸƒņæ? K•BķfM0|č`É|:Ÿ<…H$‚‹ŗ._Ŋ�R—ɐŒ‚‰‹‹ƒƒC%…Bxy{Kõŋׯˆ“:úúzRõæd՚uxüK/�Ä~ų?¯me=Į˙%Ą’äK°ĩĩ:‚˙쇯渎,>~ Įšĩë1kÆ´\ĩoߎ ÚˇkS zĮ˙ņ>}ú„.Ũ{AYYŠŠŠčߡˇÔ|;Yë•%–Ŧ˛ßâ ã—ÕkÖŖ[×ÎčĶģ'ž>{†ĢÖ"55c~ ä÷íŦ,,,dŠ/+ömiŋŒ‹‡CWW3§OAĢ–y_EßĻiÃzhßĒ™d”™H$Šķ—qÅãļ‚##"ĸŸE‰I°¤ĻĻJ&§Ë8qĒ(ĢäĩŠ„ĻĻfžņÉâ͛��ĒG  JåĘđöņũæz3ī׏¨ m•U÷z¨[§6.^Š}ģwd{^–ļ}ôø D"\3}1ŅÖŌB-ˇxãã:žž~HIIÉ6ōĻZĩĒ8vââââáæVąqqđķ{… ėņāŅ#Œû;^zzáņ“§’K­ZnR—üČŌĶĶĄĸĸ‚ĄĄĐ¯LŒņøÉSlØ´>„`ūÜ�ˆį’¨ęęUUU™ęÕÖŌBĢÍąeÛNTŦP•*âĘĩëđđ¸ �PVVΡŪĢÖ`˙CXļdŦ­,ŋy Ģ�ĐŌÔÂÖí;ņāáCD~úQēҟ?ÃĘĘJŽz.^ WWg4iÜPŽz˛ZŊv=îŪ€sgÃÎÖŪžžXļb 0x`˙B‰Ĩ0oaJų2Ί{Ŋē:x �ņų ""{öĀ/#‡CIIŠD÷myâˊ}[Úä?' <<÷î?Āô™ŗƒŨēę>üŒÎú šyŒ˜čĐē9:´ūšĐŠO¤™yĪDDDô­JL‚%đí[ÉČ#c#~-‹ŧn‰( !æ}ĩT||<�dģŖ,sMä%ķ~ũˆžĨ­˛š0îwtíŅ'NAũ,ÃŗüÛ6:Z<„]3Ë/Œz9üęš!ö˯ģCGü‚ĖŠ‘t‘�k++X[[áŅ“§026B`ā[¸8;âŲs'<zôÚĩÅŖĮO0bøĪ3‘ĄP(ÄméÛkģ8;A$aåęĩ˜4q<ôõõp÷Ū}4oÖ� Ģ#žā7&6:::’õbbb�@˛ėĪ ãđ×Ôé4T<ßÉą † „EK–COO\Gæz3¤§§cöÜų¸xų ÖŦZ.õz“uۅYO†””ŒũRSĶđį„q°ĩĩ†˛’2~7!Įc+Ģ[ˇīāÎ˙áȁ}rՓUđ‡ØąkæÎž)ųõŊB{ÄĮÅc؊UčŅ­kļ_ņ K^Įˇ8Đúō^_ŠbŠåŽ..Øē}'ŪÃĘŌ˛DöíüČ_fėÛŲŲ—/ûōåP§v-hjjbéōUh×Ļĩܟ!Š’˛˛2ÜkģĄēĢL Ļ–sŌ099!aáxđønũ{¯øß‚úįøŊƒˆˆ¤D$X=~‚Ã%Ŗ ´ĩ´PŠbœ>sÆ ĘņŊKWŽBMU ęģãŲķč70÷/šŊ{ö¤‰ãōŒACCœX‰Í2YeLlLAwG"ë~ũˆ ÚV9ąŗĩE÷n]°fízT¯&}KkYÚļ^]ņ¤„‰‰IRĪ}ŽÎ}͌aõķfĪĖq>s33�@-77<}ö †Ĩ Pž|9čččĀÕÅ -Á‡øPėž(*‚}yņ1 ũ2aą§—7ĻMų �`m-ūÅũíÛ ”67—ŦøBĄ6Öâ_ŧõõõ°~ÍJ„†…�LML°vũFX[[AMM QQŅRõfX°h)Ž^÷ĀĻõkŗv’uۅYO†į/^Â×īļmŪ uĢöOQQ(SĻLļō˛ētå âââŅĻÃך#D"‘xWÚ˜8~,z÷Ė}rįÜŊƒH$BY;é[íZZZ 9%ĄaĄŲnÃ[Xō:žÅŠŠ TUUņ)ËŨpŌŌÄ_&UTT˛õÁ’Ōˇķ“_|Yąo‹ciŌ¸î߈F H%h*T°GRRBCÃ`kkSāxŋ}=]ŒŌĨÍōŸGUUVĨaeQĩj¸bũÖŨˆĘãüZ˜&ÍāH""*^Š}‚%::sį/DéŌæhŪŦŠdyŸŪ=1eÚLlÜŧUrí{†W¯ß`öÜhÚ¸!ÔwGųreqpߎ\ˇa`` ų_ôe„BV6ÖÖ��o_É5đ)))¸˙āôõr QĐũú¤­r3jøPœ;ģvK˙Z(KÛ&''�^ŧ|‰ōåÄwyˆ‹Ã÷îÃØČ(ĮõėíËCUE‘ŸĐÜÖF˛<ōĶ'BIĸ¨Ļ[ ,^ēzēē¨ęę�prrDĐģ÷¸té llŦ%ɘŸA@@ VŽY‡_G@š˛v’åĪžŋ€P(„ĨĨn˙īôôtQą‚x^+KKXYZâēĮ ŠÉ€¯{x zĩĒ’ sĪ_¸KK ÉÉÔÔTœ=͛ŠÕŋw˙žTŊ�púĖ9œ8uÛ6oČņKģŦÛ.Ŧz2KJ÷ËĖķW<}öīßŖ˛Ãˇ'F˙2ũûö‘ZvöÜyœ:}ׯÉĩĪįĮėK?ö÷DÅ _GqŠG§™˜˜|s,ųßâ@II ĩkēáēĮ É%B�p˙ÁCčęęÂĖÔ—¯\-q}[ųŗûļ8–÷ÁÁ˜2}&æÎž‰ļ­[IĘyyyC ĀÜŧxž”••ķMŽ„~ ĮÉŗ—āķę ĀžŦ:´iŽŌfĻ5¤¯ÚøŨF˛,Ÿ7=×KÕ˛JIMŸŋ˙)∈ˆčgVŦ, ņ ¸˙ā!�qōÂĪīö8„ø„Ŧ[ŊĒ™îbĐļu+<|ø[ļ퀗ˇZ6o M xyyãāĄ#°ĩĩŸąŋ�444¤>4åFOW>>žđöņ™ŠôŸŌĨÍáäXÛļī„•ĨJ•2ĀŪũĨb€ÃGáÄŠ3Øĩmŗdn•‚ėWVÁų ąsÛf™GiĢÜčééᗑ#°xérŠå˛ļmĨаeÛØŲÚBWG+V¯…ĄaŠ\ËëhkŖsįŽXŋq ôõQĨ˛>„„`ŅŌå051ƚ•â8jT¯ŠĐ°0xܸ…ņã~ ũb_ž<:‚úõëåÅŊŊ=Ŋŧ%“cĻ‹D ’ôm'Į*(]Ú~~¯0nâ$Œūe$LŒņđŅclßš}{÷„ĻĻ&ūģwn5jHÍK3|č`ĖøgLLLāė䈛ˇnãÖí;Øŧa­¤Ė5xņō%&˙9úzzØĩg/â$“Pf­711Ģ׎GŊēĩ‘đõ5˜ÁŲŲ Ē**ųnģ°ęÉĒB…ōPUUÅž‡0rØPøŊz…UkÖĄv­š DDd$ KeīŖųĩŠ‰ Lŗ|!422‚’˛’$Á�gΞĮ5,û2)j~õZ[YĸNíZXĩf´ĩĩacc?ŋWØēmÚļi%õ•š^Yb‘õøĩüö_MM Æ ÂĀĄ#0cÖthß/^zâāáŖøuÔ‚סeŨīüâˊ}[‹žžj×lj…‹—!>>eíėāéåí;wŖSĮöŲ.9..ę×qË7š˛tõ&$$&J–=÷ôÆĢ7?f8J›™ÂŊļŽßēķ=ÂEBbt´eû8›˜˜!"""9ĢKĐģw:â�âšLŒQˇnm <Pjˆs†ĶĻ Ļ›9ŠEK–!5- eJcčAčŲŊk~Ą€^=ēaĘô™8d–.Ę>ėtÁÜؘ5g~˙c´ĩĩŅ­kg´mĶ WŽ}ŊUshh^ŧx)õģ û•YHHž=!ķãâĒ0ÚĒ[—N8rôü^Ŋ.đöΛƒ˙ĖÁĐáŖ`lbŒĄƒÁĶĶ /==s]g⸹âdĖĒ5ø#CC4lāŽŅ™FáčęęÂĄREŧxé‰j_F°�€‹‹<ŒÚŧ=sqoīš áŋ—’ĮÁCG��įNG™2ĨąqŨjŦ^ˇ /ETT4ĖÍĖ0öˇ_Ņŗ{7�âÉ:‡  Uoģļ­]{öbŨ†M°˛´Äâ…ķPŖz5I™iOÂŧ…‹1uú,$''ŖĒĢ ļmŪ�CCÃë  DhXB¯…áę5lûrõâ9æģíÂĒ'ĢRøgÆ4ŦZģ§ĪœCe‡Jøgæt„……aŌ_S1lį8vx˙7ĩ,^Ŋ~ë_&*•ĩŪEķį`í†M˜1k6ĸĸŖahX ­ZļĀč_FäZo~d=žEM–ũwtŦ‚Õ+–b՚u>j4JāˇŅŋ _ņ-wKZߖuŋķ‹/+öí¯–.Z€u6bÃĻ­øüų3ĖÍĖĐŋoo 4 ˙•¤šKŪˇG?yöĸTr%CBb"NžŊ„á{ŖēĢĶ÷K°$$BG[+˙‚�âsˆ›ˆˆ¨0 üũũE666…ZiÆ$z?ĢöēâÔņ#ß´nÛ]ĐĻuKŒ1ė›Ö‹‹Gú°|ÉBšn…ųŖIHH@JJ tuŋNČ8lä¯ĐĶÕŒ’iß Û›ˆˆŠĘßŗxĨsĻæyŦņSįH.ŊÍJMMKfOErr2ÆOSām‹ c†ÃÚŌBϞoßaéšMß´yĶ&¨|A&’&"ĸ’- �9•ŧoCvëöšŽ1ėÅÅÅ#--­@ë%$$ 11AŽm˙¨ÆŒƒ‡ãŅã'|„Ũ{÷ãŪũhߎ­ĸCc{Ņ!cģ\Ļŗ+ņ¸ė'—Qc‚Ĩš×̃ųså›@mëö¨SŋŽ^÷(ĐzģõBãæ­åÚöjáŧŲ¨PĄ<ÆM˜„îŊúâÔé3˜=s:ęģgŋíķ÷Æö&"ĸâ"$,<Īį+”ŗÍã9ņäæĄķŽŖ0$i’Ā%ˆˆ¨ˆĢ9X8sōč7¯{ūˉBŒäĮbhhˆsg+:ŒlØŪDDTœ<|ō VšĪwĶĄu ŧz˜m uuthĶ�đāņŗ"1ŗœæƒÉMAFģ} Ž`!""""�ĀÍ;÷šëķĻ&F?z8*@UUĒĒĒpt¨€ņc†ÃÔØÁ!Ą¸õīŊīoF°0ÁBDDEŒ#Xˆˆˆˆ�ššŠõ[wcԐ~šŪŽŲÔÄÃöÉļ<8$ëˇîFjjjQ‡)Q,)KDDô-˜`!""""‰¨čĪXŧj#ę×qC5'˜™åzgĄääd„„…ãá“g¸yįŪwMŽ�ƒ… ""*ZL°‘”ÔÔT\ģy×nŪQt(yŠ‹“šl\\|FBDDÄ9Xˆˆˆˆ¨„ōō}…Ā ÷ų– z/ßWß!"""ú™q •H‰‰IX˛zŖĸà ""Ā,DDDDDDDDrc‚…ˆˆˆˆˆˆˆHNL°ɉ """"""""91ÁBDDDDDDD$'&XˆˆˆˆˆˆˆˆäÄ ‘œ˜`!""""ʅ@ čˆˆ¨¤`‚…ˆˆˆč¤­ĨˆDŠŖd‰ Ŗ­Ŗč(ˆˆ¨„`‚…ˆˆˆčÔģ{Gŋ—P€Ŋģ(: ""*!˜`!"""úŲX”ÆđA} ­Ĩ-ĻĨĨ‰QƒûÁÜÄXŅĄQ ĄŦč�ˆˆˆˆ¨hØX”Æßã~QtDDD?Ž`!"""""""’,DDDDDDDDrb‚…ˆˆˆˆˆˆˆHNL°ɉ """"""""91ÁBDDDDDDD$'&XˆˆˆˆˆˆˆˆäÄ ‘œ˜`!"""""""’,DDDDDDDDrR.ŠJÃŖb‹ĸZ""""ĸbOGGGŅ!‘I‚ÅÖŌŧ(Ē%"""""""*–x‰‘œ˜`!"""""""’,DDDDDDDDrb‚…ˆˆˆˆˆˆˆHNL°ɉ """"""""91ÁBDDDDDDD$'&XˆˆˆˆˆˆˆˆäÄ ‘œ˜`!"""""""’,DDDDDDDDrRVt�DDDDT4^ŧÅļŨôôtE‡SbčhibØ ž°ą,Ŗčpˆˆ¨„ā"""ĸĐ뀷Xąn >ĮÄ2šR@"‘Ÿcbąlõ&ŧ Qt8DDTB0ÁBDDDôÚēë€ĸC(ŲˆD騏}ĸ#!"ĸ‚ """ĸPLlŦĸC(ųDŽQtDDTB0ÁBDDDD” ‘H¤čˆˆ¨„(V ‘H„ã'NĄgŸūpŽę§ĒhÚ˛ –._‰¨¨hОÃG†ŊΝŋ­žÃagī€˙îŪ+Đö3ęĖøĢäčŠf­ÚbÎŧūđAŽ}+L ›´ÄxîÂÅ­ëŪ°ŠdŨK—¯Q„Ų)ēmeQÕ­6ÖŦÛPčõæĩ’Ö–iiiØ˛mšˇj§ĒhŌĸč×Ė �� �IDAT56nۊ´´´Ë'&&ĸ~ãf¨íŪ0Ûs"‘5jÕÃÅKâØwėڍMšŖBeg4iŅĮOœĘV×Ōå+ҍiK88UEŗVmąqķV¤ĻĻæZ¯ŦņæˇíÂĒ''§ÎœEÅ*.¸pņrže3ĮŗtųJØŲ;`ێ]y–ÍĢ 2:b”Ôû_Æß”é3ĨęZ°h)ę5h‚ •áŪ°)Öoܜ­ ˛*Ŧļ-j˛W//oôę;�•]áV§>æĖ[€””Š2íÛéé騴e+ęÖoŒ •Ņē]G\ģ~#Ûļ‹ĸoË˛ß˛Æ—UqéÛ˛Ôû-}P–× đmī DDDT2ĢģŸ8'NF›Ö­Đ§WO¨ĒĒâéŗgØĩ{/Νŋ€{wÃØØHR^(būÂ%hܨ!ÔÕÕ %+KK,˜7�Ÿ�O//<|‡ÃæëāVŖzĄlG^#† Á¨àĄĄQ õΝ9¸¸8ÔqoTD‘åŦ8´mqU’Úr؊UØ˛mƍũ .ÎN¸w˙-YĄ@€aCg+ŋbÕZ„„„ÂаTļįŧŊ}đ)* ĩkša˙C˜ˇ`1&Œû.Îθķī˙įdččhŖi“Æ�€Ysæáę5,œ7åĘÚáÉĶg˜ô÷T$%%áˇŅŋäX¯,ņʲíÂĒ''íÛļĩ•&NúåĘŲĄ\Ų˛yļAXØGüöĮxDDFBII)ŸËģ 2‹C“ƍ0dĐ�ŠåĻ&&’˙˙ük ūũ÷.&Nø6ÖÖ¸˙ā!–,[ÔÔTŒųuTŽõfÛ%YŽkđ‡čÕo 5¨Ũ;ļ"(čfū3ĘĘ*˜üįxIš‚öí•Ģ×bãĻ-˜0ū¸8;a÷Ū}ņËh=´NŽUrŦˇ°ú¤,û-k|Y—ž-KŊßŌeyÍ|ëû•LÅ&Árøč1œ8us˙™‰^=ģK–ˇhŪ;u@§Ž=°lå*˟ķäš&áŋ˙îbĶ–m…ö!\SKĩjēI7nÔ�ƒô˙{÷Weų˙qü}XQq!nqå5skųũ6\™–•Š +ĶĖĘUnSË4õ[ŽÆ¯Ô4GŽÔÔTĀŽ™ā€Îī#GŽŦėõ|<|Ô}ß×}ŨŸs_÷9z>įzáÅA<äumÛ˛QEœŗåZļ(P €\\\2}^Ņ"Edgx¸—ōJÛæUųĨ-īŪŊĢo–,Ķ€ūũôōK%IM7ŌącĮĩfŨú –cĮOčëÅKÔķÉîÚļ}GŠúvūąKuj×RŅĸEõŗķÕˇw/ zŅToãFž:ųĪ)}>wžÚĩmŖÄÄD­ūe­ŋ2H­[ĩ$•/_N;vūĄUĢąxF’ę-T¨P†ņÆ ¯mÍëļĻžô<VˇŽ~øv‰Ž]ģ–a;ŦZũ‹Šģģkáü/Õ°ą_ēe3jƒäĸĸĸT§v-‹ĪŋänŪŧŠßˇīԇŖGéŠ'ģK2ŊÆŋū:Ē_7nJ5ÁbÍ}ÉLÛæ$kîëŧų TÁĢŧĻO$ƒÁ ߆ TĸD‰=X2ķlĮÅÅiū‚EôŌ@Ŋ8 ŋ$ŠAũz:v넿Í_ ĪgĪLQov=ÛÖŧîĖėšŧđlgToVŸÁŒŪ3ļ~.��€ü'Ī úzņR=VˇŽÅđ$U*WÖ÷˖hĖû#-ö-ZDC^{U_Î_ ‹—Ō_B/>>>Í?-]čė\XĮŦĢ׎iŊŸÍû#"#5ü÷Ô´YKÕ¨]Om/1˙Ī3Ŋôü€—RÔ÷‹/ĢįĶĪ™ãš9kŽÚ>ŅÉ\ĮŌoŋK7žü&/´íåËW4pĐ+ōŠS_MZhū‚…š6ã3=ŪąKēõĻ×6MZhöįsÍÛI׆ŧū–E=ũM×ËīėííĩfÕrŊ2čE‹ũeʔÖõ–Ãŧ5jô‡ęĶëYU­Z5ÕúvüąKū~ ;­ķ.čņv–_6ÚļiĨƒ‡tëöm F9:Zæ„ (  ŠÖkMŧÖ\;ģęIMōĪŋæ­ôâ˃->CRĶĨK'}>{Ϝ §[.Ŗ6˜4ešĒÔ¸ßûāöí(.œv... ŲhNŽ$ąw°—C˛^ÉëÍîļÍIÖÜ× ›6Ģ{ˇŽ2$‹ĢY€ŸųKy’Ė<Ûa§Ī(66V~M›˜ÛŲŲé‰öí´s×îTëÍÎg2Ŗ×™ø’ËKĪvFõZû fö=“ÕĪ��å‰Ë­[ˇtôč15 đOŗL­š>)†P$&$Ēŋ>*íYJŸNžšæšū QĩšuĶü3~⤠cŦRš˛*zWP`P°y߈‘īkßūš5cšÖ˙ōŗ^yųEŸ8É<F×Ν´{O nŨē?ûü­[ˇ´k÷uëŌY’ôɤКŋ`‘^{åeũēv•x^ã&|Ē~\žaLųA^iۑŖĮčČ_Gõ՗Ÿë›…_)(x¯ÖŦ]ŸnŒÚ&ĀĪO{÷í7— ViOOīŨkŪψˆˆt_~agg'ī äęęjŪ¯ė’oÃe—}÷ƒ.]ē¤7_–j]qqqÚģoŋüũt*,L’äåUŪĸL//IĻ/)ƒAĪ=ķ´–}÷ƒNü}R’tčđ­˙uƒz=ûLĒõZ¯5×ÎŽzR“ŅgHjJ{zĻy,šŒÚ JåĘjŨĒĨy;**ũ/‹ÉÅÄÄ(<<Bß~˙ƒ6mŪĸŧjŊŲŲļ9-Ŗûzũú ]š.w÷bzã­wÔ ąŸüšˇŌĖYs,æ=ÉėŗoęũâččhQϏģģnŨēežŖ*'žmk^ˇĩņ=(/=ÛÕkí3˜Ų÷LV?��@ū•'†]š.ÉÔ%73Lŋ89jÔČzéåÁęÛģWŠ/z’TŊZUũŧü˙ŌŦĮÃŖ¸U×+SόÂ#"ĖÛŧ?Rövöæ¸+Vô֒ĨßjĮÎ]j˙x;uėđ„ÆNøD[ļũŽî]M=%6mŪĸ„„uîÔAˇnßÖŌoŋĶĢ/ŋdūUØģB>|D_ÎûJĪüˇ§•w"īĘ mĄßˇīĐGcF›3§OU@‹6ō,U*ÕķŦi›�?}<n‚egg§=AęÖĩŗ–,ûN§ĪœQ//ī•{ąbōŠQ#S¯?ŋ˜2m†Îž;¯/æĖ2īģr%\S§ÍĐäO'Ļų‹ņŪ}ûe4Õ°A}­ß°Q’iČSrÎ÷†âŨž÷+ī¨÷ŪQdd¤:tî&ÅĮĮëÅũ-æ?H^¯5ņ&՝ŅĩsĒžŒ>C˛Ęš6čųTõ|LJyûvT”<¤'˙ûŦūūû¤Jxx¨SĮ'4ôĩWS˃Ôā ī•‹‹‹&}2^]ģtJĩ^kī‹5m›ÛŽ^Ŋ*Iš2u†z÷zV^x^ûöīפ)͝ˇßzCRæŸí:ujËÎÎN‡ฌ;vâ„$Ķ—x77ׇöl?ČËËËĒø”—žmkXķ fö=“]m�ëĩkÕLŨ:>nîef4ĩzũ&mŪļ3—#�ü[ä‰KŌ_„ŽŽ”L]ÛÖ­Ôĸy3}<n‚V­ø1Åņ… §;Ÿĩâãã-&Čs.ėŦšķžŌžĀ@E^Ŋ*cĸQ×oܐˇˇˇ$ŠdÉjÜČW7n6'XÖoØ(ŋĻōđđPPđ^ŨŊ{W͛X\§I“ÆúáĮ劊ŠÎ°Ģt^—Ú6xī>Fų&ûbRÄŲYÍüũtōŸSŠžsôčą ÛÆßŋŠnGEéøņōņŠĄĀā`ņŽ:ŦāŊûĖ –€�?‹!ŠISĻé›ÅK5÷ķŲĒč]Áŧ˙ãqÔ¨QC=Ņ>í/Q;˙ØĨFž åäädõõĻNŸŠ]{õŲôŠĒRš’Ž=ĒO&M‘ģģģy¨DzõĻofeW=RƟ!YeM$—˜˜(GGG]¸tI/ |AĨJ–ÔŪ}ûõŲėĪuáÂE͘6ŲĸüGcF+<<\ģvīŅ;#FéæÍ›ęĶëš,ĮkMÛæļģ÷V“iŨĒĨŋ2H’TˇNmEDDjŅ׋õæëCeooŸég숺ŗēuéŦ/žüJĩjÖTÚĩôëÆMÚŧy‹$ÉÁÁô×ôÃxļm‰īAyåŲļVfŸÁĖžg3&}<R…Ķ™ Ū`0¨{§öęŪŠŊy_tôøč“‡�ā_(O$XJ–*)ƒÁ Đ{Ũiŗbô¨ęØĨ‡~\žRm’uáM’ŪR‹vvv˛ŗËx´ThX˜üMäŨŊ{WĪxQņņ 3z”*WŽ({ zõ5‹sētŸNQllŦîÆĮkĮÎ?4aėG’dŨĢo%˙úh4J’Â#ÂåėœŊ˙X~ØōBÛ^ŋ~]’ä\ÄrrbˇbnižgMÛxW¨ ŠŊŧoŋJ”,ĄĐĐ0ų6Ŧ¯ūŠāā}úĪSO*(x¯† }´&éMLLÔû|¨5ëÖkŅ‚yō÷kj>ļuÛvmßšSŋŽIŌ?víVįN$I.EMüŪŧuKE‹5—šyķĻ鸋‹Î_¸ ų iúÔIæŪ>>5­‰ŸNVßŪŊäė\Øĸ^kâĩæÚŲ]Ok?C2ËÚ6HÎÎÎN!û-ö5lP_FŖQ“§NטҪT,ŲûĨFõjĒQŊšš7 ŗŗŗ&|2YOõčžb¸DvļmnKšÜŧv­šû}6ÔÜy_éÜųķĒāå•ég[’ÆŒĨ×ßz[Īôę+IĒ_ī1Ŋ6øe›đŠšwHN>Ûą&žäōŌŗmŦ<ƒÖŧg˛ŗ rƒƒƒƒšû5–oũē*UÂC ¤ž4Œ‹‹ĶĨ+Ú{ā vėzčËĢgÚŖ÷{� É –"ÎÎĒUĶG+VŽŌÁ¯¤ú Ũē_7¨€““ÚļI}IÚ*•+ĢOīį4múL5mŌČâØ?CĖ“ĘĻĻŋž3zdšĮ%S/ˆ+WÂÍ=ū 9¨cĮOčûe‹-–nŽŧzUåĘŨĶá‰öúđãņÚąķŨš#Ijī—ˇ¤nÃ3ĻNRõjÕR\ŗLéŌ鯔ä…ļmÕ˛š$™ī’iĖ Yß6~~Úˇŋ<ŠģĢzõj*Z´¨ų6ÔGcĮëÂŋ:á‚9)÷¨øhėmØ´Y˝ĸ÷Đē_UTT´ZļŊ˙kĄŅh”ŅhT•ĩ5zÔõčÖM‡üĨ‰÷VĒTÉ[’i>‚˛eʘĪ;&;;;UĒč­†Čh4ĒjËe^+TđRÜŨģēxéĸ<Š{XÔkMŧÖ\;ģëIbígHfYĶũûõĩĒ.ŸÕ%I—.]Rl\ŦvīTûĮÛY|ŲôņŠĄØØX]ŧxI•+W˛8?ģÚ6Ŗå}OĪRrrrŌÕVÃIH0}™tttÔõë72ũlK’››ĢžYô•.]žlēVŠRš>s–*VôVRԛ$ģŸÉ´d߃ōÃŗÜéĶg˛íLūžÉÎ6xØÜ\]ôęĀž*ã™ú0Ú䜜œäUތŧʕQĶFõ5wá]ŋqķ!D)øž(�€ŧ%O$X$iĀ Īë­ˇGh֜/ĖcŲ“œøû¤FūPŸx<Í/á’ôÆĐ×´jõ/újÁ˙,öר^MkVĨ=iŦģģ{ēąŨ¸qC|øąĘ–-ŖNMŋ ÆÆÆI’ůēûüŠsįÎĢn:æ}ÅŨŨåī×D[ļũŽ[ˇnĢMë–æ/ī>>5ää計ČĢę”ė‹IäÕ̞3ØejøD^–Ûmgj̐ƒUŊšiʼnÛQQÚškˇJ•,™ęyÖļM€ŋŸÆMøDnŽŽæ/õë×Ķé3gĩvízUĒTņ‘H”%Yąr•~\žB?|ģ$ÕĄYÃß|ŨbÂSIúyÕj-_ņŗ–|ŗH%K”ĐŽŨģ忿ǚ>Ļyi*xyÉģBmÜ´Ų"ĩiķf5iÜH… Ré{÷đŸBUĶĮĮ\æŸSĄ’$OOOmßžÃĸ^kâĩæÚŲYOrÖ~†d–5mđ S§B5yÚ ŊõÆ0UĢZÅ";;;U¨āĨN…jøģīiúÔIęŅ­ĢšĖ‘#É`0¨lŲ2)ęÍŽļÍ ėííÕ<Ā_7m6’¤=AruuUiOO­˙uCϟmIúeÍ:U¨āe~ļâããõķę_ÔųŪß7žg¤œy&Ķ’Q|ĘKĪļ5˛ō Zķž)\¸pļĩÁÃäāāarårx„V­Ũ¨ã'OÉ`ĒUޤîÛ̌g)Ŋ:°¯Ļ˚÷Đz˛Ė˜8&ÍĄj睎FÍ¸ ��Y”g,=ēuU``žøržųK];wRaįÂ:|øˆ–,ũVUĒTҍ÷ŪMˇWWWŊųú0›`ų‹FĄB…,ūŅ”žč¨hí ’dęæ|ėØq}ŊxŠĸĸŖõÍĸ¯äto%ŸęrrrŌ׋—ęõ!¯éø‰š<uēšøëTh¨""#åQÜ4ynįN4įķšēyë–&MožVŅ"EôėŗOkæŦŲr/VLÕ­Ŗķ.hė„OTÚŗ”Î˙Ōǘ—,ûNĢYŖŋ_fÕöÖÚļVM}>wžĒTŽ,WMš:]%Jx¤YŪÚļņkÚX—._ÖæßļęũQ#$™zíÔ¨^]‹—~ĢļmĶNĨ&/ˇeLLŒĻNŸŠV-›+:úūû$IƒõåYĒTЉƒK”(!{{srkįŽŨōkÚÔb^š!ƒ_҈QŖåééŠõëiËÖmÚēmģ–-6%Ô*zWPķfš<uēŠ)ĸʕ+ęØąãšûå|=ŲŖ›Š8;§¨×šx3ŧvvÕķ Ė|†$wøČ_æÉ1F>}ÚSũzYÕ’´ōįÕÚ¸yŗæÎ™ĨråĘęøņzuČ0 ķu•*YRAÁ{5īĢ…ĐŋŸ .Ŧ:ĩkŠY€ŋ>7QQQQĒZĨŠ>ĸ/į/ĐĶ˙íiž7yŊŲÕļCF÷ĩ@2øũ÷š>1r´ūĶķI…<¤%ËžĶ[o “Á`ČŌŗ-™–9xP­bnnZ°čē}Į<ÁjN=ÛÖžîŒâ{P^zļ­Š×Úg0ŗīkÛ?¯iáß8ÃäĘ´Ųķu'æ~¯ĐCĶÉSa>tĘx–RsŋÆÚēc×ÃWwbbU´ˆu˙œy '+��Ų-Ī$X$é“ ãāī¯Ĩß~§ąã'*>!A^åËéĩÁ¯¨_Ÿ^VũÚĶëŲ§õíwßëø‰ŋŗÙŗgÕĢoIĻ1ÖĨJ–T˖ÍõÚĢ/[tņ-îîŽ)ŸNÔäiĶĩbå*Õ­S[S&}ĸ˗/kčëoŠwß´aiœx‡öíôÁ‡ĢPÁ‚jĶÚr‘Ņ#GČĨhQ}:yĒŽ„‡Ģ„‡‡Úĩm­ˇ‡ŋiuĖ.\Ё?CŦŪÎ šŨļŗfL͈QŖÕĢĪķ*YǤ^{õ:tXJķkÚÆÅÅEĩkÕÔÁC‡ÕØˇĄyŋ¯o-^˛LÍ29<(/ˇåŠĐP]ē|Y—6^ֆ)—Z üc{ēIĢ$;˙ØĨÁ/˛Ø÷ԓŨ­¯.ԌĪfËģB͙5CM›46—™ķŲtM˙lļFŒ|_׎_—‡GquëÚEÃß–jŊÖÆ›ŅĩŗĢžeæ3$š>̐ƒæí%ËžĶ’eßI’ļoŲ¤råĘĻyī“;ņ÷ßÚtoĸR'''-ųzĄĻLŸĄĮMĐĩk×UĻtixį-õëĶÛ|ÎÜ9ŗ4ãŗYúlöēqã†Ę–)ŖôˇčŅ‘ŧ^){Úöa°æž>öX]-œ?W“§NWŸįQqwwŊ3üMŊ8 ŋ¤Ŧ?ÛĮ}¤>̎ßŠØØX5ōm¨ī—-–‡‡GĒõfį3iÍëÎ(žåĨgÛÚz­yŗōžÉėįB^а^Ũt¯ZģÁ"š’äNLŒV­Ũ¨Aũ{ɡ~Ũ‡—`šŖĸEŦKÄF§7��ŲÉj´uV<\­Ú>ĄŨģęaC˛t~TT´ęÔ÷՗ŸĪ˛išĖüäΝ;ē{÷ŽÅ¤‚ŊûŊ 7WW}>{fŽÅE[�rĘĐwĮdúœiãG§;DyøčņæĄˇ*PĀISĮV\\œ†Ÿj™ėööĐAĒPŪēš}ÂÎœĶ´9ķŗtŲ“Z�H]XX˜yĨÄ<ՃÖģ{÷ŽĸĸĸU°`‹ĨŖ3rįÎŨšƒ‘åMŊLjˆHM÷‘<<<´eë6íŪ¨ķæævh´%�ā‘poĄ=ķ†čL ûyp˛{��˛[Æk#Oš;ī+ÕŠīĢ ›RvOOûŽ]ÕØŋEE•w͚1U>>ÕõękÃÔšÛ“ZžbĨĻ|:1Ő­Ü@[�ōŠKW"Ō=^ŊJÅtŽ™&…ŋž~Ų)3I“;1wr0��čÁ’/mûmC–ĪŨą-s_âúlúÔÜ#Ú�—ėûķ ŧĘĨ\,I÷NOčäŠĶ)æa)T° ēw6-ĄŊ÷ĀÁÔNÍŠÍ“–Ėôv� +čÁ���IŌö]AēpéršĮK•ôĐđ!ƒT§Ļiĩ('''ÕŠY]ÇRŠēpé˛vėJķüė–Š,$X��9Œ,���$ÅĮĮkîÂ%zu`ß4—k.UŌCƒú÷Ną˙ÂĨ˚ģp‰âããs:LŗĖô`ÉLY��˛‚ ���ĖŽß¸Š)ŗæŠ…c5ŦWWž%=Ō\Y(..N—ŽDhߟĩ}WĐCMŽH™ƒ… � g‘`��€…øøxmŲžK[ļīĘíPŌu;*Ęę˛QQŦŧ�ČYĖÁ��€|é艓:}ö|†åNŸ=¯Ŗ'N>„ˆ��˙fô`��@žĢŠŗįåv��Hĸ �����€ÍH°������؈ �����€H°������؈ �����€H°������؈ �����€H°����i0r;�@~A‚��āT´HÉhĖí0ō7ŖQŽ..š� Ÿ Á��đØīYē_ØĘΠ—ôÉí(��ų ��€GPeo/Ŋ1øE-RD-™V´ˆŗŪúŠĘ•öĖíP��ų„!44ÔčííÛq������ä+aaaJʊЃ�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF9Qé… rĸZ�����€,+SĻLŽÕ# –œ ����� ¯aˆ�����€H°������؈ �����€H°������؈ �����€H°������؈ �����€H°������؈ �����€H°������؈ �����€H°������؈ �����€H°������؈ �����€rĒâāķRˇ%Rx””`ĖŠĢ ¯ŗ7H%œĨU}¤Æår;��Ķ"¯]׊ÕëtîÂ%]Ŋv=ˇÃÉ7܋šŠ\O=Õ­“ŠsËíp��Y` 5z{{gkĨ!Ĩ_H‰$V IFÉÎNÚ7XĒW:ˇƒ��9%ōÚuMšņ…:>ŪZukûäéDÁĐwĮhöäąš†Yäĩë Ū¯m;÷hěƒķôŊ�ÜϤœJŽ ęē”ä ’1H‰‰RˇĨš��ČIËW­SĮĮ[Ģus?™Tŧ˜›:ĩoŖV͚jųĒuš� r$ÁrîFNԊ|Í ]¸™ÛA��€œtōT˜šúÖĪí0ōĩ&čüÅKš� r$ÁBᤆšx€ÜUĨFm=ųßgs; �°;11*T¨`n‡‘¯/æÆÜ5�OåØ$ˇ�VŽZ­áīŧ—bŋÁ`{ąbjذžđŧų6Ė…č����Ā„ešä Ô×Ë/ 4˙éסˇjÖôŅĻÍŋé™^}ĩbåĒÜņ_#v×)Ežđ es¨,���ō'z°�Čüũôư!)öīUī~/hė„OÔĨsG999åBt˙‰WŖeį^X†20ũõaŧqG—B’˛ļ–��@ūF�ųZãFžō÷kĒ›7oęčŅcæũ!!õōāĄjĐØOÕjÖUķVíôÖÛ#tîÜy‹ķãââ4ÁBuęÚCuë7RízžęØĨģæ/X¨ÄÄÄL•kÚŦĨ:véž"ÆöģĒRĩšÚēmģÅūÕkÖĒRĩšZšjĩ$éüų zįŊQjÚŦĨĒÕŦĢũôŌ˃rĐâŧĄo WĨj5ŠžũĒFízÚüÛķņ­ÛļĢkžĒQģž|›čŊQčæÍ”ŗL?ņˇ*UĢŠ>ΰę^ßŋVũū§˜ßO(ņvŦnŒ_§+=įéî_(› e��ŋ҃@žWĖÍ´蝘IŌĄÃGôLī~rssÕ Ī÷S 9{VK–}Ģ;˙ĐÆõkTėŪōĄŖĮ|ŦŸVŦTˇ.Õģ×ŗ2 Úžã}:yšÎŸŋ ?üĀęrÍüĩbå*Ũ¸qCŽŽŽ’¤ˆČHüį.\XŠÔë��ģIDATAÁjŨĒ…9î=A’¤æūēpņĸzô|ZwbbÔģ×ŗĒVĨŠ.]žŦĨß~§gzõՒo™į™qrt”$ŸđŠ4lČ`•/_^’ŧwŸ^ze°<<ŠkؐÁ*îîŽĀ `ŊôĘk˛ŗŗ-§î6ļ›âöÖí¯w+áė5l[CÅŋė-‡*%(› e��ŋ‘`¯ŨŊ{WBB$I•+U’$…<¨ĒU*ëũ‘#Ô´IcsYĪRĨôҏ úeÍZõëÛ[’´fŨzÕ¯÷˜fNŸb.×ëŲg4~â§ēpņĸdoooUš¤ËŪ}ûÕļMkIŌž=A˛ˇˇW§O((x¯E끁AōņŠ!Ŋ=b¤"¯^ÕÜ9ŗôDûvæ2O´o§ģë“ISĩâĮī$IĻîĢ׎éë…ķ-'ŸĪ§ÄÄDÍûbŽĢ[G’ôĖĶ˙ҘÆ)xī>‹ëW­RYîŨcŽ/#†"d_Ą¸"ŖT e5%^‹N3Q@ŲĖ—˙ŒŸ:K—¯D¤Ø?{ōXŸ2Kƒ_ė'÷{ }�ĀŖ!B�ōĨØØX;~BÃŪŽŗgĪŠk—N*QÂC’Ô§×súåįåæäĘŨģwĢ*U*K’Νŋ?LČŅÁAį/\PDd¤EũŖGŊ§/f&{{{ĢË5ķ÷“$‹DĘîĀ@U¯VM~M›čĐáʎŽ–$]šŽĐ°ĶjŅ,@FŖQ›6ũ&âÅÕūņļõWŠ\Y ę×͟!!ēvoŲNƒÁ4yGΧzX$W,¯ōåÍɕ$Ī>ķŸ÷ĐÎÎN...*\¸pÚ7:šŖnŒ]+į˙4Ë›mŗé¨â‚OS6ģĘČwRKޘ…Gčŗ/ąä2�ü‹äš,-ŊĨwšK ËJ…Ĩ›ąŌŽ0é“ßĨĀsšŨÃ1JššKŋÍ´]ÁMúŋgĨĮ<Ĩ÷6JŖ[Yuŗæ|ĄYsžHõXÛ6­5aÜĮûVūŧZ?üø“Ž;Ž[ˇnY‹O0˙˙›o ÕØņŸ¨Mģzŧ][5mÚX͛ČŗT)‹sŦ)įááĄÕĢ)(YO‘={Õ˛E 5näĢ„„í?ĸf~Ú(IjŪ,@ááēuûļj׎eNž$WŠbEíŨˇ_Ąaa*VŦžÅū䮄‡+66V^^åRԑÔģĮ&öšŧŅVŽ5JINr›ĐCŽ5KS6ģʐ$%$$čā‘c:pđˆÎœ=§›ˇnË(Éĩhy•/§úukéąÚ>6{|Ž^ģŽ/,Öčw†åv(�€‡ O%XZU”6ö—ž?$=˙“%yšI#ZH[JįJGŽäv”9ãĩ&RŖrR˙åĻíáëĨC—îĐPĒYRj˙ĩt<\ēvĮōxNÄ�ä%ųZ ÷ąŗŗ“›››5l Ÿe§L›ĄšķžRÚĩôÁûīŠ|šrrrrŌ‰ŋOjäûX”í߯¯ĒU­Ēo–,͝7™'œmÕ˛…Æ}4Fe˖ÉTšfūúß7K­[ˇn+4ė´Ū}ģ‘Ę–-ŖŌžž Vŗ�?í RĄB…äëÛP.˜&<-\¨PǝŊ`Á’¤čč;û‹-bą}įŽišNRÔQ @Ę}YáXˇŦų˙”§l6—ūíŽ?ŠW­QxÄUIR%o/=V§Ļ 2(ôĖY8xXV wũˇ{ųT¯ōPãK>$höäąi–ģtųJēĮ�Ļ<•`y­‰ôW¸Ôī§ûûö_”~ûGÚũŠÔÜûŅM°4,kšũÍËm÷BŌéëŌö°ÔįD @^Ō´IãT—i~PllŦ}ŊXĨ==õí’oäė|øËƒ=Y’øû5•ŋ_SÅÅÅ)(xŸ~^ŊZ+VŽRŸįhÃú_Ė“ĘZSŽY€ŋ,úZû„("ŌôđFL“Ķúú60 V“ƍääč(įÂΒ¤č;wR‰î~bĨH‘ô‡ō$%bbãbS‹ŠŠN÷\�Čmë7mÕēM[%I… Ô ŊŸN‘@9zü¤ūˇė˙qU_,\ŦNˇVĮĮ[?´Ķ”Ü„is,ļIļ�ĀŋCžJ°8Ų›ū<čVœT{–åž’ÎŌԎRÛJ’{aéė iÎiÖnĶņ?IˇbĨßXžˇŽŸäVPōŸ/9ؙ†Ú<[×4įė iÆŌÜ ôcü¨Ô¯žŠž?/Jīnv1ˇĻÎđQŌ„mRû*R›JRđyŠYĶąįëKõįH›Ü´ķ%)āŪqãxiäFéíf–C„2Š+Ŗûĩm Ô˛ĸe ‡¯dūū�š-<<BąąąĒS§ļErE’ƒ‚Ķ=×ÉÉIÍüÔ,ĀO… Ō˛īž×ŅŋŽęąĮęZ]Žq#_99:jīž}ēté˛ĒU­"÷bÅ$I|jü„Ouöė9…>­~}{I’J”đĢĢĢNūķŒFcŠaB˙ķ¤”C‚TÂÃCŽŽŽ:{ö|ŠcĮŽO÷\�ČMɓ+’,’+?­ZĢũ¨qƒĮÔĨC;ŊĐûi}ąpą$™Īy˜I��Ō’§¯Ž9.ų”~zNj\N˛K9Ųĸ§$/éš˙“›-}úģ4ŊŖÔÃĮtüûƒĻä…k˛^ņޤļ•Ĩīšļ§t0Í÷2q›Tg–4ũifgi`ô¯;­Ŗôĸ¯ôÖ:ŠåéäUiCŠb1ë댋—5’]–Z/”ē/•ö7Å\bĸirK ÷IĮÂMĮ?ەų¸2ē_ŠÅ•ûä6â’¤sį,“ =Ē?¯’tŋ‡Į?CÔ´YK­Xš*E=v÷>€­.'I TÆ tāĪí T“ƍĖeųú*îî]-üڔųm`>ÖĄ};…‡GhĶæßRÄrPū~Måââ’îkwppPƒúõtúĖ…<dqlÉŌoS”OLLÔ͛7Íī@n8vâ‹äJ%o/‹ž+ŅŅ1ēuëļ~ûũ­ųuŗ|ĒWQ%o/ķņu›ļ樉jĖ��¤&Oõ`ųj¯i(Ėû­¤žĩLÜî<-­úKZ"EßŊ_öĩR‚Q ŊfÚ>)ŊÖÔÔ+äįŖŌO‡MɀÎÕĨoī%TēûHöé˙I.¤ÁML“į.ūĶtüdÔ°Œô^ SBãAELIŒw~•ūī°iß ŸĨ"NRw)2Úē:’ĸã¤îן(Å&HŠ|Īš+ÅÜ5ŊŪԎgWčĩŒī׍X˲r€ŧ `Á‚jĶēĨļlũ]īųHM7Öß'OjņŌo5sÚdŊôĘkÚēõw­^ŗV­Z4—›ĢĢFŽŖā}ûTĶĮGƒtčĐũ´bĨ|6PMŸJHH°Ē\’fūú|î<EGG[$XĒU­"WWWũ´|Ĩʔ.­Ę•īO<ûưĄúmëīzë÷Ôŋ_UĒXQįΟגĨßĘŲš°FaÕëųĨ ֋ƒ^Õ˙ķ”Ššš)08XwîĨhË9[ū>ų:vé.ŋĻZúÍ"ī<�dÍOĢÖZlWô*oŪŖā!æcAûCÔŖķĒčU^§ÂÎXÔÁD˛�€Ü–§,’4i‡4{ÔޞéĪãU¤y=¤ZKO|mšŖE’nĮ™žčˇŽ$•p6õvq/$ũ}oՋˇMķ•<Yķ~‚Ĩg-éˇSŌå(Š…ˇiXÍÆ“–×ßjJVq2]#šÚĨ¤‚Rp˛ÕŒâ¤˙|gú˙ĖÔšûŦm÷)3qI߯Õ+ųûä“>™ qã?҆ ›´fÍ:ÕŽ]K_}ųšų6ÔĐ×^Õü‹4~â$ų5iĸī—-ŅėĪįęˇ-[ĩjõ9:8¨lš˛ūæëzžo 988XU.Iŗ�M™6C’irŪ$ƒAž ę롭ÛÔĩsG‹˜K•*П—˙ ™ŸÍŅËWčęÕkrsu•Ÿ_ 2XU*WļęĩˇjŲBŗfLĶįsŋÔÂ˙}ŖĸEЍm›ÖzäģęØĨ‡îÆŨ͸�x˜Ōčąŧ˙āŨēu;ÕĸFs6&��˛ Ī%X$SO•ÕĮL$ŠuEiy/Ķ"KŽvĻá/vŌëkMCgâĨU},ëųá4ĩƒ)ųāh'ĩ¯*Ŋr¯‡ŋËŊĄC[JÆdG' Kō,bf“\ą{ |DĨ‘XČL7bŦēVÉ(.kīWrYš?@Nx˛{7=ŲŊ[ĻÎ)îîŽ™Ķ§¤zlؐÁ6d°ÅžŪOŧ˙^ēuēššZUN’ęÔŽĨS'ūJõØWķR_jZ’Ę”.­ÉŸNȰūO'ŽĶ§ĮĨyŧKįŽęō@G’ūØžÅbģzĩĒiÆ �Ë3OvÕŦy˙3o‡ž1ũ ÕÔˇž6mŨaQļ‰o}IRؙsû{vī”ÃQ�ą<•`ņ,bęņ`Έ­ĄŌŠŋ¤NÕLÛMĘKu=Ĩ_I;Nß/WÂųūIZ~XšĶÅ4 ϰiz­ŧ÷]")ÁŅįĮԗ;>{#åžđ(Ķ] ĻVęĖÅeíũJ.ˇ^ ��øwŠZšĸēvh§_~Ũ,I:vFGŸTįömd0´;xŋ$ŠIÃzęÜžŽ?i1<¨Ķã­åSíá.× �@jōL‚Ĩ”ŗtö]͜c,įx”ARuéŌŊ^ĸīE™l>ŋōĻ ]““ –ļœ2ÍÃâZPZ{Ü4¯‹$…\’bãMĢëKļâ^‰ÂRĸŅ4ɃŽG˜z×´ôžŋ:ÁÔËcá^Ķ\&™­ķÁיÅuáŪĘ´Ũ¯ä1dåū���dEû6-”h4jŨÆ-2ú߲˙Ķ ŊŸV×íÔĩC;sš¤eš“tl׊„��yFžI°\Ž2­R3ē•äYTZ}TēzG*]Tę__ đ’žũÁT6äĸ/ ķ“>Ū"Õņ”>ioš/¤z SRāĘŊ^?2ÍßâZP¸âūõnÆJķƒĨÛš&u :gZŠxfgéÜ ŠË’”1ی•í“Fĩ’ÎŨ”ūē"ŊÜXō-+ X‘ĩ:“\‹‘ę—‘ęyfžwH†qÅXwŋŒ!̝�� ŗ:´mЊ^åôĶĒuēt%\_,\ŦJŪ^æIoCΜ5÷\ņ({tąXmča‹‰‰UŠ’ē|%"ŨrĨJz<¤ˆ��š-Ī$X$ĶĒ:G.K}Ĩ…O™&aŊ#í=/uøæū„ĢáŅŌ ËMI‚~õMĮû/—ĘēH?<+m(՞e*ģâˆ4ˇ›Š‡ĮÚã–×{kŊt=Fšü„)‘séļ)ą3jSÚ1žķĢŠĮ”RŅŌÁKR§o¤ŽfŊNIšĩKZō_iį ŠgĘÕT3”Q\ÖܯcČęk��ȊęU+kÔđ!:ô×1…>ĒĐĶg´cwFš-ĸúukŠ^ZzŦļėíís5֕k~ÕÛC^VÁ‚r5�@Ūa 5z{{goĨŖŗĩ:<BŒãs;��S†ž;Fŗ'Íí0Ŧ’•X‡ž;ÆĒrļۃütāß.,,LI9ģÜ ���ČŦîÐ �ø÷ĘSC„���€ŧjôÛÃr;�@F������‘`�����° ������‘`�����° ������‘`�����° ������åH‚Ő•"ßŗįÁ��ā‘V°`Ũš“Ûaäk‘×ŽĢ˜›kn‡�ȂI°”u‘dˉš‘oĨ2.š��ČIU+UÔÖģr;Œ|-0xŋʕ)Ûa��˛ G,?='ŲŅ[ÉØ¤_úäv�� 'õėŪI[wîÖúM[yízn‡“¯D^ģŽõ›ļjëÎŨęŲŊSn‡�ČChh¨ŅÛÛ;Û+:'u_*]‰’éÍō¯egJ8Kŋô••Ííh��@N‹ŧv]ËW­Ķš uíúÜ'ß(ææĒreJĢg÷N*^Ė-ˇÃ�X),,LI9•K°������<ʒ'XXE�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������lD‚�����ĀF$X������ldįāā „„„ÜŽ����� ßHHHƒƒƒyÛŽ`Á‚ŠŠŠĘŐ������ō—ÛˇoĢPĄBæm;wwwŨ¸qCׯ_Wbbb.†�����ˇ%$$čÚĩkēu떊+fŪo0Æ„„EFF*66–áB������i°ˇˇWTŧxqŲÛۛ÷ŒFŖ1ã�����Č÷XE�����ĀF$X������lô˙Č'㄄5ũ����IENDŽB`‚�����������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/profile-data.png����������������������������������������������������0000664�0000000�0000000�00000073542�14156463140�0021742�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��f�� ���Į˜o¸���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨ}xTg˙˙7ĩ͜V“L5ėĖÜ Á2 m3`eŊ Ŧƒ‰–’´ĩMZW‚n uû-Ô*°ū, ģ<؟‚Ģ…ēm“ÖÖЇm"í2Pģ ėo™éŽ0QIKšqĄ‘øu’¨Ė¤ųũ1I˜<„$pxøŧŽ+×įá>÷šĪ™$į“ûžĪ˜ŽŽŽDDDDDDDDäœģĖė ˆˆˆˆˆˆˆˆ\Ēˈˆˆˆˆˆˆˆ˜DÁŒˆˆˆˆˆˆˆˆIˈˆˆˆˆˆˆˆ˜DÁŒˆˆˆˆˆˆˆˆI.ĘFīŊ÷øÃH&“ŧ÷Ū{gģN""""""""¤|āX,rrr¸ė˛ÁûÌėuŲīŊ÷oŋũ6†apÕUW ŠP‘KŅûīŋĪɓ'9yō$×\sÍ 9Ę ÁLss3—]vW]uÕ¨VTDDDDDDDäbõį?˙€|ä#n7h÷—D"Á•W^9:ĩš\y啜<yrĐí fŪ}÷]ƌ3*•š\vŲeŧûîģƒowę"""""""""ũP0#"""""""b3""""""""&Q0#"""""""b3""""""""&Q0#"""""""b3""""""""&Q0#"""""""b’ËÍ8č˜Ë>`ÆaEDDDDDDDNĢãũ÷Îų1ÕcFDDDDDDDÄ$ fDDDDDDDDLĸ`FDDDDDDDÄ$ fDDDDDDDDLĸ`FDDDDDDDÄ$ fDDDDDDDDLĸ`FDDDDDDDÄ$ fDDDDDDDDLršŲ‘KKãīŽņÔOĢųĶ_Nōūûī›]š€3†}đ*žTv3îŗĢ3,ę1#""""""įLãīŽņ/Û§õOV(##ÖŅŅA[۟ØōŖå­ˇcfWgXˈˆˆˆˆˆČ9SõtĩŲU‹Í˜1ttŧĪOĒž5ģ&Ãĸ`FDDDDDDΙļ?˙Ųė*ČÅhĖZÛÚĖŽÅ°(˜‘ ^GG‡ŲU3""""""""&Q0#"""""""b3""""""""&Q0#"""""""b3""""""""&Q0#"""""""b’ËÍŽ€Y|ÜĀōZ'ĢũĪQfŋxŲŋ]Ŧ¸aĩÎ5ė}ŽSĢ""""""rό ŽË§pâGųpĻīü‰Ö?ū/õĄę~į]ŗë7 —ÍgūMŸÆãük˛hãXø/ŋōŋ:q!žœ‰‹ ˜IŲUɖ>‚Ą0ņ–I‹ÅęĀåöPRžœb—ÕėJŠˆˆˆˆˆČˆäθ™;o*ĀvEÚâwŪá+n`úĖų´F^įųęWøå…hŒũ÷,+æÚ+ģd2ūú™,8žgˇ>Ξ0ŗrrļ]ØÁL"Äö%_fS°,ŲLōxqÛ ÄÂĩ•kĢŠĒxŒĒ• ŗë{ ķ-ģåžDĪ…›ĶKŲō•TĖē˜úíX=íNBģŠ]š;ôŊV}‚ōēĨøj—â8{•‰Ļ�>ˆH,NK"‰ÅČÆjsāöÎÃe?—Ÿ´&|[Lj¸—Sáš°Øpõv„“=˛mNÜEŗđä^Xį3‘]? *âaŲRŖwÖ ĸá�Á@8uŸÊc’‡ĸY.ēnĶHÍĒBÉKr¯¤ĖeœŖk'Pš…ŨŗĸÜÕķįVĶKlŠ c-ž2WĪĪY¤fUeËį áû‡™Ÿ›(ž-ۉV\dß÷EDäܰōņ/~‰;§\ÍŊÖŧ~‘U?=ƸOˤøĻi|i™ņOVQ͘莤ķ‹ÁĮoš›ʤšrÅ7]Ī/+ÍÉs^¯‘˛ōņŌšŒ˙Í+ÔüæOË>ÆŨëËšąëžü#ŋ=´›§jÍ˙Ŋ€r´Ņv3QĒ—ÜÉĻ`ÛÂõl__Šŗ×ķ`"ü+–Ŧ`÷ö{YQ¸‡­ķ.ķ’­˜-›JÉîüo2Á_ĩM"UûYį1ĩv¤xõxK nžuΎ™hz‰íUa ×,ŧE˛­@<F/ĩU•$ʖâ9gĪ[v ŊÅ8˛/ĐØÕZHÉ´‡īd áĀ^vWUŌRvŪ\ëvA‹ŽŠdG(ÍåĄČãĀjI…Š ÖđؑsKĘņØÁVTJYa×~-„jkg{)-˛u—fIĪú5ŗâpØ &’põøšÄh!I"ÜŽü´}ĸ„#I,įyę&B•ląŧ8×ėLjˆČír>ﰌ/Mš:mŲ;´=ĀãOîæÎüåĩ|ī×ŋ厯–0÷ŽÛųÃ˙û8˙ųGS*|ÆS0ąŋT&åĒ‰×’Ë¯Š?‡5vntŨ@á'ƓųävžîgŌ\y5×~˛”ûøë^lē ‡ † vōßÄŽŦ &°ŽĄjsßPĀp~ž­­§¤¤īP2™xˆęU_fá´(œt-…7|‚…KVQęJYCŦ›y-΅ۈôØą‰ís¯Å9éVėíUæŽ{)œtģŌ“Úá–SĀÜ9×᜴˜Ēhõ‰ŠŽÅš¸’ŽÕņŊXļđŪp-…7Ė äjBņ!´ÅŲ`uâôxđt~Í+eõS?ĄĖŖĻjB–}žŠ ԝķv‹ôØfQRėÁ™kĮnĩcĪuá-/Ɲ ‘ØšŧÁ ėNÎsÚKgYėØrsqt}9]xËKq[[ô™Ļx¨†šL*]JEņ,\Î\šų¸<Ÿ§Ŧĸ ˇ%Âîš]DÚÖūš6ŦĀjK[–KÛë\3ģÃAv"J¸Į÷ų8‘H›ÃąHΟņ‘¸‡ķü끋Du/‹ˆČČũõ§šmæØžËŪzī?z*”éö§žūÉ+ü– /ŧžĖsVIéßÕLŋĢ‚;ŽûđOŦúG–?˜úzpë>2e*š&×ŌLh™ūZI ./ø/„ÎRÖ­B‘ņ]ŦX¸ŒÚ¸ƒšĨ+ŠpÛ ¤v{5kî päą=Ŧö¸(rTųöJ,ÅŅõËz<ˆ?j`ą$ú`ÖŠŋdü’2×c€¯képËų8wŨc°á!?Õ5M”õ&÷WH¸Ë‹Sú†ÖRZQI$ÛMÉĘRŠlI"žJ–/Éîîĩb>n—AU$B :¯e˙ļUlÜî'Ō؜x—¯c]i~į_ŠŦžļ„Čō­xũĢØp˛îõŸPÚÆē•øÂ1’t“Zš&­Ģ~ßÚUlŠ ĻĘÍvPTē’Õ+guN€bõ´;‰,ОČ6ÕÔM€ÕUĘē­k(ę*&`ûęTí K&°d;ņVŦaŨŌ3.ßËÆkŠ DĀpPTą†â>Û tœ8ÕwNcM` ÎZKj÷°Ō6 uPb€Ŧ\ŧK—öŲ>¨Ál"ŦvœE^ŧ.{w}RÃĸ„c-$é2OŽ1„õũ ɈŪåäë˜V…^ŧŗr;‡ÉDņmŠ$æ.Į÷á?%ž�Ãæĸ¸t^÷įq°z=v6 Áx HĩSĸ‰@Ÿ`$š:fĩát{ņzrGĄãŠĄ%ém˜Qĩą†¨ŗ„•Ĩ]߇ēÚm)eëÚš ߖjbîR&EjđGėx——âĸ @$† ‡Û‹kTÛ/J] ŽbŠũ$ōF.^o!ĄĒÁđ<ŠŖqĖ~ŽŲHä:pA"á(äv†-‰(‘˜ŖØ…Q G×ĒH˜8vÜļîģaĐĪ]jŗ8á]5øëĸÄ0°9<x‹=§~& zß5QŗąŠ–ĸå÷DJ¤fUŅY,[ę"Rš‘Ú@ëB6Š*ēžĶ%ˆĒņų›ˆŲ=>CûŒŠˆČĨär>6s ļŪ‹¯ņ˛fƒˇûŋ­GōÚk¯ąįHūøß<ā“<ôéOrãÕŋæ?zõš™?÷ĶÜ4÷Ķũí•Ũ¯ņōî×F÷tŒúŖ'™~}˙Ŋfūrôˇ4 RÂųu>ũI…3ôę9sōwo{ᓨ.78}ŸĄ‹ßÚc&L0”�‹ī(  l\KmÜɊįö°uM9ÅķæQ\ž†íĩ›("BÕēT/ˇˇK˛ }įŊ„pãõf9õGÎüÁpÎĸ÷0ūá–3sQ)^ ŲQM¸G‰qüÕu$-E”y­@‚š-ÕDpPöØĶŦ+˙<ŪyĨTlū9Ž <ĨÂ9ÔD$’ĀbŗuŖ ­]LŖŽuOáķīĻjĨƒēĩ%,¯éjÃHŠÚ‚ßŊ†ĒįÖāeĢ—l!ė\ÎŨûØ[ûĢaļ,y€š8@˙‹Y^ hĶ|ÁŨėXį!Vš„ōĩĄîÚF‚Đ–ĩøÜëņŊū+ęömŠdŲÚŽ.LqjV,aSØÁЧjŲëßMÕ:'u[–°b×P˙&§fŲ2 ģX÷Ô>üĩ[)Žmc“/Ųs›cĨôąZV8Á˛p+ēŸŗŌ9uŒÃiĮÛˎš�áčĀåFwURåãđ–Są|)eEŲÄ|•ÔtuÛJ4Pŗc/1›—˛Šå,Ģ(Įk‹áßQC(1„õ}$ˆÔT˛Ŗ.sa9+—Ræu VącWz÷ƒ$Ņ pn1Ëdåōb-AĒ}MCĢ×Y'Obąŋ B5ÕėŽgSTļ”e˗SæĩķWSN ­žގãtXˆG"§BˇH˜¨5#9õ}(!’°âp¤žŋ Ūΐ$ōqx)+ķâ4„ĒĢņĮlx˖ŗŧŦW܏˙Č(6j<B$v—ķôIއ‘$ŌÔ_×Ãa´×5Š\ -‘ŨũĪĸa"8p8l8˛ã=zĻE"Q’6'ŽÎŸ3ƒ~îēj]į#ˆ›…KYRâˆø¨Žéęõ3„ûnPŽŌ æÚĀâ*aÅĘrŠē¤°@ŧ°ķØŗ°ÆŌ>Ā ŸQšÄä0ųoúë÷ōN*Œų¯_ņÛߡq’ËšĒkîc<ã3ßåėüMߟĐ/ī~Wú +Ė 1üō•ŨüļŋIdNžIÍ+ƒĪ/cūųŒgŅCkØ˛áá´¯/RØc2 ôž3’î fĸŠŋVFgj‰�;jb[D‘-N<žö…‹…ÂģđĮÁđĖÂE‚ ˙ÔÃ|Ā ™ëĄĖë‚p€ēŽßYãu#`sõéÕ3üræQRœ ‘v„Ō ŒīeG(Å[Œ×H“?�Û,Jzũ9ÚS^|~ĖChÂŋm[ÂŲxKæuö ØÅÖę“–oeŨ<vģWņfÖgãßRŲ#ŒŠeŗvé<\Î\ŒHˆ#Él<ĨķpÚ;‡ÖŦŠO­Lõt‰īĸĘŖpåfVÎĘĮnĩãœˇ†uå"Օ§:3¸ĘX7/7õoë<JÜŲ$„;‡XņŽĢÅWĩ™bW.v{.Žy+)s&đûŌSļÄ}TĄhåzŠ]vŦö|ŧkÖ0ךū°3„ãV €Õ0F§nC`u•Ręͅ°Û7˛nãØ^ũūP=û øëb؊Jņ:íX­VėŽĪã-48Ĩøã1ZW>vĢĢՎs^9eeEŠá‰ƒ­ī-&nÁQTLQŽĢaÅîœĮ\ˇX] g˜isáíęQaäãrd“ŒÆRįpĻĮ-‰8‘@ ūX6Îî`ÁĀé]ʲŌĪã˛[SíčœEĄ-™ęQ1”ú˛ŪæĖ…HĒį@¤)Šu’ "]Z$BÜp¤ziœA;ˇNŧž|ėv+F"L]&ÍÃeˇbXí8įyqZF1)Nĉc!{ĀVŦ$ZFaØ]ŋ×l¤ ;´Dˆt~[ˆ†#`ËÅaØq8 ĸáŽáAMD"I˛ŽTOĨĄ|î:%ŗ],œ—ē'ėšŗXXd#Šë<æîģ!ŠŅŲ…ČĀ0NĩNÂđāí>ļ‡"§åÔį¯Ë@ŸQšÄ|ˆ_Ũ{ŲI~ûōãŦ{ôE^xq?Ø´‘uÖrøęëņüMšĶoâÖOü5WpYW÷ô3LíYrâŋų—­?åĩđÛ´žŧĶÆąđąmkՐßČdîų|ˆdöž’š? gúseęk�}SŊxkęz/wŗąîiŠûû­9&’ÂÛ)vo?Í1#‰N7ENØ ŅŊŗ7‹ÍíÁå2p&€/^${ “Mą7ŋoqÖá—ã)/Åąc;5•VoNuŠûĢ %ŗņ–Îë<§Xęœ…}C§ ‡…^ķۜáx'mėšĖâ`îē§X7¯ķ„ũ„’6Š=š=6ķš°ėŠC×īé6§‹îœŗ(˛m§zŲ$*Ęņz<9­8ģ^•ĻĘuõœƒÁévaŲ"ÔD÷¤VG·+Ãj@"ŪŨ›Ā0bø×ŽĨĻ.L$ž ‘HĻnDĮ˙Š!W'ü|<…I;î0Ž3âē ‰ÃSJ…'A´)L$ÜđHM~ŋ“…%Ĩ¸ė@<B,‘ĶŅŗģ˜#׎%!–�ģŨÃę'´ŖÜnœŽ\vģŊķ: ļžˇh´ßcÚ6,ūą´ûĮČļõ¸Î tÕ:ĶãWĖĮÖĩžžË “ŠKņĻŨ†'äķq$%ž„D"  +Ėi;ÚØ u‰‰€Íë Ž$đX b‘&pt†ēgĐÎŲ6ĮŠ7-E›ˆcā´Ĩˇŧ‡ŨgđŦ?$gĢWā¯ŲHYlž�‘¸œq"‘V—ƒÔgˆš01ōqôž_f(ŸģÎjö¸6€ÕfÒčŧ~ö!Üw#`Øl=Žmąô*9āgTDDä­}<ûĮzõ$yŽžĘ­ÅŪ>om:ôāÂėá>īžhā…Ÿ4đÂĘ8ŸÎįôŽĻĐeįgŋyã’ėˇˇ 4˜ąaĩąÎž3=~˙4p—˛0íA4Ŧ!ãôŋč9ËŲ˛ĸ¨˙ŋxV€\ÜnT¨K”cOÔŒ¸W¸Ā.[ ~xō ų$-ŗ(ęw¸ÕĘq–RâÜÎ&_5><x‰ãĢŽ#i+ĨŦk›Î ÁbôßĩŪ2@sœ5ŽRļl-ëŠ ‹[Žĩgũâ Ĩ*š–ĒžВvŊ kúYxX]û“ļlcĮöėX—ĀbsSŧz}Ē÷K<A[īÉuŒÔëՓ-= Äē’;Š6ŠYˇî)ÜN+Qv,YȖĄĩÄIÕĨ×E°é †sœQ¨Û1°įē°įēđ�‰h€š>|žÎrF"A‚‚Û×ėŗ¯D‚Ô|ØöúŠ Öô%ąX¸ŧŝ)l}/ÉÔuîÛY"uiOt_į3<îpŲܔģēį|2 +FŸá0Q|•U„(Äë-Įa70ˆĒŪŽČõdŊՁ#{/‘XŦ"-v íŠaKūH\á8ŠrS‡n;'é?Ë(vC˛Úą¤Ĩī‡4ņԜ%Ųø–CēfŖĀęÄaõnjG GZŒÎad€Ã‰Ŋ„Ŗ`‹EˆšŨC„ŌįŽŗîÖŪĩîŧ~ĀĐîģáøķ7ômDDäRņ'ūīŋJ[ôÎģũ<ÔŋËĩ[x0\Âú%7pUjCZ˙ØĪÛ€Ō˜`|(—ŋ?—ŋŊÎÎGŽ|—?}ƒúˇâœÄāÃ×|Œ‚‰WsÅÉ?rė7˙Å / iāSÎį@&å/?¨V(“î fōS“įÖÖQHP<¯G˙<åëIĪB|øÖPœÕšúõŨâ pÖ,û›¸ĢČCvÕ^|!(Šī%l)ė D\¸ŨÕĄ:â€ŋŽ‹§ˆĸQ/'—’27[Vû¨Ų•ĀëņQJā(/95‰fg “ė zNø˜ iÆ3†§3āaTVŪ-Oąŧ÷¤œ†m ‹cuQ翇”ŽéœčtûZV/ŋcĮ~V[Są–^ûÄ$°ö lN'TMM$›â›)înė(ņŪå$5Ū€X¯kВH[0œãŒFŨ†"‘ ŅOāgØ=M đؑ(1\8 ƒlœĨåõ™Š k÷ޏæ•⚉xá€_u%T܇×>„õé,ŠëÜŌûOę§ p&Į6+6ģũ´ņ�ŅáX6ŽŠĪ“Ūá+ŲûGԎŠá1H #L4ÛÁBŦø"Dãą„I]ŗŽˇ;{<ôŪ/ŅįdFĀpā°A &îņôßļMab‰ėažÅh×lT¤æū FbD#Qĸ8N…/† Gv‚H$N$ÅbK›w¨Ÿ; īŨîŠëg ũž9'š9üģ6æūUÚ<3ŽORėzƒŸ…š{õš1¸vĘøÎP JũīÎĶ`Ęįîe_äÆw-¸‚Lŧ{mwåÕLøÄ|Čû(ÛļîāWCgÎŊ&^ŽöņæÕWrU÷,ž9~ē Į¤Í‰øøÁŖû9ŽTχ tŽ(*-ÆF˙ĻŒxö ĢG6Šy]ú[ŸčõAžåÅmiĄÎß@Đ ™;‹Ž3nO!„ölJÍ ãôÎ:ũ_RGPŽĩ¸œ"Kí^žjBI'%%iCĻė6 î;d)\×kâā툺ˇĨ…XŌÚãuĩģÅj?}[FCøö6u˙×ČõPŧ~%^K áH\n\–ĄPĪņá`ˆ¤Å‰3wˆõ‹'H`OŨ/Ũ…Ô°;ÂЇN8\8i!ŌcÍÁ`Ú˙‡sœŅ¨Û`âĒ6n¤*Đß, b- °XSŊ ŦlF‚–„Õj=õeš %ÜtĒ,ڋk^j’ØX,1øúŪėvlF ąHĪúE›b$ öĄ>MŸéqĪĻD?aG4L8Ũc:FĄšˆD…Ŗ]함ØZ"DÂĸŲŽîÉe‡ŨÎV;vÄ{´a”Hd4“b+n"{ņ…úšOMø|u$lē:�¯lÎ\,-ęšbāČMûĨĻsž™H€p,5ėŠûöĘįŽS"ë1,(‹‘4ŦØŦ íž#ÕĢĨįČ8Ņčy3ģŧˆˆ\4Ūå}‡č9âjnŧã>6lx˜ī•_ę>ēžšŸ85!Í_~ũ_üO¯72.§`ū´Pf>|ˇÍ˙ØyÚģ"ÁņĐ~öüĮnj^éú:HėS[ôĘ\ų7°]ŧ›t‚ã‹ŲĖāYÉę…ŲФbņüũÎO'Tũ�›|ЇÅĶ˙ąÜÃBo6$ũlßÖÔsUb/+æŪ@ᒗŌ&ôāuDCÛŠ ļ]čęîbuģ°'øļī%Œ¯{ 'Á”cĖŖÔkÜģ…ÕÛCPXÎÂܞeģ]D|=' &¯ō%Ųe*ceÅŲ„ÖŪËÆ] Dãq"Ą—X]2N?éc¸’ÕËîdYå^ÂŅ8Ņhūm•øąQč4˜Į’bu`ûŪ&ĸņ(á]kYQcRyÅi{5õáōā˛„Ųą}‘xœH ’eĢbē ’‘ĐĐŪØc÷RRūĢ¨4m á[ĩ"íŌqR“_'øņ‡›ˆ:GĄnƒąē(rۈû*ŠÚ Ü%i ᯎÄ6˜4˕z¨7RķæDũÕøÃQâ‰ņh_å6ļׄRĪvņ�ž•Švˆ'ˆĮŖDA"Xq،Á×÷f8ņfņ×hŠOĉ†wQ[Įæö }Ōë3=îŲdĪÅnĨ 4¤Ú°)@ĩ/ŽÍa!M ĄžC9G.ŽD˜`$­ë͆Gv”`° Ãá<՛p¸íluRč€#ū]„šâÄãQÂģ|DFypĨá*ĻØeŠŲÆöšŊ„ÂMDšvQĩŊŠ`Ō‰ˇô4ŊiÎ#Š6o"TÃępôøæČĩC$D8ž6Ä †öšëŌ &ĐÔšM�_0Fļŗs^˛ĄÜwØąŲ,ÄÂĄÎ˙'ˆ|Ô%͝ggīÍh˜H45„LDDdXŪ~g÷õ? î™FgXņ!>^üiŽíZqōMjjMÛšŠáOĄŗŋ7M ,ËY@îčWæŦ;Ę|Œģןzs͆eŸÄüáĐÁA_ ~1;?Ãļ!ąâŨüš“ÕĩÛŠ(ĒÂá.ĸĐaÅJ‚x,B0"–‹Ŗ˜[7wž­¨ž•ëYč_FíĻ…”„—Sâu`ĂÔnOŊÚuáēyiŋÄ[q{$7úđ% ŠŌ'‘ɝEaöv|5~’ļRÜšŸÃHĘ)*-&ģļšēˆAŅ&o¯!XVŠ+ŧl ÖPĩd1ņŠRÜÖ8G|ÕÔ$<¸-ž~æ8?xÖ?ĮVëZ6Ž.ĄĒ%.īĒÖūôRŗ6Sĩ~ëļ<@Éē’[n%›žbĨĀĀŗū9ļĢØ´b!›ZĀbsâ]ūĢ—ö39ķéXKYˇ>žĩ+đî€ėB/Ģ×oÆ]EŨ˛í”•ĀŽÚYƒb§të&ŽŦXËē%5€OųV—Že™/~Įyĸ˛R+*ŠXü%ũ÷öé=BėĖ8æ•Sfߋ? ĻÎG2#ĢÃAQŲ<<š§>hŽyå”>|žJņ$6ėÎy”y]ЇĖÜĪSâŨÅî@ Už’†…ėė\\ÅĨÃ6[ßũJđá¯ŨÆî8XŦvœEĨx=g0leĐzC†‹…Ū&vøkØ„l‡“"īįq&vÛá§ĒʖŽB;ļŽDu'+VƒŨÁ…=†ũ ˇ­¸Š‹‰ÕøđíØØp¸Ŋx }TųĖCj4œÅK)sÂøk¤:sŲqL*fÉ,W÷¸į5ÃÁ¤ė$‘XvĪđRķĖ$ÂD 'ŊGd úš ö"/ޏŸĒ-QZ0°9ŧ”xķ;_ĸ4”ûn.ī<"Õ{ŠÚHÍÅVčÅ[§ēģ[ρĶã&X¤Ē*Œģ¤ôŦ6™ˆˆ\ĖŪåÚ*ĪüwNšēįäžUĀäĢÍąëJ¸uJgØqōmv?ų ˙y^ö–0ČÎī#ƇNõ:īŊË; 2|éäųíĄŨ<[ąš…�� �IDATÛtIĪ93ĻŖŖŖc  šššFũM$c.û– TķXU5ū@˜hK‚$› —kŪ…e”ĖËíĶ[Æ÷Ā ,¯u˛Ú˙e]§QŊņTų‚DZ`ÉÆá™GŲō•”ēzũRŪ@ŅÂíÄpŗ6ø4ĨŨĢø–Lcš?Aö§nö |Ėa”sJˆu3S÷˛åõö<Ev­eŨĻ—DZĀbÃéŊÕëÔÎ]L•u%žÚĨįĮĢŗEDDDD䒰bõÚėmđą7sÛMŠ!0ũhŧÎķÕ¯đËįķŖ~>w¯˙"7õõQ]ŪųÛVíāWgĨNŖīĘĢs¸ŧ­™ļsx)6­[3ĸũ;Ūo”j’FÉÍÍp›‹"˜šdEĢ)›ģŠpņS×÷ûę'‘ķĘȂ™NF×åSčü(Î4āäŸhũã˙Rj îwņ  ÷…ÁĮ–Q1ķŖiæ$oî{†Õ6]Ōķą æB f.āĄL—ē8žâdE…Bš„$šŠ˙ŸũÔ˙ŲŽoÔnįëŊ=X. f.4Ņ�5ū0ÁšmÔ“8ĘÖS‘kvĨDDDDDDDd8Ė\hBÕŦ^]žŠõl\é2ģF"""""""2L f.4ķ6SwdŗŲĩ‘Qp™ŲšT)˜1‰‚“(˜‘ ۘ1f×`xˈˆˆˆˆˆČ9“ųÁBG‡ŲՐ‹MGY™Yf×bXˈˆˆˆˆˆČ9SvGé…ÛĩAÎ_—áË厙]‹aQ0#""""""įLŪߌįžĨ_"ķƒdŒúāųÚW—pŨfvU†eLGĮĀ}Țšš°ÛíŖ{ĐË>0Ē剈ˆˆˆˆˆˆŒTĮûījyŅh”ÜÜܡQ“(˜1‰‚“(˜1‰‚“(˜1‰‚“(˜1Éåf´ãũ÷Ė8ŦˆˆˆˆˆˆˆČyE=fDDDDDDDDLĸ`FDDDDDDDÄ$ fDDDDDDDDLĸ`FDDDDDDDÄ$ fDDDDDDDDLĸ`FDDDDDDDÄ$ fDDDDDDDDLĸ`FDDDDDDDÄ$ fDDDDDDDDLĸ`FDDDDDDDÄ$ fDDDDDDDDLrųP6ŠD"gģ""""""""‹Å2č6C fœNįˆ+#"""""""r)ijjt e1‰‚“(˜1‰‚“(˜1‰‚“(˜1‰‚“(˜1‰‚“(˜1‰‚“(˜1‰‚“(˜1‰‚“(˜1‰‚“(˜1‰‚“(˜1‰‚“(˜1‰‚“˜Ė<}įō&MÆũĄļĒgÃg'“7i23×´]ˆÕŸNmˇč™ÃŽSëÎĨäMšLŪ/Ō:ėR.BɗšûúTûöūĘŋąˆ™ŸŊ¯|ũ{<íoŧ¸Ú­ûŧį°ļŪėʈˆˆˆˆˆČÅʔ`f֜<�šƒ¯røt{Š9zē펾ʁã�ųĖuÕzJē rōō)ČīúšĀXK’uė~ņ1Ö,ųîĪ>ĀĶõU<#""""""rV]nÆAĮ{fGAƒÉãûnsĸ3´ÉȀö†W9pĸ‚‰ũä.Į‚Aōf0kâYŽø%-‡…ŧš‚^‹[spßËüxËŲŨø kJŪ¤ņ'O°Æ“eJ-EDDDDDD.$æĖ1S0›Yã�ØėoøQ’ƒ{B´3… ;ˇë¯'F+÷4�ãžÍäŗWc9ŦqL]PÁŖ/ŊĀڙ9ĐŪĀã÷Ÿ:Έˆˆˆˆˆˆ ʤÉķ™ëÎÚ9¸§ŽdīÕÉģĩCŽ‹Ī-Ę'‡vî~;2™5'ŋĪQZë_fÃ?ÜŜOyȟ4™ŧ님Y˛Œĩ/ԟŅ|(­õ/˛önë,g ųŸZĀí_ßÎŪc}j‰C?cí=ˇ1ķSŠštōŽ÷0ŗx)+ŸØGī]Z_HÍmãúú>H6RûK™ķŠ)äMš‚ëS‹øĘĻ}¤ĸĢV>ķ˙pûg‹ČŸ4™üį°čļsā4ĶęŒÖš™%;ųŸËš_áûĪ4öއ_ø.÷•,Ā}cĒ]ōģëJĢ͉î9ˆ<ÚģŒSŽ>ē(UƝ/’Ūįüŧûq&×˙Ä3wĨŽ˙?ė#™l¤vũRtÖ=˙Æ9,¸į{øŽ&GŧO—3iŸô{3yôeV–¤îŊ™Î %""""""g¤`ÆÂÔ9ĶČÚ¯r ÷3dũĢh†Œ‚L-˜ÉÔ h žĘÁŪÅz•m@æ4æz,=VØũ� JVđã—C´æ¸đŪ\Ę-E ū<ūĐ-ĖšįeŽ ĄĻG_XÆœĪ¯âņ—HŽs1kŽ›ÉYÍ^|„/}ö.6J¯|’ŖĪ,eAéˇy|OŒ›Á-ˇ•rË–ãûyū;_aNÉw9öląd$ÛŪäé{ngåžVÆģg§‚Ģævo[ÎŨÖsđŅģš}ũ/HŽ+Ä;gcy‹C/?ÂŨ÷Tö™g´ÎũŒeÍæĢ‹&�P˙âĢiõjeī×ņš‡Ēøy}c§Ėās7߄דC˛ū<ūĐí,øúžÎ``,ŪEn2€ú¯žfnĄF|;€ Ļ/šM×7ĶÎģÛpŽęžMļ5đô—oįkĪ4Bž‹YsĻ1žˇ¨ßķ_-]ĘĶGGļĪpÚįÔŊŲĀŋ>ôMjg2Ų=Éyg§ųDDDDDD.EƒxķÍ7ÛdxZvvÜu]AĮ„kgw<|°įĒ7~|sĮ„k :n~üXGGĮī;žēŖ cÂĩķ;ūųpĪí~ŗq~Į„k :œ÷BGKúŠ˙}ļãļë :&\;ŗcéķĮ:éë~ŋ§cÅßtL¸ÖÕqÛOĒÎĪ+:&\[Đ1Ꮄ˛Ūx˛ãæë :&\wsĮÃŌĐŌņˍ7w8¯-č˜0osĮoēˇßÖ1ŋķœVø~ßë|u<ŧĐÕ1áڂŽkĒ“īŪT9ך;füŨŗo¤UöĮoM­ûøĖŽi{oĮŽ˙M_ŲYˇŪí2ŒsP"í:|ķŽƒßę˜vmAĮ„ë*:vt5ŲáÍŗ¯MĩãŋîQŖŽDįŅ}oÜÜņ/oôsŒ7ļĨĘûøŊ5]Į0ûŧ;ëuĻ×ŋûžģÎÕQ¸psĮ/[zîķĪ‹SûūũÎîûr8û §}]÷æ'gvĖžãÉ÷ψˆˆˆˆˆ n(™ŠI=f€ŦiĖ’ŧÅŪ@ú•Ø×�L`ēg0–é3ķ798žļŨqŪ`ęœ¤O5{đÉíÚ!sÎCl\4Ž}iÆÎfÍ7o"“vOūėôo{>[ÉĄvČYp?ö˜Ė6‹ŠËīgaŪ5äe4rđh×q̍o‡Ė9˙‡5s{ÍTœåâÁŗÉŽīüYß^BíøŌˇoebZe'ɟš7§­•ņKŋÁâņé+gķš‚T;m<UØhû°+`<@{3'ēÆåÜĚnbÃ#ëšŖ gĪ&ËÄ/đÅ)�or°žs‡ŦÜR” 4đķ=}‡3}ņßh2gیˇķ˛˜~یôúį°øá{™šÕsŸ¯­˜OĐæ™ŊŊĮÁ>ÃiŸîmš3ņŽ*īqoŠˆˆˆˆˆČč0/˜al÷ŧ0{öŸBŅú:ūC@Î4Š:ß�4Ņ3qĀá=ÁSķ`´ņ×äSÔã5Ųŧd0uAĪĀĻK–g6S3€ÆO3GKz9“‹\ôy&ĩĖdãŋīaOÍVî˜ØšũĄf�&Īé˙¸–)Įm č7乙ŪûíTcs›0ŽéSÆõZ™ÃØą@;­ÉާüŅ:÷‘ČčlĢ$m흋Æ0kî|Ī-HÕ)ŲʉcĮ9vė8ĮŽ5“´¤†Æ´ļvíÅŦ›gIÙęy~Ī›@ŪE3:u>œ÷¯Ž›šSú&–)3Rû´7pčøp÷aû¤}EDDDDDdt™ōēė.ã=nō¨Ŗą~?ZËYœÉC¯r 2Ũ3™ÚĩaÁ ĻgVņüĄW9ŧ¯’ũl§Ÿ×d7Ķx ŖĪ|›ûv÷wäæÎ č8õĮ~^Ã}ǜ,Æį åÕĪ]Ûį0~ÜiļĪĘa|ĐÜŲ›$ũa73§īŗĨëĄ;“q9}‹;õHŪĖŒÖš@˛­3<Ë$'3mņŅWųū–íÔîĢãxÛāÅd}Y™¯đķ†Wđ­āīģŽqũ+øœĪpK÷ŧBįÁyôúË§ŸˇÆƒ%‡ą#Ūg„í“3nô›KDDDDDD�“ƒ™ÔkŗŖņx Éâšī ŅFEEĶNEž žßSĮîCIŧ ÷ŊN0ŽĪk˛“tu 9|…Ū zj§ĩ˙—פ•“q*ĐPļˇtŽKvörąô\wÚ˛‡:†d´Î}ø’ĄÔ~Æ8Æw>Í'ë+šũÎâPdæßė¸)ŸIV†HØļ‚Į{ŋčĮâæ–99üüÅÔpĻŋŸ˜ĀᝯŌŒ[p3Ķģ›Åüķņõˇœîú[ēËK{ŸļĪi#""""""#en0ĶųÚėĮ_læĀžĖÍá@đ- "wĪ9]ĻĪtÁž×9°§<ā6™Līķšl –, 9‡/VųYįnŨ,deÍmC|ī|noë~îĢëŲŌũfŅ5Zį>\IîLfSÜLĩ�œ vĶ÷8Ô9s6ąķ_æ÷ę}‘„3xœö^eY˜žč3äŧXÎô•<&RĪķ;ß&°p‘ĢĮļæžwgFrũ“ô}ü¨ís>´ˆˆˆˆˆˆôĮÄ9f ũĩŲ͇B=ÂŒ~į[ëžApüPĮŽÕq ȜŅį5Ų0Ž‚€VŽī=[ę™GŪ8€6Ž5žfR’d’d2Ųųpœsjûã§Ųžõ8ĮZSێī=eˍ­sĻc˙Æ÷w6Lŋũ3Ė›ĩ9ĖēĢw(pœCŊC™‹įfŧ〆WØ{ 8ôoøŽų_ā–sž˜|Ūˆ¯s#ũî•læD×>ŊoČûœí#""""""ũ19˜K÷¤Ŗû9°o?‡Û!§Īđ$`ĸ›éã€ú}îã01evÚp–.ã˜îšhįĀÎũô˙ÚȁŨAŽž¨+Ė8ĻNš€ƒ{ö÷ķ�bõg§RpÃ\Ö�ō: wžŪīq[¯ĻæÅəFŅÄ~6ąŅ:÷ah ˛öžīh‡Œür\Е"t'ƒŦ~: %•ÔvM„Û§Ģ‰‹[æ\4°{ßqî|•ã@Áĸųôl>ĪģÛ¯˙ņ×9ĐĪëĸ’‡‚Š}2ķ™’7Ü}·ö‘ū˜Ėtŋ6ģŊŸ>ĸ­ßáI�ųš2ĄŊŽĮŸ¨Ŗ˜:gZŋo˜™|Wž hß÷OŦ|Ąą×pėũĮåÜ}īŨ,zčåū{tšz[9S2 }ß#Ŧ~áxZ9­~ôģÔÆŨÄ-žŪۏĩģ{•|bk7˙‚62(¸ĢœéChšá­s˛ä īüw~)7´Cæ4Ö<roZ°–GAĀ[ėŨSßŖ>­õ•|åĄ×ëJÍ|âxsŸâ§.šOppO%Īī{ 2 ų✾ŨÎųy÷cd×ŋĮŋķ3ŽĻW<ŲČĶ[^ĄȜ9ŋŸrčûœí#""""""}™<Į tŋ6;XG}CdĖč÷Ā`aę\/ī§ž Ÿš3OķŽ˜ņˇ˛ņŸƒÜūõWØũĐ"f>áfzA–d3‡ƒAę›ÛaÜgØøí›~ÛĖÄr6ŽÚĪíßÚĪî‡0s›‹Éã,´6†8tŧ 2ōųę#÷žz{ÔÄr6>äö‡~Áķ÷~ŽƒîLÍ˄æ79xãm3ķüāîŪ]FŅh{ÍÔŪŋˆ§–$ۚ9qĸšûĩؙy_`ÍŋÅâ‰é×o īū ßŋ˙4nģ›õŗ™>ZCė 63ņ'øÁØG˜ÚOķ ßænfđšÛŋÁ⎥J7áÍ{ŒīĢâ§@†ûVŧũŊŠčŦ÷[<÷å9ø˜Đwü]›xæî‚‘]w) O|—Ÿ~‘Y3ķK3Gƒû o‡ĖŦY1ģoy&ûœĩö‘‘8‚™´×fĖ`ęéŪ6ėžÍdös oFŸyhz”š`3;ķįķô–Jž†đŊØF{F&9ã]|nŅ­|ué|&á-ØoßÆÎ‚Ÿņũ-?cīĄūÆv2r&0eūžēŧīĞOėmeOŪ‹|ÛĪØ{h?ĩÁ6ČĖa|ŪM|õö%|uQAŋŊ|FĶh{Oí476ĐŖOKF™Y˜2eŸ[p+‹ônclæžĮÚ-¯p`ßŋņ͌LÆLãŽnáksķ°$īg͞ãlØķ&vÂÄé{pËÍųüxsŠškfŸ688;į mÍo1āžŸš#gø×ßÅ×žē•‚-đøÎWØÛÜ™×P0g>_ûæŊũ‡Qg¸ĪŲjž1mĐÔÔDnnî9ĒŽČĨĨuįRĻÜŋÜë9ôÔÍC í†ŗˆˆˆˆˆˆœ{CÉT˟cFDDDDDDDäĨ`FDDDDDDDÄ$ fDDDDDDDDLĸ`FDDDDDDDÄ$šüWDDDDDDDä,Đäŋ""""""""į13""""""""&Q0#"""""""b3""""""""&Q0#"""""""b3""""""""&Q0#"""""""b3""""""""&Q0#"""""""b3""""""""&Q0#"""""""b3""""""""&Q0#"""""""b3""""""""&1)˜9ÁĶ%“ÉģķEZû[}t; &Má+ģĪuŊDDDDDDDDÎõ˜1‰‚“\0ÁˉĀvî+™ƒëúÉä]īafÉü(p"} ö>ēŒŸō?i ŽO߯Ęgę͆JYũŠ)ÜūÄ>ūõËsČŋ~)Īĩ†:—…đ­ŋ‹9ŸōŊ‡™w~—Ŋ=ŠōŖ{ncæSț4×§qßŖÁ´˛ģĘ ōÜ×SÛå_?‡Eë_åĉ ?ēg˙ŠEÜ÷L=É!×YDDDDDDD.fF0Ķú2Ģīų!G žÁ3˙ž˙KÛx°āMž˙åU<w ÉÁõwķĨ-o1uÕ6öüb'O,Īãāúģšī…㝅X°XÚ9öėņģŋÁ3;›K;‡ˇ}—ŨSžÅÎ˙/@Ãkßaúņ*îÛ´¯3@9Ás-ecc_ûÉ øņsžX•Īá-ËYšķT„bą´søÉRŋ` {ūįûū9ŸŖO~“E_ŽÄ˛ü ‚˙`į]øÖ˙ĩCŽŗˆˆˆˆˆˆˆ\Ė.Œ`æxGÛr˜žh6“ĮcüD ~”žēŸYY@ëĢ|˙™7)Xž‰u \Œ?ŽŠ‹žÅēE™øˇũŒÃéEeÎgŨWf3ĩ ŦÎeɂ[Yŗ  ĀØŲÜâÎĄ­žc�dáũö ė~ė[,ž’ĮøņyL]p/_,hã€?ÔŖšÉ‚[ųZŅX,ĀØ™ŗ™Jx*øģ‚,ĀÂÄ9ŗßū&‡ĪŦÎ"""""""rqēÜė IŪ f{Œ§īŋ‹ä]ˇ2×3éc™<elj}}ÃíװГ—ļ“…Š3ķÉx6ÄÁ0šsĶq.Æ÷*~l^~wH`ÉʀöļÎ3˛,Í<ˇéŸXjäXk;Éd’d0ŽŊG9ãķ&œ*Į’…%Æ\“V°…,’$“@ãĐë,"""""""'ķ‚ @{Ú|+éRĄˆĨ{[7kǟ!oÛvžōÛüô;mdŒ›ÆâßJõtim#É[<^:™Įû”5ÖV 3ä°deô­ŠĨĪĸS’!֖ŨÍĶ_`Ũˇbz^&šyîž[øū įNSøÔYDDDDDDD.N&3Ydee@ãqNĐ7H?Î r›žbŦ‹;VmåŽUĐz4ˆīÉbíũKąŒÛÚŦL,\ƒ÷‡Ûøj^¯Â,Æöî"s&ę_¤ļ1‡ÅÕßaņ”Ž…Í´ĩ L€ŗYgš ˜4ĮŒ…és\d4žĖķ‡z÷™9ÎsÛöĶ6ns RK’ĮBøüŨŊk˛&ēYŧę~fe4s¸ąōÜLÍhæX[&'æúkÁ’5ŽĮ0Ĩ3ÖÚN’ÆežZ”Ŧ_#Đ~ÚŊw6ë,"""""""Ķ&˙ģā!žæjæņ/ßÎĘG_Æį߇ī…JVßy;k‚™ÜōÍ{™Ū5 ¨ņgŦžg)÷=ąÃĮNpėX={Ÿ¨â�ã˜ZYŗųŌĸnZÁ†Ũõ;q‚Ŗ‡^dõ Xp˙‹œ°&ƒ(p19ŖįŸ|•Ŗ'Np4PÉ}˙xœÉî ڏ…8ØÚ˙`ŦAÍ:‹ˆˆˆˆˆˆČÁÄ9f øû§^`â–īņãgŋMíņ6ČĖaâ”ΰļú˙pĮ”S}F,Eß≇ŋËÚmĢXôfÚ32—7Ålãk�Ļ?ü$f}— ßē›mnƒĖ Lķ žXuķČĻjûÖ=âžM0÷YČqÍįÁ‡ŋƒ÷Äw9|ĪcÜ~'ŧP3{8 pöę,"""""""„1mĐÔÔDnnî9ĒŽˆˆˆˆˆˆˆČÅa(™ŠiC™DDDDDDDD.u fDDDDDDDDLĸ`FDDDDDDDÄ$ fDDDDDDDDLĸ`FDDDDDDDÄ$ fDDDDDDDDLĸ`FDDDDDDDÄ$ fDDDDDDDDLĸ`FDDDDDDDÄ$ fDDDDDDDDLĸ`FDDDDDDDÄ$ fDDDDDDDDLĸ`FDDDDDDDÄ$ fDDDDDDDDLršYnį]ŽŊũ{N&’´ŋķŽYÕ‘KTÆ—sĨaaü_˙W˜‘˜rÔöwŪĨ፞:›^y%c.cF5DDDDDDDäÖņ~‰övۈ˙1‡)áŒ)C™ŽŊũ{>|u6W\ŽPFDDDDDDDL1æ˛1\iXČÎúĮŪūŊ)u0%˜ų͟OšÖEHDDDDDDD$‘‘ÁÉDŌ”c›Ėŧ÷ūûfVDDDDDDD¤1—1mū[Ŋ•IDDDDDDDÄ$ fDDDDDDDDLĸ`FDDDDDDDÄ$ fDDDDDDDDLĸ`FDDDDDDDÄ$ fDDDDDDDDLĸ`FDDDDDDDÄ$|0ĶPõ wûßyÛ늈ˆˆˆˆˆˆˆœĄ >˜šP)˜1ÉåfW⌴ü†˙ZÍkáī\eã†9Ĩxúnį^ū);öáíÖw¸â#åã7}‘/ūíGšŠ?đÚwžÅO¯ŧƒīß˙IŽęŪį/ėäA;y3›ž9›œsyN"""""""rÉē€zĖ´°ۏØũ(_\šžMß\ŒÖ]üÛÁŋ¤mķGŸũ›kūĀÄۖ˙˙ėŨyxUÕŊ˙ņˇŊœ’@IdH˜’ ƒ$( Čā\+­"NˆUp¸ŠŽUąˆ8`EëPkũ +čpB´ęUA+‡Ā!`P@‚‰—$@ 9\ķû#Á$0Œ›Āûõ<į‘ėŊöZßuÎ_~žĩÖæūqwsķÍɞūOūë )Ũz'ÁŌĪXXõąâ¯ČX )Ŋ1”‘$I’$IûMŨ f đÁRčrÆú$7%6ŽŨŸKˇč-•mŠį3ķÃĩ´>ãr†öH".Ž)ÉĮ ačqŅ,üī9| ÄĻC2ËČXT™Ė/šKIôLŨ˙ķ’$I’$I‡ŦēĖä.'ŸXZ´ŠŽrą9)­TūšfßniJJ‡æUڄHîœH(w9Ų…@lWz&CÖŧe”G3Å,œˇ :ôĸ›šŒ$I’$IڏęÎ3ÅŠŠGl¨ęÅĄč*6m"ÂĖē8ŗļë ÅÅ@l,Ũz'1uúįdEŌčųŠŒErQWĖe$I’$IŌūTw‚™č!ļPŠz1Bqq•Ãb4 DSú\3‚͡y>"ļâ�™ØÎŊHŽŧ@ÆŌ)ş“E'†ĻG#I’$I’´?՝­LMiA!kVV=ĩw%Y9UΘiŅžäz…ä7 E‹æ•ŸØĄčĻ•oaŠíHŸ˛æ}ÅÂĖeĐš]Ėe$I’$IŌ~Vw‚™¸4ú$ÁÂ×ĻōÁŌ\ō×äņėLFǜ1ÆŠĮŒũōSŧ˜š’üÂBÖdĪeƟĮpį„šūÔ0–.=ÚSŧčf.‚.ĮuÂ\F’$I’$íoug+M9áŠËYķô‹ŧøč]L 5#åøķrÜ ü5sSE›)ŨÄuŅ/đâŗ0Ģh4hFrúyÜ<¸Wĩ3dbĶģ“ōė†ŽaHįPMJ’$I’$íS‡••••íŦÁŠ+hÛļí^4ķ̝IhŋWû”$I’$IÚ]ĢsķHītä^íŗ6™JŨŲĘ$I’$I’t1˜‘$I’$I ˆÁŒ$I’$IR@ f$I’$I’b0#I’$I’ƒԂJ�� �IDATI’$I’¤€ĖH’$I’$Ä`F’$I’$) 3’$I’$I $˜ųÅ/˃$I’$IԁĄėĮ2ĸBõ;„¤QÃlÚ\ÄВ$I’$IÕl.-ĨAũp cĖ$ļ8‚‚ĸ ””–RöcY%H’$I’¤C\Ųe””F((Ú@b‹#Š!u:QĄz¤ūĒ ĢÖ|OŅú”FļQ†$I’$I:„E…ęŅ ~˜Ô_ĩ l+S0ŖR>ų¤Ö-ƒ^’$I’$)pžÂ+I’$I’ƒI’$I’¤€ĖH’$I’$Ä`F’$I’$) 3’$I’$I1˜‘$I’$I ˆÁŒ$I’$IR@ f$I’$I’b0#I’$I’ƒI’$I’¤€ĖH’$I’$Ä`F’$I’$) 3’$I’$I1˜‘$I’$I ˆÁŒ$I’$IR@ f$I’$I’b0#I’$I’ƒI’$I’¤€ĖH’$I’$Ä`F’$I’$) 3%üķę4’Ú÷äĘwŠļŋ÷<tîĮûŋ2I’$I’¤ũ%ā3ëyįOđiI°UH’$I’$!Đ`ĻQßßĐ¯h&÷NÎ ˛ I’$I’¤@ģbĻŅ錑BöÄģynÕĪ´Í›Į“WĻīŅi$ĩīH×ۃ¸nÂ<*7B-āŽŪi\0y/ŨVŪ.ĨķÉ ēī=ōōæņäՃčqt)ŊqŨ´%T.ŌÉcö„kĐģ')íĶčzÂ`n™ļ„6XI’$I’$íU3%D‘|Áí\˙=ôŪN<^yÎIâú§f0įũי<*…ŏā–7*Ÿ ‡KY<å – xœwŋ˜ĪG¤=å ēėÂ#&3ī‹ ŪÅŦûÆņĪŧō 2īÆ%Gú¨‰ŧûūL‘Dæ}ønÆę}˙H’$I’¤CZđoe wåú?„7īįąų;:l&†SīžÁ;“ÆpNZ‰‰I¤¸†!Šëųt΂j-KRĪįú~ņ„øž'‘ÎzčųŸ\š„I>ų$K—3?(zĮĻ-'uÄCŒЕÄÄŌaė FĖ™ø<‹÷ņÔ%I’$IŌĄ-ø`ˆéw#ˇö]Īsw>Cv-ÂĄķ™ũøv2=z÷Ŗë҃xp”””Vk™˜ÔŽ˜Ÿ‹!‰Š-Ģt&†JJ€œy,.mÉą=“Ē•Ū7…¨œdæíÍYJ’$I’$UW/čĘÅsÎ/įé3'qLîģÍí’Ü{Ņ0ž‹ČØģGrlR#ÂäķŌÕgķØÎē oũO¸æûEë)á;ž>¯#OowŗEE@ünLG’$I’$А`Hž˜ą<ĪŲcV×Õī-y…æÄqÎ ✴­ķYŋ~ĮŒiD˜–œúÄD†'ms/&>qû—$I’$Iډ'˜!LúˆÛųí#xpb¸úB•ĸRJˆ#ĄQåĨ’%˙ÍŦ`Û@eW$õ =j&ĢÖ7"9šĘˆEĢYEBå–(I’$I’¤}ā€8cæ'1'qëˆîŦze&UŽIíJĮ¨,^žōŲyydg<ÃuwŽĻc(JW- ŗhG‡˙üx— Š#ķĄ?đā;KX•—GöüW¸ã÷pã+xČ$I’$IڗŦ`Hŧ`$—Ļls1~ cīHøŨ›9å¸S¸āĄ,NšįOŒŊb I9“¸ā÷Oėæ”Â{Ī& jÄė1Ã8ų¸ū ¸lŲŠˇ3ųŅŗ<^F’$I’$íS‡••••íŦÁŠ+hÛļí~*G’$I’$éāP›Lå€[1#I’$I’t¨0˜‘$I’$I ˆÁŒ$I’$IR@ f$I’$I’b0#I’$I’ƒI’$I’¤€ĖH’$I’$Ä`F’$I’$) 3’$I’$I1˜‘$I’$I ˆÁŒ$I’$IR@ f$I’$I’b0#I’$I’ƒI’$I’¤€Ô jāŌČV­ųžM›K(l Ē I’$I’tˆŠ ÕŖAũ0‰-Ž *LDȨĨ‘-dũûšŅ4ĐÉK’$I’¤CWid ?Ŧ+$ëßߐōĢ6äleZĩæ{šŅ”#š66”‘$I’$Iˆ ÕŖÅM‰oژUkž¤†@‚™ 7Ņ´ILCK’$I’$UĶ´I,›6—2v ÁĖ˙ũø#˙ņ Ī–$I’$IÁ‹ Õ ėü[ĶI’$I’¤€ĖH’$I’$Ä`F’$I’$) 3’$I’$I1˜‘$I’$I ˆÁŒ$I’$IR@ f$I’$I’R/čj¯”Ų÷_Æķ"•—BŅ49ĸ%Ōû3pĐ)Õ$¸ę*UÔųũ9LxôLZ]Ž$I’$I:`ÕĄ`ĻB|nģŽ?M�J7˛vųįŧûö3Œ|6įŪ~;—tnt…’$I’$IĩR÷‚™†-IęÜąr%JˇîœōģßđÂč{˜üđ$Rūv=ŊĖf$I’$IRP÷‚™šDĩåŧg2gÄ4^xŊ~_~}Ũb^?•7}Ã÷Å6nÃŅŋĘĩgwäpōxķļ›˜Đđržģŗ?‡˙ÔŲFŪšg8nŧ€§8fđų˓˜üúbVDˆŠoC¯A—sÅimĢ<ŗJķ˜ûė$žûx1+ ĒqKēœxW^”F3�ķȅ÷ŗrĐ͜žú%žËø†u‘hšĨŸÎĩםÉQ kÛøŲ9,æņKîgÕ ›é•ųO/jÉĩOßÎ)\’$I’$íSĪáŋ‰]9:r}M)�ŧķøũL^Ā…wŽãé sīĨmÉyū!ųh#Īą' ‹>ውUúŲ¸€9‹ Ë ŊiF)YOŨÝĪįŅáŌۙ8áîœ@Ö?îá÷ķö Ø|>ūî}?ÂŅ#îeâ?aÜđŽŦ{kŖŸÚZ?„ĸ"ä˘ÆÂôë™øÜŗŧ8árR–OãŪŋ~†ZˇŠÍBD…"Ŧ}û%žčt1<pąĢŽ$I’$IÚž`†xC¤ €u�DĶkø8&Üy§tH Ų ¤ô=‡ĶÛŗ0ķk�~Ųŗ),fö—•Ɉ/gķ?I˙žaãį<÷öw$#úIŗ#âI9ņ2Ž=ą!_Ėx‡ėŨ-uŨįŧúq_Å%ŨÚŌŦI<É=‡ríi-Yõū›|QZĨmģ“Ö7ž( ĒIw.üŨ¯Ø˜9ģz˜´ŗ6ģ0‡īöæÚŗģ“Ō.aĪVI’$I’¤Z9ˆ‚™åīk  ŠÃC…|1ũanŧv^r%į^xO/ƒŌHśšt§{XøņâŠÕ%ųâãÅĐš?Į6V-&'O—Î UƉ"%Ŋ ĄÕ˙féēŨ,uõ×äDâIi_írĢNmCÖ÷•ך´kË/Ģ´i–@ÃH9ĩmŗ s8ĸŨ‘Û¨$I’$IŌūppœ1@+ŋ‡†—¯ö(ũš ŖīáÍPŽ~1G%4$DīŽÉs?=͘cO<’ O}ÂÂŌîôŠ,`v&5ü˜ō cãFJÉãՑƒyuģņZ˛a#°;¯čŪ¸‘ „h˛ÍvĄ¨¨†N„Ō*ĢaoŊMŖQD(-­e›]˜CTÃĐnLF’$I’$펃&˜)]ú sķB¤\ԑ(€åŗ™ŗ:–“Į]Å)ļļ*`ãÆęĪũ2Ŋ?)‘)Ė^TJ—Ÿ°Ž\Ûŗ"1iؐ(âé5r$į&Tލ(šą›Å6,`ÖmSKéÆl ēZ`ŗĄ xÛFl DTTmÛėŖ9H’$I’¤=vpleÚ¸˜§ĮŋË÷ņũ9wk¨˛1B)iV%ä(]ū sWUĪpiŌ•“;GXøņž˜ˇŌûsôÖg;Ō!TĀڍ i•˜PųiETÃøŨ?‡ĨŨ‘$…ōČZVũ�ᕋž!Ũ–¤*aÉ÷ËV°ļj›åß ĩŦ}›}5I’$I’´Įę^0ŗņ;r-æ-æÍgö?'qÛĩ÷ķę÷mvûÅĩu%Iģ_‘ú†w_˙Œ•ë XščMƍĪ#ŠSˆČ÷_“ĩqk:͘ŖûtdCæ4žûŽ>ąkeXŅđžØ˜Ŧg˙ĘĶ+XģŽ€•Kgķø7qíÃŗųߟŠ3ëËų|^íŗ˜ėuåũž{b<K§?É _Žfíē<˛3ĻđčÛy$ö›Ę9� ŋ— //fåēÖ.z“‰¯C“žUÂŖŸkŗ's$I’$IûTŨÛʔ7›FĪŽø#DÃÆ-IIŋ€qĖQMĒ$MúsíđķĀŗåʡĄIûŪ ~ŊÖ=Cθ׸íxäŅ!$ŋėŲ›ŖÆ?ÉĄ^\‘^Ĩĸ8jøŒn8…ÉãīafA1Dˇ$ĨįÅÜ{Y˙jîÖTįŖ÷ÎŪîōŅ7=ÅŊ}rÔĨw2:4‰§Éäh߆ŖĪŋ+ÎnKÕ šxũ ^bôĩ_ķ}$šÄô‹=ŧ{ĩ•.;oŗs$I’$IûÔaeeee;k°bÅ ÚļmģWÍüękŌ;šWû<ø,æņKîaéoæogo{8ĖŽ´‘$I’$I?g_dĩÉTęŪV&I’$I’¤ƒ„ÁŒ$I’$IR@ęŪ3‡ŒŽŒxzú^h#I’$I’Tޘ‘$I’$I ˆÁŒ$I’$IR@ f$I’$I’b0#I’$I’ƒI’$I’¤€ĖH’$I’$$`æŋø˙÷ãA -I’$I’TMid QĄzŒH0͍ažĪ_ÄВ$I’$IÕü°ŽõÁŒH0“Øâž˙Ą€Üŧ˙Ĩ4˛%ˆ$I’$IŌ!Ž4˛…Üŧ˙åû HlqD 5˛N'*TÔ_ĩa՚īųa]ĄáŒ$I’$IÚīĸBõhP?Lę¯Úļ•)˜Q)Ÿ|Rë–A /I’$I’8ßĘ$I’$I’ƒI’$I’¤€ĖH’$I’$Ä`F’$I’$) 3’$I’$I1˜‘$I’$I ˆÁŒ$I’$IR@ f$I’$I’b0#I’$I’ƒI’$I’¤€ĖH’$I’$Ä`F’$I’$) 3’$I’$I1˜‘$I’$I ˆÁŒ$I’$IR@ f$I’$I’b0#I’$I’ƒI’$I’¤€ĖH’$I’$Ä`F’$I’$) 3%üķę4’ÚwÜá'åę÷(ŲīuÍãŽŪ0!gī=ŸŪŲ¤3ūNöļ‘$I’$IuWŊ@GOȃã’XĶŊ¸ÂûģÚqö¨ûč—ˇßG–$I’$I‡ž`ƒ™FíHī؃ä@‹¨*žôg]„$I’$I:DÔŅ3fpGī4.˜<—nLßŖĶHé|2ƒî{ŧŧy<yõ zFJīA\7mI喨ŧy<yuyû¤öéÚ{×M˜GŅOũîÆÖĨŸíČûˆ/@×ÎIé=€+'|DŪvũü\›ysūˆ\v2)¯āĨ"€<fO¸–Ŋ{’Ō>Ž' æ–iKĒŸ—ņwŽ;÷dēvîHRįžô=÷fžĖČĢõ}I’$I’´oĖ”””l˙ŠÅsáp)‹§<Á’ķîķų聲§ü‘A—=CxÄdæ}‘ÁCŖ˜uß8ū™ĮK#¯āĪ9I\˙Ô æŧ˙:“GĨ°øņÜōFŅĪ ˇĩé3—nÁøœF˙×;|ôÂCœ?‰ĮŪ-ĒŪĪĪļ —˛júĖéq;Ķ^ÉŠ1%dŪ7ŒK˙ŽôQy÷ũ7˜<"‰Ėû†qŨŒÕåŊÉW?AvęíL{ëæŧ:‘[S—ķØeŖx)¯÷%I’$IŌ>ėVĻŦGųm—Gk¸Ņ’K^}—ŅŠ;ŧ$õ|ŽīOˆī{éŧOvĪ˙äŌÔ�’O>‰Ä‡Ÿe~œÊwĪ ’ËO¯ILŧ†!“gō؜0 īnL }æŊĪÔyĐoÜÎI‹8uÔHæt6ãˇvS›6V7:iWžT~.OŅ›<6m9Š#^g뀤ŠņĮ0vū<†L|žÅƒnĸãę,˛×ĮŅĐItLH ņž $Ę'>Čų™û’$I’$iŸ 6˜I:ĮƝĩũáŋáF$&mũcëJ(_5&\q*pbR;~ĘÂ1„Ŗ 1ĩe•~ÂÄPųlL8Ÿ—Į- rXUTZŪīz Ąt7'P‹>s°Š8~—Z5åHĸgJ#ÆoŨ1U›6RģV~_9ķX\ڒßõLĒŌ"LzßĸĻ/ 3:&õĄÂ$žģq(%CĪį”žŨ965žŽiņÃüĖ}I’$I’´ĪĖD%Đ1­ëÎ˙s7=.ŸÉúŠ?5Ô°”#ŧõ?;x—SÉîŊhĪE dėŨ#96Šaōyéęŗylwë¯MŸE%&.Ēząá˜*uÖĻÍÖĢ1U­§„īxúŧŽ<Ŋ]Ëvņ=ũÂ4’&ū—§ÜÍÔ?­'*Ą;įüa Ŗ$˙ĖũŨũn$I’$IŌĪ 6˜Š´k˜öÂų?;Žk,ßõ~–ŧÂ?sâ8į…?qNÚ֋ųŦ_ŋŗ‡öBŸ1a”_mQN EEEģÖĻ&1Ķ’SŸ˜Čđ¤mî…ÃÄo]Zߕ Gũ GAQö<fMĮŊ7^A8á]F§Õâž$I’$IÚ'?ü÷gŔ¯ĒI¯øtLÜ̓OŠJ)!Ž„F•—J–ü7ŗr€ŨŨÉT›>RH&Ÿė%UC–,2T´6mj’Ôƒô¨|V­oDrrRå'>L8& dÕfÍÉų)؊IîÁ9Ŗn¤T>‹sŠ~öž$I’$IÚw‚ fÖ/'sÎGĖŽáķiF{5HíJĮ¨,^žōŲyydg<ÃuwŽĻc(JW- ŗ¨6ī‚ڍ>OâėŽ0įņģy.#‡UŲ øį0ģ¤JšS›65‰9‰KőųĐxđ%ŦĘË#{ū+Üņû ¸ņ•ō×mį<ĪW_Áu“?bņĒ<V­ZÂėÉĪō) ¤§Æüü}I’$I’´Īģ•iõLnŊ|fÍ÷ĸ~ÃøEsęŪ+~ cīYĀuŨĖ)Ķ!ŽëéÜzΟ85ī~_=‰ ~3^ëŗúŧ‰ }˜œ‘÷ķāeŋåŪp;ŽŊ`$ŖãĘwˇîyJ¨E›š„9öž)LˆšŸĮ ãųëĄQ;ŌOžÉŖÎ" ß&ßs?÷NÅ ?åSՈ„¤îœķčDŽO…pęÎīK’$I’¤}į°˛˛˛˛5XąbmÛļŨOåH’$I’$j“ŠøgĖH’$I’$¤ f$I’$I’b0#I’$I’ƒI’$I’¤€ĖH’$I’$Ä`F’$I’$) 3’$I’$I1˜‘$I’$I ˆÁŒ$I’$IR@ f$I’$I’b0#I’$I’ƒI’$I’¤€ĖH’$I’$Ä`F’$I’$) õ‚¸4˛…UkžgĶæJ#[‚*C’$I’$ĸĸBõhP?Lb‹#ˆ ‘2jid Y˙ū†_6‰Ĩaƒö‹Ã‚(C’$I’$ÂĘ~,csi)Y˙ū†”_ĩ $œ d+ĶĒ5ßķË&ąD…ęĘH’$I’¤@ö‹ÃhP?LlĖáŦZķ} 5Ėlظ)°%B’$I’$IUՏŠbĶæ’@Æ$˜ųŋ bXI’$I’¤íö‹Ã;˙Öˇ2I’$I’$Ä`F’$I’$) 3’$I’$I1˜‘$I’$I ˆÁŒ$I’$IR@ f$I’$I’b0#I’$I’:Ėd={+Ãî~‹5ûl„…Kį2uÂ#Ü~ĶüįåÃ6üFŽŋãž|ų3ž-Ū>—1åĻáÜųfîŪ.v?ąŊ]û=jWãž˙%I’$I FŊ  8 EVōÁ„ņŧ¸˛)=s"—ŸŅŽqŅ„"EŦÉYFƇ¯3xÃNm ēÚ:ĄđÃG¸3į×<vi§ K‘$I’$)p3;YÉ~œ›žËčąŨiQ5w 5Ĩuį^´î| }Ūyœ{Ÿ˜J‹1CéXĩuD„osV ē I’$I’u+˜)üŠ˙ņ,]K$ē]N>žÛ7bá›SyņŨeŦ)Šjڊnŋã[Í|đ§1Lmp!Ũ؋ĘĨ˜Ŋ•I›ÎâĄ?žD˛_žÄŦg1úĘîÄE~ā˗ŸãÅ-cMq4­ĶĪâōãrxxÂ&.ä2†Ėŋ‹™˙ú-]NiēM?‰nN—ßœĮŠEĪņpfŖĮžEëš'ˇ“ēs˜rۃd÷ŋ‹{No^Ņ|ãnžJvÚpūßĩi”įF9LšéžũÍŨŒ>Ĩjßģúü ô™˙ĻÖ쐇nėEl•ī)ãoˇ2Šč,î˙ãIÄÕę÷¨œßž)KgØ'Ípį…ģųK’$I’tp¨CgĖōņÄ'y#ˇCnš‡ūx9}ŠŪfffÕC^"dO„‡_ûäÁ#¸ÜŨÜ|Fs˛§?“˙úhJˇŪI°ô3V}Ŧø+2–BJīcˆ(üœ™˙ŠfĀEŊˆŖŒ<¤EMxÃ}üŋGnãܸš<ųˇnOëP,ŨzˇcÍüeWÔųÁÄ'y#ˇ9įŪr÷ßr)K_`Ęŧ""ĄÔŧáéįęnEˇ¤ŦY眭eGržâۘĸW~Ŏ[ģYŗŒėâϤthēM˙ģúü¯čŲŋ=,Ë—…Uŋ§ed,…äūĮWĢßŖĒXú\3Šs[At÷Ëx띎1pģ„jWû”$I’$ŠnĢ;ÁLá>X ]ÎBŸäĻÄÆĩĸÛāséŊĨ˛Mņ|f~¸–Ög\ÎĐIÄÅ5%ų¸! =.š…˙=‡oØôcHf‹*˙gŋxŅ\˛HĸgzųڐÂEsÉnŨžqYú:S3›2đš éŲ:–ččĻt9ûˇ$SD\R;bčæM‰.Ė% WÔyBrsâZ´įÔ+Ī"9˛iĮsûŲēC$§ˇƒœedWėĘ^´œØŊH‰,'ĢâTÜâœeŦ‰nO—í]>:ŊŨB9|<ī‡Ę2—Î%‹Nœ[ģßcÛ*ĸŖ …€P4ąŅŅۇTģҧ$I’$IuYŨ fr—“O,-ZU=ČĨ9)­TūšfßniJJ‡æUڄHîœH(w9Ų…@lWz&CÖŧ­+\ŠY8otčEˇŠ=;ß.Í%ŽSyč’=oÅúŅ'Žj1ˆ4 uR•qļŽ†ųa9khJëVąUîĩ§[ŌNvÕĸîč¤N´ŽäT„(š,\ ɝûŅĨÅdå˛-'ÔĄÉ5 ąË·:qBh˛?ųŦâmH˛ūĩ :÷ĸ[t-]ĩ/ú”$I’$é�VwΘ)ŽPL=bĢ-ŗŠŽraĶ&"üĀŦû‡3kģšQ\ ÄÆŌ­wS§NV$n‘¯ČX)u­8KĨ˜âÂbb;Ä�ÅäįÛĒÕÎõũö+˛#Í95Š|ėâÜ(nÚž|Tq„!bĢ=":6~ fĩŠģE{Râ^!;§b—‘]˜HŸ¤Ļ$'ÅđâŌåDŽ‹aaN„ä3Ú×ŧ]*nWŸ‘Ōŋ-fĪåãoOãÜ_‘ą4Dˇk;•߯ÍīąĢöEŸ’$I’$ĀęN0"Ä ĢŊŌ'Bqq•ķG4 DSú\3‚͡y>"ļbÕKlį^$G^ ci„”âĪÉĸCĶĢŋRé§aęA¤Ú˜…dŧ6—5Ą4ZĖ˙ũåŧU$÷¨4B!BD(,ŽŪ[qáNÎIŠUŨ­čŌ!š–.§0ö+žkOr4´čœĪ.cM~ Ų…­čŲaG¯†Úį[÷ĸOĢ÷øøŗ• Húœ…ŅĮps‡Š¤6ŋĮŽÚ}J’$I’t�Ģ;[™š&Ō‚BÖŦŦú?é+ÉĘŠrūH‹ö$×+$ŋ¸-Z4¯üĆE7­\õۑ>"dÍûŠ…™åÛs*_uM\\,ų+shR:ˇĸxŅûdäGˆŽ$ãŲ§ø8ԎØPy`ũÎ^üáö¨ØēÔ<‘8~āÛÜ*uF–ņeÎNÎIŠeŨɝÛCÎW|œšŠč¤ö´�hՑօËX8ī+ÖÄĩ'%nĮÃėúķÍéĶ?‰üĖ9Ėü×WÄöčUšMĒ6ŋĮŽėč}Ų{Ō§$I’$IuPŨ fâŌč“ _›ĘKsÉ_“CÆŗ3YŠrūHt§KöËOņbæJō Y“=—)ÝæRų‚ĄXēôhOņĸW˜šē׊ÚVĨÖéí‰dÎ%+qĮehR.SīÁÕwOáËVšęĸĶčûOŪ4ŠI‹Zqų į‘ŧuˇM\'ú´‚…¯ŊHÆˇ?P¸fŗ&ŧNvh'į¤Ô˛îP‡N$.`ÖĸbZwnUņl)q+™õîrĸ;tŨÁ̏w˙ųØ}éRøŗ5ĨOīVģö{�k>|’{˙ü Ų�4 :Åš_ąđÛ\ōˇ]SË>%I’$I:Xԝ­L4å„+.gÍĶ/ōâŖw15Ԍ”ãĪcČq/đ×Ė­o< ‘rŅM\ũ/>ûŗŠ6Aƒf$§ŸĮ̓{Qå8^bĶģ“ōė†ŽaHįęg˜„:˙šą0ezŖ/ęDŸ+ī Ī6Õ ķ(Cˆ‰„Ęß4ô“æœzÍPÖLœÉ¤{FAL;ēq.įÆŽį¯9;š[-ëŽnO—ÖE,ĖiO—¤­ƒ6%%)šgGč™ŪjũīÁķŅč™T…[úŅŗEÕĩų= ōÃ*˛ŗCáR4]NîK‹ŧĪÃúœnŧ|›Áj×§$I’$I‹ÃĘĘĘĘvÖ`Ŋ´mÛv¯šųÕ×$4ßĢ}îu…ķ™ôį)d5íĮšgôŖKōÖ-EŠ×Ŧ$kŅ\>˜Ŋ�~3Š›‹­ūl¤˜bĸŠ<ŗļ˜ŒGoeCyėÆîėč˜Rág<|ĮTB—ŪĮuéuĒrI’$I’jmun靎ÜĢ}Ö&âNzZ��ĐIDATSŠC+föŗØ4.͜/ßyYOåÉÜĄĀĻ-͌äÎiôšd=“ˇ eøū<†ŠÅŨzɯI‰ŋčm^ü*D—+:՝PϏ5k–ķÁķSÉjņkî1”‘$I’$i¯3˜Ų™Psē~9ŨN"Š‹#]u%LMšrÂ5WQüė+ŧø—ģ(ÜTčĻItûũM éQwüįöįW›Ô‹ĢŽ8?˙ˆ$I’$IÚE3ĩ ģĶDĻRl'\ۉûļĸ}*î”Û˜|JĐUH’$I’tpĢ;oe’$I’$I:ČĖH’$I’$Ä`F’$I’$) 3’$I’$I1˜‘$I’$I ˆÁŒ$I’$IR@ f~ņ ķ I’$I’t`(ûąŒ¨PŊ@Æ$!iÔ°›6—1´$I’$IR5›KKiP?ČØ3‰-Ž  h%ĨĨ”ũXD ’$I’$éWöc%Ĩ Š6Øâˆ@jdNT¨ŠŋjÃĒ5ßS´~#Ĩ‘-A”!I’$I’aQĄz4¨&õWmÛĘˍ”O>ŠuË †—$I’$I œ§đJ’$I’$Ä`F’$I’$) 3’$I’$I1˜‘$I’$I ˆÁŒ$I’$IR@ f$I’$I’b0#I’$I’ƒI’$I’¤€ĖH’$I’$Ä`F’$I’$) 3’$I’$I1˜‘$I’$I ˆÁŒ$I’$IR@ f$I’$I’b0#I’$I’ƒI’$I’¤€ĖH’$I’$Ä`F’$I’$) 3’$I’$I1˜‘$I’$I HŊ ĪËx†žŲKV“ŋž”¨Fq$§öaČFraZLĨI’$I’$ís3EsūČ Ëß#æŦk;ĸ;‰qP˛:‹×'>ĘčßŖčÅ\•Tu’$I’$Iû^@ÁL ŸN“Õ)71큋IÜz99•ôžq”œ;Žųķs 5)˜ō$I’$I’öƒ€Î˜)Ą¨¤üŋ%ÛŪ ÷eėko0á‚*ĄLÉjf=tz§‘Ō>gÜĖ?2ŠĒßŋ¯ō~×ۃ¸ōĄXõSƒyÜrtƒ&|ÄKˇ ĻīŅi¤tîĮÉW˙OĢtyĖžp-z÷,īį„ÁÜ2m U›äeüëÎ=™Ž;’Ôš'}ĪŊ™'3ōöÖ#I’$I’!31{rWĸ˛žāĘža֒ŧ횟”đé}CšnüvÜ4Ūxu"ˇĻfņāeWđl€"fß9”á3Jč?nīūë ĻŨŨŧiW2ėž?õ—˛xâŖdô{˜wŋ˜Ī‚ƐžäQŽųfEđRBæ}øäņīH5‘w߃É#’Čŧo×ÍX]ŪIŅ›Üqõd§ŪδˇŪaÎĢš5u9]6Š—Ėf$I’$IŌ. ėŒ™Ä grŅšãņq sDĩ$ĩgúȐ=H W4,z“ņ3ōIŋgWõ‹ ųž?QTō ųĢ‹ æ=ž~ã;Ōū0…[û%”?sĘíŒ]đ1ŋö ŗ˙đ0§ní+õ|n@ ū$Žz/?4“ŲE§ķ;ŪãąiËIņ:c”¯ÖILÃØųķ2ņyē‰ŽĢŗČ^G˙A'Ņ1 Ä{&<(ŸxĪ*–$I’$Iģ(Ā×eĮpė•ãŨ/fķú¤û¸ePWbVŋĮø‘ÃčwÂ<ˇ¤b­Ë’,.Ŗcj|åŖáŽ\ú—‡šĩ_ ä,`qiKŌĶĒõžÜ#…¨õY˝ÜĪD|j Uz!1)‰FĨĢY˛șĮâŌ–Ûŗęš6aŌûĻ•ŗ€Ė< Šũžãš‡rĮä7™Ŋ$â阖J|I’$I’¤]čë˛ĮĶąßYtėwWEķŸáēĢĮqī}39õŋÎ'žd=%„‰‹ÚÁķEë)"L\Ŗmē 7"†JÖW^‹ŲŽQaJ())ī§„īxúŧŽ<ŊŨ í(*â{0ú…i$Mü;/Oš›ŠZOTBwÎųÃFHÂlF’$I’$íŠĀ‚™’ĸ<ŠÂņÛ­4‰Iģ˜ëOžÄœw— ÄĮ4"L ųëkėbĘ˜mī—­§ˆ˜jMŅvĘCp¸ŧŸ0-9õ‰‰ ßöePá0ņ[_ߕ Gũ GAQö<fMĮŊ7^A8á]F§íŌW I’$I’qÁleZõ ͟a“sj¸™Göę"ˆK(ßv”ÔƒŽQߑ9u•6 xō÷ƒËåMíZÃ}Ȟ—EiŖ”Šŗ`Ę­žŸUåMMŊd9ĨQíHM('=*ŸU둜œTų‰ŽI (Yĩ€Ysr~:P8&šįŒē‘ūQų,Ίöz'I’$I’¤ŸõwŨu×];kPPP@ãÆ÷î¨1íh“÷O˙ũŪĪûĸŖá˙Ö¯%{É\Ļ>p'x'ŽÃÅíB¸5Íŋų'OL˙Œ’¤$š”~ËėĮīãūūƒŗo¸”ô 4Ī}“'ūë3ĸRģŌ"ē˜UŸ<Á-Í%ūĸ{¸š_<õXÍûOÍdÅúĩäliGĮ6Q/z™ģ˜ÉÆž7qį™I„í‰ĪÉø˙ú”âÖ)´‰ŧ¯gņȈ+ķQ3Î93•đüŋrŅÕã™ŨŽä¸hJŠž%ķÅ <=NēúRŽ‹˙ųŠK’$I’¤CCm2•ÃĘĘĘĘvÖ`Ŋ´mÛvoÖUĄˆĖO0~Ú{|ēä;֗BTŖ–$§õāė+näŌžURŽ’ūųĐ8{c9ų—Ō‡KFáĒ­mJV3ëĄģy°â~Ŗ„úž‰ŅWö¨8ėwwôFæ  /™Äƒ3°ē$†¤~—ķįq“ūĶ•V3ûĄûypÆgdᝇFíH?ųbnu~E›ΏŸ{'žOfN>ĨQHHęÎīFÜÎõ§$xƌ$I’$IúIm2•�ƒ™ũŠ"˜ú:o\ší2’$I’$I{_m2•�_—-I’$I’th3˜‘$I’$I H`¯ËŪŋz0ö“ÅA!I’$I’T+f$I’$I’b0#I’$I’ƒI’$I’¤€ĖH’$I’$Ä`F’$I’$) 3’$I’$I1˜‘$I’$I ˆÁŒ$I’$IR@ f$I’$I’b0#I’$I’ƒI’$I’¤€ĖH’$I’$Ä`F’$I’$) 3’$I’$I1˜‘$I’$I HŊ  ¨jÅ:¸ņM˜ŋV]ÍŪĶļ1¤ĩ€GO‡ļMvÜŽ.Īŋļs”$I’$I•˜`fÅ:H 6]ÉŪˇĸ üķárČŧĻæāĸŽĪŋ6s”$I’$IÕ0[™nxŗî†ĩU°š|ž59Xæŋŗ9J’$I’¤ę˜`föō +Ø?ŦŠųúÁ4˙ÍQ’$I’$UwĀ3ęj‘Ž}aųØtüē9dŽ…Ėž@\•ī‚s ÎŋšÃáļ‹Ąl,Ėíĩãfuí|I’$I’‚rœ1s ÜÚæCʰ´l~Č ēĒ�¯\Įn†ē!I’$I’TĐÁL×^0ũDhģ&ŋigBã9:k'5‡%×BÁ<Čjƒ›Cîrüd4.ŋ—; ē@ķO ×\ˆmãO‡-€õđÖ'0ü#| ŒŦ8Ä6k ž�#/Ū†ô%Շm“ ãO…ãCn.Üđ<ŧēVŽôī é iõ!7ūō&<PąíŠŲ‘0ųt8>6įÁ¸×*īíhNkwöũlŪÉ<ęÁ§ŗ`XĖžįķ’$I’$IĐVĻíÄÁäĐļ�†Ī€ÍGAĀϟynKųŠŽcSáĶ7āøYи=LîWå^:ˁ‘_‡Ãô!0ö [§ũĻw‡ÉĪÃôõĀ8mŧĩec6†éįÁąëāø 0ŗL?Úėéw“ ŸĀąOÁ_ÖÃČĶĄëÖ1‡Āą`đS0y3ŒgÖßųœvúũėlđ@&îč;$I’$Iģė€ fšĩ+bfžSž†߇]Y€˛b LXŸÁ[› Ĩ4̏Wđ5 ˙ fįCŗT8­Aų8Ī/‡įgÁĖMpüQP?rˇ�[`~.î¨Ö#áØ˜ š0nÔo§žG_„ >Đŧ´ ÁäŠĐä XP1æņ!xkŧēF>§Ŋ�Yė|N[ŋƒšžŸÎûj’$I’$ŠFėVĻÆõË˙›ģžâÂ:ČĨ<¨¨‚u˙Øš›Ël\q)7J*ūŨŧÉ6ãlm84¯åX[û6WšžÖØPËNj˛†ÍņŊ`zG ΅ŗĒÔ]ą‚¨dŧũuųŋģÖbN5}?Éûj’$I’$ŠFl0ŗõ-EÍU\hR*ÔvÕLķ&JęAķú@nÍĪæŽÛfœ*ísk9ÖÖ>ĻĪ€‘+ĢĖa/„oĪ‚vŗ Msx"üĨ ›͎Š;ÃÚAÖČĒŜjú~˛÷á<$I’$IŌöØ`fíĘōm9ûÁų[āØ~ģĖtģž†OÛÁĀ0Ŧ­iœ%đÖŠåĄĮĐ-@jyûˇžŦš}ĩ~ ŸFāø.Đv´í74áS Ŗ–}Ô¤Y:dQ”ŒĪĢX؁‚-•cnũ~ŌN„‘q0x ĖŽÅœjú~ílõá×í ~\åöĒ3 w%dÜH’$I’´[Ø3fȅaoCns˜<ę/†ųģđøü…и_ųˇšË`ØÜ4Ü�ƒ§Â[ĀøĄ0>fžƒ3wa°‚ōˇeĩ€ˇ†Ã_Z•Ÿũ’ą‡åŽ]7,„Ķ΀ų×Âøv0~LßP9槇ÃäĄ0ŧ>Œœ Īo¨Ũœjü~v60ųB˜ųëō€ŦíQ0ķBŲjĪæ(I’$IŌĄė°˛˛˛˛5XąbmÛļŨ÷…ÜQÃÅzĐŦŦŨLåk°ß(ÅõÅAæ ”ŋŌúŖ}Sëž*ģũĩįŋ/ė§ī§Ļ9J’$I’t(ŠMĻrĀleŠ CaI• áƒë!-ŋmûA 0n5 í ) ļīŖ ƯÜūú¤M㚯o7˙:lGs”$I’$IÕ0ÁĖņIđę’* `ø ˜|jųļš‚u0ūy¸ë[(ųv'ÅíëJ÷LZ‹š¯o7˙:lGs”$I’$IÕ0[™VŦƒ´ŋ<ĢFj.?+Ļm“íī,ķßŲ%I’$I:”Ô&S9`˙mÛ¤üčĪL=øļ´i\>¯u}ūĩ™Ŗ$I’$IĒî€Y1#I’$I’t0ŠS+f$I’$I’53’$I’$I1˜‘$I’$I ˆÁŒ$I’$IR@ f$I’$I’b0#I’$I’ƒI’$I’¤€ĖH’$I’$Ä`F’$I’$) 3’$I’$I1˜‘$I’$I ˆÁŒ$I’$IR@ f$I’$I’b0#I’$I’ƒI’$I’¤€ĖH’$I’$Ä`F’$I’$) õ‚.@’$I’$X>^įM‡ŧ°Ĩ,čjöz‡A|Cxa0ôiL ޘ‘$I’$I?ųxô›k6ÜĄ ”ĪoÍúōų~ŧ"˜ f$I’$IŌOÎ}ō<ĻēàŦŦ|ŪA0˜‘$I’$I?Yģ>č päofhƒI’$I’ô“CjĩLAmÛ2˜‘$I’$I ˆÁŒ$I’$IR@ f$I’$I’b0#I’$I’ƒI’$I’¤€ĖH’$I’$Ä`F’$I’$) õ‚.@’$I’$šĩƒq'ÂiÍĄyØŧ ˛raüÛ0aUĐÕ˜ f$I’$IŌ‹=> _Âđ÷aÅz¨ß÷ƒņ—Aã đ@nĐUx f$I’$IŌ;ž;´]mgĀ7[/æCÆr¨%Û 0˜ŲŽgĖH’$I’¤=Ö¸béGũmolĢž€ŗ>Ģríp¸m,ecaŨm0­/ÄVÜžōJØtqåß[ ŊĘŽ„6˙ē/dŪ›ÆÂē›áéîÛ?s 3˜‘$I’$I{ėÃ%°šĖ<Îl᝴:ÆÅÃ]S íCpڛv"LîR~æ—@;8­jĘSˇƒŋ,_‘ĶķtxëDøôMHyN{Ž=ϧīģ9î 3’$I’$i}ķœö6 3¯…Íc ķb¸?ÚlsĘĖ× e LYß@ÆBŸ ĮY~íø|då3ąGÂņĀô%@}¸Ģ;ĖŽZXŅG& Ī„ĶúA×ũ2ãŊÃ3f$I’$IŌ^1û#H ]ÛÁiŠpZ;y6Œ<†O gĖna§ÂäVĐļ>Ô¯õ�ë*:Ú�ĶWÁ¸.ģ Ķē�Ëaæ ŌB0}yõņ?ũ6÷€c‡öÛ´÷ˆÁŒ$I’$IÚ{ļĀ‚¯Ë?�ą‰0ũBøË�˜ųŦ­^ÃˇĀđ×āÃ<Ø ģîĒŌÍĖ/á/§ÃņõāÕzåĢg>| Ö4(?Ëæ†ápC %4ŽĖH’$I’¤CE¸>4ŪkˇTŋ^¸ îZŸĻB °ļ9 އÉãˡ2mÕx›Sƒ×~ ŸÖĢ8WĻ~ų6ĻáK*nn*s&?ãōļ)d äė͙í[3’$I’$iĪ4†˙�õ߆ôļŋŌØPņļėŠÕ.+6WŪ7‡ņ@ՐeL^^žé´zĀ×đÖÖgōáĶHų6¨ĨųUžŠ_ūÆĻÂŊ9ˇ}ĖÃ%I’$IŌž)€‘ķ å×0÷ 8ŋtmũ„û‡Āøö0s,Xķ#0Ŧ7t8:´ƒégÂüåPŋÉ˙oīŽ]Ģ:ã8¯÷JlĀ$;ŪEÄMupp'éÔBWũÜĖāāPĐQŅÅ]pQ'GÍäĻĨƒB—@†ļCHBLĀ“ãpiŠŪ áū$yžųåœß{Æī9'9ûÉəį&‡'7'Ī_\6’;¯“s“['“c?$'Ž&÷¯$o~IŽŒū |5'f���€oöōYra!™=“<8>˜lŧOŪ-$ŗ“ģ˙}¨wmđm™G“wg’’Ų§É“ÉäÔoɋ+Éš{ÉüéÅåäŌ‡äÚ_˙ŋßĪÉíËÉĩÉ$ī“WoŋŪūwÄ{˙ļmÛ/-˜ŸŸOŋßŅ8���@Ĩ΍ę ę´ŋīîõvŌTŧĘ���PD˜���("Ė����f����Š3����E„���€" ���@a��� ˆ0���PD˜���ļuĒ(Ō+*$ ���°íČd’ļzŠk“ÕÜZ˜���ļ=ū5éėŗc3Î`ß„���`Ûų~2w5ųi˛îõžQéėsîę`ß%3ÔÜ���ø^ī'_¯žbØãí ���āû%Ė����f����Š3����E„���€" ���@a��� ˆ0���PD˜���("Ė����f����Š3����E„���€" ���@a��� ˆ0���Pdh˜éõzišfŗ����ė Mͤ×ë ]74ˌg}}}W†���ØÖÖÖ2111tŨĐ03==•••,//gkkkW†���؋šĻÉŌŌRVWW3555t}§mÛv']\\ĖææĻך����>ŖÛífll,333évģC×ī(Ė����°ûü• ��� ˆ0���PD˜���("Ė����f����Š3����E„���€" ���@a��� ČGáz5üæē=����IENDŽB`‚��������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/profile-multiple-password.png���������������������������������������0000664�0000000�0000000�00000155634�14156463140�0024527�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��X��7���†dsķ���sBITÛáOā���tEXtSoftware�mate-screenshotȖđJ�� �IDATxœėŨ\“åū?đ7ĘØ ›9Ά(ãĮ‘&Ã:nv‚q<ĘHÍU¤X'ącEuRûqÂĶ)­ã­Ī'ú)žJņhAiÔ–1ų|sŗ’á/æÜ d Ô <p ųū1~  ‚ļ×ķŅÃ\÷}_÷u_ĶG÷k×u_ˇO[[�����xßΟZ[[OŸ>íp8Z[[‡°A������×ÂđáÚ\Ž@ 6l˜{D¨ĩĩĩļļ–a˜€€€aÆ u �����Øųķį››››››GŨ„ęëë‡ 0Ôm�����¸†ūûß˙Qûā˲ūūūCÚ�����€kÎßßŋššš=š\.ŸĄm�����Āĩ6lØ0—Ë…Į�����Āë ����€×A�����¯ƒ �����^A�����ŧ‚�����x!�����đ:B�����āu|¤Ÿaä�����€ËŅvžõjĮˆ�����x!�����đ:B�����āu„�����Āë ����€×A�����¯ƒ �����^A�����ŧÎĀŧP����ŧVÕÉęM› Î55Ÿ?~¨Û7Ÿ7<’>7,tô Ÿ#B����ĐU'Ģß]÷QÚ˙"A?´ĩĩ56žË}īÃSĩÖA>5‚����ô_ūŋ †ē pƒķņik;˙¯üOų´B����Ѝ˙ũīP7n|>> ƒ|N!�����bmmmƒ|F!�����đ:B�����āu„�����Āë ����€×A�����¯ƒ �����^AˆˆHŗdĸ$úū|ˍTķv-8Nr˙ÆÁ8���@?Œņņ}{Ũä;"<ú–;&ˆšEāÍz˙ĢøëaĶäiK ģÃA\.O$‘Æ)͞2RÄĖPˇ ���ĀĢNzāÉۃ>Zv ô‡CGkYō9FčīKÍ6SÕņĀ c†ē‰ŨøŽ ŋM:.4°ŲjŦĐĢw u{``ũšƒeגŒĨjŗƒ¸b™BÎgˆeMåZ}ŅmQž2§đžt¸cÁęBŖŖëw†ËJdŠ$y8čuQļ’ŧ\ƒ8ķ)å%>\›n]Ž^œž•>(Íēk1”huFŗŨî`š\>_$–*”ōĪįŦaã /uŲŨ’ŽøJé–N|¨Č㯗+'Ĩd-]Ž ˛6õƒîĨße”/Ō-uK��n\œĀąq‰cã§ž8TíâØJÕÅ?sG &tˆ›Ö䌧Ŋ} ‡ˆˆ§&—m{kķĄ3ũŦL0ũ™§Ļš?|i›é˛ `ųŸÂ÷žõĪīęûyÎk×áß<’ášÎÔÖ7u{ŽÆõ„Žį/ņ 0NúķĮũ8œÕŊ”–ĨļrĨéyog'yŪÛ´+>™¯ÉÎ\-)zn¨īoü¸ÔŲŌöûs‡Ũ¨+)ÎßhOęÆēí/“&WËKΐtîŗ”lĖדXĄLķyDvģQ§ŅäįŲŌŗRö›+f+I8 uöOųZnzûõŲ­%ęœüŦTKnņ;Ęë5V�Ā�ã9›­'+Ę8¸īđņ_FÎøû_ndŲīTæŠōūt¨z¨ÛįÁ7\õ`G ""⌊Ÿ}ßáãyv°ZP¯So=ōËšÁ:Ũåāߚ–ūĐī‚Éé$"§Ģžøũ÷v×RāmŠ™’˙l*(ûåF2Ę ´ę¯ŸīĐÚÚęūķOˇ´ Ŗķ~‘@Ž8ßzžíÄÁ~ÕmĘ_Y`%Ūėˇ7e'õøŽ¯Xžémæ%­PʰD}|oŪĩ:7OŖ5ší†'–(Rŗ–.JŲÔ*––+sŽIéØu×qYGÜō’Ī2:ōVENōėõ”Š.~ŽŗÂ+9pîãÂ/ŪÕK˛ĩŸĨwKp_f&.ÕJÚ÷ˇ•ŦÎ^S 5Ų‰„åSŲËyũę¨ËĀ Ãģ€Ä!åæôŠđ˜_˙ėB›ÕĘŌ5čYSyš•‰ËL“ˇĀ|‘HĖŖ|ĩŲb!ņĀTōÃĨ×MĖ`„š\Úņ›\ĮŸ­ĖÉĶX”iœ�øÕķ —Æßė:úŋôŒœQâ‰SŧÎq:ĪTë];JMCŌFOÁãÆö(ō˙­DDĶ`ĩāœÉP6Xįē,ūf<iŲôÜŸÎv/EÄO|Úß÷7üpceĄĄ B­­­cƌ!"ŸÎ?ÛڍüŨD>Žs§[ŽļļķũŠÚXPh$’,ZÚ3š1Šek9zãũŠ+ŒL™š|™˜g7h6æ¯Y¨Ņ¯ŅŦŋ[$K‘^¯7PJûũœNĢspŽą¤œÍšĪf+כ‰—š$!2wÔÉŋ‚īzLqę_zmÚ”ž(ŧŗU6mÎÁČ2T""2ŦHËÜhæÉR—Ĩ)„ŗfcÖBŪ5KB=ˆÄBŽŪfoO‘ŦE§Ņč;K<ĄDĻL‘‡3DD6]^žAĸ’ÛJv™¤Ė 9cŌi´:ŖÕî ÷üēŽ=ÉfÜĨŅ3ŲlD|ž8NŠLrį.‹&wŖU–!ŗi´Į,6–ĄT•Öūpk1h4:ŖÕę`š<a¸TŠR„_"—ąZ­3[YФI ĪŊm†]E:ŖÅjw0\ĄPĒPĨHøDĻ]šųz;‘9w…V’ē,-†X“N­Õ›-vÖÁå %2ĨR~Šŗö՘ ŋN%Ĩ/ōl­AĢŅŦVņ„bŠR™$nĪ36ã.ĩö˜ÅjspųBQ´bvJ ŋīōîSãúčj÷‡•Ēäę5zŗ%F$QÎVšĢe-†]ÅZŖÅæp0<ĄXĒP&I*Y…K%\Ō­D""2Ģ_ĘÎŨe0Û\F,U-]ųZû¨ŖeWNvnĄÎhw0\ĄD™ĩ|eš”éŖ\ģdâRëJíĻģųDíß,ØŌ6îMNDDš%—Ú×č×§0ļ’ŧėœüŗÕA<ąLĩtyļ{’¤irvAōš,ëšl o™æŗ ‘­$géŠ|™ą"sšj€.�Āû0.ËO‡Ģ‰ÄŪ6Æi,;î9ÔŅüŸ/ÕļÛæLssDŒsåĨĻŖ[f&O‘<ĩG];‹÷ė(ŪsmÛëjv^PÖÜÜķ6ß?ėwsfß7:8€ãl8eŌíØĻ>vŽcĶ”‡æ$ƎfœŋTëvîuuŪt'<˙ˤŖŠ}§Î‡ā°õ?Ē ˇūūœĨŅ#čŦЏ p÷IÖsj\ā”G^IŽ˙hCŨmsn ÁaëˋˇmÚgéҚkŨWÁ|:YÜ#u ’¨ž^@īnøĄæÆÉBC„Č#šhmm=ßÚÚŌhiuĩ k;ßڏšYŖŅL$”Éûķ=ŗĨ`eށ™ũļfmŠû~O™’ĻMWåe¯)š{M’L&Ļõzƒ…¤"""“Vī*•BFŖ#eĢ+1ŖTČģU+ē‚™›’Ģ-*,0.ęœŧgĶ”;¸Št%ŸˆUį˜IœžūßŲîT•’÷RBFy?Žļl6›ƒËgÚãÎÆZ’(SUb>kÕiԅ)sQûD2–5–ÅIéB>Ÿ­((,ąĮŠŌU"†XĢN­.T3YiR†5Ģ7ųŠÔŒ8ÚuÅęüB633ÅũŅ9,zQŠĘLá[ĄÎ+,ĐH–ŠÂ‰­ĐäĢÍbeZē„Į¸Ī[ĀËˏØĐ[Ą)ÔX…Ę´4),FFoīØdŲ•¯6điéi|†ĩĩęÂfáĸ$QxJf›[`UdfHE kPÛ$ŗĶUb†XkI‘ē@Í*MŌ($’H¸ååŒR! ņ/¨ÁfPįĢ­beZĻ„G6ŖZ]PĀĻ/JYJ ņ“g/’đ‰ĩĩE…ų”™•"ęĢÜŗ .ÖÕŦÍ 1(TY"†ĩėĘĪSkÄáiR†Lģ Õfą*cļ˜!ÖRŽQĢ ˜ÎOøjŲ,V"ĄXHDdX‘žT-Î|ģ0UÂs‹r–deōÔÅĪIȒ˙äŌ|&ũíĪÖGķXŗ6wųŠGŗ…?ŦIęŊ|e’Œ^Ō–ĶŨ "˛•—[x<ÆPn"y8ôz’dJ2ä¤>™Ī¨ÖlZ/˛åęKŗîˇå–ŽIaˆˆë°ŋ­V,ŨT(‹ČĻ~ōÉõfeÎĻM ĄMŸˇbÆAž�¸.ļ™ĨĐß%:\Ŧë(;ũÞī~<×üģÉsŖzî÷ŧŋŒDDu‡tæŠcÄĪĸrC÷Õy™˜UĢܑ÷vÅ:eö# æ^“÷g‰˜˜Ė ?ĩãŨœCgHtĢjÆTĪņ%_ž<îķÍš[ĪúÆĻ-~<-=ĸęĐ'˙zķc–/tņCs~Wū?Ĩŋxėîrēˆ‰šûg?Y˙æĮį|“đČ sf'ķžëžIŽ}_u&æáįßæßssDõÔŖžoũkߍ’…†rųėÖÖVŸ ¸\-į[­Ž–ķŽ–ķ.GÛųūŒY-V"Júķ´…ES w0 Ĩœl]„Š$1ąZŽ(F!㑊DīūFßĻ/ˇL™!ąåú w ÎÁ•šŗ‡+:0%UÅ#ŗēĐĐq´­¤ĐĀr•*%CD:­Ž%aRĒ´Ģvy†j0žŪfmfZkåI¤†ˆL%Z+#UŨ- įķų"IŠJÁŗęĩĻŽŊí$Q(%"Ÿ!›ÕÎ2biŒˆīŪ3#=]!aˆXŖÎh+TŠpŸá‹$)É2Ąĩ\gė<ŖPĒt@01R1ĪaąÚˆˆ WdfĻĢäbŸĪI’db˛­›ŗËšË6ĄL)ķ>?\Ž”ķ;w%Ĩg.JM ņų|Q¸\&æZÍfÃ%"†węc$ĘEOĻŨ-mŋ†¤8ĄÃlėßråŒD™1[˜ĩ…ëssrr×åĢwLļÎY ēc§TÉÅ|>Ÿ.OKŲ Z#KDvĢ„i8ßŨXUzfǜ‘r.¸xW;øŌ$Ј!"F$•V‹ˆlVģKÛ¯9F‘ļ(]uBkŅdįčI–ę^×Bš•_T”ˇ,I.I’2Ķ“¸fŊŪBDfƒ‘+3‘H.O[ģņŗeÉû,gdIŌiŨWĨ+1HŌRĨf}9KDdŅélB™BD%ķÍÂô5¯)Ĩ"ž(\ąhíŌ8ģ&—­ŊivR,ËN‘JÂųdĶäëIąė5•TÄÅ(—/Oæ_ė¯��\‚¯čÖ)1Í6ō?{ĸŽŗ´ļôVū‡15ÆÚ^ÚQŧggĮŨü Ĩ "ßžĻEĨ§:ÖpÖũ¸uįONĻÛkRŋũîڂ}Įkmgę,‹ŋ˙‰âÂ"ō—LŠ ŦŨŖŪwŧîܙēãģˇ~ē[lāœūqĪOg]DėҞj'‡ŠŪģį8KDļ#‡-<VxÁP‡ã:ō]éņsDäúĨė@5‰ÂG÷2ž1H}å;"č‚ä5ķŠGB¯ßUēĘfē\ŽÎ Ž!WK›ËuŪŲŌęjĄ6ŸūĄv=Ļé–ČŌÕvaúgÚåŌîĮŲ`$b‹˛&]P×jc‰äŠ$nĄFĢ#UąēÅ­”KųqĖz]šbøTĄ-ˇ“4EvÁˇüWt <#M\˜§Ū¨Ë^+'"›ļĀāā)ĶRˆˆ,VŗƒH×-ųH¤bn×LŧdÕŧŊBĶõ+#ŒVĨ)% ŲŦV#öxĻ…/ķėfĢÜsÛ¸BQĮ6‘XĖ× 7’L&‡‹EŒČŊÉbą˛<‰¸ëÖZ$rĩVĢÚãOØŲ‘\.u|¨ ÃZĩŲfcIJ,ąŅEoPm›ƒŠ:Īà ųÔqãː]§Ņ­v–%–eÉAŊßę3ŒÍ ŅŗXlí'%ę÷m1#’ĒI•6ŗŲh4šÍfC‘A¯+ŌĶ’D k5[I豂#Žæ9ô& I…WŊ+_m“I$bąˆĪoīâžĘģ\¤Ģ‰ˆ¸<aį&†KÄ:X"â‹%BŌ¨7’L*‹Ã{Ģ÷ X ī—vũĘ*–ާãų >™Öe¯Đ–›-6ÖÁ˛ä 1Ë‘TŠäå<ô¤9CĨT(äá"‰ûßlå"™L”Ŗ7ØHÂ×iu|é"•P›^ĸ#UĢיųre8 ŽTŪĩR _":Ôz#Ũ-'"bââbÚˇufâIģũbäqĖúcũī��oį˛üTjņ ™ō‡“‡:G<œ•û:ËÄú: ĻĶ2ǎã:oč'NH]2â(:ûŸ}{>ŲįŨ9>aŪ’9œŖú˙õCGãŲf˙1ĒŗÃ3"Čß××חÃá˜|}‰čæŅNsuMgØ;k1!Kkļžé˜čb›ÉV}ļ} ĨšŲå$_Īq¨vÎzĶŠŽŸ]Žf"˙>î⚯.5U%-{īĮëj™‡Ū ņÔ¸ ƒĶÕrŪÕŌęt´šXađ/ĮÛú3´& šd4w<{ĐQĒTĨRûˇîŖFcėõX/9{mÚ#,\‘”!ĸ$…œĢ.×WPRŒAŖs„/’ōI˜$ãžTĸ§4Ĩ­\oĻčTY/ˇŌWt $-U’ˇFS !š’lš‚r‡0-Ũ=ێĩąDÜöa æ]i7]&Ą,U%u?€Ä0|†ßu^ÖÆ[^¸ĸûœ<žÍNî$Áx´)\™™),Ņ–ëÕzƒËK•*Ĩ„O–%†×íJ†ØÎ Ûķ"ŨlēÂü›$EĨ ų ąFuŽÚŅێ]Xļ{] ĶŅ6ÖP§ļˆ•ĒŲŸ!2īĘ-°öV…Eŗ1ß@qJe†XÄ0d3äi/~ŌKbøb‰\,‘ą6ƒ:_­-2H3åIJDVõšjĪ}š|–ˆøŌŒLF§Õ´j­å %IJ•\ĖôYŪå"]ÍPŸË†ˆäéé<Ŋ^_ŽÉ/fš<ąDĄL‘ŠúšLoöšŧE튂ᅋ=깊ŊÔ _ē6W%2DēŠ,÷ŋPFšöĢsמĩ4…ƒ+TdŧļbY’¨Īō…Œ›¯ÕQš¤ŧÜ![*•0R*Ô)ÉZĸ'ŲÛR"ƒõøüŨÁËvüâvaąÄ=vå2×ęß�€ˇđÅũƤ;%ē#1"ĸæŖûŽÅßîę¨ķÛŅÆÛúøŸĖāŨÖûūV5gâ(—8s\÷A’÷ŨVŅ~—<eņcÉA‡wnú÷qkŖ‹81Ŋ¨rgÐËåņ”ëęvKëĸ+žÃŊ‚†*‘Ķúũ–M7B ĸëjDČũ§ĶŲŌęrĩļ8o:{3˙4gxV'g¤IbŌĶī2’´ëkßđ”ė×:–l3­›­Éą÷v,—á9q’ĸįÜļÎÚå ­Ô•Ûˆ´åvžL.&ĸ8šØąNĢ#…­ÄHÂ4yøUžš.ËÍÖ¨wąJšĻČŠ3:ĻÂ1 CäčŧƒucYĮ%ĸ@ŋņ…"Q¯C$ Ÿ!F4;S)îQÜk5ŒHš’&M!Öf2ę4š‚”ų”’Ë0ÄÚ=‡Uzš_īÉf4˜I’Ē’vž÷Ō—Î0ŨG;o{YŖÁčĢT]ËôQ™Å`´ō¤™wK;Âĩã*&Ią6–éöh×*e:ŖÖl!y8É• UŨ?ęčWžDޒȉX‹Ņ Õh Ü[õQŪuxŋ皈QŒBŖ ˛Y*ôuQ!ņŗîîß<L†.•ÄôļÅĻ-Đ:dkr%uüåņl¨Hžņš<ã5bMڂܕ9Of ‹Š2Âû*—+ä´Rk°ØĩϏT9)d–<ƒÅ`.'ųR9ņųžą‡Čé™Ū"ŸˆXĢĮŽvöZũ3�đL¸d„Épŧ‘č˙ęˆBÆųĮĒ gFqnŸ˛gŨ—•Í †ēaĮ÷\/Îg\|¸˙‡š‰~3aR„Ģb]ÁĮŨ!euŽä8Y–|=v˙>æ’ŨÎ™öėŲoí¸…đßrÛh÷å9ĢŋßōîļãCŋčßåĘg„:ƒ'§“=īr´:ļ3ÃM&ŖĨ—ąÁK)Ķe ™ķר¯ø‰TBĖMŨ‹=oĸE2š˜Œ%z“^ofd )Q¸\ĘŗëĩzÎÁ“'÷œpןųĒ —Õ•5‡$5ĩãR$s‰ŦÆnáŒåŊp]K|ĄĮÚXâwaˆšđŅ"˛YŒĻö™h ?\šĸ”0ŦÕʒH$dėVŗ­sG‹ÉÚm[oܡ­ĢäŲ †K~Ė|ŸËē{qcî8'Ëqų܎FŗFƒŠû­nĮ'Īv ŖŅÚũvũ˛ąÆ‚ÜÜušf-V1<># Énë­_Y‹ŠÂŌ~RF$‘+eî§yú*÷ĐŋŽļT;ęå‹b”J)ĪÖŖŪÁÚYâ ;#´MS¤íĒ&Ũ.]{W1ኌמ”ŗfƒąĪr"’+Ĩ6ƒF]bO’2DŒT.1jÕ:Ŋ%.YΑX&å: ĸYʍVŽTÖËŋZąTBvŗąķc6čõxF� ˙|ĻÁxŧķ¤ž_"âŽ â æŨ6dmëø~ĐEw⌠šŲũC C.ļąc¨ææøIáDî0tæ”Íé/ î8fä˜đ‹…Ģ…‹|}}‰ˆęî,út[û;ĘÜã?ÎęŊųoŨ8)ˆŽ‡ DķâÜZZį]ÎķN‡“užŽķĨ~>#$J[žÍeĩ/Ũŋ´ ĸĮ kڕ“ŊîuŸōԁ¯ŧ[Æ%s^ŽÆã0ķÆ‡d§¯ėXÜDĸ”ķM^‰‘â:‡“*äŒY—¯.ˇsJųÕöį@&%MÉ8Jrŗķ —1;ŧsƒ\&eČŦéZJXÍÆ/{Îum…ËeB›VŊËhąąŦÍb,ÉĪ]—¯ëí6ŲĻĶn,Й,6Öfŗ˜uz3ņÅB†‰<ŽgÖĒu&›ĩYŒģŠĘmB÷XYßøábŽÍ¨7XXÖf1|Ydæ‰ųd3[.r‹ĘˆãČU¯Ņ™m6›ĨBĢ6°í‘†/ōÆr…emãŽ=.$›ŲjcÉ=øf3›-6Kĸpc-×UØXÖfŌhlB1×aŗ^ėŦ}5FĸPY}ū:ĩŽÂląX,ŗQ§Ū¨.gÅ šˆˆDRš˜5Š î~5ë ōōÜÁ‰5kÕųģŒ›ĩŲ,­ÁÆŠų}–w;k?ēÚfÔĒ ÕēözM­ŅÎ_<;õ‹(ZÂsh ō 6›ĨBŗâ‰|ŽBLfƒŅ²ÆüĨ 3–|Š3Yl“aWNžŽ‘ĘĨÔW9ņåĘpsažŽ‰sŠâ¤<}Ū:cxRûúI™ébkūK+4F‹ÍfŌŽ[’S.TeŪŨK‚)SãH›ķRÎd14/­Ö˛ũœ��Däbmg\DŒāˇnšUzK,ŗ?īũ‚Īŋ?zĸŅIDÄ Ū<ôã&ūŊĪËīļ‡û{ú3UÕMŋũÃmĸĀüđÛî}8ŌöŸFē9LčKÍÆG›CĻĒbƒųŋ ‰QĨMōīĪ$§ë˙“ÕÍQ‰Ē ĸߌäß<’ķČS˜Õ{ķß*2ŨXW9ôSãČã!—“=ījiu9[]ßķũ^,A˛(Ŋ-sa^QölMŽTŽ ų Ųlæc:ƒŅî ž,s}îĸŪféđ͞—¤ŽT/MļĨeĒdB‡Yŗ1ˇČHqËĶ;cŠTŠāhÔv‡8Ŋsi.™BB+Ô#ĪękRŨ¨HSņŠ Ę͌bŌãi'ž*SšF¯Î_xŋ-3MÆˇĶ¨YšŒĢŅ÷ĢĢŽ‚Hž‘A&%._$Q¤õ\ŦĖ-üîTåŽb:_cw0\/\ĒJSˆˆˆ§d¤’F[´ŽØÖ^ƒōR˞3’•Ŧ@ŖÉ[Ŗá ÅōŲ*9éŦųڂ<nZV_”‰QĨ*՚’‚Ü⋤JĨLWāî.q’JaWkķr´ŒP,SĻĻđļš‚uę´§Ō$R™Ø¨Uįå‰U™ŌŲJSĄVũļžxb‰Byˇ„Ũe-Ôæo¤ôEIW¸‚€Hžhŋ¤DoĐhí–ËåķEâ¤Ô4yûŠl|ij:i5ēü<ĩƒÜŨ•Ą_ž–Fj&ßā~—‘Hœ’ŽŒaˆú*÷ˆiũéjQRšŠÕh5‹mbxBądvzĘĩXŸPžü…Ö%9Š“s¸byÆōÜĩbUˇ2+yiîū5›–e¯Čɜmu´ŋ/h}všˆH´f“ĩˇr"Éd<{ž=šķË™œÍWķUŠ/fYáÛüėœėû7Ú OĸHĪ]ģŦ÷ŋ9ĸ´ˇ×[ēbåB5‘Xžą<;mœš�đ Œ v?ˆņu6Zūc<Ô>Œ,5U‡žÛæ?ņļ 1ŧÆt°56žs]lJRãŲ&""j>ŧsĶ÷ķî›ķøëÔXmÜûiÁ4U°xęŧœų/m;ôÉūCs3—MužŠÖíüîéɃs׌ĢjgŪÎ{š“95ĐŨ=uÅoæĒkŠņäRĩI]Z}cĨ "ōikk#"“É$ēŠÕ |† īĮQ ũSrr2uB~eoŋ#åŋuUįßSŋXgž¸Ģß #ļBŗ1¯°H§3Yâry|‰T&WĨfŪíyģŽY21ĢH’­ũ,ŊŖĖģVįæŠ5Ǝ{,UfÖ2Īû?Vŗpr––åĨnŌŋÖqe\­œg&é íg a]XķåØÁ°2ņū|›2w˙;Ęî_O˜w­XšæKŲN\ĄDųTök’ĸäûķųË4E‹cm����""ZšŊârv ŅYKcīĪü˜8#‘ķÃΟęČä:{n(oŠ}Į¨KO÷16åŦ+Ũ÷é1Ė”ž&ÖŦ\~Eû÷īŖn‹e(ƒĐà 2fΜIŨƒéWOL˜áCtŪåt9šĖ'ŽÎüĢæR5ũzY Ō“_2Ē<R���Āõä2ƒP&T3†Xë)Suëōen=&|$Cž ?”ŋ^ō…¯˙ˆūŊ 57ښoˇ…Ūˆ9 ÍÔ¸ TQQáK ÆA�� �IDATršļmÛvūüųÖÖÖÎ?įF6Ņi¸ĖM~L�ĮÛÖvīēáŲ49Ģõ$Yš‰���ŋlĄŦ†|Cƌ›Āø:Yë)ĶO'¯ˇláj>gģá&zÁ•š ´āáG†äŧ7 ‹N­5ęÕëÔz‡8ũĩĖđĄn���Ā@r5֚ŽÖu+Āģ åb Đ'CAvbyæk9Ëz_Š�����ú Ač甞ļüØÚĄn����ÖPžG�����`H ����€×A�����¯ƒ �����CĖĮg°Īˆ ����ũxĶMÔÖ6Ô­€\[[P`Đ ŸA����ú/ũÁ´!ø2~e†ų<š1w°Ī9Čį���€_“Ȱ1/z$đĻ›|‡ _FÜtĶĶ^8Z$äķú´ĩĩ‘Éd‰Dũ¯eØđk�����Ā%´oí÷ą‹#B�����āu„�����Āë ����€×A�����¯ƒ �����^A�����ŧ‚�����x!�����đ:žRËÕŧĖ�����`aD�����ŧ‚�����x!�����đ:B�����āu„�����Āë ����€×A�����¯ƒ �����^A�����ŧ‚�����x!�����đ:ž?™Íæ!l�����Āāārš]AH"‘ aS������‡ÉdÂÔ8�����đ:B�����āu„�����Āë ����€×A�����¯ƒ �����^A�����ŧ‚�����x!�����đ:B�����āu„�����Āë ����€×A�����¯ƒ �����^A�����ŧ‚�����x!�����đ:B�����āu„�����Āë ����€×A�����¯ƒ �����^A�����ŧ‚�����xk„Ē4˙ãą‡f%Ū=>ōšl꜇Ÿyã3]ŖûŽûūψŒŸ¸æčŽn%ĪË#ŖĮĪú j¨����^Įw@ksT˙ãą Ž6‘_  TĀu4ÖÔThk*´;Ö¯N|áƒ73& č9�����ŽĐ@ĄęíKæüåÛz",ũšŦ Ĩ<´=ō4T•l}gŚU{WÍ{ÔąuSæxî�ž�����āĘ ÜÔ¸ęO—=˙m=QčŊšÛ7Ŋxg "ĸ Č¤‡×nß´0֏Z īdoÁT(�����J„ÖåéZˆBĶr^ÜÛÜøgs^ēįŽE/>(čVND G˙ũü‚éŋŒsÛô9ĪäíĢëyxîĶėĮį&ū>>ÆũÜŅsŸZŗŖ˛ÛSG†SĮGFĪũ°šŽnË~tŽėļņ‘ŅņŌŠs[ķMĩŖg}%,™3UĶqƒjéū.‹™ēąēێ†Ī^{rΝ é-ã#o‰—Ũšā‚ķöĻōéŅã#§ūã�5Øō×yw*ÜÍNL]ōžļĻgßUīũđųEŗĻĘcĸĮGFĮK?kŪķy%´ØQųÍ{Ī/čÚmęœyĪįiē7åĸû¸ûgÖęĘnÕēŸÔŠüũ_÷u+ŽųP5>2Z‘­ķhäßē)›:÷ąŋ}Úãc*y&>2:ūáí uÚĖ›*‰ŽǏŖy ÷G=>æ÷ŗæ=˙遆Kv"����Āĩ2PSã*>ß}ŠˆbdNé{ÚÛøy¯ŋuA!×Qž"í˙nˆœ?-ĘQsDW^ļãÍųõŸų⤎Ēǎ.šķBi=ų…JeJ™€Zę+Ëô_­[ĒŅVl)|ļc7?.—ˆZęõoĖûÛÆēHé¤ÄT_ąO_Qŧ.ë@Ũģ˙™Ø1HUWôø}OīŽ'?A|bB”ĀQ­į‘ģ÷¯Čōk ".ˇë *?}8ũm=ų…Æ%MOæ6Ų[úÕēũšŨĨäŋžÔkākČ%ĸ†úk^ŊŽ&X3ezLÃQC™agÎÂũeo~ūÁŦöƒG7Î{hUY#†NNš)"GuÅ~Ũļ7uģŋÉ.øäOQÔą[ۜÔ7ļø bdĘ{\GCûnÛw.+ܲ8–{ûÄ(dm;q@_GQM?ĒÕ×Õ´GiJlGqƒ^[E899žˆČQöÆŧGח5R`ädåŊAT_iØ_üÉ+ÅÛwdoZ÷§ØöãršDŽŖŸ.ے$8n’Œ;&¨Ŋļė´‡7W‘Ÿ &éŪ¸`ǝÔ˙c^ÚŅ#/ԁ�����×R[[[[[ۉ'ÚŽÆĪî1næ?\ÁAß/OŒ+šU6íšŨ?wUõÉÜ[c#ÆÉ˛4l{ Ģ]z{lÄ8ŲÜÍÕ]ŗe¯ĻÄFŒ“ÎŨüKGQåģ)ąã¤qˇN[ôyeĮÁmŋ|õÄäqą2 퇖ŧ8y\lĄŪ=ŌšWõާ§ÅMFŒ‹xpkĮŽGŪ-'öœļķmluáâĈqąq‹wÛÛúÖŪ!RÉí˙ę:‹ũģå3#ÆÅFÜūâ÷íevõŸ¤ãb'?Ŋģëmöīž›Öũö]‹Ĩã¤÷žßu]mmėņ2âÆÅJū´õ—ËÛĮūÕ’qąqOīîÚá—Oî›đ`f¸Ø{7{\eÉŗqãb%‹ˇÛÛÚÚØ˛—ū1N:sĨÎã’ŲõÜ´ˆqąŗ×ī(r “oŸvoN™gįΙ1.6"åõīí‡?=-b\lĸؙīW^¤#����܉'hj\}U5QhdčڝļōÕic:3c~ŧQceUį˛ČųĢÖŦ^õúĘ9ĩsĨķī!j9 +īšbåGD-$6gNdį¨Nđô’‰Z*ĘÚMrėÛúM=‘`ÖŗCDĄĘW_N jņl˜C›˙~E îYųjbר7ôūWžûQŖvcŅķ÷.ŧ¸¨/{œ%(iéɁDõĨŸ—š[͝´hí˙Žz-gŠį| ¤yĶB‰ËövĖb̝Ŧn! ŠŠôoãF=ŧfKū–í¯¸Ŋô>A˛ÄI~Ô¨ß{¤csƒ~ī œ4īžItdīū†Žō#{ ä7)qr‘Cģą¨†H0sųR™Į‚ÁĘĨO(üˆ*>Ũ\ÖíšëIö\–ÔcĪŖŸo?Aä§ČzbJĮá/=!÷ģd����\„-"ōķ ēōåāe3&u;*(84ˆˆę;7t’bæũsĻEq‰ČŅPWS]]S]]ĶāÎ==Ÿ5™4=ĄÛŨ\Ap09íˇų5G*‰ü&)¤ŨO+ģ/1ĐŗāČn}#Q`â´I=.*8!9–¨Å +ģäS.IŠîĶŋ‚&+"‰¨žōhŊģqcäĶfĪš7i 9ęܗVŨ@\"r4v$“ШČ@ĸz͚t((xŧ\Õ>˙ė2ö NHŽ$Ē/ß×°´û[(F.›,Ĩ–˛ŌŽ€TĩOwŠ(291˜ˆŽėŨßH(ģ°dŠX":uähˇDčß}Īēō#5DŖõ8<9–�����†Ä�=#Ä "jt‡’+ĖBAÁ‚>ŽđŸi8úYî;m/=ZßŌûž]ƒ=^TÄ ô#"ęõUuD$Úã´Üņņ´­ŧãWGuM=9 ų˞ųĸĮ9ęꉨĨ˛ĻžčâC`Ąą=ˇûĩÔu[w`Ë;˙ģáÛ}U\[Wœá*—ž|—aéW†ü?Īø40Rš$KPLOH’Įs¯hŸĐ)ŠǍŌGUiõ)Lņ¤īxL¨Î ­"ŠL˜2ÆŨ D<fô“`L°QKuU=Q׀Vph÷´žĻŽˆücz>RÕ~øE;����āš  LTC5G̈â¯đØK'‡~EÚĸĒZüq÷-š)v¯D°ûįļ¸ōļ:ęž$BGC‚‚ēīÖBD-UĨ_õąÜˇŖžáAČĪpš~\ĸw#ˆę4ĪĖûķŽSä7Z~īÂ丈`A—ˆjvŦx}gˇÕåÆĖ|ë˘ģļŦ˙hÛ7ĒöUĩ˙ĢOۤˆäE/,,qĖeī3>q˛`Ũ‰ZƒcN"ˇz˙žĖMˆ"ĸÄɂuûĘę(6ØQVz¤…˛„ņũĀ ŧđsârŨ ĶŅØŊ¸ûŒˇöKŊ°Ãš\L���€!2@A(8nR(•Õœ*Ų}tyüE&<5Ô5õŊŊ7•¯ú¨Ē…BīųāËד<Ž­ĢßHũ BÄõL"Ũ&ŲqšA~D-‘‹>ßŊ´˙S¸.<‰{a{,([ŋbĮ)ō‹ųsá–įb=‚BĨaõ…uE*{]ų9ęŽĐī/ŪūÅgģ+Š×>v¤æãí¯v<ŊsÉ}â§|e(=B‰QúŌJōKJ”ÅĘ&ų”ėŨß0ofåŪũ˜4]ęŲŽÆ įē#Ro™ŌSûf‡ŖįhĄŖŖA����0Dę=Bą÷͊ ĸĒ-ī\d ē­K§˙~úŧ5ú†>wšãˆžŠˆ"gĨ'uOP•Gû“‚ˆÜ Õ×Õõŧŗī^!wL¨€ˆęĒëû˙ž›–SÕÕ=ŠęęZˆ(8R@DÕe†"Š}⑨îĄĒĒįqž- Ž2+cųģ[÷æ§EÕl]_rA‡öšw˛"Ūj Ēû´†’*âšDDAŌöĮ„UûĘęÉor˛ģŧŖĒĢO]ĐõÕÕ-D4&rôÅ:ADD-õÕ=˙bÔTW! ���ĀШ Dã<‘HÔømö_6é-:4”ŊņØkĨ-õz%cBíuq{<ųSˇãŖíõũjihl¤QË}E÷ķėũ|wˇ Į'ʉõ;÷]đjĶ#ÚoöU^NšĢŌęģßū7”k̈H0ŪķᥠŋîC*5Ÿm(íĒJļúaqĪ)zAōi“‰Zę.o"ĸ )‰1D:Ŋ^gh¤Č„)íî„N‰MõmŲ~mųÅ'vŽđ6>QHÔĸ˙Ļg?ԕWQÄ$ŲÅ^¨DÁ1ãDTĄ3tīąę>į����\k„(xæĘU3B‰õĢæÜųä{ÅGģF\Ē4<9ëĄõe$˜ūzÎŧ+Zc;hL¤€ˆ*ˇĶ5HR§_ũøՑ1~DTSéUŦģW8eēĖ¨fë;ŸyÔ¨ųÛ+GˇU㸊ô#‰ęŋČū›ĮŠÉQŊũ¯-˚ŸžĒäŌCE-Ö­*ę:¸á@î;%DĄ3î‹%" ŽŒ $ĸ˛oJēŽĄFķˇŦ÷1‘ä1ŠâØ˙ūķ¯Ŧ|á¯ī•uËuڝû‰‘ãƒ/o""“8-’ZlÍßWC™´ã•­•( ¤ûÖ}s¤…ĸĻ't†Ž<ãūHĸúĢģ åÕŊüŽŽ…üd ‰ĸ‹Ššoúhĸ–âÜ7öuī¨ú÷Ëë+ņŒ���� ‘zFˆˆˆ‚“×n]/xė…ü˛šosžø6‡ü‚ jŦĢol!" ŒŸûú[ž¯ ē<“dʡŧĸ3ŦšŖŌ'Å ¨ūÄ>í~‡üĩ-m\6cUYÕú§žŠIš•ņ\ōeˇsÖ_žŪĸĪ1”>wįôΧËĸ¸ŽJũ7:šļ:Ģ1ûuĪĄ˜Ø§ßyųHú+ÚmYĶ÷ÆM‘EsĒ åēĒzō‹˜ŋꅤK.ķøĮ+–Ũ9ũ#…lŧ€ęŽ––ę[hô]]č^`š+O0rĮûUß.ģ{nņô˜ Gũ‘ŊĨeÜiīįgčŌįUÕė˙ßĮ—~ĪĶŨŗ<kÛŧĩå9iŠÍRŲøPA5ÔÕTė3œjĄŅwŊô—)\ĸāËØĮ-J–JéKëÉ/Ųũ€qã'OōûB쎔("IŪí•MĪŊų‘‡Vé6<œ¸7!I6:ČQD_ZVĶâzĪ[ĢîŊčxq'eŊx×îŦ¯Ē æOŨ/ĸÆĘ2CuđOĪڙŗ­Ãz�����WeāF„ˆˆ(XņâÖ=_}ô×ôdYDh 5֟Ēi  Č¸äš/ŧŋŗx땧 "ĸ1|°éåûdŽĒo?˙䋒*š’õÁÖŨuĪō%“Cũîūv_͕<ČÍ]ü¯-ĢįNŽ Ē×íøâŗŊ${vsÁÚŲÜŅsŖøøëĪW/š1>čԁŨ_|ž­ôHC üŪŋŧ˙å֕ŠË˜ßįāĘ_úxËK2nUégŸ|Q|Ô,ą,Ë[ÉgâÆ>—ŋnŲˏ`GÅWŸí=4ũÅĪ Ö*ĮH˙üJzŧŪđM‰ĄŪAÜņmØžū…ų‰1ÜCɎ/>ßQz †;iæÂÕ_n}k–ģļËŲĮ-F!s}u< ä$•ģ_z$˜Üã ?ÜØŒ-_ŧbnB”àų¤`ķöũÕ\é]Krˇųēōr>Ņāi9,›J5ēŊߖ­žū▂“‚‰¨ˇe�����Ž-Ÿļļ6"2™LáááCŨ˜!Vˇeüåũ~Ķs īNģōÃvWŊqÎW•ųÍx˙ĐZå€4�����ˆÉdāĄCŨŅ}ÅÛū]|´ûHDÃ]‰Œ¸Ú�����ס|Fč†QķŞ'ōkübĒ6}ŧ<ž}†[ƒîÕģ‰bîģ7rh[�����ךWĄø'VÎ-}ė“ŠŌ’Kd“Į ¸ 5††Säģčå?]b 4�����¸áyeĸ ¤Wˇl•¯˙ߏŋ9PVúUK‹_ `ŒlƟ<ņ§äHĖ‹����øÕÃb �����ā]ŧuą�����đnB�����āu„�����Āë ����€×A�����¯ƒ �����^A�����ŧ‚�����x!�����đ:žUëh1×X-ÎÖÖķU' Ļđ€ãCŨ����€‹æëīãxSđøážūWYÕĀ!ÖŅbŦ<xSPāˆáÃ0Ętcj8>2ėæĄn����ĀEøR“ß™“Ĩŧ°„ĢĖBZĖ5–ĀĀ›Fø#����Ā5ãĸ€Ļ›ƒ…ͧ\eE“[Xļ%ŸĒ������.&ĀŲÚŌx•u L:ßÖ6ĖĮg@Ē�����¸(×yWķUV™l�����āu„�����Āë ����€×A�����¯ƒ �����^A�����ŧ‚�����xßÁ?Ĩŗū؞_—:Q{ē™üƒafÜĨŧegđ›ōkW‘˙ÜĒĒ?ūãå;C†ē%�����וÁržüzÕ+opEMO[üLÖSé)1tė‹˙YšjĮĪÎAnJģcž˙§Ļ~hÎ �����CbpG„œU…ëž8ÉK\ō×cÜE&Ęn—į˙sÕÖ {&e+äĸūį“v€ ����€WÔ ä<ômŠ%HūLZG r ˆ™ûÔĢĶũÃBˆčđúŦÜZÕk˓GšˇU|ø—U?ßĩæåi:ļáŲˇjg,žõĐæBŖpÁ‹>üË{ôāS!ÚõŖ˜'ß\| 5üžđ͝u•Ö& ‘Ü>?ũŪ‰""ûwo,U ?!Ōmúö`­Ũ ēUĩ`áÆrŒ˙~:g¯¨ō…?Æ?ú=Ļį9uo˙e=Ĩ-‘ŪŧķXm“3 $ūžGL sīrú§Oūũ…ūDmC3ų ›”˛ ]áŪb7~ŊųķŊOžn"A؄Š÷ĨΒđú(?ŊáųՕIuψˆČŽ]ĩdseüŸß}2žCDTĩáŲ7NÎxeyō(˛ŪžŋmŅRßL<Q´\•–*q:Žka:m˙đûĻ;ž]37’ė‡ ?,Øc´:„§§É¯ņ ����pƒÔ tōЉ&˙č[%< ÄvéA_ĮU_ōÕÁß§žpŸ($€SÉ!gÕ×Û9Š…ËĸCBˆęŋ/gÃÉČ{ŋ<9„,ēMŪúį /§EqˆÃņĨ&CáÎQ žym1ĪY[üÆß6mŪsËķJIÚĢO4ŋøŽeÖߞĐŖY"§ņĢBÁ‚%˙\Ė#KéoŦ˙Ÿ‚>ÃĄÚīŊ÷%<–ĩx,N.üpķÚ|Қ?EsšöoxûĢzŲ pÚ+wo^˙æŪ?ŸJāôZžøÖH˙Rã‰Ļ™ĸ�"gÕá“AA?>IņQDT{Ŧ˛iÔDÉ(rVæŧ§áÜžđ™Å1<įI}ÁúuoØ9_<)€Ã!j:˛ũģhå“·ŲK×ŊˇŊ>~Ყ&ōš*wh"Ū@}zŨœ­´ŦĖũĖXy*2,øšÅ÷Œš%ÂsĢ^ķĶŸh,gdĮŊŧl.ų{nŨō¯]Û4˙ĪåĸYĶn}ôņģ¯ķj���āWi0ŸrÚívâ\ÅĒõŋ[03>*LÔ>¤d55}ÚĨą‚�:YōõAúŨ‚Įîœ2J2aÖĸģĸęĩÛ4ĩŸÛř8ãÎqBdˇ‡ŅĪ•?;‰8<‡ˆÃ čƒÚEĪēoCÄ%¨niøĄÔč$"Á‹—ŋœ5Rdˆ`TˆD1kŌ(ģąŦ–ˆęĢk›y1ŋŒ„DĘĶŗ–/ģwb@_在ITuŦŌIDTyčOv{ŒķDE-QSÕąÚ€č‰aä4~ģĮ2Jųȃō¨Q<hâĖ÷D6ü´Û`'"ōuēœaÉķ$cÃ˛öiĸj~BÔ(ž`ė­sSo põŋ¯ûvî¤õŽG_˙?;m?ˇ˙ā‰ÔĮß<yčDįÖŸkŸ^ųņ‘ã§Îœ9÷uɁ™üZĪwn}aŲomØūsíŲÚēŗë?ųæÉÅo^ĪÕ���Õ`!qˆŽnIÁ؁įī!Ņa‘čd••Â&tMēãEO¸*Õíŋú ŖÂ: jēœ–„D„u$Á5×Ö7'€ęŽ}ååĨĪ>÷tÖ_Ö~wšœN'…L˜8ęôžuol(Ūđ¤ŨIŧ°¨ą<NŸå‘œUĩDd9h¤¨[CNWT59+āH&DÕnōĶ5bÆ ‹åŦ­Ēm˙UŲŅDˉz⅌íėQĖØk2¸ņÚ;_œīVå~Ųųķ›yjĪMg˙ÛüɆŨĒO—č{nũņđ‰Ú#?_ŸÕ���Ā¯Ø NđxdˇÔ:)ŦŋƒBœ7œ€Žģ~WSŅĪ_¸ĄÛvQgŪé×)9ŽįĪžälj&âĖ˙į[F)Ķ,ˆĀĄÚĢWéŨûDĪ˙ës!;w•îŪŧįĶfΨč„ûæĪ—‰8}• ĸcÛ*ĢėÄ;Vi“9**2¨ĐxÂyGĐÁ*g”*šCälrĮßŖî™~Íí×å빊ÉŲDž<Ī÷1Îu•ŽŸ¨íQb<Ņ8įĪ5ĩôØZQUŨׁDt´ōįņc¯Ãj���āWlPƒPČ-ģë4ÉeŨVK gUią5뎇ô<Äyš#HžD’ÔWšāyīĪ uUŖPMįw6;]āOÎcĨú†0Õŗķe"÷–ZĪVō"§Î]<u.5ÕûiwÁæušœQ¯ÍęĢ|ėDIĀã ;īđIAtT�…ÜIųĮjëƒ*ícå’�" đ%gŗĶŲåœM. đī%âp8ä˛{´¸ŠŠŠß—~Qâj˙î%ÂöŸ¸Ãŋ&ļ[ēømX{Gũļs7Ī­âëŗZ����øÔ÷q$L5˙ôÉf]ˇ÷ö4U|˛aƒú›ƒvrÁ45tŪ˟Žĩ4_^Ũŧ°H!Õ[œQHˆûŋQīr†DúJõĮ*;ŖÄĪU')HÂ#§ŗ‰(€×1ëĖy¸ô@ƒģg}ÕO‡,îęBĸæŪ;Ņ×~ŌŌÔW9EŨMU‡KTDF‡ŅØņaöcõ‡kŅ1""dl@ķĪ]Ŗ§+>ÍŲ9Ņ¯Ë¨1!d¯ũšĢÅU×äĄŋeÍáøøx–ŧōLjįĪ/ūyŽį&~PĀCÎt˙<펉ž['OŒ‹‹¸>Ģ���€_ąá˙û߉Čfŗņųü~×bŠ;4âĻË8ÛȘßú˙gīî¯KĘj›éüųsŋ˜j6oüôGĮÄԏ ō=wä;íΜ‰˛Ø›9M'‹7q贝‰Uū!2€ÎÜũŋ„*Ļo_í—w|ß<aFĸ˜!""Ūo˜ŠŨ;ž˙ed„(h¸Ŗî?ßå¯}OcULŧy¸ķgŨöōá“gN3œˆˆœÕßī<HˇŨųûĐáÔxxOie“(:‚;Œ d†wĩõ|­~—ŽĄål gL8¸Ŋ˛hķW‡Ũēāž¸›™ágüîûŸ‡GĮŽæØ+‹>Üu.$¨æįæ°Û¤cęvŽ}{g%WÂã:›ę*÷îĐ)ū.eĖiuNoåx4œį¨P÷ĶYGDōũōáÄaÎî˙Rs´šE•*IDÃG7ũXŧįčšą(€ūķíĻM˙~˙đüɂ Ž+€wūĐwšō:^Øč@į/ŋøB[ĶtŽųíô?ü6đ2>ÄáŽjŪe=SÄáŨ´`V‚šĻÎårNšĩūŸ‹FĶš52vėą‘§~9ãĮ>í÷ˇŧûö34ŧ+‡üqúmB&ā´Í>ŠĪ{đî„eŲé×sĩ����p}bíÍū#Įõûp›Í6¸/T%â„M{aåØ=ę¯5˙ˇMˇŗ™ã)˙â]SŖÜņ†'Ÿû`ÅēmĢžũ†0*ꎴÔ;,oēŧųq‚ÛŸZF…Ÿ~Ŋö• MäĪ ‰NxėŲ{ĸ.5"ĻPJ …›V¯Ėu™BĐckČSo9ą9gÛÉĶ΀ą“>‘,S,�� �IDATÅ!"‘ō‘ÔĘu_­}agT´üž %UÎĒ ›_Ÿķr֒ô‚Í;7Ŧø´Áéë/‰NxėŠ{ˆCķ{-'" ˆžÖp°*zbûšŖb" KœōIc;ēllę˛ÅųÛ6ä|koö卝0õ‰gS/\‚œˆhÔÔE k?*,|ķī›9˜?¤ÍŋŖā­—9¤ve†ũ&på?2ûÚ;EōÖI_[gĪKš=/éFŠ����~•|ÚÚڈČd2…‡‡÷ģ–‡˙* °F]œ?Ŋũ—ˇš|wŲí<¸ôëÄiø~dØÍCŨ ����€K;{ōĖȈũ>Üd2 ę3B������×!�����đ:ƒũŒĐƒsë“o<ԍ�����€k#B�����āu„�����Āë ����€×A�����¯ƒ �����^A�����ŧÎĀ!ŸķmmR�����ĀEų’s•U LōgüΝkĒ������.ωÃņēĘ:&‰CEᚚ˙ÛÔzūü€T�����p_jēéLÕÔøĢŽh 0\ŋ˜¨0sĨîôŲÖVdĄRx�=yf¨[����Đ7†ãÄ Kîë•5 L""†ë'‰ ¨Ú`(Œę����� Ŧ�����^A�����ŧ‚�����x!�����đ:B�����āu„�����Āë ����€×A�����¯ƒ �����^A�����ŧ‚�����x!�����đ:B�����āu„�����Āë ����€×ņ¨ŠZ[[OŸ>íp8Z[[ĒN�����€NÇįrš`ذĢŅ˜ ÔÚÚZ[[;bĈQŖF >|@ę�����đÔÚÚÚØØxęÔŠŅŖG_e˜ŠqgĪž âķųHA�����p >œĪįž={ö*Ģ˜ IJėˆ#¤*�����€‹1bDssķUV20AČår]ũ,=�€˙ĪŪŨĮEUæ˙ãį47Ā 0ĖŒĀ 3’7ųQSō†Ä]LüBJš”ŲVVļŨnĩÛÍnĩĨĩvc–Ģ™†ŋĐhDQŧ!Ô€d�pAĪ!~pŖ(¨Ĩ2āŧžåœsëzĪ™áņ˜ךÎ����\–ƒƒĪķWŲ Ō �����Ø!�����°;B�����`w„�����Āî ����€ŨA�����ģƒ �����vA�����ė‚�����Ø!�����°;B�����`w„�����Āî ����€ŨA�����ģƒ �����vA�����ėcëēŦ>ĩvũĻ–VKgg§­kFŒáė(~øÁûW~´vGôõņœ����leHĖUŒ+W¯=ĶԊÔĢŗŗŗųLËĒ>üĢOmP�����›Ahíú͝„t‘#:;ü×nØ<¨ƒ���� ē!„Ėæ3ļ.a¨1bđG47ãå����€ܐB˜Rp"����Üđ†D�����LB�����`w„�����Āî ����€ŨA�����ģÃØē€ßĮÍÕ%:jĻ—BáęęŌoƒĶ§›jŒÆÔô§›š6�����.†SrsuyfŲÃ"‘đm\]]\]]Ôžž|ú…Ũf!‘Ģo€Ę‰%"â›OU–Õ[m]����ĀĐ2œ‚PtÔŦK§ ^"‘0:j֗_'õŨė{ßë‹GųüßW÷lĖ{xŲX>{íŠzūÚVk3îw,}:BRßÔFD$tsįs׎Išaž����Āĩ0œ‚Úwä•7öRx^Ž ã5yáÃc™Ŗ7Ü�)ČŲŨ×Ķ•a‰Üî˜â]_¸9ũ×6""fTÄŧIŗīøu‘ˆįΘ* -ÃūŠ����\ĩá„.œj¯Ų˙Á Īn.hŸôîūĩwKú6hQ/įĐyËĸå•ß|ņuiË5.Ô„ž^ę.aģŽ{pɸķöÎZĻ&âÚJ2VuŧŅ&���� %Ã)õUąņąEĢjd’›Šũ÷,ōŸũT\@Cú†/ŽöYG$R†EGO Wš‹éLmųņoSvūzēkã4sūėāQÎÔV_y|ĪÖī ëx"bÂ˙ōŌũ”ņių¨ųžžBĻ­ž8ũ›m9†Ažwą$}ÔF/=5V4P Žäûˇž*Ät����� į Ô.™ôĘļ8į/"—Ĩ˙Î#÷ą'ŒĨ#[Ödû×°‹ņÖg~ųaa-ÉÃcæ=üŗúÌJžDAķžJhØŗm卜kĀܸyO‰øˇ‹Ûˆ8"V}zÛ§oo9Cō ņŪŋdvíÛߗ×uÔˈŲ3#.(#cįžô{ūؓŋ”’¤įĶ•Ī>æōŨۇo}.–ųúŖŧ;žY6`@����°CÃ÷{„´ķ–Üí/š|ģ °Žc|(z”¨éįŸ*ÛúîōēcĘ­T°õëŋÖ75֗íúzOĨëØ™AB"— ĶBDēŒ/2ŠkN7ՕŲü}4e‚kīĄ•;3ĘÎđDŧ)wįņZįā ū}fúÎ=}3ĪõJAD×ũžį9Žxâ9L����ô5|ƒĐ$ ķÔgî9é43áĪŖûŦ9rōVšĶŠeŊ÷šnŠüå4ëë¯ F1ęĒ-Š>Ķŗ§íä‰V1ꖞ´SWUĶ6NHäéváŨíÎĪB×/����Ā•ž—ÆũA–ãÉ+‹ÛœšdĪÄ=p_Åģ˙íŊy�#ŨķīĮœßžĢ2ŒaÉ;jųGQ}ē:á,$˛ņ<×ģ•įyŽXĄčÂ{0ô†¤ �����Û˛ģ ÔvĻĨˆZ 7í÷Âc~xļqeF×J!ž­H—ūNJw^{ž­‰įÚ8Ē>°~і>{δtŸ@Ãönf„ KmÖūŋÃ����`(ž—Æ™ŽgíŪ•q¸ĻÎšČÚŊŋ¸ųwßVžņÅ“gÄÂ{ģ.ckŠÖדĢ;{ÚTWßõĶÔÆ[›[x⍕§87Wac}īŽžo9Ķv\ũ|{/…ģÅĮ›Î4ÔŨ�ˇä����¸a ß ôķēįžyōš ‡Ī•üĪ>õĖËI%ŋŗž&cˡ:á÷ÅNr%"Ēųi˙ QØũ1ažîNÎŽŠŅĶžø×‡ŖU QSnv14ûÁÉžNNnâ~å‰yį–ņŠi1c}]œŨĸgK‹sô×ôšūŦČÉĶ͉a„nî.–‘¸ĘeŽW����°wÃ÷Ō¸ģ>ÎËŋęNšržŪ6晸ų 3k>ŪYyúøŸQtô”ĨOĮˆŠ­šž27qCēž'ĸļĸm˙Iš9fė‹ŅÎ,wĻļŧpķg;íĒ;˜ZâķčLo7ĻíTáæ•6šQ[×íáÔQĪljˆ<3ŠˆčŪeˇuīÆÍã�����ē § ÔfĩŠ„W:ģŅxēéĸm•_˙ũĩ ˇĩų暍m†ãßŦ=ūM?ũņuųkō3ÍZstÛĒŖWXÚõR™_X;vĸ'ÛīÎĶG˛M:����‚†SŌUœ ­šÂƧŒĩ×ĩ˜!ˆ/ĪxëÍũnĸ~^SŽ­åŒ9���� Ûp BŠé™jŋ‘W2)ÔfĩĻĻgBICŽĩĨą˙ûÕ����Ā9Ã)5žnúā“/ĸŖfŨĸđtsu¨Í)cmjzf—Æ]|ÁWo>9(#����Āĩ2œ‚5žnúōë$[W�����ÃÛđŊ}6�����Ā„ �����vA�����ė‚�����Ø!�����°;C#°upžx9����āF7$‚ÔYBļŽbHüĶŌŲ)•H{P����€Á5$‚Đ’„…tĻ!ú3ø§åĻ.ž°����\C"ųúx=ûÄŖÎNNļ.dhqvrü듏 ūˆŪJÅ` ����0øFtvvQeeĨ¯¯īîå*'"kûŲ“5Æöŗ\GĮoWĶ����� Y7ŗŒH(đVzÜĖ2WĶĪÕį—ĢūZąļŸ-Ņ靝%ÎN7 ‰I*{ScŦ÷R¸Ûē ����¸ÁuūÖi={ļ¸ė¤6`äUfĄĢ4$RĮÉŖŗŗŖ“X„����pqĶ‘P •8Uęl[ɐVëYąHhë*�����`0ožšÍÚnÛ†Dú­ŗķ&|y ����€}q͈ŗoÛ†D�����LB�����`w„�����Āî ����€ŨA�����ģƒ �����vA�����ėΰ Búm/-yė×ˇéûn.ŪôüÃīí3ÛĻĻ!Ėŧī%OŦ>ví;nŅį}ģiŨŋß_õæûŸ}š¸+ŋÖú;n8ôųģŸ}§ŋ|Ã_œōŸ7‹.sžöúîgi5ƒS���€VA¨KUVbvƒ­‹°c ‡“ö7xLūËÏ>ķ✠Ž•Š[Ō‹Zl]ÕåņÅ)Ÿ$ūú{2ÛĩĮ(ÆÎˆžCÉ\ē•ŖæŽ¨iĄŌÁ) ���ĀN ˇ ÄxO'ĶĨnËŗØē{e:QRËÜ5]̐::É|nŸ>ÎĢŊ*ŋÂļãJ4žŦĩņˇ‘‹*(Ôßí2AHčĄ Ņz; NE����vę2Ɇ õė8ļäŖÄoī YČöŗß\ž˜ŧĢÔĐĖą2ŸđŲņņĶ|ÄTžņ…ëĻūã(‘yß;Ī&ęÂûô‰0–ˆ¨|ãŠUúŲ¯ŋ:Sv^?Ĩëž\m˜Ŋ4ÂøŋíĮĒ˜X|WÂâģĩb""‹n_â7YyúZ ĪH}'ĪOˆ îúžE—œ˜q\ßĐÆ1•fâ=‹æ…ËØÎũđÚĢģÕ/ũ;AMDÄ[ŗė“|õũīž8MJDdøáĩ×÷kŸûgŧšĶį$%fäëŒÍ$’Š‚īŠ]0]+%".īãgÖĐ}˕ûÖí4jŸø`i0§ĪIÜøíaŊ…•ĒÂî™ī}Ũ^‡ķ04Āû¨Ĩ*'k˙Ҋz3/Š‚fEN č™å`Ŧ†ŖßíÚ{ĸž•‘xžŠu!""kMūŽŨy%ĩ§Û;=|B§FN÷w$ĸ–cIĢsÜæÎĸœô"kHÜōéJjŠĘÉÜT_onī8ēĢĮN›s‡pĀq˒ŪO-é Jũô͡.zún_âMĨû3÷–T6ZHāę=zbÔt­œĄŽ‰Ŗšį–ŸzäôȘĮįĢ‹˛ví=ahhípptõÕÛō|†´“&ĄÖíß{ĸž…wTŒ™7AŸ™Ģo´2nšI‘sĮx0ħ|’lņ\|°áĐįŠÔqĶ„?eįžjļ’ŖbÔäč9Z95ė˙t]ŅČû›ãÕ]Ė_ü*Ķs*MVrR‹ŽŌXswėūÕĐd(FO‹™Ŗu!j:œøQŽÛ§īč*ĮœˇūŗƒŌØĮįûwŸē˜hIŪŽŧJs;#õ9˛ņ@jv‰ą•œnš5*:"�Ą ���ėĪp›"âØ ØˇZr’2 ũėÔm]ĩ2ĩAŊāɡßyũŲh…nëĒ59 D>áū"CIE×4W^¤—HÄUEŨ U Ĩ:‹LĢ‘]ĐËōúŒmÅÁKŪūčãOߍWWm_Ŋū°…ˆ,Į7~˜X šsųË˙|˙Í4m™ļĮDDĕ$¯Ū\*~ōwūųūËņ!üž5Ÿü`hģ2P+ŗčJŒ]Ãé +ĉž¤‚#""Kų/q`¸ŠĖ‡ÖŊŗáņÔ%oŧķîÛOßŖŦÚļōà ]˛ÄUũfģäšąūÄé’Vo8N“–žņÖ+ĪÎ÷+øvĮõX‰#}ÛHŌũxÄ`%"žĩ,÷įjĮ€p?á…íøēŦ䔜VŸYą —.œĄn˙9)iŋą{JĻC—ŗŋÚZüƒ÷/šęŪt|Gz•ˆČZ––œĨs [ôГKFŠŦš)ß51 ‘ĩ2į8?!faÜXwĸÖüô”Ũ nSc=ųØņĶŨk¤Ļv]öÖ˙¸1OWœõĐsÍđ%˛–îøjÛ/ÖQ3?öĐ#1áÂō_Ĩ•Yģ†r ŽÚC?šĩŅ c§ßBÆßĨœ`n^ôäc-Ž eN¤'îíįĮ8t4Žžģė駖Īq7Ųą>9™ģâéĮëP’•ŨĪՃÍųYŋ§.\ņôS+bÕÖ™LŦ1ŒučũØū—'ūÁ‰.ú)›ŌŠ<f<ōÄS+â4ÖÂŦŦŌËLs1 CÖ˛}Įw-zėå§ãngtéÉ[Ōõę¸ĨŋüX¤WãąôÜ~ž���Ā oø!"’ŽģGeLÛzđÂ$XŽoĪŽUE/Iī/—Ëԓâ&‰ 2öé‰UņŖōRGD¤+ŦŽŸ¨å*Š DD–ōRƒ80DÕßHĒ;cĮËX"VvĪ ?KáÁ ‘80öåŋŊēxēV%“+}ÂŖīŌRU^š…ˆĖzŖYä7yŧŋR.“ĢÂbáÅĮÉÜîĸa %U""cA ig„ŠËKõDDœŽ°ŠõUŗÆų\Ø=Kf*åRšz\ÂĸqâĒŨ™ēž ͲˆEĶCÔ>r1éöæ›dãį)å2ĨæÎ„~Üu9ûĄqąãøŸļŧ÷îĒ7WŽŨōĢcdl¤öâ¤Ī;Zį6)rŠÖËCî59ȃojíÚŲÎøM›âŖyøŽ$é0ž:MD$ôžžčūÅQᾞR™‡vR¸/՗œęĘLGī9vF¨ĘC!eˆšČEKãf„zššHŨŧGOŧŨŗŊ˛Üp‰qG9#… ‘9?ˇÄę3-nZ€B*•ĢBŖ§ûō'å÷žŸZĨˇG†xy¸ų†ēfōЄĒÜ\¤R…xĖĸ…qc]û=1ŧįmSũ"'ŋ�oj'Õø žB"F>*ĀĨŖąēņâ#:\‚'ßî)$"ĄWÆ­į<\€QŽĢ12_ĩŒZ…SCÜ"Ą*`¤c{Ccķe_ąŽúŽP…ˆQjT˛JC§ú9‘“oĐ-æÚēa°Â ���āZ~—Æ‘"âŪ;÷ŧŊ-ņXčŌ1âs› Ĩz^6AŖčyĖǃŊŲŊ:3ŠüƒTÜžb…¨Œ%¤^p§ļj_nšeŽ’ÕV°š;Õũ #õņî]˛.W*Ä|šŪDTbąåHōˇ‰:cŗŲÂqĮņŧšãˆHŽ UqÛ×ŊĮΚ:6D¨’+Ô*ēÄvup mũEOã´æRŲ{ōø ĘØĄ3‘Z^UPΊĸũXŽBg å˙ų÷>IÖ'PE UfRK‰ˆ”Ēî}fƒą™Uø){ZŠũ•TtMNwæĸ”m‡ųŅ‘‹Â”Nd>ųSvVršđšA}/¯jĒĢo¸{öNŗÉBįÎ%"ĸ"xŠ<zËtĪwMk…Ö’ŦŊģj,-í<ĪwđŪ|GOK7ß[zߎŒĐĄ5ovęŠÆĻŽ–íD’ŽK{ūÄ _˛–ä“•Ŋ% oņUtdŸŦå'H""™ˇgw´cŧF+éŲ_}×xûč�ĩJé"U*¨.nn= rņôËЃīgæF “õžŋNLīyčKęáŌũŧB98ēÉéÜCŪz Ÿ¤Šž"tp ĄDÖS(#`¨ĩÃök§����Ũ0 BÄĒ˙;îČęo˙7+8îÜÖļ6Ž2ß~,ŗO[O‹…H¨•oĶ•›IZĒ3{Oö—Šũ%É%Ü$IA9§Žîwš‰%įĨ,–a‰į8"CÖĘ÷ļ™ĮÄ-y$H)eˆË_÷bb÷܋ęî_–Ĩeė?°õŖä6Fę?îžûc#Tâļ‹5AjKVÔÆ"Ŋ<H+÷ãTÆŧrË,ĒЙ4RâÚ,<‰Ųķ^&VÄ2dæz>ģ˛âžÛ,‘„=W1{Ūŋ¯ž,;[į8nidœˆČM>Wh\ŗ%+×4]y~;kk;1Ė�o/‡ūßw y‰[öˇŒší+wd¨Ŗ,õŗŦsŸŅįēã ;ļ$en9m¤›€ĄÖüm›÷^~ÜŪÃÛųĒŨûå›{ûlŲÚŪũÁz§¸\Bb ōrŽíM=œŪ!5kŧīĨÕtõŅw%U85ëÛĒOˇW–aÎ;DHäpîápũũ���¸jÃ÷ƒ8|Á<í+_'fOŒíŨ&ą$›üø“sÎ˙Ŗ=ËJåDäĸī)Š0K‹ôō@ĩ˜”Áū´ŠÔ`’čĖ>4ũGKķy7§ãÚ,İ,ŽÔąaËߊí Of:˙ 4ąj\ėŖãb‰3éŽgĻ$nü“ŋģ$„`ģ40DžTPn֕Wˆ5‘r’Ēũ%Û ĢÍT¤—%ȉ8‘˜!“ĨHzތ ĸQīķd‰ã8 Q÷“ąXÎũûšimhnwēšœÛ"Q8QžŲĖSŸģB ġķüīx‹™NÕ0ąsB}ģiøS~mQQŖ8ôūģCŊē̞Z‰$W<.#8įØØ˜Įķļ:ûm-?7p<ņ­ÆōĸŨYĶ—Įáū����ÃÚ°\#ÔM:1aļBŸš|ĀŌ3Ŗ T3f“E¤T*ē¤,+–u…up •8V-öT‘Ī­*siÁĄ"ƒ<P+īSyĩŠįßŊ‘c*9qÍmĊ¤=cšÔu…!ŗūxžž+;ąrõ¸ø˜‰ōfŖŪ4āv"…V#Õ,(įÔ"RjüŠüxnaĩX¤""ÖG̤žuDDD\yЁd*Ÿ‹ŋeFĸTˆČXŅģōŨ\^zVÁ;J:ĖMįļ4Í$ttŧ w¸x¸ ÚëNö~áSC~ŌĻ”ÜÚKu͡ļ#pęé¨é×ĸj`ĘŖįÉŅĨgֆ¯-)i$긂qģzcÜŊ=šĖí.27y÷˜a.ZéDd5–—Uw­Ąaãgqīh04]ÜĐφéŽDDÄ7L—l����Ã9)gÆM—8ÜĐ='#›5IĒûöŋÉĮĒLfŗAwpã{mm÷=XMÚœŸYhQû‰ũĩōĒĖ]bMhŋ7J "ąi_bzŠÁl6•d%îĒ–Ž™"&šÆOÜüKfN•ŲÜ ËŲ¸ĻDĻ•ŠŧÚĖ‘ųđļ5ŽK;Ve2™M†Ō=ų&‰ŸZ>āv"RņŖ’Ũ >Z–ˆX˙@•éHZ!§ ö#""Ų„ŲĄlŅöuŲĨŗŲ¤;¸qëaN9̟%MŦvR˜´á`âÖãzSƒž0kãŪÚëpi0V#­;œšUf4ˇļ˜ E;˛ķ­ŽĄa>ļS…ßî֜›žĢ¨ĻÎXSœĩã@™ÕÕëÂ;ķõáâŖ´VæÔĩ´˜Ģ ~HŠ’ú:RĶŠē–‹ŗ‡RáPŸ¤ĖÔŌjŌįĨd6{ú8t˜ ÕV~āq‘I_il0[I:!€9‘r¸ĘÔŌÚÔPuôģ-ŸnÚQÖĪ—!ĩŸĖIKÜļ§¨ĻąŠÅlĒ)Ę)hxúœmÆÉÃ]Úaøų„™ˆxsŲŽŸp#8���€Ëž—Æģ 4÷“üž VģhÅrqRōĻU™Ím$ōT‰{vÁÄîŲq`ˆĒš <0Äŋk6GĻõ'īå&ŒšđC|/ų¤yš˙ˇō•r'VĮ._&&ĸ1ąKĻ~–¸éŸO‘D™°øNĘ0ŽĖXķģü? –/åžŪžéŨäfž‰Rēäš8-K=Āv"Ö?Tm9R Ÿ¨íĒRė§U6n ŅtO9IĮ/y‘KJĖXķÚæ6ÉÔcâž]pgŋÄYMėŗ÷sëR×Ŋ–ÍĘUa÷Üû'zīk"ލßPŖšņ—yŽ™9ģž:fi'ÔĶwzÜ´ žˇķ˜ž0š2ėØōs+ dĒ[câĻx_ōí& œö]æŽÍØĶ|TÔmôScbnęįL˞[ú6u ŠŠ4¤ėM[sœoŅLŸŠiÔĻūj -~pĘ�ãúŪ>VĄËM__ĄžģdnP`ä_æėĪ<öųnK‡ƒXĻŌĖ]8% Ÿ!é„yŅ|æŦäŸÍ펎Ū~Ķâ§÷×Đļŧ&FO8žãËĨ1NžęŠwMlŨ˛ËÖ5��� i#:;;‰¨˛˛Ō××÷÷r•‡+:áĨp˙Ç_ĨWŦŌÍčųÖ]ą~čŊ����pÃĒ1֏ õ‡ŋúü2ŧ/�����ø„�����Āî ķ5B×Q`ÂĒĪl]�����\˜�����ģƒ �����vA�����ė‚�����Ø!�����°;B�����`w†D1bÄoļŽ�����Cįo7ŗ6ū"Ÿ!„D›[Z,ļŽ�����ƒõėY‘P`Û†DéĨhą´iĩtüö›­k����€ëĨķˇÎöŗ\Ss‹ˇŌÃxBĒ‹PpŗV­:YcŦo8Ũҁ,d5Æz[—�����7¸›YF$ŒiķKã†D""ĄāfŋĘÖU�����€]—Æ����� &!�����°;B�����`w„�����Āî ����€ŨA�����ģƒ �����vA�����ė‚�����Ø!�����°;B�����`w„�����Āî ����€ŨA�����ģƒ �����v‡ąuŨŦígOÖÛĪrŋŲē�����¸.nf‘P)ĶÖį�� �IDATā­ô¸™ĩqAČÚ~ļD§wvv”8;9܄I*¸ŒcŊ—ÂŨÖU����ĀīÖų[§õėŲⲓڀ‘ļÍBC"uœŦ1:;;:‰EHA�����7°7 R‰SĩĄÎļ• ‰āaĩž‹„ļŽ�����ƒđæ›ÛŦíļ­aHĄß:;o1ÂÖU�����Ā`q͈ŗoÛ†D�����LB�����`w„�����Āî ����€ŨA�����ģƒ �����vA�����ėcë~7ÎTē'㇅††6IäĒ Éŗ˙4+XÆ^EŸæÂ¤Õë÷é,˛9/ŋĸN}fuÛ}Ÿ>7Q|ÍJ†+plÍߴů\!ŊļũZĮd)Šnl'Dá>+2ÜûŠŋŧ×ZōŪ‡˜§į ŋ_”ĢW•öqrÍØšÃíZŽ%}åû×9ÚAĢ ���āZfŸī8ũīŧˇ]ĪNžwRÂZŒy{wo˙đ­ŧ˜/FųüŅ,dÎËØ­—ŪõėĶwĒ”,7ķž%œßÕÄ*:Z ŌŋĘ2Ģ#cņ“0æĒŊé;žJĻG…Ëm]ØåđÅ)kķF??úŠCÛĩį:=Rív™?Uãĸ#ÅāT���pí Ģ Ä•'ž]/ōėË÷iģ?ž…ŒŸ8aĶģī¤lÜ3æ•YĘ?Öo›ÅBb˙°•‚ˆH3qōĩ*lŦĩ¤ ’WEÎ Q ‰ČI;grIQj‘Î.ŋÆķN×\ãÉZžFÛļGīŅA—mÄČüBeƒP ���Ā56œ‚W¸û€Q2áé8mŸ?R‹ĩ –ŋ1C¤ęJA\CŪˇ_o?Tjh&VĸĐNš??HNDTžqÅ*Ãėŗļ'Ē2YHĒž˜đH\ˆøđę'˙›ĮU­z`¯lÖK˙Đfô\gøáĨ׆?zˇéÛ¯ķÄķŪ~ųNũĮĪŦĄû^ .ژZd°Tšdņ8KÆÆäCå&‹X5>véâqM5 0n×qKyæĻäĖÂ*SI}Âæ,ˆĨ‘’á‡×^Ũ­~éß j""îØšeŸäĢī÷ÅiR""ïŊž_ûÜ?ãÕį ĸßöŌŋއ<ú'n×ö\™cĨęIąK„uĸۗøMVžžÖÂ3RŸĀÉķbƒĨDDœ1wkâöcå†fž•xĒĮü)aÁ8%Û˙vņĄUŨ*YējI8KDTŧéųwöŠbß|}Ž’ˆČœŗę¯[eËW%„Đ@įŋtãŠÕ†ŲKà “K<V-Ÿ,nČÛ´1ņPš™+ĮDÆ_ģ7Ę9 ã@äpŪęķđ<֚üģ—Ô6ķŒĢ÷č‰QĶĩōž_žĄ8-ķ@ūŠfrt×LŠœ;ƃ!"âMŲé?•U7Z:˛[4S#§É"j9–´:Įmî,ĘI/˛†Ä-ŸŽ´ÖäīØWR{ēŊÃÁŅÃ'tjätĮîŪ[Ēr˛ö­¨7ķ™*hVä”�iYŌûŠ%DПžšãÖEOßíK­e?íÚ}¤ÚÔĘ3wÍ3"Įx‰ˆĒŌ>Ni˜­ŠČĘŌģÍy"&´Ŋ,kĮÁüSõ­í‰ģ授Č1Ę '•}žĄH3…˙);˙T+/t š>'Ō­*-ķ°Žļ•¤>“ĸĸ&y û\×uHÜ4áOŲš§š­ä¨59zŽVŪįŌ8CÚĮI bBëöī=QßÂ;*ÆL‹› ČĪĖÎÕ7ZˇŪS×t8ņŖˇ…OßĐU9oũgĨąĪ÷į‹S>IĄņĢLĪŠ4YÉI5.:JcÍŨąûWC“U =-fŽÖåÚŧ7���ĀŪ §›%č +,ĸĀpÍE—­ą2•˛+Y 6­ZÃ‡,~á핯ŋ¸(МũŅĘ­å\W+–×g$įųĮŋąęƒ/ŪMКv¯ųļˆcĮ-]õü,I˙īÉ˙Ŧū[ŦēoĮ|[^ęaiôŠW+%–e‰+ų!Ír׋Ģ>øôī’—l_ķ¯uš>ņoŦúø?Όĩü˜˜|Œģ¸ėūĮ%"2ĻŊˇ*šJ6į‰WŪį…MÃö>J3)ĩ2‹ŽÄØu¸Ž°B,‘čK*ēēļ”˙b†Ģ.ƒˆo8đíÕŊûôŗ˙ķD¨%{Ũęė""Ëņ&Hî\ūō?ßķ…M[æĮŸí1éS×Ŧ9ÆD<úĘûīüķÕÅw˛ĮūģōÛōļK5ĄJŽĸXß5^UA I%Íåf""ât…U¤šUÍ^âü3,˛öū¯@ûâËqáb2¤¯Y“c YôÂÛīž\Ŋ=ĩ´Ÿswĩ„š°�ĄūđŪōVžˆŦ†Ü#Ռ_¨æâé s~Ō–ėZ‰q‹XĜH˙jG…ĩkWG}N–N>)ú‘% įøņŋdî:Úõ¤kö'Ļ—Đ¨ČÅKzrQT(S–’r°ë5c"keÎq~Bˏąîd-KKÎŌ9†Æ-zčÉ% ŖTÖܔīē;á벒SrZ}fÅ.\ēp†ēũᤤũF> æáéjĸ‘ŗzîąžÄWg%m9Đė5=fŲcÄOvĢÉJN)0wåĐŅtü N5í/‹Ļi„æÜÔ´Ŗŧoô‡ž|lQÜԚŦīŌĘų~NLGs~NĨ÷œž˙ëŖq~í?§ĨŦßÛ|{ė’į˙ú@¤Ô°;ķgSŋ‡dũ"œēpÅĶO­ˆU[ wdX/hÂ8t4Žžģė駖Īq7Ųą>9™ģâéĮëP’•]Ôré׋a¨CčĮöđŋ<ņøķNtŅHŲ”Vä1ã‘'žZ§ąfe•ö÷t����~ŋa„8ŗŲLR™üËwĖų™‡ÔŅ ąÁ>rŠL5&.aš§!gwAĪGlÎįÎøņ –ˆ¤a“5KU•‰ˆ‹Ĩ,ËJÅâ‹únĻāyņãũUJi÷.ÖÖL1Ģ Q’YzĪ$K$քi%mCC˙Ĩ÷7.W¸#­J:ųÁ„B.÷ _0GY™QʑOˆ†5”TYˆˆŒ%¤*./ÕuEÖ?TŨĪIāå˙7/B%&"ąæOs4¤ûņ¸‰ˆÄą/˙íÕÅĶĩ*™\é}—–ĒōĘ-DœAß@>ã&kršL<}éËĪ/ŸĄpģ<P+5ëĘˆˆĖ:ŗ÷äI }a5GDTU\Ίƒŗ;˙&ņ؄¨0ĩJ!ĻĒ?VŗcæÅ÷‘KeęIņ÷\œo¯a`dütĮĸäĩoŋģęÍ˙lÉétņ”Bõ‘Ã'o‹Š ōõtSøš~›7Yz>´ķ^“#'ø{ČeĘĐÉĄ^T_]ËyŽ`aÜ4?…Lęâé7iŦ¯ ąędW<!ĻŖƒ÷;#TåĄ2$ôžžčūÅQᾞR™‡vR¸/՗œ˛¯Ī;Zį6)rŠÖËCî59ȃoj%ÆQ@ÄB!CÖ˛}ĮN{Nž3g´ŌE*õ™"Đũôŗą§xŗPuG€ˇ§›š .Ŗn đ”ēHŨ|ĮDÆ/š;õ–~g};\‚'I"aĀhĨCGģᨉžB"’ĒGšSƒÁÔOÜčp ž|짐ˆ„^AˇãŠĶ7â=o›ęīČ9ųxS;ŠÆOđ1ōQ.ÕWđš1Ęņc•B"FæĢ–QĢ0`jˆC$TŒtlohlž‚.����.o]ĮKtéYcšž—Mđ?ˇdAŠņa3Ēt& WI•ŪŊWÕąb–¸ļËÍB0*˙žëĀåŪ=IL$+QôŦK‰Åö×ī¸&}•…ņ Qõ�…Ú_b./7Q :8ļūĸ§qZsŠÎė=y|eėЙH-¯*(įTŅũŪËA¤Rõ–*V*Ĩt¨ÚD$'ąØr$ųÛDąŲlá8Žãx^ÍqDbõxņ†¤wÖ#Ƈ†hüår˙ŽyĻļŗáņž’rËL[^¤W%s×é)HmĒ(6+&h¤—=˙rŋîK9ŖÁDĘŠŠž'ÂĒ4Ūôãĩ˙cK鎤Ŋæ‘ŗæMRIŠÕp4+;1E°8>´īŒ­ĩĩÍ2eīF—Ņ3âFY‰ČÁ#ĀŖį×D ĩw# Æŧ´ŦĘZsĢĩŖƒįųvrãĪ=7ßs D(´–díŨUĶ`iiįyžƒīčđæ;ˆ¨ŠŽž]āîŲ{ÂdĄsįŅųgĸĄÚØ! ōéŊuãíįîpÜPĶB '""Ї˛'ÚšküÄ?īMIj åĢVššx´lN “ö\›Į„$QH™žZęčč÷YīTšĀ‰!žīįõrqsötË8‹‡¤gFHũqЇKw-Ą€Ũz^P@ŧ3B���pm Ŗ DrŠ”ĖFGǁ&,mb¤į­ bY‘˜xÎŌûđ÷Ž)f/œ%ē >ŠUũŽkin#ūČęĨGúl•4˜‰”š ĩ%ĢĀ@jc‘^¤•ûq*c^šeUčē"Gŋƒœ? ː…ãˆČĩōŊmæ1qK RJâō×Ŋ˜ØU§|Ō“¯ŠŗŌvÜžvĮF^¤ ‹LXtˇV:ĐvVėĮn-ÕĶ8*ŦûG*ũe*˝‰”åEi VIdŧÜųī=™oá‰eĪŊũX1ËŌ5˙ŒkČÉú…;wŒCD2ˇ9Ņ­5ëä”Íõ?˙ĪķíDBf€´9ôû[ŌRē>ŊÎ{jdĖh'ĸS؟n;ožÃAĀôՐ—¸e˨iŅŅžrG†:ĘR?ËęzĒÖÖvb˜ËüļˇķÔ|hķĒC}ļēZۉœˆˆAīĒ'ĄvÎÂEĮ˙Xxčģ#Ų‰:lzÔ4ŋūÕô˙¤.éwÂtũ§˙Y—¨ī8}z@ ��€ke8!e°ŸxWQî1˄ņ}oé˕ØY̚6Q%‰‰7[ÎÛciŗH:$ŋH,‘(tÉËķÎ_—IJ")ICäIåf]y…X)'ŠÚ_˛Ŋ°ÚLEzyPB˙÷~æ,įå0ŽãI,b‰ Į樰å‹īÔveķųiUŽš{ɘģ‰3ë &oŨžrŊôũg&JÚŽ RYöé UTΊŖ}ˆmQ ĘÍĘÂjąf犈Žüüŗ ː…ãĪoyí×ņæ†V’{¸{—K%.í æöžī|F( žŨJtÅ7ĢnÕTļûDFßáįÔ5 8Ûa:QTÃÄÎ õíŗåܧyĄŖ€øvžŋä/ĸ@D3/fŌų7gcœúÃŒÔwÜ ßq3xkceÁÁĖŊŠIŽ‹wŠ/���°OÃhąšģ&+Úōļ&æöYĘm)ŪēqcjV™ČĮ_Å4t¯c!""CI'ōQ Éo‘Ģ|¤m fR(•Ũ?r1+–v-FRh5R}áÁ‚rN­ņ!"ĨÆŸĘįV‹5AĒūûkĶ—TõüÛŦ¯2“ÜGIÄ5ˇ+ęYáDĻCuÔ5ueŅ×u­iaĨĒ1wĮOõæŒåώIC”ÆâcEŸ–HĻö—č ”sę`?–~ĪųgJ)ʍŊ̎t…U5ējŒŖTHMuį­*177u889 úļsôô”P]UuO@iųõ‡õ›öW^jöˇvƒŖ '8ĩ–V÷w9ßÚNŒĀŠ'ę4ũZTMŨS.î‚öē“Ŋ'Ŧ!?iSJnmī‘DD$ķövh5ˇ ä2ˇîGF í'ŗY+K̚ēžēĐ-`ÜôIĒĶŠ+Y—3¨†éŽDDÄ7úš7���Āu6œ‚ąūąÄjš#k^k͡ûr ‹ōem|īŸ+÷ļ…,Z:GI$3IĻKŨ˜Vh4™ôĮ’Öe7¨ĻŨĨ’_Ęß5ËĮ˜ļ!)W×`67čm[ųĘßßŲŲŊ^5ƏJv0øhũY"bũUĻ#i…œ6Øo€ūËąm‰ĮŒfsƒ.'9­„´SCĨDrŸ¸ų—Ėœ*ŗšA—ŗqM‰L+!Syĩ™k+N]ˇō“¤\Ņdn0čĻũhû)i íÔĪtģv땷ĒĨDDĘ`ŽpwŽÉ'\#&ú]įßgōxOËą¤ÄCå“ą8{ãöōßŨâå)ovm)Øõ]ĄŠĨĩŠļ,+ũp­$hŧ˙…ķ/Šąá^Ö’Ė´üĘÚēęŌCŠYŋ4Iŧ—š.•zzˆ;*~>ZĶÚbŽ+ĘJ;ęā-Ŗæš:ķÅkX\|”‚ÖĘ܂ē–suÁ)UR_Gj:U×ÂŖ ŋŨ­97}WQMąĻ8kĮ2ĢĢ—Œˆ‘I_il0[…ãCĢ÷Ļe•Ö5ĩ´šjŠŌļ|ųyj7`ãëĨ§$ĻU6˜[ĖÕĨžrđVš_Õ)ŧœ<ÜĨ†ŸO˜‰ˆ7—íøÉ`ëŠ���Ā §K㈈UMņ-Ÿ=Š?dælËÍhcEĨhüKŠčú`Nbí‚ËŲ¯“×ŋ•ÜLb™OHôōø(Ÿ!™ƒˆČgÎs+ØMÉÉūŨÔÖ]íŌ™Ũ7<`ũCՖ#ō‰Úîgæ§U6n đîjŌđčélöš×ÖÖZXOíėĨK&I‰H<&vÉÔĪ7ũķ)’¨‚#ßIƕk^c—˙įņĨÜĻmÉî3ĩņŦÄS÷ė‚01ŅŦļ‘*8vdĮøw­Ág}‚T–#ÅĘ)Ũ§˙÷œUôŌ%æÛ×˙{ITc"°Ģ×öĪŊĢĀ(ĻÅÅ;fī>ōiz; $rÕmąŅ}/~×KÃãRÚîÃI›šyFâ=:ō/Ķũ„]7K€īÔ9˙gŪąwķÚ,ĢoØ´˜9Ԓ֤ЛSŖŠîÛR8-:ėģĖ›? ą§˙ø¨¨Ûč§ÆÄÜÔĪ™˜‘>ĶFSæ[~n%LukLÜo†ˆ|oĢĐåϝ¯PĪ]27(26NŊ;39ˇĩŽŪŖĻÅOrē¸&'mtŦ%m÷Á¤ Íí‰ģúŽ˜Č1CīģcŊ&FO8žãËĨ1NžęŠwMlŨ˛ËÖ5��€ŨŅŲŲID•••žžž¸—Ģ<üXŅ /ŐûģõpbØöŌĢĩ=ßÁzÃĢ1Öã ���0ŦÕëĮúÇ_}~V—Æ�����\ B�����`w†Ų!čŸrŪÛëæŲē����€a3B�����`w„�����Āî ����€ŨA�����ģƒ �����vA�����ėΐB#FŒø­ŗĶÖU�����Ā`čü­ķfÖÆ_ä3$‚HxsK‹ÅÖU�����Ā`°ž=+ l[ÐB#Ŋ-–ļ3­–Žß~ŗu-�����pŊtūÖŲ~–kjnņVzØļOHu nÖĒU'kŒõ §;:…āōjŒõļ.����~ˇ›YF$ŒiķKã†D""ĄāfŋĘÖU�����€]—Æ����� &!�����°;B�����`w„�����Āî ����€ŨA�����ģƒ �����vA�����ė‚�����Ø!�����°;B�����`w„�����Āî ����€ŨA�����ģƒ �����v‡ąuŨNVŸZģ~SKĢĨŗŗĶÖĩ o#FŒpv?üāũ+?Z;˜Ãųúx Âp�����×ИĒ6WŽ^{ĻĨ)čęuvv6ŸiYõŅįƒ<\õ)ãāŒ����põ†DZģ~s'!];#Ftvū6ČíŨ°yđF����¸:C"™Ígl] gĈAÎ܌����†!„0tĀe����0Œ ‰ �����0˜„�����Āî ����€ŨA�����ģƒ �����v‡ąuŋ›ĢKtÔL/…ÂÕÕĨß§O7ՍŠé;O7 rm�����0\ § äæęō˞‡E"á%Ú¸ēē¸ēē¨}}?øô dĄß‰qSųúē ‰ˆxkíɲš[W����p} § 5ëŌ)¨—H$ŒŽšõå×Iũėc\B&GDŒ đruS[s}õĪGö§¨ėú6ĐŅņ¯.sÍxų“#vøå Îˇ/|!Z~洕#"ÆÉSTöÅÛÛ~åm]����Āu0œ‚Úwä•7öRxöŗUčķØĸ÷–_~Ú˙M‰Šuō Ŋø6Męū{¤îšU:Œ=ü2†!r đm+úūÛã]Ķ@ōÉ÷Μ=ûW*á‰įÛNWWžF$���€Įp BL5^˙ōë÷W´ nŅL_öæ?îņœˇˇŋEDÂŅŅąîÆī>Ū°ËĐũąū×ĸãšEą/&Ėž{ņšŖvx)˜brLėgq×#˜ecĪÛ9%~ŲâÚNWîŲ˛:Ûh“ú�����އá„úhĪyû¯˙Éĸ˙ûË͚úīžŪūĘĢÚq›ŧ.yˆ“vf¨síO›zSP—3Eß˙įƒg =)ˆgdAŗüsØ(7ÆRWļ3iÛ.Ŋ•ˆˆ\Būüį¨0oOgĩŽ.Ú˙Mʑžˆŧī}õAĪ=ö¸ÎœĻpRŗūø7_güÚ՟S@ôŊŗ'ųģ°V͝{2ö8˙yYPņĘ÷wÖ9žöįč)žžÎ ×hü9ûû”ŸŒm×ø4]VeĘû›øŋ.›é1`‹ęŒ ĢÂj+����¸Ą Û dj—O˙+f?õđ8Iŗŧ 㕒â ĸK!fä(_ötnūÅ3Ö:ƒõÜ#q1wÛšņ‹­$Ÿ|oėÜøˆ˛w2*‰<Ļ-|đĘMÜ´áT šĖ‹^cú{R%OÄqŦWÄėÛRˇŊķŊ‰wŌŪ÷DüQ'ū–TÆ“Ķ¤ûÎt-Ûēv˯mNˇÍžŊā'ÖÚÆ1ž~pŲü¤M_č[$ū÷Į<ø�ˇfÍŅ>‘#jfÄė™”›ąsOúÎ=đŧ Ŧ"u՗ûJĐáŋ§8-{Úgį›û}Ÿ~$Â횏����`{Ãö{„ŧîzöĩW'!2Č(Ą›}ĩ~—9BäėÄRKõeoƒĀ˜˛ŋ>P`0ÕŠ3öW’›Â͉ˆ¨ņȖ•núļ¨ēîtS]ų‘E§%ūŖ{×!ą§§ä›x"j)Î-?#žEáFDŽÚ júeį÷9zSc}evâÎ VÔ}€P;û÷ęÉßäW7žnĒ<úũÖ#Ö[#Æ]åŌwîÉč›yŽS ""â‰ãyâŠkžŒãy˂����ā5lg„ē™vŊņĐK9íڇž_péëâˆxŽˆˆŊl—u•=ķCmgŦšˆX""ŪJncgĪS¸9 Y†aY™ŪۚëÎ]ÕÆˇņĈX"rõņ¤ĻœSŊŨUū|’ģŖkŽÅÃĪ›=}´ÜÔ{PeŠ‘›číëDÜ´ē+ötÍ ]Į����`O†uĒI˙ë’g8úЧŸ?s›ār­š3M đr%:}é†]‰Šį¨î˙ GĮ<üHPĶž”m[õMmyF<üTØyĮô;y"bXâ›Ī]vĮˇąRW rx⍞×ŊՋDDŨ˛Ą7ü ����\Ã75zgŲK?´O~uĶĮ÷ú]6Ēĸ’sëØ�ĻŧŦolL+)9’WoāP"ÆwB¨sõÎ )ųŨs8žĖœ:Žįˆ‘œģ×#rîy`ĩōtú§›vžĶnž?3@HC����¸††íĄâõ˙Ø\yööĖž¯Ö­˙bŨæôâöËŌRœF2vöüĀ>ˇávúķƒŅS&¨.lFDÔvĻ')1‚é˛9˛ŪĐH.^=Ã1žˇėš˜ŽŽĸ’s’ ­uõĻîŸ3<ßÖ4čw����°GÃvF¨Ļ˛†ˆNũ¸îÃģ6„:GDi/ŊNČúkjōN÷…3—<éuä`N‘ą™œŧ‚ÆEŒUpG’7_úK„ŦÆõuĮĀōƒĩŒ""z ĢĢĸ šˇģ°˛~āŖN—åžš9æėđē='x—ÛgGøq=IĮZŧįH˲¨ØhkƁ“-Œ[@D˟ÃĪ|˙֏_ön× ëîâÉ IčäéęʒPæîâ,ēüQ�����ÃҰ BĶ?Ė/üũGY+S׎919bæØ‰÷Žufšļ†úĘŖ_‘‘Ų/đ1e“îw_IJ—ĻpyÛ6ëŧYÕŧųO,ä>ÜyŠŖ6n팟y˙3+čLÕĪ;3ž;ŗđ‘‘]ģø˛mž°ÎŽŽypšŗˆÚę+‹2Ö¤Ú$u_'č=eņSDDĘg—ÍƯg7n����7šDTYYéëëû‡{šĘß|ūĩ+iöæ+Ή„Âˡ#"ĸÆĶM˙ZųŅ.éša„"˛ļuG aøCĪŨOÛūößÂ!u œ×´‡Ÿōé˙~zm'6¸)÷2ˇ—čöŅŋ߸–e���� āęķËpšŌUœ ­šÂƧŒĩ×ĩ˜+ã2éŅ'į‹ ŋIÚâ ãĻ™­áũēlHĨ "ĒÉūâų#.’ū’PۙĻ6L���Ā g8ĄÔôLĩßČ+™jŗZSĶ3Ą¤ËiĘŲ¸E33úĄågiŦū9eCJūĀ÷ĻŗžĨŠŅÖ5����� šá„O7}đÉŅQŗnQxēšē Ôæ”ą65=ŗņtĶ —×ŋ–˛]_•í˛u�����pžá„ˆ¨ņtĶ—_'Ųē �����Ū†í÷�����üQB�����`w„�����Āî ����€ŨA�����ģ34‚Đ[�Wm^D����>†D’:K¨ŗĶÖUÜXų|vvJ%’A����ā* ‰ ´$a!Ũ„ …kjĪįM#]|˙ Ž����p†Dōõņzö‰Gœl]Č ÂŲÉņ¯O>6ČÃy+ƒ6"����ĀUŅŲŲID•••žžž¸—Ģ<œˆŦígÍiÆB�� �IDATOÖÛĪrŋ]M?đĮøŠËl]����ÜønbD#gG÷[ŅÕôsõų…ššá¯kûŲŪŲŲQâėäp͐˜¤˛;ÍeŽ*7[����7<†,77ęHU“¯2 ]Ĩ!‘:N֝Ä"¤ ����€Ob‹›ģg[Ã/ļ­cHĢõŦX$´u�����0(Ä\ĮŲ3ļ-aHĄß:;oÂ×Đ�����Ø ū7žÍļ ‰ �����0˜„�����Āî ����€ŨA�����ģƒ �����vA�����ė‚�����؝a„ôÛ^ZōدoĶ÷Ũ\ŧéų‡ßÛgļMMC˜yß;KžX}ĖÖe����� =Ã*uŠĘJĖn°u�����0Œ ˇ ÄxO'ĶĨnËŗØē�����ļ[đģŠgĮą%%~{gČĸ@ļŸũæ‚ôÄä]Ĩ†fŽ•ų„ĪŽŸæ#Ļō/ü[7õoD)ˆˆĖûŪy6QöاO„ąDDåWŦŌĪ~ũՙ˛ķú)]÷äjÃėĨÆ˙m?VeæÄōāģß­YtûŋÉĘĶ×ZxFę8y~Bl°”ēöd''f×7´qŒDĨ™xĪĸyáōļs?ŧöęnõK˙NPqĮÖ,û$_}˙ģ/N“~xíõũÚįū¯æô9I‰ų:c3‰dĒāģbL×J‰ˆËûø™5tßråžu;Ú'>XĖés7~{XoaĨǰ{æ{_ˇ����`x~AˆcƒbÜúŌú¤Ė¯ĖQ^¸SˇuÕĘl6bņ“ËũĨæ’˙­Û´j ûĘŗ“|ÂũEJ*,Q 1W^¤—HÄUEz S‘ĄTg‘…hdôŞŧ>c[ņĸĨo/–‘ųøÆ}ļzŊėũ'Ɖ-Į7~˜XŦ‰]ūr˜œĩč÷&Žųø3ų[/Dȉ+I^ŊšTûā“K4RÖRĩį›k>Ŋņ÷ģåũoÔĘūW\b$ĩ‚ˆt…b‰D_RÁM c‰,åŋāņ*2Z÷ΆjíŊKŪãɚKˇoظōCîŋß­$–e‰+˙!ŊsÉsJ%qē¤ÕŽKg/}cĒ‚Šļû?=‘öúŧ ‡2ķÖnÍ466õ÷įDtnŸ•û÷{[÷-‘Kī˙ĶĻGO<˙ĀĶ:ã[ũ%ēSū*÷į—Ūãė7”ģ���€Õpģ4Žˆˆ¤ããîQĶļŧđ –ãÛŗkUŅKÆûËå2õ¤ø„I₌}zbÕcü¨ŧTĮé +¤ã'jšŠb‘ĨŧÔ  Qõ7’ęÎØņ2–ˆ•†Ũ3ÃĪRx°ĀB$Œ}ųo¯.žŽUÉäJŸđčģ´T•Wn!"ŗŪhųMËäǰØĮ_xņÁqōˇû„hXCI•…ˆČXPBÚĄâōR=§+ŦbũCÕŦņ@F>vĪ’™JšTŽ—°hœ¸jwĻŽ§Bŗ,bŅôĩ\LēŊų&ŲÄøųAJšLŠš3a†w=Î>QúˇûžzëË_ĘN56ļü°÷XÔŊ˙ Žßz÷Ūŗđm;››‹+ ¯ŧŋ%q]zīŽ}íŸú׏yĨ æ–ÃąË>ĐV Ųn���ā6,ƒ‘"âŪ;ĨEۏõ]*d(Õķ2­FŅķ˜U{ŗÆ ™ÄūA*ŽŧØ@]‘C|gˆ˛Ą¸ÜBÄé +XMēŋa¤>ŪŌž˕ 1ß 7‘XlŠH[˙îk¯üíŠĪ/{!ą˜į9Ž#"š&TÅY÷ŪÆ´œ"Ŋ‰#ąB­’ąngÕÁTõ‹žˆĖĨ:ŗwČø ĩĨBg"ĸĒ‚rN5ƏåŒ:)5ūâŪ§ä¨ĸfCUOTĒē÷™ ÆfVá×;I&öŧpÂė˙gīūãšŦ÷˙ņ?­ë˛í 6a6e6eS@ÁD刊°Đ¤ÄJhoŽĨ•žä¤öCÍ<éé‡ũĐĘ~P'ËÃąHåĢx0Å$Œš( nā@7u€Ī5ėēŌīüÄß÷¸ßŧÕöēŽëõzîÚę×uí&y˙‹Ė–Okūį\úCÃã­š'Ē.ʧŸ­úžųņŸl8wŽåFZōŅ:lˇ����pë|Kã°úGâÃķ—¯ûnTđ„ ­N§@U[ßzvëEûúō<‘:ШĖ0™$?drøEęz,Ŋ´L&+4 úØV/7"NÆ]xÂ2,‰‚@dŨļôŨ G؄ŠOŠå VĖMkœ{Ņ><wžbSÖÎÜÕĨ;š.üąÉņQZŽ­vΤįˇZIo+ļ(ƒŒĘ�AkÛgæGQ™ÉĄb“āäEâØo+erbĶSŽŠD'/ÉØ ŗ-ßDgĪáŋ¤­Ä\Ųôāø%›„ķįĪŦģËĮ“ˆŽ”Y/ŲZZVŲaģ���€;Xg BDÜĀ„qÆߤ툈on“JYRDūuæU‹YVŽ$"MˆË)-sȋ-Ę@=Gę`­:dĩËLÍC둁¯m1ã$8ybX–Ŧy&64ųÉƆđ䠖+Đ8mxü3áņ$ØMûˇŽOKũ@Pž=5„mŖ]ĸ\[hv˜Ėeœá!%Éõ:Ų†ĸJ[”AIJ"AĘ1dįDō e\š_'K‚ đD/†į/<ž‰îš‹“tåë/JŊĩgŧˇŋę’Ũīē‹šs…ž—ēŌVŨrĢž—o‡í����î`tiÉ#’bT–Ėô\ži:G¨gv^ĒVĢ˙ČY–S4„}p ™‹s *׌iúi‡ )ļ*ĘÖG°›+íM­›Ā¨´JjÄJåMcÚÉ35„!‡e˙>KCvb•úđĸe­ÍboŗHe4Č-Ey…fAoБڠ#ķūŨE•œ!HKDŦƨĻĻ눈ˆķ!+)´šæ%{Ídj•”l&GæC—N”Ü$ķžkų´›Œ›üÔč†ĮŖĮPw÷jšuæ˙=ŌüxáĖ8ļK—–[=!Æv´n���āv÷k¯ŊFD§OŸîÖ­Ûu÷rƒ‡ÛNUË<îm?GÉļ§´#G6ũ•Ŋ§ŋĒfįw?qžS†Ä ë%a}ä5ģ6m/9ëãįˑãXÁúĪ–]䥑Ũ-?[’šc_ÍŲ€č˙7D}7ą’š=˙Ųz°V?Āë˛ÁĒ|Ÿw’¯ąūĄŌú0gūüuúŽŗÁã#TœP–“{øŒ"0P.ۛž˛č5ü$ÛwH°#'eizÉŨ>=ŧYâ–Ũ™ßįņąö?×FģōnōŧģlËųĮjzFNˆ ĐŨÎß2ļįׯč áęģ‰8%W‘ŗå‹LŖõbÎ+Xķõ–c=úH/ŽčäŪÍyИáŊ$DDw{s'~ú!ßÄĢTŦܡfkŅ™šŗŪƒGšŠK…î>[)•KÛߏˆˆt}5ÃúꎟŦîĘŪũŸ‚?ũøyēûB™81Š?íøß˙ęõߗ˙:ūá¸a͛XųŊIc";%ŠBX~ÅÛO+úøuØn���āÖŠw8Ĩ^}ŽûđĪ/wiąņ vr i„5>1;™[›žjŲÖZ'I}õaæ$D4Ξp!ÚÚBs`ˆŽa6GaÔqé? CÂ4mu¯6nHíwK˜í§ŽO~"”#ĸ°øŠū,mÕ#™6øĄ¤'GP–miVĘB6ųÄäéÂ7VŊ^+#SL}q‚‘%ŠmŖˆÕ Đķų…ĘcC•\€Q][híbhœr’ž:WX›–•˛đk'Iú° sF´:ƒÅâįLVdŽX¸ƒUjC›øŊû ‘@ÔęP7¤īPÃōĄ†ļļ&ĪJHncĶ]>ž¯ŋ5­ŗt ����wĒ.įΟ'ĸōōr˙ëîå/(>ÜSÕũēŋ5ĨÎ^fz°é7Xītlmž—ÖÛÕU����€ģ¨ąT{Ä\÷á7ž_:ķ5B������×A�����ÜN'ŋFč LZö™Ģk�����€[3B�����āv„�����Āí ����€ÛA�����ˇƒ �����nA�����ÜN‡B]ēt9wūŧĢĢ�����€ÛƒĄ.×VĐ!‚TŌõĖŪÕU�����ĀmÁŗŦTæÚ:DęÕSu†wÖũ˙ãÜ9W×�����ˇCüŊÕ§NHũ\]G š§ĢQ¯=zĖvĒĒæ?…\ŸŖKĩĢĢ����€;] +•Éĩ‘w3R×Ō!‚IîéjĐi]]…;ëãę�����nŸą4�����āvB�����ˇƒ �����nA�����Ü‚�����¸!�����p;B�����āv„�����Āí ����€ÛA�����ˇƒ �����nA�����Ü‚�����¸!�����p;B�����āvWШūėīGŲÎū.üņĮ9Wם?wÄÕ%����Āõ¸‹‘va<īíŪīnFęÚJ:DĒ?û{ŠÉâéy¯ĖĶãîģ0IíŠ=âĨõvu����pâģV[råÚH×fĄ‘:ŽŗyzŪëÁI‘‚�����îh"qŧww_gÕoŽ­ŖCúúß9ŠÄÕU�����ĀmÁ ü^įÚ:D:wūü]]ē¸ē �����¸=ÄsĸĶĩtˆ �����p;!����€ÛA�����ˇƒ �����nA�����Ü‚�����¸!�����p;ŒĢ ¸f‚ũPN֖Üĸ2k•“¤2Ĩ6(2æ‘QÁ öút­]ūÕO&^1fū}æŦåÎIŸžÁŨ´’á*¤Lûę¸tv”ÜՕ����€čdAH°lYōî 3á1ĩŒåmû~Üžáƒ×÷Å͞;ZsŊYČą/kģE~˙œįGhÕŦ=iĒp#ą �����:¸N„sú?7XäÃį˟dlœ¯ 1dÕÛKÖ§æ„-Ĩžž~<Oœ.4DĢ""2DDŪŦ‚���� CęLAH(ڞk“ y~‚ņĸUkœ1!yņƒRmC Ēö­ûfÃ/‡ŦĩÄĘTÆaãĮ)‰ˆĖŠŗ—YcfĒڐūK…'š>"éé !ܞå3ŋÜ'U,ûˏŠQķ^3f5-ŗn™ˇ(oā3Û×}ŗ÷Öü–gĨФšÁÅŠ™ÅVžä†‡Ļ>ÎgĨĻ˙bļķœvpüô'՗VŨƸ ĀxķÖUé[‹*ėN’kBĮ$ď2ČÉēeáĢÛõķŪIŌ )3>9 Ÿüöܑr""떅‹v_|#QßbKÆŧ7÷‡<ķˆđÆŨ&‡ĀĘõÃâ§'„6bú)mÍļ}–ŧČČ5‘ã“âƒåDD‚m÷ę´ fk­ČĘ|õa$%„ĢŲÖÛš_–Ŋ°Z6}Ųԁ,QÉĒ—–ü(˙Įĸ1j""ĮĪË^X­H^–Bm˙CŠŗ—[cĻ,JK/õMZ–ÉUí[•šö‹Ų!p갇ƒoŪ���� =)YŠĘxiā@ÃeËÖXEc "žpÕ˛åŠ1ĪŧœŦáxķļÔ¯>Z*ŧ´8AĮąŦhÉJߗ´8AÅ:ö¯xķŗ”uA>>}™"ũÍwvëf.Nā8*lŲąčܗš'$vöĢ…œX–%ĄhË&]ŌÜeSYëļĨ‹ŌSŪ<`ŒMZœ J×ūãŨ´ô°Đéa—–×ƸA,Ų6Ŋģlƒšø\RˆB°üļâũ„ŋ/Ŗ4*ž+)ĩ‘^EDĻĸ2N&ŗ”– #CY"Ūü›• LÔ^2‘X•ģ.?ūéW’´_ēvÉû+–ĢŊ:RAüūÔŌJ ņÉķC•,oų1-åãĪ”¯ŋĨ$KfJJ"ņ™,o۟ū՗KYÅ{ ēÖÛ ļ—Xh žˆ* KI.Ģ-4;ƨåD‚Ѝ‚ Ãõ,_øU[įŸaYŅūãw…ŠŸ;^ĨæČē9%ågŠ|ōå1ÎQô]zæ!47ķãŌ¡_~ŸąõŋĸHcøÔŒG/ÚV/ŧķîę{K•˛{'˙ŋ‘ÄF´ÜøËÖ}Ÿ¯ŪjĢŽŌįī/&Lڑģ���€Ģ׉î'8’+”W¸|Įq`ë/Uúؤø`RŽĐ†MHékũy{ĄĐԅfDâ`KDōĐHƒŒ¯¨°ą'g‰XVÎq—õ]KÁãë´jyã&V7*ZĮąę 59¸ SąDœ!Ô(sZ­U­—ŪÚ¸BŅ÷›*ä‘S’ĸ *ĨR30!iŒērkÖ!4!ÖZZÁŲ KÉøā�Î|ČBÔ9XŨ�}+'ATūi\”–#"ÎđČ™ūģßND\`üüW^}ōŖVĄTkÆŪo¤Š}fžH°ZĒHiP)• mđĶįŋ”ü ĒÍve Qî0™Ģˆˆe&‡_ä0•Ĩ¨R "Ē(1 úā@ŽŊķoį%ÕkUUäūˇ’ —8XŖ”+ôÃģ<ßŪ$s_ü|yęĻ kõT͊Õ۞›ū~Ë­=ž8#{Ŋēļ¤ÜēāŊoĶVlnŪ´yŨO{ũ_ŋ9^]}fˏŖ'žFœë°Ũ���Ā5éDAˆ%–H¸â.6ŗETčuŠæĩAÃ:+LöƧrĩ_ķĒ:–cIp^š?"FĢS]Ô ôkJbRŽ#VĨjē.IĘqmö×ę¸vKĪ„h›€J¯“9*ĖvbõÁTņ›…ˆ‡LŋÁAzžĖd'ĸŠBŗ  kõ^R­ļšTN­–“ŊŌNDÄq|ŲϝŪ^¸ā•ŋÍ~iÆËi%ĸ(ĢŦãŠ×.ų<#§Ālį‰Sę´JŽívÕ@g)5ķD‚šØĸŠ öãĖÅ"˛—•8T!yģį_Š h\:(ØŦvRëTM/„ÕüŽüN\ž˛ęĮŨÅ-[ö—YĢhxŧ5#÷D•ŖåÖĪV}ßüøũ/2[nĒųŸsuęļ[����¸&iiœR.'‡Í*ļ­ÉŪÉ#oqËJ9žųéĩŽÉą—Î]ŌĮEĪڊU­ŽË×:IĖ_>=˙ĸVY•ƒHmŌķÛ ­¤ˇ[”AFe€ ĩí3ķŖ¨ĖäP 1´z‡iöĸQX†xA "ëļĨīf8Â&L}:H-gH8°bnZCĘa3_åļmú!oÃįß§ŠRučCIO<l”ˇÕÎjƒØÕ‡,NEeœî!ĩNĄåŋ7ŲIm.ļʍj"[{įŋųd "/Ë^øøąË’ØÆųģ~GĘŦ—74U¨ûiˆ¨Ä|ü’MÂųķįNÖŨåãIgĪáŋdk‰š˛ƒv ����ר3!up�÷Cņî~Čā‹ãG0įfŸĐŽŒĐrRŽDßb īäI*ī? Äɤ$0uū¸–w=`YМˆä!Ęĩ…f‡É\ÆR’\¯“m(ĒtPąE”tŲ ˆˆHā[ä0A‰“˛Dւ<šüäcCq´LkŦ:ėáŠa“ā°åĨ¯Ū°ô+ų{ŗ"ämĩ‚´üO&k™}Ŧ†ØÚĩ­ĐėPUr†´DtõįŸeX†xAlšg{ŗs×Ŗw/ßÖ§ņzûĢ.Ųt×]Ԙ+“tåë/ -Ŋ›æÜ:\ˇ����p:ŅŌ8b ÷GǜûV§íļˇlæKV§Ļfn+titZĻĒņ:""˛–VRļõäābJ­FîŦrJ­nüŖäXNŪp1’Ęh[Šō Í‚Ū !"ĩAGæũģ‹*9Cļõūœ–ŌŠĻĮK…ƒ”5‘Pë$VÚt…ŲÉ3QÃÔo)ÚojXiÅĘĩa'ūŲO°™ímļÉCÔļ’‚âBĢ&DĮ)ô:™Š(ŋĐ,čƒXē–ķĪĒÔr˛šmÍWo™Š*.Ûé&ā´ŨŌ˛%<$@Û? áņčņ#ÔŨŊZnų4?ž÷l\ËMŨdÜä§FwØn���āštĻ!buņOĮ[ŪMOYdÛ7rÄ@ƒ‚åm…?nĪ-BĻ$QŅ€1ÃK2S7ЇhXŪŧmŎ*môTc‡üyT6øūQšˇ7­\̜ø€QIķOi_mãc,­""mX�}ĩ=—×<Ļc‰ˆÕjí›xÁøD@ũ1|AFšnÂk/ĘØTJÆ)äDŦ!€ûᡭ?W$söĸīŌJFY™Õ\éĻ*É\ąF$MĄW˛‚ũĐĻ˙Ú8Í#jræļŪN ņlĶÛI}ŋ^ND¤Ö_mßÍkâ wõį_9ØwköÚ´_’Fé8GŅwĖמnņęŧūÖ´!ßū¸!;īÜšģŅ?áɇ[n]ŋnŅō÷WīĘ?ė-ķx"ūū!÷hŪô`ܟzöP¤|ŗåT•ãž`Ũ /'ŌŨˇ[����¸&*ąÚ枎ÉÉܲõįŒŨYNV*Së$Î{$Ēá‹9qƄŲÉė7é_Ŋž^KœB›œ8ZĶ!siÆŧ8›]•žūÁßíÎÆj§G7.buô|~Ą2ÂØøĘŒęÚBkŋ6īŽ&û�ģ#eáį'xÖ×3}ę09qaņS˙üYÚĒ7ūF2mđCIOŽ ,ÛŌŦ”…lō‡.ŦĘH˙ā'ģSdežúā sB9ĸQm´‘68~ČcÃt ĢĩXM–Ī/Qo<ũ×rūĩąĶ§:R7|õNÉ´a%%°Ë?oũž{7nėãûøŸÛښ<+!šM}‡–5t–n���āęu9ū<•——ûûû_w/7xxAņážĒî×}85cŪĢyÆĻß`Ŋãąĩy^ZoWW����ׯÆRísŨ‡ßx~éL×�����ÜB�����āv:Ų5BĐ:õ¸ˇVŒsu�����f„�����Āí ����€ÛA�����ˇƒ �����nA�����Ü‚�����¸„ētérîüyWW�����ˇC]$Ž­ C!Фë™3ŧĢĢ�����€Û‚gYŠĖĩ%tˆ Ô̧ę īŦû˙ĮšsŽŽ�����n†ø{ĢO*úšēŽ@rOWŖ^{ô˜íTUÍ A;ü9ĒąTģē ����¸v]$ŦT&×FŪÍH][H‡BD$š§ĢA§uuĐYôqu����ĐšuˆĨq������ˇ‚�����¸!�����p;B�����āv„�����Āí ����€ÛA�����ˇƒ �����nA�����Ü‚�����¸!�����p;B�����āv„�����Āí ����€ÛA�����ˇÃ¸ē€FG+ūÕĒ3˙ãΟ?īęZ:ˇ.]ēxŪËM›2yéGŸßÎáü5=oÃp�����7E‡˜Ē´Ú–.˙ŧîĖ˙‚nÜųķįkëÎ,û蟎y¸ĘãļÛ3"����ĀëAčķ¯ž>Oˆ@7O—.įΟģÍÃ}žōëÛ7"����ĀéAČá¨su wœ.]nķpŽZŧ‰����Đitˆ „é ;�–5���@'Ō!‚�����Āí„ �����nA�����Ü‚�����¸!�����p;ŒĢ ¸6Ū^ŨbGG÷TŠŧŧēĩēCMÍéc6[ææėęšĶˇš6�����č,:Sōöę6kÆ4ŠTr…}ŧŧēyyuĶûûŋ˙éČB׈ņÖúû{IˆˆÄúG;ãęŠ�����nÎ„bGēr j&•JbGú×7k[ŲÆt ‰ŒŠÔģ§—'GÎÚS•ŋæīܜ[Ūđk }_á•5˙“|7üqPĪû9VYWS/ãá+=ōÅ[EW—����p tĻ ¤÷īuõ;÷TųļŌ*ņ{ö‰¨îg~ÛĩsMŠŨÉzô Š}˛ŋ!ķÃ/ķOŪ´J;‰NĨ`"QūÎâëö7L)#'FĮĤR‘DŅYSY^ƒH����wŽÎ„.™:ĩįĢEKžŪYVw˛˙čY‹įÅôŧ§ÅÖÖ."’ôęnûĪĮ+°6~­?Xŧwqüܤ˜ņ÷•¤ėuÃĨ`Ēȸø!>ž\Ã3Ÿ¸ƒZlž8c8 Κōœo—ī°š¤>����€[Ą3Ą‹ÔnY”üáNeÔ˙Íđ/[ģrÍ+/ûĐķŠ‡xŖxžØĩĒ95¨+ŪøáûŲuÖĻ$2Š ˜)cCûx3üÉ#Ųk3~°ÔQˇącG‡úųzJÉYSYŧsÍúüc"ųM|uŠoÎƝčGCUŪĒĩė_ķMÖÁ†ū<zĮNŒĻëÆÖÛædåxŽT˛ôŊėcDD}GŽîīëÉÕļ_wl\ŋËæŧɧŠ]åëß[%ž0#Ú§Í=*ŗV.ß…Ģ­����āŽŌiƒP]׀G§<đØôqÆ{NyæoûĮņ’ãDW BL¯>ūlÍî—Īlԟ´Ö_xæ7´ ;õ‹Õ¤Œœ˙hbԑ%YåD>#Ÿ2”v§­Zyü yõ~tBėŒ8ûßז‹D‚ĀöŒŠéŸ™ądŖ]ô0Nz.ņ/Ŗŋ˛öˆHÃ&=íudõįßtzô‰IčáÁÖ;""Æė”CÅÜĩĢž°œ‘éĸ&ĮMų‹’˛÷ĸČ1::*&:ę’rŗ˛s6gį\įyk[Yæ˛Qü‚ =_ī1ãyMö?vú?˙t”÷M����Āõ:íīõŧÎÜįĮœ=fŪŗaK9)EÚ9BęéÁŌ™ĘvoƒĀØw|“[hĩŸ´–dí,'o•¯QuūˇK?XĩޏōdÍ铿üėâAa7@�� �IDAT™ŽoķuHėņ=ëØE":S˛Û\ĮõPy‘—qˆž~ËŪøŗÅ^}Ē|GZv+m<@bŒÚŊ2;}́ĘęšĶå{7ŽÎ¯ī~I”Ûœ“uqæšE)ˆˆH$AI¤†ų2AE\����w¨N;#DDDgˇŋ<úÅ˙ūîiøŋw_zPÖÎÎĸ@DÄļÛéɲōĻų!g]Ŋ@Ũ¤,‘XOۃbƇĒŧ=%,Ã0Œ”晿ŪjO^XÕ&:Eb¤,yi|éôĪĮ›Ũ•˙zTÚ0ĮâāĮÖė5ۛ*?d"üü=蒛V7ІyĄ[˜‚�����ÜIįB÷„˙méģ÷īY›ōīäĘ´ĩ͌WÚY¨;ÍSīž^D5Wîĩ!15ÕøoI߸iOÎYŸąÚrÚ)oÔ´ŋ…ļ8ĻÕÉ)ÒX{aŲčŦ̧† $‘0äõÜâ‹×Ŋ’J‰.ģeCsøA ����¸):k:[˛åßY%=}~tŒņAeÉæ)˙ßļ=Įϝt‘xŧŦ\ë7¨7c>rql‘ôŽ$+ÍßwĒžC‰˙!<+ŗWŽ?Đ8‡ãË\ÅŠDŲ…{Ũ1RĪĻ'õõ"ÕėJ]•ŨōĻŨĸX×FHC����¸‰:kē‡J2ž\ylûņƒíÛŋˇ“gd€˛cΔė8P7cPĖøũ_Ŧ9t!ķxÛģrÍū}§Ú>–a¤DÎēĻŖ˜ŪC‚=ŠŨĶwĘZMÁ=}$Ôp3Æŋ/–.R:YV.„*$õ'O5M�IēyĶéÛ~×8�����wÔio–`œūņë˙oĀŲ˙ŽøāÃÍĮz<đüûķ"īiī˜úƒ™éŲG="§Îœ=!rHPīžAĄN˜67É(äg|}åǎ>EūC#z{yxvī›4œ5UDé×]rĨ,Tsd÷qę3PŨÍŗģ˙ČĨ�Ą)éԗääŸņ¤ōöđđ҆N|væË“B=¯åÜ\l÷nž^’xøzyą$Qtīæ)m˙(����€Î¨ŗÎŨŖ{ėÕ?öęĩT_žųyĘáȨčAy˛‚ŗęTųŪožČ:ĐîøØwŦŲ0)jÆŧáBuŲžŦŒ¯M~ŦvÜøį>ČžŌQŠž‰Ņ“gÍĻēŠ_ŗŗūS÷øĶŊ6‰G2V~Q7e¤§”œ§Ę‹ŗR2÷ˇ{Oģ[ q ßđ'˙FD¤ž3•ˆhüŦ€Ļ͸y����Üiēœ?žˆĘËËũũũ¯ģ—<|æK ¯fˇ,xQ*‘´ŋUל~séG×]ŌMÃH¤TīlŒ’OŊ8™2^ų˛¨C-ë9rÚœŅšÖī§į<üõĢvˇs{‰FŊŗøf–����ІĪ/iFČTv4¸o{ŋÔä¸íÄ--æętöĖĖņŌĸ5kwŽcŧ Ãc âÁoŽt¨DDĮv|ņR~7YkIČYwډ !����¸ãtĻ ”šyĢ> ×ÕL 9ëë37oŊ %ĩįôĪŠßJãĸcŸJ–Ižēō×õ+×hûŪtŽ#ž9]íę�����n›Î„ĒkNŋ˙ÉąŖGõPųz{ukkŸãļ™›ˇVלžÍåĩîĖ‘ū}äWW�����-uĻ DDÕ5§˙õÍZWW�����[§Ŋ}6�����ĀõB�����ˇƒ �����nA�����Ü‚�����¸Ž„ē¸ē�¸a]đ&���@įŅ!‚ÜSFįĪģēŠ;Ëm>ŸįĪËe˛Û:"����Ā čAhjŌãt&nĒÛ|>īęōĖ““oëˆ�����7 C!MĪ9Ī=ãéááęBîž÷ž0ķŲÛ<œŸZuÛF����¸A]Ο?ODåååūūū×ŨË NDõg?zĖvöwá?ÎŨH?�����ĐaueŠä?ĩOW–š‘~n<ŋÜĐđ7KũŲßKMOĪ{ežwßÕ!&ŠÜÍ1ÛŠ° >ŽŽ����îpŋ bUŖäČQcī^7˜…nP‡HGŲ<=īõā¤HA�����w°Ž,ŖöQtWtĢ´žtm%"xÔ×˙ÎI%ŽŽ�����n…—ÜYÖĩ5tˆ tîüųģđ34�����îĄ+Ëü.ˆŽ­ĄC!�����€Û A�����Ü‚�����¸!�����p;B�����āv„�����Āí ����€ÛéTAȒ1oęŗY”aš¸šdÕKĶŪũÉᚚ:0ĮOKĻ>ˇŧāæw\kĘ[ųņ˛—æ/œ9˙|–šë¸ķ>šsÉKī|mēųUuÂ¯Š‹g~VĀ_y¯“[˙ņŌ;ĢŪž’����ÜT§ B *ļĨí¨runėäÎOŋĖ>Ņ#:ų…ßx>>ĘķČ7ŸĨī­suUí~M}ķĶũגŲn>ļgäØIQ~ė•÷ō ytBĖīÛS���€›ęlAˆņŽ0efėkį/ÕáV9Q\xŒ1Ǝ ņķö”ųDÆ÷¯/ß]ęڀq5ė‡ģø×‹‰HĄjčŪN’Ēû đŧ=���¸)ÆÕ\3}ĖļôŖ´u#Bžlí ĨŖpsZú‡ŦĩĢĐ ŒILŠáȜúō;Ļ?ŋļx´ŠˆČņĶ’9iĻĐg?}.”%"2§Î^f‰Yôj´ĸE?‡VĖ\n™eûnCA…Cā”Á÷'=ų°‘#"âM?Ĩ­ŲļĪr‚š&0r|R|°œļėHOËÚoŠr ŒLkˆxė‰q•m´ [žē]?ī$=‘P2ã“úÉoĪ)'"˛nY¸h§ņÅ7õ‚åįĩiYLļZ’*´Á÷Į'<`”‘°īãY)4)Yũ͊l›ņš÷§ –ŸĶR×íąđŦ\úØxŋ[ö&0—>iõsTW–™[jĢ%>ēĐņqŖú5Ír0ΊÜo6n.ļÕ1ŨüCŖ˙ŌpęųŖ{ÖoĖûõ¸Ũ)2žj˙!ŖĮ=jđ$ĸÚŧ¯ūž­œGŲk÷;MYĢĄē˛ėõŲš&[uŊ(õPõ31*€ks܃˙œ˙mĄH”öÖĖõĄÉ‹ãúpĸ({]VŅáSgHĸôJâË‘đkę[+iė´î{žÎĩ÷Iš?EwjīÆ›‹*OžĨpķž-UŦ^ŧōDÔCŽggÛjEŋĄ1Ķĸ$ģ×gå˜ėNFŲ?zÜä5K¯Šo}áûöŗaÜɝK>(čûT —“•c9í$ŋ čÉC|‰čäÖŧˇŋĪ__JčÕXLrāá5ŲGNԓL7|ōÄ`gNÆöWV×KüÄüebˆ‚¨jį?_ËVN_ׯĄœęŧĨKrŧŸš?ÅĐxęĻLōĘ]Ÿw¸Ļžõę;i\Ÿ“Ų_gU֑L:qŌč~]���ā~:ی‘ĀÅ'ôã^ģÕÚĘFĶęeK3Ģô 3ßZ˛hNŦĘ´zYĘĪUDš:Šĩ´ŦaI0[d2Žĸ¸ņZ#ë!¯0—ôŞĸ%+Ŗ$xę[}üéۉúŠ ËŋÚÃŋ?õƒ´B؈äųoŧ÷—“ έ–c'"JĶ—}H;sņ’7Ū›Ÿ"ū”ōÉk[íę@Ŗ‚7•Ú†3•q2™Ĩ´L ""Ūü›• ¨%Į/+–ŦüûķÔÅKŪ~ëųĮÔK?Øbm(%ĄbË&û Š/Ύב`Zģ|å~6}ņë æŒ(\÷ũ%SŨžÂ{SIvnODBŨo9{Ę=Œ‘éeo…õ?+Ve×ųjڂgÆöĢĪ˙âË­• ¯ÄƒŲŲ冘ĪOOŽQUíĘX“ī$"r\ķåÆß<ç=7ëĩĻ%čëw¤~›[MDİDõG˛w‰#“ĻM‹TÕíZŗ*ķ¤2æŠé¯Í9=VU™ũí× ËŪZˇī”Æö%ę=nÖÛķÆö!â‹2>ü÷~gĐØ9sgÍMА–f|¸ö ODÄ2 ‰ĮwfׄL~fJŦ–*ŗŋũW9iúksg͙ÎĨšUqųiaYņXnÎąĐĮ,^¸h‚ęDnÆŌyLô”7Ī›;œųucV+ĢÅĶģ3 ¤Ŗ§ŊšxáOųŊëō/™Xc†DĶÎėúˆä…ķßy>ĘۜŊōãôŊ=ÆÎ]¸đ§‚ųŊ3‹„+ŋ_ ËP}Éæ]’Øį^zŅ”aLɚ_Ŧ6÷6oūûķÆųŸÜĩ&§•—���pĮë|Aˆˆäƒ'<ĻĩmZwé øũvœĐÆNMŦS*úa‰IøÂŦŸ,ÄęÃČ|Č$™ŠĘäƒ#ŒBY‰•ˆˆ7˛r!ÚÖFŌŽˆŦ`‰XyčcđEy…<?˙•WŸ|U(՚ą÷ŠbŸ™'"‡ÅæDÖŠ• Ĩ64ū¯/ĪŽlŗ]b`­Ĩ<‘­°”Œāˇ,DD‚Ѝ‚Õ ĐŗļÜŦBčcSŖÕJšRžôD8Wą}kķÍЍ'Ņk”™~<`WD$ŽR+jȤÚųŽ|}ŧç=5\Ėųâå—Î|åŨ”ũ㧎ëy2įũlUFĮęßKíÛĢīø Ņ÷õĢÃ@=cˆ™<(ĀĪGŨ'bø}^bĨÅND$õ}núœ }zx)|ÔũŖ#úíWKC6`DQô>v¨^íįÍIúĮM_đÔØĄŊē+ŧģ„FEö¨?RZy…qYO 1ÄJ%œ”%ĒŲŊŖČŠ‹™6睟ˇ—¯>|ōØŪbņÎŨÕMÕ×u‹Œ‹č×K­ 'ŽŸĻ!CôŨŪ^~†ˆŋ<7mZ¤˛Õ#ô1x˛D2C_Ē'ũđ¨R"Ö7¨¯ˇh/?yųĸbPtd)qŊÂúwo:—`üĸ"5ëĶģ_wĒ“ô=¨;KÄéûöņ¨?qętģī˜(JúF…ûI‰XM}7Ēī6$ĻŒˆ<{ß׋Š>nĢmˇ ���€;Nį[GDDǍ‰#rŪĘH+0=ŒģĐl=dC ĒĻįŦ>؏ũąĖä ­.H+üTbĨ­­°”ô #Œ?í6ķcÔŦЍŒ5ŒĐˇ6Œ\ã'ozŦTĢ8ŅląĶ-ĮņųéëŌLļZ/‚ ˆĸ^ˆHi 6Ŧx—õįA!†@­RĨ×ŌÚõÁ´ú7 …‡LŋČÁA”õŊÉNzeEĄYĐÆ°B™ÉJę?éš_$Ģ ÔRžĩÂAz9‘:PÛ¸ÍaĩÕ˛Ē�uĶžœ.PMÅ7åt_¤ēā_Š;…ĐqÉCũdtúpNVæŠtéŦÄû.^^U}Üæ”¨zú4=÷ Ÿ<‰ˆˆN‘$@ß\χˇ„DĄáęŠÔY”™ĩąüä™Z§(Šĸ Šūbķ…=ĘŪÚæi,ĮžŲŊ9ëk‹ŊĒaĪz"/ņJãļ …‚íđqōö“55pŊúôŗĸŧY"ĸî~ŅŽõõ“ŽÍZūÍŠČ}ûę5 oM‹ÜEŧģ+71†!īŨ70 GĸØĘJ_¯æĮ2Ļų<\Ō¯ÚģņuK¤Rb<•žtáŠāŧŠ Ÿ˜n~Ũ›^)Ф›oĶĢc$ Չˇ$0���tl4Ģ$><ųēīFO¸Đęt Tĩõ­gˇ^´¯/ĪŠĘ “ŲAōC&‡_¤NĄ×ÉŌK˄a˛Bŗ mõr#âd-žô˛ Kĸ Yˇ-}7Ã6aęĶAj9CsĶŋJjž;_ą)kgîęŌŒ\ūØäø(-×V;gŌķÛ ­¤ˇ[”AFe€ ĩí3ķŖ¨ĖäP 1ČIpō"ql‹ˇ‰•˛ 9šŋ1ŗ\S‰N^ ’ą*fŲļž˛ß�áˇŦŦƒžÃąųu÷$Š|ë‹ĖœŠûb5-÷ãëę‰aÚ¸+�Ķúįîd^ĘįŲĩA1“'õöõdH(ųzÉÆ ßŅÉ…Ķ TŦûle.:1.Ļ„Ĩ3ģSS6ˇ?ną^éXÖG3ŗ.jî]WOÄą’æ).Å 'æHveįlNË]#J|‚†'Ä īså‹júh˙?Ž6ÎÃeģ]ür.:æęî˙Đâ)Q‹S;' ���āÎÕYƒ70aœqÁ7i;"â›Û¤R–‘9FÕbG–•+‰HbārJËōb‹2PĪ‘:XGĢYí2“C3ÄĐzdāk[ܜNpōİ,Y ōLlhō“#Œ ß"M6pÚđøgÂãI°›öo]Ÿ–ú |{jÛFģ<0DšļĐė0™Ë8ÃCJ’ëu˛ E•*ļ(ƒ’”D‚”cČÎ;‰äʸ$5ŋN–Aā‰_ Ī_x|͜9QSĪx+[ÜÛš›Ÿ'íŽ9-ĻåˇjÎSBbŊ \ÃwíÅåŒqڄđ> ‡ÔQ›3Į öžōō׸ĄŊĢâD^W=.#‘2Ô3rʔA-[Ĩž­æÖ7xøäāá$ÔU–îĪܘũéESÃd­í ����E§ŧF¨‘<")FeÉLĪ回ķĒõŒÃÎKÕjUã9ËrІ0 $sqnA%§ T‘ĻŸÖq¨đ—bĢ2ĐØúEd7W6_´aĩØFĨU’Pë$V*oĶūKž‰W^9,û÷Y˛Ģԇ'ÆE(km{›íD*ŖAn)Ę+4 zƒ†ˆÔ™÷ī.Ēä AZ"b5F55]GDD$˜YIĄÕ4/Ųk&S̤d+kž…„Ã|¨•ÛIÜ(…'#VÛĢ/´Ø+ĢIęéqIîđÖÛŸjz~rĪ??ūwÎņ+u-ÔÕ#ņlę¨jA9Qëa¨^ÉCŅ4k#/üõTãäH;ã6L °*˙LuMŊˇOw߯? ã!ģėJ'"geéÁ˛†K›XOŋāáã‡ĒÄSíwŦX†!ąžy‰›pĒâ„Kë���čø:s"RGOˆäåîŠjüȅŽ&7­û2Ŋ ÂîpXMyŠīū}áį÷T` AzĮ­Eŧ6XCDÄéŒĘŠ­?”q†­Ū(ˆ8ûOi›Y{éļ´*åa!) \ío[Žp8ĒL?§Ļ”*Œ2˛›+9öd¤|°bSA…Ũî°[åd°ËôĘ6ۉH@ĨÛs­ŖŽ%"V¨ĩįo*ŒÁDD¤3€-Ū°bĮ!ĢÃa7åĨŽŪ#ÕĘ%MŦqX¨ŧ*/mõ~‹ŊĘR´-õĮˇ`iÛ72ØÛēķë˃•ÕuĩÕ{×gíŽWpé~ēˆaŨOīX“š÷¨ĩōháÖgt*ũģˇÚg#oŸôĖ‘ųÖÚ皞üõ˙2wëãAÕ[íåY¨‡Ļ'cە{đD]Ũ SŪĘõ§ũtŒX]QæÚ—‘0Š<YÓא‘FĻ8kåβuuU'Ërŋųâõ3~kåĮęg§§¤nŪ{ôTU]͉ŖŲųvi>ž—īčR˛*oąrWq  Õ×åTēē"���€ŽŽķ.#""60>aĀîO4M˜°Æ'f'skĶW-ÛZë$Н>lœ„ˆÆŲ.0D[[h Ņ5L:(Œ:.ũGaH˜ĻõΉ”ÃÆ Šũnéŗ]āÔÁņÉO„rD?õΟĨ­zão$Ķ?”ôäʲ-ÍJYČ&˜<]øfÃǎĶkEbdjÀŠ/N0˛Dąm´ąēz>ŋPal¨’ 0Ēk ­ũB 3#ōÁSį k͞R~í$ŠB6aNˆVg°XCüœÉŠĖ w°JmčcĄwŋ!ē–ÕiWÕMū?uŲ—ī:ã$‰wŪąOÅDõ¸|?õŖĪ>Nëŗ×}ž_G]蔧ĸŽX3yčˇëÖ§ŧB= Ã'ND9ö”ß.aŸXpITõ MˆĢXš9ũõ]äŠ Ž×ŋ.Ģ25wųg4gÖ¨6ÆíéwpGúŌRãäī ÷ˇ Ųë˛Ķ—l<#2>ēāÉĪF÷keFČ+*éqq}væ—ųÕõ"ãĄô7ÄLí{ æé5y¤}õúf­ed=Œ1cŖj?ßčęš����:´.įΟ'ĸōōr˙ëîå/(>ÜSuÅÉ8”:{™éÁĻß`ŊĶŗ ęãę*����Ā]ž‘īŸ7ž_:÷Ō8�����€ë€ �����n§“_#t &-ûĖÕ5�����Ā-!�����p;B�����āv„�����Āí ����€ÛA�����ˇƒ �����n§CĄ.]ēœ;ŪÕU�����Āíđģ ve]üC>"I%]Μá]]�����ÜU5Šä×ÖĐ!‚P¯žĒ3ŧŗîüįΚē�����¸U~DÛŠę“U§ũÔ>Ž­ÄÅR $÷t5ęĩGŲNUÕüņ˛kvu ����p‡ëĘ2RÉ=}{÷rųŌ¸„ˆHrOWƒNëę*�����Ā-tˆĨq������ˇ‚�����¸!�����p;B�����āv„�����Āí ����€ÛA�����ˇƒ �����nA�����Ü‚�����¸!�����p;B�����āv„�����Āí ����€ÛA�����ˇÃ¸ē€Fõg?zĖvöwá?Κē�����¸%瞌TrŸÚ§+ëâ$Ō!‚PũŲßKMOĪ{ežw߅I*hĮ1ÛŠ° >ŽŽ����ŽŲī‚XUã(9rÔØģ—kŗP‡HGŲ<=īõā¤HA�����w°Ž,ŖöQtWtĢ´žtm%"xÔ×˙ÎI%ŽŽ�����n…—ÜYÖĩ5tˆ tîüųģētqu�����p;te™ßŅĩ5tˆ �����p;!����€ÛA�����ˇƒ �����nA�����Ü‚�����¸!�����p;ŒĢ ¸f‚ũPN֖Üĸ2k•“¤2Ĩ6(2æ‘QÁ öút­]ūÕO&^1fū}æŦåÎIŸžÁŨ´’á*¤Lûę¸tv”üæöëŦĖË^—[T~Ēž$Ũü qqŌĢ=˜Ī˙÷Ëë™ŋ,JŧīF>^UŲęÅ+Ë‡ĪœÕũ ;Õæ}õĘFioNčÛę���¸:Y,[–ŧģÁÂFÆLxL-cyÛžˇoøāõ}qŗįŽÖ\ī—UĮžŦíųũsžĄUŗBô¤ŠB€;~īŊÕæ§/ßxēoÜs Ũ˜ęōŦĩËŋ¤šĪEøēē°öŋĻž›;`֌ĐĢm7ŸrHė¸~>WŪIĒ>)Žéy{*���¸y:UĖé˙Ü`‘Ÿ3’ąqž&(dpĐUo/YŸšļ`”úúúuō<qēĐ­ŠˆČyŗ Ģû5˙ˆ 7q†#"ΐ‰Ņ…{Ķ ~ĢŽđõvuií°>.Ō�×ÖāÖîNŦOŸĄ>ˇĄ���€›Ŧ3!Ąh{ŽM6äų ƋV­qƄäÅJĩ )H¨Úˇî› ŋ˛Ö+S‡K¤$"2§Î^f™=ĒjCú/vžäúˆ¤§'„p{–ĪürŸHTąė/?*FÍ{͘մ4ÎēeŪĸŧĪ<l_÷Í>nÜ[ķGX>ž•B“æ§f[y’šúd8Ÿ•šū‹ŲÎsÚÁņ͟ W^Zuã6,�ãÍ[WĨo-ǰ;IŽ “?Ę '떅¯n×Ī{'IOD$¤Ėøä€~ōÛsGʉˆŦ[.Úi|ņD}‹A,ķŪÜōĖ#Âv›+׋ŸžÚ8ˆé§´5ÛöYNđ"#×FŽOŠ– ļŨĢĶ6˜­ĩ"+ķՇ=’”Žf[oį~YöÂjŲôeS˛DD%Ģ^ZōŖ4ū‹Æ¨‰ˆ?/{aĩ"yYRĩuūĨÎ^n™>°(-ŊÔ7iYr$WĩoUjÚ/f‡ĀŠÃJ žy” †a.ú3ÔÖgž?ēgũƝŋ?-0J˙Ш„ą!žMĶ‚ÂŠÂÕëŗw[N“‡Ēô¸Éj–ˆH8‘Ÿĩ:§¤üÔ‘‘øhƒGĮÅÜįÃQmŪWĪVNŽŖėĩûƒĻ,ŠÕđG÷Ŧߘ÷ëqģSd<ÕūCF{ÔāŲØ{]YvfvnŠ­Z”øčBĮĮęį}đŸķŋ-‰Ōۚš>4yq\Ēû-gcæÎōgDÖKÕ䨏5GDTļzņĒQ÷/Ũ˜iVN|õ˙†:ūg}În‹­Žž‘zŠúŒ‰‹Đ\ēČķäÎ%ôMŠs˛v[Îå}ąņq>åkÖī<xü yųGOˆî%Ŋhi\Ã!OÅp9Y9–ĶNō𠊞<1Ä÷ĸĨq̝<õĐãŲYÅļZŅÃohĖ´(ÉîõY9&ģ“Q6ŸēĒ˙|-[9}q\ŋ†zĒķ–.Éņ~jūƒđkę[+ilrāá5ŲGNԓL7|ōÄ`gNÆöWV×KüÄüebˆâæ|6���ĀŨuĻ›%XŠĘxiā@ÃeËÖX…ļák!ņ…Ģ–-˙Y yō厖.šûD cĮGKW›…†ŊXŅ’•žO—¸xŲû_ŧd´oOYW,°áĶ—Ŋ4JEō?Íüpų+ņú‹;û2÷Čcgŋ:eœX–%ĄtË&ūūšËŪ˙ôī(K7¤ŧšbˇ&qņ˛?œ5ˆ˙oZzpyŲ­KDdÛôî˛ô Řįŧˇäå$CՆ÷?Úd%RŧŠÔÖp¸Š¨Œ“É,Ĩe ]ķæßŦ\ā@í%c‰Ušëōĩ_ųôŗ?|n�ŋcÅōUDDüūÔŌ e#’įŋņŪ?^N28ˇ~üYŽˆČ’™’RĀD=ŗāŊ%oŧúäļāËĨëĖmĩË ÔBY‰ĨaŧŠÂR’Ëj Í""LEdč§g¯pū–í?~WhˆŸ;^ĩ Č��čIDATÂ@ŽŦ›SR~æCžxų­ˇ_N ŽÜy¨•swŖ¤ũ‡ĨæYĨu9+vä–ŗáũ/ŸĒŪķÅįY•=ĸĻ=7sΤ0ļ(ũÃõ‡ų†Mĸ-;ķ oôãs_˜6Ņ îËØ˜[MDDGŗ?][DAãæŧ0ëĩįâ‡2%˙JÍŠ¤†—JT${—82iÚ´H9ŽųrãožáĶž›õÚ Ķôõ;RŋmėD°ūgÅĒė:˙ņOM[đĖØ~õų_|šĩRč;å…ą}‰z›õöŧą}H(Ë\™’}Ú?ö‰sgNV–m\ų¯üj:ĢÕģręc’Ÿ‹é/­ÉIK˙Yė=ųŲY¯Í>mdˇ˛ßŽ)míŧЧwgöŸ0ķ7_œf¨ßŊvÕŌÍ5‘OÍ~į͙q^•™ëķO´zHftô´7/|ã)#ŋ7c]žķ’]XV<–›s,ôņ‹.š :‘›ątE=åÍÅķæg~Ũ˜ĩˇîĘīË0$švf×G$/œ˙ÎķQŪæė•§īí1vî…o<ĖīŨ˜Yt >&���ā–:QÉĘ+\žã8°õ—*}lR|°F)WhÃ&$ôĩūŧŊ°éģ“ ‘8XÅ‘<4Ō ã+*ėD,ĮÉY"–•sÜe}×Rđ¸ÄÁ:­ZŪ¸‰ÕŠÖqDŦ:(DMnĀcÃT,g5ƜVkUëĨˇ6ŽPôũĻ y䔤(ƒJŠÔ LHŖŽÜšuH Mˆĩ–VđDDļÂR2>8€3˛5DV7@ßĘI•Ĩ刈3<2Æ@Ļ˙îˇ?˙•WŸ|U(՚ą÷ŠbŸ™'Ŧ–*Ō„GTJĨBüĀôų/%?¨jŗ]h”;Læ*""G™Éá9Le)Ēˆˆ*JĖ‚>8kīüÛšAIŖCõZGš˙­dÃÆ%Ö(å ũ°ÄĮ.Ύ7<nÆXŊ_ž;ûĨ…3˙ūEļ8|ú¤°Ë§Ęrwņ41.ŦOî~†ácųĶ™Ļ/íb@ô¸(ƒÚ×G34:ܟlåĮ"ĸÃg<?mÚč>~>^Š}ĸ‡÷–ž*?ÜoˆEŅoøØĄzĩŸ7KR˙ØįĻĪ™ҧ‡—ÂGŨ?:ĸŲ~ĩ8‰H0įũlUFĮęßKíÛĢīø Ņ÷õĢëˆõ”CŦTÂIYr–dí˛÷ŒŽOÕ(ŧŊM$9˜ŗ§˛ŠøjiČĨž=ēstúØIŅ;(ŧ_/…w÷>ãf<÷øhmĢ'VTÜuŸ7K$íęĮˆõ~ÃŖúH‰ČĢ_ŠNUœh%nˆŠAŅ‘=¤DÄõ ëß]Ŧ´Ø/ßIčcđd‰d†žūTOúáQ=¤DŦoP_oŅ^~ō*Ū3Æ/*RÃą>Ŋûu§:Iß҃ēŗDœžoú§N_E����íëDKãXb‰Žü×Á6ŗET Ņ]øĸĢ6hØŦ “ljˆäjŋæeB,Į’āl­NuQƒŌ¯)‰I9ŽX™Ēéē$)ĮĩŲ_ĢãÚ-<rá{ĒJ¯“9Ėf;ęƒiõo 7:™~‘ƒƒ(ë{“ôƊBŗ mõ^R­ļšTN­–Ķ/•v"%qŸŸž.ÍdĢuđ‚ ‚(ęˆĶÖq+×.ųÜ5x@ˆA§Tęæ™Úhg¸œR3­`ÍÅuPR°m÷WÅ ŌÛËJĒ!yģį_Š h\:(ØŦvR˙YÕôBX­Áū+ļķn\ģÚĸ_dî3î‰h}7ĒĢĖÍĖJI•Ėy6üâ›%Ô;~šéŽinT„Æ>JDÄŖęÛŖŠLŠDJäl(“•ĐÉŧ5™G*kÎ8QE')Å ¯@ŲûÂ;+•:‹2ŗ6–Ÿ<SëEQDŅ_‰¨ú¸Í)QõlžÆÆ'|ō$"ēøs~˛ŦRėvŸŽųÖmŦ ŠŲUY^G~žDDŪ=üšÎ¸ĒŋÁcwÖĒքßÔģ¯žģĸ‡Ļ#ņõnZ›ĮH¤ÔÍĪĢéc-e[ûK|}ŧšË…VŪ/īîJŽŠ[†!īŨšFa8ÅĢy‡ŊÕŪĩH¤Rb<•Mī‹D*%Áyķ?$���āž:Q"Ĩ\N›U Ö˙Ž›ˆx'OŒŧÅ%,+åHøæ§×:&Į^:KtI=k+Vĩ:._ë$1ųôü‹ZeU"ĩ!HĪo+´’ŪVlQ•‚ÖļĪĖĸ2SCähu–Ŗ° ņ‚@DÖmKßÍp„M˜útZΐp`ÅÜ´†:•ÃfžĘmÛôCŪ†ĪŋOĨęЇ’žxØ(oĢÕ°ĢY(œŠĘ8ŨCjBËo˛“Ú\l•ÕDļöÎķÉD^$–Ŋđņc9–Ĩ›ūˇ"{ã~!tĘäˆ�–ˆ|ē'L:Sū^vvičä‹& DÁIäÍ´qÃtĻÕ˙Jjķŋ]ēÖæ3nĘ�•Œ%:šõúŋ[ː0’ /îd^ĘįŲĩA1“'õöõdH(ųzÉÆÆ…ŽuõÄ0í|*ëëE:Ŋ㓅;.jU:Dž C5$í?aZōŽŲų;ŋÎÍ%Ũú;qtŸÖ.ĒiũE]Ņ5Â6üã:ūsÉ š¨Ä ���¸Y:SRp?ī.⇠žø+Ģ`ÎÍ>ĄĄå¤‰˙{÷]e}'xüšWn"!QJb€Ab•"ŗmÕ-ZiĮuËhįhYĮmĩĢļsŦÎėĐŨn=ĮŽvg<ãlk•Š­:v,Ŗmg­ŋĐĐ ˆ€$HB’8IČūAP5›Ė÷õ:ųƒ\nžĪį^<ĮŧĪķ}žÛÚąĪßttvDaɐüH ĸŅ…Q8ãę˙qŅž×%eŗ…%Qrâôq‹Vmh­Ũ°ąhÚšãĸdĘäŅKWoi×6;ųĘũnČŨûtXwwOf#ękž¯ÍžzũWĪĒŪķÛeëžĩ–-Ÿ9įę™sĸģuĶęį/\zëĪJūö[g”ėņi'Wv<][ŋ96tO™[ŲĶËVmh-_ŊĨhÚį+#âƒŋ˙ŲL6ûœRčî8äŲš¯ģe[[Œ/ßg7å˜Ōą™Žm;ēŪ]°™ÂÂčéî戸Ā˙Ĩ´ũvÅī:'_tŲėŠŖ÷ęĀgQ""ļŊVS—ŠūÚ%Ÿšēį˜mīüįĸ§Ģģû=Aũnš\&JOģâōsöŊ9[&S|ĀßeĮL=sîÔ3įvwn_ŋâÉûÅOН›æû}�@š†Ņ5B‘öšYe¯.ŧ˙Åw]›ĐąváŊ÷.û×U­“+3Mũ׹DDDũēÍŨ…•.‡<WYQŌŲÔeååũ_ãŠ˛E%{.F*ĢžV˛iõķĢ6tO™VåĶ&Į†•/ŽŪR4íäĘ¯×šiŨæŊnŨ´š5ÆU”GtīėŒláŪ+œĸņĨįkcĪŠĢŽMĢWÖîšŲAļ¤ræœK?;ąģaCãA(9qzyÃښ×VÕWLŸœ8vĘäŅĩĢWŦÚĐ=å”IŲø0īļŦŧ$ę74ŧ}õVíęÍû=ižŖÆæbGũ>W•4ˇėčÉįŪũŧâ‰JcëÆēŊĶė\šäÖũjũû•YOgOdŠs{?å§íß^Š;ØÉŠîļŽČäŠ÷ū4­ŦŠ‹ūŗ‡c'”v5Ŧßž÷Šožü?úĮ'ˇž}ˆˆ8~RUĻŊŠ+7ūøãúŋŠ3™Â1hļÎíëWoÜķîg ûø™_8grĪļ]ɓ_ŲL&zēŪĮîí›po�€#l8…Pd'_üß.Žî^qĮ˙žéŽŸ~qõk¯žô¯÷ūÍ÷o}Ēsúå×]PQ4ã‚Ī[ģėŪGW74ļ6mĒYt×ōĻĘŗ?W=$?5{Ęįū°ĸáŅģŊXÛÔÚÚ´ŠæĄ[ŋ{ã-ŋîŋY\åĖIąî‰gë+Ē'g#";ųÄĘÆŽîŽ>eŌAÖËtÔ<tMCkkSís‹]՟Q1nÚ¤ĸŋũÕs›[[›jŸģ÷ŽuĮVŽÆ [Zģ;×.ģëÖÛŊXÛĐØÚT_ûüŖ˙¯Ą¨âäō8Øãą'Īj˙å‰MåŸRQ~JE÷ę'^lŦøũiEęũ¯˜õéņ5‹îiC}cÃÚå÷.Ũđá÷-ÚÄYŸˇsÅ#?_ąšŠ­­i뚇=ķÆ˜Sgīwc†‰ŗÎ¨ęZũāĸ—×o­ß¸ú™Ÿ/[Ų4fŌĮŪo 1'ŒęY÷ōŗ¯ˇíÜQ˙ʲÅĪfĒŽ–[›;ö˧ą“'ļ˙nųŠúmÍW,šgCéÔQącSÃÎîČN>ã3Įĩ,`Ų+¯×oy}ÕÃK~ŊĻs\Õq‘)ŒØVûģ-o6wVĪūä¨ē^üđęúĻļļm¯×,ŧķ‡?ø§šûÕĶđäĸû~ü@Íú7›wîØžqõ“ĪmĘTŊį"ˇ!`ô„˛ą=[^x­9"ēwŦyđÉ-‡ü�€Ãn8m‹ˆlåįįßTņä˛ĮõÜC/>֙-]>yÆĨßų/ŗ÷übEÕķūėúė?-ūŲM‹wFŅąĶį^éųC˛ƒ"ĸâ‚ŋøŗė}‹ßvccg˙´×Ķ˙kkvōŒ)+V;Ŗē˙•MĒ.ßšĒūãĶzwĩ’ߟûųėō;žˇ`[Gv|õy×]ũ™’ˆ(šyņ՟Ŋķūûž˙Í]yĘšW~õŦxŦáÖĮîø^öúŋ˙ÆuŨ÷=´øļ§;{˛ŖĮO9å’oĪ;ĩ(âōxDTžrbüËķŲ™“÷Ü""[qreĮŠĩågöŋũæũ¯œ{ŨÕ­÷.ũŲ˙y2FWÎ<÷ĘyŲ˙ģāĀ÷Ü€ėÄķŋōõâĮ–ũúž›uEŽtü”O~íŌŲS÷hė×]<ōĖO~ÔŌ)­:õĸo~ajŅž›%ÄÔķ.ūƒ=vûß,ˍ›zúy_š¤ôßÚî~đū;~~éˇ.{÷3‹N9ī˛Ķņā’;ūgŒúØ´3ŋtÉ'ãÉÆ;–˙â–ėåũÅItí%ŋ~pÁŠļČ?ųÔ¯\uΤlDüŪŦY×,_|ëēęËūâŌ˙ôů|-÷Ø˛%w/oīŠÜ¸Ē“ĪģnîĖŅûĪT<ũ˛ĢÚxäɟÜÖŌŲ“)SvŌėË˙øŒ1û?1ĪN˜}ŲŲ —üđ[‹2Ŗ'TŸ÷…Ų;<’ī™�€äôõõED]]]UUÕG^e€?^ķÚú•šŒa�ęúÎ˙zžzīg°ū‡÷FÃö™'OÍ÷��|t5¯­Čotī—aĩ5��āpB��@r†Ų5BXųE7ßuQž‡��€aÃ!�� 9B��HŽ��’#„��€ä!�� 9B��HΐĄ‚‚‚Ũ}}ųž�� ˙ŪŨst6Īä3$B¨0wt{{Gž§���CSskand~g!tÂĮĘÚ;:ÛvuôîŪīY��€#åßģ{ļīxŗŠebųņų$Ī'¤öȍ<ēzJåëo4lojîíÕBZÍkëķ=��ÚŅŲLanäIŋwBŪˇÆ ‰ŠˆÜČŖ§MŽĖ÷��@†ÄÖ8��€Á$„��€ä!�� 9B��HŽ��’#„��€ä!�� 9B��HŽ��’#„��€ä!�� 9B��HŽ��’#„��€ä!�� 9™|ąuëÖ|�ySPPĐ×חī)�Č˙ q&LČã҇Då÷-���Rck��!��$G��ÉB��@r„��!��$G��ÉB��@r„��!��$G��ÉB��@r„��!��$G��ÉÉä{€~ĩu›~vßÂö]ģwī>B‡8捪FSôÕËįMŠĒ<B‡���†…!qF¨ļnĶm?žkg[û‘Ģ ˆØŊ{÷ΝmˇũøŽÚēMGî(��ĀĐ7$Bč§˙¸pŽTP}}ƒw8��`H!ÔÖŪ>x+(ØÕŅ1x‡��†ž!BƒėˆnĀ��†žC��Hœ��’#„��€ä!�� 9B��HŽ��’“É÷�MæøŗgOÎEOķoŸ}vUsžĮ��†•aBgĪ=sVqDDUÛÚUËķ=��0œ û­qŲ|��� ;Ã>„���>,!��$gØ\#T8ãâīūÉôŅû=>ūüëx~˙Ÿģwŧüãŋ}äw=ƒ;��0Ü ›3BÅcĘö¯ ÷Ȏ7>7Ã���ÃÚ° Ąˆrĸ§§Įé ��āP†ÍÖ¸7_xčÚ&ŽîŋI\ųi_øÔ¤lDÄÎuĪüķkũ$ÔŗŖî•Ž| �� Ã&„ĸĢaÕ+ {ŋКxNuÖÖ<÷‚Ī��>„a´5��āđB��@r†iĩlyŗ3""Úļ4ˇįy��`¸>×ŊKËs n~.ßC���ÃÔ0=#��đŅ !�� 9B��HŽ��’#„��€ä!�� 9B��HŽ��’#„��€ä¤Bų��ȧ!BÅŖFE_ß Ŧ¯oÔ1Į Ōą��€!iH„ĐUWĖ‹A;KSPpÕķéX��4$BhJUå _ŋēxÔ¨#ēi­   xÔ¨ž~õ”ĒĘ#w��`čËä{€~SĒ*˙ú{˙=ßS���Ig„���“��’#„��€ä!�� 9B��HŽ��’#„��€ä!�� 9B��HŽ��’#„��€ä!�� 9B��HŽ��’#„��€ä!�� 9‡'„2™LooīaY ��ā}ôööf2™.rxB(—ËíÚĩë°,��đ>ÚÛÛ ¸Čá ĄącĮļļļļ´´ėŪŊû°,��đŊŊŊÍÍÍmmmcƌāR=Ŗ´ĮQG5a„ĻĻĻ7ŪxÃ9��āH1bÄȑ#ËËˏ:j gtOEĈ#Ž?ūøÃĩ��0ž­‹KÆö]ŅĶ—īQ>€LAwL,šŗĒē”ģÆ�@ĸž­‹ŗîŠúöáQAŅĶõmqÖ]ņlŨ@—B��¨‹ˆaR@û(ˆžž¸ø.#„�� QÛÚō=ÁGSí]C�@ĸ†ßé Ŋž—O��ÉB��@r„��!��$G��ÉB��@r„��œLž���†ņ“â–ĪŜ˛(+ŒŽÎXÛwū2lÉ÷X•��Ądjŧpe´ŧ×>um‘ķΊ;¯ŠŌņƒ†|÷‘!��āÎūTTÕGՒx}Ī÷ņâÆČ]§WD Īr��pĨ™ˆˆÜžõÄuˇĮE/÷W21îž&šoŒž›ĸūqķԈˆ‘“ĸūĻøģ‰ûüÔÄØxSÜ=5"ĸ¤,î¸*šoŒžcÍqné`ŧˇ !��ā–¯‰ŽōXzIüQYŒÜ˙¯sqį•1§-.\UˇÅĩã†KãšŌxks,m‹ ?ņÎg|"ĒÚ➍ĨąđǏ°;æŨUˇĮ=ŲXzeœ6ˆûՄ��p¯ŋs~qR,ũĶčē1jވ›gÆ owKWĖ_§/‰§âõÆxø‰XqaEDO,\UScÆŪ'Λ k⊞˜ņé˜qíĸøecŧŪ?x ^(ų' Ū+B��ĀĄ=õLœôũ8õۘ_-cbūGŨˇãš˛ūŋmÉÅü/FÍ7Ŗū/ŖųÛqv6r™ˆˆ§VEÃq1o\DD”Å…ĮÅÂW#"N­ˆhˆå]{WoĮ[âôIƒ÷rÜ,��ø`zâ7ëã7ëã%cáŸÄmÄԟÆļqņøUQļ&žü@ŦmČÄ=ž÷‚ĸą´9æœßy&f|"Ē›cŪ–ˆˆŌ\Dy´ÜôŽåģļĮȈˇåĨ!��āFæĸ´'ļõŧķHë–øĢ5ņÂIQQzRœŪ.‰§ö<aÔģnĢpĪúøō'bÚ31ī¤Xģ*~-]ãԇŖkŸgvu R…­q��Ā!”ÆōīÆãgŧ÷áęԈöhˆČGôDÃŪL:azœīœsyņÕhsĻÆœŌXúj˙ƒ+7G”FŽ%Ö5öĩôDCû`ŧš=„��đžZbūKQ}n<?7ž4)f”Åg§ÆÍ—Ɲ'ÆŌ§c]DŨÆh)ŽfÆøQqÚĖX8)–ˇEUEŒßĶB[bi{\{~T7Æ=ũKūæĨXž‹{æÆiãb|iœ{fŦüfÜ2ņā3nļÆ��‡đÔ˛8{sĖ˙tÜ93JŗŅÕk7ĮüŸÆßoŒˆh]_~)n› +×ĮĩK"ΊĮΊ•=Qž,"bᚸá?ĮĘ_ÆēˇWl‰ ˇ\#J#ãžEņW[īôõõED]]]UUÕā��ȡ‚īæ{‚čģéĐĪ9˜ēē:[ã��€ä!�� 9B��HŽ��’#„��€ä!�� 9B��HŽ��’#„��€ä!��HTAžøČ2î!��‰_Ņ—ī!>‚žwĖ@×B��¨Å_Š‚axV¨  i ‹!��HÔŦĒxúę(+> ;ÍGæ¨(+ާ¯ŽYU^ę0Œ�� OŗĒĸū/ķ=D> “ô��8|„��!��$G��ÉB��@r„��!��$G��ÉB��@r„��!��$G��ÉB��@r„��!��$'sXVŲēuëaY��⃘0aÂ@~üđ„Đ�‡���LļÆ��ÉB��@r„��!��$G��ÉB��@r„��!��$G��ÉB��@r„��!��$G��ÉB��@r„��!��$G��ÉB��@r„��!��$G��ÉB��@r„��!��$G��ÉB��@r„��!��$G��ÉB��@r„��!��$§?„2™Looo~G��8Ōz{{3™Lårš]ģvåw ��€#­ŊŊŊ°°°?„ÆŽÛÚÚÚŌŌ˛{÷îüŽ��p$ôöö677ˇĩĩ3Ļ ¯¯īíG›ššŪzë-{ä��€˙xFŒ1räČc=vĈī„��@"Ü5��HÎ˙ų Ĩ˙fvd…����IENDŽB`‚����������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/profile-oauth2.png��������������������������������������������������0000664�0000000�0000000�00000071601�14156463140�0022225�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR�������rWtä���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨqTSgžđûoįĖ!9ķ. ÷.Xp wqÕ�Ķ!8Ŋ&ž oGˆS)XŠĐj کЙQÚÎl§•ņ8`§#؞9­8Ķ mmąÕ6X{Œļ÷%úŽ1ņŧWÂÜWH×Éđ„so€{ߛMÕÜ?0APˆÚū>ką„ėįyöķėũėß~öŗˇwøũ~?B!„đ­›]�!„B|sHā!„Bˆˆųöø//^ä?ūã?P…‹/ŪĖ2 !„âkęŋßīŋxņ"˙ūī˙ŽZ­æ;ßųßú– „!„bîŨá÷ûũCCC|ë[ßâ;ßųÎÍ.B!žÆžāķųøģŋûģ›]!„B|Í} ā… ÜqĮ7ģ,B!„øš“ÉB!„ˆ <„B1x!„"b$đB!DÄHā!„Bˆˆ‘ĀC!„#‡B!"F!„BDˎ¯Ŋʍšã[3_I !„â6%#B!„ˆ <„B1x!„"b$đB!DÄHā!„Bˆˆ‘ĀC!„#‡B!"F!„BDŒB!„ˆ <„B1x!„"b$đB!DÄHā!„Bˆˆ‘ĀC!„#‡B!"F!„BDŒB!„ˆ <„B1x!„"b$đ˜/Ŋģ)Xt™ĪÚŋųNÃŊût‹žGUûÍ.‰Bˆ[Áˇovn>ÜGšiÜoÅátáöĄ¨Ô¨4Zô#Åe•ę57ģB!Ä-Okņ9iÚø ŽaPŞČhƐ F—{[3ŽļVZĘ÷ĐRmD}ŗËûMįi§ŠÉÆĀ¤Uš´‹Œ˜rô$~ÉÕē“ũžT•égžīšZŠoõaŽ.CĢׅˇ›ÍËíaĀĢ€ZEll :Ŗ “>1‚Į[/ÖÆ܆JʍˇĪE†§u 9[Ķ¯[BķŠ#W¤9ãm}cm"Ū…yĒ\;14ážôą*V‹Ņ\IUÍ*tķŧY7JoÎwVMŗ¯öŌTGSæ;8v܎­pcž6Įŋĩ<KT|"įz˙;9?~{ŽRõĐēņ1> vĐ´ŖäŠÕįú„ĒUmÚDUæ1^_qûtJ__*´æL —?ņ 8ąŲ,´ @y™iĨëãs6Ķä6QY˜ÁLģimÚĪ@‚S™ĩđ0ā°aĩ43@ĨiL$Ķ\ˆ6öVÔĻĸŖ´ą†ŧŠĒJ€~†Šx[Ål¯Āą+g. 7ĪÔĒö°yb#} ;-46VQėV°žSBâ<æžYZGŊī:. ž!nųĀãå_ŋÄĨK—&~.^ŧ8ņīîöķ-.—HœŅÄĨ‹—đŸûķœåí;ROÃ‡*ŗ†–]%h§XG­[Åë{ļ6÷b˜IčuŌZ˙[ZŦÜÃ>PÅĸ5Ž ´˛†ŊpR—Ŋ†M5Ö<{iĘËŖÁ­Ļ éĪ4„ûG6‘YiÃØxŠ&]hfŗHkķJœ¯€‚wpėē2wÖ.Ŗ¤ÅGAĶŋžīëÆR[Ëë–NÜ Äj Wí pU2b5)hSB>HIAĢVh´Øéôč1Ígoķ54āöā‹pž>—ƒ/ŅQP˛"ddFCba jšąš=øôšuęjuúy=I͟X2sŒgYQöΈīs!AkÄڅs0$z1UíĻÅUBĩnÚ¯ÎZĸqÕMëoeˇ|āqņâE’““¸ãŽ;Âūõû˙Oū§˙í€;PūŸ˙āâW ~˙Ĩ9ĘŲ‡­ÍŠ‚š‚ʲ)ƒŽ ēęvĖ IīĒ 6ĶæÕ’WRMš!´5ĩRķ˜/÷cĢQÉ ĻÅڎĶWvŧŗđ:°yÔ¨T>ļnČI›HÖnŗŖIžQ ŪĐ g‘VŲãhÛĐbmŊqŌPĻ“6ë�$”PšāÁ˛š˜jh *Š5kQ8hĢ[CŨU+.˛Ô hčÅŌ{z]G°ļģp ƒ:ÄL+Ō&FD\­;ą°‚’'‡mņ Sŧ¸ŽX°}ūWiH\ū=ŸĮŽÕjĮ50Œ‚Š„ķ ôÁû<>g3ļ ‹5¸,v\Ã>PkЙ )ԏŸŪŧ¸ŽXą}Ų‹×Ģ€:–D]y…×s ”ÕÚéÁ‡Îˆyr›øzą[l8܆}íŅ˘)¨ņálŽ§Í ĐB3SyĻDđ:ĐfwáFQĢHHĐc*\nŽ!|žéNsjt…„Ÿ/|¸íŦŽ^ŧ^@“ˆÎdÆr;Æ×kĮjģÜ&ą : ĻSÔ3X~孖kĩņøžSĒëå¨ÍĮშLæÂ‰<ņvcŗÚpēöŠPMQîHđŲ„šĘMņ‡‡.Ÿˆĩ˜‹?!ŗņ†–lj�ŅĩiŲØvŒj¸ÔR×đ v÷0¨´č ĢŠÛąbĸŋ´nūUÔņēļ…ĒfĻ×˙ĖցG1Õ§Đ°G‡uënŦŊàÖbŽn Ąär_äļŧČÖÆ#8ŨÃ(*5Z}!Uu;0§Ė~{5:‰8đ�:;[—lÄ]ų:fۋÔÛuԝú…ęĀqēĩv7V×� j´z3kvPĸSãĒĪĨ°YO͟wa IÛĶŧ†œúXOũĒÂoĩøœÍTmũ-6—ÚLJĒ*PMŪÍ}NZ^ŦĨÉęb@X™Ę­5”ŽßâëŨš •ŧ†Jļb­ÆúaŲmßōOĩ\ŧx:Æ.^ŧČĨ‹ņyûøę séĢ1ü—.ÎQÎ.N¨ ˜įč֛Ŋž–6¯ŽĒņzM…+VPXVCS[&Ü´Ô5ã f*Ĩ[čƒ)övœ0›cņ:x&tcs ƒ.‡Šn=ßxZi”ë@ąb92éČpļb�mai`˜ÖŲDƒÍ‡ĘTGëŽ JÆˇk˙Sāœ<Ûâ&öâE:ØŖû\ŸĐŌęDŅ™)­Ŧ¤ŧ؈ęK -–î‰+;ĩ”;ļa…Ĩe˜ÁĶŪĘū/ÕdT°š˛’Ō=Ē/÷Ķr$X“^;û[ÚĐäPZ^MUy ›ļ ΉĀP >6č˞ĸēz å5.‹ģw<™V,^´æ2Ę++)/ÎAåļ°ßŌ;ãM¤áCWXFyeZ6[/ĘÄ>œ–VŽzc1•ˇĮœĀ€­‹Ë¨Ņ—”“—�*}1UՁ:Ās„‹t…”VV˛š´„Lĩ‹ũ­í!ûĶėh´zpcmūg¯÷ĒW۞#Í´ØÆëĒ‚RS,Öf,ãîëÆ˛ŋ3Ĩå•l./Ü0€mŋ§o˯ŦØkļq`ßiĮæŅSPųÕÕ˜5Ŋm¯#/v‹ģOGai%›++(1Å2`mÅÚ;G•4m ¤Yąƒē7-/6ËÖKĶÖVŧæl]‘Hɞ6Ēt *x{g 8ņy–âĘĀ:ûmĮiÛSAŦ­Šâg\>~T 8iōŌđÎ~nj VĢÁw„†&Å-'čüķŸiĢTcŨē•–ņĮYKi•ĖģØo=Nû‡¯SŦ>BeųN\sQ!ŊxˆEŖPŖVûpˇ4b3ÔĐōa f5ĐÛLécõtjĢii;…ŊmėÔŦŲ„ÅēB3ZŊ%ė‰=G-NT9%˜&GŽž#lŨX‹#á)šŦĮą6U°ŋ–ũaL/MŏQߊeķž6ÚmûŠ3¸ŠßøMŊ—×R)^ŽžnASõû oĢ n“Ā#4ØũšpaŒKŋââ…1.]ãŌ˙ĨšņđޜÔææļŽũ–H1aJđâõ†ü §Ā¨×l^PsĐãÃaģ<)ĖnŗŖ¤)5ëÁe§süČövâpC‚Á4õ­ Y¤Ĩ-)àōak96âlmg�ÅŁĢˇŨÎ�jŒÅ+ÂįN$RšskÜŨôyœXÚ]`$3Ā‹ĶŪ‰Oģ‚âœ454)z Í)ø\ސ�đi0˜hҍaxĀ :ô)4 ‰)F KË)F~ģˇZ‡šPOĸFZ“‚Ūlf.ŽË +>КŒ#QŊžD< Œ˙]Biyf]"š`ųL‹bvģfxr÷Đé�9†ZCĸ~&mh›¨Ņ™+Ø\˛ }bp{t9d&(¸]Á\Ôj—ßęĀI 1‡Ōō ŠsRu—˜‚Ņ E5āÆíen$).6‘¨¸hki¤Ąv'Í­XíŨxBO¤žnl$˜J&ę*Qŋ sϚ/íÎ@]yöŠŅęƒm­IDˇĸŒŌRS`ŪÖĩ–OŽŲļ1>-Æ)ÁãBƒ^Ÿ‚Ę;Ā€ĀËĀ€‚F§GŦ{­~ÅĨ%s|+ĐAá.t‹Žü)nߓ4˜ëę0ēꊹxđ´žHŖĮD]]đ˜VkPĢ�Ôh‚smö7YđęhĒ^.1­ą„†­&|ÖĻđ“ŠWKéŽ2Lú´‰‰ŨŠĸÆT^1qą¤-,A‡ įx7Ĩ¯¤Ĩ­Ļęt)‰$ęr(/ÍAåvā˜edëu}BUƒte‡Lpˆ-¤ļbz] jĀŲ˛›Nĩ™Ú]ĢĮzŠ‘’5äaŖÉŌ ēō´>l–Ģ:K§sÁŠ+FŦ|íŦÃ:ʡ–aLҐ˜’Cy]Z%dĨöF]ą6ėĸĘBbbæš*S\45†^=ƒŠš­+ôčRnŋkˇü­– .L@Øī/ŒáŋpK_qņÂøī˜ÃĀcü˛”+–yi]ŗ„šÎɟ¨ī|—ŠÎĩnp5Qhhš&O7_�:&48xГ‰H0ŅëÕč”ßbu‚Ų>{;.b)4§M¤fii )5Öâ°ˇĐæYEi"€“ũļT™¤Œošˆ%áŠĮEÔhĩąĀđ4Û;_:[k oąZ=……ÆāՁ÷�hLÚ°B­Õ‘č;‚{€Ë#HąZBVJĐ%ĸ˛ĄÅâÅ ĶĄÕ&ĸŅ$Ķõ100 )„Ėmu"ÚX°š=—V'ĸt‚QÁå+{5 ;pÔ9€WņĪ‡…™Á{ņ C⤠˜ ÚDB/ãÕj/NĢ•/=ŧJđ‡Đ\í2Y ÃvŦVÃ>|øđų@™ãiģš”J+sđzēqģܸÜ.œV[,™…eę4āu3ā‹E§ Ī[›’ˆĘáf‰‰Z´ÎũÍ`0 ĶĻ MT“˜l€k-smœHlč:Ē@ û|õuēX:m­´zõču:´)4Sæ9:66íĀ|EĶ¨Đ„î€šUÔī°`zq %ø05›â;ãœØ] ­ Ÿ¨­6æ Wją; ö@ŠņĘāMĨÃ:ĢU­BChOĄŪŨl­ĩŅéöāõ)ÁũK{ÍŅ›p>Ú*īĸ-ė35ą™%4ŧ~ģ.!l—k�ôĻđɡjú°u瀔ëØĶdÁŽ#āąZpŠĖTޏ˛$îNŠJ.%äÃDú&žŧqģ\(*#ϰLSČˌe¸Ķ‰cđSMfæ4}ūmā–<BG< <đø*8Ōqņ+˙ ņåßüæ(į„Ā0Ü@pä#ė�TŖ-,Ą@{ųpXp\펂Ī8ĄčĘhŦ2M}îPkĐjR0 ÕN§¯ŒD_'ˇC•AŸ0ŒÍÖ Æ4œV;Š*Ķ´ˇƒf“–sŠ•ÍBĢĨ—ŌŠ”ām5úęËÃ{JpŨŠF†ÔąáŨId¨Xd.Ã41¤F­Ņ  -ŸO XŠŗ^™‚6pf~]Ö^}åj;6ģ›ÅưOE‚.sĄ­:pRQŠU“Ú8p‹GA™á=nK3û] ……$hPĢa }7-_ΰ| Š/Pū°’¨N€ks N21›ËĐ&ĒQãÅŲڄíjI;[i˛xК )ĐF‚pĄąu~n­iĶĐ$ĻĄgøząĩļbŗ´ŖĢ\…ÎįÃĮ0ŽĻZW|3!x’OÁ\^NBģN‡‡U ŧ‡Į\ˆY§Žĩ<lë¯ŖÕW‰Õč Ë(ĩÛą;íXVu,‹ …˜sRæ0„‹EgÔĪčŅiMa æ7ĶĻ.¤äjOčų|( |ؐ‡ŽáĘņáNSĨ ž€kEĪ^ËT91VíĸąPüíĩ˜*¯÷F‹ĶÖw¨2Ē&ūŽMH!qŠMSkT!y•frûiˆ%؟kͅ,jØM›sFŊ›ÅæĘ°9ã|^/¨Õ¨Â>E’wĀ Š…ĘE–+ˆuã`āĄâv~4ī–<Ļņ˙÷Ģ¯Æ¸xáĮĸ˙Ķ˙Í˙Ŧųūöoūŋ9Ę9-01ŗ­“6ģÂáÃĶÆ˛đgß­ĪZq´MN#„FØOTZ2srŽyONo2ÛŌŽÕ &o;.U&ĨF�=ƒšVg'^ĀÖ9ŒĘhšrGŸ“´rJ(Œĩ°ŋwEžÖ# ĢL”^q)ä œä&ņĖÕ¸ûõQkšę…ŖZ…Z †RЧx$S­žzĮ¨Ņ)Ô—›ÕJĢEMe‰.ėäs9•đ“Õĩƒ^œŽaLeSœøfH­BĨt’a%ņų.âyœ¸bŅ—¯BR_Sĩåe>\NŠļBcJHĘWŽ ΎŸO=9nu &Ŗ{ë@`ĸ°ZšXt%eaPOŦ>^}ęDô+JĐ¯�Ÿˇ—ŨŠĩĩʟœ8ƒå—SœQΌ­qZã đyq;ÛąÚZ؝ޏ)ī qÖÖcM4`đYŠĢwb­žæa[ĩšX,*{‡×‹'W皨ŠÎė3æÅÖjC14P_‘r~ŊągjbôčŽûɕđ�ãr{N HRĖęķ´.öģT˜Ģ§~ÜX­Ņļ°O‡é"5 PéŠoĢžâ1g•ZsÛÍå˜Î-?Į#4đ˜üķÕW>.]P¸ø•‚÷˙úz{ãPÆūvÎō6•’€[C=ŗ~šF‹6–ĀœŠŠ–O?Ė1cP ĶiëÆaĩŖ¤ä0~~43ÁŲŽŖ70'CgΚzG7Ģ´Œ”–hÁŨJĢŗũÖaTæ’ĀäĢ ­N ãöLî|¸Ũ‘혊DTx‡}ų?j@såÉn‚Ooč5‰:#fCʀ/jba 7ü%f>îaHœ|oeúl^XAzq~¨Ī™âŖ<žIÁ߀;ä&šĪ‡oōh•Į…k€ĢôķÎXĨQ…t]ÎŪ9 =<Øv×ͨ朞^ī�Š:ØN- jÃ>ux[Ē ĖM�đzpõ^Žĩ&ũ 3:ĩßĩ—‡™Ģ6öâvõ^ž?Ĩ!­‚w`ŽĻč^×NĒ[}ÖŊKSoķ‹ÔO;Ā G¯SãöxŅϤ„ü$ĸR'†.^7Ã>P%¨C‚/Ö6ۜ‡ļĶĶ Ķ%€Ķ>ŠæsáčŨÄ}ĸĖZŧļ#ØŦŸāR¯ xšŅgmĻ•âÂÕōĄĮ‰3¤‹ÔętÄ*nH ¯WÍŦ‚š[Ëmx�Wcc —.|ÅĨ¯žō}Å ~ælŽ`ŦfkA,¸›)_ŗ۔}gëŗ4X} ēÚ;Œ˜cAąŅ´ģ7|‘¯Ēŧī‘šņ“IœFĖz5gĮ0ą™ú‰ÉŖƒžDŎĩŠ:Ė×|ČėŌŌ—°7mĩõX‡c1—„GôZŊžX|Ø÷‡OBĨˇ™û­úäŋŊQ‡ÚeĨÕŪ‹×įÃëíÅiiĻŠåÜW9éēmZZāōxņúŧx=NlN/Ē- ҘƒÖįÂjqâņúđy{qZŦ|ŠÖa˜é ¯‚ķ<N;î`ļVhĩāĀãIŊ&’™™€â˛buyđzŊ¸Ÿ`s‡ ÷&ϐ¨ ĶŪ¨ƒ^;­V/ ZŠw `Žî ˇĮ‹×§!1!ÅՉŨãÃįõā:ԊCB^ÜWef1ädĸvĄŠõNW/Oo7Î#­´ØˆÍ4æ¨Ķ0fĒņØZąš<íđ8ą6īĻÉ \ŧvŦû›iĩ÷âņúđz=¸íÜhĐ&¨¯Ŋ|réæĸņ`ˇ´˛ßâÄíõâķzņ¸ÚétĢfŧĖČ0ŽövlSũ؝Á}Ŋ›úĒ–ĀS,FPįė ÎėĄĨjü)’Ā${åK6W/_"ÅåfÔÖZ67Ûq{Ŋxzí´>û�æ‚gąÍjHd‘.ÅÚJ‹Ķ‹×Ķĩv-*ZÜ8]‘y§ŒžüŠĀČĪŗŸāôxĮFU-6ĩ™ōÂËíŖ-,AëąP×ԉÆ\2í[`Õ9%˜T.šęvcëõāvĄžĒ•áĐ{/9å”ëÜ4VÕbuzđz=¸Žė¤4;âÉįÛØms̏â–Ë…¯|9žââ…;îø6—æ2đ@ƒyׇÔķ[ۚ(7ĩ 5˜ČÔjĐāÃ;āÆaw2 €J[HũëģÂF&3Vī Āļ™ļ†Š]•ŋīĸŠÛ@uĄO…h0˜ĩ(õVŦŠSč$Ž”2c›°Zl( %RŽŊŗJ+Ĩ„RC#5$”oĶ„nX9•™ęl[)ŲÜIŠIn;–V'ŖlˇĐ#ĩ!ÔēU”ÁÚnĄÉ:ŒĸŽ%AĢŖ°tÚiÛQƒą¤,VŦ-Ή÷^$jWPjN ž=ÅĨ>ŦÖvZš,(Ō-.]q¯hÖ`,4ãļ´ĶÚhƒā|ƒB­ŸÛÂŅĻVÔåe×ŌOĖ)Ą`؂ÍŌ„ƒXtF ˰ßŧĖRë)0÷˛ßfáuÄju˜ĖĢĐųŽ0°ßFK3”Vä 3pX´´¸0?…9§Ͱ[S=6uZƒ™â\ŪfŦ­ģą”<EÉ,ßG­Ö­ĸŧԎÍáÄf ÔõÄ+ĶÍåa¯L׎(ŖDmÅjmÆîUīdŅ­ Ô|kdĘ*ŠÍG8jˇĐb ŧw$66}áø$×Z>šy栍Õi–cĩļĶÚ4Œ2ņĖsúFVû+7˛Ęeąī˙WJíU´ôšhhņTcŽŠÁ˜Ŋ•ĒŨ…´U¤a*-A[ÕLųšO(Ūķ¯Ô­ØÅ~jŠkØDAŨ0Š*­q ûkŽ|”ô:k~ĮƁgŠ/^BŊJ‹ąŦ†Æ]ZŦvę*ķ¨j<EŨ랏ļÄšŪņąĩļžŌŧ*/{lÜ?iĸnĸ™’”Zę\ ”6\å=°ęÔíŠĻzk#›ÍIÉÖĘ}iœX)ōũī ~ą–ēĮōį–æę=4–ĨĖÛĻFÚ~ŋßßÛÛ;ÍėíY$ü­ŋ™“t6>ņ#ōōōiN <ĸ:~KÆ?Ŧā˙ėáŌWĀãü_Xų‹#s’w(Ŋ•=-­Øė.<Ã>Ô¨Đës0”Rŧ"%ü$Đģ›s=î‚wč }ķį´o.­ĻdrgãÚ‰Š ‰ Ô:ŪĨdbąëÆ%TÚ|ÄN~ŗčtųŪHZ!|–'0TŲH,oÃZ=ÅlęÉ/ŊŅ(ŦÚÁfåEŒUŽ+ߐ*„âé–<6Ŧ/cåʕ4'O2uėä{ËîįāŌ…¯¸ üÜįēXų)ˆYąŋ¸Œ2‹–ÚŖīRōu™á$„"ânŲ[-{ß~‹îîn.\¸ĀÁƒ¯øZ.]ēÄ#Š˙ƒ3v+*õ"Jũū6J5‡¯L\ģŠŗ kŪAĄB!fá–ņ7‹ב#8:m´6[øõG˙ ‡BˆYšeG<ÄÍæÁöz .5ą™…ÔīØ%A‡BˆY“!„BDĖ-˙!„B|}Hā!„Bˆˆ‘ĀC!„#‡B!"F!„BDŒB!„ˆ <„B1x!„"b$đB!DÄHā!„Bˆˆ‘ĀC!„#‡B!"F!„BDŒB!„ˆ <„B1x!„"bž=_ û/]œ¯¤…Bq›’!„BDŒB!„ˆ <„B1x!„"b$đB!DÄHā!„Bˆˆ‘ĀC!„#‡B!"F!„BDŒB!„ˆ <„B1˙W‹Ûíž™åB!Ä7ĀDāĄĶénf9„Bņ ˇZ„B1x!„"b$đB!DÄHā!„Bˆˆ‘ĀC!„#‡B!"F!„BDŒB!„ˆ <„B1x!„"b$đB!DÄHā!„Bˆˆ‘ĀC!„#‡B!"F!„BDŒB!„ˆ <„B1x!„"b$đB!DÄHā!„Bˆˆ‘ĀC!„#‡B!"F!„BDŒB!„ˆ˜ˆƒû֓ļ(ƒÔEä6tE6ķŖ›y7Ķ؜g­ũ™,Re˙v˙Í.Ę-ÎIí}¤.ĘĨļc&ë÷đĪ…¤.2Rm›ī˛‰ÛÅȧ¤.Ę õąƒŒÜėÂņ5áĀŖ‡÷blü¯O?ā¤2÷š(GŸ%mQ>;#×Ė…Ķŋ4‘zĪ œŧŲ 5xĩ‹2(Úw;=�#í§‡™‡]îëv>ÖÄÍ'ûĪ7SdŽx¯Húyi@˙gŧgŸûĶĀY‡s"¸šŊôsēkhĘ%9˙ÔAΟĪđ醤— ”ŽSœ‰xŽ7JOÍ˙~†Ž/Q“ōņH7öž›V¨¯­Û÷XˇŲž™"x(œÜ÷ũ@jū&ļĨŖXß˙|އ29Ķu~NSŒĨ›ŽĢEūLj•$L_W7Ŗ7'ëvEUõ88#=Üģ5q ũį›*rĮČ Ū:6¤ņĐętæ>LVŒŲ> mpę¯ôí{„ÔEĶî[ļ|ä@Š‹rØâ�8ĮĢsI˛¯˜Kĸ9ųöŗŨg Ėû¸ÛDîc/đnĮ4aĐ “wlĻč‡&ôwgzw†˛ˆ�� �IDATŽįŠ†ÃœĸpíĪI]”ņOtōĮį֓{o`žFÚ=š=ĶÄÉÁĐõŗHũ^%‡Æ€ŅYˇ(|ŪÁÕæxŒt¤ö™GČŊ×HÚĸ,ŌîÍgísM´÷MUk#œ9đkž*ÎĮpO°<w›Č.ŪLígxxļ‰üEäíę c[^āž÷¤92#]‡ŲųĖú`ūē ¤×5àr|^F>;Ī^šôä/M|īęT?,Ė u‘‰­öĐ´Æįx˙.ie;VEúĸ RīŽ mrõ¨@9{˜Ú'Š0ܓAęĸ,ô÷ņdÃįLY•WĄôįĪU?ž-ĘB•všŪũe†Ĩ ĪÖLõEdßs9­ü'ū‘wí“q°õžĀ>ˇáĀ8¨Y>ŗcí:ō‡‰ũ-õ‡¯pf¤‹wŸ)Âpwi…MœčkĻhQŠ÷ũš3(œ=ú OæG#†Â ļ蚞¯¸Ūö¸1×ql…tđî/7StŸ‰´ģ3HģĮDū˙ȧǪX_é;Ξí†ûáÉ_~0Å>å úžāą8åEƒ­÷^š<°?d î8ĐOû›—ûÖ´)ú֙÷Õâë(bĮāąfÚGũƒ,’Ā:cŒâŊs3ŽJŋŸ¯ŋŸô(€h˛V—ōøú<nˆ›´âíĪŦeCƒR—`Î]FzĖ=ŽŠyŦ‚?N>ųũ€ ĢÖ˛eīœQîdiîƒŦË_Fōˆ“CģĢČ_õí“`•JŒĄô}ÎÖŌõė<>JrÖrČÎ$^9OĮáWYWúkNûŧäü2_I4@Ôō֗ōxÅFōR¯žÍgl&wՋŧu¸%IONށŒ˜!ė_åņŽggGh§:BûsE<đ| ‡ēF‰ĪZÆĢīĮlŒCéú‚ˇž_KūsĮ/wņzÖU”N”!ÎPÂãëKųq‘ž˜ā*ƒGŸ%ŋ¸Š7;‰Ķc^]ÂCĻĐõo=˙š?=<ƒ‰ŧi˜ ŅĀ9N;&÷„]ØÁÛOCNl“ûĨļ z yYLáNLë7˛n|HZÆãëKy|Ã, QAOkK^āÃūhgßOž!†ē9ēģ’ĸĐzšĨĢ™ĩĢž¤îā úH#gåũ<´rŲåvYĩūŠ}ėz÷—káäŽĩän|™Ž“ĩœ‡ysēŠžã­Ô”>ĀÚˇCNŌ1j^.!‰Ql Ûą†5ƒÂéÆí|ÔŅšÛ¨/ŠŸÁąvų*!0R52D{C%5Į†ˆĪZÂâÔ¸ĀįĒ`ƒ)ŖœŲWAŅĻ8“FNî0ĻĒę>Á{ĪoāÉIĶ´ĮõģÎckĸlMŦũájŪ˙‚ŗĒ;YjúKSŖ8{ŧ•ēŌXģ¯gvëwŧØö÷OЧJÃŧē„uĢ—ąPuŽŖīogŨ×ķĮŽŲ^*UQ€2zžļgÖōxŖR—aÎ]ÆÂ)úÖ÷ÕâëÉī÷ûĪ;įŸ_}ū?ŦI÷/¸KīäŖŋN|:lŨäĪŧ+Ũŋ`Å.˙Ÿâ[yīa˙‚ģŌũēŸ|ę÷Mąü¯ī•MąÜîņīĶũ îZé˙͙I_°nōëîJ÷/øžÁŋ¤ā%˙Ÿū˛ĖwÆ˙›ŊÁ]éūeu! Îø_ ÷/¸Kī_žÅæũŠß×įß˙“l˙‚ģŌũ™?9æYô§šāįß7ø—o9ūŊŋŧīäģéūwü•í![öoģüËīJ÷/øū/üšTô˙ōt l+ßę Y¯õwĶũ žģÚ˙Ģ“Ąšû˙úՁm ­Û3ÁôŋģÚ˙û3á5ęOëŠzķųü$÷ę÷úžsy;˛ũõ…ˇŅ_ųĢūs°Í'o Çm“ųôąIéŧī_}WēŲŖåūewĨûWŋV“~_ûĪũ™Á} Pū_ũįt˙‚ģ–ûu:$™)÷ŋßī?ë˙}A œ™ß_î¯øčlØōŋúšÉ]éūß-ķīĪzē-ņ[~¨¯%OOjw˙°˙ŋlY>wûËÕJa Ô˂ī—ų˙0Š­‡Ožä_ųŨt˙‚ģVû?Š­˙Tˇ2Pö-ļ‰ōųÎė Ŧ˙÷›ü–°‚MŦŨPū}ß˙Č]éūßÍö/YąÉoųˤšXnđgūũät‡ũĒ Ô킂Ŋūŋ„|~#í1|¨<ÖŖÂ>ŸÖ[ž˙¯VÚuõgÂōųkû/‚é=ė˙Ã_n|ũƒĮāĘ:û¤íøĢ˙ȖņúÚí˙ˇ‰ĪíūĒīOĶ—OŲæĄ}뚗ü ÍlÚžõ*}ĩøZ‹ĖˆG×ŧįĸ—ą.7~âãSæ8 įãy™d:­Ņ8Öüæ,ųL•Îēĸ4�úģz&ŽL[ otA܃Ôũ*›Đ¯ JbÍöMŖ`ÔÖ<å-ŖQÕrę~ĩ<ü{É÷F{ål׍?)rúũf:Æ .˙gl1Ƅ,‰aqåĪ(HŊ“Ô¨N_ÍÅŨOÍīØųęMŸĄZø ë˛�Îqēkfcú§÷6aƒčÜįŠ/J ŸWŋœšî'š1ė{?āZ”1†lGÁ¨ãxØDÖĮqÎÍâĩ˛8Î?vÕx渓QĸXœŊ„˜É‰^—10ūœúĸÔ°íˆĪ˜œh`ė3j*‹+vņÚË;¨¯šÔîĐŗv9IĀhĮņ)ëdnö—~>ÜũŖD‘Uõ?šÔÖ1ƟSSt'ĐÍ[ûae_Zõ§ÂĐÁíė´+@īū˛™Žą8Øž‚đ šãüƒÆ†H~ä$O“ü˜ÂŠÉ鯰4Xˇô893ŅĖŽ=fėŽ-ÅŪˇ=@ԃÔ<™ļ˙ƛ~ÆĶšw’”<F—sđÆÖˇ5ĶÖÄ­¤ĻĘ0鸈Į\ĩ SĐũīÍčŅķcͯ~ÁŌĐĖĻé[Å7סį? …“û>ĻˆË}sØi`]ū|´÷<mûNPc\™ų“IËČKŋōãø¤8ĸ€1e” 8sĖÁ(ŊœÅS.>–ŨéÄŪĄđh^øJ҆û§ø^ ņI1Ā#C7põpŌ~ˆ"äŋ˛ŪTŲÔ˙ËąIeM''tÕGƒÃŨ Š* cdd&ŗ0/įŋ8Ų”'ũãrG}†­ĮÉéAXxĩ“Vü2ōRÁŪŨÉÉŗ°xa L§m§# Ŗa ¤ÃĄŽœa%KÃʐF^öŒÎˆWÅâÜŠļ#šøx`TA™QŠ"Ų¸œËįL…‘‘‘Ëu:œøÜĮŽČm.ö—G�i<=ÕSP*įˆ~˙c†:œœÅ‰Ezļŧ\J{I īm{ã#Ũėtގruy3ŦãŲäĀ–¯öôV*ySĨ—J2Đ?6ŠĸŦčŲĩĮŒŨĀąš!Ú°ŒÅW&HÁīQōÉõ¯*¸ūt}—S:؜į9Ķ5Yŗ=†¸ŽžU|sÍāĄœāŊO‡€;1¯5\q‚\üČäî}•žc`YNA$öȸ$Ļ:Ä&ĘĻ\ūĨ¯?0ŋ@qļPũĖĮS&780ÆŲū! ŧCŒ‰šF0uŖZ ŅĶCrÜĖ+M9û9¯56Ņvŧ“ūY=Ē2ž˙g÷mįŠŖS¯˜ßŅOW?LYé’XjZ�Ũ=؝#üda ЃÍ1 Šz–ÆĮCÖpæy,MųŠËX:ŨÕņŒŠˆŸ˛UDG~›yˆ8Čé}ŋãĩŊ_p˛ghęž&ą9Ų_úΏæ=ĪĄ†g™ōbvô\pŨnú ėįĘú9ŋ­8EŅîž~ ˆģŸú_-ŸųÉb–ųCŠW}j<Žäkœ#ÃĢ÷ÆÛãz\ßąušo‰ŸÉ#ō7˛~ ”ŠOžsš}*Žäø@@Ô×3Ä5Й™qß*žÉæ=đüôŦŖ�ŖœÜąžĸ+Ö^iŒ9xëĶ~ ÖFū=ĶSP”@75Ös‚CטĢ 09đ@5?EC \Õ51įîšßčjfíc/Ķ1 Ņi÷ķxžôähbĸT€‚}wo9¯7čw“žŪ#3čp2˛—ˇû§mN”ĸlT}§8Ųq, œ˜˛—ˇģ•“ƒŌq‚3cgXFÆL‹=īą>ŗ–>Qwb\Ŋ‘ŧŦÄĮŠūÃÔžt•úš‹ũEQ‚ũû‡?›úIJîČÄčĀD!ČX}? wwĶÄe?>t>ßųGŠŽžOGM.īÕ˞=fčú­Ë}‹*zF9Ėbũé*K5ąģ)ĘíöĀŧ¸ÍsāŅOÛ>Gđ c”gįUÖŖcßaÎŽ-Ÿtõs5ķ>ĢPÅŽR+>âXÕcˆ7Š˜`htF'u¤­á:F!.ˇOŋrŌ•‰ŖxkÆ#0*T1ĀPëZlÔ¯¯ôSĘĘfit+‡œ'8C6 '8K9ŲúĀōt‹ŖZi?~Š‘ĩ+9{üŖD““ĢŸƒĖįHĮjŸ‡¨4~ŧ[&ŨīįŦ“ķ]†ņ§Cĸ~Ā˙íuĖ×}˙˛‡?nû]D=ÆĐÁíÔ}Jq† Í:˙9‘ö¸‘cKELđö‹2:“øú×īģĻ__!›ŸĒš´ŗ5ŋ“KĪ~Ā{Î1ˆZBí=CĪ—ĶüükyŅ\1É)txnĘ÷xôOũ–ĪšŖ"9)đx×`ßĐ-vĀ%‡ŖGéë™f2¨ĸ L\}žÃŪ1đŗ~rĮĐOGĪõÜöI"=`dbHwÖTK0eEAŋ“Ķ} 'mNÆĐcĘ î 1zŒé0Öq‚3J';† j yY7ķĖޝøzN˜Į'Ÿä�Ĩ§gū˙Ÿ ¤$GcįéģîwĀŲˇ_`§cŒčėmø} IœįŊįÍə6ķ,ķŸK‘i9ļTÄû–žžķĶô-ĄĮīõ¯?ŪwõõMˇū}}r%§ŪZ´@ZS}edˆA™*fi…Ķ{ĶDžúlø˜åŦˎÎĶļĪ1ąÃĢTņŨąÁū)&~õĐn›˙ˇŪedˆFŸMķ˙Ę(œą}ÎÉŗ‘>“Xœč,N;Á•ũģ“­?\Lú÷ō¨ĩÃåĐ-Š˜)Î͊Ŋ™ļņ[IĘ4!VØĮI,5Ū ŒqōĶĶLĖëáäQgg˛Å°4; čÆîp`wŽæoLė;I,Íē3đ>ŽSØz *+û:o\Įēŗ5ÅŨ€~>Ü{bū_˜4Ũ:6ÍM„>V[W4ÍŲfĒ:‹^FÍöÕ,4ūœúGî„ūVĒwĖđ]&ŗÉžĖk{ÜØą•‘­'ë8<Eßĸ`}ÆDú÷“˙fĪėÖw|>uß5x‚Ŗ=� Xl?ČTÁmepŠÆąæ´ŧXĖŌüŠƒ÷Žĸ1?2õS—ŠXēv9qĀĐąhön1é 3&ē?ãŊ°—ÜŒp˛áŪęŸęfįøũá!úæā„*S)ĻCŗõ—ŸOē:RčûôžÜXÉēŌ—iŸuvŅÁ‹ū]).~¤,đö×ã¯˛õ@Č9u„3oū:đ(]Ōũ<dH%=ā<íĮÂ_Ü4ŌÕĖ“ĪŸ"^¨Īđ‘¤Ë÷{ÂG˜2֗cŒ‚ąã/S} įŠ }íŋŦdÃĻ =xŠĀhjÉŲËIeŒĶZķ; ú°[o ŗ DsŽ“ģ?įĖ,Ė]6Ŗ)qã#Écƒũķ:⟚x \Įį“^*׏õ—•ŧĄ¤‘ 064ŖIŦŲđĸŽÆøįÉoãq°ķg•üxãZž {áT|ū:Æĸ1VmcM2@ KĢļņPôÜN­-4­éŽĩÍîEĻ=nėØRËX“ }Fí/?;FFė¯đÚąQˆĘd]nę,×?ĖÎĮ¤ ąŸļmŋÃ>Q†<>q- +5đčvûûáy(gRũŌŠĀ›91ˇ}ĩ¸}Ėۏ‘cĐ6Ä-į!Ķĩ/IUƇ)Hú˜ˇú?įŊcƒ˜‹âÃŖú¨svķFqíÆL’Uc v9čP–ąŗ*‰­Û>›t<púG9ô|ƒī'AŌÃŧy=ŗōäķôīļqĻt;ļƒ•äĪdŠ!•xÕ}ÎNė=Cĩ€u/?OÎlGü“ĶȈƒŽĄSl-y„CŠ*bō_âˇEĶL¸]XFũ‹'XģíGŸĪ'{ˇžŒ$#=N:úG÷ĩ_Ũ|ô.‰‚ ?āĩŸ}AĪî äw-giŒô8iw ąđŲˇųmüĢä:O0t`;XÆkÁštX˜•‡;éŋ’üž4bHãéw~ÎŌ䇩˙ƒĩĪ}ÆŅį‹Č~ÛĀŌô8TĘg熯 éÔo_=ķųō ä$Á[Ž E^vøü UÖG}Œíø `9W}ėō˛˜t=Мĸ§ûwŦ-<AFL4Ļ_įGs<mGe,åŅÔÃŧŅķÕĢáhn1ĘgŽŸ Cĩœ7Zʰ—ŽĨ§˙¯ũôYēräé'ŗįļØo:6đäŪSԗäņ‘a I10ÚÃi['ũcmØFũÚ˯Æ=ûæ ėtŽeøõĄ“ŧc˛ŠŲ~?í›>ãŖįˇ“÷/ģ‚Å_åXģüįÍļĮõĘ7zléŲōęĪ8ũØĢtŦ$ûx‹Ķã`°‡ĶŨį#ã Ûxt<(PŨČúĪsæą—ąīŨ@öņeäî lŋãũcD%=Čo_=>cČYŋ’¤ãĶŦŠÜûšYœ‡jä§;ú‰ß°Gm/ōF÷\ sŨW‹ÛÅŧx´í a&å?<Ã˛ž‡ō�cœÜ÷YđĒ4•ũa/ĩĢ—3B×ņ/hˇ÷€~oĩžÎšT1˜z?2†‚í/ņ>ލŅsØíŨŗÎU-|˜ˇ˙å#vVÜOFĖyNû˜žāĖH4ÆÕ?ãOP7ƒāęÚeŗååRŒIQŒõwr˛Ŗ˙šwŽŨͧ­ÛX—ŠjĐ‰íø Î(qd­üo|˛-Y—ËŸŋ‹¯–bJUŅwücŪ;đ9§•<úģ}ė{2äüŸQ“ģ€čąsœüô]ÁK¤äĩÛ¨]š€č¨!ē:œ„ŪUJÎßŧŸ4RŊROːëÁų蘓Á=T4pč“×§ԔÆ_Ÿ„ÎīŖĮ8~ފ[2å;Ļ”ž‘úg—‘=ÆPˇ“Ķŗ{–xzĒtļ´ėĻze&ņJ7‡ŪoĨíø9brÁG­ģ0'ëųņöR˛âĸr~NģsžæŐķâ>Žíyž‡˛“PēNpčāĮ˛‡ôđøËû8öÎ×_;‹ĘdËö‡™Üd1yĪS“ CŸąuâjûjĮÚuæ?_"Ô7zlŠŌËŲ÷ɛl]Ŋ„dú9}ü§ûĮH6<Č֖Cėې]˙úeėû—ˇŠ}d 'Ö÷[yīĶSôŠô<đl#Ÿ~ōæIcÚÆžßm$/íNėÄvė§G’0˙jûĒ–‘4ūæúYī¸sßW‹ÛÃ~ŋßßÛÛKJJĘÍ.‹B!žæ"÷ŋĶ !„âO!„BDŒB!„ˆ <„B1x!„"b$đB!DÄHā!„Bˆˆ‘ĀC!„#‡B!"F!„BDŒB!„ˆ <„B1x!„"b$đB!DÄ|{ž>÷—Ÿ¯¤…BˆˆZđŋū/7ģ_ķxH# !„b2šÕ"„Bˆˆ‘ĀC!„#‡B!"F!„BDŒB!„ˆ <„B1x!„"b$đB!DÄHā!„Bˆˆ‘ĀC!„#‡B!"F!„BDŒB!„ˆ <„B1x!„"b$đB!DÄHāqÃlŊ7ƒÔE¤Ūûí#áKOīČ%uQųoöܜâ‰[Ėg?}…§ËĮpwŠ‹˛Hģ7ŸĸŸžB[×Čĩŋ~‹:ĶØĪĶžųüfåē î[8~īû5§ova„ø‘Āc. }ĖÎ}`ˆé Ōū\ų?ÛÃ!Į9†Æ :ZCįč8‹×˛ÕšācäĶͤ-ĘggWIJœ.vū0ƒ´ŸFšŲE™/}Í-Ę h_˙Í.‰7•s¤k÷+´ŨžŽb}™§žgŒh˛*ŪÄöįœ˙ÍN÷ŨGmî0vŽ÷ž…“9ãŽĐ~āc‘Čęzt}LÛ×<vī;v˜Ž›]!nxĖZ&yŲq0ú¯5v]õjmĐŪÄSÅščīÎ õn#šũ#žŊüņĄß´'röč¯Y{Ÿ‘´ģä>ŅÄé…3ûž%˙Ū,Ōî6‘˙ÜaúBqōîsëÉŋĪHÚŨY 7ŗĶ68_-flļŊŸ3 De?Ī›UŲ$Ģ‚‹âõ<úę.ĒW>Čã•ˉ8ÛDūĸ RīyĢŊ‰ĩ÷f‘öÄAF‚i|ķYÖūЄūî,ô÷=ÂSo; ‹w•ÚvToŠ‹2Đß[ēo:9ȆEKyúøpŽ7VeZØ܏úŽžÂ“ÅųîÉ"íž\Ö>w3Ą;ôHī>S„áî,ŌîÉgCÃįôÍ(XáôžXûCi‹2Hģ'—ĸį>āt°ā§wä’ēĒ…~`ėX鋌<eģÁęžW/?�J?ֆ́ãoQi÷˜Č˙é¯i;Ģ�ũüą0ĶK�tlË#uQNyĄō9OŪAꤊŠnit|@õcųîÎ uQúާzŌū œ=LíEdûÜ'ÆË$ÄÍ#Į0Vl$+ zöŊJÛtįúŽ&6<ņ*‡œcd•˛ÎMŸŖ•-ĨÛiö*UāŒ4Öŗ‡§Īą0[O<Ŗô•§~ZÁS{įˆĸëā TĪŦ‹~b=5OҎœmXIr˙ŧņĶ ÔvH'sS)ŨØģƀ(–惋P*=?ų§—¨Y›ÍBÜuōÚļ=ô%Xš(œÜąuģ>ÃŽ, `ÃÃ,îæĐKëŲđöøPÁÖį6đôŪôÅ-įņõ÷“ŦtstWOžŨĒ4XŋŒ$�ĸI_YÂãEiÄ�#G_ hĶŽvÁâĸ2͂Ķ_díO‚F°ūōIjw3¤J%' *Ûvļ~:tÍ*čÛWÉÚmcŧ“‚õĨäÄŅqp;ž?Ė Ÿũ0čŖ+'-cŨú‡ÉKšU­ĪŠk•N7ŦįĮģŋ /z k֗˛ÆGßąž.ŨNûH4E%ãéÅédŨúûÉP]%Ķk<ČSOlįŖ……ų%<ž~%Š“B÷‡žƒ<YRÅ[Įû‰1<ĖŠ0r|ŧLŗĢ!fãÛ7ģ�_ I˛Ĩ¨™uīŸāĩFŋ2\ąĘYû)”ä¤ëΛŋZNŒb`đŪJŽāPäņލ?š‡žØÍ’N’Įē÷‡čīPØú/{ųQ˛Bûˆ‰ĮrúX'JŅr8ē‡7œc}?õx s (ÃÆĪxˇņsžūÃJb"Z!b‚2Äā(@ ÉI3k…Ānpr?âXUzāīÁƒŧļīp'ŋē›š,ŦOcÃ}/bÛŊ‡öĩ/‘Ŗ8ąõD“šē€‡~ŗ‹Ÿ¤ÃŲ¸nōvŖãĶ nx˜5/>Œmß úĮâČyōŲ’ĐÃ?7~Ɛ^ŲțOĻWø�õĮ÷đV×jļÄ}Æ[Į†€8zuõ&(ĢŠ]ĩ–ˇŽ{ rŌ>JręVėĸž( ė †ŌV†l‡9ŠŦ¤ĀTÎĶ]ŸqČŲMTúƒÔŧ¸’Ųœ“įÖ Ę¯ęį¤ũ<p'ŋzē,€Úˇ‡“J1J ‹7üœžc­Ø‡ šhukgYuœāä(`ØÄ›ŋY8ž×/įŸßī!&āĖŪßa…(ÃķŧũỎG!O•ĪC{?æĩ›ČŲp EwâE9ÃԊrŒļc?đ Vė%cŌ 7뿨†Ë+DŒŽ02ųę#ÕĀŌd� Ķī† u99ãŸe%ÁánÆFFP€ŗ'Ŗ�Ši$+ƒ ņzō'8ÍJræeģÅLŒŸD¯oė)V§_>wāĖ•JzÜH Iba2ØzœØ{ '=›:KvH 1ņqĀ9PF+G\�F߇82RU Ø÷â û'íũî 柉É,•JΆ8ŪęšZäΚ:šĐRÅ% öũ) u̘IųãX˜ Ũįyī‰"úr—a2-ciî&~?O!TjÉ@c;ųÅ'ČÉ]†ÉhāŅĒåÁ‹Œ~NÛΐœžƒƒŅĨô4ĸ8Ī™ãNF6$ɉ¸)$đ˜+ɲemííäĩÆÔO>ĸû>§vÛ+´ŲO5\UTôåÎøĐģJōYTØęĘČhāįĢ<đ¯†§5ÚĪāHs“ÄÄ ŒŅ×= M|Č Y .c'ØōƒÉad?}}@ú§ßŪNíŪœéųŌ‘QFÆ�†øhSMZÜ×3„’rQЈ 9—ĒbĸĢßn´7ąuĮœė9Īč-7ĢõÚŽ]ūĖÛøą˛?īÆv°ÛÁ=@4é̎ņæoV’<ׅZ¸‰7_âŠĶåüŒ÷œŸņˇŒ§ßĀO˛F&ú™žŊ0îŧQũ "Ũ‚¸9$đ˜3*Wǘ*l÷˙ˇwīņQÕwūĮ_\2†äĮe¸˜XK”rQäļ*XA]ZŠ+ŪhĢnģ‚î‚[…ļ*]ŧŦŽHŊĩ Ũč¯@Äåļ(*ĄÔD[B*„[&Ā&¤0 ŋ?ģ áĖD^ĪĮƒG’sæœķ™`Ūķų~Ī^ŋæčw:…üâ{ãxŠ F¸īyūŽž´L.äĨī=ÄüĪ"˙ü#§Ĩåu ĶĮ÷9î?“´ęšŠ“z]âÍ1>œķ;J†8ö ~4—‡o·‡ōŖ‰cOŲ™JN “ ÄB=¸įĮæ$2�� �IDATįwĐë¸ßiËlˆ.„ģ§üŽH(‹īūä‡\ß9 Ū™Ęw_Xúͤ…€X˜ãģ;vzÉÅiÕĮ–SåH§Ŧ¤üôû.™ËŖÎōX*ŊF=Á=CŌI+™Ëß˙5uâ"–3­ŋe_~4ķ~TVȇĢķxÅīx}ÎJōįNäĮũúđīCÎöĀQĸGĩČĘʎO<ÉdŸÂ‚á“(ÉĪåũÕ+Y<gķ Vōøƒ/Ō˙­„O›ö ģyZįÚCŌrrimj9˜}'(`ņ;›>[ŨÄęĘåŖîdP¯. —ŗåČ˗›�zqĪRĘŖ´ė՗ūũúŌ˙Ō0Ņō$§ķUAKã†Qƒ ąÜiÜũØQW‚”åņ‹ŽįõÂųëŠO˙ö3쇀X9tŽų÷Ë"-ZN”4ŌŌ`cnAuīĄķPîŪ—Ë/Í ZRŨn'V~Ü߲š�–E¯Î�e”%gÕėģ/™ÉՏIKKƒėœęáÃX‹ßs¤loæ~Nr.Ŧĸ!‡oß;˜ū—æĐ>ļ•SÍÁŽ•Eë>gR´E/ĪāĮO,aKZg.8Œ1˙ōO 1ļÎĸe§{†5C°Dø¨°æ(Ņ\æ¯8ö<—Ŧ›ËsOLåWë ev_n¸mOũōAzAM7#Ë˛ÛPVžÆe5ŋĶËĶĄ,#955æŅč|cĮŖ–]|×Xžq/‹ūŋ&š™íaqa9˞ĮĪr“ųpAd‡!/Âû/LåW-ĮrÃ<frŋ;¸'k üšģo*į†^ÉlYąå…1:zƒŊjã™é‹Jî÷ OŨUĀm/°î•{é÷JˆÔÔdĸå5Ã!Ą žû“IôONuĩAËÁÜ?üYž;Ģ€įoɖk˛H.\ÉosˇBÎYĐ/‡öŲ鄨DŦāU~,BfņJ•´Ŗ3 —0ũ‰~4>LZK 8Âoŧ‹hߥÜ˙Đ`ūöŪëxéûŋcõcßáļuWĶ>šĮĸ…뉤öágŋéËÅí¯âģ}§ązE„ų~‡č;9DķV˛‘šnÛŠ†PŌ;Ķ>‘X.Ī?8•Âđ&­(#3Öįņúc¯Ō~ü­\Ü2•Ëũ9ˇũÃŽŋmˇ_ĐKcņ<îū›%'ŧ'gŸ7&žIũ=Ø8įY^*€Eë3čŌ0”°lAČ ¯t JZ¸ú\åŋ0žģ ¯ãģãī¤˙ ķ[˛č×3•×–ŗúąģš/ŋŦ[ÉÆÔ09rž“KVōŌ ŋ#ōÆ–éAfr”-ëŪe5Úëj.Ōîē“^ aõ;ūŊëčŽđū‚wÉ/sũŗķš<ķœžUé”ėxÔļ´ĢųŅ]YĮ-ĖæžŸ<ČĀΊD –đĢwŠšxâ ŧņ“;čQž˙.‹ ?§e}:É،ųå+<<Ŧ+iÅKxũ•…|Íâúq/ķÆC9žŗ‰ģ4.˙īŧø ßî™E8ĘËË!œÁĨƒīāųßĖáŅ~Ÿ7ڞĖå˙ō/ģŠK“7ąhÖ¯ųm>\6loĖŧ“L mČ$žŲ•ôĐV–ŋ1÷CCyjæĶüĶČ RŲIJš”Ã=ã¯Ŗs*”æ˛,?Bh9p o<{ ũ:ĮxîkŧūÎVŌúū?ûõĶüm{€–Üđ“§y oŠŅM,[‘×<ÆSŖ2ĒˋžĸSŅ~˙Ëud‡Ąpų<~›æž_āŠņW‘Š‘ŋb!•CËkÆr{Ī0!"|´"Â@/÷,'Rŧ•âãū–”=Ŗúŗ3ķœų yé…yiAd_ĮïŊ\såP2ũīKŋô”đ~nÁ)2fƒzš{úf-`Ņ‚5D‡LáŠīԜgĒĪsÚĀ)ŧņķ[č—áũ9¯ņü+ķXVĻߨ'˜ķķšĢØÚāå_>Æw{†)[ūk^šĩ’’ô̏įŲ7x| ŗ;?õĒĒĒNJŠŠčÔŠSŧk‘$I_qv<$IR` ’$)0I’ƒ‡$I ŒÁC’$Æā!I’cđ$I1xH’¤Ā<$IR` ’$)0I’ƒ‡$I ŒÁC’$Æā!I’cđ$I1xH’¤Ā<$IR` ’$)0 ĪՎ7mŪvŽv-IR 2:´w _v<$IR`ÎYĮ#Ü´ÉšÚĩ$IĒŖėxH’¤Ā<$IR` ’$)0I’ƒ‡$I ŒÁC’$Æā!I’cđ$I1xH’¤Ā<$IR` ’$)0įėŗZ$Iįˇ÷ˇ4äÖyÉDūZŸUņ­Ĩa=í¯ryûņ-æ<gĮC’TëŪßԐk՘íņ�Ē`ûŪú\ûĢÆŧŋÅ÷Üņdđ$Õē[æ%“�yãXõ ĒĒē6ÅOâÆžčVÖ,ZĖ[Kßįã­Ĩ”ĮB¤ļčÄEWôãÆúĶ5ė_IJT;+ô}m=(ũk‚ÖvžHČāY5œ<›ĩĨĮ­(úøũbūķ…ļôžëŸøņß“p\*ŦļmßNĻũņ%>)ÛÄļ}%qŦ$8mˇ¤KZcŋū÷´mÜ*ŪåHJP ×í8J" ũœĪ,öEŲôŸ÷ķw÷ž$t-ļUĪ|[ÆÍ§8°ÚŽĩmßNn^>Že;rĪ›Đ°m_ ËvärķōqlÛˇ3ŪåH’ę˜„ ekÎ÷ĻžĪ ™#tķƒķâë¯1ûåã§ßūß�l]úSž÷ÄĸW Oūņė=đ×891ė=đWžüã/â]†$ŠŽI Ą–OøÅÔšl=ayūfōsüëĩ-Ž,iWÔŪüŖč͟đßū%˙7#ĀRJ˙ėПʊâ]‚$ŠŽI˜ā];‡˙Wt’MŽāĻÕĄ#ēiĶ_^ÎÖO>ųl}ėOüęÍOøŋãģRįa'ëv´ë2%Ũ˛OXũËD.}om-WÅ¤Ÿbdųãô|w1åqØīų4Ä$IĒ <ļž÷Á‰C,�ŠÍI­š€eũ?įWķw¸í> ˜.¤ŸĶ ĪTŒüM3™ˇûŗ%ҞÍņ+G’¤’8Á#rēŲ¤5ĸą“/ßVD)$Lđ(øË\^Ũ~âšÔ#˜Ōk}͚­Čcîš)üdûn …œ.㙔ŨƒėPŒÂ‹˜ēúyVî’:3ĸûũÜŲ6“0ÛYąá &~RpT'ĸƒģOįû™$WŦaÚĒ)ĖŪS õ;p]ˇû×1›ô1ŠKWņ˚éĖ+¯’ČĘŧŸI÷&;%D´,šyOņ“â'ÔÜ.ã1æöĘ$wÕũÜûé‰ë%I: 5šô-†ņâīW“7,]kõxd yŋ_ÍėÛ.8öąĄPāåNj¨5íR˙iF8 ¨ß•ą}Fs ̎x"sc9Œę=š>õ!5}</tëŏsëŠŲ7Î3Ŋ‡ĐŽ$út›Âä RXąú>Fåm&§Ûd&´Júė`-Чb6×."’r%ē ĸIää<Æ´ ;ŋa2ˇŽ˜I~Ę ĻCúšū�/ôDZd&?xw 3ög3ĒīdîhzÜķhq;ĪtĪ!ōņd&:$ÅÉũŖĄęQØ=.Šw1úŌĻãŅŽS[āOgđČ(EEێ]ÔĸíÎEQ_HŽéũ×ĩ¤0o4Cō72ãŨŅŧZšMû+‰ėÜΨ‹:J"9Ŗiä35ok*a͞d5ÚEŒl§7‡Člf˛•š,šäJŽÎȆ5ŨŸŠĨ<›ŋŠō˜‘q=S[t'§ūfzvlģg2í“ĩlŠ?žžkēõfpø9Ō2zĶō`ĶÖ.`e%ŦĖ[ÄȁÃÖą/îŠ)ēŅ�&÷îAįŨĪ1ėÂZœC"Igg@ûę¯MÛCđq\ĢŅ—•0Á#Ŗ{7Úņ§ĪŽj)]ÆŖŖ>!Üîzž:œ  āõûyüíRļ~rėK§Ũâz#ącÅČũh ¯–~ösYŲv 9ŲŨĮ„ŽŲ„C!ǧ­ė&šABpp7‘Ęę-"{Ö˛ ~R�áŅ,ŧi4�É €”Ö¤R3od˙æš{™TŲƒMHkԌpØŗ‹HMåûwĨá¤Ļ'ũ;(ŌC̀šs›Öƒž�õŗéœ4—M‡+IŌ—0Áƒo įæKæōø†ÃĄbEvQTzɑwÛåo`í†ŊĮnęÆ˙ŊĄS€…~žÅ;Wąä¸9Ą6Ŗ™raÅy÷08ŋėîŗ˜u!Ā^ĘöĮ A͐L%´k5žŠģYąq7å1 â5FŊˇ”#אÜE9™Õß7jN('…p(÷Ųŋ›HėčuNiN2{‰Tî!˛?áf¤7öZĶˆTTĪ7`÷L†­mƓ‡3ö’9Ŧø°€S˰‘$éŒ%ЏNÜ4q$Õt߸÷š)qÚ§Ö ŠēËҍ [āŽļM€fd5mBî§k(#›;ģ ¤O›aLžâ&dt�ōY¸}4ĀĐĻÍh™:„ }&2ŽUķĪö›2„ą]zĶ'c wļ‚hé*ōå3īĶíĐl˛{Ķ#}ēdBÅRæE*ÉŨ´Šr¸ķ˛!ôi՛ą9ƒH?˜Īŧ-G]}Sš‹âŌטļe//Ͱ”`O—$éĢ)q:@ōEßįßĻ”2zâŠ>÷íuˆ‹žũ3ūíģ¨ėˋm›Í´m™ŒŊh /´XÄ#Ģ'ÖûwÍÂß<ÅRÆ39û~fdÄ(Ū9‡qĢĢoĻļõƒÉL­?wô~Š‘ėbŨ§3ydË 5É@tįRV'Ûw€ŠĨL^ŗ˜É{ˆą îc\öD^mŖ¸t>c×Ė$īP<ģÖĘtÉf\�%eyĖXņ¯ž0‘Ŗ‚%y¯ąŽížßĩ7 ß[å\IįN#qtJ…ŖįēgžOŸˇ ‚ˏZˇ§ŪúōöX§ž”zUUUUEEEtęÔŠVw\^ūÅ_ĸĘū0‡G'?ËÛE{Ąí^Ģš˛eÍ¤Ģšcū^hōŋų?ã˙™nčBZí•|Vz.øvœŽœXr‡ŧī$% ´ŸĻžõ6ô…ĸkĪūXëŪ†ËVœŨ6e˙xv¯QŠŠg˙|tr Õņ8,íÃųŲ›ŗv˙õ‡Tß,=ÜûVč} 3⛤'ĮĩDR6ĻâĀžøgmՅ1.IR"IČāQ-•ŒîדŅũŗ%׎"ād9Ĩoå;ÖÄ쌏ęōŋåˇ!éĢā/k`䁇Z†öĢžŒ`Ūr(8jŨáĄÕ <Û¸¯ßÎīK7œˇ]”†÷õÛã]†¤¯’ũ0ûŊg]Q3ĪŖ^^ŋ ŧ0ÕĻēĒĨniÛ¸¯÷›FŋÖ=ÎĢ!‡6ÂôkŨƒ×ûMŖmãVņ.G’TĮØņøÚ6nÅŨŒw’$Õv<$IR` ’¤„ļtKõ×=[ŽXĒēÉĄIRB›>ĻĮģÕ;’$)0I’ƒ‡$I ŒÁC’$Æā!I’cđ$I1xH’¤Ā<$IR` ’¤ZW/ŪœFC_ųâĘĶ/IĒu­RAUŧĢ8‰*hŅøPŧĢ8¯<$IĩîĩĄQę%`ÛŖ^ŊęÚ?IR­ģŧũŪžy­S%ÄĐFÃúĐ:åoßŧËÛˆw9į5?$N’tN\Ūū�ú/ō:VäPI’tž0xH’¤Ā<$IR` ’$)0I’ƒ‡$I ŒÁC’$Æā!I’cđ$I1xH’¤Ā<$IR`ÎŲgĩDöė=Wģ–$)PŠŠŠņ.á+㜏ŒmĪÕŽ%IRåP‹$I ŒÁC’$Æā!I’cđ$I1xH’¤Ā<$IR` ’$)0I’ƒ‡$I ŒÁC’$Æā!I’sÎ>ĢE’t~[Y7 J*ā@U|kiXZĻGBŸNņ­å|gĮC’TëVAŋaÛŪø‡¨Ža[yuM+‹â]ÍųÍā!IĒu7͆ČĮĒUUÕĩ)~w¨%ZÂGĢW˛lE…%åD“SiŲ9‡~ũúpyvK’ã]Ÿ$é”v”Įģ‚S¨‘Ŋņ.âü–€Á#ĘÆĶxøąŲŦŽÄŽ[÷k^z2D8g(˙ô“ ܐßøQ\ąŠ<OÁžWėˆk-g#=Ĩ5YM3™ĐíŌSÚÄģI_A ×í8J" ũœĪ,x”°ėąģšī•N–cDō~Í?Lįâ˙w'™Vw´âŠí {k å•qĒā‹+ŽØAqÅrwæ1÷[Ī>$II āåŖŋ—ģ_)āpŸ#5ë*ūvø`úe‡I&ʖuŋãõWæņQÚßņī3ã:�Ļ|đ\ G+¯Ŧ`ĘĪņlßGâ]Š$é<‘8Ácãl~z}Mč‘=ęi^~¨/-~L¯žüíwî`#ÉL‹K•GŦŲš>žԒ÷ÆģIŌy$a‚Į‡¯ŧĘēšVGjßI'†ŽÃŌ:ĮĩĶqØ)ģērkÎ-Œh“MįÆ!ĸû>%¯x.SķPPy.+ĘbŌO1˛üqzžģø4CUĮĒKsS$Iu_‚|įn­ųž{ī°BG4=åÖÉÉ rKRW&]õ#ĶöR¸e3Ęö’ÚŦ7ƒ/ŧŸYMC {g.›â]Ŗ$Iq” Á#–-5߆rč•}Üę’ŲÜvå#Ŧ>éļ!úũ|9/‰ķØ ĐŽã- K‹‘Ÿw?#ķ7× ŊÆėŒ[čÃFĸõC@ŖîŒíuC[u¤åÁ]ŦÛ6›ŸŦK^%Pŋ×uģqŗIoŖ¸tĪŦ™ÎŧōJ¨ßĄŊ&2Ą}([Å´OS˜“ÉÂwG2qįqÅ$ufD÷ûšŗm&aļŗbÃLüät“v%I:÷ībÉpv ŒØiģ!ÁI"ģm6Éä3{ĶáБDjR"Ûæ2oÛFĘtāŽū“šŗÅnæ­Ī­Ž%­ũ^č՛T’ČÉyŒiv Ãdn]1“ü”AL0†õ!œq“/Č$˛å9ÆmØHŸ sHN|öIôé6…ɤ°bõ}ŒĘÛLNˇÉLh•葤Úp˙h¨zv†‹â]Œž´éx„iß(Ę7ņŅFčôDŽä ú žî¨á—([rße] DZjüģ"5‚ƒ{9rû‘VcXpÕõ5uĮxgÅ0î-Ȱf!Š?žÉ´â`#3:`jہôŦŋ—>ÛĀî™Lûd-›€â¯įšnŊ~Ž•mŗI&.`å~(NČ59MNRK6ƒĶ›Cd63Š ŲĘ\–\r%WgdÃWdRŦ¤ķĮ€öÕ_›ļ‡,āã¸VŖ/+A‚Ggúå„yž0đú šÜüĶž‰i=ķ¯=?{xŲîū›wk~ČĸWv"ĖņˆQ‹Aƒæ„CĀ~`÷\ÆŊģŠäĻÃx˛[NõÃ7'ˆTėŽŲŽ‚Čū4hB¸Qŗęm÷ė"Rŗļ|˙.ĸt œÔ´:ØÄvŲ_Ŋ.˛p’āQŋŠ €đhŪ4€ä@JkRÁáIRÜ$HđHæōۆŌyî‹ÅsĮsßĨ/ķīßé|’[ŖķÛaqÍĢg(g0ƒÚ[íÉU’ˇ-ŸhûlFtéĖÜõ…Ä*7ŗfįfBõ}6˛o3å@8­°H!œRĶ)Ųŋģē[Ō¨9aĒB8Ĩ9Éė%Rš§:؄šN*!ܨõÉK9´›ōPņŖŪ[JÉáåw:$Iq•8s<˛ŋĪŖŖ2j~ˆ°|Ōp†|o˙š<Ÿ-%%”lÉįũ¯ōĀMÚ˙Ãũ€ n~h ‘;€ČĻטUŲ?ÁŦ>÷06ûvŧâqö@˃ÛŲTQ å̘ģ;FˎcÛą+}2īįÎV!J>]@îĄ|æ}ēš cBvoz¤cB—L¨XĘŧH%ųÛ6%›;ģ ¤OúÆ^xĒ;Žæŗpû.h6€ĄM›Ņ2uúLd\ĢæžI’ŽW¯ĒĒĒǍ¨ˆN:Åģ ˜ßūÃ(X¸•ã?ĨåDaú=rĒŽČš—=kĐÉWžGz6C5÷ņØš˜W7ĖaIy͍<Rē3ļû=ŒhՑ´ƒÛYW<›GÖ, ā'šĒe)OŽyŽß•WBũΌč=‘ąmÛ-[ʋ;[3áĸĖzw$ė<î>IYÜÚũ~îhŸIKvąîĶ™<˛fqõ1Ž“?rŅ9;O’ÎOõū5‚—A§ThzÔâĄũĒįv�Ė[G­ÛSo}yûĪîPU~úT+,x�”ņáËyāéw)<Ÿ@(܃›™Â? Lļ´Ŗœ2xœcĄ¤f$ÜMų!h×e:Kē5cÆ[ˇ2mĪß§ÁCRmû"ÁゞPtíŲoˇîm¸lÅŲmcđˆŸ™ãq´4.ģíŪ^Ȳw–°|u#åDIĨezgzõŊŽAũ˛iįų¤M’žÆŪĘŋzĖÔôIŧ͎Å~œG>…fžEŦ(ûâûl÷ĩVĩW $IŸ#ƒG´ÎôŪ™ūÃã]ČÉõl•ÃģÅīzĖōâ§÷ņx&d<ĀŦ Ąd÷*&¯šÉš“ ŸœŠŦf‰pzI‚ŋŦ‘žØP‹ęŽÄ  nbˇ1äîĖ ¸ëą›•>ĐZúGÖ$ékLė6Ļvv&I_Ö~˜}’÷sYW@VÍÕ|//‚ß^˜jSâ\ÕRĮ¤§´aŪˇžįĒô+ęÜpEģ¯ĩâĒô+˜÷­įIO9Օ1’$Õ>;_BzJžíûHŧː$ŠÎ°ã!I’cđ$%´Ĩ5Ÿ^žgËąKU79Ô"IJhĶgÂôxĄZcĮC’$Æā!I’cđ$I1xH’¤Ā<$IR` ’$)0I’ƒ‡$I ŒÁC’TëęÅģ€Ķhč+_\yú%IĩŽu*Pī*Nĸ Â)ņ.âüfđ$Õē˙õ°íQ¯^umŠƒ‡$ŠÖõéËī€6Љ1´Ņ°~u-Ëī¨ŽMņS¯ĒĒĒǍ¨ˆN:ÅģI’ô—�9T’$/ ’$)0I’ƒ‡$I ŒÁC’$Æā!I’cđ$I1xH’¤Ā<$IR` ’$)0I’˜†įjĮ›6o;Wģ–$)PÚÆģ„¯Œs<ÂM›œĢ]K’¤:ĘĄI’ƒ‡$I ŒÁC’$Æā!I’cđ$I1xH’¤Ā<$IR` ’$)0I’ƒ‡$I ŒÁC’$æœ}V‹$éüöū–†Ü:/™Č_ës *žĩ4Ŧá¯âÕĄQ.o žÅœįėxH’jŨû[rí¯ŗŊ"ūĄā@lß[Ÿk՘÷ˇøž;ž ’¤ZwËŧd oĢTUU×ĻøIØØlāŋ-ãŋūPD¤4Fr‹´kw W^?+3Rã]ž$é4vV$čûÚzPú×­í<‘xÁ#ē•wŸ|˜Gßü#Ĩ'Ŧ\^ū9-.Âũ“ȍņM­ÛöídÚ_⓲MlÛW×ZÎTÛÆ-钖Áد˙=mˇŠw9’žĸŽÛq”Dú9Ÿ%Vė+ßĀ/îē…ž4tŖtÃ\ūųģŖųņĒS?ę\Ûļo'7/Į˛šu&t�lÛW²šÜŧ|ÛöíŒw9’¤ķLRۚü�Ķ7ė%ÔŠ7ęFģЉ ĩíÆ˙ųN?:ÅūÄŽ{˜_n žR€'˙ø öøk|^ öø+Oūņņ.C’tžI˜Ą–čÚülé.�2Ž˙>?úûNü(ō 뷅h׊Ąh9[ˇí%ÜĨ áäOøŲŌåmû€įŸ\ġžD8āz?(ũcĀGŦ}*+Šw ’¤ķL‚t<ĸü÷›‹ ¯D÷ÆĒŋ wĄë7:NM%-܎Ŧot!œ #Zķō÷fņÖÖā+>eˇŖQWníõ8 nœOūČEŦģņE^é>„Ŧ¤ÃČbŌ‹Čŋj Š�õ;p]v˛ę¤p]˙ųäßô×õ›Iíøų#įķBĮ¤ãv‚̝˜OūMšú žC]"’$}5$Hđøk˙°ˇúÛĐåÜū.ŸķøK¸ũޝĒŲöŋ˙P~nË;SI]™tÕLČȄŌEĖøhs÷@օ÷3Ģ˙02�ØĖėՓųAŪzʁPĢ[˜|ÉõäœdXI’¤¯š ĨDˇ;Úuáĸ37Iŋ¨KuĮ€Ĩ[ã7Éôhí:Ū°´ųy÷3låSL[˙ Y6†QĢgņĖÆDët`D¯É<“Ķ•#wĀ�Ōä0yčĻ´9“Ŗôæé›ąîŠî5?î  9j¸)‰ŒĖ‰Ė>ŸußcJĮÖĩ˙d%Iú$x„āđ;ūČ6JŖŸŋE4Rʑ>Gr"´ ’Čn›M2ųĖŪ´™XͲԤ&DļÍeŪļ”:v‹Øļį™đ—]ĀFf,Ī´Ã™4č°n÷đāeÕÆft8ģRô`XĢ<Ļ­zŽ%•™ ë~×}ū($%¤ûGCÕŖ°{4\ībôĨ%ČäŌļd…áíŊĀŪ÷øĨå\yíénVĘŊųA͋{ˆvm[Råé…H …āā^"5ķOh5†W]OK�bŧŗb÷ĩIåfļî{)ŪSHäPJ͊6ôŊp8}ŲŒ3—ĪĢ.`å~(ūķõ ÎÉĻo3øWĪJǃ´¯ūÚ´=dĮĩ}Y <:ŅũŠļPĨû€.lzr4˙đÉ­Ü4ā.ęŌŠfB)D#E|üÉŪ}ũE~õŪá9!—på7áöˇ1Ęc1hМpØėžË¸wW‘ÜtOvË9ķ]\ÃØ7âw5’ÔŽ‘ÛûlļßMdõˇ‘ũ쀤&%•gžI’΁jŽßÉ7BģXŋĩ cîŊ„M¯˙˜īŨö/ŧuÔôč{?gôŊ?æĨ÷ļy˙Ÿ:`$×}-íIU’ˇ-Ÿ(™ŒčŌšzä¨r3kv Iq”��īIDATŽ%ˇŦ‚3=:c1€PJõ1ę7'íø‘ĻÍ7Ēū6ܨ9°—HĨĄC’ Ōņ�2†ņđ]‹¸å™Yü˛č_ų×7G-oAFģĪ’ÖéÂŧĪ‘Ģg›\Î÷ö'-õžDdĶkĖēh Ŗ.~‚Yi‹XY#Ô4›kÚįĐōā§lĒ8ņÅ?z0t OzWr‹7žÁQļS\É-ŽįÖV;ؔ>‚G¯n�Í­9C(ū Œ¸0bKYšģVžĸ$I_Jâ’ÉúûĮų×mcø‡—ŋĮĐכĶĸ]~úæƒĮž°úßÜüäŖÜØîd+ãäĐz~ōîDļæÜˆôëšŗ}ˆčžOÉÛ2“Šæ°ä$Wũ~ē˜uŪÂ5Ũ&Џ‡ÜĪ=H!¯~0‡ŊŽį}&’›˙ ˲U Šē [ÅÜŨŨ™Đģéķ™ĩú9–Øđ”čÁˆË S*4=jņ‘û %ÁmƒāōŖÖí)‡ˇ>„ŧũÖŠ/Ĩ^UUUUQQ:uĒÕ——Ņ{k”Sđ۟ķĪO,āãØ@ūíŊGšōđĒ?LãēÛfSŪeLūGnŧ(~s;z.øv܎]›r‡ŧī$}Ĩũôė?Eü‚žPtíŲkŨÛp؊ŗÛĻėĪî5*5ÕOE¯- Ôņ8,•Ŧū™_ú>(嘆F—áüôå‘\ôvÄ{:iJÃÆTØį*žœ6brŒ$é<’€ÁŖFr ˛ēw™lr'ē~#>åī›-.aųŽ5ņ.ãKéōŋ2â]‚$ņ—50ō‰C-CûU_F 0o9ĩîđP‹ęŽÄ  nÜ×oį÷Ĩęl×#ĨacÆ}ũöx—!IŸŲŗß;qqÖ5ķ<*áåEđ›Ā SmJ˜Ëiëšļ[ņzŋiôkŨŖN Y´iĻ_ëŧŪomˇŠw9’¤ķŒ/ĄmãV<ŅũÁx—!IRaĮC’$Æā!IJhKˇTŨŗå؉ĨĒ›j‘$%´é3azŧ‹P­ąã!I’cđ$I1xH’¤Ā<$IR` ’$)0I’ƒ‡$I ŒÁC’$Æā!IĒuõâ]Āi4ô•/Ž<ũ’¤Z×*åTÅģŠ“¨‚ÅģŠķšÁC’Të^Ĩ^ļ=ęÕĢŽMņcđ$ÕēËÛāí›÷Ņ:åPB m4Ŧ­SņöÍû¸ŧũx—s^ķCâ$IįÄåíđ§ø"¯c%@•$Iį ƒ‡$I ŒÁC’$Æā!I’cđ$I1xH’¤Ā<$IR` ’$)0I’ƒ‡$I ŒÁC’$æœ}VKdĪŪsĩkI’•ššīž2ÎYđČčĐö\íZ’$ÕQĩH’¤Ā<$IR` ’$)0I’ƒ‡$I ŒÁC’$Æā!I’cđ$I1xH’¤Ā<$IR` ’$)0įėŗZ$I᎕Eđwŗ ¤TŎ–†õ e üz$ôéßZÎwv<$Iĩneô{ļíč€ęļ•W×´˛(ŪÕœß ’¤ZwĶlH€ŧqŦzPUU]›â'á‡Zĸ%ųŧŋb ˗/aYn%Į2į—ˇ’īÂ$I§´Ŗ<ŪœB=ˆėwᎄ %ëæōŌKx?w 늏ûÛ[–ËF#xWlgęĪS°g#Å;â]ŽÎ@zJk˛šf2ĄÛ=¤§´‰w9ŌWVÂu;Ž’C?įŗ +øŲč‡xķti9ē„ģŋy/‹c5?‡Â\zÛŧ<ž'iA”HučöÖĘ++:ĸjCqÅŠ+vģ3šßzÎđ!IKĀ91ĘĸgģI„u/ĪæũsRĪÉMųā9CGV^YÁ”ž‹w’tŪIĀāq’¯æß7|Dá'QøÉĶ œmZųrÖė\čņTû>ŪSī$éŧS7ƒG8mˇ#eoŒ\DūČYLj•T{MéέY¤Å¤‘ÕšŸkSīáék_eõM‹Čŋiī\3‰;Ú´&TķˆĢ¯˜OūMšēŽÎxŧú\ĩ¨…āđyBø¸5Îˑ¤ā<΁véȎå“ģģ9WwĖ>ōbũeåtšą]rNx­mí2§0Ģ÷pú6ÚÁ’?ĪbƟ×R’r%ãLgr›”s|tIŌWYŠ×<ÁōßÜAvŧ 9#­šĻc&Ņs˜öéFZĻ įČYNâē>ķÉŋq<#2'˛`ø|Ö Ž)[YŸ•9ž7n˜Ãē‘ķY=ø1L¯^×Ŗ×f]ԆäfŖYxĶc\wdŸÍésŲã,ŋi>ĢObDjM‡%Š3#ޘÎ;Ãįŗnø‹<Ũåp§¤+S†/b]ŸÜzÕ,V÷?ŽcRŋ+w^’CÚžĨŒ[ø�?üĶ>œÂwŪžČԏ^cÉū“=įzdOb΍sXwĶ,æ\1ŒúÎ|œü‘Ķš5 c/"äsܑZ}žnŊfëŽHķ#ûIâę>ķÉŋáv˛� ᕑ‹Čŋņ>r�’úķôM‹˜ķÍxgä"ædw¨ŲŽ#ŽĒ>¯=ęCjĢÛyå†ųŦģéU^šŦw`Ž%IŸ¯N:%Ĩ7ׅcänZKŪļ56îÍāđáá–Jb�{3ĸŲ3qŲtVĖdØe#č¤Ļ?Ā =‘™ÉŪÂŒũ،ę;™;šBŪ†)Ė**ņƒÅOąōPÍ.›aXƒU<ûqŅ´+™Ķ›T’čĶm “/HaÅęû•ˇ™œn“™Đ* ˆ=Ém‡Đg÷L&į¯?vvLJ6=CIņ|–TV/ %Ĩf# ?YEnEŒã…;Žį™œD7Mf䲚”ˇ}€g.éL¤4ŸB:Ķ4 e‘“˛‹’}čNúČIƒâmė:˛§Jōļm„”l˛’ Ô"‡Ŧ}ģ( e““¤e“Ũ`y›Ë;Ūv�I]éĶ"DÉöĨäĘbl¯‘ôŦŸĮ´eSx•ú4ŽÍ_°¤ Ũ?Ē…ŨŖáĸxŖ/­NōwÆĶīÆɏw!g ]ú�.=¸‘•e!•ëÉ­8n¸å ĀFfoXF^éb^Ũž w sŖ$zfôĻåÁ<fŦ]ĀʝĢx1oÅd2Ŧcb›ŲtˆmĻpĪŽ\mŧ)ĶÖÎeöú×XXÉim“ÍāôæYŒâBō6ÎeÉžæôÉ8ĒgTą”i.æw;wpL”ĩ&(¯Ø]ŗ ÃúÎeÅĐŲŦ:›ÜˇTw#ŽHĸGĮ¤ĖãÕ ë)Ø9‡WwÆčÜ~�Yeëɋ5!ĢEBM{ĩ/ī"ĢU&Ą´Žd…v‘[ēų˜ŊEJķ(¤=›%‘Ũ* vÎe^Ykz´H!Ü4“ôƒųäF ˜÷—OĄYoz6‚P‹+éŲ`+˙’G,ĩ7=S øĶ×xugKōfŗōÄŦ$ŠĐžúkĶö÷˙ęĸŧG]֚k:fC˜<d6“/N@ÎëYs¸Kqp7‘š!‹XėđĢbBÕëj: ėßA jœb"äūÍD�ØÍÖĐ …äúÍHm�„GŗđĻŅ�$7�RZ“Ęæãļ;Nl œÖ Ø ėfÉã)nԌĢsbdƒã7hBZŖ4čÁÔoĪg*@ƒlCúĄŲä–ÆÜ,“ž :Cä,Ü֌īįt'§YMˆØ =m¤l=ą‘dĩČ!§Es 6-eEå0†ļĘ&')vŋFŪ!Øēi…gpĢfˇęJÚžĩ,ÜY ͚ Dö×Lū=´‹’Ęãk–$ŋÁŖ6Ĩôæē0ūų1ĻnŠ~áKn6Œ)9Ŋ~Ž5;O÷ ¸—Čū„›‘ŪØ4jMK r¤ûpr'\H|h7å1 â5FŊˇ”’ÃËîĸü¨{žžôäŠ|ōöÁČ6øŽŅz~ˇ"{Öŗ’Ötž8!xėĨl æ1õ­§Xq8\#Bé;7“Üe�#’ÚP°)ŸČžÖĻôfdzØ=›ŧãOÉĄ|V”ƸēíŽNÛN^é *ķIÎÄā!Ўåą |1swäΎÃœÖœ’íKÉ=ÜK9–R“f’ZWŸOoš"I ĄN ĩÔíŌp)Ÿ˛äĪËXš}-+ˇ¯eɟ‘{đLŽnŠ$wĶ*JČáΈЧUoÆæ "ũ`>ķļÔt))ŨšēMg§ũÍåŗpû.h6€ĄM›Ņ2uúLd\Ģæ§Û¨ÚĄõĖXŋ†˛ÆW2uāãLé:‚;ēŪĮĶ×>Į„0””m<ŽSRɚOķ(kÍāŒ´l”Å­Ŋ&ķdv&ÉĀĻų”4îÁ5áíä•îŽ6ûŗÜž9‘ŌüęqŒ ōvn&šÕ•ô¤€{ ŧt=Åiœ#oÛᡙÍ,Ü´‘´ļÖļˇz˜ l-šû =ũîHīÎМ‘ô<!,I’âŎG­ŠfŲ7Ÿ{ŽZ\šž•Ĩ1ŽŠn9ōâéÜĩ&ƤKÆ0ã()ËcƊ'xĩ`īl\ÃŨ{đƒ^ˇPŧđt¯XÉĘ&3ĩūũÜŅû)F˛‹uŸÎä‘-;€fŸûLļnšĖȃŖ›=€Ģ/Ęa{).Ëc֚Ų<ģąā„!šČ§Ođƒ”ņLΞĖŦ‹cī\ʓųkĢįĄė^KŪÁëšæ`še�YQ){Ģ'’žÄĻŌ|JČ$­4¨+1ČåŗĸôŗÉÖâEŦë6†KcK̇Y�­įŲÕsČęu=ãzw`ÅĮsYX6†‘õŠĩ˚%#`ÄeĐ)šĩ8ëđüü$¸m\~Ôē=åđևwŌ+ęUUUUŅŠS§8–‘ËßŧíôŸĪrXč*žß𠃎,XÂŨ—õš-�ŠCyũ÷SŽųËYÛ˛g úüéÜJęĪ C"ë͉\ŗz-_diūČEĩ^–$¨÷đŲosA_(ēöėˇ[÷6\ļâėļŠzôėŖÚaĮã j’ô5öVū5Ūeœ§RČhĶžŽĻoƒO™ņņ ížÖĒÖ+“$^‚ž<ūûxü m[ũš-AëŲ*‡w‹ß ü¸hͰnqgĘvV|0…gö|ū'“Õ,ķķ$)0Y#œ8Ô2´ßg—ŅÎ[NõlÃC-Ē;$xÔ=ģ!wgž]¸(dÚÂALû{h’ô5&vSkIĒûaöIŪĪe]Q3ĪŖ^^ŋ ŧ0Õ&¯jų‚ŌSÚ0ī[ĪsUúļėëv_kÅUéW0ī[Ī“žŌ&ŪåHŌyĮŽĮ—žŌ†gû>ī2$IĒ3ėxH’¤Ā<$I mé–ę¯{ļ;ąTu“C-’¤„6}&LwĒ5v<$IR` ’$)0I’ƒ‡$I ŒÁC’$Æā!I’cđ$I1xH’¤Ā<$IĩŽ^ŧ 8†žōŕ§_’TëZ§UņŽâ$Ē œī"ÎoIR­ûP/ÛõęU×Ļø1xH’j]ŸN°üh“šC ëWײüŽęÚ?õĒĒĒNJŠŠčÔŠSŧk‘$I_q C%IŌųÂā!I’cđ$I1xH’¤Ā<$IR` ’$)0I’ƒ‡$I ŒÁC’$Æā!I’cđ$I1xH’¤Ā<$IR` ’$)0I’ƒ‡$I ŒÁC’$Æā!I’cđ$I1xH’¤Ā<$IR` ’$)0I’ƒ‡$I ŒÁC’$Æā!I’cđ$I1xH’¤Ā<$IR` ’$)0I’ƒ‡$I ŒÁC’$Æā!I’cđ$I1xH’¤Ā<$IR` ’$)0I’ƒ‡$I ŒÁC’$Æā!I’cđ$IixŽvŧuëÖsĩkI’TGŗāŅŽ]ģsĩkI’TG9Ô"I’cđ$I1xH’¤Ā<$IR` ’$)0I’ƒ‡$I ŒÁC’$Æā!I’cđ$I1xH’¤Ā<$IR` ’$)0I’ƒ‡$I ŒÁC’$Æā!I’cđ$I1xH’¤Ā<$IR` ’$)0I’ƒ‡$I ŒÁC’$Æā!I’cđ$I1xH’¤Ā<$IR` ’$)0I’ƒ‡$I ŒÁC’$Æā!I’cđ$I1xH’¤Ā<$IR` ’$)0I’ƒ‡$I ŒÁC’$Æā!I’cđ$I1xH’¤Ā<$IR` ’$)0I’ƒ‡$I ŒÁC’$Æā!I’cđ$I1xH’¤Ā<$IR` ’$)0I’ƒ‡$I ŒÁC’$Ļ>@Æ 9xđ`ŧk‘$I_qõ5jDEEEŧk‘$I_qõš7oÎ˙üĪ˙°gĪ:īš$IŌWTŊĒĒĒ*€ƒRZZJ4uØE’$G‚‡$IŌšæU-’$)0I’˜˙•´8Gas­����IENDŽB`‚�������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/profile-otp.png�����������������������������������������������������0000664�0000000�0000000�00000106031�14156463140�0021621�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��\��†���w ē���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨwXSįßđ;Ė0eƒ8Ę7 ¸q×=ĐVÛ:~m­vˇļĩĩģĩ};´vZĩu×ŊĩÖ=™N¸ ¨ {ī‘ŧ�‘�ėũš.ޚ3ŋᜐrî<ĪsD2™L""""""""Œ–Ļ """""""zŌ0p!"""""""˜NõåååHOOGqq1ĘËË5UQ‹Ļ­­ }}}XYYAKKš=‹¨j —ōōr<|øbą†††*&""""""""@*•ĸ°°………°ˇˇWĘQäKZZ´´´`hh¨‘B‰ˆˆˆˆˆˆˆZ›üü|�€ĨĨĨÂtyüRTTƒæ­Šˆˆˆˆˆˆˆ¨300@aaĄŌtyāRVV‘HÔŦEĩfZZZ(++SžŽZˆˆˆˆˆˆˆˆžh \ˆˆˆˆˆˆˆˆÆĀ…ˆˆˆˆˆˆˆH` \ˆˆˆˆˆˆˆˆÆĀ…ˆˆˆˆˆˆˆH` \ˆˆˆˆˆˆˆˆÆĀ…ˆˆˆˆˆˆˆH` \ˆˆˆˆˆˆˆˆĻĶ”ii7忉ˆˆˆˆˆˆˆL&-oō}°… ‘ Œ ‘ Œ ‘ Œ ‘ LGĶŅ“)öŪŦ߸y…JĨš.‡Z1‘Hc#C<?#ÛŲkēĩ°…  .öŪü˛âOääå3lĄĮ&“ɐ››‡åŋŽFâÃdM—Ŗ.DDDDDD$¸uļhēzԈDÉ¤XĩîoMWĸ.DDDDDD$¸Üü|M—@O"‘9šššŽB- \ˆˆˆˆˆˆˆ¨ÕÉdš.A- \ˆˆˆˆˆˆˆˆÆĀ…ˆˆˆˆˆˆˆH` \ˆˆˆˆˆˆˆˆÆĀ…ˆˆˆˆˆˆˆH` \ˆˆˆˆˆˆˆˆÆĀ…ˆˆˆˆˆˆˆH`:š. Š-X0ûöîÅŲsĄ°ˇˇb÷Y›ÎœŅĩk7ėÜĩKŖu5”‘Ą!ē{y ““LŒQRZŠÔ´t\Ŋqwãâ5]^ŖØX[bėČapíė�¸u'ģBJjē†+#ĄĩšĀE&“áĐĄ°k×.\š|é��kkô퀐iĶĐŗgO WIDDDDDDB2 Àžžž|ZYY´ĩŨ1d@ b%÷°qÛ.$§¤i°Ę†ąąļÄ[ķ_€X>­[w¸8;â۟~gčō„iKVVæŋ<gĪž…ĄĄúöõGģv푟ŸÛˇocË–ÍØēu Ū~ûŧ4ožĻËũOÛ˛e nŨŧŠ0ÍĀĐ�66ļčׯ4TYĶøîģoáëÛAAAj¯ķĪ?/‰Į /žØ„• #^"AhX>LDAAôõõŅąŖúöí‹öíÛ7k-9×-Ē߉ęÜ=<0iŌ¤z×4x0úöíĢ4???K—ū�™T†÷-‚––z=EkžĪĻ8ŋ2™ ;vlĮ–Í[ƒĸâ"´ĩŗÃƒđ‹/ÂÎÎ�đÚk¯bw=­đ>ûü <ķĖ3˜;wŽ>Ŧ0ĪÜÜxåÕ×ЧOAj_Ŋjtuu1cæL…éąwībãÆ1r$z÷î­0o÷î]ˆÅë¯ŋĄÖ>4ųž^ļl)ēwëŽ6ûž‰ˆˆę#‰đÜôÉčÕŨKiŪĩ˜Ûø{ûnx÷ė†QÃã­ų/āˇ?סšÖ.ãF S[Ēˆ1vä0üąfŖĒz|!Įāڍ[ˆž#Ÿöũ—‹ĄĢS9ääæ!âü%ė;tåååš*ŗŲĩøĀE&“aÁü—qöėYŒ~ę)|úég077WXæōå˘÷Ō‹øæ›¯áââ‚!ÁÁĒ–�ĀÜÂŖG–ŋÎÍËÅķįąnŨZ<˙ü,´k×NƒÕĩN‘‘‘xø0cƌmļ}ÆK$ذqēt邱cÆÂĀĐ99Ų8wöÖ¯_‡YŗfÃÆÆĻŲę2$¸Y÷'$s sŒ1Rå<C#Ŗz××ŅÕAô•+*—ĢW¯BKK åŌ†ũĢŠĪ§T*Å+¯,ũûņԘ1xfÆ3024BLL ÖŦų {öėÆú āáá‰yķæaŌ¤Éōußzķ ¸ēēâ/< %;uę$˙ˇƒƒžZōĩüujj 6Ŧ߀éĶB°cį.tīŪũąëwrrBhX(JKKĄĢĢ+Ÿ.‘H� ‘Ä).ņņņprrzė}7M|†5֘ÁJa˝X ūXŗ ……�€gÂpåZ üīyĖ}vžYö22ŗ4Qnƒ¸vrŽ}^į–ųw„:úxÃß§Vo؂ËŅו曚cȀ@�Āî˙6wyĶâÍ=zäΜ9ƒ^ŊzcŲ˛•Â�čŪŊ;~ũíw„„Lƒą‰IŊÛLMMÅâ?D@_¸ētF¯ž=0wî\ž|YžŒ__ >LiŨĄÁCāäč€ãĮŽ)Lßŗg7œ°sį…éŨN`@_trvBbbĸŌē™™™péÜ Ə—O;~ėFww7ôîÕ ßyŲŲŲõž‹Ļ §ĢGGų——Ļ?ũ4LLLĄ‘šZ줤‡ÍžĪȨ(X[YcėØqpîÔ mÛļ…››;Ļ?ũ4ĖÍĖq˙ūũf­§{÷îhÛļmŗîS(zēzprvVųckk[īú:tDjj*>T~\Ž†}ۆÕÔįsũēuØŋo~Xē˖ũˆ§žƒAƒcŪË/ãā?‡`ff†ķįŖŧŧŽŽn ”˙ˆÅbØØØ*LĢ^ĢĄ‘üũũå?cƌŚĩkaccƒ?˙\-HũÎÎΐ–Kq˙Ū=…é‰ÎΝ™L&ŸžžžŽœė8;wĒšŠAŸ!DDDagcAũŋdJJNÁŋ˙)[Ēddfá÷ŋ6@WGãF mÎ2I---Ėzz ēwõ�ŧņū§XđÎb,xg1~øe%�ŽwM–ØėZ| —ģv�^zéĨ:›ĘwëÖ ŨēuĢw{ééé?n,rrr1ũéépssÃÃćXŋ~ĻL™Œ5kÖÂĪ΁AØąc;˛˛˛`ff�HKKÃíÛˇahh„đˆp 4HžŨ°Đ0ˆD"áXĩĨąÛ™5{>ûôėØąķį/P8†ƒĸŦŦ +ģ!DFFbîÜ9°˛˛Â+ ^…Ĩ%ÂÃÃ0wÎĩģ45ØØØ"3#S>M*•âĖé͏ví˛˛ŗĐÆ´ |ûôˇˇˇ|™īžûAˆ…D‡×_ÉÉÉ8yâ’S’!•Jagk‡Ęģ+•••áÄņã¸vũōķķallŒŽ^]ŅĀ�ųųøūûī„œėl\ģ~ ÅÅÅčØŅŖG†ąą1€ŠŽGŽF\\ ŅÆ´ ŧ}|āëëĢöqįææbßž}Hâ ‹ŅģWoĨeęÛĪēĩk_ŅDōĘå+˜3w.LLLģļú”——Ģl¯¯ÔJk_įuĢo~ÍîB]įúöÛÁÖÖŅWŽ(iiixøđ!úõŠķŪmęî,ūš;vœŌ< ŧ÷Ūû˜;wŽ;&HĢDąX wwwÄK„iNÜžCčęé"..Ε­kŠ‹‹ņ0é!&OžŒ-›ˇ %%E˜UũŽVĩpQįw�dR)ū=tŅWŖQZZ gįN=z4 ¨w-ŋūz úõëų´}ûö"9)ŗįĖQųT4×>uęϏBQQ0fĖUļēRį÷‡ˆˆHhūžJ÷0vļ6øņëOä¯oߍÃņĶį}ũ&’’SuAūž011FnnžÂē#ƒbD°ę.´ĮÃĮ…?ˆ:Üē‹ŽžîĒįŨ‰Ģwũ–v<5U….5[ēÄJ*žÄ25ųoũ Ņâ—K/B$Á_ESúÆXúÃHNNÆö;šqãĮchp0žüō ėŲŗAØž}ĸ"#å7ĄĄį ŖŖƒQŖF!"\ąĨFxx<<=amm­0ŊąÛ™6m–ūđvlW\ėß}ąO=õ�āįŸ–Ŗŧŧŋ¯øCŪ”>$$‹?ü°Eĩ(ÉĘĖ„uĩ. Gƅ‹0rä(´oßqqq8tčhkkË@ÖÖÖÆÅ āââ‚ĀŠ&h›7˙ ///ŒYŅE#**›6mÄ̝ž<x�7oŪĈ#Ņļm[$$$āāÁ(++CđĐĄō톆žC˙ū°`Á+ČËËÃęÕĢpúô)Œ¨ėúąwī^¤§§aüø 066Æũû÷ą˙>´ic 77Õ’5íŪŊé˜2 FÆÆ8> 7n܀ĄĄ|™úö3yĘlXŋææ>|Äb1ļlŲōØĩÕĮÅÅöīĮļmÛāīī{{{ˆD"•ËÖw-KJJęŧnÚÚÚõ^ך„¸ÎõÕĨjŋ!“ÉPVVĻržļļv­įĩúúžžˆˆĮā`ų!WŖŖacckk+…å…xī>ŽäädH$„„LĢu™ĀĘ 'ŧō˜„p˙ū}¸¸ē ˛-mmmtėčPŅ…¨R|ŧÚÚÚčÔŠ3,--!‘H䁋Dkkk˜Tļ˛Tįķ �.]ž7W7L›6YYYØˇo/<€‰+u!ŽĨĒĪ�¸~ũ:œœ0ujrr˛ąwī^œ<y#GŽ’Ÿƒú>'‰ˆˆ„æîĸēĩč­ģqHHL‚‡[g…ébą>ôôô ‰āÖÉQ—Ž(˝ j†š 'vī˙•Æq),,Âî‡ę]ŋ%ĪĸˇĀÎÆēÖųĩ….˙E->pIKO‡‰‰ŠüˇĮ!“ɰ˙>¸šģÃÎÎ)))ōy:::čŨģN:…üü|B$!"2B~37wwøųûaįÎ(((€ĄĄ!’““ĢrÔÆnĮĀĀ�cƌÁ† ë%˙V4==aaa9jLMM!•JĨq φ„`Ũēĩ}ŪC*•Ę˙——‡¨ČH¤§§cذŠîUÅÅň:…Ā€@yđeaa‡qîÜYų ‰H$‚ŽŽ. ĸ;XIq ŧŧēĘíaÆÃĶÃ:::(((Ā•+W0dH0ēté"ßnzZÂ#Â1hđ`hkk�,-­ĐŖGE“6SSStęÔ 5ģ:t(D"‘ŧ›ĨĨ%ĸ"#Ģ֍NNN$qq>b+ŋõ6l8bīÆ*,Wß~Äb1´´´ ŖŖ#˙ÖûqkSG¯^ŊPXXˆŗgĪ æÆ čéëĄC‡ŽpssC׎]åãZ¨s-ŗŗŗëŧnYYYuίI¨ë\_]BIIIÁ’¯žR9oÖėŲj=>ŪËË 'ŽĮŨģwāâ⠙L†Ģ׎ĸGå§ŗ5ĮûŖ.ÉÉÉ�€öjXY,ÃÚÚZžlCU°RSSąfÍ_¸{÷.ôqŖļ§Š““Ž9ŒÂÂB@'Aûö*Â‡ŽH$ōAzãããŅÅŗâŊ¨îį�cØđá��{{{$'%)Œ#ÄĩTõT´V>|„|ß111HLPėÆZßį$‘ĐĖÍL•Ļí;t‡ŽžŦxąˇâ?ũāØą<\;ÁĪģâ˙­ææmTnŗfHĄÉ– ÉŠiøî§;j¨|<—[wcąkŋú…ÖôņÔļTŠ ]VŽŨ„čëĩ?@âI×â---…›÷ę&Oš„¨¨HĨé—._A›6ĘŋliiiČĖĖDff&úøúÔēĪÄĸ¸¸ÂÍŨ‘ļŠūũĀסĘĘĘpáÂ",,�ÔOi[ÖÖ֍ŪÎԐlذÛļn•.Dyy9&Ož âÆĻ¨°:tPÚwõA&›Srr2žüâ …ib1F?õ”ŧi~RR¤åR89+åāāˆK/Ą¤¤zzk×ūŅ ģ–––°´´ÄŽ];Ņģˇ7œaggGG�@ƒIeJķļĩo‹Ō’R¤§§Ë ĩ­1`¨Ø�…Eú…ęééáÜŲŗH$((,€L&Caa!,,-Ô:éi§Ģ~3-‰`ßÎÉIn2ŗŸĮ­M]đõõE\\,âbã‹û÷ãôéS˜>ũiX[[Ģu-ëģnõͯ)%9YëÜĐũ6–……ƌU=XŠ•UEëŠTŠ’’ųtmmm…ÁZÍĖĖĐž}{\šr..ޏwī˛2ŗĐĨKĨņ9šëũQ›Ē8ĩĩęŠ"•JëmŨŖĘë×áŌYņķ­M3|ũÍ˙Ą_?åĪáÆrvvd-[ÜŨ= ‘HāYđ988âāÁJĨHKKC~^>œ+˙hjČį[ÍĪîvíÛAZ.EFFlmm›ôZÖ|Ԙ‘ĄJĻÕ÷9IDDÔԒ’S…-Õ´15śķ[\V_­ĻꁄĻģŨ$§ĻaÅ_÷4ĸ–t<ĩŅŌŌ‚—§;—–ĖÎÖ‰™™™JæÃĩZķņŗgĪČûŠĢ’ŸWҟκKŧķö;ĩ.gcSŅD<00ũų' ““ƒØØXŧķÎB´oßöööĢJBÃ``hÕ!NcˇĶĩkWtņōÂūũûņņ'Ÿ@,ãĀūũ°ˇˇG@@�� ¨rā¨Ē&âՉÅâFŨĖ<. ŒĢ6 ¯ŽŽ.,,,ä-� ¤¤�°nŨZ…Ģ>$ķōō`aQqC!ÖtlZZZ˜ųėŗ=w/^ĀņcĮ`ÚÆ DˇnŨP\š]}}}…šôtõ*÷ûč†ļúÍlMåå娏a¤R)† K++hiiaËæÍjŸ‡ĒZjēŅjė~„¨­!tuuáęęWW7�€$.ÛļoÑ#G0mÚ4ĩ¯e]×­žëZ“PךĄûm,]]ŨzŖ‹M›6É_wëŪMé‰2]ŧŧpäČaáÚĩĢh׎ĖÍÍ—æ~¨R5ÎL]+!==]­Ö=59;;ã‡ĨËä¯ ÄpttĒķZ7† ŒMŒ!‘HĐąŖRRS0ÂĄâ;#)) ĐŌÖBĮŽãū4äķMŋÆgwÕ{¸´´´É¯eõĪ"�*˙!ô9%""ĒOfVlmu—.)UũÎî˙"Vr˙{nē|ZVVNÛÖd0abbŒ1ÇĀË͆b܉•āAb� ŊŊ:;;ĸ °W¯Į`Ī?G”ÆĸQESĮ#•JÕ+ôn\<ļī9Ø ĩ\->pņööD"Á‰Į1~ü…y˙{á…× ˝3p1Ē6Č_˙ęŨwP`Vūņ.\¸€´´TˆD"øTTØÛÛ[>ūJxxüúôQúãUˆíL™<}´ĮŽ…ˇˇÂÃÃđŌŧyō7xÕëEEEJûÍĪΝ3åm*ēēēõŪDééUÜ(7^åŖiMM•›V122ā` Fjj*ÂÂBąg÷nXYYAŋrģÅÅÅ ëÔvƒ^›„„¤¤¤`ÆĖ™ ƒ§ĀĖÜL­mTŨ8ÕŦĨúĩjĖ~„¨MyyyĐĶĶSz_;:9ÁŨŨwîÜ ūĩŦëēŲÛÛ×;ŋ:ĄŽŗ:u5—víÛcæŗĪ*ÔU“§§'ūũ÷bbbpãÆ •­ęšëũQKKK¸¸ēbīŪŊxųåų*oäΜ>  ĸÛeCé‹Å‚buqrrÂũ{÷q˙ū=…Ī6XXXāŪŊ{HHx€í;ČWōųVZ-€’Ō’Ęmč=Öĩ,--mÄŅi^Ėíģ K‡vmáĶĢ;Î_ŠVęųĐĩËŖîĩ2™ 7ktŨo)LLŒņö‚`nö¨†kgg¸vVl kld?Ÿ^píėŒoZĄVčĸ k˙ŪÛcÖSæn\<~YĩNá‹Đ˙ĸ–ņ›:L �,˙ņG>^3fkkk˜››ãîŨģ*™œžŽØgΡOčéë#**ĄįBáâę*˙VŌĮĮ—._ÂŊ{÷‡~ũú×ēßĮŲÎØqã 60Āž}û°ß>HĨRLĒL¨øVOO<PÚī7Ô?9ÍĖÎÎÚ:ÚČĪ·•••üĮĀĀ�†††ĩŽŸ‘™™‰›7c䯭­­1rä(ˆ´DHMM…­DZ"ĨoÖ$@_ŦKKKĩę+¯ė Q}ŧƒû÷ī#+3KíËĸr_ÕĮ¨(//W˛ŸĒ×BÔVŸŧŧ<,[ļĄįÎ)͓ÉdHOK‡ąQE€ŠÎĩŦīēÕ7ŋ&ĄŽsC÷۔Äb1:vė(˙Qu FFFpvî„ĐsįPXXOOOĨešãũĄŽŲŗįāÖ͛XŋnŌŧĖĖL,YōēxyÉ[ëĩTNNÎHIMQŋĨŠƒŖ § hØį[Í÷pbB"´u´annŽöĩÔ××GqĐ=%95i"€'""j¨3a‘ ÁŠH$Âː‰XļäcĖ™"ŸnblŸ^ōחĸ¯ĩ؀bˈ`…°Ĩ>æf3|HVôxÎ_ŠÆĘqc¨?FUØâėØ�ĶB¯QSi-\ŧ1~üėÜšĪΜ‰īøAŠi~qq16m܈#GŽĀČȸ֖&�0jÔhŦ_ŋ+VüގĢu+JOOĮˆáÃĐ­[7Ŧ\ĩ@Å wīŪ¸xá$  x”Úųúú ¤¸ĢW­€:­ú8ÛiĶĻ † †C‡!>>ŪŪ> Øëčč W¯Ū Åå˗ÎÕԀšęĐ××G¯žŊpęÔIÂŪŪ9ŲŲø÷ßabjА•ëådgcÛļm4h0\\\ ‰p5:"‘íÛˇ‡zôčsįÎÂÂÂvvmČ¨HôõīĢöc˛mlmĄ­ŖČČõCjJ Ž;'ggd¤g ??ŋہœĢÆÜ8{ö ,ĖÍahd„ˆˆp…6u÷Ŗ/#9)III061yėÚęcll ŋ>~8}æ4ōōķāęę ąØ�yyš¸|ų î?¸ &PīZÖwŨę›_“PךĄûmŦâ’bÜ­lT“H$’m¤///ėŪĩ ŽNN*Í+Ä{WSĻLAxX/ū‘Q‘†‘ĄnŨē…5kū‚L&Êh¤ÛcC8;;C&•!új4üũŸ–įāāˆū9ˆĸÂĸŠņ^*5äķ-+; §OŸ†——232pūÂyxxx@WWWíkŲļ­=nŪŧ ß>} ¯¯ĐĐPÂÄØäQMÕ>CęjAHDD¤iIÉ)8vę† PnkVm@Ũ O˙ģ  ģö˙Û,õ5FWOˇ¯ãUËŖŖ[ēęaË÷_.†n/Ō#Î_ŌPešŅâ�øjÉČ ÃŽ;1xĐ@øúúÂÉÉRЉ‰ ˆˆˆD~~ēxyáģīžĢķQޝžöŽ;Š_~ūŠ)ŠđíĶ)ÉÉØ°a=˛˛˛đėsĪ+,„Ÿ~ú ųđíã+Ÿîęę333lŨēöööčÔšsÍ] ļŠSC°{×.\ŋv KžūFiū /ŧ€đđ0Ėžõ<&O™s3sDD„Ŗ°°&&-÷ëāĄC!‹qôčäååÁØØŽŽŽ8pP­ë88:âŠ§Æ <, 'Ož€––lŦm0yōdy‹€áÃG@_OD~~>Ú´iƒĀĀ }“ndd„1cÆâØąŖ¸rå ėÛÚãŠ1c—›‹í;ļcũēu*ŸJU͏ņãąoß>lŪ˛úúúčŨĢ7ēu톘˜˜íĮ××ģw5aŌÄI‚ÔVŸÁC†ĀÚÆ—.^ÄŪ›{QXXąXŒļmí1}Út… žkYßuŗ´´Ŧ÷ēÖ$ÄuVįũ$„ŦĖ,…ņYĒi‰°hŅjoËÍÍ :ē:ō§3Õ$Ô{÷q‰D"|÷ũ÷č? ?6m܄Eī/BqI1ėÛļōҪņŌKķ=ĮMÅØØÖÖÖHMMUčÖTŒãRTX}ąž|ܚ*ę|ž•——# YY™Xĩj%ĘĘĘĐšŗ‹üÉAę^Ëāā`ėŨŗ˗˙ąXŒž=zĸ[×nˆ}ÔŦēægQKļįāaX˜›ĄWw/…éömí`an†ž]ģĀģgE÷âĸĸbŦXŗ™Yš(U-FÕZĢĒËØ¨áëhRii)î=HŦĩQnn".\ÆžCG5Pæˆd•mŒ% ėėė„Ũ¸–vũ 5@hh(ļl،¨ČH¤ĻĨA[Kļļ6čŅŖ'FމÁC†(}[ē`Á|ėÛģgĪ…ĘûŪ§¤¤`ų?âØąŖHIIĄĄ|ûøâå—įËY%::cž �ˆˆŒ’?:�æÎ™#GŽ`ęÔ,ųúë:÷ؘíTĐ×™™ˆŒŒRų­öŪŊ{đķĪ?#.6ÆÆÆ2$ī/Z„‘#†ÃÜÂûöíWû<=ގ>øėąÖ„aƒúC__u†¸øûذu'’SŌk?MméW)´rWGYy9^ī“&ĒHxV–æČĘέ÷)•Búöķk}™´\ J*$%%ÁąÆĶN[Uāō_•˜˜ˆũûaƔиŧÆã–‰ˆˆˆˆˆZĸĮ \€ŠÖ!=ēzÂŲą#LLŒQZZŠÔ´ \Ŋqwb%_d33"ƒúõU;t)//ĮąSį°įāá&ŽŦuk KĢčRô_÷ÅŸ�f͚ĨáJˆˆˆˆˆˆšO~AΆGálx”ĻKi´=3<ųbāŌBIââpúôi>ü/NŸ>W_{­AkœÔôá�� �IDAT‘æ0piĄbnÆāŖÃÜÜoŋũ^š7OĶ%‘š¸´PǏ@lœDĶeQ#hiē�"""""""ĸ' """"""""1p!""""""""""""""j5D"MW .DDDDDD$8##@&Ķtô¤‘É`jbĒé*ÔÂĀ…ˆˆˆˆˆˆ7ãé)­§)ĩZ"Ėžĸé*ÔÂĀ…ˆˆˆˆˆˆįÜą=æũīy˜AÄā…`ld„W_œ{;[M—ĸ‘LVŅÆK"‘ĀÎÎN؍ki ē="""""""ĸĮ%“– ēŊ¤¤$8::*Lc """"""""1p!"""""""""""""""1p!"""""""""""""""1p!"""""""""""""""1p!"""""""˜NSn\&-oĘÍĩHláBDDDDDDD$0.DDDDDDDDcāBDDDDDDD$0.DDDDDDDDcāBDDDDDDD$0.DDDDDDDDcāBDDDDDDD$0.DDDDDDDDcāBDDDDDDD$0.DDDDDDDDcāBDDDDDDD$0ę/âãã5Uũ‰D"Čd2M—ADDDDDôXôõõ•Ļ).nnnÍV Ņ“@"‘(Mc—"""""""""1p!"""""""""""""""1p!"""""""""""""""1p!"""""""""""""""1p!"""""""""""""""1p!"""""""""""""""1p!"""""""""""""""1p!"""""""""""""""1p!"""""""""""""""1p!"""""""""""""""1p!"""""""""""""""1p!"""""""""""""""1p!"""""""˜NSī ¸¸]{�vm߂n]Ŋę\~Āāa¸w˙>žYō&M¯r™Č¨ķØŗw?""Ŗœ’‚‚‚˜™™ÁÉŅA™2 VVVJëũąr5žúæÛÃŲSĮĐÖÎNáXT‹Å°ˇo‹Ā€ž˜õÜLtėĐĄÖeüķ_}/ūoĖÍĖĢŽšž„ķSŸwŪ]„m;vâƒ÷ßÅŦįf*Íoė9h į:œ:uī}°AXŗú&¨NxÍųžĄēmÛąīŧģ}ũũ°~ÍjŦ\ũž\ō ĻNžˆ¯žøLĶåĩ*M¸)++o-|ĮŽŸ”Oŗ07‡-222u‘QįņËo+đæë¯böķĪ*Ŧofn''GĨí&$$ĸ¤¤–0mcĒ4_WGų4uęä ąXūZ*•!99ąąqˆÃ–­Ûą|Ų÷<p€Ęc9yę � _P î?x H]OŌųŠËĒ?×`ێûÔhĨ°åqĪAK äuhmšō}C 7gÖsˆžz›ˇn‡‡‡f>3]Ķ%ĩ˛Jqqq˛ĻPTT$srņ9šxČ._‰Žwųūƒ†Ęœ\<d[ˇīP˜ž“›+<l¤ĖÉÅCÖ­§ėˇ+eÉÉ)ōųRŠTvųJ´ėå¯É÷÷͡ßĢUã¸ISeN.˛UŽyėc‰“HdĪ<;Kæäâ!ķėÖK–ššĒr9ŋĀū2¯Ŋe%%%]—Lö䝟ÚÄÜŧ%sõė&ëĐO–““Ŗ0¯)ĪAK Îu(++“Õųžjišã}CęŲē}‡ĖÉÅCöôĖįå͞ŗŗe>ūA2ˇ.ŨewīÆj°:""""ĸ–KUĻŌjÆpųøĶĪ sslŨŧ/Ė kų|‘H„n]ŊđĶ?āí7_�üúû8{.´YëttpĀ/?-ƒ5 ąuûNĨebbn"99ū~~ĐÕÕdŋOŌųŠË§Ÿ‰ŌŌRŧõÆk011Q˜×ZÎASŌÖֆžžž`īĢ–âqß7ÔxĻĻĻxķĩWPRR‚OŋøJĶåĩ­"p‰ŋwģvī�|ōŅpué\įō/Ŋ0A�€å?˙ÚäõÕdblŒ��Ā•čhĨų'OWt'ęß/Pũ=iį§6į/\DhX8ÚÚŲaܘ§æ yļnßgWO,|ī”——ãˇ+1läxtí‰n=}0ãšŲ8áĸĘíŪ¸ƒ7ß~ũÁ­KwôôņGČĶ3ą}Į.Čd2ĩĩą6oŲgWO<;kŽĘã)**ÂW_˙û†{—îč?x(~ųm…ŧļÍ[ˇcä˜ņđčÚ=ŧũ°āĩ7‘’’Ú"ŽĩŽ÷T*Åæ­ÛōôLôôņ‡‹GWtëéƒ “§aãߛ!•J•ļwįî]ŧĩđ= ÷.ŨáÕÂG`ņĮŸ!.NŌčåũûÃŲÕˇīÜQÚÆ‡} gWOôôņWyŽž7ÎŽžˆ”Oģpņæŋō:üûÃÕŗē÷ōÅøÉ!øc՟(**RX˙īÍ[áėę‰÷}ˆø{÷0}ÆsđėÖ Ÿ~ū(())-ÅOŋü†ÁCGĀŊKwx÷ Āŧ¯âÖmåzĢL?66Ö8uú ._Q˙w–ˆˆˆˆčŋŦU.˙ų2™ íėí1bø0ĩÖųߜY�€ˆČ¨Zo›’••%� ?ŋ@iŪ‰“§��ũ… \ž´ķS›M›ˇ��&Oš�ã•yôõõ+j+(Āo/ÄĪŋū‡Ž €ØĀ�gĪ…âé™Ī#66Na{ûĸISąs÷ØØÚ`¸1čÕŗ;ŽD_ÅÛīžWßxKåSĢ~< ^}{÷@Īž=āåÕ÷ī?Žß/ÅĘÕaųĪŋâãO?GģļmŅ×ŋJKKą˙ĀAüīĨ—•ļŠŠc­í}ŗđũđŪĸqņŌetõę‚1ŖGĄgΏzí>Xü Ū˙ā#…å¯D_Řņ“ącįn˜ššbÄđa:d0´u´ą~ã&ŒŸ‚7bĩ|`@_�PMǜ ƒH$Bvv6bbn*ĖËÉÉÁõ1044D¯ž=��6ũIS§ãĀ?‡ĐÎŪƍ…ŋŋîŨ쇝žū?„<=Sá\č‹+Žu^~^{ķÜšsŪŊ{ÁÎÎFžĖëoŧī—ūˆ‡IÉ2d0ˆ0~ŌTÜšĢōŧëęęĘ1ßô÷–Ú.UĶ*FŲŧpņ� ¯ŋ´´Ôˈ|}ŧĄ¯¯ââbD?‘#†7e‰Jî?x��°ĩĩQ˜ž—Ÿķ.ÂÉŅ:´d_OŌųŠT*őŖĮ�Cƒ+Íōčhk�Μ={ûļ8t`/ėÛļ�äæåaü¤ŠˆÃß[ļâũwß�$$&â­…īŖ´´ß}ŗãĮ‘ī'ūŪ=<ûü\ėÛ}ũü2u˛Zõ ĨęxNŸ9‹n]ŊpøŸũ022�|ŋôGüôËoø}ÅJčééaįļÍpwsPŅ‚eÔØ ¸}17oɧkōXUŊoîÜŊ‹í;vA$aëßž„víú ŒŸ4[ļmĮķĪ̈́›Ģ �ā×ßW ¨¨/ŋôŪ|ũU…}üôËoōķōķōĨ ^>(0�;vîFxDž™>Mž\JJ*ââ$ĐŋNœ<…°ˆxx¸ËįGDFA&“Á߯tuuq÷n,>ųėK�Āo?˙ˆĄÁCäËæää`ęô¸}˖˙$ęUv%;ūœœqęøaˆĢ B|úĖY<ô/ôôô°}ķF…ũ¯]ŋŸÕŅe(xČ`üōÛ ü{äžúâSˆDĸZ—%""""ĸfná2÷Åy0¤ÎŸ„ÄDĨõ’““@å“[jŖĢĢ+|ėädĒW_BB"ŽŸ¨hÅRÕmĨĘšsĄ(++Cŋ~A‚íīI:?ĩš}į.rrr`ddw77Ĩų‚žƒĘɜœ|öņbyØTtk™<q�āFĩ k×oDqq1† V �ĀĄcG,|į͊å6lTģ>ÁTOnn.>úđ}yØ�S§L�ddfbÎėįåĄ �xx¸ËËÛÕē›hęXk{ߘ›`ųŌīđŨ˙-Qzė|OôėŅ�pūÂųô��={ôPÚĪ sgcíŸĢäĮŅĐåĢZ¸DD*ļp9Z1VФ ã §§‡°pÅųĄa�u5ܰi3ĘĘĘ04xˆBØTŽĢRülŲļeee��Qeؘ”œŒˇŪxM!l€Ũ{÷�ƌĨļ�ĀĖgĻÃĨs'ĨãĢŌÅĶČĘĘVjŨEDDDDDƚĩ…KjjZŖÖË/¨h2oh`Đ õ +o,ķķķĩ߆ĘÍÍEÔų‹Xō͡(..†ģģFÖčŪ"ŋ%H˜îDĀ“u~j�čØąŖĘ,MqLMMŅģWOĨéōÖ.ššōiĄĄa�€ÁĒÜO˙ @ˆD"ÄÄÜDvv6Ú´iĶ :…`ccÎo¨ílmå˙Ž &Ēŗĩĩĸ¯"//O>­šĩž÷­­ F!Ÿ_€ŒŒ ”KË�FFF�€œœG×Ģs§N¸vũ–-˙ íÛˇSķGWWū 54dyK xzxāúˆ“ÄÃÉŅ��đõõAĪŨå-ZĒZŠ„†WĖīWųŲ�ØŋŸĘķâīįWy\9¸'oŊ�mÚ´AîŨ”Ö‰Žž�đëãĢr›AˆšyKå<8;9âÚõˆŋw:9Ģ\Žˆˆˆˆˆ*4kā˛kûĨo k0xîŨŋ¯0­ęi4šÕnúԑŸWqmjjRĪ’ 7nâ”:į{x¸cåīŋ(5ręÔčéęĸ¯`ĩ<Iį§6™��K s•ķ›â´o×Nå:Ú:]tĒQō ĄĸÄŽ={å7Î5éęč ¤´I<ē̏njmíė”ĻiWv7TwīŌÖŽˇ¤˛æ9ÖÆžoîÜŊ‹eËÁŠĶg‚°ęĒRûŪ¡qųJ4ĸ¯^ÃđQcāŌš3úöõCP@�úúÉĮŊiėōA}qũÆ DDD> \ÂÃŅšS'XYZÂ×ĮᑸyķÜŨŨ™™…˜˜›pčØQŪęĒę<ˇo¯ú}hddss3dff!1ņĄBābmeĨ˛ËOReK0;;[ĨyûĒģ›cģvípíú ddfÔšĩ’1\Úˇk‡Ë—¯āî]Õ:ĒRVV&nÚŲÛ ^“ĢKg*LëëÃŪž-ô뇑#†)ÜĖ7… ‰‰ đ‡A[bÔåI9?uI΍¸Á37W¸4Å9ĐÕS˙ŅĘ•—ĒķˆéÜŧæiQTS}ᖮŽzĮ۔Įژ÷͍1˜<íĀŨÍ#G<ûļm!6¨čNķį_k•ž*eccŊģļcÃĻMØąs7nŨžƒÛwî`ÍÚõ016ÆėYĪaūŧå­Šē|P`�~˙c""Ŗ0uĘ$Üģ ‰ō1]|}ŧ�apww“°[ũÉeE……Į_Ŗ[PuúzAOQąâ͊ŒŒT._õTŖšQ==ŊZ÷@Ū-#ƒ Q}ZEāâŨĢ'ö8ˆ3įBQZZ ]Ũúo Ī_¸ˆ’’�€ˇˇā5}ŗäËz[ëÔtâäi�@ŋ áÆožœķŖTÔŠés`hdˆ’ŦlŦūã7 ¨Ĩ ȓĸ)ĩ1ī›˙û~) 0lčüüãRĨ.gU ¯ÉČČ˙›3˙›3IÉÉ8w. ũ‹ã'Nbé?!++‹?x¯QË{÷îąX,RĒēųõŠhŲÖĢgččč ,<Ī͜°Ę–BAÕēĸ$;……ŠaJu…•ŠQĒ6úzz(--•ŋīk*(¨' kú'›=1ZÅcĄG]]]¤ĨĨaûŽ]j­ŗrõ_�€AûÃĖŦųĮËPåÔéŠĀĨúˇØBxRÎO],-,� ÖŽ š>ŨFT úü¤iiĮzņŌe�ĀĶĶĻŠßįæ-Õc’Tggk‹ ãĮâß~ÆĘßPņHæŌŌŌF-¯§§_o$>|ˆöhü�000€W—.ˆˆŒ„L&ChXtuuá_ml•Ž•O1ģ_Ŗ‹e•ÜÜ\dgg�:Ô͍Šĩ5� 9%Eåü¸¸ø:ׯ+Éĸō÷‘ˆˆˆˆˆj×*+++LŸ6�đå’opųJtË¯YģG‡ļļ6ŧ<¯9JŦWaa!""ĸĐÖÎ.;×ŋB< į§>•cˇ¤WŽåR“ĻĪA_ŋ>�€Ŋû¨œ_RR‚ģ÷4zā薤ĨĢžžrw˜Æũû’ŽÃ%++;wīŠĩū~AĐĢl ’‘‘ŲāåĢT=I)ę„GFÂĨsgXYZĘįûúx#++‘QįqûÎx÷îÃj-UĒļ=vü„Ęũž<U1�ˇ5+Į‰Š§‡�Č[ŪTW^^Žã'OÖš~Bå¸2æ \ˆˆˆˆˆęĶ*�XøÖčęÕyųų˜öĖŗXūķ¯HNVü–6&æ&Ū|û]|ōų—�*ēėŪ­Ģ&ĘU’ŌRô Rī1Č ÕÚĪO}ĒZUÜŋw_a°Úę4yχL…X,FDdūXšZa^ii)üŪ|û],ZüņcīKĶZÚąV=ĘøČąã ĶŖ¯^Ã'Ÿ!/%ŠōŅßÅÅÅXøŪxw҇ˆŒ:¯´ŊĄ¤¤VVV°ļļjđōUĒ—={÷#99EŪ¨JU]ŦúĀŖ§U™>m*ôtuqôø >rTa^jjžũ~)�āš™3TĢĘČOwÚĩ{/ĸ¯^“O—ÉdXļüg¤§×>6Kyy9bã$��‡ŽÔÚŅYĢè8rÃēŋđî{āĀ?‡đÃ˛åøaŲrX[[ÁČĐéō§“˜cņīcâ„qŽú‘“§N�úõvü–*­ũüÔĮĨs'˜˜˜ 777oŪ‚‡‡ģŌ2š<íÚŲã˙–|×ßZˆ¯žųģö‡; ō uiéépčØŸ~üĄ ûͤ–vŦ/Ė¨ķđĮĘՈ‰š 'GÄÆIp.4 ßzÖ6ֈˆŒÂ–mÛQ.-ĮÔɓđŪ¡đŲK0uú tõęGGˆ BœD‚čĢ× ĨĨ…>xZZZ°ĩĩiĐōU\]:ÃÖÖ'OUt%ėSãQĖ>ŪŊ ‰ä-Xj>*ŪĄcG|úÉbŧˇh1^˜ˇ�ž>ŪprrDZj""Ŗ›—‡Á`ÎŦįÔ>WÇŖ¯ŋ΅†aŌÔéčãë }}1bnŪDVV^™?ß|ûŊĘu¯ßˆAaa!Ú´igg'ĩ÷IDDDDô_Õj�062ÂO?ū€ /a×îŊ @rJ 23ŗ`ffOw ĐS'OB›6-k\’“§Î@KK }ũ›l­ųüÔGKK CÄÎ]{đ*@ŗį`ÔČčÜš3VŦ\…đđHėŲģÚÚÚpčØS&OĜYΎŠņrÔŅ’Žuđ øŋ%_b՟!,<ŅW¯ÂÍÕŋ˙˛ƒ DIi)Ž;cĮOāŸC˙bôČxūŲ™puqÁĻÍ[qųĘÜžsååå°ļ˛ÂSŖGbÖsĪ*´|jčōUúĘĮĒų(x¸ģģáÆX[[ÁŨŨMiũ)“&ÂÕĨ3ūXų'ĸ.\Ā…‹—```�wLœ0ƍmĐĶžD"Vüú~ūõwė;páQ016†OoŧņÚ+ōn`ÅÅÅJëVĩ˛:d°Ú-jˆˆˆˆˆūËD˛Ę $ 5\Qí"ŖÎcęôhgoG5čF“ˆ¯ŦŦ ũ#)9;ˇūîŨģiē$""""ĸEUĻŌjÆp!ōņî ŋ>žHHLĞŊû5]ŅÆÎŨ{”œŒ Ā�†-DDDDDjbāB­ĘâEīAGGß~ŋyųųš.‡č‰—›—‡ī~X=]],^ôžĻË!""""j5¸PĢâîß|“’đņ§Ÿkēĸ'ŪĮŸ|Ž””Tŧģđ-tęäŦérˆˆˆˆˆZV5h.�Ėũ<nŨží;vĄ‹§žvĻĻK"z"­ūk-vîۃÉ'āš™34]QĢÂAs‰ˆˆˆˆˆˆˆÍ%"""""""j \ˆˆˆˆˆˆˆˆÆĀ…ˆˆˆˆˆˆˆH` \ˆˆˆˆˆˆˆˆÆĀ…ˆˆˆˆˆˆˆH` \ˆˆˆˆˆˆˆˆÆĀ…ˆˆˆˆˆˆˆH` \ˆˆˆˆˆˆˆˆÆĀ…ˆˆˆˆˆˆˆH`:MŊƒÄä4¤efŖŧ\ÚÔģjĻFúXŊîoäå@*mĮМ´´´`ldˆY3BĐÉąŖĻË!""""""Ō‘L&“€D"ŖŖŖ OHNGfV6,-Ė ­Õú͜žŽūŅt­LˆDxmŪ†.DDDDDDôÄS•Š4i ’ž™ÕjÃ�8~ė„ĻKhD"@&ÃĒĩkē"""""""hŌ$¤ŧ\ÚjÃ�(,*Ōt ­—H„ü‚MWADDDDDD¤­7 ĄcŪŅ""""""""1p!"""""""""""""""1p!"""""""""""""""1p!"""""""""""""""éhē€'ÍĖiáĶŗ{ËlŪągÂ"å¯}{÷€ŋO/´kk]]deįāę[8râ4˛srŧŨšĪNGˇ.î ķōō ø0 ĮŨ¸øFЃ‹Āū=vá‘å¯g„LDbR2Žž8#Ÿ–”’ �‰DxnúdôėÖį/Eãth$Š‹‹aßÖũüāŨŗ+–˙ū“’´]�HKĪĀÆ­ģä¯MMMčįƒW_œ…o—¯ĀŊ MrüDDDDDDDÔ—e?ũŠ‹—.×:˙ũ…oÂÕÅĨ+z<IÉ)HJN‘ŋ.--ENN.nŪ‰UZ6ČßŊē{aÍÆmˆētE>ũZĖ-œ ?7^ž‹Y3Ļâ‹o—7hģ�P\\‚Ûą…i—¯ŪĀG _À ?ŦŨ´ũ1”ˆˆˆˆˆˆˆjŖņĀåšgĻ#dōD�@Ôŋ8pā/zW>ßÜÜ\SĨ5šūˆšuW!lŠ’_P€]û˙Á˙ž{]Ü]qõÆÍĮŪ_YY&ÃÚŌōąˇEDDDDDDDĩĶxāŌÆŦ ÚTūÛÔÄ"-lmmäķ?ũb <=Ü1iÂ8ų´Ũ{öãrt4^{e>^yũ-ĖũŽ;ôŒLXZX`Ū‹s`U*¤¤ĻaÃĻŋq76åååđņCĻ@__ŋR™Š‰1Ŧ­,p6<˛ÖebnŨ�tîä(Hā�–æxX­ĨLu#ƒbDđ@•ķ>އ RŅ“ŽÅ?Ĩ(0ĀaáÉdōiQ/Â߯´ĩ+Ę ‹Ā;ožŽĨß.ŊŊVŽū � •ʰėĮŸame…īžū_ų923ŗ°nãߚ8fmL�™Yĩ.SZV†œÜ<˜™š6jZZZōŸ6Ļ&3"ļ6Vĩ†<ĮAĄ Ã""""""ĸ†Ņx —úôņņÁĻŋˇâæ­;pwsAJJ*ŅĮĮ[žL˙~‹+ZŦ ė„Īžúųxđ I))Xüá{Đ×̓žž>Ǝ%ß|‹gg< ]Íž´2@ŌÖŌŽs9‘H¤6ŠĢŊ–-ųXaZAa!6lŲ)o9ŖJU°RÕŌ…a QÃĩøĀÅČČ=ztGhx8ÜŨ\yáēzyÂÔÔų��++ųōmĖ*:(eeg#%5åååxaŪ+JÛÍĖĖ‚ĩ•Ōô撕• �°´0Ģu]˜!ŗrŲ†HIMĮšM[å¯KJJ‘’–ŠTZīēÕ†-DDDDDDD ×â�đīƒ?Vũ…ͧáüų‹:d°Âü2išüßRiEk�=]]âįŋoÎrՒ—_€‡É)čÕŖ+;Ĩr7—N�€›ˇko‘R›ŌŌRÜ{Øčú´5^‹Ã�ēuõ‚ŽŽΜ=‡‡“Ņŗgw…ų))MKO‡H$‚š™lll_P€ôŒ ųüĸĸbäää6[íu9~ęėíläīĢ4ĪĐĐ�ãFćĩ>ú™ˆˆˆˆˆˆˆZĻVŅÂEKK ū}|ąyÛvôęÕúzz ķOœ< 7WWˆÅúøįĐa¸ģģÁĀĀ�NŽprt†M›ņüŗ3 ­­… ›ļ #= ß~CCGķHhätvvĔņŖŅÉÉWŽŨ@qq ÚÚŲ €D"VüĩAĶeQĩŠĀ�ûú៏ ĀßOy^@_|ˇô'¤$'ŖC‡xųĨšōy/Ŋ0ë7ū7.‚žŽ<ÜŨņâÜŲÍYzÖmہ7ī ĀĪ!Į@WG™Y9¸pų*?…ŧüM—HDDDDDDD Ôĸ— €ž čĢr^jzŦ­­āáîĻ4ĪŅŅ_|ōĄĘõlŦ­đÆĢķ­ŗ!>ųziŊËD]炍KWÛîk66h[DDDDDDD$Ŧ¸¨"•J‘œ’‚M›ˇbôđa‰Dš.‰ˆˆˆˆˆˆˆ¨N->pŲžc7Ž8…  žč×/PĶåÕĢ͓•Ā�� �IDATÅ.“'ĮäIãUÎ324Ä_+k抈ˆˆˆˆˆˆˆęÖ* MDDDDDDDԚ0p!"""""""""""""""1p!"""""""""""""""1p!"""""""j2"‘HĶ%iD“.ÚÚZ(—J›rMĘ@,d2M—Ņ:Éd062ŌtDDDDDDDҤ‹ĨšŌ3˛[mč2pĐ�€­4G$Âė™!šŽ‚ˆˆˆˆˆˆH#tšrãíl-�Šé™(/o}Ą‹ĩ5^›7ĢÖūŧü|ČØÚĨ^"‘ÆFF˜=3;jē"""""""É*S‰DGGG —CDDDDDDDÔē¨ĘT8h.‘ Œ ‘ Œ ũ?{÷UÕ¯qüIŌJ(!t¤ųĒ( ĸˆŠ Å.ŌTė€]yíETPéÅîkWšˆŨ "ÍĐ$BBHBÉ$÷Ā@™9Ųg&ā÷ŗ–ëŪ˜ŗ÷īœŲsם{ž�����†Ņp�����0Œ† �����€a4\������ Ŗá�����` ������Ãh¸������FÃ�����Ā0.������†Ņp�����0Œ† �����€aÁvO°=c§vææÉá(ą{*�����ūUŪž6Ãß%7âbcԭ˙ęzö™>™ĪֆKZFļrwíVB|Ŧ‚ŲLS]ĨĨgŠũÉÍũ]����Ā‚WžyĖß%˛swiōÔŲ*-•ēuąŋébk$;w—âãbhļ������ŋŠŅ [nĐâĨ?ųd>[;!G Í�����P-ÄĮÆ('w—Oæĸ�����` ������Ãh¸������FÃ�����Ā0.������† —ũû薁CĩiķŋÖąxÉRŨ?öaŋÖ������Ēŋ` •5T>ųô3ũžlšrrr ääFēü˛KuRĢ–ū.bå*ÕILTŨēuŒ9hĝJÛžÃųwtT¤ZˇjŠ}oPƒ¤ú’¤ŌŌRÍy÷C}ūÕ7*(,ÔI­ZęöCT'1Áå‡čŗæH’v¤gč͡ĻéĪ5kŦNíÛiđ€žŠŒŒĐ¨ģĐ)'ĩÖā}įîHĪĐ­Ãnס Uî]/ũé=ũü͛1Eˇß=ĻÜŧ5j„ŠQÃēņÚĢuZĮöGÕķڛoëŗ/ŋҧīĪRPP2ŗvjĝ÷jČ­ũt~ˇsĮååí֐‘wéĻëû¨ë9g際nuyīš5mĸ Īũ÷¨{¨Ø˜hĩ=ådõžĸ—š6I–$Ũ˙ācJH¨­ģF ?jŦߗũĄGž|FͧLÔ}ãs;ÖĄ9 õæÄî||Ãßu÷é“÷f:ëyÁyēęŠKËÍųŨ÷?ę… ¯9ģļī@õŋųõŧāŧrĮ-_ąJcyRĪ›ĄĐĐÖŒ§ĩõ4BY;ŗËÍ×íœŗuĪˇ9˙NŨ´Y#Gß/I vž{ČĒÕéžqŠ{×st×íåīmÅZ+ŽOÖ@¯‹z5N@@€jëŒĶ:Ēī ×ĒV­š’¤‡ŸxZĩãã4rØ —ëŊ"Iˇöģņ¨×���p'=#S_/XĸuŠUXX¤Čˆp5KIVĪķģĒNBm—WŠÕk×+!>^‰ ņū.ÅcÕĸá2cÖmÜ´YũûŪ¤úõëkīŪŊZ´x‰^|é=ņčCŠõw‰ĮĪžøJ_ÔĶhÃE’ŽësĨ.žđ|•–JŲŲ9š:sŽžxęyŊūĘķ’¤ū÷š-YĒą÷ŽVtt”æ}đąæŧûî¸m¨sŒ>Ŋ/ׅįw+7n@@€¤˛†ÍŖO>Ŗ¤¤úzņé'T´w¯^œ0I¯ŋ5U÷Üq›:uh§ū­ÜšËWŦRaZžbeš†Ë+VéäÖ-UĢfÍrĩKRAAĄ-YĒGĮ?Ģ Ī˙W)ɍį­ßĒÅß˙PnŽÄ„Ú1äVŊúúÚæ%Ô.{“O|ã-5kÚDŊ.ꡒ’RMyíĨrįåīŲŖqŽW÷Žį¸ŧû÷ĐöéúüĢouĮ=c5îžŅ:ũ´ŽęŲŖģ^žøĻ† š5j”;÷ۅ‹õŸNwđ}ánŦC%MŸ5OÃ8ęuĩ“ģ5ãim…sĪjÕ˛šķąaåīMŖ† 4åĩ—ôë˙-×;Ķf5ÆWß.ÔŲĪĐŌÖ°AũÍŠĩš¯âqˇ\SRRĸ-[ˇéõ)S•ˇ{ˇî=ĘĶ[įōŊ"IQQ‘���HŌĻ-˙č•7§*ĨqCõģî*EGE*w×n}÷ÃĪzū•7u΍!Î˙ˇ:úfá]Đ­ o­YģVõŧP­Zļ$EEFčÚ>ŊU'1A‡ŋõ”™•Ĩŗæjë?[U'ąŽ†¨¤úe˙Jžēi“æžûžļnŨϰĐĩoßN7]­BBBTTT¤a#īÔ­ũûęŨ÷>Ôå—õŌųįuUfÖN͚3WŠ7Éápč´Nuãu×(,,L’´áīTM›9[Y™YjÚ4E­[VžÛfūÂÅúę›o•ˇ+OŅ1ŅęŲã^׃ĩW>Wzz†ĻΚŖÔԍŠŒŠÔEöP÷nįZĒũég_ІŋSõÚëoĒSĮö2ĐÜėđZĩT;žl'Ԏ×åŊ.Ō“Īŧ ’’čÃO>×čQÃtRë˛{5zä°ŖÆˆŠŒPũzu]ŽŋkWž’’ęi䰁Љޖ$]qéŚ5ī}IR§í5÷Ŋ”“Ģø¸˛†Ãō+ÕŊë9ZúãĪ*--u6o–¯XĨ‹zœī˛öÚņņēåĻëõëoËôĶ/ŋ9.%%%zeŌd]uy/M›5¯\m]ģœĨ_[Ļ^™¤ņŒÕ÷?ūŦ?VũŠI/?'Šl—Α×UZZĒ7žœĻ隆¤Ë{]tĖûXŋ^]uęĐNSĻÎĐËßÔÔ7_ÕYgüG“&ŋŖī—ū¤G|āŪŗ§@?˙ōģÆŪ7ÚãąBCC$I×^u…Ļ͚§ŨģŠYĶ&.īŋ*[3ßßîj+))UaQ‘’ę×sŽåJHp°ę×ĢĢØƒkįH{ ôÃOŋčĨgĮ+mû-ūūgSÄU­ŽÖˆģ5ājIJLHPQQ‘žŸ0I‡CAAAŨģĘŪ+ŋūžLĪŧøŠËįŽīĶ[ $UëįŲĄ��ā[ķ>üTÍRk؀›Ÿ™ęÖITĢMõŋ/ŋÕîü=ΆKvNŽŪûøsmÚúJJJÔžÍÉēúŠKĸŊûö鞟ÔĀž×ká’´;?_5Âj¨īõWŠ^D÷įīŨ§{zR7^sĨ>ūėk]ÜŖ›Îé|ēļüŗM~ú•ļmOWhH°ÚžÜZ}ޏDÁÁÁšđÆ;Ú¸yĢۚ1OíۜŦž×_UéÕEĩh¸$%Õ×Ō~T›“ORâÁ¯žHR×sģH*ûʑ$-\´Xˇč̍ČHMzcŠ>øč5b¨JKKõęÄ7ÔųŒĶuĪwhW^žž{ņeÍ_¸H]ØCAAe—ų˯ŋkĖũw+6&V%%ĨzyÂDĩnŨRÇ Ōžũ4ų­w4cö\ ėßOÅÅŊ:é ŨųL]~i/mŨļM¯MšėüđZŅļ´4Í{īģ˙5lØ@7oŅķ/žŦV-šŠ~ũ¤Jį*))Մ‰¯ëä“ZičāJOߥį_zUuÔĸys¯kŋīžŅ6ō xĢÚŸÚÆļ×mOA-ųA§ŸÖQÚ‘žĄœÜ\åîÚĨCGiw~žÚŸÚF#†Üęlž¸ŖīŋģÜcŲššĒ{đÛĒE3E„‡kŲ+tÁy]URRĒĢVëŠĮŌ’~ŌĻ-[•’ÜX™YڑžĄNÛU:_HhˆŠ‹Îŋ?ųėKEEEĒËYgÕp‘¤CoÕđÛīŅĖ9ī鋯ŋÕČĄƒœŸŠŪ˙øSmųg›^}á)ŽŊOī+ôá'ŸëĪŋÖ¨CģļęŪĩ‹žY¸¸\Ãeņ÷?(::J:T~]Į’¤zõęęō^=5ņˇôüS+00ĀŖēLǏfqW[AA$éŗ/ŋŅęŋÖĒØáP§íÔīĻëŽÚt,‹ž[Ǥúõ•ܨĄēwíĸ¯į/<ĒáâJÅ5âÍ(7NHˆŽb•–zTŽ[§ļ9Y_|Æåsá  ĒÖĪ��Āwvfį*mGēz_ÖßŲl9$ @—_|xˇviiŠ^{ĻZ6OŅ€›¯Õūũ4}îûz÷ŖĪtĶ5W:˙ņđĮ_~׈Aũĸiŗß×g_-Đ ~×ģ??¸ėü˙ûcĨî1PąŅQ*--ÕäistZ‡SuÛā[´{wž^<Ußũđ‹ēŸ{–F é¯{zRũŽŋZ§´névŽęĸZ„æöģųF…‡‡ëžąiÜ#kúĖŲZūĮ 9ŽrĮ×õ\%Õ̧Ȉũ§SGm?˜o GĢ+.īĨĐĐ%&ÔV›“OŌæÍ[˝ßųĖĶU¯n]Õ¨Ļ §*=3S}Žî­°°0EEFčōËzéį_~ՁâbũšĒüü|õ礧BCCÔ,Ĩ‰:u:ö‡ÜĸĸŊ Â#¨f)M4ņåÔ AˇsmÜ´Qéę}ÅeŠŠŒP‹æÍ5bč EGEYĒŨNĶgŋĢĢoė¯Ģoč¯knēU™™>¸,ˇdgvļôãĪŋęŲņęĩ—žUîŽ<Ŋüę–įÛ¸y‹>øøSŨt}Ie9%íÛĩҞ?VJ’ūNŨ¨   5iÜHmO9IË>žü•ǧäF ]Žëp8´čģĨú;u“3ÃegvļæŊ˙qĨ_k ¯UKŖG͜÷>ÔI­[ĒËYg¸<î¯ĩë5{îûzāîÛËå’T&:*R1ŅŅJĪȔ$õŧ ģūZŗŽ\ŽĮˇ ĢG÷nåšžŒ%I*-Õ ×\­ėœ\}=á1Ī}{úlõēęúr˙k§‚'*[3žÖļwß>ÅĮÅĒf͚ē˙ž;4lPũžėŊôęë×ņõü…ęŪĩŦ‰Ûíœŗ•ēq“6oŲzĖã]­Éķ5p¤éšûŪGúO§Ž ölw‹$M›5WW^×ī¨˙öîŨ§°°0ÕILpų_x­ZÕūy���øNvNŽ$ŠŪŽeãæ­Ęܙ­Ë/îĄĐE„×ŌÅœ§ß—¯TņŸŅĪ:Ŗ“s7IķfM”‘™åÕų˙éĐNuj+44Tē˙Îáē¤Įy V|\ŦZˇlŽ­ÛŌĒTŖŋU‹.qąąēûÎQĘÉÍҚ5ëĩnũzŊ5uēĸ"Ŗt÷čQŠ$%$^ĄaĄ*.>āü;uĶf}öŗĘĘĖ”ĩoī^tRërķ$”™•)‡ÃĄ!ÏÎSČÍŨĨÜÜ] ˆP͇3ęÖŠŖ•ZíōšĻ4QĮöíu˙Ø‡Ô˛E ĩmÛFgŸy†ÂÃkš+3k§"""TŗæášÚļ9EŌáŨ=ŪԞhcØQīË.QĪŨ%Iųų{´đģ%ēũî1zųšņ*--ûęGߎuū‹˙€›oĐŨcÖž}ûœ_Ÿz{úlŊ3cvšq7j¤‰/>]îąĢVëŠį^ŌmCĒÍÉ'9īÔĄŪž6KĨĨĨZžbĨÚĩ=Ej×ļ~úå7]uÅĨZžr•:ļ/ß ›6kŽfÎ-ûjԁûŽ‘Cę¤Ve_e›4ų]zɅJĒ_OÛw¤ķŦZŊF1ŅŅZģnƒvįį+*˛|žÆîü|=õÜËę{ãĩjŲŧ™Į÷VRš¯Ų4jØ@­Z6×ü…ߊßM×ië?Ûôwę&Ŋ÷N¯Į:¤F0 pŗ&LšŦÎgüĮåyW^vąķĢp‡ü߲?4ŨŎOTļfŽÜRYm ĩã5ã­IÎŋ“5ÔđÁ4îŅņē}xŅQY,­ßĒÍ[ļĒë9gI*ÛIÕŽm}õíB x‹ķ8wkäwkāČqJKKT\ėЙ§wŌ¨aƒ=šeNW^ÖËe†KXX¨Wã���ā_îāŽ–’#ļ[oÜŧU/MzËųwÄŊë6ee፤¤DŖĮ>~Ô0yyģy‚Gn ÖūeŸ]==ŋv|\šį6oŨϝ,ŅÎėlhīžũjÕŧŠËËq7‡';Đ}ĄZ4\‰‹ĶYĪĐYĪĐõEEzėɧõå×ßĒOī˛-Aˇ>˛--M¯žöēúŨ|ŖÎ:ķtiöŧw••UūM}ĩH’BCB^Ģ–&NxÁå˜k׎S€ĘĪw`˙—ĮJe;/†  ^Ŋ.Ō+V槟~Ög_|ЇÆÜīvŽÔԍRiÉ1Įöļv;ED„;q¨Nb‚š5mĸ?VŽÖ×ķĒKį˛é?bGGbbYķ''w—ę đuõžâ÷ė-YĒ×§LÕ}ŖG9ŋsH§öíôâ+¯kãæ-Zļb•ēüęYģļ§hō;Ķĩ˙­XšÚųk/‡ųáĩF0gčŦ$ũōÛ˙ië?iē˙ŽÛ+Ŋū5ëÖëŖ˙}Ž Ī˙W¯O™Ē‰oŧĨîžÃų|iiŠžų55MI֕—]RéXĨgdjw~ž6¨ī|ŦįŨ5sÎģēų†kõ͂Åjj%&¸īJģëŗ;ŸĄ¯ž]¨wfĖÖ%=/8ęųؘ˜ŖvmŲōOšŋƒƒƒUTTtÔšųųų PpđáõZؚšášĢŧĒíH $I’væä¨Q­¤Ŗ8â˙^|5ĄJJJuë°Ã¯UqqąÖ˙Ē[ûŨ¨ƒë¯˛5rˆģ5PqœŋÖŽĶkož­}oTddDĨ×TQe.ŋüžLO=÷˛Ëįn¸ö*5jØ Z?ßįĘË\>���ķũQڎtElx4Hǧûī,ûåÎÕkÖëįߗI’BC‚Uŗf =ķč—cúVÅ1>ž{|~PĐáŪ‘‘ŠÉĶæčúĢ.Ķ:ļS`` >øß—ĘÎÉĩ4Guá÷†KFFĻŪ˙č čwSš5kÖTŊzuUXXčvŒM›ˇ(<<\įœŨŲųØÆ[*ũp“˜˜¨‚ÂBeįä(>ŽŦŗļwī>íßŋ_QQ‘Љ‰Öž={´wī^Õ8˜ą#=ã˜ã;T´ˇHIõę)Š^=]ÔŖ‡~üI-[ļ\-Z4¯tŽÄ„ÚĘßS ŨģķŨžß~˙?ÕĒUK͛ŊCÂ]í>WZĒÂÂBÕ­“¨đđZڐēŅŲ­Ü–ļCåē—Ž>Đé—ß—é͡§küŖãĘũ´ąķüØ5m’Ŧ?VŦŌēõœ?œTŋžĸ"#õ͂EÚSPpT~Me^,Zĸ;ŗuĶ­e!ŋĨ%eßúŅđAtn—Î*,*Ōŗ/žĒ›oŧVIõëéöƒ5täŨZ˛ôGįÚ{˙ãOĩyËÖcfWTfęŒ9Ē“˜ V-˙ ĪšgŸYöŲ­Ņwß˙ !GėÆđvŦ# Ô_Ãī¸W͛Ļx]§$5LǝÕkÖÕTZžâO5Inä>æāšņ´ļŋS7éۅ‹5lPįc[ļūŖ€€�ÕI¨­w?øDŎbggw~žbcĘrƒöîŨ§%ß˙¨CĘg;}ī8ũôËoÎׯ˛5"ÉŖ5Pqœúõęę×ߖéÅW&éŠĮ6–ĶŽÍÉz}Âs.Ÿ;”ĄRŸ��€īÄDGŠIã†úzÁwjÕŧŠĸú˙Q|ëļíÎckĮĮ̍h¯rwå9˙ęũû÷k˙Å%X9Ë?i ¯USgœÖáˆĮļķøĒÖč+~o¸ÄÅÅjËÖ­zųÕIēėŌKTˇN‚öī; ?VŦԊ•Ģ4bč ˇcÔŽ][JÛž]qqąúú›Ú`ŸōōŽũŨ­&ɍÕ$šąf͙§ūũnVPP fÍyW9Ų9ēīžŅjŪŧŠÂBÃôég_ĒW¯žÚ˛å­úķO…„¸ŪĘŋdéR};‘FŨ6TuëhûŽíĘËÛ­„ġsĨ¤4Qũzõ4īŊtõUW(++KoO›ĄaƒZĒ]’BCC•™‘ĄĸĸfåYUUPX¨Ųe;‡öîŨ§Åß˙ ­ÛŌ4jø`…„„čÂķĪĶ”wf¨v|œÂÂÂôöôYęÚålįw ‹Š4áĩ7Õ÷†kéœK*ÛučÃj§íôÅ×ķU;>žÜW¨Nm{Š>ųė˲Ÿƒvķ5“#6Hûöīsū‘™Ĩ{Æ<ĸWž˙¯sĢܤÉī”ũĘÎ%=%•ŊÉöŋY¯žņ–Úœ|’vddjúŦyēûö*(,TA…†BBíxįW|ŨĮ’’ĨmO×'Ÿ}Šå+Véņ‡(÷ 6aaaęzÎYš:cļŠqZ§Ŗj÷tŦ#%Õ¯§Ū—÷ŌŦƒ_{ņV߯ĶŊcŅԙsÔåŦ3ĨŌR-ũņ}ģp‘ž|dœËú¤Ŗ×Œ§ĩÅÆÆhūÂīĸ+.ŊH;ŗs5ųēāŧs9Foŧ=]M’+.6FŸ~ņĩ3weÉŌ¨ŨģĩĪéŌY_Ī_TŽYRwk 66ÆåyÃА‘Ŗõņ§_¨÷åžī|Úŗ§Āų]Ø#…††*6&ÚšsčXĒûķ���đk¯ŧT/Ŋū–^šô–.ē ›jĮÅjOAĄÖmHÕÂīT§öeß,hÔ ž5HŌûŸ|Žú\ĄĀĀ@Ŋ˙ÉĘŨ•§QCúģ™ÅÚųņą1*(,RzFĻbbĸĩhɏÚā€vįį; QÖÎlíŨģ¯Ę5úŠß.!!!zāŪģôņ˙>×ÛīLĶŽŧ<Ģaƒ2p€:´oįĖ19–Ö-[čÜ.gé‰˙>ŖĐĐ0ßŊ›öŋEĪ>˙’^š0QÇēū`7lČ Íœ=WwŨ7VĄ!ÁjŨĒ•†* ķŦYŖĻFŽĸYŗįé› ÕŧYSõŧ°‡æ/\ärŦŽįtQVVļžzö(&&F=Î?OڝęvŽ€€�ŨyûMy{šî}āAEGGéĒ+¯PÛ6§ķÚ+īP=īô‰V¯YĢҎßVų‹ā…šī}¤šī}$Šė+ɍéá1÷¨U˲}o¸V{÷î՘‡ŸÃáĐŲĪ(—‘áΟĢ×(7w—^™4ų¨įæL›ėÜūÖŠc;ÍûāãŖžvŌŽí)Z°h‰.8¯ĢWץHŪučžĮĮÅ)((Hß˙đŗ–ūø‹^{é™r_mëyÁyZúãĪzųĩ7U¯n9=ũ—sLŸ2ŅųSÁGŪĮˆˆpĩ9ų$ŊøĖΟ§>RĪ ēkÔŨčĒ+.u¸ęÍXGēŽĪ•Z´dŠķ€ŧqRĢúīcã4{ŪúâĢų’¤&ɍôä#ãtj›“YŸĢ5ãImņqązüĄ4eÚL}ūÕˇŠŠŒĐ™§ŸĻū}o$]xÁyĘČĖŌ+¯MVąŖX§wę¨ū7—=÷Õü…:īÜ..›~žßMwŪ;ÎeSŖ"OÖĀ#cīuynlL´čĢW&MQ§ŽíÔ¨‹¯@š0īƒ5īƒzüÔļ§čŋŽsq���āZRũēēwÔP}ųíb͜÷ĄöĒVÍjܰú^w•NiŨŌy뀛ŽŅģχž|^Á!ÁjŲ,EˇÜĐĮãšŧ=ŋyĶ&ę|zG=?q˛BCBtîYgčækzë•7§ęwfiH˙uÖéôŋ/įkí†T ps•kô…€ŌŌ˛ÔœÍ›7+99ŲčāËWoPR]ū…ŗēKKĪRû“ũá����P=ŧ÷!ŊōĖcū.ã¸bĮ=sÕSŠ? �����p"Ąá�����` ������Ãh¸������FÃ�����Ā0.������†Ņp�����0ĖֆKPP %%vN�����ā‘ėÜ]Š‰öÉ\ļ6\âcc”“GĶ�����øUvî.Mž6[ŨētöÉ|ÁvžT'^’”•+‡ƒĻKuļ|õ—�����°`äŊųģ„ãBlL´ēuéŦn]ÎôÉ|ĨĨĨĨ’´yķf%''ûdR�����€…Ģž Ąš������†Ņp�����0Œ† �����€a4\������ Ŗá�����` ������Ãh¸������FÃ�����Ā0.������†Ņp�����0Œ† �����€a4\������ Ŗá�����` ������Ãh¸������l÷Û3vjgnžŽģ§�����p)4$X‰ņąJˆņÉ|ļ6\Ō2˛•ģkˇâcČfx&-=KíOnîī2�����'ũŠĩqkšJ%%ú ébkÃ%;wÍ�����`Y^nŽ2wlSÁž=*)q”{.00Há‘JŦ—¤čظJĮ VJŖ$mØôĪņßpq8Jhļ������Kļ˙ŗYŲY™j˜œĸ”čX•{Ūáp(?o—ļmŲ¨‚=ģUŋarĨㅆk˙b+>Ėö ������oååæ(;+S­ÛvPp°ëöEPPbââ­5+—)<"ĘíN_aû �����¨v2wlSÃä”c6[ŽŦS”š#Í•y†† �����¨v ö(2:ÖããŖbbUXocEŪĄá�����Ē‡ã¨ĖIzâ‰'ôÄOõxPPĮQû .�����ā¸0fĖM™2E’TXX¨ņãĮûšĸcc‡‹……ēeāPũ“fĪ÷Āō÷ėŅč{ÆhŨúŋmß.‹—,Õũcv{ÜĖŲs5ųíŠö����8Ą;VSĻLŅ‚ ´`ÁM™2EãÆķwYĮT-v¸ėß@Ÿ|ú™~_ļ\99š Prr#]Ę7ž�� �IDAT~ŲĨ:ŠUKIԊ•ĢT'1QuëÖņim5ÂÂtß=Ŗ•_ۖņ§Í˜ĨĶN렖-šŲ2žŋõšēˇ|äqũöËtZĮFĮNŨ¸ISĻÍŌúõĢF0ĩ;ĩŪrŗbcĸ%IššģôęSôį_kĸ3O?MƒôSpđŅ[Ō�����ÕKāÁ¯úZŅúõëĩ`ÁĩiĶF’´`Á=öØcÎãŽb—_Aō—jąÃeÆŦ9úcÅ*õī{“žæŋzü҇”Ō$E/žôŠ222%IŸ}ņ•vüß})((H­[ļPaÆĮŪ˛eĢVŽúS÷ėa|ėę",4T_ØCō™JKKģwī>=đđjÚ¤ą^{ųY=öāũúg[š&žņ–ķ˜§^˜ €€@ŊđÔãzxĖŊúsõ͜ûŽą�����ö T~^Žķī÷Ū{ĪŲl‘¤6mÚčŊ÷ŪsūŸˇKĩÂ#}ZceĒÅ—5k×ęĸžĒUË’¤¨Č]Û§ˇę$&( 0PO?û‚6üĒ×^S:ļא”™ĩSŗæĖUęÆMr8:­SGŨxŨ5 Ķîü=uįŨtë-Z°pą˛sr§áCĒv|ŧËæ/\Ŧ¯žųVyģō­ž=.ĐųįuUAaĄFŒ­Į}PŠŠ›4uúĖŖÎsß]jŅŧyĨ5š˛đģīÕž];EGEš­C’ÛņĶĶ34}ÖĨĻnTdT¤.ē°‡ēw;W’´3;[3gÍÕß7*8(D-Z4ÕM×_§¨¨Hí-Ō°ÛîÔČáCôՎ핗ˇ[5kÔĐ [oQƒ¤$IŌ†ŋS5mæleefŠiĶĩnŲŌŖû'IĪ<CŗįŊĢuë˙VĢ–ÍŨ/ęē̝Ô—^ŦĀĀ@ÕILPîŨôŅ˙>—$ũŗ-MĢūüKŗŪ~]ąą1’¤›ŽīŖ ¯MVŋ¯S@@€‘:�����öHŦ—¤m[6*"*ÆíOC+mëf5HNņQuîU‹.IIõĩô‡•™™UîņŽįvQbBmŨwĪhÕŦYCÇ֐TRRĒ—'LTBíÚzūéņzzüĘÍŨĨŗįJ’‚‚Ę.맟ÕŊwŨŠ—ž{Jõë×Քcd‰lKKĶŧ÷>ĐČaCôÆk4tđ@}đŅĮÚļm[šãÎír–Ūzcĸķŋ^÷TƒIjܸąÛš\YģnZˇ:ܸ¨Ŧw㗔”jÂÄוTŋžž}zŧßz‹Ū}˙Cũšú/•––ęĨ ĄgŸzB><FģvåéíŠĶËîW`ŲÂũîût÷ŖôĖøĮU¯n]}øņ§’¤âb‡^ô†NmsŠ^yéyõžōr-úî{ī_hhˆšĻ¤hÍēĩ•ŽoÄÅÆĒ÷åŊXöZ§gdjÁĸ%:ũ´˛¯-ũēI ĩãÍIjŅŧ™vįį+ŖÂ:����T?ŅąqŠ¯­5+—)7{§Ë_ r8ڕŗSëūüCąņ ŠŽ‰ķCĨŽU‹†Kŋ›oTxx¸îûÆ=ō¸ĻΜ­åŦ8æĪ9mø;U陙ęsuo………)*2B—_ÖK?˙ōĢ;;÷œŗUŖF˜Ôíœ.Zģnƒ ¯¨h¯¤đˆpĒYJM|ų5hĐ Üq RPPÖŦ]§‹ëļĄƒęqM‡;”‘‘Š ’<ĒÃŨø7mTzF†z_q™ĸ"#Ôĸys:HŅQQJŨ´YiÛwčÚ>WŠfšŠŽŠŌeŊ.֊Uj˙ūÎųģžsļsˇLĢV-ĩcGē$éīÔTåįįĢ×%=ĸf)MÔŠS;¯î_ÆIJKÛ^é:°"mû]zõ0t”š5mĸˇô•$íÎĪWddDšcŖ"Ëļ–íŪ]}~—����plõ&ĢQ“fڙąCĢ–ũĸe?_îŋUË~QVú5HNQũ†ũ]n9Õâ+EqąąēûÎQĘÉÍҚ5ëĩnũzŊ5uēĸ"Ŗt÷čQŠ‹-w|fVχ† uÔXššģ^K’”XûpĐmôÁ Õ]yy ¯UĢÜ9MSš¨cûöēėCjŲĸ…ÚļmŖŗĪ<Ã9NE9ššzsĘÛę{Ķ Î_w5%&”Ũ-(($EQKeu¸?3k§"""TŗfMįãmۜ"Iúé—_ĨČˆÃ ˆēu먴´T999Š‹+ë�ÆÄŪ ĸöž§ĒYãđØuëÔŅJ­öøūE†Gj˖­.īgU$&&hâ‹OkGz†ĻÍœĢ “ŪÔ#†¸<ļTe2|����Ž7Ĩ’ĢXĐŌ ˙ŗŠ —CâbãtVį3tVį3t}Q‘{ōi}ųõˇēņēkĘĸđZĩ4q .Į9´‹Ĩ¸äđ™’’ƒļ]¨!ƒ¨W¯‹ôĮŠ•ú駟õŲ_ęĄ1÷ĢV­šåŽu8šôæuėĐAgžūk:–#?üWV‡ģņSS7JĨ%^Í-I‡wßĢqāĀT¸sŽØSYŨ ĩfæØÔã VŖ† Ô¨aEEEęŽûŌ-7]§čč(ååí.wl^^ž$):ēú„(����Žmû?›••ІÉ)J‰Ž=ęWˆ‡ōķviۖ*Øŗ[õ&û§Püū•ĸŒŒLM|}˛ŠŠŠĘ=^ŗfMÕĢWW….ž”˜˜¨‚ÂBeįä8ÛģwßQ_ÉĖ<üĢF;ŗŗ Ø#vqR\ėPūž=JĒWO—ôŧP}@1ŅŅZļlųQĮž˙á'Úŋoŋn¸ŽĨš —$åÜéâŽwã'&ÔVūž‚rķũöû˙iõ_k”˜˜ Ũģwkwūįséé PÂ1B„­={öhīŪŊÎĮv¤gxT÷!ų{ōaŽŅņķ¯ŋkø÷:i’œ!J PķĻ)ĘÎÉUÖÎlįķ­]¯Ø˜h%&$Ģ����`ŧÜegeĒuÛЉĢíō'Ÿƒ‚‚¯–§´SvVĻōrs\Œä~o¸ÄÅÅjËÖ­zųÕIúkí:åäæ(==C_}=_+VŽRĮöeY!ĄĄĄĘĖČPQQ‘š$7V“äƚ5gžō÷ėQaQĄfĖžŖIoL.7öâīžWNnŽ ‹ õÕ×ßĒUĢ–åžrsȒĨK5ūŠį´#=]%%ĨÚžcģōōv+!ąüķå+ViņwßkČ  ”ÃáÃáPIIŠĮ5¤ÄÄĨĨĨyT‡ģņSRš¨~Ŋzš÷ŪĘŨĩKë7lĐÛĶfČáp(%9Y $éũ?Ōž}û”ģk—>ūßį:­cGÕ¨QÃíkÔŧyS……†éĶĪžTŅŪ"­]ˇ^ĢūüĶĢû÷ĪļíjĐ žÛš<Õ˛y3efeéõ)īhGz†6mŪĸˇĻÎTĢ–ÍĨ¤úõtjÛSôōÄ7”ļ}‡Ömø[ĶfÎU¯‹/4V����Ā>™;ļŠarŠķ×ûôéŖUĢV9Ÿ_ĩj•úô)Û ŦS”š#ÍåXūā÷¯…„„č{īŌĮ˙û\oŋ3MģōōŦ† hČĀęp°áŌõœ.z˙ŖO´zÍZžũ6 2H3gĪÕ]÷UhH°ZˇjĨĄƒn-7öŲguÖķ/ŊĒˌ 5lØP#† rYC×sē(++[O=û‚ Ŗ៧íN-˛ûÏ?Ēho‘Æ<øhšķ/ŋė]yŲĨÕt¤Ö­Zj͚u:ˇËŲnëTéøēķöšōö4ŨûƒŠŽŽŌUW^áĖq>d fÍyWŖīy@aaajwj]suo^Ŗš5jjäˆ!š5{žžY°P͛5UĪ {hūÂEÕ}āĀmLŨ¨Ë{]âŅ|žˆŅÕ”wĻkčČģTŖf ĩ=å$ šĩŸķ˜{īЉoŧĨ;ī§a5tÎŲgęÚĢŽ0V����Ā>{”}8ĶĩE‹ęŪŊģ,X IęŪŊģė|>*&V[7Ž÷yĮPZZZ*I›7oVrr˛ŅÁ—¯Ū ¤ēž˙úFAaĄFŒ­Į}P “’ÜŸā'[ļlՓO?Ģįž¯¨¨7Wdņ’ĨúvūB=ņčƒÖĻĨgŠũÉÍ}P���� ēZöķ÷ępF—r3FSĻL‘$ 8PãĮw{NEËWo0ū™ĶUOÅī;\ūÍ7n¤ļmNŅ_}ŖëŽšĘßåØb˙ūúōëotuī+øu ����@•Œ?^ĩūÚī¸qãü\Måh¸øYŋ›oÔώW‡ömÕĸų‰ˇĢãŨ÷?PŗĻ):­c—����8T÷FË!'dÃ%ŧV-MōēŋËđHdD„^xvŧûS7ŨpŋK�����‡ƒ‚äp8\ū:‘+GąĮĮú‚ßĨ����� ĸđđHåįåz||~Ū.Õ ¯>ų¨4\�����@ĩ“X/Ii[7̏¸ØíąÅÅÅJÛēY‰õĢĪįĐp�����ÕNtlœbãkkÍĘeĘÍŪ)‡ÃqÔ1‡CģrvjŨŸ(6>AŅ1q~¨Ôĩ2Ã�����˙ę7LVxD”2w¤iËÆõ*ŠĐt Rxx¤$§TĢf‹dsÃ%((PŽ’˛‘�����x/:6NŅąfš)û+4Ä7{Olí„ÄĮÆ(;'OŽ’;§�����¨ÔūÅÚ¸uģâc}2Ÿ­m¤:ņ’¤Ŧė\94]āšåĢ7øģ����Ā $4$X ņąJŒņÉ|ļīŖIĒīlŧ������üŽ�����` ������Ãh¸������FÃ�����Ā0.������†Ņp�����0Œ† �����€a4\������ Ŗá�����` ������Ãh¸������FÃ�����Ā0.������†Ņp�����0Œ† �����€aÁv`įđ.•––ē=ÆÛē<Ķ[Žj¨8OÅcÜÕaå~ģ›Ķc¸ēŽĒ^ģ•u`ĮŊđļOÆpW—'÷Ķėxß����Āņ‚.������†Ņp�����0Œ† �����€aļf¸TäĢ,ĶuXÉŲ°Âš%v䘸ƒ/ōBŦdŖ¸ģŋ&ōkŦ¨.īE����8QąÃ�����Ā0.������†Ņp�����0˧.ų"œy­ä–Ø‘}âmf‹'ü‘Ņâíu˜¨ĄēŒaĮœž¨Ë_īE����8QąÃ�����Ā0.������†Ņp�����0Œ† �����€a~ Í­.ė\ĩ€ëŽĄē#˜×WĮW5X×J Ģ/‚cMŦ5_����dž.������†Ņp�����0Œ† �����€a˙Š —Ēf\ؑâIn‰ģį+ŽaĸNų ŪžãÉņžČ-1‘‘ã‹1̚™����°;\������ Ŗá�����` ������ÃüšáâĢė wųîžw•ŗáí˜Vx›[bĸN;rJ*ōä^U5ŖÅD.Œ‰Œ+ušcĮû†����0‹.������†Ņp�����0Œ† �����€a>Íp1‘_ab^_äšx[Cue%_Å×ęÉkVÕ×Ũ“{abmU5ģĮ“ûí¯÷"����ü[°Ã�����Ā0.������†Ņp�����0ĖÖ —ã%§¤"+ųUÍŨp5FUį°2†‰×ĖÛ1ŦÜ +5˜ČlŠ*O^3o_WOę<^ߋ����pŧb‡ �����€a4\������ Ŗá�����` ������Ãl ÍuĮ“pPwaŸV‚cŨÍáIxǎĢVBK}1GUįtuގŦÔí‹XOîEUƒŽíXVŪWž ���€v¸������FÃ�����Ā0.������†Ųšáb%ÂÛ< #&21Ē:‡'Lä՘¨ÃÛ\w÷ĶÕuU5CÄĘkfâu¯jĻ‹'ᨑÕãîy2]����Ā;ėp�����0Œ† �����€a4\������ ŗ5ÃÅ oŗ#Ŧd`x›_á+Õ!7Ã_×ušēwUŊŋV˛fÜÍi"ËĮ“1̚WcGÎ����œČØá�����` ������Ãh¸������fk†‹ģœO˛&ŧÍĢp•5QÕ\OętwŽģë:Ö<Uåmv‡ˇŲޏģ×iåūVä‹ŧDîT×Ü����ø7c‡ �����€a4\������ Ŗá�����`˜­.žā¯ü "U5ƓsĒZƒ•:ėȉqW“'ux;Ļ•\˜ęúšyûŧ•l$;2‰����āxÁ������Ãh¸������FÃ�����Ā0.������†UģĐ\oÃ;= %ĩZÕ1Ũąōj%¤ÔŨũ2ljGČĢģ9ŦÜ‹ĒŽįxrŪ^[u §­.u����@uĀ������Ãh¸������FÃ�����Ā0Ÿf¸x›Ãá朊<ÃÛL +cšČ¯°’Oãîywcxûŧ§ķz3‡îÆ´#'ƓŒwLŦY>&˜X;����pĸb‡ �����€a4\������ Ŗá�����`˜O3\*2‘5aGvŠŋÆpw-vä}˜`GV‡ˇŲ=V˛fŧŊßV2‡ŦäōTuøb-����*Į������Ãh¸������FÃ�����Ā0[3\|‘_a"WÃÄŪ2‘_ãɘVŽqĮJ~Šé9Ŋ}ŪĘ¯Ë“×ĖŽ "+īŖĒ˛2&š/����p;\������ Ŗá�����` ������ÃlÍpŠČ“ėoķ*ėČtą’Q⏜ ėČŲqĮĘũ41GUĮôä^x;‡'cšXKU}Yɯ!Ķ���Āŋ;\������ Ŗá�����` ������Ãh¸������æĶĐ܊LÉz2FUÃ<­ņZaG]ŪÎieLoĪņä:Ģōę‹uáŠ÷ĶŽ]wŦ„'W×`h����đv¸������FÃ�����Ā0.������†Ųšáb"Ã].„•9ėČîpĮs˜ČŲđGމ9ž¸ßŽxģļ\=o%Æ4+īŨs����¨ŽØá�����` ������Ãh¸������fk†‹‰ė wš&ō?ÜåUؑ1â ;ō@ŧĶ _ÜO_ä˜Čú1‘™cĮÚ˛˛–�����žc‡ �����€a4\������ Ŗá�����`˜­.î˛;<Éņ6ÃÕņ&r4ĒĘJψˇŲ'žĘšŠjF‹ŋ^3wkËÄŊņv ™9ūš‡œ����86v¸������FÃ�����Ā0.������†Ųšáb%#ŖĒš%žä¸ã¯Ŧ™Ē2qíVxû:ۑb…?ōVÜåôø‹•ŧ%����ĀąąÃ�����Ā0.������†Ņp�����0Œ† �����€aÚk"ŧĶDXǎÁ°VBH}Äë`cģęrĮÛ`X¯‘‰ķí!öö^xō h����c‡ �����€a4\������ Ŗá�����`˜­.YÉtđ6ÄÕU͒°’åa%_ĨĒc¸ĒĶ]–Œģã­01†ģ1ŊÍ5quŽ•1܍iâūڑbĮ˜vŧÎ����pĸ`‡ �����€a4\������ Ŗá�����`˜­.&2ŧ͞đ$ģÃė¨ĶJ.Œˇsøƒ'÷Â]•œw<šßŪæęXÉōq÷ŧ‰LĮ“é����‡ąÃ�����Ā0.������†Ņp�����0ĖÖ ™#îXÉÄđļ.Y)žänx›ÍaG>ˆģ9\QÕ\_dxrމœwcڑ…búõ°Ē:æ���€ŋ°Ã�����Ā0.������†Ņp�����0ĖÖ _eŸxËÛLOrLÜņ¤no3F|1ĻĢë4ÕabëÄDF‹ UŊŋVî§Y3dē����ø7c‡ �����€a4\������ Ŗá�����` ������Ãl ÍuĮ_!ĨžķôG˜Ē• Ķęk"üˇ:\‡+vŦ5+u{{Ž'ëē€e����øˇb‡ �����€a4\������ Ŗá�����`˜­.î2Ŧd>ؑÕQÕį%÷uų+ßÂÛĖOޝjŒ'ë ĒŲ'vŦ-OętwmžČ:‘ŗ‘����āxÁ������Ãh¸������FÃ�����Ā0[3\*ōEūŠ's¸;ĮJމuy;Ļ•Œ‘ŠĪ{2Ļ;&rKŧ×ĘV^÷ŠÜÕi"×ÄĘëîíŪžī 2]����ü›ąÃ�����Ā0.������†Ņp�����0˧.y’9âŽ/r"ŦdŖXQÕ\ OÎwwŒ?ŽÃ“1ŧŊV;օ/˛hŦÎczLų5����đoÆ������Ãh¸������FÃ�����Ā0Ÿf¸x’ųāîdŒ¸:ŪJ~Š;îÆ´ríŪžceÎĒž&&î¯ë“1ĢúYÉŨņÅ{ēûī‹ü$����8^ąÃ�����Ā0.������†Ņp�����0ĖÖ wYV˛;ŦäœT5kÂʘVŽŨÛŦdŽx2Ļ;į4‘cå56‘ˇRÕŦ+¯™?^C+u���Āŋ;\������ Ŗá�����` ������Ãh¸������fkhnEVÂ>ŨãmhЧķz;†ˇÁĻ&n=švoįĩr?Ũ1tlĮkf‚/‚Ž+˛#8ÚDĀ5����ā0v¸������FÃ�����Ā0.������†Ųšáb"˙ÃÛ1ŦdŖ˜ĶŨ9&˛eėČ5ą2GU_W91Vî…/raǚ•âj oëô$oÅ]&rvLŧ˙���āxÅ������Ãh¸������FÃ�����Ā0[3\Üe:x’5QÕ9ėR՜ +™VT5¯ÆĘũ4‘kâíŧūĘ ņ6ĮÄŲ3žŒiG~ ����ā0v¸������FÃ�����Ā0.������†ŲšáR‘•|™Ūæl¸;ßę1î¸ĢËÛį=ŠËJˆˇ÷ËD.LUŗhŦŒaGĻŽ'ėČ1˛˛v܍én����ø7c‡ �����€a4\������ Ŗá�����`˜O3\L0‘5a‡ę_áIv‡ģsL䁸cG–‰yŊÍ 2Q—‰×Ė“ķ̚ģãÉ9ūzī���@uÄ������Ãh¸������FÃ�����Ā0.������†Ųšk%ˆĶÛ1+˛j"ĩĒÁąVņöøã%ØÔŨĩø#¤ØWsē{ߘŽŽ.c����‰Š.������†Ņp�����0Œ† �����€aļf¸Ø‘Ãa%7ÂÛs|‘Õa%#Ã]žĘĖ0]‡'š;Õá5ą˛ļŦ¨ęzõä~z{­d#���Āŋ;\������ Ŗá�����` ������ÃlÍpq—ņā¯\sx›câIÎFUkpÅÛyŦÜ+Ų'GrUŖˇ÷ĶÛ9=™ÃVŽÍôU=Ūę9ų+S����Ē#v¸������FÃ�����Ā0.������†ŲšábBUŗ<\cboķ*|‘‘a"ĮĘũôļ.Ojōö~šČČņÅZķäøĒ^‹•k÷Åú���€v¸������FÃ�����Ā0.������†Ųšáâ.K“ü wĪû+[Ž\\‹•ŧĶcZYVÆt7‡?2[<Y&ęt§ēŧ¯����āDÁ������Ãh¸������FÃ�����Ā0.������†ŲšëŽ•°OoE­Ėc%@ÔÛ°_ĢĮTÆJĐŠ•sŧ q5F{ĸĮÚą^=á‹k����Æ������Ãh¸������FÃ�����Ā0[3\ÜåF¸Ę€đö;rK*Îa"Ģ™#&ę°RgU3]L0‘ŗcĸnoī…‰÷€'įWõÚ=����P†.������†Ņp�����0Œ† �����€aļf¸Xɚ0=‡‰yŦœī.ĢӜ y6U} Ŧä–Ø‘ébGގ¯Ģ•9íȁ1‘[ä‹÷&����ü›ąÃ�����Ā0.������†Ņp�����0ĖÖ —Š<ɑ¨jψ‰lOXÉæ¨ė|OÆđGö‰š0žŒWց‰õęŽOÆôö~›Č5ōGF����œHØá�����` ������Ãh¸������fk†KUs8<a%kÂÛs<9ŪŲ1Vr7*žã.īãxÉŨpWˇ•ė;îEU3t<Óē­dÉxëxY;����ā ėp�����0Œ† �����€a4\������ Ŗá�����`˜­Ąš&X GuĮÛ SOBH}�l%„ØA°î¸ĢÁÕu{[ˇŋB_Ŋ}Í<™ÃÛkņä~Úh{ĸ0���€Øá�����` ������Ãh¸������æ× +šVÎņ6ŗÅʘYÉcņvL_dwx2fUŗgŦœcGۊ U͡ą2‡/˛S|•����' v¸�=�� {IDAT�����FÃ�����Ā0.������†ŲšárŧæSTœĶŽëđEžˆ+îæĩã~ģĶD>ˆ?ęŽ.cšP]ę����€;\������ Ŗá�����` ������ÃlÍpąÂŲîōUÜī -ŪÎkĨNoī¯ žŒYÕ,+uģ›ÃJŨŪ>īIŪžoä-���Āņ‚.������†Ņp�����0Œ† �����€aļf¸ø"ÃÁD&†‰ŒoĮôUÎFEvänø"cĎĩdĮĩ{{mŽæŦęÚņUæŋÖ0����Øá�����` ������Ãh¸������FÃ�����Ā0[CsũĒi"\Փē튭jí&Âh= ’u7‰0ÚĒŽáĢuā-+!ĪîÆđ¤N;î'Ąš����plėp�����0Œ† �ü{÷cWU€øŸv SÚm)-Ep@š!+’n (Q‹!€)1DŒđ" ĸ ‰ŒI%!‚š€D)%ÜKPDb)0%`/”RJ§…B§ãÆN[:—ĩĪ9ųžd29ûē֞yúŗö��� ¸����Vk‡K_%z5úL7J#:Fēŋ?÷čĢ?ãč}3ÎÁŒk(Į'õô‡4ĸŗĨ]>%ŽYĮ˙��ĀHb… ���@a���€Â.����…5´ÃĨ¯Fôƒ FŗēfÚûRâų Ļ'ĻÄ8ļe[ĪĸŽŪ—:úkJ<ßūœ3ķûŖ?=G:[���ļÎ ���€Â.����… \���� kj‡KŖ ĩkĸ?į×Ņ12ĐN—ūôllk\ũw‰Ž“ĄŪŖ]>} æž%zxęxŪÍčĢ��IŦp���(Lā���P˜Ā��� °ŅáԊļÔw1ÔNŒū_ĸgc ×(Ņg3Ô1lÉ@ŸwŖ:J:Žū߈˙­fôę���´*+\���� ¸����&p���(Lā���PXSKsU˛š­ÂĐžŸûŋĨqĩ`ĩ?×ÜÖķiÄķLëPK_ˇļm¨ę(û­cîC-´ĖŗLŠŽ’\��€­ŗÂ��� 0 ���@a���€ÂÚá2ĐnŠFŲVEÆ=ĐŽŽÁtŖ4âšũ1ÔūšJô–ôՌ“ūüÍqÍÁtŅ ĻŸ��`¤°Â��� 0 ���@a���€ÂjípiÕ‡ŽĢŽyôįšuŒŗU˙&5˜y4ãīیįŨŦ˙-���zYá���P˜Ā��� 0 ���@a���€Â.����… \���� ¸����&p���(Lā���P˜Ā��� 0 ���@a���€Â.����… \���� ¸����&p���(Lā���P˜Ā��� 0 ���@a���€Â.����…ĩ×yņ%K–Ôyy���€›>}zí÷¨5piÄ����ZWŠ���� ¸����&p���(Lā���P˜Ā��� 0 ���@a���€Â.����… \���� ¸����&p���(Lā���P˜Ā��� 0 ���@a���€ÂÚëžÁüÅÉWnIVŦM6ôÔ}ˇæioKόKn•ÕŲėŅ����ÍTë —ų‹“c~™,íŪaKRÍoéšjžķ7{4���@3Õ¸|yn2Ės–Mĩ%==Õŧ��€‘ĢÖĀeųš:¯ŪĸڒWģš=��� ™j \FÔę–î¯O���Ρ���&p���(Lā���P˜Ā��� 0 ���@a���€Â.����… \���� koö�ļæķÉwŽNß3Ųm§äõÉ˓+LūūrŗG���°u-šÂåØ}’{žžŧöfrÎŧdæuÉė;’Šã“ûf'íŪė���l]KŽpš`f˛hEröŧŪmO,MîũoōčųÉŅÉŋ_iÚđ����>TK.;ŽŽ~úZķvrđœMˇí>.šę¤äø}“I;%/­NŽũ[2įŅj˙#ßHÖŦONŧqĶķū|v˛kGräõIû¨ä˛c“Y‡$ŨĩēÆĪIŽ{Ŧ–é���Ã\KžRô§g“Ļ$ķÎL>ķ‘dTÛ֏ũõ—’#÷NÎŧ5ųÔĪ“+LŽ>)9ũ€j˙-O&_Ø7ŲeLī9ģŒIŽ˙Xōģ'ĢĪ?=ąę‹ųņɌ9ÉՏ$ל’Ė>ŧŽ���ÃYKŽpšáņdŌØäûĮ&gTæÎ1šsQrķ‚dŨ;ŊĮ^twŌŨ“ŧ°ĒúüŸ•ÉŸMNØ/ųÃĶÉŧ§Ēđä”ũ“ßž°œv@2ē-šua˛ķ˜ä›3Ģ2Ū›ūUíîąäđéÉĨĮ$ŋúgC§��� -šÂ%I~ōp2íĘ䴛“ŸHö˜üâôäŲ‹’§ô×õvráɂo%K.I–]šĖ˜ZŊ^”$Kģ’‡'_<°÷œ3Jî}>Yž69tęõĨŋ>ˇéũx!Ųor2~ĮÚ§ ��� 3-šÂå=ëŪIîzĻúI’ãöIn˙ZÕŲrōMÉŖ’ŋœ[u°\xwōˊdÃÆäÎŗ6ŊÎ܅ÉU'&íÕ9'|<9˙ÎjßÎīžjt˙뤧§÷œ÷^cš6>yîĩZ§ ��� 3-¸L_­\éz{Ķí÷ŋÜą(9ųÕį™{%‡LKŽš!yøÅŪãόë}Å(In*šöÔę5Ŗv¨ļũ~Qõ{õ[ÕīŗnK.Û|,/­.3'���`ähšWŠĻŽK^ē8šøčÍ÷ĩ%ŲˇdYWõšãŨ¸håēŪcŽØ+Ųgbuė{VŦKî{žęq9ũĀäîgĢ^˜$Y°,YŋĄúļŖg^íũYš.Yą6Yß]Į,��€áŦåV¸,_[}KĐeĮ&Ķ&$w=ŧöf˛Į„äÜÒĪíĖš[ģ`iōֆäÛG$—ß—Ė˜–\qBÕĮ˛˙”*Dyemuė܅ÉŽKvéHfßŅ{ŋ7Ö'×˙#šüøäÕuÉc/W_ }Í)ÉËĢ“SĶđG����lįÚzzĒæ’Å‹§ŗŗŗėÅ/üšgšĖūtōÉ)Õ7­~+yüUķÁ‚ÛY3ǐeęøj˙LöÜ9™;Ģzčā9Õq;’åß­zaĻ^ąéʕöQɏKÎ9Ŧ v–uUAĪ÷îé] 3P=?üÜ��€íĮ–2•– \ļw���ļ”Š´\‡ ���ĀöNā���P˜Ā��� 0 ���@a���€Â.����… \���� ¸����VkāŌVįÅ[Xģ ���F´ZŖŠ’ôÔy‡ԓė6ŽŲƒ����šŠÖĀåļ¯&m#l™K[[5o���`äĒ5p9Ē3yčŧdڄá˙šMû¨jžWÍ���šÚëžÁQÉŌKęž ���@ëæëN����Oā���P˜Ā��� 0 ���@a���€Â.����… \���� ¸����&p���(Lā���P˜Ā��� 0 ���@a���€Â.����… \���� ¸����ö~āŌŪŪžîîîfŽ���`ģŌŨŨöööÍļŋ¸tttdíÚĩ ���ĀöŦĢĢ+cĮŽŨlûûË¤I“˛zõęŧūúëŲ¸qcC���°=éîîÎĒU̞f͚Lœ8qŗũm====<xåʕYŋ~Ŋ׋����ļbôčŅ3fL&OžœŅŖGoļ“Ā���€Ąķ-E����…ũœmždÜž9§����IENDŽB`‚�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/profile-password.png������������������������������������������������0000664�0000000�0000000�00000072671�14156463140�0022675�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��X�����ĒĒRÉ���sBITÛáOā���tEXtSoftware�mate-screenshotȖđJ�� �IDATxœėŨ{\WŪ?đ/JČ@…Ä6Ą °ĐJ°]ģ•°ŽV­ik U[°ŨŠ]ÛŌ›ÚˡÛjģŽhû<ĨWqÛ*ŽļĐjĒÖ`k‰<ŋšØV‚7â  ’j.L—ßáŧ ‚6Ÿ÷k_Θ9sĸ¯OΙ3^ííí�����āIF w������†šˇë?­­­gΜq8­­­ÃÛ �����€A7räH.—+FŒAD^ííí­­­555 ÃøųųšJ�����~MÚÚښšššššnšå–#Fxĩˇˇ×ÕՍ1ÂĪĪo¸Û�����p ũ÷ŋ˙%ĸĀĀĀDIJŦ¯¯īp7 �����āÚōõõmjj"×b ---^^^ÃŨ$�����€kkĈ---„Uã�����Ā!����€ĮA�����ƒ �����A�����<‚�����x!�����đ8B�����āqŧ¯ž ¯#¯ž�����€ËÔŪÖz•5`D�����<‚�����x!�����đ8B�����āq„�����Āã ����€ĮA�����ƒ �����g^¨ ����ĢâdÕĻÍyį›ÚÚچģ-pƒņōōu“ß#ŠsÃBnúŗcD����¨âdÕ{ë>Ž?˙_¤ €ööö††ķŲītĒÆ:ôgG���€ĘũwŪp7np^^íím˙ĘũtčΌ ����Ôđß˙wāÆįåUßĐ0ô§E����€áÔŪŪ>ô'E�����ƒ �����A�����<‚�����x!�����đ8B�����āq„Hģd‚4ęū\ˍTķv-0Vz˙ÆĄ8���Ā�ŒńņŊûÜä=JuëãECÜ"đp}˙mü•°éķ˛ķ tEF‹Ũá .—'’ĘbU)iIbf¸Û���āQü'>đä ŋ-9PüãĄŖ5,y ‰úzS“­˛â¸˙øĐánbŪŖ%ˇËƆø7YMeúcu-ÃŨtŋÚ dŲĩ$mŠÆė ŽXŽTIø ąleŠÎP°FWĢĘĘW/.Ŕˇ:ßäčūáō„Rš2A!á_Ŗ.ĘV”“m§?Ĩēćk͝Ë6ˆS3’$CŌŦ ąc‘No2Ûí–ËåķEb™RĨô|Î7ŽŅō’—Ũ-䊯”~鄇 Üū*qšBqBRÆŌå*ɰĩi�ô/˙.­t‘ļ`‘x¸[�pãâø‰?õÄĄĒŽ­XSøķ1WÄ`B†ši=øFĖxęŅ;B9DD?5ądÛۛ`e‚éĪ<5ÕüŅËÛ*/ûÉË˙,Ųûö?ŋ¯ā9¯ o†ķh†CDÔrļĻŽi¸Ûs•ŽĶ t<w‰OčDåá„ŋ|2€ÃYũË)+W–šķNf‚ûMąMˇbᓹÚĖôÕŌ‚į‡ûîđFĀMž-ë¸?wØMúĸÂ܍öÔ§nŦûׁĒÔfëxŠiŠÁÎ}–ĸš+U)b>Čn7éĩÚÜ[jFŌāŪa3bål ĩÎãŠ^ĪNí¸>ģĩH“•›‘lÉ.|WuŊÆj��d"g“õdYé÷>~zôŒŋ?{Į#Ë~§6—•ūtđįCUÃŨ>7Ūõƒ)ˆˆˆ7ûžÃĮsŒėPĩ N¯ŲzäôųĄ:Ũåāߖ’úĐī‚Čé$"gK]áīīŽ!˙ۓĶĨ˙Ų”Wrú2ļ ´ęo´ujmmuũųį[ÛGP›@$P(ÛZÛÚOPŨ•š+ķŦěũÎĻĖ„^ßąķ•Ë7ŊÃŧŦĘ–¨Ÿ/āÍģVgįhu&ŗŨÁđÄRerÆŌE ""›æAåŌRUöÁ5Iģîz"6Cëˆ]^ôyZgŪ*ËJœŊžŌ5…ĪwUx%Î}\øå{iĻîķÔ îĢôøĨ:iĮūļĸՙkōt•v"ĄTõTærŪ€:ę2pEBI÷�X*¤ėŖĄL)‰ūõĪ.´Y­,]ƒž­,-ĩ2ąé)ŠŽ˜/‰y”Ģ1[,$܁JžDvŨÄ F(U(dŋ)TąüŲĒŦ­E•‚ÁY�€_=o‰,îæ–Ŗī­Øyēw”āŠ'LO˜:Įé<[Ĩ÷oŲQ\Ų0,mt4vŦ¯"ßßJEdŦĒœ¯4– Õš.‹īøDX6ũ#ûįs=ËũEáqžöõūß ?ŪpYh؂Pkkkhh(yyyuũŲŪ~hôīî$ōrœ?Ķęt´ˇˇ ¤jS^ž‰HēhiīäÂ(—­U^äč÷'¯41rUúōebžŨ¨Ũ˜ģfĄÖ°Fģūn‘<IJƒÁHI÷szŪÁe¸ĻĸR6Mä:›­Ô`&^r‚”ČÜY'˙ ŧë1åŠtyšĘÔE’ŽVŲtyz#OS‹ˆČ¸"%}Ŗ™'O^–ĸ:Ėڍ y×, õ" š›Ŋ#E˛ŊVk0Yė,ņ„Rš*I!aˆˆlúœŖT­°í21 éi ĻR¯ÕéMVģƒ\ķë:÷$›i—VwŦŌf#âķÅą*U‚+wY´Ų­ō4šMĢ;fąąÄeꔎ‡ģX‹QĢ՛ŦVËå %2•Z)šD.c-zFoļ˛$É”î{یģ ô&‹Õî`¸BĄLŠN’ō‰*weįėDæė:iō˛”hb+õÁląŗ._(•ĢTŠKĩŋÆ\øu’(!u‘{k:­Ūhĩ:ˆ'ËTĒqGžą™vitĮ,V›ƒËŠĸ”ŗ“ĸųũ—÷œ×OWģ>Ŧd× 5˜m,1"ŠjļÚU-k1î*ԙ,6‡ƒá Å2Ĩ*A:XÉJ"“rÉ`˛‰ˆČŦy93{—ŅlwpąLŊtåëŖŽ–]Y™Ųųz“ŨÁp…RUÆō•)2ϟrŨ’ K­+u›îæu|ŗ`KŲ¸˙ui—LXj_cXŸÄ؊r2ŗr‹ĖVņÄrõŌ噎I’•ëTŗķ×dX×djyË´Ÿ§‰lEYKWäęÍĈ•éËՃté��ž‡ņoąü|¸ŠAĖíĄNSÉq÷ĄŽĻ˙|ĨąŨ>gRčÍáSfœ/-Ž<ÚšefâÔ‰S{ÕĩŗpĪŽÂ=×ļŊ-MΠʚšzßæû†ũnÎė;bo ōã8ëOUęwlĶ;ßšiōCsâcnaœ§Ģô;÷ļtŨqMyᙉG7zOĄÅaë~Ōäo=-y`N|Ė-Ŗč\ea^ūî“ŦûÔ8˙ɏŧšX÷ņ†ÚÛįÜ4ŠÃ֕nÛ´ĪŌĢ5×ē¯üƒøt˛°W ę U?Ŋ€ŪÛđcõ •…†3‘[ rũĐÚÚÚÖÚÚÜ`imi&ŅŪÖ:€šY“ÉL$”+ō=ŗ%oe–‘™ũŽvm’ë~O•”ĸMWįdŽ)ē{M‚\.ĻõŖ…d""ĸJÁ!TŠ„Z­VOĒ""V_d"FĨTô¨Vt2I6WWŸgZÔ5yĪĻË+up•Š*>ĢÉÎ3“8uũŋ3]Š*)%öå)iĨ¸Ú°Ųl.Ÿéˆ;7ęHĒJV‹ųŦU¯Õäo¤ôEÉXÖTd'¤Ĩ ų|ļ,/ŋČĢNU‹b­z&_Ãd¤ČÖŦؘoâ+“ĶbE kÖjrķŲôô$×Gį°´&•:=‰Ol™&'?O+]Ļ–[ĻÍ՘ÅĒ”T)q7—žvąĄļL›¯ĩ U))2YLZ­ÁŪšÉ˛+Wcä+SRSø k3é4ųyĖÂE "IRz ›gUϧÉD kÔäÚ¤ŗSÕb†XkQ&OÃ*E:€($’JšĨĨyyŒJ)“ˆøÔ`3jr5Vą*%]Ę#›IŖÉËcS%‰ˆ,EyųĮø‰ŗIųÄÚLē‚ü\JĪHõWîŪëjÖfÔ•ę´ ÃZvåæh´bIŠŒĄĘ]ųŗX6[Ėk)Õj4yL×'|ĩl+‘P,$"2ŽH]ǧŋ“Ÿ,å9LYK2ŌyšÂįĨdÉ}ri.“úÎįëŖxŦY—Ŋ|ÅŖ™Â×$ô]ž2AN/ëJén%ŲJK-<c,­$…„ˆŒIĶe ŗ’ŸĖeÔk6­— ŲR͊Ĩ÷Û˛‹×$1DÄuØ ßŅ(—nʗŠEdĶ<ųäzŗ*kĶ&ĨĐfČYąFë _�\ļ‰Ĩß%.Ôw–ųqĪ÷?oúŨ¤š‘ŊwwŨÄģßßE "ĸÚCzķÔP1ĮŊ¨ÔØsu^&úGÕcËwäŧSvļ… ™<û‘ķÎŦÉųŋsDLô fJNíx/ëĐYŨĻž1Õ}|‰ÃW$ŽũbsöÖsŪ1)‹OI ¯8ôéŋŪú„å+]üМߕūOņiˇŨ[œ-ÄDßũ‡sŸŽë“ķŪŋ™ōȋsfO1å|ß3“\ûžęL LôÃ/ÍŋŨˇ÷æ�ŠúŠGŊßūמ( ÛōŲ­­­^hiinkuļļ4ˇĩ4ˇĩ8ÚÛ2"dĩX‰H(ČĶmžÁÁ(U ˛u*ÄÄę´zĸhĨœG•E×7ú6CŠ…äĒ4šˆ-5”šj0jõŽÜ•mÜ\ҁIÉj™5ųÆÎŖmEųF–ĢRĢ"Ōëô, ’eŨĩ+ŌÔCņô6k3ë5:+O*“2DTY¤ŗ22õŨ2 ŸĪI“ÔJžÕ ĢėÜÛNRĨJ*ņ˛Yí,#–E‹øŽ=ĶRS•R†ˆ5éMvąR­”ˆø _$MJ” ­ĨzS×…2•k�‚‰–‰y‹ÕFDŒD™žžĒVˆE|>_$M‹Éb˛^lÎ.k.5Ų„r•BĖgø|‰BĨāwí.JHM_”œ ņų|‘D!s­fŗˆˆáCŒ+õ1RÕĸ'Sî–u\CBŦĐa6 lšrFĒJ›-eĖēüõŲYYŲër5ģŒ•ļŽYŒúcĢR+Ä|>Ÿ/Q¤$ˆėF‰%"ģÕFBŠLÂw5VšžŦā_¤Ü­ .ŪÕž,A&bˆˆɤB‡Õb#"›ÕngIJŽkŽVĻ,JU_ŀÛGÄZôy™Y’'ģÖĩeää,KJD"iBzj×l0XˆČl4ąbUšR*‰$Š”ĩ?˙W†ĸßrFž %ŊÎuUú"Ŗ4%Yf6”˛DDŊŪ&”+ET´1×,L]ķēJ&â‹$ĘEk—ÆÚĩšģlMŗ“rYf’L*á“M›k å˛×Õ2_­Zž<‘ąŋf��p ŪĸÛ&Gû7ŲČ÷Ü‰ÚŽŌšâYÅBĢM5}´ŖpĪÎÎģų!JADŪŖŧ+wŸę\ ĀYûĶ֝?;™lĨæ÷Öæí;^c;[k9XøÃHÆ‘¯tbŦÍÍžãĩįĪÖßŊõ‡3=bįĖO{~>×BÄ-Šrr˜ĒŊ{ŽŗDd;rØBAc„ŒSp8-Gž/>~žˆZN—¨"‘ä–>3†¨¯ŧG\‚\ü"g>õ蔐ët ‚> [K[ZZē‚ēG„ZšÛ[Zڜͭ-ÍÔî5° ÔĄ×ô#ũyĒÆîV Lũ\ˇ\Öķ2MDlAƤ‚ ęãZm,‘B™ĀÍ×ęô¤N V_d¤Ø• ?–Y¯/ĩQ4ŸĘtĨv’%É/ø–˙ŠT¤Ĩˆķs4õ™kDdĶå<UJ‘Åjv‰c{$ŠLĖ힉7˜ŦÚwVhģe„Qę•”!"›Õę`ÄnĪ´đEbžŨlĩ‘knW(ęÜ&‹ų:cūF’ËĨb‰XĈ\›,+˓Šģo­Eb!WgĩÚ¨#ūđ„]ÉåRį‡Ę0ŦU§Õšm6ÖA,Ëë]ôÕfą9Ą¨ë<|ĄO7ž ŲõZ­ÉjgYbY–Ô÷­>ÃØŒZí1‹ÅÖqRĸß3"™z‘Le3›M&“Ųl6 Zą25%AİVŗ•„n+Č1â(žÃPi!ŠD(q5ģr56šT*‹øüŽ.î¯ŧÛE皈ˆËvmb¸DŦƒ%"žX*$­f#Ée2ąXŌWŊWšŋ4ŋûWŽPštũģĪņŠr]æ ]ŠŲbc,Kŗ,ÉT*^AÖCOšĶÔ*ĨR!I]˙fû)Éåĸ,ƒŅFRž^§įËŠ…ēÔ"=ŠXƒŪĖW¨$dĘ7:¸2E÷J)|ŠLčĐLtˇ‚ˆˆ‰îØbŌ›‰'ëô‹VÄ2ë ŧ��<]‹åįb‹wđä?œ<Ô5âá,ß˙ķ9&ÆßÛiŦ<CÁ}×uC?4)Č|ō’ųéÜöíųtŸktcô¸)ķ–Ėáԛ4˙û¯;Ī6ų†ĒgĖ–üfT€¯ˇˇˇ7‡ÃŠôö&ĸ›opšĒĒģÂŪ9KåYrģ´&ëŲΊ-lŲĒÎuŒĄ45ĩ8ÉÛ}ǃŗŽōTįĪ--MDžũÜÂq_]Č/rĒZVōūO×Õ2ũΊq!gKs[KsĢĶŅŪ ƒNoČКP,ä’ÉÜųėAgŠJLßē;LZ­ŠĪcDDŧÄĖĩ)Œ°pE2†ˆ” ŽĻÔPF ŅF­Ū!Y$ã“0AÎ}šČ@)*[ŠÁLQÉō>nĨ¯č@iJ˛4g6OK Ų´yĨaJĒkļkc‰¸ÃnÍģŌnēLBy˛Zæz�‰aø ŋûŧŦ%ļ4EĪ9y|›\I‚qk“D•ž.,Ō•4­ƒËËTj•”O–%†×ãJ†ØŽ Ûû"]lúüÜ"›4I­ ų ą&MļÆŅ׎ŨXļg] ĶŲ6֘—ŖąˆUęŲRŸ!2īĘÎŗöU…Eģ1×Hą*UšXÄ0d3æåč.~ŌKbøbŠB,Uą6Ŗ&WŖ+0ĘŌIJDV͚÷}š|–ˆø˛´tF¯Ķuå Ĩ *ĩBĖô[Ūí"]ÍPŋˆˆŠŠ<ƒÁPĒÍ-dš<ąTŠJ’‰¸LoöšœEŠ‚áIÄnõØ4Î^jT,]›­– "ũ e†ë_(ŖZûõƍŲëķß^šģÂÁ*Ķ^_ą,AÔoy´RÎÍÕé)EZZę/•IåëM”`-2ü‘ŅÆē}ūŽŽ`ˆe;˙ q샰Xb„nģr™kõī �ĀSx‹bSŠ?%ēs SyŠˆšŽî;w‡īŠŖÎĶļŖ wLéį˙d†îļŪûˇę99D4zlüĖą=7HgÜw{YĮ]~Đäŏ%ŪšéßĮ­ -ĉ~č%ĩ+Ãp†ZZܞ2b[zÜŌļĐßá^ÁÁˆˆČiũaËĻ$Ņõ3"äúĶélnmiimvøßtîfūÎȁŦNÎČĤ;fØe"Y÷מ’¤Ė×;—lĢ\7[›eīëX.Ã%r0âeīšm]ĩ+”RZŠ/ĩéJí<šBLDą ącNOJ[‘‰„) ÉU(IN•ggj5ģX•B[`dÅiSá†!rtŨÁē°ŦãQ`ĀøB‘¨Ī!†Ī#šŽ÷*îŗF$KJ‘%kĢ4éĩÚŧ”ū”ŠË0ÄÚŨ‡Uú¸_īÍf2šIšŦ–u÷Ō—Î0=Gģn{Y“ŅäĢÕŨËôS™Åh˛ōdéwË:Ãĩã*&Ią6–éņh׊äz“Îl!…„aˆÄĒ…ęžuö+_ĒPKDŦÅdÔiĩyއ­ú)ī>|@]MDŒ(ZŠŽVŲ,e­Ļ Ÿøwl&Ã—Č¤Ņ}mąéōtųšŦE yÜ*R¤ŊŽH{ØJ]^öĘŦ'Ķ…i’ūĘJ­Ô-v]el˛‚ˆ”rKŽŅb4—’bŠ‚ˆø|÷ØCäŠôL_‡ODŦÕmW;{­ū™�xF"Ui<Ū@ôĩDÁcˆ|cԏH˜Ņœ;&īY÷UyĶ”ņÃŨư ãz¯įŽ36NâûĶĄ&ĸߌŸŪRļ.īĮ㎐2ŠēFrœ,KŪî;Œo?sÉnHį+÷ėŲoíŧ…đßzû-ŽËsVũ°åŊmĮ‡ŅŋË6lĪu!wN'ÛÖâhu:lgGVV Í}Œ ^šH•*gȜģFsŏpHeR"ļÔTŲŗØũ&Z$WˆÉTd¨4ˌ\)#"’(d<ģAWfĐę<Ebī w9¯NSrY]A‘I›gtH““;ī EB1—Čję1ÎTÚ÷×ĩÄ yŦ%~7†˜ ũ'"›ÅTŲ1áKdI*)ÃZ­,‰DBÆn5Ûēv´TZ{Ėaë‹ëļĩk•<›Ņxɏ™/âsY×c/ŽcĖįdY".ŸÛŲhÖdŦėyĢÛųÉŗ=cƒÅd˛öŧ]ŋlŦ)/;{ļWŖY‹ÕA OÄÅB˛ÛúęWÖRYfé8)#’*Tr×Ķ<ũ•ģXW[ĘLõōEŅ*•ŒgëUī `í,q…]ÚĻ-ĐuÕJũ.}GW1eÚëO*XŗŅÔo9)T2›QĢ)2Jd #SHM:Ū`‰MT0D$–˸ŖÛƒh–R“•+“÷ņ¯V,“’Ũlęú˜ž�8o†Š7īz'Š7Į›ˆ8ūŖ8DDÂ)ķîļļuņ˙ ?āĸ;pFßėúÁŸĄļĄs¨ææ¸‰"W:{Ęæô…u3:TrąpuŖh!ooo"ĸēƒ; >ÛÖņŋ%ŽņgÕŪܡo¨DĨs^œKsŗŖ­ÅŲæt8Yį™Zoā3Bĸ”åQ\V÷ōũKķĘzŨš°•ģ˛2×ŖžS^:ņUwËšdÎÉÖēfŪø|Âô•‹›HU žÃ¨Í)2Qlįâp2Ĩ‚1ës5ĨvŽRĨ¸ ÚČ$Ĩ¨GQvfŽ‘bĶfKē6(ä2†ĖÚîĨˆÕnüĒĪé\זD!Útš]&‹emSQnöē\}_ˇÉ6Ŋ6cžžŌbcm6‹Yo0_,dˆ‘*byfF_iŗą6‹iWAŠMč+ë_"æÚLŖ…emãWfž˜O6ŗå"ˇ¨Œ8VĖX ZŊŲfŗYĘt#Ûiø"!Īa*Õ[XÖf1íĘ30!ŲĖVKŽÁ7›ŲląŲXIDŒĩT_fcY[Ĩ>OkŠš›õbgí¯1RĨRČr×iôef‹Åbą˜MzÍFM)+V*DD$’)ÄŦą@ctõĢYŸ—“ã NŦY§ÉÍÛe˛ØlŦÍf1ęŒ6ŽPĖīˇŧĮYĐÕ6“N“¯ŅwÔ[iԙė|ņÅŗĶ€ˆĸ¤<‡6/×hŗYĘ´+žČå*Åd6š,,kĘ]ē0mÉWúJ‹ÍRiܕ•Ģgd õWND|…JbÎĪŅ3ąŽ1CQŦŒgČYg’$tŦ‘ž*ļæžŧBk˛Øl•ēuK˛J…ęôģûHđ"Ur,é˛^ÎĶWZ*Ú—WëØN ��"jamg[ˆÁoĮßz›ėÖfÎy_üpôDƒ“ˆˆã/ŧyøĮM|ûž—ßc×÷ôg+Ēũû‡ÛEūŖø’Ûī}8ÂöŸē9LäīMMĻG›‚§Ē§Äņ­N™č;INחŗ'̚"ãÕãEŋÍŋy4˙æŅŖÜæ•9Ģöæž]PyÃ]å0O#ˇ„ŧŧŧZœl[Ksk‹ŗĩÅáååŨ6āŤ‹r×ÛŌædÎÖfÉJąĪÍf>Ļ7šėâÉĶ×g/ęk–?%sY^ōJÍŌD[JēZ.t˜ĩŗ Lģ<ĩ+ĻČTJnžVcwˆSģ–æ’+Ĩ´BŖu0ŠŒū&Õ]ņĘ5¯ ¯ÔĖ(רܞvâĢĶUk šÜ…÷ÛŌSä|Û1mž†UČšZÀēę*ˆii¤Õjs×ŲYâōEReJīÅĘ\$w'Ģvę5šZģƒáōx™:E)""Fœ”–LZ]ÁēB[G ĒK-{ÎH“Ôō<­6g–'+f̤ˇæęōr¸)ũeP&ZŦŌh‹ō˛‹ˆ/’ŠTr}žĢģÄ jĨ]ŖËÉŌ1Bą\•œÄ7Ų6jķÖiRžJ‘Ęäb“N““#V§§ÉfĢ*ķušw ÄK•ĒģĨė.kž.w#Ĩ.J¸ÂDŠE‹øEEŖ6Ogw°\.Ÿ/'$§(:Vdã˒SI§ÕįæhäęŽ4ĨˆˆøŠ”ŌhĩšF×ģŒDâ¤TU4CÔ_š[LHW‹RÔŦV§ŨXhsÊĨŗS“ŽÅú„Šåī.´.ÉJž”Å+Ō–g¯k­ú•‰Kŗ÷¯Ų´,sEVúlĢŖã}Aë3SDDĸ5›Ŧ}•‘H.įŲsí‰]_6Člކ¯îJ}ŅËōßágfeŪŋŅî`xRejöÚe}˙ÍĨŧŗæØŌ+jˆÄŠ´å™)+žÔūˆ�€G`1ãųŒˇŗÁōĶĄŽéddŠŽ8ôũ6o˙`Il܄ÛĮG_đĶĄÖĐpŪItą)I į‰ˆ¨éđÎM?ĖģoÎãoPC•iīgy?ŌTÁâŠķ^tæžŧíЧø͉O_6ÕyļJ_°ķû?¤&Í\3-;svŪûМôŠūŽîŠ-|+[SC 'k*5ÅU7\ ""¯öööĘĘJŅUŦå5bä�ŽZøčŸŠgō)y{ܝI˙­­hs:ŧŧŧOļÎ|i׀Fl™vcN~^_iu8ˆËåņĨ2šBœ~ˇûíēvɄŒiĻîķÔÎ>0īZŖŅš:īąÔéËÜī˙XíÂI:–—ŧÉđzį ”iĩjvŽ™d+tŸw.„ua͗{`'ãĘøûsmĒėũīĒz~=aŪĩb嚯ôf;q…RÕS™¯K īĪå/Ķ,Šu´���ˆˆhiæŠËŲÍ?HDį, }?ķ?jŒxΏ;Ž%ßŅŖčÜųáŧĨöU?–š(îglĘY[ŧ!įŗc˜)}MŦYšüŠöØG],‹D"ļ ôđ‚´™3gRĪ D†ÕĻĖđ"jkqļ8Í'ŽÎüĢöR5ũzYōR_6ŠŨR���Āõä2ƒP'&DJŦõTeU-ÛâÍÜ|K¨d4CūŪõ?–ŋ^ō…ˇī¨Qž} 55ؚk~�� �IDATnœˇ…Ūp†> ÃÔ¸ Ÿ|\VVÖŌŌ˛mÛļļļļÖÖÖŽ?įF4ŅkšĖM>ŒĮ‡ÛŪ~īēáŲ´YĢ $]šŽ���ŋlĩचŧũƒCĮŽgŧŦõTåĪ'¯ˇlŅŌtŪv#Nô‚+5 AhÁÏ ũIo$ŊFg2hÖi qęëé’án���Ā`ji¨Š<Z3Ü­�7l‹%@ŋŒy™™âŠé¯g-ë{)n�����¸Bן¤ĩĨĮÖw#�����~͆í=B������ÃA�����<‚�����x!�����N^^ÃpR!���� ˙›nĸööánÜāÚÛü†ū´B����0@ŠĻ Ī—ųđk2ÂëŅ´šÃpÚĄ?%����ü:D„…>žč˙›nōB‚uĶMO˙eá-"áПÚĢŊŊŊ˛˛R$ ŧŠ#ąA������×ŪÖ:āc-‹D"Áˆ�����x!�����đ8B�����āq„�����Āã ����€ĮA�����ƒ �����A�����<Ž÷ÕWq5/3�����z�����ƒ �����A�����<‚�����x!�����đ8B�����āq„�����Āã ����€ĮA�����ƒ �����A�����<ގë?fŗyxÛ�����0¸\.u!ŠT:Ŧ����� •••„Šq�����⁄�����Āã ����€ĮA�����ƒ �����A�����<‚�����x!�����đ8B�����āq„�����Āã ����€ĮA�����ƒ �����A�����<‚�����x!�����đ8B�����āq„�����Āã ����€ĮA�����ƒ �����A�����<‚�����x!�����đ8ƒ„ę+´Ÿüãą‡fÅß5.âV…|꜇Ÿyķs}ĩŖįŽûūόˆŋæč 7�nE/("ĸÆÍú°b¸����žČ{đĒrTūãąķŽ6‘ŋ DĀu4TW—éĒËt;Ö¯ŽņÃˇŌ& Ū �����dЂPÕö%sžũŽŽH O}>#MĨéˆ<õE[ß]ąfgÅŪUķulŨ”>Ž;Xį�����ˆAšWõŲ˛žĢ# š7{ûĻ—îīJAD‘đđÚí›ÆøPŗņŨĖ-˜ �����ÃlP‚ãĀē}3QHJÖkĶ‚úڃ÷\ÖË÷ÜĩčĨ§ã=ʉ¨ūčŋ_X0ũ÷qQãĸoŸ>į™œ}ĩŊ¯Õ–ųøÜøßĮEģž;úĶܧÖė(īņԑqÅÔqQs?ĒĸúŖÛ2#ŋ}\DTœlęœĮÖ|[åč]_҇KæLUDwžą¨ĘAúŋËŖÆE$oŦęąŖņķןœķ'ĨėÖqˇÆÉ˙´ā‚ķöĨüÍéQã"Ļūã�ÕØō×yRēšŸŧä}]uīžĢÚûŅ ‹fMUDG‹ˆŠ“ũ~ÖŧrŠ.hąŖüÛ÷_XĐŊÛÔ9ķ^ČŅölĘE÷qõĪŦÕå=Ēu=ŠņûŋîëQ\ũ‘z\D”2SīÖČŋu7R>uîcûŦ×ĮTôL\DTÜÃÛëku˙˜7U÷Tagķꍮ8:j\ôīgÍ{áŗõ—ėD����€khPĻÆ•}ąûÅ,HŸÜ˙´ˇqķŪxû‚BŽŖtEĘ?ū]19nZ¤Ŗúˆž´dĮ[ķËęžøęĨ‰UUm]4įÅâ:ō ‘ÉUr5ו—ž^ˇTĢ+ے˙\įn>\.5×ۜ÷ˇĩ˛‰ņ3¨ŽlŸĄŦp]ƁÚw˙3žsĒļāņûžŪ]G>‚¸ø)‘G•áŨGîŪŋ"瞈¸Üî+(˙ėáÔWuuä›0}JˇūČŪâ¯×í×î.ū0÷„>_.Õ×XķđęuÕA˛čÉĶŖëKŒ;ŗî/yë‹guė8ēqŪCĢJČ?dRÂLA�9ĒĘöëˇŊĨßũmfŪ§ޤÎŨræ$ŋu´ŲG-WŨ+ā:ę;vÛžsYū–Å1ÜËØ'Z)÷˙xۉ†ZŠėjúQĄŽˆ¨Î¨;J“c:‹ë ē "˙I‰qDDŽ’7į=ēž¤ü#&Šî  ērãūÂO_-Üž#sĶē?Įtt—Ë%jpũlŲ–Ü#AąåÜЀŽÚ2SŪ\A>‚č„{cƒ¨ŽÜđy)GŒ¸H����\cííí'Nœhŋŋl¸wlLøØ™˙<rũ°<>|lŒô6ų´įw˙Ō]Õ§so‹ +Īв%Ŧné1ácås7WuĖ–ŧ–>V6wķéÎĸō÷’bÂĮĘbo›ļč‹ō΃ÛOũĤą1áãĶķ흇Ŋ4ilLøøŪ;ŌĩWÕŽ§§ÅŽ—… pkįŽGŪ›- +›öŧŽëílUūâøđą1ą‹wÛÛû×Ņ!2éi˙ę>‹ũûå3ÃĮƄßņŌev͟eácc&=ŊģûíöÖķö]‹eáce÷~Đ}]ííėņĶbĮÆH˙ŧõôåíc˙ú éØ˜Ø§wwīpúĶ{ĮÆLy0}ĘØ˜{7ģ]eŅsącc¤‹ˇÛÛÛÛŲ’—˙>V6sĨŪí’Oīz~ZøØ˜đŲëŽwš>ĐIwLģ7ĢÄŊsgÍ žôÆvˇÃŸž>6&|lĖĖĘ/Ō‘�����ƒÎcj\]EQHDČÚĖļōĩiĄ]ŋ‡Î˜įCÔP^Ņ5…,būĒ5ĢWŊąrŽ[í\Ųü{Ŗ‰ščKģ§XųQ3)žËšŅ5Ē4ũĸæ˛’ŽG“ûļ~[G$˜õ\×PQˆęĩWšŨæĐå~PÖL‚{Vžß=öà š˙Õ'>Ô ÛXpÁüŊ /.rÁ+ng HXúDĸ?Q]ņ%ŽVs'.ZûŋĢ^ĪZę>Ÿ0 aŪ´ĸ†’ŊŗØęĘ̚‰"ã"ÜÆÛ¸‘¯Ų’ģeûĢŽc/ŊO€<~ĸ5öéÜ\oØ{„ü'Îģgĸ?ŲģŋžŗüČ^cųLŒŸ@äĐm,¨&Ė\žTîļā_jéJĸ˛Ī6—ô¸æ:’?Ÿ!sÛķčÛOų(3ž˜āvøËO(|.؁�����×Ę`!Gŗƒˆ||Ž|98ųŒ‰=Ž  ĸúē΀à ™¨œy˙œi‘\"rÔ×VWUUWUU×ģrOCīgM&NŸŌcnŽ (ˆˆŽŽÛüę#e D>•˛ž§•ßīī^pdˇĄČ?~ÚÄ^4%1†¨Ų¨/šäS.á ʞĶŋ&)#ˆ¨ŽühĢqĄŠiŗįܛJDŽúúZ×ĨUÕ—ˆ É$$2Ÿ¨Nģæ= §EvĖ?ģŒ}‚Ļ$FՕîëXŽēũÍ­ORÄPsIqg@ĒØ§?E‘DDGöîo ō—_Øre :r´G"ô‰ëšgmé‘j"ŠVĘzžC�����Ãe0žâú5¸BÉfĄ€ A?G¸ĪÔũ<ûŨˇ­kî{ßnūA‚^/*âúûu†ƒēŠZ"„†ô:-w\\8m+íüÕQU]GDcî˛gžėuŽÚ:"j.¯Ž#ēøXHLīíAA>DÍĩŨĮÖØōî˙nøn_Å×Ög¸ĒĨ¯Üe\úĩ1÷/3>ķ%ȧ(§OIPÄq¯hŸÉĘp*ĢĐëGUč !›Dqádč|L¨Ö¨Ģ Š˜29ÔÕõDzË“ 4ȇ¨šĒĸލ{@+(¤įZW]KD>‚ĐŪTu~Ņ����¸V#…DUSõŅ ĸ¸+<ö’ÁÉaX‘˛čãŠfAė}‹fÆE‚\+ė~ķųm'Žŧ­‡ƒz.‰Đؐ€€žģ5QsEņ×ũ,÷í¨ĢŋDōņšā,\Ž—¨ŲÕĸZí3ķū˛ãųÜĸ¸wab\x €KDÕ;VŧąŗĮęrĄ3ßū*úŽ-ë?Ūö큊ũ_Wė˙ú͎Č?<qŅ‹Ë‹Ŋė}ÆÅOŦ;q@gtˉįVíßWM‚šS"‰(~’`]Ūž’ZŠ r”i&|Ę8ˇ~āú_ø9qšŽ„éhčYÜsÆ[ĮĨ^Øá\.ĻÆ���ĀđŒ ;1„JĒOí>ē<î"žękë‚úßŪ—ōOV}\ŅL!÷|øÕ nĮÖÖm¤!âē'7Ž“ė¸Ü�ĸæˆE_ė^:đ)\žÄ5‹°#”Ŧ_ąãųD˙%Ëķ1nAĄÜ¸úÂē"TŊĄzŒĩGön˙ōķŨe…k;RũÉö×:ŸŪšä>qņ“ũķž6ĄøHCq9ų$Äˈˆbä}ōŠö7ŗ|īūōO˜.sīGÅķ�]НLéŽcŗÃŅ{´ĐQŅ ����>ƒōĄ˜ûf…QŖw/˛„@íÖĨĶ?}ŪC}ŋģ\ČqÄPADŗRz&¨ōŖIADŽG†ęjk{ßŲ÷Ŧ" ĸÚĒēŋīĻųTUU¯ĸúÚÚf" ŠQU‰ąšˆbx$ĻgD¨¨č}œ{˂b&ĪJ[ūŪÖŊš)DÕ[×]ĐĄũîݤŒķĄjã*Į>ą™dĘ8.Q€Ŧã1!GÅž’:ō™”č*īė‡ĒĒSôC]UU3…FÜrąN„Qs]UīŋÕUHB����0l%ҏO$ú5|—ųėÆ#}E‡ú’7{Ŋ¸ĄšŽBBŽdL¨Ŗ.n¯'jw|ŧŊn@- ‰‰đ!j>`(ëyžŊ_ėîQá¸xš?Qƒaįž ^mzD÷ížōËIs:CĪÛ˙úR] Æš?<āĶsHĨúķ Å="B}EŅöĪ>*ė=E/@1mĸ?Qs}mũåíCD09>š¨Lo0č 1erĮŖ;!“ãnĄ:ŖŽdŋŽ‚|ââģVx/ķ'j6|Ûģj‹ +ˆ(|ĸüb/Tĸ čq"*Ķ{öXUŋs����†Āā! ššrՌĸÃĒ9zōũÂŖŨ#.õÚŸœõĐú’L#kŪ­ą! ĸōíßv’ÔV?ūfUD´U×]zëžNž.÷!ĒŪúîįn5j˙öĒÖŅcÕ8Ž2õÁĸē/3˙ævjrTm˙ëc 3æ§Ž*ēôPQķuĢ ēŽ?ũnQQȌûbˆˆ‚""ü‰¨äÛĸîk¨Öū-ãGtšĸ8öđÂĢ+_üëû%=˛D­nįž"Aĸ ËۇˆˆBã§EP큭šûĒI —už˛•"ãåūtbßēo4Säô)]á†ĢHģ?‚¨nĮęCyÕ¯ŧĢo&ųÂG"éĸĸī›~ Qsaö›ûēŽwTüû•õåxF����†Ī`<#DDDA‰kˇŽ<öbnIõwYO|—E>ūA�5ÔÖ54ųĮÍ}ãm÷W]ž‰ Ō[^ÕWÍQâTwbŸnŋCņú–ŋ6,›ąĒ¤bũSĪT'ĖJ{>ņ˛Û9ëŲ§ˇ˛ŒÅĪ˙iúĶå‘\Gšá[=M[Ņų†ûPLĖĶīžr$õUŨļŒé{c'Ë#‚¸õUÆR}Eų„Ī_õbÂ%—yđ˙ãƒņeËū4ũcĨ|œ€jëšé–ģūēĐĩĀ4W‘ú`Ď*ž[v÷ÜÂéŅŽē#{‹K¸Ķ>ČMͧÎ̍Ū˙ŋ/9:ũž§ģgyÆļykKŗR”›eōq!‚�ǝ­.Ûg<ÕLˇÜõōŗ“šDA—ąK¤<!„>6בOĸë!""âÆMščķĨno1Qx‚ĸĮ+›žëÅ#­Ōox8~ī”ų-Žē#†â’ęfŸ{Ū^uīEĮƒˆˆ;1ãĨģvg|]‘7ę~E\x�5”—Ģ‚xzÖÎŦmÖ����¸Zƒ4"DDDAʗļîųúãŋĻ&ĘÃCüŠĄîTu=DÄ&Î}ņƒ…[¯<…>đáĻW;*žûâĶ/‹*hrƇ[˙uodä=˗L ņi8ēûģ}ÕWō 7fņŋļŦž;)" NŋãËĪ÷–‘üšÍykg_pGĪ|ā“ožXŊhƸ€SvųÅļâ#õūŠ{ŸũāĢ­+•—1ŋĪÁUŧüɖ—å܊âĪ?ũ˛đ¨#H6cY;Īčy>wŨ˛™ąAޞ¯?Í+Ø{"`úK_ä­U…ĘūōjjœĀ§Îøm‘ąÎAÜqmØžūÅųņŅÜjcŅŽ/ŋØQ| š;qæÂÕ_m}{–ĢļËŲĮ%Z)w}u> ä S¸^z$˜Ôë ?ܘ´-ß|˛bî”H‡QûiŪæíû̏˛ģ–do˙ę Õå|ĸA͞ō>\636„Ēõ{ŋ+:Z4ũĨ-y/%ųQ_Ë0�����\s^íí핕•‰d¸[2Ėjˇ,Pŧ˛ßgzļņŊiWūb؞Ē6ÎųãĒŸZĢ”Æ����Ā qşÁē1ÔŨW¸í߅G{ŽDÔЗQhDøÕĻ �����¸î Ú3B7Œę/—=‘[í]ąé“åq3ÜęõoŽŪŨ@}ßŊÃÛ:�����ž„âžX9ˇøąOË>NI,’O'āÖWO5OĖĸWū|‰5Đ�����ā×Āķ‚$ŧļeĢbũ˙~ō큒⯛›}üĄōYđğ#0/����Ā`ą�����đ žēX�����x<!�����đ8B�����āq„�����Āã ����€ĮA�����ƒ �����A�����<‚�����xīAŠ…u4›Ģ-ŽfgkkÛ TCLâw|¸›�����p1#ŧ}Ŋŧũo 7ŌÛ÷ęk„ Ä:šMå'ũũo đ5r†˜nLõĮG‡Ũ<܍�����¸ojô9{˛˜6åęŗĐ äsĩÅß˙ĻQ~žHA�����pÍ´_ãÍAÂĻ3GŽžŽAˆ.,ÛėįË\}=������—āįlmn¸új!ĩĩˇđōēúz������.ĨĨ­ĨéękÁd6�����đ8B�����āq„�����Āã ����€ĮA�����ƒ �����A�����<Ž÷ŸĪYwlĪÎoŠ¨9ĶDž‚°ņSfÜĨē53Äíđ�ešĪ¯Ēøã?^ųSđpˇ����āz3¤#BΓßŦzõÍü-‘ĶS?“ņTjR4ûōVŽÚņ‹s(ÛŅíØ†ūŠ­žs����ĀpÂ!gEūē/Oōâ—üõÁh?WŅø ō;š˙\ĩuމ™ĒĄš¨û夝0`����āi†.9}Wl P<“Ō™‚\üĸį>õÚtß°`":ŧ>#ģFũúōÄ@×ļ˛ž]õË]k^™& cž{ģfÆâÛmÎ7 ŧšØīŖgß§Ÿ Ö­/´D?ųÖâ[ŠņäųŸ}Ŗ/ˇ6R@°ôŽųŠ÷NŲŋsŠF¸ø ‘~ĶwkėN?Ņmę ˙0†cú÷ĶY{íDå/ū%?îŅ÷žœä6=ĪŠįŲõ”˛DzxķÎc5Nŋā¸{Y05Ėĩ˙Ÿ?ũ÷—†5õMä61iAĒŌĩÅnúfķ{ž<ĶHž‚°ņSīKž%åõS~fà ĢËūūÚL‘]ˇjÉæō¸ŋŧ÷d‡ˆ¨bÃsožœņęōÄ@˛ŪžģmÉR×D<Q”B’,q:¯ka*m˙臯;Ÿ[37‚ė‡ķ?ĘÛc˛:ũ„ϧ(Žņ ����pãē tōЉFߨۤ< Ä ģô Œ7‡ĶRWôõÁß'ŋxŸ(؏SÎ!gÅ7Û9ʅËĸ‚ƒ‰ę~x?kÃɈ{ŋ2)˜,úMŪūዝ¤DrˆÃņĻFcūÎĀĪŧž˜įŦ)|ķo›6īšõ•4åĩ'š^z×2ëoĪM öëÕ,‘Ķôuž`Á’.æ‘ĨøÃ7×˙O^đ?ŒæP͎÷ß˙žĻ<–ąx ÎÎ˙hķÚ\Қ?Gq÷oxįë:ųņsÚËwo^˙ÖŪ?ŸšÂéŗ|ņmžÅĻ3E~DΊÃ'ü~9|’â"‰¨æXycāi 9+ōŗŪ×rîXøĖâhžķ¤!oũē7íœŋ/žčĮá5Ųū}”ęÉÂEDöâuīo¯‹[¸ėŠ ŧÆōŨųų‰7XŸ^įĘ-+ŗ?7•ŸŠ z~ņ=Ąˇ†ģo5hūđS­ålŊ|ÂØW–ÍĨ�_÷­[ūĩk›ö˙ĩ´ĐŦiˇ=úøŨ×yĩ����đk5dĪ9ív;ņWą*Bßīˌ‹ u )Ų§ĻN›9FāG'‹ž9Hŋ[đ؟& ‚ĮĪZtWdnûÆŽsˇp&ĖøS4ˆ8Áō;Âč—ō_œD‡CÄáøųõŽAĸfŨ7žĮ!âˆĻ¨īŽ˙ąØä$"Á‹—ŋ’1bD° 0Xǜ51Đn*Š!ĸēǚ&^ôīあāEjÆōe÷Nđ믜91œ*Ž•;‰ˆĘāÉīˆvž(Ģ!"jŦ8Vã5!ŒœĻīöXU<¨ˆ ä Df.¸'ĸūįŨF;‘ˇŗÅ–8ŠtL˜€CvãMPΟČŒšmnōm~-īëū?iŊëŅ7ūßĪĮÎØĪī?x"ųņˇN:ŅĩuĮē§W~räøŠŗgĪSt`æ§ÖļŽ­/.ûđí ÛŠ9WS{nũ§ß>šø­ëšZ����ø˛ Ä!ŅÕ-‰ .p˙=8*Ŧ3Ŧ°RØøîIwŧ¨ ‚–rSUĮ¯ŪÂȰކpüˆ/§%Ááa]I* ĻšēF"âøQŨüĩ¯ž˛ôšįŸÎxví÷gČétQđø göŦ{sCáūƒ'íNâ…EŽáqú-÷‹æŦ(Ģ!"ËAEŪǜ|ĻŦĸ‘ČY~čG:>’¨ÆôKŖī˜čî3^XD ŗĻĸĻãWQtDg-'ęˆ<ĻĢ DŅcŽÉāÆëī~ŲÖ3,ŦĘūĒëįˇr4î›ÎũˇéĶ ģ]?7V)ŌvßúĶá5G~š>Ģ���€_ˇĄ›'āņČnŠqRØ@…8Ŋn8~wũ-Dŋlx|á†ÛE]yg@§äp8î?{“ŗą‰ˆs0÷ŸoTĨ.XčĮĄšĢW\ûDÍ˙ëķÁ;wīŪŧįŗ&N`ԔûæĪ—‹8ũ• ĸĸÛĘ+ėÄ;Vno:áŧ3ā`…3RÅ!r6ļĮ×­Ž™~M×åíļŠŅŲHŪ<÷÷3Îu•ŽŸ¨éUb:Ņ8mį›{m-̍ęī@":ZūKđ¸1×aĩ����đë6tA(øÖpŋŨ‡õōĢ%ŗĸ¸Đö‡;ÂüzâŧÜ$o??"iōkwŋ÷įø^Õ(TŖÛųMÎōķķ%įąbC}˜úšųr‘kK{+ySį.ž:—kŽũŧ;oķēlNāëķ#û+3AęˇĮtÂÎ;|RéGÁˇFPē€rû…ԏˆüüŧÉŲätvG9gc ųųöqü8jąģĩ¸ąąqĀ—~‘âā*ËŲž%ޟ¸#üŸFļGēømXGGũļk7÷­âāëŗZ����øuē÷q¤œ"júųĶÍúīíi,ûtÃ͡íä‚iŦīē—?SciēŧēyaBĒŗ8ĸā`×˙ũ8ž<Ūå ‰ô”ꎕwE‰_*NR€ ˜GNg#‘¯s֙ķpņzW-Î犟Y\ÕųGM™{īoûIKcåDykU.>PåLDcÆ…Ų4ŽDE ˆˆŌ1~Mŋ”ujœ)˙å gLD×DŋnĄÁd¯ųĨģÅe×äĄŋeĖáxyš—ŧúLr×Ī/ũeŽû&~€ßCÎtũė4íÎ î['M‹ ŋ>Ģ���€_ˇ‘˙ûßm6ŸĪp–ÚŗŖnēŒSŽū­īöîūύ¤Ļ‰ÚÚΟŽ<¨ŨŧņŗŸ<“:ΏČûü‘īuŋp&Čcnæ4ž,ÜüåĄ3v&Fõ‡?:wp÷˙QN׹ÚéŸvüĐ4~Fŧ˜!""Ūo˜˛Ũ;~8=:\0ŌQûŸīsמ¯ĩĮ('Ü<Ōų‹~{éČI3'…Ž$""gÕ;Ōíú}ČHj8ŧ§¸ŧQÎÁø3#ģÛÚVcØĨ¯o>WÍ •đGÚË 6}pÄm î‹Ŋ™yî§īøedTĖ-{yÁGģÎT˙Ōvģ,´vįÚwv–sEÁ<Žŗąļ|ī­‰âîREŸŅdõU>žG#yŽ2Í÷?Ÿs„'Ū¯IæÜū¯´Gë™[ÕɲŅD4rtPãO…{Žž‹ü¨ū?ßmÚô˙č÷Ο$¸āēüxm‡ž×–ÖōÂnņwž>øå—ēęÆķĖo§˙áˇū—ņ!ŽtTųō.ë™"īĻŗĻ˜Ģk[ZœĮGŽ˙įĸĀąĄ][#bÆÜqęôYÎČiŋŋõŊwžĄ‘Ũ9äĶo2~glö@>īÁģ§,ËLŊžĢ���€ëkoō=v‡ģâĪžP•ˆ6íŕcöhžŅūß6ũÎ&Žo@p„lūKwMtÅžbîƒeëļ­zî[Ž_`ä)ÉwZŪ>tyķãw<ĩŒō?ûfíĢɗ5åąįî‰ŧԈP˜R%5æoZŊB:˙ĩeJA¯­ÁLžõĿ߇3,�� �IDATŦm'Ī8ũÆLZøDJ$‡ˆDĒG’Ë×}ŊöÅ]œĀ(Å} J+œ6ŋņį•Œ%Šy›wnXņYŊĶÛW5åą§î #ÍˆČ/jBXũÁЍ kFGøå9Įtvؘäe‹ũrˇmČúÎŪäÍ3~ęĪ%_¸9QāÔE k>ÎĪëī›9Âč?¤Ėŋ3īí—9¤veFüÆå?ŌûÛ3Yúödi[gĪK˜=/áFŠ����~­ŧÚÛÛ+++%ɀĢ8pø?!ĸ ÁkŌõĀųķ;ĪžŨôā{Ëî¸āÁĨ_'NũŖÃnîV�����\Úš“gG‡ĪđáŽø3tĪ�����\'„�����Āã é3B7ÎmOžķÉp7�����ŽŒ����€ĮA�����ƒ �����A�����<‚�����x!�����đ8ƒ„ŧŧŧÚÚÛ¯ž�����€Kņ&/æęk„ äËøœ?ßxõõ������\B#‡ãpõÕ B‡ˆÎ765üˇąĩ­íęk�����č‹75ŪtļÖę8n0ęēj ×':2Ė\mŠ=sŽĩYč†$ņŖs'Īw+�����úįÅp|xaSFzû^}eƒ„ˆˆáúH#ÂĨ*&c‡ģ�����CĢÆ����€ĮA�����ƒ �����A�����<‚�����x!�����đ8B�����āq„�����Āã ����€ĮA�����ƒ �����A�����<‚�����x!�����đ8B�����āqŧĨÖŅlŽļ8š­­mƒR!�����€;ގ/à ūgRĖ TÁ:šMå'ũũo đ5r†˜†AĩĨvâøąÃŨ ����€k¨ŲŲræœŊė¸9úˇâĢĪBƒ[ĖÕ˙›Fųų"����Ā5âÃņūM`P ŋĒæôÕ×6Ņ…e›ũ|™Ģ¯�����āâ˙{wU™˙üãvŽÎœ˜faF˜A@AEe“ Küĸ¤¸dę–mē•[knkåÖfĩömíÁ,c-Bų‰†ßĸ$ÅHT@Eœbhfęķ÷ĸ‚šÔÎûuquÁ=gîķ9gN]įŨ}ßg<$vĮ÷ũīg�‚Џ/ūfذū÷�����pmÃYæ^č?˜Ė�����NA�����œ‚�����8!�����p:B�����āt„�����Āé ����€ĶÄ dÜņԒ‡în‡ņōæ3[ž\úĘ~ëāÕņ+aŨŋnÉ#ŠžãV}áæ7^}rõšåĢ_ūûÛ9‡jí?áÍõÖ=ųōGú¯ę׀?™žvųÛÅļkoUŋûīOžœy~pJ���€›4č#BÕ{2ö5öNĄ[ũˇŪĪĢķ[ņį'^x49ÖõÜĮog}Ķ6ÔU]2ũo˙)™māą#Ŗg-ˆõc¯Ŋ•kØŊsã'{NI����p“71~“#ĨúœĮŽķ?ÕáįRWVō-ŖK˜æįéęæãī¨:\>´ãFX*jāûƒûIlj˜ĸõēN+ÆN pœŠ����ā&1ƒŧ?Mü\ļüõŒíĶÂõvCi-ų<#ëĢŗĻVž•*ĮĮΟ?]ɑ!ũ//ëo}víL9‘u˙ē•úđ‡Ūz$œ%"2¤?ūĒ1ūšŋÅI{ôsvĶō Ļøeąæ˙ÛY\må9YčmiÜĨ㈈lúũ[÷3ÖŲFĸ Šž“–*ĄŽWöeeä76ÚyÆMĨšŊ0qŧŦvū‹5ÛĢyęå4 _ŧņá7OhR_Z5]BDdúbÍstOŧ0_ÃnËČ=Ą7ˇ’XĒ Ŋ-9åv„ˆøco<ļ‘ŦPėߔgÖ=ōŋËByãÁŒôíG6Vĸ Ÿ=Įīgû˜+˙čõ*hĢĖËÉ+(77 "ouøœ¤cēF9{uÁĮģ>/3ˇ1îūáq÷'…uœzÛųŖŲģ OÖZėãĒđŸ<3ņ^­+ĩ~đLž,5‰ōļˇO\ô\‚’Ú*ķ˛ķ ôæ&‡ v‘ĮÄĪ‹ āúÜīéwWR"eŧ¸<;|ÅÚ¤@âëJķļį–V4´“Hæ›2+ˇ%"ūdú‹›iÖR¯ŖXĶV/R7|ŗk×įĨ5õíã"ķíŪ˛§ęĖĩ›ëbNŽÍË-3ˇ .~Sâ—ÆŠgįæë-vF66.15JÁ2ũÅ÷ėŗ^z(‚Ģ?°îĩâāÅņ\~nžąÅN.~!qŠķÂ|ˆ¨~÷ß˙y<đOόę,fEPÅÖŧsurSĮ¤Î ĩįīøôxM“Cä7.ūūyaRĸÆī>›'[ļ6iLG9M…ë×å{.^ŊHÛyę-đ(Č.Ŧhv°Ŗ$Öį}”[ZĶFnĒđy fŽAč���øé{jΆ$§ŒąÜļÛÔˋúĖW×į4jR–ŋ¸îš• r}æĢ6)ĮĢÅĻōʎa$ŪPftsãĒË:×™ÎęmRVzE_,+swœ ]ōâëoŧõŌ|MõÎ ĩ‘íxúk%nĶVŦ~áŸ˙KšÖžûˇķ-DD|yֆÎĘ–¯]÷Â?WĪöo|ķ S_íŠ ÔĻ/7wėN_ZÉššË+y""˛N™¸ ņ*˛Ų´nķ)îÖ%k×ŊôâŖŗÕ;Öŋö…ŠŖ@–øę/>ŗL\ōÄãÉjâõÛ6l>NS—­}ūé•sJļyÅbĒá3.r4É+¨ļßv*˙h•‹.Z+žęŖ0}ēiK^›˙œÅKŸ~pÖGŅ{īīŽé86NįåUiã~t؊xyãĄ[‹ėDDöĶ[ßßuĘ5ré#=ûįĨ)ĮžôO šˆˆ–Čq.ī0=méŌh9QÛĄ­[ręeņ‹—=ģjų˛yMŪ'uL{ë}ŋÁ‹ū<+˜htâc/=5+ČVēã_˙>n™ĩrÕcĢŌĸÄå;ūĩí´ˆˆejä5‡Ĩ>¸(AE5yŸ|XĘD/XöėĒĮV΋dKŗŪĘ­žú´°ŦđmAūˇá÷=ŊvÍssåu;Öo*dâũcíSĢb˜“ģr{™=(´Î)Ī\úĩk^XŦŗ}ŗc{Ņk,Р?įˆZąfõˏÆzō6ŋ‘õīŦUkÖŧ°8ÔöÍŽœRūڟÃ2ä8ķų!QÂ#Oūīs‹Ļ2gļnz/ĶŧôŠÕ˙ûTĸũĄ­ųŊ����\×<5N2iîl•ųŗĖÂ+`;žs_*aIÚ$ĩL&ÕLŸ6•+ÉŨo$V@†ŗzžˆH_Z)™Ĩã+Ȉˆl†ŗ&.(LÕ۞TĶ’'IY"V>ûŽ�[ia‰ˆ J^ũ×ŋ=pģN%•)”ãnĶQõ1ƒˆŦFŗU=I­IeĒđä?ūeÕĸHYŸíĘ0-k*¯ļ™KĘIwĮ8ÎpÖHDÄëKĢYõ8 k.Č=Á‡Ī^¤IdšČ´…‘\õŪŨŨ°JcŪĻQĘ8Ō}Â"š?'D!“*´ĶŌî¸Î=ōÍņŒ\ē8FČī/OŽYū×W6w™ŗ$qėÕ9ČPxĐ$‹Kš1v”ÂgTđœšq|…ĻÎ0ā`´ņŠüŧQ1<„Ŗ…ˆHėŸđȲ•sŖ}=¤ŪŠąqQd>iėČŒ ~1ŗĻh~ž,‘hlŌ˛§Κ2ĘKęéíë8W^sũ˛Ž"bˆ‹81KÔ|x_Š]ŋtf°Ÿ§‡&2uÖhĄėĀáĻŽęÛÜŖ“ĸƌRHÅ|]m ų†MÖxI==ü´Q÷?˛ti´Ŧ×ÃûFÆk]Y"7m°?9Hë+&b}B‚=KUũÕī¤ãĸ}ÅDčŠëÕuŽĀøÅF+9"Ö{ô/jΜčÅqšā@G]CËu?1AĮFú‰‰XåX;9Ü'Įē‘ëč Ŗ˜ĻZsëuģ����€Ģ öÔ8""’ĮΛ–˙⎌âqË"¸KÍĻŗFA:Y+īú›Õ„úą_Wę­¤R‡¨øũgLĻ2—”“&ešŽz˙aƒínĢ/­dĩĶ4ŊíFĸô“tũ.SČ9Á`´ĐdĮ؊˛ļgčÍ­VĪķ</ž'"™vœŠßšévÆ­ôA*™\Ŗĸk´kBƒ(픑"uÖŗzĢ_ô¤ĘũRo!ŦēÄĀĢXžRo"ÅīÔŨÉ*ƒTThĒļ’FBD¤Ružf5™[Yy€ĸkKN¤ ˛9Ũ—i*ū0ũ�ž¸bŠŸĩTäįælĘ?6ÂåĶ̚jÍv‘|¤w×ßŪ‘Š ˆˆ¨žˆDšî2]<E$đĢwÄb{iNîŽĒúöVģ /ūB÷ÂŲhU÷Œ4–cÛžû‘ŅŌØąĨƒČC¸Ö~{†BŪ\QK>q~n] ܍‘BnE-ëÉyøuF;Ö?ÜOŧ-wÃĮ Ņゃ5JЧ˛ĮwO/YįKŒˆaČĶ×Ŋķ†áHzY $ōņöčūŨé>WôĢđė<n‘XLŒĢˇ.ũÉÛo`áãîįÕu¤ C"wŸŽŖcD ĩ ?K`���øo7$AˆXÍ=ɑEļ˙ߌй—ZívžwŋøĐîËļõąŲˆA:ŲŊÁJ’ŗzĢ_´ZĒQģe•WōSŨJ ŧ&Ą×åFÄšõ¸ée–ž'2íY˙ĘkÄÜ%QHâOlZ•Ņy+ŠēkÕjégš 2_Ξ3uäėÔäX×W;§ ŅØö”˜Hc.3ĘBt˛�^e>f°Í JŊU>Y+!Ūnˆc{œdVĖ2dížcfšŽí6žČŊT1ËöuËŪüŠÜÜĶŽ1O'Eøyų,Õŧø^N~õ„eĪílmb˜>ž Āô~ÕÔn|'¯5$>uÁhW†ø3­Ûué]: |õöˇ70áķ’âŊE,ĩNßøųõ÷ÛEp}›ûúōÜ˚Gˇ9ˆX""VÔ=Ä%¸pĨčP^Aņį[‘wHLJRLāĩÕtôqũ5ú8Wmvųá\öž{ūCˇˆ‰z\N×9Q����СĄ BDÜø”DŨĶgė‹Jîn‹Y’F˙qųŨō˛ŦDFDĘ0-—_^i•”eAŽĄjÚrÖdqĶ[•“ĩŊG[k‡Ķņv1,KĻâB=žâiēŽģHëeƒ œ*2ųÁČdâ-úãģŗ3Ō_ãe/- cûh—…Éļ•ŦzC%§ŊSFÚmgi•ĘŒ˛4/æ˛ØėD’Ke\ē“%žįmDcŗ]ú}Ā´×5;OYg;ģûšŌáæž”=īĒ9W ž˙ ÷ÚueÅUŒnéÜČĀŽˇ´QŸ#ĩÅß4¸LūcŌ”QUŲėD7ŧ_F$fhdôĸE]zļŠ]{Í7ŦOhLjh ņm5åĮsvåŊĩÕåš%nŊm ����ÎcÖu’DĨÅˍ9YļŽ{^E†ąZlb…BŪų#aYNÚ4ĄAd(+(ŽáÔA "RŽQYĪ–)3ɂtŊ/ú ‹ĄĻ{҆ÉhæšJF|ĢXą¤kŸ–#…zęœye5?fėČNŦL9?)JÖj6Zúl'’ë´cia‰×h•D¤ĐĒÉpüpi § QĢÔ)¨ko8k"ŠJŲ=e¯››B.&se÷#$Ŧ†ŗŊ<Nĸŋ\¤ŽŒĐdiēÔbŠi"ąĢËšÃĶW.v˜+ēūŽ?úî˙ίŊV×|›ƒ‘kWGĮ‹ĢˆzCA iר _[r˛Ąspä:ûí@aåūžLSŗÃĶÛ˧ķĮ…a\ÜŽZéDd¯)?]Ųą´‰uõ ™3E.4T˙ŌžĮŠeŨSÜø†ęē!­���Ā ]"RÄ͍æÎmėŧäÂgL•čˇŋŸU\mąZMúÂôWžYķNį3XmˆÆzbwŠMĒ$"âÔ:Yõî¯*9í¸^”@DœeÆįgMVĢĨ|OÆW5’ˆ¨0ŽdÚ�ŽõÔîƒÕVkŖū`úÆrŠÎ,†+OÖŖ;6žļéŗâj‹Åj1ÍĪ=aq ĐČúl'"UD�•ī-0)uj–ˆXuĘRôY)¯ ""éäøqlŲÎMûΚŦV‹ž0=ķ(¯ŊsF/KšXŨÔpIcaFæqŖĨŅXē'ũëēŸajęi:đQÎéšĻļÖĻęo˛s;d“§\š:jĒWËž­9ߜ7՜/ų4;ī´]æīÕkŸ<Õ~âösûŠL­m͕EŲÜ]¨ÉhnŊ: ų*G2æC§ëÚÚęô…›ŗ[üԌĐT]iįûŪ/#&ĒĶŸĢŠoļ‘Įäé:Ļ,wķĘēļļÆúʂß{ū§zų2$GE^ÖÆôĪŋ9ßĐØÖ\wž8¯Č"ö ôšzÃ!åæ+÷j•5ßtz{~ÍPW���đßo¨ĻÆ”œ2îđ›'ēLXŨÂĮWpÛ˛ļŧēģÕNbMÄܕ)QŖ'\P˜ĒĩÄĻîtęÔ\Ö×üäeīÉĻ&NnũŋõO,<§M^ą0œ#ĸˆä%ˇžąå…?‘›*ôδĻQŽy}îÆ5ėŠĨŦXÆŧsËKY­1n í¸%OĖÕąD }´ąęq[Q‰,J×Q% S´–˜Æ„i;GF$“–Ŧâˇeän\ķ‘ÄRMÄܕ)ĶzÁbĩÉ+SųM9›ÖėceĒđŲķîĄW>&ú)ŗĶn�Ģ™ĩâ÷.Ûķvm8Ôn'‘§īč„ÅņąžWo§¸÷Ąû(;oû;Em$ōV‡/ZpÍB¸ĐøÔ)ŸlĪŪøWrŠ™7w"å[6îûdģđé+ĸĒkxJRõæĪŗž?DŽĒЄ¤Äąmš5éŪĻ•ÍčcŋŖŖŖũNīËZ_ŽK}bū„ĐÄ?ÍÍ۞—ĩnWģxĢCSŠĶˈGlÚ}Bv^ÎûEMq‘ųkã—%˙ ŗFÅĻNˇdfŋūØ6ÆÍW?+ļõ]C]���Āša/^ŦĒĒō÷÷ŋé.ŠË*FƝ9X0ÎĻ?ūĒūŽŽī`ũo÷­š!"$p¨Ģ����� Åeũšûíˆ?C95�����`H ����€ĶŌ5B?Ŗ ´Wßę�����ā #B�����āt„�����Āé ����€ĶA�����§ƒ �����NA�����œÎ�ĄaÆũxņb˙û�����¸ļxa8;�_4�AH,ŪŪnë?������×ÖØl‹FôŋŸBŖFĘÛmöļīl~üą˙Ŋ�����\í^074Õ7ļø)ŧûßÛ� *‰F ×iTįŋ5746_¸€,44ŠË*†ē����€ŸŅp–‹F5 Sã  "ŽṲ+�����€Ÿž�����NA�����œ‚�����8!�����p:B�����āt„�����Āé ����€ĶA�����§ƒ �����NA�����œ‚�����8!�����p:B�����āt„�����Āé ����€Ķa¤Į÷?œ˙Öüũü… ?H‡������= gąh„ŸÂ{8;�)f�ēp|˙CšŪčęú[7W—[~ƒ!&¸ŽoÍ !C]����üĘüĀ ÍÖ3įÎëFę€Ürū[ŗĢëo]81R�����üL†ŗŒÂ[ę%u¯1Õ÷ŋˇˆ.ĮœXÔ˙~������ŽMę!ą;žī?„~ŧxņ7Æõŋ�����€kÎ2?đB˙ûÁd6�����p:B�����āt„�����Āé ����€ĶA�����§ƒ �����NA�����œ3Čûã-gķsŋ((­45ÚIė&S…DĮß3#TĘöŖOkéļ ė×Û¤w¯~Z“ķØû‚ˇžˆâŦd¸Å—žiŸŋūņXÉĀök¯)ĖÛ^PZÕā ‘ģŸ6*))*@|Ŗoļũû/ŲĖũĪ͟ПËë×Ē2síæĒ˜åĢbŊŽąQkáŨå˛ôsĮZ]����ŋ ƒ„xãë^ŲidƒĸãįÎV¸ą6ķą¯÷î|íųcI¯šŠŧŲ›UëąÜŊFÉm+ĻR°|܂%|€3Ū÷ū7j-ĘÚ°Ģ%8iá*­;ĶT•ģmĮ†÷iÕ#Q>C]Øõđ'Ķ_)÷ØÃá7ÚžlrBâo—ko$ÖÄ,HbFNE����ŋ$ƒ„xCÖģ;’˜•Ģč:ĮkBÂ&EMŪōŌēėôüˆ§g(nŽ_ģÍFœ:<L%'"ŌFETÁ0ÄÚNãՉķ&*9"r ›WōMFņŠĻ(ĪĄ.í:,ĩÚ\Â#Žģë8Å{Š���øÅŧ ėî-0ģM~tŽî˛Ykœ.eÅÚ;ÄĒŽÄ7ÛūņÎ#gM­ÄēÉuSįĪ ‘ŌÕ˙øŒÆYGĒ-6’hĸŌū07Œ;ēaųûĮĸęWī˙Z:ãŠguš]SãL_<õ\áøī˛l˙ø—øâęiÆ7ÛH V…–Ĩį”™l$ŅŪšäH[nzÖƒÅÆŠ&%/{ RveÕ}ėˇc˜Í°{KÖîŌj‹$ĘđģS’gh%dúbÍßöjžz9MCDÄo|øÍšÔ—VM—™žXķÜŨ/Ė×ô؉qĮS˙8öā=üW;ë­<+ŅLM^–ŪšũūŒ­{Žël#QEĪIK•ņæÃ™;‹ ĻVuķŅDܓ–Š`{oįŽŧúįLˇe¯.ĪŲōäē¯ÅÉîn‘õāĢΔŽx5-Œú:˙gĶß`Š_6ž4#ĢÜ'íÕŅ\ãą-éG VžSDÜ9?tā.”K†a.ģDęëŠĩ?šŊëĀÉÚž‘ų‡ĮĻĖ ķéäJ2ŗķ[ČE>6.15JÁņuEš™ųgĒÚFä­ ™?Á›%ĸÖžɓĨ&QŪļãö‰‹žKPÚÎÍŪUx˛ÖbW…˙䙉÷j];{oĢĖËÉ+(77 "ouøœ¤c<Oŋģú“(ãÅåŲá+Ö&RÛŠü]9ĒęÚÖC>vúŦ¤(GDT™švK]ė}cËwådķūöû)ö͟fį6šÛŒØC>vz|R”ōĘIžõÖŊVœ'äį6ļķ"Ų„„ä$īĒ­ŲN×ÜÜä¸QâËĻÆuŧeq<—Ÿ›olą“‹_H\ęŧ0ŸËĻÆUgŽŨ\ģprm^n™šUpņ›ŋ4Vt8;7_oą3˛îS×xāŨgķdËÖ&é¨§Špũē|ĪÅĢių“é/nĻY+‚*ļæĢs›:&u^¨=Į§Įkš"ŋqņ÷Ī “Ėĩ���Đ_ˇ<ûėŗ---îîî7Ũ…šĄÉÍåˇ×ŨŦęĢí{ęGĪ^ЏåŠ8‰kĮMĢ­$ũå׎ˆĸ—ūaIōŒÉĒÖÃ;ļîosk¨Į-Ô|jōZŠYøØ˙3;Fnü2kW}Œ‰á‘ˇųžø eėō—ŸH÷šĨūȗ‡…ąwOU˛íįöīŠøÖÜŽē'má­ŖeŽ\sņį§kÛũW<r_ÂöÄöėüŖ•ßOZ¸ré܁Í{ļæ~ĢŧũĒâúØo„÷-dūė/e7Ģį,]˛0!* õHFf!M˜¤øéāū —ņą.DTņU։æͤœ1Q~ ‘ídNÖ9åœ˙ —õ܏íĖž=eÕÂīŌ–?˛āŪ;4ų™;Š\&ßęĪ‘íøĻį7Ÿ9ķ‘% fß19 Ŋxëļ"×)ŅwžōĘ—„—Ĩ%Äũ.pę܏vˇŽ™ęŅk{¤E_SčíažDTŸu¤ū7v›"*Z%"âOån/ō¸m~”Į™>ĪŋõÔŪúę[D’9“ƒd.–Ī_}å+>ōū?.K‰ÕQQvNYŊ]~gT€čúL[ģMá}#÷ÃŦįđēÂũgžķĶĘFÜb¯Ūģk•×­sĻ*ŽĖMGßz=ˇI{gjRÜ4-{.W^ƒ_T¨”jO|UR_W˙ØÛīNŧ=bdÛŠŨ{ ŋ0!@LtūËW><)ž4gņ˙Ü71ā}ÁÖCŽąS5nDęŠ÷–ÔÖ}į6}öŊąZo7:û҆íåō[ß7+>zÜ(ÛŠŸ•ēDLP‰‰xͧooŪ÷chĘÜø™GŲOíŨyÄ:iJôDcÁY÷ÄĮū’&gŦĖyīõũ?ŒMJ^twĖXĪúũŸ~uVé+&j/˙OQUm“=ôöԙã4ž?ŧŋy/ũũ‚9÷LŸ0ÆÕ\°Ģ ÚoR„ėōkō;cAÁCãđßĨ,Hģ'Je>´sīɓˇ™iŠ)wu=Ŋ÷˙`'DrĄ–Ō¯ˇŒšđÛÎˇÔ cī[”pĮ4uķūO÷VyLŠôeŋ¯)Ū{vø„ÛC|¨ĩü?E§-nąķ–&Έ–Væ~včhEģ:áž% ˇFüxr{ŽÁsR„߲ŋŲgā"cƒ;×Ųk ĒÄãc"dˇ4–}}¤ĸž]ˇôū{î cJsŋ,8Qķ}øŊß7sēŋu˙Žũ&Ŕī+ū���øÉĖ M7v?ŲģŽø3hOã­V+I¤˛k,ßąžØ}¤Q“–Ē”I¤ĒˆšiĶ}L÷–đ]](§ÍŸ$g‰H­uŗUW[ˆXŽ“°D,+á¸ĢúnĨĐÄų“Ô*…¤ķ%V=#NÍąŠËƒ¨ō��)IDAT0Yšqŗ§ĘY"NŽsŗ›LŊ—ŪÛ~ųŌ/?Ģ–D/J‹ÕĘe2åø”´ģ5ģsĪō¤ ͞Ļōj‘š¤œtwŒã gDDŧž´šUĶôrŲīcUqÚ{î֒ū?Į-DÄ%¯ūë߸]§’ĘĘņ ˇé¨ú˜ÁF쌍¤ŒŒÖĘe2Š*ôöe̟\q‡ŧĪvYNbÕ‰ˆŦ•zĢ_ôTšą´†'"Ē>cā5ĄAÜõÎŋ…›˜63\Ŗ’sT]đŸ6"qū$ĨL"ÕL?[ûŗ,ËâBžåōÍû¯<ūäšåĪŧ—'Ä,[qõ%_YpāœëÄyIž^~ژy ũŠŊ­ëÄÄ%Æj>ŪĘ)q‘ūdŽĒ剈|c~téŌ™~ŪRߏ˜Ņâ†ĒŠĻގ0‚ øÅ˚ĸQøy˛$öOxdŲĘšQžRoÅØ¸¨@2Ÿ4ډˆ74Éâ’fŒĨđ<gnÜ_ĄŠXW1ĊEœ˜%û™ÜC–‘qÉ)áJЧGĀÄY)E§ķÖtß$›āëÅQˡõ‚gHä_ЧW`TâÏÜ7SÕ뉤b'x˛Dâ1á~Œāđ‹‰ ‘Į˜95T×ņŊŊeb\´¯˜ˆ¸QcŊ„ŖåęxßČx­+KäĻ ö'ibb}ÅDŦOH°§`ŠĒŋĪŒņ‹VrDŦ÷č1^Ô& ž9Ņ‹%â4Á.Žē†–č���`0 ÚÔ8–Xĸ^îĪz0Œ‚t˛úŌŽBĢdsĢõ¯ "’(üē‡XŽ%Ū~íūˆ•Z~YƒĖ¯+‰‰9ŽX7y×ē$1ĮõŲ_¯ûĩĢmL@ØĨûTšFíf5,¤ ĸĖSFŠÔYĪę­~Ņ“B(÷KŊ…4˛ę¯JčõYb•ĒģTNĄĐ‘ ‘Œ8ÎV”ĩ=ConĩÚxžįyAĐđ<§™¤æ6o[÷Ž9vŌ¸0­Z&S̈ˆ¨vvŧ–Ë/7Øâ¤ŦĄĖ¨I 5ū ĖH!KåĢ|˛VrŨķ/StNäÍ& )n•wĢŌúŅ„ë|?]kéŽ÷r[ÆiÜŠ­Ļ 'wcēhåC‘—?,ĄíÛÚÆKŲŨ( OøC8‘ˆy°oW™b‘˜ČŪQ&+ĸú­9įjšÛíŧ ‚dÂĨ#žôɊÅöԜÜ]Uõí­vA^üˆšjÍv‘|d÷īČÔDtųu^_Y#¸OPw?ēõ’3‡jĒÚČĪ•ˆČĶׯëŒËĮj]įnyˇ9rBČč`—ÔWŲĮ‰ųxvÍÍcDbr÷ķčēŦÅ CBo׹ČĮÛŖûw7†ž—ĪËĶKÆuuË0äéÛ5PĖ0 |ž ĪÎZDb11ޞŽĪE$oø‹���āæ Ū!™DBVŗ‰§Ū˙7Ųė6b$=Ļ=ąŦ˜#ˇu˙ųS÷ÉąWŽ]ŅĮeõĢzŨ¯­ÕNB҆eE—ĩē5Z‰ÚmO‰‰4æ2Ŗ,D' āUæcÛ ĒÔwDŽ^wŌs/,C6ž'"Ķžõ¯ė°FĖ]ō‡…„!ūÄĻUuĘĻ.˙ˇįŗ¯ wžķeē V„ß™ļđ.¤¯vVĀfž5R$•Vrę;jŠĘöĨŪB C™I¤S™¯wūģO&/ØbŲKËą, ø=nuŪŽã|øĸÔ¨�–ˆŧŊR´Wũ3/¯<<õ˛(ˇy2}<0éõo-údũ6ŗ|âĸqr7–č|îķ˙î1Bˆ.\}áÆwōZCâSŒöqeˆ?ķŅē]­ÍA sĢŌá¨eߛkö]Ö*ŗÛ‰\;vÕŊ'ņØšKW:WtāŖ‚\Aä<eÖŧ™Ŋ úö~P×ôßÂvüã&ūûpÅ šŦÄ ���øåŧ ¤ āž*;\l›<éō[VŪPW§šĨâÄ V[Wlv‰%ŋȝâÜÄ$ˇdubΧ°ŦXBD’ 0ŲļƒUo¨ä´wĘHĸQģí,­ąR™Q’vÕˆˆˆˇõČa</'f‰LŅz6|ÅĶtw—ÖžiUDÜĩ$â.â­ÆŌÂŦĖë?üķą(I_íÚ•mŋŪTM^“ $ļ5La.1XĨ5œövŨøųg–![!ŪvŨŅšŸŽoŠk#EŲ”ŖŽÉqy‚eÄbx‡č†¯”ļSEįėęÄÔØ@ˇŽ]õ>ŠBDTWV\Åč–΍ ėØgÛĨ€s‘āāų+õåD"†Ü'˙~a\·ŗ1Œk¯žc=ccx{CEQūöÜOŪs]ļ*æZ_����7gĐÖĢŊ-Zn?–™qø˛ĩ ļ3™éé9{JŦDJĩŠiė\ĮBDDĻōj^ŦTõž†˜LĨ”Ø­$W(:dËI:#ÉuZ‰ą´°ÄĀk´J"RhÕd8~¸´†Ķ†¨zīĪn,¯îúŨjŦļ’LŠ â[íĊģV8‘åHĄž:†ŽlÆŌãz+ąUÄ]ķoõãÍKŸíD’ 0…ųLqY‰IĻf‰¤ĩ›ž´¨ÄĀkBXú)įŸ•+$d2˜ģWoéKĢ¯Ú¨ßXO5™zŦ*iniW×+ČāęįëNĩ•U]Õ´Ī^˙ÆîŠk%3Á.ã*ęú–Ÿļ“ßTõ5XÁˇ9ˆšv}Į‹Ģ¨sôĐĶW.v˜+ē6­?úî˙ίíŪ yø3퍑ˇWį+È=zÉlö†ŠŌʎŗĪŠŊÆÄˊS uŊ­äZ,ÐāčŽ|CuŨÖ���ps/ĢNūC˛Ž/ÚøÜķˇī?\ZvėȞôW^X˙ĩ=lá˛ģDܸģ§Jõ9韕š-ÖFcņļMûUĶoĶũ"ŋ• Ŋm†ŌüŲæm‡õVkŖąxĮú§ŸY—gîxU@å{ LJš%"V¤˛}VĘëBú菱īČ(6[­úƒYŸ•“îÖq"™6€k=ĩû`ĩÕÚ¨?˜žą\Ēs#‹ĄÆĘÛĪälZ˙æļÃzŗÅÚhŌ~ö3§ QP_íÔĪô_í5*Æh$DDŠP%_ē÷°E9^Ëũ¤ķ¯Œžäc+Ū–qÄ`˛˜ĪėKßiøéķ¯Ī/zĸŦĩh×GEՍmmĩ§?Ũvā[đØĢĖāåī(ŨžíhE­Š˛ôĀG9Į=F^Ģ ?_ĄühÁųļÖ&Ķ79YŒŋ7ĩTÖ6ÛŽŠOžj?qûš}EĻÖļæĘĸė î.Ôd4ˇōÄĒŖĻzĩėۚķÍySÍų’OŗķNÛeū^DĈ‰ęôįję›mb]ėD—ĒĪŗ>-55ļĩ՝/Î|ûõ—>.nŊē(ÁœŋmË[[‹+ę›[›*Kķ˙+šũ¸ųĘ=…šCeÍDÄ7Ūž_sŨˇ����ü âĒąĒÛW=¯ĖĪųb÷Á‡síŦØMĄ7˙Š{b;nˉĶĨ<ž‚ũ8ëƒįŗZ‰“*ÃV˟ŠüEæ "RŪũÄãė–ŦŦמąØ;Ģ]×yÛĘĒĮilE%˛(]į‘č­%Ļ1a}>]M2>ávv߯5īÔŲX]ü˛%S%DÄE$/šõíŒ-/ü‰ÜTĄwĻ=0rÍës7ŽaWüëËø-;˛^Ûoą Ŧ›&tîʔpŽhFíD¤ ĸ¯ ŲuĮ#"XeˆĘVtFĶyúĘųW%,[bMßųÁËųäϊ¸3-…ŨđNīĪÜëÖoæĸ‡]ssōļ<ŋÍA"wÍÄĨķc¯.Č3j؃´uׁ÷Ūháw˙đÄ?Í ä:–Đ‡Āøä;šväžųJŽH8%~Ņ\÷“m›ˇglühūcŠ—oɅƧNųd{öÆŋ’ËHmĖŧš)߲qß'ëØ…˙H ¸÷Ąû(;oû;Em$ōV‡/ZĀŅččhŋĶû˛Ö—ëRŸ˜?!iŅRQnNöæ}íÉüCâ—%D¸]]“kXęâö­ģōß{­Å.0bypėÂ9QWo8ÄFÅĻNˇdfŋūØ6ÆÍW?+ļõ]C]���ĀO6ėâŋUUUūūū7ŨEqYÅH9–1ôƒiĮS+Ôu}ëŊoÍ !C]����üZ—Uôį~˛#ū âÔ8�����€_!�����p:ƒēFz§H|qSâP����āD0"�����NA�����œ‚�����8!�����p:B�����āt„�����Āé @6l؏/öŋ�����€kû†ŗđ%@„Äĸáííļū÷�����pmÍVąhD˙û€ 4j¤ŧŨfoûÎváĮûß�����ĀÕ~āsCS}c‹ŸÂģ˙Ŋ Ā ’hÄpFuū[sCcķ… ČBp}ÅeC]����üĘ gąhDđčQ25n�ē "҈áZĩj@ē�����øšáŠq�����āt„�����Āé ����€ĶA�����§ƒ �����NA�����œ‚�����8!�����p:B�����āt„�����Āé ����€ĶA�����§ƒ �����NA�����œ‚�����8f¨ �����€ĄQPEs3Šá;.u)7€F^ŋĨm)í?�ŊaD����ĀTŅ´Mdj˙u¤ ".’ŠĻmĸ‚Ēč A����Ā%oĨ_Ięa]ŧHÉ[ '!�����gT×6Ôܜadi€n„�����œŅ¯o8¨Ë€ĖåC�����§ƒ �����NA�����œ‚�����8!�����p:B�����āt„�����Āé0C]������üŌųĐēÛč.9ÉÅä°Ķ3Ŋũ%ŊS3Ôeõ‚�����\‹$ĨQË1zh/Uĩ‘ČƒRĻŅۋÉũzÉ<ÔÅŨ,!�����¸–é‘äo"˙l:ßņˇ…W’čAšĸ$úÕ!Ŧ����€kqgˆˆD=›Zö&%íüKâG›¤ægčâķdú#ŊHD4"€LĪĶ˙úõx—U>O›‰ˆ$rÚ¸˜šŸĄ‹ĪĐéßĶîƒq =!����Āĩė;MíœK÷ĘiÄÕ/‹čí4ēĢfŋCū¯ŅC•ôč|zНž¯Ļm4{ėĨ Į%˙6ú°’ȝ2ĶlžRŪ$˙7éC–vĻŅäÁŦ† �����×rū(Ũõ%Q0í|„ĪPņīéÅ՝[´ęš’M_›éŧ…>ŨKûˆf+‰Ę<Mū4ŽkÔ@2ŸĻ¯7‰î"zh}iĄķzi+r§UÁƒzPB�����p_ ā(<VS‹­šCU+éAyįĢ-"Z•DÅ"Ķ_¨y%MgIÄ}]Bf/J‘‘œf{Qæ1"ĸp%‘™ö9ēzo§/ZhJĀ –������7@ tĸ‚^"’øQæzínÚų>ÕÉč‹Å$?M÷oĨ3íD }øįŽE•´ŗ™î ϧиą¤kĻ”""w‘‚ZžŋŦ{G ú~°ŽA�����Že„ˆÜĒ.ĩXkčŲĶt(˜tDîÁ4E ŲŲôuĮ.—=Váà ē,iPJ0)ĄDDÔâ ǤđOÉŅcK‡cđRaj�����\‹;í{šžˆē˛YįNÔNf"‘+‘@掘4*ŒĻĐĨ—ÃĮČ,Ŗģé.wÚyŦŗņx5‘;‰Z¨ÜŌųĶ"š}0ŽĻ‚�����ô­…V!ŨT˜@ķhœœn ¤įĶÛA´s?•UUR‹+=A>.49‚2h_ų+ɧ# ÕĐÎvzh&é,ôĄĨŗËGhŸˆ>L É2ōq§;c踟h_ß5ü 05�����Žåëš^MĢ&ŅÛäΒÃNgĒiÕûô¯J""ëiē˙Ŋ–@fĸãôP6Ņ4úbH‘CD”yšũ˙’Ęģ{lĄŲīĶēģé‹?’;‘ŲBnŖgkõ †]ŧxąĒĒĘßßPw �����CjØĶC]A?\|ūúÛôĨ#ū`j�����8!�����p:B�����āt„�����Āé ����€ĶA�����§ƒ �����NA�����œ‚�����8!�����g4l¨ ¸iĖ@„!�����gäãJtq¨‹¸ IöÛčA����ĀeÍŖaŋÂQĄaÃ(kŪ�ôƒ ����āŒĸũi˙’ģĖLŗAĀü†äŽ´ EûDoĐ�����ü Eû“é/C]Äų•¤?�����€ƒ �����NA�����œ‚�����8!�����p:B�����āt„�����Āé ����€ĶA�����§ƒ �����NA�����œ‚�����8!�����p:B�����āt„�����Āé0ũīĸļļļ˙������Ü __ß~ö0�A¨˙E������ &L�����§ƒ �����NA�����œ‚�����8!�����p:B�����āt„�����Āé ����€ĶA�����§ƒ �����NA�����œ‚�����8!�����p:B�����āt„�����Āé ����€ĶA�����§ƒ �����NA�����œ‚�����8!�����p:B�����āt„�����Āé ����€ĶA�����§ƒ �����NA�����œ‚�����8!�����p:B�����āt„�����Āé ����€Ķų 1 sá…ĄŽ�����āįuá†a¨#‰Dĸīžûn¨K�����øyĩˇˇ‹ÅbęBžžžVĢĩĨĨåĮęÂ������Ū… š››ÛÚÚ<<<ˆhØÅ‹;Zŋ˙ū{Ė‘����€˙>ˇÜrˈ#¤Ré-ˇÜBŨA�����ĀyāŠq�����ātū?ŖÛĖtį˛āČ����IENDŽB`‚�����������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/profile-session.png�������������������������������������������������0000664�0000000�0000000�00000147242�14156463140�0022513�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��_��G���Fų¨���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨw\UõãĮņ÷eoEdˆ PPÜ{ĸijÎ,+KÛŲļė÷­oļ÷žÚūf{—–+Ms¤•œ8Ë*(K@eûûšzá2Xžž‡â|Î9ŸĪåœs/į}?ŸĪ1™Ífŗ������`ģ†n������Āŋá �����€_������ äpöEEEJKKS^^žŠŠŠĒM������į5{{{9;;ĢiĶϞŗĢŧo‹ŠtÂŨĸĸ"=zT...rssĢrC�����€ Uqqąrrr”““ŖĀĀĀJsKø’šš*;;;šššÕ[C�����ūɲ˛˛$I>>>į\ĮËäææĘÕÕÕøV�����üK¸ēē*''§Ōu,áKaaĄL&“á�����øˇ°ŗŗSaaaåëÔS[������.H„/������"|�����0á �����€_������ Dø�����` Â������ž������ČÁȝ›ėėÜ=�����@ĩ™‹‹ęĩ>zž������ˆđ�����Ā@„/������"|�����0á �����€_������ Dø�����` Â������94t�����˙NŅ7ßÍŅŠė7tsđf2™äáîĻ[n˜¤VÍē9ÕFĪ����@;pčˆ>øčs8•Eđ‚Z3›Í:yō”ŪũßgJ<šÔĐÍŠ6Â����@ûúÛ9 ŨüۘL2›‹õé×?4tKĒđ����PįNfe5tđod2éÄɓ ŨŠj#|�����˙fŗšĄ›Pm„/������"|�����0á �����€_������ Dø�����` Â������94tŒ6mÚŊZŧh‘Ö­T``āŋļÎsiÛ&D;wŅü ´����P]înnęÚ)\m‚[ËĶÃCųJIMĶŽŨ{ĩ˙`|C7¯Fü|}tؘ‘ k,IúkßA-\˛LÉ)i Ü2鞘Íf-[öĢ,X íÛļ)íøqI’Ÿ¯¯ ¨I“'Ģ{÷î ÜJ����@]>$BŖ† ‘ŗŗ“eYaaĄėíÛkøˆ;¤ī~Z ¤äÔleõøųúčÁ{ī”ĢĢ‹eY—Ží¤7Ū›E�ķ/ö_222tī=SĩnŨ:ššškĀ€ūjŪŧ…˛˛˛ô÷ßkΜŲúņĮ9š>ũ!Ũ=ujC7÷‚6gÎũĩw¯Õ2W7WųųųkđāÁjŨēuĩĖožų†úôéĢAƒŲŧͯŋ.U|\ŧîŧë.[V7ĸĸĸôņGŗĢôôtyxx¨Wīۚ:õžz;{tīĻ[ĻLŅ´i÷ÕkŊĩuĄ]•ŠësŋĸßíŲڇ‡ëĒĢŽĒrû‹‡ Ķ€ʕgeeé­ˇfĘ\lÖc?.;;ÛFę–}_¨ÉûDUĖfŗbccĩuË%%'а°P^^^jÛĻ­ (OOOIŌ‚ķĩ#vGĨû=fŒzöėYoįęgŸ~*GGGŨpãVËė߯īžûÎŌžŗ-\¸@Đũ÷?`SFüÎmõöÛoŠk—Ž2thŊ× �˙&“I7_;Q=ēv*WļsĪßúaîBõęŪEcG̓÷ŪŠ?˙æĶ æōą#­‚—RŽŽ.ēlĖH}üåw ĐĒڛtåxíÜũ—bwíą,›ņŌSrt(‰Nœ<Ĩ›ļjņ˛ßTTTÔPÍlPį}øb6›5íŪ{´nŨ:ģôR=÷ÜķōööļZgÛļmšz÷]zíĩWĒá#F4Pk!IŪMŧ5nė8ËĪ'OŌæM›ôõ×_é–[ύyķæ ØēĻččh=š¨ņã/̎:###uĶ7hܸKõƛ3Ô¤I%&$čÃ?Ôĩ×NÖ… ÖŽŪÚķØãO¨}ûöõV_]âš0Žwo=ĻÂ27w÷*ˇwptPėöí†/;vėŠŠĢ÷Âđá#äįįW­mĒÃl6kŪŧšÚŊ{ˇ:vė¨žŊzÉÉÉQIIɊ‰‰ÖÎ];uŨu×Ëßß_ T×.]-Û.üyĄ|}ũ4 Ë2ŸĻM-˙_įjpp°"Ŗ"UPP GGGËō¸¸8É$ÅÅ,žÄĮĮ+88¸ÖuĄ!۟āßnüč傗}âôņ—ß+;'G’ôûÚ(mßšGĶî¸Eˇß4Y¯ŊũĄŽ§g4DsĢ%ŦMČšËڞŸŸu﨎—ú÷îĄĪžŖmąģʕ{yzhøIŌÂ%Ëëģyį…ķ~ÂŨßVŽÔÚĩkÕŖGOŊũö;å‚IęÚĩĢū÷á,Mš4Y§ŋíĢLJJŠžzōI Đ_aĄmÕŖ{7Ũ~ûmÚļm›e~}ûhÔ¨‘åļŊdÄpĩÖęUĢŦ–˙üķBĩÖüųķŦ–×t?¨MH°Ëm›žžŽĐļmtń –eĢW­Ō¸qcÕž};õėŅ]?ô233Ģü]ÁÉŅI­ƒ‚,˙:uę¤k¯ģNžžžŠŽŪØ mú§;vėhŊ×ųõ×_Šmh¨f˜ŠÁƒĢS§NēdäH}õõ×jŲ˛Ĩ6nŒŽ×ö\uÕUęÔŠüˇ˙\ÆqrtRpHH…˙üũũĢÜžeËVJIIŅŅŖå¯ąąą lVũyģēvíĒf͚U{;[ÅÄÄh÷ŽŨēüō š0á uėØQĄĄaŠˆˆĐwÜ)g͛;WÅÅÅōķķŗú8::ĘĶĶÃj™———eßõqކ„„¨¸¨X‡˛Z§6Š—Ųlļ,OKK͉Ė iS'õ×ĩ†x€ŗ�?_]<ØúK‘cIÉzgÖį–āĨÔņô Íúâ[9:8čōą—Ôg3Q;;;MšîjuíÜA’ôĀcĪiÚCOiÚCOiæŸH’úôė֐MlPį}Ī—ų æK’îžûîJģ|wéŌE]ētŠriiišpųe:q⤎ŊîZĩk×NGę›ožÖÕWOԗ_~Ĩ~ũú)"bæÍ›ĢŒŒ 5nÜX’”ššĒŋ˙ū[nnîÚ°qƒ†^|ąeŋQ‘Q2™LŠˆ¤Ug*5ŨĪ”[oĶķĪ=ĢyķæęŪ{§YŊ†ĨK—ǰ°PWžîN­ÛoŋMM›6Õ}ĶîSmØĨÛoģÍænōFsppŸŸŋŌ§[–kíš5Úšs§223ÔČĢ‘úôíĢ^ŊzYÖyķÍ71HP\ÜAŨ˙JJJŌŋ˙ޤä$+Ā?@C†ĩt‰/,,ÔīĢWkįŽĘĘʒ‡‡‡:węŦ‹† ąü>fĖxSƒt"3S;wíT^^žZĩj­qãÆÉÃÃCRɐƒ•+WčāÁƒĘÉÉQ#¯FęÕģˇúôécķë>yō¤/^Ŧ¸¸ƒrqqQĪ=Ë­SU=_õ•âãKēQnßļ]ˇŨ~ģ<==kŨļǍ ?ŋÜr-_žÂjYaaĄŪ{ī]-^´HGØŦ™n™rĢn¸áË:7nÔ¯ŋŽ={ö¨¨¨H:tĐƒĶ§Ģoßž6•—v”——§7ß|C‹-RjjĒ|}}uų„ ē˙ūäpē{c¯ž=tīŊĶ”˜ Å‹éÔŠ,õíÛG/ŊüŠĨgBUõĨĸk–s.>>žŌķŋ˛ōˇŪšŠ=zjđāÁ’¤S§N魙3Ū!\W^yfxÎĖ™3Ôˇo? 0ĀĻëŠĸë´   Ęs˙|āáá.Ånßn˜¤ĻĻęčŅŖ|ŅE:|ø°eš-ĮČč!/7nPpHH…a¤›››†Ž9ŗįhßžŋë¤wZEįjm´hŲRŽNŽ:xđ Bڔ*yyy:zė¨&Nœ¨9ŗį(99Ųž•ž˙•ö|ąåŗC’ĖÅÅZžl™bwÄĒ  @!!m4nÜ8šššI˛íXžúę+<ø"õ?̧Đâŋ”t,IˇŪv[…īĪRIwų?˙üS›6Å(77WAAÁ?~ŧÜO÷Æ˛å3�.Tũû”ģ‡ đ÷Ķ;¯>kųųīũĩzÍzÅîÚĢcIɊŠŲĸAũûČĶĶC'Ož˛ÚvˈĄ=ĸ⥠KWŦ֒ĢëūETâ¯ũÔšCÅŊš˙Úw°ĘíΎ×SVi�Sļˁ¸’/]ŧ</ÜĪšķãÎŧ[ˇl‘ÉdR˙ ē„×Ä[3g*))Iß|û­yäQM˜p…ĻŪs~š;OŽŽzéĨ%Iƒ"d6›}æÛũČČõrppĐØącĩqƒõ7€6D)ŧCųúúZ-¯é~&Ož,OO/͛;ˇÜkXōËb9ģ¸čŌK/•$Ŋ˙Ūģ***ŌŦ>ÖÔ{îҤI“4sæ[jßžŊ j÷ ĢCééō<ë֕+Vh}äz ŒˆĐwŪĨžũúiųōeÚ˛e‹e{{{mŲŧY~žžēūú’ųŲŗPSßĻēųæ[4eĘ­ōķ÷Ķ÷ß§œĶIøŌĨK´uÛV >BwŨuˇ†ŊXŅ1ŅúmåJĢũFFŽWS__M›vŸîēën;vTkÖüiYgŅĸE:räˆ&L¸BwÜq§ ¨+–kīŪ3ã̞páBĨ¤$kō¤Éēūú”“­Ũģw[­SU=¯žZ͚5S‡ŽôĀ˙+??ŋ:i[U.žx˜öíÛ§ģīžK[ˇnUqqņ9×}ųĨ—4kÖ,Ũ=uĒ~]úĢĻÜz›^xū9͞=[’”­[§LQhh¨æÍŸ§ ¨}ûöēų曔‘‘QeyEžzōIũ8gŽ}ė1­XąRĶzH_~ņĨ^yųeË:ŽŽŽš5ëC…††iÍÚuZļ|šbccõî;īØÔ.Ŗ•Ŋ&Ē:Žųųų•ž˙U•éȑ#–úâããåÕČË*\HKKSÖŠ,…„”t‰ĩõz:û:urr˛éܯ fŗY………ū;ģ÷Deۇwč ;wXã;bcåįį'_ßĻVë×ĮĩW™“'O*ũxzĨCp‚ƒKŽŨĄøCį\§ēʞĢĩaoo¯V­Z— 3:->>NöööjĶĻ­|||ŦĘââĘ×××2-Ÿ’´uÛV™ÍfMž|­ÆŋLqqĩtéKy]ËŠŪŸ%i׎]ĘÎÎŌ5×LŌ„ täČaũņĮīVŋƒĒ>ƒ�āBÕ>´âžŽí?¨Õk"u,9Åjš‹‹ŗœœœd2™ÔŽ‚!=KVŦÖŌ ‰† *ū˛\99šå–įääjá’eUn>ŧžĮœĻw_{ÎęßŲĘö€A‰ķžįKjZš<=Ŋ,ßՆŲlÖ/ŋ,Vģöí äädK™ƒƒƒzöėĄ?˙üSYYY80B&“IŖ7Z搉ŠŒRģöíÕ¯?͟?OŲŲŲrssSRR’8Pá$’5ŨĢĢĢÆ¯oŋũF111–oôŌŌŌĨ1cĮĘËËKÅÅÅÚ°qŖZˇn­Ž]ģZÕ}ͤIúúë¯jũ{̉ŗobN:Ĩ˜čhĨĨĨiäȒ!XyyyŠŲŖˆ–KMš4ŅŅŖ‰Zŋ~e2W“É$GGG >\Rɐąüŧ|uęÔŲt9JÂ;ČÁÁAŲŲŲÚž}ģ†ĄŽ;Zö›–šĒ 7čâaÃdoo/IōņiĒnŨJēŊyyyŠM›6:šxĻûø%—\"“ÉdęæããŖ˜čh8p@íÚU=÷ȉ'wđ F­ Ķ7J#GŽŌũŦÖĢĒŲŲŲÉÁÁÁōmmÛf‹É“'+3#CīŊ÷ž~]ēTžęŨģ—†¸D&LĢĢ̤’›ÁožųZwOjé=ŦąąúđčškŽQbb‚N:ŠË'LPÛļĄ’¤§ŸyFcĮ“ŗŗŗŽTZ^VzzēæÍ›ĢG}L—^:^’Ô:(HûöíĶgŸ}χ~XNN%ŗâˇmÛVW_}ĩ$)00PC† ÕöíÛ%ŠĘvÕĨĒŽ Šę㚙™YéųŸ‘‘Qiyppˆ–-ûUfŗY&“IņņqęÔą“ĸcĸuüøq5iŌD‡’›ģ›üũũmžžĘ^§ļžûu!99Ų*p;۔[oU``ÕÆ:uę¤ßW¯Öūũû&ŗŲŦ;w¨[ˇō“J×ĮĩW™“'OJ’Ĩ'eEåîáŽ'OÔ¨[ÎÕÚ Öʕ+”““#WWWÅŒS‹-K‚™Ö­gé}¯ŽJÎ?[?;$ÉÃŨC#G’Trí';f5×L]ËŠŪŸ%ÉŲŲYŖFļÔŊgĪ%&X#Žę3�.TŪË‡ũ‹—ũĻeŋũQōÃĸ’˙ ‰č§ V-ÖFũz•ŧ˙{{7ĒpŸĨĄDi‘†ė!’”’Ē7ßûH—ŊÄ2˙Ë_ûhÁ/ļ?jēĄ_O€Ÿo•ë”0Ÿ|õŊbwû ’ķ>|ąŗŗ;į7î¯ēJ11åįØēmģ5*áĨĻĻ*==]éééęÛ§÷9ëLLLPhh˜Úĩo¯čŗæĩˆŠŠÔE QŸ>}UXX¨Í›7+""BQQ‘’¤Aƒ—Û—¯¯o÷sͤIúöÛoôĶ?Z—ĨK—Ǎ¨H'–ÜL&%%)7'G-[ļ,Ww›6 3>>))I/ŊøĸÕ2WģôRKķcĮŽŠ¸¨XÁ!ÖétëÖAÚēeĢōķķ-7ĪÍ[œ™āŅĮĮG>>>Z°`žzöėĨ¨uP$)áČ™‹Íå&…lØLųJKKŗ|3é_fBLWWåäžGęää¤õëÖ)..NŲ9Ų2›ÍĘÉÉQŸ&6ũŌRKywö͟ÉdR`ķ@%KĒU=ĩm›­îēûnŨtķÍZˇv­ÖŽ]Ģĩk×čņĮÕ{īžŖ/ŋúJĄĄaÚĩk—ōķķa=Äĸ_˙~š=ûeee)88DmÚ´Ņūī>]ũ 4x°:vė¨~ũúIR•åeíÚĩK………ęVæ‰K]:wQNvļââZ†[´+3I¯W#/8‘YŖzkʖkBĒú¸VuūWUŦüŧ|%%%) @‡âiøˆáJHLÔáÇO‡/%›šL&%'%Ų|=}Úzîׅ&MšhüeOtÚôôD˛ÅÅÅĘ?kŊŊŊÕD¯7V‹-´}ûv…††éĐĄCĘHĪPĮŽËÍįQ_×Ūš˜L&ËkĒLiĀV]ļžĢĩ"™Kzŧ´oޏ¸8u8đĩn¤ĨK—¨¸¸XŠŠŠ%=ąN˙qZĪޞŸ‹Í[4WqQąŽ?.Ce‹-Ŧ~vwsWB~‚Õ˛Ē>ƒ��%Ž%%Ÿ ^ÎŌČËK˙Ŋ×zŌũĘzŊžN4ôМ¤”T}ôEížjt>ŊžsąŗŗS§í _N;ī×�ÅÅÅ)==ŊÜdģ#FŒPXX˜åįuëÖZÆ^W$ëTÉøŋ;ęĄés=?ŋ’qæúâķĪ•­'Nčz衇ÕĸE jƨ’Đ$2JŽnnęŨģâ@§ĻûéÜšŗ:vę¤_~ųEĪ<ûŦ\\\´ä—_¨J’rOĩqq)˙¸2—ũņ][Mš4ŅågMėčč¨&MšXzœHR~~ž¤’I]ĪncéæŠS§Ô¤IÉĀ.Îg^›nŧé&EŽ_¯-[6kõĒUōjäĨ!C†ĒK—.Ę;Ŋ߲ŊœN×{æė뛝˛ŠŠŠôŨˇßǏ¸X#GŽ”OĶϞŗŗĶœĶÃhlQږŌųG,m9}cPĶzęĸmÕáęęĒá#FXzn­_ŋ^Sīž[/Ŋøĸ>˙âK:ũMüu×N–Î>–§oS’“ŦŲs~ÔŦYꇞ×k¯ŊĒĀĀ@ũ÷ÁuÅWĘŪŪžŌō˛J¯eĪ2cFKŸnsęT–eYE×FéyVŨzkʖk–ãZÕų_Uš———|||tøđayzz*íxšZ´hŠV-tčPŧēvíĒC‡Yāę\Og_§ļœûuÅŅŅąÜnYĐ÷ßoųšK×.åžLĶąS'­\šBšššÚšs‡š7o.oooĢđĨž¯ŊŠ”NŽ›ž~îųW ”•­F^ûW[ÎÕēāįį'OÅÅÅŠUĢÖJNIÖčÖ%@ˇnŨZyšy:vė˜dgo§V­Jæ4ĒÎg‡s™kŋôŧ-((0üX–=×+ú,Žė3�.dé'äīwfØo~Aa…ë-\˛\â鎛¯ĩ,ËȨŧ×gC†žž?j¸:uh/7Wí;§#‰Į$I-Ô6$HŲ9šÚąk~ūueššk*ŌP¯§¸¸ØĻšE÷Œ×ܟ—ÖC‹ūÎûđĨW¯ŪŠ‹‹Ķīŋ¯Ö„ WX•ŨqįV?O›voĨá‹ûY“Ø]4dH•uФO>ūX›7oVjjŠL&“zŸžˆ¯g¯^–ųZ6lˆRŋž}ĪycQ›ũ\=ņj=ũôSZĩę7õęÕ[6DéîŠS-'{é—ššåĮ feeŲ4įA]sttŦ˛Ģŋ“SÉÍÜå—O¨đ‘Ŧ^•Ė-āîîn RRRП.TĶĻMå|zŋyyyVۜë&ō\”œœŦnŧŅ2‘Š$eįdĢą÷šģûŸ­ôũ˛m9ûXÕ¤žēh›-’““åîî^nČ߀4räHũū{ɛ}é<3fÎTû ēę7;}.øøøčąĮ×c=Žŋ˙ūKŸ|ü‰ūûĀ SįΝĢ,?[éSÍĘ~(•†2^^U?õŦTuę­)[Ž [keį```•åAÁA:rä°ÜŨŨåįë'ĩlŲRŋūúĢ233•™‘i™O¤Ļד-į~}jŪĸ…nŧé&ËĪ cíĐĄƒ–/_Ļ={öh÷îŨödŦ¯k¯2îîîōõõÕŽ;QáMũÁƒ%Ãģjōhf[ÎÕēŦÇëđáCVõzzzZ†Ā%$QË--Ÿ‹Õųė(;ax~Aūé}8ÕęXžOsŠĀŋŅžŋ÷[…/-›7Sī]ĩiklšžŸ;žųÛĶl6k¯Cœë‚§§‡ĻOģSŪĪ|1Ö6Dam­{rz¸ģŠ_ī kĸ7ŪûČĻ�Ļ!|õÃ\ų—™¯ė$ĀûÆëƒOŋļú˛îBwŪO¸{ͤI’¤wßyĮ2ĄjMųúúĘÛÛ[û÷ī¯đ1ĖiiÖcėúôí+'ggÅÄD+r}¤BÃÂ,ߨõîŨG[ˇmÕĄC‡tđāA |Ņ9ë­Í~.ģürš¸ējņâÅúeņbëĒŗžJâįį''''ĢI4K1še] ŊƒŊ˛˛˛Ô´iSË?WWWššš•ûÆŧTzzēÕdˆžžž3fŦLv&Ĩ¤¤Č? @&;“Õĸ’”p$AÎ.ÎōņņąŠ}E…% ûŲcø>ŦŒô ›­&§ëJJ:3Ėĸ¨¨Č* ŦN=Ĩ?×EÛĒ’’’ĸúkÖŦ˕™Íf8p@ž§o|ÂÃÃåäėŦ´Ô4ĩiÛÖō¯ąˇˇšøøČŲŲY‡ԊåË-û Ķ /ž({{{íŨģˇĘō˛ÂÃÃåāā M11VË7oŲ,OO/ŲvĶYŨzdËq­ęü¯Ē\:}Ã{ø°ŠWĢV­$•„ééÚĩk—|||,Ã6kz=Ųrî×'ĩjÕĘō¯ĸvģģģ+$¤"ׯWNNŽ:t(?A\}\{ļč͎¯RRR´iĶĻreŲŲŲúmåo °Ėˇsž QrJ˛Õ|/ĨZĩVbb‚Ŧ^Gu>;ʞˇ‰ ‰˛w°—ˇˇˇÍĮŌŲŲYyeBÃä¤d•Õ_t�ĀŋÕÚ¨h̐Åd2éÆIWęíWžŅm7L˛,÷ôpW˙Ū=,?oŨyŪ†ãG° ^ĒŌÄģąÆn`‹jgĶÖX-9=ĪĖ’ 曊(x *ųģķÄyzŒęÃ? įK/M˜p…æĪŸ§›nŧQ3fÎ,×Å<//Oß÷VŽ\)wwJģļ;Nß|ķĩ>úh–ĻŸ5ô(--MŖGT—.]ôɧŸI*ųƒŊWĪžÚ˛yŗâââ4dș4¯OŸŪĘĪËĶgŸ~*I•>R´6ûiÔ¨‘FŽŠe˖)>>^ŊzõļúCÔÁÁA=zôTTT¤ļmÛf5énCMļk gggõčŪCūų‡ÜÜܨ™™Zž|š<Ŋŧ4iŌ¤ ˇ;‘™ŠŸ~úI_<LĄĄĄ2™LÚ+“ɤ-ZČÕÕUŨēuĶúõëÔ¤‰ˇš)>>^Ņ1ŅĐ€ÍŪöķ÷—ŊƒŊĸŖ7jĐ ÁJINÖĒUĢĸãiĮ•••Uå$ĐĨķHŦ[ˇVMŧŊåæîŽ7XŨ`ØZŗ‹‹’Ž%éØącōđôŦuÛĒâëëĢ[oģMīŊûŽR’S4|øp5jÜX))ɚûĶOÚ´)Fīŧûޤ’oŠ'OšŦˇŪš)ī&ŪęÚĩ›ôüķĪŠY@€>ũės%&&ęîģīŌÃ?ĸaÆI’ūŧPvvvęŅŊ{•åey{{kâÄĢõŋ˙}    učØQQQ‘úę̝tįwž3ŧ+ĢēõɖsĄĒķŋĒrI Öɓ'õ×_iÄéĄdÎÎÎō÷ķWLL´ÂBĪ åŦéõdËš_Wōōķ´ßž ËL&Sĩæ)éÔŠ“.X  āā ÷[ī uĄ[ˇn:t(^ŋ.]ĒC‡âÕŽ]{999*99E11Ņ2›ÍšxõÕ 2ė´:BBBd.6+vGŦú÷ˇ~ĸaëÖAúõ×ĨĘÍÉĩ<yKĒŪgGFf†ÖŦYŖN:)ũøqmÚŧIááártt´ųX6k¨Ŋ{÷ĒOßžrvvVdd¤rrsäéqĻwŨŲīĪ•õÚ�ØæXR˛Vũš^ÇD”+k|ÖdŧW\:Úō˙ŲŲ9ZđËōrëŸ/:whWím:ãqÔį캃—/=%Į2—oÜ´ĩZÖđÎûpĸ‰�� �IDATđE’^~å™eւųķ5ėâĄęͧ‚ƒCT\\ŦÄÄmÜ­ŦŦSęØŠ“Ū|ķMËSX*ō˙ųV­úMŧ˙žR’SÔ§o_%'%éÛoŋQFF†nēųĢõ#"éŊ÷ŪSvv–úôícYÖN7֏?ū¨ĀĀ@ĩiÛļŌ×P›ũ\sÍ$-\°@ģvîÔ+¯žVŽüÎ;īԆ QēuĘ-šxõÕōnė­7(''Gžžįī‚#.šD...úíˇ•:uę”<<<ĻĄC/>į6­ƒ‚téĨãĩ!*Jüņģėėėäį막'ZžÍ5j´œœĩtéReeeŠQŖFŠˆd™'Įîîî?ū2­Zõ›ļoߎĀfētüx:yRsįÍÕ7_]áĶ­Ęē|Â-^ŧXŗįĖ–ŗŗŗzöčŠ.ģhĪž=ÕǧOŸŪZ¸pĄžüō ]uåUuŌļĒ<ōČŖ ՜ŲsôđÃ)33SžžžęÜšŗžøō+ |fXÆO>)¯F^zå嗕’’"___ >BNŸ.IęׯŸ^{ũu}ōÉ'š1s†ėĻ?œĨ6mŌĻMĨåyæŲgåîáŽ'Ÿ|BiiijÖŦ™ĻŨ;MwOjķkŦĒ]õÉÖsĄ˛ķßĮĮ§ĘëÃÅÅEÍš)11Q-[ļ˛Ô߲eKEGG+8ÄēˇDM¯§ĒÎũē’‘ža5ŸËŲLv&=ūø6īĢ]ģvrpt°<ŲŠŦēz_¨-“ɤņã/S›6mĩeķf-Yō‹ åååĨá4`āĀz jËÃÃCžžžJIIąú#•Ėû’›“+gg5kÖĖĒĖ–ĪŽĸĸ" ĄŒŒt}úé'*,,TÛļĄ–'Ųz,GŒĄE?˙Ŧwß}G...ęŪ­ģētîĸÎtk/ûū �¨ŊŸ—ŽPīÆęŅĩ“ÕōĀfjâŨXŨ;wT¯î%OŊËÍÍĶG_~§ãé ŅT›¸ŸÕĶŌVîÕßĻ!čБÄs5:yō”6nŪĻÅË~k€ÖLæĶ}eãââPˇ;ˇĢÛo9###5gÎlÅDG+%5Uövöō÷÷SˇnŨ5fĖ >ŧÜ7}ĶĻŨĢŋiŨúHËxōäädŊûÎ;Zĩę7%''ËÍÍ]}úöŅ=÷ÜkyėcŠØØXŋtœ$ictŒåņ­’tûmˇjåĘ•ēæšIzåÕW+­ŗ&û9ÛĀũu<=]ŅŅ1~#ģhŅĪz˙ũ÷uđĀyxxhøđzėņĮ5fô(y7iĸŋąų÷ ����ĩõāĪ×jûCiäÅÉŲšâ‘ ãëÛį+)9ĩVõí­—ŸŽvāÂĸ"Ũ˙čŗĩ¨î5õņVFæIV<A˛ŪxáÉZmo..ĒŖ–”8vė˜‚N?e´"˙¨đåB•˜˜¨! ÖÕW_ŖĘ<����ÎGĩ _¤’^#Ũ:wPHP+yzz¨  @)ŠĮĩc÷^í;WûFփņŖGčâÁl`ŠŠŠ´ęĪõúyé ƒ[öĪöO _ūÎ.t/žø‚$iʔ) Ü����¨?YŲŲZˇ!Fë6ÄTŊōyęįĨ+R@øržŠ;xPkÖŦҊËĩfÍũßūSīsP�����€Ú#|9OíŲģGO?ũ”ŧŊŊ5}úC՚D�����œ?_ÎSŖFÖƒq Ũ �����PKv Ũ������€3Â������ž������ˆđ�����üc˜L Ũ‚ę#|����Ô9OwwÉlnčfā߯l–—§WCˇĸÚ_�����uî†ëŽūgvQĀųÍΤ[oœÔĐ­¨6Â����@ iÕBSī¸Ežîî2 x¸ģë˙îēMū Ũ”j3™Í%ũĀâââPˇ;ˇŗ¯Ķũ�����Ԗš¸¨N÷wėØ1ŗœž/������"|�����0á �����€_������ Dø�����` Â������ž������ˆđ�����Ā@FîÜ\\däî�����Î{ô|�����0á �����€_������ Dø�����` Â������ž������ˆđ�����Ā@„/������"|�����0á �����€_������ äpöņņņ Õ�����€$ggįJ˭—víÚÚ�����€›¸¸¸JËv�����` Â������ž������ˆđ�����Ā@„/������"|�����0á �����€_������ Dø�����` Â������ž������ˆđ�����Ā@„/������"|�����0á �����€_������ Dø�����` Â������ž������ˆđ�����Ā@„/������"|�����0á �����€_������ ToáKAAæÎ[ ÛīēG—ŒžT]{öUhxguīŨ_&NŌ3ŪŌą¤¤újN­Ėžķ“BÂ:čĻ)ˇ7tSū~š7_!atũMS.Čú����˙nõž¤ĻĨ銉“4ũ‘ĮôÛĒÕ*,,T—ΝÔģWOy7nŦmÛļëƒ?Ō%ŖÆ)2jC}4 ˙pĪ<÷‚ēöčĶĐÍ����� JõQɓO?Ģģv+<ŧŊfžņšÂBÛZ•8pP?ö„6mŪĸ˙{`ēÖūžRNNNõŅ´šęĘ ēü˛KegĮ¨­†˛-vGC7�����›žääähÅĘU’¤ž}ē\đ"I!!ÁúėãåáîŽĖĖL­YˇŪčfՊŊŊŊœåččØĐMš h÷ŽŨ Ũ �����lbxĪ—'NǏ¸X’Ô˛e‹sŽįééŠåŋ.VS98”oÖîŨ{ôÉg_(jÃFĨĻĨÉÍÍMíÂB5ņĘ+tńËd2™ŦÖߡŋ>üčmØ­”ä98:ĘĪ×W薛nPppP×Ÿ=į'=úÄS1P_~öąÕ~6oŲĒĪ>˙R1›7ëøņtšē¸(¤MˆÆŒŠŽ›,Ëē?Ν§‡}B¯ŧB/ŊđŦ>ūôsÍ_đŗ>,GuíÚE˙šī^õėŅŊV¯­2ÅÅÅúqî|Í_°P{˙ú[§N’Ģ‹‹ÚļmĢĢŽŧ\“ŽžhÕçĻmÎ/(ĐGĒų *!!Qęͧ—ūsß4›Û*I÷˙÷!-\´Ø˛Ī°’¤Ī?™Ĩ‹˛ŦWãP™ģvëškoPnnŽŪį-ŧd¸ĨŦ:įäųpŦ���� ÃđđÅĪĪWŽŽŽĘÉÉҜįję]wœsŨ�˙ —˙˛dŠ˜ūˆ Ôĩk 4PÉ))ŠŒÚ¨Ņ1úcÍŊõæë–`{ėMēîFåææ*<ŧŊzõč!“ɤ؝;õÍwßëįÅŋčû¯ŋPxxû­.ß~˙ƒž|ú9IR÷n]5dđ`edf*:&F/ŋúē~Y˛Tß~õ…ÜŨŨ$IÎÎΒ¤Ŧėl=0ũa­Zũģú÷ë§Ö­[iëļíZˇ>RŅ1›´äįų ŽĶļ–zøą'4wŪ9::ĒoŸŪōmÚTŠiiŠŒÚ ­ÛļiûözåĨį-ëפ͒t˙ĶĩtŲrš¸¸høđarwsĶî={4áĒktÃu×ÚÔVIvņP9;;kÎOsåäč¨[nžQ’ÔĒeˇsIHHԔÛīTvvļ^~ņyĢāĨēįäųpŦ���� Ä|ÚÁƒÍFyųÕ7ĖÁĄáæāĐpķíwŨc^ĩúsnnŽMÛIH0ˇīÔÍnž7ĄUY\|ŧųĸ‹/1‡†›ŋ˙aŽeų]÷L3‡†›ß˜ņVšũŊûū˙ĖÁĄáæŠ÷ū_×˙aöæāĐpķˇÜfYļoß~shxgsph¸yŲōVûČĖĖ4;Ūn~ņåW-ËY˛ÔnîÚŗ¯yôĨ—›-e'Nž49ĻÜ6ÕmkeūŪˇĪn ë`Ūļ=ÖĒlĮÎ]–×ŗgī_ĩjķŸk֚ƒCÃÍí:v5īÚĩÛĒž/ŋūÖÜļ}'sph¸ųēoąŠŨ{öūe 7wéŪģ\YMŽÃsį•Ģ?##Ã<|ÔXsph¸ųŗ>ļÚOMÎɆ>Ö�����ãT•ŠÔˌą˙Ŋ˙>]7y’$iåoĢtëwŠK>ēęškõÚ3´v]¤ *ÜöĢožS^^žFĄ —ˇ*kŨĒ•~čŋ%ë}ûeų‘# ’¤îŨē•Ûߝˇßǝ>˙Ô˛]M֝ȎßĪVaaĄ.1\—ŒnUæååĨ˙Ū˙’¤9?ÍSaaaIÁéa)'NœĐķĪ<ĨĀfÍ,Ûxzxhâ•WH’vīŲ[§m=S‡§Ū}ëMŊųú+ęŌš“UYĮáęŪ­Ģ$iĶæÍg jĐæŌaBãĮ-×KãÆë¯UhÛ66ĩ×5:eäįįëŽģīÕūũt۔›uסY•×äœlčc ����h8õž8::ęųgŸŌŌÅ uĶ×ĢE‹æ*((Đæ-[õáGŸčÆ[nU¯~zåĩ7•••mĩmdd”$iØĐĄîûĸA2™LÚŗg¯233%Im۔ÜĖŋũî{úëī}åÚ1°ŋÕ0•ęŽ_‘Ņ1’¤Ą ްŧŋ~’Jnž÷8hUæååUnŽI–ô“'OÖi[Kųûûiė˜Ņē|üĨ’¤ŦŦl>|DqņņŠ‹—ģģûé6Ÿ,ˇmuÚģS’Ô¯oŏ†1ĐĻöÚĸ6ĮA’Ėfŗ˜ūˆĸc6éĘ+.×ŖO/ˇNMÎÉR uŦ���� §^5]Ē]X¨ž~â1=ũÄc:–”¤ččMŠŽŲ¤?ÖŦŅáÃGôŅ'Ÿjõīč§9ßÉĶÃC’t$ĄäÛ˙?/Rä† î×ŅÁAųŠ‹‹W׎]ôčÃĶĩm{ŦbwėÔ¨ąãÚļ­ č§Ajā€~–ų7JUwũŠ”ļŗE‹æ–ģģģÉÛģąŌĶ3”˜xTíÂB-e-šWŧŊƒŊ$Y&,ŽĢļžmßūũzûŨôįšĩV7ūg3›Íå–U§ÍĮ’’$IĪéĶĸÅš'bŽŽÚIzé•×´dé¯ mÛV/ŋđ\š‰œĪŽŖ:įdІ<Ö����€†Q¯áËŲüũué¸1ētÜ™Íf-[žR>ü¨ūŪˇOī}đĄ}čAIRöéž0ëÖGVšĪ“§˛$•LōģhÁ\}ûũ÷š7Ąūú{ŸūŪˇO_~õ<=<t딛uīÔģ,“ĄVwũŠäæäHRĨOŅqv*šYÎÍËĩZîčdû#Ģëĸ­ĨvīŪŖ‰“¯WvvļÚˇ ͘Ҏ(°Y3š¸–ŧ†ĪŋøJ›6oŠpÛę´97ˇäõž+,prr˛y_UÖU‹ã°eë6­?ŨĢåī}û´vŨz Š MMÎÉR uŦ���� §Á—ŗ™L&9BûЛ3ßֆ -enînĘĪČÔgXáđš¸ģģéŽÛnÕˇŨĒcIIZŋ>JK—-×ęß˙Đ[īŧ§ŒŒL=õÄŖ5^ŋ,W77ågf*''÷œëäœ!ÜŨ*ĘN]ŋļsy}Æ[ĘÎÎÖČK†ëũwŪ*wŋ`áĸZĩŗ”ŗ““ ”ŸŸ_ayvvV…Ëkĸ6Į!''G ¤ũúęå×ŪЃ=Ē%‹ČĪĪ×jŊšž“5QWĮ����Đp ˙Ę|}d”ŪûāCmØ]åēĨķWOOˇ,kŨēĩ$)!1ąÆmđ÷×.ĶĮž¯Of} ŠäqÄįšäˇē뗴ŊdčĖáÇ+,?yō¤eū–u8ĖĻ&m-ĩeë6IŌu“'WØ{bī_ÕI}O‡IÉÉ–<_'õHĩ;;„ë“YčöÛĻč’Ãu<=]LØj(T7įdMÔæX����ŽááË7ß}¯oŊŖW^{ãœ=J­*ōqöĶoôë+IZ´xI…ÛäįįkūŸ•’’*IĘČČÔü…?ŸsũÁƒ"ätē'ÆņãéÕ^˙\J'“]ĩú÷ Ë˙øs­¤’Ą$AA­ĪšŸĘÔU[Ërv.?ėį×e+tøđIĪųRÂÃ%ŠÂ�ލ¨HĢ˙øŖFû­¨]ĩ95’Ŋ}ÉÜ+¯ŧøŧüũĩ>2J~ô‰ÕzÕ='k¨c ����¨†‡/ĶĻŪ-GGGmÛĢ[nģŗÜS[$éčącz퍚=į'IŌ-7Ũh)ģvŌ5rqqŅÆč}üÉgVÛčŠgž×§?ĸĮŸzF’”——§‡}B<ū¤ĸc6•ĢkÉŌeĘĪĪWĶĻMåëÛ´ÚëŸËĩ“¯‘“ŖŖ~[ũģVŦüÍĒ,%%UoĖxK’tķ7T8‰Ģ-ęĒ­ĨJCŽ•ĢV[-ŨąSĪžđĸúôî%I:v,ŠFí-5fôHI%Øbwė´,7›ÍzûŨ÷•–vŧZûķ8ũĻSYYJOΰ*ĢĢãиq#ÍxãU™L&Í|û]mŪ˛õLÕ<'kĸŽ5���� á>įKxx{}đî[ē˙ŋ)2jƒF/?5 (y´nJjŠĘl6ËÉÉIO>ūˆ"°lßŧy ^åEŨ˙āÃzųĩ7´`Ņbuo¯ėŦlEĮlRjZšZˇjĨįžyRRÉã“}øA=˙â+ēæÚÔšSGĩ–I&Œ‹SėŽ˛ŗŗĶĶO<*;;ģj¯.­[ĩŌsĪ>ĨGJwNĻ>Ŋ{)88HŠ)ŠÚŖ“§NiØĐ!ēmĘÍ5ū]ÖU[KŨyû­ŠŲ´Yō™öėŲĢā Ö:p0Në#ŖôđƒČ×ĪWŖc4į§š**.Ō5¯ĒQģG]2Bú÷ĶúČ(]uÍĩęÛ§—œ]´gī^eddčž{§ęĩ7fØŧŋ€�5mÚTŠŠŠÅUjĸKF Ķĩ“ŽŠĶãĐ¯oŨ}įíúāÏô<¨%?Ī—§§gĩĪɚ¨ëc ����h8õ2á‡ę÷UËõÃėĩfí:íß@;wí’Ųl–§§‡zöčŽūũúęę‰WĒy``šíĮŽ­ļmÛęŖO>Ն ŅúyŅ/˛ˇˇWëV-uõÄ+u۔[Ô¸q#ËúˇÜtŖÂBCõũėĩmûvũŊoŋŠŠŠäÛ´Š.7FSnžI]ģtŽņúįrõUW*,´­>ūäsÅlŪŦÍ[ļĘÕÕUááíuå—ëŠË/ŗ kŠŠējĢTr\^å%}úųŠÚ°Qą;v¨]X˜f}đކ]<TųúmÕīZĩúwũēlšÆ]Ŗ6›L&}ôŋ÷ôū˙fiņ’ĨÚ°1FžęŨģ§øĪ}–á9yyy6íĪŪŪ^3^UO?÷ŧŽIP^nžĨwTˇĮá?÷ŨĢu‘QÚļmģ}âiŊ÷vIHTŨs˛&ęōX����ŽÉ|z⌸¸85ps������ūYĒĘT¯������` Â������ž������ˆđ�����Ā@„/������"|�����0á �����€_������ Dø�����` Â������ž������ˆđ�����Ā@„/������"|�����0ƒŅ)--Myyy***2ē:������+ööörvvVĶĻMegW˙ũP _ŠŠŠtôčQš¸¸ČĮĮ§A^ �����¸°+''G‰‰‰ Ŧ÷|ÂĐÚŌĶĶåææ&‚�����Đ ėėėäîî.WWWĨ§§×ũFî<77WŽŽŽFV�����`WWWåääÔ{Ŋ††/………2™LFV�����`;;;ÖŊõ^#�����Ā„đ�����Ā@„/������"|�����0á �����€_������ Dø�����` Â������ž������ˆđ�����Ā@„/������"|�����0á �����€_������ Dø�����` Â������ž������ˆđ�����Ā@„/������"|�����0á �����€_������ Dø�����` ‡†n@}Zo§™‘ÚrĖNŠŲ’—ŗ4°Uąė_ >-ĖõږoēčŪž…z$ĸ°^ë�����õë‚éųōgŧÆ~ë$oWŗ>Ÿ¯?oÉĶ˙Ææ+é”4ú[gíJ1Õk{^Q ‘mŠęĩN�����P˙.˜ž/ŗbîkÖ§—X–uofÖĐā| ųÂYëŲŠƒoũ…!×w!x����āBpÁ„/ųE%˙Ęōt–6Ũ™gĩŦ°Xzeƒ~Üe¯C™&ĩđ2kZŸBŨŅëĖÖ˛ĶĶĢ´#ŲNEfŠŗ_ąžģ¸P­Šm*/;ė(¯Pzæwũ¸Ķ^ÉY&x˜5šs‘žŧ¨P§û'ĩžéĸ‡čpĻI?ívĐÉ<)ĸUąŪ›¯�ÛÚ�����ę×3ėhth‘ö¤ští\'E'˜T\É/ũ標Qzh`ĄbîČĶ}} 5}…ŖžØj/IĘĘ—Ž˜í¤p_ŗ~ŋ%OŪ’§Îūf]öŊ“ŌsĒ.¯Č˙-uÔWÛôʈBmŊ;OĪ]\¨˙E;豕gō1G{if”Ŗ:ø™ĩįŪ\mē#W[ŽÚéå5Ž6ĩ �����Ôŋ Ļį˔îEJĪ1éÕĩšŋÛY^ÎŌ€–ÅV¤É‹äV’_čDž4+Æ^ĶęēĶCƒÚ4)Ō–ŖvzcƒnîV¤Ã'L:‘'Mî\¤öMKRœ7GčĒErveV^^VZļômŦŊ^V˛Ž$…xiOŠIīmtĐ Ã åT’û¨OąnėZ˛N‹FŌ%mŠ´ųhI†VUģ�����@ũģ`zžHŌ*îū\ũ81_×w)ŌÁt“î]â¨.8k÷é wˇ'Ų)ŋHŪÆz˜Îā bíO7éTžÚÄŦ0ŗnžī¨7Ö;hËQ“ėMŌ ÖÅrsŦēŧŦØd;K}š[×ŲŖYą˛ ¤}ĮĪLÜÉĪzÆ.fK¯–ęÖ �����ŒwÁõ‡ps”Æĩ+Ö¸v%!Æqvšü““]験ķu"ˇdŊ‘_;éėį•SJ:eR›&f­ŧ1O3"ôŲf{=šĘA-ŊĖzfhĄŽí\${;UZ^Vi^ÎÖË=O˙|ōŦ)i\+8bĨ#¨Ē[/�����0Ūž;%y8•ü;ÛEAÅēŦ}‘–í+×ĶČĨdųg—åĢ“_ų‰aZx•,ķu—^^¨—‡jwŠIoG9čօŽjīSŦæ*ËĪVZį ëy-?—–Ûĸ:õ�����ã]ΒNIĄī¸hÆúōY“Ų,ũ•f’ŋGI0ŅŲŋXÎöRJ–I횚-˙š¸šÕÔÍ,g).äE{ĪüęÂ}ÍzwLėMŌŽTģ*ËËęė_,;)ęˆŊÕō GėÔČYjÛÄļФēõ�����ã]=_ü=¤ûúę•ĩJĘ2ilh‘š¸IGOJßlwPäa;}}E¤’Ą?ˇö(Ō :ĒŠģÔ+°X‡2MšžÜQÍ=͚7)_‡3Mšü““^V¨ŅĄE2Iúa‡ŊėLRßæÅU–—ÕÄUēŠk‘^_į ību 0ëĪx;ÍÚä ûûyÔtUĒ[/�����0ŪžHŌ‹Ã ÕÁ×Ŧ/ļÚkņ_N:ž#5r.™Ôöįkķ5<äL8ņęˆ5r6뱕:vǤWˏ°b=;´$ ÔēX/ĐÛQzî9ØIáM‹5{bžB}Ė õ1WZ^‘Ŗ äádÖ~uRrVÉđĻG" õā€B›_cUí�����õĪd6›Í’§   :Ũy\\œętŸ������5uėØ1CōĘöÉD ������"|�����0á �����€_������ Dø�����` Â������ž������ˆđ�����Ā@„/������"|�����0á �����€_������ Dø�����` Â������ž������ˆđ�����Ā@„/������"|�����0á �����€_������ Dø�����` Â������ž������ˆđ�����Ā@„/������r0炸øxŖĢ�đggg§âââ†n��ø‡ão �ļpvvŽ÷: _Úĩkgt������6‰‹‹Ģ÷:v�����` Â������ž������ˆđ�����Ā@„/������"|�����0á �����€_������ Dø�����` Â������ž�øöî;ljŦ øštP˂bWDą€°ŦēęÚ{īŨĩcØÅŪ{wmØÖ‚ŽåŗWė‚ ¤×|DFB€L0‘žß9œ“;wîdŪÜŪÜšCDDDDDDÄä ‘1ųBDDDDDDD¤ALži“/DDDDDDDDÄä ‘1ųBDDDDDDD¤ALži“/DDDDDDDD¤“Ũ PÕķWī˛ģ DDDDDDDô“ŗ)”ŨMøaŋ\ō%'ŧéDDDDDDD”{đļ#"""""""" bō…ˆˆĪčŊ�� �IDATˆˆˆˆˆHƒ˜|!"""""""Ō &_ˆˆˆˆˆˆˆˆ4ˆÉ"""""""" bō…ˆˆˆˆˆˆˆHƒ˜|!"""""""Ō &_ˆˆˆˆˆˆˆˆ4ˆÉ"""""""" bō…ˆˆˆˆˆˆˆHƒ˜|!"""""""Ō &_ˆˆˆˆˆˆˆˆ4ˆÉ"""""""" bō…ˆˆˆˆˆˆˆHƒ˜|!"""""""Ō &_ˆˆˆˆˆˆˆˆ4H'ģ@DDĒ;xčļlێ§OŸáKXŒa_Ūû÷EUį*ŲŨ<"""""J…#_ˆˆ~1ë7lÂāa#påjŦŦŦāRÕZZ\üį:wë‰{÷īgw‰ˆˆˆˆ(&_ˆˆ~1~ūË�“ÆÅĄũģąiũZ\<…råĘ"..[ˇíĖæQjLžũbÂŋ~�XXXË °vår\šxS'O”+ŋīĀA4mŪå*8žR´iß į/\”+Sŧ´Š—ļ“53{î|/m‡C† ËǏÔDņŌv8wūšˇjƒ ŽßoqZˇa#x5FŲō¨]ß3gĪEdT”đzRRü—¯„gŖĻ(WÁnuÜąrõõŧ)DDDDD˙aLžũbĘە�Œ›0 ķ.ÂÕ kˆ‹‹CVȟßRŽėĒ56b4î?xˆzujŖŠSe\ ē†.Ũ{áԙ@•ˇ­§§�˜=o"""āXŠ�`ībL™6Ą!Ąđöō„žžV­ Ā€AC…ug˚‹y |…žŨģÂÄÄŗæĖĮĒÕkŗøNũ˜|!"úÅLņ™‹|ų‰ĨËV MûNp¨\]ē÷…‹˙å""#áģØ�0mŠ–.ņÅē5+Ņą}[HĨRĖ_¸Håmkkk� pėĐ~lX°°pŦ\%Á2oÎL,˜7{vl…™™._ž‚˙Ũš‹OŸ?cÃĻÍ�€E įaؐAXŋv5tttāŋb’’’~ôm!""""úĪbō…ˆčSÁž<Oũ™ĶĻĀŗaX䎸„œ;ēöĀļí˛9_nܸ‰˜˜�@ĶÆŪÂúŧŊ��>B|BB–ÚФq#ččČ˜wķÖ-ĄžÚĩk�LLLp#čÜŊ… öåqķÖm$%%A"‘ €•ŪŊÄ¤DØX˙†đđp<yú,koŅ/€É"ĸ_‘‘!ūlŨūK!čōėŪąöåí��‹ũü�Ÿŋ|�äɓÂēyķš�¤R)BŪ‡diûŠooJ™ƒF__zēēé–˙ú­ŒT*E­z PŗV=ÔŦU΃_��BB˛Ö""""ĸ_Nv7€ˆˆÄ{ûîŽ\ BLL ÚĩųSXîXÉŖF G§ŽŨņ>$‰‰‰Čk.K˛ÄÅÅ!66úúú�€OŸ> 뙛›ÉÕ/üūųķ— ÛĄ­õ=w_°@�@ll,âãã…ya‘�cc#˜™ĘļŖ­­åūKę+Sē´¸7€ˆˆˆˆčđ/DDŋgĪ‚1|ä_˜0i Ž;.,OJJÂŲsį�Ȓ!:::¨ėX &&&�€#Gŋ—=tø(�ĀĄbáuËoONēs÷�Y"åėųķĸÚTĻti!ą“2‰ott4ęyxÂĨf-Üŧu+ÚC[[III(\° ę×­ƒÚnޞ1R)LLŒŗú–ũįqä Ņ/¤fjđhāŽŋOœÄ€AC‘?ŋ%ōå͋÷!Ą� 4�€lŪ•Ąƒ`Ę´™;a.ūs Ÿŋ|ÁŲsįĄŖŖƒŅ#ŋ?BēNíZØĩg/æÎ[€gΟãÚĩë°°°@hčĨm277Cn]āįŋŖ˙‡Ķgq÷î=„……ÃĨĒ3\Ē:CKK íÚ´ÆÆÍ[ŅĨG/x¸ģãūƒø÷ÆMTvŦ„ēujkæ #""""úĐöņņņ€°°0˜ĸŽ.š¨“ˆ(7“H$hčáKKK| Cؗ0ŧ{˙úúyPÕš &Ž‹Æŧ„ō•P¨PAŧxų W¯!$4.U1wö TuŽ"”sŽRÁ/^āõë7~ņmZˇD%ž=ÛĸEҤ‘lÂŪ€uÆŧPĸDqa}—ĒÎȓ'ž?Æĩë˙"Ož<øŖy3LŸę#ŒŠŠåæ ---<|ô—¯\ELL ūhŪ ŗgL•›“†ˆˆˆˆH“˛#˙!‘JĨR�†­­­Z7މ:‰ˆˆˆˆˆˆˆ˛*;ōœķ…ˆˆˆˆˆˆˆHƒ˜|!"""""""Ō &_ˆˆˆˆˆˆˆˆ4ˆÉ"""""""" bō…ˆˆˆˆˆˆˆHƒ˜|!"""""""Ō &_ˆˆˆˆˆˆˆˆ4ˆÉ"""""""" bō…ˆˆˆˆˆˆˆHƒ˜|!"""""""Ō &_ˆˆˆˆˆˆˆˆ4(W$_*W­ŽâĨí°kĪŪėnŠĀ­Ž;Š—ļÃß'Nū’õ“ĸ”8Ëč§CįnŲ.5‰5{î|/m‡C†ĢĨũâââ„~čÂÅK ¯wîÖÅKÛaŌäŠŲĐ:Em;tÚ{ōÔéėniĐũúY]×Ųŧļ" —$_~ļˇīŪĄxi;Ŧ]ˇ!ÛÚPŖz5Ô­S ųķįĪļ6äVųōĄˆÂOVŲ5å?~ÄÕ kÂßGŽĪÆÖČû/œÛ‰ˆrǟ}ũL”t˛ģ9ŅáÃGŗģ ˜=sZv7!×=j8ZļhūĶļĮcMD9őcC*•ĸLéRxøč1Nž:øøxčééewĶūįv"ĸœęg_?eŽ|I%&&sæ-@]wO”Ģāˆú^Xĩ&�RŠT(ķáÃGŒ7nuÜQļŧ\k×ĮÔéŗ�hÚŧ%fΙ�˜6cŠ—ļCTT´¨íī;pM›ˇDš Ž°¯TmÚwÂų åĘDDD`Ȱ‘°¯TU\jbūÂEXŋÅKÛĄO˙ABš´CäǏÔDņŌv¸vũ_Œ›č'TttÆĖ9퐔”ôCīŠfëļ(^Ú5kÕCt´,6Ūž{‡ōNßn ø�āäRC8†‡ GEGg88š`ڌYrĮ,Ŗc}îü4oÕĢ��’’’āŋ|%<5Eš ŽpĢ㎕Ģ×ČĩMY|gDLė2ÕGL_a#FÞR8šÔĀŦ9ķ‘œęuUËi:ޞ{¤bއ˜cŸQÜdäČŅc�€ŽÚĄxņbˆŒŠÂšķʉ=ĒŌÆĖúĻ9ˇ‘zˆŊ–s”"6.cÆM€ƒ“ *W…ĪÔéHLL˰ bÎ+b¯­(÷aō%•ņ“&cųĘÕ042D—NđņĶg˜=ÛļīĘô8ÛwîFŠR%ŅąC;,X�ë7`ä_ã��M›4FĄ‚�UĢ K§ŽĐÕĶUēíUk0lÄhÜđõęÔF§Ę¸t ]ē÷Š3Bš‰>SqāĐaHĨR¸ÖŦcŸĀē ›��::dJųÖĐgĘt„†„ÂŊ~]DFEaÕęĩØŗoŋĘīe]Û6­áæZīŪŋ‡īb?�ĀäŠ3ƒvmū„kÍ�€<zy��ã&øĀØČ;u@\\ÖŽÛ€€õ3Ŧ?åXĪžˇ�pŦT �0cÖ\Ė[ā‹ČČ(ôėŪ&&Ƙ5g>V­^+ŦĢ,žĶ#6vƒę#ϝš4yö8mmmx6ôĀ…‹˙`īž u‰-§é¸ĘJ둿ˆ9bŽ}Fq“ž>"čÚuH$x4p‡WC�éßz$ö\¨J3뛲zn'""õ{-)æ:)ŒĨËđøÉSTuŽ‚ˆČHlظ9Ķël1įą×V”ûđļŖoâââđāá#”*YŗgLƒ}y;čëëcŅ’Ĩ8ö÷ ´mĶ111¸ūī ččč`š˙čéę"11‹–,îGėŅ­ ĪžÃģ÷īáŅĀŨētRēíˆČHáŸđiS|đgĢ?��“&OÅÆÍ[1á"Ô¯[Ÿ>Æáoß Îœ>M7BLL ę¸7�H$’ ˇĄ­­ �(P ?V­đ�$'%c˙ÁC8x­ūh‘ÅwŽŌš=g>ü–.WXîģ`*98ČĘ˜††ŪM°~#ĖÍÍqâä)ØØXcĖč‘Bų”cV­š fN—M„Ч§‡…‹–`ëöčŅ­KēÛOYĪĐĀ�ûwī€ŽŽ>}ūŒ ›6�-œ§ĘŽčØžjÖŽ˙ĢĐ­kgÄĮĮ+ī´ÄÆnęv1Œ˜žęķ—/8tDv‹ÄÜYĶáŅĀņ ¨įî)W—Ør€fãJLßJ?˜ã!æØkkk§79zė8¤R)Ē:WĨ…<z`é˛ ˇ‰=ĒÚÆĖúĻŦœÛ‰ˆH<1×Ībúk1×IŠ*Xë׎‚D"ÁÔéŗ°~ļm߁žŨģ*´EĖy%üëWŅ×V”ûpäË7yōäÁáũ{püČؕ+‹¸¸8äĪo � �āˇÂ…‘˜˜¯FM1sö\œ=w}zõD‡vmŗŧí7n~ŋmŠąˇ°ŧ‘ˇ�āáÃGˆOHĀͧ΄apîõę mōpwŊ­† „ß+T°�„†~ČrÛIҧΟņōÕ+…ŸØØ8ĄLÁ0iüX$%%aŪ_H$Ė5FF† õš×¯+ü^ÕY6d˙ųķ`Ä'$dڎ& ˙čÜŧuIIIH$(`e…wīß#1)6Öŋ!<<Ož>ËR|‹ŨÔƒ?FL_õäÉSĄ¯¨åæ �ĐĶՕ‹%UĘĨω¸ŌTßJY#æxˆ9öŠĨŽ›Œ9&áâí)Kĸ”ˇ+k…[Äž Um#û&"ĸė#æú9Efũĩ˜ë¤Ô~oŌXHÚׯW�đ<øEē×ŲbÎ+Yšļĸ܃#_RŲēmVŦË/‘œœ,,O}‹ŪŌ%žøkÜ<xđĢÖ`՚�˜câøąøŖEŗ,m÷ķ—/�d…°<o^ķoۗ"ä}ž| �čęęÂĐĐPĄœæffÂīyōČžELJĩ¯ôãæĖš.jÂ0/OLž:‘‘°ĩ-Š*N•Ķ-gjb*ündd$ü.œLŌ“úĩ¯_ŋÅR­z ʆ„„ LéR*ĮˇØØĩąą^c ū8e}UęžB___xŨÔÔTŽąåRĶT\iĸo%™”o  ..Váõ”š§tuŋßFŖėxˆ=ö)2ëĢ�ŲEsĐĩë�€€õąmį.�@Øˇ=rô8Üë×�ŅįBUÛČž‰ˆ(ûˆŊ~”÷×bū§K‘/_>áwŗTõĻw-æŧ#;ĪĒrmEš“/ߜ <‡q} §Ģ‹éS|P˛d œ>ˆe+VɕĢXÁGėEđ‹¸t 'NžÆŠĶg0zėxÔŦY PyÛyÍeŒqqqˆ>¨Ÿ>}ʘ››ÁÜ\Ö!$$$ ::Z¸čüüųK–ö™˛×œų  }}}<Œ5ëĶâøųķg…ß%‰ŅÖú>°ÍĖTVV[[Ëũ—(”-Sē4�Õã[lė’úˆéĢRū MHH@LLŒûøé“\]bËĨĻŠ¸ŌDßJ2:::033Cxx8>zŒúõžû‹ĮOž� .,,Wv<ÄûŠã&=)ˇĀ‹—/^O}ë‘ØsĄĒm$"ĸ_ŸØ˙éR„…‡}˙=LöģD"I÷Ëm1į•”s˜*×V”{đļŖonŨž �(Q˛ūlŨN•ņîŨ{�@R˛löė/_ÂĪ9ļīØÛĸEŅēåXĩ|)ŠŲErr2Ūŋ—•Oē–ōmĸ2•+ÁÄÄ€üÇž=ÖŌĄb˜˜˜ DņbĐúv{âÔi�@TT´đ”úu]ģŽ 7ÃŌÂ;ļn‚žžø.ÆķįÁ e>"ü~úÛļĨK•”û–Z™ŠíĄ­­¤¤$.XõëÖAm7WY_*…‰‰ąčøNMlė’úˆéĢJ”(.ŒvH‰™Č¨(˙û„\]bËeD]q••Ø#ÕÔrĢ �Xˇ˙ģs€ln—‰>SņõëWhii íÅ1Į^)ˇõëĶ ĪŨ~ÜŊccš[Äž ÕŨFUĪíDDôķ‰šNJíđˇųY�āÔé@�˛ëėôn•s^ųŅk+ĘŲrÕȗŒ&rš3k:J•,@6GÅô™ŗņöí;!CųâÅKĖ[ā‹.;bųĘՈÅÕk×PĀĘ Īžãyđ ąąAš˛e@˜pŨúxũú †œék <�SĻÍÄØ “pņŸKøüå Ξ;Œ9�`ii ÷úõđ÷‰“3n"Nž:ƒ{÷ī#~ĩžOôc2Š3�8zh$ Fũ5RŠcF„}y;ôéÕ‹ũü1ō¯ąØąu“đ ›—Ŗ}§Ž073ÃŅã�:uh¯R›,-,ĐŽMklÜŧ]zô‚‡ģ;î?x€oÜDeĮJ¨[§6ŒŒŒDÅwjbc—ÔGL_5bØ4p¯‡cĮOā¯qpæė9ÜŧuFFFøō%La`‘/Ÿ¨rQW\EDFĒ{¤šĄƒâÜų‹øôų3~oŅ ææfˆˆˆÍ9h@?-R�Dŗ<yō(=öb…†~Āĩë˙�ŧžÍ÷’BOWõëÕÅž…[Äž Åħ*T=ˇ‘xĘŽŸSßŪžą×I‰I‰ßĘ=FûN]affŠcĮe ’í͟oNĖyÅĐĐđ‡Ž­(gËU#_2šČ)&&Ū^žčÖĨĖĖLąmĮNččę`å2?tlßÚZZØđ,-,°mķ¸šÖÄŠĶXŊvnŨžæÍšbĶúĩȓGvá×ŗ{7”,Q_#"pņŸKr÷f¤K§Ž˜5c*Š/†ÃGŽâÚõëp­Y[7­G5—ĒBšéS}Pŋn$''ãʕĢhäå o/ŲėŲēJ&3¤Ÿ#Ŗ8{ųę’“Ĩ˜;ß/^žDUį*hŪŦ)� oīž°ąąÆŋ7nbíē rõ 2––8söōæ5Į°!ƒfjcâøą<°?ôôô°mĮN<Fûļm°få2hii‰Žī´ÄÆ.Ї˜ž �ĻøLDŊēĩ‘ˆsį.ĀÛŗ!Úĩų�/Ô'ļ\FÔWY=ĪļhQėßŗ͛5E1ÛĸˆŽŽĄĄ!jT¯†ūK0h@?ĄŦØãĄė؋•rˑ5ĘەSxŨÛK–Išõ.TWŦÛ‰ˆHe×Īb‰ŊNJ9ŸLš0ųōåCāŲķț×}{÷ރŌ#æŧōŖ×V”sI¤ßŌoÁÁÁ°ĩĩUk嚨3ˇ{đđBCCQ˛d .T�ĐŠkw\¸x ƒôÐA˛š…¤.nuÜņæí[,_ē Ä?Ҋˆ(§ã𐈈ˆ~Dvä?8TâŗĀw1Nž:Â… ÁŊ~]ŧyû.^BŪŧæčĐŽMv7ˆˆHãx.$""ĸ_MŽēí('X8oēu鉖ÛļīĝģwŅĐÃÛˇl„Ĩ%ī='"ĸœįB"""úÕđļ#"""""""Ę5˛#˙Á‘/DDDDDDDDÄä ‘1ųBDDDDDDD¤ALži“/DDDDDDDDÄä ‘1ųBDDDDDDD¤ALži“/DDDDDDDDÄä ‘1ųBDDDDDDD¤ALži“/DDDDDDDDÄä ‘1ųBDDDDDDD¤A:ŲŨ�U=õ.ģ›@DDDDDDD?I1›BŲŨ„Ƒ/DDDDDDDDôˍ|ą47Îî&‰Æ‘/DDDDDDDDÄä ‘1ųBDDDDDDD¤ALži“/DDDDDDDDÄä ‘1ųBDDDDDDD¤ALži“/DDDDDDDDÄä ‘1ųBDDDDDDD¤A:ŲŨ�"""""""R¯ā×oąuį^DEĮ 9Yš­mŅŌ’ĀČĐ�m[5‡­uálmKváČ""""""ĸ$øõ[Ŧ ،ˆČčlOŧ�@r˛QX°Á¯ßfws˛“/DDDDDDD9Ȗû˛ģ Š$@*ũoļí'`ō…ˆˆˆˆˆˆ(‰ŒŠĘî&¤O"AtLtvˇ"[0ųBDDDDDDD?Åá6¨ėk’/[ļmGŖß[ J5W4mŅ ‡•{=)) 6mAķ–māRŗ6šļh…€õ‘””$WN*•ĸŽģ'NTZīã'Oáāä’áĪĮŸŌ­Wl[”íSĘ~ųų/‡ƒ“ 6mŲĻđzrr2Öm؈†ŪMQĨš+Zĩisį/*}?;įęn8yęŒŌ˛š‰:Ž  Zœâ#ã,įPöž+;žĒôOi‰‰ uŦ#æŗBâ 6N.Xŋ1Ũ×?}ūŒĘUkĀÁɉ‰‰jÛn­z °rõZŅ‹•ē/8dXē1<uÆŦLëČJLĀŌe+đGëļ E=oü5n‚B™Č¨(xx5ÁáŖä–‹‰éûÎŨzĸWßéžöüy0œ\đ÷ÉS²Ŧƒ˙ŠÁÃFĸUÛ™–ųUö166~ūËŅø÷?āRŗ6šĩüë7*|&>|„îŊúĸjZ¨įፚķ"!!AåíŠROll,ŧ›6G¯ÆYÚˇ•Y[5u>[¸ČmÚwB\\œÆ÷/7K9?eô3|Ô_jßϘ>A]ũÆĖŲsŅĸUÛއr†\ņ´Ŗ]ģ÷būÂÅØŋ*ØÛãjĐ5Œ›čcc#ÔŠ] €ėbjÃĻ-čߡ7*ؗĮõo`Ņ’ĨВHĐšĶ÷“úŖGį*••Öû[áBXŊÂ_Ą=‡ÅÕ k053Mˇ^1mŗO>|ÄčąãņųËhkk§ûŪ,_šë6`ā€~¨`_Ûwîá#ą!`5ėËÛeøžz{6„ĩ5&úLEņâļ(^Ŧ˜ę&‡Q×1T‹3@üqdœå bŪseĮW•ū)511ĄŽuÄ~VH<}}}<t];wTxíØņŋĄ­­­|ũQ#†Aɒ%ÔZ' ߗmŪē ĩkšĄc{ų‹[Ģüų3\?+1 �—¯\ņM[°uĶ:(`… cGcČđQđjčÚĩ܄rKüü‹ņcF ËÄÄtNû­ÛwâîŊ{˜6yŌOŨŽĻâPŨfĪ[€ŗį.`ōÄņ(f[˙ģs>S§#..}zõ��ŧ{˙=úô‡kęXáŋoŪŧÅŦ9ķ ŖŖ‹ĄƒĶOēĨGÕz–­XPä˗WmûĢŽļję|6°\ģ~sæ/ĄąęO�Đw6ÖÖ—ĒŋLÍÂ"ßOn ‘æäøä‹T*Ś€õøŗUKté$ģātĒėˆg΃ąjM�ęÔŽ…„„lŨžÚĩ.JĢ8UÆãĮOpüÄIšäËå+WaWŽ,LLL”Ökhhį*Nrí Į™ŗg1vô(čéę*Ôk`` ´-bö �Ž=†|yķb‰īÔŠīĄđŪÄĮĮcŨ†MčŌš#:uh�p¨X=Áēõ1oÎĖLßÛ öå°zÂÂÂĐJ]Į$…*qĻĘqdœå ĘŪs1}š*ũS ą1ņŖëˆŲGRcĨЏtų*îŨ�ģreå^;|äĘە͛ˇÔēÍĻMŠĩž)}™ŠŠ)ĸŖŖaWŽŦB<g$Ģ1)•J1o/š5m,$‚ëÖŠ oΆ˜6s6*;V‚‰‰ n˙īļī܍iS&ÁŌŌBX_LLįô¸ŋ˙AļlWSq¨NÉÉÉ8zėotīÚnŽ5��ÖÖŋáŌå+8rô˜|Yˇ~#lŦÌi“!‘HāXÉ*XSĨžGŸ`ËļíhÚØūšôã;Ģ"emÕÔųLGGCDŪũĐöĪÖ(Yĸ¸æw6—204@õjUŗģD—ão;zųōŪž{‡ēuä/Ļj×rŝģ÷ mmmlÛŧ^áÛĀ‚ üëWše—Ž\E5—ĒĸęMĪō•ĢPĖÖž ¤[¯˜ļˆŨvÆ 0oÎLĻ˙Ūŧz¸¸8š–––ę×̃ËWƒŌ] O7ÁĐĀĢ1Ž-ÛļgX>7P×1IĄJœŠrg9ƒ˛÷\•>-ĩŒú§Yé÷˛ÚWŠũŦx(]ē:"ˇ<8øîŪģęÕ\ä–ĮĮĮcībxx5“KMx6úK–.ūá9{ūB†ÃÄ_ŧ| @õaÛŠ?÷ÎÕŨФYËt?÷)}�DFFÁĐP|œd5&Īž;ĮOžĸsGų[\ū5ÉÉÉXāģ ˜<u:Ü\k ąˇ—\911-ĻLĘ-Ięŧ=,)) sæ-D­z āRŗ6† …°°páõjŽu°~Ã&šu|ĻLG۝…ŋëē{bËļí˜ŋpx5FÍZõ0pČ0ᖏn=û`˙ÁC8xčœ\đāáCQõĒcĶÆĄ˛ļŠŲį#ĮŽÃŅš:<|(ŧ~ķÖm88šāÄŠĶ mPV^"‘@*•BWWū{Ņ<yōȞōÍŠ3đöō„$Õ˛ęÕĒ ąÄ֓œœŒ)ĶfĸuË(QBqô˛ãSģž6mŲ†~‡Āšš+Úwꊾ+”ë7p:vé�đ]ėGįę?´Īę:ŸUqĒ ûōå°~C†ÛĸŸGĖgWėyDYŋ—Vbb"–­X…Ļ-Z õnßšKŽLhčYŦWwCŊ^Xļb•úvžr„Ÿ| ūv[šĐ�� �IDAThmũ›Ürkk�˛ÎWKK E‹™™™đzbb"._šŠJ…eņņņ¸qķ\Ē:‹Ē7­ĐPėÚŗ}zõ”[žē^1mģí‚ düÆ�HL”Ũ+Ģ›æŧææˆˆˆČ°š4y*nŨūfΘ†]Û6Ŗ[×N˜;ß§ĪfēŊœL]ĮP=ÎÄGÆYÎĄė=Û§Ĩ–Q˙”ZVúŊŦŦˆûŦj’“’áá^ĮŽ˙-÷ŌĄ#GQĒd +f+W~úĖ9Øģ˙ † „Ŋģļa@˙>Øē}.Z�¨ZÅ öîüūŗg'ʕ-ƒŌĨKĄPÁ‚YjŖ˜Ī}ęž �ĸŖŖa`` zYÉŗį/ TÉ(\¸Ür333L7{öĀČŋÆ!$$ãĶšEALL‹)ck[nŽ5 ĨĨžK¸}û")9 ūKaʤņ ē†é3gĢT‡ŽŽÖoD‰Åqôā>ėŪš÷î?ŠUk��‹Ė…]š˛đlØ�§ŽŖTɒĸęÕÄū*kĢŪž áæZ3fͅT*ERRfΞ÷úhPŋžĘå% ūhŪ ;wīœ§Ī��wīŨĮ‰“§Đę�dŖ9>|øss3Œ7ĩę5@¯ÆXļb•Jˇ ĒRĪŽ]{Š~}z§[—˛ãŖĢĢ‹]{öĸTɒXŊrŧzājĐ5DDDe"""p5čŧĘF{/V nŽ5ŗŧĪę>ŸšÖŦŽ ˙Tš;'ũ)¤RÄÅÅĨû“ú}ķŲ{ũ¨jŋˇĀw Öm؄žŨēb×öÍčØĄ-æÎ÷Ş}„2ã'MÆĶ§ĪāˇhV¯ôGXXNpŪBJ%Įßvų-smld$ˇ<å[˛¨ ÁĩØĪoŪžÃüšß?„7nŪ‚T*E%‡ŠÂˇĒÔģqĶ”,Q\aX]ęzÅ´%Ģû”–ĩĩ5´´´p˙Á8Vr–?~ú€ė‚ÖÜÜLaŊ‘ÇB[K[8a-ZÛwėÂĨËWQ¯nQÛÎiÔuL�ÕãĖÎŽœ¨ãČ8ËŨŌëĶR˨J-+1ĄÎĪũ8oΆđķ_Ž‹—.Ŗļ›+¤R)Ž;ŽÍ~—+Žƒ‡`ؐAÂ7ĮEllü›ļlÃāA```€ĸEŠë,[ą ¯^ŊƖMë §§—Ĩö‰ųܧíËĸĸŖq÷î=´īÜ Īž=‡Ež|đhPŊztƒžžžÂ6˛“7nŪ‚ŗSúˇ6ÕŽå†õëáÄŠĶ9|( XYŠžķ"5ööRUķŖ,--0fÔ�€}y;<|ôë7nFLLŒJ‰­âŊĄYĶ&�d‰$×5pīū}�€‰‰ ´´ĩĄĢĢ‹ŧææĸëÔÄū*kĢXãĮŒF‹Ömą˙ā!ÄÄÄâ}Č{øûųfšüđĄƒđåËüŅē-ttt˜˜ˆNÚ ķĨÜ~ģÄoZĩlöíÚāÖíÛđ]ŧ‰‰‰Øŋ¯¨v‹­įǏđ[ē “'MČp4–˛ã#‘H` ¯/Ė#S¸P!Ė[¸į.\D#/O�@āŲķHJJBCw�˛ÛÄRnËĘ>Ģû|æXÉËVŦÂķā`Î=§!?AÕéßōšyC€Üü€Ę>ģb¯Ué÷""#ąc×ntīÚM{Ëę-R÷ī?ĀڀõhŅŦ)BBCqåjƌ)|9đר¸tųĘž;ŲËŊŽ+šz5FžIĨR8z'/dsË~M9>ų’ž‹ũ°uÛ,˜7E‹ØË/_šŠĘŽ•˛tQƒŨ{÷ ōÔ2Ģ7Ŗļ¨ƒą‘ŧz`õÚõ([Ļ ėʕÅÉĶgx� Ŗ“~xaMĀz\ģ~Ÿŋ|4YŠđ¯_Q$ÕE8eĒq&ö82Îr/eĮ7ŗūéW‘˜˜ˆččháo]]]•ūiĖ-~û­0*VĀĄCGPÛÍ˙Ū¸‰7oŪÂĶŖî=ø>ĮÃG””„ŠėåÖˇ+W111xųō•Üü—._ÅĘÕk1oöLš„ŒĒÄ|îS÷eÉÉÉĐÕÕÅģtîØVųķãÆÍ[Xžr5ŪŊ{™Ķ§dš-i}úôųķ[ĻûZDDn˙īôõõqęô´oû§Ü-˙uŠã€l^ŽÄÄDŧzũĨK‰Ą�ĨŌLjkjj‚đđŒouĖNęhĢ•U~ 2ž‹ü”œŒąŖGÂ"_Ɠƒ*+ŋdé2\ ē†Yͧĸx1[<xô |#oŪŧčÖĨžXss­‰Ũē�ũĶøéĶgŲ­=}z‰š¨Yl=ŗį·ŖŖę×ĢŖŌû’–CÅ Âīųķ[О#Μ9+$_Nœ: —Ēΰ°°PXWÕ}ÖÄų,ås˙áÃG&_4¤ˆ ĻOõI÷ĩâÅlåūVöŲ{ũ¨Jŋ÷čŅc$$$(ܞëäT{ö@TT4ž= �šD‘D"}ųōxđđQFģūŸ3{ōfrũ$‘Hđģˇ~÷ū>/Ytt Fûd>‡#Éäøä‹Š‰l†ķˆČH˜˜˜ËS†;Ļ^–œœŒŠĶgâø‰“đ[ŧPČZϏr5 ęĢ\/�üsé bccQËÍUĄŠëĶUˇ™Q#†aĖø‰čÚC6œ´b{ôėŅsæ-„Y:ŗÃ'$$ ī€AHLL¨ÃPŦXQčhë`đ°_÷Ÿ6uPį1ÉJœ‰9ŽŒŗÜGYŸ–"ŗū)ĩŦĄ:ã(3—¯\E˙AC…ŋ›4öūéOTųUxy6ÄßňˆˆĀ‘cĮQÁŪÖÖŋÉ%_RžõMûmwʡŠ]!ĄĄ3n:´kķC˙¤‰ũܧîË´´´p!đ¤Üë•*B*•bŅ’Ĩ=r¸ÂČēŦÆdTT”¡ä)æĖ_]ŦYš ]ē÷ÂöģŅĻuKö^}´ĩĩ‘œœœîk‰ßnĶĐM“ô666–û;eÄPllŦJÛNo¤Ņ•ēÚęíéy B[[GTügTūíģwXˇaĻOõ—§ėŸš2eJ#:* |ãĪV-aôíķWŽlš:+Uš€õxķö-ŠØ(˙EL=/^ŧÂ?—/c×ļ-JëS&m|5ôpĮß%ˆ‹‹CBb".]ž’áĶ„TŨgMœĪLŒMžŊ–ū|Pôãô ô’ũ–ÍäŗĢĘõŖ*ũ^äˇsbŪũ:­žü햨Ÿ> įÅ´íSeN˛_Ö¯ķ]CļËņɗĸEeō˗¯P¸Đ÷û´ƒ_ŧ„––l‹~Ī‚Îš3§Îbå˛Ĩ ŋ ĮŊû0aÜ•ë€ĀsįPą‚ŊÂE`ÚzÅ´EÕmgÆÜÜ Ëü!$4�PĀĘ K—­@ŅĸEd“ŧĨņŋ;wņčņŦ]ĩN•…å_ÂÂđÛoŋ)”Ī-ÔuL˛gʎ#ã,wĘėøĻ–Q˙”VVbBq”™Šė°z…đwzß ’LCwĖŋ§ÎâÄÉSÂSTRKš(ŠŠ–[žōˇąą, ‘€‘ŖĮÂÖļ(ė˙CíķšĪ¨/K+å[ː…¸ÎjL āŠŋđ<ŒåK—ĀžŧēvîˆÅ~KQË­Ļ\ũ?KŪŧæxüøIē¯Ŋyķ�`i)?‚'&&6Íß1� ŒKoO\\ܡUSõǃØļų/_‰üųķ “q*ûdTūÕĢאJĨ(Q\~d…5â‚ĸEŠ@OO_Ō<ũ/)I6:$íÜj)PĀJi=Ÿ<‰¨¨h4úŊ…đēT*…T*…ŖsuŒ>íÚü)j{iš×¯‡™ŗįáŌå+BėÕ­[;ËmMMįŗˆČ”„Œü?ëôßŖĘõŖ˛~/ĩ”ÄûŒŠ>éÎWU¨`AŧyķĀ÷[ÚR¤ÄΝbô$Ž`ҤŸ|)bcƒ"668xVx2�œ D§ĘÂėāĄ#Øwā ÖŽZžî?)Wƒ‚`ffОeJĢToŠ  ëhčĄ8ãzÚzÅ´EÕmgæčąŋacc-l'11‡ƒ‡{ũtËĮÅÅ€ÜIíÖí˙á͛ˇ(o—ņ?w9ēŽIVãLŲqdœå>ʎojõOie%&ÔG™155EeĮJjŠ+§Ë—7/ĒšTÅÚuđõk<ÜŨʔ.U ÚÚÚ¸qķ–ܡ‘ˇū÷?˜ ß4/\ä‡×¯ß`Ûæ ŪB(–˜Ī}Úž,8øųųŖßŪrˇAŨūßhiiÁÆÆZa;YI ‹|rOÔ�€¯_ŋbō´éhÚ¤‘0ŋDn]pėø L›1ūK2ž˙CSjV¯Ž“§ÎāŌå+rC䓓“ązíz°˛RxÔø›7åūž{ī>ôtuaķmÎ##C…§@=zü8Kˇa§ž<SõĒ›˜ļŨš{›ļlà Ų(ŽACGĀŊ~=”ˇ+—n™•/øm’ęįĪ_ l™īŖ<‚_Č&†ĩ˛˛‚ļļ6ĒģTřºÂ-8�tí:LMMEOT.Ļžũú S‡örë>rƊe~Čo™ū-xbä˛.UĢā܅‹ˆŒŒB-ˇš01N?ąĄę>kâ|öáÃG�{t<ũ7Šrũ¨ŦßK­téRĐĶÕŧĪ_āQĖVXūųËhI´ §§ÛĸE�>æ$KHH@Đĩan–y2đŋfጉĸĪé ‰‰6V}ˇøæt9>ų�Ŋztä)Ķ`ee‡ŠpîüœŋđV-_ @6ŧlÉŌep­Y111ēv]n}‡Š¸|5Uåž QVoŠččhŧ{˙ŋũĻø XÚzÅ´EOWWÔļīŨ O–JņęÕ+ĄžŠė‘'Oœ<‹;wīâ¯Q#`nf† ›6#&:F˜Ü-­2eJAOO[ļí@Ÿž=đøÉ,öķGõj.~ņŸ>Îô~įœLĮ$ĢqĻė82ÎreīšT*u|Ėû'�ØļcŽ;ŽõkeKęXGL\QÖy{5ĸ >pŠęœî?ææfhö{Ŧ X‡"6Ö(SĻ4Ž_ŋí;vĄK§ĐŅŅÁŠĶØŧu&Žƒ˜ØáņŌ€ėŸ+Uo)ķšOۗ.\?Á°‘Ŗ1 _XåĪë˙Ū@ĀúčĐŽ0Ü;+1™–c%…‹õYsæ#9)Ç|tnž<y0~ėhôę;�&gĶbĘ>z Ÿ8……ķf§û„™&Ŋą{ß~ õ:ļo‡reËāķ—0ėŪŗ÷î?ĀÂyŗæyķö-VŽ^ /Oŧ~ũ;ví†{ũzÂđyģ˛eqúĖYth׆F†Ø°q3ÂÂÃa•?ŋ˜C+035ÅǏđāáC,PPTŊĘöõc.ūŖ°ŧTŠ’YžüXYÛR+îåéį*˛‰˜ë×­Ÿ)Ͱeã:…ĘĘ-bƒÕĢaąŸ?Œak[?Ášĩëи‘—đÍ{Īî]ŅĨGoLš< ŋ7mŒ;wīaûÎŨčߡˇJķ )̧€••Â{gii mmš97ÄŸôx4h€UĢ×""2>ÆÉŊvčđQœ ĂoċŨgMœĪ�Ų#ÁÍĖL9ߋeôdˇ—Ö¨^MT=Ē\?*ë÷R316F‹ͰlÅJä57‡}y;ŧ{˙sæ/DĢüđ[´… BÅ öX°ElŦ‘/_^lŪē]¸Ūú•ÄÄÆÁÄX\š 6FĩÛSsģ\‘|iŌØQŅŅØ°i3ü—¯DĖ=C8ųŋxĐP„œÅŠĶ ëŸ:~—¯\E÷.UĒ7EøWŲ$Piī- P¯˜ļXZZˆÚöôYspįÎ]áīm;vaÛŲķč؋ß~+Œ cGcÆėš?q2âããQŲąÖŽZžáũ|yķbʤ XŧÔAyģr˜â3ĄĄĄ=f<zöî=;ˇĻģnN§Žc’Õ8Svg9‹˛÷<"2BÔņ2īŸ�āũû÷¸ũŋ;ÂßbbB눉+ĘēēĩkC__ž™|Cüר024ÄôYsđųķ,X�=ģwE÷ޞžäü…‹�€)͇(˙5r8Úļi­R›Ä|îcãbåú2===Ŧđ_‚%ūË0{î|„……ŖPÁ‚2¨?Ú´n%”ËJLĻUÛÍ{öîĮÛwīP¸P!ž=‡ÃGaÎĖi ˇ8¸TuF“ÆŪ˜3!jTw………¨˜Sæųķ`ž=—a;uuuąj™V¯]‡“§NcŨ†MĐÕՅCÅ XŊBá‰wI‰IčŅ­+Ūž}‹vģ">>n5k`Ėčīs$ 6“&Oƒgãßajb‚æÍšĸiãFøį’jOņhûg+Œ›čƒ.Ũ{cūœ™ĸęUļŋ�đúõš9ŸRLņ™€ß›4VŠ)”ĩmíē xŠËü„uFŠæ-˙ÄęĩëСˇücŽÅ”Ÿ3s–._‰I“§",<ųāåŲú}Ės… öXâ;‹ũüŅĢī�ä˛ƒôC§íTÚ?uÕ#æø¤§~Ũژ>s6 ôõQË­ĻÜkOž>řĀīuŠmĢ&Îg�páâ?pĢYķ—šDûW“Ņg%_n]UØëG1ũ^Z#‡ Š‰ |ûáÃĮ°´°@Únę‰[ŗĻOÅäi30xčŖUËhÜČ 'O˙Z›Ž‰‰…‰qúsœĨ­âÜ`šDúmügpp0lmmÕZš&ęL™‹ˆˆˆčg‘JĨhųg;TŠâ”íOkÚŧ%ėŨ•­mø™rÛūūjrōņšūī tëŲģļoQxĘŅŨØŠsŗ´ŪˆŊP4ÛvĶüō5æû­ĖŌvfLŠRyu=¤!Evä?ď$"""ĘĨ$ F ‚}û"8øEļĩãü…rÕÜWšm59ųø$&&bá"?´h֔‰ĘUĸU¸•(íÄŔš\qÛŅĒ^͝:´ÃˆŅc°iũÚly´˛›k ¸šÖøéÛÍ.šmÄŋ7nbАáJËÚŋGéĶ€ÄĘÉĮĮĪ9ââņW6t#úŲTI¨ÄÄÆh°%9“/DDDD"õīÛũûöV^č'+oWÛˇlTZÎÔTŊC÷sĒ!ƒ`Č ŲŨ ĸŸ.F…y\T%CLžũōōäÉÃÉȉ臩4ō…É•pÎ"""""""Riä‹*e‰É"""""""‚Ēsž0ųĸ &_ˆˆˆˆˆˆˆ‘QQĸËFEEk°%9“/DDDDDDD„ûžāÅĢ7JËŊxõ÷=ų -Ę98á.!66ķ–ŦČîfäHųBDDDDDDD¤ALžiPŽIž$%%ÁĪ9œ\°iËļt_ß°i šˇl—šĩŅ´E+Ŧ߈¤¤¤LëTļNll,.ōƒgŖßQĨš+ŧ7ڀõHLL˰ہC†ÁÁÉEágęŒY閏…wĶæhāÕX…w„4aËļíhô{ TŠæŠĻ-ZáĐáŖr¯ĮÆÆÂĪ9˙ū\jÖFŗ–"`ũÆLãqFéųŅ>íņ“§é˙”Ÿ?e¸meqŽŽu”í#ŠF*•âБŖčŅģÜę6€su7x7mŽq}đøÉSĩo¯VŊXšzí×#•JQ×Ũ§NŠ>W‹‰71ņ%ĻžŸš-U?ƒ‡ĖôsūâåK�ę;VoßžCûN]á\ÍU­ŸŲÔ1��>B÷^}QĩF-ÔķđÆÜų ‘�āį÷mDDDbåŠ9_>|øˆŅcĮãķ—/ĐÖÖNˇĖŌe+°aĶôīÛėËãúŋ7°hÉRhI$čÜŠC–×™4eŽ^Ŋ†AûĄˆ ūŊqK–.Cbb"z÷ėžnŊQQҍ]Ë Ûˇ•[n•?ē嗭XPä˗Wė[B°k÷^Ė_¸û÷A{{\ ē†q}`ll„:ĩk�fĪ[€ŗį.`ōÄņ(f[˙ģs>S§#..}zõHˇ^ÆĨĨŽ>íˇÂ…°z…ŋÂz‡ÅÕ k053Mˇ^1qŽŽuÄė#‰'•J1füD=ö7ŧ=âæÍ`hh€ā/ąk÷^tėŌK/„SeĮėnĒ‚G#,<ÎU*‹ęÅě˜øSĪĪÜVV?6ÖÖ7ft睰˛�Œ:%K–]gFö8ˆg΃ąÜ l‹ųáúR¤ŽwīßŖGŸūp­Q+ü—ā͛ˇ˜5gttt1tđ€ŸÚˇŠ"W$_Ž=†|yķb‰īÔŠīĄđzBBļn߉íÚ kįŽ�€*N•ņøņ?q2Ũ䋘už~ũŠ‹˙\ÆčÃФą7�ĀŠ˛#>|„Sg3ü§8::våĘšŠ“Ō}{ôø ļlÛŽĻŊqáŸKĸßR/ŠTŠ5ëņgĢ–čŌIN•ņėy0V­ @ÚĩœœŒŖĮūF÷ŽáæZ�`mũ.]ž‚#GĨ›|aœQzÔҧ*û°°pœ9{cG‚žŽŽBŊbâ\ëˆŲGRÍî=ûpôØß˜6y’ĐO�@m�´h†ŽŨ{aåęĩXáŋ$û™ËWŽÂŽ\Y(kąņĻ,žÄÔķ3ˇ%ĻžŒ zĩĒ™–iÚ¤‘čú2ķõëW*XPíIŧ”055ÅŌe+`cũfL› ‰DĮJ°°°FzūŦžˆˆHUšâļŖ† `ۜ™022L÷ummmlÛŧ^¸˜KQ°`„ũ*üíģØŽÎÕE¯cjjŠ 'å.t@[G:ŠžĩJ]/�DFFÁĐ0ũļĻ–œœŒ)ĶfĸuË(QâĮŋąĸŦ{ųōŪž{‡ēuä/Đj×rŝģ÷ ‰DŠT ]]ųœgž<y�‰Dø›qFʨĢOKkųĘU(fk Ά Ō}]LœĢc1ûHĒŲē}+9(ô�`ld„ukVÉ%^ąlÅ*4mŅ ÎÕŨФYKlßšKnŊø„,öķG¯Æ¨æZģõÄÍ[ˇ3lÃĩë˙ĸJ5WėÚŊWļ~|<ø.†‡W8šÔ„gŖß…{Š]ērÕ\NJŠkąņĻ,žÄÔķ3ˇ%Ļž‘öļŖÚõ=°iË6ô8ÎÕ\Ą4&:w뉭ÛwâéŗgpprÁš€õJņ‘cĮáč\>ęšyë6œ\pâÔiaYJ �ĀŠ3đöō„$ÕyŗzĩĒ—éŅDßFDD¤Ē\‘|)X @ϝkiiĄh‘"033–%&&âō•̍äPQXVŧX1¸šÖTiąąąøøņvîۃ3įĐą}ģtëd# ”î׎]{Š~}z+-Kšüížykëßä–ÛX[]ØI$üŅŧvîŪ‹'OŸ�îŪģ'OĄÕ-„ug¤Œēú´ÔBBCąkĪ>ôéÕ3ÃzÅÄš:Ö”ī#‰÷õëW<yú UĢdX&í?ô |—`Ũ†MčŲ­+vmߌŽÚbî|_ėŲwā{™…‹°gī~Œ:kV.ƒ5úŒ×¯ß(Ô˙âå+ 1]:u@Ë?š�ĻΜƒŊûbؐAØģkôÛw`áĸīI øøxܸy .UEÅĩØxS_bęų™ÛS:éęębמŊ(U˛$V¯\Ĩ1áˇhš˙ŪļļExę8ÚˇũSé1ööl7ך˜1k.¤R)’’’0sö<x¸×Gƒúõ�ČĮ@XX8>|øss3Œ7ĩę5@¯ÆXļbU†sôiĒo#""RUޏí(+ûųãÍÛw˜?wļ°Ŧi“F™ÍMoũÁõoĀÔÔ>ĮÁËķûá´õFEGãîŨ{hßšž={‹|ųāŅ >zõč}}}�˛{ŋũ–.ÃäIøÍđ@äˇoŌŒä–§Œ,‰ŠŠ� :_ž|Á­ÛBGG‰‰‰čÔĄÜŧ+Œ3Ō„Ėâ�6nڂ’%Šgz{‚Ø8˙ŅuHŊ>}ú �(\¸ÜōÄÄDÄÅÅË-Ķ×΃č˜ėØĩŨģvFĘ-R÷ī?ĀڀõhŅŦ)"Ŗĸ°{ī~ < =dŖ &Ž‹˜˜ŧzũZîŸØ°°p 4ĩÜjb@ŋ>²ƒ‡`ؐAÂh„"66~M[ļađ ĐĶÕō›ˇ •J3LĻkuś˜z~æļ~„49QQŅ Ë%d8úQ"‘Ā@_C��DDF* äÉŖm-mä57}ŒĮ­Ûb˙ÁCˆ‰‰Åû÷đ÷ķڒ:Ūž}�X⡠­Zļ@ûvmpëömø.^ŠÄÄD ėßWa_4ÕˇQÖĨŊ˜›0ų’ßÅ~ØēmĖ›ƒĸElÔ˛Î_ŖFāãĮ¸t }Ļ"""ļjŠP.99ēēēx‚ÎÛÃ*~ܸy ËWŽÆģwī1sú�Āėšķáčč€úõęüČŽŌOļdé2\ ē†Yͧĸx1[<xô |#oŪŧčÖĨ“Ōõg”Ęâ&&&ģ÷îĮ˜Q#˛Ąu¤iZZ˛Aމ ōˇķėŲˇĶgΑ[ļz…?´´´€ęÕ\ä^srnj=û **OŸ>C||<ėíí„×õtu1ŽüĶŌ0läh(`Ÿ ã„å=BRR*V°—+oWŽ,bbbđōå+”,Q—¯\EeĮJĐĶĶSدŦœĢsŖĮOžĸF­ē Ë pųB`†ë9TŦ üūčŅcĨ1‘6A/ö[YåĮ°!ƒāģČIÉÉ;z$,ōåʧށ„oˇ+ššÖDn]��öåíđéĶgŲmR}zÉMFĖžˆr3c##Yrųŋ–čJa”&Ų[0ų’Jrr2ĻNŸ‰ã'NÂoņB¸TuVÛ:ĨK•DéR%QŖz5būÂÅhŌČ[á['---\<)ˇŦ’CEHĨR,Z˛ŖGĮ˙îÜÅ?—/c×ļ-YßYR+SŲĶ‘x@¸�� �IDAT""#abb",ˆˆ��˜˜˜āíģwXˇaĻOõF¤”)SŅQŅXāģļj™áčÆe…ظųįŌÄÆÆĸ–›kĻõ‰‰suŦCęeii ‰D‚7oßĘ-¯W§J~›Įéã§O9z,� ōÛˇü=z÷CęËĩdŠô[ŲøúUvüôõ3ŋuqËÖ툊ŽF‰â՜œ,,OIļĪKéĢĸŖe#5Ž\ ‚Gƒúre2‹kuś˜zRF ũŒmũkkLņ™ °\Ų“Œ…ßÅĄ‘‘üĶÄc�đöôĀŧ Ą­­ŖėOFßÖ-WļŒ\ĮJ•°&`=Ūŧ}‹"6ßqšėۈˆūëÚĩn†•›ŗģŠ$´kŨ,ģ[‘-˜|Ie֜ų8u&+—-…}y;å+(Y'$4AA×QˇNmš‹2eJ#..!!Ą(VĖVÔvJ—*)Ģ3$Ÿ<‰¨¨h4úũû<!RŠRŠŽÎÕ1rø´kķ§¨zI=Š•]ėŊ|ų … }Úüâ%´´´`[´n˙ī¤R)J/&ˇŽ5â‚âÅä_KÁ8ŖŦÛ§ž;‡Šėann–a@\œĢcR/##C”/o‡ŋOžB˙žŊĄŖ#;õ[ZZĀŌŌ�đæÍ÷ÄLĘ­3Ļú Tɒ õ*XPHž(ģŖXąb?vē÷ė ßÅK1zä0Ų6žũcŸöv˜”ŋŽ{÷`¸1re2‹kuś˜zRú۟ą­a`h€ĘŽ•~¨11Ą°ŽˆcœÂųJäΟ_˜ÔwđĀū�  XAOO_ÂÂäęLJ’ˆŅMķ$#MömDD˙uļօŅĢk{lŲąQŅҐ~K˜g‰D#CC´kŨ ļօŗĩ-Ų…É—o:‚}bíĒåĸ/ĘÖųøņÆMôÁôŠ>hėí%,ŋ˙$ RŧX ~E~ūčߡ7J–(.,ŋũŋ;ĐŌŌ‚5ôëƒNÚË­wøČQ8x+–ų!ŋĨĨØŨ&5)bcƒ"668xVx"�œ D§Ę000@Áo§ĪŸŋ@Ų2ßŋĩ ~!›čĪĘĘ*Ũēg”ĒôiAAׅy;2#&ÎÕąŠ_§öm1jĖxŦ]ˇŊztSxũîŊ{ÂīĨK—‚žŽ.>}ūbļÂōĪ_ž@Kĸ===ØÚ…žž>Ž]˙W˜%99=z÷Cķߛ ķ‚Ôr̉˛eĘā¯Q#0~Ōd¸šÖ@ęÕPēT)hkkãÆÍ[rˇĨÜúß˙`blŒ"668}&ffĻ([Ļ´đ瞏VWŧ‰Šįgn+쉉 …uDc�¸s÷6mŲ†ūK‡ACGĀŊ~=”ˇ+‡ĢAAr1 ­­ę.Uq&đŦpÛ�]ģSSS…I‰5ŲˇũŸŊûŽj"kÃ�ū¤ĐÁ‚"Ø� HQT;öîĒëęēkīŽk]{īąwÅŽkßĩ—ĪŪwíŊ+b¯Ô„Ė÷G$Ė؅įwG3sįÎĖ››ä͝;D˙îōaX˙žŨ ú,K$_Ž]ŋĄųuN%xüø1Ξ;�đķõ ˜;!*U BllŦf]?XZX`įŸģpđđa„LŸŠ¸¸8ƒÛ”đöBPųr˜:=111(ėák×oā÷hú]cͤĻÉë͗//nßžƒūŖWĪîČãä„ķūÆī+"đsëV°ĩĩ…­­-œS|QĪ;7drŠxōVĀĨkįŽ=nōäÉ?_9z GĀŌEķ�nŽQ!¨<æĖ[�{{{¸ģģâöí;Xކ ęi~Ydœ‘!ĻęĶ�õĐ˙gQQȟ_{2Ö$ë6lÂŽŨ{°"l)�ÃqnĒm Ŗ••Õˇ=‰YLÚĩp់˜ŋp1._š‚:ĩj!{öėxųō%ũī8rô8j×ĒŠyn›5û /AÎ9āSÂĪĸĸ0mæ,8įqÂŧŲŗā`oĻMaųī+āâėŒB…Üąiķ\ŊvcF ×ŲŖ†õņŋ#G1jėxlZˇ9rdĮwMaųīáp-X�ŊÅųķcũ†MhßögČårœ:seËj&åĶZZXˆŠ71ņ%ĻžôÜWFž&ėí ÆDJbÎąBĄĀØņQ¯nm”-S�P#¸ƌ›€5á:1��]:u@ûÎŨ0zė4iÜWŽ^Ãúā—Ũ´Ę™Ŗo#""úY"ų2qĘ4\šrUķxŨ†MXˇa�ā¯í[đņĶG<ņĪžĀƒ‡uļ?°į/äΝ wîŪÅĄÃG��>ĩÍĖiS°`Ņb,Z˛>|@^´ũš5:uh§)›ŧ^KKK,^0s,ÄÔé3ņîŨ{äuqAßŪŋ Õ-LøŦŠ5jXŅ11Xšj5,Zׂ1}ę$ÍJ�˜6yæ/Z‚ŅcĮãŨû÷ȕËõęÖA¯ž_nãĖ8#CLÕ§Āû�hĪī\TT.]žĸy,&ÎMąĄc˟?kWũCũ† rXˇaĻÎP'ksæĖ?_,˜ŠŠ‚4eöī‹l3/_ŊBî\šP­jeôJv7™~}~$„„ÎAtL ŠxzbūœYZsn$7bØ4oų#ÆMœ„éS1dĐo°ŗĩÅÄ)ĶđæÍ[¸¸8ŖK§š~ëÔé3čÔūK&ļ?obâKL=隯Œ~Mˆ‰‰” ã°đ•ˆzū‹Îû˛ŸũĐôû–XŽ�āë냚Ą31gŪtíŅ Ž9sĸw¯žhûsk­ræčۈˆˆž…Dø|ņ׃āîînŌĘÍQgŌägDDDDDDD”ų™zōķŒČHMē7""""""""ŌÂä ‘1ųBDDDDDDDdFLž™“/DDDDDDDDfÄä ‘1ųBDDDDDDDdFLž™“/DDDDDDDDfÄä ‘1ųBDDDDDDDdFLž™“/DDDDDDDDfÄä ‘1ųBDDDDDDDdFLž™“/DDDDDDDDfÄä ‘É3ēÆzõîSF7ˆˆˆˆˆˆˆŌ‰ƒƒCF7á›ũį’/… æÍč&‰ÆËŽˆˆˆˆˆˆˆˆĖˆÉ""""""""3bō…ˆˆˆˆˆˆˆČŒ˜|!"""""""2#&_ˆˆˆˆˆˆˆˆĖˆÉ""""""""3bō…ˆˆˆˆˆˆˆČŒ˜|!"""""""2#&_ˆˆˆˆˆˆˆˆĖˆÉ""""""""3bō…ˆˆˆˆˆˆˆČŒäŨ�"""""""2­į/_áđŅ“ˆ‹‡ Ú‰Dk++TĢg§ÜږŒÂ‘/DDDDDDD™Čķ—¯°kß!ÄÆÅexâ�A@ll,ví;„į/_ets2“/DDDDDDD™ČĄŖ'3ē ē$@ūmKLže"qqqŨũ$ÄĮĮgt+2“/DDDDDDD”.ū —Ae„,“| _Ē5jŖX Ô¨S[ļn×ZŸ˜˜ˆeaá¨]¯ŧũPŖN},^牉‰ZåA@Ųō•°gī~ƒõŪŧuEŊSũ{™ėZˇäõŠm‹ĄcJ:Ž™ŗfÃŖ¨7ÂÂWęŦWŠTX˛l9*VŠŽb%üQŋŅw8xčŸĪí;˙DqŸ’ØŊgŸÁ˛Y‰)Î `\œâĪ#ã,ķ0ôœ:ŋÆôO)‰‰ Sl#æĩBâtíŅ EŊąxérŊë_Ŋ~"^žđ(ę ĨRi˛ũaŪ‚Eĸ‹•ŧ/ëÜ­‡Ū>jĖWëHKL@HčÔmĐQQĪXĄ úô˙M§Ė§čhT¨Œn=ÕZ.&Ļŋ5î[´ú ?ˇë¨wŨŨģ÷āQÔíŪŖY–ÖsđoŅĩG/ÔoÜôĢeū+Į‡™ŗf#¸f]xû VŊ†XŧtšÎkōúõøņįvđō-…Ā U0aŌ( Ŗ÷gL=qqq¨RŊ‚*WKËĄ}ŗ¯ĩÕ\īgSĻÍDŖīšgŲ_čĶKŌûSԟ—o)ÔĒ×&MAäŗg:åÍņzS§Šö;zėxÔŠßø›ëĄ˙Ļ,qˇŖĩë6`Ԕ鸭”ô÷Į‰“§0`Đ88ØŖfę�ÔĻ–……ŖßŪ(éī‡3gĪaڌH%téüåCĖ7ņöŨ;•4XoÁųą&"\§=[ļnĮ‰“§=GvŊõŠi‹˜czņâ%z÷€×oŪ@&“é}nfĪÅK–áˇũPŌßĢ× [Ī^øcÃZøųú¤úœ6nØ�nŽŽ8x<==āY¸°Ņį%ŗ1Õ9Œ‹3@üydœebžsCįטū)911aŠmÄžVH<kkklŪ˛ ŨētŌYˇc័Éd:É×o5|Č`+VÔ¤uÚ}Ųī+VĸFõ`tęĐŇsž<ŠnŸ–˜€ã'NbYX8ļoŲgL7ŨzūŠÆ  Fõ`Mšé3B‡ cGk–‰‰éĖ÷+W­ÁĨ˗1cęätŨ¯šâĐÔÆN˜„cę¤ đ,ė.^Âāa#ŪŊz�"Ÿ=ÏmÚ#¸jD„/ĮãĮO0fÜČå2h€č}[OčœųˆŠzŽ\šMvŧĻjĢšŪĪ~ëß§ĪœÁ¸‰“1qÜķdįZ° ĻL�ˆ‰‰Åĩë׹~ã&lÜ´K/@`Ų2š˛˙•×3‘>™>ų",Z‚6?ĩF×ÎęœeËāÎŨ{˜ŋp1jÖ¨…BĢŅą}[͇ŌreqãÆMėük—VōåØņđõ)ƒõÚÚÚĸ|š@­öŧ{÷ûҪ`iaĄS¯Áļˆ9&�Øļ}r9:bų’E(¤ķÜ$$$`ɲ0tíŌ ;ļ�”*‰7naņ’e˜?7ôĢĪ­ŋŸ/Ö¯‰ĀÛˇoEŸĖĘTį$‰1qfĖydœe†žs1}š1ũSą1ņ­Ûˆ9F2^™ŌĨpėøI\šz >%ŧĩÖmŨļ~ž>8wū‚I÷ŲŧŲw&­/IR_–-[6DGGÃ×§„N<§&­1)&Lš‚͛jÁĩjÖ@“F 1bôX–-üũĪEŦZŗ3ĻM†S˛[iЉéĖ÷WŽ\͐ũš+MIĨRaûŽ?Ņŗ{WWĢ�(X°�Ž;ŽmÛwh’/‹—,ƒ›kA„Ė˜ ‰D‚2ĨāäädôČcęšqķÂWF yĶ&8|äčˇŦ‘ ĩÕ\īgršCũ†ÖmÚŖ]›ŸQ´ˆ§ų6‹˛ĩĶ>‡ÕƒĢĸCģļčĐš+zöęƒÃ÷ÂŪÎĀãõL”šLŲ҃ņ42ĩjj˜ĒQŊ.^ēŒŸ>A&“aįļ?ĐŊkg­2ųōåÅģ÷īĩ–=~+‰ĒWŸĐ9sQØÃ ÔĶ[¯˜ļˆŨwÆõ1n(ėėlõ?7!>>AåËi–IĨRÔŠ]ĮN¤>õĢׯ1`┝TA•ĢĄsˇž_‘jųŦĀTį$‰1qfĖydœe†žscú´äR럒¤ĨßKk_)öĩBâ999ĄxņbØŧe›Öō{÷îãō•̍\ŠĸÖō„„Lž:*Ŗ¨ˇ*U­!ĄšK :œę0˙0~˜vō×}qŸ’¨^ĢžŪ×}R_�Ÿ>EÃÖV|œ¤5&<„›ˇnkž¨%3j8T*&M™…BĄÃG"¸Z4mĸ=Ŧ\LL‹)“tI’)/S*•?q ƒāí€n=ÅÛˇī4ëKø—ÆŌeaZÛ 6›~¯y\ļ|%„¯ŒĀ¤)ĶTšüJ•Eįn=4—|´úŠ-6mŪ‚Í[ļÁŖ¨7Ž]ŋ.Ē^SoĘ84ÔV1Įŧ}įŸđ,îƒkׯk֟ŋđ7<ŠzcמŊ:m0T^"‘@XXh˙.jeeĨž;Čg{öíG“ƍ IļŦRÅ MÂF,ąõ¨T* 1?ˇn…"EŠčÔcčü”.Waá+ŅĄs7/áĻߡDģŽ]tĘučÜ Íø�0uúLx˙2*6-ĮlĒ÷ŗreáīį‡ÅK—Ĩē/2;;[Lš0oŪžÅæÍ[5ËSžžĪœ=‡–­ÛĀ? >%Ë E̟pæė9Íząī+†úA}åCįĖC:õ5õŽZŗVĢĖķį/ÔąīSA•:gŪˇ<%” dúäËŊ��ŽŽĩ–ģšēPwžRŠînnȞũ˰DĨR‰cĮO Lé�Ͳ„„œ;+‰Ē7Ĩ¨įĪąvũFôųĩ—ÖōäõŠi‹Ø}įuqIåYIĒWũ‹EŠ_r9:âãĮx÷N˙—´ÁC‡ãü…ŋ1gÖLėÚąŨģuƄISąwßū¯î/33Õ9Œ3ąį‘q–yzÎÅöiÉĨÖ?%—–~/-Û�â^+dUĸ ęÕŎj}QÚ˛m;Š-‚Â…=´Ę=6mư!ƒ°o÷Nü6 /VDŦ”i3��ʗÃÁ}ģ4öü…Ū^(^ŧōå͛Ļ6ŠyŨ'īË� :Ú¸äKZcōĀĄÃ(V´ōįΧĩ<{öė˜4~,ÖoüŊúôĮŗgQ˜ į11-ό‡G!WĢŠÔtá6nڌDU"—/Åô)qōä)Œ=Ö¨:är,^ēEŠxâčĄũØŗk._šŠ9ķ��–.šŸŪhØ Ν>ŽbEÅ]6`Žã5ÔV17l€ājU1jĖx‚€ÄÄDŒ;õëÖAŊ:ĩ./‘Hđc˰zízÜē}�pųĘUėÚŊ­[ĩ ÍņâÅK8:æDßū„ ĘÕ:gžQ— SĪęĩë…~}zë­ËĐųą°°ĀÚõPŦhQŦYĩ6ĀÉS§ņņãGM™?âÄÉShܰ�ĀŗpaW̚æc6õûYĩĒ•qøG˛ėĄÉŗparwÃé3gõŽ‰‰AįŽ=āéYl\‹-›ÖĄxąbhߊ+ŪūąIėįIcûÁÉSg`ɲ0üŌŊv˙š :ļÃø‰S°~ãš2 Á­[ˇļtÖŦ ĮÛˇoą‹ķfi™ū˛ŖOŸ3×ööZËí>]û”Ę/\ĶgÎÂã'Oą`ŪͲsį/@”(ĨųUØz—‡…ŖhOTǍ=”8yŊbڒÖcJÉÕÕRŠWŽ^ÕúBvãÖ-�ę´9ô\';røPȤ2,X��P¨;"V­ÁŅc'PģVMQûÎlLuN�ããĖ××GÔydœemúú´äR럒KKL˜ōĩAߎIŖ†˜9k6ūwôjWƒ Øļc'ZũĐBĢÜÛˇī°yë6 <PķËą›Ģ+îŪŊ‡°đ•4p�lllāîæĻŲföÜųxøđļoŨKKË4ĩOĖë>e_ö):—.]FĶ­pûö8å΍úõęā×_zĀÚÚZgiÉsį/ | ūK›jTFŊ:ĩąkĪ^Œ6.ÎÎFģXM›4ÖUķ­œœrcĖČá��?_\ģ~K—˙ŽØØXØØØˆŽĮŗpa´hŪ €:‘T­J\žr�āāā�™\KKK8æĖ)ēNs¯ĄļŠ5aėhÔŽß›6oAllžE=Cxؒ4—6d ^ŋ~ē C.—CŠTĸsĮöšųŒŪŧy�˜>c~jŨ ;´Ãų 0uz”J%~ëßWTģÅÖķâÅK˘9 ĶĻLJu4–Ąķ#‘H`cm­™GĻ@ūü˜0y*ūš4j�Øˇ˙ Ņ ~]�ęËJ’.-IË1›úũŦLé�„Ι‡ģ÷îqîš /_>ŧ|ĨŌäČgĪđ):ß5n¤97ŖGCÃõ4īCb?OĶ~üô ĢÖŦEn]ĐŦi�€ģ›Ž\šŠE‹—ĸe‹æˆzū'NžÂØŅ#Q!¨<�`ˍ8vü„Éž›ôāã] Ĩũ}5#ĪAĀų‹—qåÚÍ nŲSĻOž¤ÅÔé3ąbå*,œ?…Üŋ|°<vüʖ)Ļ•ąąąXģ~#ƎĄŗîkõĻÖS°ˇŗCㆠ°`ŅR”đö†¯O ėŪģû÷�ČåúÃÃÎÖ /ÅŠĶ§ņúÍ*īŪŋ‡ģģģIۗUgbĪ#ã,ë2t~ŋÖ?ũW(•JÄÄÄh[XXõĨ1Ģ(P ?J•Ä–-ÛP#¸Ξ;'OžĸQƒú¸|õË|×oÜ@bb"J•ô×ÚŪ×§bccņāÁC­ųŽ?‰šķbūœP­„ŒąÄŧî“÷e*• ˆŒŠB—Nāœ'Νŋ€Ųsį#2ōf͜–æļ¤ôęÕk8;ëŸÄ÷ãĮøûŸ‹°ļļÆžŊûĐĄ]­K$ūíʖ)­õ8 TI(•J<|ôō˜Ø2eŲėŲŗáũû&iŖŠ™ĸ­ÎÎy0lČ L6ĘÄDŒ3 šsåJsų!Ą8qę4f‡Ė€ga\Ŋ~“§N‡ŖŖ#ēwí ÅįkÁÕĒĸg÷Ž�Ô__Ŋz°đ•č×įWQ5‹­gėø‰([ļ4ęÔūļ<J•Ôü?O'–-ƒŊ{÷k’/ģöėE… ōȝ;ˇÎﯺ9ŪĪōäq NF1ų’ū”JeĒq]ČŨ… šŖß€AøŠu+TŽT%ŧŊP.°ŦόØĪ“Æôƒ×¯ß€BĄĐš\ˇ\š@ŦßøĸŖcpįÎ=�ęš “H$øûųáęĩëøˇúņû&°úĘw‰D‚2%ũPϤŸfY||Öūą-Õmč‹LŲQ6‡l�€Ɇ7‡ę7ØlŲ˛i–ŠT* >ĢÖŦEØ˛Å:ג?qR“E7Ļ^�8rô8âââ´î† ¯^1m1vß_3jÄ0x/†–­ÛĀÛ/�ĢÖā—žŨ ‘HôŽFP(h׹3Ž?aCcëđįö-(áí%zŸ™‘)ĪIZâLĖydœe=†ú´$_럒KKL˜2Žžæø‰“(YĻŧæoä˜q&Š73jܨ!ö<„?bێ(éī¯ųE0IŌ¯žöövZ˓~ŽŽŽÖ,‹zū}û˙†ŽíÛ~Ķ—4ą¯ûä}™T*ÅÅķ§ąeã:Ô¯[ĨJĄ[—NčŨĢ'ļíØŠ÷zũ´Æä§OŸt~%O2nâdČ-äXˇz%ūšx ĢÖŦ3îāMH&“AĨRé]§ü|™†EФˇƒƒƒÖã¤ÄellŦQûÖ7ŌčßzІŠÚÚ¤QÄ'ÄC"‘ˆŠ˙ÔĘ?ŒÄ’ea>t5Ŧ/¯âøžYSôéõ BgĪEttŒfÂŅ”f—)]qqqxōôЍ6‹ŠįĐá#8rėƎ)ĒίI_ ÔÃá#GOŅŅ8zė¸&“–ļ&gŽ÷3MŲÚe)}Üđ�ųōéŋ”U&“aÃÚUhPŋ.Öm؈ÆMŋGåj55ˇ 7æķ¤1ũ`Ԝ@­Û´GņūšŋACÔ#g^žzŠyŸLŲ×s™ėÆᎆ —éGžxx¸P_ģ™?ߗë´īŨ�ŠT BîšecÆMĞ}ûąze¸ÎíoßŊ{+W¯aŌ„qF× �ûDŠ’ū:_4SÖ+Ļ-ÆîûkräȎaKõü9�ĀÅŲ!ĄsP¨ģz’ˇūšx 7nŪÂēÕ+ĩnûöúÍ(P@§|VaĒs’Ö83tgYĶ×ÎorŠõO)Ĩ%&LG_SǤ?Ö¯ų2žž_PI­Aũē?q2vīŨ‡]ģ÷hîĸ’\Ō‡ĐOŸĸĩ–'=vpP'! zõîBô[˙oj—˜×}j}YJ^ŋ�ĸĸĸ3g­uiI{{{Ŋ“ņ:|lۊ•ŋ/‡Ÿ¯ēué„i3fĸzpU­úĶ‹ŖcNÜŧyKīēĮ�ō¤¸ wLLlŠĮęQdI_ôâ‰‹‹ûæļšĢ^SÛļYŗįÂŲŲ …Ąsæ|¤VūáÃGE<ĩGV¸šš"AĄĀŗ¨g(äîKKKŧIq÷ŋÄDõ萔sĢĨÆÅÅŲ`=íŪččT­ņeūA <‹û`İÁhßļ¨ũĨTˇNmŒ;GGlŦú9­JâJL[“3ĮûŲ‡I í/įd~gĪĮ‹/uF˜$—ËŅCÄĐÁqûÎ,[Žƒ†ĀĶŗ0âââDž4Ô&—”ˆŸ5cĒŪųĢōå͋ĮŸ�€ÖüFĀ—xúˇZģ‰#XĖ)Ķ|qsu…ģ››Î¤JûöīGšĀ˛šŦææ-Û°ņÍ_žTī—”'O"GŽėđö*nTŊINž<Ōē]ĻŦWL[ŒŨ÷×ėØų.]žgg¸8;CŠTbëö¨•Ę­6ãã�@ëÃė…ŋ˙Á“'O˙ĩŋnĨS“´Æ™ĄķČ8Ëz ßäRëŸRJKL˜2Žž&[ļl([Ļ´æĪԗĪe&šQŠb,Z˛ īß@ƒzēwņ*^2™LįÖĶūųööšK‹ĻL›‰GcnhHĒ—Š%æuŸ˛/ģwī>ē˙Ō[3Aiōí¤R)ÜÜ\uö“Ö˜Ė;^ŧxŠĩėÇ:b$š7ûN3į—Ũā”Û ÃGŽ1âčM§jåʸ˙āĄÎŧ*• -…‹ŗŗÎ(‚sįĪk=žtų ,-,āöy2R{{;7nĻíz˙ä}¸)ë551mģtų ÂÂWbÂØŅ7z$–.˙—ŋr;í¯•Īûy’ęģwīkms÷žúą‹‹ d2*WŦ ģ§NŸAöėŲEOT.Ļžũú`×ÎmøsûÍ_×Α;W.ŋŋĪ�� �IDATüš} š4j$j_úärtD… r8xøØģ˙�ĒWMuT™ąĮlŽ÷ŗ¤×}ō[Į“ųŊ˙#GEūüųPŋ^]Ŋe?~‚}ûhņôĄqŖ!•JqëÖmŖ>Oę“ķō*K ŧzũ… {hūräĖGGGXZZÂŖP!�Āĩë74Û) œ:­ōā“6-›Ą}ëĸūÚ´l–ŅÍũOÉô#_� WĪî<l\\\PĒ$:ŒC‡`õĘߨɘŠjU+#&&§NŸŅÚ>  Ž8‰ ōåĩ~ 1To’˜˜D>{†‚ķë´-eŊbÚbia!jßWŽ^Ķ W >|¨Š¯TIXYYaĪžũ¸xéƌœ9r`YØīˆ‰ÕL—W1XZZ"|å*ôéõ nŪē…i3BPŠbÜģ¯^ŋūęõΙ™)ÎIZãĖĐydœe.†žsAD_āëũ�DŦ^‹í;vbãēÕ�ÄÅŖ)ļW”v5Ā€CP!¨ŧŪ/9rdG‹ī›aáâ%pws…ˇˇNŸ>‹ˆUkĐĩsGČårėŲģŋ¯X‰IãĮ"6.Vs{i@ũå*ånCÄŧîSöe äĮ͛ˇĐŖWo č×ÎyōāĖŲsXŧt9:ļoĢųÅ2-1™R™Ō:ÎĮŒ›Uĸ Ç Ö,ŗ˛˛Â„qŖņsģŽØŧe›f2F11-ĻĖļ;ņį_ģąhūŊw˜iÖ´ Öm܄ŊzŖS‡öđ)á×oŪbŨú ¸|å*͟Ŗ3Âã'O0oÁ"4nÔ�=ÆęĩëP¯nÍpyß%°wßtlßvövXļüwŧ}÷Îyô΁“šėŲ˛áÚĩë¸vũ:ōēäU¯ĄãԗÁũīČQåŊMķäĮ†ÚĻP(0dØ4nÔ�åËŠ'bŽSĢ&Žm›7ęŒČ0Tžģ*WLji3B`ooÂ… áÆ›X¸h š~×XsųM¯žŨŅâĮŸ1xč|ßŧ).^猈ÕkŅŋooŖæ2TOŌ%É999A&—ĄXŅ/ˇœs~ôiPŋ>æÍ_ˆ?bę¤ ZëļlŨŽŊû÷cáį âÅŗ9ŪĪ�õ-ÁsäČÎų^Ė(&úËg…B7n"|å*DĮÄ`EØRÍg–”"Ÿ=C^}0xā�ÔŽ‰D‚­Ûw@*•" TId˞MôįICũ`rööhÕę„Ι Įœ9áīį‹§‘‘7q2ōē8cų’Eȟ?J•ôĮÂEę÷Ņ\šņûŠˆTåß$AĄ„ˆųŖ� !AaæÖd.Y"ųŌŦiDĮÄ`éō0˚=înn˜7g–æÍīŪũûˆzūQ{ŸcĪ^ŨÛØž>~ĮŽŸ@Īn]Ē7ÉģΎ:Ķ÷A4eŊbÚâä”[ÔžGއ‹/iGŦ^‹ˆÕęûĪ9¸ äĮ¤ņc0rĖ8ü6h(âããQļLiŦ[Ŋ2Õ!ûš1}Ę$L›‚Í[ļÁĪ×ͧNÆķįĪņkŸūøŠMėųkģŪm3;Sœ“´Æ™ĄķČ8Ë\ =į>~u~¯÷O�‰ŋ˙š¨y,&&Lą˜¸ĸ´Ģ]ŗ&Ŧ­­Ņ¨AũTˌ5övv9f^ŋ~ƒŧy]ĐĢgwôčÖ�pčđ˙��ÃFŽÖŨväp´mķ“Qmķē‹ÕęË,--žĶCfaėø‰xûöōå͋ÁûŖíĪ_öŸ–˜LŠFp5Ŧß° O##‘?_>ė?p[ˇīĀÜЙ:—8T*fM›`ü¤)¨Rš"rįÎ-*ĻŔšsį.ö8˜j;-,,°zEæ/\ŒŨ{öbɲ0XXX t@)Ŧ_ĄsĮ;ĨB‰žŨģáɓ'hŌėÄĮĮ#¸jŒũeÂŌáÃcАá¨T­˛gˆ–?|æMŋÑŖĮSm‡>íÛūŒūŖE̟ąpŪlQõ:^@ũëw‡ÎŨt–O›2ß7kjT“jÛĸ%Ëđ,ę9"V„iļ9|(jÕm€‹– Ī¯ŋhÕ'ĻüŧŲ!™=ƒ‡ĮÛwī;w.4nÔú}šÍŗŋŋ–/Yˆi3BđsģČå舁úĄsĮöFŸŠęs~ôŠ[ģ&FŽ kkTŽĒĩîÖíÛØˇ˙KbÛjŽ÷3�8üŋ#ŽZõ?5‰öÍŖĮŅēM{�ęšŧœķäAÕĒ•ņKn_Ŋ|ŗ\`YLŸ2 KÃ~ĮŦŲs!—ÉP¤ˆ'͟‹B…Ü@ÔįI1ũ`J#†F6L™6/^ž„SîܨY#ŋ č§)3;d† ‰.ŨzÂÁÁ­l‰Ļß5ÆîŊ˙îÛM'$$ĀÆZ܏\ &_Œ!>šzđāÉī"bŽ:‰ˆˆˆŌ› ¨×° ʗ/§šiFŠQģėŨ•ĄmHOYíx˙k2ķų9söZũÔģvnĶņCô_žfcšļkX§rįrUöåĢ7øsīÃõhßēEšļ3•ŒČdú9_ˆˆˆˆž•D"ÁˆaC°aã¸wīžá ĖäĐá#đõųú<N™IV;Ū˙šĖ|~”J%ĻL›‰–-š3ņBYJ|B‚č˛ F”Ĩ,rŲҎĒTą:wl_z÷ŖMëõÎ`nÁÕǤzÛøĖ(Ģīˇ8{î<:wía°Ü˙î3x7 ą2ķų™9k6ââ1fTꗞeFÆĖ㒠`ōÅLž‰ÔŋooôīÛÛpAĸtæįëƒ?ˇo1XގLgđĀ<p@F7ƒ(Ũ3 'Ü5“/DDDDD˙qVVVœŒœˆž™Q#_˜|1 į|!"""""""(Œ¸”ˆw;2“/DDDDDDDdäœ/LžƒÉ"""""""B\|ŧč˛ņF”%&_ˆˆˆˆˆˆˆĀĶgĪņęõƒå^Ŋ~ƒ§Īž§C‹2N¸KDDDDDDDP(Øšį@F7#SâČ""""""""3bō…ˆˆˆˆˆˆˆČŒ˛Lō%113g͆GQo„…¯Ôģ~YX8j×koŋ�Ô¨S‹—.Gbbâ7ÕÛš[xõÖų>jŒ¨vĮÅÅĄJõZĒ\ͤõ’y„¯Œ@ÕĩQŦ„?jÔŠ-[ˇk­‹‹ÃĖYŗ\ŗ.ŧũPĢ^C,^ēJĨōĢõ2Î(ĨoíĶnŪē­÷Ü&ũŊ|ų*Õ}ŠsSmcčÉ8‚ `ËļíhŨĻ=J–)â>%QĨz- 47oŨ6ųūƒ0oÁĸoŽG”-_ {öîũ^-&ŪÄė˜zŌs_Æž&ēöčõÕ×ųƒ‡˜î\=}‰ĻߡDņū&}Í&�¸~ũ~üšŧ|K!°BL˜4ŠĪwÜHīžˆˆHŦ,1įˋ/Ņģß�ŧ~ķ2™Lo™Đ9XŽū}{Ŗ¤ŋΜ=‡i3B •HĐĨsĮ4×ûéS4jTF§í´–;įÉ#ĒíĄsæ#*ę9rår4iŊdzk×m¤)Ķņ[˙>(éī'OaĀ !pp°GÍÕ�c'Lƒ‡1uŌxöĀ?/ađ°ˆGī^=õÖË8Ŗ”Lҧ,k"ÂuļÛ˛u;Nœ<…ė9˛ë­WLœ›b1ĮH⠂€žbĮÎŋиaüØōØÚÚāŪũXģnšĩh…ß—-F`Ų2ŨT7nÜÄÛwīT>PÔ{ĩ˜x_bęIĪ}Ĩõ5áZ° &ŒŖw‹ŗ3�`øÁ(VŦ¨č:Sŗa͏}į.V†/‡G!÷oŽ/Iōˆ|ö ?ļiāĒUž?Á˜q —[`Č éÚˇ#K$_ļm߁\ŽŽXždJéŦW(XąÛˇEˇ.��åËâÆ›Øų׎T“/†ę€ččhøú”@ųrFˇûÆÍ[_æM›āđ‘Ŗ&Ģ—LO,X´m~jŽÕ1Xļ îÜŊ‡ų ŖfęPŠTØžãOôėŪÁÕĒ�� ,€ŖĮŽcÛöŠ&_g”’)ú4[[[ķúîŨ{ė;p�ãF‚Ĩ……NŊbâÜۈ9F2Îēõącį_˜1u2š5mĸY^Ā­~Ā­~ÂÜų ž<㙊cĮOĀ×§lll ÆĩØx3_bęIĪ}‰Š'5ļvļ¨Tņëå›7ûNt}_ķūũ{äĪ—ĪäIŧ¤Ȗ-fΚ 7ׂ™1‰eJĀÉÉI3ō%Ŋú6"""ce‰ËŽ6ŦųsCaggĢwŊL&ÃÎm {×ÎZËķåˋwīßkO>žÅ}D× ¨GØÚĻž^_Ŋ� RŠ0lÄhüÜēŠ)’Ļz)ũ<xđO##QĢĻö´ÕĢáâĨËøøé$ A€……vÎĶĘĘ H4gdˆŠú´”BįĖEa4lPOīz1qnŠm�qqO⭈X…2Ĩ´/Iėíė°qŨ­Ä‹RŠDčœy¨Q§>Šû”DõZõ°jÍZ­í LŸ9 A•ĢĄ„i´hõÎ_ø;Õ6œ>sÅJøcíē ęí0yętT¨ŒĸŪ~¨Tĩf„„ę\†yôø TŦ$*ŽÅƛĄøSOzîKL=ß"åeGĨËU@XøJtčÜ ÅKøããĮcĸE̟°rÕÜžsEŊąpņRƒįxûÎ?áYÜ׎_×ÔsūÂßđ(ę]{öj–%Å��ėŲˇM7‚$ŲûfĨŠAš5ô1GßFDDdŦ,‘|ÉëâōÕõRŠînnȞũËPTĨR‰cĮO Lé�Í2Ī…\­ĒčzõČC_^SÖ �Ģ׎GTTúõéæz)ũÜ{đ��āęZPkš›Ģ+�õ;‰D‚[ū€Õk×ãÖí;�€ËWŽb×î=hŨĒĨfÆbĒ>-š¨įĪąvũFôųĩWĒõЉsSlˆ‹{įǸuû‚Ę—KĩLĘ/ô“§ÎĀ’eaøĨ{7ėūs:ul‡ņ§`ũÆ?4e&MžŠõ6aÄĐÁXˇz%ÜÜ\ŅŽc<~üD§ūûĸû/ŊŅĩsGüØę�ĀČŅã°aĶf 2ûvīÄoúbEÄ*L™6Cŗ]BBΝŋ€Š‚DÅĩØx3_bęIĪ}‰ŠĮ”,,,°vũ+ZkV­€Á˜[ē?|ß…pîôqthׯā9nܰ‚ĢUŨ1ã!1zėÔ¯[õęÔ īŪŊĮ‹/áč˜}ûD@`‚*WCčœyŠÎŅgŽžˆˆČXYâ˛Ŗ´˜>s?yŠķæh–5oöŅCs?EGãŌĨËhÚĸnßž§ÜšQŋ^üúKX[[ë­÷ŋ—˜1sĻM™”ę/\bęĨôķéķ¯bööZËíėė´Ö2¯_ŋFŨ!—ËĄT*Ņšc{­9Ugdúú´ä–‡…ŖhΝ^ž 6Îŋu2­¤ F ȝĩ\ŠT">>Ak™ĩĩbbcąjÍZôčÖE3RÆŨÍ WŽ\ÅĸÅKҞEs|ŠŽÆē ›0lđ@4¨¯M0iÂ8ÄÄÄāáŖG(X°€ĻΡoߥS—î¨\úõŅ,Ûŧu†¨āæęŠģwī!,|% �K œ;‚  t@)ŊĮ–2ŽMobęIĪ}} AĨBttŒÎr‰Š&×% lŦ­1dĐ��ĀĮOŸ ƄƒƒŦŦ,!“Ęā˜3§čs<aėhÔŽß›6oAllžE=CxØM[’ĮĀ“'O�ĶgĖÂO­[Ąc‡v8áĻNRŠÄoũûꋚú6""J; $† eBLžč1uúLŦXš įĪE!wˇ4×ŖRŠ`aaČ¨(téÔÎyōāÜų ˜=w>"#ŸaÖĖizˇ;~"ʖ-:ĩkš´^Ęx3BBqâÔi˙Ώzũ:&OGGGĄôb1ÎČC}Zll,֎߈ąŖFd@ëČܤRõ WĨBûržõ7aäčqZËÖD„C*•BĄP rĨŠZëʕ Äú ::ˇnŨFBBüüž\Ęhiasgkm“€ŊzÃÅÅS&Ž×,ŋ~ãQǤŋVy_ŸˆÅƒQ´ˆ'Ž?˛eJÃŌŌRį¸Lõ^ŲŨŧužĨtį`ąąąÁՋįSŨ. TIÍ˙¯_ŋa0&R&ņŞcgį<6dĻN› eb"Ə…ÜšriĘ'Åį˕‚ĢUEĪî]�~ž>xõę5ÂÂWĸ_Ÿ_ĩ&#fßFDY™ĩĩ5âbcĩĻ7øWXŲdÍq™|IFĨRaøČŅØų×.„-[Œ AåŋŠ>ŠTŠ‹įOk-+P ‚ `ڌŒ1 9sæĐZčđ9v ģwĻ~{ôÔKæ•Í!�āÃĮpppĐ,˙đáƒz}ļlx‰%ËÂ2c*5Ŧ�đō*Ž˜čLš2 m~jĻkųg”ą}ڑŖĮ‡ՃŋZŸ˜87Å6dZyōäD"Áã'ڗÕŽYE?ĪõôōÕ+ôęŨ�4ķ[´nĶ^ëw)• |.ûīßĢύÍ×ûŦđˆŽ‰AĪÂPŠTšåI# ėíí´Ę'4ˆŽŽ�?q ę×Õ*ķĩ¸6Uŧ‰Š'..>Ũöõ-Ü\]1mĘDå†î˜”ŧ-bbÂÎN;&ö@“F 0aŌdČå:?$ûĪÛú”đÖ*SĻti,\ŧOž>Õ\*˜ˇo#"úˇ Ž„]ûet3tI$ޜ5oĻĀäK2cÆMĞ}ûąze8ü|} oF^ŋ�ĸĸĸtžŧūĩ{7ĸŖcPĩFmÍ2A <‹û`İÁhßļŅõ’yyx¸P_ž?_>Íō{÷@*•ÂŖ;ūūį"A@ĪÂZÛēšš"AĄĀŗ¨gđ,ŦŊî[0ÎHlŸļ˙āA”*鏊܂5‰˜87Å6dZvvļđķķşģvŖßېËÕoũNNšáä”�4—s�_.Ŋ˜5c*ŠÕŊũpžŧy5ÉC—cxzzb¸QhÕē-ĻNÁ¨CÕûøü÷ͧh­ōIėņîŨ{\šz “&hÎųZ\›*ŪÄԓ”TH}} [”-Sú›ę:ۈ8ĮIf͞ ggg( „Ι‡Aŋõ�pqq†ĨĨ%Ūŧ}ĢUgbĸzDŒEŠ;™ŗo#"úˇsvʍzĩ‚qččIÄĮÅC€Ąí‘@+k+W‚ķįĪY “/ŸmŪ˛ ˙،õk"L–xšwī>Ļ͜…ū}{ŖhOÍō ˙ŠT 77WmôëƒÎ;h-Ûēm;ūØŧ+ĮÉ)Mõ’yššēÂŨÍ {÷í×ܑ�öíߏreaccƒŧŸ?œŪŊ{Ū^^š2wīŨ�¸¤q2EÆécLŸvōäi4lPß`bâÜېéuîĐŋö€EK–ĄWĪî:ë/]žŦųŋ—WqXZXāÕë7¨_ØCŗüõ›7J¤°´´„‡G!X[[ãô™ŗšųXT*ZˇižoŽ™¤zpUx{yaˍømđPŴƕ*ÂĢxqČd2œ;A범 ˙ü{{MĖäȑŪ^Å5ë ÅĩŠâML=隯Œ&&&tļqŽāŌå+ _‰ˆđ刏Gįn=Q¯nøú”‰“'ĩb@&“ĄrÅ Øģoŋæ˛#�8uú ˛gĪŽ3)ą9û6"ĸ˙g§ÜhÕŦQF7ƒ>Ëɗ+W¯i~S >|ˆS§Ī��J•ô‡ ˜ŠjU+#&&Fŗ.I@@)XZX`ËÖíØģ?~žØĪPŊ äĮ͛ˇĐŖWo č×ÎyōāĖŲsXŧt9:ļoĢ™č.yŊ.ÎÎpqvÖÚŋ““drŠUˇŗŗU/Ĩ¯^=ģcđ°pqqA@Š’8xč0>‚Õ+�rwCåJ1mFėííQ¸p!ܸq -AĶīk†S3ÎČSõi�ƒČgĪP° ödŦI"V¯Åö;ąqŨj�†ãÜTÛ:F++Ģo{ŗ˜õëáėų  ƒ.^Dƒzõ#GŧxņûĀÁC˙Cũzu5ĪmĢV? tÎ\8æĖ ?_<ŒÄ¸‰“‘×Å˗,‚ƒŊ=~øž,Z‚ŧ..đô,Œ5ëÖãŌå+˜:y‚Îū›5m‚aāaØĩcræĖß7ÃÂÅKāîæ oo/œ>}ĢÖ kįŽËå8vâ$‚Ę—×ÜR8..NT\‹‰71ņ%ĻžôÜWFž&ėí ÆDJ9rd7xŽ † Æ |š@�@Z51xčplÛŧQ'�õsŪâĮŸ1xč|ßŧ).^猈ÕkŅŋoo­ræčۈˆˆž‰đŲũû÷S3GiņŨ÷-…BEŧôū=~üD¸zíZĒë ņ^ŧx)‚ L™6C(\Ŧ„čzA?~"ôî7@ŦPY(âå+T­^[ _!(•JM=)ëMiųī+„ō•Ēj-S/Ĩŋ•ĢÖ•ƒk Eŧ|…Zu íŪŖĩūǘņ…˛å+ žÅ}„ō•Ē ã&LĸŖŖ5egdˆŠú4A„§‘‘BĄ"^Âļ;õîK_ÜŠsSl#&îÉxûöÚ´ī$”,S^(âå+”ĢXEčŅ̎đŋ#GĩĘ) aæŦŲBÅ*Յ"^žB…ĘÁ¨1ã„?jĘÄÅÅiú3/ßRBĶī[ §NŸŅŦ/Uļŧ0wūBÍã7oŪ * ŨųUAˆOH&Nž*”ĢXEđ,î#TŽ)Ė_¸XPŠT‚ BåāšÂÚu4Û׆âMl|Ē'=÷•–×D—îŋõ}§w]r)ĪUųJU…!ĄZeÄÄĨ1ã„Úõi:Įsæ-J–)/ŧzũZŗMTÔsÁˇd!tÎ<Hräč1Ąa“fBQo?!¨R5aéōßuʘŖo#"ĸĖ##ōAPĪ–öāÁ¸ģģ›4ącŽ:‰ˆˆˆˆˆˆˆŌ*#ōR“ˆˆˆˆˆˆˆ´0ųBDDDDDDDdFLž™“/DDDDDDDDfÄä ‘1ųBDDDDDDDdFLž™“/DDDDDDDDfÄä ‘1ųBDDDDDDDdFLž™“/DDDDDDDDfÄä ‘1ųBDDDDDDDdFLž™“/DDDDDDDDfÄä ‘1ųBDDDDDDDdFLž™“/DDDDDDDDfÄä ‘1ųBDDDDDDDdFLž™“/DDDDDDDDfÄä ‘1ųBDDDDDDDdFLž™“/DDDDDDDDfÄä ‘1ųBDDDDDDDdFLž™“/DDDDDDDDfÄä ‘1ųBDDDDDDDdFLž™“/DDDDDDDDfÄä ‘1ųBDDDDDDDdFLž™“/DDDDDDDDfÄä ‘1ųBDDDDDDDdFLž™“/DDDDDDDDfÄä ‘1ųBDDDDDDDdFLž™“/DDDDDDDDfÄä ‘1ųBDDDDDDDdFLž™‘Y“/rš‰‰‰æÜ‘(‰‰‰Ëåéž_ŗ&_Ŧ­­mÎ]‰ōéĶ'ØØØ¤û~͚|qttÄû÷īņîŨ;¨T*sˆˆˆˆˆˆH¯ÄÄDŧ}û?~DΜ9Ķ}˙fk#•J‘/_>ŧ~ũOŸ>å%HDDDDDDD”îd2ŦŦŦ7o^HĨé?ũ­Ų÷(�@"‘˜{WDDDDDDDD:’rI9Šôf֑/JĨ‘‘‘°ˇˇ‡d2™9wGDDDDDDD¤#11qqqˆŒŒDž|ųŌ}Ō]ŗŽ|yķæ ėííakkËÄ e™L;;;ØÚÚâ͛7éžŗ&_âââ2da"""""""ĸ”lllîû5kōEĨRqŽ"""""""úWÉdP*•éžßôŸâ—ˆˆˆˆˆˆˆ( aō…ˆˆˆˆˆˆˆČŒ˜|!"""""""2#&_ˆˆˆˆˆˆˆˆĖˆÉ""""""""3bō…ˆˆˆˆˆˆˆČŒ˜|!"""""""2#&_ˆˆˆˆˆˆˆˆĖˆÉ""""""""3bō…ˆˆˆˆˆˆˆČŒ˜|!"""""""2#&_ˆˆˆˆˆˆˆˆĖˆÉ""""""""3bō…ˆˆˆˆˆˆˆČŒ˜|!"""""""2#&_ˆˆˆˆˆˆˆˆĖˆÉ""""""""3bō…ˆˆˆˆˆˆˆČŒ˜|!"""""""2#&_ˆˆˆˆˆˆˆˆĖˆÉ""""""""3bō…ˆˆˆˆˆˆˆČŒ˜|!"""""""2#yF7 =tØjuWd_-3§ž]J'~ĩL™ÖčUN‰!•”ĻlžI°æ˛ á˙Čpå…ą 6õЍĐŋŧų˛™ŋ žsŦņŗ¯c‚S~UĀüŗr„˙-ÃÃ÷Č& ]ÉDô)§„ėRæ87ũv[āČC)Îw‹7YDDDDDD”õd‰äËŠJ´ņ˙’Xé˛ŨŪN*ô úōEŊX.UF4Í$TĐvŗ6_—ᇉčZZ{KW^HąđŦŽČđįOņđuLē¨p8�� ėIDATßEge8˙LŠĨĸˇ{XŽŲ§å]M‰˛ųT8öHŠ‘åJ€žå˙}I-""""""ĸo•%’/ŪNŧž$Ŧå\ėT/ôßM¸$ˇøœ \—!ü;Zú|I2Õ+ĸB‡RJTˇÂĪ›-qĄ[ü7.Iéī(ã*S$ ĪÉņk ũ?'ž*ģŠpå…›ŽÉ˜|!"""""ĸL)K$_ĈWcËąņĒ /ĸ%pąđŖo"FVUBžJŽáčC)ŽąDH:$BŠĻ•cã5}ž¤æ×@%ē–ų’q›eAxü^‚M×åøTrUa~ƒ¸ØĢË$ÅčCr\y!Eĸ�øæQa\u%*šęOÍ?#GB*­ÄK’Üļ¤ ´Øh‰Ũw¤hPT…ŅĀĐũ8ü@†7ą@lē—Qâ—Ā/ÛįžjU•Z ‘;-péšĮ;ÅŖöJK}¤~bV]’áTgõĨ92)0é¨KÎÉņ.¨æŽÂ’Æ Čc§^wĒs<m´GāČĻÂßĪRŋ,,ōĐë/K~ Ekā×rJŧļŨ”áīîē—y„ZŖKi%†VVˇ=ęP(ÔÍŊąĒų—Q:…B­Ņģœũ‚”ˆü�ôüĶ˙{(Ev+ s�ADDDDDDdœp÷ŗ>ģ,°ōĸSj)ņOxŒĢŽÄÂŗr Û¯??uį­6Yĸ_y%:¨“ÃX`Ö)9UTâ\×xô.§ÄĀ}˙įKbÁBĖ:eī<nôŠÃųŽqøû™“Z��ĸ€fë-áå$āp‡xé ž\¨ÉZKŧÕmĮŗĀŨˇÔđH}OŌēŖÕ§ģûKœz"ÅŠĻ 8Ķ%ŋUPbđ> lŋ!>6ū€€ŧZx'âq˙8øäQ'T6]“áUŒ›[Æ#üģœz"ń#ęc“J€ÂŽrÚ|ŠGŠŪ—ĄBÁÔÛ˙Ë_–¸ø\Š?$`ۏņ8öH=RF*Ņ_žš{"N>ūr,ĮÉP ›€ŋœ‡Û¯%ˆúT/¤>wˇ[âęK ļ´JĀî6ņx+ÁÖ_Ÿ'ˆˆˆˆˆˆˆH Ž|đ:X}Y†I5øŪ[ũeÜ#g"nŧ”`Ū9&ÔPÂRĻ]žé:KÔķLÔL.û!^}ųĪĀ Jüä§ŽŖ°c"ū~&Ōãr´/ųeTIą\*´ũ<Mė@퉸đL,xüA‚ņĀž‰(ž[Đ˜YGŨ.+=g+ōŖ:áš=õ䅍āb˙ĨėôÚ Č¤€{uũEr%bņ99ܗĄqqq—beˇäRĀJŽ]“|yHõ蒀|ļßLÄŲ§Š'uF”ãÁ; Ö~¯¤ÉķOĀŪģR„ÔQh’Háß% č\käsĐ?‡Muėą€JP'|Ž<”ĸe‰D,>/ĮŊˇxäpė‘Nļ€Ÿŗ€§€C¤­Ģ@5wõ>Bę(pās“DDDDDDôíøíĀåR(U@`~íÄC@^ĸęQ.I”@ĢM–Čī `aÃ/—°\z.EB"Pŗ°vUÜU¸ûV‚O _–ųäŅ.“ÃZЌj)â( h.íˇX`Æ 9ū~&LĸžÅÖBˇíIŖ?”ĒT†|–”ˆ��;KķĪČ¸Ä ĄÖpŸe+/$xŖgdąĘĐ>6';SšYЈr,<+GDŗx:ęO¤Ü}+…J�‚’Œq°ÂWįëŠæ–ˆņĀ•ę>úPŠJn*”ɧÂņĪ—J{$EpĄDH$ĀÍ×ęeeō}ŠS"Ņ~LDDDDDD”VųāCœúßlVÚË>?Nž<˜VŽO €Wn‰”IĒŖN„%’§ATŸs Ī?I`˙9Á`ŖįYOJ=ȤĀūļņ9)GØF”Ŗ`6c‚•híĢ;§Klę-ŧK=ųĢ�^Dŗ P$×XAŠfÔQ ¨Ŗ rđÃFĢTˇ7†­\;‰"IvlITĐëO lē&ÃÖ4ŖMôyŖū×ÁR{šzŪũĮ\ ;P4—€¤pąOÄí×PáLuōĨ"Ž=’bxõh›¤ķk“"šeoš§„ˆˆˆˆˆˆ˛8&_ žTP_:”\Ōã¤õ�P<ˇ sę)P; #Z`zm…V™°& šųO’KJ’ˆádLŽŠÄäšJ\)ÁėSrtÚfâšTČ'č”õv°ņĒ ƒ**!Ņ“8x_=˛#¸ g#Ĩ¸üB‚ũmP1Ųž/Ŗˇė_ļŅWOŦø;JU˙=Ø~S†]?ĮŖtž¯?/֟#4&Åž Ō .¤ÂÉ'R8Ų đÉ# ģ5PÁU…ūģ-đ䃏ŪK4Ŗgė>'v>Äk§ŠŪ™`$/;āëŦ‚\ œzĸ=Áęé'ę;ß$ŋ$Ļ^ü]„ÔQ`ūöžÄ×Y+đ2Z‚bš͟Ŗ€Üļ‚ŪųZôyđN‚7ŋœ/'së+ “�×^é?]ŋ–SâęK –œ˙{w#WYĮqü73gg§íVÚŨŌnS• ōv!PRB–˜ąIB 1¨¨ÁßB‚FŖ ‰ j VŊ¨B€@lˆP( ĘK„ Á’ĀRĨŠ%}Ëî–vggŽƒ}ÉļK =Ĩčįsŗ{æ<3s5ųæ9˙gú€Ømģ{ƒ€Ī.ŗbi7{Ū­rā‰Cūg=Ŗ;k)č īëŸ^Ø:}ũōțR’äöįųÍ_š˙ʡ/ÉūīūéÍû×ۛ<úęĖÃpWŧ5twÃĻFFNęE–åKēyeG-÷ŧØČéCåž vÚPīīs[ö§v'y|“ģ���ŧsvž$œ•\}v'7=QääųŨœ3\æą×ęųÅ3EžqūĄšžęŦNÖŋÜČĩ4ķ—/íÉĐėäšs;ųŅc}Y0§7/dĶŽZžõûž,™[æŪ+&§ßäūąĢ–+īiæĮMe劝ԒÜųBīdŸåKũxÎÕgwōøkõ\˙P_žØTĪĒ3ē™ĶWæÅ7ęšõé"e™ÜũŲŊŠÕzĮVˇŠä–§ŠÜpa;{Ŗžī=Ō—‹OîæåíõlHÎI–-îæ|}ųTæö'7?Ydû›ĩ,>`ČíŧV™įūUĪs[jG´ŗįÍvōũG‹\ōán&Úĩ<öÚÁÛkÎ÷ ÁÆIōĄųe– —YŗĄČ ĘĖk•šņŅž,˜yŊ.ífķX-nŦgÍÅŊm3sû“,*sëS\zÚūīōƒ'”Yž¤››ž(rĘ`™g÷fâôÕ˙˲���‡ žŧå'Ÿhg Yæú‡šŲ:Ņ{Lčģ#Sųæ‡>…'I~ēr2į­måēõÍÜqųdÖ|ŦúËÜđp‘-ãĩ,(séiŨü`ő?¯sáIŨŦ]ÕÎÍOų៊õäĖŨÜõ™Éœ:tčPĢ%ŋZÕÎĮOéæ×Ī6rŨīú˛gĒ÷.;ŗ÷NœĶģöÄ9ÉÚOĩsã#Enž•swŗvÕd6Õōų{›Yy[žšvoÖ\ÜΗčËé?ke^ĢĖ–urÕYSyø•ũuäkįMåšû›šh]î¸üíãŌÆmĩŧ>VËë/ÕrßKÍi˙õú=˜ūēuĢ'ķ•ûrÉo›Y<ˇĖwFĻōė@=Īl>üÆ­y­dŲâ2OoŽôxÕčæ–§Yąôāų9ëVˇķÕûrų]͜ĐJžxnoÆÎũˇû��€wĻV–ŊGFGGŗtéŌŖzķŅŅŅ Õ{ō˙gw;™ėô‚ĘŦŧ­™ÁYÉí—ŲŽ"���H’-[ļTŌ?fē§/÷>}g3['jųų'ÛY8§Ėúõüq´ž{?'ŧ���pü_8î­[=™o˙Ą/WÜŨĖD;9e~™_Žjg劇?ĸ���Žâ ĮŊEŊ™,ÉQ:ë���Ž!GM���TH|���¨ø���P!ņ��� Bâ ���@…Ä���€ ‰/����_����*$ž����TH|���¨ø���P!ņ��� Bâ ���@…Ä���€ ‰/����_����*$ž����TH|���¨ø���PĄJãKŊ^OˇÛ­r ���€#255•ĸ(Žų畯—VĢ•ąąą*—����8"i6›Į|ŨJsĪāā`6lؐĖž=;FŖĘå€÷(ģä�€ŖÁo āp:NvīŪņņņŒŒŒķõ+/EQddd$ÛˇoĪäädĻĻĻĒ\���`šĸ(˛`Á‚ ž+UžbQY¸paÕË����—œv���P!ņ��� Bâ ���@…Ä���€ ‰/����_����*$ž����TH|���¨ø���P!ņ��� Bâ ���@…Ä���€ ‰/����_����*$ž����TH|���¨ĐžøRE:Îģų^����ŪS:NŠĸ˜ņš}ņĨÕjebbĸō7���đŋb||<ŗf͚ņš}ņepp0ģvíĘΝ;Ķív+s����īUN';vėČØØXæĪŸ?ãĩĩ˛,Ë_¸mÛļėŨģ×#H����‡Ņh4ŌßߟĄĄĄ4¯=(ž����pt9í��� Bâ ���@…Ä���€ ‰/����_����*$ž����TH|���¨Đŋ {Âúą]����IENDŽB`‚��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/profile-update-email-button.png�������������������������������������0000664�0000000�0000000�00000052573�14156463140�0024712�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��ú�����Ā‹)���sBITÛáOā���tEXtSoftware�mate-screenshotȖđJ�� �IDATxœėŨ{\Ķõūđˇž[ zØÜ Ø~†CÔ�3ŅRĘ/!ex;j&”šŠ)zDÍģŖŽJϐĮ+^ķvĖŖŌĐRšÕÆ †éF ˇQüūā""(n€—īëųā҃}žŸĪįûŪĖמ~žß}ץēēš��āi÷ĖŖ.���Úâ�€÷��Ŧ€¸�`Ä=��+ î�Xq�Ā ˆ{��V@Ü�°â�€÷��Ŧ€¸�`Ä=��+ î�Xq�Ā ˆ{��V@Ü�°â�€÷��Ŧ€¸�`Ä=��+ØX<rį;Ščt“ lwzŒĪ=sVŋ6rŗŠDģĪ/¸wkÖÂcw“bÉ؃c…-ܝūøÅĖō[žų¯|‹‹~l¨ãÂ>ũą_XøŨž‰bĸ¤˛÷ŋ3ųĖ;—øŽ¸j2žßëŖsĻF­{@,ņéúî„PŸ–ūiĀãĮō¸ī,Ąô\múélōņl´M}1UEDTœ–Ž$iŖ­ĘĶŠÅD$äĮŪôāōE‘ÖØ E¯š^n"Ž}7aÃw3ŽHČmįŌ8‰DČŠ{dŌkÔ×sŌ¯į¤ŸÜš{füŋūæÛŪõ�@ë°<îÅūĘUŠŌSÕäy÷§&ũt6‡CĻÜĶŠšŋIīNuuz犈$ũŋ°ˆ0t]rhÆâmŖ-Ë"ĪŽ=ĒLjˆHđÆ†ƒŅō†-úėƒ+ŖVemX?đøTÉŖĒ �ŦaÅÚŊ|`å&ĨkîŪ`ĖHÎ2‘Û!/å&ĨëīŪĒĪHÎ%"ßĀÆ˙&€Įß3tqtˆ€ˆrÎgéØ�KÖœĒ• ō™2’l¸(AÆŦ¤L |^• Ȕ‘”ÕÄV˛ī,kØŦĪ9ąúÈā~ū2wO‰WPāčKæ<0Yô9‡–~ÜĪ_掐õ ;÷ËŗjcŖ>šĖŊK§‡öSHÜ=%^ūÃĻˉ?ß°—ūā‰ģ§ĪÜķdT]4%¸ŸBâŽđé:uíy ‘>c÷ßĮž$s÷”õ ũđËÔFīn–o.‘&5~vč€Ú}ŋķÉÎĖÆģjƒz¸b‰€ˆˆL^ßžŧDdTžŪ47"¤Ļfw…Ī€Đąsŋ<Ĩ4>l�°†5qĪõ îmOdJ;ÚđoeÎéT-qäžō@_•§ŸÎh8(ķtj9‘}īAūwր5IŗCF´ųD–^ā3xʑAn”ķŨöy#ƒ§ŸP7ŋ{åÁÁo.Ø~"×(ōéėįÉ×ĻÚ0áĩˆÕ™õÕ•ģ§„ŒY˛=9—D#ÃÆŒ öá§øtjđčŠuųĮår8DÆō‚ĶĮÎI֋ũō67).rü–œŒ-ãĮ.˙Î(zappo!]Ī<ąaüô¯• ʰŦx qõg?;~mIzķõĒôÃŅīLŲÖ  ļŠG¯Ė)&"‘\Ö`eŽE/¯1įËĐ7#×ĘŌØû 1|äĐ�OnqÚĄ ĶŪģ)ĮØō>�`­jkčŽGôģu“q§íįÍ#ÜēËGlWWWßø×ÛrˇîCWeßŲzmÍPˇîr‰uõME{ÂzČŨēN9 ž]ßx#ųŖ—ånŨ}ÂvŠkwuėonŨåno× ü9aDš[1Šõ3é.¯áŅ]îöęēkĩ}â†öģuøŅŠ jΌyÃĮ­ģ< :ŗvw§Ū÷č.wëá0qĪĪuüŧũ-îrˇžŊ_~?ą¨ž5aDģŸQˊoõWŖänŨå#šRSdOŋŪo|zĄūŲÜÎ^Uķ\–eļB=ˇëū4ŗmĐ];0+ ģÜ­įß‹4ˇčåÕ}ûž[wŸ›•wꊞũķöw_č.÷˜xđFKû�€ĩŦģîžß{‚CtũlšĒŽI“z>—Č­ŋˆHØ'PFTšV\ˇĩ85­€ˆ|ƒę/?ÉHø2ÍDöÁķքŠîđ F2ĞLi {J×ËØķuω!3?ö¯Ÿ‰ī9ķ I7 G•ĄŦ™yGމėƒgEjpHĘ÷ųøŖöDÅĮ÷Ūõ“Û„%oIë*õ$ĸrŊxĘüQâúց¯Ë‰¨XŠ2ZSŧåĘŖVÍīS˙l¸ōđPį¨ô­VöčܰĐŅõ?ĄŊúŧ>ī4ųŲ¸/vTƒsō-{yĩJĩ‰ˆ/UH\ŅՎ_ģ{ĮîãK Š…}�ĀZV~ĖJXŗ¯JNŠ](Đ_<—I$č$'"’ú÷e'םŽÕ§ŸË!"YНK0UŠi׉8ž!ŽĻįûôåО2îY+¯åäs×eÜĀ5˙NNūæķˇĨD¤JÍÔ‘gp㙚ЁžĸōŦ;oRD$ņëĶđú"Ą@Č!"QEÃëdB!‡Č¤7­(Ū ĸ€Aw]3CB‘€CDÆr}ĢÕcŌæū˜™U˙“[\ND\Ōk3ĶsŒnáË+’J뉴§Öޏ{!^čéī#× mI�°–åbÖûûIčGUNJĒūŨQ|2fžN5‘Ŋ_ oÍfy@û2O§G æ’1-%ÃÔčL­Ē˜ˆLĘŨKĸ’Í­Uį5>ĀĢÅ î“5}bŅ=}ø1ŸHĢÕhˆęĶĶū5ī#ö"Á]CëŪ]ŒVo¨Ņd܆å´N=Ũ&Inx!ĻQ_ŦL;Ŋ9ö‹í‹ŋ;šŧ|÷W#¤wöõĀ——;øŖÅ¯g}t,kĮ´!{í%>ũũ‚‚úûË|ž %}�ĀZÖÆ=ÉömUgK3ŽÄÍNÎ*'NPPīÚŋ§\Ÿ ΁ä“2ƒũšį/–‰îēĶXs \œ~˛¸Šé‰Lú&ÎÕՌâpī÷éÃår‰Č¨7ī859ŲũķÆ˛âÛN›ÔÃå‹<Ŋû™ŋŒûæøį—,=*|ˆ—W<ôŗ#˛×woŨ~čt†ęâ1ÕÅc{6ŊÛ )ķĸ§ÖūƒĒ%}�Ā:VĮ=Éų ļŌĻžĪĸA‚ÔôëD/ųŨYOīčCÉS“UäOįŌĩDö}îē“Ëåiá;Î-ķoųNš|>‘ļüžáÅår‰LåÆfß-¸Üûž]´¤ ‹Šo;mYßod āĀmjōÆĐÜ‡zyų’ÁS?<•ŒšœŒô‹IĮīOÎMZ75ģ8ūxLŨ˙+-é�V°ūiĩ—cj3ŗ”šŦs毋āBŋ�9QqæjõŠ*"û€†—`‰ä"ŌĢ‹ęēp‘DDDåjÕ=KŅFŖąöWPÛ§øŪëä‹Õz"ˆ­ũøĒeŎļ­‡Ëå‘I¯ˇøåå å}BŪūįÁķ;ÆHˆŠn={OĨ-é�h…;brkOϤžOÉ6ŨķqYŠ_QÎųÔôķŲDÅĀ>wR‹úøw#2ĨOšį/ĩ*5)]Šiō�^äĢčFDÉ)w‡MÖÂ×|åۃ–Ļ‘¤7"Ę8~ąņįzĶNg˜ˆŊƒŦŊ‹ƒeŎ6­§æ<0 $5Ģõ-{yõĒŗĮ÷nKR5š‹ī?ĐמȤ×č[Ö�ŦÖ7@ŽšĶ”ģ+>ĢŧņZ ɂ|ėÉôãöøMDžÁŊũÃÜ3âoū2_9į ĒAiÎ.Š˙ūøĐy'šŧ–Ä7ė]‡Lį7,<X\7JŸŊeÅŅb"Ґ‘ū ûŦ_šÔ`ÍųĨëž+'Ž<âŨ>V?uˊo;mU>g˙ÜȍšDä6jŦ_M[‹^^ãÅÍs—,›÷ÉĻģ?úĢ9w2ĩœH ņļŦ�XÍúĩ{ĒŊ3ũĮœÜrâ R4ZįúōáœHÉÉ%"Ų Ā{ūîŠßZŗ*}ėܓIķBãũúČ\Ŗ6;==Gk"Ņ+k–Œhú/ģôŨ5 RÆ.NIšįã)âęUY™ÅåđMÛđžo}Ÿ˜ôąķž;đūë~ž{Ō¤Ļ],.'AāüĪÆˇÆ­ž,+ží´B=ÚŖ3CS9wˋÕÅå&""Á %ąÔ_´Ķ’—W8<:ōĐØu?Ž´ËĮĪS$ā“^Sœ›šuŨDŨ^_0ŗˇe}�Āj­÷u—c‘<Ā÷Ū ķüzRJ&Iú4u™…8dŨqŲНą_HĪ:u¨ÜÄąˆ}^}kڔĄžÍŸ¤“Ž;.ßģ1vīŲĖŦs*GāĻ:yZäģƒĨwâAúy˛äĐÆ¸Ŋg3SŽĻ—“Ŋ@,2mėäiĄōÖ:ûgYņmĮęzLZUŽļaĮ^ yÁ×ohxÄ[ũĨwEo ^^ŽįԄãōŊ›N¤ædÍ*7Į^ ō:yäÔÉŖj{ĩ¤�XĢCuuõŖŽ��Úžŧ�€÷��Ŧ€¸�`Ä=��+ î�Xq�Ā ˆ{��V@Ü�°â�€÷��Ŧ€¸�`Ä=��+ î�Xq�Ā ˆ{��V@Ü�°â�€÷��Ŧ€¸�`kâ^ŗs´§äCúFÍĘ/CÜS“ŦŠ ��ZŽî�Xq�Ā m÷š´/ŖFûxyJŧüGĪŪ”ĻŠßrvˌ~ū2w…Ī€°9ģsę…ŌöSŒ?ŋmR°ĖkĘ~}ÖÂ~ŠąņY§–G÷ķ—yųžŗâė9Ō7M ėĨ¸{úô Ú’^7IͨôũsÃ{)d^ÁĄËOk4é›Ļ‡úõRČú…FíÎ1>  �€§JĮŊūÄÂé_(åķw˙;éܑ¸å'-د!"cÆōņb¯û.ˆKūîx|¤$cųø¨ƒÅDDÄårMę=_œķ›ŋ;qŪ`>qšĻė¸IŠÅĮ˙›–{æĶ>Å;ĸ֞7iöĪ›˛F%ųā̃įž;ŋ@–9įxm\sšĻė„/rBb“Č<ŋJĻLø$tŌ×ÜČøôŌŽGpN-_yôe��<UÚ8î‹s•å‚>Ą=Å"ąÔį˜-˙5ŗ?ŸHzãîyäÚe!>bąČ7tņ˛Pûsq{ŗëĮŲ]6u ¯\Â'""Ŗü­č —ˆ„Gú ĘsrÕDDüÁK&m]<J!‹%ž!ī‡ËËSĪeÕīÜ(ëƒ !—H8ЗĘÉ˙oå|"Ž4x ØTŠzp��O ›ļ^Đ_´uįĖcÄ[ƒü{÷‘ =B"ĸœôlSˇ7ü%uũ¸ž2ΞŦ y ‰ˆDrqƒi„ŋž+ŸCĻrcͯ\íūĩ+įdŠÔz“Ņh4–‰LõŖÄ7~Ũ.‡ÄōnuSpųd4‰T(�āŠa]Üs‰ČdlÜZnŦŲBD\ŋč}ģ%q_HX˛ëĶrލ÷¨G‡H¸úr#]ß>Æsû]Ũôz"!QMĻ7ܡŠŊŗ–Žŋ“3|Ų’y}$ö\ŌîŸ>rcŗu—î™åAe��<5Ŧ‰{>ŸĪ!Uąæîl4kH ŦoúŧŊā펐^™~*aåŌ™S¸ĸähž=—ē ū"nš¤ÁH.WØđūrU Fíût”ĸæąļŧüaŸAk”�đ$°fížÛ'؇Ŗ:q ŗáņ}ņū¸”rQĀ 9‘QuęœĒf3_ę7jÁĖūmļJO?_ŽV]n/•Jj„\._Äoj7ÍŌ›Œ$Ų×>2æœ<Ĩ"2ŨwH#­R�Ā“ĀĒSĩyøhˇO;gˉSįΟ:øõÂwÆF§Ûüäũ>5 'ĒŊ §O‰Š?Ÿ­Ö¨Õ9gãw¤’ČWÎ'ūĀ Ą‚Œĩ­NĘQk4ĘĖC ß ™yHķ€ŪMîãÉÉ=pZŠŅ(ĶžŽZTėéĮ1Šŗ2ô÷,/5§UĘ��xXšv/ī_Ĩąë7īYr´¸œėRÅ+K÷Íz[QwŠ4hq|ˊĨq B?՚8ö"IīQâ>ˇOLÂūŠÕ‹ĮoĶ–“Ŋ›ođüø#nÁ\8|YLVÔÚ؃öĀgčĮ1ŸÖŦȞžuė;tđ›-|­P�Ā“ CuuõŖŽ��Únĸ��Ā ˆ{��V@Ü�°â�€÷��Ŧ€¸�`Ä=��+ î�Xq�Ā ˆ{��V@Ü�°â�€÷��Ŧ€¸�`Ģîwo2WŠKnTŪ6šĖU­U��4ÂallŸåŠģpËCÛōûŨ›ĖUš?˙ō×ÎĖ_ūŌá™W���÷Wũgõm“I§˙]öĪYœø–ĮŊĒđ:‡Ãąæ­��ZŽōļņ?ū¸vŗl¸åk÷ŋWT"ë�ÚÍŗNåí÷=,û?ūüĶâą��đ°:<ĶÁšĨ¸2�€÷��Ŧ€¸�`Ä=��+ î�Xq�Ā ˆ{��Vxŧâ>wĮĮã—üģä!Fä'˚ļčDi›U�đ”xŌ?Û5 ,Âۉ˙¨Ë��xÜ=éqī õëû¨k��x´KÜëōī8t&¯H[YÅã‹ŊƒĮD uįÕnē–¸mߙŧ_ÍŧŽŪÁcüīŒQ%ĖZ_2$* čĐáŒ"ŲÁõåŅQCx);öĘ+50N=‡EL~Ų…Ąü„Yë•ÁęT7dÖā˛Ã‰éEZ9HûFLãíĐO�ā1×q¯KŲöYbYīÉFČH§úvëļM[ËŖüxD甏MĮĩŠÉsĸŧ ĘäÄÄ ÕĨ3ÃT&+7mÅDCúĻųq KķÜO˜ĩŅ•)9ą~ŅžÄ4ßYwG9ÃTžLŧæÄč2ˇ~ēý'ö`ÚūI�<æÚáT-¯į¸…+> : NRŋ׸Væ^- "ŌeÉ#īaáRGKΰŅ=ywŨũĮė8Üˁ!rđRHŠ’<^ėĘ#bœ}}UĨĘĻNК]‚Âũœ"rPxđ EEÚļ†��ŋv8ēgxŒ>åĀž­Ē_ĩŗŲl6W9š‰ˆJ ´äāīÂĢëé$są=Ū ÄÎNĩÛ[Ɔ.ŽuS2<2›ÍMėĖÁY\?ÃcČ\ŲT/��Öiû¸7Ģv­Y†é1nŒĖɖ!}ĘË×l2˜ dãpgŠ…axÍŦģ05˙yđĒ ƒ…�€Ļ´}Ü^H+åĖÖ<Ö D5‡é<†Ą*ŨÃoŗÁ`hķz��XŠí×î+ĢĖÄÔ­°˜ /].%ljxGą3éJŠę#ž(W…¯8�hm÷.nŽ6ę”äĖŽ$īôĻ•šzØ˜Ë ”3 ēōÍŽ3yĨÚUڎÃWĖļm^��+ĩ}Ü;ô×—ÉØ:ö‚•Ô=߉ˆŌךäەkŽ’ã€)“ 7ü}ū§ )ŧWÃ_â3æĘ6/ �€}:TWW[62ãÚO"'aëV��÷Q\ĒņíŅŨ˛ą×-Ō��  î�Xq�Ā ˆ{��V@Ü�°â�€÷��Ŧ€¸�`Ä=��+X÷Ī<ƒˇ �€öSũg5‡ąü6ƖGļŊmåmŖÅÃ�āĄÜ6™lŸåZ<Üō¸;wšĨ˙Ũh2U˙iá]w�� %Ē˙Ŧ6šĖˇôŋ‹ģX<‰åˇH#"“šJ]rŖōļŅdÆ}ę�Ú ‡ąą}–+vîbÍbŽUq��O œn�`Ä=��+ î�Xq�Ā ˆ{��V@Ü�°â�€÷��Ŧ€¸�`Ä=��+ î�Xq�Ā ˆ{��V@Ü�°â�€÷��Ŧ€¸�`Ä=��+ î�Xq�Ā ÖÄŊņčt…ÄŨj’ūŽfÍŪą^A ĶŦĢ ��Z•õG÷åIŸŽO5ļB)��ĐvŦ{ûĀ!AúÃKãU­R ��´Ģîí‡FGʔqKvĒ›é Iß4=,°—BâîéĶ/4jKzŨŌOÖÂ~Šąņéûį†öRČŧ‚C—ŸÖhŌ7MõëĨõ ÚS÷oÍŲ-3BúųËÜ>ÂæėÎŅ7ŗ+��hŽĩqo$Žtėüˇ…7Ž=ŨT köĪ›˛F%ųā̃įž;ŋ@–9įxmG.הđENHlō™įWɔ Ÿ„NúšŸūCÚņΊå+jˆČ˜ą|ü„Øëž â’ŋ;)ÉX>>ę`ą•e�°Mk\™Ãõųā“átbÅÆĖ{—đųƒ—LÚēx”B"K|CŪ——§žËĒßl”ŋõAK$ čKåä˙ˇ‰r>W<Pl*ČTéOoÜ] \ģ,ÄG,ų†.^j.nov+Ô �Ā"­s!&?hæĮå;}­lŧ…ËįjĪÆF†žė×/ȧWčę,2Mõ›Å7~}G‰åŨęÆqųd4‰TéŲĻn}ü%õúĘ8ĒŦ MĢ�Ā6­4pÔ'“ˇŋšuéÁáņ šYKĮßÉžlÉŧ>{.i÷OšąÉ ¸5˙á6nחéúö1žÛījuĶ뉄­T;�� ´VÜIß]6vīČĩ+OųøŨiĖ9tT%ĩīĶQŠšĮÚōō‡œ–oĪĨnƒŋˆ›&iĐČå ÅÖÖ �Ā*­÷Äõœ˙úņČÕqÜ;‡Ũz“‘"ûÚGƜ“§TD’ĻĮ7MâįË9Ŧ.ˇ—JëfÕĢIÄoĨĸ�XĸUoĸĀøqdoõĄÃiõ‹ķrONĶJF™öuÔĸbO?ŽI•Ąoņី'„ 2Ö~´:)G­Ņ(3-|'$dæ!,Ũ�<”VžgŽxėŧ‰˛…׊į&ĪôŌ ąksÅ|ēlĘp‰jëØwžhņĨ5Ü>1 [BíĪ.üR˙I[•ōųņF`Ũ�āĄt¨ŽŽ~Ô5��@›Ã1�Xq�Ā ˆ{��V@Ü�°â�€÷��Ŧ€¸�`Ä=��+ î�Xq�Ā ˆ{��V@Ü�°â�€÷��Ŧ`ÕˇY™ĖUę’•ˇ&sUk��pÛgšbį.ÆōĐļü~÷&sUîĪŋ8uqėÄīhM��p&sUŲMĻė–ė˙žŗ8o-{UáõŽvŧ.Ž,��ĨäFYåmŖÄĩ›eÃ-_ģ˙ŊĸŌą3ž!� 8vv¨ŧŨâ/úž‡åq˙ĮŸūåœé�h'ÆÆšĨČk��V@Ü�°â�€÷��Ŧ€¸�`Ä=��+ î�XĄÍo~piŨ¸E)æ&7yGn^õŠf˔čWƍ{ŪŌ=˜Îޘ´*ũÎ.žPŌ3øíqožØÅŌ)�ž:m÷˛ˇæ¯|ĩæ×âëžē ֝CDDvĸNDšÖŲ°˙ܨū‰ˆLęK‡÷î^Sûy¸´uf�xâĩyÜw{ž Žų•÷ƒqē<÷ĸ—gëīÆŽ›ÄËĶĨæw/…7įú˜ØŦÔáRqëī �āIô˜ÜÉŌđãĩŸĖR›™.îũgĖŽxąsMû­KļÆË.ēeæŸë:yĘkĪwlÉ| ÃĶ‘S÷Фš°cëΔėĸ[ÄéÔÍû•ąSĮ)ēŅoWÄíHūĄ@SAŧ.n>CĮEŒņętŸv�€'ÔcqĒļ"-!ņVŋYĢVÆÎ{ŗkÁÉU;2MDDĻܯbíÕxLœˇeũŌ0Qîļ˜Uß=pņĮô[Áãöfw xŗOíÚ}ÅĨÍ1Kŋ3÷Š\ˇmũĘiž7˙Ŋ2ú̟LDTņßĪWėWģŊģōķØíëįOpģž3fSŌÍæÛ�žXÅŅũīvÁ3&õëJDbáčžGüō+)\*.íüöēä­u‘""ęúʤųŲķ&)_ijEūģ§ŽØ]˙¨ŗû°š{˙ĩæÁÍKGR4ãMč)$ĸŽū3^ˊü÷‰Æ}Đ÷Æ˙ԆNŊô–v!"a×iķÅnuļ#*nĻ�ā‰õXÄ}g÷î]kåtėdG&"RgĢĖ /Q]/ŽĖ÷9æÛŸķn’´ķ=Sˆ‚Ŗgw!"2›o^ŋplôŦ_Ļ.?TLTü“Ę, rÖ÷uéņsđ—ÜÔW¤č%üæÄēĶÁ}Ŋ<_pë$õčDDÔ\;�Ā빈{§ŠÖŠ iŽĖ ;rWkˇß+ˆî{F(v{žöT­[wYĪįhÆŧí{/ĪîÍŠ¨ø˜†ĮæŽ]G2›*ˆ8žSW-u9x$éčÖ“Û ŒĐ3xܤЁ"Nsí­÷”�ÚŲc÷Mŗŗã°īŧyŖE 9œÎ-ēšž›¤ SĄžū+‘‹]G2ßŦ¸ŗÍTQņ;ņjß�:w:éŖĄ“čwuö…c qëWrēÄNõhž�āÉôXœĒmšØĶƒšõk…‹XTûĶ™ÃąļčĘú%Wmfē;‘[w ŖÉÍŋsގčę/fŪķ’.dēņĶ…ËÅ&""ę(ö4ql/æ–ǏĸšöVŠ��íæq>ēqø+ĸw|ļŨîŨĄîL7˛oŪzŽķä-‹ú˙õŪÎ×UWŗkŽ1UhŽ|ģ˙ˆF8$ęŎDd÷âčW„ķölÚ'šÔߍķ{ū‰ ßj$oDŊĀ!R'ž2ÛcܤˇũŸīHˇŠÎŸŧBÂ`7ģfÛ�žXqÜį…i‹ĸíâ7Įže ^7™˙ģK'5•õD¤9ģ*úlͯ OØÕÍsĘŌąÃŊj?ŊûÂÄEŅĖÖíąķâo‘đš^o͟2ōyõœ3í븃›fmĶ™^‘gđėųoģĮ­év�€'W‡ęęjËFf\ûɡG÷Ö­��îÚā}Œ×î� õ î�Xq�Ā ˆ{��V@Ü�°â�€÷��Ŧ€¸�`Ä=��+X÷Ī<ķĖū؊Ĩ��Ā}˜ĖUÆō;ßX÷övļ7´øB?�€vRvSgû,×âá–ĮŊؚˍ˛[ĨšßLæ*‹'�€2™ĢJ5ŋŨ(ģ%vnŅ7~4Éō[¤ÕT .šQyۈÄ�h;ÆÆöYŽØš‹5‹9VÅ=��<)pe��+ î�žxŽW�� �IDATXq�Ā ˆ{��V@Ü�°â�€÷��Ŧ€¸�`Ä=��+ î�Xq�Ā ˆ{��V@Ü�°â�€÷��Ŧ€¸�`Ä=��+ î�Xq�Ā ˆ{��V°&îG§+$îž÷ūČϟ6ļZ… Ĩ/ėį˛EuĪīJ]$öĨō!7�<‰lŦ@4|õĘáâF×Úy›ä6rÁō ‰ Mæ�xĒY÷önžū~ŌÖ(Ĩ„ž!#ÚiW��O—ö\ģĪZØO16>}˙ܰĀ^ ™WpčōĶMúĻéĄ~Ŋ˛~ĄQģsj—€4é›Ļ‡öRHÜ=}ú…FmI××Îвœf‡iߞâãå)ë2uËyÍ]ŖšÛ”ž°Ÿblüųm“‚e^Söë‰Hsvˌ~ū2w…Ī€°9ģsęįפ}5:ØĮËSâå8zöĻ4ÍũÛ�ÚM+ÄŊą‘æ{ršĻė„/rBb“Č<ŋJĻLø$tŌ×ÜČøôŌŽGpN-_yTCDšũķĻŦQI>øęāšīŽÅ/eĮFÎ9Žo~ÖFî3\ŗfäf•,ú_Iį÷­ŠŨē1ųNP7ŋ‰ËåšÔ{ž8į7wâŧÁ|cÆōņb¯û.ˆKūîx|¤$cųø¨ƒÅDDú §Ą”Īßũī¤sGâ>–lœ´`ŋĻųv�€öTmšÛßŧįãÖ]~ĪĪ˜ė&ûgÆŧ,÷xī¸Žæ‘î`DwyžĖڍ?Į ė¸ ĩēēúļŽHųsŅíēQę¯FÉ_øø\uuuuuڂžōĄ›•÷ü~WUÍŋągDwŸˆēēMŲĢ^•ģŊ÷ķũ7UgÆŧ,w•PT[öņˆ ÷{ûBô@ˇW×]ĢŽŽÎ^7°û˜ŒúJn\ËČžqģųv�€vdõÚŊdĖÆ•#î:UËĩKj~Ģ?Úįrš\.—ˆH,qã×vãs9$–wĢÅåSmg>WģíĘ9Y*ĩŪd4åD"S‹ j~¸*KM‚7äüúŌũeö›kV†îŗ‰ˆˆDrŸÚį¨JĪ6u{Ã_Rŋ;ß@gOV††<%ũE[wΌ0Fŧ5ČŋwšĐS!$"jŽ� Y÷‘§Â§éSĩį–øM>\NDDö#ļd­âßĩ•[ķŸ{.á1f-7~'gø˛%ķúHėš¤Ũ?}䯖×sŸázŖž¸Ν ¸üēŊßgSÍc~Ũ6}š‘ŽoãšũŽŊēéõDBŋč}ģ%q_HX˛ëĶrލ÷¨G‡H¸ÜfÚ[ū¤��ŦfuÜ߇âũŨûŪĒYĘį ܈ Z4*įĐQ•`ÔžOG)jkËËf§÷ÎįrɨŊķīŖ^¯đĻFøö\ę6ø‹¸i’\ްæā_čķö‚Īß^@zeúŠ„•KgNኒŖ͡�´—ļŧ2‡/ōTøø*||>žbūƒû×ЛŒ$Ų×>2æœ<Ĩ"jųZÎ}†‹dRŌ*ī\G“›–U7ī}65"ņķåhÕåöRФöGČåōE|"Ŗ:ëÔ9UÍÛ_ę7jÁĖūmļJß\{‹Ÿ�@+°:îË 2Ν?{÷Ojša&÷ņääH8­Ôh”i_G-*öôã˜ÔYú–}P÷>ÃÅGúĐšØ%;ĶTjeÖŅEëĪëŪîŗŠūĀ Ą‚Œĩ­NĘQk4ĘĖC ß ™yHCDĒŊ §O‰Š?Ÿ­Ö¨Õ9gãw¤’ČWÎoļ� YŊ˜S|øãɇ7r†lžēn°e ‡/‹ÉŠZ;{Đø ũ8æĶÁšŲ͎Ž}‡~`ŨđYooX§šˇbõ¤×—rŨúŒērjrÍZ¨ųMpûÄ$lá¯XŊxü6m9ŲģųĪ_0BHDA‹ãcV,[úŠÖÄąIzÚ÷œ¸ōĻÛ�ÚS‡ęęęG]��´9Ü�€÷��Ŧ€¸�`Ä=��+ î�Xq�Ā ˆ{��V@Ü�°â�€÷��Ŧ€¸�`Ä=��+ î�Xq�Ā VŨīŪdŽR—ܨŧm4™ĢZĢ �€ĮÜļ„íŧĮŋvî$îæ:l¨cįNObyܛĖUš?˙ō×N|;¯C‹§�xōÄŽŽiĪŨ•Ũŧ•vņōĒ ˙œ;sēʼnoųbŽēäFįN|‡AÖ�´)ĮΝ†~åå€>Žœ°xËãū÷ŠJ‡ąx8��<˙Ū=‹KJ-nyÜ˙ņįŸ8Ŧ�h7Ž;ũvķ–ÅÃqe��+ î�Xq�Ā ˆ{��V@Ü�°â�€Ŧē‰��܇šŧäŋhõMŨeÆļŗøųį:ķČ\vũĶMČoûbÚ!îķf­W˙=f¨SÛī̆Y—wéø.\É+Ōę+Í6ļ™o`ČŪŽŧ– ołÛęšįîøxĨę•‹_snnVk˙?b€'€ūęÁģŽVvđ› ZÛįGF  ¤/˙™ĸe$Cĸ&õˇņįVŸēŖ{sŅ™-›‹ũ‡ŧ2y˜›ŗ€Į˜õ%Ēü´˙[šđüđ#ģâ“Ā­Žk@X„ˇS;�<AŠ’Žæ:†Îœūĸ}3ĖęĖĢz‰L|)åĒ“Ÿ%<;w˛¯c[ôtÅŊščøšØĮŅŅËz;×§:ãčęÕ×Õëŀ¤ØĨ_ėr^áŨĸc|h9Š_ßG]Āãæ÷˛ōNŨ]›Íú_/úæÚ­˛Ûō°ˆ!'v&ũfßŅļ j¯¸¯,ģŧgWâ÷Ē3ĪŲ땈‰¯Éj2×\vųĀÎÃéų%zbøN˛—F„ė! ""]Ūŋw8Ĩ°Ė@ļ×FŽņp¸O;‘Yy`ë)ÛŅS{ Ėe—ėLü>ŋÄĀsõ1ų%Õē-•“×O Īüûáī_÷äHD¤ģ–¸mߙŧ23ĪÉ{ȘÁúë2ŅËF¸6.]wåÄŽÄäüŊ™qté9$<üeŠæŽVö¯[žĐ[9{—R1íŸ3 ‘*aÖúÂ!KĸÕOŌ’ūdūc—ÍÛkgöu¨eHûüã­ú+>(¨­öW3¯Ģwđ˙æ^įûtköĨV%ĖZ_2$* čĐáŒ"ŲÁõåŅQCx);öĘ+50N=‡EL~Ų…!"]ūņ‡Îäi+Ģx|ąw𘈥î<ĸģsjf›5¸ėpbz‘Ö@ŌžSÆx;4Q,�[™ŊtčHIGۛš74ę/“†ŧ7cf×ŋÚˇõh;]™Ŗũ~ß) œüɘ}ōļ-Ķ@Dd¸˛cũgßWyOœģbŨ’yãÜu˙‰]ˇGe&"ÃÅ„Ī•¸ŒžˇlųÚőŖ]JoHHŅ5ßNDēK‡ŋį…Œë+ ]ÚļU[¯:˙pų?×Ī-¸°i[–ÁÕŨ•qčŲĪ­$3ß@D¤;ˇéxŠĶč9 WĖ#ËۗŽ73ļ÷,ô˜•{Ö¯ûĻLšbå’Ųܔ{ÖoúžŒČĨ§Äļ$¯ĀPĶIu­Īį]+ŦT’¯48Ę<ūŗŦ%ũ˙Īŋŋ;å]¸ŦĢdČOË#i˙¤K‰ÛtŧÔ%|ÎōĩŸLĐ{8ÃĐÔË|ŸnÍŋÔD SU˜|ŦĐoڊØĪ×N—$',ũĮifØŦąb‚™Ë{ĶtD¤KŲöYbŠĶđŽ]ų÷Ųa.…ßlښŪD SUx2ņ˛$<fũ†/WEČ´ßm:pÍ܂˙I�Xĸ,ķč‘ÂŽŒōŌeM‘}cÛg=ĩ[ÜA“ÃzK\ŊFD Ž^¸b ŌeJ/“‹íå"pptõņrגīŋģb&ŌĒK*dũŽGŗÄ\dôœŪŧæÛ‰tW/(]ƒüdÎ;ļ+Ãqøûoûģ:đxŽŪ#_—’^ qs â99ōtĨZ"ŌfĨä‘÷°đR'ŗûāŠ#¤æĘĻęÎ<üŸ_]‡MŽđ“ŽŌ—Â#^â]9yސНŠō•f""åÕŋž2sAn ‘A•_Âs÷žëŸ -ęĪķ ęɨRŌËjwžw!—z đu ]֙<ō ut¸ô Ũ“×ԙūûtģĪKMDDf—Āá^ ‘ƒ—BJ•äņę`Wãėë#¨*U–¯į¸…+> : NRŋ׸Væ^-hōÛėîįÄ‘ƒ"ƒo(*Ōļô˙€§\YæžeŲP]Ö˙ĩ×čBģˇĪ‰¯vŠ{ÄMP÷ģŗĢ¯Ē´PKTĒ*Ŧr”Jî;{¸0•EJ-‘soĮ˛3që’.^)ԙÉÁUęâĀ4ßNT˜W*čáæ@¤LĪ2xÔīĖdļu•Ô]4Rs_VPBŽŽ.uK Œ{OISëZ%ų…UŽ2ú NŠ—˜)-Pęˆ'éájVå–Qé•<’zy;—åĒ DfåÕÆŖ‡ôî™Zԟé1Ā§üīÅ""sî÷ųäÕˇ'¨´@KÎ.õo˙N2—ĻVųîĶí>/u͐ŗ¯ūõą!K]O†á‘Ųl&"†Įč¯|ŗiéÂĖúxzä˛DUí†{98‹ë‹`x ™+qt@DúĢŋ<ŠūõÚĨ+š*"úĢΈ¨ˇäív‘C;­Ũķø ūĨÂØ2Te6* dãĐp cËŖ*ŗˆq˙äcį“ßĻ$ī:ŗˇ’qtîįÄ4×NƒÎāāÁ'2hK .]īĖZxMiv,aˆČPZfpt‘Ál&ĻÁŽžĘîŠģ˛ŌLe§VL;uWkWƒČŲ]&8¤TéČ!_ŠHĨ~b^ų%ū•Y:ĖŊņ琠%ũY˙žÎg/¤ž6ÚųZZĶsF†ˆ fŲ8ܙ‘axM]_tŸn÷yŠŠŠƒî]ÖRíZŗū Ķ7bÜ™“-Cú”/–nĸˆšÉ›Ų�Āf•WöœŦ:#˛ëÕŨëũlĶcôô!‚ĘrĸæÎæļļvŠ{ƒžÁ᝚ŌL6 ¨Ę–GUē‰c6TČļ6•$ÂŪF†’üËÉûvÅÅ2ŽËÃĨ͡™ëžSƒƒN]Ú7J…ŗé.§ĢĨ~î 1 Cæģ6tM­†ÛÚ2äđ~dHà ĘÆA@D.Ūŧ3y:‡k…w)œŊ$´#ŋDËWę\ü=î]ˆkYמ.§S.…H.]áŊ8ۃ!"â1 Uéî<)ŗÁĐTĩ÷éÆģīKŨ…ŌJųķ#j˙Ųĸ7ˆÚôĒ1€§MUå͟œųß;Ģė{Œ˜ū|ÁWą?ö™3EŅNĮGí´˜Ŗ+*¨?YRXj°q’ ˆ\$Ž6eJ՝ƒę’ŧ"ŗ­‹Ģ€ĖZÕåĢĨ5ÁÅsvámŖ+,54×NÄ´EĨD<™—‹áęwiZŗYW”ļãĢÆÍ1 eRBbŲ‹ÃũˆˆœÄ*+,­Ë?sūeUSĢáÎîRÖ`ëėėTûãĀ0<Įš”zš“ęZJ†š'qw&"OW]ū•ôk%w™ ‰ÉZÖß) ŋD›qîđ÷×üúÖFĢŖØ™t%Eõi]”Ûdĩ÷éÖüKŨR•Ufâ ęŪĖ…—.—ÖŋÁ@îIņg;?[uíčŋTŪSÂ}ģ2ˇË+›ú‹ÜfÚ)s Ių%:VyzW˛ÚÁˇ¯Œ!âų„ŧä¨ü&áøÕR­ŽŦ0cßÖ˙”šžüŠŒ!*=ŸđyėϤk…ZV[t%ét.9J]xÍļšúē›3.äšIđrD„¤t×ÂČéK.ģ oÜkū×6ÍZ°õĒËäĮHkū=\čĘ7‰i…eē’üS[Ž)™ĻVÃyŠÁ/9(|•˜Q¤ÕéJ”Ö,^´åBÍ[ãŅCĒË:uÕāęåBDēČE§’ x>÷\ÍųũüŊuįO]u čįRÛ$PHčĘ7ģÎä•jKTi;_1ߊļä?›–Ž9¤ŧˇûŧÔ-äâæjŖNIÎ,ŅéJōNoúW™Ģ‡šŦ@i@ä4ÉŠģĢ6%)ŖāÆÍ˛ßn–ũvŗŦŌiȤ QS'Lã˙lĘ5—ΧjœēwkŋĨĪöXĖ1›mœ‡ņ/=ļnĄJkæšzŽ§āņdaŗĸ˜‰Û–%ę‰įčâ=,*|¨ CD^áŗĮíÛu2aé^ŊŲÆVāė05j¸+1Ôt;1^¯†8ŦJØŖˆ×#`ꀄ/ŪNfŗ™i°Ļė4øũˆ’¸Ã[cß­į°ŅŖ6ĻēˇvF6nVo_âŽõ§ô•dÛUę;fvXŨuņ<woWũ•쎤f^G™„—xÖėīërīDҟ×Ã_bsĨ*Č˙έL™\˛=1qÃßw1]e/ ißgĩ—™ËÔJ%Ŗ{@ˇæ_ęrč1Ž`́­ķĪ’ƒ¤÷čw"zęm ?˙våŠ^ÜŖåĶ�°Fį€1cŨwtĶÚ[M]öGļŸī>z@—ö+¨Cuuĩe#3Žũ$rļn5ÖŌen]“ë4zXˇ´fÉÅl()ĘŊzáĖŲ,˛`öK >íc6ˆWw.͐ļáã­ąqfīG˙‘[ŨÅu w1—Gų>úZ�ā^KW­]ķHvųņ"‹wũtŨDÁA1yąĶå¤c§ļ/ÛTjfl‰*̈ßUęĨ˜°Ā_Úđ“egÖ,Ūeč1áU™ƒY{õÛÄkŒ÷”8_ ē’’‚3{wå:ŋƒŦ€VõtÅ=1N=‡Nî9”Čl6ĖÄã5yÉ"‘ã€÷ß3ė8”øŋë*mxŽ’žīĖ ÷{Ä Ģũ~ķüŊE’žīMië{X�ë<uq_ax÷]švč2ŖGH{•Ķ‚Asã=¸�€đmV��Ŧ€¸�`Ä=��+ î�Xq�Ā ˆ{��V°<îŸyæ™j˛đš��đ°ĘnŪęÜÉō/ĩ<îííloß6Y<��JÚÅËân–Ķō¸;wšĨ˙Ũh4YzĶ��h‘˛›ˇN&9“raä›C-žÄō[¤‘É\Ĩ.šQyÛh2ˇë]›�Ąm ;Úy;9ˆģ9|s¨cįNObUÜ�Ā“Wæ��°â�€÷��Ŧ€¸�`Ä=��+ î�Xq�Ā ˆ{��V@Ü�°â�€÷��Ŧ€¸�`Ä=��+ î�Xq�Ā ˆ{��V@Ü�°â�€÷��Ŧ€¸�`kâŪxtēBâîyįĮË?pôėMįŠë:¤/ėį˛EÕ e�€ulŦ@4|ãĘáB""2ę Î%|ąfrDņŽãËüšDn#,’î?fwDHæäôUÖV��Íŗ:îíŨ<ũũ¤ĩüúÉčĩą;Nė?”OBߐoĖÎĖ5Z[��<@k¯Ũse 9Į¤.Ö5^Ė1ŸZ;%¤ŸBæŽđ6{[šžHŗķß ‡ĘËM•¸‡ŦÎ9?§—gh|ũZĨÎõ— ûZ];•blüųm“‚e^Söë‰Hsvˌ~ū2w…Ī€°9ģsô­üL��ž*­~ĒļX­6q„aãvcęōˆ¨ƒôúĘŨĮÄ},Ī]=iĘ6ĨpÔ?Ė‘‘ũĐĩi—v ŋĪ´\.פŪķÅ9ŋųģį æ3–Ÿ{ŨwA\ōwĮã#%ËĮG,žĪx��–ŗz1§Ŗ^•ē{Éæ\ÁāȁüFÛô'6ÔúÆė~/HHDԘOõƯĩÅzŽ”Īåqí…üÆ#îUl?t÷ԁb"ŌŸØ¸ģ@ylYˆ„ˆÄâÅË2ĶÃãöf‡ÎōlÅį�đą:îs7 rßpįĄŊÛ %[– ē'ģs˛˛M‚7äuũ\Ÿ‰˙XGDDå-ߕHî#ŽųM•žmęö†ŋ¤n ×7PÆŲ“•Ą!Ī{ūY��Ô q/ŗyÃ[5)Ėå„RaĶGéÆr#qĢvÅå׍חéúö1žÛīÚîĻ×!î�šbuÜsDRš\úĀn|{.ĩq(ODdlî’ž=—ē ū"nš¤A#—+?Üü��ėŅ^ŸĒ•øyrŽgd֟MÍÚôN؝“ĢĩąÎårI¯­ĪøbĨĒ™÷‰Ÿ/GĢ.ˇ—J%ĩ?B.—/zđō?��[ĩWÜķN é–ûÉę¤Ŧėœôũ‹–lĖ$…ˆČžĪĨrUĘŲ•ZīϐsTįgč‰HŸŋâ€ļ™ÕūĀ Ą‚Œĩ­NĘQk4ĘĖC ß ™yHĶNO�āÉĶn÷Ėáö‰‰ÛĘ=ĩ8âõ7§ŦÎėöÁWąĨDÄī1FRŧcÂčņ›s„o|˛x$ÛOáĶoėjí[‡Š8M¯čpûÄ$l ĩ?ģx|đKũC&mUĘįĮou{�€æt¨ŽŽ~Ô5��@›Ã1�Xq�Ā ˆ{��V@Ü�°â�€÷��Ŧ€¸�`Ä=��+ î�Xq�Ā ˆ{��V@Ü�°â�€÷��Ŧ`՗šĖUę’•ˇ&sUk��pÛgšbį.ÆōĐļü~÷&sUîĪŋ8uqėÄīhM��Öȸö“ČéiūrŖ:üå/Ȍæe7e˙÷œÅykųbŽēä†SĮ.Žõ��m§ēēēĒęæ/]Ô%7,žĮō¸˙ŊĸŌą3ž � =üųg5—Ë­ŧŨÔךļŒåq˙ĮŸūåœé�h'Tũa͉Rä5�Ā“áOëžZq�Ā ˆ{��V@Ü�°â�€÷��Ŧ€¸�`…vø@ŦéėŠIĢŌÍõžPŌ3øíqožØĨíw�P˜öųĖM™wŽ[gl]Ŋ‡|Í[đĢj?íu˙a˙šQũ;™*ԗīŨŊ(Ļ"öķpi;í�€ˆˆûž7ą¯‘ŲP’yę›ÃëūQŗl„ëŖŽĢ´WÜÛu“xyēÔüîĨđæ\›õƒ:\*n§ũ�ņēēz¸;×üîŅCÆü:}ûĩ+%#\mYíáŨŨŒab:rjdĮNXĄŨ7ãĢíWģÍØ>Ŗš°cëΔėĸ[ÄéÔÍû•ąSĮ)ēŌOąSĸķ^]÷ųHŅͤšŋĘõ›8ŋ7‡ˆč§Ø 1ĒĐõŪūvõH܎ä 4Äëâæ3t\įNDDtëԁ­ņĮ˛‹n™9Âįú†NžōÚķÍķ€ĮcÃÃcjä'ĖúŦdČ{=¯îJĖëą>*€)ģ|`įáôü=1|'ŲK#ÂGö*aîje˙ŋĮ u""Ō[9{—R1íŸ3 ‘*aÖúÂ!Kĸ9ęōūŊëĀų+…e˛¸ö0rtˆ‡鎜ؕ˜œ_ĸ73Ž.=‡„‡ŋėÂkûįÚū§jMŋü7novĮ€7ûÔŽŨ3ÆüëˇûčņîĒUīöĩ̏´9féwæ^‘Kãļ­_9ÍķæŋWFõ“‰žëëÁ+ēöķī5ŗägŠ:9t,ČRÕĖĄÎÎĢžā%¤Š˙~žbŋÚíŨ•ŸĮn_?‚Ûõ1›’n‘)÷̘E{5įĮmYŋ4L”ģ-fÕwšvú�đ˜0ë /îú&Ÿ×û՞ĩk÷6 SĨ={ėŠĮčyŸŒéÉ3\ŲąūŗīĢŧ'Î]ąnÉŧqîē˙ÄŽÛŖ2“KO‰mI^ĄfÕĩB>ŸWt­°fŽ’|ĨÁQæáH†‹ Ÿ+q=oŲōĩ‹#Gģ”ېĸ#"ŗrĪúuߔIÃ"WŦ\2{˜“rĪúMߗĩÃn¯Ŗû˙íž:bwũŖÎîÃæNėũ×ÛoØõ[5˛wW"ēyöHŠÆcÜĸ =…DÔÕ?bÆkY‘˙>ņøŧũēĶæė\S˙9”›ņSį€ū’”ŦÕ$ĶīųŲEvžS܈ ū§6tę5 ˇ´  ģN›/pĢŗQÅĨß^—ŧĩ.2PDD]_™4#?{ŪÁ$å+8�Ā&E‡įO>\˙ČAōę{a ‡Ûĩŧį UˆHwáTz™täŦŅ^ŽDDžc"^žļč?ß]9YæëF;ō•æžŪ )¯8øõuMŋ–[BRg2¨ōKxîáŽD…ę’Jī~ W9 ÆE:÷Ķ;đˆ ™‡˙ķĢë°ŋGø9‘@ĄĘ_yō\áKm~ū Ŋâ^=;¸ ‘Ų|ķú…cûŖgũ2uéüĄuk÷]Üēw­ų­ø'•Yä~įË \z<Įü%÷õõ𑘓¯Ķ‹nÅ?\%ŲÄAŪÉįō+ƈ™ÜŒŸ8^Á2")z ŋ9ą.ÆôFp_/ĪÜ:I=:dĢĖ /QŨŦ™īsˎ?įŨ$iįvz �āŅs Œš$ "2›õŋ^N:ļnIQøœ¨uk÷ˇÚcũRUa•ŖŋÄą~¨ŗ‡ s˛HŠĨž’Žæsš%äíZz%¤aA˛ĸsi*Cˆ3ŖŧZĀxI‰Čš‡ˇãˇgâ֛ƒ{z¸Ë\\ĨDD…ų…UŽūNuŗ2R/1sļ@Š#׆o;m Ŋ➊Ũž¯=UëÖ]Öķ9š1oûŪ‹ÁŗkVŪ‰cWģxFŋĶŲîÎPĮŽ#™MDbĪēėÎÍģE˛so>ė!”ytŠŋú“é•N?ä™eaž"âxN]ĩÔå⑤Ŗ[On30BĪāq“ĻŠ8&Ō™v䎲ēũ^A„¸`ÆŅŲÕĨ.Û%R/1-\žøMfĀԚ•÷; ųd¨4Cƒ5u†ąåQ•Ų@$u— )U:rČWęÄGŠ„Ÿ˜W`~‰Ee–sgˆˆq˙äcį“ßĻ$ī:ŗˇ’qtîįÄTVšŠėԊi§î*ĢĢÁ@ô”Ä}cŨ$]˜ õõ_‰\mąŗëHæ›wLŋ¯ŗ=ßˋwâÚOŋuÎRuņ”Ų‘‹owڜ]tŖSî­įú{ÕŊEtî>tŌGC'Ņīęė ĮâÖ¯ät‰jgĮ!aßyķF‹ė‹ÃéŒk˙XÍÉÕŅÆPZĒ%j|mĪ–GU:ÝŗĄŌ@ļ<"rņöāÉ+Đ9\+¸KyU*õ��÷IDATäė%Ąų%ZžRįâīQ÷á öŪ€02”ä_NŪˇ+.–q\nkːcĀû‘!N öÅ0mí˙ŖúTí/šj3ĶEØÄĩ[w ŖÉÍŋsĩčę/fŪķ’.DD2_OĘËJNûÅÎÃĶ…ˆÜ|$7ŗ8ŸĨîâéŨ…ˆČtã§ —‹MDDÔQė9hâØ^Ė-Uq‰==˜[ŋVØšˆEĩ?9;!ŽĖ`ˇ"eiãčØÄĩ‹ÄÕĻLŠēsĩ$¯Člëâ* "’zš“ęZJ†š'qw&"OW]ū•ôk%wYÍR‘VuųjiÍįKyÎîa#ŧmt…Ĩrv—Úč´[gg§Ú†á9>EWæT\W]ÍūņjöWŗ/ĨŨķŲ0ø›H[ģGŋ"ĖÛŗißåâ_oj”i žÕH^ō‡ˆˆãĨŨēx$ŖBâû‘]wī.ŋ9ö“WīÚ3ŽęäĪWŽ\y4SyãÖ¯7ūwéčÉ+$”šŲ‘Ũ‹Ã_é”ģãŗíi˙ûõæ­ĸŧŗą gÍXwöˇvzū�đx0üZ˜—Ÿ›—Ÿ›—%ãB↯N•9 R4‘ļ<Ÿ—•ß$ŋZĒՕfėÛúŸ2ח_‘1DDŒGŠ.ëÔUƒĢ— O"J.āyøÔžq-=ŸđyėϤk…ZV[t%ét.9J]xÄS ~ÉAyāĢČ"­NWĸŧ°fņĸ-tm˙ÔÛk1GsvUôؚ_ž°Ģ›į”Ĩc‡{qšęj÷ÂÄEŅĖÖíąķâo‘đš^o͟2ōųÚŽvžŊÜt?ä{žč^Ķ |Áƒ˙­9Čīššíœž“bĻ}wpĶŦm:3Ãë"ō ž=˙m7"âŧ0mQ´]Büæ˜Ãˇ Äë&ķwé¤ūmĒ�xj•]Ø´æBͯŒ­ŖĀÕ=|ÎđÁLS]y˛°YQĖÎÄmËõÄstņ>ÔĨļ+ĪŨÛUEåî-Šip”Ix‰gÍūžĩë͌WøėqûvLXēWoļą8ģLîJDŒlÜŦ(ŪžÄëOé+ÉļĢÔwĖė°žmŧnODÔĄÚŌoÃʸö“oî­[ �ĀÃʸö“ČIøā~O…âRÅÁ‹;b�°â�€÷��Ŧ€¸�`Ä=��+ î�Xq�Ā ˆ{��V@Ü�<žéĐÁĒᖏ|æ™?ūü͚}�@ËũÅæ/Æō;ßX÷övļ7´7-��-÷Ė3ŒFŖíŗ\Ëg°x¤ØšË˛[ĨšßLæ*‹'�€ûëĐĄƒÍ_lĒūøķWí-ąŗåßŅaų-ԈČdŽR—ܨŧmDâ�´ccû,WėÜŚÅĢâ��ž¸2�€÷��Ŧ€¸�`Ä=��+ î�Xq�Ā ˆ{��V@Ü�°â�€÷��Ŧ€¸�`Ä=��+ î�Xq�Ā ˆ{��V@Ü�°â�€÷��Ŧ€¸�`Ä=��+XūĨæ54i_¯ŽŨ{6§X[nâØ ¤ō€đæŊ­āˇJq��ĐZ:TWW[<Xɧų#Ū˙ ´ˇX@ÆâÜcqļg æ$|OۊE�€ĩŦ9ē7Ļî9Q,›ĩ{Õģ⚊Ü×_`Ŋ23SErIĢÔ��­Âšĩ{ŖŪHDFcÃ6nā˛oŽo[—õÆâSk§„ôSČÜ~ÃfoKĶßi_^ÛîĶ/tęÚķęÚ ész)Bˇœß?7,°—Bæ<ũËT}ũ뚺[f„ôķ—š+|„ÍŲSŋE“öeÔč`/O‰—āčŲ›Ō4V</�€§QĩŠvŊëŅŨgā ßf߸ŨÄöÛĸzôũÛ?Īf˙œ–øņPo}õsuuĩî?tû˙öî?(ęûÎãø“ a×DpqIdqˆŦž2'’\$œļʐšĒIĒ\ŧ —¤“´Á4­dŒÕ6ĻbjŽö.N1#vōÍČ=tꏎ‚ÎÉz6BĘ) .°q×pŗ÷Į˛„ ʏ Kž¯Įø‡ûŨĪ÷gwæÅ—Ī~ųî}+69W˙EÃ'‡~ĩä>ûüˇ×ëõVŽŊß>ũž%/~ØāözŨ_|ôbēũŪUû[Ŋ^¯×}zcĻmæ’ĩ:ęëNī}iūĖ”ėŊ ^¯×Ûē˙éû3×}ôI}Cũ˙:ö­[2}æß˙b8+ųļVÜ{Ŋ­' Ÿ?Ķn‹ˇÛfÎĪ|âĨÍ{+ëģ‚ŋĩ${fbÖ^îē;žaķ‘Vī%Ų3íKv6tōɯ3m÷ŊpĐííŒûåÅ]Y]ŋs™mæ÷ĩzŊ­ûŗgÚ3 ĪûŸqŸX7ßļđõOŧ^īš×įĮĪ˙eU×x_|Røįˆˆa ķBĖ¨Ô•o|ôßG>|ë՟>’Õøqa~NÚ?=ũNĩ úĖšëŅ3ė1mM‰Ol}ũgiQ\8sîúÉISēF™–’Ņ^ãđočÄØü}¸3..ōzcu#\8yîúŠsē>0%ĪMˆ¸pĻĒâ|hĘįīŦÎ~ščĀ‘ęf13’ė1Ļá­LDäÛe¸b˜bf¤-™‘ļd´9ūđãg ^yĩtÁÛËb<íLŅ}ÚˇĩˇaŠŽė6€)2 §ŊķaTį"Lx<hk÷đųÎĨ3vöËÖÖ1)ëöŧ÷æī÷îúÅģŋj˜2ûąׯ{8N/"ŌeXqīikn3Åt?ŽJZņüwŪ:úQõyˆ‰Š4áiiīĶ-*2ĒįqO[{[ˇoëņ\{&“ ĸ"MÜą`ۛšŨ/ų1™b|WÅ$._ûÆōĩ´?yhWÁ+̟6Mųh]Ōp'"ō­2ŒÍœ†?üË?<”StĄįŅæķmDO‰âRfD|^åhô?uæw˙šõã’Fė‰=sūdÍõȄ—sŌč¨ičzĒēîz„Í>âR’#ZÚ#§M‹ëüc2EM‰OÙCG/ø.Šš–ōØÚÕE´œģđõ=""rˆ †Ø5Ę6ĩųãŋßSŪ|ËmˇņíMįĢOŧģų᝛ˇvũŠ{nĮtˇõŌˇŸōÄÅMŧūŲ‘˙|u͟ny4ī‰äÉSŦÎÛŪ>aOœ|ۗ Įˇũtˉ˜üō'i1á4–ī(ŊØŪtĄÃ6cjėŲģaséßæžđķEq&ĶŨ1ÎŌ¡+žŧ;ajÍĩ‡ūũGO¯?6éąEv“ã?~đLĄã6Û´čÛ<mŸUŊŋ}įIæ?ķÄ?ÆÜx""1ŦŋĒ…ļĒ’m…ī}\Qũyûu""ī˜–”ōčĶ̟˜ãZĪ…?n)øíū“ZˆNxđßÖŽ_å{ĘĶxhË/^ÛōB ‘SĘzaŨʔ€“/?SõČö\Ī[¯•œiôDÅĨ=õë‚ɝ7eh<˛eĶk%§Îˇ´iKūΊŸ­]–xΕlzåÍōĒ -×#"§ÄÍūįŊôüw§hī^D¤Ë0ã~ĝ|ųœĒė÷¯Ô劈Œ$ŨSDÄ÷""†j›9"":ģ1ÅŊˆˆ!(îED Aq/"bŠ{CP܋ˆ‚â^DÄ÷""† ¸1ÅŊˆˆ!(îED Aq/"bŠ{CP܋ˆ‚â^DÄÂGpŦ‹WX}�Įe.ēFpÔk!i2ŋÉ$vb€gCŧxŸP^ÂĀĩ‰Č¨ą¯7šx…äm¸Ü#2XYĖT=Û;’ÆJņ>Ąŧ„€ĩ‰H(ąÍœŧŖŸ57Ãå&ī@īƒcĨxŸP^BĀÚD$ŒXÜŠŠ‘‚îĖåŪGÆPņ>Ąŧ„žĩ‰H(ą¸ÖŠe4UŠž{ŗ-Ģnĸeß­íQ9/NœKŨzŽ­aĄÕ_yˆ/!`y}Ū PūđCÄČFōŖÚQæ"īhí2nZV ą-$lãĶpÜcĸōąö ‹HwÁŠûÄû)žGlEå$-ÂrûĄ~ZÎĻhI‘8ëÉÛËî–Ũ‹cöĩŗRũÎŖÔēãšu~$(ÜÃę:°°u9$š–ęįp¤âōîÄŲ@Ö.*;‚Sŋ•ęįp¤f2YVœudíĄŌŌYĒkÖãÜ‚ 6 3Y<Ú);Nî1˛ž%"@Íz˛ļ“īĢŧú끧Ú)\@ē§“ŧŨ|0øSæž“6ŦÖh.k ×°ë>č ‘ĐœëîŖ)z˜Xš%¸ī% ¸ÖoËâEXęHŨA•ĸL&øģ[[Xü56|-;pCę,(l!õJ“ŲPB…™ŧLí>ϝ帎˛øVÉAĢß7—Šũ¤ÂrEiūƒÉ¸Ž’_ ã)~œÅŗƒœŗd,¤x6Eģ)n‡Ëdl§Ŧī" ÅKIŊBúvJÃ)^Ös7#ФĢ 8םaĀ7HDB[Pâ~’$(-gW-ĢËčÄÔÅâ­¤îŖ˛ŽŌˉõw/+į`›ËšØŊy-ÛëØzāđ)ÖRÜ�bû\ÍæZĮ “ƒV?�ĢŲŪ@å)ĘŽ‘`c’ŋÔÜSia’Œq”–ŗģŽŨ‡(ŊFúŊ˜[pv@'­} ˆ'õVJOPé¤ā$æģČ?Č%štR j˙ž˙šú{ xƒD$de3Įbpļp'ũ˙žo&'“Ö[Î}áÎîžę̏ēuw^pw�¸Ú\_Ø×’އõ°ęĀåŸËé3_Íx�°Nė6š¯ÍxŦčë’ķ8Yū#I¸:ˆ% 0i¯j§œË ũŋ†ŧA"˛‚÷žĢDŦ‘�LÄJŋ™;'ü{(ÜÁĒ:Ö<KAxīîĻģÉÍ×īcˆ <áXÍāėŨØšŖųÛ8Đ×Ĩ¸„üzIƒÉú'íUíų€sY|Ôß ‚ Ä}S=5°8e¤Ļ æp�ŗ™9ŗÉą�$YØÕŗģ\ã‚Qfŋnž~ë,6ÔRacņ8ĨŠ×hՔ-`ņ<˛;ĀÎâq”îŨĻwĩT|Eú,b¯›BŪDrwQ9¨%ô?i¯j˙2øššFû ‘!ÎGĩNrâ´Rôæs8úoxä8ĨÍä,ĨĐFN 5álÍdB÷î˙3P÷`šéú}gą¤Q<į_É9ŅįéĢdŊKfSh§t?YU7ŅEÖj&S–ËÖģ(;:č+‹˜´wĩC˜kÔß ŧģgNØË=‡3)œ&ˇ˙RÅũÜß7Gwc‡Ŋ‹įĻëĻ*¯ķÚÄo؍—Đ×7Um¯ÚD$ŒØfέ˙ zž$'Yû‰M# ɞKBĪ_ų]ÍV¸.%ØĻöؘîQ<ƒŠŋžQqã%Œžžĩ‰H(ą¸OãƒŽŋr‘[BŅĘrq]Ąp7>ÃķŲHM5\I}ŽËėQ<ƒŠ?:hUčÆK=}k‘P0’7@Nz#TN00Á„ãš�wÅû„ōÖ&"Ą`Äâ¸x…ŧ8.s)$¯Ë›j!i2[û˙nP.Ū'”—0pm"2ęF2îED$déģjED Aq/"bŠ{CP܋ˆ‚â^DÄ÷""† ¸1ÅŊˆˆ!(îED Aq/"bŠ{CP܋ˆ‚â^DÄ÷""† ¸1ÅŊˆˆ!(îED aÄžš\Dd ųķE–Ķü7:ÆÂú…‡s;{˛x0včƒčË EÄpū|‘´ˇcŲį%,ŒŖO =ņĩ™#"†ķũŨc-ë0ŧ^žŋ{č(îEÄpšÚGģ‚Ą ŖåęĐ{+îEÄpÆŪŠŊßp>iP܋ˆ‚â^DÄ÷""† ¸1ÅŊˆˆ!(îED Aq/"bēgŽˆČ@&Ų(˜G†ë8ÜרqRxí Ŗ]Öā)îEDú5!žŠl\§É-įb;æ‰dĨQø$–ílvŽvqƒ¤¸éWúlb/[Â%ßã*ë0¯$õ.kq¯Ŋ{‘~YÂĖŨu°jKNųŽgÍãÔ­Åģ‘+kxo.�Xš’k+:˙ī“ŊīJϰp.Uk¸ļ‘+?aįė͂Gq/"Ō¯ÃÕ¸'Sē”EVLd?BA vģ…Œ$ÍŖh@éi°‘ŅõƒÂL–Ã§šs2)›GÅļQNj&ÅÉßÄZ÷""ũētŠŒƒ`§ô9ÜëŠZÁĻdĻvÛ/ŨGÂ.v5pÉEåY ¤Į4USYņÍ&ēÅÕ`fÃlåŦ:Ë%•UäV‘‘Fbđ×ĸŊ{‘9†ũ‰62ėdØČ”üäîbģ ĩƒœŨEŦs8æqp€Ģ7P0‹ gi…ŒYPGéU¸“¤[)ŽûzüŠZÜ)¤ŽįĖ0nn|3÷""7ŌÁ™ZÎÔ˛&ÜIņrļ>LéšÂų͓ävģÃ͸!g9üJOŗ5“ôp>'+žÃûhÆa†ŧ\ōzÎ`1ƒâ^Dd´˜ĖX:hęøúHkĒŠ°“�MV˛b(*d—˙2|KˇOu›jŠ'ËÆa3é[ Ā5ÜPôÍŨĻéĀé öR÷""ũąpøEĖI>Öãp‚Žâ¤ķTũĸģķ¸ÉĘâčĘņĢÕQ0‹Œp¨ĨĖ×Ŧ…Нˆ5ķi‹ŋ™™ŠĐüÕčŖZ‘~¸Č?IÂBN|e6­<ĪĻĮ)ŧ‡ŌŖ| \Æņ90}<Ķm/ÂQ‡y"süįøegąÄŗ!ž˛* ģŲZEę6Ų™:žéwōģ'q,eRđWæõŽŨ¯ņа—ŅxN2ų)¤[ąÜŠû5õãˇūĪZ§'Sŧ€¤Hœõä@i$ÛBę6Î�fūk dlæ`ˇĄ… (H&!ŽQQMū*ŨæÄģqÅw§¸ÃT܇š!ĮŊ6sDD Aq/"bŠ{CP܋ˆ‚â^DÄ÷""† ¸1ÅŊˆˆ!(îED Aq/"†6Ú Yø02[q/"†3)ÆâícŧDß>ôۊ{1œ÷—6ĪđÃÂxŲĐģ+îEÄpŒåčSX#‡ĩ7ōM ˙;Ŧ‘}Šc‡>ˆîˆ)"bcäG›ˆˆ â^DÄ÷""† ¸1ÅŊˆˆ!(îED Aq/"bŠ{CP܋ˆ‚â^DÄ÷""† ¸1ÅŊˆˆ!(îED Aq/"bŠ{CP܋ˆ‚â^DÄūĪmÂ7ØÜ´+����IENDŽB`‚�������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/profile-update-email-modal.png��������������������������������������0000664�0000000�0000000�00000064421�14156463140�0024466�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR����x���ŦįŨ ���sBITÛáOā���tEXtSoftware�mate-screenshotȖđJ�� �IDATxœėŨw|Såūđor˛›Ņ´é.:é ´”=EAQQ¯(Îģŧ÷Ē×qšW¯ãęũéu+¸ÄÁ”- ›.(-ĨŒ–ŌŌ™ĻMGšŅŒūū”ŌB9iZųŧ_ü‘<į9Īųž“r>9# gųōå��p}¸î.���~ '��ĀÄ ��°�q��,@œ��� '��Ā^oÚÛÛõzŊÕjĩŲlũY��ô.—Ë0ŒD"áp8×9TĪqŌŪŪŪŌŌÂãņÄbņõ/��Ļööv‹ÅŌŌŌ"“ÉŽsoßķÉ.ƒÁĀįķ˛�ā7ŒÃáđų|>Ÿo4¯s¨žãÄbąđųüë��g6›¯sžã×K��nįúwû¸ŗ ��X€8�� N��€Ŋ~î¤;ƒÁ Ķé,‹ëĒ��—âņxRŠT,ŗ?˛“ũ}}}÷ģ߅‡‡ģĸ��p5ƒÁPZZēvíZĩZ­P(ØÜŠ8immõõõ}ūųįņ1�€ÁK,ĮÆÆFGG˙ûß˙njjb÷ØĀŠk'zŊ~ŪŧyČ�€ß�.—;wî\Ŋ^Īō°Ît˛X,QQQė.��Ü%**Šõ áÎŪŲ%Ø]0��¸‹H$bũãę¸Q��X€8�� N��€ˆ��`â��X€8�� N��€ˆ��`â��X€8�� N��€ˆ��`â��X€8�� N��€ˆ��`â��X€8�ĐÚÛÛ×­[wčС˛˛V­ZeĩZûŋĒîqœ Žŋ}Á"wWáOūņЈaąuušŽĮÕ55,ŽÕm{#o|€~ļiĶĻŧŧŧíÛˇwI”ėėė͛7Ÿ>}úûīŋwWmš0NVŦü,bXėˇĢŋëqj\âČ´ŒņŽ[zw~ŧâ|YY.ŅubcĸĮ+đŨ]�¸\||<ŸĪ'ĸΉ’““ŗyķf"ârš‰‰‰îŦī’A|trMÔęē7ūûVYYšģ aĮŖËúōŗ …ÂŨ…�€ËEDD,^ŧ˜ĮãŅĨDÉÍÍũųįŸÛÛÛš\î‚ bccŨ]#Ņ'ųî.� ē$ĘĻM›ėY2ūü’%4@âdŲcOF ‹UĢëžųû ŖF—8uÆĖoV­îÜgΝûfĪŸ”š>ö™ŋŋĐÜÜÜeãĮķ—=ödJZưØã'Mûķ_žŽ¨¨´OzđáG~ô "z`鲈aąŲ9šövFķâō—ÆMœ:,vÄČô1?úÄņṳ̈Nfųß˙1,ļššųī/ücÔčq1 ɡ/XtüxžÁ`ø×˯Ž71.qäü;ī:QxŌÉÕĄ+¯\•ãĄČ‰m븃ŊM}ũŊ÷?ŸôËŽŨÎlĢļļļOV~:söÜÉŖâ“Ro™uÛ'+?ĩŲlÎL¸EFF.^ŧ˜a"jooįp8ķįĪww]—ņÜ]�‘@ ĸe=1:=íŖŪĩŲlīž˙Á‹Ë_âķø īŧƒˆ˛srzä1•Ęû÷O<æí啙•ũĐ#sš—ŗ°āDáÂģ—xz*¸o‰JU~áÂ×ߎÚāāŽ­?+•žO<ö¨§ÂsŨ†O>ūh\lLÔĐĄDTßĐpû‹š››ßĩpذ¨ęęšož]ŊđŽ{žü|ezÚ¨ëėÃ,Dd?éųøī˙4*u䟭8uęôs/.ü÷ŠŽ5t芏>¨¨Ŧ|æī/ün鲃ûvÛ;;^kÚļWęĒÛöĒ|>Ŋüī×x<ŪīŸxlȐ!ÎlĢį_üįk×͙uëŨ‹q8œ}ûžöŸ˙VVVũķ/\u*ĀŠĨĨĨãMU{{ģN§so=] ˆ8ą ōô_Ÿ˛?~˙ˇG÷Ū‡Ųãäũ?ļŲlđ^âˆ"Zxį/.Šã ƒˆŽįįG |îŲ§G§§Ų[üũü–ŋôīM?o^rīŨÉI‰G2ŗˆ(%9iℋ×˙ßūß{5ĩĩ?}ŋzDÂÅxŸ{Ûė3gŋōÚ6ŦũĄĮ û0 ņ……†ūū‰Įˆ(.6fĪŪ}[ļnK‘`_߄ø¸ėėÜ/žú:ŋāÄȔäĢŽÎ5mÕĢuÕm{Õöđ­ö‹O?鈙Ģn̟ˇlMNJ|û˙Ū°O]ŧhá˯ŧVU]mĩZ†q<õšļ�ĀoÃņãĮ7lØ`?ĮÅápŦVëÖ­[‰hôčŅî.íĸ'ŗoŲņX&“•zđĐaĩēNĨōÎĖĘ2Äž;ŗ[´đŽÎgÃîY|×=‹ī˛?6›Í6›mčĐH"ǍŧâŦN‡ööö-ÛļEāīßqʈĪã§$'ī?p°ĩUīá!šūY:›qĶôŽĮáaĄD4mڔŽ–ˆˆ0"R×Õõmup<”Ífsŧm¯Úˆ8͟7ˇ#KœŲV|¯˛ĒJS_¯ōöļwxūīĪtŒéx*Ā&??ũúõ6›Í~ŊD |÷Ũw-QPœ„‡…u~ęįįKDu­Ũf2™BB‚;OŒˆč2ûēõ×üđcŅŠĶ---KĪŸîŠ¯¯×jĩÚÆôąēO­ĒޞŸģĻYü|}_ķ˙:ZÂBBZúģŽ§ūūžíī¯ũũü:Zø<>YĖ–ž­Žc†R×Õ9ŪļWíĐ!"<ŧãą3›÷O|ō_/ŋ:eÚÍͧM=:mü¸ą7ˆãŠ�7”‚‚{–p8œyķæŲ¯—,\¸p͚5*Q\'ĩˇˇ÷8Ņfŗq¸œÎ-"ą¨ķS‰XBDÍÍ͉„ˆ„aįŠBáOßøī[~ŧ"!>î…įž,Μ-~öš^ĪŗëZ[‰(&&úoOũŠûT?_ßîWĨU¯_ũŨåLIî'ö3Buoéķę8āx(ƒÁHˇíU;tÉ¤Ųŧ÷/šwXTԗ_ģmĮÎu6Ҥ‰^ZūbPPāU§ÜPJJJŦV+‡ÃšũöÛ.ž'>|øwŪšfÍ›ÍĻŅ8uWŽĢš0NdR)566uŸÔŌŌb2™”žC:7 †.}ˆHéé) ‰ČÔfę<ĩĩUßņØd2}öÅWūūĢžū˛ãŒSįwâŨI=<ė:.Ĩ\ÕUg‘ËåįΜėqŌ5éÃęôy¨ĢnÛĢv葓›wLÆč1ŖÛÚÚ˛˛s×oܸvŨ†{îûŨö­›ė×öO¸qÜvÛm6›-""ĸËĮŖŖŖ,XPRR2kÖ,wÕ֙ oŽ!ĸ]{~í~€˛ķ—ŨD”tåĻ).>×ųŠũė>ž>>*ŸĪŋpáŠË§NŸîx\W§1™L ņ¯^dfe;¨MĨR)•ž%%įēÜķZßĐĀâ,}͇ÕéķPWŨļWíĐŖkÚV`ÜØŒ7_õîģ••—,r~*ĀĀ~Ž+))Šû¤ØØØŲŗg÷I=raœ$ÄĮ%ŽH8~<˙īžßųĘr{åĩ˙pšÜû—ÜĶš˙?ũÔņ¸´ôüņü‚đđ0o//—’œTV^ŪųS _ŗĒãąJåMD?Kq˛¨híú Ôém5Ãp‰Čhŧü.{æÍ7ˇĩĩ}˛ōŗŽ–ú††[fÍ]ēėŅŪÖ¨ŗô3ĢÃÖPWŨļWíĐĮÛęXŪņŅã&Ž]ˇĄķ,\.‡ˆx|žãŠÎ­:�ô7žėâp8˙{ëÍÅ÷Ü˙Î{lÚŧ%)q„P(<wŽ4+;‡a˜W^ūgtôđÎũÛÚĖK—=:eō$›ÍöņŠO‰Č~g--{čÁĖŦėĨ?ēāŽyJOĪĖėlƒÁh?™FD"‘hĘä‰ģ÷ė}îÅåŖĶŌÎõÍǎ˙ûŸ‡y|ĪžŊŪ<mĘdû‡!>údҊŠQŠ#G$üá÷īųuī}ĸV×Ĩ§ĒUĢW­^ĶØØx˙’{{[Ŗ>ĖŌÎŦ‹C9ŪļtĩßĮÛ*!>ÎSĄxöųŗssccb8*((üqíēԑ)ą1ŅVĢÕÁÔ>oX�p)fŌ¤IŨ[FŖũŖ…v:nΜ9}]ĄP,¸cž@ (+/ĪÉ9Zx˛ČfŗŨ4}ę¯ŊŌųŦúÖm;Ξ-ūōŗeežYĩzûŽ_TŪĒgŸūëís/.4,,42"ĸđäÉŨ{öœ(Œ‹}ũ՗øq­@ X|×B";vLmMíž={ŲĩÛjĩžōŌ?ĮŽÉ ĸCG2ÉZ0^lLtqIINîŅŖĮōŌFĨF *‘HfĪēÕ`0ėÛ`Ãϟķ N$%ŽxķĩWŌFĨöļ:}˜…ˆ~Ųĩ§đdŅ}Kîņôŧø Ä#™Y™YŲ æĪ ŧxaš°đäŽŨ{n™qĶđaQÎŦÎŪũΞ-~čÁ<<$ö­÷ā÷õ¸‹ŋęPqą1ގíU7~÷ŧęļâršŗni4÷í?°}ĮÎ#G˛ &ãũKîųĮ Ī ĮS¯úW�ÎØ¸qŖL&ëxÚÖÖ&‰ôŋ*ÎōåËģˇ666J;í›ĒĢĢWŽ\y=‹qėÉ?>ĩyËփûvøûģn)��ĐaéŌĨOu:]įw„}0 žŗ ��;Ä ��°�q��,qōîÛ˙=wæ$.œ�� ^"N��`°Cœ��� '��ĀÄ ��°�q��,@œ��� '��ĀÄ ��°�q��,@œ��� '��ĀÄ ��°�q��,@œ��� '��ĀÄ ��°�q��,@œ��� '��Āž“ũJ/Tģ´��Ԝ“đ!.­��5œė�� N��€ˆ��`â��X€8�� N��€ˆ��`â��XĐëĮ›šš:ëtē~)��ú‰N§ëŧŸgæ:ė5NŧŊŊ;›LĻë\ �� ( …ĸķ~žąąņ:ÄÉ.��`â��X€8�� N��€ˆ��`â��X€8�� N��€ˆ��`â��X€8��¸0N~ô‰ˆaą˙FĻš{ÉYŲ9RŌ2Ūûā#×�ũĻķKyũ/ë?ūųԌ™sŽu�¸W¯_Ɋ!C^{å%ûcu]ŨĒÕkîēįžĩ?|—8"ÁĨËuНžY•_Pđæë¯ēģäšgž>|˜ģĢ��wrmœH<$ŖĶĶ:žÎ˜>mŌ´_|ųõ[˙ũK—ëR'Nēģ„gūŧšî.�ÜŦ_¯…ÂčáÃĪ——wŸdąXŪ~įŊŠ3fFĮ'M™~Ë7ĢVwLŌÔ×?õ×gF›hŸôÅW_wLĘĘÎY¸øŪĔ´ø¤Ô‹îî8“æ`4':jô¸/žúú•×ū“1~ԈäQK—=ZW§!ĸEw/ųqíēĩë6D ‹=YTä`„‘éc>ûâĢ–.‹ŽKliiéŧÜŪwPRúØ īž˙Ąũq]&bXėøsĮ€ic&|˛ōSį׎‹QŖĮ}öÅWĪ>˙bJZFâČôW^ûĻžūĄGOJ=v”׎ģę áĖ .ŗ×ÖĒXē,:>)-cüÛīŧ×y.“ēlaëÛÛßIoí�Đũ})ūBEE€ŋ_÷öW_ķ“•Ÿ=ūȲm›7<øģû^ú÷kk~øÉ>éégŸË=zėˇūģuĶúG–-}ų•×wėü…ˆôzũŌ‡:4ō§V¯ûņģčáÃīđaû¯Á8ÍɅōøŧW|5t˙ž_ļoŨTpĸđ÷> ĸŊ;ëÖ[r26ĖÁ|>õšī‡ļę›/Åbqįåö6¸ƒ’Æfdääĩ÷ÉĖĘđ÷ĪΚ¸ī+-=¯ŅhƍãüÚuÁãķV~úų´ŠSr3=ũ×?¯üė‹|øŅeË><ŪÜūņ/ûVíí…p’ƒŲŸúÛ3gΜũlÅGĢžųBĢÕnŨžŗc.“ēláŪÖˇˇŋ?�ĐŽ=ŲED‹Åū@S_˙åWߜ;Wúį˙ŪĨO‹N÷ÍĒՏ.{hŪíˇQXh艅}ŧbá‚ųDôÂsĪ2\fȐ`" ûú›Uûēiú´Ēęj]këÜ9ŗ‡FFŅ?^øûŦ[oŽGsrĄD442rÁüyDāī?i„‚'ˆH&“1<ž@ đR*ĀápÄ"Ņ3{ĒĮÍŌãā;&ãŸ/ũÛfŗqšÜ#™YsfßúõˇĢËĘËCCB˛˛sŧ”Ę˜ččkZģ.âbcĻNžDDŗgŨúü‹˙LINJIN"ĸŲŗfž÷ÁGįJĪ''%ööB\å/ā’Ūf¯Š­=tøČ?˙ņ˜ŒŅD´üÅį<dŸÅÁ¤.[ØÁúööwRYUÕcģ“Ģ�]¸öčäÔŠĶÃbGØ˙?yõš^õåņãÆvéVTtĘl6wnOOO++/omÕ‘‡Äãķ/ŋž9{núØ iãOŸ9ÛØÔDDáaaááazęo~ŧâDáI†aŌĶF‰ÅbĮŖ9šP"ŠîtmYĄ755_SŲDdß#÷¨ĮÁ 8fĖh]këéĶgˆ(3;;mff Ų9šD”•3vl‡Ãq˛6‹ÅŌ|‰Á`°Oˆˇ?IĨDa*•J‰Č~˛ŽˇÂIŊÍ^\|Žˆ:îÎāp8‰#FØ;˜d׹…Ŧoo'Ŋĩ;ŋF�ЙkNÂÃBßúīöĮbą(,4”ĪįwīÖĸĶŅâ{īīØ#ÚÚۉ¨NS'Ū÷ģĨ‹õÅį˙Îcx?ú¸ŊÃ0߯ū擟~÷ũoü÷­Ā€€§ūô‡ÛįÎq0š‡G¨3 ĩw‰D‹loowžlû2™Ŧˇ-Ķãā ËÎ=ęãëSZz>udōąŧŧėėÜ;æŨž•ķû'sžļ˛˛ō–.ŗ7Îģũ6û-j]Ū• …Â.å™ÍæŪ^g8˜ŊĩĩĩË‘H$Wd׹…oē˙Nzûûq~Ĩ� 3ׯ‰P$‘ÕnöwÄoŊųúđaWÜlw<˙Ôé3ß}ûUÚ¨T{c}CCpp°ũąˇ—×ŗO˙õŲ§˙zļ¸xå§_<õˇg†t0š“ ur펄kplFFîŅŖ*o¯áÇÉd˛QŠ#—˙ëåĒęęĘĒĒąc2œĘKŠ\ŗęâep•JådmŽ_ˆë™]"ĶĨ ģæ–‹‚&uáxĶõøw’×[ģ“+� ˆOÅĮÄD ø|M}Cdd„ũŸ§ŌĶËËK ˜LmD¤TzÚ{=–WQQi/áBÅÎ_vŲÛŖ†}ų_˙āršgΜu0š“ ŊjÁöŽg„kŨD4vLFnîąĖŦlû999ŠŦüÂæÍ[#"Âģ˜ƒĄärų¨Ô‘öáaĄ=ÔŅ/ÄuÎND'‹NŲ'™Íæ#™ŲöĮ&9ŋžŊũôÖîä@.ŋī ™TēhŅoŋķŽ—R™8"Ą˛Ēę_˙~5ĀßīĶO>Љ.žøę›?<ņøé3gūķæ˙;æ\iŠĻžžĒēúŅ'ūđô_Ÿš:y‡ÃYŋq—ËMINr0š“ u\­B.?y˛čdQQ€@ßFčÃv ĸŒŅi5ĩĩŋėÚķÜߟ&"ЇGôđá_}ŗjęÔÉ×:T8x!TŪŪ×3{PP`rRâ‡}âííõų—_ .u0Éųõííöžm�qBDĪ?û´\&{í?oĒëę|TĒiS'˙åŠ?‘ˇ—ׯŊōŸ˙ūßÚuF$ÄŋņúĢĩĩĩOūáĪwßûĀö-ßx핟}ūÖ˙Ūå1LTÔЏŪ7<<ĖÁhN.Ôąû—Üķįŋ>Ŋ`Ņ=ž÷ŋžĐ‡í@Drš<>.6ŋāDZęH{KjjĘW_;ŽÛ™ŽëYģŪ8~!Žsö˙ũߛĪ<÷ÂCË“Éd‹īZxûÜ9Ûv\ŧ!ØÁ$'×7=mT'ááaŊũũ�@p–/_ŪŊĩąąŅĶĶŗãiUUÕĮÜE�€‹-[ļ,00°ãi—Ũ~ ˆk'��0Ø!N��€ˆ��`â��X€8��¸đFá6ŗĨĸZm0šÚĖ×-�ā†%āķÄ"ap€¯€īūO}¸Ē‚6ŗåTq™ŋ¯÷�YO�€ßž6ŗĨ^ÛtǏ,zh¨Û÷´Ž:ŲUQ­ö÷õöõötû�üV øŧ�_ooΊjĩģkqYœčZ ŪJš‹�€ŪJ…Áhrw.‹ĢÍÆpq�Āå|Ū@¸D=>��°�q��,@œ��� '��ĀÄ ��°�q��,@œ��� '��Ā| =ŊüÕÉãĮÜ<ub—Į׊ėBåžC™gKJ›[t|ĪÛ[™=y|†D,žūÁYÄâ*Ā qr…yŗn đģÎAĖËę6œ)97qėč铯y*f‹Ĩĸ˛úĀ‘Ŧŋųîc. ôgĨZ�€qr…ôÔäëÁfŗŊŋâK‰äŧ˙(āķí"Æ 3|čî}‡>úü›įūō¤H(ŧîb�÷Į‰ÍfÛö˯šĮ ´MJOųäņcÆg¤uīÖÔܲúĮ gŠĪ‰ÅĸÉãĮÆŧ'Ÿ˙ËīģtÛ°eĮŽŊßyũŸ]Ú[t­ë~ŪvĻø\ĢŪ ô”O3zŌ¸ŅŨ—ŌųĖÅjŨ˛cwVnžÁ`  ¸mæMa!W-xëÎ=gé’EVĢuÖ™9ĮLmæäq)‰ņŸ~õŨ/=WxęĖáŦŖ“ĮgŅ3Ë_›1uâŠ3ÅgŠĪ-˜7ëģ7ūíŅšķåo}°ōÁ{%%Ä:(ūŲž~Ķ” 5ĩęŧ'Ûmíi)Ķ&_ũãúâŌ2Ą@p댩ŖS“‰č¯/üûĻ)jë4…E§MĻļčaC/˜+õôíĩčmËX,–ŸˇíĘ=^ĐÜĸSČeŖRoŊi —ËužN�ŧÜ'ë~Ū~03gŅŧŲáĄ!§Ī–ü¸q a2ŌFvéļęĮõ•U5ߡLæąiÛ/ĩj ×Cņūž>ņ1ÃēˇûũēÚēēû/Ëd%įËV˙¸ÁKŠ㨰Mێ/X0w–ĘÛkīÁ#ī¯üęī~ÜÛKé `ŖŅ´{ߥgūô‡ÃųzÍēŠĒĒĮ\ĸPČwí=°úĮ ĄC‚¸\î¨”ÄŖĮ ėqÂã13ŗbŖož6)tHЉÂS߯ûųO-moo˙aũæäqI ąŽ‹gîî}ΛŊhūœƒ™9kÖn:SRzįÜYŨŧyĮîī×n-‹š ÷—_Ė›}ķŨ æĒ5õī}ōÅOˇÜw×}{-zÛ2kÖũœĸháŧY!ÁAĨåÖŦŨd6›įÍžÅų:ūĨ�Ā€ææ;ģŒFĶūÃYĶ&ŽM™äŖō—1*mdŌŽ=ûģtkiŅ.ž1ebô°Č �˙û/hmÕ÷8`zjōÃ÷ßŨŊ}ūœ™O<t˙Ј0_īŒQ)AūEgŠf2ĘĘŊyÚä”ÄøāĀģî¸-føĐ:Mƒã‚ķ ƒƒ|TŪe*ŽåŸxä{ƒƒdRÛfŪd4šÂB‡‘¯Ę[ÛØÜą _pÛĖ›ÂC‡pšÜ…ķæÔÔÖeæ;p$[ÛØ´`î,gŠ ˆÎápR“FQxčđĐ!'5)Álą¨ë4줧&s8?Õ¸Œ´ŧ‚“mmm}x-zÛ2­z}VnŪÍĶ&Ĩ$&¨ŧŊF%'N;úā‘‹ÕzMuĀ åæŖ“ŠĒjĢÕ=lhGKTdØáŦ\“ŠM(t4Ē5õíííö3*D$ ‡GEÔ¨¯a$ vîŲwϏT×ÚÚŪŪŪĒ7øĒŧô¯ŽQ[,–Đ!Aö§<†Yzī""*>wŪAÁå•UQ‘áDt8ëč°Čp•WG7[{{XČ"2ĩĩq9œŽöđĐ!rŲíŗflØ˛Ãjŗ-ŧ}ļLęáLņ~>Ē‹›E$$"?ŸKOEDd0\ü„!AŗøųZ,–ÆĻ_ŸËã8ųZôļeΟŗŲláĄÁ=C†ĩ™Íušú�?_įë€AĘÍqb4™ˆč?īØŋÚÚۉ¨šEį#ŧŧ/nÕˆHØéōĩ‡¤ëyŦVëû+ž´ŲlwÜ6ĶĪׇËå~ōŎŽgŅ D$đ¯ŠāĻĻæ@?"ĒĢo°īCíJË.´ĩĩŲwÁšúŸN;qąčŠkōŠÉ#~Ú´•á2‰ņ1NĪã1Ÿō¯üųËvjˇ?čŧõ„>ŒįWÍÉ-ĶųûM&Ķ5Õ �ƒ”›ãD,Ņ}wÍôŋâŪYĨįŋäČįņˆ¨Í|ųäŒ}§æ¤ķåU5ĩ|ôÁČđP{‹N×ęíĨt0‹ÔȌŨ~āĖqÁíí퇈„žÍfë˜ē}÷^ąX¤Ëˆ(7¯`˜ôŪ–ģyûnOšÜbĩnŲšgÎ-ĶûV|LÖÅž^]ŽU8ųZôļeėFS×ĨØÛā7ĪÍ×N‚üy ĶĸkõķUŲ˙yxˆĨRI—Ëė>*o"*ģPij4™N-q~)‹…ˆ<$÷žĨeęĩíß ûų¨ø|~ņšķö§íííoøiVnžã‚ŊŊŧjjëˆhØĐČã'NĒëęĩMĢ~Ü õđ°Ųl­zũöŨût­­ö ėŨ•WTî9pxáŧŲwŪ>k×ۃåU}+žGg/­ •UT ø|OOEįNžŊm™ā�.—[ržŧŖgiYšH$t|R�~3Ü'"‘pėčÔÍ;v=^Pß =[RúŪ'_~ũŨZûÔũ‡˛Ūú`%Šŧ•ÁAÛwí--ģPĢÖ|ĩú'šLÚ1HG7"ĘĘÍ[ųÕę.K ôįņxŋ8ŌÔÜRtĻøûõ?G‹T×iZt­ Ë•˛}÷žŦÜŧōŠĒī~ÚX^Qâ¸ā¸čaĮōOX,–qŖâcŖß|ļâđB�� �IDATīãˇ>XxįÜYūžžĪŋüfIéųĮ—ŪĮíég­Vëˇß¯OM;<*1>æÛī×Y­Ö>ßŖĻææ-;vkęĩ…EgöΙ”Āŋ2'œ|-zÛ2‰xô¨”ģ÷åžŌ66eåæí;”5y\F+ �ŋ=îŋQxŪė[Ä"ŅúÍ;šš[ä2iB\ôœ›§Û'546––]°?~`ņßū°î>SČe3ĻN,¯uŦtîV]ĢÎ/<ÕeR{îŧ}ã֝Yšy!C‚î]8¯ąŠųķožįãΟ{ę‰Ū ›;k‡ÃYŋyģŅÔā÷؃÷ĒŧŊ=,ŌÛKųĶĻ­wΝu×ü9w͟Ķ1Ú_ž|ØfŗYŦ֎Ī6vąsĪūÆĻæ'—Ũo:ÎĖ—ßxgĮî}ˇLŸÜ‡âģ“6Ro0žņîGfŗ%!vø‚šˇvīãäkŅۖY0÷V‘PđũēM-ēVĨB~ķԉĶ'wžB�Ô8˗/īŪÚØØčééŲņ´ĒĒęã?žĻqžMŽ‹ēÎâ:k3›­ĢX|ņDüģ.‘ˆŧw‹‹`EcSķû+žTz*fLhŋũ×fŗÕ¨ëŠNĖĖž>i|÷qô|1Āo[všË–- ėxÚeˇßî?:qŌGŸ}ĶŌĸ[4Ž\&=QtúLIé#ÜãîĸzāŠ˙õ÷Ëvī;ôíë5õ BĀh2)=ŅQ‘÷Ü9¯ã^g�€ß˜A',^đĶĻ­+ŋZmj3ûx{Ũsįíq=}ú} 7O›tķ´If‹Åh0ŠÅĸ?Ā�đ[2hvs2™ôūÅ Ü]ÅĩáķxüNˇ ¸Ņë˟uw �đ‡ģn��€ˆ��`â��X€8�� N��€ފ.—kíôˆ��ā"mf‹€īūÛt]'2ąZŖuŅā��ĐĄ^ÛÔå§.ÜÂUqāĢŽoŦŠkh3[\´�€\›ŲRS× Žo đŊzosÕņ‘€Ī‹ZQ­Ž×6!Q��\AĀį‰E˜ĄĄád— +đy!Wī��ƒîė�� N��€ˆ��`â��X€8�� N��€ˆ��`â��X€8�� N��€ˆ��`â��X€8�� N��€ˆ��`â��X€8�� N��€ˆ��`â��X€8�� N��€ˆ��`â��X€8�� N��€ˆ��`â��X€8�� N��€ˆ��`ĪÉ~ĨĒ]Z�� jÎÆIø�—Ö��ƒNv�� '��ĀÄ ��°�q��,@œ��� '��ĀÄ ��°�q��,@œ��� '��ĀÄ ��°�q��,@œ��� '��ĀÄ ��°ĀŲß;�p^ÉųōĪžūN×ǎŲlîŽåšqš\Їäw÷.Š qĐíĀyēķ;Ēk%K{ŋ•֏C>ôũ"æļ:āč�XVržüíV6ˇčc–‘Ífknnyûƒ•%įË{ësā<MXIÕ:wf YÚŠē…&Ŧ¤įŨY†â�XöéWßšģ„ëÆáP{ģƒY°†Üš#p¨ŊŦqwˆ�`]‹NįîØĀá´ęõŊMŦméĪRކCš°É'��=sp˛n š\âŪsnvˆ��`â��X€8�� N��€ˆ��`â��X€8�� N��€ø H�pŗQ)‰cŌFú3 ŖÕ6åîŪwHo0اžļü™=ûoßĩ×ŊE^ŅŊIô`*đ'1*šiķiz}U]úhũúģ)LIIī]žåÎxún!ũ~3ŊwÄ-%÷â�ÜiÉĸųŠÉ#ŽåŸØ˙SļÅb ž06=yDüÛ}ÚŌ2�ž9äēq9´úNZOĢķéÃLj1Ņz2ƒîAĶ>ŖüÚfJ_ŨA¯īLYBˆ�pŖŅŠÉŖRW˙´ņPfŽŊ%ŋ°(3÷Ø_ž\6ëĻ)ĢÚčŪōXņX:Ũ™@wOĢō/ļl>C+rčāÃôũ]÷?˛^ųũ(1>´áúŽ€žŨŅ˙Å^Ä �¸ÍÄqŖË.Ttd‰]­Zķŋ?Ģ­ĶtīĪc˜Y7O™” “z4ˇč˛Ž߲cˇũ›ĩ"ÃCgß<-0ĀËáVV×lÜēŗ¤´Œˆ¸\îÍS'Ļ$%x)=›vī?tāpv˙ŦŨ2hgņå,ąĶčé¯ÛhÃ=4s8m:ušŨ_J[īŖ#héēūŦ‘ˆ�p‘Hāŋc÷žî“*ĒĒ{œåÎyŗãbÖŦÛT^Q2dáŧŲ>oíĻm>˙‘îÉÉË_ũĶq&ŒMlé’į_~Ã`0ÎŊuÆØŅŠkÖn:wž<:*rūm3­ëáėŖ.^š‹¤4ԛ>é)ŋvM ģ'Rm^Bĩ:Z°š,ƒđ—b'�ā ™ŒÃáh´Nö—HÄé#“ÖũŧũčņD¤Š×úûúLŸąaËNĨR! ŗ¯UkˆčĮ [Ž?aąXEBáø1i;wīËĘÍ#ĸõ C‚§OžĐoq¤ ":ߨÃ$ƒ…jt$ŋø”ĮĨîĸ”@zj+ĩšû§:–áFa�pvj'"ĢÕęd˙ā�.—{žüBGKyEĨ@ đQyĢëękë4÷/^0}ōøā �›ÍV|îŧŲl ôį1LŅ™âŽYΖ”ú¨ŧģëŌû7Üķ{ŲŅr9dģtá$ΗTú0“^Ŋ‰Ōƒû§:–áč�ÜŖŠšĨŊŊŨWåíd‘HHDFŖŠŖÅhj#"‘PĐŪŪūö+§M?&-uÎ-ĶĩM›ļũ’}ô¸}–ß?ō;jŋ¸Ûæp8D$—I5õ ėŽN.4…+{˜$摯•7]|z^KVP›•âüčĮģ(å}Ēëõˇģ(Ä �¸‡ÉÔvĄ˛:=5yûŽŊ–+Q’b-ë‰ĸĶ F] ;‘PHDŖ‘ˆt­úõ›ˇ¯ßŧŨß×gĘÄąKÍ¯Š­ŗgĪWĢŦĒžâ†ÜÆÆ&ęuz*TĶĸôĘŪŽŋ¸5m(ŅŽ’‹O›Ld°-úŽōž¤īŅMŸwŊék€ÃÉ.�p›=û)=7OŸÜšŅßĪ÷Ž;nKˆ‹îŌš˛ĒÆfŗE„…t´„‡1ušoĨgBėÅū5ęēī~ÚhŗŲü}+Ģj,‹TęQ[§ą˙kÕë[Z[-NŸaģ~o¤x?z4ũŠFo1Ŋq3Ģž'Ēu´x M §Woęˇ؁Ŗ�p›œcųQ‘á3ĻL›W`jk œ0&ŊF­^÷ķļ.õÃáėŖ7M™P§i¨¨ĒŽŠ Ÿ06}ׯl6›RéštÉĸ [vœ(:ŨŪNŖRÛÛÛKË.Mσ™9ˇŪ4ĨĩU_V^áĨôœ?gfcSķGŸĶoëøY.M §÷gĶøPZ_D:ÅûŅ“Ä!ē훞$x×9ú×nZ>•2+č§Â~Ģôz!N�ĀV˙¸áôŲ’ņiwÜ6“ár5 Úíģ÷î=˜i6÷p{Ķë7MĻ…ķfˤÚÆĻmŋüēsĪ~"*>wū›ī×M0öÖSmV[u­zŗĢë4õD´vĶ6ûíÂr™´šEWpōÔĻ­ŋôį ļŨ÷#m;Cĸo#.4Қzm¯ŖĢ#/ũJcCéķyTXK§zøÎ@ÄYž|y÷ÖÆÆFOOĪŽ§UUUüq˙�ƒŲ“{ŅŨ%°æŨ˙üĢĮvÎķũ\ČÕĩŋ|mũ—-[Øņ´Ënŋpí��X€8�� N��€ˆ��`â��X€8�� N��€ˆ��`â� gö¯îyRÖáŪ�ؗ€�āˇE&•v|!ü ÖŪ.õđčmĸŸŒzūž-ˇh'U¯•öÄ �°ėÁ%‹¨÷÷õƒ‡ķā’EŊMüaá�ZE‡~Xčî"'�ĀēȰ?>ļT&•:8Y4q8™TúĮĮ–Fvú2ü.Æ…ŅžĨä/sķY&—üe´o) sgvøFa�`_dXČ+/ūÍŨU¸Ö¸0Ē~ÚŨE $8:�� N��€ˆ��`â��X€8�� N��€ˆ��`â��X€8�� N��€ˆ��`ŗqbŗŲ\Z��ôWėŌŠ>ŸōäI֗ ��nqōäI@Āî˜NʼnD"Ų¸qcûoā÷p��nxíííëׯ—H$ėËLš4Š{ĢŅh‰DOZ­ÎĪĪ÷öö–Éd<žÖ�`đ1gΜYąb…Z­V(]&uŪí÷ŗÁ RŠ4Í;īŧƒ‹(��ƒÃ0ŪŪŪŦ| Į2™L&“ą^��üāFa��`â��XāĒ‹ęV›­šĨÕbąZq­�Āe.—Įcä2†ëæÃ—ĉÕfĶ44I=Äa �~ÃŦ6›Á`Ō44ŠŧîŨßēdŲÍ-­Rą‡X„,�p)†Ëĩīo›[ZŨ[‰Kv÷mf‹D$tÅČ��НX,´XŦî­Á%qŌŪŪÎáp\12��tĮpšnŋP“Q��ĀÄ ��°�q��,@œ��� '��ĀÄ ��°�q��,pįaU^ģ§ŦįĨ}ĶgM×įnÚ]:möĪž.ÁZv`ÁŠË‹āō%ĘĀČ#†zôuH��č‰;ãD7~Z¤ũaËŲCG/Č&Æ{1DD$‹ˆôė,Æ#l\z¨ˆˆČjnŽ*:Q°goÛĖ™ JvF��"÷Ɖ@îã'ˇ?äW ˆņPúú°ŋžLéësq9žūžLË™5UÍ J9û‹�¸a ü_}7×Ę:YĶlc<ŧÃŌÆ$^ü1ccUŅŅŧĶuMF+ãá9$&eäPO3ã1 —séŠU_‘4ŋLŨd$F$ķ‹HHáo?fPŸÎ=^RÕ¨7ßÃĶ?*11ÎWä �āF6Đ㤭"¯0 :czĶZ•s¨āĀqŋųéū Y5Įöî)æFĨŸč-2¨OÎŲ{€™>%\âp0ĢĄą*ī„Z::øâĩsUί{+<âÆL™¨´ÕŸÍ<r`ˇuĘĖd/Æ|!kakččéc<VŖæLîĄŊ9âŲã"˜^Ú(�pcđqLOâADōĄq§öh›ZÉ_nŽĘ/nQÆĪH ‘‘GxrZŊú—ĸmxOWD 6}WĐņLä=|\r ØūÄXuĒ\ī8))@BDA‰éQ5[Ξ­‘ÜÚØl†*=ˆHâ‘:^nņ‰Zzi�¸ą ô8Ģŧ.Ũ„ÅD|ŌļY‰¨šNk•„ųĘ.õbTžÜ⍑”ŨäĮDxŲŦ†–Š3…ģˇ5ĨN%'jŽ×Z%aŪ—iä>žÜ“šV –ųzœ>sh¯uxD°¯Ÿ§Hé-""ę­�āÆ6Đã„azjm3[Ijį§Žh•ĩĩuߡs=䞞¯ģ{zŠ´å—c…UŒŲÜFLįc †á Čj51>#§O‘*9}ôėQ3×Ã'21edˆŒé­ŊU�ŒzœôLĀgH2düø8Y§F†9õi™Ōƒknji%’ķų˛͗§YÍæ6â_ ‘WTō˜¨djkŽĢ8“—sh?ã1s¤wīí��7°ÁųŠxšŠ1ęÚøršėâ?1Ã$NŨŲEMšfW*‘Ō[Éčëę/ĀĨYŨhã+•dmm¨¨ną˙ļ™@î‘œČšÍŊĩŗžŠ��ƒËā<:áF‡‹vĪĖ$Fy‹Ŧ­ĩ§˛sĪ‹GΞ*îŪŲÜĸU׈ˆČjÖĢ‹ OĩJĸŌDÄŒ —üR](K S2mõgˇ*‡§û1DÍį2¨U‰)#‚<dl*/Ž%H%ŋ×v�€ÛāŒbüR'Mäåeī-2š‰/S'MIé)Kˆ¨õüŨįíš|‰Té;rJ|´¯ũbß/yŌDæčąĖyFâ{xƏãÉQ@ō”Ôã9EŲێšl ßCæ9f|‚'1ž=ˇ�Üā8˗/īŪÚØØčéŲ÷}dM]ƒŋWߋ�€kt;ŪëÜíĶ`Ŋv�� â��X€8�� N��€ˆ��`â��X€8�� N��€ˆ��`Kâ„Ãá´ˇˇģbd��čÎjŗ1\7¸dņ>¯UotÅČ��НÁ`âņÜüģK.‰šĖŖÕ`Ôé V›Íã�€ÕfĶé ­Ŗ\æÔ/>šŽKžQ˜árU^Šæ–VƒÁ„D�p†Ëåņ•—Âí'ģ\õõ —ĢTČŽŪ��~pg��°�q��,@œ��� '��ĀÄ ��°�q��,@œ��� '��ĀÄ ��°�q��,@œ��� '��ĀÄ ��°�q��,@œ��� '��ĀÄ ��°�q��,@œ��� '��Āžk†ĩžŲŧbë9k÷ LÄĖGo gØ_båîO×W'-ž{¤ōĘĮ}TąįķŸj—Ü•Ō}“��nX.Š""’EOŸ-īŌ(Qš KˆH;aj¨R⒱�āj\'Be@PPŊ…—øGE÷Ķĸ�� WÆÉ5¨Ũũé熑ŗcë9§Ņ[%> ãgœÜsāX…ÖÄSFĻOŊ)^Å‘ž2gĪáü MK›U Q…%›22HHäė Ž^g'Ō—Üš?ŋ˛Ņ*ô M?üŠšz›TšûĶM #gF–ũz°R9åÁŲąB}Yî¯ō*ĩz+#SEĻNš¯^\ōŅŊOœ×´´‘@Ļ 1v|jÄA;�ĀāâÚ8ąX­W\?a˜ŪÎtņk]^–fōĖûĻKLgˇ|ší—ī*‚’§Ī}čVž6wŨˇûöŸŽ¸=Vĸ?šsĶÁ–¨éˇM –žö莝[vČ—ĖŽ:WŽƒŲõ'ˇmÉiŸ>oN˜ÄT}|˙Ás&’\šĢ×I<cm>‘U3ūŽąžJĄĩf˙ēõŧ„ésfûIô•Y;öŦÛĘ,š##ĶŲ]?g5›qĮÍ*ĄU_“÷ëŽ ģ$÷ĪŽeziG �Ā`ãĘ8Ņ^õÁá+›dÉw-™ ęšģÕ'>=TÂIB"¨´!xd˛JHDʈųĄã5ZŠ•#'/ šRÎ‘\ž–pėԑķĩâ\AŊĪŽ/-¨¤Đi“bũ…D˛ČņãkĘÖäØgr0‰ˆˆZQwŒ —‘éė‘‚FUúâ)QJ"’Ë'MŠŠü)÷D]L†O‹FÛ& ‹ ÷‘‘L>yļ2Fī!$ŌöŌ�0ظ2N”qˇL‹šâR<O ŋx.ĘjĩZ,"âņxYäJåÅ)OČ0$WÉ.ÎÅ0B˛wf„<ũɃvÔj›MV‹Åbm#’÷p˙X/zŸ][ĶL’á>;re°JŖĨĢL"""™˙ÅuÔVÖYeÃ/_-bBUˉš=ų(CÂdG펭ŗ$ÅGĢ$>ū"ĸŪÚ�WÆ #÷ņ÷ëųRFŲ¯Ÿl<ÕFDD‚čŲNŋō 9c¯Ŧۉ1kížĩëķ™č)“Į+…<ԟüyÍįëq0ģÉb"Fry #ŧ´eL"""žđŌ4“ÉB-Į~x˙ØKõ4™ˆ$AîŧC™›{2īׂũmŒ,0v뤉QJ†éĨŨų•�Üt)Ū?íŽņöà FâIÔčÔ\uE§ĩ’ØScũíĪõĻļkY¨ƒŲ…<†ŦúËĮ9V“ÉtõI]…<’EŪ:{TįexûÁ‹Ä/aüĖ„ņdŌV–äØģmOļd‚īí��ƒŠ›>/”ųøûųûûųûûųȝžV`˛ZHŌŅŨĒ9[ĸ%rū\—ƒŲe*/Ō7Ôuä„ϞæŌ¸&uĄ ōgôÍ&Ą˛ƒ„ĮeB"ksmI™Ö>›P;!#ŒŅĢĩĻŪڝ^%�€Â•qbŌV—•—]ų¯ĸō:v–>ū>Œæd^ŠV¯×V߲ģŲ'ˆą6×Ԙœ‹ŗË#bũ¨,ķׂJmŗļöĖžCį­‚‹s9˜Ô…0"9FRshûÁsšfŊ^[sj÷ÚoŋŨvJODÚģ7oڒW^×Ŧon֔å¯ Y€°×v�€Áƕ'ģZNíÜxĒk#3tÖc3"û6 $zęäš-‡ļu‚$~Qc§L‹lŨ_ˇųčkiá]NÜÜåhöŒ„›ghŲ`ÃĒŊŒgpÂø‰1û7ŗŸŦ÷I]×-xōíŗ…ûėYwLßFO˙ˆņˇOˆ–Q褚“÷īÍŨĩfŋŪĘdĘ Ø›įŒVŖęš�`Đá,_žŧ{kccŖ§§gŋ��îqũģ}|Ŗ0��°�q��,@œ��� '��ĀÄ ��°�q��,@œ��� '��ĀÄ ��°�q��,@œ��� '��ĀÄ ��°�q��,pÕīXmļæ–V‹ÅjĩŲ\´�€æhŪņ~^ĸX,RȤ1ÃŖÄ"Q?/ē —Ä‰ÕfĶ44‰EBŸOW,�`€š9}r.Î`4VTV8œ=.c”{Å%'ģš[ZÅ"!Ãp‘%��.%‰ĸ"ÃÃB‚Ož:ëŪJ\'mf ÃāĒ �@?  hÖéÜ[ƒKvúíííŽ��z$‰ Ŗ{kĀ1��°�q��,@œ��� '��ĀÄ ��°�q��,p՗Ŧ��€ļ6]cŖŪÔ͎PņÄrO…ˆO6C‹‘+“ûŊļžqoœÔßv¸!rŌä(i-ŅfŌT9ĄVͤ7Yl\žPĸ𠍩 Tđ™Å‚]ĩîšã;hçMęx\'ģ]ˇū‰“ē(ŗ@mK„=ž!âyÆ&…SInVšžQFĨ§ËɤéčÄÚ|>7ģ°I>r¸R*á36S‹ļžōüéģĘĸ3Ō#ƒád¤! IžŌÁōî  4Ÿ;ŖĮdLôŌÁÖ\Ŗ6)UŠĒr­ölfo\Šŋ¸_+ė‹&NŦÍgf–Iâ&N ”2—ąÂ7XáR’š7Ģ@6)Ņ׊cpžPėî�š6ŊIä­č5KZ+ŠNĢ‹O|RÔŲüŊP0(öÔ HŗĄúDAašVgåKũ“’‡ĒėûtĢĄē(˙TE}‹‰ĄT“ë#!""“ϏādYm“ÁL<‰Â7<6.J%tĐNdĶåķc&ĻJŦ†ę“ų…åõ:3_“ĸ=”c9#%ĄfOQų0ßH1‘ąîäąĨƒ•/õ‹Š‹4åĒ˜85ZŅĩt“úlAa‰ĻÅdc$ō€¨ ar>iī<ØvéôŽąėĀö‚†€ÔYiū\""íņm‡Ŗ&OŒėęūŖCkŽäsGĖČžô>ß\™ĩ3×3mB¸äbĩ­Vž‡_d|¯;oŨzŨÔÚãÛˇDĨ‡4Ēn6Z…Šđ¸ŅCųåų…ŝ™+ ŒNJ “s‰ČX6ŋ¨TĶŦ7ÛøBš_d\b”7Ÿčʓ]öŅ2" § +šôfy Iįëæ/ÕPl­UE§Z|ŖĻŽĩ9ˇ$jTZ†‡X0(Ūčē?Nôå'J‚‡ŸĀ5TäœĘ<&Ŋ)͟OfuūĄĖjɰÔqéržY{.īXæ!ÛØ)ņJŽš*/ëŒ>(e\Ē‚o5jĪäÎNOazi̊ĘųÃ&KČTyė@žÎ?)#ÉWbĶžÍË>Ē3+†)Ąrˆ˛āBŊ92˜OĻķšŲgôžIcGųōMÕE…yM&ŸĮt-ÜĻ=qčP)ž’žŽ™4grĘæNĸPōĘ4æ()ŸČĻ­k ųMuMä¯$ĸ–úŗØO%&Ō_Į™ū^A"īüŖĒÁaö¯šžBC^ņ2•įfŸŅû§Œí'27œ+,Ŧ6S;hŨzßÔD\ÆÖtîLSâ¨iÉBseÎ/9y{5ĒČäŒ[ŌŨŲCģ }ũ3BDĻōc™…ú ”ŅI*™´%šGŗJĻĻuũ/ĀelMg Ģ’ĻÄK𯚪ûs˛‹|oIöÁIF�;CÍéSMF[UÕj#â =$âA’%4n6KÂRâ•2ŠÂ7:)JnŽ­P›‰ŒĩÅåđ¤X_šD$VÄ%…yčĘKk­DúæŗP5Ä_!KdĘ Äô‰ccüøŊˇ™Ô´Š°` Ų4gōĢÅŅi A !Ÿ/öæE&‰R)$âK%|ŖNODúš2 ųOķ’JdŪ‘#cŧŦ–žęŽ)*mUD§$)%ą2$!)„¯>[ÖD\e€’´õZ+QƒZ+ âcÕjZˆˆĖÚúžĘ÷ŠÃ§úķũCmYĨáâÂ5ō ’ąĻTCžÃBŧÄB‰< >.ßĶ"ē9ØÔDDdU„Fû šDB_%YHŠāqĨūū›NĢ#"~@â„i !^R‰DĒ ްhԍ=žÜVEXB”KD"˙•ĐÜÔ¤īąĀĮPS˜_ËĨKY"ŒKņDŨ'/OÉĨĮR…”okiŌé´M6ą—ōōÅ'™JÁ57iõDR?‰Ą4÷đņ’*u“ÉFB…—\ČôŪNÔT¯“øz ‰*kĖǰŽå‘l<OåĨ›ŽCDúF‰=—^DÆ;Ā̧­ÔRßdĢŧ;nXâzų*¸:­ÖH|Ĩ¯ÂÚ Ņ‘N­!/ßP_™žNk&˛iÕZFåãuåHNõg|ÂøÚ •:""›æ‚†|ƒøDēF= e—īK“Ē=q:čæ`SÛ_ Šôâl\Ã%‰üŌæc>Y­ĒĐb�� �IDAT6"âōšĻÚĶŲ{wíÚēmįæ-{ ĩdĩZŠ'"™ŧŖ†Ī%ĢĨį~�7“ē(ˇ¸Y§ŽĒmĩ‘Ø?ftœje „“]|a§#9.ŸK6Ģ•Čl6WÔy —Ī'›ÕLÄx'Œ++.);—_z•x‡Æ&$IšŊĩ“Ųl4 Ŋ…DfŊÎ,Rx\ĩIŨ`•Ur‰ČŦĶ›%Ū"2[­Ät*ŠËōŠû[h‹ŲF†’ũ?—\Ņęa6Éŧ}$E Z ëLōĨØK),Ŧo´…kĩ6åpīŽé$qĻ?W6DzūByĶĐXŠēRäųr‰Čl5WtyD.—ßí´œãn6u ˇû[ĢļāāáRfHRbœJĘgČTžĩ¯¨‡"ėƒ÷2āFfQŸ(ļEĨĨKՇЏžqiC%æ6÷vĩ~@rœ˜;ŒĮfļ—áŲø|˛͝§˜ÍÄŋ¸×)ÃâSÃâÉÜR_}ް '“+žšāÕ{;ŅÅepŠĶ›fSåé :&@*""Sueŗ2țKD ÐÕdî´dS÷=+Ī%qHZú°Î¨`Ą„ˆäž*~ŠFkÖ5IŧŊø$õõĸãõ-zaƒQŦę~"ÔšūŠāPÅš˛Ęæ(eU-?pŒŠKDÄg˛/oB›ŲÜSĩēņnjg4UT脡ãC.v™z,�zgŗN—6Žۄž1#<ĩG3kƒĮŽôLīŋÜ_ĢąIkēôXפ3seJ ‘BŠā´†Žn-õM6žB!!›^[­ÖŲwŒ|™wH|Œ×Ô¤3÷ÖNėH„úf_å+7ĢK+õ6›ąšōøŅ2ŽRČĩšÍfmI^Ą>0&HHD$•KČШģ´G´ÖW7ôt5BæíÅ5éÍ|ŠLzņŸˆáōÅö°—¯7iëĘkšøJo))|&ēR­“¨T’sŽŋ4$LН.;uA- ĸ´ˇIäR2ĩ4uėŋ›4ڞĒuĐ­÷Mí,‹ÍFBÉĨøą5UUé:�zĐíO,æYëNįkũF&ø{p-m–Á÷_Čũq´”å•ÔëŒ&}Ci~Iŗ0 XÅņũ†…ˆĩ§ķÎĒuzŖĄŠēđhŠAŽbˆtåy™™Ų%uMz“^ßŦ>wŽŽÄ^ ~¯íDŠ�•ĩēBc%IXR’R—ŋkËĪŋæU+ĸG% ŠŗˇíĘU+FfÄ)í¯°Ä'DAęS'+› Ļ–ú’ÜĶ LOĮp|˙ČaÃÉŖ'Ģ›õF“ŽĄâøÁ={r*ėŅČUų*5Åĩf…ũ˛;_é#i..ŅōU~Ũî6ž†ū P?SYI­$tˆüb“Ä?TIęĶį5:}‹ļōøŠZëåjuįsö<ĨuÜÍÁĻv’ÜSÁm.;WŖ3štšŌėãzO×ĻoԚß˙€~!õRčËÎÕ4ļ ŖÁ`4˜ĨQÉÉŖG&æÅ5éĢĘ*ZĨŪ2÷‰›OvYm\éđ¸`Ũ™Cģĩz+_á7:ҟODÄWŏIgō î+4_"÷‹NOˆ’s‰Č7aLbaÁŲŧŊ'L6.O"S…ĻŽŽV—zn'"Žoä0áŧū}BR'„t* aŌÍ dŗYšÜË{OidZ’.§(÷× $ô ŒŽ‹ædjģ×ÎU%ŽÍ/,<~¨Ød!ž‡W@ܘøKŸ á{û)Lj­ˇŸŌū!V)ų…į­Á=ωĶũų>ÁJn­-4HÖŅ$KMi9VXxø×|ÆC?"äđꋷĸYõMÚŽņ*ŨzßÔN IJlĖ>yô—ķ$TÅ%&˜ 3‹÷¤‰“|œā†! ‰Kh-<}ČØã‰ažØ3 !.ĖŖŋËēNœå˗womllôôôėķ 5u 2ŠķįJú…ąæčÁ<$,nx¨¯—ũ””ÍÜŌ¤QW”ž¯Ą¨ cB:ŨCa5›‰éZĩšōđÎ\Jē%#ĐũwĢíÎg’§Ļ¸ŋ�čnīÃ3§OvËĸˇėÜs=‹žÎŨ>šũč¤˙ˆüS&Ģ>wĻäØŪlË'2ÛHčáå’<!øŠ{ģ įîÉ7%%GĒD6Ŋē¸PÍõKõqķūÛlŌĩhKOäk¤C§ K�`āša℈i@TJ@‘Õf6[‰Īīņ–Z"qXÚ(s~Qá‘_Mf._ĸ H“ĐíŪũL_žũˉfĄrȨTW0�@_ÜHqԁáō‡—D>Qi>QũUŽ3$‘ãæF^Ŋ�€ģ ˛;��ūŋŊ{}j#Ŋ÷ūĨ[7ZÔĐa0cl0`c›ą=ž2ēq&žį”7™\*›ÔÉVĨjķr˙�˙û"[;§jvΤ’ÚlÍIœ'ãd|23žŒ|Ã66`°š…H ! Ą[‹}Øđ•ûû)ŋ@­î~~-•Ÿ¯úé­LŒ""Ō�ㄈˆ4Ā8!"" 0NˆˆHŒ""Ō@Zâ$+++Ģ%"ĸEMFŖŲĻ ?(;-qbĐë’|*ŅrqŲŦžÄ9-qbŗšŖą¸šT1•ŽÕҌÉh´ˇāî7îMUžö:-Wŋ‚ äæCąxBMņ.åDô ųûįg–ŗšl“Éfĩė{ũĩŒvĨë&+ĸ Č9Ö§ĪGDôÉÔí„WžŲEDD`œ‘'DD¤Æ i€qBDD`œ‘'DD¤Æ i€qBDD`œ‘'DD¤Æ i€qBDD`œ‘'DD¤Æ i€qBDD`œ‘'DD¤Æ i@—žÕĒwūöoŸõĢ'ˆĢR˛yįî†R+�`đôoO Õ˙ø'Ûåô@DDË*Mq�°V}įÍ* � ÆŽļË_˙õ“ā?˙¤ŠHäMûŋU*KO^A¤ã“? m˙/o–¤ąH""ŌB:ãÄ(;ŠŠf÷>ŠJKüßã7Ûú÷U!9+Ģžļŧęō%ĶXi&q2¨8âõ`(į vŠĄžKg/v ú#0*å ûˇŠí˙îô €O˙GˇŊáGû&ūüŠįĪ~X?=V÷į˙ögߎ_üh‹ ƒ§ûéØöˇ+\gŋ”›ūåMƈëęŲæļAD­JECãZŸ|ÛIDô*ZÆ8A0TEIZ0ÂĨēŋúäŗ~y×ÁŖæØĐõŗ§˙ō)~ttķwûøß¯ČßūĪo”Ã_>vĩ:¨;.ģĒ÷Ũc—Ēįü''ÚuuoūĶ;Rdđō?Î|ō™øî‘jkZˇˆčˇLqĸÆüîŽŗW|RÅwËįī(ÄzZģ"Î7Ž6”J�äσ1ĩ-Œ‰˛QŅ(Ÿžk2TŨ^nëšØPvū¸ŠR`ŗ56y˙|ĩÃ[ũē# ÛEDDĶŌ'ž ˙į^xøŌ`_÷Æ;MëdƒĪãUĨŽŲą`륡��ņgoĘępÚĻ˙ōzUëÆ‡l –*b‡Įã)ū‰ˆčÅĨ3NäšÃ‡j§{y(I˛´ø^F2–„(‰KjJgœ]>K"tũO˙zũ‘÷íąĀ8!"J›tƉhËU”§_Wb4ę FbΎnU}ĖFŖ֊īžķÚ܆Ed{žõŅsYWÅËE14ä Ížnũøøg]ŗ/gbCÔé‹<8m84æĖP˜\ä#Á˜Q~@Ō‰F+Īė""JĢ'Æu •VĪĨ/žîöúo9{Ņ§Ķ Œ:Äũ߸|ū`Lv*ĸßÕ퉈yÛÎߊ<ftˏnkĩäių¯û}ÁHÄīé>ũņūpĒ;˛Ŧ›DDôĘYÎ…G,nzį;Ļæ‹g>i@RJv}¯qĢ ĀXV_#~ãÄ{ęž÷‹ûŨ§.˙í Ņh-ŦŪˇˇÚ˙ŠkŅ/ąøīŋc<ß|æ“ë‘8 vįē}ßß_Åã&DDi•uėØą…S€Ũn_öbˆˆ(3–Ūí¯€Á.""Zũ'DD¤Æ i€qBDD`œ‘'DD¤Æ i€qBDD`œ‘'DD¤Æ i€qBDD`œ‘'DD¤t=īDMĨ‚Ą‰dRUSŠ45ADDĸ čtĸÍj… ī¤%NÔTĘ76n1g¯„-$ĸW–Į;fĩŧĖĪËBV–Ĩ&Uߨ¸’›“Ųū6-mCsļ9ÛÄ,!"JŸ)LĨR)AĖ’)šČl1iéî㉤d2ĻcÍDD4ĪÔԔN“ÉÅwžŒŌ'SSSYYYéX3-”JMeü@5ŖˆˆVŊ)LeēÆ iqBDD`œ‘'DD¤Æ i€qBDDH×=ģžęjūKŗûášŌ‚^’×TlŪŧq9ƒUŅĢ&5xųԕĄš}QvN~iõĻõų/ķ-Z4–Ų8�˜Ëöî,5€šŪīęh?s.ūöÛurĻë"ĸW‹´öĩ­ÅF�H%žžîî– É7žU•“éēV‹'z̜ī°M˙īĖCēäšŦ“m™-‹ˆ^1zsŽ’g™ū[q(Bøo×GFBU9ÖĖ–ĩjŦ€8™Gˆqú…÷ō‰ķã›v¯ēv}ÄļķČŪuBÄ}ķÚM×ČxĸÉZ°ŽŽaŗĶŒąËŸžö­ëíj+�Dû??qÍWŧû{׈�0vųÄŲąM‡m&Gn_ŊŅw?I@oļ;+ˇlŠÉ7�ĸ÷ģŽĩŨöŽGUŅl_[Ŋmûzģ!C�­ĸ @ĐĪôEŖ7N] UžV8rŗĶgŠ?´ŗD˜ęēŲí Å -JIuŨ&‡˙Īŋ+k|ŖŌ�QWķ´6Ūá�Āãԅ@å*˛cžŪö[ŽáņÉtRN~ųϚJeú>‡ą‘žöÎ>_(–%[aåæē2›>CĀķZQqĸNîˇuŒJwĪ;E15Ņ{ë~yũ›[Ŧ6}âūĨŗįÜæšŨMdC|´įŌÅæĶjĶÛ[sŠ}ßČXŧÚj�ÔQßd4ø=~ŦQ�Ŋž„yMž„ÄŊËį;'JwŊšÛnPŖž;W[ÎĩfŋŗwIõ]?wĻW¨Üšī@žiräօÖsÍâ›Må4%z5ĨbãžÎîQ}Ņļ™n@ÄTdāöpIÍžM‹>1rŊåԐ´ĄaīN›>áīoģ~Š%ĩ§Š6§PÖš|DĨE¤üŪqŖQ?î‡SKd(ŲHÜoģ|'R´moCŽ^úûÛ¯^h3žšŗÄ”ōw´´ÜˡíÜ)›bž;Wo´\ė.ÉÎč§ņŦV@œÚ?ũ¨ũÁ+SŪÆŊ[×Ėũđ&ôkßŦ^cuuÉÛŌX_(0mŲYéų{OĪĐæųEy¸âõŠĨkDø†FŗKËd—g8ņø¨w\īØn`´Ļ|l ™öŲĘŖ&=¸ŗ7$מĩŖÄ Ā\žuĮčČ]}ūrŋ!z•Œwņ—ōú×jsoŅí­tJ�ĸî^÷¤ŧi÷Ļül�(ŦŠ÷œ¸;\ŊM)”qcÔ¯į‹ņ‹×ÚŨ#žd+ūҐ^ŠËƃĄ„ą`­3G-mŲiY3遄§ëîDNUã–" �ФŽŪīkîq—ŦŽã7+ Nlëė^g€”:rßé<}jŧĄioåėąŗœ7ŗ¯õĢRYŪÝ›Ã.Ü ø&PœW §úGBXc @ŲZQāīMÔØŸgT,X§�°:טoßi9§n\Wœī(°›ä<�ŧ~U*Ë0>**…vĄwĖ…lZĻĪ€ˆ2ĪRēŗĄT�5›ęģŨrv|ķžeŗ}ƒ”cŸé}ÂūņTvąüđw¯UÉzÆũĘų9ę€/ŒüœđˆšuĨĘø€ÛŸ¨´Šūŋ¨”æ°8 ¤ŪģW/¤Ö•*yJŽ1'×�ãŖãŠėâŲÃ7€›Ÿ# øũQäŦ†žhĉ`ļŲí3ŲaĪU sđ÷/ŽwŪ_÷úô‘ˆ†Ų‹c‰8D͜qDQÔ Ē ĀæpšÛŊž(L^ī¤\ĄHŠbē>2Ē–›†|ĒR눎ío6Ųēēûn_빖ĖŽŠ-Ûļ—XÅxBE¤ûķãŨ”eĮÕđ‘6ÄlkŽmļ/—å|žüĒķ理aúČĮÃ)H$æöE‚ ×#Ĩ&�kžCęķĮ`‹ŲJäė\ŲØ9H•‡ũ)ycž�@ĖĢÛˇĮÚÛįęŋyˇ#)HyĨ›ęęŠ,B2‘Âdßų“}”eN$VG_´âd>ĢlãĄ `ūš]zŊj4ņp‚šHÄĄ7éØ ķõwŧc“ŲŋŲĄčasæĄÕœ0yŖö˛üŲ¯Ũ”[šuwåVă^÷ļ֖ķĸųííŊiíž}5sĪßE¯}!zĨYr$!G�Ëŧwôz=RsûĸT"‘˜é‹lųŠūŽĪ3zĮĨŧ\=,ųš¸1ŠĮĸļbåA_$—Õ6”Õ"ęīloŊ$dĢN§]˛cį†ší‰ĸq•Æ]Wŏû‚)Á"-Ærž,FŧŖ‘‚#”^–Í� æÃįéw ŠÃ@.'Ŋ÷ŋšf�P'ÆÜCĄéĮ•lŽu[ëÖˆŅą`6‡"FÃqŊÍfų—-ЉgvŊÚÆĮÂ)A’y˛lŽœ#LŽų'LŽ§ô99�äæįÁīũÆ3Ž—ķ,�r91ßČāHXR �R˙ĐHxúšIŊ5¯¤ļē@ˆ‡°æå ąHBoąZfū™DAŸŊZÎėZq’ųGŧÃ#ŪáīũAWÛš‹ŨRņ5‹ôæú55åŌhû•ÎĄĐD4âŧqĄwBŽ\_ €˜īTĸƒŨC š0�ôyæ@÷íQC~ŅĖõ`˙ĨæķÍw<ū‰čÄDāūŪa˜˛ú5Uå&ߍKmƒ‰h48ęēüåŠŋĩ¸&@D/ąÄĸoÔįõųFG†Üˇ.\ë‹d—Žs.Ō›ë 6”dûoˇõŒ„#ŅÉņĄÎkw'sĘĘ�%_Žzz‡9ųĶ}‘됂Ŋ}~ŊR0sD=üMÛĨKWúŧã‘X$éī÷";7GŊŗĸÄ8vëÚ­Ą`$ šo|}æLĢ;ļ|Á’Ŧ€ÁމæĶĶ zÉ"įooĒ­Ę›U_°ĩņ€xíúĨĪÛĸЛíkj÷m¯ļĪĖĒwŦąĮ†FkōĻ'HŠž­7UZ4ķ Š…[›n´v]9u-–õfĢŖb÷ž:;�ą Ąņ€Ą­íĘšŽhzĢR\ß´­tuœšGDZ‰ÜģōõŊé?}ÔíŠĒPũÍ­Wjwīov^ûĒ3Ŋd+¨ÚYWi›™UŸWņįČ͞Yß9 ÎôEB~Ũî-í=mį:b)A'Y•Ō†]U9�eËî]úÎÎ-Ŋą$ôæÜšŨĩŋė­HYĮŽ[85Øíö^ŠĮ;ætäžxQDDZđxĮŦ–UräaÉBáČR:Ū%vûXƒ]DD´ú1NˆˆHŒ""Ō�ㄈˆ4Ā8!"" 0NˆˆHŒ""Ō�ㄈˆ4Ā8!"Zõ˛•éŌ'YYYSSSéX3-$YĸáŨƒ´4oĐë&"ŅtŦ™ˆˆæÉĘĘJĒĒNˇč­—OZâÄf5OLFÑI5•JĮú‰ˆ@˛„,!•ššˆDmÖ ?Ŗ)-wAÉÍ †&&'cL"Ę P8ōô™V3Qt:QÉÍÉø`WēnP/ ‚œc}ú|DDôRā™]DD¤Æ i€qBDD`œ‘'DD¤Æ i€qBDD`œ‘'DD¤Æ i€qBDD`œ‘'DD¤Æ i€qBDD`œ‘'DD¤Æ i€qBDD`œ‘té[udđFķĨ—7‰ĢĸA’%›wī­sĶ×"eJēâ$æúōŖŋöĢv4í,˛IPƒž;W/œūøDė?lPŌÔ&eLšâDuwô„”׏žšÅ6=AVœÅRōį‡<~(rz%"ĸŒISœ$cI�Ē:wšXŌôŖŸ<|Іú.ŊØ5čĀ¨”7ėoÜZdœ™ŪröâAĸ$Wŋ~`w‰ �˙ņŋ>õ7ŧ]7vųbŋ/ĸmĨ[šn+ž<‹¸ŽžmnôGTŅĒT44¨UĻ߉ ^;÷uĮ€/‡ÁĒ”lŪŗ¯ĄHJĪVŊēÄÆÆÆ…SŖŅ¨ÉdZÂjuÆäŊö[w\~ŊÅnˑô ŽøĢîs<Ų“]đ­Û*äā­¯Zúôë7fĮ\_˙ëũĻoįÛ{ˇntDî\øĒs˛´ļÔ" äēŅåréˇ~įûßŪķZŨ{åĖÅayķ†<TĪųãž–Ŧhzë;{Ũ8wážeC•ÈXĪŠ?6JšŪ9ôzCÍēÜP÷šfˇĨfŖCŋ„#"zé,šÛOÛą[íÛGb_žžÔ|˛§ĸU)**ÛPUˇĄČ&�b=­]įGJ%�rĶÁ˜Ú Æ`ŧ{Ŋ'äÜũũ=ĨV�XˇˇŠÎõ˙ÚÛvŋU1Ŋ ŖvoĨU •īĒ/čjévÅ*7 ˙b{@ŲųãĻJ€ÍÖØäüķÕoõ뎐Ī—ĘĒË6�VÛīČÕ3Ī "ŌZúÎė2oûgõ¯ûWŋk`°ŋõ‹î֖’ĻīŊ]§ˆđyŧĒ´Ņ1;č$l=ô� zŧĒuŖĶú`-šEŠxÕį ĸB�ÉĄ<¨˛É˛AõxCؐôĒ֍EɈ…ĨŠØáņDāKĘŦ×nžú$Y_[Q\TŦH'G爈´—Æ…@”ĨUŽŌĒ æšņŲߚĪ}Õ]ņĪ5R2–„(‰ æÅbĨ9{ĸÎh„ĒÆf^%ÜÚET5 ÄbI„Ž˙é_¯?˛.{,HEûpTžzõVÛŲöķqŅēfĶžÆ•ō–‰ˆh)Ō'j,ĶIsÃčܲkŨĩī÷Ž’ҍƒ‰-XĖh4>:]Åb0>˜X$ūđŊd,QÔ0ę`­øî;¯Í=eLÔIĶg•IuûŪŽÛ‡˜°¯­ųÜŠOu֟íwjļĨDD„t]ŧqü˙î“6˙ŖS#cÁ$›€\äCCžĐė[í˙Ŧ+ÅųčtŒ úTƒâ˜9ŨĄ!_pö-ŋ/ Š˛Ã ČEN1Œå$h´58ÜįōOŸ`f”‹6íŊLŒŒøæ-IzÎė2ĘöHÛÕÎģÁ`ČJÅ'ü^wû×§/ d•īoܒ§‡În ÜžŌ1¨ææfĢãŽK_6ŊžĩĐbĩ„{ŽÜÔ9œC2xīĘ?ZÜŌ–ĻŨĨ’€ĐŨëŨxØ?%įÛu‰‘[g›ģĨģTÉ:ŨînŊy/aw؍ˆŒö]øėĶ3.KM•ĸķ\úøo­Cz9WŌĢąqĪ­ÖëƒXˇck  Íąô3ģ˛Ž;ļpj °ÛíKY/ķt]šŌŪīö…â*DƒUvmjx}ëƒk>T˙–æéëK$ĨdëūƙËAÔP_ËŲ¯ī ú#0X•˛ē×lŸ~cđôoO UŋķšzĩųÖpH5ĘĨÛž}pËėM[BŽ–ķÍ]ƒūHģs]ũŪũ5N#�ÕÛuūÜÕģD VšhãÎ}ģÖYyė„ˆhŽĨwûé‹Í žū퉡ú˙d;/Ē'"ŌØŌģ}ŪQ˜ˆˆ4Ā8!"" ¤ųē-5ũËÍt DD´8'DD¤Æ i€qBDD`œ‘'DD¤Æ i€qBDD`œ‘'DD¤Æ i€qBDD`œ‘'DD¤Æ i`™žwˆâT<!ĸËĶāķą›ā´âP%ėĻEŪ]áÅO[ɛđäڈčå°qˆâũˈ&—ĄŠˆ"ŀŋÚ1ŋË[ųÅO[ɛđ„ÚˆčĨąƒ]§zVAw šÄŠžųWKņĶVō&,ZŊ4–#NüËЈ6<ĄųSVQņĶVō&,Ŧˆ^Ë1Ø•ŽŸÆ~ĩ ē>ŧįzĻ9҇÷Ÿ6įÂC ų]ī,Åģå°$ņQî�úđžweoÂĸŸđ‚/h%|"ĸ%ZĻCņÅŠv`"Ķe<ŗÚ"Ø#xī2|’ĢĸōÕö ‘æ2'Îĩ8Z{ mwáÜ“ īõ=fÎ")‡Ķ€p§nĄ#ōČâ÷f̎ā×;vÁ—‡z žAœ āđF8ÖNœō&Ēúđū(~Ŋá>¸ķ°Ë†pĮÛāNĨ§~ ~ŊŅAøŦ¨ĩ ėĮņN¸M3ĨF `š‡îÁ$ãp%Ē,@Ŋ÷pŌ…ÚØk€_7âx+öÖÍė<`wāpĘL‡qĒŨĪ˙“aŖáEĢM.֖eąĪđÁ'ėZė "ĸWž_w"áH%ėQœėB˛�N�Š‘pt#L~|p> ŽTÂ4ģ¸%‚nÂg‡2=g I ¸�ž´FPŧī:qļ nvUĀ>wĶsŽEԅîÁbĮÁ´Õ?Ũ–î;ø}Ly8R:;ąQž 8Z‡*āÄuœÆú -B[:â@ļĸwaԙp´œø}+ē­}tŸÅb.^íĸm=ų3\ô "ĸWĀrĮ‰E†辋ļQœē‹'ũ°ŽâŖ‹øā6Ü~tG Ë†}vņŪģč ĸų.sgĖ�`ā>zGŅL‹ôļQ/šGŅ{@भ~�@Ā‹Ö ÜƒčMB‘a™-õä "°8°^‡îģčđŖŖŨI”@A8¤ā /˛~KŠtģáŖy:ÖžskÔ˛XĩųoëqŸáž "zš-÷h„I�á�`á'T C}%ęeXĻ#/2gņ� ŽčœÅÃQ�HĻ� €¨ēøŠ§MŦĪ��IDATįDęéa°¤ú�ŅŲļÂI@‡é‹.‘™]‹iÎÚĻį1āÉ7ŊH}jg§8M@ü96á ÎĢ6wŅļ’Āã?Ã'|ADôr[î˙ėĶgYŒ�€lXđØ>Ŋ¸{ķĐz'ũØģ…ų‹ëž¸xš<{ũĶ,&耤�‹X°ˇ1Ũ)ĪŦmvžđW8ŊHGžŸ-éy˛äɍÎĢvlŅļžxbÆŋ "Ę”åŽ“đ8|@U)jS(.}RwŖ�@§CqęM�ā4ĄíŅÅM@tyˇāŲëŸf)@ãÜvTéāa/z+PUŽú  J‡ŪĄ§ÅÉ(Ü)”åÃ> {veãdÜĪĩ ot^ĩ#ĪßV8Ķ_eʲŠãDŠ†Î Īãg¸‡îękp؎]đ 8T ĶÜҟ´xē<sũĶ<Ã0•âhÂŖ8qoÁÛqoG/px ;ĐŨƒãCO[cĮ;áŗâ§ 8”ƒ^×sŸ™ö„FįWûmeü "ĸ É:vėØÂЁ@Ānî3†įØéG_ °'gOĨíÁ ûŲĖ9ÖôčËĶ æxÆúŸųÚIÍ=}ZŽjįÕFD+ÄŌģũå‰0éæ\˜mÂĪwÁÆņ;°—Bšƒ¨/…ōh!ŅZ‡20ėžđ…įŠņôMČŪ˙‘č%ļqR&ŖûÁUxQœė‘ ü´Ņ(Z;qvÉ õŧ 9­ķ§<R<ž§~)mU>ŅĶ7!sÖFD/åˆ“C•đ?üėÆÃËĐės3ép¨rūÄyÅãŲëāũgeŌÔ3nÂ"Ō_íĸĩŅKClll\85šLš L˜t¨-@ ŠdjĨŒēĖc7ĄLÆģ›)ZáÅO[ɛđ„Úˆh…XzˇŋLgqÚMxˇnyšŌŪĒ.~ÚK° D´ÂņYņDD¤Æ i€qBDD`œ‘'DD¤Æ i€qBDD`œ‘'DD¤Æ i€qBDD`œ‘'DD¤Æ i€qBDD`œ‘'DD¤ez#ŅjņM�ęĀDŠLWō,ĀlĀĒE‰=ķ•ҌoøŨ5„VI–�HĄ~w ß2\ ㄈčĄ?vb*Ķ5<ˇ,LMᏝŽ‚qBDôĐD,Ķŧ˜,D2]9ㄈčĄÕˇk2+ãŖsŒ""Ō�ㄈˆ4Ā8!"" 0NˆˆHŒ""Ō�ㄈˆ4Ā8!"" đž]DD/Č"ã`9Ö[`Ņ!™„/ŒÖ^´3]V†0Nˆˆ^„)ŋ܂čNŪE ]6jKqxL­hgē¸L`œŊˆ˛5°‡ņ›.ĖÜz1ˇēį�Œ""zF&˜×‡Ļpōōœ—ėŨˆvĸqôŪÃIĸ@C%đßo :;cũŅã7­�ëKqp-’Qtģpjđál+ÅŊˆ’ŧ[ƒ*Ëâ?ĖëĢqЌŗmøM >끺G � {ąūÁ2:ÔĘB�(ŽÄOËáîÁ{-øđ.Š+q´pšļgɸwBDô"ƒøP‡Ãåxˇ�HÁãGīZ‡˜Ŋc÷mŧøĸ�ˆĸu-sa„ŊpWĸ6Ã�`ĘCpĘ čĐXĪ]œ€ĀNÚđķR8‡āÉĖ&>Æ Ņ páŊ{pĘX¯`ŊŒŊÕØ[“mh @4…ú Ɂ]�˜�ÄŅÄÁ|˜†Öį~tĮœ:ü×īC˛ÅxâŲžįÃ8!"Z‚<ŖđŒĸ0Ųpt3m@÷5„چ†NŪÆĀ’@ũf4Î.Ô=„C•(Đ- 6ˇ ‡ØÕ€]ļ`ŌŒ"ĸ—•NS á9‰q֋_: �a j%´ĩĸmö2͜î6< ÷FÔĘĐĄ 8é�$ÚÚŅ<1§™ÂĢäX<Å=?~ž?];˛bâw5əé: Ǥ9=nm~”åcŊŒĸwzļÜ)ØuđEf˙őLōĖ."ĸ—X_ BŠĀ/7ĸV†Ķ‚˛<ŦÃá<tģā‚'…úĩP Pd­‚'�]6Šg÷QzG`ĘCcz=ŗ‘ÄÅ!Wā voïj`ÉÔ6>'vŊˆÛø}{‹p¸&É$|ãøâ:.NKãäm­Ā¯‹â‹ntáŦÃΎáƒËđ�a/6b} §FYįGI܈] ˇöŦšk"'DD/Č=„†ûŽoīĪ}7Œ÷ÎÎy™Ä‡gąPozû´)o™q°‹ˆˆ4Ā8!"" 0NˆˆHŒ""Ō�ㄈˆ4Ā8!"" 0NˆˆHŒ""Ō�ㄈˆ4Ā8!"z(+Ķŧ0!ĶĨ3Nˆˆ2ŠLņĻ 2\ㄈčĄÔ +Ķ?ķ_@V~P“á'DD•Øņ‹m°3?vôŒ„,XŒøÅ6”Ø3\ ī(LDôˆ;ū۞Lą qī„ˆˆ4Ā8!"" 0NˆˆHŒ""Ō�ㄈˆ4Ā8!"" 0NˆˆHŒ""Ō�ㄈˆ4Ā8!"" 0NˆˆHŒ""Ō�ㄈˆ4Ā8!"" 0NˆˆHŒ""ŌĀâq"ÂÔÔj|\2=ˇŠŠ)AXęŪÅâËëtēx<žÄUŅĒĮõzũW˛xœdggĮbąh4Ę}"ĸ—ØÔÔT4Åb&“i‰ĢZüYņYYYVĢ5‰„BĄT*ĩÄ6ˆˆheAEĢ՚••ĩÄU-'�˛˛˛Ėfķ×NDD¯žŲEDD`œ‘'DD¤Æ i€qBDD`œ‘'DD¤Æ i€qBDD`œ‘ū?û\CÖ%´˜ī����IENDŽB`‚�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/profile-webauthn.png������������������������������������������������0000664�0000000�0000000�00000070553�14156463140�0022645�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��\��˜���8öĖ���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨwTT×ÚĮņßĐģĸ‚Xĸ‚‚ bT°ÅŽQcbK3Ŋ™˜äMŊš)7Ŋ÷äæĻ5ÅŽąÅM4 öv^įũc`dčČ˜|?ką}Ę~Î>gyfƒŅh4 ������VcS×������üŨp�����°2ģ˛ ķķķ•˜˜¨ėėlåįį×vL������W[[[9::ĒI“&˛ąšØ¯ÅPr—üü|9sFNNNrqqąØ������(33S™™™jŪŧš9R*ᒐ š¸¸ÔI ������WšôôtIRãÆ%•1‡KVV–œk7*�����€+˜ŗŗŗ233Í?—J¸äååÉ`0ÔjP������W2ååå]üšc�����ø["á�����`e$\������ŦŒ„ �����€•‘p�����°2.������VFÂ�����ĀĘH¸������X™]mTb°ą­j������ĒĖXŲÎM������+#á�����`e$\������ŦŒ„ �����€•‘p�����°2.������VFÂ�����ĀĘH¸������X™]]�����ø{;rü¤fũ8Gi™*((¨ëpp3 rsuŅíˇLQĢÍë:œ ŅÃ����pŲ9~RŸ~ņ­.¤Ĩ“lAFĨĻĻéã˙}ŖĶgâę:œ ‘p����\63˜S×!āīÆ`ŅX ¯gū\בTˆ„ ����ā˛IMO¯ëđwd0čBjj]GQ!.�����āŠc4ë:„ ‘p�����°2.������VFÂ�����ĀĘH¸������X ������+#á�����`evu@m™>ũA-]˛D#"Õŧyķŋmåi×ÖOÁÁĩpŅĸ:����ĒËÕÅE]:Š­okšģš)'7Wņ ‰ÚŊī€=V×á]o¯Æ7j¸ÚųJ’ū:tT‹—¯ÔšøÄ:Ž ÖrÅ%\ŒFŖVŽüU‹-ŌÎ;”xūŧ$ÉÛËK}û†iĘ 7¨[ˇnu%����† ׈Áåčč`.ËË˓­m † בØãúqŪ"ŝK¨Ã(ĢĮÛĢąđ^9;;™Ë:w ”ŋ_ŊķÉį$]ū&ލ„Krr˛|`š6nÜ(WõíÛG-Z´Tzzē<¨9sfkîÜ9zâ‰'u˙´iuî?ڜ9sôׁeÎ.ÎōönĒūũûĢuëÖuŲåņîģī($$TũúõĢō1ŋūēBĮbéŪûîģŒ‘YĮąØXEFEéĖ™ĶĘČȐŖŖŖZĩj­ž}ûĒe˖ĩËĨ´u}PÖkĸ¸Ā  M˜0ĄŌã¯<X}ûö-ĩ===]|đžŒFũû™gdcSĩŖ%Ûķr´¯ŅhÔŽ]ģ´}Û6ŝ‹S^^ž<<<ÔŽm;õ “ģģģ$iŅĸ…ÚŊkw…į9j”zôčQkŋcƍ+gggũ<{ļEų† 4õ–›õō+¯ęæ›oļØöØŖjũú?ŗĨJutīÖUˇßq‡ĻOČ*1WGŸŪĄē~Â=ūøĩ^7��•1 ēíÆ‰ęŪĨSŠm{öÔĪķĢgˇÎ=|°đ^}öíŦ+ώËĩŖ‡[$[Š8;;iܨáúōûë ǚ›rũXíŲ÷—víŨo.{īĩįeogJ=\HMĶæ-ÛĩtåoĘĪΝĢ0kÍ“p1šūāÚ¸qŖŽ3F/Ŋô˛<==-öŲąc‡ĻŨŸŪzëMųûûkČĐĄu-$Éŗ‘§Ž}ųįÔ´4mŨ˛E3gÎĐíˇßĄ-ZÔatWĻččh9sZcĮŽĢĩ:ÅÆę‡PĮŽ5nė89ģ¸č…ElŒĐŦY3uĮwĘÛÛģÖâ2dh­ÖgMž<5jä¨2ˇš¸ēVzŧŊvíÜYfÂe÷îŨ˛ąąQ~AõūãēÜíi4ĩ`Á|íÛˇO;vTž=åā`¯¸¸sЉ‰ÖžŊ{tĶM7ĢiĶĻęÛ7L]:w1ģø—ÅōōōVß>}Ėe›41˙ģ6~Į„÷ ×W_~ŠĖĖL9;;›Ë#"6Ę`0(22ĸTÂ%**Ráõ4!8ãûīĩsįNŊķîģu ��•;rhŠdËĄ#ąúōûŸ”‘™)IúũĪ(íÜŗ_Ķīš]wßzƒŪúđ3OJŽ‹pĢ% ­_ųÛ ‡]‰ÂB{ĒO¯îúæ‡9ÚąkoŠíîn20\’´xųĒÚ¯Ö]1“æūļfūüķOuīŪC~øQŠd‹$uéŌE˙ûėsM™rƒÜ ?1­H||ŧžî9…õíŖ�˙vęŪ­Ģîžû.íØąÃŧOīЍ1ŧÔąÃ†‘o›ÖZˇv­Eų/ŋ,–o›ÖZ¸pEųĨž'<Ŧ¯ÚúųęôéĶĨŽMJJ’ģļēnüxsŲēĩkuÍ5ŖØ^=ēwĶSO>Š”””JÛârp°wPë6mĖ_:uŌ7Ũ$wwwEGoŽ“˜ŽtgĪžŠõ:ŖcbäÕÄKãÆ]+ŋļmÕŦY3ĩo¨oēIž =uâĉZ§K—.jÖŦY­Öi-öōõķ+ķĢiĶĻ•ÕU­¯3gJ?ģwíRķf՟+ęrˇgLLŒöíŨ§k¯¯ņã¯SĮŽåī đđpŨsĪŊrrtŌ‚ųķUPP ooo‹6ąˇˇ—ģģ›E™‡‡‡ųÜĩņ;&<ŧŸrrrmQŠ~ũú)*2RFŖŅ\~ôȝ>}ZáááVŠßÚvīŽ¸��õ…ˇ—Žîoų!ĶŲ¸súčķoÍɖ"į“’õųw?ČŪÎN׎V›aĸ 666ēãĻIęÜA’ôčŋ_Ōô'Ÿ×ô'Ÿ×ûŸ~%I éŅĩ.CŦ5WL—…‹J’îŋ˙ū ģĘwîÜY;wŽô|‰‰‰í8]¸ĒoēQíÛˇ×™Ķg4kÖLMš4Qß?CŊ{÷Vxx?-X0_ÉÉÉjذĄ$)!!A”‹‹Ģ6mŪ¤AW_m>oTd” ƒÂÃûimą$ĘĨžįŽ;īŌË/Ŋ¨ æëÁ§[\Ê+”——§ë ‡!DGGëîģīR“&MôĐô‡Ô¨qcmÚĨģīēĢĘà .7;;;y{7UŌų$sYAAūܰA{öėQrJ˛x4PHh¨zöėiŪįŨwßQxx?9rDąąGõ˙÷¨âââôĮīŋ+î\œ äĶÔG 2%ČËËĶīëÖiĪŪ=JOO—›››‚;kæöxīŊwŪORR´gīeggĢUĢÖ翚käææ&É4Tc͚Õ:zô¨233ÕĀŖzöęĨ*_wjjĒ–.]ĒØØŖrrrRî=JíSY=3gĖĐącĻ.’;wėÔ]wß-ww÷ĮV™üüü2ģû9::–U•{yėØą ī[eÛKyąÖ}ŽŦŪúĀÍÍUM›6ÕŽ;-’$ :sæŒú`‘�ĢĘŗ{š‡hmŪŧIž~~ęÔŠtW` :DsfĪŅĄCĐžÆõ•õ;Ļ&zôč!gEDlT˙ūũ%™^ĪģwīŌgŸ}ŽģīžKû÷īSPéŠČ¨(IRX˜)ᒗ—§O>ųXK—,ŅÉS§ÔŧY3Ũ~Įēå–[,ęÉĪĪ×Ë/Ŋ¤… (3+KũúõĶ›ožeūp!!!A¯ŊúĒ""6*9%E͛5ĶÔŠˇęļÛo7ŸŖC‡ =ōđ#ēįŪ{ÍeO=ų¤öîÛĢ%K–jĘäÉÚ´Éßüųķ´tŲrI’­­­>üđ͚9SRSÕˇOŊũÎģjR؛¨gîzđÁé:uú”–.Yĸ´´t…††čĩ×߸b{›�ęŋđ>!ĨŪÃø4õÖGožhūųāáŖZˇ!BģöĐŲ¸sŠŠŲĻ~}BäîîĻÔÔ4‹cG ¤‘C•Y׊Õë´|õ:ë_Dū:|DÁËŪvčhĨĮסë)Š(éR˛§Ë‘Øã’L=]ū ęĮģđ*Øžm› ƒú”Ņ•ūR|đūûŠ‹‹ĶŦ~Đŋūõ´ÆŋNĶx@ķæ/ŊŊ^{íUIĻîäFŖQ1Å>ŨŒŒŒF­Í›,?EŨ´)JA:ČËËËĸüRĪsà 7ČŨŨC æĪ/u ˗-•Ŗ““ÆŒ#Iúī'+??_ŸņĨĻ=đ€ĻL™ĸ÷ß˙@ĘÍÍ­YƒYQrR’Ü‹}JŊfõjEDF(,<\÷Ū{ŸB{÷ÖĒU+ĩmÛ6ķ>ļļļÚļuĢŧŊŧtķÍĻ7*ŗg˙Ŧ&^MtÛmˇ›†ĩ4õÖO?ũ¨ĖŒ÷ŠËĩ}Įv 2T÷Ũwŋ ēZŅ1ŅúmÍ‹ķFFF¨‰——ĻOH÷ŨwŋΞ=Ŗ ֛÷Y˛d‰Nž<Šņã¯Ķ=÷ÜĢžaaZŊz•¸8.ą2‹/V|ü9Ũ0åŨ|ķ-ĘČĖĐž}û,öŠŦž‰“&ŠYŗfęĐąƒ}ė1y{{[%ļĘøûû+!!AķæÍĶŠS§,>Í/О{™““Sá}Ģl{YŦqŸ/ĨŪKa4•——WæWEíZüø ´{Īn˜ËwīÚ%oooyy5ąØŋ6žŠ¤ĻĻ*é|’|}ËīëëkęN{üØqĢÕ[ōwLM888(4$D7n4—mŠŠ’Ŋƒƒ ¨ļmÛ*2"Ōŧ-22Būōņņ‘$ŊūÚkúüķĪu˙´iúuůēãÎģôĘË/iv‰9aæÎ™Ŗüü|}÷ũ ŊũöۊŒˆÔŗĪ>cŪūԓOhëÖ-úđŖĩ|ŲrŨw˙4ŊōĘËZĩre•¯å‹/ŋT§ā`;V[ļnS` éŧeK—*é|’žųö;}øá‡Ú˛eĢ>x˙}ķqöööúüķĪäī  nÔĘUĢ´k×.}üŅGÕkL��Ē!Đŋm™å>Ēu"uö\ŧEš““Ŗd0ÔžŒá:ËW¯ĶŠ2’u•œXŧl•23ŗJ•gffiņōĘ˙¯×ķĖãĶõņ[/Y|W˛§Ë?ŅĶÃ%!1Qîîr­Â<•1ZļlŠÚĘĮĮGįΝ3oŗŗŗSŨĩ~ũzĨ§§+,,\ƒA›Ŗ7›į„‰ŠŒRûĀ@õîĶ[ .PFF†\\\§#GŽ”9 ęĨžĮŲŲYcĮŽÕ?ĖRLLŒš§@bbĸĸĸĸ4jôhyxx¨  @›6oVëÖ­ÕĨK‹ē'O™ĸ™3gÔ¸Ũ.Eņ7…iiiЉŽVbbĸ†7 ¯ĘÎÎVĖ–…‡…›{&5jÔHgΜVDÄFķŠSƒAööö<dˆ$Ķp°œėuęlNn >B‚:ČÎÎNÚšs§† ĒŽ;šĪ›˜ M›7éęÁƒekk+Ijܸ‰ēv5uiķđđPÛļmuæôÅ!Æ “Á`0ŌܸqcÅDGëȑ#jßžėŦtq.\PėŅŖ1r¤Úžņ>|„Ž>bą_eõ899ÉÆÆFvvvrqqąJlUŅŊ{weffjãÆ?ĩß>98:čĒĢZŠ}ûö –ŊŊŊ¤ĒŨ˔”” ī[rrr…ÛK˛Ö}Ž,.k9wîœŪxũõ2ˇŨqįUZ>žS§Nú}Ũ:>|Hūū2ÚŊgˇēv-Ŋ:[m<IMM•$s¯ž˛ØÛÛËÕÍUR/\R•ũŽą†đđ~zíĩWÍ=###ÕŗGŲÛÛ+$$T‘‘ēãÎ;%IQ‘‘æ9–RSS5kÖLŨ?mšŽŋŪÔą¯¯vīÚĨĪū÷Š&OžlŽŖI/ŊđĸéģΝ;kīŪŊsĮ<÷Üķ˛ąĩUĢV­$I~mÛjƌīĩaà ĢâĩzxxČÎÖNöjÔ¨‘šÜÍÍŨ\wpp°VŽ\Ší;ļ[ÛŽ];Mš4I’Ôŧys 8H;wîŦv[�PUž Kx˛tåoZųÛĻ–˜ž ī­6­ŽRP@[õîiú{ČĶŗA™į,JDõ ŠËž qņ z÷“/4nô0ķ|.>ĸEËĒž,t]_ˇWĨû%]žšņ“ví-‰ŋĢ+&ábcccņ‡uq'LPLLtŠōí;vĒAƒŌ/ļ„„%%%)))IĄ!ŊĘ­ķôéSō÷PûĀ@Eožxū¨¨H 0P!!ĄĘËËĶÖ­[ލ(ͧœũúõ/u.//¯K>Īä)SôÃŗ4oî\sÂeŊĘĪĪ×ĉĻ?€ãâ┕™ŠĢŽēĒTŨmۖžÜâââôÚ̝Z”99;éš1cäWĶŲŗgU_ _?Ë,tëÖm´}ÛvåääČÁÁ´ü[‹–'Ālܸą7nŦE‹ĒGžōķķ“Zˇi#I:uō¤ŒÆR“f6kŪLš9šJLL4w…oZĸKŧŗ“ŗ2ŗ.öjpppPÄÆŠUFf†ŒFŖ233Õ¨q#UEb‚iyēâoĻ ƒšˇhޏŗq5Ē§ĻąUUXX˜BBBtôč=rTGŽŅōeË´aÃzŨxãMōōōĒŌŊŦėžUļŊ¤sqqVšĪÕ­÷R5jÔHcĮ•=áqŅđ‚‚åää˜ËmmmÍI-ɔŧhŲ˛ĨvîÜ)˙�?~\ÉIÉęØącŠ9~jëų(Á`0_SEŒFŖyßę¨ĘīkīnJjGEiøˆŠˆŒĐ5ט&ëíŨ§ˇž}æYåįįëđáCJHH0Ī߲wī^åää(<Ür¸Vī>Ŋ5{öĪJOO7ˆĐĢÄ˙EŨģuWnnŽŽ‹U``\\]õŲ˙ū§Č¨HOLTAARRR*ė=TU=zXqlܸąļëa(Ií-t <táBŨĖ�øg:wîb˛Ĩ˜zėAËE *ę9\<!Q×ÃnââôÅw5[¨>]OylllÔŠC —ú˧iSÅÆÆ*))ŠÔ„šC‡U@@€ųį˙4ĪuQ–ô4Ķxž;ęÉ'ž,w?ooĶ$–áááúîÛo•‘‘Ą .čȑ#zōɧԲeK5oŪ\›6E™%‘QrvqQ¯^e'q.õ<ÁÁÁęØŠ“–-[Ļ^|QNNNZžl™š7oް°0IRVá°'§ŌK‹999]Ō›™šjÔ¨‘Ž-6Ą¯ŊŊŊ5jdîq I99Ų’¤™3gXÄXôK2--ÍüIŦ“ãÅkŗąąŅÔ[oUdD„ļmÛĒuk×ĘŖ‡¤Î;+ģđŧŽŽŽ19Ø;Ö{ņ mņ7ŗ%åįįëĮ~PAA†ŽÆMšČÆÆFsJ ¨HQ,%{J%’.ĩkÄVööö hožg#öčQ͛?OkÖŦŅ 7ÜPå{YŅ}Ģėž–d­û\Ũz/•ŊŊ}ĨËh=rD?ũô“ųįÎ]:—Z•Ēc§NZŗfĩ˛˛˛´gĪnĩhŅBžžž —Ú~>ĘR4ÁmRRųķŠäææ*#=C <Ęū$Ē"Uųc íÛĘÛÛ[ Ձũûõō˯H’BC{+5õ‚öėŲŖíÛļÉŪŪ^ĄŊ{K’Ō {øÜtã Rņ×Da*ūÜ9š&LÜKLôî\؋-##SšššēõÖŠĘĪËĶķ˙yA~~~˛ŗŗĶ=wßm•ësvqļøŲ`0”úCĩŦ˙[Ē2 �€K•”|AMŊ/—ÎÉÍ+sŋÅËWéHėqŨsۍæ˛ääŠ{ÎÖebÂŨŨMcG Q§rqvŌĄ#ą:yúŦ$ŠesĩķkŖŒĖ,íŪģ_ŋüēĻÔ\4eŠĢë)((¨Ō\Ą‡Ķü_VÔBDõĪ“péŲŗ—bccõûīë4~üuۊO(Iͧ?XaÂÅÕíâ=Ŧ´î~áũô՗_jëÖ­JHˆ—Á`P¯ÂI'{ôėižeĶĻ(õ ĩx#m­ķLš8I˙ųĪķZģö7õėŲK›6EéūiĶˏcáÃYYĨĮϧ§×ÉÆööö•‘pp0ŊQžöÚņeNžčQÁ< ŽŽŽ2t¨† ĒøøxEEEę—ŋդI9ž7;;Ûâ˜ōŪ —įÔŠS:wîœn™:ÕbōÔŒĖ 5ô,˜DqEoūKÆRü^]J=ֈ­*ŌŌŌäāāPęšnãëĢĀĀ@:tHRÕīeE÷­yķæ•n/ÎZ÷š*qՖ-[ję­ˇZÄUR‡´jÕJíßŋ_ûöí+ŗW]m=quu•———öîŲŖđđđ2ŋGš†Ö]JOĒüŽą–°°pEĮD+&:ZNÎÎæĄ›M›6•ŸŸŸĸ7oÖÖm[ÕŊ{ķŋĸydŪ{˙}–1„ĢYąØKΔ™‘!IruuŅöíÛĩß>͞3×bÂãķįuUĢ‹Ŋ *ŨžYŲĨ˙O��āJ°˙āa‹„ËU-šŠW÷.Ú˛}WŠŪŗÁ/ū?k4u ÄĐũúÂŨŨMOLŋWž /~ĐĐÎOí,{ˆģšē¨w¯î hį§w>ųĸJI—ē0ãįųjZbÁ’ų>zLŸ~=ĶâƒĐ’+fŌÜÉSĻH’>ūčŖObéåå%OOO>|¸Ė%“-ĮĖ…„†ĘÁŅQ11ҊŒˆ”@€š×E¯^!ÚžcģŽ?ŽŖGĒ˙åÖ[“ķŒģöZ99;kéŌĨZļtŠ 4ĄpN�Iōöö–ƒƒƒNž<YĒŪ’“ŗÖ'>>>˛ĩŗUzzēš4ibūrvv–‹‹Kšķg$%%YLüéååĨQŖFË`cP||ŧšúøČ`c(ĩdņŠ“§äč䍯W)žü<S&Ŋč ”$8qBÉIÉUNb5*Ŧ+.îâđĄüü|‹¤`uę)úŲąU&--M~ø"#"Jm3JLH”›Ģ)Y•{YŲ}Ģl{IÖēĪÕ­÷rrrrRĢV­Ė_e]ƒĢĢĢüüÚ*2"B™™™ęĐĄôDdĩņ|TEHh¨âããĩe˖RÛ222ôۚßäããcžß¨ž īŽŋPDD„yū–"ĄĄŊĩ}ĮvmßļMáũ..$GG%&$Ēmģv毆žžjÔ¸ąEB0&:Æĸž;wČÁŅQ­[ˇ1'=‹%ĘļlŲĸ'NXÜKwwˇRsáėß_z‚dzĻ��ŽFE[$V ƒĻNš^žņ‚îēeŠšÜŨÍU}zu7˙ŧ}מz› ;r¨E˛Ĩ2<jėˆ!—1ĸšŲ˛}—–ÎŗŧŒųcĘJļøĩ1ÍGwĄžŪ#kģ‚z¸ôÔøņ×iáÂēuęTŊ÷ūûĨēægggë§Ԛ5käęęVnOI=ú͚5S_|ņšž(6Ŧ(11Q#G WįΝõÕ×ßH2ŊęŲŖ‡ļmŨĒØØX x1kŌK9ŲŲúæë¯%ŠÂĨUkrž høđáZšrĨŽ;Ļž={YŧAąŗŗS÷î=Š;vXLœ[WæV…ŖŖŖēwëŽõë˙‹‹‹š7oŽ ))Zĩj•Ü=<4eʔ2ģ’ĸyķæéęĢËßß_ƒAģwí’Á`P˖-åėėŦŽ]ģ*"bŖ5ō”O3;vLŅ1ŅęÛ§o•—ÉönÚTļvļŠŽŪŦ~ũú+ūÜ9­]ģVž~~:ŸxŪb†ōÍšąqãŸjäé)WWmŪŧÉbØCUëqtrRÜŲ8={Vnîî5Ž­2nnnęÚ[ūÜ ´ô4ČÉÉYiiŠÚąc§Nœ<ĄëŽģ^RÕîee÷­˛í%Yë>WˇŪK•“­Ã…=‚J2 ՚w¤S§NZŧh‘Úøúš—ļ.ÎĪŽ5tíÚUĮ͝+Včøņcjß>Pö:w.^11Ņ2š8iR {ŦŽđđ~ĘĪĪ×… uo‰^•ŊûôÖķĪ=¯””d‹ųZÜŨŨuÔôÁīËŗ‘§ētéĒS§Néå—_R3}ũ͡æ}Oœ8Ž?ūHcĮŽĶņãĮõÃŦY9r¤œœœ$G''}÷íwzčá‡uāĀŊõ֛ęׯŸŽ9ĸ„„5iŌDÁÁÁZĩj•îŧķ.šēēęĢ/ŋTrR’ŧ›65×Ķ ‡öėŨŖ={öÔjĪ-��ĒëlÜ9­]Ą!ÃKmkXlBŨëÆŒ4˙;##S‹–­Ē•ø.Ep‡öÕ>ĻS9KG×wœ-īŊöŧėK|žyËörŽü{šb.’ôúoČ(Ŗ-\¨ÁWRHHˆ|}ũTPP Ķ§OiķæhĨ§§Šc§Nz÷Ũwåėė\îš~ä­]û›>ũī.^!ĄĄ:§~˜ĨäädŨzÛíû‡‡÷Ķ'Ÿ|ĸŒŒt…„^ėŌĐ^ 6ÔÜšsÕŧysĩm׎Âk¨Éy&Ožĸŋiīž=zã͡Jmŋ÷Ū{ĩiS”îŧãvMœ4Iž =ĩyķ&effĘŨŨ:K¤^C‡ ““““~ûmŌŌŌ俿π€� tušĮ´nĶFcƌÕύ(ũņĮī˛ąą‘ˇ—ˇ&Nœhî0bÄH9:8jŊJOOWƒ ŪĪ<īMU¸ēējėØqZģö7íÜšS͛5טąc•–šĒų ækÖĖ™eŽJUŌĩãĮkéŌĨš=gļÕŖ{uîlūôšĒõ„„ôŌâŋõũ÷ßiÂõŦ[e"/oomßļMK,QffϜœœÔŦYsŨxÍI‚Ęîee÷­qãÆ•ŪגŦqŸĢō<YCrR˛Åü,Ål zæ™gĢ|ŽöíÛËÎŪÎŧ:SIÖzvkĘ`0hėØqjÛļļmŨĒå˗)//OęÔA}ÃÂj%ņSSŪŪŪ hß^8`žŖĨHhhoĨ¤$ËÃŖ‚ƒƒ-ļ=ûÜsōhāĄ7^]ņņņōōōԐ!CõøO˜÷ÉÍÍĶ´ičäɓēvÜXeegkĐ AzņEĶԊ7ÖÛoŋ­ˇŪ|S .Pp§`ŊķÎ;Š;§|P7ŪxƒV­Z­Í 2v�� �IDATgžyVO<ņ„ÂÂúĒaƒš4y˛ŽģūzmذÁ\×mˇŨŽGũ?Mœ8AŸũīŗËØb��ÔÜ/+VĢ‘gCuīŌÉĸŧy35ōl¨nÁÕŗ›ižŊŦŦl}ņũ:Ÿ”\ĄV‰kąžĮUåæZũcęRnnŽŽŸ<]î0ĸÔÔ4mŪēCKWūVŅÕ>ƒąDßâØØXųøøXˇëN`Š9sf+&:Zņ ˛ĩąUĶĻŪęÚĩ›FĨÁC†”ú´túôĩtÉmŒˆ4ĒwîÜ9}üŅGZģö7;wN..Ž Ņ<h^>ļČŽ]ģ4vŒiUŠÍŅ1æĨc%éîģîԚ5k4yōŊņæ›Öy)į).ŦoOJRttL™Ÿj/Yō‹ūûß˙ęč‘#rss͐!CõīgžŅ¨‘#äŲ¨‘–.]Våv���€šzüŲ—ktüĐAũ4üęrt,{ÃŅc'ôÃ܅Š;—PŖz.ˇ^˙Oĩ'÷ĪËĪ×˙=ũâeŠČúš4öTrJĒōōʞäørxį•įjtŧą ßJ‘˜œ={Vm W;Ŋ".˙T§OŸÖĀũ5iŌdŊRb)T����¨jšp‘LŊCēw_›VrwwSnnŽâÎk÷ž:t$ļæAւą#‡ęęū}̜tÉĪĪ×ÚõúeÅęËŲ•­>'\ލ!E˙t¯žjZ†ôŽ;î¨ãH���� ö¤gdhãĻmÜSųÎõÔ/+V“<ų‡!áRĪÅ=Ē 6hõęUÚ°aƒ~ä‘jMŦ �����j —zn˙ũúĪž—§§§žxâIŨ?mZ]‡�����*AÂĨž1b¤Ž­ë0�����@5ØÔu�������7$\������ŦŒ„ �����€•‘p�����WƒĄŽ#¨ ����Āeãîę*uūnŒFy¸{Ôu"á����¸lnšiRũīŠ€+AwNR×QTˆ„ ����ā˛ņkÕRĶîš]2x¸šēęáûîRsŸĻuJ… FŖeßŽØØXųøøXˇ[Ģž����� ĻŒųV=ßŲŗgÕĻMIôp�����°:.������VFÂ�����ĀĘH¸������X ������+#á�����`e$\������ŦŒ„ �����€•ŲÕF%ƂüÚ¨����� ^ ‡ �����€•‘p�����°2.������VFÂ�����ĀĘH¸������X ������+#á�����`e$\������ŦŒ„ �����€•‘p�����°2.������VfWVáącĮj;�����€+šŖŖŖųße&\Úˇo_kÁ������üÄÆÆš˙͐"������+#á�����`e$\������ŦŒ„ �����€•‘p�����°2.������VFÂ�����ĀĘH¸������X ������+#á�����`e$\������ŦŒ„ �����€•‘p�����°2.������VFÂ�����ĀĘH¸������X ������+#á�����`e$\������ŦŒ„ �����€•‘p�����°2.������VFÂ�����ĀĘH¸������XY­$\ĻÜ4U~ôÂ˯V¸ß !#äĐA¯ŧöF…û…†õ—_@}7cæ%Į4oÁBųtĐ͡ŪqÉį¨ĖŒY?Ę/ ƒü:č÷>¸lõ”eų¯+åĐAã'NŠÕzkjöœyō č [ī¸ģŽC1Ģ1����ęˇZI¸ 8@’´aßåîsōä);~\’ôᯈr÷ûëā!ÅĮ'H’ôīoÅ(­īį9sÍ˙žˇ`Ąōķķ­^Į /Ŋĸ.ŨCŦ~ŪË­>Æ]cÂßĪ���đ÷W+ —ũûI’ŽÆĶŠS§ËÜgß%INNNúëā!Åŝ+sŋĸdLĢĢŽ’o›Ö—!ZëØąc§öī? ß6­ÕŠc;¯ß˙Xoũzvíļú9kCyqO¸~ŧöíÚϝ>˙´–#ǟ1áīéJ}Ũ���¨ēZI¸ļOĶĻ’¤õåôr)J¤\;nŒ$icDd…û xeôn5r„FQX6ĪĒuäææjßŪ}V=gm¨(n[[[9::ĘŪŪūūžŽÔ×-���€ęŠĩIsô—$mظąÔļ‚‚EDm’‡‡‡ŽkJ¸õx).'7W›6GK’öš)nßžũzė‰)Ŧ˙Õjßą‹ēõęŖ)7MÕü‹d4ˌË`0(++KoŊķžŽĀŽ]Ô#´¯|č˙täČŅKēÖ´ôt-YļB’4nė57ö ũūĮz;_æ1ß|7C~tĪũ–š}ÆĖ,ļ˙ßcOĒ}Į.ĘÉÍUjZšyޘ?Öo°8ÎÖÆFúōĢo4lävęĒÎŨzéæ[īЖ­ÛĘŦĢ:í8wūųtĐSO?Ģüü|}öÅW>jŦ‚‚ģŠsˇ^ēåļ;-ęŠ,îŠæKŲžc‡Ļ?ō˜z‡P@‡Îę>@O?û|™ŊĄ 4{î|MšiĒēõę#˙ `uîÖK×MŧA?ū<[V‰iëļízđĄ˙3ĮÔĨ{ˆÆOœĸ/ŋūVYYY5jĢĒĒNģÔ$æŦŦ,ŊūæÛ 0XģhĀāaúôŗ/ĖĪÄėšķ5jėxwSמŊ5ũ‘ĮJ=īEĪņãO=m:ß[ī¨ß ! ėØE=CÃôāÏ*öØ1ĢÆ]ŨļŽËį_’>ŦĮŸzÚÜ.ēöÔÕCGęų^ÖŅŖąeÆ ��� ūąĢ­Šôī¯ŲsįkcD”ōķķekkkŪļk×nĨ¤¤čęAÔ9¸“ėííĩ1"RFŖQƒÁŧßļmە™™)GGGõĩœ˙`Ųōzô‰)77W]ētV˙~a:¯Č¨ÍÚŖ?6lĐīž-Ë“­­­nŋë^íŨģO!!ŊÔŠSGEĮÄhų¯+õgD¤͟­6­Ģ7tiɒeĘČČP—.ÕŽm[IRŋđ0­ßđ§æ-X¨i÷ŨSŨæ+eđՃäčč¨9ķæËÁŪ^ˇß6U’i¨UqÎÎÎzâŠkåę5ęĶ;TūÚŖˆČ(ÅlŲĒĨ‹į›c”ĒߎŽŽŽ’¤ôŒ =úÄSZģîwõéŨ[­[ˇŌö;ĩ1"RŅ1[´ü—…ōķķ­rÜ%Í_°HOũûYFuíŌYƒƒuđā!͞3O˖­Đŧ9?)Āŋy˙§ūũŦæ/X${{{…†ô’W“&JHLTdÔ&mßąC;wîÖ¯Ŋ\­ļ,釟~Ös˙yI’Ô­k ėß_É))ŠŽ‰Ņëož­eËWč‡ßÉÕÕå’ÚĒ*ĒÛ.5‰yúÏjĪŪ}ęŅŖģΜ9Ŗ­Ûļë÷>ŊŊŊ˛˛˛ôég_(ŧo5ķiǍMŅZļ|…Nœ8ĄEķį˜ëwpp$ĨĻĻéîûЎ;ÜI;t0ŊîVüĒ?7FháŧŲCkĢ­ëúųßškˇĻÜ4UYYY TĪîŨe0´kĪÍúņ'ũ˛t™~šų‚‚Ģô|����¨CÆŽ=Z˛Č*RĶԌūAÁF_˙ 㖭Û,ļ}üß˙}ũƒŒ_|õŅh4§Ü4ÕčëdÜģwŸÅ~īŧ÷Ņ×?Čx۝÷X”Ÿ<uĘØŠĢŅ×?ȸ`áb‹mąĮŽ\=Ėčëdüéį9æōšķ}ũƒŒí;v1Ž?Á˜””lŪ–žžnŧnâŖ¯ņéTûZĮŽŸPĒže+~5úú\=ĖXPPP昝ŋũŪčëdŧûžĘ<į÷3f•Úž˙Ā_F_˙ cįnŊJí_T_įî!ÆŖĮOž<eŪ–’’b6rŒŅ×?Čøō̝›Ë/Ĩ—-_aôõ2véj9æZãŠĶ§ÍÛ.¤Ļeôõ2žúú›UŠûįŲsžūAÆŠˇßeQw`§ŽÆ€‘Qæōüü|ã+¯Ŋaôõ25Æ\~đĐ!Ŗ¯Ņ/ ƒqĮÎ]įßŊg¯ų9Üā¯KŽéĐĄÃæķŦ\ĩÚb˙””ãˆŅcK]÷Ĩ´UEĒÛ.5‰šs÷ã͡ŪaLKK7o{÷ũžūAÆ!}}Â÷í?`Ūļwī>Ŗ¯Ņ×?ČĸŧčuØŠĢqĖĩד“/žî.¤Ļ¯0ŲčëdŧwÚôZoëúđüß÷ĀtŖ¯ņ÷>(ĩ­č÷ä´.ĩ ���@ũP<§RkCŠÜ\]ÕŖ{7IĨ‡ ÍËŌ/Ŧ¯$)Ŧo‹ō’û•N4c֏ĘÎÎֈáC5ūÚąÛZˇjĨ§ž|Ė´ß?–Š+''G/<˙Ŧ6l`.sqqŅ˙=ü$iÍÚuĘĖĖŦōuîŨˇOģvī‘“““Fi.2øjyz6Ôņ'ĩis•ĪWSŠŠŠzõåÕĸEss™‡‡‡šŠ߸¤v,ėtáÂŊüÂķjŪŦ™y“ģ››&^$ißū—| sį-PvvļÆŽ­>ŊCÍå666zô‘‡Ô˛e ŲØØ˜'dvws×ĮŧĢwß~Cƒ;YœĢc‡ uëÚE’´eëÖKŽé‡Ÿf+//OÆҰĄC,ļyxxčą˙{X’4gŪååå™6Xš­ĒÛ.5‰955U˙yîßæ$’4yŌIŌų¤$Ũuįí l`Ūhnûƒ•Š=;;[Ī?ûo5hpņuįîæĻĮ cX÷ûĘČȨqÜÕiëúđüŸ<yJ’Ô­k×RÛîŊûNÍøöks����ęˇZK¸HŌĀωn‹Oœ›žžĄ­ÛļĢqŖFj_ø†­oŸŪ’,į{IIIŅŽŨ{Lį)1andd”$iđ AeÖ; _¸ ƒöī? ””‹m 60ŋ/ŽWĪî’L ™Ã՘Ë姟‹&Ë.w77sšƒŊŊƏ3Ŋ‰›måÉs+Ō°auīVúÍ[ĶĻŪ’¤ÄÄķæ˛š´Ŗ‡‡‡9ĄV\ŅĐÔÔÔKģ�Ik˙CŌÅįĸ8ggg­_ģZ˗,2'•š6õÖčQ#ÍķĨ§gčĉ“Š=vLąĮŽÉÕÕU’táÂĨĮ´9:F’4h@Ų“7÷éŨģ°Ž ĨžkĩUuÛĨ&1{{{Y =“dž[R™¯ĄĻ…ÛĶŌŌJmķđđPĪŨK•÷ęŲC’ibŲ“…‰ĸÚjëúđüĩņ‡ĸŋJ$ĒėííÖ§ŌĄn����ę‡Z›ÃE2-ũæÛījĮÎ]JMM•ģģģĸ6oV^^žÂúö1Ī×ŌĨs°ÜŨÜ´9z‹˛ŗŗåč計¨M*((P›Ö­ÕēU+‹ķž<eúTxŅ/KšiS™uÛÛŲ)'7WąąĮÔĨKgsyÉsqttT#OOOJ2MüŲąōëËĖĖÔ/K–J’&M¸žÔöɓ&č›īfč×UĢ•’’bņéūårU˖e–Í=‘ŸŸo.ĢI;ļlŅĸĖũmíLsõŸ¤ļēN~ęßĖĮ§ĘĮ:|X~üŠÖoøŗÜ7ģÆr&RފĸļjŲ˛ėëvuu‘§gC%%%ëôé3jāoŪf­ļĒnģÔ$æ˛ę(>SQĪrģ)Ÿ[`,}=mZ—ũēsppĢĢ‹ŌĶ3” �˙vĩÖÖõáųúŠ'´cį.íÚŊG#F•ģvęÛˇˇú……)Ŧooķë���@ũWĢ —ĸåĄĪÆÅicD”F j&ÖĮŧŸ­­­BBzéˇĩëŗEáa}ĩqŖi™če|ʝ‘nzPŪRŌÅĨĻĨ[üėėė\îžEon˛˛ŗĘŨ§¸ĨËW(ĩđĶü÷>ø¨Ė}lmm•““ŖE‹—čÖŠ7Wéŧ5aSėMqejŌŽö—oÉäėėlI’]Õ×}ûökâ 7+##Cí4jäíjŪŦ™œœ$Iß~7ã’V*.Ģp˜™““Sšû8:”ũüXĢ­ĒÛ.5‰š˛:ėíĒwM...ånsssSzz†RSM¯ĨÚjëúđü{{{iÉĸųú᧟´`ábũuđ:¤īgĖ’ģ››îŧã6=8ížR“���¨j5á"™–‡ž=wž"ŖL —ĸDJŅŧ-EÂûöŅok×)"2Jáa}/Îß2 ôrĐ.Ž.ĘINŅ7_~VfBĻ"999ån+zCëėT~RϏŸgĪ5˙ģhDyfĪW­„KNnn•÷ŊT5iĮËÉŲÅE9))Ĩ†q”įí÷>PFF††ĸ˙~ôAŠ7§‹/ąZL™™å'ã2 —*v­ š`ĒÚ.õ!æ"Ŋ ÍĢT[qחįßÕÕE÷Üu§îšëN‹SDD”VŦ\Ĩuŋ˙Ą>úDÉÉ)zūŲ§ë,>����USë“0@’)!‘œœĸC‡ËסÅd“ŌÅ/›Ŗct6.NĮOœ“““BCz•:gëÂe›O>]íxĘ;&;;[į“’$IÍ|š–šOqū:¨mÛwČÖÖV›#ÖëČ_{ËüÚąuŗĩ˙Ā_Úąs—ųxĶpĒ‚‚ü2ĪōäÉę^ZĩÕ¤/§VW™†EM(ZRVV–ŌĶ3ĖĻnÛžC’tĶ 7”ŲāĀ_Y-Ļ'N”š=55՜)oX—ĩb¨jģԇ˜‹”÷Œåää(Ŋ°§I“ƍ%Õ^Üõņų÷iÚT׍§/?û¯žúüSIĻ%˛sk! ��� fj=áÖGvvv:đ×A­+œôŗhuĸâÚĩm+oo/íÚŊĮŧĒOŸŪ!eÎaСp…–%K——YgNNŽ.ūEņņ ĨļÅÅĶĄÃ‡K•Įl1­`ãââ"ŋļ~•^×ĪsLŊ[öī§&Mš”ģŸģ››†Ž´2{îÅÉs‹&r={ö\ŠcrssõĮú?K•ŠÉ\$ÅÕ¤/EUãî]׊•ĢJmËĪĪW؀ĢÜ­§öîÛoąÍŅŅĄÔūŋŽ\­'N–[•c ‘$­]÷{™Û‹î—ˇˇ—Ú´i]ĨsVWuÛĨ>Ä\¤ŧ×]ŅP/''']U˜hŠ­¸ëúųONNŅÂÅŋ”[˙~árppPnnŽÎŸO˛J ����.ŸZO¸_úÛīgH*=œ¨HXŸ>ĘÍÍÕĖYĻeXô/ģ›˙S&ËÉÉI›ŖcôåWßXlËÍÍÕķ/ŧŦĮžø—žyū…RĮÚŲŲéĨW^ˇ␓“Ŗ>1}š<bøP9ØW<?Cvvļ.úE’4Ąp؊L¸~ŧ$iÉŌeæĨoÛ˜VhÚģoŸĸcļXÄ˙ōĢo(šŒa#n…Iš´ôt%%%WZoejŌŽÕQŨ¸oœ2IöööÚŖ9ķæ›Ë ôū‡+))Y­[ĩRp'ĶĖÆūíL+ŊŦYģÎâ<ģvīŅ‹¯ŧǐ^=%IgĪÆ]zL7L–ƒŊŊ~[÷ģV¯ųÍb[||‚ŪyīIŌmSo1OmmÕm—ús‘˛^wŲŲŲúđã˙J’†"‡Zģޟ˙ėėl=õôŗú×3ĪYü(˛|ÅJåää¨I“&ōō*?Š ��� ~¨õ9\$Ķġ›6Gk÷žŊ˛ĩĩ5R_RXß>Z¸øķ‘ō–…mŅĸšŪ~ãUũßãOéõˇŪŅĸ%KÕ!(P銎Ųĸ„ÄDĩnÕJ/Ŋđœų˜ŧ\Ķ0‹ũ•rá‚ ĻŨēÉŨÃ]‘Q:q⤚4nŦGy¨ŌëYūëJ]¸pAžž uõ •îߡOo5ķņŅ™ŗgĩlůšxũuęÜI]ģtŅö;tĶÔÛÕĢgy6l¨ģw+77W>ōūķâËŸŠûø4U“&M” ą×MP[?? :X7N™\i eš”vŧՍÛTįķzú™įô¯?§īžŸŠfÍ|tđĐa<yJÎÎÎzûÍ×ĖoļīŊûNÅlŲĒ/ŋúFû÷o›Ö:r4V‘QzęņGååíeNRääkōÄ ęØ!¨ú1Ŋøŧž~æyŨ;mēBzõ”¯o%Ä'hstŒRĶŌ4xĐ@ŨuĮm5j̊Tˇ]ęCĖEúöé­ÔÔÔ2_w<=õøŖX^g-Ä]ž˙§Ÿz\/ŋú†&ßx‹‚;uT›6­eAGccĩk÷ŲØØč?Ī>ͤš���Ā NūjĐ˙âġƒ;ÉŨÍ­ĖũŠ÷|ņõmcbP–ŅŖFjÉĸųíXĨ$§č—%Ë´îõjܸ‘ĻŨwΛ-ŸĻįb)š×Ŗ‡ž˙æK=J;wīւ…‹•––Ļą×ŒÖ‚š?—š[Ļ,ŗį˜†3Fö•ô†‘$]7~œÅą’ôõ˙ĶÄë¯SÆ ŗE[ļmSŸĐP-˜ûŗ| ‡JÅ-™V<zīí7åëÛFįÎÅkīŪ}U^ąĻ<ÕmĮKq)qOžxŊæÍūQÇ QBBĸ6üĄŦŦl];vŒ–,œ§ž=ē›÷|õ ŊũÆk l ¨M›õËŌeĘÍÍÕįŸ~ŦģīēCŖFŽĐčQ#ekkĢ_WŽRzzú%Å4iÂõš?įG>LGcc5Á"mŽŲĸ  @Ŋųú+úėĶk|?*SvŠ/1KĻ^83ŋûZcĮ\ŖŨ{öjáÂÅJOKטkFiņ‚šjŅĸyÄ]×Ī˙íˇNÕĖīžÖ¨‘#t>)IĢ×ŦՊ•Ģ”˜x^cŽĨųs~ŌčQ#k|����.?ƒąÄDąąąjĶĻM…āīlŪ‚…zō_΍oŸŪšõũ7•�����Wâ9úĨ�����X ������+#á�����`e$\������ŦŒIs�����Ŧ€Is�����.#.������VFÂ�����ĀĘH¸������X ������+#á�����`e$\������ŦŒ„ �����€•‘p�����°2ģÚĒčĪXiŌĪR|ē”gŦ­Z¯,vÉËUš3E oS×Ņ�����€KU+=\ūŒ•ú%I#ŲR‘<Ŗt&ÕÔVÆÖu4�����āRÕJÂeâl‰<K$ŖŅÔf�����āĘT+ —¸ÔÚ¨åoÄ %¤Õu�����āRÕJ…Ū-ÕĮĐ+�����Ž\ŦR�����`e$\������ŦŒ„ �����€•‘p�����°2.������VFÂ�����ĀĘH¸������X™]]P‘E7Iã‚,Ëō ¤Ø$iÉ~éÅĩRJvõΙđoéƒé•ß­fš>šFč'uúčō×�����ęzp‘¤ÃįĨģ^üŲŅVęŅBzĒŋė# ûV2Ö]x������ĨÔû„KZŽôûQ˲•‡¤ŗŠŌ××I}[I×Ml������eŠ÷ —ōD0}ŋĒÁÅ2;é؁ԔÎRë†Ō‰éũŌ˙6—G[é•ĄŌ”`ŠŠ›t&UšĩCúĪoĻáK’äí*Ŋ3Rė'5r1÷“(éŖČ‹įiî.}5^ä'ĨdIŸUP'�����ø{ģb.MLߏ§\,{{„tO/éūÅRÄqiH;éÃŅRNžôõ–˛ĪķéXéÚŌ´_¤˜SRī̤˙•œí¤GW˜öųæ:)ĐKēaŽŠgMxké‹kĨãÉŌĸ}Ļ}fL0Å4z†)iķ@¨t}')1ãōĩ�����¨ŸŽˆ„‹]ąĩ”lĨž-¤wGJģã¤ČÂáDŽŌ´Péõ?¤ÛMe‡6K=šK˙ę_vÂĨąŗ4ĩ›ôįŌė]ϞÃįĨ /鑾ŌŋV™’5,“ōŌŅ$Ķ>%Jô–†ĩ3%\ZxHƒÛJ,‘Ö1í3}Š4´Ũåi�����PŋÕû„K)÷%˲ŖôëAéžE'ĖíÚĖ”ŒYuČrßߏJwõ”ÜLķÁXœģ™)™S4<ŠHĖ)ÉÕAōo,í9g:î_ũMÅŧ\%ƒÔČY:˜hÚ?ČËô}ķɋį0ūÜ­yMŽ�����\‰ę}Âå¯éĻš~ TÕ^ēyŽ””uąÜÃŅô}Ũ’ਞE6Ķw7éĐyËss!˲<ĩpŠiwGÉŪFZy›)1ķđ2iŧin—Å7_ÜßŊđ<™š–į)™ā�����˙ õ>ᒙgęqRäąԘ@é­ŌŨ‹.–§&Mnž+í:[ú<'RJ—ãádY^ôsJ–z•ÔŲGę˙Ĩ´áØÅ}ŧ\/1J/LŦ4(qž†%~�����˙ 6•īRŋœĪ”ūŊJēŗ‡Ô¯õÅōgĨė<͊Bû.~%fHņéRv~ésí8kę­ÖʲŧĪUĻdËÁDÉŠ0%U|ōÛ>WIžžRaįH0}īÚėâ>ö6Ō@ŋ_.�����¸]q Iúj‹´å´iĨ [SŲ…lé‹héÅÁŌä`SBd ¯´úéû eŸį|ĻôÍééŌ¸ ŠUijWĶ„¸D˜’1;ÎHYyŌC}¤fnωr?cš+ĻŊ—)Ás,Ų4yīĶũMÛģú˜bËÉĢŊ6�����õGŊRT–ŖiE ¨{MIŽיĘ]!%gIo —ššKgͤ_öI˙^]ūšĻ/5ÍŲōéÉÛÍ4ôč•uŌëMÛã3¤ÛįK¯3­hsJēmžieĸŲS¤ĩwJ>’nœ#}5^úåf)%[úlŗ4sģt]ĮËß����� ~1Å§˜•bccÕĻMëVōŦUO÷a|ĨŽ#������UU<§rE)�����¨ĪH¸������X ������+#á�����`e$\������ŦŒ„ �����€•‘p�����°2.������VFÂ�����ĀĘj%áb¨JūfėH…����pÅĒ•ˇõMŨ%kŖ�AQI�� �IDATĻŋ ŖÔÄĩރ������—ĒV.s'KēšT™Á`j3�����peĒ•„Kxiũ]’;Ce*bgcjŖõw™Ú �����\™ėjĢĸđ6Ō™§jĢ6ü=qĻŽC@-đŊĒY]‡đˇÅk—¯Y��đOU+ ūˆGmāú~Ÿ\>ŧ†����멕„ İ~Ÿ����¸0Ŗ �����€•‘p�����°2.������VFÂ�����ĀĘH¸������X ������+#á�����`e$\������ŦŒ„ �����€•‘p�����°2.������VFÂ�����ĀĘH¸������X —˙oīžãĒĒ˙8ŽŋDÁ­¨9Ā‘{Ī܊ÛlYY–?3ÛfæHMSsdÎĖĘÜĻåʆšĻVÎ4wŽ25G8AA-ë÷Į‘ (Č÷BŧžĮ}įœûũ~Κ|œ7ßķ=������&#p�����0 �����€É˛māRŋq3yWŠ~×Ģa“æ2l„ŽøųeYß-ÛøČģJuũôķ/YÖGfd‡ēĻL›!ī*Õõæ !vĢȨ´~Ÿ$žžû_ŋ,é×ÚsÖŦs›ķ���Č>˛mā’¨h‘"*WļŦʕ-Ģ’%K(čÆ }ŋnŊē?ō¸ŽeIŸ5kĒļmZŠxņâVŋኟŸŧĢT×â/–™RCjíeĻŽėÂėãc–;/ÄĢÖŦĢļ>5jĖ8?˙O–õ›ÂŗÜ(ųī“䯒%KØģ4����˙1Nö. =Ãßĸž?fųūÄ_'õÔ3Ī)88DĢž^Ŗ^7ŊĪ)“'fø=7n2ĩ†ÔÚËL]Ų…ŲĮĮlE‹‘›››nëÂŋēpņĸžû~>™5C>íۙŪßC͚ęzāõžådwū>���€Ŧ’íG¸ÜŠFõjjÖ´ą$éĖŲs–åqqqš3wž:wëĄjĩęŠeÍ_¸(Å{CCC5hđ0ÕŦÛP ›4׌>֒ĨËä]Ĩē^}c eģ;G\ģv]#GŊ§–m|TĩFĩhŨ^&}¨ČČHIRĮzjōÔ钤‰|(ī*ÕĄ†MšËģJuíúuˇ{˛—jÕk(IŠŒŒÔÔé3ÕÖ§ŗĒÕǧöģhÁĸ%JHH¸g{ŠŠXģūõxŦ§ĒÕǧšuĒWī>úu÷žûXĮĄÃŋkԘqĒĶ ‰j×k¤ÉS§+...ÍcĒÁC‡ĢfŨ†jĐä!}8u†âoט\f÷ĮšĪĖV†ŋ3D;ļnŅąÃûõËæjܨĄĸŖŖ5đíĄY2’jĘä‰Z4ŽęÕ­czÛ¸?֞/éũÜ'­‘ŖŪSMT§~c›0IąąąiÖ`íī3kÎO����ö‘ãIŠˆ0‚ŽÂ… Y–}đá4MŸ9KaaázéÅäî^@NĄ [ļ3n‚Öoب„„ĩhū6˙ôŗžXö•$ÉÉ)íÁ>¯ŋų–V¯ųV•+WŌķĪ=+OĪ’Z˛t™†%Ięņpw•ōô”$5nÔP}û<¯ŧÎyåėė,Iš2}ĻBCCU¯n]IŌčąīkîü…Ęī–_}û<§ëAš<ešV­^sĪöî´`Ņ :\'OVģ6­Õ°A}8xH}_|Y[ˇī°l—XĮ¸ņ“ā ŸömŽ ëģĩëŌÜīąīOÔÚõ?ČŅŅQ;uÔî=ŋéûĩëīÚ.ŗûcÍgfŪŪ^Z0oŽ .¤¨¨(-_šĘ˛.Ŋ ážO?+ī*Õ5eڌmņŽŧĢT׎š5R ĪžXöĨ:téŽĒ5ę¨uûŽš<ešÂÂíî;5f†‰mí?pPž­ęĩëĢeũ¸iŗüũô|ßUĩf]ĩëĐEĮŽ˙q_uۓĩįKz?÷É}ōŲį:söœ7j¨Đ°0-ûrš–,ũ2ÍŦ97Ŧ=?���ØGŽ \ĸŖŖĩaã&í?pP’ÔēuKIR`P–}ĩ\’ôņGĶ5xĐ@-]ŧPNNNš3oââ⤍›6K’&O¯Y3§iũwk%IrppHĩĪČČHūũˆœœœ4wÎ'5r¸V-_Ļ7^{EM›4’$õī×W^^$I;øhĖč‘rΛWŽŽŽ’¤üŽŽÚŧa–-Y¨ččh:ũˇ*WǤ)LÔđaCÔ¯oIŌæŸ~žg{Ʌ†…iÖėO%IĮĶgŸĖŌ‹æëųŪĪ(!!A3>úØ˛mb%Kׂys4cę‡zäáí;vĻēßA7nhÏÆm@Ķ>œ¤IãĮéģoVŨUGf÷'444ŨĪĖžÜ PĮ>’¤}ûX–§w!Ü­kgI)k||ŧvėØ%Iz¸[×Tû›9kļÆOœŦ�˙�uíŌYÎÎÎZ°h‰ |ÛęžScfxÔÖDš¸ēČÛËK˙^šĸwFŽÖ[ƒ‡ĒtŠRōĒP^ž.hĐāa–°&;kSĻÎP›öîz=v˞5į‹5?÷ɕōôԚUËĩ`îgzáÆvĢVjÖü>ŗöü���`?Ų>pygÄ(˄ĻÕjÕĶŽ‡(!!A?öˆÚˇm#I:zė¸âââäāā ’%JČīęUÅÆÅĒėeĸŗįÎëÜšķ–!ü>íÚJ’\]]ÕŅĮįžũģēēĒLéԊU—n=4yĘ4íÜõĢ^}ų%=÷ė3VíÃÃŨģYFĐä˗O×}§-?ŽWõjU­âŋI’üũũ­>.GŽMēĨŠ{ŌE|ˇŽ]$I§O˙­[11)ŪĶŠcË×ĩjՔ¤4o—9{öœåxĩjŲB’äœ7¯|ÚˇMą]f÷ĮšĪĖŪʗ+')éYs!Üĩŗ¸ü}æŦūŊrE’ą¯AArwwWëÖ­îę'88Dķ#>ĻOŦ™Ķ§čģ¯WĒ`Á‚Úˇoŋūøķ„U}§ÆĖđ ą­ĢTŅô)“ĩhūį’¤ˆˆyyUДÉ5įĶŲ’¤ /ęâĨK™Ž;ĢéâĨKwŊĸĸĸīÚö^įKFîy¸ģ%Ômߎ$éß wŖ’uį†ĩį'����ûÉö“æ&Nf*I˙^šĸ¸¸8ŊúrŊ3t°e››7oJ’ÔĒ]‡ģÚđ÷÷Wd¤1’%oŪŧƟ?ŋe]á…îÚūNŸ}2K#FŊ§S§NkÁĸ%Z°h‰Ü ИŅīę‰ĮM÷ũ‰b‰VŽúZ —|Ą .*>>Ū˛<#Ķ/Ũ¸!ɸđsuuĩ,Oܟ„„ų_õWŲ˛XÖ*XĐōuž|Æh…¸dũ'wãF°$ãxš¸¸X–{xxÜĩmföĮšĪėÁ*•ĶnĀÂÂÂ$Iy\2ĩ aI*û@ũã{AgĪ×ƒU*Ģaƒú:tøwíØšKŊŸéĨ_ļn“$uęā“愪ĮŽY.ŧwww9¸×˛ÍÖí;Ŧę;-w†ë~ØpWx ŖqŌ Z´xH’T˛d yxxčæÍ›jŪŦ™$ÉĢBy9įÍĢ[11 ŌŲsįīĢnŗMũp’Õ“æĻwždäįžH‘"–¯ &k7$8äŽßũ}–Ūų ���Ā>˛}ā’üŠ"S§ĪÔÜų õí÷kõęËũ-=Œ GGG͝ķÉ]m<XĨŠ|/\$ÅÄÄ(""ÂēŨHˇ†ÚĩjęĮõßË÷Â8xH?˙˛M[ˇm×đwGĢyķfō,YōžīwĖ“4hûŽ]5fœœķæÕ¤ņãTŠREmÛžCŸĪ[`ÅŅH’8Mtt´ĸĸĸ,]A–m *˜ę{­j˙vpŖČČHK¨s=00Åv™Ũk>3{ķõ5~fʔ.%Éú¨{×.Fā˛Ã\ļnÛ!IęņpˇTû šŨŽ‹‹Kšˇ„Üo@efxāáînų:ą^w÷ÉÚΧ[11Š‹‹ËÁZfdôį>8$8éë`ãk‡T_k΍ /JJ˙ü���`?Ų>pInā€×ĩáĮMē|ų_Mš<Õō˜äÚĩkĘŅŅQqqq*íéŠjÕĒ*66V?lüQîîrw/ ŠŪ^ʓ'âããõķÖmzäáî H1iij.\ŧ¨6ü¨âŊéé§zĒBųōzĒįjßą‹ūņŊ ĢW¯ĘŗdIËí÷līØņ㒤Š•*ęé§zJ’–¯0&e‹OēŊ"Ŋöę×Ģ+www…††ęĮM[ôøcH’6Ü~ürÚĩäžėÂ8Ŗ*VôļĶmÛw¨[×. ז;æ§ČėūXķ™Ų“ŋ€~Ųļ]’Ôĸš1ĸÃڐ¨KįNzâÚŗwŸÎž;§3gĪĒh‘"jÖ´IĒ}%vQQQēuë–eŽ”āāÅÄĨ@ˇ, ¨Ė S“‚ĩ˰öį>ŅÆ7éąGzH’%€ĢRšRĒ“u[õûĖĘķ���€ũä¨ĀÅÅÅEīyO/žüĒÖ|ûz<ÜMÍjĻbE‹ęŲ^OéËå+Õˇ˙ËęčãŖ“§Né÷#GUŋ^]ĩmĶZųķį—Oûvúéį_4rÔũ˛uģū:yRų\ōŨŗO777͝ŋPQQQ:pčJ–(Ąķ˙øęß *WļŦĒU­*ɸŊB’žXúĨ._ūWCŪ~+Õö*WĒ(ɘceŌä)ērÅĪōWé .júĖY:xPēíšģģëíˇhüÄÉz÷ŊąÚķÛ^Ũ¸Ąģ~•“““†’ų-ãVŽ>í´yËĪ1ę=mßšKG—›››nÜļLˆz?û“Ūgf/—.]րˇŪÖ­[ˇäáᥧzÔֆDŋSŖ† tāā!Mš<U’ÔĩK'Ë(wz°Jš¸¸(**J[ˇīP—NĄv;+88D˗-ÉŌ€*ŖáAFdˇ`mĘÔúôŗšŠŽÛ´amŠÛķîÅڟûظØÛ۝Qī>/¨`AmŪb„"ĪõN}(kŸYs~���°Ÿl?iîÚļiĨ.:J’FŽc™8vĖčwõ֛oČŲŲYĢž^Ŗ|}Õû™^Z4˙såš}KΤ ãÔžmÅĮĮk˙ūęÖĨŗēv1&9͛ÆcĄ‹-ĒU˗Še‹æÚēm‡.ūBĮŽ×cöĐWK+_>#°yéÅ~ĒTąĸn††jĪo{Sܖ‘\×.Õ¯o,čĄU_¯‘S^'Í˙üS=ßû9æÉŖu?l°ēŊž}ž×‡Lˇˇ—6ū¸I‡V‹æiåWKÕ´Iãû8ʆņãÆ¨]Û֊‰‰ÕŽ]ģÕĩs'=ÛëiIŌ­[ˇî{ŦųĖl%ņé5-ZˇWŸNúãĪʗ/Ÿ>ž9ÍrkVⅰ$õí˙˛Ū;^Ŋz÷Ґa#ôųŧ)æŌHœŧxįŽ_%IŨĶx:‘dÜúÕŋ__IŌđŖ4lÄģęųôŗ Q“ƍԤqŖ õQw†oŧ9Č2áobxYYYwf¤5iîÅK—o}HaíĪ}ây2öŊwU¤HíØųĢ .¤×^yÉr.ĨƚsÚķ���€ũ8$Üņ§P___U¨PÁNåd­S§˙V@@€*UǍŌĨŒy9úŧđĸvīŲ́^× ė\!l­~ãf ą|īāā bŊĒÅCéĩW_RĨŠSl§OįĖÕ7ß}¯ĢWũåáᎎ;kčāˇRL†z=0PM›ˇV||ŧJyzj÷έ)=Ū˛ūŊrEs?›­Ž|¯y éë5ß抟Ÿ<=Kʧ]; ø†åÖ0kûNîÎ~$iųĘUzoėxÕŠS[߯YĨ„„Mš<E߯[¯[ˇnŠ]Û6?ö=͜5[ĢŋūFÅKׯÛIĩ­ÆÍZęz` –,œ§Ö­ŒĮ´×ŠßXĄaaZŊâK5jØ Su���@N”<SÉUË˯ Đ/[ˇŠtŠRōißV˙^ņĶÖmÛU¸p!mŲ¸^ŊKŋ�����€T$ĪTrÜ-E÷ãŖéSÕ¯o9äqĐĒÕkôį‰ęÔŅGĢW|IØ�����L“ĢF¸������d•\;Â�����Ā\������LFā�����`2������“¸������˜ŒĀ�����Ād.������&#p�����0 �����€É\������LFā�����`2������“¸������˜ŒĀ�����Ād.������&s˛E'˙\ōŗE7Čåŧʖ˛w °~ŸdÎ!���Ā<6 \�[āB¸?œCČ y�� ˇâ–"������“Ųd„KąBlŅ �����@ļĀ������“¸������˜ŒĀ�����Ād.������&#p�����0 �����€É\������LFā�����`2������“¸������˜ŒĀ�����ĀdNö.��síģė¤>kķézDÅ&Øģd…<RņüņZõx””Žŗw9���9#\��™˛ī˛“:-wÕÕp–˙˛øÉ?,Ú™_8Úģ��€ƒĀ�)ΝÍ'r–\ÂÁ^žūÖÅŪ•���ä.�€L įŋ\ÅAō å3��°s¸ Wķ‹ ĐĖKô÷ÍäyÍŪådH)×âĒâáĨÁ5^P)×ö.šŖ[rŸ8>t���ĢeÛ?U}:gŽ5maķ~[ĩë ų §ų}j&O™ĻĮŸ|&Íõo Ļ: šX^jĨG{>­i3>’ßÕĢĻՎŒņ‹ Pī]C´Ķ˙@Ž [$É/ōšvúPī]Cä`īr˛­Í[~VMôëîßR]˙ū„IjŅÆGAA6Ž ���ĀYļ \˛‹ĄoR‹æŨw;ex@ įÍŅÂys4}ĘduíÜIÛvėԓO÷Öáߏd¸Ŋ•Ģ×hôØ÷īģŽÜlƉŠ‹°w÷-,6B3NÜ;ĖÍ:wę &iڌ™ē“bŨ_'Oéûu?č×^QŅ"EėT!���€˙"—tôx¸›ĒWĢzßí¸æwUŖ† Ô¨aĩjŲ\/÷ī§oV­PĨJ5dØ……‡g¨Ŋ“'OŨwMšŨī'ė]‚iÎÜôĩw ŲÚČw†ęß+~Zžb•eYBB‚>œ:]UŦĸ§Ÿ|ŽÕ���ø/Ę1K[ŸÎZąjĩf|ôą:téŽæ­ÚéÍAƒuũz $ŠĪ ũõڀˇîzßëoŌķ}_”$iÔ{ãäĶš›5kЇíŠĢVßŗß;o) ¸Ļ×ߤFÍZĒ]‡.ú|Ū‚Lī“›[~=R7‚ƒõÃ-ËĶĢŗßK¯jŨôÆU§A:}Ząąąú|ŪõxüIË{V¯ų&Ķĩ劍nyáÁž:đÄ÷Ú÷øˇĒčQN'{ũ¤Wk<{×v^îeu˛×OPķySkęéŨE?u_ĒÃ=×kšĪGĒZ¨bĒĩåqHyęæÄ[ĸlÉËĢ‚ž{ö-X´Øō;cãĻÍ:vüŊ;âåÉcOkÎŖÃŋŅ ũ_Q‹ÖíÕŦe[ũ¯ßK™Ĩ���āŋ-Į.Ny´dé—ĒXŅ[›~XĢo×ŦÔ_'OiŪ‚E’¤Î;čĀÁC ĩŧ'44TR—N%Icߟ cĮ˙Д&ę›UËÕī…>š6c–ļmßauŖĮž¯sįÎëĶgjáü9 ÖĪ[ˇgzŋŧŊŧTž\ŲléÕųņĖiĒ^­Ē:wę [ˇ¨rĨJš9ë}ąė+ŊÔī}ŗzšžîM›1Kß­]ŸéÚrŖ^•ģërØU5ũî ųE\Ķ[{&hËÅ_mŌwõ•4žņ ųG^×ŧŋVĒrÁ šūĐČTk‹OˆˇIM˙%¯ŧÔOÜ čŖŲŸ(<<BŗfĒĮíĄÚĩjZļIī<ŠˆˆĐĀACäííĨe_,ÔWKŠJåĘzũÍA ą×Ž���ȆrLā"áÄŖ=–“““<K–T‹‡Ō_'OJ’:ø´W\\œvíŪcŲ~ĮÎ_§N}$IÆŧ­šŸÍVƒúõTž|9=öH=XĨ˛öî;`U˙ūÚā ^čÛGM7’ˇ——Fŧ3TÜōß×~•*UJ×-ß§W§ģģģō8:*oŪŧ*\¨""#õõ7ßĒoŸįôp÷Ž*_ޜžęų„îÖE‹—,Ŋ¯Úr“o:~ĻrJĢZáŠ:Ųë'•Î_\7OĘĩ”$õĒÔ]ûŸøN;YŠe›§xo­"jMĮOuüŠÚųČJŊPĩgĒ}4.Q['{ũ¤žŪ]$Ĩ)ãá\@_ŸũQ#öMĶüŋVęWŋƒōö(+'ĮģjstČQ§nļ?~ 2H6nŌđwG):ú–Ūzs€e}hXXēįŅU……‡Ģ[—ÎōöōREoo 6XŸÍūHÎÎÎöÚ5d#ΎŌę§Ĩ€‘RįĘöĢcxK)~‚”0ŅxÅO0–��ĀvrÔcĄ+Wǘâ{w…„Ü”$/^L ę×Ķöí;Õ­KgIŌĪ[ˇŠIãF*Z´¨$É-ŋ›-YĒC‡+čÆ %Ä'(äæM•+WÎĒūΟ÷•$ÕŦQŨ˛ĖÁÁA5kÔĐŠĶgzŋbccåččhų>Ŗuūũ÷ÅÄĨYĶ&)–7hP_ß­]¯đđšŨg(”ŧŗīC}ŅvšŖohô™ŠKHzūi7OiøĻ_ûS3-Ö5ŸŗŦ+äėĄ%íĻčR˜Ÿ^˙uŦڔnĸwęžŦ ¨`­ķũÅęū÷ųÕ>˙ŖˇÛtWŖĩu&ÄWą qŠÔÆ—ĖččĶ^ß55žX4fÔH*TвΚķ¨|šr*_žœŪ=VOö|\͚6QĩĒĒaƒúļŪdSķ•žĒe|ŊļˇÔsĨ´átÖ÷4J*ėšözéÃNÆËōžHŠč¤Ŧ¯ �� ˇĘQ‹‹‹Ë=×węčŖ™ŗ>Qtt´bbcĩwß~Ŋ÷îIRLLŒ^0Pąąqzgč`yy•—“Ŗ“Ū<Ôęū#""R­#ūû 3.\ŧ¨&eēÎÄ wûŋōē’-ŋ\ŧ.77ëBĨÜėüÍKЉQDl”N‘—{YËēæžõå ->ĩFGޟĐüŋVĒšgIR‡˛Íåæ”_Ÿ˙š\ģũiŋ˙Q=îŨIŨˡSČ­P5ķ4.Æ÷øVT\Tēurv×ü6¨ŗ‡†ũöaĒĩ!ķÚĩm­ŊûöĢ]ģ6)–[s•/WN_,š¯/–~ŠīÖŽĶėOፔ§§ŧūĒēwëb“ú˙+\œ¤×›HĪ֖,.Hc€Pø-éÔ5iÅqiÎ~)*Öv5Nî( nnŒZš—eG¤—×J=ĒIŗ“zT5|NŌOHÅ>°MŊåū&���¸9*pIOûvš<eēöîÛ¯ČHãÂļmÛ֒¤?ū<ĄŋΜÕâsÕ ~=Ë{nĢL™2Vĩīęj-aaa)–‡†…ĻļšU~?rT׎]ˇüU=3ups“$}0aœ*WĒt×úRžž™Ž†BųŒ‘AQÁ’¤€Č ËēŽEo/3n ‹‰Upt¨Jä/ĒúÅkĒO•Į$I1‘Úë˙û=û)š¯–ļŸŽRųKhāîņÚpÔô}AęŦ=Š.ŦÁƒjđ :wūŧ–}šBŖÆŒ“—WÕ¨^͆į\e JûHĩJĻŋ­›ŗÔ Œņę[_ęļLēdŖér†4—ōĻļėō•†l’Ö='uĒ,ŊŋMōY,ũŌĪ]~ģh“RU„‘*���ŲÎ*p)R¸°š4n¨]ģ÷(,,\­Z6—{’¤čč[’”â‚cĮ˙Đŋ˙^QęÕSmīNʗ—$:ũˇęÖŠ-É‘rđĐī*T°āŊۚǐMš<EĨK—RĮ>Ž3áö_ŪĢTŠ,įŧytCŊ*XÖŨ¸Ą<y˜[Â7oĄZ1—"’¤2nIWŠW#ŽK’Jä/&JÎyōĒHž‚:ōf[¤™ĮYļ­[Ėø Ũōc˙ÃIrtČŖš­'Ē„kQŊ°ũäŅßļdÍytųōŋ:söŦÚļ1‚ÜŠŪŪũîp­ß°QįΝ'pą‚‹SúaËéëŌđ-ŌÖsÆ(ŒvĨ)Œ÷lė#5ūÜ6#]ÇŅio“Ī1)l‘¤ąíŒ}Ko=$Ŋē.kkL.jœ1ĒÆĒmc%×qYY ���ūS‹$uėĐA .VhX˜ÆŊ7ʲüÁ+ËŲŲY+V}­W_ę¯3gĪjö§sÔŦių^¸ Ā  -Räžm—.]JĩkÕÔâ%KUŽė*R¤°–¯\-įŧyĶ­+2"R–d„4gΜՊU_+"2Rs>™eiÃÚ: zxčôéŋuęôiy–ôÔã?ĒĪįÍWáB…TŗFuų]ŊĒŠ3>RÉÅõéĮŨĮ…$íķ7ž"ÕŋÚSŠŠ‹ÖËÕ{YÖmŊŧGÃëŊĸ×j<Ģđ˜u,ÛBųõũųŸîjįbčÅ%ÄëáōíõGāiõĢ–4šî“ģĒf‘*:p\MJÔQ“u$Iߞßĸ čā,ŪC¸(îytÕß_ƒ‡Đ Ôēe 988hãĻÍʓ'ęÔŽeī]ČŪh’~ØŌtŽœėîģu'Ĩ˙Hû^5Ūûzi枴۰•;ÖDcÛI'¤˙}kÛzBĸĨVū¯’ūŨ���¸O˙šG´oÛZ׎I jÕ2éI2E ÖøąīéˇŊûÔí‘Įĩ`Ņ7FĪ=ÛKWūŊĸ—^yÃĒö?œ4A*”×[oÕkoŧĨRžžęŪ­KēéŊtų˛úŋōēúŋōēŪøļVŦúZ͛7ĶęËTĢf ×ųĖĶO*āÚ5õ}ņøë/ <HO?ŲSŗfĒGžxJŖĮŧ¯úuëhō¤ ™8J7ŗŪ��ŲIDATЏ“očŋšvtĘģ—ŅÄÆƒĩáÂ6I’S'ß ÕK;F**6Zŗ[ŒQãu4ņđ§Ú|i×]íEëŖã‹UĻ€§Ļ=4ŌĘ888¨RÁ ’¤F%jkp-¯äŖ`ĩŌ;6¨¯ ãÆhϛôtī>zöųžÚģoŋ>š>UåË3O’5žŠ}īõīlNļ$ Ž2FŊHÆŧ/ö–VØ"I[ÎHëOÚžĻāČ lKā��å’=ŠE’¯¯¯*T¨`j'ĄĄ™ŸãČ*7>aīLu ›˙œŽ\ĪcŠ{†ß6Ƙ—%-îãĨ°[iŦs–nŽ1&Ō-0>Ã]gXÂDãß;o)J/lyä+):.ëëģĶūWĨÆXší%ŠéŧĖõssxÆūOwwĪøĪ ��@N•<SųĪpŦåætg¨æ0ž.Åė]åîø×˛kØ"elÔ #\���˛ r­EkÚģĶT)čeī�̜ēvīõíŧĶ^מĸņīétÚČJŸõH;lyn4˙QÛהˆ[Š���˛äZCjôûOŒrqsrՐũė]`••ĮīŊ~J'АËŨË šë$iE:mdĨúĨī^–8˛åÛgĨ>õl_Sĸ pÉ@8��€Ė!pAŽUĘĩ„V´šŠV%åČ[r<]ŠŠUÉFZŅjĻJš–°w9€U>Û/ũáŸöúĒÅĨŊ¯H=ĒJœWĒÆŠ,fŧwÎ~ÛÕ{§<R~Ÿü6ĸVėR’ˇ��d/˙šĮBQĘĩ„Ļ7aī2€\#*VęļLÚØ'íĮCW-nĖ“r§?ü÷FÅfm÷'] —.ߔ~ŋ"ŊąŪ~sļÜ)#zæąĐ���YĀ�`S—B¤ÆŸKo41]ĩxÚO. ŋeĖû˛ō¸1:ƞa‹$•™jßūī….���Ų  �ĀæĸbĨ{ŒWv'åuLz<´5nŲq´ËĩpëˇŊ‘uu���ĀĀ.��¤bÆ#tąVLœ4ĶŽŌOg¤ƒ—Ķßîāec[���d-F¸��Š‘?¯œ"$Zj<×ŪU��� #\������LFā�����`2������“¸������˜ŒĀ�����Ād.������&#p�����0 � Sė]�lΑ��Āj.�€L)á/%Øģ ØL‚TĘ=ŪŪU���ä.�€LųōŅh90â!×Čã }ũD”ŊË���Č1\��™ŌôXm銒nņrâ“˙Ŧ<ÆhĻm}"TŗDœŊË��Č1œė]�� įjú@ŦÎ ˆĩw���@ļÃß$�����LFā�����`2������“¸������˜ŒĀ�����Ād.������&#p�����0 �����€É\������LFā�����`2������“9Ųĸwww[t�����-0Â�����Ād.������&#p�����0 �����€É\������LFā�����`2������“¸������˜ŒĀ�����Ād.������&#p�����0 �����€É\������LFā�����`2������“¸������˜ŒĀ�����Ād.������&#p�����0 �����€É\������LFā�����`2������“¸������˜ŒĀ�����Ād.������&#p�����0 �����€É\������LFā�����`2������“9Ųĸ“+WŽØĸ������Ģ•.]:ËÚļIā’•;������ŨpK�����€É\������LFā�����`2������“¸������˜ŒĀ�����Ād.������&#p�����0 �����€É\������LFā�����`2������“¸������˜ŒĀ�����Ād.������&s˛UGģ}Ĩ§VI×ÂĨØ[õ ؟ŖƒTÜMZ÷œÔø{W����°›ŒpŲí+ĩZ(ų…ļ ÷‰KŽ†JÍæIGũė] ����Āl¸<šZ"gAŽæ ÅĮK=ž˛w!�����[°Iāâj‹^€lÎAērĶŪE�����lÁ& Ŗ[�C'����ä <Ĩ�����Ād.������&#p�����0 �����€É\������LFā�����`2������“9Ųģ€{YÛ[z¤šôÔ*i͟)×yüFHmI;ūąO}Č^žzRę]įŪÛŧļ^š{Ā6õ�����r¯l¸HR\ŧ4­ŗ´á”kījo4‘= õũÖ6ũMŪ)}ņ{Ō÷KŸū Ļũš´ėäĩûīĮÖû����Čy˛}āōÃ)Š­ˇ4ŦĨ4~ģŊĢAF4(cÛūN¯D‘ą’_¨ôË9sûąõ~����ržl¸„DKļKã}¤Å‡ĨË7ĶŪÖ)4ēÔĢļTžt)Dúhôųí[HŽ —>ß/MØa|Ÿx[Ō×HO¯NjĮo„4sˇ4m÷Ũ}8;JãÚI}ęI…\¤Ŗ~Ō;[¤ß.ëķ9J;HŊjI% ü_“Æn•bãmüGŖ1Ē—¯.9æ‘’Ļū*-xTjYA ģ%ųEúâČíãđžôÁŠjqŠÛƒRgé§ŗR˙īĨëë{ŌŠ\Aã8šį“vũ#Ŋ´VēfŨq´Ļ/J­ŊŒm˙WOĒ÷ŠtôęŊ?k[°fßZ–—&uj{ŸÍ1?éŨŸĨ]žŲwŋ�����ŲKļŸ4×ŅAšŊWē"Mí|īm§u6FÂ|°CĒ5[ššGšÕMząą~ë9Šyų¤í[{Ü-+$-ĢRÔb~>›z3ēHũJƒ”Z/”ÎI[úJ^…õszHũHC7KÕ>6.Ôßl*Mí”ÔFLœ4¤š´ū¤TüiÄiH iĶ˙¤wIE'IKmvIzĪ;­Œųj<?”ę}&Õ/mė_"kû~§Ĩ1¤ÂtŠÆĮƈ1m­?ŽÖ´ķČWŌáĨUĮ}üÃ˙۟­¤ˇonyĨ }¤ŋ¤fķ¤&ŸKĮ¯ŸMa—ėģ_����€ė%Û.RLŧ4d“ôLmŠyšÔˇķČ'ŊŪƝcŲQ#™{@ZvDŅĘØæ—sRŗrRãû6^ԊcÆčŒŠEŒe­*H×ÂĨcЌZpw6–ņÛĨ¯˙”_‘^^+m9#U*"u5FžLØ.­ūC:$-?fF/72FĮ$:â'm8-%HZyÜXļ÷ĸ´÷ŌíeĮ$×ŧŌƒÅ“ŊįŠÄÄ'H§¯û÷D #$ČHß'¯IK~7FŊ\ž)mú[jXÆúãhM;!ŅÆōč8cN\B:´ Xŗoå Û}uĖØŋŋŽI7J]—û’÷ ����ũdûĀ%҆ĶŌæ3ŌėîIIruKĄÂOwŒLŲņTЍq ÎÖsÆÅt­’ÆēÖ^Æm".ˇ‘HR+/#˜Ií:ēfIÉÅI:x9iŲ­8ŠįJéįsRRÆ-+û.Ĩ|ߥ%7gŠrҤe§¯'}}3Úø÷T*Ë æKZöû•”íž0ę)ã‘ąžß&Ũˆ” ģ_[s­i';˛fßūžn|6˟4B˜úĨŒ‰›wúJ1v)����eû9\’üŖtüMé…úŌÆĶ)×yÜ&ļŋ(%$KKÃĪƈ†Ķ×ĨåųMĒ•ö\š–5n+úâˆ1ÂåũmŠ÷Ÿ&„ßJ}}b 7ŖR.Ŋž¸' OĸSyâRT*ôÉÂĨ°;úMŦŖkÆúŽŧG?ÖĮôÚɎŦŨˇ–ķÛˇ^j(Mî(] –F˙"}yÔö5����rϏœŧ&ÍŲoLhē㟔ëBn Ī­‘ūHåv K!Æŋ‰ķ¸„ķo„DKģ/HŸt—Ę4&RMëŠ6׍=\R_ŸXÝëŋš# ɨ䡉” ܈4n+2ŖokcNdíž]‹†m6^Ջķë,ëiĖërøĘŨī����āN9æ–ĸDãļˇÎ m‘rųąĢƨ‘nÆ­9‰¯Ā#(‰Ž3ļûåœ1Lë ÆíD’1wJÅ"ŌĶĩ¤S×ŌN_7n+i]!iYigŠO]Ŗ†Øøģį™iVÖ¸Ø?xûžŧ_ɘ/%ü–Q¯Y}[{­•ŧXŗo^…ĨGĒ%Ŋį¯kŌĢëŒÛŠj–LZžö ����ũä¨.’t#JŗUšÕ5åō›ŅŌüƒŌûíÉL\6FĢĖęf<á¨û—ÆvÛĪsžô¨fÜĸ$IĄˇ¤ãūԀϯ“ƒŌr3Úx4õģmŒIb˙ ^ilũž“‚"õ#[ˇĻšbLĖûFScĸÖÄG3gViã‘ÔˎHՊĀŽúCŠŠ5^fômíq´Æ(Š^iŠŽ§ FfnŋÍbÍž•+(}ûŒ4|ËíI¤Ūu‰Š÷^Ėžû����Č~r\ā"IķH¯6Nšü6ŅāMRp”ņäRîŌÕ0#@y÷į¤m‚ŖŒÉg= ũ꛴|ˇ¯ôfŗ´o'J4lŗqņ=­ŗq‹ĪņĢÆlÎŨž×äÍ Æŧ)s–J0.Č'n7÷|ŋ2æ‘9đšņŖNIoū´ŪŦž­9ŽÖ˜ũ›ôå“Ōî—Ĩ'VH[ŌxÔļ-Ĩˇo;}Ĩž3n#īcU'üĨĮ–Kß%”÷ ����Ŋ8$$$¤x ¯¯¯*T¨`n'ŖMm.WēūŽ4ë7iâ{W‚û•0ŅŪ�����˛BōL%ĮÍá�����Ũ¸������˜,GÎá’ûĀŪ������k1Â�����Ād.������&#p�����0 �����€É\������LFā�����`2›.ļčČ9���� W°IāRŌ]R‚-z˛ąŠ´‡Ŋ‹�����؂M—5OKüeš\é‡įė]����Āl¸´¨ íę/yēKNĖƒ\&ƒT˛€´ī5ŠN){W����°'[uÔĸ‚ä7ÜVŊ�����ØãM������LFā�����`2������“¸������˜ŒĀ�����Ād.������&#p�����0 �����€É\������LFā�����`2������“¸������˜ŒĀ�����Ād.������&#p�����0 �����€Éî \œœœgZ������r¤¸¸8999Yžŋ+pqqqQxx¸M‹�����ČÉÂÂÂäęęjųūŽĀĨH‘" Qpp°âããmZ�����@N§7n(44T… ļ,wHHHHHmãĀĀ@EGGs{�����@•/_>-ZTŽŽŽ–åŠ.������Č<žR�����`2������“¸������˜ŒĀ�����Ād˙^sņ ­7����IENDŽB`‚�����������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/register-complete.png�����������������������������������������������0000664�0000000�0000000�00000054265�14156463140�0023026�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��U�����oy���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨ}|”W˙˙W+fĻÕ$Ŗ_Øž Ch›Ũΰū`ĸ ö&S‹&ڛĐ֒X-ŠÚ†Z ,Û&¸Ú„íWA[IŨļIĩšXh“Ū,uÍĀã!3|ŋ ]`Ē@FIwbP'‰n¯Ą7ųũ1I˜ÜAn’ļīį㑇2ךÎuæēΌžĪ|Î9tuuõ]xá…\|ņňˆˆˆˆˆˆˆČŠũíoãBÃ0¸čĸ‹&ē-""""""""ī]tžųæ›\pÁŨ‘wŒ /ŧ 'ē""""""""īD Ljˆˆˆˆˆˆˆ¤AA‘4(¨""""""""’UDDDDDDDDŌ  ŠˆˆˆˆˆˆˆHTIƒ‚*""""""""i˜r.*ŊāÂ÷‹jEDDDDDDDŌÖ÷ö[gĩ>eLjˆˆˆˆˆˆˆ¤AA‘4(¨""""""""’UDDDDDDDDŌ  ŠˆˆˆˆˆˆˆHTIƒ‚*""""""""iPPEDDDDDDD$ S&ē"""""""röūũQžūI#ũŸ×yûíˇ'ē9išđ ųāÅqËM%ä]:}ĸ›3‚2UDDDDDDDŪe˙ū(?Øü=ũÛ;6 đöÛoĶĶûW~°ų ˙ūčD7gUDDDDDDDŪe~Ü8ŅM8{.¸�úú&å{RPEDDDDDDä]Ļ÷o›č&œ]\Āß^}ĸ[1‚‚*"""""""2éMÆiL Ljˆˆˆˆˆˆˆ¤AA‘4(¨""""""""’UDDDDDDDDŌ  ŠˆˆˆˆˆˆˆHTIÃģ3¨Ōž™ĸY3)¸7øŪ¸îĸ›¯Ã1ëJ*['ē%""""""ōÎ3…_zŸ,ēÛ–—r÷Ĩ”ŨTÄ⏔ŋ3OtÛ&‡)Ũ€S3ˆnĢgc“ŸP8BŧÛ a2c˛ØqēÜ/¯Āį´Lt#EDDDDDDŪ5ĻLģ‚Ī–\Í{æ×ßxã æ^ÅõotŅļŊ…§ŲÎëÔÆÉ`rUŒ0u+î 6Ô ĻlfšŊ¸ŦfĖtF‚[ę ĩ4ŌPö8 ĢÜ(P6Á"ã-Ē#:ėeS‚Ęĩ×ãx=$˙Ę+ЈW~úúņ÷ŊmwQPgMčĮ”LŌX n¤ļ9rŌ2&g1Ģ|ŗ‰4>LSŧ•ånNövŒö ū@˜hgœn#ÉœÅjĮå]ŠĶvĒģg‹ #ũįƒÉbÁ>ˍ§ĐÉ)OOC¤ņašŒĨT.w&Ÿmü ͍ÍD:Áæ-ĮŨ<ôø;œ „‰tÆ0â€Å‚Íj§ĀSxâųăÔmôĶy˛Š,.VT,ÅŽgSķˆo˛­6ėN/^ˇmŌŪ7#\O­?›âU×ã8ߏŠÛÄZz7žÜķ}q‘.Ę[—o]Ȍ‹†é◛e;å˙[r5×\ķEžqéžWŋ?ODC'IT‰Ņ¸âjCÖĸõÔ­/1 7"ĪSšĸ’íuwQY°ƒMK'é(õ=ŌĢōqV:ūmĐnfãÆJŠŖ üO—`›ČæŊÅoÆ,'´Ąđŧ\ĪėđPZęüw,ÔČöÎ\ŠŠ\''–ņ?EŖũyę"˜…x=v˛-@ŧ“ļ@+- õĨå¸ĮŦÎ Ōŧ™æ°ÅáÆãŗc1ÄŖBĄfĩâ˛3Öázęĸ*úG˛V×Rаücá�mŨ6–”úpØ,`zülˆ4>LØy7%į9ō7ŌĐ{. [ļŖ;J$¤Ĩ.Bġœ§,NŠJ­$NŒh”xqšú_3[°��V\%^Į0ˆEBüõtË)+Ô7ņ uqŠĘ—ę{QDDD&Íåļá•×ģøÍ <ų˙âũ/dGũī8° ”ģ}ËøĘ q6l}ofŦLÚ5UŒm5T‡ LkiØ02 `v\ĪĻĮ×S\\‚w<ņ”x˜ÆÕwPtՕ˚IÁ•OҊÕ4†ūaĒÎÄQ´yXļE;uKfŽž>Éļģ(˜u%eیaΠŽī}×Ŧ™¸ÆX›%\ĩ�ĮŦŋ?qžqæÕ7ãŊōJŗŽÄĩäjļÅŪĸķÅjwãvüâ-ß@Ãz„6ĶpōE[°íü>Kŗ {nîā_2Q!kĘkvËøūŅP„nk!Å>7Ž\6‹ [ŽīrŽlˆvÆĮ<×7Ķ6°ûĘ)+)ÄéČŞ;gáõ”•ų˜e´á÷ˇŸņ[îŒũŧXr8sO|Š$ ˛8r-XĖ#Ÿšғπœ#ąVšüĖÎRʖ_Û9{n.g!žōrŠđĒŋ™`Œ-ĩØĖ˜0cĩĻžfI 4™ąÚSŽåÎÆŊ´Ÿ:ۂ#2ÚŪ‹ŒÎ(ņĩˉˆˆˆœ?f>Vt5s†d¨ŧN[c]J@eĀ›tėz†ē_ũë?ąä#įą™“Č$ÍT1´øI`ύb9ö“u”PŊ~UÆˇQY´’–¸%%Ģ(sYĄ3DK]#ko ōęã;Xãvâq™iđˇ6ĘąŒâ!13&“A(p gV IPކôą3¨kųíØ[ļĐāoďī7ĻÅß ÖJ b4¯,fU�ėETyí˜;C´TŽę“Ū¸ķËâp`#Dg ręŖÛǍŽ}ž`´LvœžUT¯_:øŧũ+¯¤’j6Ų¨ŦāŲôkj Ûņ¯^M­ŋhˇ)ێĶ;ô<#\ΚĒÍø#$0cwzYąvũ`@ŧņf<5šÔ>îĀŋf3ūön0ÛņŽĒĨļdāyÄđW­fcsˆhˇ&+īŨTo(9)1üĢīĨēš8&ėŪģY3<Ų$¤nM ­:ĻlŪ˛ĩT—ģ1§ņ–ĢXXŖÅΊ–Ŧr@´y5k6n#í&a2cwú¨Ŧ^77§s.' åâ-/?ÉšqÂÁ ģīhë&YœøĘŦCŗfŒAŋŸP$FˇŲV.īRÜšÉgo„ëŲ°âķB 9ŒQP‚§ŗ–(@Õa+ž˛rŦé?"õ5ũĮũlĒōcõVā1ũ'N¤ÕO ­xĖÃŽ‹ŅN°9@(KN˛Xq¸ŧxŨš˜9HcM¯@c ÕæJW]ˆGļáoíėŗ[‡ĸĨŗO:ÕętDCa:) Ø›;J֍‡¯Y› c¸—ž\ 3VĢ Â â'žĶÃŋąŽ¨Ģ‚2w˙1Â4Ô4sŗ*õŗŧąžNW9ĨnKrĒ“?H¸ŗ“ŲXíNŧŪBėÃnĒҤŲ$Ōm€Ų†ĶëÃë8Q(ŪFK0BŦŗ›„Ų„ÕęÄã[J˛ˆA¤ņ{4ãŖĸdö‰{i¤ĻŧĢJ°‡7ŗÉŸŒ¤=^ÂîĢ ´˙K˜ˆnn&餛lėŪÁį>؇‹-DšÚgÁáņás*ßEDDDÎЇŽā“W Ÿķsˇ>ĀÆžŅEÛŽl˙}´ŋ{y;msKp/ü(/5ūŽ7SÎŧfɧ¸zɧFŊÔËÛ˙ƒ—ļ˙Į9xį×$ÍT‰ `ráuŸƒ5U´ÄTū|›Ö.Įˇt)žåkŠkŠÅC”†ęzb€ËëÁ”h#š$l%Œ ¯7›x8DlđĀAĄnpâe”“~]ŗ)-v@ÂOķđ ˜p#ūN°ûJq„ë¨ ˜<Õ4n(§dā}5Ũ á‰øé{ íÄČÆ20öŲv/ōÄŊëi ė¤åņr˛•ßģmpn6A"ŧ‘ē˜Ú§›¨tC¤f~3Ĩ›Zh ė¤Šv9f˙JJĢÂũ׊§ô–ÚėĢhhŲC°åqVXƒŦũÜ]4÷ßlŗŲ Æ6jë ŠvŅöë_ĶRaÆŋf ũeĸ›WPŲØ‰ŗúiZ;iyünŦĄÕ”žÆÎNŅÍ+¨lîÆSۄį T†ŲT81}‚8͕+¨ØŠ|ē…ÖĀvĒ´m\Aå6°Pōx •0m"ØöĢ@¸ŠŌĘfđn Éŋ“ÖŸoĸØŧŠ˛‡™|‰@fėĻÎVššƒDb§‘scDyĩŦĮ˜Aŗ%umŽ8Áúząl<ÅåŦŦXŽ×ŪM Šž?Ķb Ū°Ģx9Åî\œ%e,ą&׉Š\ĩΐqŠįōU”:ŗÁębÅĒU'đ)ĸÛęinƒ‚ĸ唕•āąuhjėŋŽA¸š‘íņl<ĨåŦŦ¨ ÔkĨ3ĐHsÄ�fSRæc`÷UPY‘ ¨‘įih “px)­¨ Ŧ؍éÕfšžĨĖĨŅX7&ģcėéSf+tGŖŒOtzŒxĖ&N#Ųi 6vņhôÄũˆFˆY˛1wFO|ˇÆŖD vģâašš‰˜””U°˛ĖGĻąq[Ęw1`D bØŊ%”•-Įk776(ÛFCs>J+*XYZB9BScëĐzNÂâ^NŠ+,.JW­ĸt0phiõĩ{).+ŖÔk#jÆ?˜QiNöá�8—ßÍĒU÷Qæ2iöíį""""i¸čŌ™LíĀë]´ũęWėúÍķ§Ū7™b>‘ŸņáK?”×!+o&Öa§Ŋ´ũ?xy”ĀÉģ% “6¨#˞ĪĘōAšš;!׃Į'OųÃI‘Û ‘mâ`vâÄ Ÿ8;$‘ëĻÔë„Hļ˙oo#ĢË3j6͙Ôe/YŽËdhØ6d0nlĨÅÅÉ_aŖÁ ˜q/:đ´ų(-œKAÆ#ĪSYëĮrŠ�1šęš1\ÕÔ­ZŠÃfÃî.ĄvÃ_GSę¨$n§tũr<ÎŲØĖt‚ĶGą;›Í†Ŗp9[~N]Y2$ܰ™6ŗ—Ē ×ãĖĩ`ÉuS˛~-KP×Ü>Xm"aÆSV> ŗûJp!Ü˙¨ė%ĶÔō4ÕKØûÛWæĩŌō3pqÆĻxWQŊt66‹ §o+]Ļ”2ŧÕ-ø6āsæbŗåâ\ēŠR‡AĀßŧ1[0›�ĖXĖũĪĶYACK uĢ “Ķi…”•bІwDwYœ%”xs!â§ŠŽ†ęšīQ×ø<pûÉęFŲ–qæe´ˇč4ãôõ?{‹ ĮRžėNBöūBfX]×ãĖĩaŗ�f3ÉȌ9pÁ &�ËčĮ0Á6›Į‡;׆Ŗ‹ĶįÃãČ&7�3o9+KŽĮiŗ`ąX°9 )°&ˆFú˜Å”ŧŒŲLōqÂÁ6 ûRŠ gcŗX°ä:ņys1"!ÂgeđlīsöÉʼn8Ũgãz‘m´´u“]ā<yâ8Yšm\<7ÚÃ2ˉ(Ņū{dDŖÄÍvė6ˆ…ƒŧJ^ŸģłŒ‹ģ¤[w˜@dh¨ĘîIö‹Å†ĶįÁnî$2đŧl…”–•S\˜›|6ļ\Ü.;ĻÎ×=53ĻūîdŌ¯˜Ūäw‚ņŨéÁaIĐ;QqÂ�ģĮ=˜écq:ąŖsÅŅEDDä)ķCŪ?ėĩ7ū¸—Í˙įQęļžĖĪęåŸŋũ(î2(øûōwš߲…Ėų0ųA><Ę\˜á•wS@&íôŸ”y9Ũ<NãįŽbmÛđ×]Ô´ũßhcĸX„hˆÔásՍqÍä¯â8\xPÉ­?ƒÄęrãtšq$ž‡? ^7ÁV"dãķÎŊJËÔeņQęŽ"l %v=Ĩ6€0MNLååŧĩ(uÄ(fėöl8 CĄĶcĐR1“–amÉ.(ĄvSy˙ԙ0ÁØ+œCAfw!ÎDÁ0ũīČuųŊĀW€Šr Å÷F(õ.Mîūbsö×'é§'Š;pæB -ä&_39pĨ2›°rˇ,mŦĸē%L4Į0ɉ,æ“MgI%GéĐÉBÎB'´œ¨Álî$PUEs[„hÜĀ0ÉNo?ŲU,Đž™5UÚĸ1âFÀvŒ‰ZHį¤ĖÉ ”Û Ö!iįÕh„@s€ƒĸâFŸĩpzAÁxg'‰ūÁķ lölēŖÄ˜âbVæ ÅbtfÖÔöÚpûŽü—Ų'ė÷ķj,F<†a$WsĩŒõĀ’kŦX<CÃ5Û،mD;5;nr‰ŌRS5ėģĀ„ÕYLéŌÜŗrŗÕŽ0ŅØmqĸQ°zØ;ƒDĸn‹™Îh;Ø}Ø1G;Áę:íČlgVv‚P{ í˛ ë#6ėŲčŒ6Ā ŨAüūŨF˙gđl<Ö!7“m#‘ŌWĖÃۗŒûMʏŋˆˆˆŧÃŊNÛË-üú/o}ųÍ)ä,YÎZß°âÊ H ĸŧ›*0iƒ*Öä4‘ÎūŒ•!˙?ՌŨWBQĘ ŗ3ÔLčdŋĐũaĮr6VzFĒ™-Øí�š¸\Vh Ōf,Įf´ŠšqU:ÁNk7ĀApĪ&ė’0âsŠŌ™ÔeÆ[ęÅhĻąšŌōÜūŠ?fœĢ|ƒ;E$úˎ–Ņ“üú|UĖxÖ<MĨ{p;˛­šÉŒ€†A"¯Ö.ÁQ;˛WwĘC7YČN9fķũ˜Ķf664R[YĮڄ쎂Ēęrܖ8ŨŨ`˛˜‡=c ŲŲÉÅFSÂ'y{¯ŖÂŸKiíĒv˛ÍŲx˛Įy ƒî#2Ė&3ĻÁV„Š.ž…Fŗęę§q9,˜‰Ņ´ĸčÄ|ÅQě2Œģr}NŦf X…§bōMūƌ-׉-׉0bAš›üøũaŖmMlÎ&ۜ ;ĮŠŖ FÜ�ŖĻĒW°$3-’ )Ų g‹1Æg0)†ŋž0xŊËąÛ˘‰nŦ#0f• 0 Ķŋ‘j˙ČÃv#™sfĖX˛!ŌĮ`Ŧ-Ž âņdũŽOŠ—Y›ÍX,–3nõ;öėVĸXĸDģmؒS}Ņ8ÍDĸ`÷äBāƒÎfjĢF~MŠ.3ÃúH˛Ī$H$?ŊáFęšcØŊ>ŠļäTĻč666žT‘É‘a("""īMŊ‰ķ’­ōÆŖDJū˛G×ĩķÉģîáŗ)ČŊ=éļĘīļ`ʀIT™\äĩĨ– o鐟q/_OjïŸPËđ:RX’Û|b˛SPXxĘ­+7Ų ­øÃ≎1Pępâr™i ˇmŨ˜Ü<įĒŽÂ|ŲÍ45ų‰–—kÜFˇÉC鈭Ž ŖüD?ÉŽ*įRļՉãdĢšš“ŋŧÎZū4›Š‡Īē3“m;ų¯ŊöĨåÔ.-âDļ5R[]CYĨ•Āã…C‚'Škm¤[Nũkn+ÍūnfUÔ˛&õõĶI1›1› >ė#?‘}n¤9š¯ižÁŦ™ņ“ÆÁâ$\ĩԔĻÄ'ņoԆačŗÍgVĮ_ŅÉ(ĶAĖÉė€í‘ąBÛ¨Ÿ[#$lrāÎĩ`ļ˜ÁlŖ¨Ė;ĘÔsĘVŋg™ŲŒƒî1“NÂD:ŗq–]?$#g´Īė‰:M˜Í`u•R<Ę"ŊŖOS:]6ėölmmDŲŖ¯ĢbDˆt‚ÕeO+#ÛfcDŨYeÃn7Œļc˜#IJí™Ábˇ‚?J,nĻͰ1Ģ?5Ålė^VøŖņ†¯TKʗH2 c2›0cGHØ}øÜŠ üžzc\ĨDDDD&Îëŋ˙-GÉgÆā+QđŠ_ÛÍīū:,¸2m6ģäÄ?{˙–÷âläIēĻ xJ|X1ÔÖ0ūĨAĮ`ącĪ&š†ÉhĮ‡– Ŋ¸LŨ´ōIä20Žqš ÜJ¨=šŠÃ[xōß΍.7Ĩ%vˆ6ŌnĨÉߍÉ[‚7ĨŨaē‰ŽX�Ô =ßY*ãåÄé0Å‡lŨkĪĩa2ÛN˛€ĨA¤u[ĘzKËYSę  łÃa…p€ĐĶ"„ÚÁ1džĪIÉüːāNōū÷;v DÂCŗGB­)=0n`ôO+if{”“Œŧ’ƒw“Õœ2ȍão LÎÁZ<HCM ŖŽ iĐŲmŒČF:Á‚ĶíĀÔ¤Ĩu”Åbâaš›ũ„Úâə4V+ŲFƒäē%ƒf’—sÅbĮjîĻsČb1‚õõ4‡ãũ™rÃ2YbÉ`ňÎ4øoV̉xˇ1ôŊXĖ$×v9;MˇģŨX‰ā÷ļø­AÄßĘĢ8pMâ]eėšvˆF Gb˜íũÁ›kw”h$J,ÛŪŋŗĢŨ Ũņd =åΌyč÷Ž#:¤ËÅčė‹5š3�“Å”ō}m ˇ§|“GÆĐOeŧ}.z$"""’ę/ŋá—ŋy}ČK_æåĢ˙ôOl|¸_ĘļÉ]°ƒ)-]wūnŦŲ?īj“6¨‚{kО!ZOŲį>ąëÂq÷RëOĖÆg¸)ōfC"@ŨæöĄ‡ŒV*—\IÁŠįSÍtãuš‰…ëh ]TŅârbKņ×ĩÁ×uĒßoĪŦ.Gq ŗˆŌRUƒŋ;oÉĐ=yíN'Ų›†.hK{= ÁɚŊ`Ŗ¸Ė‹Ų_ÅĘú ŅxœX{Æ{¯Ã[t/1›'´ą’ŌUøÃíÄâ1ĸáįŲØÅäôā�œewã2üTßû<áXœx{ÆĘ*f/ežq Í\šŽßL0'ŪŪJ͊Íāq‚ŅÎĢí㚯ŗ)öŲIøĢXŗ-L4ÖN°ņ^6†L .Uëtã4EhĒÛF4'ŦgåęN \fŅ0ÉÍ>’‹5'^ ˆ´3lĖrd“đ7ŌŽÄ_u &v’ËķūÔ1ĸíí#ūb`qâqY‰ûëiØ$Ō#‹mhŦĮ13ĢĐ9öî>N%N ņ@=ˇŒ$Ú~pëķÔÕ5ÍváØ8׍Ë'ĐŧH,ŽaĉEZiظyŒ ÎāU0‰X„h,Nüto y6nG6Ņ@3HŒXŦđļf`ĩ[Ā–‹ÍÜI[đ qÃHöIĢŨD"ŪÉ@<Ô tFۉœ!§Û9â§1؞</ŪN¸šžē†į‰ž­‡lqSėu@¸‰ēúį †mo'Ōŧų{4E Āįc´­' {.v#B(j`Íí˙Œ›mØŗc„BíÉuhú‹ÚœnėF˜–æ0ą¸ 6RWˇ˙°˙‰NôŖČļ�Ŧ8m€›5›D¤`,YGd[#!s.VâD;“A>ĢŨáÁ °ŅŪJ ʐŲ=ɝČ:‰ļ§ŅīDDDDÎ ƒ˙ly™ũ¯vėƒdöīļ<åŌøū>sđȟ~ÕÂö˙>/ œt&éô� Ū ?§†[XĶRG™§ģËCŨ‚ƒxg”P0LgLv5›6 ÉāÎŊj=E•´ÔQŠ ØkĮÜĸĨŽ‘@§•ĸęÔŨs,¸ŧv5~ü 3žÔESr )ČŽÃß a-Á•{ę÷qFuå–PęÚČÚPŦËû§Ĩžą2* šŠŦĄdeĨDƒ47†ą¸í˜œ XæĨhĸŠęÚģ(Ēî&aĘÆîžžÚĻĩxÆ|Ž6J߄ąĻ†ę[éL˜˛í8=kiXŋ49Vą•P÷´ÁšĒJ—T’ ģ{)›Ö3bÖԘr)Ģ]Kxe ež:°ēđ­Ų@­;ŒǤēčLÛ|ĘéŽUS̤ļōs4‘Í,īŨT¯˛Ę_J¨^deU%Ū&Č.đ˛fũŧąÕ´­ŦŖ´šZîÃSZ‚Ŋ˛ž˛Ī=Oņã˙—ęĩßgEįŊÔ_EÉŽ{ųZ6n°ãī R]ą„ʍ{Ø´ôff oŖĨadØŦ’U”8ĖØ—.§ÔÖJ ¤šÍOÂ�“9‹Ũާt)îܓĩՌŨWN™Ŗ0L 9DÂ0a˛Øp¸Š).œōl¸—/ŋÃædFÅ†ÃSBņIWu5ãpģ5‡hhˆā*žû´wĻąû–SŧÍOĀ_O ŲÖ\<ÅžūÅdyÛi 4ŗ)Ųvīõ8Œmt6h¨‡ŌōBœ.;‘@ Qž˛Žë)õmÃßÚLŋ›„9ĢŨ¯téЅVΐÅYB™5L &äoN&îY,ØėnŠKÜ8Îe–ĪŲ`ļcˇvķjԎgđÁY°ÛÍl¤ŽĮcqR\ †ēf˜ČÎÎÅé;ą•ļa€ÉbĮëąnާšĶ�‹ §Īwbˇ°Bžîfu5ĖVė./ÅK-Dâõø7Ķ\r7%nKĸÍę&€‹ÃMQĄhķ‰ B‹ÃÍŦĐ6ļ7lĻÍŗœ˛q&͉ˆˆˆœSŲĮ“OMãˡ.dÆEŠ.bÆåš\ôĮ),ģÉ;˜Ĩō§ßlá[Û5ķpÁ‘#Gúlļŗ›Ú}Á…ī;Ģõł<ŪĐH !ÖmĀŒÉjÅé,Ä[TJņŌÜĄY*í›)ōÖ-zšļ )Qˆx˜ÆšīŅāí6Ā”p—VŦĸdøOą‘‡ņÕ҉‹ĒЏ)<lā_qƒėĸ§ ĨÖ?ÖuĶŠ+…Ņ|ŽĘ�ļ˛üĢFŲiČͰēŠ:„ÎdÛ]ø*×ŗ2ąweˆĸē_S[8ō4ywĒ\SuFįO™vŸ-ššöĖŅ ŧŅEÛö~úËvzĪčJ§§ļzíß÷ö[gŠ%IĘ{]põ–7ÛŠÚūcJ&īŌ""""""2IœiP%i žt6Ιä~čƒd]¯÷Æ99˙ú0…y˛U&ņô ˛™ęæN˛Ŋëī’ """""""gîMūüûßđËß˙fĸ2i)¨2)Dļm#Ô ąž™WņPŗęģ ‰ˆˆˆˆˆˆČyĨ Ę¤#°Š’Úˆ™ė¯RŗŒ�� �IDAT5ë7(KEDDDDDDd’QPeRĘĨŦ厔Mt3DDDDDDDdLNtDDDDDDDDŪ‰TIƒ‚*""""""""iPPEDDDDDDD&Ŋ .¸`ĸ›0‚‚*"""""""ī2™ø�ôõMt3Ξž>>xņÅŨŠTy—)Ŋš&afGÚ.¸ ųž&UDDDDDDDŪeō.ÎWĘo'ķ˜”ĶfÆë‚ . ķā+厓wéô‰nÎ9r¤ĪfŗŨJ/|ßY­ODDDDDDDäLõŊũÖY­O™*""""""""iPPEDDDDDDD$ Ljˆˆˆˆˆˆˆ¤AA‘4(¨""""""""’UDDDDDDDDŌ  ŠˆˆˆˆˆˆˆHTIÔsQißÛo‹jEDDDDDDD& eLjˆˆˆˆˆˆˆ¤AA‘4(¨""""""""’UDDDDDDDDŌ  ŠˆˆˆˆˆˆˆHTIƒ‚*""""""""iPPEDDDDDDD$ Ljˆˆˆˆˆˆˆ¤AA‘4(¨""""""""’†)�ŅhtĸÛ!"""""""ōŽ2ĀápLt;DDDDDDDDŪQ4ũGDDDDDDD$ Ljˆˆˆˆˆˆˆ¤AA‘4(¨""""""""’UDDDDDDDDŌ  ŠˆˆˆˆˆˆˆHTIƒ‚*""""""""iPPEDDDDDDD$ Ljˆˆˆˆˆˆˆ¤AA‘4(¨""""""""’UDDDDDDDDŌ  ŠˆˆˆˆˆˆˆHTIƒ‚*""""""""iPPEDDDDDDD$ Ljˆˆˆˆˆˆˆ¤AA‘4(¨""""""""’UDDDDDDDDŌ  ŠˆˆˆˆˆˆˆHTIƒ‚*""""""""i˜rÎjNŧÄm¯$p|ė"™dMŸÁ<× Ü^ūæO7ŗæœJĪ–ræŪŋ Žgߏn kÂZ""""""""īį!S%ƒŠyŗÉŸ=üo͞;ÜÆöŸ>ČMŸž‘Ē@ĪšoÎ9–Ø~/ŗg]ËÃ&ē%"g—úˆˆˆČPį.SeĐTŠŲÂÚüŅ&íäģë*y,t'žū î˙؀wŌD˛–mæđĩ 0YļĖĄP˜ãL\ÆČšĸž-"""""2Ԅ¯Šbēl!÷ũā[,Éz_á';&0[å *ĐÅū¯•ĻˆL.ęÛ""""""ÃMxP€ŦĢX’pœC:Fī ķķõ+YöiÎ+æwÅ\\Ÿž•ģk_âPbŒ:{Âüøˇ˛øsɛ5‡ŲŸXÆmë“å÷×^KŪŦ9,Ž=1ĄgK9yŗæwĮV†„uzĐR{/7\{Öf|1×ŪņĪü[ ƒÄķ š/p„ĮŽO–]X;tŽD΁—xøkˇ˛ønfΚCŪ¯¤jËF†“ÂT}jyŗžĀŋía÷ËYüņšä]q+?īĪía˙–áîâkq}ŧ˙> ^/<ĘõNRS˙ũq~c'ĐAëīeŲ§’īaößō�?Ū7V]ė~æŸųRņb\WĖ%oÖ\œŸ¸–ŋQGëŅ”x¨ŽkgÍ!ī˙ĖîuæŅO÷ß˙¯ŧ4˛í=[šíŠ9ä]ą˙X}"Uâ0ūŽņ\ƒcßܞ[ŠúÚúŸß\fö>Π|מŸQõ•/°°ŋßæ]áfĄ¯œUOîdä)!V}|ycNÉ ąæ#ˇ~ÍMŪŦšÜöbēÂü[ęįäã‹Yöĩ:v§Ü‚ņöm‘÷šķ0ũg<ƒÁ‰É"‡~ÆmĨ89.^Ā4Sûwîâ…Í{đīØÅžEᴔszv˛æú/ņ“ s6žkf3önŠdŲžžš×›ŧVæ)2SzBŦ))į'‡“‘SĀüÅ ˜fJĐu8ĖŪTī|™í>Ã37æaĘŋš;oͤõ™—9p<“š7|†yY&r\SĢëÚ~/Ëžū2Į3˜:ۅwá%˜z°;𠞸˙´ė¨eËŽaúāũ÷ã8/>ČcB˜œNÜĻd2ŠĻ‡Öo,ãö­¯AÆTōŨ ˜?ÕĮް;˜ŧž?øC^üÎÂq-Ęk2e$z_ŖåkĢųęŽ æēāÍīåĐžBĪąö–Ã$ž˙)_ŧ,åÄD˜Go)§&Ü ™3p{ŽÁ›™āč=ļ>Bđŗšķé'šon\ļ€ų9p ŖŊ‡`~j=]{Nū×ãûvąŸk˜Ÿz™}ģØ{2.būŠîM"LUņ­<qđx›Žbzôt„iŨŲHõÎ_|ä~xmΐĶmYɍ÷˙‚cdãtQ8ē‡ n}„⋝pįĶOqß\SšåzĻ‚×íę/ŋ€Ī.ž =¯ąwį.žũÖ.Zļ”ōäĶßdūN3™L@/‰Ŗ¯°fãüŧ'ųs1'ŅÁŪ`û^z„›ãŲįŋÉ<ãęÛ""""""īIGŽé;'ŒûnŊ<ŋoÆĖE}í?EŲ?né/ëęĢđ)ö÷ũ ČŲ7cĻŗoŅ}ž?Š˙h_Ķ—ö͘™ßWđå}Ũ)‡ūŗzQߌ™ų}3–ŽëûUę?ûV/uö9.Īī›13ŋīšĮ ę~ļ,yΡ ÖõĮŸ,OÖ˙şöũaX“ģ÷nčģæōüžģ§ī—ƒMö­ū‡üž3¯éûÎđ÷ü‡Ÿö}áōüž3ö•?{´/õ]öũqG_å'ķûfĖtö}á'GSęûÎŌä}šę“7ôUļūąoÜöoč[43ŋoÆå7ôũ`˙Ģõŋ{Ēī†ËĮhįXüwõ9fæ÷Íø˜ĢīĒĪ}kč}5ö÷}§ČŲ7cf~߂ę}Šú~ĩ6ų,E†žĶgôũ×Ëû fæ÷Íøäēž_õ7ņW÷%Ÿéžú^ģ_¸ĢĪ1ĶÕ÷…/Ū0jģžų Oí;•?<ņųä{Ū?úúúūđlY˛M˙pĸM}}}}}÷ėōúڝzRwßÖÜĐ_߆ž˙Jģüædšš¨¯Ō?ė9wīë{hāūŽŨ—Ōw‚}•;ŲsŊ?ūjm˙įæcŽžE÷íúšė§ŽžŠVã”u‰ˆˆˆˆˆŧ—MøôŸÄŅW¨úʡ“[/į}†Û='~éOxėāq˜úĒZHj2 Ļ>÷ā]¸3 7POËāt…0/ėx Č`IÅ=C՟æbíˇ?Ī´“lķœęčáäT¤ių)Ų#IYsīâ‡O?É ÷Ÿ:3ØûTÁ㐚ø~j–å ]îsÚ"Ö>p5™'øÔĪ8”r(3 —žüŦõ š'7õjÖ~ŋ–‡YĪÍųChēė3Ü4ā{ŒkŅ ŊSųÜCÃ˛%LųÜ´l6�Ÿ˜šĶõmy ˜Áŋ3ėY`bÎmë¸s6Đņ2Oôīü4oņUdûmœ˜í’` ãŗšî äp„ŨûRÛ}˜ŨÁäu–,š]2šŦ…wņŊGÖķŨī‘õ1ũÚĪS˜ kcīҝīũi=ûŽÃÔkŋÎ}îԓ˛˜WņuŠō.!/ã0{ĨYūЇĖÅ÷°vɰįœåäžĘEd/ūŒŨã™Ū4ŊĻET?´hčįjúÕÜäÎ�zGŸŠ'""""""ƒÎÃôŸc´|}ģ3FIôvp´Ŗ—ã�SPõũ{˜—2ūßŋ#D/špҐ×M[Ā’|†Ã÷%¸y‰ ē˛ŋ‰Į5rž„iîg(ĘkāąÃ§nųôü2xÃ[áŅ…ëøĸ;5bbú\׊+N ú3˜wí‚Q§Ûdš1/ãe‡Ãė킈Ģį-ŧj\ĶtM˧pIʖK‰ēēzû Ļ ā8==ãŒ0 ČYĀ’Qvrš–3• āxĸ— ‹SrÆ:ō(ô\BÍÁרģķ,qbr/HŪ‡pˆũ,b�‡ „ŽÁôRæģķd4Ōēs=7^“ŧ']áäÔ œC§ !벅xSĘ%zēčé9žŧ7‰˜€ŪŽõ”8ņüæxœ#÷ŋ1-¤æßw¤ŧFų}Į�˜ŗxôūašÛß?zÃCáģiŽL×ÕŖ|ޞ˜–“ŖįØYŠŪˆˆˆˆˆˆŧK‡ ĘqŽ>Čąąį\ÅMˇ.įöe‹¸lČh2ÁŅŽäY‰pĢžöܨ§wK^ãPĮ1 ŽuА1•iŖ&v䑟—‡OL˜ví:ÖžxkwîĸĻt ߝ:›ųŽĢđ,YDáB×°öžĖ1’I/Į9ô˃ÜŊ}ô2ÉĈtĀĐô ĻåœF–JŋÄĄWøîÆ:ZvļŅŅ{ęōã25‡ŅZ286O‡wutĐ Đŗ‡Įžvņ&:’ĪáXĮazp’•品đv‚y—G÷°ģĻ~ÁÉeY3đĖ…í)ëĒ$öíb˙q˜ęZĜqžŖ:žģų9üûŽĐ{ĘŽ0đü˛˜>u<=ŨōS™ž3FųŦŠLĪŽŖĢ 8 A•ŦiY§Ø ų4n""""""ī1į!¨r ˇ?ŋƒĩÁ=ÛW˛øŽ_pŦ'÷ĩÃ*� ‰ä îøá]ŧpŠĖ’Äą ũ‹ŪšLc Mdeš׀ҔĮÍ?zy/>ÎcĪŧDkč —xЁj2Éŋæ.ĒW/gŪ)ã ũÁ†ŽĐ˜|RÅqzF$Œõ^NrÅõÜxˡŲ× ™ŗ¯æök]äOĪ$+Ã$nŽä‰điVzēmč~ôdûKO^x S„æģg@ø0Áp_ž,‹žĐ.ö“AáB'`bŪÜ 8�ķķa˙Î=ô’ÉüÅŖd…Œâč–rŽŊŊd’ˇ¸„;:™–•EVĀa~r˙#†Ąž_Æ8wŨ>åMũĮô$pÚ=b´*GI‘q›°Ũ˛–Ŧãž…{¸oį/¨Z÷ ķ0lmL˜˛’STōƟeGå8šĻ$Æšē Ņ{:͞˜sí=|īÚ{ ŅÁū`ˆÖ/ķė‹ģ8đŌˇšņđ1ļ4ŨܓŽqM˜˛€cSšŠ!@ĩû4.Ÿ–.Zj˙•}Ŋ0uq-/ūāša÷6[3xâg"deõÚ]ë>ũųQ3\F3gņUäl>ÂŪ@˜Ä2{aŽãÄĶŋSΜ…NĻn~.šŽJ~oręLÆ–¸ĮhHėäáõģč%÷ĪđĖmyà dX ǘČĘŽõŽđÍé—7™€ãŊcwÛÁĀ‹Š÷žņŅ‘sgĒÆį¸‹šplĮƒTmīvÜÄôœä–­]Gp˜95š&Åņct ¯€ÃėĮԟQ™r˜ãš/?´™˙^ËuSáøÁŸņDđT­Ë!*@G;FmÔYv„āžãĀT oPčH˙œ†Ŧœ2:sôT…Så/b^&ô†wą?q@¸ō0āä/`NF2CĨ§kƒÉsÆĩÕđá=ėī2pĶÃ*@׍˜Ģ–C^@/Gą°o"Ab Kę´ËO=QžcŒō=íI–žēoöʨ=°gŦĪ€ˆˆˆˆˆˆœ ģûĪeËŠŽ˜MĮxaŨˇi6�œŗĐE&ĐzyŒOėŧÂîC)'NŸÍeSØ7ō¤Äįđc‘Zča`+?~f'Ŗs§-`I~ ë”™/9Ėw_g÷‹ģ}œ{˜ŨÛCę:šud5JRC"XOËĀ=;5âŒ%W:vņŅËtí{˙žŽĄ÷Ääd‰;:Âė †’ëЏœ Ž-›åēĮ÷…Øģ/Ä~ ņU#vhŨ‰éa#c0 ö?UOpÄë9Ė›{ �{wėĨ?„Yķéyä_š„Ē`:åķúûė}q΍ũŖ'øJrŅߊWáŧĻūįÛK×(ũĻ'øRō9'&|Kå9ˇ­ãæ<āØsŦYŋsȀŌä)=qėŸ^–íāč‹đĨÜTúmZĮ”NŽ[8čÅŋąžũŠc͞ã9ē2Į͞cŧđ­ÕŦ]ˇšUĪ™ pôžŨwČaNŪĀđ|`Ũ‹c=:ôŒ9ˇ–á΀ã;ŋÍĒ-ÃëëĸõŸ*¸íŽÛXv˙KŖqNKųy�¯Ņēãkõ¨įK÷īaš3yē:Æ\BøĖe-âÎe—�Gøņ7ū…ŨÃŪXâ@=ĢžRÁ%ˇōŨ}CNdūB'píOŊÂ!2˜ˇĐ™rŧ?hqlOü4L/—0ß=JÖÉhrœ\–ô†ya_joKphËÜũb&ss�Žq4%P1ī ˙›Įw>š-)÷´‡ũ?üZ:€œĢųŦûLË˙+UۇŨ¨ŽTmøŊdëræ˜ÁÜŧäöĮ­?}eHŋIÚĘĒoíŗļlĘØ}[DDDDDäŊjÂÖTdrōÕ>ƒÅstl}‡—ŊHõāÚų|õûëØ_ú ­,ŪYĀ|WĶL= ˇ<| 2fp͎ī§p0#ÃDaÅ×ņėXM üË>ũ …ŽŲLKŧÆîģčšģŽûōū•ĩ/j;œ<î|°˙ Ö]‡ëŠæÍÎaš z:ްwßAŽĪ ī ë¸spš—ūAnG//ÜŋŒŽŸæ@ÎįųáC‹ČšūyjžâÆoŧĖöû—ąđIķķ§bJc(ācĮ!įŠyđ†q¯=2ļŠnûGžûõ_pxķm\{`ķs įp˜ÖĐ1.ģ÷Iž7í‡wqlËƒÜÆŽģņ›|î,ė(3”‰ųĢ7°ę@95ánúÔ+Ėõ8š,z:˛;t„^2™[žûæ=sškųė!¸ŗ ¸jp=•s:É|ęe;ŠŸcËæQd-āÎe—øék<{Į2Ž.^ĀeYŊŨˇ‡Ũ‡3šųGOâŲrˇoíĨõ[åŦ ]ÃMŸgŪeËŠYŊ‹×íbûũײpŗ“99&z‡Ų×Ņ ŗšķ‘ģúˇ€Ō)˙Pˆī˙ĪŪu{] ˜—— Įް;¸‡Ž^˜ēđ›|oČ0YŪz 9;ŸŖcG%‹?UĪŧüИzްw_Ķn[Į́Õ<vđlANŌˇĪBí""""""īDžŠåšŸĩ‹§¯ņ“u˙ĘŪ”1 é˛Īķäŋ?ËÃåW3'ë5öîxŽgˇîbO&îžÎcĪoĄÚ3lX7ũ~ظ‘ÛĪ&ĢĢí[ŸŖ%ÜÜōōâ>ĪôqŽķ™åū&/>ŋ‘U7,`zâģwŧĖŗ[_Ļõ@Ķæ~†Ußß‹šR•Y=ø->ëœJFī‚Áƒ¤Îʘ~í†d}×8É:Æŋõ9žŨĻ+ËÉuåĩŧđü&ŠÆ7‡å”Ļ]ģ-”âÉ3qtįsüdË+ėMĖāæī?Ã3_Ęgúĩ_gíâd?Âîwqā\­ŊarōåĻøÉƒ%,ɇŖWúīa‚é KXķøžŠeמË0`íŲ)ëŠ T;wArjéZÄŧq¯ŨšÅüÕOņXų?’ŸÕËŪ—ųų‹aēĻ]MuĶ3ŦuOŖ°bŸ aü;ƒ —Ũ¸™×qĶÂ<L]a;wą?1•š×|Įž†ûæ퇧]~Ų&v4ŽįöÅ3āđ.Z~ÚHKđĻŧĢšķÛΞãGŸį˛aī3ËŗŽgžŋ‚%ŗ/Ž6;vąˇ'īCĪđLårú˟ų,¯“÷m‘÷ĸ Ž9Ō—››;Ņí8øŋâæÎĮq?ØĘ37žy^ˆˆˆˆˆˆˆˆŧ÷LüôŸs įPˆŨ;Hä,ĸhX6�‰ƒ$wÆš,OIĪģ2¨ŌĩķÛÜų­ƒŗ‡ŦÆoQ8;I°˙Éáį@Î?ōŲš'ŠDDDDDDDDä$ŪĶa.ž•Į‡ŒK˜ëv2=3A×Á=÷&ˇũÁ3#×b§wgP q˙“ßቭ{ØôŊĮ!cę æ¸qû—VP”¯€Šˆˆˆˆˆˆˆ¤īŨT9‡&ŖĘ""""""""ī4 Ljˆˆˆˆˆˆˆ¤AA‘4(¨""""""""’UDDDDDDDDŌ  ŠˆˆˆˆˆˆˆHTIƒ‚*""""""""iPPEDDDDDDD$ SÎUÅ{ķ-~ũ§ŽŋũoõĢ̈ˆˆˆˆˆˆˆŒî}€é}īãŠeņ÷ŋīŦ×N‚*˙ķæ[ėųã_¸øũSȚ’Ąt9īŪŒ7ßbO×_pũŨ‡¸xĘŲ Ŧœ“xĮoūÜÃÅīŸ‚ų P‘ q!pņ”÷a~ßûøÍŸ{ÎIũgŨ˙ŧųĻ N‘‰gžō>ŒˇŪ:ëõž“ČGpÁš¨XDDDDDDDä4]įdŊWĨ“ˆˆˆˆˆˆˆˆ¤AA‘4(¨""""""""’UDDDDDDDDŌ  ŠˆˆˆˆˆˆˆHTIƒ‚*""""""""iPPEDDDDDDD$ Ljˆˆˆˆˆˆˆ¤AA‘4(¨""""""""’UDDDDDDDDŌ  ŠˆˆˆˆˆˆˆHĻLtÆī v<z?ëÂo}yĘEØū÷l>s]Ĩ—˙¯3ŋ˟ū“ęGyåčĖ,ž[^}˜ož^Âŋßã&“ßRsß&~ķđÔ§­g~­ŗnō´īOø3ų—Mˇá™Đ–LŨ‘wˇwPPĨß˙rņā­.>Ü˙Īã˙ķG‚ŋx™Į6n"vĪŦšõū3Ēūˇ—yųO9ÜyO ‹§ūw ĢßČ%ãĖ[~Xšēø&ÜļĖq–˙-5Ģ[¸ôž{øü™ÆŖb;¸õŅV=¸Œ+€ÎZĖꛧ0ķ Ģ}¯ųS`#ˇYLË­ųŨ‘‘†}ÎEDDDDŪëŪyA•‹Ŧ˜5ûā 3™yüķ#<÷ƝšsÖĮoHa4Ŋ˙ķ:dšņ˞ō€Yîäž#dqÅUîņ˙Sŋí†KĪ•{˙đ[~˙æ‰ė“-ŸklgĄâ÷”7øí‘ŽOt3DÆ0üs.""""ō^÷Î ĒŒæũ9\>} ú32c;¸ą:ˆgÅbū{k#;/.â'ßø$yãOļ6ōŖ˙û;~ßYVæũC_Ŋ!ŸĐÃÖīŦĨö@ 7ŨŲÂG‹āŽ!ĶFęũCĮšv°ãđų+™\:ËÅWoö1ŦĖąÚÆD÷ĩđŨ­˙ÉŪÎ^¸čī¸üī¯fUÉĮ°$ßtāҧļđÜĢæøÅVÜKo ¤§‘{÷]Éæ}Ė>ũįNv46ōŖp;ŋīų˙Ûģûā*ĢCßã_5;œ$`ļ%É!Hp’˜ˆ@F ”!å–LĄ+p¯ŠE{aŽLæhŠ8´ĩx¤ž#ĩ"VĪ@<CĻšÛÆXđRčPÆQˆ8€…$Œ!ŧ’ÖDHŌ$$Aī;ČÎ $l Ôīg†a˛÷ŗ×ŗžõŦgĪ<ŋŊÖzڈNB֘ö2+~Ý+ßĨ([ēˆ5cæąea<ŋøá*§-`rY1k aÉĶ ™[CIq1/ŋ_ÉŅē&ˆI$clKæN"#�•[–3÷õŋ�ų^Á[dÍ}Œ')ę4õĨ•ōëy~Ë>ĘĒëCed}…‚짐=āĪüđ*§=H~Í&Vŋ‚ĒFHŧå–Ü?‡ ƒ.|úë?z‡į_ÛÆÛĮji tmˇúļķükoņöąZˆáæ[F“÷lf‹Z)Yũc–‘Ī Yx捃m„ÄĖ<–ÎGÃÖ"Vŋ„ĒÆ2ÆĪáņyŲĄíØFžũÔ>&,ø:-onbÛGu´Ț8›ĮķoŖûĶ_ĮÎ-ÅŦ~ķ0GëZ‰eō´|åĻOVū+??°†I;†pßŌĨ,Aëâü1ž4ö0Īŧq€ĘĶMLįž{įņĖØĪˇģđ9:Â3KŸc˙ä°Še§ßáS6æ~ļ,ŧœ?åĶ~ÂKSÃ+Xƕ?ãQųüöû9aíĶČļÕ˙ĘōēŪ\Ŗ�xâĄ5ųX‡ōwū˜MįˇNš@ÚJåŽõ<ķÆ>ĘĒ›:ĩ}¨~ëãŊoÃŽ.¯†F/ũ¯7†đxAۊŪĸ´ĒŽ–Ø$&ΜËŌÜT\Âųíņ;æ"íTÛÍuūRnLš”$I’Ža' ÕÖr˛Ļč„øĪ§Eˇ5QōÆn‚3äĨyŲ$ŌČÎĸydGæ=DŅS?áÅšéԖŦaqņÎĀŒÅ˙Îŗã!é+ürå“ŧ”›xŅŊRSʲ•E”DåđøŖņÛGīeFÛ{<ō‹õėoŊđĮēÖ ę÷žĘkŪŖal>/-Œu §÷įWy đOÔPĮ†—×ōJu‹HŅâ،;´gŪ¯§%Ûíô¤ō7Ö˛ėƒ(f-ø!ŋ]ū/͛Dô…,Ūp2į°Ž`q Ĩ`é“l\Í�DÚ8Yō{J3gķæ09*ˇŧ˞’zn[Āēåąna‰‡ŠY\TÎ`øÔ‡xar"ŋĖ O=É ģŪhÕė*äÂƒÄåÎcŨō§čÁ;šųØ&~a•įÚ%ĐFųÖõ”ŒČgŨĶ+ø¯åsWķË^?Ā™ žƒwxdåzĘS§ķä~ÂK rˆū Š„Ú­j;¯ÜDYōtž}ôßŲøčũä'æį+ Ų\ :�-‡ļņJã^|z[NĘĄM,{ēmÃōY÷ôŗl˛€å„��/IDATü~6õ;ŠYŊˇũÄ€ļZ6oØMúŨKØúâŗl\8šú’B)ŠéĻĸ­ė/~‡ß¨åÖģ (Zūž™DYņ‹,ÛQ$0Ŗ` Šwû<6Ž|ˆyë¨ũ+ļ°úX6˙ô ļ>÷%W˛Ļp åŊ:GC™<"†Ŗ‡Ž´÷G8Sq€ō„xâŽøŧ Ēŗŋ1‘q™û@ŧÉépč=JN‡ŊÜx˜m‡ +7›”¯ŅČÕė*äģ…Iœz//ũô!žœ–ĀžĸYžĢ蹏÷ļ ģîørû'Dĸ q̎ļ1ëÁel}qëĻEņvQ1¯×ôžn=Į\ŧzsK’$I_4×|¨rĻąš[Šyåx<“sG‡&ЇŦ;Y4>ŒäœŪGņŽZ˛fÎeaV*)ƒ‚dŒÃ’Ü!ŨąŌVH : $ˀ–g)/ŲF)ãX˛  ÉAR’GņûŋNVÍ;ŧ˛ˇņ"ŸėT7jØŧu ™ŗyrömdƒ ΜÄŌģGҞw[čæĒfŋ?93ķ™}K)É|kÁdĩ6]`­TĢ…Ôlfd&‘ ’‘5…Įô+Ļ ŠčØXâÎĩ*6›%ĶoãÖaIÄÉīįĨG øūØ4†ˇ×íž1‰ÔÚĮQ€@,ņą�ĸu×nÕlŪē–1w˛tjà ¤Ü’Í’{ŗ‰;žâÎoŲ’:‰Eã“BŖŨƌĖxŽ ęGš˙ÛØ3hKîÍ!{XYy,šûËdQG-°ŋä-ĘbŗY27‡[“&g0{îLn=ČovUŸ/(FūÔ4â M]ĘI†Ú˜Û¸b¨.ņ™ŖÉNhĸ˛Ē6līm$Oŧķķņ™ĶųN&”íØĮÉÎmÜĮ–ü…ô™ķX2>”`['æŗdb Ĩ[ŪĄCt�ÄŒe�—ĶĮēҚNūėQ¤€@Šˇ§Ũ>Ŧįs kĖp¨8LY{˜SVv„ÄÛī`\k%{ÚOP}E9GcĶÉÖu÷ņc'19p„ßŋ>tĒ?ô{ÅŦ1 Ћk425l{sŒÍŌÜQd$§’;—%ĶŌ‰ŽĢå ŊčãŊjÃŽúĒļ´˜0-¯}TW€áˇįÁ ʎ…5ĘEëÖ‹ī˜žÚŠĮë\’$IúâšöĻ˙MÍé f“į°dløü(2F„Íũ¯>By["y#:ūēzsæPĸˇž Ŧr/i :Ę+ūÃōžÛAéäÛ(>tÆ_h™ÖNuk=ÁžcpķĖ´ĶFâGŒ"Ŗm={Žĩō­Ø#T’ČŦԄķŌÉÅīģA€ŦņiÄnāĩÕĖēũ6&dĻ‘Lëq͙äÔ´S(ÄÂÉëyūũœŦkĸĨĩ–Ö&ˆmíŨčÖjĘĒ⿉Ã;ė{@j:ŧGåņ:¸%ôZbōĐÛDĮ ĩņëŒÔQ~ŧ–褴°5v eüˇX1>ôū†ãĩšŪ>…Ŗ]ėP˛’Ą´ĸš3´Ÿ‡āĐĐÍ(�1ÄÅBt°rcˆ‹–Ļđ;û2†…¯/Ëđäx˙'ĄC¨*õŋĖđíde %úíJöŸ†Œ.Sœ.§u#yhØ4E4­ÔˇŅĢsŸ5ŠŒÖwØS†USz˛ō'‘}ü]ļU4ōä�ee•DgN"ĢģũF1ëöÜą‡ĘŠy §•=;CÖ\&Į‡úúm×~} ŋ}hû%€X&Ė^Ā„ķöŽ_Ŧ ģčÃū5„Ŧđ *EĐŪ/z~{ņ“Đ‹v’$I’ÔÁĩĒ$}™'LúüĻ?:@brB7AA ŅąawM4Eb§ĨbˆŖ–KüŅZŠoŽ1Ŋ ¨ËģŅÉMœ°›“‹Ô­ĩ‰–68üúΘôz×­ĮÕ5­´čT˙�q 1Đm¨) x)æ-~õf)˙šöüŧ-†›Įäądîš_ŧģŪ-ŲYôėM$îŊ,š%‘¸(¨Üúžá2:hmĸĄ â~ÚÄ5­įī Ŗ/é×īÖĐy F] (jĸĄĸƒ1ĻG…B“–Öϰ°&ĐÍĒŽ•éîDuũĩ>­]C Ļ&Z¨ĨøéEw)u M@—sr9}Ŧ;Ũãš]õâĶÜDYE$ĻŦn(3FÉĪęC•œ™OiE+Y3Ķ/P§�ŲšwpķÛĨl>–ĮÂäl;Åä…ŖBÛ÷ų5zūØZÚ ēķą}îRúøEÚ°ëŽû´öėâįˇĮī˜ÛI’$IRg×^¨2|XjĮQ�Ŋē1ĢítcvĻą‰bēÜČõĸ"ĄĄđ™ŗY7wT—›™č˜ÄŪßėbˆ‹‚ôŠđo:ŊE\BTˆĻ5T˙ĪjnĨĄîBĶBu>6GĮæAkåeīąúĩM<\ßiąĐ‹h=Ėæ]õ¤Ī|EãĪ˛¨lŊ„šíĮw˛ą ;ž ŨČ÷ž`âÛo>ëĄÛ`-üæôüų¸ĐÍėĨjŖžS3œimƒØnʍ‰!šDž^PĀw:ļD‘ØíÉčÃ>֓^Ŗ 9™1ŧ~č5 (f ÃŗŌāÕrŽÖ$Pvz(S/˛h+Ãr˜‘ú›ß?Î}#vS›Íŗįƒ~×hËÅēcûą]°ĪöEī~Į}Ō?ûäiPŊųŽéŠ$I’$uqͯŠŌkŠidDÕRVŅqXĮŅC'h‰JÆ%¯š˜@ƈ!PS͙`ÓĪũK$.Câ K CÉEUMÉÉáeÅH  $%™ZĘĢÂî8[SRŅíŧ ‘ō˛?ą˙ÜZ2ÆæąhōPZĒ„­ųqĄĪŸÛG @ü ˜°×°ųƒĐŌ–Ŋēá e\r¨­ëÃ^>Sq˜JÉHí|“×[ d¤&Âņķë|�Ôėz•ī>ĩ‘Ũ­ŨŋOã öTAFæĐË %š(?t<ėī:ʏ×A°›Đ/9ƒŦ¨:ǚbÂÎoÃĸˆŽ vŧáūŧŽ}ØĮzŌËs”••ØüÁ âF¤‡Ž3uu‡Ųšë�•Át˛/z-%1#7ĒŊīđŸ;’xûÜzî­^]ŖĄŅAõuáũļ†ĘĒ‹„‹Ąd ƒŖe•aĮÖĘÎâ•|÷Õ?Qß}ŧ[ũŨ?Ãôæ;ϧvęĢēH’$IGž8ĄJėhHŲEüĒŦš“§k(ßģž'JjIĪũ ã"¸?ÍČÍc\ã{,/*eU55ĮŲše-ķ—=Įš.å×Ū 3Ļ&zī–ũąœĘĶuœŦ*gÃÚį˜ûÔĢėl‚Ŗ˜‘ Ĩol`ÛąjĒĘųÍÚßSˆš@™Mė~Ŗ‡×ŦgÛGĄã­ü¨”_í¨&.uTčf8&Š8j)-;BeU]÷ëŖÄ†nļËJļŗģĻŽšĒŦ^Ŋ–ĖáĐøĘĢ9DĮÄĀé#ė9TMåéÎĮ$oÚhĸ˙ŧ‰å%íĮ÷Q)ĪīĻ%3ü[.ĄŠ:ɘú˛wķ|á;ė>vœũ{ˇąüĩ÷8Ė #�ˇNÍc\ãnž/*eM5Uål(ÜDiėhōoŋܧ—DҰwĪī­Ļæt ûwŦį•C0.wt×Q@ąŖÉŸ˜@Ų†BVī=ÎÉĶuT~TĘ3+ÆüĩĨí3¸bˆ@CÕvĢædc/ûØąí,~j5ŋ9v9ĮŌģs4 sY§÷Q\ÖDFÖĐöcÎ¸ā ŠßŦ$.ķ6zZå%xû$ręŪĨ¸,‘S;´QĪ×hYŠQ-+e#@#åÜĀæē‹ ē ’÷ÕŅD˙y=Ëūx€ōcĮŲYō*Ī—ü…¸[ԈīeD˙öĪŽmĐãwLOíDOך$I’ôÅsíM˙‰X,Ųųđd ˜Õ…OŗĻâ‚CəYĀĸ驑ũ"ĖáÉŰæĩm<üDQhŠBr:3<Ā?ßri)MüØ{yqŪ&žŖų¯ÕĶĪ͙Ų,]|'b’øÖÂ{9ēv˗˙$ gōĖ9,LXË#ŨVŽo, ĨhĢ_x‡ĒĻ6ĸ†•5‡gīž-42bØ$ō3÷ąĻč9ž›™ĪēÅCģ)'‰üyŗ)[ģ…‡—ū‘č`:yŗæ˛4ŗ’–ŠWųÅS/ũčƒĖ˙&ŋ]˚•OŗmÚCŦėt??ÛÖķü–—™_Ô1‰dÃŗwOę°(î% NáŲÅđĖkÛxäŠZZ‰dŨ>—ķG…Ž18‰'ˇōĖk[xxY Ässæh_<‡ŧ‹Ŧ+Ķ; äΜBtÉZæ¯ũ !Œ›ļ€Ĩ]ĻW�Čž÷žŒŨĀęĸ)Žk‚˜!ímpn*V,9_ũ27žÅÃOífÖ÷Ÿ`If/úXĶ Ę+Nrą™`ŊĐĢs›NΰzJ+ŌÉqŽ7"†5oˇ1ulw}¨“ؑ䍈ĸ´myĻBõæM`j~>ģ×nâÁžEtl"Yį°pb5”ĩ]pTIpü\^lÚĀķ[ ųîkMĄë'ŋ€%€„^õņˆúiŋöĪŽzūŽéŠ`x§ëŧpvęEö(I’$ũũģŽĸĸâŗ´´´>-ôŋūûc‚.oĩ uŖĩ‘zÂÜČļ_ü+Ëš—ßĪîņŠ>ęCUųöOß#û‡O°ä2FÚôVJÖž@Ų´Å,ėæQÆWĶģYüĶbĸį=Ɗą—ŧ˜‘$I’$E¤æL ˙ãoęĶ2ŋ@#UŽu5lXų3~Ņ˜Í’yyŒÔĘɲmŦūs€œûG¨|‘ŪĮæĒ4îģÚ•Æ:*ĢŽđúkÅėIÎcŠ$I’¤kœĄĘ5#Čė‚4mbõ ?Ŗļ)Џ`“į>Āĸņۜ~Ą ĘfÅŖŲũ]‹Üą–š¯ qÄ<ž īԟā%I’$IW§˙H’$I’¤ŋ{WbúĪįé?’$I’$I}ČPE’$I’$)†*’$I’$I0T‘$I’$IŠ€ĄŠ$I’$IR U$I’$I’"`¨"I’$I’CI’$I’¤ĒH’$I’$EĀPE’$I’$)†*’$I’$I0T‘$I’$IŠĀ U>ģRK’$I’$]‚Oë¯ëûr¯H¨uMmg¯DŅ’$I’$I—¤ší,17ÜĐįå^‘PåÖh>{–ĻŗgųôJė@’$I’$ŠŸMgĪŌ|ö,ˇ&&ôyųQ}^"đ˙ĨŲ˙I§[ZøÔš@’$I’$é˙ŗë¯ƒ×ßĀø!7Õ÷#UŽH¨Ą`%gȍWĒxI’$I’¤~åĶ$I’$I’"`¨"I’$I’CI’$I’¤ĒH’$I’$EĀPE’$I’$)†*’$I’$I0T‘$I’$IŠ€ĄŠ$I’$IR U$I’$I’"`¨"I’$I’CI’$I’¤ĒH’$I’$EĀPE’$I’$)†*’$I’$I0T‘$I’$IŠ@ԕ*x× øæ¯ā¯ƒŗŸ]ŠŊčRŨp|éāw÷ÁŠũ]I’$I’Ž]Wd¤Ę'a¨j0PšÚœũ Ēęaâ/aīÉūŽ$I’$I׎+ĒÜų |j˜rõē>ũžųJWD’$I’¤k× UŽŸžĨĒO]˙]×ߕ$I’$éÚuEBŠ\œš%I’$IRä|ú$I’$IR U$I’$I’"`¨"I’$I’CI’$I’¤ĒH’$I’$EĀPE’$I’$)†*’$I’$I¸*C•1Ŗá˙|N.ƒĪž€O† 3 g`×Ŧī$‡Ī–Á]ũ]I’$I’‘Ģ.Tš+v~ FÖÃO×ÃôBøÁ>H;˙îŠëīFč&ØŗrÚ˙<Uķ7ÂŪ~­”$I’$IŠTTW \Ō8Xwl˙|}WØåPøüū_`Í ØR gú­–‘”#ÃZûĖĮPøq˙ÕG’$I’$]žĢ*T™˙e| vuķæ)˜˙ËĐ˙į•Aаę0+ÃĒ˙ ŋŦ ŊŸ4Ž|îyîš Ļß›áõ7aūŽP9wͅ_š¯ÂȸĐ>Vl„§*ÎīūŸFÁǝÁ”/M°sl†ÛÂļ*c— ųxŊ~° ’'ÃÁiĄmv>;ŗ€Ē0ëqø]ûįĮŒƒUšĄĪĶ{ËáĮ›á­†ĐûĢ#K`Ս°b4¤ „ĒãPđØÚĐ'@’$I’$õÚÕ3ũg LOŊ†Ę lRũ1TŸ 1n‚-÷ÄŋÂô˙€ä˙€Uõ°æ~˜78´Is[¨Üš°Ļn|ƖĀŦģ  l›Ã`E Üķ<ܸ~đWX1Æ´ījĐ(Ø~/ >�~c‹āTlŸƒÚˇI ;į@Õģ0aĖ*)wÁēŅđá˜ōđ LYSöt=ļ¤Ņ°ũŠ÷`ėĪad!L-ķāŸ8_׹š0ëŒ} bž…íƒá×_ƒ—Ųü’$I’$éŌ\=ĄJ$GNõnķœ;`B3l„ŌCË/7ÂëQđƒŅᎀ-%įG{|¸/´ŽÉ„”°Âĸ`Õ ˛ hƒ_alûú-ķsapĖú|p >Ŧ€ų›aā(˜ßÎÜķeā@hĖÃ[ģ  šã`@œjm×Ü�gÂFˇœ3?ūæī€OAåņĐąJ¤†ĪÉĐ3g�`] N´Ū5›$I’$Ię#WO¨4Ŋ}ĀĪØā$ė (šaįĮ0rXØČVØYļMœ‡O|ú†•sĻ9T—ÁQ@LI†ƒ‡ :ė#§ÁŪ�LI ÛæXĮĩ^ļūžŊŖëŋDÁ„›ā`œ{ųĖÉP�42,�Ēú¸ã6ÍÍĄĪ˙=I’$I’¤kÂÕŗĻJTĩBZ °¯įÍ„æĻPøîT3 Œ dč\ÎįĸBŖ]ÆNƒĪĻu}{{ÜųmšģŌ+Q08�§:ž š[!9ė,EŧI’$I’Ô§ŽžPĨļ +FÁ˜?ĀŨl’4 îiƒ5åÓđ‘ áaKŸŒŪhƒS­°wÜŗģëÛ§Îo32ŌÖl˙|Zį _(l‘$I’$Iũîǚūŗî]8õ%X×ÍÂĢn‚usāĮãBaÉŪ“@ L2”›`oE>rš-4}(m0ų> û×ÜÕÍ᎙q~áZ€i3`Į7;žvĄ}l˙FŽč¸í€a0’öc•$I’$IW•Ģ*TŠŪ 22ūoX4ĻeĀŧ¯ÁÎīÁ”SpĪÆĐš"ĨīÂö°ę›3’n‚ī́éͰĒ͇.Åēhŋž˙Ão‚īåÃÁīÁ´öŅ%ŋ~š3CیI†iãaÕx8u,Tßæ& ό•ŅŲ¯KBŸ_7ž}аf Ŧ€UĮûöx$I’$IŌåģzĻ˙´ÛēÆ=ų§sÚ§ŋ|[vÁ=o‡įĻœ‚Y/ÚoĀ–E0˜ĐB¯÷ŧ ŋičÛ:>�SÖÃǝÂŪoĀĀÖĐžæŋ [ÛcŠŪSÂĒ\Øų  ^ß íOūpŧ~Ŧ¸î)éŸtÜGõ>˜úüŪģ€&Øy�Ļožđ#Ļ%I’$IR˙šŽĸĸâŗ´´´ž-ôŅ>-NWĐgOôw $I’$Iē6]UĶ$I’$I’ކ*’$I’$I0T‘$I’$IŠ€ĄŠ$I’$IR U$I’$I’"`¨"I’$I’CI’$I’¤ĒH’$I’$EĀPE’$I’$)W$TšîJĒ>wƒ'J’$I’¤ˆ]‘PehđŲ•(Y}æ3øĮ„ūŽ„$I’$I׎+ĒüöÛpŊŖ Žj×_›îëīZH’$I’tíē"ĄJÎ0ØQ�Éq†+W›ë¯ƒ¤8ØšƤôwm$I’$Iēv]WQQņYZZZ×C’$I’$éšâĶ$I’$I’"`¨"I’$I’CI’$I’¤ĒH’$I’$EĀPE’$I’$)†*’$I’$I0T‘$I’$IŠ€ĄŠ$I’$IR U$I’$I’"`¨"I’$I’CI’$I’¤ĒH’$I’$EĀPE’$I’$)†*’$I’$I0T‘$I’$IŠ€ĄŠ$I’$IR U$I’$I’"`¨"I’$I’ëŖĸĸ8{öl×C’$I’$éšqöėYŽ8p ûÛßúģ.’$I’$I׌††ŽOLLäôé͜:uŠO?ũ´ŋë$I’$I’tÕ:{ö,Ÿ|ō õõõü?đ˧ƒMOPO����IENDŽB`‚�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/register-enter-data.png���������������������������������������������0000664�0000000�0000000�00000160113�14156463140�0023230�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��[��É���kŪ,z���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨxTõ˙ũ§Ö;3ļ›dvž3k¸:!,LBmÚu†oo˜´ ULŦÔğ‰ZIj+ą? Ö-PjZkÂzWlĢÄ]5Ņb“ šˆ.Úfāēƌ÷.Lļ Œ[ Ķ%ßN’íí™TûIÂ$$d€ ¯Įuq]͜s>į3įœ™z^ķū|Î%‰D"ˆˆˆˆˆˆˆˆ¤ÅĨē""""""""$ [DDDDDDDDŌHa‹ˆˆˆˆˆˆˆH]–úĮģīžËūđâņ8īžûî…ę“ˆˆˆˆˆˆˆČ”ö‘|“ÉÄ´i͏ôŌŅĩ,— Oûîģīōß˙ũߘÍf>úŅž´ĸˆˆˆˆˆˆˆˆ$Ŋ÷Ū{ŧũöÛŧũöÛ\qÅŖr”‘°åøņã\zéĨ|ôŖŊ`š˜üéOā¯˙ú¯G^‰] ÃāōË/?˙ŊšH]~ųåŧũöÛŖ^ [Ūyį.šä’ķŪ)‘‹ÕĨ—^Ę;īŧ3úĩ Ô‘$…-""""""""i¤°EDDDDDDD$ˆˆˆˆˆˆ¤‘‘4RØ""""""""’F [DDDDDDDDŌHa‹ˆˆˆˆˆˆˆH]v.ŋäŌœËæEDDDDDDDÎXâŊwĪiûĒlI#…-""""""""i¤°EDDDDDDD$ˆˆˆˆˆˆ¤‘‘4RØ""""""""’F [DDDDDDDDŌHa‹ˆˆˆˆˆˆˆH]vĄ; """""""įΑßã؟ĩđ?˙ßÛŧ÷Ū{ē;īËĨ—^Ę_|ôrnģĨŒŧĪ¸ĐŨ9-Uˆˆˆˆˆ|@ųŨ1~˛ų)ú˙įOmĐđŪ{īŅ?đ?üdķSųŨą ŨĶRØ"""""""ōÕü\˅îBú\r $Å{RØ"""""""ō5đ§?]č.¤×%—đ§ˇßžĐŊ8-…-"""""""rҏ†C)lI#…-""""""""i¤°EDDDDDDD$ˆˆˆˆˆˆ¤‘‘4RØ""""""""’Fė°Ĩk3ÅsfSx_āÃąß D6_‹cÎ'Šé¸Đ=‘‹ĪeüÕĮ¯äŗÅ×sGE9÷ŪUNå-Å,ųôßōŋĖēoSĶeē“gŲŅÄĻVÁP˜XŸAÜdÆdąãtš)­¨ĻÄišĐųlú•|ąėjÚ3GŊūį?˙™ÂyWqŨŸ{éÜŲÎŗŋęâí ÔĮŠčâ[Œ+īĸ!ØĻlæ¸Ŋ¸ŦfĖô„ڛļˇĐ\ų$ÍĢŨ(XģĀÂã-n$2æeSššu×áø�$ßĒORĢ#ôėu“ŋövÜCauŒĩÁį(›ĸĄjĄĄ-|ĘuLÎRV—äny˜ÖXĢĒܜęí]|ū‘ž}F“9‹ÕŽËģ §ítGĪ  „‡ļ“Å‚}ŽO‘“Ķnū>„[ĻÕXFM…3ync‡hki#Ü6oŽČæŅË/rF4„ß"Üň 6ĢBO҉ķ иÉGĪŠ˛¸XYŊ s¨‰ĮÚNú& ÛjÃîôâuÛĻėq3BM4ø˛)]}ŽķŊķX€ÆMŦå÷R’{žw."""rÂåyKųĘ틘yųØ%Ŋü걟˛“ŋå˙^z5×\ķ%žõņ­<Ú´Ÿ˙s!::]aK”–•ˇŅ4°o qCŲI7ęFø%jVÖ°ŗņj wņØ˛)z÷úĄbÆUķ$̜Ãô…ÚØ´Š†ŌHßŗeØ.d÷.Bą–[ņĒn,:/û3;<”—ģFūŽ[ØŲ“KqąëD b™üY4ē^ĸą9ŒŲY„×c'ÛÄzčôwĐŪ܄Q^…{Âæ Âm›i Xn<%v,&ƒX$L0ØÆ“ovQZyö!žjĸ1âĄzč×ęZF1ö‘@ ōĶŲgciy ›ŦŖ—§C¸åaBÎ{);Ήd,ÔBs[ė…¸<.lŲfŒžá@€öÆ0ᒠʜ°8).ˇŪ0â§ŲoPXæÅizÍlÁ�Xq•yq /à â÷5ŅcTPY¤ob[bW-Ķ÷ĸˆˆˆL9;Æ-o÷ō¯mÍ<ũ¯ąĄąĢéˇ\XÎŊ%+øęõ16nS… \sļ;ęŠ ˜ ×Ņŧņä Āė¸ŽĮžÜ@iiŪÉä,ą-kîĸøĒOR8g6…Ÿü;ŠWŽĄ%d ­ĸnŅlśĮTgtҏtöøķŸė¸‡Â9Ÿ¤r‡1fÁY´õčˇp͙k‚š_Bĩ qĖųģÛ‡h[s+ŪO~ĮœOâZzõ;ĸŒíŅųbĩģqģ‡˙á­ÚHķ7Ķ|ę‚ Gg ķüžKŗ {nîČŋdaC6֔×ė–É‘`˜>kĨ%nš6lļ\'ۊ\Ųé‰M¸­jŖ-d`/Šĸ˛Ŧ§#{n>Îĸë¨Ŧ,aŽŅ‰Ī×uÖoš'2úķbÉuâĖ=ņĨ7 ČvāČĩ`1ŸŧüėE‰œ˛dä‰vĐę cv–SYqng>öÜ\Î"JĒĒ(uĀ›ž61�3ļÔkĀfƄĢ5õ5KJ�eÆjOY–›{Y%Nčé œT÷adôDˆÅOŋžˆˆˆČųcæSÅW3wTEËÛtļ4Ļ-ÃŪĄ{ĪũGŦ˙ģ˜Ĩsģ9…MņĘģ8fŠĢ+°ŸjUGu&Ņdl5ÅĢhŲYZļšJ—z‚´7ļ°îļ�o>š‹ĩn'—™f_!Ŗ ûđ]C,ˆ?jÆd2úAQūHŗ€8…,u›aÔĩwmU܉Ŋ}+Íž|¸ņŽz#!Ú}=`-Ŗŧ JÛĒRVûÁ^\M­×Žš'H{Ũ ԝōĀ_‡Azĸ0\›ŲQK]ÃK"}`˛ã,YMŨ†e#įÛˇę“ÔPĮcöfjšÂxûwŠēđ­YCƒ¯“HŸ)ێĶ;z;#ÔÄÚÚÍøÂ=Ä1cwzYšnÃHÅ@ŦåV<õš4<éŽv3žŽ>0ÛņŽn Ąlø|DņÕŽaS[HŸ&+īŊÔm,;ƒĄQ|kîŖŽ­“&ėŪ{Y;ļ8% qm=ÍazâĻlŪĘuÔUš1ŖåļĢXX‰ŖŨÎĘö]Ŧv@¤m k7í é#n2cw–PSˇoîû9;į’qŠ (oUÕ)ļ „‰ÛŊxĮ›—É⤤Ō:ēĘÆˆđų†Ŗômuāō.ݛ<÷F¨‰M~+%^đˇ…0 Ëđô4ĶhĻ.dÅSY…Õ?<ŒČA¸Š~hšĮj}XŊÕxNF#ÜáÃßŲE,æ1ûÅč"Đæ'‰&‡QYŦ8\^ŧî\ĖĸĨž•7  Ĩž:s!å̝ÃÄÂ;đu„‰ôôŲŠ­ĐCņ˛üSŲ:‘`ˆ )õæŽSĨcÁQRĜMmQÜËŌQ{aÆjĩA(NĖāÄ÷âûŎБˆĢšJ÷Đ1B4סu”˛:õŗŧЉWånKrȔ/@¨§‡8ŲXíNŧŪ"ėcĒŅ Í Üg€Ų†Ķ[‚×qbĨXhí0Ņž>âfVĢOÉ2’Ģ„[ĨĒËōOÛp õ-ā]]†=´™Į|ɄíÉÚ ö’jʇžÄĖņ(Ąļ6üáúČÆ^č9ī#×pŠ…pÛp˙,8<%”8U#"""gé/¯äŗWŽ;t9…ˇ›MÃūš—Î=ģŲųËũtŋ}u'ķĘp/ú[^ių-ī¤lyÍŌĪqõŌĪģĢWwū’Wvūōŧ‰ kŠWļ„ † 0šđēĶĶb ž–ö˜ƒš_ėâąu”,[FIÅ:Û𥚎‰(āōz0Å;ņ§•:áÂëÍ& Yp°E¸Įšûy˙måS^ꀸļą3Ą|=`/)Į j¤Áo`ōÔŅ˛ąŠ˛á÷Õz/„.ÄOåčę!J6–á{ĸ÷QZŨBĖģV˙nڟŦ"Û_Cé};FnÎÍ&ˆ‡6Ņ-ĄáŲVjÜŽ_IĩĪLųcítøwĶÚPŲˇŠōÚĐĐ~š(ŋ­žNûjšÛß Đū$+­ÖŨpmCÛl6ƒąƒ†FƒŌæ=tūûŋĶ^mÆˇv-ÍCëD6¯¤ĻĨgŨŗ´ûwĶūäŊXƒk(?ƒ'ME6¯¤Ļ­OC+žŨ/SWâąz˙‰aÄhĢYICØNÍŗítøwŌ\į sĶJjv€…˛'ÛŠq€Šø1/ŗÚ„j)¯iīFZ}ģéøÅc”šwP]ų0S¯pȌŨaÃÔĶAk[€pô jtŒoö€Õá˜0\0[RįūˆhjÂÍÆSZÅĒę ŧö>ü­MC•Ãí†ņ‡ \Ĩ”ēsq–U˛Ôšœ‡ĻfužQ÷ĢfœĢ)wfƒÕÅĘÕĢOÜØ§ˆėhĸ­ ‹+¨Ŧ,ÃcëÁßÚ2´_ƒP[ ;cŲxĘĢXU]Mš×Jŋ…ļ°äSVYÂĀ^RMMu2h1Â/ŅÜ"îđR^]MeŠĶ›m4ˇJSĨS”H´“Ũ1ņ0,ŗ‡ú"&Ž?:3F,fgP5ģ‰X$râxDÂD-ؘ{"'ž[c"†ģŨąmÍm„ÍNĘ*ĢYUYBĄĸĨeGĘw1`DđûŖØŊeTVVāĩ„ZZđ¯ŨAs[%”WWŗĒŧŒBs˜Ö–ŽŅ휂Å]Aš+,.ĘW¯Ļ|$P4wøˆØŊ”VVRîĩ ļáŠĀ4'¯a?8+îeõęûŠt™ ˇųF_į""""īÃåŸÍŒņŧŨKį¯͞ßü7x‡ËĖ'ę7ūęãÃeoCVŪlŦc6{eį/yuœ@åƒ´Ā”[ĸÄb€ŲBvZĻ/ĐÚÖš<ÖąXĘ?œģÍہ?fwN ‚ūЉ­ũâšnĘŊNčūoŪX'ÁX]žqĢoÎĻ-{Y.“ŋyĮ¨›œPK=8(-Mūj čÁŒģtŲčR[ åESc ĘXø%j|ā¨ Ô Ĩĩą ÃUGãęe8l6ėî2Öz0|´ĻŪ­Äė”o¨ĀãĖĮf†H¸œ%”ēsąŲl8Š*ØÔū +“õ&ĄæÍtšŊÔnŧgŽKŽ›˛ ëXŠŸÆļŽ‘fãq3žĘĒ‘Ė^R†ƒ0ĄĄSe/{’ÖögŠ[æÄ>ÔŋJ¯•ž o’Æ!ZZÃā]MŨ˛|lŦr™RÖąā­kĮ×ŧ‘g.6[.ÎeĢ)wø}CĄŽŲ‚Ų`Æb:ŸÎjšÛÛi\]”–ã(ĸ˛ŧS$Hp˛wzį‘ÅYF™7Â>ZëŠĢ”Æ–—đ‡ēN}oÄ00‘m™dGWū3ΒĄsoąáXV‚'쇠ŋkh%3qŦŽëpæÚ°Y�ŗ™dbcNq'1ƒ Ā2ūr#D ĶĀæ)ÁkÃbËÅYR‚Į‘M<f�fŪ*V•]‡ĶfÁbą`sQh 0‹)šŗ™ä.b„öe”åcŗX°ä:)ņæb„ƒ„ŌrSmësöŠŽ¯‹Åņ}éØ_xí}d:O]ą8IVG.DēF&ítEąĖqb'Bdč‘1ŗģ ĸĄ�oRˆˇÄŨbÁbÉÅ]V„­/„?<:²{’׈ÅbÃYâÁnî!<|žlE”WVQZ”›<7ļ\Ü.;Ļžû==3ĻĄËÉ4ęēŠcvx“ß v§‡%NOôDÃqė÷HeÅéÄF”ž)”¯‹ˆˆČÅ)ķ/-ü_c^ûķī÷ąų˙ų)Û^åįM?åģũ”Ÿî1(üģŋåũ͕”ŦXÄÜŋ2˙‚ŋg ÍØĀåƒ´Ā”F4\z'gŅrÃUŦëûē‹úÎį(ī^)&”¸'ØgōWt.<h…ˆâÄ6Tqbušq:Í8ââ × F ƒ0Ų”xķĮoŌrmYJ(w× 4ĶŊŽr@ˆVĻÂ*Šs‡ßZČÆzŌ#YĖØíؐ†[¤3cĐ^=›ö1}É.,ŖáąĒĄ!8!a°W;GDfwÎx-CīČuúÕŊ°¤SÍZJī Sî]–|Í9ÔnŒp¸œœ¤6ėĀ™ ūÎ0›|ÍäĀ•ē’Ų„…”ŖeHK-uí!"҆Oˆ1ŸjXLĒ‘(8ĘG:r9ĄũD fsūÚZÚ:ÃDb†O^ôöSíÅ]›Y[ë§3%fÄ1 ˆcĮ¸Põœ’9Vš ĸ]a"á.ی„ņˇuâ÷;(.-cüŅgÆzzˆŨTŸ`ÁfĪĻ/ŌCŒáĄ2–1ëœĨh”ÌÚÚ_î’ëFū2›c„|>یF‰ÅÁ0Œä,˛–‰NXr‹gô$ŧfģ›ąƒHãVĶM-ÚëkĮ|˜°:K)_–›–=˜­vl„ˆDÁn‹‰€ÕëÄŪ 1p[ĖôDēĀ^‚ƒP¤ŦîŅ×ĖvædĮ vEÁ1Ü/ۘkĆ=ü=1Ā˜Ą/€ĪϧĪĀĀú Ļ㤘°ŽÚš™lņ”kÅ<ļÉ<pJ~üEDDä"÷6¯ļķī|gôËī\FÎŌ Ö•ŒY}ĖjÃRÕrĐS>lą&‡›ô U¸ŒúīW3ö’2ŠSnF{‚mOõ‹ž1tƒė¨`Sgü[8ŗģ —Ë -: lF'ÁˆWlā´öá÷w>!_€¸ŠĪ„CÎĻ-3Ūr/&-m]”Wå !2ã\]2ō䊸ĐēãU�%ą>ßa‹ĪÚgŠq<ž„lkn˛‚`˜aÃ› Kq4œÜ‚Ģ/夛,d§,ŗ•<Gģi3›š[h¨id]܌Ũ[Mm]nKŒž>0YĖcÎą…ėėä$§)1Į)ۃ˙žkŠöåRŪ°‘:§l3„7]KEÛ$ƒaĐį¤JŗÉŒi¤!ęJoŖÅ\B]Ũŗ¸ĖDi]Y|b<ä8bmwQ\Â]ŗ‘M%NŦf P‹§zę "͌-׉-׉0ĸÚZ}ø|!ã=B؜Mļ9N_4 ŽĶ§#FĖ�Ŗ“Öړ’X°$+3’,ÃÕ#éb`LđLŠâkj&D!^ov›31B-ø'l2ôø6Qį;yąŨHV˜3–l÷Å0˜čQĖąX˛G'ĮŽ§Ü˜á†Íf,ËY÷z‹{v‘,"}6 mÉ!CūHœf°{ra(Ą§†Ú“?ČĻÔāˢk$yÍĉ'?ŊĄÛĸØŊ%;lÉ!Q‘ljIGiÉÔ¨H‘§?Æø33ĒēåĪ'Aųã~~ēž‹ĪŪķMž8\˛<đ?§|üķ=d6ÅÖüää˛í´ J–úwÅRķ ß}>‚ícÛHaI>Ž“ÂĸĸĶ>bĶéq“Ũ܁/žXaS!ån�'.—™–P'1ĀßŲ‡ÉíÁsŽÚ**Ŗ$ģÖV‘Ē*ĸ-;č3y(?éŅKņq~Ԍâ)/įRļՉãTŗČš“ŋÔΊx–ĮJĮŽę3“m;õ¯ÃöeU4,Ģb„w´ĐPWOe˙“EŖB•Ôš<RC˜Ķ˙úÛA›¯9Õ ŦMũūLĘFĖfĖ&ˆŲƈĮNTk…Zh‹dSŌē‘’‘*›(ąSæc1ü-~âŽę̊RrČ)ü›ļa`˜Į``ļšņĖ đä›QzgX‰9YM°3&Zd÷sk„„LÜšĖ3˜mWzĮĸbNy$qš™Í˜1蛰H%D¸'gåuŖ*xÆûĖžhĶ„Ų VW9ĨãL<ūp§3eÃnĪÆßŲIÄČŪ#L¸Ŧ.ûûĒŲČļŲ8Šč.­lØíf‘. s˜hļb3XėVđEˆÆĖô6æ •˛˜Í€ŨËĘĮ8áŪØrIųI5&ŗ 3ĄP˜¸Ŋ„węħŦ1ŠĩDDDD.œˇ÷ŸŖ€™#¯\Náįxk/ŋũŸ1ĄËô|>uʼn?ûü'Õ<åįlOY V ü õL~JŌ X먺IΑ2Ūōą7ŅE^\Ļ>:ũ‡úÄs‹žßqš !ÔA°+9ĮŠÃ[tęß!ĪĒ-7åevˆ´Đę ÕׇÉ[†7e%ģÃô9iâQƒHä|WĩL–§ÃL$õˆa{Ž “ŲvЉ3 Â;RæĢ°āXVÅÚrņp+„üGm&ØŽQã†NÁHÖYF…>Éã?´xėØ-Ž6 v¤\1chxˆp;#œâŽ,ySo˛šSn~cøÚũSķ&. šžžæqgî4čé3NĒ^:Á‚ĶíĀÔ ŊcœÉhb!ÚÚ|;cÉ9V+ŲF ƒäŧ(#˙Ė$ƒ˜sÅbĮjîŖgÔdQMM´…bC•uc*_ĸÉ㤋iäoV̉XŸ1úŊXĖ$įŽIO×ín7VÂø|ãMēköuđ&\Sø)7ö\;D"„ÂQĖöĄPČæĀÚ!ŽÍļ=iȌÕn…žX2€OųgÆ<ú{ĮˆuÉEéé‹5Ų€ÉbJųž6‡ēR>ƒÉ%qcô§2Ö5'UIõĮßđĢßŧ=ęĨÎōōĩī|‡MWS’ōxįŋ]¸ˆ™#%0Ŋv˙vĸQD*S>lÁŊšĩÅŲiĸō†‡O<b”Ą–ûhđ%oØ&ž˙pSė͆¸ŸÆÍ]ŖÔ,ũ$…+_J™ŦĶ×i&j¤-8z2G‹Ë‰-ĀרA^×é~ī=ģļĨeĖ!B{m=žžlŧeŖŸlw:ÉÆ Đ:z"]ēšhLÕjĨ•^ĖžZV5ˆÄbDģ´Üw-ŪâûđOØíÁM5”¯ŦÅę"‹ ŊÄĻÖ&§āŦŧ—áŖîž—EcÄē´ÔÔâ7{Š,™ä ŖŲ+BM› DcÄē:¨_š<N0ēxŗk2Į5ŸŌ;q_-kw„ˆDģ´ÜĮĻ ‰‘)rnœĻ0­;ˆÄbDMŦZĶCĄËL<"ųđ‘ä$Ņņ7ũøÃ]D sŲÄ}-4‡bÄĸ‡đÕŪCŗÉƒä įy?ëņ(‘ŽŽ“ūE ĀâÄã˛ķ5Ņŧ#@¸+J4%ŌÂßŌ„/lfN‘sâ§ 9K(sZˆų›ØÔ˛ƒ@ø‘ŽC„:^ĸąąHļ‹’áĮįēqYcøÛvŽÆ0ŒŅp͛6OöŒė3†‰DcÄÎô�šķq;˛‰øÛđ‡ŖDŖ]„v´áīĢŨļ\læ:‡ˆFōšôŰÚMÄc= į¤f 'ŌE4– Œœn氏–@WrģXĄļ&›_"’Ž“lqSęu@¨•ÆĻ—„éę" ĐļųQZÃPXRÂxOŪž2ėšØ0Áˆ5wč3nļaĪŽ v%įšZÕætc7B´ˇ…ˆÆ ŒX”H …ÆÆÍøÆüLØâ: īđÆJĄĶX°Yŗ‰‡; D“m„w´4įb%F¤'ūYíč „ÃFWūŖF %ŸŒÖC¤ë}\w""""į„ÁŋĩŋʁˇĮ[öd=ú˛/¤äī2G–üá×íėüīķŌÁ)oŠ#°āŨø 깍ĩíTzšąģ<Ú-X0ˆõDBôÄÁd/ĄūąŖ*>Ær¯Ū@ąí Ŕ†Ģ)õÚ1÷iolÁßcĨ¸.õi>\^;ņzž¸Oę¤,šEf7âk펖áĘ=ũû8ĢļrË(wmb]0 ֊Ą!HŠoŦ’ęÂ6ęük)[ÕIšĮ‘�m-!,n;ø§f!—yŲFZŠĨŽáŠëúˆ›˛ąģ¯ŖĄuž ĪŖō'ÃX[OŨm-ôÄ LŲvœžu4oX–ŧ‡ą•ŅøŦÁÚÚzʗÖ'ģ{›Z7pŌčĢ åRŲ°ŽĐĒz*=`uQ˛v# îF°†ēâģ0í|î´Ã*̟¤>ZCCÍ ´’ÍīŊÔ­…Ęšá'ДQˇ!ĀĒÚŧ­]če톍xŖkč\ÕHy)´ļߏ§ŧ {M•7ŧDé“˙/uë~Ėʞû¨/ŊŠz“wÅ:6m´ãë PWŊ”šMođØ˛sXÉ1VŦ“öæ“kÆæ”­ĻĖaÆžŦ‚r[ūP€ļNqLæl,v;žōe¸sOÕW3ö’**ø!ümA↠“ņÃUJiQ~Ęy°áލ�Ÿ_ķædÅ†ÃSFé)g“5ãpģļinã*Ŋ÷ŒŸ”c/Š t‡ŋ¯  ˛­šxJK†&ąuRėíĸÕ߯cAČļ;đx¯Ãaė §ÕOs”WátŲ û[iŽ8(Š,ÃḎō’ø:Úhôõ7gcĩ;()_6z‚×ŗdq–Qi á÷‡úڒ…~ 6ģ›Ō27ŽsY”f;vkoFėxFNœģŨĖΠAaę|?'Ĩåā÷hnl#މėė\œ%'ųm`˛Øņz,„Úšhë1ĀbÃYRrâéeE%xúÚđ7Öã7[ąģŧ”.ŗŽ5ákŲL[ŲŊ”šKXiÃßø0~ĖXnŠ‹ėDÚNTZnæw°ŗy3ž *'Y|'"""rNũq?O?3¯Üžˆ™—§.¸œ™ŸČåōß_Ɗ[ŧ#U-øÍV~˛­‹qķ™ĄK‰D ĢĢ ›-Ŋ%â—\ú‘´ļ ´đds ū@˜hŸA3&̧ŗoq9ĨËrGWĩtmĻØ[O¤øY:7Ϥą-õŌė é3Ā”ŧ/¯^MŲØŸnÃã)n¤ĩÁį(Ylā[yÕ~ƒėâg Ļļ?Ņ~ßO[)ŒļģpÕøąUļã[=όÍkjiô…é‰CļŨEIÍVÅ×āŽ RÜøī4ŧ™ˆˆˆˆˆˆ|0ÕŦ‚ĩˇŌ�� �IDAT­=Ģí/›~%_,ģš…öĖņWøs/;ÛyūW] œÕžÎLCŨēŗÚ>ņŪģięIR4%77wäī‹*lų° ŦYHE›ÚĪQ6u§N‘)âlÖ¤ËøĢįSčœMî_ūY—ÃÛ1ކrđ?~K÷ =Õ֋`‘�ŪL][ŲŪ LvĘ‘ŗ÷˙įwŋáWŋûͅîČECa˔fŪąƒ`§Ÿ–Ļ6ŪÄCũęĶ<õHDDDDDDD.(…-SZ˙c54„Íd–PŋaŖĒZDDDDDDDĻ8…-SZ.•í˙Iå…ˆˆˆˆˆˆLÚĨē""""""""$ [DDDDDDDDŌHa‹ˆˆˆˆˆˆˆH)l‘‹Æ%—\rĄģpZ [DDDDDDD> 2?ö1H$.t7Ō'‘ā/>úŅ Ũ‹ĶRØ"""""""ōU~k\• “vÉ%É÷4Å)lų€Ęûø žZu'™ûØE1üf"—\r ™û_­ē“ŧĪ¸ĐŨ9­K‰d=QWW6›-Ŋ_ú‘´ļ'"""""""rļīŊ›ÖöĸŅ(ššš#̞EDDDDDDD$ˆˆˆˆˆˆ¤‘‘4RØ""""""""’F [DDDDDDDDŌHa‹ˆˆˆˆˆˆˆH)lI#…-""""""""itŲšl<ņŪģį˛y‘)G•-""""""""i¤°EDDDDDDD$ˆˆˆˆˆˆ¤‘‘4RØ""""""""’F [DDDDDDDDŌHa‹ˆˆˆˆˆˆˆH)lI#…-""""""""i¤°EDDDDDDD$ˆˆˆˆˆˆ¤ŅeŠD"‘ Õ‘‹’Édõ÷¨°Åápœ×Έˆˆˆˆˆˆˆ\ėēēēFũ­aD""""""""i¤°EDDDDDDD$ˆˆˆˆˆˆ¤‘‘4RØ""""""""’F [DDDDDDDDŌHa‹ˆˆˆˆˆˆˆH)lI#…-""""""""i¤°EDDDDDDD$ˆˆˆˆˆˆ¤‘‘4RØ""""""""’F [DDDDDDDDŌHa‹ˆˆˆˆˆˆˆH)lI#…-""""""""i¤°EDDDDDDD$ˆˆˆˆˆˆ¤‘‘4RØ""""""""’F [DDDDDDDDŌHa‹ˆˆˆˆˆˆˆH)lIŖËÎųâ¯pĮ§kđNŧJFF&Y3f2ßu=wV}3Lįŧ[éßZÅŧöĀĸ ė˙§ëÉē`=‘‹ŅyŦlÉ`Z^>ųc˙ÍdzVœãG:ŲųüƒÜōų›Šõ÷Ÿŋn#ņ÷‘?g9ŧĐ=I/]Û""""""§vî+[FLŖø‘­Ŧ+iüđn~´ž†Įƒ‡xęâūåFŧ Ŧ$kÅfŽ,ƒéėĒkC rá*tDÎ]Û""""""§6eæl1ÍZÄũ?ų>K3×øŲŽ XŨr–A ôrāā[iéŠČÔĸk[DDDDDätĻLØ@ÖU,-�äđÁ÷†øÅ†UŦøŧį•sÉģrŽĪßÎŊ ¯p8>A›ũ!žûÖí,ųĖ<ōæĖ%˙3+¸cCrũ Ëɛ3—% 'ÆCôo­"oÎ\ōîÚÆ¨¸§˙ í ÷qķđžįĖ%˙ĶKX~×wųg7ņQÛqā(_—\wQÃč1ũ_áá¯ßÎ’Ī¸ÉŸ3—ŧ+=,*]Eíփœ3…¨ũÜ\ōæÜÄ?ëgīU,ųô<ōŽŧ_ôNæĀös`븡t9ŽO‡‘ũ…ÆŲß)Z:>Îoíēéxâ>V|.ųō¯ô°äļoķÜū‰Zėeī–īōåŌ%¸ŽœGۜy8?ŗœ›ŋÕHĮą”x¸‘åsæ’÷™ī˛÷¤6ŽđĶĪ˙¯žrrßûˇqĮ•sÉģrž‰Ž‰Tņ#øž˜āŧ&>¸ũˇQûõ›†Îß<ōĮ{gą~īūŸSû՛X4tŨæ]éfQI̟ŪÍɛYũéšäM8´'ČÚΜŧŧãënōæĖãŽíqč ņĪŠŸ“O/aÅ×Ų›r&{m‹ˆˆˆˆˆ|؝ĮaD“ -N*.9üsî(˙qČČ)¤hÉBĻ›ú9°{/o~ߎ=<Ņü}ŠĻ§lĶŋ›ĩ×}™Ÿu™ųxŽÉg:ŨėÛZÊũŨ|-o š¯ĖĶT˛ôY[VÅĪŽ ’‘SȂ% ™nŠĶ{$ÄžŨ-Ôí~•naËÍy˜ ŽæîÛ3éØō*3™wũ˜Ÿe"Į5m¤šŪ÷ąâ¯Ō=˜Á´|ŪEW`8Ę^˙ë<õĀë´īj`ëOŽaÆČCĮcîíōøÆ &§ˇi&Y§-Âé§ã[+¸sÛ[1÷BL3ÁņŖė $÷į <Áö.šÔdĀ&S@|ā-Úŋž†¯íĘ`ž{!Ū‚īr0ø"ën;BüĨįųŌŦ” ã!~z[õĄȜ‰Ûs ŪĖ8ĮžÛ#ļŋĘŨĪ>Íũķ˛`ÖBä<ÂÁîNö†Šíôž˙HōîßÃŽaAęnöīaß d,Z˂͛xˆÚŌÛyęĐāPŸŽbFôw‡čØŨBŨî× <˛…'–įŒÚėđÖUÜüĀë'ƒ§‹ĸyĐ{$D`Û#ļŋÆŨĪ>ÃũķLīsũ8‡ˇTsķú=Cë/ä‹KĻA˙[ėÛŊ‡žŋ‡ö­å<ũė?°ā,‡Ų™L&`€øą×XģéÛüĸ?ķ37ŪÍž@'û_y„[į…—ūų&&um‹ˆˆˆˆˆrôčŅÄ9alOÜū‰‚ÄĖŲ‹ß;pšuŋuh]WĸÚg¤,8øIą31sļ3ąø~â÷ŖÚ?–hũĘĸÄĖŲ‰Â¯ėJôĨ,úˇēʼn™ŗ 3—­Oü:uÁī‰5˜ Į' 3g$ŽyüđČĸž*“Û|iëH[ŋ˙YE˛ũ/=Ÿø¯1]îÛˇ1qÍ' 3?õÍįFēHŦų߉™ŗ¯Iüpė{ū¯į7}ĸ 1söĸDÕ ĮŠī2ņû]‰šĪ$fÎv&núŲą”‡?\–<.W}öúDMĮī“v`cbņė‚ÄĖO\ŸøÉQ{Kŋ}&qũ'&čįD|÷$ŗ 3?åJ\uÃ÷GWã@â‡ÅÎÄĖŲ‰…uûS$~Ŋ.y.ÅGo“0˙ņTEĸpvAbæg×'~=ÔÅ_ߟ<§7Ŋ0úŊöŊ|OÂ1ە¸éK׏Ûīás~ũSĮ§ķ_OŨ˜|/c¯D"ņ_/T&ûôŋOô)‘H$ÃĮė×'žˇ7uŖžÄŋÕ_?ÔŪÆÄŧīõ7'¯§Ų‹5ž1įšoâ{ÃĮwŨū”k'¨ųÔŠÎãø×ã¯× }n>åJ,ž×čĪÕČuęJTw§mKDDDDDäÃllĻ2e†ŏŊFíWJ>":ī Üé9Q÷7ķøĄA˜öꞡˆÔâL9Üđā=¸3`ĀßDûȰ‡/īz Č`iõ7GWLwąîĄ™~ŠĮQ§:v$9¤izAaJĩIRÖŧ{xâŲ§yšåĶWR�ûži$0™K ~EÎčiF§/fŨˇ¯&“AĪüœÃ)‹23�č/XÉ:΍#pjĶŽfŨxø‘ ÜZ0ēƒĻY_ā–y�GŲwpRã‘N˜Æ ßS]a*ā–ų�t<rbˆOī+<žõ-`&_úá˜s‰šwŦįî| ûUžzÕü%W‘ đwrbÔLœūN3ōšöĻBr8ĘŪũŠũ>ÂŪ@r?KŽFOÖĸ{xô‘ üčÁ{NĒ™ąüFŠ2ãė;vâõ}Ī7ąĻ-˙÷ģS7Ęb~õ7(Îģ‚ŧŒ#ė;ü>×Ļ™ƒƒšä›Ŧ[:æ<g9šŋf1™@÷öŸŗw2ä&aĀ´˜ēī-ũššq5ˇ¸3€ņ‡ô‰ˆˆˆˆˆČ„Îã0ĸã´c{3N^čæX÷�ƒ�ĶRûão2?%8°+Č�šhņ¨×GL_ČŌ„BöĮšuŠ zq ‰Įuōx Ķŧ/Pœ×ĖãGNßķ9dđGļ>ÂO­įKîԐÄČyŽĶ7œ2˜ŋ|á¸Ãv˛Ü‹™Ÿņ*ū#!öõÂŦ1÷Ûķ]5Šá>#ĻP´4åPņ~z{†Œ8qS0H˙$“§a9 Y:ΓĨĻįL#ŒĐdqbhĪDÛ@Ež+¨?ôûv…ĨNLî…Éã r€ÅĖāūāq˜QÎwūŒ:vŋA˙Í×$Io(9Ä(gáčĄGȚĩoĘzņū^úû“Į&0ĐĪņá5NœŋšįÉĪã1-ĸū_vĨŧđ>Ö߀šKÆŋ>Lķ†Ž#P4ÁĶŊÎDĻëęq>WYLĪÉŽĶ<MŠŽˆˆˆˆˆČ‡Äy [9~äĮ'ZœsˇÜ^Á+3kÔ]fœcŨÉ­âĄfVũÅq7ī=žÜĮáîã@īĻ cĶĮ-ÉŖ /Žœ>d˜ž|=ëļßÁēŨ{¨/_ʏĻåŗĀužĨ‹)ZäĶßS9N˛HfÃ[äŪã¯“,¤čæ`7Œ.7Č`zÎTĩ ‰~mj¤}w'Ũ§_RĻå0^OFîŲSîĪ{ģģ�čƒĮŋ~߸ Žw'ĪÃņî#ôã$+˅§�üĄ7Ø{æĪŽŊÁŪn˜v““YY3ņ˃)ķļÄ÷īáĀ Ls-fî$߯1#?Úü"žũG8íĨ0|ū˛˜1m2'ũũŽ?9ŦŸ5YĀņãôöi[˛ĻgæAÎgĉˆˆˆˆˆ|ȝĮ°å î|iëÆÜöī\Œ{^įx&îåcƒ€8ņxōfođČ^>M%Jüx?ņĄÉvMĻ n$Mdeš˜Ô¤)[˙éeæo’ĮˇŧBGđūWáĨ™:2)¸æęÖT0˙´9HœøPŅ|•SΤ˙¤‚‚‰ŪË)öx°‰›o{ˆũ™5w.wQ0#“Ŧ '°š†§BgØč™öačü1pˆ¯:õĘÕ%ä°Ā=BG„úųĘŦ,úƒ{8@E‹œ€‰ųķfB0„˙ ,(€ģß`€L,§ŠdĮļVąü= IŪ’2î^ädzVY�GøŲāN ŸŋŒI>ü\ŦoZ§?‡3ž"Ækrœr3yß.øĶˆ˛–ŽįūEop˙î׊]˙ ~2fîL˜˛’C]ōĒ^`WÍ$ĘY⠁ˆ8“áYĖ]ūM]ūMˆws ¤c×Ģŧ°}_yˆ›gkë7™{Ę{_Ļ,āø4niöSį>ƒŨŋ/Ŋ´7ü#û`ڒļ˙äš1Į6Û2xęW.de ŨĖģÖxöÆq+bÆ3wÉUäl>Ę>ˆø ûü!qâzrĪÜENĻm~19oKÁ@rNÆB–ē'@Äwķđ†= ûÛ[ØrGۘ2ņ¯F…-&˛˛€ããaã9ķõM&`p`âËv$1 =Mhr4HDDDDDäü™äNį†oßÃŧ 8žëAjwöYnbFNōҞŊĮŽOūĻ1sZr΋ÁãôŽm€#ėŸÄĸq™r˜ëšž¯|o3ģūĨk§ÁāĄŸķTātŊËĄ`@?ĮēĮíTš%°˜FŅícƒ€î÷ Î@VN™�ŨG8vē•S,f~& „öp ~h�ō˛`ø,dnF˛ĸĨŋ÷ ü‡’ÛLę‘ČGŪāĀ�ą[n´�ŊG9|Ԙˇōr�8vd‚ …ãqâÃUUgŧū´ëwO°~7Įú“ëÎHx¨ÚeÜ+°ĸĪ€ˆˆˆˆˆˆœ S lfUPWOĮyyũCtŒš1œģČE&0|u‚'°Ä9āŊ‡S6œ‘ĪŦi�‡đī?yŖøÁņMbr\čį€ĪmŲ͸ˇŋ͞´ ˆĶ{ÚJ™¸¯�Ųģ}ãß˙aīÎ ‡{ĶQ‹0ÜFYãAÄM´ƒ‰K)ÎZrRW {/Ūũ¯áÛß=ú˜˜œ,ug@wˆ}`rž—“‘9mŗœx `p}ûƒ� –\uŌŖÆwb˜ŲÉŲLœĪ48éõæĪģ€}ģöŒs=„Xûųų|r)ĩ÷ŗ~ŪĐõûļŋ1îõŅx-9Ųđ´ĢđŒĶĐų wœëĻ?đJr9/ĻFØĖŊc=ˇæĮ_dí†ŨŖn4MžōËžķژęˆ8Įļ›/¯Ŧæ–ō‡čš×tríĸiĀ�žMMHŊíōđˇ^¤7s2=;ÎËß_ÃēõkXŊåČɕĮ^ã…ũƒ@sķ†oۇįÕ8ÎącŖˇ˜{{%î Üũ̎Žm¯—ŽīTsĮ=w°âWÆwÎHy�oŅąëā¨}õlâËŧÁtgō ôvO8uņŲËZĖŨ+Ž�Žōܡ~ĀŪ1o,~°‰Õ_­æî˛ÛųŅūQ˛`‘8ÄÎg^ã0Ė_äLY>fƒ§ž1Ā,pSĨ2ž'ŗ2€/īOŊÚâŪúmîŨžÉŧ€ãK 0æßTÁŧ Üũkˇv§Ķ~<ņÚ쁜Ģųĸûl×˙GjwŽ9PŊģŠŨø:dPp{ FĖd^^ō1ÍĪŋ6ęē‰ŪÆęīŋi›–eâk[DDDDDD’.øœ-#LNžöí/ā[ų"ŨÛäáÛŠ™{Ŗ€¯ũx=ĘÄŋ­š%ģ YāĘcēŠŸcĄNGŽCÆLnyčŠF*8LUĪŽ5øC°âķ¯QäĘgzü-öîŪCīŧõܟ÷Ŧ{åtįÉãîËņŨՌũĩ¸ž)d~~ĶMĐß}”}ûq|0ƒŧ›Ös÷Čt2C7ŋŨŧüĀ zŸĪœyâ{‹Éšq#õ? rķˇ^eį+Xô´‹Ķ0ŏs äāņAČų{ęŧ~Ōs›L,‡â;ūž}ãuŽlžƒåŗ ú„čgÖ}OķčôGXÚÃņ­r šöæā†4<áf4 ÖldõÁ*ęCÍÜōš×˜įq2+úģą7x”2™Wĩ‘ûįŪrēk!ŧA`w'pÕČ|-Ãæ.r’ųĖĢøwĶž0ÁŖĨĮ‘ĩģW\˙ųˇxáŽ[˛YYÛ˙{drë?=gëĩÜšm€ŽīWą:x ˇTßČüYÔ¯ŲÃÍë÷°ķå,Úėdnމū#!öw@F>w?rĪĐŖĒ÷ŗū÷‚ÜüĀëŧpĪĩės-d~^&?ĘŪĀtĀ´E˙ĀŖŖæ˜ÉĸčökČŲũ"ŨģjXōš&æLÃÔ”}ûģ™~Įznõ¯áņCéGNqm§Ąu‘‚)Sؐåy€uKĻoņŗõ˙Čž”{CĶŦyú_^āáĒĢ™›õûvŊČ Ûöp ?÷õßāņ—ļRįsģ7ãzžhŲĝKōÉęídįļiõ3ˇę ļ˙äFfLr~Ņ,÷?°ũĨMŦž~!3âGŲģëU^Øö*û™>ī ŦūņVļĪ•rŗ™Eņƒßį‹Îid %8DęčŽË7&ÛģÆIÖņžm/ōÂŽŊYNŽ­jāå—ŖxrcaNkúōl}¤Ož‰cģ_äg[_c_|&ˇūx [ž\Œåß`Ũ’™deīö=<Ws{˜œ|Ĩõe~ö`K ā˜˙ĩĄcgÆĸ2Ö>š•-5ã<EhÖB ĪM’Ÿ2_Ëpŗķ&‡(™ŽÅ˟ôœąY,Xķ Wũ=Yė{Ĩ…_lŅ;ũjęZˇ°Î=ĸęõ|1?ēCøv…F*FfŨŧ™í-ëšeQĻŪūŨ{8ŸÆŧkžÁã/máūyŖ¯Ã3^ÅcėjŲĀKf‘=´?ßB{ā(ĻŧĢšûĄØõO72kĖûĖōŦgˏW˛4˙ číÄŋkûúsđ~o [j’3´ūŲ;õĩ-""""""pI"‘H�tuu‘››{ģs>Åņ}ÕÍŨģq?ØÁ–›ĪžŽDDDDDDDD>|Æf*SgŅ9Đ8ČŪCŨÄsS<Ļz€ø!ü“Oꙕ§ EDDDDDDDŌãļôî~ˆģŋrŪ Ģåûd*q<ũ~Ņ äü=_œwŠFDDDDDDDDÎĀ:l™uķzîŪz;z‘;?džÛɌĖ8Ŋ‡Ū pd 9Šîƒß<ƒy>DDDDDDDDNíƒ?gKüž§ĖSÛŪāĀąã BÆ´™Ėu-æÎ/¯¤¸@ĪP‘÷olĻōÁ[DDDDDDDDÎĄą™Ę”zôŗˆˆˆˆˆˆˆČÅNa‹ˆˆˆˆˆˆˆH)lI#…-""""""""i¤°EDDDDDDD$ˆˆˆˆˆˆ¤‘‘4RØ""""""""’F [DDDDDDDDŌHa‹ˆˆˆˆˆˆˆH)lI#…-""""""""i¤°EDDDDDDD$ˆˆˆˆˆˆ¤Ņ9[zyŽt.yˇmŖŧŇY>g_Ūyn{!"""""""rž¨˛EDDDDDDD$ˆˆˆˆˆˆ¤ŅÔ [âGh˙Îí,ųĖ<ōæĖ%˙3Ëšų;¯p8~b•ūƒÛX{ÛrœWÎ%īJKîúG:ŽXŪģåvō?ķ]Úw~—埞Įĸ ĄäpĨ+—ķp`7?Ŋk9ŽOĪ#˙ĶKXņ­W8œ˛ûūũ?guéPÛsæá*ŠâaīImûÜņyųWÎÃųųU<w°ŸÃÛŋËŠĪšÉŋŌÍĸÛ~@GoJÃôŌņÄ*–ÆMūœy8?w̎h•ˆˆˆˆˆˆˆ\ÔĻTØr`S5_Ûeâ–Gļâ}'[ēĶŽîh%W8ļ{o[ƒĪô}i'ū—6đÅø‹|yåØ7ȘL@˙~;˛…'nĪO.ėæ^$Ģf ÁŨOđŸĶŋíÛÔnŠ<ú_cí]Ō1ũFžhŨ‰˙Õ-Ôšûųį¯VķÜąáļMĐ˙:?ڒÅũ-~ũë3ܚņ:ëîē™ĩÅ<ņË�‡~ų}æifíæĄ>g߆;¸sĶ[Ė_ŗ™]¯oįéę<öm¸ƒ{ˇvŸ¯C+"""""""įÉ [â>Ø Wsƒ;3r˜ëŠāŅ–-<qûL�<˙$~ŽĻî‘JŠfå0cÖ"žōČ=Ė?ösß5\'bbppšˇ¯įwsg˜†^dƊorkA�Yķž€7oû&g]Åũ-/°õĄ ä0cVŪę pˆĄ5(ƒƒ™}ųÆ˙ŸŊ;Ž˛ž÷=ūļwī \ C$LĄ2Ŗņ=R EĀ™*ļZĄZmuj­Z§VĢļÎÁ NÉŊÆ,Đ%¨”€ČP †11HÂ!ÃŪ§Ü? Úō~­ĩ—îũ<û÷û>Ī~XkīO~ŋßCv<Ũ—üA)PŲ‘‘ˇæ‘ œËŋįDąyõĘ�*˙ÂcĶ75á×Ü{a_RS;Ō¯āįÜ[Į‚gg°ĒéOŦ$I’$I:šQØMŋ ûˇđÆŪô¯-§´âSû’”Õ#Yš Žoôļ䜝ZĮ˛%kŊؕA9҇´GFzĮFĪãIŠ‚ÚÚÚũĪãĢVđô¤1\øíķøŗé;ôn×Õ5ÚˆęHVzŖVĸŖ!>…Œũ5E ĩĩÔŦ—Uu) ÔčMDĶ//“¨õËYvĐt#I’$I’tĒ 4yŅ�uÔqcĩûvR žeVÜK<=å ģųyîŦ‹#ũŧkš÷îņ NŽŖ˛Xs;9gŪ~XKQéUúˆŠĢŸNth!‡ŊÖHÉK\}ų#”÷3~4—Œ¤(¨ũ/~ōowR{ÔÁī‹ƒC;;čYeĩlaō%ŲL>ŦĶŽõĮ”|”ē$I’$IŌ)Ĩ‰Ã–xâãŖ`ũfĘ8<S¨Ũŧ™2’HŪŋ!šŒüņ<œ?jËXĩā úÕŖ\=)‰…/ä œÄŪ{XnߑhøœPįØJŪzƒâčsyúÁŅ Ū×xŲ—ooŋø8ĸIáüß?ËŌŲMręŋځ$I’$IjNšxQ4ƒĪëKÔúyŧ^|hlą™?>ģˆĒŽšägT˛jÁ_L̉N&;<wŽÉ¤nũrJI&;§+l^Omj:û‰Ž#9ųhÃVŽ­ļŧĸãhÜLéŸgŗ ūĩÄ%} ũĸĘ)­ŠkTs:ÉŅDĮw$ūØ-H’$I’¤SH“¯Ų’|á$~ÔˇœÉ׌å'ĪĖãÍ ysÖKÜqųXî|7Ž‘ˇũ°a$I‹Ÿ¸…Ģo¸ŸšÅë)-ÛLIņlžžŊž¸Ŧ<2€ė1×2¨ō ~r×l–•”QVēšŋ>3 ŋ=–‡ sž˜Ô}‰+_ÄäYĢ)+Û˞Yˇ1ņŨŽ N‚Ōâ5”}ŲæãĪe\AË~}+­Ļ´ŦŒ’âúÛW_xķl\˛E’$I’¤¯–°fK×ŋ<‹Œ'áéWīfîæ*ˆK"#į߸g揚,gߨŽŽ|īÉ'¨ũųŖ<tÍ 6WÕ•Ô•~y?ãÅÛĪ­’:‚g^†‡î{žĢŋs;Uđ”>€ī>:…ļ îŸ?‰‡ĮLāžģF2ˆ$˛Îž–{ Īnāęg'paôŗŧ•ķĨN�ƒ9…gâīįĄŸ_ÍĘĢ Ž+ũÎû/Ū>ÂåZ$I’$IúŠ9cīŪŊ{6°|Í�� �IDATnÜHZZÚI.G’$I’$éÔrhĻԌnũ,I’$I’tę3l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ @SwP SēuÕ5ĩԅÂMŨ$IjFĸ‚bcĸIíЖ¨`“í$Ijšô[O](˚>&ĄM+ūwl,g|íŒĻėN’$53{˙š—šē:Ö|ô1™Ũē¸H’¤ĶB“N#*Ũēƒ„6­ˆ  Z$I: ņĩ3ˆ‰ĻU|KJˇî8ŲåH’$Mļėūīj˙‚%I’ˆ‰ŠĸēĻöd—!I’tB4iØō?˙ügS6/I’Ng|í ×n“$I§ īF$I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A†-û…øûīnäę‡ßaĪÉ.E’$I’$˛'쀿#Hįü˸6ԕāÉ.E’$I’$˛ [Ię1„Ü“]„$I’$I:Ĩ5˰e×Ú˙Ë´×ōÁĻOŲC,I{2tä(.ėŅjß|0o¯ŊõļV†&vâëÃ.åŌs:Ņ€õLųņ#l6‘ÜOfķÆ˛OØjEįsF1qX MÉ›kˇą'؞¯ŋŠkĪéD˙ŨÍ<^}Oūd-€=% ˜6ã/ü}Ķvö„´ęt&š#¯bT¯}uėĄäíטVXĖĻOĢ âéÜc_1‚¯'Œ3'I’$I’Nļæļėy)ŋûOĘ^ˤë:Ņ"´‹’ˇĻņüŖShõāDr[…(yõ~ķvĄß›ĀÄôVėZûŸ<?õž ŪÁ-ßL  ŗé­˙dĶ?āūīĩbĪģOņŗg§pĪÚ39܏yŦs­ķáŽW_cIŋ“ÛęĐ:Š™ōÛiŦé1ЉˇåÜÃĻŋNãŠß=MŌŊ˙ÁĐ$­}Į_ū™ã&pmV÷|ÂüSxę÷ąüōįßĻÉ?{’$I’$é$k~aKy)[Ģ[Ņû9tNH$éŠ tøF%­Z�{Šyãíítū ŽØ€¤¤Kšjũ?x p›ž9‚Î M…:åqq¯VVŊrČ`9[{üÎī\?ūĨCŋž$Íú/JļqxØŌâLFŨv;´ęDR‹†:†˙™oOáīë÷04Šģ6mcWlWrĻĶ!Xŋ΍ļįëåAØ"I’$IŌéŠų…-zŌ;ņ˙1˙ŲG—Į×{œIfįVtÎhHC6ũƒMáDõhßčMA2zĨüëJvAį†]“:´o˜Vc  ŠSbŖˇiAˆPčH…´ Åž÷yíõi”lĢdמĄPˆP8LFÃ’zôĨsč ž8ČųßęOīgŌ9Š=Ôž$I’$I:4ŋ°%x&—ŪöS:ū?Ŋ5ų3Ē &žIîČKšt`{‚ÕՄø”7ī˙oöævėŲ:JØw‹ĄāņŪkhë_øÍÃŗŲÕīŽŊŽ'Z ´œį'Mc6ĶųÛLē-‘?.dŅĢOđZu€V鏸ōQ íÜâh­K’$I’¤¯¨æļ�´Jgč˜ë:ölũk&Ķž}‚`â}\KDr8 Ûōž`VšŋŗuŲ;”s˜øŊŗÉܗĪė‚CÁ´č<€QßĀ(B”—ķæŦiLųmˆ¤¯Ĩˇ÷–$I’$é´ķĩ“]ĀĄBåëųûĘmûCÎ$wĖzvąiÛčp&]”ī‰ĨC‡ö­‚[$Šņ$ĄĘjÆŌĒQ`Rūî;”ĀūÄe×ĻbūžiOÃÖ I¸´`I•ÛØTĄB$I’$IŌ)ĨŲ…-l[Ȕß=ÁSE˛Š|ååŸđAŅ_XC"Z@‹Î˙f+J^×–}BųŽ]l-y‡)˙œģžy‡]*#ŠGWZTŽâÍŋ}ÂŽ]ŸRōˇ)<ĩ6‘Ėx(__ĘŽėzo6OũöyūŧėĘËwQžõĖ/\Ny|W2\!W’$I’¤ĶRŗ›Fėu)ˇ\1“i…S¸gF%Ą@,IÎ$÷ûš¸3@Ė+~ĖÄ3ymę#ŧYY ąíČčw ˇŒrÄåZžŒũFqíˇžfÚÔûøņtîõ¸ę{gCá6~Søw'ōؘ‰\z…7Ļ>Čk•aÄĶĄG_ŽũÉ%ĻI’$I’¤ĶĘ{÷îŨ °qãFŌŌŌ"Úø˛×Ņą}rD۔$I§ĻÍÛĘč×ŗûÉ.C’$)âÍTšß4"I’$I’¤S˜a‹$I’$IRļH’$I’$Ea‹$I’$IRļH’$I’$Ea‹$I’$IRļH’$I’$Ea‹$I’$IRļH’$I’$EP“†-_ûšYŽ$I‚Ŋ˙ÜKT0p˛Ë$I:!š4 ‰ûßąT×Ô6e’$éPSWGlLôÉ.C’$é„hŌ°%ĩC[>ĢÜMm]{˙šˇ)ģ’$IÍĐŪîĨļ.Äg•ģIíĐöd—#I’tB4éxŪ¨`€Ŧn](ŨēƒĘĒ˙Ļ.nĘî$IR3 MVˇ.N#’$I§&˙Ö Ū9ĨŠģ‘$I’$Ij\ÁV’$I’$)‚ [$I’$I’"ȰE’$I’$)‚ [$I’$I’"ȰE’$I’$)‚ [$I’$I’"ȰE’$I’$)‚ [$I’$I’"ȰE’$I’$)‚ [$I’$I’"ȰE’$I’$)‚ [$I’$I’"ȰE’$I’$)‚ [$I’$I’"ȰE’$I’$)‚MŲø–-[š˛yI§ˆ3Î8ƒŊ{÷žė2$5)))'ģI’¤&פa‹_¨$I’$IŌéÆiD’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’AMļÔ2÷†ŌĪĖūœGO•gS%pŪ™gsGq}ģoېCúåŗŠlÂę%I’$I’ž¨Ā éĨãÅ<ôĀŤļ!ŽŒÃ_”$I’$I:e˜°%Ž+ũ $ã„t&I’$I’tō45[VßOŪ™įqĪęƒ^äĄog“wßō/Ņ`ũ4ŖĖfŗjÖm =›žŊrčûíëxjIŖ‰GeīōÔ cČ;Ģ~ĒSßo0ņ™wMMZÎßČaė‹īōĮ˙¨ß/ŗ×yÜ÷ĘĘŪåŠ xV™ß(`âôÕÔ6ęŋ¤č~ŽūöŲdž™MæY2öŽy”Ô"I’$I’žâšGØqŅDGA]ņīyhõ0˙ŋ XžôĪܙžœ‡'ũžU�”ņĮI×ņđút~ôÂ,ü×ōâí™Ŧzb?ų큏%:ēŽUS~Īę Ÿā­ĨÅ,|0“’)ˇQpÍKDOx‘w—.áĪWEņæ}0ˇŦū=•Eˇ1ö‡oPyŪĪ™õ_EüųÉ+‰_xc˙ã/Ž1#I’$IŌWÜ [Ǎ­­=ÂŖ‰ģ­ȸ[ķHĸ;rŅ…ˆÚŧ†Ue�ņœ÷,Šž˙9ßÍI'55~ūKŗĒXŧāāŅ4ĩYŖųŅŲÉDÉyįŌ*4žīeÅŅdœw.Šu(^°™?>[HåĀI<sëšd§v$cĐhž-—ÚˇžãĨM|Ė’$I’$é¤:1kļŦyžīũüá¯Į]ĖäĨŋâ[MÕoz&ŅžFĮEM9•ĩ�ŅÄG—ķĮ_?ĀO–¯§´˛Ž>�Ē:ÖÔLjzWâ÷7Ot¤fĨ4j8šxÂŖÚ5,Y ú’ܨøœ<˛ëîgÉęZž—Ú¨(I’$I’ô•rb–ô‹yėŅ‡ß(:Љ͍æscÚåÜsÅÕŧu1÷Ū=‰ÁéqDSÎoÉcGoōč-×VR[Ģķī¤˙æđ̓Ę+á F’$I’$}•œ˜°%Ē+Ų9}ŋ`°R״͌VĪfîú$ž;ķW|7gߋåTUũ‹íFĮYWŋČã#’ŨH|˛A‹$I’$I_e'&l9–¨$ĸŠŖļņ걕ë))kÂ>+ë¨%‰Žq^Ē]]țëôĄŨčLr˛ĸøkiŠФ´4šäøŖŧW’$I’$ōšĮŨˆR3Ɏ+᝺RPģžš÷Í`US.m’Õ—ė¨5ŧ>å/””•Q˛ä%&Ūĩ™ėQԕ.gYå—VĶ‘ī^w.ŅoŨĪÄßĨ¤ŦŒŌ’wyåĻąœwÉmüÕÛI’$I’ô•Ö<–č<î|āRßŊ•ŧ^ƒøģ)>īĮü =ĒéúLž˜{y1ŅoŨBū7ķûë5ä˙ōWÜ{ŨŤ¯žą—īģEôŸ˙+Ļ?x.ĩ¯ŪʅßügįV&WåōđÔ_ņ-GļH’$I’ô•vÆŪŊ{÷lܸ‘´´´“\Ž$I’$IŌŠåĐLĨyŒl‘$I’$IúŠ0l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ @SwP SēuÕ5ĩԅÂMŨ$IjFĸ‚bcĸIíЖ¨`“í$Ijšô[O](˚>Ļ}ÛDŋdI’tĒ …ųtį.Ö|ô1™Ũēø]@’$štQéÖ´o›HÛÄÖ~š’$é4 ĐĄm"ɉ­)Ũēãd—#I’tB4iØ˛ûŋĢIlߔ]H’¤S@b›VT×Ԟė2$I’Nˆ& [ūįŸ˙ä}Í5x%I:ŨEŽŨ&I’N&!’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$I8Ų.ÄŌ)÷ķâ‡áĪŨ#Đs,]•E0R]VoeiŅ|Ūūp#Ĩ;k ŽM{Ōúæņü,ÚEŦŖ#Ģ|įÜ>ˇ%ãu }šĒ“P‹ĻMg·å„zŽâžĢzĶĸŠú:.xųŽÉ,ŠiÍ9?ü1#ģžGhåLni%՝/âž˙/ôvBÎą$I’$I4˰ĨA›.줉GÚ×>rA eĖá9fUĨq^ū( RZd7kßãOEĶylįXî¸,ĢIƒ‰ØŒ<.+Đą û`Ķ;Ėû°†n#ÆķI'9h9 ØÍŠĨŪĨë!Ÿi5+–~D(™Kô„œcI’$I’hÎaKL2]3ēŌŽŠûŲąŒE›|ũšą|§ĮžŸûɤĻtĨcL O.ŨĀÆę,˛c›Ž„`Ûî nÛtíPŊ›j’ČîՉvqMÜ×q Ú9•Ō•ËXwQW˛§-ÕkXRCjJ˜ŌôtBÎą$I’$I4į°å˜ĒY7ë)_ۍ‰ˇ§{ÃõíķŸåųFŪô=rv2˙w2+0‚ĐīČŖ9ĒÄB‡oJr%wiüJˆí+‹xŊp%ëĘvCLi9CsQī†ŠF!VLšŸÉ\ÄÄ3×1Ŗč#ļ×@|z—îEõüŲüЏ”ŠšRûãęŅŊIäS\Ē6P4̈E%Û¨¨ Û˛=YyÃ=´ëūc¨,YČë…īązËgTCBJ7r‡ #?ãđ$ĨtŪã<øv9�3gŽāÁáģųÍo—ŅįŌ<* į˛"6Ÿ;nB"!Jß/äõųkØXļbZ“ÚcÇĄ{Ā'ŧúËÉlzƒļQøá6*Ã-I<ŒņCcX2̐ų%åT’č“?‚ˇt8ę(¤ØŨIÛ´%kCd÷:°gå‡ËXHãœä(-;đ™/}ūa^f÷]{`T¨ø%~: Fß}%ƒc7đę/§˛}čXúŦËœõIŒžķJ˛‹ŋč9ŽfÃ;…Ėz{MũÔ˛@K:ĻįpAÁųôI8ĘI’$I’N{ÍzÜP(tøc˙ÖXēģˆAķjŅÖú×+–ņęürŌ† 7 †ÄÎŨčŨšõįw’’EV›ū>k*¯ŋŋí՟ŋ랕ŗyėĨbĒ{^Ä-“nfŌUCˆ];›ĮfŽf�A—,¤¨fīē‡nJÂú"&˙î5–Ļ\Ĥģîâžkzągé\æŦ<BÂC‹gLeΎ$†]s=ŋ˜4ë‡ˇ§´h:/7Wũ¯N™Īö”aLŧõf~qĶ O)gŪ ŗY\ux‹Šųãšī’L¤RpëĪxđĒúā)ŽaEŅÄįã–KzTŋÆã3?"vđ(&Mú w\“Oģ-E<ųüBļ7´ †Ųŧh>›sÆrĮ/īâîKÚŗ}Ņl~ķü;ōĮņĢ_ūŒIyVĖ-déę9H\&ƒŌÃŦ^ēĻáėdÅâRâ{õÛĸŋ�Á`˜ŠÅķY1Œ‰7ŖĪaŖ’Ž}ŽC%…<7{# ųW0iŌÍüâÆ‹Č ŋĪä)΃$I’$IGŌ|Gļl-âÁۋްĄ5įÜôcFĻ�ąŨ)(čÅŊSæ˛¨˙X ‹Øœ’Ī iͰo,}†_yôQƒ]yÍ(˜YČÛ3'ķ6â’SIë‘Åāū9ôIŲ÷K}'KŪ^IuúÆ_UŋXkÂ�.ŋhŋ˜ļ%Y Ũ7â!ĘĐÜNõ#$Úv#;>"‹ ú'‚Ytoš˜eŸɇCŸ‚ëéJkÚ%Ô' ‰ CÉ]TLáÚRČé;ˇ˛ŊĻ%Ųge‘š�ІĂ+hwÖnâcŽtŒą ¯ÆÆŌb€ązä32§CÃķ2Ūžŋ†PĪQ\ž×0Â#Ą7Ŗ Öąę™w˜˙qc˛ Ĩ `X¸úãé‘Ek؞‘ĮІķÕŽg …ī°q >ę´Ĩ–d÷OcÆĖeŦ¨îÍāX b Kļ´¤ĪEŠ—íŊŸ¯"ļ7‡fí_ķ§ō ­Į>Į•[ĘŠŠIePßN Ŗ–ÚđĢ’čŗ3€[$I’$IGĶ|Öäū\}IŋÃØcHl”O´č1ŒË{>Á‹/<G :‰á7 8ōĸēGlۛ17öføŽ Ŧ^ģŽÕk?bÕâB>XTDĮÁcšĄ ;ņĄmŦÛíōSē+N‹.Ũé.dŨ–C~¸“Ё„ũF ąąˆKj´ūLũkĄę#Ũq)H‹ān–Ė+äåMå|Z&ĒÚ4ėŸÜė6‹X4í„rĐ'#î)q¤vųĸ‹ąHíœtāi¨œ eĐŽęASŽ‚)]IĨ˜í[Ē !lIHn´Čn †@�R hA˜đįßTjŋøũČb6KŠĢ<$Ží˗Q—IA— á/ļ$¤¤å:8ö9NĖČĸca/?`čāŪdĨ§‘šL×&\ģG’$I’ôÕĐ|Ö@Rģt:Žrc鞛Iėīß§ōĖĄ|ũ_vĐĸmWÎjەŗō·ĐNV˚ĘäÅŗ™×÷§ŒIŠ!†Í…O0Ąđđ÷vĢǁũĢ“ްNÉá§úˆ9Dč^z2‹9Œ.F÷ļ1Ų͒)O1oß>Áތŧq<íæ/dÉĸšümn 6i v#s’ŋĀšbÆ6Ú;\CubŊPC˜RųyÉIpßžäåۍÁ=L^ē’O‡¤ąøũm$ôAW`Ũ—k‘@ĖQj9žsœ’ĮÄ4˙=Ī™Ęœš�q{qAÁ0rSL\$I’$IŸ¯ų†-Į­ŒEs‹ Ĩw#aũ|f­Íäō_äĮp5•UwČ{‚mčsAiKgŗ}GtŽ!6�sĮ1ŽËCÚĄ[ülYÆŌ˛– úaƒģė{q7{ǁ6ö‹ëDîđKÉ{vl`ÅĸBfM›J°ÍŲåđfK ū+jj€FĮķy!LÄĒuV7‚/}Š•;YQ–DŸũS›Ž-ÄįWŸį8Īq‹”Ū|į˛Ū|‡Ÿ~ŧ†ˇ į2ãų0 ?ģäā;'I’$I’ÔHŗ^ ÷xl_8›9ŨsŲX.Ī °tV!뎲ČíÁBŦšņˇ?:—GxO¨b+ÄŸÁö¤Ĩ¨ØYCBÛdÚí´$hI|¤;Ԅ Ķ’ÄFí…ļ|Š2ö' ĄŠOXąļl˙bÁ-ÚveđEųdvSZvܸ`{ē'Ãö’mĢ…ĐĻ l§5Š)MwĪč`~ô‰ŲÆÛs×P‘œÉā”#í „p¸æ peûĻō/ÖŲqœãĘ-ĢYąeßš ’ØĨ7#‡å°ģœÍ;ŋXw’$I’¤ĶKķ [jĘØ°vĢŽđXWRVTŧĮĢEåtŋā"úÄéš_wĸ— 74„ÕŦš7gįm8(<8 HvūPē…W2ųwĶøĶûëX÷ņVJ?ŪĀŌ…sx|Ęb*;䑟Ú0čœL2yáļWUņéŽ ,zå9îũŨlVũ ĮAR:Ņ1°Å‹VŗŊNJí%ī0yÖg¤ĻW|†ę”ŊĮĢSĻ2yá:J+Ēø´b+ĢŊÃ:Z“ö/MqiÃYC3 ūŖˆ—ßi8Ə—1cÎJÂéyœķeGĖ`ƒzÆPąķ3Úõī÷9ĶĮ‚´ëœ›>`éŽú¨Š˛äM ׆ŋØ­ã8Į•ÅEL~ū5ŠVnå͊†Īúí5T´L%­Íąģ$I’$žšī4ĸÅŧōBņ‘ˇz1ūWųTÎ,bcJ>wôoqėĘđ‚Vŧ0›9}'0&Ŗ†íë×đA ëķûIĀ 7ĩæíĸ…,ž÷oīŽ!מ´ūŖ¸ehoRό´č5‚]RÄëE¯ņĀÜŨ„-i›Ū‹ËOv¤FļÄå0Ļā&Ī{{C\į^ /AŸĒBJ§,âņ§á–›/ↂB^Ÿ?›ßĖŨM8CBrƒ.Ŋ‚ Ž8"äøÅįŒbb¸×įOįŲ5͚´žÃ¸~ø_xø‹ ’Ö?“¸åĨœÕķĐ;4Đ.w›^Ŗđˇ÷3'Вv=†0zX76Nûėøģ:žs|㌠ĪeŪŦᘺ; –´MĪä˛ ûˇŖ–$I’$NÎØģwī^€7’––ŅÆ—}¸Ž~=ģG´MI’tjō{$IúĒ:4SižĶˆ$I’$I’NA†-’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’AMļ|ík_ãūųĪĻėB’$ęBaĸ‚“]†$IŌ Ņ¤aKÜ˙ŽeGųÎĻėB’$>Ũš‹Ø˜č“]†$IŌ Ņ¤aKj‡ļėøô3ļ•UP 7eW’$ŠĒ …ŲVVÁŽO?#ĩCۓ]Ž$IŌ Ņ¤ãyŖ‚˛ēuĄtë>ŨšËĀE’¤ĶLT0@lL4YŨē8H’$6šü[OT0@zį”ĻîF’$I’$ŠYđnD’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$IhĘÆˇlŲŌ”ÍK:EœqÆėŨģ÷d—!ŠHII9Ų%H’$5š& [üB%I’$I’N7N#’$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤jâ°Ĩ–š7ä~föÁ^ƒČu O-ØÜ´Ũ7'E7’yæUŧRv˛ ‘$I’$IM)pBzéx1=p1É Ok+7°`ĘīyøÚĢØ<õĪÜ;(ú„”!I’$I’ÔÔNLØוėAÉØ˙Â@žuv&|{,¯Lų ?tņ'¤I’$I’¤ĻuōÖl‰Î$'+ŠēŌÍė›YSY<ƒŸŒēžŊ˛I?3‡Ã¯ãĄæŨÔŽgî]WqŪ7ę§&e~ãBÆŪ5’Úco/›u™gŨ›ĩš[|×Ų¤Ÿy!O•x­~ŋÛøk-Pģ™7īģŽ ŋ‘Cæ™9ôũFß˙õBJ÷īũ.w|#‡ą/.äלGf¯ëøc%ĀfŪŧë*ōÎĘ!ŗ×Ų\xĶK,ŽjšĶ(I’$I’𗓏@îfJKëˆJNǟ^Tųî¸ænūš<šg^+bAátîTÉn˜Ā+ éÆĒ'&đŖˇĸšôŅY,ø¯"f=0ščˇnåę_/?æöäį’Qģœ%Ģ÷õŋšīBRR9 –ī tjYļ` ĘĨ_t%Ŋë*~0Ģ–o=0ˇūögĻß=€˛éßįęû–SŸŲD]GéĢŋgÁŸ1ũĩIœ%ĪL`âŦ*žõËéŧ5:÷žŊ†Įžxēxv%I’$IŌÉqbĻĸļr=‹§ßÍĶk’8šõSˆâđĶ™¯CrŠņ�Ip%ƒ§ßFŅōJë—Đî�� �IDAT.KĻdõfČē–īJ¯Oę•<>ŗ/Ĩtjž=5ŠÁɏ°Ŧx3ät„˛,+Ëäģc+yeÁj ’‰f K–×Ōīēėũ…ÉŪBέSøéŲë Ī˙÷._ÄŋO‰ŋŪúÎoXjfsÜL˙ūš¤°š§g¯!úŧg¸ķÂ,ĸÔ‚ŸķŖ%‹¸tö =͒$I’$é$81#[Ö<J~Ŗģeõ˙wÆ=[ÎāģŸáŪü}ĢĩÄ_ĩ‚§'áÂoŸĮĀoœMߥwŗ¸ŽŽÚÚZ š~ö%náŒŊé^)ZNi%ħö%;5ū8ļ§“?0žUī.§¨-^ČĒô\Fæeŋ|Ģ�J—ŗ¸,ŗ&ÃúåŦĒKĄ_NĮƒ%c`&QUk(>0—ˆŽY}‚ v%Ĩ‘“΁eŖÉ”ŲD'W’$I’$5''fdKú%<ũččũDtTÉÉ/Š[ōW_ūeįũŒ‡Í%#) j˙‹ŸüÛŨė[f%ĩāYfÅŊÄĶSŪāą›ŸįÎē8ŌĪģ–{īĪāäcm&ûė>Dß÷.̏�Ž >g<9ÉŽ|ŽeĨą|%ÉœŦ¯ĸ’h’â>”čč8âŠĨļŅ,ŅņQžÔÖRYŅŅQŋ/.Š(j‘$I’$I_m'&l‰ęHFVVŖģŽä­7(Ž>—§Íā}CBĘ8$žˆ&#<į‡Ú2V-xƒ‡~õ(WOJbá #H>ÖöydWÎ`YÉj(ŽĨ߄Lˆ.įėô ,X^FƂ5Äŧ‚l€øúPĨü…mk+̍$ū°æ@‰ŅDGAeíÁ+´ÔVUšf‹$I’$I§“¸@îÁjË+!:Žäso(ũķl–ACâRÉĒaŲžĩlŖ“ÉÎĪc2Š[ŋœŌcn’pvúz–ŧĩˆë39;'čHŋœ$–-(dÁōZúŨˇ~úOV_˛ŖļÔ¯ņŌHÉģk¨‹Ë$;•#‹îJF2”¯o5,ŧ+I’$I’žōšMؒ:°/q勘<k5ee›Y6ë6&žÛ‘ÁIPZŧ†˛Ú*?q Wßp?s‹×SZļ™’âŲ<={=qYydpŦí�é ”IJ)SY•žKŋäúž3ō2Š]øsK3ÉØ0š)ū\~PBņˇņԂúöVŨĪOĻo&kėFß&‹‘vĨę­¸įĪË))]ĪâéˇņØōhĸ>ī-’$I’$é+ã¤ÜčHâķ'ņđ˜ Üs×H‘DÖŲ×rīŖáŲ \ũė.Œ~–wŸ|‚ÚŸ?ĘC×Ė`sUQI]é—÷3^ŧŊūŽFß;Æv€ėŧ0å ĸĪëģZStV.Ų•…,IŊ?€xß>…§ŖīæĄI<\q3ųքgšķûY|nÖdOx‚‡Ęnãą˙Ë4’Č:īZîŊ-šīßŧų(ī’$I’$I_gėŨģw/ĀÆIKK;ÉåH’$I’$ZÍTšÍ4"I’$I’¤¯ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤2l‘$I’$IŠ ÃI’$I’¤ 4uuĄ0Ĩ[wP]SK](ÜÔŨI’¤f$* 6&šÔm‰ 6ų×I’¤fĄIŋõԅÂŦųčcÚˇMôK–$I§ĄēP˜OwîbÍG“Ų­‹ß$IŌiĄI§•nŨAûMlí—+I’NCQÁ�Ú&’œØšŌ­;Nv9’$I'D“†-ģ˙ģšÄ6ņMŲ…$I:$ļiEuMíÉ.C’$é„hŌ°åūųOū××\ƒW’¤Ķ]T0āÚm’$é´a"I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A†-’$I’$IdØ"I’$I’A“]ĀáB,r?/~>øå@ )ŨČÍĪ'ŋG›“Sډļr7ŋTÃČ;ŋGnÜÉ.æ Úą~ũŠß˙)—gœėbšČŽ7šį×Åt˙áOĶådsˆPK ™Wŧ‘ģ!ļMŨį3rhwŋd“{Ū‰˙˜āęģ/åŦ`DĢ•$I’¤¯”æ;˛ĨMW×_šOŸĀFæŧ0™WKB'ģēĶ^å;āļëNvMĒŲ㎅<đđ<6|îÕŦš9™‹!÷˛ëšīÎ Lŧ(ĒųĶyrÎ'|u˙õlāÕûŸe~ÅÉŽC’$IŌéŽų†-1ɤftĨ{Ã#ģ×�F^3–sÚ|ƒEkØs˛ë;­…(Ũ´đąw<…5ßcÜŗeۏVXh#‹>ÜMĮĄ14#™ø¸6¤öē€á=cØņᔞ°JO°ŠmlŽ:ŲEH’$IRŗœFtÁö¤Ĩxģâ3Ē€ĀžßcÖÜwXąĨœęp€¸i ē`ßéŅ0ī&TÆŌšs™ˇ˛”ģÃZ&‘Ök(c.ęMģāҎĮ.˙?ŸĶ’qw^BŸ†iëf=Äã‹c~ëDōÛÖŋVųūøųœÖŒŋŗ€lv˛ĸp.ķŠ7˛}7[&Ņ­>#/Ø7}c¯ūr*ۇŽĨĪÚšĖYŸÄč;¯dpėNV˚ÍëÅĨT†ch×3‘=Žq>v,äß.#ëĒ|Âķ Y˛i7Ą˜$Î>Š‚ļ™1k!̎ė†6iä_2Šü.ąĀ:^žk*Ûķoæ–ŧĶąÖÍøoĘ/nB"PY˛× ßcõ–΍Ļa ×°aägĀĸ§fÆz€ŠLXšÄy7Mä;)G.1Pũ ‹^™ËŧˇQhMZN>WôŪ?•娟_õ<{ĪkT ģ™Ij%´Ž—ī™Ę†Á×sį úæĪ*äíĩÛ¨¨¸™äÆĐŒŖĖŊĒÚ@ŅŦ"•lŖĸ&LlËödå côĐŽ´ ę cŖÂØž˛ˆ× W˛Žl7Ä$‘–ĶčZ`'+æĖe^q)Ûw×@LkR{æ1ē`�ŠûöŠX͟fÍgÉĻmTÕˆmĶž>į Ŗ`H'Ēæ?ÎŊ…åĀG<ōĶŤ¸™[†:­.@0�‡Íô ôŸ#Ē˙<˛bËg„GĒBeđęŦ"–lú Zļ§Oū.ŌĄĄŨc_ˆSîg21>ų=^^TN÷Ģnc\Ûß/äÕųkØXļ›p †ļ{qAÁ0Îj<v}›æpÛ3īSl|ā.æôÅCWõ&xŦkcĮBîųí2ú\šGEá\VÄæsĮC~îõĒÍį“$I’t2œZa ŸQQ&ג8€ęÕĖxa.ë2†1ūÆL5l^<—ÉSĻ“xëuä&@iŅt^\ؚ‚ËާO›�Õek˜3ã5ž ´æîᝎž=7‹váwXˇútØĘĒˆkš›U›ĒČo„ظvd -XÍĒ“yîÃ֜wŲxƧÄPũņ;˘9•'Ãã™4ŧAƒa*Īgu˙aLŧ ‰vą°}ūt&ŋƒ.O~F •kį3§h#aÚũ”„?cIŅ:F^:‘ aVÍxЧfNeCz/Æ\ķcÆÅîdņķOņĘŦ÷éssíŽį4WĀĢSæS‘3Љ—ļ'6ŧ›‹æōō ŗ‰ûŲ• ēęzǟ~ŠĸļŖ¸Ŗ ąąŸ[Ģ‹Šį ã†üĒKæ3yölftîÆ ũcãķëÆY=bxqų>Ͳ?  ­˙€5I|3§PFŅĶ“™ÎdäU#ČjfķĸšŧüÂTÂ7Ũ°?;X‹gLeÎÎ^\vÍēĮAåĻ…ŧ<s:/ˇš™ërâžĀ1°gål{é#ĪÁ-ƒÛÜųš9›ĮĒÜqY-høœàK¯`\JKØųš9—'g%q÷čŽŲÉüi¯ņˇĀ`ŽūÁXÚńŠX;ŸWįNgFÂ͌ËĪĝOņøÚnLŧ1Ÿ´˜#L#7§5O.žĪŠžŅ'!Ȟī1e ķúyPņĪ=SHu˙‹_JlUũŋ…ĮB1Ü1ē{ÃGēĸ9rķĮ2ŠM˜ ķgķĘėš¤õ¸ŽĄ Įs|AoZHQ`�—?vm‹xræJÎÅ-ũ“ˆ —ķ÷yŗyqJ í~r~}ÍG­o“ŽŦáŪ—ĘÉŋißLŽ%xœ×F0\ÊĸČÎĮ-)­‰¯ū€ÉGšū›ˇH’$I:†S&l U—ąnņ\Šļļ¤O~&-�bĶ~ãõׁĆߜ‰ųCčžx6+6U“›`û–Ī %AÉõīIÂÕ7ĻRA:úö„�ŨãŠØ¸i'tiUĨlŦjĪ Á5ümí6Bũã˛u›Â¤“F‹Ē5ŧŊü3Ō†ã;ûņíuŖ7}ă‹ßaõ°NûGČTÄöfâĐŦ†�a+Eīo#Øķ FæÔHė”|ÄãKufÂ$ž5”ŗ‚@ėœTK?"5o(ŨcڐŨŗ=Ėũ„í!Ąđšvne{MK˛ĪĘ"5ĄžÄ‚+hwÖnâc Œ!1Ä5…¨!Đc—÷īP˙´mgŊŊ†Ĩ›ĘĄ§ãøübÉ:+Øi°ēbš �!ÖDu‡! NĐڅmmÉ Ž ˇKũÁ%A~É˟ŋsFw=ÂHŽú\OWZĶ.Ąá= CÉ]TLáÚRČéN0öxq˙IcÉÛ+ŠNÁø ˛ˆHĀå­ã͞¤"‹Ą Đ,ˇô ĐŽm\}] ČīšG>\Ívē’ĘglŪ&!�Ų)õ×PâÜĐeá6A‰ˆ‹ũœQ*AēŒãōWĻ2ų{ĻBč8x,7 MūÜ#ذh!ÅõįĮũč @2Ŗ‡īäõĩģŠ‚ú€“0]ķG04ŖžįvųøÛŌ"6n ABđ8ޝAUkr †ŊīÔĻäqÃMˆKiøwHōķēQôÂFÖU@jÂąę Ō.¨?/1ą´~‘kc7ôČgdNÃuēåč×ŋ$I’$Kķ [ļqīO‹~-&‰Ū#Ž`t¯}ŋĐb‰­^ɜ¹lÜą›Ęę0áp˜P8LZ8 Ē–“JėĖBĨŒÜžYdet"1ĄSÃ:Žą=@Ÿô­/eO^‚¯csÛîŒéQÆßgŽŖ”ît­(e]UgeÄÁŽO( ˇæŦÎOëh—ŅžĀÛÛØ°ú4ü5=!%õĀ]aBålß í'5úņ¤cF{XzŦUCbh—ĐčOíbiMj›-ÅÆ>ū…Q“ģ“Ũf‹ĻũPî�úd¤Ņ=%ŽÔ._ôOú1tÍčĐčyKb ÚwLĮúü EF?ē^céÚ*r‡ÄA¨”ŋ¯ŨMĮŧLÚÛˇlŖ:JvJãØ!™´.-Šú¸” ēa4OÁŨ,™WȲĘųt_ŋ5@›/šJKhëļ@ģüÔú ĨA‹.Ũé.dŨ–C‚c âũB^eU5„ÂaÂáˆŲ÷ų´§O–,)œĘŗ;pVĪnde$“˜Ōé SÍē9Ķ™QԚ ŽAŸļ1ėŲ˛Œ9s^ãɘ+¸å‚NGiĒØŧå3ɝ:_‰9Ú.§ū˙÷�Ú“Õø\ĮÆ T7œļc_ƒäޤ6ΰ‚1°ãfĖųˆŌģŠÕ&Õ$Q)ģžCU÷ĩ ĩsRŖ]"uũK’$I:]5ß°%š?ã/@BÃĶ` %qmãö‡$�ėx‡§ž)ĸ˛į0.ŋŦíâZÃËĖŨ˙Ã.ą˙Üŗ˜ĸE˘7m3Â1´í™Į˜‚<ēĮk{Ž=R ÎŲĀfzÃ?J‰íœGģέéXŊĐnĶ:ļĮĨŅ­-°Ŗ†jÄ2"ˆ!–0áę¯búp˜ę0Á˜úäč‘˙ƒ]yãxÚÍ_ȒEsųÛÜmŌ4ė"Fæ$u͏/TÛq|~Ävcp�“—¯ĄrČ�b7-cUM*ų}ëGhėŠĒđJžģ}åáíˇÜI%ļ„>áõ§'ŗ(Ãč‚atoCŨ,™ōķŽûØŽ!†Í…O0ĄđđÍŨĒj€0Ģf=Įsᅵ‚ŒéÜšØ lŸ˙ī?`ú\2ž‰‹RôūB^^TH8Ļ5Yƒ/bôĮyÛæŠb^_ôŨŽGūž`˛íųŒ —ōķYķY:ôJ6X'L¨Hüoė0GûLĢãøëCš}*ߟÎofn#mØÆõmO|ø¸{_*˙‚õpü×F ÁØFWuÄŽI’$I§ĢæļÚĐ.ĨÃQ×Ųūá262É�ēīûTÅ!#8‚´ë•ĮåŊō TEéÚbæĖ-âÉ-šûÚ~Äk{Fw:ÖŧĮÆ[áã0iųí!¸›ėļåŦÚTEģĩۈMR?="ļ>TŠŦ>¸ÎPu ÕÄÂ8ÖúMĢÃ+Ąššv7œĐĄÅu"wøĨä‡=;6°bQ!ŗĻM%ØæĮŒė™>÷ķËęۍā´ÕŦĒĘ!qųGT§äҧ!…k1™\vc~ÃԒ˜ƒF™ėˇeKËZ2č‡ Ū,ģŲS ēÖėņ Ä€Žšã×ŋåĄ‰‹ƒĐj–,ßMĮüqŒĖ90gû!Ÿ;Á6tĪN÷ŧáõĶį۟Īë…Ķy.îz&å}ū4 ũĘļRAK˛Ú|ÁŎiMl¸”í;ÃŽÅ�ąąÕ°Ž;Đ8HhãņßaĒXõūGT§āōĄŨ÷fĄƒFc}ņúžÔĩąĪ ¸ū%I’$}u5ß[?‡PU bˆkô§æO‹—ą~ąWSēv5öŨ6Gj¯<FnO¸ė>=æv .ėärÖ}øĢĘړÕ%´!­sK6Ž]ÉęMaŌz¤Ö˙ĩ;ĨЁĪū?{wU˙üš5 “‚ ¨ˆb"*h*šĄšę––ļĐĻ›ÚWÛŦt7u­¯ŨŦŨš›ÛÕ~Ëvĩ˛5ōnł’M*H 5EMĐ@LPPAl†ŸÜĖlũūTTŌÁ›|=ŽËë’9sÎį}>įx]œ—ŸĪį8×xЧ$¯‡G�Aįz7ļĸĩ7”8Zīá˛ná]—3`0Āq[ũā JŽTŸjšŧß•žŦÅĶŋũF#ĖPIQiŊ$é"“ Æ¯Ÿ“ņÆî„ ØųŨ^žÉĒ&øĻn'Gxøā]} ~´ö?õĮĮä 8…Pí×Éub�ė‡žegiįÔÔsŦ{SVyE5>ūõkņÂ`đrmU€ÉģŪÂö\Ōŗ*O5UUJnVžķŪŒ&?ēFdXˆƒ’Gio/ŧФüČ鱕õČ1lxáÛālo,-āP>õvŗf&đÚ[_Û”9hM9ŋ†wtŽėōŽ?ÚÅÆÎo ęíĶÔúNíqA÷?áū9‡Ģ:lņ ą`ĒÜËēm‡ąÚ*ČߖĀûy-õ‚ōÅXíÕäĻŦd~ÜjžŲ_J™­‚’ũ¤l;Š)0”Ö4ļĀNŊ(Xŋ‰ƒ~Ąt¨{Pm}c�Žī6ņME�=:Ö="šē0Ŧw RIųÎyŧĸŦÕ,Ų|Œ ~ũOŪ8Kĸ"ZQ•ĖG™…””—’ģ)‘ä†fzԊā@GžË ŋ  ŠĸõɤÛęĩTē•Į-fŅú\ŠĘm”•f׆MäŌ‚ā@āÅQu$—]‡J)ģĀįĪÆ¯_ŨˆēŅĀŪĩ)ėŦļÕíTZ`ŧą?ƒÛ%%~5ßė¯ĀjĢ (ë ū9won(m¸áĀļŠŲŧ!‡›’}›X”p KˆGy!ųUö 8Į–DŨÚCv2‹ÖįSbŗQv$Ÿ Kßå…ˇŲU˜õƒ‚Í›Č-ˇa=’Ë'qëąw´@õQŠŽTawŗ6~1˙\‘Aî‘ ŦåĨäg­eãÁ!Î7SM`+bīžRJl ¤ }ÜÎÁö¤•lØWŠÕVAÉwëY’R€)ŧ=Îąôˆå–ūWgņQüVr&?k=KVeRÖ˛AM™;Ķ”ķ;GßYŊp|ˇ• ûmXËķÍĒ•l0ãĪ1ōUpÜŪ„ú< ˜8FÎw…”ąÁ…ÜЄû_DDDDDäüŽūųįŸ8vė-Z´péÁ‹KËiãߤU&ęųÃ;6YՁũÛs愌úŒūíhmÛÃĻ/×đé†,üÎ=Ŗ‡Zģ‡M›ļđõņ™yÛIų2ä5_ķuÎQÜ;f|lūFÂĪŗŨŲŽ‡Y“~�CˇAÜæžrŊ{-ģÖeRėׇģ¨Ķˆo§.XŽīåË/Sų4u Ûü@đ &üúÄĸģVr6l§4 ƒBO=õšƒCđŠØËĻ5ëøüë]ģsĪ@7vfWŨ“vî tĀ˙;†¯‹0÷prZ ßņå7V:Ü܇ŽŋŦëŅâ¤dCÁ^īNk‹™cß~ÍĮÉ_˛fã.JZ âļĀ"2ËZss˙ö˜[uâÆĘŲšaÉ_ŦcÍæ,ōl>ôųÍŨŒčhâzŒ˜=ld}ŗ…¯6īÂ:€n>M¨j 6of¯wO†ukŲ„ë׎!a-ëiá^ƚ yÔtĖčūm8ÕŪtŒ ÁP°ĪSÖđéš-l?āĀ28† ƒ‚h¨Ûp÷§Ã Vvn\Į§Š›É(õd`ė] ŦdįכY—ķŨúwÁŌč9î#íëb|û [ 0úwĻ›¯•ė´uŦJ^Įš-{)õîÂŨãî ĸÅõĀ/i×ۃĸí_“üEsŦø ŠáwũoāЎ-|õõZôŊÛÂŨ(ĖXĪį)ëø<m;™~ ]t cŖįíuƒĸoˇŗ~Ã6ö]Æ-ĄgĻ'&ÚuÁ|dk׎!)õkžÎĩâyÄvĮ÷ú†:0ĩ%˛“…;6ōų—ؔSŽšĮp&ņĶōz°ÚAjÎuDîNā‰c8ŠŲēntĻO€wãįŅĶŪ¯ØVʐž§Žop öÛYķŲžÜV@Uģ!LŽgÁNŌŌļp8 77učpŪúøĨ ˛Ø´i3éÅžôé}#Ũģ7¸O¯oôū‘ uaŋˆˆˆˆ\ųÎĖT~ņã?ūPPP@pp°KËČÎĨgx¨K)"""W'ũ^ """?Wgf*Wõ4"‘+ÂRØ""""""""âB [DDDDDDDD\Ha‹ˆˆˆˆˆˆˆˆ )lq!…-"""""""".¤°EDDDDDDDąˆˆˆˆˆˆ¸Pŗ†-×]w˙ũá‡ælBDDDŽĩvnFÃå.CDDDä’hÖ°Åû—&Ž­hÎ&DDDä*PVņ=&÷Ë]†ˆˆˆČ%ŅŦa‹Ĩ?GʎQ\ZN­ŨҜM‰ˆˆČ¨Öî ¸´œ#eĮ°´ņŋÜ刈ˆˆ\Í:ž×Íh ŦS{ŠĄŦâ{."""×7Ŗ“‡;aÚk‘ˆˆˆ\3šũˇ7ŖvÍŨŒˆˆˆˆˆˆˆČAo#q!…-"""""""".¤°EDDDDDDDąˆˆˆˆˆˆ¸Đ% [ŦėúôuĻŨs}ģEŌ9’ˆÁą<<'‘ k34W“ĮŌ?ÜADįŽtųÃjŦû^ghį<Ų mÉY6?;Qī˛īr"""""""r\‚w0–ōşîeÚj‡ŽaúÄ>Xŧk)Ũ˛šEËgqwęÄŋƯũ\Ødæŧ‘jŖßėå<ŨŗšŋÔŋ ļ!"""""""Ō€f[Jf1sĩ3øx˙ېSáˇŋëËÄģfķôœß0č˙ĸqwUŖÖŖXéĀĀĄtô0ķëØ0W]DDDDDDD䜚yQžŋ[ÄyĄ~Đr‚e sã?âĶ—O-ÖĖ˜yĪP"ēu%¤[CĮ˙™Ĩ9§æ•.Ÿ@—›Ÿį‹Ė˜6j(Ũ"érs,Ķ–įPėzõBū¸†Zļōˀބ<xÆ4ĸžøC$]ūČW¯ūŽžŨĸ˜–ė{—;ēŨÁ+i_ō×ņ§Ž;ķĶ<Ŧ9+˜vķŗˆÛb~æšį>-˙]nū3Ii¯3ņöÎ}˙Ž™ŸæQsō[ųbÎCÜqs]:wĨËMC‰}bģjęčK^y –ž7u­›võ;f.߁ĩŅí5|ņ§(ēŒO¤ôäÁrxåöŽ„Üü<›O~V÷ŊV;Yēžųˆ%ú&į4¯žˇ?Ä_ë×ŧī]†vģƒWR™v{$]îų€ĸēũ^yā"ēuĨËÍwđđÛëëĩ+""""""ríiŪ°Å烴Ũ6ô,Ḋ_Į0üN$-û>`âø×Éų#īōéŸĖãūV[xfü˙ōa‘ķ+îîî`]Ã+īÔ0öŊTvdĨķéCn|ņÜl–A׊ËIéW¸Ņƒ§“7“ųĪ˧ĩčŽģÔæ,dAŅæ.yŸĮ#ë6ÕäÃyë‰|éSvdĨņv´•žxˆØWÖĩõ)Īí`îŗ+Κ‰;îÔũ’Wâŧ™ŸÆŽŦ4–Opį‹Sy#§î4ߞʴåGé9û>]“§˙|ŋ-ŗ™øė–ēpã ˙šņKknaî’ŌÖ$đöCAdĖ™ĘĶi5l‡~# gÊđĻt'Ĩ­hÅN2Nž›ô5tŽĀ\ŗƒWî›ĘE=xüŊOI_“ĀÜHš1‘™)§‚%÷Z_ĖKÆoj / ĮR>œ1•y]xfI ëã_åîŖ y#ĩ9âš:4oØRšO)nX,M[,%ãߐiÁ ‰ĄgG?ü:öeÜ_eXÍ}šwō{ĩĩn zčAúų¸ĶņŽē˛›Ėœp7ãįíüÜŨlÆ|ŽšIĨAŒũË˙0(2 ËÉ4ĻKėiāéÜA�� �IDATqĖ ē#ˇZ]'ü‘~f€  íy;ØWsŽãâl{ĐÔéi0ĶõŪIüÚ;Ÿ¤gÚb‰GÂ'oķ˰:Z‚č5†ÉC9ēåËēį 9yĩX†ŽaPXKũîũĮôH÷Fˇ›ûŪBך¤ÕuYMæzv…ü†ß†$}G]R´…ÍĨAô‹ĸ&ũ–æņû—Ÿgdd~–=<‡éGų"îËz#UŽÂĀ<sG];úá^ē†e[`āÔįømd~–0~=ëI~mŽmŌõų9jæiDŪ¸įÍ%N*eWÎ!ëC×ú‰š ‘!°oGūŠã¸…Y w7Ė€õ§<ã‡ôĄĢųĖŊéToĨ^woĖtĒ ŗŲ7j9§.D՟5åBX”tžƒģŠR_bâ¨;ˆžy }oŠbâōCPSSwÜ. ‹nEÎĢņđœHJĪÊ;–°:š›°ŨŌ‡~–Ŗdd:c’Œõ;1GáΞAėJßA `Ũ˛•}~}ÖömŲÍûŒšņŖkd ĩy;ęâqŖkDŊ/åí ˆVt Ģߑ!Duņ>oˆˆˆˆˆüœ5oØâ„Ÿ[-E9yV+¸š›ĪX(×ŗjkŦõˇ‹¯ÍŨĖYY î¸d•^77ÜO;¸îîP[SC Vžzö^&ŋsŽ˙÷ã?âĶĪūÃÛ÷Öûž™_ŋŧœe˙ۇšô…ĖŧīN"oĘÄW×;×Iit{ûšŲˇ~'5䑑YCĪč.tė;ļ˛2Ōwā1„Ž@ĩÜŊq?ãܝSļlõúŨŒ{ũĄBÖŦ¸Ķę´Ëá~úwDDDDDDDŽ1Íļ˜Ŗ%ōR“Ųuޝ”ĻŧËŋŌRĶ`¨įaŽ`ĩĩXO[ļ¤Ū9ÔlåŖOöĐßxæŽ:Züđķ탚3†å¸Ņoâķŧŋ*ÛūÃĸŠŊ?•‡ßĪkŌöžŅ= g=ģŠļ’–×…‘î֗žĨ[Č(rŽ×ŌsXî8GëPcŖæŒá:5Ö0{ŸģßÍî¸SÃŅĶJ¯Áj՚-""""""ríjæiD!üvâ-xį}ĀĖWwœ5õĻfß f>ųw$äQƒ]Ã!g+õŋhŨMztíÛåę [ØMzNŊ“¨É'ķ XBq¯ŠÁ ˜ũęMĩŠYĪGŠG°æą9eKŨ(p7‡0hâsLŽĒe_f~ãÛ÷Č!t-ŨIʧëŲr‹sũsQ!ų¤}ē…ÍE ëë~céÛo›ŗŸO9HFÎ!ÜÂ"čzŽĶ ęBGޞ/§~¸˛›ôZŗEDDDDDDŽ]Íļ€_ėی bß;úĀ‹üëĶ/ų*íK>|õ1bGĪfsĐ}ŧų—hĖ@Ī e]Í_ŸM$Ŗ¨”Ō}[Xúä뤙Åũw4m‘Ũ+Åæy/’”sŌŌ’æŧÎW5]¸;&ėähŸŒå‹Ų\TJéžõŧō‡…ÔôíÖ|víŗRSŗ›EO>ÄÄ'Ųŧī ĨEyd¤üƒe™nôŒęmđë˰<>ŒÛ‰{dĸg¤7q‹ŲrKŨÃā5‰q!Yúė‹|‘sŌŌ<žzûĪŧą#ß>4ĸéVu,C¸;ŌæÍfizEûvôėë|UŖ5[DDDDDDäÚehū&üôrŸũ¯ŧŗš7žXŒ­Ö ī . ēwoNBĮCV,cx{I OĪųoŸ…V„DũŠ7?ÅHŋķ6reqëÃôŠ!¤<q/3w[qŠāˇ/˙)BøũËO’9ãLüÕB܃ú0ō˙ÆÜž;¨Ųņg^=÷Oâ˜ûŪQžžķžkÖŠūzčžš7jd;@ũĸZņBÜQ†EGœ,­kßjâ’ņģŖo]�¸‡ņøâyx?÷wžŋ˜Ŗ67Zuš…q˙ˆãņ¨ķ' bÜß_#īÉyå;ųĢ{úŨû$ĪÄžÄÊļæčY‘+Ū/~üņĮ žĖå\ũJ—O jN+dŊƯ/w1"""""""ŌėÎĖTš}‘ˆˆˆˆˆˆˆČĩDa‹ˆˆˆˆˆˆˆˆ ]‚5[Ž-~÷ƑwīåŽBDDDDDDD.lq!…-"""""""".¤°EDDDDDDDąˆˆˆˆˆˆ¸ÂRØ""""""""âB [DDDDDDDD\Ha‹ˆˆˆˆˆˆˆˆ )lq!…-"""""""".¤°EDDDDDDDą ÍŨ@­ŨAŅá#TU×Pkw4ws"""rq30y¸ci㏛ąŲíš"4ëo=ĩvģ÷î'ĀßWŋd‰ˆˆ\ƒjíĘ*žg÷ŪũtéÔ^ŋ ˆˆˆČ5ĄY§>B€ŋ/ūž-ô˕ˆˆČ5ČÍh ŋ/~ž-(:|är—#"""rI4kØRų˙ĒđminÎ&DDDä*āÛōĒĒk.w""""—Dŗ†-˙ũáŽŋNkđŠˆˆ\ë܌­Ũ&"""× %!"""""""".¤°EDDDDDDDąˆˆˆˆˆˆ¸ÂRØ""""""""âB [DDDDDDDD\Ha‹ˆˆˆˆˆˆˆˆ .wgŗķM܋ŧŸí8íSƒG ,7öaøđhēú\Ėņ ųčÅwŲ1…gF´š¨J¯5š ¯đæūū<=#šÖ—ģŗnúŗ’ŧxđoŖéáĘų‚ŋžšIčįwíĪņŦeĖø šģŸų=ˇxģ˛ņĶšü›rn""""""× +wdKËH&>|?ĶžŸißĮũÃ;aؗÂü÷ž č'(Ÿŋøk˛ŠNš \Æ{Āģ;wNÔE„Æēé_üyEîɟMŖۇ K_ŠˆˆˆˆˆČ5å ŲRĮÃKĮ§FPt Ĩ“ņ(OÄī%įȝąø7ņ8åÅ´ņŗ‰!?ÁåŧLmčŅûrŒ ˛St ˜úãÌūĄôkęŋš`WnØŌƒLõĢļ咒Â†}G)¯oŋ`n6œQ‘~÷­âĪooÃŧô,ĢÂīᕠ-ęvŦ&wí2ūŊv/G|ÚE2fÜē68Ã9ĩi ÃųCH.­- ¤ÚÉ¯ #ÆÄpK ąî{ė\•ÄęĖ"J*ĢÁŖ–đhÆÄöÁrâ+å9|’°–ôÅØĒ ˜ZĐãÖáÄöo‹įyˇ°wé\Ų†3{rOĖ�據ķI­ęÍ´gGZWëÎĨsYT5’9“ēãyžūÁ9úṔVŒ…”øLĒzßĪėQmÁ–Ë'+’Ų˜wģG+Âĸ‡sĶi}REūĻdÖíύĸ‡Á‹ HFÄūš Žâ°ŗ3îE1’isY‘˛—’j0‡D3~L7ĒÖ&ōIfåÕX"†3qLw|­æš˙ÛM?=ĘŨ'Žå<īí7>čŦÕ^Ę7IIŦÎ*âHĨƒW+‚ģ æw#ģĶú@C÷@wŒ •ߡ‰EĢÖŗŗ´Ŗw�Ŋ†Įpw]_’Ŧ>JÎ"ˇ´<ZY×ΚxÖT› v&$ōQfV‡­ÃŖšûÆŗûęüíōīŋ,ĸdđũ ŽHá“ĖbĘĢÁ|ōļąaÁ\Vä,fę7­ú§i ŪÆ4"{;S’XŊ­€’J0ĩé°Q#ÜŅä,Ö_wīS^íĀä@XôpÆ îāŧWEDDDDD¤AWIØbĮzh7ĢR 0EÜsęaŪ^Č' –ŗÎÉøî%ÔÛAŅŽd–,[„Õ0•ûģ įÉ˙Šæ…Ž2ėO÷3ĀĪ„‘B�Ǟ“Iš1šņ ÃXą›–ĻđūęPæŒ mđAÜ8ö­eUËūđÔXĖ”˛yŲ"–.LĻõSŖ5BÉÚå,Ú Qcīãū@/¨ØË'ņIü3ĄŗĮtĀHk—­dŖĄ'ßKkåß­åßIËYá3ƒûoŦ<īö17Z`U.Eöžt5ļ" l^xŠ(8Ąū�Åäp`šÅ‚gŖũcÂ`Ē÷’˛9˜['<ˆĨe+ĀÆæĨËI­č¸‡īŖĢw5’ų$ĢęÂ(ûždŪM, tô}ŒņÂP]ˆU‰,Š3ņdƒkē1ĀņŨzRÚÅ0íŲŅlâŸ˙—ĖĸˇrÓŖü°ī[Íko'ą*ŧ ÷w;W‚qēĸ”åŧŸÕ‚ØqSčŅŌ@UénV­XÉ? -˜=ĒĄ{ā$'ð÷1ĸĨƒüĩ‰ŦXļ“˙4î „ãY‰ŧņÁ^|oáą~+öōI|"oTxz\X“ˆ’ĩËY´ ĸF?ȰŽXŋ[ËĒ”œüNSÚ1\›ĖÎQÎ~3ÚrXōÖō“÷pԄ)T-˜OŠ˙=<Û “ ĒöׯÄNnŌ"eĩbĘéá]Mūú$Vŧˇūôƒũml^ą˜UŨ÷@ ĄŪ`=°ž%ņËYŌrEšštmDDDDDDŽEWnØr8…O9í#īvˇp˙¨°ēQ`ĪÛÄÆŌÜú§QÜT7ęĄëāFdĪåŖ ģąvë‰Ųh� <LxÖ{ĘŽ2õaˍēŅū-vãzæ*ϜĐķL7 fØđPĖF�?ú ‹$å˙ļ‘ž7œĐøôž—Į ´ö÷v>ĐûôaXøz^ĪÎĄ„X8ÆÁ#|†õĄk`K�|ûĮđ‡öÅ8ZĄ‘ížUĄX›Č)…ށ`ߟËA˙Hxl#÷@ÃüMP^@Ž­a7ļĞ—Ōh˙0āp8°D¤_Įē˛e˛1ÂF¤_{įCuQÃÉ˙n>Šu=a=t›‡…¨ˆļuŖ-ZrׄVô¨0pŪåI ßR7ŠĮŋ]ũ`/aŒčí=bėF¨×f J~į;R;%‡ŽA`4QũœĮõéĪÄG,”Ķ 0žķhHč°nšŅųĨÖąÃØ™Ŋœ™‡š+ЃôuYT…Äđāˆē{ЧãGæōü˛õ¤—‡1¸ŅuYŗy[1Æđû¸;˛ FŽ÷HFėÛ˛ߜøNE“Ûąö!öĨī0ĸBŧH?q›<œAšÁŗÉy Ģę—R•EĘļJ‚cd؍ŪuįC•c֊*đ÷ Gė:ЂÖ>ÎūđõĖ-2IūŽ"C;Y‘k֕ļøõæÁq}ęÜØmGŲš~-˙ü{1wOūnņ‡’}ÅTyzÚ3š7AíZāČ,¤„ž'ƒ™3™ÛYœA �FLŪp¨ ûųjōoKPũ‡õ–mđĨš’ōjˆŅʡ%ķŅŌbĘmÕØŽjđpÔ7€7z‘žŧ˜w*úpSx'Â:úáØļl7ĶŠe {÷Û Đ›‚=E˜ÚEs“÷^Ō÷aīŠ=/Ÿī`ÆøCÉļÆûĮšXj+:ĩĢwbG )˛ë^hEh ŠGœ?ųv #(9…%oܯ;a!ÁX|üčĐØ€Ÿ6øœlĘ“ Ū­ę\ÎĪėUŽw?›‘āH ĻødŪ\ZĘ-a„ul‹¯OÛ ˜ępz?m 9ĨGąÛ ä‚ÖÃ,§ŨSžíC r$“{ČÎ`ŸF’ûQJ* uŋVõF× ę�ßԝ¯Ŋ¸ í8?3ûœvŽ“ÜÃ'*¤ČáÅMõæÍÛ2x\ۓ?z+I_Ė’G)Ģrāp8°W-›zmDDDDDDŽMWnØbhIëĀ6õÂÛŌáÆ�˜;ŸU)9D Ã^í�ƒÆ3žqTWŸ÷ĄĶp!gn0œ>Å`Ā`�{u5``WÂģŧ›Ũ‚[ccø]ģ˜ŒP˛ö]ŪĖ<ąƒ‰ŖdÚæõ¤l[Ī’ É8<ZÖo$cF„âÛčö6tíčÁÆ=EØûˇĸ`ŋƒāX ؖO ÁX÷al× PÔÔū1x`ŦßUĒ0`>­Œë/–Í´GZ˛v+›W-fUĩīvŨ;œ[Ī—¸˜ÆsöÅø)ķžŊīã1Í¤lČ`õ˛ ŦpxāÍībŖ ũ)¯R6đ<­tF8ėŽēā &ĪcjōŲģv˛9ˇķr8¨r€áŒ›ĪčaĀpâŒÕMhįdšÎQãŦk\Ŋ,bƒ!’1ąÃ õ÷ĀH%éqķY}͊ˆˆˆˆˆ\ ŽÜ°ĨA­°´4Puä(倧‡ÕØíœöœk¯v€‡Gcž?]•ãô�§îÁØäáöŌwT4ė~îŽ<5”¤ÄqFl`lIhô(BŖGa¯*%wÛZ>J^ÎģŪSx2Ú¯ŅíÁ-”KQy%ģJˆjg:lÛDAy1eŗ`ŧ˜ū10âĀzZévĒĒĒOûšg`wîםģ°Sļ7뒓XąĐĪSŖkĘ4gđqŠ‘ÖŨĸß-ė6ŠžËdUR ˙\áÅėIįŨÔĐqíU€éôŸ >Î0Ęd€ [îįūŪ^gėgĀäŨ„TĮ`Āh€Ē3î {uõŠpÉāŅ„vŽ5ųŒÎÉÃŦUįØ~(ƒoJŊˆúc,ũڟø°’ãU@ˋo^DDDDDäįėēË]ĀOSLAŠƒO ŧŸŽ˜Ē‹É-­˙ ÃØļnŠ ü´qįQ‘OAũ‡ĶC…ᅯŋˇsÔ8§#`Ī%=ĢōTUĨäfåSVˇŲhōŖkôH†…8(9p´ņ퀱}Aļ"vîČå _¨sڎÉB¨˙Qví(`o……uo“iz˙œĄeZSIÉĄú'ë\x÷ëĄvžÜnġ}wî‰OåQV4Š7›ÆāáDrBÕQJl' čģōOülôÆŌ-šģûā(-<Ų—MēÅäǧ؋)¨�ŋ(¯¨ÆĮߏÖ'˙xa0xanĘząÆV´ö†’Gë…vv ž+Ž÷´sÚ9ãs˙X Į(8P˙b’˛āmĢ€jŧđ­×žũСė,=Ī1EDDDDD¸’ÖęRŠöå“[÷gWVŸ,\ÉēŠDŨŌÅųF–høccÂjvĒĀj+e×ÚD’´ jp7įzL#įģBJŽØšļžÅšŠY—°•ürÖ#š|’œ…­e´LÎĩQ 6o"÷Äö¸õØ;Z ú(EGǰ;ŠYŋ˜ŽČ ÷HÖōRōŗÖ˛ņ€ā�hl;€w0=üŽ’žžC{KŨ4Ģ–ˇķ `ũ&ŠüBONiR˙4ħ Qí '%‰ ûJ);RČ7 )랟 ’Ŧ™),Z¸’”ŦԕÛ(;’Άuģ)÷˛ėʑ-°xT’ŗ-+8_ķŧj+E'ĮdU“›˛’ųqĢųf)eļ Jög˛í(ĻĀēŎÂ=“’Ė7u}ĩ3i-;ôëŨhIÔ­]0d'ŗh}>%ļēs^ú./ŧ•ČŽs9Mĸ"ZQ•ĖG™…””—’ģ)‘ä†zCĖ\Ņ€s„LՑ\v*ĨėĖũL]Ņ‚‚”D>É*¤čP>›’X}�‚Ûĩ„ĀļŠŲŧ!‡›’}›X”p KˆGy!ųUõ/IDDDDDägíʝFT‘ÉûoŸ\ėƒG |ƒ‰}xƒOŧ5Į؆ģ&ߋgB ˙^°[ĩī6đ?÷s׉īöá֐ŨŦJ|—×BFōÄä€k"˙ūŒēą„ˇS(Ēp`jĶq†ĶĄîíDˇŽNūŌĩüķĨ [Ķkx ã;a?HÂ[Ë1ūé÷Œ ’Iky÷˙ŽQå0`j@Øāû¸ģK e#ÛZÚŅ‹„ •tīl9Yš%ĂcCæČāSëÜ4ĨԒ[ÆŪCI|2ĢŪ›ĮG†V„öÎŨŊ“y7ÛųÔnv÷;’Xđ.Ģ*`đÂ?¤ ã&'ԕSˆŒĄÜ=ē7‹’VōܟÁÔ2€›†cXébÖÕÕ:xÂŊ8RXõŪ6ĘĢŧZ|ãpόĒ{ķY÷@Ÿz‹#;Ų ^5ĖoVŧ˒ÃÕ[ZˆÃ0įw<ģÅ0}t ĨŦäĨ¤J/üCē1~ō0ē6qĉeØŊŒŗ%’˙.ņ"(<š1# ŧģėÔÔ W´&ēŪŌ˙øÍĖ+‹<ΈĶ;–ĐØû˜hJfuÂ"R+ÁģM'F<0’Áū�‘ü.ļEĢWōÂfđn׍Qą1ô°%Sˇ7ĀcãšZ‹ˆˆˆˆˆČĩå?ūøã�ģôāŲšô ˙9ŧ"ÖÎθyˇj$/Oîyoš‘ŸĪī""""§;3Sšr§‰ˆˆˆˆˆˆˆ\…ˆˆˆˆˆˆ¸Đ•ģfËÅH Ī2īr—!"""""""W<lq!…-"""""""".¤°EDDDDDDDąˆˆˆˆˆˆ¸ÂRØ""""""""âBÍļ\wŨuü÷‡šŗ š ÔÚ¸ —ģ ‘KĸYÃī_š8r´ĸ9›‘Ģ@YÅ÷˜<Ü/w""""—Dŗ†-–6ū);Fqi9ĩvGs6%"""W ZģƒâŌrŽ”ÃŌÆ˙r—#"""rI4ëx^7Ŗ°Ní):|„˛Šī¸ˆˆˆ\c܌Lî„uj¯iD"""rÍhößz܌BÚ6w3""""""""WŊHDDDDDDDąˆˆˆˆˆˆ¸ÂRØ""""""""âB [DDDDDDDD\Ha‹ˆˆˆˆˆˆˆˆ )lq!…-"""""""".¤°EDDDDDDDąˆˆˆˆˆˆ¸ÂRØ""""""""âB [DDDDDDDD\Ha‹ˆˆˆˆˆˆˆˆ )lq!CsüĐĄCÍyxšJüâŋāĮŧÜeˆČ 00đr— """Ōėš5lŅ/T"""""""r­Ņ4"RØ""""""""âB [DDDDDDDD\Ha‹ˆˆˆˆˆˆˆˆ )lq!…-"""""""".¤°EDDDDDDDąˆˆˆˆˆˆ¸ÂRØ""""""""âB [DDDDDDDD\Ha‹ˆˆˆˆˆˆˆˆ )lq!…-"""""""".¤°EDDDDDDDą ÍŨĀ˙sü—oËŦÔūđ_ūûcsˇ&"""W’ëî×_Oˇ–f~iŧūr—#"""rI4kØrÜņ_ļŠĀĶhĀlpĶ0‘kĖ@ĩãŋl-­ ¯K< \DDDäį¯YķŦr+žF×]§ EDDätāi¸ë¯'ĢÜzšËš$š59îø/î×)fšÖyާúŋ˙ŊÜeˆˆˆˆ\͚„üüĸ9‘ĢÂu ĩÛDDD䚡a'"""""""".¤°EDDDDDDDąˆˆˆˆˆˆ¸ÂRØ""""""""âB [DDDDDDDD\Ha‹ˆˆˆˆˆˆˆˆ )lq!…-"""""""".¤°EDDDDDDDąˆˆˆˆˆˆ¸ÂRØ""""""""âBW]ØRļu!ˇM~š˛7°ÕÎö¸Ų ˜ąÔī/yir>Å̏wōĶĖÍģ܅€mĶ|<˛Tģë]–ĀC?ƀGæ0ŋ°){ä2÷ņéLøŦÄšÚ<<ō>iŽ/í’_DDDDDDŽÂ°ÅˇO “;;HŽ˙ŒŦ3–k “™ģÉJΘ†Ūpyęk>vŌæ?ÉŖ[ ™~ϊS™đ\Yy˜˛´yŒŒËqIIįg%íķ¯Č5āĩ'&1:ā4ųyuĘŦqŊ܅¸čڊˆˆˆˆˆ\‰Žē°|‰{;áey3­¤Ūįe|ŧė+Š-Ù9Đ÷˛U×|JČ(j†ĄW0[a.{;šųŠuEAĒĸō8xučNŋļ­ņ5^’F÷€0FôĨÍeŽÃ5×VDDDDDäĘd¸Ü\€L˛‘‡˙“ĀęŪSq”mMä_ų>üæņA´ĀNîĻŪøė[˛Kl`ō!4|“īš•^7�äđŒ¸ķYŪr*œŲ÷$S oįçomđ´,mŋũ?ŗ'ųōņŌud”UáæÆäIcéUœÄœ3Čũ|Bú2ķXúŨ�&p÷œo8ë9ώ=q¤"æ?7—Ôđ|4:8NVZ"o~ū-šeUÔŧéÔš/ŒÅ@ߝ<ųČ{Ŧw�ī=ŀe}™÷÷qôj >[ŪFŪX™ĘúÂrjū„÷ÎĖŅŊho<ą}oŦüŠõ…åTbĸ]HwFßCL[Ī‹8ŋUÜûōˇô›4œÚ5I¤æYŠ5š īÃėŅ=h8ú˛ŗ?3‰7ˇ“QbĶéĩî˙lc?>ėæáÉ_>öYŪ苭0+SIÍ;B%Ū´ëܗéãFҝÁFŦ$žū ¯îXĀ€MūŒŸ5‹ņu[k‹ˇ3wYĢķËÁDôc™5Ђ{ę;ûtļķäŒ8į5*z‹ë}ũøsLo[FZb<īmÛË+¸™[Ķŗ˙HĻĮ„5-đ°Ÿo˙æÎú;YŅ&îöÖÎīŋ‘Gžˆ';â>›ŌŖî\ ˜ûø<roû3/—1*Ū›ߚČĀēĪ÷ß6•ŅeIĖßvâãg\[€īs˜—ĀĮ{ĘŠõlMÔm1ŒļÆķXfwŪ™=ĒáQ2e;™ŋô3Vį¤ŧʀ—oҎÅ2}`0åį¸ļ/`ŞâŗR\>–îŒ˘Îfį1›rŸŲKHįŊ°:p3ûQīēŲˇķčŒ8Ęî€Ak�� �IDATŦ×g"""""".vŽl0ŌíÎŅ ÷ÜÍüs°ĪaūĘoņęËīCœOÂe[ãx$n7^'đūœŋ˛lęHÚ&ņØŧTö_DËnF˙–÷ŌLLyâÖū}*ŋ1~ËĢķūÎÜ==xqÎKŦ3–đ⯘ûyA“[ŗ'‘§–í%āÎÉŧ?įY>|b4ũ_ķÜüTö̓غGôû,ŸÍŨ`ĐBŲFžz=\ËíŧøÄŸygRn;âxdY6€âu<özŲˇķÚĶeÕĶ0Úŧ—W_cuŲEœŸp”ŗ:q;î™ÉįoŊÆĒ)ŨąĨÅņTZYƒįkË\Ę# ļP9šwæ<ËûSnÅk×R‰Û‰ h?dķĸ}Ā÷fæŊü"ķúûBY:ĪŊžŒ4CŗŸ~–ŸĮĮžz3áŦ)eNfFLžÉd xõžĀĒ×gđûa—ã ‹Wî¤Ũđūķ3˜nįËeņ|\Ö´úÎbėÅėWf0ē5øôŸĖĒ×g29ä8›—ŊÅS›ô›0ƒe/˙™ˇÆvĸ<mÆPĶč]ŅØūADw0q`OÁɚjōsČ5{ãU”Cî‰Ãī%ë¸=;ŸHšä~ž@Z‡Ņŧ˙ŠķÚö,ûŠį>ΊĢĪJâ{ YRŌšÉ>βGcčš'‘šÛlÔ=qk°î2V,ŒãcGf=ú,Îyœoķ!;~!sŗí _[JXüú<ú0~Ęã|8g3;—ķ¯7°¸øDĶč}–ûŸ…<ˇÃĀo&=·sžå pÛĮŖ‰uņĻgxē˜í}‘ u•†-€1”é÷ô„Mņ<ˇ0/éÎô{Âđ „ÕŸKmÄHf ĨŊ¯™6!Ŋ˜9Ž^EëˆŋČEZk&úŨ6€POĀLtg8îȘ0į˙ŽßÆĐŠ‹ŌpÔpļōÂĘMíŅ;˜öžž´iۃ)“g0oBO�wŗ āf2áíŲđü”Ŧ/Sɸa�3ĮEŅĢmkBÇ2ķž› ĮJ9•öŲžŊ˜96Šnf|B‰;’hûnVl=5%ëÂÎĪA@˙‘'GČxwžû:CöĻo9|VĨeŦū<ƒĘÎ1ŧ̓P__ÚwĀŦ{¨ÍLu?FOŧ=Œ¸Ũā‰ģrĶRI§'3' Ĩ_€/m¸īá„—mdIfÃkŲ¸{šp3Fžžžu#=�ė„ß9Ž1áÚ3â΄sėB{Ķę;G[>FĀ`pļõũˇÄo-'üÎąL ˇĐæ_B#c™9П›Ö‘ŪØŦ°F÷7Ņō÷’]wŦėė|z÷Ĩ§}?u!…-?—žˆjÛp3ĩ–LīĶÚŲ77ô`Dgo* R Pö-É{ ęÎŅĄ´ĻM@(c&$Ü^užÂËÉ-v1€~m}iãۚ^Įņú“x ƒąÁk[“Ę’"3#&Œ#ĻskÚøZ8zã˙ynŊ`ę|÷™ũ…å`éňέiãëKhø­Ė~b/ ņwîn åž)S˜in¤ķEDDDDD.ÜÕļ�Ū‘ąLéZEú.á11 ôŦÛ`/!ģÚun_ž8š[:ŠũE֋kØāChŊÅOŊđôĄŨ‰ö1âæiģŊÉk…´é܃Nö æŧž”Å›rČ-ŗƒgkēĩõ­œ•ÜĸrÜZ×MŖĒ;nŸ1ŧ4!ŠöuÛąt"´~VãDx�Č/9õ@{Aįg"´mũiž´0CŲÁŗÃûA2 Ą]×āĶĻyw#ÔqŒÂ†R+šųG m==ë}|C'ĸ|dī9xžži€!ˆ~–za2áTÚ/´ž”ëđ!ŧÃé#JÚu­ę Ų%qMØß;$ŒP{A]°RBú@ŋ€r2ōv˛ŗ÷ãÖ9Œđs4ãtÚŋ7“ėĮ×ļŦ€ũøjŠN;1°Ãųf 10ܛŊ‰ x2~Š{J°a¤MÛ`Ú{6ŧGqáA* í‰j[˙ælMxoĘ ÷S|ōŗķŨgFÂûãĩ+‘GŽ"1ŗ€ÃĮÁÛ7˜Pßs4,"""""Ō ŽÎ5[N2Õ'vÁŽõHíUT:ĀÛxÆŖ 7”Ų/vĄŲ3ŽkÎhĢáéįŅv(o=áÒĪ7˛:~ Ē øtčÅīĮžZOåüėÔ| §=8Ÿâ\ŧÕÍ×tFm&ŧ<ĄÖ^U/8šķ3ā~æ€ŖŽ78Ų̍uĀŪ˙ƀĪŽ´§ĩęė°c;-ãöÉËÎÚĮ- Šhb0…ķøįZĀö‚ękĀņ**1āsÆås7šđÂáŧ^ģH'zú&‘oķ^˛­AŒčāKxoæīŲOMoŌķí„ßŲ w ˛fÜÎw*ĮíÔb<Ŗ#^fįļåÉĀ 3˜×!•%›R™ŗ&‘Z“QG3ķkÕØŦUāČāŠŠgo4—Qu!âųīŗ6ũ'ķŽé+¯Iį_ ŋäU‡‰vC™9vhŨZM""""""Íī*[ÎÁhÂË�‡WõūGū\!L=ĩ—ė…?jĪx‹wÛ^L™Ô‹)Ø9œ÷-ņĮķę<;mæL¤_ŖĮ3â]𨠁ĀåôPåT(qzsáoíq`;ķUÜvxžîpōútōéæt^æ†Ļx8ĪÎ1ŧ?6ėŦcē™|~BĐԈ ǝžÎP¤üŒPĨæx•˜Î Q.l QM|ŧ§€2sšžĄ„{Bûđ`XšË23Ųß1¤ķŽė0qÃîŦádži§ŌzžiD€Ņ—^CÆĐkČjŽ—ą)™7ō¤ųq↜Ŋ0­ˇŲĻîĖzbäY#p܌&|NūÔØ}f¤}äPžŽ v+šŲ[˜ŋ2‰ĮâŧųpZÔ9kq­ĢzŅ9ƒč�ö<m1͚üŊõĻD8˙‡Üf­Ÿx”ąŋ¸‘‡Č ĒĮŒvjëúx ûŋ¯×ráNŌ O<UiŌ‹éŋéK€õšõGœķušfB->Ptjũ€˛­KyčåUlˇ7ŧãÉ(†ĐÎAVT‘짍ŪĪVr‹Ŧāk9mZ�Æ ÂÛ(.Ģ"  5íOūņÆÍhĻáfB;øCY 5žõ÷ņÁËhÂį†FFšü”í‚ęk€%˜PC9Ųų§9°į ĩĻ B{ōoâūáᝠ?‡Õ;âÕĄ“ŗŋ-a„Z÷˛ykû};ŅëBS†€ ('ˇ¸^âcßKZūyŪÛ|ŧ„홚'§š{ļĻߐŅÜ×ŲÁü’w h„OU9eÔīīÖ˜œáVũpđÜ÷Ųqrŗw’uâߕŅLhäPĻGQ[RĐĀÚA""""""ÍãįļāËĐÛēãļ+‰9išė˙ŪĘáŧtæÆo§ļķPF‡�´&Übā@v:YĮŽ“ûe"Ģ­Í0ØĮ7ˆP“Í›rœ3/ė%¤ŽÜHnŊ| |[Ī͋cqf‡ËŦ.Î%ņķo)6ˇ'ÜĀ€°Īnr‹Ë|#NčA„ßÎqŲ^XDVf*sVná°o(ĄFč6d(=oįeéd•Y)+Î%1.‰tĪîŒî}ą˙įo 23‰72K(ûžŒŦM ,Ų=vo`4/#nëŽ[f"Ī}Yw}ŠsI\øwÆžŧ”ÍuĪõn&|_@ƞöo'tāPzßœeéd[)++bķg ™øÜßYwŽ4ńˇ*‹sØ\XÂáÆĻîü„úåŲņũ}ČūĪ2g—pøû2r3x!­œNŅŗą™HMÜßŊsáßK|vĄáAuûļ§§īAâ×ėĮĢs†_ĪܤŽc„Ō˙“HjaeÅšŦX˜Lļņ<oķą$>nÆĨŗŊ¸Œ˛˛˛2?ãã|᝝õymŨÃ1ÚR’¸RķĘ(ûžŒÜĖU<öüßxäËúÍųîŗ*ļ˙'ŽĮ$šįė¯ũyé,ŪT‚—%ĖBŲ X<!od^äēM""""""įņķœFøö™Ā[ŽŪøė=&.Ģ“ᑱŧvĪ€ē5#Ė =ší “˜úøW¸yúŪ?–)ũKx*ÛqĶi` cú„›yneŋ}ŧ|ƒ3’ņÅ ˆ¯ûJ蝓™mįŊegÕoÚuîÎŦGcéeã7CړūyewgÖķ“zæ ß[yíQ˜ģ2•§^.§ÖčCxīąŧ5ēî-MžxņQ;sW~ÆcĪ-ŖgŗečE¯gafāˇâ– Piô§įm“˜uÖ4'īČqŧ5!‰7ūĮĕ6j Ū´ë܋YŽ¤_Ũyĩī3ˆčõņ,xũRo›A\L/> VĻōØ ËœSi:1bŌ#'_ų}6Oĸ~u3íâžâą—ˇķ›i/0š gĶ”úįI¯Ņđĸ1žųq¯°ĀęŧöQwNfúí–&Œ$jâūžˆjk#=ŋQNôƒ/=;˜X°ŪÁČ Ļ܀֌™2Ž “˜3į/`nOôąL1/äŠüsėrC/fMĩ2wåg<õB9•ƒŗîÛ&3} 3z;ûÚZ¸īŅŠ¸-K`ūŧW:×Ų§M;:˙}6fō$j—%ÕÁ›ŲŸđđX^ģ§GŨÔēr2˛ŋĨŦÃHN›b("""""âBŋøņĮ((( 88ØĨ_{č(žî?yŠXšÚ¯âŪįˇĐëņ˜rš‹—ŗĮ†'Ū'ķŦ㤞ų sĮĒiŊÎą s3Đ}&rÕ+ĢŠep`ĢË]†ˆˆˆˆË™ŠüL§‰ˆk”‘øú3Œza)ĢķJ8\VÄö´xæī2ÕŋËĨ ZDDDDDDŽ"?ÛiD"â žÄLžDå˛$æĪûåUŧ|ƒ‰ûĶû\āŽDDDDDD~æ4HDDD. M#‘Ÿ+M#iF [DDDDDDDD\Ha‹ˆˆˆˆˆˆˆˆ )lq!…-"""""""".¤°EDDDDDDDąˆˆˆˆˆˆ¸Â2\î~~ė¤Î’įv8Îų ˇˆølJÜ/aUr™ ŧ Ši/O%æ†Ë]Œˆˆˆˆˆˆü(li.ž}™5Ą/ m3ũ„ %—šŗ’h÷誌ņuYu"""""""ŌLļ4SkÂ;‡ŌūbSvÜīĄ+j‘f§°år*Ne éô›‹×g Äį—S‰™ĐČ‘ĖšĐ‹ö{V0ōõ¯)˛gMgAÄ>›Ō wŦlū,žųkörĀjĮÍ7ˆčÛF3} o�r™ûøöß6‰čėxėņgæ+SáyFû…̏÷åoé7i8ĩk’HÍŗRk4Ū?†ŲŖ{āHcg˙Ļæ~ö-Ų%6j &څô䁹ą 0:c˙˙íŨ\•õÁ˙ņ—ur rTŧ…`dĸK…ŧcf.Ą[ØŦ›Ū…÷jZͲŲwÚîēĨÚ ĶMÜ´ļtF÷”´›¨™7ÚJŦnÁŒ0q‚Š€LsĖī×Qųuę(hīįãácqŽëú|>×u}Žîzķų|Ž#lÎÉaõŽ Ö;đđBô¨Û™Ÿ<†s×ÛŊ?^Ær|xúųTâÅí\ķŋg!mņBîw ĒŲžŒäøŗäų{‰Ĩ†mrXũņ>փ‡o�Ŗãîdî´(ģ<˙ļ­YËŌ+¨ĩ[3™šŅ—ö‹ˆˆˆˆˆČ׏–.TÕÔōVá‡�Ü;Ž@ĢŽoąÛ9ŨūCŗší"G-y9;øIĘ#l öĸa˙zx~ K#"y1n:YiMĖĖ<Ę} "ÉæEė|šŗŒĮļ™IJI#}¸5{ķyvÍr™įķbœ0ãavPĩí-vÄMcŲ´�BÚ-�fgũv’6{>ķƒŊhØģž‡^ÎæIÛ/ø}ŧöoâŅė"§¤đû¸!xۏ˛yí°ōt"á@Ų›ĢX´ËŸŸÍ~œx̉ÆęŨŦČÎæQŗ?Iízû­#fßJŅ!ˆPɎŊāīÛĀŽōzîˇųvJJCÄĸͧ(Ė^ΓÅūÜ7ûԃ,4–oå…ėLĩ?BVr(ũ]œ˙üÕ,Ú )p_„…šĪōÉ|s- íŅ}éŠÂŠöîcÁŠU465đÚæŌįĖftĈîPš‰Ô‡7u˛ÁŸä…‹˜|îgqw2-ØHC|†#>`+yûB\(Vŗ0áá兏8ĩ›WļeÄŋ`ūØ��㒙ŋįŋOYœ€�T{aųÔœŖ=\q`k]ÄTîxŸEÛwS?‘ĀāÉŧ¸pūÁÎQ3Vîŋ5ŠW—ícg „[í8T A“IˆpîcČĶO„RÍā"Û­&Æ ØDIy ˇÂÉ”Ô%!ž‰Ü’ÜŽķĨ?‡)*ˇ=e>'w“ķQ-ŅĶbN´s›˜éĖ//%u[;Ļ]!Ķöü+yuûa<bŌøŲØ ú;¯ÛOö–ōđöîŨR‘îPØâBFÎúķA @cS9ëÉ~ęņîpO§Œī¸@ŽŲB`›- ŗYÛüėo†‡ŊķrĢË(sø39" uĄDGÅãŊ|zoÕą…^$h1ę n]–!6_øø0U@ ŲÕŧđúĘjęi´;hąÛi$€ģŗîąĄxgoāĄUGHēņb#B ´†:Ã.˛ŨĖÍr÷ áV+åĨ”Ųĸ˜}„ÍŲĨ”Åõ5ė<Āä_8RaœXÛՂ‡E ÅãíÔÔ@ŧ­“ķˇå@ „Äi5˛ČLxÄPØîúÍQ"""""""=Ĩ°Å…}•‡ģõ™Kf+áÃCģą@ŽÉ˜ÎĶ]MM´PKÎķsÉé°qM€3lņđęÎí5Ņŋ}ũfœ˛ĶÔl_ÅŲ‡‰Nē—_Šŋ (_ĪĖĖŖįwŒKã÷–­üiË^Yõ.ŋvX6j2ķgNfĖ€‹m7ū­P<^/ŖŒ1PRwØdBÂü ?ĩ™’Ų_ĘÁ#mĒ›hĄģiQũÍŧqĐręÂgmÎßn§Ņæļ'ëa1ãÂq…-.üë¨ëųÛŽO;|Öë,<đįö´´ķ‹Įžg6áßã×C;hh7ˆæ´Ũ^<¨gĮö=4FĖdáÔ(Î}-m+&$f2OÅL{=e%˛âõM<–íÃ˙l<֋mˆ"üÔû”TWBšƒč;†‚Ų‡ņļŖėØ_OČg‡ņŽ˜hLō2B•ÚSíÚ|ljF,B˜ M4ãa‚F{ۓmijjw."""""""_Í5ŊŨ€žjö ø<˙s€˙@fߙĐK­i5ōÂN´Šžę& !ļ€ |MxXŦį§ît_e{+[ũ\OYe=XƒÁNƒ<|-xˇÚūŪö}­ŠS”•|§'?š} ™Ėܛ‡Ōr¤‚Ē‹nŒ`ŧíEÅ{ØQ=”Ø03`åú0J>ÛɎũĸŋbL˙ %ÜTkŦņŌĘÁŊ‡ią %ÜUØdBČ�8P~´ÕĸÅ΅wEDDDDDDÜH#[\JöSOP´w�Ŗ#Fāãeé~MG())5„v<Ėū„Ÿ[,öb,&ŧŠeGIąūØl#IŽķåą ŲŦ°L#)Ė—–šRrÖæ°Ų7™ĩ?OΎ˜h,ŪÄŌ°éÜfĸęŗMŧēF§ŒÄŠ/áA>´|ü>šûC™< žĸw7kÁ0öQr¨†+ė|3›W˜Āüģ'm5ŅRŗ?m?‚wĐTBh"¯Ëí�ŒŽđåÕ-`›H´s԰襴doeķŠĄĖ‰pYņÉ}qū<üæZūdKfr‰Æō­<ŗ­–ˇĻ0Úå”Ŧ nBÎģëyųŖ{InĄĻä-^)7áŅŖë%""""""Ō5…-]đņ˛3ōË\ķ!Ī.û°ķmĻŅ,YžJ|wĘ ž@rÄn2מÄÉd=:1÷>į ŦX윜ú&° !:f:ŋšģ§A €/ņwLÄcÛ*RWĨŅ<„ŅSfŗ0΀1ĶR¸¯f-¯<˙˙Č´ atütžNņg[ũ2^^ũĪÎú%éiŗiYģ‰ËŪ§ēɁ‡īĸŖ§ķ›ģoĀ˜q‘í�áŅ#`ˇxŒ 9ŋÎM˙ (ÂOQd›p>€/Æ$?Äs+˛Ÿ'ŗŧ­CGs§ĩ}­v;áwĖfaũVgŋD.>Œˆ™ĖüģÍ<šĒļĮWMDDDDDDĕ~gĪž= PQQAhh¨[ ˙ßĮÚ_ãúŦęüpņ‡ŒyüæīíÆˆˆČÕŽæt ˇ|sPo7CDDDÄíÚg*ZŗEDDDDDDDčˆˆˆˆˆˆ¸‘Ölų:ŗ%ōįĖÄŪn…ˆˆˆˆˆˆČUE#[DDDDDDDDÜHa‹ˆˆˆˆˆˆˆˆ)lq#…-""""""""n¤°EDDDDDDDčˆˆˆˆˆˆ¸‘Â7RØ""""""""âFĻŪnĀÕĮÎæ X´ËŅa‹‡īĸŖ'’v÷Ž÷roÛV,āÉĻdō;‹žė*Xēđ% oœĪŸ§õvczätMšų›Éûė�kšĀâƒ-x “n'9&€ūÎũ W=Æcwė­Nų–Įá™G–ķVSÛm†EŒdÖ´;‰ˇ™/ÍɈˆˆˆˆˆČ—ϰåRąŽãé”qøŸ˙ĀAíĄüéÍŽļ“õÄDBÜV™™đ[“YhÅŖ›GÔl[Fjųd6ĨDš­_g§÷oæáe›(ķA¤i˞ųâqĒ–đJæķäÅÍfyJV úŽ4–ÅŸ;ō(šĢrxĪv'Kî9˙||#�øJæé[‡œ¯ĢąĻ”ŧ7ˇōä‹ĩ,Y8›ø—ņDEDDDDDäĸļ\*–�Â#ÂÛ*QŒ÷mâĢ7“ŗ"ķ‡ģ¯ēˆņv{o;eå‡iq_õ_oö2^^Ŋ‰2ë­,{4ąÍ¨ĨØąãHږÉkŗy6âŧį‹-œ1ļs{XØaëPb#Â;-ŪÃ:”1Ą­> '>Ė‹”Å›xuW ņņÖKtb""""""ōe(lšĖ|‚CąQJM†S@í ķõÍlŪ”F|1Žš÷&{ūÚNŲö^xs'e'ÍøĮÜ쇒÷\ĖN'=†͈jönfé†÷Ųq¨–F,؂#Iš6û#`Ë˙_īČdÂö!ܡp!s‚í(ŪÄŌ ;):Ō�–!Dßx;ķ“Įb6Ú°mÅ‘ĖÛû<ûîQFĪIįWŅĪŅuŨžÎĶŠaۛ9ŦŪžƒõā4’û’“™ŅznU;ķWņÂÛĨ´›ą ĮüY͉=?ŠŖžÂüVlŲĮÁz;ÖĄÜ<%™šņAÆ4Ēęͤ<ŗƒØ9wŌōöōö×ĶâĀ­w§0×ļÖnfĮĄz°Žāž”î~Žî‹]‡ŽŠ7“WãOŌãˇw2=ĖLH|2÷mûO2ßũq“Ũ3ĸÉB¸ŠĒ�…-""""""}‰ČíBUM-¯üO>¯üO>U5ĩn)ķtÍją`ā|r¯ŲÁĸײÍ4ž§Ÿú%˙ũÔŊ$8>äɗ×ķŠŨyĖŪõ,ČŪ ŖRøũĶķyúfČÉΧÄĐIpj'/Ŧx‹AĶXūô/ųī§Ō˜t”W^^CŪI_Ōæ“Ū7ϰņÅGøI04¯áĄĖiŒIæ÷Īū’Ŧ9ņūl eBF=fhŠÜĖĢ5cXøčCĖ ëäģŦĀÎΜå,Ú“S!kas‚“ųr&¯U_(Ļąx¯ÖaᏓ5g2‡ļ˛(ˇ”Ķe|šŗŒĮŪŦåúģĶXûė/øÍ”ä,gŅöš …8jÉ{ŗ”č”_đöō˙`Itoegō†’^ÄÛËÁ\ë2×~Āį!ŋ•|v€ßHnîjũ”�âo •Ĩ”œrąKOŦĨÚVë•ŊBˆˆˆˆˆČÕH#[\(Úģ+VŅØdŦNúÚæŌįĖftĈ/Yĸšũ;Yšŗ“Ú€īpģ3¨(Ûļ™ŒfÉėÉÄzXšÖíėX¸žW‹§’>֋’íģŠļŽãéä°Nga}?xŊžķĒj*9ĐäKlÜ „[2īM#$Ž/čoļāaĖŦ^^@ šoŅ1“%Ķn0ÆIX'°đîR~°j3y570ãÜ≓ū$͜člkĪëæÔN^Ũ^OôŊp´1Ō%dæŊ4Ø ¨­9Îé5^˜Ÿ<ƘeķįžčÍ<vč0ÕDrj7¯l;ʈ;~Áüą�Æ%3˙>Ο˛¸Dã:á 0îv&[̀™ØCđØž‡đ[ogŒķZ 9°Cˆš†ŧî^‡ķNQ{˛ |­]Ž/ņˇúāA-ꁞ.Žl‡Ķā\`×NCM›sō)2āį1Õ""""""Ō×(lq!#gũų  ąŠ‰Œœõd?õx÷ ¨ÜÄĖ´Mí>4a‹˜Ā’”DŽ7ÔSV~‚'3ēõø€Œˇ:ČŲ{Æp ē PZ¯č=‚a¯îŧn[ąÖwÉ]ŊŒ–I¸9bŖƒ} îÛųūöÂaw„ļ |ÂĸwŦ§čV፠[8á]…Ģģŧ‚2‡/“ƒZĩÅĘŒŲŠmŠņ iĩo_ :eŦ3S]F™ÃŸÉ­Ž0=÷đéIœ×ĘÂ0këz,xãO¸õÂo‹ ėFš=š­twQâĖxwsßÖĒß{‰IīĩĢĶIڜĻ)kés¯˛cŅŲg.ÜĒŲœƒ[˛YTÂÜ´éğ+ė4œ*×25mm‡u4‘��•IDAT"<lMœĻ‰F;x˜-į_ €Ĩ‹‘æpæ>ūÃŪŪĖ[[rČ}Ŋ ë’’ųŲØ€ļå�؛hqĀžÜ˙dBnĮâF×7q~ē’ŲŌu`pąēíM´`Â˙"o,öčj{S-Ԓķü\r:lBãųŒĖÔé,+—zrÎķÂßęåG¨˛ãr]—ÚĘZ0ųcu‘wuÅ˙ƙ,™2ôü}ķđōĮfõęxEDDDDD¤OPØâÂŋŽēžŋíú´ÃgŨfļt~1Ôđģ§ą­d5K_Īč”(cWĖøxĶȚÕa„„‡ÅŸūÔkĨØí­Ļ’�MM4€ëāc@(Ķ’g3-ĒËxīŨ ,]‰‡usÛŋÉlÁÛ#n}ˆ_ÅĩOLxûö0!čĒn‹Ô~•ĩK,<đįö´4îˇĩÛf6áoĒ;;đ"žäuÅûŊR6—Û‰č,mŠĄ°ä(Áڎ`ę&ß�Žęų""""""Ō+´@Ž ŗīL ĀāųŸü2û΄/_ × Ė™Iãö2÷:WžÅ—đ°!Ps„ĶÖ�Blįūøãmļā?Ā øbĩĐR]ÁÁVÅU•|ŌæįÖN×T°­äˆs1Y𱅓|'ãMõ”i•rœk†y(ŅÁ&Ēkš°ŲZˇÃŗ/Ö­ÛN¸Š–’ōV ŲRÁŸ^|‘_ļ^Üļ+ļpĸMõT7YZĩ5€_+_zÉØ/y|ĸ'“`māŨ5ëŲŲ!D˛s`[¯Túpķ”q=x=ˇˆˆˆˆˆˆ\Šļ¸4”ė§ž`IÚl–¤Í&ûŠ'ú•Ę Œ›ÎOÂęÉ]ŗéü›†Âã'3úԇ<ģvŸV×SSSIaū*RŊDæ~;`&:.˙#°4ŋŒĒ“5”}´žgˇ7áīĒĸę÷yaE&‹Ū-ĨŦĻžĒšJ ß- ĸƒŧ� >fhŦ.ĨđĐĒNYI˜2â ,zˇŒ'ëŠĒ.cÃĒ—˜ųÜ {2 åbu{äžąū”ŧš–Å”*#oM¯ė‡č°n.@â5’ä8_J6dŗĸ¸’Ē“õØŋƒ^üORWí ›‘M'žäu0‡’6ëNÂë?āąÅËxáŨl+)ĨđŖ÷YąâyXģÛ¤æĮ|‰a-""""""rÅŅ4ĸ.øxYˆéÆHē{šĪŋĪ īŽ#{jXĮŗäQČ|}3=ŗ–F,øÛF0û!~â|•°Ot2K’ÖōėۙüāM3Ã"nbîˉŧúüúNgíĖ‹÷ŽgiūxŊ“›m ŗĶøI0€ã'Ũİė­<öÜN’~ö ķcîeyĘ&–ž™Męë ´˜|1†…ŪéúÍC_Ēn3cfĻņ´e=Ģ×.ãÕzđŠâ'?KfFû)A.™sīC,ņÚŠĩËÉŠoËĸcĻķ›ģĮwųV ‹ņų’ץ˙đÉ,{*„ÜüÍämÉ!¯Ļ ,>؂Ŗ¸īá4’Ŗ­ZcEDDDDDäkĸßŲŗgĪTTTęÖÂ˙÷ĮąöīîģZ¤+§O¯V‹ĸî_Ī÷ŸßMüÂEĖ î͖‰ˆˆtOÍénųæ Ūn†ˆˆˆˆÛĩĪT4˛å pzī~đb)áI÷2gė<N&÷õŠ ¸‰-"""""""}ŠÂ–+@˙ˆd^LYĪŌülČmĸÅäÈč›xzv"áŊŨ8iCaËÁLxÜ –ĮÍč톈ˆˆˆˆˆˆČEčmD""""""""n¤°EDDDDDDDčˆˆˆˆˆˆ¸‘Â7RØ""""""""âF [DDDDDDDDÜHa‹ˆˆˆˆˆˆˆˆ)lq#…-""""""""n¤°EDDDDDDDčˆˆˆˆˆˆ¸‘Â7RØ""""""""âF [DDDDDDDDÜHa‹ˆˆˆˆˆˆˆˆ)lq#…-""""""""n¤°EDDDDDDDčˆˆˆˆˆˆ¸‘Â7RØ""""""""âF [DDDDDDDDÜHa‹ˆˆˆˆˆˆˆˆ)lq#…-""""""""n¤°EDDDDDDDčˆˆˆˆˆˆ¸‘Â7ēäaËŲK]ˆˆˆôy_�×ôëíVˆˆˆˆ\—4lņ2]K“ãĖĨŦBDDDŽ�͎3XŽŊ!"""rY\Ō°åúž4Ÿ9CĶ™3|q)+‘>é  éĖšĪœázßŪnŽˆˆˆČeaē”…Ã|-cäĶõœliá Í)ųZšĻx^s-c‡ Äˤ‘-"""ōõpIÃ0—ņC^ęjDDDDDDDDúŊHDDDDDDDčˆˆˆˆˆˆ¸‘Â7RØ""""""""âF [DDDDDDDDÜHa‹ˆˆˆˆˆˆˆˆ)lq#…-""""""""n¤°EDDDDDDDčˆˆˆˆˆˆ¸‘Â7RØ""""""""âF [DDDDDDDDÜHa‹ˆˆˆˆˆˆˆˆ)lq#ĶĨŽāīyåOëhüį)žøâ‹K]]¯¸æškđū†?š˙ū%t˜Ëũ>: ‰‚c˙„3g/cEž‚kûÁāoĀ÷Á¸ ×ûŠËåĐŨū(""""Ō›.éȖŋW$ãw̍ohŧjƒ€/žø‚úú2~ˇŠŋWėtŸ]U› Սz•+˙ŗPŨ�q+Ą¸Ēķ}ÔŋåréNém—4lYũĮu—˛øžĨ_?8{Öå9ßų*|Ą‡PšRõƒ/ž€ÄW;ßŦū-—ÕEúŖˆˆˆˆHoģ¤aKCcãĨ,žīé׏ž:ÕéĻʓ—š-"îÖūQßų&õošėēč"""""ŊM äē™ĢéRúĨŋ\ \MR˙–Ū )k""""ŌW)lq#…-""""""""n¤°EDDDDDDDčˆˆˆˆˆˆ¸‘Â7RØ""""""""âF [DDDDDDDDÜČÔÛ pÅgx,‰ßˡžé‡¯ÅŒŊЁ#˙ØĮßŪz‹÷6÷vķ.ģū~Šá9h‚ŠjČÚepēˇÜ5r-āˇNövcÜ$`,T'@ŌĶđF/ˇåjŧžpeôí6Aé<(Ȅ9•—ŋúO@ė0úŊË_wŊ|DDDDDú˛>9˛Å1ĮŌn!čÄGŦËūé/üŽ—×ü•2Fp΃?fr`oˇđōęoƒ‚ŸBz~�÷dCŌF(�§@ÁÍĐŋˇų%­x æúõv+\Ģ+‡ÔPÜÍũÆBÕt÷Ô}×Lxk䅟‹ˇCę6¸šĸÆĢšo_*Yy°¸Ŧûûģí;6ŠæÂøs?7‚ŋ@VĘšĘôÁ‘-&FÄ^õeQN!ĩį>>VMÅūFĖŨNXČ ¨:Ū›ŧ|L>baęJØÚęIûíŨ°. Ļ@Z),ŊŌ.‰ÄxÞŪnGN‡ė\ט`đtSŨŨ~>PŲn*ģO¸šûö%´c÷Å÷9ĪßąŲú_ŒfxŖČ ‹ˆˆˆˆ\…údØâe6ūˇCãûx-cY›,ÃndúqÜđÍÁx™íÔ˙Ŗ‚yظˇĶđi<Ę˙-‰õ[4ėv?t=eĢ^bÍ^–Āof˰ÁxŅĀ‘ũÅüeũ_)=qiĪ´;ú‡Cę`X—Ũöaôœ­yķė:÷0j‚ģnƒÅ#!Ōš  æŊœģŦx"ˇAV ,Ž› Š?‚¤mšķÂĀĪš[ õ#cĮŒ™L-‡Œxˆô„ēã°ø/°˛ÚEã=an"Ė ‡P TWAz,- j؀؟CúgāˇÖ¨kĘ͐~“ŗũ' wĖûČõԙ€ąP1Ɂ˛`:øAXŪEęwYwÁÔAĐ\™yP= TAāÆŽĶˆ ã6˜j?Œé.™īĀsåđā,Č 3Ę=;Ō—CFpįm߃$ø™ļeŊO:G,lXIf`œM„‰Ī‚_ûiDŪđD"¤…9ĪīŦÛ v;§Ū ‚ĸŸBūĄîf˜l´š¸R×Ãįé{—Zûvģķ­k€ü íŊ }ã\ßÎé#!ÔĒ+!í5xģŅš“ îš‹G}ŦŽ“~ŅĶ>ØÆ (ũ)äæ@čmÔ ‘+īßuQF˙™8h‚ÂŨ–Ÿ;.œã’éÆ9z6Cū6Čđü(ˆ] ģh7Č3Œīņšī{aŠŗĖāÎŋcÎk”éaöd7Ã] ūŪ Éč'ia—ŽģöL1šXø žq姍müũë<ŋâ2X[{pDDDDDŽ}pQ3e%•Øŋy ˌed ˇëDČ3’ŗųVÃGüaų‹,záŦûģ'S~Ȅā8XÄ' ša”­ÍaCGEbmØÃŽũCjÚtnp‘•ņ"‹26PhŠáßfŨNhˆĸbÂÁ¯ rË]ėāhõ0 LI„ÜҐŋ"ĶaâF°…ü„ Ķ1šsÄė†Čg!t=DŪ…)ĐŧŸ5r“āoį1€g¤„ŠŋËŗ°ādĻĀw\\§'fAz ¤¯…Đ_CZ9,ū<1(‡˜5P,Xļ# Ÿ�ų“ 0" Sˇ@lŦíú5;�OX02ÖBŌŨ¨ã˜Ėˆm€Šŋ…Øŋ€mĖs5åÂ2gBdL\ Ąŋ5B™Å?‚oČZcü\÷ ؞ÅÕ.ÚæŦwj$­„Đ ŖmķfƒÎēīY ų@Áā÷ØÚž-&X2 „Ų`û5¤î„{f@VTĢũːš�uī÷Ֆ ~c Ŗ‹ëyšô´o§L‡ôÁ°8Û¸ŸSķ fdĩšjÕė€˜xH:1΁å7PāënģĐ˙ŋãėOëÖCĖrãžĨ˙æ:ûŗéƒí5›!iTo1úU50 îŋRˆũ5ÄŦ…ēp(˜œĮ=8 †Ģ!r5„AæHđtt>}lÔ$X™9Æ5‰]ÍQ.ŋcÍg§WBnŗĻŦ …!ÆŲļę0ČO4ŽÛįÛaâ‡Ā ˜˜;Ņ0 žueDfÞ@ČOëzpDDDDDŽ}0lÚÂ?ķģŧ}Ā<ÎKĪ>ÂŗĻ‘øíPüÛ c¯`ãōßņ›œBöUÕQ{ŦšŨŨN6næ ŽJūīĶŦ׍dčųƒl|ûēÔZÄ> Ŋ™oą›ukūFéą:jícķš˙Ĩbā|7Ú]Bž<›7ĐŽ‹î Ū0o$n1FHh„]ĨöDŽ†Š­Žg•ąîÃiāH”ÃRį(•ĪKĄÂėü u+éīĀā€ė-°ĮRÃ:6Ĩ8,„ŦŋĀĘr8PoäAúq˜o<XqžSs3œt†‹ĮBņ˜ŗÛ8fG¤ÁÔxÕÅŠ{š5M˛ËaW]÷ęIČȃ­ĮáķJøņĐėãĸ?ˆ´@ÁNŖŽĮáĩģō›átŗ3\qĀ‘æ ģļoͰ`Ĩņ`ŧĩÚ(į-Æ:%IÁÆ1§ģķÚtō”Ũ? ŌCÆ_āĩJ8Roŋ‹AŌMĐjߊFŦ„ÜcÆt§ŪÖŖž än4āŗ+}c7dVÃÄđļûyVÁŧsŖ{!Ģü!Œāk4žĪ•ÁįՐŊ”‚m _ŠvPė6ŖĶ@j<ø•CŌ;F?øŧRķĀ3 Rũ�?ãģ”ŋVVũbivúFڀ*ãÔÁŽ2¸gå…ĀąÃwĖ)´Ų´ŖÚ­SQąŋ…yĨđšŗméĨ` ƒH�Ô9ûas#œîäžĨƃįgēŨ(ã@Ĩ12Ļ.æuķūˆˆˆˆˆ\%úĀØÎ4ŗ¯āĪ<ķ7o†ATt8ßú—Hž;c4ßM(cŨĒ?ķ~•hĻÉDâíw:Ä_‹ “É„ŲlĻÂd2ĘŲĩ‡ú¸Hž=ø¯>Žä†!'øŋœJĀ› aƒáīą¯õmcŸ0sËpėĒč pƒîßĨÁc†u‡Ú~ŧ§šã!ÖŪpލ8ŪjJ„šíPQÕļŪ:ĀŗuŨĮĄ¸õCVT�‘Œ ĩŸōÛM1*<ļ`ãÁĒÃ4–AÎöˇéPXÍã Övu1Õ  Õyw§~Ī@đl‚ÂÖëTCA$uVÁqČ?i3ĀķcDFA5ė毛X ÚŨ“:OHŋÍŗlžÆuö4wũ`ŨZd˜1*¤ ŨZ&ŇĀs¤ņ€|ÄųŲžvûT;ÚŨ×ŪŌ“ž¤ŪYÁÆôOxZ€vĶũĒˇîĶÜlÔã čėcíúúŌįuŖvŗŊÅ­īš &Ú`Ī– ÷āä!(6ëķ,m6î[fģļå‚ԁ×Q¸ęžɐšōĪ….i[EՅi…`…ĄŖ!ãõv^[ã¯Đî­Ad‚ØA°ggÛkēĘX\:2p~Oēŧ?"""""W‰žđČ嚪‘Ã{‹9ŧˇ˜Í€eX,Š) |?1†OV~LÃāXæ<ø]|KŪâÕ5û8Ōā�s$÷=™ˆų\û‹ø¤v,Q҃ØXpœĄ1‘Ôîáŋ˜°X€oNįųį;žBÆ~ÔÆ3aoŠhŧ…)w]Ŧ!žÆšÕíFBœ>œ¸zš9÷Ûī‹5ĻũTgHĶYš~>€rwRNƒąŽD‡°Åb<pÍKƒyæį ¸ [ėÎQ%=¨ŋŲhîxŪuŽ.„Y {â!õ&HûžąžGÖ;­~Sߍļ1ōg­R_ƒ=€ ˛~ŪũNOOŖ=ÍíúÄšiKmĘéÍ܅õmŧ4 ŌÆh‰‚cÆ}KŊˇÛĩũ5i_Ž'FāÔŠîôÁnjnŨœaZĖ8;ĨãžÎézžtüūÖ5.–EÛ n‚Åɐi†=Ÿ×hkÁdsģ:Ļ$Bndl„´CPį€Čx(Ųųņ˜Œĩ‡ęÚ_WįßļV˙ŌtyDDDDDŽ}2l1yzcq4ŌĐî˙”7,ä­Ooæ[×�XĸGæØÃīs>f_Ģ&ÍmŽĒdĮį'ɐ‚}|;ڏ#ģŠ8 €ƒĻ&āīy¤¯ß‡Ŋ];MuŊūœē§ ęn‚{ĸāĩÎŪBb‚”8cÛ]ÍF¨bk÷@ØßEĶcí❐…u @¤ŽtNQjĨŲaŦaŅA“ņ�ĩŌĩÛæ€ęŧbļ;õGēøz—ԍ°2Īø3`1e'c†ē<Ō.`,’뀤õ°ĩUŋíÉoöëÎĩŊ]čé"@ę‹zÔˇÁ=ƒ!+͘FtNOÂā|k˙iŋŊË>8¨“ã.Æuvc:Ų=;;nŽk‚;o›Ÿw×E^ ?.L0*Ō :„ūąí(—L:Ԙ:õHĢûŲ“œįÚūēē aDDDDDŽr}o͖ąüėWķī˙ÚŲ7ūžĐp‚zĀė㠎æ6ĄŒĖhcîĢÄĨâ˙öP?8ŠoEŒ$j`Ÿ||îQŋ‘ĘƒĮ`ā`Ė'ŽsôØš?u49šŠoėũ'„ĶåuĖXŦvF'Ķuž“�™“`Ē7PÅvˆmˇGd˜1]ϏaE§ü ļõÃT Ä`LIj¯ĸĒ-Î,Į/üŠh6,;r l­ųŧŅšæDšÚú+ŽëŗÄ´žŽ6˜čb͖ū~pWø……<O‡ė<ȡCLëĩm.ōęéƒņāŪĒ{…Œ„ØÎŽuQVE9ÔY`bģ¯Il 4;§nôu=ęÛÎ'­R¤ū6HLĪ"ããGž˜[û`(Ŧ†P?ãûŌēėfį?3ĻåĩīKI]Ŧ¯3*Ɵ cÆMķ>ĪÁ=XÅd„ąum?Ku.´Ü­<ËaLi‹ ģ°Ø/@˙`cjTq•ĢEDDDDŽN}/l9QĖÆíĮHø1N‹eĖđ †1""†Äũ˜×5ķÉ˙s¨Ũ_É)ŸLüļ o?Bŋ=Ôáu”5€˙0>įÂîæ“˙šx=ĮŠ(lõëÅīQf‰ážé1„öÆg ¨‰?dÁĪ˙Äa}`āŧ&X÷Søķm0#î +fAū8ČßĪš!Ŋb'Ááâ Ŗĸ kŦ1: āĢfG˜—ãũ `,š l' ķPĮ]O—AF,ø>Ė‚�g[ōįBAœs§&c$ÎÔp¸nôo†Œ"ˆŊ –Díŋ.Č8Īâäļ‹ž^Lwę?YvX`œSHü×]@ƒ‹Bo#ZŖü ÄĻÄÁD Đų0Yį�ŋÁ0Å!.žR+ĘĄÎæ6Ú5~4Ŧ 3֊ †�gˇĢÃxx5¨í,AEæ1Hģ î˛åLšCÖ{_!¸œzŌˇAbęMp7\ëî‚ârđãģ;ÂĨŌwߑ%Q0Ę)‰°8ČXĪ7öÁö˛ļo ZgœCČ x0ö<S<:ČǂГ`†ÍøŽÍM6FAš2qäßkôņsm]0ÆxõčøsqM ŽCė8øÎšīöLđ,<!Æy\sā Ìöˇˇn4Gß\įm|Ÿ2Œr2ē9ęKDDDDäjŅŌ„öšŲˇáŧ|đžĮŒâe{Ķ ŽŦ`cæ_)Øoü ļŠä-^ŨūCž?ũßųO¨üü=^ËųnĜ[~ČûŸX¸Ą¨dgI#ˇÜ<Ęŧ=m]Ũ‰bū ‰‰73gŪtŧhĸūX;Öūy{d Āéj¸e)<8Éx@ŋ'ŪxđŲsæeo.9gk$9 }:¤û@Ũ ãí&ķŪëbM‘î: Ę cÄ 4čR×Ā—éšÕМé)jšĐ–{ļ;w¨†ŒrHŋ Ë!f5lŨI͐žķ|€&(,5^ķÛ­)=ŠŋR×ÂēD(øšq>yP÷tRŪé2˜ē2âĄđ{āi7F)då¯yČ˙�öL‡ü!ķ° “rN–Bꇐ‘hLg*.ƒ´õ@<äĮ‹n„ĖíÆp Ã!uiĮõrž\ u‰9 l¨Ž‚Ė5đ¤ĢW)÷AŨîۍÆ:$ënƒ=ã ú,xr} f&Ė2ŪĻĶ[7Â=Ͱ8ø×mņaéņ ÛŨÕ[;Y ×CÆ$(vöŸ=åēŪvŪÜĨk rd=4@îXĐš.Fˇ,]ž­úxsƒą˜īÔ<gāÖÉwŦ3Ø˙scJÜēw ĩ<ƒ!ãAhū-ŦÜ šã }Üŗ Fˇ›ud7L4ߏâģÚ^ˇÖ*""""rõęwöėŲŗ�„††ēĩđ‡˙Ĩ[ËģR,{ūW>ë÷T/4Äîš šđ[}…ŒščĻūžāéhõZ\üךâļwyč×ÖŲg:~vĨ÷ī>ÃdŒbjũšæ?‚,ĀöĮĢëģį.õG‘Ë­}ĻŌ÷ω\.ž°î1(žßdLëHI„{LUÚۍ“¯ŖgAõO!%ȘĻöąųE ZDDDDDŽ$}p‘ČeŌ ŠŲų=ČũŠąHhEĩ1ĨdåW]LXäKXšüœĶ‚lΊošo¯‘+‡Âé–7ÖBŋŪnÄ%p˛~¸˛ˇ[!âÔĪ­…įzģ"""""ō•h‘ˆˆˆˆˆˆˆˆ)lq#…-""""""""n¤°EDDDDDDDčˆˆˆˆˆˆ¸‘Â7RØâfũúuū‚äĢņĩÉōõs­‹ŽŦū-ŊÁUém—4lņņö†ŗg/e}ËŲŗxãnę |.…\…ÎÂ7};ߤū-—]ũQDDDD¤ˇ]Ō°e֏î#=ŽJũúį܉˙ū!\ķ5ērõšĻlē¯ķmęßršuÕEDDDDzÛ% [ū%tķū}6>ŪŪ.§×\ úõ뇏ˇ7ķū}6˙:ŦĶ}ÆÃö4°yëĄTŽ,×ôƒ�o(œŖ;ßGũ[.—îôG‘ŪÖīėYcžOEEĄĄĄŊÜ‘+KûLE 䊈ˆˆˆˆˆˆ¸‘Â7RØ""""""""âF [DDDDDDDDÜHa‹ˆˆˆˆˆˆˆˆ)lq#…-""""""""n¤°EDDDDDDDčˆˆˆˆˆˆ¸‘Â7RØ""""""""âF [DDDDDDDDÜHa‹ˆˆˆˆˆˆˆˆ)lq#…-""""""""n¤°EDDDDDDDč·-&“‰3gÎôf[DDDDDDDDŽ(gΜÁd2ĩųė|ØâééÉ?˙ųĪËŪ(‘+Ucc#‹ĨÍgįÃNž<I]]_|ņÅeoœˆˆˆˆˆˆˆČ•âĖ™3œ8q‚††Øf[ŋŗgĪžmŊcMM §OŸÖ”"ŽŊöZú÷īÕjåÚk¯mŗ­MØ"""""""""_ŪF$"""""""âF˙7÷ å9ZW����IENDŽB`‚�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/register-no-verification.png����������������������������������������0000664�0000000�0000000�00000045436�14156463140�0024312�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��]��0���eĩq���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨ}\Õõũ˙ņGM9ØÆ‘ ’߸0å"åĀJpũ¸XSpf°¤¤,Q[j-ĨUbY朁Í)ÎmvĄŌVŠÍÂD“Ô¯`5Ūn ~÷…ÃšČ–û‚Ã` Û<­ķûƒ WŠGԞ÷ۍÛ->īĪûsa7?O_ī÷į‡Ãá@DDDDDDDD.Ē]|ųå—üã˙Ānˇķå—_öGŸDDDDDDDD.{ßøÆ70 xyyqíĩ×v[sĨ˗_~Éß˙ūwÜŨŨšîēëzÜADDDDDDDDā̝žâĉœ8q‚›ožš[ŽŌ)tilläÚk¯åēëŽģäšũûß˙āÆoė´ŧScŗŲ4hĐĨ땈ˆˆˆˆˆˆČnĐ Aœ8qĸÛōNĄËŠS§¸æšk.Y§DDDDDDDDŽt×^{-§Nęžŧú""""""""rÕSč""""""""â ]DDDDDDDD\@Ą‹ˆˆˆˆˆˆˆˆ (tq…."""""""". ĐEDDDDDDDÄ爈ˆˆˆˆˆˆ¸Ā�WāškŋáęCˆˆˆˆˆˆˆˆœĮW_ēüĒtq…."""""""". ĐEDDDDDDDÄ爈ˆˆˆˆˆˆ¸€BPč""""""""â ]DDDDDDDD\@Ą‹ˆˆˆˆˆˆˆˆ čīˆˆˆˆˆˆˆˆkUũ­–ŋĪå_˙9ÁW_}ÕßŨ9/×^{-ßēnSJ%čŋūîNŸ¨ŌEDDDDDDä*VõˇZ^]÷ÖũûŠ \�žúę+ŦÍ˙âÕuoPõˇÚūîNŸ(tšŠmx;ˇŋģpņ\s 8WĖ9)tšŠ5˙ûßũŨ…‹ëškø÷‰ũŨ‹>Qč""""""""W”+e˜”BPč""""""""â ]DDDDDDDD\@Ą‹ˆˆˆˆˆˆˆˆ (tq…."""""""".põ‡.ÕëH>ŒđšÅ_ãöĸfŨ=IÆŪūˆˆˆˆˆ\ypÃ-#ø^ŌDĻOMãÉG͘ųPcoŋ•›Üûģo—¯ũŨscŖf÷zVo. ÄRIĶqvƒ;OLQŅLššN˛Éŗŋ;)""""""rÕā=‚ûRĮãīŅiųɓ' ÅO6P^˜ĪÆ?TsĸŸúxšēr*]lrĻИž‚üâj›Iœ”ʤäDĸ‡4aÉ_ĪŧIąLZQŒ­ŋû*PšœÄáÃîō>j,3ŧOåUv“ æŒ$xĘûįöėížMøđ‡ÉmrU¯DDDDDD.Ė  žœ“Ú-pūđōĪyvÕīyŋr�Ąw˙ˆįĻFpCŋôōōu…Tēԓ;c Ų%6†$-%gi*Á]ʗl•ī“1#ƒÂœŲd„īáåqĒxéîDeŧÎSûī6Ž[ļŗzu“jėlLŧ?ģwjĘ}˜ÄâY”ŦŒīīŽˆˆˆˆˆČÕîúĻO‹%pĶ˛ üqûŪücûŋbĪú΍ˆIãÉ䞘ØÄĘ­ĒxiwETēØv¯ ĢĆ!|Vv\�܃Č˯/eŌ¤Tû’ˇ4YČ]đ(IŖF>|á#ī iÆr-íĩ ˛b‡œ´ŽšN;V““0ŦįųQvĪ&|øHfîîZīpmũæ9ĸ†#Ē—ša,™1ŋãôūļCl_đ0‰#G<|$Q ˛bw}ŋU˙ ņ&:ēũ'žÄY+Ų°4JÖąĄ˛Ÿ:u+/.W%—ˆˆˆˆˆ\î|'i<a΁ '(ĪÍq \ڝĸnß&r>ų'Cž›D¡/a7/sW@čbÜ_€wͧâĻMƒSÉZú,ÉŅgI]šv“‘t?‹ļ×āŸ<ŦÕ/“5TngŅ”{Č*0åÕ{ą8ŋå6•`ŽwĮ`°Qb>ÔŠŲbs1vÂIˆîš ]@[S!ŲŽäRĐíD,äƒ!ãH‹¨gûœIĖÛ\‰édŽÎfaZ�ÉēŸ,ķå3†Å38ŽqŦūô˛šŨ™ĖL¸Ŗ-�KڂŨĒ‚9# Ÿķ>æ÷5˛=¤ĒĻ`ÁÃ$ŽŲ1tŠë~6Ëz2î!|ä0‚GŽ$qĘ\rÆ65å>Lø¨X֓‘CøČ‘„ē‡Œ\įûQOAĻs@äššœ[fTOÁ‚‡‰9˛5ā›ģžb{—MšŠÉ™s?q#ÛĪį2Öĩ—k"wĘ0fæÛ°įĪ xøXV´u fûŌÚŽ]ë9.  úœ:'"""""ŌŲõ#øŪˆA]"|Ú Ŧ^ūbëĪŌtfŽ ĀĀÆgģ )?áMtė­Ũ†Õܝp×éũēüܝp×Ĩ9§~p„.•”Xl`ˆ"1úâ´Xŧ"“üĻ`2ŪÛÃË‹Ļ’<nÉS‘“ŸM5lČZO=•‡Á^ŽŲšČ¤x/ĸHLL“Ĩ„ĶšÁ!Ė%Į!8žž2Ÿķo+„´IÁ`/`{× K.ĮĀ?9 €%‡lŗ C\š+g‘Ú~^›ŸËą‹på.’ęcÔ3ĪļëdÛ=—Iéš4%.eŗšˆü×g1؜Á¤šģ;Ē:Ü `ˇŦ&§>™ė›Éˆ†Ę3H/p'íå|öš‹Øœ=÷‚9¤eZڎŗž´)+(÷ŸĮ†üįŋΌ!Å,ē6ÛÛ.ļģģ;Øv“ccŌ†}”˙éOä§ģS°p!ÚļŠY7ƒŒÜc˜˛6’o."˙õ'R˛€´sø2UÍēdl?N\öf Š> +ŪÂË+˜Î]šØž1ƒėJ26æŗ×\ȆŦ`ĘWĪ cˇ đ$õõ|2‚Áô2Åå0/°d’–ąW˛š ˆŊīŊĖ$÷Ũ¤Ī\~ŽĄˆˆˆˆˆČiƒn†_O+N4PūÉ'ėûôīüŖųÜOĮ+7Üōmœ�cĐ0†tŲmgáĮė*ü¸[sģ ?fg˯W@čROSāîÉā‹ōĒb6o?qÄ iĸŠÉéIŅîPšs¸GĮcÂF‰Ųrzos1ö€hŌMPYLy{*ĐTNI ‰ŠëąįBÚōOJ”Á†yÃnœëU,š{9F0“&…�PS\Ė1܉ž4ŽNšO2iņ—Į7ŧš*ß'#ģ�‚§2ÉPĪæœíØĸ˛Č™7Ž`üŖSÉ^‡­ ‡ÍõÎ;û“ļt*qĻ|ÜĄĻō˜’™€ÁņSY˙93ƒ°lXGš{"™+ˆ)ĀĪ€hR—."39ÛĢ;šĩÛŨ‰›9Ģ#,ķON%˜J,mˇĘ?õu6įo$kœ ˙ļūÍLÂņ’‚>‡ČŨ\ ‰ķČ‚§Ļä•ˉ28mãIbV>V’l ĀĮ'�͏y¤Û0´…;�ÜņtoģŸĻt6äį“3/žā�|‚ã™™ĄĻ„’zDDDDDD΋Įõž ė˛ėäįĨŦûÕkälŨÅģë_ãgË^ãĩ}6Â›ž=‚ä”XÂn�<žÅ =Ė Û5xšÚ¸"&Ōm{š´A×ŅĐDîũŖXTŪuy+Ęß&š§œĄž’;P™CrTN/ĮŦá/Į€ā(â‚!ÛbĄ>m(Cĸĸ1™Ü ļ˙† $Fƒ­x/• &91¤į&=/ -ĪdŌĸ3))Ū@~ũIķ°°Ų| Cø,’ÚO­ĖŸŽ'îŽŋ˙`āx/įë*6ō͇‘ßĨ/ƒÃSÉ~ymŅŕāŸnęšGĮc˛gRlĄí|€čNķų„'‡cČXȤš•¤%Ž#.Ū„Š­Ũ&*+)Î c �sy%ĐēĖL”ķFî<qēZžP“›IVž…šú&l6;6lānëãü*5ÔÔCpZp§ĨĻxäŸnÁŨũæĖLļ—WRĶdÃfŗˇ>ôūg:Š'T¯caĻ™ōšzšlvl6°ãM“ŋˆˆˆˆˆČEs‚ō]ųü韧:/>5�߄Š,Jî˛y—ÍÚ9‡,W{āWDč2¤uĘąļŠ—N%îø'§’äôRzŦd;%gIck{QžĘęŒ8zŦ˙p÷Äß €¨¨![Lšm*>ļrJj܉Ę0˜†Įl>Ņ!X Šąâ‰ëuԅ´åNbZ"ķvrˇW“6+ mh‘;ĻyÉ_�˛ˇmÛSEûāN1Â%âN܍dD:~<$�į{hŗaˇÃ_˛ÎîŪBÔq§›nđd°Ķ:ŸäˇÉ7Ŧcõ†\˛3rXdwĮ?1ĖŦYD{6qü8<ŨģÜcOģÍ909S ķÜ{H/ -{%Y&ģCåę{˜ēŊ—Áfã¸Ŋm(“wƒ;†Ž^XȚ4…\÷d˛˛6ė‰;õlž‘Äę34Ũ´ũQ’2,DgŦdu˛‰!î@q&qé\$"""""į¯ųŸMœä۝Ē]Nžė!Iųg¯-Žæ{ŗŸážöaÍ˙â‹3´ũu[Ú]ĄKqQîlČ/'ŋØFō8įWwĸ§.Å9į(˜[@I~×6œxzļžÂü ?ë'‹MqŅ Ū°— Ä5íĨŌNZ4€‰¨(wr-å4æōãĸãˆsU[ņŠ$ŪÎæÍÔ˚E}înŽâHëöŠ&ö*šŽõĪDēƒ‡˜>Ãîî 6ĀđŠyyR×Qî ö9ķ¤Čūãf‘=nĐDåî\˛ŗV03cæ×ã;…+§ŸšÎaĖŲ‹Aö˛Ŋā8ÃĶŗY8.āôâs)#qwĮŨ�M]öąŲ›NWoYrŲ^3˜äÍ+Iî¨ēЧéŒ9Yæ\3ö¨lVˊwĘ#Uâ""""""æÄßūJ-Ąv,Dø]1ŨĪg˙ęžx‡đ›O˙j­ú+—ŅŦĸũę ˜ĶâR“‚ sö ú>ui/<ũņLë*=­īú2ŸH”á8åæC”cˆĮÔöv–Ŋ”TˇÎÁœƚ‰ k+š´T¨É%ײ—ÍĮ1$Ļ’č´‘°?pœšúŽ/Ũ6jj.u•K_™0ģSS߄@€Ķw<{Ŋ 6*÷îÆŌ‘%y<n Ķ‚ąW–Pƒ'ÁÁCĀbϤĶn•”TCp§ņDg`k­ōėū´^˙ļÕ}āŋ'TZ:WŸ”ėuz›lØđi}6ÛUn§°†žÆÕutî¸ Cܝ—& ōÍŊī""""""Ō˙ü”?|zĸĶĸë†&ō“Ÿū”ÕËĶIvú,ô­1ąv”Ä4P\ôYoŖ‹žvŽˆĐ…čy,L 5ë™y˙rĖ=Nڄ%w.Ų60xž!üˆ&)q0ØÍäŦĢîŧĘļ—Œ„‘„ĪxßiÂÚhMîÔ[rØ^rœÁáĻŽ‰r=ŖLøØ‹)ČŲK%Á$FåSÕØVđ¤T†SC~æ Ž&15žĶz“‰ÁØ(ŪÜyÂ]Ē×ŗĄør­~đaŌĖDÜ 2™ŗž˜šĻ&ęĢ‹É{‰Is1÷Úí&JVg6#“K5õMõÔXŪgõæ Ļ8‚ĶĖ'‰˛5÷},õM4U“›‘‰Ų=‘™ÉgĢqjãLT�XÖ¯Ŗ¸ž‰ĻęŊŦ˜ąâL`Ģæ/Õ}šŽ!LJöĮ^ÉÂŨjęĢ)ΝËęSéšĸ1*؜ŗ›šĻ&jŠ×3gÁ1ÂŖÜą×XÚ>5Ū:™´ũ/fĖ•ÕÔÛ|<{A.,M4Õĸ s6 qøSƒĨ˛^5/"""""ržlüoū.žčiŨˇđhûšô€[bHžÃŖcÍ?>ɧđī—¤ƒW„+`x€'‰+ßcSX˜ŸÃˏ øGÅîī‰'6šŽÕPRlᘠūÉŦxye§ Žĸį-%É<‡üė$&UĻ3)Ņ÷c%äįäb>6„¤,į¯˙x•č}Evwâœ'm ˆ'|pÛÍØ‡¤pö폠ļRI‹ZÍĸ’J2ĩmh’ķ‰Í$=|;Y慤Î)'-.jŠŲžkÁ3ÚĖ—g—û¸•l&“ŦėŲ$eĮnŒôÉŪŧˆ¸^īŖi¯ŋŒmá ˛ĻärĖnÃ0ØSÜ"6,×ēų¤’ŗŅÆÂˤ%d`g0ūŅãXŊy)ŨFeõ*€™Ų‹°ĖYÁˏEō•dG[°•d•ô(†Âˇ9[sÁķ^gE}Ų÷ŗ™Á O|’Ŧ…03Ŗ-AôL%ki1s23HÜ ƒÃY¸t%‰õ (Ÿ“CÚ$؜˙,qiŠøgŦgæũī3éõ˙!kŅ+Ė86—“FąÂāOôÔEŦ^éOÁąb˛ŌČX}€—ĮąūJDDDDD¤g˙,ã͡ŧųņ´X9¯Dām ú|�)%vTšüãĶ<^ŨZM9Í×Ô5‡ÃŅūKuu5>>}Ŧ�čëŽũÆEm¯ž8—×7äb.ޤū¸ ;î† ÁdŠ'1)Iã:WšT¯#)q5I)_é”R4YČ]ņ6”Ps܆Öō´ôy¤šēŧBW.'.)‡cD‘Yō6ŠĢmĖEēŲÆā¤”8ˇßÛqΧ-'ļ폕aÆgf>ķzøR’͆™äTrĖƒũŖHÎXĘûĸ3JHĘųŲņŨw‘ĢSÆÂĖ Ú€÷îKOŒŋGĪœl ŧ0ŸwūPMķéÜdg-ē ũ_}y‘zŌĒžžž€€€NËŽ¸ĐåëŽxA Sˇû“Yø6Š÷V‰ˆˆˆˆˆČUčBC—V¸á–ÂMøū[Á‰æ&ŽTVPņįĪ¨ë‡š Ž„Đå ^$�TŽ#kû1'.Ĩ¯S’ˆˆˆˆˆˆˆ\¸S|ņˇOųÃß>íīŽ\Qē\ölTîŪMIš™ÜõÛų qŦ˜w–¯$‰ˆˆˆˆˆˆHŋSčrŲĢĮürŲ•î OfÅŌ•Ērš(tšė03˙¯ĖėīnˆˆˆˆˆˆˆČ9šļŋ; """""""r5Rč""""""""â ]DDDDDDDD\@Ą‹ˆˆˆˆˆˆˆ\QŽšæšūîBŸ(tšŠy|ķ›āpôw7.‡ƒo]w]÷ĸO爈ˆˆˆˆˆ\ÅŌN…+¤2¤OŽšĻõœŽ� ]DDDDDDDŽbAˇøņÄŦGđøæ7¯˜a9=šæškđøæ7ybÖ#Ũâ×ßŨé“kŽĶ5FÕÕÕøøø\Ü\û‹ÚžˆˆˆˆˆˆˆČ…r|õåEm¯žžž€€€NËTé""""""""â ]DDDDDDDD\@Ą‹ˆˆˆˆˆˆˆˆ (tq…."""""""". ĐEDDDDDDDÄ爈ˆˆˆˆˆˆ¸€Bāę8žúŌՇšė¨ŌEDDDDDDDÄ爈ˆˆˆˆˆˆ¸€BPč""""""""â ]DDDDDDDD\@Ą‹ˆˆˆˆˆˆˆˆ (tq…."""""""". ĐEDDDDDDDÄ爈ˆˆˆˆˆˆ¸€BĐuAMMMôCDDDDDDDäŠe0ē-ëē_’Έˆˆˆˆˆˆˆ\-ĒĢĢģ-Ķđ"Pč""""""""â ]DDDDDDDD\@Ą‹ˆˆˆˆˆˆˆˆ (tq…."""""""". ĐEDDDDDDDÄ爈ˆˆˆˆˆˆ¸€BPč""""""""â ]DDDDDDDD\@Ą‹ˆˆˆˆˆˆˆˆ (tq…."""""""". ĐEDDDDDDDÄ爈ˆˆˆˆˆˆ¸€BPč""""""""â ]DDDDDDDD\@Ą‹ˆˆˆˆˆˆˆˆ (tq…."""""""". ĐEDDDDDDDÄ爈ˆˆˆˆˆˆ¸Ā€KrûNĻߞšĨ÷MÜÜ<0ú5‘GfŨËh?Ã%éZOŦyŗˆ˜ŋb—Röۉû­'"""""""rĨēĕ.nx…Ōõ'oŖÆĒr ßYÂC?˜LĻŲziģæöš„ ŸĀōŠūî‰ČÅĨg[DDDDDäė.MĨK/’Våą(´įĩöÃEüzqkJņÆĶKˆūx%‰ũPfbLYGÕ;.ŦÚæp‰…ú¯bGÄUôl‹ˆˆˆˆˆœŨe5§‹ah,Īžú @ķ‡ü~O?Vģ\`ā Ŧ8zQē"ryŅŗ-"""""Ō—Uč€q Ą�-Ž¨ëžžÁÂ{Kįōƒ8L#ÂAÔĻņdöNÛ{iĶjáíįĻ1ö΂†‡rg Ķ—ļn0{AÃÛ}zœ„5oAÃÃzt+bkųŲs™Ü~ėáa„Ü>– ūŒß™ë°wÚ?žgK�Ž°æ‡­ÛÆfw‹a­ØÉō§Ļ1öÎhB†‡4"ŽØIsČĖĢ {Üd!ķŽ0‚†?Čīj­ė_;‹ąˇG4bī5ôåÂZ9˜÷sžœ4¨ÛÛŽCĮņ,=ī -ĩ]ĶsE@{×Î%åŽÖsĮØ)/đvYo-6°ĶĪxlŌXĸFD4<Ķ˜ü\{knāá& #čΟąŋ[Uŧöƒļë˙ÄÎî}ˇneúˆ0‚FĖĄ ˇg™ŊŠ‚ĩŊÜ×âŪ/Žĩb+™O=Øv˙"éé<.`û†˛wÉ|âAb۞۠ŅÄ&ĪbŪ›EtßĨ„yˇ‡Ô됟ŪŲ}ũŪ§ĸ Áôvh°đ;į?'ˇ%åŠö;]‚ž>Û""""""rɇõ…Ŋ#ŧčVlrø]ϧ-ÁÜnžáďÁÛ`å`Ņ>>Xw€‚=ûXģá%âŊöąąđ‡ņû:Ā#„¸ģCđĻŽŌŧ RĘęøIPsëą<ÎRŲb-aaę,~_Ղ›o8ŖĮÆām°ĶPeĄ´(—Ŧĸ].ŲÄĻÉABĮķø4önÚEE‹ī%ŌhĀ7ĘĢŖš†Âš¤<Ŋ‹ē7ŧBĸHŒŊCķö›?âų‘ŋ'›ŧWcˇļëŅBŨŽ%ŦYY‚Ád"ÚˆņŦE9Vö>—Â#[‚›ĄŅ1Œö2@ãöˇ¯ x-;~Û§Iƒ 7Ü�{ķQōŸZĀOö¸Cbh3‡ËJ¨(ŲÆĸ)UØß‡ uÚŅnáĩ)ŗXai@ĸãî&ŅÃNmÅĖ[WQŧco|“g#Œ04†ŅžĢ¨¨+§ô0Œvn§á�æĒÖ˙l)ÛĮAîf´ķaĘöQÚnąc}ļkcˇ9iojiëĶ(üÜĀZgaoQ.YEQŧjk'øvÚípŪ&Ī˙ˆFÜđ5E UЎŽĸxĮ‡<žņ-ž0œįövoJgōâ}mÛĮpßX/°Ĩ´h[^ÚG~^on|žŅ8üÎ`0�ÍØk?dáęxĪÄčˆ1„Ųë(-.§lį*:ÔȖ÷Ÿ'Ō@ŸžmiãpräȇKØv8ĻŨę6ÆņâÁŗlûy^ÛļQŽô›ĶŠƒŽW“LŽĀa&Į˜g͎Ī;ĩ_ëØüãXGā°PGø÷8Ž;­úßŦ1ŽĀaĄŽĀq‹Ÿ8¯øŧØą`œÉ|[¨#pX¨ãî5‡;Vß2ŗuŸåu´õųī§ļļ˙Ŗw˙×ĨËĮKW:îž-ÔøgččrącÁwCÃîvüĸë9˙ß;Žo u‹uĖÚRëp>KĮį{ß u39ü}­ĶŠÃŽ_ŒkŊ.Ŗž7Ņ‘ą÷sGŸ\é3,ÔxÛDĮĢ;Íaûė-ĮÄÛzégo f;‚‡…:ŋåu˙K¯Ģí ãI&Gā°PGLV™ķ Į'‹ZīEpŌĘÎû8lŽ?ŋ1Õ>,ÔøŊŎOÚēøÉŗ­÷ôÁ-ĪõøŗÁÃĸūhbũnŋįߨuœÍ˙Ŋņ@ëšt}>Į˙m™ŲÚ§īžî“Ãáp8Ú¯Ųm/îwŪé¸ãWLlkoĨãĪįŊũēÖįiØGFA—û|ŧĖņbûõ]Tæôė;2žsĻûØķķøÉĸļ?7߉rŒyvOį?WĪi”#}¯íŦm‰ˆˆˆˆˆ|õ”Š\VËėĩ’ųIJÖOKŨË#q§+ėæ Ŧ9Ô^÷’õb,ÎÅ,|šÉlĸŨ ŲŧžüŽá>Øsp#!ũ™ÎUŪQ,Zö�ŪgøŒĩŗÚĒÖĄNŪĄáNÕ'­ŒŗYģņM>ȝöĘ  ô­Š[Ācė|V¤øvžŽÔ{ ‹^-ŋõ.‡Vy¸4c Áĸ¸NWāĖŧÆŗč•l–¯ZĘÃĄ;hz/E�Ą´ĸOã”NköâūģT[By(%€ēŠĒĶCv˛&ī(ȏ~Ņå^` lúbęvņFۗĢ"ĮŽÂ8h.įôh;Íå´¸…p΃áør„ũeÎũŽbqëqb;W§ôÄ;›ßŦZƝ—ĖîV5â7áâ=€ÆrJkO//}g=e-ā5áižvŪÉHdúĶ$ŨL[Ĩ‡Īsûˇ6PŅcŸaQB—ûl4ņlÆ<€ēī˛ŋ/çú Ų0†ŦĮtūså7ž‡ĸŨ€æž‡ú‰ˆˆˆˆˆČ]âáEä?Â~ˇîkėÍuÔÖ5ĶāCæ+Īé”ÜSB3ā;ĻĶōŪ1$„BąÅBq™‡ Đpˆƒu�&âĸēÃ0DÜKRĐÖTŊį~Ąž¸q”ĒŧUŧģ˜E;‡%ü"ĸÎŪp:p#rBLÃyŒŅcˆtۅšĘBi íōŪ;ĒOÀ:x‡ŸāôÉ(앆†æļ ÎŨā´`ĩö1jįCB_ĸōöõ hą7cŒœōĶÛ>D|ÜÍŦ8t”ŌĘŖŌ�� �IDATĸ#`ÂĶz,%d ‘�Ta.iŋ4FGavËeoŅŦ“īnŊ& –ÖĄGž1‡$õÂ84–D§íėÖŦ֖ÖkcˇƒhļŌØÜžÅéûgęūũC,+ū{Ķ‚ķØžŦ€°ą=?†ˆļįŖŲBqÄ÷ō5°sá5ž‡?WFŧ}@#ÖÆ‹”ˆˆˆˆ|\âĐĨ…ÆĒC4öļÚwM›Ę#)cÚémĶNm]ë^vËæ=ĩ­ĮŨ[q¸Žđ…Æ:�Üŧđîą0$ˆĐ 7¨:{Øā=a1‹vLgQŅ>V¤%đk¯FG".a ņąQ]ú{&´Í´pxĶž,ėy›ÖŠ:*ę sųŪžįPåŌÆ~øC~Ŋ:‡üĸręšĪž}ŸxųŌSO:ŪŨŪĶęęh°`ÍSs{üذŊŽõ>4ÖUańŅE\(˜-Ø"‡ĩØ_^šj $. æuą—íã` xE!Ŧ§QkÎá×ëļQPv„æŗ> í÷ΈŸW_núųnī…Ÿo/ÛŊđ344�!t1zĪōčs äDDDDDDäR‡.7ķČû{XÔå%ŅZ8‡ąŗ?ĸŅęAô„ށ €ģŊõĨ¯ĨjœĨ2ÅŪh|ÁŪ6)¯ÁĐË ĨŖ‡>ŊP‚xøˇšãuÖlÚÉŪ’C˜wÂŧsYxz÷l˛L%ōŦyˆ{[QW˛‹3ÚhÁÚ­Ā ˇs9Ã+Ö3yĘ2ƚÁ#d<Lˆ"ÔĪŖ›°Sŧ.ƒ7,įØčšöĄíūŅ|ˆÂ‡Îŧq{Ĩ žŒŽKÅ+?jÄZ˛ƒ¸k DFB‰sŒ…ƒEhƃŅc{¨*éAmŪ,&ĖßG3MåņXŪF#F7€*~?æN!UûũsëãWÅ]ąŊĄmĢŨįüDôÔdåg""""""rA.‹¯ķlėž-úˆĖÅ2úÕ.sK`Ā`l4k {2úøOûía‹ŊˇĄvėÍį2lÂH؄gøÍ„gĀ^ĮÁâöîŲŖû¨ØšŒÉUäm~†°3ž0F/Ú`&+ú^ČĪū%eÍā56›¯ŪŨåÚÚaĢo¸¸’Áhl{ŠZLņÆzŦéIØØQøŽ;BŠŲ‚=%ŠRŗ…LÄĩ}é',ք×ēm­ķē„6ˇÍq‹!!ēA„ŊˆåK÷ҌŅ/lbĶô .x`^ t ] @csXOÎ}{ƒhiîũąíf m_ę š´.“‰tŊš˙…ŲD¸Aãž%dZģŦ7āįÛúIÚ†ÚÆžŋ<zxĩΉŅŌHC×&¨ĸŦC‹zdđ%,n"?~q{ū;›{ŧ åĐģŧQ|ļŪųę`ĨļŽĮN]dG(.kŧˆŸÖ5p¨;˙kpŒžžx�ÔUQ{ļ…Ž!Ōš-û8h?„ŲŌ A1Œn?‘ĐÂÜZ+\Ŧ 0jŨ§OŸRŽ:ĀÁfĀ-†‡&w \€†#î6Η _€fjĢz™xØnĮŪ^euÎÛ{ŪžŽ—í­uÔZ[ˇõsž+¸­úĨĮ'ĐÚ۟q•Ë$t†N%+=7ų`ņ2övyA ‹Âh.ŲÕË[ė4ČūÃN;ú…0Ô āæ˛î;Ų+ļQЇItÁĘAķVŪŪTD¯ÁŪ1$„ēvÎZ9ãËč蛁öīØGĪīÁUė/,ápÃŨMhoà cEöâõäˇ_ƒŪK+.Xëä¯@Ũ>>¨čy›†˛)(Ģë|M &ĸŨ ÎBiqIë|.Q&:æž5šˆ …–˛JËJ8„ŽÕí S=;=üŦ{Fcįā[ë)îļܗȈ›(Ũŗ¯‡įÁÂÂD:2ĖâķŲ>¨íų€Ōz|>ŦÅļNJė5Š¸Ž ahģŋÍ4ôđÜX‹wļî#""""""—Ėåē�aĶķpи…K‹:ŊpâŌN¯ûé‡]Ē%ėÔîxĮf¤ķPÚ2övŧsš¸'Ö hĻ`õz:ŋ‹ZKXūÜ6<úŌŗF>xi‹/`ŪĻĒî•ĩ˛ĨŦđ%,¨ũõŊ}ŪFjk;ī6m&ŅnĐR´Œyy]Ûk`īOĶ™>{:)ķwöōœ“ Bƒ�ޞwOE§cY+ÖķØüx›Z/BC]¯S_8ãOš8ÂÛĪũœũ]NĖ^ąžyO¤ķxę4~]ÖiGFĮš€Cžõ!‡q#2Öä´ž-Ôh<ĀīXhæfFG÷PĩŌ_CŨ€f ”9?mvįŊĀ“;<ˆđh¤Ö)Ȉ|p*nĐR´Š…yuN×ÔĘÁĩ?'ŋđĪ}Ņēũ/É,ėrĄŠČ\ų͸:m*Ŗ;VÔúyįŊī|ØéšąŪĘŧ—ĀE›ļĨ÷g[DDDDDDNģ,æté`0ņ“îĨ`Æ6ęļ.ayʲ:ææå'¯,æ`ÚĖ[Ķ[Îč¨ ŧ Vj-åW5‚[ -›O|GE‡øô§‰Ûŗ�ŗe)?øø¨ŧíGŲ_´†ˆÅ<ôKí<Ûį|‚x|InĀŧøĸŪ '2ÄoXëŽPZvˆÆ7‚\ĖãĶÍ´Ŋ×5ķÁüŪņßXûâŒ~°â%L~n…ķSˆ}3ŠŅĄ^ė,)Ąĸą|ŋΊ%û<÷Iī|Išū}~ũôGT­›Î„Š1Œök•…Ŋ% û&ŋņ^ÅXË>ķ–0î™ü<÷_„/âtf`ô‚•Ė̘ŠËēëC"âL õ�kŨ!ö—Ą"f­ä؈Î{zGÅĘЋƁQķš´ ‹5áņÖ.ĖE€×ŊŊ|’ēÆOšķ;GŲōh ĩccjlĻļė�ûĢ<xøˇo—wlmfīKŗ˜Wr7Ĩ?@äĐŠŦX°É‹÷Q8ąëL„ų°VY(Ģkˇ_5ģí×Āųl˙b “įĖŲ÷PCd4aņęšÁ+öy~Ķi#ņĶîÆˇhu{2{×z"CŊ0XPZV‡÷ôÅ<l^šC#$9Ãŗ}ZšZ\V•.�Ƹų,ëå÷‹IŠĶ;ĸačŧųß[X>k<aÆŖ”îŲÆ–­û8hõ zâĶŦy?Ŧ¸.¯}~Y›ģšGƆ`l(§pë6ō-VÂf­eĮĢā×ĮyHŅĪŗãũÕĖ›ƒŸũû÷ėbËÖ]ė­°âq/ķ^ÉcĮ‹QN/F’–ŧÄ}&/ܚP\|įQ~Vļļwˇ cŖ…‚­ÛØ˛ĮBƒŅÄ=ŗ˛ųāũ—IęÛ™ŗōž°’ŧUiĨ-ÚÆīķ>¤ÔČïlbĶcĄøMxšEcņh9Âūû¨pÕÜ?Ūüŋ_’JB(Ԛ?lģ†vübSYøz›2zøęĐĐFˇĪ]â4ŸK{ŗ1­C—�¨1DöynY#ŖŧŚYß'ÔØLéÎ\ŪÛaĄÁ{<Y›7ą(ڛøôÅÜâu öX:*H†N^ĮŽÜÅ<„ĄÁ‚šhí^DÜũ4kŪßÄŗŸÃsŪ>åeöä.å‘ąPĩüwrÉ/>‚!h</Ûžß>ĀĐ.įiŒ[ĖĻWfr34”cŪŗRĢ/‰/nbSF žmÛ_ø(˛3?Û""""""Ōę‡Ãáh˙Ĩēēš€€€~ėÎĨf§ā‰hßĶBô’Ŋlš|áu%"""""""ōõĶSĻry /rëáöĒÃî;†¤.ÕØaŽhũ˛ĪĐ ."""""""rņ\õĄKCŅ2éø˜ûņ؊ƒoūœ÷ę�ßīs_Ä9GW}č2tōbĪ›ÆšCÛxäŽ"ĸMøyØi8t€âĒæÖÉw—<sķ€ˆˆˆˆˆˆˆˆœŨ×cN{ožÂ[p°ļ‘æpķ $,j <6ƒ¤P}sEDDDDDDDÎ_O™Ę×#tqĄž2•Ëî“Ņ""""""""W…."""""""". ĐEDDDDDDDÄ爈ˆˆˆˆˆˆ¸€BPč""""""""â ]DDDDDDDD\@Ą‹ˆˆˆˆˆˆˆˆ (tq…."""""""". ĐEDDDDDDDÄ爈ˆˆˆˆˆˆ¸€BPč""""""""â\;ųODķ“=-Ŋná6v5–WĮ`p}gDDDDDDDD.‰Kē´ņŊ—åËîů§u^!Ꮤ°đŽ_´á~Ôcc""""""""ũīŌ….DFG1ôBÛŠ=ÄÁē}q‘ËkN—Ã9L1åÅEŧöčĸn äöą¤<ˇ“Ã�Å?#ęûË(k)'ëûa„<ą;� ė];‡ wF2<Ķ]2oS֎†KXxg“ß,âwŽ%dÄ,ŪŗöpüŠŸ;|,™˛üaÄ.ĩ´ūj¯"˙§Ķ{gAÃÚs“ē“ÃöĶ{X+ļ˛pĘL#ÂĮØGÉŪÚĶë6M#äΟ‘_ø3&ÜŅÚöŲÎŊŊí˛w™7Š­íáD%ĪbššĄû54Hæ”ą˜FDrg ķvTa­x—''ĩ.3ũ`¯•9_„ŗ]C9—4tąÛíŨēnÔRĮ{KˇaĖØDÉË(ųíŦ[_ 3Ī ŅĪŗã•ņxÂŧ÷÷S˛ęn Ø)]:GV%rÁ:ö|´ƒ7̓(]:'ķęÚ5`0´PûÎ+˜ŖžgĶæų$ĪīŽNį'{ <´*ķG…ä-{�Þ Ļgˇ…2ĩ[yrĘ ÷ō›÷ 1ŋŋ”ûėÛxlÆĪ)m;Yƒ°îcÍ;-<ōę&ÖN 9ûšX?dáŖKØëũ�k7bŪĩ‰Ŧh+ŋ{"ˇBZęxouËv`ųÔĖÚX+[ž›EJvũv–Ow°Č׊ŸžÛčô劈ˆˆˆˆˆČš¸tĄËĄUÜ32’ĐŽ?Ũ*KZđKy†‡C[ScÄŊ$ĩp°ė`ĀÛ`�Ü0x1�ë‡üzĶBͺɚ`ÂĪĪ—Č”ÅdĨx`^÷.ZŽķ¸›ŦĮÆÄųe.vWÔAčxîÂĪĪ—°¸Šü&wk§pđ×13žŦU3‰ę‹ßĐX~ŧj6‘ĩī˛fO{Ũˆ––ÂĻ-æūčPÂüÚg´9ĶšÆQ<›ģ…ŧeSę‹ßĐPͧ2šCZœkRZđK™M’Ÿ0?Á„[K3aĶf3ÚāKüب˛´VčœÃ5‘žštsēĨōëeģO¤kđĶ- ōuúŨˆ—[k•LĒJ8Ør3IŅ΍ˆŒ Áí Ĩ æŨēÔ7ÔÔķDž}f r‚ ų˘üÔš0†øh~~&Â�hh HBg´…mŧGį×ÂŇ`BTÛÂ@ĸ#ēN|ļs7blŪÅō•K(­j¤ÁÚŌZ-ÔŌBd§ëãÁP_o§n{`ėPßĶĮ3 ¸ŅŌZit×PDDDDDDDúæŌ….nž„E˜ú0‘ށsúv´ĩ;Gy#5Œ7ē­ ÄjÚƒŅíî™_Ę:ō<Öŗæ­müúé×YÔâAĐØd-™Éhī–ÖãZ@ÄđŨöu j>=œĘÍŖu˜Q'g9÷Ãë™>å—4Œ}žĢbęåö˜÷ũ%]†išîŠˆˆˆˆˆˆHß\ēĐÅUŒ¸™ÄWÖņx×O x_đgĨ[č\dc`hÂLV$Ė{ÍÛXūŌ*ĻĪ÷ĸčˇ1@Ô|>XĶ-÷0}1@÷ylúčđžm”ưæ0ēŊņ†ķo¯ƒË¯ĄˆˆˆˆˆˆČ×Ī紜ūĪ ("ŨļQÛėÁĐĄNåÖ:jņ=ˇš[Üŧ0ЂŨyzk‡;>då ų�öĐ1DzoÂf˛¨j īX¨e"a°Ŗ ģßTÂ:R; ĩVŒŪįR~ŌŊŅ œ›ŠŨą•R ōB’—‹y EDDDDDD¸”é6ĄÔ\ÄŪ~öWõũĶÄF7ŒÔa.˛pøpvãIņĸ4;ƒå…Ô64p¸Ŧõ“ÍžŪJÃŲ[<Í/„0Föæĩîg¯"éģė9šŲŋz.͟ø9ųeUÔ6Ôq¸l+kļVáËP ėÁD[ˇ1ī§[)=Ü@Cm{×Ļ3á“Y^va5)~Q&<÷ņF^ u”æŊĀ“%žŒö‚Ú˛C4œoķķŠˆˆˆˆˆˆp)+]ęļņėŒm=¯sΚOW’Ø—vB⑍Xžx2)Q‹ŲąņFŋøk?gųâéüŽą<‰û<o.˜xnS‘bY´,•'_Ę vũBHšû WÍj›ëėŊēûâU,ô]ęš[pķ $2öyŪ\0Ļĩ"Äo"k7ÂōĨ¯3ũ‡ hƯ QÜŋę-~ŌmâÜscL˜ĪŠĶÉüé}DãEhÜ ˛–=�ëŽ0}]: ë(™v>-.Ū5�Žq8Žö_ĒĢĢ čĮˆˆˆˆˆˆ\yzĘT.Ũđ"‘¯…."""""""". ĐEDDDDDDDÄ爈ˆˆˆˆˆˆ¸€BPč""""""""â ]DDDDDDDD\@Ą‹ˆˆˆˆˆˆˆˆ (tq…."""""""". ĐEDDDDDDDÄ爈ˆˆˆˆˆˆ¸€BPč""""""""â.ÅAZNžĸöīŸsÂf§åäŠKqHšL|ã×bpˆŋīˇq7 ėīˆˆ\2.]ZNžâĐg5øÜt#~ßž ˇ—$įųÚ)ũķ_ņõņîīntķåW_ņīŸ ōp !ˇúcpSđ""""_.^Tû÷ĪņšéFnēŅS‹ˆˆČ×Đ7ŽŊŖĮ7ųæuƒ¨Ž­īīˆˆ\2.]ūõīÜxŊŅՇ‘ËÜ7ŋ9{KKwCDDDä’qyčōåW_ņk5_¯ˆˆČ×Ũ7ŽŊ–/ŋüĒŋģ!"""rÉ( q…."""""""". ĐEDDDDDDDÄ爈ˆˆˆˆˆˆ¸€BPč""""""""â ]DDDDDDDD\`@w g'ųã[?įÍ?Ÿęu‹ˇMfų´P^Â^šÄįE,ËūŋĮžeĘĐķoÆúÉīX˙-fž”J8GxįÅ7¨ŽMgū]ŪįÕŪ?>ųŋØZM@ęķ<qĮ Žå'–˛egåU4ãÎMˇŒ !9Ņ7:Ck"""""""_?—ičŌæúNäƞÖyø\ų‹Ëx<‘°›žu~ģ7˙‰wwUsĸëō/JÉYŗ•ę›F“2-‰i⏅ģx{ëžO%\š‹ˆČÕīd=û~÷ÛŦüxöD†^×ßš|]ŪĄ‹ģ7CŌßũ¸âxyžûž |û.j‡F0ōđ§8×ŗQAĶŊ›ÛÛ–a×˙‹ęėŲø$á#ƒ‰ˆ\ÕNÖŗoí/yŊĖ ėfŲ¯ŒŧøÂžŨßũšL]ŪĄËYā¯y¯ņ›Ę[y2#™amīüĮ>^Į˛pßS?"æ†ōņËĢČ0‘_<Io˙ ÷ŸšäåQ~´‰“ŧˆ¸‹“F2d`ûúOČË˙„ōŖMœĀ›n áŽäņÄ´ Ģą~ō;z1% sË8qĮ#,]Ͳ_•účxŽûx˙­‰| ŋÛ˜ōĀČNaŌ€˙ĮžˇķŲųįzšx‘Āô”‘U>g;~gŨ‡õu˙˙TîbKĨÉOäŗ_}ŠÕiŨŅđ3wņøFNqĘv T{$"rõ긴8p ūĪ/"""r—ũDē'OžėūĶąvÃÆ'MīūŊuųĨŧķq#ã's€;7Ūr+#oņėũ _ gí.jož‹™sŌ™ûp$?Ũ˝ķūĘ�>˙„×ÖrÄû.~üÔ<–>5™ģ<ĒywÍföŅځ€í3 ÷Ÿâ{Ķf23ƧuÅŠ&Ў—2čî™ŧôâOYúh˙ųãVļüķāSTR<ž'žú1OŽ÷áûˇōnû6}8ūõu˙“G؞wcBŖoč!äÁ7xtú ö˙•j|v‹Æ‰ˆ\ĩz\Ž ē—ųOÅáՏŨšÜ]Ū•./ä {XáÉ÷žz†ûn #%eYoåŗīŽÉ܏̐ē›xîģסm;ˆđ䩄Ÿá0Göņ™Į<“I �Ū<üOļTū‹fāØūO¨vÁ“)‘đ &%ƒ™›ųƒĨŅwy8uę~ąIŒÚK|pŠīHč¨(šÎ?’pīũ˙­îø¯ļØ<ž)w´hßËí8ÄÛļ9Ō§ãŸáüú´˙IŽėʧØ=–ųą×Mgl€/ūÄÆŧOt{˙īĻŗo.""—Ą˙Tąų•ßōŋÆ{xōGßåÛ]KWz \‚īeūS?ā•šˆˆˆˆœŅåēxßÁôÔHnčē| ;7:å ׏gĘmĢyķˇ9 8áEōSŖzž|ˇGÍÔmb€÷uîscD2ŗ"Z×ī;Ú7âëü—ËA>zCÅß9I{gŧ¸ĩÛß@ŨrĶõ~7€S'OuZ8ÔyDüˇ¸ÁŊ}›ÖūõíøŊŸßŲöxt?īĮF÷i“_ü‰kˇR}ĶxžHÖë°-šœÕSđĢ_˛ŖęđËNÂüĮœ‚.""""äō]\Ÿ˙õ!ݘŊō?X‡ßÅwēĨ4grŠ“'€ôØ8qÜāŪeÜē;ƒÁŠ“ļĶݸ3°ÛЇĢ|ĻmÎáøįŊûōŠ8yĮd’ũĪūˇč“G?áÕ× 9æŸÄOŠė˜÷FDDŽ48p ´M›~ŧė-–­m ^č)p™Äü§Æ(péŖË;téŗöå—q2čVn¨ú˜ŧĘĻ÷uŽ‘áà‡āĨs¸qúī™ÃŒ3ĸoĮ?u!ûņ˙û7Ÿ˙í žŲße÷ܟ“ž3‚'šÂ0€ĪŗĻëm“˜˙@(ƋxĻ""rŠŨČ]O=Ã~õK6WļÎ#vŧė-–Ŋ\O Ÿ°ãĪ \DDDD.ÄUē+ÚĘö/n呧'áQôŋÉÛEôS) ëSîâß͞Pv„ę“‘„ĩũeŌZ–GΞo‘üX"ž=ŦįD=m�ŋ;|\ēxôéø'.d˙˜žqkįāæT-ī¯ŲJķ]3™bōlâuō[ŪÚEmđD.""W‹˙ń§žįāåĪģŲᴉ‘ķsy‡.ļŽTū•ô°jā�O|‡zsŨx§°‘aɓ ÷ ID[Ū`ãŽHžK ä:NppįVöņ]ĻÜØã"ŋ˜ī°ŋ-š˙ÅĀģüđÅgėÚ^Æ?†Ļá;Ž‹‰åÖũ쨒Č „[šņT#åģ ŠpazÄõ=´xqũ˙öî7ļŽúžãøĮöul'qBėāâÄIC€@AÕ@ ´m‰V$"ŅN0†´Q­<@ę&­“Ö>¨F5Ąv]Ë´J§ĐĘJLŖ* @ "@CˆĶüÁ.6qbĮvlĮ×ö$@Bū@Nlāõ’"Å÷Īų}ĪÍëķ;wá Ž˙aŪߨôūB՟Š)Ĩ\?;sö=×ųÜãųŋ]sķ‡+ϧŗíwé|÷ĩĨ”fÍÎÂß`đą´?ŧT˙Ķ÷ōī¯œņ�€ã7šŖËŽ—ōŗû^:üsĨešõ;We÷ĪŸĖ–æĢōˇÖī{ŧzaūäËįįåû~‘_~ö¯rãĸĄtnېõĨŗŽŧNÃĨšũļäÁGŸÎŊßīÉHé”,8˙KųúuûoÛpQnŊ­œ}*?üĮ_d0ĶĶÔē4_ũ‹ų\ũG}Ō‡›ī×˙Hæɛ›w¤\.įW?Ŋ?ŋzßŗõ—|-ßųōÂc:-�&‘ę3rõū+^Ū /‚ �‰Šį‡-[ļdÁ‚éë^}#œķ™ô˜�ĀĄÖŊúFæÍ=Ú7Ú}#ÛķÄ÷˙%OäŠÜņ—}piëmŋ��ŸH‡k*“ûJ�āäĒ>#W˙õˇsõDĪ�đ P9Ņ����|‰.����]���� ē����@t���(€č���P�Ņ��� �ĸ ���@ .•••+z�`’KU•˙ī�>= ˙ͧ~Z]ēvė*z�`’L͔)=�ĀISxt9ũ´Ļtu÷ä­ˇwfīHščå�€Iftl,}{Ōŋg0 NŸ;Ņã��œ4Ĩĸ˜R]ĘY‹[ōæīģŌŊĢWx€ĩŋõöDpˆĒĒĘÔTWgéâ–ÔLŠžčq��NšÂŖK˛/ŧ´Îo>K���L îf���P�Ņ��� �ĸ ���@D���€ˆ.����]���� ē����@t���(€č���P�Ņ��� �ĸ ���@D���€ˆ.����]���� ē���� TôE/| TTTd|||ĸĮ�&æææ‰�ā¤(<ēøÅ ���ø4˛Ŋ��� �ĸ ���@D���€ˆ.����]���� ē����@t���(€č���P�Ņ��� �ĸ ���@D���€ˆ.����]�~Î��� ˜IDAT��� ē����@t���(@Šč6ėڔÕ™ÁŅĄĸ—â$ŠŦ¨Č´ŌÔüŲŌ•™_?oĸĮ��€IŠđ+]îũíĪ—O˜ąņņô äžõ?Mû@įD���“’íEˇņŒįß6üĮD���“RņŅĨĸđ˜@ģ÷öMô���0)]Æ _ 46î���Įö"���€ˆ.����]���� ē����@t���(€č���P�Ņ��� �Ĩ‰āČjŌ<ûŌ\1wIM?%3*“=CŨiÛų|žÚž>[G'zžÉĸ&-ķVfÕ-i(ŋœŋør–]¸*-ŋ˙Qž×Ūú97å[ r˙sä•‰���>E&it™–eŸš9ĢN­MW÷oōÄĻmŲY.Ĩ~ÆŲš|îušŖaQî_˙H^)zŽĒ,;ķëš´ûšwĮpŅ‹ŸŠ–,?Ŗ%Ųņ‹ÜŨž-]I†ļ<š×û'z2���øT›”ŅĨžéÚ|åÔÚ´ĩũ$÷vvŋ÷DĪÆŧØš5ˇ~vEnXđr6ŧą9åB'iĖĸiĨ¤ûƒ_9aJĶRWYNû΍éÜwųĪÖë'x(���`F—Æ\8wQĻî~2uĻv ¯ËšõíÉPWĘIęįܔŋ9Ŗ;k6'W,>/u]Ģķí-IĻåĖyœkNkIĶ”RF†:ķjûãųĪÎŽ î?TŨô rũ‚‹rÎôŲ™ZYÎîū­yqÛÚ<Ö3dIžzÉƜ[™dɝšģõåüķķkĶ–Ē45,ĪõķĪÎĸŠĶ“ōŽlŨņtūŨoĶ5~ø3š5įæÜuFwÖlęĪE ÎËüÚÚdog^Ũļ6íč>úyTˇfyëō|~fcJÉî=[ķŌ›˙“Įvt§<ãÚüŨ˛ķ2#I–Ū•ģĮ^ˏŸûÍAۋuėķ���ĮnōE—Ēæ,ž´oŨœ]GxIß`×ģ/')ĩæŠ9Ûōë ¤c¸;IUZܔ?Ÿ;šg6­Éũ}™1ķ˛|ĨuUVß—{ģz“Ē%šáėYÔûdXŋ1;Įj2oΊŦZē2;×ũ$ĪoĖęuįkŸ[‘RÛōĀŽū &Šk¸.ˇ/mÍÎ7׿žßvĻ\͚/,ž.ˇ/ÍwߨønĐ9P9åTOY’kN{6Ģ×ߓ­Ŗ5i>men_rCúœĮŽp͚朕šlėåŦyíĄ´”Ō<ûĘܸäĻԏũ(Ģw>žīŽëĖ-,OßĻäÁîĄ f^–åã=žų��€c7ųžŊhJcęSÎÎážų†rĒ+KéøũãyqwW:†G“Ē%šzîė´o$īčČŽáŪlíz<w eiķiN’Ņ­ylũŋæžM/¤m 7ģģōĘöįĶ–9YV_ŗīČ{‡’ą¤<:”ÁŅŅ$3sQķŲŠë}2lۘŽáŪtí^—ˇ´ĨÔxi.Ē9úœļ?ģ˙ĀÃéxëŲŧZžķOm:ây”f^œKĻöäéļ˙ĘKũŊéîÎëíkķÄîéYvڒÔg4ƒåōž-Våũ3͉Ė���‹ÉwĨK†SNR}LīéÎĻž‚C]Kš+{ōRīÛkFŗĩ§3#sįĨĨ:éÎPÕŧ|aņŠ´ÔMK}Š”Re)ՕĨl­,%9Ės+æ¤uzŌĩŊ#}<<Øˇ9•WĨuZU~=|¤đŅ™ļ/#īNĮžäüšÆ”ōΕ;ŸG͌ĻL-weĶž3ŽūžTĪž—ĻŦ?hŽtBķ���ĮbōE—Ŋ=é+eÖÔÆ$]øō$ÉØPĘcü\ĒM)§äōķž™ËyņŽÔ–’”.Ę-˖gF÷“YķÆætí-'•KrãįVųCŠŦIue2¯åļÜŨrčĶ›Ēk“ aÆrĘõŒrFƓęĘŌ{é}įQ]*í{ė}÷Z+ī?Įct"ķ���ĮdōE—Ņ}Wv\ÛpvšˇuĨã0/Šo¸4ŒŊ–gzzŒōPĘéÉ Ė˙ž˙&%cåô 'MķÎË‚ąšĶē´Ŋ5Ē?āÎāXŌŪą:Ģß~œ(gpīQ‚Ee)ĩUIŪ /ĩŠ­JFĘÃÉá¯ė,—“Ęڔ*’^ĒKĨũįxŒNd~���ā˜Lž{ē¤;/žÕ–=S/΍훉 Ĩē rãâåY~jã‘ˇ nÍÖąé™UJ×`÷{ö–3RîÍ`’RuM26”žbÆŦŲįĨ%I*Ūwŧw>ĨņÎlë/gVMmvxÜÁūŒŒõ§ī¨;sædņ´Ē÷~ŦhLKm˛s°įˆņdįîŽė)5eņԝ™–Чdd ũ°Aę¨Nh~���āXLÂč’ôu­Íꮞ4ž*wue.ŸŊ$gž˛$Î˙bî8wE =ŸÕ›7ų›vF7æéŽū´Ė˙bŽihĘŦęiiš~nn8įļ|cÉšŠO˛kwGöLiÍeMMНž™–ĻkŗjFoÚö& õsR_‘$ŖLŌ4Ŗ5Íu3S—ŪŧĐą1åÆ+ŗę´ųiĒž–YuķségnÎ]į^—3ĢŽ4Đ>‹Î¸*įO›™úęϜŋđ˛´˛3/ŧ}ä-TåŪgōܞSrÉÂ+ŗlÚĖÔW7æĖy׿Ē=yĄũĩãøĻĄ›���øđ&ßöĸ$É@^ßt_žˇķ˛|Ąųė\ĩøâL­,gĪPg^ëĄŦŪž1]ãG{˙hÚ6˙,÷—¯Ė5­ĢrŲ”Ú¤ŧ#[w>™ũŨū›Īî|2žĩ2׎ےoĨ?íģžÍÛ~“ĖkĖ-§¯Ė7ÆÖäī7ˇåŲŽöœyú—rĮ)¯gÍē‡ķŌÎGķÃMËsũé_Ė7NOõX:{_˚WŸĘëGģRdl[ū{{w–-ē97N¯ÍČP{^Ø´6O­œŒwåąWʞÖåšáœ‹3ŖTÎîūļ<ˇamÛ}|—Ĩ īü���Ā1Š7_lŲ˛% ,øH¸ķ™ī|¤Įû8ǟsSžĩp ÷?÷H^™ča p÷įŋ9Ņ#���Ā„:\S™”Û‹����>îD���€LŌ{ē|˛ôuū,wvNô���ĀÉäJ���€ˆ.����]���� ē����@t���(€č���P€âŖKEá+0*ũ��Āa]Æ _ 4­zęD����“’íEˇŠ$ˇž}ãD���“RáŅåÖŗū4uU5E/ÃIT™ŠÔWOËįۜyĶæLô8���0)•Š^`éŦÅų‡?¸ŗče����&ۋ���� ē����@t���(€č���P�Ņ��� �ĸ ���@D���€ˆ.����]���� ē����@t���(€č���P�Ņ��� �ĸ ���@D���€ˆ.����]���� pPt)•J¨Y����>vFGGS*•yü čR[[›“6���ĀĮ]ęęęyü čŌĐАŪŪŪôôôdllė¤ ���đq3::š]ģvĨ¯¯/ŗfÍ:äųŠņņņņ÷ŋĄģģ;ÃÃÃļ���AUUUjjjŌØØ˜ĒĒĒCž?$ē����pâūĪHįJ˙Q§ß����IENDŽB`‚����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/register-verification-no-username.png�������������������������������0000664�0000000�0000000�00000063051�14156463140�0026120�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��^��|���öe���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨ}\Õõũ˙ņ‡†œcRA’°¸0å"ķāJŽí+P_•Ļ›–”jē.¤uåRįJl-p~—ÕJÚW“–…Ĩ%ŠßĀÖˇˇ¯×ž%Bƒ Ąå9\x~Ā“jĪûíÆMũ\ŧ?īΎõyîõ~Ųív;""""""""ŌįtDDDDDDDDÎW ^DDDDDDDDÜÄÃÕÂļļ6ūõ¯aŗŲhkkëī>‰ˆˆˆˆˆˆˆœ.¸ā žžž |b}Ë îsŧ´ĩĩņĪūŖŅȅ^čr'cĮŽqôčQŽ=ʈ#NČQN^ęëë<x0^xaŋvTDDDDDDDä\õī˙€Ë.ģŦËōĘYŦV+C‡íŸ^‰ˆˆˆˆˆˆˆœ†ĘŅŖGOX~BđŌÚÚĘ AƒúĨS""""""""įƒÁƒĶÚÚzâō苈ˆˆˆˆˆˆČ÷‚‚7Qđ""""""""â& ^DDDDDDDDÜDÁ‹ˆˆˆˆˆˆˆˆ›(xq/""""""""nĸāEDDDDDDDÄM<ú㠃_Їé5ûą6ˇC/""""""""nĸāEDDDDDDDÄMŧˆˆˆˆˆˆˆˆ¸‰‚7Qđ""""""""â& ^DDDDDDDDÜDÁ‹ˆˆˆˆˆˆˆˆ›(xqî€ˆˆˆˆˆˆˆ¸_ųUŧņf6ß|{”cĮŽ twž“Áƒsņ…CšëŽ$B¯ čîôŠ*^DDDDDDDÎså_TņōÚu4~ķīs6t8vėMßđōÚu”Q5ĐŨé/"""""""įšŦ?ftúΠA`ˇŸ3į¤āEDDDDDDä<×ôītúÖ AüûčҁîE¯(x‘sΚ2dJÁ‹ˆˆˆˆˆˆˆˆ›(xq/""""""""nĸāEDDDDDDDÄMŧˆˆˆˆˆˆˆˆ¸‰‚7QđŌ*֒0zc/ü~ˇ•ko&lô5¤î螈ˆˆˆˆˆČéķāŌ+ĮpC æÍIæá{“™G“¯ŊŠËŨˇŗ—Į@wāÜcĨōà ŦŲ”K‘ĨŒ†#Vl#Ÿ LŅffÍI!Ņä3Нé3~c¸%i*ƒŧē,oiialÔx~ŌRĮūŧŪøsG¨g+UŧœĢ…Ėģ&Ÿ’NNaÃLņÄĪJbVb<æá Xr6°hV ŗŌ ąt_Ĩ… ŽšŽ ĘNĩ]!Š×Œ"ltןą×L$aá r+úĄĢũ¨!ûNÂŽyœÜĶۉäŅ×°đCwõJDDDDDÎFCC§đđ¤B¨ãĪ/ūš'VŋÉûeDLģ‡'įDqų€ôōėu^Vŧ|žõ8ž~ūĒø;q÷¯īŖVkČžī.2ŠŦ OXIæĘ$Âē•RYËŪ'õžTō2"uėN^ŧI•/§†AģŦØã `ôÁn�Ę 4܌ũ$ˇeXüJÖ$uūûHí.ļĻg‘2̆5y/¯[zz*֒0ŋ–´ŧe˜ē/"""""rú.‰bŪÜB†:-;ZĮ_ˇfąū¯ í JŲšásJ&&ķpâLĖh`ÕUžt8§‚—į~ũ,ĮŽëüikkëüķž1vs O_|Íąk;†ũĐßúėØÖĶI+˛bģŒŦUIšØÆö^|ÍÆŌ D÷æŊÁBvú dåQyÄ †a™o"9eI&#`!-æV˛|‘›ŗĀé˜dN™BFĨ‘„Ėŋ‘įÔæ‡16%ķšŊd†9ė ÚZ8 ˋ›!á ŠV™O8 ˊ‰$eYIČü‹ck)[WŦāÅ­ûŠ´Á° hfĨŽ$ą—¤ĪÔäsÁ‡l ÂŪP ŗ~€GípÚ˛vĶ×ķāCãđ0ĖfįˆĀLüXâĶÉĖ­!>Éßũ}?XË Š´t7DDDDDä;1ōÄŠD:‡.ev&ë˙Ū}œG+Õģ7’é÷�?ŋ>){Ö°õŸũØÕŗØ9ŧ´ĩĩĀ AƒēüiˇĘ%×ũ0Û7˙ĸ­Å†Ũ~ŦŽl%?'FRæ¸ ]:…%‘ļ˛M6|HjÂBr‚˜’´ˆųŅÃĄļˆœĖl–ŨUČ˙Ŋļ“ĨfąŅF˛rwaą. ¨#/h("ŋƈÁ`Ĩ(ŋâÂ;›-Ė/ÄÆXϘĐā|Ā3hkÎŨål&+7›\ĖÄw9 9šĩ0<‰ä8€ļ.œÅĸ|JHaE|ÆÚ"rŌn%í¤Žų'Ņúˇ$ČžĪûĘĀüÍ/Ɲz?W‚M„ ¨Ŧp/Ö˛÷I[ą†­–Jl #ȜÄŌ´'ˆmĪe˛ī$6=˜Œ4X“ē•#Ioŋ, KöŌÖäRVkÅfÆhs)iOߑį4ė"si:Yģ*Šm­S—ąôĻāŽížFîŌĩäVcņ‹2ČH:~ī*ˇ.aéšąTÁf0dJ$5m%ņÁŊ?í˛­ŗtE.eVđ ‹'uQ÷š•rW,aÍ֎āp8aņ“ļ*‰0 rídâ3*|’Fo`lZ›’üąZ˛Yēb-še•ØlF†…E3+u‹âTN$""""rVšd 7ŒÚmáPÆÎ}Š5˙lŠc˙îō>.ĻÂjåķyėJÂsÛŗ?§ÕiĪiSndę”]jGŪĮlĪûØ '1đΊ9^ÚÚÚ€ŽĄKĮO[[ĮÚÚ°6TŅōíŽĩ4c?ÖÖGG.ŖČbC4ņ'||'…é+Či#õŧ¸l‰7ŨDâœedædK%Yi¨ĸãc1Øö“īüaĸÂ]Xˆ&>~ –"j:W”’_tÂâ0ģx‡ũîm…“<+ lšlũ°[ĒiÉ&ˇ‚“CI,™dä[1ÄĻ‘ŊjIįĩéa°ÔöÁ•; īãą´‚ļœ78–û8XžãĖ; 5ÔÆ;ū]ķ>)ˇĻ’kLâŜvåŦb–5›…É+°´īb4Ö]ŦŲdeūk›Čœo‚Â%Ė_ZHĐĸ7ČÉ/`×;+‰ĩf‘:-•�XHŸĩ5•&–ž‘Ga~ŗ`kĘ­¤ļ_wŖŅÖÉČ´2+k7û˙ö7rRŒä.]JVĮÍŗŦ 9u+įbSnģŪy‘YÆI™˙<§œęσe ķSs!éErōōČZd&?ũ…Îķ¨\{ŠŲĩ˜Ōį“ķÚà /ZBrûW´‚|†YÃaxŠūÆĻ$°~ČŌ얐ī?‡Ėw Ø•ģ‰´č#d-ŧė×]‘1ôĘQēZq´ŽũŸ|ÂîO˙ÉŋšZņ0¯é¸ôĘ+đ8 ŪĄŖŪmˇíyŗÃE¸r>‡.p/Îa‹ķOkk3ĮÚZhkmæXk3ĮZm؏õUÅK €Ņ‡a}ō‰ŦB6m­…āXb‡7ĐĐāôƒ‰ŗĘ>$ŋŒæ8LX)Ę?ūĘ[˜_ˆ-ØLrŧ Ę Ųߑ'4맍†GĮē um%Í!Ú`%?ëÃ.…4–ė]ÔÆŦYŽj‹ĘÂBj1bžu]˛˙D’O2ÄĮ-*kaū*Úâ~BkZƒ*{ņfß-›ąÖ’Ŋ4"ĸ™Õ^–R–ĩ†|I{qąÁūøĮ1˙Å'0Õd“ŲLąŲŦ„%¯"ŅN˜?ԔUrÄ`&1ŅDŋ?ūa7ąčĩ˛2÷kײ*‡“œą’x“?>ūÁÄ.XEęØ#ä:]w›ÍHėüáZPba”a鸭ϞrrČ\GX°?ūaqĖOŽÃPYDQ/Í R;<‰Ĩ‹âō÷'ȜDÚüŽŠcPŌklĘyƒ´›LÛĖΑĸÜö€ĮˆãOŖOûŊ7Ƒš“Ã֌9˜Ãüņ'~Ņ|Ė”‘÷]ƒ1q ¯K|ŌmYËá}Ŧũ¯ß“šeooø=ŋzî÷ü~ˇ•ą×]ÅåWŒ!qf ‘—^sŠ‹16Ũ×ķ=tsl¨QkkkgĐtų{[k3öÖVŽĩ4ĶÖÚ öA}ŧ´ŋ4ZÁvÂē˛oΞũŨ—G“ž˙$ēĘjʨ´e™$FgöpĖJū¯‹&6 2,j0áß^‰2<ڌÉd$Ėöšˆ7ƒĩpe #1>Üu“>gЖO"Éæf‘Sķ’ũ,lƝÅ0v Á§V c¸÷74 8ŌÃųēi­Ŗc’Vu)qëIíĻ[ ÛÔu™ax,НŊ„cz—Ę,•`J!Öų}ĸ‰õˇ’™o›:Š0b˛ s<ŖI'õV˜?'‘Øč8Âüƒ1ĩĪÅSVdÁf0aî27aĻáØļQÆOUE†0ĸGũ øā|e} b-KWäŗŋ˛†Ģ ĢlaíUļŅ@eå aŅ8wÅh2DžsרĖ^AZŽ…ĘšŦVVŦ`´žäĢ^F|Žė'-c)–˛jŦX­6°Y1ŲŦtūމˆˆˆˆČYč(ûwäđˇ¯ģŊ]ĩz0eËēOėŲÃK˜sĐrž‡.pŽ/Î/Đ5xii¯tikąaoĩ2Üī0ŸÛ{ķĒŨÃ˙Ī}m{åK—R#A‰I$ÕŦ-ÚJŅÉFÕXÛ_LÃæ°&5ÖõĢĻ҇  €`ĸŖ‡Cv!û­sđˇî§¨ŌHtĒ üÁ4üųųĨ`Į’[ˆÍ×åeŋĢ3iËH|r<†ü­do­ yApû0##ĻE‰tLQbkßÖUeqX×xāl4,!ƒĖa÷Ä8,˜ .!’•#G€˛TLŖSOØßæTdđq 9ęļ€Mīąfí6-š ›‘acãI]š’¤0#Ö+ēĩé^du ũNN4lŊ—„T æÔUŦI41ÜŽ 6Ĩˇ8bsĻˑŒF§GßJūã7“’LrÆ*ŌLA 3Bؚ›™ŗõ$MWl ųŽtj㗑ūb,aÍ`ÍeQė}‚]DDDDä,Ķôu-\ŅĨęĨĨÅÅ{ö×Åü~y7<ôˇt ŋhú†¯NŌö÷!pépN/Ž*^:ūliiĻ­ĩ•ļf^}ÍĨ>˙bČ}õņĒpĮÄ´9ûÉ)´’x“ķë¨ķœ•8gšįR”s’æ||/°† ÆÆÅu†=1Ś–ĩ‹\ Ä6ėĸĖ0–d3€‰čh#Ų–ũ4�ųû`0ĮëŽļâ’Hļ•M›rŠ\°€šė9bˆ%ų„o,[ąšx‹n¨m8qáYÆčŒ)Ŧ‡Š!Į D/ckډĄ™Á'øäí‡ŨÄĸU7ą¨ą|ČkéŠ,ģ ü÷Ž"ȧ{ĀâĐS ãZųŲųØĸ3H_×%(é=#FØŦŽŊ:ĪąĄ`�쨚{„Ņ)˙:sōãTæfŗŸxÖŦJ:ū;ĶpzŊ‘ūqô‹Ī¨"‚Î%C{ãD ŋÜÃįßt `üÂųáˆã˙l,˙Œ~žåķŦuNÍņâŧt˙iiąrŦÕF[‹†¯. ĸÂ[s÷Ņhß]lR"Ãą’Ÿ‘NáŠ7?9Ÿ ‚†á˜SÅÕúî/¯qņDް?ŋ”ĸÜBlÁq˜ÚߨŖÍcÁ˛‹ĸ Įœ,aņq'¯‡8ŖļĖ$'Ae6Ų–]lĘ=‚!>‰x§‚‚€#TÖt•ļRYyvWģôŽaĻ ¨,ÃLĶĪ0Ŗū'ų0OCŲ‡ä–ŋ.ūĻ›Xē(‰áGĘ(Ģ€ h›…Â.…)5ė/ĢÅ`ŠĻû7…\ŗâøĀsuJš9ų.†Éõğ  #”u™Œˇaaû$Ā€ÕQģäĶå„ĪDûj×ŊĢm�ŖąË$[5[ŗ“ö*}9ģ|ũ)ū´kAÅ#ãųų/ɚįSHŧâøōĢ&ÆŌų ^GaÁįŊšîáû✠^€‚—æfĮZ[8ÖbŖÅÚÂŋę< ĪæxĖ‹Xš0 *70˙ÖįÉw9Ii–ėĮÉČĩ:†™ôÜ ņÃĀ–OæÚŠŽĢŦģHr cī{ßi[3ņ–LļaØXSįäš>Ņ&üm…äfîĸŒ0âŖOõIŪ3k+lVŖŠ$gE:šG†ŸÔõÍA&ðR¸Šë$ŧTl ĢđüxŗKN!ēa+‹KE 5Ĩä¯Ŋ—ø)7“féyŋÚ­é¤Ūõ™–RSĶ@ME!Ų™R;ĖÄØ` n>ÉAĩd-YAnY ä¯}œôũÃIœ˙“^Î~âĪč°aØrŗÉ˛8ú–ģâ!˛ ąQ‰ĨŦĻWųFlR<ÃjŗI[ņ!e55”íÚĀŌ eĮû` #:,ÖRXĶ@CÅ.Ōī[ ą&°Vđí_aæ Š +¨l€ h3†#ģČÚZJCC –­“R„yTZ8ûkĸDDDDDžOŦüoθLr1^í_šö¸r"‰×yuŽų×'9äũŗ_:xN8'‡' 9jmą:æximĄ­ÕÆ AëËāâWŊC:wą4'“ųąYEĮ26ČŦ4ÔVRThĄÖ† DŌ_\ÕĨ¤;ķĸ•$ä/$'#Ye)ˊÂX[DNf6ųĩÃIHsū*ŅņAØŌsÉĩ‰užÄ%8ŽąÃ2ÉŨšmxŅÁ§>3j+8‰äč5,+*ƒásڇ)9ŸØ|RÆn%-)I ÷“•…lÍļāc‚üķ ØĖ˙'dži+֐œŠ #Âc™•ņ‹NR–ļč 2ŦKXŗtGŦ`Fų&Ō7u U gŅĻņYšÎŌ[7pÄfdXX,ÉkVąč4>cn^ö÷Õ>NúŦņ¤‚0ĪYƚUAäÖ’–2…Ô5{I;e#+ÉJƒÔôTŗaxX<ŠËÁ]KÛ7f~Æ2, Ķ™› ÃŖI\ēŠ ŗkQ*i ÷bČû#I‰ķ™’ĩ”ŒäļÎßDÎĸedĖē´% ˜Æč¸‡I˘kĘHÎ\Hŧņ5ŠVöŅ7ÛEDDDDäĖ}]Ėú×ũx`n !CW %äę`†ö`æņÕ.˙út3/oŠ ¯&ū8 ˛ÛívįøûŸj֑Ķ<Čā ú¤ûîŊ‡)SĻ8Úėŧxŋ@äÜÄŋëĘ9Öâ^ž<\Ë´_|Ø'ĮvVS˜ÍkYŲä–QsĊ #†áÃ1™âˆOHfÖMÁ]Ģ*֒ŸNeÂė_åôRŲ`!;ũ˛r‹¨tzONYD’Š[ĩIŲķÄ&dRK4+ŠūHRįj+š÷'%ßʰ„7(rnŋ§ã~—ļœXˇŪKtj>ūķsČ]äb>Ģ…Ŧ%+ČĖ-ŖÖÂĸIL]ÉBÛĖŠE$dūŒ¸w÷H]ēâŒö÷đÃ-IS™äåzƒ–:öįåđ֟+h:Ŗ#žŒ´eg´ŋũX[õÄĄĻφāāā.ËΊāeŪÜ9L›6ÍŅf÷Iv‹žįš‰SkmĄÕö-•‡J˜öTnŸ[Ž+\2‘9[ƒX‘÷ĮöO,‹ˆˆˆˆˆČŲėLƒ.Ŋ2œąĻQ_r1ŪCáhS‡ĘJ(ųûįTĀėįBđrN 5z}ũ:JKKimme˖-;vŒļļļ.Ūú- s1/ÂĶx!C< Øí}9ÔH�([KÚÖZ†Å¯$QĄ‹ˆˆˆˆˆČ÷H+_}ņ)ūấîČ9åœ^æÎģ{ ģđ=gĨėÃ)ڟOö†­üą¤/:ÅדDDDDDDDäÜ^d Õ˙b*eF†M$}å*U숈ˆˆˆˆˆô‚‚é…`æį|Æüî†ˆˆˆˆˆˆČ9fđ@w@DDDDDDDä|ĨāEDDDDDDDÄMŧˆˆˆˆˆˆˆˆ¸‰‚9į 4h ģĐ+ ^DDDDDDDÎs^]vû@wŖīØí\|á…Ũ‹^Qđ"""""""ržKž3 Α ‘^4ČqNį�/"""""""įšĐ+ypÁŨx]tŅ93DĮ•AƒáuŅE<¸ānB¯ čîôĘ ģŊk­QEEūūū}{Áôi{""""""""gĘ~Ŧ­OÛĢŠŠ!88¸Ë2Uŧˆˆˆˆˆˆˆˆ¸‰‚7Qđ""""""""â& ^DDDDDDDDÜDÁ‹ˆˆˆˆˆˆˆˆ›(xq/""""""""nĸāEDDDDDDDÄM<úã öcmũq‘ŗŠ*^DDDDDDDDÜDÁ‹ˆˆˆˆˆˆˆˆ›(xq/""""""""nĸāEDDDDDDDÄMŧˆˆˆˆˆˆˆˆ¸‰‚7Qđ""""""""â& ^DDDDDDDDÜDÁ‹ˆˆˆˆˆˆˆˆ›(xqW +++ûģ""""""""į4ƒÁpÂ2—ÁKXX˜Û;#"""""""r>Ѝ¨8a™†‰ˆˆˆˆˆˆˆ¸‰‚7Qđ""""""""â& ^DDDDDDDDÜDÁ‹ˆˆˆˆˆˆˆˆ›(xq/""""""""nĸāEDDDDDDDÄMŧˆˆˆˆˆˆˆˆ¸‰‚7Qđ""""""""â& ^DDDDDDDDÜDÁ‹ˆˆˆˆˆˆˆˆ›(xq/""""""""nĸāEDDDDDDDÄMŧˆˆˆˆˆˆˆˆ¸‰‚7Qđ""""""""â& ^DDDDDDDDÜDÁ‹ˆˆˆˆˆˆˆˆ›(xq/""""""""nĸāEDDDDDDDÄM<úíHļíĖģ6•üæž7ņôôÂ;0„qŅ3¸{ÁO™hčˇîu׸yQ‹wCĖJŠ˙0ī뉈ˆˆˆˆˆˆœĢ âÅßĐp"Âģ˙„āįmŖž|?yo=Í?žÍŠüÆūī^ŗå=Nøčé<_2Đ=é[zļEDDDDDN­˙*^:ų’°z3Ë"\¯ĩ,āwËSyĨ¨”u>ųãUÄ@š‰÷Ėĩ”OˇáĖĒnYhfā*wDÜEĪˆˆˆČŠusŧFÆđÄËĪ2Å húˆ7w`Õˆ.PĮ’/û¤+"g=Û""""""ŊqÖ/�xgJ@3KĒO\_g᝕ ™ųãXLc" EôįōpÆvÚzhŗŅŸœËäE::’đÍdŪJĮö2Ļ::’ÉĮĮL4n^@ččHBīŨB—č§ą„œŒĮ™ŨqėŅ‘„_;™é÷ūŠ˙ίÆÖe˙8ž(8Ä+?ql“Ņu\FcÉvžd.“d&|t$Ącb‰™ĩ›K81r˛°âÆHBGßÎW5˛įÕLž6ŠĐ1sy§Ž7ļ‘›ÍÃŗĻ}mûuč<žÅÅņNŌRûõ1=Y�TŗëÕĮ™yŖãÂĮÄ2ųާøcqO-Öągã¯øŲŦÉD‰"ttĻMgö“™ėĒrē3™>:’ĐũŠ='´QÎīÜ~ũÜ~bߡ0oL$Ąc’ÛĶ3áĖVNîĢ=Üמ/ncÉV<r{ûũ‹"ÜÕyœÁöuÅoŗâÁۉinCĮ˜‰I\Āĸõœ¸K‹Ž$´Įá?E,ũ҉ëw=b&ttķļŲ ÎÂ;˙ž\;™™d˛ĮéôöŲ‘jÔļÎ�ã„ĸ“ƒo3/ųiōëÁ3`,q“'âghä@Án>Xģ—ܝģy5ëYâüœöi,`éO~ƛՀW8ąĶÂņŖš}›S™Y\ÍĪC›Įō:E…KcK“đfy3žc™0y"~uåöd“V°ƒŧ§7˛qv(†ˆŠÜ?׋]wPŌėEԌŸ2ÎÛ@@´ogsuy3ķŅT7{âM|Ė M‡Ø“˙'Ö-ū9;3Øüō4;÷đlŋÍTo{šWVa0™0Bđ>eqN#ģžœÉŨ[žO_"Ė™āk€úCė)t/ˇđUļũ&ĻW  žxļĻ/Éyd ?ßéI”y"ņM,.ĸ¤č=–ŨUŽíũˇ¸g¤ĶŽ6 ŋŋké–&đ Á;x/U%{É߲šÂm;¸˙õ<å #'2!`5%ÕûŲw&8ˇSˇ—ürĮ_›‹ws€iLp>Lņnö5ƒgĖ$&œęÚØ,Ŧ˜5—uĨÍí}O '4V[ØUMZÁŸ(\Ŋ‘W§tŲíāæ…Ė^ü'ęņ$ĀM\ԕ[(ܲšÂmq˙¯ķD”á;noãāÆf/ßŨžũDn™ė _˛¯`7ī>ģ›œÍÉŦãL8ÃĄxƒhÂVõK×<Å;ĄLˆšD¤­š}…û)Ūžš;Jëy÷ũ_0Î@¯žmigīæĐĄCŨõ ë6ûÜĢ#ė!Ŗ&؟9pŠmonß6ڞ’kuZqĀūr‚É2ĘdŸôDžũp—öĢė›ˆą‡ŒŠ°}`§ũˆĶĒ˙M›dašišũᇠíKn2ŲÃŽŽ°‡ŒŠ°O{å`įĒ#īÎwėsĪæÎļŋ9ĮŅū=oŲ˙Ņ­ËGö­˛Oģ:ÂōÃĮėîėrĄ}ÉõöQĶėŋé~Î˙xË~ûÕöQ1öīVŲĪŌ~x§=õ†{Č(“ũö7̜V´˙æ&Įuà {ęŽÃö^;°Ę>iT„=äęö—t9šŨúųëöW÷ĐĪžä>daųa´}ü­ĪvŊŽÖöß$˜ė!Ŗ"ė͊WØ?Yæ¸a ĢēîcˇÚ˙žnŽ}ė¨{Č Ëퟴwņ“'÷ôöwģžë‘˛‡Šļß~Ī —ũî¸į3ÖUŲOåënsœK÷įÃnˇ˙ãŨųŽ>]ŧOvģŨnī¸fWΰ?ŗĮy§#ö˙MŸŅŪŪ*ûßŋķ™ųČ#�� �IDATökĪ͍IöÔÜn÷ųHąũ™ŽëģŦØéŲ)´§ūđd÷ŅõķøÉ˛öߛFÛ'=ąŗëīUįsmOŲe=e[""""""ßgŽ2•ŗn¨‘­ę#V<øœãŗĶĄ?åîØãļü,^)mߟ’öL ÎE-¸õé‡0{BSūr:‡FXø`į—€'SRëZāͲįnÃī$Ÿ¸vVUîöä1ÖŠ ÅÁ;ę!^}c=d/>u…°īõL ›ÁkōbŌgtĸÔo˞šŠÍžū6Vyy4ŅqËbģ\“ķĘ˛—2x~õJîŒčÚAÃȟrGĀ!ö•ôjĖŌqMžÜúLˇĒ CwĖ  ē¤üø0 ēíŧ˛ųK „{~Ķí^` rŪrîĒw°Žũ‹Vã&Į 8ŋŸã#klČßOŗg87ß>–�ą§Øšßåė)tgJL×*Wŧcâ…Õ+ųŨĶP=8ũ6âŧ€úũėĢ:ž|ß[(nßéō„Ųy'oÆĨ<JBčB=ËŲwđ;n˙z%Íā5ų1–MévŸŊM<‘: / zÛÛėéÍPĒ^h2L"í™I]¯§r‡Ųhr=ėODDDDDDNj�†ՓķčLöxž¸ÆÖTMUuÍ�žYņŌcŒsĘė,ĸ đŠ™Ôey'ŋ‰L‰€B‹…ÂbwN1@])ĒLÄFŸ8&ÃõSBŗxĨüÔ=ŒĀ“/)ßŧšßĮ,įŗs`b 0*úԍ�ĮƒOÆMŸčrhˇyã<w_na_Œėöî=.f|¯†uō‹ nŠĶ§¤lÔÕ5ĩ‡6lO ™ÆÆ^ĻP&2ÅÅĒü|ņšmM4ŪūĶĶ>J\ėŌKŋd_Á!˜bÂ`žč¸–"0‰q�”“_TÉL0‡’ī™ÍŽ‚Ŋ4Ξæ¸&uĮ0¤€‰]‡'õĀ{d ņNÛŲëhllv\› @S#õM[ŋ‘ąĻŋëcˆ!ũv:-øÛ×9Ųõķaˆj>š,–C\_ ;^ŅS]ü^yãā ÔĶXßG ˆˆˆˆˆČ÷Č�/Íԗ—RßĶę€ņÜ1wwΜÄČ.oœ6ĒĒ{Ų,Y,zä=—ģ×Õ;Žq°ē€úję�<}ņsY JD¨'”Ÿ:p𛾜eÛæąŦ`7éÉSøo8ĸĮ;eq1ŅŨú{2õ8Šgš9¸ņiÎsŊŖĀĸš’jčZ†ā‰_ĀiTģ´ŗüˆß­É$§`?ÕM§ŪžW|pՓÎ÷w§wõēęjš�÷ōĘ#ģüą­ÚqęĢËiĄˇw4ąoŲ˞ƒ0n$Pĩ—=Õā{쉑Ū!ÄFAžĶ</ļâŨhßčIDöō4Ēō3ųŨÚ÷Č->DĶ)…ŽûįM oonúwŨŪ—Ā€ļ÷ö%Ш¯§ŽčƒāÅÛĪû‡>ÍPNDDDDDD"xÁŨīīdYˇÅÆŧ…L~čOÔ7zažŪ=t°aŗ9^üšËwķÁ)*Tlõ@�ØÚ'ę5zxŠ4āíe W/•†PîüÃŒÛö¯lÜÎŽĸRōˇ—’ŋ=‹4ŧˆ˜öiKæ0ˆ [{ Q]´ƒ“āhĻņ„BƒžÎå$G,ŲĀėģžŖ¸ ŧ§r÷ôh"Ŋđö4�6 ×ϞÎršžnÚīMĨäm/=ųÆ'0Á–r -<0Ō›ÆĸŨĀ“¸``\TYČ/ p `/Mx1a˛‹ęĒ6/`úâŨ4áEčä$î1áį퍷'@9o.^M~— Ēãūyöō‹ãîØŪĐžÎFŖÍ§ũD¸jŌEšˆˆˆˆˆˆœ‘ŗæĢFŪS–ķDĖ^ž(ø+–Ą—ģÍ5ƒˇc8Lč‚wŲ™ÚË˙‹ŋ#pąõ4L†­ét†Px9ũ1^˜ūØĒ9PXÄŽ;xwÛnJļ?Įėōz6ozŒČ“ž0xõžÜ‘•Ošų4˙Ô‘“ņ[Š›ĀwrÛ^žÖíÚÚ`‹'ëÜ\ŅāíŨūbŊœÂ7nsY)ãJääņŦ=Äž| ļ™Ņė˷Ќ‰Øö/�EƘđ]ûžcž—ˆ&Į0ΉL1÷"Œ°đüĘŨ4á‰ųŠlœÚm/ō—]‚ŪŪ@}“‹PĖ•ĶßŪ`�š›z~l;ÃCûW‰zGƒ…DDDDDDú×Y4šŽˇ>õQžPŋķiVä5v[o 0ĀņšÚēĒúŪŋ@zų:æČhާŽ{“�”S܋aF.ˆŒÁĪŦeį˙dpŗ/4—žÍēÂSõ.€_€FĒĒ]vĒĸ°¸đ%nn÷Đ úģ_ƒĶā€@u9U§ÚØYÄ$ÆyA“e7lĨä[š t":N$b"‘žŽJ—ÆēŊä—:öéÕg–Ë÷r  đœČŗģ‡.@Ũ!ž0..€Đ�€&ĒĘ{˜ŒØfÃÖQmuÚÛûßžē‡íĢŠjtlč<p{ŒË'°ą§ßq—ŗ(xFÎ!-%Oęų`ųsėęö’ĐT´Ŗ‡/šØ8˙{:íÎH_€Rō‹OÜÉVōšŊ˜X9ŋ…?n,Āå̰ßDĻDx6ęNYAĀķ ™=Ûvãú]¸œ=yEŦë‹…Ž6<ņvQa+Ü@NĮ5čšÄâŒ9&„ĒwķA‰ëmęŠ?"ˇ¸ēë51˜˜bö„j û ‹ķģD›čœ×ÛDl4ą¯¸ˆ@Ääņ'|yĘĩãCŅNĖilx}…',`\Ô�öíÜíây°°ôĮ㈸f + ŋËöĄíĪėÛļ×åķŅXø‘cĸbßņÄv^CûũmĸÎÅsĶX¸Ũąˆˆˆˆˆˆô›ŗ+x"į-įÎP ū=–Ž,čōŌiˆM>žî—u̚°Qĩí)~v_ w$?ĮŽÎ÷N7ĮøMäŽŲĀį÷ŅÆ"žō=ęŧzĶŗz>xv ˖/aŅÆō+ Ē>âŨâf €ČЎWøŽy8ęŠĒęēGäÜų˜=Ąšā9mîŪ^ģ~™Âŧ‡æ1sņv×AĪi %"āKví,érŦƒ ülņ^üLŽ‹PWŨã´ĮgÎ{÷ĪâOūš=ŨNĖV˛EĻpŌ\~WÜeG&ʀRō^˙ˆƒx2.Æä´ž=بßËēˇ,41‚ fÕ+Ž˜é 4Yø ØųiŗqpķS<ŧ͋¨�€zĒœÂŒqˇĪ!Ęš VŗtsĩĶ5mäĀĢŋ&§˜Ę-æ3ŨūˇŦČëvĄę XąęO4áIÄÜ9Lč\BT¨ãĶĪģŪú¨Ësc;¸…EĪî…>›ÆĨįg[DDDDDDŽ;kæxéd0ņķ§~Jî}īQŊåižŸš´Îš:"øųKË9ü4ų[R˜\0– ŅĄøО맰ŧ<C¸ãšÅÄuVvˆKy”؝KȡŦfæ?".:?ۗė)ØM]Ôržũ-ËļŸę3?ĄÜ˙t2š÷f‘ŋüfĸ_˸đ�ü ĐX}ˆ}ÅĨÔ7{zûrîīœ~ĻũE¸ē‰Τî­�¸WŸ™„wām¤˙ψŲOî oņLbÖG3!ƒ­žEE”Ô7CĀ’ūôŒ^Ī…Ōŗ�æũ'ŋ{ôO”¯Įô’IL€Ær ģŠęųøz^đ[ÍdËnę7?Í<&rķė_pk|)§+–ŦbQÉŌ-YÜqãGDŚéÕĨė):D^D-XÅQ]÷ô‹žH{),ØŒīœßĨCdŒ ¯×w_�øū´‡ĪUģā=‘ûgŽ ˙­/y÷Ū™TMžČHī&NJ÷˛§Ü‹;˙°žØÍ7s÷–&v=ģ€EE͏#å6ƍœCú’ŨĖ^ž›ŧÅ͉Yk"2Ā@cš…âę&đ įūÕĩūø.Û?SÄėÅâŨ‡nf_ôDƅzAũ!öîĨē |c~Á ]æ¤ņ&nî4 ŪŖzg*“o܏_ ‡ØW\ßŧåܙŋ„WJû"(9ÉŗŨ­‹ˆˆˆˆˆœ/ÎēŠ�īØÅ,›ė |ɛËË>§÷DÃČÛX˙?īōü‚ŠDzÉžīņî–ŨhôÂ<ãQ^y3iąŨ^ũgđjöwŨ~ōļŧGŽĨ‘ȝ˛íåÛėåܤۿ_°íũ5,š1‘@Û!öėÜÁģ[v°Ģ¤ŋ¨Ÿ˛čĨÍl{&ÚéÅĶ›„§Ÿå“/žM‡(,,ÅyHāôUŽöĻ™đŽˇģå=ŪŨiĄÎÛÄÍ 2øāũIčŨx™Sō›žŠÍĢ“‰ 5PUđonūˆ}ļî|i#AāôGY69¯æCėŲļ›wÍb0ņĀĻxķé$ĻD@UūGí×ĐF`LK_ÛĖÆT_#9‘ s™„;ÍīŌŅlÔDĮ0&Ā+zãz=ßŦ7–ŧÎ+ ū“ī&ömĪæmęüĻ’ļi#ËĖ~ÄĨ,į–p/¨ļģĶŌYI2röZļe/įŽ˜P uō vsĀæKÔ´Gyåũ<Õõ9<íígžČÎė•Ü=9Ęw“ķV69…‡0„NåūįŪeįncdˇķôŽ]ÎÆ—îcJø¨ÛOūÎŨėk ū™lLH@ûög>ĸėäĪˆˆˆˆ8 ˛ÛívįPw‚ÜÍÜŋŗķĶģØ8ûĖëKDDDDDDDäûĮUĻrö 5rƒÆƒEė)­Æ0‰„nUØJÉ/q|ņgd¨Bé;ߋāĨŽā9îļöâũ,qųŠëÍ;Õ@ĀrKÔI9MߋāeäėåÜŋy.¯”žĮŨ7e6čeŖŽt/…åMŽ yŸ~ė4æ9ĩīĪ/ļrr×ŋÄē-{9PUOS3xú†=‰ģv ú‹ˆˆˆˆˆˆˆ|wŽ2•īOđ""""""""âFŽ2•ŗōsŌ""""""""į/""""""""nĸāEDDDDDDDÄMŧˆˆˆˆˆˆˆˆ¸‰‚7Qđ""""""""â& ^DDDDDDDDÜDÁ‹ˆˆˆˆˆˆˆˆ›(xq/""""""""nĸāEDDDDDDDÄMŧˆˆˆˆˆˆˆˆ¸‰‚7Qđ""""""""â&ũŧØČy0ŠĐŅ‘=ū„?øļūéL—~å>Eč][h8ø[&ŽeiqŋwDDDDDDDDÎCũz´€Ÿōüs?%ĐÕ:ßp ũÚ�‘s—ķŧÍ4�Į‘ķ]˙/^!Œ3G3˛_zræÜ:Н‘ķŌ92Į‹…Ĩ?Šböú"ŪyōvbŽ"|ĖdfŽüˆēē"~˙āLĸ¯"üG3yxc‰ĶĨjrW.`úĖ„Ž$üÚÉĖ|ōmtnĐm¨‘ˆˆˆˆˆˆˆHę÷āÅfŗøĶ‹ũ †fŧū%Ķ×°ķ¯Åü&œƒ¯?ÅĖ{7`HYOŅ_ Ų6דܕΑSįØįāĢ)<ŧąžqO¯e۟ōØöō|üŠžfŪ/‹`>ųžéߥFĨĢšųšÕ.VŒāî÷w˛,âäģÛ"nãįą~�ŋ˜IŒãO4Īįžo�FNžDāĒ,ŠËáV?œš†Í“=9Ōąˇq˙äLnŲų‰&˛OODDDDDDDÄY˙/ĄIüîš'NŽkđ"0´ũī]Ē` ŧ ß††āŨš7OŒáԎolØl›Pĩų9V<ZJU}S{…Mx÷ŽĘFDDDDDDDäLôođâ@d”餓ëX3›×~ŲųīØÕ{X?Ũņ†Ž?zúQ#ģ~9›ûwŽāîgž%Í4o\;›;ļ}×éŊū ^zaäÜ5ŧ;ššķßŪĄŪ'Ųú$l{yw[=)ëY6=´sņA[ķIvé;g]đbđ‹`œ_4dŗŅxûy9-+āŨõŽŋöÁ!DDDDDDDDNĻƒ—ĻCėË/ ĘÅ*ƒ!€Hs(ßąžåDŪá˜CáwŗØĖH[)ëž} [ôXØyˆ7˛§aJ""""""""gŽƒ—ę÷xâž÷\¯ķœĘ+ŸŽ"žĪĘ=ŋYLņŖ/1ī?_Ã0ž„ÔgIļ`ŗ<EZR †÷×ŌÅ5""""""""Ž ˛ÛívįPwDDDDDDDDÎMŽ2•ÁĶ‘ķŸ‚7Qđ""""""""â& ^DDDDDDDDÜDÁ‹ˆˆˆˆˆˆˆˆ›(xq/""""""""nĸāEDDDDDDDÄMŧˆˆˆˆˆˆˆˆ¸‰‚7Qđ""""""""â& ^DDDDDDDDÜDÁ‹ˆˆˆˆˆˆˆˆ›(xqū:PsK+U˙<ĖQĢæ–Öū:Ŧˆˆˆœ.¸`0Ī!\Ņ0d ģ#"""Ōoú%xiniĨôķJü/ŋŒĀ+.ĮsHŋå="""âžŋF€ŋ_ŋ¯íØ1ūũīŖ”Ŧ$üĒ ž _DDDäûĄ_†Uũķ0ū—_Æå—ų(tųē`đ`ŧŊ.âĸ ‡RQU3ĐŨé7ũŧ|ķīŖ\v‰wJDDDÎb]4[sķ@wCDDD¤ßôKđŌvė Ö<ž"""ßw L[Ûąî†ˆˆˆHŋQ""""""""â& ^DDDDDDDDÜDÁ‹ˆˆˆˆˆˆˆˆ›(xq/""""""""nĸāEDDDDDDDÄM<ē"""rnøöĶ×øÅũ…#í˙â‹Ķf0r@{%"""rv;‹ƒ—ūúú¯Y˙÷Öˇđ¸z6ĪĪ`H?öĒĪÎeEF1Ŗz‚ÛƒÜsˆ–¯>Ŗp÷^ ËǍũúŽļz0ô_‚C¯aâ{ųé_ÁÆOū›%93˙Ų$ÆēĄĪîn˙M{ųŨŠ ķKŒéÍ-ėũ×dMā7÷ãB—Ûԑˇz ŨÍŗ3Cú´ģ""âHcgčĐrä_|;`97œÅÁKģKĸ¸3i—šZįån‡.�^×đ“¤+đēÔåŗ7˛îãožîzĻ&M%đō‹Š•Ưk(ųKīž¸—=‰É,¸ÎĪ8´°˙õtv›åÁ¨ĄŨ‘1dČP†�- †žû˙;,"""âfgđbô#ddÃēî2ô Æ^w…>Ęg[׹î` w-ē‡H/įuC¸l¨GņCS./üa#īú=Ā-AúĪįžÕķŲ—­`č~ˆˆ œ!éŧ\xa""""Ōáė^Né(Ÿmū=/”]ÅʉŒjĪj?^Ës{pË#÷0ņŌ¯ųøÅÕlö˜Ņķ°–:ūš“ÃöOĢ8üM+û<æFnO¸†áím~ûå>ļn-āŋ¨į(syhˇĖŒ'˛ŊZĨņ“˙fyž/wßy ģ7Âg_[rÉU$Ū9ƒQ‡ķxcĮ§T5÷•QÜvį4GrÂPŖ¯Ųŋ5‡íÅUÔ~cŖWĮpÛĖņvä"_•đūæ)üĸ†&ĢC/ņgė S™yũ:ĪíÛ˛ŧņw_n_˜H¤W ĩŲÁ[JE]+C¯Œâö¤öŧ˜Įe÷Ļp÷Ÿŗ*¯”Š÷]Ķž UÉáíŧOŠjōĀûĘ(nIŧ‚=/æĀŋ`ÁWM>ÎakAĩß´2äÆŪĀĖë¯āÂÃ<—ņ ÁNÊZ>}“'6”<c?ŋž=:\Ās˙ĩ—Q?{Œ)íūƒˇ~Ińߨ^•Ķ´—ß­ČĄĸËPŗđÖ3ë¨ē&‚¯>ų×>ôˇ8Ũú2—˙ULČŊrWŽķÛū)UVŧG„3uęɯÖ>ĩ‘ŋĩoūš”ÍQ<üL [}[ų ol. äŸV†øbžƒGĪ#åDDÎ=Ũ+\Tņ""""rJįÄWZZZNüé\;”QS0SĖ[y˙t,˙jo}\OđÔLŧĀČeW^Å5WúôxŒĒŧŦ˙ԃ‰w>?Ęãˇgȧ›xyĮ?|ĩõ¯laŋĮ8æ=ō(ŋzdZ‹ÉüÃvĩwÆcˆXKŲžĮHâÂ'XũôŨü‡G)oŋ–É[åĖ˙ÅSŦūÅ ‚īáí˙á˛ĩodŨžož™Ėâŏ˛xn ^å9ŧŧųPû9ÍĮonâ˙ĩ^Å]÷?Ɲ?Āü|8”ŗ‘ˇË:ŽJ…ÛK>e*cŊ ö“,Vm­!8q>+Ÿ^ÄĪo€ŧ?ėāsü5bÃMãūE íģˇÜAfv)\=‹ĮS`ŪøøíŠh\ū'v ‡ļŽã÷y '&ŗtq LņåPÎ:Ö˙åk¸<˜Q—XŠ(¯ëÜŖâ˙ĒzņÅT—WuŪËoŋøŒZc0cG8ˇíĪØ+ÔŦęœG Ĩō3Ē/ž˜Ą_~FUĮf‡+¨°ú0Ę<‘kũøë_9=#PU\ĘáKÂų°!´TļŸßuŗYœú�Nũ%; ¨îņéˆāîÔ"€Ģf<Ęo~‘î›-áí×wPáÃƒŠōäÜŧ÷ė °ŠĮFEDÎ=CģU¸¨âEDDDä”ÎūŠ—æņ›%y.VøpÃ#qË`č(fÎCÚë9ėžn6—íČŖzÄžŧū’öm‡26qÎI&imĄö˃y¤Ÿã?"/Ŋžy ų _�ĒöPÂæßCäP€K˜rįøõōū~# Úįũhm5qãx‡ü€ą#}ØšĮķÔQxx]ÅĩAüī—54ōĮ2'—^7›Į¯ö`øå^ŽˆãŌņLšē€ßūŊ„ZB¤ęí\:e<‘#įwŲõ3x0¨†ÖKÚC‘ÃĨ~}ĶL^pôoŧŋŖ†ĢfĻđ“0GeɅcĻrÃ_‹YßBĀāR.Ĩ„¯ŦøK)_]ÅŧÄ.Æ]MUü*įחīh);öÔ0%…ÛŖU)—]šĀí_TđÂĮ{ŠēîF"B=Ø}°†ooôãBę8pFńSąįUDB e5 ēžā!p´ŗņ!_›QŅ2ŽČ!ŽĐÆ+*ŠĀâĪųü0„\ß~qˆZc0ˇŒ¸īëüųsÁ>>K!r@ũ{—›Æ|ļ§ũüĻr aģt<ˇMüŒ%o~Ūã2ÄË0d¨‘ ‡v)´āÛ˛}ė˙Ɵi‰×3ęr�/ĻĖl`˙s9üĢĮVEDÎ1Ũ*\†x¨âEDDDäTÎūāÅī:æ%ã„šg‡šĖi>Ø ÃĻr×ÕkX˙‡L<Žú’øČx×ōē4„ā¨@†fīā…?Ö1ŅAÄČpŲĨCwš¨úĸFÄp•ķŧĒ^ÁD^ŌĘĮå5Õū՝ûåáF†=~,Ŗ4ĩv{uī<-žúËŪũc _5YiimĨĩÕ ÆŽíũv1…;˛XûõxŽŊú*"Fúq؈tļņ헇øjDÁC Ĩėo”pķ¯vžäĨZāŌ+רÅ#xx8Îĩļî<FüĀē´ģ,,˜Ësj\_žÃ‡¨jõáÚPį z‡<ڏ=UT4 á‡˙ŋŊ;¯ē>đũ˙JrN’@ö@Ø"a•M)L‘"­pĢŌEœ_Ą­Öļ÷WÚ^ũéhëĩŊÖö×:ļZ-ÎXÛąŌŠ´ĢEí…NÁ^ÁŠ"›lB€@ļ’“åœ$ŋ? 눜$âëųxœ?Îwų,ߓ<9ī|–…đâö3–aábŠÃ}ųčøa°bÅ!¸$ë [JĸôŸ9€ ƒč5hĸo˛Ŗ.-hm.ųô$†–Žåí’fæ(Ūļ`Ņ$ āÄI ]žœ7ļE¸tLʡ˛Ą:‡Éûë_îĀãÖ ę5čō9}đr6ĄŌJĸÉčŸ×é`V_ 31x‘tņ8q„K¯ #^$I’Îĸį/L x‹ëĻ0ėŠ‘¤<ž–Úá3˜đw ʞx#˙”ŧ†å¯­ãå˙|ÅŅdōFOcۜi KŌĐ�xžoëų“›˜×Ø)D9á‘Ļ�Á㏝ūŋƒ ŧģäIžÜœÁ•sŽcŪ  R‚PļâI}įXã>7Ÿ[ÖŦbųÚU,zíĸÉŒšü)æ^3Œl !ÜéôĘ*AæåG׊ ˛-ĨĐbßö÷Օ„d§�4ԁ` ųøvĻdž4:፯Fĸbåã÷°ō¤“944@¯ĸa6ŽæŨr(,ßÁūĖa Ë@´ ’ % Ė`Åá& I?šüŦB†f.§xOŌ;B›A™\2(wî#21-%Q gļˇ9} W YÎâˇļrxĖX›7*¸œ yĮúGzāø/ Á�īg¯ĸHc#'|ļÉa–$]$Nņ4v‘$I:›‹čka¯ũņ"C†’ĩkKļä†īåĢtü1͏aĖ4ˆ„Ųˇí^üãr~ļ8ī˙ãPRR€!Wķí9CO N)œ<ųä<DŠyc}ũg~…ë/;6z¤,z ­ÁL†Mû4ÃĻ}šHC;ÖŽāšW~Ã“é ¸kڑûĸFąDé\B՚UŧU—Á•yí=)Ûŧ‘Úĸ™ļ÷†@"ŅöûöĩĄ‘8u8‘œL€ >úĨ™™wš@ cĢėB.Í|…wK—ė#ĨhŲ¤S8(—ˇ¤–ėËĘÜSfũ¸´(™˙ŗkĩé;؟y —¤@ūˆö)HeĄtŠÃ}™0äHëR7m(‹ŸŪȖ†ž„ŪФpژŽPíũ‹Fĸ†cá˙úw‚ÉÉ=qSc{`'I‹^ŲôˆU�ú <m$/I’¤‘¤|â�� �IDATˆÅuĪEŲĒįy14”y_ü<7\ā­%¯°ãœŋô6°oÛvY5˜Î€1͏~r_ĸ{Š"ƒr ē‚Hf.ųyG^¤’é~f¸GŖí_ūĶ“‹ėāMíkĢD*Øąi÷Ņé+Á”\.ö)f‰RVR @īĖ (¯$d*¤wÅ:Vė i¨f÷ę߲¨$Â@”h¤ÚËX´*ĀĖ™Ã:B–4ō3“‰–īĨŦSĶĒļm9îũqō.Ą0PGUcr§g“K~z€@JfG¸‘ËĐĸ4öo{‡w÷D)Ō>Ú&ŋh�”lá­mI)vÜôĻÎ ‡BÉŪØ|”Á…í#  †1 \Ė–õ;(Ë,dX§Đ&8dŌ‹ycųjÖT努S­ŌČĪM†ŠãûW[˛ûôũëė4ģedh<ČūōNCû(>Ͳ8’ôÁ4„/üø ūãįOđ?Œ˙5ŗow7H’$ŠĮëųÁKcģˇíāŨSŧvėŦhßé&ô&Ī.¯dØ5Ÿb\zKfļīr´č•Ũ;á4đîË˙ÉŋŋŧûčÎ8'TŽåŋcáĶ/ķ֞ ĒÂՔíYĮōĩ•¤´/Ā:`ō4†6ŧÃĸ%ëØ]Ļ6t€wWü†û|’÷ŧīą.íRú2,Š×ŦfG(Lmų^xz‘ĸĐXÉžō"҃Ŧøí3ülņ:v”WSĒ`÷ĻüŸ’ĀŅ0#X4ŠÂęuŦ)ĪāËWį°ã釹ũ˙ũ%K cŪįŽfÆø�oüÛüpI%ãžx#3ŽŽT R8q(ékynÅnĒÂÕė{įe­m<ũTŖ”‘Ė˜˜FņËŋã…M¨ ‡)ÛŗŽgŸøW~üëuÔv\6`ô�Øšš7*ú2lp{Ėt Ē7ą|[”aÃOģ@°h…á­ŦØÖH˙áč§ `XæAVŦÚGJҍãC›ā%\1>¯­%<bã:­ą3lâHŌĢßaɋ[ØĒfßļÕ<ģĻōøŅ.‘<÷ØŋķËĩÕÚ§"•íüûĘĢOú9ę5ârF%dŋĢxˇŧš˛Ō-ŧđÛu4$#IHo<ķcîøßäëß}”ßmĒéîI’$õx=ĒQõ;üúŠwN}.0†ų÷ͤöˇË).˜Éw'vŒj^§į\Ɔ§žįÅņ˙ƒyE”íÚƯĶT’ÉŒ›>OtÉr^|j-ĄÆ(´ G\͂OęØåčræ ^|q ˙åyH&=ˇ~á+\3øBíéË•s¯f÷¯Wđŗû_#˜YȄ̝ã†ĸ}DJžgÉcŋ!ø˙|•nŽcņWđäŋĸ! %ŗ/ŖfÜČõGvqJÉÕW°pÉ2Æũßŗ¸dÚø_ĶŽ¯)î-üd.D"<ĄųŊF|ŠųW?ĪĸĪpīō�yC&rũœ),ü•ĶŦOd؜¯0?ų^\ōKVÖ5Br…Ŗ¯fÁ§/?؍ĸ°q[2/cؑ()–[Į–ŠĄŒ*:ÃsL)dTA[J šôčķÎdčād^\eÂč“˙ë:ā˛Ąô^ŲȨɅĮ¯IPt5_ŋ.ĘĸåŋãĮkdŒäęÒû#`ÕHYé>jÃC\‚CšâŠlYų;~ēm$7Üų°ÎXæŨtˆEKVņäƒ+f⪟ēš+W>Êö䒤ĸ˲đÕ}íoŪåĨĮžĻߏoáŠ>ŨÛ.I’¤ž,Ž­­­­ķââb /h%ë6īāōŅÃ.h™:‹Hkūķ–”÷eæÕ3øčˆ~ôîH "á Šwnå5o˛#ũjžũÅQ'íJih€”N‹(îy™ī=ž•qGļđîņ"ė{q!?Ũ6’ēsÖi§0IŌ‡ÕēÍ;čß7÷ėፇocáæÎs.{3ãΟp͈÷V÷ūƒū] I’.J§ĘTzūˆŸ`.“oZĀ€ĩ+xyåoXūĢCDÉ@#Q2č?d(šâķĖĶīäĐeįž÷ocĀÕ×ņéņ9ÃyíīP›;‘öøĐ%BmčeۖŗhM#ãž0ÍĐE’.ˆ ƒFô…ÍûŽ  Če^$I’ÎČāåĸ–€‰×đß'^D8ÜĐ$Ķ+åĖSŖ‚EŸâëŸ{…įVüŽŸžŌH4F˙ųĘ>#GŠyņ_žá r{õį™;æũl-IęŦßĖ,¨ú9ŋûë^÷Όy79ÍH’$é,œj$IŌ‡Đ{Ÿjtá8ÕH’$]ŦN•Šôü]$I’$I’>  ^$I’$I’bÄāE’$I’$)F ^$I’$I’bÄāE’$I’$)F ^$I’$I’b¤K‚—øøxZZ[ģĸ*I’Ôƒĩ´ļ’ā˙}$IŌ‡G—ü哞šByeuWT%I’z°úú’ģģ’$I]ĻK‚—ũō(¯:ÄÁŠ͑hWT)I’z–ÖVÂõ‡Š;Ü@ှŨŨI’¤.čŠJƒF ĖžåTUמH’Ôė?XŅeu%$ē 2rč`’ƒ]V¯$IRwë’āÚ×!ƒ ēĒ:I’$I’¤nįęv’$I’$I1bđ"I’$I’#/’$I’$I1bđ"I’$I’#/’$I’$I1bđ"I’$I’#/’$I’$I1bđ"I’$I’#/’$I’$I1bđ"I’$I’#/’$I’$I1bđ"I’$I’#/’$I’$I1bđ"I’$I’#/’$I’$I1bđ"I’$I’#/’$I’$I1bđ"I’$I’#/’$I’$I1čNJĸĨŠ|ø šļī$z ŦĢĒ•ÎI _>IÃ‹Čšík úvws$I’$I‰. ^ĸĨŲ{ÃZëęģĸ:é=‹(#z Œ†ˇÖ3pŅBÃI’$IŌŅ%S*Zhčĸ„Öēz*ZØŨ͐$I’$]$ē$xix{CWT#]M;vuw$I’$I‰. ^N5Ú%0īŠūē띑žĢ–Œ$į•e-œI<Đë‡K)Zu7ŊŪG‰ ×>@Ņ_Ÿ%g™ęģ–„÷QĮųēũë ííüŪŲۙ8´/M'11ļíq "I’$IŌ…Ōe‹ëžZ3MKŸ"ŧŊķą2›cUß^jŋ/‡ëvŌĢ*zĻ§īãĀōƒ4uwC.¸ 7’3?“ĐŌWiuwk$I’$I:ģî^–=O͚ŗ_™0į īJäĐũo’4I‰{Šģ˙ÂÐ?oqVRūĩ9˛&’ũŊ¯‘>aqÍ%4>ũ(eŋÚ@+éũŊ{éSü�ģ,?c}ņcæ’wĪu¤fŅVēžđũ÷QĩĻ’~ĪŨdO{^įĐ[ĮŋˆŸđUúŪ3‡¤ėMKV=z&HÚOž'øJĒ–$cNeŸ¸ÆÁĶÉžëFŌÆô…ĒõÔŪ˙ Ą5Ր8„Ū÷ÜJæôQŅüöJ*ŋ˙ !ˆ<ėģn&mL_âšŌ´ė)*~•Č UŌMwĶoúë˜v‡ĪPŪ…ÎãČũ¯I{k!UW’=숏oēķ>j÷D k*9ÜBúđDĸo/æøņPų¤}ûn˛f"˜ĸéÕg(˙ÁKDĻ˙ˆÂM"ČũĶ’n™CÅÛIģ햎k›‰lzęû!ŧ'r´5í$eū8?ō:ŠßCëc7ŗīW{Lz/|†Ü•ė˙˃1 û$I’$If]2ÕčLŌķ ô;öJH žæĘf` iĶëŠūÎS4QDī{î#‹į){øMÚ g‘ũų!@*Ŋîē›Œ õÔŪz ¯&Ōë›w9ė=4*qYÜL*¯SöÕģ ׍'ãG7“’ 3o!gÖ@ĸKāĀCëIž>ĒĶ#Éēg)Ië Ũz‡O¯ė#į"íŖl Ļ’>øuĒ~°„f†ųĀwčĶoU nĄōídūčŌŌ 8īNr§'R÷Ũ[Ø{ë34 žEÎüqđIÚ]wŌ'{• žFéũk‰Ÿĩ€Ė)ŠgėŌéˋÅsnĻ­â§\K¯Oqđģ+i)ü{˛ŋ1‰xRéuûôÛLũũ÷Rąt ŠŽ…W sî ÷úDžŧ…}o!aÖ­äÎɧmõT, ;ŠžåBoIúƏČŋ~ ÍOŪËū;Ÿĸšß,ōY@rb§~ĖN¤öūG ˙e%õĨ4}b{ژ6Ž^c‰Ž^I“Ą‹$I’$)FēyÄKŠ?z†Î‘AëĘQü­ĩ$Íš‘´a‰Đ\ĮágASĮ|™†%Khx;‘ĀϤLØIíͨ͝ Mˇũ=)ƒûĮ^ž•’‡CDÔĶ–¸•œŲS aĮ96Ģy'‡žv35áƒDBZŪ:HÆįL 7}ņŦ§æņWiǃĒ%ŗIũffû}ƒ§’R�‘ß<CÍÛ[aĶbĪž—ôŖåėĨæūńC°¯’ZÍO?KxĶ.ŋDÆėI›¤‰ $Ĩ‘8f ËÖRųŲ—:ĻGeļ‡%ŲIŪ—Čę§Ø÷‰GĪĄS§+īō9īYNõ’ D¨Ŗžx™…I Ž^Iƒí‹Š~iörhÎ,ōÆųüdß[в§ŒÖ´×ižëīIžuˆT5uDˇīĸĨy™ŗúÂö§¨zv- ōŲŲ¤Ū>•´1RŨŅÆ§Ąfyû˜šđō2æM%%ëyę†˙=ÉI!ꗭ§íÜ~*$I’$IzĪē}ĒQÃĶ÷qhĶą#mĨ[h#¤kįĐg,@ˆ¸WŅ‘ÔŅZ ĨŠšĢiŠk/§Ĩ͍…„ sɛ?•Ä‚´Ž!=ͧŲq&i$Îģ…œYŖHHOė(Ŗš¸¤4ŌĄŠžh]û•-U! #xIo¯¯ĨĒcōLsˆ–đ E7•=2Ŋ'=‹x pĶŖ\2 ŖŽ~}‰,yĒ1wqĶÜ„wRķƒģŠ|ĩšđC IūŪ¤ßu/}€–â•TŨyá=§īŅ™Ę;ŲzÎá-�Էߗ˜JiħۏœĢ&ZuŦи‚Šdß5—äÁY$$u<ãSu(1“Āqå@kUˆVH?2jĒŽčc™š—žNķMsHûH&‘ ãH¨ZKŨۑĶ?4I’$I’Ū§n^"oŊÎáSŦņRsķ,j:ŊO¸ö‹Lģ’œģf‘øö#ėũÜK´Lš›ÂĻž§VÅMž™ŧëĮ}ėkėũÕ.ŋũ,ލŖ%Ü I_úë P˜ėÆĻ:Zø~cxŌō fNSQ8D+ĐüÜŊøÕŪŖ‡ÛÂeP‡žõJĖ$qĖTúÜu+}n›KíĢOĐŧã%Ęox‰¸Ŧ$}ä:rî™MöMË˙`íé;UˇõôåŊ§§ĶáŧŸs­u@v @+™û™j”Oú] HÍ^IŲgîŖŽ™üéNNš‰Qsu{øu´Hč—E<uDÃ8ÕÎX{–Ū>Œ™×‘V˜EtõJ×v‘$I’$ÅT7¯ņ’HŌŦëč3¯ķëZ’ûŊŸ"‰KĶH<•Ėyã€D†‘pŽÛ';Ļķ¤œ0—Ė)i@&‰Ã͈ŦŲB+ŖČ¸}&)Ķį’5ģīąûöŦĨĄ ‚WŪHÆô‰¤cÉIg¨gĮJę‹!qʕ¤ôË$0ũfōøŠŲARîy–Kūp7ŊĮdBs5­ÍÍPWM#ÉūíR?2—ä‚ má2Z›ĄĩŠî ŠŧķtŪĪy'‡7ÖÁđkɞ3‘”9 č3üX™ ‰@b¸‚‘¤ÍŸMbÄõ+"˜mÍíëļôērÁŦ-„—„áבķĨŠ$OŋŽœĪAéJ›NW÷^ę–î$aĘŌ ë8ė4#I’$IRŒuđ2{9ˇw~ŨLúā÷Qdh%ĄßlĄuĖÍ<p<y/Uo…Îģƒ>įXnÛęńV$øųûč÷|jŋû�áŌ,Ōîē™ĀŌ…T­<HÂĖ[ɛ?ÃŋYO+‰íAMķĒŋŋ„FƓũÃ[HĢ[F]1ČiĻ:íĸúΨ­GΏŌīρ´,]LŨž O>Jí!d=ōqŠM+)˙ū"låĐũKhîw}ņ¸ŽøˇRöøÖ3ôčL坧ķ~Îõ~øjˇ§‘rÛŨäLŪBíĢíĄQ<{Šy|ML"į‘;HÛŗƒĪî¤mĖÍäÎÎ'˛l9á4Roģ›>ÃĄéņīPöÜ^‚7ŨM˙ŪH°x)eˇ>uÆÅrŖ¯.Ŗ1)‘øđëN3’$I’$Å\\[[Ûq˙ô/..ϰ°đ‚V˛ķīf]Đō¤ķ–6~¯|‡ÄåwSōƒĩ§ņRô×e]Ú,I’$IŌߊ2•.Yã%>ĩ­õ‡ģĸ*é4R NžH¯97Ķ+Š„ęߜ>t ôÍëŌ–I’$I’.^]2Õ(å#ãģĸé ōIŋũ;äLÃŨGõļO^Ôu͒$I’$]ÔēdÄKÎí hxkŊŖ^ԍvúÜ,Bgš*>ĩ9ˇ/č’I’$I’.~]2â%PЗŋ~‚ԏMq‡z¤@ß<R?6…ŋ~‚@Aßŗß I’$IŌ9č’/Đžô}đû]U$I’$IRˇëæí¤%I’$I’.^/’$I’$I1bđ"I’$I’#/’$I’$I1bđ"I’$I’#/’$I’$I1bđ"I’$I’#/’$I’$I1bđ"I’$I’#/’$I’$I1bđ"I’$I’#/’$I’$I1bđ"I’$I’#/’$I’$I1bđ"I’$I’#/’$I’$I1bđ"I’$I’#Ž¨¤´´´+Ē‘ÔÃÅÅÅŅÖÖÖŨ͐Ôtw$I’ēD—/ūq%I’$I’>Œœj$I’$I’#/’$I’$I1bđ"I’$I’#/’$I’$I1bđ"I’$I’#/’$I’$I1bđ"I’$I’#/’$I’$I1bđ"I’$I’#/’$I’$I1bđ"I’$I’#/’$I’$I1bđ"I’$I’#/’$I’$I1bđ"I’$I’#Ž¨dw¸„_mûáæúލNzß^*YŅŨM¸ âãâČJĖāgû>cŗGvws$I’$éCŖKFŧ<žņץK[WT'é­m­T6…˜ģüVļÚŲŨ͑$I’¤.žj×ĩÕIęÄŅF+_˙˙îéîÆH’$IŌ‡F×/æ-RQŪPÕŨM$I’¤Ž ^œa$õq´ļĩvw#$I’$éCÃ]$I’$I’bÄāE’$I’$)F ^$I’$I’bÄāE’$I’$)F ^$I’$I’bÄāE’$I’$)F ^$I’$I’b¤/ŠL}'ŽŸLîé.I™ĖmSīdA~ęyÖ1ˆ9˙'ˇõĪęxŸÄ ū_āîÉ˙“'^ˈķ,ĩg9žéų_āÁɟaô…Ž&ņ2LŊ“›˛ÎpMÖūyę˜œxĄ+—$I’$ŠgëÁK=›ĘöIˤĶä*ššcéßē‡7ĢëĪŗŽ*Ö/eŲĄŽûã1cā`¨üm^ÉÎķ,ĩg9Ą’$I’$ŠËõĀā­cs4‡Ër Nq6IŲ94ZĮĻæķ­Ąž’ʍlŽojHĨW|”ũĄí”6Ô=ßb{”ú(I’$I’ē\ ģpJŅ]ŧĒã˛ėą *.Ĩ¤ķšôą\–ŌČļŨģh0mČULËČ'+�ĩõÛYQügVÕtŒôH™ĖˇÆeĶö5d ū$ŖŖ+øÉÆrfLü"ƒü×MážŅãč 0ō[<ØēƒUáK˜Üö'î{w#áŖ'1~ä-Ė Žā'×R}Šf'§ŽåšÂÉ\žžC u”×nā…]¯˛­ņ }M™ĖmãĮ˛mëJũ¯bRzhī˙ņCĻ0"5 šöđ—ŋŊŠpG’8ˆCŽdJī|˛šËØvāĪü~IûsasŽôqčzšyWņũ‡3(%`k#åáwųßģūĖú†–Ŗ×ä}’ë]JA J¸~;ËJĘN(§Ŗ‡\Ëgrú“ßHyh5/:žžŅ#oįūÄžœšŲėÚú0‹Ãķ‹Īb|á'ųoYũÉK iޤ$ôŋ/ŪBEë9œī"é3š}Â\ŽÉDoBė*_Æ?¯ũ¯ÕųÄĪķSaŪūņ|oĖD†$§Aũzž\{?/w„’$I’$]LzfđB ;ËļĘΔ>ϤæØ˙!š—’Õü.ĪÖļ�YĖũEfÅoį…mKŲÖ  ī'™7j.õŋ`ECû]‘ødÆ ÅÖŊŋæ_ëk“}Ŧǚ?ņ“ueĖŋ|ĩû‹Cq=“‡ŽetâFÖUÄeŊĄ¤xË)C’ĮrÃčŲÔ­`Ņú-”“ÅeCfķåQŽ˙/JÎôÅ?>ƒ‰‡đŽįÅÆ�#†~•ųCį2¸ö]~ŋe!‹ĸ}˜xéW™7är6­_CŠL:—k“ŪåŲ-K؁ô´)Ėz=s›~ÆĶ•į1Ę%ũJæŊ”ę}ā_+B4ÄgqŲāŲÜ8˛‘Šu¯R Ō¯âËC‡Ū÷{*AŌ%üˇAĶ(�ūÖQLn˙ëš!ŪüÛĶŦ¨m"=ã Ž8˜ Gšĸ­LŸĖĮ[׹xs å įöü \ĪY5ŧ°ã)65ĩ’<Œk†~–ų­5ÜW\zÖķ]"u&?ũøŒŦzŠúĶJv1k&ŨÁcĶšéOO°ž5BS $åŨČíõōO/=H)ų|vęBūųŖ7ōÚŸ`ëŠ˙æ˜xlĶ3]ĶI’$IŌûŌ#§DÃëØÔÆ˜ÜAĮŌĄøALĘJ#Tš‘]­ȘÂĮSëxķoKYSĸēąœÍÅKųKC>Ķúwē4¨^ɋ•Ĩ§˜JÔBcK´ũX´žÆh ĄulníΤė>G¯Jî=–ĄėäõĐŠG$äMf$īōÜö5lk¨Ąēa7+vŦĸ$i3˛’ÎŌÛ�Õå¯ąžąhb[e)‘ødJK_cW †mUeR@^<@#›v=ŏˇü‰ĩáՍ!J*_cu}2C3N5=ëÔ¯áÉõOķ˒Ũ”6ÔP]ŋ›ĨģhHLQrû%ƒō‡“Õ´JvSŅXCEÍ;üūĀūNĪ9IyųDC+xą˛œęæJĘ˙Ä˙Ž9Åä­` ¯īZËļp9ÕŅsy~ äĻeĀá-ŧYĸēą†ŌCkY´éiūã`Õ9œī#‹æ2•|˙õÅŧ.Ŗ4ŧ–Ÿŋū ëSgķ:-Z”°_­_Ki+ĐZÆË%ëiJ-bdōéËūæ˜ųFĮëH�#I’$IęŲzčˆ€rV——ņąūãŋ›Í­č=Ž1‰•Ŧ.kŊ™šOJk)Ûę[:ŨbO¸ŽkĶ Č¤„ �ĸ”ÖŊ‡/ß­ģxŊ˛‘¯į"÷Ā*H (o0ZĘæS.�“JAzÔ¯agįķÍ%lm 0­w>T–ˆO"ĐuE[ŖD[´ģ‘ōĻNN[# Ô°ŋéXŋZĸDt|`-4ļĻ1iĐUĖKĪ&3!@0>@ �4įGÚÚ)ų‡Â" ’SIŽk/3…*qí}ĖKI#ŌPJy§ÛëJ(§¨ũM|6šIP~0Ô)ÜjĄ´ļ ōNhWC ĨG/:ˇįWRšŸ†ĄWą`xĢ+w°­ļ”ęÆRŽŒe9ÛųØËddÎ 8´˜ŋF:nÜĀĒúDž”W%ڏÕîdW§QPÍÍšH¤wŠK>ēņ GžH’$IŌB^ ĸj{NcbV›+aDß!¤Ô¯buĮĸ”`Ä_ĘW&_zōÍÍ}H‡ŽāĨ‘HKËÉלV ģĘ6PŪw,“R×đrÃ.īeĶļ]§Yx7@J�HÍ§Î>él¤!‰@ü(žüwŸeä‘1FMåҎūĢcũš(´Ŋ‡æÅđéŅ_dJëžÛõgv64%•‰#nfÖ{(Ļŗôŧëšeh>{ö,eQU9áV í*ž5ōČ´Ŧ$RâH”ã–Ŧiíô>>@¯øöPŠŗh´=4:Nkc§rÎáųÕå‹y4:‰÷ËŦáSų‡Ž5d~ŋk ģšĪ~>öé2īä¯ķî<élSmĮvÔnæ\›tbčr„á‹$I’$õ|=:xĄq o†gđ™ŧ!$‚‰vî>ļÆJC¤ ĸÛxvãJöœpk´­ŠĶ¸įĄ~#oÖ˙“˛ķøKŨĨŒhy—'kNŪDiˆ5Ëyh×."'žmŠ!Úā…ÍOŗėČÁÖú퉑:–ËRęxsãKŦ=ÚÉÔöđâŧ61JeDn)5KYŧ÷Ņį ĄĶHĮÚ,ņ’áXh’L2p 5J¤Râ˙ą ’ žąūsx~m¨­aqh ħR1–k.™ÁüĄõ !Ÿí|Ŧ5SĘrŨ›kOú(š"Ī9léėąMĪŽH’$IŌT]ãĨ]=ī”íŪŖ¸,{CŲÛÕĮĻäTחQČ  Į^Õ--44ŋßmĄCŦ=¸ŸĖė˙•7„pÅÆãwW:ĄĨáJHÎ&ĐÔš-54ļ5nn_ģĨ"\JɑW}Íųˇ/!@zĒ;HŘÎķ ‘H# GĨ2:op§Ā¤Žō†Æöuf:Ũ™ž>čØûÖ*Ę#—žÕ)°I`pFūYę?—į—DAÆ02ŌZOih /,#˜R@æYĪw…jļV–@ę@’ę÷˛;|äupk•'FJ’$I’¤‹]^ ņĐ:ļļqíā"8´ŽM† DŊÉĒúl>>ôŒOīCzb ˛Ļ3˙˛˙΂~YīģîpÕ:ļ'đąŒŪŦ(?ãĩĨåkؙ0ŽyCÆ2(%•ôä<FôŋžÛ.ûפŸfáŽķux?Ĩ­ųLę7ŒÜÄTrûLä‹C2(­‰L*`PāŊÖWÃúķ ��—IDATūú:‚—3%ŊŊíã ?ÃÔÖ=”͇AŠ}HŽoagÅvj“Æņ™Âa$÷Ą c"sōŗ;…5åŦ­¨$%ë*>S@nrCōg3+=zŌ(–ũų%S4đŗĖņ Ƨg‘™Ø‡Üôą|</›†ú]”Ÿõ|רēs1 ÎâžI3ŸžINęŽu/KŽy„ÛŗĪ<îG’$I’tņéŲS�ĸģXjdl^€wî:~}ĘYąų×D‡\ÅĩŖ&€†Ļ2ļí]ĖĸĄ R÷;uQFÆŊÍ; gšļq#ŋÜ ×NfūøŲ¤ĐHmÃÖn˙O–…ßËú2į y#ŋß՟}–o÷…Úđģŧŧk)›‚WQ0r* FÃŋŽßũžŠÜUōIž–Ycoášh%;ū™EĢatđ‹|føW™ģũg<]ųg~ž3Ā܁ŸåöžQBõÛYVüŒū$�´Pēī9žMœÍŦĄ71•:ö‡VķÜî(_ŪįĖ 8ëķĢaÕļß2ŖãŗiޤäĐrž,ŪA#œõ|—¨_Î7˙ˇO˜ËŋōNzSGEízūđú<VåˆI’$Iú°‰kkk;nY×ââb /h%wŧūĪ´ŧ.“8Šŧė“Dūö3ž×â)ú€zŠdEw7!ĻļĖ[vö‹$I’$IīÉŠ2•ž?âĨ;RÉM)`Já')jXÍC†.’$I’$é<ŧœBfîõ|û’|jÃX´cMĮ–Ô’$I’$IīÁË)TøwčîVH’$I’¤ēŋĢ‘$I’$IŌ•Á‹$I’$IRŒŧH’$I’$ňÁ‹$I’$IRŒŧH’$I’$ňÁ‹$I’$IRŒtMđ×%ĩH:Ģ6âũ…”$I’¤.Ķ5ÁK[[—T#éėōzåtw$I’$éCŖ‹ĻųģŒÔ=Ú÷âˆcáĮ~ĐÍm‘$I’¤. ^ž1ö‹¤'Ļâœ#Š{ÄOvr‹g=ĘȌĸînŽ$I’$}hÄĩĩ?¨¸¸˜ÂÂÂnjŽ$I’$IŌĶŠ2w5’$I’$IŠƒI’$I’¤1x‘$I’$IŠƒI’$I’¤1x‘$I’$IŠƒI’$I’¤1x‘$I’$IŠƒI’$I’¤1x‘$I’$IŠƒI’$I’¤1x‘$I’$IŠƒI’$I’¤1x‘$I’$IŠƒI’$I’¤1x‘$I’$IŠ‘@WTRZZÚÕH’$I’$ŗ‚‚‚˜×Ņ%ÁKWtD’$I’$ЧqĒ‘$I’$IRŒŧH’$I’$ňÁ‹$I’$IRŒŧH’$I’$ňÁ‹$I’$IRŒŧH’$I’$ňÁ‹$I’$IRŒŧH’$I’$ňÁ‹$I’$IRŒŧH’$I’$ňÁ‹$I’$IRŒŧH’$I’$ňÁ‹$I’$IRŒŧH’$I’$ňÁ‹$I’$IRŒŧH’$I’$ňÁ‹$I’$IRŒŧH’$I’$ňÁ‹$I’$IRŒŧH’$I’$ňÁ‹$I’$IRŒŧH’$I’$ňÁ‹$I’$IRŒŧH’$I’$ňÁ‹$I’$IRŒŧH’$I’$ňÁ‹$I’$IRŒŧH’$I’$ňÁ‹$I’$IRŒŧH’$I’$ňÁ‹$I’$IRŒŧH’$I’$ÅČIÁK  ĨĨĨ;Ú"I’$I’ôÔŌŌB 8éøIÁKrr2õõõ]Ō(I’$I’¤‹A]])))'?)xÉĘĘĸĻφC‡ŅÚÚÚ%“$I’$Iú jiiĄēēšp8LffæIįãÚÚÚÚNuSUUMMMN;’$I’$I:„„’’’ČÎÎ&!!á¤ķ§ ^$I’$I’ôūšĢ‘$I’$IRŒŧH’$I’$ÅČ˙Q"¸īÃ����IENDŽB`‚���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/register-verification-username.png����������������������������������0000664�0000000�0000000�00000100572�14156463140�0025506�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��]��û���šā���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨ}\Õõũ˙ņ‡fŦqt ’ŋ@œr1ķĀ*Žß~\Ŧ)45XZR–¨•ēVŅē€fŠs&–œße&m•Ô,,5Iũ Vãāí–ûĘqMáģDØÛļy‚į÷ĮA<\ ^P{ŪoˇsS>īĪûķ9ēõyúzŋ?CœN§š †vDDDDDDDD.G ]DDDDDDDD<`XO ÛÚÚøÛßū†Ãá ­­m û$"""""""rI¸âŠ+0 øúú2thįږ!]įtikkã¯ũ+ŪŪŪ\uÕUŨv—“'OrüøqŽ?Îu×]×)Géē4440tčPŽēęĒˆˆˆˆˆˆČĨč_˙ú�×\sMĮ˛ne,vģáÇ\¯DDDDDDDD.qÇįøņ㝖u ]Z[[2dȀuJDDDDDDDäR7tčPZ[[;/¤žˆˆˆˆˆˆˆˆ\Ö爈ˆˆˆˆˆˆx€BPč""""""""â ]DDDDDDDD<@Ą‹ˆˆˆˆˆˆˆˆ(tņ�….""""""""0l 2dčq‘~sžlķhûĒtņ�…."""""""" ĐEDDDDDDDÄ爈ˆˆˆˆˆˆx€BPč""""""""â ]DDDDDDDD<@Ą‹ˆˆˆˆˆˆˆˆ ėˆˆˆˆˆˆˆˆįUüĨšˇ—Į?˙}œ“'OvwÎÉĐĄCųÆUÙ}o !×vwú¤J‘Ë\Å_ĒyeÃ4ũķ_—lāpōäIšš˙É+Ū â/ՃŨ>)tšĖåž“7Ø]¸p† §ķ’8'…."""""""—šæũk°ģpa ÂŋŽė^ôIĄ‹ˆˆˆˆˆˆˆ\r.…aR ]DDDDDDDD<@Ą‹ˆˆˆˆˆˆˆˆ(tņ�…."""""""" ĐEDDDDDDDÄ爈ˆˆˆˆˆˆx€B—Pš¤qc™đ”õëqÜ^Tm¸Đq7^4Ø=‘ŗ7Œo]?žī'MgۜT{0•÷&1ųÆīp­÷`÷íâ4l°;péąSĩ{#ë6Pb+§ņ˜‡ÁÃČ LŅffÎI#Ų4r°;)""""""rÁ ķĪ)Sˆ ōé´üĉLˆŧ™¨į`a>o˙ž’ãƒÔĮ‹‘*]ΆŨFÎė͞ȎV2”HâĖf&'bՈ-#3c™™eÅ>Ø}•v6ޏá&Ž(īk;+é7Œ%t\įĪ„bHzt•ĐÕԘwĄ7<EÁŲíDę¸xtˇ§z%"""""Ŗá! <öhJˇĀęųũK/đôÚßņaų0§>ĀĪæDrí ôōâtYVē|‘û^~ū­üņŊyZ­%oūl˛KėŒJZIÎĘBģ”OŲË?$}~:…9>a/Ũϊ—ÁSː";ÎxxÄi�Ę­ eÆy†¯eDâJÖĨuü|ŦވíYš¤ÍŦe]áË$ę+=;•HZPGfáRLƒŨ9{ߌdŪÜXFw[vŧž?lĪåÍ?4ļ/(cĪÆ/8“ĘcÉ3X8Ŋ‘5[Uņ—Xč˛ę…į9yōdĮ§­­­ãĪÆ;ĘIŧ|ũņ5Įq˛í$ÎŖŧ`ĮļīÎ"ŗÄŽaÂRrפÔÃ6ŪĄ?âĨ×,ŲXItÎmäeŊHnA UĮė`Aų6RĶ–’bōldÆŪEîČ ōēŗ’œ„˛ĢŧIĘų#Ųņnmî~„ iĖëö“ę~°ķhëŅŠØ^ÚIoS˛ÆÜí4l+bHÉĩ“”ķ˙\ûÛËØžb/m?H•FE33}%Éũ¸$L­…+}„ĄŽ œu0ķ˙0Ŧnmš{i‹ī}°Ą÷¨PĖf÷xĀLℑ$%f‘SPKbŠŋįû~ą—[Оv7DDDDDäœxķŊ¤)D¸.į`^oūŠëøŽVjön"Įī'üô?’HØˇŽíĀŽ^¤.ŠĐĨ­­ĀĀ@�† ŌéO§ķsžyĶ˙†āøįßh;áĀé<yŽlĮ’_€o’Ōæô¸tM!se?šlÜMzŌŖä7‘’Á‚čQPWB~NKg[ųŸ×÷°Äl".ڛ܂"lö…Ę K°Ôzc0Ø)ą”A|XGŗV‹H0{CŖûĪŖ­9÷”ŋ…Ü‚< 0“ØéDläÔÁ¨RãjŲūčL2,””ÆŠÄ ŧëJČĪŧ‹Ė3^¸ Ė?…Ö?Ļ@Ū]xÍ/ķG´ŧß÷~= 6j€’ō:ĀēØË?$sÅ:ļÛĒp0‚ s K2Ÿ&Ž=“iĖģ¸Ŧ`˛3a]úvŽĨŧei(ļŧÅdŽ+ ŧΎÃ0‚qæŌ2Ÿ&ņT–ĶXDΒ,r‹Ē¨kŦ’Ķ—˛äļāÎížJÁ’ Tī 3˛ÉN9ũŨUm_Ė’uģąUÃađ&ȔLzæJƒûÚå۟bɊĘí024‘ôŒŽĩ*ĩŦXĖēí§BÃQ„&>FæšBĒ “IĖŽ,¤ŒÛȄĖb6§øcˇåądÅ ĘĢp8ŧÍĖô5dÄ̌HDDDDäĸōÍņ|üđ. ‡3aîŗŦ;õã‰zî-ĻđĶR*ívžØUČÁČĖąßagŪ´ēí95áVĻ$ÜÚãĄv~ĘÎÂO=pƒë’šĶĨ­­ č¸œú´ĩĩq˛­ {c5'ū}Œ“'Zpžlģ@G.§ÄfC4‰Ũ =Ή5kųĄ¤ŋŋ‡—–Î!ųļÛHžŗ”œülâ¨"7s#ĩ@tbĮA,î/ ˛a#šÄÄ4ÚJ¨íXQ†Ĩ䄯cîáųõÜÛ #uf(8 ØžģKšiËŖ ‚’S]ÃGl9d[ėâ2É[ŗ”Sįĩų1°Õ]€+w?dؒJÚōßædÁS\a;Į™vkŠFrũ\û!iwĨSāÂKųÅå¯aĻ=GSW`kßÅÛ°ąnŗ¯o&g Ŧ‹Y°ÄJPÆÛä[Š)z%qö\Ōl  �Y3e]•‰%obĩä“=ļ§ŨEzûu÷ööûn˛sėĖĖŨËÁ?ū‘ü4o –,!÷ԗg[AjúvH\Ãæ‚bŠŪ‰™ŪģI[°š>§ļ9Åļ˜éōų……äf˜ądŊØq~�U擞W‡)Ķu>ų¯?ƨ’Ťļŋ-+háGlœ9 FĨ°ąälNņûn–Ė^ŒÅ9īST°™Ėčcä>:Ÿŧڞģ""""""ƒcøõc ėiÅņz~ö{?˙+kne˜÷ézŽo]˙m†cČXFuŲmgá§ėę!Xš\¸C÷ ÅũĶÚÚÂÉļ´ĩļp˛ĩ…“­œ'/TĨK-€÷HF\×`YŲŧŊ‚ãˆÕHcŖÛIfo(ߍĨŧÍņ˜°Sb9ũ¸kĩXq›IM4Aš•ƒ§˛„ƃ”TÁ¨č¸ž‡?G[A)sˆ6ØąäîîT@cË+ĸŽPfÎtUYTY­ÔáyæmtĘ}ü“I=ð¨Ēƒkh‹˙­™ņ ŠęĮS}—\Æ^k%oI%D3ŗŊĨ<w’É|i!qÁūøĮŗāĨ§1Õæ‘ĶJyãpØ M]C˛9ŒP¨-¯â˜ÁLr˛‰ üCo#ãõ|rŗ“]ßWŅFrĢF‘šŊ’D“?#ũƒ‰[¸†ô Į(pģî‡7q vkAÉ)„RŽíÔ×jJ#7?ŸœŒxBƒũņgAj<†ĒJúlX7îĻnT K2â ō÷'ȜBæ‚ΉcPĘëlΛĖÛLÛ,HÅą’‚öpĮ›‘#]zl˙îŊãIĪĪg{öĖĄūø‡‘˜ą�3åžk(&"""""áķ͑\ŲeŲ‰¯°á?_%gë.ŪÛø*ŋXõ*¯îĩ3áĻīpíˇĮ“<#–ˆo>ßā[=Œ­éŧ\΁ \bËZ[[;B ĶßÛZ[pļļrōD m­-ārC—öF;8ē­k$īŽ›Yz°ëōh˛žCrO9Cm9U <‡äčœ^ŽYÅ˙ÔĄŅąBļÍF-&üÛ+PFE›1™ŧ uŧH Í`ˇQÎ’Ãznräy´52™Tķ JŦšä×ūˆT�›-u&,$)øÔŠU#åßõÄŊ ëå|=Ā´ÖS#bRÖt*këMŨæģŨÜy™aTé¯ŋŒk:—FĘmU`J#ÎũGFįo'ĮbƒÛN…ĄÄšåūæDƑEú]°`N2qŅņ„úcjŸ{§ŧĆÃ`ÂÜi.ž‘„šFáØ^B9?rUB‰véãm`$îWv$Tn`É Ģji´;°ÛÁAö~åTUÍ{WŧMf‚°¸wĒŧdæÛ¨ĒmÄnw`ĮŪö3ŧŊ˛‘Į’™Ŋ[y-uvėv8ė˜v:~×DDDDDä"tœƒģōųã?ē<]ĩ# aKģNäŲËC˜{Čr9.p‰….î•.Đ9t9Ņ^áŌvŗÕÎ(ŋ¯øÂ؟ĮėūåúûēöŠ—N%Ū%§tú1ŗŽd;%gIco( Ãēô¸ž3ŊGLtô(ČŗrĐ>ûAJĒŧ‰N7?˜FÃb)sļ+C|§ũÎΧ-oS1XŊ’Ô…ÁíC‹ŧ1e$sjJGûļ=Uyč \ŒF$e“ŗ0´ã;ņLP§�ÉÎąc@y:ĻqéŨö7„ēÕFē†ēÍīąnÃF6/žOļÛI_š’”PoėvđöÆĐĨMא"ģ[āwæ`ĸqûƒ$ĨÛ0§¯a]˛‰QŪ€uqiũ\ÔČ1‡ë0ŽäíívëÛą<u;iÁ¤f¯!ĶÄo(_w;sļŸĄéʍ¤ÎÎĸ.q)Y/Å:ĘėdÄ-ÖkÖEDDDD.2Í˙häßîTírâDĪŲ˙(åÕe•|˙‘'šķÔ°‹æō÷3´}š‡-§\RĄKO•.§ū<qĸ…ļÖVÚZø\ũž5ōo\yŅzAU˜kÚüƒä[í$ßæū(ęyÎJÜsŽ‚§ (É?Cs#Gē^ ALˆī,zcŠ33"ˇˆÄ5Qn˜@ĒĀDt´7yļƒ4–ƒĮ0˜ãˆķT[ņ)$ØÎæÍT-\HmŪnŽâHíöe;Žž ëģ/ŧČx ÆÚKĨk FŒ�ĸ—˛=ŗ{`f|æöCo#cÍmd�ĩļŨŧž•ÎŌŲāŋ A#ģ†+.Ŋ…1=kĒgÁMÖÂøN!I˙yãm�‡ŨĩWĮ966ŌŒ� ˆíĮ—–Ũ1ɯë0g>NUAIdŨš”Ķŋ3g×;Į˙ōgĒ gtĮ’áL¸5ë—ûøâŸ]Âŋ0žwŨé›*ūĖ�ĪęyQ礿tq]ē~Nœ°s˛ÕAÛ ŋ‚ĘJ_-]GŸģ¸”dFaĮ’…ĩīÍĪldA#pÍĄŌĶúŽŽņ‰DŽqĐRFIGp<Ļö§éhķ°QR隃%41ūĖuįՖ™Ô” ¨Ę#ĪVÄæ‚cSHtÛ((48FUm×Įh;UUw•K˙Œ$ÔU娃ƒ rûŒđ‰˙^ĀĶXž›‚ōĶ×ÅßtK2RuŦœōJŠ6apذv*HŠå`yS4]ßÔ3;Ž šWĨ4Roéah\oü ō†ō’Nī6´ļOø Ø]5K#;°ëžh_ŨsīęÁÛģĶ„ZĩÛķ\ô*yš¸üãs~˙yįb†ĢÆ$ō͟˙œuĢĶHūöéå߉‰etĮ#x=Öâ/ú5ÅÃåî’ ]€nĄKK‹ƒ“­'8yÂÁ û ūV? .؜.€9ƒ%I# j# îZĨĮ Iąå=EvŨ5´¤÷ÆHJ 9*;¯˛‘žpæč6a­™D“7ĩļļ—cÄSĮDš#ŖMø;ŦäQN(‰Ņ}Ŋv÷üÚ ™Â8ĒČ_‘EÁą$Ļt~ sÉÄėX7wžp—ʍäZ/§ęĐÔ4ĸˇ“ņԇØ*iŦ-Ã˛áAn'ĶÖû~uÛŗHŸũ9ģ˨­m¤ļŌJ^ÎnęF˜˜ Ä/ 5¨ŽÜÅ+((¯Ĩąąˆ§Č:8Šä?ęįl'ūŒ Ŗ \›Ģo+!×GUØĘkû•mÄĨ$2ĸ.Ėģ)¯­Ĩŧh#K6–Ÿîƒw(ŅÁ`Û¸km#•EdÍß�q&°Wō?•ío[1m”X+Šj„ h3†cEän/ŖąąÛö§H+ Â<ĒŦ6.ūZ(‘¯;˙ŋ‹C="ų>ío“v} É7ųtŦųÛgųūu@:xŅģ$‡Ũ†ĩž°ģæti=A[̃!C†qōB†.Œ$qÍûd1›%ų9,ˆË%(:Ž A#‰Æē*JŦ6ę`J&ëĨ5*@ē2gŦ$Éō(ųŲIĖ,OcfbŪu%äįäaŠERĻûÛF„#Ģ€‡7qî“ļĮ3aDÛ-8FĨÜ÷yœW[Á)¤F¯ciI9ŒšĶ>4ÉũĐ6a;™–%¤<zÔ¸P¨˛˛=ĪÆHsX.ƒ3˙‘ķ>dŽXGjR:ŧĮĖė÷É8C9JhÆÛdÛŗnÉL˛ŲÁ0‚ ķmdm>5<-ŒŒÍ/1rIKîÚČ1‡7#BãH]ˇ†ŒŗxUšyéË˝{ŠŦ™7“eÂ<g)ëÖQPg%3-ôuûÉėŗ‘•äfBzV:Éy0*4‘ôĨ0{IûÁ,Č^ŠíŅ,Äåh’—Ŧ!ÛlÃ^’Nfԃ ß!%y šKČNMbû‚Íäg,%{æ|2'afãâ#3{Ŧ+'5įQŊ_§dåz/숈ˆˆˆœŋ”ōæ[~üdn,Ŗ‡ģ¯Îčī3üĢaˏ7ąŖĘåoŸoᕭ•\¨É>.uCœN§Ķ}Aee%ūū}Í2r–zÅigūƒājŗKčâUú"˙÷6ūU_ÁÉŽĐå˯ę˜úĖî rlwĩÖ<^ĪÍÃb-§ö˜ŪFÂdŠ'1)•™ˇwŽJ¨Ü@RbUIospÛeŖŧŦÉ-(ĄĘí!<5-ƒS—*“ōÕÄ%åPG4+JŪ!Ĩcĩ‚ų7“fą3"émJÜÛīí¸įŌ–ûö‰Nˇāŋ Ÿ‚Œæ?ąÛČ]ŧ‚œ‚rę0"(šäô•<ęXŒ9Ŋ„¤œ?’ß}7ņŒô%+Îk˙a~ãš3e 1A>=opĸžƒ…ųŧûûJšĪëHg';séyíī<ŲvzâR[[KpppĮĪ—Tč2oîĻNęjŗë„ē%Ģš!f C€“­'huü›ĒŖ‡™úlÁ9ļœf]ÜíAŦ(|§ũ5Ę""""""r1;ßĐÅeßē>Œ Ļąķ‡ÃņæFŽ–æđŸž ff”¸ØC—KbxŅ[ožAYY­­­lŨē•“'OŌÖÖÖéĪ{BūÍ!kīĢņōžŠ+Ŋ 8rx‘�PžĖíuŒH\I˛‘¯‘Vūū—Īųũ_>ėŽ\2.‰Đeîŧûģ _svĘwīĻ䠅ŧÛųâČĘčã-I"""""""_s—Dč"ƒ­ËKéd—{3bB2Y+רĘEDDDDDD¤ ]¤‚Y˙g v7DDDDDDD.!Cģ""""""""—#…."""""""" ĐEDDDDDDDÄ爈ˆˆˆˆˆČ%gȐ!ƒŨ…>)tšĖų\}58ƒŨ ĮéäW]5ØŊč“B‘Ë\ę})p T†ôې!ŽsēČ)tšĖ…\Čà īĮįęĢ/‰a9Ŋ2d>W_Íà ī'äúĀÁîNŸ†8ë‹*++ņ÷÷ŋ°zÅmODDDDDDDä|9Oļ]Đöjkk îøY•."""""""" ĐEDDDDDDDÄ爈ˆˆˆˆˆˆx€BPč""""""""â ]DDDDDDDD<@Ą‹ˆˆˆˆˆˆˆˆ(tņ€aqįÉļ8ŒˆˆˆˆˆˆˆČEC•."""""""" ĐEDDDDDDDÄ爈ˆˆˆˆˆˆx€BPč""""""""â ]DDDDDDDD<@Ą‹ˆˆˆˆˆˆˆˆ(tņ�…."""""""" ĐEDDDDDDDÄ爈ˆˆˆˆˆˆx°žVUU t?DDDDDDDD.iƒĄĶĪ=†.ĄĄĄŌ‘ËEeee§Ÿ5ŧHDDDDDDDÄ爈ˆˆˆˆˆˆx€BPč""""""""â ]DDDDDDDD<@Ą‹ˆˆˆˆˆˆˆˆ(tņ�…."""""""" ĐEDDDDDDDÄ爈ˆˆˆˆˆˆx€BPč""""""""â ]DDDDDDDD<@Ą‹ˆˆˆˆˆˆˆˆ(tņ�…."""""""" ĐEDDDDDDDÄ爈ˆˆˆˆˆˆx€BPč""""""""â ]DDDDDDDD<@Ą‹ˆˆˆˆˆˆˆˆ(tņ�….""""""""0lĀŽäØÉŧĶą´ôž‰——ÆĀŅDEOįū…w01Đ0`ŨëĒiËB"í…Ø•”ūf:ÆA뉈ˆˆˆˆˆˆ\ŠĄŌŠߐ0ÂÃē~FãgtĐPqÂw—sīgąÂŌ4đŨģĀ…O6nĢvOD.,ŨÛ""""""g6p•.|IZģ…Ĩá=¯u)æ×ËŌY_RÆO,ĮüéĄĖÄ8cĶ`8ŋj›#%6ZŧŠOŅŊ-"""""rfŨœ.†1ą<ũĘķ$ø�Íķģ=ƒXírž Ôsčđ—¤+"ŨÛ""""""}ščB�Œ7“Đ‘Ã5Ũ××ÛxåŖĖøaĻņ„Œ$ú‡sy,{'GŊ´Ųd㝟Íeō-‘„Œ‹ ė–Ė[éÚūPö4BÆE09ûô8‰Ļ- Aȃ[éû4&?û)f:ö¸ÂnœĖ´Áo-58:íĪĶ%�GY˙#×ļąŲĮb4ŪÉęĮį2ų3aã"GėĖGYąå0Ũã&+n dÜ=üļē‰}¯-dō‘„ŒŸËûõũš°MÚōÍœFôíץãxļŽw†–Ú¯égÅ@ E¯=Ō[]į6>ŽÉŗŸåŌŪZŦgßĻ_đ㙓‰IȸHLˇLcÖĪr(Ēvûä0m\!ˇü‚}ŨÚ¨āÕļ_˙‡wvī{ĶV捏 düŖôvO¸sTPđZ/ßĢĩ÷‹Ûtx++ŋ§ũû‹$Ŧ§ķ8íëKßcÅÃ÷Û~߆Œ7›ŧŒ7‹éžK 7FŌ됟–ÜŌ}}ŅãfBÆE2o‡ęmüÖũ÷äÆÉĖx<‡}n— ŋ÷ˆˆˆČ×Ũ /ęGGxŅ­ØäČ{ĖK]ŽĨŧ&?9?C‡Š÷ō҆ũėŲËkšĪīįļOS1K~ôc~Wø„75 ?j8°%Ĩ5ü4¤Ųu,Ÿ>*[šJX’˛ßU´ā0‰“cđ38¨¯°q 8Ėâ].ßÄĻY!§đĐ\Š6íâp‹‘Ķī Ęh ÚˇŖšú§˜ņÄ.jZŧđ ‹&1ö: ÍGŲgų„7}Būžlļŧ2•ĀŽ=ŧÚ¯G 5;–ŗ~M “ ŗa4Æ>‹rš(úŲ îßú%xųnŽaĸ¯ޞĪę:^õ5vü2ļ_“ ^xŽæ/É|1?ŨãE¤9†ÄđfŽ”–p¸dKgWāøđ]ãļŖÃÆĢŗ’ekŸŅ˜ãĻ’čã úđ~,[×bŨą‹‡Ū~“§#0&†‰k9\sG`ĸ{;õûąT¸ūÚRē—CLeĸûaJ÷r ŧb'1ą¯kã°ąbæ\Ū(kiīĶÍzASĸâ<2‹?Áēv¯M č´Û‘-2kŅ'4āE€)šøH¨¯°aŨē뎏yčíˇx:ŌpŽÛ;8˛)YËöļoݓ}ĄéKīåƒį÷’ŋ%•7ß~†‰į9üÎ`0�Í8Ē?fÉēgyŋ)„‰‘“ˆpÔpĀzŌkšˇŦ>|†(ũēˇEDDDDDpvqôčŅŽ‹. ûįÜī†;Gä|îPÛ~ĩĨ}ÛhgZŨmÅ!į+I&įčą&ᤧ-ί:ĩ_íÜü“XįčąáÎ ?Ųã<æļęŋ3'9G wŽžm™ķ3÷_Y‹o39Cŋî=6Ü9uũ‘ŽUĮ>XāÚį-m}õģ9Žöx×ųŋ]ē|ėĀįÔī†;GīIįī;ēlu.ūpįčąSŋėzÎ˙ûŽķžī†;Gu.ü Úé~–ίö8Ķŋî=ÖäŧįwÕn+Ž8y›ëēÜüũéÎôĸ¯œũvhsŌØpįčīNwžr¨ĶҜö/ŪrN˙n/ũėMÁ#ÎĐąáÎŅߋvŪ|×ķ¯Ģũķ—I&įčąáΘĖR÷ÎĪ–ēž‹Đ¤5÷qڝzcŽsÂØpįčī/s~ÖŪÅĪžv}§÷|Đų\}ôˆ3tl´ķžĻ÷ØīSßųô7Ē}ųß7îvK×ûÃétūī \}úĶ}r:NįŠköŨéÎįöšÆ4–�� �IDATītĖųßYĶÛÛ[ãüĶ9oŋÁu?äL/čō=+u>węú.-uģwŦÎôīé{ėų~üliûīÍ÷ĸ“žŪĶų÷Ēã>vĻŲûlKDDDDDäëŦkĻrŅ /rTˊ‡Wš^-r÷ĮŽpXrY_Öžwų\,îÅ,¸kų#˜Ŋ Ų˛‘üŽá6>Úķ%āEBړĢüĸYēęnüÎđkwÕŽĄN~áÜĒO\Œ‘đÚÛoōQŪĸž++€oå`mŸÉ‹ČšĐy:RŋI,}v >´`}ë=ޏ­ōņhĻ)|>Kã:]3ķÂŌ—ŗYŊv%÷…wî aĖÜ p”‡û5Né´f_îzŽKĩ…!œ{g„Ps¸âôПúŦßō%0š~ŲåģĀ@Äŧe<Ôėâö7WEMžāå §GĶ88d9H‹Wˇß3�ޞ¯ÔŊßėŗēŽ“Ûš:Ĩ'ÆØGxqíJ~Ŋü‘nU#Ķî&Ūh8ȁęĶËŧģ‘ŌđöO›Ũw2•öI!×âUÁ#į¸ũ[šnŸÉO˛4ĄË÷l4ņtú$|€šīą¯?çúĄŲ0‰Ėį&uūŊ œÂŊf/ šįĄ~""""""ŌĢA^Ô@ū3ØįÕ}Ŗš†ęšfZ�|cXņō“Dšå‡ö”Đ øÄNę´ŧƒ_ á`ĩŲ°–:¸/Á�õeĒ0Ũ}†!ō’BrY_ŅwĪÃđâK*ļŦåÕØe<`vK FF÷Ũp:đ"jZLÃyŒæIDyíÂRaã@=ŒéōÜ{sŋ†uđ '>Áí•QŽ&ęë›Ûƒ ƒĐBSS?¨SbHčáMT~žx-Žfš�#§‡üôļ„wYe_r ø($˜0˜c\×ÁVÂ!&@–’Leĸ9‹WEÅûiš5ÕuMęmŽĄG1‡$õÂ8&–DˇíMõ45ĩ¸ŽÃ š‰†æS[œūū"âLŨßßcˆ%ëŋö¸-8‡íK�ˆ˜Üķũaˆlŋ?šmX+ ž—ˇ Ÿč)=ü^ņ 0 45\ tGDDDDDäkbB—*ĘhčmuĀÍÜ;w÷ΘĘNO›Ēk\{9lšd<ž­ĮŨë\Į8RĶ�@C õ�^žøõXBxˆTô6øM[ÆŌķXZŧ—ŦÔ~íÆÄ蛉K˜D|lt—ūžIŽĸ™ŽlZÎc…=oã*Ŧ¨áp t.?đÂ/ā,Ē\Ú9Ž|˝×å_|šæžˇīß�zęIĮŗģÛsz}M Í�MûY˙øS=žlØQãúj*h„ŅM\8XlûŲwĸÆ�ÕûŲWž÷˜cM\$ēÍëâ(ŨËĄđžDD?OŖÚ’ï7lŖ ô(Í}Ū §ž?#žũųŌĪu{_zŲŪčK hh ž¸�Ą‹ŅĪØĮ  Ī2ųš„Đå:î˙pKģ<$6>ĘäG>ĄĄÉķ´Ž €‡ÃõĐ×Rą—ú¨Lq44āhŸ”×`čåŌ€ŅĮ@ŋ( !Ü÷›ˆÚņ:ë7í¤¨¤ ËÎ2,;sÉć𩏐šxQ}æ!íaDMÉ.Î<hŖ…ĻnŊËŽxx#ŗf¯ĸ´|ÂĻp˙´hÂ}0z�Ö éŧa;ËFĪļíßÍeî,;ķÆ§*M`ĸy4Ø*°ÚšøÉ#M%{9„ņą&Ā@Täh(ąa9 ÃáPņ~šņaâäĒJzPŊe!ĶíĨB&§đPŦ ?ŖŖ@ŋ[´K§ęÔ÷įÕΎŠ{b{Cû:MœõŅS“=”Ÿ‰ˆˆˆˆˆČ9ģhŪ^dLXÆĶąûyēøV,û˜‰¯t™[ŖkLČÂؓŪĪÚ?ļ8záĀŅ|6Ã&ŒDL{’§= ŽYK(Úŗ‹vėåđÎUĖĒh`Ëæ'‰8ã3°ƒhđåŪ\ ™æŗ8ü9Š'?ûW”6ƒīälvŧ2ĩËĩuĀV/Ūđp%ƒŅØūPŊ ëÛw÷X!Ķ“ˆÉ7°á(,63ĸ9`ąŅ‚‰¸ö7ũDĚđŨ°Í5¯KxŗkhŽW æ~ŽbV¯ÜK3^˜ŸŨÄĻy!]6đÁ˛ čē0†æąžœũöĐŌÜûmÛĖÚß>Ô? $"""""2p.ĸ‰tũ¸ëŲGˆô‚†=ËYQØÔeŊĀ�×+iëĢú˙đčãëšŖĨúŽMPAi?†õČ@DÜt~ōÜöüW6ˇûBKŲ{ŧaíĢw„û4Q]Ķc§.°ŖXK[�_âįv \�jÎũœc@�>�5T÷ĩąģđIDų@ŗm/‡eXlÍÃÄS'C„—ĢÂĨŠ~?–2×>ũz•rÅ~5^1Ü;ĢkāÔåHˇąp„�4S]ŅËÄÃŽSUVgŊŊīéíkzŲžŠ†ę&×ļîsˇWŋôx6õö; """"""žp….˜9dĻ…áE-[EQ—ĈØh|€æ’]ŊŧąÅÁ!ËĮė;âļc`c|ʰ”vßÉqxũ˜Dš8dŲĘ;›Šéņ1Ø/†„p/ĀA}Ÿ•3L4_´°oĮ^z~Ž`_a Gę/DmŠ6ŧ0öPá°n$˙Ô5čŊ´âŧš&jöōŅិŠ/ũ˜‚ŌšÎ×Ä`"Áė56XK\ķšD›č˜ûÖh".ZJK8PZÂ! |ōÍŨŪ0ÕŗĶÃĪēg4ŊĩkˇåDE^={{¸l,ųaá7$°Âz.ۇ´ßp`Įūī&ëĮŽI‰}o&ŽãBÚŋßfę{¸ošŦ;]ûˆˆˆˆˆˆČ€¸¸B bŪ2î ļądeq§NC\ęéu?˙¸Kĩ„ƒęĪōãųiܛēŠĸŽgNˇĮúÍŦÛČ!÷gŅĻV˙lõ>ũéY=ŋ˜ĨË“ąŠĸ{%AõĮ|PÚręņũÔŧ TWwŪ#bîĖ^ĐRŧŠŒ-]Û̧čįiĖ{d3íė9ä9+!„‡�|IŅžÃŽÕtx#?^´?“ë"Ô×ô:Åņų3NâĄ×Gyįg/°¯Ë‰9o$ãá4J™Ë¯K;íČÄXPFá[s/ĸbMnëÛC†ũŧņŽfŽcĸš‡Ē•ž˜ã4Ûø¨ÔũnspdËŗ<ļÇČ�€ĒŨ‚Œ¨{æé-ÅkY˛ĨÆíš6qčĩȝĻp§ų|ˇ˙+ ģ\¨úbVŦų„fŧŸ;‡‰+FâzŊsŅģwēoGļ’ņü~¸`Ķļô~o‹ˆˆˆˆˆˆËE3§Kƒ‰Ÿ>{ķˇQŗu9Ģgė ŗcnŽp~úō2Ĩ.Į˛5ÉŘ‚ŸĄ‰jÛAŦ ā5š{W-"žŖĸÃ@|ÚÄíYŒÅļ–?ü˜øč0ü_˛¯x/õ‘Ëx:äW,ŨŲ×ë|Bxhy*æbYv;ŅoM *,�?4Õå@i -^„ÜŗŒ‡:Ļ›iŽiæŖE3¨7�îæĩį&a ŧ›Ŧ_–0ëgģ(\4ƒØ7Ŗ™î‹ÁŅĀĄ’7´@ĀČZ>ŊßsŸô.€¤y?ā×O|BņyL;<‰‰ĐTaŖ¨¤1OŊɋ~k™lÛKÖåĖ#†Ûg=Ã]ā8˜¸x ‡’eËåŪ[?&2ÎÄhĒ)c_ÉQšņ!rណėŧ§_t áėĮZ|¸šc>—S"bMøŧĩ K1ā{G/¯¤î1†‡f\‡åŨ/ųāÁTOŽaŒą™ęŌũėĢđážßŧIÜ–Ûšk3EĪ/$Ŗd*÷ĻŨMԘ9d-ŪËŦe{)\4Ø &" 4UØ(­i¯0ZûHû+ށsŲūšf-ú„šŅ1D…ø@ÃQöY÷SĶ žąĪđb§9hŒÄĪJ@ņ6jö¤3ų֍D…ûbh:ʁŌüæ-ã>Ëb֗]ˆä ÷öh]DDDDDärpŅUē�ãąt˛/đ%ŋ[ö+¸=#ÆÜ͛˙õĢN!Âø%ölロ{9ÔäƒyúŦ˙p ™q]û§ķZŪ:ąū …[ˇ‘ok"bákėxånû9ŠŅü ;>\GÆôGŲˇglŨEŅá&ü"ī ãå-ėx.ÚíĄĶHŌōįšĶä‹WķQŦÖ2ÜG}N[ãjoĒ cƒ‚­Ûø`zŖ‰Ûfķ҇/‘Ôŋ12}ō›ļ†-kS‰ 1P]ŧßmų˜ŽŅÜ÷ō&6ũ8œĀiO°tōh|ZޞoĮ^{jŸlūˆß-O!!Ē-ˇ_Cą),y} ›Ō{xëИ&žšģ$Ėm>—SÍFƸ†.>Ņ“ˆę÷ܲF&.~‹õ @¸ą™;ķx‡zŋ)dnŪÄRŗņi˸3ĖjlėąuTŒ™ĩy˸76CŊ Kņ^9|‰œúë?ÜÄĶ‘īÃŗŪ~ÆKėÉ[Éũ“GCÅ^ōßÍ#ßzCČZõ{~s7c眧1n›^žOBØuP˞Ŋh ņšMlJ! }ûķEvæ{[DDDDDD`ˆĶétē/¨ŦŦ$88xē3<læĄ=-˜—ąiÖųוˆˆˆˆˆˆˆČ×O×Låâ^äMGJØWVƒ#`I]Ē p”a9ėzŗĪ˜."""""""ra|-B—úâU<ô|ėĮ˜÷<ņ؊ƒCožĀû5@3ō ˆˆˆˆˆˆˆˆœ…¯Eč2fÖ2Ú2—õeÛ¸˙Ö"Í&}ԗíĮZŅėš|wų“g1ˆˆˆˆˆˆˆˆČ™}}ætqTPđæËŧąu?‡Ēhn/ßŅDDOâūĪ')\ī\‘s×5Sųú„.""""""""Ô5Sš(_-"""""""rŠSč""""""""â ]DDDDDDDD<@Ą‹ˆˆˆˆˆˆˆˆ(tņ�…."""""""" ĐEDDDDDDDÄ爈ˆˆˆˆˆˆx€BPč""""""""â ]DDDDDDDD<@Ą‹ˆˆˆˆˆˆˆˆ(tņ�….""""""""0l`ã ˙a3?ŨĶŌë^“×a{e†éˆˆˆˆˆˆˆˆG PčŌ.āV¯ēƒĀžÖų†EāR’[EHîģ<Đcc""""""""ƒk`CŸŅD™ŖsžíT—q¨B.DŸDDDDDDDD<āâ›ĶåHĶÆOcĩĩ˜WœFô‘„Ũ8™?ÛÉ�ë/ˆūÁ*J[’ųƒÂŪ‰€zŠ^{”iˇ˜ ‰éÖ{ČØt˜ĻކKXrK$ŗŪ,æˇN&lüBŪoęáø‡_ vÜdVÕ?Œ vĨÍõŖŖ‚üŸĪeō-‘„Œ‹ ė–iĖúųNŽ8NīŅtx+KfOÃ4>‚ņqL~đWUŸ^_ŋi.aˇü‚üÂ_0íÆHWÛ}ûŠļKß#cf{Ûã"‰N^ČjK}÷khų˜ŗ'cIØ-3ČØQAĶá÷xlĻk™é‡ yĩÔũ"ôu EDDDDDD¤ŋ<tq8Ũ?]7jŠáũ•Û0ĻoĸäĨ”üfM[ŸeŖ&0?Η§āCîŖdíT 88°r÷¯û’¨ÅØķÉŪL áĀĘy<ļĨĻŊQC ÕīžŒ%ú6m^DĸņÜÎáĐē4~ēĮĀŊkˇ`ų¤-ĢîÆ°'yŲíĄLõV›Ŋ˜Ãŧøa!–Wr§c?ž˙ÚOÖ`�šö˛ūŨîe¯Í ëûÜš>fɃË)ōģ›×6bŲĩ‰Lsŋ}8wÜBZjx]1‘Ģv`ûÜÂkąM|đŗ…ĖČŽáŪßėÁöų–ØČúų{íNŽĄˆˆˆˆˆˆˆô׆.ekšũ†(Âģ~ēU–´8ãIî wĨ"ÆČ;H iáPéQĀ€ŸÁ�xađ1b4�MķëMG OË&sš‰ĀĀ�ĸf,#s†– īqČ­åŸŠdūxQá!œ[æâāČáŸÂ]戈›Ã‹y›xmîh�Ŋû:ϐšvņcËOÖ>BTõ{ŦßsĒnÄ@KK s—q—9œˆĀS3ڜéÜãÍ<÷[VÍabx�cÂIL›ÃDĘ(´š×¤´8ã’ €‘øi&ŧZš‰˜û�ÄOƒ ›ĢBį,ŽĄˆˆˆˆˆˆˆôm`įt Iá×ĢĻwŸH×āC`§ Z|āöŗ_/W•L*J8ÔrIf÷F DņáõŽõáįZnęy"ß~35̈́ĪĸUĖzü(÷N›DŧŲD` ‰�ę]IøüöpŖßÍÄļđ†ĩ ĻEˇ/9˛ëôÁ}ģcķ.V¯Y΁Šę›Z\ÕB--Duē>>Œ đsëļF8}<ŖŅ€-ŽJŖŗ¸†"""""""Ōˇ ]ŧˆˆ4õc"]gõîčĻf|É)ŧŅmåhšš€öĀĀ`ô:‹†{8c[|6˛ū­müú‰×YÚâCČäųd._ĀDŋ×ņĘ9nqˇ}ŊBšO§ōōq 3ꤏs?˛‘yŗEũägČZÃ_/p|BÆ–wĻåšk("""""""}ØĐÅSŒ>¸ŽÄ—7đP×W ø÷kĨ[č\dc`L²€ŖžC–mŦ~~-ķųRü›ŒF z-é–{Œ û<6ũtdĪ6J “X˙Ëģ™xĒņúso¯ƒĮ¯ĄˆˆˆˆˆˆČ×Ë%紜ūkH4Q^Û¨nöaˎrŒĻĒ 8ģš[ŧ|1ЂÃ}z”Ļ Žtŧ¨‰C–ũ8Â'åüˆHXĀԊ]$ŧkŖšéDDކ8įŅ‘ē8¨¯nÂčw6å'Ũ9šĀāƒ{3Õ;ļr�ˆ:ŸäåB^Cā‰t›rĀRLQŸ}֊ūŋšØč…‘,Å6ŽŠĮaœÄũ3|9ÎęÂÃT××s¤ÔõĘæiOlĨžīO #§ĸ-ÅŽũä¯|C!G3ûÖ=Åŧ‡_ ŋ´‚ęúŽ”neũÖ |ÂcDÜ3sĶ62~ž•GęŠ¯>LŅkiLûá,V—ž_MJ`´ Ÿ†Ŋŧąå0õõ5Øō,•0ŅĒK˨?׿/ä5‘ŽtŠŲÆĶķˇõŧÎk ë?_CbÚ ŋ›ûŖ?aõ˛Yˈ^ƎˇīfâsoņšņV/›ĮošÁg4Q“ŸáÍÅĶĪn*C,KWĨđØķéĎc`IO=ÉC Ûį: āWÖáXļ–ÕžGMs ^žŖ‰Š}†7OrU„NįĩˇaõĘי÷ŖÅ4ãƒoČÍÜĩö-~ÚmâÜŗcLXDÖ=iŦøų˜ņ%<n>™Ģî† G™ˇ!i† ”Ė=—– ˆˆˆˆˆCœN§Ķ}Aee%ÁÁÁƒÔ‘KS×Le`‡‰ˆˆˆˆˆˆˆ|M(tņ�…."""""""" ĐEDDDDDDDÄ爈ˆˆˆˆˆˆx€BPč""""""""â ]DDDDDDDD<@Ą‹ˆˆˆˆˆˆˆˆ(tņ�…."""""""" ĐEDDDDDDDÄ爈ˆˆˆˆˆˆx€B6Pj9ŅJõ_ŋâ¸ŨAˉց:Ŧˆˆˆ\ޏb(¯+ ø6Ū†+ģ;""""b@B—–­”}Q…˙ĩ×øíkņērž‘¯•ú3ū~ƒŨnÚNžä_˙:Nų‘*ž„ÁKÁ‹ˆˆˆ\ūdxQõ_ŋÂ˙Úk¸öš‘ \DDDž†Ž:ŖĪÕ\}Õp*Ģkģ;""""b@B—ūë8×|Ķ8‡‘‹ØÕWĮŅŌ2ØŨē´<ÉC5g¯ˆˆČ×ŨC‡ŌÖvr°ģ!"""2 ”„ˆˆˆˆˆˆˆˆx€BPč""""""""â ]DDDDDDDD<@Ą‹ˆˆˆˆˆˆˆˆ(tņ�….""""""""0l°;Đģüá­xķO­Ŋn1ėģŗX=7œ+°WņU1̞?#đĮO3{Ėš7ĶôŲoYœ˙ <ŸÂŽōîsoP›Æĸ[ũΊŊŋ}ö[~šĩ’ā”gxøĻáËO|y€vs°ĸfŧšöúņ$$'0ņēághMDDDDDDäëå"]Ú}3’ûRĸ¸Ļ§u>ū—~āâ1ž˜“§qí7Îm÷æ?ōŪŽJŽw]ū÷äŦßJåĩ™17‰khä…ģxgŊĢžIa‚r‘Ë߉ZöūöUļ5Eō“GĻ3æĒÁˆˆČÅéâ]ŧũ=f4Ŗģ—FGFãžĮ9¸}Õc"šáČį¸×Õ؊9LķœĘíËØoū“ĘėOŲwäÆ+šŦ¨eīkŋâõŌ&`7ĢūĶČsĪNâۃŨ/‘‹ĐÅēôé8Ūō*/–‡ĮŌ“ÛūĖ_÷éV}:Œ;€˜oũƒO_Z˖aĶųåCQôör˙ŽÚĪ–üb~ŲȉažGŪĘ=I70ęĘSë?cKūgü˛‘ãxsíõaܚ<…˜öa5MŸũ–e…žĖž…yĨŋé~–OŦdÕ üÁ)\õé.>ũK#ĮųßM`öŨ7t “†˙_öž“ĪÎ?ÕŌ<l$Á‘ Ė›qCG•O_ĮīŦûđĸūî˙īō]|PîOōã7đÅ~N“ÛēQīį&0ēīâķ Œ´ŌjoÕ‰ˆ\ž:..W^yĨūË/"""Ō‹Kb"Ũ'Nt˙tŦÎØ)I˜)åŨÂŋē–˙ũ�ī~Ú@đ”éÄ| Ā›kŽ˙7\?˛÷ƒü}?9¯íĸúē[YđhOŨŕŸoæ×[ūĖŋžúŒW_+ä¨ß­üäņ V>>‹[}*yoũföũŨÕİ+ûîkåûs° Æßĩĸĩëö Ÿē€įŸû9+ ãߨĘ˙Ī}đN+‡ Š ÂÏ˙„ĮĻøķˇ}[yīÔ6ũ8ūõw˙GŲžĨ cBŋÕC&7܇kžåĶé°˙}äĪTâĪØë5ļHDä˛ÕCārUČ,z<ßAė–ˆˆˆČÅėâ¯tųk!ŋ\\ØÃŠ‘|˙ņ'šķ:`øXfĖOæ[ųėŊi×ė*¤æē~ößlßv8’į0á ‡9玘/|nâÉQŒĀģ“˙Áå˙¤¨Û÷•ŪãylFŖ¯đ!fF‡Vlæ÷ļz&Ūę Ŗĩĩ•ĀØ$&Ži%žh嚛:*JŽ Šb‚ß>Ŧi€›ūO{ė Âė›Ú ´¯åÆß—ņ‡ömŽöëøg8ŋ~í‚ŖģōązĮ˛(ö›@ãÛāīäí-Ÿ3üÆTūīĩ}o.""ĄW°ųåßđ߯Ûyė˙āÛ]KWz \Bī`Ņã?äz•šˆˆˆˆôęâ]ünb^JßęēüJoŽqËŽ ÂėīŽãÍßä0ė¸/ɏßÜķäģ=jĻæËF†ũöî<žęęÎ˙ø ¸7 $@V $H‚l˛ eŠTiÅnUœ)ØjÕ:´ÚŠZ­ZĮvlkĢÕĒĩZ[mĢT[Ĩą‚J‡´ ũáV”EvHBHČYîMøũ‘° ˆČŊ‰øz>yHžË9ŸķMū¸y{ÎųĻeļÜ'eØ%|mXķų×ˇī†Œ>ô<ôÃe|wú¤Áę-e„Ø_L*šīûGˇô¤Ãžī€p(|Øą>9‡ŽˆO 9n˙5ÍõX˙ĮßŨÜžˆ™‹āė¯9Ą=tBåīōܯ_ĸ0}ߘœwĖe[’¤ļŦ˜y?˙¯n 3¸?w~ũāÅĀE’$é¤ĩũĐ%DfīŦâÉ;ģ?ņŋ\LUŋ Œx_Js<aBĩ@rāÁAĩĩHŽ;bŨzņņÕ\îˆ#øž§8'}ŧk>D˙'}˙N^Ÿĩ€ĐČ+¸¤÷Šm_ȝū;Ÿ’Ūsķ—†Ø÷F’ôq$ BËļé•Kgp˙¯[‚ޏüw~ës.’$I' í‡.'l'¯ĪYJ¨o.É ˜ĩļ?Wžqĸ{Œ„{á(ÁËááÆÁĪ™‡‡Į>>Šë?üQî/wļÔQēåin]tÄíü 7ž6Œ›îžL@é[L2ŸĒA˙ƝSĐųŽT’m)LøÖ­ėũųĪøĶÚæ}Ä*—ÎāūĮ‹9›…ŧēŌĀE’$éd6ĄKɂ—˜]žËĩˇü‰ žāąYsũ­ÉäPî’HfFWXē‰ÂĐpļ|˜ŦZ:‹é¯'pÉ×ΧįQÎS[Ėú9˛{„C—ÄęŋöŖÜŸœÍ5ˇå܄‹xųɗ¨ž0+‡vm^âÚċ3æRtÆe.’tēfqáˇn…Cƒ—•˙Ë̇\bā"I’ôáĩũĐĨn'›ÖŽg×QN]陓FĮōˇx!ŋŒŧKŽ`Hb&^ĖčeOķÜÜá|gr:RËĒ×^âuÆrå}Žē„(ķėąd/ĘįÅ?fœI ü=æÎ^ĘŽœĢ脎g'wŅ\^œÕ‡ø‰š¤„ËX>7ŸÕqũšfXŌQZ<ĩú|ÄūOäū”ô#Ē: œ˜Jˇäæs%‹æōzEwΞ”@ɆM”¸6@ )•>ÉžÁH’>–Z‚—āĪÆÖã¸H’$œļēT,å÷O-=úšĀ`ĻŨ7‘Ē?æS˜1‘īLl>ėÃ%“‡ąüŠ—˜=ôFĻæÔQ˛q īģŸäąÜđu˜9gĶßM(Еė(ļ|f�� �IDATa—qķÅ-Ä&bÚרSĀŊD- ¤÷íĪ5×OâŦÄS=čŖÕ÷û?%õ‡(ÚXF8fūīžfūgĮ\Ë}“û|¨aI’ڐ`įˇĖxŲŧ¸H’$ŧvûöíÛwčÂÂB˛ŗŗOi'KVŽgø ŧSÚĻ$Izŋ%+×ĶŗûņŪhwB[™÷ø“ĖãŗÜôÍS¸l+ŪéįI’tZ:2Siû3]$IRtŗ8˙–{9ŋĩë$Iú˜kßÚH’$I’$Ž ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’" *ĄKûöíiljŠFW’$Š kljĸC˙Ÿ$IúdˆĘ§žÄNņ”–UDŖ+I’Ô†íŲSKlLLk—!I’Q ]2{¤Sēk7Å;Ëi…ŖŅĨ$IjC›š¨Ūŗ—šŊĩdgvoír$I’ĸ"Nb‚äöĻhG)ģ** ^$IŠ mÅ;[ģ„÷éĐĄ=ąÁ ũs{lír$I’ĸ"*Ą 4/}{eDĢ;I’$I’¤VåNv’$I’$I`č"I’$I’†.’$I’$I`č"I’$I’†.’$I’$I`č"I’$I’†.’$I’$I`č"I’$I’†.’$I’$I`č"I’$I’†.’$I’$I`č"I’$I’†.’$I’$I`č"I’$I’†.’$I’$I`č"I’$I’†.’$I’$I`č"I’$I’†.’$I’$IˆVG[jjš{ņV–WŗuOm´ēÕ)–Õ)žAɉÜ3˛?Ŋâ[ģI’$I’ÚŦ¨„.[jj9ī•7¨ …ŖŅ"hëžZļîŠåârūzŅ8ƒI’$I’Ž!*ˋūkņ—ĶLU(Ė-^ĶÚeH’$I’ÔfE%tYX\neĢĘĢ[ģI’$I’ÚŦ¨„.G›å2høg(žę3ܓtČÁ`¸ęķžĶØhvÚęÂ˙úyŠ'fĐų#ļ”ÖwÅWËŨŪÎŊy$I’$I:ļ¨m¤ĢhÚÃīŪXBACU­]Š$I’$IŸPm:t‰íܝ{F÷ãŌ´xbky§pwŧUĖ{MûĪåpiZ<ÔVđģ7ßåŪí ¤åæŨ1í™ņV5ã†&Sđˇ×y{ĐgųuZ1w,kĪ´édą‡—-áæÍu@_=”īew%ĢC=+ļžĮÍ˙WÄĘĻö|᜖ûV¸qh*ʸwÁ&b‡ åļî1Tm[Å˙ØÎ{ģžÃ%ņč”Oņ…âuÜĶ›Û×1ęīĨ 4˜{$“ĶÄ{[×qũ˙mįŊ& .{ÎČW’ÛŗuÛf6åđŊŦržūÂRVø4¯hâĮ//äņĒæ)ī~ē3æÍį;%øĘ¸á\]ų.ũō‹™pÎgųur1?.ėčũjš~ærŪ8VŊí;1åĶC¸'ĢT–ō‹ß*.I’$IŌÉhÃQĮpéčÁ\_ÎķŪ`ōĸ2:g÷įļž ‘ÛÎĘÕ ÕÜ=īMî(éčãō… @ЉKsÚķģE+™YŲD=@|:_I.åæŋ­ĸ ą3SFõe–w&öëÄúe‹¸ā­Ũ¤gâ§ũ ÷MIØÎÍo•QŸĐ{>7Ü īrGaYŊúqc7> žC5Q×q=ŗ˜PąŽ;V–Ķš÷`ž‘Jũ†%\øˇÍTeœÉĶC�˙2j0_KkĸāÜŊŊ S˛šŦoú0Īąe éLé\ĘŨolfÅqęMËČŲØēaW,.įŦŦŽ'û”$I’$é­ ‡.4īë߉qIņPļŽķfÎįú­aHęÎŋtõ…›˜YVÍË+ļ˛"&•K3ÚCcķŊoŦXÅôÍģXĸ9‡Ąšß-+æ˛íLß^ņČƒĒ­īrŪËopũšJŪ),e›Üōäũ÷­(åÂ­ŧŅ�q•Ûy|s/o(Ŗ”X˛:ĮŋžŖŠŲÁŊ‹ˇķrIge§ŌĨą‚ß,Ģ`eÉf~SÜH^Vw‘Č„nA(ßĘŊkJ)Xŋ†é;OōA6ėaú››˜šĩ’ĮŦ7ĀYY]ˆŖ‚éīķNI÷Žs’$I’$I'Ŗ—5 ÃáGã€æ” ™‹×0n\.SÆ įj ˛rwĪ_΢Xē�éƒGS8� Cķ} ņP ĻtĪ›÷6ÖSZ×üĪē†ƒSEbēņŨŅ}×%–.-ĩTq_e]sM•P×Pß|žĄąy ĀņęaĪû‡^ģ‡R�t‰ī�Rytęy< ĐĄ4ƓE.1@y}Ëĩ l­k<Îķü�ĩÆėzčĶC”†š/-­m�ˇ5–$I’$éCkĩĐĨ´ĻčÂāÎ1PÖŧ÷IlZ2šĀÎ=ṍFE×ŋZDl\'Îę֛{>Å=ƒˇ1susđQšn _Zq0Ô¨k¨ƒŦSESF÷gRüž>k9/“ÁŦ;“Av0 ĮŠįš›0•ĩĐXÁŨ¯Ŧ¤ā@ĻŌD)˜Đ�ÄĮ’TCVÜĄ UĐū@PÔ%ūCü(YogĨ6B‡Xԃ@˛ēĝxģ’$I’$é€V[^´së6ūŪЁqcFņ‡Ņy|wä^ߓôÆ*fŦĢēpĪ%įąüŗ}—ĐžēP-UPÕ†ŠbūR y=—KVV?ž<ˇ?“â?lHī�thO\Bž04‹AÛŠ3šīۏå8Nēž&Ū(Ŧ ˛C.ÍéDZ|WĻΓƒ‰Ĩš7v† 9‹īæĨ0!¯?ĶŌŪYZSK \: ;#ēõæļœN§ Ū&Vn¯¤Ž.|sT˛úp[·~¨’$I’$‰ÖÜĶĨn;×˙m%3Ë;pVN_nė—J—Ęü×ŧˇxŧ  ’_ŧš™ ŊyzŌ8^;ˇ7‹×đõw*jš˙.3k“yāü1üap'J7lâĪzû‘=üfÉ6VĘŸĖĨ•k¸vuõiyü4įÃĖđ8ųzvn~—kߊ$­ßp^›4ITō›ģ¨"ĖˋW1ŗ<Ā„QCš'Ŗ’ŋl;¸ŧ¨jëzîŪRCįėÁ<3˛sוŅü.ĻVīÖ k¸{K-éŲytH' VWPGûlW’$I’$í×nßž}û=PXXHvvö)í¤ûŗ˙{JÛû¤š0ū<žĪ*ãš?,å/­]L‹âĢ>ßÚ%H’$I’Ô&™ŠDeĻKb°÷ëUÄdvrū‹$I’$IĮ•Đe\÷ähtŖ(”ÜšĩK$I’$ŠÍŠĘ”ėĪÅåT‡Â|ąŽŠ`Á_éŪÚE´H øŅČū­]†$I’$ImVTfēôJˆįoãķYé.Ių˜ËėĮįŗŌųÛEãč•ā›$I’$I:–¨mļŌ+!žįVw’$I’$I­Ēõ^-I’$I’t3t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤DŖ“íÛˇGŖIm\ģvíØˇo_k—!Š ČČČhí$I’".*Ą‹Ŧ$I’$IŌ'Ë‹$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$)ĸēÔ3įÃčÛoā1ŋúãoÔG§˜Ãęš÷aôŊō%Ē�6üŒķú}†ī-z!’$I’$é4ˆjo=/å§÷_JæŅÎĨö'6ĒÅ�Ä2đęīķĶúĄ­Đˇ$I’$I:E7tIėÃđҟ"'Ē_æčËø×Ö.B’$I’$v>&{ē,ã{ã†qÅ3˙äž3•ņg Ŗ˙āķ˜|īߨšķŸ<ņÉ|ęŦaô7™›ž_}Č2ĨmĖģ÷k\8n4ũû ¤˙Yį1ų;3Yuā‚#–I’$I’$"Q]ęëëß˙u÷ÅÆ6°jÆ/Y}á/øëÛKYđ@6ˏ‹É×ũŽØŸáŸoŋÉĢWĮ0īŪû™ŗŗųž ŋž‘›ž/cøÃĢĪįÕ_M#íŸ?äšģ˙Ų ûĮH’$I’¤O’č./ZķųČQNdpíËåŋ˙öúS¸ų3iÄiã?ĮpūΆŅĶøę€Î�äœ÷92~–Ĩá_Ķ sō/˜u^ 99Í÷9…ëĪ›ÎåũøOņđ$I’$I’ö‹nčŌ÷‹<z˙eīßH76‘Ėž-˙>löK,ąclr›ŲˇÜĶ™ØČqH;ątĻžúú—P4ë~~tËŠĘĒ[fÖTCį›]#I’$I’t˛ĸēÄôdā°ĄĮŨHwÕ/.äĸßl?đũgYÄ3åÂØũ˙9Ö{‡ĒøĮŨWpũ_3¸öžûøņĐ :Į†ß\Á—^=ŲH’$I’$˜č†.' įę_đây žīܡķqŽ>ŽúˇxņÕ2Üø ˙ua߇7Ô7į&I’$I’¤SŖÍ….ąižv ǝ§ 蜖xČąŧø×˛æž‚.$I’$I’Ž%ēĄKõ&–üŋåTllOŽîËIÎkyŋÎũŨ}ūY}ę*rę×đô}˙Mũ§†Ā_7ąjCÃsŽĩ4I’$I’$éŖ‰nč˛íĪÜņī>úš˜I<šâaÎ?eõåĢÜÉŌ[~É5Ÿũob{ŽââÛîãÁO-Ŗ~Ų]üø‹7ûōo8“j$I’$I’ŽÔnßž}û=PXXHvvv+•#I’$I’ôņtdĻŌžõJ‘$I’$I:}ēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E@ Z5„Âí(ĨļŽž†P8ZŨJ’¤6 & >.–ĖéÄŖöņC’$ŠUEåSOC(˚÷6Ķ==Å[’$}5„ÂėǍdÍ{›éŸÛÛĪ’$é!*ˋŠv”Ō==…ô”Ž~Ȓ$é(& Gz i)])ÚQÚÚåH’$EETB—š=ĩ¤$uŽFW’$Š KIęBm]}k—!I’Q ]›ščĐŪ={%Iú¤‹ ÜÛM’$}b˜„H’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E€Ą‹$I’$IRēH’$I’$E@ ĩ 8ļoĪø ĪŦ ķŠĀ +øéÕFąĒSŽt?zh)y˙qS{GĻ‹PųzŪ|ũ-Ū\[DIE ĩá�ņIŠd÷=“ŗ'ŒaHú‡‚U Ëwį$0íž/2$5Gēũ÷Š~‹G4—ø¯ÜÍ×ŸČ !–Īø Ķk/æë‡Ķņ¨×ė$˙‘_PĐûZî›Üį”–+I’$IjûÚpčŌ"i_ūâpRŽv.ąûĮ;pH<“/|ą‰É‘hŧ–õĪķtA Ų#Į2鋓ČLO ž:Ē*ŠYŊx/>ū‹.šŠ¯L‹D§‘Ëg<ČëCoáÃâ[ģI’$IŌĮ@Û]âŌč“͇n­]G¤Ä÷`ČČh¸–õŗŸæé ™\yûW˜xčš )ņ‰œ}I#†Îãą§žįÅ´¸ŧ÷Į>Š 2ÖoÃĐÖŽC’$I’ôqŅöC—TËúYOđØÚ\nēíōZrƒ’‚ßpA€ËŋõUÎNŽ āņG˜¸ėØKAB;y{Î^[QDiM˜@B*؃'0õâ3éÖŌæŪíK˜={īl)Ŗ–ŌûãōÉį3°e–JÕÂßōũüTŽũr¯ĪZČúŠ:‚Iš\ōåËČ+Íįšš+(džÎŊ†1åË4!ī[^TÁōŲsxmi%5uוĖAã™2y™û3‘ōÕŧ<Ģ€7ˇS] >Š;CΝÄäąYÆļwí\ž[™ĘÔo^ÂĀÄ%‹įōBÁ w†‰ī5ŒŠ_ėÃĸĮķIšîFŽđį¯aŌŋŸŲrˆĸÅs˜™ŋ‚ĸę�{ ãōKz°čņ9đĨ˙äkƒÎTŗĒ`ŗRR&˜Ô!į^Ėäą=čXē€ûZHö!K¨B+ūĀŋ[CöeˇsķØ–D¨t÷˙ü-ōž~+´ģ•~2Â17rį„–Ų8ÕoņčæPxØō˛­ŧpĪĶ9€ō…[9ë?nåōC—kmŸĮ~ž”>×Ũ•gĐ<ž×VPT sF&M:^đĩšßÜõ<?ü„g ãĻ{.~ßU{7/äšY XŊŖŽ`Z&Ŗ''pėÕq’$I’¤ĶÜĮb#ŨP(ôū¯gãɛt1ŖYĘ ų;š—/ᅂ2˛']ÆŲÉ�q¤ôĘåĖ^]ŲGQūķ<ŗ"ĀŲ_žÜy ߞ2ŠāŠ?ņĢš[›/(_Â3OžÄōĀpŽųÖ-üā[—1&ŧ”éOŊÆĻ–bÁ�Ô­áĩEq\ōÍ;xä‡×ōéĀfū÷t^Ø8€i˙yüįed—.bfÁÖŖÖQRđ<O/Ē!{ōUÜyį-Üyõx7ÎáWŗ6ĩŒš‚‚?ü‰˙ įråõˇđƒ;o`Úš]Ų4įyfŽŨ˙Tvōækkč6qCĄdáŗ<<ģ˜ėKĻqīoįæs!˙ŠšŧGwō2‚t:œn[VSØr{hÃ\Ļ˙q ú7ž}Û \3 fPŽē +ÄĻŲOķDūn˛/šŠīŨy#7LLe͜§yfq¤g“—TGáÆî(\WD|BÛ6øYîŨ˛ž’¸l†dÚvw†ôŠŖdC{÷÷ļy=ۈߞžĸũ—•RXוŧŅgsVÚnŪ~{Ķ!ŋ#P´t ĨIũųôAB›[Æ7ō îŧíž1)‹Õs°í˜ŋ¸öļ‹�ä^v üįÅäyIíjfΘKaâxžqÛ-|įęņt^4—7ĢŲ¨$I’$é4×ögēėČįīæåDWÎũÖ­\žÄį1yō`~<c¯ŧ‚”šųl˘ČwÆ&ĩ\ΐKžrœ YC”lß ã“Ö<Û#y,×|3“rR(Z´€Õ fڗĮ30 ‰‰_žĀǟĖ%åžÖ˛ĪG8Į€ ŖČŒČbHNWūē¨+Ŗ'åŅ 1—ŗzxg{1Ud5;DōČ+øö �ŨŌ›ãäQL´€Ÿ­\M }Čd7ÛJÃ$OÅŒæņĨŒŊŒoô.&œÔˆ”Žá͊\.šĩīōōÜbr'ßČÎhžQŌqđ$Î}{)ĪT÷ĄgHîJ2Ģ)¯‚P¸x åIøæ’d$_Ā•ÕEü`NÍŅ_íæ.*ŖįÄ™:Ŧy6JJōÅLŨRČcoQ4rúx}C1{'¤Ņ‘ŦÚ�yãûS¸hE  ! ×ė=–ė Ôh<Hö L˜ĩ‰ÂĐp››ÄaÃČ\úī•BŸtØģe%qŲ\žŅƒÎ#ģ3ÁÖ_܇A€ŧŊr7éCGŅXŋ¨e|ä5/[KŔŗ×ķŨ?ŧwĖߐ`b GĮø éĀŪĩKX^Ķ .K^:@"'īfųũsØuĖV%I’$I§ŗļē¤äš/į}ûĖãH9dī׎gLâĘAŋ♧ύMå’o:úæģG${X&ņœËcŋßÉŲC0 '‹”äũËuĒ)ÚRãÉ=tÕÄl&…)ØX ÃZŪNčJæĄuוnņû Ä :|ğí†EųâšŧøûbĘĢë…ÄÃuˇ˙úî 9#7į>Ëo*FqÖ \䤑’‘u ŊÛ7Qž1€ė „ÖžËjr™6čĐM]‚ä^™ÍĪ(Ô!Íc-ŲYC #Ģ9pi‘rF6ésŠūøJ7QîĘY}ŨŒ7HvŋîQXdÄŲ0{=Û8“ŧęB Ģģ3zh, °ú$ŗzK˜ž3 rhč{å‘~‹õ;a`Fs`Ķį’Qän_Ė;[j™˜ pmÁœQdÁ‘ŖČÍĪįÍĩ!Bé–W¤2fdƒãKË:lŸ ŽŊúЍc‡.¤|{á¸LzĻr0š;ŲIēH’$IŌ'TÛ]IdöÎ:tãÉ;ģ?ņŋ\LUŋ ŒøoJyߎ[DūëKxí¯33Gú ņL<žŧÄ0ĩĩĀŽ—øÎ/ŊŋÄôēC”#i<<üØąˇĢ­eÕŦéL_Ų•s'_ÆÔ^]‰BIÁt[z°Á!_œÆM‹ŋxĪŊ>—p\WŒš˜)ä‘ÔV×AbW:%;wCŌđûŌ�*bõvč9˛{ķ÷e”ē’PGm‚¸ÃëŒOzßŦœęęŗ›ųŋŧ›ųī;™Jm-tĖÉ#ģn!ĢJ!ģt=ےōČKÎ$œQÆō-ĩL ˆÂęTFôM|ûÉŲä&åS¸š[›^Iôé•Āė E„F&°zK˜ė‰ŲÍ5'æėžųĖ|{ {ŸIõĘw)ĪΈôƒã#1pøŪ>Á�åDĄē:ŽøŲÆĩY’$I’¤OĸĶčOŧ>g)Ąžš$o,`ÖÚū\yƇų3:HˇÁãšrđxUS´v)ŗįäķĢ™ üđßs‰úNâ;“sßšâģōū''!TțËjč9ņZ.vpÖHIøˆŨXƒIäŋ„ŧņ—ĒŨÉúÅŧ8÷yĻ'ŪĀã÷ß>döJ˜C[Øĩho×tåÜô摔Ŧ|—Ēœ‰d7†@BáæûŒĩļŽZ8z0G€ŽŒūĘULL?â\ Đō:ėl&ÍeՖj ˇŸ3žÉî•Ākk‹Šb=EIšL9jXփ9qü߯"Ē×ŗ-Š}âĄÛÍˎJĘ)ŦîΈžûĢ‹gČø\fÎx—ÕĩŨ)_ZFöøÁ-3ŸšĮ…Ų ƒ—ãīãâ |äėĨēæ°N’$I’ô‰ôąØH÷D”,x‰ŲåšLũō\yv€ˇgÍeũ ˙Á[KŅÚÕlÚŋéi0‘ĖÁãš|LwÂ;ˇ˛‹D2{ĨBÅNBIitKß˙Օø@OŅĢ–Ãáæ?üã ­įÍÍ{Š„jw˛~ÅĻKV‚ņi 1û†)ŲR@į¤ŽPZF9Ō+›Î;—P°ĄšPm›ū‘įļ$ÕRĩaĪ-0qb^KĀ’@ˇ¤8ÂĨ[)9¤´]kWöũaŌû¨aW]Ü!Ī&n‰ņI-ÁFš9 l[ģ”U›Ãd÷mžeĶ-'ļŦæíĩÅÄįäļ¤éPŲũ˛aËzŪ\YL|īėæ™OydV˛zŲzJ’˛É;$° öňÄBŪĖ_ČĸŠ\Î>°ŧ*niq°ķđņUmŲtėņęo#JÎčJ Ž˜mĨ‡,/ĸđÛāH’$I’Nm?tŠÛÉĻĩëYu”¯õv6ŋŅĻü-^Č/#ī‚‹’¤ĪÄæˇ=7wSËojYõÚøÍk›ŧįˆNXŸ˙'ž˜ņooŪÉŽę J6/!qņ͛­fŽOníRž›ĩ„MĨÕT•ī`UÁķÜ˙ĐtfoūČs\šÅw'/ -d}y5UĨëyyÆB9™PWFQi-Ąp1|–_Í\ÂúŌ ĒĘw˛iE˙ˇ%p Čæ ģb ‹ļŊ'pͤTÖĪx„[ô4ŗvä1õ‹“˜04Ā›ŋ~Ī*cȗ¯b*A˛G撸s1/lbWuEK_ãšÅuĮ^^ߟ #(|íOŧŧbģĒĢ)Ųŧ„žüü~ U-—eʄ ysgwōz7G<Á^}ČŦXAūÚ0yũŽš@0'ėę5Ŧ­Ŗgŋ–eQņ™ä%S° ˆøœ‡6Á>œ=4õ¯/ĻúŒQ 9dOŧ‘ũIŦXĘŦŲĢ)*¯ híB^XTvø,—Đz^|ü7<Ŋ¸ĸå@ķōŖ’ īQTZņžßŖŽg g@\1ŗ°Ē´‚’íĢyųK¨C’$I’ô Õö—U,å÷O-=úšĀ`ĻŨ7‘Ē?æS˜1‘īl™ÍėÃ%“‡ąüŠ—˜=ôFĻæÔQ˛q īŖ“$&\}áYųĖ~j1åua ŠdŸ1‰.Đō6ŖáLģfĪ^Ā?‰ZâHLËfô—Žå‚Ū§hĻ iœ;e›~_î`R6#&]ƕ9E„ļŧÄŦĮŸ'ø­¯råu5˜SĀôŸīĻ6 >Š;&\ÅåûßÖߟI# xbÖ<†|ũ|úŒ˙˙5ūđžēMš‰ŸNP‚G”ßņŒ‹™6é%ž+x–äHī;’Ë'%˙—sąMŧÉ×2-n.ŗg=Íüš:ˆK%{Đ$n¸dø°&Øk�Ųu+X4ŒŧũOâ3ÉKĢaõÎ\äį9Æg3 Ŗ†Õ[˛xāy'‘Û;ŽŲ‹ÂŒÔũ}ˇdËĨķü:ŒÉ>Ŧî`Î$žqY˜įō˙Ä‹$gôgŌÅā×sāĀĸĒ:JļQUŨ2ĩ%˜ËŲgg˛zūŸxxmŽŧũߎØķæLĻ^Ŋ›įf-`úC“2}ņ$Îe딲�� �IDAT˙,G{÷–$I’$éô×nßž}û=PXXHvvö)ídÉĘõ ”wJÛÔídŅžeViw&NšĀč3zĐš%%Uī¤pÃŪ\ôë'ņ/8|SY T[ ņņƒ…ͯņũ_ŽaČū×tˇy!Šf?ÁÃkûķíÛĪ?æ˛%IRôųš@’$ŽŽĖTÚūLœ`cŽžĖÅŧ6˙yōˇ›P ¨#LWzöÍåŦŗ¯`ōāī\6Ėâûŋ~ĖI—qÉĐT‚ÕÅŧ>g)Ui#Ũæ—Uåģ)Y›Īs‹ęōĨņ.’$I’¤VačrZ‹'sä|mä@ˆŊĩu@ãŋ*˜s1ßøâ\^,øĪ­#H į#šöK‡#…ĖūųŗŧI*gNē‚)ƒ?ʋ %I’$I:y./’$IQåįI’tē:2Siûo/’$I’$Iú2t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ€¨„.íÛˇ§ąŠ)]I’¤6Ŧ!&&hí2$I’ĸ"*ĄKb§xJË*ĸŅ•$IjÃvUTÛÚeH’$EETB—Ėé”îÚMņÎrBáht)I’ڐ†P˜âå”îÚMfôÖ.G’$)*ĸ2ŋ7&`@noŠv”˛ĢĸŌāE’¤O˜˜`€ø¸Xäövy‘$IúĈڧž˜`€žŊ2ĸ՝$I’$IRĢōíE’$I’$I`č"I’$I’†.’$I’$Iĩ=]Âۋ){äIę×m ŧŖ$ZŨJ'$ĐŖąũrHŊåzŨ[ģI’$IŌi *ĄKx{1[¯ŧĻš=ŅčNúĐÂ;Jī(Ąöíed=÷„Á‹$I’$é#‹Ęōĸ˛Ÿ=aāĸ…Ļš=”ũė‰Ö.C’$I’tˆJčRûÎōht#õë7ļv ’$I’¤Ķ@TB—ŖÍr L}”œÎ;Ę×Ŋ$&DĒ’ū¤ÎGÎitüņ+ä,¸‹ŽĄÅ>HÎ?_ uÄņúģĄ“u*Æ Íu~˙ƒëŒÉ"á+įŲzÜsH’$I’t*Dm#ŨŖk ū•§¨^wčąę"ÕßVĒ~øöÖl )R]´!õ3îcG~1õ­]Č)ŌnÄU¤NKĸü•ĐPŪÚÕH’$I’t|­ēĖ{‰ĘE|e‡É’}g ģi“‰ŲJÍũRŨīēM@ģķ)Ŋū!ö–É#Iųūõ$ŽčEģ†-ÔÍxŒ’ß-§‰,:˙t)|M7äˇŋöƒ§~÷eÄg'ŗoû2Ēīŋ]‹* &‹Äģī"åœ,Øüģß>|ÚEû_ĨûŨ“‰M)§~Ö|ÂÎIøéKtë7Ÿ]ųYt\BÉįîŖŽ÷9¤Üy ƒģÃŽeTŨ˙å‹* Ļ/īž™¤s œ†wæSöÃ'Š-‡vŊĪ!åÎëHܝv ÅÔĪ{ŠüƒĐaUėÕwŅãœ7Ø1ū>ö§ŊS˙œ‡öˇ‡Hxû vî:—”‹rhˇã-ĘoŋĒÍ!HGęƒ7‘Ø/†đ;39|T7žsÉį SNũ?žĨôžW s/Ų÷Žĸ=ö—YÄŪ4™īd‘pËM-×6Zņ÷?JõæĐqTūlņ͆°÷Ņ7čôŊÉ4=~EŋÛ $Ņų‰gI˞Īļ/<Á O’$I’ôI•åEĮĶ!ąŋ:$qeEÂ9{¨øîSԓCįģī#™—(yä-öeŸOĘ}Ntŧķ.ēŽØCÕÍ7ąķ1tüæm$å}ˆĸb†üāutâ JžzÕ5CézīuÄĮ@‡‰7‘z~áWdĮĪ–w΀CnėOōŨS‰]FųÍ÷ą›ĄtLŲ.Ô<ģ&c‰Ŋß`×=ŗh /I~—.=6˛ë†›({'‹¤{o"!‚So'íœjžw[o~–úŪį“:míH"áÎÛ钲œ˛Žgûũ‹iū $ítÜ!ģŊH<įö5@ûąŌqũSo>ŲŸ&å?FŅžNtŧõ6ēœŲĀžûĀÎW˛č4â`pÕaōm¤]žEhúM=˛šįßLÚänė[ø$;ᕍ¸é6Ęß û÷Ōíō,Ļ˙€mˇ?ECķIôâbĮE1TŨ˙ÕŸĪží{ÎČæ¤1aĮ^8ŸzI’$IR´ōL—:Ũû,‡ÆMķīĨđŽÅÄNžŠ„ŧh¨aī ŋĨžeLíŦYÔžC`ŠďØ@Ռ7¨k€ú[>M|īî´c+uÜĖ–GĘ īØÃž˜5¤^4Ž`v֟`Y Ø}ũuTV*Ņøv1]¯Č"˜¤Ũ9hĪ2*ųęk`×Ŧ‹čôͤæûz#>BĪ?Kå;k`ÅLö^ô´ °•ĘûgR]ä}•NŲĐ0ãĒWl„ęWézŅU$Œ RObˆœE‡y‹)ģôՖ%QIÍAIJqũēZøEŸ{ėuŦöŽp*Ÿķæ|*f-'D { Ī');‹ÔĐņŦX7“ŠW—b+ģ'ŸOúāũ?˙‡(z7—Đ”đ w~šØ~Ũ f9Ą] @ áuilBŌųŨaŨSėza1! ė…‹čtë8?FEË8ęf<Je~ķ\šęü-t:Žøä—¨é÷iâbËŲ3oûNėˇB’$I’¤Ĩ՗ÕθŨ+Ųˇ}5ûH öÂÉt9 œv˙ømKPCĶŽ�õ ĐPAcMs;‡ĖVč0b éĶÆ“‘Đ2•§á(3:Ž'˜Š7‘zū�:$Æ´´QAģØ:$Æ@ũÂ5ÍW6î*ZB—Äæūwĩ,˜i(§ąúˆĻëKī_Ō“˜L{ põcô™ ĐŌWî„f=ÄŽÁˇŅõęÛɸ¨Ū@å=wQö ĒöqßŋŠÄ;@ ąp>ģnŋęÍĮŅņÚ{ŋSôœĢËi`Oķ}1hGí€uûĪUŪu°ŅvãHšs qŊ“éÛōŒ6 ˜$‡ĩMģĘi"‹@âūŲR5„w\ŧÔđĘ4\=™„ŗ’B‡]‹Šy'tė‡&I’$IŌGĐęĄKčí7Ø{”=]*¯;ŸĘCžīpá 6™p.ŠwžOĖ;˛õ‹¯Ō8ö.˛÷ĄĒj7æ:Ō/JøņëŲúģÄ|į2/¨Ąąēb[ū❁@vˇƒ7Ö×Đ´īŅ2w'ĄÁ`Į1:Ē.§ hxņėøŨÖ‡÷U—@ ėžãßŲ“DĖāqtšķfēÜ2…Ē<IÃúW)ŊōUÚ%g{Öe¤Ū})WĪŖúžÅĮT͚cˇ÷ĄžN‹“~Î54Õ�)Ét�šH"Øc˙ōĸn$ŪyRæSō…û¨a"šŖžŦ¨Ąĸ9ø:Đtč‘L{jW‡āhoĀڜOõēŠtx ŲɄÎw/I’$IRÄ´ōž.1Ğ]Ļúu!q=>J“1´‹bčĐ{IS‡�1tč—C‡}Õpl°e OÁSH›�$Ķ/ĐĸÕ41€ŽˇN$ūœ)$_Ôũā}›Sģ ‚į^E×sF’øS‰‹=N?ëįŗ§bƞK|$į\Gˇ¯§SJøģ_ ĪŸīĸķā$h¨ ŠĄj*ØGRūø ŊB\F}Õ%45@S}Íq::^{'餟ķöž[ũ.$eōHâ'ß@—~ÛėÄi—ŅŸ„iSízäL€} Íû´t<wÁäÕTĪ+†~—‘ú•qĝsŠWäĀöųT¯8Vß[ŠyeÆN&1ģ†Ŋ.-’$I’$EPë‡.Ũ@ꭇ~]GbīĐdų|Ɵ_MĶāëČxđ2˜ūvŊ]Npęmt9Áv÷-œIųÂb‚WÜG˙čFÕ÷¤z{2 w^Gā•'Ø5ŋ˜o&}Z{Ÿ_F1Í!MÃr*~8‹:†’ōã›H¨™GM!Ã1–7m¤âöŠÚ5„Ô'ŖĮÕY4ž2“šÍ!j§?FՎž$?ú$YŋŊNõķ)ũá,BŦa÷ũŗhčqŨû$™^FûˇŸ ä—kŽ3ĸãĩw’Nú9īaī#Rĩ.ø[î"uĖjĒūŅĩg+•ŋœG=ŖH}ô66?Aņ Ø7ø:Ō.ęFh^>uÕ tēå.ēôƒú_~—’ˇŧú.zūø*‚…¯PrķSĮŨ7üyÔÅÆĐžú —I’$I’"ĒŨž}ûûŸũ………dggŸŌN6|ęüSڞtŌÎĄĮÜī“[îY|Ė™.9˙œÕ˛$I’$IGf*QŲĶĨ}§Ž4íŲŽ¤cčDpĖH:NžŽŽą[¨xū؁K {zT+“$I’$žĸ˛ŧ(ūŦĄŅčF:Žn$Ūú]RĮÂ۟ŨGÅq^Û/'zeI’$I’N[Q™é’zë ÔžŊĖŲ.jE)˙âų”ĀUí;u$õÖĸR‘$I’$éô•™.ŒîdũūI:}fŦK7Ô&ē§Ķé3cÉúũ“2ēđ ’$I’$}€¨ĖtæāĨûC?ŒVw’$I’$I­Ē•_-I’$I’tz2t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤0t‘$I’$IŠ�CI’$I’¤DŖ“íÛˇGŖIm\ģvíØˇo_k—!Š ČČČhí$I’".*Ą‹Ŧ$I’$IŌ'Ë‹$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’"ĀĐE’$I’$) ]$I’$I’" N6UmaÆÚ?SljFwj%íh×Ú%|$ûØwā߯m™ßz…D@ûvíHŽéƝ>ķCÎLéßÚåH’$IŌ'BTfē<žâ9Š5íkĸŦžœ)ų7ŗz÷†Ö.G’$I’>\^$}"´Úą&žņ˙îníb$I’$é!:ĄËĮ{ՉtZ)­ŨÕÚ%H’$IŌ'BtB—}|‰¤hhGĶžĻÖ.B’$I’>\^$I’$I’†.’$I’$I`č"I’$I’†.’$I’$I`č"I’$I’†.’$I’$I`č"I’$I’m0téÄČAwđ𐹤ë’øąÜ:înčÖé$ûčÅå#īâ֞)-ßĮŌģį—ųî˜ģxxäEœq’­ļ-‡1ąÛ—yxĖĨ >ÕŨ‡sø;¸&ų8×$Oæ'ãžÄ˜˜SŨš$I’$ImW ]ö°˛d3Ą„3uŒL%=íLz6mæ­ō='ŲĮ.ÎaŪîšæoÛõfBVo({‰‡W°á$[m[ŽŖ$I’$IŠĒ6ē@mÅV„S––q”ŗéŒJNeoÅV†Nļ‡=l.{—{ę›ŋ t"ž}˜måëØ^ģ‡đÉ6ÛĻ1FI’$I’UÖ.ā¨7đVy ӇĐģp;›=—0„aëX[¸Z€|ĻīDÆ'u#9�U5ë((Ėį˙UĩĖ‚‰ËCĪdåē…$õšÄāpŦ(áŗ#¯¤÷Ž_ķŗęą|đ:ô˙7­gAuÆ4ÍåŪÕīR} ãX†q3Sƒ<°â-*ŽRv|§3š {,ÃSéH %•Ëyyã|Ö/÷ˆË­CĪdíšųzžĮČÄ‚á],-ü3/×öâō>c9#!ę6ķ÷÷ūLAMKcÁ^Lč;q]ē‘°ˇĄ„ĩ;ōyqۖæįB/.ß?ÆmģNāĄw =}"—gôŖwĮ‚Mu”T¯bŪÆ|–Ö6¸&#}—÷HĪ@˜ęšuüī–’#Úé žō…Ôž$´|!/ī>ŧŸAũoåJūÂ3{‡3%#…kášŨ×éL.ČÃđÄT⊥´j9/oükëZnmŸĖĐėáķÉ=I j(cKųëüOájv6Āų(Iė:‘[GL႔^tϜĨķøÉâßōú€ Ÿ;û%æQĻnĘ÷¤o\ėYÆôÅ÷ņßĨ';{K’$I’ÔÖ´ÍЅF6¯Ŗ<Ŋc;įŗšęāũ9éInXÅ •@ ]ÉįÛ¯ãå5¯°Ļž={LbĘĀŠtXöÍéáöq ĘČÚĸįxlO%Õ¤ėĒj.?]RÂŋŸ@õ{ŋdæŽ:HúWÆäapđ]îŸMĶĄ7ÃēĀæÂUG \ˆ=“+]LFuĪ-[E)) ë{×đIJŋ˛yßq†Ûž+#3ûōōú_ķr}€3r¯ãkšSčUšŠW˙Šįģ0rĀu\Ņw+—/¤”NŒĖĘEąĢx~Õ+lAįÄąLÉũWĻÔ˙’gĘNbvKÂĻå¤ŧčĪ<ļvĩíSÖë"Ž:ŖŽŌĨķŲ&rmn?NJū‡Ÿ•남žœßk<p`IVZĪËš2ŪzoUõ$v=› ŗzd8ĶH¸ ‚‰cølĶfŽÜBi-w&W爌šž[ļšR’Ö÷"Žā‰ecKdd^ÎUɕŧŧū)VÔ7—Įš—2­Š’û ˇāų¨č4‘‡?{;ũw=Ŏ˙2ŸdqÁ¨ÛxüœŽū˓,k QßąéWqëžĮøöĢąn\:î ~2ú*^Ÿķ$kŽĶü7_Āã+žÎx$I’$I'­M./×ŧÊŊ Jī}0jכQÉ ”—-gÃ>tËgjxëŊ9,ŦÚEE})+ _áī{ģņ™žŊI”`w/—m?ĘōĄFjÃáæcá=Ô66Rģk +šz22ĩˁĢâģœIX¸ëč32ēĨ?ĢxqŨBÖÖVRQģ‘‚u Ø7‚ )ą0Ú�å;°´ž¨gmŲvBíãØžc*Y[^3HoPĮʍOņĀęš,ŽŲEEũ.6—-`QM9]{~˜Į|О…L_6ƒgļld{m%{6R°c#{;ö&ˇĨüŪŨû‘\ˇœŲ[6RZ_IiÕ^,ŪFđ@#éŒJīF¸ŧ€ŲeĨT4T˛Ĩô/üoåQl+ycãbÖV—R†Œô1-ĪoQËķÛDÁúl‰Á„äX i ]aī˙oīncëĒ;Ž˙l_Įv“Ä&Në ’Æ B áAdhE+6ŌĐRŌXģjESWĻiEŦoˇŠâŨ* ĩĒ´V]éV¤vjUZŠĨęИHQđĐđ`“¸‹ÁŽãÄOø:ž÷z/bį‰<”ã$åķ‘î‹{Î=įü}ŽũÂ_ķ?ŋĘŗ#ÃŲ_I˙íyôĨo呎÷Âú…ąŽûî|4OæŸ~,˙;6ūąíųæĶßÎ ­[ōšß™7IQÎüû ÛĶ_KRČOú^ČTkwÖ5Ÿxß÷_ųįųÂėk.ž���pî:G¯tI’ÁlŨ;WmĖēēŪŧ4“”–nĖ‹†˛māĐU í­+ŗ¸ÖŸW'Ēķļۗžņņ\°dUÚĶ—Á$I%ũã§ņ÷LOļ•ķ×+Ö§ķ­­LCēWŦNö?ž—ĒĮÛ 5]m&[g#ÉŦéžė,—rã+“Ąž”ęšŌ8›šĻk•Tfæ>\ÎŪŠy1§VÎdF˛gęđÎ&Ģ•L§4û…U3YkÍĻÕ7įî%i/•RĒ/Ĩą”dę ŋŌ™rŌ˛)wŽéNWSkZęísqöĨĄūĐĪØŲŧ$Ķ“{fĪé돯ú2˜îCoę;˛ĸ)|{x^ØĒĻt é<j\“}é÷Csįo[zæ÷™ƒ}Ų9UĘGgĪ_ßОL^rs>i{ļŊžWGûŗŋܟškXNļžxËŗî‹“å™ųķ •w䩉EųtgwŌˇãĐ˛ŅžôÎģåéāÁéLeQ.h8ūžį‚Ëœ/¸â��āœwG—dphGv]ôŅ\×Ņ”—†’u+×fņøSyzöļĄ–ÆĻ¤~}>{ũúc7>¸4mÉl (§R9n-9jzvdāC˛Šuk~üNwŽ^ZÉË;{N0Én)‹’,š=_žáöcÖNO6§Twy>ûûŸĘēšk‹ĘĪäá_ū|vžšJr:sŽÔuå“Wܛëk;ōŊ۟§g˛œJZsŨeŸËŸœÆnækëŧ+sÉĘôí~< dŦ–¤í–üũēš[ąšĶ\Ÿdē:;gĖŦZåđûúR×'•Ú‘gŠR9ŒŽP+§üî›RZJIZˇäË7l9flĶ“M)%Ų?øXŽlĘMې[/Ŋ!wÍÎķŊŪmé=xōõÅ[” “,0ĪÜķā1k§F—äđSŗæT‡ttp™#ŧ���œÛÎé蒩W˛}ėcųäŠĩi؟\ˇŧ”7Ū<<§ĘäôTRy5˙ųâ“é;jĶéZyŪ$¸g`âÅ<;ū{ŲÔŅ™_4­Īe•WōÍŅ…›JŪŠ&y"˙ÜÛ{L˜™ŽHeĻ”žôH~6ˇpf"{Îtl­sõâņ<ģãņl÷‰Đ­Y\JrF+jÍe+ēŗxäGųîžŪwĪ[) ķnǤZKëŌ’-ĨæÃīk•Lג–ú#­JĨæyû9žJ&+IFžČWz{sôCŠ*ՑŲsZÍŪámylx[RߚŽe˛ų#Ë}—LäĄW^ĖØI×í`F§“ ~=Ÿzvû1_ÅÔôÛ§ZæûęKßV���ÎCįėœ.‡LäųŨÉŌõšúÂõéÎî<;|ø6œá‰Œ––Ĩ-û28yø5\Ф|đ7}ôķžlؓööksëŠĩÚqäS”Žg˙ØPŌԑÆōüąČd­œąéCsĩ Ž÷g÷ÜkbäĖĮW*Ĩ”‰ Īk@ĨÖõšbqÎđ-eq]2}pjŪU,­šrÅęyąd<årŌ˛*ķļlkģøđûÚž N'míķj^CV/[y’ãĪžŋæŽ”Ļ†ŗwrî5’ōĖTÆV“4ĨkŲīæâšKEjéŪ–ž=Æ–Ž,?éú…°?;‡ú’Ö‹Ō4ņëŧ96÷z;cĩņ •Īøį���œ‡Îņč’Lî.;kŨŲŧē;Ų˙\^ž÷kåĀ3ųŸņŽÜÔ}sŽ^˛4mKĶÕūGšīšŋĘįģ:NŧĶS46ôËė\tmn\>’g÷žįgûļæŌÆÜŨŊ!Ģ[ZĶÖԙËVŨ•/^ķ™||É &ę8S{Ō_[™Mž4­éŧ`SîũȲôTŌØÔ•Õ §{ŧ‘ėyg<Ë¯É Kũę5wäúÚî diVˇ.MK]5=ƒ¯e´yc>ąæŌt5-MײMšŗŗc^¨ĖöŊCiiŋ9Ÿ¸°++šÛŗvå–ÜÚV9æę•ŖõnKOÃÆÜŗvC.niM[sg.[ugūîęOgs[C’æt_tGîģėsU[{–/ZšmrSgG&'z3xŌõ cgĪcyĻņÖ<´é–\Õļ<ļŽÍ^ūų¯Í˙’/vŧ÷õ>���üv9ˇo/J’jOļ—ŗĄŗ”įzŽœO$ƒųī—Muí-ųøúkĶ^JŪ)äÕ_7žõ><ąĻڛįĮ*YW˙Ë<?y’ĪNŊ˜G^N6¯š>Ÿģęö,N9ŖīėÎöמ“ŸŽŸÎ|2§`zGžßģ*÷^|GžôĄdtô•üøÍååÆ[Ōĩî†|ūŠäáŊ§ĩ˞Ũ?Č/šļ䖍›Í•Ąôŧũķ<úÆHŽ\ôgųäĨ™ģ_ûZz"ßč)型îČĒdxüĩüt×Sɕˇ%iHRK˙˙}?ß]´%ˇ^ō™ÜņėۚīŋYÉ_\ēôŊP~1˙ör˛yÍäžĢļ¤%åŒNîÎö×ū#?Ģ&ÉS¯~/ĨĩËĮ/ŋ6íĨRĻĨīĀųÆŽ×SNNē~AL<‘û‘|ņÚģķ¯ú`.ČxöŽž<ũ`žēĪ•.���$u3333ķėÚĩ+kÖŦy_ōĀĶŊ¯û[0—įžknËô_Ë#Ãg4YĘJ]ęÎö~#39ü§đ“ž'ĪŪ@îųŲÉ?��Āi9ēŠœûWēœ ­élY•ë×ܖîwļæ+‚ ���pšD—ãXŪyWžô‘•Ũ‘G_ßē`ķ����ŋ=D—ãØ˙ÖˇōĀ[g{���Āųėœz���ĀųHt���(€č���P�Ņ��� �ĸ ���@D���€,LtŠ[Ŗ�'5“z��� baĸËĖ‚8‹/<ÛC���ø@p{| *ŸuŠË×oü§ŗ<��€†‰.÷_yoÚ[âPĀqÔ§>ÍËōØ­gŨ˛îŗ=��€„ē™™™#nūŲĩkWÖŦYs–†���p~:ēŠ¸Ŋ��� �ĸ ���@D���€ˆ.����]���� ē����@t���(€č���P�Ņ��� �ĸ ���@D���€ˆ.����]���� ē����@t���(@i!Ōßßŋ‡���8e]]]…îAĸKŅ?���ĀšÆíE����]���� ē����@t���(€č���P�Ņ��� �ĸ ���@D���€ˆ.����]���� ē����@t���(€č���P�Ņ��� �ĸ ���@D���€ˆ.����]���� ē����@t���(€č���P�Ņ��� �ĸ ���@D��� (2´���ÜIDAT€ˆ.����]���� ē����@t���(€č���P�Ņ��� �ĸ ���@D���€]JĨRĒÕęŲ ���ĀyŠZ­ĻT*ąė˜čŌÜܜ‰‰‰���Āųn||<---G,;&ē´ˇˇgdd$H­V[°Á���œoĒÕjöīßŸąąą,_žüˆuu3333ĮÛ`ßž}™ššrĢ���Ā 444¤ŠŠ)ihh8bŨqŖ ����ŋO/���(€č���P€˙JS`PļSˆ����IENDŽB`‚��������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/reset-credentials-login.png�����������������������������������������0000664�0000000�0000000�00000050663�14156463140�0024115�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR����Ž���ˆaįÜ���sBITÛáOā���tEXtSoftware�mate-screenshotȖđJ�� �IDATxœėŨy\TõŪđ:3 ä°Č  û› ā6P0SxB“íĐ6-oeWĶŽu­ŦĖ͞Ŧ{õĻe†šqEƒ”Ķžė†āhė  Ā Ė0> "(Č(Ã}Ū/_ŊâĖ9ŋßwΜ9Ÿs~įˌFŖ)--‹ÅÆÆÆƒ  DÍÍÍ #FŒčpooPQQ1hĐ ccãŪ/ŽˆˆzŲÕĢWXXXÜúĐ ĨRiddÔë%Q022jhhčđĄAÆĀĀ — ""ĸ>1hĐ FĶņCŊ\ õOĖ""˜DD¤Å< ""€y@DDZĖ""˜DD¤Å< ""€y@DDZĖ""˜DD¤Å< ""€y@DDZĖ""˜DD¤Å< ""�ôuDD=ĢŠŠIĄP¨ÕęÎ~ϝĄP(‘HÜ×ĩ�Ė"ؚšš*++‡ Ōv쭚ššęëë+++ĨRi¨ãED4)Š!C†ÜsĪ=ũa‡{“ÁƒK$ccc…BŅ×ĩ�Ė"ØT*•ąąq_Wq;ÆÆÆjĩē¯Ģ�˜D4°577ôuˇ3xđā~raƒy@DD�ķ€ˆˆ´˜DD0ˆˆH‹y@DD�ķ€ˆˆ´øųd"ĸ""" ē؈ƒƒCxx¸^ęéM<?@Ü*/7ˇ9eŒfûŦ"čđų˛ .âŗČøÔŒėęjĨR,›9xĘåaŋâeÖ×ĩQ?õG<Ž×—šĘôm‹—lL­†ØĖM$nhUųųÔÔčˆÔčČ]‹?˙zĨ¸¯k$"ęWúcä|ķŠČŌ*/˙lĀ’/îǁ˛ČÅ 7Ļ*­BÖ}ūN˜[›ŋ2;zՒUqۖ­ōüáĶ ž%ŨĐ7yđÁ4_×ÔÔ¤ũīSŖ¯ BŗHj%õ hnjž–wúîWÆ}´.U)öZķõ†0‡ö‰ŨB>ũ\ĩ&ĸ@nÚųōÕé‘?‹ˆO-¨VBlæ  aM˜—H_7iN„éʸčÅכ-Ø´ą@˛-}CĀõÅã^ôz1AūiJXËßē/ul˜ũāÂB÷5'ö‡[ĩ­'zņ¤U n-“Ģ6žž12Ą °r zá˙¸Í3!"ē}“MMMļļļ�´ß3ĨũīĩkgĖ'ü0PÕU5ŠU׎5ßUÛʄ˜8%Ä!/„;tø¸[Øēu/]ˇę‘ŖĢ‚ÂV>#ŽōԘí‘kĻfˇÆĮ+@.ŽˆKHW.vОsT§&”‹ÅbejB6Ü´ ¤$¤*á$#^;A÷ĨÆ=ņWņ‡Ģ"c ÂߨŊ:!2U)–?>à @úē9‹# Ėäa+Ã†Ģ â#–-1e ‘^ôÍũEMMMhZMMMÍMMĘębu}MŗēņZsĶ]ĩšĄ„X(ŋ›…S6ž]íļrܧkÂC‚‚BÂ×lûvC� "Ū(äbeFbęõšS2  2ĢÎHŊ~{Ovbj5ÜämÆĸt_ĘoVXŲ‘‘Ų7–ŽNˆĖPŠƒĖ�eôg‘p˙<b]xHPPØâ Ņēf+īæ™Ũ¤Īδ€ëy�@ŖilnR7i›5€Áĩæģ;?(¯ŠÄfĻwsŊ8å?1ep˜0ŧēēēuĸį š8!!.ą:<Lā‰¸Ô„txA{PīđĖえ[•šĄ ˇÕŠ° p�Z÷éâ;XĘíŅfŅ‘1‘é+×x¸A3‚Ä�RS•° ķēQąOø ‡ČmŨŊYšˆŽãįz›FŖ1¸E“ĻņšFĶŦnlR7656Ūmˆ@ Uģ‰Õ‘sÜÚ žõ°ēė|ČŪâĶÖ¤U J 0ģ0“¸Ą,#Ŗ ĐÔ[yĘŊ<ܔŠņé� LMȆ™ [ģfīd)Ÿđ0”ÅėJiŠ;!2Ciôh�”•({Īvã`nžŧSŠˆôĄ¨5ÍšÆ&ĩęšF9ÜōŌī×îî"†›šeeÕÕĀAąũŒ°‡–Œ(KNíđã[*Ĩ€[ø§+nŪĮš:8�pË­™šĄ ˇRf¤ˆå+Ŋ`OĢę„Älø¸ĨĮ§*Å>7-|'Kš……šmÛ÷Ÿ¸ >A¨ŽĖPZ…=Ž}HU­ÄbņMĩ1ˆôčx\¯/}“­įhsA­nlŌhšU’{Ž 5Ģn¸ĢļŨäâˆčŒ˜TeHPëŽRėžŽu/ˇ*.5ēŖEMÍLˆ<Ŧ:z€W€Ü,"!>5 ŲbĪĮå�ŧärqdzF5˜^-–ûtk)‡°pųgkâbâŪ ’ĮGg(¯ŠÅ€RŠTļÍ�íßDDŨ֏ƋÔjeŗFÕ¤VU_œŸ/U5 īŽņ€GgXA™°qcʝ.ifī`d§fÜ4Ŋí7 P.ŽNOĖNOU:h?é,—{"#!ĩ #ĩ�nAˇœ[ÜáRf3ÂÄʄ˜„ėøČ Ĩ[XØõŅ'Ģáb ü|ģĄÍėŒķwú4‰ˆ:Ō—y€6746ǚ5ęfĩJ­TWUp—×�Ÿ•˙1CAĒ9nĒN\ĩ1N ąYGÃ,>3Í LØŪö­2aÕ˙xy-Žž~Yč).Oß“ZmæŲ2”o&÷ŽLß– ˇ@y‡sģ“ĨÄAaAbeÂg¯oK‡WøŒ— ärO1 â"Ķoˇ+†ß\DDzŅ—ãEhsņĀĀĀ@ŖV6k›4ę&ĘĀ@Đ|×y�ŗ  û7`áëŅÛOŠpx9˜šBUS^šš^Ļ„Ø!dç‚: „•ī„$žŊ1dÎų ËScļG&”Y…ŧĶúif3yƒrc\\Û ū^fÛâb”VaōŽ?õpgK<:Ã,:2Ŋ@đB`›‘+ŗ‹ƒ6ĻFG,™SķL˜ÜŦ:;.2F)—‹ãRADÔ]ũ(ÕĒ&ēI­jV74ßíįŅ´B6œ?š}WdBjB\ĒR ąØj¸g@ø33ÂÂ:ŋ%Į,hÃˇûå?‹ˆ˙l]´öķÉ3Öm^æuc +y€•r[ä7>âāč)ŽNPšÉŊ:jõŽ—ō ›aQÖūK5ÄöjēncLÜÆ5qb+ˇ >Į5æâR•JˆˆēÅ //ĪĘǺ̧=åÉ'Ÿœ6mÚįR?ãl�4kÔU}A^fđęØ^.Ŧŋ(‹ ˙Ÿ5įg|˛îæ{•ˆčŽ”––Z[[ë>Ÿ|ūāN‹ėϞ˛2GGĮ[§÷öųÁ×_••ĨŅh<Øöˋš››įÉęĪĨÄŠī‰…"ÃîüĄUĮ}´1n+3 ˆ¨÷ôv,\¸°—{ü#)K‰N<ŸŊ=&UéūÎâŽ/EQâį¨ČøĪëkĸ!v/ūtÃĘÎŽDõæA´!={C_ADRüũd""˜DD¤Å< ""€y@DDZĖ"Č tíÚĩžŽâvššš‚~qkķ€ˆ2CCÃēēēžŽâvęëë…Âģü:gũbŅ@&‘Hęëë …ögÛû•ĻĻ&…BQ__/‘Húē€Ÿ? ĸmđāÁRŠTĄP\ž|YŖšģ_]ė)@(JĨŌÁƒ÷u-�ķ€ˆŧÁƒ›™uøÃ$ÔĮ‹ˆˆ`‘ķ€ˆˆ�æi1ˆˆ`‘ķ€ˆˆ�æi1ˆˆ`‘ķ€ˆˆ�æi1ˆˆ`‘ķ€ˆˆ�æi1ˆˆ`‘ķ€ˆˆ�íī'ôuDDÔK ;œ.�āææÖģÅQŸÉĪĪīp:Į‹ˆˆ`‘ķ€ˆˆ�æi1ˆˆ`‘ķ€ˆˆ�æi1ˆˆ`‘ķ€ˆˆ�æi1ˆˆ`‘ķ€ˆˆ�æi1ˆˆ`‘ķ€ˆˆ�æi1ˆˆ`‘ķ€ˆˆ�æié#TGž=Ræzë?oųäyOŧņõ‰bUÛŲk/‘šŽ”=~°V}Ķ@ĐŖ›DmÔ™ëHŲĶ˜íoîã:ŧ;z<?Ieîî­˙œd6†ĩ% {ßōĄĐUņúëh@I{#@6ūĩäž.ƒˆH ŋϤ3>ŽzŨŖŨ$UEę7ĢWŽKĖ;°|ĨĪņZ李ĸ$-ŗ˛¯kāLB?ĪŽ‚Ąa_BÔßõėõCKųS˙út ĐxrGTnöõ‡¤Ę:•Ų×5ü0 ˆtĐķד Ŋö—ČIÍRu>Wmæ‘˙ļhęũ>îŽ#eŖüÃ^x'*ķ–ąŋÚsQī- ›.ī-séŪ2[úMŗŠr~Üō÷EĶ'û¸ģŽ”šz{M˙÷mq97wŽ[wY­vĖÚëī‰@ɉŋ:ŲĮŨu¤û耩ŋöÍŠ–OüÍ[6æÅīÅĄŽ#eŽ>ĢîĻ UΑUaîŽ#ũßHŠo÷–šŽœúīō¸xĪ<™ëH÷°}Å­sŸÚ÷Î_įųßī-s)íã˛dÕW‰m/�ĩ,ō×#7­ĐŠ=‹ÚNךŧNuY‰vŽĢwüÔĐŋm;QŦBĘZšëHYØ×Åm*iŊ~ ËKs[]o„wŌE›ÆLúŊ˙čļ j×ļ×ßUĒܘw—Lŋŋe L˙ëĻ[6uŊŊkn;Oú;“GĘ\§˜ĶŽŲä7dŽ#e÷ß4"ZōeČH™kš”ëO§Ģ×úļ›Ķ]ŽCē•Į‹:eb"*UĒZ ÃŖŠø—C—Į–4Фîō ˙†Šŧä„c;V‹9ē1ę_Ķl[æĒ=ņ÷Đ'^„Hęáãį+5De^rĘąĢÅĨüûđū&��Uæļа3ĩM͒Ēj‹ŗNĻü8åpėĒČ=K= ī¤ĮŽé˛ŦĄĄH¨cūö—ŽŠŧ}ü‚<9§R3SŊūxŽęÛŊO9ÃvúÂ' S˙s0C!r œīgk(õ‘ŨEY_ŽūgL‰ÍHš“ĨLÔQŊ–3æû}˜z,wīž´g_Û¸¨ @ä;˙A[�Påėyqū›I•Ųxų͞*EíŴĤë“bĸÂŋÚõǝI/t[:—×!+Јųëė—ŽVB$õö÷s–ĒŠS˙ųä#'ßyQT °ã“]^šÎé´ęÜEEĖ_įŋt´ĸŪS§Œ”ĸ"3vMØÉĖP‰Ģ×€J‘õÍĶ+ם’xx{MōV夜Ė<ēũš”ôwöī|Ėų Öå]ĶÕ<îrɎƒyiŠpn}—g&¤V@ezB&|[“kSrÉÄ@o]_ëÎ7§ģ_‡ÔŧŧŧkŨ¤<ŧh”‡“Ë”ˇĪuüđO/ɝ\<<_JP^ģvíÚĩšī;šx8=UŖ}ŧhīŧQN.ūK+[ēttåN.^ķvˇL9÷ҧQŗūuîÆ\ĘßwÎåáä2탖Žk~Xęåäâ5kk΍™Ž)ßąĐĶÅÃ튨KwÔc‡t\6îy7§qō‰Ž˙ĨĻĩsĖđrrņđ[wĒeĘīMqņp÷ę/wŪ…RÛÅ}ūSÛųģōÚí(VŪįáäâ˙äöķíœåâá4îųhm…ŋ>m”‡“Ë”•q—nĖSsęmmͯŸŌ.\´{Ž“‹‡ÛŌÃ7õyi÷ÂļĶī ŧ[7 Ũ*Qžxuĸ‹‡Ķ¨šm6‰â^šâ9ĘĢmk5;šx8=uŊq_šé´ęڅ2ųÖú•ŋī^<ŅÅŖŨÚ¸Íåå9ãŖßZįĢ9õÁŖ^N.žK×ÜAÁēŧkēž§æģįŨ\<<_:ÚfŖŨ;ËÅÃīąÅ~.ŗvßx)•'Vxēx¸i‹ÔņĩîdsęÎ:ü3ëlˇßķãE?îHT�ĸą^¯ĨíܖŌÉÔÕBmnĖ`9åõׂ%hLŲš¯åTüú?7~øņģyܘËĐyæo�yi™Úû—*sŠgoY›ž ŸØ¸'bĪáˇĻXŪQŨŠVK!}ôí6‡Õ† BŨ”dæŪæ„VĮ.ZĒ”ũcĄķí‡Į å ĻŽ�*cö¤ļ=˙/>zä ņŸdĸí7"ŗ’Š+^lsgâõĘĘ) äđžäی÷ŨÚ§îåŨBˇJTÉQ?VŌé+žēąIØŊũæ$“ÆŽû¸Ģ—FˇPĮ.ZëžMũ†ÎķW?éŪuų-ĨžũüØÖ.Lŧ^Z9M (Žœ¨ÕŊ`]Ū5]Īc"÷+‚"5ņÜõ‡kSĪA2vūĖąœK<ŲēbĪ%Ļ+ ë?ŅDį­Ž“ÍIëÚčÉ<PUä$ė[ūZ‚"÷…¯Líp¸!79å" ;ŨīχM|όšéiڍÖŌcRā´G=L�¨j+ŠKŠ‹KŠ‹+U†"�ĩĩÚ]€ŗLTÆm|¯ũЧåH/g[“;ëą;ÕjŲøļŋáĘŌF* RtžĶšÃ.¤ÚwŅÃąķ§É�E➸k‹¤3æË ĩũžĒ0ręÍũzO+é)wqC€NåŨDĮJJÎe)�ŅØ€öĮ&ōŲū:ŒÜÍKŖãF¨c%į2€häMõC6ÉgD×õkIåŪí–6ôö+ŗN•č^°.īæąô ”•É-ĮDĒ´„“p÷‘Oôņ@㊤ë9ĄŨÂeū–wŧÕŨŧ9écRzŧ~pqĮ##wtô€H6sķŋVŒėø ą2ˇ@cΞˇ–ÅßüP1�”d–´\vPåüøÉ§Ûb3JÕ`´ō͇ĶW~—ņ\đ>‰Ėk’Ü/`Ēß$ËŊßAŨŠ�¤675ĶRÅí´ģÛEĮ<æ.đÚž.=i÷ҊĄ–�sä@`<ÛĮ°MŋR[›[bÛDjkTVVT�wēs׹ŧvtŦ¤2ˇB;ÛM–áHo'ˏĶÂtxi�6Bģ¨Ė­`b+ŊųiZĘl€‹]ĩ�°qŋųZ—ĄÔ˛ũ‹Ĩ§w.ķØø8!+7%ŊvŠŗ ›Ē€ĖË×ŌŪNHŊ~ Ą"=!ųųÚ⎷ē›WŠ>Ö!ĩĄĮ<Ie2ËÖK††"CšØ8ųÎztēWį;•J�%Šą%ĪĐXĢ�Uæ×ķ˙”÷ā'§Ë=l%&"C@•ōųĘmī[ąļų[÷‡÷lßqđĮ´Ü“ßåžünīĮ8.YũúŗūļwŌcwĒí†;ėĸ“Ģώ°™1â‡é'Söć.´rŽÆf˛és¯_aÖö+ꨚ–‹—ĩ*Uë>MWē–×–Ž•hgë C“;šđ}G•é¸ę֘Ē>Í;Xg={í$•Jęī]ŖĶ<#ũ'J?ĪKKHW…úŸL.tžŸ3�˙‰ŌĪ÷'ŸĒ€‡ĨęTŌšFHå~#[ËÔ}ĢģųųęcR=ûy4š�•Ō ë|n3[EĖÆM§NŨxø_ĶÚ¤‹ E;Đū<ŨDôėú gĄĒČLK=øĐŽfÅôėš’¯ŋ-7ŅĩĮîTÛ=ՅåÔš“$'ãĶÅä,\ęœw8 đ\0īú-MÚwPŖBÕAžĩîyo˙ënŪi%††@ŖęÖŲTŨÎäNÜÉFØ5íĶlė ~•ΡKĒn]émW‘ß5ēÍãíī+Ų˙]zŌ9ø;§&å@4Éß �<äcEûO$žŦ?-'ņ¤’ISŊÚŦ„ģŪęôąŠ>˙>;)€Úâ’Ûŋ~y)§é¤EĶڟj”œĘíô}hhéá;}áë˙ŠJŒ˜#JĸÕŊĮîTÛ=օɔSĨ@ÖwGsU™‡dA$Ÿ5ãÆpƒTf@Q\rËœڒâZ�R[ Í Į͟?(Ņ×­uŦDji  ˛ĸâæANfžž*šÉŨl„“XZ¨-Žŧĩ~:´cfmŠ*+´ĢČō. îä]ŖÛ<†ŧE(IO+V%'¤7Â+@{yÃÄĢå‚*7ųT%D¯_öĐuĢë„>Ö!ĩŅ÷yāë3hL>œtËV—›ŸšĶōn×ūWdrĶébĘ×1ÚËMÚ#„Ú܇÷}ķuOŸ)c%@cmE­î=v§Úîčš. 'Íļ2Љ:’ ‘īüā6ģ ™¯Ī�i‡OŪÔomʏi€tb€3Đrū‚ÆŠ›ō*÷D‚žŪ:Vbã!iŠYífR%8ÚC_ĸÛF¨+™ˇģh<—Ū~ąôøDë/9™Üūf8ÕŠÔ´F@âî-Ķš`]Ū5:Ŋŗ�˜øúģY)ŠŠ)é Čü|[ļ0_ī¨LO8u2!"o˙ëˇ]éēÕuBëÚčķ<ĀČE‹}DhL|UTn›ĩâÄ/>ņüĄĢT�€ĖCāâ‰Ŗ™­ķÔf~ũėꓖ^´œĒNnũû[ëVŋļĨũ§@+b“€T6ŌR÷ģS­î$†�T%ÅmĶwmxΜ!rŋ~gĪEHĻ,đo7Î>vŪBo7ŊĶöË+ßųč˜"E }�&N6�˛bwˇy)’7žļŖDoŸ�Ō­ߊrPõĪ˙ߘ)âT=ôY$Ũ6B]úN÷“�•‡7}ŲĻŊ´īũ§B÷îeíXŋīÆũ>ĒÜo>­$ūĶ| u.X—wnī,�ļūSdhL‹ŠH.TîÕē3wö—K—üųįá<Õ¯õ@DĮ­ŽzY‡tCo|>š ļs7|:˙īąņĢCũŋ’ûzH U•įRS3+aķ↎fY€ÍŒ'üdųąÜϘž9Å×ĩšé'R+_ūjŗåĮSĶ“*ŖŪz~Ī_ņú‹į”ąaNĀn/ųHŠ j+J˛’Ķ/6bÄÃ˙Xîk¨{ŨŠV÷įî>RŠĖʓkæĖûNfh2}ũæP=wŅŽĮ‚yž[×g(!>sŌM—]nx;uūęcž8Mî7V&Ae^rĘɤū¯n~âú•šOzí[—žĩ5,đ„§­acEfę)•߇+mÖŧ̟‹ēUb9}ųK{R7¤'ŊōĐÔSåÎ†ĒœÔS0åÃkÖ'ŨÅNWtÜ_}TˇæLWŧ"Oz=5cCXāwŪžļ&™é§*œVŊč÷ÉGĮŅØõē”Ī™QņŪôÉ'ųģ[ĸ2'5)Ĩ¤ŋ×WN1šƒ‚ux×XÎÔéĀY>É;R“*! Ô^<��zO+:”˜8Mōi3¤ãV×sëÚčûķ�ļĶ?:üí§ĢĻy™TĻĮ<tāhz…‰×ÃK6~÷íg­ܖĶ?Šú8<@fXœxhwԏi*§ĮūšgĪŗļĶ—ŋ>ÕIԘ—|8)ŗÖpäŗ;o_ŊĀßŨ°$ũđCŽ$Ĩ•ŽöˇßFmžnyG=v§Z]úŋō~¸¨ą$#ųTIëļĢĪ.n.~Žˆ ųūˇ^§sũėčūwŸœę„ܤ˜ŊûcRō eÁĪŊāčsÛ|HöÔ;ߙ5QfR›™xėDJ.ŧžßą˙ŗGe†&_ßŊ:Ubčąô‹=Λ(3ŠL9rč?‰Y¯ØŊ˙Ŗ=ö5ēēm„ēˇgķØ{ļ.yĐÛR•“z,>%K%›ųaäįKŊĨ&Đņōŧ×KģöŦ›nRœûŸƒĮŌjĨSŸŲúí§ę˙]Ŗë; pkĪĪŽ_<Đ2ņjų:éě>–ĄÛV×sën0ČËËsttėë2¨ˇäl›üqĻûōøčŎ˜ũ#ĢØŗČį͓ĸПĻ˙kĘ@Ŋë°öđīåIŋ{j×Ŧžēģ–Žüüüwûũâü€zKmܧ™ų<1s „AEfrüÁoâ3ÛÖĻĨd°•9 Ô0 ę!ũāúõUΞ•kŽTÂfÎ+Ķ{lHĨ7•Zõ|D‰Č=w×W¯{ˇ%×Ļlúđ¨pŸ=Ģ‹Ąg"ē ķāO øČšGrr3Ō˛*EîO~ü樁qäėũüēyIĪîÍÚ1'đ„|âHŠamIzZúEDKŪŧí×VQ˜ŠŦäŖĮr!ąņšųÜ?V?æ=0Ō�€É¤ˇ÷Dųl˙äĢĶN%}ר(’HmåÁĪ-zūŠ@Ų€y’DŊ†×“‰ˆū\x=™ˆˆn‡y@DD�ķ€ˆˆ´˜DD0ˆˆH‹y@DD�ķ€ˆˆ´˜DD0ˆˆH‹y@DD�ķ€ˆˆ´˜DD0ˆˆH‹y@DD�ķ€ˆˆ´˜DD0ˆˆHKŋ—ŠT5””ŠÕMMÍŨoˆˆ:$ ŒÄ†ļÖÃDÂųŠãî6ĒT5fįJ$÷˜H† ô‡<Û()Ģ;ĘĨ¯Ģ "ęBŖZSuĨ&ë÷÷{z"ēģ/()“Hîblô "ĸ? ‘P`=ĖÂŌÂŦ¸ôRO´ßŨ¸RŲhl$ÖK)DDÔ% sĶĨĒ'Zîn4_ģ6ČĀ@/ĨQ—DBAŖZĶ-s‡ˆˆ�æi1ˆˆ`‘ķ€ˆˆ�æi1ˆˆčÅ<P§|öÂo}_ǎĪī\ņÜGĘôÖ^WjOí~å•we7´ŸŦž°oĶ‹oėū¯ĸ× !"ęÜķƒá~ķÍmŌkũ™x‡Č4)ŅĮķÔ7&Ē/ßûßē{ƒƒĮKz­"ĸņĮÍSgų}ãė{ąGsŋĐɎW~Jޏ>åJRTōeëÉķî3īÅ2ˆˆzD|ięSWũvā›CŠįKk!4ąr˙ËŦŗGIĩ՜ür˙ņė*ĩąÕ˜ā9Aĩß|”æũúēYö8ŋsÅϜŠkߞfäî\ąŠ4xEPÕĄČÔĸĘz˜:ߡhɜ1ĻúŽs؄Pŋ“›âc“ŊúJP{*6ļĐĖīyßá-ĪâJFlĖ‘Sųåu‘Ū;!pö4 �¸°ëˆōĀå/ûˇÄƃ›@�� �IDAT…}ë7_œŧvų}ČÛûvDųäųžŲ1ŅšŌš¯/ô5jíė6KĄ6'ņ@ėÉĖ‹Õ q¯_pp ŗö EqîxLtb~yFhnåųŒĐûŦÜÜŅäü ;Ę'?9ųJüˇ§Ę.+abī=÷ąi#[ÚȋŠOĘ)ģŦÔ ąōđž;ŲÉ�ŠöžŊŖ|r¸ĪÅøØŗeĩš!ļžÁ‹'‹SĸbįT6¤žŗŋĪZxģ2ˆ¨˙ęyP:bĶæ4‹éĪū}™q}î;ŋüô#õ+oĪ“ Qsüķ-‡+G-ZĩtŒqíoöī,ĒU oiB(ÔÆFū6oŅÛķŦ„5§ļ¯ßēåOžuëœŨ#t œá“ž#úČĪDGgvĐvŌp.jĮļŗfS[ŧx„¸Ąā—}û#ūĨYŧ:Äîļ5„BÍåä㙂—M“7ēŨŦ74œŪģķøeī°e ŦŒ4uųI1ģž8(yuĄ¯DŊcK˛ā/sÂÛŠÍ=ž+jĮWÂĨ`~KGĘĄĻäxlFČŦÕ!–BEæŽĪö|uÄåŨš.B(’÷ED_ũØĶŗ\$¨-LÜĩĪ.ķåKŧ�…š’¤ã%Ąķ×Ė•4œÚŊn÷Ár˜ûäúE‚ōã;Ū‰ũīč§n[õ_ũ`ŧ¨&=.ĩĘ9dQØh;ŠŠ…ũØ9‹^úĮŃĘô¤lŒ Y0ŲŲJjíôė,guCgͨíČ­„�LŊũÜLꋊ*{ĸZĄSčŒŅøoĖWßÄūîŗC\Z{Y?ĨW;ÎzÄÍÚBbn;zÚ\_éĨ_ÉTßž9�¸l4fîd§–ēA_)-Wqīa;ÔÜb˜ŨøĐ𗟠)˛b“+mÃæyÛY 5wš0cŪqæņ“ŝt¤11ÔÛR@âá#Ōpąė2�ˆ=C—Žyz†¯ƒĨÅPK'īÉ~#”ŋgˇļõˆ‰Án!`âæá%œũ'0„ÃGy ÕTæ_ęē "ęŸúÁųAYnĄÆÂGfŅ:ÁÚÍN[”S‰qĩyĨ°˜lw}ÜGč:N&HĒę¸SkÛÖũŠĐXuƒģâģa<:8ÄõĶoÎãŪ9ឭGô—ŠŠ5fãíowļüT–wžÃēhpč[‹.fiĪŌe¤yRŌî/Õ~=]FHl$�p1¯Xc6^fy}>ĄŖĢ• š8_[I™ ŗj]c#4Ú5&4ÖĨ‰ŨUXYÕ Ņh4j%`~ãˇZJ[–ˆ avŊ 14 pŠ‹2ˆ¨ęyPßPi›ccĄĐČu=P¯VCØæ!ĄąŠ1:ÉĄžĮ†:'ém…ķīÖf�¤AŲ�I›Ą@lĻĶķ™â;|„Nŗ_X<üxbJRĖ˙Å(æŽ>Á3f{[ •J Ēúį?ĩ›[ÚĐ�H:čHĐaˇęĸ[w$ ŧį†ģ  Q—˛sˑŽËĐūį–Vē*ƒˆú§~ÆFÆĐÔÔߘ Žo¨‡‘Š1Đ BŨæ!u}Ûųú#ą4ĩmöūęeÄ&]¸ģ¯.oˇ”ÄÎ/d_ę/åe$ÅF펚¯˜- `æŗ0<°í‰@ z'Ũ\LûoÅŸįC}´×Õ7�w4ō¯—2ˆ¨×õƒëv2{AUNîÃūŌė"ĩ‘Ŋ°˛•ĸǰėz¨Ī˙–Û#ŋĄ#ėlÕų…WZ'”į–iÄV6æ�ę­•_)ŋ¤ÔĄÅN—R_.ĘȮЎ†sōč!¨+ŽhĀ0'GA]•R<|˜eË?‰@`d~g7ö(5 ą¸cꋧ3*€;Zëz)ƒˆz]īæA}yΙŗ§oü;_X{M˙‹ENôÎÃgĘ*kĒ Ķöo˙ŠĘūŨ…€t”ŸNGGĻVՔžû÷w9BoÁéuFîĖōãÆgWT)ŽŸ9˛+šÚÆ÷>!�ŠãÁĨė´ŧ� ʼną) ]NË:_ĒâäŪ;/_VT].=—ô˘9Ž0‚‘ûä CōD~{Ļ´JĄ(/HÛģõĶžIĢŊŖ'2ÂÎFP–œ”YŽP”įü˛#ĒÚV&Đ\.ĘĶũrŒ^Ę ĸ^×ģãEUŋl˙ß_ÚNŗdĶËrc÷y+– ŋ‰ür]d-Œ-믄,[0M{›ĻUĐķ‹J??´ũíĀÄi\HX˜éÖÍšŊZ˛ÎŒ\f<šXķíž-Ņu02ˇō Ÿ=Y{3žd|Ȍßŋ‰ßüN˛Plæ8!8dBåļlMWĮܝ.%t›ņ×ĐØĮ~S§ˆ‡Z:ú,Ÿ6€Đ%ôÉÅâØč¨?Õ)!–:Ž ^2öÎ>Ã-ņžZ´ãHäēdHėG‡„ÎōTÄīLÚŧ//÷Đ­ }”ADŊÎ //ĪŅŅņŽ—O;{ÁÆĘ˛ëųîšēžÆÆ-׊ëS>~e;}˛|ĸJĘ*ƎrŅ_{DD=+íė…îėĩōķķ;Üí÷ƒëɡSu|Ûģë'.zōÜMՕg~ˆ<+ŗdGĸ‰ˆôޟįÅäį—ÖGŒüßĩ5 c Ų¸ĮW,3ˆˆô¯Ÿį`:jú ŖĻ÷uDD^?¸ß”ˆˆúæĖ""ŌbĀ< ""-æŨĪƒæk×ôR uŠQ­ {äŖŨÍ#ą¨ŽŽŋ~5Ņ€SuĨÆHlØ-w7lŦęęW뛚›õRu¨Q­)̏|ŠĒÚÖē̟]ŧ+Ũ=éŠÜí JĘ*ĒŽ45ũQ#!íė…ž.ˆ¨ "ĄĀHlčq¯CéĄQąĄČMfßũvˆˆ¨ņū"""˜DD¤Å< ""€y@DDZĖ""˜DD¤Å< ""€y@DDZĖ""˜DD¤Å< ""€y@DDZĖ""˜DD¤Å< ""@/ŋ T5””ŠÕÜßÃ!"ę˙´ŋ‡ck=ŦŸūŽR՘S(‘Üc"2xĪ6nVRV1v”K_WADAŖZSuĨ&ë÷÷žų‰´îîÁ JĘ$’{†1 ˆˆz”H(°faiaV\zŠ'ÚīîN\Šl46ëĨ""ꒅšiƒRÕ-w7š¯]d` —Rˆˆ¨K"Ą Q­é‰–9ČCDD�ķ€ˆˆ´˜DD0ˆˆH‹y@DD�ķ€ˆˆ´˜DDčåû‹tŖNųlų–Smî™IíGM™5}´EoÕ Y¯ŧŸûā{o>d­ŋ6kOí^ˇģØķéw3j3Y}aß§›ĪZ=ąjÁx‰ū:(.D}¸šāž5Ëũ‡÷u%DC¯å�ĀâžĨOŨg �Pח>ú]ä˙nĒ\ĩv‘›°WËčLŧƒC’ˇė‹>ū—ŋMsēž2ԏīũoŨŊŗ‚DÔ zwŧČx¸Ŋ›Ģģ›Ģģ›ë˜ą ūö\EURüŠú^-ĸ2÷ ėxåרäŠëSŽ$E%_ļž<ī>ķžŦ‹ˆū4z÷üā&B;g;A\UU `  æüሃĮŗ‹*4Æ&ļcĻÎY4ÍÕ�P“ũũ Ģęa$ĩ5yvØt7ĶNĻWíüû‡9“Öž=Í �jŪywŽ÷s˙zÁ[�š;Wl* ~ëõ@ ¨Ģ~;đÍĄÔķĨĩšXš˙eÖ‚ŲŖ¤�p~įŠÍĨÁKĮŲ™=|ŅĻe~ęŗ‘_î?ž]Ž6>fęŸZÃ&„úÜ›ėŊĐW‚ÚSąą…f~Īûļ †¨¯dÄÆ9•_^áéŊgOsą�€ ģۈ(\ū˛Kl\Øˇ~ķÅÉk—ßgŧŊoG”Ožī™+ûúBßcQyģۈ(Ÿ<˙/—ŽĮž-ĢՈ‡ēŨ7oŽŋ‹vE^|T|RNŲeĨÆhˆ•‡đÜÉNÚĸ6'ņ@ėÉĖ‹Õ q¯_pp ŗ¤“éÕ{ßۖīûâęɖ� 8ųÉ;1ųŖæ¸ČC�E{ßŪQ2ųŗũÍ;jˇÔ¯šđížØ˙Ë­T‹ĨūÁã{č… úŗęĶ<@Ue•Fhbj �5I_nŽŦšøĖßš›ĸ&÷‡í_nŲ.}w™Üõ'w~ö]Ĩü™ÕKėŒÕ59Gwo˙x§éËü„N_:Nf””W?ÍĘPįž-411.:[og�Ĩįsę-ƸY�õ§#6mNŗ˜ūėߗŲ×įū¸ķËO?Rŋōö<™ĄPSyâģĶ÷‡­žmem\“´aËáJīgV-cZŸs422­Ļ=ą6„N3|ŌwDšā‚čč,ŖņáÁÚÁŖ†sQ;ļ5›úØâÅ#Ä ŋėÛņ/ÍâÕ!vˇh…šËÉĮ3'/›&nÔū1ĻøxüīĄķ×Ė5‡"sßg{ļí7[ģhŒ1Éû"ĸ¯Œ~ėéY.Ô&îÚŋg—ųō%ŪFh8ŊwįņËŪaËXięō“bv}qPōęB_A‡Ķį{ڋSrŠë'[ę‚ %C†]ŧP '�—ōķ•fÎæˇ}j7Õ¯HŪēįč÷Įž )Qæ'Å~{F ޤéOŸåēž,ë§Ũ‡‹LÆ=īe �ÆãÂ×ŧ kŠ€TúđäŖŋ:“ų(T—6˜ŽšßÛ^ ĀBūĸõũĩĻÆ@i‡Ķ…ÂąNˆ8ŸŖžoŒ9gōLå÷Ų§žÍ*…ŗ5ęsĪ—ģ.°jŌãR̜g¯Ķ^Í;gŅgßøéØéŲ˛qB�¨4ž°zšˇ@MÂņlŒyrŸŗ1`1n^XΙw÷ĐJ:…Î}î똯‚ p2ÄE{TEÖOéՎÁO>âf�Ŗ§Í-üũƒä_2ƒí<ģēōrŲh˞É_˛11ÄÛ\@âėg››–Ų0fŧ‘Ø3tŠˆ°:Ų/éTlv1ŧ]pĨ´\9däxÛĄ�Ė-BǏ¯3N FŲ"*/_=v¤ųį‹%ŪŪļ§~˙ũœ†Ąž0¯\ė8{D×OíFũŠ“˙— 93|Œ�x†įeo9ÚũuND×õnzõ™C7ū4>îņÕîô„ÆÂÚ¤ûˇį–WÖĢÕjĩē°P€õ¨1?˙|“zĒ˙87Ww{S{gĶÛM—˛W'd•bŒ}Ųél8Ī p/JHÉ­Ÿn-Ė9“'t pP–[¨ąđ‘ŨØOZģŲ c‹r*1Î�¤vNRíey•0õą3ž>Ŗ•ģŅᲞZCÆŖƒC\?ũæ<îîŲzDЍXc6Ūūƅ„áÎV‚ŸĘōŽĀsX aÛŲũ[&#ŦLŽ˙ŋÅ0БϏø Æ …u)GbwVV5h4Z ˜k�ĀŌe¤yRŌî/Õ~=]FHl$ˇ›nībĢ9yĄ#GTœËSČÄ{/ūú[aCā0A~vąĐyĸŖOíFũ—ŠĒ0düˆÖ•"u!>Ú#_Oô'Õģy`åŋlI€v?+š˜Z›ļîeĄÎŨŊaĶqá}‹Âį¸[ Q›ôĪw[ĸCčēāĩWŦcH:ēûøžĄ…Ģßė äVÂÎĻK]ŨĨsrk`z>§ÆÖOfá,3‰ĖÎS˙ÅätŽÚ9ÄU žĄ‚6ŨC(42†F}ũŌļĐøúwŊēSa›{ôn(ÉHo+œĮxˇ6—‘” ˜´đ ÄFĐhēnN îô%6’´ųá @�F ¨‹lŨ‘$đžė2L,D]ĘÎ-GZzušũÂâáĮS’bū/F)0wô ž1ÛÛRØŲôĄŽ÷šĮį( ÉĪWXų؛;Ų‰Î)VO’Y¨q tęđÔnÔß i€Ā䯺 úv´“h éŨw”ĐÂÚŪŽãÛö I)3ņ{u‘ŸŗöīÚúz õČÖT6yŪŌÉķP_zūˇŖûwūŠĐâŨΝMˇãf|<;¯ÆôlĄÔÕŲÖŖeˆ8_Zi’ScįãĻ22†ĻĻ͍Męú†zĩMˆÆB!45ę63Ö÷úũPFb#hjÛėũÕ ĘˆMŒ:˜W÷īEoP(oüĄQ6@ Ķū[1ÄįųP_íuõ @k6IėüBø… ūR^FRlÔîĄųŠŲMˇé,ūŋÜâZɅs''# wŗET^ųeIžÂjœĖčΚ‘@M­Ļ͌ Ę[f"ĸģ×o>ŸÜ QÃDz}wŦ.üõˇ2@ �ęĘÜßΔiwČÆÖŽ~ķfÔ–Õw6€ķhWäžMJ+6–šZ°i_sūtęŲRŠĢģöôÄNf/¨ĘÉ­jíŋ4ģHmdg/ŊĨ0 [kԔĩf@QVnüÅ팰ŗTį^iPž[Ļ[ؘC{`_¯h-éJų%]÷’— ËZŸųÅJ@jk(5 ą¸ž;V_<Qh�@}š(#ģĸe…sōč!¨+Žhčl:�GWG^H9[fäā8Ā[E~fú…rsG—Ą]>ĩöĖ­‡ŖŽübkt”](ėõ‚h@ë7y`įd/(N:zĒ´ĻĻ4ûĮ-ģĒėŨęĒŧœz5Ęw~öé–øŗ…•5••E§ãĖ‚…ŗq§ĶĄÛ(įšô¸3õöŖí�ĀXæ.-Š;šgėæe¯íÎØkú_,rĸw>SVYSU˜ļûOUö<č~ëPÔÛO†ĶŅģg—U–æĻD:­îč°ŧGšN0ˏ?Ÿ]QĨ¸R|æČŽäjßû\„�¤Ž#—˛Ķō�4'ÆĻ(t=į3ē|ōĀņŧr…ĸ*į—‰e’Qc=Œ€v6‚˛ä¤Ėr…ĸ<į—QÕļ2ærQ^ƒ'÷îŒØ‘xĄø˛ĸęr隤_.ĀĖq„Q§ĶĄŗ‹Ŗ"ëxļŌÆÕ �Œl]Ėˎ'9{ØvũÔÚęîcĖø˜¤œŠĒKE˙Š?§æĩéSŋ5ŊoQxŪ–Û_=SŲİĮĢ5*üė‡÷7āõ7ŧžwėÎwöÕĒFRkWŋg—Í´‡O�c×1öĩ§s]ĮČ´û w™qä ĩĪXģëũģĪ[ąLøMä—ë"kala7&dŲ‚iŪži1yÉ3Ĩ;"#?^ģ[8Üũ9 ū˛sš#÷údä2ãÉł˜o÷m‰Žƒ‘š•G`øėÉÖB�Œ™ņû7ņ›ßIŠÍ'‡L¨Ü–­ŅåČyč„ĀņŠã˙ÚP|Y#æŧ8ÔÀÄ{^hŅŽ#‘ë’!ą:ËS[ŧ3iķVŧŧ|Æ_Cc?øQLF jéčŗ |ÚŅņt�0rôQ—Yč8˛åÆYķ{ÄŅɚqŖŦtxj71÷[Vž?6ú‹O¤.žÁŗ'Än;ÛË/Ņ@f——įččx×˧Ŋ`ceŠŋzš’˛ŠąŖ\úēŠ[åí}{Gž˙õ‹ŅJÚŲ ŨŲąäįįw¸Ûī7ãEDDÔ§˜DDôŖëÔ̜æŊņv_×@Dũ Īˆˆ`‘ķ€ˆˆ�æi1ˆˆ`‘VwķĀĀĀ ųÚ5Ŋ”BDD]jTkDÂų¨@wķĀH,ĒĢëõī&"úŗĒēRc$6뉖ģ›6Vuõ ŠĢõMÍÍz)ˆˆˆ:Ԩ֔U\žTUmkŨÕ/#Ū•îžtˆ EîÎö%eUWšš H;{Ą¯K ĸ@$‰ =îučĄņ"=4*6šÉėģßõ!Ū_DDD�ķ€ˆˆ´˜DD0ˆˆH‹y@DD�ķ€ˆˆ´˜DD0ˆˆH‹y@DD�ķ€ˆˆ´˜DD0ˆˆH‹y@DD�ķ€ˆˆ´˜DDčå÷�<÷-vūe“^#"ĸˆcŅ8l}¤§Ú×C¸n…ËŨo†ˆˆnGŲ„ŸÄOšČZŪ#íwwŧh҆QīÉŽÂĸ=Ōrwķā?§õRéLJvŧŨĘM3ę5úĒ„ˆˆtŌC;Ūn偀w' ÜŖĀ< ""-æĖ""ŌbĀ< ""-æĖ""ŌbĀ< ""-æúú=ĸ~Čk ū6XÃJąeÕHž€˙ũ'ĒÛÍf8Õs‘˙<ÛMwƒŸæÂĒėDО7 'ęĖˆÄxoVÛj$įá§L(pˇÃC÷cæDŠÆŧ4¨nÛĀp| ĢR<Ä0 ? æ @/-Âj;äg`f4ŌÛė͇;aī˜¯”˜ŸŲéâĻNø~Ģ1ķ œ`П¯Đ@c:kíP‡öˇ �åyxh7˛€yĶāĶÉąĄ-ž_÷:Ėû?0 čĪ„y@ÍĖą0öCAGĒōđūyĀĪŲu𨥞_ß:<ņž­ëáB‰úæ 4Ø øž¨Ķ~ē��Ūļ7O7”âĐ"< Áŧ/°¯úÖåˆ8^? E�+P‡üÎP°ŦJĀĘŧũTkė•ã! ”EČâ™ũ)ņü€&ņÎī4x?°Ã÷ĄŪ#Eõk<? Eƒ|%0ޤtrŠ`e1_ŅnbõyøîF阮`¯'•âÄ.îI%`x~@ÍOE€3:á�HnĄ,Ų�Øˇ˙[ ß˙ÁWcz˛Jĸū‡y@Í÷i¨fÁĢŖŗ_C[Ŧv*đUq'Ëk°ü|߀yĄxī–kÎDķ€ššLüí<ÄÖø~|†´{h¸ž îĀÖh¤ßωjĖۍ,!V?†šCn3Ņ€Âë4�íÜĢĮđž+’_ÆŠ<œĒ†p´ÃÖĢņÕ>,Íëĸ…š<ĖüɏāĢEČ˙w§—"ˆžĐ@¤Ä_Āũ|•3;<!Įscá-ĀĄŸņĀGxō´NmdŸÄŧß ļÆĄ9pčáz‰úžЀ•‰';˙’ĸVĒĶ0ę$!~ˆ‚A”~‹"ęŋx~@DD�ķ€ˆˆ´˜DD0ˆˆH‹y@DD�ķ€ˆˆ´˜DD0ˆˆH‹y@DD�ķ€ˆˆ´˜DD0ˆˆHĢģy`,ÔKDD¤ĢÚņv7 \ĶK%DD¤ƒkxtt4ÜŨ<Ø9Ç0ˆˆzÅ5X›`įėi[×Ę^ÅėQtŋ%""ę”Ā�ŗGáâß{Ŧ}Ŋ´ōŸzi†ˆˆú ī/"""€y@DDZĖ""˜DD¤Å< ""€y@DDZĖ""˜DD¤Å< ""€y@DDZĖ""˜DD¤Å< ""€y@DDZĖ""˜DD¤Å< ""€y@DDZĖ""˜DD¤Å< ""€y@DDZĖ""˜DD¤Å< ""€y@DDZĖ""�€ėėėž.ƒˆˆz‰ĄĄa‡Ķ�zˇ""ę3eeeNįxĖ""ŌbĀ< ""-æĖ""ŌbĀ< ""-æĖ""Ōb ũūĸnúšpĐc„•õškŨol€û=.¨¯K ;ö𩾮€ú“AæBĶOî[3ĘÜĩ¯kŅŗîžü\8hę×ĸ˛Ģ "úShžÖ\Õx%üÄĒėÚŧžŽEĪē› ĸ„ "ú31� ŽĄųĨ_ŪéëJôŦģypŠÎ@/uũąT(/÷u zÖŨ<āÉũ)4_kîëôŒ÷Ā< ""-æĖ""ŌbĀ< ""-æzųūĸģ6Ü ī?ˆ‡Ŧ`ee˛Ę°õü쏋Ĩ>~†G\¯”Øg„C><8|DDáãûÔ}]Ę˙}¯Í˜ƒz*ÛSģQŧįé⯋ô1ÛpŖ…O  1U…ĒŸVũķ°ĒNUļ! ö;¸ÉVÔv’ĒĄ0ŊėāĻß§wkxžųČF¯ķO‡fŪū9uKŸåŠ ’Ąú7<w ų ˆÍ1/�[Ÿ†ŲŋņAY_5` Ũ`;z_ÁÛŋöu!]kSj‘b͆ú|Ŋ|ôŖ7>Xwuī–ō3WYßoēøĨŽĸâ§ę?n¯Æ­H‰ĢlųC$5{`ņč—vJDŗŽ믗ŨJېTÛÅǐ:oˆ’î HųŧĘũ֟|öY<0ŽĨpŒBöīJ¤äAü,|í�恞 EŖ˙ ß+ŌļÔ+Ēøxũ´j4ôá! o--;|�đs}ŠČ~Ã,—ƒUôĶà EY—2nėú/ũš¤ŧįčũsZ|ĢęŽŊ’^ØåĒēK īē¸û,Ė� n;IƒĨ˙lķ§�<ˆĩcá.Au)Ū?‚OÚ_MōĮÖ�¸ _Œįöá‡ë'ū˙ã÷ī‡ģĘ+8”€ŋD �`Ëßក¯ŦąÖVœ:‰™ x"s‚™‡Žá‰“PŨļ…~D4ÜøųeCƒŧ„¸VU¤üî‹ĘūĒ� 7×âųéÆŽVƒQ§>˙kí?ˇT˙“-û,ĮXīüpá•Įžš|Ą}S‹— }ØKhæķ?Woú¤æˇĢ€§4vŊhįÚĢ÷-:ލ2øŠ:sŖ…Ë,æ{ -D×.æ\ũú“Ǎܖę0ôÍįLÆŲÔ6ėŲÚ~�ĻŗĨÜĨąE;WV[?mä,‚Ļ3q˙ØÚpŲŧ}Š›mn34ÔĶlõĶ&㝅QSUNũž-•_gÜúu­+@QŨĻ 'Ž´ÎÖ|>Sƒ ƒ,�ŊįÁ-W˛J0QzUÚŗs?e+\ŊdF"Õ՜Ī}ō^NŽB;Ģx Ÿįæŗ2l,JĖÜōĩxŲ6۟įų2Ģũx‘tØÜW=§û™[™ ŽäƝ{3ļlŋ„šî[; ĀúL§Âmß?ŗŠēŗ^<ß|dŊ,síÖËV +Z{č‡nœ"šŋųČFYæĘ¯īyz…Ŋŗ•Uqkۚ¤Dįeģŋ9cŗ×īײÄĶwÎ|ÉĢx­<éįÆë ēg>=˙|yģ5"ö|fđ†×ė��/IDATÂĶķ†9ۈDĩ 9IįˇŦÍĖhYƒ†OŊėy'/™j¯üŧ=퓯/]íbēײįíŊdFP5Ĩæ}ņ^Ư`ÂúĐõNgÂ[ģžčõĩYĔ^ėôišß˛õģ!üôŲõäŸ2Ą´ÆĄ9xÄ ō¤iØ;{ŖāũV—âũ…xIÚō™VKđÄŋáŊųVØÔŌ‚Ī4|˙ ’Ā}#:ßiØ;ļeĨŪ÷Ãû4Ü߅cÜīGō"(Áú]x˙Œ™Ķ0oH-´Zû Ž­ģųßÚ{`uėŖÕŦÕŋĩŦ襧/žŸ>8tŊõrw�C&YnßzqNxÁã¯]>ogūÉJÉĐ+ŠUĪUGSÜģųžtĨŨîO(^žaøÃ¨{kYņc¯]>/úÉzS�×T"Ah¸ņˇ^|jëÕ:Ąáō #[)7ŊVôČĶĨۊÄË7Ÿ>�`.yw­šCnÕŌđ§Ö×ZĖ5`Hkã/…k*‘hŅŌ{Îl.Í{dcƒučđ&ā6ĨšKŪ\oáXtåĨĨ<]ē)ĮpņÚa÷Üŧn:^@é¯5{O4ļ ĢÁã&UEz{UnCtu•Jí~Wč5vÃļŅVéi¯M‹yzə"¯q?rjY™sī_쨤hĶąĨĶŽm99léZ‡Ą†MWU75'œôĻ_¸SõÖEG§|˙ÚGÕv/LZ9S|å`âs]AmÁģū^úŦú6Ŋ4ǚE6÷†û]Úē(~ëíĮËTÍ"wĨSĒ6Īú6Ô;fcę=ĄyMŨŽėœ+ëdÃÜ%Úgj1ÁŊár­ųms//QŲÉKíÃ�æ3}ÖŋlRôŲ‰ĨSbž^’–ã>zíz{íK)œ8nÃGN8’˛lÖ÷¯mĒ–­˜´~Ąä6Ķīņ›¸á3'QRʲi‡ž^ôkēôŪõÛÆē‹Đ…NžæMëPįx�éŗ<(8‰‡~�<pč(ßDÚBŧ7­§+bŦ‹ä8|pŲe؍ՙ°2oyĐL‰įŽ Ĩéđū˜YÀk'âÔ1,=‚j¤¤áš4<�¯Ö&KąöT@ų$ČÃ'e�‰|!|-ģnAkí1ŧuŦŨ”ˇŽamû)=hčũfVĘm/'įj.—7žØZU(z8ÔHX; %—˙ÜXZŽ)ČĒûāĩ’Ĩ_Ô×áZŨÕk* ņjSŨÕvß?8ä퇇Ē>ÛXœĢžĄxãå僭[v˛BÅO_˙ŦēPÔ<äūĄĄöĒmë+ãŗ4ĨåĒßT|WgŧhēĀĐûMÆŖ~Û'Š3åMĨšõošĒ¸ūfŧÍR��ƒķßUÅ]pųgÅo—ģ: ĐyЍģúūŌ—>Qœ)Ō”Šâ#jĪßįqķĘéd Üd°īsÖĢŊÔ{ˇÔ–v÷ÕčŠPjøŪ¸,kãi/)īū^ûĖ3ëß*Ėēxĩ<=ī“ĩyuūĶŨÜs˙ĖaH<ŗy_UŅEEÆ×?‘>hHMŪã,]NÍû9ëjųEEV띝Í;öER#ÕW›€æĢ•ęĢˇé�`ĶøĶÚ˟ĶĢ‹ˇ4oXũŨ{…E�”?ŧtŲÄĖŲæveĢĶK2aáå5�ŧŦ=* ãNō˜(�‰ÔS֘‘xķŽĩîĮ_—N;ņÉĄĒĸ‹W‹Ō #ö]"ˇö��ዝ†ĻglÜ^–›WqčäÆMÅåŌ{îétēøū…öV§ÎlÜT–{QYžU˛õ­ß eNĄū:ėÕ:|šíÖa×m @}y҉Dxü/'<䁇œ°z6V᚝øw` o!öļž]5ød˙ˊĐz`W]XC @ o!öļSJž�ĨžCūŋŊûiōÎã�ūeũ!mT” ¨Ø^Ũļ9ĸŅčáí‡1Ā’ŗšÛi2æ ‘adâM–Laõ4.’8ĖÍåîô5š;<“qæ€áĘL¨8@aHÁBą}*íSÚŪ-ŋ,-Š :?¯ôžĪķũ|ŋĪ—öų<ũ>? # �7-c3?ŖpēqsRđa@"œ9Â8˙Ūīz`Ž“€x#6ŗ×&ŽšÜ?tzˇ%JâŪ­æ{ˇ ŠĪËy§Ē?´:oXškÖ0‘¤cÄClûØ:œéîžũaÛÛF-ęą™mŋ˛ÅíēŌá}G'Q‹× 1Äļ;ÆõŒ´ŗĘø™j €įfĮølĪ΁‘†=Ãáöē”ōŧYrœPÎđ†'ú‚g‘(Ģ$öŖĨܗ…ũ§LOäéŧĘėCö¤ŋšŪęŧīOũ[ĢĐk暁‰a1öu¸–éô˜¤ēD˜O[Į:ė5^ä2ƒžÁÖ|Ųa0¤•‹¯W_hmļšƒūËaZq@īpÛíŨˇØ:ƓĮqHÅá6ˇ Ū~Uúá¸5Ņ0ļ\hTnz+Jú•Ķktށ Æû[pÛ=ĘL}Î~uÜ"#0bp0� ÔiC5#`:ųŨ~�ˆSnŽœø,ttē~“¨—âRpĸ{Í|îÍg>�€QoĀx_� ãԟđסqŽ ũBH�ķčô•œĶ–G@|˜ƒ§GJ€ûŽGĀų(0‘æ4�Ëøā<“Ūî>Žõ‚áɁ[­ƒīípoÛ"7ä/(yoí_ ƒO‹‘ņĀyBųØą2šČĩ‘S–wķ倜áO‰āöؠ֐ŋÛ¸ÁūŽjՕ ŽÖRV9r“õA)/¯T¯ÆÍ4K˛b>Ōqeš}úƒk?ļęŧ†ę^�`Vė>¨1•^ž4-#’‰ĄŲ–^ģmJnš1b¸-8n›“Ep>đļ–]ÜŅĨےą"ß°RærĪuĮ…iÅ �..äčģ<Ķ•† h769˛õj%œ)zIĮŲÁ~Ŗe¨ ZĮt ­ŽbŒ×Œ÷ˇÄ×­¯0xjKš+m,eÆęĘŧĀ"™œ%¸ĄĘE28ˤ8/kÃĖ8aj3Ÿ{ķ–ÄDŽĸŌžũî/(iô@˙œ€Fē~°8ŋũew&ŽÂüāķ€aŽ3Ÿõ‚Č' xrŽ@†2Ų}f;ū’Š!WŊoŸ¯oëāĩĄXÖ75TčÕĖļÅÃ7'rœ§Ps>0‚‰Ÿˆ/—Í\k’6Ęâ‡ėīUØÛĸäˁiC…ar ˙Võ'— �xú;‡;ũ_2M-•ŋ{Ģ䓕ĩß72¸›uÁ|örņqÛ¤*^Îâ�¤œ ĸ(ŅxŠH!™nž€Ķtúęg§¯B.OŲ°"÷“´}.ĮÖ˛É/…ievÂėŧ<Ān‰ÖE9SĩŽVŖ]fRSĨVŊ¤ã_AM*7žĨ:{ąâ\ ÃJ8Æ[aĸ‚÷æĄËmSË‘L‡eēd'Ļ[oĀ< R$žŨƒŋ~ą6`a`ÁnŦŠ›X´+˙ :ĩ;…W܈—āēeėÅÂé|˜Ģƒ=Âhoã\IrĖxhe"ßŪáęoĄö…UqūYī­VûĄĒ{.•xÉØfđĮéĻqj(mäąÃŅoĮ¯ærŠD2Ö}Ģ'đä|ö~/€ž7TLŌøAėԈdfæZ3 înjáÁ::ö­ˆˇ|4~šÕf`ôBq÷ģ'î?Cû临6ÉvDíæmFŖGĩˆaģė=—“sqũv�öŽ^h´ŠąŪōõÔĶč2RíMœŨŪzŽĨĒŪŖŌMÔšŠ•Ų ĐØ×Š¨ÔŒÅKíƒÆ.�Ö&“DŋúĨÔD‡ą18‰1ŦŊcģl&rŨ ´bōhôę‰7äûëHˆ Ynm›ZŽÄčD1×ar�ā\($㓨‘\ƒ=Į™cž6}EĐūßmƖč5XģĨDeÎũ×8QöV­GŠz ļmFÉb\ oĻŊŠUQĒÃV,ÆąlüøíŪžd„ĮK'Y•1ņJaT�Û`­6KļFފĒb˜ĩ9ŅYי3NX˛éÅō}1†TfĄR¸01ŗŗŨ°^‚øÔˆåqÂÉ ļÁzŅ,Ų^¨ZĢe–§Čwå+“áētėĖ6 W‰ķvĢÖ& UJQrzTeÕâ}é�} öŸđÂö|ÅĘ8á­lWö bvæZá„čj{įZ*7¤‰TJŅʢ"ëŽĢËDSj‡�QúŽč]éSë<Yˇģ*ŋ´¨ ŠŲū3Žp7œėŌ§ė.X”+Q&¨Ķ÷ޝ:›–ĀŅ|Ί5ÉšoFÆDÉSļĻũYīîūiųĻŊköHJÕJ•ąŌÄÕ+2_Ųhu�ŦÍ …"uud\,Bˇ2;aē pMFIÚVŦ­¯�ÜĻFĮŌ­I:û@ƒ)8Ôp›Éŗ4sEZ‚D™y UcāÄR­^$‚ģáxˇųåä‚EZmdJFj~žFK¸ō.ķËɅīkâb%1ÚE9û—iÚ~>ĶčĐcæĻëE�¤Ú¤œ ɃLPNÚ|—<5æmž¨î<Öõ čUTū‘"8G`ęAQÕÄMuįapĸd3Šä0÷Ąä$[ÂFęÎ#É˛ÍøPŒāJŪ¨ÁCM <z„Į)vcô‘“ îgö]t8íėãv¨÷SĢášŨqīøîÁ“>�W÷•åFm+\T âģX÷-Ŗ}Oąõ�ĮŊSÕܧYą'ÖŨÍ7XZÆãšeÅũöeQ…RŅö&kū‘ģĶ\‚év~ąĶĖæ¨‹*¨e°wģęŽõŧč€~۞ũĸOsT‡ĢÔ\ˇķLĨĨ:GŗV:S­0&wuâä6†jī”ébōvŋôFúv¨ŦܥΗb+ŅûîÄ­¤ž# X™&ßČØŋ¸8‡Oé9uĩzKúĻŊ+jŗÚ:wSķÎ<.§ ĩbk„ \ˇą÷Ø--�Đs˛ž<áõėŌ7žÁHÛĨkĮ:KŽĒīĮ™Мû‰ŽđÔJ•ØÃöڌįŠpÔ_¯î|-ëxúēSu†ĪBļ2;aē ¸[ë­Ē5QÆúĀ ũMƒėĮ˘š_:ωäŦ-iÖHŲ]ŗ w,ßl.ŋ$ÉÕŽ1|Ŋ†˙œhj*.ārōR+ļGāŽĩéhŨ‘“v�îåŽĻ–yž¯û86GGũĩŨĨm�XkZŋš–ũuF–‹3ģ*vÅ]ČĖtŌxęĖ~ŧžQŧŽŽ.F3ëúŸ?Ô˙ķîįڍ3¯Dž2› æĒ%žT‡=0§&}síéR”¯ŠĢ›ũ<yÂŽfœŸī.ˆŲlŽ.ާĘyēÄlYúō…ę¸Xiâ+Kķ?^Č]îjĻd@æĖ|_oJ č?]_"OÍ.X[õ"ÃŲm—š Kģg}I!ō!O gķWõÍ_Íw/Čs‹æ‹!„�”!„øQ> „P> „âGų€B@ų€BˆßŖæƒgäWy !äņōņuûŋGÍŅ2žČ¯ŠBČSíň ĮK=ã5ü#ËÍûĩåHB Ã€Ū‘×˙2ß=yĖ5¤ŊäũīV.FæŌ™BČs€žJŧā›uIŠ„ųîËcö¨Ī7%„ōlĄį›B ‡ō!„€ō!„?Ę„B�Ę„Bü(B(BņŖ|@!�øBĄĐëõÎw7!„˝×+ §]ėH$N§sŽ;D!d^ŒŒŒDDDL숝RЇÃáđųč9Ĩ„ōĢåõzY–Q*•ĶŽĀķų|gppĐåry<ž9î!„š!ÄbąZ­ĶŽĀŖ¯„B@×BņŖ|@! |@!Äī˙õd´vš����IENDŽB`‚�����������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/reset-credentials-page.png������������������������������������������0000664�0000000�0000000�00000106034�14156463140�0023713�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��¤��Ö���ī¨Ķi���sBITÛáOā���tEXtSoftware�mate-screenshotȖđJ�� �IDATxœėŨ\“õū?ū§ \*0Ür„m¤ 37ķČ8~”yÔ¤BÁŦ :‰žSá9ŊĶŽ%ÖÛÄSĮ§o‡~Jį¨PYhv„ÔˇÃ2•›•ŽJ˜ nļ ę�•küüū1~ œĸķqŋyķ´k×õz=¯×uq|píĩëŌŌŌB�����ŽČíėŲŗ6›­ŠŠi°+���� ĄC‡2 ãëë{Įw8.RYY9lذ.K����n!ÍÍÍuuuuuuūūūŽÉvH]]Ũ –����ā,—.]"ĸŅŖGˇ/Á]����pžžž]Žä"ė���€‹¸ãŽ;;-ŦR�����Ž7„]����pYģ����ā˛v���Āe!ė���€ËBØ����—…° ����. a����\–Û€ˇrĮP'Ö����`×ŌÜäŦĻpe����\Â.����¸,„]����pYģ����ā˛v���Āe!ė���€ËBØ����—…° ����.kā•����€›JŲéō?ΞxšŽššy°kĄ;î¸cÄ0ĪĮI8˜e bß����ā,e§ËßŲŧĨæâĨ›!éQsssMíÅw6o);]>ˆe ė���¸‚Ŧ˛ģ„+ B--ƒ[Â.���€+¨Ŋti°Kčΐ!—ęęą„]����¸ŽwZÂ.����¸,„]����pYģ����ā˛v���Āe!ė���€ËBØ����—u[‡]õķÄa ˛ĖˇFŗWØŋ|B¨xAæuī���\‹§ŸhœÛí[n#RŲŊ÷ŒuģÁ%]?Žŗ'8ĶūĖôjŪ`­æl Ëđ„2š">)%VÆėÚ����™§8æ/ą‚š3Æĸī=Yqą‘Xßq!o7j¨5ŸüĨJ*v#jė2ÃåÂ.§ĪXüÔF]51>a •œĪ˛ÄY ZmnĻ.7;+ųƒŦŠî‘""R?;!EÍuŧfXžX•˜˛"9Z0xEõN›:ų1}r^îŅUÖ[uoRŅuîá )Ģ;œŲ Õi &‹ĩš#†Į†)”Ņ2Á5‘Fuz–Iž’ŦāYKr˛s ¨ūĀ×íîX~sq"†í4ϜõYz[ˇ› cW$ĘXCöú‡XƇ/–+ŖĸėŠU›™žGąË“dÁ¸;=ËĀ‹]–č°Ė”ŗ>Ë$KL™ÕķÉĶų@8™YžaŠHž‰ āVäîí:-6tZĖoEĨ=šŸ>Ë9z˛5ŒÜĘœëæ ģ'ŗž÷đœ2ūũ§­jœŊøą:Ž?o]ÆēąC†ā ģ—/^ž—ņĖōˆoÍē ˙ųŋ™đcĶ7&ø‘ÍjŌd­ß˜Ŧ5eĻ)šŽÚũ¨JģDˇ)ÚŲ s†œÍ9zŽ'V(c…<†ŗš :]ÎĮņɈû™w9}f†I™+"D¨b…>­Û›õšĸjALbŦXĀrŧŽå)ˇŖ į˛r2wč9žLĄTy gĩŠt9×ĮÄ')ÄW&$FØ×ŦÖįæ|T Jžũ5Ķūk/"~^[ĩU´ųyY™Õ‰ËTũ.–'ōIg0q˛öC`6YĒÉÆŒ$“´­f6˜lŒP|ƒMēn‡��€ˆ¨ĄáBų/%Úī•™Ũ§$¯‰[)‹9e(ųQ¤Č0ØÅ9Սģ¯ŋö÷æ6MMMöŋ˙8žåjöđø*”ÍMÍ-§~XãÜū i:މXĩ)ĄË?KŦøˇ>°ĨfåŊ]Ģ>{Ãŋ˛Ô:S5GŒP1+1eu‚Œ%Ō§E-Čâ­p¸(hˈ‰Ųhbįeü´ą=í&"EŖH?ßúēī[}Å{ĮéĶŌTÍÎDĮ 7ÖŨÉQË5âÕų;“DÖüõŠŗ5Æj"žXĩ,uĩĪĀFéęxbąBŅVŗB9KLQ ˛ŗö§*fŨŋ´EÜÕ×ę7NŸ“Ŗį„ąKÚæŌE™L”‘ŖVËÄũ 4“š­HV –ĩŸ56Ž#™XÄã‘Ãōpč™Ŧúœ=…%,I;ŽƒL™•—ŗ_¸d–€'ļžc61D<žP$ęÚ #ā‹:.ä Å|JĪĐëJ”"IOQPčŖ1Ė$níÄj2YųBĄÕb2‘DØąŒĒnôUÕët�āļ'˜0Eā~6÷•WĖĩmķFŲ˙ĮŨ+hüä ņ“įSC͙’Q”wā렕é<7:ė655Ґ!CÚ˙niųyäŊŋ#bģxŽŠÁÖŌ2°(sš\ĩØy)IŨ_€'¤­ëykëūåķžÍĩ cV$ËųdŅåfd¯~L{üƒŠ ™RÎfŠķõÜĄũßRĢNcf†ĶiJ(ēõōVŖĩQDŒ‚%ĩ}Aߡēįņ§Ųõ+5Ų9ÆD‡Ī⭚l­•'Å ˆHŋ6!9Ķä#_‘ äÛLęĖ”Å>×-ív!“ËØ,“ÉB$´×ĩyՆ ÉžēSŌŌė C›:yą)å-•fÕ­8íđŋ•úÍi2Õ‹XžX•¸buÛ'ŧfõÚUé9:S5‘P™°"u…ũĶY}ęäĮL)&šÖoĖ)2sē%¤ŊĩZißČĒÍHŨ•o°Ø8ÆGŦJ^ļäj3RŦų–¯ÍԚˆ*“WĮvzĢÛÖŦŲM^­#ĸÅâ\áâÜ+øũī´‡RôZƒM¨Ru™5ΓÅ&ķ‰×žĸ8“6G­3Z­D<XŠRĩ~ļoT§g[ä aĻi”hÔ)Ã"ĘJĶķ•É*n‡ũĶsVŸš!×DDęˇÖĒųĒ„Ļ1X ųjM‘Ņj%–/–Ģf)D,gÔæht&s5gcx|ą\ĨRˆXâښ˛wąD)芰ū2iM$ŒwV¤REčŗô:ÃŦXņ�šųŒÎZÍõģ*‘PČęL3‰DDœŲda…ą26Gk2“ĐžĖd°’@ΡˇŨëPpVÃūM‘ŲB,_¨PÅ*Zöģg"2ælČĒVĻ$ļM~0åŦĪ2G?ģDfętbÛēÎVkŒ"ž,6a–%"ŗ:=Ķ"O’[՚ãf+GlĮ[��Ũá~ūį7zú‰î q3:yŪáŊ†Ō‚OOÆMōöŸđĀ”ßür´Ží­91ĶgĮLīŌÖžŧƒ{ķۘēėFߍĄŠŠ‰’Ž]SSSsSg-o¸\ŨÜPßŌÜ4 ļ :=GŒ\5 OÛĩÖæZÅËwxkuRėŦYąIĢ3r7*ɔ•–i&’Ģ”Œ­HŖm_;_Or•ĘĮĒ×ĩŨ ĄDŖĢ&q´ã„Ŋžo— bčøŽl‡Ī Ŧšė"ŖLTņˆ¸œôl ?ø(-éÕŦ„äM_l›Žw?ŗŅéŒ&ĮđųöĪ’õk$§›diĒ5yY+„EkãSrė{Ã˛,gĘJ×ČWgí\­ĸũŠ‹Ķ âÕ;ō ōs?LŌ?Ÿc%"Nķü‚”lNšq‡Z—ˇ#MaÉ\œ´VO­Mpúôĩjų:õ៊ 6ĘM™ĪŽÍˇFÎōÅ Âåæækō˛ŌÄE鋗īīũ˛—5įŲg?0ČŌ>,ĐäžkŲŧQmëxĢûÖx ä.3ī-mŅ+Äč´œé¸…øbņ•Ÿ+°ŧލd۟™Ĩą UIÉ)K•>ufŽŪū+5Kdŗę5&Ą*1ņūy&Įđ‰‘Å/_‘¤ė¸ÚČʒV$Ę|ˆ/_ŧbE˛ÂÛąĶū˜"Š˜—”œœ X4;˛ĩV"âô9ŲyVeâ’gSRU|‹&;ĮĀą˛„N]ô\X?YM& dŨÍÚ‰…ŦÍdØÍEŦVĢáą x"ĄŠ6™Z÷Įl0‘P(ä }Ŧ&Kë2“Élã‹íכ{ k‘ZGōyÉKĮËX“:;§„#ęyœ{ŅõgPk­ķ’—,ŽæYtŲjcÛĘ6ŗNmÅ&§ŧ°"%VXíø�@ę긆ēķîu\ųŦ+*,ĐüîdC7ëīÍ;¸¯sŽŊ%’. JØr…ÆÆúæĻ†ĻÆúæÆúæF[KķĀŽėš­V"–7 iŠÚ9)•|k’ÍS°dدą̈–§Ķ´f2­Fk)U22´}æm-Ō™ˆ/W:^TîĪVŗâc}ȔŗCßļą5‡žcTą*Öž)GüčxYG㊤Ø1ƒ3j6¯J7ø¨âgąDÄí+Û–ōVÚ,™@ ÅnZëŖIĪlĪčŸØĩKfÉÄ"Ö¤?nķQ$Ė ‘LĩîÃŽPōˆŦûŗÔ–ˆ›VDK<xÖę´$Ą);SŨŪŖ,1m–ˆˆˆ7+^îc;n0ņTišęŦMą2‘@ ’ÍZ‘(æ4j-õÂĒÎŌ‘rÅēX™€'¨V¯ŽáĩĮ‹ž[cy,c˙v ö8ŒVŽ^¯“ÅšM‘…¯LP‰<O {@Á×ęÛĶ_5+V)$eYb‰ˆeģĻ;–"âu]ÎéĩEœ@Ģ x‘,6V)öąY9"VŦZōlÂ2Įã ÄŅ|›É`&"rėâj…õkŦÄøt˙#ĘãąÄU÷?CsV“6Gcņw›Ą¯Ž Tm2qDöŦË YPȚ öYF“Éæ#ōčęĮČæ#›7K"āņĸčyJžÍTd⨡qî­ŽŽG™cĒÖÆJ1c3[:‹/SŲ“b%2ĄO§ˇ��ē5RōûHßē nĖåí7]8˙sáÉĖģ­EeŨĨŨÎy÷VIētã§1466Ú.ĩ]Ų%ĸĻÆú–ÆÆæ†úĻÆzj2аk˙X–:_î´f/˜ŧēČq‰|CŅGą]ūU4L6"CFŦ<ãŠfMĮ-DbšRLõz3ÉTĸŅUķå ™ŒÛūĨ֓JAœ6ß@>ą*I§MyũØJ‘” ܑ‘“ŠMŨ¤ "Ģ&[oķQ%Ė""2[L6"aD§t+– 2 h¤ŽÂ°AļĄã%#ŒIû0Í>_× ŅÛøą Qû› ĨŒŲĄ×[ÉūO-ŋ}ž¨8ZÉĪČ~öQ.9IĨP(Å<ąũ|ƒNoãĮ:Ė&ËeL†^o$ûˇ‹xŽČÂōXâŦöˆĘ˛ÍÚĩ9E“•ã8؈„Ŋ^3hMäã$Šöƒãm-÷­ĩ~wÚŖ>Ä0ĢÉÂųˆ…X(0:“…#ûĩ_ž°×°Ü3ŗŲÂąb~{ Eė­eąVŊZ}Ülļڈã8âˆxWėāÕ ëˇk˙DÂĸ~kmĮīGÄōÃbTËēD<ą¯ÖšL$[M&Ž'˛DB‘€r ’ívÅĸū#ŸĪp‹•Ä‚žķÕ°|~{ã ÃqíM°>G—aČņ-�€î](ų琯LŠŠ5üܞu-úÃF7A„'[d0“øŽnˇk¸ˇJŌĨA™ŗ{eØmhŦonŦoj°ĩ4r|ŋŗ'[v_7>Gd1[­D˙ąÂ؄ymÅĸËŅYē۔ã8"'Ĩ/Wvũ“å …D$’Ëų”­-â’\‘ÎÄʗËH@2~ĩFSB ‰^­ĩ1ŅĘŽ3(úŗ•8!^œąQ­&…ŠŦęė"?!Ņūg刘+Žã1ĻĢ&¤ŋ•hÖ,#ā‹xũZ9Ž,YņĄY7¨ns–×^”"5wgXúæËw¤q _›ē.m–ˆŦG,ßqē1˲ÄŲĒÛ_u[“>-ūąl66-íCš˜Į’yĮâyéŊī…•8bųcİí/úØZ˙;í ëãÃÚĒÍf÷ü%'Žã¨Z—ąV×i)Ÿk›ˆ:°é[›&ļģËŠfuf–ž"TĒ$Ą€eÉĒĪÎĐôŋ°~ā x¤Ģîü#ÚÆjåˆõé[žįËãceö“ˆeyė�'0´W%ō4Ŗ‘„ÕĮĢYû5\Š”o0ßb˛°ĸÖé"W=F<ĮBXļ5tömœ¯Ļ—ŧļũ€Û•Ÿ$°ōģ^‘ŋ[u‚ˆ~-üž&Ũëg)-¯Ģ5Zč.~ÛŨB1×n¯ėÚ˙nh¨ojllǎy ŋ0ŠwÎ}hŨ՚é–D)gŗr‹rĩ\lĮmXEŌēöĒ~^­ËínSGDŒ0"ēĮûXʔ ŸŦ|ĩž”Ö|‘¨ "™\Îfë‹ŦDšĸjFĄT^ĶVĸøDyzĒ:g?§R¨sõœ0ŠmÚ˲D6Žsēā8ÛušŗË ÅbI÷s$x,K|Uú‡)Žß"bY~ˇŖÆ“%Ŧ~;a5qF­:cmjĘcėŽÂTËgŠvXÍĘqÄã÷ūm;}vŽÉ'vĮĻØÖ1[Ģ{]ŸėiŠŗ8 Q5×öĸ­  Ķž°Ąå æhA—Ąâ Z=#VˆxIJ,ųˆ’”˙߅Ŋöåą,K\õ•×úĖzƒÅG–ü@ûuv[ˇ×X+ōIk0XW܍Öh°p>Â^~č„ĮœwAXČčLŗÉl&akŽeųBÎd˛šLf†ßö}¯Ģ EÛį­¯8ûnĮ�āFrLSUôËÅF:ú5ҍiDî‚ŲO%ēōrŸ)ú`ĮąķQ=…Ũ[΍žŗÛv54p͍ļĻ›õüPŖŅ×Vī>°Æ• ą|â47ô{Z%O(ô!2h‹ē,wü00Z%gĒ‹4%:ĩÖ&Šļ&/WD>_g,Ō™HŦŠîæōJļâÅ&)N“›oPgëmâøøļIž!˛:MZ0 Æ]đÄJ9Smąņ„"QëËđēû4ÛŦWįí˙ɊąëV¨˜jƒ‰#™\ÆXô> :ŊˇŨøŠVŽ#°=rōLWû4\(SĩŠãk@zŽíŋûØÚ�:íOĻ3mn~į™šV}NŽZWd刈'äŗ\5ĮōÚą]/OČgĢ-ĻöiœfmffŽŪJ\į+žfƒÁBtesfa<šBLĻ|u—īˇqFĩēˆã+”ĸ4zíøbSm*2ZH(jû?wPȚMZƒ…xâļ{\m(8‹Ĩ}üŦ‹åņyÔû8ˇ_ūĩod6ß ¯ĀmÎĶ‹;ųKUû'énnnDîÃFyšyMZ8/ÂsđŠsļÁ ģäp+†!C†Ô×ۚšl \ÚJ7āœ]"ŊÔy>dĘL^°^ĶõëV}öķÕ1Ũū#­˜§ō!›&cŗąc—ŋ<fBÄâŨm˙,+T2ÖŦĪČŅUûDČė>yr™ĀĻUgäHŦęūžũ؊• bmųéŠzŠHš'ęhD.cɤîøúqęĖŨŨÎȸžØY‰ą>úĩĪlØ_bļZMúŨŠņ1Ēgw[¯\͐™úėcĪfæĖVŗšDŗ9SCü1KėŦÅąüĸ ĪgäÍVŗa˙ÚåŲ–°¤ä+/Šw"SČΌũ&ĢÕ¤Í|v•%BÎÚLz}/WČĒøŌlX•­5šzõĒõŽíCk<–lĮ5ƒŅ,î§ŊŒœ,6AÆŗj2Ķŗ÷k %&c‰>wFFŽÉGĢąDÄJŦY“­1˜­g5ëՙ›3rēíe‰lfƒÉlĩöĨVĸû˜49ƒŲl6ę÷įh,ÄōH °–"m‰•ãŦFmļÚĘ26ĢÅĖuî‚ú^XŸÆ!Vƚr6gääë F“ąD¯ŨŸ•‘Ĩŗ‰U ƒö´7V(Q_dá ;îŲ% Ȥ7XÛ&6PŽQĩ6Gk´rœÕŦUë,>â!Q¯ã,āķ‹Ao戈3kÕEļöÉ6ũ<Ę��ũQwÁZGä62p‚lü=˛ņŖË>ß´-įāŅSįꈈÜŊüFģPØœi ä0awȐ! \sc}ScCSŖmȡæ‡]âŠ6íÜ@Ĩæf$+ŗ„re„Į#Îj1é´z‹a놎6ŠēMģ+ÖÍĶ<›ģq^ŧ!%^%d-ē܌l…?/­ũyk<šJhÛ VÛXeûä\Qt„O†:Gcã'ČEŨ—Ô¯­” ą>šŲE&VšŅņöŧØdÕF]NÖâÖä9Īz\Ã)äŒēķÜÁAąnį[ŧĩRãŗĒ9b„2ÕęŦut“Qĸ7e­[•–ū||Zĩaų"eüÆWˆ‰ˆUŦۙΎڸ|ŪÆjbøbUĘŠK$W6Đ /!möŲĩËU;Č'B•ēn“ĘŧĒč،ÄxڑÛĶŖÎ om<ž|mÚâ"Ą"iujÂÚgÕÖĢĩö‚21A¸<3yÁîøžīeĩūß –Æ.IįĢĩzMŽÎÆ1 O –ĮĮGK:Ž0+)UĢՙZĢXž@<+Q%ëîleÅ š.G—•eĮĮvķū„ąIņûÕuĻÆJ>|‘2>VÁ#"Ų<•q‡&į-ųÅJÕbnŋe‡&+“—D;tąLÕ×Âú4âØ%‰b­NkĐähĢ9bxaXėâk}lōĩa…a>6“ÅGčø%@ĄXĀLŦØqnE/ĮˆãR%ŗj˛ŌÍÕÄō…Ēx•„%"ļˇq–Šf™˛ķŗŌĩÄō„*U„ĩ턎G9áÆ �ÜÜĮˆ&xŽæÖxš˛ŧXß~3ŗņ—īw;&Xqī„Ib‘ËLcRW7° ˛4䎡ØjņSŒ‰‰ĄÎa×ãčŋÂ7ëReYsƒmȡ3g-s^Ü?°ĒėĖÚ랞5ZƒššŗËđų2Y´j^bü,‘ãŋ§ęį'¤äŠ;ž[ÖÍÔV$8>°^9/ÃBōĩēZsęœS4œĪŧu›Ũ7Ûˇ­ÚčĶĸdYUé‡ßîĘMû×ĻmÜ­5Ųâ˛Ôuâܘ.Oh��€Û×ōÔĩ}XkĘ;ŨÎ˙fíūn�ŦhælßâÜī+ŨŧFēÕ^pÚGKĶV÷kũ>uˆČl6‹žžyŖÃî'͙3‡:‡]Ō­Ÿ0möĸæÆ†FÛeĶŠâ9/Н֒‹2g'ÆŦ2Ä~¨[7 gc���ĀíĒoaˇ›īY ;g-7•ŸŊØčÆōøcųžäéÉ:YëėÚ1ėŪ¸i Ûļn)))illüüķĪ››››ššÚ˙~8øō1­ša‡{°ÃÜ=˜>.ØXÕÖëHŧ<I���ާÆĒŸ~¨"7vŒŋh‚Đ­ąÎZ^ösÅĀîūzsģqa÷ņ'žŧa}ŨzĖڍA—ŗ9Gg&ŽK v=���p;häΞ>yv°Ģ¸ŽnôÔ {úėÔÔb„ŠäuVČŽž>����ôÂîÍaÖĻĸã›ģ����WsŖīŗ ����pà ė���€ËBØ����—…° ����בũŅ ƒa���Āx N--ƒ]ÅZZF 6ˆũ#ė���¸‚ÄGhP¯ĄvoȐÄGą„]����W<6đé%Oz >¸ĶÚ 2Äkøđ§—<<6p0˨ĢĢā–w un)�����DÔŌÜ4āmÍfŗH$j‰+ģ����ā˛v���Āe!ė���€ËBØ����—…° ����. a����\Â.����¸,„]����pYnŪōZnö ����pāĘ.����¸,„]����pYģ����ā˛v���Āe!ė���€ËBØ����—…° ����. a����\Â.����¸,„]����pYģ����ā˛ÜL&Ķ`×�����ā Ã8žŌŌŌ2XĨ�����8—Ņh‰Dí/1����\Â.����¸,„]����pYģ����ā˛v���Āe!ė���€ËBØ����—…° ����. a����\Â.����¸,„]����pYģ����ā˛v���Āe!ė���€ËBØ����—…° ����. a����\Â.����¸,„]����pYģ����ā˛v���Āe!ė���€ËBØ����—…° ����.ëÚÂŽmīãÃÃēų#¯˙ááĨ/z¨Üæ¤R]Y͞%ÁaáÁ}^sÃzÜĩ$8,<øŠūõxãël•‡Ū_" ‹{ˇt°k��€ūsʕ]ß`‰Tâø'ČĪÛVUV”÷ɚGū°h­fŖŅ‘—•Á“^:4¸E *ŒĀ�ØĘŋ\˙#› Ģģ���07g4â;ī]ĢĨ]—ÚJ Ū|eų{ē’-Ī­QܤōvFWQq¤ø6+ŨŒ€wÜæ˛š6b˜A)čæWž÷ŌŌ•˙-ŽõU.yĐļ5K[?Ø��Ā€\Į9ģLHÔ īü=Ƌ¨öˏ ŪÅ][ÉŅâAëüĻĐĶ éöČväķŊÅŪĶRŗŋØē|šß`W���vŋ æ=9FJDõĨÅŽ‹kŠ÷Ž˙ëã3§*$aáÁã•QņĪŽŨUÜ%ÛJŋ|÷oĪŽ„…‡EĘĻĮ-ú[†ē´ë āŪ›Ę˙kdđ„”/ę‰j˙ûHXxp˜b…æ*%מö¯Μǐ„EJĻÎ]ôˇŒ|‡iĮöŠŽ˛ŋØJ÷ވWJÂÂŖ^Ö÷kŋˆ*ōß>nēB.™¤œûÔk;¯\Ĩo­ĩĶŠÍņʙŊôŅҚŪG ģ9ģ5ĮvŊļ,~Ž|RdpX¸¤ĩG}īŋĻôá0•Ŋ>÷ũ˛^[ĸō퇅KžŪÛåWnŧËr§œÔëŅ œųâgģ7˙1rĐ>����§pĘ4†^ØėÄņbeŪķqĪí̍÷đ•ČUQūLíŠCš¯ļŦü*÷ĀÆ]īĖ ´oVœ˙Fą}‡|[MyÉaíįoh÷ė[ącûŸĨL› œ›ô$ŖÛųyQ­GPĖĸiŒ¯"¸ˇrKw=ģhåWUä “GGRe™^ûųÚ=_ūéÃm/D2DÄ0Dļڒ˙Ŧ|;ˇ" \äėŅ÷ũ"ĒĖ}zŅ_T‘‡äĖážTYŧ/5ūpqœW—JúŌZ[1gr˙ēę/Ž÷ĻŖ�� �IDAT<"ĶTŌÚŌŖēbŨW?VfÛũÉCú>5ų‹{ōķ3äá+UL›âËPÕŠCÚ¯ļŦüJ­}Ī?ĸē }}<LÎåŦsƒz;šĖĸ…×Ŗx���¸ŅZގįņģĨAĄ3^=Öà gw=~ˇ4(TžĸæZ—üúÉÃwKƒBŖ–|VÎuŦv`ųīĨAĄ˛‡?.oiiiiŠŪ˙gYP¨ėĄ÷J;ÖiáNnIŠ•Š˙¸ël?šji9šiF¨4čžŋģęîœÜöĐŨŌ ģzõPuÛĸę7<$•ÍÚô‹Ŋõ3âPiĐ}Q3ŨvŌĄ¸>Ãzqr¨4čî…īk_‹;ųqōäPiP¨4čŅ]ÕũÚ5{1÷Č'/øûwí%sĮū1O*–v´—¨ū,9(TôĮļmš* ēû!‡ÂZ8û€„ÎųGÛņ­ū"ŲĄÎžĻ–ę_žøäß[>É9VŨŌĢ_?^*˙y×yųŲ“–;ķÜčņhvĸIš[úĐ;'{/���n §Nr|y§1ØĘŋ\ûôëšzĸāŸTļ^o;˛-C[O^3Wnˆ č¸îį7cõKŗŊ¨^ģíĶR"ĸĒŌōz"īČ`‡kƒLȡgmßŗf†_?šę‡#Ÿd­'ßšĪŊ hŋŽé=1åšyÁūÁeGJ‰ˆZ;ĒōR­J q(ŽoÅØíú˛ŠČwî3ė¸ęɄ,Zų¤¤s%ũÚĩZ߯ž8ĨŊdFúHœ„ˆ*ŠËú1QÚwöęˇ7ŽcŨŖ—c™‰$ĸSGŠ+ģÛĻO‡‰Č;|îÂ?>ąpžÔ)Sœynôt4��Āe8eCUîsq‡<:-˛ÕV”WÔÖ‘ī´ĩo˙ĪÄÖ$QvH{†ČcâÜi]‚ˇbÆD}š2ũ‘J ņ  öĸ’*õÆ×b^˙UG ņ W´Y¨Mõ}/Z WĘ:Å&jÃ˙čēŽīde§ģOôą˜ŠcÅĩŨtAÁŅ ˙ %g¸kĶb:ß Ã/Ā׃¨ŪV[CÔ׀é'noÅVSYYk#"˛ŲĸúššnoFЗÃät×áÜčz4��Āu8%ėÖW••tss¯€É<žôd܌ŽÄQUVADõĨÛ×,Ëë˛vU9QEq‘ŖZūĘũúå_čŗū4ûS¯`Y´|šræ´h…ԏéoS}ß {ƒŪž}ȇžîc1UeUŨwá@Ôvûšk]‹iģ`ŲΧyØJŋ|3=#ˇ ¨ĸļ[ôå09Ũu87Ž@���pN ģūOî>āxŸŨšŧgg>ķUU—bŽcŌ%"›ũ kē}Ô­ú{D œķ¯Ũ’ûˇ°åķ/”ūĸėđŸŧA^A1KVŽ^دĻúĘŪ GŸîĮÅtYĢÅØl5ŨwŅš=§īÚÕ؊3=öúŅZō’Ė~rŽ\čåíÁŲ´›—oŅ÷ŧŲÕĶuāôsŖëŅ���×q]îÆāķĘ Q‡_(øjí+_Nyg†Ãe3†ņ&Ēō}$K“Ϗj+ÁĒĨW-%[eņŨáŧ=˙Ũy $oĶŌc[÷ŧ*÷îWS}Âx{UÕ(Göą†aˆęëmWtaOÁũl͉*s7ūķh-ųÎܸį9ĮËFŸ{lĄ^¨p•ÃäWŒ×>7���āVuž æˇāĨg"=¨ęšĩyŽ1.@ęKD5åũøęã'27iõ;ģ ˛‚‰*v}_3ĀĻz@DĩåeW|ËfŗŲŽ ¨ļí[1^~~DTS^ÕĩąŌâ3¯œžkWuJ{´žČ7úņ9?Đ¯8ZÖ×G‡õp˜úĄ}öE×ûėVôø�ŧun���Ā­ęēŨ!$)-EâAU_ŧōēCč ˜ĸđ'Ē?´§đŠRv(OWZi#"Ē)Ëßķéōē>ƒĀ[1cĸQ}MeMŸ›ę‡€‰‘ūDtä@aį´ĢOũÃD鄘ĩÚŪļí[1Á‘/ĸúc}įâôyŽyÎéģvUö=ŧģLÎĐfæÚBˇQŋO‡Šƛˆę+ģDÔ˛|Ãoƒsn���Ā­ę:Ūz,ü‰W &Ēúoęē‚öĖūx˛Âƒę ^_ąĢĖ!qTæŋœōÄ3OÄ­Ü[IDļÃīũmMÚʗŪ=Ú)ĢTjöĒ%ō ÷ësSDD^ Ų*ĘģŊ}–ƒ‰'EzP}ÁŠģ*ÚŦ9öūkšDŗį÷úxߊaĻĖæETĩįŸ˙)n_̿ȯ×vVzôŋĩžģęKƒ‰čLū‡˛Š3—Ž<ė'ķĸžŽ­öí0ÕÛķéļ~šÛÃâÚyKƒˆ¨dßĮUÚøŌ– ‡'n8ųÜ����w=Ÿ ÆČūōԃęÅ˙­ø|Íú¸=i †ˆ(pá†čũm_Ūʸ¨­ō)R_ÆVuL§+ŽĒ§€˙ˇaÍC~Dä÷āę”Īm*ڐ üX&đõϚƊ’Cú3õä˙ĒįĻØ/@öĨ)" ”„ûRqÕáԄ‡ŋfŧįūũ_qŨ’´aUáĸW ķV΍Ú, `jĘôG+jÉCō§7ž™ØûÎö­ī˜˙yA^¸ZW´!>æ‹Čˆ@īúĘbũŅĘ )ĶŪÜôU=ÕÛúĶZ_]}æ=ņ˙Ū|î̞ÍOĖ-ž1%€jĘôųēǐįˇūËī™úÂĒ]kž i÷/zQå¸QUåŋŋfC IŸŸ|•[íJ>)û4M_ō^|Lž""Š¯,ÖĩM[ŋ< õ•}­WŸ{nô¤üķeOgĩŨˏĻŧžˆĘļ<÷Eë¯$AOžŗiÁõúū���8Ķõ}\°ˇråꙅ:pæãWū9÷‹öģíÎŨ´G2įŖôĖĪtzõįĩõ^ž˛ûãūiɜđÖ,Ą/ŨļGúé{Ûö*ÖįëkëÉÃË7`âœÅķ—.^ā˜úĐõÂë‰å¯|Ē­(:Tã=ˇˇ‚CmŪ#ũôÍôOķę5eõžA‘s˙)%IՇGôŠ xôßÛũŌ˙ųŪžÃĮt_{ųJ#\˙Î3 jūš…Čq*oßZë›>Œ€ßÜMģčŸkĶ÷*øīĮ^Ōɏžū—˜`ÆöÜęëœ:´‡Bæ’Ēsģ}<L}üĮocÖũsKž¸āĢR/˙đ¨gļ,OŽŽxi=Q•Íf#bœ{nôÄvęXIIįŠŽ÷×ŗ•a��Ā-bHKKË`×�����āFŖQ$ĩŋŧŽsv����Â.����¸,„]����pYģ����ā˛v���Āe!ė���€ËBØ����—…° ����. a����\Â.����¸,„]����pYģ����ā˛v���Āe!ė���€ËBØ����—…° ����. a����\Â.����¸,„]����pYģ����ā˛v���Āe!ė���€ËBØ����—…° ����. a����\Â.����¸,„]����pYģ����ā˛v���Āe!ė���€Ërđ–gΜqb�����vūūūÎjjāa׉E�����\˜Æ�����. a����\Â.����¸,„]����pYģ����ā˛v���Āe!ė���€ËBØ����—…° ����. a����\Â.����¸,„]����pYģ����ā˛v���Āe!ė���€Ërėnvŋ”œ(6ŋxé˛Û1|X¸$,\ęÄ6���āJģŊųĨäDi™qÖĖß{ îÄfk/]:˜˙mK Ũ-AŪ���¸Ž0Ą7ĮJŽOžęܤKD^ÇOžzŦä¸s›���€.v{séōe§'];¯áÃ/]væÔ����¸Â.����¸,„]����pYģ����ā˛v���Āe]KØĩå>Ūņgŧ"*ūųw5NĢîF9ô˛286Ŗt°Ë�����įēæûė<øæëú‘­æ”fÛÛ?^‘ĩ'MÁ\sm7ĩĻ Ĩ…ßũtÚÚĐÔõĄŪãĻ͝Äŋųö ņģŸĄƒQ����8!ėz…+ä!­/äŅJ ũaŅGÛž|A1ĮûZ›ž™Y|SBbī&Ę6]:{á55ÔüöãžoÜPŽõŧņ���€ķįė2’HŠG}yEĨũeĨîŨ§Žš.›ˇė}]Mۊ•ÚŒeņ3eãÛ&?h+{^ŽO>÷ũ˛ļ-?].yúK[ëk}ęÔȸ­DDļ õē%s§FJÂ"eSã–n,(o]G—:5rŅւ˙<5S2~ÉÎĸʂõO͕—Lģôũ‚Ę~īįĨę:^P`ˇIˇÆđÍĄÂCúsã÷ŨÉöģa����p§A­ĸŧŧŪÃĪ׏ˆ¨rįĘ%ʂ˙ōī]š¯žØēJr,=eŞ"ĸšŊŠOŋ]*}qû˙åivo~AzęͧVíŦėiš$FæUĒĶۃ˛íhÁ1__īâ‚cöKuGjĻ(ˆjō_~üOģlҝo?đ͞ík&Wn_úÄ:Ŋˆˆa˜úōOŪÖČ_ÜžcĨĘģrįs)ī•IV˜WŊq~Õo¨évgŽâʨÛTcøæĮr–Ĩ‹ÖãßčkÃgÆã˛.���Āāšæi l5e‡ļ¯y¯ÄW•2ۈČ[ĩf×D  dˆ(0đ™Gļū÷MžæFQEIi­ot܌đ@" |õũ¸*?oĸ˛n—3LŒŒ^>|ÄöP4CG Šüæ>ž§đP)M ĄũáRīÉĢĨD•_nŲs&rųļ”DD1/Ļé īߞ™ŋ|“Š!"Ēđšŗ}éŒ@"Ēüôc)_eA¤7Q€jÕĘŖķßģöũoMēîÕegjš‰Ŋ38Ô×ķu���Ņ5_Ų-y#Ļín Ō{īrsՔ5ī§ÅØįë2ŪLU~zJÜfʧ*e“âÖëÉfĢ'" žpæŖįOŨē7ŋ¸ŌF~á‘R?ĻĮåŪ˛iá6ŊĘ4:šĩP\ĄÕ×ŲŽhôŒ<j"•éÕûOŒ h/-D.ņ¨-9Ú6•!@* ´˙W™žœ|C¤í“Šƒ¯k‡Ļƒ&˙§úĄmI÷žŲJ|5 ���`]ķ•Ũā„÷ŪXh‘Œ‡¯_ˆ_Į÷Ōlúĩ‰O|äņ`ښ•S‚ŊĒÚųôü7ío1ōÕŲۃ7g|ļmÍĮ¯õ˜ŧ`ų+Ģį3=-œ<%đŸGŽV’īá#•’ų‘#}×kõļ8_Ū61e2CD5ĩ5Äø:¤V†ņō&›­ļíĨˇGëÕØjˆņõpXŅûīqéÄ7‡ÍwJü GO!é���Ü4ŽųĘŽG@ˆT.•†KĨ!ŽI—ˆŠ?Ī-ķ]đęß(‚ũüüü¨ļÖá]?ŲŖĢŪÚuP{tßÖ´¨ÚÜį–Ŧ?ÚËrŠRî}L[Ty´āXĀä‰Ū%#ũáŌōÃG*%Jš}Ō„—7ŲĒē°ÕÔ֐ˇī•mŊ†lUõ+Ö hÎnë™ߑ÷„û¸ķžŲJ?ŗæ‡ōĢo����××õ|‚ZMŊ|Ú˛Ļ­xŸēŒ¨žˆČVŽWkĘėˇSđ‘/Xõ\´GÕą˛šž–ŅĨɤ/Øy Ä;RBDŌiá•ēü=…Ĩō)ö ËRY¸Į™#G;iQĒ+Š÷’„^QX€$„ĒJ‹Ûn‰V_ÅJ}āp‹ŨƋæŋųęāowÅ(C|¨ļŧŠkH‹����āL×3ėJeá%Ÿmû˛´˛˛T›šėåŠpšG}šūHĘ>M}zɲ­ĮĘ+Ëˋķˇfĸ€‰Rī—1Ši+ŋÚRP%!"ō–)JļlĶ{Ëg„Ûģķžņ§8˙Ŗé/ŊĢ)+¯Ŧ8–÷ڊíŌE‰SޜĄ8cžŒ4ék>Ō–•—ęs_ūgž­ŋsv‡ûxZOšÔwf<Ÿ77Vâ3´Šļü× ž^#>p����āÎŧCW~ĻŊĒ_ļņų˜OČW6į…W˙ŽĒ|íØĶ,zŒvåŧ˛õÕ×Ön^÷÷Ēz¯€āÉ ŪØü)1Ōî—y˕Ō*~˛2Ōž^ĻDzoøÄv˙LI[ŪSVm{Yŗ~e܆*ō D§l^ŊTÚŨl܀GߨTļōĩõOŨŋ– š˛håę¸×—¨ífÅņ&ūNōõ7ytķ5oū¤ßŨ=Ē_c����×Á–––ÁŽáæĩõãO<+6���p{2"‘¨ũåõœÆ�����0¨v���Āe!ė���€ËBØ����—…° ����. a����\Â.����¸,„ŨŪ 6ŦöŌĨëŅríĨKÇ ģ-���@;„ŨŪ„KÂjžuzŪ­Ŋté æÛpI˜s›���€.Žįã‚o}wKB‰č˙ōžžtų˛›>lX¸$ĖŪ8����\?x\0����¸<.����nģ����ā˛v���Āe!ė���€ËBØ����—…° ����. a����\Ö5=TĸžĄąüˇŗuœ­žĄŅY���ĀmČÃŨ͓eīãáî˧ž ŧ­ú†Æ’“&Á˜ŅN¯ ����n7õ į.T—œ4IÆ ˜->Ąüˇŗ‚1ŖĮŒæ!é���Ā5ōpwģsĖhŋŅŧōßÎ:ąŲ‡Ũ‹—ęFôvb)����p›=Ō§Žŗ9ąÁ‡ŨĻææĄwāûm����ā4înÎũ2Ō*����¸,„]����pYģ����ā˛v���Āe!ė���€ËBØ����—…° ����.ë<üŦá‡m¯mũÅá~inė(˙qĶbbbÄ#¯īÎtb×ú™îK}.Š?ȅüúŲkĮd^=įÎA.ä*N}ōęcTĘĘé~7ŽĪ†ĘöíÛ{Ôxö"yŽô7%fūôĐŅ}ŨøVX���čĢuewdäKŸ\ļôÉeKŸüķ#1nƜoų¤´áõ~ËĒųî?/}zb°Ģ¸ē_g=ÖËŪ˛õ(M{ôĪëV§,›'Ē=¸ũœ_oūķėV9Đ���ˇœvYŋ Đ Đ đņ“į?ĩč÷#­Ú’Ë7¨û[TCųiŗ3!rŊÜø:{čąÁXøËŀéķχøy{ ?'önöė/?•ßĐÚāV9Đ���ˇž0Ą;î‘ŋÛ×į­ĩDȨöTŪŽŧÂRķyŽŅs„@5{áô aDDTSZđŲžÃÅgŦuĎō7möė˜¯–[?y-Ã8ĨíCķÚÃoŽÍ5ŪŊhũãRw"ĸ_?yuKÅô”įŖFRÅĸ}š{-É}„ī¸{cæĪąŌ}ę“Wŗ,ĶErsĘ|ŽNšŌxb÷§ûž)Ģj`}ĨQŗ'uŋ3ŋ~ōęËôDřŧ}ŋ˜kGN™<ÕîÚw°´ĒÎÍ7"æĄĮîģĶN|ør–%æšįŖZ'oœøôī˙:3ũŸģot÷ģC…īmø´Œˆ˛R~đų×eøwé—;qđãOž<Ûč6jläÂGį„{õ8˜ ßJ˙ŒˇnņÄa­›×ú`ÃG\Ė˙>{ßhĒ=v07§ĀhšØč>Rņûyq÷Ũ9ŒŽĐ}ËĩWĢŗcû{éß ĐËȸšģ‘{§3­ũ¯Î.ååîũŪhšHžwJbbįMņĐĀRw§MģĶ]ŋWėNOÕĩŖđ3Ũ˙t���@ģĄ˙ûŋ˙;°-͕įī͗ɐÍŋé Ö)īŽčXxū—ƒ‡JŊdŗīšĶjeŊŋķœ(.qūüŠÁųÂܯËFß;IāNu?m{k_UčɏÄÄČĨü G?ÛSƓË›ģ]~ßXŗNs–¯ŧįNwĸ†ų˙-å˜Ëw„F‰GŅŲĸ/ ­â?ü^âUwlgÆûzÅĸ…Ũ˙ģIūÜŋ÷ÛÚqSÅ>CéĸáÛīgÎ׍ŸņØYȨæļdė>'ZøäŖ Qâ'ŋüĸčü%ĻˎQáÛī‹O[Ŋ§/LŽWM}jߞC‡O\ Ž]´86zbsŅgûĘFÉ'2į‹] šoĒĐŗm ´ĩAŋŋīŽaŨīĻ|ō=Æđũ鐸5OĮ„ręĐcqá§Ī™Ģŧ&Ī›Ž7~Ģ)¨üũøŅC{L/^Sé×Ú*˙Š2{ôĢ+ÉŲ]ÂûũĶīēãTNFēĻ>".ūÉšQŖÎjv8î3~˛ŋg§ŊėąåáwŪŨSÖŸķZ…ŠiAÉzîĨŋ'@Ī#3Ô{ąĸ/˛úKĮņ=‡^>{8go‰ûī˜4ŧķž4œØũ~ÆŅaŅ ãįGßÍŋđãŽ}Į˜ “‚†``ŨéĘĶĻĮ5ģíwęTšãîĐéĒsGžewûĶÁôá‡��ā&ÖįŲ=ĢÕĘãņÚ_•Ũ†ēʇrķ~#FDÄFÄũ9ˆxüQîD4zÔôi…G÷Ę)2”.üfáF„O’Ž"ĸ‘Ŗãų“.zŗD•Ũ.wwģ;v26L w'ãņr¯ČČĀŖ'OžĨ 1tųô) +šīOT[ōĩŪ*šũäö¯ĮŸŗđôÉúŽxö]îDDį=',›.MDĩ‡ŋ)#iÂŧ)BO"Šˆ}ĘđvĘōlą—;‘ģX*ĸKHÔtO"âß-ĩī;ãYšŌˡvĶŨus'rcŊ==¯Ü¨ÎsōÂØ Ŗ‰h /F\đîķy å÷<˜Ūã'ˆr÷ũ`¨›éID— GNPāüņ^T÷ĶžCU1)GúŅčQķ>mü×ÁÃå÷Ē;uØcËîžŊÕŲVnIĪŊôûčydÜCãž|ėŖŦ-¯¯m$"r ˜˛čé+ŋW÷sŪ÷EqÉ1b/"âĮ=T×ø]ͅ:3ĩ7ŲqÚPCköĐ¯û‡ŨšÚáčččĖ‘î:���ĀÁ ģŋåĨŊ×ņ’õđPâÂņö¤â>ĖũĸvīžOWĢkllllāˆF6ų…†,,üø? Ķ&G„ˆBũŊ…^Ŋ-ØxøD%…ûW+Ĩ ØÉãÎ|˙ãéē˜1nFCš{Čdũĩŧ‘7ilĮ ø!ˇ¯Í§.PÄ"ĸQū­ŋJœũõ˜ÔqĶ7ԟ=pļûũåįÛú9ĩëæFŖüÛ~ŸpsFŊĪĮėiwzå=ļ­Nr÷ôbéL]Cīƒé%™4v_ŽŪx9R:ŒęŠõF žáEd:UŪțܞŨEaˇCåÆZ ėTEĪ-÷ÅŲ^zé˙ ĐŖē9Û?-åÍIz(b {ųĖ‘œœī°‰ĪĪšĢĶL†3ŋ–7Ž˜äß֔û]ĶŊkāKDާM/köØ/סęÜŅ€N��€Û͍ ģ~÷&?:yšģđãÕ1%´á×ĪŪÛRčš0nvč֝.jˇŊģ×ū–{Đüg“ų ´…šßärn#EŠŲķæGúš÷´|”hÜČ<ŖŠ–ŧŒÆZbėČ ą#rJËîQ|ēQ#r'ĸ:ŽŽÜŧ.ēģąžÔØX×úŌm“ēÆ:rķî!wwĪ> Wë$Ņū lOģĶëFnŨöĐË`’WÄŊŸåüt˛AŅxō…ÆIŧ‰ˆãÉúõÛ/ŨŠ!ßē:"¯>ļÜŊôÂö˙詗ķG?+´ŽKz2Æū{ÔՓå¯ė:øÃô¤)Ž—€šÆNGļ“ū,‘ãiĶ˚ŊöÛęj‡ŖŖŖ6���ˇ›vŨFōũīėūö´gŽüP9BņLÜĄũõÅËuDí^ŊîšûČ´Xē|öTQáž]gšüŸųž–ßÂ~SV^ãuĸbdP'ņŁ´ë”åŧ—ąVpO°'‘'ëI5uũ7ÔquÄz_ų ŧ§›;5Ö4:ŦXĮ]ąŌĀ58^íi7ûĢ×ÁôO5îûĄŦa\ŨO'iÜÂģ=‰ˆX֍xŠ¤Ä˜1í¸šyęGËW×K/Ļ�ŨĒüí<Žé8–Ū#yžå– DŽĮ—eŨ:ŸW×÷ŨīeÍžôÛĮÃaįŦĶ��ĀuŨOPãiÄčļ,Ōpæ§ĸJĸF"ĸ†ķŋ*í7I6&hĘŧŠÛÅōĘēž–‘(LD§Oh1{ E|"ō Ŧ5ëOXFŠBíqÁ˙Ž@7Ģņô…öū-eæFVpepy'Ÿ.Zδgķ‰Ķ×r{(777ē\ÛŪÂËŲÖčÜËîĩE_õ<˜DD^ãÁ'Žž,ūÅHâ‰Rûjc‚DnĪq,Œ_ë/77Ī‘]īÆĐ{ËW­ŗ—^úôØŖ×/ēxūlĮ}ukÎZkiÄč.ī ę|üš÷Ūæ-ß_ ^\u÷û˛fīũļ­Ķ§ÃqÕÁ���"ē)ÂŽ˙]næC…Å–ÚZKéw[vYƒŨĪ˙zĒŽ*˛-kKÁ‰ōķĩįÎ˙vŦđģÄų{ö¸œČ=$TT[rĐĀ„ ˆˆ<CGš”{†H[ŋnå)‰š—gĖû<ĪPyŽöBųĪ{?<d ˜r_蕟ūŽ’(ÆRq^naiåšŗŋū°+īXÃĩ|ũĮWäīvÖpäTՕėĶÖļ]VīqwXO7Ē;{âØ™Ęs}Ė0Ŋ &‘Wx¤¨Îˇ×@ŌIã†ĩ Čô{G÷îØũķoįjk-Ļ#Ÿŧ—ūŽÔôŖå>ÔŲK/8zęŅōôą?æî(,­ŦŠŊ`1|˜gôŧ{r„W×bbd<cŪįģūĩüĖŠCģr÷ž&ŅØ^/S_e`ûļfũ:ėõípP/§ ���tŦ[9`ÆųÔ}ķõž‡ŽTSÆ=0Ã˙bҎ‡ž.nޏÖdŸķE……ûÔ_učį˛ÚQ“ޚHh�� �IDATœ?'ÄĶŨwœ¸ģåC‰ČŊXŦŅWNŊw,CDžwüĻÍ/k–Κ5Ņ×~‹*÷Ņã$—O~ųå=t?žnE?ô¸ęŽaDö[zU &G‡ÚÑįØqcęĘ~üękÍ×ߟŽ͘ãú¨…7íw!]n=ÖyĢĒžüšY5Ų~‹¯ēōī #đeøŪ֟žũīž/ŋúæ˜Å7z–ųŅsüŠ÷ Ŋ{Üwoļöįtų‡Ž5„ūnü¨žz$Ģáģī΍™öģīžsü}!ŪDĖČæ˛¯~4Ũ!y0.¯õ–]CGK%c/ŋųōë}4…?Uš‡L,~’ Kúī­eI`÷u:ŪzŦį^úô<2žc'{Ÿ=~đāWšžũöDÍčČšOÅM=´ķžĐĐŅâqüËe…ŋVkŠŒMūĒEķgÜÅ t`ũ*ˇęmͰ îûuÜeôīz:ĘÚËO��Ā­ĖšˇŌŌŌ2°†ŽürbâŨĄŽ����āJ×2FŖH$jyLc�����¸>v���Āe!ė���€ËBØ����—…° ����. a����\Â.����¸,„]����pYģ����ā˛vī¸ãŽĻæf'–����ˇšú†Fw7'68đ°ë5ÜķlÕ'–����ˇšsĒ=YƉ <ėŪ9æė9Ģšō|}CŖ ���€ÛP}CŖšōüŲsÖĀ;Į8ąŲ_%öpw“Ž–˙vö܅jä]����¸înž,#'tî4†kjËÃŨ-xŦŋŗJ����p.܍����\Â.����¸,„]����pYģ����ā˛Ž=ėÖÛķĪeņsåã#ƒÃ"eĶã–ŽûüHÍ5ˇZžwEŦBˇuoęÔđšī—]s‹ŽãĐËĘ⨌ŌÁ.���āæwwv¨T˙uҞŊĩ!3ūå‰É^õ•ēŊ[ đŊėM*ŋˇ{듎?̐ŦČzež”Šô[§ öŊļ:���āvtMaˇr×Ē{k§ŦŲžuQpë"åŒ˟x`MęēŖ˙ŋ¨?ūĸĻĻ–üT)‚‰į>t-E���ĀmëZĻ1”íÜZX+{&­=éÚ.ܐũŲž´%][…zŨ’šS#%a‘˛ŠqK7”ˇŽ§OšhĢ^Ŋîņ™S’ņЍĮ^˯$ĸʏâÃų¤ŠĘۈ ŸÛiƒ.ujäĸ­˙yjĻdü’5öt;˙öpÔ¤HÉø™qëžŦŦÔŊûtœ|R¤djܲíÅļŽ5ëVLŠŒ{ŋ må˧3ĩOē¨ÔŊûôÃQ“"ƒÃÂeSã–Ŋ¯ëxG›ą,~Ļl|xđxETüķīj+{^ŽOî0īĸōĶEaᒧŋlĢDŸ:52nkE¯#Ķe7‰* Ö?5W6>\2uîŌ÷ *¯á˜���ÜVŽ!ėÖč5%$9-đŠwüB¤~­Qˇ&˙åĮ˙´ËũúößėŲžfråöĨOŦĶۓÃÔÛüZ^ä+{žÕ–üû”ŠŦe läˇāÃü-ųRpâgßÚĩČqÃ0õåŸŧ­‘ŋ¸}ĮJ•71LũąmoĪM?đÃŅ‚HJˇŊ÷T&“˛U÷ƒvĪãęu¯į^ ĻūØæ7´ĘM~8Ē?øĘÄâ7–ŽÜ[CDTšså’ eÁų÷.ÍW_l]%9–ž˛bO QÍŪÔ§ß.•ž¸ũ˙ō4ģ7ŋ =õæSĢvVö´\#ķ*ÕéíAŲö˙ˇw÷qMÖû˙ĀßÖØéöŨÔuØŨĐÃFĘMĀ ņ$Ū$Ø 5īMÔL=™™eæmzJ­oYiŪ¤¨ŠTĸ‰Ę÷+šqs ЂqŠŅQG7RļD7:°î÷Į¸°;ƍ:_ĪGį<`ģŽĪįũšš.ß\û\× ˛Šx<Ž"ĢČ\}I^žN&°Û3-šŠųú•E;”âUĶŗžÜ2QģûÃŗí_ ���đ@hG˛Ģ)ÕÛ×W`w›s{O•Ëm\%ņå cŪØ0Y¨L؟ŅpĶ IX5FÄ?zb(O¯(V1 ŸaˆØ<.—Ë´Z ĄæÄn˜,qJXÅgˆø‘ŅÁ¤§°šŗ%\"Æd´¯ą´Āęm’„åc Ï^2#HŸy<CGDÜQkSŌw¯~N&ōõY8EĸĪɔŠ‹Kôŧđøč@_¯ŋtÜēĪRž2œkëu&8FJō DDųY…ü1 …9%DD:ų…nH”ÄqĪ45SķíĄ<ŠZ´ú9™€ī+ĩrÅ(ŽŅ…á���x�ĩg‡!jĩN 9ĨŧČč,kJˆũCÅl}qAÃö|‘˜ÛđÃe“Qī @"Djy-ŲW$ä6ėΰÉWŌđõÅ Ã%ƒÁZq|‰˜ß´ģˆcT+ÔæũĩÛÅ?32tX”tHü{r2ŒDDĸˆá‚ō/^™ņÖžĶ ø2 Ÿąų:Whį*‰H™™GÁ‘ Q"uŽ\GdČĪ”3Ą‘ÁNôLS3•rņü%ũ$ su����ĩ+Ųå ølŖJa÷Ą`:ŊŽžEnÆ0. úÆ_Û\-ÃeÛxÃüŽKä6 ˆÍ˜sbƒ|ũ´™ī)xSÖîLųæčŲ˙ŨˇDÚ¸MčĒ/¯É)JZ;ëŲáŌ3Ū:Ĩ4ØyŨ7$ÜW_ !ͅ|8L&–ņŠråRfĘ ÁQ! 9Ņ3ÍÔtÄđØr]žņ���āÁŌŽd—+‘ōlZQĢw4éģ>ĪTˆˆËá’AĢozË Ķëšg›]O×, Ŋކ!RKUōž[ˇņš0‘/ŸĪį“Ūb+âK_XųqĘų܂´}"õН$žW`įuIT(ˇ(ˇPSU$ æ’¤”äJTō5â¨P.Q[z†Ë0dĐ-6ÔaÍ.���€SÚŗŒAôÜĖŽr˙k[䖋 %ɯ­ø`GŠŌ@Di ģ<ŋ@ŨønI^ą‘#l}S[R7, EŠ‘-”ˆtFņ šĻA‘vFId$"2¨äg2•æ6rũCŸ[ųĘpļļHŠŗõ:G†<ëëŗÅ\Y¨?I"5y§˛K|CÃÍmwžgbŌ–(Üâ\9Öė���8Ĩ]ߠƏįŖ8AÉÎ#įlúüÔšŒĖs_oy5ūųĩ9‚i­‹ä7z~ŧOÁļ7ˇg*UuQúĻ×Ģ%“§…ßÕĪá9ęäõŸå•h4ĒÜũ듊y#ã†sÍŲgņҤs%MIîūÅoĢCŲF•<_g eō[/%.Ū—U¤Ō¨TŠŒ}rH,áÚ|ˆ ‹Ö|ģ7K)&"âJÃÅ{“äÜĐč@sÎ÷ŒoôD)en[ûEŽRU"O}ûũ Öė���8Ĩß ÆūnĘŠ‘Ÿŧˇķô‡¯Đ؁xøäm-Šö¯OÚ¸á+“v0kß[ŋYKxøĸĢæIîîšSßøWÆj?™ųŒ\māŠĸV|ļ.šKDü ÖÉoy5æņ¤ąË×mĨŲTôŌîÉS)åÄę}ë6­ßš2~ŖÖČæD!Ī}°s‰„‰õ׉ˆ¸ĄQmĻ<$JfnĢ \ÆŨ|Ä0v¤¸! į{FđÂ[•+6Ŋ7gėzF>yÅĒøĖ;̎˛!����4×Íd2ŨíēRŪ[ÃfæĪ8yjžČņļ����pŋ)++ķķķküĩ]Ë�����îeHv���Ām=hË����Āa����<(ė���€ÛB˛ ����n É.����¸-$ģ����āļė���€ÛB˛ ����n É.����¸-$ģ����āļė���€ÛB˛ ����n‹ÕžĩuĒß*Ēk ÆÚēŽ ����\đyԁģBÛtë֍Ķũ‘šŗĻú=&čŧZ\Ovĩuŗ˙͝OoßGû°=ڕ4���ƒÆd2éôUīoÛšüī |}úuR-Ž/cPũVҝOī>Ŋ{"Ķ����Wtëf2ŨųlīÁÎĢÁõdˇęęŪŪÜ ����8ŨēŨŌé;¯xדŨÛwî<üîo���€v1™LW8˛U����p[Hv���Ām!Ų����ˇ…d����Ü’]����p[Hv���Ām!Ų����ˇÕ_~VûcŌĻ}ŋÔ5UéŲĶw`ČčŅ‘ŊÚTÎÕŖ›vIŦŠ}´Ŗ#ŧį\JyīŖ˙<ųÖ+‘}īv$6”Yˇˇ,rҊüÎ(ĄC›ßÎPÛßR��€{G>>&$Ч'×ËŖļZŊürvZÚ?¯ÔÜí¸:^W}͝ˇlæķÁ\"ĸēę Åųôôí{Ē_m”¯Ŗũt?|ū+‘:?D°ĸĶúŸ6>.°OŽ.ļžE؝[��ĀũČk@ÜĢ/ŠĢ/ž?’^vCOŪũž3iŪ,¯ˇŸũín×Ņē*Ųõäûú ë/Ôųô÷ĐžūåeEÅ(ß>öwĢU]šVgčD×˙Ą,¸S &jv§V��p?bõÜģ<}õ—97Ė/hŽ•)Ģ<^-ü~ĶŪŨā:\W%ģ-ĢeąˆåÕXymeaZę邲ëUä҃×hĖÄØ€Ū¤ĪŪą9YIDũČų÷8""ĒštūБķ—+ęXŊ—%ŧČiQtéÁˇ\1ų/įĶ~šĻĢķė5đÉIĪGx‘ž4=%=ģäڍš:¯ũ$‘ŖF!""]IÖŅ´ Šō›ÕäŲ˧ÄčŅ1ū¯ß<˛iWYxÃGÛú ŽO-4ųŊ"ĸĢGÖíUXôj¤ˇvQé‘uŽ˜ôkę %/aÕôđēKß$§ũSŠ­õäI"Gąh­Ā,\=˛nīõĶÂĘĶĶ~šĻĢëá>zîĪܔ´ķ%Új/(&n꓏zĐĨƒo¸ķĘ̑ۿŨ.%oü¨|ĚWžėmŊjŪ˙‹Ÿõi^mõÍŠß\TUÔyöąėdŌO=‘UvŊĒÎÃģ_ĐSãâŸ|ô‘–3 ųō�ŊÍæÛ.ÍÜęY#*Ķŋ)¸vŖ†¸õķĄÅ´WŊÛĸĸÚĘÂôÔĶËŽW‘×Ŗâ˜ņãFø{؟���n‡õˆąšeu—“˙g[͝=üFÆĮDø÷ëíåņ_ũoŠŦ´äīĘĒŠį_.šX“ērOAuũvžas^›ę™žú“œÔCōÔ¸ņ‘~}9ŦÚ× ŋKMÉšVMw_×'ģĩēōâée^ŌŋÕ¯Ų­.JŲģ뗞#_˜;×Įŗú??$yāĶēš+Æ?6cAõŽíé}ūöV|//í ĸę_ŌŌFN}9ÆŖ˛øčéûNŧ“āŅĸIŦ:ÕųôËņ“ßJđ&Ŋ"ųãÃģžėšfƟ!}Nō•ƒ_˜Ā!Ũ•Ŧƒ_>čũJĸĖ‹Ē˙u$éü ŲßOéįUWU–zpĪ1ÎĶÃYV_Ÿô¸gn‰ęŋ#øÕūį’ēG¯ōK*’‰¨ĸŦŦϧÄßÛNģ<ˆåáQw#įŧbččÅąŧž^úœ‡ĪVŠ_˜7-SS–öÍĪ5dNhmÖ<Ũõđ¨SgŸWĮO~+S]phÃĄc[KüžJ˜ĩqëúųŊ˙HMûqđėđ–X°QKķūošĶ‹iįe#Ļž<ÎŖ2˙čéģžä­™!y„jKOėŨžÃúËķĶæ>ŪC§<0eī>/ õļ]Ŋ>į ͡[š‡Gú|Záø¸ãųzÅÁ›įCķ°¯ĩ˜{—R÷îũ™›07ˆSSš•šŧį�ũ=qDÛ��Ā Õ\úEU?"qJŨ7įVüVÕęSÜa Ķžõūųāžc—ôÄ}<ręķ“§V~°K~ŗđGÕÄq–xüd^ÜëŲ˙ ]JũųąüÆÍz)ŧ.ûËģŽTqE#ϯΚYģ}û7ģŧu-uU˛û[ú†åéŋq˜5^Â5˙ĸ/ūN~ĶoôŦgz ŽM¸rųŨœŖ ōōdyą<š ŠVĩWHÂø?÷&ĸ>=cfm/ŋvƒŦÜÆä2^æíADÉčßÜ´|EõŸ‡xyÅ/RĪžŊ<ˆ¨w¯ŲiŋĒH@•ŋ]¯é8Dâۋˆŧ{ĮOë;¤ŠëI¤ąúēk/Ĩ”–ÕzPŲŋU™Ėˇāōå öĄ˙^)Ŋîé7ŅĮnģ<ˆˆnxũyņIo"Ō_ø§’$Ī ˙“]úëöŗæ†Ø Ŧ•ZŸŅ9D%~T|Ũ?r„õ$é•öCYŲKvmÔâáҞ˙-U÷ ™Z?Ŗ&¸ŧ!=_Q-BÅi9ZAĖĸI2>õî5nŌ•˛Î_P ĩŊ>[_lŗųÕJĢõ ‰—ņÍ&ę‘kž­ĻMCÄ?§_Ŧō‹Ÿ3CD}ããĒë~ĐUVSÛ��ĀŨČ9üŠgܤ˜ØDY,ÕVĒ”eŠ‚ülyŲú´ˇĻ0eûzēYQYGD7*ĪgGčKōËúŸ˙U6nôž?ÉkˆČk`p�ŠŽū\EžƒG‡ķUé%ËĩDtãĮÔ#û-"ø1]}ÛID]—ėō‡Î}!¤Q]­^[˜uūĶŽMœ?=ĸQÅUU]Ī!7]ųëëߏõŨĩŌJ jĩĸ—û¸oīú=ŧ8žT^]k­6ŽO?nÃĪŊûđŧęTĒJâåņˆGUî鴃W´ŋW×ÕÕÕÕÖy×ņŊŗŗ}^äīāÃņũĮŪëøÖ]¸¤Ą@MQ Į‡ô/ŋøĶ•ę˜>Ŧ˛_Uū!~ä¸]Ŋ|ÚRqõwę1ħ13ãøxž­ {´Ō‹ĪĢ˙؝åÉbQ/Ÿžõo°XP]ũ…ˇN×ŌŦÆĻą ž><¯ēkĒJR[ĒĒë9DÔøėŋũX9Ē2=ųÚ*ŌNķ+”Æí͝qąˋEuÖįCŊōĢĒēC|âđxlÄ ™´91���ÜSÍåīoČî!õ— ôĮ$ĮÄ^:˛ûđ?Ģ#ĒĢŽí;zęãŧŪ^,‹åáETÉ""Ē*ūņĘčņR?/yq5yJ¤~¤L-Ŧ"z\čëQųŖ˛qŊo]Ųŋ¯Õ>éë׃ÔUw¯•DÔuÉ.Ëģ¯ĪŖ —`ėG›ˇŸHW„Ŋ ņ¨ŽŠ&×âœËĶ‹ęęŦ­ō`9¯Įââ'‹ÅĸēēZĸÚĢGwėÍfÉâGôņô Ēܤí§ëĢN|ynßķYšŲŠ˙L­ayû…7QÆ÷°õz/ŋūŪée˙ҧŦLß/ėqoáã=N”¨j‡öP\Šķ‹ņķ "Gíby64ĻēŽšXÜĻĻyx4.gļ€Æ{˜˙¯-#ëB--;Ųŗž“kjęčæwŸŧũ]ŗmyÕÕDļ’];ÍwTš“ķĄ^]M]ŗŠؙ���nŦŽJũīõŋ Îy=>sFėÄņ˛ÂĪ.ęYžņķgGÔå'§¤]ǍŠĨa3^Š­ß§Ēđĸjâø?÷g˙‹ÕČ@ē”RŦ'"OOyxyŨˆfhŧŧˆ”dˇ%ž¯7ĢēB{ƒ¨¯—§Õé,RÛÚęšjōäļcÁdĩŪâ)qu5ÕÄby•į˙¨éļ0>üOæ7Ēū[MÔxá•ķXÄø)ãéŋĨ…Ųi)‡xx/ø'[¯?čīųOĨJĮš¤ö Ŋ¨ī@_J)Ŋ~ƒSĻī÷„Č‹ˆČųvyą<¨NWgąaĩEüļkˇZËk—m¯ĨZoą]MąX^DäÉĸžaͧÅX^•gą8vžŠl§ųžm/ÍOOVķŠgb���¸–g¯ē*ËÉ̝ä¤ũ8øŅžDzŸā!}ôšËŊb~ŗĮ#^D•õ[ęÍ/c"b]öúsēœüK ‘ųúTeNԁô ‹jęęô•t×Ũ­oPģVĻŠcõęÉ!"ŸĮ|Y7ËŽ4uÆuåĩ:Ī~‚ÆlŖíŸ'߸rí÷ÆŌĘĩu,ž¯7QM]õčŨk֖˙ĢPS_xíĢ…ŋj˟€?ŌG>.FÂĒRiĒmŊND~üčĘĨÜ_Žyũɯ/ųøęËōK×ŊũĖŠ˜Ãv5ō~´/U]/oĖÂŽ]ēRßf;´‹Åĸ˙6ÍëĘë5NÕbŖ˙uå*]cĶĘĩÕ,žĐ›¨ĐUõ{gß>üú˙8,–—ˇŊ'Ønž+Ĩ5jvaķššžcįŪ‹•v&��€ō_ŧnųKŧæ¯öčÛ˓ô•:"ōdą¨ę÷ÆĪĸũsP‹ ¤U—s•Ŧ�YÉ ?ú5_aÎ&*JËj{ôöŦŠĐhë˙Ķ×ÕUßŧžÆĐUÉnFURzФôRIiŅĪųßėūęģʞaâGˆČK3´gYúąô_5ŋë+U?Ÿ>˜sSūd€yzą¨ēâRQšæ÷ļô–׍ GĪ—^×ë/ųáhÖ5Π`‰‘ĪcÖĩœlÅuŊūzÉ{SnúŠXu7Ž–VגæÂ‘¤{ŗ.Šnčŋņ[Qö—¨§Ÿ—Í׉<üüôÅį­ čGDäåā}í|–ĘË_R'–Ŋv5×Kö8)ŌSŗK4ŋW\ũ1%Ŋ¨ļa…€í�ڎįįÃĒø5ŋ´šˆĒUYišú†ikŗ{ũīQqáHVéuŊū÷˙Ôwr"/ņˆĄ=ĘNõÍĪŋũŽ×_˙Oū‘ÛŪũ"_×roįšīBiD6ÃöĮH{–ĨûæįĢĒōԜ”ÔĶWČīqo{��ĀũTœøAĶ7vÖŌ¸đ'Dž‚G}ûŸ>+a`Máų‚ "*ŋĒŽ}4<BܧG>ĸđYņ=U%ĩŊķĢ_YUTPæ50&v )~ŧ\˙/mMņų‹U~ą?¨_¯=ú<.K˜ŋčõdŽīę|]ĩŒĄ˛`ßgõUzöėåã?/f„ŋ9īķ 7k.+õ›äí'ĒČËģŸ$fÚďzyF íķeÎöū˜Éö—Zę54fˆūü§›U7ę<û =7^ōqd“â¯î=ũՆâ<>x||\>M•”ũŅzõ•q/ŧ=lkjUËŗß/lĘ´Xō ë¯yųI|ĒWü˙dŽËģ˙Ÿ<OäÔ=1¨_CvÚՂwĔŋ]˙2íĞmGYŧ€đ҇ĻíúĨšˆ<Ú Í8CƏģüEúGës<<{ú =~¨vׯuuöjąė˙å“ü›ĘĒĢcõ=DSßɂÆN&€øYs=ĶN¤ėũŽĒ†<y~ƒF/Ėĩ“ũæģTZ‹°˙fņēG@ü´™^i§Söž­"ÎŖũcįŒ҇ˆėL <��ÜOÍåcģ>ē2"&üɄĄŪxPmuåõ+e'v¤§Ŧ""Ē*8’ōØŦØŋ­z’t˙ųųDĘąBÎhß‘‹įĶÖ˙IWéų×ĨøøĀēũĩņ“ĐēËĮöîĒ=>~ÖS/Ē֔ũ’ļũDūî5˛Q7“Éäڞųŋ\ tĻÍŋ­����āÁ°hųÛw;×m{o]GUVVæįį×øëŨZŗ ����Đéė���€Ûē[ë<ÂIowØep����¸¯áĘ.����¸-$ģ����āļė���€ÛB˛ ����n É.����¸-$ģ����āļ\OvzčĄÛwît`(����đ�ęÖ­ w=Ųåt÷ĒĐVv`(����đĀ1™ū—ÛyÅģžėú>Ú§â÷›×47Œĩu����<@ę6oöÔÎ+ŪõoPc{°$ũ˙¤ú­â÷Ę[Čw��� ­8=ēĪ›=Í÷Ņ~WE7“ÉÔyĨ����tĨ˛˛2??ŋÆ_ņ4����p[Hv���Ām!Ų����ˇ…d����Ü’]����p[Hv���Ām!Ų����ˇ…d����Ü’]����p[Hv���Ām!Ų����ˇ…d����Ü’]����p[Hv���Ām!Ų����ˇÅryĪōōōŒ����ĀĖĮĮ§ŖŠr=ŲíĀ �����:–1����€ÛB˛ ����n É.����¸-$ģ����āļė���€ÛB˛ ����n É.����¸-$ģ����āļė���€ÛB˛ ����n É.����¸-$ģ����āļė���€ÛB˛ ����n É.����¸-$ģ����āļė���€ÛB˛ ����n É.����¸­ŽIv %)oÆ“‰Č"ߑwIÖåŧ&ŋŊä.†@gū. ˜ņ…ænÆ�āÜâÁĸŋíWŨõJUûãÆ~Pf¨Õé͏Mãuæī2Ņ€IŸwíAāŧ{!Š�gtX˛›˙ÎHŅ€@ҐWĪZŊ§;Ŋūíãņ”7ßX2Ō‡ˆˆÔûög<§0ž$4*RėËÜí8ĀMéō˛‹HĘŋہ8Ô1'P÷ļ–ŖŒņ‚NāĖɤCN8nęrŋë dאučT9‘ūÜĄŗē–ī*‹KŒÄŗtÃĖ„įÂøDDšė7~rRŲ1•ß_‚įíܡįáÜģ¸'CNĻÜ( ÷ŋہ8ÔA'P÷´VŖŒņ‚ŽįĖɤCN8pęrŋë˜dWw6ųŒ–$qddĖŲ—ÖęĪƒˆárš~WäáĒ?@'gæé9ŌčĀģ‡C8 <0ĘĐœ™f21Ÿī_’ėjRgëI<6qáD)Û(ONmšē¯įŠž˙RK¤Ū9Q4 0ôíīRįČ$/ĻéIôÅ@Ņ€¨ˇ ‹‘ņvâ˜aaââ!QcæŦųĸ é"ņ™ŋËDƒ_NUåŊ7u¤x@ØâL;áä}ūúŒ‘ÃÂÄdŌ“–ÕbyCšœ}¯Æ‰ЇÅH9Į�� �IDATĪû,Īr]Aō[sâ#‡ČDÅCFÆŋôū™’Ļ•õa蔩ī$Ž& ”ļ*A“škŪø‘ŌÁ2ņ°13ß>V¤“¯(ŋĢ¤ą„†%kí/­9õS­-qĶ›išO•ÕĐ?õũüĩĸąŸuŠsdĸ3žļŧ:o8=op hę1]ú’L481UĨøâõI‘Cdĸ2éˆI¯VXlŋĢÉ ÎøėÕøgĸăEƒeĄĪĖXüY–Ęņģ­ûGšũ™@Ņ€°×2› 7œzY<@6ķ”ÎQ‡Øl¯&ķũyãŖ¤ƒÅCFÆŋžœīÄϘZŽ*UdįhŲÁ1ŌĻO‰]j] 67ŗ{ĖÚ#Í“@ÛŖ}0(sŊ6Īl.33ƙ͞õˇ>Ę-Öė:8 X˛Bp +†Ō:ģ˙ĩ˙œĐ‚Ë3ÁÁŋ›Î˜ļãtĒ!.FhûdbŅÍŽpœ-ÄQRá¸iĐųXPFÉņCyF’Nį/`&‡Ž—g:"_°RJDDÜá‹>Ûzāĩ­ŲĖČℌHhÜļžŗvÕimXâÖYĄiÎŊöüĸŖ:aLüÂY2iä'“’WMŊ Ü“˛*Œ!"†2jĶ7Ž-b"–ŧ*đØFszŪŗËŌ Â˜ø…ŗD¤ĖJūbëŧÅļS˙Ũ¸†ą`ÛŧŊjņ¸oL$ezRrúÖD?}_<Ÿˆ ›â§PrCĻ$ž!ãŗ ĘėC‡wΟĻŪņÍÖQ|""†aČXzčĨEɋĢöŦā“:cÛĘ [ø'ëKČŨ˙â5/hʲ…2F›{ꃙ/ųëˆxLëUj[‘`ÜäĐõyŲGO)gĪ5uIÖņ#;lōh_s˙<ŋ,]'Œ™št‰„cĐäŨųåōŋ]PLY%sjsÁ0DdTîxi7ėÅ {Ä|*?šeíŽÕ35LĘžx]­Ëx{ōŦcIlÂĒD!cĐ+˛’ŋŪ:/GųŲŲw#šöŪ  åė=•¯›ëoū¨Ts!SÅfŗõ9YJŠ’˜ãËĪ-4’4&”ë°CŦļ×P°iō‹”ŧ)Ë^ ãUéɋ_âØ˙`ļĻ–•Ērķ”$žÚđ˛K­svĐŗöF0|™Õ“@{ŖußĘÁ™ÍĨrbF9<-ÛípëŖlÁáAaÁū ÁĄŽJ+ėŽZûĪ ­š6üģéđĀt§ã†´#B‡ĶŒˆëÚ ĮŠBˆČnRá¸iĐ5Líö͆ha€tŌŅ “Édēu*ņ ‰đÉ7žĢąØ"uH€$bsQã W÷Æ B—e4žPķÃĒhá ¸O‹,vĢ85㠉p\ŌU“Éd2}ˇ<T šRa/–šī–G ĸWæ4–SqbA¨0 z]‘Éd2ũ`.ä…#W÷¸ŧ3:@2pvĘ-“Éd2ũrhaܸéīæ7…QqtîĀ�ɤCõ՚KYžyĢŠ1Iq’ŗOŨ2™Lϊ¯fK…ąëšJP|A* ŸĢoČ˙-‘ ĻŦč˜ŌZuĀŲEOH„Ũú‹E?~õ‚T8há‰[ũščLS…ĻË;c5xëÄlŠ0`úWˇ,Ë<•8H"|Ąž‹œs3%AKÎ6•tëlâáSÍą9ęęĖEOH„/ą¨åÖĻOZôSƒwo\80 tQF}É5g4wؒȁMVôî_%Âq;/;îĢíŊÕj\j~Xmo\:`j9Sé­¯fK…Ũzša—Zg…ĩÍŗöGĐĘIĀĩhŒĘÁ™ÍĨr8ŖŸ–uxëQn6^Š˙["$ėšjr8ëüĄl]ĨũQk÷9Á —f‚ũ8˜ãt´A;#´2ÍZsá„ãD!“ Įt’ŌŌRË_ÛŊŒÁ|k'bĘH>7zJ$‡´įeļé*ŊüčŠrE įé4H:VÆĻâs""†ˆˆ>)ÖîMæō“YZDOiúƒ‰?ęÍ}‡lŌtŊ„3<q‚oãoūâ@65Z퇁“?N9‘´ŧņ¯:ƒ+đኔåĩđFÅ[\KđúsȨQkˆČP˜™k$QôÄĻŋ Ī-ŠåŲ‹šCKc"όá‘ōôŅĻÅ!ŲG Œœ‘qŖ¸D$?yVKŧ§§ÄX\ ņŸ0EB$¯īg‡œ3ÎđøˆĻš¸!celRËsTDŽģšaˆH-/jšGÜđ•I‡?Ė8x—LúüŦbķųY…FQȔ1)ōōÍÛk s”$‹đwĸCŦ´× Ī,0’ Âb\˜đÉąV.(Xh÷ÔrĸRÅĖ#/4ēáæ4—ZgĩÍŗöG°ĩŽŠÖ-(g6WČņŒr|Z&"{îˆsE}lmœN­ čôĄlÅÁ¨ĩ÷œ`ĩ•Dm?TíĮédĪ8ŒĶÎíŒĐ58âv’ '"čtí]Æ`ž5›0Ē~Â0á“GķN™‘tNįėĶTÅ%zĸâŨc˙˛ģÕ{jĨ–¨ž ŋũ'ÖhJK´DĄb_‹×_I¸åī$, aš~Š+JųäÃÃįō•Z­ŪØ´Oŗjx‚f˙<Zė¯ŅnjÄ4@HĮm¯ÄëØŌ˜đÉDGv§Î[. eˆ4YĮķŧq“#"Ō”–č‰$’æwęķ}E’Ģ•ęÆ~vČŅ@4lÖŧĢš|—¨\Š%ō%G]-•tfëņY#äa‘1QᑡüÆŌėžË‰Ķ暊¤ž¤ČĖĶ BCĨL qwĻ‚Æ…‘./̈xĪ”&ŲšiŪ^MšJO$i6.$û˛ÉîMēíœZNTZ•Ŗį„7ėâäp;;šÍ6s|ĖÚÁV:,Zw< œ;ŗĩm€ Žf”ŗ§eÛî˜3…Y§“]3”ZûÎ 6ĩq&Ũ8í‡qÚŪ ēĻ#GÜvRáėAŽÉŽųÖ4NL”@ŖR×ŋÆ Îûōh^rĒ*nļ“sŅ`0‘xڎe-†ëÛtč3ļŨrt:›ąēüΊ8ōˇĖœŧŗ˜‘>ŋd]t €Ë‘r˙ŧiN 3Ã4[CÆårí‡ŨąĨI&LīŪ|69Ã:ŠŅœI‘ õ×÷O˅ž\†!2ęlÜbŖ¨Įn1 C ƒí°Ģ™ĀyI§$É;Ž?sö@îéDlAäôUkŽōeŊ+ ķĄÃyųē龆Â%'øU1ãKÁmF–‚Â$E™…FNH”ŒHåd‡4o¯Áę4ŗ?ë:fjŲ¯´(낖-iŧPäėp;9š-:Áá1kŒZéāh­š(§Îlm ŖåėiŲem:(Ú8Ŧꊡ´ā`ÔÚ}N°ŠCfB‹VthĪ´ĐÎ]Ķíĸ.8ˆĀYíKvÍˇĻ‘1}ÅØô–ī:ĸ˜ŊLâT9\.—ˆA`Td{ūT3—c4čuŽũŊdČŪą¯Ø(x~ßÁ5áĶĀąˇK LS:×4ŗuoŧtą4Ņs“C>\}4S7JöíÉŖhæ„`ķ;6úGg0q¸ļO$ŽøFCķ=›*rĒĢߨéĸĻo ]Inö™”ũ{íž?MwčÍģØ{702”—”Š0 ×fąÅSd ‘8LĘųĸ PC”)ײeáŒĢÂ0 ‘ąÅ¸tŖíŠÃĻ–ŨJÕ9™Ĩ$™Üxōvu¸âÔ1k­ØYŅևsßP.œŲÎҌę¨Ķ˛-m>(Ú2ŦëÚĄ´?jí?'8Éá8ėÆŲf;#lGĨÛ.ęüƒœÖŽ5ģųG’Dĸ¸wv|˛­Ų˙x^Â&eJrŽ“'užĐŸG¤Ė+jžŊAׯ,ąžEŗ§Оž>œlķq6ÍļTĒŒÄ–„Z.+)(Ôļ!�ŸMFÚōų6E^‘4¨JãIÎÖįË.:{,ß(ž×đ'_čÛēH]ĸÔ[ ‘ų^^2?š‘FĢq-~R+”Í RŠõD<‘ ­]Íõ‹]đnŌg3|HũíI…ãw™°č`ļ6?K™ŸYhEķ‰ˆ SAVNIaŽ’GFpəą†/đeiK›=ÅHYlīų‹1ĩTĒÉKW’(,¤é”ęZ뜍§MĮŦũėühë̏o(ÎlČáŒę¨Ķ˛-ŽNL'ēt(íZûĪ Nr<ėÆŲfû"tĩŌNouūANkG˛kž51Yܨ˜čf˙Å/]Å!mÚĄL;Ņ`h:mHĮŽä‘>{Į>‹äH—õÖŗáŌ9ĮÚ˛†[:6’GÚ´Ŋ§wŌel[š|õŽ\ŊŠqy\jēYˆ %Éë¨Ų Ÿ:ƈÃ$DÅigš uęÎ4Ī_.—ƍž2’ŖĪÜũVR!IÆ5-J’NŒä‘öÛCéMGšA‘|TNlYėp.1|—H­T7Vręx[ū5ą¤Ī8rēiøTß- …ķwĩ!÷ũ1ÃF.>eyF0 ‡Ë8x—ˆˆ Š‘ąUNæiy2ŠšøĄR_cazR–Ŏé:ėk˜ 0›”įŽ4Î Ũ™}ij›uČÔrPŠĄā\‘‘>Ōō$íRëœåā˜u<FD-N­šūû÷€ráĖæč¤ęxwÔiŲr”-´å pb:éT%Š•ŖĸK‡Ōî¨ĩ˙œā,‡ãhvuūŲŪ냲>͚鐎39ß4č"Ž/c0ߚƋ›6ÎÚ'ÃgŒœũōĖ‘s瘨Ö͆Ë÷aSqÆÎĩÛÕbß°„q&|Ņę‰Y‹Žn<YųâÄ(Ŗ‘ŸLJÎTûL\Ũ–-˜đe+b˛–Ĩŋ>yfnB”ˆ”YÉ_giy#ˇĖ—9ą7?bb(;7ī“Åo3ŗ"9:ųšC)ÅÁk—†ŋ˛63o˙įé4**ÔQ‚įGīX˜öዉÚÄ 2F››˛?ƒÆNËmC+Ú_Zũm‚JvÔ?žöĩ|}؊ąYËNž2Ų0súX Į Ę>”t\Á zkås?‰ā9ūõ;›d+'2ƒĖOŪ;ˑq¨Ā•Å BŨ'“įΊ—ruÅ'“öy1‹Ļ’]ÎŨŋ÷õxMnÂX!Ŗ&÷ôŪcå銉b öŪ%""~øH‘qËéT#{x¤´>˙ˆ`ŪîԔlŖ ĄáÛtwˆ5üq‰ąæßûŌ ŨŒ8W§<{<ÕÆųÖæ¸tĀÔrPiūŲB='$ĻŲē!×Zį$GĮŦĖÁY9 tb´f÷īå™ÍáIÕá4î€ĶrëQnz¯-ãh:QIōŧŅ(¤ĢsŋJ°ĨũQk˙9ÁIĮŅ~œ]p`ļ3BģĶŦA‡œpœŠ¨MƒŽâō3Ėž Äž[dëũ’OĮI„ƒ^ĩōœ]SMŅžŲŅAáŅËŸrWQppÕÜØ'CH>;{õÁüĻgū°<T÷ée“cW3?]’ņ„T z*aŅŽĖÆ§ŲY+$wŲæGޚcČũtA\Č ‰pPhÄsK÷äT˜Lˇ~؜2H2đɅ'*œ(ÁTķËŅՓū:0@2đɸÎ^Ŋ•šhDøÜĪŲmWiļŦ{J"|ÂüÉæ*r÷,Ÿũ¤t`€dāŅqKļūßeË'UŪúåĐqO…ÖŋģüČO랒ŸĢÂĨ“Q˙€ĖËE_­šũ¤t`€4čŠé+O–X<RŌAW×·§BH„Ԑŋ&,Ú|Ē)RûīšLĻĸ­’ÆŽ67íÄlŠ0@˛<ˇŲŖ9ívˆöÖ\>ŗqÆ_#v9ōĶ­‚uOYœ•no˙Ô˛]iŅ秚ŨĸŪļˇŽ%››Ų=fŒ‘õ“@›Ŗ}@(“Š­g6slvșiÜæĶrķo5ʖãåđ °xÎŽŖétygl@‹ņÚŌYCiíQëˆsBKŽÎģq:ę‡q:ՐöDhõdŌBÛO8ÎŌ1MƒNĐâ9ģŨL&ĶŨΡŨîØĖĄ+s"ˇäíąr‘ģŗJS%O~fmIüžŦuĄyĢĶÎü]6˙´ø­o8ûD€ģâ>9 Ā!CúËĄĮbķ>u0@J€@YY™ŸŸ_ã¯íūR hŽ$å͙S_ūÜb%—æėņĸĀHŠ ™ŽĢĨiÎlų$—ÄŗfālĐ”ûŌeœ*ô•: %ƒ¨Ŋ_*-øŠ8ĒŧãæLRΈ“ņŲ:EÚŪÃŒ‚ Kâ]y¤^›KSå}U\pj˙×y:Ҍmŗũ­ođ`Âåļ jƒė•UvNŗJ€–1t<Mîū÷ļ%g”jl6O™°dŲôpW×ĸˇŠ4CúĢŌ…iĆO^ąyYä]\˙Že poēO(h' %ĀĨÅ2$ģ����ā>°f����Hv���Ām!Ų����ˇ…d����ÜÖŊžėžųģL4`ŌįĒÆŸg|Ņ9ß'ŨŠ…ˇ_ׇ—ķz˜h@üö’Ž-ÕP’ōfü0™h€,ōš3;´š�õ?w Îî[—įp'ĩ��ār?=g—/ Ō };æQāęŒ}įhäôá ÆęĐÂ;^į‡×˛C:…îôúˇ0ASی“I|:ŗĻ6čĘĄŋĮ§��€ûšŸ’Ũāy;÷Íë ˛4ŲnüÄ_ԔÛudᝠĶÃkÕ!BY\b$^üŌ 3C;ŗšļéĘĄŋĮ§��€ûš×—1tƒ"¯ƒ?ŸŋĪuU‡ D —ĶU���´?ŲÕ$ŋ5'>rˆL4 P<ddüKīŸ)14ŧiH}I&œ˜ĒR|ņú¤Č!2Ņ�™tĤ×+tÎnĐLËõŽšŧĪ_Ÿ1rX˜x€L:bŌâΞ,WBÚL—:G&y1MOúŖ/ŠDŊU`­pUVCáâ!QcæŦųÚ"¨3—‰ŋœĒSĻž“8f˜L4 P:,~Ūgyļ—bęRįČDf|mŲ0ÃéyƒESÕŋfPg|öjü3QâÁĸÁ˛Đgf,ū,ĢqšĻexÎÔŽÉÜ5oüHé`™xؘ™o+ŌÉ׏ße-ŖĩŪ!f iröŊ?B&(nŨFü‹ˇĮXôŌV‡Žˆt_Ī =˙Ĩ–HŊsĸh@`čÛyÎtĩ=6wlŨXåögEÂ^Ëlœœd8õ˛x€læ)]gömKm­Ģ9uęKQĸQķNŠę���hį2CÁĻøŠ”ܐ)‰oČølƒ2ûĐáŨķ§Šw|ŗuŸˆ†ˆŒĘ/-ã†Ŋ¸a˜Oå'ˇŦŨązφIŲ/pbÛ4§į=ģ,Ũ Œ‰_8KDĘŦä/ļÎËQl;õ?Ņ|q×m[ĪYģę´6,qëŦPŽŋČZáĪ/K× cf.]"á4yGw~šüoSVÉ"b†ŒĨ‡^Zdŧ¸jĪ >Š3ļ­Ü°u‘r_ŧkßCŠËx{ōŦcIlÂĒD!cĐ+˛’ŋŪ:/GųŲŲw#šÍ7uXģ!wSü‹Ôŧ )ËĘmîŠfžä¯#â1Ö‹Ú됂mķöĒÅãfŧ1‘”éIÉé[ üôú6jÎŊöüĸŖ:aLüÂY2iä'“’WMŊ Ü“˛*Ŧu=Üá‹>Ûzāĩ­ŲĖČℌHėLWÛdoGqT(gīŠė|Ũ\sßi.dĒØlļ>'KIQsųš…F’Æ„r)Ŋķú֞6Î"]Î;‰¯5„ŊšīŖ1v ���°dj‡_-Œ7ũŨüšÆW*ŽÎ ™t¨Âüë˙-‘ $AKÎŪjÜâÖŲÄ'$§6ūŌ† ö\müyúÁ “ÉTķŨōHa@ôƜÆĒ+N,D¯+r*°Ģ{ã„ĄË2šÚŌĒđČEgš‚2]Ū;H"|.éĒÉd2™~X* „,ĪlÚâjR\€dāėSûXēubļT0ũ+ˡkN%’_Hše2™L™‹ž_8RaąËĻOZôSM‹đÖ^ņÕlŠ0 v]SķU_ šâo­u‡˜k záHĶ.—wFHÎ6\ķÃĒhá ¸O‹š:ŲTqjÆá8›ĩ˜ōW‡H"65vÃŽn5Ė?;ØņÖɅBeÔĮVsfáĀAs—-‰ØÔEīūU"ˇķrį÷­9ėxYîxųĐô �iė†\ ���ꕖ–ZūÚŽe “?N9‘´ŧņ œÁĀøđ‰TĘr‹­8Ãã#šŽMrCÆĘؤ–į¨œß 5ųÉ,- ĸ§4]Aäzsߥ[§œĖvágĩÄ{zJŒÅU˙ S$DōsMŸ1ķFÅ[\sõúsȨQģú�+†!"ĩŧ¨é|nøĘ¤ÃŸNļ~ÁĐví†ÂĖ\#‰ĸ'6]<ˇ(–įJTœá‰šîXķ˛É¨ŅjˆˆäGO•“(b8O§iDŌą26[ö’}Nvu›wä†F“>?ĢØüN~VĄQ2%FLŠŧ|sk s”$‹đˇRx×ô­Ŗē,h2ߜšú7v뾕Ą-.ķ��€}í|ƒŽ(哟ËWjĩzcãĢÍ?dH–ų—Īã•+ĩDžNnĐŠĻ´DK*ļ|Ÿņ•„7ũîL`ļ ×I$ÍĶ ž¯ˆCrĩRMT˙ 3OĐ,Įiįͤ¤ŗƒÎl=>k„<,2"&*"<24o§LÛĩk´*#ąÍ;GHĮ3ÛU‹Ąaš>ĒW—艊wũËîV{Š•ÚÆ^˛ËŲŽnûޞ(1m.ĢHęKŠĖ<­ 44PĘwg*h\éō˛Šˆ÷ÜH‰ĩŌģĻoÕÕĀ xŪ+Į5Ōæ%:���Đ&íIv ų[fNŪYĖHŸ_˛.:PĀeˆHšۊ´æ›ą[,fd"2 ÎoЊNg b3ļVI:˜ũÂ9-.Ąq†Č¨ŗUģ0ķ’NI’w9~æėÜĶˆØ‚ČéĢÖ.Õև˛t"b˜fņsš\vFÛ0>âi;–E´Œáú:šĻÔåŽvŧŖ(<ˇįåëĻû s”œāWŌ/ ´Y “e9!Q2įâlÔ5}ÛLņ‡¯DĘė|Õt˙N}0��€;jG˛kČŪą¯Ø(x~ßÁ5áųŽĄõSĨŒ-ŌVÁ@Äá2ÎoĐ —Ë%2ô:Ģ×ūœ Ŧm…;ŽĒ횷›ņšž!júŌ•äfŸIŲŋ÷ØîųĶt‡ū×ĸÎ`šūXhÚOgpîNãršDÄŖ"]OĀ\îj'v Œ å%eg* ÃĩYElņC$“rž((ÔeĘĩlYDÛ:–ēĒo›1re+6ĪPŽ_øå[¯ėūjēĩu���`S;ÖėĒ”*#ą%Ą–‹JK ĩ-ˇS+”–IFĨÖņDį7h…/ôį)Íö¤Ęúúpō™ƒĶŲ,ܡuá¤.Qę‰-´~tƒSsrÔ,­×h5FĢsũÃbŧ›ôŲ R{RŅÆĒø>›Œĩåšgƒ"¯Čz]ŽĒ‚ŧĸæ ģAזÄĪåŽvbG&,:˜­ÍĪRægEÁ|"b‚ÃÄT•SR˜Ŗ¤Ā‘m^˙Ú5}ÛLĐüLķÆæD1Éß_ŧEŅY-���¸Šv$ģ\—oW""2”$¯?ĸf7|ØÛ@ŸqätĶ 7Ēo‰BƒųÎoКtl$´i{O5î§ËØļrųę]šzÆéĀ 6–JH'FōHûíĄôĻŧÍ H>*'ļ,v¸‹÷1|—H­´x@jÉŠãyŦ!÷ũ1ÃF.>e™)šSõļ_KfÄaĸâ´3M™ :ugššž­ąJ:v$ôŲ;öYüĨĸËzëŲpéœcNßĨįrW;ą##cĢ œĖĶōdRķQ~¨Ô×X˜ž”UDâ˜ĐuŊoۉ ^ôÎ))v.{/é.��@´c?bb(;7ī“Åo3ŗ"9:ųšC)ÅÁk—†ŋ˛63o˙įé4*Ęü•°BŨ'“įΊ—ruÅ'“öy1‹Ļ6äpƒÖ˜đe+b˛–Ĩŋ>yfnB”ˆ”YÉ_giy#ˇĖ—‘ã|6gė\ģ]-ö K'aZ>6kŲÉW&fN+áTŲ‡’Ž+8Ao­œāōBÁc"xGŽũÎ&ŲĘ Œą$ķ“÷Îrd* 2qeŅáÜũ{_×ä&Œ•ņ2jrOī=VÎ‘Ž˜hõ*{Ī%ŽŪą0íÃĩ‰dŒ67e7"Œ–k{ģbžhõÄŦEGˇNžŦ|qb”€ŅČO&%gĒ}&Žuū>*—ģڙųá#EÆ-§Sėá‘Ōú×ü#‚yģSS˛‚„pW¸Ōˇƒ‘,øĮŌĖg˙ąwÅÚ¨o6ēúG��§=7¨ņŸû`§fõ?öĻŦ]’ÂH"f}°ov“3ķXŅžoß[MüoB""ÎØlá&}°ãd•–tĘĢWąĖdn`ĩōØĪžälßōÉĄSŸdę‰#zuõōy‘|į7ráōČâŗŽoVúLLh™Ûņc?ú†'ÛōÉĄ”M¯iÄņ Œ|qĮĸ…Ŗü]_ąË„­Øˇ–ŪÚyüĩįĮ'päÜ͟ŠO>?šĀ`$"b¤Ģ$‰ļ}r(k÷ú#z#ąy"qxâ–%‹b]Hɸ1÷ũƒŗ~gÚĢŗŋā‰ÃĮŧqxûŊaivÁĩß!VņŖ7yXļí“Cgw¯?Ļ'Ī_6aũ _ĩ%sšĢØŅ7,B`,VSHTĶ“ÂÄQöŅ,#/4Úî_S6šĐˇÆúæuŲcVmEÄŠOcņd���gt3™LWú™ŋËæŸŋõí‘Ų6nbr¸t Ũą™CWæDnÉÛ‹k‚ } ��p/)++ķķķküĩ]_*÷Ļ’”7gN}ųs‹;Û4gįFJ‘ĩú��āūŌÎ/•€{‘¯ˆŖĘ;žaÎ$åŒ8Ÿ­S¤í=|Á(˜°$ŪÉį߂Mč[��€û ,oĪr�� €IDAT’]7ÄČŪ8|@đŪļä3ÛÖ2˛Ų<Ađ˜›—MĮĩĮvCß��Ü_:wÍ.����@Wš]����xP Ų����ˇ…d����Ü’]����p[Hv���Ām!Ų����ˇ…d����Ü’]����p[íúĩŨ(~)cõ ãÍ;øf ÷õPˇnŊØ=?ZûįŪâģ ���@Û¸~eˇøfI™%ZCåĶ î5wLw´† éK7Kîv,����mãz˛ģ ķm™/čvë¨hāžÔ¨›‰îŧ”ųöŨŽ��� m\Ov¯˙Wƒ,÷RQũûŨ��� m\OvMõ˙ƒD7ŦW��€ûžÆ�����n É.����¸-$ģ����āļė���€ÛB˛ ����n É.����¸-$ģ����āļX_…Įčˆcīû˛7¯)~;õIarö.—´úŲ-ŌΉW\íˆ;Z÷á#–Äzŗ?m.–ŦlˆķŪŽ���ĀuA˛KDDœYš{FKDÄær†M<g×đîq§?/îĖ:yū›SxÉQš;ŗ+ÄūĶâē—lũöŖĶ•5üšÍŲē]�����˜uU˛kŧZ\QXjūšâbŪĮōÂB"8Ÿë;¯J1Οéŧâmã°{PMúšŠĢåDåWŌīF����@ÔuÉn ˇ2ęŋ}Ö3čÅĄs&õņ°Ųēę’ėo_Ŗ(ŦOƒę=xņBĄTäEēĘīwį¸ŋĸåęNŸųG†?ĨūéĩĒÆŪ O'¯éCDÂ+ģū÷Å÷u}ŖĨ‹>.y‘Ąúj^éžM…Ë[†´1~ŖPą&™;íe?īá*õoĮÖä&_¨%ĸ ÕĪn)֜{tņŌ>W×_yüļÕŊF&¯áŅœĶ“æ(~zGŪ´ŒĄīÉâĨ¤"/ļᏒsEn*QļĘüíÄcˇĶ<‡. ›ŸĐ§cŧšĨØžßsņ.ßī'Õ_GwĻ^����7Ņõ7¨yđzc°T÷ŸäsõYĢ÷„°¯r¯~œą :uNb~‰x𚍏w7oōÄæ­B:ģ8îß|˙ĻhéđĶ9ÍËãŒŲ1Öđ뚗K,ĘĘcYķˇV’î?īD]ōņÍî!›?˛ŗsĮŸ3ãĸœ×ãŽ`1›Z2ÜfK$sBÛ›264õ#š÷œÃ†sˆˆŒ†;lA˙i;f¤ī8WkĢĀĘcß&Ė-ŊAē”IGã&]Ž´Õ ŌāÍģ÷“įŋ›:'ņįĢŌ'ļlömŊíxėtšw°5sšWß˙vAėˇÛ/ôY°æOŊ˜ÛÚR/���€{čĒd×{ÎéIg“Î(&Ęzfe„1åÕüīĩõīUģ¸ 6ãÃãŋ_-˙ãĒüʁäĘĄJˆˆ<žš+ė%/ܲûš˛ôfáņ [ŪW]įuīŪTŦĮĐՑ DŋmL,,66¯ĐXû‡ņ6Ņ?´ĩ=‡Mŧ_ÁĪ[ŪŋĻ,¯š^ŦŪąöō‘0>Ōjëož|˙Ęu#‘ąæûUpáQ˙ŽĀøŨÅ÷ō›Wõļ 4ŪŠ4Ü&ē]ŠĢũÃxĮZųDä1la˙Į?o\{Ĩ¸üëōŌהVEJƈ­nl=۝Ö}؄>”õķGÉŋ_-×î˙~üĄŽÔ ���p˙ëĒe ē“/RMDÄf<‹8m×_ũ7}ģ2YODĩúÛŪqŌų{?&đ`3ŗ6Ņl""o‰øá§+ŽŪ)Ū˙ÃÆĻ2=DĶ#—ÄÖlŸ”Û˜7ÛĀ•ˆžvú÷ë/(+”†@‘´;kõžļ˛¤ą´rŨUŨÃbIwJģID¤žŠ(o{Ö⑊žvēĸņRt­üˇC‰Ô“ŠkœŒĮv§u—ˆčZrS§ÉĪũnŒëŪæz���Ü@W%쎝+o*ëoPŖbųĩŠŨątđ°c?|o|Hŧâé-“nŸYsqGžŽĘHŪ"vŧlŪđĄ ĩˇ­)øÚŗ ēž\‡ĩ{ôā’Qkqí×x§JGlvëu Dē‹5ÁwŒbŗŽ˙Í`l(ĸ-Z‡Ą~3bÎĖhöęŽQ̤Ķz<v:̓ÍP­ļļqŸZ]MuosŊ����nā.Ũ FWšÜĮDĨŪŖbš7ŽĨo9ūģų-o.›ČœāÕVˆÍŗ‘A*Sæ^ŧ:÷é×6J/Æå­oU_ŽŽy9l\úCkmާÅ2‰‡zpÉhlmˇĨ@ĢņčÚąŦ•ģt/Ū1j­=xØz<v:íļŅ@<Æ}<¸ž ËÚR/���€¸[ß æ/å˛ \Ķ’ųJdĨē!Md÷|*šKdž˜Ē“ßî'íŨx•øÅ§?lŧĄJ­Nŋp3ũ͋˙ä \ļĻ_w˛ĘÜžJEķrHÔGÄKŠ­%y‚Ūū> ? Ŋcn_U´ŪŦ-ZĄ“Ëo÷°ĢJõWë˙Ģ1Œ×­Ž€°NĶ—¨ŠŸ˜ÛĐ!IŖ{7dåmŠ���Ā tU˛Ë~LÜ'(¤OPHŸ AĖŌáoÎč~íØŋŋ×ŅMEņm˙¸Ã„žŪÂ>q[‡ö+Ž02ŨÅRĒũ~וk˛Á˖ ÄâžA†.yš7Éĩ×- Ö^ųčM;.lI´G‹*ĢtwˆËŅķ1ú~Wé5Ųāe/ö{ĖĮŗ¯X0c˙~ŠË)yÖn Ķuû†$HčéíĶ'îūUzvmĢjÛP ĩßī/Ŋ! zsŠ@äãé-ėŗúé=Į†ÅđŦmk=;öĮÅã•9xÁčž}yœ éÃĻIīTšP/���€čĒe ŨGŊ˙ô(ķã5õÍÂMßîŲ_QKDTsfÍEÉÖ 7O÷'öģ÷/n>įš@9)éišôŸ_¸°rŠqūËCˇĖõ"M兏3>Úßō:dåšÜNĮŽYöOyv†Åjd˙zRŋ+æŠ#“ÖūôÚˡ/ ÛūĒéū(ÉūųÍM ĨÕEęŌ=§=§íŠ•ŽRūļ}ŅÅī­]øüã‚ĶZS{áâk/į/ēeēW2^‘̎'ū”nõ6;ëņØí´ũŲ›…OÎŲôĖĒVœûyûû5k>îŨæz���Ü@7“Éäڞ’#Ŗ:6”{@ĐęgˇØøˆģÂÕxęÎĄ?žŗŖûčáɛhsdFFû—+(&iw����¨ŦŦĖĪΝņ×ģĩf:O߄§“ŗžY6Ą÷c>ŨE!ūK^}ԘUz s��ātˇžÆ�įzröÎĐ9K‡īáŗē?į.,Ût\���€–1@`���Ü㰌����Hv���Ām!Ų����ˇ…d����Ü’]����p[Hv���Āmĩ/ŲíÖAQĀ}ĀôÆ���î7Ž'ģ}Ŋxäę3zá~ÔįŪŨ��� m\Ov?Šxģ[ũĨ>¤ŧîÍDDŨ¨Ûö¨uw;���€ļq=Ų ę-NŽų¨ˇ§÷CXøëÖĸ‡z{öLõ‘¸§˙ŨŽ��� m\˙ē`����€{ ž.����Hv���Ām!Ų����ˇ…d����Ü’]����p[Hv���Ām!Ų����ˇ…d����Ü’]����p[Hv���Ām!Ų����ˇ…d����Ü’]����p[Hv���Ām!Ų����ˇ…d����Ü’]����pˇoßfąX–¯ Ų����7QUUåååeųĘC7oŪŧsįÎŨ ���� ũnßž]YYŠ×ëŊŊŊ-_īvũúuƒÁpûöíģ����@;=üđà ÃôîŨûá‡ļ|Ŋ›Édē[1����t*ŦŲ����ˇõ˙¤Ņi(ņ´7����IENDŽB`‚����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/reset-credentials-reset-recovery-codes.png��������������������������0000664�0000000�0000000�00000153570�14156463140�0027057�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��ŧ��Ļ��� •Úé���sBITÛáOā���tEXtSoftware�mate-screenshotȖđJ�� �IDATxœėŨe\[�đgXZ@R”” Qģûæ{õšØqíîÎĢ‚X ØMŠX`€‚tįÂÂÆûaŧëJė.°ē¨Ī˙Į‡™3į<gv–yöL,)44B!„DĄ Nđx<&“Éáp¸\Ž´B!„TÉd …Â`0H$RŖ>' <¯ĸĸ‚JĨĘÉÉ5U!„B?*Įfŗ+**ÍČüWÕÕÕ4N§cƀBũ„H$FŖŅh555øœ4°Ųlö­C!„P[DĨRëęę]ô9iĀëB!D"‘šJ ȍÎE!„Ē“„B‰“„B‰…*ēúOuuuee%›Í–v !„ZˆJĨ*((ČÉÉI;ī& â*--mßžũ„  qoCĄīQuuõû÷īÃÂÂōķķ•••ĨÎ÷“ąTUUĩoß~Á‚ø „ú~ÉÉÉYZZ𛛝XąĸŦŦ ŋ6^Ķ &“ˆBũ�Čdr@@�“É”v ßLÄÂápLMMĨB!É055Å ÔZ@b§'H¤<˙ ĶéŌ!„dČĘĘōxŧûČÅãIū™?ōöB!„aŌ€B!ą`Ō€B!ą`Ō€B!ą`Ō€B!ą`Ō€B!ą`Ō€B!ąāc¤Bũė¸<n-‹Åárš\É?Û 5Čd2…LĻËČÛÆ#%0i@!ôSãō¸L&“N§ˇc3—Į­ĢĢc2™ Ŗ-Ä&ũB!)Ēeąčt:Fo GåzČ$˛ ]†FŖÕ˛XŌŽ�“„B?96‡CŖŅ¤…04Ķ6Λ`Ō€Bč§ÆãņHĐĻĘL"ˇ‘‹-0i@!„X0i@!„X0i@!„X0i@!„X0i@!„X0i@!ÔĻņxŧđđđ4\wâÄ ‡ķíŖú9á!Ҏfbnmcc~æÔWĒÖ¯\ž{˙ކ†ē˜…īßšĄ­Ĩõ•âiá]hq›ĩ"’ĸ¯ũųî\ŧxņéͧÄkwwwūüøøøË—/Āŋ˙ū;|øđoORRRiii++QQQąĩĩ•H<ߎ4|–zuĮÖ­§“*%VaxÄ#3ËzŦlģõęũ÷ü…?fJŦ%1ėÜŊ÷Czúˇlņ›Š×5K ķŽžtz[yTKjjš‘™åØ “Å_Ĩ^$ÕÁļļe“ĩĩ5ņđĨĢW¯ōĮˆŒL&GßīÔĪ0ŌĀ.MMŠ}ų.+ˇ¨†ÅæP¨Ye-]=+;×Nš˛ß yG{'GūdYyųŗg/NŸ9w%ōęŋ§Žw23ũ1äįüŗ~ŖĨ…yGƒoĐܡ԰kĶB&O iÆē ė‚;ølôs2221bÄņãĮŲlöÕĢW@FFæŌĨK<L&2ÄŌŌō[Æķ3į(?zŌĀÎMŧņ ‹5=c]y*ØUE™™o’˛ŪŧLrčŽ÷ĩ7‡ģÛ¯ŗg֛šw߁Uk×­[ŋqīî_š}�€gΟƒV¤âîá‡ī Bâ ō†'Ny‰D"2†   oœ1üäÚôé‰âgWËŪŨ˙ŪŌ *_^{Å’7ķ>%dÄ�__////ßC'Nác$ĪÎKŧ|5ĩF’‹mėØŅ4-áņūœÂÂÂEĄË<ģ{™YvvtuŸ2mfŌŗĪG‹ÚÚÚ=ûö÷ķčlīlmįÔˇ˙Ā=ûö >UTČę§L2m&�ŒŸbdfŸØTT…sį-ėâŲŨ˛ŗC?˙€ƒ‡°ŲlbŅŦ_˙02ŗ,,*=nĸšĩŨĩë7Ä �nŪēãdnmįäę1wŪÂōōōz ¯á—ß˙42ŗŦĒbŽ^ģžkīNVļî]{î?x˜Įã5Õ5"Ԃ‚Bĸ†¤¤g!Ķg9¸¸™YvîÚÃû÷?įdff5Ú}‘YR„wJ° ’í ÅĖĖŦ†'ΌĖ,\Üø…EžŗVŪ‚=DČ^�YYŲ˙›;¯‹gw3ËÎ.n“CĻ'%=# 6ŌČĖ2//_°ļœÜ\#3Ëa#ĮˆĶz؇eÜÉ*;'G°Î’’RS ›Ā!Mž2oq­üŒü<ŒGŒAĄP€xđ3‰dmm-í¸~.Ri¸wī.—Ëå āršš@…ĄĀĐ5āņxPš×˛ĘŲŠ÷ogą)šŨ}­Tž\DU3÷Čšū´TOČ ŠšÜ—÷c“RŗJYl6ŧwīŪ–…Ņ8Ëå2ä䈊ĸââAƒ‡•——lffš““{ėøÉāáŖÜįęâ � -9> ŋßČÃH$Ōģ÷W¯]Ÿ••ŊdņB‘ĢΜ>MEY%<âÂŦĶŦ,-LML¨¨¸x@ā`fsĐ ē::ąqqËVŦ~ũúíę•Ë�€NŖĀōĢŠTęė™Ķ;tč NØņ ‰“§NWWW›=sēZģvąqņ“§Î “É‚ ¯8‘9}Ö/ôôļlZĪår7oŨžbÕ%%Å!A"ģöüÅËā‘cTT”ĮŖĄŽžņņãŅã'îŪģyIUĩŪN!b#KđN –”lųTUUW-_*8'9%åČąÆFFĤČ÷ĨĄ–í!Â÷ē뜜€ ĄÕ55#G 331ÉÍË;vâdđˆŅGpvrāß?ņņ“ĢŅ1cFä‡ �üÅiŊaĖÁC'$> ˜9}*ŋΨĢŅgpP@ŖoM@Ÿ‘ŸJEE?įņx••’ģ ‰GĘI—ËURR�éķ…đxųr:ú�ĀŽ­æqŲü¯_ÍÄNĘĒ™Ģmã˙;ÕŦŧŧš^ģæŨÕWŪÔ(Yy8ęÉgĻ´(†&íÚŗÃáxz|ē xĶæmšyyįū=ŲŲæSÖ0ĐߡŸ˙ĘÕk#ÂÎ�ĀĨ+‘övļ›6üC,1,xųĘÕŲ99‡BĄ_ŨŪÎöQl�8ØÛuīÖĩА6mŪ–——øĀŪŽž�0yâø‰SĻū{ö܄ņcÍLM¨T*�—”Úŋ‡˙MdØÛwîæršģwlŗíl�ÁC/ ]&8Ô!˛*… �**Ę˗.& ,_珇—īÕč˜!A"ģ–ôė™Š‰ņüŋįtqu!æhij†.[qņŌeÁ# AøFnjģĩ€đN –”lųäåÁCķ'ËĘĘvėŪŖĒĒÂīģČ÷Ĩ‘Nĩhž×mØ´Ĩ¨¸xįļ-ž>ŪÄęž>Ū}üŽZŗ.ėĖÉ~}}—._y5Z°§—¯DŌi´~}}ÅiŊaĖúô–._Y/i¸E§Ķũũú5ÚņÖt$ņųy$%%EDDg%H$‡Ã‰ŒŒ€.]ēH;´Ÿˆô“Čˆ<—ĮãrX•<.€-L 3sŲ@10ŅmÉʙ÷oŋŠQsé¨�Ã`´(�€Gąq›ļlãO–—W<Mzö4)É@_˙ˇ_g�ĮģeŪÉL[K‹?æLŖŌėíīŪģ_UŔ—gШÔŦėėÂĸ"u55ĸĀ‚ys‰âŦ.2Hw92R[K‹ŸĮ�Āâ…ķ'M¯ŽŽ˙Ŋ5AüãČvåädcãâõ;t ū†>vâds#ôųKž~‡˛˛˛9šb?1|ԈOŖĘuuu\.×ÄÄ�2ŗĀ˛‘ŋ†wJPŗ:Ø(÷۟s˛˛˛ØĢĢŖ-ŨŖZ°‡0rBö:s]]Mͧ÷įėŪÄØØÁŪ.!ņqIIŠZģvîn÷î?āŋe9ššOž&ųúx+))‰Ķ‹†1ËÉÉų÷÷;qętBâcâúåĸââGąq~ũú(**6ēõZĶee%I}F~xĪž=;ū<—Ë%Žc Ķé§NÂŧáۓrŌ@œ—"^ķ_pš—Ëãrˆ¤ĄĨ# •55�TY™–t1ķåë*P17¯ŠŠ�`ąj[�@\|B\|‚āĩvíĻO2iÂxe�(***))-))uõčÖpõėœlS“ß~ĩtųĒ^Ū}z{{uéâŌÕĶCKS“( Îę"ƒĖĪ/(--ŗö°ōŅīĐAŋCÁbF††ü×"ÛUTTdąXúúz‚ķųā͊\GG[pJe׹ŽŌ¨đķNŸ9›œōēĸĸ‚?“ÍnäQ0B6rCåååkÖmāOvÔן<i‚˜!ZĶ)Aâw°Q›ˇnŋuûÎ_ūîáū邆ÖėQÍÚC”•„ėuųų••ÖÖV‚K‰&ŋ˙đAUÕn@ŋ;wīÅÄ\>l(|>71 YŊŒ�‚‡>qęôš°p"iˆēÍårë�ņ ˙āˆė‚ŽŽļ¤>#?ļįΟ‰D $Žc>}ú´Tō†Ÿų9 mq¤Ëå�—Ëãr¸\đ Ĩ# T��6|ųŋŗæåŋ{n|ņuN×gZP§z›Ą˛¨ŒP”xrī§AB6› p¸EaĀė™ĶųwOÔÔÔôîÛŋ´´täˆaDÆ��•UU�`aaū×ŋ5\]ŗ}{�7f´™ŠéáŖĮŖĸcÂ#.�@îŨ–….ŌÕÕgu‘jX5�@§Ķ…STTāŋŲnQq �ČĐeįËČ|ž?rbšūYŋqįîŊ6ÖV įĪí §G§Ķßŧ}÷÷üƯQ˛‘Žb2Ožú—?éč`ßܤĄÅÔŦ6tķ֝-ÛvøúxO2‰?ŗ5{Tŗö’ŌRhz¯cV3€ŨŸŦŦ �0™Õ�āãã-ŗ(4ōj4‘4\žФ¤Ôŗg÷fõB0f�°ąļ˛´°¸|%jņÂų˛˛˛—¯Dikišģ5~@ūÁŲ…ęęĐgäĮ–ššĘápH$Ō Aƒll>ĘtęÔičĐĄ§OŸærš………Ōđį!ũ¤Ą‰‘—Ã.[^žĒZvųē‚Ŧ,@UeM ĀįĢŠĘŦĖ”?}ŸĢĘzUÕØĒÄĨĪjļũ< ˆ TTTÔĸꓕ•]4˙īéŗB—ŽØŊcë§@åå‰B.8��wˇ.în]jkkãâĪ_¸1jė„Ģ‘Å\]8 uu�(/¯Y’OdģUL&�°jY_ĖŦbŠ_C+ąXŦ‡Žhki8z˜?Š+øuŧĄĻ62qŜ m-­´7¯žFØâkAe|üøÛŸūŗf•ā|IŊ/"ë!ŌĻĻö:y†<�0ĢĢëÍ'ŌŅD¯ŨŖ¯]/++cVW?yš4,xņfĩĻC‡….]~ãÖmgGĮظøéS§^™(HøGdˆėAŠŸ‘īŁš\Ž‘‘QŊ¯ææææC† IMMíßŋ˙ˇŒį{!)ßrÉiL8—Ëåq84Zœl5…T×ĸēÕ;ęRō^g ųRõlŊ|˙cĢÛDÎ$++ �TMƒŽ :jkk7^˛ųz{{uīÖ5æÚõč˜kŸUWWUUIMMĢwĢUQqqÃÕét秇Ûē5ĢF–ž‘‘ü*šYĢ7…Á`´SU}—šZW÷yk§ĨŊ?rôø›ˇī]Edģęę4íãĮ/Nާŧ~-~ ­TPPČbąllŦĪûÆÆÅ‹\ąáF–H<×â@MMÍÔŗ9öŽí[øG&‚¤Ū‘õßë44ԕ••ßĨĻÖ;Aų65Î) đīĪápnÜē-xßD+{0 ŋŒŒĖåˑ—Ž\áņxAß7Ņú.Hũ3ōŊ ÎJØŲŲ5\diiéīī˙íCúiI?iLū;=ÁæqŲ\.ģϚTZĘ`sZxáēu'y`§ßŋßėĮ5Ë*+Ë�fļđ^OQ-˜G§ŅB—Ž Æ _Ÿ>ĩĩĩ{öā—)*.îÛ?`RČ4�xō4Š‹g÷°đÁJČd�Pi4‘Ģ�…B€šš/žĐÔĶÛģWii™`+›ļn ]ļĸļļÉë9„ˇKĨRėíŌ32ī)?z넸5ˆCH׈K8Zđ*99ė|4ønbldi‘Të™ŋ04%åõÚÕ+MŒ.mũû"f=Â÷ē>>Ū…1׎ v0)é™ģ[âŽ+�čŲŖ›ĸ‚ÂíÛwŖcŽéęč÷1ļ˛JJJ}|zßŧ}įlØy'GáĪQmMžÍg! ’ūé âÅ# lËåq8<.ˇšMné5 �ēŨÍŪ]y“tá_Ē_?ƒ/N\Öäžŧ}?• ŲÆ6^'c™W¯ŌK›ŧŲŊÅ ;Lš8~ĮŽ=ë6l ]8�~™=ãæ­Û;víÉĪ/puqÎËĪ?qōtiié¸1ŖĀÆÚJEYųī‹â--,H$xūüåŲ°p'GK s‘Ģ�qĶüŽ={?ff:;9 ^ĒÍ7{֌ë7o/Xŧ$9%EWG'6>ūÆÍۃX[5ų´5‘í†Lž?iĘ´!ƒUUTbããĢĢkįA$!]“••íÕŗû›ˇį/ íââōöŨģ#ĮNlZŋvōÔ7oŪžpé˛w¯žüÂ"7˛´Hǃ‚Â#.„G\°°0//+?ũīYÁEŨēyjkiĩū}!ˆŦGø^÷ëėY×oŪūũsĮedh˜™•uôØ yyƂŋįđ› Ķéž>ŊcŽß¨ŦŦ ™<Qđ’ÃÖô"xčāˆ‹—’“SV­X&ŧd+ģđ >#Iô“†ÆŽi`ˇNpšl[z÷�ČûûĀšëo/LRÖ5ĐT–•vMUiVf^(ʝ|ü|Í<ŧĖŌ¯ŧypâßBW+årI?§aÆ´đķŽ;1h€ŋ­mgu5ĩđŗ§ˇlÛqãæ­đˆ ÃÅŲiû–ļļ€JĨž:~tëö×o܌¸p‰FĨęęéūņÛ/cG"6šđÕĀģWĪ>žŊoŪēķūCúĘåKM´ĩ´ÂΜZŋqķå+QeååÚÚZķæū5~Ŧ°L"ÛíŅŊۖëˇīÜĩ˙āaE¯^=į˙ũWßūuĩubÖ ’đŽ­YĩbŲōUW¯Æ\ētÅÚÚjīŽíÎNŽŗfLÛŗīĀō•kÜ\]ų%Endi‘TĨĨŊ€ää”ŋ,ǎčāžŨÚZZ­_"ëž×ijļ?îôĻÍÛΜ +..QQVvss=szŊŅ‘ū~gà` ŗZĸ‹Ģ‹ŽļvqI‰_ŋ>ÂKļ˛ ßā3‚‘BCC‰WĨĨĨ B ­ˆÔ’3įáˑJÎ# }“Zf Ã&‘ČUUĻž­ĘŠ+ŗ^&&ŊHĪ,Ēdą9@ĨČËkiu4îdkeŦ"˜0¤^ŨqåZˇņÁļÄføō‰ĨĨ’š!Ôöeįäôđō:8ˆ˙�.ôã™<y˛ļļ�TTV(*4ōŽ6ĨAōx- ~eeĨŠJ#ímq¤Ãåđ¸šŦËårę€×Ú…*čZu×ĩYĖØwú,_iY-+¯V^��99Ų­Œ!ôYšj-�L?Vځ ļŸĶ OŸ>),,äršÉÉÉÄONđ~ÂZĩŽ 3•BĨSh4 …ŌŠĶ!Ô ŌĶīŪ{síúŊûfΜnhØQĘ!ÔÆH-i°ŗŗ—VĶ!Ô¨””7‹—,k§ĒúįīŋN ™,ípPõ=ŽHŠ”OO „PÛŅĮˇˇÔŸŲ…P[&åį4 „Bč{IB!„Ä‚IB!„Ä‚IB!„Ä‚IB!„Ä‚IBĄŸ‰DâA›~ —Įmę÷Ųŋą6B!$-T EČĪųļuuuLB!ŠŖËČÔÕÕąjYÜVüXÃWÂåqYĩŦēē:猌´c‡;!„úɑIdƒQËbUWWsšm+o “É2™Á`[ô̐‡IBĄŸ™D–••“v߁6‘š „B¨íä!„BbÁ¤!„BbÁ¤!„BbÁ¤!„BbÁ¤!„BbÁ¤!„Bb‘ØsxmīAZÄãĩéĮ’#„j.÷cšži@!„Xđ‰âz˙1GÚ! „BŌ„Iƒ¸ ;hK;„BHšđôB!„Ä‚IB!„Ä‚IB!„Ä‚IB!„Ä‚IB!„Ä‚IB!„Ä‚IB!„Ä‚IB!„ÄōÅÝĘĘʤGWYY)íBIRee%õšBĄP˙EŌ ĻĻöM‚ųū°X,i‡€BH’”••ņ¨×”ŌŌŌFįãé „B‰“„B‰“„B‰“„B‰“„B‰“„B‰“„B‰“„B‰“„B‰“é›õëFf–9ššŌä{5wŪB#3Ëé钭6ââ%ˇŽ=LĖ­W­ųGœō‚īãWzO‰j %[-úņ˜˜[2LÚQ UtÔRáūøß\Á9$ŠĒĒŖŖÃÄņcĨØ÷nįîŊ}ûøt40øzMTTTĖˇNŖũūëėÎ66_¯Ąfą´0///§ĶiŌ!ô“¤áĢst°wrt ^×°XiiīcŽ]ŽšļnÍĒĀAĨÛ÷(?ŋāŸõ--ĖŋjŌđūC:‹Å8}攝×JsM ™<-d˛´Ŗ@ũŧ0iøę<ÜŨ~=SpN\|ÂČ1ã—ŽXÕ߯/N—V`ߊgΟƒVˆŸ(“—g|ƒļBč{×4H‹ŗ“ģ[—ōōōä䔆K ….ķėîefŲŲŅÕ}Ę´™IĪž8L&%= ™>ËÁÅÍ˞s×Ūŋ˙9'33‹ŋ´ļļvĪžũũü:Û;[Û9õí?pĪžũ\.WüúëYĄx† idf™——/XaNnŽ‘™å°‘cZĪÄ)S§L› �ã'…™YÆ'$ō‘Éä]{öuëÕģ“•­Gˇ^[ˇīäņx-ëõØ “ƒGŒ€Ũ{÷™YÎ_JĖĪĘĘūßÜy]<ģ›Yvvpq›2=)限MĮ'dÅ.žŨûö˙bĀɧ¯ŋ‘™åÍ[wøs.\ēldfqAđš†_~˙ĶČ˞NJšzíúŽ=ŧ;YŲēwíš˙āaÁ^߸y; h¨…Ŋ‹[×%ËWÖÔÔ¸wíŲ`PSqŠS§-ŲŦž4lč]aQŅčqÍ­íŽ]ŋ!˛EBAAáÜy ģxvˇėėĐĪ?āāá#l6[œßúũŗaĖC‡2îd•“#XgIIŠŠ…MāáMmųwpķÖ˙€ sk;'Wšķ–——×Ģ_x/D~ÆâÑéPUQ€ęššzķ‹Š‹ V^^>bx°™™iNNîąã'ƒ‡:|pŸĢ‹3�<ņ2xäåņcĮh¨Ģg|üxôø‰ģ÷îGG^RUU€‹–œ Đßoäˆa$éÎŨûĢ׎ĪĘĘ^˛xĄ8õ7$ŧBáņ đøÉÕč˜1ŖGō+ŒŒŠ€€ū-ˆgæôi*Ę*áf͘feiajbÂ_´mûŽWÉ)ÇR(äÃGŽoÜŧÕĀ@@ŋ´ōËŦŽ.Îë6lōõņ4p@==�ČÎÉ Z]S3rÄ03“Üŧŧc'N}ôđᗧ_ŅĶÃ=,<ĸŦŦLYY� ‹ŠŪĨĻ2ŒØ¸øž=ē5<Š€Žî7nŪæWKŖŅ�`úŦ_:čémŲ´žËånŪē}ÅĒ5JJŠC‚ .>aĘ´íÚĩ›2YUUõJdÔŦ_˙¨ĒĒŌÔŌl*T‘u ߒÍęKÃÖé4�,_ąšJĨΞ9ŊC‡âŧwEÅÅ3̘ƒ ÔÕ҉‹[ļbõë×oW¯\&rãˇ~˙lsđÁ ‰ÃÂ#fNŸĘ¯3ęj4‡ÃĐčfoM� >!qōÔéęęjŗgNWk×.6.~ōÔd2Y°~áŊūGH& RPWW÷$) �ŒŒę-Ú´y[n^ŪšOvļą&æ ô÷íįŋrõڈ°3�ôė™Š‰ņüŋįtqu! hij†.[qņŌeâßĨ+‘övļ›6|ēāİāå+Wgįäp8 …"˛ū†„W(<ž~}}—._y5ZđŸōå+‘t­___qú[Ŋ-qÔq°ˇëŪ­ĢāĸééágO‡=w7ˇƒ_ŧt…HšÛŠƒŊ‡Ã€Ž>ŊŊ‰™6m)*.Ūšm‹¯Ī§9ž>Ū}üŽZŗ.ėĖÉF78+ڄÄĮ^ŊzĀŖGq Ĩ_߸ø~ ąąqæęęę‚ÕR)T�PQQ^žt11gųŌÅ=ŧ|¯FĮøm;vršÜ}ģwŊ1lččq+*+…„*˛Ná[˛Å}ųÔ:• �Å%%‡öīáķDžw›6oËËË?|`oWO�˜<qüÄ)S˙={nÂøąfĻ&Â7~ë÷Ά1ëwĐ[ē|eŊ¤árdN÷÷ë×čfoM�`ûÎŨ\.w÷Žmļm� xčāEĄËáDöBøgŧҘŅO OO|S,+åõ›ŲŋũņņcĻ˙~_üëäņxWĸĸĖ;™iki4*ÍÁŪūų‹—UUL�5bøÅķįˆ#t]]‹Å211€ĖŦOghTjVvvaQŋÚķæîØē™BĄˆSCB*Zģvînņ ‰üÕsrsŸ<MęŲŗģ’’RËâiʤ ㈌�Ŧ,-Čdr~~ž˜[U$s]]MͧˇĻ‰ąąƒŊŨͤ¤’’Ō¯čéî�üÃęÃØØNffn]\ŸŋxÁd2 ?ŋāũ‡ônžÖ4čķ—WũdeesrķˆÉ¸øD##Cūq‚BĄL2IœÎ6U§Č-ŲĘžH$� āg "[äņx—##ĩĩ´<†DQÚ�� �IDAT./œüČAuu5‘ŋõûgØåääüûû}HOOH|LĖ)*.~įëã­¨¨Ø°×­ė—ˍ‹×īЁČ Ö/˛Â?ã ‘†¯nËļ[ļí¨7ĶĢWĪ˖ԛYTTTRRZRRęęŅ­a=Ų9ŲÄh|øų §ĪœMNy]QQÁ_Ęfsˆŋũ:kéōUŊŧûôööęŌÅĨ̧‡–Ļfŗę¯GH…áņ čīwįîŊ˜˜ëÇ …ĪcŋZOS:vėČM"‘䌚–¤Z)((Ŧ¨Ŧ´ļļ"|F†† ‰ßø ĒjײėíĖ;™Åũ÷ŊđŅŖØîŨēš8;q8œĮO’<=ÜÆÆ@×&´::ڂ“4*•]Į€ōōōÚÚڎú‚KėEöTHâlÉÖô…`dhČ-˛E%EĨŌŌ2kKÁÍĢߥƒ~‡�Ÿ_ ō]“Čū)3�|âÔésaáÄmSQWŖš\.1TĶP~~Akē ŖŖÍbąôõõ— aŠĶ ‘Ÿq„ø0iøę\œøC÷d2YEEÅŲŅÁÂÂŧaÉĘĒ*�°°0˙ëß.Õlß�ūYŋqįîŊ6ÖV įĪí §G§Ķßŧ}÷÷üΧĮmfjzøčņ¨čârŗŨģ- ]¤ĢĢ#Nũ ŠPœx||ŧe…F^&ū)_žФ¤Ôŗgw1û+žĻž^ ‘V˜ÕL�`ČÉ՛/++�LfukVôôp?xø(“ÉŦ¨¨|˙!ũ¯?uuu´ĩ´ââã==ÜÅÆÉÉÉ95qŲ1<ŪPIi)�ČĘ~ŅŽ‚ŧŧāŠîĻ4U§8[˛5}!(**ˆß"ŅÍĻnAgãKd˙Œ�lŦ­,-,._‰ZŧpžŦŦėå+QÚZZîn] ˛†U͚.TW×�€ ]FpЌĖįIqz!ü3ސ Lžē.Ž.õnšlŠ‚ŧ<ņĸŪŲz>‹uāĐm-­Gķīü~OpwëâîÖĨļļ6.>ņü… aáŖÆN¸yQdũMiĒB—+2yų^=ēG_ģ^VVÆŦŽ~ō4iXđâōąĮĶ,iEž!�ĖęúÉqāQPhōÎLqVôôpßwāĐã'I…E…�āėė�NNÄ8llŧĢ‹3Öŧ:~âÆQžęęęÖ\/Ζ”l_DļHtŗŧŧūG€ ÎÆ˙JûįĐ!AĄK—߸uÛŲŅ16.~úÔ)MĨkęę­é‘=°jŋxŖOē‰Ų‹Ļ>ãÍŨņĐ¯ihCÔÕÕUUURSĶęŨ1UT\Lŧ(((dąX66ւĪˆ‹o´6:îéáļnÍĒ‘Ã‡Ĩgd$ŋJYŋp +3žūũ9΍[ˇ¯K§ŋ!‘V44ԕ••ßĨĻ Ū|�oSSĄÁčtsWtqvĸĶh ‰‰ř™š´SU�g'Į§O“>~ĖüžŪ­Ģ°ņü&ÚÕ ‘HYŲŲ‚3ŸŠwƒhSÄŲ’’í‹Č F;UÕwŠŠuuuüĨiiī=ūæí;1ßĩ¯ą č/##sųräĨ+Wx<^P`ã÷M´ž ęę4íãĮ,ÁĨ)¯_ķ_7Ģ ?ã"{Š~6˜4´-ũúôŠ­­Ũŗī�NQqqßū“BĻ€ēē�>•áUrrØųøīĢÆ“§I]<쇅GÖI&“�€JŖ‰Ŧŋ!ኌ‡ĐŗG7E…ÛˇīFĮ\ĶÕŅŧAąšņ��…B�âz1ĩ •†úøxÆ\ģΟķ*99)é™ģ[%%ĨÖŦ(++ëččđäiŌÃØXū- ÎNNĩuuû€ŽÍNč4Zg디שŠićŗkĪžæÖSČ-)ņžˆląˇw¯ŌŌ2Á]tĶÖmĄËVÔÖւxīšd÷O‚’’RŸŪ7oß9vŪÉŅAøĶK[Ķ*•ę`o—ž‘!øÜ…ŖĮNÖ/ŧ"˙i $OO´-ŋĖžqķÖíģöäį¸ē8įåįŸ8yē´´tܘŅ� ++ÛĢg÷7oĪ_ÚÅÅåíģwGŽØ´~íäŠ3nŪŧ}áŌåŨēĒ(+˙Ŋ`Q|bĸĨ…‰Οŋ<îäč`ia.˛ū†lŦ­„TH"‘„ĮãŨĢ'ƒÁ Ķéž>ŊcŽß¨ŦŦ ™<Qđ’ŽæÆ�Äíûģöėũ˜™éėä(xŅxËļǘ~=ëúÍÛŋ˙oî¸1ŖŒ 3ŗ˛Ž;!/ĪXđ÷œÖ¯čéáž}įn&“É?К™š(++Ÿ=ŽŖ­ml\˙Ö\qLž4aæė߯O=b¸‚‚Âų ;tĐkåhŗ8[R˛}ŲâėY3ŽßŧŊ`ņ’ä”]Øøø7o `me âm|ÉîŸ|ÁCG\ŧ”œœ˛jÅ2á%[Ų…Écãâ'M™6dp ĒŠJl||uuĸÂįË,„÷Bøg\ˎ ũ<0ih[ÔÕÔÂĪžŪ˛mĮ›ˇÂ#.0 g§í[6ÚÚv& ŦYĩbŲōUW¯Æ\ētÅÚÚjīŽíÎNŽŗfLÛŗīĀō•kÜ\]O?ēuûÎë7nF\¸DŖRuõt˙øí—ąŖG˙ EÖ_•J^ĄČx � đ÷;�ũ›Õ߆ŧ{õėãÛûæ­;ī?¤¯\žDœ¤Ą­4¤ŠŲūüšĶ›6o;s.Ŧ¸¸DEYŲÍÍuöĖé&ÆÆ­_ŅĶÃũŸõĀÅŲ‰˜C"‘œė¯ßŧåī×Wü õëãģzå˛Ũ{÷¯Û¸šŊ†FĀ@˙Ų3§_ŧt…"ÆĩMgKJļ/"[ÔÖŌ ?sjũÆÍ—¯D••—kkk͛û×øąŸębžkÜ?ųē¸ēčhk—”øõë#ŧd+ģĐŖ{ˇ-×oßšk˙ÁÊ ^ŊzÎ˙û¯žũęjëÄé…ČĪ8B‚HĄĄĄÄĢŌŌR UËaæž}ōümf^aq9ŗ–CĄĐe”T5õĖ,l, 5¯ķMģ˛íō{ŽĻįøĄļÂFxĨ-;;{÷îŨŌŽĄVЍŦ´upņęÕsīŽíŌŽåĮ—“ĶÃËwčā ū“˛P[ĸŖƒwˆ4ŽŠ”ā+Œ4°ōž\ģz?­‚¸OŸBg(2€Åd–äŊ/É{˙<VŨĻwŋ^F<ä!$Ag΅ ;?˙ī96ÖVĜsaá�āėä Õ¸~+W­€‰ãĮJ;„$IŌI'ī~XxB!(*fŽîNú â™bœōܡO<|’Uøür8Ëop_#üũ@„ž"Sã§O“&N™6jÄ0Íöí_žJ>yú_míaC‡H;´Ų‡ôôģ÷Ä\ģ~īūƒŲ3§v”r@I”d“NæĢ … kz rR|)EIËŧ[ ŽÖåŗ‘ion>´Ôõ2i˛"„P+ŲŲÚ;rpûÎŨG(+/WSk0đ÷_g ŋ×ĩRJʛÅK–ĩSUũķ÷_§…L–v8I˜D“fĘŖä �ŠŽ‡ī—Ÿĸ™ˇWîÍ4†ą…ę-S˜™OīŪšQPQËĄ0TĩôíŨ=m´Ķ VAr|ÂķôĖ’ f-‡Ba0Ô5Ml]-4ų…XÉw]Ë ›ûO뭚žøāŅķŒ‚ŠZ 0”Š™žx÷Sēĸ†uw[ƒŠģ{Ã^2ŋŧĀ‚Uø6!áÅĢ÷Ų€×4 ī“ŖÃÁ}¸ß~S}|{§Ŋy%í(úZ$™40ĶŪär�č†ÎM_˛ Ŗß­~ũ™TVzÔŲÛi ĄĢkŦÁ)ÎÍ*ĖJšVÂ>Ø^•(ÁJ9}>Ĩ( u]}=˜Ĩ™YīŸ\{˙.Ķdo}" R)�Nmś¨ë‘i-]}cÚÆjcžš|:2 †–žž*ƒSžwūdV/W žxˆ.3íꊨw UAM‚ !„úI2i(Č)ä�PôŒ´šûĶh‰”,Æyę×9p ī˙{6Ą0īɋ<ûޚ��…o§T�EŨcč`ū§$éėÉ{š)wėGz¨�?ÉÆÉ|p[Õ"hĸ§žLãĩqŌŪNcEĶc(˙JEjTxô&įs5�å/¯DŊĢā0ŒŧÛ+7ūW„Bčį!Á'B2Ģ*j€Ą¤Ôėkj–Ŋ˙Ë�€ĸncĄ�%ŸûĮ0íîįĶģ—ĀYŠĒš�”æ0ë×ÖëŋŒĄ‘Ú8™ÉiL�†Š›ũįڍ{öč(ÃŦ&7)1‹t#O E|p&B!$Á‘‡ �@mÁO°+Ģ1C^‰Aā°Y,��`¨ũW‚ÃbVÕ˛�Ø €ÃbqšS[yAa-�EĢã—"2ē–úô7)ĩ˙M—dfV�P´ÍôņzM„B$š4Đ)T��6‡#Ēd Åz÷_ūwâķNÉûGą‰¯Ķķ*jAĩ1‹Ģ�€ĄŦX/šĄhhĢBJŪ“Ėâr�ā?ŋ™ U••âô!„úI0iQRd�0™%,ДėˇsNaŌŲs÷rkŽnboϧŽD—ĄPØY ŅOōD¯^¯2�(”]§ČČ4(Yī*�ØėēúĨBĄŸŒ$/„Ôč¨NIÉāäĻĨŗĖ͚Î8,&Č0šsƒųúūÃÜZ`ųŒô3Eā@2å 4{`ƒBā|:™ōe`ė/JÉ�06ã{éBö—?1ŒBũ„$y…ŸŒžš€ķ>>ą°É#9'īū™#{N^}U"~Å%™š�†é—įĘsKš*ōō�ĀŦbÖ_ˇ¸ T`JIƒ�Ŧōōfü3B!ô“čm2F]lT� 0ņęõ4fc%*ŪÄ\}RŠeÉ4įŠtŸ~ÅBöËŅ NVŌk"ķh8h Œĸ†*€““Uøeu¯žˆYQOO€“ų&ŗ„B$œ4�EÃÕ×C“Pš|ųôé›/3?Mį”įĻÜø÷TäÛ  ¨;õéĒ׌ķĒĒ�Pņ!íķ�Ģ0ébL–ŧ&�˜å&(M‘Ņ3ŌĨ�TŧŠ{UΟÉLŊq+•M,§aë¨KNÆŊčäns@!„~H’ūÁ*ŠēSĐ ™¨ĢˇĶJs_Ü:÷â…Î`ČP8ˊONR4ôčãeßŧĮ?)v˛7|õž$1üx‘ž°Jr?d1ÛšęËxx$/ƒ™|ë<˜ŲxZŠWÃĖ­ËķŦûy1ĮŽŧ2ŌmGagŊĪÃŪŽĩ7îf|>iĄdåĶ;ķlĖģ´k§3Tö6'b„BčĮķ~›ĸnã7Ō8÷í“į)˛ Ë™ĖŠ …ލŽĨŲŅÂÎŪLŗY×@ĻžÃāáíØˇ™)Ī)tEuŨÎ~ũēŠR8nŨĶĘīĨ•fžvfžâGč4p0ãÁŨ„´ŧŦˇ)š -}ˇ [íŦ‹7ž,¨dę;RŨėyėĶ'īņBH„B?;Rhh(ņĒ´´TEEEĒÁHķEøŪ›ŲŖ~Ķü ë%6ŲŲŲģņĢBčĸŖŖ#í(Ú¨ĻR‚ŸōųČĖÂĖ´”įiõnņ`åd€’ĒJķ‡BBĄßO™4”§D_ž~#ęúũÜĪ7F°˛ŪOĢPˇ´P•bh­÷úÍ[#3ˆ…BÖĒŠŠYŋqsOī>–z÷íŋ{ī~6ûĶ=)g߁C>}ũ-;;xųöÛŊw?§‰‡~:¸¸mÛąKäĸšššÕk×{v÷ędeÛĩ‡÷ÎŨ{ųm ÆĶ­WoˇŽ=šÕ÷ļéGę Bč'÷Žihû´œŊŦĶ/ž(|ræČ]] …Už—“WQ uĮößwÎ�ôtO=$8'üü…)Ģ( YkÉō•×oÜZŗrš‰ąŅͤgsæ-`ąXŗgN€ ›ļė;pč÷_gÛŲvŽ‹OXģn™Dš<iBŗĸš?wN§NfÄëŋūž˙đaė˙ūü­ŖA|Bâē ›ØlöŦĶËoÚ˛=77OM­]ŗZi›~¤ž „~r?eŌ�2=ë=~ô$-77㠇CĄ3”tMœí\ėTŋ÷s Ŗ‹Ģ ˛´´,æúõĨ‹Ņi´ĻVárš.^ž>uJĪŨ� CŊģ÷îG\¸8{æôēēēÃGO7&dōD�puqNIy}éJds“† Ā�âEyyųí;÷/˜8h �¸8;Ŋz•#˜4¤ŧ~sčČŅ AoŨšÛŦVÚ Š/!ôs& � Ŗaęæoę&í0šáUrrEEĨĸĸ‚Ĩ……økmÚ˛ÕØČ¨ŋ__�<B^^ūđĪwŽŸR^^~öô GŖ}Ūddd€D� …r)âœĒĀå0::Ú/^ŊjĒ96›ŊlÅęđˆˆšVWOÕ+–ŠĒĒ�€ƒ‹Û„qcgNŸĒ¤¤””+¸ …JüeT.—;oÁâQ#†éčč9ĐæååĪ[¸čÁÃX%%ʼnãĮVTTFEĮÄD^�į.žĶĻNyûîŨÕč‡<$hĘä‰Ī_Ÿ(Ī`üöëŦÁƒ� ļļvũÆÍ/]),*j¯Ą0Đ˙×Ų3ŠTĒđEõXŲ:ū:k?…š;oáĢää ág…ôečđQ˛˛2GîįW2}VAAaؙ“í§O IMKģyķv“ŲÕĶcõĘeíTŋķą/„Đä§ŧĻá;´tųĒūƒ†Û`вĢÅ\+7/īäé3ŋ˚ILúûõ{ø(ļĸĸ‚˜Ŧ¨¨xđđŅ€ū~$ixđĐã'Oŋyû�žŋxuuİ`� “É ”•?Ú`ŗŲ÷î?prthĒÅ3gÃ8\ÎĄũ{˙YŊâáÃG /iĒdMMMAAá‰S§cŽŨ˜4a<ūņ“§sssûeļđŽũŊ`ŅËWÉ{wm?ŧo\|ÂĨˑdŌ§™JŖîÛĐÛĢWbėƒ9˙û}߁Cã'N™2ųIüÃ Ā€…‹—–••ĀÂÅK˙=6oî_1Q—ūüã×ÃG­^ģŽ¨AČĸfi´/ÁCßđ(//Ÿ˜d2™wîŪ@Dž{īū...qī^Ž{ųōÕ˛åĢZĐ.B}%˜4|^%':r”?yđđ‘WÉÉâŦ¸˙Ā!3SOO*}ûør8œˇn“1×np8ŋ~}�`ŪÜ˙ŲÛÚöņ`fŲy`āĀA'ŽÛ°ÂÖoü˜™5sú´†‹ęĄ įwļąöë×wô¨Ņ׎WWW7ZrÜÄ)ŽŨÖŽÛ¸fÕr˙ūũˆ™ųųëÖo\ŧpžŧ<ŖŅĩ………ˇīܝ1mǧ‡ģšy§MÖ—|ņ[&V–^={H$˙ū~�ā`oį`oG"‘üû÷cąXiī?”””†˜5cZŋžúúüĮ}ōô™Úē:!‹„„ÔPS}ņë×GA^ūÂÅKÄ䍛ˇy<žŋ_?~äAd2ŲČČpÄđāČĢŅLfŗxŠB_& ߁č˜ë"į4T]]}ōô™qcFķį´o¯áâė}˜ŒŧíîÖE]]�ÖmØôāQėæ ë"ÎũģvõŠsáįwíŲW¯Â5˙Ŧ?|äØÖM ;��›Í.˙?3pvrä—w°ˇcŗŲé /tŅ‚Ãöōŋ9ķŽ8IĖ\˛l…ŗŗŖ¯wŊÂõÚz˙!Įã99ØKäå=Ũŋ8ĶdddHŧPTP��c#ŖO%� ĸĸ"9%…ÃáØÛŲōWąąļĒŽŽūđ!]ČĸFģܔĻú"++ëßß/<â1uÕ×Į[QQ‘˜´˛úü\S3S“ÚÚZū˜BIŨO{MÃ÷ÄŌÂ\䜆îÜŊ_SSãÕ̧āĖū~}WŽū‡ÅbÕąŲwīŨ_ą4�˛˛ŗ÷ė;°aŨâŋ……9ŗŠšrõÚŅ#Gߒš\îü…‹/]‰<°oˇģ[ĸĒûŽŸBŧ4pŨšU�Ā?ø€œœ�4up5īdfŪÉŦ̧‡ŧŧüŠUkÆÆ%Üšw/ęŌ…†…ëĩåÛÛ�ääųTTŋx ūÅΈČČ|ņKí<¯˛˛�j——€ĒĒ*!‹írŖnŪēĶT_� xčā§N''§vŧuįîî[?ˇÅø<,!'Į�€ōōō†5 „T`ŌđđéííâėŸ@Lē8;ųôŽ˙ũĩĄk7nØÛŲĒ|y§e_ŸÅK–ßŊwŋēē�||ŧ ==ƒĮ㙚ķ‹č×ÖÕå俘@čŌWcŽ?r¨ŗ5ŋŒŊíéŸNšÃ�Ād~Nˆquã‹ÁųÜŧŧ‡c}z{ķí-,ĖY,VNN¨Ē*fw/b>Įãņx&æÖ æÍ (ØÖĮ€čĄŦ´LäD$7••Uü9ÄkEE…šššĻÖë2‰DŦ–X�„ôeܘŅ6ÖV–—#ŖŦŦ,U”•ųy�TU ļ[ �JĘÂî•EĄo “†īÊãG:R^^ޤ¤4aÜqVyø0ļ˙gĘųÔÚĩswsŊqëvEEe¯žŨ‰Ņ{mmm�HM}Īŋ/#5í=�hii@Xxęsa§OĖ�@IIIđd!!1‘˙úŲķtÍ@ŋƒ`‚‚Â?ūšģaŨš€ūĜ—/_‘H$]]?~ûEđŠČķ΅?zø@{ zmQ(d�HzöŦ“™)�TVUŨ{đPŗ}{q6 ÁÂܜBĄ$$>柆xüôŠĸ‚BGu5õĻQŠÔz]VP/˙īÂR�HyũšäŌbÎĐ!Aųđ!=pĐ@2ųķYÂØ¸xūëį/^ČÉÉéhk‰ß/„úĒ0iønˆ™+˜LfvNN‡ē ųõëˇmûÎōŠŠ5+—s ;tõôXģnƒ‚‚‚ąąaJĘëģö   /_SSŗnÃĻŨģ2™ĖGąqüJė}đÃĮĖĖm;v đ÷ËČøxü䊞}|eee ØX[yz¸/Yļ˛ĒĒĘÔÄäų‹—ģöė:$HVVVKVVKS“_RCCƒBĨiA=ú:XYZlßšÛÄØXYIiÍē ęâo�PQQ28pįî= ô---bcã;1eŌ*•*dQÃzlŦŦĸcŽO7V^A~ßūƒ%ĨĨDîĸĨŠ)ŧ/ú¯ZķOVvvtäEÁ ķōķ7mŲ8hāģwiĮŽŸô÷ëWīÜ BI& ?ĻŌ˛2øō ž>>Ū /‘“•íÕŗ;æļÍ6lŪ:įīų%ĨĨęęjüû˙ņÛl�H{˙>7//7:īę—Obīßixœf׹§O ÉĖĖ8”ÅbõėŪmÉâ ØšmËÆÍ[6oŨQVVĻĢŖ3i¸éS§4ˇƒ[6ޟ3oÁˆQcÛkļŸ1męķį/ž=ŪŦB-P—_ē´¨¨X[[kæôŠĶB&‹\TĪüysūš;ßŗ‡—˛’RđĐÁAƒîÜŊ/NëJJJn]\ĢĒĒ:Î2¸Ŧŧ< hhM ËĢWĪĐEķ›Õ)„úĒđW.łŋrŲJļŽŽS&M˜1-DRVWW×ÕÕ)))“#ĮŒWQVŪžu“¤ę˙ڊŠ‹ģ÷ōYģjyŋž}ø3ųĀ’b`ũ<đW.…h*%äH›Ã)+¯ds¸<O‚ÕļE%x{ •——?MzVQQĄŠŲŒkDš8eZaaҊeĄęęę7nŪzø(vßîŦ˙ë)--ûūa؊ÕĻ&Æ}|}¤B?¯ÚŠ5Ĩ?ÚsPHd*eč ų̜IXĨl§¨¸Œ.C——Ą×ģ¤üPU)+ējLôĩësį-tvrôãŽņmŲ¸nŲĘÕĶfĖfVWw4Đ˙gõJÁŗ-mŲ™saë6lrqvZŗjšā%ĄoŒŽ #Ģ,'í($Ž u”ę˛ Yeũ¯‘7HėôDQI™BĄĶ~Ė‹$rss:(í(BILȔA::ŌŽâë¨ŖÖÖqéōÚ-Ž Š”@b_tØl­ąËËB!ôMҏ<ëkT,ँđݔ@!„žG\—ũ5ęÅSĒ!„ & !„ & !„ & !„ & !„ & !„ & ?—I!͌Ė,˙æ/ ũ ÕÔÔtëÕÛ­k–•äp8ë7n62ŗ<pčHËppqÛļc—¤‚lYũ!ôƒ‘Öã˜ęʲRßĻæ”Uŗ¸@c(¨k™uŌSmäĮ–‘$UVVyõę9qüXū⧜%nĶ–íššyjjíZP2?ŋ`öoS(”¯›ĻB !•‘VNÂÛ *dĩ-œŨŨlt˜OoßzœS#p�XF>)NÛßVUUUGũ.Ž.ü?CÎo%åõ›CGŽ Ø˛’.Ēĩkwūėŋ”¯ųĶ â‰Bˆ …¤•ņôqVē­gOķŽēíÛké[:ôėaĶž.ûé‹îˇ¸e%eŌhˇy^%'ĮÆÅŋJNY˛ŗŊķŽ=ûū7wž“̇…ũ”i3‹KJˆE••U ŖŅĩØlöĻ-Ûŧ|û™[ÛõęŨ÷؉“âTØ—˝ˇ`ņ¨ÃLMMį;ēē8tdü¤s+ۊŠ !%û÷īˇ}ë&yųÆãdeë¸wßūäÜy  ,ØŖe+V;¸¸Yvv™>̤¤Td…EEüonĪîÄF8tä¨Č� 7/oü¤sk;Wn›ˇn_ģnƒ—O_áēztÛēũĶĪr™YÎüåw~….îŨöėÛ/NĶ!ô-}û¤Ą2#­ Nĩ“]G…/f3 ģvëe¯A�(x|åŌíÔjūÂÂ'QįoŊg�%E]š—Zúđú…‹ąuõ&€•˙6áfTԅˆ+—cî=ūP^÷ŠŽ’¤¨+÷RKr^<ŧuábTôũ—ų5�Āúp˙ʃ v]Fėųˆ›¯Ęc*z|åĘíˇOîũĒ}`�� �IDAT=ēī+n1,]žĒ˙Ā áŖÆö´lÅjᅩ4ęîŊû쏏Ä=ŧ{9"ėåËW˖¯"UU5™4ŦZŗnĪž3φD]Ž˜8aė˛ĢOŸ9'˛Â†ŽŸ<››ûÛ/ŗëͧŅh'O˙ÛÉĖėÄąÃrrrBJjki ī ˜Îœ ãp9‡öīũgõЇ-\ŧDdsūžŸøøÉ–ë#/žŸ2iųĘ5Ņ1×D6ôûŸs^ŋ~s`īŽãG… RiÂ+ôpsKH|L”‰‹×ÖԊOH &ßŋ˙PXXčéáŪú-€B’õ͓†ēŌŧ2PÖÖhxā’QT’} ›LĻp™^įŠ[uífĨMĢ7É-yņāA ŗĢwīžî‹Ÿ?ˆĪø”|)ܲˇ/sTmzõé3 ˇ:ķ}|rdô]ēY)Mץo?Oså/Ŗšeo“ Û;ēzxJ¤÷-ķ*9Yđ+īÁÃGDŽ7XYZÉd##ÃÃ#¯F3™L�¨ŦĒzöėų !ÃŦíœzz÷ųgũƚš�¨¨Ŧ<vâä”I ėh`0rø°Ā€ģvīYa=ųųëÖo\ŧp~Ãq‰$'+;÷¯?ėí¨Tǐ’’ĸĄĄēp~gkŋ~}G}ízuuĩđ Î˙ûčĄũ.ÎN††‡˛0īt÷Ūá­äæå=Š›6uŠģ[cãĨĄ ee>˙zSz¸ģ=yō”ËåĀŖØ¸ū~UUĖôŒ �ˆ‹Oh§Ējan.ÉmB’đ͓†šĘ 7õeWLLšŽŠ–Ǟ­Ūd]nōû*es[]UCNU߯NŸ–˙6?|ĀQîhŖĢ@�Y-}u™ē˛2&�™F#“(4­‘ÍĄl`Ĩ+'ŨŸâŠŽš.rN=VV–ü×fĻ&ĩĩĩyyų\.—FŖeįæNž8ūđŊ‡<|ôīų‹� 99ĨŽŽŽĢ§-WW—ôŒŒĒ*ϐ Ųlvųˆãņ’e+œ}}ŧĘÁŪŽ˙ZxÉF5lN8g'GÁĻŲlvzÆGáMË3ä>ÚĪ?ĀÕŖ›‹[××oŪ––• oúŨģ4�°´øtŒ'‘Hövļ"+twīRYUõúõ�ˆwqvļílŸ�qņ n$üũ7„PÛķíīž R�8­Ģ‚ĄŦÂht˛ĸ¨Œ+§§Æ?ņAn×^™üĄ¤¤”e�d•ø÷gPhdā°EF"ĢŦ$Ķēh[@2§y´LNŽ�åååd29)1–?ßŅÁžĮã­]ˇaŅ‚y••�0bô8ū‘ŠËã@AaŧŧASŪđpü¤bf⠁~}ûŪšw/ęŌ…ĻĸRTT$^ÜŧuGxÉFÕknŨš&O‘Ôk�ˆ"ÕÕÕBšŽĢĢ;a›ÍY´`žąą!•B2m†ČĻĢĒĒ�@Aáķé6yyy‘jkivŒO|ŦŅ^ãũûNŽöOž>O8(.>aöŦéâo„úfžyŌ Ë!s™e• ĢÔâ:Č4Jã“ė:.T§ŪŊ”úEqųē:�Y�€\ŒO“‘ūm >ŊŊ]œââ?ķvqvōé-â :q$#TVV€’˛rÃbæ� 77WQA�6Ž[ĶÉĖL°€Žļļ ;ž>ņéŧ‰ēēúŽ]ģĢǘŨŊ|ˆ9<Į㙘[/˜7gÜ˜Ņ‚Õ^‰Šŗ¤ {;[Áæ� Ū×qâT “Y-đš � ãÄŠĶM5mei™ōúÍŠãG\œˆĨEÅÅzzz6ÍĮ`éČį“5eååċ§IΚĒ�<ÜÜ?VWk׊“™ĸĸĸŗ“cčŌåŲ99YŲŲînMm„’ĸož4Д4áeNv™ĨRŊƒ+į]&E×°Ŋ\Ã#;WĖĄ * rú.Žf‚YR(2­8RĮĒ]čë;uüȁCGĘË˕””&Œ#˛|l\<˙õķ/ääät´ĩŌŌŪ¯]ŋņ÷_g›™š‹?yJ&“ ô9\.F+,*îglD,**.&“Čt:]H…222‚§�ūøí—IÆķ'ĪG\8vūčáí54ę…'~IAJJJ‚Í€‚‚|yE2åõk~Ā�˜Čũėų :f ßAHĶ/_%€ĒĒ ãdffuļąi´i>#CC�xų*ŲÎÖ�8ÎãĮOˆĢļŠ ĀÃŨm؊U*ĘĘDJaoo—žņņōåH##C~ކBmʡ?=Ą o¤ņæIÚãWšŨ-Uųųˇ"=ņIJY{eũörd SČPĮâg Õ•ląęVTkGūČŦŖ)(ūwJĄŽš rb4‘—0‹Ë™ *Vë_™8š_^~ūĻ-Û |÷.íØņ“ū~ũdddôôt_ŋ~3mæė?~ûEŗ}û¸ø„Ũ{÷O7†¸ÄdذĄ›ļlm§ĒjÛŲ&+;{éŠUÚZšû÷ėRaŊFĩ45ĩ45ų“*Ĩ“™)4 ŧ䋗¯ˆÁ .—žžū(6�ėílļhces}¸ąō ōûö,)-|VÕĮĖĖm;v đ÷ËČøxü䊞}|eeeĩde›jÚÂĸN?täØ/3gŧ~ķfíē žîiīßŠĢŠ5ĩŠuuuėíļíØĨ§ĢĢŽŽ.x ¨đ Ũē¸äæå]ģ~sūŧ9�  /oŪŠĶ‘c'ŧŧz6ÕBI—ž)Ŗoį\ôāŅۇ×Ę ŒõÕiPS˜ú!ģ‚ačnĢA�PPU&ŋĪĪ,15WĨՕĨžĖ¨!ƒŦ¨z€ĻeŦ/ķāÕãW4ĢŽĒ2\fAęŗg™2ŊŨô„^—@ĨQ Ž2?ŋLNĄĀø2Å 1͟ŋUhO¯my‡Ĩ!xČā˛ōō€ Ą55,¯^=CÍ�:~ôĐū6l\˛lEIIŠŽļöœ˙ũ>fÔHb•ĪQRT\Ŋv]~A†ēēˇWĪ?˙øMx…_ÉÂĐĨIIΈ×GŸ<zü$�ÜšŖ§§[¯äüysūš;ßŗ‡—˛’RđĐÁAƒîÜŊO,b׹§O ÉĖĖ8”ÅbõėŪmÉâÂÛUk×îŸÕ+׎ßŅŲÆúŸ5ĢōōōfũōûČŅã¯^vųÅĻõ˙Ėŋ0dÚLEEÅՕ•ÅÆ‹ŦPIIÉÚĘōŲķ.˙a8999zÜĪM „Ú*Rhh(ņĒ´´TEEĨÅå+*ˆ€[™ķæåÛŦ²ę:.™ÆPŌÔ51ī¤Ĩđߊ܊˧‰É™ušœĒžĩ ŧx”ߥW(ys?Oˇ›ˇ%qIDŊI�¨ÎõōeFa‹ 4ųvÚFVÖĒ´FJ–ŧ¸~;Oˇ§—š2�+įųŨ'é•CˇŪļŸOX%E=,Öw5ãž‹õæíÛoœoĖÁÅm¸ą3§OmŗūØ/Yö(6^xž’ē)ƒtt„ũŽÕ”UËǘ´xõĻRiũöYAÛÜUģÉ[�Ȋz=ô>Īč9āĶĒ6ŊûÛ|ž_o�äÚ[:ĩˇ„ę—Tĩö °ūôZFÛÆ[ÛĻá:��49]S7ŠzŽÎ „B?ü•K„B‰EZ# č+z÷°Wøc[˛xĄ´C@Ą¯â˙ėŨy\wū?đwfrrF9DņžZĢö´÷ļļö˛Įî÷ˇßīŪÛcŨíŊuģŨnˇĮÚk{īöÚÖļZm­'xRåT$9 df~1 úz>øƒLf>ķū|f’ŧ23IˆĘYŧ,Ô5��� 8=���ĸ 4���€( ��� B���ˆ‚Đ����ĸ 4���€(Á ‚ÄÆ���``˜AúJ… …™”moŋ" ãršžųķ_fÎYž•3kîÂW^[ëņˆûqQ˙ūđĮĮ/_rUŋɛ<íĨ—_=Ÿ•ž CĐ´Ys˙ō×ŋ…ē �&:Fđ‡*hID§U››­ ÉdR‰DŦfá‚ųõī.,ÜũĢ_ūobBÂŪ}û×<˙‚ĮãųŲC„ēŽ‹Á;ī}P\R˛æŲ§C]�\ôę`ÛZJ]ü`´´Đ eYC¤Îjs´:]ÂEwĸÂét…ē„ÁeŗŲļnÛņ‡G~ŨĩWŅäIųåå6nBhŠŌŌ˛P—��=ĩ;Ü.k[¨Ģ:)#S(uņfPNOŗQ)ËFéuAlpčā;BĘ+*ėv‡FŖÎĖČ<įÄ)Ķzāūí;vîÚģkĮüËŽxāūûŽ=úíÆMĮßtÃōûîŊûw?ļwßūp•ęūŗë¯ģVĢÕÜŋÛˇVĘJŲΟ*775=ũĖs; [ZŦ#ãâVÜ~ë+nīuÕ §ûđŖ…ģvk5š[ošÉ÷ŽISgöY†wNĮķø“Ī|ūÅ.—{ÖĖĪ<ų¸^qã-ˇ)•ŠwŪzŖĢÁUūŦąŅüŲĮž[F¯-Ņø “|`UÕąc?ü°ĩÕéœ5sÆ3O=Š×‹/O|/ēÜü“{öî#ĸĪ>˙âĢ/>1b„ŋÁlh8ũûG+(Ü­Õjî^y‡ŨîØ°qĶĻõ_yīeæÅ—^~īũm6Û´iSŸ{ö)CTTā=�kF+#F†ēŠaŸžūôÄĶËŽ^~Ëmw,ģzųãO>xf™Löáŋ˙“ž–öÁ{˙ “ʤ¯ŋņÖÂķ÷ī.øÍ¯ūīõ7ß^y÷}Ŧē÷ĮŊ…˯ģæŅ?üÉjĩv-ëršÍ|ôīMßmžįޕۉŋųŨÃû‹~|ņ¯Yŋîŋ÷¯ēቧžŨ¸éģ^Wũ‹_˙ļ˛ōțk_ũāŊˇ-Ëúo7uŨ%žŒ?ųŒãšˇßXûÜ3Oîzô$ĸ›n¸~gÁކ†ĶŪyœNįļí;Ž_~M¯eôڂˇ†×Öž1uōä=…Ûŋūâŗ˛˛ōĮŸxē_åõk0ŊÖžúqY™Ë–^ąo÷Îô´´�ƒųģG++¯Xûę?ūõÆÚ={÷}õõzFröúÕ7뛛›ßXûę Ν)*úņ…ŋŊx7��:„†a ŧĸâíwŪíēųÖŋŪ)¯¨0ŋD" S*ûë_äMȕJĨD”•™ą`Ū\‰Drå˛ĨD”7!7oBŽD"šrŲˇÛ}ėø‰Žeīŧûž)3f˙yÍ_Ÿ}ú‰+—-ņN|ôáßŊûö“'å'%%ŪxũōŒąéÛwœģŪú††‚Â]÷¯ēwú´ŠcRRV?öˆFî;ƒČ2ĸŖ Ģ}x|ö¸ĨKޏũļ[7~÷}[[ÛŌ%‹Õáá_Žë|ÛŊų‡­‚ \štI¯#Đk ]5,ŋî†a’““nŊåĻõßnt:ũ*Oü`zi4V*•Ëå‘z=˲ūĶl6oŨļũĄîŸ9cúØąé/<ŋĻŲbņmGĢŅŦ~ė‘ėqY‹/ŋlÁüy÷Úw�€Áƒ_š6núūÜ)ORäMČõŊ™œœäũGŖVQJr˛÷ĻZ­&"ģŨŪ5įęĮill,(ÜõĢßüŪfŗŨvë-DŽ åĩĩģvīnjnxĄÅjMLL$"ĮĶõŠ+“ÉŽ=FD9ãŗŊS$IÎøņeågķČ2&åOôíˆĮ㊎996=íĘeK?˙âË{īš‹ˆÖoøöōE 5MÂ´@DYY™]wĨĨŽioooh8””(žŧ>gëĩ¤.ūķø‰jAōķ&tļ>sú´ŖUĮ|:2Ąë˙¨¨ČÖ­�pa!4 ™cûœŌƒFŖņŊ)—Ë}o*Ũ>Šã{áęØô´ąéiŗfÎōé?_wÍÕ2™ėŽģîņx¸Įų}JJ’”•Ū÷ĀCŪ™wŽŧg•÷˙ëŽŊú˛ ˆHŠTvĩĻRŠP†oņŪW\īq‚›nŧūƒū]Qq())qËļí¯Ŋü÷skđ~BÁ_ DîSRX˜Šˆl6[ŋĘësļ^Kōęččđ7˜---Dîsl&BáÛ˛Ju6|H$’‹īrc�ú†E—-œ<)ß{1Mž”ŋ貅Á]E}CCaáîE—- ī|MÍČëvģëęę›-–C‡+?z˙É“ōŊw5575Šˆ&äæüûƒÎķ&ƒĄļļ–现Ųm(ÆélķųßIgÂGö¸ŦˌŒ¯×oČĘƌĐéĻO›zn [ ĸÖÖŗoĐiuAžzˇ×’ŧ,ö7˜ŪđŅÖvö’[kKĪË#��B ĄaxøčũwŪ|û›ÍĻÕjīēsEĐÛol4˙â×ŋ}~Íŗ×\uĨwJYYšD"1GÖÕ7‘ūĖģŪĸÔ֚Ægg‘VĢõ= —Ɉ¨ŧâĐÄŧ DÔŅŅąk÷^}DõĶžũûģū/.)•Ëd ņŖŊ7oŧaų[oŋsâDõu×^Í0Ėš5ôŲÂî={ģî*)- Ûß ëĩ$ģü fbb,.NOK%"Gk뎂˜#‚[�Āų@h6#+tÉ—5sÆô?>ūTkkkę˜1%Ĩe¯ūķõoXŽT*32ŌårųÛīŧ÷˙~úĐáĘĘ?¯y~æŒéĮŽ775õøČŸŅ8rBnÎ+¯ū31!>**ō­Ŋëũu˛ļöĨ—_ŊęĘĨ55'ß˙đŖ+_ŪuĘãšĢ–=ũėsĻS§6Ž_7°NŸ~áŗŽģöęŖGŊ÷ū‡W.]ŌãüÂ`Điĩåååqqąū3~ôčŦˌŧōژ”VûėšįŖŖ }7 �páĶĐ镗^\~íÕûûˡßy÷û|tĪ]wūá‘ßQTdäsĪ<ĩmûŽ9 ŊôōĢĪ=ûôŨ+ī¨=Yû“ÛWžÛČߞ_“œœtīĒīXyīȸ¸k¯šŠø~•áéđÜ{÷]fŗųęënŧN<ųx¤ë^­V;mę” š9‰ káĻގÚl×,ŋņ§˙īgĪžĩúą‡ûUŪĀÜšâļ†Ķ§o¸ų6“É`0_üë_bFŒ¸õļ;VŪģjÁüyS&MR(ä}6�pÁHV¯^íũ¯ĨĨ%ĸ˙G’/§Nzíĩ×B]PSsķœų‹ūüôKŽX<€Åķ&OģëÎ;~úāũA/,(ÚÚÚ:::´Z­÷æOVŦŒĐéūņ÷B[ĀÅjÕĒU#Gâ˝zį/āô --ÖÕ'ō™Ô1)‹/_ęrÅŨ÷=`67=ųøjƒÁ°ų‡-…ģvŋūÚ+Ą. �ā,œž€ááãO?ģéÖ*•ęÁ{ äÅįÅŋŽÉČHāĄ˙YzÕĩŸ~öųsĪ<5ۜP�pŽ4ĀđpīŨ+īŊģ—Ģ(úĨhOaPŠ$ƒáoΝ u��~]œīØ��� č���@„���%˜×4x8Îjsx8ūâûVü&Ë@ž��†Ŧ&‹‘)ûžoXaF*eĩšpvp.Zhđp\SŗUސ‡+ä‰$XÍ­Ž‹mĮ�¸ÄEéĩąŅ‘ĄŽ"Č8žoks››­†HŨ`䆠ĩhĩ9ä š\&Ŋø��°Ā2Œ:<,<Liŗˇö=w˙-4x<œLŠp��„XX˜ÂãáŖå …‡���BŽeŽīßīūˆ„OO�‘ÉtęÚëo›•ķæÛī ęŠWINË<÷¯ąŅ<̞<íĨ—_ n›‡nšíŽŒė “§Ī~âŠg:::č‚÷ � $pBˆˆūķɧGŽVŊķöÉI‰ƒēĸŅŖŒŧûļī”Ī˙ûeAá.]„.XĢxįŊŠKJÖ<ût°ôuĒŽî–Ûīœ7göģoŋqōdíę?=!•Ę~ûë_\€~�„BY­Vãȑ“'åöŠT*ÕÔ)“ģnļ´X7}˙ũŸūđ˜\& Ö*JK˂ÕÔš^ûįë ņŖŸ_ķŦD"ɟ˜í=Ōpú�r ÃFyE…ŨîĐhԙįœ4uæCŽ:uĒnŨ×ß´:Z'OÎú‰ĮŖŖ ×ßtkxxøŋŪ\Û5įĘ{VŲl6†aöũHDÉi™ŋúÅ˙^{ÍUŋ{øąÂ]ģu:í­7ßävģŋŨ¸éûëĪ]QCÃéß?úXAán­Vs÷Ę;ėvĮ†›6­˙*@ =ZxáÅŋ§$'/[z…÷ĻĮãyéåW×}ũÉtjd\Ü]+WÜvë-Ŋö1+gâĪöĐŊ÷ÜåŊųÛß?Z^QņåįŸÜü“{öî#ĸĪ>˙âĢ/>õ]dü„I>°ĒęØą~ØÚętΚ9㙧ÔëĪm<@ßnúîž{îîúˆĐĖĶz-oĀũ�ĘpMÃđđ§'ž^võō[nģcŲÕËō™Ā3KeŌ×Öž‘š:fûß}ģ~]IiŲ‹/ŊLDW.]R¸kˇŨn÷ÎfˇÛ w]ĩlé›k_ŊņúåÉÉIûvī\yĮí˙÷Ëß>\ųæÚWßį-ŗšéŗ˙~!•öūvųw<VV^ąöÕüëĩ{öîûęëõŒ„ \ƒ¯ú††˙ũņ˙ûŲOģĻ<ũėšžūæC÷¯ÚđõwßuĮãO>ķī?ĨūXûę?Æee.[zÅžŨ;ĶĶŌÎ–Š“'ī)ÜūõŸ•••?ūDī§0ü•ŅŌb=}ē12R˙ķ˙ûUŪäiĶfÍ}áŗ8Žį%ʃŅ/�€Ą�Ąa(¯¨xûwģnžõ¯wĘ+*/2&%å†å×IĨŌ¸ØØšŗg—”–Ņ‹/į8nķ–­Ūy6}ˇ™ã¸ĨKk4…BÎ2l¤^ßbĩîÚŊįûī›>mꘔ”?­~TŠčũ‹­ĖfķÖmÛzāū™3Ļ›ūÂķkš-–>kđõƛo§ĨŽézŗnw8ŪûāÃûîšëēk¯NLHøÉ-7_wÍU¯žļ–úCŖŅ°RŠ\.ÔëY–íqoVfÆōëŽa&99éÖ[nZ˙íF§ĶŲcž�e477Ņskūš––úökīģįŽW˙ųú_˙ö÷ Đ/�€Ą�Ąaظéû>§ô06ũė›lNkĩڈhĈčɓō7nüÎ;}ũˇ§O›j0t;epôč1"ĘĖëŊ)‘H&äæx˙÷x<ļ3ÚÚڎŸ¨!?o‚÷^uxøĖéŨŽÕ÷ZC—ļļļ˙ũņ+nīšRQq¨ŖŖcÖĖ]SĻL™\]SĶÚęėąęĀ} ++ŗë˙´Ô1ííí §{4 Œ‡ˆæÍķāũ÷ĪˇōŽw¯ŧã͡ßņ=ØĐ¯~ ¸#��!qá¯iāM{6ė­câ&/˜įsĐÛUŊcSĨfÚe9=ĪzÃŲ—đ�SzP*ģčú5eK¯xę™įÜnw‡Įŗ}ĮÎ'˙´ēĮ‚­­­D¤VĢģĻ„‡‡{˙ŲYP¸ōžUŪ˙¯ģöęË/[HDáęđŽ9#ôbjđÚļ}§ËåZ0^×ģÃADˇŪ~g×÷}đ‚@DæÆęęßUø“á*U×˙aa*"˛Ųl=úuÅâËũ•Ą'ĸq>É#âÄW^[[k2%ÄĮ _áá ë�@H„ęBHO]é!ķˆlCĪãĮЋE—-œ<)ß{}Mž”ŋ貅kjņå‹ūđĮ'ļīØŲÖæ"ĸE‹zļŖR…Q[ÛŲ7ÁV[į‚ š9˙ū ķ,‰Á`8yō$yÛ霺Å*ž’ī6ož›áķ‰DZMD]ķlkFÆÅEęõžĢ&ĸßVîršHo$ōr8D¤Õ钒}ollôW˲ršÜ÷, ĮyˆHæķ‰~õKLÍ��CGhBƒlÄHŊådÉą¤yŠęžįĸŪį͡߹ŲlZ­öŽ;W ¸¨ČČéĶĻlŪ˛Õnw˟7GŖî9ūÉIIDTV^‘›“CDĮũč=đ Õj'åOėš“e":X\œž–JDŽÖÖ…1#FˆŦ¤°p÷˛ĨK|§ddŒ•ËdæĻæ%)ÉŪ)MÍ͌„‘ËåršÜwÕD¤V‡ÛÎ\ŅID‡–Ëå]7ũũÎęî={ģū/)- ĢP(|7ĸü•ADŗfL߸éģīŋĪ{׎Ũ{t:]\lėĀúp„��†œiŸîÜ|¸ä„qZĸĒ×9ܧ””U™ínžUiãRĮg'jed9¸igsâÜΨáĒŪņmIs\ū˛Éą ‘åā†Â–ÔysRÂ|Úi*úfˇ=uR’ãđĄ:›‹“Šb’r'Œ1xßēšŽW7ۜŧLĄIÉĘIōŪã6-)¯n°ļuTĨĄúŗ&į“|-]˛äĨŧbŗÛŸ}ę‰sī5GæMČ}éåWGƒaíëoúk'~ôčŦˌŧōژ”VûėšįĪũDĨ?N§ķT]ŨčŅF߉ĩúæ›o|áÅŋGęõ9ãŗM§NũéɧãbcŪøg/_阝•ĩqĶ÷wŨyG¸:üõ7Ū˛´´tåV[^^Q^QÛķ­|ÃéĶ/ŧøŌu×^}ôčą÷Ū˙đĘĨK Ey—ņĶīŋá–Û~ķģGŽ_~íÁâ’wß˙đ˙~ū?]‡=Îŋ_��CYhBGŦ:1+éÄΊōzc~ė9Ÿįã-ĨĮŲ¤ŧ)SôJˇšr˙Á‚Ŋ˜éņē8Ŋ´ÚÜŌ‘Ē–ņ–FĢB!ŗ6Z)VODöϿްCXļX†ˇŠ0įLZ8!Œ\õļīÛũŖjŅä‘2r×ü¸ģĖiĖ›škP’ÛRĩŋho‘jÁŖŒ:NØSé4æÍĖ×É8—åXÉÎâæ 32ĀâE ũÃÔĘųķæô:à yîˇ?ē恟j4š[ošI§ĶîÚŊˇ×9_üë_~ķûGnŊíŽ1#zāū’’Ōâ’15´X­D¤ŅhzLäwŋŅj4ĪüyÍ鯯hƒaá‚yŋüÅ˙öÚÂÃŋ˙ͯûđĖš tZíM7^ŋüÚkļmßéŊëΡũ߯~sÃ͡ŊōŌßz,uĶ ×[mļk–ßčršĖW„ˇ%�� �IDATŸˇúą‡{m<@99ãßøį+^ķümwŦ‹ŠŒüÕ/ū÷žģî bŋ��†2ÉęÕĢŊ˙ĩ´´DDDœ9úÆfē÷ƒŨņĻ=ö3“ŽĘî8}đģBķ¨Yķ˛#™nBvœ*ØPä{æˆņæƒ?ė0į-Ģ2í^P9õōœ,™n:ĀŽŽ¨=­›1;UC5ģחĢĻ/Îîūnˇéā†ÂjMöå3ŧī(U;6–Ë&-žb”ņN§›Tj•÷#$mUÛž?¤™˛tB4Y}ˇÅ3kAvį/­ģ>ųŲįxp†ĩ?üņņ]ģ÷~û͗įŪÕÖÖÖŅŅĄÕjŊ7˛be„N÷ŋŋpa +oō´ģîŧã§ŪęB�`HXĩjÕȑ#C]Å`ŠolŽŽđâū"A(ŋR6blֈKš;ĻÛŠu{“•Õ5‰ĄcNX,.ŌéGč¸fĐ9N›)2;Á`=QkéHÕ°–Ķ֐Đë)uÚŽcĐ*FÆ7[dÔ12Æ]SQļŋŲáėāyžã:ˆT‘::FuôøūB>9!ÎeĐ)Ô:\{Ņ‹ģī{ĀlnzōņÕƒaķ[ wí~ũĩWB]�� –Đ~´"~ܘĒ-G‹kFO÷Ŋ~ÎĶÁS[Õö¯ĒēÍŪŅA¤‰ŠVU4[ܤhjvkãõa‘zEYS ¯h°đúô¨^ŋwBĻđ9Â0 ņGÄYJvgGįædÔ2–Ü5{ļu~e•=k†æhUõąâãĨFĨ‰ vß//ūuÍãO=ķĀC˙ãlkKLˆüō��€‹@¨{B“œ›TŊĩŧŦNuvĸTÆPXüä)ižoīYVĄ""íƒė¸ŲâV4ZUQ‘2Rˆ¤ƒMv§ĸŲĨečũێ;Ügođİ,‘ĩļÖĄH˜•æD‡Ī\¤Ô'ŽËOGöĻēce{*z~›áĨãxÔß]ƒáoΝšÅœĸ=…Ą.�`x ų7B2úôŦQtĒėhËŲiš¨HÆíėŠ5ęÎ?%ËČÂŧ‰ rDYkę­2}”šˆtŅ:ˇų´é´Ce0øšĻÂŲlëúÚ‡ÕÁ3ŠČÃķ¤P‰ŧõÔ)ODÄ;-u§<É4Qņã2ĸ$íÁī:��°ōĐ@$‹ÍåŦ9iæĪNI‰W4—•×؜.ˇŖšöāÎ~ØWë&""Æ0BīĒ?ÚĐĄĄ#"’éŖUļŖU™!Fįo Îę’#M—Ûi>^\eSč!#ŌFč[õąz‡Ëí0ß{Đa`xg‹Ĩƒ'ǴŨģ÷V5Zn§ĶvúØ1 õūû ���—ŽPŸž ""Ub֘Û*Ī~ cș>UVVv°ā¨ÛC˛đȸŦéãFu^Ė(‹ŠŅšO[ĸbôŪÄfĐËĘNpŖâüeRÅgŒrWlļ89™:&kjNŦŒˆ”ŖssZö–}w‚zcVNnœģŦe÷Ņí;iÎÜėé9e%Gl-uķŒTĨ1ÄeŽÔ���ú.üG./°Ļƒ ›SæžįWOÖ××ŋķö[ÁĒ ��Bš Ā_$§'���`8@h����Q†Ä5 ƒ)*gņ˛P×���p1Ā‘���Ą���DAh����Q���@„���Ą���D fh„ 6���Áņ<Ë ĘA 5*“˛íí}Ī���ƒŠ­Í-•˛ƒŅrĐBƒNĢnīčhoīpĀ�� 8žw8ÛZÛ\ZMø`´´o„”˛Ŧ!Rgĩ9ZŽ‹/78ŽP—���ÁÔdą12e¨Ģ2–a¤RÖŠ¤ĶÁüi)ËFéũū>õ°Æw 4��\TĸôÚķųČK>=���ĸ 4���€( ��� B���ˆ‚Đ����ĸ 4���€( ��� B���ˆ‚Đ����ĸ 4���€( ��� B���ˆ‚Đ����ĸ 4���€( ��� Š4ˆmy8Îjsx8^„ 6;4YlĄ.��‚ŠÉbcdĘPWd,ÃHĨŦVÎ2ƒrP hĄÁÃqMÍVšBސK$’`5;D´:”ĮOօē ��š(Ŋ66:2ÔUĮķmmnsŗÕŠŒÜ´Đ`ĩ9ä š\ĖCCJŌč¸P—���Ë0ęđ0"˛Ų[õ:MĐÛZ ņx8™ôĸM ���ÃEX˜ÂãáŖå …čĸ;)��0ü° Ãņü`´ŒOO���€( ��� B���ˆ‚Đ����ĸ 4���€( ��� B���ˆǝcę°šĒŽTÕ5ZÛÜ<ÉTjC\rZú(ŊėüZuž*ÚS\kåuãr#Ž5§Ė—ĒNŊ���—ŧ„wŨž{Mš¸ÄŒ”(•Œs™OU8°ĩîô”Yyqįņë!Ö‡kœēŦŲFëVæŽP+‚W3��ĀĨ.ĄÁ]s ČÔaș9=ņĖa€ąņ‰Q[J”ŽŽÉđ)“Ž)ĸã j‘Ę8*Hõ���Q(BƒŖæXc‡>+7ąû‰UÂÄYzRi;×VWQ|¨ļÉî&VĄ6ÄgdgF̈ˆ,7ÚS§Ĩ´*Ģĩ:;H9:wbÖĨûÄļM,Dtčģ/éÆåEž==ŅtpÃn{ꤸĶÅefuîâ4Ë÷…öÔ)ņļŠCu6§Đ%eM#Ģ).;jvt0ę‘csķĩŨƒ‹åHÁŽ 28���C×ŋ˛ŖĨÁJē8očFĄŅ*ØÎ™NėŽácōf.ŧ|Ū˜(׉ŨĨī÷h3,o=RV§ĪžŋxņU—åœĮ÷V4ō¤ˆŸqŲôxŠ“æ,š|Nĸī‰ †ayį‰Ã †ŦYŗŗâdİŧõXĨÕ8iá’%—įéUļî:ΤOģbÉâų)ĖŠ’˛ZWĪÚ$Ŧ0(Ŗ��0|\đĐār¸ˆQŠÎÍ žķ4­mͧįfŽĐĒ”aē¸ŦÜÄpGÍņ†3?ŲÅéŗj†ˆ”ąņE‡Õę$bXÃą ™Lư=›tƌšŠązZÖŲBÂØ †H1"VO2¤¤čdDŒ:6VÅ;,Ž w��`Øģđ§'¤,Q?Øé°Xų°Qú°Ž ƒŽ9bĩ8)NCD¤Ôhģ>fÁĘâúūP•.Â7§¨Ôé)ːJ{æN–•Į Ęoƒ� EU'jŪ|÷#GĢ“œßET èÃUwŨ~sJb|¨kš$\đ# J•‚áÖ€īå;::ˆQú|ü’ad2âšŽŽ›ũ^-#;įāƒKDÄāû*�ā’Tuĸ慗_ˇŲÃ11Īķ6›ũ…—_¯:QęZ. üÅRĻÖŖî”õœ{ÜuGĢNˇņD$“ɈwuœŊ‹īčč ™ō<ŋÅ��ē{㝏B]Ây“HH.†Ž ūļ:>9Zæ8VTnņĩŧŊz˙‡*kéô:Ļ­ŲŌÖu¯ŊÉĘËtē€B��@ŲÅ5\IĢĶę". !øžE|î¤Ļ‚]G ŋŗ&¤ÄGidä2ŸĒ:qĘŽJšž-#"YLZ|؎ÃŽh˛ZļÃrŦčx›.%Éāį ��\â†éé•a'$ߊ1aÎüØĘ˛#ĻCEĮ;xFĻŌÆ$æOJUwÆ™aÜô)lqYŅļ27ÉTژąS˛Sĩ¸î��� „$ĢW¯öū×ŌŌ1ā†ę›5ę‹öüA}}ũ;oŋę*��‚ėgŋ~,Ô%Íß˙ü§~ÍŋjÕĒ‘#GR1!Wߨ9āÅũEŧ{���Q���@„���Ą���DAh����Q���@”|O�� K“ōrĻOžh˲ŦÅb=PRļy[ŗ­ķ |ŸYũÛļ~ûũÖĐ ƒĄ��DYqķōü ã,.Ũūé^Į“?jöŒ)Ə{áÕ7ėö‹â먥/ ��СŠų&åå|øé—ģ÷y§—UėŪ˙ã/ļjŲĸų~úeh˃ #˜ĄAH" b{��0TĖ™9ĩúdmWbđj8mūÛ+o64šĪ_ʲË/œ˜›­Q‡ÛėŽ=EŋŲ¸Ųû )I W.^82.†‘0Ļēú/×oĒ:^MD Ã,^0'/7;RŅŌbŨŧŊ`GáŪ Ķģ‹ Įķ,3(×,-4Ȥl{{‡BŸ¯�¸Ø(• c\ėÆÍÛÎŊĢöT]¯‹ÜxŨ•9Y˙ū|]M­)1~ôM×])—I?[ˇA.“Ũŋōļ}Š?üô IfΘōā=+yâšļ6×5K/Ÿ15˙ߟ­;vĸfljĘōĢ—pŽpoŅ wî"ÔÖæ–Jå7ƒtZĩšŲ*‘L&•ā€�ĀED§ŅH$sŗEäü*Uؔ‰šŸõmŅÁR"27YbGDĪ›5í‹o6éõ:ĨRąˇč`Ãi3}ōÅ7EK=NŠP˚>yĶæm{ö ĸMÍŖGŧlŪl„†~áxžÍånms"uƒŅ~ĐBƒ”e ‘:ĢÍŅęt ‚Ŧf‡§Ķę��BF ˆ8Ž9˙¨¸X†aNԜėšRSk’Ëå҆¨†Ķ æ;oŊa{ážŠĘŖĩĻēŖĮNQ|R‚”e+*v-r¤ęøôÉåry{{{0;sF“ÅÆČ”ƒŅrą #•˛†HŨP?=ADR–ŌJ´ 9žĄ�.]V›]„†(‘ķ+• "ršÜ]S\îv"R*ä‚ ŧđōë įΚ>9˙Ē+.ŗ´X×mønoŅAī"˙s˙]tæm§÷ ĩVŖ675ˇ;^QzíųüäĨ Ÿž��€>¸Ũí'MuSō'|ûũVO÷ã šŲ™WZqØwb›ËMgĸƒ—RĄ ĸ6—‹ˆ­Î˙~ũíŋū6vDôü93VÜŧŧžĄŅ›0Ūųđ“Su žMĩ´X­[ĐoøFH��čÛÛ ôēŗÍķ3â–ë¯ÎÎÛcfĶŠzžį“ãģĻ$%ŒnsšÍÍQúˆėĖÎųëO7~ôé—<ĪĮŎ0Ē÷x<juxCŖŲû×ętÚ[[=ĸΉĀ€# ��С}?§Ļ$]>öhcÜū%îööxãČŲĶ§ÔŸ>ũųWzĖėlk+Ü[´hūėFssíŠēԔ¤Ų3Ļ|ŋeĪķz}Ä=+nū⛍Ĩ‡&åå‚pŧú¤ËíŪš{ßŌEķ[[Õ5ĩ‘úˆåW-iąÚ^}ëŊôz…Đ���ĸ|øÉ‡T͚6ųúĢ—° cnļ|ģyë֝ģ;::Νųã˙~írģoēîJ:ÜŌbŨđŨ–M?l'ĸŖĮNŧ÷ŸĪĖžąôō<Į×5œ^û¯ÍMDôŲē Ū^j5j›ŨQR~hŨúī.t'! „��Ģč`Š÷S”Ŋúíęgēūį8îŋ_}û߯ž=wļŊE÷<w:Īķ_oÜüõÆÍA)Ži����Q���@„���Ą���DAh����Q���@„���Ą���DAh��€aĪû“˜0Ø��.]ĩē맨‡1AP‡‡‡ēˆKžF�āŌu÷Š›_xųõPWqŪ$’ģWÜ<Hm×´ĐĮĨÔÚNü ­ ¨ĸp9Ũ0Žâ#Ģ}��¸DĨ$Æ˙üÁ{4jõ0=ŧ/‘H4jõĪŧ'Åįg¸ƒ¨Ļ…Ū*"û0I DÄŲŨôVÕ´ Jû8Ō��pIKIŒęą_‡ēŠ!ę?e4üNŪHHč?eôËÁoG���z×ęu#!įāTŽĐ���Đģáw˜áŒA:Ÿ‚Đ����ĸ 4���€( ��� B���ˆ‚Đ����ĸ 4���€( ��� ž�� ÔzZ˜DcÔ¤–’ĮCfí;JûlĄ.+¨���Η2ŠîÉ!W}uœZÜ$ Ŗq ´,”ûh‡#ÔÅBƒX<Ī3 Îæ��\ x>Č_™˜8’"ôBuūP”“j-$ͧQ:"„†KL&+//7n\¨ �€ (//—ËåAlPÉõxMåéĢ=>7å43ōõ!%W;=I_U“‹(?ŸwКƒä:3cn]#ŖöQ ҘZ8š rō¸čP5m0-$đÖY•JĩnŨ:Až_C��Aøâ‹/T*UÛ<a&šnÎĸąęŪߎįfĐÂpÚr€^( ÷ŽPl]CDt¨ŽHOcē–‘Ō8=¨ŖĸQŠt[ÕĄ— čŊã4*•Ž bÉÁΝ;×ûŸËåR*•!-fč’Ëå ÅÅÅQQQF*Å�€áĮårUVVŽ]ģļĄĄA§Ķõ9˙–ãĸ[ļS OiFš<Šf'ĐXE0Ôė$י7›-V:POĮZÉå![+1ʕŌ3ĩģ)y4EļRi+‘2š–DSÁa:%ĄĢÆQûqúĀD.ŲtZAķcéP­ØĶs“ÄßKwüDŧø‰e0Ėfķ‹/žô3a��pÁ°,ô–OTĶK')VOc 4FO33hf }u€ö9ˆˆ\<åĻĐ5:А’”!Š”¨ˆˆÚŠÔF G˛\DcFYčP;‘–b*ĩœmŋļ™<F%§úö ×.BC?h4Fę*��`¨âŠž‰ę›h‘RK׏§Åit¨ˆ -ÎŖ|žž:L'ZÉC”;žæžYčP-NĨD†14.ŠN&ÉHJ45ŸĻv_ƒRJ„Đ���0|IĨ¤äÉás$ÚeŖ-tO4ˆj§ĸûčĀ™¯mPúŧü:š¨6Æé鄔‰žj$"ĸō(Ą­>ĢáÉŌ+!q!$��ĀųQŌŗéļŅ='”Díg´x:§KÕ4Våķ ÜN,”8‚ƈšč¨w6'Õō!%ŗķĖ_;y<øô��°æĸīLdHĄ{ŌiœžbՔE ŗiYĒ&3ŲŠž§ÜŅd“AO׏Ĩú’†Ņ¨3ĮŽž&e͍ĸŖõgb‡vÕҍZMr2hiYŨŸEęPõ‘ˆpz��āü8LoÛhĻ‘–Å‘’!‡ĖVúîGÚåŊ’ąž:L×§ĐOä°Ņw‡č‚bŗéÎ<z}Õ9éD:áiCSˇ6?ōĐÂtš*'ōPm#Ŋw$Äß…Đ���ĩuôQß{ÍuôĒīŊzi‹ĪMŊˇ…Îu´ŠŽV§ŧ Āé ���Ą���DAh����Q���@„���Ą���DAh����Q���@„���Ą�� w’P0`Ėā”ŽĐ���Đģp‘ę"@ •|PFh���čŨY$†G$ē1kPZFh���č]|­Ė#ĩb°Žö#!ĩ‚VæQ|Ä ´_š��đ+>‚~9#ÔE 8Ō����ĸ 4���€( ��� B���ˆ‚Đ����ĸ 4���€( ��� B���ˆ‚Đ����ĸ 4���€( ��� B���ˆ‚Đ����ĸ 4���€( ��� Š4X Ųíö`5���įIŖŅŊÍ …†Á(���†œž����Q���@„���Ą���DAh����Q���@„���Ą���DAh����Q���@„���Ą���DAh����Q���@„���EÄļAp:Įņ<Äf��@ †aX–UŠT‰d0ÚZhÁnˇKĨŌ°°°AĒ���ÁãņØívF3¯ÅA;=ŅÖÖ&“Éär9��@HH$™L&“É\.×`´´Đāņxd2Y°Z��€‘JĨƒŅrĐBŽc��� $É Ŋ(ãĶ��� B���ˆ‚Đ����ĸ 4���€( ��� B���ˆ‚Đ����ĸ 4���€( ��� B���ˆ‚Đ����ĸ 4���€( ��� B���ˆ‚Đ����ĸ 4���€( ��� B���ˆ‚Đ����ĸ 4���€(ŌPĐoåeŌΚēM‘ĘŖ^˜ŸÂ‡_oēŠ>"}×Î?ĮGĄ1É7ģX“‘ģw´ŒŲDi8ÎŽ­Ĩ[fq)Yã� fņ@AŪā"4<_f•Âué|ø™[­fg5ķnŨ›;(OvŽ:öu˙ķôađĘäSǐ“§„ Ášģv~Ų>�\œ†gh 1:ál>ĐqÉröĨ fˇ_ĸ ūÚlOđ[žĨŖųP–âŸoaįYđí#�ĀEix††s(ÃIZŨg§Td7›$Mí$U iŖųEq‚’ˆˆVfãq昝\D:01‰›Ž ´HQąô›"bŸhĻįqķÕ>kå%‡Ģ™m ’ĻvRĒ…é)Üdy™7æŌ›ŲÍ-’iÜxŠßb¨]˛ų0SÔ"ņȄ#ŸÕŊS~–ęl˛‹ŲÜ(ąvP¸Ž_2–O‘÷(•w•úēo—aö[%V)åB˛‘_2úL > N?ų-Ō÷œÂyėszÂÂŽ)‘¸ēŨ+ĖĪįĻĢŒd/‹ÚjŊow"^rø8ŗ­QŌÔNRšÃ/Jt$):ĀndųŸgķ]+-.‘~Éņ?Ëåu}meo=™ZI9Ã˙4›īÚûĘˤ_vđäōŨļO˙ #ī8÷€K×E<nI+‘QŪyĶTÅ~xŠōÆr7jČaežŦd?g¸[bōH֗1-ŅÜícIɓÉÄ|YĖǧpãå~ŸÉšŠŲ‚0îūTAŲ}´ĒĢØĪ›„Ųé\ēŒL&æ›b–&r“U$e¨ĨŽŠŠánOĸ(Š˙bˆŠą.áĒ.YN&ŗš‰¨Ī.Ij8ÉNáīM¤í’/°Ÿ~žŪŖTÉFŸR‹ŗ›ŨÂUã¸99ėĖ—‡ØuJĪ Ņ=Ņīā €ŋ"}‡đ| öĨãî,éüŸ§ũ‡Ø=$¤(ûÉ‹Üjūļ{Õöã&azwc8šė’oŗđÜŊ)BZŒ°ņ¨ä˜‡2Ĩã\ÖB cx]_[šĢuS~HRŲNyō3-XɘÂ÷Htũ-L:Đ=�.Yç'6æË*‰CÅįhˆˆČ#ŲvJ“Č/‰tJÁÃ-‰ĨĒ“’"rIĖJˆb”‚N%dĻr+sødi E¤R’2D ŠĨŨ–‡)¨'c7]/DŠ…ņŠü|9Îę°J…%ŖŖZP(ĻŲßB) Üx­ V é)|z× ,åŊ?\X-H‰H.äčČÕ*ą(•(-•{`7^+蔂1šŸ¨Ąęf õāopĒ×"}oÁžŌ)īŸĢ‰Ųãeō1Lß#éKÔVķˇŨۙ=§É˜ĀĪ× :šÅ/‰š˜*žÔQ‚‘$e–Îĩ¸,L5 YQ}×ÖU.ŠOc%;GĀeeĒI˜Øãúūv>{ �\š†į‘ķĘļnqGÁߐÆŊ͜’2ugß#iÄÔN1*!EIE‡X‘O× jŠŅ DD˙‹ø{ŸŨJ ev]É“3¸ŗõ„Ÿ9ü ˜6‰•ČØÕ áTĐÖ×Rr"ĸpÕŲÅR)O¯ēP2T|œũŌ.ązČÓĮC¤8g&ƒĶ‡'OD$eH0söˇČ~Ü—•ųĪ IZĻ'ĪģîžFŌ—¸­ægģ[ŠA LÍŲEĸ"Hz’L.JWņY:fķi‰+ZP;-Ą>MNdëŖļŗõ0ÂÄhzˇAŌdĸˆĒë%¤įŌz<vũí ëø�—ĻáTü c…""j2ąŸY„E™>o’8‰‡hĪéžnË.‘\X”ËN2MlQI•Âø$~Q´ ´ˆŸx‰‡ĩŸWMИb<äęŪ‚¨ĨäDÔĮĢõšĨn,f‹aI*—&°$).gˇ;ãgp|įą°/œšt@Ãũ2ā'JúWä� >Wģd]#É]Õõ.ŧ?[VÔøûÛĩĪ`IAIŒĮCD”#lŦbĒy.—”5SB¯Q›Ô§ĩ„8>ĒNrĐAķU’2+Ĩe =ē(ė<ö@�¸4 ĪАAŨų鉘îĐ>vc•$!ũĖûZV’d|7#ŦÛ"áŪģåB^ ——B.§¤ŌÄlŦ`Ĩ ĪĸĀ‹ôФÄ8ú|ī å’’ÄásųŋË#bŠpHʝ4>—æŖ%.oÜ{ߤhøÛs;Håƒvz[|ÁžxɞrļZɯLņyAØHXĘãgģKe÷éŸWkĩ^0rL™•<L5 Kĸú_›ZČQ3%ĶĩĖ1)Ëš¨úÛ!&\¨=�.Ã34ø’ ’„ĩ‡ŲÍąž%ŪgR•`”Õ#DŠÎĖã‘XIPy\’Ē6JŅ R"ĨJŸÂ­gڈĸü.ŌéÜOöŠ„ ™ėę<†/)(fęcøëbzÎæˇe…E’ĻVĸÎkâ%Õö3$ĀR}:ˇTNâ!!âĖĻö8$‡Da=įō;8žĄA*ÄũC­-¸ĶqfŗK¸ĒĮ l$,åoģGQ÷éÔÔB)ÅtFU>'‚Ų|ZrŒ—P$—<­,Œļ™$ۜ’đhŪØ[Íũ.Œœ=�.^Őē~ļ–ŠŽ0&īˏT˜KĻãėæ&‰ĩšlĖ7ÅėÚ ÆADmĖúRös“¤ÁEV—¤ĘÄԐ` ¸ J†\m’*‡Äęû6N*LAĻjfs“¤Á!)>ÂlŗŌ(Í9ož´Ŧr´TUÍY%V§¤üSŋX*?Ĩ† 1ÉA“Aeô�� �IDAT¤ŠšŦĖįG%1äqKL=Ū•úœAt~ûp4ą›$)Iŧ‘—X]~ #`)Û]ĘO%S5S`‘XÛ% MĖ—§$1q|™GXōÁeaļZ(9öĖKo?kSGķ)íĖžf!§×0  °āī�p‘ūGˆˆ„ŧd~˙æ“āũ”B*wƒ”Ų|„ŨĶN$ŒQü-)ŧšˆôÜ-iĖÆ“ė[U䑐.\ŸÉĪVS Eˆ’|ÔaæÃ)oü™ƒÔšČuRfëļ Ôjaöxn˛Ē—âüˇ,äåĖ•ėæbv#+$Äņ‹b˜›ú\*ŸRĪ^•Ir~Išäŗãė+u¤Ö ķĮpiLC)ķn1­Ė;ûō#õ?8ƒį| öÕd–8ˆfûLLHõÜ7Ā‘ °”ŋ힐ÂŨĀ0›ŗ›ÛIŠ’šEŖĪž(QG •L+,Ō‹ZK/¤B–†Ē!̎Ũl@…€‹›dõęÕŪ˙ZZZ"""ÜPKK‹Z§€AĶ.ųp+M÷܀_†�€ž8Žķ|Mīuņ‹áôĀEÎCM6ÉÆrļZÅĪGb�€Đš8NO�\ĖŦ ė+Uĩ–ŋv,~�B Ą`¨ĶšGzųŧ�Ā…†Ķ��� B���ˆ‚Đ����ĸ 4���€( ��� B���ˆ‚Đ����ĸ 4���€( ��� B���ˆ‚Đ����ĸ 4���€( ��� B���ˆ‚Đ����ĸ 4���€( ��� B���ˆ‚Đ����ĸ 4���€( ��� B���ˆ" b[VĢ5ˆ­��°,;Í34DEEą5���˜–––Áh§'���@„���Ą���DAh����Q���@„���Ą���DAh����Q���@„���Ą���DAh����Q���@„���Ą���DAh����Q���@„���Ą���DAh����Q���@„���Ą���DaįΝëũĪår)•Ę7tž‹ûpWmxëŨõ{kĩ™™Ņ˛`4xŅĒũá­ˇv éŲqaAhĖ´ųw „Ôņ#Ãē˙?ŧ4l{û_[É9ŖUĄŽ"¨ģtčØŽlüėŗõ[öWčNũŅđ|(]0ƒú˜ âSŲđ{V¤×ôĄw¤Á]SRMzŊĸžä¸-ÔĩĀ…á,ũ|íϚPWĨ{*l†Š×ŨtEFlæėSa{ԇŧOãúķ˙ 6uҐ†ē€žœĮJkĨÉWĖbˇ~]ZiÉĘׇē t\cŲę"�‚Åín§đŒŖ^K¤Męr† A}Čû6ފ=¯ņbSĄė‡Kɓ¤&UYy…%z¯ŠÁ´ųuÍ—¤ToŲiŌĪŋûĘL…ŗz˙–L'Įj )ųsįŒ3(ŧķröĒŨ[vU˜,NR’ōgĪ`TtN/Ø˛ĢŌdqĢŌĘ˜6gzŧ–ė%˙yĢbîŊWí\œÜå_ŧĩÉ=må9Zōˇßz4Š#ŽČæŪuõØ3ĄÔ]ųõ[[§­¸1GëۉūFDDʝ›ļ›Z8EDBîŦôncâēÍeÚ÷Caq­ŲŪÎÉU†Äܙķ'{™­GŊÔͰųíOę˛oũÉD=‘ŗė“7ļÔ'/y`iKDÔ°ųĪ'ūäĻ\ĶT´ugé ŗŊäCüøŗōžiŨYōŲ[›MD´îo‡"ōoųÉ Ŋ˙î‹=""wíūo6īĢąpŦ&6cÁ♝oüöŊaķŸ7Oŧv‚Ŋ`gĨŲæ&UˇĨŧcnįú„‰3'8ˇü÷XōMˇM‹5æö’ĪŪßĘúŲün蚍¯­ŗLYqSŽÆģLíĻĩŸš'¯ŧ%GÛ˞ßĩ.ĶÆ×ÖYō—d7īŲuĖėäڄœų ķF)úØîū6PoĶí"7ē˙}øœúš�ģtŸ;ao ú Č =KØgœ%˙yksžķ÷BÃŦEqû7Öåv˜ŋ}Š_[|Híuv- ĻVw{Č/ôVØûcÖīCĖ˙øwk|Žû‹˙ž[š÷.ôÕT˙v {öđ3ÄŽi°”mŪcIž=+I­V9ė­lOɍīežļ“Åeufk{ĘÔE3ŌFjåæíŸ|ZäI™ų3&ŽŅ6ÜZxR66ZAÄÕnũĪWGÂr^>'/Eo+ßVP%“æŽūū“/+e™‹ŽX4sBz´ŗ˛p[Y[¸„H­pōĮŌÃøô(o rßšõ¸:ū„LŊßĩøÖ36E×rđĀimVFŒ÷’ ÷ɂmUa“įOčv‰F S3ä,_÷ÉĢqū•Ëæå&†Õî*<juɌ9ŲqaÄų¯Í—ŗü›ˇXs–\6gRöƒĩd۞SãŌŖ¤Döã?rÄfÖũõ¤õÅ-Q9c R"îäžíĻvy›dTnĸšˆ,•lņĶōãŲ#ūŗŖ%~ū•‹§åg%GÚmŨQĢÎJ÷ ™!9YV]Ö`\t×õĶĩÜIŋŨ'Ŗ×Z} ŧÁjļ†›3orNBX}ņžƒŽØŧ¨ī­'‹ËN˜š(kūÕ gL͊l,Úą§ĨsŠ’uŸė´ÆÎšrņœė8Ž|Įî“§lôącވÖõž3đū7´ĩjĨkTθØÎĻlĮŠ*œÆ ŲąŠž{~˜OęˇWŦ¨Žm–M¸âÚE3&eE4îũaWƒ~|Z”4@ßŨ~6ßëôœ˜&]`îQŋPéw—ö%žAßŧg 5ãwŸ1D7Ō^yˆËŧiŕSâ:Ēt=”ėKâˇx€^„d¯ đ°ō×āhcˇ‡ŧë¤ßĮl€‡˜˙ņīÖ¸ŗúėS™ŋ­ė¯ aŋM `ĮĐ]øK.‰k+*ĖÚԌX"ĸčqã Î#%&ÎßĖvyęü‰IąŊÂ}lWI‹aĘĸųŠ1Z­&6cîü EõūŌF"rŲWጝž ?Á 73į/œ‘ŦqÚÜä<ūã{ė”…3 Z•&:yæüėKųŠ’ĶbÉt¸Æí]‹ģæP-Ť'Ģ(ĀZē×ŖI—Â6”WÚ;[0UÔRüøäîQŗ˙…‘ķx‰‰ĻĖ͌ը´†”YŗRg§¯ÚÎP¤ĖģiÅUs3cõZ­>6urļĄŊöDC Mâˇ66%–ęMõQ]uCxÚØQ\C­…ˆČ]o˛(Œ "ģŲŌŽ‘­Õhõ1iķŽŧéēi‰Ũ“ ĢP°,ĢP)l€î‹=īōqķgĨÆęõŅ 9“äífŗMDß9ø9Šz–ˆTI™FUįRļãå&J˜27;V¯Õ',žËĩ÷kĖūv‘=íÍŲ=˙ÜûĸĮÍLÕ°DŦ*ijnL{õĄjwĀžûÛ@ŊOˇŅûęÚŲúėŌžÄ7`Ŧ‚ú,`ŸaY+%bU īŽŨ%Āž$~‹šŊŽ˙O)=ōŪ {}ĖöÕŲŪĮ˙œÆ;ų}ŌđÛŋM hĮ¸h ŠĶ %•vCöØhī-mrĻĄp_šiŽ1žímnMtlįQk‹Š‘ͤģNd°q ļ´žŪIŅ–úFN•}曍™°ør""S}#§IÕtĩi4°ûÍõ6JŅ'ĨĮnßQyʝš¤ wuĨ‰ŒsSTDõū×ĸę^ŸĻø´âˆ%7OO\m…‰Ļôx>3÷ŋ0gŊTégßRčGäû,}@ˇŦÂ*¤Îō;66XlnÎãņpíDڀO˙õÄÄGsĨĩJ0XĒM;kܨÆŌĘzwž^Z_]ĪĮÅ‘>>QSTŧásOQÆQUtlĀãtVį{žĘßč‘*.æĖé VŽ ŗÛ#ĸīĒČŗĮxĨJ–8ˇ‡ˆėõͤvEƔXļÂû rĖũí öĀ@#Dž{Ú9T҆ޕkõz9Wßh§4…˙žûÛ@ūĻ‹Ųč}mDŸGŽ˙]ڗøŒUŸ%ˆüí3ūؗú"ĒĄŲëú˙”Ō›Ūŗ}uļãī˙I#˜ĪŠØ1†›!¸ęŌ*;į,øāo>S-'¸ø”ŪRƒ´+üšŨ˛˙øņ?~ėv„ÛMäq{ˆUģ¸Ûí&VåķBÎJ â87ŠR2bļnĢŦå’R<5‡ĢiÔŧ$Uāĩ¨ē×Cė¨ėąúԊrsŪ }Me­4eŲ9šg�…š=în‹°ŦBÚĩTāÚ:q Û>ûo1;vūŧ™Ŗô )9Ëŋú÷ŽsJ[OŦq”ļ°ŽŪIĒSu­†ĖXM\Ŧjgm—ĄĒŽįâόd‰ˆ5Ξņzũūũåļ”log5#3gĖíĖāũ]/ŖGDDŌ^§÷Õ÷Ū—r{8’ĒägËQ¨dë,UԘûÛDö´×*üŽŸÂ§V’˛Râ8OĀžûÛ@ūĻkElôžēæķČõŋKûß`€ą ōŗ„w€ũ­ļ7öĨžˆęEHöē<ĨôÚxīžžžrû5ūūļrpŸŠ˙… 7C'4¸OTsĮä-ŸŸzv[¸kļ~ąŋü˜;%ĩ—ąg)RŌ¤,Ŋr’oXfĨ*-‘[!%ÎyîCBĄPtŸÎšŨnRx÷UBF,ˇŊ˛–Õ~¤–â$+úXËš c3 Ë+Íųą•'Š×ĪŲ‰ũ/Œ“˛Ä9šî÷ô9ž+[T™7,ȌõŪvēû<>h  FEqmƒSUŨ¨5Æ)HŸK?˜šmĒ:§!­ë˛DULöŦ%ŲŗČm1UØąuÃ:ŠfÅėØŦŽÛlŊ^�č;ąR–<Îŗsrng?Įœüė "{ęŊKô;7ˇO­äqģ‰eĨ}õŨßę}ēˆ.žk ˙ģtˇŲú1V~ûYĸßėKįđģŇÚ^ן‡•øŨ¸SŋžrEĩÖÛVōŗâÅoČ\ĶāŽ)9ÆÅfæŒ2ĸģūŒY™FŽēü¸3đ˛zc,ë´šú.*)ĢĐ(ˆHoŒfíuõö3ŗ6ėûė“õv2ÄvŸNÍ&3'7D{wGU|ϑ̭ŦŠŽ2QBF‚ĸ¯ĩôVSfvŒíXéđ´ąŊŧD 0!’œÍ]ģĒšķܲøÚܜ‡TÚ3S9ķ‘* QāGrŠKIõÕåUfEŦQOD†øčVSõ‘šf­q”–ˆˆŗ5TU[ŧkP荙ŗ§%˛ÎĶ–Ūž+šžW×÷č0€ž‘Ū %{cWÁœŠĒŋcNÔûΨ§ŦTJng×M{ŗEôÉīēŗgO-æŽÕGkõŨß °áúÜčb7"ÜĨ}‰o0€ ?KôW€}Iüj{] ‡U_ öũčë×Snwį6îo+÷ųĖpnSÁŨ1†›ĄœĮJ;¯7ėF‘’f$SEUāÔ HžĄĒ/øvį1ŗÍé´ÔÚüŲûīo8ä$"Er~ĒĻ~÷w;54šMå?lŲUOąąR$åghęwˇ¯ÚbsڏíØXb7dįŒę<" JL3ēk vUSbFŧĸĪĩôF•:.ĄĩėĮjmf†Ą×šû]˜693†Ēwo)1Yl–†Ę NpōŽÖDÕ͚ˡ8ĶÁo6Ûĸ,g̝wûø(֘į<ūc;:Á@D¤ˆĨ5˙x ^aLîŧ0ÅRēųëuߨi´9m6sõƒĩ¤‰ëų™šBJ햚jŗÅFˇK_ŖĀ�úNDÚøLUīŪQiļ;-Ļ7ėŠgû9æDÔëÎh`õąÖR}¨ŪMDîÆÛ˝bwĘíĨ[÷›,N§Ítpëŗ*9#A°īū6˙ ÷˙ÛģÛ¯&Ō´Qô—T  $iJ+L$ČK"Pl¤`ÛjË8ÎéiO?=ÛõĖ<{ÖÚg­ŊÖ9Îā°?ėY3ŗÎ3Ģw/ŸŨķ83íØÚ=v;Žm#ČĢ ō*`—`ĸI“ļ Áj*“Ōķ!ŧ$ĒThĀëˇú ŠûžîëžîĒ;•ÄDŸô(‹+4Ŋâ%J~ƒÖū,#‰Z’?ã‰VuRËJĸÁ%/qŋ0ÆSn”ÆÅfYjbM­ial4 ōō„īa˙ĐÔWŧIŽ4äén}7hķYJ$. „Žöt=ŲÜrëJ7ᇔ Ąęôáāg” ĩūxjKĮ­+PLöÁS5Ĩ4�ēçO*[o~ŪĘAŠ’É)¯¯ŪĪ,N:eČĶŨúÎNėŽÖ2z‰„Ė.ĐvĄ(?ō;ŒâLi9ö{ŗšåĢ?5:KUĩŠųęClTa]­ëZÛ?>�*3¯ŌzÄøŧyę›û—.ُ˛Å†!•(RĢg8ģ{gŽ&ø ĨNCļō ķ')B_ķĶÚæĻŽī>oæ"EIkÍĮ~rpų&ŠĖ)ŲCÛûåÅËŠ_Z%į%ZöÄIŨ ~]ú~{ŊũƟ?*ĶX^UI]ûÚĩƒüzˆP ‰Ĩ ×8ގ_ú´— •YĻĒC&öĒ]ÖÍ]•Šĸ€ģ{ånŸ@ŌúCõĩšd”ąGž ‚Ÿ¸h“uq…(é°ôÉnPÂڟ%b$QKōg<ÁĒNĒ´*Ä ]ō'Ä/B1žrÆԸҰÖ"ΞÔDšZÛÂØ`ļœ;w.øĶôôtFFFÜ ­ōđMˆųōÄģgë oÆ+]›•Āķ°ø‰+ŪöÕųP÷ëSŌī˛YáUƒŗá͐|% 5Š%ųđôf[§kz‚ÜiØ\xŽeŨũˇôūqšnlžū/.4ųķŦGöé(Á;ŲÕ2IčeĮ0ŠX hŪĒkI>Ŧ:´npͰöŧC×>köP™…ĮíÃ'}œŌrōĢŊõĢ?q~"E™iŦ=]Ë3C,´`ĩĩ$VZ?øōB!´ŲŦĶ5=Q>=BĄ‡›„Bɂ›„Bɂ›„Bɂ›„Bɂ›„Bɂ›„Bɂ›„BÉBÔÔÔš››KMMģĄU;÷í˙øßMœĄxW´¯/y œ ŸūąíeŪŪi��Ū‘—/˙ŊņŪ8YP¤y˙¤ëkÉLø_ģWķMÅqëüųŽ—–Ŧĩ˜K™…ą&õÏ^?˙Įŋßs¨Ėfuō*ÚŲübŸâ[āCx=k?áōëvMĮ; ë6Ž;¨ŸŋfO Üō2væ¸)÷7‰‚¸ōɡ“kÕÚĢĖųÚFž9„ä$l-Ŧ/~˛ß4MēúĮŊ¯ĸŋ7Ę+œĮÕÁķízÃMÃz 4y…Ff~“Ëķ~ØĒ7jių†|uj„ЧžĀÚ5÷ sžÆ‘o Ą9 [ ëŠp( •Uš”gĀÆž‚ß(¯nW Ύë-ž°ŠsŪoj˜đøüĸd˛÷VV•i)��Á7z§ącČÉr@2še‡kJĩ‹ĩË;ēŽ5tN˛ĄÔ˜ęŽZØ söŽÆ–'Ë „’1–ÕTëŨŨđé•gûëÍSícN Ô–Ēúũ)ƒˇZē,¯ åuG‹‚_‹.ւxœKŋ¤˜ėŋxžÁ �íŸũļŠ:ûq‰��|ũ—/45ŋ:ĩøŨđŧí›ķ7žWœũ°X%øFÛ;lN–‚ĸuϊęw˛U��“7ūp•-?{fžp|ûÉžˇųQą*B:E2Ã9;oĩ÷9<>ŋB19%‡Ŧûĩ$øú/^h"k~}Ēp!ŗüāWįŋå+~ųaąJ<ĸD‡1{Đų|ƒ�Žūf8ŖėŖ+9M9>Ŋúl˙ ŖŊąÕI[˙­Ū<ˇ<įÛ‚0ŲúmsŸĶ'´~˙ĄRŽņË1Ù_T¨EÃāD"_ķųėBŠ",•‡ˆĪ#ø´@fčKĒ ÂæRz).õĩĩÚ<^¨ų2[–“ū̐/ėŽ\~ËÉXb+ųöģIÃÛ9z…“z08ĖŊņ̚bI‹Ø™'ō ÆˇˆBãQæíp$‹”Mč b ,î)^–ˇĨy”5A\ŦĢiahņ aūņ¯ũe *ōQb5ƒäڏķÆ“0īiāGŽ_l™ÎļÖĢ(ÛcØænjq¤ī)P' ŽĻ‹_¤•y¯zŸ‘öŪnMŪmÎJ{nītĪxfļUמ]ŦOsõuöÎjößJÁÕ|é‹ûŖõŊã•ûwĢžõ6ĩ?NĪ/T“�Ī÷=˜pyĶˎ××UėÍpv´ô?æv>ņūáy/6ĩ=Ví1Š“Å[3t0žņîáYeīNSX´ĶgĖgÎ֗gQIķwvČ ÅĶû=ßĢö˜2ƒōÛnĻŊm-Uŋ°wéoļdķŅãG•¨9[ûí?ę‹ôéI03Úe›Ķ/žPįģ?ÄiK-Ë^¸“Č 7xí¯3ÚęīV°ėffúoß}’QT°Rž|Ü=0Íė-ØÜFōã­MãéeÖŌIâɌ8ä4�^t‘ŗWd*0$Û¸ĩGõAEŽJrûKŧ)øņq߃§žŋņāŅĘüĒ´…ũorxΉĮ 2[x9tõRëŒĻēūXĩ%Klšķx–KŪUjÉJ ƒV"Fžæķ+6X�[ ËIņˆÎ#7xõRˌÖZ˛ļ$'ÍŅŅūhf.Y[lÉJ‹ē”œ?ĀëŠ#•÷l›ēßrwZŗĪȄᄺ/†Xų)ÂęGÖ[}Đp—5ŽĘMO§¸‘{6ŋą${k„ĮÉO‹XbŊa›*žEOĄņ­éŪČe:øXKOŠsŠE¸Ŧ âúc^MéIR’Âĸ¸×~肒ˆAŦæßJZĘĪ[âk˙UŋĪfŗŋ§ÁįaũÔ.SŽZĨTŅ™ųĩõg~V‘Cđ#Cœæē2=C3ZŗõHĨAÉyųāA|J‘ĩ*OCĶj}ņ}Šßãņ�?ÖŅ?͔ĩæeĒTJŠÆj"í]S ] ęĸr=E�PŲ†,đƒn)C´Á Ļ]Ŧd bqŠ ŠP�I’DČŨ2ŌPd$܃6ßü@œCČŪk €īņiʏTęĨTY-ė`Ī„[:#gHcí™ŗ?Š1kh•ŠÖäŊmaüŽ 7�P†| 8N.$vrØ™*j2#‚Höˆ`r’"Ão)Fˆ/%Īē?WÃĐĄ3–s^v ŪņA'čËk,ZEkKUhÔ0Ä"_ų8Xé˛tˆÄ<rãũNЗט5JJÅĢnjäB4Q—STG�@åšĩT°ĖDgSŧüÂĮÛ šō¨ōL��uQÍô;Es*+-b‰ŸÁ¸Ņb<Ją˛‘3ãĨĩŠ)Ž”>×jŠs!â\ûĄMD[Ąk>”čÚß,fĶ@gį(}}ׯ4ôŒØ=œ�”ZÃP€Į5%P;Ô '2KŊWПŸe*+sá†An%Aā�Ā:§å.íâÍI"KĪŦËÅÍ˙ŋŠ^¨I b” $H’-ˆÅ+"ےOē†FX��Á1äŊÉH°Ž)A™ĨQ.>p›–!üWŒī늜 H7qįÚį˙ųŲ'Ÿž˙˙ūđ—V7‚��@åhĀa{Â�đv›´&#=™H !ÖėEˆR­‰rĮO~ >×3PĒ—*MkÔ2‰`æ7ō`%ČŌ!ķČēŧ@m[zfI똈z��Pۖîc+R‰…2#^~ĄâYbî~›1ǃ˙§2˜~tPt× +-b‰•˜ÁxŅR<be*ŽĀV1Åș øVS|CßŲ#ÚQŅk^Î$nd ķžB{øÃ莎ÁžÆūf?ĄÜiŽŦŠÎŖ‰��Bė”Ąˆø{ž€¯û¯ŋīûmĪ,Ûí�� XҊT "qĘéBß:K!=04čŲWIOÚ ãÉl�xž‚ Š0BA’ |l­GΌāž}ųË>ĸĐZ{HG“ āŋūŧcūo”Ņ”ŲtÛærÉ‡vĐÕæRK2I Al–ņ-Ѝīu’ß@A-ˆ’"Á+Ģ‘HÖ~~#Vz,N|ų�ÖAŠčGQ�be&FĒüBÄZ$�‚}`Ô'pmúM[ČoŲĄ !Ûé0YiKŦÔ ÆšˆBfV¤lBÅØ*Ļ89ßjŠobâ;{D=*z͢č,a6 �@eZĒNXĒ€gŖ=-Mׯ*”g“¤.ĻK&I*@i|ŋū@čĄ%˙](Ō-DŒSK„ALĄ™é´yĘ4ļ 2ī´–vM†WāyH*ŌFu哴(φ˛”ųįuæųh9ŪŋôGJoŌÍ6‡ ķ8 ģÎ@ã‰9™ŌCˆ){ądcĩ- œ?䁋‡ÅÆĢ™_™ Db9ˇÔ—Āķ|ôŖâ Y~ab[büÄĐŸšī˙°æ-e‘ŸlúĒkpŒ7æIN’Ä�y‘ÄJÎā,ĸˆeŗ<æÖzŠŖNP|ĢIˆkbâ[ļĢ?į€ŒIÜČåå ÁëĩŗÁ’ i­ųpEÁ}Īō@kՄīŠËˇđ@wįåKō‰6�´VCp^ž¤Q ‚TÆ0éâ-ˆÆÚlÉôŽ ÜšÜš_8ŋâMøxá™Ķ#¤0j� đÜâÍ0ß3Vė¤+‚@Š!xFFY€ÅuHe›ĩ‚Ã6iu‚Ū4‡;ŽdŠ!JöV^#Ĩ˛!ühFžŠĨ`œŖ.An#‘¯î¯d~e.‰yT2ۀ{6ĩXÃįâĀWŗ”VæDēü‹u‰ņ“ũc‚Æ\ŦcõâÚ=f­`—¸Ëe€b‰•.†5XD‘Ę&<æ˜[Ķ)–5Aņ­Ļø† &žŗĮęĪ9�Ņ'q#K”M° ß\ŊÖ39ååŧ^Ŋ§×Ę,5 ¤Ą,OéēsŗuĖ=åqŪjėp&ä§HCЉrĩũŖuĖãå8Ö5Üpų…ëÃQN2[‹3.T^‘ūųƒnģĘlZø”!™[fRēîķ§É�� �IDATÜė´ŗ^Î75ÖrŖßĮXŠu�І`íÃ.�øŠžæA.Æ ŦZŖ&<ƒ=ã,ĮąÎŪk ^ĩ–ŧ.\~TNž–Ÿlë°CŽ)›Œš 1CÍ^ Š�?;i÷°^^^S2ÉoA•mfĀ~§ÅæņqŦŗûú]‘"Ŗ‘Čƒ }ķ+sHĖŖĘ`ÎûÆ~'ëeŨļ[mBJôŖ¤ˆä$Jų-_bėĀĩĪ/ˇģÂ;ãÆæßo>`cžœCŖŌąJ P,ąQ*j Q„˛ 9æĀÖvŠåœã[Mņ Atrã:{Ŧūœ�Q'q#K”—'}ÍOk››ēžûŧ™ˆ%­5ûÉA�ĩūxjKĮ­+PLöÁS5Ĩ?€ԘŽöt=ŲÜrëJ7ᇔ ĄęôáÂXŪŊ*Ū‚hœq!ŗ 4„](Ę_Š;|ú¤ĸąõæį­¤(™œōúęũÁ:‚*8\ã¸Ū~éĶ^‚Tf™Ē™ØĢöX^Ŗ  ëj]×ÚūņŲ�P™y•Ö#ÆįÍSßÜŋtÎ|TĄ  yē[ßŲ‰ŨÕúYcNĻøDŗGæ”ėĄŋíũōâˆåÔ/­Z9ؐI~ téûuėõöū ¨LcyU%uíkWÔFÄ"÷ æW摘GĨåØ{ėÍæ–¯ūÔDdč,UÕĻæĢc|´Ŗ¤r’“ŖKŋ–*ŋėĨ@ŗXĀëqšˆįa}ųö?MqEL¤!OwëģA›ĪR"ņCb€b‰RQk°ˆ"”MxĖ1ļ–S,1A!â\Mq A4q=VÎ 6#=‰ؖsįΚžžÎČȈģĄUū†âFžüc#ņîŲzÃ&zsíÆ%đ<,~Lˇ}uūÔũú”ôĢâ’p~Q6GŲŦųjÚX`×隞(wŪ8<Į˛îūۍz˙ĮúÔ°yøúŋ¸ĐäĪŗŲ§ŖīdWË$Ą?–įÜāüĸ8lž˛YĶÕ´ąlžIŒ 7 ¯‡wčÚgÍ*ŗđøą}›îöÕĨ´œ<ÁßjoũęOœŸHQfkOWĮûŧįÅa•ÍZŽĻeMbdøōB!´ŲŦĶ5=a>=BĄÄ†›„Bɂ›„Bɂ›„Bɂ›„Bɂ›„Bɂ›„Bɂ›„Bɂ˙"älß|ō÷ą/"R”LöŪōwĘôÁ¯Ėq6|úåĶ’ųx˙Ļü‡ÂĐëį¸uū WņŲV˙OŅ%X­zGn|ĶhķęĒĪH}•¯zhÜČĩ˙}ŨmüÉŋՇū“ˆ‚ãÛ _Œ1Į˙ˉüXžLĄ× 7 kMYxüŨųīˆøi{ĪŨÖŋ]ņūėcĢ6æ¯ICčõĄÍ‡ëôtĸ\ÍĻîy™ĘŸÕä3‰’|T^ÕĄūŋ44ßÛĢ;¤Y8 žģ ÜļļęMŪ1pW.<Ũ˙ëwŗŖ?% |yb­‘´ZĢÕiĩ:­VoØsøÔ‰RĨo°'Ęw¸"”`(M^Ą‘I”/ āy?lÕĩ´ŠÜˆ›oĨĨömw ŠŸ]ø¯¯Ą×Ëŧ]W´Ęģ&š0õÔxŨA X%ŌÁ7ÚÖØas˛­3UTŋ“­�p7|zåŲūĶĨžļV›ĮËĨ1Õ;¤ŸßĄsöŽÆ–'Ë „’1–ÕT­<ÕMŪøÃUļüėâMĮˇŸ|áyû—Ģ�8įũĻց Ī)J&{oeU™–’Ũr4ŖQŨ^°âXņö9gį­ö>‡ĮįR(&§äuŋ–œ˙ËĒĸ=<ržū‹šČš_Ÿ*\hŠüęüˇ|Å/?,VEīŅ×ųBQķĢS… ĪĻxÛ7įo<¯8ûaąJtē%&ËŲđéÕgûO퍭NÚúoõæĨūVūI<<Á7z§ącČÉr@2še‡kJƒŲR|IG™ˇÃ=’,’‡Đ„ÅXpF'[ŋmîsN d†ž¤Ē@^‰WQh>īáK/°ōĢÛU!×ņ|ƒ�Ú?ûm;SuöãBFŠ;oüá*[vÂōėnĮ˜‡H•žØzdŸ.R4ĸ1‹MGgē¨ēdāķ;-ƒųõf ¸‘æˇrīĪ‹éŎb^ áÄBĖ9aYÅxjâú/ŸopĀÕß g”}ôq%ėņUž4P<ˆšššāOsssŠŠŠq7´ĘÃxûw—ūfK6=~ôPišŗĩß~đŖžHŸžĪ÷=˜pū�{Ŧ§ŽTÜŗmę~ËŨiÍ>ã[I ¸š/}q?`´žwŧr˙nÕŗŪĻöĮéų…ęeu13Úe›Ķiæīģ?ÄiK-’š~ąe:ÛZŦĸla›o¸ŠÅ‘ž§@,ŗåP/š˙ˆ×[˛ŌBēīę{B,dāīžÕXöîLŠČšÁkmœŅVŸxˇú€e73Ķûĸ‚í Xe´ĸ‡‹õH)_>î˜föln/ųņÖĻņô2kéŽ$=’Ч÷{žWí1e&Üv{4ímkŠú…øt‹Oüø¸īÁSΌßxđheūNUZȞw؟R<ĸá ŽĻ‹_¤•y¯zŸ‘öŪnMŪmÎJĢĀmĒx’OĄņ­éŪČyHIWŦĨ'7xõRˌÖZ˛ļ$'ÍŅŅūhf.9Xâ%!ZĄĩZĢ 0”xŨŽĒ͘Âĸ>Û°`>sļž<‹üžUÎÂôŲ{‡ėŽgÉĨĮO­<°'cęŪ­7Ŋ7{ø2”ˆYl:â83�@RúÎíŪwú§5E癿Ģw8ãģĮ‹i€äŠOb-„ Ubt2įTö˛ŠųԔÎ ÉöníŅ_}P‘ŖR,ôû*O›Ü:]Ķæå nŧ{ħ)?RŠgT”Rm8dĩd°ƒ= o+˜ĸę<š��*×ŦĨü�øąŽūiĻü¨5/SĨRjL5Viī˜’߯ĪÃúŠ]Ļ\ĩJŠĸ3ķkëĪüŦ"‡\‹–žĩw5ŪķPFKîō3ŠTû¤ąöĖŲŸÔ˜5´JEkōŪļ0~Į„{ ĸ;\ŧGʐ¯įÃÉųWWøÉad(™=’†"#á´ųæw9 {¯Š:ŨR3–’gŨŸĢač•'éĨ?I„ĮtqšwęĘô ÍhÍÖ#•%įå%BŠ; ‹ņ(ÅōV1Üxŋôå5f’R1ÆĒ*#šA‰ØDË@Jä>Ûĸu6I1!AP„€ H’$ą,LuŅĄ<%@PšK2ũöaûō— Åc›Ž¸Ī „ļúđnnüûõfäVÎ^¸­˙Z˜'j´‘1§�2—Uė§&‚$ € )2ė5§W|Ō@ąJ˜]ëš”šĨWøļiĸËãō‚‘� ļ-Ũ�T¤ đ�`S‚˛@ģø.h"KĪ.j™o/ĸŗs”÷ûŽ_ ”uZCŠ5�ĀTŧ-{Ú?ûmûŌ˙Ļdj뭆ĩ*9A*¸Á֖nÖË @@đ¨„5ˆVėpī‘Ę-Đ4ˇØžđyš$đv›´5F Ā%¯G"ےO~14–ėŖAp 9AÄH8ŖLˇĨZŗüŪėĘ?I¤—uM TÁb”Dféą÷�¤CŠ3 Kņˆå!”'öĀ8—¨‚ĨᏴŽIédŖe@´ ¤D^€aÄĢ(TE*–%OŠ—Ū6ŠĸéÁ5åƒü°´‹w*62+?ŌPU™}áÛIĐųÉŌėG;õE'*€tJeĖ)€Ėeĩ†'ŌW{Ō@ąJ˜MĪķ@P!ë™P$ÂÂ3EÄ÷?ņ|�|Ũũ}wØo3x@æĻĐūđēĢk°§ąŋŲO(wš+kĒķh"î–é='ŠæcĻ(šŠŧˇ•hŸtßžüeQh­=¤ŖIpƒ_Ūą&Ҋ.ˆ÷”Ņ”ŲtÛærÉ‡vĐÕæRŌņ‡å‡ĐY 遥AĪžJzŌæPOf}ē%(Äß ˇô'‰đ|�jeR!ř„PEō*ŽĀø�vAŠÅŖÄc)ƒ=‡Šŧ�CIUQˆxŠ0,ō&IĨ„@‚°ėĒ(ŅŠøtŦâœCåä30 ú÷?Žb-€t¨ŅR}Nƒ“ŗŦĊ*žtŊŌ“ŠUÂlH’ ™~įy E.šKG)@i|ŋū@čN“PPŅv•Bč*ĶRuÂR<ëíiiē~UĄ<{8Ū–Pmc˜čĪ$Úw =d)ķĪë˚āo9ŪŋvŅF<¤z¤ô&Đls:˙ˆ˛ë‚wMä÷Čš™ŪA›§Lc› ķN?zËt ōīĶ.’'á]/"Ōj“�"yXsŒ N˙KÔ €Hh"…-ߔdŨ†ŠŊ—Ä’sž i"Āķ@ËNx1‹OGœg1Ģ_ bĄĘŸŲĄÆ\TņĨëĩœ4< ķžFŖ&|O]žÅ_<sz„F-][´VCp^ž¤Q ‚Tލ"BĄ�ž[|–á{ÆÎ¯ÁëĩŗÁ#i­ųpEÁ}Īō˛[Ž—Dûŧ�JĩГāe„5ˆVôpņ�¨lŗVpØ&íŖNЛæ˙}šōC›-™Ūą;C“[ķ įO_RĶ-:Yk“^ZŪĩģķōĨŋųĸTāj“ ’‡đ˜cLÉlîŲÔâyÔãt ‹­‰Å&ZĢ$]E â,ÂEąäÜ÷téEzÖ3-´zŲ'%:›Ž5?3Ŧ~-ˆ…*3ĨōÅQTQĶ9ž×qŌ@ō$ĖρĖ-3)]wnvÚY/į›kšŅīc,Å:éģJ¤ĄÔDšÚūŅ:æņrën¸|áÂõanųãh C°öa�üTOķ ˇĐ.;ĐđÍÕk=“S^ÎëõØ{z ĖR“Ō-ŗ×>ŋÜîZÕxÅÛWkԄg°gœå8ÖŲ{­ÁĢÖ‚×åâ…øĸ]"v¸D��TNž–Ÿlë°CŽiáŨ[r3�@åéŸ?čļĢĖ&faøĶ->Yk’^ŌP–§tŨšŲ:æžō8o5v¸@ŖQFĢĀÕ&!rÂcŽ90•Áœ ö;ũNÖËēmˇÚ&„”ÅÖDc+ƒUŠRE â,ÂĨ,ÉĪyŠo ŠËÉrœ×ŲÛÔãĄ &ũ˛QJt*:k}fXũZ UfJc5öĸ’JW Š�?;i÷°Ū[Ö×pŌ@ō$ÎG.ĒlƒznōūŊööģũ]/˛öÕ+~€æšŊgpz{QņŽųÁf'{ĖlˇėŨĩ’Tš†Ė9gßŊģwîvõ?b mų{ufzųĢ.ÉÛ2Ķ}zšÛîtõ˛ĘÕŽ‘™íE–,*CŸ>3Ú{˙NĮŊû#Nî-SÍ҃Z2I˛åŲGí폒sėŪÖKď\† ũŦ—xûÉÛŗŌgGûîļßíy–Vlĩîg¸Ņžžî‰ÆC•…ąGģ(Il°â=æZvmHVŊ|roȕ”[e͢ßgĘĖ|pn͎8{Įˇ°îÛąpŌ–˜nŅÉJ[Q !–ũI"ŧ$UNöļ9G×Ũ{Ũ6׋Ū{w˙Ždɐ�bNB¤P#ä!l~bŒĖÜĩŨīėęęęzÂī<xPũtä™jīŪ]iⱉ–h­J,ĀŅĒh5E(Lô ÍjŠ-Yã įīū§Šn¯ŋķÆwmw†Ļ’tå'ę 3áC“ę4;+ōtÄqfXōOĪđũ1ČŨˇđyB€xׂŒĘ‘í‘3§˛—•xQI¤Ką•äƇú{F…ėŌÜe7•_ÅIc“[§kú–sįΚžžÎČȈģƒUŽ63näË?6īž­_ų)’7 æáÕI°īÎ@ąÂŞjëtMO˜7BĸM‰įXÖŨģŅAī˙øM^ü˜„dÂŒØpĶ€Ö‘wčÚgÍ*ŗđøąÕīâ†y@H&\, _ž@!„6›uēĻ'˧'B!”ØpĶ€B!YpĶ€B!YpĶ€B!YpĶ€B!YpĶ€B!YpĶ€B!YpĶ€B!Yp͐XˇÎ˙æĪ÷Ųׯzr6|úû ]›zˆ¯Œw䯟?ųŨo˙ũķž‘Ŧnž ‹Č}û?~˙Y›į•İúž¤æeũūú´ü*3ŋNœ"Kfķ-Ÿ5†›†77på“o'_S´ųpŨAũ÷sëajā9øŗ3ĮMš7'̝žzQĐę3˙ÚįOḊß=ņĻžz¯­GJ“WøJ;ßŧxŪ[MF-­PŊ)Y}õՋ‚VŸų×>wx"Z‰ŗip6|zõŲūF{cĢ“ļū[Ŋ™äė]-=N–%c,ĢŠ.bæŋōLđŪiėr˛LnŲášR-9˙ûﯛ“倠hŠĸúløú/^h"k~}ĒpáĶøÁ¯ÎËWüōÃbˆõ2o‡{$šæW§ öĨŧí›ķ7žWœũ°8ė[āc ��¸ÉÖo›ûœĶ™Ą/Š*ˉh8įũĻց Ī)J&{oeU™vŖ™svŪjīsx|~!…brJY÷kIāú/ŸopĀÕß g”}ôq%õ�p7|zåŲūĶĨžļV›ĮËĨ1Õ;¤§|ũ—/4Ō™YÖc ˙Õâw›­7OĩwŒy8R[Ēę÷§ Ūjév°ŧ‚6–×-bÉT„ÍŗXüĄKŽE´åɸʖŸ=Sĸ ãøö“/<o˙ōŖbU„’^ėËyãWŲ˛–gw;Æ<œ@ĒôÅÖ#ût$DXŠˆ]sũĪ7¸ ũŗßļ3UGŗēnˆ|´œÔIÄ#Q‹ÕëHZŋ˙P)×øå˜áĖ/*Ôō§,,RņŽ"×Ō‘ā49ēŽ5tN˛Ą ÖxĻLzDâ}…’XėaC‘Xøá­%Ä)%–˝å™'–ëBX{+‡â'"ĸĻĻ&øĶÜÜ\jjjÜ ­ōp€÷=xę™ņ­ĖߊJņ4_úâ~Āh}īxåūŨĒgŊMíĶķ Õ$€āhēøõHZɑ÷Ē÷iīāíļŅäŨæŦ4ŪūŨĨŋŲ’ÍG=TZ ælíˇü¨/ŌoSŊ|Ü=0Íė-ØÜ#ņã­MãéeÖŌI.Ņ^Bã)4ž5ŨÛķŊj)39ØÂãļÛŖio[KÕÉ!Cˆ5°ô$ā¯^j™ŅZëO֖ä¤9:ÚÍĖ%k‹-Yi ˆÆÆ\ŋØ2m­?VQļĮ°Í7ÜÔâHßS pƒ×ūÚ8Ŗ­>ņnõËnfĻ˙öŨ'EÛĶƒ!ŲūĀ­=úĢ*rTФč‡(�ž?î{0áüöXOŠ<¸gÛÔũ–ģ͚}Æ´âéũ(™IwĪj,{wĻÍ7ëōĻ—¯¯Ģ؛áėhé~Ėí<|âũÃō^>lj{ŦÚcR'‹§"ŒDžÅâ+død†čX^ˆˇ<3Úe›ĶiæƒņŽŨâ´Ĩ šŧ¤ĶBvč>{īŨņ,šôøéŖ•ödLŨģÕáĻ÷æoW,?*ÉškZ]X´ĶgĖgÎ֗gũĶŪŗ˜UßøR†eĻN"‰Ēāú¯^jŅT×Ģļd ƒ-wĪrÉģJĨĢW”DG‹#R…×ŌÜãžA÷ŒgfkQuíÛÅú4W_gīlpZ%ŠAbĘ$FôÜ.ڗĖ" ‰…*qN)ɲ3ŋļgųׅĐIˆ8œ"SØ‰(tjBŪØÖ隞XīiđĨäY÷įjšäĮ:ú§™ōŖÖŧL•JŠ1ÕXM¤Ŋk` �ø‘Î!NķN]™žĄ­Ųz¤Ō äŧ<pãŨ#>Mų‘J=Ŗĸ”jÃ!Ģ%ƒė™€2äkĀųp’öÂO; ŗĀ@D/áņ( EFÂ=hķ͡ār@ö^CøŗŒØnŧß úōŗFIŠcU•‘ZÍįaũÔ.SŽZĨTŅ™ųĩõg~V‘ŗülLkΜũIYCĢT´&īm ãwL¸€ I‚� HŠ$ y‡ LQuM��•kÖR~Į @ĘȌx��‚ē¨\O�Tļ! ü Û_ʐ�m0¨„i+™ŠPy?lübc‰Ö˛„Ĩ’^ų7uŅĄ<%@PšK2ũöa;ŋâ(ņŽ ‚"�EĶ‘ĖÔIÅ#^ŪņA'čËk,ZEkKUh<ũ΋R~A+k‰O)˛VåihZ­/> O™ŸÖøĻLbD}…’Ų¯ÄÂë/N)r3ŋgYׅP"Ñ>!™įå ��ĨZ3‡uN Ęíâ#"KĪ.jÖ5%Pę…Ë‘Yzė=��§kJPh”‹­mĶ2D—Įå#[ iną=áķrIāí6'hkŒ€Kŧ*<"ےO~14–ėŖAp 9Aĸė:ā‰=0ÎåĒ`i›L똔N6ZčėåũžëW%EFVĮPjÍʛ¤Šā[[n¸Y//Á ’>eF9„ÚļtP‘J€ĀdfF’Š^¸ *H‚�ŗ(‚ !HĻ"tÜŦDˆĮ–�‘ąH•V”Ņ-•Đ ”šY _EĶ)‚kĘųdøQŅ…ĖÔIÅ#^>×3Pî]*x­QC ycîwņ1ąW,��••ša‚ÜJ‚‡@ŧy“‘D_Ądöˊ/üP‰uJYN$kæ‘u]7Žá ™kĶ XÜ˙ņ|�|Ũũ}wØß3x Ā€ Vnyž‚ š\ ’Aā€2š2›nÛBŽ10ųĐēÚ\Jē*< t–Bz`hĐŗ¯’ž´9ƓŲËCˆ#0>‡B¤bņ(ņØ´‡?ü€îęėiėoöʝæĘšųįЋ÷íË_ö…ÖÚC:šT�7øõį+BƒXQDۜËȌL�€Vmš&^�ņ‡Ey,ŅZ– JCR)!#  — Đ…w×ÁÃeĨN"‰Ēā(B"HŠoĖũ΋Ŗbį#ôÛøō&1"‰žâčWbá‡J¨SĘ ‘ŗąg9ׅĐē"â’)ą6 KHRJãûõBˇį„‚Rđ¤nåâ'I2ü÷Īķ@—Ĩ7i„f›CĐųG]g Ŗô˛Shfzmž2m‚Ė;­]Qdė N˙KÔ ��•iŠ:aŠžuŽö´4]ŋĒPž=Ŧ yäÔĐC–2˙ŧÎ<˙KŽģĪI‡EÍĖ*ɜ&ɐ+âXbiYņšEĪ…ä7Āķ@+—ã*K…GŽGĸ*ƒž“WŊÅ]~Å7e#ZÛ~Iņ…ŪZRdZ×3üēZĢá ë= Kh­†āŧ<I/ĸŠ$€ÖĒ ßS—oáĄîÎ˗ū>äFū{xæô)Œz~Id›ĩ‚Ã6iu‚Ū¤'Ŗõ)&ŗ%Ķ;6pghrk~a„ō‹#0%ŗ ¸gS‹Ģßãt ‹­‰Å&xŨŖv6ø8’ÖšWäÜ÷løŠ…@ŠF"xFFY€ĐëŲĘk[ÔCDEˌX2ɜ&éŨY„ąHĩL(Ās‹wŠ}ĪXš×:ßĶĨWÄYĪ´@Đj劭rPąTxäx$Ē‚fTā›Z,<Á9*ŖzEÅT~Qk)ž)“‘L2įKbá‡J¨SJPÔ|Ŧë™G^]ENÜ'"�‰ģi Ĩ&ĘÕöÖ1—ãX×pÃå Žs�@Ęō”Ž;7[ĮÜSįā­Æh4J sËLJם›vÖËųĻÆZnôûKąnūy/•“¯å'Û:ėcĘ&Ŗö •W¤ū ÛŽ2›˜Ž#0•Áœ ö;ũNÖËēmˇÚ&„”ÅÖDccžšz­grĘËyŊ{O¯”YËŪ?ŦÖ¨ Ī`Ī8ËqŦŗ÷ZƒW­%¯ËÅ �)¤üė¤ŨÃzy™‡D%3b=Ę$sšĸ€\Æ"Õ2­aÖ>ė‟ęiääö—âhęr˛įuö6õx(ÃÂ^v K…GŽGĸ*TŲfėwZlĮ:ģ¯ßu2ĒĀ;ôĪ/ļ8–••Üō“WKņM™Äˆd’9_ ?ŦĩD:ĨČĖüēžydÖŗčpVw"B�¸/O�ĄĢ=]O6ˇÜēŌÍų!%Cc¨:}8øzBg­?žÚŌqëJ'“}đTM) �¤îđ铊ÆÖ›ŸˇrĸdrĘëĢ÷3‹Ģ•2äén}g'vWë ŊDBfhģP”ųŊTqĻ´{ŊŲÜō՟šˆ ĨĒÚÔ|uŒ›žæ§ĩÍM]ß}ŪĖ DŠ’Öšũä➋5UXWëēÖöĪ€ĘĖĢ´1>ožúæūĨËpæŖŠœ’=ôˇŊ_^ąœúĨU+įC”銒2¤ĮŖQšŠ˜XYĶĨ�äŠ0‰–Š‚Ã5Žëí—>í%He–Šę‰Ŋj—õ…ĘTQĀŨŊōGˇO iũĄúÚÜHOÄW9¨*<r<’…Tú~{ŊũƟ?*ĶX^UI]ûÚŊ_žõ¸<+Ū] ÕQvhBBj鄸ų+ž)ŖÅG$“Ėų’XøĄé”"3ķë{æ‘UĪ„čpVy"B��[Ν;üizz:###î†VyøÆĀ|ųĮFâŨŗõ†˜^'lĻĖŧŠą8>M¨X&Ūxž‡Å¯ņļ¯Î߀ē_ŸĘ‹–8÷í‹C†ktqÅēžâB gŽé {§!ÁđËēûo7:čũo‚ëâÚL™ŲLcy|ũ_\hōįYėĶQ‚w˛Ģe’Đˎš8Á9ôXĨ/Æ*Î!ôæĀMƒ,ŪĄkŸ5{¨ĖÂãĮö%ČsÃą™2ŗ™ÆōJ(-'OđˇÚ[ŋúį'R”™ÆÚĶÕ2ž“ښĩQõZÄ9"„ŪøōB!´ŲŦĶ5=Q?=BĄƒ›„Bɂ›„Bɂ›„Bɂ›„Bɂ›„Bɂ›„Bɂ›„Bɲi6 ΆOĄ‹}Ũ‰ũŧžá9n˙͟īŗkиÄá] Œ��ÄIDATqļ›lŪ‘ūäwŋũ÷Ī{|ŅŒÖN<“…zSmėM7på“o'�€6Ž;¨ũ~ĘĩöŠģ“–PÁÄijā9øŗ3ĮMTČ´ĸ7Î>B‰lC÷„0õÔ˜˙™ŌäžÂŽ_qwŌ*˜8ņŧZZ‚}iZŅ›&tQ#„N"m8gį­ö>‡ĮįR(&§äuŋvūģbßčÆŽ!'ËÉä–Ž)Õ ũ—Ī78āęo†3Ę>Ēáŋ ~ˇ¯ĸ˙â…&˛æ×§ žg†üęüˇ|Å/?,Vgījléq˛œ@(cYMuūu4î†˙¸ôÔ˛đÁ܃KŸ6ē 'ū¯÷s‰ā_?Ŋ2ĩ˙ã3%ŪxžJ˜÷67ļēY¤õÅÖ#ûtķ}G*ø¨ÉÖo›ûœĶ™Ą/Š*XúCØ÷sÎûM­ŸR”LöŪĘĒ2-%ŊRą…E §hlˈ×ņ|ƒ�Ú?ûm{ú[0; Ķúq%#ÖŠŗáĶĢĪöŸ0Ú[´õßęÍdԎ–YŲ‚ø�#T 9˙ûﯛ“倠hŠĸúløâ*ÂĐx”y;Ü#É5ŋ:U¸p‰ˇ}sūÆķŠŗĢB[`Q'KN)Ɛ7ŅjŒÜž¨?ŽdÄ* !ôz555ÁŸæææRSSãnh•‡pƒ×ūÚ8Ŗ­>ņnõËnfĻ˙öŨ'EÛ�‚Ŗéâ×#i%GŪĢŪg¤ŊƒˇÛF“wí1$Û¸ĩGõAEŽŠŗwĪj,{wžĨ|ų¸{`šŲ[°=¸#âĮ[›ÆĶËŦĨ;’\͗ž¸0Zß;^šˇęYoSûãôüBučY1-ÉÕ×Ën/.d�ÂãÎf§?åĮ-ē’œt�`mm=Ū늞lĘ7>ß]€ØĪĄ|ãŨÃO<ß{ˇ—ÖÖž]Ŧ‡'÷īu{Ô{ķi2ĸ�nđęĨ–­ĩūdmINšŖŖũŅĖ\˛ļؒ•Ú)?rũbËtļĩūXEŲÃ6ßpS‹#}O:Y"Ŋą…G"N‰ØäLqSX´ĶgĖgÎ֗›ōČĮ‹Ķēå{ŅN|Ü÷āŠgÆo<x´2§*MŊŖe[äe-¤xDûŠXæŦ4ŪūŨĨŋŲ’ÍG=TZ ælíˇü¨/ŌoSÅS„Ąņߚîíų^ĩĮ”™láqÛíŅ´ˇ­Ĩęä!ÄXz’ÔdÉ,EŲy­FąŽŌCčĸVlėWOz­Ö隞8Ģ’4֞9û“ŗ†VŠhMŪÛÆī˜p�đ#Cœæē2=C3ZŗõHĨAÉyy‚$ € )’$Bĸ ųp>œäƒ˙ËO; ŗĀ@?ÖŅ?͔ĩæeĒTJŠÆj"í]SaaŖ\N—��đÔîۚ_¨Ü�€w9YRĢ÷Ų_Yt´*OCĶj}E]ãˇŲy�7Ūī}yYŖ¤TŒąĒĘH úđyX?ĩ˔ĢV)Utf~mũ™ŸU䐒镈-”Dœ2c (B@P$I’TČ´FKŽ/%Īē?WÃФŧŽ"$lą‰žD*¸ņîŸĻüHĨžQQJĩáÕ’ÁöLņáb<JC‘‘pÚæßĘ;‡Ŋ×ūΕØ“š,™Ĩ(?obÕ(~ˆØĸF%ˆÄyy‚ Ü`kË 7ëå…@ øT�€Į5%Pę…Ķ%‘Yzė=��đGn‰Ę-Đ4ˇØžđyš$đv›´5F Ā圔Úň,=C ¸\¨CNÅdfļZp° gXģ4UEēŠ›‹/Ŗ.ģ‹ĐeÅ;Be–fņÆ2Íd¤ž)ädEŦË TÁŌs>ZĮ¤tŽ|Ë;ŖŧßwũJ ¤Č¨ĶęJ­ ļ"ž^‰ØB/ÅŦxœ2c‹CR��JĩFá°:ZjAr€‘+Đéš”åbkÛ´ ŅåqyÁHĮY„KņŲ–|ō‹Ąļd ‚cČ ú#ÆeĪųŖ†D`œødEËvĖyĢÆŠ:B%”„Ų4îۗŋė# ­ĩ‡t4Š�nđëĪ;‚ 𠨞wPFSfĶm›CČ5&ÚAW›K�ĪĀ×ũ×ßw‡=8ƒįBOU*­NÕūÔÅõäésÆŦQfi¨V‡[0Qv—Už3î'@$•˛ô? R‚āÒ@d¤š#´‡?ü€îęėiėoöʝæĘšę<šH¯DlĄ$â”[´"ˆ–EħŖąt´Ô‚D_bČķ<TȅœP$qaȈĨôė̤'m…ņdöōâLb˛d–"„Į)u”H5ÆŌB(Ą$ĖĻajč!K™^gÖ˙Ÿãī#¤Ž;2JoŌÍ6‡ ķ8 ģÎ@.´Ŗ4ž_ ô͋„‚Zū\•ŅkÉ>‡›ŖėS*m ´^ˇœĪŧÔSŽÉ_ųv:Ųx.äÉn€�A� /*RA€Ō€Āķ"Ą2-U',UĀŗÎŅž–ĻëWĘŗ‡A<ŊRą… ''/6‰)#wĘVŨ‘t_ŧH’$ū{įy ƒWëÕ!��0…fĻwĐæ)ĶØ&ČŧĶÚģąĨ!˜ >Yņe[ú¨ˆÕ_GĄ0īiā…�PĒ… •āe‚§6ZĢ&|O]‹˙æģķōĨŋ-üoÄģÎTļY+8l“öQ'čMzrž Áyy’^D)RšrĨß .û⍇Ôhi�`˛ÕĪö‘Ég*­nį5nĘÍ-üĖzĻũ­QɎJÉlîŲÔâuĀ3˙ļ‹p‚×=jgƒ i­ųpEÁ}ĪōR镈-”Dœōb‹CøHĸu*!ĻŽä PŦMøīá™Ķ#¤0ęųëåNJ0øhŗ%Ķ;6pghrk~Ą&RĖ1&1Yņe[ü(ŅjŒÚQÔÉBŊ& ŗiPkԄg°gœå8ÖŲ{­ÁĢÖ‚×åâ eyJם›­cî)sđVc‡ 4%@ Š�?;i÷°ŪåO|-?ŲÖa‡SöüɈ4”š(WÛ?ZĮ<^Žc]à —/\¸>Ė­ˆ…Đęŗ¸ņîI^|Ķ#ŠŅŠ<Ũ=.RkP¯bˆ;đ]“å8¯ĢˇŠĮCL:BvT*ƒ9ėwûŦ—uÛnĩM)ú`žšz­grĘËyŊ{O¯”YjR*Ŋą…’ˆSflŅbX2­ wĘâęHö�Å*Ė-3)]wnvÚY/į›kšŅīc,Å Š[UÎ7‘W¤ū ÛŽ2›"Ŋ˙6ŽĀ$&+ÆØĸ%VR…-jvāÚį—Û]Ō „^Ą„ųČeōöŦôŲŅžģíw{GžĨ[­ûn´¯¯{âEŽ%;+'{ۜŖŋëîŊn›ëŎīŊģG2€b+ɍõ÷ Œ ŲFxöYĮdÕË'÷†\IšUÖŧŒų­Q’*א9įėģw÷ÎŨŽūG,Ą-¯ÎL¯|‰FAū8Ūųh:ŗô°93�Č$OķEÎÁʅļbūČåČŊ‘-Ū-bīŪ¸Õvgp*YW~ŦŽ0C!?*2s×vŋs°ĢĢĢgč ŋķāAõĶ‘gĒŊ{w…~ä2)CŸ>3Ú{˙NĮŊû#Nî-SÍ҃Z2I*Ŋo=-t8qJÄ&sŠw =CŗšbKVZØ´î+Ū+Öés{Īāôöĸâ]+^—ėhëŌã–ĩ 1Ā$•HǞ ęšÉû÷ÚÛīö?tŊČÚWwŦ\ŗøŒ9Æ"Œ4"EúgīøÖÖ};"=ã#0‰É’YŠrķ&ZR…Î~ŠúûööGÉšvo‹0v„”uēĻo9wî\đ§éé錌Œ¸;Xå᥸‘/˙ØHŧ{ļŪ˙›iBo uēĻ'Ė!BĄxŽeŨũˇôūqĮ€J ¸i@(y‡Ž}ÖėĄ2 ÛËŋUŽBë7 %"UÉ˙wÉë!„Â%˧'B!”ØpĶ€B!YpĶ€B!YpĶ€B!YpĶ€B!YpĶ€B!YpĶ€B!YpĶ€B!YpĶ€B!YpĶ€B!YpĶ€B!YpĶ€B!YpĶ€B!YpĶ€B!YpĶ€B!YpĶ€B!YpĶ€B!YpĶ€B!YpĶ€B!Y¯;€%O|đ§^āüđâuG‚^ģ$�*>Ú ZÕë!„Đ‚DšĶāž…OîÁ,î��ŧ�˜åáu‚köu‡‚BhAĸlūÔ/_w (ąl—/áĪ}¯; „B eĶā{Ũ ´|X!”0eĶ€ˇPDørB%ŽDŲ4 „B(ÁáĻ!„B˛āĻ!„B˛āĻ!„B˛āĻ!„B˛āĻ!„B˛āĻ!„B˛$ĐwOČTdÔaŋ øÁņÜGā5ÅôË)€} ~w<¯;„Bëmãm��æāŌ,~)Az:ʅŨ ˙Ūš.—Žt-ü7üĄuh!„Ú86æĻ!�.6dĀÂ#?ü?{ā  žöŽ}oÕMB!´–6ÉÕpÎĶ�éi� ›†Ũz8˛ ˜ĖÁ°Ž;!ø%é43ÂîtH˜ž…ÎQhaĨ)Û'3��ÎeAË]¸ōĨ‹'c‡ámp„†¯[ ' Ú)$Aa.Ôd“sŗĐ2ėÂīP“ L ü0ņŽÂ4@Yû'ü^Xü⅒bøi2üĪN˜Zh<|°'�˙ŗwé~L‘~JÂī:a:4q1�GLPFƒ"�Ã@øDđv„BÅ&Ų4(Ō ĀáŸ˙_]üB Cđ—H§á§đÁ øĪ§� 8i 7üĮ�Ė%nü´fÛ Į/zHO¤îƒCĪáwa.ü=Ą…ŨOá?FÁī '>PCã ķ Û'‹îBģ ā˙TCËø‹R߂“&øEüû ?…cy°[ÁPDÃÄLK -<ßVØc‚Âčô/ĩā ß1ÄX� ¤Ĩ—÷á‘tģāˆ`iG!´l†OO¤Ģā§yÎAĪ ��( F ŽqøÚ Ķsāx _?…ŨzĐ�@*0 ˜x Ž9˜æ`ā!ü¯ûđ( uH ��/`6�+ßg™€¯íā˜…9‰Np( ŖĐōxfĄį!ܜ‚ô4€8˜ Žq¸ųLûÁ5_;É‚ŨI0;€ĸíķŊ¤n‡€)ÉĄ…Į33Ã/ $sĄr�:§ÂŖ=0H˛ x4=^˜ƒá,ĩsÚBm(ķNC:üwkØ/ϧá/ƒā~%"š$šûíx-čRĀÅÁŖ9(+Åcfab\ŪųE‘|6<í[xî.ŅéVĐ$Á€oá/ ã��Đ I‚™ĨC<ĶЃ.†9đ‘ę†9€Ũ;�Xö¨ĸÄšĪ čtÃŋfķ<�9€B.đAĘØ#!B˜đÁĄ­ņĻ!„І˛17 üe`ūęČė‚ļÃõž+b2(�–ÁÁđƒR�~¸Ū =”낞<ĖAĪ(\wC@ō 9&`våw<+ `6äBxs� �ĖŋB‘“ÃIP´&Âl´Ą…Å0áJŌá&E4 ÷¯¸SG`Á?…"+bi_Ņ3BĄDļ17 /Ā3;˙é ×n‡cy01´đļÁB� §Zž‡2üŗ:G sR)(ÜĮö@āG¸.}ˆ-( �ž2Ķ˜ ˙Ŋ"äj=û8  ˆ† ä�|=%chËĖBĪ,”dB‹vā?WžņđŸąF@� =äE­ÔŇővŧ߀BĘÆOC�nŽBjĄ~Áãd(ĀÃ-üį‡@�æ�ŠP¸}~Ŗ4ĮAĪ<zš­R‡Ė‹š'‰8pŊ�Ũ[K=´>Čđ-˙=“Š�¸ޞ=,äė€Ũ Ā ī�ˆg¸'d¨ĄFŗnpDŠ9æĀ~�Ŗ\úSÎ[K­Åœv„BĘÆß4�L?…F/”€.8š�t<ލ!#œÜ˙m¤ĀV8iv&2Ra÷.Čpø$˜{Š[aw:dHܗ‘h!�-nĐåÂ5hŌĄ¤�jTā˜@ËSĐå¡퐑5üT .'L,Üüô=¤n‡šíđČĩ°-ŒsĨY7<JƒÛĄįiä˜clzŧ°;ĘhČ  ¨�v'-ĩsÚBm(DMMMđ§šššÔÔÔ¸Zåáãrš#ĖÉp× \Č/]ŗ`ÖÃn:g��Ļ€'ÉP–5šPļ,|9 žđâGãĄpÔā-d“ĐgƒÆix!~�Ė ? jAÁ‚-äIũî]°í9Ü]¸í/ŅÂô3đ$C™gƒ. ZĄÍ�0͂+ŪÉk.ŊŽI¸<žtÛĀ?Ų9 { 7Âŗ—Ņ{Y�Ā € Č˙'|=ųnD=yŠ4ŧŖ‡ƒY:ŗ`N‡NpqĨ]Žš\yC!´`Žé[Ν;üizz:###îVyøš†¸EâRā!0™Šū؄uÎũ1!„B­Ķ5}3ŧ<"P�Ŗ‚c{!‡ƒ›yĮ€B(qlĖOO h2˛āŋįÁŦ. ā÷O"„Z¸i؜ĻÚĮ¯;„B› ž<B!YpĶ€B!YpĶ€B!YpĶ€B!YpĶ€B!YpĶ€B!YeͰåu€Sĸ(BĄÄ9'+I€—҆Ū,/A˙?ŽBh%ĘĻáŒļāŨnËø—Ŋ¯;„B eĶ UÁ-ƒô|�lHO_€Lą¯ũF!ôĘ%Đ?#­UÁ˙{čuB!‰r§!„B 7 !„’7 !„’7 !„’7 !„’7 !„’7 !„’7 !„’7 !„’7 !„’7 !„’7 !„’7 !„’7 !„’7 !„’7 !„’eÍ6 III/_ž\ĢÖB!Ÿ—/_&%­ËM5kTĄPøũūĩj !„BņņûũÉÉÉëŅōšmŌŌŌxžŸ››Ãû !„ĐkņōåËšš9žįSSS×Ŗ}ÅZ5´eËĨRÉqœĪį{ņâÅZ5‹B!™’’’‚P*•[ļlYö×lĶ��[ļlŲēuë6ˆBĄÄŸž@!„,¸i@!„,¸i@!„,¸i@!„,¸i@!„,¸i@!„,¸i@!„,¸i@!„,¸i@!„,¸i@!„,¸i@!„,¸i@!„,¸i@!„,¸i@!„,¸i@!„,¸i@!„,K›†¤¤¤—/_žÆPB!ôÚŊ|ų2))ō=…Ĩß* ŋß˙ĒBB!„P"ōûũÉÉÉ˙´´iHKKãy~nnī7 „Bo —/_ÎÍÍņ<ŸššņŠÅŸļlŲĸT*9Žķų|/^ŧxU"„B(!$%%ĄT*ˇlŲņŠĐ˙Ų˛eËÖ­[_I`!„Ú`đĶ!„’7 !„’7 !„’7 !„’7 !„’å˙æįH{x1ß����IENDŽB`‚����������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/reset-credentials-start.png�����������������������������������������0000664�0000000�0000000�00000047624�14156463140�0024145�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��¨��&���Ņî0c���sBITÛáOā���tEXtSoftware�mate-screenshotȖđJ�� �IDATxœėŨ{|“õŨ˙ņXr5ą‰2ڟØt@S)s¤7-ˇƒöž- €P”ƒĀ2!āŠNNå¤ )‡ûnË-÷-ģGÛû––ÍĐ‘-É6’ôôûŖį큖¤\¯įÃ6×á{}Ž+Mžī^ų^WzTTT���pŊëéë���€kā ���U ø��@j~*++ģté’Įã)++ķaA���ĀÕ¸á†EŅëõ={Ö;ÉÛŖōâ˛ožų&00đÆol°���Ѝ”——_šråʕ+ũúõĢ›lĢ‚¯ÃáčŲŗį7Ūčģ ��€NķüCDúôéS3Ĩ*ģŨîŪŊ{ûĻ(��� ŗõîŨûʕ+u§TßŌŌŌ=zøĸ$��� ķõėŲŗ´´´Ū_•���\K_���¨Á���Ē@đ��€*|��  _���¨Á���Ē@đ��€*|��  _���¨Á���Ē@đ��€*|��  _���¨Á���Ē@đ��€*|��  ž.�žgûĒđƒöüãŸWĘËË}]‹ôėŲķĻ{?4yŧņö_×��Ž+œņU;ÛW…ظÕõ÷øCę‘ōōr—ëīظÕöUĄ¯k��ׂ¯ÚmÛąĮ×%4ŌŖ‡TTøca�� ;#øĒŨß˙ņ_—Đ”=ūy劯‹���ׂ/ü”Ÿ Ŋ���× ‚/���Tā ���U ø��@ž���P‚/���Tā ���U øVIo4čmēGŗ>ŲNoÃ?669+āæž&ķĪ~z;߀ ��ü×uŸTÜįRˇũ~wš5÷tqąÛÔ°Å2ņĄ§Į˜ƒ|][7Ķ{PÜÜ1}įĪæūųķãŲ_ũŊTõ?ëĢ ׅ/ŋp˜ˆ”úēL��€Ļ]×ÁםķîĖYÉÖb d‰ˇÜĻ(âųö¯VëmÖģ?˜šaëŧ¨ĻO`ĸYŊtũ 3`XÜ7šīíūß=˛ŋtWÎēŲˇ•��´ĖƒoÁ‡ 4†žgÎ~;ëŊ5pa÷ĖiÉVwß1+6,Ÿ8¨NÂuŸ>0ÖüÔwŸ™?ø?ׯsŪˇJJž/ü"?ëĪ'smzEĪ\6&qĄ9îĖéüŋäœĖ=íëâ���ZäËāûÚk¯•W+++Ģü÷‘ģ*zJšFßW[^V^qæ;Ö¸;uÍ Ģ;Đŧxëę‰ũëĪ 4fíĪâmį,ˇ4ŋ~qÎîäßoKŗž+vK`PKÜÔ§O4ŠäŦūĀļ[æĨ˜YŨėšwãã“ĪŽy7gulõęŠsĖs2,kŗ&V=nûZŧõöžú*|ņą]SûÖ­įĀĖáķ3UM.ÎH^’ŧ;ã\ąHßAņO/ZÔžtŠž?‰îÛëbĘŌĨ\Õc~Xųŋ^ÚĐ솆Ū5tŧ”8Īį˙PŌŌŋ(îâb���:—ÁˇŦŦ,$$DDzôčQķoEÅ˙ũāg˙"ŌÃķ÷Ke%žŠŠōĩíÎHIuK☧§öorū ‰+V4ŋvqęü_Î9PÜ?~âŧĮ,ˇÉˇÖ”ģOŗžŪē8Ęk ܖš‘ãžŲŋō,rą5ãÛĀĀ@ˇ5ã´ÄĒl +Ãę–Áņ–@I̜Đöĩ~:ãÉĀU 3v§œ›:ŗļöâŒŨVw åĄŅ}E$gÅ3ˇ ˛Lœ71ö6Īš´mĪĖēĨ‹“īÍŊžûŋŋüĩ´ˇáŽŸ†œ=ņåwuæ•dîü*$ņŪP]ŋŸü2úo˙õEö•ęY q÷Ūwoƒļޤ}z8íĶŽ­�� _ŪÕĄŦŦLę¤ŪJeeeåeeîâ’^./ņV”—u¨íĶÖ\ˇZâ,Y9+yåâAķvĨŽ]<uL|ü˜Š‹ßũduŦœÛļrÛK\l ;7ĶZŊ´5#W,ņņAÅšÖę›*œÎ´Ë XKam_kظ‰ņrz÷î:cŠ3vįēc§Æ‰¸ü~÷9é?uÃļSĮÄĮOœšúĀǁ_vwdOÛëĘwɕ›ūŲĩ1]É=ž™õég_–4ąüá´OÔΏ¤^��ā+>ž=)-õ–—•”•zËKŊåĨžŠōŽņũörąH`Đ-šx-ë㔠Ō?6öļâZ2x´%PN§fK %v°¸­9UKgXŨũ-Å Ž Û""ÅšÖsŌ×[÷ds{֊Ÿ0:HÎĨėΊ^š2÷ƏŽkĻÕ-}c'škš:ēé۝ëá˙Šŋō}iĀ÷ knŪđŨ˙?>ōÎâ\[Sɡ~ö%õ��ōåP‡ŌŌŌʰ+Õg|E¤ŦÔ[QZZ^â-+õJEŽß@ˇxęM,Ūũ@ÔâœēS,ĢsļiŽ/üõœ[äôģcĸŪmÔėW§ŋd‰$ÉššÄÜWNgZ‹ûZ,æÁƒÜŋOˑø(q[3NKĐč¸AõV jĮZQS'ößũnĘY‹ÍQR•{ƒâ'ċˆ\øöœ[äöÁõ’î Áũå\‡ŽT;|Ÿ˙§ãrëŨqŽĶ˙W“{ŋÍųül@ßÁŊsO_A˙¯ÉõjÂ.Š��øĮø6ž%ĨŪōRoY‰§ĸÔ}›áâ—ģ/ėmˇ‰\¸P\,R;Ū đöŅĮô¯ ÃŦŦM~߃Įí‘AS×΋mxžXšĨéoąô•ŨÖ\÷Ôžî\ëš@Ë<ŗô•Á}‹32OKÔ œ4Ģ;066ĒÁĘíYkĐĉƒŪMNũ8uuTŧ§íÎu÷øPå,Oą[$00°Am×čžl†đûgĶFūëíŽŋ‰HÉ×Į˙,w˙ĖđmAá×Ųoå˙ŨÖĖzD^��ās~tÆˇōߒoYii™×ŖŊéû]ęuÕ֚iŌ XKāļš)V÷˜øšL5uEMMŸj=ĐÔǎŨ""ũĮÆömjžˆ˜c-AÛ2Ōr$örÆéĀÁYDÄląîÎÉ-ÉĖ)´ÄÄ^ÕZũ'Nĩü~qjJęōxKځ\w˙ŠÕC”Ā@ˇÛíŽv+wĩ€ž?šÕ‘ûÅßK%ûO"?&ŌĢī}NíõCm¯{#ûÔw1Í_���Ÿķåߚā[WI‰ģŧÔSVâ)ūî†ŗgõo¯Ž5;at_qg$'gĩwÍ Ûû‰œļæ6˜^7ZÆÆY‹s2O[ĶŦîūą•ßgą –Ü ëš\ë9ßčlq;× =56Н‘’q:mwŽ{ĐĉÕ'úŪÖ?PäÛŋÖØp:÷¯íŨÍöë­uų…Ŗæ |@@€H¯¨í%"ĸŊ{ŌčÁŊģž��€Žō}đ•:ˇtčŅŖ‡×ë)/-)/ņ”¸K.Ų¤ƒc|Eĸæ-$įļÍz 9Ŗá†âœŨķ“SŨÔԁ¨ŅqAâÎØønléΘ˙ofķĖÕˇ¨ĩÄ ü6gcŠĩ8hpÕpÛ ‹ų6ˇ5íŨŒĶ2(ÎŌä÷b´g­Āø‰ņîŒß/y7GĖu/^ŗXĘšÔÚKßĝúAJ“Ŗ6:וˆü ä'æģ~jžĢmߚ->Í>s銈H/­ĄÁ��ø1ßu:|{ôčQZâ./õ–•–”•zzô(īpđ• øÕģVË´%Ū9|[KŦš˙-ˇˆįōˇįŦ֜ n ė?fõÚÕņM&ßyËĮdÎ9<æŋ>=1Žŋō­5eãîŒ }Į,¯ųžˇ K|wrjjŨÁŧũcĖAīĻĻd¸ûN´4}“…ö­;atЁŨ9įcŸŽĢ3č"hôĖødëmŗ¸üØDKPņéÔŨ)n‹%0Õ*]¨×­wü¤÷Í7”ūĶ^˜—SsS‡ gŋøķ^ ŧÕ>øg?š{Đ u���~ËˇÄSVZRVâ)/ņöčQŪŅ/°¨ÔĖęc– ģ7~°;ڑjuģ%0°īmƒc§>6zęÄøūÍ^ŋú“]–äßoKûũŠ•ßÜ6zÅ[ķ&šk×čk‰íë~÷‚XbkolŽx Ãd‰37Õjģ׊š8ēīîmÅąë¯r`ėę]koY‘œ’šŧ85°ī ø§7,˜ōoŠVˇÛ#]䙂€īžų˛é+ û6äíŨ–^ ũA@Įe��tĩ"röėŲž}›ģ”ĢĢ<üđà R?øŠuÕO†Ũ×C¤ŧ´¤ÔķĪsgōî[xäæ/.ėžúo‹˙:zk֊†wˆčDķ_|šKčbéå..<WxņīĨAˇŨr[oéŨ۝{âKWg×ļúĨ:ģI�� ".\¸ãŽ;júæŒīÖ­[ķķķKKK÷íÛW^^^VVVķīdã?OeĨ*7ioėĨQŽîŒoˇVœē&Ų*ƒæÍėÂÔÛnĨŽ˙ũ‡ŪÚīŽŸô(ŊR\hûŋĸŽŨq��āÚōMđ6mšOļÛ=\Č:ųW끍)Vw˙ŠËg^‹īdk§R÷ůžŧčë*���Úŗc|Ņ´Ü—,> ũ-3׎ž×Üha���´Á×˙įÎ9ŊÚ×E���\o|y_���āš!ø��@ž���P‚/üT孝��: ÁWínžé&ЍđuTTÜt㍞.��\Wžj75iŧøášÕ=Ļ&÷u��āēBđU;ãí!O<6íæ›nō“Ą=zô¸ųĻ›žxlšņö_×��Ž+=***DäėŲŗ}ûöõu1���@§špáÂwÜQķ3ž���P‚/���Tā ���U ø��@ž���P‚/���Tā ���U ø��@ž���P‚/���Tā ���U ø��@ž���P‚/���Tā ���U ø��@ž���P‚/���Tā ���U ø��@j~:wîœë����:—ĸ(uÖßAƒ]ķb���€ŽröėŲēę����U ø��@ž���P‚/���Tā ���U ø��@ž���P‚/���Tā ���U ø��@ž���P‚/���Tā ���U ø��@ž���P‚/���Tā ���U ø��@ž���P‚/���Tā ���U ø��@ž���P‚/���Tā ���U ø��@ž×™ĸ÷'FF„?yØãëR¤pkâĀãĀČŲiW߂ī÷��tw×”3åŅØš™Ūæfk4zƒ1tHTÂøéc‡‡(ˇŨÎæÉûxķq§)á‘Ø`_—Ō}pĐ��€ßëÄāÛ ¯×Q”ī(Ę˙üā–uą/Ŧ_?Ãä§á7{įĒ5ģd˛™ ×M4ðë’íĸ„DúŽ0��€j]|õcßųäŲ! §zEļ“Yû7m8’įrdŧ<{~đžˇâ ŋõĢV˜—īŅûēŒîĨéƒĻŖãŒ>Š�� ąŽ8ãĢ1 #­Á3z¤eÆ˲\ŽƒköĪ›Ö›ŋ:žSÖ|_×ĐípĐ��@7pí†:TRÂÆÎšnĘ>‡ØŽŸ°Ī ̐=…Yû7m9|,;ŋĐáōj´úĐ!–qSf59&ØSļuĶGGOä)t¸ŧĸŅęƒÃLCã&O{0Ψk´p[Z>ņbė”•?;>šaüHD4që˛×Įĩē[ž‚´›ö>–}Æîp‰ļ_X¤eüôĮŒ5Öļ^¸5ņ¯fKŋ‡?I_ Ųˇ|áē”ŧķãŗ‡Ô¤˙vížx 3?\ģq{fŽÍáÕhõ!ĻĄã§?÷HKĨļšũšRwĨ/‰´ŸØüÆ;{Ÿ´9\^6Ø=ręÜ9ã"ĒqK­ĒFĮĐStlĮģÛ}~ŌVäpyEŖŅ‡Ä$L™5ŠmƒŋÛõÔ��TšÖÁWD1õ"—ŗŪ•úEŠŋžõĖž3^>8Tņ8ŠlšiļÜ´Ŋ[ĮŋąyuŊqE)ŋš>÷đyŅhõũtâr:ÎdgžÉÎÜŋ=áåŋKiË:cxdxQĄíŒÃ+ĸ 5+ĸhÂZMRÛĮĪÎZ^UŒFŖņēÎįeî_‘yxû¸5›_QU‰ĸ(""^Ũē|éĸíE""âuUƒvížxōļÎxčÕ,WåŌũBtâĖ>˛Úz<íų—›‰žíiŋĻT§íÃG“–dēDŖÕ*Z×å*ĘOÛ˛čX–cīî™J‡šũčüŠĪīąUĸÕëuâq:ŠrĶ>ĘMÛģ˙ņ÷6/ˆjųˆˇëŠ��¨ŖĸĸĸĸĸâĖ™3WëōGĖĄLĄ?_ú—–s˙iŽ%t€)ôΙ.×Nüˊ„ALĄb&ŋ“ņĩģzę×o?:Āzį¤÷žŦŗĨÔį0…ˆ™ž)ûĸģĻÂĪ6Í6Ā:Ā2ëāåŽĩ\Qqęĩ3…0 ]’ÕļŊvļdDč�Sč sļWsšā?WO<Ā:Ā<nSAՂ÷N` `™>wæā;æŧsčO'˛ūtĸārGŠ,x{´9t€)ô§“^:VXŊøÅŋlj؝1Cj `ôÄ!wíōíl˙reŠæ„ ãũ|ÚkĮ ĒŽæå‚ F„6<ÂÍ´¯ˇŒ` `ž•ZÛô'*Ÿũq‹R jš¸xrËôŸ›B˜B˙õåŋ¸›lĄjj{žz�� v "îĩž¯§`į›é.ŅDŽˆŽ9ĩW¸Վ3^ãŦĩ›gĮÔ|Ü­„Ä<ņ^ō”`oî›k:Ģ?•ųšKD§.™a6Ô|6ŽGĪXģzúĐH‹Yį,ęXËíV¸ÕŪķ"͜ĩo%UŖ3ÆĪ[ģb¤VěŊaë‰Ę“ēU›ve¤ÛâßŪņÖė„áQ–áQF]û‹ôdmŨ”īŅDÎYŗ$6¸zqФ5o%iŽĻŠėČAđæåÉ#īmX[=~@gŊč7qZqČčĐ ^įņ=.Ņ'.\RgX‚!rÚ[ķ†iD¤čȞŧ–hĮS��Pß5 ž§=īØŽ’x5Û+"ĄÎ[ķázaúž,¯ˆfčãĶÍ Įx*–‡G…Šˆ+ķđÉ_bā(˛7üZ%zŅ–ŊlXdēĒ–ÛŦ0}_ļWDcž’Øāö†ø9Kį?ŋpųŧ ¯ķ3Nz<ļŪ§ųí-˛ ũ¸CD4æņŖÜpM2}’ŠŠ";v4Q=ŪāĻsēđ(ŖˆˆËQԑŋt ›˙īT۟šgiP‰Î<4LDÄYؖ†ÛđÔ��4ĐÁ׹küĀcÃ˙†DūËø‡—îĪv‰hçŦÛ°$˛&ųTß ÄŅÔũÍÂ,f­ˆ¸Îœ*Ŧš1r¨^Dģf?ôÛ3lÍĨvˇÜNžSŲų""†đÆí+Ļ„'fO{01&Ŧ~Ä Ž˛Ôŋ—E{‹tØÍmTB‡4ŧŠXĮB˜%ŧŅx[­Žrw<Ū—šĸ3ã­X,-ļÛæ§�� Ąkzq›&x؄ÉcĮ'& Š—Ā…v¯ˆˆmãũ76ŋv‘Í!&"ĸ‹]úÖŦĸŲr]9ģ–<ļk‰FoŒ3,näˆáõŽĢjwËíä´yED´Ámŋ#ą!¸A2mo‘.ģŗ…ęƒ+/ėxûĩt:m ^õ÷ŽØķŧųpZ^žŨá˛;\Í~×_SÚüÔ��4Ô_`ņĀĻ˙øMtŨxäąŊ˙PŌę|¯WôqI CßlŦę$ŸV¯o!Ui”ÚsēčyĨ<ŧiķÎÔĖĪm.‡ÍzÄf=˛}Í"ŊyėÜE ŒÔu´åvņzęßmE§i0ĨEzZÜhՉĶĢhŋncÍ/ŪAÎ+gĖŪ’_u; Ŗ1¨¯>…\tŌzĻņøäFÚúÔ��4Đ%g|uēúéK1=ōÚ´ƒ7æí_ŧōžC¯ÅÔĪ&šĒčū؎íøJ Cd‚ß%,OaļõDÖŅ´ôãĮrÎ;rö/y('īŊ+ĸtnšíĒöŗ:‹vH{‹ŦŽļMoŌĶčâļ.?mįL[öĖ–|—ˆ>fáúW' 1Ôų5Šēéo›´áŠ��hč]ÜϘžZ‘*"Eû–-Īh02Sgֈˆ8Šėj;$2fÂėߎߝnũdiœ^Ä{f{ōūÂNhšUúÎhŋŊEju•įŒ]M._do8ĩĢBÛyŽí;ęũØÕoOĢ—zEÄåh˙€Ũžz��€†ŽŲ]”!ķ–N ‘ķ{–žzĖYVT¸ˆˆãķŒoeÕ*iŌ‚éá""ļœ‚NmšJDdeûš' ÍĖÛųĖ“OĪ~ōˇ)­ąöŠ 3ęEDėų“lŪį'žņíęƒĐv{ĄWDÄ84ĸŅ Š‚ŒãļĢhēŅS��ĐĐ5ŧ¯bY°lŦ^DŠö/Nļ֍ž†‘ãĸ4"ræãõī&ë9šr”ųžÄŲəU1Īytū˜ØđģF-ĪnâÃ~OåWĄ)Z]ZŽ;¯mƒBF&DjD$ûkũö=Į6¯;˜ūĮ´l¯Žĩ ßÚ[dDŒE+"ۜíé îYë<ļyãøØáƒĐ^­4RųŦ8Ü-Øš|C~덴įŠ��hāš~….váŠŊˆ}ôÂĒŦ:áĮ0vAR¨FÄqøų¤ŸĒ™ã´Ĩ&ΚŊåŒËaŗĢŋY8<RįôzĪlzrÎęŨĐĘYöĘâgDD?2!ĸ-‹ˆčôZ—õhåiéV˛\ČØ‰ũD¤čŖ9ŗ“3 =U[?kū>‡ˆÆ4ũąá­^"ÖÎ"•¨IŒ"âÍ^9gqšÍSŗđĘYĪŌ_mûíׯƒfˆŽ ÉßöæĄĸęEœi¯'M}ŞøXœVD¤ +ŋŲCŪŽ§�� ž"röėŲ;î¸ãęšrĻ<;7Ķ+úöü÷o‡4ˇ”ũđėŸ—æ ~`û'ŋ­ũō6)JųÕŦų‡ĪxED4ú`Ŋ"^{‘ŖōĄiōÚÍ/ÅÔ&ŗÂÃĪL}á`å­ÄDĢ×kņ:U—ui͏­īšļ,r*yÔũÎÔ<ÔOŪl}ÉŌŌ~{l>9cIfå-Ä4­ˆË[ŲxpÂË;~—Ruxö%ũlQ–HÔĢĮv$6™í+Ō™õJŌŖÛō*€FĢUÄãryEųüÚ)ÖY 2Ŋš˜dë{ ēŽĩßRŠÎ^)bYšõÁ¤šyM´ĒëÕ4që˛×ĮUŽ9˙—ŗ÷‰ˆhƒCC´b/:ãp‰>f鎷qōɸ™ŪĒģ=Œ[šyļŠN Yëã‘ö>õ��@ÕDÜkũ•ÅbHX˛h˜VDŠvͯ7ā!xôīöĻo\:eä`Ŗ^q/*r*úШ„ŠĢļĨí­û$$á­˙ØûÎ ęCƒĩ§ã|‘Ã)úPSĖØĮ_Ũ‘žģAôiOË"sÖŽ7X¯ŅčC#L ŋĸ!Åøā{žņØũ–PŊVŧ.¯hõ&ËØųëöĻפŪÖĩ¯H]Ôoö~˛öņ„ÁFŊFŧ.—G ąÜ7ŨŪŗ-­"ĩ7Zë`ûíÕÖƒĻ‹YŊkķâqCz­§čLžÍĄņđĢ;Ŋ7)L1LXöōxŗ^#^§Ũ)ēfΓˇīŠ��¨Õ‰g|���?âë3ž���€/|��  _���¨Á���Ē@đ��€*|��  _���¨Á���Ē@đ��€*|��  _���¨Á���Ē@đ��€*|��  _���¨Á���Ē@đ��€*|��  _���¨Á���Ē@đ��€*|��  ŌĘųķį;Ĩ��� Ž~ũúuVS|;ą ��� +0Ô���Ē@đ��€*|��  _���¨Á���Ē@đ��€*|��  _���¨Á���Ē@đ��€*|��  _���¨Á���Ē@đ��€*|��  -Ė+++ģté’Įã)++ģf¨ë†nPE¯×÷ėÉŸŠ€?ĸ¯|Ží}eŗÁˇŦŦė›ožšųæ›ûôésà 7tv…�Ú¤ŦŦĖår?ž_ŋ~d_ĀßĐWū í}eŗķž˙ū{NÄ+đĄn¸!((HĢÕ~˙ũ÷žŽ@Cô•€?h{_ŲlđuģŨ7ß|sg #nžųæ+WŽøē � ŅWūŖ-}eŗÁˇ´´”ĪU?qà 7”––úē � ŅWūŖ-}eKˇ�:ĘS˜uxûŪ#'rō .¯h´úcø‘÷MILbP|]�¨Á�:›Ũú‡…ķŪĖtxEDDĢīŦˆĮyŪ–sܖs|ĪÚuąķÖŧ5ÃŦķq•� :_�čTÎĖÅSįlˇyE>~Îŗ'ƄU'\gÁŅ×žžę𙌗§'mØąČBö€k‰‘I�ЉœŠ/.ÚnķŠ~ØĒ];V΍MŊ"ĸ ņČīöî}a¨Vŧy[æ-ĪpúŽN�P#‚/�tž‚ovˆčī_–<!ŦɁŧJČä1ZGĘÚũ…ÕS{gF˜é)8<bløĀˆ˜sŽ]Ų� _�č4éGōD$øžĮãZÅ`=įž`oÎáÔęäĢ(ˆĮ•˙ūÂRŠ´–ĄFÍĩ¨�Ԅ1ž�ĐYœ§Ŧ6Ņ[FD´ŧ iÄíŽ"W~VŽį‘EDD#"âÍŪļĮøÜĄO§5}˛�pu8ã �ÅQh÷ŠHˆ)¸••`S°ˆx íŽĒ U hã‘z Ģ| ŗx\^Eiu”‚ĸ(""‡§ŪdũĐXS—T�‚/�tEĢqzŧ­-éņxDDtúúgwõÁ†.) � Bđ€Îĸ‘Â<›Ž×čC�� �IDAT§å=EyE"ĸ1ëëM¯: �č_�č,J„%\D\™GOļœ|ķŽŸt‰ˆ96’  �×Á�:MČČqQĮ‘wŲ›_ʙ˛a‘ˆÆ26ž‘ �p | ķ„Œ›ØOĕąrŪûyMžõõėˇ<Ũ%úāŧr/�\K_�čDJôĸ5‡kÄõųЉ‰ŗ×=e¯ŋ΂Ė”¸đ¸C´‘Ī'/`œ�\[|�t*Åŧāƒ-ú…ΝJ?“ļfNڍV¯×éÄcw8*īvĻ Ŋ˙ĨäՉ&b/�\c_�čl:ķ#o}xĶŽ#'ŦųöķEŅhõFķāč‘ S’"Zø>c�@—!ø@WP ‘ãDŽkëâqŋĪ˙kW–�`Œ/���T‚ā ���U øĸ}æŽãÄ­…žŽ�ĐŲZy‡OũU¤qāä÷ k~žūa 7ŗnfÅĢ׎Mûŗē;Ōš‡Håü%ønžl‘´ˇÉ_ÕĖųwGīųíÉk]��ūÆyęĐëĪ<”swdøĀãĀHķŊ‰Iŋz=%ĪéëÂjL–ؘđ_ܸć›î\×͎ø.n� ģ°§ü*iîáķšāĄņ‰ĶLÁzÅãČË9žzxãÜôŖYoīXëw 2{ÃæŲĒÛtįēnvÄß|�č<Yo,?|^ŗôĐ{“Bj'?ˇ$ãˇ‰íÚūōÆ)ąĪEøŽ<Ā˙ųËP‡vđ[˙|âŋĮ†ßaŧ+Ōōī͟YŸYo܋=įÃgē'*|`DøŨąŖũí‡Ų-}�”úĢHã]O§ZW=42|`Ô3mnĮn}˙×ĶGŪ>0Ō|īägÖgÖ¨Q˜Y=ˇjõĢ>‡*úpb¤qāä†# w&Œh§Ŋ [oTs҇55�ČžoF˃n;¸ Õkgŧ>{LŦųވđģG&ūzįÉÆUÚų\��Zā´Ų"Ŗ~Rē.öšÕë’ßY6ļvz[:§-eåŦQ÷DF˜īIœŊŪZ÷]ŧõwøæ5ŸÚÚļę+Jy2Ö80vöĄĸÆŗZí@Œņufī\ühbĖŨ‘ƁáwL|ōõԂ&ŋKŧģįĒÚG{Ū‡ŋžsw¤q`¤eĖĶīg;œ÷ņ‹ĶG֝RGËĩ]7ƒ•ũMˇ;ãë<öbŌÃû<Ļ„IKf…*W^æÎ×Ė>a[ŸūZŒNDėGį?0g34.ņЇ#õbĪ9¸eį’‡>ˇŊˇwITĶ#eQÄëH{yŲ)eØÜįƒÃ‚EÚŌŽũđė_ÎK턯%>õ°Ql™;?\3ûDŪÚCŋa¨œûĀŧ4gh܌Ὶ´ģuΆ] &~ž÷ÁŪ%‘Áņ‰æå99Ķí&jĘ(Ėܗ-š¨Ä_Ú°õF5G'Y–[ī9d{dļąĻM{æū^MTŌ} Ū"Ģgwxņdŋ’ôØ6›~č”yĪFŧ…i;ŸyR[ī3ļö?�€čôzČŠôĪí‰ †ús†Ä%Ô>jĩQņžŲūäéą%ī-4HŅąĩ‹VŦ™ã1ܜhļŧÃˇYĢÛĒĪybåŦų鞨6ŋ5*¸ŅÜ6t ux˛_I|h›M7tĘŦßD4Ûņí;6>>ĩčOÖÄ7ŪrÛ]Kûøė"ŨČg×Ī öäo]üë]+žœ—grØMO­ŪĒ]ūė+ž|5âĶ—Ŗ•ŽÖ†NQQQQQQqæĖ™ŠúOéR_oš:Ā4yĪÅĻffĖûŠ)ôįK˙RQQQ‘1į§ĻĐ?ĒŗÜåĪVL›üĖŋ¸+**ܟ-zᏎOškį_<4ũ§ĻĐŅ[žnfĶZ` `üČŪ:mļڎûO bBŒXtĸf‹ž°„ņŌŠšš1sR/׎ū冄;MĄļ|]QQqqīä;MƒęíEáLĄwÎ<pšM{ŅDÍîô9?5…ūۚ/ęŦŗûAsčO¨SEWˇ —w?bđŌɚÕŨŸ-: fŽ<hÁ5~Ih‹kũÂtgŋ6Ú:Ā4xôs¯mO˙˗—ŨM-ÔęÛīg ,ĄLCdÔžÅŊeÜ�Ķ G]ލhÃ;|ūsŽ9t¤÷žŽųyÚÛ˛­z+~š}Úāæ„YMw\­v õ6ũÅö§ÆžöZí^T\Ü3sĐ�ĶäíM†6ē&Uîã°ŲÕĢš˙4×:Ā4tnz͎|ąbDč€/Ŧ~ØZmuw¤î!BËZ ´Ũn¨ƒĸˆHQΊڏ tŅ‹ļėx{ÚEDrö:/ÆaÃõN{ 1ߊ‘üŖĮšųŧ@ŅDOŽû×sĢíäĖtHđˆ)ĩ˙â_Øŧ}ۚ)Á"’s0Ũ!ú_L‰ĢķįqØØ)&‘œŖĮė"†ãŖ4ŪėÊ5%ߓ#ÚØąÃumڋ&jV†MĨÛá=ŲÕSėĮ÷d{ĩ#ĮÅ7ũGúÕí‚''#Û+ÁÃÆGÖŦŽD'%ë´ßį�ĐÅŧāƒĢ&ÕŲŽŧŗtÎøûĸMwL|ōˇØk-ŦũŧoŋúøÄ˜ÚˇøĐ0­xíEviË;|{5ŋ­:ė/ĖXúš.aÍæE–fĪ.ˇŌÖ‘ôûŊļ,¨Ų GÜĪ Rh;ßLëWĶs量2WoI1„čE4CF Ģ)Ę` qÚ=­ ŖÛ u0?<kpęšũߛ3,.vXtŒ%ÂPũ{S˜_āÉßx˙ŋll´b‘Í!ŌėĮÁauoŌj;rĻĀ!b ¯;„@ 1EW>ļŸ)p‰˜LaõV4„ĩ’Sd+1čâ-‹3­3í&¤ęcíũãFčÚąõk%:iŦņŖ);Ŧ "-Šˆ=s˙I¯~tŌ°Ļ?›ą_Ũ.xÎēDLõVcxˆFlm<†|”� 3Mxi˄EöSYĮOX?ĪʲžHߕžkõĘÁŋąvIŦĄÍoŋú`}ŨYuú {kīđíÖüļĒyō^Ÿũė~ģyaÕpģfĩ؁6ä<ĩwŨ›;Žž´9.oÍÔÆC(Ē´zčëûÉûķĒ˙ÆPLcˍÉčú`m=ԊˆÎ ĢŨSEi°×íŦ ÄĪ‚o3#ÎëLV"fo9dÚųÎGûS͎eŪ&ĸ Ž™ļdŲSņ!Šx< ŸúÎŧFiOŅ…´ôÛ¤h5uˇ×Z;v§GDĶ衸ŠŗrnÃáP:Eņ:=""ē˜ąÃ5Į:nOgĸc{sE;v|ŦŌĻ­7Yŗˆ˜ÆŽ߸:}į1%^ą§îÍņOšŌܘ$įÕ킧ÉÕë<ėøs�hbˆˆ;îņØûúâ•G6=šČôé† W˙öÛę;|įËķŲ\¯WÄvüdá´°Ļ/LŠŌRZįdōŒ¤ ųŠųš/ˆÖ)"bÛ:{á‘f›nõĐ9ķ~´ŋæƒUą™įΰ´Tkŗ‡ŦũĩĄ“øKđUtŠˆ8Ž&Î:N—HíßMJHė´ąĶVˆŗ ëxęŪ­›öm||Ēsûü6Z§Ķ‰ˆĶâĢĻ5­ļãŅéDŧ—ŗÉ—ēĻį:=mÕn膍ÕĻe9æ7Áy|OŽčĮVÚļõf'$ }séņ=ÎøČ?ĖögŒŌâ>v|Eņz<žē¯kĶã­×B'<�€–)ÁŅIkVÛrÆoą´:'XŽúíˇÕwøÎįÕE.\=ŨļüŠ]‹ŸŨ:d÷´°–mĄ­ËsüÍųŪā6đÛÚšmŖåęļÜÚĄĶMÛû×imÛŖu 6tãk0†‹¤ŪøÆ[öĖ#'E´áCŊ taQ Oŧļeũô~RôĮƒy"†Đ0ŊˆÍzĒū™cŗˇĐjĩĒō ęÎ.ĖüxĮÎԏBCĪ•ĸ›K4ÁĻĒARēáã†iŊփ™ÎÂô}ŲŌ/>Éĸ´që->jŌpëÄžã§Ō÷ô†gjm;ē †āˆãLŊį˖_ģ|g=�€*ļ_|:éÉ­MÍĶéu""žÎxûmõžķ ~üÕiÃã~ŗzV¸äŧūLr^‹ˇkž­ĢĐVčÉ2¤Îŧ‚ė\G _ŗžĢĩĄ“øKđ•ČI›5ۜ×įoŽ÷ëîÉÛ9åq—„?8;FņdŊ>ꞑĪĒû+Xų[Sy&Õ|˙HŊ¸ŽŋŗŲVۈ3sņ/ŖÍîkĪõT­ļcž?F/Ž#›Õ´ę<ļvŅ‚ĨīfšķøŊ8ū¸=­ļNOŪÎ=9ĸ‰L¨}¯ÄŽŽõž<t$%=_‚GŒlû֛§1e¤Ö•ąqņ–\1OŨԟĖWˇ Ęā¨HØŽîÉŽŠŅ™ēųHQŨö;įš��TŌJŅįYéëæ'į4 böŖo~”/šđX‹ŽŪ~[‡ī"ʐ9+įš%oÃŧUY-Eßæ;Đ:tzˆ×î¨ŲeOÁÎåidžr4éZõ\Š Ã_†:ˆyãåŦŠķŌ^oųhčps¨AņÚm9ĮŦg\Ō/îÕä&%rD´nëĻ_'Úŗ&ŨŠWÄkĪ:ŧißy­yáx“ˆ(Ņs–ŽĪœŗgMR’íąņąÁŠ=įā–EũÆ/ky°|­ļŖDĪ[—9/í×I3˛&ÅŖšķãL‡~dōã‘UsīΜwđŲ$Όi÷›´žÂãÛˇėĪĶ^ŧhlû0XƏÔLũ—×8ĢĢŲ %:é>ũá]Ų6MėĢ īpŪpÉĢÚÃčY oZ÷ozrēsú¸HĶ–ž?Å38JûĮŦNØ �@c† ˖fL}áā†$ËĄĄÃcÂÂõZ—ÃfË9‘‘[äÕFŊ°ôÁ錎ßVßáģŒbzâÕį2~ųęĻ…Ëb?yšņĒkŽ­Ã0lŧE“e]÷Ė‹ĘÃ1ZgÎŅí{ķ‡,{.úŲeÖ­ī§I|Ŧ%¤ŅHŪkÔsĩĄļNÜęōŸā+’°ū“đ”ëļīûüØĄĪ]^6Ø1îŲ)ŗ&Ģ9Mj^˛m‹qíēí™—äōŠFo ž•<wNBÕÉMÈÕģvDŽ]ˇ=}ãō}.ŅęÃ"Į.ãŠ#ÛyãíVÛ1$ŦßĨũCōēí‡Öe¸D˙üŌŗc ÕsßúD™ŧnûŪWæ;ŧĸíķØ;sžŠĢ†Äaú}ûúxƒ1 WąJÔ¸ŅÁģ69‡ŲڋôęvAģtĮ:íō5GRÖ,KŅö‹ˆ™šūđƒŋücVåÅWˇ�€&„$ŧõá÷īØēįĐį'íLsyE4Ú`cĨĮ$MYũļÕoŋ­ŋÃw°iĢ_:>jáūų ‡z;Ą™žŦų´–aÂėK_Ũ´wŲÜŊÚ`Ͱ‡ßØüH”rbÆžS›˙¸jŠ>ą„4nũõ\­×Æ÷<u‘"röėŲ;î¸ŖîŒÆSĐ=îLú÷e‰›3_âes]á% ø!^˜€_i5ĐúÍ_t{jōē, x:Š�� ę€ĢQhũ83?ûĐ֏­Nãôĩ´tY��€|¯žŧ‹—mhôŦĩĢį™9Ũ ��Đ�Á÷:ĄÄ­É˙ë_W��āŋã ���U ø��@ž���P‚/���Tā ���U ø��@ž���P‚/���Tā ���U ø��@ž���P‚/���Tā ���U ø��@ž���P‚/���Tā ���Uđ“āëIy2Ō8æŨ‚NkĐēøžˆQëmÖ��>F_ \­�_ĐEBĮ/ZkÔûē ��ü}%Tįz ž†!ŖÆųē��ü}%TĮO†:´ČS”ērÖ¨{"ÃFšīIœœYX3˞šęŅQæģ"ÃīIœŊŲz"yTøŋŋ~J¤ūĮ79‹ī‰Lڜ“ērúČ{ĸÂyč•cvßė ��]‚žh˙žÎc/N|¯gøĢ;Ō˙ëЎeCí;fĪX™ãąøėœwlĄ >؛žma”õÕŇ^ViԄĸxOmx%-réĄ˙ÎĘ˙ôåčĸmĪ$gzŽųž��Đ5č+6ņûāk?ēéĐųČ9//ˆ5…‚#â~ŗ")Ôļwë1Há÷X%vÎŌ#!a–GŪxvˆĮÕ\3ͤ%ŖŒŠˆFŒˇč]yų…Í- �@÷B_ ´ß_[Î)oŋ!‘Á5Â,áW~vĄHQNG˜ U3K\¤Ļšf Æp]õΊN#^Å�Žô•@Ûø}đuēœĸčĩĩEĢĮ%âôxęÍRtz]-T­ÕĨU�ā;ô•@Ûø}đÕiuâqÔųXÆãt9E§×Š(ŠRo–Įépú B��|‹žhŋž&s„æüÉėĸš Ö|¯6<"DÄ"E§lÕ/`5-Ûë›"�đ!úJ müé>žŽ3'3ęÜ~EƒÉaņxâē)k_øƒqéh“âĖŪ:G‘iFr´"2l|øë+Öžšb|*ZS”˛fŨIEÛ|ë��tô•ĀUđ§ā[´ÁcûëNˆ}ãÄæQēčE[ŪQ–­Z˜¸Ú!Úāđás6,™mRDDŒŧũ˛íŲ×į˙rŋčĮĪųÍũœĮŗ}S;��×}%pzTTTˆČŲŗgī¸ãŽē3OņG§StēĒÁøÎ”GcįËËÖ÷šˇt[Ũã% ¨L÷xaŌWB5Z ´~?Æˇ%E>kųå gÛ ķNėXļ*Sž8ŒW2��Õč+Zū4ÔĄŨ‚|{­séĢŊßáŌhƒÍņË6/Åk�€ô•@­n|E 1Oŧ턝Ģ��ĀŅWÕēõP��� ­ž���P‚/���Tā ���U ø��@ž���P‚/���Tā ���U ø��@ž���P‚/���T!Ā×Tņ–”~sņŠÛã-)õu-@+4Ŋz*!?ēUĶË_^A�ԀžŨˆö•~QЎ¤4˙Ës}oíãoGh’ˇ¤ôŌ÷—ķŋ<ūãūüƸ6č+ŅŊøg_éC ŋšØ÷Ö>ˇö ōŸã´@Ķ+āGˇö1ô *üæĸ¯k ô•č^üŗ¯ô‹āû÷\éķ¯Ģ�Ú§Īnšâöøē �jA_‰îČßúJŋžeåå7ôô‹J€ļĶô `˜€k†žŨ‘ŋõ•ŧ„���  _���¨Á���Ē@đ��€*|��  _���¨Á���Ē@đ��€*|��  _���¨Á���Ē@đ��€*|��  _���¨Á���Ē@đ��€*|��  _���¨Á���Ē@đ��€*|��  _���¨Á���Ē@đ��€*|��  _���¨Á���Ē@đ��€*|��  ~|{öėYV^îë*€öņ–”jzøē �jA_‰îČßúJŋžÚ›z_t|īë*€öšôũåہНĢ� ô•čŽü­¯ô‹āōŖ[/^*ž`˙Î[RęëZ€ÖyKJ/ØŋģxŠ8äGˇúē�jA_‰îÅ?ûJŋ8ųŦé`úq˙Âo.^úū2¯gø?M¯€ŪŠéĮũũęã�×7úJt/ūŲWúK)š^ÆÛûųē ��ü}%p•üb¨���ĐÕž���P‚/���Tā ���U ø��@ž���P‚/���Tā ���U ø��@ž���P‚/���Tā ���U ø��@ž���P‚/���T!Ā×��€69m—7?§[Ę}]IõŅĘÜŸË ƒ¯KD„3ž��t §í˛ėS)î>ŠWDĘEНȞOå´Ũ×Ĩ�"Bđ� [øŨgžŽ czˆTtÛâqŨ!ø�Đ \vûē‚ë!Žî[<Ž/_��ĐĩēŅđ \ßü*ø:Ozũ™‰Ŗ,wEFšīMœŊrßIgG:ņbŦqĖģ]���ē/˙šĢƒ=õWIĪv…œ4wÆĐ­×n=ŧiĮĸņéĮßŲĩ&žĢA��puü%øÚ÷.šØŊlĮæ$cÕ¤Ø&[fürŲâ•c‡˙.Fņiy���čîüd¨ƒíãÍĮ]æ§VÔ¤ŪJ!“VīÚsčĩęÔë)J]9kÔ=‘á#Í÷$ÎNÎ,ŦYŌžšęŅQæģ"Âī5{}fũģĻØ­zÔ=Qá#Í÷Nžŋ#¯CŖ'���ĐŊųGđuædä‹i䰐Fs a&CUėu{qúã{=Ã_Ũ‘ū_‡v,jß1{ÆĘˆˆũãgįŧc _ōAZæŽäņŽoĻׄ[Īɕ3^{~Čĸ é<´yŽņäĘĪė-ēFû���ŋáÁ×~Æ.šā—9ēéĐųČ9//ˆ5…‚#â~ŗ")Ôļwë1ˆũÛ­;gé„Č`Cˆ)~ŅÂxˇj-įŅ7wœ1ÍI^1Ę<$qéŠDmƆ§ŽÁN��ŸøGđ­"âiy[Î)oŋ!‘ĩá8ĖŽqågŠØr EfŌUĪ1F…kĢײžōö‹ŽĒAĄ ‰ רrNō2���*ãˇ‚ oažMF™š]ÆérŠĸ×ÖNP­N<—ˆĶãE¯Š3G§ÔŦå‘ķ›ˆØT¯­P§S„;E��ŽSˇdōb’ ”xå|ą¤įĘŅī:sĻģeIy>UžéĖVŽåÁWe”Œô#§æ™"ęĪą§Ŋ›ĸ$<Ŧč´:ņ8\ĩŗ<N—Stz­ˆGQÄãđ֙ãŦãĢĶ*Ō/~Ũ†Įë^5§(†ÆŖ‰�¸.ôî+/ÅĘ?ĪČ{_ˆŨ-Ŋn’{ÂåŅ{åÆtIšėëâ�Ÿō“ĄÆ 3†im[į'įÔđā)Ø9áīėĩyDÄdŽĐœ?™]{]Z5ßĢ  GAíŨōŗrĒS°Ņ2Dã(tiÃŒU˙EŦ��ŽOF1˚Īåėō•K .Čļ Iwɀ>žŽ đ5˙8ã+bH\ųV֌ŲĻĖ›ôpĸ%L+vëáM;ŽOŨüRŒNDt#O\7eí 0.mRœŲ[įī(2ÍHŽVDBFŒ7ŋždí˛ƒŸŽwÜōú1V*EčF<œ¨Ÿ‘<o•vá”HŊ§čøĻ—Ĩ–Ļŋ7Ž‘�€ëŌ7ˆˆôĒ;Š\ŪO­ˇĖāpI ũz‹÷ōį<Ųj“+""ōČhé—/Gn’¤˙'_’÷NHŽ[DDeōPqĢhŧ’ķ7ųīk˛/@įō—ā+būÚŪC#×­ÚpøÍ_osy5ÚāđáIkߚ3"ŦjŧŽ.zŅ–w”eĢ&Žvˆ68|øœ Kf›‘āßXc[øĘĒGī_Ž„F'-\’øęėôĘQJôK[Öë^YĩtÆû—hC‡ŒüÍæE¤^�Āu+¯HJ~&ĪGˎ<Éš,Ĩ‹”…a’n•5—ä–[å‰ģå™ryíŦˆHIšô—ŸeËÂl) ”Ų#dÎOä‰ĪĨT$&ZÆÜ$øTrŨōげԯÕËŌŋã?ÁWD”°¸įÖĮ=×üüāøEâ55+dĒF,Š}“?¯æįāáķ~?|^+�pũqØäU<z§<ģH™œģ(9_KúWâ(‘^2!LÎ}!ī-"â8+īõ‘%árûYųJDD4Şõk)ˇd\”á?ƒČ72ōVÉąJæw""˙“-úĘí Đa~2Æ��tšŧ|™ˇOfȎŗō›dŒEŪ%#n­ôŋAN]Ŧ]øoßH‰NV=,vU {o‰HOé%"Z1ˆœ˙žv­SÅ]ŋ@gķĢ3ž�� “”ËWäĢ ’"Ōû‡ōĖŋČ´!ōį?Éeôšo¤ÜWņ{‰¸EDJƚj­—Ü$R\^;Ą¤¤ëJē Á�€ëJ@/šŠL.× ŠWž“ĪËō~ŌOä˛WJDŽ—uî*eRüĪ-¯HPĪ‰oėÕü€ŋ"ø�pšQ–Ü/š\ųM~ŊÉũnņHąˆ¸äË21h䛚āÛKôR;ŧĄi˙ķ"ũ~ R}'āˆ>"œôEwÃ_��Ž#˙” ¤ß`YvˇDäö[ÄÔW&ß#ũHūœ'߈H‰9+~"“û‰>P~ôCyä^y%Zni­ŲŒKbžSFD¯•čģÅL‚@7Ä_��Ž+y˙#/]’_†ÉŖwČM7H‰WÎ_’ŸĘØkX㕤ģåžŪ"^ų[‘ŧš-­~§ÛŅԟE&ĮĘ´29U [ĪĘķÁ]ŧ'@g#ø�pŊ)8+¯ŸmiÜ˙•Ü˙mbúļC˛­n;Ų2ĨæÁ?e۟ę͝ŪT €?ム���¨Á���Ē@đ��€*|��  _���¨Á���Ē@đ��€*øË}|Ŋ%Ĩ…ß\ŧâöxKJ}] Đ M¯€ŪJȏnÕôō—W��h•_tÛŪ’Ōü/ĪõŊĩIŨ‚ˇ¤ôŌ÷—ķŋ<ūãūüÆ�Đ]øÅP‡Âo.öŊĩĪ­}‚Čč4Ŋ~tkCŸ Âo.úē�čzöđu€ˆøIđũû?ŽôųÎ×U�íĶįˇ\q{|]�ĩ¸%P¤Â×EtL…h_×�ˆˆŸ߲ōōzúE%@Ûiz0$Ā5ķ̟‹tĶķĻ=äW?÷u €ˆøIđ��-dĨ÷Ę-ŨiØ@ĪrK ,ŊW|] "~rq��hÕ ƒüaŒ¯‹�ē3Îø��@ž���P‚/���Tā ���U ø��@ž���P‚/���Tā ���U ø��@ž���P‚/���Tā ���U ø��@ž���PGtą��IDAT‚/���Tā ���U ø��@ž���P‚/���Tā ���U ø��@ž���P‚/���Tā ���U ø��@ž���P‚/���TÁ/‚oĪž=ËĘË}]Đ>Ū’RM¯�_W��ÚĘ/‚¯öĻŪßûē  }.}šw âë*��@[ųEđ ųŅ­/_°į-)õu-@ëŧ%Ĩėß]ŧTōŖ[}] ��h+ŋø VĶ+Āôãū…ß\ŧôũe˛/üŸĻW@ī@Åôãū u�� ņ—n[Ķ+Āx{?_W��€ë–_ u����ēÁ���Ē@đ��€*|��  _���¨Á���Ē@đ��€*|��  _���¨Á���Ē@đ��€*|��  _���¨Á���Ē@đ��€*øē€*§íōægâtKš¯+éD=Et2÷į2ČāëR���TĪ/Îøžļ˲OĨøúJŊ"R.R|E–}*§íž.��@õü"øūî3_WĐuzˆT\×;��ĐMøEđŊėöu]LJ¸Žī��čü"ø^÷Žŗ!���ŨÁ���Ē@đ��€*|��  _���¨Á���Ē@đ��€*|��  ž. Ũn1Čä;Å$A)ņĘųbIĪ•Ŗßĩžâ#ŖeĀßä7ųßôÕˇ����_éfÁˇw_y)VūyFŪûBėnéu“Ü.Ū+7ĻKĘå.ßúąlÉqVũ|‹Q^é#O~Ūå��@§čfÁ7Â(†byæsqT>vIÁEé5Rôéúā[đuíĪũ˙{w\UpüîM“"„¯ø¸] Å-LĨm˜!VŦO[tEŨYuļÖYÚŠĶŠëÔíT;;ÛvŦ3ėÔumuĢâē°3˛]vGvŨ­Xa­ÄČōå)$ŊšI_đ`@Yh6ܛp>Ÿá÷œËšŋķ"sžsøŸ›‘Q|Æ?�€~3ČÂwh*"Ž/Îîøö?~õĨ?ŽÚuņČĻ#//ģ&ŽŽ~x4”‹cō¤¸-ĩŠØą3ž~3Ög#"îšĩâ'Uņ'ãĸ*MīÅ?nˆĪũv4ÔDy.Ū\ßÜ]Ŋ–:üūŒ¸ģ&"âųķcéƋgžš�ø ˛‡ÛÖolU,˜“‡÷ĨŲGeĸ!báãĄŸGkM|åš(‹ˆˆlwœwqd~ žüwÔ^~.˛ëâūīĮC›âšĢbZéqĮųÉĪã…ÖčØ÷-‰—U/�Ā`0ČÂwĪ–xüíˆqąāãŲãąkãÖLŒ:í“(?ß\īĩÅÖæxzC”‰Gī˙VKŧÜ]û›csDėŠėˆØš=v§b|åqĮéĘF6‘‹ũŲč귓�ā dK"bũ†ø‹Q_ĮÅÄŅŅøŲhœO˙4~|w^wī=ēæ!bgkt¤"34V폈ØŨ‡wä"›‹Ũ­Gߗ‹ŽˆâTŸ��ų5øÂ7"ĸ;ļ6ĮÖæXQ6"øŨ¸ãĒXŊōԏˇ<ÔëE.˛ŸZ´ŨŲū�€`-uHĮđãGîÜ/īˆâʨũ´÷ŸĐĩC{/ÕMEqDVá�$à ߥņđ ņĐÅ'nŽņa^›ÍEy¯ē­vÜ;ĢĒbøŅŋ­Šō\lęø æąū�`đTá{0^|/j¯ŒŋSGGũđ¸lLÜú;qĪØXŊ>vFDÄĻÖ¨GDԏß+9î�UÆŨãcliŒwŒÖíņŋŨ}œĨ#åÃâĘá1Ę×ų� ƒlīú5ņčŪ¸ū¸;åŠČ~;öÆ ¯ÆvyÃëkãōiņÕÆøčŖØÜ/4łąGž÷ˇ8;ÖÅ ‹‡ū(F§âƒņ5Ghû˙{{cėøl<t]üč§ņíŨ§~?��…UÔĶĶMMM™LĻ÷ŽOn9s>˙R~>§žŋšĐ0ČåķG8M~0a@9eĐĒĨ��ĐWÂ�€Dž��$‚đ� „/��‰ |�Há �@"_��Aø�Â7†z�€Äá;ŧ4ĸ§ĐCœ9=QYRč��o@„īƒĶ"Îâ{ĸEņā´BĪ��x"|/ĖŒáĨgے€!E1ŧ4™—Œ.ô(��‰—.ô�G\2:6z��Î^âŽ/��œiÂ�€Dž��$‚đ� „/��‰ |�Há �@"_��Aø�Â�€Dž��$‚đ� „/��‰ |�Há �@"_��á¤á›N§sš\>GN&—ËĨĶéBOœČĩŽĶšVž4|KKK;::ú{$ /ÚÛÛËĘĘ =p"×J8NįZyŌđ1bÄūũû[[[ģģģû{0ātårš–––ļļļęęęBĪœČĩ‚ĶŋVžô†đ!Cjkk÷îŨģ}ûv˙…’JĨJJJƎ;dˆų0ā¸VÂ@pú×Ę_ˇ"•JÕÔÔôë`�pVq­„AÄ=$��Aø�Â�€Dž��$‚đ� „/��‰ |�Há �@"_��Aø�Â�€Dž��$‚đ� „/��‰ |�Há �@"_��Aø�Â�€DHûÛģīž[Ā9�� •””ô~ųqøžwŪyy��Î”æææŪ/-u�� „/��‰ |�Há �@"_��Aø�Â�€Dž��$‚đ� „/��‰ |�H„thËÖmßyņ{;ģģģûë˜Á!Cʇ–ÍģõÆ ęë = ��}×?w|ˇlŨļđ[ĪļĩwœeÕŨŨŨmmí ŋõė–­Û = ��}×?áûÜ ßë—ã PEEŅĶs–Ÿ#�ĀŲŽ¡ŊŖŖ_Ž3pėė,ô��ô‡ÛN×ŲˇŠ� Q„/��‰ |�Há �@"_��Aø�Â�€Dž��$BēZ:nâĩŗĻ_:žļjXqܡgķē×WŦXÛt(ĪcdnyøŽĖkOüÃĘ=yū`��ō/˙á[1áķ_œ?ŠôWīŦ~åĨ÷÷uχ]8aÆÔš Ž˙¯O~÷Úķ>��ɐīđ­œ|ÃŧIĨ›?ĩpÕŅûŦ7ŧąęũûlŧĩņëŸßܕį��H†<‡ī¨)׎úÁ+‹Vŋē eõwžÜ-ÍGǎ"sŨÜYĶ/3˛Ŧø`ÛÎõ¯-_´˛Š3"ĸįŸķę3¯VĪē~ԘĨq`ëÚE˙ą|ũáûÄéĒ ŗæĖž:sNetîØ°b鲕[/ž¨¸lƜÆk3įTĻŗûšß^šlņĒæÎ<ž3��A~n+­ģŧ6ļ­Ûŧī{Úv7ˇÍŪ)ˇÜ~}͞åO/|äą'.mŽ›uÛŧ‰Ĩ‡÷eŗÅãf6\šuŲã_{ė+-ŲT=íÎŲĨ#"Ō͙?˙ęXŗčŠĮ˙éšĨ;Æ4Ū}ûŒŅ‘ÎĖ™˙ŦĒĻĨĪũŨc˙˛pŞķįĖŋsrUžN�€"ŋw|+G‹ė¯ZZí›ŊŊxá×ĸuWKWDėkyõg͝j¸¤.ŪÚ|xwņŽ7ŋĩ§+"Ú7ŧąĨmjí˜ąyWégf]]Ņ´øŠmlˆ]‹—”Ĩ§ Ģ.ļ‹ĻŽŪļâ‰Eo퉈}k–ŊXŸųķ™×Œ[ŗbû>W��”</učĖžú#ģ:ŗSf7ĖĢ5˛,N§‹Ë"Z>ūGv}ŧPĄĢŗ+ŌeÅQ{n]qûšGŽëÚļōųīFDԟ_WܲfËą•]M›ŗĶę2ąŨƒt��I’ßđmk=-Y;*Ūj>é{ŌusīŊkz×/-^žiץlTLųÂũŗ{íīúÔĮßŌĨéč:đÉ]ĨĨ騞ųåGgˇuwYY„đ�H’ü†īĄæMģãō+&Œ[Ū|ÂJƒĘ+ĻOÎžķŗ­]ĩWMŽi{ãÉ%ol=ŧ§bhYDË)|¨+ŌÃĘ>u{ËĒnÅŽ^ģēÚNy@��Î.yūÍm{ŪxmĶÁšŠķęzwzôÕķnūƒYW*ŽˆŌt:Ú÷]͐;áƚĶčķ]īoËVgę=ĩVwŨ—žxįäĒØõ~SļbdéĄ]ģ÷ųĶÖÕÕŲę[��’&ßŋ˛¸m͒[ŨzÎĖųsw̉—^vņĨSnZđåÆņ-¯?ŗxsgDėøåöėØŠĶ/­Š¨¨š`ęüšUÛŪˏ87Súkã÷Іoĩœ?ë†Æ+ęÆÍLšaÎėúhÚÚ‡6ŧēē=3ûĻÆ+ƌ¨¨¨ŠŸtËŊöW:Š2O§ �Ā@‘˙ßÜÖžūĨ…¯›yũŒĪ4Ü<mhqöāžæõĢžfņ]‡Wčļ¯}qņšķgßôđ´8đÁ;K/yģ˛Ąî ×>po|ãŸ×Ÿü°]›?÷LgÃėšķgUƁ›_yzŲĘŨŅĩyÉ3Ojhœ;FeYtînZˇ|áŌĩmų9W��Œĸžžžˆhjj3fLŸō—_ũûūiāúúŖ]č��8]ÍÍ͙LæØË|/u��€‚ž��$‚đ� „/��‰ |�Há �@"_��Aø�Â�€Dž§Ģ¨¨¨Đ#��ĐwũžååŅĶĶ/‡ zzʇ-ô��ô]˙„īíˇŨg÷ ŅĸĸÛoģąĐC��ĐwũžÔ×ŨwĪåågßz€ĸĸĸŠōōûîšã‚úēBĪ�@ßõôôDDSS͘1c = ��ô›æææL&sėĨ‡Û��Há �@"_��Aø�Â�€Dž��$‚đ� „/��‰p$|SŠTwwwaG�€ūŌŨŨN§{o9ž%%%… ��ú_gggYYYī-G¡ĒĒĒŖŖŖŖŖãđo0�€AĒģģģŊŊŊ­­­ēēē÷öĸcĨ›ËåöîŨûá‡æršBL��ũ •J•””Œ92•JõŪ^ä/��Iā[��Há �@"_��Aø�˙ú“níŊĩĸ����IENDŽB`‚������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/scheme-certificate.png����������������������������������������������0000664�0000000�0000000�00000147202�14156463140�0023112�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR����¤���HËD���sBITÛáOā���tEXtSoftware�mate-screenshotȖđJ�� �IDATxœėŨwTTįēđozcčŊ÷*ŠĸˆącWTDą—XcÔčõ$1å$'ŊMbŒąEcAģb{EDT:R‡:™šėd2‚´a#‘<ŋåZwΎŋōî1ëúœŨ†ązõj����4aļw���� ›ú?RŠ´ĒĒĒļļļ}Ģ���xEąŲl@Ā&„H$KKË9s游¸‚öŽ ���ā#•JŸ<y˛oßžÂÂBV`` ššųęÕĢ---9N{×���đęáp8}ûöŊvíŗĻĻfüøņ ŖŊĢ���xĩ1™ĖqãÆ1U*•‡‡G{���Đxxx0 !\.ˇŊ+���čø|>ŪČ����@'¤+����:!]���Đ é ���€NHW����tBē��� Ō����Ž����č„t���@'¤+����:!]���Đ é ���€NHW����tBē��� Ō����Ž����č„t���@'¤+���ø—Ōh4qqq/^Ŧŋ+%%eįΝ*•JiÛ!]š{w ˜Üš˙X-ĒüÍĨoģzúæ´iI���А„„„›7o=z´NĀēråĘĄC‡ŌŌŌĸŖŖõ˜–†tõŲ_šzúvé$“ÉZ?���ĀËŅŠS'‡CŅ XW¯^=tč!„ÉdvéŌEi[›ŽJelÜ~ƒQYUu8ņh+g���xi\]]ŖĸĸØl6ų+`]ģvíāÁƒ†ÉdFDDøúúę1mkĶՑŖĮĘĘ$S§L&„ėŽŪÛĘŲ����^Ļ:+!!ŠV&LĐ/Z‘Ö§Ģ]ģŖ !ŗgÍčŅ=đęĩë22ęt8}æėčqŧ;tīŲį?īž_QQŅŌõŨēu{Áëov ęåéÛ9¤˙āå+Våää6Ō˙­å+\=}+**Ū}˙ÃÁ}}üģ†GLžuëļT*ũøŸ÷ õë8aŌ”ģ÷îëŽĘÍÍ[ųŸwƒû†zúvîÔëĩ¯ßēuģų•ĪŋZTÛR[[ëęé;mæœY\\üÁGŸô äéÛ9°gīų‹ßē}§É¯���ZÉÍÍ-**ŠÅbB4 ƒÁ˜0aB§NôžŨšjž<yšœrĨ[×�g§ņãÆ^šzmOtĖ{īŦŌv¸rõÚk _777[˛øu3SĶä”+¯-|ƒÉd6ŋC}wîŪ‹œ:ÃØØhöĖææYŲŲÛ˙Øyîü…c‰MLŒ_8„ē¤úƒe=ēnŨŧņÁƒ´÷>øč%ËŧŊ==ÜŨ7ūōsNnîŪ}ÎŧΞĸ:įåį›0I*“Mšėéî^đėŲŽģ"ŖĻoßļšG÷@ũ*oDIiiøÄÉQS"===ōķ vüą+rĘ´m[~ëÔCŋ9�� ™*++Õj5ĩ­ŅhĒĒĒZ3[ĢŌÕŽ=Ņ„ˆ ã !#G˙ī˙>Ûˇ˙ĀĘËšÕá§õÔjõ†ŸėŌ؟9iâ}råę5í Mv¨īÖíÛînīŊŗ*¸gÕbmeõŅ'Ÿ&<4cúÔ$‹MqvrZ˛øuBˆŸ¯Ī餺‡téėŋjåۄ˙N~WŽ\ÛúûöÛwîvëJųnÍڒŌŌõ?Ž :˜š$lčāa#Į~ūå7ûöîŌ¯ōFŦųáĮ‚gĪbŖwuö˙3);:lÄčĪžøęĀ>\o��hCˇnŨ:pā�uAÁ`¨TĒÄÄDBHpp°~ęePĄPÄÆíįņx#F #„ˆDÃÆ–•IŽ;AuPĢÕÉ)W¨üA™9QģŨd‡š5%a,­”JĨ\.www#„ää6vq6tˆvÛÅŲ‰2xđ@m‹ĢĢ3!¤°¨ˆĸŅhŽ?inf6tČ mw7ˇn]nŪēUV&ҝō†h4šÃGŽx{yÚX[S8lNˇŽ]īÜŊW]]Ŗß´���Đ¤Ûˇoīßŋ_­VS÷ZMž<™ēD˜˜˜xųōeũæÔ˙ÜUâŅcee’qcF‹ ¨–‰ÂãÄīŲ3jäpBHaQ‘\.wt´×åæęĒŨn˛CCâöĮīŲ“ú ­˛˛RÛX[ÛÄûžŦ­-ĩÛÔgmeĨmá°9„Ze-!¤¨¨¸˛ĒĒS'?ƒĄ;ƒĢ‹ËÕkן<}jkkŖ_å/TRRRV&)+“ôė͝ūŪŧü<wwũf��€FÜšs‡ŠV cüøņÔŊV‘‘‘{öėiÍ,ũĶu?{ĪžAO33Škkks3ŗ /eeg;:8HĨ2BËĶÅãũũąÉ/ôõˇß¯ß°Ņŋ“ßûīũĮÁŪžËå>LôÎ{ī7Y0õ8@ã-”i !D(Ôiįķy„šŠ~•7¤Ēēšâããũo/Ģŋ×ĘŌ˛~#���´^FF†JĨb0áááūū^ōōōš4iŌž={ÔjuqqąĶꙮ?~’rå*!䅱&zoėŠåKŠ,"WČuwé^įj˛C}rš|ķÖßmŦ­wnß& ŠFŨ3X´ E„Š´N{M”b` ÔŖrBˆRŠ|aģHDm„ö ŅĢ^���ĐĮØącÕjĩĢĢkˇ†z{{GDDdddŒ5JiõLWÔũė‘BBúęļËåō•ĢŪŨģoé’Åææ';ûšÛĄ¤Ĩiˇ›ėP_QQą\.÷÷ī¤V„ä”+úEC,,ĖŒŒedPejÛĶ32!Ž..BĄ°ÉĘŲl!DY[ĢmÉnāĩæææ&&Ə+** ĩí%ĨĨfĻĻt���ŧ�uAđ…ģ|}}_ęûލûŲšΊˇ—Ļû'|ė˜ĄCŸ>“Äfŗģu ČĖĘŌ}oĶö;ĩÛMv¨ĪÜ܌ĸûvĢûŠŠûö õÎ$ĩŌ°Ąƒ‹ŠŠŸ8ŠģĐ­[ˇ{÷ 644lNå–„ŒŒĮږ}qZnİa …â×ß6k[JJK‡7oÁ"Z���^}Î]%=&‘”OūÂ3+3§O;rôøîčŊCZđÚÜä”+ķæ/Š˜8ŪÄØ8ųĘŠTĻŊ žŌd‡:ø|ūĀĄ§N'Ŋ÷ÁGÁAAéũžcįšoŋzmá§O'Å<4xā�ĄPØĐđæ[ēä͓§“–¯üĪŦĶ\]\rrsˇīØ) W˙õ6¯&+>ö]ģ˙÷ŲīŦZ)đŸ8uãæMŨSnēŪZōÆé3I?˙ōkaaQĪ Ī wîÚ#‘Hf͘Ūúc��€—IŸsWžŸ}æ‹˙áīÔÃËĶ#éėųü‚‚ūĄũÖ~˙­ššŲĻ-Û6lÜdfjļūĮ ”Š?ī@j˛C}_~ūé˜Q#=žúƒŽ]ŋąņ—Ÿú‡ö{ķE••˙ûėKēŪ_`eeš?vΈaa{c÷­zwõö;{õęŗĮÛÛĢ™•w čōõŸÉdō9¯-œŋpq™DōۆŸE"‘BĄ¨ŋœš™Y\ĖžŠS&_¸xé?īŊŋaã&īŊģvôíĶ›–Ã��€—†1oŪŧ7ļw����Dkg����t!]���Đ é ���€NHW����tBē��� Ō����Ž����č„t���@'¤+����:!]���Đ é ���€NHW����tBē��� Ō����Ž����č„t���@'¤+����:!]���Đ é ���€NHW����tBē��� SŖŅ´w ����Î]���ЉMy’ßŪe�@‡%āķ¤2y{W�đō° !.6í]���@+ƒ����tBē��� Ō����Ž����č„t���@'¤+����:!]���Đ é ���€NĖĒĒĒöŽ��� ã`ĩw ����Ž ���Đ é ���€NHW����tBē��� Ō����Øú “É™šr…RĨRĶ[PÛ15mØŧŊĒēFŖŅ´w-Đ C,ž6{šŗƒ]{×��Đ4}Ō•LŽHËČ‹E†bķÕ8ûu;5}Ëļ‚\õęŅh4•Uß­ûõ˙–.˛ˇĩnīr���š O6ĘĖ-‹EBÁĢ­!ĮOœD´z…1zÖí]��@Ķô‰G2™B(āĶ^J›ĒŠŠiī uŒōŠĘö.�� iú¤+ĩFÃd0h/ q¸a��^ ¯ĖĨ=���€WŌ����Ž����č„t���@'¤+����:!]���Đ é ���€NHW����tŌķWœ›cÃÆÍ—’S^¸kÆ´)û‡ļŨŌ­×Ŗ[—ŪAvļÖ,ĢŦŦüæ{§Î^Ŧ‘JŠŊ_|ôŸĶį.=™TgûŸLˇNcŖšĶ#ílŦ>>lpč+Q?��ĀĢ‚]YŨV?3rDXß>Ŋ¨í›ļŲÛÛB}´ąųG˙īŒÉēwí|ãöŨsąWjkkíûõéŲĩs§5ŋlĒŦŦĒĶy_‘ŧ‚g­Y.¤w“ŊŨŽč¸ÖLŌäĖēuö ´ļ´üqãļÂĸ⊴5õˇ]ņ���¯(ļX$lŖŠííėėíė¨m.cddčįëĶFkŅ(¸{×ŨēėŠŋ˜|•jš}/5ųڍo.5tāŽØø:ũSŽŨl劎vﭜĄ93ëÖ)J%’Œ'™¤Õõˇ]ņ���¯¨6ŧ2؈OŋüšËáŽ\ū–ļeíOëËË+ŪwÕĸÅËF Ë/xvûö™\îįë;{ætCą!DĨRÅ:œœ|ĩ¤´ÔÔÔ$lČ ļ¸ŧÚ783;G­(Ī ‹XŋųYQqũūēWܘLæ°AĄŨüMMŒ%’ōSį.žŋt…ęöŲĢŽžL211 ėâĪįņ=yē3æ@eeÕ[ į¸ģ:Bzvīúڟsķ ´3ŗXŦCüœŧ‚ũ‡Ž>ÉĖn|•Ī?üĪŅ“I>žîžî.Y9yŽÎŽÚ™ßœ?‹ĒsŲëķ¨öu_}Ÿx|Phmũ ­h … ķōp IyŌ…ä¤ — !uŠĪ/(l¨0­C 2 Ncâņ͇ŸÖįo ��⟧}ŌUhHßM[~/“HLŒ !2™üîŊûS"#!,6ķđ‘cS"#æĖœņŦđŲ×ߎŨĩ;zÁks!{bb“’ÎΘåîæz/5uįîh‹Ō—ÆÂø|žõąSgëīĘÉËorø¸‘a}‚ģīŲ—đøi–ˇ‡Û„ą#TĩĒKWŽBT*Õāū}=ųááī Å+Ū\0|p˙踃ļūņæüŲEÅ%{÷ŌŪ×E 5,0 StÜĄâ’ŌĐ>=ߘ7ķķī~,)“4žJŸāîwī§%ž8SX\üÆŧ™õg^ŋyûøQÃ\ž˙ų7…B1(´O“+N›neižåčŠĘ*7§)ƔI$ˇī=¨S|ø¨a ĻEĨ(Ũ€…h��Lû¤ĢŨ˙ØĩįrrĘ𰡄[ˇok4¤gPwj¯ŖŖcßŪŊ!6ÖÖú‡Ä<<S&×hÔ§N'1ŦOī`Bˆ••åĶĖėC‰GéMWFb1ƒÁ(.-Ķc,ŸĮ étüÔYęZÛų’R{Û!úiãÅŗÂĸËWoB$å÷ĶŌíí!2™\­V×ÖÖV×<wĮíŨ3p˙ÁŖ7nß%„ėŠįņxææfÕ5ŌFVŅh4 …ōĀácÔ$/œY&“+kkÕu3W,)“ÄÄÖ¨Õ%eBHQqIŋŪAŪžîˇī=Đ-žÉÃ×Ō XˆV��Đņ´OēâqšÁAA.%SéęęĩŨēŪæėč¨íiggĢT*%‰¤ŧĸļVÕÉ×WģËĮËķėšķ2™ŒĪįĶU˜†h!*•JąvļÖl+õá#mKzƓŪA\.WĄPBrķ˙žyŧF* ĖfceÅaŗ3ŗsŠ*•jĶöŨ„7§ÆWĄŽå饥 !r…bč€7‘ˆÉ`…‚ĸ⒖ž.mĸB´�€Ž§}Ō!¤_HŸĶIgŗ˛sŦŦ,oŨŊˇäõÚ]|OģÍãr !ÕŌŠLFųō›ī„AíRk4„ōō ĶUyEĨFŖą47Ķc,ŸĪ#„,Y8‡h4T ƒÁ „Š ŠKJ !JĨRˇ?ƒŅØlB!ŸR?—4šŠL&ĶŖøFVd2™oĖ›Áb2c~VTŦRĢˊĒ?ŧÉÂę@Ž�€ŽĒŨŌ•‹ŗ““ŖCʕĢNNŽB‘¯ĪߏJuōT*#„ˆÂZĨŠ2î{ûįR355Ąą*š\‘›ßŗ{×Ŗ'“jŸ?ƒāī[[Ģē›šÖĐX™LNų}WL^ūs/8HĘõ¨¤ĒdžüYÚn•æŦčėhogcŊfũ&ęCBˆHTRīâiÛ��đjiĪwĩ‡ôí“rõZʕkŊ{÷d2˙>““öđĄvûiæS.gjjęč`Įaŗ+*+mml¨?"ąXĖápč­ęôš‹&ÆFÞŽÍÚĘrĘÄąū~Ū ĖÍ+¨­­50=+*ĻūT×ÔTVW×6ã:#ŖŪ‰ŦÂĸb…RI=‘Guxkáœ Ā€–ŽRæ†4´"‡Í&„T˙õ^4gG3SBūž–Zĸ5‡��Б´Ûš+BH¯ā č˜Ø’’ŌĪ>ūPˇ]")‹Očœ—ŸōtRpPw.—C'´_ßũņ b‘‹‹KIiéÎŨŅĻ&ÆË–,ώĒĢ7n{¸š„ ėį`gsíæšBáhgÛ¯wĪ‚Â¸ƒG(“Ë/$_9t`uuMfVŽŠ‰ņ„1#$åŋlŲŅøŠ5RŠŊ­u™¤ŧĻFĒíRĘõ°Ą’ōŠ‚gE}ƒģ;:Øũą7ŽEĢčÎÜä7¸ĸLŽŦ­ íœxü´­Õ˜áCR>˛˛07‰ĒĒĢu—Đīđ��:˜öLW"ĄĐÛËK&“YYYęļ÷ë×§ĻēæãĪžP*”]:OIĩO‰Œ …Ņ1û$åF†F]ü'Ž×…íŠ9–žŌ+hâØ,&ŗ¸´ė芤¤ ÉunœĒo_ŠT6nd˜ĄØ ĸ˛ęÎũ ‰'š\.éüåS&,}ŪoŋīÖŊ+|˙ĄŖ&|dĪËË/Xŋi{qIY‹VŅš9ŪЊ;öč1¤g`@VNŪŽ=qFFâ9S#—,œũŲˇ?ę.Ąßá��t0ŒųķįoذĄEcnÜKˇŗļhũÚ•+ßY=w֌ ÚÆÅKß:xИQ#Z?ŋŽOžüŽŪ Ą]Ŧûęãö.��  ísîĒĒĒē °pמŊvļ6ŨģĩK ����mĄ}ŌÕų câö{zxĖ=C÷~v���€W]û¤ĢaaC†… yáŽ×|û’‹��� Q{ž‘��� ãAē��� Ō����Ž����č„t���@'}ŌƒÁPk4´—иf˙d"��@{Ō'] øÜĒĒÚKiSB€ žŌ4#CÃö.�� iĖøą-ãdg]U#­ŦŽQŠÕmQS[4p�ÁkK_iLƂ9ĶÚģ��€Ļ1Ŧ]üōßmé0™\‘™[ W(UĒW&`™ ŨōGeUU{-&6-˜3ŨÉŪļŊ ��hš>ŋâ ���� Á3ƒ����tBē��� Ō����Ž����č„t���@'¤+����:!]���Đ é ���€NĖC§.ĩw ����“T´w ����säØđöŽ��� ãĀ}W����tBē��� Ō����Ž����č„t���@'¤+����:!]���Đ é ���€Nlũ†ÉäŠĖÜšBŠRŠé-���āĨ̟G‹ú듮drEZF–X,2°˜¯ö؝܂ĸ–~e���đīqã^zK‡č“Ž2s Äb‘P ĮX���€ŽMŸ3O2™B(āĶ^ ���@ OēRk4LƒöR����:€WûŽ)���€¤+����:!]���Đ é ���€NHW����tBē��� Ō����Ž����čÔļéę‡×Κˇ0åĘ5ŨÆrIųŦy SĶļéŌ¯Žčũ?ũöGj{ÕGŸ9™Ôžõ���@‹´ųš+&“+W(Úz!���€‚6OW]:W×Ô9rŧ­���ø'`ˇõ`덑qBBz™š˜ÖīP^Qą':ö~jZuMĩŠŠÉ ũ‡HízsųĘŅÇåæį_ģ~C­V÷ëÛgø°Ą[ļíHOÄãņÃĮ éĶ›ĸRŠâNNžZRZjjj6dĐĀūĄ´ˆZ­>râĖĩ[wJËĘMŒ „ôéôĸÊÜsāáŖĮ@Ho™LvķîũÕ+–42syEåÎŊûf<đy}ƒ{Ô]WĨŠOLš~SŠŦõņt‹Š' é<0��� U›§+ĩZ=xĐĀ3gĪīŲˇhūÜú6oũ=ŋāŲÂųs Ļ?Úē}‡š™iˇŽ„6“u䨉éĶĸfMŸzæėšmÛwĻĻ=œéæēh߁øíėę  ÷ÄÄ&%Ÿ1-ĘŨÍõ^jęÎŨŅ,+4¤/Ŋwđč…äĢ“ĮvqrLKΈ‰?ĖfązÖéļ3fn^ÁüYSÅbQ‘Ī ‹Ųė&žäßwĮ/š3ÍĐP|öbōÍ;÷uķĶĨ+×ģtōycŪŒâ’˛]1öėK˜3-’ŪC���ĩũ3ƒÂfŗ"'MLNš’žū¨ūū¨ČI+–ŊåíåammÕ/¤ƒƒÃŨ{÷ĩ{ėģvņg0ÁA=!îŽ.înnÔG…BYPP •JON6lHŸŪÁVV–û‡öîÕëPâQzB&“Ÿģ”28´OP`€…šiß^=‚Ž>W§[eeUjÚŖ°ĄŪžnv6Öŗĸ"ĒĢkŸYR^ņđŅãĄúyēģZ[ZDŒÉįņt;ŠÅĮŽt´ˇëÖĨSHī [wSJ%ŊG���4zIodčÚÅßŋ“ߎŨŅjĩĻÎ.>wüäŠÕ}ōÖÛĢ–,˙ŋœœ\ŨDbmmMmBˆÍ_ų„Š43+§ļVÕÉ×W;ÄĮËŗ°°H&“ŅXN^žJĨōöt×ļx¸9—”ĘåĪŨ­_X\ĸŅh\ĩ‡æåáÚøĖĪ ‹!NŽvÔGƒáė`¯ÛÁÍÅIģíâä¨VĢ‹KJ[q(���ĐļÚüĘ ÖäȉīøÉš üũĩĩĩǝ×ŦÕ¨ÔQS&ŲØXąŦ~Z˙\}œį*Ŧs•MŖŅHe2Bȗß|Į ĒQ­ŅBĘË+ø|>]ÅËärBČÚ [ĩPĢTTVYđūž™ŦēFJáéœ|jō)™\Aáp8ڏĢÛĘ‘îâr! <€ ��đöōŌ•Í Ąąqņ>^^ÚÆĮOįääžķ+ŧū:-TQYianŪüi…!dūÜ9ööļēíĻĻ&tTũ'ŸO™9e‚í_įŌ(&Ɔē9l6!DĄü;ũÔHĨĪĖår!RéßgÚj¤ĪuĶÍRԋ-¸Üįâ���üŖŧÔwĩ3Z­V%ũûí Je-!ÄĀāĪ<22Š‹K4¤îÕÃF8:ØqØėŠĘJ[ꏁČ@,ëž j=;k6‹UYUmeiNũ‰Â:įŌ,ĖÍ!™ŲšÔG™\ū =Ŗņ™­,Ė !šyÔG•J•ūø‰n‡Œ'™ÚíĖė\6‹eaö‚G/��āâĨĻ+‘H>vĖŲķ´-ööḉ͉äîŊûÛwîéäį›_đŦŧĸĸ™s ‚Đ~}÷Į'$§\),*NM{øõ÷?üļy+Ŋ•ķųŧ>ÁŨ;uũ֝’Ō˛ôŒ'?ūēmûî}ÔŪsSž˙ų7Bˆš™‰ŊÍŅ“IO2ŗŸ˙ž+ÖPl DÛM—Š‰ąŗŖÃąĶgS>ĘÉËßĪfąt;””–9™T\RöāaÆųKW:ûŅ��€^/īĘ e@hČé3I9šyÔGCCņÜY3bâö_ŧ|ŲŲÉéĩŲ3JË$ë7lúę›5Ÿ~üA3įœ! ŖcöIĘ+Œ ēøO?ŽöĘĮ.āķ÷:V^Qi(6đ÷ķ3lĩĢT"y’™MmĪŽšôĮŪ¸ĩŋl62‡ ÍĘkOeévĶ5kjÄÎŊûŨōŸĪīÛĢGPˇ.7īūųÔ¤ZĨ40´¤´ėëĩŋ(kkũŧ=&EûĄ���ķįĪß°aC‹ÆÜ¸—ngmŅFŊdšE]ũ<hœPĄTĒjUÚ[Ņ×mØ" æNŸLã���đŌܸ—ŪŌ¨đ˛Ī]uxŋlŪQYY5yÂCąÁŨÔ´‡OΞÖŪE��Ā˃tEŗŲQą ‰ŋũžKŽPZ˜™N›îįãŲŪE��Ā˃tE3ąØ`VTD{W���íæĨ>3���Đá!]���Đ é ���€NHW����tBē��� “>éŠÁ`¨5-ø)@���€}Ō•€Ī­ĒĒĄŊ���€@Ÿtådg]U#­ŦŽQŠÕ´���đJĶįmĸ|×ÛÍ13ˇ ¨¤LĨzå֍{éí]���tzžĢĪãzš:Ō[ ���@€g���č„t���@'¤+����:!]���Đ é ���€NHW����tBē��� Ō����Ž����č„tp†�� �IDAT���@'¤+����:!]���Đ é ���€NHW����tBē��� Ō����Ž����čÄŦŦŽiī����:œģ��� [,ę1L&WdæČJ•JM{M����í‹Ëa ø<{K.‡ŨŌą-@‘ÉiYbąČPlĀbâė×˓[PÔÕĪŖŊĢ���čøĘڒ˛ō2ŊŨZ°ôÉF™šbąČ@(@´��€‰ËaÛXšY˜įäļtŦ>ņH&S|=���ŧBĖLŒ¤2yKG铮Ô “ÁĐc ���Ā+„Ëa+”ĩ-…K{����tBē��� Ō����Ž����č„t���@'¤+����:!]���Đ é ���€NúüÎ`ķũđãú7oQÛ\.ĮÜĖŧS'ß°!ƒĖLMĩ}/}{čāAcFĐo‰V¯_íë ^ ę¨m,—”ŋĩbÕĒ•Ë}ŧ<[ŋDđë֝wî? ļ9lļŠŠą¯—Į€Ū&ÆF4ޞęŖĪ„ô6(”Æ9��^ŽļMW„KK‹Ų3§Bä2YvvnŌųķįÎ_Xúæb/OwĒÃäIímíÚēŒfb2Ņ1ą]ēøķ¸ÜöŽåŸËÜĖ$jâ8BˆBĄĖÉËŋ˜ríŌ•ë gOssqjrėŲ‹É™ŲšĶ#Įˇ}™���íŖÍ¯ ōx</O/Ī€.GūŋŪˇˇˇûiũ/R™”ęСw/ggĮļ.Ŗ™ētŽŽŠ9räx{ōÆãō<Ü\<Ü\ü|<Ã…žģ|ą­ĩÕoŋī’É›ū%Ļėœŧ—P!��@;jķsWuđųüY3ĻŊ÷ū/\L<°?yūŌ^ÚÃGąqûŗsrÔjŖƒũ„đqŪ^„E‹—–_đėöí;2šÜĪ×wöĖé†bƒ:“—WTė‰ŽŊŸšV]Smjj2h@˙ĄƒB>ũōk.‡ģrų[ڞkZ_^^ņūģĢęĖ Ǝw !$¤—Љ)ЧĄ%!o._9zø°Üüük×o¨Õę~}û 6tËļééx<~ø¸Q!}zBT*UüĄÃÉÉWKJKMMM† ؟ūë_jĩúȉ3×nŨ)-+716Ō;¤WЋ§rWˁ‡ ü!Ŋe2ŲÍģ÷W¯XŌĸĩx<c?ũf]ō՛Ą}zB*ĢĒãyøčquÔÄØ°_īāū}ƒ !?ü˛ųŅ㧄”k7W-]ddhøÂn‚JŸ˜rũĻRYëãé1N$629!$ãIf‘šųjĩÆŪÖzô°ÁîŽÎÍ˙6���hņ˛Ķ!ÄÎÆÆÚĘ*-í!•Ž´d2ųšĩ?õ ę>sÆTĸҜ<uæûÖ}÷õ"‘Åf>rlJdĜ™3ž>ûúÛĩģvG/xmN™7oũ=ŋāŲÂųs Ļ?Úē}‡š™iˇŽĄ!}7mųŊL"116Ļē{īū”ČˆúĩŠÕęÁƒž9{~ĪŪ¸EķįÖīĐĐ„6“u䨉éĶĸfMŸzæėšmÛwĻĻ=œéæēh߁øíėę  ÷ÄÄ&%Ÿ1-ĘŨÍõ^jęÎŨŅ,+4¤/MßîŸâŊ|uōøŅ.NŽié1ņ‡Ų,V¯ Ā:ŨvÆėĪÍ+˜?kĒX,J8râYa1›­Ī֖æf?ĄŌÕŅqΊŠfEEŠÅO3wÅ051ęėį3VÔē [-ĖÍ"ƍ øŋnŨųÂnԜ—Ž\īŌÉįy3ŠKĘvÅØŗ/aδČF&W(ŋlŪā?yÂĸŅ$]LūyĶö˙­^!šųm���ĐĸŌ!ÄÔÔDRQ^§ą¤ŦT*“öîdgcC™:%2¨Gw6›EíuttėÛģ!ÄÆÚz@˙øƒ‡gĘä|>Ow†¨ČI &ĶŌœbmmuōLŌŨ{÷ģu čŅ=đ]{.'§ JšuûļFCzuAeÂfŗ"'MüaŨOƒ„zx¸×ŲßĐé`ßĩ‹?!$8¨Įļí;Ũ]]ÜŨܨ  lmmNN9bXŸŪÁ„++˧™Ų‡Ō›Žd2ųšK)C„B,ĖMŗrķŽ>W'OTVVĨĻ=Š;ŌÛĶ2+*âƒOŋ522ÔoQScŖŠĘ*j{˜L&ÃĖԄbiavöbręÃGũ||>“ÉdŗY"a#Ũ¨I Åâ‰cGBíírōōO&]P(•\§ĄQĨ’r™\ŪŖ[kK BHÄØ‘]üŲlv3ŋ ���ē´OēRĢÕ,&ĢNŖ••ĩ•Õ†›ôíäëëääā­ķ˜žŗãß÷fŲŲŲ*•J‰DbmmĨ;ŸĮ;xähꃴĘĘ*FS]]mmiIáqšÁAA.%SéęęĩŨē†ĘëÚÅßŋ“ߎŨŅž÷N] -AąļļĻ6!ÄÆÆæ¯|BHTš™•S[ĢęäëĢâãåyöÜy™LÆįķ›úښ+'/_ĨRy{ū =ܜ/Ĩ\“Ë<Ūßwë—h4×ŋnzãķx^Ž…Åú-ĒVĢYĖ?oããņ¸ĮOŸ}øčIUuĩFŖŠŽ‘Zš›ÕŌx7Ũ{ä]œÕęsÅ%ĨļÖV ˛47ŗ´0Ûļ+&¤Wˇ§›Ŋ­ uYđQöĶæ|���tiŸtUPPčįë]§‘ÉdžģjÅá#Į’ΝŲˇßĖÔd|øØ>ŊūŧĨ†Īûû4õ@_ĩ´Fwxm­ęë5k5*uԔI66V,뇟Ök÷ö és:élVvŽ••å­ģ÷–ŧž ņ 'GN|˙ÃOÎ]¸āīßĖ%!lÎsßgĢlF*“Bžüæ;aPj†R^^Acēĸî._ģa ã¯j•ŠĘ* Ūß7“U×H !</–ēąI?…Å%^n„•JõĶÆmjĩzâØV–L&ķ×­Ôīßd7*’Rx\!DĄP42ŠÉd.{}Ū‰Ķį/$_O<nbl4*lPP`@3ŋ ���ē´Cēz˜ž.)—tōķ­ŋËĐP<yŌ„É“&äæå=vbãĻ­ļ66.ÎN„*—P¤R!DôüɧĮOįääžķ+´īz¨¨Ŧ´07§ļ]œœRŽ\urr4Š|}|/ŌÎÆfĐ€ĐØ¸x/¯f.ŅB€2î{{[ŨvSS“æOŌ$ŸO™9e‚í_įŌ(&ÆĪ]õã°Ų„…RĄmŠ‘Jõ[1ãIfyE%u…ņiVN^ÁŗĨ‹æjO>UUU›Õ;Ā&ģ)&W(!\.ˇņQ"ҏQaãF…<+<yöâö=ûŦ­,›ųm���ĐåeŋĢŊēēfێŨææfŨģw̺̰¨øú_¯ĩŗĩ1m*“ÉČÍûķū´‡ĩ=Ÿf>åōxĻĻĪxP*k !FŽGÅÅ%ĸŅvéÛ'åęĩ”+×z÷îÉd2HSƍ­VĢūũv†&—h’Ŗƒ‡ÍލŦ´ĩąĄūˆ Äb1‡Ãiū$M˛ŗąfŗX•UÕV–æÔ‘H`` Ŧs.ÍÂ܌’™K}”ÉåŌ3ôXŽF*Ũŗ/ÁÔĸkįN„ÚÚZBˆH( ö>ÉĖ.)“hę}IMvËx’ŠŨÎĖÎeŗXfĻŒ*)-ģ}īĪלZ[YN?šÁ`ä<kæˇ��@—6˙F.—§Ļ=$„¨jkŗsrŽŸ8#—ËŪ^ļ„SīßļŌŌŌūeŌ„ņ]ēø3ãRr ƒÁtwuĨöJ$åqņ }‚ƒķōķOžN ęÎå>—Hėí9Îņ§Į™“›ˇwßūN~žųĪĘ+*Œ !Ŋ‚ƒĸcbKJJ?ûøÃæT. ÃĮŽŲš;ēųK4I „öëģ?>Al rqq))-Ũš;ÚÔÄxŲ’ÅÍŪL|>¯Op÷CĮNˆ„NöĨe’ØøDc#ÅsĻBÎ]Lšzķö˛×į™›™ØÛŲ=™dmi!>Ļû’ mˇúķËōôŒ'„Z•*/˙Ų™ķ—ä Åķf˛Y,Bˆ­5›Í>sūōđ!ō žÅ'÷öt+,*ŽŦĒˆ„B~Nn~N^žąąa#Ũ!%ĨeGN&uč\\RzūŌ•€Î~§‘ÉË$åŋũžkėČĄ|ŧ„qåÆ-ƒáâ䨸ˇ��@ģ6OW……E_~ũ!„Éd™øûû9ĖÜė÷8{{yΝ=ķČąq˜L–­Í›¯/ĐŪˇŪ¯_Ÿšęš?ûBŠPté<uJdá††âšŗfÄÄíŋxų˛ŗ“Ķkŗg”–IÖoØôÕ7k>ũøBˆH(ôöō’ÉdVV–u×nĀ€ĐĶg’rrķššDsL‰Œ …Ņ1û$åF†F]ü'Ž×ĖąÍ7~ôpŸŋ˙ĐąōŠJCąŋŸ÷˜aC¨]ĨɓĖlj{vÔ¤?öÆ­ũeŗ‘Ą8lPhVŽX{*Kˇ[Å%ek7l!„0 #Cą¯ˇį°AĄÚ_Â1‰ĻM O<žríĻŖƒŨôČņ’ōŠ-;ĸ×nØōŪۋû÷éĩmwĖ÷?ũ6oƔFēŠUęACKJËž^û‹˛ļÖĪÛcŌ¸QMN>mRøŠŗ=Åb2­­,įΌ˛´0küÛ��� cūüų6lhҘ÷ŌíŦ-Ú¨ ĸåĮ+**WžŗzîŦē?#øjÉ-(ęęįAㄠĨRUĢŌŪ?žnÃĄP0wúd—���xĨŨ¸—ŪŌ|˙ˇžTUUîÚŗ×ÎÖĻ{`ŨûŊūÍ~ŲŧŖ˛˛jō„1†bƒģŠi3ž,œëe���­ō¯HWį/\Œ‰Ûīéá1wöŒæÜĪūī1;*"6!ņˇßwÉJ 3Ķi“Âũ|<›��� {5ŌՏkžmÍđaaC†…á>›‹ fEŊā���@o/û ����Ō����Ž����č„t���@'¤+����:铮 †ēūīÆ���t, e-—Ķâ,蓮|nUU���^!%eå>¯ĨŖôIWNvÖU5ŌĘę•Z­Įp���€8…˛ļ ¨´°DboĶܟ'ÖŌįmĸ|×ÛÍ13ˇ ¨¤LĨBĀzŠnÜKoī���:>.‡-āķ|ܝô¸2¨įģÚų<Ž—ĢŖ~c���:0<3���@'¤+����:!]���Đ é ���€NHW����tBē��� Ō����Ž����č„t���@'fe5~���€68w���@'&ŸËiī����:œģ��� Ō����Ž����čÄÖo˜LŽČĖ-+”*•šŪ‚����Ú—Ãđyö6–\N‹Ã’>éJ&W¤ed‰Å"Cą‹‰ŗ_˙:šE]ũ<Úģ ��€6¤P֖”•?x”éíîÔŌ€ĨO6ĘĖ-‹EBĸ���tH\ÛÆŌĖÂĖ8'ŋ°Ĩcõ‰G2™B(āë1���ābfb$•É[:JŸtĨÖh˜ †���^!\[ĄŦmé(\Ú��� “žĪ ���ü3ŠÕęû÷īgggËd˛vāķųŽŽŽ>>>Ėļšƒé ���:”ÔÔԊŠŠÎ;s8/ūš?ĨRųäɓÔÔT??ŋļ(�W�� CÉĘĘrvværšŒpš\WW×ŦŦŦ6*�įŽ��� C‘Éd<¯ņ><¯Ąë†­‡t��� Ŗ]_n€t���MtĩmÛ6BČĖ™3_ÎęHW���ĐŅč> ¸aƄ„BˆBĄX°`ÁKXŊmĶÕ?ŽŋqķVũöî]/Ōķđ/}{čāAcFĐģĒÖĪPßžũņ×nÜúāŊU<.—Æi[oûģ¤Ĩúņ uˆŽŲw/õÁ{ĢVrš/~°ĸ•?Íܰi[ÚÃt‘PŌgΌŠl6‹Úuā`⁃‰Å%%V––“#Âõī§ÕČ.��€&iĪ]QŅjíÚĩ„%K–0™Ėųķįˇõęm~îĘŌŌbæ´¨:F†bŊ'œ<iĸŊ­]KG8uæÉͧ¯Í™Ĩ÷ ¸Ÿúāčąŧ˙Î?-Z5Į„đąŌîÜ=kÆTÚ'/*.ųĪę{vųŅęügĪÖoÜÂfŗæĖ˜JI<vōˇ­;fNėåé~ëÎŨoøY$uo|��@shĶUNNÎēuëÜÜÜ!ëÖ­Û˛eËK¸%ĢÍĶĮķķõĄqžŊ{é1*3ķī§.õ›Ą!fįîčž}zŲŲØĐ8íKÃbą&M˙å7ßÔßŪŽÎĐIŲģī€ĩՊĨo0 _/S“ÚÚZBˆFŖŲ7zÄĐ‰áŖ !ū~>Ų9šģc₃ē7˛‹ŪÚ�� Ķ^üėŗĪ´ēÛûhBüKXæ….%§lÜ´åÃÕī:9:BŌe|úÅ×o,šß#°ÛĸÅËF Ë/xvûö™\îįë;{ætCąyūēŪ›KWŒ9üîũÔÔÔ?|˙•RYģ':ö~jZuMĩŠŠÉ ũ‡Hųâëo¤ĨB.\ŧüßŪûúģ5Ú”Jel\|ʕĢåÆF†Ŋ‚{†Íbą!o._9zÄđŌŌŌä”Ģ2™ÜËĶ}öŒiFÆFuŽâæ­Û9šyK—ŧA}L{ø(6nvNŽZ­qt°Ÿ>ÎÛ˃ĸRŠâNNžZRZjjj6dĐĀūĄÔemíø„ “ĢĨR'ûIĮ{¸ģé][™D˛eێÔi`@˙ŨRĒÍÛËĶÕÅ9ņČņ׿Îĸ÷¯øâå+ÃGk˙WB×.ūÔF^~AaQqpPmĪž=ŋYķSM´L"ih—P( ˇ<��萸|žBĄhüĨ rš\ hĢV˜=úhŖŠ)FĄPÖųŖŅh!Ŋzuö÷ßūĮnFŖVĢwėÜŨŖ{`Ān„›yøČ1o/Ī5ß~ũßŪËĖĖŪĩ;ēūä,ûĖŲķövļĢV.ãqy›ˇūūčņã…ķį~ōáę‘ÃÂvGīŊ~ã&!dÉâEÎNŽ=ƒzŦũūûįNĪl˙cך "#ÆūÉGĮ‡Ÿ8yfOĖ>j›ÉJ<rĖÖÖö›/?ũô“÷Ÿdf8x¨~ 7oßąˇŗ573#„Čdō5k˛ĩąYũîĒŪ[å`o÷ũëĒĢk!{bb9>zäđ˙}ô~ؐA;wG';OͰ':æėš “#'žŗršĨĨÅ7߯-,*Öģļ_7mÍÉÉ[ļäU+—UUV]Ŋ~ƒjo¤6BHįNnßšKũŊĐĨ˛˛Ē´ŦĖČĐđĢīÖEΘ7}îĸģ÷ĒÕjBHN^>!ÄÆÚJۙÚÎÍĪodĩ�@æččx˙ū}…BŅĐÛD Åũû÷Ú¨�ļŠX˙[ š#''wūëoÖiüpõ;.ÎN„™ĶŖŪûāŋį.\R(ĨĨe˗ūŨĶŅŅ‘ē„gcm= HüÁÃ3er>˙šĘ`.—3iâxęcTä$“iiaNąļļ:y&éîŊûŨēB&“Åfŗ¨ŗ_Z•UUį/^ž1ĄgPBˆĨĨEn~ūą§"&„sØlBˆuŋžŊ !Ļ&Ļ]üũž<ÍŦ€ééÔ BHIYŠT&íŨ+ˆēJ8uJdPîl6K*•ž:4rİ>Ŋƒ !VV–O3ŗ% é+•I“Î]ˆŒ˜ĐŗGwBČŦĶäryaaĄ@Ā×ŖļŌ˛˛ÔÔͧNņõņ&„L‹š|īūƒÆkŖözx¸ÅÅ'äĐx}ŗŧĸ‚˛eûŽ‘Ã‡„yīAږßwĒjU3§MŽŠŠ!„čž‹ĸūDM´‘]t��›OjjęĨK—z_¨@ pppđņĄķÎ%]m~ߕ••åü9ŗę4ÚX[S&ÆÆ‘bb÷ŠTęéS§jû8;:jˇíėl•JĨD"ąÖ9ĨAqwsÕnķyŧƒGŽĻ>HĢŦŦŌh4ÕÕÕ֖–Ô–ŖVĢŨ\]´-ŽÎN šüŲŗgÔHöÚ]BĄ°ēĻĻū$å寯Æ—••ĩ•Õ†›ôíäëëääāíåIy–^[ĢęäëĢåãåyöÜy™L–››§T*¨v›M=My?õĩåįB\]œŠvƒáâ┕•ĶHmęĒb𤜯tUĢRB‚ēwœ0Žâáî*‘Hö'ž5‰Ž%���^¨ņĢ1ô^ĢŠ¯ÍĶ—ËuĶ @õ÷ Úĩ;†Åfv Đmįë\.ĨžÅĢ–ž Üđ˙ēhZ[ĢúzÍZJ5e’‹Áúá§õ×FEZž€˙÷l|>!D.“SúõG]RŠL{á–ÉdžģjÅá#Į’ΝŲˇßĖÔd|øØ>Ŋ‚Ĩ2!äËožc?ī@Rk4„ōō ęڏW÷aCũjŖFq8˙ĩōyüÆkŖöŠÚāüPĀ'„¸ëD?īčØĪ ‹ D"BHuuH(¤vUWWB DB…BŅĐ.k�€ŦŨÅšũß&ˇ?ÁÄĸVUģ?ū`ĄpmģTįlžT*#„ˆũûúøÉ㜜Üwūo…—§;ÕRQYianŪČ*™É¤ē I !ü–Ü=-đŠQCCņäI&O𐛗wô؉›ļÚÚØBČüšsėímuĮšššPįœtkhMmÔ|RQ5MÕF]ĸ­–JÉķ×ãZĪÜˌÃá”WVj[T*!„ÍfÛÛŲBōō ¨Ë¸„œÜ<&“aog+ ÚEcm��ЁeeeuéŌĨ‘S$Ô¯8ßēuĢŌŗé.méÉĶĖc'NΘ5=jrâŅãē76Ĩ=|¨Ũ~šų”Ë㙚š62•RYK10ø3=ĘČ(..ŅSõÎ:ÚÛ3™ĖôGږGO~ã×ë024’H$ÔvaQņõŋŪžjgk;cÚT&“‘›—įč`Įaŗ+*+mml¨?"ąXĖáplŦ­x\éÔ(ĩZķųWß\¸xYŋÚ¨+§YŲ9ÔĮÚZՃ´´ÆkŖZĘ%å„##Ãē3ļ“ÉėÖÅ˙âåmËíģ÷ DæfĻ6ÖVļ6Öēģ.%_õ÷ķåņxėĸą6��č_qf2™L&ķã?~úô)ĩũôéĶ?ū˜Ú~ĩÅY&“ŨžsˇN#“Éėäį[[ĢÚ´õ÷^=ƒ|ŧ< !ŨēnÚúûGĢßĨnĩ–HĘãâúįåįŸ<ÔŊņ—‰;ØÛs8œã'N32'7oīžũü|ķ ž•WTŠDÂĖŦŦĖŦlSSíQHß>XZY::اĨĨŸ:}fذ!Ô[šÉÃÃíá_¨´´ôĮŸ™4a|—.ū ¸”œÂ`0Ũ]]AhŋžûãÄ"—’ŌŌģŖMMŒ—-Y,Búö>x(ŅÔÄØÖÆæĖŲsOŸf͝åĻ_mæffnnއą˛´‹ÅĮOžb˙ÕŋĄÚ¨Ŋé2 Dļtŋ˛kō¤ņ+Ūųp͏ŋ Ô?íaÆÁÄŖĶŖ"Š4L‰˙ũŋ˜›™ųx{$_š~åڍĪ?~ŸÕČ.��€æĐž ČŪŪ~ųōåß˙=!dųōåŖGîo-**ūî‡ë42™ŒÍŋŽ?”x¤ŦŦė˙–/ĨŖ&Gŧģúŋ'Ž3ŠŌ¯_Ÿšęš?ûBŠPté<uJdã ŠįΚˇ˙âåËÎNN¯ÍžQZ&YŋaĶWßŦųôㆠđëĻ-Ÿ~ų͛‹ž{˙ũ´¨HŸ÷ûö]•ĻĻ&ŖF5<ŦEĐŲ?éėųâ’s33o/Īšŗg9v"î@“ɲĩŗyķõÔų¤)‘BĄ0:fŸ¤ŧÂČШk€˙Äņã¨&MœĀ`0öėŨ'—Éėíí–ŊĩØŌŌBīÚž6wķļí?Ŧû™/ í×ĢWđõë7 !ÔFš}÷^įNh˙ÎËÃũŋĢWmŲžëÔŸ͚5~ėHj× ũ¤2Yėū„íģĸílŦßYš´s'ß&w��4‡ö_4ęwo–-[F5jÔk¯Ŋö2VŸ={öæÍ›[4æÆŊt;k‹6*ˆŌ?ØF4Íęūįãå1-jr{×ĸ§´‡>˙ę›OūûžCķŪ՞[PÔÕĪŖ­Ģ��ĐO\\܁u[ļnŨJ™5k–nãŠS§ÂÃÃISnÜKoéŋzí|ßUĀ`0ĻLšpîüÅü‚‚öŽE*•*:&ļ_HßfF+��€ž:¯={öėŲŗë4ļŨęHW4čäį6tđOë•+í]K‹í‹‹WÖ*§5uá��āŌĐ[Ú_Nēj˙72ŧЏkžmīZfü¸1ãĮiī*ô11<‚4}^��āĸũįvņMW����úi˙_qnŖy���ÚEû˙ŠsÍ ���Đ.:ū¯8���ŧLL&ĶĪ͍~åĻY´×Â����’>éŠÁ`¨5õ~´��� cQ(kšœ_čĶ'] øÜĒĒ=���ŧBJĘĘüƞ=|!}Ō•“uU´˛ēFĨVë1���āNĄŦ-(*-,‘ØÛXļtŦ>wĩķy\o7ĮĖ܂ĸ’2• ëßčÆŊôö.��  q9lŸįãî¤Į•A=Ÿäķ¸^ŽŽú���čĀđĖ ����Ž����č„t���@'¤+����:!]���Đ é ���€NHW����tBē��� Ō����Ž����č„t���@'¤+����:!]���Đ é ���€NHW����tbë7L&WdæČJ•JMoA����íŽ×Ë�� �IDATËa ø<{K.§ÅaIŸt%“+Ō2˛Äb‘ĄØ€ÅÄŲ/€įäuõķhī*�� UĘڒ˛ō2ŊŨZ°ôÉF™šbąČ@(@´��€‰ËaÛXšY˜įäļtŦ>ņH&S|=���ŧBĖLŒ¤2yKG1e eKĮ¨5&ƒŅŌQ����¯.‡­PÖļtS,ļE5����˙Nz>3���đΤVĢīßŋŸ-“É^؁Īį;::úøø0Ûær¤+���čPRSS+**:wîĖáp^ØAŠT>yō$55ÕĪΝ- ĀC���ĐĄdee9;;sš\F¸\ŽĢĢkVVV€sW���ĐĄČd2×x×ĐuÃÖCē��€Ž†ŅŽ/7@ē��€ŽĻNēÚļm!dæĖ™/gu¤+���čhtܰaCBB!DĄP,X°āeŦŪĻŗ˙đãúYķ:r´N{yEŜų¯ĪšˇPĨRĩi˙B‹—žđ0ŊsîÛ˙Ū‡ŸČ BČą§VžŗzŪÂ7ūķŪ‡.]Ļw!-ŨŖĐãˆZ˙%l˙c×{|L‰ŽŲ÷á'Ÿ)ZūŌŨf:p0qÎÂ%c"ĻžöÆ˛“gÎļŅ*��˙6ÚØũõׄ„„ĩk׎]ģ6!!aãÆÚ]mˇz›?3Čãr/^LŽĶx9å*‹ÅjëĨ˙&OšØšS''ŧŸúāčą¯/œĮãrĪ$Ûŗ7v@hŋ˖öėŲ}ãĻ­7nŪĸq­Ōãˆt‡œ8ufãæ­z¯>!|,‹ÉÜš;Zī‘xėäo[wŒōéGĢCCzûÃĪ—SŽļÅB��˙6Ú•““ŗnŨ:wwwww÷uëÖeggŋ„tÕæWŨŨŨîŨO}ú4ËŲŲQÛxųR˛ŗŗczzF[¯ū/Ôˇw/gĶh4;wG÷íĶËÎÆFŖŅ<œ8h`čˆaC !Ū^ųų ‡ģtĄqÅúô8"Ũ!™™­zā–ÅbMš8ūËož<¨ŋŊ]kĻĒCŖŅ뉉=bčÄđŅ„?ŸėœÜŨ1qÁAŨi\�āßI{eđŗĪ>Ķ6zxxč~l;mžŽŒŒė/\žŦMWųO23ÃĮŽÖĻĢōŠŠ=Ņą÷SĶĒkĒMMM č?tđ@BČĨ䔍›ļ|¸ú]'GBHúŖŒOŋøúEķ{vĶ]būo…9<l(õqķļíYYŲŊ˙.!$íáŖØ¸ũŲ99jĩÆŅÁ~Bø8o/BˆJĨŠ?t89ųjIiŠŠŠIؐAû‡Ö/~ŅâeŖF†å<ģ}ûŽL.÷ķõ=sēĄØ ņŪ\ēbôČáwī§ĻĻ>øáû¯„‚ŋkHzŌPûâĨo<hˍ„ĨRŸråjyE…ą‘a¯āžácGSg ß\žrôˆáĨĨĨÉ)We2š—§ûė͌Œę|7oŨÎÉÍ[ēä BČŗg…Å%Ĩ]´{ētūõˇ-RŠT ė‰K<vlķ¯ëëĖĐČßN3kĐ=ĸ7—¯=|Xn~ūĩë7Ôjuŋž}†ēeێôôG<?|ܨ>Ŋu‡|ņõˇŌŌ !.^ūīīŲÛŲ6ô=—I$[ļíH}&ôŅ-ĀÛËĶÕÅ9ņČņ׿ÎĒ˙‰Ūōō ‹Šƒƒzh[zöüfÍO55RĄP@ãB��˙6|>_ĄP4ūRš\.´Õ˙ŗmķ+ƒjĩ:¨{āå”+Ú[Ŧ.\JĩĩąÖöŲŧõ÷G/œ?÷“Wļ;zīõ7 !Ŋzuö÷ßūĮnFŖVĢwėÜŨŖ{`hÕ™LžfíOļ66Ģß]õÁ{Ģėíž˙öî;ŽŠĢ˙øÉ ;"+le¨E¨āu[Ž:ĢÖn­­?ģû´O­ļjˇĩZWŨŠĸ".ܡ‚ˆČŪC d@ÖīĢiŠ$„p‘ĒŸ÷Ë?’“{Îųž‘÷ŪÜŦüQĄPBļíÜdøĐÁ˙ũấúmŪēũäé3OŽĀb3ãâļ^ņŨŌ˙|öqffö–Įgˆ,ŒĀbąOœ:ãéážđƒy\ˇ1õ˜ëba(S˙ÚrúėŲņcG˙īË/ƌuô؉m;wS/ą™Ŧƒņ‡ŨŨŨ—-ųęĢ/?MĪĖÚģ˙Ā“{āúÍ[žîN-ZBō ‹!.ÎNÆWŠĮ…E„™ģÛKíÚYųÖ4¨†Z]âmúãōecŖFÅ>úũƟ† øĶĘī"#ģlükK­đÎ[s}}ŧģ„wūaų2/O īÚīkÖåääÍ{įͅĖĢ’W%^Ŋf:ÎKmÛŪŧuÛ`04h–åäåBdnŽÆęqn~>ŗ��ŧ€ŧŊŊ“’’jjjĖŨM´ĻĻ&))ÉËË̉ xŸėÚ%|WĖŪ›ˇ“ÂBÛ † /õęųcĮc0™Ô¯j77×c'NŪž“Ô!Ŧ=!dęĢ?ūė?§Īž¯ŠŠ)+{8˙Ŋˇ­Ÿˇôa™J­Ščî!“B&MŪš›ÍRŠT ĮO2(2ĸ+!ÄÕÕ%#3ûĀÁCŊztroooę4“ĖÍ­OīąûãĻĒĢ Ŋ… ÂáØ3ēņõ˜ëRRVwģétōĒĒ3į.DęŪ™ââ✛ŸøhÂØ¨Qvl6!D&sëŲ=‚"u”†ļk“ž‘ųäōSS͍Cb„ĩZIáķyÆWš<!DĨVBēGtŗáž55ÔâíåڎŌ5ŧķú›ZúøûSO÷í?XPPāīßŌ¸ą€/`2Yl6K"YØĪe&'ß}uŌ„ā BČä‰Ņw’îšNÚĒ•Lėžŧ‚j‡ĶBŠTBLSQ˙‹R*UtM�đb NNN>ūŧšû…ōų|//¯āāā&*āi|ŽŗS‹�˙sį/BîĨĻ•””véü+Kx\î‘c Ÿ|ņåģī/|gū‡99šÆ#ŽãĮFíÜĩ;fOė¤ ãí%ëį•šēēšēŽZŊv\|FF“É lÍår3ŗr´Z]ېã–Á­‹ŠŠë||Ŋ˙ž\ĖÃÃ]ŖŅ”——×;B€É/øÆÔcŽ‹švĶ鞺sôzŊK?cKK_ŸšęęÂÂB꩗—§ņ%@ PÖ>ôEЍŦppp¨{˙ŌÁšjqs{tԓĘ"˛Įq‡Š}J•Ųhba?įįBZúųRí ÃĪĪĮ´/už˛ĸŧŠ5�@ķŗ|ļŪsOzJ÷ģęÚĨķļģ”*åų ũ[úš8;ef>:JĄÕꖮøÁ ĶOœ0N&se1X+ūõŸ}ÃˇlŨÉb3;vh_רf1™Ė.ˆ‹?|ōô™ģ÷´:ŽõJdˇŽÔą–%ËžgGŸĐ „ŠŠJWkžIdár8„…JYīŧēNåÚPĢĢK]Ė e:õx&‡š¨ÚĒÕÕÔSsßmiJĨROKS)•*ã•d*Ĩ’"lÄykkj¨…m÷ŋ´lö?žZøą°ŸŠ}eg22ûŋ Â&8Ē$ ! …R(x´? !D$Xę��õiöoq~Jé*ŧSĮÍ[ˇ'^šžxõęˆaCM_zū ''w҇ [P-•ršŗĶß÷ÄėŲįčč Õi÷Äî5ęÉÁk}¤˛Ļ篸X"G‹Š•›—wčđŅÕkÖšËd>Ÿ2{æ OOwĶŽRŠã“ƒĢLhŠTjBˆ/Đjt֏`ʆzęėâįëcŽŨ؝Jxj•iũ*B¯!WLķų<ÕãŖA277BHQQ1u!$ŋ Éd¸™\9ô$ īÎSfa?—”Įī/ĨÖ10…JEūy¯ņ<=Ü !yųÆKŲrrķ˜LÕ��6ËĘĘ ĩđxę[œoܸŅDéęiœ$„H$âļmBâR(”]:u4}IŖŅBDĸG˙_ŋŸ–VRRj Ž@¤gd>zlĘĢ_}đБ:¯Ëáķø “ƒ Ųšyԃĸâ’ĢīÆäáî>eō$&“‘›—įíåaĮfWĘåî2õG$‰Åâ:߆”{÷Œ3238\ŽT*mĐF6ÔcŽ‹švĶéŧ==™Lfęũŋo{q?-Īįšš¸X(˛{‰}yy9õØÅÅŲÕÕåŠÉåŪ׎Ũl]ûŒd-æŪ§Ę@!ö3ŗ˛s¨ÍĩZŨŨ”͍s‚öö 81]/™›ĢģĖí܅KƖķÛĩ Š÷›G�Ā2ę[œ™L&“É\ŧxqFFõ8##cņâÅÔã&ũį§”Ž!]ģt.(, Ŧõ‘{/OO;;ģ#G———ßž“´qķļļmBō +*+ĩZŨšuēu lũRģļ;„­YˇAĢ­}{w_¯k×nČĢĒ4ZힸøĒĒ*ĒŊŦŦė§_~‹?t$ŋ    p߁8ƒв%ŸĪīÕŗûžØ}/].*.INšˇtųĘ?ĖÜp˛ŧŧ"&v_QQņõ7?Ų5ŧ‡c× Œl¨Į\síĻ͉DÂŨ#÷ŒŋzũFIiéŲsŽŸxy@ŋŨĮĩU+˙{&ųlÄĐ!ĮOžŪ7åŪÖíģnÜē=bøŖ#‘gĪ_øņ—UOŽ`îŨyj„BAfVVfVļV§3ˇŸZ´đ÷oy .ūö¤ĖŦėu7ą˙š—RDBwú.i§L;:îĐŅíģöŪē“ôĮēM—¯\›0.ŠŪ)��^LƏzzzΟ??=====}ūüų^^^ĪÃŨD:´år8ĩŽg'„H$â™ĶĻėŒŲsîÂ_ŸYͧ”=,˙u՚o—­īÜņáÇΏÚrbô؏>ųĪū¸ƒ#G 3aÂøąŦ[˙ūEAĪ‘Ũ#ēŪž“D l=súÔøÃGcöîc2Yî˛ˇß˜CĨ˜0~Ŧ@ ØžswyEĨŊÄ>Ŧ}ģ1ŖGÖYvĪž‘J…rņ×ßhj4íC_š4aŧqR+G0˛Ąs]ÜÜ\Í ejōÄņ|wÃÆ-•ōJŠÔqذ!ôâŊú[û—ڝ<uϤ´”:ŅU]]}đĐá˜ŊûÜ\\Ūx}Vp`kjËŧÜükׯ?9‚šwįŠĐ¯ĪīkūüjɲˇįÎļđŽŊ>kæÚõWūø OĀīÛĢgˇn]¯^ũ{97oßyŠm[ÚûõéŠRĢwíŲˇqËv™ÛĸŪ{ŠmHũŨ�� >ÆągĪžM™7o!dذaŗfÍzŗ{8ĩČ).iPŸkwR=ܜ›¨ ĶûXž˜ Ã'_ü78°Õä‰ŅÍ]KŗIšw˙ß.ûō?ŸzYw¯ö܂â°6­šē*��0'&&ĻoßžĻ-ëÖ­#„L›6Í´1!!aÔ¨:ŽįŽåڝԆūĢÎėÚ@ƒ:Ā …Á`LuúĖšü‚‚æŽĨyčtēí;wõėŅŨĘh��˙ĩn:}úôéͧ×jlēŲ™-r)xĩm2đåū?˙ú{uMŗ}ܯ펉Õh5“Ÿ�€g‚šģ´?tõôŽģzũ´âģæ.á_aôČŖGŽhî*šĮØ1ŖÆ’ú�ĀŋŠņ[œ›Ō���<Wž˙oq���xš^ˆoq���xjšũ[œ‘Ž���āšÂd2Û´iĶDßrcUÍ51���ĀsɖtÅ`0ôíĨ����üĢÔh´ģŸčŗ%]ņyœĒ*Ĩ ���ž!Ĩ+ø<KŸ=Ŧ“-éĘĮíJŠ’+”:ŊŪ†î����˙r5mAqYQiš§ĖĨĄ}mšĒĮåų{gæ—>Ôé°�jģv'ĩšK��€FáØąų<np€ gmüĖ Ë lém[_���€į>3���@'¤+����:!]���Đ é ���€NHW����tBē��� Ō����Ž����č„t���@'¤+����:!]���Đ é ���€NHW����tBē��� Ō����Ž����č„t���@'¤+����:!]���Đ é ���€NėÆt.--ÍÍÍ­ŽŽ6 ĩ^JJģv9ųôØĄS{wИ)����ž-:v•™™Ébą$‰ũ?ŠĩōS7csJ“÷ßjĄû[īÎoŲ:dËÖíĩÚ;„wûé—ßž| ‡ũ ��ĐÔ•ŽĒĒǘLĻæŸŠĘōãÎm‘W—z¸{u ęnŽoeeåŅc A­wÅėiL ΍ ›6/X¸čéĪûņ˙-ėŨ̧åmšĢ6��€įCŖŌUMM^¯×étų%ŲrEšN§+­(<re[^Eǎ—΍>Ķ}\ÍõŨ€ĪįŧháÕk×323SÆŗčöí;Í2oÔč‘mۄXŪĻšj��x>4*]iĩZŊ^Ÿ™o×ņßĪ܊+¯*š”z¨@žfī(îöЎSŧRnŽīŽŨ{†ҭ̇ģ{ĖžXkĻĢŠŠųߒĨ=ú´yŠ{¯~Ëž_ĄÕj !]"{ūøķ¯Ô6ÅÅ%-[‡ŧõî|c¯đˆžŋ˙ąĻÖPmB;Žūc­ņé˙}ôéˆQc¨Į—.'ŽŸøjh‡đļí;žtérĸqą+~øŠßĀ!AmÛ÷0xĶæ-ÆîģDŦ]ˇaúks‚Ú„Ęårs#EOš˛swĖî˜Ŋ-[‡$%'›[W-ĩfąPOaaŅĖŲ¯ˇ ëŲķ÷?Ö|ˇ|å€Áè—LĪ ÖYg­ÚŦ_u=o��Ā ŖQéJŖŅhĩšėâûrMqjÁ•Ŗ77g?LIø]‚ø9ˇSĢÔ555uvLK{pãæ­¨Ņ# ƨ‘#böÆ>y]ü“>ũ|ņöģ?úŋÄī_đū{ë7núæÛe„ČnŨ¯\Ĩļšxé˛ĖÍírâŖ@“žžQRRŌ=2ÂĘ)•Ę×fĪ đßĩcKĖέAĶfÎŽ¨¨ „üoɲß˙Xûæësâė9cę—_}ŗmĮ.Ē—Ũ–mÛ[ˇŪŧiŊÁ`07‚Ņęß~nÛ&dØĐÁ‰Īļnmn]ĩ˜ÎÂįķ-Ôŗč“Īî$%¯ūíįõkV_眏˙ĀA&ŖömnĨĩjŗrÕ|>ßĘ= ��đÜkÔg5VĢëŲ~˜€Ü+HT2JėĨ⏈@§nōĘ*‹ĨŅhęė¸cWŒŸŸoXûPBHÔč‘?ũōÛåÄ+á;Y˜ëáÃōŨ{ö.ZøÁ°Ąƒ !>ŪŪiiÖŽÛđáīGFtûĪ—_éõz&“yáâĨÇnükKfV–ˇ÷ĨˉRGĮā  +W”—Ÿ_ĨPŒ1<Āߟōų§ :˜ÃáČĢĒ6mŪ2wÎŦŅŖ^!„øúøÜž}įˇUĢĮ"„0 >÷žOšŸ–VįĻŗˆÅb›Íáp¤ŽŽÖÅąŗ3íe:‹…zJJJNž:ũÅgŸP™rÅ÷Ë"{öusuĩrĨ|>ßX›õĢ���ŖÆģŌëõÕÕÕá­^îØĒŖŖc+Y˜ŋcĮj•F¯×ëõú:Ķ•N§Û;ę•Z­VĢÕz¸ģwėļ;f¯åš’īŪÕétT Ŗ´kÛFĨReddFDt­R(RRîB.^žŪšsčKí.'^!„\ēœŲÁ`Xš"?__??ßyīøëĒÕˇī$ąXŦ.áų|~rō]FĶŖ{¤qË.]Â3ŗ˛ %õ´CX{Ë#ذŽ'76ÎbĄžôŒLƒÁĐŠCÕ. ģGtŗ~Ĩ˙¨ÍęU��€[ūø7Ĩ ¨ëŽ4 ŸÁ‘E8ˆœZđ|tÕDĢÕ2 ƒÁPįõC§Īœ-**ū~Å߯øÁؘ’rī‹Ī>æņxææĒĒĒ"„ˆDBc‹P($„(ŠÖ­üü|/_šęė✞žŅŠcØĩë×/_ž2fô¨K—ßyû ëWÄbąļoŲôûę5[ˇīXúŨrw™ėũyīŽ9B^UE™øę4cLĶ „â’bĄĐ‡"‹-`ÃēžÜØ8‹…zĘËË !B“Ŧ_Šé6Ö¯���ŒØ<Ž]ũ[™ĄÕju:N§S(<O&’ËåzŊŪtƒ'{íÜŊ§c‡°O?ūû3˙555_vøčąƚ›‹úE^Uõwæ ‹Å"BHdˇnWŽ^uj! l-‹;węøÅâ˙æåįįæåEÖuäĻÖŅ,ĩZm|ÜB*]´đƒE ?HŊ˙5ëŪ˙đ˙üÅ"!dų˛%­[›vt—ÉžŧÎÚĩmcÃēĖî ķõdeeBTĒŋWTQ^AęRo Z5���PufP Čår*ŠÔÔÔ( ƒÁĀxL.— ‚Z]¨Û\|eøKíÚ˙tęØ!2ĸ›å“ƒÁAA,Ëxõ:!äęõëb‘Č×Į‡Ņíʕk/]Ļ.Ū kŸ™•}āĀÁ–-ũęŒ"‘°ŌäcnwSR¨ŲŲ9GŽŖˇ øīâĪ™LæŊ{ŠÁÁA;ģ’Ō2˙–ÔGŠTZë‚* #<Yu!ŋåu™Ũæëņõõ!„ܸy“Ú˛JĄ8sîü“#XŽķQmV¯���ŒuU{XXXbbbff擟øc0:ÕžP=v˙­V;đåÚ_3tČ ˙ûčĶĸĸbį:įrp°;fô¯Ģ~÷õņ žxņōÆM›gŋ6ƒÍfBēu /(,<zėøĮ-$„ˆ„ ĀĀ ›6÷ë×§ÎŅÚĩisøČąĶĻ EÂ?Öüų°ŧÜÕŅ’—Ÿ?÷­w~đ~ŋ>Ŋ ƞØ}L&ŗCX{ąH=nÅ?JC_j—›—ˇøĢ˙ÉÜ\×ü^ûžįæF¨ĩ™ŊD’””œ”œ,s“YX—9ęņöōjüķ¯Ģüũí%’%Ëžwvvzr ušÖfåĒ��Qé*  A]vÅė īÜÉŠE‹Zí/÷ī÷Ņ'ŸīŨ7ëĩæú~ņŲ'"ĄđĶ/—––ÉdnoŊņúÜ9ŗ¨—$IÛ6!7oŨīԑjéԊƍÕyA7!äã~øwīŨĪ^"?nLÔ¨‘§NŸ%„t īŧô›¯W¯ũsųĘŲ,VĢVŋũüŖŸŸ/!ä“E %bņ7ß.+*.vvręߝ΂÷į=9˛…LM›2yū ĮFOūõ§•Öe…z~XūŨ>™8yNj̞s_ŋuëöÍ[ˇŦ¯Ķ´6+W ���FŒéͧ¯]ģļū áŲĄRŠ4D"ĄžNš2ŨÁŪūįW4oU���/ˆFģ‚§™ŗį–””~õåNNN ĮOœŋpņUŋ6wQ���/ ¤ĢįĐ˗}ųõ7sß|GŠRųúx/ũæëž}z5wQ���/ ¤Ģᐓ“ĶĘīëø.���x uG����¨é ���€NHW����tBē��� Ō����Ž����č„t���@'¤+����:!]���Đ é ���€NHW����tBē��� ÃŨŠEnqIs—���đœ`vîŨ§šk����x~0Ĩbqs×����đüĀuW����tBē��� Ō����Ø6ôÉËËŖŊ�xŅ0 ƒÁĐÜU��ÔĪŨŨŊAÛےŽ:���Ā‹g���č„t���@'¤+����:!]���Đ é ���€NHW����tBē��� Ō����Ž����č„t���@'¤+����:!]���Đ é ���€NHW����tBē��� ÛļnęęšĖ܂ęN§§ˇ x ¤öÂUk7V)”ƒĄškyū1 ąP0kúd_/æŽ��ž[Ō•ēē&%-K,JÄ"Gŋž17“S˙\ŋÉ@ĢžƒÁP)¯úūĮß?|oާģ[s—��MΖl”™[  E>ĸÕŗčČŅcˆVOƒa0čWũšŠšë��€§Á–x¤V×ø<ÚK§CŠT6w /$ŖĸRŪÜE��ĀĶ`KēŌ LƒöR�žo¸Ę �āS{����tBē��� Ō����Ž����č„t���@'¤+����:!]���Đ é ���€N6~‹ŗõ Ú O>“•Ŗ×éĨRiĮŽaƒô‰„ö­÷ŪšŋÆBJJKųmuvvÎØ¨QąâŒíĐS&Du ­ķĨmģ÷špyÖԉ-žYņKÛ´ôíßĢģ—§ģH(PĢĢī§gN8•™Ķ”%›õÍ˙wüôųCĮN6Ëė��đĸiōtõûš?Ī_¸ÔĨs§>ŊzŲŲą¤gK8ž˜xuŅķíė4ÔŅ„éŗfL#„DãéîAĩŸ>s.7/˙ũyīĘÜ\BąŨĻSŧā'œžxųõøÕč¨ŧ‚Âc'ÎPO ŠŠ-÷mÕŌ÷ÍYS¯\ŋĩqÛŽ*…Rę`? Ow^Ÿžô‡U…EM[w]vī‹Ī+(|úķ�Ā‹ŠiĶÕŠ3įÎ_¸4íÕIŊ{õ Z:v‹ˆčúå—ėŽŨ7}Ę䍖™™e|Ü=ĸ›ņąBĄpjŅ"(°U­v˜Nņ‚+(,2&!FSY)Oš˙ĀĘž="ēoÜļ›zš“›Ÿr˙ÁûoÍđķi–tuéĘõ§?)��ŧ°š6]MHhéįkŒV™l҇ķŨÜ\Ч:.ö@Üŋ‰ĨeeRŠãĀũúöîEŊôö{ †|;)99ųޝ¯Oęũ4BČŲsūķŲĮKŋ_Aüę›ĨTû´×^3zdüá#Æ3ƒ­voėžŗį.*T*/ĪqcFˇ đ'„TTVnÛž+)9EĄTHĨŽũúô~š_BČ7Kŋģ›’jœÂĶÃŨ\aĻRîŨßŗ';'G¯7x{yFKon�� �IDATIå<Fŗ+&öŌåĊĘJ{IˇŽ]FŊ2œÅbBfŋųî¨C|™aíúYYŲ_|úQ­%¯\ū­§Î%XØi˙l6‹Åf™ļTW×|ũŨOunėīį3|Pw™+“ÁĖÍ/ˆ=x$-=“Âd2õëÕĄ};ŠŖCyyEÂésgÎ_ļÜÅ\ģé™A6‹5lP˙ŽíۉEÂJyÕĨĢ7â'čõzBČן-<t뤪Ŗ}ĮĐv<.÷~zÆæ{åō*Ķj‡ č3x@ŸZK8xäxܑã´ė:��x4aēRĒ”YY9Æ~ō%oããm;w<yfĘä‰ū-ī$'oŪēÅbõęŅÂbąOœ:Ķ>´Ũˆaƒ]]]ŋ[ūƒĢĢë¤ ãEBąûŧwßÜē}WjjÚĸ… ¸\Nüá#ŧ}įĨˉ“&Fģ8;M8žlų_~ņŠ‹ŗĶÚuō _Ÿ=ĶÁ^r/õūē›œZH;„ĩį­šß.[aœb뎝æ 3RĢĢWüđs—đNS§L"Ãą„ËWūøũŌo„BÁÆŋļ\šv}ʤ ~žžiŌ×oÜ\ŖŅL?ÖōN3]2—ÃŨ˛mGK°°Ķū n'ĨLķĘĖWŖž8•“gáۋ9vv¯OŸœxũæ–]{„Ņ3˛Ë¯MųäŋKU*õČĄ#ģvÚļ{߃ŒŦ VūQ¯ Ņiuį/_5×E§Õ™ĘtÆqŖ‡‡ļ Ūŗ/+'××Ûküčá;öî}ņ„N×ŋw÷‡Ž}÷ŊD,ZđöœÁũ{oŲoڝJQĻ Ņ ��jiÂtU^^Aqqr˛°JĨJ8~rčA‘] !ŽŽ.™Ųĸ‚ƒA8ģqcFS3™,6›%‹LGđvl6“ÉŦÕŽRĢNž>;~lT—Ν!ĶĻLŽŽŽ.**rqvš8~ƒÉtqv"„¸šš;qōö¤aí|q ˅•>,SŠUŨÂ=d2BȤ ãÃ;wbŗYōĒĒ3į.DęŪ™ââ✛ŸøhÂØ¨QvlKûÜtÉæ–  ­Š­ģtE āė×Ģ}ģĩē:-#ķæģ—¯ŪĐh4ĩļtt´įņ¸—¯Ū(,*!„ėÜwõÆm­VĮãr{D„I8EÔ;SZæåé> OĪķ—¯šë"5Ķn:@ĀīŌą}ĖūCWoÜ&„””>tsqîĶŖÛŪ¸#:ŽRXT|!ņ!¤ŧĸ2)%ÕÛŗŽkøLĸ��<‰mgg×DC3 BH­3Dĩdfåhĩēļ!!ƖāĀÖ§NŸQĢÕ<āßŌļŲssķ4¯¯õԎÍ~kîę1ËŨ(ųnŠ\^e0 …›‹KC ŖČ\]Ũ\]W­^Û§w¯ļ!!>>^A­ !iŌõzŊK?ã–-}}jĒĢ ==ęščŪ¸dsK¸›’jMmÍëč‰3'Ī^ jÕ2°•PĢ€ Q#÷īũķęõĩވ/*.-,.™6qėéķ—’īŨĪÉÍŋ˙ ƒâíįÃfą’īŨ7n™š–Ū‘Ãá˜ëbŽŨ”§ĖÉdfde[˛rr9ŽŗS ꂰÜüŋ/~WĒT>ŋÎÕĸ��<Š ]98Ø3ŒB‹W1ĢÔjBȒeß3ƒjŅ „ŠŠJ*(đĖüzĢ—BĄ$„pšœZíZ­néŠ :ũÄ ãd2Wƒĩōį_m(ŒÂd2?Z¸ .ūđÉĶgvîŪĶBę8zÔ+‘ŨēĒÕjB˙÷–T¯juuŊ•—ln VÖÖė4Í­¤”[I)„Vū~¯M‰5lĐ¯k7šnc0VüōG˙Ū="Â;<āayÅžøŖ—¯Ūāņ¸„w^ŸAŸU¤ÂēD,*)-Ģŗ‹šĄL§Ŗ†U›ŧ ęęBīņNŽutÁ0ģ:ä*��0§ ĶŸĮ÷ņö:{îüđĄƒk!ģœx…mgúul`öĖžžîĻHĨŽœ],BÔ˙ŧæ†ō ũANNîĸļ Z*årį'N_Z_˜D"Ž=.*7/īĐáŖĢ×Ŧs—ɨ„d:ģJĨ"„đ|BH­_Ų5Õ5 ZBĶí4ēˆÅĸę暚šŋוš–~ãVRHPë'7ŽR(÷8´įĀ!7įžŊ"§DGSh֝yų˙¸“uēšÎ.ŲšyæÚŨUęjō8cQx\.yX��hŅ´÷j8 iŲÃŊû˜6æäæŽÛđ×ĩë7 !Ū^vlvĨ\î.“QDB‘X,6{žŌėåŅĩÉÜ\šÎŨ{ŠÔSŊŪđŋo—=wAŖŅBDĸG×ÅßOK+))5˜Žk ÖVT\rõúŖŖ#îîS&Ob2šyyŪžžL&“ú0ãã‰Ōų|u ’Īã+”*ãKĻŋū­YBƒwÚĶ% ˙ûņ‚Ŋk_æâėTëķw„ŽíB‚¨ĮEÅ[wÅęõz™›Kn^V̉„…Å%Ô…R)W(´:š.æÚM§ËÍ+Đëõ-}˙ūP…Ÿ—J­..)Ŗq��Ā ŽiīČĐ­kxrĘŊũqņ™YŲ]Â;qšÜĖĖĖc §ÜŨŨĸĮ&„đųü^=ģī‰Ũ' ũüüJËĘ6oŨ.ut˜÷Î[OŽ& 2ŗ˛2ŗ˛­9HÃįķ{tØā ÔŅÁ]&;qętFFÖĖiū<ĪÎÎîČŅã#G ÍÉÍÛą{OÛ6!ų…••ö‰éÖVVVöĶ/ŋ‹ڎAį/^b0˜-[ŠDÂŨ#÷ŒwquņöōLIIM8~bĐ Ô|}ŧŽ]ģ1p@?øhUU•c]7V5ˇ„í´&ÅårƒL[ōō +*å §Î ė×K"ŨLēĢTĒ$q×Na-}Ŋ˙ükG­^›Ŋ7îđí䃁tîj0Ō3ŗÕÕÕg/&}š¯BĄĖĖʑ::DR^Qų۟›Ėu1×n:RĨ:ųęË}{—”åäåˇō÷ëŲ娉3Ô���hŅä÷jŸ1ur›āĀã'Oũĩe›Nopqj1lØ ū}zsšÎÎL?V lßšģŧĸŌ^bÖžŨ˜Ņ#ëj@ŋ>ŋ¯ųķĢ%ËŪž;ۚŠĮ‰b0ÛvėŽVĢ===æŊû–‹‹3!dæ´);cöœģpÁ×ĮgÖô)eË]ĩæÛe+žZü™éÖØzæôŠņ‡ÆėŨĮd˛Ü=doŋ1‡ē•×ä‰ãų<[*å•RŠã°aC† h\ōëÖŋŋđc‘@ĐŗGd÷ˆŽˇī$5h Öī´&åÔÂņ™SL[6mš˜xmoÜáüÂĸn;L;J(āĢÔęŦėŧ_Öl¸{/­Ö÷dlÚ͝gäЁũô:}~aŅęõ[ŠKJ !ģ÷ÅS÷eˆE•ōĒ[Iw÷<jĄKqIŠšĄLíØs@]]=~ôpąHø°ŧ"ūč‰#ĮO7åN�€cöėŲĢV­jPŸkwR=ܜ›¨ hj_.ųžšKxqũøíâæ.��š\Ķ^w���đĸAē��� Ō����Ž����č„t���@'¤+����:!]���Đ é ���€Nļ¤+ƒĄ7Xũ…�@!„Á¨��xØ’Žø<NU•’öRāéđųáøé3ė%’æ.��ž[Ō•‡[•R%W(uøîÛgPŋž}GQž:&cΌÉÍ]��< ļ|‹3Ë ō÷ÎĖ-(.}¨Ķ!`=cœZŧ˙֜ß˙üK^UÕÜĩŧ(Ä"ᜯzĘܚģ��xlIW„—ØŌ›ŪRāiúúŗ›ģ��€į>3���@'¤+����:!]���Đ é ���€NHW����tBē��� Ō����Ž����č„t���@'¤+����:!]���Đ é ���€NHW����tBē��� Ō����ØļuSW×dæT×ht:=Ŋ���4;Ž›ĪãzĘ\8v Klš\ŪĐ>ęꚔ´,ąX(‹XLũ‚įPnAą‡›ssW��ÍÆ 7¨kjîŪĪ đihĀbŠÅâ†Î—™[  E>ĸ���<—LŸĮĩ—ˆrō‹Úזx¤V×ø<:���<CxŽJ]ŨĐ^ļ¤+ŊÁĀd0lč���đ a05mC{áÔ����Ž����č„t���@'¤+����:!]���Đ é ���€NHW����tBē��� “ßâl=ƒÁpîÂÅS§ĪdeįčuzŠTÚącØ ũE"aSOũômükËŨ”Ô¯FyëŊ÷_îßoİ!´Œ<oÁ˙EFv3ęZF{’iåĐPwSî<t8#3K.¯âķy­[ĩ:dP@K?+7Xų͝%ĨĨ_~ūICį­÷‡ËÂČ;cö:tdõo?Q›]ģ~ãÉm:u {kîãoĖ™ŪšŖņՊōŠw,\øÁüāĀÖĢV¯=ņREN™<Ąoī^_ˆ5ۙLĻ“S‹öĄ/1TĀBY!�@ƒ4yēú}͟į/\ęŌšSŸ^ŊėėØŌ3Ž%OLŧēčƒųööM={3Š7ÆĶŨŖšĢxM8‘ž‘1kÆ´§3]rĘŊe߯ėŌšĶŦĶDBQŲÃ˛-]ļüĶzzxXŗÍhüárqqž:yb­F{Éß_BĘd2ļīÜڎËá<Ų}萁Ũ#ģQW¯Yīéé>xā�ęŠLæFËBę­ĐÅÅyúÔWŠĮZ+îā᜜ÜæŋË`0Y!�@ƒ4mē:uæÜų —ĻŊ:Šw¯TKĮa]ŋüī’ŨąûĻO™Ü¤ŗ7¯îŨšģ„TffÖ͜.!á„ģģlökĶŠ§žžŪÁÁ˙ũzéŊÔûTxĒwÛĐûÃÅårۄ[Ø }čKÉwSâãŧ2b蓝zzx×ÂáÚŲÛK,fĘʅÔ[!—Ë lm|ÚŽm{‰díú÷ī§ĩjИ �ĒiĶÕŅ„„–~žÆ4)2ŲĸįģššRO5ÍŽ˜ØK—+*+ė%ŨēvõĘp‹EyûŊÇž”œœ|wåōo.úĖô)—Í=wņbbiY™Tę8p@?ãáũ”{÷wÅėÉÎÉŅë Ū^žQŖFļ"„Ė}kŪ°Ąķ oŪŧĨŽŽn2}ęĢą¨ž2æ0|Č➞˛‹—ÕęęĀÖͧLĻūKũ°ŧüĪõ›’īĻđųü>Ŋ˙ąLĶ3ƒ–GXˇA0p@?ĨJuõęõ¯ŋüüɝÉb2÷Æ8vâ¤JŠ š9}ŠŊDBŅétæöCEeåļíģ’’SJ…T樝Oī—û÷Ĩ^˛Pš…kŊ#Ô9Š…}[Ģ—ÛÎÜŽļ0ˆ•Uųúú¤ŪO#„œ=wÁÛÛK,}0˙]c‘?üükEEå§-Ŧ˙īŽÕ4Z­NĢ3máķøĻįXëŨĀ6Öüp҈Īįŋ2lhĖŪ}=zt“:JišéâīīG)-{ØĒQ�4X^ÕŽT)ŗ˛rBęúĸ7—ËĨoükËéŗgĮũŋ/ŋ3zÔŅc'ļíÜMŊÄbąOœ:ãéážđƒy\ˇÖĶm;wÅĮ>tđŋøtā€~›ˇn?yú !D­Ž^ņÃĪî2Ų'-üėã…^žËWū¨P( !,63.ūpP`ëß-ũĪggffoŲēŊŪ2ØLÖÁøÃîîî˖|õ՗ŸĻgfíŨ€zé÷5ërrōæŊķæÂæUÉ̝^ĢsWXáĪõ›23sŪysî‚÷Ūžw/õŌåD†™oČžx)Q^%Ÿ÷ΛsfÍLŊĪŪũTģšũ@YģnÃũ^Ÿ=ķËĪ?:hāÖí;Ž^ģ^oåŦõ˜–gaßÖęeé7?ˆ•UŊķÖ\_ī.áXžl@ŋ>IÉw–—S›ŠÕÕˇī$OŅ%ėĨvyųų?ũē*-=]¯7ذ ŦüᲞÁ`¨ŠŅÔúc0ü]­^¯ī߯¯ŖŖãļ1*ũŸŦ_HŊ>а°ˆŌĸY�ĀMxėĒŧŧ‚ââädayUՙsĸĮFu īLqqqÎÍĪ?|4alÔ(;6›Á ŽŨ¸1ŖŠMŸĒTĒ„ã'‡Ņ•âę꒑™}āāĄ^=ē—>,SŠUŨÂ=d2BȤ ãÃ;wbŗYÔ ŪŪŪÔ9;™›[ŸŪ=b÷ĮMUWk´ eBd2ˇžŨ#!RGihģ6附˛‡““īž:iBHp!dōÄč;IwÍ­´Î***oŨž3ybtÛ6!„9ŗgŧ˙áGŽŽuŽ đ'OŒ&„øųú\švíAzēåũ@™8~ƒÉtqv"„¸šš;qōö¤aí-TnyĀZīH-uî[kÚËō;nnƒAo}UL&‹ÍfIÄĸđΝ6oŨ~áâĨÁ_&„ܸyĶ` ]Â;™ũëh“^ŊzT)ûöĮ'^šÆįņ[ĩōīĐ>´[×pc2¨wXķÃÕ 99šŗßxģVãįŸ,ōķõyôÄ@ØlÖøqcVūøs˙>ŊZĩ  e^ëR…„čtŽjĩÚôŒŦ-Ûwzxxøˇ¤ĨT��ë5aēĸŽÁ°Įš:egįčõz“OWĩôõŠŠŽ.,,¤.’¨õ/ŖņifVŽVĢkb|)8°õŠĶgÔjĩĖÕÕÍÕuÕęĩ}z÷jâããdr5†¯ˇˇņą‡‡ģFŖ)///{øĐr^^žÆ—BŠ$„äįBZúų×ëįį“••SįJ륰¨Đ`0´ đ§Úų<~›āāŧü‚:Gø{WH$’´é–÷Įãqšûã%ßM‘ËĢ ƒBĄpsqą\šåÉīˆŠ:÷-urĮØĢŪwŧÎAĘ+*m¨ŠËát ?{ū"•ޝ\ëÖŪôl&]†ÔŋoŸ¤äģw’’ī$Ũ]ˇņ¯ØũqīĪ{ÛÃŨŨĘ ƚŽquu™ũÄį�dnĩ/÷ m׎m›M[ˇūņ"Zæĩ~!õV˜3sΛĻ#ˇkÛfڔIæ�4&LWö ƒ:8oŽZ­&„đø<c õ˲Z]ũč)ŸoēŊņŠJ­&„,Yö=ƒ<ú§So0B***]]]>Z¸ .ūđÉĶgvîŪĶBę8zÔ+‘Ũē>Áä€õé'…JYovvvæŠˇŗû{ō¸ŧ'7ŗ0B•BYĢ$ĄĐė*8&gâŒŋ.,ė6ÛnéŠ :ũÄ ãd2Wƒĩōį_ë­Ü€Ô>ŠõŽ˜Ēsß>zéq¯úßņēąšĒž="Ÿ<••ãęęrãöwۘcŽøFâršaíCÃڇB’îĻüüËĒm;vĪ÷-ë7hk~¸„Ãáø[wŒ'zü˜O?˙ōôŲŗíÛĩküŧÖ/¤Ū Ũ\]į˚A=>vüč[ˇįŧ6C(¤?L�ÔĢ ĶŸĮ÷ņö:{îüđĄƒke‹Ë‰WØvvaĄ/QŋÕ*ĩņ%•JEá Ėū §ø|BČė™3<=˙ņŋŠÔ‘"‘ˆŖĮEE‹ĘÍË;tøčę5ëÜe2ę õ{úņ\jBˆ/¨ŽŽąĄ ęĖŽĘ¤—RĨ˛\v-Ôš°jMą…:Ļe= ûáAúƒœœÜE.lũč$NĨ\îėädšrË;Ö˛:÷m­mę}ĮëDĢŅŲV•Ÿ¯ˇ×Ĩˉ>>Ū"0$˜ū‰U”Wpy\*äQB‚;v ģqķļ•ØĀš.›ˇĖC&ëק׎˜ØāĀĀÆFãBė8vÆŗ„ŅcĮ\ŋqsÛÎŨ3Ļ>ĪL€­ĻŊWûĀũKËîŨwĀ´1'7wŨ†ŋŽ]ŋIņöôd2™Ô‡ŧ(÷ĶŌų|uËo/;6ģR.w—ɨ?"ĄH,ÛŲŲ—\}|_Aw÷)“'1™ŒÜŧ<Ē%åŪ=ã ™.W*•ÚVuÎ++ûŅŠ@­Vw7%Ĩž]ōŽ..„ôÔS•Z•””Ü ,ėFK‰W”VRRj ˕[°Ūbęܡĩ ŽoW×ũ5´*“k{tŧ”xåŌå+]˜LšOUTTÎûpQ\üáLn0ä8ØKŦŲĀfõūp5‘#†ëõēƒ‡ŽĐ2ZS,D$Ž5ōÔé3)÷îĶQ#�@Ã4íēu ONšˇ?.>3+ģKx'.—›™™y,á”ģģ[ô¸Ņ„‘HØŖ{äūƒņ.Ž.Ū^ž))Š ĮO 4€ú|ž|>ŋWĪî{b÷‰EB??ŋŌ˛˛Í[ˇKæŊķVYYŲOŋü6.jthh;aœŋx‰Á`´|tNĄŧŧ"&v_d׎yųųĮŽŸėŪ‰Ãąãpėl(ÊE ˙–ââ]]œÅbņ‘c ėúĘŽÅÅÅŲĮĮ{_ÜAww7@°c×{û†ŨŌÂ~đōô´ŗŗ;rôøČCsrķvėŪĶļMH~AaEeĨ…Ę- Xo1uîÛZÛÔûŽ›ÄÎúĒ„BAfVVfVļTę(‰ēu ßžsWiiŲ׋ë¸ĪE#ŲÛKŊÜ?v\EeeûĐP‘PP^QqöėųÔÔ´šs^ŗfŠZ­žy뇲<==¤Ž–ŽĖÕûÃÕ ‘ŸÜŒÂd2ŠĪ[Ô" FŊ2bķãs6’Í ąP!!¤gî'OYˇaĶâ/>Ą�<5MūÎŒŠ“Û?yę¯-Ûtzƒ‹S‹aÃõīĶÛøŠÉĮķyÜ ˇTĘ+ĨRĮaÆ <К‘'Œ+ļīÜ]^Qi/ąkßnĖ葄 ĀÖ3§O?|4fī>&“åî!{û9ÆįôėŠT(ũĻFĶ>ôĨIÆ7ό×gÍ\ģ~ãĘá ø}{õėÖ­ëÕĢ×´æÎššfũ†%K—Û;Ø:8##+=#ŖA#˜Û‰xæ´);cöœģpÁ×ĮgÖô)eË]ĩæÛe+žZü™…ĘÍ X/sûļËģÚÜ ÖW5 _Ÿß×üųՒeoĪŨŽmĄ@¨VĢ]]ë9 j›qcF{¸ģŸ:sfíõŠ*…@Ā÷õņY0īãoũz7 „—|ŋō'ĶagNŸŌ#2ÂōÔõūpY?ō“›B˜LÆÚß­sę>Ŋz?q2'7Īr…V˛m!–+d2“'GųՒqņ#G ŖĨN��+1fĪžŊjÕĒõšv'ÕÃÍš‰ jRô~÷-ĒĢĢĩ:PđčüŨ’eËEBá›sg7oU6 eß6ÅTY)˙`Ņ'3§M1ũŽŧzå?ŖÉ�€^šÅamv[b0of+~øš˛R>uĘD‰Xrãæ­äģ)īŊũFsõœ¨ĒRmŲļÃÃ]ÖŠc‡æ.��^HWÍėõŲ¯mŪļã§_VUW×8ģ8ŋ6cjû&û´×‹æĖŲs;cö´nÕjæô)´_ĪŪÔîĨĻ.˙ágs¯.ũßW"‘Ų›wüĢ<7 �°Ū‹ufĀJÍ~f°ĻFSQYiîÕRéŗ’Ÿ›…�Ā gžŽŗS‹æŽ‚ĪÍB��Ŧ×´÷ģ���xŅ ]���Đ é ���€NHW����t˛%]1 ŊÁP˙v����Ī2ƒŪĀąkđG�mIW|§ĒJiCG���€gˆēφĪãÖŋŨ?Ų’Ž|<ÜĒ”*šBŠĶëmč���đ/gĐĒk4å•Už˛M­-÷ģâq9AūŪ™šÅĨu:,x>å7w ��Đl8vl>ācÙAī&Ęãr[zÛÖ���ā9†Ī ���Đ é ���€NHW����tBē��� Ō����Ž����č„t���@'¤+����:!]���Đ é ���€NHW����tBē��� Ō����Ž����č„t���@'ļ }ōōōh¯�^4 Ã`04w��õswwoĐöļ¤Ģ†Î���đâĀ™A����:!]���Đ é ���€NHW����tBē��� Ō����Ž����č„t���@'¤+����:!]���Đ é ���€NHW����tBē��� Ō����Ž����čÄļ­›ēē&3ˇ ēFŖĶéé-ž !wíÆ­U Ĩ^˙Ėü`2™"Ą`ÆĢŅūžŪÍ] ��<ĪlIWęꚔ´,ąX(‹XLũzá\š•´v}|sWŅ`zŊž˛Ržâ—?Ū{ã5,��h:ļdŖĖÜąX(đ­^LĮN4w ļb0ˆÁ°fÃÖæŽ��žgļÄ#ĩēFĀįŅ^ <+Tjus—Đ †BŠlî"��āyfKēŌ LƒöR�žŽgčZ1��xáÔ����Ž����č„t���@'¤+����:!]���Đ é ���€NHW����tBē��� SĶĻ̝žYēäģĩķōķ§ŊöúåÄ+M:ĩ9ķüßΘŊÍ2õ‹ĻChÛŋ]ÔĒVû„1¯|ûŸDBaŗT��ĐÔpėę_äh‰Õk×5w´šzãvĘũQǰX,cŖ§‡Ŧ[įû­R(šą6��€Ļƒtõ/’™™ÕÜ%ĐlGĖūR‡>=ē[Æž24'/˙ĖųËÍX��@“b7w$åŪũ]1{˛srôzƒˇ—gÔ¨‘A­!:.ö@Üŋ‰ĨeeRŠãĀũúöîe}wVģ7vßŲs*•—į¸1Ŗ[øS]XLæŪØĮNœT)•ÁÁA3§Oą—H,Īøöü†”›Ÿåę5Ŋ^ßŗ{äāA/˙š~Sję}.—7jä°‘ @ĸš�� �IDATŦų›ĨßŨMI%„œ=wá?Ÿ}ė.sÛ{érbEeĨƒŊ¤[×.Ŗ^NzûŊÇž”œœ|wåōo5íļíģ’’SJ…T樝Oī—û÷Ĩ&zX^žnÃĻäģ)|ž`ā€~J•ęęÕë_ųšõĨ6RaqÉņĶįöëuņĘušŧĒsX¨Ÿ×÷?¯6 „&“9¨_¯íÛIĘË+NŸ3Ļ.?ŸáƒúģË\™ fn~AėÁ#ié™Ļ#Đgđ€>ĩĻ;xäxܑ㴯�� AX;v>|xƒú—IDV]4súĖ9&‹Õ=ĸĢiŖŧĒęØņ“á:z¸ģĢÕÕ˙ųōÁAĶ§ŊÚĢGdiiŲöģûöîÅáØmŨąķØąãÆŒ3z¤D"ŪŧuģŊŊŊ¯ˇéPēoŲēũÜų‹'Œ4 aQŅÎŨ{ģ„w ‡-(,rpŒ3:$8øhÂņĒ*EûĐv„ 39’ö }āËfL, vÅėMž›2vô+“'F+”ŠŨ{bûöîŨȚģvé|')90°õÂķZlØ´ųÂĨ˓ĸĮ=Rææŗg_•BŅŽmBČá# šyų­[ŒéÔÂéˇÕk˛˛sf͜>dā€RéĻ-[Ŋ<=e27BȝĢūČĘʙ;įĩžŊzž9{îîŊ{,Ģ_Ÿ^–[¯SgĪ[ŗ%=#+˛K'įŌģŠisĻOēzķļ1B6¨W÷ŽąqGb‘W)ĸ^RY)ĪÉËįØŲ-|wîŊ´[vî=éŠÔŅaÔ°A§Ī_ŌjĩÆaSd0iåīgliP´ōD2�� K3ģ*}XĻRĢ"ē…{Čd„IƇwîÄfŗT*UÂņ“C‡ ŠŒčJquuÉČĖ>pđP¯Ũ­ęŽV<}vüب.;BĻM™\]]]TTäâėDø“'FBü|}Ž\ģö =RīŒŪ^žaĄí!]Ã;¯ß¸9 Ĩ_€ŋ?õtßūƒîî˛ÆÔĖår™L›Í’ˆEōĒĒ3į.DęŪ™ââ✛ŸøhÂØ¨Qvl6ƒA8ģqcFSN?ŽÁdRKsss=vâäí;IÂÚWTTŪē}gōÄčļmB!sfĪx˙Ï­Y,j4š]ąq3& vlöŪ¸ÃT;Ëí~$áÔĨ+× !gJËŧ<ŨôéyūōUGG{{ųęÂĸBČÎŊqWoÜÖjuĩFϞu G­��āßŖ™Ķ•ĖÕÕÍÕuÕęĩ}z÷jâããؚr7%UĢÕĩ 1nØúÔé3jĩšĮãÕÛũ~ZšFŖņõõĄ6ŗcŗßš;ĮØ+  ĨņąD"I{NÉĖĘą<Ŗ››ÕÎįķ !2™ėņS!DŠRÕ;‚åšMegįčõz˙–˜iéëSS]]XXčéáA đ˙{ <.wüĄäģ)ry•Á`P(n..„ÂĸBƒÁ`<ĘįņÛįåXŗXz]ŋ•t75­Mpë-;÷*•*ĒŅÃŨÍb%ßģoÜ,5-="ŧ#‡Ã)*.-,.™6qėéķ—’īŨĪÉÍŋ˙ ŖÎ‘‰ Ņ ��ū=š6]1™ ĸ××jÔéõ„›Ma2™-\øäé3;wīi!u=ę•Čn]Uj5!dɲī„AõŌ „ŠŠJĶ_˙æē+JB—ËŠŗ*‡k|Ėxü ŪŲv˙ØWlö?ž †FÖl:šZ­&„đø÷ĸF¨VW?zĘįS´ZŨŌ?tú‰ÆÉdŽ,kåĪŋR/U)”„÷īÅ ßÁĘRitķvrp뀡“MVÄ%„ŧķú b0P- ƒ"‹JJËVüōG˙Ū="Â;<āayÅžøŖ—¯Ū¨sdä*��øˇiÚt%‹srōj5–—Bė튧‰8z\Tô¸¨ÜŧŧC‡Ž^ŗÎ]&đų„Ų3gxzē›ö•JkVgwąXLQĢÔ֗jũŒĄÎšũi#Ã“iũ*•ŠÂđk õ ũANNîĸļ Z*årg''Bˆ›MŠÖÔ7V(•t-ļņÔęjBȆ-;ķō MÛËË+!U 垇ö8äæâܡWä”訂ÂâėÜÚ—���ū…šöŽ mÛ´)(,ŧ}'ÉØĸ×öĮÅKŠ ¨‹ŠKŽ^tLÂÃŨ}ĘäIL/ĪÛËÃŽÍŽ”ËŨe2ęH(‹ÅvvvĻã›ë.ssår8wīĨ'ũߡËΞģ`ĄT+glüæj~ô˛Bŧ==™Lfęũ4c¯ûié|>:ågJŖŅBD"ÁãÍŌJJJ Ä@quq!„¤?>§ĻRĢ’’’éZlãåæhĩZ‘HXX\BũQ(•r…BĢĶĩpthDmVPTŧuWŦ^¯—šÕ^;��ĀŋSĶģęŅíÔéŗ?ũōûũ|ŊŊ+åō“§ÎddfžũÆëL&“RVVöĶ/ŋ‹ڎAį/^b0˜-[ōųü^=ģī‰Ũ' ũüüJËĘ6oŨ.ut˜÷Î[Ļã[čŪŖ{ÄūĨŽî2Ų‰S§32˛fNķˇPĒ•36~s5B„BAfVVfVļT横{äūƒņ.Ž.Ū^ž))Š ĮO 4Āôļœ/OO;;ģ#G14'7oĮî=mۄäVTVē¸8ûøxī‹;čîî&vėÚc˙øxaãÛxęęęŗ‡žÜWĄPffåHĸF )¯¨üíĪMŽŽ¯M‰ŪwøvrŠÁ@:w5 é™ŲO­6��€ÆhÚtÅfŗ>|˙Ŋũqq‰WŽĮĮaąYūū‹>\`ŧÔ:(°õĖéSãŲģÉdš{ČŪ~cŽ››+!dÂøą`ûÎŨå•öû°öíÆŒYk| ŨĮ‰b0ÛvėŽVĢ===æŊû–‹‹ŗåj­™ąņ#X¨y@ŋ>ŋ¯ųķĢ%ËŪž;{ōÄņ|wÃÆ-•ōJŠÔqذ!Ã|rF‰D<sڔ1{Î]¸āëã3kú”˛‡åŋŽZķí˛_-ūlîŦ™kÖoX˛tšŊƒũđĄƒ32˛Ō32čZlãíŪ¯RŠG(‹*åUˇ’îî;x”r˙AÆĻí1ũzFØO¯Ķį­^ŋĨ¸¤ô)—��`ÆėŲŗW­ZÕ >×î¤z¸Õ“Tāß ēēZĢĶ Î.Yļ\$ž9wv#‡ũrÉ÷.­™ũøíâæ.��ž[ͯvh:+~øš˛R>uĘD‰Xrãæ­äģ)īŊũFs��đœCēzžŊ>ûĩÍÛvüôËĒęęgį×fLmúRs��đœCēzžŲÛKæÎžŲÜU���ŧXšöŽ ����/¤+����:!]���Đ é ���€NHW����t˛%]1 ŊÁ@{)�OƒÁhî��āyfKēâķ8UUJÚKgŸĮ#Īnŧ6DBas��Ī3[Ō•‡[•R%W(uz=íÁŋ_ŸžŊÉŗ{ø‡Á˜9%ēš‹��€į™-wåq9AūŪ™šÅĨu:ŦŽ›ŗķ{oŧļfÃÖ*…ÂđėÄb0"Ąpæ”h_īæŽ��žg6ŪĢĮåļį¨Úן}ØÜ%���üá3ƒ����tBē��� Ō����Ž����č„t���@'¤+����:!]���Đ é ���€NHW����tBē��� Ō����Ž����č„t���@'¤+����:!]���Љm[7uuMfnAuF§ĶĶ[���@ŗãØąų<ާ˅c×ā°dKēRWפ¤e‰ÅB‰XÄbâč×s%ˇ ØÃÍššĢ���hfŊA]Ss÷~fP€OC–-Ų(3ˇ@,Š|D+���x.1˜ >k/åä5´¯-ņH­Žđy6t���x†đ8•ēēĄŊlIWzƒÉ`ØĐ���āÂ`2j4چöŠ=����:!]���Đ é ���€NHW����tBē��� Ō����Ž����č„t���@'ŋÅŲzƒá܅‹§NŸÉĘÎŅëôRŠ´cĮ°Aú‹DÂÆ ûÖ{īŋÜŋ߈aC!%ĨĨŋüļ:;;glÔ¨ØqÆvxÎlŨ“š–nÚŌ!ôĨĄƒúS/&^ģtåĒ\^å`/éŪ­ËKmC¨vƒÁpūRâå+×Je ŠcߞŨ[´| Õ.ûá—.:öˆčB)¯¨ÜŊwaQqß^=NŸŋ`l�€įR“§Ģß×üyūÂ˙ˇwßņMgߝŖ=mKŪÛÆ€Á`0ĶŒYŗ {%„„„$mf›4oÚĻMĶ•Ũ4OÛ”ļ™„ŊˇYÆÛx°Ė0Æ ī=åĄ­ķū!Æ [9Æ@~ߏ˙îsëHy~=KácFO™4I(Üŧ•2îTVÖŲwŪú…Ŗ“cĻ:+?˙ųg×ҊeK|ŧŧ­íIÉŠ%Ĩeŋ|ã5Ow™\fkˇCë%9tņŨa0öī>f”­E)WX_œ=Ÿ}âT”¨ Ūžžų……ûÅJ$âũƒˆ(!%-ít攨Ho/ĖŗļīŲ˙ĖS+ŧ<<zŖÂĖŗįKËĘĖ™IDͧLrsuąļŸŋxОēvÕ˛Å.j•D"ļĩ˙Č%��āáÔģé*195ítÆÚ§VOž4ŅÚ2jäˆņã#ūø§wī?đ˚'{4[AAĄíuäøqļ×ÍÍÍ.Î΃‚´iˇCë%9tņŨĄ7<ÜŨü|Û´ŗ,›œ–>zdØøđ1DäīįS]S—œš>°Éd:ž>:bė("ōņöĒŦĒN=šä‰yŊQayy…íõđĐ!ļ×:­ÎÉŅÁßΧMû\��NŊ›ŽNÄÅõ °E++oOĪw~õ wë[ŗŲŧ˙ĐáôôŦšÚZĩZ3}ÚÔɓŦ›^yũÍysf]ē’““s5 Ā?īú "JI=ũ‡ßũæãĪ>ˇžüķ[Û×>÷â’EOÄ;n;3h4™öí?’šŪŦÕúûú,[˛h@˙ "jĐhļmßu%'ˇšĨY­VM›2yFôT"úāãO¯ææŲ–đņöęŦ°Ö^zųšsbĘĘ+˛ŗ/ęôú!!!Ī<ũ”ƒRŅĻūŋ˙í#Ą@¸kĪūŒĖŦÆÉŅa\DøÂķø|~דØņųøųų*Ў~ņš­Č/ūųeCƒæŨ_ŋÍá—kąX’RO_ÎÉmĐ4:8(ÂG="Ŧ}ˇÆĻĻCąĮķ ŠÄbqø˜‘zŊūęĩë/=ˇļŗi/^ÉŲw0öš§W{¸ģQQqɡ›ļ-ybŪāāzŊA$ļR[[ß i dkØŋßۃGôz}CcŖŅd đ÷ŗļ3 3h`Pæ™ķ.m2›“Sŗ/åčôzwˇi“"}}ŧģŪĶOŋø2r\øÍü‚ü‚BOO÷ĸâR"ĘžtåųĩOū°m§õ ⎛ļ—Ņ?ülę¤Č´Œ,ۙÁÎVljn9q*!ŋ HĢĶ98(F>z}ŋe{Aaąm 7W—î|��đ€õbējŅļĪ3Ģũ&˙;˙ÚŅļģ’×<šĒPŋË99›ˇnįķų“&FŸ/ˆOL:î,ww÷O˙ö…ģģûę•Ër™mø¯ũ|ëö]yy7ŪyûMąX{ėøŨ™ˇīĖČĖZŊj…›Ģ뉸SŸüí‹?ž÷Ž›ĢË×ß~_V^ņâúuNŽ×ōŽģņgõČa¯žüŌGŸ|n[b뎝Ö_Ā;{låōĨĪ>ŊĻĸ˛âãOŋØ˛uû Ī?ÛĻ~ąHüŨÆMgΝ_ŗze`@Ā›ˇžÛ¸Ų`4ŽZž´ëIėø|.dgũíÆēúz•“étúK—¯Ŧ\ž”ģī–ˆčøŠÄs.Ξ1ÍĮËëVAÁŅ“ņ|ÄđĐ6ŨƯ¨¨Zļh\.;•˜\]['āwõ_]hČā+9šGŽĮ­]ŊœeŲØ§B <€ˆ ƒHØAēĒŠĢ#"UĢÍÖ×5ĩõ<CDŪŨģ7d2™N¯×juRФÍ<'âŽ\͍‰žĒrrĘ<{nĶö=/<û”ĘÉą‹=åķųg/dė4q|¸Z­ÚŧmˇZ­Š‰ž"•ˆmĶŽX˛āD\bQqéšÕËEBAZFÖ}W<xähumíÂyŗryaIÉĄØãNŽĘāũ—-š˙Ö]ļ%ēų��Ā֋éĒžžˆÜ\ēēÄDĢÕÆJ˜3{æ„ņDäîî–_PtčČQkz`‰„Ë–,˛væņøßzDĮF&• ×Ļ]ĢĶ&$Ĩ,_ē8|Ėh"ZģæIŊ^_YYéæę˛jų2†Įŗ^ûâáá~2>áŌå+#G„ɤ2Û]ֆŸŸŸõŒ¤§‡Į”É÷<ü´N/‘ˆ[×ßØÔ”œzzÅŌÅácĮ‘››kIYŲąqK/ M²;>ŸącFoŪēũtzÆŦ˜Dt!;›e)|ėč~]ŅëõgÎ]˜1Æzņ¸ZíTZQ™’žŲæŸöæĻæ7ķgFOíčOD įĪūâ_˙S*•]O>;&úß˙ûîÂÅËF“IŖi\šôö ÆŌ˛ō¯žßRU]ŖPČB‚L! =‰Å"Û B‘ˆôFƒ—‡Ã0Ĩå•ÖcBDTYUMDzƒĄMēŌôį˛/M›5dp0͙9Ũ`4ÖÕÕˤ’Ž÷T(N›|ûč,Ããņų<šLÚzf‰XÂđ^ÛöÎVT99Θ:…á1֌čėŦĘ:wūÆ­üāũ%b‰m‰n~��đāõbēb†ˆø~} ‹M&ķА[Ëā⁉IÉ:N"‘Q˙ ;oī*))5ūÖˇBāå—^°ž–ˆÅcæ\ÍmllbYļššŲÃÍ­§…ĩāw÷Pœˇˇ—ŅhŦ¯¯ˇžú´Õ_TTląX‚úÚzö đ7čõ>ŪŪMRß ąãķ‹DcĮϤĨ[ĶU֙sÖ먭Ž{Ę+ĒĖfsŋ;/øųœŋpŅ 7ˆZĨœšē:–e}ŊŊî& đĢŽŠëzrĨB=%*.!ÉląĖš>Íz¨’eY>× i7v”R!/*)ML9Ũ i\8¯ĢûCÅ"ņĐÁƒRŌ3<Ũ]==ܯæ]ĪÍģAD|>Ībą Fk7ŸWUUc2™ŧ<oŸ°đųKŸ˜GD…Å]īŠˇgw?ĩ{uļ"‰D”ôŒü‚ĸ–-KŦVĢsVŠÚ īæW���^/Ļ+''G†a***ģčŖÕéˆčÃO>cˆąļXX–ˆ4Öô ‘JģŪ…ææē÷x†•Édūøķ/XŗeÕĘežžî|†˙÷~iGa­IÄwO‰E""jÖļÜŪt§~NGD’VĮKŦķčuú.&ąûķ‰š8áTBbaQąģģۅK—_ũŲ vŗ›Ū` ĸ[w2wZŦ…55ˇ¨[}æZžˆDĸģ-R‰”č>銈†† :—Āįņ ėomaæ­×nëāëãͲl\BrLôąXLDzŊA"žũņZ?UŠHLD1ŅSö8üŨæíDäíå9.üx\ŧD"š™_°eĮk˙aCCB $"Ą°í˙EÜwOÛ˙7ÖMÖ/ˇũŠfŗyĶö]Ŧ…‰žėėŦæ1ŧíģ÷ĩŪͯ���ŧ^LWR‰ÔßĪ7%5mۜYÂ{/—ÉĖ:# G &“J‰hũēg}|ŧZwPĢÛū/õž˛ž~ŌiumÚoŪēY\\ōÎ¯Ū žķĪļĻąŅĩŨéËfũgōök­ŽˆäíYcPëz´Z-Iîœ-ęp“ŅÜũ2Z đ÷÷ķÍČĖō÷÷SČä!ƒwŨŋ§ŦYđ‰š3Ũ\][ˇ;8Üs~VĀį‘Ád°ĩčômŋ‘%$Ĩ9(&‹91%męŊwEظģšQŖĻÉE­&ĸÚēzGëϚÚ:†aÔÎ*"’J%Ģ–-Ō46‘ƒRŸ”âŦR /¯§W-ŗöWČåZŊžˆ zC›Uēš§vÉdŽXRZVYUũôĒå~žˇĪf6ˇ´8ĩ{|Iī��?Rī>Ģ=fztMmŨž‡Z7—”|ûũĻsįŗ‰ČĪ×[(hŊ<=­ šBŠT ;ēx™ˆˆíîŌžîb‘čęĩ<ë[‹…ũëGŸ¤¤ž6MD¤PÜN?×oÜ¨ŽŽa[ĪËRO ËŊvÍö:ŋ _$ĢÕę6}ü||x<žõļž;KߒJ%ļ“’Nōc>Ÿ‰‘2˛Îddž?>Üzq7‡ÜŨ\|~s‹ÖÅYmũ“J%2™T ¸'¯ĢU*"*-ģũŊA3ŋāž“—–—§Ÿ9;+fÚŦéSĶ2ÎX‡W×ÖnßŊŋ˛ēÚÖ­¸´Œa•ĘQĨrRĢlß5åæ]÷÷õą^éĘÕŌōrĨŌAŠ´X,/į DD‰ØĪ×Įú§VĢ\Ô*ĄPXPTbeŲī6oËžtĨ›{j‡ÎV4›ÍD$•Ū>–Y\RZß aÛũ—ß{…�ĀÔģ˙x\ÄØœÜkĮ…-‹ NÆ%zyyŦX¤R館ČŊû(ōĀĀšÚÚÍ[ˇĢUNoŧúrûŲärYAaaAaQwŽlIĨŌ‰‘ã:ĸV9yyzÆ'&åįŽ[$‘H„Báņ§ž˜?§¸¤tĮîŊC‡„”•W4h4Ž­—č~aõõ {ö˜QZVvōTBÄØŅí PČ'FN8x$ÖÍŨÍĪ×'77/îTü˙ͭOdč|Ą}ŸRĄ1vûÎ]55ĩy˙÷÷ũ¸zJ, MHN•I%^žž Íą“ņJŊ% ‰(ëė…K9W׎^ŽR9zx¸%§Ĩģ:Ģ%ÉÉø$…üî3úmŨZĪl6›>64dõšVƒö?päčsO¯vrp¨ŦŽŪšįĀ䉔 yaqIZzVøč‘ÖĶŽĮE8rĖAŠôņōĘģ~ķúÍü'W,ąN˜›wŖ´Ŧ,&zĒL*IË<c0š"FėpÂB‡$§e8(.ÎÎg/d—•WΟåÕõžļ!•ŠË+*Ë+*îså~+ŠÄ"@‘u>jBDeuu\brŋ@˙šÚÚĻæ…\Öz‰î��R¯˙ĪÜgŸ~rČāāS ‰›ļl3[X7įšsgFO™,žs™ŅĘåKe2Ųöģë4ŽŽ#ÂB—,zĸÊĻO›ōŸ¯žųķ‡ŸŧōŌúî,ŊlÉb†aļíØ­×é||ŧßxíe77W"ZˇvÍÎ={SOŸđ÷ū™5ĩuõ_nøęŖO>˙ķûŋkŊD÷ ‹ŠšĐŌÜōū_>0ŒaÇ­^šŧÃnOŽZ.•ˆŋ߸E͍QĢUsįΞ;+æž“Ø÷ų„"—Éët:w÷ļ×ėsbúÔÉbąød|RcSŗB.8 ßÔ¨Û7T6h4Å%ĨÖ׋æÎ9{tã– …<r\xYyeiYyûn6Ч3›lŲhÆ´É_ū÷ģ”´Œ¨ČqĢ—/9•|ôÄŠ­ÖŅQ9uR䘑ˇī4lhˆÁ`LËȌOJuVŠ/˜k{č蜘čÃĮNî?k2›ũ|ŧŸ^ĩLŪɯ0M›Å0ˉøDƒÁčæę˛rÉB•ĘŠë=mcėČ{ųvĶļĨŨ{Zig+Λ5ãTbōÅËW<=ŨĖŽŅ46íŪwø‡­;_\ˇĻõŨ/ ��$fũúõ6lčҘs—ķŧ=\īßī§Ąõ/öí$mh4oŊķÛuk׌mõĶ1÷UR^Åí—k0-f‹äÎķŸ6nŲ!•JzéQé���œ+)¯1d@†āĮPSSsyeå–m;ŧŊ<Gęā,؃´mįŪææ–Ų1ĶryŪ›ų…E+w|ė ��āņ€tõJNIŨšgīĀÖ=ŗ†ķëŲ{jŅŧŲGãvî=`0šTNŽķgĮ čoį3Ė��� 83÷āüĖ ��Ā#͎3ƒŊûD���€Ÿ¤+����.!]���p é ���€KHW����\˛']1 ci˙ŗg����ÖŠ„=~|•=éJ*55ĩØ1���āĸ3¤w~n¤ûėIWūŪM-ÚÆæŗÅbĮp���€‡kaõcŊĻÉĮŗĮ?×kĪŗÚ%bŅ  ŋ‚’ōǚ:ŗëqSR^Õ×%���ô1‘P •ˆ÷÷ˇãĖ ŋ„#‹‚ûųŲ7���ā1†{���¸„t���Ā%¤+����.!]���p é ���€KHW����\Bē���āŌ����—Ž����¸„t���Ā%¤+����.!]���p é ���€KHW����\Bē���ā’Āža:ŊĄ ¤\o0šÍn ‚^âéĻJHNĶéôŦ…íëZ~,†ĮH$âI‘ã<\]úē��€ļėIW:Ŋ!÷FĄR)wP*ø<ũz\ÎŊqáÂyzäcÕmŦ…ÕļhcŸš9} ��<lėÉF%åJĨ\!“"Z=*Žää<6Ņę†X6!ųt_—��Ж=ņH§3ȤÎKŪc0úē„ŪĀčtēžŽ�� -{Ō•…ey Ãy)�=õ\C��œÚ���āŌ����—Ž����¸„t���Ā%¤+����.!]���p é ���€KHW����\˛ķWœģeŲÔĶé‰IɅEÅŗE­V5bæôh…BūcĻ}ųõ_Έž6îl"ĒŽŠų×ŋ˙[TTŧtņÂũ‡ÛÚŒÖĢΘ>큭ÛKĻD÷÷õļŊÕéôuõõį.^ŠŦŦM~aq|Rš­CÖ3WD�� �IDATšėKWr[wKÄËÎåņxßmŲ‰§€�ĀcŦ×ĶÕžú&ítFø˜ŅS&M 7o埌;••uöˇ~áč䨪ŠNÄÅßĘĪūŲĩD´bŲ¯Û˙Æ'%§–”–ũō×<=Üer™­Ũ­—čĻÖĢÛŊŽė(ĩ›4MŠéYÖ×RŠ$¸ĐŦčɏžŦŠŠŗuHË8Ķf”N§ˇŊ6™LAūmŌUŋ�_ Ëâ`)��<öz7]%&§ĻÎXûÔęɓ&Z[F1~|Ä˙ôáîũžYķdf+((´ŊŽ?ÎöēššŲÅŲyPđ€6ívhŊD7ĩ^ũA˛ŖÔn2™LåUw**Y<VČ I)ļeå•]ĖPQUãíéŽV;ÕÖÖÛƒükjjŨŨ\{Šl��€‡DīĻĢqqũlŅĘĘÛĶķ_ũÂãΑŗŲŧ˙ĐáôôŦšÚZĩZ3}ÚÔɓŦ›^yũÍysf]ē’““s5 Ā?īú "JI=ũ‡ßũæãĪ>ˇžüķ[Û×>÷â’EOÄ;n;3h4™öí?’šŪŦÕúûú,[˛h@˙ "jĐhļmßu%'ˇšĨY­VM›2yFôT"úāãO¯ææŲ–đņöęŦ0›6ĢĮLŸļkĪūŒĖŦÆÉŅa\DøÂķø|~›}ųûß>zû7ŋŸ7kfIYŲ™ŗį,KTä„Y3g|ķŨyy×ÅbÉÂ'æNœ0žûĨúûųöÖWHd1[ęęŠîŅjĩĩuõũũ3î¤+…‹ŗú܅KíĶUXhHذ!mĪg_>ņʏ)�� ¯ôbējŅļĪ3Ģũ&?Ûëm;w%$$¯yrU˙ ~—sr6oŨÎįķ'MŒ$">_Ÿ˜6<tūÜYîîîŸūí ww÷Õ+—+ä2Ûđ7^ûųÖíģōōnŧķö›bą(öØņģ3oߙ‘™ĩzÕ 7W×q§>ųÛ|ī]7W—¯ŋũžŦŧâÅõ뜎å]˙vã.Îę‘#Â^}ųĨ>ųÜļÄÖ;;+ŦŗÕ7nÚræÜų5ĢWܸyëģ› FãĒåKÛė‹X$đøąĮN<õäĒĩO­ŽOLúnãæœÜkO­ZÔīĨŨûöoÜ´edX˜\.ëfМwm(ōúMëŋí)>‹Ųb{Íđ˜[…E!Á2Īe[/ą ô¯Ģ¯¯×h¨kŠj°­��ā‘Ö‹éĒžžˆÜ\\ēčŖÕjãN%Ė™=sÂø"rwwË/(:tä¨5Ä0 ‰DÂeKY;ķx|€ī ŧį ŠL* <¯MģV§MHJYžtqø˜ŅD´v͓zŊž˛˛ŌÍÕeÕōe įæęBDî'ã.]ž2rD˜L*ŗ-ŅuaŽŪØÔ”œzzÅŌÅácĮ‘››kIYŲąqK/ mö…ˆü|}F %ĸˆącžÛ¸šŋĀūAAÖˇ)// ęםRíûvēÆđë ‰D<xā�'G‡Œ3įm[Õ*§5+ˇr ö„íÂ,"ē™_8jx¨ˇ§{qI9õ đģvũfgËĩXˆV��đ¨ëÅtÅ0 ņü.ú›LæĄ!!ļ–ÁÁ“’u:D"!ĸūAũė[Ŋ¤¤Ôh4ø[ß ‚—_zÁúZ"Œ=šs5ˇąą‰eŲææf7ˇžÖ^QQąÅb ęhkéāoĐë+**|ŧŊÛ‡‡õ…T*%"OOĪ;o%DÔĸÕvŗTΊUNO¯\b{Ģ7’Ķ2KË*l-McbZF›Q ÷ÜjnjЍĒîP\RîææĸT(n9ĢU-jKTˆV��đ¨ëÅtåääČ0LEEW—?ku:"úđ“Īē}°Ä²DÔĐ ą†‰TjßęÍÍ-D$‹Ú´›Læ?˙‚5[V­\æééÎgø˙į—v֞N§#"‰ôîVkOũ›éÚė‹@x·/Üķ–eŲn–ĘšMcbjēõĩÉdŌ46ĩy€‚ÉlŽŽŽŊī<7ķ ĮŒ. úøUU×45ĩt‘Žš ��Ŋ˜Ž¤ŠŋŸoJjÚŧ9ŗ„BaëM™YgBáˆáÃdR)­_÷ŦWëę.˙îĨRID:­ŽMûÍ[7‹‹KŪų՛Áû[[4ŽíN_ÚQ˜5<ĩ^Qk=ū$ŗ3 vŗTÎ™ÍæÖįøė–_X>*ĖßĮ'ĀĪį’��üdôîã‡bĻG×ÔÖí;p¨ucqIɡßo:w>›ˆü|Ŋ…ĻąŅËĶĶú§+”Je›4vWˇBééá.‰Ž^ËŗžĩXØŋ~ôIJęiŖŅDD Åí+Á¯ß¸Q]]Ãļž—%{ #ōķņáņxÖ[īL~K*•Ø}.¯›Ĩ>´ô:CiYÅĐ!Áb‘čVaq_—��đ€ôîÆEŒÍÉŊvđplAaQøØŅbą¸  ād\ĸ——ĮŠe‹ˆH*•NŠŠÜģ˙€R! ŦŠ­ŨŧuģZåôÆĢ/ˇŸM.—uįȖT*9ūāĄ#j•“—§g|bR~~áēĩA‰D(?qę‰ųsŠKJwėŪ;tHHYyEƒFãčāĐz‰îfĨPČ'FN8x$ÖÍŨÍĪ×'77/îTü˙ͭOd°ƒ¯O7KUöäq ?ž@ đōj÷čT–Z_›eu3ŋ0jBxiyEûƒˆ���Ģ^VûŗO?9dpđŠ„ÄM[ļ™-Ŧ›‹ķÜš3Ŗ§L‹ÅÖ+—/•ÉdÛwîŽoĐ8:8Ž ]˛č‰§š>mĘžúæĪ~ōĘKëģŗô˛%‹†Ųļcˇ^§ķņņ~ãĩ—ŨÜ\‰hŨÚ5;÷ėM=}:Āß˙ųgÖÔÖÕšáĢ>ųüĪī˙ŽõŨ/ĖæÉUËĨņ÷ˇh5jĩjîÜŲsgÅôäĶē‡ƒƒ˛›Ĩ†mûŧ¨^å T˘ÕĻŅbą|ŋeW›ÆÂâR“ÉtĢ čA•��Đ÷˜õë×oذĄGcÎ]ÎķöŽ%›ļīV9(ûēŠ^ąvõŌž.��āøŲ7����.!]���p é ���€KHW����\Bē���āŌ����—Ž����¸„t���Ā%{ŌÃ0öáū‰;øi`xL_—���Ж=éJ*55ĩp^ ô‘Hô°˙æŗ=X‰DŌ×5���´eOēō÷öhjŅ66ˇ˜-Î ‚Ū2x01Ũa†™Ņ×E���´eΝ8KÄĸAA~%åU5uf3Ö#ĀÉŅađô) ɧu:kyäb1<F"‘LŠŒđpuéëZ���Ú˛']‘D, îįĮm)Đۖ/œÛ×%���<ūpĪ ����—Ž����¸„t���Ā%¤+����.!]���p é ���€KHW����\Bē���āŌ����—Ž����¸„t���Ā%¤+����.!]���p é ���€KHW����\Ø7L§7””ë FŗŲÂmA����}N$H%bO7‘°ĮaɞtĨĶro*•rĨ‚ĪÃŅ/””Wy{¸öu���pkauÃÕëƒúû÷4`Ų“ JʕJšB&E´��€ĮÃc¤ઃĸ¸Ŧ˛§cí‰G:A&•Ø1���ā"‰´:}OGŲ“Ž,,Ëc;���<Bc0šz: §ö����¸„t���Ā%¤+����.!]���p é ���€KHW����\Bē���āŌ����—ėüįnúû?ž<wū‚õĩH$tqv:4$fú4gĩēMŸŸŊđüØ1Ŗl õ ¯ŊųöÛoũbpđĀ.æ$"ĨBáããŊ`ŪÜAÁzsW€ˆhëÎ=y7nĩn9|؜™ŅÖ×éYį2Μmllrrtˆ>lhˆĩeŲ´ŒŦĖ3į›[ZœÕĒŠQ‘ú÷ëÎrGŽŸ,(,yqŨn÷�� Wõnē""77×gž~Šˆô:]QQIBrrRrĘë¯ŧ<°ŋ­ĮlßškøđPąHÔŖ9‰¨žž>>!ņÃO>{÷×o÷ ā~îįD\ü­üüįŸ]ûā—~đ FãĀūũÂ[å`Ĩ\a}qö|ö‰S Sĸ&x{zæî;+‘ˆö"ĸ„”´´Ķ™Sĸ"ŊŊ<2Ī^Øžg˙3O­đōđč›}���čeŊ~fP,88x`ØđaķæÎúĶ{īúøx˙ķËkuZ[Ÿ°áÚ[Zbc÷tÎÁÁĮ…ũåë¯99:?×;{p…}˛nŸĐ *•*ĀĪ×öįėŦ""–e“ĶŌG >ÆßĪgR丐AÁÉŠéDd2™N§gE„Ž;Ę×Į{áŧYÎjuęéĖžŪ��€ŪŌëĮŽÚH$k×<ų›w˙’š=u˛ĩQ*•.˜;gĪž'ŽSĢÔ]īˆH$ôõõЍŦ˛ž}åõ7į͙uéJNNÎÕŋ˙í#Ą@¸kĪūŒĖŦÆÉŅa\DøÂķø|>ŊôōsįĔ•Wdg_ÔéõCBBžyú)Ĩ‚ˆĖfķūC‡ĶĶŗjjkÕjUĖôiS'Oj?@€ŪõD”’zÚĪĪWŠPŧõ‹×l…}ņĪ/4īūúíķ‰ĩaąX’RO_ÎÉmĐ4:8(ÂG="Ŧ}ˇÆĻĻCąĮķ ŠÄbqø˜‘zŊūęĩë/=ˇļMˇ“ņIiYŋũÕ­/^ÉŲw0öš§W{¸ģQQqɡ›ļ-ybŪāāzŊA$ļ_ŽļļžAĶ< ČÖ2°ŋŊčõú†ÆFŖÉāīgmgfĐĀ Ė3į;ÜģÆĻσGŽį‰ÅĸQaÃZoúô˙ū9.\ŖŅ\ÎÉ5Œ~ž^sgÎP(ä÷ũÄ���°>¸ĒŨÛĶĶÃŨ=7÷š­ÅbąDO›ĒRŠļíØcߜUUÕjĩ“õ5Ÿ/ˆOLöņözû­7Ä"ņÆM[’RR–/]ô×?žˇdŅÂ'ãˇíÜ}ģ§€w8öØ āŸúņ~÷›‚‚ĸ-[ˇ[7mÛš+6öøŧ9ŗūôŪģ1ͧmŪē=!)šũü¯žüR€ŋ_øØ1_üí“éĶĻ\ÉšZW_oíĻĶé/]ž9aœ}{ԙã§Ķ2ÎDŽ á™5ŖG;™pîÂÅöŨÆ/¯¨ZļhÁĒe‹ ‹Š/_ŊÆ0|×.ÎęAmCC <r<ŽeY‹Å{âTČ ƒƒ‘Á` ;HW5uuD¤rr´ĩX_×ÔÖ[Ė"đîŽ.“ÉtzŊVĢk?Īۃą•UÕ+–<ņÔĘĨ-ZmÎĩëļM|/-=ĶÕÅų՗ž{ņš5e啉Ši]}R���}¤oîTĢUõš†ģīYøË—-IĪČĖËģŪų¸ģĖwÔÖÕíØš§Ŧŧ|rÔDë&†!‘H¸lÉĸūAA-Zmręésį„ãææ:.bltô”øÄdŖéöī]ûųųEŽĮã1žS&OĖ:{N§ĶkĩÚ¸S 3gNŸ0>ÂŨŨmęäIãĮ;tähûų• ĮøJÅØ1Ŗ%ņéô kˇ ŲŲ,KácGsõĄ‘^¯?sîÂ¸ąŖ† QĢF:4$%ŊíYļæĻæ7ķ#Į…÷ ôwws]8ļļEÛá„ÃC‡,_üDûöŲ1ŅÕÕ5.^>s>[ŖiŒ‰žjm7ŒĨeå_}ŋåƒĪūņ˙|—d4‰Č`Đ‘X|÷˛9ĄHHDzŖAĨrdĻ´ŧŌļŠ˛ĒšˆôC›E5ų…"Æúûš:;Όž*Ūsž‹ŗ:lØPį Tö ,+Ģ$��€‡Īƒ>3heąXø<~›ÆÃCC‡ųaëöß˙李‡¯{áįļˇr™ėŲĩk† ąĩôęgëiąX‚úŨ=<Ķ/Āß ×WTTøx{Q€ŸŸm“ˇˇ—ŅhŦ¯¯¯oИLæĄ!w'<01)Y§ĶI$’Öķˇ&‰"ƎMIKŸ3ƒˆ˛Îœ9"L&•Ũįŗč‰ōŠ*ŗŲÜ/ĀßÖāįsūÂEƒŪ j•ljęęX–õõöēS˜80ēĻŽû )Šč)Qq If‹eÖôi šŒˆX–åķx šĻqcG)ōĸ’ŌÄ”Ķ šÆ…ķfw1•X$:xPJz†§ģ̧‡ûÕŧëšy7ˆˆĪįY,ƒÁhíÆãķjjj‰ČËËŨÚÂ0Œˇ§GųžDäææj{-‹ĩúŽ~��ôšžIWåå•CBĩo_ą|Éģŋ˙cRJJXhhÃ=ÜŨ_xūYëk‘Hčîæ.ܓÕ$RŠõ…N§#"‰Trw“DBDzūö[ąØļÉzĮbŗļEĢĶ҇Ÿ|ÆcŨdaY"jhĐX‡Ûæo#jâ„S ‰…EÅîîn.]~õg/tąv°īŲ¸u's§ÅZXSs‹ēUēŌęôD$juĻT"%ęAē"ĸĄ!ƒŽÅ%đyŧAwîîdæ­×ī†Z_o–eã’cĸ§ˆÅb"Ōë ņíÚú KEb"Љž˛įĀáī6o'"o/¯ČqáĮãâ%ÉÍü‚-wÎ< ? w˙›Ũ{ŠPĐ&‘ŗ=Ú#��€ŖŌÕĩŧŧú†úևšlŧ==§M™´kĪūÁÁÁ]Ė  [ŋé‚5éZ]âŖÕj‰H"ģŦAęÎ&ÉĨ2“ŅLDë×=ëããÕz6ĩZÕõrūū~ž™Yūū~ ™<dđāîŲ}Ö,øÄܙnŽŽ­Û­ß ø|"2˜îžzĶõü0OBRšƒBa˛˜SŌĻNšØaw7W"jÔ4š¨ÕDT[Wīčā`ŨTS[Į0ŒÚYEDRŠdÕ˛EšÆF"rP*ã“RœU*Ą@āãåõôĒeÖū šŧŽĄZ_"Ōéõ��đ¨yĐ×]57ˇ|÷ÃVįŅŖGvØá‰ųķ,푪Ũ}:C×ü||x<žõļ>Ģë7nIĨ77ëÛÜkw/ŽĪ/ȉÅjĩÚĪ×[(hŊ<=­ šBŠT ;ē ›čžc(#'ddÉČ<3~|8ĮtÜß^în.>ŋšEëâŦļūIĨ™LÚúxŠU*"*-̰žÕô7ķ z´Piyyú™ŗŗbĻ͚>5-ãŒuĒęÚÚíģ÷WVWÛē—–1 ŖR9ĒTNjĩĶÕkyļMšy×ũ}}Ŧ—Ā_ērĩ´ŧÜAŠtP*-ËÅË9‘D"öķõąūŠÕ*ë3f+*oĪo6›ķ‹Šzū!��ôą^?vĨ×ësr¯‘Ųd**.>~"^¯×ũōW…‚Ž—–Ëe Ėß|įŪŊIĄOŒœpđHŦ›ģ›Ÿ¯Onn^ÜŠø™3§[ŸČ@Dõõ {ö˜QZVvōTBÄØŅ"‘H8)*rīūJ…<00°ĻļvķÖíj•͝žÜaÁ………EjĩJŠPŒ‹ģ}įŽššÚŋŧ˙{NvĄ5ąX<2,4!9U&•xyz6h4ĮNÆ;(+–,$ĸŦŗ.å\]ģzšJåčáᖜ–îęŦ–H$'ã“ōģO.°u#ĸėKWrķŽ/]8ŋõ*fŗųĀácCCøųŅ ũ9úÜĶĢ*ĢĢwî90yâĨB^X\’–ž>z¤õüŨÄqŽsP*}ŧŧōŽßŧ~3˙ÉKŦææŨ(-+‹‰ž*“JŌ2ĪŒĻˆŽ˛ĩ“Ŗƒˇ—WĘé •ĘQ.“eœ9×úNC��€GE¯§ĢĘĘĒ?ūŒˆx<ÆÉQ:dۜ™.ÎÎ] ™2iâŠø„â’RN xrÕrŠDüũÆ-šFZ­š;wöÜY1ļ­QQZš[Ū˙ËFƒ1lø°Õ+—[ÛW._*“ÉļīÜ]ß qtpēdQ÷ÖŅôiSūķÕ7ūđ“W^Z:tˆ\&ŦĶéÜŨŨ8ŠŋírS'‹Åâ“ņIMÍ š|ā€~SŖ"­›4ۇļhîœąG7nŲĄPČ#Į…—•W––•ˇīVU]cŊÆŧĩÔĶ™Mļl4cÚä/˙û]JZFTä¸Õ˗œJH>zâT‹Vë荜:)rĖČÛOÛ64Ä`0ĻedÆ'Ĩ:ĢT‹Ėĩ†3"š}øØÉũ‡bMfŗŸ÷ĶĢ–É;yNÕÂųŗ9ļ}×>‘X<jİĐ!ƒ¯^ëÖ=¤���fũúõ6lčҘs—ķŧ=\īßīĄ÷ōëŋœ=mūÜŽnyë)Ļņ­w~ģníšÖ?›x_%åUÜ~¤ŖŅbļH$ˇ¯Ų߸e‡T*YōÄ<—���ø)()¯1¤gŋeÜ7÷ >–šššË++ˇlÛáíå9zTĮW•=0Ûvîmnn™3M!—įŨ¸™_X´ĸŖįZ���įŽ8“œ’ēsĪہŦ{f į×ŗ÷ÔĸyŗÆ%ėÜ{Ā`4МįĪŽĐŋƒgt���į~ŌéęŸĘál3cĻΌ™Îá„?†\!_4ŸË3ž���ĐM¸' ���€KHW����\Bē���āŌ����—Ž����¸dOēbÆÂ˛÷ī���đ(c-ŦHØã,Ø“Ž¤QSS‹���!:ƒAzį‡OēĪžtåīíŅÔĸmln1[,v ���xČąVo0Ökš|<{üÃÁö<MT" ō+()¯ĒŠ3›°¸QR^Õ×%���Ām"Ą@*îīoĮ™A;ŸÕ.‹‚ûųŲ7���ā1†{���¸„t���Ā%¤+����.!]���p é ���€KHW����\Bē���āŌ����—Ž����¸„t���Ā%¤+����.!]���p é ���€KHW����\Bē���ā’ĀŽ1ĨĨĨœ×�?5 ð,Û×U��ܟ——WúۓŽzē���ĀOÎ ���p é ���€KHW����\Bē���āŌ����—Ž����¸„t���Ā%¤+����.!]���p é ���€KHW����\Bē���āŌ����—Ž����¸„t���Ā%}ÃL&Smm­Á`0™LÜ���ĐįH$RĢÕAÃ’=éĘd2•––* š\Îįķí˜���āaf6›u:]iiŠ——WO–=gkkk …L&C´��€ĮŸĪ—Ëå2™Ŧļļļ§cíIW:N*•Ú1���ā"•J COGŲ“Ž, Ã0v ���x„đų|;.1Į=ƒ����\Bē���āŌ����—Ž����¸„t���Ā%¤+����.!]���p é ���€KHW����\Bē���āŌ����—Ž����¸„t���Ā%¤+����.!]���p é ���€KHW����\Bē���āŌ����—Ž����¸„t���Ā%¤+����.!]���péaOW,K›/ō§/ōøXâüĄ$ėKņ{§ĩÚz>šK(ũ“äĢŗüV›Ī§’’Öׅ LÔ7b§ŋJū/ßē���~jö°nŸpë%ūâķúQFŸÍ*åũ;K°;‡lŪCqˇ[ŊŽåņCŨØ˛ųëFš{¯žgōĪ”ņū;ßHDL7qĩXÛŋ;ĪĪŠbŽ6 P[TR˛ĩ˙Č%���ā‘ķP§Ģī/đˇ\â˙cļŅ˜ ˛ŦfŽúZüĮá?įÜÍÛ/ņeBúpēqö&ŅZ&HÍöRIįĘīí{rØŨW§cüŲH?K›öš���<rętõ¯ ÁoļÍą¨A.ėą5úÎ÷ä§. ‡˜'XüŲÍųīN2u8ĄÁLJlž$¨×Ō0wË_ĸM>"2Yčƒ$ÁŽ+üÂÆĮ}eŦiũčۋú~&ųÕãɛüø|Ū(OKj1ˆ~ČæŸ~N?g“øåpĶ˙‹4MũV”VĖ#"éŸ$œjú<M`mībÅĘfzį„0>Ÿ_Ģ%öÅŅϟ5ҌīEI…w—âÆvVXJ!ī÷§—*yf–BŨ,īO5Yŗ���ô­‡÷0IƒŽ˛+˜Š ķ`eÂģosĢ™Ėfu¨‰ahU¨yķE>ÛÉĄĢ˙wBøíyÁ‡ŅÆcOéƒÔėŧÍĸüz†ˆ~}RøˇĶ‚_M0e­×ŋnzë¸đÛ펝ßņéës‚!n–ŖOļ/3Œôd—†˜‹~Ąęvw=+ kÃĖÁÎlŅ/t?cęΊ/.æ}ˇĐņŧūÍņώ ÷_åŅŽ{—čŦ°f-Ú&ėĘÆ?ŖO|FęÎ.Ø"Ēëčr4���xĀŪcWåM K¨ē˙9žī/đ:ŗá>,=5ÜüA˛ Ĩˆ×ū@NŖžž9Į˙Ë4ãâ3ũsŽąŲČܨeÔRvC˙­ņĻÕÃĖD¤6Ÿ+ã}’"Xf&"†!™ū<ívfđH, Ų=3;JH" >¯m{g+8ąĪ0ōyāÄŅ�gķ†,ÁÉ[üųƒ,Ž’ģKhôÔYaEFŖ§•ĄæA.,}c\b?ŧ_&��ĀOČÃû2à īw  ŲB[/ že2YˆˆüŲq>–MŲüöéęJOgĸQžˇÛE|ÚŧØ@DɅ<ƒ™ĸƒîö °|sžßd …ˆˆ(ÜĮÎ3n­HDrûiĒ0!ŸWŨÂXXĒÕRēíđėŠN  f:ŗk÷׏6O 4‡y°ũqZ��āĄđđĻ+OËcčz Ķuˇ7yĨô‡xÁâīîËå*ú,Æ(ŪĶŗNGD$ĩAŖ#"ŠŲ(˛­da‰ˆ*š…š%"‘×ČwļĸŅLķ7‹Mú$Æ8PmđiŲqûá]¤fOŦŅ–&øú,˙Ũ8¯ûLj͚�� ÎIDATŪĶĒĐ^ŧY���ēéáMWJ1…y°?dķߎ4ĩ9įĩû O, 9-D´ņœåãwī4˜™˜ĸ×øË†Ü“6\¤,5ĸ{Ō’Ŗ„ˆčë†Ö—R‘ÃŊņ°ŗ3Ky+™k î`Ģj&ĮļÃģ.ĖUN6ũ5ڔSÅüũ´`Ũ>á gËH¯ŪēY���ēéáŊĒˆ^ 7i˜ŋ$Ũ“­ŽT1/篧;šZjåÅÚūÆųZĻZ6eˇ=§8Ѕ• )Šāö.[XšūŊhS6?ÔŨ"æSU3ėÂZ˙ÔRÖEÆvvSg—ˎ×؊:‘Zz{ĸôb^~=ĶzZëë. ˯gäŪžv°+ûŗ|†ŽT?Ôß&��ĀOÄÃ{늈V 5'æķ>Jœ/į-b– Ųseŧ gƒ\,6ŅöK|Ŗ™ j{FlqˆųĨƒÂ˛FōTŪmtĶĶaæRŪė gËWįgËxæÄ´n¤ųO‰B9ö˛60oz+ŲŨ+ íKr’°*xʙîŲęlEĨˆ•č_™‚_O4^Žâũ.NŨĪ’WËĢl&7ų=KtVXQŗr§čĪĶLŗ˜ĸ­—ø<†ÂŊqé��@ß{¨ĶũkŽqj?˲øoš,¨bߎ4Ŋ8Úd}"ÃĻ‹‚H?‹›ŧí¨yÁæŸnģ,x=âžį#üeš‘ĮĐ¯O 4ÔͲw…ĄŸŠ%ĸ§Åė¯Oʛw;w åS:~TúĪÆ˜ÖíMûNŧeIŲĢŊÎVüĪ<ãģq‚MŲ’‘ž–˙Ė7”62OíÍúA|æ}ë%:+lĸŋå?ķ?-x?A āŅ`ËļĨ†Î8-��Đ÷˜õë×oذĄGcōķķ=<<zŠ ���€‡Gyyy@@@†āJ����.!]���p é ���€KHW����\Bē���āŌ����—Ž����¸„t���Ā%¤+����.!]���p é ���€KHW����\Bē���āŌ����—Ž����¸„t���Ā%¤+����.!]���p é ���€KHW����\Bē���āŌ����—ėIW<eYÎK���x¨˜Íf@ĐĶQö¤+‰DŌÜÜlĮ@���€GˆV̉D=eOēRĢÕ---ÍÍÍfŗŲŽá����9ŗŲÜÜÜÜŌŌĸVĢ{:ļĮģˆH xyyÕÖÖÖÕՙL&;f����x˜ ‘HäååeĮ™A{Ō•uI777ûÆ���<ÆpĪ ����—Ž����¸„t���Ā%¤+����.!]���p é ���€KHW����\Bē���āŌ����—Ž����¸„t���Ā%¤+����.!]���p é ���€KHW����\Ņ­ĸ˛ž.�[R‰XĢĶ÷u��Ž€ˆ}=ûē ���€ĮÎ ���p é ���€KHW����\Bē���āŌ����—Ž����¸„t���Ā%¤+����.!]���p é ���€KHW����\Bē���āŌ����—Ž����¸„t���Ā%A_����=p#ŋđë[›š[,K_×Ōc<O!—=ûԊ �ŋ.ē%į͞­TÕL&ö•Ö–€!W9m_A‘=‹cW���Œų…Ÿ˙ëšÆĻG1Z‘ÅbŅh?˙×˙nävÖ'9ŸĸūGeM}­ˆČÄRY#Eũ’ķ{<é ��ā‘ņÕ÷[ûē„aˆe쨑ĨÛ¨OcU+ ą,-ŨÖãqHW���ŒÆĻĻž. ĶÜŌŌŲÆŠÆYĘũ0TŨķé ���´.Îl>,Žî°ã%Ō����—Ž����¸„t���Ā%¤+����.!]���p é ���€KHW����\Âī ��<﯌>~ė(o/>Ÿ_W×pūâå¸ÄÔ­Öēõƒ÷ūߊ¤´Ŗ'úļČƒ!z*ŒÖĻa$Pą†åŌ‡‰Tzᑤ{WS€ŠÂūqwȲĄ´u9ŊzˆūqēˇĒBē��x<­Yąxôˆaį˛/%íĘ4™L~>QÂG úųŋŋjl|žųÎchË2Z:”ļdĶ—éÔ¨§aôĘ8Z9ŒĸŋĻėŠ†Lô§ī—ЇIŊ­é ��āą1zĘ‘ÃˇėڟšžemÉžœ“~æÜ›¯ŧ0wÆÔ-ģö÷myœøY8- ĨÕÛisöí–C×čŋY”˛žļ¯¤!'ķŊYėJûž¤­écŊ[Ō��ĀchRdDAQą-ZYUTV˙ũ˯+ĒĒÛ÷đųsgF U*䚯όŗ‹ŗū^MP ˙ŧ™Ņ^žî<†WRVž˙Čņˇ ˆˆĮã͜6idX¨ZåT_ß—”šœ–ų`öÎęĩqtüúŨheUŨBoÅŌž'iv0¸zˇŨCAGžĻĶEôܞ^/ é ��āq#‘ˆŊ==ŽÅ%ļßT\ZÖáe‹æ 2x۞…Å%~žËÍ ģĊ„Ÿy2ë|ö–]ûbĸ&„˙ėš5ŋũĶĮZ­î‰91"FoÛ}āf~á A‹Ė6›Ėi™g{yįnķTPgúOGqîøu"ĸIwĶ•BD‡ÖPE-ŨBĻNá3HW���GĨ’a˜ęÚēnö—ɤáŖÂö<zöÂ%"ĒŽŠķps2qÜžÃĮU*G‰DœyöBEe5íÜwøė…K&“Y"O?öx\bƙķD”\Sëëã5}JÔKWŪŽDDųõlԚ¨ŧ‰ŧnŋđhĮJéEŋ<BÍÆQžČ���đ¸a‰%"ŗŲÜÍū>ž</ŋ°ČÖRX\"‰\]œ+Ģj*ĒĒ׎Z:}ĘDoO‹ÅrũfžŅhôöōđų9׎ۆäŨ¸åęĸ‰DÜîKg,""a'A†ĮåÎEWCÜČEF_ĻĶ_gP¸Īƒ¨ ĮŽ���7 šF–eŨ\œģŲ_"‘N§ˇĩčô"’ˆE,Ë~ū¯˙EOž8~ėčųŗĻ×Õ7ˆ=‘yö‚uČĢ/>KėíÃ0 9(Õ5ĩÜîN‡ŠˆˆUl’ ČMN… ˇßæ×QÔÉ`Ļ!î´s%ü'UĩônmHW���ŊŪPTR>zÄŅ“ Ļ{`……†˜LæK9š­ĩ:=ŨÉXVą˜ˆ´:55ˇė=ttīĄŖnŽS'MXŗbqyE•5Š}ŋegiŲ=O>¨¯o ĸĒ….WԊaô—ē÷Ö@ŠîODtōÆíˇ zԚˆˆVlĨķ¯ĐÖ4ã›ļˇr g��C§’RUNŽ3§OiŨčáîļrɂĐ!ƒÚt.)-ˇX,ũül-ūžZŽĒēÖYårģyeÕÖ]û-‹§‡[IišÉdR(äUÕÖŋæ––ÆæfSˇOGūxKĄĄîôRø=ÎRúx&+ģ›ŽlƚhÕ6šHŅģ…áØ��Āc(ë\ö€ Ā˜ŠQžŪžgÎ_Ô ~Ū^QãÃË++÷ŒmĶšEĢMË<;cjTUumqiŲ€ Ā¨ á'ã“-‹Jåôܚûģ”“Ë˛4fäp–eoéôú”ôŦ93Ļ67ˇĢUN‹įĪŽoĐüû›Ø>~}†&Ō?įŅDڛCMzęN¯Œ#†hÁmhYŧIīĮŅ{Ķ(Ŋ˜v]î­ÂŽ���O[vîËÍģ1qÜØ% fķyŧęÚēŖq )éFc7ÎíØ{H§×/_4OА×Õ7Ј?~*‰ˆŽßĖ˙aûžiQæÄLŗ˜-e•˙ũnKUu í>k}.ƒƒRĄilēxåę#'ä˛DOk´~ mX@5Đļ‹ôABWWVũ1ž&øĶ7‹čr]íāÉ_`Ö¯_ŋaÆ^™���8õƝ~××%pæ˙>zŋÃvæˇ¸ûc˙Ôŗū¸î ���€KHW����\Bē���āŌ����—Ž����¸„t���Ā%¤+����.!]���p é ���€KHW���đ 1 ĶéĻYG7zž•Ž���J…‚Øžø‘² šŧŗîJęø˜ûK.VÚ)¤+��€GÆē5+¨ķŖ> †YˇfEgw,ˆv‘ahĮōBē��xdøŊūŗį” EgÖf Ã(Š×ö\P€_g}"(ņ9ōPÚsJŽCy()ņ9Š čņXfũúõ6lāž(���€Ÿ$ģ���āŌ����—Ž����¸„t���Ā%¤+����.!]���p é ���€KHW����\Bē���āŌ����—Ž����¸Ä#"‹ÅŌ×e����<, O(^šrĨ¯+���x\šr…'“É8žl_���đhcYvßž}ü3fTTTdgg;;;+•J@Đׅ���<bt:Ũĩk×ūûß˙VTT0īŊ÷566677ã,����ûđų|š\ŽP(nŠR*•JĨ˛ok���x ≠����\ú˙0=ņ°<#˙ˆ����IENDŽB`‚����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/scheme-email.png����������������������������������������������������0000664�0000000�0000000�00000200725�14156463140�0021717�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��/��™���Ö0˜���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨwxÕÆņī–$¤‘:i@čE)*Ō̈Š€HÁŠ;ˆ¯PĒ•*X@$ôHŌ{ßŨûG4!„VßĪķÜû°SÎųÍŦ˛¯3gæ6ū°Ų†ˆˆˆˆ0oßú#iii¤ĻĻ’]Ūõˆˆˆˆäãā‛›ÎÎÎ�˜ĶŌŌ0Œ;–ƍ—sy""""ųíÛˇ%K–žžŽŗŗ3Ɣ”†Žā""""×Ĩ&Mš0|øpRRR�0šL&ęÖ­[Îe‰ˆˆˆŦ^Ŋz˜L&�Œ™™™ †r.IDDD¤`ƒŒŒ �Œå\‹ˆˆˆH‘(ŧˆˆˆˆ]Qxģĸđ""""vEáEDDDėŠÂ‹ˆˆˆØ…ą+ /"""bW^DDDÄŽ“““Ëģ‘ĢJNN&!!Ŗ‹‹Ky×""""rU...¸ššéļ‘ˆˆˆØ—2 /­Úļcũ†—]wâD8u””T–%ËC“åĨW^/ÔļŊúÜł…‹Kš"‘ëOrr2ŋūúë%Ë#""8vėXąÛŊĻ𒒚JÃĻ-éØĨ6›íZš‘™å˗ķũ÷ßŗiĶĻŧe,Y˛„e˖QÜqˇ×^Vŗ†F œœĖÎßv]KS"""ō/sûíˇãææÆÖ­[Ų´iS^pÉÎÎæļÛnÃŨŨŊXí^SxYēlˇ÷îE¯ž=XļüË|벲ŗyúšh~c[njבĨËVä[áB÷ Eãæ7ĐŊWB÷îŊb_ËŋüŠN]{RŋQ3ÚučÂg ¸í+¯Mã‰)ĪđĖķSéÚŗ7mniĪú ™ķé<zßq7­nē•OįÍĪÛūĖ™ŗŒķ�-[ßDÛvxōŠgINIÉ[ŋ`ábniߙĻ-[ķ‹/cŗZķÖ=ķüT&?úDžū›ļhÅĻÍ[.[Ûŧų čÜŊ ›ļ¤[ĪÛųå×W<n{åããÈ#ōĖ… ÉÎÎĻOŸ>4oŪŧØí;ŧ9r”ÃGŽr[ĪžÜugž[ģ.ß埅‹–°eËVV}ĩŒ6|Īž}ûōgyö…1 ü˛ũG~öé%áįbĮOœāɧžå…įžæĐžŨŧųÆëŧúúė?pđ˛Û›Í|ˇvũúŪņī×Đ¯ī]<>å, kV}ÉÛ3Ļ3ã͡INIÁjĩ2âūąøTöaû–M|ģúkÂ#"xņåW8xč0/žō¯Ŋō"ŋīø‰!!ü°åĮbŗÕkžåÏfķîÛo˛?ô7ü0#Fá|ddąÚšŪUŽ\™N:`ąXĻE‹×ÔfąÃËË–ĶĄũ­TĒäIķfMŠęįĮę5ßæ­_ˇ~wŨŲ‡Z5kRĄB4‘Ŧėl�˛˛˛ØŧåGî57WW|}}vßāû đ÷gįĪ[ik;ŒF#­[ŨHíÚĩ8xđĐeˇ7 88ˆæÍšĐŧy3’““:d�-[4#+;›sįÎķûîPÂÃ#xęÉĮpvvÆĢR%&ŒËęož%##ƒuë7ĐŦinmw fŗ™{ôŖzõjÅ;gK—sīĀ4jØ�ŖŅH÷n]hÖŦ)ß\tŪDDDūMNž<ÉÚĩk1 ¸¸¸pėØ1~øá‡kjĶl0ŠŧSff&+W¯áõW_Ę[vם}Xžâ+ß;€ķ‘‘ų~ä+Wތ‡‡�ŅŅ1X­VĒWû{}```ũFV|ĩ’/ŋ^I|\<FŖ‘ø„˛˛˛ ܧ˛ˇw۟psuå¯wÚ888äĮ™3gņņųģ6€Ā€�rrr8yá’ã� (øä\ÁéĶg8pā _,]žˇ,)9™ +숈ˆŊúã?Xŧx1ŲŲŲÜyįÔŦY“yķæņã?bŗŲčÜšs‘Ú3  FĖÅ)æûuëIJJâ‰)O3åŠgČąXHOOįČŅcÔ¯W—ŦŦŦKž@˛Z,�yĄãâõVĢĨĀū–|ą”Ys>eķŽĻô¸­Īküg(+(¤]ļN[î˜ŖŅđįúüûX,VŽä¯ũ˙ÉŠ‚ ŧŸūw÷͡ŧB§+ļ'""b6nܘ\š5kˆ#˜;w.ģvíĸUĢVÅ´[Ŧđ˛tŲ úõŊ‹‰ŽĪˇüŅ'ϰlų—<˙ėSTņõåėŲsyëΞ;—7ÖĮ×'oYāŸW1Ž)øyīßw‡rËÍ7å—„„DNūqĒ8Ĩ_ĸNÚÄÆÆ‘˜˜˜wõ%,ė8TõķŖŠ¯/ģ~͡ĪŅcĮ¨QŊ:{U'>>>o]RRŠŠi—íĢv­šœ=wŸĘyËbbc‹=ÚZDDäz6hĐ ūøãęׯŸˇĖÛۛ#F““SvOũqę;vūÆ}CQŖFõ|˙ģ§?VŽ^MVv6ÚßĘ×ĢVs,ė8qņņĖxķ*T¨�€›Ģ+7´lÁœOįGxx‹–|Q`ŸÕĢUãØą0RRS‰ŧpgž{Ē~~DEGë /ÖęÆ dڌˇČĘĘ"::†>šÅíŊ{áččHû[Ûąwß>6lÜDJj*s>™KB|BŪūuęÔf˙ūƒ¤Ĩ喏fÍÁŅŅņ˛}Ũ{Ī�VŽú†mÛ"''‡;ŖsמėšĘ“V"""öČŲŲ9_pųKåʕņķķ+vģE/˖Iũzuiܨá%ëzöčFvv66ld¸ąÜrķMô8˜žŊī m›ÖøUŠBΟˇŽŪyķ ˛ŗŗiץ c'<ĘŅ#ōÖ_lØĐ!TŽėM››Û3xčú÷ëËčQÃųtŪ|æ~ļ ¨‡Á`ā“YqáB­onĪ}ûĶ´i^žú<�7ßԖ)?Ęķ/žLÛ[ÚsęôzöčŽÅ’ĀŨwŨIũzuéÔ­'ˇŨŅ—€�ĒúųqÉŊ& KįN<2i"O=ķ<šŨĀŗ/ŧČ Ī=CŗĻM¯éDDDūK ŖGļ͙3§ŧëšĸûīŋŸĒUĢoĖ‹ˆˆˆHYûåˇŨ¸ē…iVią/ /"""bW^DDDÄŽ(ŧˆˆˆˆ]Qxģĸđ""""vEáEDDDėŠÂ‹ˆˆˆØPˉEDDDĘZõúM¨bˆĶ•ą{ö'Æ#㙜ō.EDDD¤˛“9˛?ŖŸ9ĢŧKšĒJ€% ŗ9'Ēŧkš*¯ÚĩŠ\##é)å]‹ˆˆˆČU‰5â]É1÷iŖ¨Ø„ōŽGDDDäŠŦf3Îđõö,īzDDDDŽČĮˇ"€s•ōŽEDDDäĒjģYq˜ÁšŧkšĒô¸3ėKÕKęDDDÄ>ė ‹$6Ks‰ˆˆˆQxģĸđ""""vEáEDDDėŠÂ‹ˆˆˆØ…ą+ /"""bWĖEŨ!Įb!ât$iiéXmļŌ¨ŠD¤Ļ¤˛iËbbãĘģģUŲۋ‰cFPŠ’Gy—"""BÛĩjĩĸ‡—ˆĶ‘XŦVĒøzc4JŖļņÎ_“œœŒá:ŽņzËģŗæ2uĘäō.EDD$O‘oĨĨĨãYŅíē.�))) .×Č`0_ŪeˆˆˆäSäđbĩŲŽûā""""˙^°+"""vEáEDDDėŠŅv?1$"""ō›Í†ÍfՕą/ /"""bW^DDDÄŽų%u…q""‚9Ÿ|@bR"ޏ8;đÂsSpŽā\ŨKŗÆ ¸īžžÄ'&å[žeÛ/l˙õˇkjģkĮvX-V6mũ‰w§Måá)S¯ŠŊ‹™ÍfĻOÂŖĪžRbmŠˆˆØƒR /ūūL{õE�æÎ_H­š5éŌŠCitU"ÂNœäãy‹JŧŨ ›ˇ•x›"""˙uĨ^ ’–žÆ”§Ÿgúk/ãėėŒÕjãą'ŸfʏđüÔWéŌĨ#ąąąÄÄÆ2p@?ũũ‰ŠŠfŅįKČČČāļžŨiÚ¤q™ÕüƋOŗéĮíTŠâƒŸ¯[ļ˙J`ZT¯V• [ļąw˙!ũksG¯ndggãččČ×kÖ~ōŊēvÄbĩ˛nĶ—mÛŅŅ‘!îÂĢ’6|ŗv#ĮއĶ(¤];ļ#33 “ÉČ_}CtL,ujŅīŽ^Ä'$rö\d^;fŗ™}o§ĸģ;&ŗ‰ca'X÷ÃÖ|}ĩžĄ9CܕoŲŽ]Ą,Zöuɟ4‘RTĻc^\œ]hÖŦ);wîāČącTĢV_223hÔ „ąŖGrGīÛøbŲ �æÍ_ČíŊzđČÃōЄX°h ™YYeVŗƒƒ™ÃĮN°āķ/šC m>˙r5_¯YË­m[PŅŨe+×đŪėĪXõŨznīŅĨPmwíØŽØ¸xŪ|o6‹—¯¤]ÛVTprbPŋ;ødá|øé~Ųš›~}zpwŸ^ŦúnsæÎų Ņyítép Ņ1ą|øéŪ›5õëR§V|}ũ3¨(¸ˆˆˆŊ*Ķ+/�ÛßĘĸĪ—Ōž};vėÜEû[oÉ[@íÚĩ8sæ,VĢŖaaŦøzeŪ<Efâ¨RŎÄj ô¯Í”Iãķ-›= qņ �œ>{€””Tĸbbķūėâ’;v'.>ž^];āčč€ĢĢKĄúõ¯]“M[ļy!ŠO~AZ5ˆOL"99€c'ÂéwgnxŠVĩ áä.?ž×NŨ@\\œŠ_7€ œđņöæäŠ3ųúÛą+€ā€: .""bˇĖ)))eÚĄÚXs,œ=wŽãĮOpß yëŦV+&“ ›Õ†Á`Āh4ā`v`âƒãqu)\ (Ž\qĖËÅ/ōŗY˙ūŗÜ@5rČ=Ė[ŧŒ“§ÎPŖzU†ŧģPũZ­ÖBMųW˙oi4ūũ);;‡īÖ˙ĀžƒGŽÚ֎]Ąy!FDDФ¤¤˜˜ˆŅÃÃŖĖ;īĐĄŸ-XLĶ&0›MyË9 ĀŅcĮ¨U3÷ļGPP ;ËŊ͔˜”Ä‚EKĘŧŪĢŠčîFLlîėË­[6Ãl2]e\áĐ(¤�•<=˜4nį/DáQŅŠî�Ô âĶ�DFEP§�!õ‚ķÚ9qōZ4ũ{ĐŨ}záîîví&""rņđđĀÛÛģėo´iu#‹–,eÔđĄyËL&a'NđÖ­Ä'$0üžÁ� 2ˆĪ.fĮÎ]deeŅŗ{ׯ'8°Ī>>1߲đųŠU…ÚíÆ-L¸(ÉÉŠlŪū3!õ‚éÕ­\eę…ļũÂāūwōØCc0 |ŗv#™™Y,Yž’ƒ•• Ā_­āĢÕßs÷ŊHLLæÄÉ?ōšßŧíÜ՛ÉãGc48žwÛIDDä߯0fĖÛŦYŗ ŊCčÁ0Ēûų\S§Ą{÷ąmûOLœ0.oŲČ1ã˜;ûŖkj÷b/OģÄÚú¯{ī—ĘģƎKĩjÕĘūĘË{Î"!1‘ Œ.ëŽEDDä_ ĖÃËCãĮ^vyI^u‘/Ím$"""vEáEDDDėŠÂ‹ˆˆˆØ…ą+E/ƒëUŪ_""""RZŠ^\]œIHJšîŒ››Ū0[ŧŊ*•w """ųųQi˙š~DœŽ$2*6ߜ?כÛēwcãæÍÄÆÅ—w)vËÛĢYŪeˆˆˆäSäđb6™ŽSŊ4j)qˇ´nVŪ%ˆˆˆH Ķ€]ą+ÆäÔ´ōŽADDD¤ĐŒîŽ.å]ƒˆˆˆHĄéļ‘ˆˆˆØ…ą+ /"""bW^DDDÄŽ(ŧˆˆˆˆ]Qxģĸđ""""vEáEDDDėŠÂ‹ˆˆˆØ•"O˘cąq:’´´tŦ×ņŦŌ"""rũkŪ0¸Čû9ŧDœŽÄbĩRÅ×ŖÁPäËÛŲČčb()YĄÊĩ_‘oĨĨĨãYŅÍ.ƒ‹ˆˆˆØŋ"‡ĢÍĻā""""åFvEDDÄŽcŗĘģ‘Â3nŪyĒŧk)4cvÔŲōŽADDD¤ĐŒXŦå]ƒˆˆˆHĄiĀŽˆˆˆØ•"ŋ¤Ž°.ūœ={÷ņęKSŠPÁ €ĐŊûŲšķ7ÆŪ?˛´ē-1¯žõ>™™™88ü}ŠŒFĪ<úā5ĩ›“Ô^į­WŸãģõ?`4™čŅšũĩ–+""ōŸQjáĀŲŅoÖ|G˙~w•f7ĨfôЁÔĒQŊŧË‘‹˜ą&•ZãŨētæ›oŋãĻ›ÛPŊjÕ|ëŽ;β/ŋÂŅÁĖĖ,čKŨā`V­ū–čØL&3§OŸĻEķf$%'“˜HRr2NšˆŲlâûuëŲģī�fŗw77†Ũ78ī Oi›ũŲš4 ĄÍ Í ŨwŸvėbÂčĄŧ?g>ˇ´š‘æMæÛ>üä)–¯ú/OĒWõˡîBT4sæN\|5ĢWåŪ~w`ĐK�EDD dôwO+ĩÆĐ¯/‹}~Éē„Äîģw O>6™ũîâĢ•Ģ0™M$%%3bč`ƌÁНVŌŖ[gƍ 6'Â#8r4Œ3åņGxlōDŧŊŊØøÃæR;ŽēˇßlÜŧ˜Ø8žÛ°™!îÂ`00üŪ~4 Š{Éö+V}Į=ģq˙°AøUņ͡îBT #‡ āņ‰c9uæĮND”ÕaˆˆˆØ%s›^w—j7ļlÁ[ˇķˎT¨āœˇŧreoVŽū€Ėėl’“SķÖÕŽ]�÷Šî¸ģšáUÉ €Š+’š–ĘŠS§‰ŒŒdڌˇr÷ĪĖ"ĀŋN‰×ūŲ’8:8ä}ŠÄŊēáîæJī]xãĶ˙ŽÛđô¨˜[¯ģÛ%mØl6ÎE^ Đŋ�uƒō­¯€ÉdĀŋNMÎG^ Ū?ļ‘ŋ•ę˜—ŋ š÷ۜų?ú÷Ŋ3oŲGĪaܘŅpōä)æĖũ,oŅø÷CPã?ˆ˛Ųppt E‹æ 8 Të>¨_c^“’puq!6>áĒíØlļŋ˙lÍ˙húÅĮŠ @ˇŒDDDޤL•öķĢB›Ö­Xģn#ûcž˜˜DeŸĘ�üôë¯äXr Ũ^Ũ  öîÛOFF&�ØÂáŖĮJžđDĮÄō͝ģxbâėŨˆ3gΐœœBVvvžm ~ž>„ŸĖ}“ņĄŖų§˙;ÍfÃfŗqōÔjTË?&FDDDō+“+/�ˇß֓_wėď* úôžˇfūŠîéÖĨû÷äëUßāpŅmš‚Ōą};Ļŋų6NNޏ¸¸rķM­KŧæO|‘īQi€ƒ°låîžŖÎθ§oo.ũŠĮ'ŽåŗĪW\vĀîŨ}z˛bÕwxzT$Āŋ6ü9 ×jŗQÅׇy‹—Ÿ@Z5 ¨SâĮ!""ōob3fŒmÖŦY…Ū!ô`Õũ|Ją¤Ōu62šæ ƒËģ ‘˙ŧЃaEúM;v,ÕĒUĶvEDDÄž(ŧˆˆˆˆ]Qxģĸđ""""vEáEDDDėŠÂ‹ˆˆˆØ•"‡ƒÁ€õĸ7Ɗˆˆˆ”Ĩ"‡Wg’R`DDD¤\ų ģū5ũˆ8IdTlž9{ėIčÁ°Ģo$"""×Ĩ"‡ŗÉDpËOV("""RÚ4`WDDDėŠÂ‹ˆˆˆØ…ą+ /"""bW^DDDÄŽ(ŧˆˆˆˆ]Qxģĸđ""""vEáEDDDėŠÂ‹ˆˆˆØ…ą+ /"""bW^DDDÄŽyVé‹…ˆĶ‘¤ĨĨcĩŲJŖ&ų3xTtŖFULÆĸ_G)rx‰8‰ÅjĨН7FƒĄČJÁÎFFĐŧap9W"""Rz,V+gÎGsæ|4ĩĢW)ōūEŽ;iiéxVtSp‘b1Ô¨ęCbRJąö/rxąÚl ."""rMLF#ĢĩXûjĀŽˆˆˆØ…ą+ /"""bW^DDDÄŽ(ŧˆˆˆˆ])ō{^ káâĪŲĩ;7W7�ÜÜ\čÖĨ3-[4ā˙ŊOŸÛzP¤vŋ^ũ &Ŗ‰>Ŋ{]S}Ī<˙~~UxhüØŧe_,[ŸŸnŊåšÚ.m)Ši<õâ4|}ŧķ-¨]‹Áî*r{¯žõ>99ؘÍ�øVöĸWˇNT¯ęwÅũΜ;Ųd¯ŠoÛ|ˇūŒ&=:ˇ/r]"""—Sjá Gˇ.ôėŪ €đˆ“|øņlŦ67ļlÁ䉖fׅr!*Š}ûФqŖō.ĨČxîņ‡KŦŊƒPĢFu�v…îãŊYŸņøÄąx{U*pŸ=ûRŊZÕ+†‘’VĒáåbūu|ī=Ŧ\Ŋ†[ļ`ڌˇč{güĒø1ûĶyX­V222iŲ˛ˇõčΗ+WƒŲl"5%OOO†”¯ÍM›䧟ÅŅŅƒÁĀŖGræÜ9VŽ^Ã3SāÜųķĖ|īCĻŋú†ŧŸf`˙ģY´d)!õëáāāpÕļ=<=˜>ãmüINI%<<œŨģvü11ąÔĒY“{ú÷%++›ų‹“˜˜„%'‡!!ÜŪģgŠžßŋ¤gd0ã3vÄĒøTfŲĘ5x¸ģĶŊs{&>ų¯=˙$nŽ.Wlã†æM8uæ,?ūô+}oīÉļŸw˛ã÷=8:˜1 ԟ˜Ø8~ŨJE÷ã8:8PŗF5|ņ%VĢ•ĖĖ,ÚÜМv7ĩāBT4sæN\|5ĢWåŪ~w`ŗŲXúÕ7DFEcŗŲ¨âëà~w�°ėë5œŋ@%O¸ ›ÍÆ_Ž&99…‹…zAt×Õ‘˙¤2ķRŋgΞ˷,4t~U|yâŅI<3å1L6› ŖŅHbb"÷Τ‰8{îGŽ͡oZz:N~ˆ)?BŊēÁü°u+ BꓔœLdä�vėÜÅ­ˇÜ|Ip¨YŊ:†đŨ÷ë/YwšļLf3ŽŽŽŒ6„.:˛æÛĩ 2˜I'°yËVŦV߯]Ÿo›<‘'›Ėž8q"ŧ¤Nã9W¨ĀĀž}XúåjNž:ÙŗįéÚą�S§LÆÕÅšPíÔ āÜųÜs˜ž‘Á„ŅC™øĀH‚ę°ũ—ÔŠE mēvlGÐēÄ'$pK›™8vÉ7k7bąX�¸ÃČ!x|âXN9Įą¤ĻĨSÅׇÉãGķȄû‰ãxøIbbã8ņ“ƍbŌ¸Q„Ô "%%•[ļã[Ų›ņŖ‡ōИá:ÆÉS§Kį$ŠˆČu­ĖŽŧ�¤Ĩ§]ōvېú|ģn=ŗ?™GãÆ čĐž]^ĐĖÛŽv­šœ9s6ßžŪ^•øhÖ'˜Ífbã⨈Á` Ã­ˇ°ũįŸé×÷.víŪÓ'XSßģúđüÔWhÛĻÕUÛūKÚĩ�¨XŅš5Ēc40Í8:9’‘™ÎáŖGIIMåāáC�¤§§s!:ēČã{Ž$;'‡iī|˜oY÷ÎíiŪ¤!uƒØ{ā0Ÿ,øœ‡‰ņĪI¯ŧ*yēũôôŒŧũ*yz0wņRĖfââ đ¯uÉö^•*ņãO;øeįī˜L&˛ŗŗIĪČ ^p�&“ �˙:59yā€:¤ĨĨņūœų8:8KJjujáęâĖŸĖ§QH}š5n€GEwއ“š–Α°�dddGZ5‹~ōDDÄŽ•ix9zô8uükį[æëëÃk/MåXXŋīeÕęīxyęŗ�Ø.zm°ÕjÅvQđ‰ŠŽaéō¯xíĨpuuaíēDĮäNlxËÍ7ņōkĶšąeKĒøúāáéQ`M.Î.ÜuGą”ęÕĒ]ĩm�ÃE3`˙1ĻÍŽŽŽôíŌ‰͛õšƒŲĖ”Éã \Ÿ˜œŒŖŖ#‰IÉøTö.pģ‚„…Ÿ¤VÍęÄÆÅŗōÛu<ķØC¸8;ķÃ֟‰‰‹ģdû•ߎŖjßŧĢ<?÷jŪē|įČ``įîŊœ>{ž Ŗ‡b0˜5o1�&“‰IãFqæėy9ƛīÍbܨûpppāļîmiŌ0¤ČĮ"""˙.evÛ(âä,˙r%ũûŪ™oųoŋī&âäI„ÔgČ XŦ’Sr'j:|4 ĢՆÕj%ėD8ĩjÖČÛ/))W\]]ČČČd×îPrūŧMáîæF`€?‹>_Z¨'‡nžŠ éé>|ôĒmFpP ;~û�›ÍÆâĪ—’˜Xčũ¯ÕoĄ{1MŒuKŋú†ŒĖÜ+ qņ ØlļĢîŋ+t‡ŽŖĶ­7‘”œ‚ŗs\œÉĘĘbĪūƒXrrĪ…Á` ;;€¤ääŧÁŊģöė#ĮbÉģmv"›Í†ÍfãäŠ3Ô¨æGRR2^^ž ĸcb9yę4‹…ŗį#Ųą+”ÕĢŌŊs{ęrö|$ūĩų}Ī ÷œŽXõIÉśĐKDDė[Š^yYģ~#Ûú‹Å‚Ģ›+ŖGĨnppžmĒUĢĘg a4°Zm´mŨ ¯Jš?‚•ŊŊųxÎ'ÄÆÆ@ũēÁyã^üũņõŠĖkĶßÄÕՕ>Ŋ{2÷ŗE„îŨOķĻiwķM|:o>6,T­Cäų—^ģjÛ…Ņ­K'.ų‚W§ÍĀjĩR˙ŠWŠ#+;›—gŧ›o™ŲdfüčĄ|ˇ~3L¸w7WZßМ¯VĪ ūw2uÚ;؝ˇxfŗK~žžL7ggj×ŦŽˇ73?úggztîĀ’+9pø(õ‚XũũŦV+ÛŨÄ×kÖņËoŋͰ~]Z4mÄüĪWP§U|}˜ˇx9ņ ÔŠUƒ €:x{Uböü%ŧ?û3ŧŊŊ¸­[gž]˙÷āÃGųiÍĨŅ�� �IDATĮ.Ė&ŽŽ.4mËž^Ã;~‚ÕjĨ^P�ŨŨJôœŠˆˆ}0Ü;xˆmÉĸ……Ū!ô`Õũ|Ją¤\×ú>—Ī—­ĀËˋî]:•peĨįldî­Šæ ƒ¯˛Ĩˆˆˆũ =V¤ßŧącĮR­Z5ŒîWylÖŪÄ'$đŌĢĶIˆO KG=J+""ōoSĻv‹âŽ>ˇkŋJžž<˙Ė“%\ˆˆˆ\/4ˇ‘ˆˆˆØ…ą+ /"""bW^DDDÄŽ9ŧ Ŧ…xŅ™ˆˆˆHA,Vë%oŠ/Ŧ"īåęâLBRŠŒˆˆˆ‹ÅjåĖųh<+īeŖE~TÚŋϧ#‰ŒŠ-ÔĢæĨčB†•w """ĨÆh4âYŅU‹÷ŌÛ"‡ŗÉDpęÅęLDDDäZiĀŽˆˆˆØ…ą+ /"""bW^DDDÄŽ(ŧˆˆˆˆ]Qxģĸđ""""vEáEDDDėŠÂ‹ˆˆˆØ…ą+Ež Įb!ât$ii障QDDDŠĖd4âņįÜFĻbĖ,]äđq:‹ÕJ_oŒC‘;”ëÛŲČhš7 .ī2DDä:rüD8É))Ô ÂÉÉéŠÛfffr4, w77‚/ģÍ_ŗJŸ9MíęUŠ\O‘ãNZZ:žŨ\DDDū#"/\(Tpprrĸnp0‘ĸ ÜÆd4RŖĒ‰I)ÅǧČáÅjŗ)¸ˆˆˆü‡dff*¸üĨ‚“™™™WÜÆd4bąZ‹U늈ˆˆ]Qx‘bųđŖųđŖËŧß"Øy{æL–¯ø€ŒĖ ™4ŠĖúVx‘"yįŨwYžâKæĪ› °#1Lš8ąLú×m#)’ˆˆ“ĖŸ7—ēÁÁÔ fūŧš„‡G”Y˙ĨvåeáâĪųm×¸æ[ūĀũŖ¨S§V‘ÚZķŨZL&#=ģw+ô>ģC÷аANNN<8éQ۟ųV‘ú,Čwkד‘‘Aß;û”H{×ęĨW§sßāø×Š}É瘨X>šũ)<ü Ž..%Úo||oĖ|Ÿ„„Dœœyō‘‰TõĢB\|<3f~@Jr F“‘ cFQ78‹ÅÂûĘąã'°Y­ÜŲį6ēuîPĸ5‰ˆHŲøßĖwō}Ž|ɲŌTǎzöčV¤ĀQŪŊzyŸī×m ((÷™ô’ .§ĪžeĮoģxᙧJ¤ŊŌVŲۛÎÛŗtŲ FZĸm0{.7ˇiEīžŨØŧu;Ûū•ū}īā“y‹ō–:|”7ßũ€Ųīŋ͆M[HKOãũˇ§‘”œÂÏ=EķĻņŠė]ĸu‰ˆHÉsúķŅįÂ>.QÄGĢ‹Ē\ÆŧŦ[ŋ‘SgÎp˙ČáÄÄÆōÆ[3yîé'ųßQ§vm2Ō3ˆŽ‰ĻMĢVthߎ¯WƒÉhĸOī^L˜ø­[Ũ@ŽÅÂđû3ŅΝ;ÍÕĒVaøĐûØ˛u+'OūÁėOæ2|č`žxęYæÎūˆôŒt>[°„””˛˛ŗhܰ!}z÷âČŅ0V­ų–j~UHKK':&–‰ bE÷|u¯]ģŨ:c4HJJfö§ķ°Z­dddŌ˛e3nëŅ¨¨h}ž€ŒŒ nëŲĻM“‘‘Á§ķč×÷„Ô'tī>žû~NNNX,† „Ÿ_ĻĪx›ĀĀ�’SR9wî7ŪВn]:‘˜ČGs>ÅŅŅOOO,–€ëiĶĒ_~ĩ’Ä„D<<=JäûËĘĘfßūƒ<ųhîŊ͎ˇŪ’ˇnWč&Ž@ƒzX­VÎG^āˇŨĄôčÚƒÁ€GEwZ4kÂî={éŪĨS‰Ô$""ĨĮ¯Š/GèL…Ģ„’ŒĖLŽ ïŠoŠÕS.áĨ[×ÎLķ>Âú ›¸÷ž¸ģša2quqa𤧧ķøSĪŌēõ ųöÍÎÉĻEķf4j؀Ĥ$ĒVņcÄĐ!�L›ņ6GÃÂčÔĄ=+W˘Ņ#Ščīēõ›đđpgܘQääXxáÅWhĐ ŗ‰?ūøƒ‡ÆÁÅŲ…Ī,f×îŨtęĐ>_ßûdā=ũ� Ũƒ__† ˆÅbaÃÆÍØl6æÍ_Hß;ûDrJ S_z•×^y‘o׎ÃĮ§2ƍáĖŲŗŦ\Ŋ˙:ĩ™7/?˙ žü˛cK–.ᑇÄd6ã`v`ÄĐÁ$&$ōėK¯Đ­K'ž_ŋ Ā�úõŊ“ظ8ž|úš+Öc4¨[7˜ƒGŽpS›Ö%ōũEEGãáQ‘å_Ž"tī~*VtgĖČĄxzTÄjĩRĄÂß˙`{{U"6.ž˜Ø8ŧŧ*å-÷úsšˆˆ\˙üũ ˆ tĪŪĢž|ÎÉÉ ŋ*žøû—Z=Ĩ^Ö­˙ŸŨ™÷ŲÍ͍'„Á``Ԉaŧ>ũ-5 ĄyĶÆyÛÔ ĀŲŲOĸĸbōĩiŗÚ¨W7wîw77RŌR™ņöģ8::ráB)ÉÉÖsüD8;uĀl6Äɓā_ģÕĢUÃÅ9w\ˆ‡gERSŌō훝“CVF&înša($¤>ߎ[ĪėOæŅ¸q:´o‡ÍGÃÂXņõJ ž…Øėā@B|ĮODĐĢ{W�jT¯ÎƒãÆrâD8^•<ķŽˆ„ÔĢĮâĪ—åõđg=¤Ļ¤pęôzt뀡—U|ĢXĪ_5x{{Wāy)*ƒÁĀšķ‘ÜĐĸƒîš›ĩ~`æû3õ™'.ŲÖfŗqŲ2ÛČĢODDŽ6Ø ũFÜŌũûŨœœšvõ­ŠŠ{ˇNŽyIKMÅÉɑ¸¸ø?ārÔzņ‰ąZ/ûįāā�ĀOŋüĘŠS§ytŌDŒF3˙÷A‘küĢ}“)˙ƒW6Ž<c¯Ŋ4•caaüž;”UĢŋãåŠĪâ`v`âƒã/ k2ąb^0šLų*úû˙˙ö×ų*°ž?ĪÕåDņxyUÂÕŅēÁšnŨÜĻķ.ÁÉÉ “ÉDZZ:..Î�\ˆŠÆ§re|*W&&&–@˙:y˛5iTb5‰ˆHé 8IJj*-[4/ôČáNĖx­ŒJĨá+ÉÎÎfîü…Lx`4ŪŪ^lÚücŪēC‡�@|bž>>˜Deo/ŒF‘‘ˆ įĪ1 ƒėėŦ|ÛrčĐ!�rr,;N`!/k9˜Í8Vp"9%wŠß~ßMÄɓ4П{ĢÆj!9%…  @vūļ €Ä¤$,Z’×÷žŊû€Ü§€^›ū&ÕĒW%>!‘ø„�9F@@Āë¨Q­*ĮŽŸ�ā…(.DE]ą€ØØXŧ/ēes­œ+T np ģ÷äĪŪņ¯ûYĢZ°~Ķf�öė;€ģģž>•i}cîr›ÍFBb"{öí§EķĻ%V“ˆˆ”ž’ž˜ņZ•ęmŖī׎įĮ­?å[Öŗ[.DGĶ´IcjÔ¨Á=ũû2õå×iÔ €ŦŦ,>œ5‡ķ‘¸§˙ŨųÆOüSÛÖ7ōŪ‡ŗx㭙øøøpįˇķõĒ5ҏQfŧõ.ãÆŒĘÛž{×.,X´„oŋKVVˇļģ™:ujqâDxĄŽ§qƒ8xļ­[S­ZU>[°ŖÁ€ÕjŖmëVxUĒİ!ƒølábvėÜEVV=˙ŧUÔŊkgæÎ_ČK¯NÃfĩqwß;pŽāˍá÷ņáŦ989æį°û_ą†ŨģōŅŦO™6ãmŧŊŊđ÷ĮfŗXÕjãØą0îéסPĮXXĮßĪ;īĪbūĸ/prräĄq÷0jØ`ۜų›ļlÃŅÁG'Ž s‡ö ;ÁCNÁ€ŅÃīŖR ‘ŌõĪ'&NšĖƒÆS78wĮą°0Ū˙āÃŧĮĨ 31ãĩ0Œ1Â6wîÜBīz0Œę~_ šĶfŧEß;û䝌ëÍéŗgųdî|^xæ)ŒFû¯ņķ¯;8rähĄ•>Mķ†×įų‘ōąiķ:ÜÚ.īķ;īžË˛å+ōŊa÷žũķŊawËÖmtîØáŠí† +ŌoÎØącŠV­šĻ(ŠšÕĢĶúÆXšú›ëæ%uWËĻÍ?ōČÖw)""ō/2ųᇱŲl 1€ūũî.ŗŠā:›ÛhĘ㏖w WÕĢĮĩŋt¯ŦTööæš§.}HDDäZ=2iœ*�0~ÜeÚ÷u^DDDÄ~”uhų‹&fģĸđ""""vEáEDDDŽČЈ>—öČE/ƒk!Ū+"""˙M˘Qˆ�S؉-V+FcņŽĄyĀŽĢ‹3 I)xVtË÷*{ųw*é‰-V+gÎGãYŅ­ĀmޤČáÅŋϧ#‰ŒŠ-Ô\=bB†•w ""rrõ¨Œk!ļKΰ˛÷đ‰×F<+ēQŖjņ^z[äđb6™ŽSŊX‰ˆˆˆ\+ ØģbLˆø­ŧk)4cø‘ŒōŽADDD¤ĐŒ5Ģe—w """"…fôiÚŠŧk)4cøŅķå]ƒˆˆˆHĄ]˛#Ęģ‘B3ú5ēŠŧk)4ŊįEDDDėŠÂ‹ˆˆˆØ•"Ocąq:’´´tÍ.-"""Ef2ņøsn#S1f–.rx‰8‰ÅjĨНˇf•–Ŋŗ‘Ņ4o\Ūeˆˆ”+ĢÕʉđ"##ÉĘžōûárg•ŽB€Œ“ŋf•>s>šÚÕĢšž"‡—´´t‘˙đˆ“¤¤ĻŌ˛EsœœœŽ¸mff&Gψ (0đ˛Û˜ŒFjTõáāŅ(Fx)ōĩĢÍĻā"""ōyáõ‚ƒŽ\ ÷ĘKŨā`"/D]q;“ŅˆÅj-V=°+"""W”™™Y¨āō— NNdff–Z= /"""bW^DDD¤X>üčc>üčã2īˇČvEDDDŪž9“å+ž #3ƒG&M*ŗž^DDD¤HŪy÷]–¯ø’ųķæ0lÄHŒF#“&N,“ūuÛHDDDŠ$"â$ķįÍĨnp0uƒƒ™?o.ááe7ŅsŠ…—…‹?güÄÉdfeå[ūņėOyėɧKĢÛ˙„caaŧûūG%Ō–ÕjãŨ÷?âĀÁCX,æÎ_Äs/žÂŗ/ŧÄÖí?—H›6ã-Ž……ųÖ|ˇ–ī×­`wčž"bĪĘĘfĖ„‡ÉÉą0ũ­™DœüŖČu_M\|<OŊđ =2…‡šca'Jŧ‘ëÅ˙fžCŨāŋ_āY78˜˙Í|§Ėú/Õ+/Ũ+˛{÷žŧĪiéiœ>sļ4ģüO¨ĖÃŽ+‘ļļlŨЎW%5lĀöí?“‘‘ÎKĪ?Ï=ÂęoÖW"ũüSQĄw¯ôėŪ €ī×m 3ëĘoxŧŗŲĈĄCødî|ŦÅ|ˇ@A>™ˇˆ›Û´âŊˇ§1vä0Ū|÷ƒm_D¤<9ņŅįŒ">Z]TĨ:æĨI“†lŨļļmZ°cį.ęÕ bßū�$&$2{îgX-223šåæ›čÜą=ëÖoäԙ3Ü?r81ąąŧņÖLž{úIÜŨÜōÚ^žâkÜÜ]ķ~ĐĻžü#†ŨG%OOf:ĢÕJFF&-[6ãļŨ‰ŠŠfŅįKČČČāļžŨiÚ¤qžzŋ\šŠ¨¨ĖfŠ)Šxzz2tČ ŒF߯[ĪŪ}0›Í¸ģš1ėžÁT¨āĄ‰ĐēÕ äX,Œv_^[IIɗ­#++›ų‹“˜˜„%'‡!!ÜŪģ'ĮOœ`éō¯ppp ##ƒģƍ˛åĮml˙ųWĖØlVFNtL,Ģ×|Į“Nâ|d$ Ņh$=#ƒÛ{õ YĶ&ŦZũ-ąņņ€¤Ä$œ*T`ܘQ—|Gß­]ĪĶO>ĀŪhßî ŨŨhذĸ}ģ[9f3ߚAE÷ŋŋƒŦŦlœüŗ?x€ĐŊûŲēm;?8Žņ'Ķŗ{7ĸĸĸ9sö,ô§^Ũ ŧ}=–w ĶgŧM@€?É)Š„‡‡ĶŖ{Wޟ &&–Z5krO˙ž|ŊúLFnnޜ<ųŗ?™Ë𥃹ZŦ—ũ^ÃŽŗč‹ex{UĸfÍyũúúTĻFęüž;”ohYÄĸ ļ+tĮ AH=ŦV+į#/PÕ¯čoŽšŪøUņåhXuƒƒŠp•P’‘™ÉącaøUņ-ĩzJ5ŧÔņgīžDEEãëëÃļíŋ0h`ŋŧđG‡öí¸ąe 233™üøÚˇģ…n];3ũÍw8tøë7lâŪ{ä .Wēŋ*ž 4‹Å†›ąŲlĖ›ŋžwö!88ˆä”Ļžô*¯Ŋō"NŽŽyûF™ōø#�ŧ:mGŽÅh4qāāáŧå+žZÉÆ6ĶģW˛s˛iŅŧ6(T߯]Ÿoî9›ÍÆkĶߤAH=6nÚBˇŽšąe bbc ûķļÃĘÕßōō‹ĪâîæÆ‘ŖaÄĮ'äëgūÂ%tëڙ͚Ë˯MgÆ´W0™MDGGķäc“˜ōĖ œ=žęUĢæí{öüyœœ*āUŠ�ņņ xzzæ­÷ôô !!€7^7W×B}�ŲYŲqûm=ųuĮolÚŧ9_xš˜ÉlÆÕՕūwßŖˇąæÛĩŧöōT,V =üũīž+oÛNÚŗrõˇŒ=’ŠînLŸņöeŋ×Å_,Ŗß;iÔ°;~ە¯ŋ!õØđP‰†ĢÕJ… ˙ ííU‰Ø¸x…ųWđ÷'<"‚Đ={¯z&wn#_üũK­žRÚ¨mëVlûųgÚ´n…ÕjĄęE?ž•ŊŊŲ´i Ûļ˙ŒÉd";+›´ôt*ēģ1jÄ0^Ÿū…Đŧiã+ô_HH}ž]ˇžŲŸĖŖqãthß› ކ…ąâë•ūœÚĀėā@B|Uū‘ ƒƒ˙ž‡Ąv­šœ9s–´ôt"##™6ã-�23ŗđ¯€ÍjŖ^ŨK'îģ\ƒÃG’’šĘÁÇ�HOOįBt47ŪĐ‚Ī—­ ėøqš4jD›Ö7psÛÖLķnlŲ‚æÍšRĢf =–×ΉđÔ¯€Oeo\œ‰ŠŽ 00 o;OΊ¤Ĩ¤æĢ1>.o¯JŸLŒšį̞ˇwÁÛ 8(ˇORūŅ÷?ÕŠ] €ŠŨŠYŖ:FŖŖŅŒŖ“#™é—ŨĮjĩ]ö{‹įĖŲŗį†ĨúõķíWŲۛŋũ^äã) ›Í†fŅ‘› l…žå^ē–zxšŠmŪxëŦ+mÛļɡn؊¯¨V­*Ŋ{õ�`܃“ķÖĨĨĻâääH\\üŸ?˙8˙øø×üžž>ŧöŌTŽ……ņûîPV­ūŽ—§>‹ƒŲ‰ŽĮÕÅåŠõ^üÅX­VlŽ´hҜÁ\v‡K–T‡ŖŖ#}ģtĸEķf—ėSŋ~=:Ėú?đķ/;3zũûŨEĮގ˛˙ĀAf͙KˇŽđõÍ \ũ@ū5÷”É”H“ [ūĪļüŸŊŧŧˆ‹§v­š�ÄÄÄŌ $˙˙ÅūŲˇÍjÉ÷Ųd2]Ô÷•.šyôŸŗÚ ØŲh4\ö{ĩŲlųöšÜŋl—üķtL&ii鸸8p!*ŸĘ•K´‘ōRŌ3^ĢRTÚ×§2^•*ąõ§Ÿisã ųÖ%&&áã“ûü/;v’cÉÁbÉ!;;›šķ2áŅx{{ąiķ—´ëâęBbb2�É))Dũ9Ôoŋī&âäI„ÔĪŊecĩœ’BPP ;˙ŧ}˜”Ä‚EK.[īáŖaX­6ŦV+a'ŠUŗuƒ‚Øģo?š—Ę6ū°%ßՏË)¨Žā @vüų_ũ6›ÅŸ/%1!‘ožũĢÕʍ7´dč{9NFF&+W¯ÁĢ’ÛßJĪ]9~2¯ƒÁ@`@� :&–ĖĖL|}|ŽXÛ_ŧŧŊˆ‹ĪûÜŦicļm˙ ›ÍFbR‡Éģ‹Õš?EüÚŌ3r¯Œ?^¨~¯•Á` ;;÷)ļË}¯ƒĒUũ8~<÷ÖÛūƒķí‹×•Ž8CĢZ°~Ķf�öė;€ģģž> /"ōīP3^‹2yIŨÍ7ĩá÷Ũ{đđ¨HjZZŪōn]:ątųWlÛū37ĸՍ-™=g.ūūuhÚ¤15jÔāžū}™úōë4j‚ßEãZßx|4›YsæâæîFÕÁfŖZĩĒ|ļ`FƒĢÕFÛÖ­đĒT‰aCņŲÂÅėØš‹ŦŦ,zvīzŲZ+{{ķņœOˆ#80€úuƒ1 tlߎéož““#..ŽÜ|Së+sAutëŌ‰…KžāÕi3°Z­„„ÔĮÃ̓Ē~~ŧ=ķ=*89‘•“ÀģûRĄ‚VĢ•—_›F… °ŲŦÜ7dIIÉyũ ŋo0 —|Áæˇ‘™™ÉũŖ†_öJĐåT¯Z•ĖŒtââãņĒT‰›Ûļ%"â$/ŧü* ÜĶ˙n<<*đÄSĪ^2` wĪîŧķîøųųáâė|É՜ŌиQfŧõ.ãÆŒ*đ{tOąœJžÔ ĘwĄîĐáŖ´hŪ´Dk5l0oÎü€M[ļáčāĀŖĮ—hû""åéŸ3Nœ4™'ŒĪ{\úXXīđaŪãŌĨ=1ŖaĈļšsįz‡ĐƒaT÷+ÜŲۛŋžhéĶģWy—Rf6mū‘ķįĪ3dĐĀō.ĨLDEĮđî{ōŌ Īäģ­Uŗ‘Ņ4oxé˜&‘˙’M›ˇĐáÖvyŸßy÷]–-_‘ī ģ÷ čŸī ģ[ļnŖsĮWl7ô`X‘ūŽ;v,ÕĒUĶô�˙uÛßĘ{~ˁƒ‡.ybęß&'ĮÂŧ‹=rXĄ‚‹ˆˆ\Ū䇯fŗ1lÄH�ú÷ģģĖĻ�Ím”Ī]}n/īƜŅh(ąŪ]īĖfO>Zv‡‰ˆü›=2iœ*�0~ÜeÚˇ9Ŗo*)ëĐōŖģ땚žhVią+E/ƒk<+"""×‡Ō˜˜Ņbĩ^ōRŌÂ*ō^Ž.Î$$Ĩ(ˆˆˆüGü51cF!La&f´X­œ9gÅÂÍ[øOE~ÚČŋϧ#‰ŒŠ-“’‰”ˇĐƒaå]‚ˆHų3ģp,âláļup!9ÃZāߟFŖΊnÔ¨Zŧ÷Æ9ŧ˜M&‚ëT/Vg""""×JvEDDÄŽ(ŧˆˆˆˆ]Qxģĸđ""""vEáEDDDėŠÂ‹ˆˆˆØ…ą+ /"""bWŒņ'~)īDDDD ÍxüPFy× """RhÆÚ5˛Ëģ‘B3ú6īVŪ5ˆˆˆˆšņøáså]ƒˆˆˆHĄ]ŗ—w """"…fŦÚäÖōŽADDD¤ĐĖÅŲ)2Í´ߓØ—CļÕV¨}Œšx›}:w`ÄđĄ<ųøŖyëĘŖ“Æbąđū‡ŗhūÜâ”v]Yŋa#ínšggį2éīŖYs0›LÜ?zdÛŦúf wÜŪģLę) Å /¯˙žDcĪ4rÄÁh(Ô>ŲV+OYx~[$ĩk×â˯Wņčä‡1›‹U‚]˜ķé<n¸Ąe™…—qcīŋâúĖĖLfÍųTáEDDėZą’Á¸ž-BpÜ+/wÖ2ąø„ŖŨ˂ÅKøqë6:węXā>ááŧđŌ+FRRR˜03n߯Y@‡ÎŨ6t0#† å—_wđŲ‚EĖúđŊŧ6~Üē‹ķéėxmÚøųųqGŸŪ<ōؓX­VRRSéŅ­+cīEff&Ī<?•č螺ŗšŠm˙�ŋîØÉ‡ĪÂjĩŅ­kĖfžüz%NNNX,ŪzcÕĢWËëwņį_°˙&=ō8¯ŋú™™—K§ŽíķīĖ˙ŊĪ™ŗg9ÁsOOĄreo^xņ ŠŠŠ<0æ~:v¸•čč&=ú8VĢ• Ā@Ž……ņô”'ØŧåGĖf3ãÃķS_æø‰Øl6ĒU­Ęôi¯ōĖķS9}ú4“yœ™oĪ`Î'sųaˏ8::âUНž<šŨІÛoëEvv6Ķ^{šĐßąˆˆHY(VxÉļÚō‚KŸ ÷’ģ՝*ā`4`5iŌ¸úŨÍŌå_^1ŧ<ķüTFJ×.9sæ,}ûdëæ deeŸ@jj*ūūuØąsW^xië-…ĒgÆMø×ŠÍÔįŸ%''‡Ī,Âfŗ1kΧԊ]›7§ŋŽÍfcĀŊC¸ųĻļ899ąwī~ļlZOĨJž´šĨ=ßŗŠJ•<ŲųÛ."/\Č^ß;w˙÷öî<.Ēęãøgö‘Mvpau_Ā]3÷%SKKĶĖ5SÛL[mŗÔ4MsË4[ėgešš™šįšûžĸ *(;Š Ė3ķû# TA›zŪũĶĖÜ{Îsīøržž{î=Ÿ3kÆ4\]\č÷Ė bE¯×ėŖĶ鈏ŋÄĘeKP(<=`0¯ŽEŖ†a\šr•î?ÁækYđÕׄ…6āĩ1¯pæLŨ{>‰BņWŧp1Žƒ‡ŗqŨj�~Yĩšôôtžū'O†3kÆ4<ÄÎŨģYōÃ"�ĻΘÅĸīķüˆį0tîԁ‡Zļ(ŅšB!î§rÍFĨRŅĢįc˜ũŠŠŠ¸ģģģŨŅcĮiŅŧ9�~~ž8::pábĩlÁÁC‡¸r5ƒÎ;°lÅJ, {÷ígÖ§ĶJTCķæMYđÕ7ŧöÆX~ø!žîÛ…BÁŪ}ûÉČČ`×î=�deeqūÂĒU­J`` ..Î�ô|Ŧũæ‘.éĐķ˜p[�� �IDATŽ-5kÖ¸cˇ;–ę!Á…ļ m€BĄĀl6sāā!ĻΘUđ™VĢ%1)‰3‘—ˆjÖŦŋĩBmøųúāââĖĀ!ÃčĐŽ-;uÄË˓čŦ˜‚möîÛOLĖyúö€Á` ~Ŋē�XĖfš4nTĸķ(„BÜo$ŧŒķ:ú z4j5+WũĘđaĪŲÆbąMø“RŠāáVągī~RĶR=ęeŽ?Á‘ŖĮ¸~ũzĄŅ Hyy&�ĒVŠÂĻõŋqāā!6ũž™9sįąūˇUčõz† @§Ž íwôØq´ZMÁëˇŪxgú÷cĮ;ũڛ 2ˆ§z?QėņŪéXūNĢÕŪüL‰VĢåËųsqrr*Ōüĩ¯RQx}MFÃŌÅß~ú Ûļī į“Oąđëhn™_¤×ëéÔąãŪ{쨚˙ŦC!„ø§y ĢJĪ™9uĢáķĪfŗlÅĘbˇQ(„…6(‰ŋDvNUĢTĄYĶĻ?q‚ØØķøWĢJ“ƍ˜ŋāKš7kV¤ŠNN¤ĻĻų?úĮŽŸ�`ũÆMœ8yŠ–-š\:Jŋr…F ÃXŗn}Áöã?šLJJjĄ6ŗŗŗ™ũŲįTōöæéžO1|ØPŽ;Vô” ÏåN5 -¨%5-qæĪ? āčÍū"ĪFqū…BûEDDōķĘUÔŽU“—^I˖͉ˆŒDŠTb0nļÆļí;ČÎÎāģīŗo˙;Ö#„Bü<Đ[}Zļh†Åbáđ‘ŖÅ~>i⇌ûp"?.ũ‰ėėlĻOũ­V‹VĢE§ĶQŲĪ€ĻMķú[oķŋož,ŌFŨēuđđpgÄ /ãęâLĩĒU°X,ōÎ{ T*1™L<ÖŊŪ^^ <qã'ŌģoL&3-[4ÃÃÝøK— Ú´ŗŗÃd2ŅĢw_ėíí1™L|4áƒ"}?Üę! ƜY3n{,wōŅ„y÷ũųmÍ: CÁ-Đ#ž{–—GŋĘŪ}û ĸV­š¨ÕĒ‚ũ*WŽĖgŸĪgÉOËĐh4¸ē¸ĐŠC{Ôj5:­ŽĮŸčÃĘåKyēož0Ŋ^ŗŗ3Ŋz=~÷/M!„xĀǡ,X°ĀĒū%™Õíō'šZ3a÷Īíį§P§Nmë*ĸΝ#11‰Vĩäúõl:téĘÆĩĢ‹\^B!ūMFŒĪƒyĨãāāĀįķ'0kÎ\L&c^yY‚‹Bˆ˙ /6¨’ˇ7K÷ ËB!ˆ2aW!„ĸ´$ŧ!„Âϔ枑FŠ(xĘîŸqK"×lA]ō„B!Š(ÕČK]W5Ģ.šJŧĸ4ä—•ķąĪ+ömB!„%QĒ‘—ą ŋ'™%ą*ōJ˜_Ô ¨nŸGk…ÖķB!„°FŠÂ‹ˇŠ9­Ũšx1ƒÁpķqõw§P(ĐëõTŠRš4Ũ !„B”ūViFC``@YÖ"„BqWrˇ‘B!lŠ„!„BØ /B!„°)^„BaS”™×ŗt B!„%ĻÖk5Víg2—HvvæŪ"-„Bņ'•RIE'ü*y RZČę[Ĩcã1™ÍxyēĄ”'å–ØĨÄBk?č2„BĢ™ÍfĸcbILLäFnîˇÕétx{yā_ åm‚‰Él&>!…ø„ĒúzY]Õá%;;G‚‹Bņ{žŦë×iŠN§ģãļFŖ‘Ȩ(bbc ,v•R‰_%Â#cĄáÅęąŗÅ"ÁE!„øILJĸzpĐ]ƒ äŧ„“˜”|ĮíTJ%&ŗšTõČŨFB!„¸#ŖŅXĸāō'ŊN‡Ņh,ˇz$ŧ!„ÂĻHxB!DŠĖ›˙ķæqßû-õŒB!„øīš1kËWü €ÁhāÕŅŖī[ß^„Ba•™ŗgŗ|ÅĪ,úv!�ƒ† EŠT2zÔ¨ûŌŋ\6B!„UbcĪŗčۅ„Ėĸo{ßú/ב—ˆČŗü´ėgŽfd RĢ ĸīSŊqrt(q&Me@˙žøWĢZĸ퇞…_Î/mÉÅēp1ĩZ…¯ĶgÎáŅŽ]¨Y=¤Lû¸Õē›YųëT*m~ˆžŊ{–[_B!„ĩæĖšYčuHpp‘÷ĘSšŧœ‹‰eū—ßđtŋ>˜>…Š“&āáéÎŦ9sਨ˛áōå„ûŌWėų ü˛z-ŗ§OæŗO§p:"’k™™÷Ĩo!„ĸ8:+o}6XykĩĩĘmäeÍÚõ<ÖŊ+ÁAųO×SŠTôėŅÖ­BĄP˜Č÷‹—ĸT*É1čŪĩ ę×#ãjķŋú­V‹ŗŗ3&S^A›ë7nâø‰S¨Õj4 ?z}ņ'įÆ\ũ°˜ŒŒk˜ōō¨Uŗ&Ũģ=BDdŋŽY‹ˇŲŲ9¤¤Ļ1ęő899˛eÛūØš GGGB‚ƒ8C÷Ž]Øĩk/§œĐhōׁ:u*œĩë6––NÛ6ĶŠCģ2;o;÷ėŖCģÖØÛŲ0áũąeÖļBQŪ^žDFEŒū.ĄÄ`4rölŪ^žåVOš…—øK—čųX÷"īģē¸�°čûéÔą=a ꓒšÆÄÉS™6å#Öoú Ā�žėõ8iééŧõÎû�DDFq*ü cßx€+Wąyë6ēuíRl˙ë7lÂÛ͋ᆯbą0yętjÕŦŽF­â… ŧüÂpė*ØņŋīsčČ5lČ/ŋŽf꤉ØÛÛņų_ĄP@ppÕC‚hÔ0”õëąyëvL&¯EjZã'~\ĻáårB~>•?é223iĶĒ%=-ū…Bˆû!Āߟ˜ØXŽ;~טüĩ< đ÷/ˇzĘ-ŧ(•JĖwxėotL,ĩjTĀÃŨ ģ HNIáb\<]:u�ĀÍÕ/Īü5""#ILLdĘ´O0oā_íļ퟉Œ$ëúuÂΜ ''‡¤”ŧ<<đõņÁŽBūČFEg'Žge“ˆˇ—7ööųī7jʞŊûŠmģNíZ�¸ģšq=;ģ„g¤d 81Ž÷Æž†Áhäĩąãđ¯FZ5Ę´!„ XJü8˙ō]FH}5ö`š4ėįëKdäŲ"mOœ<EíZĩ(ny$ĨBÁßgÃü€4Z aaĄôīÛ§DũkĩZzuhGXhƒBīGGĮ RžęcÁ‚Åb)TĶÖoRŠoÍ|e;ĮŨÍ 77WT*övvÔ¯[›Øķ$ŧ!„x`ĘzaÆ{ĨŒ‰0”KÏvíĖÚõ9 €Édâ×ÕkYŗnJĨ‚Ā€�ÂOŸ %5 ŖŅˆ§‡~>•8{.€¤¤d’’ķv âø‰“ ųÃU›ˇnįLäŲÛöČūƒ‡°X,,^ōW3nģŊˇ— ‰‰är�8|äØ_*w]ŧŦ´h֘Ŋû—g"7/3‘gŠVĩĘ}é[!„(Ny,Ėx/ԕ}ĘįG9Đߟ—^ÉŌeËIŋrŊ^GęÕ3ęE ƒôįû—˛mĮNŒF#Ī=;FC—ΙŋāĻL›››+ūūX,‚ƒiÛēS§Ī@§ĶbggOËMoÛ§íøūĮĨLš2 ŗŲL͚5¨č\‘Ô´´bˇwvvĻsĮLúxînnTŽâG|üe�jÕŦÎōĢ0™JˇúĨ5jVĄIŖ0^ûyy&Z6oBŨÚ5ËŊ_!„âvūž0ã¨ŅcxéÅ ālTs?ŸWpģty/ˍ2dˆeá…%ŪáhxžŪåVЃ´s÷†5ĀŽ‚k×oāúõlú<ŲĢLÚž”˜Bhíā2iK!„¸ŸļlÛN›‡[ŧž9{6˖¯(ô„Ũ§úô.ô„Ũíė¤}Û6wl÷hx”Uŋ#FŒĀĮĮuLäũy~‰-ČžžÍ䩟â`o‡VĢcؐAē$!„âgĖ+¯`ąX4d(�ŊŸ|âž- � ļËŊķũ§ëÜŠoŪé$„BˆÛ{uôhô:=�/<?ōžö­öŽĶâžv(„Bˆ‡ûZū$ 3 !„ÂĻHxB!„Mą:ŧ( Ė6ļ°ĸB!J¯<f4™Í(•ĨCąz/{ģ \Ŋ–%F!„øøsaFC LIf4™ÍÄ'¤āėäPĒzŦ^ÛČŋ˛7ąq‰$&§a‘�c•ŖáQē!„ĸtÔvœŊT˛m5vdˎũŨS*•8;9āWŠtĪŗ:ŧ¨U*‚Ģų–Ē3!„Bˆ{%v…BaS$ŧ!„ÂĻHxB!„M‘đ"„B›"áE!„6E‹B!lŠ„!„BØ /B!„°)^„BaS$ŧ!„ÂĻHxB!„MązmŖ<“‰Ø¸D˛ŗsdei!„BXMĨTRņæÂŒ*Ĩõã(V‡—ظDLf3^žn( Ģ;˙-—S­ü ËBqÎEĮ™•Eõā t:Ũˇ5DFEáčā@P``ąÛ˜ĖfâRˆOHĄĒ¯—ÕõXw˛ŗspvrā"„BüG$&%•(¸�čt:B‚ƒILJží6*ĨŋJd\Ë*U=V‡ŗÅ"ÁE!„ø1% .ŌëtÆ;nŖR*1™ÍĨĒG&ė !„ÂĻ(5̓ŽA!„6hŪü/˜7˙‹ûŪ¯Õv…B!f˚Åō?`0xuôčûÖˇ„!„BXeæėŲ,_ņ3‹ž]Ā !CQ*•Œ5ęžô/s^„Ba•ØØķ,úv!!ÁÁ„ŗčۅÄÄÄŪˇūËuä%"ō,?-û™Ģ¨Ô*B‚‚čûToœJÜÆ„ISĐŋ/ūÕĒZŨ˙‰“§Ø˛m;cFŊdõžw=;›sįĸŠ_¯.kÖm@ĨRōHįN÷ÜîĩĖ,fÍųœ—_‹ŗķ=ˇw¯îvΖ.ûwÚˇm]æ}¯Û¸™•ŋŽAĨRŅöá‡čÛģ'�ŋ­ÛČú›AĄ n회6…BÁŠĶ|špŗgįŠŧ>ú%*:9–y]B! ›3kfĄ×!ÁÁEŪ+Oå6ōr.&–ų_~ÃĶũú0súĻNš€‡§;ŗæĖÅbƒOæ=ū'Nž [×.e\�~øq ]ģtúG—’čķdOļmßArrJ™ļ{ūŋŦ^Ëėé“ųėĶ)œŽˆäZf&ņ—.ŗzíĻ}<žš3Ļr9!‘?víÅlļđéėĪķŌH>›1…:ĩjđŨâĨeZ“Bˆ|ēÜú|+ƒ•ˇV[ĢÜF^ÖŦ]ĪcŨģ”˙t=•JEĪŨiŨę!  ‰‰|ŋx)JĨ’ƒî]ģĐ ~=2Žf0˙ĢoĐjĩ8;;c2å´š~ã&ŽŸ8…Z­ÆŅÁAúŖ×>9›ˇngįŽŨx{yaooWđū­#8ƒ1oŒeūgŗøeõo¤ĻĻ‘ÄĶ}ŸÄd˛°ėį•h5ŒÆôíĶ‹Ę~~,ųi9Ŗ—uČÍËEĨTŅŖ[WŽ?ÁēõŅét˜L&=ķ4ŪŪ^L6ƒĀĀ�2ŗŽsųōe7jH§í ՚œœÂŋņŧ0â9�ļīØÉŽ=ûĐhÔX,f† Œģ›Ûmûđ‘ŖŦ^ŗĩZ—§'C$7ī˙ûîG˛˛˛¸‘{ƒēĩkĶŖ[W""ŖøuÍZ|ŧŊČÎÎ!%5Q/ŽÄÉÉņļįėvõ´o׆ŋof@˙~eöįeįž}th×{ģüū'ŧ?€­;vŅŦIŖ‚÷ÛĩiŁÃG¨\Ų''Į‚šíÚ0æÍwËŦ!„ņöō$2*Аā`ôw %Ŗ‘ŗgŖđöō,ˇzĘ-ŧÄ_ēDĪĮēyßÕÅ€Eß˙H§Ží kPŸ”Ô4&NžĘ´)ą~Ķīđd¯ĮIKOį­wŪ "2ŠSágûÆĢ�ŦXšŠÍ[ˇŅ­k—‚ļ3ŗ˛Xĩú7ĻNšˆŊŊßũđã]ëÔ¨5¤ĻĻōū;oĸP(ØđúõĨZĩ*DDžeåĒՌ}ã5ÚĩmÍĨK—éÖĩ ŋŦū €œœž]ôĮŊKEįŠėŨŋŸZÎ̝ŧ„J­FŖÖ0d`2ŽfđŪ„Š„—S§OS§v͂×ĢV¯eâø÷ptp "2Š+WŽ’šš^ėqˇoך˙ũđ#“>‡““#‹—üDDd$įĸc¨XŅ‘į‡?K^ž‰ÆD­Z5ҍU\¸p—_Ž];ū÷Ũb9BãF o{ΊĢĮŨ͍Ú5k˛~ãī%úsPR—’đķŠÄøIŸ‘™I›V-éņhŌŌŌqsu)ØÎÍՕôô+7ßw-xßÕřô+WĘ´&!„ųüũ‰‰åčąãwŅétx{yāī_nõ”[xQ*•˜īđäŧč˜XjÕ¨€‡ģv*œ’Ÿxētę�ä˙Pyyæ¯yIbb"SĻ} €Ņxƒ�˙j…ÚLLHÄĶÃŖ`ô fęėÚŗ÷Žĩĸ¸ųÔ`ww7VŨ 'ÆÜ\23¯ßvŋ˗puqĻĸsÅüūĒWgņ’eĩ�@EįŠ\Ī*ÚNzz:.ˇü0ˇlŪ”ŠĶgŌ¸aĄ ęSĨ˛ĢV¯)ö¸ãââņtwĮéæūũž`ÃĻÍ´o×�ĩZEPpįĪ_Āŋj|}|°Ģ`wŗ&'ŽgeßņœW€ĢĢ+WŌË6((pūbī} ƒŅČkcĮų~üKŽÅ<á9˙J¤<ųY!ʋÅ–?ˇ|˙>.ˇđâįëKdäŲ"mOœ<EíZĩŠûũAŠPđ÷Ų0 VCXX(ũûöšmŸ–ŋí}kxēĩŋŋ?ŽX­ųë4Ė˙â+ž>ŒĀĀ�ΟŋČW ˙wÛūŠsëŌ J•ĒPuEęĩ€â–/¸÷“=iÛöaNž gÁW éÔąŨmû\tt‰įũĖTĒÂSœ,7˙ģÕ­įŦ¸zZˇz¨D}ZËŨÍ 77WT*övvÔ¯[›Øķđpw#é–ų5IÉ)xēģãáîFJjZÁûÉ))x¸ģ•KmBņ_{žŦë×iZâ…cbcoģ0ãŊ*ˇ ģvíĖÚõ9 €Édâ×ÕkYŗnJĨ‚Ā€�ÂOŸ %5 ŖŅˆ§‡~>•8{.€¤¤d’’ķv âø‰“ ųÃU›ˇnįLäŲB}z{y“˜”Äõėl�N†Ÿ.øĖŪŪžŒŒ �ĸŖcŠ­Ųbą‘q ww�vīÛGŪÍ97J…’ÜÜÜBÛûøVâĘÕ Ž\Ŋ Ā™ˆŗ”øššš’žž€Á`dÕę5¸ē¸ŌļõÃ<ŌĨ#Ņ1įo{Ü~žž¤¤Ĩ‘~%˙ĨËV°˙ā!‚ƒ9}:˙¸ķōLœ‹:Gā†înwÎnW1* -š5fīžäå™ČÍËãLäYĒU­Bã†Ąė;pˆëŲؘÍfļlÛAŗĻ¨ZĨ2ŲŲŲDßŧ5oĶ–m4oÚ¨LkB‘¯ŦfŧWå6ōčīĪK/Œdé˛å¤_šŠ^¯ŖFõęŒõ" …‚ÁúķũKŲļc'FŖ‘įžŒFŖĄKįŽĖ_đ SĻÍĀÍ͕�, ÁA´mŨŠŠĶg Ķiąŗŗ§e‹Ļ…útrr¤ûŖ0yĘtÜŨ]ŠäíÍĩk™@ū„ΕĢVsøČ1|}*[ŗBĄ GˇGųtÖœčÔĄ'O†ķ˯ŋŅ(,”_V˙Æ÷‹—āpķVī ú <;x�ķ|…N›˙…ĐŋÄį¨N­Zlú}+�zŊŗŲĖÄÉSĐëõX,f<ķ4ž•*{ÜzŊžĄŸaöÜ/P*xyzÚ�Sž‰ī~ø‘i3fsãÆ nՒjÕĒÜ6°ŨîœŨŽ€đ3g¨SĢfąí•VÍę!4iÆĢcß#/ĪDËæM¨{s>Pīž=xķŨņ�„5¨Gķ&ų!åŅ/ņŲ_c2™ņpwãĩW^(͚„BäûûŒŖFáĨ_ $8€ŗQQĖũ|^ÁíŌ%Y˜ņ^(†nY°`A‰w8…¯ˇGšô_ķų_Ņ´q#5 }ĐĨ”ˆŲlfÜøõâķxzŪũĪÁĨÄBk߇ʄB”—-ÛļĶæáV¯gΞͲå+ =a÷Š>Ŋ =awû;ißļÍÛ=eÕoĈ#đņņ‘å´O÷eöÜyúÛÄŗ^–­ø…6­.QpBņī4æ•W°X, 2€ŪO>qߖ�YÛčsrräũwŪzĐe”Xß>O<č„Büŧ:z4z€žy_û–đ"„BˆRšßĄåO˛0ŖB!lŠ„!„BØ /B!„¸ŖÚŒV‡…BŲW…B!Déüš0ŖĄϤ 3šĖf”ĘŌĄX=a×ŪŽW¯eáėäPčQøB!„øw*ë…Mf3ņ )8;9”ĒĢËeobãILN+ņÚ:âŋíhxԃ.A!D°¯čŽ} ļË4˜9~&úļŸ+•JœđĢTēg†Y^Ô*ÁÕ|KՙB!ÄŊ’ ģB!„°)^„BaS”™™™ē!„BˆģĘĖĖäęÕĢ(t-B!„wåč舺ŗŗ\6B!„m‘đ"„B›"áE!„6E‹B!lŠ„!„BØ /B!„°)V/g2—HvvŽŦ.-„BĢŠ”J*Ū\ÛHUŠ•Ĩ­/ąq‰˜Ėfŧ<ŨdUé{t)1…ĐÚÁē !„âžúsUéø„ĒúzYŊŋÕq';;g' .B!„(•R‰_%2Že•jĢËŲb‘ā"„Bˆ{ĸR*1™ÍĨÚW&ė !„ÂĻHxB!„M‘đ"„B›"áE!„6E‹B!lŠÕĪy))ŖŅČâĨË9á<J…ŗÅL×ΝiÖ´1×2ŗ5æužėõ8Ũēv)Ø'11‰ąī}ĀŗCb4Ū`ķ–m�¤¤ĻâââŒZĨÆĮĮ›ÁđĘĢoāSŠ�&“ o/O ė‹ŗsyŌsôD8?ü´gg§Bīˇ}¨5o|_kųbátéКÜÜ<6lŲÁËÃßs›‡Žž QhŊ{/îύčØģ֖“c æÂEj×aŨĻ­(U*ē´o]h›Ķ‘Qüą{?#‡>Sfĩ !„¸wå^ÖoüZńqīš–Æ'ŸÎĸvíš�¸ģģąwīūBáeמ=xx¸ĐĄ]:´kĀ›īŧĪË/ޤ˛¯/�×2ŗĐhĩLš0Ž`ßŋüĘŌe?ķüđgË됨ā@˙ďčŸ5DEĮ–I{šyylŪžĢLÃKI\ŒŋDxÄYjך¯ũ !„¸wå^Žggc2ũu˙ļģ›ŸLžä‡­F‹‹Ģ ‘QÔ¨ŒŲláĀĄ#ÔŦ^ē“úuëđŨâ%EŪ?ÍOËWĸŅh0 ô|Ŧ;uëÔæTøiÖŦۀVĢ%//ũûáííŔiŸâįëKLėy‚‚ą¯`Įc=ā÷-ۈ‹į™~}YôÃb22ŽaĘËŖV͚tīög"ΞfízĖ C„¯ōt!î?,û…ˇ^‰RŠdÚg xĸû#( –Žüw_{Šđųˆ9ĪēߡáíåAZú´ ūUšt9‘K I éßw7vî9ĀūÃĮĐjÔ( =Ũ'GfąnÛßļž9 žÅŋje˛Žg“˜”LhŊ:´y¨šyy,ũy5™™Yä™LT  sûÖ,ũy5Šéé,úq1.ōƨ‘8ØÛąxų*rrr6°šyyŒŸ2“‰īžÎ†-;ˆŒŠF­Ráčā@ŋ'{ ÕjyķƒÉ4lPSž‰Æaõ ęšt9‘˙-YÁ ĪĀÅš"�ƒ‘•ŋmĀ`4ō{ŝ…ę˙cĪ~ö8‚§‡övveøM !„(+å6įĨsĮöœŋp7ßyŸEß˙ȁƒ‡ÉÍÍ-øÜl6ĶŧYvîŪ ĀŠđpĒU­‚N¯ˇē¯üās˜āĀ€"ŸmŪ˛NÛķækŖyaäsde]';'›ī/aÔK#yõ•—čÚĨ?,ų �ĩZCģ Œ{w,ĩh΁ÃG ÚÚā-š5cũ†Mx{zņú˜QŧųúNœ:Ett Zĩš˜ØX^1ėž€Ē•}iPˇˇė`ÛÎŊT ( U+ûōŌsƒŠl¯V̏œDĪG;ķüĐÄ^ˆÃŪŪŽū}zRˇv ; @ŽÁĀ‹Ã2jäP‚ĒąkīÕŖVŠPĢÕô{ĸĪčËÆ-;�Øŧ}žînŧ0l /ĖéČ(Î_ŒŖSۇqqvfĐĶOR#8čØ �¤¤Ļ‘u=‹ÅBĖų‹úW#:ö§NG0jÄ^>­VÃ{ōëĘËÍŖ~íZ<Ũûņ‚ZŽ\ÍāģĨ?3l@߂ā ×ëhÕŧ ĩk†ĐąmĢ‚÷¯ggŗvĶV^1˜!ũû`AÖîBˆĸryqwsãÃ÷ß!îŌ%""βcį.~^õ+īŋũVÁ6 CCYēügrrrØĩg/͛5ãô™3%j?÷Æ Ū˙�‹…Ā€�úöîUdģÆÂX˛lQįÎQ¯Nš5mLäŲ(2¯e1gî| ?üܸqŖ`Ÿ  �ĒTö‹…Ë čt:2ŽeP=$ˆUĢ#ëúuÂΜ ''‡¤”ŧ<<đŠT G‡Ō´;ˆŽŊĀ”™ķ Ŋ÷Üā§qsqĻKûÖ˚˙ fŗ…1/ä_6SĢÕTtr,ļ-/Ow4 �ööøųäĪr´ˇ')%�įŠ,\üĩšô+øW)q­U+āäč@vN�Qįb¸žCDT4?ú‘’šN?߂ũj„q.&ßJĪāŠ�� �IDAT^¸TŦˆV§årBgĪÅPĢząâ ¨†ōæ"^ÁAū;™˙X,‚Ē´•“cāķ¯ņøŖņōt/QŨIÉŠx¸šbWĄ�!ė?t´ÄĮ-„âū(ˇđ’•u;;;*ûúRŲחŽíÛ2cö\ŽŸ8IŨēu€ü×ĢS›ģ÷rî\ #† -qxŅhĩLüāŊģn×0,”5Ēsúô6mŪʞŊûißž înŒ}ãĩb÷QßüahÖŦ AĢĶĐŧi Z­–^ÚÚ Đ~ŅŅ1h4åsJũĢŪvÎKn^ššyX, Æ8¨ī\ƒōo+x*ˇžļ–~…Uk7ōîë/cWĄ[˙ØCjzz‰kŊĩ}Ë͕Į5 vnNŊ›sžū””œZđ˙5‚Ųŧ}'Ū^žVCŖŅKTt,­[6+6HÜēT…ú–ãNMK§YãPļūą‡Ú5BP”`I ËßVIˇXJ÷Øj!„åĢÜ.}:k[ˇī(x}=;›´Ô4<= ˙+¸eķfüúÛÂBëŖRŠĘŧŽßÖŽĮl6͏QC>Ķs11øVĒDÆÕĢ\ē|€ˆČŗlŪēŊØũ›7mĖą'8xč(-š5 8(ũų?x‹—üDÆÕŒ2¯Ŋ¤Vūš7ĻcÛV,]š€ŧŧ<2Že–ĒŊk™YT¨ ĮŽBnܸÁą“á˜ōLˇŨ>33‹Ģ×îØf€U;䟺ŋŽãZf …ĸārb… zÔj5'N!øæå¯ĶQ˜Ífœ¨V…ŗŅą˜oŽ…qö\,ū7GyūޞŸŊē?‚“ŖCÁĨĢ[ëT(äææÚĮËĶä”4rr �œŽ<wˇS%„â(ˇ‘—ŸΏK—ąmÛÔ5,´o߆āā ŽeūĩŠdÍÕŅët4oŪ´\ę¨äí͌YŸĄ×鸑—GŸ'zĄ×ë>l(ß|û=:“ÉÄĀgúģŋģ›ÛÍmĖx{į/ÛŨŠC;ž˙q)“ĻLÃl6Sŗf *:W$5-­\Žōīî™8mvĄ÷ũĢŅ N-RRĶč÷D įā‘ã¸ē8;aˇ$ĒVöÅÃ͍YķŋÁŽBē´oÏ+VqęLdąÛī>p˜k™™ôyŧÛmÛlÛĒ9Ë~YÃĖy_c6›Š€“ŖvôhÔjĻÍYĀë/§Fp {ÆÃŨ€äÔ4ÂęךyŧU ĢW‡9 žEĨTâęâĖC͛ÜņXúöęÁ'sæčOTĖų‚:ĢUņcíĻ­,[ĩ‡›sėíéÜž53æ}›‹3^žîdf•nÅS!„åG1|øp˂ JŧÃŅđ(|Ŋ=Ęą¤˙ŽK‰)„Ö~Đe!„ÄŅđ(Ģ~GŒ<aW!„ļE‹B!lŠ„!„BØ /B!„°)^„BaS$ŧ!„ÂĻX^ f‹Ŧų"„BˆŌ3™ÍEžø^RVīeoWĢײ$Ā!„ĸTLf3ņ )8;•n-@̟°ë_Ų›Ø¸D“ĶŠŦ#Ŧw4<ęA— „BÜWJĨg'ü*•V‡ĩJEp5ßģo(„BQdÂŽB!lŠ„!„BØ /B!„°)^„BaS$ŧ!„ÂĻHxB!„M‘đ"„B›"áE!„6E‹B!lŠ„!„BØĢ—Č3™ˆK$;;GgB!„ÕTJ%oŽm¤*ÅĘŌV‡—ظDLf3^žn( Ģ;ĸŧ\JL ´vđŽD!ū]Ėf3Ņ1ą$&&r#7÷ŽÛęt:ŧŊŧđ¯†ō6ÁäĪUĨãR¨ęëeu=V‡—ėė .B!ÄHLėy˛Ž_§aX(:îŽÛF"Ŗĸˆ‰%(0°ØmTJ%~•<Œ…R„ĢĮjĖ‹!„â?$1)‰ęÁAw .?ōLbRōˇS)•˜ĖæRÕ#v…BqGFŖąDÁåOzŖŅXnõHxB!„M‘đ"„BˆR™7˙ æÍ˙âž÷kõ„]!„Bˆŗfą|ÅĪ�Œ^=úžõ-áE!„V™9{6ËWüĖĸo0hČP”J%ŖGē/ũËe#!„BX%6ö<‹ž]HHp0!ÁÁ,úv!11ą÷­˙ryų~ņ9ŠƒŊX,X€ÎÚĶēÕCVĩŗw˙~š7mZäũ'Oņëšu˜ķLäæåâãS‰AĪôĮŪŪŽī/aīū˜>V[°Ī_~ÚčhĻOĖØw?� Į`Āh0âė\€Īôãúõë|ķí÷¸šš`2™¨Sģ&O÷íSėwÖm؄Á` ×ã=Øŧu;Ûwü 5B‚éßī)å|kųíÎQYš0i*ú÷ÅŋZÕÛnŗaãīlûc'û÷Ŗv­šVĩ˙ËęßP)Uôč֕#GQģVMNGD˛o˙AžūėŊ–_Äoë6˛~ãfP(¨[ģ&#‡ .÷īI!ū-æĖšYčuHpp‘÷ĘSš^6ęŌŠtîĀ•ĢWų`Âd‚‚ņ­TŠDû߸‘Ëē ŋųaÎÍÍeÁW ˙Áģ¸ģš°tŲ 6mŪBĪĮēāäčđ#ĮhŪŦ �Ų9ŲÄÅ_*hcʤņ�ėŲˇŸ#GņŌķ# >;xč0ÕĢ1fÔKu˜3—;wŅļõÅj‰ģt‰ũņÁģo“˜Čæ-Û÷Ū[ču˜9g.ûĸYĶÆ%>gÖēŨ9zŽ?ÎĐAŠtOíŦßø;AAA„Ö¯ĮŅcĮŲŊw-›7+Ŗ*!ūŌeV¯ŨĀŦi“¨ ¯ĀMá]{iŨĒE™õ!„˙&盎>—ôviƒ•ˇV[ëžÍyqqvϞŸ/I‰IøVĒįĢ×r:â *•'GG|Ŋ^Į‹Ŗ^Ĩi“Fä™L˜L&R’SųâËoyËŋžÆäæårëŌJ}û<Y¨ŋzõjķĮÎ]áe˙CT âÄÉSV׎Õj¨Uŗ—‹|ļaÃītéÔĨRÁ‰S§ mPģ v�´hŪ„ã'OŌŦicĻ~:‹v­[ҏQÂ}Íf ß/^Âå„ËX,āæęĘĐ!ųdúLīŅ­`ôbŪ‚¯¨_¯.š7rŲĩg‹Å˰Ąƒųå×ß ŖSá§YŗnZ­–ŧŧ<öˇS§Í ŸĖŦëÄÄÄĐĨsGĸÎE“ššF•Ę•yĒw¯BĮ•q5ƒų_}ƒVĢÅŲŲ“)¯āŗõ7qüÄ)Ôj5Ž ПŨ{÷™+ĄWĪĮ¸|9Ũ{öĄÕjP(Œ6”ŠÎ ā Æŧ1–ųŸÍ*h{ëöœ?/ŋ^Čāũy¤sGæÎû˛LÃËĄŖĮiÖ¤övųßSģ6­8pøˆ„!„¸ o/O"Ŗĸ F—Pb09{6 o/ĪrĢįž…—ääâ/]ĸjÕ*DDžåØņŧ˙î[(•JžũîļlßÎŖ]:“›—KXhęÔŽÅå„ΟŋX(¸�88ØĶķąî|0á#‚ ĸah}|nŅ ¨æĪņ§HNNÁĶ̓ģöōtß'K^r 9?qŠNÚųėDx8}ŸĘNW¯\Ååæå'�ggŽ^Í�`ä°Ąčõ…ŋđäädÎFcŌ„q�ėŪģĖĖLÚļy˜ģöPģVM #QQŅ<;doŊ=މãßÃŅÁˆČ(Ž\šĘŖt.8GŲ9Ų|ˇx ž˙6vė8~š–üÄëcFĄRĢąˇˇ§÷=Ųžc'kÖn`ōÄ1™MŧüĘëô~ĸ'Jå_—MÖoú Ā�žėõ8iééŧõÎû�DDFq*ü cßx€+Wąyë6ēuížũčÛû 8Ãkc^ÆŪΎUĢ×°õ?čŲŖû]Īuģ6­Yĩz-Ç ÅÉŅ!˙üįä~%W×gw’––Ž›ĢKÁk7WWŌ͝”IÛBņoāīOLl,GŋëÃįō×6ō$ĀßŋÜę)×đ˛qĶVöė;€ÅbĄ‚^ĪĐApsueīžT¯\0¤Fõ9 €ÅlĄzČŨÖ{¤s'Ú´nEDdg""˜2m&Ũēv)0š7mÂÎ={hÖ´ fŗ‰J%ŧ\pöl4ī˙�ĨRI“Æ iŌ¸aĄmrķō¸a0âčāPl‹nÎŖ¨XŅŠČįîîî88Ø1}æÔ¯Gð¸8;͏a+~^EvN6ĮOœĸaXtZ--›7eęô™4nFhƒúTŠėĮ億‚ö.^Œ'ķZsæÎōGvnܸQđyĩĒU�prr¤˛Ÿ/JĨĨRV§Å`Ė)1¸O—N€üw/Īüĩ'""#ILLdĘ´OüQ°�˙jEŽÍÍՅų žF­V“–žNHPņë[”„››+iiWĘ,ŧüŨ­ß“BˆâY,`)ņãüË÷īÔr /;ĩ+˜ķr7 Å_a5͡5›-äää`ooGhũz„Ö¯Gŗ&M˜ˇāĢBáĨEķf|ōéLĖ&3Í­ŧėX0įĨ¤\]]HMK+xšš†ģëípÕjoŋų:.\äÄÉp&NšĘ̝ŧˆŸŸM7äāÃ?uĒ`Äĸ÷“=iÛöaNž gÁW éÔąÁˇ„Vƒģ‡cßx­ØūˇL6ūûÄã[/Áüí%æ›`5Z aaĄôīÛįļĮ•œ’ĘOËW2yÂØÛÛąaãfRRķW|ž5#XŗĻEYf w7’’S ^'%§āéî^v!ÄŋLY/Ėx¯Č­ŌÁAAœ‰<‹ÉdāLÄY‚Š/)JnäŪ(ōūÉS§˜4e׎ŧwņb^ž…¯¯yz¸ãęâÂģ÷ĐŦqŖ2> ШÕhõ:2ŗ˛�¨_ˇG';'ŗŲĖžŊ{ ­@FÆĩ"CmqqņėÚŗ—ĒUĢĐŊÛ#ÔĒUƒ¸øË�´mũ0;ví&3+‹Ę•ũ0ŒŦZŊWWÚļ~˜Gēt$:æ|Ąsä[ŠW¯rér~‘gŲŧu{ŠŽÍΧgĪE””LRrū[!AA?qƒ!˙X6oŨÎ™Čŗ…öŊv-{;;ėíí0Œ:r”ŧ›ßĩŊŊ=ų—ŌĸŖcŠí[ĄP{Ë÷ž––Ž‹‹KąÛ–Fã†Ąė;pˆëŲųßĶ–m;hÖ´ė˙|!ÄŋEy,Ėx/ČCęLJҏaŸ|:ĨR…ģ›mÛ´.˛››+Zĩ†ņ}ĖûīŒ-˜“Qŋ^]RR͘6cvÁí­în ôL‘6ZļhÆá#Į¨X҉ëŲŲe~,ukÕâTx8͛6ÅĶ̓G;wâãOf�P§V-Â䇗/ž^XdÂŽ‡‡;Ģ×ŦcûŽ]¨Õ*ŊšŊ——'*ĩŠ–Í› ×ë0›ÍLœ<Ŋ^ÅbfĀ3O9GÇ å›oŋG§Ķb2™øLŋRW—ΙŋāĻL›››+ūūX,‚ƒiÛēS§Ī@§ĶbggOË…īt đ÷ĮĶÝÉS§cooOn°đ?pôøI:´kÃĘUĢ9|äž>Å_ÆĢ[§Ķ>ÍķßEĢĶĸ×ëqģÖĩ*y{ŅģgŪ|7˙Žŗ°õhŪD‹BÜÎßī45z /Ŋø!ÁųĶ<ÎFE1÷ķyˇK—÷ŒŠáÇ[,XPâކGáëíQnؚ¸K—øzá">x÷íB^īUú•+|2}>x­öΗŅūÍ.úžę!Á%ēÛčRbūĨ ĐÚwŸ3%„ĸäļlÛN›‡[ŧž9{6˖¯(ô„Ũ§úô.ô„Ũíė¤}Û6wl÷hx”Ug1Yā^UöõĨiãFŦZũŊīQ&mūļv=ûdā3ũūĶÁåčņ“ Æ2ŊMZ!ÄŊķĘ+X,  @ī'Ÿ¸oK€ŦmT&ēv)Ų¤ä’ęūč#tô‘2mĶ…Ö¯Khũēē !„Åxuôhô:=�/<?ōžö-áE!„ĨrŋC˟daF!„BØ /B!„°)V‡…BųīO4B!Äŋ–ÎĘ[ŸK˛0ŖÉl.ōĀԒ˛z/{ģ \Ŋ–%F!„øøsaFC LIf4™ÍÄ'¤āėTüō:wcõ„]˙ĘŪÄÆ%’˜œ–ŋ&Œ˙0GÃŖt BņīŖļãlėĨ’mĢą#Ķ`žíßĮJĨg'ü*•îšqV‡ĩJEp5ßRu&„Bq¯dÂŽB!lŠ„!„BØ /B!„°)^„BaS$ŧ!„ÂĻHxB!„M‘đ"„B›"áE!„6E‹B!lŠ„!„BØ /B!„°)^„BaS$ŧ!„ÂĻHxB!„M‘đ"„B›ĸ.ÍNƒĶg"ȸv ŗŲ\ĸ}”J%qqņôy˛zŊžāũöģ2sú'ÔĢ[§4Ĩ”Øņã'xõÍą�¤Ļ¤ĸĶéptrāוËyĸw?ĻM\¤Žœœ&|4™§ÂQ)•˜ĖfF>7ŒîŨē–kŊeá×ßÖđX÷n<t˜¯žų–/įĪ}Đ% !„÷ŦTá%üLöövTĢV Ĩ˛dƒ7fŗOOŽ=FãF Ņh4ĨéēÔęׯĮ–ë�ûÎûÔĒYƒúßuŋ/ŋ^ˆVĢeí¯+¸tų2ũ ᡖ-pqq.ךī…ŅhdÁWßđX÷n4nԐƍ>蒄Bˆ2QĒđríÚ5ü­.?ōRŠ’7IÉI\ŧG``@ĄĪÍf3ã>œČščh, >•*1uĘ$,f3īŽû””TrssiŅŧ/Ŋ0˛Đž&“‰÷>Īšsųûđņ¤ VÕw;×Č3™ ^ûúø°}ËÆbˇ9û3ļlŨŽRŠāąŨyvČ bbbų`ÂG(•J˛˛˛xņų‘´kۚ9sᑐ€ÅiiiØŲÛ1{Æt<Ĝšķ đįÚĩLââãY0.nŽŽėÜĩ›y_|‰^¯'// ŽÃŋZU6nÚĖgŸĪGĢÕâ_­*S&OäŨqĮčWß īSŊ™;ī ~X´Đęz„BˆšR…ŗŲ\Ē` T*ąX, †"Ÿ]¸ĮÁC‡Ų¸n5�ŋŦZMzz:˖˙LĩĒU™>õc, }ú=CËÍ mPŋ`ß+W¯æ–&�Đī™A8xˆfM›”æđ yvč žqm;tĄe‹æ´hŪ”íÛĄÕj mˇ{Ī^víŪï+—a2™6âyëŅwĮ}ČĐÁéØĄ=ņņ—čÕģ/lûFßx÷-ųė\t4†“§Â™?w6ŽŽŽŧ;îCÖoØČcŨģ1îÉŦūe9ŽŽŽėÚŊ‡'|ÄįŸÍâŊq˛aŨjÜ\]˙Ņdöí?ĀķßãäÉpf͘ƞũ ę´ļž ĀĀ{>‡B!DY*UxšW‹ĨĐk…BŸ¯..Î 2ŒíÚŌšSGŧŧ<Ųģo?ėÚŊ€ŦŦ,Î_¸P(ŧ¸8;“‘‘ÁĀ!Īĸ×Wāü… ¤_šR&ĩúúø°ú—Džbßžũ,]ļœé3fŗrųRœ+lwôØqš6iŒJĨBĨRņŨˇßŧßĸys�üü|qttāÂÅ8�ÂBėīáîÎÕ̍T*B‚ƒptt,ôūé3¤ĨĨņÜČ ÎaNNgÎDPšreÜ\]øāŊw�ˆŽŽ)öxŦ­G!„ø§y áåV)É)899ĸŅhXēø;ÂOŸaÛöô|ō)~Ŋ�Ŋ^ΐAčÔąÃmÛøå×՜ ?Í˙žų ĨRÉs#^(ŗúŽ^ÍĀÉɑę!ÁT fĐĀgúÜHļnÛN¯žl§T*1› ‡2‹Å‚BĄ(ŌĻR™˙žJĨ*˛}qīču:ü*ûątņw…Ū?rôKÉ&M—ļ!„âŸäŪ*ũÕ× Ļj•*DDDōķĘUÔŽU“—^I˖͉ˆŒ¤QÃ0ÖŦ[ä˙˜Ž˙h2))Š…ÚINNÁĪ×ĨRIėų =vœÜÜÜ2ŠqđŗĪņÃâ%¯¯]ģF|ü%ĒTŠ\hģF ÃØš{7šššäææŌā._N ,´AÁ¨Q|ü%˛sr¨ZĨŠÕu‘œ”LÔšs�ė?pīž_Lõâã/‘˜Āä)Ÿ°vŨz”J%cáËs …ĸĖęB!”2ōōüK¯ ¯ §VÍ|>g�•+WæŗĪįŗä§eh4\]\čÔĄ= …‚qã'ŌģoL&3-[4ÃÃÃŊP{õčÆČ^fĀāgŠ\Ų1Ŗ_fÖėš4 ÅĪΎÄuŊøōh´ēŋæ˛ü˛|)ŸĪ™Å„IķÏKŅjĩX°0h`5 +´o“ÆčØžŊz÷ÅbŨģâëëä‰2îÉü¸ô'˛ŗŗ™>õã"ķeJÂŪŪŽ™Ÿ~›oŋ‡]… äåå1qüØÛÛņņ¤ ų"*•ŠjUĢŌąc°XĐiu<ūDŪ|ũՂvĘĒ!„âAQ >ܲ`ÁĢvÚ˛m; ÃÂîža19‚—‡uęÔ.ÕūB!„øo1b>>>ō„]!„BØ /B!„°)^„BaS$ŧ!„ÂĻ”*ŧä?ͤdĪš•Ųl.ö9#B!„%UĒđRŅɉ¤¤dĢŒŲl&)9 N+F!„ĨVĒįŧÔĒYƒ#ĮŽ‘˜Pâ§°* t:ŽöčõúŌt+„BQēđĸ×ëiܰ!/Æa0Ŧ 0zŊžČĶi…B!JĒÔOØÕh4”e-B!„w%w !„ÂĻHxB!„M‘đ"„B›"áE!„6E‹B!lŠ„!„BØ /B!„°)^„BaS$ŧ!„ÂĻHxB!„M)Õō�Y7Lü—Iâõ\,”l…hŧėÔdGáøŅ#¨U**T¨@‡6ҰA]�Æŧ=ž™Pš’ õÖĖ™:ūžÛų§‹ŋœ€ZĨÂÛ˓/ū@—­ŠVĨ2_ˇ„Ôô+ŧôÜ`&N›ÍÔßž§ļ…Bˆ’R…—?â2qäuŨ-((ŲĸŒÄ\ŋAĸŗīŧÚ;;;’SԘ÷Íwču:j× )M)˙iĮN„ãëS o/OF}�‹ÅÂÉĶ‘Ėúø EŠ‚ËßÛB!ūIJ^’˛ķ¨ëVōāų#/ūöíčJBB"xz¸ņę‹Īá`o—ŋRÉæíģ8q–kYYô{â1ũĢ’šv…åĢրŒÆtlۊÚ5BHJNåĮĢ0›-h5jžyĒ.Î úĖ1ølÁ˙čŪĨ5Ģŧ{!ŽUk7ĸQĢ1<ÚŠ=5ĢŨļŸu›ļ’vå*IÉŠx¸šâééÎ#Ú�°c÷>.]NäéŪŗeĮnÂĪDĸVĢąˇˇŖß=ĐjĩŧųÁd6¨‹)ĪÄĶŊ/t^ÖnÚĘŠĶ‘( ‡ÕŖmĢ%ĒŖYãPö:Š“ã9´ ›wėĸ[įö8r‹Å‚o3 o/Ū?•9SĮc4Ūā‡e+šr5PĐũ‘T `įžė?| ­FBĄ`ĐĶŊIMK/Ôļ—§Gąõ!„BŠÂ‹ŲBApؘZ˛&:ģįĄ$Æ`0ŧīäčđ×F ^žthķûeûŽŊúWåĮĢčÖš=ÕĒp=;›ŠŗæķūŖøqÅ*:ˇkM­Á8|ŒƒGŽĶŠŨÃ�ä™L|ķŨR:´y¨Ppøc÷~ÚļjAƒēĩ¸r5ƒsąįnۏFŖ!-ũ ¯Ŋô—’X´dEAx9|ė$=éČš˜ķœ9{ŽQ#‡đۆÍėØŊŸŽm[‘—›GũÚĩ¨X¨ŽČs1DœæQ#0›Í|ņíb…Ö/Q …‚¨čXÔ­Míš!lŪą €§zvãĀĄŖ#1ú}ûNÜŨ\yv@_’’Yûöî;°§ë˙ãøķŗ˛÷ˆėiÄ(ZJkSĨ¨RŖ[ÕĒZ´Ē|ŠļŠĸÔŪ{īYģJ! Id“Ŋ?Ÿûû#¤B¤Â7ßßûŅšûšįžsîį#ŸWÎ=÷žmģđ÷ņ"ŋ €a¯ÁÔԄÍÛwsāđQēuzoO˛c˙8cîÛ#„B<hÕ /˙–ĸT<bŖ( A׃†­ ųų(ŠBTt,ë6oGĨ*_ŖÕj¸–‘Eėåxü|<x¤iãrĮZ°tîn.4ir[=ŗzũf.Æ\"(Ā—fŪąžŒĖl�ŧę×CĨRáîæ‚$§¤ad¤#+;oOļėØCJjS@QQ1õÜËúåãåq[;b.Åáë]ĩZZ­føk/TēUs)Žm[āęėÄ̃û`kc͜…KŅiĩ\Ŋ–‰—gŊråîÖGģ*ˇC!„øˇJxšYRr :##ėmm�Đh4e¯)€JĨBĢÕōú‹155ųį5EA­VW„EÁÎֆĄ}ÛÖåGw€F!øy{r!ō"{öæØÉĶ éß§ÂznĐŪÔŽæĄ 9ų÷YŒt:š‡6DĨRĄĶéhHŸ§ģUØO­ööS­VŠnk˙ú[Q;ĒB­žŊŽôĢ×Xŗq+Ÿž˙6fĻĻėÚwˆ´ĢWĢÔ!„âA{(ˇJĨŖ3į-&%5íŽû{Õ¯Į‰Ķg�ČÎÉeŲę ¨T*ŧ<ęrö|8�§ū>ËĒõ[€Ō/ܧģv¤ãm˜ŋxÅm_Ú[wíàmĖsŊ{s)îŽõT¤y“Fœ=ΊŋÃĘF|ŧę×ãėųŠŠŠ�Ø{đ‘cîÚ/oOÎGDĄ×ëŅëõL›1—Ģ×2*Ũ•JEqqņ]ë(ĢĢžGŲšēz-ƒ)~VAÁ�� �IDATĶg“•ƒŠŠ fĻĻņי0ô%úێ]Ųö!„ÂCy™5ÎÛvcanFī]ôķšëūũ{?͒•k9ų×ŠŠ‹iũōGŋŪ=Xŧb-ģöB§Õ2°ß3åĘĩn؜ ‘Ųž{Ų\�G~ų팍(..ĄWˇNw­įVļ6Öé0 8:ØĨ_đ­[6gę¯s12ŌafjJ‹f+,ƒˇ§ ƒøá§™ @ĶÆ °ŗĩŠt;ü}ŧXˇy;ƒáŽõ�<ŅæQ._ÃĶf ( Oué€G]wíí™2}6fĻĻtiߎE+Öpö|xšcWļ=B!ă :t¨2cƌ*šũwJ€ĒMØŊą u2!!ÁUkŠB!ū_{ũõ×qss“'ė !„ĸv‘đ"„BˆZE‹B!j /B!„¨UĒuˇ‘ZUú¤\JŲDÜĘ0 iI!„˙Nĩ˛„ŗ™–˜<UĨW”†Ō°“§ÂJU\­'Ä !„B@5G^¯kÅöČĸō4•0*l4%xĒŗ01‘'ĩ !„ĸzĒ^,ŒÔ<ågĪåËqÜq­ĸ[ŠPabbBŊzuĢS­B!DõŸ°ĢĶéđööēŸmB!„¸'™?+„BˆZE‹B!j /B!„¨U$ŧ!„ĸV‘đ"„BˆZĨĘw•čõÄÄ%‘——Ą’ˇH !„BÜ QĢąļ˛ ŽĢ#uÕĮQĒ^bâ’Đ 8;ŲŖ–'劇$!)•Đ`߇Ũ !„Õ 7ˆOL%>1wį*—¯rÜÉËËĮÆĘB‚‹B!ĒEŖVSĮՑĖŦœj•¯rx1(Š!„Bü+ĩŊÁP­˛2aW!„ĩŠ„!„BÔ*^„BQĢHxB!D­"áE!„ĩŠ„!„BÔ*U~H]e˛pÉrb/ÅĸVŠ1(ēuîLËÍÉĘÎaÄČ÷éÛģOuëRV&))™ŅŸ}Á+/ Ą°°ˆ;wš–†­­ Z77^2˜wŪû�7WW�ôz=.ÎNŧ0d ļ665ÕĨ˙*›ļlŖ  €ā@6oŨÁ;Ãß$<"ŠYsæōDģĮéŪĨķiĮđwGņĶ”IÕ*[TTĖđ‘īķËÔÉLš:~}žÁŗžĮ}m߲58uú,ffĻåļŋøüŗÔuwģ¯uŨËGcŋeÂØŲ´mj†.íÛ>Đú…âE…—Í[ˇŖĶjøęķĪ�HKOįûISĀÁÁžÃ‡” /ÂŅŅ€OļŖÃ“í�øđ“1ŧ=ė ęē쐕ƒÎȈq_}^VvÅęĩ,Yļ’7‡žRS]ú¯—Ā‘cĮųâĶQĢUøų–>iöÜųķ´yėŅ\€j—›iĩ^2ˆŠĶ~á뱟ĄŽÆŖĸīĻCģ6´oûØ}=fuLûņÃn‚BüO¨ąđ’›—‡^˙ĪÃgėíų~ü×@iø0Ōakg˅đHü}1Ž?I ŋ_ĩękÔ „ų ßļ}ųŠÕXXšĶĩs'�Æ~=ž—^LqqK—¯B§ĶQPPĀ3={Đ $˜””T,^ @AAŨģvĻQÃŦ^ˇž´´t“yž_&Oũ‰Ž;‘’’J|Bũû=‹ŋŸáQ,[š #ŽÂÂ"ú÷덟¯/k×m$5= FK\\MB“•MfF&YŲ،zwZ­†Í[ˇqúīŗhĩZ,-,xađ@LLŒËõi˖ítéÔĩZÅųđÖmØD¯OqøČQ4j ffftl˙DŲūˇļŨĘŌĒÂ>ffd2}ÖlT*ööv\š’ÄĀūĪ’›—ĮÎŨ{9b8�K–­ĀÎ֖NÛķōĐ7™3s:ßMœDwwĸcbųøŖ÷ `ÃĻ-QRRpqq&22ŠK–aogKŨēuĘÚčäč@:îœ8yŠæÍšVë3PUģ÷"ūJƒŸëÍÕkL›9—÷ß~ũ‡‘•Mŋ^O•ÛķŽ=¤_Ŋ†FŖ!áJ"B‚ČÎÉ!3+›œœ\Ūzu*•ŠĨĢ֓”’Šĸ(8;9ō|ߞ¨T*F|ô?NøōŽí9ÉžƒGxãåA�ŦŪ°kkÚĩnɲÕHLNĀÖÆšũžAQ–Ŧ\Gvv%z=ū>^tnߖȋ1lŨĩEQhČãļ¨š“(„A…—ÎÛķĶ/3øđ“1āOhã†čt:� ­Z>Âūƒ đ÷ålXõ=ęalbRåēJƒĪ |ŊŊ*]fĮÎ=tęØžæM›–žNdäE�æÎûƒŪŊžÆ×ׇėœÆ~5Žņß|‰NĢ#--1Ÿ|ˆJĨĸ¸¨_ztīƟGŽąs÷nüũ|ČČĖ`đ€ūÔ¯_ áŦZŗŽŅŒBŖÕ••Í{ī /ģ<6yâxėlí˜0q2ŖcP…ŗaįũÁ{�ŦXĩ†ģv—ø;,ŒūĪõ-ˇÍß·Gš7ÃÄØ¸\pnkû„‰“+ėãæmÛņņņĻī3=Iŋz•>ƒZŖŠÔųÔju˜š™ōų§ŖÉËĪcūÂŌķ1fĻfœ ;Į‚ÅKyä.YÆŗŊ{đcĮË#(П3aįXxi×ēĶfĖ%"*š]ûŅįén˜›™Ņöą(†ÛÕj4dįäđæËƒIIMįë‰SųúĶ÷ąąļâĮs‰Ŋ“ŖÎNŽ čÛ€CTt,žŪžÕngZúUĸb.ņé¨ŌđxôÄ_ääärøØIœėü\oEaĘôŲøûzĄĶišĪØŅ#173ĢvŊBņßĒÆÂ‹ƒŊ=cĮ|B\B.D°w˙VŽY˘?*Û§ih(K–¯$??Ÿ‡ĶĒeKΝ?_Šã1æËo�Po//ú?ÛģŌíkŪŦ ‹—­ 2*І!!´lŅƒA!<2’Ģ× ēž‚V§#ãZ�>ŪŪeÛ|}JÒĩ599šĨũv°gÍēõ�“[ļŋ‡G]�,­,ą´°ĀÎÖ�+++rķrš|9ޤ¤$ž›Xz)ϰ°/Īúåû]RBQA!–•îëÍmŋ[/ĮÅĶĨS�ėíėpvĒÚbY~>>�\žOvV?ū4( —EEE(ŠB|BžžĨû”+ī`oĪŅc'ĒTgeėŪˆc'O—ũlnnÆÛC_DĨR1đšŪLųå7ũ| ôĀô.úÆ<K sĖÍ˰ąļ*û97/s3SōōōøiÖ<Œt:RŌŌÉÉÍûWíˇŗĩÁÜĖ”Ÿ›GH`�ameIdT4šyų\¸ŧ IM슪ƒ.NŽ\„˙ŗj,ŧäääbffF]wwęēģĶąũLžú§˙>Cƒ!�˜˜Ķ0$˜ũÍ믞\éđĸ32âë/>ģ÷ގ,Ãtc…ĻMB đįÜšķlÛą‹C‡0ô՗ĐiuŒūV…ŋøĩēō§KsĶ¨Äŋͧ˙:‹7‡žŠˇˇąą—™5į÷˛}nžËĄēu^‡ĸ 3ŌҤI(û÷ģwŋĒčFÛÕjÕûxëXƒ^¯/më-kYŨi- íõQ5‘G{F0ĒüņåĻJ” Žsk]÷ÃmŊ㜗üŧ|ŒŒ¸–™‰ĸ(÷Ŧ˙æuŊÔę[÷U8zō4q ‰ ģ~ iÆÜ…w<Vėå8æ,X@Į'Úā`gWîuÃõķŖŅhx÷ÍWˆOH$ėB?L›Á›¯ F§ĶŅŊs+^ŸGvķqĩÚû§-„]Ũ*=iʏėÚŗˇėįÜŧ<ŌĶŌqē>!÷†ĮZĩdíú 4 mT. Ü/fæfdff“CĘõyë7nÆ`0ĐŧYS† @Tt4�>>ŪŊ~9#3+‹ų Uē.EQČĖĖÂázūų'%ú’J—÷ķņáôßg(((`ĮŽ=œ(ˇNĢÅČĘėœę­Ä wîcw7""#Ō;ŋRRSŌs˜uũ*ŠBttĖ]īîęJfF WŽ�p!<‚ģö RŠpuu!*Ēt¤āLXXšriééØŲŲVģ_UURRÂÂåkxyđsØŲذīĐQ�ō ČË˯Ö1ŗ˛˛ąŗŗAĨR‘š–Nė叞xĢúõęōÕ'Ŗøę“Q´iõff&eīĢĸ(Ä^Ž !1‰#ĮOQĮŨ•ÎíÛâīëMBb^žœøëlŲū+Ön"+ģúŸ !„¨-jėĪŗaoeŅ’eėŪŊ­N‹‚Bûöíđõõ)÷ 60ĀccZĩĒ™I…-š7ãįé3™1k–ÔŠãЂ̋ “§LÃÄØ˜ĸ’úõ)Ŋäô įųũ…9zœĸĸ"ēvîXéēT*O?՝IS~ÄĘԊNžäĖ™0V¯]_6×įn|}ŧyĸm&ü0cc#ĖĖĖyŦ‚É– ‚‚8FĢÕ;gwęc—N˜>c6ß|;Gû˛ ĩžõąļļâĮŸ§caa‰““#ŠrûœLLLúęËĖžûÆÆFčõz† �ĀķĪ=ËÂ%ËąĩąÆĪ×§ÜĀØšķá4 mT­>Ũ͎=û9t´üüšö?FjúUBũpsqĻ×Sų~ę¯úųpōīŗNØ­ŒæM1sŪ"~šų;öövtīԞÛváYŋŪ=ËÖĢãŽĨĨŗæ-ÂÜÜG{{@ÁÁΎ-;öpđČq´ ææf4 •ŠeĢ7đŸ_~Ã`0āīㅕĨW¯]Ģrģ…ĸ6Q :T™1cFĨ œ ‹ÄŨÅą›$î%.!ßæĖ+ģUēĻ|5nƒöŋīĪ^ŠHJjS§ũÂW_|ZЏ„¤TBƒ}kŧ]B!jΊ°Č*ũ.ũõ×qss“'ėÖFuŨŨiŅŧYŲÄāÚŽ¤DĪÜų xõåjäŌĄBˆ˙-2̝–ęÖĨS×ņų§Ũ{§û@ĢÕđҍwH]B!j?yB!D­"áE!„ĩŠ„!„BÔ*U/*• Ã]n“B!„¸ŊÁPí…xĢ\ĘÜĖ”ŒŦ 0B!„¨ŊÁ@|b*6VU[濆*ßmäYׅ˜¸$’RŌīú 2!jÚŠ°Č‡Ũ!„Õ VĢąą˛ ŽkõžWåđĸÕhđ­ī^­Ę„B!ū-™°+„BˆZE‹B!j /B!„¨U$ŧ!„ĸV‘đ"„BˆZE‹B!j /B!„¨U$ŧ!„ĸV‘đ"„BˆZE‹B!j /B!„¨UĒŧļQ‰^OL\yyų˛˛´B!ĒLŖVc}}aFēęã(U/1qIč œėQĢTUŽPˆ#!)•Đ`߇Ũ !„˙‚Ū` >1•øÄT<ܝĢ\žĘq'//+ .B!„¨ZMWG2ŗrĒUžĘáÅ (\„Bņ¯hÔjôCĩĘʄ]!„BÔ*^„BQĢHxB!D­"áE!„ĩŠ„!„BÔ*U~ÎKe˛pÉrb/ÅĸVŠ1(ēuîLËÍÉĘÎaÄČ÷éÛģOuëRV&))™ŅŸ}Á+/ Ą°°ˆ;wš–†­­ Z77^2˜wŪû�7WW�ôz=.ÎNŧ0d ļ665ÕĨæī3gŲš{#G ŋíĩČČ(ÖnØÄ{ī gÄ{đĶ”Iäääōí„pswcد=6w?M™Tíō_›Āāũ9rô8ŽŽ´ĸí}lœú;ŒKWaccUnû­ĨuĢæ÷ĩŽ˙FÅ%%Œūâ[&ķ°›"„÷]…—Í[ˇŖĶjøęķĪ�HKOįûISĀÁÁžÃ‡” /ÂŅŅ€OļŖÃ“í�øđ“1ŧ=ė ęē쐕ƒÎȈq_}^VvÅęĩ,Yļ’7‡žRS]z芋‹™ũû|øūģ¨Õę˛đp9>+kĢ\€\nÖ¯ī3|ūå74ÂÉÉņžķ_oOŪxyĐ}=ĻBˆ‡¯ÆÂKn^zũ?÷o;ØÛķũø¯Ōđa¤3ÂÖΖ á‘øûb0(=~’@ŋjÕרAķ.žmûĘ5kIIICĢՐ›“‹ C=OvV3įüŽA¯§ °Ö=Jû'ڒ••ÍĖŲs1 Ō´icēwéLÔŋ,]ž NGAAĪôėÁŸGŽÚ¸͚†rđП,Yļ‚˙3•JŎß˙ĀĀũIŋz•M›ˇbllŒ^¯į…AĪãââĖw'QĮŨč˜X>ūč}öî;ĀūqqvÆÜÜŦÂ>>r _ėlmxyč›L›2‰+א––ĘÜų xiČ?_ÖįÃ#ذq3EĄihcoũķ,$33 }I AôxĒ+™™LŸ5•J…ŊŊWŽ$1°˙ŗäæå•Z˛lvļļtęØž—‡žÉœ™ĶYŊn=iié$&&ķ|˙žXYZą`ņR� čŪĩ36(ĢÃČČôú�Ôj5íŸlĮÖí;<p@ĩŪûĒÚŊ˙ņW’ü\oŽ^Ë`ÚĖšŧ˙öë$%§˛dÕz>U~Ä+<*šÍÛwãä`OaQ1yųyŧ0 /fĻĻ,]ĩž¤”TEÁŲɑįûö`Ųę $&§�`kcÍĀ~ĪPPPĀüÅ+K?[…E4n„…šņWéÛŗ;×22ų|ü$Fŋûîn.lÜļ s3Sũ}Yžzjĩšü‚Bē´oKp ›ļí"ũZÉ)iôyē+Šĸ°|íFėlŦqwuy įR!† /;ļį§_fđá'c $0ĀŸĐÆ Ņét� Zĩ|„ũāīËŲ°0ę{ÔÃØÄ¤Ęu•Ÿøz{ŨöšZ­&33“Ņŧī&r!<ccÚĩmCķĻM(,,däŖiÛĻ5§Nũ…‹ŗƒžī^¯gûŽŨ(ŠÂŽ{čÔą=͛6!-=Čȋ„„AŗĻĄœģpOĪúÄÅ'āėėDúÕk8:ØķÃ~äëĪ?ÅÚÆšÃGްhérŪ{g8Z­S3S>˙t4Ų99ŦYˇž ãžÆÜ܌ų UØĪŗaa4oÖ´Ü6s33ú<ķ4›ˇn/\�Œ´ZĸcbøūÛo°´°`íē¸89ķÚË/ĸ( ã'ü@P ?ĮNœÄĮĮ›žĪô$ũęU>úd jĻRį^§Õ‘––ƘO>DĨR1aâdz÷z__˛srûÕ8Æķ%›ˇmĮĮۋžŊ{•ÕqCp` ›ˇn¯T}÷CģÖ­˜6c.QŅėÚˆ>OwÃÜĖ ēî í…ÛöWĢTÄ_I䍗ablĖú-;ØsāOÚ>Ög'G\,?ū:‡¨čXlŦ­ˆŠšT‚Žžø‹œœ\Î]ˆĀŅÁžg{uĮ`0°įŸøûzŗįŸ@iH ō÷%2:w7"ĸĸyūŲ^,]šŽvmĨap�é×2˜4m_~ü:ŽôĢ×5ü5T*ßOũ•ž];āįÍÉĶgØųBˆ­ÆÂ‹ƒŊ=cĮ|B\B.D°w˙VŽY˘?*Û§ih(K–¯$??Ÿ‡ĶĒeKΝ?_Šã1æËo�Po//ú?ÛģÂ}}}ŊËūßŖ^]âãhŅĸ9;wîa˙Ch4Š‹ŠÉËĪ'00€[ˇ1ķˇš4hDģļmPŠT4oքÅËVEÐZļhNvv[ļî� 11™ŽÚq><‚ŦŦ,üũ¸r%;[ŦmŦô÷gáâeemņķņ )1 'GĮ˛—Ā�:|[?Ō¯^ÃÎÎļRįį7WW,-,�8NNn.aįΐŸŸOrj*—ãâéŌŠ�övv8;Um ooT*ƒBxd$+V¯Auũ)ĖZŽŒkw­ÃÎΎkW¯UŠÎʸs‰īūķKšm¯Ŋø<öļ6 |Ž7S~ų@?BũKÛĒÕbmeYáąÜ]]016 ŽģGOü…š™)yyyü4kF:)iéääæáUŋæfĻüüÛ<BhÜ k+Kü|ŧØžį�ķ—Ŧ$ČߗÖ-›add„ĸ(äååMĮ'Ú°{˙amҌėœ\œˆšĪë>ĨÁÜŪÖR͝āUŋ*• EQ¸’”Œˇg=�ü|nōBņŋĸÆÂKNN.fffÔuw§Žģ;Û?ÁäŠ?qúī34h€‰‰1 C‚Ųđ0QQŅŧūę˕/:##žūâŗJíĢÜôøaƒÁ€ĸRąlÅ*ÜÜ\ËæÜŧ9|$�NNŽŒ˙j,‘‘œ8yŠĩë6ņõØĪhÚ$”€�Ν;Īļģ8tøC_} ĩJÍŋŅ89:āīįĮ‚EKÉÎʤApP…mšyiíõQ(…ōĢsîô¸dEAEՖfĐéūy‹ŒŒčŨáIš„6.ˇĪūƒåƒ’^¯( eÛīĐ.íõ:Ôj:­ŽÃßÂÜŦüĨ¯[×ŋcī#oO;ÎyÉĪËĮØČˆk™™(Šr[_oe0üĶE1 RÁŅ“§‰KHdØĢCPŠT˘ģ�FÃģožB|B"a"øaÚ Ū|e0n.Î|öūÛDÅ\âôŲslŪą›GÃßכ¨˜K$§ĻáãUŸ5ˇ‹ŋWiÛ*hĪĪ‘öĻ2åĻUŪ•p~…âaŠą[Ĩ'Mų‘]{ö–ũœ›—GzZ:N×'äŪđXĢ–Ŧ]ŋ&ĄĐTōREUÄ`P0 D^ŒĻ^Ũ:dff•M>|ä(%úôúŽ8ILl,AĨ—Ž z˛srXŋq3ƒæÍš2dĐ�ĸĸŖ`ũÆÍøcgkËĩĢ׈ˆŧHpPnîŽ\ËČäZFFi;.Dāåuû_Ä.Î.$%'“›—Ā™°söÃŪŪŽôĢWĢ}|}ŧ9rėPúEˇpņR232ŠãîFDd$PzĮWJj*�fæfdef—ísĪ:||ŧ9zė8�™YYe—Ā긚u€ää’SRĘĘ\ŊzÛ*Ž(ũ%%%,\ž†—?‡ û-۞™•]a™+‰Iääæ‹ģĢ YYŲØŲŲ RŠHMK'örzŊž„Ä$Ž?EwW:ˇo‹ŋ¯7 ‰Iüuæ—âđ÷ņâŲžŨŅë ääæčīÃĄŖĮqr´ĀÕŲ‰}‡Žčī‹JĨÂĶŖ."KĪ]úĩ ŠŠŠp°ˇ+×>•J…‹“#Ņą—8Y#įN!ūÔØČ˰7‡˛hÉ2vīŪ‹V§EAĄ}ûvøúú•ũĪ*’ū˜ĶĒU‹šj ööü:ë7Ō͝âëíE€Ÿ/ÅEE,]žŠũŅ A4oĘĖYs4h�ŋĪ_€úúeV-ÁÎÖW&O™†‰ą1E%%ôëSz‰*$8˜ ›ļ2āšgp¯ãFRR æ�ŧōâ`~™1 cŖŌK/ x[ûŦŦ,éŅŊ+ãŋû;\]JŋoÄŲ°s4oÚ¤ZįĄS‡'ųcŅÆ}7ƒÁ@``�Ö6Ötéԁé3fķ͡qt°§nŨ:�xzÔĮÚڊžŽ……%NNŽåūē¯Č ƒžį÷?räčqŠŠŠčÚš#�]:wdúŒŲ|7q2öövxyz–+ėüyB‚ĢÕ§ģ‰ŧÃ×§–ÛæíYs3SBũpsqĻ×Sų~ę¯ú•ÎŅŠhÂ.€›Ģ k6n##3 EQč>¨EEءˆŸfūŽŊŊŨ;ĩgãļ]ŧöÂķœ=ÎÁ#ĮŅj4˜››Ņ(8Ģ™,^ąļė2OķІØX[annÆŦy‹éÛŗ�>^õY˛r/>_ú™ęß§'ËÖŦįāŸĮ(,*bp˙>hĩˇ˙ĶíķtWVŦŨ„ĩ^ž ¨ !ūGІĒ˘1ŖŌN…Eâîroi­IĢ×­GŖÖđôSŨvSūĩââbƌũ†ßˇėŽŖšpã,žõ=jŦŽ Ÿų #†ŊYŠ[Ĩ’R ö­ņvŨ,ōb [vîåíĄ/>Đz…âŨаČ*ũNũõ×qss“'ėÖ&:ŽW^˜ß˙x sF„e+VĶŽíã÷ũ/B!ūwÕØeŖ˙Ī<Ũãa7ážōõõáũ‘#j´ŽĪ?ũčŪ;Ũ'ũûõy`uU—¯ˇ'žŪžģB!Ž“‘!„BÔ*^„BQĢHxB!D­"áE!„ĩJ•ËJĨÂpg}!„B܍Ū`@­ŽŪJ•K™›™’‘•#F!„Õĸ7ˆOLÅÆĘĸZåĢ|Ģ´g]bâ’HJIŋįĶV…¨ §ÂäŅ÷BQ›ŠÕjlŦ,¨ãZŊg|U9ŧh5|ëģWĢ2!„BˆK&ė !„ĸV‘đ"„BˆZE‹B!j /B!„¨U$ŧ!„ĸV‘đ"„BˆZE‹B!j /B!„¨U$ŧ!„ĸV‘đ"„BˆZĨĘË”čõÄÄ%‘——/‹3 !„ĸĘ4j5Ö××6ŌTceé*‡—˜¸$ôÎNö¨UĒ*W(ū7$$Ĩėû°›!„ĸēąĒt|b*îÎU._帓——•…!„BT‹F­ĻŽĢ#™Y9Õ*_åđbP .B!„øW4j5zƒĄZeeÂŽB!j /B!„¨U$ŧ!„ĸV‘đ"„BˆZE‹B!j•*?įĨ˛ūX¸˜cĮO`ff^nû¯ŊBũúõîk]ÃßÅOS&ązŨz4j O?Õížŋ"Ÿ9ËÎŨ{9bxÖķÕ¸ ØĪúåļ/_ą KsēvîTŖõ˙[ã&ũDaa!:Ũ?5ĩZçŖjöŧŨ‘cØ˛s/o}ņžûžW|7 �� �IDATâĐŅãčõzT*Å%%„6ĻÕ#MīYvĶļ]¨5ē´o[íļNũuOunˇ§ĮŊwž‡â’Fņ-“ƍ)ˇ=?ŋ€čK— đģãššSY!„¸Ÿj,ŧ�tíŌé|šū4eR×!ĒīÕ!ũŠWĮũa7ŖÆlÚļ‹Âĸ"žļĻ&&�”””°vĶv6lŨÉSÛ?äŪ—ãģAp€ßÃnŠâ˙š /•ĩvŨFRĶĶĐh´ÄÅÅŅ$´1YŲŲdfd’•Í¨wG V̘ˇ`WŽ$ĸ(āæęĖ‹CŖVĢxyč›Ė™9ŊÂc ,\Ė•Ä+( ØÛŲņōKCP ķ,$33 }I AôxĒ+Â#Yģa#n.Îäå哚–Έao`eeɎ]{Øā .ÎΘ››•Õ‘’’Ę‚ÅK((( {×Î4j؀Õë֓––Nbb2Ī÷ī‹ˇwY™đˆ(–­\…‘NGaaũûõÆĪ×—ĖŒLĻʍ‘‘666čõ%eũ˜3o>É))ØŲØb0°°,Õ6â=Z<ԌŊžAúWدŦŦlfΞ‹Á`   ĻMĶŊKgĸ.^déōUčt: xĻg„×Ô[]ÎĖßŅ0$–ÍB9õwgØĢCøiÖ<ZˇlNhÃōíøqÆ\<=꒓›GRr Ą Ch×ē%……,YšŽœÜ<Š‹‹ ô÷ĨKûļD^ŒaëŽ}(ŠBŖ@Z=Ō”%+בC‰^ŋÛˇeÃ֝¤Ĩ_EŖŅ›—‡•ĪõîņĪ{y‘MÛw3ō­WHNIãךđų‡īv!‚ü‚žyĒ +Ön"ũę54 ææf„úŗkßArrķ°07#:ö2+×mÂÄĄ†ÁŦÛŧƒIß|VŽį#ĸØžk:Ŋ^ĪsŊ{°eį^ШAGOüÅĒõ[øö‹PŠTL™>›g{v 3+›o'˙ĖĮī āË ˙Ąíc-i×ēcØ{ā09šytëøūžĨŸÅ9 –臃ŊË×nÄÎÆwW—ÛŪĢ‚‚BV­ßBAa!Û­÷Sŋ^˛×ĸc/ßĩŦBÜo˙áEŖÕ••Í{ī '))™ŅŸ}Áä‰ãąŗĩcÂÄÉ\ŒŽÁÅÅ Wg^2€ī&N&<2’@˙ģ˙˜’’BDdãžú€ƒ‡˙$;;›ũûáâäĖk/ŋˆĸ(ŒŸđAūč´.]ēÄÛo ÅĖԌßį/äøÉ“4o֔5ëÖ3aÜט››1Áĸ˛:æÎûƒŪŊžÆ×ׇėœÆ~5Žņß|‰NĢ#--1Ÿ|ˆę–ûedf0x@ęׯĮ…đV­YĮčFąyÛv|ŧŊčÛģéW¯ōŅ'ĨÃī§Īœ%9%…O>|ŊŪĀØoÆãåU€â’bš„6&$8ˆĩë6Vدøø\œô|ôz=ÛwėFQvėÜC§ŽíiŪ´ iééDF^ŧ_oë= čۓŠĶgããéÁĻíģöęT*/英‰ņmûk5´Z-ú<MVvßNū™v­[˛{ß!,-,xņųgŅëõ|÷Ÿ_đõF§Ķr).žąŖGbnfÆæ{pr°gđsŊQ…)Ķgãīë…ZĨ"++›oŧ Ā~ųˆ‹1ecôķņbéęõ¤¤ĻãähĪņŋūæŅGšĄRŠØsāO^ü[wîÅÖÆš~Ī<EfV6Ÿ}3‘$Đ·+‰Iøųxą|ÍFzuī„ŋ¯7û[ÖË/(`Ųę |øÎ˜š˜p!â"Ë×lä‘Ļ‰ŒŽĨQƒ ÂŖĸņ¨ëΕÄdœí𖑉ģ[i`°ļ˛¤¸¤„Üŧ< ‹prp ōblix‰Š&Đßc##ū<v _oŠŠŠˆŽŊÄāįžá?ŋĖĻg×NøysōôŲÛÎŊ‰‰1mZ=•äd:>҆ȋ1e¯­XģéŽe…â~ĢŅđ˛uÛ.ũy´ėg >õn…ûzxÔĀŌĘK ėlí�°˛˛"7/K rōr™8y*FFF$'§“}Ī6888`aaÆ˙ų‘ƍŌ´Iclml8NNn.aįΐŸŸOrj*ΎŽ¸ģšafZ:˛bmcEnNI‰I89:–¸øsāĐa …đČHVŦ^SP´:×2�đņöž-¸”ļ˞5ëÖPX\Lvv.�—ãâéŌŠP:JäėTēæC|\<ž×ĨÕjđ÷õ);–bPđ÷+]gčNũ `ãÖmĖüm. ŅŽmT*͛5aņ˛DFEŅ0$„–-šßķœVÕī‹V`¤Ķ•ũčīCĪn°´0įŠ.øūĮ_yļgwlŦ­�°´´¸ãąŧŽNŦ,-ČËĪ æR?Ö�Fƒ—§—âđ¨ë†‹“#æfĨīYdT4šyų\¸Đ IMģZzܛæŠÔqsåJb2uއ•JÅc-šķįņ“<Ũĩ#§ĪœcøĐ1 (Ļ&&=ų,ņ°´0ĮÖÆk+ĖÍĖ((,BQŽ$%ã}=p†6 aíÆmåú–p%‰œœ\fū^ŒEĄ¨¨˜�?ovî=”Žú´kŨ’Čč˛srņõö,wŒ�?o.Æ\"77F!>vEQˆŧà úbeiÁēÍÛÉ/( ėBB‚ĐjĩĨmķ,‹æįãuwôeũĒFY!„¨Ž /;=Y᜗mÛw˛mĮN�Ū~ë �Ô7­*Šēu…IEáāá?š|9ŽėŌ”ŽT´Z ø>—.]æī3a|=nīŊ3 ###zwx’&ĄËíņb4Mųú•ë˙ŨĖpũ‘Æjĩ VĮˆáo•}I–Ģ_Wņ)žūë,Ūú*ŪŪ^ÄÆ^f֜߯×UŪzn¯ŋüĪēëáāNũ˙ÕX""#9qōk×mâëąŸŅ´I(ūœ;wžm;vqčđ†žúR…mŽŽŸī{Į9/™YY˜›™‘~=ėŨË͟å.̚ß5Ņj˙9˙:Žî[Ņ08°Üž›ļí*;Ī7Ž{kŪlŲ<”ĻͤIÃėą˛´ 3+33Ķë!FÁČČ(čëæR:“SSņõŽ_vœ‡­(ĐętZėílyįúPų~̈Ŋ‡ŖŊ>^õYžf#YŲ9ųû”Û/ĐĪ—ˆ¨h˛srčŪéIb/Įs)Ž‚‚Bėlm�hŌ(„S§Īv!‚nž,ësY˙Ģø¸îSV!ĒãĄÜ*ŨŠc{~˜0ž&ŒĮÃŖrwedfá`o‡Z­"))™č˜JŽĪ𛏏x:Œ‡G=z<Օ  �ââ¯āëã͑c'€Ō_ž /%3#ķŽĮqqv!)9™Üŧ<�΄+{ÍĮĮ›ŖĮŽĨ_Æ7_RLjĸ(dffáāč�ĀÁ?˙,ëK7W"ĸJG’“SHNIĀŨ͕ȋQ…ââb.„GTxė;õëØ‰“ÄÄÆPzéČ ';'‡õ7c0hŪŦ)C *:úî'ô>JMKįāŸĮųpÄœ>sŽø„D�˛ŗs(*.Žôqŧę×ãBDé9ĶëõDĮ\ĸžGÛ÷ķôāÄ_Ĩ—5EaÅÚMde—. y1EQ0 DĮ^žm™žõę˛|íFkŅ �3S22ŗPĢÕh´ōķ ČËĪgįۃeá&-ũ.ÎN¨T*ėˆŊĀéŗį¸•‹“™YY$%—žįQŅąė=x�_oļė܋¯'6ÖVddf{™�ßōáÅĪۓKqņ¤¤Ļãč`W}ļíŪWnD¤uËG8tô9šy¸ģē RŠpqr$:ö2�įÂ#Ëö-**"95 �•ZEqqųsw++„5ĨFG^6oŲÆŪ}ËmëÚŠmÛļŠōąZĩhδ_fđũ¤)8::ŌĢgV¯Ũ€ˇĪ]Ë9::°nÃ&öė=€VĢÁŌÂ‚ĐÆPŠāEK÷ŨD XÛX“–ž^áqŦŦ,éŅŊ+ãŋû;\]\ČĘ*Ŋlõ įųũ…9zœĸĸ"ēvîx×6ŠT*ž~Ē;“Ļüˆ•Ĩ:<ə3aŦ^ģž.;2}Ælž›8{{;ŧ<=Q…ƍrôøIžúæ;llmđŦīqë” �:ux˛Â~šššōûü¨U* …V-ÁÎÖW&O™†‰ą1E%%ôëĶģroHü6Iš[Ĩ^zžËÖl OĪn˜ššđ\ī§øcé*>ņ:ŋ/^Qá„Ũ;yōņGY˛j=?͚Gqq1ļhJ]w7b/Į•Ûī‰6­Xļz˙ųå7 ū>^X]ŋDegkÃ܅˹–‘gũēøxÕ'*:ļ\ųÍCY¸l5×G;t:ÖÖVÄ_I¤wŽüąt%ôíŲÕļ°jũfú÷yēŦ|ŸŨX˛j=ļ6Vųûĸže„ĪØØˆ!ú˛`ŲŒtč †˛‰ÃAūžlßŊŸ>=JāęâLJjffώC§-ÁđņĒĪKWņÖ+CĘöqt°CŖŅĐĸYč?m{ē++ÖnÂÆÚĒôÚõ‘Ą¸„D–ŦZΧŖ†Sŋ^6nÛŞ5m|ΞBQSTC‡Uf˘Qé§Â"qwqŦÁ&‰Ú !)•Đ`߇ŨŒûĸ˛ĪYYĩ~ vļÖ´kŨĒlÛÕkĖ[ŧ‚]:ās}>ËŅ›ôĢ×Ę„ ąļļÄÕŲ‰ØËņŦXģ‘÷ß~ŊFút7™YL›9—Ņ#‡ĄĶūWĖŲBü?u*,˛Jß%¯ŋū:nnn˙w ņß,3+›YķcogC¯îåįpŲŲÚđÆKƒØēs/;÷D­VĄŅh¨WĮMËĪ;RĢUĖ_ŧcc# }¯ßâü mŨĩ“ášgzHpBÔZ2ō"ĒåiäE!ÄÃQŨ‘YÛH!„ĩŠ„!„BÔ*^„BQĢHxB!D­RåđĸRŠ0ÜåÉĻB!„÷ĸ7Ę=5Ŋ*Ē\ĘÜĖ”ŒŦ 0B!„¨ŊÁ@|b*6Vw^ËînĒü Īē.ÄÄ%‘”’~×ĩeÄ˙žSaō(x!„U§VĢąą˛ ŽkõŊRåđĸÕhđ­_ņ"{B!„5M&ė !„ĸV‘đ"„BˆZE‹B!j /B!„¨U$ŧ!„ĸV‘đ"„BˆZE‹B!j /B!„¨U$ŧ!„ĸV‘đ"„BˆZĨĘË”čõÄÄ%‘——/‹3 !„ĸĘ4j5Ö××6ŌTceé*‡—˜¸$ôÎNö¨UĒ*W(n—”Jh°īÃn†Bņ@ÜXU:>1wį*—¯rÜÉËËĮÆĘB‚‹B!ĒEŖVSĮՑĖŦœj•¯rx1(Š!„Bü+ĩŊÁP­˛2aW!„ĩŠ„!„BÔ*^„BQĢHxB!D­"áE!„ĩŠ„!„BÔ*U~H]U\`鲕ddfĸŅjđķņĄ˙sĪbeiQ­ã>r„V-ZTĢėĨËqhĩÜŨÜĘmßąk;vî 5- [[´-nn.ŒöfĩęĒ)cbŅiĩÔĢ[į_—Ũ¸e;………ôîųT…û˙}ö‹—¯âÛ/?ģã1ŋøfžíM€˙ũyĀŪ…đHæūąˆ ß|q_ŽWģ÷ā‰Į[WĢlNn.į/DĐŧi(�ßL˜LRr2ãÆ~†ĩ•eŲ~ŲŲ9LųųWōō (*,ĸYĶÆ xļ÷}iŋBüTcá%*:†é3g3üÍĄøúxŖ×ëYˇqS~ü‰1Ÿ|„ĒŠĪŠ)**fĶ–íÕ/ĮNœÄŖnÛÂK‡'ÛŅáÉv�|øÉŪöuŨŨĢUGM;pčŪžÕ /ˇ–=qę4C>÷¯ÚķågũĢō[QQ1+V¯¯vx‰ŒŠæØ‰S4oŠÁ päØ Ö.[€Z]ūŗŊnĶę¸ģķŌā oŒÅ#MCņöōŧŨBˆ˙wj,ŧlظ™ž=ēáë㠀FŖá™§{ĐļMkT*‰IIüąp jĩšü‚ztëBãF Yģn#é׎ Y™Y›˜đæĐW˜ˇ`!Š)iü:s6o }…Í[ˇqúīŗhĩZ,-,xađ@LLŒykÄHēvîDJJ*ņ ôī÷,j8p˜ŗ6Včt:7jxĪö‡;Κuøtô�\ILdĘ´_øöëą 1Šž ==´ôtú÷닷§'gÃÎąaĶŒŒŒ())aȏ¸TũąĮ9w>œí;÷pÂÎ###7lĀ´_gqíZ%%%4n؀ūĪ>Ãęu‰ŽŊĨo‘’šĘĮŸÃ¯žXŽlĶĐFÄ'\Ą~ŊēĖ_´”Ĥd´Z-ŲŲ9ØÛŲ2ėõWËÕ}öÜæĖ_ˆą‘…ŧúŌ ‚øčŗ/2đ9 …EËVR×ŨÜÜ<’’Søüã÷ąļļâŗ/ĮĶņÉv´mķhšcÆÅ'0õ—™ ŒŒųvé(—^¯gĘOŋƒÁĀįŸ|€­56ocįî}ĄVŠøpäÛØÚÚđŅg_RßŖá‘QLøf,ŗæĖãr\<ŠNŽŧûö(ĨÂs5í×Y$%%ķũäi4 mÄĒuËĩQ§Ķ2åûqĖŋ++Kúôęˆ÷?fčË/0kÎ|ō pX𖤔 _Ž˙žQīŧ…•å?#/V––DĮ\ ¨¨ƒÁ€e5G…BÔ`x‰OHā™ž=nÛngk Āŧ?ŅŠc{š4nDjZ:_ŸĀÄīžAŖÕššĘGī`ô§_˜H÷Ž‰ŊĖC_áBx$gÃÎ3úƒ÷�Xąj ;víæŠn](.*Æ×Į‡Ũģōį‘cėÜŊ›ˇ^ ?š5 ­Tp `ūÂÅ$%%ãââĖ‘ŖĮyŧõch4  $ĀߏŗaįX˛l#G cūÂŌķ1fĻfœ ;Į‚ÅKyäˆûr>ƒũ  õŖ-x¤Y-]‰ģĢ+ŖFŧ…ĸ(|đÉ4nB¯Ũũųלūû,Ģ×obč+/Đŧihš˛gÂÎčī€Z­æęĩ &|ũ9�īü9Ÿ C}ĶBYW¯]ã­×^ÆĮۓ3aį˜ŋpišK;:­–¨‹Ņ|öŅ{˜›™1mú,>B÷.õÎ0LMLnëĪÔ_fŌŋī34kԘģ÷ą{ß…{)Ž?‰Ŋ-“§MįĀĄ?éŅ­3šyy|ũÅĮX˜›ŗpé 6nŨΠūĪĸĶé077cĘ÷ãH¸’ČŲsøõĮ�Øšg™™YlÛąģÂsÕ¯wO"ŖĸųđŊˇh˙Äã•~?LMMčŪĩ—.ĮҝOOôz=;wí­p4Ē{—Ž|:v¯ INNŊžîŽ“ŖcĨëBQ^…ĩZá.ũŊCP€?�Žö˜™š’’𠀎ˇWŲ~66Väåäbna^ļíBx8III|7q�……ExyÖ/{Ũ×§´ŧĩ599šÕjŋJĨĸŨã­9pč}{?Ãņ“ņáMA䯈’‡G=âã¸|9žėŦ~üi:�ƒBQQQĩꮌĶgÃČÎÎáÔéŋŌ5§Ž$%āīË{oŋÉŸŒĨIhCZ4kr[Ų§NĶ4ôŸč_ö˙Ū^žÄ^ē\î|:;9˛pÉr� ŠŠČĖĘží˜uë`nf€­­ ŲŲĨëUØÚXßļ¯ĸ(\¤Qƒ`āŸĐp!<zuąˇ+ ¸NŽäææĨŸ‘ī~˜ŠN§#%5­\›ƒŽˇĶ +K >ûr<-š7åŅ–`og{Įså{ĶįŦ&­XŊw7Wžũj yyų|đéXš4nøĀęBˆ˙55^ę¸ģg}rÛ˙>s–ā  *šōrcÍ$ĻüMP JšŸuF:š4 e`˙~Ö­Ņhn*{ģé3~ãbt4Z­ŽīÆ}yĮ>´~ėQž?æM›âėäˆõM_ăFƒbPPŠTčŒt88Ú3úƒQw<ŪũdldD¯ĪŌĒEķÛ^ËÉÉÁÄĘ´´tEšm~ŅŠŋΔ›¨{sČ4 Üúæ|;q ŖGŊC€ŋ/QcøaęΎÕyķ9‡ÛßŗŠö7(ˇīŖÕŪ~œ¤äfĪ[Ȍ'aaaÎĒĩIJN.ÛG§Ķ••ũ~ÜX.FĮpėÄ_ŒüđSž3úŽį*.>Ąė˙wîŪwĮËFˇžŊūîkqŦYŋ‰5ë70fô(NŸ9[vžÍĖL āü… /BQM5^ēwëĖ”Š?SĪŖAūčõz6lÜBØųķ4 ÆÛˋ°sįiÚ$”Ô´t ī:”ŽVŠ)*.Éđķņa֜ßéĶĢ'&&ÆėØĩwwˇ˛K!RŠ(*.āÍ[æt܉Ĩ…Ū^ž,Xŧ”Ũģ–{íü…p6!<"‚zuëāîęJfF WŽāîæÆ…đ⎔MžTj…×Gs‚ũŲ{ā0­Z4GQfĖžĮs}zaaağfđÉī˛nã6lŪFnËĘfdfĸVĢËÍÉøûė9 P8w!‚6ĩ*{MQŽ]ËĀÅÅ (ŊSRRRé6_ËČÄÔÄãúĄRāĮŅc'iķXKöü“ ‘´y´åŽ‘…š9ærđđ<ęŨ>i9&öcbéđD[ŧŊ<š’”DLėå;ž+ĩZ]6:Öū‰ĮīxŲČœk™�degsåJ"@šō7ëÕŖŊzt+ûšŽģį/DĐ4´ƒČ¨‹´|¤Y%Ī Bˆ[ÕXxņöôdø[o°dŲrŽ^ËĀÄĘ�FކJĨâÅÁųcŅvīŨOaa!¯ŊōbŲ_ĐąˇˇÃHĢãËožeĖ'Ŗyĸm&ü0cc#ĖĖĖyėŅģ߅čĪōkĐë <ŪúŅģî{ŗ6=Ęėšķ .ÛĻŅhˆŧx‘]{öq-#ƒÄÄĄĄ¯žĖėš`ll„^¯gČ •ާ27 aîüÅčõzõčÆĪ3įđūĮŸc0hÔ°ļļ6Ė™ŋGš5ĄžG=^yq#F}L“Æ Ëʞ=wĐÆ Ę×ÉŅ‘ “§’ššFP€ ‚9v( úõáŗ/ĮccmM¯Ũ8~ō/,^^Š6Ošú3Úˇģíœ{ũĻū<“Õë6 32bäđ7ȸūŊû ŒĸZØ8ūߖ^ é@HBīˆ‚H‘bAÅ‚H/ ŠŊ] ŊÖW¯Ø@D‘Ļ‚‚(ĸéB€@éôŨ}?"’�ĢĪīŲSvv–}öĖ™9§kEHP S{OOîŧm¯ŋ=ƒõ›ū(ˇ]PP sŋXžīWbļ˜ņöô¤k—N T¸¯<<Üq˛Xx`Ęcŧ6í…3Ž:ŠGˇËyá?˙eúkoãååIxãF`ˇĶŦIŗį~Á;3g1väŨ•îƒÁˇß›īĖäҧž§¸¸˜íÛŌŽMĢJˇ‘ŗ3Œ3Æ>cƌ*Øŧ#–Đ ÎdÚ_˧^ŊzôëĶĢėącîeÖĖwk­#I)´kQ;÷JŠŽŲŸ‰ÉdĒŗûŽüü˯xzxčËZDDΰyGlĩž˙ƎKHHˆî°[™ŒĖLž{a™™ôéŲãbwĮa9Y,´lŪėbwCDDūFęôģŽĖ×Į‡§¯ø&lĩ9ęrąŨuĮ­uZŋævˆˆHmĶȋˆˆˆ8…q( /"""âPĒ^ C…7Š*ĢÍVn)šę¨v)w7W2ŗs`DDD¤FŦ6‡ĻāãUŗEjĢ}ĩQxà â’HJ.ŊõŧԎÍ;b/vDDD.ŖŅˆ— ‚kv߸j‡ŗÉDtãĐ5&"""rž4aWDDDŠÂ‹ˆˆˆ8…q( /"""âP^DDDÄĄ(ŧˆˆˆˆCQx‡ĸđ""""EáEDDDŠÂ‹ˆˆˆ8”j/PbĩŸD^^žg‘j3xŸXÛČTƒ•ĨĢ^â’°Úløa4ĒŨ \G’Rh×"úbwCDD¤lUéÃGS ŦvųjĮŧŧ||ŧ<\DDD¤FLF# ‚ũÉĘέQųj‡›ŨŽā""""įÅd4bĩŲjTVvEDDÄĄ(ŧˆˆˆˆCQx‡ĸđ""""EáEDDDŠÂ‹ˆˆˆ8”: /3ŪŸÅĢV—{ėˇõyí͡kŊ­cî­õ:/%Įķōøsëļ ŌÖ#O<ˎ]ģ/H["""5Ą‘pāĀAļnÛ~ąģ!""rI¨öō�ĩeÕę5Ŧ]÷‹ģŨƨÊīįĮö;ųféw899QRR°!wČËĶ_ĨAh(ûãđč#“ą˜ĪėzVf3g}„ÍjĨ °nW\Nīž=ØŊ'–Åß|KHP yyų¤¤Ļ1ņžqxx¸ķҧŸqäČ<==ŠįëCũúõšĻßrõž0m:ÃÂ(Č/ %5…Ë:wæĒŨY¸äkRSĶ8zôƒī¸…œÜã,]ö=ÎÎÎX­Vîžk0AA•–/**æãŲsČĘĘÆZRBķ˜Žŋn�ģöėå›o—aŗÛiÕĸë~ũ•‚ÂBÜÍĒUĢ™öâķ¸ēēbŗŲ™üČc<6u2õũüjõũąŲlŧũŪ˙8”pģ6aâøąėØĩ›ĪžøŠ†Ą!?žGŌądžzt2ŪŪ^|ŗl9߯øoo/Z4oÆŽŨ{yîÉŠĩÚ/‘‹^-ų–įŸ}Ovī‰%##77W>™3—gž|7W7ļīØÉėšķ˜<i"fŗW7Wžzŧō/ÃԌtŽęŅNÚSXXȤ)SéŅŊŗ‰ƒr˙ø1¸šēņŅ'sØôĮøúø””ď>Laa!O:BĨ˛�� �IDAT<ķ<Ŋ{ö8Ŗ^“Ņˆģ›Cüü|Ļ<ú]ētÄbšĘ“=LAA<ū4Ī?õ8Ū>Ūüē~=ŸÍû’˜PiųåË$( Ņ#†cˇÛyqÚ+4iŠ“ŲĖūøxūķŌŋņôđĀÅʼn#GtÃõdff˛aÃ&zôčÎîŊ{ Žõā“CƒĐP&ŽĀ#O>Įöģp˛XØˇŸ'yw77Ūz÷}Öūēžn—_ƧsŋāƒwŪĀÃݧŋŽAwb‘:pÁˁŌ/´+ēvaÚ+¯ŅŠC{ÚĩmCŖ† ØŊg/9Ųšŧųöģ�ØlvŠŠŠĘĘ6‰Š:kŨõũüXšrkÖŽÃd2Q\TL^~>�Ą!!¸šēāíãÅņÜ<rrri…Á`ĀÅŅÖ-[TZw“čŌļ]]]ņöö!99€¨ČH ‰‰GŠį냡7�1M›2gîg-ŋkĪrgĮŽ�äįįs,%…@B‚ƒņôđ8Ŗ={\ÉėšķčŅŖ;ë7lĸĮ•ŨÎēOjĘËĶ“œÜ\朝IL<Jvv6õũükØ�wˇŌ}éëëCNN. ‡Đ $w�ē]Ū…•?ũ\'}‘ļ: /^Ū^ää”_p)33 W7W�nŊå&zöŧ’mÛw0ãũYôŊē „RßߏŠSǏŗËYÛübūBB‚šîšū�Ü;aRŲs&Sųé=vė`N8ÛHíÔõlļ˛m͖ĘwáŠk@UTŪÉɉA}zŅž]ÛråââöcФŪđÆaØJŦILdßž8†žŖŌöĪĮĘU?ˇ?žįŸz ŖŅĀŗ/ü§ė9“ÉTn[;vėvûŠģRë_‰ˆHŠŗ ģmZĩdŨoëÉĘĘ =#ƒU?¯áŠŽ—QPPČĸ%ßPΎ={\ɀūWˇ˙�ĄÁÁdefr$1€Ũ{öōÏĢĒÜfVV6ūūõøuũJŦ%X­%•nHÜūx� ŲļcgĨÛî<qNff&Y™øû—{>$4˜ŒĖ,223Øĩ{/g-Éúŋ`ˇÛ™3wY™Yg´m4)...ûûĒĢēķŅ'shĶē%fŗéŒíkCzF&ūŽ$e÷ŪXŠK*ߗĄ!A$I,éZ÷ÛÆ:闈ˆHŧ4iÆõ×`úko`ŗÚ°XĖ\Ũ�Zļh”ŽD<˙â˸¸¸`ˇÛz×`\\\3j|ø)ÎÎNX­V†Ũug•ÛėÛ§ķž\šĩëhÕĒ%;u`æûŗ¸eЍnßž][6múƒį^˜†_=_šFGc2VŠŠŠxgÆûM:ÆíˇŪŒ‹‹sšį]]\9|(īĖxg§Ōįî:äŦåûöéŧŸ}Î /OĮfŗĶ ooRĶŌĘÕÎÂ%_ķ霹 r'—uîÄėĪæ1rø°*ī›ęęye7žųU{úßrםˇ1{ŧ{H…Û×ķõeĐĀk™<õ)ü‰ãĀÁ„:럈ˆüsƌcŸ1cF• lŪKh˙š7t�ššĮųsÛ6Žčz�˙}ãmúôēŠÖ­Z–Ûîåé¯2证4‰ŽŽQ;į[ūt›˙Üƚĩŋ0ņžĒßßæHR íZÔNû•Yņã*.ŋŦ3înn|š`19ššŒVqØŲŧ#ļZßMcĮŽ%$$äâ]mt)puuaįÎŨ,˙áGĖf35¤ÕY&í^ Ūzg™YYÜ7nÔÅîĘrs3åągđôpĮŲŲ™‡&ŽŋØ]‘ŋĄôČË?ɅyŠŽšŽŧčģ"""âP^DDDÄĄ(ŧˆˆˆˆCŠvx1 Øėöē苈ˆˆüCXm6ŒÆšĄTģ”ģ›+™Ųš 0"""R#V›ÃGSđņ:sœĒ¨öĨŌá ƒˆOH")9 ģŒCŲŧ#öbwADDŖŅˆ— ‚kvõrĩËŲd"ēqh9_š°+"""EáEDDDŠÂ‹ˆˆˆ8…q( /"""âP^DDDÄĄ(ŧˆˆˆˆCQx‡ĸđ""""EáEDDDŠÂ‹ˆˆˆ8”j¯mTbĩŸD^^ū%ŋ˛ôņÜãŦ\ĩŠÔ´ô‹Ũ•ręûÕcâ˜{đõõžØ]q8Õ/ņ IXm6ü0 uҧZķÚ˙-$''Ã%ÖĪ”Ô4ۘ1‹gĻNēØ]q8Õ>m”———Į%\�rss/šā`0HKΏØŨqHÕ/6ģŨ!‚‹ˆˆˆü=iÂŽˆˆˆ8…q( /"""âP^DDDÄĄ(ŧˆˆˆˆCŠŗđō霹,û~y]U/"""˙Py‡Rí;ėž¯ŦĖ,fÎú›ÕJAa!ŨޏœŪ={°{O,‹ŋų– @ōōōIIMcâ}ãđōōdåOĢųyÍZ<==iÅž¸ũ<øĀ„Zë“_=_nģņ:ėØqqvfųO?ŗsw,ũû\…¯7ĀÃÃÂĸB>ūl~š˛]:ļãŽÛn*÷ØúM›™ũÅÂZ럈ˆˆü傇—ÔŒtŽęŅNÚSXXȤ)SéŅŊŗ‰ƒr˙ø1¸šēņŅ'sØôĮtėЁ…‹—0í…įqwwã˙Ū{ŸÚžGŪā[oäÛīW˛˙Ā!ÜÜ\™ú¯ņ<˙Ÿ7°Z­Ô¯įË[3?ā‰) ¨ĪąäÔ˛˛ë7m( 0 ."""u낇—ú~~Ŧ\šŠ5k×a2™(.*&/?€ĐÜ\Ũ�đöņâxnG&„ģ{éã;´cŨ¯ŋÕZ Qáa\ßŋOŲc%%%x{{0Ąėņœœ\ÜŨÜΨãd€‰ŽhŦā"""RĮ.xxųbūBB‚šîšū�Ü;á¯Å MĻōSpėØąÛíåFZj{iģŨN‰ÕĘĖ>#ŋ āŒįm6[•ęYŋisYˆ‘ēsÁ'ėfeeãī_€_×o ÄZ‚ÕZRéöAūMJ"ŋ ttæ÷?ļÔzŸöĮĸ}›–�x¸ģsۍ×Õz"""R;ętäeŲwËYũķ/eß5øvúöéÅŧ/°fí:ZĩjIįN˜ųū,ntc…uøøøĐīę>ŧđŌtęûųҰQNŦÕ~~ž` wŪ<m[áädá‡Ukkĩ~Š=†1cÆØg˘Qå›wÄä_‡]:͚_ÖŅĄ}[Ü\ŨøvŲw?žĮmˇ :gšį§ũ÷ôŽæŪúĪsģ """cėØą„„„\ø9/5‘w<§ŊЇģNNΌēįî‹Ũ%šH"ŧôëۇ~}ûœ{CųÛĶvEDDÄĄ(ŧˆˆˆˆCQx‡ĸđ""""ĨÚáÅ`0`ŗÛëĸ/""""įTíđâîæJfvŽC‹Ũ…JųÕķŊØ]qHÕžT:ŧaņ I$%§aŋÄĖĩũúōÃO?‘–žqąģRŽ_=_;âbwCDDÄ!U;ŧ˜M&ĸ‡ÖE_ęDˇ.m/vDDD¤iÂŽˆˆˆ8…q( /"""âP^DDDÄĄ(ŧˆˆˆˆCQx‡ĸđ""""EáEDDDŠÂ‹ˆˆˆ8…q( /"""âP^DDDÄĄ(ŧˆˆˆˆCQx‡bŽIĄ¤<+/˙žÍļôŠmö*•ą ØīÄŧúCŒšé˜LFÚˇkĮC“Ā××įŦe7lÜÄäGeȝw0vôČjõõˇõxû÷˜ũņ,vîڅÅb!:*ĒÜ6w ÆC“ SĮÕĒ[DDD.ŧ…——~ĪĻ•ˇĮ[:a1ĒTĻØfgÁliúoö¨ÕjåÕ×Ū`čđ‘,^đ&“ŠŌ˛ŋŦû•[oTíārēeß-'&ĻŲáEDDDGÂËöôž¨FpŌ‘—A-Ė;hãĐĄ"##xlęÃÜ0čV~Zĩš>Ŋ{ąfí/ŧķŪL\\\())ášgž"%%…%_‹ÉlÂËĶ“˜˜fL›ū*...äåįķØ#SčÔąĶĻŋJ=__FĀĀ›náåŸ/kĶīđåW đ÷ĮÅŲ…^={œŅGĢÕĘO?Ëž}qØív"#"xé…į0´éЅ1ŖFpđā!öÆÆōØÔ‡éÜŠ#ŋ˙ą™įūũ"žžž\ŨģĶ˙û:ÛˇlĒÉn‘*¨Qx)ļŲ˂ËĀ ĒTfI/,F%v(((-c0¸ŦKgvīŲK—Νxę™įY˛đK<==YûË:žyîß|<ë}Žģv�nnnÜ=ė.ž]ēŒgŸ~’–-šŗ~ÃF^}í >ŸķÉ9ÛīØĄ=:vd@˙ž€ŒĖĖŌĀōīį�¸ķŽģŲ°q—uéLaA:vāž{Įōõ7Kųdögtîԑ§Ÿũ7SžLˇ+ēōÉėΰÛĢvMDDDjĻFáå|úŸƒ¯¯/;wí&--Ņãî+Û&??˙Œ˛ BCyã͡Č/( ##ŗÖúåëãCVVÃ‹+$=#ŖėųöíÚāī_ŸŦŦ,ėv;{ccéÜš#�× čĮ´é¯ÖZDDDäL%ŧœdĩZŲôĮ\Ũ5¸8;Ķ aƒsŽĸLxāAŪ|ũUÚĩmÃö;yhĘT tįT%VkĩûŗpņļīØÉGŧŅhdôØņåž7›˙Ú]'˜ŨnįdËFŖ.ŪŠkíÛ˜ŋ4ā ޏŧ+QQQ$K&vß>�ÖoØČ'ŸÎ)WÆnˇ“’’BÆ �X°p1ÅÅÅ�x{{“š–@FF&<ŖMŖŅ@aAaĨ}JNNĄAh(FŖ‘øŲŧåΞú+b0hÜ8ŒÍ[ūāûīWTcˆˆˆHM\”‘—ûî˙f' Ũģ]Áģoŋ€ģģ¯Ŋú~ô Ü\])))áųgŸ.WÎ`00áž{š{Ähęûų1bø0V¯YÃkoŧÅmˇŪĖ}÷˙‹I=Œ¯¯/͚6åôé'—wíĘ´W^ĨÄZ­7:Ŗ_7 ŧŽqãīgčđ‘4l؀I˙ēŸ×ßx›íÚUúZž|l*O>ũÁÁAô¸˛æŗ\5%"""įĪ0fĖûŒ3ĒUčʅÉ,éåToÂîÉí߉NĄeËÕëé%jí/ë It[ūü“gŸ‘…ķį]ėn‰ˆˆüíŒ;–‹;įåīĀh4ōāäGpwwÃjĩōô“]ė.‰ˆˆü­)ŧœ§Ëģ^Æ7‹ŋēØŨųĮĐå1"""âP^DDDÄĄÔ贑Åh(ģËîɉ¸UQlŗcŽúŠ""""g¨ŅČKĢzf˛VyEi8ą0ãĄš¸—œqC9‘ĒĒŅČËÔ^<ģ.™šņ&Jǘ_Ėhę^Â˙l\\Ē>Z#"""rĒ…— 7oö¨ĪĄC Ty1BƒÁ€‹‹ 5ŦIŗ""""5ŋTÚbąQ›}9']m$"""EáEDDDŠÂ‹ˆˆˆ8…q( /"""âP^DDDÄĄTûRéĢ•ø„$ōōōąUņū.""""'™ŒFŧŊ<hėÉXũq”j‡—ø„$Ŧ6~u›˙Zw$)…v-ĸ/v7DDDęŒÕfãđŅM!,4°ÚåĢwōōōņņōPp‘14ö'+;ˇFåĢ^lvģ‚‹ˆˆˆœ“ŅˆÕfĢQYM؇ĸđ""""EáEDDDŠÂ‹ˆˆˆ8…q(ÕžĪKU}:g.7ũŽ››{šĮĮIãÆjĩ­ ˙zˆˇ_•…KžÆd41đēkj\׎={YōÍRyč_•ļS]%`6› ŠqŋNˇyëfĪ[€WšĮ{vģœn];Uģžĸâbžųî6oŨÉhÄh2ŅĩS{ú\Õ ƒÁĀīÍâē~Ŋ‰ ĢqŸ'>ō4oN{ļÆåOU\RÂÔ§_âÕžŦ•úN———Īkīüā �††`4éŨãŠZmãpâQĖ&AŦøiM´QuŊOEDjK…€ũû2 _ßēl FâBļŗņ÷?kØ Và @td8ãFÜU+u}đÉįÔ÷ĢĮS?€Åb!''—™ĪÅÅřî];×JŽäđŅ$ŧ<=q×íuÖÆ–­; &(0€Ģ{v¯ŗvDDūnę4ŧTÕâ%ߒ’–ŠÉd&!!öíڒ“CVfŲ99<ô¯‰>žũ‰‰GąÛ!$8áÆb41æ^fÍ|ˇÂēm6;ŸÎ™KâŅDėvđĢW÷ cŅĸ¯ņđt/ WĪ<˙"÷Ü=€âĸbfūīCŌŌĶ0 Œ;//ΞvŠŠŠųxö˛˛˛ą–”Đ<&†ë¯�ĀÂÅ_ŗåĪ­ ēvéLDxcÖŽũ•í>^X,Úļi]įûķ§5ë8œ˜ÄĐÛ‘ž‘É[3?dōũcI:–Âį žæņ‡&”ÛūĐá#=–ĖØ{†`<q›fOOî;‹ųĖCdåę_Øąkfŗww7îŧy ķ~CëæÍhĶĒ9~ß‚¯ŋãĨ§Á`0đúģpë א–žÁĖ>ãŅīāŲi¯ŅãŠË¸Ē[WöÆÅŗrõZ%áéG&áââŒŨnįŠ_eŌøQdfeķåâoŠįãMhpP…¯Ŋ°°ˆŲ_, #3 0pũ€>4Š`ÛÎ=ü°j ÎNNX­Vn4�?ۜņ!áa É=žGŌądÚĩnI—mY˛tiééĖŋo/OŒ&ũ{÷āá§_¤CÛVXKŦtîЖĨ+~"(П´ô œ,"ÃÃ8’˜Ä‘ŖĮ¸gČ­ø×÷cÍē Ŧ˙} N3ƒģßJjZ:ŋmڌ—į>œ,&.kŖ˛ž>üԋôžĒŠŠi$&%3čúūgŒ„U÷õī?p¨Â}Z\RÂį_-!''—Ģ•ĻQôëŨŖĻ‡¤ˆH­ē$æŧ˜Ė&˛ŗs¸gØƌŧ‡ų Ņŋooî; ėvâöĮ““›Kp`OÂN!9%•=ąąįŦ;99™ŊąûxôáÉ<öČdZļlNNNÎYËJ8Ä-7ßĀŖO&4$„?ūXîųeß-'( É“&ōđäIlŨž¸¸ũėÜĩ›í;wņôōøÔ)lŨžƒ€Ā�š6‰âÚũ.Hp¸Ē[W222Ųģo?_,ú†›^ƒģ›a C™0úî3ļ?š”\vjäTN †ĶnH¸o˙víŨĮÄq#?jõ|}XũËzbšDģ˙��{öí'Ŧa(‰GQ\\LFfĄ!Ĩ_Œ~õ|).)áx^i™Ô¯Ol\išŊûöĶĒy3Z5oÆn v˙‚¨įëÃüÅKša@_Fß=˜ Ā€ _ûŠUk¨īWÉ÷eČm7˛æ× 2wū"FŊƒņŖ†ŅĩsžZ˛�ŗÉ„ŲlæÎ›2rč|ŋr5ŽŽ.\ßŋ7 CC¸ķ–ĘÕ_R\B›Í|던Í&ãĻkûqīˆĄÄLĀŨŨ!ˇŨDĢÍø}KékČ/(āžQØ8nQYûë"7"2<ŒĢ{v§EL“˛úĪÖ×ââb"7bČm7ŅëĘËYũËúķ~ũ•íĶV­% žãG ãū1ÃŲš'–‡*Üį""ZŽŧ|ŋüGÖũļĄėo į’�„…5ĀĶËOęųÖĀËˋãyĮņôđ 7ī8Ķ˙ûNNN;–Lî9B@ũúõņđpã•×Ū¤m›Öthß_Ÿŗ–iÚ Ŧũ&MĸYŋacšįwíŲCîņãėØĩ€üü|ŽĨ¤ššFŗ&ŅFŒF#S|āœũ;qņyųĩwĘ=6zø`ü|}rû ^įÄ4‰ĸeLS�Ėf3Ū^žgÔc4ąÛ˙ēËáÆÍōõ˛�đôđ`ĘÄąeĪÅÆÅ“œ’ĘīÍ ¨¨˜°FĄtíܞ•ĢāXr*Wu쌨ũņää':2ŧ\{͚DãĮķhĶ2†_7ūŨn'6.žģīŧ…F BųrҎ\ŪĨ#ŋoŲÆå]:bˇÛIL:Fdxé|Š&Qî“øƒ ôéŅ €āĀ�F Ŋƒ‡đņöÆËĶ(=ŨöåâoËĘDœ8öŧ<=ČËĪ?ë>ˇÛíDEü5ÚP‹Å€‡ģ; B‚K÷›ģ;ĮRRđõņf֜yXĖfŌ3˛ˆ¯|ÎWRrōŲûzbž˜—§yyyįõúĪļOc÷íįx^>ģcã€ŌP•’šNãF ĪēDD.„: /ũúöĒpÎËō+YūÃJ�î? Ü¯~Ãé+LÚíüōëo:”Pv éõ7˙¯J}0›M<úđd<ÄÖm;xū…i<øĀ}pÚ §Ūĸ¸Ü„ŨÎé;991¨O/Úˇk[îņ¯—~‡ũŽ´V霗üŧ|œœČČĘÂnˇŸ1‚rǐā@}û=Å%%XĖf:ĩkC§vmČĖĘæ­™–ÛÖbąĐĻe 7<sR´ŅhāĀĄüũęҘ/}KvN.͛F•Û.ĻI4{÷í''7—kûöâĀĄÃÄL   zž>ÔķõÁfŗ’t,™ũqۍĨ§œNŨˇöJn)m4ĒôœēÄÅŠīwUƚO9•vúh•ŅpęßvŌŌ3Xôí÷<>ų~Ü\]ųņįu¤Ļ§Ÿŗ*õĩĸmĢųú+Û§‹…kûuĨu‹˜jõUDäB¸(§ú^Ũ›WĻŊČ+Ķ^$,ŦjWefeS߯FŖ¤¤cė§ÄZrÎr ‡YģîWÂÂqũuhŪŧ ‡qsw#+Ģtä&'7—äcÉ•9|˜ėœŌÅĸöÅí§QÃåꌎŠdũÆßŌ˙üįĖGVfMĸĸØžs'%%VJJŦL{å5RĶŌĀ` ¨¸¸J¯ŗ6”””0įËEŒz;õ||øyŨ†˛Įŗ˛Ī­ "<Ŧ!Ÿĩ„ĸĸ" tÎÃī[ļáâėRnۈƍ؞koŲvĢYOl\<�MŖ#ųnåjĸŖÂņņö"3+‹ũŅ,ē|xiÎÁ„Ã$§¤á_ߏ¨ˆÆ,˙éįrŋüģ]։š_-ĄeLL&ƒ �ö8ĀÎ=Ÿ2ŒlÆö]{�HĪČäõw? (0€Ŧėė˛×ž7n?áaf!;'WWÜ\])**bËļXKŦ� ŠO;.Ρ¯ÕyũgÛ§áaüže;PzŒĪ_ŧ´ė3!"rąÕéČË˛ī–ŗúį_Ę=6 ozô¨ū•]ģtâ­wfđŸW_Įßߟo¸ž…‹ŋ!*2ęŦåüũëŗä›ĨŦZŊŗŲ„§‡íÚļ!÷x.˙÷îLfŧ? O4ģģÍFDxcžœŋ€ŒĖLŦ6ãĮŽ*Wgß>ŊøôŗĪyáåéØl6bbšáíãˇ7íÚļáų—ρŨN—Ν¨īįGķ˜Ļ|9VĢ+ģ]^í×^™Ø¸xžŸūFšĮ"ÃãîæJ˘&„rãuũøĪīĶ$ŠœÜÜ 'ė |+KWüÄķĶßÄb1bšD1˛ҏŨ.ëÄī}ˆ““7WWēt,jŪ4š?­áæëKGe‚ƒINIÅÍÍĩ\ÎÎNXĖfüęųҘOį-`üČaeÛthך/}Ë]ˇŨTöØÍ0ņR|ŧŊˆƒ F“zvŋœ9_.╷f`ˇÛšŽ\œrëM˚=g''�nŋy`÷ōų kŠŋŸ¯ŋûnŽŽôī}ŸÍ_Äö]{hÁ’e+°2âqž}­îë¯lŸöėŪ•/~Ãkīü›ÍF͍ˆ˛ĶN""›a˘1ö3fTšĀæą„ų×a—.MÅÅŌŋīŋ÷vļs$)…v-ĸë´ G°mį~Ûø;Ŗī|ąģ"""udķŽØj}į;–KãRéKÍfãų—ĻŅšsĮ‹Ũ•„˙}2—Ŧė\F­ģ{ŦˆˆˆãRxŠŖŅČsO=qąģņ1j؝ģ ""r ģ$îķ""""RU /"""âP^DDDÄĄ(ŧˆˆˆˆCŠvx1 Ø.ā]dEDDäīĮjŗq—ōĒĒv)w7W2ŗs`DDD¤FŦ6‡ĻāãUŗ›_VûRéđ†AÄ'$‘”œvA×ņų'ŲŧãÜĢe‹ˆˆ8*ŖŅˆ— ‚kvĶÛj‡ŗÉDtãĐ5&"""rž4aWDDDŠÂ‹ˆˆˆ8…q( /"""âP^DDDÄĄ(ŧˆˆˆˆCQx‡ĸđ""""EáEDDDŠÂ‹ˆˆˆ8”j/PbĩŸD^^žg‘j3xŸXÛČTƒ•ĨĢ^â’°Úløa4ĒŨ ˆˆ#:’”@ģŅš'"ޝ¸¤„ĤT“RiPíōՊ;vģŧŧ||ŧ<\DDD¤F,f3 BČČΊQųj…ƒÁ€ÍnWp‘ķb2ąZm5*Ģ ģ"""âP^DDDÄĄ(ŧˆˆˆˆCQx‡ĸđ""""EáEDDDJĩoR'""•Ë=žĮŖĪžL€ŋ_šĮ#Â1äļ›ĒUWŌądfąÉ÷`í¯YųķZŦVfŗ‰Žm[ĶŋĪUkp‡Ōŧŧ|^{įĐãŠËXšúÆ \ízÎæpâQĖ&AŦøi FŖ‘Ū=ލÕ6ĒŖ¸¤„ŠOŋÄĢ/<YĨíãâVkŋ¤ĻĨķŪŦŲÄ4âæ×PT\Ė×Ë~`ķÖí˜M&\]]ésU7:´m@QQ_÷›ˇîĀ`0āėäD˙̝ĸcÛÖ�|ąčļlۉģ›�înŽôė~9mZÆ�°yëfĪ[€Wš~ôėv9G’’ˆ‹gÂčáøx{1kö<.ëĢųĒ�� �IDAT؞æÍū7YTxŠeN ONyāŧëqqqÁÉÉ €V­åĪíģx`ÜH|ŧŊČĪ/āãšķY˛l7^Û¯Úu>š„—§#îē€Čđ°ķîīéļlŨAhH0A\Ũŗ{­×_×"ÃÃĒĩ_öí?@ķfŅ ē~��|ú9>ŪŪ<ũČŋ°X,$§¤ņΟāâėL‹˜&Ėüx.Aū<=uŗ™Ô´tū÷ÉįĶĩs�z_yEYā;˜p„>ũģŨNÛVÍˆŽ g܈ģ*ėĪgķđíZˇÄÅŲ''ËųėŽKŠÂ‹ˆČ’_PĀô7ßcė=wč_Ÿ/}ƒˇ§'ũz÷`â#OķâSāáîVļŊ‡ģ}ztÃnˇŗüĮŸypÂh|ŧKeģēē0|đ­eÛnÛš‡V­ÁŲÉ ĢÕĘíƒāīĮ›3>$<Ŧ!šĮķH:–LģÖ-éŌĄ-K–Ž -=šķĶą]kž[ššûĮ įá§_¤CÛVXKŦtîЖĨ+~"(П´ô œ,"ÃÃ8’˜Ä‘ŖĮ¸gČ­ø×÷cÍē Ŧ˙} N3ƒģßJjZ:ŋmڌ—į>œ,&Æh2ŅŋwJûúđS/ŌûĒn¤ĻĻ‘˜”Ė ëûŸ ‹˜ũÅ22ŗ�×čC͍ˆJëÜā_.ū–z>Ū„•ÕS\RÂį_-!''—Ģ•ĻQôëŨŖ\[ąqņeûå—õ›Xŋi3ŗ›ŨÎĐÛQĪ×§Üö6ģ'Ki@8|ä(‰G1úîÁ˜M&�üũxđžŅx¸ģ0´ô î5 ÉŋÖ÷ĢĮ7äƒO?/ /§ kĘ­7^ËŌå?•…—ŗqv˛`ŗ•ŽAxY§ögŒ:2Íyš@\]\¸cĐ@æ}ĩ„‡søČҞ‰gĻNÂŨÍĩÜöfŗ™æÍĸÉĖĘÆŽ �˙rĪģ¸8ãââLAA!sį/bäĐ;?j];wāĢ%KKë0™0›ÍÜyķ@FŊƒīWŽÆÕՅëû÷ĻahwŪrCš:KŠKhĶĸ9ƒoŊŗŲDâŅcÜtm?î1”øƒ ¸ģģ1äļ›hÕĸŋoŲ”†˛ûF câ¸DE4fí¯ˆh܈Čđ0ŽîŲ1MĘę?[_‹‹‹‰l\zz­×•—ŗú—õgėÃĢÖP߯“īːÛndͯÎZįüÅKša@_Fß=˜ ĀŋÖĐųaÕZęû1~Ô0î3œ{b9p(ĄŌ÷n؊Ÿ{Īî{×öíEfVöÛ¤§gāââ@bŌ1††”—“ŧ<=0$&ŖqŖeÁ夰†Ąää'/?ŋÂ~4‰Œ 1éXĨũ<•‹‹ é™�D4n„‡ģ{•Ę9ŧˆˆÔ˛â’^~írõëŨƒv­[Đ$*‚?ˇīâŸĖåq#ƿ̜ū+ūTƒĄėtE’’“ņņöÆËĶ(=•đåâo˞k”~qVöĨx’Ũn'*â¯ŅŽĀ€úXNŒ&x¸ģĶ $�OwwŽĨ¤āëãÍŦ9ķ°˜Í¤gdŪ¨æ}mÜč¯žæåQ>ū`}zt 80€QCīāĀĄ„ ë´Ûí$&#ōDšDE”Õģo?ĮķōŲ”†Ē”Ôt7jXaŋ;ĩoÛī}HģÖ-hÕŧĄ!Aåž˙iͯløãOtpr9Ęo}o0 ’ˇÔnˇS\\Rásųå–艋?xÆą6zø`ü|}¸ōō.L{ũ\]\čÖĩSĨ}qD /""ĩĖb63uŌøJŸĪĘÉÁÉɉŦėüëŸ{(ßÛËŗÉÄĄÃ‰4jRöxAA!GŽ&a29ˆ~ęÜŠzíöĘCĐIfķ__ §O6NũÛNZz‹žũžĮ'ߏ›Ģ+?ūŧŽÔôôsļQĨžV´­ŅPĨ×p˛ÎSˇĩÛū ‹…kûuĨu‹˜*õņ†kúråå]Øą{/͝O¯î]˝ÚéŲŊ+ŠiélÚ˛•î];Ȃ¯ŋŖ¸¸¸,üAé$l‹“ĄÁ,_š›ÍVî5:œˆ—§Ū^žö#v<†–ũV霗 ŋoĄKĮvģā:m$"rAmÜü'&Ŗ‰ņ#‡2oÁאž‘Y闲Á` oī+ųlū"ŌŌ3�ČËĪįãšķŲĩwAdeg“ub…ŪŊqû ĢxĄļeįäâęꂛĢ+EEElŲļk‰ĩŦßÅÅÅåļ?ßžF6cûŽ=@é>{ũŨ*­Ķ`0āĪū‡�Øš'ļŦžˆđ0~߲( 8ķ/%;'ˇÂ6‹ŠŠXļâ'|ŧŊčvY'úô¸‚øC‡ĪØ.4$ˆŦ§“BC‚kĘgķSXXĀąäTf~<—ä”TÂ6 ž_=ž\ô-Å%%e¯įķK8āę ûqčp"K–ލôųĶefeXĨmF^DDjYQq1ĪOŖÜcf“™ņŖ†ątųO<xßh<=ÜéŌą –,cđ­7ōĖ˯1a÷TŊ¯ŧg''Ū~˙#ŦVÎNNtéØŽŪ=ŽĀ`00ä֛˜5{Î'ŽNēũæuū:ĄtކŋŸ¯ŋûnŽŽôī}ŸÍ_Äö]{hÁ’e+°2âáâė|^}íŲũræ|šˆWۚŨnįēū}ÎZįÍ0ņR|ŧŊˆƒ#2=ģw勅ßđÚ;˙ÃfŗŅ4*ĸė´Ķ霜œ°ŲlŧōÖL\œ°ŲíÜ>čúsöuä]ˇŗdŲ<÷Ÿ×ąX,x¸ģ1čúūÄ4‰`Ė=CøzŲ ž›ö:FŖ'‹…kúö¤]ë–euŦüų~Û´›ÍŠ›ĢCoTnsl\üĮZdxcŸ6—éīÆ0fĖûŒ3Ē\`ķŽXBƒüĪŊĄˆČßȑ¤�Úĩø{Ü'Cj߯Í Ûnēîbw€O>˙Šļ­ZĐēEŗ‹Ũ•JmŪ[­ĪÔØąc Ņi#‘ÚĐ,*’}û0ņŌ‹Ũ>˙j ‰IĮˆŽh|ąģR'tÚHDD¤xzzđØC.v7�¸ã6ŧX4ō""""EáEDDDŠÂ‹ˆˆˆ8…q( /"""âPęėjŖOįĖeãĻßqs+ŋÔ¸Ņ#iܸōu/jbÂŋâí×_eᒯ1M ŧîšrĪs/ŗfž[ãúŸ{aC‡ÜAxãĒ/~</}ûâhĶēqqûųrÁBĻNy¨Æ}¨Ž_ׯ§k—.güģ6%'§đڛ˙G˖ÍrĮmōÕĸÅlØđf‹ w77ôëËe]JoM]PPúüĻM`0pvvfāõ×ĐĩKg�6nú>ü?ŋz�X,f:uęĀ€žWc4Ų¸éwŪ˙đęų”_ĻīÕŊ0Œ,[ž‚!wÜFëV-ŠŽŠŽĄŠŽ›ĘŽą å|åsŠėX?ŸvO=ūF›ĀûīŊ}ŪũŦŽĸĸb&LšĖĖ˙{ãÜ‹Č%¯N/•Đŋ/úõ­Ë&�xûõWëŧę:pā [ˇm§MëVŧíĸĸb–~ˇ‚Ž]ē”ûwmÛŊ7–V­Z0øö[øŋ÷ŪĮ×חi/=‡ŗ“IIĮxõˇpqqĻm›ÖŧõÎ{„1íÅįqr˛œœÂ[īΠ¨¨ˆŨKZkÚ4ŠIK/5ĖČĖäũ>"#=ƒģß@LĶč˛įOįėėĖ–?ˇÖ(ŧ\ŠĮĐßA]"ōĪuIÜįeņ’oIIKÅd2“@ûvmÉÎÉ!+3‹ėœú×DŒFĪūŒÄÄŖØíČđaC1 įüUh0Y°h û㐙™É=Ãî"22‚ŖII|:įsŒF#ų\MÚļiMVfīž˙NNNøøø`ĩ–Ž;ņÂËĶšqāu´h^ē×;3Ū§MëV\Ņõ˛ríåįį3wŪ—âģô;bš6Ájĩ1ëŖOH<z ›ÍÆîÅÛۋí;vōÍŌīprrĸ¤¤„aCî$č´ĩ(^˜6Æaa䐒šÂe;sUîdef1sÖGØŦV évÅåôî؃gĪ!%9•÷f~€Él*û÷¸1#YöũrūÜēŗŲŒ§‡w‚‹‹3÷M|.;Rbĩrųe—ąø›o $//Ÿ”Ô4&Ū7¯Ķ ŗŲl8¸÷Áƒ‡8|ø÷ß7ˉEŨ‚‚yâ‘)xxz°/.Ž”ÔÔ˛÷ Ÿģ‡áwg–…—Sųúø0vô~ôInxî[q;;;U¸ōî䊏ķÔãâåéÁŦ>áx^>÷KQQ1?ú$˙ūŖÆ¯ōČÂŅŖIŧųī‘––FXX#îvƒĄŌ}{ĒS÷ķˆģ‡VXÆÉÉÂøû¤OŸž¤ĨĨ‘𖯎ŨBdxxY=•Ŋ÷� ͖?ˇb0čÚĨ3ũúöĄ¨¨˜gĪ!++kI Ícb¸ūē•ë§Ģč3´yë6�nšŠô6ä?ŽŹ1|XYšSÅqcFb4ųöģīŲļmŲŲ9 6˜&ŅŅUúđÁ‡Ÿ–ž¸eĐ 4iÆæ?ˇ˛tŲ÷8;;cĩZšûŽÁģŲŸ_=_6lPVOeûBDĮ%1įÅd6‘Ã=Æ0fä=Ė_°ˆū}{sīØQ`ˇˇ?žœÜ\‚ƒx|ęžxt É)Šė‰=wå€ÕjĨQÆLž4‘Žŋ–ĪįĀĮŸ~FŸŪ=™<i"ãFdÖĮŗ),*bŲōDEFđā¸áúk8’˜@ĪĢŽdÍÚu@é)ØØ8:vhF{ŽŽŽôę؃6­ZrŨ5ũ8|8‘›nȏN!$$ˆŋ˙A^~ŸĖ™ËÄ ãxđ \Ķŋ/ŗįÎ;s˙¸ģš1ōža<0a<ķ."ŋ ŸÔŒtŽęŅGĻ<ČÔ)ōÕÂE””Xšv@?üüę1nĖČr˙ŪŊ'–í;v1uʃLž4?ŋzüđãO�—Ķž][FÜ=‹ŲÄÁƒšyĐ Œ=‚Ą!lúã3ú•𖆛Ģkéë;’HXXŖ˛ār’ˇ7&“‰Ã‡‰ / .'E4nLvN6ĮįUøŪy{yČŅŖG+ƒOpqu=ņÅV^‹æ1Ğ8V’Ž%“››ƒŨng_\M›FŸŅ§s9š”ÄøąŖyęņŠÄ8ČŽŨ{ÎēoOuę~ŽŦŒŅh¤ °€–Íc;j7\w-Ÿ1ŋ\=•Ŋ÷;wífûÎ]<ũÄŖ<>u [ˇī +;›eß-'( É“&ōđäIlŨž¸¸ũ•ë§Ģč3tՕŨøíˇõeqũ†g„ĐS?�;vB‚ƒ™:åAŽЗ?üTåĪÁˇß}ŋ}žz|*#†ßŏĢV“ŸŸĪ‡Īf¸1Lž4‘+ģ_Îgķž`Îį_pë ™xßŊ„—ÕSŲžĮQ§#/ß/˙‘uŋm(ûÛÃÃGúW…Û†XUÔĶËOęų–Î{đōōâxŪq<=<ČÍ;Îô˙ž““ĮŽ%“›“Såžœ<•ФI4~2€¸ũņ4oÖ�˙ú~¸šē’œ’ÂĄ„ÃôīÛ�ŋzõ (ũØŠC{æĩˆŧü<ūÜēíۖ-v.ĄĄ!øž˜ĢáįWüŧ|:LNv.ož]ú‹ßfŗSTTTaų&ŅĨ yšēēâííCrr*õũüXšrkÖŽÃd2Q\TL^~~Ĩ}ØŊgIIIŧ<ŊôIaa፰Ûė4mō×úĄ!!¸š–.įíãÅņÜōábųŠ•üöÛzž}ú � FCšåæĪ`¨|{ģŠ‹+~ŨPēzîÉ%ã÷îãÉg˙]îų‰÷Ũ‹}?bš6áëo–ōîˏ÷ė%@Ëæ1ėŪKà ¨įë‹ŗ‹ ‡°s×nZĩl^yŸ+Ņ"&ŗŲ@td GÉĪËĢtߖ{­§ėįŗŊ�ŅQ‘�„…5âđá#åęŠėŊߡŸfMĸ1FĻ<ø��ģöė!÷øqvėÚ ”Ž;Ëą^‘Ķ?Cõũü aįŽ]„††p</ŸČȈŗî;ģÍNĢ-JÛķķãx^^•?ûââšĻ_éjē BC™pīXââöSĪ×oo�bš6eÎÜ/°Ûí>r„čŸ›˜f­íRŲž8WßEäŌQ§áĨ_ß^ÎyYžb%ËX ĀũãĮ”}9Œ§ Ųíüōëo:”PvÚáõ7˙¯Z}1œüumĨ_¤† ~p N˙Š=š"ĒÅbĄs§lÜđ;nßÎMU8•q’Ų\ū5Ųącq˛Pß߯JyO]•› ƒÁĀķ\6ēsī„Ig­ÃâdĄ}ûv šãˇXĘūm2ŲßSõŊē7É))Ŧß°‘^Wõ Ah(s˧°¨¨\ ;’˜ˆ““3 †ōÍŌeX­VL&SŲķÄÛËŸĶ&បš–FVV6ĄĄ!¤gdФIdĨs^<D‰ĩ„ąŖF”{ŧyķ–.[Nhp01M›`qvbמŊėŪËÕŊzVX×ŲNŠąÛÁĀš÷íŠNîįs•ąŲl˜L&ė6;†ĶÖĘŪ{ƒŅXaHtrrbPŸ^´o×ļÜãk~ųõŒ6+súgāĒ=Xû˯„G„seˇ+*-{Ē“ÁīDUUū˜*ym§3žØW§nzj°Žl_ˆˆã¸(§ú^Ũ›WĻŊČ+Ķ^$,ŦjWefeS߯FŖ¤¤cė§¤’ķķŲĩk7�ûââhذƒČˆvėÜ@Jj………øûĶ $˜Ŋûâ�8v,™cÉÉeõôėq%Ģ×ūBNn. 6 °°Ä Ni FŠ‹‹ĪÚ§Đā`˛23ˆęwīŲË?ŽĒp۝'úŸ™™IFV&ūūdeeãī_€_×o ÄZ‚ÕZ‚Ņ`¤čÄHÆŠ˙nş[ˇQPPĀ?ŽbמŊįŪy•hĐ é™�4jØ€ČÆųčãŲ�xô(oũß Ž;Fdx8ūūĖūlEEĨû%5-gÆ-7Wŧt{VV6˙›õ×]Ķ—sö'##ƒĐā3Nšģšav2ķĮ–?iÖŦ)M›DŗmÛvl6kŲ/öęØŊ'›ÍŽÍfg|<6¨Ņž=W™]ģ÷�°gī^2g¨ôŊoÅö;))ąRRbeÚ+¯‘š–FtT$ë7ū€ŨngÎÜydefõX?ŨéŸ!€6­Z’p$‘õë7pųegNĘ=õøĢLU?ŅQ‘lųs+Pzėŧ8íBBƒÉČĖ"#3ķÄ>ÛKDDƒāā öxmÛvė(WOEûBDGŽŧ,ûn9ĢūĨÜcúöĄGîÕŽĢk—NŧõÎ ūķęëøûûsã ×ŗpņ7DEFĩœÍfÃÕŕ¸ũņü´z é :€áC‡đégŸķĶę52zäp, ũû]Íģ3>āåé˙ÅΝááeŋø0™M\Ņĩ+�‡á~ÂK˙~Ļ\ģ‘á,\ō5ŸÎ™[áę�...Œ5‚>ügg'ŦV+ÃîēŗÂm‹ŠŠxgÆûM:ÆíˇŪŒ‹‹3}ûôbŪ— Xŗv­Zĩ¤s§Ė|ūë~œĖžũ÷KLōPŲŋŸ|l*={tgÚ+˙ÅŲŲ 77wޏŧöŽšwÜhžZ¸ˆG'' žÜ~Û-´lQzjæ ÷2ábĻ>ūF“g‹7 ŧŽÎ:”ÕągĪ>ę9l6F“‰ž=ē—MDØĩ'–Š?]ŽŨ&Mĸq÷]gí[‹˜~^ŗŽĀĀ��Ž%'Ķšc‡ŗ–ŠˆÍf#$8÷Ū˙iiéD„‡ĶŦi€jīÛč¨ČJ˘L&bãâøqÕĪddf–ŗ'UöŪ?2åAÚĩmÃķ/Mģ.;QßĪž}zņégŸķÂËĶąŲlÄÄ4ÃÛĮûŦĮúНš˛ĪŅh }ģ֤ϤááQūļPzŠôÔã¯"Uũôģē7ŗ>ū”į^xģÍÎ̓nĀÕŕ‘ÇōΌ÷qv*}÷‰ž žũVæ|ū%ž>Ū4‰Žâd¤­l_ˆˆã0Œ3Æ>cƌ*Øŧ#–Đ ˙:ėŌĨ-=#ƒ˙ŧō:Ī=ũNNĨÃ˙~2›{†ũËķ|ŧ<ũUŨ8&ŅŅįŪøúõˇ ÄîÛĮ°ģ_ėŽ�đķš_8œx„ÁˇŸûÔÍĨŽŽīįR[JJŦŧ4ũU† žŖĘŖ¨ŽęHR �íZ\ZŸCGļyGlĩ>ScĮŽ%$$äŌ¸ÚČQ|ũí2ūûú[ ģëÎ˛ā’—ŸG§ Ž8ú'h͌=ąqĖŠāʐ mÕę5,ũn9;uēØ]ųĮØēm;ĪüûE:ļoûˇ."riŅȋˆHhäE¤öiäEDDDū^DDDÄĄ(ŧˆˆˆˆCQx‡ĸđ""""EáEDDDŠÂ‹ˆˆˆ8…š bc÷Wy…š`fĪžÃ˛eËĒ´J|e^DDD䂘={GŽaäČ †s¨„‹ˆˆˆÔš“Áe„ûpw?súęPx‘:ujp1 ,[öŨyÕ§đ""""uæôāōÖ[o“——§9/"""réŠ(¸„††rĶM7j΋ˆˆˆ\Z* .ƒ߉Ņx~ņCáEDDDjÕŲ‚ËÆ›xã7uÚHDDD. į .sæĖĄK—.įՆ‹ˆˆˆÔŠĒ—ÁƒĶĨKgÍy‘‹Ģ:Á%==ãŧÚRx‘ķRā2gÎgĖ™3įŧÚĢvx1 ØÎc’ˆˆˆü}ÄÆî#==ŊėΚĢV­&**ĒėĒĸ­[ˇ2dȐ˛—“ËØl6L5ŧęČ\ŨîŽdfįâãåņ<ÎW‰ˆˆˆã‹ŽŽ"*jBŲ–kŽ€Ũn/û{ôčQeûųÕãᇧ`0ø˙öî6ļĒúāø÷ö„J[ Ĩ @‘§Ā H†2djØCSˆÛX–Ė- [XfœĶn‚‘E as ™`tؘ ā&ŌAéZl/íŨ‹ģÁjĄĐK¯×ü~Ū‡Ûķ;īžųŸ{{NœĒ Gvˇ„ŽŲîxĐ¯€cĮOsúLÕMũĖI’B´˙āáT /=-ėîYôëĶ+ĄĪˇ;^2ŌĶ< oB“$IēY~aW’$Åx‘$IA1^$IRPŒI’ãE’$Åx‘$IA1^$IRPŒI’ãE’$Åx‘$IA1^$IRPŒI’ãE’$Åx‘$IA1^$IRPŒI’ãE’$Åx‘$IA1^$IRPŒI’ãE’$%āLÕŲTĪ!I’tC2�zįõHõ’$I7ÄĮF’$)(Ƌ$I Šņ"I’‚bŧH’¤ /’$)(Ƌ$I Šņ"I’‚bŧH’¤ /’$)(Ƌ$I JFǐ$éĶŦĻĻ–Õŋ~‘ĘĒęTŌ.=ķrY˛ø!rr˛[+Ģ…ikĄ´b)˜- ʅ×A˙Öã]—+/’$ĩaUÉ:**ĢR=FģUTVąĒdŨUM] GR.ŋî‘ęø‰0^$IjCUu ‘H$Õc´[$ĄĒēæĒĮŽ~J‘Ãx‘$é3&U+.—čƋ$I Šņ"I’‚bŧH’¤ /’$)(Ƌ$I Šņ"I’‚âؕ$ЃäôČfîė™ ¸­QĒjjØöęN”ŸbÆ=ShjnfĮÎ7R=fģÜ=žš}ģCZė*…'ūgęâĮ,ož˙(o?ķč ļ@s’~“mŧH’Ô:wîÄw[Ėvėä…õ‰ÅbŒ>„o=ŧ€ĪŦIõx šĢ?ŧtĖÛ?iđØxØ] #VACSËķ09ūo˙īۘŧpãE’¤1aėhJ•ągīžËûž˙?_ųįĪ_hqîČáCšgʗihh$==M[OeU5ķįĖĸ ŋ‘H„ęŗgyyķ6ššš˜:i"#†áŌĨ&.Ô׹iËvŖŅ¤ßͧ“¯ÅÃāR3ŦÜ ĶÃŖ`í;WÎ]<ĻÁĖõņķ’Éx‘$ŠäsŦėxĢũgkĪĩØžĨsgîŸ;›Ÿ­üįĪ_`ˍ;˜ûÕlŲū*ƒŠúŗâŋāK_üYŸëJĪŧ\† šÕ%/0ëŪiLžk<~ũ/Iŋ§ĪįÃÛ˙nŊ˙­2¸ŖāĘöŧ‘°äN(zēõjL2/’$u€X,FZÚõ߁Tß‹šÚs—Wc>(=ĘÜ93¨Ē>K]]=>ŧ€‡ūÅģQ{î<Ǝ&ŋwO–>˛€ĖĖLʎŸHęŊüOs ŽõZ§[ū¯ †÷‚5{āųŲ0įåäŋ~Āx‘$Š”ŸúaCąëÍŨ-ö؟'OĩųŲX,Fss3Ģž_GŋÂ>Œ6˜ī>^ĖskKôŌ%Ū=pˆWļ˙)™ã_Õ{§aŌ�8üą—jßyl=xe{ųë°ī$l~2-ū¨)™üŠ´$IāíŊû¸ĩo!“'Žģüę‘Çō¯ĪĨS§N—Ī;õá˛ģw#ģ{7�†Ü^Äą˛ãä3vô(Nœ<Ŏ]oōĪÃĨöɧôX#† ĨSf&�“&ŒcpŅ€OäžVŧË§Âø[ãÛixt ˁõû[žÛƒ6Į!͙ܚ\y‘$Š466ōôŗ%Ė›3‹éSīæŖ8SYɚ^jņ…Ũ††F6ün=0ŸÆÆø—n7mŨ΅ēzîv7ĮĄŠŠ‰ uõŧwā}ŖQŪÚŗ—%,$RW‘ŋŊŗ˙Zct¨ŋ–Áũ›aõL(čéØu&ũĸWųRnm|mėZ_­Ųßö‚SÂ"‹/Ž•””$į¯K’¸Įŋ÷ŖTpSž}jyĢ}‘'S0Č5Ä~zãįSXXčc#I’ãE’$ÅPĒS��IDATx‘$IA1^$IRPŒI’ãE’$Åx‘$é3æú/1ød$:‡ņ"IRōrsR=BÂŽ5{Q.ÉŅõÄū;GŒI’Ú°´xa“—›ÃŌâ…W=ļs ĘKŨ L„øõw.Jėķž@’¤6äädķãī?‘ę1:T˙l8ōíTO‘8W^$IRPŒI’ãE’$Åx‘$IA1^$IRPŒI’ãE’$Åx‘$IAIËČČ ŧŧ<ÕsH’$]Syy9™™™�¤eeeąqãF.^ŧ˜âą$I’Z̝¯gÆ deeŅĩkW***XļlŅh4ÅãI’$ĩ”™™IVV]ētā?Ŋûƒō@ʲ����IENDŽB`‚�������������������������������������������glewlwyd-2.6.1/docs/screenshots/scheme-http.png�����������������������������������������������������0000664�0000000�0000000�00000133376�14156463140�0021616�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��%��™���Ļë„%���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨwtåūĮņĪn6Ŋ!!Đ{ ŊˆHīŊˆˆŠ(özŊŪkŋļĢ?õĒčՋ]AzīE@@TzUz'�é=Ųũũ˛˛$D6L€÷뜜ÃĖ<ķ<ߙY=ûŲ™g×ôâ‹/Út‘ŒŒ ĨĻĻ*77W����ā ‹E>>>ōôô,ŧíâ…ÄÄDiܸqĒQŖF‘;����@idddčØącš;wŽÎœ9#‡íöP’––Ļ   Ŋøâ‹2™L×ŧP����7&OOO5lØPõë×כožŠ¤¤$‡ 悤§§kȐ!����eÂl6kĐ AJOOw\_đÜÜ\ÕŠSįš���āæQ§NBķ×-&S~.1™Lrss3ĸ.����7 Ųl6äéĸ;%����`B ����CJ����ŠP���ĀP„����†"”����0Ą���€Ą%���� e1ē�����7—_ˇl—ˇĪ!û2wJ����ŠP���ĀP„����†"”����0Ą���€Ą%���� E(���`(B ����CJ���€›˜ÍfĶŧyķôË/ŋ\ļÍæÍ›5uęTååå•I å"”<öä3ĒYˇĄâãĪ] œŦvũÆ<|Ô_ÚˇāuëäĒ���P`ŅĸEÚšs§VŦXQd0Ų˛e‹–,YĸhæĖ™eRC™‡’ˇŪū?ÕŦÛP‘-Ú(33ŗČ6 ÔW§ŽäææZÖåčŗI_čø‰e>���p=hܸą\]ķ߇_LļnŨĒ%K–H’Ėfŗ"##ˤ†2 %Ų99š3ožL&“RRSĩt؊"Û=4á~}÷õō÷÷/ËrtæLŧŪ}˙8q˛LĮ���Ž5kÖÔčŅŖeąX$ũLļmÛĻŋËfŗÉl6køđájذa™Ô`Væim[ˇQGßųō+•¨;īČ|gúĖYΤvīŲcčø���@yTT0Y´h‘= :´Ė‰TÆwJĻMĪæėŪ{ÆĒuĢ–Úēmģ9R¨]QsJĸĸĸõė?žWģŽUˇaSĩhĶ^÷OxXģvívØ÷žTÍē •œœė°>77W5ë6ԘģĮŲÛ=đĐŖųõŒŸ šujËÖmöögĪžÕ˯žŽŽģĒnÃĻjŲö=đĐŖÚĩÛ1ČÔzöÜ9ŨuĪ}Ē߸™~\ŊæŠįĄ¤5JŌO˙M5ë6TZZēŪūŋ÷ÕéļnĒ×(Rˇtęĸ¯žųN6›íŠc]ÜGrr˛žéĩn×Q š4×āáŖ´k×neddčĩ7ū­v;ĢQdK q‡öūūGĄ~Jz $iíOëÕĐPÕoÜL­ÚvĐ?žŠĐņ–ö\Ĩ¤× ���ĨSĢV-=Z...’ō'Ā›L& :T7.Ķą-eÕņącĮĩiķĩhŪL5ĒW͐Aĩeë6͘9[/üķš+îŖACG(#3SwŽĨēĩk+6.NSĻNĶČŅwiōw_ĢuĢ–ĨĒį҇R˙ šˇ`Ą{ä!5jØ@ujז$;^ƒ‡Rrr˛Fß1RuëÖQLLŦĻü0M#īŖīžųRmÛ´–$š]xŪî7ߖÅbŅã>ŦĒUĢū…3T´‚įų~ė U ×ÄߗÕjÕG˙WoūûųųųjøĐ!%ęã‘ĮŸRëV-õí×_h˙ūzáåWõČãOŠ~ũēĒSģļžøß§:Ĩ<˙’ƍŸ ëר÷-Í5Ø˛u›îđaUŽ Į}X•*iĶæ-ē˙ÁGd6;/÷–æ:�� ôRRRdĩZíË6›MŠŠŠe>n™…’i3ōī’ŧîÛˇˇūõÆ[š;žũÛĶö7÷Eų·uîüy}öÉDõėŅÍžžgnęÕw ūũÎ{š;kZŠęiŪ,RŋmÚ,IjŅŧ™:ßÚÉžíÏ>Ql\œæĖœĻĻMūLƒöWĪ>ũõÖÛ˙§sķ=+¸Ĩu>!Aß~õšSßtK’Å%ŋ˙ üõÆk¯Ø×ŋņÚ+ē­kO­XšĒØPRĐGõjÕôøŖK’5l ĩëÖké˛åŠlÚDĪ=ûŒ$ŠIãFÚ˛e›žũ~˛vīŲĢ–-šK*Ũ5øīg“dĩZ5éĶOŲ´‰$iäˆazųÕ×îF]­Ō\'���”ÎŽ]ģ´`Áû#[&“IyyyZļl™$Š]ģvN¯n­Ē]ŗē¤2z|+;;[sæÍ—ģģģúôé%IōņöVīž=”¨•+ŧėž6›MĢV­Vå€�õčŪÕa[íZĩÔĸy3íÜĩK ΙcŗŲ´tųrÕ¯WW!ÁÁŠ?k˙sĩ¸ĒEķæÚŗ÷wĨĨĨK’L&“$ičAN$:xÃrDÕĒōđđPLl\‰ûčŲŖģÃręÕ$IŨēŨî°žæ…ÙøxIĨģVĢU›6oQDÕĒö@R`ÔČa%Žĩ8ĨŊN���(šŨģwkūüų˛Z­ö9$ŖF˛?ĘĩlŲ2ũöÛoNķā‘c:|ô¸¤2ēS˛ėÂ÷AúË×ĮĮž~ØĐÁšˇ`Ąf˚­~}{šo|üYĨ¤ĻĒqãFö�pąš5jhëļí:vü¸*VlvÕĩž;wN ‰JHHTÛˇ^ļ]tL´ũq¯‚:ĘRhhHĄuŽ‹rsrKÜGppÃrÁ‹*¸J•KúÍŋkUĐwiŽAhhˆ˛˛˛^¨]­š5K\kqūęu��Ā•íŲŗĮHL&“† bŸC2räH͘1ŖĖî˜<rLR…’‚ îmÛļqøMāā`UĐÆ_~ÕÉS§QÄ\ŒôŒüOēŊ<=‹ėÛÃÃ=ŋ]z†SjMMK“$5hP_æŠËļĢäøß××į2-Ŗā1ą˛čŖ¸žKs 22ō{ÆŨÍŊP;w÷ÂëūĒŋz���peGŽQ^^žL&“Ŧ&Mū|úĨ^Ŋz1b„f˘!ĢÕĒŗg˙cį“EĘ˙tÜÅI=zL›ˇl•$ũķ…—.ÛnæŦ9úÛĶOZīíå-IJĪ(:t„¯+֑““Sĸz}ŧŊí˙žxžÉĩPŌ¯ĩŌ\ƒ‚€’•U¨]iĨ*î\y���nd”ÕjU͚5‹üqÄúõëkøđá:räˆúõëW&5X”™ŠLYäææœ &¸>T:u,´=++KĪ>÷ŧf͙Ģ'´Đ§ö•åīī¯ÃGŽØŋ†ėb‡.|ĨpÁãS– åä:>ÖtętT‰ę­\š˛*VŦ #GŽ*99Y~~~ÛĪ?¯€J•JÔ×å\m×Ziށ———\]]uęTácŲā@Ąuõ\\‹ë��p3*xdëJ6lXļŋS{âœr=äå%R0ÁŨÍÕU{æ)õéÕŗĐßāÔŖ{WÅĮŸÕڟÖŲO¯ŨVĢ~\í°ū}û´k×nŨŌžũMiP` $éȑŖmįÎ[P¨_—ü‰é™™ŽŸę÷éÕKŲŲŲúü˯֟;^Ŋû Ōø •â,Všˋ’^‹Åĸ͛éÄɓ…~+dō”Š…úŊšsQÖ× ���ưH´(¸quųfŸšęΖ­XŠÄÄ$ 2øŠŸZß}×-_ąJĶgÎR÷n] mōņĮ´zí:=ũė?tĪØ1ĒYŖ†NGEiō”ŠōööŌ‹ũÎɐÁõôézã­ˇõĪįž•§§‡Vũ¸F;vˇcŌ*ø=‘˙}ū…N>­Ö­Z*˛i=ņø#ZûĶ:}úŋĪuæLŧÚļi­¸3g4uÚ %&&Ʇw]Õy)MåEiŽÁ„ûīĶĻÍ[4ū‡4|ØUŦPA›ļlQFFĻÃHWw.Ęú:��Āæz-[Ē^€sæģÛÁũî+ŋ9lÛĻĩęÕ­ŖuëVLllĄíUĒiūœęĶ̧f͙ĢįžQ“§LUûöm5oö Õ¯_ĪŪļyŗHŊûö[ĘĖĖŌ¸ûÔ>Ē„ÄD}9éSy{{+;;ÛŪļÛí]ÔĢgwí?pPŸ|ú?EEGK’*hŪėēķŽQÚøË¯úĮ /iŌ_ŠAƒúš5mŠ:v¸åĒÎKij,/Js në|Ģ&~đž*WĐWß|§I_|Ĩ€Júė“äããŖœė?į‹\Íš(ëë���c˜^zée›$ÅÄDë‹/ž0¤ˆĮž|FK–.ĶĻëXؐ����\›ĩ”ˇ¯}šė~ũ¯’’’$IžW���āZ+“ß))Š={×ú ?kķæ- -4˙����ĀĪĐ;%ųUīđ‘BBBôæë¯Y ����ƒz§äÁÆëÁÆY����ƒ•‹9%����n^„����†"”����0Ą���€Ą%���� E(���`(B ����CJ����ĘĐO���pķißē…BBBíËÜ)���`(B ����CJ����ŠP���ĀP„����†"”����0Ą���€Ą%���� eąŲŦ’$›Ífp)����n6›M9DâN ����ƒY.^8s.ҍ:����ܤBIP@Ŗę����p“âņ-����†"”����0Ą���€Ą%���� E(���`(B ����CJ����ŠP���ĀP–¤¤$IRjjĒÁĨ����¸¤ĻĻĒ ‡H’% @’”••eTM����n"ūūū*Č!o���0Ą���€Ą%���� E(���`(B ����CYœŨafVļNDÅ*+;GyyVgw_îTō÷Ö¤¯'+5-]6›Íčr€›’Éd’¯ˇ—îŋwŒĒW 3ē��PJN %™YŲ:pä¤|}Ŋåįë#ķ}#f÷žCúæģ)˛‰0Éfŗ)9%U˙ųøsũũɇltI�� œšNDÅĘ××[>^ž7| ‘¤U?Ž&�å…É$›ÍĒIßL1ē��PJNM™™Ųōōôpf—åZzzēŅ%�¸˜É¤¤äŖĢ���ĨäÔPbĩŲd6™œŲ%�” sģ��¸ūÜøĪX���(×%���� E(���`(B ����CJ����ŠP���ĀP„����†"”����0”Åč&}ņĩ~Ũ´ųŠmƎšCˇßÖųUTöîŋ{´*VĐÛ~Zh[Íęzęáņšøŋ¯uččqŊc¨Z7ŧb3æ.RÍ%j÷ķo[t˙ŨŖÕ´Q}‡mŠi銎‰ÕŌUku䨉+Ö~ņžyyyJNIÕūƒ‡ĩdå§˙šöÛ¯ūCk7üĒĢ×]u_÷Ū9B-"kڜ…úeĶV'T'ŊūÂß´ië-^ąÚ)ũ•†3Ī‘c���X“S%I)ié†СOOuėĐŪžüÅWß)<<TŊ{vˇ¯ 6ĸ´raåš Ú´e‡}ųŽQC§Õ?ũl_{&^‡(QģgĪ×ÔYķíË~~žęØŽĩžxpœŪûøs<uؚ.Ū×Åâĸā  õęÖYUÃô·Ÿ9õĩį.ZŽčظĢîĮĶÃCMÕWTLŦÚļlö—CI§[Ú¨Zx˜Ļ˜wÕ5]íØÎ:7×z ��€KY*øųH’ŌSŊ ) <,Láaaöe7wWųûûŠQÆÔSŪÄÆQlÜûrNNŽ’“StāđQ‡vIÉ)%jW ++[‡ŽwXˇkī>Ŋōܓē­S;}?mÎekētßũČjĩjø žĒPIņgĪ•â¯lķļNé§eķ&ĘÉÎŅÜEËõØ÷¨r@%=wžÔũD„…:ĨžŋâŌąunŽõ���—2üņ­’xķwåæęĻgŸ~ÂaũÄ˙~Ϥ¤dŊôüszčŅ§Ô¯oOÅÄÆi÷î=ĘĖĘRŖ† uīŨwÉĪ7?xåååiá’ĨÚ´iĢΝ?¯J•*Ēg÷Ž7ÔŖaW#77WŅ1q (õž9šš’¤ĖĖLû:oo î×SõęԒ——§“´nã&­Ûø›ŊM­ÕÔŋW7…†T‘ŲdVTLŦ.[e„ėŌĮ‡\\\Ô§{ĩiŲL^ž:ĢųKV訉SWŦ¯]ĢæÚžk¯>Ēķ ‰j͞™–Ž\ãĐæũ7^ÔŌ•kĩzũFûēŅÃ*,4DīNüŸžxpœj×Ŧ.IjÛĒšũņ;ĢÍĻ^ŨnĶ­íÛČĶĶCĶäs•š–&I2›ÍęÕĩŗZ4kĸJ+(11Ik6üĸŸŨbį­—ŸĶŠÕëTąĸŋZF6‘‡ģģ;ŽŠŗ(%%ĩČą{āžR›âŽGIư¸¸¨_¯njŲŦ‰|}ŧ•œ’ĒÍÛwiéĘ5˛Z­%:–KõéŪEŊģw)ōē-[ĩVKW­Ŋâĩ��×ŋëbĸ{įNõĮžũJHL´¯ËĖĖŌŪß˙°?úåb1kéō•Ē_¯Ž>|˙]ũëåtâÄ)M›>ĶžĪŒŲs´|ų*õīÛ[oŧú’zvīĒŠĶgjŨ†Ÿ YÖLf“<=< ũšģģ]ķZ.PŠĸ“’‹mg6›e6›åæęǚÕ#ÔũļŽÚŧm§RRĶėmƌŦšÕ#ôÍ3õī˙üW+×nАūŊėsRÜ\]õāŊcwFīōšŪûx’ĸbbõđøąōôô(rÜÁũzé–ļ-5wŅr}øŲ׊?{NŒŋ[+\ļÖ*•U­j¸6mËŧmķö]jĶâĘķoŠ2éÛtōt´ļíÜŖŧúļĸcōkjŲX>ŪŪúėë)úvę,Õ¨^U}{ÜnßoPߞęz[G­\ŗ^oŊ˙‰ÖŦ˙ECôQûÖ-ėmōōōÔíļŽŠ=ŖWūũŊųūĮĒĒŪŨnģâØĨ97Å]’Œ1bHĩoŨBķ/×īMÔÂeĢÔšC[ ęÛŖÄĮrŠĨĢÖjYÁƒ@�ĀÍãē¸SŌēUKũ0m†~Û´YŊ{æŋųŲĩ{ˇl6Šm›VövęxK~H V—Û:iáâĨē;3K6›UkÖŽSß>ŊÔá–v’¤*U‚tüÄ)-YļB;uŧĻĮ\E˙÷Úķ×tĖK™ÍfR_ouîĐNU‚*köÂ%WÜ/,4XŊũĒÃē?ԜEËÖÍ^¸T6ĢUįōÃdüŲsēõ–6Ē_ˇļv˙ž_+úËÃÃ][ļīRܙŗųû,XĒíģö*77¯Đ¸îînēĨmKÍ_ŧB;vī•$M›ŗPîîîĒ\9Ā>ÎĨÚĩnĄ¸øŗ:~ō´$iĶÖíęÕĩŗjÕ¨vÅIũ—ĘĖĖ’ÕjUnnŽŌŌĶ/ZŸŠŲ ōĪŲŠ¨hE6n¨ęá’$wwuēĨV­YoęįsįU5<TŨģÜĒ_ˇlˇ÷w&^ŋmÍN‰IÉúãĀ!E„‡]qėԜ›âŽGqcxyyĒmËfšˇx…ļīĘãėšĒK§öZ°t•ōōōŠ=–ĸ„‚;&��n.×E(qwsSģ6m´ņ×MöP˛uÛĩhŪL^ž΅Šá°_XX¨rrr”˜˜¨Ä¤dåææŠqÆmÔĢĢõ~VffĻ<<Šūtž,\:Ņŧ@hH ØˇĖĮ/*X¤gd臙ķ´˙ā‘+î{&ūœž›6K’d2™äīį§ŽíZëŸO=ĸū÷•ΞK$eegĢG—NĒS̆|ŧŊe6™äååiŸsr&ūœââĪęžŅÃĩá×ÍÚwđ°NGÅčđ%s] „TŠ"W‹E'Nũ9 ?//O_Mž~ŲZM&“ZˇˆÔ†_7ÛCØų„$=~Rm[6+U(šœŖĮKIMSõwIųįŲââĸ};´9tä˜niĶRnnnĘÎΖ$E]rg"=#C^žž%ĒĄ$įϏëQœđ`™Íf?éxŧ'OGÉÍÍM•ėķšūĘą\B$��Ü\Ž‹P"Iˇvę ĩëÖëäŠĶĒR%HģöūŽĮžāĐÆÃŨŨaŲŨ-˙Q¨´Œte\˜ëđÎ{˙‘I&{ë…oŠJJJžĻĄ¤¨‰æ’”wášü˛vq°¤ėė9{Î>/āJrrrtōtôEkĸôûūƒzåš'ÕķöÎúaÖ|™Íf=2~Ŧ\ĖfÍ^°Tqņg•gĩjÂ=Ŗí{Ųl6}øé—ęv['ŨŌĻ•ôÄ$-ZūŖļlßUh\// ĩf—ø8Ô­-?_õëŲUũzvuØ\Eŗæ/ąĪ‡ųĢ.­Į&›L^bų¯ÉĮ']ô­dĻ ü|}ėîsrr õm2ZU¤âÎMIŽGq Ž%33Ëa}fVū˜=zøW…0�ĀÍéē %5ĒWSĩˆĒÚŧeĢĒU‹—ˇ6pü†ŽŒ‹&YKRFFū˛ˇ§—rsō+yāžq /ü J•*U,ŖĘ˧ÂÁâęäåå)6.^Ą!U$IÕ#ÂŦ?ûĘán„ˇˇÎO°/§ĻĨkū’šŋd…‚ƒu{į;j¨bãâu*ĘąžÔÔüGŠ Ū—DÛVÍuôøIÍY¸ÔaŊÅbŅãîUĶÆ ´mįI™ÁÎÕÕĩÄcĨā ü÷Ķf9G#11éĒú/PÜš)éõ¸’Œ Įré\úß��@I]Ũ tęØA›ˇnĶæ-ÛtË-me6;~ôzāāA‡åã'ŽËÍŨ]•*URDÕ0šZ,JNIQhHˆũĪĮÛGžžžWũæķfgqqQHp“ō<ŅՒŸwĶ.úũ›ęUPŠĸtáNU@Å jŌđĪbŒ=¯ésĘjĩ*$8¨ĐgâĪ*;'Įū QRū‡'§6-›j_đÛ$[ļīŌÉĶŅGŸÔCGöËĖĘ*4Áž d]ĖTŌÛ’ĸĸc•››+oÅşĩ˙ĨĨ§+%-Mšy…įÎ\ÉåÆ.îܔäz7FTtŦŦVĢjVw|L˛FĩĒĘČĖTüŲŌÅ2��€t…’öíÚ(11QÛwėT§[Úڞ˜˜¤y éĖ™xíÜĩ[Ģ׎Sģ6­äææ*OOOužĩŖæ/\¤M›ˇčLüYí;pPī~đ‘žüúÛk0×1ww7ÕŠY=˙¯V ĩˆlŦ‡Æ•ˇ—VŽY/IŠŠ‰UNnŽ:wl'?_Õ¯[K#÷Õžƒ‡U%°˛|ŧŊUąb;Jˇßz‹‚X9@ŊēŨ&›ÍVäWüffeé×ÍÛÕķöÎjŨ"RUÃB5jHET ĶŅã…ᆴlŪD.fŗvîųŖČãØž{¯ę׊e˙Ę蓧ŖÕ´qyyyĘÅÅEŨģt’ˇ—ãī÷¤gd(<,DaĄÁōō*~žGfV–6nÚĒž=nW‹ČÆ ¨XAujV×Ŗ÷ßŖą#‡ģIĮ.îܔäz7FzF†~Ũ˛]=nŋUMÖWÅ ūj͞™níĐV?møĩDū��åēy|K’ŧŊŧTŋ^=effĒJ•Ÿ¤ßzkĨ§ĨëĩˇŪVNvŽšE6՝wŒ´oŋcäpyyyiæėšJLJ–ŋŸŋš7kĸaC]ËøîU¨”?Gâ‚Ô´4<­‰“žŅ‰S§/ŦKהķ4 OwĩmŲL'OGkƌyō÷÷Õ¸;GęņīÕ[īĸ)3įŠë­ÔˇgWYķŦЉ;Ŗ/ž›vŲÉ×ķ—ŦÍfĶāž=åîáŽč˜X}öÕdûäú‹ĩmŲ\‡°˙^ČĨvīŨ§;†Pëæ‘ZŊ~Ŗæ.ZĻ1#ëõįŸQzFĻ~Ų´U›ˇíTƒēĩíûŦûų7Ŋc¨ž~xŧžüūōė/6wŅreddjPߞōķõQrJĒöüą_‹–ũXĸũK:vqįĻ$×Ŗ¸1fÍ_ĸĖŦ,Ō_ž>ŪJHLŌōŌĒĩJu,���3ŊúęĢ6IŠŽŽÖ¤I“ŽĒŗŋRXp S +JrrŠžũį‹ēīžąjĶēĨÃļGŸ|F=ēuՀ~}ĘlüKŊūÎŽŲX�Jîã˙{Íč��ĀL˜0AĄĄÎķž.ĻĻ)öĖM›1KaĄ!jÕ˛Eņ;���¸.\ĄäįŋhöŧųĒ[§ŽîģwlĄ î����Ž_×E(éÕŗģzõė~Å6Ÿ|øū5Ē���€3]Wßž���āÆC(���`(B ����CJ����ŠP���ĀPN %&“IV›Í™]@ИøÆp��Ž;N %žnJMMwf—åš—§§DĘ›Mū~~FW��JÉŠĄ¤ZX°RĶ3”’–Ž<Ģՙ]—K]oī"ņCŽ@ųa6i¸1FW��Jɒ,ÉYŸ+z¸ģŠ~­ˆŠUüšååŨØÁ$°r€žyt‚>˙æĨ¤Ļ]pSķõņքqw)<$ØčR��@)YVîMŅ°ÆžNëĐÃŨMõjF8­ŋëÁ[/˙Ũč���€ë–9jį^%]���€›–Y ąŠšyæĻ���(gĖRŠÎ&]���€›•YĘSfžŅe����¸YYžxė^IRt´Á•����¸)9õwJ���� ´Ė é<ģ���Ā8æé̉/ß���`s×.õået����nZæē~F—����āfÆDw����†"”����0Ą���€Ą%���� E(���`(B ����CJ����ŠP���ĀP„����†˛8ģÃĖŦlˆŠUVvŽōōŦÎî���€Áš7ĒãÔūœJ2ŗ˛uāČIųúzËĪ×G.æ÷FLTlŧĶ/���PŪíøũĶûtj(9+__oųxy:ŗ[����70§ŪĘČĖĖ–—§‡3ģ���pƒsj(ąÚl2›LÎė���Ā îÆô���āē@(���`(B ����CJ����ŠP���ĀP˛ 3æ�� �IDAT„����†"”����0Ą���€Ą,F I}ō™vėÜĨ‡'ܯ6­[:lKJLŌ{NĪ=û´ÔĢkP…ÎķųˇSĩįũ—ŨŲ¤ĄÆß5Ę)c=÷ęŋÕĨĶ-ęÕĩsŠö›9ą9ŽžyÔ)u����WR.B‰$™Í&͜=G‘‘Mäîæft9eĒr@E2 Čmžž>׸���ĀXå&”4‹lĒ}ûhųōU8 ¯Ņå”)w7wÕĢSËč2���€rĄÜ„OOO ė×Wķ,R§NíUŠbĨËļÍÉÉҜy ĩyËV%%'Ģ‚ŋŸÚˇkĢÁûËÅÅE’ôØĶĪĒī^ŠŠ‰Ņļí;dĩZukĮęŨ̇žųnŠ:,ww ÔO:Ür­ŗÄļîÜ­ī§ÍŅߟxPáĄ!’¤ŖĮOęƒOŋÔ}wRŗ& ĩ`éJ­^ˇQßų×eûąæåiÎÂeÚŧ}§rrrÕ n->HŪ^^’¤¤äM5_“§‡ģ:ļk}MŽ���(Pn&ē[­Vuëzģ*VŦ¨ŗæ]ąíäĻiÃÆ9|ˆūũúĢ6d°~\ũ“fĖžkoc1ģhųĘÕ,2Rđž†Ŧå+Ô>úDũúôÔ'Ŋ¯ÚjōĶ”––^ևįĀfŗ)'7ˇČ?›Í&IjÕŦŠ7¨Ģ™ķËfŗÉjĩjÖü%jŪ´‘š5i(I  TãWžgķë–í˛ŲŦzdüX1X͌š‹ėÛŋŸ>GŅąqzhÜ=ūā8Ĩϧkįž?Ęîā��€K”›;%˛I‹‹FŽĻ>ū¯ēuéŦ:ujj–’šĒŸųMŖ†UÛ6ųŸę**&F+\ŖáCËՒXUÃÕ<˛‰$Š]›ÖúnōTÕŽYCĩkÕ˛¯[´x™bccUĢVÍkt Rtlœž~ūĩ"ˇ=ûøE„‡I’F 7ßûX›ļîPvNŽ“ôđøąöļm[5WÛVͯ8–Ÿ¯¯† Ė."<L§Ŗc´zŨFeįä(==CՈAũTˇvūņØWûqÆa���%R~BÉÍ#›¨IãFš2}Ļ^yៅļŸ:uZVĢUĩjÖpX_ŗz5ege)..NáaųoꃃƒíÛ===%I!!!­ķ$Ĩgd8ũ8Ž$°r%5´ČmÁAöûûųjpŋžZ°tĨōŦVÜ_ž>ŪĨĢVjË5ĒEČjŨ ŗįÎ+%%U’T-"ĖžŨd2ŠzÕpŠŽ)Õ8���Ā_Unß稍‘ÃtęÔimظąĐļĖĖLI’Į…@QĀÃ#9+3ËžÎâZ8sY,…×<2u­¸šēŠzDÕ"˙Ü.ųæąV͛*'7W&™Ų¸AŠĮōŧä<šģšJ’˛ŗŗ•™•-Iruuulã~cû���ʗrJÂBBÔĩKg͙ˇPYŲŲÛ<.ÜņČĖČtXŸqán‡‡—įĩ)ōY˛b*øųÉĶĶCKW­-õūŲ—œŋ‚ķéææ&ˇ %ã’s™~É2���P–Ęe(‘¤AúËjÍ͞ĢÖG„‡Ël6ëĐaĮy‡“§§‡‚ƒ‚Že™eęäé(­ũųWŌ_#÷ĶęuuōttŠú8rė„Ãō‰SQ˛¸¸(0 ’ĒV–$EEĮÚˇįååéĐŅcW_<���PBånNIoo/ 8@S§ĪtXīãã­N;hņ˛å ǤˆĒá:pāÖŦũIŊzuˇ%py–™•Ĩ?*r›ÉdRƒēĩ•——§fÎWĢæMU§Vūü™ČÆ ôÃĖyúûĘÅÅE›ˇíÔîß÷iüØ;.;Öšķ ZžzZ5kĒŗįÎëį_ˇ¨YĶFruuUĨŠT=ĸĒVŽ]¯Ę•+É×Į[?ũü›,×Á9��ĀŖÜ†IęŌš“Öū´N§ŖīŒ=Ržîú~ō4%§$ĢRĨŠęׯúõîiPĨĨsî|‚>ûjr‘ÛL&“&žķ/­ZģA‰IÉzlÂ=ömCôŅīNÔĘ5ëÕģ{ÅĝŅîß÷_vkžU]oīŦsįôîÄ˙)'7Wę×҈Aũėmîšs¸ĻΚ¯ĪŋųAęØžĩÚ´ˆÔÎŊ|-0���Ž Ķ̝žj“¤ččhMš4éĒ:Ûņû!…ßđ¯æę]���pMíøũĐUŋž0a‚BCCíËåvN ���€›Ą���€Ą%���� E(���`(B ����CJ����ŠP���ĀP„����†rj(1™L˛ÚlÎė���Ā ÎŠĄÄĶÃMŠŠéÎė���Ā ÎŠĄ¤ZX°RĶ3”’–Ž<Ģՙ]���¸AYœŲ™‡ģ›ę׊Љ¨XşKP^ŪLvü~Čč���€ëžSC‰”LęՌpvˇ����nP|û����CJ����ŠP���ĀP„����†"”����0Ą���€Ą%���� E(���`(B ����CJ����ŠP���ĀP„����†"”����0Ą���€Ą%���� E(���`(B ����CY“S%I)ié—���āfĝ����†˛Tđķ‘$Ĩ§z9ĨÃĖŦlˆŠUVvŽōōŦNé���€qÜ\-ōôpWxHÜ\-Nīߊ=ffeëĀ‘“ōõõ–Ÿ¯\Ė܈šĸbãÕŧQŖË���Ā *;'Wį’´˙đ Õ¯]ÍéÁÄŠŠáDTŦ|}ŊåãåI ���nnŽ…(0 ‚NĮœqz˙NM™™Ųōōôpf—����ʉ€ŠūĘČĖrzŋN %V›Mf“É™]���('Ü\-ĘÎÉuzŋ<c���ĀP„����†"”����0Ą���€Ą%���� E(���`(B ����CJ����Ębt’ôŅ'ŸiĮÎ]zxÂũjĶēĨÃļ¤Ä$=ņˇįôÜŗOĢAŊē×´ŽGŸ|F=ēuՀ~}Žé¸×ĘįßN՞?ö_v{d“†ר2Ël6ËĪ×G ęÖVŸˇĢ‚ŋŸSÆqļ™ķëБãzá™G¯‹~KkßÁÃúüÛŠ1¨¯ÚˇiYü���e \„I2›Mš9{Ž"#›ČŨÍÍčrn•*jԐEnķõõqúXŖ‡ ’$åæå)6.^ËW˙¤§ĸô§–Édręx¸˛ŗį4wŅr=öĀ=ĒY=Âčr��ĀMŦ܄’f‘Mĩo˙-_žJô5ēœ›†ģ›ģęÕŠuÍÆĒS̆}šAŨÚ2›ÍšŊ`‰âĪžWP`Ā5Šųl6›Ÿp¯|}ŧ.��ÜäĘM(ņôôÔĀ~}5oÁ"uęÔ^•*Vēlۜœ͙ˇP›ˇlURr˛*øûŠ}ģļ<°ŋ\\\$I=ũŦú÷îĨ¨˜mÛžCVĢUˇvė ŪŊzč›īĻčĐĄÃrw÷ĐāAũÔŠÃ-%ĒąÄãöé­ķįĪkĶæ­ĘĖĖRŊēĩuīØ1ō¯ā/IJHLÔˇßOŅžũäééĨžŨģ*=#CÛˇīÔ[¯ŋr•gŌųļîÜ­ī§ÍŅߟxPáĄ!’¤ŖĮOęƒOŋÔ}wRŗ& ĩ`éJ­^ˇQßųWŠúvuÍ zz¸Û×Ĩ¤ĻiŪâå:xø¨ŌŌ3Tą‚ŸnŊĨnëØÎŪæČąZ´üGEÅÄĘjĩ)<4Xũ{uSíšÕ%IVĢUËüIÛvíŅų„$UŦā§.nQ§öm.[KRrŠĻΚ¯ƒGŽÉĶÃ]Ûĩ.ÔĻŦúÍÍÍÕâåĢĩm×%§¤ĘßĪW­[DĒoÛe6įOũúįŋŪQĪŽ•˜¨m;÷(++[ĩjVĶčaƒäwáŽVIڔäJŌϏk���PRå&”X­Vuëzģ~Z˙ŗf˚§‡¸ī˛m'˙0MÛvėÔØ;īPęÕuäč1}7yǞsr4zäpI’Åėĸå+Ô]cFëžģîÔOë7čģÉSĩīĀAŨ5z¤jÕ|Hs,ÔäĻŠEŗfōöö*﯒ŽģlųJ 4@īŊķĻ’’“ô¯7ŪŅ‚ÅK4vĖhIŌ7ßMŅɓ§õø#ÉßĪOsæ-PtlŦ\-ŽN8“ĨcŗŲ”“›[ä6‹‹‹L&“Z5kĒí;÷hæŧÅzęáņ˛Ųlš5‰š7m¤fMJ’‚ƒÕ¸Aņs~ŦV̤ü7á§ŖcõãOÔēE¤ÃŖb?˜§¸øxŨ3z¸ü|}uäø M›Ŋ@•*úĢiŖĘÎÎÖ˙žžĸ–ÍšhÔĐ’ÍĻuŋlŌ§_MÖ/ūM^žžšˇx…6nÚĒQCúĢFĩ8tDŗ.•ÅÅå˛s'žŸ>GgâĪęĄqcäįįĢõŋlŌÎ=ČÛëĪ×FYõ;cŪbíŪģO#‡ôSDx˜Ž<Ĩs)''GCú÷–$š¸˜õãOÔ¯gWŊöü3JNIÕģ˙§e?ŽÕČÁũKÜĻ$ĮP\›’\��€’*7ĄD6ÉbqŅČÃôŅĮ˙Uˇ.U§NíBÍRRSõķ/ŋiÔđĄjÛ&˙į  @EÅÄhåk4|č`šZō+ĸj¸šG6‘$ĩkĶZßMžĒÚ5k¨v­Zöu‹/SllŦjÕĒyÅōJ3nHH°ní˜÷ĨRÅJŠlŌHĮŽŸ$%%%kĪŪß5fô(5n”˙†~ÂãôĖߟWÅ ¯ęūŅąqzúų׊Üöėã&I9d€Ū|īcmÚēCŲ99JHLŌÃãĮÚÛļmÕ\m[5ŋâXQ1ązâ¯:ŦkP¯ļ†čí°nč€>2›M ¨”>‚´ū—MÚwđ°š6j ķ‰IĘĖĘRë‘  ”$ ØW-#›Čbą(33K~ŨŦ]:ŠMËf’¤Āʕt2*Z+×n(2<$&%ëāáŖ1¨ŸęÖŽiīs˙Á#ö6eÕoZzē6oÛŠA}{ĒŅ×kå€JŠ‹‹×Ú ŋj@Ÿ˛\¸¨v­[H’*øûŠaũē:y*ÚaĖ+ĩ)É1”¤Mq×��� 4ĘŨ쇿‘MÔ¤q#M™>S¯ŧđĪBÛO:-ĢÕĒZ5k8Ŧ¯YŊ𞺞§đ°ü7ŌÁÁÁöíž>š šh‡$)=#ŖØēJ3nÕĒámŧŧŧ”–ž.IŠ;'›ÍĻ:ĩ˙œĮáéáŠF (:&ļØ:œ-°r%5´Čmo6%ÉßĪWƒûõԂĨ+•gĩjäāūĨž‹X9@÷Œ&)˙MbRŠ6ūļEođŠžxpœ*ä?˛įîîĻUk×ëāácJMK“ÍfSZz†‚*įĪ9 Ē  Ā�}7mļ:ĩoŖúuk)<4ÄūØĐáSĮ•——§úuCmZÕõëæmĘĘʖģģã—)ĝ‰—$U‹ŗ¯3™LĒ^5\§ĸc$I§ŖcʤߨčXY­VÕ¨æøē‰¨Ļėœş=§*A’¤Đ`‡6^ž…^ŋWjS’c(I›âŽ��@i”ģP"IŖFĶK¯ŧŽ 7ĒY“&Û233%IEüåŦĖ,û:‹káÃ+ęS\›ÍVlMĨ×ÕõōaĨĻå‡ww‡õŪŪÆL6vsuSõˆĒ%jÛĒySÍY´L.fE6nđÆrĩßy‘¤jUĨF ęęĩw>ԊÕëtįˆÁĘËËĶŋøNVĢUÃöQ• @™Íf}ūíöũĖfŗžzxŧ~\ûŗ6nÚĒ…ËVŠbõëŲUmZ6SfVūĩ˜8é]ü}^Ö ×99%UîŽs–2ŗ˛%žv‡Œ˛î÷Ōׄû…åŦŦ?_[nEŧĻ%Į×ī•Ú”äJtœ•+]ņ���”Fš %a!!ęÚĨŗæĖ[¨õę9lķ¸pĮ#3#Ķa}ƅO‚=ŧĘæYvg[đˆWVNļÃú‚;)åŲ’kTÁĪOšyyZēj­ôî~Õ}Z\\¨čØ8IŌņ“§§'ēOĩjTŗˇKMMŗ?Î%I>ŪŪÔ¯§õëŠØ¸3ZŊūMž1WÁU‚äy!(Ū}ĮP…;Ū5¤Š ˙&Š›[~hȸäúĻ_´\VũÛĖ‹Â‡”˙¨ÕÅ۝Ą$Įœ’RléĘ× "<Ôi5�€_šũE÷AúËjÍ͞ĢÖG„‡Ël6ëĐá#ë9&OO•I=ΡʅvĮގ¯ËČĖĐėsZ­eáäé(­ũųWŌ_#÷Ķęuuōttņ;#77WŅąqō÷ķŗ/K’÷E!S:—¨‚ZįÎ'h÷īūcp• Ō_&“I1ąq –ÅÅE)ŠiĒTŲūįíí)¯"ī–U Ŧ,)˙QĒyyy:tô˜}šŦú  –Ųl֑ã'ö=vâ¤<<Ü폭9CIŽĄ$mŠģ���ĨQ.ī”H’ˇˇ— ŠĶg:Ŧ÷ņņV§Ž´xŲrU RDÕp8pHkÖū¤^ŊēÛŋš×؜5nPP ĒU‹ĐĸĨË,///͚3_ūūūeRwq2ŗ˛ôĮCEn3™LjPˇļōōōôÃĖųjÕŧŠũwF"7Đ3įéīO<(mŪļSģß§ņcī¸ėXYŲY:t$˙͸MRjjĒ~ūmĢŌŌŌÕãö[%IaĄÁ˛X,úéįßÔģ{EĮÆiá˛UĒ_ˇ–ÎğUJjš“ôå÷Ķ4°o5nPO&™´eĮ.™L&Õ¨!wuh×JKVŽ‘ˇ—ĒU ×ų„DÍY¸Lüũôā¸1…jĢTą‚ĒGTÕĘĩëUšr%ųúx맟ŗO0—TfũzyyĒ]ëZšfŊT5,D‡ŽĶú_6Ģ[įö¯v†’CIÚw ���JŖÜ†IęŌš“Öū´N§Ŗ?‘3z¤<=ÜõũäiJNIVĨJÕ¯_õëŨŗLëqÖ¸ŨŸžúî{Ŋķîō¯ā¯ū}{ëøņ“:vüxŲ~įÎ'čŗ¯&šÍd2iâ;˙ŌĒĩ”˜”ŦĮ&Ücß6t@ŊņîD­\ŗ^ŊģwQL܇O΋rö\‚&NúÆžėãíĨˆđ0=6á^Uŋ°Î[cF ÖÂeĢ´yÛNET Ķ]#‡(1)YßL™Š‰“žŅ Ī<Ē1#kÍú_´dÅš˜Í ޤîm˙Æ!ũ{ËĶÃCķ—ŦTRrŠü|}Ô¤Q} čuųGÎîšs¸ĻΚ¯ĪŋųAęØžĩÚ´ˆÔÎŊØÛ”UŋÃõ•‡ģ›fÎ[¤”Ô4Uô÷S¯ŽÕŊK§+žĶŋĸ$ĮP\›Ú5Ģ{ ���JĘôę̝Ú$)::Z“&MēĒÎvü~HaÁÅ7ŧÉeee)7/Īáw*Ūyīųx{둇(uQąņjŪ¨Ž3K���Š´ã÷CWũŪs„ ũsjšžSrŖúp╜œĸģĮŽ–Ÿ¯ŸvíŪŖ}ûčÉĮ6ē4���āš#”āÁÆkęŒYúäĶIĘĘĘV`P Æģ[Í"›]���pÍJ āī¸Īč2���€rĄÜ~%0���€›Ą���€Ą%���� E(���`(B ����C95”˜L&Ym6gv ��� œČÎɕ›ĢķŋĀ׊ĄÄĶÃMŠŠéÎė���@9q.!IžîNī׊Ą¤ZX°RĶ3”’–Ž<Ģՙ]���0HvNŽbãĪëĖšD…‡9Ŋ§Ū{ņpwSũZ:Ģøs ĘË#˜\+;~?dt ���¸AššZäéáŽĩĢ•Éã[NīŅÃŨMõjF8ģ[����7(ž} ���€Ą%���� E(���`(B ����CJ����ŠP���ĀP„����†"”����0Ą���€Ą,‰ÉŠ’¤”´tƒK���p3âN ����CYŧ<Ü$InŽ—���āfĝ����†"”����0Ą���€Ą,Îî03+['ĸb••Ŗŧ<Ģŗģ���pššZäéáŽđ šš:=B87”dfeëĀ‘“ōõõ–Ÿ¯\Ė܈Áõ+*6^ÍÕ1ē ���Ãeįäę\B’ö>ĄúĩĢ9=˜855œˆŠ•¯¯ˇ|ŧ< $���Ā ÂÍÕĸ �TĐé˜3NīߊÉ!33[^žÎė���@9PŅ_™YNī׊ĄÄjŗÉl29ŗK����儛ĢEŲ9šNī—gŦ����ĘųSį���"+;GGOœVBRōeŋ ×ÅÅŦ ~~Ē*O÷k\aŅ%���Ā +;G;÷PXH jU —››k‘í˛ŗstæėyíū㐚5Ž'÷Ë´ģ–x| ���¸=qZaÁ ŠrŲ@"InnŽ ­ĸĐā@=qúVxy„���␐”Ŧ Ę•JÜžJåJJHJ)ÊJŽP���Ü�ōōŦWŧCr)77Wååå•aE%G(���n`oŧņ†Ūxã ŖË¸"&ē���7¨įŸ^_~ųĨ$)==]oŊõ–Á­\„’>ųL;vîēėöV-›ë҇&8mŧGŸ|F=ēuՀ~}œÖgYö{9sį/Ôļģôō ĪÉŨÍ회YV&˙0MûŌ›¯Ŋė”ūfΞĢß÷í× Ī=[ĒۘÎrôø Múę;8xHŪ^^ęÜŠƒÆŊS‹‹Ŋ͂ÅË´`ņ2=wNU‚‚4jø`uŊíV‡~JŌ�� (/ŧđ‚žüōK­^ŊZ’ÔĩkW™Íæryפ\„I ÔŨcFšÍßĪ׊c1LáĄaWŨĪk~ŌąãĮu˙¸{œÚoIüąoŋVŦüQ/ŋôĪë>”…Ąƒj˙ƒš:}Ļî{į5;ūė9ũãÅ×ÔēesŨũꋊ‰‹Ķg_|#‹ÅEã.Ô˛låj}ųíŨ}į(ÕĢ[[ģöėÕû}*o//ĩkĶĒÄm���.įāÁƒZŊzĩš4i"IZŊzĩ^{í5ƒĢ*Zš %îîîjÔ°Á5Ģã-íŌΉ'ˤßâØl6M>S;´WXHČ5ķzãââĸÆč÷>PˇŽˇ)<ėڄEIš5wB‚ĢčoO>"“ɤ† ęŠRŊĘÍ͕”ũfĖž§ū}zhØāū’¤&čÔé(MŸ=OíÚ´*Q��€‹š¸˜•cJdÖŦYۛ4iâ°.;;G•–ĪžüNžAuÕ¨IuŖk)Ö¯›6닯žŅ+/>¯jU%I‡Ņ›oŋĢGz@­[ļĐC>Ĩ~}{*&6NģwīQfV–5l¨{īžK~ž>’ ?fõؓS˙žŊĩ÷}Úˇoŋ>úā˙”““Ģ3įč}”–žĻJ•*Ēk—ÛÔŖÛ풤ˇß}_û’$müå7ũëåôî>tč7''Gsæ-Ôæ-[•”œŦ ū~jߎ­ė/— /€Įž~VũûôÖųķįĩiķVeffŠ^ŨÚēwėųWđ/ō<ėÜĩ[§ŖĸõäãØ×8xXsæÍ×ŠĶ§eĩÚQ5\CRũzu$IyyyZ¸dŠ6mÚĒsįĪĢRĨŠęŲŊĢnŋ­ŗŊœÜ\-X¸HŲ¤´Œ UĢŽƨNíZN=ž„ÄD}ķŨíÛ@žžžęr[§BĮX’z‹;æúõęĒfęZļ|•îŋīž’ŧĜâ—ßļhØāū2™LöuÍ#›Ø˙Ģ3ņgÕŽMk‡ũÚļnŠ÷>ü¯ŌĶ3”˜Xl//Ξ=��p]ŠčŗįU5´J‰ÚĮ=¯ ūÎ}"é¯2ī)ßs{ĩioœĄ…Øl6egįųgŗŲ$IíÛļQĶ&M4ų‡é˛Ųl˛Z­š2uēZˇjŠÖ-[H’\,f-]žRõëÕՇīŋĢŊü‚Nœ8ĨiĶg^vl‹~Z˙ŗÂÃBõÜŗOÉŨÍ]_ûŊ=ǏO¯ŋōĸúöęŠé3giûŽ’¤Į}HÕĢE¨m›ÖšøÁ{Ē^ø“øÉ?L͆5røũûõW5lČ`ũ¸ú'͘=×ŪÆbvҞå+Ē÷ŪySožū’Ž8Š‹—\ļŪģ÷(<,T•$I™™YúpâĸŸN/ŋđœĒ†‡éƒ>VZZē$iÆė9Zž|•ú÷í­7^}I=ģwÕÔé3ĩnÃĪö~g˜­õ6jÔČaúįŗO+((Pī}0QgâĪ:õx>˙ę[>­§DĪ=û”RSRĩuû‡c,ŽŪ’ŗ$5mÜXģ÷ėĩŋ†ĘZJJĒÎ'$ČßĪO˙÷Ÿ5rėxŨußCš2}–ŦVĢ$éttŒ$)$ØņËQ11%j��pąęUC¯SŅqĘÎΚlģėėŠŽSTėU¯z +ŧ<KåĒÕŧę.ŒM2´Ķ§ŖôĀÏší•˙ŠÕĢI’îžk´^xų_Ú°ņWeggëüų=ũ¤ã~öGŠB‚ƒÕåļNZ¸xŠîÎĖ’‡‡{ĄūMĻüīi1lˆ}Ũč‘#d2›˙ŸŊûŽoĒ^˙�ūIŌfuˇt/ ´e´,‘=Ä ČYe”! ΋W¯ūDœWîupŧŽëB'˛dƒė Š Ģ”˛ē'Ũ#Mšä÷GÛ@Ō´IJÚāķ~Ŋxéyō|ŋį$=OÎ÷œ?ßV�€€�ėÚģgĪG÷n]ĄT(!Kāä$1œšYiY>ŠÉņãŅĢö×n??_ddeaĮÎŨˆ?ÎN5Ŗį0°_�€ˇ—7ēÄvÂÕk) öUrōeÃŲ��¸^X€JU%úöéiÎ5uĘ$ôŧˇœœ$¨ŦŦÄî=û0răčס7�Āßß×RŌ°yëv Đ•ĒJė;p“âĮŖ×Ŋ5CƒfΘ†ĒĒ*äææBĄÛĨ=……HLŧ€éS§ c‡ö�€i “qîüC{ŦÉ×R›ëozGŦ�� �IDATDFF`Ũ†ČĖÎn‘ĄnÅ%%�€oVūˆ‘Ç`ėč‘8w! ß|û´ÕZ<2m2**jŠ&Ķ3 EÍtEEĨUëŨL!—ĄkL4ޤ¤#-3§ÁgH$xy¸ĄKĮ((Ė Á p‚L!t WS-Áßßsj/7`ø/OOLŠÕkÖBĢÕaúÔ)đpw7ZŋuX˜Ņtpp4 ŠŠŠ`ūtVģˆļFĶr™ ›ļmGâ…$”––A¯×Ŗŧŧ~~Vĩ'--:mÛÍoÛ:ęĒ*äääŽs 1ZGŠTĸŧĸ ).)†§§§a:ĐßūūøüËe|ß ÄtėˆđđP´Ž�\HJFuĩ1;Åé…ũBĨR!##­k‹?�pvr2Üõė|âģ´'++ģæumZ–‹D"´iŽÔÔt�@JjēÅ|-ĩšNŨąâĸâ)JĒk?ü={täņc��‘íÚĸ¨¨ë7nÁô„‰ÍžĐØ(=Zh‰ÕæBwŠTŠ“ !Ŋ{õď?­†ÄIŒ{ēw­ˇ\.3ŽøęîNU^Ųđž\qãéęj-Ū_ōôZĻLD` ?$" –~ō™Uų€JĨĒ+7~yÍt•ĒĘ0ĪŲŲļ[ÖVVĒ ŋ˜€X,Ƃ—^Ā–m;°īĀAŦ^ģ>Ū^7öaôëĶ•ĩšŧģøˆpã:]íŪX\\bō$“™ŋ“—ŊÚSĮŲŲxדËnÄĩ&_ŋFÛ\ĮĨ…Ī,(kû§IņÖŠC{ŦZķ+rrķāęâ�(/¯€‹RiX§ŧŧ�āęĸ„Z­ļ¸ŅÍĒÔœ:›„ā@_D„‡4øXĩZƒÜüœ>ŸŒŽ1Ґ đøSS”ØbŨúđōōDĩļë7lBüøąFËëj Ķ•5Ķ. ëäŽ\Ŋ‚ôô ŧü⠈Žjg˜_RZ ßV­ŦŠQWä¨*MsŠ98–ßÂEĘ …ܧŽģģ&OÉĮ##3Ûwėė_/GP` ”ĩšĖ™õBBęôöö2œÉ0Í×Ūí‘ÕŒ•&q*nj5ųˇšn¸_ymܖē(ŧ•œQ\Zj4ŋîôŠ““B‚kڔ™•m�陋E ‚˛ļil"""ĸ›]IIGp€/BŋĐ]*uFH?ôĩ¯éŲĻŅõ[‚XčluõZ vė܅Ķ0=a2ļn˙­ŪõI/M_KšŠLoooĢŪCŖŠšuĢĢë"æŌåËČĪŋŊ銰N}……„@,#ųŌeŖų—._…B!ˇz˜9î(**2LįæåãĪ›>„ĶĻB,!#3aĄÁpvrBIi)‚ ˙\]\áæægggøC&•âÂÅdCNŋˇ‡ĩ[{ę†ĪĨĻĨæUWkq!)É0mMž–Ú\§¸¨æZ)ã!~ÍE,Ŗ{—X>zÜhūéŗįáęę‚V>Ū đGP`@ŊuŽûą:B&“YĩŅÍ ‹Kā×ęÆņn||<Μ9c˜>sæ âãã Ķū­ŧQXlüCĒPæL‰JĨÂé3gÍ.‹ÅˆéÔÕÕZ|Ŋü[ôéÕj¯¸§{7|Ŋü[ŧąpáįĸĸbŦÛ°ũz÷FfVvíŲ‡Ū={XũdīА8;;ãˇ{0fôH¤gdâ—ĩëĶŠ#˛˛sP\Rww¸¸(‘’šŠ”Ô4ï÷u\]]0 ?lÚē ~ū~ ARR2vīŲ‹b¸…nSDFFāâMÅAAA>ūô˜8~ēt‰…"9v"‘íÚļ…BĄĀ ũą~ÃF¸šē M›6¸^P€~Zo/O<÷ėĶP(Đŋ/6mŪ o/Obīū¸v-ŗfFØ­=­||Ņ›ˇlƒŋŸ/ÜÜÜđÛŽŨF÷Čļ&_KmŽ“|é2\]]ԂĪs™<q^xųu,ųøw’.^ÆĻ­Û1=a’á6ÁSâĮáÏ˙‡V>>čĐ>Į~˙ŋŸ8‰ŋõĒ!Ž5ëÕŅjuFĮģQQQˆ‹‹3zĸûœ9s ËĨRį/†oiS”äååãƒĨ›]&‹°ė‹Ī°yë6âÅŋĪ7,K˜ ßÄĻ-[1fô(�ũPQ^ˇūõ4j ēv錩S&Y‹ģģf͜ÕëÖãđŅŖhŽĮ‚Â"|öų×xoņ,zë5 ‰Œ/žū‹Ū]Œgž˜S/δ„IPČeøvå()-ˇˇFQÃ‡ŲØ;ÆēvŽÅžũ‘ũ:Zųø }tf=úļíØ‰uŋn„X,APp žyrŽáĖĔIņP*•Xĩz-ŠŠKāáîn]c1aÜC܉ÆC$áį_ÖĸJĨBHH0žûÛĶđķķĩk{æ=> ËVŦÄŌ˙~ šRû DŸ>Ŋņ៧ ëXĘך6ĀéŗįĐ9&Æč™!Í-:˛Ū\øžYų#vŋöOxzx`æ´Œ{x¤a¸ÁQŠRaÍúXųã*āåĖGį˜Ž6­CDDDԐE‹A¯×#..�0{ölŧũöÛgežhÁ‚z�ČĘƲeËn)ØÉsÉđĩKbMeú`Ä;‘^¯ĮÂ7ŪF‡čHLK˜,t:+éâ%üûŊŸᛝ"´ OtĪČÎCˇN‘–W$"""r�ŽÄ€^Ũęͯ+D.\hõksō\ō-#͝;AA7Ž‘u˜3%d=‘H„)ĮãŋŸüq÷ßgtËdĒĄÕjąjõ ĐŋI ŅÂ\1âhnģ ŨŠFL§Ž6ô|ōŲ¨ĒŊ},Ũ°vŨhĒ5˜fð=""""ÆwĻäã%˙:…3nĖhŒ3Zč4Rü„ąˆĮXË+Ũ!$1ÔjÕ7wRĢ5F7Ī”Ũŧ<ܑ“_`õú9ųđôpkƌŦĮĸ„ˆˆˆˆčĐ:4™ŲyHËˁZ­ip=ĩZƒ´Ėddįĸu¨c<ųŽžEDDDDt7RČeč+)éHËĖiđ$‰^nčŌ1 šc<™E ŅB&uF‡Č6B§a3ß""""""AŲĩ(‰DĐéõö IDDDDDB­Š†ÔŲūƒ­ėZ”(äR”•UØ3$9ˆë…ÅÍrŠ]‹’đā�”UTĸ´ŧZÎžĄ‰ˆˆˆˆH jM5˛ķ {Ŋ!~vo×s/r™í#’‘ŧë…ĐjY˜ĐííäšdĄS """œÔŲ š څ7Ëđ-ģG”ˤˆnfī°DDDDDt‡âŨˇˆˆˆˆˆHP,JˆˆˆˆˆHP,JˆˆˆˆˆHP,JˆˆˆˆˆHP,JˆˆˆˆˆHP,JˆˆˆˆˆHP,JˆˆˆˆˆHP,JˆˆˆˆˆHP,JˆˆˆˆˆHP,JˆˆˆˆˆHP,JˆˆˆˆˆHP,JˆˆˆˆˆHP,JˆˆˆˆˆHP,JˆˆˆˆˆHP,JˆˆˆˆˆHPNö¨ĒR#%#Uj ´ZŊÃQ “:;A!—!$ĐRgģ—ö-JTUj$]N…›› ÜŨ\!ķD ÕČČÎCˇN‘B§ADDM ÖTãza1.\JAûváv/LėZ5¤ddÃÍÍŽJ """"ĸ;„ÔŲ ~>đõņDzVŽŨãÛĩrPŠÔP*äö IDDDDDÂĮË•Ē*ģĮuĒPŠ�*ĩ斃éôzˆEĸ[ŽCDDDDDŽGęėĩĻÚîq<Ũ]�eJģ'"""""˛Äū—Α ĒÔ\IIGaqIƒw•HÄđtwG›° (ä˛ÎĐ<%DDDDDw€*ĩ§Î&!8Đá!JÍާVk›_€Ķį“Ņ5&˛ÖkIŧEŅāJJ:‚|čß`A�RŠ3B‚üā‹+)é-˜aÃX”Ũ ‹Kā×ĘÛęõũ[yŖ°¸´3˛‹""""ĸ;€VĢkô ‰)ŠÔZ­ļ3˛‹""""ĸ;ØÛoŋˇß~[č4Å Ũ‰ˆˆˆˆîP ,ĀW_}�¨¨¨Āŋūõ/32Ī!Δ,ũø3˜=Į?QoYqQ1fΞ‡Ä¤‹ÍžĮĶķŸĮ†M[šũ}„ÔRmlĘûŦüūGŧōÚ[�€ŒĖL˜=—._ļ[–Ļ›ÃŨ°O ĄĨûuíú xåõĸJ]ķ°Ų;wã//ÄėyOá˙^y‡Žĩ)ž­ûĻ=ÚkKŒŗįÎãņ'žÁž ķV­^‹×˙ų/¨íđ ŨĻøuĶV<6īYŒŽŸŠĮŸzģöî$"ĸÛÉ+¯ŧ‚¯žú ģvíÂŽ]ģđÕW_aá…B§e–C%� ‹°jõÃ}j“'N@į˜ĄĶ°H&Ģšgļ\fũŊŗwîŪ‹/—-7L[jksô…­9PĶ´dŋžOŧ€í;vâÉyŗ!“JąwßüüË 4/<7ŊzõĀ—_/ĮÉSYĶÖüíŅ^ĶĻûjÜÜ<üøķjŧø÷ŋaЀū†ųãĮ> ‰XŒ~ZuKy4ÅÖģđÕōī0bØ,zc! č‹˙,ũG˙ŅâšŨN.^ŧˆ]ģv!66ąąąØĩk’’’„NË,‡žÕĩKg$^HÂļmŋááŅ#…NįŽÕŋoĄS°Š\&�Hk˙k””TŖiKmmŽž°5jš–ęWŊ^~Z…ūũú 80zŊ›ļlEÜũƒ0âÁĄ�€öŅ‘ČĘĘÆÆÍ[Ņ­kĢâښŋ=ÚkÃt_­Ŗ×ëņâ ķááîn4_"‘`â„qxwņ‡x î>„ßrNÖĐëõøyõ:<4b(&Œ}�ÛŠŌŌ3đĶęučŨŗG‹äADt;HÄPĢ5†‹ŨųåŖåąąąFķÔj œ$’Íą!S”( <<j$ÖũēôˇWÃˇ3Ķh4XŗnŽ˙ūŠKJāéáŽ>Ŋ{aėÃARÛąĪüũxhøƒČČʉ?OB§Ķa`˙~ūāP|ŗâ;$'_‚L&ĮØ1Ŗ0 __Cl­N‹~^…ÃGŽAŖÖ S§xô‘épsu­‰;˙<4r8ΞODbâ,ũđ=Ȥ2lØŧĮŽũëđöö°!q¸˙žA ļĄ)ų—”āįUkp>1 ååđööBÜāû0ôû�GŽĮ—_ƒ×.@xX(� ųŌe,zį}<õÄÜ{Ow<=˙y } ŖGhļ<ŦQXT„oV|‡Ä IP(|ß�“ũAށúÁÍÕ�0įŠŋaėč‘>l¨ae+V"55 oŧē�īŧ˙\HJ�:|ožö Ū˙`‰Q[M™öEŌÅKXŗn=ŌŌĶĄĶé‚ņcĮ }t¤Uíļ&Ģ÷ŨÃQPP€cĮ˙€JU…č¨vxtÆ4xxz˜mËO?‡Q#‡!+;§OŸĒĒ :vÄŖL‡ģ›ĢÕÛÍÜū­T(ŪËR?iĩZ‹ŸK1,-7ŨvÍÕ¯§ū:ôŒLĖö)�@NN.ō¯ [׎FëuíŌ_|õ *++ĄP(đËęuØēc–}ņ™Ų¸Ļų[bĪíÍ1ĖíĢ!ÁAˇ[ûč(´mĶ[ˇũ†ĮgÍ´*÷[•™•Üŧ|ôîy¯Ņü^÷ۃÅK>AEE%”JE‹äBDäčŧ<ܑ“_€Đ ĢÖĪÉ/€§‡[3ge‡)Jt:ˆģ{÷ÄĪŋŦÃsf5¸îĘīĉ“§0cę´iŨ—¯\Ŋ•?@­Ņ aR<�ĀI,Áļ;1}ZfNŸŠŊû`Åʐ˜tĶ&!ĸíXûëŦüūGtīÚ..5^A÷nņüüg‘›—‡å+žĮˇßũˆ§æ=�Hœ°w˙Atí‹ŅŖ†C&•áįÕk°oßA˘–€vmq.1?ü´ ‰ÄhøÃ͚’߲åß"+;ķæĖ‚§‡;.&_Âō•ߥ•7ēwëŠ>ŊzâØņ?°ōûŸđĘ˙Ŋ�Ŋ^ī~ø ÷ö¸÷ŪĶŊÅō°Æ_/GNv.ž{ö)xxz`÷îŊøãĪ“puq­íg {dēUą�āŲ§ŸĀ{‹—ĀßßS§L‚Ģ‹Ōō‹nĸRUaÉGŸ WĪxdÆT@¯ĮŽŨ{ņáŌ˙âƒ÷ßąĒŨÖä`ížģuÛŒ3‹ß]„â’bŧųöģøuĶf˘–`6‰“[ļíĀ”Iņxė‘ČÉÍÁû˙ų?ū´ s �ŦÚnæöo[ûÉŌįÁR ‰Dbņ=ZĒ_O>ƒā ´ōņ�dåä�ü|[­W7“‹6­Ã€ÎąąfcÚCSŋßę˜ÛWúeĩUßccb°s÷čõzˆDĸfkcôĖ,�@`€ņØēéŒŦ,DF´mö<ˆˆn­Cƒpú|͏Nū­ŧ}ĸ{N~2˛sŅĨcTKĻØ ‡)J œœ$˜4q–ū÷<0x"#ÛÕ[­´Ŧ ÅäøņčUû˙ŸŸ/2˛˛°cįnď g§šf……† [—šƒŪ=īŊ•? ]Û6ha˜ˇqĶVdgg#ĸöš‡‡Ļ%L�´iŽ´´4lŨžUUUÉd‰jîé<qÂ8�@ee%vīŲ‡‘#DŋžŊ�ūū~¸–’†Í[ˇ7X”4%ŋ„I!‹ @ūØĩwΞ;o8¨|dz^yíM8tjĩ…øûügíúæČŖ1……HLŧ€éS§ c‡ö�€i “qîü‹¯mˆRĄ„X,““ÄpfĀ× PŠĒDß>=�˜:ezŪÛNN5ŋ´[jˇĨlŲw0°Í/ÜŪ^ŪčÛ W¯Ĩ4چ°°0ÃĀ€� žo�6lڂGTUËeVm7ĶũÛÖ~˛æķ`)F~åmŅRũšœ|Ųpv�TĒ �5gōn&“×LWĒT�j†J5÷ŗĻ|ŋÕ1ŨWmų‹ŒŒĀē ‘™mØ>ÍŠĸĸĻĪMΆ(ŠÚå•ÍžŅíB!—ĄkL4ޤ¤#-3§ÁgH$xy¸ĄKĮ((äÖ_ŋۜ§(ŠÕ­K,bc:áģŸVáõW^ގ<--:mÛÍoÛ:ęĒ*äääÆ:–×ũ ŧéhŨEEå?jQQ7@� ""Zíväåå!$$�ĐîĻ?đ)Šé¨ŽÖ"ĻcGŖ×uˆŽÂūĄRŠ —›ŋ.ÂÖüä26mÛŽÄ I(--ƒ^¯Gyy9üü ¯ņōôĤøņXŊf-´ZĻORolxKäҘŦŦl�@Û6­ ķD"Ú´ GjjēU1ė-ĐßūūøüËe|ß ÄtėˆđđP´žņëÁ­ļۖ}744ÄhĨR‰ōڃŗ†´ 3š‚FŖAQQü­Îŋ]#ŋ:[ę§ IÉ?–bXŗ-n֜ũZ\R OOĪ— Š)ßo ąå{Ŧn¨[qQq‹%DDÔTúF—é[,�‡+J�`ō¤ xõõâĀĄCčj2BUûK¤Üä—Ęē?˜UĒ*Ã<'įúÍsrĒ?OĶVQʍ“IĨ5qĢnÜLޏąNŨ/Ŗī.ū�"ÜĘ ĢY\\Ō`QbK~ÕÕZŧŋä#čĩ:$L™ˆĀ@HD,ũ¤ū˜õŪŊzâĮŸVCâ$Æ=Ũ-ŸšhŽ<Rˇ MŪWnÃEíö&‹ąāĨ°eÛė;pĢ׎‡ˇƍ}ũúôļkģ­Ųw­Ģ!ŽÉĘęöŨōĘ ›ōŋy˙6eПŦų<øûû5ÃŌ{˜jÎ~­ŦTøޝЍ¨4ēÖϞļ°qi¤īė­)ßo ąå{ĖĨ…ĪP¸ēÔ\WV^^å>///¯]nÛPM"ĸ;Y•ZƒSg“苈đF‡oåæāôųdt‰†Ė†§Ā7‡,J‚7xÖŦۀŅŅFËę˜T•*Ŗų•uŋâßâUę*ãéǚii§ļ”ĩųĖ™õBB‚ę-÷ööēĨ|ę\šzééxųÅucX[Ii)|[o_ˇ~#ŧŧ<Q­­Æú ›?~Ŧ]r°5†ÔŨîˇŌd6ö‹Žš‘ëę*ûŪ>ÚŨŨ “'ŽĮä‰ã‘‘™‰í;vâ˯—#(0ú–ÛŨÜûnŨå¸5Ķ. Ĩ]ļ[ÆúÉÚĪCc1Ú´ˇ¸üfÍŲ¯ …ܨÔÜ6ˇî:�ČĘ΁X,B@€u:[žĮĘkûŖĨ.. ŽÉ'3+ÛčZžôŒLˆÅ"Ãr""ޤ¤#8Ā!˙=’Jä}ík:Dļitũ–ā0Ī)15fôCĐé´Øēũ7Ŗųa!!‹ÅHždüPŊK—¯BĄ[=”Ļ!“ã^šv ÎNNđķõ5ģ~Xh0œœPRZŠ Ā@Ã?WW¸šš5éos4šj�€Ģë_/]žŒüüëĐßtzîęĩėØš 3Ļ'`zÂdlŨū›Åkš#ÆÔ¸ĨĻŨĒU]­Å…F+PnōËlZFfũ›x*27/Ūôœ‰ā  ˘6bą™™ļĩģš{ßMēhü€Ņk)× •Éāíím—íXî'k>–bXZnĒ9ûÕÃŨEEE†i??_øûûáğ'Ö;yō/DGE îÛFíώå{Ŧ¸¨�āáŅø°P{ đGP`�=n4˙Čą?ÛŠãí×įDDͨ°¸~­nÜÁ6>>gΜ1LŸ9sņņņ†i˙VŪ(,.mŅâ°E‰‹‹cũÍwuuÁ€ūũ°ië6üyę/ä_ŋŽC‡b÷žŊ:$ÎpûĪĻĘËĪĮ†M[››‡ŗįÎcīŪčqOwÃPS …ƒöĮú qėøīČÍËGbŌEŧ˙áR|eæÁdMgggüļsŠŠŠpöÜyŦüágÄtęˆŦė—” ēZ‹¯—‹>ŊzĸCt:ĮÆāžîŨđõōoQ]mūB§æČÒV>>ˆˆh‹Í[ļáėšķHIMÃō•ß5zŸėÖáĄ8yō/”–•AS][ļĄŦŦĖh%RRS‘’š†R“e–āãO˙‡mÛCVv6˛ŗs°qķˆDb´kÛÖęv7–CsīģEEÅXˇa#rsķpę¯ĶØĩgz÷ėŠÔŲ.Û͚~˛æķ`)†ĨåϚŗ_###pҤØ=röė;€M[ļáBŌEü´j ū:sŖēņ|ĨCGŽâŋŸ~Ūä÷m 7īĢÕZ­Õßcɗ.ÃÕÕA-x=ɔøqØ˛}'V­ųgΝĮWËŋÃī'NbĘÄņ-–Ņí@ĢÕ ؊ŠŠB\\Μ9ƒ3gÎ ..Ņ7B’JŧžĨ9äđ­:ƒ ĀžŊûnō‹ø´„IPČeøvå()-ˇˇFQÇŨŌûiĢu5|ō¯įãÍE˙FĩĻcc0męäF_7eR<”J%V­^‹ĸâx¸{ [×XL7æ–ōš™ģģf͜ÕëÖãđŅŖhŽĮ‚Â"|öų×xoņôŧ÷âÅŋĪ7ŧ.ar<,|›ļlŘҪZ$EoŊf1ÎŧĮga؊•XúßO!W*p˙ čͧ7ūüķ”Ųõ§LŠĮWËWāų—^ĢR‰úĄßŪ8{îŧa!qƒņÅ×ß`Ņģ‹ņĖsljWûč(ĖzôlÛąë~ŨąX‚ ā@<ķä\ÙkÚm)‡æÚw`āĀ~¨(¯Ā[˙zĩ]ģtÆÔ)“�ØoģYĶO–>–bø[|–ę׎cąo˙Aä_ŋnŽÕ¯oo¨ĒǰuûŦûu#üüđäŧĮŅáĻ ņ33˛pō”ų}ŲQ˜îĢÖ~>{cbZävĀuâDĨJ…5ë7båĢ€—˙1c:Z~1Ņ]lŅĸEĐëõˆ‹‹�Ėž=oŋũļĀY™'zã7ô�™™‰Ī?ŋĩ_öNžKFp€ųaNDÔ|l}YG¯×cáoŖCt¤áVáwŗ¤‹—đī÷ãŸožŠĐ&<Ņ=#;Ũ:EZ^‘ˆˆšäĀą“ĐĢ[Ŋųu…Č… ­~McNžKžåīķšsį"(čÆu}Ļ„ˆHH"‘S&ŽĮ?ųâîŋĪpĄûŨHĢÕbÕę58 “ ""ŽšbÄŅ8ė5%DDŽ ĻSG ú�>ųė TŠí{ĮˇÛÉÚu ŠÖ`Zí@"""{â™ĸ;ĀĮKū#t w´qcFcܘŅB§!¨ø cûŨ^œˆˆėO"C­Ö4ø|SjĩĻŅ ĩ$ž)!""""ēxy¸#'ŋĀęõsō āéá֌YE Ņ uh2ŗķ–™ĩZĶāzjĩi™9ČČÎEëPĮx-‡oŨrēÆDãJJ:Ō2s|‰D"—‡ētŒ‚BîĄeQBDDDDt‡IŅ!˛ĐiØŒÃˇˆˆˆˆˆHPv-JD"tzŊ=C‘ƒPkĒ!uļ˙`+ģ% šeeö IDDDDDâzaqŗ\‡b×ĸ$<8�e•(-¯€V§ŗgh"""""ˆZSėŧä^/BH ŸŨãÛõ܋\&Eûˆ0¤dd#īz!´Z&DDtÃÉsÉB§@DDM uv‚B.C‡váÍ2|Ëîå2)ĸۆŲ;,ŨĄx÷-"""""‹"""""‹"""""‹"""""‹"""""‹"""""‹"""""‹"""""‹"""""‹"""""‹"""""‹""ú€ĩ^�� �IDAT"""‹"""""‹"""""‹"""""”“ŊĒĒÔHÉČF•Z­VgīđDDvŅĄ]¸Ã}WI$bȤ΀\&:""ĸc×ĸDUĨFŌåT¸ššĀŨÍ1OÄ‘ãÉÉ/pČī*­N‡ōōJ$]NEtD ""ēkØõ/qJF6ÜÜ\āĒT8Ėy""S"ˆōģJ"ÃŨÍ.JR2˛…N‡ˆˆ¨ÅØõ¯ąJĨ†R!ˇgH""ģĢÖjúģĘÅE*ĩFč4ˆˆˆZŒ]‹^ąHdΐDDv§wđī*‰Xė0ךĩĮˇ@DDDDDw%%DDDDD$(%DDDDD$(%DDDDD$(%DDDDD$(%DDDDD$(ģ>ŅˆčNĨŅUãį´ĩ؟w…š"Ģ^ã-õÂĀV}11t,œÅüē%""j˙JYaUÚ:üššÅĻר ą>s3D" !,ž™2#""ēũ‰‹JĘPTR†Ōō Ąs�|úų—˜9{öî; t*xzūķØ°ÉÛų}-1ÍËŌôŨāČąãxü‰gđĮ‰“ˇ+#33gĪÃĨ˗íY˸›ļųūüÃMzŨcŅ Ø—×´×Ũ-ÄJšJšrŠŗĐš ŧĸ'OFHH0>Úä8;wīŗ˖ßr>“'N@ᘘ[Žsģ2íGKũŅũe¯mŲ\úôꉗ_|ëÖo@FVÖ-ŒÉd��yíoļnsGߞ)PÚüšŲŅSņHäÄ&Ŋ–ˆˆčnâPÃˇŽ˙RŠ3&ÅãŊ˙,ANN.üũũlŽ“’’j—|ú÷íc—8ˇ+Ķ~´ÔÍŅ_öږͩm›Öøŋ—žGYYŲ-őËä��iíoļnķÛa{ڃ"<Õé1ġyHčTˆˆˆn U”<t={ô@‡öŅhåãÃGaėÃÆÔį<õ7Œ=Ç 5Ė[ļb%RSĶđÆĢ đÎû˙Á…¤d�ĀĄÃGņæk¯ (0�kÖmĀņß˙@qI <=Üҧw/Œ}ø!H$�Ā3ķ_ĀC#‡ãėųD$&^ĀŌßË/ŋŠĄÄaô¨�€¤‹—°fŨz¤Ĩ§C§Ķ#,4ãĮŽAûčH�€VĢņÍ[p먏^P�oo/ ‡ûīdČÕÜû(ĘFûڏ–r3Ք~|˙ƒ%FũaęéųĪÛÔ_Å%%øyÕœOLByE9ŧŊŊ7ø> }ā~�0›CHpÅž°Ôļ†ļCZZĻM}h)së…BށúÁÍÕ€•ûĶß˙‡F GAAŽ˙*UĸŖÚáŅĶāáéŅ`[eR™Å؅EEXūíwHŧ…B‰aCâPQY‰?˙<…ũķu›ˇššíšøƒĨMĘ­)ŸŖæĐĪŋ'Žäū^o˜'‚ĪÆĖÆ¸Ö#[<""ĸەÃ%™YY¸rõĻNž‘H„>}záБŖ3zD"‘Õqž}ú ŧˇx üũũ1uĘ$¸ē(ąüÛīpâä)˘:mZˇÆå+WąbåPk4H˜TsņŠDâ„Ŋûĸk—XŒ52Šņ•Ē K>úŊzöĀ#3Ļz=víŪ‹—ūŧ˙\\”øyõėÛw3Ļ% ]D[œKLÄ?­‚D"Á ũ­zs,Åĩ&7[™ëG[X“͞åß"+;ķæĖ‚§‡;.&_Âō•ߥ•7ēwëj6‡Ÿ~Ymą­aē ƒÍ}h)˙ĻôÉcL7ŦoÍūä$–`ëļ;f4ŋģÅ%ÅxķíwņëĻ͘1-Ál[eR™UąŋYņRSĶņėSOĀÃŨkÖũŠĖėl8;™ęiŠ}æļgSskĘįČŪšŒ™Q“ą5mŪ;ũ1tzŊŲ‚äį+ŋļxnDDDˇ‡)J:‚�DD´� čÛ7mEŌÅK ūRmŽRĄ„X,““înŽ(-+ÃÁÃG19~<zõŧ�āįį‹ŒŦ,ėØšņãĮÂŲÉ " •:câ„qfã^/,@ĨĒ}ûôDp` �`ę”Ičyo89IPYY‰Ũ{öaäˆҝoo�€ŋŋŽĨ¤aķÖ톃)Kīcƚ¸–rk Ķ~´•59%Lš‘X ?ßV�€€�ėÚģgĪG÷n]ëå`m[Ãt;ddeŲ܇–ōoJŸÔąĨ­Øŋ/�ĀÛË]b;áęĩ”ÛjMėââœ9{Ķ&#ĻSG�ĀÜ9áųĀËĶËlXjŸL&̎O5%7s¯kiũzafÔd�ĀđĐ8čĄĮâ͟áīąs1*ėÆē/¯Ã˙W’#ŅíÄ!ŠN‡ÃGŽãūÁĄÕj�>>ۈlÃGÚT”˜JKK‡N§CDÛ6FķÛļ‡ēĒ 999 �´Ģ-ˆĖ ô÷G€ŋ?>˙rß71;"<<íŖŖ��’’Q]­ELĮŽF¯ë…ũBĨRA.—[|S)ŠéãZĘMÖä$—ɰiÛv$^HBiiôz=ĘËËāgū:"kúĸŽ­qķvhJښŋ-īaK[CCCŒÖQ*•(¯0ž›ŪÍmĩ&vNnôz="ÛE–+ä tęĐ™YŲˇÜž[É­)Ÿ#{;œs[ŌvbDč�€Ą ‹w'ģÖYue """+9DQröÜyaíú Xģ~ƒŅ˛ôôLL2 2Š´IąU*�@Ž0>X­;°ŠRUŨ˜§P4G,cÁK/`ËļØwā V¯]o/Œû0úõéĘÚ÷ywņáÆpŗēąæÅÅ%†÷lė}LY×ß߯Ņ܄`ŠŋĒĢĩxÉGĐkuH˜2ūˆ$XúÉg Æ´Ĩ­qķv°”¯ŠĻäoË{ØŌVggËwÎģš­ÖÄ.ĢŊE¸éĀ\\\ėŌž[É­)Ÿ#{Ķéõxī¯O ŅUãáđ Ų”ēŸž˙F¨ôˆˆˆn;Q”<|‘í"0yĸŅüjī,ūž<…>Ŋz�Ė]]ĸŽR7ģîĀEUŠ2š_YYYŗ\iũģģ&OÉĮ##3Ûwėė_/GP` ”ĩī3gÖc Ē÷ZooķC^,ą6ncšĩi^īuļöcS4–“FŖFzz^~ņDGĩ3ŧϤ´ž­Z™gm_4ĩmļôá•ĢWlÎߖ÷hŽũÉÚØšyy�€*qŋ™ž1eë~ؔÜ…z|xæs�0&�°)õ7,>ũôĐ7ôR"""2!:ēg“ôéŨmZ‡ũ‹Œl‡N;āđ‘c†õrĘ+*b¤edÖ\{<ąXŒäKƤģtų* yƒCmLåæåãĪSσƒ‚0cÚTˆÅ"ddf",4ÎNN()-EP` áŸĢ‹+ÜÜÜŦú5ÛkâZĘÍ[ûŅV–rŌhĒ�ŽŽ7. ŋtų2ōķ¯×?˜ĢۖVöąÕmŗ!_S6å߄÷hŽũÉÚØūĩŸ‹ĢWŽ^WŠĒÄųķ‰ Æĩē}ėSÍŲîæPW˜üš˛ �°%m'ūsæS$DDD6üLÉąãŋCĢ­Æ=Ũģ™]~oîøfÅJÁĶĶ­ÃCqōä_6$ršÛvėDYYŧjo �..J¤¤Ļ"%5 ŪŪ^Đŋ6mŨ??„…† ))ģ÷ėŃ1Üؒ‚‚|üé˙0qü8té D8rė8D"1Úĩm …BAûcũ†psuA›6mpŊ �?ü´ Ū^žxîŲ§›Ô?ÖÄĩ”›9MéG[XĘIŠTÂŲŲŋí܃1ŖG"=#ŋŦ]˜N‘•ƒâ’x¸ģ×ËÁš>ļĻmļæk*4$ÄĒü›úÍĩ?YÛĪĪááaظe+‚‚ T*ņ˚õđđ¸ĩ>´´O5g웋z,9û9Î^ό=Fˇ&"""ë^”<tŅ‘‘õāętīÖËŋũGŽĮđaC1eR<žZžĪŋô \•J ĐũûöÆŲsį ¯7_|ũ ŊģĪ<1Ķ&A!—áە?ĸ¤´ŪŪ^5jF fužíŖŖ0ëŅG°mĮNŦûu#Äb ‚‚ņĖ“sā�˜2)JĨĢV¯EQq <Ü=Đ­k,&ŒsK}d)Ž5š™‹ik?Úšœf͜ÕëÖãđŅŖhŽĮ‚Â"|öų×xoņ,zëĩz9XĶĮÖ´­)ųŪĖŨŨÍĒüoå=šk˛6öĪÂ×+žÅģīO<4r8Ž]KÅÕk×ĖÆ´Ļ}ÖėSÍŲî[á-õjđÉė:ŊÛŌw7ōZīæJ‹ˆˆčŽ Z°`�˛˛˛°lŲ˛[ vō\2‚|í’ ĢĒĒ ÕZ-\”7†¨ŊģøC¸ē¸ā)‹TG“‘gķwÕŠŋ`]Ææ&Ŋ߸āQ˜6ÁĻ×ddįĄ[§ĻßyˆˆČ‘͝;AA7ŽüL 9Ļ%}‚’’R<2#înîøëô$^HÂügž:5AćŒ�ėË;ÜāSŪRoÜįÛB„=ËCDDäčX”‘YķæĖÆ?˙‚?ũUUjøúųböc k—ÎB§&gąÂâ‘/t*DDDw%Dd–‡‡;ž˜3Kč4ˆˆˆč. ø-‰ˆˆˆˆčîÆĸ„ˆˆˆˆˆÅĸ„ˆˆˆˆˆÅĸ„ˆˆˆˆˆÅĸ„ˆˆˆˆˆe×ĸD$A§×Û3$‘Ũ9úw•V§ƒDÂߌˆˆčîa×ŋz šeeö IDdwN‰CW•—WB&u: ""ĸc×ĸ$<8�e•(-¯€V§ŗgh""ģŅCīßUZĨå(̍Dxp€Đéĩģ><Q.“ĸ}DR2˛‘wŊZ­ãüą'"ēY‡vá÷]%‘ˆ!“:Ŗ}Dä2ŠĐéĩģ?Ņ].“"ēm˜ŊÃŲŋ̈ˆˆ¯¤$"""""Aą(!"""""Aą(!"""""Aą(!"""""Aą(!"""""Aą(!"""""Aą(!"""""Aą(!"""""Aą(!"""""Aą(!"""""Aą(!"""""Aą(!"""""Aą(!"""""Aą(!"""""Aą(!"""""A9Ų; ĒJ”ŒlTŠ5ĐjuöODDDDD-Lęė…\†@?Hí^BØˇ(QUŠ‘t9nn.pws…D|gžˆÉČÎCˇN‘B§ADDDDÔ"Ԛj\/,ƅK)hß.Ü]̆”Œl¸ššĀUЏc """"ĸģÔŲ ~>đõņDzVŽŨãÛĩrPŠÔP*äö IDDDDDÂĮË•Ē*ģĮĩkQĸĶë!‰ė’ˆˆˆˆˆ„ÔŲ jMĩŨãrŒ ŠE  ŠE  ŠE  ŠE  ŠE  ŠE  ŠE  ĘIč�`Ņ;īÃÉŲ/=?ŋŪ˛ĖŦ,,xõM<5īqÜÛã�ĀŌ?ÃÉS­'‹ŅĒ•ēvéŒ1ŖGBŠP–-ũø3ä_ŋŽžž°ybÁËĀ™ķ\Ū%ļ#fOŸl—÷ZøöûčŨŖF=ø€]âŲB¯×cÁ[īaōøŅčĶĄŪôíėĨ7ūÁúâÁ¸AB§Ō$wŌļ ""ĸ;‡C%Máįį‹G™n˜ŽÖhp-%[ļî@zzūņ÷ŋAä€O—oåã…ÉãF›]æææÚĸšė?| )i˜>iœŲéĻĘĖĘAyEĸ"ژ&áp[‘#ēm‹™L†ŅQFķbc:ÁÃŨËVŦÄĨK—ŲN ė&“Ę!t�€´ôĖF§›ęBōe„…AĄ›&áp[‘#ēm‹’†DÔū|Ŋ ‘įŌTœ:o\ƒ˙6!A�€+×Rņá§_aÖôÉčÛŋn؁]ûáŖwßl0ŽH,ÆÖ{qāđ1TVĒÕŽ-ĻM7W,ũß2\ēr �püÄ)„†Š’ã'NáĨųO`égË0ôūČÉËĮšÄ$TUŠŅ>ĒâĮĀÕĨfxœš<.$_2*ŧnžÖétØļs/Nüu…ÅđōtĮā}1 OOĢÛ^ZVŽu›ļáâĨ+(¯¨„—§;öíûú÷6ŧįËož‹Ą÷DvN.N=ŊN>=ģãûāĮÕëqéj dR)F‹CīŨ��˙xu‘Åöš˛ÔsĒĩZlŲąĮOœBeĨ !ÁxxÄP´mVŗŧē›ļí‰ŋΠ¤´ în¸ˇ{Œz?Äbq“ÛgnÛ9‚;îB÷œœ\�€ˇĀ™˜§×륊Ž6ûO¯×�zt팘QXĩnôz=t:~YŋŨ:wBרŽ�€�?_Ätˆjė­đį_gPVVŽyMĮĖ„x\IIŖģ�sf& 48ŨģÄâ߯˙žš=Ãh:(Āb‰;÷DdÛÖø×k/áĨįžDZF&ÖlØbxĶ<ĒĢĢqųjĒáĀ×tzŨĻíØšī†Ũ? ūū4îĐk6lőã'Ŧnû÷ĢÖájJ*f&ÄãåįžÂÁąvãVœ>—hČC"c÷ūCˆíÔīŧū=bvī?ŒOŋūCÄģoŧŒ^=ēaÕڍ¨¨Ŧ�ĢÚkĘR{Ėžfã69~ãŽŋ=1 ­|ŧņÉWßâzA!�āįu›pä÷?1vÔ0ŧúgņĐđ°īĐQŦßŧũ–Úgē-ˆˆˆˆÅm}ĻDĢÕūŋēēW¯ĨâĮUĢŒvmĖŦa™Ų9øû‚ˇĖ.ûĮŗs �˜4n4-ū/Žũqj…EÅxrö ÃēŊztC¯›~7G!—#~ĖH�@XHū:›ˆkŠé†ebąNNÃY�Ķi� 4ŧŋo+ôīĶÛvîŔņjHĨŌzy\š– čõhZoZĨǁ#Į1tđ�ôŧ§+�Ž•7R32ącĪôéyUm?zÄb|ŧŊ��~ž>Øø/^BįN7.Ū DL‡h�5ÅÎĪk7ĸMx(ÚÔæÖŖk,ļīڇÜŧ|´ ĩĒŊ7ŗļ=F¯ŠĒÂáã'0fä0tī�˜2áaTŠÕČË/€\.Ãņ§j—Į�Zųx#''{ÁčCá$‘4Š}ĻۆˆˆˆČQÜļEIZZ:fÍ}ĘhžH$BlL'˜1Õ!/rjZgLovY€Ÿ¯á˙=ÜŨ0vÔ0üēe´:&}nŽ.6ŊW›đ0ŖiWW¨RĢlŠh4čī‡ęęj—ÂĪ×§Ūú’/#ĸm8œœœęM_KM‡VĢEû(ãk}"#ZãČņ¨ĒRC&“ZlģL&Åo{öãâĨĢ(+/‡^¯GyE%üZįãīÛĘđ˙ršŦvžīMķjŽĢ¨ŦŧŅ'ļ´7=3ËĒöÜ,+;ÕÕÕ 6Ės’H w]ģxé t:ڄ‡Ŋ.,4jyų×čīפö™n""""GáG'bąĐéĖ.ĶÖǘHøûcîãĻwíŲ‹ŋΜÅÜŲÁĨņ˙Ž@ę,5ü*oInąfãVHĒ&ŨžU&u6šŽŠĶôļŐÉĖÆŦTUš]?)ų2ēuŽ1;­ĒĒ98ūčķopsɨĢļVRZ_YͰģ†ÚŽÕjņɗ+ Ķé0ááđ÷ķ…X,ÆËŋ¯—‹““¤Ū<gįúģŧūĻ>ąĨŊļ´§NŨP*ŠÉļ1)7ÍŖvēĒęFekûLˇ ‘ŖpˆĸÄÍÍ é Üų)?/�āéáa4ßYęŒ6­Ã Ķ“ã'āÔ_§ņķęĩxė‘i͗l Úŧ}7<ŨŨk.ŒūmFŌâ9TЌĪŦ¨j§• EŊu+**‘–‘…)ã6;­¨ũåū‘)ãPīõ^žî†˙o¨í×RĶ‘™ƒųOĖBD›ÛŋŦŦÜ0œëVØŌ^[ÚSĮÕÅÅ(ŽŠēŗĒ*ķyÔ-ˇ•éļ """r$⠕*5Tj`IÄtę„ėœœ=wŪhžN§ĮĻ-Ûāíå…Ö&C‘Lšēē`ÂØ1Øā ’.^jÎt[Djzö<‚IãÂÄąŖ°kß!¤Úé–ŊļHŽŊCW”ô HáééQoŨ¤K—ĄT(`v:80�N JËĘáī×ĘđĪÅEWWĨaXQcm¯ŽŽ�¸(o WSŌpŊ°zÛNŨr{­mĪÍü}[ÁŲŲŲpį3 ææK>ûĮOœBH`�Äb1._K5zŨՔTČå˛zCÔŦeē-ˆˆˆˆ‰“§{Íû*ʄōÔŋoė?pú† ‹Cë°0””–bßūƒ¸–’‚gžœg¸jcč}ûbųˇßá­7ÂųσB•J…ĶgÎÖ{MHH0ŧŊnũvkŠĒĒp>)Ųė2‘H„Qí ÕjņũĒõčŅ­3"koqÜ%Ļž_ĩ/ūm$ ŽŸ8…Ķį1{Ɣ&įĸTʑž‘…ôĖ,xyzԛ€â’lŲą=ī醜Ü<8r÷t5ôíÍy\Hž‚¨vm ×ķ˜NËå2ôëŨ›wė†Ģ‹áĄ!((,š [áéáŽyMŗØöā �899aīÁŖ>d02ŗs°aëohÜŧ|”–•Û|íÍÍ,ĩ÷fÖ´�>Ž?NÆsOΆ\.CŸ{ģcûîũđôpG€ŋũŠé™˜6q,”JzßÛ;vBƒ‘|ų*ö>Žõŗęs`Žéļ """r$1|ËÉI‚ŸŸM[ļā§°mÛo8IĐ."/ŋø"ÛYw SąX„iĶ&㟋ŪÅæ-Û0fô(Ã˛ŧŧ||°ôãz¯™õč č××nmąäzA!>ûzĨŲe"‘Ŋû&~Ûs�EÅ%xfîLÃ˛ņŖGāí÷?ŽŨû1|Č`dåäâô𠎔Ë}ũú`ÅOĢņá'_aöŒ)õĻ oĪ{PQŠÂû˙ũ4šjÄvŒ6ÜŅ €QI/ačũ ËL§`ÜCÃĄËą~ķ—”ÂŨÍąÚcôƒ5ÃŗŦiû´‰cąaëo8~âÂBƒ1}Ō8—ā›īVáŖĪŋÁ+Ī?Ũä>ąÔ^S–Ú�EE¸š’f˜3jD"ÖoŪU•ÁūxrÖt´ĒŊuü˜‘Ë¤Xĩn#JËĘáåáŽãaČāMn—šmADDDä(Doŧņ†�233ņųįŸßR°“į’ākyÅÛ\Fvēuē]ÍhŊ—Ūø7č‹ã J‹¸ÛÚKDDDÔ'Ī%ßōąđÜšsd˜žãžHDDDDDˇ%DDDDD$(‡¸Ļ„Ķģoŧ,t -ęnk/‘Ŗā™"""""‹"""""‹"""""‹"""""‹"""""”]‹‘H^oΐDDDDDä ԚjHí_ģ% šeeö IDDDDDâza1r™ŨãÚĩ( @YE%JË+ ÕéėšˆˆˆˆˆĸÖT#;¯�š×‹čg÷øv=÷"—IŅ>" )ŲČģ^­öÎ-LNžK:""""ĸ!uv‚B.C‡váÍ2|Ëîå2)ĸۆŲ;,ŨĄx÷-"""""‹"""""‹"""""‹"""""‹"""""‹"""""‹"""""‹"""""‹"""""‹"""""‹"""""‹"""""‹"""""‹"""""‹"""""”“ŊĒĒÔHÉČF•Z­VgīđDdڅ;ÜįT"C&uFxp�ä2ŠĐéQ ˛kQĸĒR#ér*ÜÜ\āîæ ‰˜'bˆMN~C~Nĩ:ĘË+‘t9Ņa,Lˆˆˆî"v=IÉȆ›› \• ‡9Đ!"c"ˆōs*‹áîæĨ)ŲB§CDDD-ČŽG$*•J…Üž!‰ČÎĒĩZ‡ūœē¸(PĨ֝ĩ ģ%:Ŋb‘Čž!‰ČÎôū9•ˆÅs ĩ ĮģADDDDDw%%DDDDD$(%DDDDD$(%DDDDD$(%DDDDD$(%DDDDD$(ģ>ŅˆîL:é™9Č/(„ZcŨ3D¤ÎÎhåㅐ@ˆÅŽ{ b"""‹"˛(=+•*:ĩ€L*ĩę5Uj5ŽĻf"#+ĄÁÍœ!ŨΞõîâąđ6¸<;;3gĪÃÎŨ{­Š÷ôüįąaĶ–§ÍYųũxåĩˇŦŠo kŪÛQ˜æšũ:ŪZôŸ÷4vüļËaÚŌ\ۊ—Ŋ­Ã‚Ŧ.H�@&•ĸMXōŽ5cfDDDt'ŧ(é߯ŌĶ3––nvųáŖĮāä$Aīž÷6)ūä‰Đ9&æVRŧ#íÜŊ_.[n˜6í§##3 Ī?÷7ôęyoŗôŖiä¸ÔMI™Tjõp/"""ē{ ^”ôčŪ r™‡7ģüČąãčÖĨ \]]šŋß>hŨ:ėVRŧ#Ĩ¤¤M›öSyy9Zųø }t$<<ܛĨMs """ĸģ“āהČd2ÜÛŖ;Ž;Žøņc.ˆMžtyyų˜:y"� ¸¤?¯Zƒķ‰I(¯(‡ˇˇâ߇ĄÜß`ü§į?ĄÄaô¨�€Âĸ"|ŗâ;$^H‚BĄĀāûÔ{VĢņÍ[p먏^P�oo/ ‡ûīdXį™ų/āĄ‘Ãqö|"/`é‡īAŠP6ÚVkâšŌTWã× ä0ã�� �IDAT qčđ1”WV"<4'ŒCdģˆ&įÚēu8’/]�:|ožö Ū˙`‰ĄŸŊķžaųĖŲķ0aÜlÛņ›Q?ZĘËŌļzįũ˙āBR˛QáaĄFmˇf[Íyęo;z$†j˜ˇlÅJ¤ĻĻáW˜íͤ‹—°fŨz¤Ĩ§C§Ķ#,4ãĮŽAûčČ&÷ihh”J%^xîYŖ÷ú`éĮ(¯¨ĀĢ/ŋhU\kÚĶ”}ˆˆˆČ‘ ^”�@ŋž}pāĐa\HJBĮí ķ=wtŽ­6´lųˇČĘÎÁŧ9ŗāéᎋɗ°|åwhåãîŨēZõ^_|Ŋ9ŲšxîŲ§āáéŨģ÷â?OÂÕÅÕ°ÎĪĢ×`ßžƒ˜1-í"Úâ\b"~øi$  č�Hœ°÷˙Ûģīø¨Ēü˙㯙$3“é !€ôŪģ ‚ EŠ"¨¨ØËŽĢĢģëĒģūvUžģÖĩWTŠR¤7éŪ Ŋ¤÷™ßC&™$IH˜ īįãÁCįŪ;į~Î9ÉÍũÜ{îš+WĶĒesėŲd.sߎ”[â;S§ąaã&î3ŠĐ/]Ƥ˙žÃ?^ūĄ!ĩ*kXX˙÷ßw ãîŅ#ņķu>Ą}æŠĮø~ętöī?Č Ī?‹ŲlbÁĸ_ĘWY}õäãđƤˇ.ƒĢ}U^YYŲŧõÎûtėЎņã%K—ķߡßå?oū__Ÿ ĩéĄCGøaÚ 223 BFfģvīfäđģ*Ü˙ĨŠČĪžˆˆˆHuV-’’† ęR‹5ŋŽs$%yyųlØ´™n]ē`4ÚG™9ƒŅHhH-�ÂÃÃX˛|ŋíÜåRRrūÂvīŪÃ=wvėgė˜QėÜĩĮąMff&K—­`ĀíũčÚĨ�aaĄ9zŒyķ:N 0™ŧq×P—ęčjšNßÉĘdÅĒ5Œ>ŒŽíÛpī¸ądggsúôiüũ|+ĢŅ聧§ū%Ođ}ŧ}đōôÄh4–ēžŦ¸BCj•ŲW>Ū>WŒÁ•žĒˆsΓ™•I—ÎˆŠ°Īu÷č‘thßOO ÷­š5™2uÛļī sĮŽ�lŨēĢÕJ‡vm+Ô˙—S۟=‘ęŽZ$%ƒŽ;ą`ábƍÍÁl2ąãˇßHKK§[×NŽí,f3s,d÷žŊ¤ĻĻaŗŲHOO'<4ÔĨũœ8q€ēquœöKB‚ũAûŖ ĮÉË˧Y“&Nßmܰ+W­&++ ‹Å@ũzu]ŽcyĘ-˜˜Dnn.uęÄ:–yyzōø#Øŗw•ÄZ–˛â‚kĶWFxX}ō9ŊzŪLŗ&MˆĄQÃ@ÅÛ4((ˆ† nb˖mޤdãæ-4iܘĀĀ€r•ëŠĘėOwĢI ؇p͜=—-[ãéÜąk×m .6–č¨(Ā~įä͡ŪÁ–oeĖčDD„áađāí÷?pyYYY�xy9WÛb.<Ėŧ´Íë“ūƒÂį[Ŧ6�ÉÉ)Ž“G‹ˇˇËû.OšŌĶ3�0›KŸõ¨Ēb-KYq]ĢžĒŖŅȋĪ?ËĪ ąbÕjĻ͘IÍā ŊķēvîtUmÚĄ}[ž˙q99šä[ķØšk7ãīTŦ˙¯¤2ûSDDDÄŨĒMRRĢ&6`ũú´j؜ømÛ9|˜cũĄÃ‡8~<‘ž{–† ę;–§¤ĻRĢ–Kû0›ícī33ŗœ–gdf:ūßįŌÉŪCî'::˛DÁÁ5\¯T)×ß߀ŦbņVuŦe)+ŽkÕW�ĨŊ'<';įŠeø3jÄ0FFbR -æ“Īž$2"âĒÚ´]›6|ķŨ÷ėÜĩ‹ėK1´Ŋ4ŦĐÕr+R‘ëÛ§.Ēk—NėÜŊ‡ˇ`ŗYŪM’››€Ÿ_áŅäėŲsذšT~xx� EŪ‰’——ĪžŊ{ŸkĮDáåéIJj*‘Ž~ž~øûûãååUĄēU¤Üˆđ0Ė&{öíw,ŗZmüëIŦYģîęcu­ŲĘWšúę21¸ŌW�ŪoŌ3œ•c‰I—ũô™ŗl‰ßæøɸąwc4HLJēĒ6 đ§qŖFÄo˙­ņÛhŲĸŪ—’WË-o}DDDD~Ē͝€ömÛōÍw?0}æŦī&‰‰ŽÆËˋ_/cČāOLâĮ3iÖ´ 'Nž"9%…Ā€€+–_ĢfMęÕĢËŧŸ‚ŋŋ?ŋ,YЧ‡‡cooonîŅ™ŗįāīįK\\įΟįģī§\#ˆgž|ŧBuĢHšŪŪŪtīօšķæ\#ˆČˆ–¯\ő# L¸ˇŪUÅęëëÃŅ„Ž&+÷•˛â˛X,.õUņüũ xwĨ¯�ęÄÆ°uë6në͋łE‹IKKŖFP`ŠąŸ?ž÷ū÷!#† ĨeËæ0đëú  Fę×­{Õũߥ}[æĖũ™ŒĖLî?ΊÍ\)ˇŧõų=¨VI‰ÅbĻ]ÛÖŦYģÎ1CQ€�&Ü;Ži?ÍdíēuԉåÁûÆqūÂE>øč3ۘô¯ŊúR™ûxøÁ |ūÕdŪ~÷X|ŧšåætî܉-[âی9ĻN›ÁÅäiŨĒ9w rUõĢHš#î†Á`ā‡g•EttĪ<õ8ĄĄ!WkŸŪŊøøŗ/xíõI<ņČCåŽKYqšŌWÅchŪŦŠĶ>\íĢOŋüŠ?>˙ü||čŅŊ+ŨētⷝģJģQÃL¸o< -æ§Ys0=ˆŒŠā‰G':îÎ\M˙ˇk͆¯ŋ™‚ÉäEËֹ͜Rnyës­˜ŧŧČÎÉ)÷[Ũŗsr0y•˙Mđ"""rc1ŧüōË6€¤¤$>účŖĢ*lëÎũD…‡TJ`"R5Ož)÷īéąÄ¤gfW;ŌåÄ$;'‡Ã Iøz›‰‰Š(wŒ­›ŪTŽīˆˆˆČõcâĉDF>g[­î”ˆHõN≓ėÜsœÜ\—žcō2R3¨ˆđ*ŽNDDDŽwJJD¤LFŖ˜¨ˆrßņqEĩš}KDDDDDn<JJDDDDDÄ­””ˆˆˆˆˆˆ[y^LI 5=ÃÍĄˆˆˆˆˆČČĶĮbŸŪĶbĒØ›ĘEDDDDDކ†o‰ˆˆˆˆˆ[UjRb0°Úl•Y¤ˆT˛ęū{šoĩâáĄë%"""7’JũËīm1‘–ĻgSDĒ3Ojũ{šžž‰YÃIEDDn(•š”ÄF…“–‘IjzųVke-"•Ć­Zūžæ[­¤Ļg–‘Il”Ū/""r#ŠÔ7ē[Ė&ÕĢÍŅēœ9wüüęsÂ#"…׏­vŋ§FĖ&/ÕĢÅlrw8"""r UjRöĤaŨڕ]ŦˆT2ũžŠˆˆHuĄ§IEDDDDÄ­””ˆˆˆˆˆˆ[))ˇRR""""""nĨ¤DDDDDDÜJI‰ˆˆˆˆˆ¸•’q+%%"""""âVJJDDDDDÄ­””ˆˆˆˆˆˆ[))ˇRR""""""nĨ¤DDDDDDÜJI‰ˆˆˆˆˆ¸•’q+ĪĘ.0+;‡Ŗ‰'ÉÎÉ%?ßZŲŋˆˆˆˆČ5fōōÄÛb&:"“WĨ§•›”deį°÷`ūūžøûáaԍ‘ę(ņäZ7ŊÉŨaˆˆČu"'7s’Ųsā(ęĮVzbRŠYÃŅēøûûâį㭄DDDDDäwÂäåIDhMBjqüÄéJ/ŋR3‡ŦŦ|ŧ-•Y¤ˆˆˆˆˆT5k’™•]éåVjRbĩŲ0 •Y¤ˆˆˆˆˆT&/Orrķ*Ŋ\ąˇRR""""""nĨ¤DDDDDDÜJI‰ˆˆˆˆˆ¸•’q+%%"""""âVJJDDDDDÄ­””ˆˆˆˆˆˆ[yē;�€ˇßû€­ņÛ.ģūõ×^%,,ôFTu&;…={÷ķÚĢ/š;”*S´Ž‰IIüåĨWųë ĸ~Ŋz*īņ§˙Hß[{3xāí.}רŨ\ķö{pöÜ9ūņ÷ŋ–Xˇo˙~ūßë˙Įķú6āŖO>į×õŽXŪ¸ąŖŲŋ˙ KÛŨŌķæRū~~DGGqĮ 4jxSų+u>ūō;vėÚsŲõ-›7á{FUŲū§ÎœËūƒGøË¯˛}¸ęų—˙E¯î]č×ûær}ĪfŗņâĢo0jØ`Z6kĖæøLŸũ3Šiéŧņʋ|øÅ7xzzōÄC÷–øîŠĶgųį¤w¸ėHZˇhęX~āĐ/_MBbééXĖfęÅÅŌ÷–ÔŠíØŽ<ũWÚļFŖ‘āA4oԐū}zámą”(ûūą#hŨĸ™Ķ÷RRĶøË?Ūāɉ÷qSŊ¸ûũü›ŠlŨūŖ† ĻkĮv%Ö”}Įí}šĩgˇëSĶŌųë?ßÄjĩōöŋ_Æh4ē\×w?ū’ÃGŅŽu ÆÜuĮeˇ/úîúčģŖĮYšv=û&%5 /OOjÖŦAķ&čÕŊ3>ŪŪ—ũîõ¨ĸĮ&wĢI @hhãĮŽ)u]P kk/]Îá#Gxđū{ŨJĩe6›°\ú¯+Ўë¨wuŲíËZ_ę[ЈˇßFˇŽŸ?ųė+ĸŖ#é[Į˛ˆˆpÜTßĨí „††pßø{Ÿ/^ŧČō+y}ŌøÛ‹ĪS7ŽNÕTč2jÕŦÁ¨ĄƒK]įīīwMcq§ĄûVîī%8EzF .āM›5F ę3t`?,׏•ö<Ė{Ÿ|EÛVÍšgäPü|}¸p1™_–­âžāOON$ĸȅŊōô_­š5s×Įįŧŧ|“XŧlI'Nņ؃ã1 ŽõƒŸæ.¤Yã†xyyšff;ví!2<Œõ›ļ–zb āååÅúÍņĨžØnŽßŽŅhÄjĩ–ˆŋŦē>8~4ÛÛÍäfĐš}âbc\ŠÔwÕŊīrķō˜ōã,ö<ÄÍ];ҧg7‚ÉÍËãxâ V¯ÛĀk“ŪåŅ ãˆŠ /mWr U›¤Äl6Ķ´Icw‡Q.G&¸;„jĪbļ_‰1™-elY¨xģvëŌų2[ēļž"ÔˇRŅQQDG&Č&ŗ%ŽmÁ5j¸´]ŗŲLㆠœ–ĩmŨšį_üŋ,^ĘÄī¯ÄZ”Íl2ĶđϊŨųü=éØŽu…žˇg˙AjGGâím!'7—´ô ÚĩjQá„nåÚõD„…2nÔ0Į˛˜¨HÔ¯Ë˙Ŋ÷ u:ą-O˙™MæWĮ7ŦO€ŋßũ8“ÃGQˇNmĮēæM˛īāa¯XC˙[{ē´MņÛ1yy1tpŪûøKΜ=OH­āÛÕ­S›Ŋûr,1‰˜¨H§uˇlŖvt$‡Ž8ģ]ŠĢÅlĻCÛVL™>›sį/¸œ”¨īĒwßY­VŪ˙ä+|}|xéų§1I´,˜iܰ>ÖgéĘĩ|øÅ7üåŲ'ĘuU*_ĩIJ\‘››ËôŸfŗaã&’SR  s§ŽÜyĮ <<<�xâég4 ?ŋíÚÍîŨ{xûŋođü_ūΠūũH<q‚Í[ļbĩZéŅ­+ũûõ勯ža˙ū˜Íî2î]ģ�œ’ÂS§ŗk÷^Ō3Ō ŽAī^=é{ë-�üûÍ˙cĪŪũ�ŦYģŽW^ú ąĩd.^䋯ža÷žŊx{{ĶĢg÷uzčą§¸sđ�úßÖ׹ėķ¯&“pŒ—˙öbŠíđÄūÄ ÛûsūüyÖoØDVV6 Ôįžqc tŊ­ūđ§rˇKqeÕŅÛÛBî]ņ÷ķuŠžĨĩë›˙yëŠÃŒŠCÚģī�ĶšÉąãĮąZmԎ‰f؝CÃ\*ŌˇŅQ‘Ėž÷3ë×oâÜųķ×āļ>ŊšĨgá­QWú˛´ŸOo§ú”eí§¸üüü2ëX\YąēRfņXcbĸņņņáŲgžtÚ×Ū~ôŒ ūöÂs.•[UíT]˜L^ÄÄDsęôw‡RĒMņÛųzĘtž{ęaĸ##�8t$˙ūīS&Ü3ŠV͛0ëįE,Yą†w^ĨÔ2’SRųîĮ™ė;xo‹™nÚ—Ø&//š –°yÛRRĶ đ§}›– č{ FŖũąÉ^yžˇôāäŠĶÄ˙ļ ›ÕFįm¸ĩgwĻL›ÉÃG1›L ¸­7.%Šiéü4wû"=#“AôčŌ‰žŨ:9ö]|ˆÄ ¯ŧÎmŊoæÂŋlŽßAvvõęÆ2æŽ!9iŨŗ˙€ãdËfĩāééQáļÎĪĪ'/?¯Är‹Ų\eÃÜâ. +ēp1ŲyŸ ũníÉŧ…KéÜž Ae–ĩ~ĶVÚ´lFƒzqÔ dÖxôŊĨÄvūDE„ŗasŧ͉íŠ3gI8žÄ€žˇ”8ą-OOl6›ËÛĢīĒwßÍ˙eƒƍ"??ŸY?/bũĻ­dįäŌēESÚ´lÆg_Ī›˙ø ;÷ėã× [čÕŨ~‘Ķjĩ˛`ņr6oÛÁų ÉÔ  W÷.tīÜpíøæĘ1¤"ĮĻ?ũí5úŪԃSgβs÷^˛ŗshÔ >c†ÁΎôŋ_eÕ§ē¸Ž’’ÉßNaķÖxÆŨ=š¸:u8xč0_MūŽœÜ\ƌ€‡‡'ËWŽĻUËæ ØŗÉŒ§Ņƒ‹sĪØ1Ü{ĪŨ,_šŠ¯&ĮîŊû¸gĖHęÕ}„ŗf3ųÛ)´iÕ __>˙ōkNœ<ÅÃM (0€}ûđåäo¨U3˜6­[ņäãđƤˇ ãîŅ#KũAøøŗ/9uō4Ī<ųA,]ēœM[ļâį{uÃ<Ė_°ˆ;‡ fŌ믑œ’Ė+˙|Ysį1îŌ8WÚĒ"íRŪ:zxxp‘a'eqĨ]¯$++›ˇŪyŸŽÚ1~ÜŨ`ŗądérūûöģüįÍW¸oŋ˙q+VŦfÜØ1Ô¯W—ģwķŨ÷Sņđđāæî%oG_Ni?Ÿåŋ2öSšĻM/W]‰Õ•2‹ĮzčĐ~˜6ƒŒĖ G‚‘™ÁŽŨģ9üŽ ÅzĩídĩÚHĪČ(ĩ ÜéĖ™ŗDEE\ķũÚl6rķJžHxzx`0h×Ē[âw0õ§š<ķčØl6~œ9Ö-šŌĒy�ÂCChÖ¸AŠå�|ũũtNŸ9Ë#÷% Ÿ•k×ŋcž>…ŋ?ü4—íŋífäЁԎŽâpÂ1~˜1‡ÜÜ\†ę€‡‡‘Ĩ+×0rč F ˚õ›øaÆö<ˈ!yp|4ķ-eęŒ9´hÚoožú§ÎœáŪ1à đ÷į⑪L™6‹ā´hZú-#‹—¯bāmŊyõÅ?’’šÆ›ī|ČüÅËyį ĀžD<œ@Ÿ^=*ÔöĨiÖ¸!SĻĪæĶÉßͧg7jGG9 Ë)Εū+Ëéŗį�¨qéB˜ŖlĢž];ąfŨ&fÎ[ČŊc†_ąœS§ĪrôX"wŨq;ƒm[ąqs<ˇ÷éU"ĢÕJë–ÍXžęWîØĪ‘tnÜOdxaĄ!UR×ǤžĢšžËĘĘféĘĩüų™G1 Lūá'Ž'%ņč„q°dÅjĻL›ElLFŖ‘ömZ˛eÛGRōĶ܅ŦYŋ‰QCkŋË3möĪxzxĐšC[—ŽoŽC*rl2zYŧ|5CõãîáC8}öī}ü%Ķg˙ĖøŅw•ÚeÕ§ē¨6I‰Íj%++Ģ”5,3ŠiiŦ^ģŽQÃ‡ŅąƒũjYhh‰'N°hņR†ģ/OO ûÄw u*ĨvL4­[6 S‡ö|5ų;ę׍s<|ŨŠC{æĖĪɓ'ŠW¯.cFŽĀ`4R €đđ0–,_Áo;wŅĻu+|ŧ}0=đôôpēVāü… ėŪŊ‡{îM“ƍ�;f;¯đāVyDD„ĶŖ›ũîEp`Z6oĘá#G\n̊´KU׹Ŧv-Ëš įÉĖʤKįDEØOÖî=’íÛ9Žf•ˇo333YēlnīG×.ö+aaĄ9zŒyķ–ë$ør?Ÿå‰ŋ2öS\EęXVŦŽ–Y<ÖZ5k2eę4ļmßAįŽØēu;VĢ•íÚ^Ķū(˜˜ČcOūÁårĢB~~žã˙“SRX˛d9'NžäîŅ#Žy,I'Oņ‡_-uŨŸžœHíhûĐ´‘CķÚ¤wYŋi+9šš\¸˜ĖŖŒslÛą]ëËē˜œÂž‡1d ęۏ=ÃīĀž}Û¤gd°as<CÜF›KĮąZ5ƒ9uę ËVũĘāÛûâyéÎptdÍ7 ]Ģü0cqą1ŽáíZ5gá’œ>s–:ĩc6øvŒF5ƒk�R“•k×ŗ{߁Ë&%`O´:ĩo@P`�M5 áX’cũĄ# `ŗQ÷Ō~srr�w°+ĸKĮv¤gd˛pé ļíØ…ÅlĻn\mZ4mLû6-†­€ëũW čX˙ŧü|Ž%2cÎ"ÂBK u˛aÃÃÃĄƒúņŅßŌŖKG§!BÅ­Û´…АšÔš4Ō SģÖ,\˛‚ƒ‡RŋnÛˇkŨ‚š ŗkī~š5nˆÍfcĶÖítšĖŗ åŠĢŅh$77÷˛ą§žĢž}ŋc'ŅQ„ÔĒÉŅcĮŲēũ7ūö§§CËî¸Ŋ/kÖmĸÎĨ6­U“ S�{Bŗę× ôí՝m[R+˜„Ä$-[å8‰/ëøæę1¤ŧĮ&€č¨Įą3,¤Ũ:w`Áâ化ƒÉdrj+WëST›¤äxb?ūt‰å&ŗ™ß›cĮŽcĩZŠW×y|dŨ:ąädgsęÔ)ĮíúÅNžÂà `ōž4ËBDDD‘eög223û­Ķš ˛{Ī^RSͰŲl¤§§ęÚ,`'Nœ´ĮWĮąĖ`0KBÂq—ʸ’˜˜h§Ī>>>Ž+šåiĢōļKQU]ĮŠˆ #<,Œ>ųœ^=oĻY“&ÄÆÆĐ¨ČxüōöíŅ„ãäååĶŦI§å6`åĒÕdeeaą¸ūĖLi?Ÿå‰ŋ2öS\EęXVŦ{öîwšĖĸąҰÁMlŲ˛Í‘”lÜŧ…&PŽr]áJ;ĐŧĀņã‰|;å—÷UQĮŽgÂÄĮœ–ųúøp˙ŊãhÖ´ÉežUuBj;/*ŧČĪĀ�îxŗ~^DžÕĘČ;9†r–Ĩ`XZlíƒÁ@˜hŽ%� 1é$VĢ•¸Xįãaí˜(rrs9söœc<~ØĨ‹€ãä°"Ëė?3™™öģ_fŗ‰_–­d߁äĨ§Û™„ÖĒyŸ##œ–õņļ8?÷ė?HŊēąx^ē0´fũ&L&‘áå`ž¨>ŊēssˇNėŨŊû˛gßAžŸ>›‹—ķØã/ō\‚Ģũxâ$Oũųe§eƒÆ ë3zØ—ŊZŨŦqC7ŦĪŗæņܓ—ēÕjeã–mtëÜÁqō\#ˆēujŗas|Š'ļ5kÆÍņ4kܐCG8wá"m[5'áxR‰íËS׸Ø6mŨNÛV-0›MĨ~§(õ]õíģ„Ä$Įķ4ŋnØBƒzq%žuąÚlŽ“üėœŒ—ÚãxŌ ōķķiÔ žĶö7ÕĢï6“ƒŲl*ķøæę1¤ŧĮ&€˜bwČ#ÂBÉËËãbr*Ą!ÎåģZŸę Ú$%ĄĄ!<p߸Ë nņÜEąx;ŸltVv‘a–RĻvķô*YՂ? EŲl6ōōōyķ­w°å[3zax<xûũ\ŽOAŧ^Åök)ĮßWrĨ™1ĘĶVåi—Ëí§ĒęXFŖ‘Ÿ–Ÿ,bÅĒÕL›1“šÁ5zįtíÜŠB}›yŠž¯Oú âÖKm’œœRŽ“āŌ~>]ŋ<Ž´Ÿâ*RĮ˛b-O™ÅcíĐž-ß˙8ƒœœ\ō­yėÜĩ›ņ÷ŒŠpŦWâJ;•ö 9€‡ņÚ ũ sz˜Ũdō",4ėĒÆ˛_ “—ÉņĮŧ,íZˇ`úœųx=hŲĖõÉL˛˛íWĄ‹ëŠūņĖĘļˊ?œZ0ë_vv‘c])mUüØöĢÅųųųŧ˙ÉWX­VîēãvÂBC.MSúm™q›J) Ÿ{÷tLšúÁg“ŲwāŒOáĪĄÁ`¸ėŗ ųVûŗŌŽÎ›ŧŧhŪ¤͛Øī\ī;x˜OŋžÂOsōȄ{Šlįz˙…ÔĒÉŊc ‡„Ŧ\ģģ÷1~ô]eNŖ:tPūõŸ÷Yˇq M/] .jĪžƒ$§¤2oáæ-\â´.éÄ)†Pęßēv­[đĶ܅dfeąqË6ęԎĻfpROlËS×ģīÂū÷)Īũũ˙ņúË/\q6-õ]õîģääG˛xæÜy§É�=FNNą1ö‹gĪ'äŌÉ|Áq坏ž čžāoLJj!æ`G}J;ž•įRžcSsņcžÉŪ֙Y%/ —§>îVm’ŗŲLƒ›.?ß~Á‰CVĻķ¯Ė‚;>•7ĮôĄÃ‡8~<‘ž{–†E2˔ÔTBjÕēÂ7 üĀd‹ˇø‡ŌNir.ũ1ލkÕVŽÖą¨Ē¨oqūŒ1ŒQ#†‘˜”ÄÂE‹ųäŗ/‰Œˆ 77§Ü}[pđ~hÂũDGG–X|éÖleÕíJņĮՉ­’6tĩŽå‰ĩĸe´k͆ožûžģv‘}Šnm[ˇ*WŦ×âgíZņ2yW'ÖŨaTČŧ…K  /?ŸŸYÆāū}ĘūöÄ J;ž~.H>ŗ˛Ÿí)xÖ§<ÉiQGŽ“tōO?2zq…íž––îŠQ™K<Áčaö÷)Œ:ˆš —0eú,^zî)Į° ??_’$Íiü��TIDATNœ*ĩŒsį/�öģPRRĶ0›L%Žv6¨GĢfMØšg_…c6yy9 “:°;víaæŧEežĶ#<4„î;0gÁâRßoąnĶVę֊ͰÁũ–įååķö‡ŸŗmįnÚĩjQâ{mZ6cúėųlÛą‹­;vŌ˙Ö^Ŧŗ™?/ÂĶÃgPæ•cõ]õî;›Íæ¸d6y•˜nxáŌx{[}ą9~=ēØīĖŧŋeüčaD†;ßų¨T8ĀåŽoUu )Uú1¯´dŗ<õqˇëæîĩŖŖ1ė?pĐiųƒ‡ņöļ¸<ŦĘššöĢüü Ļ<pđ gĪžsĘTŠ,~)CO8V8Œ)//Ÿ={÷:mįmņ&=Ãų$ūXbÉ+åq­ÚĘÕ:år}]Ÿ�ÅÉé3gŲRäEsQ‘‘Œ{7FŖÄ¤¤ õmí˜(ŧ<=IIM%2"ÂņĪĪ×ĮÕ Ęč˲â¯ŦũįjËkEĘ,āOãFˆßū[ãˇŅ˛E3ĮđÂkŲruŽ'˛lõ¯Œ:ˆwdɊ5Ĩ^-MÁ†Ä¤“Žeųųųė?tØņ9:"ŖŅČÁbŗö>š€Åb.s¨Õåä]z¸ÖˇČœÃGqîÂEĘ19S {ÄĮÛÛņ>„āA ęw+É)ŠK<áØŽIÛ8söœĶķ3`ŋcŊhéJ‚Ã7RSĶøÛk“Xŧ|U‰ũŲl6N9K€ŋ‰uåããÍāū}øuÃf>Zæöˇ÷íEžÕʒkœ–ŧßĸ}ë–ÔŽŽrúWˇNmŨT ›ãK-ĶĪחF7Õã—åĢČĖĖĸMËfĨnW^ģöėŖ{—ÔŠSæãęģęŨw5ƒƒ9yĘ>´Aũzlûm§ĪœãÂÅdž›6 ?__ŦV+é,\ē’´ôtĮęQáxzxš–NXh-Į?__oüü|ŖIŽt|ĢĒcHũ‡Ž8}>z<“—AÅ&/(O}ǃęIüü|éŪ­+sį/ 4,”Ú1ŅėŨģŸĨË–Ķ¯_ŸĢzĐŦ¸˜čhŧŧŧøeņ2† ĀņÄ$~œ1“fM›pâä)’SR Ā×ׇŖ M8Fpp üũ ĖŽUŗ&õęÕeŪĪ  Áßߟ_–,u<tY Nl [ˇnãļ>ŊąX,,X´˜´´´ŗb”Įĩj+WëX”+õ-ŪŽåqūüyŪû߇Œ6”–-›cĀë7`0Š_ˇ.>>>ęۛ{tcæė9øûųĮšķįųîûŠ×â™'wšnWeí§8ooo—ęXžX+RfQÚˇeÎܟÉČĖäūņ…C;]-ˇ*ÚI랞ŗŲuiÚė⠍Ô'??Ÿo§Î¤]ëŽ+­-›5æÛŠ?ņÜSãááÁ†Íņlßš›ƍ.QNp ęԎaҞ•ÔĒŒŋŸ/ËW¯s:žøøxĶŠ}-]IH͚ÄDE°˙āaVŽŨĀ­7wu ˙-¯¨Čp<==Yžzũûô"éä)fĪ˙…F ęqúĖYRĶŌ]~6ύ=ûŅ ~]§Ū‚+›yEfęØļk×oæĶÉS¸Ĩ{bĸ#IKKgÍúM$OâÁņŖuķ÷÷ã–]X°d)Ši4ŋ4COJjë6máБîģÛy2WúīJēthËÚõ›˜2mūÃcW<æûx{3°ooĻÍūŲiųĻøíäįį͞yéĪDĩn،ī~œIrJĒĶ…íZˇāëī§Ķ ~Ũ+NŠRžēæåį;Ŋå|õēŦÛ¸•?<ö@Š?KęģęÛwM5ā›Š3¸ãö>tëܞ§N3éŊ°˜ÍôíՃömZręôYūúĪIÜT¯=0ŪŅ/‹™ŽÚ1oŅRü|}ˆ‰æü…‹LŸ=Ÿ Ā�žl™ĮˇĒ:†HNIáįEKéĐļ5§NŸaÕ¯hÛĒšcŖĸ\ŠOuqŨ$%�cĮŒÄÛbæëÉSHIM!8¸ŪÎĀūˇUę~ü™pī8Ļũ4“ĩëÖQ'6–īĮų ųāŖĪxcŌ[ŧöęKôéŨ‹?û‚×^ŸÄ<DķfMĘyøÁ |ūÕdŪ~÷X|ŧšåætî܉-[ ¯ Œ9œOŋüŠ?>˙ü||čŅŊ+ŨētⷝģŽĒ×Ē­\ŠcQŽÔˇxģ–GŖ† ˜pßx,ZĖOŗæ`4zÁNtÜŲŠHߎ9ĻN›ÁÅäiŨĒ9w -|[neôĨ+ņWÕΌ+uŦHŦå)ŗ¨vmÚđõ7S0™ŧhŲÂųjÚĩę)ŨšķøāŗÉĨŽ3 ŧķú+ü˛l“SxbâŊŽuÃßÎ?ß|‡EKWŌŋO/Nœ:Íö—Ÿ­īŪģ‡ķŨ3ųø‹oąX,tëܞmZ˙[a2�‹ŲÄԟ搚–NĀ�úõž™>ŊJžĘU~žžŒq'ŗį˙†ÍņԎ‰âž‘Cš˜œÂßL坏ž¨Đ;$öî;@ß[ʞ ØÃÃ''Ū˂%+ˆßą‹Å+ÖāéáA\ž~dB‰‘î¸Ŋ/aĄŦŨ°™íSg’‘™‰Åb&6:ŠGWâDՕūģƒÁˆ;2éŨųåR_^I×NíXŊn#I' ‡5­ßdúr'f-›6fĘ´YlÚēŪ7w-ąžEĶÆxyyŅļUķ+îûjęzņb2G/×´ÁęģęŅwÔŖfp ĻĪ™Īˆ!=l0Ŗ‡9ŋūŲ'Âjĩ’—Ÿ_b–ŗĄƒúãmą0sŪ"’SR đ÷ŖyĶF îgžåĘņ­*Ž!ēthKFfožû!ššy4oԐáC\vû˛ęS]^|ņE‰'øüķΝǰ­;÷^rŽiŠ^OžĄuĶË?Į'×FNn.üË?xøūą4mTūYö¤ōüáÅW3|íZ> ņęoķŌsO•ēŊúŽú(­ī.&§đū'_Q#(ÛzßL\l FŖĢÕĘÉĶgØŊ÷�kÖo¤OĪîÕjZܲiĢģlŨš˙Ē˙†Lœ8‘ČČÂgC¯Ģ;%"""ŋ'&//||ŧŲ˛í7ęԎvz1¤\9ššl˙m7šyyN!īÜŗØbīũ(J}į~—ë;°ŋ'čOONdéĘĩ|ûãLΞ;Ųd"+;›A4ēŠcG ŊâģXäÚRR"""âFÇ `Æėųüys<oŧōĸãũPrm|ôŎ>zŒŽÛ9ŊP°iŖeŪQßš×åúŽ€Édĸß­=éwkOrķōČĘĖÂÛÛR­î–Bę7j×ĒEŠĶ§ĘĩņÄC÷Vøģę;÷*Oßyyzâu…믯ŋü‚ģC¨2×͔Ā"""""ōû¤¤DDDDDDÜJI‰ˆˆˆˆˆ¸•’q+%%"""""âV•š” Ŧ6[e)"""""ÕDNn&¯ĘŸĀˇR“o‹‰´´ŒĘ,RDDDDDljs’ņļ˜+ŊÜJMJbŖÂIËČ$5=ƒ|Ģĩ2‹7ÉÉÍãä™ķœ>w‘čˆĐJ/ŋRīŊXĖ&ÕĢÍŅēœ9wü|%&""ÕÕ֝ûŨ‚ˆˆ\'L^žx[Ė4Ž[%Ãˇ*ŊD‹ŲDÃēĩ+ģXųŌė["""""âVž&“ �///7‡""""""7"Ũ)ˇRR""""""nĨ¤DDDDDDÜJI‰ˆˆˆˆˆ¸•’q+%%"""""âVJJDDDDDÄ­””ˆˆˆˆˆˆ[))ˇRR""""""nĨ¤DDDDDDÜJI‰ˆˆˆˆˆ¸•’q+%%"""""âVJJDDDDDÄ­””ˆˆˆˆˆˆ[))ˇRR""""""nĨ¤DDDDDDÜJI‰ˆˆˆˆˆ¸•’q+%%"""""âVJJDDDDDÄ­””ˆˆˆˆˆˆ[))ˇRR""""""nĨ¤DDDDDDÜJI‰ˆˆˆˆˆ¸•’q+ĪĸNŸģčŽ8DDDDDä唔„Ö rW"""""rƒŌđ-q+%%"""""âVJJDDDDDÄ­””ˆˆˆˆˆˆ[y^ŧhŸq+55ÕÍĄˆˆˆˆˆČ 55•‚<Ā3(Č>ãVFF†ģb‘ˆŋŋ?yhø–ˆˆˆˆˆ¸™’q+%%"""""âVJJDDDDDÄ­””ˆˆˆˆˆˆ[))ˇRR""""""nĨ¤DDDDDDÜJI‰ˆˆˆˆˆ¸•§ģ‘ō;x$Ī'OZzVĢÕŨáT;FŖ?_îŋgõęÔvé;ĢĀˆīáL:äŲĒ6žë§B|aę(čV§ōËם‘ëĖÁ# ŧõŋOIIMSBrVĢ•””TŪúß§<’PæöĢ@OáDš’ŌäŲāDĒŊVŠüō•”ˆˆˆˆ\g>ûú{w‡p}0ĀfsŠŊ†˙�ĘEĘ`oN†˙PųE+)šÎ¤ĻĨš;„ë‡Á@zFF™›JŊąüālüø))‘ß5W†¸é.‰ëĒbx›’q+%%"""""âVJJDDDDDÄ­””ˆˆˆˆˆˆ[))ˇRR""""""nĨ¤DDDDDDÜJI‰ˆˆˆˆˆ¸•§ģ‘kãÁņchŅ´Ņe×ŋōú[œ=wūFt}[r?Ôō–ī•žžAMØû <>Ū__vyg_„ˇÖÂ?——ūš4ī „žuĄŲ;卞zQR""""r9{î<ßOŸ]ęēää”kÍõíË-đõ]Đ" ļŸ*š~l+Čɇ)Û+VūįΓWãõBI‰ˆˆˆČ $;;‡Ŋš;Œß…é;áũAöäãš…%×ßŨfī†ķ™+˙Ģ­WßõDI‰ˆˆˆˆ81ôë}3mZ5'¸F/&ŗtÕZV˙ēŅąÍŋūūg.YAãõiP?Ž^}—ž{šEKWBËæM0ŒŦŨ¸™ÅËV3føԋ‹%;;‡y‹–˛~ĶÖĢÚWVVö5o—â2raÚNĶūŧŦļÂu]jCŨ`xrŽũs¨/LęŊëB°K†÷ÖÁ;ŋ^žüâÃˇ"ũáĶ;ĄW]H΂7”üާūÚFĩ€Ø û~ūģ>(˛í™áĩåС>ÜRÂūÉnnN%%""""âdȀÛčÚŠ?Ė˜ÃĄ# 4ēŠÃüŧ|~Ũ¸€üü|ēvjĮoģö2ņrrrrÉĪΧ÷Í]ų~ÆlĻLŸM׎í5l0 ęÅ1õ§yIøŽˇõfäŲžs7™™YŪWuņ帯 ôŠƒ%En@m 'Ķ`Á~ûį·BŖ=NĻBˇXøx$\„™ģ]Û××wAƒZ0āk8‘ u„aÍā\Fá6oöƒ‡ÚÃ#ŗ`mÜZŪ`FöŲfû69yömæė,ƒôjМš}KDDDäb00™L%˙yy`1›éŪĨK–¯fÃæxΞ;ĪęuŲ°9ž>Ŋz8ĘąŲläää2ëįEI8†ÕjāXâ vîŪĀæø�>zŒ# ĮėËļnĮËˋ°ZWŊ¯ę`Õ8tîi]¸ĖË#šÃ7ņéîÉĶķ Ī°ōė;Ÿom'íw+\�ŊëÁŋWÂŌC°û <1R˛ ˇ 0ÃŖáÍUđu<8oŋ›ōõVøsasb2rāų…đë1ČĢÍŠ;%""""7Čđ0ūīŸ-ą<''‡?ūõŸDE†ãéáÁî}œÖī?x˜.Úb2™ČÉÉėÉFq§ĪœuüVļ}LĐŠĶ%—y[,WŊ¯ę†ũ¤˙Ũėw'2ķ ¨écŋ‹R -ĮžôĒ !ž`4@°7ė?įÚ~‡Ø˙ģá¸ķž7‡Ö‘öĪ­"Ā䋜›“å‡ávāg˛Įöd¤:QR""""r9sö<ßLQbšõŌ‹€'žl…I �üũĶgeeQ\^^^‰ešĨ,ÃpõûĒ.žÚ ŋ†4ąĪ´5ļlJ„§íëŊŒ°đ^ûķO̓=gėw'fu}ūöĻ"ŗØPĢ‚$ėwJ�–MpjNŒöæ$ÜĪ~÷ėΤT'JJDDDDn 999:’pŲõ=eI'JÎs{ņbrĨÅr-÷U•Ž\„G`L ˜ģ5‚gįŽī-ÂĄĮ'°ęháō_8|Áĩ}¤_J>-Î˃Š|.H4ÆūXúTÂĮĒqs*)‡Ä¤“äååáįįËŠ"Cąü|}°Úläåį_—ûĒj_mî€áÍėw&Šž›ÄréŒģčéc Žl<ŽKö^jžVöØÁ~ĻgŨÂrˇ„ė<ûL_{ ›“ûĖ`ŲÕ¸9•”ˆˆˆˆˆCVv6kÖob@ß[HOĪāhÂq‚k1lđí\LNáÃ/žš.÷UÕ~ü Ū¯õ-ųn’m' +žė ¯,…æáđ¯žög?†Ø“ˆĶéW.˙čEø5^čÎÁé4xĒ‹}&­)ŲđņFxĨ7œÍ°?oo €ãÉ0prÕÔŊ2()'3æ,pL×āīGJj;víaÎüÅ×õžĒRzŽũeŠã[;?āp&î›nODÆĩļ?orītûŒZ?Œ‚Ĩ Ų;eīcĖTû{JfĩŋWäà 09†6-ÜæķábŧqDøÛ§%žŊ^üĨrë[Ų /ŋü˛ ))‰>úČŨņˆˆˆˆHžxî%w‡pŨy÷W¯¸ŪPrB2šÛ?¯îû'N$22ŌņYī)ˇRR""""""nĨ¤DDDDDDÜJI‰ˆˆˆˆˆ¸•’q+%%"""""âVJJDDDDDÄ­””ˆˆˆˆˆˆ[))‘ß5ƒÁPö6× Žß Ī*Č ””ˆˆˆˆ\güũüĀfsw×› ?_ß27 ķÔ¤eŗA­˛›ŗÜ””ˆˆˆˆ\g&Œ.\ũĀ`°ˇW~Š&u…Á`oĢĘϤDDDDä:S¯Nmž~ôüũü\št#2 øûųņôŖP¯Ní2ˇīVV>�áūU3<ézįi´ˇÍĘėmUéåW~‘""""RÕęÕŠÍ˙{é9w‡ņģŌ­œxŪŨQܘ”Šˆˆˆˆˆ[))ˇRR""""""nĨ¤DDDDDDÜJI‰ˆˆˆˆˆ¸•’q+%%"""""âVJJDDDDDÄ­””ˆˆˆˆˆˆ[))ˇRR""""""n唔X­VwÅ!"""""7€ŌrGRâååÅŽ]ģŽi@"""""rcŲĩk&“Éi™#)ņņņaöėŲØlļk˜ˆˆˆˆˆüūŲl6fΜ‰Ķrž={ž `2™8}ú4Ûˇo§f͚øûûãéééŽXEDDDDäw$++‹}ûöņÉ'ŸpúôiÖ^~ųe§[#ŠŠŠ¤§§ëųŠ4øúúâįįWb]‰[!ūūūøûû_“ĀDDDDDD4%°ˆˆˆˆˆ¸•’qĢ˙d‰7a2RĐ%����IENDŽB`‚������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/scheme-oauth2.png���������������������������������������������������0000664�0000000�0000000�00000170105�14156463140�0022030�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��Ũ��•���,Š:F���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨw|“õÚĮņOš´Mē÷ ƒŅÅ*K”-Ŗ‚€Pö!Š{E҇zŽĮ-Š Ų{”Ŋ-[Dö*ewīŨ´M›<ĸĩ…ļ´„uŊ˙‘Üų+w|õ›{+ö:f@!„÷œÂ`0Hč !„÷Øøņã1ģßE!„ ]!„ÂD$t…Bˆrœ9s­V[bŲ‰'ČĪΝÔ8ŨđŨ{hÜė)ÖoÜtĮvã&Ldíú ež×¸ŲSÄÅĮWĒĀ{éÜųķtyļÜvZŨB!L'==°°0,X@^^�‡bíÚĩėÚĩĢRcU8tׄ­åˇßdMØēĘU+„B<ÄčŲŗ'ņņņ,Z´ˆß˙­[ˇâããCHHHĨÆĒPčϧgq‘aCOBBĸņŊččúBû.L|íM ŒīíÛ€ŽĪt%äŲîüđãŒÛŽėø zôęG‡géŌ­'ŋ:\fģÆÍžbŪ‚…Œ|qí:>Ãļí;ų`ŌĮ 2ŒAC‡“›› Ā…ˆ‹ô4”.ŨčŪŗ7áģvĮøéįY´nבîĄ}Øĩ{¯qų ŖĮ˛qĶ–Ûžž%|÷ēõčEû. {a4)ŠŠXƒB!fM›6åšįž#::šmÛļQŖF †Ž……EĨÆŠPčnØ´™îŨēĸP(íņë7n4ž÷Ÿ˙}ÅSO6goø&žü‡nfQQī}0‰O>ūđí[ptt ;'§Ėņ'}ü c^ɞđíLxi,}ōeļSĒ”äį0˙—YŧōŌxŪxû]ƏÃĘe‹QŠTėÚŊŊ^ĪĢoŧÅķCžc+_˙īŋŧņöģ¤ĨĨséōefÎū…ukV˛iŨ.]ē\Š•ŸĀ;ī}Ā7_~ÁŪđ´ē~ôIĨÆBņpRĢÕ( ãŋ•JeĨĮ¨P膭]GīО�ôíÉÚuŗ=räzöč@ŨēAÔ¯_€k׎ŖÍÕŌĄũĶ� čחÛ]ŧ~íjB{<@ë–-‰ŠŠžm-;´ĀĮĮojÕŦ €¯7 ‰‰DEG“˜Hī^=5ōįąc>ōO=Ų77WĖĖĖčÛ§WE>žŅîŨ{ nXŸēuƒ�:xģ÷ėE§ĶUj!„— .°víZ\\\hÛļ-WŽ\aųōåUjUy "/]âĖŲsô0ظ,'7—3gĪҰA}Ō32°ˇŗ3žįā`@zFv[nii‰Z­.sŽ_Ãw1Á"t:EEEčõúÛÖcme€RŠDķˇņĖ”JôEE¤ĻĻaī`oü5rĢĻ””TŌĶ3°ˇ˙Ģ&{{ûō>~ ™YYüyėí:<c\ĻŅhHKKĮÍÍĩRc !„x8$''ŗjÕ*9r$666˜™™ąoß>vîÜI׎]+<VšĄģ&l=oŋų:Ə5.ûeŪÂÖŽ§aƒúØÛŲ‘••Uĸ8�{{;2˙ļ<''×xÖW‰“’ÂÛīū‹ëVãīįGLl,í;uŠđø'gg2Ō30 ÆāMKKĮÅÅ]ĄŽŦŦėsßĸT*Ņū ûėėŋÚŨâææJ›Ö­˜5cú]×'„âáâââB‡hÔ¨666�„„„ ŅhhÔ¨QĨÆēãîåĸĸ"ÖmØ@—gJžÕå™6nÚLaa!M›6aũÆÍ�œ8y’ˆ‹‘�ÔôõÅ\ĨbĪŪ}�,]ļ3ŗŌĶĨ¤¤beĨÁ×ĮŊ^Ī’ĨËŅëõetEx{{QŖ†§ņŌĻĶgÎrųĘUš?Ҍ'š6åĐá#$$$RXXČę5aÆ~nŽŽ\Ŋz €QQœ;ĄÔØíÚ´æøņÆv'OæĶ)SīĒN!„víڕÚ;ÚēukcWÔˇtüvkkküüę”XîããĢ›+{÷āƒ÷ŪáõˇŪĄeÛö4kڄNÛŖ/ŌcnnÎįĶĻđņ§SĀ�ƒöĮÉÉ }QÉ]ĮAth˙4!]ēãââĖÛožÁG˙dĐĐáŦ[UŠ P(øūÛ¯øhō˙1ũ§ŸQ[Ē™ūŨרÛÛcooĪ ÃŸ§gŸūØŲŲ2dĐ@Μ=ĀȆķÆ[īđĮŅ?ņöōâévm1Pō´‹‹ ˙ũ|/ŋú:yyųØÚÚđņ¤*]ŖBˆĮOVV–<đ@!„0…1cÆČm …BS‘ĐB!LDBW!„0 ]!„ÂD$t…B‘ĐB!LDBW!„0…B!Ą+„B˜Š„ŽBa"ēB!„‰Hč !„&"÷^B!L a“'dKW!„0UbJúũŽA!„x,¨Üœîw B!ÄcAv/ !„&"Ą+„B˜ˆ„ŽBa"ēB!„ ´hV_BW!„0…#'¯Ič !„Ļ`§Ę“ĐB!LÁÃÍFBW!„0…ËŲÎēB!„)Ļ]GU^Ŗė\-Qą‰äå˜ĸĻr]švÃ‡ ÍËŋßĨ<´4jKõ 副Á÷ģ!„x|(ĘŨ¨ØDŦ4–8;ڛĸ¤r-^ēœî~—ņPĶæåŗbÍ ]!„0!§Ú>åī^ÎË/@ŖV›ĸž ‘Ā­˛§@!LËO“#Įt…BSøãô% ]!„ÂT$t…B‘ĐB!LDBW!„0 Ũ ˛ļ˛â͗ĮTë˜Ŋû�;ļŖ†‡{ĩŽ-„âÁSîuēwRXXġ?üĀ•ĢWņņöÂÜ܂g;‡ܰAĩx7ÔjKBģvÆÅŲ  ;wī'ōōUc›^Ũģ R)YŗaëĮÚŋëˇėŧíûÍĶúŠfčthĩy,YĩŊ^_ĸM‡ļ­nP—ĸĸ",-,øũcúã˜ņũģ÷Wú3ŪĒ+'7ˇŌ}…BÜU ]•JÉ;oo­MûĪ˙3zŽ.ÎŦ\FfVmZĩ$'7—/žü†.C8|ä(ž>ŪÄÆÅĶŽM+š6iĖÚ ILHĸH_DĶ&hÕĸE•?Ô >=‰ŧ|••ë6`kcÍkãGņķŧ%¤¤ĻáëíEbR266Ö¸ē8“”œ‚“Ŗƒû†ōĶ/ â­ĐÅ+ÂhX/•JEØÆmXX˜3°Oė‰OHbũ–x×đāįš‹),*bÜČĄÔđp':6ÎXKËæMŠ]͇g/@¯×cŽRŅëš.¨--m†öīÅĄŖĮÉÎÉĄ÷sĪ’‘‘…FŖfEØFTæ*^5Œs‘ØŲXŖÍËįÄéŗÆē­Ã`0�ĐĸyS† ėĀáŖĮYŧrm•×ĨBˆęsOv/wxēûüĀŅ?ū¤U‹( Ŧ­­čߡ7Ŗ_ÎÚ ‰ŽŽæōå̌;š—ÆžČæ­;ĐVy~ŋÚ59xäOãëŦėŽž8Mũ ��ÚļlΟ'Īpčc´kõÔmĮšv#š¤”TÖl؊Á`ĀÆÚšUë63sŪš6n†­;),*ÂŪ΍ZMBbR‰1‚Ôc÷ūƒÆ­_]a!Ģ×o!/ŋôÍ)BģvfĮŽũŦXģ‘ˆČË´iŲ}‘kk+6ogؚ 4nXŋT]ˇÜ Z \!„xđ´z˛YÕļtoĮÍÍ33%ņņ üqė8ãÆŒæĖŲŗ¸ē¸�`ccMvV.‰É)¤ĨĻ1{î|�459ŲŲ888Ti~3ŗŌŋ%€^¯ĮŅÁž:ĩkōü€^�xyz˛õ×Ũ75-Ũr†ŋíBôĢÍĶ­[0éĒR? †âŨÛáėėHû6-hÛ˛9Äß đŒŒĖŋxĮ1=ÎáŖĮ+4ŸBĶē'Ą Å[ģ7oÅÚĘ ;[�ãV`zz:ļv6¸ēēāæîÆØŅ#ˆŠ‰Šrā\ŧt…ömZ°÷ˇÃ@ņîåĻ2sŪ:ļkÍâa\ģ @ÃzA´iҜÃGcn^ŧ:Ԗ–Æ[_Ū ÍŋoQū]ĶF đ¯S‹šKV–:– pâôYžéЎy‹WPXT„JĨbHŋPÖmŪ^ĒmrJŋî=@Ll<ļ6ÖčõˇØĘ„šBˆÃ= ŨfM›°hŲrÆŨ T�NĮ’å+‰ŽŽĄOh|ŧŧđöŽÁĖŲsŅęđôôĀĮËĢĘs¯\ģ‰ĐîyyĖôz=fffŦ^ˇ™\­– €:„müëäŠķ/ŅŖkģö$;'—ž=ģ’›Ģ5ž t#:†ƒûą<lCŠy Cú÷âŌ•ëŧ8|0�ģ÷äŌ•kÆ6GŸÂÚƊ‰ãF’_P€JŠäĀĄŖdeį”oĶö_ íÖ­V‹­ k6n!'G[ægŧU×ÜÅ+ĘÜU-„âÁŖ0ÜnîĻãg#ņōp­ôĀyyų|ũŨüëŨˇ13SpđĐa’“SíŅũŽ‹ø÷ŋŽRņ—ž˜rŋKBˆĮÆØącī͖îŲsįŲ˛m}{‡bf&ģ@…BƒÁpoBˇAũz4¨_¯Ä˛Ö-Ģ~)Bņ0“;R !„&"Ą+„B˜ˆ„ŽBa"冎ÚÂm^ž)jŠssķû]Â#A}ķ:d!„ĻSî‰T>^nDÅ$’šžeŠzĘÕĻuKūƒŧč‡ĀÃF­V3¸_Īû]†B<vĘŊNW!„U7xđ`9Ļ+„B˜‚ŊŊŊ„ŽBa*ēB!„‰Hč !„&"Ą+„B˜ˆ„ŽBa"ēB!„‰Hč !„&"Ą+„B˜ˆ„ŽBa"ēB!„‰”ûƒė\-Qą‰äå˜ĸ!„âĄŌ´A@…Û–ēQą‰Xi,qv´¯RQ÷RL|RĨ>´BQŽŸŦTûrw/įå ‘g¯ !„U&Įt…B‘ĐB!LDBW!„0 ]!„ÂD$t…B)÷’!Qځß˙@WXˆĩ)iét iŋKBņ¨rčnÚ˛Ŋû0å“IhÔ�>ũ÷g|úņ‡U.Ž:ŊũŅTüjų–X6väPĖUōģC!„iTKâÔĢIJåĢ=rD‰å׎Ũ lũėíėČČĖdÂø9yō ‡˙8Н7‘—.Ķ8¸!šZ-#/ņŌ¸QĢÕĖŋĩڒŦŦlú„öĀĮĮģĘ5ZZ˜ķō˜’õåæjųnÆ\&Ž}ä”T6nû•—FcEØF5ŦGũ ŋn¸qåÚ 6o§†§™YYÔōõ)^~õ:+Â6—HHû6ׯ[åZ…B<šĒ%tƒ6āô™s;q’fM—kķķ<°5<=Y´dį/\DŠ4ÃÎΎ>Ŋz˛cg8™ č߇ĩ6r1ōééx{{Ņŗ{W“X´t9oŋņj•kĖ/Đ1kūRãkwWBģuĻgˇgXģiŠéé Ø…BÁā~ĄĨúoßĩ—~ŊēSÃÝ5ļ—[ZZ0¨oO2ŗ˛™ņË" ]!„ˇU=ûV †ęĪ_}‡_ÚÆÅæ*%ÛļīÄŌŌ’Ģ׎āīRi†ƒƒ]ņäæ*ėíínļ5G§Ķ‘”œDTT ņņņ�(Šj)ŅŌœq#‡–Zä_‡ÍÛÊSË{;ÛÛöOOĪÄŲŅ�g'G †âånŽ�ØŲڐ“S-ĩ !„x4UÛMĩZÍā}Y°h)ffJ�–­XÍøą/âæęŒ™s0 §ŧĻŨ\ŨpqrĻKįt:))ŠÕUb™Ž<ƒZÄÆ%˜Œģ›K™íėímIIKŖ†‡; IɸšˇKNI 3+[›{ZĢBˆ‡[ĩžEĀɓg¸ví�6dņŌå8;9QÃĶ“đ]ûčÔĄŨĮhÛēķ.æ—y ÉČˤ}ģ6xx¸Wšļü?Ī]\bY§§[ŗīāa&ŽIFF&‹V„1qÜHÖlØBŖúõ¨äolÛĨãĶŦۈ‡ģZm¸0 ˛fÃbãyîŲNUŽS!ÄŖKa0ÜÚQZļãg#ņōp5U=wEž2$„â~8~6˛Âų3~üxš9†Ba*ēB!„‰Hč !„&"Ą+„B˜ˆ„ŽBa"ēB!„‰”ēj ´yyύE!„x¤•{s /7ĸbIMĪ2E=wíøŲČû]‚BqG冎•†z5MQ‹BņH“cēB!„‰Hč !„&"Ą+„B˜ˆ„ŽBa"ēB!„‰Hč !„&"Ą+„B˜ˆ„ŽBa"ēB!„‰Hč !„&"Ą+„B˜Hš÷^ÎÎÕ›H^~)ęB! 6Öŧ=\ҍ-+ܧÜЍŠMÄJc‰ŗŖ}•Š{ÄÄ'Ņ´AĀũ.C!„ ¤ĻgŸD@-ī ÷)w÷r^~ĩēJ… !„';˛s´•ę#Įt…B‘ĐB!LDBW!„0 ]!„ÂD$t…Bš§ĄģqË6ĸĸĸ+ÜūāĄÃlØ´åV$ū)=#“fÍŋße!ÄcĄÜëtËŗiË6~˙ũ0.ŽÎéąĩąĄŋŪ8;9Ņŗ{×꨹B._žÂį˙ûŠ?xŸš5}˜1s}{‡âîîf˛:ĘķöGSņĢå[bŲØ‘C1WUė̏v#ŠYķ—âëí…Á`�āؐöÔųĮ˜7kūRƍz÷E !„¨U]€öOˇĨKį�ΝŋĀôgōņ¤1ŅbÚļnIRR ĮNœÄÂÜ K FÆÜų Q˜)ąĩą&*:šĄƒ–sņŌåäæjÉÎÉĄe‹'ąąļæčŸĮ=r�˙ūü ^0cŸ&ąhé Ū{û ,,ĖË¯]ģAØú ØÛŲ‘‘™É„ņ/RWĀ×ßO§I“FÄÄÄâãåŪ įęĩët}ļ3õëąvÃF’(ŌŅ´I#ZĩhQåueiaÎËcF”Z>sŪē>Ķ7W~˜5Ÿ—FcĶļ_iÔ°õƒJŪpŖ–¯1DĶ32ųiÎBƏFnn.›ļ‡cgcCfv6ŖžȉĶį¸tõ;wī§n _Š÷˛ŗsXžf™YxyēĶŖë3>zœSgĪŖRЊHõęΑ?OpęėyĖÍÍą07gH˙^$$%ŗ~ķvėíėČÕæ1¸oO4šŽ[!ĘR-ĄûwõëÕEŠRk\včČô 큝ÚDEEŖ×ëAĄ N횴oזŖ'|ĪjÕŦ €Ž°_žn׆ĖŦlžüæ[>ũh+V…ĄÍĶ’‘ž‰­M‰Āp°ˇį‰ĻMYŊvm‰׿į1x`?jxz˛hÉ2Î_¸ˆŋŸųųô íÉõQ,X´”É“Ūį|ÄEūüķv6Ö\ž|•wŪ| Ŋ^Īä)ĶhūÄŪ"Ŋüŗæ/5žöpw%´[g†öīÅüeĢqwuášg;acmÅā~ĄåŽį`oGpƒēœ=§ģ}{tÃŨͅ•ë6qņŌUžj֘đŊŋŅšc;"/_-õž¯w ˛srاfffLûj:žnƒBĄā…Ą°07gÚWĶŅæåqôÄ)ēwîD-_obââŅëõlÜē“ΟĻvM~?ō'ũAįŽíĒ´Ž„âQUíĄ PTTˆ••ÆøzؐAlŪļƒ5kÖRŋ^=||Šo™åę ƒééPœš(Pœ’ÂÂÅKQ*•äë03SĐâŠæ9|”ÔŒtÚˇkSj^ƒÁ@Ģ–OqüäIΝŋ`\nŽR˛mûN,--šzí:ūūÆyĖÍUØÛ˙ÛBĨĸ@§#19…´Ô4fĪ€FŖ&';ģTĐW–Ĩ…y™ģzmmm¨åëMDäeöéQŠ1‹ŠŠĐ¨Õ¨T*~Ũ{�K nDÅāWĢf‰vˇ{ßÕÅ3ŗâÃûöļdeį�°zŨf,--Đjĩčt… čՃ{öŗaëN‚üëāåéArjû~;ˁCPPP€Į´+_!4ÕēgΞCŠTáäčd\–œ’Âč†a0øâĢoy˛y3�Š_¯.IÉ)8::۟ŋp””TÆž8’ظ8N9 ĀĶm[ķķėščõzBŸë~Û†??„ožû;[�–­XÍøą/âæęŒ™s0 /÷s¸ēēāæîÆØŅ#ˆŠ‰ŠrāŪIrJ*Ņ1qÔ ôįđŅã´hŪ´BũŌŌ38s>‚ŽíZ3gá2F€ŗ“#ķ–Ŧ4ķ5Š?oØÆ­ežŸ’š†Á`@ĄPž‘…FmÉÖ_w3ųŊ7(Ōë9yæ<ƒÔ´4†öī…Á``úŦų4mÔg'B:´Áģ†'YYŲ(Ėä„x!„¸j ŨŊr>â"z}jĩ†W&Œ-ņūÕk×Ųž3[[œpu-Ū‰‰eŅ’eÄÆÅ3ę…á\ē|�//Oââ˜ģ`1înXŠ5:ü-[<‰••žî(•ĘÛÖckcCīĐįøæû2h�6dņŌå8;9QÃĶ“đ]ûŒ[ģˇããå…ˇw fΞ‹ŽP‡§§>^^U\SÅģ—žģ¸Ä˛Đnΰfã6÷ ÅÉŅžfÍĮ¯v-Â÷ QũzÔ *Yëĩ¨hfÍ_ŠÁ` ¨¨ˆaûbogKũēŦ\ˇ '<ÜŨØwđ0ÖCmŠ&lãļ2ß÷ņށŊ-ë6o'%5f`kkƒ›‹ ‹–‡am­Ąn€;wīÃŪΎ]ûbcmŖŖ.Ύôx6„ [vĸҍÉÎÎĄoh7lŦ­Ēŧž„âQ¤0ÜÚÜšãg#ņōp­ö‰į.XDÛÖ- ¨ÜSyž˙ņgėÛÍā~ČS†„âņrüld…˙î?ūŪĶŊRĶRY´dAdā !„åšoĄ;ú…á•jīäčÄë'ÜŖj„Bˆ{OÎzB!LDBW!„0 ]!„ÂD$t…B)7tÕhķōLQ‹BņĐHMĪÄæow_ŦˆrĪ^öņr#*&‘ÔôŦģ.ėqrüläũ.A!„ ØXiđöŦÜ},Ę ]+ õj–×L!„åcēB!„‰Hč !„&"Ą+„B˜ˆ„ŽBa"ēB!„‰Hč !„&"Ą+„B˜ČCķ<]!„ĸĒō t\šMZF&EEú2Û(•f8ØŲQÛˇĩeĩÎ_nčfįj‰ŠM$/ŋ Z'B!LIŠ4C›ĢÅËĶŋšŪXX˜—ŲŽ @Gbr*§ÎEĸąŌÜ6œmŦ5x{¸V*˜Ë Ũ¨ØDŦ4–8;ÚWxPqīÅÄ'Đ´AĀ}ŽD!į#¯âä኷§ûÛYX˜ã]Н“KŖē~eļKMĪ$:>‰€ZŪŽĄÜcēyųhÔę („B<ˆŌ22qsqĒp{w'Ō2n˙Ü';˛s´•ĒAN¤BņX(*Ōßv—rY,,Ė)**ĒÖ$t…B<ÖĻNĘÔŠSM2—œŊ,„âąõá‡2gÎ�rssųėŗĪîé|ēB!K“&MbΜ9„‡‡‚™™Ų=Ũę•ŨËB!K/^$<<œāā`‚ƒƒ '""âžÎyßC733‹ÅK—Wē߯-ۈŠŠžëyu……|ûũ¤§§ßõw+6.ŽīĻĪ(ķŊĨ+VröÜyW$„>ĨԌ‚ņõĒUĢ6žfÕĒUÆ×:TJeĩÖPåŨ˛ļlãˇƒ‡pvūë4ėæO4ŖÃĶm+ÔßÎΖaCW¨mjZ*[ļî`ØĐÁôėŪõŽęŊ%<|7͚6ÁÁÁĄJãTˇ~}zķß˙}MP` *UÕŋė%+VséōU˛˛ŗ6x�M5äčą„­ß„ĨĨ%^5<3rqņ ˘=  xsâŦŦ4Õđ‰„âÁāhoGBr*>5î|î- ÉŠ8ØÛVk ÕrLˇcûvtéRbYäĨËlŨž“×^y‰ŋî"?/'gG9Н7ąqņ´k͊š5}˜=w!īŋũŸüû3ęÔĒE`€?ž„­ß€Ŋ™™L˙"kÖnāÚÕë;~‚§NĶļuKŦŦŦXŗļ¸]fV/<?€o~ø‘āādffĄQk<°_‰úüO>ū­VËėš °˛Ō™™E¯ŨņķĢÃÚ ILHĸH_DĶ&hÕĸŋ>Ėī‡ū@¯×Ķ(¸!]žéÄĒ5kIĪČ@§ĶQ7(NÚ3yĘTžlۜŧ\-7ĸŖy}âËäææ2ë—yx¸ģĄT¯öÛÍāĪéŗįhÚ8˜Ē¸pņ11q|ōáģddd’˜„Á``ÎüÅ|ûÅ4ÔjKū÷Ít.DD˛aķ6õīMƒzuŲļs›ļî``ŋ^Uš_!$ĩ|jpę\$P| îîH•œJL|"ëVk Õē{ä|ÄEãëžŊzāīĮ™ŗįØŧm;‘ŧņę+:rkk+ú÷íMvv_|ũ oŧú˛ą_ĄŽg;‡āááÎųˆ‹ ؏žž,Z˛Œķ.Ōēe Ԗ–4kڄ§NļnŊz<G­Zžüöû!vîÚE—ΝÉÎÉĄo¯P ī~đQ‰ĐÍĘÎFŖŅ`ŽRqũF``ÄķC),Ō‘•Ctt4—/_å7_C¯×3yĘ4š5mĘú [˜6e2 …‚]{öMLL,oŧö ƒ&OĄU˧(*ÔĶōŠ'quqæû&6.Ž3gĪҤq#žéԁcĮO˜˜DL\|Ššj×ŦɕĢWĒēį/D`cc͌ŲķHKĪ`đ€>¤§g`eĨA}ķļe~D^žBäå+øāĮ˛•kĒ4ˇB<h4jKš4 âĘõhĸbn{ ŽRŠÄŅŪ–ÆõMīåŠhßļuŠ-]€Ž]žáõˇß㕗ÆafĻ�ĀÕÅ�k˛ŗrKõqšųžšJÉļí;ą´´äęĩëøûciaQĒ}Rr nîǝP�� �IDATÅ}Ü\\8uú,�Ž(ÅsŪúī-Ú\­q׊Ú4lPŸé3fĸT*é×'”ÄäŌR͘=w>�š”ä,Ֆ(oîßŋžnîŽÆ9ėėIOĪ�ĀÉąxˇĩĨ…ē‚ŌŌŌ¨_ŋ~‰ĪXÖܷ֍6ˇrw9)‹NWˆJĨbÜč$$&ņŸ/ŋcōī”hcĐëQ(•%֑A¯GĄ¸ī‡û…â2Üņ=ÃŪŽ‚{zÉĐĒ5k5b8[ˇī nŨ �‹īœžžŽ­MŠ>ˇūö/[ąšņc_ÄÍՅ3į`@BĄĀđ5áęâB\|~ĩkŸ€››KšuŠ5ro†ZrJ õëÖĨS‡ö\ˆˆdãæmôxŽ+nînŒ=€¨˜<==Đjĩčt:”J%ë7mĻyĶĻėŲw��ŊŪ@ZZ:ގ9Fėā`OJJ �qņņˇ{¸ÉÉÉAS ĮSëÔŽÉŅc'�°ļ˛ĸ°¨GGō´yäjĩXi4œˆd`ß^\ˆˆäBD$Á ęq>"’ērOg!ÄŖ%ŋ@Į‰3•zāA“†AXVâ.V办ŨËûpöü_gÜúøøāWŊŪ@Ģ–OĄT*Yąr5ūū~čt:–,_Itt }B{ÜvĖF ˛xérœœ¨áéIøŽ}Œ9Œsį#øíā!cģ~}{ą&lļļļäæä2bøĐrĄØŲÚ ÍËCWXĀâe+°ąļĻ@W@§íņņōÂÛģ3gĪEW¨ÃĶĶŸŪŊčß§7?ü4Ŋ^O“ƍđņņĻfMf͙‡N§Ŗ{ˇ.¨osŸęļ­Z1cö/\ŋ~ĩFmüņđĪšŽ^ģNPPՏ#<Ņ´1GŽãŗ˙}KNNŖG `ˍáLũĪWXZZR§VMüũj3âųL˙ų6lۊŌLɛ¯žTåų…âAråz4^•|āÁ•ëŅÔ ¨]m5( ˙Ütü‡ãg#ņōp­–É:Lrr Ą=ēWËxUąeÛlmmh×Ļõũ.Ĩ„ü‚ūûŋ¯ųđũwīxö˛<eH!*įāŅ“4oT߸…;`Ā�&OžlŧlčôéĶL™2ÅxŲPAŽŖ§ÎĶēyŖÛŽyüld…˙?ūū_§{ŋt~Ļ;q_ŽĶŊ“5k×͎whĩ\.$„â/˙|āA`` !!!œ>}šĶ§OBPPņũ{ņ“۞uËϜîŽĖUĒgN?(†pŋKBˆĮ´iĶ0 „„Ÿ<f˘{ūāš÷˛BˆĮÖgŸ}†••�}ôŅ=ŸOBW!ÄcÍa{Ëc{LW!„05 ]!„…>đ <÷â冎ÚÂm^^ĩN*„B˜Ú­TTy<HMĪÄĻ’72*÷˜Ž—Q1‰¤ĻgUj`aĮĪFŪī„⥠R)‰ŊyƒŠ>đĀÚĘúļgmŦ4x{Vî>冎•†z5+5¨Bņ Ę/Đ=ü<B!–æÕz[ĮʒŠ„B‘ĐB!LDBW!„0 ]!„ÂD$t…B‘ĐB!LD.BņظunZF&EEú2Û(•f8ØŲQÛˇ†é¯ĶÍÎÕ›H^~AĩN,„B˜’Ri†6W‹—§+~5ŊīxGĒÄäTN‹DcĨšm8ÛXkđöp­T0—ēQą‰Xi,qv´¯đ BT§˜›ˇmkÚ ā>W"„x˜ŧŠ“‡+ŪžîwlgaaŽw w @vN.ęú•Ų.5=“čø$jyW¸†réæå QĢ+< Bņ JËČÄÍÅŠÂíŨ]œH˸ũsœėČÎŅVĒ9‘J!ÄcĄ¨HÛ]Ęeą°0ŋíũ™ī–„ŽBˆĮÚÔŠS™:uĒIæ’ŗ—…B<ļ>üđCæĖ™@nn.Ÿ}öŲ=OBW!ÄciŌ¤IĖ™3‡đđp�BBB033ģ§[Ŋ˛{Y!Äcéâŋ„‡‡Lpp0áááDDDÜĶ9%tMčô™ŗė˙í`•ÆXžr5gΞĢϊJ:xč06m!33‹ÅK——ÛžĸíūiÆĖ9\žzoŋ˙‘ôôôģ)U!*MŠ4Ŗ @g|ŊjÕ*‚ƒƒ¯ƒƒƒYĩj•ņuA•RY­5T9t7mŲÆ[ī~ˆ^˙×ÅÃ{÷`ÂÄ7Ģ:ô#'¸aÚĩi}×ũ¯^ģNVV6 Ô'11‰¯ŋ›Î?ÍdúŒ™hĩ•;mũNėėl6tpĨÚÍ]°ˆĖŦė ĪĄR*éڃĢÂîēÎŋ‹‹O`ōŋ˙ÃÔ˙~Í´/ž&7ˇúևâŅāhoGBrj…Û'$§â`o[­5TË1]WW'Nž:MĶ&8qō4nn.�\ģvƒ°õ°ˇŗ##3“ ã_$::–­ÛwōÚ+/ąã×]äįåĶŗG7ãx˙šô ˙™ö�LŸ1“^Ą=ĐąvũFllmČÎĘfô¨ČÉÉfÅĒ0ČÕj1lÖVVÆqæÎ_ˆÂL‰­5QŅŅ 4GæÎ_„ZmIVV6}B{āããÍ'˙ūŒ:ĩjāOĢ–O Õj™=wVV23ŗčÕŖ;Ū>Ūeö_˛|%ŲŲŲčhPŋ.­Z<UĒoBRÉÉ)„öčÎĒ5kIĪČ@§ĶQ7(NÚ3yĘTžlۜŧ\-7ĸŖy}â˨TũĘÚŊg/žn @Øē ôčū,ėŲw€đŨ{iŲâIf͙Į‡īŋSâû)k}†‡īÁ܍ZMäĨËŧ0l¨ą}jZ*ŗį.äĨGņõ÷ĶiŌ¤11ąøxyŖ7čšzí:]ŸíŒ‡ģ+ŗį.¤īPNœ<N§cİį™ŋpqŠõķëŽ=œ9{ww7RĶŌ�¨]Ģ&ņ ääæ–øŪîÆĸĨ+Ôŋ7 ęÕeÛÎ]lÚ烁ũzUiL!ÄŖĨ–O N‹НÁŊĶŠ’S‰‰O¤qũĀj­ĄZBˇu‹ü~øM›4&&6–¤ŨüÃĒÍĪcđĀ~ÔđôdŅ’eœŋp‘fMsæė96oÛNDD$oŧúJšsœ9wŽ€@ē?ۅä”TĖ V‡­§G÷gņ÷ķcīūėŲģįēuũĢ“BAÚ5iߎ-G˙<Nøž=¸ē¸âííEĪî]ILLbŅŌåŧũÆĢę yļsŨŠ$&.00âųĄéČÎĘaßūßJõëõ‰œ>}–ß;;[Ž_ŋQf߄¤â;+EEE˯Ŋ‚Á`āŖÉShÕō)Š õ´|ęI\]œųūĮŸ‰‹Ã×į¯;\šzÞāÚõÔŽ]€:ĩkąaĶfztīZ*poGŠ4Ã×Į›vmZŗã×]œ<s[ëmJ%ųųô íÉõQ,X´”É“Ūį|ÄEūüķŨģuĀΝînŽ 4ŋ,ĩ~Ū|m"Ûwūʧũ33˙ß_')ÔôõåúõÔ¯WˇBußNäå+øāĮ˛•kĒ4žâŅŖQ[Ō¤aWŽG›pÛkp•J%Žöļ4Žhú{/W„•ĩV+RĶRŲā !:röüy�ĖUJļm߉ĨĨ%W¯]'Āŋøc×.ĪđúÛīņĘKã03S”;G§ŽíŲŧe;_|ũŽ.Î ؏¤ä$~ ßÃîŊû)Č/ F ĪRũ\]Ў¸ėHOĪ� **†øøx�ŠŋævšŲö˙:ĩiØ >ĶgĖDŠTŌ¯O(IÉIĨú+ F Âĸ%ËČÉÍå™NhÖ´IŠžˇ$%'ãæîjėoī`oŦÍÉŅ�K t%īw]XXdÜōũ{ŨŊ…ĸōG œ°°° ;;°.ÕÆÁÁ�ssööÅ˙ļPŠ(ĐéJĩ-ūlĨ׏VĢÅÚĘĘø=ģē8ÛÛØX‘“›[éÚ˙Š:ևâqb¸ã{†;Ŋ]ÕvÉP§ŽíŲŋ˙w˛srJüQ]ļb5ãĮžˆ›Ģ 3fÎÁ@ņąßUkÖ2jÄpļnßAŨēAXZXû(•Jôz=fff¤¤ī‹K {ˇ.XiŦXŋa3GŽü‰›Ģ+ŨēvĄĻ¯™(ĖJ˙ĄMHL¤~Ŋē$%§āččˆĢ‹ .NÎté‚N§3Ž øGö'§¤Pŋn]:uhĪ…ˆH6nŪF€ŋ_ŠūyyyXZXđʄqäååņéŋ?Į××§T߯Å?öė;�€^o --G‡r׹RĨ2oíZ5š|ų*uƒ¸tå*~7ˇzËėWÆúŦv 0ô¸šē•Z?†œÜ\c ņ‰Æn99Ú*īZô÷ãBD$Á ęq>"’ērŸf!DIų:Nœ‰¨Ôš4 ²wą*Oĩ…ޝ7kÖŽ#¤c‡Ë5lČâĨËqvrĸ†§'áģöaŽ2G¯7ĐĒåS(•JVŦ\͈ŋSlŨę)fĪ›Ģ+*s ƒœœ~øiNŽäæj6tūŦ^ŗ++ YYŲ Ô;[›ķĮÄIJhÉ2bãâõÂpėlm™ˇp1ŋĖ[HFf&íÛĩ)ąKųŸ/[ĩ5ē:uhŋŸ_ŠūŽŽ.ė ßÍļŋbĻTōtģ6eöÍĖ*ž‡§75kú0kÎ<t:ŨģuA]û[ûÕŽÉÕk×đ÷ŖoŸP.^ƯģvĄ4S2zäp’SR˜3w˙z÷­ũĘZŸÕ­v­ZĖūe>cFŊž•ĢK­ßg:vāĢoĀÅÅnũŒŧ~ãũûõŽōü#žČôŸaÃæ­(͔ŧųęKUSņhšr=¯J>đāĘõhęÜ~ŖĻ˛Ý˙?‰—‡kĩMhJs,ĸmë–<[=WŽ^cמŊŒõÂũ.ĨZ\ŋ~ƒ-Ûw2a܋wl'OBT‡ƒGOŌŧQ}ã<y˛ņ˛ĄĶ§O3eĘãeC:Žž:Oëæn;æņŗ‘ūÛ4~üxšN÷aR§v-Ŧ­­9{îüũ.ĨĘ ož>x`ŋû]Šâ1ņĪÂéͧ9}ú4!!!ßŋ<x¤o9ú…á÷ģ„j7d`˙û]BĩPŠ”ŧņZųg­ !ÄŊ2mÚ4 !!!�Œ3æž?øā‘]!„âN>ûė3ŦnžĖųŅGŨķų$t…B<ÖLļˇČ1]!„ÂD$t…B<ūųƒōܗ¨-,ĐæåUë¤B!„ŠU÷RĶ3ąąŌTdžréúxš“HjzVĨĸē?yŋKB<ÄT*%ą7¯û¯čŦ­ŦoûˇĮÆJƒˇgåîcQnčÚXi¨PŗRƒ !„ĸüŨÃ˙Ā!„âa`ia^­ˇuŦ,9‘J!„0 ]!„ÂD$t…B‘ĐB!LDBW!„0 ]!„ÂD$t…B‘ĐB!LDBW!„0 ]!„ÂDĘŊ ¤N_Č/KØŊ‡”ü´ ęĸvĸ‹WF ÅÜLî4)„B@ļtįF,eŲåĩ\€äŧT–^cŪÅeÆek×o`úO?ΰFß]ĩ }û°:líũ.C!„ ”ē;böÜÕĀŖƒ†˛öâæģęû 7aâ=˙évméߡO™ī­]ŋ-ÛļßĶų…B˜Nšû~“ķ*ūĀß[Æ=Īđ€ĖXJQQJĨ˛Ėv_|ų5‰IIäåå͞œ¨Õj˛˛˛õÂ>øh2vļļ|đūģ|ũí÷4 nČ3!�ˆŽŽaŌäOY0w6�!Īv'|ûĻLũœôôt´yZÚļiÍķCķí÷Ķš~ũēÂB:?͉^={đáĮŸ ĶépuqÁßĪáá¨-Õ¨5j>Ÿ:ÅXßę°ĩųã(?ΚÃđį‡ō¯IcggKbB"ãĮĄų͌m7lÚˆ›077įûožâĮ3KÍģpņRöī?€—¯\áŊwŪâbd$11ątėОožûGGGŌĶŌ™ôáûĖ™;GüšzõĢÃÖáčā€-ūëŊŸãŊwŪĒô÷$„´Ēõ€Ģ¯4̀Ú=ËļīØI÷n]KĩŊp!‚ČČKĖžųƒgģ‡2ëįųüŋ˙cÔ #ČČČ 3#€ŖãĨqcî8ˇÁ``īūũŦ\ļg''Ξ;Ī…ˆ‹?q’sgSTTDĪŪũčÖõYĖU*‚4`Č⁌|qoŧö M7æÂ…ˆ?z‡ödöœyŧ4n ķ,¤qŖ`ƌIl\¯žūkVūĩû\ĨRammÍw_Yæŧ]:?ÃÜy Øĩs+�!]ēŖPüU˙ūŋņdķæŒ3š˜˜XlllčÔĄ=õęÕÅßĪąã_aëĻu¨ÕjŪzį}Nœ<Yâs!„xđŨUčļqŠß˙@o0—)PđZÃ1ô­õ\‰ļËWŽ*3toDEáëë[ÜWĄĀŨŨ }QŲŲŲ\ŒŧDM__ŌŌŌ‰ŽŽAŖQceeuĮš S>ų˜ÉŸL!3+‹áĪAĄPĪ;ī�€ éié�øøxđéäI˜5‡/žü†Ö­ZRˇnP™ãGEÅĐĒåS�Ôđô$>>ĄToī›mŖJ̓-ffÅ{ôũũũJôöü~ž9›á#_ÄÛˋIŧg|/++‹ôŒ >úä˙�HMK#%%ĩÄįBņāĢtčŽ ĖČĀÁl į‹SĶŅ eîŠ+ë¨Äɓ§JãëãÃō•ĢĐëõÄÅĮãîîNĶ&™ķË<B{ö ))‰īĻ˙HÛ6­KôUĢ-ÉĪĪ +;›ŦŦlrrrą˛˛âĮž%''—Đžũųé‡ī¨UĢ&_ū÷s�".Fâææzs”âÍĖččX>Ÿöo Ã^ÍsŨēRģv­â zƒž¸^_oŽ\Ŋ˙`đōĒQę3)nnēúúú–šˇvíšddfb00 \ētšDß˗¯0~단ÚÚōũôŸØ´y+ …ƒ^­­-..Î|>u æææ\ģ~Oöí?`üB!|• Ũļ-8€n>!0đåŠŧ<žž]Œí–]^ËĪį�ĐãšîĖ™;ŽڗĢnŨ 6¨ĪÛīū‹ŧŧ<^7kk+Ú?ŨŽQcÆ3åĶIKOįũ?âå—Æ—čëâₓ“#S?ûööö8:8`iiÁ‚…‹™ķË<Tæ* čGP`�uƒyķí÷Č/ČĮßĪ Ā€c>s†šķāääHž%ļ•J%666LûüŋŧųúĢ|đŅd>˜ô1)ŠiLúāũÛŽ§˛æ}ë×2h ã_~Ow\]]Pü-0322˜0ņu<<ÜÉĖĖâĶɓ8uę ?Îøo//Ūyķ &žūV …EE|ņųÔĘ|uB!� ƒáoûˆËĐ~SoãŋÍ Ūmô Ũ}ž1.‹É‰ÃËÚĶøzå• üxnŽņõŪëĒŗŪ‡ÚžũxęÉæ¨ÕjúĘĪ?~‹‹Ëũ.K!„ Œ?žr[ēzƒ/NūˆN_H¯šÅĮi˙¸›nėā§sķǎĘGH\\<ŖĮž„“Ŗ#mÛ´–ĀBˆĮLĨé0đÍé™�ÆāØtc'_žš;n8?Ö ėĪ ũīwB!î“ģē÷ō­ā]}�[ĸ~åĢĶ?Iā !„wp××é0đ홙œKģȎ˜Ũ%.B!Diånéē¨nûžŪ``[ôŽÛŽ‹Úųî+B!1å†îŗŪīzđŽUč+„B<jĘŨŊ<*p�ÛŖwWø>Ė.jgēzw4^Ķ+„Bˆ \§+„BˆĒ?~üŨŊ,„BˆĘ“ĐB!LDBW!„0 ]!„ÂD$t…B)÷’Ąė\-Qą‰äå˜ĸ!„âĄ`c­ÁÛÃÚ˛Â}Ę Ũ¨ØDŦ4–8;ÚWЏGML|M”ßP!Ä#)5=“čø$jy—ßøĻrw/įå QĢĢT˜Bņ¨qr°#;G[Š>rLW!„0 ]!„ÂD$t…B‘ĐB!L來nl\ßMŸŒ™sîg)÷ÄK¯ŊCQQQĩŒUXXĤO§p!"’y —2ö•7Ģ<nrJ |2•ŒĖ,Ūų`r•Į3•O§}AŽļb'0ÜúŒ€qUeŨíÚģŸeĢÂnû~Uŋ÷ęø^…Ļr/*ĪĻ-Ûøũ÷ø¸?°^ĢÍŖyŗĻtéRŠq&Œs×5<ts•Š'›?a\VTTÄ;īOâ›/˙c\ļuû”fJētaü+¯S7¨ø’ƒĄ¸ũ‹Ŗ†0ũ§ŲLžôū]×s'É))Ŧ Û„સöĮ™Œ>{;Ûrû¸HP ?�ĮNœĸi“F<üĮ=ŠņnėÚģ ssÚļnYęŊü‚^ûúõéIįNø`ōŋQŠT˜™˙æ›ôŪ[\ŋÅüÅ˰˛Ō TĒxķ՗Pš)ųdddb0 ôgØāčt: ąŌhLü)īŪÖâņQåĐh˙t[cČæääōņ§SéŌ9„¸øxVŦ ÃŅÁ\­–ÆPTXÄŦ_æááî†Rõ×ô˙šô ˙™ö|ōī΍ŚūÔĒå[Ēŋ™BÁ/ķĸ@6/į dێX[ÛPÃĢ^žžĒŲŌŌ’7_›h|ŊlåjNž:CãF Ģc•�`ffÆ˛•aÄÆÅ“™•Å˙}ô> –Ŧ 2ō2ĢÂÖsäcčt:F Ė˙}ö͛5&;'{;F Ä3fĶĒœ4oքc'NņDĶÆ�œģÁ€žĄÆyŽßˆbÁ’8:ؓž‘ÉÄņ/biiÉ7Ķg @AŽVËÛ¯ŊLJj —ŽĀŅÁ´ôtūõÎë%ęÍËĪįĮ™ŋ”œ‚¯ŖG<_æØŠii,Zļ ;;[˛2ŗ;jaë7akk‹¯¯ë7n1Ö đËüÅøųÕ6ݓËw˙›f ]€…KW0nôjúú°vÃfvíŲOģÖ-éŌ‘āõČËËįå7ŪeØāœ9wúõ‚�˜=o ‰IdggķüāÔŠ]“¯žû ;;[“3j8vļ6e~?ķ.%5-Ŧėl&Ŋ˙ĨúÖŠUŗÔˇņŲ˙žĄ×s]KüķĪīũŨ7&–X?_|ũ=íÚ´"99…B­ É)ŠxÕđd`ŋ^wûŋ›â!P-Ąģ÷ĀAÎG\$;;33ŖG`uØzzt??öî?ĀžŊûP(ĖhŌ¸ĪtęĀąã'HLL*1VĄŽg;‡āááÎ?Í,Õ_iĻ$(0Î!šI~A‚ƒŠéã]áĀČĪĪ7îڎ‰ĨY“Æ´jŅ‚\mNuŦ�ôz=íÛĩÆĮۋ)ŸÉĩQtjߍZ̀žŊ8üĮŸŒũ:]999Œ>…BÁĎۧOĪįxuÂXãX".ōü ūäæjQŠT˜››ß[¸tCöÃ߯6á{öą~ķVėlm nPŸĐįēröüRRĶČÕj;j8>Ū^ü4k.'OŸ%āoa˜‘‘É„ąŖP(Œzé5FxžĖą­­ŦhPŋ.ũ{÷$1)+†'Ÿh†_íšøz{•¨û÷#Gq°ˇĮĮÛ˸,//ŗį‘–žAíZž ØKKK˛sŠ×ŊÁ�W¯]§[—‚ÔcÖ܅9zŒQÇÅ[úmZĩ ōō“øčũˇHJNábäe23ŗčŅ­ ͚4âĀÁCüēk}{õ(ķģéÔĄ5}}øaÆlŽ;A-_ŸR};ļoWj�ƒŪŽĶĻ[—R?ÔūųŊ'§Ļ–X?……EtîÔ'GGÃüYĶą´°āÕˇ?ĐâW-ĮtÛˇmÍë'0bøPŠŠ 4¨_€¤ä$~ ßÃėšķ9sæ:ŌŌŌpv.ŪíââRæxˇ–—Õ?%5—âūÔĒå[æ …ƒĄä˛ĸ"=Š›[W–––ŧ>q¯Oœ@­šžÔĢ„ēˇōǍž�hÔjtēÛļssuEĄP�ā`oOZF†ņŊŦŦlŦŦ4¨TJNœ:MŖ† JôOHÂĶĶ�Owwâ’HLJÆŨÍ€õęâīWsskÖmäį9ķšxé2˙¸ĩ§ģ›+fff( ”7×SYc?×­ š9š|øé4–­ ÃLYö˙F)ŠiėØš‹Áú”Xūί0ūőLzīMbbâ8tä(cFcËļ_ų懟IHLÄÂâ¯ãFāģ/?cÉōUäæjšxé2ūuHúÛgtuqĻM̧P[ZrđĐfĖžĮžũÉŋÃíKoũpss%55­ĖžeͰeû¯\Ŋ~ƒõë–9vyß쓪#�vļļhÔjĖĖĖĒíøŋâÁU­'RųxyQÃĶƒŋŠƒ¤[×.Œ=’aCŌŠ#ö¤¤¤�_æ87ŗ§Ėū.ŽŽÄÅ÷ûöî;0Š2˙ãø{[˛Š¤Ō%„ŪDŠ@PPzÁvž?õÎ;{ÁržíėŠ‚‚H“""]PEzG0BzĪ&›ėūūDc’lP>¯ ŗ3ĪķÉÂgį™™g÷îÛĪŪũ0Āņ‡„5xyy–I;NöîÛOTdd…ū†Ėŧ‹(.Žũ˙ôJ? 8O˙€Ķé� éäIŽŌ劊iøų•mŗmĮNÚˇm”žåuėĐļ\›õCCHH<@Bâq„Rŋ~(ņ ĨËļīÜ͎]{˜:íFʝĮS?$ĮŠžĪϞļŽ3|č@^xú1B‚‚ØđíĻJ›6˙„ÁhäĩˇŪcíú|Ŋj-‡á—Ãŋb6›0 x{{QRRBvvãĮ.Ŋ–k2Ņ"6–=ûöķūG3�đôđĀ`4’’–N=__ŒFãŠ}L 9%… §x�� �IDAT+×đޯhĶ”ģíÚüvŦ+q"é$�'“S ŦtÛĘú�č{í5Œ=‚7Ūū ĘcTz|DäŌS#ÃËŋ7xĐ�^üīĢ\Öą=Cdū‚/đôô ''—Ņ7 §G׎ŧ;õ#Ž=†ÕÃzÖ˙+ÛūĒ]™öņ,Ūy*6&Žŋ…üŧ|–.˙Š  @š4úm¸tâ„[™>ķSÜ,f ‹hßļmŲÍHŋLķØfŦ^ŗŽ+ŽčX͇¤œ°õŲžs7kÖm YĶ&ŧüúÛLžũVüüü˜öÉ,N$%ĶŖ{ŧŊŊxûũčrE'ļnßɈĄĨÎqĮ≎*˙ÁáÖąŖ˜1kõ|}ÉÍÍãŪģ&b6›yũ­÷yáå×ÉĪ/āŋŨE§Žxįƒi„Á˛å+iÖ´ņYë­ŦíCŋaĘ^%(0€ŧŧ|îēã6:ĖgķĖęĩßĐåŠNÜØ¯/7öë ĀŌå_cĩēͤ1[ļnįņg^ĀÍ͂——];_Î/G~åĨWßÄĪĪ??ēvž€5ë7ōä”˙PTTDīkŽâ—ÇéĐŽôHãč†D7Œäų—^#;'‡ąŖGȜĪrč—懹kĪ>âO}hø=‹Ųˆī6q29…œœ\:uh‡éÔĩØßo;đÆ~úHNIÁbąĐåŠNėŪģÅËžÂĶ̓ŖĮâ™8~\ĨĮąIŖFeĮGD.]įŲRØļįáõõÅÕä¤ĻĨņĘīōÂĶÕH{""âÛö:į,˜<y˛&Įq…îE (0Pgš""—�…Žˆˆˆ‹(tEDD\DĄ+""â" ]Š2t­nnØlލEDDäO#=3oĪķûâ•*'Įˆ !>1™ô˜ .ė¯j۞Cu]‚ˆˆÔoO"œß<U†Žˇ§-b^pQ"""RJ×tEDD\DĄ+""â" ]Q芈ˆ¸ˆBWDDÄEē""".ĸĐq…Žˆˆˆ‹T99†ˆˆČ_Ea‘#GČČĘϤÄQé:&“?__E…áau¯ŅūĢ ŨÜüâ'c+,ĒŅŽEDD\Éd2R_@xƒ`š4ŒĀÍÍRézEEv’SĶŲš÷žg go/"ęŸW0WēņĮ“ņôp'ĐŋŪ97*"Ĩ“RčĐ*ĻŽË`ߥ_ ¨LDƒĐŗŽįæf!",'›—OÛæM*]/=3›„¤bĸ#Κ†*¯éÚ ‹đ°ZĪšA‘‹QFV6!Aįŧ~hP�Ygū˛Ÿ�?_rķ ÎĢŨH%""—„’Į‡”+ãæfĄ¤¤¤FkP芈Č%mʔ)L™2Å%}éîešd=ōČ#|øá‡�äįįķüķĪ×j ]š$=účŖ|øá‡ŦYŗ€ŪŊ{c4kõŦWÃË""rI:xđ kÖŦĄM›6´i͆5kÖpāZíķĸ ŨėėfÍūŦFÚz÷ũ9z,žÆÚēPvģgŸ˙+W­Š‘ZÎ&;'—į^üo´ĩk÷6~÷ũmģtų âã.¸o{q1¯ŋņ6™™™ÜÆ…:~â˙{ëŨJ_Ûōķ6-^ęâŠD¤ēL&#EEö˛Ÿį͛G›6mĘ~nĶĻ ķæÍ+ûš¨ČŽŲdĒŅĒ=ŧœ™™É'ŸÎÁl6ãp8(ļ3ū–›ņķķģā6}}};fTuKĢß˙°‹ŲĖå.ãŽÉ/¸´´t|ŧŊéÛ§w VWŪ´3>tH´õŋˇŪåī÷ŪE›Ö­.xÛũ¯¯V kÖŦŖc‡öÕz/ՆN—uāģM?püÄ Â4¨v{›ú™ĨËŋϤ¤„NÛ3lđ�ļlŨÎÂÅËpww'<ŦĮÅ^\ĖĢoŧƒŨ^ŒÍfcâøąD7ŒĒ=š4ø×ķådj:‘agN÷´“ŠéøÕķŠŅĒē›ÜBlL ×õŊ€ũ‘™™ŸŸ‹–,%ųd %Ž:´oK×ΝųôŗĪÉÍÍĨ¨ČNĢ–ÍéÚų ĻN›§§ŲŲ9 ēą?ū~Lö ?x?ķ,"3+ ģŨNķØfôēæjžxf —wę„-ŋ€c üũŪģ1›û4˛zízvīŲKhhé�lüî{˛˛˛šą˙õÄ'&˛xÉ2Ǝž‰7ßy???ŪØŸø„ļnێÅlÁ×ׇÁ°bå*ŧŧŧ ã͡ŪãÅįž&!1‘‹–PĪחėœnŊy4ûäĮ-[ˆŒŒāhÜ1ēwīĘå—u,Ģiåę5$$&˛vũ74‹iZaûŊ°yķO˜Ėfîš4ąlâ⎱pqéēYŲŲÜ5ųvrsķ™1ķSúŋû�ø×ŖOrĮ„[Ųžcvģ›FŒ °¨OfÍ&==ƒ°°Œ>”­Ûwđíwßãí퍧‡ŖFį‡Í?U¨Ûfŗqđā!žüjūūū¤ĻĻҧwO>úø (°Ų¸ãļ[ÉČĖĒPۖŸˇ•m{29…ŨēÉôŗđđđ 33“ú]GHp0¯Ŋų6mÚ´";;ĢŖF+÷ŪúöûM<ųø#�Ū7ŊŽšēŌ÷W||Ÿ/X„ÅbÆĶĶ“ ˇŪÂĪ[ˇōÏ[đōō ÜvË͢9‹›Ģ•CŋæÖącđôô䃏ĻS?4“ųˇ•õ}ՕŨŲ°ņ;F^­?………Ėžģ€—_x“ÉĖÖí;p:|øņ,^é9ŦVwūûÚ[ė?pˆ¸ŖĮˆŠŒ`ôˆĄ$?ÁģS§3åÉGĒÕŋČĨ$:2Œ{ĨĪāžmFĒ“Šé$&%ĶŽeŗ­ĄÚĄÛĄ};>žų)™™4nԈæÍcņõņ&!!Ã‡åĄ˙ģ‡ÃÁĪ<Įe;˛k×yø!|}}8zô‰'’�'ˇÜ<†â;š9yemĮĮ'˜xœûīģ§ĶÉcO<C×.WPRė Ë—ČoŋĮņ'ˆŠ,ÄápōõĒÕüįšg1 <ūô™/ˆMf222yô_˙Äh4Čä‰pwwįŅ'žaČā´m͆†‘„˙îŒfáKtã DGGņŨĻXĩv- Ŗĸđđđ`ČĀÄĮ'°hÉŌrĄ{mīžddfŅëšĢyãíw+ŨŪŨjåŽIˇ—Ģą Đƨ‘ÃkЀ™ŸÎaßūƒDFTœũ¤I“Æ„†3úĻ‘�ädį2vĖ( ˙ø÷cŒ>”Ī>ŸĪŗO=Žģ›ī8_ŽüŠÉdŦP÷=wNbÅ×ĢšĄßõ|˙Ãf�žŲđ-ąÍšŅ§wO:DFfVĨĩuëŌšlÛi3f–nûí÷4ŠŽæúëŽ%-=wŪ˙ŋß{7šyy 4°ŦÆß‡nNn.XĖfœNg…÷Meī¯N—]ƂE‹šiø"""Ø´y3šššĖ_´˜)O?ÅlfÚĮŸ°gī>L&#Q‘\ŲŊ+W¯eĮî=”Ķž][Žíu [ˇm'99ĨŌž7lČō_ŸË?‘ŗJH<ŽŸ_=f|:—“ÉÉ\ÕŊ™™Yxzz`=5ĩ\LL>BÜŅctīÚ€đ°œH:YíūE.%VwÚˇŽåČŅ⏟<ã3¸&“ ˙z>´kŲĖõs/W%$$˜>x?)Ši9r„>œF÷Ž]pˇē“‘žÁÔiāáa%?/[Ǝfæ§sČËĪįÚ^×ĐąC{ZˇjÉ[īžÉdbؐ˜-�¤¤Ļ €Á` ž_=23ŗ�đ/rtwsÃ^ôÛŧĐxyzb4� <kũeëBéYģģ;yyųØ í•n“’šFHhPéūąs×FEáī›[šëįē}eĩZĖ&V|Ŋ www~;JLĶĻgŨŸĶ‚‚1K/ŲFō ōÉËËã“YŗČÍÍ#'§tĻ•sŠ;-=–-[�Đ,ĻtZÃ_>§ÚRSSiŅ<€Ā€�2ŌK¯Ņúûųa0”ûĶžV_€§§GŲk|ß`0Txååæ’šžNāŠãØĩsgōōķqwwÃręĖ58(˜”ÔT�~ˇßšššdeeҞeËSĮ/čŒ}7l…§ˇųųļ3˙ΑŨ^LffO>ōJūūāŋyôáĘ­ãt80Ôđu%qžõ5įŲ^ކj‡îĒ5ëhŪŧ‘ááâááÁĻÍ?Ōŋßu„„†pĮ„ņ�Ä'&bĩZqwsãžģ&aŗŲxęŲˆŠŠ¤eķŌ!ģũąôËÜ4ĸôēdpPë7| ”žÁfddâ_Åõ=ōōķq8FN&%āfą`/. ”ô´ô˛õO˙__XTÄâe_ōâ”g(q”°uë681Āņ‡Ŗĉ¤“4iԈI' :¯cvÆí˙<�sæÎgōˇÄģīˆn ŧöĨ  €ÛŠiČ āt:0*ŪįéáI=ßzÜvË8Ėf'O&ãīīĪŽ;+Ŧk0pūaŸƒ‚ƒ9q"‰íÚ˛wß~0X°đ‹ ĩUļméū&íHNN!0ČŋĘcdõđ ?ŋtŋl6[…÷Í=wOĒđūōķķ#$8ˆ'NŌ¸Q4ëžŲ@Û6­ąŲ ąÛíX,N$%ŅŖIW+ôéįW´´4€SõVŪwĮíÉĪËĮĶŗúĶŖF„‡a21 XĖf, ž>>Ø läāéáÁž‡9tn ûĸcûļ‹O *"ŧÚũ‹\J ‹ėlß}āŧžđ }ëXÜĪcĢĒT;t›7oÆįķb6›1™Œۋ9b(a ÆûS§a/ļĶ A}¸‘UkÖąbåjŒ&W]Ų€YsæâíåE‘Ŋˆ^×\]Övdd FōÁ‡ĶąÛíôī×kķ@Žíy ¯ŧū&AAAxûxĶIķØXÖŗ‘ų ŋ�ƒĄÂ§7‹…úĄĄL6oooZĩjɗËWĐ<6–ĨËŋ"čwgĄÃ†bÁÂ/đņņ!?/Ÿ[ƍa˙ūsŋÍü|ļoÛē5ŗfF`@�a °fí:ļoˇˇ7ŗįÎÃËĶo/o�EG3õŖšeė˜Ęû2wŪ˙�w7wƏ;Ã14bĩZ™ķų|FEpUŽLûxīŧ?•‚ĮßrÆÚNo{ÚU=ē3ũ“YL›1‹œœFQå1ōõņĻĀfÃ^\ŒÅbŠđž‰ ¯đūŠ gčā|>fŗ/OOŽžōJFĘģ|„§§žž´lҜŸˇnĢĐgŽ]ywęG=z ̇§ĶYiß�GâŽŌ¸QŖ*÷Ŗ*ŪŪ^\ß§7O?˙_œN'Wõ芏7oĮ”_ÁŨŨÆŅ iÚ¤Ņ Ŗxí­w™ōŸW)**bŌ„[ĒŨŋČĨäČŅÂĪķ ŽM ELõ˙­Ÿfpūņ´äļí9DxũāëPä\-_ąoŽėŪ­ŽKŠāˇßcØĐAåŽõWFß2$rņø~Ë:ĩmYv†;bÄžx≲Į†víÚÅ3Ī<SöØPQ‘-;÷Ņ­SÛ3ļšmĪĄsū7>yōä‹ķ9]€>×öâį­Ûëä9ŨŗŲēm;áaUވ\\ūø…͚5ŖwīŪėÚĩ‹]ģvŅģwobccË^¯/<Đ4rҞ˜ÍÜ˙ˇģëēŒ :vhOĮíëē ŠĻįž{§ĶIīŪĨķ'Lœ8ąÖŋø@Ą+""—ŦįŸOOO�{ėąZīOĄ+""—4W„íiēĻ+""â"U†ŽÕ͍[õ'ŠKüƒĒTõ…é™ŲxŸšÄį\UųČPn~ņ‰ÉØ~7듈ˆČŸÉXúĨįú…ņĮO’‘•M‰Ŗō×Ŋ==ˆh|ÎSENž<šękēŪž´ˆixN Šˆˆ\Ŧ l…ô…59˙˛n¤‘KÂ_â DDDū,ÜŨ,5:­ãųŌŨË""".ĸĐq…Žˆˆˆ‹(tEDD\DĄ+""â" ]Q芈ˆ¸H•Īé?~Üuˆˆˆü)………ķēU†îų4&"""gĻáeQ芈ˆ¸ˆBWDDÄrrrē"""ŽāããŖĐq…Žˆˆˆ‹(tEDD\DĄ+""â" ]Q芈ˆ¸ˆBWDDÄEē""".RkĄģ|Å×ŧōÚ˙Î{ģ ŋeūÂEÜoīëú_đļUyįŊØŋ˙@še‡aŌ]÷VģíIIŒŊu�ģ˙Ájˇ'""Ÿs Ũœœ}â)>š>ŖĘuĢ@W]؃áC‡TĢÚr÷“hŪ<ļÖûyķõWÎøZMŧˆˆÔ*ŋÚāŗšķ˜8á6>˜ú!%%%˜L&æ/\DJJ*wMžƒņŋ7ßĸWĪkøņ§-ŧ÷Á‡DEE˛o˙~ūũØ>|„ÛoĪu}¯åĨ—_%9%›­./gė˜Ņ<ōø“Øív‚ƒ‚ˆ‰iJbâqÂ4`ãˇß°vũ7L˙đ}üęÕㅗ^&4$„ėœlĻ<ũ$žžž<øĪa2š¨_?”’â’rĩ?q‚‰“îâĒ+¯$;'› Ā@¸˙> FģļméÔŠ#-[´āåW_'88ˆ´´tž}ę &ßũ7Ū~ķ5Ô¯Īę5kųöģīąŲ 6t07⁇ĻQtC,KY_¯ŋņGÃ^\LŸk{1hĀåö­i“&ŦZŗĢģ̇•Ļ<Séņî}]Ö|ŊœgĻŧ@ff&ļztī†ģģ{ŲņŊsŌÄ ũ‹ˆHŠ2tív;û䎉¸úĒĢøzå*ú÷ģžŌuĀÔ§sᤉ,_ņ5žŧ0åöîÛĮëoŧMèHú…ŠīŋƒĶéäēū<h ŗ™6­Z1zÔH-^ĀĐ!ƒ:dŸĪ_@PP ;´įŽ{īãŽÉwĐąC{æÎ[ĀėĪ>§k—ÎđΛ˙###“™ŸÎ)W“Á` ;;‡‡˙ņ�ƒ eÂø[)*˛sûmˇŌ¨Q4“īū÷Ũ{7­[ĩdŅKøø“™ 4€/—¯`â„ņ,^ú%“ī¸Y§Ú^°pŊ{^Ã-ãnfåĒÕ=ĪūŲļ}3ĻMĨ¤¤„ƒ‡ŅīúëĘíÛøÛ'q˙}÷Đž];öī?PöĻ2N§“o6näķ9ŗ `ĪŪ}Ä6‹);ž""ōįSåđō˛å_wô(ģ˙A–.û’Ī>Ÿw΍G7ŒĀĶĶ“ÂÂBŽÅĮUēĖ`0BōÉd�"##*lŋåį­Ŧ^ŗŽũķ!�ŽÅ'đÉĖOyčáŗaÃF HJ:IDx8�ūū~x{{Uh'<< ƒÁ�@`@�ŠiŠ�DD”nŸ@Ã¨Č˛:âxã Ŧ\ĩšŧŧ|ĶēU˲öN$$üÔļ‘ĨuĮĮĮsâD=üo~ä1ŧŊŊÉĖČ,ˇoO=ņ(s?ŸĪ˜qãYŊvŨ÷ôņyæÉĮyâÉg7ūvĢ8Ú""rąĢōL÷ķy ˜3knnn�<˙âKėØąĢģ•ÂÂB�Ž?”…Ãé8c[Q‘‘|öų|�'’’ =õĒĄÜē‰Įķō̝3õũwĘŠaT$“î˜@Ë-HMMÅh2qėØ1NRjj*99šú=ÃáĀh4’”t’ā  ˛zO×uäȝ´kז#ŋūJtÆøųÕ#00€iĪāÆú•k/4$„Ä„Ō>üúkiQQDG7äå˙ŧ�ƒ‡ .ˇo Įyášgq:ŒŊu7ôģžFĸ+=Vyyųxzzōö›¯“——ĪĀĄÃéŨĢįY¯ˆˆ\ÜÎēßoúđđ°˛Ā¸ņ†ū|8m:O<úof6——_}ƒÁ€ĶéÄd2áííÍs/ü‡ÚWh¯yķXZˇjɃ˙ø6›;'MÄËËŗŌžŸ|úY O?û�nčĪ÷ßĮK/ŋ†¯¯iié<ūČŋh×ļ-fŗ™{˙ū�Ą!!Uh+(0_z™cņ ôīwõęÕ+÷úCü˙žō:ūdge3噧�:xūã_Ŧ[ķušõ‡ Ė}˙÷ ģ÷ėÅÛÛ§ĶIlŗšĮ6ã˙ü'…E…4mŌ„Øf1åļÛĩ{7Ķ>žA@€?aa *=ģ?ÍŨŨŸĖâÏĻcļ˜šiİrĮ÷Ņ?|ÆmEDäâdp:Îē.ĸ6HJâ?ÂŦĶęēš„Mž<Y“cˆˆˆ¸Ę_ūLWDDäb 3]R芈ˆ¸ˆBWDDÄEē""".ĸĐq…Žˆˆˆ‹(tEDD\DĄ+""â" ]Q芈ˆ¸ˆBWDDÄEĒü>ŨŸwėbî‚%Ø ]QTÁĶÃʸŅÃiŨŧY]—"""įŠĘ3]îÅ%¯ĀÆ'sæ×u""rĒ ]îÅÅ�Øęē š�ēĻ+""â" ]Q芈ˆ¸ˆBˇŽÕķõáßÜCˇÎęēŠešĐ}ė÷Đ§į•„ÕŊ 6Bƒƒ¸ãÖ15YÖÕķõ!Ŧ~(‘á \ԟˆˆÔ*ŸĶ­JŸžWrEĮv¤Ĩg‚Œ#ŗįAfVvMÔwÁV­ÛX§ũ‹ˆˆüQĩCāģÍ?ŗūÛM�\qY{z]՝…KŋĸŸž`2šØĩg?[ļīäŸŋ“ŖĮ9wŖŅHۖÍ)*ļc/˛3gÁÚˇiI§ömÉ/(Ā Ė™ŋ˜›† ¸¸›­ÆŅQ|ļp )Šé ÔĢo//ļlÛÉOÛv”Õ4fø ~Ø˛Ö-bņķķÅl2Ķ"ļ)?ų­š7ŖËåČËË'ßfã‹e_ãíåÉ-Ŗ†“œšFIIIM–3 ĢJ—Ë;b0€ˇˇ�ŖŖ6°�ĮOœdĶO[kĩqŊ ŨßķöōÄVXHXũPĸŖ"yįŖO0 <|˙]lÛĩŗŲĖڍߓ’šÆ]ˇcųĘuO Ŧ~(ƒũúđÜ+oQRR˜ƒiŪŦ ‡ƒ„ãIlŪ˛ĢģwĄUķflÜô Į“øá§­xyzrī¤[˅îiKžZĀÍ#‡0ķŗ8†Üx=/ŧúöâbn5Œ†‘4mܐŨû°áûÍ´m՜ Ā€rítîԁą#‡�°yË6f}žč‚QĢÍčye×rËÂꇖ ‡§ĻĨ+tEDū‚j$tģ^Ņ‘˜&0 +;‡/žüšfMãīį[T6[!^ž�¤Ĩg�0ī‹/éĶķJöīÃÁC‡ÉČĖĸ°¨¨ėL3--@�23ŗ�(˛ÛņöōÄétčīĮČ!7RRâĀbąœąžŪWw'9%•{öcĩēãåéÁMC�āå剏ˇ~õęą˙Đ/§ęËŦĐÆæ-Û�ˆi]­ĀøæÛ8üëQ ôúņ˜ƒŲšg?kžų€Ŧ:š‘ÚQ#ĄģéĮ­eÃ˧ĨĻĨ“œš^P BCČÎÉ-ˇN ŋsæ/āo“ÆŗuįÜŨÜ1›L—”įĮļÖ â QąMāīĮĖš ĸUķ˜JkkŲ<†°úĄĖœģ( ˙ėÜ\fĪ_ŒÃá (0€ĖŦlꇗ|HpPĨmmŪ˛­,|ĢŖČnįHÜ1�Š‹‹ČÎÉ)[&""M5>ŧ|ډ“ÉO:É-Ŗ‡a1[HJNá˯ה['*2œžWu#77ôĖ,ŌŌ3XŧükÆß<’›[!9BģÖ-*m?$8ˆŅÑœ’JÍFĮvm*Ŧ7~Ėö8TvÆŊ|Õ:–~ĩšÛĮŨDaQ&Ŗ‘YŸÁæŸˇsۘD†7ĀVXˆÁP;Į叜ÎĶqM""Rw N§ķŦ˙Ũ˙íŸO¸Ē–K’ÅlĻOĪ+ŲŊī ĮĪyģ7_zĻĢ‘š6yōäÚ;Ķ•sc/.fųĒuu]†ˆˆ¸Ā_nr ‘‹•BWDDÄEē""".ĸĐq…Žˆˆˆ‹TēVĢÕuČ9rúˆˆüUēŖ† Pđ^D<=ŦÜ:zx]—!"" Ęįt/k׆Ë*™éIDDDΏŽéŠˆˆ¸ˆBWDDÄEē""".ĸĐq…Žˆˆˆ‹(tEDD\DĄ+""â" ]Q芈ˆ¸H•3R“––FAA+ęųSđôô$00“ÉtÎÛTēŠŠŠxxxāįį‡ĶéŦV"""ƒ‚‚ŌŌŌ 9įíĒ^ļŲlX­VވˆČ)N§ķ>§kē \‘ōœNįyįŖn¤q…Žˆˆˆ‹(tEDD\DĄ+""â"U>2$""r1ûü‹/ųiëŽjˇsyĮvŒ|C Ttf:Ķ‘?ĩŸļîĀIõž˛qâŦ‘āފBWDDūô ętûsĨĐq…Žˆˆˆ‹čF*ųKķõņ&ļiœN';÷¨¨ÎjQ芈Č_Ōčai…ˇ—>Ū^�ddfa+,äįm;ųzí—×TįĄ›m 1Û@ˆ—“FūšãYDDjÆˇ?l!5=ŖŌ×ļnßåâjJÕYčH5p÷—ž˙í˛rûúNŪî_DĮ0…¯ˆˆTOII ûõŠôĩu7š¸šRur#Ց Ŋf¸— \€íIúÎtgįI×Üē][RķĄ×ĮnįŧūËߛYyøĪyOÛŋ3ŗëŋ¯ŠFÎ;÷ũŠ ųgøÚŊââbŠ‹‹]\MŠ:9Ķ}d…ô°ÁîømšÅyv¸˙+ kĮ—ŋĐ=pļ>îN>fJ˙cņ[3ĶÛĢUËÁ4OŽŗo§|ܝLéU\n¨ûŅ5fŠJ üˇīŲûēk™…g{yy{L|ŧŨ„§|Ũŧ?ĀŽŲß5r÷åÅdŲāÉõޤ0Āᄇēsu´ƒ„,Ū`—�� �IDAT¸yĄ;ßÜVČĀŲnäÛÁ×Ŋôx]îāąĢŠq7Cv!<ąÎÂátNgéņ}ϧÎĩ3zđîuķÆŠJÍVųō‚Ę—ģ‚ËCˇØ_2Ōˇ‰ƒŠ‹č?˝=)ĸę9Y9ވį6˜™šĶDJ{•ßÖ×fī21ĻMIšåSž1s8ÀŊÄ:čáāŸĢ,ĖQÄŦ&>üŲÄúۊXgdųA#/õ- ŠÂb=ߍ郋hZJ›âŒšīÆ÷ˇb2–D1NRōāPZéßgî0q2ĪĀCŨŠŲ“lāšūŪŲÎW‡LË}ėäÛ üũ+ Ųš9xŽw1;OXxSîfņšģ“ 4 ,í×Ķã—Y¸*ÚÁëחîßÉ\č7˝7a1–ÍWŽŗĶŽž§ūķ™GÖXxå:;Ŧ°ĐĄÁomÍ4đÁĪ&:GüŽ Ų†~æFŸ&%dŲ {9yōšbē}čN§pŨ"´ uđÔz Ą^NRō ŧ~}7ÍsãĶĄEDԃeŒŦ3Q`‡qíJˆ ppûb7š:ËÕúĮßÍM­KĘõ3ĒuųßĨˆHM),,Âáp`4–IĖ?Cģ‚ËĮ4ŗ Ą¨ž=fd_Š‘¯ÆŌ?ÆÁĒ[ŠČ°Á—‡L�¤Tb~ė*;ūl">ëˇ×v'ø1ŅČôÁvf ąķĘ÷fø89™[zϏ!ÎHC?'é°îW#×ĮüvjŊķdiˆž\€Ž‘=œL+íãŖmf†ˇ,á–vÅLŨzæĪ(#œ4öwđR_;F¤xõ:;ķGą`oé>=ÛĢôlôx6dÚ 6ĐɆŖFŽlčĀé„īâLčđ[…zÃČÖ%gz6āū.Å,ÜWÚĮē8w]ū[ ũœ<×ûgŖN'™6˜Ō̘ˇn(ũ°^�…%đˇ+ŠÕē„§×[øw;oöˇ30ļ„w~43ēu ÷—ƒš{LŒkû[ģŗvšéSÂĢ×Ųéé8ãīύ¤|?""ĩÉf+Ŧ°Ŧ.Īt]ē~ÖŌ3Ö|; ëÆūT# n*"Ŗ�nøÔô0!˧âp¨ģ^ģŪÎ=_ZĘfŲü5Ã@Bļ;–X¸s™w'iųĨCŽ;’ dčÛÄÁ†8#[Žéå(×fąŖB78“Žeø>ŪČ]Ë,<´ŌÂĘ_ŒdT~‰ ‚(_&ci(š~w”×ĮypĨ.ÂÃĢ˜čÕ¨4|Ί4œN0Uq‰ģØŪnŋ/į9Œ$GÕsb8Õn°—“äŧŌĸũJ7ŽË4ĐøÔ{#?'q™Fļ.aņ~#šEĨĮĻCƒß:JĖ1а^éĪҧļ;Ķīæ÷ũˆˆÔĻÄIį´ĖU\>ŧl4Ö%|đŗ‰|; ųĖGŽ*æÕīͤŸ ´ą|Ũ+ßž]}'W6tđŪ–ŌŌû;ičdęĀŌkŠ{’ 4đëš–ŽĶ"ČÁÕŅž\gÆĪ nĻßÚjęä× ?7pŲŠ;ĻH0’e3Đ4ĀÉŋV[ø`@QŲõĐ/™žŨLT='ļS—nãŗKÄĶ×agÉ{|{ĖÄĖĄE˜OņŽ$˙íS€W7,áMÜ}ÅoÃË öšXpĶŲä~ų;3ƒ›—ns]“^ûÁĖCŨJĪBãŗ <ĩŪĖGƒĘ_kŽË2–Õš˜m ÔËYļ�üKĪö//ũŗI€“�ņ‚77—žũ˙^o'GO@J3žõwķû~DDjĶīOĮÍíw7v:ŲĢw/PuÔɍTĪô´ķŨ1#{R äŲKoT:-ÚĪÉĢם=dčZLߙnDÕsŌ*ÄIĢ`ˇ}aĄ°Ä@l ƒV!Åôˆr0vĄŸ /"Â×ÉĪĮÜ×Ĩü0Ģģ>^Äck-•”ž!zšÁėáEä–GŋÔįˇ3˛>M<ŊŪÂŖ ųđg7ž\gÆĀog–—5p0qą˙ë_ą~‡îųŌ ŒYPú¸ŠU1a>ŋqžŪĪÎãk- ˜mÂl,=ƒ}õz; ũœ$d•oīŸĢ,ÔŗB^tlāāņĢK÷íĨžv[káú™nXOÖg*šš+ÄĶÉ#ĢÍÄeÚĸō¯?u'×Yōt’i3đFŋŌ}ĶϘ‰KÜØ}wųᙛÛsë"7ļ'ņu/=&gúŨˆˆ¸R]Î@õG§ķėƒ‘qqqÔ¯_ŋÆ;βÁ” æî6‘’_:ė<¤y O÷´W¸JjVBLZęÆōąĪQDäBũã‰įkŦ­˙>ķČy­Ÿ””Dttô9­;yōä盪žūÛ×ÎûÚ)*)?ė+""r>œ8Ģõõ|ÕŨū\]32(p]+ĸ:Ë‘ŋŒË;ļĢ‘īĶŊŧcģĒčĖę|îe‘ę9øFžĄŽË8'Å™ŽˆˆČĨ@Ą+""â" ]Q芈ˆ¸ˆBWDDÄEē""".ĸĐq…Žˆˆˆ‹(tEDD\DĄ+""â" ]Q芈ˆ¸Č9}áÁŅŖGkģ‘?ww÷ķZ˙œB766ö‚Šų+‹‹‹;¯õ5ŧ,""â" ]Q芈ˆ¸ˆBWDDÄEē""".ĸĐq…Žˆˆˆ‹(tEDD\DĄ+""â" ]Q芈ˆ¸Č9ÍŊ|žrķ ˆ?žŒ­°¨6š˙Ķ;ĮæÍ?R`+ŦëRjŒ‡Õ›† ä˛vmęē‘‹V­„nüņd<=Ü ô¯WÍ˙é͚ũEv{]—QŖ l…Ė]°DĄ+"rĩ2ŧl+,ÂÃj­Ļ˙ūj{Ú_éĖ]D¤6蚮ˆˆˆ‹(tEDD\DĄ+""â" ]Q芈ˆ¸Č_"tß}˙à ËÖŽ˙†•ĢÖÔA5¯ömZrãuŊëē ‘KV­<§[•eËW°iĶf‚‚q:Ááp0aüXü.¨Ŋģ&OŦąÚū÷ÖģüũŪģjŦŊ ČĀëû`qŗ`Ā€­ĐÆâåĢHKĪ�ĀjugP˙ž$§¤˛nãĻŗļuĮ­c˜:cö_Ôŋ/ BC0™Lė=p°ĘöDDäÂÔIč\}Uúö)=ëúöûMŦXššū×õåÍwŪĮĪĪ7ö'99™~܂——'``äđ!ŧđŌĢ<÷ôã�Ėž;˜ĻMX°p1/>÷4‡~9ĖÂ/–NVf6M7ĸ°¨ˆiĪÄju'''—!oÄĮĮģ\? Ŗ"ØøŨ÷<xˆE‹—°eëĻ<õƒšķŌ(ē!ģ÷ėÁ`4áãíE|Bcn‰Ÿŋ_…ö###ĒulĖ&o͌ŲķI<‘@ãč(î¸e4˙ųßģ8Nē_҉ĩžãÚkŽÄ`0āt:éÜŠž>ŪŦZˇ‘Ą!ôīۋŨûĐ´qCŽŊĻŠi鄇Õgô°„†ŗvÃ÷‰;JÍÆ{Ķga0xō_˙W.t;węĀØ‘C�Øŧeŗ>_T­}š”]ÃËš9šxX=0šĖdddrĪ“hÉüE‹šsŌíL¸u8ÄÅ%Ŧ~(qqĮp8œėŨˇŸöíږĩŗô˯3z$cn‰o=_�6lüŽˆˆpnģe,ŖF ãķ‹*ôsZˇ.ņķķcČ 4ŒŒ`ßū8NöîÛG‡íĀ` qŖ† :˜+ģwgÍúõ•ļ_]áaõINI+ \€#qĮČÍĪ'48ŖŅHXƒPRRĶŲģ˙ íÛ´<c[?mŨAVvĢ× @QQs,aŪâ/éÜŠšyųŦ\ģ€ĻŖ9ōëŅr۟ZވHõÕŲ™î7ß~Īžq:øÕĢĮM#‡bˇ—€Ņh /?ww7,æŌƒƒ‚IIMĨ[ˇŽlūi ųų´ˆmVö:@zFÁ�ĶIJj ņņ‰$%•˜Á`�(ëįLŽšēk×mĀ`4ûģ~‚ƒ‚�đķķ%33  ŌöĢËhŦøyČ�8œ.k׆ā @nģyƒĢ•m;÷œSģ)Šé�•;v×ôčBpP ŸÎûĸÂ6›ˇlcķ–mļ#""RĻ{t+^>ÍnĪåtfyyzbŗbˇÛąX,œHJĸG“ŽÄ6‹aé˛åäååŌģWĪrÛûûų‘’–Fdx8'“’¨JHpAôíĶģŨNZZičT–§‡išÅÄ0įŗųŦ_ŋú—­s29™–-š“’š†ŋŋ?ÁAA•ļ_‰Į“ ô'*"Œc ĮhÔ0+)ŠéŒ:7Ū›FqI �ƒoŧކ‘áĨĮęTúŸš÷ÚétVųA`č€ëI<q’y_|YíÚEDäĖę,tĪÅČaCy÷ƒđôôĀĶ̓–-šĐ´i#ūĩÜĐ0Ā ũŽã“™ŗ Ŗ  §ĶIn]™ūÉ,>šū YŲŲ\}ewbbb*íĪh4bĩZ™ķų|FN—Žųņ§-„‡…•­“˜xœ™ŸÎáø‰$nģuž>>Ú¯_?´Zû]\RÂÔs|C_L& ņŅ'ŸĶ8šôŒĖ˛ĀØē}ŊŽę΂%ËéŪårnŧūZp:1Pē6[!Cnŧž_ĢĐWTDW\֞ÃŋŖmĢ�|:oųųÕÚŠČā<}jwqqqDGGŸWŖÛö"ŧ~puęē(Ŧøz5>žŪtīÚ€i3fŌŖ[š!´ĪÕŗ˙yĩ&Ęģ(ŊųŌ3u]‚ˆˆËœOFNž<ų⸑ęb4ũ“Y‹§kįÎu]ŠˆˆüE\ÔÃËuéļ[ÆVX6áÖquP‰ˆˆüUčLWDDÄEē""".ĸĐq…Žˆˆˆ‹ÔJčZŨÜ(°ŲjŖéŋ‹ÅR×%Ô ĢÕZ×%ˆˆ\ÔjåîåČđâ“IĪĖŠæ˙ôēwë›Âöú`bĩZ5l@]—!"rQĢ•Đõöô ELÃÚhú/ĄC̆ŨЧŽËĶ5]Q芈ˆ¸ˆBWDDÄEē""".ĸĐq…Žˆˆˆ‹(tEDD\DĄ+""â"ĩ29Fn~ņĮ“ąÕFķ"""5ĘÛ˃ˆúÁxXŨkĩŸZ ŨøãÉxz¸č_¯6š¯‰I)thS×eˆˆČE =3›„¤bĸ#jĩŸZ^ļáĄÉīEDäO"ĀĪ—Üŧ‚ZīG×tEDD\DĄ+""â" ]Q芈ˆ¸ˆBWDDÄEē.ļīĀ/ü°e[…åĪū÷—D˙""—˛ZyNˇ*˖¯ā›ßōĖ“âaõ�āŠgŸįŠĮŠ‹rÎčÁĮĻĐ$:Ēܲ;ƏÁbžđÃÖ"ļéÕĶ8�[a!Úļâęî]\ÖŋˆˆÔŒ: ]€Íc™ķŲ<&ŒŋĨÜō¸¸c,\ŧ„zžždegs×äÛŲąc7›ÚBTd‡~9Lģ6­É/(āāĄ_¸sŌíX­VĻ}<Ģ՝œœ\† ŧ‘ČČę?āėîfáî‰åëËĪ/āīNãŪ;n%5-Ĩ+Vsį„ąĖ]¸”ļ­[Đ2öˇ 7NœLféWĢņõņ&'7›†āĀĄÃ¤edr}īĢ™9w!Fƒŋzž8�ļlÛÉO[wāp:hÕ<–kztÁŨ͝Æāp8˜ōō\Ūą‹—¯ĸ¤¤_oŽëu5ŗį‡ÕJVv}z^ÉŽŊû kĘåڑ—ŸĪ[SgĐëĘngíįžũlŪ˛/OO<<Ŧ šņzļlßɖm;1›LÜvķHL&Sĩ­ˆČĨ¨ÎBˇMëVėÚŊ—­ÛwĐą}ģ˛å…6FFXƒĖütûöÄd2âëëːAXšj YY،>„EK–rđĐ/dff΀ūדœœÂĖ؟ņāũĢv…Ev>øxvŲĪõCƒدú]Ëĸe+HĪĖdėČĄ F Xaûe+V͝Ī5D†‡ņãĪÛY˙í&„†�p4>‘Âĸ"&ŽE^~>ž˙‡ÃÁōUëxėĄŌÚ7núŠB›Ev;N§ŗŲŒÉd$*"Œî;ąūÛM4ŒŒ ×UŨČČĖbÚŦĪšyÄ /_ÅåÚąmĮ.īđÛqŽŦ€EËVđČ÷`ąXødÎâŽ%`2š°ēģ3~ˈjS‘KY….N'cnÎK¯ü&•-ļ˜MŦøzîîîüw”˜ĻM1™Œøųų`ļ˜ŠWĪ÷Ôēėv;)Š)ÄĮ'’””€Á`¨‘ŨŨ,L?ĻÂōØĻųōë54ŽŽĸž¯ĪˇOKĪ$(0�€ Ā�öė?Tē™YŲúûāåé‰Õꎭ°ww7ŒÆŌKíWwī ”‡ĶÁđ7āfą�čīǝ bš4Ā߯™YYÔ !/?Ÿ‚îæļ›G˛˙ā/gėŋĀf#?ŋ€Ī. 7?œÜŧŌ~ü/čŠˆČoę.tĢÕʨ‘C™1s6Fcéåœšķ™|Įí„ņîûâÄAU÷{…‡Hß>ŊąÛí¤ĨĨ×jŨ[wėĻiãhŽŸ8ÉÉäTBC‚*]/0Ÿä”TFFp2%•ā €˛×ęųú–‘ @NN.6V+6[!ÅÅōFVŦ^O˙žŊÎū�§?_œ’ @jZ§ĩcģ6|ķŨxXŨņņöǞ/Æ „Éd"%5??_vī=@Í|Œš´Õič4‹‰aĮŽŨÄÅ mëÖ˚ũ„5hšĩču͕gmŖGˇŽL˙dM˙„ŦėlŽž˛;õë‡VģļÂ";īM›UnY¯ĢēąáûÍÜ;i<YYŲ˜ģ{'gÁ’å´mŲĸ܍Jú]ËŌ¯VáíåE~AŖ† dīūC�4Œ Įd41mÖįÔķõÁ×ĮƒÁĀĀ~×2õ“98Zˇh~Îĩvģĸ#ŗį/föüÅäææ1t@?�:uhËĶ/žÆ¸QCË­_Y˙�ŽīôYsqssÃáppķČ!tėDD¤"ƒĶétžm…¸¸8ĸŖŖĪĢŅm{^?¸:uÕ:}ːˆˆüŪļ=‡Î;Î'#'OžŦįtEDD\EĄ+""â" ]Q芈ˆ¸ˆBWDDÄEē""".R+ĄkusŖĀfĢĻEDDj\zf6ŪžĩŪO­LŽB|b2é™9ĩŅ|ŲļįP]— ""oO"ÔūüĩēŪž´ˆiXM‹ˆˆüi隮ˆˆˆ‹(tEDD\DĄ+""â" ]Q芈ˆ¸ˆBWDDÄEē""".ĸĐq…Žˆˆˆ‹(tEDD\DĄ+""â"ĩ2÷˛ŨQĖG>eeÂzŌ 3Îi› k�}ïaBė,ÆZ)KDD¤NÕʙî´ŗ™sxŅ9.@Ē-Ų‡2ũ✠ęķ_<ÎO[~.ˇlŅâ%ŧõÎ{į´ũßîŊûö]Pß"""įĸVBweâú ÚnBė>ÛŊˆ['ÜÁŽ;kļ(‘:V+㸊ļôķŪfbė͌‹Á´ŗ™1m*�K—-gŲō¯hŅ<–={ö2tč`ú]ח§ž}ŽœėŌ328↠ĀÜyķY´x GŽüʋĪ?[Žũ×ßx‹ŖGa/.ĻĪĩŊ4āF>™ų)ŋûžč†Q$%%UĮEDDÎĸÎ/ž0pOĢ Œh4 lŲ¤ģîåŊˇßĀd6áããÍũ÷ŨËūũx퍷čŨĢ'-[4gäđa¤gd0nüíeĄÛļMn;†Ĩ˖3sÖZˇn ĀūŲļ}3ĻMĨ¤¤„ƒ‡q]ß>|ôņ Ö­ZŅh¤˙Ā!u˛˙""répYčvŊ‚MÉ?áp:˖0p_ë‰ žĄÜēŧûVŲßÔ¯€ÕÊÍfÃ�$$$ōø“Ī`ļ˜ąŲleë6mŌ€đđ0VŽ^Sļ<>>ž'’xčáāííMB|õ|}1KGØ##"jv‡EDDūĀ%Ą{[ŗQŒo6Нâ×đŌΡp8•îÜ#‹ĢlkĶ›I<~œW^z‘ǏđÍ7Ë^;v,žn]쐐˜HƒúĄeËŖĸĸˆŽnČË˙y€ҏq#˛˛ŗ)))Ád2ņë¯q5ˇÃ"""•¨õĐíQŋ3㛍 _doœ8yyįģ<Đf27Fõ-[oÎáEŧˇo�ˇOē“!ƒa4UŧĪ+&Ļ)G~ãߏ>NŖFŅøøxŗtŲrũ“O?Ë/‡đâsΞeëV�b›ÅĐ<ļ˙÷ā?),*¤i“&<p˙}Ü2öfn›8‰ˆˆüųŨI¸ˆˆH38gš¸¸8ĸŖŖĪĢŅĢ— .ûģŅ`āmīĄäĩeËķNîÕ ėįĪ,áíŊĶĘ~ūæÆ/ÎĢ?‘ēp>9yōäڟ‘ĘátōŌŽˇY|tEŲ˛ßî˛c+ygīôÚ.CDD¤ÎšdH'N^Ûõ~šāXvl/ī|'בŋ>—ÍŊüĮā]ŋšWvŊŖĀ‘K†KŸĶuâäõŨīŗ7ã +ו{|HDD䯎VÎtƒŦg|Íát˛"aí7ČX%‰ˆˆÔšZ Ũë"z^đļ×Wc[‘‹Y­ /ßÖl4�_'Ŧ;įy˜ƒŦ\Ņŗė™^‘ŋšZ ]‹Ņˤæã˜Ô|\m4/""ō§ä˛ģ—EDD.u ]Q芈ˆ¸ˆBWDDÄEjåFĒÜüâ'c+,ĒæEDDj”ˇ—õƒņ°ē×j?ĩēņĮ“ņôp'Đŋ^m4ŅHLJ C̘:ŽDDDĒ#=3›„¤bĸ#jĩŸZ^ļáaĩÖFĶ"""5.ĀĪ—Üŧ‚ZīG×tEDD\DĄ+""â" ]Q芈ˆ¸ˆBWDDÄEēšÜŧ|^{įÃZícÕēė=p€÷ĻĪÂVXXĢũUæ§­;Xąæ—öYRRÂ[SgÔIß"riĒ•įt̞lų Ü,úöé]+íĪ_´˜ Ā@ŽšĒGŲ˛˙{č_ŧöō‹,[ž‚M›6@AN;СOīZŠkÕēlŪ˛�ŋ˛eíÛļĸÛ—UĢŨŸļîĀl6ĶĄm+fĪû‚ũûâíåyAmũr$ŽĢ{tĄ¸¸˜’’ŦîĩûpøÅâ×ŖņDGÕî3y""ŋW'Ą{&ķ,"3+ ģŨNķØfôēæjV¯]ĪîŨ{ æÄ‰$F ŒÉlbÁĸ%Ôķõ%;'‡[oM=ŋsŸˆãęĢz”k^^>?5ĨÖ>��tīr9=¯ėZnŲ‘¸cŦųæ;î¸u4ëŋũÂÂBޏŦ=3æĖįūģn/ˇnaQs-%#3‹Ą!ôéy%k6|‡—§'g ģ÷ ¸¸„ؘÆlŨą›ˆđ$LĄËåh͞9 –,'/¯€"{ÍcšŌŖëå<÷ō›üãžÉ`0�āfąpāĐašD7`ųĘĩ¤¤ĨSRâ mĢæ´Œáfrš–ÎŌĢšsÂX,YN~ŧŧ|:uhËåÛņŲ‚%X,f, Į“NŌŦIcrrsIIKgâ¸Q|:ī ŒF#Ū^ž$ObØĀūåö÷}węĐļÜë —Ž =#ƒüũûô$2<ŒŲķŋĀÃj%+;‡>=¯$(0€w?šI›–ą?™Lxƒú8Ž%$Ōûę4kԈũĄEl é™ĨĮš°¨B;ŖŖØđũföø… @N&§2°_LfKŋZ¯79šyÜ4t�ž>Ūe5VļĮŲēcá ęŸx‚Ν:ĐžMËJۏoP3o>š¨\4ĄŸ@bâqîŋīœN'=ņ ¯čÄĘÕkxqĘ3�üëą'Ā``áKtã DGGņŨĻXĩv-Ã˙ŸŊûŽoęž÷?ū:˛-[˛ŧåmc0˜Ŋ÷ Û ŗg!$„Ŧ6ím{oo{;nÚūnĮŊi“6‹$„0ö #ėa†Ųfŧ—$Û^Hŋ?Lܸ6+ļd ŸįãŅGĐņ9Ÿīį|Ĩú­sŽt<eōu0ų(i—¯`6›QŠ˟ëŦŨ⨉ͤ_ģQûxÜča´ŒmAڕĢė9LúõŧôÜ\EЏ�fŗ…éĮĄ( ŋũã_™8vÚNˇÎ8täS’IģœŽˇV˄1#°ZmŧŊø:ļkËחĶųņ+/ ķö&+;€_ūô�¤^ēB떱�¤]šJ×NČÉË'ãV¯ŧ0ģŨΟūö];w`Bâ6~ąƒÉÄÜS¸mˇNŋ^Ũ1[ŦüãÃĨôęŪ•›Š˜QôėڙÅK?#,4˜aƒûķÖûK°X­(@lt$ũz÷āĢ _sčh -ĸ"�î:ļģ›�ˇ˛r0ŧ0o6FS ˇ2ŗ9vō41ŅQ ÜŖŠ„%+Ö°čš§Š¨Ŧdė¨adåä˛jũV~úƒIŋvƒsžĻMĢ8nÜĖ$qäSĩĄÛPŋō<ûå×˙ū#�Ūøķ[ Ā;÷8r(Ņ‘œ8}ŽÉĮHJYûœ5´-c[āååÅØQÃČÎÍcûŽ}tîĐ`}!Ä“é‘ ŨÂĸ"BBƒP??ōōōŅj4¨T5ŋ…"ÂÃīŦ[LH¨€ŊžķRëÔR) 8wkČĀūŒ9œĖėl>]öÚˇsÆ.Õę×ģGŊ#]€áƒđ‹7ūÄķķfĄ(w˙M€JUsųũ›š¸ûē5§ąĩZ ‹EQ˜1yĢ7lÅf+gđ€>uŽĸŌ._ĨOĪŽ�ÜĘĖ&)q$ĶŽ`4•°|õ�ŧŧ<ąXŦ´mŨ’m_îĨel ü|}¸}û6ƒ‘ÕˇâĻrŖĒĒĒļŽŸ�îĩG€îîTUU×¯%ĨeĩÛÛĪˇĻžŅTB`@Íļū~øûą~Ëvâ[ĩŦ]f*)ŠéáÎ6î˙ėÁÝĒĒ*ŦV^^ž¸Ũ s€bƒą^ōō 4^šė ųJ�� �IDATÚį'üÎk´Ø`B€>(ÔKé >o˙ē~ž�¨=ÔTUUßĩžâÉôČ|*X¯'/¯��ģŨŅh",4‹ÕŠÃáĀnw“›[ģnn^>�šyų„„čëԊŒŒ ũęĩÚĮ—¯\%,,´Ū˜Ņ‘‘D„‡rôXŠŗvëž6īØÅėéŲs šN`=EÁņ­7ßüģ Č�@Ii:7•xĒ=xū™Y,|v›ˇīĒS&+'—¨ˆpĘĘĖčtŪ¨T*ôAëƒxfæž™9…™S&āįëÙ¯.Ōēe,yų…äqåę ĻfNžĀ ~Ŋëôs?EÅ�øß "āŽc×ū<0€üš{^Œ&ŽĻœ"(0‚Â" &´ŋ}ũün.Ĩ_ĨMëVu–5TĮËËĢ͆ÃáĀáp›_xgŨ€Úuķ ‹Ö>đ>~ÛŨę !žLÍv¤{đP2Šii�DFD0cÚbbĸYüŅ'TUU16qŪŪŪ <ˇßyŸĀ�üü|Q…ŠS&˛~Ã&|||°ZŦĖ{fNÚŊ{öāÆ›üé/oĸöTđÜŧ†O!Oš8?ūåMztīzמëhĘ).§˙ķM@TDqą-pØôęÖw776~ņ%#ŸÄōUëųáK î[3:"œûčOLt$ËW­§k§öTWWŗ~Ëvrrķ?j8îLNaīí#¸ššŅ¯W͸ūįÍwX0wūwæ4íĘUÚÆ×„PDX(áĄ|úų:ĒĒĢ FĢŅpčh ¯Ŋ8Ÿ’’R–¯ŪĀܙSČ/(âŗu› ŅĄņōâôš 4'šyŦŲôųų…Ė™>‰ëˇî:vDX(ËWo [įtlזˆ°P>^ž ‹ÕƸQȎ įŗu›ųlŨfĖf S&$Ūwü´+×9tPeũ{w¯WGĨR1 OO>üôsüũ}ņõŅĄ 0!q[wėFįíÕfcÖÔ$Š&>^ļŠũĨ÷ņVVNŊ>îV_ņdR÷9<ÉČČ 66öĄŠžMM'2ŦiN“]¸˜J›6ņxĒÕüūūÂ^Y„ß]Ž\íQû+C'Ī|EąŅĘáCšģ•{úlí&úôėF̏˜æn偤]žJë–1xxxđæ;˛pŪl|žõĄŠ†<Ė>~—úBˆĻw65ũĄŸ?LF.Z´čŅšĻ{7Å#{ëč||čĐ!ᑠ\ņũa,)áŊ%+ĐykIˆoÕäčėúBˆGĮ#¤û({Ԏt…B|wŽ8Ō}d>H%„B<é$t…B‘ĐB!\DBW!„p§„Ž—Z­ŧÜĨ…Bˆ&g0•ĸĶjœ>ŽSž2BfvSŲũW~œM­ @!„VCT¸ķŋuã”ĐÕi5´‹<n| „B¸Š\ĶB!\DBW!„p ]!„ÂE$t…B‘ĐB!\DBW!„p ]!„ÂE$t…B‘ĐB!\Ä)w¤2[mdæP^QéŒōB!D“Ōykˆ FãåéÔqœē™9h5žø9Ŗŧ¸ėŧB�ēuˆoæN„âņ`0•’•WH|l”SĮqĘéåōŠJ4^^Î(-„B4š@_Ė›ĶĮ‘kēB!„‹Hč !„."Ą+„B¸ˆ„ŽBá"ēÍ`ëödff5wN‘vų*ĮO­ŗlÍĻ/¸œ~íļ˙Ũ_ŪrF[BņHpĘW†îį‹í;9rô8AAĩËæĖšNDxø×xëīņúk/×>Ūē}'†bŗgNG­öhŌ~›Ú„ącšģ2neōÁ'ŸųĪ9 bÚÄqĒÛŽmk�L%ĨėŪ˜é“WO!ž$Íē�O Ĩ‘Ãë,ËĘÎfũÆ-øųúRZVÆŗOĪ&íōNœ:Ett73n1`@?ĘËËšr%m;v2.q ••”••Ōŋ_ŽOačā|¸d)ŖGŽ Et?˙åo˜9cŨētây“ŧōģöîĨ ŋÛöÛtëڙ~}ú°rÕĖf3••UthŸĀ°ĄCXģ~#Ļ’ĒĒĒHhۆaC‡°tŲJ<ÔxĒ=ČĘĘ!Ąm[JËJ)(,äĩ—‘›—Įęĩđ÷Įjŗ1oîlŧĩÚÚũ\ōéröKÚõņđpGĢÕ˛āŲyœ>s†ã'Náí­ž›÷4)'N՛ƒ^=ē7ú9ˆ‹‰æÅųsę,ŗZmŧûņr^[ø,EÅļîÜÃK æō˙ūīü뇋PĢÕĩë:šBÚåĢčƒČ/(")q$šųMs+3›ķŠi�œ9ŸĘŠŗįÉ+(âŲŲSšzã&eefF>5ˆœŧ|ļīŪĪ ĪĖ`ķö]”–™ąX­ŧ0o6înnŪW!„x<R§—7lÚÂÄņã˜?īiztīĘî}ûpsSĄŅh˜œ4Š“'rėx ũûöÁßߟq‰5GŒGŽŖ{׎ġnEÚĨË�tîØK—.SPPHlL Ō.ĨQ^^ĸ(Ž]ģÁĸ… xiáķlÛą‹ĘĒ*.\HeöŒéüā•E´Š‹#33‹ėė.˜ĪË/žĀŪŊ°•ÛpsSŅ2.–iS&Ŗ¨TDD„1cÚĖf3ef3ë6lfüØŅ<ûĖÚˇkˁƒ‡Üßõ73sÚd~øęËthŸ€ŲlfŨÆÍŧôâķ,xöpØIũ:­Á9h ™Y,^úYí˙N=VĢaBâ6~ą“Í;v1gú$Eá—?ũAĀĩÛíė?|”įĪaʄD ‹  üŗvīî]ˆoGįí�ˆŽįé“éŪĨ#žž|מėv;Ŋ{t噙Sđ÷õåëKéM˛¯Bņ(hļ#Ũũsūbjíã_X@aQ1!Ąz�BôzÎ_H%ĻE �PĢÕTVVÕŠcˇ;8|ø(!!Áė?pˆü‚.Ļ~MĮøxé2Ü=Üé߯ûæJz:íÛˇŖ ¨ŖÁȇK– ŅxaĩX˜7w6ËW~ŽÅjeİĄ5}„ ( ~ū~˜L%�øûû×ôäၟ_͝ˇÜŨ=¨ĒǤ°¨={°˙āa*+*‰ˆhø´y‘Á@>€~}ú`ąZņôTãá^ķ´ëƒ),*Bįí}Ī9øŽbŖŖęé´mŨ’m_îĨel ü|}Üļŧŧ—EŠIÚđ;ķt7ÁúšK žžj,ë=ÖT Žy øSZZö�{"„‡Gęôr°^On^>­ââČÍË'$DßāļŠĸāp8�8wū<=ztcüë¤fŗ…—.ãõ×ÚS]]ÍåËé ˜ß—SgΒrōŖGŽ@Q)„„†°pÁ|�2ŗŗņōōÂS­æÕ—_¤ŧŧœßūîxõĨ…8” ԄģŅh"āNØŪKHp0‰cFĶ"š’’RUÃ'B‚õäææĶ2.–ũŅšSGĘË+¨ĒĒÂÃÃÜŧ<ļę‡Åby)m2gžēH떱ääæ“_PDh΃——'V›­öyČÍ/Ŧģ¡žŖ†¨=ÜŠĒĒyķ`ŧķFφƒbƒ‘`}ÅFᡍŪ!„xT4[č6dꔉŦß° Ŧ+ķž™ÃĨKõOEĒT*ŧŧŧø|Í:222xaÁsĩ?ĶéŧqØíääæŌēUKŌ._ÁĶĶ“„ļmذiKmĐFEEđÁ‡K¨ĒŽ"<<ŒIÆŗ{ī~vîڃĘ͍ÁƒELL4‹?ú„ĒĒ*Æ&ŽÂëno9yRëÖoBĢÕPVfföĖiøúčę­7eRkÖmÄŨŨo­–!ƒ1cęŪ[ü1Z­­VCûv œ<uúģOę=dÜĘäũ%+jĢT 3&OāĐŅ^{q>%%Ĩ,_Ŋ×^œĪ_ŪzŸŸŧļ°öŗJĨb@Ÿž|øéįøûûâëŖCųÖųå}WŽ^įÄés Žß2Žäã§ØēsOÍVwÚŨ͝Ķ_]Ä`0bąÚhßVî-„xr(Ž{Ž�ÄÆÆ>TŅŗŠéD†ŨûtŖpWũÁƒ´ËWiŨ2Ū|įCˍOo.„âqp65ũĄo>LF.Z´čŅ:ŌcI ī-YÎ[KB|+ \!„¸ ]ņõīŨƒūŊ{4wBņØx¤ž2$„B<É$t…B‘ĐB!\DBW!„p§„Ž—Z­ŧÜĨ…Bˆ&g0•ĸĶjœ>ŽS>ŊBfv“ܯ9M•û !ăĐi5D…;˙ūN ]VCģøg”B![rMW!„p ]!„ÂE$t…B‘ĐB!\DBW!„p ]!„ÂE$t…B‘ĐB!\DBW!„p§Ü‘Ęlĩ‘™S@yEĨ3Ę !„MJį­!*,—§SĮqJčfæ ÕxāįŒō.“W@ˇņÍ܉Bg2˜JÉĘ+$>6ĘŠã8åôryE%//g”B!š\ ŋ/f‹ÍéãČ5]!„ÂE$t…B‘ĐB!\DBW!„p ]!„ÂE™Đũų/ĀÖí;ÉĖĖjŌÚī}đQŊeûd×îŊM:NS1•”ō÷ÅK1[,ŦÛŧ­šÛBŅDœō=ŨƘ0vĖCoŗäĶåL›2_]ƒ?yŅ mëŽvī?LĘŠŗø×.ëÚšũ{÷htmˇ7Ķ&Ž{¨mL%ĨėŪ˜é“ÆąīĐQĖ I‰#XšvmZĮŅĢ[—ĒUYYɗûq ų8yã¨T*lååŦ\ŗ�Ģ­œ1ÇĐĻuKöH&íĘUŧ<=QŠæÍž†‡{ÍËëøŠŗėØŊŸ˙ūĪ{¨}Bˆ'Mŗ„nffkÖoÄÃíV˂gįÕūlɧËØŋ/>>>Ŧ^ģ�Ŧ6ķæÎĻ瞊ŋūũ:uę@ii/ ŊztãÜW¨ĒĒbá‚įPŠ”zãũü—ŋáøoŌ¯^cÃĻ-DGGRb*ĨU˸&؟}{ņÔ ~u–]ΏÅۃGXøėl$§ĸĸ‚Ū=ēōéįëøŅËĪ×YwÃ֝ŒFŦļrƎ| }P P ËWoā/Îį|ę%RNÁ[ĢEŖņbōø1ŦZŋwŧ<=šžq‹YS“Øš÷ ˇ2ŗ9ŸšÆ°ÁũųôķuœOMŖŧŧOĩš^Ũē°yû.ÂCCčŨŖ+6[9ođ O ęGڕĢ�¤Ļ]áåįŸÁÃŨ];qîBjm¯9šųôėÖ…ŽÚ“•ËöŨûhĶē%!ÁzF Ā[ī/Á`4Ŧ§°Č@ÚåĢxkĩM2×Bņ8k–Đ]ŋq33§M&**Šc))˜ÍæzëŦÛ°™ņcGĶēU+NæĀÁC 8ŗÅ”‰I(ŠÂĪūķŋ˜5c*Ą!ÁĖž9ŖÁĀũļ­Ûv0gö ĸ##YšjM“íĪą§IŋvŖöņ¸ŅÃhۂ´+WŲs ™ôë7xéšš(ŠR/poeå`0yaŪlŒĻnef׆îˇmüb'ŋøˇWņđđ`Ųįëɸ•…ĘMEdD8}{vã@ōqR/]Ąw÷.xzĒéÜĄ�sĻM䝏–áææÆ+Ī?Ā€>=YšvŊ{tåėų‹ôęŪ…Ū=ēŌģGWŽ<ƒNG\LtƒûÚ*.ĻößįSĶčØ>€Î8~ę,ûĄ}B<ĄÁznßžÍú­ÛyzúdŪûxyã&Y!ž�ÍēEAú �úõéĶā:…E…ėŲ{€ũSYQIDD8�ūū(JM¸~ķße0 ĒW¯×ƒÃņ]wĄŽ~Ŋ{Ô;Ō>x�ŋxãO<?oÖ]{5šJ � Āߏ�?L%ĨuÖą•—cĩÚXĩa+�fĢ…2ŗĨf?_�Ôj,kŊúĢ •ĸP]]ŲbÅĪ×}P n*…ÅœŊĘŗŗ§5GįR/ņÂŧY÷ÜßĘĒ*6~ą“ˆ°Đ:§ŅûöėFŸ]ųhŲ*Ž\ŊÎĨôk ęם÷=ë !Ä÷Eŗ„nH°žÜÜ|ZÆÅ˛˙ā!:węØĀ:Á$ŽEL‹hJJJQT÷øĖ—‡ũžãøûSX\Ltd$ųyy„…†6b/îoķŽ]Ėž>‘=’iĶ*zëč8zâ�Ŗ‰KWŽŌ>ĄMu4^^øøx3gÚDÜÜÜ(,2āīīKڕôúƒ* Ž;o&ĒĒĢYļj=3ĻL ˛˛ŠeĢÖķę ķPŠT čۓ/÷DĢŅ ķÖb4•°uįÍÕ=æēĒNJĨ+×0fÄS´ˆŠ�jBøũ%+xmáŗ¨T*ü|}(3[Hŋ–AIigžēHąŅȎŨûIųÔwN!„xė5KčN™”ÄšuqwwĮ[ĢeČ Aõ֙<)‰uë7ĄÕj(+33{æ4ŧŊū T\l,~ŧ”W_ZˆFŖšë¸ãGŗlųgDDD`ŗYkÊąŽĻœârúĩÚĮQaÄÅļĀawĐĢ[ÜŨÜØøÅ—Œ|jËW­į‡/-¨]72"Œˆ°P>^ž ‹ÕƸQÃc˜‘,YąĩZŨnįé“\/DĕĢ×9qú—¯^g`ߞ„…ĐšC;6~ą“ŠIcéÜĄk6ncŪŦŠ�ŦŪ¸X{įĶŌ=ēvÂjĩqæĢ‹˜JĘøhŲį$´iMUU5ų…EėÜs��?_fN™@÷.yįŖex¸ģŖŅxŅĩS{ztíTÛןßz_WņŊ§8î“<ÄÆÆ>TŅŗŠéD†7ϝG“üW†*++y÷ãåŧūŌ‚‡>M/„OĸŗŠéũûūa2rŅĸEŪW†cũĻÍX˙åēf‹¨(† Š$ũ}v9ũģ$3~ôp \!„pĄ'*t§NšØÜ-<ÚÆˇĸm|ĢænC!žw™;R !„O: ]!„ÂE$t…BqJčzŠÕØĘ˝QZ!„hrS):íŨŋrÚTœōAĒčČ2ŗ 0˜ĘœQŪåÎĻ6p !„O VCT¸ķŋęę”ĐÕi5´‹š˙ŠB!Ä÷ˆ\ĶB!\DBW!„p ]!„ÂE$t…B‘ĐB!\DBW!„p ]!„ÂE$t…B‘ĐB!\Ä)w¤2[mdæP^QéŒōB!D“Ōykˆ FãåéÔqœē™9h5žø9Ŗ|“ĘÎ+¤[‡øænC!D32˜JÉĘ+$>6ĘŠã8åôryE%//g”B!š\ ŋ/f‹ÍéãČ5]!„ÂE$t…B‘ĐB!\DBW!„p ]!„ÂE$t1ĮOžáoī}ŒŠ¤´QuL%Ĩü}ņŌ{ŽķÉĘ5dåä6j!„Î)ßĶŊ“ÉIJ•ŸãîîŽŨn§ēǚųķžÆßßŋÉÆ8z<wwzõėŅčZ?ų¯ßĶ*ļEe įĪÁÃŊé§īRú5ĻLHÄßĪ€ĒĒ*ūüÖû :ž=ģqúÜŽ8Z큗—sgLFĨRąc÷~2ŗs°XmŒ5ŒĐ`}Ŗú¸r퓏áííŲlfōø1x{kYąz*77*++™>q<ú �>ødeívšųL?†.Ú7j|!„x5KčϜ8EÛøxFĀĨËé˜LĨøûûŗqËV ō šmŋMˇŽé×§ĮRR8vü$vģÎ:Ōģgwūūîøûû“4~,ÅÉGŽĸĶéĐj4L7–ģvãí­#"2‚ČđđFõëŠö╿Õ[ūÁ'+3b(a!Áü}ņR^Z0—/vîĄsĮv´oûĪnØĘËYžj:o-Ŗ‰ÉãĮÆö]û(,6pûļÎАq+‹/÷dÆäņøųú°qۗDGFÔÖĘÎÍãĨsqwwgņŌ•ääæS}û6…Åŧ8˙iĖ Å�fŗ…UëˇPRZFdx(ãĮŒāā‘.§_ET{4]^^ÁŠĩQ€ŠŠJæÎœÂéŗįÜŋmã[ąīĐQžž|•ĘĘJÚ´fp˙>deį˛m×^æĪ™ÎK æPRZÆĒõ[čÔ!ĄQķ-„OĒf Ũn]ģ°tųJŒ&-ãâHHh‹¯ŽŦŦ,Ž]ģÁOüCėv;ŋ~ãtī֍Í[ļķ‡7~ĸ(ė;p•›;FŖ‰_ūüßQŠŪy1ŋûí¯đTĢųāŖ%äæįĶšS'bĸŖ¸�•U,^úYíã°Đ`’G2gÚD–~žŽĐ`=ãFCį­eÖÔ¤zÛ[,VõīMģ6­9{>•ã§ÎŌ¯ww2neņĘ ķ°Ûíüéoīņŗ×_"&:’‘O ÂĪ×‡ķŠ—đŅé Ž­•”8¨ 8Ģ­œĐ=ÉĮOĸŅząvĶ6ĘĖfF€ŲbaÆäņ¨T*ūđ˙āŠAũŲø(ŋųĄ( üëģ�9qŠø–ą Зk7nRRZʰÁøđĶĪ Ą°ØĀë‹pâĖ9Ŧļr�8ČÎͯŗŸļî`âØ‘¨TrÕB!Ō,ĄĖŋ˙äGsũúu´„ũúâéå‰Ņ`äÃ%KĐhŧ(.*ÆĶË777�F Ji™™ĀĀ@T*Ģ͊Åba؊šP4›-”••5iŋžj^œ?§Ūrą-ĸ¸œ~“Įßu{ĩZÍų‹i¤Ļ]ÆTRŠÎۛĸb#FS ËWo�ĀËË‹ÅZģMIiĮNœfáŗŗ9|ėdzWŽ^įĐҿΙއ‡ÕÕˇqwscʄDŠ&>Yą†æÍ"XT€ū~>Đj4(Š@P`Íé|Ŗą„ļņ­�hĀGËWņĖŦŠÄDGrâô9öLfôđ!lŪž‹eĢÖŒÚÃŖļ§ĖėÜTn„…†<ôü !Ä÷Eŗ„îîŊûIHhCtd$Áú 4 ĮRN06q4!Ą!,\0€ĖėlÂÃðŲlTUUáææÆæ/ļ1üЧ¸“h5Zü|ũxnŪ3¸ģģ‘Ÿ_@@@�×oÜĀîp8u?ŠŠ deįŌŽMkRNĨOĪn Žw ų1Ņ‘ôí՝äc'ÉĖÎA@°>ˆgfN '/?_ŸÚmÎ_LCQVŽŨD~Ann*ĸ#Â)(*"ũz æÎŦ ÔȈ0žž”€ÆË“ÛöÛ�Œ8EÁTRFˆ>Ģ͆ŨnGĨRQPX €>(€ü‚B:ĩoËåĢ×QŗŲ‚Î[[SSŖÁląbļXéŨŖ+-ĸ"šđõ%Jŋõææä™¯čŪĩcΰB<Yš%tÚ°fíÜŨŨqsSQ]U͌éSˆ'**‚>\BUuááaDOšČ´É“øûģ`ˇÛéÚĨsŊzS''ņî‹ņT{bˇÛY0˙bĸ[°uûôú ZÅÅ5ĒߊĘ*Ū_˛ĸβ¤ÄŦßē“YS’ đãī‹—Ō*.–Ŋ‡’éÜžíÚļŽ]ˇu\,;÷āfV6ĄÁzŽŪČ`ČĀžD„‡ōéįë¨ĒŽ&,$˜ˆ°ĐÚmõīÍ ūŊ8x$OO5áĄüíũiËGËV0lpÚĩiÍÅ´Ë,YąĢ­œIcGáp8đķõaĶļ/)6éŪšZ­†!úđîĮË ĀÛ[ čÛĢ;Ÿ­ŨĒk(¯(gîŒ)L3‚ĩ›ˇáĢĶaļX™2a žj5Ÿí܃§ZÃŗ§ũķTzNn>ûönÔ< !ēNq8î}8˜‘‘AllėC=›šNdXđũW|Č_B5Ųõ°yđ0šhŅ"ųžŽBá*ēB!„‹Hč !„."Ą+„B¸ˆ„ŽBá"ēB!„‹8%tŊÔjlååÎ(-„B49ƒŠVãôqœrsŒčČ2ŗ 0˜šövŒÎr65Ŋš[BҌtZ QáÎŋŋ„SBW§ÕĐ.>ÆĨ…BˆĮ–\ĶB!\DBW!„p ]!„ÂE$t…B‘ĐB!\DBW!„p ]!„ÂE$t…BqĘÍ1ĖV™9”WT:ŖŧBҤtŪĸ‚Ņxy:u§„nfNZ'A~Î(ßd˛ķ čÖ!ž™;BҜ ĻR˛ō ‰rę8N9Ŋ\^Q‰ÆËËĨ…Bˆ&čī‹Ųbsú8rMW!„p ]!„ÂE$t…B‘ĐB!\DBW!„p Ũ'@uu5˙÷Åė?|ŒOVŽāŗĩ›¸vãf3w&„âۜō=Ũû1™L,[ų9îîîØívĒĢĒ™?īiüũũ›Ŗ{ÚŊ˙0)§ÎđĪŪēvî@˙Ū=šąĢē ÆŧŊĩ<5¨Đ¯šÛBqÍē)'NŅ6>žŅŖF�pér:&S)ūūū|žfEEÅX-&&M 6&šO>]FŖÁd21.q4AüõíwčÜąĢ __S'M$7/Õk7āīÕfcŪÜŲxkĩîw@ß^wퟮgÜbīÁ#,|v6’SQQAī]ųôķuüčåįëŦģbÍFūœ;ŸĘ/úƒĮ9qú§Ī] *2œŧüBúöęF§ö üé­÷ˆŽĸe\ ÜŨÜ9}î<Z…ŲS“8|ŒœÜ|’dōQ~õŗ×kkæąyۗøųúbĩ•3kĘ4ųĩB4‡f Ũn]ģ°tųJŒ&-ãâHHh‹¯Ž7)**æ¯,ĸØ`āÆ›L>J\l,cF Ø`āŨ>â՗^Āfĩ2}ędEá×oüžŅ#F°nÃfƏMëV­8x8™1.qLŖû=vâ4é×nÔ>7z-c[vå*{$“~ũ/=7EQęîÍĖ,***xū™YM%L>~×qEÁ[Ģe˜X­6Ū^ü Ú'P]}›§õ'$8ˆ7ūü7~ņ“āîæÆĘĩ›¸”~ĀũU*�� �IDAT!úb*)e`ŋ^ėO>Z§æÖģųÔ`âbĸ9vâ4ÉĮO2ōŠAž!„¯YB7$$˜˙ɏ(,*æúõë,ūh úõE­ö@¯ (0 Ā@VŽZCģ„ļĩˌSÍŋƒ‚P�__JĘJ),*dĪŪė?x˜ĘŠJ""›¤ß~Ŋ{Ô;Ō>x�ŋxãO<?oVm/˙Ęd*­=5āī‡×}îëXŗŽVĢÁbąÖYnŗ•ŖVĢqws@@ąÁHā=NËŒ:’Bōņ“TVVrīBá4Íēģ÷î'!Ą Ņ‘‘ëƒĐh4K9AâčQ<|€ĸâbR/~M°^On^Ѕ‚‚B‚ô�aˇ;PŠŒ#ūž~„“8f1-ĸ)))EQ9÷sb›wėböô‰ė9L›VqxxxÔ[G§ķϤ´ �ŖŠ„ōōŠ{Ö,(2�PRZ†Nį]ģ\Q4/***ŠŽŽÆŨŨüÂ"úÄtģg=}P Ç *"œ˛2ŗĶįD!ÄŨ5Kč&$´aÍÚ ¸ģģãæĻĸēǚͧNTdīŧˇŗŲˤ‰IÄ´ˆæ“e+Xōé ĘĘʘ=c:�ž~žŦYˇž‚Â"zöꎷˇ–É“’Xˇ~Z­†˛23ŗgNÃ×G×č~ĻœârúĩÚĮQaÄÅļĀawĐĢ[ÜŨÜØøÅ—Œ|jËW­į‡/-¨]ˇel 9Ί5ņŅyãí}īkĖÕÕÕŦ߲œÜ|Ə^īᓯŽâ“•kŅhŧĐxyŅļuKō ŠîZoüčálŲžÆ ŗŲ”¤Dt÷éA!„s(‡Ãq¯222ˆ}¨ĸgS͉ nL_÷d0øpÉ2ūã'?jTWü•ĄęÛˇšœ~ m¨¨¨äÍw>ä?˙íÕ×=yæ+Š&Æ â´~„B4ėljúCįÁÃdäĸE‹šįH÷ûÄŨ͍ķĶ8rüvģņc†sãf&'Ī~UoŨPŊž:Bá*eč6ú(וfO›XoY\Lt3t"„ĸ9ɧj„B‘ĐB!\DBW!„p ]!„ÂEœē^j5ļōrg”B!šœÁTŠNĢqú8Nųôrtd™ŲLeÎ(ßäÎĻĻ7w B!š‘NĢ!*Üy÷—ø†SBW§ÕĐ.>ÆĨ…BˆĮ–\ĶB!\DBW!„p ]!„ÂE$t…B‘ĐB!\DBW!„p ]!„ÂE$t…BqĘÍ1ĖV™9”WT:ŖüízF))'°•W4w+÷¤ņōdæÔ$ztéÔÜ­!ÄcÃ)Ą›™S€VãIP€Ÿ3Ę?ŅV|ļŠĘĒĒænãžlåŦ^ŋEBW!‚SN/—WTĸņōrFé'Ūã¸ßxԏƅâQ#×t…B‘ĐB!\DBW!„p ]!„ÂE$t…B‘ĐũžØˇCökî6„â{Í)ßĶŊŸ/ļīäØąôÁA�ØlåôėŪQ#‡7G;ėčņ<ÜŨéÕŗGsˇ"„â1Ô,Ą 0dđĀڐĩXŦüęˇŋgÔČáäæåązíüũąÚlĖ›;›ĸÂ"6nۊÎG‡šĖĖ‚įžeŨú (*7|tŪdfe1gæ BCCXģ~#Ļ’ĒĒĒHhۆaC‡đë7~O¯ž=)ˇÚ¸••Åë¯ŊBvvvŊš‹šŪØŪZmm;wíÆÛ[ĮųÔTÚ´a@ŋžXŦVūüŋ凯ŊĖ_ß~‡Î;`ąÚđõÕ1uŌDΜûŠä#GŅéth5f͘Ö蹋 eâ¸QTWWcĩ•ķŲÚMtéØŽž];cĩŲp�Ÿ¯ÛL\L4ãG';7?_7nf0väSčõ¸ŠÜ¸z‰SįÎ7ē'!„÷×lĄ{0ų(i—¯`6›QŠ˟ Āē ›?v4­[ĩâāád<@|›ÖŒ=Šĸb*EEĄe\ C äÔéŗė=p€Aũû“Ã~ø*‡ƒ˙úõôëۛÛÕvúöîE°>ˆˇßyŸœÜ\.~ũuŊš =.q �ŪŪZ:węDLt1-Z°äĶe čחS'O͝OlV+ͧNFQ~ũÆī=bĢÖŦãwŋũžj5|´„Ģ×oĐēe\ŖænBâ6oÛEN^>=ģvÆ[Ģ!)q$øŋpûömæLŸDB›V ؏u›ˇ“›_ĀÔ¤D &°c[DķîĮËP…˙øŅ˜ŊĘíÛˇčĶŗsgL åÔYVŦŲØ¨^…BüSķéė΍‘ÃÉĖÎæĶeŸŅĄ};� ‹ Ųŗ÷�ûϞĸ’ˆˆpƌÁļí_ōį7ß"XÄŦSÖëđ÷÷Åd*Ą°¨ˆĐ`�EÁĪߓЀĀ��<ÕjĒ*+öԐz5ģ!!!Á¨TnäååsōĖY^|aÕՕĄ( �ž>žä`ąXXļâ3�Ėf eeežģ�?ŠF�N;ÆË‹ŠĘĘÚā,.6€ŋŸ/ÅFSÍ2ƒP Āߡ6XËË+đÖj(-35A ß2VW!šXŗ…î7ĸ##‰åčąôīKHp0‰cFĶ"š’’R•ŠÜÜ|Æ&ŽBĢҞyË6Nœ8 @~AíÛ%PXTL@@�Áz=%`ˇ;0Møû78nC5ûÛė�Cbëļxkĩøúč0 aˇ;PŠŒ#á!ĄøųúņÜŧgpww#?ŋ€€€€FĪYąÁHhp0ˇ˛˛Ч'Š—ŽāŠöÄŨ͍ęÛˇ ÖsãÖ9JJË đ'7ŋ€`=…Å((2ÔjxhHmā~#åÔŲÚđBŅtš=t&MœĀ˙ō&=ēweō¤$֭߄VĢĄŦĖĖė™Ķ°X,üũŨÅøcĩژ;g&×32ČÎÎaųĘĪÉÉÍãšgŸ!$XOLL4‹?ú„ĒĒ*Æ&ŽÂë.÷€n¨f|›ÖõÆöõŅÕnŨ‚­Ûw ×ŅŊ[W–žŠ˝ũš¯Ÿ/kÖ­§ °ˆžŊēãí­eęä$Ūũ`1žjOėv; æ?ĶčųÚēs/“ĮĻĒē›ÍÆŅ§ŲŧũKæ?=[y9ļō Ž\ŊŽŨngÖÔ$rķ ĐxyĄ(Å俐“—ĪŧŲSņp÷ ¯ m_îmtOB!îOq8îēŨEFFąąąUôlj:‘aÁé뾖|省ũûŌ&>ŪŠãÜMyyožõw~ūŗŸ R)Œ>\˛Œ˙øÉU÷wzŗ‰:tŋ˙ųænA!šÍÃdäĸE‹#ŨĮMę×ilßš‹)“’PŠ”ænG!Äcâą ŨĪ6ū4íwÕĄ}ģÚ~}#0 °ŅGšB!žlrG*!„ÂE$t…B‘ĐB!\DBW!„p§„Ž—Z­ŧÜĨŸxÍŨÂģÛw …B4Ė)Ÿ^ŽŽ !3ģ�ƒŠņˇ<üžĐŋ/ĮSNRūˆŋiņōōbÖÔ ÍŨ†B<Vœē:­†vņ1Î(ũÄëÖ!žŠãF6wB!œ@Žé !„."Ą+„B¸ˆ„ŽBá"ēB!„‹Hč !„."Ą+„B¸ˆ„ŽBá"ēB!„‹Hč !„.â”;R™­62s (¯¨tFy!„ĸIéŧ5D…Ŗņōtę8N Ũ˜´O‚üœQū{!;¯nâ›ģ !„ø^0˜JÉĘ+$>6ĘŠã8åôryE%ų 4B!ūž˜-6§#×t…B‘ĐB!\DBW!„p ]!„ÂEžˆĐŊp1•ÃGŽÖ[ūķ_ūĻēyōœ<ķ;÷lî6„âąį”¯ ŨĪē›Ņ1tđĀÚe?ūéĪųë˙ūņ;ÕëÔąCSĩÖ¤ FÛwėbîœYNŠ˙“˙ú=­b[ÔYļpū<ܛåiBqÜoį#GsæÜW¨=<P{ĒynŪ\ΜûŠä#GŅéth5f͘Æ'ËVrûv5~žžDFFPTTLŌøą|øņRT*ūØö:ĩ‹ ūúö;tîØ‹Õ†¯¯ŽŠ“&ō›ßũ?ZÆÆŌ&ž5ŅŅ‘Ŧ߸?__JËĘxöéŲ\LK#åÄ)ZDG‘“›Į ũčÖĩË}û2•”qã&6mÁd2ą`ū<�~÷?æ/ŋˆŋŋŖæĘSíÁ+/ĖĢŗĖjĩņÖ{KxmáŗØēs/-˜Ëę [éÜąíÛūķģŋ'NŸã|j¨=<˜=m"ŋûË[üęg¯đŅōUŒų�×oÜdõ†­äæ0|Č�:ĩOhTīBņ}ôȅîņ'™˜4žÖ-ãČĖĖÂnˇŗjÍ:~÷Û_áŠVķÁGK¸zũîn*âbZ2tČ ŽOāú **+xíåE”™ÍėŨ NmE›ÕĘôŠ“Q…_ŋņ{FAuU5ŖG',,”ˇßy‰ãĮۂ#ĮŽŗ{ß>"""đöÖ2mĘ$Ėf ~ķ¯tëÚåž}Ĩ~†—§'“’&đĢßū[šS)>:]Ŗ ĸ˛ŠÅK?Ģ}LRâH&$Ž`ã;1˜LĖ1EQ˜55ŠŪö§ÎgėČaÄļˆ";7ģŨ^ooxzĒ™9eĨefŪûxš„ŽB|Íē*E‡ŖÁŸÍ=“m;wą~ũFÚˇkG>‹Å²5áb6[(++@¯Ēŗ­ŅhDTŗĖG§CŖŅÔĢ„ĸ(�øúøRRVz§–€ÂĸbBBkūĸ×sūB*ßųšNįšĖŠÕf}āžT*…>Ŋ{r"å†C x˜éē+Oĩ/ΟSoyÛÖ-Ųöå^ZÆļĀĪ×įŽÛOŸ8žŨŗeĮnÚļnIdxØ]× Ā×G‡Ųbi|ķBņ=Ô,ĄÁųķ:d�—¯\%,,€ĸâb<;‡ÃÁŸ˙īoôęŲ?_?ž›÷ îînäįĀŋŠĩáų ?ŠŠ‹())Åf­w‘ÂÂ"ėv*•‚Ņ`ÄߡæV•ß” ÖëÉÍ˧U\šyų„„Ô„m~A!�&“ _Zöž})Š‚ãΛ‹Áûķū‡K°Ûí$۔ĶYĪ™¯.Ōēe,9šųäzgū•Áhdδ‰8ūąx)Ũ:wDĨrÃnˇŖRŠ0Kj×-*6PZfÆG§sj˙Bņ¤j–ĐíŨŗ7nÜäOyĩ§€įæÍāFÆMžÜŊAAë™:9‰w?XŒ§ÚģŨ΂ųĪ4XˇU\,nnîŧûÁ‡øûûáįW˙ŪĪž~žŦYˇž‚Â"zöꎷˇļÎΧN™Čú ›đņņÁją2ī™9\¸˜JUU+W­!++›ÉIãkÖŊO_!!Á|v™#G3 _´Z-áaĄ¸šš5v šĶËī/YQgŲ°Áũ9t4…×^œOII)ËWoāĩįŗ~Ëv:ˇoGģļ­k×Ŋ••ÞÃGŅy{ā>(€ŪŨģ°bõF‚‚pw¯éĶápP}ģšõ[ļ““WŅÚ¤!„øžQŽģœįŊ###ƒØØØ‡*z65Č°āÆô媁—,ã?~ōŖ‡Úîčņ”Új5ÆÛīŧĪŦĶ nøČķÛä!„kMMčßģ“‘‹-zô>Hõ$2 ,_ššļņņ¸B!žLßĢĐ |čŖ\€ū}û4zÜ×_{šQ5„B<ūžˆ;R !„ ]!„ÂE$t…B‘ĐB!\Ä)ĄëĨVc+/wFi!„ĸÉLĨč´õībØÔœōéåčČ2ŗ 0˜ĘœQū{ãljzsˇ „ß :­†¨pįß_Â)ĄĢĶjhãŒŌB!ÄcKŽé !„."Ą+„B¸ˆ„ŽBá"ēB!„‹Hč !„."Ą+„B¸ˆ„ŽBá"ēB!„‹Hč !„.â”;R™­62s (¯¨tFy!„ĸIéŧ5D…Ŗņōtę8N Ũ˜´O‚üœQ^4ėŧB�"ܯQ!„xÔUßžMV^!ņąQNĮ)§—Ë+*Ņxy9Ŗ´BŅäÜŨÜ0[lNGŽé !„."Ą+„B¸ˆ„ŽBá"ēB!„‹Hč !„.ōX‡îĄäŖüΟ˙—ŖĮsøČQ§Ž•“›Ë[˙xīĄļšp1Õé}=Ę,VKWŽvęGŽŸāęõ�ŦZˇ‘ŠĘŠ×Û°e[Ŋe§Ī~EĘÉ3NíĪY㗖•ą|ÕēĮÉË/�ęÎÍwõÔųīˇ}ącˇ2ŗU˙aŨë9nŦäc'Č/(ŦŗĖ¯‘‚ÂB>Zē’ë7n6øēhJÎŽ/žSž§û víŪËŲs_áæîNyyƒögđ U#õë4f͘N\lĖoŗčÕ׉oŨĒβžú2jĩĮCũ :uėpΟ/ųt9ĶĻLÆ×GWģlŨÆÍ胂:x`í˛˙ôįüõ˙ČÛwrėX úā �lļrzv‘ÃųbûNÔŒ9ŧÉú?rüį/|ŸŸoí˛„ļņtīŌŠQu/¤ĻáææFû„6Ŧ^ŋ™™S'~įZˇ2ŗéÕŖÕÕÕÜļÛņT7üÅö)IãĒî‘ã'¸p1 _€Ãî`|âH|}|žSŸ;ūŨ|3wQ‘á÷]÷›šyRÜī9nŦũz;ĨîŋĘĘÎĨCûﴌ‹Ąe܃˙îzPßū˙WSŊîDĶi–Đ=”|”Ģׯķ“ŋŽģģ••Ŧ]ˇ›ÍFąÁĀú[đķõĨ´ŦŒgŸžMÚå+œ8uŠčč(nfÜbĀ€~úûsũÆ ļnÛAģ„6Ølå$ËGŸ|ŠÃá@ÄŠSgøÃī~SglOOO~úãÖëé×oüž^={Rnĩq++‹×_{ĢÕĘâ?!,47÷šŠ*6øëÛīĐšc,Vžž:ĻNšHVvvŊžS/]ĸ¨¨˜āāû?÷ÕĒĒĒX¸ā9T*偿nČāĩÁjąXųÕoߤAû¯ēwíLŸ^Ũë,ËĖĘæØ‰Ķ˘’DĘŠŗTUVŌŠc;6}ąƒgįĖŦŗneU%ÛwíĄ´´Œ`Ŋžū}{qėä)4^ŠFneeqäø |t:RĶ.LQ‘Ž;Åæm_âåå‰Åbeđ€>DFDđūGŸ˛`ŪlPjæLíáÁŒ›´ˆŒ`ßÁԙ-TWß&ĻE$=ģuåŨ?á•…Ī‘™•́ÃG ÆbąNEE[vėB*+ĢH;€n]:ÕîûWR9vâ4ũ{÷díĻ­øčŧÔŋ/__ē\gŦŽíÚątåj^z~�ģö :2’ũ‡’ī:~UU[wėF­öĀjĩ1d`?´MqÂBC°•—×ΝFûĪīÁŸ8}ŽPZfž3īUĩsķÍÛvŌˇgBCƒygņ'Œ6„6­[˛üŗ5LšČîũ‡ëôō`7N)-+cķÖLœČá#)¸{¸áŠV“™•øŅ#Đy{ķŗģņT{Rf63 o/Î|uáŽŊ$ŽÁĻ­;hÕ*‹ÅŠ—Ú“Ã“™•}Īįøũ>%!!žęĘjLĨ%LŸˆÃá¨7ˇŪZ-ĢÖmĒW˙‹ģčÜą=ŠĸÔ{ŽūÕŽ})))Š9`ЏđАzûÉâĨËißļ-ä’4v4įSͰÛ턅…˛ãË=ŧ˛đ9Nž9ĮĩëøQ\ldØāäbąXС7ų……:rœ1ßĒķēČ/(ärúUÜŨŨņÖj<°_ík$88ˆĩļđĘÂįøúŌRĶ.ĄņōÂĄ(Œ=‚Ô´Ë|}é2Ą!ÁäæĐĨSÚĩ į\|wÍēgĪ}ÅøÄҏģģāŠV3wÎ,�6lÚÂÄņ㈍mÁ‘cĮŲŊo1-Z Ņh˜œ4ĖĖ,6nŲĘ_}™–ąąŒMMvN6[9×nÜ ĸŧ‚W_~‘ĸâbvīŨWo늊Š:§‰##˜6e2ˇĢíôíŨ‹`}oŋķ>9šš\LũšŽ]:3bØPΜ=GAA!Š6Ģ•éS'Ŗ( ŋ~ã÷Œ1ĸÁž#""�psS5ØhH0ŗgÎxāĀ8˜|”´ËW0›Í¨T æĪmÄ3qgĪ_$ãVfíãĄƒúÉĩŒ›M9ÉÍĖ,fM„ĸ(õĀją1fÄ0Eá|Ė𥃈oGhH mZ“úõeôíͅÔ44^^ <›­œåĢ×2vÔĀAâ¨áÜž]ÍZsZņĨž ũÚuZD×üžvã& mãÉ/(¤ °˜YĶ&áp8øāãetęĐŽļŸäã'5b(ĄÁÁ|šw?�gžē@LtŊ{tãVfefsũũ°ŲđôTŖrsŖŦĖĖssgQPXÔāXú �rķō áFÆ-† ÄūCÉw˙ėų‹„ëد7FŖ‰{ö1q\bí8ʝ7/¯Úš �Āáprę4¯Ŋ¸�EQXüÉr�nŪĘŦ›o´nKÆ­LÔj"ÂBšqķą-ĸ@QHģrĩ^sĻOšīëÃV^ÁĄíģ?n ž>>¨ÜBCBčÚŠ)§Î’~įôvDX}{÷ ¤´”õ›ˇĶ§g×ģöĸŅh°•Û:°?Šĸđ÷>fİÁ÷}ŽĢíÕtéО€�žÜŗk7nRRZÚāÜ6T˙^¯‘oËÉͧ¤¤„é˙ŋŊû lęÎŋ˙•lĢŲ–%ËŊ€ ! Ŋ0ÍôŪ{O`&C˛Ė&™™ũīîĖΓÍėf63a&uŌ R ´PĒczĻ0l°ģå*šČÖķBXÁqƒ%Cr>oĀW÷žķģįĘ:ˇézÆTŠKJČĘĘ!ņÜųë¸|É|jkėtíō8F?=ˇl§ŧĖB÷.cŗÕŨ.€ÚÚZŽŸüŽ•O-āw×@ ˇŋ˙ šyųLŸ2•—˙x-qCcī‘@“ɚ܁„CŦXūžėøōkŽĨ]GŠTĸV̉2ˆœÜ<â‘ĻëmŌt ĩv{Ŗ¯åå�@P@�gĪ%Ņž];Œˇ>dT*UUՍ.k.,Âdō ĀdjôŠXjĩšį~ųLŖËû ŽyT*ĒĢĒ0›ÍtéŌÅ/ Ā9ŸÉdr~ę}õ—–4:îēĻ ÜÕø” 4Q€¸!ƒ;f陙|¸öcēvéÜäŧ­Ąwn Žtbcú˛ęõˇ™5}˛ŗ1üP*ˇ(î°sa08NckĩŦ– "ÂBéÅĻm;P*”ŒˆĢųáZęuztslŸŦŦlFÆ !åę5ŒˇļŖBĄĀĮĮ›Ō˛rį2%%ĨõŽĮ“üüĀî˜uëEģHĮ#āŽĨ]ŋm‡ÃŽˇŖGÅfĢE¯÷EĄPPT\ÜhŽŨē’t1™ŠĘJĸÚEâéáŅl~sQ1ššy:cÎ<ÍŠ¨ŦDĢÖ8į3Üēp{mętˆŽbĮ_ãééA÷nų.ņ,722‰Žjßä~h×îŊ—”Đ­Ëã�ė?˜@`  ãm— ün].ņōōÄjĩRV^NTģvŽ×ôzJKK› €¯ī÷ë^7”;oc…ķRˆˇˇĨeĨMŽWcņë4ļNž:Ãå+W 0ŽŸŸŸs}üôzžÚw Á:6V[MÃßũĘĘ*4Ēīˇaā­Ī°Ļüđ}ņõŪxŠŧ¨¨¨ĀV]Ķ`ūŠŠJŧŧŧœīCŖŸEÅÅh5ZįĨ-/O/lļ†ËŠÖ×&7RÅôíÃģŋĻúÖ)°ĒĒjŪyo ÅÅ%•@VvAAͅĒG¯÷Á\T @~A֊Šû§ÁāGAAÁ­ąd;§įååS[ëhŒæB3ŊßŊ[v{mŊIááa¤\šęü9ųōBB‚,NXh0GŽoņzĩ†}ņ Lš0†Ŗ'N:ˇã]S(°ßÚą¸}ũ ͎mWZZ†Î[KQq ŅíÛą`ö ÆôåБõÂdįæDyY9:oJĨƒŸ…fķ­ØvJKËĐ{ÍÜ××s‰#OŨņíÛ9õú RoŲ÷îŅyŗĻ1oÖt&íŧ–ø}ƒk<×#ĸ¸žžÉų —čŲŊūuũÆō }¤#Ķ&gԏҌ9ŧ^žĻj QĢąVVP[ë¨caaQŊÚÜNĢŅPS[Ãõô ÚEFā­Ķ’t1™Nĸ›ÃM?š…sg:úøą#Ņëõœ<uĻŅųu28×Õl.ÆĪĪˇŲą4æîļąÂ"Įú—āëĢŋëõē]cÛ¨_Ÿž,œ;“1#ã0úų9§—xúlŖëxˇÔjÖĘ ėv;vģŧG/Ol6⨍S÷ž¨ŽŽæ›#G™4~ Ŗ†CŠT`ĮŪđ=ĸQSUU팕_Xˆ˙­ƒ�á~mr¤;°?ĨeeŧüĘßQĢÕØl6†Į ÅĪOĪŦ™ĶØŧežžžXĘ-,]˛K—’ī*î#aĪžƒŧˇæCü|õøøø4˜§˛˛’U¯ž^oÚÜŲŸFË[īžĪõë7Đh5Î7˛ŪOĪÆM›ÉÍ˧_LŧŊuŽûÜų¤fĮÅģīĀʧŸBĢÕĐŋ__RS¯ķį—_AĨV°li㧐§O›ÂK/ŋBß>Ŋ�ˆ˙æI/ÖäzĩDâ™s¤^ŋîü98(ˆđ0ėĩĐŊKg<”Jöø†AcØžs7KÎŊcĖĐ  ŽĮ`ĐŖRŠųz<ĄÁAØl6žÚw€ÜÜ|†ĀîŊĐj4TÛĒé×Ûąžo¯^ËŦŠ“đõqėņ_MģNtûvÎņ…ąmį—Øl6bÆ8ë0x@žüz?&•••`´ĶĢÁww�� �IDATG7v~š‡ÍŸīĸ˛Ē’ŠÆqķfÖ×Ŗš\áĄdŪĖjĐøÍßŊ ;wīeĮ_QVnĄwĪîDF|jØ\\Âæ­;øų“‹œĩ‹ķŠĀũûôâãĪļbđķCĢĶPVZîŦ 8Žåv~ėQ}¤aaŽĶē^^´oÉÁ„ÃL›4ŖŅ¯ÁüũīüÁėĄT2*nmÜLXXÝC€Ū=ē:bīۃĨܘ‘Ã5jb,喆ĪŋŊ›mėĄđābōŠŠŠ°Z+čŨžÚššfkۘÆļŅ킃 `ĶļX­Ä DHp`Ŗëx7”J%}zvgã–íčõ>øxëP  *2’S§Īą˙›CŽŗÍ?8æéé‰ÉčĪį;wŖÕič՞#ĮNĐ>2ŌųûUgtÜPļl˙FF­&ē};.\ē|×c­Gaˇ7s.HKK#**ĒEA“RÚä¯×TÛl\¸p‘ž=ēSQQÁ‹/ŊĖ‹ü}Ģæ(4ōîęĩüî×Ύj\w{ĐūĘĐš¤‹—0tЀļŠxHÕŨ,÷0ēz-v‘áxyyąfũ§Ė>oīļÖONfvŊģļėēvKzäŠ+Úî+CŽāåéÉŠĶg8øÍ!jkk˜5}j[I!´”O7mC§ĶŌ!Ē4ÜąՑޏ{ڑŽB´5wé>ÔO¤B!&Ōt…B7‘Ļ+„B¸‰KšŽFĨēīīČ !„îR[[‹Nëō<.š{92<ˆôĖ\ ‹Jī<ŗhSu7T !ÄO™NKD¨ëo,uIĶõŅiéÜŠõ˙z†Bņ0“kēB!„›HĶB!ÜDšŽBá&Ōt…B7‘Ļ+„B¸‰4]!„ÂM¤é !„n"MW!„piēB!„›¸ä‰Te+é7sЍŦrEx!„ĸUųxk‰ DĢQģ4KšnúÍ\tZ5&ŖŸ+‹ģP÷Lå–ūAf!„ø)*,*!#;NQ.Íã’ĶË•Uh5W„B!ZŋAOYšÕåy䚮Bá&Ōt…B7‘Ļ+„B¸‰4]!„ÂM¤é !„nōP5Ũ’Ō2ūôŌË.‰}3+‹ŋŋūV‹–9w>‰„ÃG\2ž‡AYš…UožįŌ{$p!9€ŦYOEeeŖķ­ųhcƒi‡Ž~ˁ„Ŗ.ŸĢō—đÚ;4š'ãfPŋ6-ĩqÛN’SŽŪ͞Bˆ{į’īéŪ­›ļPmŗąhūÜfį[ũá:fΜqW1WŦ|ŽNtŦ7íŲ•Ī RyŨķ8›ŌŊ[×f_¯ˇŪ×Į9mĶÖĪ 0™>lˆsÚ¯~ķoŦúËKėüb7G' Đ€ÕZAŋ>Ŋ;f;ŋ؍ĘˋącFĩÚø÷HāøÉDüį´^=ē2¨ßûŠûíŠ3xzzŌģGWŪųāc~ņäÂ{ŽuåZqCbŗŲ¨ŠŠAŖnü‹ëË5˙úĄ=8ņŨiLūFėØą×ÚY8g:?ũ=ŗĨų›RWģčö‘wœˇŽ6-qŋÛCqÚŦé^KM#88ˆ’Ō2˛ŗs &/ŋ€×}Äo~õ,�˙ö˙ÅS˟āô™sTWW3oÎ*Ģ*Yģūc ̈́……2wöĖzqÕjĩsųÛũá…‰é׏ ‹•<÷ËÂbąđÎûk ÂÃĶQŠ‚ÂBVŊú=ēuĨÜbE¯÷aÖôiddf˛yëvüôzJJKybŅ’.]"?ŋ€ Ā@Nœ<Idd×Ķn0xp,ūƒsÜO-_†RЏĢēÄ âlŦåå~˙Į[ĩŅūĐā1Œ[oÚĩ´ė‹?ĖSO,āāĄcTVVŌŋo/>üdĪ?ķŗzķVVUąaëĖEń1fÄPö}soŽÜüŽ¤Ļąį@~z_ž;}ŽˆđP˛sōĶ›NĸYˇa Z­†ŌŌ2&ŒNTģHūô—×øíŗ+@ᨙĘˋä”ĢtŒjĀö/÷P\RJuĩNŖ۟˙~ųīüūˇĪq-íģžÚGXh%ĨĨDĩ‹¤ĸĸ’õŸmETVVąxžã=3h@?įē?™ČžøÃŒ>„÷Ö~‚ŸŪ—ņŖ‡sęĖųzšúõęÁĒ7ßãßũK�ļėø’QíŲą{O“ųĢĢĢųčŗm¨U*ĘĘ-L;oīzy"ÂBąX­ÎÚy{ëœ5Ž?|œä”+˜L—�PU]íŦÍņ“‰œM爧§'ž>>Ėž6‘uļ046†¨v‘Ä>€Z­rn€Sg“8™x–ėÜ|žX0‹�“ĢŊ¯„kŗĻ{đ›ΛCeeģvīfáŧƏ:vė@pP nŊ^ZRÆâ…ķQ(üö˙ũgƒĻ[YYYī4qxXŗgΠÆVËĀū1˜xõp3+‹ķIčÕŗŖGįTâirsķP(Āją0gÖ  xáEƍ͖mۙ6yQQí8|ô{öī',, �%Z­–S§žžÁÖí;xvå3ÎqßmÈ?t„‹É—)++CŠT°üÉÅ-ŦlË=ņ)WS?O7’Qí¸xų {"åZ*O/[ŒBĄhĐpĘĘʙ3m …‚?ž´ŠiĮŌíņG  ĨgˇÎœL<˘CųöÔŧu:όÅbåÕwÖ0æTĀÎŧ™S°Ųl”ßúbúüæŸHēt™G:DpņōzuīJfV6YŲšŦXļģŨÎ˙üõubúôtŽįĢũņ˚6‘°`6o˙€Ã'NŌŠCqƒr5õ:Å%% ×ÃbAŖQãááAQq ˙˛ō)nfį4š+8(€ôĖ›D„…’œrŠĮ˛c÷ž&ķ9ņa!ÁŒ9Œü‚B6nÛÉŌųŗyˇv.tZ­ŗvAŽŗvģ Gø¯ß=BĄāĨUor5ÕY…BÁ į ōōâO}kEEŖÛēŸžė‹?˘CŲ¸m'‘aĄ ‰á@ÂQÎ]Hn°ķ%„h}mŌtķ ¸|ų k>\Āô Ę'[îjŲ€�JĨãRtŨŋˇSĢÕ<÷Ëg]ļî4ĒZĨĸēĒ ŗŲL—.]nÅ pÎg2™œ„z_=ÅĨ%äåė˜'( €ŗį’œMĀh4 RЍĒĒnrüJ…ėö&_2ˆącF‘ž™É‡k?Ļk—ÎMÎÛbû÷môÃvÔ°Áüû ægKį;kŅ“ŋņļíŅü΅ÉßQNKyš…¨vt~´ī­ũĨS&ŒŽ7˙Åä+ č× €é™L0†ķ“Gd …?Ŋ/Å%ĨÎeŠŠJ0ŨÚ&#v;˜ÍÅ<ÖÉqÉĄc´ãhųŌå̎ípØŅûú2}ō8lļŒ? …æFs čכīNŸÃb­ SĮh<=<šÍŸ_`æfV69yųÎX€3OsŦč´Zį|u5ŧŊ6�›ļíB­VaĩZŠŽļ5ŗN`€cŨÔjååw÷û'„¸?mŌtŋŪģ§–?AĮŽ�H<s–ƒ ‰Åfs4,ĢՊĩâÖ#š`ˇ×ĸP´î}_ƒ�deg;§įååS[kGŠT`.4cĐû@VvŖŖÉĘÎ!(( Š°ßģ5îۅ‡‡qöėy†Į  ųōBB‚,NXh0GŽgđ –]ˇk Ÿų5 æLcīÁC<Ú1/¯\W(°ßÚą¸}ũsķ (.)ÅĮĮ›Bsu搨Ž\KãĢ}ņ<špŽsūŒ›YĖ ›@ii>>Ū(•JLūF?y+ļĸâ úī¯ÃúųųR`6LN^>A˜ŒääæŅŊËc$_šæl`íp”Ų,ˇ5¸ÆsšüŲŊ7žr‹•¸Áę-ßx~üF Åfŗa.*žUĻFîmĩĐj4XŦVjkkQ*•äæÔĢMuu5_î=Āūõyjjk9sū"vģ•——ŗųš‹Š0 ļ‡ÂũÜŪt-V .\Ēw:š[×.lŪú9ãĮŒÆĮĮ‡7|†ˇN‡ˇã¤č¨(Ū}˙–.žķ •••ŦzõõzĶ~x ēΐØXŪz÷}Ž_ŋFĢq~ØéũôlÜ´™Üŧ|úÅôÁÛ[ĮŦ™ĶØŧežžžXĘ-,]˛sį“šKŨ¸W>ũZ­€ūũú’šz?ŋü *ĩ €eK?…<}Ú^zųúöqŅÄsˆ¤‹ krŊZâČņ“õîb !:ĒöZ;1Ŋ{âéáÁ֝_1fÄPÖ}ē™gŸ^~Į˜‘aĄėŪÉ߀F­aˎŨD†‡bŗŲØŧũ nfå0yŦã:õgÛvá­ĶQU]ÅĐXGûßWŪ`ųâšüô( .^žâ<R  !2<ŒĩŸnĻēēšŅ#†ĸžUG€ą#†ąaËB‚ƒ°Z+ ĀΘž|üŲ6V¯ßHEe‹įÎäúŒ;ŽGsšĸÛG’v#ˆ°ĐzË4–?6Ļ7oúœ6nĨ¤´ŒAú9¸ ĖEŧŋöSūõš§ĩ›4v$āhĖqƒđæûë0ųņöÖQRRęŦ§§'AŦût ŪŪZīԑ=žĄwŽė=xˆËWS).)ÅhđCŠT:ˇ‡ĸm(ėöfÎuiiiDEEĩ(hbR á!÷3Ž6Sh.äŨÕkųŨ¯ŸoëĄÜ—í¯ }{ę æ"ƏŠkëĄ!DŖ“RZü™Ų’šbŊ‡ë{ēB!ÄÃŦMŋ§û ō7ú?ôGšĸÛī0BˆŸ*9ŌB!ÜDšŽBá&Ōt…B7‘Ļ+„B¸‰KšŽFĨjōQtB!ăϰ¨Öåy\r÷rdx陹•ŪyfáR‰I÷ö§ß„â§ÄG§%"ÔõĪ—pIĶõŅiéÜŠũgB!~B䚮Bá&Ōt…B7‘Ļ+„B¸‰4]!„ÂM¤é !„n"MW!„piēB!„›HĶB!ÜÄ%Į(ŗXIŋ™KEe•+ !„­ĘĮ[KDH ZÚĨy\ŌtĶoæĸĶĒ1ũ\^܇Ėė<zwíÔÖÃBˆJaQ ŲytŠŠpi—œ^ލŦBĢҏ"´BŅęü zĘĘ­.Ī#×t…B7‘Ļ+„B¸‰4]!„ÂM¤é !„n"MW!„p“ēé9vœí;ŋhĶ1ė?Ī×{öĩh™_ė&==ÃE#zđ••[Xõæ{.Íąį@’S¨ŠŠáõw?lbålú|Wƒék>ÚHÆÍ,—ŽĪUų‹ŠKxíL˙ī—˙îü]mî%ŽÂĩ\ō=ŨæØl5üíĩ7�¸–šJdD8^^*ƍE÷n]]’ķęÕkü퍷héœÄâ…ķ]’oĘÄņÍžū÷×ßâš_>SoÚĻ­Ÿ`21|Øį´_ũæßXõ——øËĒWŠĒĒBĢu| Ëbĩ2cęēt~œŋŦz•9ŗfĐž]$­e΁ŽŸLÄßhpNëÕŖ+ƒú÷Ŋ¯¸ļŠĮâã­ã>æO.ŧįXWŽĨ7d Š×͉j×ø÷ę|ŧŊ™=mR‹âî9Ā‰īNcō7bĮŽŊÖÎÂ9Ķ1øé[<Æ{ÉߔēÚŨēÚÜo…­ĪíM×Ķ̓ßüęY�ūôŌËü|ų2L|{ō;^{ķmŧŊu€‚eK9—ąŲjxãī0fÔŒF>ۂŅ`Ābĩ˛tņΝKâÄɓDFFp=íƒĮ͎OŊŧtčĐ Ņüá…‰é׏ ‹•<÷Ë"5--ÛļNqQ ;DSPXČĒWß GˇŽ”[Ŧčõ>˚>oO~Įą'ë{ÍÚõ 4ÂÂĸãǍ¨āōåv}š›IšoΡ[´`žŗą&ž9K|Âaēt~ŧåā. ÈĄąõĻí=xĨRÉČaƒxŨ§Ä ˆš¸˜ėœ<ό]oŪĘĒ*6lŨš¨˜Đā zvëĖų‹ÉØl5tz$š+Šiė9€ŸŪ—īNŸ#"<”ėœ<ÆôĻS‡hÖm؂VĢĄ´´Œ c†ã§×ķá'›xū™ŸQU] €ĘˋK—¯ĐųąNTVVņņĻmh5ŠKJ3b(ūFë6láŸņ$ņ‡“œr…�“‰ĸâ�2ŗ˛ųü‹¯ņōôDĢÕ˛hÎt� čį\÷ã'Ų˜öíÂ9™xO–-šËޝ÷S\RJuĩNŖ0œ>…ŗ§đ—×ŪfūĖŠlŨõU“ųsōōų|×WøéõXŦ˟9…ĒęjŪ[û ~z_ƏNDX(i7Ōĩ›2áû:¯Û°ĨBÁOOmm-€ŗ6§ÎœwÖxØ ÖĻΗ{āááÁØ‘ÃøâëũäRSSKŽĶ¯w^úۛôéŅ kE™YŲŦxr­ķFâ'ÆíMˇ)›ļ~΋˙ßđōôdõkIēp�{­ÕŦeø°!téü8¯Ŋų6“'Žã‘މO8ÄÁøo DĢÕ2cęŌĶ3Øē}GƒĻ{-5•ŋŋū–ķįū1}ˆ0€[-ûĮ`âÕ7ūÁÍŦ,vėú’… æÎGŸn@Ą�ĢÅœY3P(üá…7zt“ãđđP6×ʧÁî¯öļ¨á|˛á3´Z-ų„1g֌{-õ]9zâ;RŽĻ:ž4n$Ŗâķöš°ÛíđH‡¨&—/++gδI( ūøŌ*ĻMK`€‰™S' ĶjØ˙Íƌʡ§Îā­Ķ1eüh,+¯žŗ†ų3§væÍœ‚ÍfŖŧ܊ŅāĮķĪü €”ĢŠÎÜŠ×Ķ™0f GOĐ>2‚‘Ãa.*fõúülÉ<�ėv;Žđ_ŋ{…BÁKĢŪ`ĮîŊLŸ4ް`N&žĨÜbi¸ Ĩĩš'Î!3+›Ŧė\V,[„Ũnįūú:ŋZųsļîúŠššō ĖøúøāããŨ|ū/÷0fÄ0ĸÛGrôÄw:ö-ąũûRT\ÂŋŦ| …B@TģHgíl6�×Ķ3ŠŦĒâįKæSnąđ͑õjĶŋOOöÅfˈĄ<t´ÉÚ$9AMM ƌāfvi72ø§Ÿ/Ĩļļ–?˙í-zõčJMM-}{÷Ād4đ“GxXČŊŧ­„øÉ{ šnšÅ‚Z­ÂËĶ1œĀ€@ōōķQĢÕˆOĀWīÒG§‚ķōķØģī â¨ĒŦ",,�ŖŅ€JĨĸĒĒēAŽŅŅéÎŊ~ĩJEuU…f3&�`ˇ`2™œ„z_=Ųš9ŽûvwWĨBáĖ͘ķæĐž]$ûƓ—W@PP`“ķū}é ËÛkÖķ§?ükŗË›ü(•Ž[”JÅæuÔ_§ĶR^n!Ē]íÄ{k?ÁCéQīčābōôë…Åbu4D ÍtęØ�ŖÁĸâbįü֊ tZ­sÛÕå3›‹Ûž_īÎųŋßá°Ŗ÷õeúäq\Lž‚Éßą- ͘üP(øé})-+§Ëcp)å*Š×ĶĶįŽųķ Í|sø8‡Ž}KUU!ÁAÎņ×Í۔ĸâLˇÆî­ĶĄšõŧØēÚÜŽŠÚddf‘“›Į3˗8ÆS`Æ\TĖē [�ĐhԔ—;vDŒˇN¯ĢT^Tۚ~ !š÷@ÜHå­ĶQQQIõ­ScYŲŲ9>€†eæ´)|đáG�2aüXžZū$‹ÎgÔČ­>ŖÁ@^A�9ŲŲÎéyyųÔÖ:ŖšĐLhpH“ãnŠBĄĀŪHs #åĘUįĪɗ¯Ü`ž¸ĄC8wū<99š-_ąûdĢŠa΁oX4o[w~uO1ęÖŨn¯uNËÍ/ ¸¤o ÍE<ÖŠĪ,_ÂČaƒøj_|Ŋ7ŗˆ åRĘ}¤#�&rķ;<ųæz§Oĩ ĢÕy 67ĪąmLFrrË:ö-æ"G3Šíߗ_<š_<šˆųŗĻĸQ;Z]4ųÉŊĩseˇÛ)*.Á ×3 _oĪ&qéōUēu~ô.ōû3jø`–Ė›ÉÜ铉<Б§‰†{ûûÆOīKš€ŌŌ2Ŧõjs{›ĒMHp ˙˛ō)>Ųü9•˜Œ˜X2o&KæÍdŪĖ)øé}‹âŪ<Gē�sgÍä­wŪG§ĶĸĶiéŌųqŽ;Ž—§Ŋ{õ$ųr _īŨΌéSŲ´y:–ŌŌ2Ė›}Wņ¯^KeÕ̝;V(”<˙Ī˙Ôčŧ“&Œcíē Ãjĩ8?ėô~z6nÚLn^>ũbúāí­ktÜĮN|Ûä8”J%†O6nbÁÜīĮŪŋ__RS¯ķį—_AĨV°léâË{xx0}ę>ŲđĪ?ģ€>ŲāŧÉj@˙ pW5iΑã'INų~' ",[M ąúŌ¯WŽĨŪ ņėyjjjÉÎÍcō¸QwŒŲ>2œuŸnfųâyhÔļėØMdx(6›ÍÛŋāfV“Į:â|ļmŪ:UÕU ā8ût3KæĪÂā§GĄPpņōUÆ  Ā ū}øxĶį|ŧésĘĘʙ9e‚3¯BĄ nđ�Ū|&Ŗãúģ&Íļ_áéé‰NĢađ€~wU›đĐ"ÃÃXûéfĒĢĢ=b(jĩа`rrķ‰nQīšg“ųĮbû{Đj5Ž1O€ˇNį\îØÉD˛˛s˜1yŧŗvuëÕ>2ĨĢ×oÄOī‹Ū×sQąŗ6 …ÂYãÉãF6ZOOOüƍÎúĪļōķ%ķ æÃO6QmŗHX#;~Bˆ{§°7vØu›´´4ĸĸĸZ41)…đמūtˇBs!īŽ^Ëī~ũ|[åž<hečÛSg(01~T\[Eņ—˜”ŌâĪĮ–ôČ+V<§—…BˆŸ‚æôōƒÎßč˙Đå>ˆbúôlë!!„ÛȑŽBá&Ōt…B7‘Ļ+„B¸‰4]!„ÂM\Ōt5*•ķËúB!ăް¨Öåy\r÷rdx陹•ē"ŧ¸O‰IÍ˙Ų7!„øŠņŅi‰uũķ%\Ōt}tZ:wjīŠĐB!ÄCKŽé !„n"MW!„piēB!„›HĶB!ÜDšŽBá&Ōt…B7‘Ļ+„B¸‰4]!„ÂM\ōpŒ2‹•ô›šTTVš"ŧBŅĒ|ŧĩD„ĸÕ¨]šĮ%M7ũf.:­“ŅĪá…dfįĐģk§6‰B¸^aQ ŲytŠŠpi—œ^ލŦBĢҏ"´BŅęü zĘĘ­.Ī#×t…B7‘Ļ+„B¸‰4]!„ÂM¤é !„n"MW!„p“EĶ=w>‰„ÃGš|ũ›CGøß˙û …æB—áfVũ­-s§q˙ؕ•[Xõæ{.Íąį@’S�øĮšõTTV6:ߚ66:-ãf–KĮįĒüEÅ%ŧöÎÍÆŧŊ6­ábōŽLlōõcߞâooŊOQqIĢåâaã’īéŪÉĻ­Ÿ`21|Øį´_ũæßXõ——î)^÷n]›}=éÂEæĪƒŋŅ˙ŽąVŦ|ŽNtŦ7íŲ•Ī RyŨĶØšs§q¯ūpŗgÎ@īëãœÖ\ív~ą›ŖGhĀj­ _ŸŪŒ3Š_ėFååÅØ1ŖZmü{$püd"ūFƒsZ¯]Ôŋī}ÅũöÔ<==éŨŖ+ī|đ1ŋxrá=Įēr-¸!ąŲlÔÔÔ Q7ūÅ÷e‹æļ(îž œøî4&#vėØkí,œ3ƒŸūžÆŲŌüMŠĢ]tûČ;Î[W›ÖŌųąGš}ũRĘUfN™pĪ5âĮ Mšns9ÆŠĶgPyyĄRĢXļt1§NŸáĐá#øøø Ķj™?w6kÖ~DM ?Ŋžđđ0ōķ ˆ2˜U¯ŊA÷î])))EĢŅ͎7×RSŲąëK–,šĪŪ}(*.ĻēēšĮ{”‘ÃãęåWĢÕüæWĪ6×^x‘˜~ũ¨°Xš‘‘Ásŋü', īŧŋ†ā <<Ĩ,(,dÕĢoĐŖ[WĘ-VôzfMŸFFf&›ˇnĮO¯§¤´”'- éŌ%ōķ  äÄɓDFFp=íƒĮâo0púĖ9ĒĢĢyjų2”JÅ]Õ/nØgc-/ˇđû?žØĒö‡ŒaÄĐØzĶŽĨŨ`_üažzbŖ˛˛’ū}{ņá'›xū™ŸÕ›ˇ˛ĒŠ [w`.*&48ˆ1#†˛ī›Ãxëtäæp%5=đĶûōŨésD„‡’“Į˜ŪtęÍē [Đj5”––1aĖpĸÚEō§ŋŧÆoŸ] GÍT^^$§\ĨcT{�ļš‡â’RĒĢmtęÅĐØūü÷Ëį÷ŋ}ŽøÃĮINšB€Éä<"̍¨dũg[Q�••U,ž7€Aú9×ũøÉDöÅfôđ!ŧˇöüôžŒ=œSgÎ×ËÕ¯WVŊų˙ūë_°eĮ—tˆjĪŽŨ{šĖ_]]ÍGŸmC­RQVnaâØøx{×ËŠÅjuÖÎÛ[įŦqc1ĢĒĢĩŠ[w€÷Ö}ĘÄ1#ČČĖâlŌEŧŧŧPyyą`ö4Î&]âøÉSxëthĩfLĪÉĶg9™xOēwysq ƒú÷åĢ×ĶåņN”––ĄÕhčÕŖ i72øj_<sgL&ūđąÛ@ˆŸ‚Žé;ņ-ĶĻNæ‘Ҥ§gP[[˧7ņßü=j•Ўß[͕kŠxz(‰n߁áqC9rė8� ĘĘ˙9m* …‚ßūŋ˙dūÜYtˆŠbâ„q”•–‘™y“įŸ]‰Ũnį?˙đąûŖÕhų+++ë& aöĖÔØjØ?†Ā�ŒbŖ$��ƒIDAT¯žņnfeq>éŊzö`ôČáœJ<Mnn X-æĖšBĄā/ŧȏҪ؞m;Ķ&O"*LJcĪūũ„……āáĄDĢÕ2cęŌĶ3Øē}ĪŽ|†ā @Ė›{×  ūĐ.&_ĻŦŦ ĨRÁō'ˇÎ†iÂŅߑr5Õųķ¤q#éՎ‹—¯°÷ā!RŽĨōô˛Å(Š  ŦŦœ9Ķ&ĄP(øãK̘6q,Ũ”đ°PzvëĖÉÄŗŒ1”oOÁ[§cĘøŅX,V^}g ķgNėĖ›9›ÍFų­/ļ˙Įoū€¤K—y¤C�/_ĄW÷Ždfe“•ËŠe‹°ÛíüĪ__'ĻOO�ėv;Žđ_ŋ{…BÁKĢŪāđ‰“tęEÜā\MŊNqIÃĶŖe ŠŠKø—•Oq3;§Ņ\ÁA¤gŪ$",”ä”kL8–ģ÷4™˙ȉī fėČaä˛qÛN–ΟíĖŖ¸ĩsĄĶjĩ 05ģN)WSĩiĖÉĶg™8f$Qí"ČĖĘĻļļ–­;wķī˙˛///Ö~˛™´x(=ШÕ<špߞ:€RФÜbaŌØ‘Îí:}ō8ÚG†3fÄPĘĘË­KSg!„ø1i“ĻĢT(ĀnoôĩÅ æąk÷×lŪŧ•.;c đ§ŧŧœĩë?ŌĨĨĨ�Üú`šŅ`p~Õũ['/?Ÿ ā@įk~?ŠŠŠŅ†|ßtÕj5Īũō™FĮVwU­RQ]U…ŲlĻK—.ˇÆāœĪd29së}õ—–—_@P°cž €�ΞKr6]�ŖŅ€JĨĸĒĒēŅüĐ|í�↠bė˜Q¤gfōáڏéÚĨs“ķū}éŒ6˜áĪüléüÛáv&#JĨãւ;í\˜üõ×鴔—[ˆjAįG;ņŪÚOđPz0eÂčzķ_LžÂ€~Ŋ�¸‘žÉÔ c81™�“ã2ƒBĄĀOīKq‰ãũd­¨@§Õ:Į[—Īl.æąNŽKŖG˗._Ŋm‡ÃŽŪחé“ĮaŗÕ`4øĄP(((47šk@ŋŪ|wúk:FãéáŅlūü37ŗ˛ÉÉËwÆœyšĶTĖÛk͘9Ķ&ŗį`ÛŋÜÃctĀßhĀbąōé–�”YĘ)-+ŋĶØ`yƒŸūļßÃú¯5UM 4]ņã×&M7<<ŒŗgĪ3<n(�ɗ¯ @~A˟XŒŨnį˙ūú7búõÁOīĮ˛ĨKđôô ''ŖŅČųķIwüĀųĄĀ€�~s€ÚZ;fsFƒáK5Í`đŖ  �€Ŧėlįôŧŧ|jkí(• Ė…f z?ČĘÎĄct4YŲ94ö{ °ÛkëMjŽvˇ‹ ',4˜#G3xPë]ˇģ[Ÿų5 æLcīÁC<Ú1/¯\W(°ßÚą¸}ũsķ7—”âããMĄšˆĮ:u`Hl WŽĨņÕžxž\8Į9ÆÍ,f…M ´´ o”J%&#‡ŸŧÛNQq ŊãŖVŖÁbĩR[[‹RŠ$7ĪąmLFrrķčŪå1’¯\sžīÛá(ŗYnkpį 4ųŗ{o<å+qƒ8—m:ŋ?ūF#†ÆbŗŲ0ß*S#ī˙Ûj×\ĖēÚ�(•Î×ÍfGėBŗ™…ŗ§aˇÛyũčŨŖžžŪ,œ= ōō 1ôœŋLË~ ›Ž‹?mŌtû÷ëKjęuūüō+¨Ô*�–-uœMMģÎW{öáëëƒÉäO``�ŗfLå͡ßA­RS[[Ëō'—ÜSŪČČÚˇä÷ÖP]]ÍÄ cŅüāŅ•••ŦzõõzĶæÎžŲhŧ!ąąŧõîû\ŋ~Vãü°ĶûéŲ¸i3šyųô‹éƒˇˇŽY3§ąyË6|}}ą”[Xēd!įÎ'5;Ūč¨(Ū}˙V>ũZ­ãhŧšÚũĐôiSxéåWčÛĮqD˙Í!’.^ <,ŦÉõj‰#ĮO’œrÕųsDXŅQí°×ډéŨOļîüŠ1#†˛îĶÍ<ûôō;ƌ e÷ūxLū4j [vė&2<›ÍÆæí_p3+‡Éc׊?Ûļ oŽĒę*†Æ:Ø˙žōËĪum]ŧ|Åy¤Bdxk?ŨLuu5ŖG E}ĢŽ …‚¸Áxķũu˜üŽëĸv͇?ÛÆęõЍŦ`ņܙ\ŋ‘qĮõh.WtûHŌn¤ęœŋŠüą1ŊųxĶį|´q+%Ĩe ĐĪyÄ P`.âũĩŸō¯Ī=íŦŨ¤ą#›ŒYRRZīH´ŸžŦß°“Ɉ§§ã¨ûFÆMö'ÁĮÛŖŅ@€ÉȔņcXŊ~*•ŠÚÚZ͝qĮ´´.BüØ)ėöfÎUiiiDEEĩ(hbR á!÷3އVĄšwW¯åwŋ~ž­‡r_´ŋ2ôíŠ3˜‹?*îÎ3 !Ä=HLJiņg^KzäŠ+~ßĶB!ÜŨË;Ŗ˙C”û ĒģÃX!fr¤+„B¸‰4]!„ÂM¤é !„n"MW!„p—4]J…ĩĸÂĄ…BˆVWXT‚N{įī“Kî^Ž "=3—ÂĸRW„n”˜ÔzúM!T>:-ĄŽž„KšŽNKįNíī<ŖBņ"×t…B7‘Ļ+„B¸‰4]!„ÂM¤é !„n"MW!„piēB!„›HĶB!ÜDšŽBá&Ōt…B7qÉŠĘ,VŌoæRQYåŠđÂjmV6lہĩĸ˛Õck5jæÍšBߞ=Z=ļB<L\ŌtĶoæĸĶĒ1ũ\^´˛Ėė<6lŪ‰Ĩĸ… â[**Ų°y§4]!ÄOžKN/WTVĄÕh\Z¸ˆĩĸÂ% @q+žBüÔÉ5]!„ÂM¤é !„n"MW!„piēĸÅ&ŒÎoŸ]§§KîÃBˆ­ēéūÛüWƒioŊũ×o¤sî| ‡Ô{mĮģIOΏīŧĢ?\Į唔ûŽķcÕ>2‚váhĩrŗœB´D›6Ũ›ļđҧīiŲîŨē2tđ ͅŦ˙øS�ĻLOddDkņō÷×ßjë!!„¸mv~đZjÁÁA””–‘CHH0�īž˙JĨŖŅ@­Ŋ€Ŋûr>éÁÁAšÍ�9vœüürrsIKŊΊÄ͜>{Ž!ƒĸĶéØŧu;~z=%ĨĨ<ąh�Ģ^{ƒîŨģRRRŠVŖeūÜY:r”S‰§ņōôB¯÷eŅ‚yŽwõkQ(=đõņ&=#ƒ…ķæâĢ÷áŨ÷?Ä×ׇüüæĪŪׇ×Ū|ƒÁĀÔÉIĪČh˙ƒĩáĨōB­ō"#ã&?ö%Ĩ%äæåņËgV•Í†Īļ`4°X­,]ŧ€S‰§š|9…]_îĻOī^ ^¯ŽĒræ;z_íŲ‹N§Ĩ¤¤”i“'Ōąc‡ûÚ^Ŗ† Æ`ĐĀäą#ŠĒŽĻļÖ΁„#—ÜW!„øąkŗ#Ũƒß$0 ĻÃb|<āhĕU•ülŲRƌIyy9ĩĩvžÚŗ—gW>Ã‚šŗŠ¨Ŧ˙ĤAđøãŌ§w/į´-Ûļ3mō$ž\ēˆž}ząg˙~”•—3sÚT–-]Ėw‰‰�(P°âįËyfÅĪšp1‹ÕŌø€ :DˇgöĖé <˜}RVZΨq,b #â†rčČ”ž˜ÍEŦ|ú´oŲh|%ĸŖ˜=s Ĩ’°°æÎžIYYĨeelÚō9“'Žã‰% éŌų1Æà0 Lš0žŅ×oĪëáéØYēh!O˙b9žžž÷ŊŊ&OÍđ!ą ‹Éßč¨ũ€~ ËČaƒčÔ!úžs!ď]›éæpųōÖ|¸€é”Oļ`6› 0™�đõņAĢÕbĩZņÖéP*n 0Ũ1~^~AÁ�pö\�Fƒ…§î_€>ŨˆZ­ĻŧÜBueu“q1 =EEŨU*ž;ušĶgÎb.*Æ×Į�įx›Šo0�Pyyáįįxr—§§ÕÕUäåįąwßAÄ'PUYEXXčÖ¯ņ×ëō>Ō!šn]ģđú[oãááÁŦSīXŗ;yé•7đöÖ0mâX:Dĩã>ĸÜbĨļÖ΍ŒĖûÎ!„?vmŌtŋŪģ§–?á<å™xæ,xėŅGÉ/(� ¸¸ĢŊVĢĨÜbĄļļĨRINvnŊX …ģŨ^oZ`@�YŲ9tŒŽ&+;‡  €FĮQYUÅį;wņŌ‹/PS[ÊS‰Ôbot^€œÜ\ēt~œŧüŒF#_īÛOttÆ b˙ÁxŽßHŋ5Ļ{‹_'(0 ãĮŌž]$ÅÅ%(”ĘzëŲØëˇįÍ/( Ëã3rx—’SØąk7ĪüâgwĖۜœŧ|Čsüßju<]*-=“ŌŌ˛ûŠ+„?%noēĢ… .ąpŪ\į´n]ģ°yëįŒ= OŪ|û] ?üüüP*Œ1œŋūí5đņõ†ÛšlPP .&søČ1į´Y3§ąyË6|}}ą”[Xēd!öFzĘˋā`Ū]ŊēvíÂŽ/v79öĖĖ›Ŧûčnfeŗė‰%dggŗ}į—¤ĨĨBrr 7ŗ˛î9~ͧ˛iķ6t:-ĨĨe,˜į¸VŦŅhødãĻF_÷ööŠcũ'đņöĻĒēŠ‘Ããî˜ŗ%ėu;U!D“ö&ū@ZZQQQ- š˜”BxHāũŒëŗúÃu 4G;uj르ēĖė<V¸îŽįŦSGÚE„ąį@B‹ōŧö/´thBņ@kI\ąbEÛŨŊ,^É)WINšÚÖÃBˆ‡Ž4Ũģ´ü‰%m=!„šú‰TB!ď‰4]!„ÂM¤é !„n"MW!„p—4]J…ĩĸÂĄ…‹h5šģxlĮŊąßŠ/„?u.š{92<ˆôĖ\ ‹J]^¸ĀŧY“Ų°y§Kv–t ķfMnõ¸Bņ°qIĶõŅiéÜŠŊ+B ęÛŗG[A!~Ô䚮Bá&Ōt…B7‘Ļ+„B¸‰4]!„ÂM¤é !„n"MW!„piēB!„›HĶB!ÜDšŽBá&Ōt…B7‘Ļ+„B¸‰4]!„ÂM¤é !„n"MW!„piēB!„›HĶB!Üä˙Ô{´ū'����IENDŽB`‚�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/scheme-otp.png������������������������������������������������������0000664�0000000�0000000�00000157316�14156463140�0021441�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��!��—���3}N›���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨwtTuūÆņg&“Ū{Bz ]@D@z/RėØÛęĪŽŦĢŽŽeu]t­(%Ō{QĻŌ¤(ŌĨĨQŌ{™™ßŅ˜�&3Ū¯srNîŊßų|?wįĖ3ˇĻL™b•¤ŒŒ …††jčĐĄĒ[ˇŽ<<<����Wĸ  @GŽŅ‚ túôiųûûË$Iššš ÕSO=%ƒÁāė>���\%<<<Ô¤I5nÜX˙üį?•••%ŖTB† B����P% ƒ ¤ŧŧŧŌb6›Õ°aCg÷���ā*Ö A—†ĢÕ*WWWg÷���ā*æáá!‹ÅRB����ĀQ!����Š���ĀĄ!����Š���ĀĄ!����Š���ĀĄ!����Š���ĀĄ!����Š���ĀĄ!����Š���ĀĄ!����Š���ĀĄŒĘÎÎvv����ŽŲŲŲ2úøøČËËËŲŊ����¸xyyq:���p­˛Z­š?žž˙ūûķŽŲ˛e‹f˘!ŗŲlˇyĢui虆Žc÷ąW“+Ųīū›ęÅÄ*9%ÅÎ]��ā¯`ņâÅÚšs§VŽ\YaŲēuĢ–.]Ēũû÷+>>ŪnķViyåÕŠ^LŦZļ‰SAAAUN���ā5kÖLŽŽŽ’T.ˆlÛļMK—.•$FĩlŲŌnķVY)*.ÖÜų d0”“ŖeËWVÕT����.CŊzõ4nÜ8™L&Iŋ‘íÛˇkɒ%˛Z­29r¤bccí6o•…+W)==C7-=UhVü×U5���€ËTQYŧxą-€ >ÜŽDĒÂ2sVé9cˇŪ2QíÛĩÕļí?éĐáÃŽ]ûŨz 2\›ĩRģ]ôäĶĪ)++ëŠĮžĪŽ]ģ5ųŪÔ&Ž“bb[¨[ôčcO(!!ąR¯čŅĮT/&VYYYzúšÔžcW5iŪZCGŽŅŽ]ģ•ŸŸ¯_ú§:víŽĻ-Ûjø¨ąúeΝåę$&&éņ'ŸVĮŽŨÛBmâ:éÎÉ÷j׎Ũ—Ŋߡßuˇ­ˇ?*))QŊ˜XŸtÛ÷íĖ™3z~Ę?Ôĩ{/ÅÄļP۝u×=÷k×îŸ+õŪ���❧~ũú7nœ\\\$•^°n04|øp5kÖĖîķ™ė^QŌ‘#GĩyËVĩiŨJuëÔÖ°!ƒĩuÛv͎ŸŖgžzĸĖØ­ÛļëÎģīUHH°ŧ˙^iķ–­ēķîûd4/{ėųüüˍžyĸüu뤉 Ņņ'4ũĢÚ°q“V-_ĸĀĀ€ Ö8wŪÜ}>ĸöíÚęŗO>Ōž}ûõĖķSt߃¨qã5lĐ@ũ÷?JHLԓO?§Ûî˜ŦMëר^›”œŦ!ÃG)ŋ @7Ŗ˜ ”rō¤žœ1SŖĮMĐôĪ?Qûvmíļߕ‘š–ĻĄ#Æ(++KãÆŽVLLC%'§č˯fjôØņúüĶ˙ŠC\{ģÍ��€ę#;;[‹ÅļlĩZ•““S%sUI™9ģô(ČČáÃ$Iũû÷Ķß_zEķ,Ôã=*ˇŗÄ%éũĻÉbąhÚŪSËÍ%IŖGĐķSūĄ­Ûļ—Š{)cĪg×îŨjØ žžyę uėg[Ž)˙xY‹—,ÕÄ 7_°†ÉĨômĢSģļŧ˙^IRĶØ&ZģnŊ–-_Ą–-šë‰Į˙&IjŪŦŠļnŨŽĪž˜ŽŨ?˙ĸļmZK’Ūz{ĒRĶŌôÁ{SÕįÆlĩûÜxƒúöŦžö†æ}=Ķnû]oŋķžRNžÔÜø™jŅü÷Ä;dđ@õši ^yõ_Z8Ķę���Ž6ģvíŌ… m§` ™Íf-_ž\’ÔącGģÎg÷ĶąŠŠŠ4wūšģģëĻ›úJ’|ŧŊÕ¯ĪJOĪĐĒUߨÆZ,mŪ˛UŅĩjŲ>\Ÿ3fôˆ2˗2öBƏĢÅ æÚHqqą Õ A}IRBbåNɒ¤>7ö.ŗ\ˇNmIŌ 7\_f}Ŋzu$I§NŸ–Tš*W¯ūV!ÁÁēąw¯2cÔ¯¯6­[iįŽ]JOΰÛ~_ŒÕjÕ˛+Ô¸QŒ"#"túôۏĢÉUmZˇÖĪŋėQnnžŨæ��€ķíŪŊ[ ,Åbą]2fĖÛŠY˗/׏?ūh×9í~$dųŲ Ō‡ (_ÛúÇjūÂEšũõ čßORé‡ōÂÂBEG×,W§~Ŋze–/eėÅĖ_PÚĮŪ}û•]f[IIåÂVfųÜ*"<ŧĖzWS鑟’âIŌéĶg”“Ŗf͚Ę`0”Ģ[¯n]mÛū“Ž=Ǎ¨Hģí÷…¤ĻĻ*==CééęĐåēķŽKJNRà ė6/���œįįŸļƒÁ aÃ†ŲŽ=z´fĪž]%GDėBÎ]ŪĄCœŽ;f[Ąā`múū?qBŅĩj)?ŋôŲ!înîåę¸ģ—]w)c/äõ7˙­Ļ}¤æÍšęšgžT­š5åææĻéŠgžĢtIļ;Tvũ9yųĨGŧ<=+ÜîáQē?yyųvÛī‹ÉÉ͕$5iŌX˙÷ˇGÎ;.<,ėŧÛ���đ×røđa™Íf  :T͛˙~æMŖF4jÔ(͞=[‹EgΜąÛŧ&ƒÁXáˇņ—ãˇßŽhËÖm’tÁôņ_ĪÕc>lû°]XTXn˟Oûš”ąįSXX¨O>ûB‘š1ũsy{{ŲļũųˆHUōöō–$ååįW¸=/¯tŊ—]ö[*=íėB|ŧŊmŋwŋŽ[Ĩë��ā¯kđāÁ˛X,ĒW¯^…#lܸąFŽŠÃ‡kĀ€v™Ķ`0Ø÷HČš ŌGŽnŨē–Û^XX¨ĮŸxZ_Ī§‡ŧ_Ą!!ruuՉå¯ÃØˇ™åK{>§OŸQaaĄš7oV&€HŌæ-[+UÃBCCäīī¯C‡ÛnöGĪŪʸ^Ũēōōōē¤ũ6=õ̏¤¤ĖúšũpHHˆtøđoĘĘʒŸŸ_™íŠii  ēøÎ��ā/ãÜ)X[}Ÿrî‚t7WW=öˇGtSß>å~†¤{÷ŌéĶg´öģu2™LjĶē•Ž?^î9ĶŋœQfųRƞOHH°$•{ȝ{÷jŪ‚…’*>âPúŪxƒNŸ>ŖÕß|[Ž—]ģvĢs§Žōķķģäũ •$>ü[™õķæ/ŧhO7õíĢĸĸ"}øŋOĘŦOMKSŋCtĮä{*ĩo���Ā…ØíHČō•Ģ”‘‘ŠÆ^đķIÆkÅĘ՚˙ĩzßĐK“īŧ]›ˇlÕwŨŖ‘#†)0 @›ˇnU~~A™ Û%]ŌØŠxxxčúžŨĩfí:=ķüuŒ‹ĶÁC‡ôŗ3ôö›˙Ōwß§ĩk×iŅ’ĨēáúžōōōēhÍËõđƒčÛĩëôčãOꖉãU¯n]%$&jú—3äííĨg˙đ<•KŲīaCëĢ™ŗôŌ+¯ęŠ'—§§‡VŗF;vî,wôįĪzđ>­ũnūķßuęÔiuˆk¯“§NiÆĖŲĘČČĐ-'TÉ{��€k‹ŨŽ„Øž>éÂT;ÄĩWŖ˜†Zˇ~Ŗ’SRÔŖûušúī7Ŧ?ũ\Ķ>úXÁAÁúāŊwäããŖâĸ߯e¸”ąįķÚ?_Ö ũĩråj=ûüm˙i‡>úīûęŅũ:=pß=ĘĘÎÖK¯ŧVåˇĸ Ķ‚šŗuSß>úzî<=ņôŗšūå uęÔAķįĖVãÆlc/eŋ[ˇjŠ×_}E…ēíÎģu×Ũ÷+=#C˙›öy{{̍¨čŧ=…kūœŲēyėmúū=ųĖsšöŅĮjŌ¤ąžžųĨēvé\Ĩī ���Ž †įž{ۚœœ¤>úČŲŊ����¸ĘŨyįöX!����\!���€CB����8!���€Ãü°õ'B����Į"„����p(B����‡"„����p(B����‡"„����p(B����‡"„����p(B����‡"„����p(B����‡"„����p(ãöu›t8ÃŲm����¸Vp$���€CB����8!���€ÃD׌"„����pœã I„����ŽE���āP&Ir‘t*•ûô���¨zĻ™äæ&…8ģ����×�c‰g°üŊœŨ���€k…1ĸ~ų:ģ ����× cŖ`“ŗ{����p áîX����Š���ĀĄ!����Š���ĀĄ!����Š���ĀĄ!����Š���ĀĄ!����Š���ĀĄ!����Š���ĀĄLö,VPX¤c‰)*,*–Ųlągéj)Čß[Ķ>™ŽœÜ<Y­Vgˇ0 ōõöŌˇŽWZ5œŨ��Ā_’ŨBHAa‘ö>.__oųųúČÅxudŲŊ÷ >ũüKYEø¸–X­Veeįč­w?Ô˙=|jFE8ģ%��€ŋ”NíÛØītŦc‰)ōõõ–—įU@$iõ7ß@ŽUƒŦV‹Ļ}úĨŗ;��øK˛[Z(((’—§‡ŊĘU{yyyÎnÎd0(3+ÛŲ]���ü%Ų-„XŦV {•Ē=Ž��¸<W˙yS����ĒB����‡"„����p(B����‡"„����p(ŖÕjá.?����Âjĩr$���€cB����8”É™“Oûčũ°yËĮL?V×÷čĒŪ“Æ)80@¯žũŸrÛęՉÖ#÷ŪĄŠ˙ũD;Zf[\ÛVęÔžjDFČÕÕ¤ŒĖ,ũ˛÷€žųnƒíÉŨĮWûÖ-/8˙ėy‹ĩņĮ­ēsŌ8ĩhÚ¸ĖļœÜ<%%§hŲęĩ:|äXš×žōü:yę´ÔĢSŠ9ĒSī���¨>œBúßÔG]ģt˛-ôņįĒY3Jũúôļ­‹ŒŒpFkՆÁ`Đ-ãFĒu‹ĻÚžķgmøaĢ Žî]:Ē]ëæzwÚgJJ9ŠUk6hķÖļ×N3\I)'õíwmëRNļũ~&5M3ž^`[öķķU׎íõĐŨˇéw?Ôņ„DÛļ‘ōöōÔÂeĢäîæVŠ9ĒKī���¨^œBjÖ¨Ąš5jؖŨÜ]åīī§ĻąMœØUõŌ­SœÚ´lĻĪgĖŅļģmë÷ė; ī7o×Ŗ÷ŨŠÛ&ŒÖËoŧĢ”“§”rō”mLqqą˛˛˛ĩ˙ĐoÖ.,,*wÄe×/{õÂĢGˇŽúbæ\ÛúÆ ëëDb’ŽO(3ūBsT—Ū��PŊ85„TÆË¯Ŋ.7W7=ūčCeÖO}˙efféš§ŸĐ=÷?ĸũû(9å¤vīūY……jĢ['MŸ¯$Él6kŅŌeÚŧy›RĶŌ¨>Ŋ{UûSŊztí¤}—ųNn^ž,]ĄģnšYMĮč—Ŋû¯xž’’%%ŸThpp™õbękßÁŠÁųT—ŪĪšŠwOõëŨŗÂmËW¯Õ˛Õk¯¸���\\ĩŋ0Ŋ{ˇŽúuī>ĨgdØÖę—=ŋÚNår1ĩlÅ*5nŖˇß|]ū;vB3gÅÛ^3{Î\­XąZû÷ĶKSžSŸŪŊ4cVŧÖmØXnÎĒf0äéáQîĮŨŨ­Ė8?_…†ißÁCį­ĩīĀaIRƒúuėÖ_pP 22ŗlË&Õ¯[[ûŽtęŌû-[ŊVË+���ĮĒöGBÚˇk̝fÎ֏›ˇ¨_Ÿ%Iģvī–Õ*uˆkg­ŽKCIdD„zöčĻEK–iRAĄŦV‹ÖŦ]§ū7õU—Î%Iááa:zė„–._ŠîŨē:tŸĸ"Âõ¯Ÿžč¸�?IRZzÆyĮ—”(+;G~~—Õ‹Ņø{õõņV÷.ĸ9‹–ÚÖ×Ģ-ƒ¤#GWēnuéũĪ΅sGD ���ŽWíCˆģ››:ÆÅiĶ›m!dÛöjĶē•ŧ<ŊlãęDG—y]Q*..VFF†22ŗTRbVŗØØ2cš4ŠŅú UPP Ēߙŗū|Qõ9Q‘á1¸ŋmŲrö!’.F— Ö3 —õĀÉQzįÕ)eÖååįëĢøųļŖ’Ô8ρ9ĻŗšŌĩĢKīųcč €���8^ĩ!’t]ˇ.ZģnŊŽŸHPxx˜vũ˛GŪ;šĖw÷2Ëįîā”›Ÿ§ü‚IŌkoŧ%ƒ ļ1į>(gff94„TtQĩ$™-–2Ë™’¤ā €ķÖr5™äëã­ôŗc/ÅŠĶŠú|æ×ļåĸĸb:“*˟úhÔ°ž~ÚõË%ÕŽ.ŊŸá��Āyū!¤nÚĒ]K[ļnSíÚŅōņōVl“˛wĐ:4lËųĨËŪž^*).ũ˙ŽÛoS͚QåęVQįW&'7OÉ'OŠMĢæZšf}…c5Ŧ/I—tŊÆ9ÅÅÅ:žtÁ1^žžĒU#J3į,ē¤ÚÕĄw���TOÆĖĖLåää8ģ‹ęÖĩ‹ļlÛŽ-[ˇĢsį2 eļī?p ĖōŅcGåæîŽ   E×Ē!W“IYŲ؊ŠŒ´ũøxûČ××WŽŽŽŽÜ•K˛vũ÷ŠŠWˇNqåļyyyjȀ>JHL>ī­l¯TŖ†õ”›—¯„¤äK~­ŗ{��@õ“““#Spp° ŨËEuę§ø9s•ššĻW^|ĄÜöŒŒLÍ_´X]:vTRr˛ž]ģNãÚÉÍÍU’Ģē_×U -–¯ˇęÖ­ĢÔ´4͘¯ Ā�=ōāũŽßĄJúaëOjP¯ŽF  úukk÷žŊ*,,RdD˜ēwé(ƒÁ ?ûĒĘæoÔ°ž\fHpvī���¨~üũũ˙§cI’ˇ——7j¤‚‚…‡‡•Û~Ũu]”—›§_yUÅEÅjÕ˛…n;Úļ}ėč‘ōōōRüœyĘČĖ’ŋŸŋZˇjŽÆ8r7.ËôŲķ´w˙!uéØNc†’ĢɤôŒ,ũ´ë­^ģ^9šyU6wã†õĩę<§SU†3{��@õd˜2eŠ5))IĶĻMģĸB;öTˆP;ĩU^VVļęYŨ~ËDÅĩo[fÛũ˙M7ŪĐKƒÜTeķ˙Ų?^{ËasĄúz÷_/:ģ��€ŋ”É“'W˙#!99šJ9uJ3g­Q‘j×ļŗ[���pĒ}Ų¸é{͙ŋ@1 ęö['–ģ ���Ā_Kĩ!}ûôVß>Ŋ/8æŊˇßtP7����Ž”ŅŲ ����¸ļB����8!���€CB����8!���€CŲ-„ YŦV{•Ē=w‹��¸,v !žnĘÉÉŗWšjĪËĶS"t]ģŦVųûų9ģ ��€ŋ$셐Ú5"”“—¯ėÜ<™-{•­ļz]ßSâÁ‰×.ŖA“oīė.���ū’ėö°Bw75Ž­c‰):š.ŗųę"Ą!ÁúÛũ“õá§_);'ĮŲí|}ŧ5ųļ ĒáėV���ū’ėúÄtw75ĒmĪ’ÕŪ+Ī˙Ÿŗ[����ūR¸;����‡"„����p(B����‡"„����p(cFVޞs¯į{����p.S€ŸōrŧœŨ���€k§c���p(B����‡"„����p(B����‡"„����p(B����‡"„����p(B����‡"„����p(B����‡"„����p(B����‡"„����p(“=‹éXbŠ ‹Še6[ėY���@5ĐēiÃ+ŽaˇRPX¤ũ‡Ë××[~ž>r1^ÛYSNÛå���T;ö´K셐c‰)ōõõ–—§ŊJ���¸ ŲípEAA‘ŧ<=ėU���ĀUĘn!ÄbĩĘh0ØĢ���€Ģ”éwßWIHkMsv'����Ž ×öÕã����ÎôĐŊw+éäIg÷���āa”‹‹ŗ{����p áš����Å5!����Š���ĀĄ!����ĘäėŪyīíØšK÷NžSqíÛ–Ų–™‘Ї{BO<ū¨š4ŠqR‡Žõág3ôķ¯ûÎģŊeķXŨ1aL•Íŋ`‰>Ēgūvŋ$é‰)˙TĪnÕˇW÷*›���×§‡I2 ŠŸ3W-[6—ģ››ŗÛqēā@6¨Âmžž>î���°¯jBZĩlĄŊûökŊÕ<¨ŋŗÛq:w7w5jXßŲm����UĸZ„OOO Đ_ķ.VˇntŪą™YYš?WŋîŨ¯Üŧ\ĒWĪēņ†ëmcxôq ė×W‰ÉÉÚūĶY,]×ĩ‹úõŊQŸ~ūĨ<$ww 2@Ũēt–$™Íf-ZēL›7oSjZš‚‚Õ§w/]ßŖzž†´mįn}1sŽūīĄģU3*R’ôÛŅãú÷ū§Û'ŒQĢæąZ¸l•ž]ˇIS_û{…52ŗ˛5ãë:pøˆ<=ÜÕĩcû ĮYĖfÍ]´\[~ÚŠââ5‰Š¯q#‡ČÛËĢĘö���W¯jqaēÅbŅ ŊŽW`` f=˙‚c?ųė úí7Ũ}×íúĮ ĪĒß>š˙ĩ~ÚąĶ6ÆdtҊUߨU˖z÷ßohäđĄZąęŊõÎ{pSŊ÷ΛęŌĨƒĻ5Sššy’¤ŲsæjŊÕØŋŸ^šōœúôîĨŗâĩnÃÆ*Ũ÷ŠX­V—”TøcĩZ%IíZĩPŗ&1ŠŸŋDVĢU‹E_/XĒÖ-šĒUķXIRDX¨š59˙ĩ4_˚̤”“ēįļņzđî۔“—§?˙ZnÜ[’ÕjŅ}wLÔøQCuāĐ͞ˇ¸jv���WŊjq$DVÉdrŅčQ#ôÎģī놞ŨÕ°aƒ ‡Ž=JŖQaĄ!’¤ˆˆp}ûŨ:ũ˛įWĩiŨĘ6.ēVMĩnŲ\’Ô1ŽŊ>Ÿ>C ęÕUƒúõmë/YŽ””EEEjÍÚuęS_uéÜQ’ĻŖĮNhéō•ęŪ­kUî}9I)'õčĶ/V¸íņ'+ēf IŌčaƒôōījķļ**.VzFĻîŊcĸml‡v­ÕĄ]ë ëddféĀĄß4jČ�Å4¨'I9¸ŋö8\nŦŸ¯¯F .=M.ēf %$%ëÛu›TT\,7W×+ÚW���\{ĒG9ĢuËæjŪŦŠžœ¯žyĒÂ1îîZ˛bĨöîÛ¯ėėY­Våææ*",ŦˏˆˆÛīžžž’¤ČČČ?Ŧķ$ååįëØņ•”˜Õ,6ļL&b´~ÃFČÃÃÃ.ûXĄ!Aš8fx…Û"ÂBmŋûûųjč€>Z¸l•Ė‹F(_īJÍqōÔiIRíčļuƒAujÕԉ¤ä2cë×­]fšníhY,t&5MQᕚ���8§Z…I3z„ž{áÚ°i“Z5o^f[I‰Y¯ŋ=UVŗEãÆŽRdd¸\ .zįũĘÕ1š–ß5“Šü:ĢÕĒü‚IŌkoŧ%ƒ ļm–ŗ§>eff94„¸šēŠNt­Jm×ē…æ.^.Ŗ‹Z6kRé9 ‹$IŽ:’áî^ūîdį›mŒ[ékŠŠŠ*=���pNĩ !5"#ÕĢgw͝ŋHM5*ŗíˇ#ŋ)!!QOũßcjķûéZYŲŲ šė9ŊÎ)šëöÛTŗfTšíAA—]ģĒ-]šF~~*1›ĩlõZ ę×ģR¯s;$ōķ ĘŦĪû͞T>lž]vãvĘ���¸ Ƈ¸Ocģ×qve 4P‹YËWŽ.ŗž¸¸D’äãķû]™>Ŧ3gRe•õ˛į‹ŽUCŽ&“˛˛ŗiûņņö‘¯¯ošŖÕÅņ„D­ŨøƒF¨QCčÛu›t<!ŠR¯ ?{MMbRŠmŲlÖÁߎ”{øČą2ËĮN$ĘäâĸĐāķßÅ ���8Ÿjw$D’ŧŊŊ4tđ ͘_f}­š5åęęĒÕßŦՐAũ•˜¤¯į-PŗĻąJN9ŠĖŦ,ųûų]ō|žžžę~]W-X´Xž>ŪĒ[ˇŽRĶŌ4cVŧ‚ôȃ÷Ûk×*Ĩ °Pŋî?Xá6ƒÁ &1 d6›õUüĩkŨB ëו$ĩlÖD_ÅĪ×˙=tˇ\\\´eûNíŪŗWwL[ŽNP`€ęD×ŌĒĩë$_o}ˇņG™\\ʍMMK׊o׊]Ģ:“šĻ?lUĢMĢm8��@õV-Cˆ$õėŪMkŋ[§„ÄßŋŲ÷ķķÕíˇLԜų ôũ?ĒNíÚēķ։JKĪĐĶ>ÖŋŪx[/ŋøüeÍ7vôHyyy)~Î<edfÉßĪ_­[5׈aCėĩK•–š–Ž>ž^á6ƒÁ Š¯ũ]Ģ×nPFf–˜|‹mÛđA7éĨ×§j՚õę×ģ§’OžŌî=ûÎ;Ī-7ÔŒ¯čÃOŋ’‡‡‡ēvj¯¸6-ĩķ—ßoĶk1[ÔëúîJMK×ëS˙Ģâ’5mÜPŖ† °Ûū��āÚb˜2eŠ5))IĶĻMģĸB;öTˆĐ‹ŧF$ϜVëĻ Ũ���`7;öŧâΏ“'OŽ+���pí „����p(B����‡"„����p(B����‡"„����p(B����‡"„����p(ģ…ƒÁ ‹Õj¯r����ŽRv !žnĘÉÉŗW9����W)셐Ú5"”“—¯ėÜ<™-{•���p•1Ų̐‡ģ›׏ֱĝNM—ŲLŪÕŋ�� �IDATŲąį ŗ[����Ēģ…Š4ˆ4ĒmĪ’����Ž2Ü ���€CB����8!���€CB����8!���€CB����8!���€CB����8!���€C3˛r”›įė>����\#8���ĀĄL~>ĘËņrv����Ž ���āP„����E���āP„����E���āP„����E���āP&{+(,ŌąÄËl墨4����'ps5ÉĶÃ]5#ÃäæjŸø`ˇRPX¤ũ‡Ë××[~ž>r1råZ˜rZ­›6tv���¨"EÅ%JMĪÔžCĮÔ¸Amģģ%…c‰)ōõõ–—'���¸J¸ššŦĐā�%$Ÿ˛KMģĨ…‚‚"yyzØĢ���€j$8Đ_ų…vŠeˇbąZe4ėU���@5âæjRQq‰]jqŪ����‡"„����p(B����‡"„����p(B����‡"„����p(B����‡"„����p(“ŗxįŊ´cį.Û˛››ĢB‚CÔŦYŦúôîĨā  2ãīøoēņ†^4ā&ģõP5˙ėÜ~Ū;ųNÅĩo[f[fFĻzė =ņøŖjŌ(ĻĘz¨N>ül†~ūußyˇˇlĢ;&ŒŠ’š\M&(ļQCõėÖYūv™įbž˜ōOõėÖY}{uwČ|���ՕĶCˆ$………ęÖI$I…:q"Që6nԆ›ôđ÷ĢQLÛØ1ŖF¨fT gĩzEŒFƒâįĖU˖Íåîææėvœ.$8Pc† Ēp›¯¯Ũį7bˆ$Ѝ¨X IÉú~Ëvũ°õ'Ũ}ëxÕ¯[û’ę­˙~ŗŽHԄŅÃėÚ'��Āĩ Z„ww÷2G�ZĩlĄŪ7ôԛīŧĢ÷?ø¯^ûį?äéá)IęÚš“ŗÚŧb­ZļĐŪ}ûĩbÅj ÔßŲí8ģ›ģ5Ŧī°šÖ¯k[nÚ$F=ēvŌ>ūB˙ûbĻ^xōy¸ģWēŪ‰„¤Ēh��āšP-BHE<<<tËÄņzæšŋkĶ÷›uÃõ=$•?uj˙Cš;N$$Čbą*ēVM :D5”$Ũs˙#Đŋ’SNj÷îŸUPX¨ĻąąēuŌųįÛöĖŦ,͎ŸĢ_÷îWn^Ž‚‚ÕĢgŨxÃõ’¤—_{]nŽnzü҇Ęŧnęû(33KĪ=ũD…u===5x@Í_¸XŨēuRP`P…ã*̓$=đčãد¯““ĩũ§˛X,ēŽkõë{Ŗ>ũüK<xHîî:d€ēué,I2›ÍZ´t™6oŪĻÔ´4ĒOī^ēžGõ<EhÛÎŨúbæ\ũßCwĢfT¤$鎪Įõī˙üOˇOŖVÍcĩpŲ*}ģn“Ļžö÷KĒíîîĻą#ëå7ŪÕæm;ÕŊKIRvNŽæ/YĄ‡~Sn^žüt]įŽęŅĩŖ$é˙~ĸCŋ•$mŲžSO<|üũü.øšs,fŗæ.ZŽ-?íTqq‰šÄÔ׸‘CäíåUŠš%éđ‘cZŧâ%&§ČbąĒfT„öŊA ęÕ)ÃbҊožĶö]?+-=S~ęŲ­ŗēuŠģä÷�� *TÛ"I5"#ŽũûØBČęíŠīĢC\;MšxŗdĩęÛ5ßéßīŧ̎^UŪŪ^r1ĩlÅ*=RˇMš¨“§Nęõ7§jæŦxMžķļ įũäŗ/”œrRwßuģüũtāā!}6ũK…ŠMëVęŪ­Ģ>ūô Ĩgd(0 ĀÖË/{~ÕØŅ#Īģ?‹E7ôē^߭ߨŲ_Ī×=wŨ~ŪąëA’LF­Xõ&Œ§[&ÜŦīÖoĐįĶghīūš0n´ę×ģGķ.Ōô¯fĒMĢVōööŌė9sĩnŨFM?N ę×ĶžŊ{5cVŧ\\\ÔŊ[×Ę˙qėĀjĩǏ¤¤Âm& ĩkÕB?íüYņķ—č‘{īÕjÕ× –Ēu‹ĻjÕ<V’ĒfM.īZšˆ°P…†ëĐoGl!äĢøų:yú´n7R~žž:|ô˜fÎY¨ @ĩhÚDwŨ2NīNûLĄ!Á9¤ŋŧ<=ôág3.øšs~Øú“Z6kĸû3Šéš9gĄfĪ[ŦÛÆŽÔÜEEEúī'_ĒmĢæ3|dĩjŨ÷›õŸ§ëĨg“—§§æ/YŠM›ˇi˰Ē[;Zû֜EËdrqQ§¸ļž���ŽT­Cˆ$*#+ŗÂmŠéiĘ/ČWįNqĒYú-ųÍcG+Ž};™L.ļqŅŅŅļ͏"#"ÔŗG7-Z˛L“ åáQūœqŖGÉ`4*,4D’Žoŋ[§_öüĒ6­[Š}ģļújælũ¸y‹úõšQ’´k÷nY­R‡¸vįßĢd2šhô¨zįŨ÷uCĪîjذA…C/փmßjÕTë–Í%IãÚëķé3Ô ^]5¨_ßļnņ’åJIIQTT¤ÖŦ]§ū7õU—ÎĨßŦ‡‡‡éčąZē|ĨÃCHRĘI=úô‹n{üÁɊŽYzíĪčaƒÎ­ØĄĸâbĨgdęŪ;&ÚÆvh×ZÚĩžė>‚ü••c[>č&J’ÂBƒĩūûÍÚ{āZ4m"OF™L.ōņöĒÔkÎņķõՈÁĨ§âE×ŦĄ„¤d}ģn“ŠŠ‹åæęzŅ:i™*(,Tû6-*I9¸ŋÚļl.“ɤ‚‚Bmøa‹nėŲMqmK˙„†éxb’V­Ũ@��ÕBĩ!‹E.F— ˇE†‡+"<\Ķ>úD={tWŗØXÕŽ]K˙t‡Š:ŅŅe–kԈRqqą222^ŽŽ‡ģģ–ŦXŠŊûö+;;GVĢUšššŠ “$šģšŠc\œ6ũ°ŲBļmߥ6­[ÉËĶëĸûÔēes5oÖT_ÎŠ× Ī<UᘋõpNDD„íwOĪŌëf"ΞŌu’¤ŧü|;ž ’ŗšÅƖŠŅ¤QŒÖoب‚‚yxx\´{ ŌÄ1Ã+Üvîļ$ųûųjč€>Z¸l•Ė‹F(_oģõQúoė÷ģUģģģiõÚõ:pčˆrrsKßûŧ|……ŸˇFe_ķį āë֎–ŞAgRĶ~Ņ:a!Á Öį3į¨[§85ŽŠ¯šQ‘ļSą8*ŗŲŦÆ1eÃmÃúuôÖí*,,’ģ;7E���ÎUíCHJĘ)5m\á6ŖŅ¨§ŸxLËVŦŌē 5gŪjØĐÁęŌé÷sč˙|Áņš;Såæį•ĢYRbÖëoO•ÕlҏąŖ.ƒ‹Ūy˙ƒ2ãŽëÖEk×­×ņ ĶŽ_öčÁ{'WzŋƌĄį^ø‡6lÚ¤V͛_V’dr-˙'4™Ę¯ŗZ­Ę/($ŊöÆ[2Č`ÛfąZ%I™™Y !nŽnĒ]ĢRcÛĩnĄš‹—ËÅčĸ–Íš\ü—āԙTÛōfŗYīôš,‹F žIáaĄ2úđŗ¯ÎûúKyÍšPxŽģ›Ģ$Ѝ¨¨RuŒFŖš÷}ŗvŖ6mŪĻEËW+0Ā_úôR\ÛV*(,”$MöéūÂŋ˙ŗ˛sę~ūk‘���ĄZ‡*#3C͚ƞwŒŸŸ¯ÆŒŽ1Ŗ†+1)I+W}Ŗ>ūLQ‘‘Ē[§ô[įsžĪÉĪ/]öŽā¨ÅoG~SBBĸžúŋĮĘÜ8+;[Ą!!ļåēujĢvt-mŲēMĩkGËĮË[ąM*˙á¸Fd¤zõėŽšķŠIŖF—ÕÃĨō:{¤äŽÛoS͚Qåļ=¨:ZērüüTb6kŲęĩÔ¯ˇ]ę>rL™YŲjSBŽOPRĘI=|ĪíeŽZäääÚN‘úŗKyMQQQ™åÂŗËnnn•Žããí­!úhȀ>J9yJߎ˙^ĶgĪSDx˜<ΆČIc‡+ęGÉÎ đĢÔû��P•ĒíĶssķôų—ŗŦvíÚT8æÔé3úé:ŦĨ‰ão–ŅhPbŌīˇPŨā@™×=vTnîî *˙pqqé…Ō>>ŋ”C‡ëĖ™TYe-3ļ[×.Ú˛mģļlŨŽÎ;Čh4čR 4P‹YËWŽžė.Et­r5™”•­¨ČHۏˇ|}}åęęzŲĩĢŌņ„D­ŨøƒF¨QCčÛu›tܡČÍËĪ×ėy‹ Ö-šI’JÎ^(īíåiwäØ ĨĻgČzžˇūR^søČą2ËĮN$ĘäâĸĐā JÕIMK×î=ŋ?x1"<Lc† ”Á`PrĘIՈŒÉÅEŲ9š  ąũx{{ĘĮĮĢÂŖd���ŽV->‘jīūŌ `.)҉„­ūæ;čo<(×ķ|pJKKĶ{˙ų¯F Ļ–-›Ë ƒ~ØŧEƒQ ęÕŗËČČÔüE‹ÕĨcG%%'ëÛĩëÔ1ŽÜÜĘčŽUŗĻ\]]ĩú›ĩ2¨ŋ“ôõŧjÖ4VÉ)'•™•%ŋŌo“;uŒSüœšJMMĶ+/žpÉûíííĨĄƒiÆŦøËîáRxzzĒûu]ĩ`ŅbųúxĢnŨēJMK͌Yņ Đ#ŪÉ5¯DAaĄ~Ũ°ÂmƒAMbČl6ëĢøj×ē…í9-›5ŅWņķõŨ-mŲžSģ÷ėÕĮžwŽÂĸB<|D’Tb6+)ų¤žÛøƒ ‹Štß“dr)Ŋî¨FT„L&“žÛøŖúõ”“Z´|ĩĮÔ׊Ķg”“+_oyyy(!1Y IÉ đĢÔk¤ŌąâÛuj×Ē…Î¤Ļiã[ÕĒESšēēVjîôŒLũī‹™Ü˙F5kŌH´uĮ. Õ­-wuéØNKW­‘ˇ—j×ĒŠ´ô Í]´\ū~ēûļņöü��\–jBN:­×^KRéSÅüÕŧyS ėßW!Áįŋ¸qŖŨ~ë$­Xõæ/\,ŖŅEQ5"õĀŊ“Ë\p~Ũu]”—›§_yUÅEÅjÕ˛…n;ēš~~žēũ–‰š3ž˙ņGÕŠ][wŪ:Qiéú`ÚĮú×oë埗$y{yŠqŖF*((PxxX…õ.Ļg÷nZûŨ:%$ūūÍūĨôpŠÆŽ)///ÅĪ™§ŒĖ,ųûųĢuĢæ1lČeÕģŠiéúāãén3 šúÚßĩzíedféÉˇØļ t“^z}ĒV­Y¯~Ŋ{*ųäŠ2G*r&5]S§}jĢíīįĢØÆ1ęÛĢģümã|ŧŊ5~ÔP-ZžZ[ļīTt­š0z˜22ŗôé—ņš:íS=ķˇûÕŖK'}>kŽūũū˙tĮÄą•zÅlQ¯ëģ+5-]¯Oũ¯ŠKJÔ´qC2ā’æ?j¨ÖŦ˙^KWŽ‘‹Ņ¨ˆđ0Ũ5iœÂBK˙¯ ØOžZ°t•2ŗ˛åįëŖæMkP_ûœÂ��pĨ SĻLą&%%iÚ´iWThĮžƒĒzņöį‡ÚSVVļęYŨ~ËDÅĩŋ6o}š˜rZ­›6tv���p€{^ņgŋɓ'W#!599šJ9uJ3g­Q‘j×ļâkV����”Gš 7}¯9ķ(ĻaCŨ~ëÄKž ���¸–]õ!äŊˇß´{Íž}zĢoί���.GĩŊE/���€Ģ!���€CB����8!���€CB����8”ŨBˆÁ`ÅjĩW9����ÕHQq‰Ü\íss]ģ…O7åääŲĢ���€j$5=SžîvŠeˇRģF„rōō•›'ŗÅb¯˛����œ¨¨¸D)§Ķt*5C5#ÃėRĶn+ôpwSãúŅ:–˜ĸĶŠé2› "׊{:ģ���T7W“<=ÜÕ¤AmģŽe×'Ļ{¸ģŠQŊh{–���p•áîX����ʘ‘•Ŗė\.(���ā ���āPF/7y¸š:ģ����׎„����p(B����‡"„����p(B����‡"„����p(B����‡"„����p(B����‡"„����p(B����‡"„����p(“=‹éXbŠ ‹Še6[ėY���€¸ššäéᮚ‘arsĩO|°[)(,ŌūÃĮåëë-?_š9Ȃę)1å´Z7mčė6���ūŠŠK”šžŠ}‡ŽŠqƒÚv "vK ĮSäëë-/O���p•ps5)2,XĄÁJH>e—švK Eōōô°W9����ÕHp ŋō íRËn!ÄbĩĘh0ØĢ���€jÄÍÕ¤ĸâģÔâŧ)����e×ģc���pĢÕnjŒ egg̍¨H§NRII‰ĸĸĸd4åââ"oooųûûËÕÕÕŠŊB���€ŋ8ĢÕĒŊ{÷*%%E~~~rqqQffĻ,‹rrrdĩZeąX” ’’ÅÄÄ(22ŌiũB���€ŋ¸ÜÜ\Ĩ§§ĢcĮŽrww—Á`PĢV­lÛ­VĢŦVĢĖfŗ’““ĩoß>ųúúĘĮĮĮ)ũWÍŸŠ… æ;er����WîĖ™3 •‡‡‡\\\d4e0l?įNĮrssSPP<<<TPPā´~MÁÔŊ…Ųi ����¸2ÅÅÅōđđĄwĢuss“Éd’Åbq@g3F5ĒŖ�//§5����āĘdff*??ŋ\™={ļfĪž]fÁ`PjjĒrrrŲbĻ_ŋYĸĖčp§5����āĘxzzĘÃŖėƒÃŋøâ ­ZĩJ’TXX¨‰'J* !žžž2™œwy¸ŠK7íÜŋĶi ŧķŪÚąs×yˇˇkÛZ÷ß3ŲnķŨ˙đßtã Ŋ4hĀMvĢY•uĪgŪ‚EÚžc—žæ šģš9dÎĒ2ũĢ™Úˇ˙ ^~ņų‹ŽŸ3O{öîĶ3O<.77ĮßZîˇŖĮ4íãĪĩ˙ĀAy{yŠ{ˇ.ēmâÍ2™\lc.YŽ…K–ëLjĒÂÃÂ4fäPõęq]™:•��PYžžžrûÃgÂéͧkÕĒUzéĨ—$IĪ>ûŦ ƒ&L˜ Iō÷÷—ˇˇˇSz•$SĶ.7*¨^˛Ķ¤°°PM?ŽÂmū~žvkˍĒUãŠë|ŗæ;9zTwŪv‹]ëVƯ{÷iåĒoôüsOũåČĨ>t°öí? ŗâuËě:÷é3ŠzōŲÕžmkMšōŦ’OžÔ}*“ÉEˇíeųĒoõŋĪžÔ¤›Į¨QLíúųŊųÎäíåĨŽqí*=��āRüų¨Fbbĸ^zé%ÕŠSG’ôŌK/iÖŦY’Jī”uîbug1ŲįÁëWÆŨŨ]Mc›8dŽŽ;ŲĨÎącĮ̤îÅX­V͘¯Ž]:Іīíė,...5b˜^{ãßēĄWÕŦá˜ā'I_Ī[¨Čˆp=öđ}2 ŠmŌHA*))ũ_dĩZ5{Î| ŧéF:P’ÔŧiHHÔŦ9ķÕ1Ž]ĨÆ���\*777effÚƓO>Yf{:ul늋‹URRâÔšV˙|BuŧĒCš°6oŅGĒž}ZĩŖkI’:Ŧ—_}]÷Ũs—ÚˇmŖ{îDú÷QrĘIíŪũŗ Õ46VˇNš ?ßŌ{ ˙ų´Š~Lû÷Ķ/ŋîÕŪŊûôÎŋ˙Ĩââ͎ŸĢ_÷îWn^Ž‚‚ÕĢgŨxÃõ’¤W_Sûö”$múūGũũųgôú[o—Š[\\ŦšķiËÖmĘĖĘR€ŋŸ:uė ĄƒĘÅĨôԝ}\oę§´´4mŪ˛M…jĶ@ˇN/˙�˙ ߇ģv+!1I?xŸmŨū‡4wūHHÅbUt­š>tˆ7j(I2›ÍZ´t™6oŪĻÔ´4ĒOī^ēžGw[â’-\´X›žßŦÜü|ÕŽUSŖF SÃõíē?éúôķ/ĩwß~yzzĒgnåöņbû͏QŒęÕ­Ŗå+VëÎÛoš„EWæûˇjÄЁe.øjŨ˛ší÷¤ä:}Fãڗy]‡ömõÆÛī+//_éãååYĩ;��Ž:~~~JHHPZZš|||lGFÎ}n9÷Œ‚‚>}ZŽŽŽåŽ!q$Sҝë´öL–͐Jߔĸĸâ ˇšēšd0ÔŠCœ6oŲĻé_ÍŌ3O>&ĢÕĒ/gĖRûvmÕžmI’‹É¨e+Viėč‘ēmŌD<uR¯ŋ9U3gÅkōˇUXßÅŤīÖoTĢ–Í5h@?šģškÚGŸ(9å¤îžëvøûéĀÁCúlú— R›Ö­ôāũ÷č_oŧ­đđpŨ<v´|ŧËß]lúW3ĩ}ĮNMŧyŦęÖŠŖÃŋŅįĶg¨¨¸XãF”$™Œ.Zžb•†¤7^{Y™Y™úûK¯iá’ĨšxžĶĶvîūY5kD)$8X’TPP¨ˇ§ž¯qí4iâ͒ÕĒo×|§ŋķŽŪzũUy{{iöœšZˇnŖ&ާõëiĪŪŊš1+^...ęŪ­Ģ$ivümŲēM7Ŗ°ĐP}ŗf­Ūø÷TũcĘs ąÛū|øņg:™rJ<xŸüüĩfÍwÚöĶųxûTz$ŠEŗfúfÍZ[Ú¯jŲŲ9JKO—ŋŸŸūõÖģÚžs—Ü\]Õ§÷õ7j¸ŒFŖ’JOkŒŒ({Ŗ‡sˉÉÉJKĪ¸č˜†õëUõî��€ĢŒ›››jÖŦŠ´´4%''Ëjĩ*##CfŗYÁg?7 ™L&yyy)22ŌšĻ=^ÉÉÎŊ&$!!QwŨû@…Û^xö)Õ­S[’4iÂ8=ķüßĩaĶ***RZZē}¸ėëĸŖŖm§FEFD¨gnZ´d™&ĘÃÃŊ\}ƒArssÕ¨ÃlëÆ%ƒŅ¨°ĐIRDD¸žũn~ŲķĢÚ´n%/O/.2™\lGXū(;'Gŋ˙QcFW‡ŗßx‡……*19YĢžYŖ‘ÇĘõė=22B×uí,I RËæMuäčąķžWÔô4åäĢs§8ÛéY7­¸öíd2š(??_kÖŽS˙›úĒKįŽ’¤đđ0=vBK—¯T÷n]•_¯u6iôČáęĐžôt [&ŽWaaĄN:%OOģėOZzēöîŨ§ 7Ul“Æ’¤ņãÆhΝû*Ŋ?į4lX_ķ-VRJŠCNKËĖ* ęŸNŸŠūũzkč ūÚŗoŋ>ũb†Ė%fM?Fyyy’TîH†§gér^^~ĨÆ���\ސ¨¤¤D‹EfŗY‹EŽŽŽļ‡珏Čd29äKÜ q^üųƒđđ0Ũuöī?‹Œˆ°ũ Ņ#‡kÎÜy2›-špķXųûų•_':ēĖrQ*..VFF†""*žqƒ?}ķėáîŽ%+VjīžũĘÎΑÕjUnnŽ"ÂÂ*ĩ?'N$Čbą¨~ŊēeÖ×ĢS[E……:yō¤íZ†Zĩj–ãååĨÜŗT+’™•Š€€�Ûrdx¸"ÂÃ5íŖOÔŗGw5‹UíÚĩÔ¸QŒ$ißūƒ*)1ĢYll™:MÅhũ†*((Pbb’Š‹‹Uįlؓ$W“ÉvW˛_÷îŗËū$'§”žŽnÛvƒÁ ēukëøņ„JíĪ9įNīĘĖČtH)1—>Đ3Ž]k>D’Ô°A=eddhÁâeš0nT•÷���p!VĢUŲŲŲĘÎÎVQQ‘N:Ĩ’’EEEŲž˜îíí-§^"U“âææĻú•<Ĩc‡8͜5G.&ŖÚļiUnģ‡{ŲŖįî•›ūöžŋ+]RbÖëoO•ÕlҏąŖ.ƒ‹Ūy˙ƒJõ'Igë–=ĪîÜyw……ļu—ú ?ŋĀö­š$F=ũÄcZļb•Ömب9ķ(8(PÆV—N•ļ—×ŪxKũžx-VĢ$)33KššĨīģ{ÅwÚ˛×ūœĢãęZöŸ‡ûīu/ļ?įx;øČ×Ų}˙öî;>Š:˙ãøkûn’MBé$Đ‹bŖ ĸ€ôb/ÜŠ?=ŊĶSÎĶ;ŽXÎvžŊ (ŌAšˆĸˆ€ ! $$$”„ôēå÷G`5HĀu7āûųxđ8vfö3Ÿ™]ÎyīĖwĻÍAŦsĮ˚ŗ€Ŋûövč6weeå„ūė eee�„…†P]]Ũā2""""'Ęëõ˛eËōōōĮd2QTT„ĮãĄ´´öGuĮCvv6.—‹víÚě5‰r"æÍ_Dŗf‘¸Ü.æ/\ˈáCëĖ?|Đí{]Qû:ÔҏƒģwūHvvūĶŊ´oׯ7Ŋ¸¤„͛7ĒÆáPSYqd/ĩĖö_0đØá°ûęîdôČáŒ9œœ={Xžb%¯ŧ6•„øxBõrˍ7””pTŊ¨¨fž3Göëīíą ˆGÔ)?í9|i^ŲĄ÷jwķčh, E%%uĻģ!1›Í$%Öîß=šyžKų�˛sö`4HJL äPđ8Ū2""""'ĒŦŦŒƒŌŗgOl6ƒnŨ~úÁŪëõú§įææ˛uëVœN'aaG-„āŨø$ėܕɊ•2qÂX&ŒÍŌå5~"}Ûļ:¯weîÂjŗÕ¨uÔÔÔŪ),,ė§Đ˛}ĮČĮ‹ˇîÂGŧ<,9) ŖŅHÆöuĻoßą‡ÃŪčËēęAaaĄīõžũøúg{LLH`âøqröė!še"ŗ™â’âã}ÂBÃp:X,âãbąY­lŨ–áĢãņxų×cOđyâ|Ž�� �IDATŲÚu~۞×ÃeíÎöMsšÜlMOoôöVTXTģ?"ę^Ž÷k1œ}fWÖŽÛPgúw›~ ,,”æŅQÄĮŒwÔ2Ÿ¯˙’Ž;aŗŲĩŒˆˆˆČ‰:pā�-Z´Ānˇc2™xėąĮČĖĖôÉĘĘâņĮĮjĩ…Ũn÷]Ĩ MâLHee%ß}ŋŠŪyFŖ‘.;áršymę[ôęŅŽ‡ÆœsöYŧ6õ-žü€oĐraaķ.ĸOĪžėÉÍåÏVĶŗûš~ēvˤ$, ŦüˆĢ‡\AvÎŪ›;Ÿ.;‘›ˇ—ĸâb"Âà !3+‹ĖŦŨDE5ĢS#,,”~}û°xé2bbcHn™DzzĢ>ú˜K/ŊØwKۓŅļmÛ~ xîų9|gžŲ>_ŋƒÁH›Ö­q8 čߗų á ĨUĢVä0}Æ,ĸšEr÷ˇãp8čס7‹ß_JTŗHâãųø“OŲĩ+‹¯KķÛö4Ž&-­5ī/YFlL œN'|¸ ķĪŪßĐö–ą}aaĄ$đ4â葸÷Īåéį^äâį“žm‹—.gÂØQžÁ]cF ãŠį^¤yt4;´eũ_ķÅWų×ßūâĢ͘eDDDDNDMM vģŨwL’˜˜Čäɓë<1}Đ A@íPŗŲŒĮã ZŋM"„ė߀'ŸyŽŪyFŖ×_~÷—.ãāÁƒüéwųæ=‚&?Ââ%KšzČ`�ú÷īCyY9ûįŋŠŠŽĄÛ™g0nˍF÷îäÆë&2{Ū|ÖŽ[GjJ 7_?‘‚ƒ…ŧđŌk<öÄĶüãoqņĀ xųĩ7øĮŖOpĮ¤[ŽĒ3~ė(voM{—â’bĸĸš1xđå žlĐ îēēŅ•ÕŸŦá@~>ÍŖŖéĐž7^-ËVŦdŪ‚E&ãšãwˇúÎ<Œ5‚f͞KaQ1áœÕ­+× ģÚWwä5Ã1 Ė|o.U••$%%r÷˙ŨNLL ŋnĪm7ßČëoNã™˙>=ÄÁ…úĶĢWOžūú€FmĀw›6sF—.ŊŗCûļmxdō}ŧ1í]V=ôw"#"¸nüX†]u…o™ô§ĸ˛’9ķ1íŨY$ÆĮņį?ŪÅ]:Đ2""""'ĸ¨¨§Ķé;6š0a^¯—É“'pÉ%—0a öÆ@ųųųDGGķ ŽĐų% <đ€777—×_ũÚ¸9ƒÄ¸~jëäų ÂĶ‘×ëeōÃSčØž-ãĮŽv;A‘žm;˙zė ūūČ_hyOLĪÉÛĪYÛ6ŧ ˆˆˆČ)bķæÍ„……‘’’RgúĖ™35ę§åkjjذa-[ļ$ųˆ;Ë6dãæŒ_|uë­ˇžZcB¤6šŽ9œO×Ŧ%7//ØíœÛífÖė9ôī×÷¤ˆˆˆˆČéČétÖ;ļtÔ¨QuČa„ēŗg0(„œ‚ētîÄ K.â/ŧLÕĄ[žūVĖˇW ãOā;‘ĶŨ‰<ũÜëõb00ƒšÄ˜yîé˙ģ…€võ†]=$Øm܈k†2‚Ą /("""ōbĩZ)**ōŒãŠŠŠÁårõ…:"""""rŠ §¤¤„‚‚ĒĒĒpģŨ¸Ũn<ˇÛMuu5ÅÅÅäååaąX|ž†ĶęLˆˆˆˆˆČo‘Õj%))‰‚‚rssņzŊâvģ‰ŽŽjĮ›ÍfBBBˆ?ĄK¸üM!DDDDDä4Đŧys"##qš\žŗ‹Åâ{hĄÉdÂl6ô1õQ9M˜Í栞áh, ‘€ō[1 xŧ^•‘&¤ēƅÕâŸŗ,~ !ģ•ŌŌr•‘&$˙`ûŅD<~ !)‰q”–WPRVŽÛãņWY ĸęyû ؗ_HR|Œ_júmԊŨfĨCZ2™9yėĪ?ˆÛ­ "MׯÍÁnADDDä”`ĩ˜qØmtl“âˇËąü:tŪnŗŌžu˛?KŠˆˆˆˆČiFwĮ‘€R‘€R‘€R‘€R‘€R‘€R‘€R‘€R‘€R‘€R‘€2Z­V,K°û‘ߝ ‘€R‘€R‘€2ûŗXeU5™9yTU×āv{üYZDDDDD‚Āj1ã°ÛHŠÁjņO|đ[ŠŦĒ&}GNg(áÎ0LFd‘ŖåäíįŦÎmƒŨ†ˆˆˆ4Ru‹üƒElŨžI‡6)~ "~K ™9y8Ą„…8@DDDDDNV‹™ø˜hZDG’ģĪ/5ũ–*+Ģ qØũUNDDDDDščfTTVųĨ–ßBˆĮëÅh0øĢœˆˆˆˆˆ4!V‹™ę—_jéē) (ŋŪKDDDDD‚ÃëõRXXHII ÕÕÕėÛˇ—ËEBBFŖ“ÉDhh(X,– öĒ"""""rŠķzŊlŲ˛…ŧŧ<ÂÃÃ1™Láņx(--ÅëõâņxČÎÎÆårŅŽ];âããƒÖ¯BˆˆˆˆˆČ)ŽŦŦŒƒŌŗgOl6ƒnŨēųæ{Ŋ^ŧ^/nˇ›ÜÜ\ļnŨŠĶé$,,,(ũjLˆˆˆˆˆČ)hŅģŨŽÉdÂh4b0|_ŽeĩZ‰ŠŠÂnˇSYY´~BDDDDDNq555Øív ¸[­ÕjÅl6ãņxĐYũBDDDDDNqEEETTTBfΜÉĖ™3ëL3 äįįSZZČëИ‘SœÃáĀn¯ûāđˇŪz‹+V�PUUÅĉÚât:1›ƒ‚~&ä™į^āē›nãũeËë_T\Ė ˇüŽënē ˇÛāîÄßnŋë.^°õ͝ŋ˙úwĒĒĢXąrüķdnēí÷Ü˙ā_ųėķuëåįŽÜūØ/ŋÖžöÎģ<øĐß�˜5{.ũû?ŠŽŽņûzcÁâĨÜp۝ 1Ž›7~üIPúijœN'6›Í÷zÚ´iŦXą‚)SĻ0eĘVŦXÁ´iĶ|ķ#"" FĢ@!�6Ģ•ĩk××;oŨ†/1™LîH~-ŖG^Ã]ēd]?lŲĘō+ųŨm7aŗZųxõ§Ė|o čĪŊwßEįōĘkSŲøÍˇéįxüą_ŽŦąrÕĮŧōúÔ_ØY]Ç^…ÉhdúŒY~­ÛKW|ČĢSßæōAķ‡'3 _oūķĖķŦÛđeĀ{ijŽ<Ģ‘““Ô)SHMM%55•)Sϐ““ÔŪ)ëđ`õ`i—cĩi“Ææļ°kWŠŠÉuæ­û|=ŠŠÉddėRwâO}{÷ Čzŧ^/ĶgĖĸoŸ^$ÆĮãõzYŧd)/Āå—^@‡ömÉÍÍcŅûK9ĢۙéëXüą_ŽŦ‘™™õ‹kÉd21ōša<úÄS\4đ|’ũžŽúxŊ^fΞĮ•—_Â5C¯ kįŽėÎÎaÆėyôė~n@úiĒŦV+EEEž€q˙ũ÷יŸššę›VSSƒËå ę ›D‰Œˆ eË$>[ˇŽNÉÍËcgf&C¯ē˛N)*.fæŦ9ü°%˛ō2ĸĸš1đ‚ķšäĸ ø|ũ^yí ū:ųR’[ą}˙ø÷ãü~Ō-œwÎŲGõpËī˙ĄCŽā˛A—øĻŊūæ4˛˛vķđ_� }ÛvæĖ›Īîėl</É-“>ôj:´o €ÛífáûKXŋūKō ˆŠjÆ ‹ráųŽší“nŋ›ÁW "7o/ß}÷=•UUtîԉ믝@¸3ŦŅuī¸ë^Žŧâ26ũ°…-[ļōĖSâŠŗ.ôßP†æß~×=\rŅ@† ž¨ũG0gŪB6|ņ%EÅÅDF„ĶĢg†^uĨī Øø#W^~Ŧßđ%••U´o׆ë'Ž'"2ĸŪũúͷߑŗ‡ģîü=�{÷îã@~gũė~Ų�ŨÎ<ƒ—_}ƒŠŠ ī͞ĮŌ+xũåę­Û˜īÉÉô{ä~šãäĘË.%'7—¯žŪˆĮãĄß>\vé%ŧņæÛddlĮfŗ3ôęÁôëĶû¨˙~ü?lMĪ�āŗĩëxäĄIJLhđķ=XXČožÍ–­é8.8ŋßQŊvhߎ֭RYēėnžņēzˇĮßöäæąo˙zv?¯ÎôįÃO˙ōō BBéEDD¤) ';;›‚‚ÂÂÂ|gFT?üŒĘĘJöīߏÅb9j I 5‰âņxč~î9ŦøpŖG ÷|~öųz’HˆĢŗüëSß"7o/ˇŨr#‘álËØÎÔioĶ<:ŠŗĪęF¯ŨYŋáKĻŊ3ƒīŋ¯×ËÛĶgpŪšįÔ@Ŗ˛˛Š§Ÿũ=ēŸËĩĮ×ˇĢ>æŠgū˓˙›ĐĐfΞÃęÕk˜8~,mŌZŗyËĻΘ…Édb@ŋžõÖ5™,Yļ‚1ŖFpÃĩŲģo/˙įYŪ1‹[ož QuM&3˛†ngveČā˰YmuÖãūĒa2™\Į‘ĻŊķ._mü†‰ãÆĐ*5•?îäÍiĶŠŽŠaė¨�˜&–.[ÁĐ̇đÄŖ˙ ¨¸ˆGĻ<ʂÅī3qüØz÷ë7ß}ORbÍŖŖČŨģ€˜Íë,wøuŪŪ}´JM!>!Ž3ēvmÔwâXNĻßúj,[ą’ ãĮrŨ„q|üɧŧ9m:[Ōˇ1aė(ŌZObL{į]ÎîÖí¨}{įí“x뉧‰eܘQ„…†0ãŊŲ ~^~m*{ķöq÷ŋ'"2‚UĢ>æË¯7Z÷AFgtéÂĘUų~mųĩeīÉ >.ļÎôïsrsi›ÖúWīCDD¤Š˛Z­$%%QPP@nn.^¯—ÂÂBÜn7҇އ fŗ™âããƒ:0ŊI„€ž=ē3gŪžÛôgŲ¯×ËēõĐ˙č_bĮމÁhô@ÆÅÅōáĮĢŲ´ųÎ>Ģö—îk'ŒåÁ‡áĶĪ>§ēēš‚‚ƒüáŽ;NēŋüƒTTVĐģWw=â~ܘQt?ī\ĖfŦúh5W\~)}z÷ 66†]™ģyéōc†€äädßĨ4ņqq\p~?.^Âĩ•UxŊžFÕ5Ājĩ0ōšaŋZ˙ Õ8PpüųG*)-eÍÚuŒ1œ‡~ᎉiANn.+VŽbÄđĄXũ㈏ŖßÚ_üŖšEqf×ÎėܕyĖ}š‘ąÃwö ˛˛�‡Ŗnâˇú âĐÃzúöîå—KŖN´ßú$ˇLâŦ3kQĪîįņæ´é´iŨŠ6iiži‹/%//´#ĀC!&ĖfáΰF}ž˛eËV&ŒC§Ž�?v4›ØzTomÛĻ1oá"öäåų>ë_SyyíįwäŲ‡Ãqh~ŝۃˆˆHS×ŧys"##qš\x<Ün7‹Åâ{hĄÉdÂl6äGÄãi2!¤EķhÚ¤ĨąöķuœufWļeėā|zœw.™™uŪė6‹—-gËÖtJJJņzŊ”••ã[ĻYd$ŖF göœš¸Ũ&ŒCDxøI÷K\l,/Ŋō:œ?€.:‘’Ō’íÛ°5=—ËM—Nęŧ¯cûv|ōé*++yĘ+5šî8˜ÄÄjjj(,,¤°¨¸ŅuÛį—`ôßP†æi÷îl<i­[ՙŪ:5…ęĒ*öîŨësвeReBBB(;t`ZŸĸâ""##9˙×vĸũÖ'.î§3€‡ļãvĀ8P•W4|�ž™•Ũāį›››@ëVŠžųƒV­RČĘĘŽķžÃ—•$„ˆˆˆHÃŧ^/%%%”””P]]Íž}ûpš\$$$øž˜JDDDPĮƒ@ !�={œĮĖ÷æP^QÎįë֓Öē1-š× !.—›ĮŸ~¯ÛÃØ1#‰Åd0ņĖ˙Žž†ŋgîŧ;c6&ŗ‘sÎîvÔüa4yāž{Y˛lĢ?]Ãėšķ‰ŽjưĄWҧWOß/é>ņ$~J–¯€ĸĸâc†ģ­îĨS6Ģ€˛ŠōĒkwûšxôsÜ ­ãH•‡Öi?âėÄáíŠĒŦōM;Ņ(•žwĀ7>ĻŧŧĸÎX™ŠCÁ ô8ûîdøãļŲrô?ĪúN›z}FĮ͘Ī÷đįa9bŊvÛŅßÛĐ�Ÿ;t Á˛˛rBC~úüĘĘĘÍ?úR?‘߯×˖-[ČËË#<<“ÉDQQ‡ŌŌÚí=ŲŲŲ¸\.ÚĩkWįĮÍ@kR!¤ûšį0}Æ,žüęžüúk† žâ¨e~Üų#ŲŲ9üųO÷Ōž]ßôâ’Z4¯{Ŋ˙ŧų‹hÖ,—ÛÅü…‹1|č1×]ß ŠęĒę:¯Ã݌9œŅ#‡“ŗgËWŦä•×Ļ’OČĄƒ˛[nŧ¤¤„ŖjEE5;æē ú^WÔžu„āĒqŸtŨ#ųŖ˙ãÕh•šŌāüŸ;š*+ŽÜūÚ[û/hėpØ}u ö27€}ûöûƉ�äæíÅh4wÄXƒciĖ÷¤)jĖįģ˙Āā§īßaõi);4-PƒÁ“k{Ū“›Wg\OvÎŒFƒožˆˆČoUYY¤gĪžØl6 Ũ~vC¯×뜞››ËÖ­[q:„……§ę¯§I<'ä°đp']:wbÉŌ唕•ĶãÜsŽZĻĻÆ@XØOŋ|nßQ{閗Ÿ~Ūš+“+?dℱL;šĨË?8î5ų샞#~ÕŨŗĮ÷÷}ûđõĪž'‘˜ĀÄņã0 äėŲCrËD,f3Å%%$ÄĮûū„…†át:ûËxúļmu^īĘ܅Õf#**ęÕũ9ôßP†æ)9) ŖŅHÆöēˇ_Ūžc'‡ŊÎåu'*"<‚ÂÂBß똘ÄÆÆđÕ×ë,ˇqãˇ´o׎ÎÃ}ާĄīI“sčŸDc>ßÃA,k÷O—^š\nļϧU€ˆˆ“ŋÄņDÄĮŒĮÚuęL˙|ũ—tíÜŠŅŸŸˆˆČéꁴhŅģŨŽÉdâąĮ#33Ķ7$++‹ĮĢÕJTTvģŨwD04Šĩ—dåíŨK‡íëŊiˤ$, ŦüˆÂÂB6mūiĶgŌĨs'rķöRT\;†âĩŠoŅĢGw:ļoĮ]ģpÎŲgņÚÔˇpšęęzjJK6nü–’ŌRj\.-YFiiŠo~AAĪ=˙"˖@n^yy{Yôū #mZˇÆáp0 _æ/\Äú _°o˙ļ¤oãņ§žáÕWXXÄŧ…‹Øˇo?ß|û~´šžŨĪÅjĩüĸē?įūĒŅĐü#………Ō¯o/]Æ×ß|ˁü|>[ģŽU}Ė%üEŠlÛ6mG„›!W\ÎGĢ?eņ’elM߯ŒYsøöûM šō§3nŸ}žŽ˙>˙Ō1ë6ô=iJBCCČĖĘ"3k7.ˇģÁΎyt4ii­yÉ26mūĖŦŨLö6æz>‡Œí; %!€§qĮŒƒå+™5gßoūW§žÍ_mdĖČáëADD¤ŠĒŠŠÁnˇûœ'&&2yōdvíÚÅŽ]ģ˜<y2‰‡ÆÚZ­VĖf3'hũ6ŠËą�Îîv&6Ģ•įÕ˙đąđp'7^7‘ŲķæŗvŨ:RSR¸ųú‰,䅗^ãą'žĻûyįpđāAūô‡ģ|ī;zL~„ÅK–rõÁGÕ3j¯N}“{î{°ú÷ëCßŪ=Ų´ų öŲ7^-ËVŦdŪ‚E&ãšãwˇú~A3j!!!˚=—Âĸb"Â#8Ģ[WŽvõqˇš˙>”—•ķˇū›šęēyãÆŒĒĶÛÉÔũ9ôßP¸¸Ø×q¤ņcGá°ÛxkÚģ—ՌÁƒ/gđeƒŊmõévFWV˛†ųųžË¯úôîIeUK—¯`Ū‚EÄÅÄđģÛnĻãĪÎīÉÉeã7ßŗnCߓĻäâđōkođGŸāŽIˇ4ę{tÛÍ7ōú›ĶxæŋĪcqpá€ūôęՓ¯ŋŽģOžÛ´™3ēt č5^ПŠĘJæĖ_Ä´wg‘ĮŸ˙xgtéÔđ›EDDNsEEE8Nß›'L˜€×ëeōäÉ�\rÉ%L˜0¨ŊņL~~>ŅŅŅÄü‚+O~ ÃÃ?ėŨŗg/Ŋtė_cãæ ãZøŠ­ßŽ#R'ūáõz™üđ:ļoËøąŖƒŨÎi%}ÛvūõØüũ‘ŋĐō$ž˜ž“ˇŸŗ:ˇmxAi´Í›7FJJŨ1¸3gÎ`Ô¨Ÿ~āŽŠŠaÆ ´lŲ’ä#îŌڐ›3~ņĮoŊõÖĻw9–ˆ? ƌΧk֒›—ėvNnˇ›YŗįĐŋ_ߓ """ōëp:õŽ‘5jT�rXDDĄ‡î> !rÚęŌšƒ.šˆ˙Ŋđ2UÕM˙V§‚šķRãĒaü˜Ŗ˙ĪLDDD‚įDž~îõz1 Á‹MnLČoÍsO˙'Ø-œÖ†]=„aW v§× eĮžÕĩˆˆˆ‡ÕjĨ¨¨Č0ާĻĻ—ËÔęLˆˆˆˆˆČ).<<œ’’ ¨ĒĒÂívãvģņx<x<Ün7ÕÕÕ“——‡Åb9惴AgBDDDDDNqVĢ•¤¤$ ČÍÍÅëõRXXˆÛí&úНB fŗ™âããOč.S9 4oۜČČH\.—īė‡ĮãÁbąøZh2™0›ÍŊÍ~}Ė………”””ĩ ųåĖfsPĪp4FII æČČHĘË˃Ũ‹ˆˆˆˆˆü8N˙ L7 xŧ^•‘&¤ēƅÕâŸŗ,~ !ģ•ŌRQ9å,Âa?úˆ'Ão!$%1ŽŌō JĘĘq{<ū*+"""""AT]ã"oûō IŠņKMŋZąÛŦtHK&3'ũųqģDD¤~7gģi$ĢŌÃnŖc›ŋ]Žåץķv›•ö­“ũYRDDDDDN3zb爈ˆˆˆ”Bˆˆˆˆˆˆ”Bˆˆˆˆˆˆ”Bˆˆˆˆˆˆ”Bˆˆˆˆˆˆ”Bˆˆˆˆˆˆ”Bˆˆˆˆˆˆ”Bˆˆˆˆˆˆ”Bˆˆˆˆˆˆ”Bˆˆˆˆˆˆ”Bˆˆˆˆˆˆ”Bˆˆˆˆˆˆ”Bˆˆˆˆˆˆ”Bˆˆˆˆˆˆ”Bˆˆˆˆˆˆ”ŲŸÅ*ĢĒÉĖÉŖĒēˇÛãĪŌ"AĶąMJ“ų^›LFlV )‰qØmÖ ö""""r˛üB*ĢĒIߑ…ĶJ¸3 “Q'YäÔˇ÷@A“ú^ģ=ĘĘ*HߑEû´d9%ųíˆ*3'§3”°GĐÔDüŀĄI}¯MF#áÎPBCdæäģ‘“âˇŖĒĘĘjBv•i\nw“ü^‡†:¨ĒŽ v""""'Åo!Äãõb4üUN¤Iđ6ŅīĩÉh úø‘“üëKDDDDDä7E!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDĘėDNG^¯—ęŌbNJ ņxŊĩ͍ũ_ĩĪ1 ؜‘XÃÂ14Ág‘ˆˆˆˆüZšTyūĨWØđÅW\7aįčwÔüÛīē‡K.ȐÁ—×ûZN-§ëįįqģÉßöYK§S°q5ۚ<7‡˛&Œ+Íēõ#ų˛ąDˇ?ŖÉÜÆEDDD¤É\ŽUV^ÎÆož#))‘5k×ģĶÂĘUķĘëSƒŨÆ1y gtéŌčå›úöVSVLæŧWqmü€ÖÖJڅēénāŒH8#:…hęĻ•ĩ÷7+Ų5īUĒK‹‚ŨˆHĀ4™˛~ÃX­ƎÁö;Øģw_°[:åeffģ…ãęÛģŠŠÉ^žŠoĪaU%Ŕel$6ÔL„ÃF˜ÃFˆŨŠÍfÃfĢũ{˜ÃFëŅwŌãoSŠØū UĨÅÁn[DDD$`šĖåXk>ûœîįžKĮíiÅÚuëzՕ'TŖĻφ9ķ˛á‹/)*.&2"œ^={0ôĒ+1™Lüß=÷qáųũšęĘ+�(*,â˙îŊîįÃīnŊŲWį˙îųƒ.žˆË/Ŋä¨uÜōû˙cč+¸lĐOķ^sYYģyø/�žm;sæÍgwv6—ä–I z5ÚˇĀívŗđũ%Ŧ_˙%ųDE5cĐÅšđüžšwÜu/W^q›~ØÂ–-[yæŠĮØŊ{ĪqëūÜŋ˙[Ķ3�ølí:yčAâãŽģęS_6Ģ­Áū2õ­ˇŲ˛5‡#„A¤ŧĸ‚¯ŋū†ūũ¯ĀŅ—coŋÕˇ=I‰ 'ĩC!Įüų‹/6Ģ ŖųčąĻĐĸ.ģŽĐķGđíŗ÷cĀ ‡Æ‹ˆˆˆˆü4‰˛'7—wîbÜč‘ zõęÁgŸ¯ãę!ƒOhĀî´wŪåĢß0qÜZĨϞãĮŧ9m:Õ55Œ5‚Nۓą}‡oų­ÛļÕ,ŠômÛ}ĶōōöRTTLįNOj[*+ĢxúŲ˙ŅŖûš\;qxŊ|¸ęcžzæŋ<ųøŋ aæė9Ŧ^Ŋ†‰ãĮŌ&­5›ˇlaúŒYÁŲ}�� �IDAT˜L&ôë €ÉdæãOÖĐíĖŽ |xh°îĪŨyû${âibcc7faĄ!L}ëíãîŸúŲ‡ÍjkT˙oŧų6YYŲÜųûID„‡3gŪöäåa1[NjŋÕˇ=3Ū›}ÂûŅfĩÔįz"ŧx1M˜ŦÂ{\BåŽ¨ŲŸSÛOh8ŅCnÅqö@6žō/ [Öa2šđzBDDD䎪I„5Ÿ}N\l,ii­č×ģ7‹/%}Ûözå¯OIi)kÖŽcôˆáôč~�11-ČÉÍeÅĘUŒ>”Ν:ōÎôYx<^ŒF[Ķ3čÕã<V~ô1ûöí'&Ļ[ˇeî #šeŌImKūÁ**+čŨĢ;‰ņņ�Œ3Šîį‹Ųlĸĸĸ‚U­æŠË/ĨOīž�ÄÆÆ°+s7ī/]î;x6Ājĩ0ōša�ä俎î‘B!&ĖfáΰFí‹ųč¯Ã‘}4Ļ˙ĸĸbžß´™ņcGĶĨs'�nŊåîųĶ4‹lvRûÍfŗÕŲž“Ũ`�L&=.%vâ”gld˙ôĮp—•3ņ/„véŎŗ^Æąål!vöUTøî˜%"""ō[ô1!‡ĩŸo w¯¸ŨnÜn7ŅŅQ´m“ÆÚu ž{w6‡´Ö­ęLošBuU{÷îĨSĮöTTVSûĢtúļ ÚĩkCëVФgÔ^ęŗm[;v<é[ĻÆĮÆËK¯ŧÎâ%ËØĩ+ ŖŅH‡öí°ŲldfeãršéŌŠS÷ulߎ}ûöSYYé›ÖæP(kL]ėŸcųyéīžŊxŊ^ÚļIķÍwØtîxėŗK'ē}'ģÁ`0b6›)up{!ŦkZ ŊÄ;ž$ėėķŲũÅjŧ_-Įiˇb˛Z1›ÍēE¯ˆˆˆüĻũLČĻÍ?PXTČÜų ™;ayŲŲ{7f6ĢĩÁ:‡:í{év{íëĒĘ*’‰‹e[Æv""ÂÉۛGÛļiėøq'Ûé×§7Û22råā“ŪŖŅČ÷Ũ˒e+XũéfĪOtT3† ŊŠ>ŊzRq¨ĪGŸx˛Î¯ß‡Ÿ%QTTėëŲîp4ēŽ?öĪąüŧÆô_ZV^ûž#ÂChhč1×qĸÛw˛û1 €ŲlĄlĶZžyãq:ŊgĪKØŋí{ö¯˜N„Á…ÁbÃāņbąTŖ """"ŋ%A!kÖ~NÛ6iŒ=˛ÎtWM ˙~â)žŪø Ŋzto°ÎáÍƊĘ:Ķ+**jį‡ÔÎ?<.$Üé$))‘GíÚļáíé3É/(ā@~;u8æzę;VŦŽĒŽķ:<ÜÉč‘Ã=r89{ö°|ÅJ^ym* ņņ„ęķ–o ))á¨ZQQõ_ŽÔPŨVŠ)Į|4~˙4¤1ũīÛŋ€ĒšēûĨŦŧü¸ĩOdû~É~üĩ `ąXˆ ĨrĮlķ GMÂU]Eú;OS‘åđØŗÅĸ3!"""ō›ÔËą?¤WĪî´JMŠķ§mÛ6tîԑĩŸ¯oT­ä¤$ŒFcį�ÛwėÄá°@įNŲž};éÛļŅžmíx“´´VėÛŋ _|I|\ŅQQĮ\Ãî ŦŧĸδŨ9{|ߡ˙�_ķ­īubBĮÃh4ŗgÉ-ą˜Í—”īû†ĶéÄbŠāvCuÉ{bû§!é?öP­?îōŊ¯ĸ˛‚~ØrĖēŪžÃÛs’û1L&3ģLœ!!Xļ­gķü7YûėC$¸bŗ;0Zm­60Y°Ø˜ŒzPĄˆˆˆüvõLČú _āvģ8įėŗęŪšgķÆ›Ķ(,,$22ō¸ĩÂÂBéס‹—.#&6†ä–I¤§g°ęŖšôŌ‹}ˇ íĐĄ= ŲøÍ÷Œ5¨ -“’øpÕĮt;ķŒãŽ'5Ĩ%7~Ë ‹bˇÛYļb%ĨĨĨ4‹Œ�   €įž‘‘Çqæ™]1`āķõ0Œ´iŨ‡ÃÁ€ū}™ŋpΰPZĩjE~AĶgĖ"ĒY$wßy{Ŋëm¨n}BCCČĖĘ"3k7QQÍĩԘūcbZ’’Ėĸ%KIHˆ#$$„÷æĖ'""â˜uŗ}GnĪÉėĮ@0…„âhŨ…ōŧ Â,œ;žũ˜–6 &Ģ ¯×‹×ëÅãņR^SŊUgĖ!Πõ+"""hA !k>[Gûļm‰¯wūŲgucę[īđųú užËq,ãĮŽÂaˇņÖ´w).)&*Ēƒ_ÎāËų– !%9™ģ2i×ö§;oĩm“ÆĘU7xkŪ1ŖFđęÔ7šįž Ąŋ>ôíŨ“M›� CûvÜxũĩ,[ą’y a4šHHŒįŽßŨJ\\Ŧ¯FHHŗfĪĨ°¨˜ˆđÎę֕k†]}Ėõ6Ļî‘.x/ŋö˙xô î˜tKŖöOc4Ļ˙I7ßČkožÅŖ?EDdW^qģveąs׎“Ūž#ˇįdöc ˜C4ģpÅk—˙ãw<.<ŕ• F0™1§žITīË1…†ĩg‘@2<üđÃŪ={öđŌK/ũĸB7g×ÂOmÉŠŽĒĒ —ÛMhČOĪ/yô‰§ å÷“n bg'&'o˙I¯Ũe¸J‹đz<xxĄŖsX&Įąė7ÔÛYw k‘ĻâÖ[o ūĀt9==ũė˙(..áډc w†ķíwßŗek:wŨņģ`ˇ0&GčI ‘Ķ™Bˆü*nģå&ĻĪ|įž‰ĒĒjZÄ´āĻŽmpĖˆˆˆˆœūBäWΤ[n v""""Ōũ‰é"""""ōÛĸ""""""Ĩ""""""Ĩ""""""Ĩ""""""åˇb0đxŊ /(r iĒßkˇĮƒÉ¤ßDDDäÔ䎪‡ŨJiišŋʉ4 f“ŠI~¯ËĘ*°Y-ÁnCDDD䤸-„¤$ÆQZ^AIY9nĮ_eE‚Ę‹ˇI}¯Ũ%e唖W’ėvDDDDNŠßVhˇY鐖LfNûķâv˙€MÄ:ļIi2ßk“ɈÍjĄCZ2v›5¨Ŋˆˆˆˆœ,ŋ>1ŨnŗŌžu˛?KŠ4 ú^‹ˆˆˆøFˆˆˆH@)„ˆˆˆˆˆH@)„ˆˆˆˆˆH@)„ˆˆˆˆˆH@)„ˆˆˆˆˆH@)„ˆˆˆˆˆH@)„ˆˆˆˆˆH@)„ˆˆˆˆˆH@)„ˆˆˆˆˆH@)„ˆˆˆˆˆH@)„ˆˆˆˆˆH@)„ˆˆˆˆˆH@)„ˆˆˆˆˆH@)„ˆˆˆˆˆH@)„ˆˆˆˆˆH@™ũYŦ˛ĒšĖœ<ĒĒkpģ=ū,-"""""A`ĩ˜qØm$ÅĮ`ĩø'>ø-„TVU“ž# §3”pg&ãoû$KNŪ~ÎęÜ6Ømˆˆˆˆˆü"Õ5.ōąu{&Ú¤ø%ˆø-)dæäát†âøÍ‘Ķ…Õb&>&šŅ‘dįîķKMŋĨ…ĘĘjBv•‘&$ēY•U~Šåˇâņz1 ū*'"""""MˆÕbĻēÆå—ZēnJDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDĘėfĪ[ĀōåđʋĪģ•&aÔě¸úĘË3bXP×;áÆI\táų\;nT@û8žĒęjnģã\.Ķ^{Á7ũá<Ɔ/ŋ>jųË.Č“nĀãņ2wÁbžŋŒÂĸB’šnühēŸ{vúīĖxÕŸŽå`a!Q͚qų ‹~õ`L&͝ŋ""""ŋA!ōÛļhÉrļmßÁ=wūŽÁeߙņō ˆŒ¯3Ŋĸĸ‚įÃĐ!WԙÕĖ÷÷é3gķŪÜ\;~ ÚĩaŅŌåüí_OđÔŖShÛĻ5�Oũ÷Ežũ~׏CBB<›ØÂ›īĖĀåv1väp?l­ˆˆˆˆ€BˆŲö;ĩÜŽĖ,,^ÆE ā˯7֙W^QI›´ÖœŅĨSŊī­ŠŠaöü… z%ÃŽĒ *ÚˇcįŽ,Ū›ģ€ūt7Ĩee|ĩņnģņ:^Đ€.:°ãĮ]Ŧ]ˇA!DDDDďš\Ißļ9ķæŗ;;ĮKrË$†ŊšíÛ6jū-ŋ˙?†š‚Ë]âĢųú›ĶČĘÚÍÃy��ˇÛÍÂ÷—°~ũ—äՌAäÂķøŪsĮ]÷rå—ąé‡-lŲ˛•gžzŒGH�÷D­ššŪ|g&ŸŦYËÁÂ"ĸšErá€~Œ3Âw‰PËÅ;3ŪãÏ>Ą´Ŧœ´V)Üpíx:uh@aQ¯žņ6ß|ˇ‰’ŌRZ4æĘËqÕāˎģn“ŅČô™sXŧteeeœyFūpį$"#"ŨÛĻļōæÛ3ؙ™‰ĮãĄuj ׎C×Κoō#|ŋy �~ô ˙}ōß|ōéZæ,XÄâ9īúúđxŧ<ûüË\qéÅÄ´h~t)/Įaˇs;öäæQ]]Ù]ģøĻúôėÎÂ%Ë� åŊˇ_?ęŊF“ “Q—b‰ˆˆˆøS“ !••U<ũė˙čŅũ\Ž8ŧ^>\õ1O=ķ_ž|üߘLĻãÎ m\H˜9{ĢW¯aâøą´IkÍæ-[˜>c&“‰ũú`2™ųø“5t;ŗ+C_†Íjû57ũ˜ū÷ŌkŦ]˙ˇßz#mÛ¤ą5=ƒį^|•Ēęjnša"�¯ž1OÖŦeŌ-7ËÂ÷—1ų‘đüĶÃS˙}‘ėœ=ÜwĪD5‹dķ[yö…—‰iҜ^=Î;æēW¯YËŲŨÎā‘ŋÜĮž}xęšxûŨ÷¸ũļ›Õ[eeOy”ũzsĮīn¯—EK–ķĐßūÅ´×^āĄūȟú; ņņLēųzœaĄėܕÉyįœ]§%Ë?ā@~ƎdųĢŽęŗĸĸ›ũ؟ËíĀbŽûuˆ§ŦŦœ’’RœÎ0ßôĒęjĘËĘų|×ŦÛđwß>ЁOIDDDDND“ !ų ¨¨Ŧ w¯î$ÆĮ0nĖ(ēŸw.fŗ‰ĮŸßŦúh5W\~)}z÷ 66†]™ģyér_1Ājĩ0ōšĀ˙šâ’V~ô 7]7žū}{KVvķ-áú‰cŠŠŠaŲĢj—éĶ €;w •••ėÉÍ#.6†[oŧŖŅH\l �‰ ņ,Z炝ŋųî¸!$44„I7_@Û´ÖŦ]ŋômÛŨÛž(¯¨āÂķû‘œ”Ām7]G˙žŊ°XĖØl6LF‹™ˆp'�]0€‹.øéŒTÁÁƒL}û]ūpĮ¤cží¨¨¨ #cw˙i2™ģwĶ,2’~}z2fäplV+ņqą2vüH§Ží}īە™UûūƊ:!äĄŋũ‹ī7o!,4”ģnŋũz7ö#‘FhR!$>6–¸ØX^zåu.8�]:u"%Ĩ%ÚˇkÔüÆČĖĘÆårĶĨSŨņÛˇã“O×PYY‰ũĐÁn›´ÖūÛ¸“°sgí%LÚĩ­3Ŋ]›ÖTUUągO.åÔÔÔĐļMšožÅlæÁ?ũÁ÷Úaˇ3kîžũ~3EÅÅx=^JJKIŒ;îú_ÎuXdD[+2Ũ[ˤDâyėÉ˙rÅĨ—pvˇ3HkJ×ÎõŨ¨Ī‹¯LĨK§ŽôîŲŊŪųŗÅĖūų ŋz0QQQlŪ˛•é3fŗ>ŧûvBôëÃŦ9ķi“ÖŠ6i­Yûų>ßđ%P{Öëį&Ũ|= ųöģM<ųė픕•sÅĨ7ēg9ž&BŒF#Üw/K–­`õ§k˜=w>ŅQÍ6ô*úôęŲāüƨ¨ŦāŅ'žÄ€Á7ŨãõPTTė !v‡ÃĪ[xbĘ+*� ŠÛ‡ãP_•”––`?ÆåH.—›ų'ˇ›[oēŽ–I ˜Œ&ų×ã ŽßfĢ{æÁđĶîjToFŖ‘'ūųīÍ[Ȳ>dęÛīŌĸy4Įbāųũ\˙_mäĢßōÂŗĮîÕh45–ŖS‡vāõōÆ´wšõĻk w:šíÆëxôÉgųãĐĄ}[F_3”—_ gXX÷§Ļ$“š’ĖŲŨÎĀápđĘo1đüūĮÜĮ""""rbšTw2zäpFNΞ=,_ą’W^›JB|<­RSœo¨§fuUĩīī!‡’ošņ’’ŽZ6ęgˇu Ú1.ååuĻ———×Î Áh4ÖģĖaéėĘĖâą<L—N|Ķ‹ŠŠ‰‹‰ųU{ƒÚq7]7ž›ŽOÖîlæ.XĖžyžä–I´māLͧk×QQYÉ ˇŨ雿õ‚×ëeđđ1Ü|ũÄcŽo•šüNœÎ0Ļüõä�Đ<:ŠiĶg‘˜Õjá@~ß~ŋ‰Ū=ģךėĢUĢĒĢkØā�-]R&""""ŋL“zbúžũøú›o}¯˜8~FŖœ={œā°;(;âĀxwÎßߓ[&b1›).)!!>Ū÷',4 §Ķ‰Åbų•ˇ˛ņZ§Ļ`4ųakzé[Ō3 qGRb6Ģ•ī7˙ā›īņxų̓ķáGŸP]]@øĪÆ<lI߯Ū}ûņâũU{ËÛģĪwÉ@rË$nŋífŒF™YģzĶ1ژ8vĪ?ķ8Ī=õ˜īĪ5W_IdDĪ=õ čKvÎūūī˙Ô­lMĪĀh4W{ÉŲęOגąũGšGGŅ<: ˇÛÍĒ՟ŌĢûš�,,ä?Ī<Īēõ_ÖŠŗcĮN 11-NvW‰ˆˆˆČšÔ™‚‚ž{ūEFƙgvŀĪ×oĀ`0ŌĻuëį¤Ļ´dãÆotņ@ėv;ËVŦ¤´´”f‘ĩˇ•u8 čߗų á ĨUĢVä0}Æ,ĸšEr÷ˇsÔát†qÉE0sÎ|âããHk•Ę÷›~`ņ’å z%&“‰Gí2ŗįŅ<:Šä–I,]ž’Œí?r÷“q8°X,,Xŧ”qŖ¯aWænۘö.gw;ƒėœ\ ‹Š|ˇÜõwoûāū‡&ŽŖûšgc0øhõ #ã eĮ;Ųąs-šGķÅWų|ũ—LžīžĀđs͚Eb2IMn ԎwŲ•™Å”GŸäÚqŖˆŠŠbĶæ-ŧ7o!W_yšīĒĩë7žąƒI7_O¸3Œš SYUÅՇpØ6­5gw;ƒ^}ƒōŠ R’[’ą}īÍ]Ā ‹.Āfĩžü)""""u4ŠŌĄ};nŧūZ–­XÉŧ‹0M$$ÆsĮīn%..–¸¸ØãÎ3j¯N}“{î{°ú÷ëCßŪ=Ųôŗ3cF $$„YŗįRXTLDxguëĘ5ÃŽÖĻͤ›¯'Äaį/žFaQ-šG3zä0FģĘˇĖ×ŽĮ`0đÚÔw¨¨¨ Uj2{č~âí“?Ü1‰7Ū~—?ū„ļiiÜsį$äđī'žáūŋüŸ}âWé­kįNÜ}Į$æ.xŸiĶkoœœœÄ_îŋ‡Ä„Úģ›]5ø2ú9îũķ_™|ßČĘĘfŨ†/ŨƒÅb៏LfęÛīōÂĢS)..!ĻEsޟ8–!—ō-wĮ¤›ųßK¯ņŸgūGMu ;uāą)õ…S€īûĶĻĪbúĖŲ””–ĶĸÃŽĖČkšŪ÷BDDDäTfxøá‡Ŋ{öėáĨ—^úE…6nÎ 1N—Ŧ–“ˇŸŗ:ˇmxA‘SÄÆÍŋø÷Ö[omZcBDDDDDäô§""""""Ĩ""""""Ĩ""""""Ĩ""""""Ĩ""""""Ĩ""""""Ĩ""""""åˇb0đxŊū*'"""""MHu ĢÅė—Z~ !ģ•ŌŌr•‘&$˙`ģÍ/ĩüBRã(-¯ ¤ŦˇĮã¯˛"""""DÕ5.ōö°/ŋ¤øŋÔôĪųĀnŗŌ!-™Ėœ<öįÄíVŲ¸9#Ø-ˆˆˆˆˆü"V‹‡ŨFĮ6)~ģËo!jƒHûÖÉū,)"""""§ŨKDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDĘėĪb•UÕdæäQU]ƒÛíņgi ĢŌÃn#)>ĢÅ?ņÁo!¤˛ĒšôY8Ą„;Ã0u’EšļœŧũœÕšm°ÛiŌĒk\ä,bëöL:´IņKņ[RČĖÉÃé %,ÄĄ�"""""rš°ZĖÄĮDĶ":’ėÜ}~Šéˇ´PYYMˆÃî¯r"""""Ō„D7‹ ĸ˛Ę/ĩüB<^/FƒÁ_åDDDDD¤ ąZĖT׸üRK×M‰ˆˆˆˆH@ųõîX"""""^¯—ÂÂBJJJ¨ŽŽfßž}¸\.0˜L&BCC‰ˆˆĀbąĩW…‘Sœ×ëe˖-äååŽÉdĸ¨¨ĮCii)^¯ĮCvv6.—‹víÚ´~BDDDDDNqeee<xž={bŗŲ0 tëÖÍ7ßëõâõzqģŨäææ˛uëVœN'aaaAéWcBDDDDDNq E‹ØívL&FŖƒÁāûsør,ĢÕJTTvģĘĘĘ õĢ"""""rŠĢŠŠÁnˇchÄŨj­V+fŗĮ€Îę§"""""rŠ+**ĸĸĸâ¨2sæLfΜYgšÁ` ??ŸŌŌŌ@ļX‡Æ„ˆˆˆˆˆœâv{Ũ‡ŋõÖ[ŦXą€ĒĒ*&NœԆ§Ķ‰Ųŧ(Đ$BČÖôm,]ž‚]™Y”””âpØi×ļ-W\~)mZˇ v{ŋšÛīē‡K.ȐÁ—˙ĒīiĒNåmņx<L{÷=fΞĮ-7Läę+Ū†weōŌko’ž-ƒĐôëà Įa6›‚ĐąˆˆˆœÎœN'VĢÕ÷zÚ´iŦXą‚)SĻ�0yōd &L� ""‚ĐĐĐ ô M „lI߯O>CķÎåæŽ#,4Œ‚ƒŧŋt9?ņyđ>’ƒŨ&+W}ĖÎ]ģ¸ų†ëüVsôČkHJ8ąm;™÷4U§ęļ<ČŖ˙y–ÂĸbŒÆú¯hÜ Ÿû'˙ķÎ9‹kžLîŪŊŧđĘ˜Í&n˜8.Ā‹ˆˆČéîČŗ999L™2…ÔÔT�ĻL™ÂŒ3€Ú;eŦ,A!ĢV}LBB<ˇÜtŊoZjj2;ļgĘ?g[Æö&B23ŗü^ŗoī^yOSuĒnËGĢ×ÎÓīcô„›ę]æŊš ˆ‹åŪģ~Á` SĮöD5k†Ëå pˇ"""ō[`ĩZ)**ōŒûīŋŋÎüÔÔTß´šš\.WPXôRãrávššî°;øĮßĒ3Íívŗđũ%Ŧ_˙%ųDE5cĐÅšđüuę-X¸ˆĪÖŽ§Ŧĸ‚”–IŒŧfmÛ¤Á˙ŗwįáQ•w˙ĮßŗŲ23Y ;„}ēĸ¸TŅZĩŠ"āöXęãRĩĩZØÚJ}ęŌÅÖļRj](*Vë†K) P‹ *ZEâHHHČžgfÎųũ€føŧŽ+—Ė9gžķ˙8ŸÜį>7píõ?äŦiSųāÍlÜXČ}ŋšˇáūĘēŋŧįWđŸ5ođŗŸü? ú÷ëŌÛĩ7ŪÄYSO§ŦŧœˇßYišœ0é8Ļž~=ō7Š‹?ÂíöpΡĪäøãŽvŊéÚoâŦ3ĻRSSÛkßĸ­­áÆpŲ뙄RC{~Ī^~îw¯ū>į|kS§œë˙¯,dķæ-Ü~Û­û\÷ˊ6}ÄĶĪ<˖ŌRLĶĸŋ|Î;įی>t—ī˛ūŊ÷šī÷Ømģ~ņs˛˛2ģõûO„ÉĮËyß>ë+YķÆ:žsÎY]&‡;æ@ˇ&"""‡¨`0Hii)555¤¤¤ÄFFv^‹ė\#¤­­ĒĒ*\.×.sH)é!düácxhá"î˙Ķ|Ļž~ `ˇīūŅb‹ŸzšUĢ^cöĖ <ˆ 7ōØOâp8˜|ü¤Îcž|ŠĩëŪââ’Ųˇ/ËVŧĘŊŋųwÜ~™}ûāp8Yšú5ƍÃˇÎœŠÛpmŨ뎚Šģīũ-YYY\|ŅtRüž]zsÚŧ˛tŗfÎāŌYŗrõŋydácl,ÚÄŦĶ<č*ūņÜķ,\ô8ß7˙jŧüĘRÎųöˇ¸÷Ž_PßPĪĪæŨÅsK^döĖģ='ņøÜQˇ­­ßūîsô‘\2ûb°,–¯XÉoîû=ŋžį—ģ?jÄ0îúÅĪc¯-ËâO Ä4MŌĶĶēũûO„>_šŋąą‰šÚZBÁ w˙ú÷ŧũî{.SN=™œ—ÔĄO98†A~~>555”——cYuuuDŖQ2v\ģØl6œN'>ŸœœœC{búäÉĮĶÔŌĖ K^á­ˇ×ãõx:t0ß7–‰ŽÆívĐÚÚƊWW1íŒĶ9îØ �deeōYÉ^|ųŸL>~­m­Ŧú÷˜~ūysԑ�\:{&íííTVV’Ųˇ6†‹ žsnˇëúŧ>ėvN§ƒ``ĪĢJöī—ûk÷„Ŗâ‘…1dĐ@† ÛöÂĐ6ē�� �IDAT’—Ѝ¨`đāAģ­‘““Í “:GŌĶŌ;f4Ÿ~Vō•į0ŸīēÕĩ5´ļĩrėÄŖÉËÉāâ‹ĻsôQGîvbļÛí&++3öúŲį—Pš­ŠÛoû1.—Ģ[ŋ§žĸžĄ€‡>Î´Š§rÎˇĻąĄ°ˆ‡}Œh$Ę%3/Lr‡"""r0ęͧŠŠŠD"LĶ$bš&.—+ļhĄÃáĀétvk=‘)é!`ÚÔĶųæÉ'ņáÆB6|¸‘ ōđÂE<ŋä%~pÃĩäåæR˛š”H$ĘaŖFuyīČáÃXũī×hkkŖŦl+áp˜ bû]N'×\5§Ë{†|Ⴙ;uģ;T•ûˇ×ë gĮxįļÎ:-­­{Ŧҝ_~—×>Ÿæ––ūšņŽ›“•EvVķü•“NœĖaŖFQPЏÇ}íįnøp#Ī/y‘Ģŋ÷ŨX0‰įīé@‹D;o/<úČņL?īÛ� 2ˆēē:ž}á%f͸@Ŗ!"""w–eŅØØHcc#TVV‰DČÍ͍­˜î÷û …BI=$„@į_ÂĮËøqcø°°ˆ?üq>‹˙ūnüū5´îXVūŽ{Ī“›iY�Ô×7ĐÜܲŖ–ÁWņ돠ēUˇģˇN׎§swÃ\֎Úģŗ/˙CÄãsã]×nˇsëÍ?äĨW–˛ę߯ņÔ?ž%#=sĪ9›ã&NØãgÖÔÖ2ÁƒœöÍS8âãcÛãų{:Đ|;ÂŲ—/=zäž|ú9ļUV‘“•ŒÖDDDä eY7n¤ĸĸ‚`0ˆÃá žžĶ4ijj²,Lͤ´´”H$°aÃēüq9Ņ’Bęëęq{Üģ\@Ž1œ#ŽĪ{˙ũ��ߎāđŨ+.'??w—:ééiąƒļÖļn~węlv7øÖŅŪ÷Ī \xÁy\xÁy”mŨĘ?—.cÁƒ“›“ÃĀ/ŒVí‰Dųã!+;‹ķĪ;§ËžŪô{ꓑËåĸžąąËö莒dŪ)"""§ææfjkk™0anˇ›ÍƸqãbû-ˊMN///§°°@ @Jʞ§HIŊ'¤žž~ôc^zeé.û,Ëĸŧĸ‚ÔP€ūũōp9446’›“ûIņ§pš\ädgá6 7Įę˜ĻÅ˙Ũ}/˙YķÆn{čNŨĪ›Šī÷O¯ĮKsK×[¨ļ”mëgTVmįwߋŊÎËÍeöĖ‹ąÛm”mŨũg=ųôĶTVUņŋsŽÄáč:od¯~OIfˇÛųÆØ1Ŧycm—í˙ũāCRRüôÉHORg"""r°Úž};}ûöÅãņāp8¸ûîģ)))‰ÍŲŧy3÷Üs†ažžŽĮãĄ­­û¸ˇ¤ūI6 rúißäų%/QßЏącIņû¨Ģ¯į?˙yâ⏚jNį: ^¯—É'LâŲį_ âgāT×ÔđØO’ž–Ę ×]ƒ×ëåøIĮ˛äŗIOK%7'‡•Ģ˙ÍgŸmæŠKīļ‡îÔđû}”lŪLÉæ-¤§§HRjŒ‡ũXŋū=Ϝz ‡W–.ŖŠŠ‰´އššî˙ã\pŪšŒ;6^s-6›!ƒv˙ö;ëYú¯\6ëb:Ú;Øļ­2ļ/LÁįõuë÷”}üilŒiYl-¯āŋ|ˆaC1 ^p.?üņOųíũpę)'R´éc–ŧüOf͘žô‰`"""rđ ‡Ãx<žØuF^^sįÎí˛bú”)S€Î'i9NLĶLZŋIŋ/ä‚īœK^n.Ģ_{ŋžûÍMÍø|^đÃŽã°ŅŸODžhúųø|>ž|ęÔÕ7 †?n ß9÷Û_¨w6›Å˙ímmäįįqÃ÷¯!3ŗī{čNŨSO9‰??øŋ¸ë^ŽŊęģŒ9lô9! pŅôķųËÏđƒ›˙)>'“ŽĀ>ŒÛgŒ>Œ+.ģ„W–.ã™į^Ānw›—Ãĩ˙;‡ėŨˇxīũÎÛîZ¸h—}3g\Č7O>ą[ŋ§DøÃü)*ū(özÉËKYōrįhŪCķOVf_†ÂĪæŪĖC gÅOî 5âŌ™38÷ėi íUDDD õõõX™5k–e1wî\�N;í4f͚t>ǎē皌Œ 233÷Xķ@˛Ũ~ûíÖÖ­[™?ū~ZŋĄ˜ŧė=_č‹ô4eUŒ=4Ųmˆˆˆˆėˇ 6’’BAA×yˇ‹/`úôéąmáp˜ĩk×Ō¯_?ú÷īŋWŸŗ~Cņ~_?͙3'ų#!"""""˛�†ąëbŋ>ž( á÷ût[{¤Å DDDDDzšŊyúĻeYØlļ¤Ž[Ļ"""""ŌˆAsssˇÖ… ‡ÃD"‘¤>]T!DDDDD¤— ƒ466RSSC{{;Ņh”h4Šiš˜ĻI4ĨŖŖƒ††***pš\I]čYsBDDDDDz9Ã0ČĪΧĻφōōr,ËĸŽŽŽh4JFFĐųT,§Ķ‰Īį#'''Š (+„ˆˆˆˆˆúôéCjj*‘H$6úaš&.—+ļhĄÃáĀét&}Ũ2…‘ƒ„ĶéLęGwiNˆˆˆˆˆˆ$TÜBˆÍfÃėÆl|é}: W|FYâBŧƒĻĻ–x•‘¤ēļ¯Į—Zq !yŲ4ĩ´ŌØÜBÔ4ãUVDDDDD’¨#ĄĸdžĘę:ōs2ãR3nŗV<nƒƒûSRVAUu-ҍ‚ˆô|ë7'ģ‘Íp9ņz܌RˇÛąâ:uŪã6>¨<KŠˆˆˆˆČAFOĮ‘„R‘„R‘„R‘„R‘„R‘„R‘„R‘„R‘„R‘„R‘„R‘„R‘„R‘„R‘„rÆŗX[{%e´w„‰FÍx–‘$0\Nŧ7ų9™޸清ļöŠ>ŪL ā'HÁa× KoRVQÅøŅC“Ũ†ˆˆˆˆô0áÕĩõ~Tˆ!q "qK %e~R|^‘ƒ„ár’“™AߌTJË+ãR3niĄ­­Ÿ×¯r"""""ԃd¤…hmkK­¸…͞°Ûlņ*'"""""=ˆárŌŽÄĨ–„ŠëĶąDDDDD$9,ËĸŽŽŽÆÆF:::¨ŦŦ$‰››‹ŨnĮápā÷û …B¸\ޤöĒ"""""ŌËY–ÅƍЍ¨  âp8¨¯¯Į4Mššš°, Ķ4)--%‰0lØ0rrr’Ö¯BˆˆˆˆˆH/×ÜÜLmm-&LĀívcŗŲ7n\lŋeYX–E4ĨŧŧœÂÂB�)))IéWsBDDDDDzšíÛˇĶˇo_<‡ģŨŽÍf‹ũėŧË0 ŌĶĶņx<´ĩĩ%­_…‘^.ãņx°uãiĩ†aāt:1M3ížBˆˆˆˆˆH/W__Okkë.!dņâÅ,^ŧ¸Ë6›ÍFuu5MMM‰lą Í éåŧ^/OׅÃ}ôQ–.] @{{;ŗgĪ:CH ĀéL^č!IJ,Öŧņ&Ģ˙ũ›ˇ”bFMŌĶĶ9âˆņœ~ę7IIņī÷g,\ô8…EÅüâį?Ųį×\˙Nûæ)|ëĖ3öģŸũuÃoá¸ã&ōsÎNv+|ōY ķ|„ĸMÅø}>&—Īž§Ķ‘ėÖDDDD @�Ã0b¯.\ČŌĨK™7o�sįÎÅfŗ1kÖ,�BĄ~˙ū_cīĢBūüāCŧūÆZŽ9ęHNš<—ËÉ'Ÿ~Æō¯ōÖ[īđã›n$”Jv›IĩlÅJ>ũė3ŽŧüŌdˇŌEÕöjn™ûsŽ:b<—Ü>—ōmÛøĶ‚‡p:\>ûâdˇ'"""rHøō¨FYYķæÍcĀ€�Ė›7'žxč�Ø9Y=Y’BVŋļ†×ßXËĨŗ.æÄÉĮĮļņņ{ėî˜w˙xū.›=3‰]&_IÉædˇ°[˙ĮsädgņÃë¯Æfŗ1jäpŌĶԈD"ÉnMDDDäaõõõą€qË-ˇtŲ?`Ā€Øļp8L$IꂅI!ËVŦ`ĐĀ]ČNy99üøG7’Û‡yú™įYģî-ęH ™8áÎ9û,ŽÎÛjëęxč‘ŋąą°¯×ËI'îZ;ōü‹/ņæ›oQ]SCzzSN=…“OœÜíŪģSãÚoâŦ3ĻRSSÛkßĸ­­áÆpŲė™ąŅÚē:~tgŋ>Ϝz -­­ŧķÎģÜyĮOųå=ŋĸ°¨€˙ŦyƒŸũä˙ā°ÛyîųYžr­--Œ9‚+.›M(ėöwØ_kŪXĮwÎ9ĢË$¨ņcĮ$ėķEDDD‚Á ĨĨĨÔÔԐ’’Ųyļsļļ6ĒĒĒpš\ģĖ!I¤¤†–Ö6o.åĖiS÷xLAA˙.¯.zœˇ×ŋËė‹/bā€|üɧ<˛đ1:ÂafL?€??ø0Û**šáēĢ Ĩ†Xąb%oŊŗž˙į‹ą,~ęiV­zŲ3g0dđ 6lÜČcO<‰Ãá`ōņ“ēÕwj8í^~e)į|û[Ü{×/¨o¨įgķîâš%/2{æ �zäolŪ\ĘuW_E(äégžckE.gg:ŊîšĢ¸ûŪߒ••ÅÅM'ÅīāÍĩoqØč‘ÜpŨÕTW×đāÏđėsK¸d֌nūöOcc5ĩĩ„‚AîūõīyûŨ÷0\.Ϝz23.8/ŠC|""""‡Ã0ČĪΧĻφōōr,ËĸŽŽŽh4JFFĐHœN'>ŸœœœCwbz]]=�™}útëøÆĻ&^[ķžĮ}Tį{3ûRV^ÎŌe+8˙ŧshlldãÆBf]|ŖFŽ�`æŒ ŲđaaŦNkk++^]Å´3Nį¸c'�••Ég%[xņåv+„ėMœœlN˜t,�éiéŒ3šO?+ žž÷?ØĀĖrØčQ�ĖųîåüāGˇ’–š€ĪëÃnwāt:>R>Ÿ—™3.`ā€Ū^ŋžO>ũ´[į2ę�xháãL›z*į|k ‹xčŅĮˆFĸ\2ķ„õ""""r¨ëͧŠŠŠD"LĶ$bš&.—+ļhĄÃáĀétvk=‘)Š!dį—wtķ)J[ļ”bš&ƒ ė˛}Ѐ:ÚÛŲļmõõƃčō9°ys)�%›K‰Dĸ6jT—:#‡cõŋ_Ŗ­­ík‡§öĻFŋ~ų]Žņų|4ˇ´�°­r–e1tČāØ~¯ĮËč‘#ŲZ^ņ•= 2¨Ëë`0ČĮŸ$.„DĸQ�Ž>r<ĶĪû6�C‡ ĸŽŽŽg_x‰Y3.ĐhˆˆˆˆH‚X–Ecc#tttPYYI$!777ļbēßī' %u>$9„¤Ļ†°ŲllÛVŲ­ãw.-īņv ;/öÛÛÚcĮ¸\]ŋšĮũų{Zws×ŊŋÆÆį)Đ´, stâëBČŪÔøĒ_rSsˎūÜ]ļwį‘i†Ņõ=‰Îŗžŋ‡!_ …ŖGŽāɧŸc[e9_˜Ī#""""†eYlܸ‘ŠŠ ‚Á ‡ƒúúzLĶ¤ŠŠ ˲0M“ŌŌR"‘Æ #'''iũ&5„x=^ ú÷ã?k^įŦiSw{ąžî­ˇqē\Œ{8¯€ļÖļ.Į´ļļāņyqī­_:ĻeĮ1�žuž{Ååäįįîō™ééi_Û{<j�¸v܋×îč˛}įHIOÖ'#—ËE}cc—íŅ#$ÉŧĪPDDDäPŌÜÜLmm-&LĀívcŗŲ7n\lŋeYąÉéåååHIIųŠĒNŌī•™rę7ŠŽŠåš^Üe_iY?ēˆõīū€ūųųØívŠ?ú¸Ëq}ü)^¯‡ėĖĖØ“´6o)íDĸÅ^÷ī—‡Ë餥ą‘ÜœœØOŠ?…@ Đ­áŠxÔ�ČĘĖāĶO>‹mkmkåÃ7îz°Õ­’ cˇÛųÆØ1Ŧycm—í˙ũāCRRüôÉHORg""""‡–íÛˇĶˇo_<‡ƒģīž›’’’Ø\Í›7sĪ=÷`éééx<žØDɐô?UOœp4‹6ąäĨW(Ųŧ…cŽ>ˇÛMII ËWŦ&77› /8€”?ĮO:Ž%/ŋBfV&ũûåSTTˊWWrúé§âp8蓑ÁāÁƒxņĨWČĘėK ā_ËWāt|>īÄëõ2ų„I<ûü Rü 8ęš{âIŌĶRšáēkžļīxԀΉõũyáĨ—ÉÍÍÆįķņ÷§Ÿ%ęē8ŖßīŖdķfJ6oéö(K"\xÁšüđĮ?åˇ÷?ĀŠ§œHŅĻYōō?™5czŌ'<‰ˆˆˆ*Âá0'vũ•——ĮÜšsģŦ˜>eĘ ķIZN§Ķ4“ÖoŌCĀå—ĖdôČáŧēj5‹_LÔ´Čė“Á™gžÎ7O:÷æK˜1¯ĮÍŖ §Ąąôô4Î<ķ Μ:%vĖ÷Žŧ‚ŋ>˛û~˙G<>/'O>‰'đÎ;īÆŽšhúųø|>ž|ęÔÕ7 †?n ß9÷ÛŨî;5�Žēō |äQîēį7„RCœ5m*Ÿ}ļ™O?û,vĖŠ§œÄŸ|ˆ_Üu/×^õŨŊĒ :„ŸÍŊ™‡>ΊŸÜAj(ÄĨ3gpîŲĶ’ŨšˆˆˆČ!Ŗžžž@  !ŗfͲ,æÎ Āi§ÆŦYŗ€Î‡6UWW“‘‘AæŽģrÍvûíˇ[[ˇneūüųûUhũ†bō˛ûÆŠ­CK{{;‘hŋĪÛv×Ŋŋ!ÅīįęޞŠ*ƏšĪ‘øÚ°a)))tŲžxņb�ĻOŸÛ‡Yģv-ũúõŖ˙Žkō}õŠ÷ûšqΜ9=c$äP÷Ûßũ††F.™=ƒ` Č{˙}Ÿ…E\í˙&ģ5é�†aė˛ũ‹áã‹BĄPˇžÆz („ô�ßûî˙đØâŋs˙įĶŪŪAßĖžüĪå—0nėáÉnMDDDDzŊy*ŠeYØlļ¤ŽįĻŌ„BAŽúîÉnCDDDDz)Ã0¨¯¯Œ¯‡‰D"I]°0éč‘ũ ill¤ĻφöövĸŅ(ŅhĶ41M“h4JGG TTTāršžvqîI#!"""""ŊœaäįįSSSCyy9–eQWWG4%##č|*–ĶéÄįķ‘“““Ô…ĨBDDDDD}úô!55•H$ũ0M—Ë[´Đápāt:“žž›BˆˆˆˆˆČAÂét&u„Ŗģ4'DDDDDD*n!ÄfŗaZVŧʉˆˆˆˆHŌŽ`¸â3ʡâõ45ĩÄ̜ˆˆˆˆˆô Õĩõx=î¸ÔŠ[)ČËĻŠĨ•Ææĸϝ˛"""""’DáU5TVבŸ“—šq›ĩâqŒܟ’˛ ĒĒk‰FDz›õŠ“Ũ‚ˆˆˆˆô0†Ë‰×ãf䐂¸ŨŽ׊ķˇÁđAũãYRDDDDD2z:–ˆˆˆˆˆ$”Bˆˆˆˆˆˆ$”Bˆˆˆˆˆˆ$”Bˆˆˆˆˆˆ$”Bˆˆˆˆˆˆ$”Bˆˆˆˆˆˆ$”Bˆˆˆˆˆˆ$”Bˆˆˆˆˆˆ$”Bˆˆˆˆˆˆ$”Bˆˆˆˆˆˆ$”Bˆˆˆˆˆˆ$”Bˆˆˆˆˆˆ$”Bˆˆˆˆˆˆ$”Bˆˆˆˆˆˆ$”Bˆˆˆˆˆˆ$”3žÅÚÚ;()Ģ Ŋ#L4jÆŗt¯6rHA9/‡ˇáĸ /ÛHj/""""rhŠ[ikī čãÍ~‚v ˛�lÛ^ĶŖÎKÔ4innĨčãÍ Ü_ADDDDD.nWÄ%e~R|Ū¤_h÷$6l=ęŧ8ėv‚?~Ÿ—’˛Šdˇ#""""‡ ¸]ˇĩuāķzâUî ‰F{äyņûŊ´w„“Ũ†ˆˆˆˆ‚âBLËÂnŗÅĢÜAÃęĄįÅaˇ'}~Šˆˆˆˆš’ˆˆˆˆˆRBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄœÉn@vΞ,:šho¨Ã´ŦÎmtū×Fįē#vģ w #%ˆ­ŽE"""""˛;I!÷Ũ˙'ļWWsĮOįî˛oSq1wŪõ+nžéFFÛnYk^“Õ¯ŊƖŌ2ÂaŌŌĶ;æ0Θ:…´ÔT�æ/ø+¯ŋšö+?öĖ‹8ųÄÉÜw˙ŸX˙î{]öRRČĪĪãėŗÎdÄđĄqøļŨcFŖToú/›_~Œšõ̰ÂaL3ʎ,‚ÍvģģË mÜņôŸ:ƒŒácą; ëQDDDDd_%=„ė-Ķ´x`Á_Xģîm&}'MžŒĮãfKŲV–-•7׎ãϝ§_ŋ|Ļ1…IĮMŒŊwÁƒŸŸËÔ)§ÆļåädĮū™Ų—Ë.™{]WWĮĘUĢšëŪ_sÛ­73hā€D|EÂÍ ”<ķĸ˙à ÃÄa€ŨfÃaëL!QˆiE‰ŌJõģËøŦ­…Ā5ķđ„ŌԟˆˆˆˆČūču!dÅĘUŦ]÷6sŽŧŒ‰ĮÛ>nėáL>~wūōūđĀîŧã§äį呟—;Æpģ…‚Œ5rˇĩŨnw—€#Əįæ[oã_ËV0įĘËĖ—ú’öÆš‹×Sāw’âÜķmVgĪaȐ#Yõķ˙ĨŊŠA!DDDDDz…^71}é˛åŒ5˛K�Ų)HaúųįRąmīŊ˙A\>Ī0\ôë—ĪļĘǏÔë.;nÍ}7?Ž´L˛füˆĐ´˙á“> vĖéézD1M‹æ––]~ÚÚÚģWWWGeeÕG2�FíØWT´)nũUUm'==5nõžŽ……ŨîĀa'NÃČˆÍi`s8C}čsî5ø;›õ ūkã8ė,K!DDDDDz‡q;VYYW_wã×W[W@ŸŒŒ=ã6 BĄ`ėØŊFc˙Žoh`ųō•”WTpņEėSŊ}aĄcN'kö­´¯§ęąģ‰67’9û6ü‡MäŊ'˙ŒwãjÜ>•­­ą'f‰ˆˆˆˆôt="„|yBøNĨĨe,z|qėõÎ í¨ųĘzĻeíĶ%ų–-Ĩ\1įę.Ûü>—_:›ÃFÚ‡ŠûÆfŗãt:iŠÚˆZ2æ88įi}ņ Įæ˙,ÅzûŸ<QËÂé ëŊ""""Ōkôˆ˛ģ á�{× ëôô4�ļWÕėąV{G ddėũ$íėŦŦ.“Ī ÃEVfNgb}kœNÍŦáŨ‡îaôŒk L8€ĒMīSĩô1Bļ6—›iáru  """""ŊE˜Ō]Á`€ŧÜ\ŪX÷Öį@|¸ą€Q#Gėu}—áb ØO^nnÂtŽâršųũx?^GáĶ hk¨Ĩi{E‹~K¨ĩ—áÆæ4Āiātš4"""""ŊF¯ !�§z2eeeŦXšz—}MM,~ōi ú÷Û§ŌS8N\/8\|>\›ŪdÃŗ°æw?!7R‹Ûã=) ‡ —Û‹ÃŽ… EDDD¤wčˇcí&GQQ1 =ÎĻâøÆ¸ÃqģŨ”mŨʲå+ą,øūĩWõę‘‡ĪwĐa´T“ârđzØūŪJúš]8 7–eaYĻiŅîĀ3p4N_ Ųm‹ˆˆˆˆtK¯ !6›+¯¸”1cFąjõk<ŧp‘p„´ô4Ž>úHĻ>…`°w_;ũŌN>Ÿ†5/QũÉą™LͤĄ­�› ģŨ'ÎcI?ö ū”dˇ-""""Ō-ļÛoŋŨÚēu+ķįĪ߯Bë7“—Ũ7Nm<Ę*ĒöųŧD[›‰4Õc™&֗#´aÃfˇãL áđú÷šˇņŖ‡îĶ{EDDDDöŜ9szßHČĄÄáõīsĀéŠzŨÄtéŨBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄâBl6Ļe}ũ‡˜žz^ĸĻ‰ÃĄ *""""‰ˇĢP¯Į ŠŠ%^åN‡ŖGž—ææV܆+ŲmˆˆˆˆČ!(n!¤ /›Ļ–V›[ˆšfŧĘözV:/QĶ¤ąš…Ļ–V ō˛“ŨŽˆˆˆˆ‚œņ*äqŒܟ’˛ ĒĒk‰F“ÁŨSŒRĐc΋ÃaĮm¸1¸?ˇ‘Ô^DDDDäСAdø ūņ,yĐĐy餙É"""""’P !"""""’P !"""""’P !"""""’P !"""""’P !"""""’P !"""""’P !"""""’P !"""""’P !"""""’P !"""""’P !"""""’P !"""""’P !"""""’P !"""""’PÎxkkī ¤Ŧ‚öŽ0ҍĪŌ"""""’†Ë‰×ã&?'ßøˇŌÖŪAŅĮ› ü)8ėd9ԔUT1~ôĐdˇ!""""qÔŽP][OáG%ŒR— ˇ¤PRVA ā'ÅįU�9H.'9™ôÍHĨ´ŧ2.5ã–ÚÚ:đy=ņ*'"""""=HFZˆÖļö¸ÔŠ[1- ģͯr"""""ԃ.'áH\jéž)~}%�� �IDATI¨¸>KDDDDD’Ã˛,ęęęhll¤ŖŖƒĘĘJ"‘šššØív~ŋŸP(„ËåJj¯ !"""""ŊœeYlܸ‘ŠŠ ‚Á ‡ƒúúzLĶ¤ŠŠ ˲0M“ŌŌR"‘Æ #'''iũ*„ˆˆˆˆˆôrÍÍÍÔÖÖ2aÂÜn76›qãÆÅö[–…eYDŖQĘËË),,$’’’”~5'DDDDD¤—Ûž};}ûöÅãņāp8°ÛíØlļØĪÎÛą à ==ĮC[[[ŌúUéåÂá0[7žVkN§Ķ4ĐŲî)„ˆˆˆˆˆôrõõõ´ļļîB/^ĖâŋģlŗŲlTWWĶÔԔČģМ‘^Îëõâņt]8üŅGeéŌĨ�´ˇˇ3{öl 3„œÎäE¤‡ûî˙ÛĢĢšã§sw؎Џ˜;īú7ßt##‡‹mˇ,‹5¯ŋÉę×^cKiáŽ0iéiŒsgLBZj*�ķü•×ß\û•Ÿ?{æEœ|âdîģ˙OŦ÷Ŋ.û))äįįqöYg2bøĐ8|ÛN×\˙Nûæ)|ëĖ3öĢÎÂESXTĖ/~ū“8u–<Ī-y™į–ŧĖöęj˛23šđüs8åÄ’Ũ–ˆˆˆH¯0 #özá…,]ē”yķæ0wî\l6ŗfÍ  á÷û“Ō+ô€˛ˇLĶâaí玙pôQœ4y2›-e[YļüUŪ\ģŽ›nŧž~ũō™vÆ&71öŪ>B~~.S§œۖ““ûwff_.ģdVėu]]+W­æŽ{ÍmˇŪĖ âō.ŧā;äįæÅĨÖÁāåĨËųËÃ㒋/dø°!ŧ÷ūüęž?â÷ų˜pô‘ÉnODDD¤Įûō¨FYYķæÍcĀ€�Ė›7'žxčüƒūÎÉęÉŌëBȊ•ĢXģîmæ\y9&ļ}ÜØÃ™|ü$îüå=üáÜyĮOÉĪË#?īķ‹}Ãí" 2zÔČŨÖvģŨ]F\�Ž?ž›oŊ-[Áœ+/Ëw˜tėį?čaY‹Ÿz†ŗÎ8īœs�cFdKiO<õŒBˆˆˆˆH7†A}}},`ÜrË-]ö0 ļ-‰D’ē`a¯›˜žtŲrFŲ%€ė ¤0ũüsŠØļ÷Ū˙ .Ÿg.úõËg[eÕn÷˙7ķÜ /Æ^××Õsé˙|?Î_đĨã~ÄK¯tŪ“wÍõ?āų%/Åö]{ãM,]ļ‚'ž|Š~x W]sŋũŨ¨¯ĢS[WĮ¯īģŸ+¯ē–ënüĪ<˙Â.Ŋ„ÃažxōinŧéĮ\1įj~đŖķÔ?ž%îs¯ÚÖō *Ģļ3áčŖēl?æ¨#ØTü1--­ éCDDD¤7 ƒ466RSSC{{;Ņh”h4Šiš˜ĻI4ĨŖŖƒ††***pš\ģĖ!I¤1bšÍ--ģlokkīōēŽŽŽĘĘ*N<áø=Öĩc”Ŗ¨hãĮ—ūĒĒļ“—ˇû%GNņGĮ^nÚDzZ:E›>Šm̍ØF}}ÃG`œv/ŋ˛”sžũ-îŊëÔ7Ôķŗywņܒ™=s�~đaļUTrÃuWJ ąbÅJŪzg=)ūĪ˜Y¸čqŪ^˙.ŗ/žˆđņ'ŸōČÂĮ臙1ũü¸ôoĨ[ËČÉÎę˛}įë˛ōr†”^DDDDz+Ã0ČĪΧĻφōōr,ËĸŽŽŽh4JFFĐ9!ŨétâķųČÉÉ9´'ĻCį=kW_wã×Wģcd ĪŽš;nà  ƎŨ[;G �ęXž|%å\|Ņģ=~ô¨‘,zėILĶÂnˇQXTĖÄcŽbŲĢ+ŠŦŦ"3ŗ/…›Š Rčß/Ÿ›““Í “Ž =-ącFķég%�ÔÔÖ˛qc!ŗ.žˆQ#G�0sƅlø°0öūÆĻ&^[ķžĮėUČĖėKYy9K—­āüķΉ[¯ņÔ˛#|ú|Ū.ÛŊ^īŽũ éŽ>}úššJ$‰~˜Ļ‰ËåŠ-Zčp8p:ŨZOä@ę!äËÂw*--cŅãŸ?רFįɊš‘¯ŦgZûrZˇl)åŠ9WwŲæ÷ų¸üŌŲ6zÔnß3jäpZÛZ)-+Ŗŋ|Š63ũüsųäŗĪ(*.&3ŗ/›63räȝüe÷ûŌEŋĪį‹•—W�t™oŗŲ8°€Í›KcŊ›ĻÉāAģÔ4 €ŽövļmÛˇ^EDDD¤įą,‹ÆÆFéčč ˛˛’H$BnnnlÅtŋßO(Ję|č!!dwÂöŽÂééi�l¯ĒŲc­öŽÉČHßë>˛ŗ˛ēL>7 Y™Y8Ž=ž'=-ėŦ,6D(¤b[C‡æãO>Ĩ¸ø#Ž?îX6ķ­ŗÎüĘĪūĒ˙ÚÚÚvĶõ×åq{v9ÆãízoßÎ{ũÚÛÚÉĪˋK¯ņ”˛ãŅpÍÍ-ø}žØöæææû}ģ}Ÿˆˆˆˆ|β,6nÜHEEÁ`‡ÃA}}=ĻiŌÔԄeY˜ĻIii)‘H„aƑ“ŗûé‰Đ#BHwƒōrsycŨ[œ9íôŨūĩū͝ˇ(íŧmio¸ ėõûvÎĩäįįáķú6t{l1Õ55l¯Žaô¨Ŋīg'ˇÛ @kk[—í-­ŸßĒäŲqûRۗŽiŨqŒgĮíNē×Ŋ•Ÿ— tNPĪėÛ'ļŊ´l+vģ-ļ_DDDDöŦšš™ÚÚZ&L˜€ÛíÆfŗ1nܸØ~˲°,‹h4Jyy9………RRRžĸęĶ랎uÚŠ'SVVƊ•ĢwŲרÔÄâ'ŸĻ ŋ} !ûjô¨‘|ôŅGmÚÄ𡝋<ĘĒJÖŽ{‹œėl2Ō÷~df§ė“´7o)m‹DĸÅ^÷ĪĪĮnˇw™xđŅĮŸâõzČÎĖLH¯{+';‹ÜœlÖŧŅuQÉ×ß|‹1ŖGŘˆˆˆˆėŲöíÛéÛˇ/‡ÃÁŨwßMIIIl.ČæÍ›šįž{0 ƒôôt<OėNšdčU#!�'L:Žĸĸb.zœMÅņq‡ãvģ)Ûē•eËWbYđũk¯J蜆#†S[WĮúwßįĸéįāõx闟Īō+ˇŸOéꓑÁāÁƒxņĨWČĘėK ā_ËWāt|~›XJŠŸã'Į’—_!3+ŗsÎGQ1+^]É駟ŠcĮąē×}qŅųįō›û OF#G åÍuī°îíõüßĪoKx/""""ŊQ8ÆãņÄŽķōō˜;wn—ͧL™t>IËétbšfŌúíu!Äfŗqå—2fĖ(V­~‡."ސ–žÆŅGɴͧ ړßįŖ >ũŦ„a;F�†˞+ãō¸Ûī]y}d!÷ũūx|^Nž|'NāwŪ3sÆtŧ7.|œ†ÆŌĶĶ8ķĖ38sꔄöēˇN9éZÛÚxúŲXøø“äådķ㛎įđÃv˙0�éĒžžž@  !ŗfͲ,æÎ Āi§ÆŦY‚˛ŲlTWW“‘‘AæŽģeÍvûíˇ[[ˇneūüųûUhũ†bō˛ûÆŠ-éĘ*Ē?zč×(""""qĩaÃRRR((č:ŋyņâÎ'ÍNŸ>=ļ-ŗvíZúõëG˙ūũ÷ęsÖo(ŪīëŊ9sæôž‘é*`Æ.Ûŋ>ž( áßņ”ŌdčuĶEDDDD¤ĢŊYũܲ,l6v{ōĸ€BˆˆˆˆˆH/gÍÍÍX–õĩĮ†Ãa"‘HR,Téå‚Á ÔÔÔĐŪŪN4%bš&ĻiFéčč ĄĄŠŠ \.WlQëdМ‘^Î0 ōķķŠŠŠĄŧŧ˲¨ĢĢ#’‘‘t>ËétâķųČÉÉŲĢ[¸âM!DDDDDä ЧORSS‰D"ąŅĶ4qš\ąE N§3ĄkęíŽBˆˆˆˆˆČAÂét&u„Ŗģ4'DDDDDD*n!Äfŗavc6žˆˆˆˆˆô>á†+>Ŗ,q !^ASSKŧʉˆˆˆˆHR][×ãŽK­¸…‚ŧlšZZiln!jšņ*+"""""IÔŽPQUCeuų9™qЎY+ˇÁˆÁũ))Ģ Ēē–hTAäP´~Cq˛[‘82\Nŧ7#‡ÄívŦ¸N÷¸ †ęĪ’"""""rŅĶąDDDDD$ĄBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄBDDDDD$Ąœņ,ÖÖŪAIYíaĸQ3žĨ{Ĩ‘C zĖųp8ė¸ yŲxÜFR{‘C[ÜBH[{Eo&đ ¤ā°ڃ,Ûļ×ô¨ķ5Mš›[)úx3Ã÷W‘¤‰Û•qIY€ŸŸ7éÜ= [:ģ`ĀßįĨ¤Ŧ"Ų툈ˆˆČ!,nWĮmmøŧžx•ëõ"Ņh<~ŋ—öŽp˛Û‘CXÜBˆiYØmļx•ëõŦz>v{ŌᧈˆˆˆČĄ-ų÷ ‰ˆˆˆˆČ!E!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDʙė¤+˲čhj ŊĄ͞:ˇŅų_ëŽØí6܁TŒ” ˆˆˆˆČWIj™ŋā¯ŧūæÚ¯<föĖ‹8ųÄÉ@įúš×ßdõk¯ąĨ´ŒpG˜´ô4Ǝ9Œ3ĻN!-5u¯ëŪw˙ŸX˙î{]öRRČĪĪãėŗÎdÄđĄûņ ÷ŽRŊéŋl~ų1j֯ ‡1Í(;˛6Øíė.ƒ´qĮĶę 2†Åîp$ŦG‘ũ•Ô2íŒ)L:nbėõ‚!??—ŠSNmËÉÉĀ4-XđÖŽ{› GÅI“'ãņ¸ŲRļ•eË_åÍĩë¸éÆëé×/¯ędföå˛KfÅ^×ÕÕąrÕjîē÷×ÜvëÍ 8ā�|û]…›(yæ/D7ū‡A†‰Ã�ģ͆Ã֙Bĸ– ͊Ĩ•ęw—ņY[ kæá Ĩ'¤?‘xHjÉĪË#?//öÚpģ…‚Œ5r—cWŦ\ÅÚuo3įʢxĖ1ąíãÆÎäã'qį/īá,āÎ;~ēWuÜn7#‡ë˛íˆņãšųÖÛø×˛ĖšōōũũĒŨŌŪØ@sņz üNRœ{žÍ*ãė9 r$Ģ~ūŋ´75(„ˆˆˆˆH¯Ōk&Ļ/]ļœŅŖFv ;)L?˙\*ļmãŊ÷?ˆËᆋ~ũōŲVY—zŨeĮÂm¸ąīæĮ•–I֌šö?|ōĪ'°aÁŽų""""""ŊE¯!uuuTVVíq$`Ԏ}EE›âöšUUÛIOO[Ŋ¯caaˇ;pÁ‰Ķ0rbs؜ÎPúœ{ ūãÎfũ‚˙ÃÚøģËR‘ŪĨW„Úēz�úddėņˇa cĮî­h4ûŠŠ­åīO=CyE'žpü>ÕÛ6Āáp:æt˛fĪĨīE7á꓋Ũ söm'Í‡KÃģq5!ŸģÃ{b–ˆˆˆˆHoŅ+ŅģķB;jFžō8͞öé’|˖Rޘsu—m~ŸË/ÍaŖGíCÅ}cŗŲq:4EmD-Hsœs Ž´žx†Žcķ–bŊũOƒ¨eát†õˆ^éuzEIOO`{U͏iīč ąĄ‘ŒŒŊŸ¤•Õeōša¸ČĘĖÂéLėŖom€Ķéĸųƒ5ŧûĐ=Œžq- §Pĩé}Ē–>FČÁærc3-\Ž”ADDDD¤ˇéˇcƒōrsycŨ[{œņáÆB�Fą×õ]†‹ b?yšš  ĐšˆËå"ä÷ãũx…O/ ­Ą–Ļí-ú-ĄÖj\†›Ķ�§ĶåŌHˆˆˆˆˆô:Ŋ"„�œvęɔ••ąbåę]ö565ąøÉ§)čßoŸBHOáp8qyŧāpđųpmz“ Ī>šßũ„ÜH-n7ö¤,.\n/ģ*‘ŪĨW܎p¤ã(**fáĸĮŲTüßw8nˇ›˛­[Yļ|%–ßŋöĒ^=2āđųņ:Œ–ŠbR\.^Ûß[I?ˇ ‡áÆ˛,,ËÂ4-ZÂxŽÆé $ģm‘ŊŌkBˆÍfãĘ+.e˘QŦZũ/\D$!-=Ŗ>’i§O!ėŨäN€´“ΧaÍKTō_lfĶ4ihkŖĀfÃnˇƒÃ‰sĀXŌ=‡?%Ųm‹ˆˆˆˆė•BîžķޝÜoŗŲ˜xĖ1ģ]°p_ë~˙šĢöĒցdŗŲI8ov"MõXωõĨÅmذŲí8SB8ŧū$u*""""˛īzT‘N¯_CDDDDZŊfb爈ˆˆˆBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄBDDDDD$ĄâBl6Ļe}ũ‡ˆžz>ĸĻ‰ÃĄė)""""ɡĢQ¯Į ŠŠ%^åz=§ÃŅ#ĪGss+nÕė6DDDDäˇR—MSK+Í-DM3^e{- ĢG¨iŌØÜBSK+yŲÉnGDDDDaÎxō¸ F îOIYUÕĩDŖÉŋđNļ‘C zĖųp8ė¸ #÷Įã6’Ú‹ˆˆˆˆÚâB 3ˆ Ô?ž%{=‘Ž4CYDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDJ!DDDDDDĘĪbmí””UĐŪ&5ãYZDDDDD’Āp9ņzÜäįdb¸ââBÚÚ;(úx3€Ÿ` ‡]ƒ,‡Š˛Š*Əšė6DDDDä�čG¨Ž­§đŖF )ˆK‰[R()Ģ đ“âķ*€ˆˆˆˆˆ$ —“œĖ úf¤RZ^—šqK mmøŧžx•‘$#-Dk[{\jÅ-„˜–…Ũf‹W9éA —“Žp$.ĩtߔˆˆˆˆˆ$T\ŸŽ%"""""ÉaYuuu466ŌŅŅAee%‘H„ÜÜ\ėv;‡ŋßO(Âår%ĩW…‘^β,6nÜHEEÁ`‡ÃA}}=ĻiŌÔԄeY˜ĻIii)‘H„aƑ“““´~BDDDDDzšææfjkk™0anˇ›ÍƸqãbû-˲,ĸŅ(åååHIIIJŋš"""""ŌËmßžž}ûâņxp8Øívl6[ėgįíX†ažžŽĮãĄ­­-iũ*„ˆˆˆˆˆôrápĮƒ­OĢ5 §Ķ‰iš čl÷BDDDDDzšúúzZ[[w !‹/fņâÅ]ļŲl6ĒĢĢijjJd‹]hNˆˆˆˆˆH/įõzņxē.ūčŖ˛téR�ÚÛۙ={6ĐB�Ngōĸ@RCČüåõ7×~å1ŗg^ÄÉ'N:'ÔŦyũMVŋö[JËw„IKOcė˜Ã8cęŌRS÷ēî}÷˙‰õīž×e_ %…üü<Î>ëLF ēß°ĢkŽ˙§}ķžuæûUgáĸĮ),*æ?˙Iœ:KŽöŽ=ņwVũ{ ĩuu¤§ĨqƔS9īÛgâp8bĮ=ˇäež[ō2ÛĢĢÉĘĖäÂķĪá”OHbį""""=K Ā0ŒØë… ˛téRæÍ›ĀÜšsąŲl˚5 €P(„ßīOJ¯ä2íŒ)L:nbėõ‚!??—ŠSNmËÉÉĀ4-XđÖŽ{› GÅI“'ãņ¸ŲRļ•eË_åÍĩë¸éÆëé×/¯ędföå˛KfÅ^×ÕÕąrÕjîē÷×ÜvëÍ 8 .ß÷ žC~n^\j ~ķûxīũ¸læEäææ°áÍ<˛č "Ņ3.8€——.į/˙K.žáÆđŪûđĢûūˆßįcÂŅG&ųˆˆˆˆô _Õ(++cŪŧy 0�€yķæņÄO�Øß9Y=Y’BōķōČĪûüĸÜpģ…‚Œ5r—cWŦ\ÅÚuo3įʢxĖ1ąíãÆÎäã'qį/īá,āÎ;~ēWuÜn7#‡ë˛íˆņãšųÖÛø×˛ĖšōōũũĒ�L:vâ×tˆhjnæíõīōŊ+.唓:G55‚?ųŒ5oŦeÆįaY‹Ÿz†ŗÎ8īœs�cFdKiO<õŒBˆˆˆˆČ†aP__ ˇÜrK—ũ ˆm ‡ÃD"‘¤.XØk&Ļ/]ļœŅŖFv ;)L?˙\*ū{wU}īü=K&“d&!ûJöMQ@E÷YUj]ĒļÚj­ŋëíÃëĪ{Ûj[ĩĩTŽV[QYWvŲQ @@ļldO&“ufÎũ#a$dq8Cäõ|<xÄsÎ÷|į3˙˜wžßīų8 M_mÉë9ęÚ5KJË:ŧū‹_=¤wŪ[<ŽŠŽŅ­wÜ­é3ž˙Vģĩpqë\ŧ{ī˙•Ūŋ0xíž_ūFK?XĻŲsßĐŋū­îš÷=ũ×ŋĢĻē&ØĻĒēZyæYŨyĪ}úų/Ô[īžwX----š=wž~ų›‡uû]?͝|Xoŧųļü~˙ ×z˛šbbôú+/ČAV›M6këTŦĸâ•–•køĐsÛĩvîŲڑŋKõõ ĻÔ ��pĒ‹•ĮãQeeĨšššä÷ûå÷ûä÷ûÕÜÜŦÚÚZ•””(""â°5$fę ĶĢĢĢUZZĻ‹FŽ8b›ūmŖÛˇīĐYƒÎ É떕•+3ŗã$û÷ëŖüģ‚ĮÛvėPB|‚ļīØ<WRr@55ĩGą[mZ´xŠŽŋîũé˙­šÚũ×ãÔ;ķhÚÍS%I˙ûĪé@IŠøųĪ×%N˖-׆Ī7ĘķÍÆ23_ĨĪ6~Ąi7MQˇÜ\íúzˇū=ķ55ˇ´hę¤ !ŠõdjjnVŊˇ^ŸŽÛ 5ëÖë{ī‘$K’ŌĶRÛĩ?x\X\Ŧ^=ē›[,��Ā)Čáp(++K•••*..–aĒŽŽ–ßīWbbĸ¤Öévģ]ŅŅŅJOO?}ĻĢĒ¤ļ°#‘‡ââbƒm×ÁQIĒŠ­Õ‡.WqI‰nš2ąÃöú÷Ķ̝ÍU `ČjĩhÛö|7ė\}đŅr•––)%%YÛvä+ÖíRv×Ŧ#žnzzšF^xž$)!>AƒĐî={%I•UUĘËÛĻ[nšĸūũúJ’nž:Y[ļn ŪīŠĢĶĒÕk4yÂx k1HIIVaqą–~°LÆ_˛ZO–ß=ö{}ĩ%OޘŨīŨ5ĸõ퍝¯—$EGGĩkÕv‘��€ƒ’’’ÔĨKų|žāčG PDDDpĶB›Í&ģŨ~Lû‰œL"„XÔú!ųžīl0 ČĮšnŋëgíÎÅDGëļ[§éŒũ;ŧ§ŋ>jhlPAaĄ˛ģfiûŽ|MšpƒžŪŗGÛķķ•’’Ŧ;ōÕ¯_ŋīü%wũ֗ūččhyÛž|—HRģ…ņ‹EŨēåhßž‚`í@@=ēwk×O÷Ü575é!ĢõdšįÎ̞ĒZ›žÜŦŋüuēŧŪz]9î˛Ŗß��€ Ã0äņxäņxÔÜÜŦŌŌRų|>eddwL‰‰Q\\\X׃H$„$$ÄK’ĘË*ØĻŠšYžZŽģ˙´ÔÔv‹ĪŽĨϤĘnˇņž„øĨĨĻjGūNÅÅÅĒä@‰zõęĄ]_īV~ūN¸ā|íČĪ×5W_õ¯ũ]˙466ļĩi˙krF:kãŒj?§īāŋĻÆ&eef†¤Ö“%7'[š9Ų2øLEEEéų—^Öč‹FĘÕöØ8¯ˇ^1ŅŅÁö^¯W’䊉î°?��€ĶaĘËËSII‰bcceŗŲTSSŖ@  ēē:†Ą@  ‚‚ų|>õîŨ[éé/;0C§!ąąnefdhÍú ēęĘqūĩ~k^ëĨƒĶ–ŽG„#BŨrsŽûžƒk-bŨneee*:*ZŊ{õÔ+¯ÍQEeĨĘ+*5 ˙ņ×sPdd¤$ŠĄĄąŨųú†oĻ!9ÛĻ&5~ĢMC[gÛTĻ“]ëņ*¯¨Ôϝ6ëüáCuČĸ¨nŨrÔÜÜĸ˛ōreefHj] ž’œlSPX$ĢÕŧ��pēķzŊĒĒĒŌđáÃ)‹ÅĸÁƒ¯†!Ã0ä÷ûU\\ŦmÛļÉívËår}G¯'O§y:֘Ë.QaaĄ–-_yØ5O]æĖ§œėŽ'BNԀūũ´sįNmßąC}zĩnjØŖG7•–•jŨú JOKSbÂņĖ”Öļ�{ßū‚ā9ŸĪ¯mÛˇŗŗ˛dĩZÛ-<—¤ģv+*ĘŠ´”Sj=^UÕÕúķ3Ķĩfí†vįwíÚ-‹Åĸ””dĨ§Ĩ*#=MĢ×´ßxōĶĩ4p@˙`H��8Ũ•——+99YN§S6›MO<ņ„öîŨ\ ˛oß>=™Ą;ß��IDATųä“r8JHHĶé Ψ ‡N1"I#/ŧ@ÛˇįkæĢŗ´#§† >S‘‘‘*,*Ō.—aHŋ¸īS×4ôíÛGUÕÕÚøÅWš2ŠusŊ(g”ēfeéÃeË5ø{>Ĩ+)1Q=ztׂ…‹•š’,ˇÛ­÷?\&û!ģ‰ģ\1qášŋhąRRSZ×|lĪײ–kܸ˂;ŸėZW¯Ũ5dđ™úĮ /ŠžĄA9Ų]•ŋs—^ķŊôbEļíø9e zęŲį””˜¨~}{iíúĪĩūŗúũc˙ijŊ���§˛––9ÎāwáĖĖL=ōČ#ívL;vŦ¤Ö'iŲív°ÕÛiBˆÅbŅˇßǁûkÅĘUú×ĖWåkņ)>!^C‡žŖ+ĮUlŦÛԚbĸŖ•“­Ũ{öĒwÛč‚$õęŲC,[’ĮŨŪ}įízņß3õĖßĻËĨKFÔyį ×įŸlsķÔIŠrFę噺TëŠUBBŧŽēę ]uųXSk=^˙ņĐ/5ķĩšzmÎōÔÕ)%9Y7\{•&Ūx]°Íč‹GĒĄąQķŪ~O3gÍUfzšūÍũ:ķŒŽ���p:ĒŠŠ‘Û톐[nšE†ač‘G‘$3FˇÜr‹¤ÖīÕJLLTJÛŦŗY}ôQŖ¨¨H3fĖø^mܒ¯Ė´ä•…Τ°¤Lg čuô†���8)ļlŲ"—ËĨœœöëœįĖ™#Iš4iRđ\KK‹Ö­[§Ž]ģ*;;û¸^gã–üīũŊīŽģîę<#!����:ævģåh›Î~¨CÃĮĄâââĶö$Ōpč4 Ķ���tėxv?7 C‹EVkøĸ�!���čä‡ŧ^¯ Ã8jۖ–ų|ž°nXH���:šØØXy<UVVĒŠŠI~ŋ_~ŋ_@@@@~ŋ_ÍÍÍĒ­­UII‰"""‚›[‡kB���€NÎáp(++K•••*..–aĒŽŽ–ßīWbbĸ¤Ö§bŲívEGG+==ũ¸Ļp…!���øHJJR—.]äķų‚Ŗ@@ÁM m6›ėvģŠ{ëu„���ü@Øíö°Žp+ք����0UČBˆÅbQāVã���č|š[|rD„f”%d!$ĘéP]]}¨ē���p ЍĒQ”32$}…,„ädĻŠŽžAoŊü@¨ē���FÍ->•”UĒ´ĸZYé)!é3dĢVœ‘õ푭Ŋ…%*̍’ßO9lܒî���p8"ėŠrFĒ_̐MĮ éŌyg¤C}ēg‡˛K����?0< ���€Š!����LE���`*B����SB����˜Š���ĀT„����Ļ"„����0!���€Š!����LE���`*B����SB����˜Š���ĀT„����Ϟ‡˛ŗÆĻfí-,QSs‹üū@(ģ���Žģĸœ‘ĘJO‘#"4ņ!d!¤ąŠYÛwí“ÛŖXˇK6+ƒ,áTXRĻŗô w���čäš[|ǍĒŅļ{ÕˇgNH‚HȒÂŪšŨ1rEG@���€G„]é)‰JNėĸ‚âԐô˛´ĐØØŦč(g¨ē���p IŒSCcSHú Y †ŦK¨ē���p qDØÕÜâ I_Ė›���`ǐ> ���@x†Ąęęjy<577Ģ´´T>ŸO˛Z­˛ŲlЉ‰Q\\œ"""ÂZ+!���čä ÃP^^žJJJ+›ÍϚšÕÕÕÉ0 ČįķŠwīŪJOO[Ŋ„��� “ķzŊĒĒĒŌđáÃ)‹ÅĸÁƒ¯†!Ã0ä÷ûU\\ŦmÛļÉívËår…Ĩ^ք����\yyš’““åt:eŗŲdĩZeąX‚˙NĮr8JHHĶéTcccØę%„����\KK‹œN§,Įđ´Z‡Ã!ģŨŽ@ `Be#„����\MM !sæĖҜ9sڝŗX,Ǎ¨P]]™%ļÚ��� “‹ŠŠ’ĶŲ~ãđ—_~YK—.•$555iÚ´i’ZCˆÛí–Ũž(pJ„mÛwhŅ’ĨÚŗwŸ<ž:EE9ÕģW/]yÅ8õėŪ-Üåu¨ŧĸB͟{^û÷hÂøë5dČāvĮc.îOHSsŗ^ũēV|ŧZUÕÕJˆ×c/ĶøëŽ’Íf ļ{gū"Ŋ3‘Ę+*”š’ĸÉŽ×č‹F†ąr��€Ķ—Ûí–ÃáΜ9SK—.Õã?.Izä‘GdąXtË-ˇH’âââ–ZĨS „ämߥ?ũå ;÷ŨyÛ­rŸTYUŠ‹–čÉ?=Ĩ˙ü‡”•™)Iú`ŲríŪŗGwŪvë÷zÍPôķņĒÕ*,*Ö¯ø…ŌĶRĩlųŠvĮĄĒ÷{<žúÛsÚôÕfũøæ)ĘČHז­yú÷Ģŗåķû4uâxIŌĸĨę…ŊĸŨ4Y}z÷Ôϝ6ëĪĪLWLt´†=Į´Z��ĐęÛŖ………züņĮ•››+IzüņĮ5{ölI­OĘ:¸X=\ÂB–-[ތŒtũäŽĪåæfĢ_ŋ>züžÔŽüÁ˛wīžŧf(úņzŊJJLTß>Ŋ:<…PŊßcUįõęŗ_čîÛoÕč‹[G5ÎčßWģžŪŖÕkÖięÄņ2 CsŪxKW_1F7^ĩ$iā€~Ú_P¨ŲoŧE��‡ÃĄššš`ĀøíoÛîznnnđ\KK‹|>_X7, {iņųä÷ų;åŒŌ?öģāņžüŗļmĪ—$}˛zūëw˙Ą.]â4gî<mÍÛ.oŊW ņ}ņEsé%Áûîģ˙×ēúĘËĩykžōōļ)77Gų;wĩë''ģkûšZZ4ī­wĩnũÕÔÖĒK\ŦÎ>L×_{ĩl6›ūûOû¸õŽģ•˜ ŠĘĘāņ7\§^={jŪ[okACŲ]ŗ4ūúë‚!Åī÷ëŨ ĩvíUTV*!!^c/­K.uÄ÷ûí:CÍŖ×_yņ°ķV›M6këTŦĸâ•–•køĐsÛĩvîŲúĶĶW}}ƒĸŖŖNj���h/66VĒŦŦ”Ëå ŽŒ\¨~pÆÆF•••)""â°5$f {9ëˁziæĢzö3tų¸1ꖓ+ĢõđG‹ũüŪ{ôğžVjjĒnš2Iޘh=ķėt—ĐŨ?š]]âbĩ#§ū5ķ%%&hČY­›ŗØlv-_šJƒ Ô5W]ŽÔÔTũųŠŋļëįÛfž:KŸmüBĶnšĸnššÚõõnũ{ækjniŅÔIôĀ/~ĻŲsį)?—~č×ōüzëíwƒĮ’Ą‡ū† =G?šv“dúpŲr=õĖßô—'˙ ˜˜hÍycžVŦXĨi7OUĪŨĩ%/O¯Íž+›ÍĻQ#.ėđũšŠŠšYõŪz}ēnƒÖŦ[¯îŊG’TPT,I‡M9;x\X\Ŧ^=ē›Z+��ĀéÎáp(++K•••*..–aĒŽŽ–ßīWbbĸ¤Ö@bˇÛ­ôôôĶ{aú¨Q#TWīÕ{ķkÃgåŒR¯^=4dđ 7|¨"##%IŅQҞZm˛ÛmŠuˇîė8uŌDYŦVĨ$'I’ŌŌRõáōÚŧek0„X,’ÃĄ‰7Ū|Ío÷s(O]V­^ŖÉÆkXÛ_ûSR’UX\ŦĨ,Ķ„ņ×+:*ZvģŦVk°C ‹‹ÕĐØ ķĪĒĖôtIŌMS&ičšįČnˇŠĄĄAË>ZĄ+¯§ Î.IJMMŅžŊûĩ`Ņqa‡ī×Lŋ{ė÷újKž\11ē˙Ūģ5jÄų’¤úúzI:l´#**Ēízƒš…��@’”””¤.]ēČįķ)Čī÷+("""¸iĄÍf“Ũn?ĻũDNϰ‡Iēōōqēô’‹ĩ5o›ļlÍĶ–­Ûô¯™¯ęŨų õĢîSfFF‡÷9##5ņåmÛ.§N†aČëõ*-%Ĩ]ģžĮņ—ųũû Ôã[OåꞛŖæĻ&8p ¸FåHŌSS•–šĒĪŋ¨‹/Ĩ3ú÷WNNWõíĶ[’´m{ž|>ŋÎčßŋŨ}ũúôÖʏWŠąą1ŦÃc’tĪ?VeUĩ6}šYųëtyŊõērÜea­ ���Gf†<<š››UZZ*ŸĪ§ŒŒŒāŽé111Š‹‹ ëzé !’ŠŗŌYƒI’ļnÛŽŋOŸĄ9¯ŋŠ_ūâŪÃÚû|~=ųô_eøš:eĸŌĶSeŗØôĖß˙qX[gÔą¯Q8¸}Ŋ3Ē}8 š›ŽÚ‡ÕjÕ˙{č×Z¸xŠV|ŧJoŧųļâuÃõ×ę‚ķ†ĢĄí5ūø§ŋČĸoRhĀ0$I55ĩa!š9ŲĘÍÉ֐Ág***JĪŋô˛F_4RŽļGšyŊõЉūfŠ˜×ë•$ͧ�� 5€äå劤¤Dąąą˛ŲlĒŠŠQ P]]ë끀 äķųÔģwoĨˇÍØ ‡°‡šęE:#ûŌŨŋo}öYÚôåæīûz÷×*((ÔÃūZ}z÷ ž¯õx”œ”tÂõ , íÎ74´N3rãĸëØXˇ&O¯ÉĮ̰¨HK–~ į˙ų/e¤§+ēí5~rûmĘĘ:|”'!!ū„ë˙>Ę+*ĩéĢÍ:øPEōûčÖ-GÍÍ-*+/WVfkŊEÅ%Áip’TPX$ĢÕŧ���ķxŊ^UUUiøđኌŒ”ÅbŅāÁƒƒ× Ã.N/..ÖļmÛävģår™?í_’Â÷p`ĩūÅ˙ÖÂÅKģf†ŠKJÔ%.ö[Z´´ø$I.×7yßšk—ĘË+dlô]ŽĐ$;+KVĢ5øôĢoúŪ­¨(įaSŊ:RZVŽĪŋØ<ÎĖČĐ´›o’ÕjQaQ‘˛ģf*ÂnW­ĮŖŒôôā?WŒKnˇģũđØ1ŧ•PŠĒŽÖŸŸ™Ž5k7´;ŋk×nY,Ĩ¤$+=-UéiZŊf]ģ6ŸŽŨ ú×đ���Ā<åååJNN–Ķé”ÍfĶO<ĄŊ{÷ׂėÛˇOO>ų¤‡ät:ƒ3€Â!Ŧ#!qqą7æRŊ;Ąjjk5xĐ šbĸU]SŖO>ųTųųģtĪ]wÛĮÄDkīž}ÚģoŋâããĄ÷?øH×]sĨ ‹ôú›oëŒũU\r@5ĩĩŠ‹íđuí'!!^îC ËŖ^ ų‹+%5EŲ]ŗ´}{ž–}´\ãÆ]Ön×đ#ŠŦŦÔŗĶŸĶÄņ7hĐ ˛ČĸO׎“ÅbUĪîŨĨQ#/ÔÛīž'ˇ+FŨēuSEeĨ^›=W ņ]ôĀĪī=j'C¯Ũ5dđ™úĮ /ŠžĄA9Ų]•ŋs—^ķŊôbEļíÂ9e zęŲį””˜¨~}{iíúĪĩūŗúũc˙yRë��@ĮZZZät:ƒ Î333õČ#´Û1}ėØą’ZŸ¤eˇÛÂV¯íĸ‹.zÔãņčę̝ū^•”U*Öuü[ŋčßOÉIIÚ˛uĢV~˛ZË>ZĄ­yyŠÕ´›§hHÛŠu‹O>]Ŗå+Wiā€ū:c@-˙øcÍ_¸XJËtÛ´›”‘‘Ž•+?Ņį7iôÅŖ´déĘÎîĒūũúvØOŸ^=•ú­Ņ3ô—ˇÎĢ —jūÂÅÚģoŸÆ\vŠŽšōŠā/ö˯6ĢŧĸRŖ/uØqRRĸ’’’ôҊĩ`áb­üxĩšuËÔÉęŲŗGđ}777kņ’÷ĩ`ŅmŲ˛MgôīĢi7O Ž„­ÎīâŠĢWzJâq˙>Î~Žęęŧšŋp‰-ũ@EÅ%ē|ĖhũčæÉ˛ˇ°îŨrëÖģ ëŊ…KUįŠĶĪîž]įž}Öqŋ���žŋŨģwËjĩ*ŠmY AƒTWW§įŸ^˖-͘1c4mÚ4IR ĐÖ­[åvģ|Ë�JĘ*Oč;æĄæĪŸ/ËŖ>jiƌßĢŗ[ō•™–üŊú@č–”éŦĄÛŊ���§Ž-[ļČår)''§Ũų9sæH’&Mš<×ŌŌĸuëÖŠk׎ĘÎÎ>Ž×Ų¸%˙{ĮŧëŽģÂŋ0���Ā÷ãvģåh›:¨CÃĮĄâââsüŗ˜B%Ŧ Ķ���|Įŗûša˛X,˛ZÃ!���@'įp8äõzeG´jKK‹|>_X7,$„����\llŦ<*++ÕÔÔ$ŋß/ŋ߯@  @ ŋ߯ææfÕÖÖǤ¤DaŨ›5!���@'įp8”••ĨĘĘJË0 UWWËī÷+1ąõiV‹Evģ]ŅŅŅJOO?Ž)\ĄF���~�’’’ÔĨKų|žāčG PDDDpĶB›Í&ģŨÜv"\!���Ā„ŨnëĮąbM����S…,„X,Ža5>���€Î§šÅ'GDhFYBBĸœÕÕՇĒ;����§ŠĒE9#CŌWČBHNfšęęäņÖË„Ē[����aÔÜâSIYĨJ+Ē••ž’>CļjÅéPßŲÚ[Xĸ˛Š*ųũ‘pÛ¸%?Ü%��� “sDØåŒTŋž9!›ŽŌĨķÎH‡útĪe—����~`x:����SB����˜Š���ĀT„����Ļ"„����0!���€Š!����LE���`*B����SB����˜Š���ĀT„����Ļ"„����0!���€Š!����LE���`*B����SB����˜Š���ĀT„����Ļ"„����0!���€Š!����LE���`*B����SB����˜Š���ĀT„����Ļ"„����0!���€Š!����Le?øĨÕáŦ���Āi"BR섺����§ Ļc���0!���€Š!����LE���`*B����SB����˜Š���ĀT„����Ļ"„����0!���€Š!����LE���`*B����SB����˜Š���ĀTöp���€Îmמ}zqælÕyëÂ]ŽŠŦVĢ\1Ņēí–Éꑛ}L÷ŦÚ#Mœ-•y%Ÿqrë ģEJŽ‘æN–.Ė MŸŒ„���ā„íÚŗOOOAĩžēĶ.€HR Pm­GOOAģöė;jûU{¤‘/HÅu#€H­u{Zë^ĩ'4}B���pÂūųōėp—~‹dĮôYL˜#u’ėŅ^ë[Ԅ9ĄéŽ��€æŠĢ w §‹EŪúúŖ6;ā1Ą–“Å"•‡č×M���BāXĻŖuĘQC„j !���€Š!����LE���`*B����SB����˜Š���ĀT„����Ļ"„����0•=Ü���āôuîA:čŲĘĖH“ÍfSUUžøj‹–­\­ú††`ģ?<ú[}ôņ§ZōáŠ0V^Iˇ –n?G:3MОKĩŌ‚íŌWJE‡ėÆūöMRnŧ4øŲö}L<Cš=IúųéŲ5Ļ–ß!���a1mōxsÖ™Úøåf}<oŊ|>Ÿrŗŗ4ō‚a:ëĖ3ôôs˙”ĮSî2O V‹4kĸ4á i֗Ō?ÖJžĻÖ0rßyŌ”3ĨK_”ž<pä>FäH/ß(ũņãđ‰��€0~ÎY:wČ Íš÷ŽV¯Ũ<˙å–<­ũlŖ~}ß]ējĖ%š5īŨ0Vyęøé0iâ@éĻšŌk_~s~Áéų Ō'?‘æN‘<#ųÃīī—,Ŋsŗ4û+éáĨæÕ}$„���˜nԅÃĩwAģ�rЁŌr=ķu Ŧüˆ÷Ûm6]5îR=x ÜŽÕzę´îķMZ¸t™€$ŠGˇ]=îRe¤§ĘjąĒ°¸Dī.z_ģvī•$Y­V=JCTB|UW×hŲĮĢĩęĶõ'įMŋ8Ozgû�rPyŊô›Å­!ãŠ>Ō{ÛÚ_OsI‹~$­Ų/Ũņ–9õ !���Ļr:#•™žĻĨËVąMAQņwö1ņ†Ģ5h@?Íyë=í+(TnvWMēáj9"ėzķŊÅrDDčîßŦ _|ŠYķŪ‘Eŧ`˜~zĮ4=ōø“jhhÔuWŽÕÃĪҜ7ßĶ×{öŠo¯íōûüútũįĄ~Û',Ũ%õL”ū÷;˛Ņû;[ŽĘmB\iÁ4é@4a–ä œÔR!���ϊsģeąXT^YuB÷GGGiØŲƒõÖü%ú|ĶfIRyE•ŌR’uņˆķôÎÂ÷'§3Rë?ߤĨ­#*oŧŗPŸoÚ,ŸĪ/gd¤Fœ?Tī/[ŠuŸ}!IZUQŠŽYēė⑧TɌkũš§úČm|RI”ûÍ9ģUz}Š4$CúÕ"ÉÛrrë<<ĸ���Ļ2ÔēhÁī÷ŸĐũYéi˛Z­Úŗoģķû åp8”œ”¨Ō˛ (+×­S'貋G(+3]@@;ŋŪŖ––ef¤Énŗ)oĮÎv}äīÚ­ä¤9Ž{s'AÛė2Eå›ģÕ"Y2 EJŠn]Äūû1Ō°Ŧ“Wãņb$���ĻĒŠõČ0 Ĩ$%žĐũNg¤$ŠąąŠŨųÆĻæÖ둆Ą§§ŋ K/Ąķ‡žŖk.ŋLUÕ5zoņZ˙ųĻ`?ŋû6Éøæ›ģÅb‘$Åē]*¯¨<ĄúBmMëĪnņGne—Rb¤}5ߜÛS%|^jöKRĨ7ĻHCū.•ÕŸÜz!���ĻjjjÖūÂb ;į,-ųp…|Œˆ Ø_>Ÿ_›ķļv­Ą-| 9##ÛŽ7J’ęŧõz{ÁŊŊ`‰ŌR’uɨ 4mōx•( ˜—gŊĄĸâßk[]]sØšp)Ģ—ļ”J“Ī”ūg…ÔÁïtiĪ֟îúæ\MSë4-Iš<[úâ>iödiĖK?AËLLĮ��€é>úxĩâģÄiÜev--5ESnŧVôíđŪÂĸuĪÍnwž[NW546ĒŦŧR‰ņ]4°˙7÷—”–iöŧw”ž–ĸÂĸų|>š\1:PVüį­¯—Įëí0…ĶSŸHg¤J÷ ;üZb”ôä8icqûr¨â:ięéĸn­SŗÂ‘���˜nÃÆ/ÕĢG7Ŋd¤ēfĻëŗ/žRSsŗ˛334ōüa*)-Õ[ķwxo}Cƒ>]˙šÆ\2Reå•*(*V¯Ũ4ō‚aúpų*ÅĮwŅĶ&띅Kĩ9oģ ŖuwvÃ0´{ī~565é“ĩtå˜KäõÖkīž%ÄwŅøkŽPuM­ž{é“?‘īöâgŌ¨nŌ߯nŨtđí<ŠŽŠ5˜Üw^ënęמŌņ(ÉA~-=ļLzt´´ļ@šˇÅŦęG��@XĖzãmĪßĨį Ս×^!›ÕĒōĘ*-YļB+>YĢ––#?Îéõˇ¨ąŠI“n¸ZnWŒĒĒk´øƒåz˙Ŗ%I;ŋŪŖWæžĨŅ#/ЕcG+⍸@Šž˙÷,••WH’Ū|oqđQŊąn—j=uújë6ŊˇčSŪ˙ņ0$ũč iņé'įJ3Ž•œöÖõ"sž’ū°âØÖzü˙åŌ9ŌK7H[HێŧËIeyôŅGĸĸ"͘1#<��� ĶēīÁ߅ģ„SĘߞxė;¯[1А“ČxüûŨ×]wą&���€š!����LE���`*B����SB����˜Š���ĀT„����Ļ"„����0!���‹Årô6&Ôq2ŲC”!���8an—K2Œp—~†!WLĖQ›Ĩē%u֏ː’Žū !���'ėöi“ĨcøÁŗXZ?‹Ŗx}Rįũ¸,–ÖúC��€Ö#7[÷˙ôš]ŽcšŽôCcąXävšt˙OīPÜėŖļŋ0WZy‡”æŨÔĻ“Ínm­wå­õ‡¤ĪĐt��€ĶUÜlũĪī wÆ…šRņCáŽ"ŧ:Iū���đCA���`*B����SB����˜ę˙�ПđOŲyy����IENDŽB`‚������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/scheme-webauthn.png�������������������������������������������������0000664�0000000�0000000�00000136765�14156463140�0022461�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��’��ö���BŒČ���sBITÛáOā���tEXtSoftware�mate-screenshotȖđJ�� �IDATxœėŨw\įö0đgf—-°•Ū{GPÅ.bkbĒą+ĻhĸInnōĻÍĪ›r¯÷^MŒÄ{%‚ ˆØÁŪYÚŌ;lafß?Æ;Yļ°‚°zžäŗĖ<Ī™3‹œĖėΜÁžüōK‚ är9�€žŠÍf …BƒÁDÕ××Θ1cܸqŊ��h•––vôčQKKK!D’äØąc{;%��ĐeüøņJĨ!„cN†aŊ��č‚ã¸JĨÂ0īíL�� k l�L ”-�€‰aöv��`¨ė+×-xųp´�01Pļ��&Ę�ĀÄ@Ų�ô˛û÷ī͝ķōōôŽ7¨lũ™”ė\[W×qÕČHÍ[‚:.é^Úâ§O[ũũ=ēi�@ˇ“H$ģwīÎĘĘB]ētiĪž=ē§TļŽ&%ģēē¤Oë†�@ĢĢëØącĶŌŌŽ9’šš:zôhOOOŨSô_�Q_ßpë֝ŋ_ũÛĻÍoŋųB¨­­íũeËBũ‚Š1—Đd2ŲōOūÖØØÔÚÚúųgŸN¯:žv")ų˜H,ĘĪ8}ę”ÛwīŪŋŸ˙ˇO– ûō›oKJJe2ŲûīÆGŽÕ1~RrJNnîgŸ~ÜŪŪ>f\ĖšĶÔr•J•°ōģÂĮE …"~ŅÂ1‘Ŗ xß��ŊiÔ¨QRŠôƍ~~~QQQzĮë?Ú:–š:6jĖčQ# ?–VT „R§;:8lŲ¸!bđ ‚ :]BĢĒĒŽ‰ž°kûīĢžũæ×čOmĮęęë˙oeÂüšsöėÛ˙īV.˙đƒ#&ĨgœT*•;ļnūõ—ĩߎZ­RŠtÄא~â$‹ÅÚūûĻMŋũú~RŠTzw�ĐģŠ‹‹ķķķAAAAQQ‘ŪņúļŽ&[úū{ cbLôą”ã æÍÉøpĀ€„Đ ˙:u\Bŗĩĩštųʑ?“H’lljŌXëë㍉„Ū^^!ĄPØÔÔt÷î=ę ĖÚƊÍfÕÔÖ戝áú§ÎœŊs÷Bˆ ÚëëÄb‘Ū}�ô–ĒĒĒ]ģvšēēžņÆØĩkׂ ėėė:ėîâáĀWč)[eååˇnŨ^ũũÃdm2€ŋ`Ū• aCŠH’Öq m÷Ū}ææŋoJ|\T´ü“Ī4Öâ8ãÉ Æ“ã>RaĻ"Ÿ%‘¤ ĮņŽņé–_fff įÍ}ãõ™ē÷ �ĐGpš\//¯ŠS§š™™Íœ9399™ĮãiŦP’æļ"='‰IÉ)īĖz+%éHĘŅÃ'ĶSęëŠ$OO÷[ˇī „ÎgeSÃ:.ĄUUUģ8;#„RRĶ” Ĩ!ģܯßÅK—BÕÕÕííí–bqĮø<žE}}=Bč^N.Rkē3 HFf&B¨ŠŠéû˙eČæ��ŊˆĮã͜9“Åb!„ĖĖĖĻOŸnaaĄmpYEƒ\īg[G“ŊúĘ ę5†a¯Ė˜–”œ2iâÄĮE_ëĢW¯ąŲl„PĮ%´)“ãöî?0{ŪBw7W…Bąo˙zwcü¸( ‹wæ.xoéGĢžũēĶøƒ>ūpÅ'YŲÕˇ86jŒŗ“ĶĖ7Ūž3ņĀũõn �`BœíXJRŒ}õÕ×ååe7nÔ?��zUŋa<>\%�01Pļ��&Ę�ĀÄ@Ų�˜([��e �`b —<�Ād ęāāG[��e �`b l�L ŽR‘Đ” �`T*•JE>ųHž°¸ŧwŗ��=)[.Ŋ›��>Û�˜([��e �`bđ†††æææŪN��ôknnnhh`ZYYÉåōŪN��ô …VVVp’�01Pļ��&FOˆú†æâōĘv‚6NNNîåk×ÉĪUė pŸ>):räĐŪN�Đ =ȅŧŌR$03ëūū6ŋoßŲgo*"Iōđą4([�ôMzNÛ ĸ'jB¨ĪÖ,Jß< � øl �`r l�L ”-�€‰˛�01x}csSKko§��†bŠŧÖfķ.ÍI9ž^W_íÚͰ°ÎŖGėŽl>\2˙įÛ‚xoÁė͞sōō‡G„+”Ę+×oŅcƎ.))Í/xŦ#ޝ—‡Ãļ‹ËĨ÷tWz�€žĀ˜‹bc& „JJËŪ~ã5„Đ?˙ŊvéûK8cí/ŋúųz×ÕÕ[[YW×T3úÜš ,‹$ÉWgL3$rۃ‡>^î%% MM~ž9yųū>;÷ļŗĩ:8LŠPb8ŪÖÚÚ?ØĪ×ۜÃÉžr}ĐđÆæY›ŒiÆĖTXđX˛dŪŦĻæfĮËĨ•B‚ü|ŧ¸NUMH(¨­k°ļ˛üãĪcĄˆđŗ^›ŽētõÆÎũ‡x��Ŋĸ>Û pųĘÕ;wīö Æq<(0 &z\CCczzŠRa8&­¨+†„ēy''$0 ĐĪ÷öŨ.‡ÍbąBm2Ų¨a8†ŠT*;kœį>x˜|<C(äD{csKkkkpŋzœ‚ĸâ[wsÚ "'/˙hę +K1IĒ<,<}>ÛÖڊCU+¨Y�˜œn¸”tČāAk×' øüŲoŋyöüyj!A’a#† uuqŽŠ­eŗX†„ǍĒļ˛ã üāŸ)V–â cFŪÍ}@­ētõfišT$„õVŠH„J…"GKĪ<SßĐÚ?˜$I ÃBę×Į*ÔĘĨRŠÔØÜĨĢ7.]Ŋņ ģ�čŨPļ¸\ŽĨXD¤……9B('7O")ą˛˛Œ=ōhRŠX$bāøĖW§MR\jcmĨloŋ“s˙ŗŪ]ųÃBgŗ.Ō‹lhh"I˛ĩ­Í×ÛËÉŅĄŽžĄ¸´,:jteuMKKksKkDØ�G{;&“YYU9bhqiŲŗī� ¯ÁĘĘĘ;]}ã^ž“ŊŪ(™§íílûĻĻĨ{¸ģûûųꝲę‡5]NöųZ÷ãĘŪN�đ”øøxGGĮnølëôŲķåååAԏ}úVC�€é놓ÄČQ#č×Ŗ'<{@��Đ¯ęí �� Kpũ\�@_Bmií-…a˜RŲūüŌé3¨k)��}!„j[íîdßCM™‡ ŋtõZßlˆa،¸˜ŪÎ�Đ9&BHVUĒmĩHČ y=ąáA>oΜŌ‘�/6!¤Ph^>��}ŽbąĖz; ��0ŽâØ;õv��`(!„,ôߋ��}\ˇ�01ĐK�`b l�L ”-�€‰˛�01z×446•U„Ö›� Û ōŅąVOŲ**̰ ͘ŨЖK‡Ri•î,�/÷ōuĐs’HdO×,��čøl �`b l�L ”-�€‰˛�01Pļ��&Ƙ˛uņŌ•/VÉ „ĐŪũTU×twVFÚ°eįÉ3ōöv"�€däÅ B‚9úæk¯R?>.:}öœ@Ā7įr1„546ąŲŦēú7WįÜÜûņ‹TTUž;wÅb‘$ųęŒi†lBZY•}ųËˌTŠ&ĮŒCŸËēė`o{7÷ūō÷Ō#KŠOŸŋčæâÔÜŌ‚ē~ën^ūŖļ6ŲkĶãŒÛ;�@_fäIĸ›Ģ‹9‡{/'—úŅÂÂB āķĖ-nÜŧãx`€ß”¸IUUUQ‘Ŗ…BAm]]fæiRĨÂpLZQAĻéu6ë’JĨÂ0ŦĸĒZŠT"„.^š3vü˜‘ÄĶäČžr#.zܘ‘à BČß×{rˏ†ÆF™LnÜŪ�ú2ã/%›ŗ>q“……B(%5-6f‚H$ētåBˆif†ã˜™!„Ḋ$B#† uuqŽŠ­eŗXn""l “Ŗ}]}ƒ™™BˆĒb¨ãĶĀT* ĮB$I"„pœZ‹!xf�/"ã˃ÁxuÆÔUĢ˜<iĸˇˇgŌąT[;[>ßĸēļÖÅÕEcpTTäҤąHÄĀņ™¯N7$ū¨aĮ3N‹ Į–”I‡ 8šrÂÁŪĮņĸ⒒2éđˆp„Đā°GSŌė1 #Iō~~AI™T,rØlŖ÷�Đga eee‰‰‰Žžq/ßÉžĮ xOb}C#Žã>ī×-;Ū˙NOg�č7îåk+ņņņŽŽŽĻtŋĄL.OJͰ ûøõv.�€^cJeËŪÖfҜ7{; �@/ƒËM�&Ę�ĀÄč9IÄ0LŲŪūZném ��=õČÃŲūų4e†îĻ��é)[B/DĀ{>Š��€!āŗ-�€‰˛�01Pļ��&Ę�ĀÄ@Ų�˜([��e �`bđúÆæĻ–ÖŪN�� Å x­ÍæŊ��JĪUō ÍĪįæ��@ąĖ˜nNö< ŽŽ1zĘVQY…•XønĨîvvL�ô)m2y¤,Č×CĮ=ÉiŠ5 �`ĸ¸ļBŲŽ{ |“�01Pļ��&Ę�ĀÄ@Ų�˜([��cˎ„/]9sîŧŗŗ“\&ķņņ9|ØÍ[ˇííėėíí:Ÿš–îîîāįĢ;fręņ¯žøŒÍbíŨ˙Įب16ÖVFäÖ]ŽŨŧ“uéĒŖƒLŽđōp>P}탇2š<$(€^rōĖWgG/]ßÚ�ē…‘7 ŠÚ¸ẏģ[Ii™š9ˇ¨¸87÷>‹Íōōô¨ŽŽŽĢĢˇļ˛ŽŽŠļ˛˛D>.:}öœ@Ā7įrīåä-} gŦũå×~@Å|čČŅ7_{•úQ}<†°†Æ&6›UWßāæęœ›{?~Ņ‚ŠĒĘsį.°X,’$_1͐´Ĩ•UŲ—¯ąĖĖH•jrĖ8„PaQņšŦËöļwsī/oĄúā!ũF „Úąī›‹“RŲ~áâ>ŸĮåp¤•IZ˜›_žv“Z‚ē~ën^ūŖļ6ؐAĄôHąHøāa‹eæîęâč`§ąu�€žõ$ŅËËŖ´´ŒzŨÖÚÆæ°…†ãxP`@Lô¸††F…R‰˛°°ø<s‹7o‡…¸|åęģwû‡ĶĄÜ\]Ė9Ü{9šÔęãq đ›7ŠĒĒ**r´P(¨­ĢËĖ<MĒTŽI+*ä …!ŲžÍē¤RŠ0 ̍ĒV*•Ą‹WŽĮŌ?f$AÚfy¸ē”K+,Ėš|>Īœ{''ĪÃŨu@p H( — „ü}Ŋ'ĮŒkhld2ôō6™ŒÅb Ôqë��#<ëĨ¤÷<œ6eRUu Bhø°!Í--ˇoŨš|íēĨXD HC!”’š3A$]ērmČāAk×' øüŲo?õ”é¸I1ë7YXXhŒG1ÍĖp33c"„0W‘$BhİĄŽ.Î5ĩĩlËĀ„#Â:9Ú×Õ7˜™™!„¨:‚ĸūÛŠ‡…'ŽsâÔšq‘#„BÁĩ›wŠåKpœŠ€:—=v4ĩ<"l@KkÛŊÜû7nßí¸u�€Œ,[W¯Ũ¨¨Ŧjii éä`oO-<uúlUM ‹iæėäØÚښ“›'‘”XYYRĸŪŪžIĮRmílų|‹ri…ĨXD¤…ÅSwq3ŒWgL]ĩú‡É“&Ē¯Ž­uquŅČ!**ōhRŠX$bāøĖW§’ö¨aĮ3N‹ Į–”I‡ 8šrÂÁŪĮņĸ⒒2éđˆpjđÍ;÷*ĢkZÛÚũ|ėlŦ=Ü\ŌNžąąļâY˜ĢTĒË×oųxzĐKjjëëęJʤb‘ĐÅɁ^~45CĨ"™LĻŖŊ‡ģ+ŊõŠąŒ{į�XBBBYYYbbb§ĢoÜËw˛ˇ1"njZē‡ģģŋöá32OÛÛŲö 4"¸! ŧ'ąžĄĮqŸ÷ë–īΧ‡’�îÆŊ|mŧņņņŽŽŽ=xŋĄJûĒĶgĪ———3ēįļn ™\ž”ša)ö đëí\��銲51Z×IPä¨=´ŨޞˇĩY4įMũã��}\n �01Pļ��}H›LÎ2Ķs¨g5†aĘövmšuã^~o§��čĒģŠî1zꑇŗŊé6e†îĻ�ŧô”-Ą€"ā=ŸT��ĀđŲ�ĀÄ@Ų�˜xŧ+�ĀÄ⿇ˇõ�L†I^Ų��čãH’ĖÉÉ)..–Édāp8ŽŽŽ8ŪåĒ l�ē_nnncccHHˆļMJĨ˛°°0777((¨ĢÁá#y�@÷“H$îîî, Ķ‚ÅbyzzJ$#‚ë9Újhl6ŨËM�ŊE&“ąŲlŨcØlļL&͏›…ēJžgÁÕ1QOŲ**̰ Môæžga`ģ.�@§?¸ĢŖc°:?´6™ŧ@RäĢëi2zęA/aÍ�<;˛ĩmÛ6„Đœ9stĪârØ eģî1P’��=Bũ+ÂÄÄĤ¤$„BĄˆÆČPļ��=‚>ÚĸjÖÚĩkB˖-Ãq|ņâÅĪžI�ôúKҒ’uëÖy{{{{{¯[ˇŽ¸¸˜^e\dæ™2äkä\��Њ>I\Ŋz5ŊĐĮĮGũGã0ëīßŧ/7æŌ ��ІÃá( Ũ×@Čår.WׅÚ0ųîũü8ü.ÍšxéʙsįB"Ą`ŌĘN‡ŨŧuÛŪÎÎŪŪŽãǜÜ<™L†ã¸ļęRާ{{yøútÃå˙^ûķōeĐ?:rtPX¨‹‹ŗĶ[ÛÚvėŪĪd2…ÁđĄƒwī;Čį[ØÚØD Ŗ_O›ûėy`ę\]]srr‚‚‚´U.š\ž““ãâĸųüSC0lc>•ųäqaI)ĮŨ]]H’,+/'I˛ŽŽŪÚĘēēĻZ$™›soÜēU^.  åķų§Īžøæ\nY™” [[[ssŽ’h?‘‘)‹p„ŗŲŦÚúzkKËĘĒĒYoŊĒ­Ģ­¨ŦŠ™péĘÕÜÜû,6ËËĶÃŲŲéÜš ,‹$ɈAái' ϟ¯ˇ‹‹s§Ą†F N?yĘĶŨ­ŠŠY=ΤؘÍŋo{/~‘{‘y†Ëå¨H•ŊŊmRJÚk¯LuqvZõũŋ¤Uô븉ŅL&Èˇ€I@@@nnnvvļļ{š\Ž‹‹K@@€ÁüHūĘÕëģöîßĩw˙å+×â&F_žríڍ[1Æã8=ŽĄĄQŠT"„8ŖHpHp? €Ī3ˇ¸qķļ——gxØ@ęĪûÔŠ3ąŅĻO™\T,!H"ĀßoÂøąŌŠJjCˇīäôBĩĩļą9ėAĄ‡‡efž&U* Į¤é'3§LŠûÎ[Ū^^ÚBŊũĘ´)ŅÆ1 õ8l Įđļļ6÷ē\Zčī7gÖé§jjj­Ŧ,BBŋĸ˛’~]ßĐ`Ü[ Ā FĨŌņŦT=kucŽvDååæú>MũhĢŊP(•8†ŠßD$†ž|ÔĪápB)Šią1D"ŅĨ+×4ĸŅ_(`c=}ãeSc“ŗ“Bhø°!Í--ˇoŨš|í:BhİĄŽ.Î5ĩĩÉĮŽĢT* Ãä2™öP* ĮB$IĒĮyû×BACCŖ'Ø"‘zÁfŗ,-EÕ5ĩŽÎNuõ övôk‘PhH(�^l=z+ĩ‘×m]šzŊ\ZAŊfŗĖ&Œ"ۉCGūäķy9šyI‰••%ķé§y{{&Kĩĩŗåķ-T*UÖÅKöĄąQ‘)i'DB‡›;ŗÃų>¯ąą !tęôŲǚĶĖŲÉŅÛÛëhRŠX$bāxTÔčŖÉ),ËÛÛS[¨áC#ū8xØŲŲ Ãpõ8ĄÆ†FĐЏöĸĮųmķöë7o{{zDŽąsĪĪ"t@ČāđPú5œ!€’H$ũû÷×VŗBÔ­ÔˇnŨ2ĸla_|ņEyyų–-[:]}ã^ž“ŊááRĶŌ=ÜŨũũ|ģš‡65ĩĩG“SæÍžÕ]ÕÉŠM[ļŊŋ¤“ĪļāžD�žÅáÇGEŊNHH˜={ļ§§'B¨  `ûöí ÔĒŗgĪNŸ>]cî{ųÚūúâããģ˙rSãOX;ceiigkû ŋGžxx,åø”I{"2�€žĻÔŲŲyŊ………………+VŦpqqyÖËMģ7҉Ņē7 B(6ĻûcRfL›ŌC‘�tUĸnåYž|9B(..nŅ"Cŋģ×îI�ôõ[Š—,YB}57wîÜg e �Đ#4Îį͛gČŦ6™œeϧ.éYa˜˛ŊũålšĨŅt�Đ%~tÕiwSŨSôÔ#gû—ļ)3|“€Ņ?¸kā#yŒøCĶSļ„^ˆ€×Õ �€—\ŪJ ũļ��Ũē•ZĄPh{rBĄ0ūVęnO��zôVj([�€î‡ãxPP7îŧ'‚�@΁Ŗ-�@÷#I2''§¸¸XÛI"‡Ãquu 0đ GuPļ��Ũ¯G×ĀI"� ûI$www‹Ĩí›DĒqDb˃,˜­2…LĄÔļēĄąųĨŊÜ�`4™LĻûĸ-„›Í–Éd^%ĪŗĐu=S$āĩ6kínZTVa%žœ7÷ôĐü ˜œĮîxsÆŋí6™ŧ@RäëĄcŠžzD$Ô,�€4ĘÖļmÛBsæĖŅ=‹Ëa+”íēĮ@I�ôõ¯“’’B …">>ū#CŲ�ôúh‹ĒYk׎E-[ļ ĮqĒq Ņā›D�@ ŋ4,))Yˇnˇˇˇˇˇ÷ēu늋‹ûVSf�� Đ'‰ĢW¯Ļúøø¨˙h([�€î×į×\ŧtå˄UÔë˛ōō…KŪ'ˆ8ĪĶÍ[ˇĨ˙{°Ŗ)ĮĶäįWVVmÜŧu÷žũé'N<5-=÷ū› Våäæ]ŋqŗĶ˙^ûķĄ#G‹‹K ŲĨ\Zņ¯˙Ž˙mËļ#I)†Īāų苍k,ÅâœÜŧĀ�˙sįŗ}||B…‹NŸ='đÍšÜv‚twu!I˛Ŧŧœz–ĪŅä?_Ÿ¯ũyØĐ!šš÷Yl–—§‡ŗŗĶšsX,I’¯Î˜†:–zŧŽŽŪÚĘēēĻz˘ŅôZ ķōrixXhHp?„ĐĨ+W; âââL/GҝĢĒĒÍÍšJĸũDFĻX,ÂÎfŗjëë­--+ĢĒfŊõB¨ļŽļĸ˛*6fÂî}&ÅF;:8Ŧũeƒ§§GšT:rø0ŊđķŖ§[YY"„JJËĖÍš7oŨnhlbŗYuõ nŽÎšš÷ã- Ve_ŧL„H,ž|ų •íĀū!é'Oyēģ555OŠŲüûļ÷â }¨IRJÚk¯LuqvZõũŋâ&FÃceAŸŌŖkŒüH~𠰋—¯Č …B.āņBŸgnqãæí¸‰Ņ—¯\ģvãVĖ„ņįļĩļą9ėAĄ‡‡efž&U* Į¤r…Qũ.bĸĮ544ϧgĐkU*Ô?$˜ĒY:‚465ĶËÕĮPŗN:=aú”ÉEÅ‚$üũ&Œ+­¨¤ÖŪž“Ķ?8!ÔPß ‹B|>ĪŌRLÕ,Í7Į5ĻĶËüĻÄMĒĒNJŠ- jëę¨U^^žáa/\Čĸŗ=uîü+ĶĻDOĮ`0Ø,Žámmmū jkë¨r)đë œĀsŖRézhĒîĩē13K‘×?ÎgŗXæ™§† ”‘y!”’š3A$]ēr H…R‰cAÔQ�†ã*†R(”Ç iniš}ëÎåk×B#† uuqŽŠ­eŗXę› H!Œ^{íÚ ęEmA|>ŊüĩWĻ͝…5‘ūōCëé›<››œBbą¨ļŽŪɁÛĐĐ(ā čę{bišG”if†ã˜™ķÉRķÖ(:ÛŖÉ)Ž#„H’D „‚††FĪö­­-Ģkj]ęęDBĄ!S�xnzôVjfmÎõEĄiEŽņÛæß'ÅDSeËÛÛ3éXĒ­-ŸoņûöÆG‘íÄĄ#žöę „Ÿ×ųŦė’ŌRĨ˛ũÔéŗU55,Ļ™ŗ“Ŗˇˇ×ҤąHÄĀņ™¯>yĻvNnžDRbee9z$ŊV(¨o][ĄP@/WĶÔԌ™’vB$x¸š3;\ũ/āķ›BcŖÆų3ÉÜÂŧ_P`qIIIi)uĀĨžFŧcöö'Nž?~ÜąÔ4*ÛáC#ū8xØŲŲ Ãp„PcCŖ@Č70Ú䨘{đxĄBā ô5‰¤˙ūÚjBˆē•úÖ­[F”-lØė¯úq wtēúÆŊ|'{›Ž}Šiéîîū~žĪsŖęđgŪėYĪĶ!šBąiËļ÷—tōŲܓLÎáÇGEŊNHH˜={ļ§§'B¨  `ûöí ÔĒŗgĪNŸ>]cî{ųÚūÁĮĮĮ;::âLvŸûĩņ§ŧĪÆĘŌŌÎÖöA~ī<ņXĘņ)“&öĘĻč ô—†ÎÎÎ+VŦ(,,,,,\ąb…‹‹Ë‹vš)õÍco‰éĩ­Ī˜6Ĩˇ6 @O Ģu+ĪōåËBqqq‹úuš6Ė('TVĻĩq ��GũVę%K–P_ŠÍ;÷Ų#÷šŖ-�Ā‹AãpŪŧy†Ėj“ÉYfz꒞Õ†)ÛÛĄåV¯Ķč� @ßgāGWv7Õ=EO=ōpļ‡ĻĖ}|“LËãw |$˙ļõ”-Ą€"āu5(�ā%×įnĨ��Ũúâ­Ô�� CŪJ e ~^¸�� �IDAT�Đũp 2âÆƒ‚÷DP��č9Pļ��&Ę�ĀÄ@Ų�˜([��e �`b Ŋ�ĸĻĻĻ´´T.—wė�ķ试Üs3'͉Œč¤s<��t/CËVQQ›ÍË+jJÎŪ>ZĶ(M>ĩĘ�ā90ô$ąššĮqåĶ*kËS˛ö4Ékœ]ÂüGlÜ´åâĨËÔ{4iJVöÅôFLĖ<uĻđqQˇ„�<ĖúÆæĻ–VŊã I’AT֕ņ¸|sŋžšúä?Ęō]]Üb"Ū˛ĀlîUįŅãī?Č˙}ëv{{;Įīåä&Ž_÷͡ĢüũüĻLŽKXõŨO߯Fũŧ~CÄāAƒÂÃ,^2cÚÔŦ‹—¸ÎĀũũü|÷8ČápH’ ÄNŒnnnĻLŠˆ:ōgAļļļ{÷ 6´ėƒ÷B7mĄĻ899ŌqĸÆDîÜĩĮ××įd抨1‘|>/''‡xæėy‚ ė“’S4ļ;zÔČ{ķ�Æ`šsX–Ö§kĐÚÛÛI’,*’ŊÛË%`pĀØËųiŌĻGB1?rāTWk˙Į…ÕĮīÜĩ'~ņBwˇø÷–ÚØØT×Ô0ĖGŲ/ŽŲI!hll2įrŖ'Œ ˜°ō;333Į>zÔ?$$jLäčQ#÷ėŨO Ļ„…‹EĄ7ÔŌŌjaaÎ`2Š)ß|ģŠŽSYUõņōœĶŌOtÜbss‹X,úãāáŽÛ}Ö7�ĐŨ ũlKŠTļˇ+‹Ģ6)ĢōĨ­ĘŠYOĀ õŒō° –ĩÉ …úxõööŖGßēm‡¯wūÃG˛/Žøp)ĩĮqę~™Lūƌiuuõ§NŸIIMCŊ2}Z@€Yyųņ´!õ_ųy§Âņ'¯¨)ęqÖŽ[áBˆz´—F@?_ņôv�}M—Ę1j@œ™9z ŊڊU -ųNÃüŦ‡6563 ĨRŲĐĐHg֛ŋmÚbcmÜ/hôčQ_~ķíĄũ{ŲlvĘņ4>˙ÉŖ�…‡<täūƒ|š\žs÷Ū’’R6›åįį:`Ũ/ŋÚÛŲá8ncķäqgę¨%î[ˇī˜?w6Ŋ!Ū=īĖz‹Ž3}ڔZãëëÃ`āz¸ģmŨžcÁŧ96nŌØ.� ¯Ážøâ‹ōōō-[ļčˇoß>wwwƒÁdašŌėGuW]ŁūV#F’$ƒÁ(**zũõןOŌF(—J™ ĻõüEKļlÜĐÛé��ŒA='ŅĐŖ-ęŗ-ĨRÉŸÃD<k+Ž!Gíí톊TĒövcÔüÜ´´´üsÍĸƌîí\��Τ e‹ ‚ ZZZ8ĮÁÜŋŠŠ‰$Iõ=“a÷đöōÚđËÚŪÎ�Đ ŊnËÜÜŧŠŠ‰úô[ĄP´´´¨T*ēŋjSS“š9<l�đ<z´5pāĀĢW¯uŧšÃ0‘HŪŨš�@' -[ŪŪŪŪŪŪ=š ��:@��L ~ĻŦˇS��€Ž`Ößŋy_.éí4��ĀPLž{??ŋˇĶ���Cá 6<*�`Jā#y�€‰ÁG;"„āJQ�€Éč†^ō†ąŲl'''++ĢîN��4=k/yJ{{{QQ”-�ĀsđLŊäi8Ž755=zTĐ)Ē7z߲uûų Ų=ą Õ_ŖŲü‚ÅKŒ‹Öąi}G=ŨÆÚ䃏ĄG[t/ųN×bĻT*BĮRRuˇ„§ÚoÜü{uM9—[QYāŸ}é?k~JܸšĶÖōTŖ÷ÇQwk§ĻĨ]ČĘjjj~ũĩWwīŨgcmÍįņ˛.^Jüõg„ĐûK?úėĶôv?YņBčÎŨ{ôČE į#„æ-Œ§Į/ûā=z-ĩ;÷<āķyõõõ[ˇī Ž­­Ŗ–kÛģ�?z9Bˆ~-).áķy …‚nĢĪår+**%IÂ×_"íņķōîoŪēÍĖĖlPxX`@@§œœœč×ŗŪ~“Î'.v"=7;û’úģĀ ĀĐŖ-ĒߖTãĒA{Lô„؉1;vî&IĮņ‚ÂBRĨRoÍÎ`0†˛ôũw‹‹KfŊõĻ­TZĄž9õ8aĄŖ'ŒŖ˙ę"GüėĶ‹KJDBĄĩĩP(ĖČ<5aÜØ3gÎffžŽ™0^}ģ2™ !¤>’ ĸ>žãZʁƒ‡>^ūá‚ys˜LfĮŦÔˇRSSK/WCÍĸÚę´ėƒ{9šA 1îlú(ŒÚ;Ē#>5ĻĨĨ!´c÷žĨīŋû+ÂCCĩE`0ôkõ|ļîØIĪÕx÷�xtŠ)sģŽĻZJĨŌÚÚJoKx‹ÅÂqœÅ2CáN„ļÖōtŖw —ûä/pÃÆM‹ΡˇŗKNI‹‹]õŨ?’øîÛonßšCo—ÃáhŒ¤æĒ˙î?hŦĨ¨T*ÃBņ¤­˜ļŊŗļ˛ĸ—˙ũo͝mlŦŅĶŨî1 cŗ9ßŊŽņ1„‘$‰aXKK‹Žę¯é|Ö˙ú=WÛī �ĶՅ˛EUŽN×ĒT*ĨR) ˇlŨŽģ%ŧÚZËSŪÃÃÃ,ÅbõņĄü˛>ŅŨŨÍR,*((äp8fff\.WŊ…ügŸ~Ŧ1ōÖí;ũC‚ų<=^}mYyypp?*ūŒiSøį?_úÉ:ŪĶËÕĮÔÖÖĸ§Ûꛙi>$ÉCKG|j/¸nXč@Ũ(ę{­>—ŠßņŨĀtuĄ—ŧ››A^�Ņ÷{É�^�]î%¯úŸŽkû~/y�Ā ŖËŊäu čĻ”��@—.÷’īô’�<7ĐK�`b —<�ĀÄ@ã�€‰˛�01Pļ��&gąXÚ.ŧ�€>Žļ��&Ę�ĀÄ@Ų�˜č%�01ĐK�`b‘‘‘MMM“'OÖ=.??ŸĮãiëqĘ`0ęëëŨÜܞ%•Ŧė‹šyy••šyy^^ž:ƨ¯Ĩ–IІ‰E"Ŗˇžyę °ÜÜŧÜŧŧ3įΡ´´ēēēčÍV[ž”›ļ(•ĘųÕs[°xÉÔÉqŨžŋF¨ââ’/ŋN+æææÚÂ樅ß6m&I•“ŖŖá[߲u쯛FížŗŗSWwġīÜũōë„đđP>˙É3Õ×üg­X$˛ļļî‰Í>"99™Īįw[/y…Bq˙Aū_-Ī9Ēyŧ­-Ũ×<¸_Ž.īųQũŧ0 sp°OJNéØŠę+/‹ųS}–››ĢŽŽíƒÂÃč“b'"„âß[š¸~Ũ7ߎō÷ķ›29.aÕwž|>ęėîããMwŦgÖ[tLĮén÷V–VtöŽvĻī¸×ēķ§ēÎĢw—W'é>ũĮRŽĶĄŽĨ¤â FaaĄģ›Ģ\&Ŗ'z{yilZũ­Ļ~)åRéã"Éâ… ôļü˙tÅr:2Rkķ˙ŨĘoč=ŌÛt_ĮcÔû÷ģēēŌɗ–•á F‘D˛uÛjâī-ųäŗ/~ūī#˙ €éčBãęĀĒĶĩT/yĒ嚇ģ[ü{KčO5˙_',}˙]gį’’ŌM[~733ÃqüáŖG2™ŒÃáĐ}ÜĶOd˘6U,ÕÕՋÅĸ?ĻGÚÚØ˜sšŅƇ…lnn‹Eövvŗĸ˙um§:$blԘ9ķųųúŌ¨„Ēkj æŖ‚‚ė‹GY\R‚ (‹JJË"G?nėœų‹ÔcúųúŌģL¤ú^Pé ‚TīLŸ~"c\Ôz<ՙŪÕÅåxZzĮŊ֝?ę.ßņėBŊÕRijäČRŠ´ąąIc"BHcĶęo5õK9{ö|䨑č-˙G 6gūĸÕĢž}ø¨@Ŗåŋzä'3é7íŠ1;wwúĢÜģī�ũ:aåww‡šNõ槆•””ŌÉ/^´@*•?žNOD1ŒĻæfúāE՝Ŋä5ZžSÍãÕ{ĸ#ĩ~įÚēŧĶ:íÔNõ•×6K[Īuõđ_ų9BhĖčQ[ˇíđõņÎøčBöÅ.Ũš{¯z(ēcŊzLã¯n÷ęƒģڙžĢų?Yĸåėا_ƒúÄģ÷tÜ´Æ/ĨĒēÚĮįÉmķē[ūk¤Dŋiô6Ũ׹;ęŋ;šBŽ;ykkĢęę([/ŧîė%ßiËķ§{ĸëęōNÄ‘ŖI3ĻO;tøČ‚ys6lÜÔąS;ÕW~ČāÁŗŧŧŧöŽíęũŨŠ%C†D|ú÷/íßËfŗSŽ§ŅŸxx¸oŨž#0 €žĢ3bđ ēÛŊ‡ZöŽvĻī¸×ēķ×ũNŌ}úŠ”¨PÚ&vÜ´ú[MũRŦ­ŦĒĢĢĩũKPoų¯YÛxT;mē¯ãąęØ |aác.—Ŗņ/ĒēēÆÚĘR[&⅁%$$”••%&&ęŊä_eååë~ųõß­ėíDēF&“}ōˇĪ^ûīŪNô Ē—|ž“¨Ō š2ŋ0<ÜŨŽ\ŊÖۉtÍú ŋ}đž‘ĪĻzɃN,^¸ ˇSč˛-ëíĀsŊä�&zÉ�L ô’�˜č��01x}}}SSSo§��ú555Õ××3E"Qkkko'��úņų|‘H'‰��e �`b l�L ”-�€‰˛�01Pļ��&FOŲJJNy{öŧ•ßũ#aåwúwCî—^°¸GîÂ7.,=++ûbú‰ gež:Sø¸ˆšBŊÖ6R[؎ŗ L@÷æ��Ȑ›{b'FŋũæĄV|*•VÔÕ×˙Õ+Ãč~įS§LĻ{Ĩ?ÕTžËíØœę§Ū&\Ŋ›ø´Š“é×cŖÆĐaŠ|~^ŋîe>cÚÔN#tœE5‰ˇĩĩŨģī�•Ø˛ŪCuÚ.ęūNMĄZŗ×ÕÕíÜĩĮ××įd抯ŋüBo;v*BÆÉLúũimm#"?˙Ą¤¸$vbôģ÷:Ũ Iq‰ŪĻō�ŧäôŸ$ϟ8ųÃO˙z{öŧĐЁÎÎNtôŒĖS c萈ųsg>.ĸzĨ/˜7‡ÉdR=Ņ?ZöÁŊœ\‚ †˛ôũw‹‹KfŊõĻ­ Ũœj=!vbĖŽģI’Äqŧ °pëļôëŊûĐa;æĻ-BĮYaĄŖ'Œ;xčXKK+ú_ģôNĶŖĻP–Úģ%‹ĢŋԘ?Ļ7-“ÉčôÔßj$×Ü\ŊQzĮŊ –hŧtŽūvx!é?ښ0~ėÛožąkĪ^&ƒ‰:ôq§ûĢ÷J×č‰Ūą9ĩJŊM8Rk žøÛ&úõē_~ĨÃRŗÔ{™k‹Đq Ž?yĨ-=u*R…áBÃđN;Ák´c§iôƒGQŌĩ텍5Ō×T€—œĄ Ūzãõų‹–Œ>ėŠ^éeåž>>Ô�õ^éē{ĸĶ´uWoŽ–šĨŪË\[„Žŗ<<ܡnß1îl:1.—Ģ{—=ÔZŗOŸ6åĮŸÖøúú0¸!íØĩEŖģÔkۋÚÚZ¤¯Š<�/9C{ÉŋäĘĨR&ƒicc=Ņ’-7ôv:�ŧ¤¨^ō†mŊäZZZūšæŋŽQcF÷v.�ŧė lÄÛËkÃ/k{; ��Bpš)�Āä@Ų�˜([��e �`bô|$ßÜŌVT*U(áŅ­�ŧļlÛŅģ XŠEŗ^›îãåĄcŒž˛UT*õtuärØŨš� /ēq/!´îĮ•Ŋ˜CIYųÆm{žũ|…Ž1zNĘv¨Y�€įÆŲŅĄļŽ^÷øl �`b l�L ”-�€‰˛�01Pļ��&Ƙ[ŠoŨžÛÚÖ64bŊäÎŊÜ™§ų<ž‹ŗ“Ÿ×CGų|ž9—;gÖԀÂĮE ŋ˙×Z_o‚ |ŧŊBú>Ë>t˧#jsV––9y÷BW¯ß\˙ߟĖ:똚qęLÖÅ+vļ66ÖÖ>Ūžô~ĩ´´ĐОEJKkëūƒ>.ZųÕß՗<’¤{Įŋųî‡oŋüŦãk�ž?•J•t<Ŗ­M†áXkkÛÔØ b‘иPČäō €gÉĮ˜˛•yæ<A|>?ķôY‘Hhan^YUũę´ÉŽ.Î_­ü‡Ÿ¯wüšB˙ķ¯W<’T\R:bØkk+z!BčđŅcŪžO.';{>ëÆÍÛÍ--övÕ5ĩļ6ÖåŌ [kjĸ•ĨøpR “ÉėčīhoŸ”’æėėxņōՙͧ<–ßđp@H?Iq)AÅ%Ĩõ ÔtKKąˇ§I’’â’WĻMĻ77rø‘Ç\ēzŨÃÍõÃGCúøķŨķfŋu$)ÅÚĘĮp‹ÅįņT*•ŊÍųŦ‹ô~õĸSU(”A<.’Ô74ÚŲÚVTVNš8!=#“Íb“$9xPhrjēģĢËĨ+מúüĶ×_™öÚĩĄ¯W}˙Õ០„V˙øī~ūĄôŒSr…B,Ōûā˙įąT_o¯†ÆÆŧûųôëgųđŒ.]ģ)ĻLjmkÃ&­ŦĘž|efFĒTææÜ††FąHT]S;rØāŋ–sšŌĘǁ!A< ‹ ¯đų<.‡#­¨$HR,^Ŋq›69f\Wķ1æ$1ĀßwøĐÁÖV–"‘Īã]ŧ|mÚäØÔô“{jnnq°ŗđyû™=ŽÁ`  āîęB/ŧs/Ã0ęī!: dÎŦ7jëęÚۉūÁAĶ&Į––•Ķ“SĶߜ9c钅ū~陧gŋũú›3g˜1™#† ‰9\,MŸGåÃbąčé¯Í˜zö|vÖÅ+3ĻÆilŽ ˆ“§ÎŽŠžGĮRĶgNŸ2덙 ‡ ŋpNü‚9ĮŽŸ˜3žŪ/‚ čTŊ<ŨŠ-ė<cę¤ēēúÃG‘¤ Ãą’˛˛ã'2įŧũúĖSÛ B(ā››?iŖ:4bĐÕë7/_š>|č`„ĐŲķYĩuõ“cŖÕ÷%=ķôėˇ^Ÿ>e“ÁPmį €îRV^áåîŠĘētõXZæ™ Īf]RŠT†UTUĢH•—į˜‘CĢĒkžZŽRõ đ ôķą0įōų< s<w×ÁŲWŽĶÔJeWķ1žßցCž:}Š•ĨåŲķŲ*•ęĩWω„‚›ˇīâ8žnÃϘqQž>^G’R¨ŪĮ …rÃæ­ÔÂ_7ūÎįķŽgdVW×øúxá8U:1„!‹EĮ§&bFí^[[BˆęŋNdCCãöŨûW,}—nģŒĐ_Ķ ‚T(8†y>ëŊšÃ" ‹„ôÃ0 Įq• !„ ĩ!jnEeĨX,ĸZā“$Iī“Éü+Õ§$‰!4.j´§ģ[UuÍŽ}`ŽÂą§FŽ1,qķ6’$?X˛ 95Īįį?z¤T*[[ÛūÚ•ŠÚ I’OŊ ÷¸89<xTčėä0,"ŧšĨåpr›ÍŠčäh_WßpķNËė¯JĸžœÃf!„Nœ:7.r„P(¸vķnĮaF´7Ļl9;9ū™œÜ/hīÃŽŽö˙aAáŠ3į|ūäØč?­Ģ­;{!ëė…,ąXLMQ_¸pŪ;fLfŪũüŧųJĨōæíģ…E6ÖVŦ⏋Ūŗ˙›Í đ÷7fôÖ{íUHĩfŨ¯VV–9ęč`Oåãåņ×ML;÷îŸ69–h'ļīŪûîĸy!jsÖVV)iƒÂBũũOdžy\$Q(”“cŖ˙8’d)ųxy™™ąÖ­ß(đûúã ÆÚõŋQûU^.ĨSõôp˙39ÅŨՕZbkc=1zܞũ­,-8>6räīÛwģššā8~?˙áš ŲÅ%Ĩ›ļî˜ũÖ,–™™“Íf#„ö>pÃĻ­Õ5ĩôžŒ3ę÷íģ=Ü]1 Wmį €î>0$9íäžCI8ގÉdŖ‡aŗYĮ3N‹ Į|>=rÔ°ˆŽË=Ü\ŌNžąąļâY˜ĢTĒË×oEvâô9jØÔØ ]N(!!aņâÅ*-Žß} mU/úzÕ÷Ŋ‚JĨRũqøčíģ÷:.¯ĒŽŽ­­SŠT_­üĮsO �ã]ŋûāƒOŋęí,T:rXŧxqBB4e~&ÔiφÖ6Ųļ{mmŦŠÃ:�@÷2ɲÕGŽxeÚäN—ģ:;}õ÷Î;�xvđĄ �ĀÄč)[8ގÉäĪ'��0„ž“D/WĮIt7�<%eåz/Á×SļxÜ _]ŨQ�/˜Ĩûēˇ. ßy}†î1&ų‘<� ‡ônGfÁGō��e �`b l�L ”-�€‰˛�01Pļ��&Ę�ĀÄ@Ų�˜([��e �`b l�LŒž{I’ü×ŋ˙[ßĐ@’$I_ųÅžøúøŒ>TcäÆM[‚ƒûŨšs78¸ßˆÁF¤BE0dnVöÅæææ’Ō˛N3�ŧØô”­CGū´˛˛úôãåĄŧŧûMÍMĄÔ´´ YYMMͯŋöęîŊûlŦ­ų<žúŦ‡í?pÃá$) +**%ɔÉq;wíņõõ9™yę˙ˇ’ķɊē4×ÃŨ o:“īV~Ķo� /Ōs’x˙ūƒ°ĐÔk?{;;„P䨑Ÿ}úqqI‰H(´ąļ …™§ÔgíØš›$IĮ I’:$būÜŲ…‹<ôņō—,^ØŪŪŽ>F&“uinXčĀč ãX,Iwŋ-�€žKĪŅV`@ĀÅK—û‡#„îŪËár8!.לZģaãĻÅ įÛÛŲ%§¤jL|eú´€�˙˛ōōãi'ØlĩPEĒ0CQOĐĸĮp8œŽÎĨЙ��^zĘÖÔ)q?ūķß˙â+6›…3ûxšúÚĐ~YŸčîîf)••—÷Ŗ–ŋ3ë­uŋüjog‡ã¸ =~ú´)?ū´Æ×ׇÁĀÕĮ|öé_Œ0dއ‡ûÖí;ž}˙�&KHH(++KLL|+—J™ ĻõüEKļlÜđÜæ�^ ņņņŽŽŽĪĩģiKKË?×ü×ŅÁ!jĖčį9�đ"yŽeËÛËkÃ/kŸ˙\�Ā‹.7�˜x� û‘$™““S\\Ŧ~y“:‡ãęę€ã]>x‚˛�č~ššš!!!fffP*•………šššAAA] '‰�€î'‘HÜŨŨY,Ļ‹Åōôô”H$F×s´ÕÜŌVT*…§R�ēD&“ąŲlŨcØlļL&ģq/_}!ˌéædĪŗā꘨§l•J=]š=›��uÜÅ0ː‘ƒ|Ôl“É $eAž:Ļč)[ e;Ô,�€4ĘÖļmÛBsæĖŅ=‹Ëaë=Ŋƒä�=Bũ+ÂÄÄĤ¤$„BĄˆÆČPļ��=‚>ÚĸjÖÚĩkB˖-Ãq|ņâÅĪžI�ôúKҒ’uëÖy{{{{{¯[ˇŽ¸¸˜^e\d8Ú�ôú$qõęÕôBõe �Đũ8ŽBĄĐ} „\.įru]蠍1eĢĩ­mĮîũL&S(ŧ:}2Bčû­õ÷õž69ֈh�€ĢĢkNNNPPļĘ%—Ësrr\\\ŒnLŲĘČ<ÃårT¤ĘŪŪ!tøč1oO]Y��^6šššŲŲŲÚîIärš...F5û4æ#ųriE ŋߜYo¤gœēyû†aũũˆ�xŠT*Ŗ×ęfĖŅ–H$¤^°ŲŦķY—D"áņŒĖęęšÃ"Ŧ­ŦŒN�đÂčŅ[Š)[ŅãÆüļyûõ›ˇŊ==^u:B(ī~~ۃ|¨Y��ŠD"éßŋŋļš…ĸnĨžuëÖs*["Ąđo+–Ē/ņ÷ķņ÷ķŅ6�đ˛Qŋ•:!!aöėŲžžžĄ‚‚‚íÛˇ'$$ ˙ŨJmDp¸Ü�Đ#čkJWŦXQXXXXX¸bÅ ¸Ü�ĐŅU‰ē•gųōåĄ¸¸¸E‹=cd([�€Ą~+õ’%K¨§8Ī;÷Ų#ë)[8ގÉäĐģ�ĐUį€ķæÍ3dV›LÎ2ĶS—ôŦöru,”AwS�@WøŅU§ŨMuOŅSļx\Ũm� ŖĮîøHîφ€Īļ��Ũ¯GoĨ† ��Ũē•ZĄPh{rBĄxގR�€n=z+5”-�@÷Ãq<((Ȉw ŪA� į@Ų�˜([��e �`b l�L ”-�€‰˛�01zĘVRrJėäiÔëG ü‚úˇˇkŊ­:ķԙÂĮEŨ’Ö‚ÅKôŽÉĘž˜~"ЍR#HŖcpŖC=šũļiķ•Ģ×ē%˛Æ¯cÍÖæåŨī–Č�A˙åĻvvv˛˛‡ē˙āĄAáaĄ;wīíŪģĪÆÚšĪã)ÛÛûųđáÃv‚āķy'3ĢkjĖšÜŠĘĘĀ�˙ėėK˙YķSâÆÍƒ [°xɐˆˆŽ¨žĶ7nŪÚē}GHppmmŨĪë7ü5eđā‡ b'F[ZZŌ›Îøˆ 77W>Ÿ§P(~ßēŨŪŪĮq.—[QQáčč(‘Hžū!täĪ$‚ lmm÷î;@YöÁ{Ą›ļPaœ÷8ČápH’Œšs×__Ÿ“™§ĸÆDōųŧœœœŦ‹—¸ÎĀũΜ=O„ƒƒ}Rr 5^(PAFŠņæ,Z8!”—wķÖmfffƒÂÃÔķėø>lū}›T*uvv.)-}ķõ×:ŨŠYoŋIg+ éå­­mAŒ>ĖÜÜŧ\*}\$YŧpÁą”T:y??_zb\ėDmYҧN™L˙:Ôãŧˇdņ'Ÿ}ņķ×ôč?M�´Ņ[Y„�� �IDAT’;1)9E&“ÉÚÚÄb1BH$ÚX[ …ÂŒĖSīÆ/J>–š~âÄÂOšé0ŒáC‡,}˙Ũââ’YoŊick#•V¨Ô1āĀÁC/˙pÁŧ9LæSõ”ÁdF‰=j¤úĻÃBFOGÕģģöÄ/^øŅ˛îåä1tHÄüšŗé%jäÁCGč1--­ęawėÜM’$Žã……{÷øxų‡K/¤+›ĖšÜ˜č ąc¨P<L'U**5X=CjɎŨ{–ž˙î˙­L ÕČŗãûĀĀņÇ-œ?ˇĒ˛ę÷­Û;Ũ)õlI’¤—Sš™››#„Ξ=9j¤FōęˇîØŠ-+: ú¯C=‡Ãa2MÍÍ]˙÷@7Đ´Å5į EÂm;vMŽ›´mĮ.„І›/œoog—œ’ÚŪŪ.WČq W?ydąX8ŽŗXf!à ’Āqœz,šL&īt�5QĨRáŽ"RcŠ………ÆĻՓToKa›Íé¸#ęcpüÉ+*,Bč•éĶüËĘË׎[áBÞÔôWfLĢĢĢ?uúLJjšŸ¯Æøãi'č fˆ!Œ$I ÃZZZ4ōÔö> „’ÔąSę[ītgĢĒĢ}|ŧ5’WŸ¸ū×ß´eET˙u¨ĮųúËĪ­­­ĒĢkø<^ĮMĐĶ ē'ņÍ×gŽøäīņ‹Pe+tĀ€_Ö'ēģģYŠE_|ųÍü9ŗ•íí˙ú÷ZĄP - 𰃇ŽÜ/—ËulhÆ´Š?üsŋŸ/ŽcNQß4AGŽ&yyy!„Ū™õæo›ļØX[÷ 渌#÷­Ûw˟;›ŖŅ.ãYo­ûåW{;;Į§O›ōãOk|}}Œ'ekįîŊ%%Ĩl6ËĪĪ×ÃŨmëö æÍŲ°q5ŪÆÆF=”z†ˇnßéLįr¸aĄuįIš}1'7ĪŅÁáÍ7fv:X=[õ­Sģf)[[YUWWk$:€žhHVęŋõ8Ąęęk+KŋJ�z–PVV–˜˜ØÛ™ôåR)“Á´ąąžŋhɖžķÖ7nÚ1xĐ3Æ)+/_÷˯˙øneˇdĨA&“}ōˇĪ^ûīž€ņņņŽŽŽĐBSKKË?×ü×ŅÁ!jĖč^IāY2Nstpđpwģrõõ-J÷ZŋáˇŪĶ˙U/�=Žļ��&ƒ:ڂËM�&N�Ũ$ɜœœââbmŨM9ŽĢĢk@@€OĘPe �ĐũrssCBB´}cŽT* sssč€ '‰�€î'‘HÜŨŨY,–ļG`°X,OOO‰DbDp=G[Í-mEĨRxŧ+� Kd2™î§!„ØlļL&ëôņŽ< ]"ĶSļŠJĨžŽŽ\ŽžÍ�€ēĮîøTjĮģļÉä’2ŨO•ÖSļĘv¨Y��#h”­mÛļ!„æĖ™Ŗ{—ÃÖ{zÉ�z„úW„‰‰‰III!…B˙Œ‘Ąl�z}´EÕŦĩk×"„–-[†ãøâŋŸ%2|“�čô—†%%%ëÖ­ķööööö^ˇn]qq1ŊʸČp´�čôIâęÕĢé…>>>ę?Ę� ûq8…BĄûš\ŽŅBĘ@Ɣ­–ÖÖũ˙,|\´ōĢŋ>.:pč(ŸĪ3įr'Œŗ{ßA>ßÂÖÆfÚäX#"�^ ŽŽŽ999AAAÚ*—\.ĪÉÉqqq1"¸1eĢŊxũ•i?ŦY‹Âp<~á\Ą€˙ų×Ģä Åk¯LuqvZõũŋâ&F3™ #‚�^�šššŲŲŲÚîIärš...F7æ#yĄ€onūäĐÎŨÕEĀįí;xdbô¸ÚÚ:++Kj@}Cƒ‘�/ ŨãžĨ¯Üŗ~ļĨP(7lŪ3.Ę×ĮëAūÃęšZWg§ēú‘PøŒ‘�ĻĢGoĨ6ĻlŨĪxîBvqIéĻ­;Ø,v]mŨŲ Yg/dÅLˇīĀaĪ"t@œ!đ2“H$ũû÷×VŗBÔ­ÔˇnŨzNeËĪĮÛĪĮ{áÜw:ŽúøÃ÷Œ�xÁ¨ßJ0{ölOOO„PAAÁöíÛĐ˙nĨ6"8\n �čô5ĨÎÎÎ+VŦ(,,,,,\ąb…‹‹ \n �č‹čĒDŨĘŗ|ųr„P\\ÜĸE‹ž12”-�@Pŋ•zɒ%!4wîÜgŦ§lá8Ū&“Cī�@WiœΛ7ΐYm29ËLO]ŌŗÚËÕą@RŨM�]eāGWv7Õ=EOŲâYpuˇ�€Ž?¸kā#y4ē›>Û�tŋŊ•.€��t?ęVj…BĄíÉ= …âšŪJ ��ēõč­ÔPļ��ŨĮņ   #nÜ1(xO�€že �`b l�L ”-�€‰˛�01Pļ��&Ę�ĀÄč)[IÉ)ą“§Q¯=*đ ęßŪŽõļęĖSg uKZ /Ņ;&+ûbú‰ C6Jė–Ä(7mšxérĪm‚Šļeëöķ˛ ŠûM0üBũļiķ•Ģ×ēœqg4~‰kūŗ6/ī~ˇD/9ũ—›ÚŲŲ]ČĘ>lčūƒ‡…‡!„îÜŊˇ{ī>kk>§loīHäÇÛ ‚ĪįeœĖŦŽŠ1įr+*+üŗŗ/ũgÍO‰7G 4(<lÁâ%C"": zN߸ykëö!ÁÁĩĩu?¯ßđהÁƒ>*ˆmiiIo:˙á#‚ ÜÜ\ų|žBĄø}ëv{{;Įš\nEE…ŖŖŖD"IøúK„Đ‘?“‚°ĩĩŨģī�5fŲī!„ōōîoŪēÍĖĖlPxX`@€z„Žnū}›T*uvv.)-urp@=|ôh˙ƒ‡$ÉĘĘ*‚ ė“’S¨%ŸŦøHãŊBĻ‘˜úZšBAĨA>>ŪŠii˛˛šššįΙEoK(ŌqZ[Ûč7A.“ŅģãíåeÄU.•>.’,^¸āXJjÖÅK\gā€ū~~žôĻãb'j{ĮčPS§LωęqŪ[˛ø“ĪžøųŋkzđŸ3x9č?IŒ‹˜”œ"“Édmmbą!$ mŦ­…BaFæŠwã%KM?qbá‚'Ít ÆđĄC–ž˙nqqÉŦˇŪ´ąĩ‘J+ÔępāāĄ—¸`Ū&ķŠzĘ`2ŖÆDŽ5R}ĶaĄŖ'ŒŖęŨÎ]{â/ühŲ÷rr ‚:$būÜŲôÁ5ōāĄ#ô˜––V„ĐŽŨ{–ž˙î˙­L ՈĐ1CŽ>láüšU•Urš!´cįn’$q/(, î=aÜĶK¨{Ôf0‰Š¯Uß_*a‹9jägŸ~\\Rĸž-’$é8ęo‚úî÷F={>rÔH„Pcc“9—=!vbŒúĻˇîØŠíŖCŠ˙Õãp8&ƒŅÔÜlđ?N�:§˙h‹kΊ„Ûvėš7iێ]Ą 7-^8ßŪÎ.9%ĩŊŊ]ސãŽ~ōČbąpgąĖB8†$ã8õX4™LŪé�jĸJĨÂ1!D¤Æ MĢ'ŠŪ–Ã06›ĶqGÔĮā8†ÂF’$†a---´eˆ"H’~ũĘôiūeååįÎ]ĐXBõrÔHø˙ŗwßqM]íŸ{CH IaËFA–"*2TPęļ­ŖZˇX­ÖŅé¯ĩ´úvמo­{‹u¯*ĸ8ęTŦ Aö= îÍīkc ŊõųūŅOŧ9į9įž OoÂ=į4ę˜úģÎ÷éā tšļu*úLķ'¨v:ģ÷´c JJKííí�`ÜØŅååį/\Œ:­ŪôÚuŸ7bĒPęQ=ÎōĪ?344(-- …M;PÛĩiNâÄņo-ųđͰŲ3™´åéîžfíkk+}ŠdŲį_ΘúŽĸĄáį_~‹õžĄˇˇ×ĄÃG“SR™û”į;zÔ÷?­rrt IĸŲ*ęMS4uôØq[[[�˜2yâÆÍ[ ]{öhēĮ‘õö3ĻŊĢ*Ã,—1eō¤ÕkÖ ø/O–#0ŽÆÆ%&=™š2Ëq0ÕMŒI’ đßž3bæôŠë7mfŽ|ōŅŌFÎĪ/p°˙ĮŌBęīJĨ’ÃGū`Η鰋Ú,SõŒÚ߃đėtÚ7P†ĨĨĨ�°k÷ŪÜÜ<OÛŅŅÁËĶ]Õt[FLũ"ĒĮ€ŌŌ2Cũ~�j "<<<??Æ /ģ'¯ēM›ˇēššúôéũ˛;Ō‰ō V¯Y÷íƝ;#xmmí‡öÛ¯ŋtFpôš “Édø�Ä čČößŦ 35ĩąļŌÔ_Yģ~ãûķZ˙1B­Â…kÚjöŦ/ģ ]aÎŦ™yÉĸ…Ŋnđn !Ä2xˇ…Ō<šĻ>|øŧÕMų|žĨĨĨŗŗswĘP‡i !¤yIII•••nnnĪûģŧBĄČĖĖLJJjĮ ¨ø!!¤y999ÖÖÖÚÚÚĪÛC[[ģ{÷î999íŪĘŨVUõ“ėŧBÜŪ!ôBjkk[Ūm �x<^mmmŗÛģ u[ڈŦ•´•WØŨR&āˇŌ<BŠËJIhãŽÔļw}R[—‘“ßōŽŌ­¤­zEæ,„P;4J[;vė�€ŠS§ļ\KĀįĩúņŋ’Gu õ?nذáøņã�P__ÖÁȘļBBuˇÅäŦ_ũ�.\H’äœ9s:˙’ˆęĒ?æææŽ^ŊÚÎÎÎÎÎnõęÕ>TŊÕžČxˇ…ęljß|ķę ŊŊŊú?ÛĶBHķø|~}}}ËĪ@ÔÕÕ1KHŊ¨ö¤­ęššũ‡ūČĖĘūú‹O3ŗ˛>& u‚āÁģ÷‰tģڎČĄKKËÄÄÄ=z</sÕÕÕ%&&ZXX´#x{ŌVC5~ÜčīWũ �I†Íš&Ö}ļ|E]}ũÛãFY˜›­øîįáÃB´´8íŽúpvvNJJŠ}ۜD@`aaáŦļfÛĩį+yąžHGį魝ĩĨ…žH¸īĐŅa!ƒ=*70Đg T<~܎ČĄ–×§ëČęuũnĢž^ą~ËöĄƒƒėmSRĶJËYš›•W<–ˆÅŒŒb¯NJŨž´•œšvųjėÃÜŧÍÛ#xÚŧōGå—ŽÆ\ē34xđžG„B]Ow7ü„ˆĐë,''§W¯^ĪËY�ĀLĨžsįNĨ-G{;G{ģYĶĻ4}kéķÚ!ô/Ŗ>•:<<üŨwßíŪŊ;�dddėÜš3<<ūžJŨŽāø¸)B¨S¨ž)577_˛dIfffffæ’%K,,,đqS„ĐĢH••˜Š<‹/€áÃ‡Īž=ģƒ‘1m!„:…úTęšsį2O›6­ã‘[I[$I>Š­ÃĩkB/ĒŅgĀéͧˇĨ֓Ú:mn+yŠ•ˇm-e9ų¸ē)BčEĩņĢĢfW7mšJ+iK¨+hy™A„j*+%Ą[ō4ZŨ´-đģ-„æuęTj|�!¤yĖTęúúúįíÜS__ßĨSŠB¨e:•ĶBHķH’ėŅŖG;&î´)xgEĄÎƒi !Ä2˜ļB,ƒi !Ä2˜ļB,ƒi !Ä2˜ļB,ĶĘs[Į#Ŗöî?āčā@Ķ”\^õũˇ+ĩ´žUŲ´yĢĢkĪž>}TGū<ŅÆÆÚÆÚĒ#}ЉĢĒĒ 2¸#AB˙V­?n:,䝉�`Ņ’ ‹jëj÷8ÄįķišëéĀģĶgm\÷‡Ã ›ˇ ˇˇ—H$<{îĪĸĸ"™L–““3rÄđ]ŋīqp°?÷įų{€õ7š››įæå3z÷Ū}F††"Ą�ŌŌ3B‡…œˆ:EQԎˆßˇl\Į„Ũēi}'Bˆ5ZO[§ĪœËÍÍģ{/!$xˆššŲ—_­ārš$IĻĨ§ģöč�CC†DF”ˆÅūUUU�Āáp|ûú œ:cöC‡—.ūĀĖL}ú C’ú÷<(hū‚EZŽ‘ĄĄX,>}ælHđ Ā�˙~……ERФ´ŦLļS‡�!Ä.­ˇ<dĐ'- ÂĶÖfŽŒ3zŅÂ÷?_ö)ķqÄđ7ŽūqüX䉑ßíDÍãņ™JZI�Dãļ(šŪļ3bÜØŅ“'M¨W(�@WWWõnŗaB¨­s'M?cöÜũûM™<iõšu&ÆÆ$IęKĨ�  MLŒŠJÜÜۈcFüáĮUöÎŗ´u56.1éĖÔ´‡‹ķšĩŦ­­ôĨ’üü{{�°ąąŪž3ÂÛÛĢ…°Ą×žŸŸŋaƎDŲškwwëũû5}Ģ °P‹Ŗedd8cö\æ+ĒM›ˇēššúôéŨ‘°Ą×PXX˜L&ĶĀ ûöLOOŸōÎÄfß­ŽŽūiÕ˙dĻĻAĪžĸjË>Ú-‡EŊļ4sˇ…B]€šÛÂĮMB,ƒi !Ä2˜ļB,ƒi !Ä2­ü%ąĒúIv^!nīŠęĖöŽBŨ–6"k%meįvˇ” ø-mv†Bšō¤ļ.#'ŋå]Ĩ[ųX¯hœ…ę2>¯ÕwøŨBˆe0m!„XĶBˆe0m!„XĶBˆeÚŗÄŅãQV–Ŋ\āÎŨ„š'Ot‚š'O|}šY‹fˎßß;ōĀác|>Īĩ‡‹[O—Vã:zÜŪÎ6#3KÕĘķ0­ĢˇûŨĪŋ:9Ø …ē1q7ŒģÚÛu?ķį‘Phanfkc}ččqąXO$NžøÖ‹Ÿ:BčåkįÂ5WbânßšW]SŖP4PÅĖĘΊx\iÜ­[Qqņā ˙Ŧėœ!Arš\O$’Ëåą�då<<z<ĘĐ@Ÿ$H_ī#ĮŖ´´´zē8Y˜›Œ>+‘ˆuutšļ2<4DUËÜ\vįnĮsr´ŋų׊ĸD"QAaᐠ€#ĮNØuˇ€ŠŠJ‘P¨T*MŒŽÄÄŊ9z„Ĩ…ų_ëč`÷ūÜYēē:ËÂ˙ĶáĄCŊíL[Ū^ž}ŧ?˙ę›ũúŠõD+åb=Q~AĄG/מ}ŧŋųᙊ‰Ŋm÷´ôĖî6ÖĖ �œ8yú­1#ÍdĻ+žûЍ¤dâ[cMŒģ—�€D" …1q7úöņjԊz-]Ą.ŸĪīߡŗ“CMÍąž¨§‹SO§{÷ ‚čéâô %uP€ĪđųŸõMØĖŠ'OŸĶĶUUU›g?|¸g˙á>^šA„Pkįw[|^KĪ R4M��WãŽûúôfūĢz— ˆ§m„RŠ$âɓ'˙1$(āĄÁ Īž4SoEUk˙€ąŖ†į<ĖŨ¸u§zŖWbŽUUWŸ:ûgü_ˇ““ ‚ B[[›ĻéˇĮžđæmžvVvŽ•Uø˙}|ûnB]]]ûÎ!ôrutuSs3Ų‘QƒΝŋ`miyûnBFfv7#Ãŧ‚‚Ŧėœâ’#CæŋLųĄ!חHėmmûúxīŲ˜ĮĶvvrpvrØ{āˆLfĸ§'*)-ŗˇŗUoEŊÖŲķ—ŠŠK´š\k+ 3™ėČ(>Ÿ_ZVöŪėé�đ 9õAJĒXOŧzí&==QO'’ÃųuíF=‘hDhH͓'Ģ×mâķyF†ŧ3/Bč•ÕĘęώî§zô°ocŦCG;ØÛēöhũKw„zžŌN§Ŧnچ5âB¨C4°†Ę¸Ņ#4 !„š…›"„XĻ•´E’ä“Zü‹B¨‹<Š­Ķæļō)°•ˇm-e9ų¸ē)B¨k0Ģ›ļ\Ļ•´%Ô´ŧĖ Bu1ün !Ä2˜ļB,ƒi !Ä2˜ļB,ƒi !Ä2˜ļB,ƒi !Ä2˜ļB,ƒi !Ä2˜ļB,ƒi !Ä2­¤­ââ’ÅK?ūá§UËžør˙ÁCín&&6îô™ŗÍž5sÎÜļDØ´ykÜĩëmieëöWŽÆ>¯ĖŨ{ ŗÃæå´ĨŅfãˇŖ"BHƒZ™J}/!ÁÜÜlÉĸ…AdddūļvŊOŸŪŊŊŊfΙÛÛÛģ°°ĐÜÜ<7/ĪÄØXõzâøˇˇmßibbL’¤€ĪOKĪr"ęEQĻĻ&Į#Ŗø|>M̓‚ˇīŒpsu}ô¨œi+lŪ‚ kWųÕ 'GĮ‘#†‡¯X6{æū‡˜ōb=ŊČ'¯\ŠŦ”ŋõæØŨ{÷Š„ÂēúzU— ô (Оˇˇ;}5&F.¯˛˛˛,**’Éd999áË?€ÃGŽ’NFfÖ¯Ģ×6ędJjZiY™Ž@PT\ėâė{íŋĢ~|œĸj+5-ĸ¨ĒĒĒ›ˇn ø|÷^o„ëôK„ú§Vîļü­Ŧ,—‡¯ø|ųWų…ęoqHr@˙~ŗfL+).iP(T¯ˇmß6gÖĸ…īßOLĸh:(0Ā Ÿ—§GHđāƒ‡ŽĐ4M’dFfæŪũ–.ū`æôŠZZOS§LfZZVÆáhĨgdÄÆÅųûųEėÚ­*ßĐĐā7 ˙‡KkiiŠÅâŗžWī͊ļļvĀ@ŋO>Zú07—Ãáøöõ™1íŨĖŦlόŸß€~ũŖŖĪ4í$‡ÃéīÛwÁü÷>Ė<iĸQ7ŖÂÂ"‰XŦj‹‰_WW¯# 6Ts!ÔV­Üm%'§ ôæØ1EMœ<5Ā RŠ€Zĩĩ)šVMĒŊÁ€ ]]]õ€ãƌvvvĘ/(XŊfI�@QOĢúÜž#ÂÁŪ.5-ũjlܒ܈W•?qâ$‡C21ˇíŒ˜?wމąqdÔI’$›vI xļG,Įoz^Īë¤ļļ6I’ÚÚ\� ’ĸŠM[ļ͙5ƒiëé)Œ]^^qūÂŨ“ŅË?˙ŦåDi\+iK Ę/Âŋ616ŽW(††ģöėqčđŅä”Tf“ÁĢąq‰IdĻĻ<Oõzℷ6nŪjdhčÚŗ—ËeâØØXoß1súÔõ›6›“$9vô¨īZåäč@’OķGßž>}ēėđūŊ</ęT´H$š2yŌę5ë˜ōbą˜iÂÔÔ¤§‹ËšĩŦ­­ôĨŠTrøČL—˜V\œ[=í)“'6ídŗ<ŨŨUmQ4uôØņ”Ô´GĘy<mGG‡6Œ0BHÃZŲpŦ›6oussõéĶģŅk„ę$ØpLŠļŋ˜÷Cu‰öo86{֌f_#„P§ÂĮMB,ƒi !Ä2˜ļB,ƒi !Ä2˜ļB,ƒi !Ä2í�B]YYY^^^]]]ͧˇĶoŨHēüÖS|†h¤-„ĐkN3i+;;›Įãééé5:^T–{éĘÂČķ{1m!„4B3ĢĒĒH’TüSņŖ‚¨˜=ōē23™…—Ķ€įÕmu!­6.ȅzMhānëô™ŗ$4MSU\ž/ˆtøĸŠĒŌsˇæ?Nĩ´°ę3I—0ē—p_ĩp•߀ū[ļīāršŊŊŊ˜ ûĒ­­ëįëŖZ`Ģé‚\!šÛ:xøhCCMĶŲ)‡Îoŧr/ĒĸĒôzjtĄ<],xŒ˛4t’WĘÕފØŊgÁü÷ūķu¸ˇ§'�Dž8Y\\ōîäIę l5] !„@#w[‹ÎOOKkhP<,I“+JR k*Ek‹„zĪîA6FŽĩOjëëë×oÚŦZ¸Š�‚Ļi‚ ĒĢĢ@*•ÜK¸___-.ȅB ‘´åâėü )ŠĄč>œĢ)…ņ5DŠX_äbÖĪŅĐW^YÅáp …úÂU}úx¯^ŗNĀxyz�@˙~žū_­üfúÔ)Ēļš.ȅB Šŋ$* šĻëęęúØ‹DĸôōxKŠ‹­ÔĢšĢR(ãĮ7v´ĒĘČáo4 âéá�˙[õ“ęķÍ×ü÷Â4ŌI„ĐŋƒfŌķŨ–BĄĶ~ĄĄߊǃ††‚ ”JeCCƒFB!Ĩ-Šĸ(ŠĒŽŽæS|S'š\NĢ­1i !¤)šynKGGG.—3ģJÔ××WWW+•Jâorš\GG§Õ !ÔššÛōđđˆĪÎÎn:š‡ ‰Dâíí­‘†BH3iK*•öėŲŗŲ9‰Ađx<ŠTĒ‘†BH3iĢ  ĀÜÜ\OOīŲîƒS*••••ššši !ôšĶTÚ*477'É|Svö•Į••ˇī%öęéLÕ?ŠkPÆ\‹—™×ÖÕJ%âĶ^úlÉ|�(*.ũö—5ĢūķEŖ!ԔŌÄé3gŋũūG˙A!‰IIęĮ 72´›‘ḑĄļ6Ö�āáÖsÜČĐIoŽJHJ�‰X/9-�boÜ´ĩąęxOB¯ Üm<|�ärųé3įZŪúîũ¤˛ōō†*tH ŧĒÚŗWĪ›ˇîvˇ˛¨W(„ēø§F„P›hānkŅÂų|>ĪL&{sė˜–Kēõp<nä0ģî� ­­­Ŗ#¸xõšˇģ[ĮģzMh mš8;žüŌų3ææf/ZˇŋOī; ‰Ũ­-;Ū „ĐkĸĶׄY0gķT„—ģĢúqÕ?—ž?�Ļŋķvg÷!ôī€šCąŒfŌ3_úyī2}4ŌBi&mņxŧĘĘJšĻ•MĐ4]YYÉãņ4ŌBiæģ-SSĶÜÜÜ&÷˜ššj¤!„ÂīļB,ƒsB,Ŗ™ģ­‚‚B� I’ø§†††/ŋ]•žõ°ŽŽŽŲŠëˇîjöøũ¤”’Ō˛ĻÅnŪžw%ö�œŊpåJÜ t!Ä.šŲ'ņģī,+¯Øŗk{ŖÉ=qņˇ‚öšīëåzîŌÕĮ+ĨIiŲŖŪž[&Āi�� �IDATnŽÄYY˜UUWŸģxĩ°¸Äí‡DŦwárŦDŦG¤‡ÃįķĒkjTÅÔÞ8ũ§Šą‘g/×Ââ’Øë7ĩš\ZŠĖÉ͛3uI’wė~oƔŽŸBčÕ¤™}áī9‰ęĮ•Jåõ›ˇôõ6З––=" ŌŪļ{ ŸoIiYė[ÃCúõãp8$Iötvtq´ŋ{}H _hpPn^~C�ęÅTacoü•‘•ãdo�—bŽ1OW•”ötvŧy'!19ĩ§“cĮO !ôĘęÄ9‰÷“ ęĖyZŠ|’�ÚÜŋoî”J‚$�€YožĪĶ�€§_=ũŠėŸÅ}{{L72bßáēēz�đņō 7b˜—ĮŋîÄßēįåņĮņB˙2šŲ'ņëđå^^^žŋ{}ÎÔI"‘PŠTūōëښš'ǎúxš‹:-35Q¯2°_Ÿŗ¯č‰„–æfĖÂ[Í#€04Đ#$hĮžƒÃ‚Ī\¸,ŅĶ#HbTh°DŦGĶ´Ž@Đņ“BŊ˛ˆđđđüüü 6t$J|||Ķ´ĨĸT*oŪŧŲËÉ_ŠšÖÍĐĐÉÁļŗBŊaaa2™ėßķÜVĖõ›…Å%ŽöŨ_vGBK3Īm1s[¸Ûę‚9‰ũúxuvĄWÎIDą ÎIDąŒÆžÛĸŸã^JüēũßŨKûKS !„^sššÛĘÎÎæņxzzzŽ•å^ē{ŦŦ˛0ōüŪ�Ÿ!i !ôšĶĖŨVUUI’Š*~TŗG^Wf&ŗđrĐlřsæ6{üĪķ3ŗ˛›+..Yŧôã~Zĩė‹/÷<¤^åîŊ„Ųaķō TGbbãNŸ9ۖūˇŊ$BčĨĶ˜D’�šĻ)Š*.Ī D:|QEUéš[ķ§ZZX õ™¤KmÚ˛­¨¨H&“åääŒ9bûÎ7W×GĘ7mۚ–ž:,ÄÄÄdÛö&&Æ$Irš\‘HXQQĄ*Æ´u/!ÁÜÜlÉĸ…AdddŪK¸ŋ{ī>#CC‘P˜—ŸOr8Ų99ÛwDđų|šĻ‹‹K(ŠÚņû–ë8NØŧž}}T}˜üÎÄũŠ—ĐŋŸŽî{†ĐĢN3û$Ž5œĻéė‚”¨ØŨļÎ}œ]O.”§‹Ĩĸ�Q–†NY™YĮˇ¯Ī  ĀŠ3f8txéâ,-,NEŸæhiøôûâË¯ÃæĖ˛ąļ ›ˇ€™’­^Œi+0Āŋŧĸbyø ĨR94$ØĘŌÂČĐP,Ÿ>svÎė™………§Nærš$IĻĨ§÷ëÛˇ[7ŖŌ˛˛Č¨“ą80Ā_ĄP¨úąkwŖ’˜ŗb ¤­E į§§Ĩ54(–¤É%Š…5•ŠĸĮĩEB=g÷ #×Ú'ĩõõõ�Āãņ™*JĨ’$H� (�tuu€ ž=ŨÅŧhT �’“S† zsėŠĸ&Nžjoo7gÖ cãȨ“ĒūŒ3ÚŲŲ)ŋ āōåĢ�0bøīÍ_¨¯/]žüБ?T}hZ!Ä š™“ø )ŠĄč>œĢ)…ņ5DŠX_äbÖĪŅĐW^YÅáp ÅãĮUUƎõũO̜HōŲc¨S&OܸyĢ‘ĄĄkĪĖ’M‹)AųEø×&ÆÆõ ÅА`ąžŪšĩŦ­­ôĨ’ĖĖ,€?eō¤Õk֙“$āŋ}g„ˇˇ—‰‰1Õ@‰Åbõn7[R_*íø€ „:•fæ$îÛˇĪÚښÃáhiI…ąéåņ–R'ƒT=AĶ4‡ÃÉÎÎ?~ŧĻ:ũĸvîÚŨŨÆz@˙~/Ģ!`æ$j戆†šĻ …€¸˜ö“ øVT4440ķ~4ŌP;ėÛ0==}Ę;_VBšĨą´EQEQÕÕÕ|ŠoĒã$—ËÕÉz‰iküÛožŦĻBA3ĪméččČåræõúúúęęjfú4C.—ãéBšĸ™ģ-øøøėėėfį$J$’.Xl !ôšĐLڒJĨ...ÍNĨNLŋu91ŌÄÆĀÎÎN#m!„^s8'!Ä2šI[UUU@ĄP¨,——ĒĪIŧr5öö;V––{÷ptp¨ŽŽîŨۋypô‡ŸŠŽĒæpČÚÚēO>^Ę<<uđđ‡3fÔH�øōĢīN~GĄPŦ߸Y" …BG&�øôé<X#'‚zõuŨœDÕßC‡…ŧ3q�,ũøS7W×ņņ–æĖ‘„û‰?ū´ęÛ˙Ŧhļ!‚$—ąL_*}įŨéŽĒ8Ą×J×ÍIÔ6ūééធ’šœœ:iâĶ'Q{öpÉČĖz^CŽöJĨríú“&ŧMĶʨ“Ņéé™�0(( ?ߎŸBˆēnNĸn“Š×oÄđūüęęęë×o89:�@ÂũD[Û§{XčKõĶŌ͘×ųų††uuu_­üfü[oörs=…w[ŊžēnNĸĒüŠč3YYŲûûųuīnceeųÃĪŋ,_ĄĨĨU[WûņŌ%L1ŋũÎ_¸žâ?ĩOjôī'‹ũmmIqɉ¨“'ĸN:;;wŧį!6ŌĖWō …‚Ļéēēē>öÁ"‘ˆ™“h+õĒ{ĸ iš …B1 ŋī€ūž�0bx¨z]‡ķŲĮ6ÉårW|ĩ\ũČÂ÷įi¤ˇ!Vû÷ĪIDũËüûį$"„ūepN"BˆepN"Bˆe:}N"ŗŊĢW EiHįÎId444dggh¤-„ĐkŽ÷IT!IR.—įææi¤-„ĐkNÃs›-Ā<ˇ�É)ŠĒAŗ[~¸dQĮģ„úĶĀŨÖÁÃG™įļZĀ<�ąë÷=asf-ZøūũÄ$Šĸ|ûú˘önfVvÄŽŨ4M“$™‘™Y[[Ûņ.!„ūÅ48'ąĄ…‡ŗ˜ģ­î„H4ģe!ŸĪo6B146'QĄP</m)•J&mŠī„ČårUÔˇ,üäŖĨīBč_L“s•Jeŗ@0ķ~ĖÍÍ�ā›_5*°cë&�øßǟ4Ō„ĐŋžÆ&÷(˙Öô]œ“ˆŌ ĪIlĄ€FB! ĪIlÎIDiÎIDąŒfŌ–nƒˆęšųˆB]ĶBˆe0m!„XĶBˆe0m!„XĻ•ŋ$æįįwM?BHE&“ĩđn+iĢåĘ!ÔõđC"Bˆe0m!„XĶBˆe0m!„XĶBˆe0m!„XĶBˆe0m!„XĶBˆe0m!„XĶBˆeZ™“X\\ōí÷?šššT<~ėŪËÍĐĀĐÆÆÚÆÚĒÕ¸1ąqUUUÁCŋh‡6mŪęęÚŗ¯OŸ–‹ũyūĸuAAAUUUn^žƒŊũ€ūž/ÚBˆZI[÷ĖÍ͖,ZHDFFfô™ŗ"‘°ŧŧ|×ī{ėĪũy>xČāĸĸ"™L–““3nė˜Ũ{÷Š„ÂÔ´tŠĸî'& č߯ˇˇ×Ė9sûö铖ž:,ÄĖLļ˙Ā!>ŸOĶô‡KĀŊ„ûNJLģŨē­jbå×áÛļī411&IRĀį3A’SRD"áŅ?ŽSeoow2:újLŒ\^eeeYZVĻ#ģ8;ÅÆ^ûīĒÕwĀFą]+ü­Ŧ,—‡¯ø|ųWų…ĖÁ‡/]üÁÜ9ŗ8Žo_ŸĶŪÍĖʖˆÅF††bąøėŸįŊ<=B‚Ģį Ž–VP`€˙@ŋˆ]ģiš&I2#3ŗļļ�Ô+6mb×ī{ÂæĖZ´đũû‰IM3A˜bL+ÚÚÚũ>ųhéÃÜ\‡Ķߡī‚ųī=|˜;yŌDŖnF……Eš6„ĐËĶĘŨVrrʐAAoŽCQÔÄÉSü@I+ ’��‚ €Įã3…×oÚ<gÖ cãȨ“Ė’$™-Čjkë�@WW—9>nĖhgg§ü‚>ŸßlEõ&‚ ˆ§ũ!BD@đlFmmm’$ĩĩš�@$E?wĶY„ĩ’ļ” ü"ükcãz…bhHp]]�Œ=ō‡W98Øs8˙¸Yķtw_ŗvƒĩĩ•žTBŅÔŅcĮĮsøČÉ)ŠLEƔɓV¯YgblL’ä'-mT1ŋ ĀÕĩ§zS&OܸyĢ‘ĄĄkĪ>îŲØXoßáâėŦŠá@Ŋúˆđđđüüü 6´ŊNAaĄGËČČpÆėš[7­īŒnuA!Ö “ÉdíŲŪĩēēú§U˙“™šúkŧ[]ÖBˆĨړļėlmׯųUã]éâ&B,…›"„XĶBˆe0m!„XĶBˆe0m!„XĶBˆeÚķ�DSeeeyyyuuuĖTu‰éˇn$]~덊>C4ŌBč5§™´•Íãņôôô/*ËŊt÷XYeaäųŊ˜ļBĄ™´UUU% …úÁryiTĖy]™™ĖÂËi�MĶ?˙ōŋŠĮišĻ)zųįËū<aīūŽÕÕÕŊ{{Ŋ9vLŖåŊŪ~s\mmmАaŸ˙ß§ĄCCZčŽ?ü4wö,ŠTŌ(‚€/¨”Wž3q�|øÉg‹ŧonnÖlĖ’×oÜ,‘ˆ…BĄĩuô™ŗæff2™é„ņo­úåWŽ6×@_:gÖL B¨#4ļNŸ9K@Ķ4EQÅåųBH‡/Ǎ*=wë`ūãTK ĢĄ>“t ŖÃG˙000øhéb�xđ Y^%€Đa!LNYúņ§nŽŽ>T_Ū �>2kæô={÷ĢRLØŧÖŽūōĢNŽŽ#G _ąōĮīžŠ(¯J%Đd°û‰IM;Ü4&�$šü‹eúRé;īN×ĶIÄb%(-ĖÍ:"ęŌ4maaŅņąBuœŌÖÁÃGĮŽNĶtvAJTėn[ į>ÎƒŽ§FĘĶÅRQ€Į(KC§ŦĖŦää”áo„2UœœņôpOII R^Qą<|…RŠlmmuøČąŨÛRĶŌnßšãŪĢ�ČdĻĨeeŽVzFFl\œŋŸ_ÂũDg'&N`€ŋz�8vüDbŌ�¸uë6�Đ4Ũ4&�8:Ø+•Ęĩë7NšđvŸŪŊ:]Š3fŲXÛúûô›1{îā @\qĄ—NikŅÂųéii Ї%irEIjaMĨĸčqm‘POāŲ=ČÆČĩöIm}}Ŋ‹ŗsÜĩëŊÜ\ á~ĸ€ĪWrũFüīĪo´ŧד'5�đŋÕkhŠŪ˛mįę˙ū �ūˇīˆp°ˇKMKŋˇäƒ7o8ám&NŖSŪ™4rÄlj�pîĪķę1ũŪš{wâøˇmlŦŋZųÍøˇŪėåæz#ūĻ‹ŗ3A<___ĘDæķyEaÚBčĨĶ@Úrqv~”ÔĐ@ tÎՁ”ÂøĸTŦ/r1ëįhč+¯Ŧâp8 …bôčŅ?üô˧˞āņ´IįãĨ‹“œŠ>“••]ņøąŋŸ_÷î6‰IIęË{íÚŊwãēՆ††�đŪû ķōōÍĖd}ûú|ôé˛Ãû÷ōxŧ¨SŅ"‘(//_fjĘtĻŅaM{Û(ĻOŸŪcĮŒ€_[[R\r"ę䉨“ūū˙ī‹/ĨúŌ>Ū^cƌZųŸī._šÚÃŅ˙ĪT‹z)ÚŗŪVSûöíŗļļæp8ZÚDRalzyŧĨÔÅÉ`�UOĐ4Íáp˛ŗŗĮ¯ŠN#„^Oí_oĢŠ††šĻ …€¸˜ö“ øVT444ĄT*4ŌBi,mQEQTuu5Ÿâ›ę8ÉåršĻÕ h¤!„ŌĖäš\ÎėTQ___]]­T*‰ŋÉårVƒ „P[hænËÃÃ#>>>;;ģéä‚ $‰ˇˇˇFB!ͤ-ŠTęââōŧ9‰—#Ml ėėė4ŌBč5‡sB,ƒsŸē{/aĶ–múb=Ŋa!Á;ßmh`@’äĸ…īĀÂÅKŨ{õš1í] B¨#pNâS<oåWËõôôĻL›)ÔÕņF¨o_Ÿw§Ī€-ÛvôėáŌņBiÎI|6'ņΝģĢ׎ đ<čŖO—9zĖŊW¯k×oŅÛÛûÖí;+„PĮqärųˆ#ÚÂÖÖĻ´´T(ÔŊŸu#ŊčļŧŽ´Xž]\•Å×Ņîí0ØYÖWQ¯xô葁arJǎ—'�$ÜO”Ëå%ĨĨuõunŽ=`Ëļí#Ū-**vīå6lhˆ˙@ŋ?]&•Jâoū•›—WSķäú›ĖÍI'ĸNŲX[—=*Īyøpâøˇö84jÔ‘H�$ĢGčåæjmmĩháûƒî%ÜīëĶįúxõ˜ĩOj÷î?`jbō07ĪŲŲiėčQkÖ­OLzđéĮKßzsėŽˆßææjq8׎ŨH¸ŸØ×§H(ÔĐČ#„^Xdd¤H$Â9‰Oį$^šŗė‹/u:Ļ&Ļ!ÁƒˇlŨĄ¯/555ųōķe�pëö[ˇī˜š˜t|¸B„sBŦsBŦ„sB,ƒsB,ƒsB,ƒsB,ƒsB,ƒsŸ:tø¨joÄņo[ˇaĶƒä”­›ÖĢá’E.„PGāœÄ§Ę•ŠöFT44ŧ7wÎĸ%Â?÷OėøX!„:į$>“8fÔ(Õۈĩĩĩ™ãęû'v|ŦB‡û$>Ũ'Q^UĨÚ‘ĸ(UĮęęęTû'v|ŦB‡sŸÎIŧ~#^ĩ7bJJꉓ§ŌŌ3žųî>Ÿ¯Ú?ņÃĨ‹ĩq{W„^6œ“ˆb œ“ˆb%œ“ˆbœ“ˆbœ“ˆbÍIėŲŗgŗs ‚āņxRŠT# !„fŌVAAšššžžķ9QRŠŦŦŦĖÍÍ500ĐH[Ą×œĻŌVĄšš9Iūã›˛›ˇīÅ\‹—™S]Xįââ"¯Ē>z"Z*W×<ąļ4÷ííŠP(žūácG ķpëŅBüŖ'ĸ‡úéęč<Ž”ĢGĐærŸ<ŠāÛ�"ö 2Jš™WPxöüŸĪëfhp'!É@_"•Húųxˆ>Įáp„ēēƒhd4BJ_ɟ>söÛīô’˜Ôx ‡[Īq#Cßũ†’V–?ŽĖÉÍ7JF <qÜČî֖�+h`˙Ģq7TU6íØ �ûF^Ŋ_WWŋk˙�¨ŽyĸĢŖ�M#4Õ4&�1nTč[ŖßČĘ~(¯ĒÖÕ(•` /Ŋ‹Įãq8CũŽB¨ hfN"�ČåōĶgΚ8;Ģŋu÷~RYyyvNŽĄĄžTĸ/W×Ôė? Jpwuéfhpũæí…aĶ ‹Šŗrr­-Í@*‘ČĢĒ9$YT\’’žáâh÷0/ß\öt˜žÎę� ūöŨÜü�ČĖ~�JĨ˛iL�™+•ĘĶ^ęߡˇ]wkmm.O[{ÍæÆF=œ]í×mpuqäp8„P§ŌĀŨÖĸ…ķų|ž™LöæØ1Ūrëá<*4ØÃ­'‡$ ŋ Č­‡Ķø1#ŪũFԙķ÷“ ęĖyZŠ<9†ŠŌÃÉá•XSãnJ%$§f8;Úßš—čÖãi6l�ŧŨŨ&Œ9aÜH+ �hķúÍÛûFæ*ö>îhoëŲĢgIiAÚ\-ĄŽ.™ËåĒ?h†zeifNâ×áËŊŧŧš~Īčß×ûį˙Æ=*¯P‚rßáH‰XDQ”ģk+ą×įL$ `ķÎ=Ę+ôĨ{;›]û/}Ž––Öí{÷|ūŖōĮR‰˜ Õ(BĶļÅ´ënŨĮË�NžŊPY)˙ëÎŊŋîÜëáä°÷Đ1]][ë>^|’fa&ãâ|C„Ø@3sããã[H[JĨōæÍ›øčB¨ƒ˜9‰šyJ!„ēŒfŌ3_úyī2}4ŌBi&mņxŧĘĘJšĻ•MĐ4]YYÉãņ4ŌBiæqSSSĶÜÜÜ&÷˜ūŊŒBu~ˇ…bœ“ˆb™×eNbNnŪš‹WEB]@āîęr)æēžP— ˆĐā �ØöûkKķ@?_ŒB¨SŊ.sĩ´´Æ1ndhFV΃”t/w×7Bed?€ķ—c,ĖđĢ7„Xã5š“˜ũ0÷äŲ =Üz8īÚäÆÍÛ6–æiY�„]wĢĖė܎B¨ pärųˆ#ÚÂÖÖæĪķįy<ūō˙ûLOOOuŧ °Ø@* X_¯¨’Wöėá’_PdmiîáÚÃÅŅ~×ū#ēēēYŲeåuõŠôĖ,æ3AŨMčfh ¯Ē)-+īįã{ũfoĪ^ĖžŠ"XY˜w34 ęé☓›ook“–‘­SĄP\Ŋ/ë=*/7“™úxšGŸģ˜WP8*4¤ooĪ‹W¯=*/'I25=+7¯ĀŪֆĪĮ5zuEFFŠDĸ×eN⃔ôŊ‡ŽiksĨą[Oį?/_ęęJ%â7G…@VÎÃĖė\‰X¯i@„ĐĢį$"„Xį$"„X į$"„Xį$"„Xį$"„XŋÛBą ÎIDąĖë2'ņZü-Õۈž}<ΜŋœWPøŪŒ)ęû'Ž:X#ŖęT¯ËœDõŊ)ŠH�˙Ü?ąãCę¯ËœÄ>^îĒŊ{8Ųki==qõũ;>Ą.đē듍ž7"­öįNõũ;>Ą.đēĖILĪĖVíXPXôם„Ââ’#‘§¸ÚÚĒũG ĸ…ģR#ôĘÃ9‰!ÖĀ9‰!VÂ9‰!–Á9‰!–Á9‰!–ŅLÚ200Āš;ĄŽ_É#„XĶBˆe0m!„XĶBˆe0m!„XĻ•ŋ$æįįwM?BHE&“ĩđn+iĢåĘ!ÔõđC"Bˆe0m!„XĶBˆe0m!„XĶBˆe0m!„XĶBˆe0m!„XĶBˆe0m!„XĶBˆe8rš|ĈÍž}<2ęë•ßÜŋŸtūÂÅã‘Qƒ‚H˛ųL—ôm÷ļ´ĘÎÎyņđaîįËÃëęëutt¤IģƒoÚŧUĄP¤¤ĻA¨ĮQ¯{÷^ÂįËÃŊŊ=E"QËį{ņŌ•ÚÚēÂÂĸc‘'ŠŠŠ™ƒQ'ŖW>vqvĻ(ęûŸVEGŸšxéōŠč3^^ŲŲ9+ŋų>îÚõø›õķíÛōéĢZ‰:™•éåéŲ™››=/ÎĖ9sGŪō•?Ī_l42­Ú¸y M+˙úëvü͛nŽ]´k÷ķŽu[Ž/sŽĖOT Wše-ŒaøŠ˙¸8;ßš{¯í?ęÍöķ…~YÔĩ|‚L؋—¯TW×XZZŧhpÍj÷9ļ,22R$ĩž–|谐w&N�€EK>*,,ĒŽŠŲļ}§‰‰1I’!ÁCT¯ssķ(ŠĐŋŸŽŽÎƒÉ[ļīāršŊŊŊ\{öØāŸĪ§iZŦ§—–ž:,äDÔ)ŠĸŦŦ,E"ቨ“$‡“™™imeYW[ĢĒhgkģ{ī>#CC‘P˜š–NQ”ŠŠÉņČ(&”ŗ“cLÜ5ŸīáŪëĐaĒŪ&§¤ˆDÂÄÄDÕģ/]Quėđ‘Ŗ$‡“‘™õëęĩLˇ|>Ķ%˙~Î�Ž\mtpéĮŸēšēۈˇ´0gŽ$ÜOüņ§UĶĻžģü‹eúRé;īN˙ģ'ŠĒÁy^+4MOš2͡o_ՙú č¯Ļäūƒ‡jkëúųú¨†qPPāönŽŽ•3enŨžŖ:’–žŽ*y?1iÃēß�`ū‚Ežî"‘P}„Õ/MĶņ,(,ĖĘΙ3kæņČ(ÕđĒ ĸęvLÜ5UCŸ|´DUæÃ%‹Õ‹ÅĨee:AQqą‹ŗSlėĩ˙ŽúqËļ………æææšyy5Õ5Eíˆø}ËÆu'lŪ‚­›Öˇņú2ī2?QĖwīNŸĩqŨoLœO>ZĒē"$IúôéŨÛÛk朚[6Žo4†˙¸vAQQ‘L&ËÉÉéëãŖ§':úĮqU‹aķlXģú˯V89:Ž1<|ÅĘw'ŋŖ UŨšš'ĒZL„nŨēíŨw€ieáûķ�`úŦ°f‡Qõ‹Ŗ~‚‹?üdĮ–ęCĄĩˇˇ;}5&F.¯š6urŖkĄūģéâėŦ~š-_—‰ãßnvL&ŋ3Qũâ6:ßĒĒĒ›ˇn7ũ=í ÖĶÖé3įrsķîŪK bnnöŗ_‡Í™ecm6oAAAĄęu__“n:::�ą{Ī‚ųīY˜›įææmŪēËå’$™–žŪËÍ-(0Ā _aa‘T*IĪČ�?ŋ………••ōFĀČĐP,Ÿ>svėčQRŠäāĄ#ĒPŨŒŒt‚ā!^žMû\Y)WŊ[UU-•J˜Ž1mEGŸQuÛÃŊĶ%UŨ¨“Ņéé™�0(( idO÷””ÔääÔIĮ3GzöpÉČĖrt°W*•k×oœ4ámæøŽß÷´Đ 3ĒõõŠ…īĪ“ˆÅĒ3ÍĘÉQĀŠčĶ‘'Nš˜ŋ?oî—_­P{ÉūŌĨ‹?°´°8}š‰vāĐaՑˆ]ģU%ũ^ŧx‰ĸčĄÁCŠKJZ¸4MĮķŌĨ+jf¨đųĒn9BՐz™ÚÚZ>Ÿ¯~¤—›[ßžú÷›:cö7+žJKĪ(,,âä€ūũ šŋ`QŸŪŪĻĻ&Ĩee‘Q'%bq`€Û¯/ƒšĘĻ&&�04dˆ*ŽúqtphS} ];ßž>ƒ‚§Î˜žüs�đōôPĩ(“™––•q8Zéąqqū~~ęW3$xˆĒnčĐU-&ÂĄÃGU­TW×čęęÔė0Ē~q“¨N ˆFCĄÍÍËč78Šl�� �IDATdđ Š3f7s-Ô~�6nŪĒ~š-_—mÛw6;&úŲč|ËĘĩđ{Ún­§­ā!ƒŪ™8á÷={ĩ8Z�@ĪļjũįëgU hš&ĸēē�ƍíėė”_Pp*úŒŽŽn mŠWŒØŊgÎŦ&ÆÆ‘Q'UTĄ ĘË+Î_¸u2zųįŸ5Š3nėhÕģŽö[ųį)4ęRŗw[*×oÄđūüęęęë×o89:�@ÂũD[Ûîuuu_­üfü[oörsmK+ˍ2¯˙oy¸ęL T*š—pŋžž^ũÜW¯YG$�PÍDP*•ęGT%E"Ҋ•ßR4ĩōĢ/ˇīÜÕÂĨi:ž%ĨĨöövM¯‘ĒƚuTŨ><TÕĐŨ{÷Teø|~ŖZ§ĸĪhkk“$Š­Í�’ )šR§hšųI1ü÷æ/ÔחŽ_ŪLZŧžęÔãüü˯Ē+ÂáĖæxĩĩuMĮ°ŅĩãņøĪ‹č?pûŽ{ģÔ´ôĢąqK>XđŨ?Ģ˙ÜļPWŊ’$�āyÃØė/N C$<K⍎…ú@ŖĶlųē´0&ęũltžęWĒéīiģĩuÃąIÆĪ˜=w@˙~S&OܸyĢ‘ĄĄkĪ!ÁƒU¯ļīŒđööŌ—J§Lž´zÍ:_āåéÁŧ616&IŌČȈ‰fccŊ}g„­­mŖVÔ+zēģ¯YģÁÚÚJ_*Ąhęčąã3§O]ŋiŗ*Tnn§í訸›�°k÷^Õģ6ÖVĒŽũŨĘŗSāršm9ũSŅg˛˛˛+?ö÷ķëŪŨÆĘĘō‡ŸYžBKKĢļŽöãĨK6lÚRR\r"ę䉨“.]ŦÍåļŊõ3íĶĮ[5�ĐŋŸo`€˙W+ŋ™>uŠjĮŽõũO̜˜Ÿu�P?ĸ>āŸ|´”Īįsš\@Đt„]šFãih`PZZĘŧVŨŦēęŨÎČČT5Ô¨ĒFũ 4r56.1éĖÔÔŲɑš^&&ÆT%‹_ôúĒ …Ē8ęWħOīC‡&§¤ÖÕÕ57†-];kU‹}ûú|ôé˛Ãû÷ōxŧ¨SŅ"‘H}Xōķ ėí›Öb^ΘöŽĒ扄Âf‡ąŲAS?5õ&\œUežw-ūūhũGTu]&NxĢŲÂĪë'Ķ™”Ô´GĘŸ÷{ÚnDxxx~~ū† 4ũ 0ˇuߎüē ÚÚ´yĢ››ĢOŸŪęwîÚŨŨÆz@˙~ ŽŠ8¯ Î>ĩf¯ËË&“ÉđÔ<™ŠŠĩՍø›]Ķ\ŖũĖ÷í?˜žžŪŋŸoÃj*Î+¨kN­é>ķ¯ŧÛBąŪm!„X ĶBˆe0m!„XĶBˆeÚúÜÖķÄÄÆUUUiiqmlŦårųęßÖņöō´ąļjĄ|đÁÍžģęŋŋ† qrrė`¯B˙b­¤­.ž]8oîœ?YöÛ˙VuÍÉ#„ب•´ÕÅŗ ‚ĐâpäUU"Ą° N!ÄF­¤­ŽŸ]hhhPZZ†i !ô<­¤­ŽŸ]XZZfh ß gŠú—xĩž’¯­­ũđãĪ~ûõ——Ũ„ĐĢčU|J~íúīĪ›û˛{zĨuôÍZ˛háËîBčU÷jŨm!„PĢ0m!„XĶBˆe0m!„XĶBˆe0m!„XĶBˆe0m!„XĶBˆe0m!„XĶBˆe0m!„XĶBˆe0m!„XĶBˆeZYoëB&L?Y]Ķ™×Žĩļƒ�›—Ũ„XĨ•ģ-ĖY*ĢĻzŲ@ˆmZI[LÎZ?ĸ§Áöq0ĮûÅĸ˙6üšˇkFĀH'xÃ֍|n•q=ĀŽÅŨ0Z-ĐŽÆā`đôĩ‡)œž{ĮÃÖą@�|×Ú°F4éĖ›=āĀ„įvLŊ-ū_ĄÕÖE™ŧ gĶ�žš•0ÆöŨ/3ĐãAE-Čë ¨;D&Ã�+H+ƒjô6ƒ÷Ž=­;Ã|,€ËČ��‡�>; ‘S€$`xlŋÅÁ48x�`\X9f@(́ūV0ˏ$M‚PĐãÃûĮ!ŗ�žXÜĒëÁ×Ļ?맅'î‡zęi֌�Š�näBķ§Õ}-`„ĶĶ×ĢB!ŗæ�XæËÎ@|8�‡OœH†ū–p5�`ĒĮĶā§RÁÛ ¸xĸ�Š�,%X õ PVã]AĀ…Āî˙čų‰čmz<č&„ĖrˆËy: 3kä""ôziëWōųÁöqđ†Ŧŧ�ß 5qĐ@CZôˇĨĸSĄŧļũ2= •p2ĸR ŸåĶēÜāŅĐášô§9 �*ë 2ĸR ĸ”J��’xú֑D8ö�X=ũį;Ŋ⋺0aLq‡OOÃĨLüĪí͔J8• 19ālôŦ°‚úGÁ0‡gÕ§y>{}3Ž%=-I+ŸžjCˆ=čņĀŲĻz6îÉ[=Ÿž—„Á•ŦgŽæĀŅD�å?ĘĶĶ–j@Äŧ6?Bč™ÛŖ—) u€é‡a/TÖÁ_y_ ú: P*Ÿf�˜čö0÷đ0�8tŧĖ ģŲĀŗĖUŖ�-�āf>,č æb��’€ņŽĐŖĖ:ō´úū{°b0čpa÷ø2$|XÕ¸c´”J ˆg…˙ʇÉp#īi: vŪzVũ‰âŲë;xÛ"“�žģßCY P4đ´`ú!H,¸0ĐáBâYđ?’ —)dW€.ę(ČxoģBemã^ŠĘ+(¸]�ų•đ¤Ūv}6 •u/4ü!€V÷I$>áˆŋ ‡ƒ÷áBfG{öúPŽ|Ų=@ˆ%˜}[šÛķāņ ŪŧŲū>Ŋ†Ŧ$/ģąM+ßmŒŋWČJÛĮŊėN Ä6­ÜmØ@և]Ķ„jœÜƒbL[!–Á´…bL[!–Á´…bL[!–Á´…bL[!–Á´…bL[!–Á´…b™Væ$Ō4ũķ/˙Ģxü˜Ļišĸ—žLWW§ią˜Ø¸ĒĒ*--Žĩ\._ũÛÚāā!Ū^ž6ÖVM ĢĘÜBĶwī%¨âœ={ÎÕĩg_Ÿ>m<+&~n^žƒŊũ€ūžZ !ÔÅZI[‡ūa``đŅŌÅ�đāA˛ŧJ^PX°˙Ā!>ŸOĶ´XO/-=#tXȉ¨SEYYYŠDÂQ'I'33ĶÚʲŽļvËö\.ˇˇˇ—­íîŊûŒ EBajZ:EQĻĻ&Į#Ŗ˜PÎNŽ1q×|ž‡{¯7B‡Āá#GUq˜Î¤Ĩ§ĢšūpÉ"�XŋqsaaĄššyn^Ū¸1ŖŎˇˇ;}5&F.¯Zųõ—�œ’ēmûNc’$AQQ‘L&ËÉÉ _ū9�ũãxŖZĶĻNnÔ≨“Ē~:::4Ší;wmX÷�Ė_°č“–4-ā?Đ¯¯'B¯VŌVrrĘđ7B™×NNŽ�đåú\.—$É´ôô^nnAūũ ‹¤RIzF&�øų (,,ŦŦ”@Äî= æŋganž››�F††bąøô™ŗcG’J%Q…ęfd¤#„ņōô`šSÃˆØĩ[Užļļ–ĪįsHr@˙~ƒÍ_°H‹Ãi?7/?` ßÁƒĻΘÍDØõûž°9ŗlŦ­Âæ-đpīåÛ×gPP ę]/OFĩšļXY)Wõ3üë•M‡"?ŋāâÅKE ĸ^]U@ƒĄ×S+iËÅŲ9îÚõ^nŽ�p?QĀį1Ŗō NEŸŅÕÕmĄ:MĶATWWGėŪ3gÖ cãȨ“ĒĒP†ååį/\Œ:ŊüķĪžPUžĪį̧hzÛΈųī…5Š/üã#-Aņė5÷ ÍÖjÔ⸹ŖUũlv(†]ąō[ŠĻV~õåŨ{÷Ú>VĄ6j%m9ü‡Ÿ~ųtŲ<ž6Éá|ŧtņ”É“V¯YgblL’¤‘‘SĖÆÆzûÎ[[ÛFՙžĀËĶÃĶŨ}ÍÚ ÖÖVúR ESGŸ9}ęúM›UĄrsķx<mGG‡įuFŊéO>ZĘŧ—˜ô@fjÚÃÅšQ|gį&&nÜŧÕČĐĐĩg.—Ûč]æ,Ôk5mq×îŊĒ~zyē7 ‘PČįķš\Ž@ hvŦBÔĘZō¯¸M›ˇēššúôéũ˛;‚ę ĖZōŦ�BŠÚ/!ôzxą Į^5ŗgÍxŲ]@u5Ößm!„^7˜ļB,ƒi !Ä2˜ļB,ĶĘWōUÕO˛ķ ë ]Ķ„ĐŋÆÖí¨Ĩ/•L~{ŒŊ­M eZI[Ųy…Ũ-e>¯Í#„^[ˇî§Āęž~ҊšųˇīūzŲŌĘ´ō!ą^Ņ€9 !ÔeĖeĻå[.ƒßm!„XĶBˆe0m!„XĶB¨Ģ•”fdå´ģ:Ļ-„PW;|ėä/k7ˇģz§§­Ŗ'ĸĢkjš}+%-ãîũ¤ķ—c“SĶÕËßMHŧ{?ŠíqBėŌÁ…[ÚŗDaqIėõ›Ú\.­TŽ:�2ŗ^ŽšnjŌ-!)yņŧYę…ĢkžÄ\ģYņ¸Ō@_ú¨ŧŧ—ĮÕ¸"‘PĀįS4-31žu÷ūƒÔôÚÚēņcGT×<š—˜LŅ´XOt5.^"Ö#HrØā€ęš'+DFs8;+ãnFĒ8ƒüûĀ™ķ—U­ đíŖęĄŽŽ@.¯âikWTĘÍe&)iĶ&ŊÅáp:2jĄöųđ‹•uuõĖë/€Åķfuˇļ|Ą íšÛēsMŠTQTRĒP(� îÆ_ÇčGQ”zɇyųæ2’$lƒö{\)įpH‘H¨Ģ#¸—øĀÆÚŌŨՅÃá¸89Œ .{TΔgŽĮÅßč”›—Ÿž™m.3šs}蠀 cGtˇļÔÕ¨â<=ĩVūŧŖęĄ’V:ÚŲPVöČΎXOTņ¸˛gę8G{g;]�pv°sq´ov3°–ĩsŊ-/3™IyÅcfic&G�ĀŗĨÚ�āÎŊÄū}{˙u'ų'M+/\Ž ä/ëŨŧ *ÆĶæĒ—OJI�€§Ņ’’öķ),*aĒ­Ģģs}pĀ€FqT­j=ŧ}/QK‹C„––ĶCšÆ•z9fL�k7īLJI›7ëŨöiOÚØĪįÔŲ ==‚$Ü]]rķ ûxš‹:cjŌ$Éė‡ššų…ũ}ŧāQųcŠD �ÉŠšų…úRąšĖ4úÜE#CĄŽŽRŠŧū×s™Š*2SŪØČđü•Ø žg/^Ņ -Í͊ŠKĨą_ŋ>§Î]ĐærmŦ,lŦ,Tqââ˙ĸ(ZŊ•ū}{Ģz¨'ĩohB„ĪįņxÚíŽŪĘZōˇî§zô°o5JÅãJ’$õDÂu[#Ū›1ĨŅģį.^ĩ˛0ŗënŨî^ļE×´‚j‹[÷SˇîˆhĮœD�XđņōįUd֒×ĖĸĖĩuuĮOžÕ—Š{:;6[ kV|Įuåzh&m™t3š=uâķŪeūŌ×ŲēĻ„ĐK‡›"„XĻ•´E’ä“ÚēŽé BĩE+m-e9ų¸ē)B¨käæ0´ •´%ÔôphiuT„zæ9ø"•ˆ§ŒÛrvoīŠzeĩīé‡ļäB,ƒi !Ä2˜ļB,ƒi !Ä2˜ļB,ƒi !Ä2˜ļB,ĶĘs[ųųų]Ķ„R‘Éd-ŧÛJÚjš2Bu=üˆbL[!–Á´…bL[!–Á´…bL[!–Á´…bL[!–Á´…bL[!–Á´…b™VŌÖņȨwŪūõĘo?ųėķ›ˇ¨ŋwúĖŲįUdŪũķüÅĖŦlõã›6oģvyŨôŨ–cĒ0ÛX¸3įĖmšwī%Ė›—_PĐBÚÚÚ~~Q§ĸ[hĨŲ㍚V+..Yŧôã~Zĩė‹/÷<¤^ĨišÂņȨĐŖkkkā›ī~ČÍÍ{ŪŲĩÚÃĻDčUĐúÎ=ĄÃBŪ™8ĻéISĻų¸mûNc’$ssķ(Š2559Åįķiš‹ÅEEE2™,''§Ļæ EQVV–"‘°ĒĒj÷Ū}F††"ĄP=rrJŠH$<{îĪFĩÔcėú}ƒƒũš?Ī”–ž:,„ŠxôãEíˆø}ËÆu'lŪ‚­›ÖĀôYaÖũ�ķ,úäŖ%ûzÚ===ĻēžžŪönŽŽ•3ĄęjkˇlßÁår{{{ÅÆ^Ŗ(j@˙~:::‡%9œŒĖŦ_W¯eÎZĀį3Aüú1gqđđ‘Y3§īŲģ?thČĻ-ÛTį2jäU+›6oej™˜˜¨ËåŠDЊ U1&āŊ„ssŗ%‹‘‘‘y/ážjôōōķI';'gûŽ椊‹K ˜Q#ƒ~ūå˙÷Ų'LĀ´ôtÕ 0噺ģuûŽĒiõ2÷“TčéáŪh|\{öP•tvrŒ‰ģ&āķ=Ü{Ŋ:Ls?–ĩ¤õ´uúĖšÜÜŧúzÅÂ÷įíú}O؜Y6ÖVaķô÷õ51évđĐ.—K’dZzz/77ßž>ƒ‚§Î˜:4D*•¤gd€D,624‹Å§ĪœØ(>‡ÃiTK=fqIÉŌŘ™ÉĸOŸáhiøôKLz��^žRФ´Ŧ,2ę¤D, đgtņâ%Šĸ‡‰Øĩ[Ŋ{Lõe_|štņ–§ĸ˙ŋŊûhę\˙�ū=' $ldĸ KAp‹˛EÜÖmŨâ¨Ŋvx{{­ĩęŊŪŽÛŪօÅU+j ĸuPë^YJYjeОrōûã` *‚GŸĪ_š'īûŧĪyƒß&šÉÉQ~J䎝ķįÍąąļÎĘĘŽ(¯066ŌŅŅāãĶ'///66Ny֞ų"üDŽãöî;°#rsJję•ĢWUĪåį¨ŊĘU”ūÅe)gg�ĒÃøš~žũ -^ĒP(ŲļĩQîŪŦ™ĶķōōŽ9Ē<Š^=z´nmĻļ ..ΊŠiŋÅ'VŸĘ&đãųŗS]ZuŒ_ŋžĘ ŧ˙āÚūlØ´Y9˛ĩ™™ŽLÔßĢ‹įKüō|4ĮVP˙€ ãÆōˇÄÆ1Lõq卑Ç9;wĖÉÍ=§­-­[!|ũ†Y3ĻY˜›ŠŽŠw‰ēŗ”5\ąša� ÃĐÕÕU98tМyīˇjeŧtqõI†††,]ļ\Îɗ}ųÅĩë×UÛã§+ –aČå\õš€á8Ža˜ŌŌŌēí1 Ŗr֌jĮ=ā‡Ģ89ˇqķVOÎĘsQ[…ŸUģT=Ã�$'ßîā?jÄpš\>nâäÚ×Ũ=åI9¯ļ §ĪĘ;{ÖüÖ;žŪ•côõõ•ąu[ŨũQŽ451)((<qōTtLėĸĪ>­īą%¤é=ßĪģNš8nŨ†MfĻĻînŽNNŽ[#§Ož~ƒ…š9˲fffʑööv[#�tņđXĩz­m+cŖœÜ\wwˇgÕįgŠÖ>lČ×ß|įčØA$RŽėííeaa.¯’>ũ'ǝ§'•J%‰L&›4qüŠUkÔÚ1lčWß~×Ņɑe™§į5~ÅĒ52ŠĖĢ‹§˛l+cãēg-‘HT{Øļc×ē5+LMMĖyīũĸĸ"å]uWQ+%‰ęĻ€âķÅK,ĖÍ+*+(w/#ãŽL&U=)?ß~õn‚X,^øáÃß;oÎėzĮˇ26V]ZuĖ' ?Rn`ŨũQÛŌŦŦlmm-''Į˙piJĖâŋsrrÖŽ]ÛŌÔ/7/O,›™™N›9›ëĒŽ­Ûv´ŗˇëĶģW3÷öZĄM oƒ°°0++Ģį{ļÕüJKKŋũî+KKŋ~õøi÷ž´´´IÆ5sc¯ÚōVyŨcĢŊƒCøĒ0fô¨fkæĩE›@Ū*ôqSBˆĀPlB†b‹"0[„ĄØ"„ Å!D`(ļ!CąEŠ-BˆĀPlB†b‹"0[„ĄØ"„ Å!D`(ļ!Ŗáz['305 w ›§RÍΛGÂמĨû äĩ¤áŲeV‹¸SˆŠQš‡ōvŌ[|f…AĖd�88áC4éŠö­j功Į‚yÆøēÜÍáhROŦ Õüô„/ûÂcęíJuŨē k\‘ŸRwũׂgiėE™¤hgŒV:Č.ÆøNÜRŧw÷ą ×ō`o ‰Ķö€Ÿ=>ÆŊQZžm1yƸC&ÁøÎuĒ5‘ãU ˙v8”Œ>ļØ~=l` ÖzČ(@E>F7ëZË)kĻ�ĀdĪę)O0ŌË1}ŧŦjĻ|Ꮥg1Å{nâ˙|‘Q€Yû`õ`”VĸkĖ9�O˚ņ߅ Ŗ�g3ŅŨžTÖŦČßÅwÕÛļf]Ĩē'ŽVíJ.ŧÛĀ΋Žã7Ë įđđqucļF˜…õÃ1ûüYTßÃ@iü[ō‡’>‡“`Jüã(Ng Đ ļ\‚;ä?Ž†Ú5S IAB&œÍŸ‰ũˇ0ŲS}babSPP†Í—`e�H}ˆŪT×Q[NY“§œ`ß-HB[õ)�ø_ķR-Ë)sҎŅĢm­ņü˜ąĒĪHÂÖŦ¨:]u]5ĒMĒUęŒĨ'pë~õ°-—PTVĶØ ˜Õ%å”Y„4¤ąąu$.­›�[/ã tˇÁŅT�(—#ę&LtpˇÅåĩfq (5?[w"� PT‡Ë 'čHSŒ'Uí^˙,ĩšĘ)&ēãŽ!ˇÖ”‹9˜ßNf�ž_SNqTĪQžQ…ŧfEÕéĘu[éÔŗ]jS”Õ&ai ÜĖÁ)jv@ŲXĖmŒrÃúßų˜ō–Ōđ;‰ĖgÍÜOķYŠ=7q2ŖYíī€!Îhkˆ)Q((SŋwēüÛaÂĪÕ˙SąŦY{#äõרßI4ÔFQyÃC„ęŊC-°h\âŌžyØxąúļ­QķtDˆđhx‘¸"ũûiļFˆŲŌMōēŌđlË×w>nžN!¤QčË=„ĄØ"„ Å!D`(ļ!CąEŠ-BˆĀPlB†b‹"0[„ĄØ"„ Å!D`4ÄÖÁCŅŪēdŲō%˖‡¯ÛđŧÕĪ;Vī]ĶgÍnĖxeŸ|úŲē ë-uíú™asŽÚ—qįîú ›Îž;¯V!dđ0ūvZZē“kįĒĒdžŨąõˇøDM'�u—#„ŧjš/Ę20x¸ąüíÕáëÜ\]är.55UÎqyyyÖÖÖYŲŲĮŨũs”T*å8.4d⯈-‰¤ĢˇWbâ9š\niiqđP4o€ŋ_ÄÖČNîîųų|ͤ¤dĩņ=ēxųŠL*õôčŦl€ã¸ņ“Ļ0`RĶŌC[XXlŽØjaaβl~~>+eddØŲļåkĻĻĨ)ûqrt477OHėŨĢįî¨Ŋ]ŊŊ�\ŋqsĮޟĖLMõõôRRĶärš™ŠéŽŸvķm�ˆ‰OH()y´lÉ�ĻÎ[ģf%€yķŧ˙Ū\å\~š•ĢÃģwëÚÕÛkúŦ؟~˛PšôĮ.hÚG‹‚ÆÄVtLlZZ�o/Ī9a3?ųô3‘ˆũ÷˛%6nîĶģW`€˙ŧų 6mŪĸŖŖÃ˛ljZZDäļųķæØX[geeW”Wí‰Ú'‘Hø{ėūëŖūÖÖÆæHėQž~䎝jã>Ėבɂƒú{uņ<t8æhÜņŦŦ늊Ę÷ߛ›”|ÛßΎ__ŸĪŋX6k†ŊmØÜųC‡—({ŽÜļCšb;{ûАE{uņ,{ōÄØØ€‘ĄĄ™ŠŠĄĄáҏc#† 566Úŗoŋ˛¸ãŋúöõé0yÚLž`P`ĀŠS§årn@PÕšū~jÛĨētYY™T*m’Į‰ĸô|Īļ*++Ë+ĘY†U}%į8�#‡sv›ģzÍ:Žã†)--UŽQŪģbÕ–aČåFmüČà Oœ<ëéŅ9¨€˛¤äÛēēē�†Q^—™Šī”+^ŧxY$n‰Ü>8tЖČí�Â×o˜5cš…šųĄč˜zېÉj]k944dé˛årNžėË/–-˙Jm.˲ …@YYšęŌ”Y„ŧ Īņl €L&6ųŨĘĒĒ˙~˙ŖI+ãøÄŗˇūH˛˛´?nôŠUk,ĖÍY–4qüŠUkdR™WO{{숭‘ͧN_ŋŋwİĄ_}û]G'G–­›ēão§¤æįhkk999>ĢĢIĮ­Û°ÉĖÔÔŨÍU[[ģÎŊã•ũ¸8;7æ?ūGØĖé|luņđXĩz­m+c#9'ßāā”Éī*Û¨ģœžžžT*•H$2™LunNnŽģģ[Wo¯¨Ŋû“o§”——Ģ.ũɏķBž‹†kÉ7`ũ†M:šwīÖõU´E!uņגŠ@đ¯Œ!¤95öį]ëš9cZöA!D7%„ Å!D`(ļ!CąE oɟ<“¸īp,Įq¯´ –e‡ öõéųJW!„ŧ4<Ûj†ĖĀqÜžÃą¯zBțACl5Cf5ķB„ĄŖ÷ļ!CąEŠ-BˆĀPlB†b‹"0/ōUę€~Ŋ :ģš\Ŋq+7ī~Âų‹MŪ‰õl��MIDAT!„<ˋÄÖņSņ�,-ĖŖÄ�čÛÛÜÜėōĩm­Û¤¤e¤ßɜ=uâžÃGzvķĒŦ¨dXöБc�ē{{N=Āš —ˇíŪפgAy‹ŧø…k”äwãVōÉŠm­Û(öíÕ].—+ 3S‰X\YUuîÂe�ÚŲQfB^FÄ€ōōr�üĩØH$b�į.\ÉÎÍ324¨|záųs.ķáE!/Ŧib‹—–qˇ‡ˇ§•…šX,>pn@ oQQ ĮqbâšpBČ[îÅckåēūÆÉßĒ 5ũNfúL�gĪØ˛cĪËvG!uĐ !CąEŠ-BˆĀPlBæu‰-ū“„ĸ‘†Ø9x`3 Ã0#BŧęU!o €đõéI—x'„ŧV^—‰„ŌH[„ĄØ"„ Å!D`(ļ!CąEŠ-BˆĀPlB†b‹"0[„ĄØ"„Œ†Ø:x(zÂģS—,[ždŲōđužˇzBâŲŖqĮęŊkúŦŲüõ6=wžá:ŋž8•qįŽę‘ßâWŽoäZuK5r°Rcš$„4Íג<aÜXūöęđunŽ.r9—šš*į¸ŧŧ<kkëŦėė‰ãĮîū9J*•r2pcĉDŌÕÛ+1ņœ\.ˇ´´8x(šŋ7Āß/bkd'w÷üüå‡ĮüŸP\\ōΨ;vũdfjǝ§gee™pöœL*õôčœųg–žž^yY™˛˛™Š€õ7ßģwĪĘĘ*33ķņã'jkųûųnÛžĶŅąÃņ_O¤ĻĨ‡ Nž}[__o˙/årų–Čí×­‰DasįoZ`挰ĩkV˜7ÁûīÍU6Ã÷šrux÷n]ģz{MŸ5ûĶO*Oųã4ųCyÍą›––ĀÛËsNØĖO>ũL$b˙ŊlɆ›ûôîā?oū‚M›ˇččč°,›š–šmūŧ96ÖÖYYŲ寯F{ĸöI$ūŪģ˙účƒŋĩĩą9{Tš„OŸŪÁAasį‹Åb3SSCCÃŖqĮ† Ŧ#“õ÷ęâÉ?Ņ‹ÜąSYųnæŸ�D"QĪŨüũ&O›2 Xm­û|ôÁßÚ´ąŠ='‹ũũ|ûõõšõG�¯.žÆÆF=|x(:ÆČĐĐΎßIP`ĀŠS§årn@P#CCe3ū~jÛšm‡rĄ˛˛2ŠTÚD!Dƒį{ļUYYY^QÎ2lÕ͟> į8�#‡sv›ģzÍ:ūKKK•c”÷ŽXĩ†eX�r9§ŧW$b0 ŗykäŧŲŗ,ĖÍEĮŒ1Ŧ  đÄÉSŅ1ąffĻ�0u+kkĢį…r­WŦfX�ð�tuuÕF4gŪû­Z/]ŧˆ?˛tŲr9'_öå˖5kÆ4žū^–e €˛˛rՅ(ŗiNĪņl €L&6ųŨĘĒĒ˙~˙ŖI+ãøÄŗˇūH˛˛´?nôŠUk,ĖÍY–4qüŠUkdR™WO{{숭‘ͧN_ŋŋwİĄ_}û]G'G–­žú œãø:––n..ĢV¯ĩŗŗmelôõˇßs§­­åä䘟Ÿ@ĩ˛™™™ZŸu×>lČ×ß|į訁ÅēƒŊŊŊ,,ĖåUrCCCū¸žžžT*•H$2™Ŧ‹‡‡˛™œÜ\wwˇŽŪ^Q{÷'ßN)//į›áúdáG/ü�Bžŗxņ✜œĩk×>īĖõ6uęäŪŊ[×WŅV“ČÍË‹ÄffĻĶfÎæßēĒkëļíėíúôîÕĖŊB^@XX˜••ÕKũ*5˙ŠéĩUZZúíw?XYZúûõĢwĀOģ÷¤ĨĨMš0Ž™#„ŧŒ­™3Ļ5a¯B{‡đU?60`ĖčQÍÖ !¤ŠĐĮM !CąEŠ-BˆĀPlBFÃ[ō'Ī$î;Ëq\ÃÃ^˲ÃĶ2BCÃŗ­fČ,�Įí;ûĒW!„ŧ4ÄV3dV3/D:zo‹"0[„ĄØ"„ Å!D`(ļ!ķ"_Ĩč×ÛČĐ ŗ›ËÕˇrķî'œŋØämBČŗŧHl?ĀŌÂ<ę@ €€žŊÍÍÍ._ģŅÖēMJZFúĖŲS'î;|¤g7¯ĘŠJ†e9 ģˇįÄŅÜģpyÛî}Mz„ˇČK]o‹'Ꮞ’˙HNmkŨFy°o¯îrš\ĄPX˜™JÄâĘĒĒs.čĐΎ2‹ō2š ļ�”——ā/ô@"8wáJvnž‘ĄAåĶ ĪŸģp™/ByaM[ŧ´Œģ=ŧ=­,ĖÅbņé„s}‹ŠJ8Ž;ׄĢBŪr/[+×Eđ7Nū–ČßHŋ“™~'Ā™Äķ�ļėØķ˛ŨBHôBˆĀPlB†b‹"0[„y]b‹˙ä!„h¤!ļFØ Â0ˈĐ¯zBțAà |}zŌ%Ū !¯•×åE"!„4Å!D`(ļ!CąEŠ-BˆĀPlB†b‹"0[„ĄØ"„ Å!D`(ļ!Ŗá;‰÷ī?XūÕ7––…EE;5ō…WZŋa“ģģ[îŨšddBâŲGeeį8včЧwOĩãAũ9ūš<ĢxƒÅb‰ŊŊ]IIɊ•Ģƒ‚ú{{uąˇŗ}Žâ‹—ūknØ,SS“oŋû_qq‰HÄũũãŖŖ¨íŌŗö?ŪÎŪ^õĄ451ĩˇˇ{V3ŧƒ‡ĸwíūŲÉŅ‘ãä%%žZžL,Ž˙FĩyŽãūûũ…EEĮqrnŅg˙ÔÕÕ)++ķī?đŗ˙ûGȀ↗HMM _ˇÁČČPOOīQiéܰY­[›5Đ$y iˆ­ë7nX[ˇųpÁû ä§g¤ĻĨíū9J*•rgh`š–208bëļĩkV˜7Á' ?Ŧ; __eÁäÛ)›#ļZX˜ŗ,°1b‹D"éęíÕŪÁaĮޟĖLMõõôT_ˇ!//ĪÚÚ:+;{äđaĘ1)ŠiršŧC‡ö1ąąņ %%–-ųĀū_ĘåōÖ­[īúég~•÷ߛĢ<Ž:~Ęä‰ĘV?ūp€õ6ņ [XX(› ę¯ŧ••-—ËûôîĨŖŖ“””ŦlŪŨÍĩîYŽ>"—ËmmÛęë뎎aEĸŒŒ ;Ûļåeeõž5F––EĢvÕŖ[7ũŊûą07˙ûĮ(**R^–Cu?eRéĄÃ1ŋÅ'—ŧ3j„Ú~Ē=”ąqĮôõõ ļmßéčØáø¯'‚úŪģwĪĘĘ*33sņĸĪøY!ƒ'Œ `Á‡ ķōî•>~Ŧqgöî˙ÅÄÄdáG�HJJ.yTĸĢĢŗgīžͧîÜĩ[-ļę.Á°ėĸĪ˙ŲĘØxÂģS'Mo` ˙ÖäÍĻ!ļü|û.ZŧTĄP Š;vL"‘°,›š–ÖšS'?ß~}}rrrO:-—s‚úGnÛQw€jÁmÛw†Íšaog6w~vNÎüyslŦ­ŗ˛˛˜™š;čī§/bŲ>Ŋ{øĪ›ŋ@,)ĮŒ6ÔØØ(+;Įˇ¯O˙Ā€ÉĶfōãŊēxEíŨ¯\Ĩ´ôąŽŽ\uŧjĢeeeRŠT$ķ ūÅåôÜÜ<åíŪ={ZX´ÖŅŅšc§˛ų ›6×=ëŧŧ{ÆÆFié�||úäåå—¨MT=kūŒöDíSëj@p�Éɡ‡ `×O?'%'›˜˜HĩĩÕöŗ“›ĢOŸŪÁAasį‹ÅbĩũT{(ųíú9jīGü­MĢØŖq"‘¨gîū~ĘÍp4îxVVöĩë7‚ƒú[[ˇiĖÎ$'ßÂOīØŅ �Įq{÷Øš9%5õĘÕĢ;ĢūI¨-@ĄPŦ_7~ėhūÜ QŖ!ļ’“o÷đ5b¸\.7q˛sG§‘Ç9;wĖÉÍ=§ĢĢ 44dé˛årNžėË/Ž]ŋ^w€*†Šš~†˙iÅŌŌŌČ;g͘fan~(:ĻŪNäˇykäŧ9ajcd2ēƒUWaŲZ× S¯lU*•ōGø†k5YëļĘ*ÍĢ–Ē÷ŦkõĻéŦëvĀÅÅ9ņė9WįącŪy˜ŸŋüĢoœ:t¨ÛĒHÄō76oœ7{–jeĩ‡ŌΎ�§`X�ð�´ĩkVäõ˜0nėöģÄ"q#wÆÅŲųėšķ;š¸qķ–L*MĪČ�đÊUœœÛ¸yĢ_ŋžW¯]7f4jjK”——šėßcŪÅW ¤. ąĨ€âķÅK,ĖÍ+*+õõéŊbÕ ss–eÍĖĒßqĐ×Ķ“JĨ‰D&“Mš8žî�U“&Ž[ˇa“™ŠŠģ›k˙Ā€ĢÖȤ2¯.ž]<<V­^kggÛĘØ('7×ŨŨM9%>ņė­?’Ŧ,-]]œ•cäœ|˙ƒ.ÎÎjõííí"ļFN›ōŽr™LĻ<Ž:^ĩÕO~ôŦ&ƒƒ•ˇœ#ļFz{{ĩ26æ§ķÍ×{ÖüŠuv`|Ŋg͟ŅôŠ“Ã×o¨ÛÕ°!ƒ˙ûũŸ-úR,•”<zwŌ„ķįWk•‰øŊ˛´´psqQÛOĩ‡’˙EŪáÆ|ũÍwŽŽøŧ{–ņcĮL›9ģOī^Ų™ĄCBŋūöûüķsmm-V$úûG,ų×ōukV˜šš˜ķŪûŨģu1|čŗ–ˆÚˇ˙Áũ‡ŖcGĮ|üŅZI‘ˇŗxņ✜œĩk×ļt'õ[ŋaS§NîŨģuméFŪLšyyb‘ØĖĖtÚĖŲ›Ö‡ˇt;„hfeeՔŋJũŠ(Š–náUZZúíw?XYZúûõké^iŦ×=ļfΘÖŌ-ŧÉÚ;8„¯úąĨģ äųĐĮM !CąEŠ-BˆĀPlB†b‹"0[„ĄØ"„Œ†ĪmĖĀÔ(Ü)lžfČۋe`o 9‡;…°3Âæ‘đĩoéžČëJClQf‘WjN7h‹ !+CŅÕĖg¸SˆŠQČø¸Ĩ›#¯+ ąÅgVøØˇBy&aũ…FÕ]Š=7q2ŖQæ÷ĀÍûĐĶBf!ūŒâr�ŋ˙ Q/RoYū •>†:ÃPŠmWđ¤ Ws‘šßP‡üI•”Ŗ° 3öÕ?ÆŨåU¸ũ�<-ņU0ōŸāq%ĻīÅ‚ákîáõŧ–‡ūP�'ŌĄ#ž6ūq´ēā?ûĄ¨Ŧf?ŊŦ`ß š%H¸‹.Vę-ņ5sK°ëޤ4t:uÛū2�KOāÖ�åŠ1îŊ ųž”‡E­GĄŠĶ<ĨŪĮ…ožep0 .fØsöÆđn°įŽĨ€˜Å˙� § ĘĒđkzõ\ú%i@cŋÜķÍœHGŌ<ŠÂ GHņŪAlŽ/ŚĄ˜ô3Ūqƒž6J+Đŗ-&īŠž5žwŦÜ×=l` Ä?Ņĩ œLąįfõ°˛*t0AĨ“<°č8ÎgĄ›5”×›Q-`¤+–bú>ô°Š.Xđ�ڛ@ŽĀÚßq4_ãác„8Áģ 댰č8ŪqĢé-Ŋ æ¤Ž§áîÂZKô´Ššũ]2 0k?�üŗū‡ Ųp4ˆE+NFīļˆĪŦĩK|ÁEĮ°įb&ãĖ]>éĶ+ŲĖęŠ5į1žSÍ~ūšŽoÎT˙RSAĩ&¯j“÷ą ‡ŦbøˇÃĄdôąÅöĢÕŌZX3ģâƒh�ã™+ÃX†ßŗĐÍēĻNIyM… �ø?ßZÂŗ6gpĮšíåMë‚î6ˆ0moMķw °~8’`VWČ9Œß5CjΎŅž6ø*“ŖQ�B4zžˇäĀģøĮQœÎ@ Ō 0É?]ÃLoÄĻ@ĄĀ‘$dÂųékĻtŠĖ�ŠŅÛƒ;bŲI\É­)ģūÖūŽM—Ā0`„:á3_tŗŽ§€}ˇp }lk ōÂĪã›3čl ÃĢ rÂŌ¸u@=ŊXčƒsŗąōl­%To_ĖÆ?ĒsOŸ¨či!¸ ´ál†É]°4KÕ JÅØ1SŖp!Šų8ûgõ�CdĒ<P<1ƒkUPm2b$úØÖjLĄĀ–K(,Cl ʰųŦ j6„oûNlĒ‹Ägb˙­ęYkÕQ­ĀS{žĩ9ĒÛËÛ ųO #ĄvMķ˃đ¯“�ô�íZĄ­ę2”bøv Uŋ !õkėŗ­…>XРߜÁ“J|á#)>ˆFq9>ė¯Oãėlŧ#]Á) PÔ\4nëåšÁßĀĩ<äãJ–ĀÚ¸ŽžĘōSøO0˛Šđ¸9Åõņ´Äw¸ļƌ}XX]°•�Léī6°ø5NĻ�sKam�î�õŪ�|sO°<Û¯Ö,Ąz‚Áí1Ú‡’ā?§ņī <| 9m1ĻFáÖœ ƒ˙a<ŽT/č�N÷{áo‡PT^ŗâŸEÕ˙n•ûéeU}WgËZū—€ yõūŲ–ę>tļ@š�€BūƒœĒ7äIFģ#âîÖyŠU.W¯ŖZĄŪGAuŧęæčjÕÚ^�Q7áÕw Ģ_`Ē6?ʧī`Ī ė]ũē•gcˆ÷z�@”WŠwKHŊ4\o‹ųŦ™ûiJũ0Äm 1% e-Ũ €§īmÛŨkíƒ%ŋÖʈĻõǎWąŦékĄkÔõļ ĩk=Y–¸4ÄĨĩtĩ]ŋ×L™`ėO¯ļū+Ũ^Ûú^KÂĶđŪÖū‰ôDš›­"^ü—íțOÃŗ-īÖOÉ̍¤wHķzŒË75"o-‰Øļ…žŽŦ1bënv^ģļV2ŠvÃÃ!¤I<)+OĪĖqulčK^$VTVQfBšLĒ­ņå}•š"0[„ĄØ"„ Å!D`(ļ!ķ"ąuņĘõßoōVžåvjúĩ›Ô=~ķÛūz˜™•Ŋvķö‚ÂĸįĒyâLbrJ=ņæk6žÎņSņ)ijŗÂ7mSÃ÷ߘĘĪģú QëayŲ_ĨÎģ˙ ņüE-‰„S(:ģšÄŸũ]__O&•ŪNMŸ9e<€M‘솄)Į ¨6KGGVRōH[K̰¸ÄÚĘâvjú”ņīüz:Ą°¨Ø¤•q~AAyy…œãN'œ ›2eŲu[vĖ™6 @NŪ=ŠTûŌÕ,Ë0 āøéøĸĸbc#Ŗŋæ÷îá}ōLĸ‘ĄÃ˛}dįæ8“(‰ÚÛÛ¸|ífRJZYYyĪn^Ęļ9Ž“Jĩ¯ŨJRÖygØ �k6F*Og@ Ÿr<ŋ |'ĨŸüíŦ­M›GĨĨ™Y9Ę1y÷îË9ŽĩЉTĒ]YUĨėJKKĸļ _įĪėÜÛŠéZZģļ6]:ģ8~*>īūĪNŽ­Œ”ûæîât*ūœykŗë7“Ü];vhgį`oži›ƒŊ­TĒ}ífRŊS”žäãNH zŲ‰§Î) †aî=øKK"Ö××ĶՑ]ŋ•ÔÉÍųVRĘÍ[ɝŨ\TĮTVVĒÍRp §ö}>Ė÷éŲÍĐ@ŋ°¨˜eŲŽŽū}{—´icéáîâáîzņę[É)nTpvjīėØÁČĐ��˰Úųųô|đ×Ã3‰įûûų„ųgeį”—W�8“p~@€ī؃ÛŲĩāŌŅqhHĐÃü]™˛íęMQŠÃQ=ēãy‰ŋ_ ôķé%‰TĮØÛĩõpw‰D�Tģâä ĩUxOĘĘ´´´<Ü]=;šV÷Ã˛nÎN.NT÷-ūÜÅЁú‰Åĸz×gMyÚáK> —}ļ ģ—g+‹‚ÂĸØã§}û\ŧrÃĢŗ{ԁhNĄ3bđŨŦlå‰Dĸ6ëĘõ[bąˆaąX €aŽĢšŠ Į)0�ŧ=:­ßēSOWwˈĐšŅ’TŸ¨žH Ã2|eūpYy9�m­ęNâNœQļ]ˇOõtöˆŠ;� ~!Žãę­ŠŪŖžĘĶņ(}üäæɗ¯Ũ9$„?(ÕÖRÛˇ#ĮOņ—›‘Ë9öéåxø˙*<sĘą“ĘØCB^s/[—¯Ũ¸÷ā�w׸“gŒ –ąˇĩ‰=~ĘĖÔDOWįۃŋ$‰H$Ō’Húöę~äØI~ĖА �ĒG ôõë]"9%=+'¯•ąĄĩ•ʼnßÛŲŲp§#kčËJJ}{u;vę7}ŊļÖm´$�>Ŋē9~RK"ąˇĩQŠÚvaQqŊÕ¤Rmå騎/(,jk]}ÅŦn^ĸZYZ0L­­P(į/]ĩlŨZ­Ģg=å9“ø{~AX,ļ˛0¯sR5ûÖŨËã@Lœ™Š  p°ˇ=wárNîŊĘ*õĢNQí°1{HČëIÃõļ.ßLņtíĐĖ=8~*ŪÖĻMûvvĒO'œkmjÚŅŅĄųûy…oÚ6{ÚĖ&Ķ@ėđ×Ûz}?�ĄPÔúš†„ķķî?pęĐŽĨú!„ŧ&šāŊ­W! _oĩ#ŊēyĩH'¯?zĒEŪ6¯īŗ-BŠ—†ØbYöI™`¯ĘLš'eåõū?ėĒ4ÜíĐÖ*=3‡ŽnJiüÕMŖ!ļôte _fBšŊˇEŠ-BˆĀPlB†b‹"0[„ĄØ"„ Å!D`(ļ!CąEŠ-BˆĀPlB†b‹"0[„ĄØ"„ Å!D`ǝˇ•ņgnËöA!T[ö6–-Û!„4ŊH$„ Å!D`(ļ!CąEŠ-BˆĀPlB†b‹"0~Ū•"P'Ī$î;ËqÜ+ĒΞėđAÁž>=•Gū—€…1¨RŧTY1ƒobA¯—~Š!¯ĢWšY�8ŽÛw8VõČĮGPõŌ VqXxDÊ-BŪL¯4ŗę]BÎĖKe4gÅ!D`(ļ!CąEŠ-BˆĀPlB†>ˇEČ[„a˜Đā�Ë0 Ëîų%Ú§G×ĖŦė”ô;¯hEƒåAĐ׆œƒL‚Obč�#ÂĪca—cíīĪ]“b‹ˇH7/’GĨ`ea.“jķĮ-Í[ûųô,,*æŠ{÷˙rlo_YY™q7+'/¯g7¯ĘŠJ†e9ö+ÎėŠô„Ÿ€.–X„ãi�đe�nŨĮO×_ä,čE"!o+ ķô;™üíœŧ{EÅ%ümŸ^ŨâNž‰Ž;aĶÆR_OˇĸĸōĘõ[—¯ŨčÛĢ;Ë0 …ÂÜĖT"~‘g9îæˆŋ[}ûR.œL`Zôn‹¸Ô< zļEČ[$+'׹Ŋ}fV6�ë6–••Õw( xúĨœķ—ŽjkI\;:zvrpî•ėÜ<#CƒĘĒĒXņrúŲãú=�čb‰?�ĀæK8šŠˆ‘ŋ*žģ&Å!o‘ —¯ Øü¨Ą•UU§8Į?p.СOqÉŖĖ?ŗ{x{š´2ĒŦŦĘÉŊ—~7s@ oQQ ĮqĘÁĪeķ%,ÂĒÁ¨”C&Á§GÔ -‹ŽaÛ;ûʞ3)ļy‹(Š_ĸĒ9~:žŋą+ę@ŊSļėØķ2+Ęø{­o.b×Ķ÷ŗŽäaØöŠIīmB†b‹"0[„ĄØ"„ Å!ä1LËkŊÜĨMų "MąDąEțiäāõÄJĶafDč�Õ#߇@$zŲ˛"ž a }�‚7“¯OOÕ Ŋ7ƒŊ4\žŠĐŗ-BˆĀPlB†b‹"0[„ĄØ"„ Å!DO¯­ÃĐÖÖŪącG‹öC! Q(;wîÔÖÖ˙š-##ŖŗgĪž:uĒĨ#„g’JĨFFFācK$™˜˜´tK„Ō(˙IZŌ Ë ŋg����IENDŽB`‚�����������glewlwyd-2.6.1/docs/screenshots/scope-add.png�������������������������������������������������������0000664�0000000�0000000�00000125007�14156463140�0021224�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��#��7���Ąö���sBITÛáOā���tEXtSoftware�mate-screenshotȖđJ�� �IDATxœėŨg\×ūđßöÆŌÛRDĐ(‚ŠŊwADąkcn’ę51Í$7Řä“ÜÄMė" Š]Ŗb‹ Ä.ŌDé}ģû1ē!h`Yˆæų~xą{æœ3ŋ¸×'sfg9oŊõ���� ø ß(•ĘšššúúúŽĒ���⑯įķ-,,$ÉŊˇÆ ŽŽŽ ,đōō2n����)•Ęœœœ;vYYY‘1iÕÖÖ:::žõÖ[§C+���xTI$??ŋ?ü°˛˛R"‘p™ uuuˆY����­Äår'Ož\WWGD÷’V}}}—.]:´*���€ĮD—.]˜ßų—ˆ8ŽP(ėčĒ����bąØ`0p8\nGW���đØBŌ���` ’����[´����؂¤���Ā$-����ļ i���°I ���€-üæģ����@ ũvöw™E&Ži���°I ���€-HZ����lAŌ���` ’����[´����؂¤���Ā$-����ļ i��Ā?šÁ`ˆ?}úôƒ›RRR6mÚ¤ĶéĖžŧÒVgŋîSĸώĻ���@ë%&&^¸p၍ÂÖŲŗg÷ėŲ“žžköäm–´>úäSo_˙ ^!*•Ē­æ���`[÷îŨ5 [įÎÛŗgqšÜ   ŗ'o›¤ĨŅjˇĮ'p8œęššŊû´Éœ����íĀÛÛ;&&†ĪįĶũ°•ššē{÷nƒÁĀårŖĸĸüũũ͞ŧm’ÖūËË+fΘND[bˇĩɜ����íŖQØJLLdbVdd¤Ų1Ë͂Km•´6o‰%ĸųķæôéũÄšÔßŗ˛ŗu8zėø„É‘~Ũƒ{÷í˙úŌˇĢĒĒZÚáA/^ZôėķŊBÂ|ũ{„ūōĢKōōō›č¯Ņhž˙ņ§ą&÷čŲ§{pī1ã'}˙ãOzŊŪØĄ¸¸äõĨo‡äߪר “×ū˛ŽžžŪ¸5?ŋāĩח†äëßŖWHØS‹žŊxņ’qëĸgŸ÷öõ/**~}éÛ}Bt 6jė†M›PRRōβ æëß㉞ũž^üÜÅK—›=L���h>>>111<ˆ ‡Ã‰ŒŒėŪŊģŲzzģXņ[_YNÎÍ䔺Ŋz{uōŒ˜<éėšÔ­ąqožąÄØáėšÔ§žyÖŪŪî…įžĩŗĩMN9ûÔ3˙âršĻwxĐå+WŖgÎąļļš?wŽƒŊũ­Ûˇ×oÜtâ䊃ûvÛØX?tČ[īŧˇ#~âøq3cĻs8œã'N}ōéįųųīŊû6•–•MŒ˜ZW[7eĘ$W—ä””>ü$==ķ“> ĸ‚;w&GNSĒT3cĻûvî\x÷î†M›Ŗcf¯˙eMŸŪO‘P($ĸEĪ>Ú7äģ˙}Ĩ×ëŋúæī,û@ĀDO›ĘĖ?eęôĒĒǘŅžž]îÜ)ܰqsôŒYŋŦũąoHŸÖ˙��� •ĒĢĢ—` CMMMĢϓȭ…•m´6o%ĸ¨Č"7nĖ{˙ųhGÂÎ×^}Y(0žųvĩ^¯_ũŋ¯ƒzQô´Šī,ûāėšTã ÍvxĐÅK—ētöyķ%Ą}C˜g'§e|˜¸{ΜŲ3:d÷Ū}=ƒƒV~ą‚y3=ú?}RpįŽN§ãņx+ŋüúîŨĸ_Öü> ?=ĩpū§Ÿ‰Ûž`ū\ß.ŋXšĒ´ŦėÛ¯W9œ>jäđŅã&}ŧüŗÛū¸påáážäĩWîÔĒ•}B|ũíwLŌZųåׅwīnŨÜ#đ^:ž<i¨ą>úäĶ;°Ū ��ĐÁ.^ŧ¸sįNfҐÃáčtē}ûöQhh¨™3ęõz}ĢW5Íöø‘H4vėh"˛ÉƌY^^qđāaã^’SÎz¸ģ3)Š1=zjÃ2šîđPŗbf$&lgb–VĢUĢ՝;ûQ^ū_. øüü‚‚’ŌRcË[K_˙ßW_ōx<ƒÁ°gß>…ŗķ€ūũŒ[ß}û͍ëÖÚÛÛ †C‡~ĩˇŗ9b˜qkgŸ^=ƒ/\ŧX^^alœ0nŦņĩ\.īͧw~~AQQąÁ`Øģŋ_W_…ŗsqq ķ#ā zõėyųĘÕÚÚēĻ���XuéŌĨ„„Ŋ^Īܛ5}útfqßž}gΜ1oÎúęââúV¯î;p°ŧŧbōÄ r Ļej䔸ģļn‹?n ĢÕjˇ†Ŗ|ŧŊ¯›íđWâvmŨ—v=ŊēēÚØX_˙—Ī{éÅįß˙ĪĮC‡1|XhhHø€ūÎNN÷j(*ލ¨ėŪߟÃáû{¸ģ{¸ģ3[Ģkjēwh¸•ˆŧŊŧÎĨūžsķĻM0ĶâÕŠSÃNNŽDT\RÂårĘË+ĘË+úöø`aw ētîÜėņ���._žĖÄ,‡ÁܛŊuëÖÖ\Ų瑝âōy­MZĖŊđ}û†ÜĖÍeZœííėNūíÖíÛîîJĨŠˆDBQÃQ"Ņo›íđP+>˙īˇĢėđö›¯ģģš …ÂŒĖŦ7Ū|ģ‰!ķæĖöíŌå—õ÷<ŋs 4đƒeī¸ēē¨Ô*ē¯Õƒę”uD$•Hĩ‹Å""ĒĢSūŅ"7ė •H‰¨ĒĒJ&“Qˇn~˙~åĨįwrtlú`��€=ŲŲŲ:ŽÃáL™2%0đŪ [׎]§M›ļuëVŊ^_RRbÆ´üũ;9[ˇ*iŨ¸‘“rö=4âÄnÛūęË/2qD­Q7ÜÔpŊŦŲRĢÕk~^§pvŪ´ū&ÄQÃ+[Ĩ_Xhŋ°PF“r65a׎ņ;gÍ]p`_ĸƒŊ=UU=|™TFDuJeŖv&cYXH-Ę?÷aJ˛ąļļÉ˜–AÛ-���ÚͤI“ôzŊˇˇwŖ'”úųųEEEegg?یi…Dĕļ*i1÷ÂGGE†‡hØŽVĢ_[˛tÛö/žđœƒŊŊ@ ¸}ûOˇO]OO7žnļʋKÔju``wcĖ"ĸ䔺&–- ôĐ?L"–lÜŧ%íZZPP[›Ŧėl­V+¸#˙9'O íëÛĨŗ••UVv6ķ™Oã<™ŲŲDäíåelÉĘēŅŲĮĮø–šÎįāč`gkkcc}ŖĒĒĘŌŌŌØĄ´ŦĖÎÖÖIJ��€ ĖĸáC7ųûûˇæąĨԚįi1÷ ‚W_yiėčQ ĻLš8rİââ’ŖĮ’ø|~¯žÁšˇn5|vÔú ›Œ¯›íđ {{;"jøôŦkii;vŌׯŒÎ_¸:`Ўø š\ņ"1|hEEeÃ+ŋúzŲj4"=rxqqÉĄÃŋ6Üãŋ—ú……6LNÛļo7žÎÉšyņŌe/¯NL–;z´FŖųūĮ5ÆĨeecÆO~rŅâ&Ž���iæ_ĶÚwā`EEåԈ)Ŋ*3wöŦũm‰Ũ6bø°EO-LN9ûäĶ‹ŖĻFØX['Ÿ=ĢTnjwĐQŗ‹ÅC‡ :r4éÍw–…†„dfe­Û°iåįŸ>õĖŋŽMÚĩ{ĪđĄC¤RiÃ!ŨŦ­ŦŪxëŗŠŠūŨēq8tųōÕ¸ņŊŸčåß͏ˆ^xū_ŋMzëŨ÷ŌŽ_wuqI>{öČҤ)“'vđ'ĸ_xū×ŖI/ŋöúŧ9ŗŧŊŧōōķ×oØ$“IßjđØ0"Ōh´O.Z<tČ`Ŋ^ŋú‡Ÿˆč…įže6ũß ˙:z,éß}_TTÜ7¤ĪŨĸĸM›ˇVTTĖ›3Ûė_���üÍņ DD55Õ'NlŅČeī˙'ŋ `Å'98Ø?¸ÕÍÕu˙ƒŋŸŋ55"°{€ˇ÷Õk׎Mē|åj€ŋ˙ō˙ŗ-n‡P(Œ™MD:y6ŨáAũû÷ģ[x÷čҤÃŋŅét}đ^˙~aDtúLōogRĸ"#.,—Ë?nŦJĨ:~â䁃‡ÎœIQĒUķæĖz÷í7™áåcG*--;üëŅ#Į’4ZísĪ>ķīW^b ja!7vtiiŲŪũwīÍČČ ëûßĪ>õõíÂĖŋo˙ÁĖĖŦ_Öü›{{ÃĻÍcÉkS&ß;ĢRŠtÂøqJĨōø‰“;w_ē|%8¨ĮgŸ|Ō§w‹N;���<vīŪ-—Ë9oŋũŨšSđÃ?ttI°į_|eĪŪ}§ŽQ8;wt-���Đņ-ZäââŌΈ���€FÎ]¸$ĪĖFŌ���h{jĩ†/ĩöÛx����❠iĩ¯V~~#ãnŌ��€†´����؂¤���Ā$-����ļ i���°I ���€-HZ����lAŌ���` ’����[đm<����m/ŦO/…Â×´����؂¤���Ā$-����ļ i���°I ���€-HZ����lAŌ���` ’����[øƒžˆ CGW���đø0 ƒ×´����ØōĮˇņäÜžĶu����<~ūHZ^îŠŦ���āņƒÕC����ļ i���°I ���€-HZ����lAŌ���` ’����[´����؂¤���Ā~ee%ÕÔÔtt%����šššĘĘJžŠÕꎮ���āņaeeegg‡ÕC����ļ i���°I ���€-HZ����lAŌ���` ŋ5ƒUjMn~ĄZŖÕéômU���Ā߄PĀ—ˆEn GĄĀĖČd~ŌRŠ5éŲˇär™Ĩ܂Į}´¯å÷ čŌŅU���ĀߋF[_Z^y=+ׯŗ§yaËü„”›_(—Ë,¤’G=f���<”PĀW8Ú9ØYįŨ)2oķC’JĨ‘JÄf���x$ØŲX)Uf>ãŨü¤Ĩ7¸ŽŲÃ��� B_Ŗ­7o,ū����؂¤���Ā$-����ļ i���°I ���€-HZ����lAŌ���` ’����[Ú#i}ųõˇķž|&åljÃÆĘŠĘyO>“–žŅüC,Yöņū_“| ���…[QUSQUS][Įîn¸œØ¸íj†ÕŊüÃEŒā‡īÉ��ø[¨Ž­Ģ¨ĒáZ[ZX[ZČeRVwÔŖļŽn˙ūCŦîåŽoīžîŽ.]���ÉeRkK ~ûėL"‘L?.~gbxx˜­íƒ*ĢĒļÆnŋ––^[Wkkk3lČā‘Ç2›žųĩ cFįßš“úûyŊ^?p@˙1ŖGŽũeCff–H$ž2y|x˙~D¤ĶévíŲ›œ|Ž´ŦĖÖÖfԈaCjķŅëõûKŊxšŦŧŌÆÚrHxŋ𰐇Nõ渝Y7$ņđ~*•ę•koŊúB3ŋņŪō‘CŪ-ēpåšAo é5|pøæ¸„Ŧœ\‘P8nÔ°ĐŪ=‰¨ēĻ6~÷ūŒŦĩuJkˁũBefX˛ėã!áũFkûŖ���ķ´SŌŌëõÇ =vüäÖmņ‹Ÿ^ø`‡5?¯ģSx÷™§Z[Yfdfũŧ~ƒŊm¯žÁDÄįōö<<{VĖŧŲ3?ņËúMiéŗcĸ}ŧīØškũÆÍŊ‚ƒe2éÖ¸íII'įˊéėã}5-mĶ–X7(|@ÛHüî§’ĪM˜āå鑞™ˇk/ŸĮ yĸQˇMq ų…OĪ›)—Ë÷ž[TÂį7sĒy<î‘ã§ĸ#&Lœx*ųÜÖ‰Ų9Ķ&jŽÛžƒGbw$öđ“J$cãīĪ‹‰˛”ËŗoænŽÛikcÕ# [Û&���´‰öúėĄø|^ô´ŠÉ)g33ŗÜ=í՗ūΝkgg§áũŨŨŨ¯\ŊfÜęáîÖ3(Ãᄆô!ĸÎŪ^}|˜ˇļ°°PŠT9š4zôˆūũBœ‡Ô/,lĪžm{*•úÄo)Ãõy"ØÁŪv@XŸ'‚=ҍ[uuMZzÖ¨Ąƒü|}\ÎķbĸjMģ ÎÍEŅŊ[W‡Ķ;¸yyē{yēs8œŪÁÚúúĸâ"Šœ8öš§æuöîäč`Ö§—Ģ‹sZÆCÎ'���ü´Ķ5-FĪ ĀĀîļÄžûæ6‰EĸŨû¤]O¯ŽŽ1 ĩĩĩΎŽÆ­ÎÎÎĖ ‰DBD …âū[1Õ)•šˇōęëuŨũũCēuõ=~â¤JĨ‹ÅmU^ÁNįįÛŲØŌŧĶo)ŠjĩF$‹JJ ƒw'ãĄuíâ]XTŌėüNö÷†ˆEDääāp˙­˜ˆ”J5‰DÂCGgdåÔÔÖ †Ú:ĨŖŊ]Û���´ĩvMZD4=zęÛī~pâÔŠāĀ@cc}ŊnÅĘU>fÆ4…‰Įá}ų͡ĒRđ§:­Ä ĨJEDË?û‚CĻQo0QeeU&-•ZMDĢV¯åÜoaöRU]ã úãæŗÚ:%‰D"c‹LjŌø|^Ãˇ‚?ĩ :î›~ŅëõS'urtāršß˙ŧŅŦC��€öĐŪIËUĄ6dĐöø]Ũēv56ŪČš‘——˙Æŋ_íz˙rQUuĩƒŊŊéĶJ%"zzá7ˇ?}øÎÖÖĻ-ĒžG"ŅÜ‘.÷¯ą1lŦ-žđųD¤ŅūņH‹:Ĩ˛M ¸y+¯ đúxy2-55ĩvmzŒ���І:āņ“'NĐëuûüņÄ­ļžˆ,,î]øÉĘÎ.))5Áô9=Ü]|~Uuĩ‹BÁüXČ,ärš@ hÃĘ]Î|¯ēĻÖÉŅžų‘É$ŌFרėíˆ(÷v>ķVĨV_ĪĖn“ęëë‰H&•0osro—–WZpž��� ]u@ԒɤS&M<~ō”ąÅŨÍM :|´ĸĸâĘÕkë7míā§đneU•‰sJ$’A$ėJLN9[T\’–žąâŋ_ū¸æįļ­\,õíŊįā‘ß/^.-+ĪĖÎųúû_ÖoŲÁl=q:åŋ˙û‘ˆėílÜ\~MĘÉŊ}ˇ¨dŨæí–r ã$ÆnfpuqæķųĮNžŠŦĒNËȊMØíįëST\R]SÛúŖ��€6×Ū̇Œ!ƒÂKĘË/`ŪZZĘΛŸpúĖ™NžžO͟SV^ņíęŸ>ũlå‡īŋcâœ3ĸŖ¤Ril܎ŠĘ*+KĢžÁS#&ˇyåÆHÄâ„=+ĢĒ-å~G`6•UTääŪf^Ī™ļq[üĒīÖXYĘG t+OnŧÄÕ°[KYČdŗĻMŲĩīPJęw×ŲŅ•Uk7ÄŽZŊöÍWžkũŅ��@Ûâ,[ V¯^Ũĸ‘į¯fē:;°RTģË/,îЖßcŖŅjuõ:æŖ‘DôÕęĩRŠdáėém¸ ���h7į¯fļ4*,Z´ČÅÅĨcŽi=öž[ŗĄēēfzäDKšÅ•´ôŒėœgæĪęčĸ��� Ŋ!iąb~LÔöÄ}?ŽÛŦÖhėlgM›Đ͡Ŗ‹��€ö†¤Å šÜb^LTGW���Ŧ>{���đ¤���Ā$-����ļ i���°I ���€-æ'-‡ŖĮWî��ĀãNŖ­ Ė|\ƒųIK"ÖÔԙ=���ā‘PZ^)‹Ėk~ŌōtuŽŠSV×Öéôzŗ'���øÛŌhë ‹ËŠJ+ÜŽæÍ`ū“KÅ"ĄŸGn~aqišN÷ȇ­ķW3;ē���ø{ øą¨[gOŗW[õŒxąHØÕÛŖ53����<ÆđŲC����ļ i���°I ���€-HZ����lAŌ���` ’����[´����؂¤���Ā$-����ļ i���°I ���€-HZ����lAŌ���` ’����[´����ØÂoÍ`•Z“›_¨Öhu:}[���đ7!đ%b‘›ÂQ(032™Ÿ´TjMzö-š\f)ˇāqqmŒuų…Å=ētt���˙ m}iyåõŦ\ŋΞæ…-ķRn~Ą\.ŗJŗ���āą$đŽvvÖywŠĖ›Áü¤Ri¤ąŲÃ��� v6VJ•Úŧąæ'-ŊÁĀåpĖ���đH ømŊycąđ���Ā$-����ļ i���°I ���€-HZ����lAŌ���` ’����[´����ØŌĒo˜6Ņ—_{ūÂEæĩP(°ˇŗīŪŨԈavļļÆ>ĪŊøĘČáÃ&ŽkŪ.Z9ū†–,ûxHxŋŅÃ5zŨæbvgfß|ķ•įZ?U›×Ų†ĩ�@‡h¤EDŽŽķįÎ&"ĩJuûv~Ōɓ'Nžzņųįēúvf:LŸ6ÕÍÅĩ}ЁGBÄøŅ. §ŽŽ�� UÚ)i‰Dĸn]}™×ÁA=F ōų—_}ķíwË?ū@"–Ņ€~aíS <*úöîŲŅ%���´V;%­FÄbņŧ9ŗŪ|ûŊS§“‡L^ūKĪČÚŸp;/O¯7x¸ģEN™ė×ĩ -~îĨņãFŨ)ŧ{éŌe•Zāī?îlKšEŖÉ+ĢĒļÆnŋ––^[Wkkk3lČā‘Ç҇ËWÂ×^ū?cĪUß|[YYõöŌ% ‡?˙ōkƎ)++KN9§RŠģúvž?g–•ĩU3ß5ftū;ŠŋŸ×ëõô3zäÚ_6dff‰Dâ)“Į‡÷īGD:nמŊÉÉįJËĘlmmF6tpÛ¯ˆéõúũ‡Ĩ^ŧ\V^icm9$ŧ_xXȃŨ*ĢĒ7ĮíĖČē!‘ˆ‡„÷SŠTŽ\{ëÕZ?3ŊņŪō‘CŪ-ēpåšAo é5|pøæ¸„Ŧœ\‘P8nÔ°ĐŪ=‰¨ēĻ6~÷ūŒŦĩuJkˁũBef0e%މáoŧˇ|Ô°AåŠ.ĢÕoĪ˜Š“™ŋ–ĘĒęMÛ2˛s$bŅ€Đ>5yŊNˇ÷ā‘”Ô JĨĘÍU1iėHīNMīÔÄse^mMė÷õeŸŒ6čzFVF֍Ū]"ãĢß�ū.:&i‘ĢBáė䔞žÁ$-#•JŊrÕ7}CzĪ3“ †_ûī—_}ąâ™LĘãs÷î?8#:jÁÜ9w‹îŽø|Õæ-ą‹žZĐhæ5?¯ģSx÷™§Z[Yfdfũŧ~ƒŊm¯žÁƒÂü´v]yE…ĩ5ŗŖ+W¯ÍˆŽj4œĪåíÛpĘ䉟-˙°˛ĒōŊ˙,ßš{ΜY1MĖˌÚđđėY1ķfĪ<vüÄ/ë7ĨĨgĖŽ‰öņ^ŧcįŽõ7÷ –ɤ[ãļ'%œ3+Ļŗ÷Õ´´M[by<Ū đm{nãw8•|nzÄ/OôĖė¸]{ų<^XȍēmŠKČ/(|zŪLš\–¸˙đŨĸ>ŋ™ŋg&"{äøŠčˆ Ķ#'žJ>ˇuGbFvδÉ㟚ëļį⑨‰=ü¤ÉÆØøģÅÅķbĸ,åōė›š›ãvÚÚXõčfâ‘61œĮã>vbü¨aī/}ĨĒēfÅĒīö>=e­Û˛Ŋ¨¸dņ‚Y––ō㧓/\ž&“Jr°‰ûŋx9jōx{;Û¤SgžųqŨŌ—˙egkcbÍMœ+ķjkbŋ|>īTōŲ@ŋŅË„BĪ��´ƒKZDdkkSQUŲ¨ą´ŧLŠRö qU(ˆhæŒč>Ŋų|ŗÕÃÃYgT8;žk÷Ūš*ĩX,j8CLô4—ëč`ODÎÎNŋKērõZ¯žÁ}z?ąqķÖ3É)cF$ĸ‹—. Ô7¤÷ƒ…)Îô#"[Û Ā€œ›šMĪ|¯6wˇžADŌį—õ›:{{uöņaŪ&îŪWXXčâĸ8r4iÜØŅũû…‘““ãÍÜÛ{öhÛ¤ĨRŠOü–2rHxČÁDä`o{+ŋāāŅōPuuMZzVÔ¤q~ž>D4/&ę?ˇ˛˛lũĖFn.ŠîŨēQīā[w$zyē{yēQīāĀŋ&—tōpœ8–ËåØŲÚ‘ŖƒŨņĶÉiYĻ'­Ļ‡;;:„öéEDÖV–ū~žˇnQEeUF֍i“Įûvö&ĸ¨IãŽgd?ä`ÕęĶ)Š“ĮęԝˆfL¤ÖhŠKĘėlmLŠšŲseFmMīW(N;ŌÄķ��íĻ#“–^¯įqyNNÎNNĢX3dđ îūūžžî~÷ođ"ĸNÆ×ŽŽ.Z­ļĸĸÂŲųO÷M‹EĸŨû¤]O¯ŽŽ1 ĩĩĩΎŽD$ CCBNũ–Ė$­sŠį{õ –Jr1ÃŨŨÍøZ*•ÖÖÕ5=3ÃŲŲ™y!‘HˆHĄPÜ+&ĸ:Ĩ2÷V^}ŊŽģŋŋqHˇŽžĮOœTŠTâļ[îÉ+¸ŖĶéüîԀˆēøtú-%U­ÖˆD\í(*)5 Ėrsh]ģx•˜7ŗ@ĀWĢ5L#Ī Dää`orąˆˆœîŋ‘RŠ&"‘HxččņŒŦœšÚZƒÁP[§t´ˇ3ũ`›îĸp6ž–JÄuJ%Ũ-*&"O{ŋāp8ÜŨnÜi4ķÂĸúúzO÷{Ũø<Ū“ŗ§›˛ĶfĪķ[0ŖļĻ÷ˤX��øģéȤUXXāīר‘Ëå.]ōęŪũ“NœŒÛ‘`gk1eR˙°{÷ŖˆE\žbVIj•u ‡××ëVŦ\eĐécfLS(œxŪ—ß|kÜ:0ŧ˙Ҥãˇnį999^ÍBĄk�� �IDATŧrõ…g=´0@đ`cĶ3_đ§“Ųh%Î`0(U*"ZūŲâ0zƒˆ*+ĢÚ0iŠÔj"Zĩz-į~ ŗ—ĒęŅÕ¨­S‘¨Áų|č š‰3—–~ûĶzĻ1ä‰āŲŅDdŧÉüųüČ ĶéžųáŊ^?uŌX'G.—ûũĪM?Ōf‡ ūŧ D¤RkčĪŋâ†Ԉ‰>BaãŋknöˇĐŌښŨ¯äĪWv�āoĸÒVFffEeE÷�˙7YZʧO‹œ>-2ŋ āĀÁÃ?üôŗ‹BáÕɓˆ˜°ÂP*UD$ûķEŠ97ōōōßø÷ĢÆįGTUW;Øßģ¸âÕÉĶĶÃ=åė9OO ŠĖŋ›ŠĢTÍÎl ŠDBDO/\āææŌ°ŨÖÖÆôIšÅÜ =wF¤‹ŗsÃvë?­ ø|"Ōh5Æ&[˜7ŗ…Lúââ…ĖÛ?ŖđWnŪĘ+(ŧûââ…>^žLKMM­ÉgÃŧáLxbūxu ^YČdD¤RŠÍÛŠ‰ŋĶkkåš�€ŽŌ1Έ¯­­ûeÃ{{ģŪŊ{5ÚTT\ōûũĮœēē¸Ė™5“Ëåä0-éƞ7so E"ۏ?%"­ļžˆ,,îůŦėė’’RŒÂôO9—šr6ĩ_ŋž\.‡LÖėĖÍōpwđųUÕÕ. ķc!ŗËåŊ„f6W…3ŸĮĢŽŠur´g~d2‰……´Ņ56{;"ĘŊĪŧUŠÕ×3rģ’‰3K$b/OæĮÁäåŋúúz"’I%ĖۜÜÛĨå“΍yÙ5Íü‚Bæ­N§Ëŧ‘ķĐn ëÆMæ­Á`XųíO)ŠLÜŠ‰ŋĶkkåš�€ŽÂ¯¨Ē!ĸęÚēfģļ†Z­NKĪ "]}ũíŧŧC‡ŠÕĒW^zAđĀ?<eee_˙īģi‘AAâü–œÂáp;{{3[+**ãw%ö -¸sį×ŖIĄ!Ŋ­ī¸ģš ‚C‡Nž8./ŋ`ێ„îūw īVVUYYZQXhHlÜöŌŌ˛ŪˇE‡ĐėĖ͒H$ƒHؕ(ˇyyy•–•mÚkkcũŌ mųpąXÔ?´÷žƒG,dROwˇ˛ōŠíģöY[Y>ŗ`8rîÂĨ—ž}ŌŪÎÆÍUqā×$gGŠD˛sīÁ†×ĸŒŨLŸŲ Ž.Î|>˙ØÉ3cF )(ŧģkß!?_Ÿĸâ’ęšZš…ŒĨáļ6֝<Ü=noo+ˇ;y†Īk|ŗ s°a}z8rÜÚĘŌŲÉņԙŗˇō fM›"‹LŲŠyįljÚZyŽ�� ũU×ÖUTÕ´ĶęaQQņō_—Ëąļ˛ ˜0n´ŊŨC.~øuõ]8îūƒ‡ãw&rš<WÅķĪ.2Ūķ>p`˙ēÚē÷?úDĢŅõ˜9#ēŅpKKųÂysââNŸ9ĶÉĶķŠųsĘĘ+ž]ũͧŸ­üđũwˆH&•úuíĒRМœīģIÍÎlŠŅQRŠ46nGEe••ĨUĪāĀŠ“[T†)"&Œ‘ˆÅ {VVU[Ę-ü&ŽÁl*̍ČÉŊÍŧž3mãļøUß­ą˛”6čVžÜx‰Ģa7g6ƒ…L6kڔ]ûĨ¤^đpwQQYĩvCėĒÕkMųūŗ‡Ī›ĩi[Â÷k7ŠÅâa}Bz]¸ríÁn“Įâp8 {¨ÔW…Ķŗ gÛÛŲ‘‰;5ī\ũUm­<W��ĐQ8˖-#ĸ‚‚‚ÕĢWˇhäųĢ™ŽÎŦõÚäË ĢĒĒ_{ã­…ķæ„ôyøŗ ūļō ‹{tià 5Z­Ž^Į|4’ˆžZŊV*•,ŧ˙ ;���0:5ŗĨ˙ /Z´ČÅÅĨ#?{ØÎjjj ‹Š6oŨæęĸčũDãûÃūž[ŗĄēēfzäDKšÅ•´ôŒėœg曚���õJZ'OŽ‹OđíŌeáü9-ēūq5?&j{âž×mVk´vļŗĻM čæÛü0���0ŲŖ”´ž^ųyk†5bô(ķo*züČåķb���´ĄŽyĘ���Ā?’����[´����؂¤���Ā$-����´8Žßģ���;ļ^(0ķq æ'-‰XXSÃîˇ%���t¸ŌōJ‰XdŪXķ“–§ĢsM˛ēļN§×›= ���ĀߖF[_X\VTZáĻhŲ×%™˙äRąHčį㑛_X\ZŽĶ!lĩ‡ķW3;ē��€Ą€/‹ēuö4{õ°UΈ‹„]Ŋ=Z3���Āc Ÿ=���` ’����[´����؂¤���Ā$-����ļ i���°I ���€-HZ����lAŌ���` ’����[´����؂¤���Ā$-����ļ i���°I ���€-üÖ VŠ5šų…jV§ĶˇUA����B_"š)…3#“ųIKĨÖ¤gߒËe–r ׯZ&ŋ°¸g@—ŽŽ���šĸŅ֗–W^ĪĘõëėi^Ø2?!åæĘå2 Š1 ���KB_áhį`gw§ČŧĖI*•F*›=���ā‘`gcĨTŠÍk~ŌŌ \Įėá����Ą€¯Ņ֛7 ����lAŌ���` ’����[´����؂¤���Ā$-����ļ i���°I ���€-­ú†i}ųõˇį/\d^ …{;ûîŨũGfgkÛ{îÅWF6qüXV‡<ĸ˛nÜ<|ėä­ü‚ÚÚ:ąHäãå9rčĀNnĖÖˇūŗ"´wĪņŖ‡ˇO1ą ģ3ŗožųĘsíŗģ‡Z˛ėã!áũFÔču›kÃmķ:˙ŋ�€ĮF{$-"rtt˜?w6ŠUĒÛˇķ“Nž<qōԋĪ?×Õˇ3Ûģž>mĒ›‹kŗŨ9–sķæS æ™>äQ—™ķõŋ<8;:ÂB&-¯¨<tôÄĒÕk_{a‘ÂÉąŖĢëãGģ(œ:ē ��x|´SŌ‰DŨēú2¯ƒƒzŒ>äķ/ŋúæÛī–üD,au×ú…™Ō-7÷VK‡<ꎟNV89ΙÉŧuwuņíėũų×?dßČũĮ&­žŊ{vt ��đXi§¤ÕˆX,ž7g֛oŋwętō𡃉H§ĶíÚŗ79ų\iY™­­Í¨Æžˇ’ž‘ĩ=>áv^ž^ođpw‹œ2Ų¯k"ŌÖ×īܕxętr­Rééî6mjD—Î>Dôü‹¯N7æĘĩ´´´ë_ū÷Ķŋņļq)pņs/7ęNáŨK—.ĢÔę�˙ųsg[Ę->YņųõôL":uúĖ{īŧšâ‹•Æ!Z­v{üŽ”ŗį*ĢĒŦ­,ÃBûN™4ĮãŅķ/ŋ6a똞˛˛ä”s*•ēĢoįųsfYY[ĩíšŌëõûKŊxšŦŧŌÆÚrHxŋđ°ģUVUoŽÛ™‘uC" ī§RŠ.\šöÖĢ/41ŗN§Ģ×ũé[œÄ"QŖ5#—ģīđą§“•J•ogīYŅr U×ÔÆī۟‘uŖļNicm9°_čāĄĖ×—}2jØ ëYY7>zw‰H(üĢú+ĢĒ7mKČČΑˆEBû´ō Ņī-9t`áŨĸ WŽô†°^ÇoŽKČĘÉ …ãF íŨŗéâMY‰kbøī-5lPyEEę…ËjĩÆĮÛ3fędKš…é[¯Ķí=x$%õ‚RŠrsUL;Ōģ“GĶ;5ņ\™W›éŋh‰ß7�đ“´ˆČUĄpvrJOĪ`’ÖÖ¸íII'įˊéėã}5-mĶ–X7(|€JĨ^šę›ž!ŊįΙIïGŽũ÷˯žXņ‰L&Ũ—röܢ鎇ũėŋĢ>Xö廙=Į?vüdpPāÄņcDBQÝōøÜŊûΈŽZ0wÎŨĸģ+>_ĩyKėĸ§ŧđÜâO?[éää4sF´…LÚpČú›SĪ_˜3s†W§NŲ7r~YŋIŖÕÆDGŸËÛˇ˙ā”É?[ūaeUå{˙Yžs÷ž9ŗbÚöDÅī>p*ųÜôˆ ^žé™Ųqģöōyŧ°'uÛ—_Pøôŧ™rš,q˙áģE%|~3ŋÜîŨēnŪžëĮõ[F āáæĘyØ÷…˙~ņ˛_—ÎĪ,˜]^^ąa[üۃGĸ#&ŅÆØøģÅÅķbĸ,åōė›š›ãvÚÚXõčFD|>īTōŲ@ŋŅË„Â&ę_ˇe{QqÉâŗ,-åĮO'_¸|M&•>Xƒ‰g€ˆx<î‘ã§ĸ#&Lœx*ųÜÖ‰Ų9Ķ&jŽÛžƒGbw$öđ“J$MoŠ&†ķxÜÃĮNŒ5ėũĨ¯TU×ŦXõŨžÃGŖ§LhÁÁ&î˙ũâå¨Éãííl“NųæĮuK_ū—­‰57qŽĖĢÍô_´‰g�āŸĻÒŲÚÚTTU‘RŠ<r4iÜØŅũû…‘““ãÍÜÛ{ö> ´ŧLŠRö qU(ˆhæŒč>Ŋų|žRĨL:q*:*˛oŸŪD4oÎ,ĩZ]TTäč`ĪáP(˜65âĄ;õđđ`ÎÎC‡īÚŊwŽJ-•Hš\ŸĪcūߨēĻæäé3ĶŖ"û†ô!"GG‡ü;w>9EĀį‘Bá<p@?"˛ĩą Čš™Ûļ§HĨRŸø-eäđ'‚‰ČÁŪöV~ÁÁŖ'åŒę暴ôŦ¨Iãü|}ˆh^LÔ;~neeŲôäũúöŽ­S8’tņō5ąHäíåŅ# [Ÿ^ABĀØG"GMGDn.¯¤Ũŧ•Į´GNËårėlmˆČŅÁîøé䴌,ã?üBpŌØ‘M×_QY•‘ucÚäņžŊ‰(jŌ¸ëŲfŸ#7E÷n]‰¨wp­;Ŋ<ŨŊ<Ũ‰¨wpā_“ŠŠK:y¸7]|ŗšîėčÚ§Y[YúûųŪē]@DĻŦZ}:%uō¸QŊ‚ēŅŒŠ“ÔMqI™­)57{ŽĖ¨ÍÄ_4��ü~EU U×Öĩ˙žõz=Ë#ĸÜ[yõõēîūūÆMŨēú?qRĨR)œœœœV˙°fČāAŨũũ==ŨũēúQVvļVĢíÔɓé/āķŸ[ŧČ8ŧŗ÷_í´“‡‡ņĩĢĢ‹VĢ­¨¨pv~øMСoįéõzo/c‹w'OZ}÷î]7WW"rww3n’JĨĩum|ķ îčt:ŋŸčâĶ鷔TĩZ#ũqĄ¨¤Ô`00ËLD$‰ēvņ.,*ivūC MĪĖNĪĖžž‘ŊeûŽũ‡ũëÉ9Î÷īĶōōüãtYXČTˇÔĖk‘HxččņŒŦœšÚZƒÁP[§t´ˇ3ödÂMĶõß-*&"O{Ÿ<āp8ÜŨnÜ1ũ |ĩZÃ4ōø<& :9Øß; b998Ü+&"ĨRŨlņÍjz¸‹ÂŲøZ*×)•DdâÁŪ),ǝ¯÷tŋ׍Īã=9{ē);mö\1-fÔfâ/��T][WQU͑״ ‹üũˆHŠRŅōĪžāĐŊ,ŊÁ@D••UNNŽK—ŧēw˙Á¤'ãv$ØŲÚDL™Ô?,´ļļŽˆĻ†Ä’ŋŧË^,úc=‘Yō¨Uūe<RŠTD$–üq ķļZu/p\ūaƒJ­&ĸUĢ×ö˜3SU]ã úãĩuJ"58´‡ŽL=”P ô÷ ô÷#ĸŒėœ×mŽß}`ņÂŲĖV‘đäpˆČ@D:î›~ŅëõS'urtāršß˙ŧąáœą¨ŲúUj ũų>ôˇŲÄ ÅĨĨßū´ži y"xvtņųŧ†Ã‚?ũ…ČĐlņMkv¸PĐčS"2ņ`™č#6ūŖ2ąæf˙ZZZ›éŋh��ø+|ŠXHDâūĪm™™•Ũü‰H*‘ŅĶ ¸šš4ėckkCD––ōéĶ"§O‹Ė/(8pđđ?ũėĸPČår"R)U-Ũ/“ęîŊVLjH&ųËPÂ$ļ†{Q*•D$–˛ûyI#æ.ãš3"]œļÛX˙ieYĘÔh5ÆæßėĻUU׈„†˙äûúxw÷ŋz=Ŗé7oåŪ}qņB¯{×kjj™&Ķë/)+Ŗûŋ‚û5?äˇŲÄ 2鋋2o-ûļIņm8œ OÍŦ…LFDĒû9žĨ;5ņ¯ÅôÚZyŽ��ūáÄBT,ė˜gÄ×ÖÖũ˛a‹ŊŊ]īŪŊˆČÃŨUĀįWUWģ(Ė…ĖB.— ‚ĸâ’ßī?õÔÕÅeÎŦ™\.'ŋ @áė$ ¯gd2›ôzÃĮŸ~vęô™fwžņGŒ¸™{S(Ų jhÜŲÃ͍ËåffũqÛJVvŽD"vvl§‡ ¸*œų<^uM­“Ŗ=ķ#“I,,¤îvw°ˇ#ĸÜÛųĖ[•Z}=ķ!ˇ5T]]ķö‡Ÿ>vĸaŖÁ`¸[\b)—7=ļžžžˆd÷ãfNîíŌō Ãg¯éú™ežü‚BϧN§Ëŧ‘Ķĸ$ą—'ķã`ōōŸéŎápÖÉÁ^ dŨ¸Éŧ5 +ŋũ)%õ‚‰;5ņ¯ÅôÚZyŽ��€ÚíŽxĩZ–žADēúúÛyy‡SĢU¯ŧôs1F"‘ 8 aWĸÜBæååUZVļiKŦ­õK/<WVVöõ˙ž›Č!ÎoÉ)ˇŗˇˇD" Đo÷ž}ļ6Ö. Åąã'nŪŧĩpžOŗ•TTTÆīJėZpįίG“BCz3˙M/“IsoŨĘŊuÛļÁ˛[XČÂôßŊoŋŖ“Ŗ‡ģ[zzæ‘ŖĮFÁ<åĄˆÅĸūĄŊ÷<b!“zēģ••WlßĩĪÚĘō™ŗˆčÄé”s.Ŋôė“öv6nފŋ&9;:H%’{6ŧÆcėÖpfšÜbčĀ~ûMĒĒŽ đ“J$UÕ5gÎũ~ãæ­ų3§5]•Ģ‹3ŸĪ?vō˘C īîÚwČĪ×§¨¸¤ēĻ–y„)õÛÚXwōp?xô¸ŊŊ­ÜBvėäūÃÎjĶgĀ Ļ߆ÃM?ذ>Ŋ9nmeéėäxęĖŲ[yŗĻM‹EĻėÔŧsÕDm­<W��@햴ŠŠŠ—¯ø‚ˆ¸\Žĩ•M``Ā„qŖííū¸1#:J*•ÆÆí¨¨Ŧ˛˛´ę85b2ųuõ]8îūƒ‡ãw&rš<WÅķĪ.bn`Ÿ65’ÃálŨļC­RšššžôĪ9::4[ɁũëjëŪ˙č­FÔcæŒhĻ}İ!ß˙´öÃåŸ=ŋøé†ũgÅDKÄĸuë7WUWŲÚڌ?vü˜Qmxfš1aŒD,NØs°˛ĒÚRnā7qôfSYEENîmæõü˜iˇÅ¯ún•Ĩ|Ô°AˇōäÆK\ ģ54iėH…“ãé”ÔKą uJĨX,ōts}öÉ9Ũš{pŋ…L6kڔ]ûĨ¤^đpwQQYĩvCėĒÕkü —&ęŸ73jĶļ„ī×n‹ÅÂú„ô ēpåZ‹Î€ZT|7ņ`'ÅápöPŠ5Ž §gÎ%"wjŪšúĢÚZyŽ��€ˆ8K—.%ĸ;wîŦYŗĻE#Ī_Ítun>Ųü­ü}žĐ0ŋ°¸g@—6œPŖÕęęu’û7īĩz­T*Yx˙“k���Đį¯fļôî (ŠŽüė!´ĄīÖl¨ŽŽ™9ŅRnq%-=#;į™ųfޝ��@[AŌzLĖ‰Úž¸īĮu›Õ­ƒíŦiSēųvtQ���˙t˙Ŧ¤õõĘĪ;ēļČåķbĸ:ē ���ø“ŽyĘ���Ā?’����[´����؂¤���Ā–Öņ����íãüåĢ×2o˜M‹Ãáčņh���đ¸Ķh녂_œŌjë †VŦJÄšš:ŗ‡���<JË+%b‘ycÍOZžŽÎ5uĘęÚ:^oö$����[m}aqYQi…›ÂŅŧĖŋOK,úųxäæ—–ët[-vūjfG—����M øą¨[gO3V­ē#^,võöhÍ ����1<å���€-HZ����lAŌ���` ’����[´����؂¤���Ā$-����ļ i���°I ���€-HZ����lAŌ���` ’����[´����؂¤���Ā$-����ļđÍYPPІu����üš¸¸˜1Ęü¤eŪū����ū9°z���Ā$-����ļ i���°I ���€-HZ����lAŌ���` ’����[´����؂¤���Ā$-����ļ i���°I ���€-HZ����lAŌ���` ’���@Û"~G—���đęč¯P(pM ���€-HZ����lAŌ���` ’����[Ú2i­Û°ÉÛ×ßÛ×˙ŗ/VļᴌŊûxûúO‰šŪæ3ˇÔÖØ8o_˙š žz,w���m¨-“Ö–ØmĖ‹¸ņ:Ž5S-{˙?AŊBÚĸ¨ÖjįJū>���­×fOy¸xņŌõëé^<e2Ų•Ģ׎%6tˆųŗ]žŌV…ĩԃ•Lœ2yŌ.—•…×vŪ���°ĒÍūũf.h3zė˜ŅD´%6ÎėŠ´ZmÚĩ´ļ*Ŧ5Z Į‰DāQß���°­m’VMmmâž}D4iâøIĮs8œcIĮ‹ŠŠu[ķķ:o_˙§?ר}ŨúÆö—^ųw׀ V[]SÃÜõ•tü„ą'ËÕëõ?ü¸fä˜ ~Ũƒ{ôė3kî‚ÔßĪ7š0-íú+¯ŊŪāĐŽA=û„MŸ9gûŽƒÁ`ė°mûo_˙%oŧĨĶéžûūĮQc'v ėŲŖgŸŲķgûĢJzãԅ‹Ÿņ•Đƒ|ũ{„ôÆ[īÜŊ[Ô°ƒ^¯ßēmûô™szö ëŌ-°GĪ>Q36mŲĒ×ëÍØŨīį/<÷ÂKĖî‚z…L‰šūÃOkU*U‹���ØÖ6̇‰‰{ęęꂂztöņ!ĸđũŸ8ˇ#ūŲgžnéTƉDąqۅÁüysˆČÃŨŨ¸U"‘ŧļdéC‡ÃBûúúvI9{îôogÎĨūž{įvf×D´gīž—_{]ĢÕõŪŋ¨¸øˇ3))gĪ%8ąōķĖ2œH$"ĸÚēē—_[räčą°ĐPOO /:ũÛŲsŠ{wÅ{{{5]ICÛw$,Yú–Á`ęŅ#0033kklܞ=ûâb7ûvéĖôY˛ô­í;Aߐ>öö%ĨĨŋIžpņâĨKW>ųčƒfŧĄ›ˇŧũîûDÔ38hđ••gĪûxųŠ={÷m\÷ŗL&5ņ�[úĢ��€–j›¤Å,FOdŪN‹Š<~âdėļí‹=ÅápZ4Õøqcētéˇ]$-yí•F[/]žâĸp>°g—ĢĢ UUUEMŸ•™•ĩeëžNDų¯.YĒÕj?˙ô“)“'2ŖroŨš;˙ŠŨ{öõ ED|ˆNž:íâĸ8°7ŅEĄ ĸ暚)SŖoÜČŲģméë˙nēŖÜ[ˇŪz÷=÷ķO߇…ö%"Ŋ^˙ņō?­ũå…_ŪŋgeegoߑĀápļmŲØ#°;3đęĩ´)SŖcãļΟ7§Ģow—}ãŊ>"ĸīžY5rÄpĻąĒĒ*:föĨËWžüęëĨ¯˙ÛÄlŅī���ĖĀ­SięT•Fkö×ŌŌ._š*‹Įô 6ÔÆÆúÖíÛg’SÚ¨Î{ĒĢĢ?üā=&f‘ĨĨ%§nææ2-ë6lRĢÕŖG0Æ,"ōôđXōīWˆhŨÆM÷š8"ĒĒĒú`Ų;L !"š…ETdĨ]O7Ŋ¤mq;ÔjõÄ ã˜˜ED\.÷å_pssåršųųD$ˇĩōķĪW|bŒYDāß­gpĨūūģéģÛ¸yk}}ũČÍ1‹9¯ŧôDˇŖžžžm���Ė ŌhëTžĩĨÕÕH͞hķæ^øQr ĻE(L™4qÍĪëﯯķG›°ļļęÕ3¸a‹““#•––1oûí  ŌøcƒÂp8œë×Ķ+++­ŦŦ˜FKKË'zõlØíŪĩŸęjĶK:r,‰ˆú……6l”H$ĮjX丹÷bhmm]YY™N¯#"™LFDUU-Ø]ĘŲsD4dĐĀFíaĄĄDTUU•}#§ĢoĻąM���Ė —I­--Zģz¨T*w%î&ĸi÷—ŅĶĻŽųyŨūƒ‡&›ÖswskÔÂܐd||W^~>%ėJü-9šQOŸ¯ŅjoŪĖ ęÁ´¸šē6ęÃãķˆČx—ē)ōķō‰HáėÜtˇŦėė/ŋúßņ'L9 oÕos€nn+—ɤ66ÖååwŒIĢM���ĖÖÚ¤ĩ{īžęš"úbåĒF›x<žFŖIؙ8wÎŦVîňËã5ŨĄŽļŽˆNūí¯:T×Ô_ „mđčĩZMD|~Sg2-ízԌYuuu~]}ĮŽ™īĸPˆ%b"Zûķē–~PĨT‘X,~p“H(""•úO ļÉ��€ŲZ›´ļlŊ÷\xfUëA[ˇÅ5›´4ZķīkD*“j**×üđŨāÖ×X"‘J5•••••MôYņÅĘēēēQ#‡ŗjeÃg&ėL4owJĨęÁMJ•ŠˆdRķ‚�� mĩ*iĨgdžŋp‘Įãũvâ¨ŊŊ}Ŗ­Õ55!aá×Ķ3.^ēÔ#ˆ¸\éõŋ¨'//¯5e4äŦMũ�� �IDATééYQq)ŋ  ­&l–‡ģÛåĘĘŧŧüFí*•J§Ķ‹DB>ŸūÂE"š9cFŖGŊ§gd˜ˇģÛˇo…5l¯ŽŽfŌۃ Ŧ���ĐQZõäRæáƒ†?ŗˆHna1jÄp"ÚēíŪķâ™Ā ˙ôHO­V›tüäƒÃ[t÷’QŋĐžD”¸{oŖvFŋsWqq‰s6]Ihh_"Úwā`ÃFN×ĐĐĀžŊ¯Ĩ]76ŠD†}ö8tûvۃķ7ŗģž!DtäčąFíĖ9tttčÔÉŗ‰á���ОĖOZjĩ:>aMŒøĢ>S#§Qâî=uuuDÔÕחˆŽĨĨ=—ĘtĐjĩ|øIş—Ū,d2"ĒŠ­-/¯hiU1ĶŖÅbqĘŲs?ü¸ÆØ¨ÕjßYöÁ+¯Ŋūæ;ËZ4›)•ÄLŸ&RΞ‹Ûδčõú˙~ųUyy…§‡G`÷�"ęŌŲ‡ˆ9juųĘÕ÷ūķaHŸŪDTXxˇģ›-~=zėĐá_ÅÅ%Ÿ}ą’ˆæÍ™ŨŌ˜���{Ė_=Üģ˙@UU•õĐ!ƒūĒOŋ°P…ŗķÂÂ=ûöGEFôėtáâřsæ÷éũ„ĩõĨ+W´ZíË/žđî{¯å8;;ŲÛۗ””LŒ˜ęãí=rݘéŅ&Våęę˛â“_zuÉĮŸ~–¸Ûŋ›_]mŨŲsŠ%ĨĨžī/{ģEĮhJ%žī/{į7ß~}éÛ?˙˛^ĄpÎĖĘÎË˗H$+–ÄäžEO-<—úû?Žaž„ûFÎÍĶŋYōęËŽLDĶéuŅQSüģ™´ģ÷ŪyãÍw=û|HŸŪ^^JŠKRÎžĢŽŠ6dđ“ æĩč���€Uæ_ĶÚGD“&Lhâˏš\nĔIÆÎDôĶ÷ßFEFX[[Ÿ=—šzū|Xßž;ļmņęäI÷?ÄGD<ī‹ËŊŧ:_ģ–ÖôĮú4nė˜Ä„íS&OŦŦ¨Ü•¸įhŌq;;ÛgŸy:>nĢŗ“S‹Ļ2ą’č¨Č¸­›F^RRzâäi•J=yâ„Äø¸ŪOôb: :dÅ'ųuõ=“œ˛k÷­Vģú_=õ䂱cF;†Įãí?p°ļļÖÄŨM›š=v͘Q#snŪÜž#!å\jˇn~Ë?ūĪw˙ûĒĨį ���XÅYļlŦ^ŊēŖ‹���xL,Z´ČÅÅĨUwÄ���@´����؂Ûz����Ú^ĘīE×2pM ��� íiĩZƒÁ€¤���Ā$-����ļ i���°I ���€-HZ����lAŌ���` ’����[´����؂¤���Ā$-����ļ i���°I ���€-HZ����lAŌ���` ’����[´����؂¤���Ā$-����ļ i���°I ���€-HZ����lAŌ���` ’����[ø­Ÿĸ´´4??_­V †F›ŽeŸ?›v"jÜÜÁ}G4Úôôâį˙zÄøÖÆÆÚ¯k×˙{ū_!}zˇž¤öôî{œI>{`īŽŽ.���ūvÚ iåææŠD"KKËFíwKķŽ_ÚUZU¸ûč–“y¸ģōŅĖëĸââM›ˇÎ˜5wĮļ-A=ūŸŊûŽkęjã�ū$öŲ2eŖ€YîŊp×Yëh­Zmkk[ëÛÚŠmmmmĩÖē÷ÆŊ÷�Üʖ![VØ!ķũãÚ™rņ÷ũøGîÉšį<įÜhī=šˇSĶŖ���hqČ´JKKuuu%‰jaaIŪŅĢÛK*ķmm:tõ­qG=}ŊĀĘÍÁô0xÃÆÍŋüôcĶŖ���hqX§%‹åršL&ËĘK+)Ęd˛üĸœS7wf%ØwpĶwσĨģ:íčččx¸ģ§<~Ėlæåį/üč“ĀĐŪ>~ũŨ°iŗ˛fDdÔÄ)¯úv đņë6aŌԈȨ:Ę{„ôZõĮŸL…ÜÜ<g7¯wį-P6Ükíē˜Q|÷Ãōāž}Ũŧ:‡öîŋâį•RŠ”ŠĶĩGđú ›fžūχˇoIIINΓ™¯ŋéáãÔsåoŋ7uú��� íŌ@Ļ%•JåryjVüŪsk/ß?*,͋H8‘]ōČØÔ°˙({s’â5›JKOˇļ˛d^/úôŗ›ˇn˙öËOĮÂĖ}ķõeßūpōÔi"*//}Î[;ēėŨŊ}˙žîî3fĪ)**Ē­<$((ęæ-ĻÍ‘ÖVV‘QO3ŗää”ŧŧŧА`"úâ˯víŲˇø“O?üáÂų7oųūĮL5ˇ}į.w7ˇm[6ęęę.üø“øø„õ˙ĩmˆÂÂÂc'N5}�� MŌĀÕC‰D"•JŌrK$š Ų匜"Qށ‘nį~Ní;‰*Dbą¸ļ}•įōōķ7nڒ””üåį‹™’/>ûT‹ĢÕĄƒ999nŪ˛íŌå̃ČĖĘ*-+6˛Ŗ‹ }ųÅâÇōųüŒĖĖËC‚ƒū÷õ7ršœËå^ŋ6røæ­ÛS?v°ˇˆŒ235õôđ(,î;pđĶE>”ˆėí=JZŋaĶĮ-äķxGW øäã…D”“sõÚõ˙}ųEpP -]ōųå+W›>‡���Đ&i*Ķ’õōÁĶŖøė¨rNžą™Ą—m°ģyPIqŠ––V•%\Jąąqn^•›FFF?|ˇŦghŗŠ¯§˙įšŋ¯ß¸‘_P +„EEŽŽŽDääččääøÁ§N™Ô34ÄÛËŗG@÷:ʃƒKËĘâââ===nDF.^ôņŊû"Ŗn2™VHH‡Ã‰‰•Édū~žĘ`:ųxWTT¤¤¤ēšv$ĸ.ū~Lybb)×ės8ßΝFĮ4}�� íŅLĻ%—Ë+++\>*Œ˛7õr1íZY!‘Ëå§ļLËÉŅᗟ–3¯uuŽ<OŲækŗ^—JeK>_ėâ⤭Ĩ=į­w˜ˇ´´´vm߲öīvėÚŊü§_lŦ­~0oĖč°ÚĘ­­Ŧœœ#oŪjoŅ>99Ĩ[W˙ÛwîDFŪ?vLDdÔûīŊMDĨĨĨDd` ¯ŒM__ŸˆĘĘʘMCCCæS"”5õôôš>‡���Đ&i ĶbÖiI$]ŽŽ—u°‰y;ƒŦ’¤R)‡ÃQ(ĘK„Uč;ųÔøÖģ÷bãâwlŨ¤ŧŊV~Aķē™Ų§‹>útŅG ‰‰ëūŲ°đãO:vtéäã][yHPĐÍ[ˇĖۙšģģvīÖuéWË2ŗ˛223C‚ƒčŋDĒ´´L�ķÚĐĐ J`zzēDTRōlåYqIqŖĻ ���Ú>ÍŦˆ—Éd2™ŦŦŦŦ˛Bj­įQY&Uū Q&“Õ–iÕĄ˛RLDĻĻ&Ėæ­ÛwŌĶ3˜;ŖĻĨĨŸ:}†)wíØqŲW_ršÜøø„Úʉ($8čæÍÛ7""™ŧÍßß/õqڑ#ĮœlŦ­‰ČĶÃCKKKšpžˆnŨšch`āčāP%0g''"ŠŽ‰e6%Éõ‘ ���ŧ$4pNKOO¯¤¤„šÜ&‹%‰BĄāp8Ėģ%%%¸žæééÎįķ7lÚ2īŨwââã\ņshHpRrr^~~fVÖ[īÎ[ôŅÂū}ûp8œ‡Âš\nŋÚʉ((0 ;'įô™sŸ-^DDúúîî›ļlëßŋ/Ķ‰‰ņ„ņc˙\ŗÖŅÁŪËËķÆČÍ[ļÍy}–ļvÕųąĩĩņ÷ķũķ¯ĩŽöíڙũģq3˙ŋ+ž����Uh Ķō÷÷ŠŠJMM­ū4‡cbbŌ­[ƒ°ĶÎĖlų÷ßūøĶĪûöėÜÉgųßåääŧ7oÁÔWgž8zhų÷ßūŊūß_~]Ĩ­ĨåęÚņ¯?V999:99ÖXNDFFF>Ū^÷î?č֕iŋ[ˇ.›6o Rö¸tÉįúú_,ũ*?ŋĀÚÚęŨˇįžõæ5ÆöëĪ+>ųė‹7Ū|ÛĐĐpĘä‰cF‡?‰=���@ 8K—.%ĸĖĖĖ5kÖ´t0����m„÷@Îi���@]|mll4°"����j„L ���€-Č´����؂L ���€-Č´����؂L ���€-Č´����؂L ���€-Č´����؂L ���€-Č´����Øĸ-,.%ĸ’˛ō–Ž��� í()+—jë øD$āķZ:���€ļCĀįé ø¸z���ĀdZ����lAĻ���ĀdZ����lAĻ���ĀdZ����lAĻ���ĀdZ����lŅnĘÎĸJqjFvĨX"“É5���@+Áįië tėŦ-øŧFĻLĪ´D•â¸G õ ´¸87ĐJedįú{ģļt��/$ąDš_X›˜ęŅŅĄqÉVã3¤ÔŒlCC}=]¤Y���Đ&ņyÚÖíÚˇ3IĪzŌ¸Ÿ$‰Db=]AŖw���x!´35ŽU6t¯Û÷žšxĨņ™–\Ąār8Ū���ā…Āįi‹%Ō†î%‘H üö���€5Múí!���@ë$—ËŖŖŖĶŌŌD"QŊŊŊ§§'—ÍįČ´���  Љ‰)..îÜš3ĮĢą‚D"INNމ‰ņööf/ \=��€6čņãĮŽŽŽ|>ŸS >ŸīėėüøņcVÃĀ9-���hƒD"‘ŽŽNŨutttjģļ¨)Č´��� mⴂ›$ Ķ��€ļŠJĻĩqãF"zíĩך3†æČ´~ũũĪÛwî*7 ėėlGááū‚=!dķÖíąq ß|ĩ¤ųģ~wūÂAú‡ÆFãŦŽ‹Č÷8tķöŨ%Ÿ-ŌáķOž>{ęĖŲÂÂBķvæ#G Ô`GJĒŖhĈš> Ęc´kĪž‡1ąŸ-úˆĪ¯yg<|ėāácyųų–“&Œéß§Ŋ��4ÕŽYŗ&<<œˆÄbņ›ožŲl14Ķ9- ‹ö3_{•y- Ī_¸øÃŠŸŋXŧČŲÉąyxŅMzeŧmKGĄŽĶgĪ'§¤ŧ1kąytL뉓§—|ņŠŸūÂĨģ÷Ž3ĘŲÉ)&.öī6čéęúûųj°ģę1"Õ]T'§ƍŋmĮŽͧ6Ž…:;yfŨ†-¯MäîÖņîũ?ũēZ_O/0 ›Æ;�hĘsZLšõÛoŋŅûīŋĪårįĖ™Ķ<14SĻĨŖŖãéîĻÜėęīŋhņ§NŸ}ķYÍĀ‹.48¨ĨCh€ÔÔgŋãĐlä …bێ]Ą!AļÖÖ …âđŅcũûõ6dy¸ģfee‡9ÆvĻՈŠîĸ:9 ĨĨõĘøą?Ŧøe@˙>vļšLa ÅÎ=ûG4~ĖH"ęä홖žącĪ~dZ�đâRfZéééĢV­rqq!ĸUĢVũûīŋÍļ„ĢeÖiņųŧėržä2›EÅÅ;w퍎‰++/333íߎΠũ˜ˇââ÷î?–ž.—+ė;؍3ššæXcųŧ…‹úõé5jäp"*ÍûpQ@÷ŽoŋųĶÔŧ…8`ؐA‰dīūC‘QEÅÅ&ÆFA=ƌŠĨĨEDīÍ˙päđĄĸcbbbũåĮĘJņŋˇÄÄÆéęęöíĶŗļáÔ¤L&;täčQųffσöī×§w#ÆEĪ_~Ē+ū6´  āFD”HTéîÖqæôiÆ&ÆU. kלwæ >tđ fsũÆÍ§-ũbqõɑH¤5ĩī—˙—@DWŽ^˙ß’Ī–˙ŧRƒ‘ßš{/=#sūûīQNΓŧü??åģ~ž×ŽûˇĸĸBWWw÷žũĮNž\ŋöĪ*-Ô5@õbP=ī-øhäĐ!YY7oŨ–ËåŊBC†ôīÆ- ‰::‚1ŖGô VŨĨĘäØŲÚÔö Šãy¸ģ9;9;~ęŲ3Ē-3+ûIn^`@weIî]WŦüŖŧŧBOOWƒ�4åÕÃoŋũVYčęęĒēÉļ[Ÿ››gkkÍŧ^ŋaSVvÎÜ9ŗMŒâ7lŪbŪÎŦ‹ŋŸHTšōˇ?zt{múTR(Μ=˙˯Ģ~^ūŊ––Vå^žî ‰˜6cããÍLÍââ™Íėėœĸĸbo/O"ÚŧuûÍÛwĻOėäčø()yãæmb‰dĘÄ D¤ĨĨ}ūâe?ßNa#†ęđuV­^›“ũäƒ÷ß161>{ö|Ô­ÛúUR[úúz;÷ėŊpáōôiS:ē8?Œ‰Ųļc—––VīžĄ —žžžjuįÍÕ:vüä˜Ņa+~øĻ¨¸čË~8xøČôiSĒÄŧöŸ õŽĢē*“ŗæīõ5ĩ÷ß}ëĮ+---§Nžh éČīÜģogkcŪŽeå<!"‹öæĘw™×Ų9OœŦmŦ:węTī TŠC•]ŽŸ<ũę´)3^zūâĨ›ˇÅÄÅŋ:eĸ‹ķ[ûÚŧu{??ÕÃWervėŪSã'„ę;F}|NŸ=§P(4ø˛ôĖ,"˛ļ˛T–0¯3˛˛\]œ5Õ �@ŗbą¸î=TVVęę˛ûŸÉæË´d2ķĸ¨¸øĖ™ķYŲŲS'ŋ”L™ø ‡Ëež&­Ŧ,Μŋđāatŋü‚ QEpP€­ĩ5M<1 {7mm­ŧ‚šËŊŊ<ˇnÛ%—+¸\Nl\BPî§Īō$×Âĸ}l|‚‘Ą}ģ’ŌŌËW¯Oš0ŽG@w"˛°hŸ‘•uōôŲ ãÆđ´ĩ9âķy¯ŒKD……11ą¯NėåéADĶĻLz[}\ĩYQQqö܅áÆ„‘ĨĨEJjڑc'z÷ mč¸TģĢ;~"˛ļļęLDfĻfžŧ“SRĢŦæ¸ĒSœ:ŽšžŽ—ĢĨ­­edø\öÖôȉ(!á‘ō‡"Q9éę ”īęDT!QhpP#.ķŠCöėü};Q`@÷›ˇutvęčâÂl†>–íĸ’ύNNŸz‘ĢĢËūCá™ŲŲĖGE#ĘËˉHõôķ¯Oyy…Ļē��hNöööŅŅŅŪŪŪĩ%[•••ŅŅŅ:t`5ŒfĘ´ŌŌŌgŋųŽrS_Oo֌é>Ū^ĖĻ@Gįđņ1ąq%%Ĩ …ĸŦŦĖĘ‚ˆŦ--­,-×üŊžoŸŪ>^^<ÜŨę(÷ōt¯U¤gdØw°‹‹O˜8alRJJ\B‚…EûøøOOO‡“––.—Ë]œ”Á8;:ˆ++srr˜U/˙û^ĖĘĘ&"åš}‡ãääđøqz•ĄÕLl\‚T*ķņōRÖôtwģxé˛H$jč¸ĒĖdŨņwč`§|KOO¯ŦŧŧJ jŽĢFU’†ÚŽZmš9™˜˜¨jã¨CVVVĖ &/ąū/õaRĀōŠZĶ”ÔĮéĩ}Bę=FĖ5Í"a‘3-�€6ÆĶĶ3&&æÚĩkĩŨ›TWWˇC‡žžžŦ†ŅL™–•ĨĨrņ;ŸĪŗ´°TžĒ‘JeËWūϐɧL~ÅÚÚR‹ŖõëO×ÖpšÜŋ><züä…K—÷ė;ĐÎĖtė˜Q!Aĩ•›™šYYZÆ'$eįdģēē<JJNHHėŸ6r1Ķ-P9"ˆ¨RTųtķŋŗˆLMīŲ tžíĨT[0Ė™•VüĖĄ§×wä [ZZ4h\ĒŨÕmOwĒŌBŊãĒ‘rrę8ju÷Û”Č‰¨ĸB¤<Í̧ĢGDååĖ "Ē(/'"ũ&œV'†*´yĪũ%ŌÖ~nSĄPÔļcŸz‘> g› ôõ‰¨ŦŦ\_īé|–••Q•KĀ��/:ūŽ÷]MiĻL‹Įį99:ÔøVRrRzzƧčî֑)).)ioūtņ‘‘á¤WÆMze\Ff打§˙ūgƒĩĩ“ŖCmåĖR-#CC;;[=]=7׎[ļíĖ/(ČË/đöō ˙rQÅŗôļĸĸ‚ˆÕÖü2'+TjÖv~ĸÆ`ôtu‰hÎėYvv6Ē•ÍĖL1.åîęĮ_›ēĮUeՏ¸R\c#uĩ5=r"ŌÕTü­ĩ•=y’Ë,Û"ĸŦė.—cĨ˛Ō¨:5Ø ęø„äæåQŸŊ˛Š zūJ_ĶŲŲÚQfVļré[zF&—ËaĘ�^8xÂôS‰”ˆ žūŋ9ņŅŖŧŧ|)ˆčInŪ­˙nyjkc3}ÚT.—“‘™Y[9y{y&&&ÆÅĮģģē‘‹‹Ķ“Ü'‘QÖVVíĖˈČŪΎËå*ÎQâŖd]]Aõ+_Ėöã´§—l¤RYl\\õøk Æžƒ-O[ģ¸¤ÄÆÚšųc o`hhČãņ1.%õã¯MŨãŌ薩œ)IËȤšÔqԞĒö˙„ĻGNDÆFÆBĄymaŅŪŌŌâæ­ÛĘwoßžëîæV÷âG5Č.QŸz?{EÂ""266Ō`PÖV–6ÖVW¯G(KŽŨˆęäíUīSÃ��Z§Vō„é–Ī´:ØŲņxŧS§Ī …ÂŖ7oÛéã핕ST\\PPđûęŋŽŸ8••~ä(‡Ãíčė\[9yx¸ …ˇīÜwsíHDēŨvvgΞgNh‘~ĪАÃĮŽßēs7/?˙ĘÕëgĪ4°?s¯UæíÚš¸89züÁÃčÔĮi6oŅŽV‡ˆj FWWˇw¯Đ‡ÂoDD>É͋‰‹_ū˯ëÖo¨c—:ÆĨ¤~üĩŠ{\Žnßž[RZ*‘JÏ/--mčQ#"}}ŊÔĮS§•¨ėŪôȉČÕÕ%^%W >ė܅K‡‹ßąkīŨûÂFgŪēríúĒÕkǎ æ�ŲŖœŠLVÛ'¤ŪĪ^Bâ#}M/Ԛ<aėҧwí=x˙aôē ["oŪžüĘ8Ív�Đl˜'Lsš\.—ûÕW_Ĩ¤¤0¯SRRžúę+æõKņ„i##ÃŲ3ĻīŲāęõëŽo˜^P(üsÍ??ŽXųÍWKfĪ|íøÉĶû†sšZ6ļÖīŊũĻ••Ĩ••eåD¤¯§į`oŸœ’ęæúôjŽ]NŸ=ĪÜ߁1mĘD]ÎĻÍۋKŠÍĖLGŒ6bčāc›ûÆėõ7˙ējĩ@Oˇ_ī^AAˇnŨŠRĮÃŨ­ļ`&Oœ §§ˇkĪ>aQąą‘ąŋ_§ņcG׹KãRĨ~üĩŠc\“'NXˇaãÂEŸčéõęøāatCÚĀū}×ūķī7?Ŧxī­įîĀÛôČũ:wēpņr^~>sÅ0$8PTYyėÄÉũí,,Ūžû†ōš™YˇīT=Xę=Ē“SÛ'„ęûėŨ{đ°ŗÆoģ×ŋo¯ ‘hīđÍÛwŲZ[}úŅüÎ>^õī�ĐZ)˙´ŗŗ[°`Á/ŋüBD ,9rdŗŨš”ŗxņb"ĘĘĘZŋ~}ƒöŧũ0ÁÖĒ=;QÔLĄP|žt™§ģë´)“Z:–ŸøŨ+žūßÔģG|FvŽŋ÷ öŒQ�€ĻÛŋŸ>}”›k׎=|ø01BõQ<įΟ3fLŊ­Ũ~˜ĐĐK}üēęļü9-�õq8œÉ¯Œ[õĮ_ũûõąūīö /™LļkĪŪ^=CÕLŗ��^fĒO˜ž;w.ķ›÷3f4k ÍŲ@Ķųx{ 4ā?×VŠ[ėgƒ-hßūCŠdÚä‰-�Ā  Ęø™3gΜ9ŗJ!Û1āœŧxƎ;:ŦĨŖhƏ™@õŸå��RY§Õ‚i��@Û¤zõ°Ĩ Ķ��€6¨•<aēås=����cž0-‹kģsŠX,n;O˜���hN/×Ļ���š—Ëõööfõ™†j…ҞŨ���´aĪ´8Ž\Qí1Â����m‹X"åķy°ņ™–Ž€_ZZŪčŨ���^ų…Eē‚ē~ÃX‡ÆgZļVĨå%eå2šŧŅ����´Zb‰4;ˇāIžĐÎÚĸq-4~Eŧ@‡īábŸš‘›_(“!ŲhŊn?Lhé��^H|žļŽ@ĮŗŖCŖ¯6鷇žģŗ}SZ����hÃđÛC����ļ Ķ���Đ<ĮÁK���ØāßÉËÚÚį´����؂L ���€-Č´����؂L ���€-Č´����؂L ���€-Č´����؂L ���€-Č´����؂L ���€-Č´����Øĸ].‘H,iéH����Ú‘XR.k›QyŠ^C÷ĪĖĖd!*���€ÖČÆÆĻAõ õõLŒ ´›­?���€— Öi���°™����[i���°™����[i���°™����[i���°™����[i���°™����[˙4����¨MÄ­ģ:Ņņ8§��� y‰DĄP Ķ���` 2-����ļ Ķ���` 2-����ļ Ķ���` 2-����ļ Ķ���` 2-����ļh2ĶÚ´e›ŗ›—ŗ›×ŠŸW6hĮ=ûö;ģyM{mVƒĒíÜĩĮŲÍëĩYoÔą‹:uØĻæčØvôø g7¯1&ĩlÔėĨ5|§õ2��h4MfZ;víf^ėŲˇ_&“i°e5-ũj™o—€æīˇj=SŅĖ‘´ž��Ÿ{x÷îŊØØ8'G}}ũŖĪ_¸Øŋ__M5^ŖņãÆŒ5’Ë}–,ŪŊ˙ Ū:/‰ęSŅRšų ā3���­ŠÆž~˜ZÆ™Üļ�� �IDAT6tíØĩGS-×FKKKGG‡Įã1›‰$&:Ļî:/‰§ĸE4ķAÁg���ZÍdZĨeeáGŽҍ°ŖÂFp8œķ.>y’[ŊĻX"ų}õ_ũ õđöíÖ#äí÷æÅ'$6ޚęú›~ėîí+–HJJK™ĩb.^ĸZÖčÜē}įŨ÷? ííæÕŲˇK˜ “ūūį_‘H¤Ŧ°{ī>g7¯EŸ~.“ÉūZģnđ°0ĪNūũģŋ:cöÍ[ˇU›’Ëå;wī4uē÷ WĪNũģ0yێršŧĄs¨NSë7lrvķšķÖģUöŨ´y̞ŧļŠ`hqšršüīuë éáã×Ųŋû´×fUÅÄÄ.ü蓐^ũÜŊ}ũģMš:}īž …ĸASÔ ƒrįîŨ÷æ/dJ`hīO?_’“ķ¤AķĶRŸ%>zôáĸO{öāáíëã×­ßĀĄK–~œœRĨZŊŖf㐉Dĸī~XÚģŋ‡ˇoīūƒV˙ĩ–Šļs÷Ūaac<;ųûu |oūÂęëíBũ�ŧ<4sõ0<üHyyš¯oįŽ..DÔ34äâĨË{öí{îœ*5?XđŅą'Á€ũõõôbbcĮŒŸøęÔ)ĢĻÔŋ__]{öōyŧ™3Ļ‘}‡5ÖÜē}Į_~EDū~ž}zõEFE}÷Ãō#GmŨ´A__ˆtttˆ¨Ŧŧ|ÁG‹Îž;čā`įîŊ+W¯EFŨ<zhŋŗŗĶÚĸşīŨw€ĮãõčŪŪÜ</?˙ÚõwîŪŊwīÁ÷ß~Ũ 9ÔTSuO…ŽŽîG‹Ÿ8u:(°‡››kDdÔÕk×ŖnŪ:|p/sėˆčČŅc >úD"‘øúvîÕ3äInîĩë‘Q.]ZųĶræ2œ:S¤ūAŲģīĀĸş+ ?ßΝ;uJHHÜškĪ‘#ĮöėÚîæÚQÍųiŠĪ@u÷î?˜4uēH$ōôôčÖĨ ‡Ãš˙đá–mÛ>˛}ķOOõGÍÆ!{oŪ‚‡Ņ1]ģvÉĘĘēuûΊŸWōx<‘H´ú¯ĩĄÁAÖV–×oD9z,--íĀŪ]Ę0ÔéB́�ŧ\–.]ētéŌ9sæ(š lĖx'WĪí;v1›GŽwrõėŨo\.W­vņŌe'WOwoßččeáÆÍ[;zø8šzN>ŗAÕvėÜíäę9}æëĖfl\ŧ“Ģgg˙îĒ=VŠ“˜øČÕŗ““Ģቓ§”uŠŠŠ† srõüæģžÆô˜“̧o×CGŽÎČĖd ‹KJúĻZ-!1ŅÉÕĶŲÍëîŊûĘÖ<Œfēˆ‹gJvīŨ§vÔlęŸ7:šzž1÷*ģoÜ´EĩŧÆŠ`Jį.C†‡Ĩ§g(Į>hčH'WΝŋųŽ)IĪČđđņsrõܡ˙ rߔÔÔŪũ=wˆÕ›"uJJjLJŸ›WįĢ׎3%2™lŲˇß;šz6˛AķĶüŸÍ}į='WĪ?¯T-\õĮŸNŽžoŋ;OũQŗqČ:w ˜öÚŦŌŌ2Ļđ§_~urõėÚ'&6Ž)ŒŽŽqrõtrõT–¨Ų…:�xyxûv 魁̇Ņ11÷<ÇeJôīgjjō8-íú՚ÃQ؈áĒ˙ģ>mŠkG—FTk„­ÛwJĨŌA 8@Yhdd´đƒyD´kĪ>ŠTJDÄáQqqņ×K—ØX[3Õ &ŒKD1ąq˙•ŽZųĶOËŋīÜÉGؚˇ—§ŋŸ/ŨŧuKũĀ4ØTŨJJJžųúļļ6ĖĻ‘‘Ņ˜ŅaD”’šĘ”lÚ˛­˛˛rČāL9ÃÁŪ~ŅĮ ‰hĶÖmO‹Ô›"uėŪŗ¯˛˛2läđ ĀL —Ë]0˙};;[.—›‘‘IÍ~j”žžADū~~Ē…ož1{Ķŋ˙0͍樚=d%%%_~ą˜9oGD_OD……¯ĪžéáîÆzzz0ķœđß%{5ģPgā��/ \=ÜžƒY ?ØĐĀ€)áķxcF…­ß°iįŽ=Ę/"ē˙!ö¨ú#üžĄ!ąqņ ­Ö‘QDÔˇw¯*åADT\\ü()ŲŨ͕)422ęÚÅ_ĩķ[RRÂlZZZ ö4š,++/((ÉeD¤¯¯ODÅÅ%ęĻÁĻęfbbÜÅ˙š/BKK "ĘĪ/`6¯]ģNDũûVũŨhīžĄ'66ލ¨ČØØ˜)ŦwŠÔqöü"  T-ÔÕÕŊxö”jššÍ~jÔŅÅåat˝Ģ~ˇŗŗU^äņxĄ!AĘ:挚ĄŲCfaŅ^y͑ˆŦ,-™LÎĒŌ…%ŨPZZÚ .Ô8�ĀËĻŠ™VEEÅĄđÃDôĘøqĒå_ŋ~ÃĻã'OŠū+Ÿ“CDVV–UąŗŗSŨTŗZ#¤gd‘m•r}}=SS“ÂBaff–ō[ÖÎļj5-m-"R]ĸžøčҝĢV_ŧtšúW¯âų•ÂõŌ`SučPm™å;ĘûŸ1StāPøĩ7ĒÔäik‹%’””T_ßÎL‰:ST¯Œô "˛ļ˛ĒģšĻæG㟁ę>]ôŅŨ{÷ī?x8dx˜kĮŽÁÁ=CBB‚™Šf¨9jŌô!ĢŌŖ––ķ‚ÉŪTĘšD$WČԅ:�xŲ45Ķ:|ôXIi)ũŧōˇ*oiii‰ÅâÃ_›>)a~ÛUũŸ]>Ÿ¯ēŠfĩFUT‘@ ¨ū–_‡ˆD•Ī~}Æã×s_€˜˜Ø “§•——{¸ģ :ĶÆÚZ + ĸ7lRįįi,5U7îßŦĩ)/+'ĸ+W¯ÕVĄ¤´LųēŪ)RGee%ik×õQÔāühö3P# ‹öáönŨž}ßūƒņ ‰ ‰‰7m140˜=kÆģoĪeĢ3j†fYm=ō´ëК]¨3p�€—MS3­;ŸŪžš(SŨÎŨ{”™–Ÿ/‘HÄbq•:ååeĒ›jVk]==qQQE…¨ú["éëéŠßÚōŸW–——4āßVĒ~‹8ŪĐĀšØ”X"ihĩŅĶ× ‹Ö˙ũWŸj××X”ĸĸĸ:ęhpĒ5û¨žžŪœ×gĪy}vvNÎÕĢ׏8yîü…•ŋũ.-ųüSRoÔjj†CĻ~õ�āeͤ˙eÆÅ'ÜžsWKK+âęŤøč*îۊĐŅ҉‹ŋ{ī>SŋŊE{"ĘyRõŽAÉÉŠĒ›jVkûvD”––VĨŧ¤¤„ųÎĢ~ĨώīÜ%ĸŠ“'WųĪz\|ƒ“ŠŲ—Ë!"šŧ꓎ŌĶĶÚcmˆ(#3ŗŪššÂf1ĩ*‘HTVVÎŦO×āTkö3P/+KËącFũũ×ëÖŦ&ĸ­ÛwH$RoÔjj†Cֈ.j8�ĀËĻI™s_ø>Ŋzš››W×ĐĀ`đĀD´s÷ĶûÅ{yzŅˆHÕj2™ė܅ Ē%jVĢQŨKv˜UögĪ¯R~áâe"˛°hīččPoUčč<wMķø‰SiiéõFԏϘāŲŲĪå ‰„‰ŋŠÆ­î ėADᇏV)‹ÅûĘÍÍkD›õ”ĀDtėÄIÕB™LŌģ_'˙nŅ1ąĘB5§ēų?Ē„ÂĸũUŸĀ^=Cų|žD")((¤†Œē^l˛FtĄæĀ�^6Ī´*++÷8DDãĮ­­Îøqcˆ(üđ‘ōōr"6t08~˙ÁCĻ‚BĄøuÕĘ_Q1ÔŦV…ž>•–• kĢ3eōD>wæÜųS§Ī( ssķVüŧ’ˆfL•ÃáÔ9čį0w8}öœ˛äūƒ‡˙[öM@÷nD”ŖņĻÜŨ܈(:&&2ę&S"‘Hžūæ{áķĄÔ™ŠÚL™4Q DDFũŊnŊ˛P"‘,Yúõ>ųlÉŌĩĻÖA™ô Į‹ˆŒÚĩg/S"—ËųuUaĄĐÁŪž“7Š=?Í˙¨Ž˛˛rҧŸōŲĘcÄ8zė„X,677oßŪ\ÍQĢIŗ‡ŦŅ]¨9p�€—Mã×i=~ĸ¸¸ØÔÔ¤_ßŪĩÕ  ´ļ˛ĘĘÎ>rėø„qc‡ xõÚõņ§ôčĻŖ#ˆ‹ …īŋûö+~VîĨfĩ*ŦŦ,ÍÍÍķōōÂÆŽwqv4°˙”IĢÔq°ˇ˙ęK>ũlɛoŋĐŊ›““c^n^DdTIii˙ž}^Ÿ5ŖA3đæŗŖnŪú{ŨzæŅÚIÉ)W¯]_ôá‚öí™oP™\6qÂx 6Õš“Ÿ¯īģw§NŸŲŊ[WS“{H$’ķß˙ō_+Īå¨3ĩąĩĩYūũ7|¸čģW?ėåéQ^Vu3/?ßÁŪūĢĨ_4hŠÔ=(K—|úŲŸ,ūbÃÆÍÖÖV ‰ŌĶ3tuu—˙đ-“÷¨9?Ū^žÍü¨ÎŌŌâĶE~ũÍ÷§ŧÚÉĮÛŅҁCœä””ûršÜ/?˙”š�ĒΨդŲCÖč.Ô8�ĀËĻņ˙öíÜĩ‡ˆFYĮŗ{š\îØ1Ŗ”•9ÎÚ?ëÍ7ŦŦ,oDDŨž}ĮĮÛkßîĖm™_cŠ_­ --­Ÿ—˙āääøäInttLmŋązeü¸Ŋģļ <(9%eīžQ7===~ønŲ_ĢWŠķC0Uũûõ]ūũˇîn×oD:|D"‘ŦYŊę×g :dø°ĄZZZĮOœ,+SkŋúMũŗöĪ ãÆš˜˜DFŨŧyûvPûvīprtP5§ĸ6Ç ?°wĖč°"aŅĄđ#į.\l×ÎėíšsöīŲŠŧũ’šÔŒdâ„q{vn<h@^^ūĨËWEĸĘŅa#Ã÷īéÖĩKƒæ§ų?5šųÚôÍū6tHAaáŠĶg8™Ÿ_0rİŊģļ+ī ĻΨէÁC֔.Ô8�ĀK…ŗtéR"ĘĖĖ\ŗfMK���ĐFøøuÕ70Äų|����ļ Ķ���` 2-����ļ Ķ���` 2-����ļ Ķ���` 2-����ļ Ķ���` 2-����ļ Ķ���Đ<ĮiĘĻ��� 6]|mllpN ���€-Č´����؂L ���€-Č´����؂L ���€-Č´����؂L ���€-Č´����؂L ���€-Č´����؂L ���€-ÚÂâR"*)+oÄÎRŠ´  @,KĨRMđŌŅÖÖæķųfffÚÚx )�Ā ¯¤Ŧ\X\Ē­'ā‘€ĪkčūRŠ433ĶÄÄ_ �!•JKKK333mllđw �āE'āķôüÆ_=,((011122ÂW€Fhkk3§ Z:��ЌÆgZ"‘ČĀĀ@ƒĄ��ˆÅ▎��4Ŗņ™–\.įrą @ôĩĩąđ� Í@Ē���ĀdZ����lAĻ���ĀdZ����lAĻ���ĀdZ����lAĻ���ĀdZ����lÁƒt����4īöũ‡Ņ I8§��� y‰TĄĀÕC����Ö Ķ���` 2-����ļ Ķ���` 2-����ļ Ķ���` 2-����ļ Ķ���` 2-����ļ Ķ���`K+zîaJ!}p”îdQаĨCi Ž&ägMŋ #GĶŪÍ/î;t4=3ģ °aŗcfjbgc56lX;S“ęīļū9gcZęž��� j-™VJ!ų˙ABQKĮŅrR„”"¤ķÉtûĒYE~Ąđ‡_VWˆ3;…‚BaÂŖ”Eŧ]%ąx!服iŠcN���4Ģĩ\=œ´ĩå7Ąˆæ­Z¸÷āŅÆĨYJ"ŅۃUÛ}æœiŠqN���4ĢĩœĶēÜŌ°ÆÁ„–’úZ*Ē]˙J-¤ģ9œNj§Ē\ûē›UĩZB’f'#+ģJIËÎymĶRãœ;ĶR}N���4ĢĩdZĒ'WēŲRä[U+ŒÜL‡ãšÚ‹‡9Å˧/NĶžO_,;˙ė]m.-ęI¯u!G*ŅáXúâ4e–Pâr1ĢÚÔô=´iüĶ×R9%äĶ7įiëŨĒÕL诉‰ æxLÉÁTŅËQŌc-O™XT_2%UV)áršû„öčæofjR^!zwäęĸâ’:Æ^}%S•Z5ŗûŸt'ĢY§ĨÆ9!vĻĨĄ‹Ū���ĒĩdZ՝KĸŖņĪ6ŖŸh ÍŒbš°äÔüîŋcišŨÎĸ•WÉŨœfuĨ~Îä˙}wLuÉΈæ?‹*§äYēÚ4Ŗ mOņy™ņ\›+KkKŗ”L´b°tÂ΋i¯ŒéŪÅ7=#ëÜĨk–æAŨģ¸utnôZ.FÃLļĖ´4bNˆi��hŠÖ›iE¤ĶŠËĩžëgEˇßĨ%§)Ė“ŧ-čtõ1­#Ќ&īĸËŠDDŗēĐį}ÉŌ€"Ķiæ>J.$[#Ú=ųé9­*ŧ-hšOρ˙’TNDôe_ZڟŪîAß^ "ęfKķ‚ŸEÕÍöš /§ŌŲŲ4ÂŖjJŅËAĄÎ`;[¨UaeiŅŊ‹oÂŖäß˙Ū(—ˉhč€>Ãõë›°hū[ĮĪ\čæ×ųʍČĶįkŸÁjūšITm˜-8- šbmZ���šĸĩŦˆ¯ÎX@Ž&O˙ØW}ˇRFD4ŗ+}|œäЇĄ4՗^ŨMfzôõ�""wsú{ ]NĨĄŠģ-ROwŊˆˆÖE=Í'ˆhÕu"ĸ~ÎjE[$""âU›Nc•37ģvíųđãEb‰D$Íû`áū•oU_ÂUWgG"ēq“É'ˆč•DäÖŅY"•QH@×ŗ¯<Œ‰¯Ŋ uą=-ššjŪi��PSë=§57€æ<}]$"“e4¸L¤¤"ĸđX:—LûŖŠģ­ŋI§QTš›e•ī*J/&Ąˆbže=ŨëeĢ,é)¨ J)™éÕš—€MH—GŸ÷%"ē˜RWå[ļ&%%•––UŠÅׯßHOO3zT=aÕD Đ!ĸâ’ReIyE…T*Õ×Õ%ŅÃØøK×"ŅrulO‹Ļ愚wZ���ÔÔz3­}iķ§¯%2"ĸPšLDT*~šiå–•ˆ‰ˆž0¯+‰¯EDÄãŌwƒ¨—éh_‹’ ëé.­ˆˆ¨ƒĘÉ3SéhĶ“ŌÚö z>ü;ŠŽ'ÔUyí_ŧ6ëõ /‘ŖƒÃęßWÕS-„Âb"25yĢŽŽ@[[줴ŒŲĖ×ÜBoļ§ESsBÍ;-���jjŊ™VB>ˆyŽä““ôÉɧ¯=ĖëŲ}a(đ ëéL=xŸõ ôęc’ĘiNwÚz—$r"ĸw‰ˆNԙ<1é \A ča}köۙ™ŲÚØd¤g‘­­icī™™”úX.—‡ôčyë.sĨŦWp"ЉĢBҰNu`{Z45'ÔŧĶ�� Ļ֛iØŅ‡ĄĪ6o¤ŅĨÔėnĀ'"˛5ĸšÔÁ˜drrŽsŨOR!­žAīŅõšt"ÜĖiœ7ÅäŌÚČēöĒžÖá÷įGDD::8HdŌ+WŽÎ_đŅęßUwgų…¯Fô üđŊ91q‰íÍũ:ye?ÉŊr=JõŒŽF°=-ššjŪi��PSëÍ´ú:S_•e×?\lXĻõûuęëLĸ=iÚnÚū -BŸĒk—G)ģ„fuĨCŠPDëĸhņI*“42~F‘čŲęokG‡ ë×IĨŌéŗfÛØX+ĢĨ6đēÖžđcÅ%%AŨģöīR^!ēq3üØ)ą¤iąÖ‚ÕiŅāœPķN ��€:8‹/&ĸŦŦŦõë×7hĪ””GGGÅņšĻZj]vO”ŽtvŅJ"‘đx<ÕŒđ8ŽęŊŖ˞käŊ—h$˜U?~ĨēŲ‚sŽ:-jÎ ą3-U椕Đė_.��h>~]õ [ī]چOhŠÜ5S™I¨ĻE"úđD]'™_Õ5QĢē‚Ļ:-›ŌÄ´´Ē9�€6ŠĩdZÆČ%ZŖT!Ŧå…Įqjŧ–*¤đ8NĀķq¨ļ(ÜÕŲŠé‘ØŠ\˜c´āœ×1-5Î ą3-Õį��@ŗZË:­>ÎtPíĨå/–T!5čŠ2~ÕžũĮ–”\ũ1ętƍVĨ°eįŧŧĨÆ9��ĐŦÖrNkå°6{ZĢAŒuheĩo˙vĻ&Ÿ|đN'/F\í251îäåņÉī´Ģv…hÎ5;-uĖ ��€fĩ–sZŽĻtį]š”îd5æGgm€ƒ ųYĶĘaäXĶŨ(ڙšĖ™1Eŗ=žsŪüĶ�� A­%Ķ""GS:0ĩĨƒxÉ`Î��XÕZŽ���´=Č´����؂L ���€-Č´����ØŌŠVħŌGéNĨ´ÖßÁiŖ ųYĶ/ĩü¤���چ֒iĨ’˙$Õ_ŗmHRАÎ'Ķíwl��´A<ĶzŽÎ?úĨYJBÍ?ÚŌA���� ü;yõīŌZÎi]Hné4ÄÁ„–’úZ*ĒŠJ-¤ģ9œNjĢŪ&ônVsF���ÍĒĩœĶR=Ą•´-xúúõޤXF—Ūxēųã`R,#˙šž ėaNŠeôyŸÆĮ0Ѕ†š5Š)ē1GæQCšEDĻæĄ¸1Gĸú°ä—aQ��ĀKĢĩdZĒ.§’ŗĩĶ%" č@DÔՆ´šDD]mФ’îeŗŌīwƒžfZļb°ÔDPO­,mR7���đ‚h™ÖĨ"ĸnļDDv‘Nē<ędIDÔņŽ>&™‚†¸ŌŨwŠb)]|Ŧ žíkŽGįfSé:0• øDDŗēPŌB*û’ÎĪ&'S"ĸ>N¤XF¯w%úīôÕŌ~”¸€ēÚŌ;tũͧMéōččt*]B§’üŦHąŒ>íEįfSŲ—O Ģčå Pg€-ÔĒ���/ē֘i]N%"ęnGz<ōą MˇIŽ ČŌLt9•ėiīJ+ĸ ŋČP‡6Oxļīd_ZIk"i”'Í "wsú{ ]NĨĄŠģ-Rk§SvíēO3ö>-™îGį’čô# ķ¤ižT)#"zģ}ūŠ 0Ošę[ĩc•ZģvíųđãEb‰D$Íû`áū•oÕxm���ڞֲ"^UL.å•Sw[ęjCZ\:™Hqy؁ ʉˆ.ĨĐXoŌãŅo×čN6­¤?F’…ūĶ}DĶö{´įŊ@ũ]hÕuō]EéÅ$QĖōąŦĩĶč'DDše›GæDD§ŅōË´ûō$oKē˜BDt(–N$ŌŊlZRWkD´qËÖ¤¤¤ŌŌ˛JąøúõééécFŌĀė���Ā‹Ŗ5fZDt%•ēŲR€TPB>ŨHŖîvô¤”$2ŠH§~.DDĻ’\AZ\âpČŪ„J+‰ˆ2Љˆ$r*Ŧ 3=âqéģAÔˉt´ˆ¯EI… ˆ!š€ˆ(¯œˆHGëiaV Q‰øšÂ­ũë×fŊ~áâ%"rtpXũûĒô ���mB+Í´.ĨĐ(OäJ‘éDDé4՗Š(*ƒ*¤”QDDôÆē‘ö´~F11?čcÖlņĩČL>Ą…Ą4ƒŦ§3Iôā}h‰eDD†:DD6FĩÆĐÄĩTíĖĖlml2Ō3ˆČÖÎÖÔÔ¤Ū]��� ië´čŋĨZ\čF:Ņ4âiQ`‡§åGãŠBB;‘ĩ!}Ņ—ÖŒ&ųiŅxįMß "6Hxē(ŪֈæPc2Ķ%gSJĖ'™œĻúR¨}útĮJ))dOĄšÂ;īĪˆˆttp°ĩŗŊråęüiĻ]���xq´ŌLëV&•KˆËĄˆ4"ĸ{9T!!.įiĻ•^Lãļ‘“)˜A~ÖôŨĒ”_‹ˆčß[ôQ(ÍéNÛīŅ7č÷ëô ‡ūEÁö4m7ņĩhųzRF‹O‘“m@nq9$‘Ķē›äcIË4>ė"•ģ‚ŲØX;:8lXŋnã?ëllmllžŨ,÷Đ��x9p/^LDYYYëׯoО)))ŽŽŽ‹ãsMĩԒvO”ŽtvÕQ"‘đx<ÕŒđ8΄ĪŽÛ*–5gŒđĐė_.��hŗfͲļļæ–‹Äå"ąH,iéxڂOhĢžÖRfWĒiV‘ˆ><ŅJ—Į��€ĻˆÄ’r‘˜kbd`bd`¨¯×˛Ņë´l˙š‘*¤€ĩŧđ8N×S…Į XËS}× å��Ú"C}=#ƒÖrrĨ3Œié 4!UHĒWëåWĶ3�� mh-+âWk#§ĩÄX‡Vké ���€5­%Ķr4Ĩ;īŌ(Ī—åjšƒ ō¤;ī’#žĖ��Đvĩ–̇DähJĻļt����šĶZÎi���´=Č´����؂L ���€-Č´����؂L ���€-Č´����؂L ���€-­č~Z����mFÄ­ģ:Ņņ8§��� y‰DĄP Ķ���` 2-����ļ Ķ���` 2-����ļ Ķ���` 2-����ļ Ķ���` 2-����ļ Ķ���` 2-����ļ Ķ���`KSŸ0šžšfũæŌ˛r…BĄ‘€ā…Æáp õõۘ9ÍąƒmKĮ��ĐōštN+=+û§ß֔”–!͆BĄ(.)ũyÕÚôĖė–Ž�� å5)ĶZŗ~‹‚cÁķ8…Bžæß--��@ËkRĻUTTĸŠ8 MápŠŠņŲ���hZĻ…ZP\P�� üö���€=Č´����—ëũ�� �IDAT؂L ���€-Č´����؂L ���€-Č´����؂L ���€-M}ŪxmJgoåĻ\.Ī/>ˆ‰=zęœHTÉR§ß/ũäÜĨk'Î\hJ#Fwuqúö§ß›Ėן}x#ęöágšØŽĻp8œ>ĄAÁ]ĖĖL …E×#ošp7Á��ĐŦfĘ´ˆ(/ŋ`ÛîO{ÕÖî`g3°OO+Ëß˙ŪČRûgfį°Ôø‹nÄāūũz‡9q&åqzG'‡°Ąrř‹WZ:.��€6Ĩų2­ĘJqBRŠr3&>ą¸¤tę„ŅΎöI)Ųč1âæ6šm¸\nīĀs¯ž>™ˆ“RlŦ­ēøų Ķ��ĐŦæË´ĒKIM#"ccf“Ëåéßģ‹_'3SĄ°čėĨĢ—¯E2oN?ÚĩŖSyyŕë‘<Ī×ĮëëåŋŅOË>?zōœ2E˜2~”­õōßūĸį¯~÷å''Î\đtëčÖŅé͝~‹%õö%Ē]žY[đ.N#‡ °ąļär¸YŲ‡Žz”œJDZZZÃö čę§§+HĪĖ>päDrjŗ‹\Ą2 O¯ �]]A|bōæûJËĘęøˇK<{ŅÚ˛Ŋo'/.‡{5ōæés—§LåâäPY)>rōė¨Ûuˇ6t`˙Ū!ķ>YĒŧBĄø~åę˛ōreIĄ°¨ƒ­M#$���Ôĸ%3-‹ö툨P(d6GØmįžđ¤”ĮŽ.ãF “Ie×"oŅôÉã-ĖÛũõĪæĸ’’Ū!Ŋ=Ë+*ԗL& ėö :îØéķbą¤Žž^4NŲW¯ā~ŧU3ŸĮ›;sZԝ{Û÷ä§WHˇ_Ÿūų˛åĸ1#†tõķŲĩ˙H^~AīīŧūÚw?˙ž_($ĸ.ž>ąņū\ŋÅĖÔxę+c†ęˇsxŨ—Édũ{‡ėØwhûŪC!=ēMææâ´k˙‘”Įۆî?qˈ{c**Du´ũ$÷al|•ø E^~r“Ëåz¸š$Ĩ¤VŠ6l`ߥûV)<vęÜŅSį4ų���/­fÍ´¸Ü§?uÔŌŌ˛ˇŗ3bhfvsĘG ŖĶ38āÔŲ‹Ė%ŋËųėlöíu-ō–ą‘Ą›‹ĶŽũ‡ã%ŅŽũ‡=Ũ:6´k…B!K=Yo_}í>pÄÃĩ†žLMČ[wsžäŅžƒGoŨ} •ĘttøÁ=ē8|âöŊD´}ī!sķvLĻ%‰ö<BDi™ž>^ŽövuÃô•–‘õ0&žˆnŪš?i\XrjZĘã4"ēyûېūŊ-ۛgįäÖŅBÄÍ;õ^E :°ŠéēMÛĢ”3•j˛…4 �� Aš/͞ĩąúõûĨĘM…B—°}ĪAåģÚZZ1ņ‰Ę ’ƒēōų|+‹öD”ž™Ĩ|+ųqšuCP^Å̎¯Ô´tå[ŠiévļUûz’›Ÿ“›7cʄK×"bâĶ3˛“RˆČŅžO[;5-ƒŠ&“ÉūŲŧCšWRJšōuIi™ŖŊNŨÁˆÅb"z’›Į”‹*+‰ˆÉ픛ēAŊ-Ô-lčĀŪ!=ūŪ´=7¯ úģĒÉŌ,��€†jžLëInūÆíģ™×=ƒ{x{¸mÜž§ĸBĔ:DôūÜYôߍ8ččč‘ęÍ *Eõ'ՉDęö%–HŸõUYC_ …båęuúô č6t`Ą°(üøéČ[wõôDT[~ŖZŽ ‡SO0Ė>ŠTĒڈäųMâÔßBm8Τqa]|}ūüg3s¯FĘė i��@C5_Ļ%‘H§g2¯÷‡īäå>zø`å9-&‘Ú´}OfÖs÷e ‹ĖۙŸĪW2 ŖĘ x<^Ŋ‘ÔۗŽ@GY¨Ģ+ š”–•8râĀ‘Víûõ™>i\vNnii9ũ—<ŠŠŽ`Øna¨áž>žĢÖüĢ<.ĩAŽ��Đ8-søōŠŠCĮNuīââäĀ”ddfKĨRũœÜ<æOYyyIY™T&cŽ—uøī‡Ãqr°W6%ĒŦTM†lŦ-ëíŊŪž”—&š\ŽĢ‹Sõڙštōzz#Öė'š;ö’ËåÖVOrķÄIGgGe¨ķæÎ čę׸`ęHSZčęØŨõēMõĻY���Đh-öÛÃk7Czt<nÔwŋü!“ÉD••WnD Ô¯ŦŦ<õqē™ŠÉ¸°aÂĸâŋūŨR(,JJy<¸īüÂ’’˛ūŊCTÛyœžŲŲĮķėĨĢ••â~Ŋ‚õõôŠŠKęîēSĶõ땛Ÿ_RZÖ'4HVSžbjjōúôIž|§PP÷.ž …"95MTYy-âÖā~Ŋ…EÅŲ9šĄŨė;ØnŨŊŋqÁ¨9“uˇĐŊ‹¯¯ˇį:•åbDÄĶÖ9d@tl‚Ÿīú_^HDIŠi5Ž���§Å2-…Bąk˙á…īÎÔˇįąĶį‰h_øqæ†F†Å%Ĩ÷Ŗcϝf*oÜžgʄŅs^›R!Ēŧ|=˛ŧĸByĒi_øąi¯ŒųzņÂō ŅÕQ7ī¨ķËÄ:úÚ°m÷” Ŗßœ1•é+âæßN^UvOLJŲ˛k˙^!Ã÷—ËäY9OūŪ¸=7/Ÿˆ9ĄP(Æ Ŧ#ĐÉĖĘūķŸÍyų…FMu´`miŅIå9H sc#“N^U†ļøëKJJÔ5���ԁŗtéR"ĘĖĖ\ŗfMƒöLIIųiõzV‚ĒĻžEŦZõãW-‹*%%ÅŅŅąĨŖ��€&ņ#hÉ;—���´U]|mllZfE<���ĀËā…<§ĩûĀ‘–��� ~8§���ĀdZ����lAĻ���ĀdZ����lAĻ���ĀdZ����liZĻÅŅPĐæpđŲ���hbĻelhH …ĻBļCĄ062jé ^TRŠT[û…ŧŅ��T×øLKOOoŌ¸0ââÜTÃåŧ9kZKņĸ*--åķų-��hWX\*,.-)+očžfffFúēoΘj ¯ĪFdđ‚24Đ˙đŊšvÖV-ȋG*• …Âââb33ŗ–Ž��šĒ¤Ŧ\X\Ē­'ā‘€ĪkčūÚÚÚ666‚‚‚wfO•JĨ,D/*…Tœ’’ŌŌQŧx´ĩĩų|ž Ž�´>OOĀoŌ?čÚÚÚš ��� Á]����؂L ���€-Č´����ØĸɅˇų…Â}‡ŽĻgf 5Ō ™Š‰ÕذaíLM4Ō ���@sŌXĻ•_(üá—Õ"‘Ļ$ĸ‚BaAĄ0áQĘĸŪF˛���/]=Ü{đ¨fĶ,Ĩ ‘hīÁŖl´ ���Ā*eZ IɚjĒēŒŦlö���`‰Æ2-‘¨˛zᒏį¯úņĢáƒúŠßŽe{ķU?~5l`_ÕBM-ü���hN,ūöĐÎÆēŊšY|b’göz���hĩX|čG_Ÿâ’Ōc§ĪĪ›;ËÚŌ"+į ŲÚX}2˙íCĮNyēut°ˇ‹KxôīÖŨ‰$4¨{ؐb‰ä•ëė…���МX<§åßŲûîƒčGÉŠĨee]|ŸžÖbžØ+¸ĮŠs—._‹ėäåŅŨŋs;S“WFČĖÎYŋe§›‹3{!���4'ļ2-;[kķvfÉ)iÆF† ’Ÿ]@TŨŸxæÂe"˛ļ˛twëČápÎ\¸œ”ōøäŲ ,…���ĐĖØēzØĨŗMŸ<NYĸŧ€HDE%%D$Ē‘ļž•”–)ß���hØĘ´ü;û$Ĩ<>yî"iqšŗ_ÔÅ×įČÉŗ5V.¯¨ "#C"2ÃJ�� ­`%͞ˇŗ1ogzáĘõ‡1ņLIĘãt˙ÎĩfZq ID4 OOąD2¨o/6B���h~ŦŦĶbVeÅÆ'*Kbã--ĖmŦ,kŦŸ›—āȉöæíĻNuûqĩ´Ø ��� 9q/^ü˙öî<<Ēōî˙øg’É$„$ėI€Ye‹la_QDEKŠX­KŸÚÖ>?kųĩ\ĩÖÚÚēâ‚EŠė@öEVŲW„˛$$d'™ė“d2ķü11’9™$ž_˜sŸûūžĪußgΑ”””4gΜętôüo^sSI{į¯4´���7š6mZhh¨Ûæ´üü|ŨÕÕĩB‚ƒŒë��Ā nKZíok뎎ŽÕ*,Ô¸Î�� âļ¤5qüƒĻĩüü|'ŽcDĪ���†r[ŌjüĘ/ŸërW7Žô…ušĢÃ+ŋ|Ž ˇ~���u;īōĐ$$øé§Ļ¸ąC��€:ÍĀį��üČUkNËnˇgddšž  :ĖfŗÅbiܸąŲlÔÃ��5Ŧę˙ĄÛíöÄÄÄāā`~1�naˇÛsssÃÂÂø™€úĄę̇ÁÁÁ5âWāfŗŲõ3•‘‘áéZ��îQõ¤eŗŲÜX �IEEEžŽ�āUOZ‡Ãˋ ę73›Í\ø�õQ ��Ā($-���Ŗ´���ŒBŌ��0 I ��Ā($-���Ŗ´���ŒBŌ��0 I ��Ā($-���Ŗ´���ŒBŌ��0 I ��Ā($-���Ŗ´���ŒBŌ��0 I ��Ā($-���Ŗ˜-‹$OW��PøøøX,æ´���Œböt—Åfę—kt$IąVO—R7EĢ{¨ū1F‘!ž.��HĒ=I+6Swŋ'ĢÍĶuÔeąVÅZĩõŧ?GØ� V¨-̇/­!fš‡ÕĻ—Öxē�� ŠöĖim;īé jąˆ`Ŋ9ŌŪ­…3âš™Ē¸LM1ũzƒ9ŽÜ’ëҤšŦ��\WmIZå'´ÎüÚ5žēÁÔ%úü!Ŋą]¯l¸bûĐÛô˙‡ŠkKy™t2UŋߤõgŽhĐĀŦ™ÃôH…*)G1'ôûMĘ)ēŲÂF´“ˇÖœV‡Ļ:ų’~ŋIŗļęWũõû!r85ā#}÷b鯛WÖį͈ÖŪ§‹ƒũŽŗ7D!ÎčČ⨏|ĘÂē�PKԖ¤UŪëÛŌ@­éÅ~úú\i"IÉŠ eˆŸžzBqVŊŧVŪ^ú]´bW›ŋ*-˙r›ÅjėÚ¯…ßęŽ&zĄ¯:4Ķčš7]ĖHí‰×šĶ瘭Iķu<E’~Ĩķ™ēû=Z.oŧ…üĄĪ›ņˇQöëÅŦ2Á~úÛ(û¤…ĩņo�€ŗÚøģų̓’Ô+\/öĶžũmgéÛkuhĻ>úĪQ}|@’öÅëö&˛;.7čĒąwjĶYúˇNIzu°z„)ØOV›Fˇ×ŖtGSíOĐà ”œĢģCuč9ÍÚĒ)]õŅ~ũŦˇÚ5VĪpõ ×SKĩøQũ~“&t,rsÎRį•nœĩUƒÛę­{ugSLĶKĢĩë‚$MëĄW‡¨E€ö'č'Ët>ŗtÆÎÕį=ŗ+¨á*ŅΛųĐē6ŋŠf�� &Ֆ+âĢæû4åę•hũcŒÆuЙ -:ŽĖr ‘ŨB%麃Ĩ1KŌŦ­zđKYmj¤ĨSŸĨž*ĐW_L’$›]’~ÖKoíŌęSš˛H’}̧–^îķ‰%JĘŅŅdõū@%?¤ēūZõ„ōŠôĀ$iåō÷ŅMõņÚ§{įĒw+Ŋ9ZŌ}VXÃU‚ĘMh-Z´äåßüØfŗŊøË_-YQļëÚK¸��€ĮÕÆ9­›—iͰ9úĶpũŧ^ę§ĸ}´_ŋ\syZĢšŋ$Ĩ\3Q$éÁNjčŖí֑d}´_īSs9’´æ´Ūß+IIJËĶ÷éęĐ´ôĀ“i**QnĄ\ŧŧq\XôˇÚxVg¨SsYŧ•”Ŗnī(![V›NĻĒs I:‘zšĪ—úUPCjŪuĪwîŧ˙œ;w.77¯°¨hĪžŊ L_ÍĪ��§n'-Iû/jô\ų™Õŋūw°~qöÄë?GK÷&åHRhāåöf¯ŌŌ@’b“Ã)o/™LjŦÜBIŠÍŧå2ÂI* Iį3u>S’š4Đë#ŨVžŪ˛xëÜ5ŨVXC%IëŖß{rÚômÛwHŠŒˆx˙ŨwnšP��PƒęvŌuģíĻ×6éB–6ŸSvĄöũ\‘Á—¸(IĶzjū1•8%éĩ!šÔYc>×Å,IúYŒöƗ6ž˜­ˆ`I——ožëú*W¤ëØLÃÚ)愞Ō}4|Ž6ŸĶņäw͇]a •hŌ¸qxXØÅ„‹’Â[…‡„WÖ��xZKZ}ZéåĨ¯íOŅÔîŠjĨš‡U\ĸ)Ũätj[ėåö'ŌôųaMŊ[;ŸÖ–sjßDuŌļXÅfjÍië‘.ē`Õô^j¤1×|!ąĐ.§S}Ûh@„Ō¯?Õ$iåIŊ=Vŋ¨ÜBŊ6TškŪ‘ŌÅĮđFzĻZŠÄĄÛBŸušĪ›ŠĄŧį^xißžũ‘Å%ö]ģžyé~ũūģ˙ŧÕĪ��Ԙ:vEüÛôæčŌ?ŠMg5yĄr‹ôŋƒôÚP9œšŧP;ãŽ8ä§Ëõ‡Íj毗(Ē•ŪŨŖņķ䔲5ņKĩ Ņú§Ô=T¯oSaÉÕÃ;ôÉAunĄYÃoPXRŽ&Ė“ŋE1ĢĄîŸ'ĢMīîŅņ}8^ũÚčņŞxëÍŅWôy35d•ģĀ?,,42"âßs>™ûé'aáaaaĄeģ⸇��ĩiæĖ™’gĪž}KGÆÆÆFFFē­ŽWŨÕS}ŗøû¸;//gûøø”á˛ę”Šüũ´œŗj˛F¸™{¸��1cƌ°°°:6§õ#ôōzsųi­˛tU>feŲôōú:ļ �ĀAmIZAžžŽ ļŠŗĒĪG>ĢN™*\ŒŗjÕ)SŸrâ‘J¯ë��W[&BßĻ'=]DmgÕ-=i§{čÛ��€P[æ´Ūô–{ųęí1ž.��HĒ=I+2DG~ĄņYųĒēˆ`ī¨#ŋP$Oæ� v¨-̇’"C혧‹���pŸÚ2§��P˙´���ŒBŌ��0 I ��Ā($-���Ŗ´���ŒBŌ��0 I ��Ā($-���Ŗ´���ŒBŌ��0 I ��Ā($-���Ŗ´���ŒbļZ­’rrr<] ��@ũ‘““cĩZÍÁÁÁ’ōķķ=]��@ũĖę!��€QHZ���F!i��…¤��`s5KHœ=į‹Üŧ|§Ķ閂P§™LĻ@˙†?ûÉ㑭Ã=] ��žW­9­„¤äˇū5;'7˜§Ķ™“û÷w>JHLöt-��x^ĩ’Öė9ķœ"cáJ&“Ķé˜ũŲ<O×�€įU+ieeqŋSTÄdĘĘæß��ÕKZLházXP�@|÷��Ā8$-���Ŗ´���ŒBŌ��0 I ��Ā($-���Ŗ´���ŒRŨįŪ¤Ÿ=9Ĩk§eoĮĨ ëņ“ߝظĩÍVhĐ ™ųĘ×;v¯ßŧ­:Lš0ļ}ģļ~ëŨjķ§˙÷ōŪ‡ŋZŋššũ¸‘Éd;rčČĄŅËV­Ûēsˇ§Ë� ĒĄ¤%)ũRƗ‹cJG5›[ˇ 1x`XËī~<× —­Z—˜œbPįu]ŖĀ€Ÿ<öp@€?ˇ�Ā85—´ ‹ū{.ļėíÉĶg˛sr›4áļČ6įb/1➃GŒčļ~č}wˇœÜŧæĖ{cæ+žŽ�€zĢæ’Öĩbãâ%šŪzyy6¨G÷.C‚­ÖŦ-;žŲš{ŋkWPŖĀ)Mh{Ûüü‚]{öûøøtë|ןŪü§¤ˇfŊēfÃכˇīrĩœōĐøđ°Đ7˙õĄŽ\=|ũ¯Ŧßŧ­ãˇßq{ÛßũņĸĸâŽe+°íÜŗ˙zŎk1nôđ°Đ^&¯‹IÉ+×n<{>N’ˇˇ÷˜CúôėŪ°_BbrĖęõįãâ]‡8œÎŅÃG÷íĶ ßé3įŋX¸,7/¯ō˙ķkŋŨ°e{h‹fŨēÜåeōúf˙ÁM_īœ2i|ģļ……EĢ7lŲ{āpå=ÜīˆaƒúŋøĘĖĢę?xôÛ˛ ��ēIĢyŗ&’2­V×Û cGõŋ§×ÂeĢÎÅ^čĐžŨÄņcJė%ģ÷’4õ҇š7mōá§_dåä ęO×Nķ niŦ’’’ū÷ô:~âÔÚM[‹ŠŠ+ë‰ÉËÆŠîÕŊK§ŧüüĢzŗøø<ķ“Į96é “LŅũŖž>õÕYoظotĪî-_~)cP˙¨įĻ?ųúßßŊ”i•ÔŖ[įīOŸũ`ÎŧÆ!A=üĀØ‘C._Uų‰—”” ÔÁ˛•ķ—ŽėÕkōÄûīh×vŅōÕąž;jØ#Üwėģ“ļJzHNMûîûĶ×~ ÖŦė~hcF šwĐĢ6ŽŨøõš_ßŌ‡�ĀV&-/¯Ō¯:z{{ˇiöĀ}÷&&§¸Ļ|ü|}öëŗqËvגßÎK­[…Ŋ{˙Ą Fw´kģhųW§Īž—´hųWī¸ũV‡v:EEÅ+Öl¸áXwŪ~[ŲX‹cVwh_ÁX!!A~~žûMIM—´dŚCGÛí%žž–~Q=cžZøØqIķ—ŽôõõmÚ´‰+iŲlļ%+VKŠŋ˜Ø­ķ]‘mZU^ŒkŦø‹Iߝ<-éā‘o'Oŧ˙|\|ė…xI=lP‹fM“SŌ*éaßÁ#U^Eu%Ēōa‹˜�Ā-Šš¤ÖōŸ™YöÖétž8õßųKV”í5{{Ÿ<}ĻŦÁĪžī×§§ÅbiŲŧ™¤„Ĥ˛]į/ġ ŊÕĘVņn8V\|BŲŽ¸ø„VáW•šv)%-ũŠ)“vėŪwōô™„‹IgÎÅJŠlĶÚĮlŽ‹ŋčjVRRōé ʎ:_ö:'7/˛oåÅIJMKwmˇJre잎 üünØCu”[Ä,��nUÍ%­Ô´Ksį/vŊØ/ĒS‡;æÎ_RP`smņķķ•ôÂ3ĶôÃWáL&“¤Fžžž’Ęß ĸĐV•�aŗŨėXEÅöËcV0–Ķé|ûũO†دO¯ûī‘iÍZĩnĶūCG6ô“tŊ|S~ģSN“éŤ_ʐdˇÛËwR|å[™nÜC5•Ĩ+b��ˇĒæ’Vqqņ…„D×ëåĢÖušëÎ cG•Íiš‚Ôįķ—$&]q_Ģ5Ģi“I‹ĨlŖ+и\uŸVrÃąøų–mlĐĀOÉÍˏYŊ>fõú–Í› Ôęä‰É)iššųú!<Ũ¤JŠŠąnˆŒ�@Õxæņų+×nęÛģGģļŽ-“ív{@€JZēëO^~~N^žŊ¤Äĩ^Öú‡%<“ÉÔ6ĸMYWļÂÂōa(,´Å GŋáXeK“^^^íÛĩŊļ‡&!Á]î*ŊkrjڂĨ+GhËæŠiéEÅŎßYVę‹ĪLëĶŗ{Պšá‰¸Ģ��`}÷p÷žƒũŖz>:qüë˙x¯¤¤ÄVX¸kīą#‡æååĮ]Hh<ņū1ÖŦė?›—iÍ:{aÔ°A—23srō† ę_žŸ ‰];wܲã›ÂÂĸĄŅũü6ĖĘΊ|čĘĮ:?rhtÚĨK9šyƒô-Š(¯„„OŸ:yŚ ĮOžr:ÕģG7§Ķy>.ŪVX¸{ߥQCYŗ˛“SŌÜĶĢMëđ˙,^^ĩbnō“Ŧŧ‡Ū=ēuëÔņ“r—‹š´ māë+Éd25kÚ¸ũm‘’Î_H¸ją��T‡Į’–Ķé\´üĢ_ũâé‘CŽŨ´UŌ˛Uë\7,h“ûí‰īW­Ũäj<wū’)“&<ũä”[áÎ=ûķ ĘϚ–­ZûøÃüé•_`ûfī}ÜĖ7+ëß_.ž2iŒ§sĩīā‘n]îēęđ3įbį-Z>,ē˙ØQÃ%ޤ”ԏįÎOKŋ$)fõz§ĶųĀØQž~ž‰IÉ|úEúĨĖ*s“*é!´Eķ.垃Tæ‘Æšžü()ē_Ttŋ(Ixũī™Ö[��TÂ4sæLI‰‰‰ŗgĪžĨ#cccßzŽ!EŨˆģžECŊķ×?zē„ē*66622ŌĶU��ĒeƌaaažšN ��āĮ€¤��`O>§ĘĮŦöt ���7Ɯ��€QHZ���F!i��…¤��`’��€QHZ���FŠ^Ō2šŠ Ô;&ūm��P¤ååå(§ĶÕ žp:ƒ5ōtu•Ũn7›ëäî��×ĒzŌōķķ›<ņ~y1wkx™fL{ÜĶEÔUššš‹ÅĶU��ÜŖęIĢqãÆüĖxęą�7„ē.0Ā˙åįŸiÚŌĶ…Ô=vģŨjĩfgg7nÜØĶĩ��ÜŖę‹fŗ9,,Ė/#㚟>fˇÛŨXę:§Ŋ(66ÖĶUÔ=fŗŲbą„……ąz�õFĩūC7›Í͛7wW)���õ wy���0 I ��Ā($-���Ŗ´���ŒBŌ��0 I ��Ā($-���Ŗ´���ŒBŌ��0 I ��Ā($-���Ŗ´���ŒBŌ��0 I ��Ā($-���Ŗ´���ŒBŌ��0 I ��Ā($-���Ŗ´���ŒBŌ��0 I ��Ā($-���Ŗ´���ŒBŌ��0 I ��Ā($-���Ŗ´���ŒBŌ��0 I ��Ā($-���Ŗ˜Ë^Oō`��ŚáĘ�� _IDAT�õĪå¤Õļu¨ë���¨X=��0 I ��Ā($-���Ŗ´���ŒBŌ��0 I ��Ā($-���Ŗ´���ŒBŌ��0 I ��Ā($-���Ŗ´���ŒBŌ��0 I ��Ā($-���Ŗ˜=]���¸egc/ĖųbAn^žÃáđt-ˇĖËË+Āŋá´'&ˇ‹lSIŗązxŌōdwÖXiW3›ÔĖ_‹&k@d{`N �€:ælė…ˇß˙$;'ˇ.Æ,I‡#;;įí÷?9{ázmvÆ*ú%åz2fI˛;•”ŖčO´3ļŠ=´��¨c>ũ|§K¨6“INg%'2iĄ<ąĘ1ÉéÔ¤…U<š¤�@““›ëéÜÁdĘËĪŋŪĜš,åFLJ¯ęGNŌ��žQÉęgm™ĐúA•1IZ���F!i��…¤��`’��€QHZ���F!i��…¤��`ž{�@=×ģGˇ~}z†‡ĩôööÎĖĖ:ōíw[ļ“_PāÚû—™¯|Ŋc÷úÍÛ<[du˜¤'ēë§ŊÔĩĨ˜•­Õ§ôÆv%ūpû͘Įĸîī^>äáÎZđˆ^X­w÷[I �€úlęä‰ŊîîzøØņK÷ÛíöČ6­ĸûGŨŨĩķÛ~š“Sî5īeŌü‡5ŠŗæĶ{•S¨Ž-õ|_=ÚUÃįčXJ‡ ŒĐį鍆Į,‘´��¨ĮîéuwīŨæ/]ųÍŪŽ-Įž;š÷āᗟŸqßČĄķ—Žôlynņl”îĸĮéËcĨ[VŸÖĮ´ëi-zTūŠ’+oīŪą™V<Žßęwjĸ<’��õÖ ÷ÄÅ'”Å,—”Ôô~0'%-ũÚöfoīûFīŲŊK`€vNîžCG×lØâzfNģļãF máeōē˜”ŧríÆŗįã$yyy6¨G÷.C‚­ÖŦ-;žŲš{͜Ë‹}ĩņĖå˜å’ž¯_¯ĶŠĮ5æN­úūōö–Zû¤öÄkúō*¤�@ũäįįÚrÖí×îJHLĒđ‡×­SĮ…ËW]H¸ŲĻõ#Žŗø˜—­Zgņņyæ'8rlūŌ&™ĸûG=;}ęĢŗŪ,(°M;Ē˙=Ŋ.[u.öB‡öí&ŽSb/ŲŊ˙Á'W*4@ˇ7ŅGEģg$iPäå¤`ŅęŠJÉÕ¤ų˛_÷‰‹nFŌ� ~ 4™Lé™7ŲžaÃQ=ģ/˙jũĄŖĮ%Ĩ_ĘlŲŧؐ}WŦŲäįįģ˙ĐŅ”ÔtIKVŦ9tô¸Ũ^âįë;°_Ÿ[ļī;xDŌÎK­[…]cI+<H’b­ė*°+9WáJߚŊ´øQõ͝Ö*¯¸fĒ“¸Ë��õ•SNI%%%7ŲžUhK//¯Ø ņe[.$\´X,͚6IMģ”’–ūԔI#† lęp8Μ‹-..kiöö>yúLŲ!˙={žYĶÆ‹ÅŊįr=‡$ų\'Îx™äøá"­NÍÕ´Ą>ØĢ×G*ĒUÍT'1§�@}••ãt:›7mr“íũü|%Ųl…e[l…E’ü|-N§ķí÷?>x`ŋ>ŊîŋwDĻ5kÕēMûuōÂ3Ķä,M4&“IRŖĀ€ôKî= ÅgIRې v50Ģšŋ.d•žÍTôĮ**Q§Zō¨zŧ§´ü(¤�@=UXX1)Ē×Ũë7oŗ_9ŗÕŊË]v{Éņ“§Ęo,°ę‡ŧåâįë+ŠĀf“”›—ŗz}Ėęõ-›7:¨˙ÔÉ“SŌ\ąėķųK“ޏ›‚ÕšĨ‘–¯īR5šĢūŧMW~ÅPÃo—¤ÍgKßfĒĀ.I“čČķZ0Y#?ģúk‰F`õ�€zëë߄1¤üƖ-š?úĐø.:\Õøbb˛Ãá¸-˛MŲ–ļ­ lļ´ôŒ&!Á]î*mŸœšļ`éJ‡ÃÚ˛ųÅÄdģŨāŸ’–îú“—ŸŸ“—gŋé%ËęûĮ.unĄŸG]ąąIŊ9Z‡“.'­2Išš˛PƒÛęõ‘5QsZ��Ô[kßŽí¨ĄŅ­ÃCųļ°¨¨MxXtŋ¨äÔÔå_­ģĒq~AÁîũ‡FNKĪHHLjߎmt˙¨Í[w:ސāéS'¯XŗáøÉSN§z÷čæt:ĪĮÅÛ wí=0väĐŧŧü¸ C‚'Ū?ƚ•ũágķjėįÔ ļzoœF(æ¤r Õš…žī+“4~ŪÕ].›Īé[4s˜ö&héwƖGŌ� >›ŋdÅŠ˙žØˇĪCãĮx{yĨgdŽß˛mÛŽŊÅÅ|oqĖj[aá#Ž đĪ´f­Û´uã×;$9;oŅōaŅũĮŽæ(q$Ĩ¤~<w~Zú%IËV­sŨëĄQ`@vNîˇ'ž_ĩvSMž Szr‰ÖÖĶŊ5{ŧüˊĪŌÂoõ—m•]‰õ§­ęĄĪÔw)úž‚;‹šiæĖ™’gĪžmā8��ĀMž˙Íkž.ÁmŪųë+Ünzĩ† š1įŦ[k?cƌ°°0ŽĶ��0 I ��Ā($-���Ŗ´���ŒBŌ��0 I ��Ā($-���Ŗ´���ŒBŌ��0 I ��x†ÉdēŦã&˜Ģš˜HZ��Ô1rVøčä:Åé đ÷ŋŪÎĒøéĐáTĶëVz$-��星NŦëĪÕ&ĶO§NžŪÎŏÔĸS4™´ø‘*KŌ� ŽiŲæĨg§T˛úV›™LĻĀ€€—žŪ.˛ÍõÚ ˆÔöéjXõe;ˇ0{Še ļO׀ČĒöāÎr��@hؿΝũÆĶUk@¤’~ëé"Ē9-���Ŗ´���ŒBŌ��0 I ��Ā($-���Ŗ´���ŒBŌ��0 I ��Ā($-���Ŗ´���ŒBŌ��0Ęå¤åp8<X��@ŊQĢJ“–Ī‰'<W��@ũqâÄ ‹Åĸ˛¤Õ°aÕ+W:NV��Pį9Î˜˜˜† Jō<x°$‹Å’ššzėØą&MššÍf×��PרlļͧOüņĮŠŠŠAAA’L3gÎ,۝“““——Į[���Uãíííīīāz{ÅÜU````` 'Ē��¨‡¸Ë��€QūãŖƒQhîl����IENDŽB`‚�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/scope-list.png������������������������������������������������������0000664�0000000�0000000�00000115432�14156463140�0021450�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��V��ō���ÁuJ9���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨu\•÷ûĮņץ[Q ą0ĀnEDėœ5ģļétn:7÷[¸rõ]wę6][svëŒŲŨ-ˆ…R‚Ōy~ G<ĸāö~><ôÜņš¯û>÷s_÷įsŨŖŅhDDDDDDDDDî˜EQ """""""ō RbEDDDDDDDÄLVŲ˙ÉČČ ::š””222Š2&‘bËŌŌ[[[ʔ)ƒÁh4322¸xņ"vvv888`aĄŽ,""""""""yÉĖĖ$))‰¤¤¤ŦÄJTT888ul""""""""„„„„Ŧ+ÉÉÉØÛÛu<"""""""" {{ûŦÄJzz:ƒĄ¨ãy`XXXčŠ@""""""""æRbEDDDDDDDÄLJŦˆˆˆˆˆˆˆˆ˜I‰3)ą""""""""b&%VDDDDDDDDˤĊˆˆˆˆˆˆˆˆ™”X1“ÕŊjØ`ay¯š1‹13ŖPÛS3)ą""""""""b&%VDDDDDDDDˤĊˆˆˆˆˆˆˆˆ™”X1“+""""""""fRbEDDDDDDDÄLJŦˆˆˆˆˆˆˆˆ˜ÉǍ‘Ÿŗį™ūįlâ“ČĖĖ,ępäf0prt`øcƒ¨XÎ̍ÃÉE=VDDDDDD¤P…œ=Ī?ũÎÕø%U䎍Fâââ™đão„] /ęprQbEDDDDDD Õ´ŗ‹:ųˇ103ųuÚ_EI.JŦˆˆˆˆˆˆHĄŠKH(ęäßČ`āj\\QG‘‹+"""""""ō@0EB.JŦˆˆˆˆˆˆˆˆ˜I‰3)ą""""""""b&%VDDDDDDDDˤĊˆˆˆˆˆˆˆˆ™”X1“UQp/û,K—,aËÖmxyyũkˇy+ÕĒVĄnŨz,X¸°HãšSŽÔ¯S“Ē•+áėäDjZ‘QŅ:zœāĶgŠ:<ŗ¸ģ•æáŽņ­V€§NŗhųJ""Ŗ‹82šDbÅh4˛råß,\¸û÷}ų2�înn´`ĐāÁ4lذˆŖ‘ÂĐĄM ]ÚˇÁÖÖÆ4-==KËthHHčYūœģđˆ¨"Œōθģ•fÜŗŖ°ˇˇ3MĢWģ>UŧųbâäMrĨ„‹3qņ dffu(÷MąOŦÄÆÆōė3cØ˛e Ž4§\šō$$$pōäIfĪžÅœ9ŗyųåWxz˘ĸ÷?mĘ`ieÅŖ>šk^TT“~ü‘žũúRŗf­"ˆŽpF<ČžŊ{ '##ƒ.%¨Yŗ&Íüũąˇˇ7-ûå—_āį׌–-[æųúAÄĪ?MæāÁƒÄÄÄāääD“ĻM3æ™˙l˛söė؜8~<Į4{{ÜŨ=hÕĒ•*U*ĸČDDDDŠ?ƒÁĀãCú͍~\ķ;É_ķҤa=ēunΏgG1é÷éLī•^Ũ:įHĒdŗˇˇã᎝ųyƟEÕą´´¤{įöø5n€‹ŗ�iiiŧøÆûĻeŪzų˙8}æ“˙˜AzzzQ…z_ëĊŅhdėŗĪ°eËē÷čÁ{īŊĢĢkŽeöīßΘ§GķŲgŸâããC‡Ž‹(Zų/Y´h!‡ĸV­Z4nŌKK Â.„ąs×NŽ9ÂĐaÃprrĘsŨ:âîî^č1íÜš“‹ÃčŲķáBo;/ÛļmcØĐĮčŪŊ_|ųĨJ•"ėÂ&MšÄ!ƒY´hžžÕīK,ōk)WēwënzĪžŨģ™6m*Ǐ \šrEŨŨšßį™ˆˆˆüˇô|¨cŽ¤ĘŠP~ž2“Ĥ$�ÖoâĀácŒ}j8OĖgßNârLlQ„{G|ĢVšõŧkCƒŠģ]:Đžu‹œ †/mmm¨á[•Q?ōŸIŽëâĩ˙ŦYÃæÍ›iÔ¨1ß~û]ޤ @ũúõųqŌd Œ“ŗsžmFFFōö[oŅ" 9ž>ÕhÔ°O>9’ũû÷›–ņoæG—.s­ÛŠc*{WbŨÚĩ9Ļ/^ŧˆĘŪ•X°`~ŽéæļØ"€ĒU*–kŨ˜˜|ĒUĨOīŪĻiëÖŽĨ{÷nÔ¨QÆōę+¯påʕ|…˜gßž}:xˆŽ]ģҧO_j׎M5iמ=?>œø„x6lXËõëׯOŲ˛e =ŽK—.z›ˇ3mÚTĒųøđÕ×_ĶĒU+ęÔŠC§Î™:m*T`ĮŽ÷5žâÄÆÚ†JŪŪϟ:uę0ä‘GpvvfįÎEŪ]šßį™ˆˆˆüwxēģŅŽU@Ži—Â#ønōīϤJļË1ąLūcÖVVôęÖé~†ųŸæ×¨>�_˙đ c_y›ą¯ŧ͋ãßËsŲė䊕UąîĪQ(Šõ.X¸�€§Ÿ~ ‹[į€ęÕĢGŊzõōm/::šŪŊæęÕ8†<2„ęÕĢs1ė"ͧOcĀ€ūL™2[2ū<bcc)Y˛$5”åäɓ888˛}ĮvÚļkgj7h[ƒĀĀ–ŦŊ!Ybn;#žÉûīŊËüųķxöŲą9öaŊ¤§§Ķˇ_? ëîņ“OޤL™2<7ö9J•.ÍöíA<9rämYqpæĖ6Ŧ_OxD8™™™xzxŌĻm[ĶP‰ĖĖL6oÚÄáljŊK —ø5kF“&MLm|ųåļ$$$„ĐĐĶŧđ‹„‡‡ßļŨģĩsĮŧŧŧhÔ¨QŽynnn<öØPʔ)sËõo TũüęĢ/ lÉÕ+W8|ä0)))TŦX‰îŨģãääÄ´ŠS9s&Ģ äũų䓤¤¤ÜĶ㐖–FZjjŽéNNNŦZĩ:Į´ÔÔTžųækĖŸĪ•ĢWŠ]Ģ6¯žöšiSRRøōË/Xēd QQQ¸ššŅĢwo^xáEĶ/âēuęđĖ3ĪĖÚĩ˙˜”D`` Ÿ~úĨJ•˛ÆŨNœ8ĨK–pūÂŧʖeøˆ'xėąĮLąėØąƒ/>˙œcĮŽ‘‘‘A­Zĩ÷ōË4kÖŦPŽË­XYYáîîAĖåĶ´„„ÖŦYÍéͧIJJĸ„K š4mŠŸŸŸi™ŧÎņŒŒŒ|×ûęĢ/iŅ"¨¨HŽ;Fff& 4¤y@�K—.åÜšŗØØØĐēuę×Īú™ßš˜×yæîî~Ī?§7l`ãÆy×V­ZŅĒuëģxgDDD¤¸lî—ëÆĶÝī>}×ôúdđiÖmÚĘÁ#ĮšAĐŽŊ´lî‡ŗŗqqņ9ÖíÚą-ul›įļVŦ^ĮōÕë 'nãDpukÕČ{ŪŠĶųŽ_öĮųÚđŸĐŗ�Ô­U÷Û\ûüWzŽë+ī}{÷b0h˙ÂđÍ×_Îô3xíĩ×éŨģcžy†šķæcmeÍG}@`Ë@ŒF#ģv^ŋãžmÛVŦŦŦčÖ­;ļįŧãŧ}{5kÕÂÍÍ-ĮtsÛ<x0ÎÎ.˟7/×>,_ļ[;;zôčĀ÷'‘‘ÁäŸ~fĖ3Ī0hĐ žūújÔ¨AZZÚŨ°{(55•YŗūĸŒ[|8#F<ģ‡;3gūIŌĩlôšÕĢŲēm+-5j4ÍüũYĩj%{÷î5ĩciiÉŪ={pwsãŅGŗ.žķk÷n$''N•*ˇîÆWļlYŦ­­ ÜfA÷sÛļ­”qscėØį=úi.]ēČĻMY›ũ  lŲ˛ÔĒ]‹_z‰’%KŪĶã�ĐŽ]{N:ÅĶOfßž}ˇ-Nõ҇2ë¯ŋxãÍ7™5k6•*UbØĐĄœ=›õ ųíˇŪbÎėŲŧ>~<ĢW¯áåW^aĘSøäãMmX[[1yō$šųûŗs×n–-]ƑÇyīŊëh?ūč#&OžĖĶcÆđ÷ŠŋņÄH>x˙=f͚@bb"OŒķĖgáĸ…Ô¨QƒĮFlėŊī>ƒŗ‹‹éõ’%K8ū<Ŋ{÷ኧFĐĸĢW¯âøņcĻen>Įmll ŧ^PĐ6|}}yņŗh׎=AAAĖüķOxéĨqÔĢ[+–ø3wķyæîî~_>§­ZˇĻUĢVš§+Š"""ō¯RçjžĶOŸfŨĻm\ŠˆĖ1ŨÎÎ Õķfŗ|õ:Vä‘l(Ф ĀĸeĢHJJÎ5=))™EËWæģ~qÛ€‘CĶĢ{į?7û/ô\)Ö{ŗŗ ŽŽŽwŨ–ŅhdŲ˛ĨT¯QOOO"""LķŦŦŦhܸ7n$!!-1 ėØšÃTŗ%h[ÕkÔĀŋš? Ė'11ÂÃà aÔčŅšļin;öööôėŲ“3Ļŗk×.Ķßččh‚‚‚čÚ­...dff˛}Į*UĒdēãœmā AL›6õŽÛŊråĘRSRŠS§Ž)!ÕšsjÕŦ…••)))ėÚŊ‹ĀĻŪHĨJ•ââÅ0ļnŨb*Œj0°ļļĻ}‡@ÖP¯Ûĩ{ˇâããÁ%ķ–fŽ‚î'@éŌehĐ �...T­Z•‹aYÃ2ėėė°°°ĀĘĘ ‡{~�Ė•ØX&NœČß+VāääLĶĻMčĐąŊ{÷6đį¯Y1ūõņtīž•üčãIHLāĖ™38;;3ū<^}<=zô ’ˇ7§Nâˇß~ã•W_ÅÆ&Ģ|íÚĩéw­ˇV•ĒUōČ#L˜0ÄÄD222˜>}OCßžYËxWŽĖĄƒ™ôã 8°° ÄĮĮŅĢwoĒUķā˙ũnŨģckk[(Į%ۍ‰ĻøøxvíÜItt4;_˙cĶŠS' ƒi˜céŌĨŲĩs'!!!T¯žu7ãæsŧ ëxz–ÅĮĮ€:uę°bųrĘW(O… ˛Žg:lŪŧ™ččhÜÜÜō=o>Īîįį4;’ŨsEI‘ג.šĻ-]ų+˙ؐõbIÖ?mũņŽXšžUņo’õ}ÃÕĩDžmf'˛{ze"<2Š/'ūÄÃŨ:™ę­œaᲂ?nš8íPāQ5|ĢŌ§Gf/Xz#*Å:ąbaaqËģāũûõc׎Ü5öí?@‰š?TQQQÄÄÄC3ŋώÜfXØ|||Š^Ŗ;o¨´Ö­Ûāį׌ôôtöėŲC`` AAÛ�hŲ2÷ŨT777ŗÛ8h3fLgîœ9ĻÄƊ+ČČČ ˙�„‡‡“œ”dēHēQÕĒyg{‹‹ŌĨKSēti.\@ãÆM¨RĨ žžžTōö˛† efdRųĻž!•*yŗoī>RSSMÛåʗ+pģ…Ĩ°†Y]ētŠĀûéqSÁ[{;{’’ķî}rŋŽÃ觟fØãŗeķf6oŪĖæÍ›xcüëLœđSĻNÅĮĮ—ãĮ“’œLŨ†ëŲØØđ㏓�Ø˛e ééé4¸é)BõęÖ#)1‘ĐĐĶĻ"¸ĩëä,dæëãKJr2—.^$2*ŠÔÔTs>mÉŋš?ŗfũEBB•+WĄjÕĒ<˙ĪņčŖŅ˛U+j׎ŋŋĄ—đđp>úđÃĶėėíčŪŖUnøl򯯡uËBCCILJÄh4’””DŠŌĨrŦ{ã9~'ë•.uũuvâ¨té2šĻĨ$'ßŅš˜íNÖ)ŒĪ鍉%UDDDūũ.…G\OĒÜ „‹ /=Û5Į4ŖŅxËvnL<e˛’+?ũqwO˙)NûSPFŖ‘“ÁĄEÆ=SŦ+ž„††“ĢpmĮŽņõõ5ŊŪ˛eŗiė^âŗÆÛÕĒ]›W^~å–Ëšģ{�Čŋ˙Nbb"W¯^%$$„W^y•ōåËãååÅöíAY ‘mAØ;8Đ´iŪÉsÛŠ[ˇ.ĩëÔaŲ˛eüīŨwąŗŗcų˛exyyŅĸEVæäk]æíėr?˛ËÎÎÃMՙī5ƒÁpË_hŲ ˛ė„„……C‡ cÛÖ­ėŨģ‡uk×âR…6mÚR¯^=RSS€ŦŠ7îGvûņņņĻēvļ×÷?ŋvī–ŗŗ3 æōåģn ¸Ŗũŧ“áE÷ú8ÜČŪŪž;šzemŨē•1O?ÍG~ČīLáęĩBĘöyޟũŲ˝™ÍáZOĩøø„ëĶrnûÚëĢqqÄĮÅđȐÁ9*“¯{‘xWŽĖŦŲs˜<yũ5“Ī>û///^7Ž>}úšw�ōPĒT)zŨP`ÚÚښRĨJaiiiš–‘‘ÁŸ3f™™IįΝ)]Ļ Ėž6léF7žãw˛^^Ŋ?nÕ#äNÎEsÖ)ŦĪŠ*"""˙^1ąWņpŋ~(5-īš‹–¯"$ô,O=>Ä4-6öęmÛ.Ę„ŗŗ=ģt N­8ØÛq*$”ķa—�(īåIĩ*Ū$&%sčČ1˙Ŋ&W­˜ŧ—„JôåJ—ē}o~ŖŅČôŲ Ø{āĐ}Šęū+։•&MšĘúõëčŨģOŽyO•ãõØąĪŪ6ąâxÃŖo[ˇi“īļ[ļ䗟fĪž=DEEb0hz­0dã&MLõQļoÂŋYŗ\wr ŖũđÎ;oŗví?4iŌ”íۃxzĖSrÂöZB%99÷8Ŋ„„„ÛfmīGG"ÂÃķœ›U°ĶÉéú“›M䑑‘mcņĸE”)S›Ŧ;éŊzõÎķŅÄ..šģ ¤]//¯ģŲElmm)ëY–ũöĶ"00Ī‹ÔŖG`iiY G ßÍ~æį^€ˆˆs Õ  sįÎŦ_ŸõËžTéŌ�ˇü‘ũ4¯›įg'\\\ޟ3 9–‰ĪJĻ”pq!õZ!Ũ¯žūšÕs+{mŸK—.Íøņo0~üœ<y‚_~ū…—^|_ęÖ­[€=ΟĩĩužĮøÂ… DDDđØĐĄ9 ļ&&%RŌĩdĄ¯—sÎÅâú9‘Ķą“Á9+ʕĨiŖúėŪw0×H†ēĩ¯ß3šoqŪ gg'^; ג×GUøVĢ‚oĩœ=~đoÚßjUøbâOJŽī~úMŽĀ_~đĻé˙ŲI•ģ÷ŨīĐîĢb]ŧvā A�Løîģģ.¸éææ†ĢĢ+ÁÁÁy>Š8::į˜6ŋfͰąĩe׎lÛē __Ķ×ĻMũØˇgĪžåôéĶ´juë;¨wĶÎÃŊzagoĪŌĨKYļt)™™™ôģV;ĀŨŨΟ?ŸkģG-øÁ)$UĢVåōå˄į˜n4Ų˛y Î.ÎĻĮ ĮÄÄä(´éææF׎Ũ0XˆŒŒÄĶĶK+K(SόéĮŪŪ‡[ŪuΝŨÂā×ŦW¯\eSO)‰ˆˆ`Ų˛eœ8qĸ@m™ģŸˇ’Lģ×Į!22’͙<yRž1„„„āvíBģJ•*ØŲÛŗcûvĶ2™™™ 0€yķæRŗfMŦŦŦØŊkWŽvöėŨƒŗŗ ŪŪ•MĶļmĪąĖ°wp Ŧ—5kÖÄÆÖ–č¨hĒVĢfú)éęJŠŌĨąĩĩåėŲŗŦ^ĩĘ´ž/|ø!–––?~üŽËČ¸VũÆ^8įΝ#6&öļIQs×ËĪœ‹ŲÛ)ΟSyđlڙ#b0:¨/ß~ō?F>6Č4ŨŲɑæM¯?ĄsßÁÃÅ6ŅķĄŽ9’*ų)åZ’ž]:äŋ`É>ÎUŧ+Yß SSSsüdû¯$U Ø÷XiBīŪ}X°`>Æå̝ŋĻ|ųō9–IIIaæŸ˛f͝nŲs [ˇîLŸ>Ÿ~šĖË7 ŠŽŽæĄ.ŠW¯ŋüú5”ĻIãÆėŨŗ‡ĐĐPÚ´šūX+?ŋϤϤđÛ¯ŋ˜››—ģi§D‰tîܙ•+WræĖš4iŠwåë™VVV4jԘ  mėßŋ?GÛĸ(\[¯^=öíŨËÜysņoægYOŲģg/]¤˙ūĻŪ6W¯\aîÜš´k× ‡Ä`0Pž|ylmmiÔ°7nĀÁÁ///Ž^šÂĒUĢpvqaĐ AyƐ_ģ…ĄnŨēœ9ʖ-[¸xéĩk×ÆÆÆšK/ąs×NÜĘ¸ŅĄCĮĩeî~æŲ–á—štéąąąĖ›wīŽƒ››OŒÉÄ ˆŒˆ¤C‡”(Y’ČČæÍËîŨģøn køÔĀųūûīņ,뉏/ΘÁƒøėŗĪpuuĨ˙üøãx{{SĢvm‚‚ļ1uęTF•ãâ<""œožųšŪŊû|ęͧM§G÷ØŲŲaggĮāAƒų曯q-åJũú ¸páīŋ˙e==ųõˇß ãé§GķęĢ¯Ņž}{�-^„……nĒņr¯š{x`ieÉΝ;hŲ˛‘Ŧ]ģ–ĘUĒp9ú2 yî6wŊüô\ŧņ<sqq)ļŸSyđ\ `íÆ­th˜k^É Ûöéņé˙‰‰I,\ļ*×ōÅEŨZų÷bŋY[<’š8Øąg?í[ˇā…1#MĶŌŌŌxņ÷s,÷_JĒ@1OŦ�|üÉ'1˛pÁÚˇk‹ŸŸ•+W!33“°° ėØą“„„xj׊×_~izI^ūīųįYģö~øū{"#"ņk֌ˆđpf˘Nll,ÞcųĀĀ–Lœ8‘ÄÄüšų™ĻûúV§dÉ’Ė™3///ĒVĢvÛ}¸›vÄĸ… 9rø0Ÿ|úYŽųŖFbûö ž1œūāZŌ•;ļ“””„ŗŗųÃHĖaiiÉ#>ʖ͛9zô([ˇmÅŌŌ’ōå+0tč°Ev+y{ĶŖGOļąaÃz,,,pws§˙ū”ž6t¤c§NØŲŲņĪ?kˆĮÉÉ ___ÚļmwË ŌnačŪŊ•+WaĪîŨŦZĩ’ĖĖL\KēؒĻM›ŪQ=sö3/~~MY´hSĻüAŋžũîųqxíĩ×ņņņaöŦŲŧúę+\šrgggęÖ­ËSĻæx<îk¯ŋŽÁ`āã>">!š5jōûī˜Š•ūīŨwqträ­ˇŪ$::š˛eË2öŲą<=fLŽm4ˆ+ąWčŨëa’’“éĐĄīž÷žiū›oŊ…K >ųøc"##qsËJr{ųe�üũũųėķĪųå—_øę믰˛´Â×חI“&į(*{?8::ŌŗįÃŦ]ûĀĢŦ=zö$>.Žyķį1}Ú´<Ÿ6fîzQsņæķŦ8NEDDäÁŗxÅjJš–¤Qũœ-đ*ëI)ג4Ŧ[›& ŗj˛%'§đĶ”?š[ĄˆãM5 ÂÉņÎך_–üŊ�ŋFõ¯×HŧŠļįŽŊ8tô8ģ÷ŧßáƒŅh4†††âééY¸ [XæŋĐØļmŗgĪb×ΝDFEaia‰‡‡; 4¤k׎´īĐ!WąÖącŸeé’%lŲēÍ4f?""‚ ß}ĮÚĩ˙ƒƒ#~Íüxæ™gM˛ÍvđāAzöčĀŽģLxräŦYŗ†ņɧŸŪv›æ´sŖÍšÃΝģprrĘ5ɒÅ|˙ũ÷œ ÁÉɉ:2ū7čúP\K•béŌe>Î"ÅUŖ† >bcĮ>WÔĄˆˆˆˆH>ÆŊų~ū ŨFĮļ-éÜŽ5ļļyH8}æ3æ, <"ęŽļs¯}ķņ;9`Péŧđúģ÷(ĸ‡/>xëŽÖ7ffR$Y˜ÄĘUXXmZˇbĀ€|pĶã[EūK”XypÜmb˛z{4¨[‹*Ūqvv"--Č¨Ë:zœS!Ąwä}ĐķĄŽ´kPāäJFFk7neņŠÕ÷8˛[qKŦûĄ@˙u~ø�#FŒ(âHDDDDDDÄDļlßŖíģō_¸˜Zŧbĩ’$˙JŦCĄ§OŗiĶ&V¯^ÅĻM›øŋįŸŋīõDŠ›={˙…¯DDDDDäÁĸÄJ1tėø1Ūyįm\]]yųåWrđ‘âA‰•b¨K—‡9ZÔaˆˆˆˆˆˆˆH>,Š:�‘•+""""""""fRbEDDDDDDDÄLJŦˆˆˆˆˆˆˆČÁ`(ęrSbEDDDDDD •ŗŖ#E†üۍ¸8ģuš(ą"""""""…ęąGĪŽō`ŗ0đÄĐAEE.JŦˆˆˆˆˆˆHĄĒRą<cžŽŗŖ#%X¤89:ōŖGâåéQÔĄäb0ÆĐĐP<== ˇa ËBmODDDDDDDän33 ĩ=õX1“+""""""""fRbEDDDDDDDÄLJŦˆˆˆˆˆˆˆˆ˜I‰3)ą""""""""b&%VDDDDDDDDˤĊˆˆˆˆˆˆˆˆ™ŦîUÃÆĖŒ{Õ´ˆˆˆˆˆˆˆHą +""""""""fRbEDDDDDDDÄLJŦˆˆˆˆˆˆˆˆ˜I‰3)ą""""""""b&%VDDDDDDDDˤĊˆˆˆˆˆˆˆˆ™”X1“+""""""""fRbEDDDDDDDÄLJŦˆˆˆˆˆˆˆˆ˜É*û?gΜ)Ę8DDDDDDDD8ĻÄJõęՋ2‘ކ‰ˆˆˆˆˆˆˆ˜I‰3)ą""""""""b&%VDDDDDDDDˤĊˆˆˆˆˆˆˆˆ™”X1“+ė͔×�� �IDAT""""""""fRbEDDDDDDDÄLJŦˆˆˆˆˆˆˆˆ˜I‰3)ą""""""""b&%VDDDDDDDDˤĊˆˆˆˆˆˆˆˆ™”X1“+""""""""fRbEDDDDDDDÄLJŦˆˆˆˆˆˆˆˆ˜I‰3)ą""""""""b&Ģ{ÕđésīUĶ"""""r‡*W([Ô!ˆˆü+ŨŗÄŠ~q‹ˆˆˆˆˆˆČŋ†‰ˆˆˆˆˆˆˆ˜I‰3)ą""""""""b&%VDDDDDDDDˤĊˆˆˆˆˆˆˆˆ™”X1“+""""""""fRbEDDDDDDDÄLJŦˆˆˆˆˆˆˆˆ˜I‰3)ą""""""""b&Ģĸ@ 5-ķ#HJN!5-Ŋ¨Ãų×jXۇāĐŗü6í/âÉĖĖ,搊 œņØ ĒzW,Đ:á‘QŦß´ä”Œ™Æ{áÛŊ˙`Q‡ "`0pvtāÉáâ]Ą\Q‡#""d0ÆĐĐPŧŊŊ‹:ųJMKįØŠ3xē—Ϥ‹6ÖĘ÷‰Ü {ŸÄÅŅ–o~øĨ¨C)ūŒF0x~ĖČ|“+á‘QŦXĩî>f%VD F#ƒ¯<˙4åŊ<‹:)� ’"wūbžîĨq/]RI‘{ė׊uƒŒÆ¯uˇŨ‡€Dä?Ã`ĀhĖdōīĶ‹:) %V¤ČÅ'$QÚÕĨ¨ÃųOˆ‹/ę ‰‰ų.–œœ|‚‘˙ƒ+WãŠ: ) %V¤ČedfbiĄSQDŠÕ ‘ĸb4ŋzM""’7]͊ˆˆˆˆˆˆˆ˜I‰3ËÄJZZķæ/äÉŅĪĐéĄÔoÜ ŸšuiØ´9Ŋû⋯žáRxxQ‡YdæÎ_@ßZ<:l„iÚŦŲsŠâ[‹a#ž,ÂČDDDDDDDū[Š]b%*:š>ũņōkãųgí:ŌĶĶŠWˇM›4ÆĩdIöī?Ā“~ĸS—îl Ú^ÔáūëüīŊ¨ßȝ¨Ãy ģgÛžõÎģ>r”š5kđõŸáëS-ĮüĶŧ:ūMvīŲË˙Ŋø2›×¯ÁÆÆĻˆĸ->úõíM¯‡{`q—E`÷<TH‰ˆˆˆˆˆˆüûĢ+IIIŦ^ŗ€Ū}'WR J•Ęüöķ$œšrå ›ļlŊßaK–––ØÚÚbmmmviii=r´Ŗųw+V‰•ĢWãLļŦPĄü-—svvfÕßK9ŧ7íÛļÉ5ßūũŒ}ū%ü[ã[Ģū­yũ͡ ČŗŊ={÷ņės/˜–¯ßȏŪũņķ¯ŋ“œœœcŲŠĶfPŎã^}ääd>ūė Zļí@ÚõiŌŦĪūߋ„ž9“įvŽ=ÆK/ŋF‹Ví¨^ģ> ›6gĐ#C™7á]?RīV5VN3îÕ×M1ÖiЄvâí˙ŊĪéĶĄĻå^xéĒ׎OjZqņņTņ­EßZlظéŽâų7+V‰ww7ėíí˜=gŪm—õôđĀĘ*÷HĻyķŌoā#,_ņ7åŧŧhĶēövö˚=—Ž]ēqâäŠËΘųũaųß+)įåEŸ^Ķŧš?gĪžåãO?gĐ#CIHH4-Ÿ=ė(..ž'G?ÃĖ™ŗ¨XĄmڴƈ‘å+ūĻW߁œ͙\Yļ|Ŋú dÁĸŸ{¸Ķ§WO5Ŧ΁ƒ‡xųĩņüߋãLIĨÂrāā!zöîĪü‹pqqáĄ.éÔĄ=–V–L˙s&ŊûâčŅc�´oזũúfíŖĩ5Ŗž|‚QO>AÅ  5&‘›Z5|øúŖˇųúŖˇŠUç¨Ã‘ûŦXÕX1 <öČ~úåWžøęöí?ĀāhŪ [[Û|×?sö,ožķ.–––üņëO4÷o@ff&ú9ŋū>…įž‘ŋ—- 88„wß˙€IßG§ŽLm]Ŋz•CãĀÁC|;a"ã_{�+ëŦCļqĶf|ĒUeĶú5”(Q€¸øx†Éūũøôķ/™ôũw�\ cÜĢãIKKãËĪ>Ąw¯ž9b6üI–.[A€ŋ?ƒöŋÛÃhōãäŸHNN晧GņŌ ˙—cŪÄ&ņÕ7ß1ņ‡I|?áēw{ŸjĖž;[[[^}ųĨB‹ã^:ƚõ›9{!Œ„„DėlmŠZšÚĩÂģâ­{=ŨOo~đ9ūMŌŊK‡ü)ÆĒUņĻCë@*”÷ÂÉҁääN>ÃĒĩ9sî|Q‡ĀûoŒcûŽŊ,]ųOQ‡rĮœœhÛ2€´´tū^ŗūŽįß OBŊÚ5rLËĖĖ$úr,‡Žcųęu$'§Ü—XnôÉ˙^cŨĻmŦügÃ}ßļÜ[˙7zvļļü2u&Ņ1ąw<ŋ¨xW(oēáį]Ą<GŽ,âˆDDä~*V=V�^zá9<€5˙Ŧ剧FS¯‘ũáŗ/žbķ–m¤ĨĨåšîœšķIIIĄgnϤ €……/>˙å˗ Â�˜1sééétęØ!GRĀÅÅŔŒ˜=w>ééé9槤¤đö›ãMI�g''Æ][gŨú $&fõt™:ũORRRčŌšcޤ @ĨŠyõ•Ŧ$ÆÔŪŲÁĘĮųķ�hØ AŽyŖž|‚Šŋ˙jÚöƒčdđi&üôö<6°ãÆ>Å#zĪw“įâ-†~ũ—lÜēiŗæßōĩHAųTņæŲ'‡‘˜Č´Yķø|ÂdfĖY€‹ŗ#Īާ‡{Q‡ø@sw/C÷Î(]ĘOˇ;ž/EE_æģIŋ™~&ũ6 ]{hŪ´1#t_cš•–~<: wQ‡!… Zoʗ+˸įFSĩrĨ;ž/""RŠ]bÅÚښ÷ß}›K1lčŖ”/_Ž´´4öėŨĮ¤Ÿ~ačđ'hâČ'Ÿ}™cˆĀÚõYwŽšûįj×ŪŪžkWŗ|ÉBʕķ`ĮÎ]�´mŨ*ĪXšûgĩsõęU‚CNį˜įââB“ƍr­Ķ´Ic Ģėųk œmۂ�hßļmžÛiŨ2ƒÁĀącĮšråJžË˜ŖZÕĒ�|;abŽ!PÖÖÖļhū@õŲ¸u;e=Ü:¨/5}ĢQĄœõj×ä™'‡Qē”+Á!y×ēų/9w>ėļ¯E Ēe@3.…G2mÖ|Žæü…‹8|Œ‰?O!úr ÕtcļjUŊéŌž5vvy÷ĖĖoūŊ–’’ĘɐPĶĪҧXĩv#ķ–Ŧ ēOUĒxW,’¸nTņÚßuų÷prtāŲ§ĮŋiîīZ™/""r?ĢĄ@7ĒîëÃ;oŽį7Įs)<œ;wŗs×n6lÚÄšsįųé—_Yˇ~sg˙‰ŗ“�ŽõĐ(ëéY mœŋĩ|ųōåōœīčč€ĢkIbbb ģHußëcfŊ+åũEŌÆÆGG‰ŠŠÂ×§ši; /aÛöíyŽgmeEjZĄĄg¨_ŋ^âĪĪ믞Ėū9xč0]ēõħZ5üiŲĸ-ü 4ŧĒ8ËČČ =#=×t;[[ŪxéŲĶ233ų{Ízvī?Čå˜+¸–tĄmË�Z6÷3-ŸĀ‚ĨsâT ‰I¸–tĄU€?m¯'ę^ûß'tnߚc'NqâTŊķ*ÖÖÖ,_ĩ–ģ÷‘””Lųrey¸k§ VŦYĪĻ­ÛIJJÆˇZØg'Į<÷íåˇ>¤SģV„GFqøčqRRRŠá[!ũ{áäčP xŋô§BBØą{Ę{™+;vīãÕ៯ËĶ#ßã’×>ŋ÷éˇtnߚ˜ØXvī;HJJ*UĢTbHŋ^¸8;ŨÉÛ(++K,­,sMOIIåŖ/'æ˜faaA—ö­iÔ .Ĩ\K{…ĩ›ļ˛yÛNĶ2NŽŽôîŪ™ę>Uqp°'6ö ļlgÖ Ķ2ŋķ+˙Ų@MßjøVĢĖëī}JZZ:];ļůqėí8v‰…ËVrúĖ9Ķz™F#]:´ĄUs?ėíí8qę4ĶfÍ'>!á™ģ`€& ëQ§fuķæąĐkĮŧä =7 ōŪW­\‰]:āUÖ ƒ.^bņŠÕŸÎJ†ųÁ›,_ĩŽ6n1­3¤ßÔķ*ËįßMĘĮ˙Aĩ*Ū�4kԐOžų;[ÛÛnãfŸŋ÷+×nĀÃŨ:5}ąĩąáč‰`ūœŗ„kŊO ˛oyŗ7•*Čļ ōų(áâĖāžã[­2‰IÉŦÛ´{;;ę׭Ň_L(pĖ7ęÚą-uĖû&ЊÕëXžz]žķî+KKéß O7-[•ĢĐ~ķEDDî—b›X𑧇=ēwĨG÷ŽFVŽZøW_įäŠSLüa¯ŋ2ČžäYÔ6/ÉII�ØŲŲŨr[›ŦäCrJΧ988Ür'''‰‹‹ ņZΚ-[ˇåS\|á}ņwwwcÉÂy˘9“ų qâä)Nž:ŔŠĶqvr≏ķė˜ŅXXģŽKR§fufÎ[Ė/Ķūĸc›@*–/‡Á`ČsŲKW˛eû.õéAåJ9~2˜š‹—ceiIsŋŦ^F3f/ <2’Į‡ôĮÅŲ™āĐ3˜ģˆRŽ%¨Wģ&uqšeûNęÖĒA—m°ĩąaŪâėؐūŊēSĻt)6l âû_Ļ2ūÅg(]Ę€=ûRçŖG<FLL,Ķį,`ųĒĩ ėĶ#Īx-,-Xŗ~3}ztá‘ūŊˆˆŠfâO0oņr† îW xŸz|&˙[™ŌôīÕ ƒž˙yĒéĩƒŊ]ŽK^ûliiÁšõ›čŪš=ī‰Ģqņ|ūŨ$VŦYĮĀŪyī“<Ø9Îā~ķÄcƒXŗ~g·Ũō"ĻWˇÎ´đoÂŦųK =K ŸĒô}¸+élÛš€GôÆÃŊ ŋΘÍÕ¸xĒVŽÄāž=‰‰åĀáŦĸÚ´đoÂĄ#ĮYąf=ŠŠiôéņÔaö‚eDE_Ļu‹f<3r5ŅToĄQũ:;ĖŋM§”k ЛnÚ1kÁ’ûs° ĀĘڊÖ-üŠPŽŦYķ‹wˇŌ�ÄÄ^¯s‘ß{ocmÍčᏲkßfÎ[„­Z4cĖČĄŧųÁį$%%ßjsˇ4ųŒ}j8‘QŅĖY¸Œôôt>xķå;ÚFFfÛ´dŪ’ü9g!neJņė“Ķ÷ᇘ:s^ö ō>gÍŲVA>ƒû=Ly¯˛LūãOâââéņP<ÜÜrÜp(HĖ7ĘNœÜœ\)Š¤ĘÚˇj§›ŋ˙9;ßų))Š÷<žZ5|đžéé•>U+įø×›Ö =w^uWDDūňÄʍ ]:w$8$„/ŋū–íÛw˜æŲ;8zåJ‡Ķd/ģ/rIסėxS"%5õÖ¸ããŗ*%KfŨÅspt 5ö ŋũ<‰6ˇvt¯8::đÔČ'xjä\ gëÖ VŦ\Åēõøæģ‰ÄÆ^áí7_ŋ¯1–€fMHHLbåÚ ė?x;[[ĒTŽHŊÚ5iÚ¨>6ÖÖ�$'§°iÛ:ĩm‰_ãŦz3neJqöBĢÖm2%úöėŠ……Á” qw+ÍÆ­Û9zâ”)ą`cmÃÃ];eĩ’ÂÖģéÕ­3ęײžėϤĻuŲԖŊũ{u by/ö:JčŲÛû,_Ž,͚4Āí Íũø{Íz÷MÅÆÆ&ßxííė°°°ĀĘĘŌÔËåÆ×=.7īs6Ow7Sė’%\¨U×ŗį4ÔčßjëŽŨ88ØĶš}kÔ­Err ÁĄg8pø;÷ė7Õž˛ŗĩĨe€Ģ×ndĮî}�lŽžL…ō^tlÛĘt17wņrŒ™™ĻdHdT4­ü¨á[Ítáh4IMMcŅōU�ØÚÚĐŦ1 —ŽdīC�˜ˇ[[[ʔ)mj+99™š‹–pîBõëÔ*6ÅŦŗunß ˇŌĨ͞ŋŨ˜€ˇ´´¤by/zwˆ°KáĻŪByī]]K`ggËÎ=û ˆ`îĸåėŲˆôô ŗbKNN!33“ôôtņp/cÖ6΅]4ÅÍæ téІŋæ.ÆÂÂĸ@įõÍįŦ9ÛJMKË÷ķáėäH­ę>ĖY¸Œã'ƒøãĪ9ŧ?~ąW¯˙,ŪėæäJQ'U˛ÕŽéËØ§Īwū~ēįą<9tđmoâUĢâmęE•-==Æŋw#‘ĸRŦ+[ˇągī>š6iL3ŋώ]6ģ6Č嘘Ļ•įā•+Ļĸ­7KNN&##[[ŦŦŦL˟;whžkų¸¸8S’ĻBųœ_Ę/„å}™ššjĒũRæÚ—âJ•*{ā–ëÜ/žôéũ0}z?Ėú ņähfĖü‹×_‡õĩ$ăĻcۖ´ôįøÉ`ŽŸ æØ‰`ūšˇ˜ŋ×ŦᙑCņôpį|ØE222¨á[-Įē>UŊŲļc7))ŠØÚÚ`kkÃęu9qę4ņ F“p/“ķâĻrĨëui.^Š ==JŽ'ŗ˛´ĖUĐąōMCĮœœI>{û'iÜ|§ēŦ‡;éééÄ^‰ÃŨ­tãŊ•‚—›÷9›W؜CîėíHŧÖ LūÖŦ߈-ÛŠáS…ę>UŠáSÁ}{ōP‡6|˙ķ.EDRÎË+KKŽžČY×édđiücccCjj*)ŠŠtjÛŸĒ•qrtÄÂ`ĀÁÁžČ¨čëŨ8ħŦ‡ÖVVœ9wũw|FFŋNû+Į:!ĄįrŧŽ‹OĀģâƒ=ôą(•ķōäÛOū—cšŅhäČņ“Ėœģ(ĮrųŊ÷‘Ņ„GFņøūlÚ'NqūÂEͰÅÂ`î6nŽAuņRÖVV”(ႋŗSÎkČyΚŗ­Č¨č|?neJc0 =kj#%%•c'ƒMō úYĖˍ‰”âT)îŠUbeúŸ3ų{åję×ĢËŦ?§accsËeˇe3öŠVÕ4ÍßŋfÅĘU úhŽå322hŅē11ą,œ7›zuëāßĖƒ‡ŗvŨzk6n˛†Ôx{į,ĖÁŠā`SØlģ÷랆U¸ÖM4Āŋû÷`ÉŌåĻ'Ũ(55•e+ū&0 �7ˇ2ˇÜį;{…u6`eiEî7wH…V-M_Ē._ŽÁã†'z<hc”mŦ­Š[Ģuke=ôDđi~™:“KWōô‘|mˆØw“ᯁB™×öķj\<ĨŦJđũĪSČĖˤßÃ]ņpwŸū˜‘k{ö7ĖN$ØØÜ>1e{ĶüŦKˇ?Î7×ĀÉn#)9‰ŒŒŒĮ{+9.nļĨ€œûœÍÆ:¯_Öš#w.--ƒGŽsđČq ĢËûČĄƒčŨŊ ?ū6ÍT`õšŅ#ā†ß%ŲÃô\œ¸Ë3#‡biaÁÜEË Œ"#3“QÉĩŊääë= ˛†mŪŽĮ`^ķšÅ(Á"ŗęŸ´ēÍPŸüæßO‘ŅL™9Įôēe@3j×đeĘĖš9z|äŊŠžĖ7?üB‡6- đkB·:{…%¯aįžũ…¯Ņh4k)77Ųį‘ƒŊ]÷ ržŗælËÂÂ"ßĪGv/ÚėßãŲ˛k´@Áߏ[)n •ÃĮNđĮŒ9|ūūˇ?ü<ufžC˛{М ådp·„“GŌ‹ˆČŊQŦ+cĮ<Í?k×ŗ˙ĀA†Å;oŊ¯OÎģé/]bÚô?™5{.�Ç 5Í2h�L™ÆŽģ˜=wúõ˛ —~ũíbbbŠTą"uëÔÎZ~đ@ĻLÎ?ëÖŗzÍ?tėĐŪÔVdd_|õ �},Wí+++Ūûāc~™üƒ)”’’¡ž sĮĻéC ä÷kqũüËo<9r„Š´´4ŪūßûĖž;íÛņĶ9‹@š+%%…W_kkk<==LO+Ęļ|ÅJRSS)Sό)™ãä˜UH5>!˜˜X\]KJ,÷ĘÕ¸xlmlLŊ*˛ųV­Lƒ:ĩ8|ė5 `ØāžxåQØØĩ¤ ĄgĪv)œįŸ~"ĮãããLCmō’}Ėn.NXRnj3{ööfĮ{Ŗ‚‘lÎÎN¤¤¤æJZœ >ÍūƒG¨Uø~žN9—°‹ášÚ‰Ŋ‚wÅō”+ëÉ7?ūšŖ˜¨“Ŗ#Ņ—cr­“->>ëĸą¨žŽS˜ŌŌŌųgÃfš6ĒOíkĮîNæßOiiiœŊĄ‡Å‚%SˇVuzuëœŖĮJAŪ{€ø„D.[ÉÂe+ņtwŖ]ë ԗKᑜģF^šũ;íU™ß6ōbwS2;û<KLJ2 ûČoß ęvÛ*Čį#==ĢŽŠÍMĮÅŅÁŪô˙‚ž‚u›ļ˛`éĘ[ŪøÉo~a;rėdŽz)]Á”X9|ēØ%ĻDDäŪ*V‰•š5kđÄoxáĨWØ´.ŨzâááNYĪŦ;v‘Q‘„…]Äh4bccÃ[oŧF`‹�Ķú•*VäŊ˙ŊÍëoŧÅkãßâ)Ķ([֓“§‚9ūööö|ūéGĻ$IĨŠyīŨˇyũˇ5f,~M›Pš˛7Q‘QėØš‹¸øxÚˇmÃȏįŠ5 š?qqq´nß‰Æ âėâĖÖmAœ;wžRŽŽŒ{ņy͞åĘyņų'ō¸Wųøŗ/X¸d)ĩjÖ 1!‘ģv}-öˇ íXzx¸ķúĢãx˙ÃO8ä1ęÖŠˇw% 8ĘÁC‡ą°°ā7_7÷ôô L™2DEEŅŗO?ĒVŠB§Ží2h`ĄÅUXâââyëÃ/čÔļ%Ũ:ˇĪ1Īh4…‹ŗ3�åĘfu‡Ž‹OĀÃũz ø„ VVVĻ/Š7~)=}æŅ1ąTŦpëÚ ne°ļļæTH¨é)@FŖ‘o'ũF€_cSísœŧŠÛú™ķ°ąļĻdÉ\ž6îūNãŊQAŽ‹€ŗ“#ŧ1ŽUk7˛lÕÚ\ķŨŨʘŠu_ģDzz:NNŽ„GF™–qrt Ķh$=#ëkįVö°I�īŠ(]Ę5Į0Ÿ›EDF‘š–Fĩ*ŪĻჁįF gÛÎ=Ļ: #ėÜŊŸØØ+4÷kœģx~ķ‹HbR‹WŦaPŸėØŊĪtņ_÷ž´kIŧĘzrđHVK‘ü5o1Í7 Ŧ§;į.„‘œ’‚Ŋ}Îĸō^e=ō­Á’ũˇŊ ÛČËÍ51*–/Gjj*1ąW‰‹KČwßîÄíļUĒdÖMÛ}>"Ž ĒTą<Ã#€ŦDÕ}ĒråjP°÷Ŗ¸ËČČā¯ųKēE=˜ü拈ˆÜ/ÅîĘŠ}ģļŦ_ģŠŋfÍaĶæ-‡pøČŒF#ÎÎN4nԐæūÍĐŋ/åŧŧr­?°_|ĒUåį_c÷îŊœ Ądɒôę؃gĮŒĻJ•Ę9–Đ¯/ž>Õøų—ßŲĩg{öîÃŪŪžš5kСO/úôzKË܏ÍĖĖdÚŋōíÄXšr5—.]ÂÉɉŨģōĘK/RŽ\ÎØēu}ˆjÕĒņĶ/ŋ˛}ûN/Y†ĨĨ%•*V`@˙žŒ1ÜTėļ° 6_fΚÃū8y*˜ŒŒ ÜʔĄG÷ތx|õëÕ5-oiiÉWŸĘ;īŊĪųķHINĄëC 5ĻÂâėėDģVüũĪŽÆÅSˇv ėíšOĐŽ=„„žeø#YÃģėėliá߄eĢÖâäč@Ĩ åšËŧÅ+(Y…Ņ#Í‹neÅúÍA<Ôą-a—ÂYŧb55|ĢE\|BžFļŗŗĨyĶFŦ\ģ‘’%\đôpgKĐNΞãŅŊīj¯\ŊĘōUkņkܐđˆH6mÛAãuąļ˛*pŧvœŋp‘ķaq-Y"×ëüŽ‹dÕ(Yģq+ÛˇÆÅŲ‰GŽ‘˜˜„‹‹3ūMRÅģ"ŋ_낟œ’–íģčÖŠ ‰œ9{žRŽ%éÛŗ+ąWŽ2é÷é\¸x‰´ôtZúŗbõ:ŧĘzĐķĄŽ=q ˇ289:æųhää”ļíØCįv­‰Ŋr•Ká‘ú7Ąb…r˘ŗā~–Bs28”+qņ´kgoœüæ…m;vĶĸYc÷}˜ŋūžŒŒŒŊ÷ŽŽ%9t‹–¯âĐŅãĐ´Q}ŒFŖ)Yvö|õęÔdíĻ­¤¤¤ŌŽU�ŽĻ„A^“’(_Ž,åŧ<)YĸDžÛČK gēvlËöŨûđtwŖe€ģ÷$==ôôô|÷íNÜn[ų|D_Žá܅0:ˇkÍĨđH’’’čŲĩW¯%8Ą`ŸÅâ,>!‘_ĻÎŧå#˛ķ›/""r?ģÄ @)WWƌ~Š1ŖŸ2kũF đãÄī ŧ|ƒúõų~Â7w´ĖĖLxũ•qĻĮ=į§ē¯_~öÉm'/ũúôĻ_Ÿœíôcā€~š–mМš ķŪJ`‹æüŗrų]Įx?<Üĩe=ÜŲēc7f/$1) ;;[*•/Į˜‘CŠyCQÖ>=ÂŪΎ…ËVqåj.ÎNÔ­]ƒž]:Y]ŦЛÅ+Vŗc÷>*V(Įcû{å*ŋOŸÍw“į—žÍ3Ž^Ũ;c0X¸l%É)Š”+ëÁ˜'ŖLéRwĩ~ILJæķ “HKK§n­ęĻ' 4Ū6-š3寚|ũũ/Œ:8×ëüŽ‹HļEËWq1<‚æMņH˙Ū8:ؓ”œĖŲsaüđëTŽ6-;Éß$%%ĶĢ[g\œ¸NËç�� �IDATĪÁ#ĮX˛b uA4}ÖzvíHŗÆ 8{>ŒéŗPĸ„3#ČsŖ‡ķŅ—y‹\¸,Ģģīnąĩŗ%ėâ%~üuQҎBô ˆˆˆbéĘhÛĒ9iŠéw<˙~3Ė^°”—ž}ŠNm[˛bÍz ˙÷ūTH(Ķg/ }ĢtëÜžĖŒL.†Gđķ”™ĻÂŦķ—ŦāŅŊyüK$&%ŗuû.vėŪ—ãwúÍ6lbčāžŧ8f$ŋLũ+ßmäeێŨØÛÛķōsŖ°ļļæĐ‘cĖY¸Ė4?ŋ}ģˇÛVA?ŋĪøöî::ŠĢãøoI \[ !¸kpwˇBq/m)V  ˇâîVÜĨPęô­áRŠ–wŠD€$ėûGȖč $›Ā÷sNÉȝgæ ģĪŪ™Ũ 6-Ģ÷{ôāá#}ũŨOĘūö[ĘūÂÃÔ_eĖ åü9;;iŅō5ĻoE˛f=��ö`0FyzzÚ;–$aãæ-údĐ•õ+ŖU˗Ø;œ×‘“įT4.{‡‘¨ 1NU*”Uíj•ė ^#GNžĶ’å+íF’3sbÜ_‘šPĖ|‡Ž°w‰ÚøƒôÃĪŋéëī~J2ĮJž<šüÂÃr{uī¨Ā `-YĩîeĕōåÉĨní[K ¸mÔg°ØĘÜø�HåŒ���ÄîŊÎmåáæĒ5›ļëQ@€ äÍ-_oÍ[’¸oņy]ũuúœú& �o*+���I˞/×ĢiÃ:ęÖĄĩR¤HĄÛwîjåēÍĻoÄ�� ‡Ä MņŠŊC�€7Æ /˙üŗ„>ÖŖ€@-_Ŋņ•”��^‰+ÅôāX�����đfJfī�������’*+������6"ą�����`#+������6"ąģK–,™Âž=ŗw�Á`°w�ŪP ?�tXŨšģēč֝íđFpws“ŒF{‡‘4rsu5왺ŗŗ$Ž)€WČhT*{G�°‰ØŨÛY2ęÖŨûēqûžž†„Ú;āĩÖĨ}+>ĩ”Á~ŊˍRŅO×Ā+”Ė ÛÚ; �€… FŖŅčīī/OOO{Į‚7ØĶP]š~KÁŸ\âQŅüšôˇ˙%-^ąV22{%ƒÁ 7WWuißJ9=ŗ[´ĪÍÛwôÞßôøÉãD9yåĐąö€…ÜŨ\ÕŖs;åx;ĢŊC�XˆÄ �����€¸�����ĀF$V������lDb�����ĀF$V������lDb�����ĀF$V������lDb�����ĀF$V������lDb�����ĀF$V������lDb�����ĀFŽņUđ?—¯ĮWŅ����Ŧä•-‹ŊC�€×3V������lo3VȈ����€×3V������lDb�����ĀF$V������lDb�����ĀF$V������lDb�����ĀF$V������lDb�����ĀF$V������lDb�����ĀF$V������lDb�����ĀF$V������lDb�����ĀF$V������lDb�����ĀF$V������l”¨+ÅJųÉÛ7ŸJ”.§ĀĀ HëFŽ'oß|š5gžĸC|ЍûˆŸ<ЍJõÚ2l„.\ø'Úö*W—ˇo>íũæÛWK|– XÃÚ~‘ŧĘūC_��@R¨+îũû¯–,[nī0`éŌĻUölŲää䤋—.iÍÚõĒ×¨ŠžũîûHەõ+Ŗ*•+*C† vŠH8–ö‹ÄĀÖžyíúuyûæĶ’e+^ē,��� !9Ú;€˜ -ZŧTíÚŧĢÔŠSŲ;$ ŸôWķĻM$I.üŖÁŸ ×ūõQߏõãˇ_+cÆđ7Xƍļg˜@‚˛´_$ļöÍ]ģvŋ˛˛���€„”(gŦÔŦQM4oÁĸ8ˇ ÖÄÉSTĨzmå-XTÕjÖŅÂÅKe4M۔(]NŪžųôĮūúđŖžĘW¨˜*TŽŽ¯vīŅ͛ˇÔŽcå)PDUkÔŅąã'Lû………iÎŧĒ]¯Ąō,Ē •ĢkÁĸÅņvΈÎÛÛK įĪQš4Šõøņc}šf­i]Ô[nßžŖO‡|Ļ •Ģ+OūÂ*_КF¯āā`Ķ>ÅK—5íĶĢO*ZR…‹—ÖčąãkæÚŲ{|$oß|útč°Hûõ˙dŧ}ķéたÆXnDÛ<x谆 ĄÂÅKĢPŅ’7qr¤xhįxQ\ũ’ú´¤¯HŌ˛+UŖN}åÉ_X•ĒÕÔ¸ “hZŅæöũü?5iŅJ‹–ŊoZŌī6iŽq'K’F/oß| ŠņV ­Ûw¨a“æĘ[°¨ )ĄVmÚëį˙ũ)vKû���đ*$ĘÄJ§íåčč¨ĢžÔíÛwbŨnčđĪ5oÁ"ĨtMŠŽíÛęÎŨ{7a’ÖŽÛ`Ú&EŠ’¤#GËŲÅYŪ^^ēzíš>ųt¨z÷ûXYŗd‘—gų_ŧ¨>ũ˜ŪŦŽ?I“§LS@@ ēué$ww7Ÿø….Zŋ'HÜŨÜTŗFuIŌīėuģ÷{õÖē ›”+—Úĩ}W™3gŌŌå+4`ĐĶ6N)œ$IC>!7הęĐž­ž<yĸ%ËVhéō•ą–mŽĩjŲ\’ôŨ÷?šÚŅhÔĪ˙ûU’Ô´qŖËũ¯mŽŅ­›ˇTŊZjáĸ%Úŧu›ÅĮ\íüM[ŋ°¤>-é+SĻÍĐČŅãtëæ-Õ­S[)R¤ĐÂÅKõáG}MÛD´š “§čŅŖG*Z¤HŒąZŌī6¨¯,™3K’J•,ĄŽíÛ)yŠäŅĘZ¸xŠú}<P§NŸQÕʕTĸx1í?pPģt×w?ü-6s} ���xeb%k–,jŅŧŠ?~Ŧ™ŗįƸ͓'OtúĖYåōņŅ„ąŖ5p@uîØ^’´gī7Ļí$Iš}}5yÂ8-^^^PPŧŧ<5aÜh͙5C’tņŌ%]ē|YwīŨ͊U_J’ĻOŦ~}>Ōō%‹äčč¨9ķō‰gˑ=ģ$éÖ­Û1ŽÖĄÃGäčč¨ysfjȧĩöËú g•)]Ō´]D[(SĻ´ÆĨū}{냞=$IkÖ­ąlKÚYÅ å•5KŨšsGG—$üë”îÜšŖ,™3˯Léˎˆ'SĻ Z8Žž˜8^ԗ$ũđãO˙Şhįoލũ’ú´¤¯Üŋ˙@ †Īr™<qœĻLž Íë×(UĒTúũ÷?tâĪ“’ūks)]\´gį6­Xķ CKú]×Îååå)IĒYŖē† ũT)’GNŦ< Đ´ŗ$IŖGŽĐė™Ķ´lņĩkĶZFŖQ_Lí˜qõ-���āUI”‰IęõAOĨH‘Bë6lԕ+WŖ­wrrŌŽm›õõWە/o=yōD2¤—$Ũŧy3Úöå˗•$eʔQ’¤r~~’$/ĪĻņwīŪĶŅcĮ&ƒÁ L3ęú Uļˇßԃtūī ņrΈY@@€$)™CĖÍÕÅÅEoeÍĒĐĐPÕŠ×Pã&LŌOû~Ö{ŨģŠíģ­Ŗm_ŊZĶīĨJ†ßžđĪ?ūzm[KÚY˛dÉÔŧYøķ/"&úãOû$IM7”Á`ˆķüjÕŦaúŊ`Á’ū{ŗL;Glĸö KęĶ’žrôØ1S_¨TŠĸ$ÉŨŨ]GüĻĶ'Š`ü‘âhPŋžÍ?ŽËš~“#GŽšnWjXŋŽiyŊēu$Ig̍VV\} ���xUåÃk%)sĻLjûn+-YļBSgĖTÚ4iĸmŗfíz-ZēL/^ŌŗgĪLË_xô„‰‡ģģé÷ˆ7—îînĻeNNNzĸ°°0=|øđy9FUŦZCQŨŧySš}sŲ|n°Žŋ˙EIŌ[YŗÄēÍė™Ķ4hČg:}úŒ.^Ē…‹—ĘŨÍMÆVŗĻ#mëáîaúŨÕÕÕôûƒûLI‹YŌÎZ4oĒ™ŗįę›oŋ׀ū}ĩīį˙IŠũ6 ĨNõßšœÂoa{á8´sÄ$jŋ°´>Íõ•ĪËqvvŽ6k$&1õ™˜XÛīĸē÷īŋ’ÂÛ°‹‹‹iyš4Š%…Ÿ÷Í7•-ÛÛĻuæú���đ*$ÚĊ$õ|¯ģ֎ߨ­Ûv¨N­š‘Öũđã> 6B)’'ט‘#äã“Sß˙đŖæÎ_øŌĮMåūbÜÁÁAķæĖŒļ>ˇ¯īK–šyķ–žũūIRųrecŨŽPÁújûų_ŧ¨ũę›oŋ×wß˙ ƒ‡Ē\9?eΔɴíŊ{÷ĸũn0bü*KÛŲ[YŗĒb…ōúißĪ:~âO9zL… ”ˇˇ×K?í1‰Š_XZŸ–ö•ĮëéͧĻį•Üŋ˙@!!!rss”ØpHfŲÄGkú]LŌ¤O <yōD?–ŗŗŗ¤đŲWø9���ØCĸŊH’ŌĨMĢNÚÉh4jīˇßEZwėxøŗ,rúäÔ;-›Ģxąĸē~ũ†$)ėŲË=ĸPĄrppPXX˜˛fÎŦjU*ĢR…ō៍‘f� ū\ž|EŨ{~ §OŸĘÃÃC-›7qģ‹—.i֜yZˇ~Ŗ<säPËæÍ´pŪlyyæĐŗgĪtãÆHÛīØõ•é÷īŸ?đŌ7—’Įđéŧ5íėáņ}>jŒÂÂÂÔ´Iä™2ļ #ĒØú…%õiI_ÉíëkJZD<6((HUkÖVérMΞ–š~qË\PPPŒû+ZDîĪgd}ĩûkĶōĪŋĻšpĄ‚Ļõ���@BJÔ3V$Š[—NZąjĩ=ziy.Ÿœ’ÂīĢ3n‚Ž]ģŽ;wīJ’.^ŧ¤ÉSĻéã~}l:fútéônĢ–ZųåuėÚ]5ĢW×ŠĶ§uøČQ+ZDU*Wzš“BŦ&LüBŗfĪShh¨Žß¸!ŖŅ('''MŸ2)ÖOŖ]]]5oÁ"=~üXûTόuáũãQŲŗeSŪ<y"mūüßjĶž“R§JĨŨ_ī•$ĩoÛ&Æ˛­igÕĢUQútétäč1%Ož\ ęՍąLkĐÎ!YÖ/,ŠOKúŠ“““ēvî¨Ysæiā !úū‡uōä_ē˙J—*ŠŌĨJš‰6fæú]ĻL%I˖¯Ô•+WÕŋoīHûģģģĢoī5rô8 ūl¸~ųõ7Ũû÷_ũ´īg9::jā€ū6Å���ŧŦD=cE’<<<ÔŊkįhËëÖŠ­ÎÛ+U*­]ŋAŽÉĩ`î,ĩkĶZɒiێ/uÜaCĢw¯”"E ­]ŋA˙øûĢMëVZŧ`Ž’Y8õÖģ{īž.]žŦë7n(}útjŌ¨ĄvlŨ¨J+ÄēOúté´öËĒPžœžûūG-Z˛LĮŽW“Æ ĩjų999EÚž_Ÿ”>}:ũđĶ>ĨI“Zũú|¤Ö­ZÆXļ5íĖŅŅQ*”“$UŠ\é•Ü–@;‡dyŋ0WŸ–ö•>}¨ũû*]ētÚžc—ƒ‚ÔŠC{-˜;ËævaŽßuëŌY>9sęáŖGúå×ß"=O(BĮöí4~ė(y{{i×WģuđĐ!•/WVkV-W™ŌĨlŠ ���xYŖŅhô÷÷—§§§ŊcâU…ĘÕuõÚ5͛=C5kTååßû÷_UĢYW<ĐĘe‹UŽŦß+?ÔÄwŋ���ė-Ņß $vW¯^͈Qctōä_zđāĘú•!Š����o+ĀKzōä‰~˙ãRÍÕ5zäp{‡����H Ü �����`#žN �����`#+������6"ą�����`#+������6"ą�����`#+������6"ą�����`#+������6"ą�����`#+������6"ą�����`#+������6"ą�����`#+������6rŒ¯‚˙š|=žŠ���`%¯lYė�ŧ–˜ą�����`Ŗx›ą’>ĩ[| �����(0c�����ĀF$V������lDb�����ĀF$V������lDb�����ĀF$V������lDb�����ĀF$V������lDb�����ĀF$V������lDb�����ĀFŽö����o˙+×´fÃëŲ3ŖŊÃI2 ƒ\Sē¨Ũ;M•í­,ö�đ3V���`ü¯\Ķ‚Ĩ_ęQ@I+FjŪ’/uũæm{‡�xŽÄ ���Ėęõ[íBŌf0Čh|Ļåk6Ų;�Ās$V���`íBŌg0čQ@€ŊŖ��<Gb���HbŒFnŖ€Ä‚Ä �����€HŦŧ`܄IjÚĸõK•Qąj -X´äEôf1ÚųÕnuíņž*TŠĄ’~Tˇa 6BįÎ˙iۄ¸Îö¨ËWŅãĢ\[ŽGbé‰%Ž—1`Đ.^Z7mąw(‰cGä>ū÷… *\ŧ´Ž?aˇlÅ8csãBī~T¸xéH?•ĒÕTˇ÷>ĐĄÃG"m;kÎ<•,S>!Â�āĩÃ×-ŋb÷í#Ÿœö#É1útč0íŪŗWuk×Rŗ&•2Ĩ‹ü/^ŌÆM[ÔŽc͞1Uŋĩw¨o,[ÚļĩûŦYˇA'˙úKŖ?nmxq–“ÔûåÇõãOûä›ËGÛwîRķfMėŌ+ņ*꛱#:—H˙&%Œ3–ŗt\ČööÛūŲ`ĶߡīÜŅÆM[ÔĨ{O­\ļX äO¨�xm‘XyÅ6¨gī’¤M›ˇj÷žŊũųp5¨_×´ŧ’¤fMĢS—îZ°h‰æĪ™iŋ ßpļ´mk÷9uę´ÕĮ°¤œ¤Ū/wŊWÎÎÎęߡˇzŧßK/]RŽėŲíÖK{õÍØ]J—”Ī˙Mz‰ÆËY:.¸¤tQÉÅ#-ĢVĨ˛ę7nĻÕkÖiܘ‘ 2��¯­Du+Đ­[ˇõaīž*UļĸĒÕĒĢe+Vj֜yjÜüĢĘš{īž†|6BÕk×SIŋ jиšV¯]íXī÷ęŖ’~TĩF͝ŋ0Z9UĒ×ÖĒÕkõųčąĒXĩ†ĘWŽŽ/ĻN×Ũ{÷ôQߏUĄJ ÕĒÛPÛvė4íu*p•ęĩĩzí:}1uējÔŠ¯rĢĒWŸ~ēsįŽ•WįõļfŨz-R8ŌŖnŽŽZļxaœoŒBCC5wūB5lÚÂTįë6l4­¯VĢŽæ/\lúûΝģ*\ŧ´ МĒ5ÃÛ]TÖîßąKwŊ÷A¯håôé˙‰Úvč"ɲ6URkÛQ÷9tøˆ:uíĄō•ĒɯBučÜÍ4Ŋsˇ÷´mĮNíØų• /­ĶgΨRĩšZĩzmøų”)¯G™Ŋ1•5ާOŸjĘ´ĒY§Š—.§Úõiæėš ĩęüĘöģTĢFu•.URYŗdŅŽ¯öDÛæiHˆf˚ŖuęĢLųĘęĐš›Ž;nņzs}HŠģū,Y˙ĸ˜ęɒz‰Šą#zwssU“F •*u*IÖՋÄ8“TÆKƅØ899)—.]šbņ>�� v‰*ąōųčą:}æŦĻO™¤š3§ëĐá#Úķõ7Jf°.ĖáŸŌąã'4aėhm\ûĨ:wj¯I_LĶ÷?ühÚfčđĪõ÷ß4kú-Z0G÷īß×7ßũŠĮäŽZąōKUŽTQ?}ˇWŊ{Ŋ¯ĢVëƒ^}ÔĨSíû~¯6¨§1ã&ęÁƒ1Æâ˜ÜQK—¯TΜŪÚŊcĢ6mXŖŋNŽôBûM÷đáC˙û‚J•,ë6ŽŽ)ã,cĘ´™Zļb•ēuëžTģļ­5é‹iÚŧuģ$Štɒ‘ŪD<tX™3eŌá#˙Ŋš¸xņ’îŪŊĢ2ĨKE+ßÚũ›6n¤ß˙8 [ˇn›Öé×ß~WŖ†áŸjZŌŖJĘm;((Hõé/oo/­XļHĢ–/–oŽ\zŋW=xđ@ͧLRžŧyTģV ũøŨ×ĘåãŖäɓkãæ-ĘåãŖE æĘÅÅÅė5ˆŠœ¨ÆŒ›¨-Ûv¨_Ÿ´eãZ}øÁ{ZŗnŊĻN˙ī xbéģ˙üã¯?OūĨ† ęÉ`0¨~Ŋ:ÚųÕîhß1eętmŪ˛M÷í­Å æ*[ļˇÕķÃŪēråĒeëÍô!sõgn}T1Փ%õō"Ǝ˜û¸ŖŖŖF "w77ĢëEbœI ãŒĨãB\Ž^ģĻĖ™2Zŧ=��ˆ]ĸIŦÜŊ{Wŋüú›ēvî$ŋ2Ĩåë›KãÆŒŌũX^lÅe@˙žš7{†Š+Ē9˛ĢIŖ†Ęí›Kŋũž_’tķÖ-ũą˙€:ul¯ŌĨJĘÛËKƒ>ųXn1ŧ�Ī“ÛW•*”—Á`PíZ5%I… TáBŸ/ĢĄ'OžčâĨËąÆãíåĨÆ ČŅŅQ™3eRų˛eõ׊SVŸ×ëęîŨ{’¤ŦYŗDZĒĀĀ H?aaaŅö õ7ŠcûļjPŋŽrdĪŽ–Í›ŠAŊ:Z˛tš$ŠLéR:~ü„ž={&)üÍMÚ5ŦK—ÃëîĐá#J“:ĩrûúF;†ĩû×ŦQMnŽŽÚŊįkSû~ūEFŖQĩkÖ°Ē ž()ˇí7o* 0PõęԖˇ——rz{kā€~š=cĒR¤H!www%spPōäɕ&uj988Č`0ČÅŲY}{¨Â… ĘŅŅŅė5ˆŠœŨŋ˙@;v}ĨŨē¨v­ʞ-›ę×­Ŗw[ŊŖM[ļéiHˆMį_ļnߊ9˛ĢPÁ’¤õëęęÕk:|ä¨i›€Ā@mÚ˛M=ēuQ­š5”?_^ :XeũJëō•+f×[Ō‡Ė՟šõQE­§G,Ž—ŒæĮkëEbœI ãŒ%ã‹BCCM?7oŨŌ´ŗäīQ͚4Žķ8Iƒƒƒ:ĩiŠqÃ*oîčɯ„RŊry͘đšfNŠ™GjƄĪUŊ2 €×IĸyÆĘÅK—e4U´p!Ķ27WW•)URūņˇĒ,הŽZŧtš:¤{˙ū+ã3Ŗ<|¨ėĪī=žp!ŧŧųķ™ö1 *?ŋNŸ9Š,OĪĻßŨŨÜ—åøo™ĢĢĢ$éŅŖG௓+ĘÃė<<ÜõāÁCĢÎéu–,Yx~/4$ōt˙Í[ˇiˏ‰‘–-š?'ÚŊâgĪžSHHˆüʔŽ´ŧxņbÚŧuģƒTĒT ęÜšķʝÛWV˙>Ŋuō¯S:rô˜˛gËĻC‡¨L™R2 Ņb´vgggÕŽUS;ŋÚ­íÛJ’žũî{UĢZYîîîúķdø‹fKÚā‹’rÛΑ=ģräČŽÁC‡ĢEķĻō+SZyķäV‰âÅb-_ ö"sל3gĪ*,,Ėô†$BžŧyŦK—.Ë'§ˇÕį´k÷nĩlŪĖtû@Ö,YT¤p!íØų•éŦ˙}AOŸ>U˙Õ{ŠäÉõÅÄņ’¤cĮOÄšūĐá#fûšúŗĩ~#XS/;ÂÅ5vØR/Œ3‰{œąt\ˆpöė9/].Ę1<ôųđĄ*ëWÆĸķIĒZ7k¨b…Ãë {‡wĩhåZ<û˙ą¯Ę„Ī?ķGƒAęÖTŖē5M˂‚‚5pĸx �?Mb%bpĘ(ŸxEÜ#nАõüđ#…††é“ûÉË+‡ÕģßĮĻm‚‚‚$IÎÎΑöM™2ú§m1}ĸįäämY\ĶoŖ‘ĨOŸ^ƒAW¯]‹´ŧjåĘōÉū‚ķÎŨģ0ppLģ+ 0P’ÔĩĮûzņmÍŗįurįîĶ‹íÃG)}†ôēxņ’Š.¨ã' éđáŖjÔ ž9ĒŨģÄxŒĖ™2YŊ“Æ ĩaĶf9sV9rd×˙~ũMS'Od]ŒÔÛƒ–-^ eËWjķÖmš1kޞdÎŦßOõëՉu?ˇįoÆ$ËŽ9ĪÛKÔ[D"ŽQÄ5”ėßwũíwŨž}GŗįÎ×ėšķ#­;ūo ú¤ŋœõđaø›Rgį˜_ț[oiŠĢūl­ßÖÔKƎč×)*kë…q&ņ3–Ž rdĪĻąŖ˙{@­‹‹ŗ˛gËĻäɓ[u\{kX§†ĒT,+Į(ŗƒĸÚč¨ÖlÜĻ‚ųķčĮ_~WÁüy”ŌÅEŽŽŽj÷NS 1>"ļRôŧ,� I4‰•ˆ_?‰´üĄ•ŸŸøķ¤Îž;¯% įEúÔæßû÷õÖ[oI Q!I‘ö}û'fˆ?ŽŽ)•?>íũö;}Đŗ‡Ûeúôé”>}:IŌÕĢ×bŨßíų'žcGˆņ>÷,™3K’Ę”*ĨcĮ+]Ú4ʕËGîîî*Z¤°ÆOœŦë7nčÚõë*]ĒdŦĮąv˙üųō*OîÜÚûíwʓ'ˇRyx˜ÖÛŌ_‡ļ6Mõëķ‘úõųH_¸ +WkȰōōōTū|yÍîoÉ50'â T``ä7ęģššZv2 `ûίT¤p! čß7Ōōuíņžž˙ņ'Õ­]KП' #ŪĖEenŊĨ}Č\ũŊLũÚR/Œ˙‰Ģ[S/Œ3‰œąt\ˆāäėivQRUĩbŲhˇ\Euū‚ŋ6îÛ(oníūæÍ\°LŊēwTJ]đũ–ŗWiāpfž�Ā›&Ņ$V˛gĪ&IúķäIĶ´Ø€Ā@ũž˙€2¤Ooq9Ož<•ôß )|üÕĢה?_ø ‹ˆiȧΜU‘įˇ÷A´‚�� �IDAT…„„čĀÁÃJĘē2x5Úˇi­O>Ē%ËV¨{×ÎŅ֟üë¯X÷õõÍĨɓëîŊUĶËĶ´üŪŋ˙*™!™)iWēTIMúbĒRyx¨XŅ"’¤B… ęō•ĢÚģ÷[yzæ0Ŋ‘Љ-û7nÔ@_Ž^ĢK—.ĢAũēĻ[liƒIŊm_šrUįΟW•Ę•$I9ŊŊ5tđ@mßšK˙}Áô†'ŽOŽ-šb+Į7W.988čČŅc‘Ļé;qBînnʞ-›m'øŠ=|øP?ū´Oú÷‰ņMQéR%ĩs×nÕ­]Kžž9äėėŦƒ‡›ęũŲŗgęÚã}5iÔP•+W4ģŪ\2WŠ<<,Ēߨ"ęÉÖzaėˆģ[Úī"0Î$îqƚqáu‘TéõɰXˇqtp0%U$ŠN*’¤™ –ŠJy?­Ũ´=ū}nęØaĻd¯9!ĄĄę7˜¯Ŋ€¤,Ņ$V˛ŊũļōæÉ­EK–ÉÛËKîîš6sļŌĨKkU9šsįRŠ)´zízŊ×­ĢΝ?¯ŗæČ¯Lių_ŧ¨ģ÷î)kÖ,*T°€–,]ŽėŲŪVÚ´iôåšuJ‘ÄĻÅžNjÕŦĄÃGiöÜų:ņįŸĒUŖ†RĨJĨÛˇo뇟~ŌžŸQÍÕŖŨ¯.…ߡߴic͝ŋ@iR§VüųtũÆ MübĒ2eĖ YͧJ’J–(Ļ›ˇnéĮŸ~V˙~Ŋ%…bí›+—֎ߨŠã~œ-û×ĢSKS§ĪÔõ7´yÃĶr[Ú`RoÛ7nŪTŋƒÔįŖMŗÜĩ{’%KfzžA*9sV§ĪœQæLŅߨZr ŌĨMg9ŠS§RãF ´xé2eĪöļrįöÕĄCG´nũFulßÖâÂņm÷×{ĒjUĢĸžVÍę1rŒnßžŖ ŌĢIŖZŧtš2gĘ$//OmÜŧE'˙:eúvsëÍõ!sõgIũFĩžlŠƎ¸û¸ĩõÂ8“¸ĮkĮ…7IÔ¤J„:5ĒčúÍ[Zšns‚Æüø‰ÜŨ,ĢįĮÁã9�@|K4ß $IƎV†ôéÕĩ{O}Đģ¯*UŦ ŊÉÉ)æo.ˆIÚ4i4røgúõˇßU¯QS-\ŧT#G SÛw[éÚÕkęÖãIŌø1Ŗäé™CŊû~ŦžôV–Ė™Uŋ^=3>‹¯ĶƒŸ~ōąĻO™¤PM˜<E}úМų ”"E ͙9M“Ə‰ņŪ|IĐ¯ŪiŅ\ĶfĖRŖf-5tØį*V¤°ÆeÚÆÃÃCųōæŅ›7Uüų§Æ’T¤H!]ģ~]~1|Uę‹lŲßÃÃC%KWĄ‚”#ĘC­mƒIŊm—(^LŖF Ķίvë6íõnģŽúí÷?4uōDåČ~mZŋĶBˇnßVĮ.=bœi`é50WΠO>VŖõ5füDÕoÔLs,Tˇ.ô^÷Žņ{Ŧ°ũųC(ĶĨ9š\ĨRE%K–L_íŪ#IęÛģ—5Ŧ¯)Ķf¨sˇ÷tîÜߚ=cĒé“qsëÍõ!sõgIũFĩžl­ƎØû¸ĩõÂ8“¸ĮkĮ…7ElII:uæŧNœ<ā1[‘, zLb�’:ƒŅh4úûûËĶĶķ•×ūcŦyxx˜–u{īĨōđĐä‰Ü¯Š¤įŪŋ˙Ē^ÃĻú|ØÕŦQŨŪá�H";đ:<j’Õû˜~ĢLÔ[Ė%U,ûRĄ1|Ũz|û¸WwåČöļEÛú_ēĸ/f-°é8c?`Õöîîî6�ˇÄ1įũš^}úëîŨ{úlČ ĨK—Nû~ūŸö8¨™ĶĻØ;4Ā*÷ī?Đå+—5qōTy{yĒzĩĒö @ĀØX.ą&U$)Ȋ+ÖĖn�$N‰*ą2aė(Mš2Mũ>¨āĮ•=ÛÛ5b˜*V(§ÃGŽęŖ>ũ͖ąsÛæHœėaێš9{ފ+ĒƘ< �qaė�,ײIũX“*Ë×lTëæüŲ*ŦI–?ŽĮH�� !Q%VŌĨK§ņ/Ü×ūĸüųōjŨę•fËđđ`Š#ė¯Cģ6ęĐŽŊÃ�Ä0v�–ËöVÖhË"fĒ|Đ­ƒ|ŧ=í—Xąâš)ÖĖn�$N‰*ą'''Ŋà���xķ¤Níéīo˙ņņö´OPĪY5c…Ä �$yI&ą���D {Ļ€Ā@Ũŋ˙P—¯^Ķú-;íöL•¨Ŧ™ąbÍļ�€Ä‰Ä ���’œĄŖ­˙vĄ„bŨ3VHŦ�@RGb���‰ZXX˜L_ģl {Î^  ´xÛĀĀ xŒ�øē���$jßīûUaV$JÂÂÂôÞ_ã1ĸ¸:{^/_5ģŨÅËWuęėųˆ�Ÿ˜ą��€Dmûîo´}÷7öÃb?Ņä™ķí� 0c�����ĀF$V������lDb�����ĀF$V������lDb�����ĀF$V������lDb���Hb {G��ˆ@b��� ÆÍÕU2íFŌf4ĘŨÍŨŪQ��ž#ą��€ķnËÆLˇxYÉ ęđn3{G�xŽÄ ���ŒįÛYÕŊSššēĘ@‚ÅjŽŽ)Õŗs;eɘÁŪĄ��ž3FŖŋŋŋ<==_iÁ=zĨå���°ģ;ˇ@|`Æ �����€HŦ������ØˆÄ �����€HŦ������ØˆÄ �����€HŦ������ØˆÄ �����€HŦ������ØˆÄ �����€HŦ������ØˆÄ �����€ãĢā;÷âĢh����Vrwwˇw�đZŠˇÄŠWļ,ņU4�����@ĸĀ­@������6"ą�����`#+������6"ą�����`#+������6"ą�����`#+������6"ą�����`#+������6"ą�����`#+������6r´w����xŗüíIKVŽU@`ž={fīp’ ƒÁ wהęÖŠ­<ŗŊeīp��Ī1c��� æo˙Kš6g‘> Šb%ŖŅ¨‡4eæ]švÃŪá��ž#ą��€ŗxÅZ{‡´ 2ŸiūŌUöŽ�đ‰���$˜Gö!é3ôāá#{G�xŽÄ ���ÄF{‡��xŽÄ �����€HŦXéęÕkjŌüåÉ_XK–­PąR~š5gži}Ôŋa9ŖŅ¨-ÛļëŨvU¤Då)PDĢÖP˙OéĖŲs‘ļMˆëlēūų(ÕĒÛ0Aļ¸}ØģŸŧ}ķiÍÚõö%QķĢPY_Lnī0^™sįĪËÛ7Ÿ9jīPĸIč1Ã\čŪķCyûæ‹ôSŧtYĩißIûŒ´íS§+Oū¯,6��đfã떭´~ã&;˙ˇV,[,o/OĨōđPîÜžö+É3ęĶ€vėüJ ë×SëwZ*eJ]øĮ_kÖŽWĶ­´tŅ|•*YÂŪĄžļhۉ×Çõíwß+On_mÚ˛U­[ĩ´wH¯ÄŠUĢuüÄ Mž0ÎŽe$f)Sόôob’c†Ĩ} {ļl?v”éī[ˇokõšujŨļƒ6oXĢ… ÆK|��āÍFbÅJ<Đ[YŗšŪā7kÚØÎŊÖŽÛ ;ŋŌä ãÔ´I#Ķōj’ZˇjŠ–­ÚhæėšZšląũ‚|ÍŅļ¯í;wÉÅÅEC>¨vģČ˙âEyæČaī°^ڟžLe$fŽ)]Ÿ˙›ø+ 9fXÚRēĻT™ŌĨ"-ĢUŖē*W¯ĨeËWjęã-F��đæJTˇŨŧyK]ēŋ§ŧ‹ĒtšŠZ°hąž˜:]5ęÔˇĒœBEKjŪ‚E0h°J”.§ŧ‹Ē{ĪuīßMÛ/]VK–­P§Ž=”'a=zôHOŸ>Õ¸ “TļBųæ+¤ō•Ēiō”i •$ĩhÕF+V­6M͞;ĄŲŠÎĄĄĄš6c–ĒÕĒĢ<Ѝj:Zĩzmč5ļ|å*•(^,RR%‚›ĢĢ6Ŧ]gRÅÜu.]ŽĸfΞkúûöí;ōöͧ{÷‹TNОáí.*k÷oŲē­Úwę­œī÷RĶ­%…ˇ÷N]{(O"*åWAĶfˊõü"ĐļßL›6oUŊ:ĩUÖ¯ŒŪƚU[ļnļÍ͐MúbĒü*TVūÂÅÕĸU:|Äâõ–ÔįūõÎģíT¸X)(RB-Zĩ‰t‹…šõ/jÕĻŊ6nŪĸÍ[ļÉÛ7Ÿū:uĘl;ĩ¤ IJ–,™f˚ŖR~”'auęÚCwîŪĩę\ŖĘ_¸¸.ZiŲ ÁŸŠa“æŸŋ%ĮÚÆgjŲŧ™R§Imõ5–^Ÿ1Ò>'''åɝ[ū—.Yŧ��€5UbåĶĄÃtō¯SZ8oļ–/^¨ũjįŽŨJf°.LĮ䎚ŋpąĘ”*ĨũŋũŦ]Û6ëäÉŋ4jôSœ'OŽ5ëÖ+ˇ¯¯V¯Z.}6|¤ÖoÜŦÁƒ>Ņ7{vęãū}´|å*Ÿ8Y’´dá<ĩlŪLŪŪ^:øĮ/ęÔĄŲXÆM˜Ŧ‹–čƒ÷zhĪŽmęŌšƒF¯u6Ywq^c>ÔŲsįåWĻtŦÛ¸ēÆũi­šë\ÎĪO6m˙Įūʒ9ŗüī É?˙øëΝ;*_Žl´ō­Ũ˙Íõ˯ŋëæÍ[ĻõAAAÚ÷ķ˙ÔŧYø§ēũ?¤ŗgĪiÉÂyZŊj™ūũ÷_íūú›8Ī“ļũæųûī :vü„š5m,ƒÁ &jËļíŅž bė¸ Zˇ~Ŗ†~:Pkŋ\Ą9˛ĢCįnē|ųŠEëÍÕgPPēvī)ŸœÚ´aļl\Ģ<šsĢc—îzđāŲõQ-œ7[ōįSũzutđ_”Û××l;ĩ¤ IÚųÕnŨģwO‹ÎĶ´)“uøđM›ū_â2>ÚŽ%įoÉqŖö_www;JînnV_céõ3,íqš|办dÎdņö���ÖH4‰•;wîč§}?냞īŠ|š˛Ę“'ˇĻM™éS5käĪ—W͚6V˛dÉäííĨw[ŋŖŨ_īUPP$É`0ČÅŲYƒ>é¯bE‹čŅŖ�mŪēMŊ>čŠúõę(Göėjܰ:ļo§5ë6čiHˆÜŨŨåä”BÉ”6M9;;ĮÃŖ€�­ZŊFŨģvVĶ&ä™#‡Ú´nĨĻjŪü…6×ëčöí;’¤ˇß~+ŌōĐĐPEú ‹ļŋ%ך\Y?9rTĪž=“$ũūĮ~5lPOAēøüSĖũ*mš4ʛ'O´cXģŊēĩåæęĒí;všĘøū‡Ÿd4Õ ^]Ũ¸ySŋūöģŪëŅMeũĘČ'gN6TînŽf¯mûͲaĶyyyĒh‘đm6kÚXWŽ\Ձƒ‡LÛjíúúčÃ÷U¯n,_cGTÅ åtņŌ%ŗë-ŠĪkׯ+ 0P6OΜĘåãŖáŸ ÖŌEķ•"E ŗëŖrww—ƒŖŖR¤HĄ´iŌčáÃGfÛŠš2$Iîî1l¨ ȝÚĩj¨ZÕ*:z돤økģæÎßŌãFíŋŽŽŽ#6I}˰¤ŧ(44ÔôsãæMM˜ô….\øG­ßy=žMË988¨S›–7| ōæöą[Õ+—׌ ŸkæÄ‘š9q¤fLø\Õ+—ˇ[<�€W/Ņ$Vūņŋ(ŖŅ¨Ŋš–ššēĒ|Y?›Ę˟?_¤ŋ}sųčéͧ‘f+ZÄôûŠĶ§fzáĄ`ü –ŋ˙EĢc8uę´BBBTĄ|šHËK—.Ĩ‹—.)00Čę2_Gɒ…7ÃАČĶũ×mب‚EKDú‰é…´%ךlŲ2  ԙ3g%I8 R%KĒpĄ‚Ļ2÷8¨råüd0ĸÃÚũÕ ~=mŲößtõŨ{žV­šÕåîîŽķį/HR¤) .TČėõĸmŋ9´uûv5iÔĐôFņ­ŦYUŧXQmŪ˛Í´ŨŲŗįôôéS*TĀ´,Eōäš3sēʗ+kvŊ%õéåé)//Oõí˙‰æÎ_¨?Oū%•.UR...fכķ*ÛiąĸE#ũ.]Z†'žÚŽšķˇæ¸/ö_kŽ›¤<fXÚ"œ>}Fžų ™~ĘV¨ĸ5ë6h¸ŅŅŽ×_ëf UŦpššēĒ{‡w•?oÂ<}Â៚’(3'ŽTŖē5#Ŋļ0 jTˇf¤m&Œø4Ab�ďDķđÚû÷īK’\Ŗ|bq_šĩĸ>čĪÅ%üī‡š–šģģ›~$šE9žĢkøß/Ę­ņčy™īļë¨ßĒ?{>}ųöÛruMú |Y3f”Á`Đå+W"-¯YŊē|så’$ŨžsG~Ô7Æũ-šÎž9rČËËSV†ŒôĪ?ū*Qŧ¨Ž=ĒŠyĶ&Úā >ęõ~ŒĮȒ9ŗÕûŋ͞šV¯]§S§NËËËS?îûYķįĖ”ô_{Šú)¯%ßüAÛ~süüŋ_tëÖmM™6CSĻ͈´îĖ™ŗ1lˆœõāAxŨG´…¨Ė­ˇ´­_ŗJ .ÖÚõ4鋩ʚ%‹ú÷í­&ĘÁÁ!ÎõæŧĘvš2eä$ƒÁ`0Ũ6_m×Üų[sÜû¯5ĮˆMR3,íŧ<shę“Lģ¸8Ë3G%OžÜꘑ¸4ŦSCU*–•ãķ™iąŲč¨ÖlÜĻ‚ųķčĮ_~WÁüy”ŌÅEŽŽŽj÷NS 1>"ļRôĪt��IHĸIŦ899I’‚ƒGZūā~Ė÷›õ…_Ä ETŠbÜ>âEe@@ÔũŸ¯wŗ:wˇđ}ĻNž`ē÷˙EYŗdąēĖבĢkJ*TPģvīQŋ>™ĻžgȐ^2¤—$]šr5Öũ-ŊÎåüütčđaĨO—VšsûĘŨŨ]%K׈‘Ŗuíúu]ŊvMåâ˜!eíū äWžŧyĩk÷åΟOŠSĨRYŋ2’ū{ã÷čŅŖHĮxøčĄĖĄmŋ96nŪĒâŊęŗ!‘?É|úôŠŪm×Q{ŋũN ë×SÚ´i$ũ×ĸ2ˇŪŌúL—6­>8@Ÿ sįĪkŅâeę˙É ųøäTÁųÍŽK|´Ķccۍi&ÛãĮ‘˙ŋŠëü_UŸąå'å1ÃŌ>ÁÉŲY… ˆZ ^U+–5Ũî›ķüĩyĮuīØFysûh÷7?hæ‚eęÕŊŖRē¸č‚˙å‰uāđ×ķëß�ąK4‰OĪđOĒŽ?ŽÜžáŗõŋ_Sό­.īũ"ũ}âĪ?åââĸŦY2Į¸}Ū<yäā⠃‡Gšū|øčQšģšŲôÕĻyķæQŠäÉuįî=ÕÍémZ~÷Ū=%3$‹ķžø7M×NÔĢOÍ[°Hž˙^´õĮOœˆu_K¯sš˛~5fœR§JeúzĐĸE‹čâĨËÚĩkˇŧŊŊâ|scËū-[4ĶŌe+äīQM›42Ũöäíå%IúëÔi~û[HHˆ~˙ã€Ō¤Ž{–mûÍđđáC}ûŨ÷úlČ ß(–+ë§Í[ļŠaũzōöö’ŗŗŗūØĀԞž={ĻwÛuTËæÍTŖF5ŗëÍÕįåËWtúĖÕ¨^M’”ËĮGŖG×Ļ-[uöė9ĨN•*ÎõąŊ鏘Iō2íԚ‡˜ÚÚvŨÜ\õ0J"ôô™3ĻíÍ]Ÿš5ĢŋtŸ1wŒØŽqR3ŦéxũE$Uz}2,ÖmLIIĒSŖŠ$iæ‚eĒRŪOk7YūmR/kęØa‘ž‘—ĐPõ<2ž#�ħD“Xɞ-›ōįËĢŲsįË'gNĨōđЄÉSL3ŦuķÖ-M›1KM›4Ōųķ´ęË5jP¯ŽifLTŠS§R‹æM5wūyæČŽ|ųōę?håĒÕęŪĩŗÅ˙9žČŨÍM­ZĩÔ´3•6M.TPW¯]ĶČ1ã”%s&-^û×RžięÕ­Ŗ‡kĘ´:zė˜ęÕŠŖÔŠSëÖ­[úæģīôũ?ŠnÚŅîų—,ŋÎ~eJéÆÍ›úöģ4dđ@IáĪņɓ;ˇVŦZ­jÕĒÄŖ-û7nX_ã&LŌÕk×´w÷ĶōˇŪĘĒĸE kîŧđö–.]Z-]žR),˜ŽNÛ~3lßšKĄĄĄĒUŗFŒëëÕ­­Aƒ?Ķ­[ˇ•1cĩlŪTsæ-P–Ė™åã“SĢ׎Ķņj¸Ņrws3ģŪ\}^ģ~]=?뭁úĢZ•Ę2 Úē}‡’%KĻbE‹˜]“Túë¯SúëÔ)eɜÅĻvĩ slmģķį×ŪožSįŽäęæĒE‹—ęßû÷M‰sį˙*úŒ-×XJēc†ĩ}�oļ¨I•ujTŅõ›ˇ´rŨæ'øņšģYÖWG™­ �HzMbE’fLũBÕģm;(cόú į{:qâĪ8g+ÄæÍõāáC5nÖR?QĩĒU4bؐ8÷1l¨Ü\]õ؈‘ē{÷ž˛dÉŦßO={tŗõ”4ốōpw×ø‰“uëömeHŸ^ÕĢUŅĮũc~^țlÄgCTžŦŸVŦZ­‘cÆ)00PiĶĻQąĸE´lņUŦûô-šÎ*?ŸŽŸøSĨJ7-/Qĸ˜VŦüŌėƒ’mŲßÃÃC~eJ+000Ú§ŧͧLÖ !ŸŠ[÷åîîŽw[ŋŖ&jĪŪ¸ŋr™ļũfØ´e›J•,ĄôéŌŸžfõj<t¸ļmߥn];ë́dH–Lã&LR@` ōäέĨ‹æ+Göė’dvŊšú,]Ǥ&Ģ…K–jęô™rtpPŽ\>š7{ĻŧŧÂĒ×ú˜tlßVũ T‹Vm5wÖt›ÚiÔ2,aKÛ2x >4Då+WS*Ŋ͞šš5iŦ}?˙bŅõąõ¸/˛ä1IĒc†ĩ}�oŽØ’*’tęĖy8y:Ác ~lŅ7ũIRĐc+�ÔŒFŖŅßß_žžžöŽEÁÁÁ ‘‡‡‡iY›ö”:U*͞9ÍârŠ•ōSįŽbŧĨHHwīŨSĨĒ55qÜhÕ­SûĨËŖm°cŖ¸nį‰Íˉ#cÜ×\ReÁ˛/f[ /áã^Ũ•#ÛÛmë銾˜ĩĀĻãD\�€}%Ē+]ē÷ԝ;w5fÔĨOŸ^ß˙đŖ~ûũ-š?×ŪĄVš˙ü/úkԘņĘå“SĩkÕ´wH��ŧVkRE’‚Ŧ¸Ŋ'ę7��’žD•X™1u˛F¯ž|¤ ā`yæČŽIãĮĒj•J:pđēvīižû6 !lØ´Y“§LSŠ’%4aÜhĶCk�ĀĢҞIũX“*Ë×lTëæüŲ*ŦI–?ŽĮH�� !Q%VŌ§O¯éS&Į¸ŽPÁÚĩ}‹Ų2<<Üux˙o¯:4Ā*ŨētRˇ.^yš´m�Ö`ĖĀë,Û[YŖ-‹˜ŠōAˇōņö´_bŊįĻX3ģ�8%ĒÄJ\œœœôöÛoŲ; ���$ŠS{DúûÅÛ|ŧ=íÔsVÍX!ą�I^’IŦ����ž) 0P÷ī?ÔåĢ×´~ËNģ=S%*kfŦXŗ-� q"ą��€$gččIö!VÖ=c…Ä �$u$V���¨………ÉÁÁÁǝļįė•€Ā@‹ˇ ŠĮH�� ¯*��@ĸöũž_fEĸ$,,L?ėû5#ŠÛŠŗįuņōUŗÛ]ŧ|U§ÎžO€ˆ��ņ‰+���HÔļīūFÛwcī0,öøņMž9ßŪa��3V������lDb�����ĀF$V������lDb�����ĀF$V������lDb�����ĀF$V���€$Æ`°w�€$V���`ÜŨÜ$ŖŅŪa$mFŖRyxØ; �Ās$V���`ē´oÅt‹—•Ė ÛÚ; �Ās$V���`rzfWŸ÷ģĘŨÍM,VswsÕĮŊŪĶÛY2Û;�ĀsŖŅhô÷÷—§§§Ŋc�����HR˜ą�����`#+������6"ą�����`#+������6"ą�����`#+������6"ą�����`#+������6"ą�����`#+������6"ą�����`#Įø*øŸË×ãĢh����VōʖÅŪ!�Āk)Ū+éSģÅWŅ������‰ˇ�����ØˆÄ �����€HŦ������ØˆÄ �����€HŦ������ØˆÄ �����€HŦ������ØˆÄ �����€HŦ������ØˆÄ �����€HŦ������ØČŅŪ���āÍâåšÖlØĸĀ `={f´w8I†Á`kJĩ{§Š˛Ŋ•ÅŪá��žcÆ ���Œ˙•kZ°ôK= "Šb%ŖŅ¨€€@Í[ōĨŽßŧmīp��Ī‘X��@‚YŊ~ĢŊCHÚ Ī´|Í&{G�xŽÄ ���L@` ŊCHú = °w�€įHŦ����IŒŅČmT�XX�����°ß ‡ŠUk¨íģ­ÕŊkįXˇ7a’<ŦÍÖ$`d¯'ŖŅ¨ģvkËļí:{îŧž<~ŦL™2Š|ų˛ęÜĄŊ2fĖ`ī%IŗæĖĶōĢtā÷˙I’z÷ ÚgZīää¤ŦYŗ¨œ_ĩmĶZY2gļW¨x ŧÉũÂh4j×W{´yë6={N!ĄĄĘœ9“jTĢĒļīļVęÔŠâí|����K‘X‰ÃĮ}ûČĮ'§ŊÃx#<{öLĻŊß|ĢēĩkԐé��|IDATŠEŗĻruuÕŲsį´fŨzíŲŗW æÎ’¯o.{‡Ŗloŋ­áŸ –$?Öé3g´eÛvmÛžSͧNVņbEí!’ĸ7Ŋ_ ųl„víŪŖZ5k¨EŗĻJ‘"…ū<yRk×mĐŪožÕ’…ķ•>}ē„8•WnÍē :ų×_ũųp{‡��€—Db% ÔŗwoŒuë7jī7ßjėčĪU¯NmĶōŠĘŠYĶÆęĐŠĢ>4X[6Ŧ•ƒƒƒ#™KJ•,QÜôwÅ åÔĻu+}đQõ0H;ˇo–›ĢĢ#DRô&÷‹­Ûwh×î=úlđ 5oÖÄ´ŧZÕĘjPŋŽÚvčŦŲķækøĐÁņ}ņâÔŠĶö���¯HĸJŦ<}úTŗæĖĶž¯ŋŅŨ{÷”!}zÕĢ[[={t“Ŗcx¨å*VU—Îõŋŋ~ūų˯Li6DiR§–$…††jáâĨÚũõ^]ŋ~C™3eRÛ6­ôN‹æĻcUŠ^[ŨēvŌõë7´gī7 RąbE4|čĶ' QoēuëļFŒŖÉŨÍM-š7Mā+ôúúrÍ:ų•)éÍc„4ŠSĢ_ŸÔģß�ũī—_UŠb…mļruMŠaC?U“æ­ūßŪĮEUī(.˜ Ļ0 "î]+5ĶĖܲĖ-3Ķ[î–ån.YĻ-îkjϏīûnyoÚōëw뗹T×p siQdUøũ126*0†x=˙˜3ßųžĪ9Ķ™yķ=ßŖ]ģ>Ōķ]:ßQ(zŠōyąvũFÕŦQŨ!TÉŦ% d ´/sæúáĖv9ĶÆ™}x-5U .ŌŽ>V||‚džUŅĐÁuÚęÕ÷e:ü$i×îĩaíJ…W­ę°‡§yķčøņ˛Ļ§+ŦJ¨ xÅ>Ę'ģūŨwëx��(ę\jōÚ “Ļjێ]6dļm^¯¯žŦu6jÖûsímĖîf-[ąJ=đ€>ÛģG׎RTT´ĻN›io3sö\-_šZ}{õÔæ kôÂ?Ÿ×´ŗĩuûN{ww-[ąJ•+‡hĪŽíÚ˛iŽFFiáĸ%YÖ÷æ¸ˇuōä)Í{ĻGĖWllŦö~úyŪėŒ"ä…‹:söŦÔ¯—e›Ė÷:,Š`ƒÜ VP`%û(ĀYEųŧˆWtô1=Ü ~–}T ¯*///ûkgŽÎl—3mœŲ‡3gŊ¯­ÛvčĩĄƒĩ$âCUĒTQũ ÖŲŗįôūĖiĒ^-\O´jĄ/>ũˇĒ„†:l[RR’ ސ`­\žXĢW,QX•*zeāÅÅÅåØŋŗûãn/���EËŒX‰ĶŽ>Ö°!ƒôDĢ’¤ĀJ•sZĢ׎×āAäéá!Éö…:ķ6‹%Hžé ˆÅK56)IÖôtmÜŧEŊ{v×Ķmž”$*22JK—­PĮömíë VûļOK’ü˗WŖ† u42ōļõũūĮÚ˙íŊ>j„ę×{H’4zäkúŋoöįÍ)B~˙ãIR…€€,Û/^\e˖Õ.ؗÄq`D@@€.^ēt×úCŅP”Ī‹‹mËī읂S}åæúáĖve×&>!!Į}˜˜¨-ÛvhØājÕŌVĪ[oŽQrr˛Îœ=̊ëËÍl–‡‡‡}dČ_ũöûīJHLÔS­ŸPHp°$iԈajÕ˛š<==sėßĮĮ'߯§€+2›ÍząË3 ÖĘõ[}ĸ@ęhūX#ĩmŨB&“I’mbî{öjß˙)z��wŸË+ŅĮŽÉjĩĒv­šËĢW Wrr˛~ųåŒB+‡H’ÂÇL‡VŅĩk×táÂE]ŧtIŠŠŠˇüĨķęjëöJLL’ˇw IR•›&Ļ-YŌWqqWn[ߊS1’¤š5Ēۗ™L&ÕŦQCQŅĮrŋÁ°ssŗ œJMKËļ]FzēÜL7YÄq`DZZšKÎ×V”Ī‹ĖîNõ•›ë‡3ە]›cĮŽį¸Ož<Ĩk׎ŠfÍ× O͘:ŲŠí TPP Æŧ9NĪvꨇÔWĩđĒzđē’¤ū{$Ûū÷{ ߯§€+zū™ļĒ[ĮvôëŪU‹W­×O‘y˙mĘÛ¯ĢÄ_FÔŨĖd2ŠŨ“-ÕîɖöeIIÉ5~Rž×�Č.Ŧ$&&JŌ-_ŌJ”°ŊNJJēąėĻ‹Uņâļ×WŽ\QÂõ~úŧôŠLi“ž‘!IēxéĸŧŊ¯ޏĶõeŽ˙æĪdÖãüũËK’Ν;Ÿe›””ũyų˛Ŋ­T0Į§ųÅ>Ę pVQ>/î)wL&“N˙ō‹S}åæúáĖveׯ™}xåJüõ~˛ūa•ŗŲŦåK"´|Å*mŨžCsæÍW€ŋŋŧō˛Ú<Õ:Įū âz äĩļ­[¨éŖ åžÃ*ž=ôŊÖmŪĄZ5ÂõÅWߨVp•đō’ģģģ^xŽŖFw.āĖwϜ›��\—Ë+>>>’¤ÄÄ$‡å™¯}|n<9â¯_ mml_ūJ–*ĨkŠŠ’¤‰īŽŋåžuI đ÷7TŸ——í‹vBB‚Ãōø„xCũáŋ2eT9$D˙ūd¯úöîi˙kõ_í˙ö€$ŠŪC7~ˆÄq[‡ŋû^.\ĖvŽāvŠōyáãí­jáUĩk÷ĮęÛģ§<==oiķÉžOUĖĶSMmœĢëĮĘ|ŠQvû03øČÜ÷Fø•)ŖaCiؐA:yę”VŽZĢ7Ū¯ā`‹J—.•m˙Ž~=Œxü҆9Žū<q*F[wũKũztSĩĒĄÚŗ÷s͍Xށũz¨„——NŜɗZGcä �5.Ŧ„UŠ"ŗŲŦīž˙ÁaøōGŽČ×ĮG•*Ų—Ũ<ááŅČHyyyÉŋüŊōķ+#O]úķ˛Z[ėmūŧ|Yn&ˇÛ~Aw†%(H’}ĖūԅÔÔT8xXĨK•2Ô'nøgˇįõöģ´aĶuéėø´‰ØØ8͘=GáUĢĒAũ? â8ȍ¸¸8M˜4E*¨e‹æyž>üũåķĸ[×.zcėx-\´D_íīđŪ‰“§ôî„Éjūøcjōhã\]?îTXX•÷ĄÅ¤âŋëāĄÃöëEzzēúŧôŠ:´kkŸ¯$ãúȏ›={NĮOœPĶĮšH’*‡„čÍ1Ŗ´s÷G:yō”š6m’m˙=âŌ×SˆĖPeāȡ˛lãn6ÛCIjŨĸŠ$inÄr5mô°ÖoÉŋI—gM|Ëūޜ¤ĻĨiؘwō¸"�@^r™`ĨtéRjßîi-Yļ\•*ĒjÕ0:ô6lÜŦ/ūĶáâôĮ… úpá"ĩyĒĩ~ūų´6nÚĸ'ZļPąbÅTŦX1uėØ^.ŒP™ŌĨUŗFuũúÛoš:c–Ęß[Nķ۟e¨ž TģVM-]ļB•*Ę͌ÖŦÛ`Ÿ�wĻCģ§učĐaMš2Mß}˙Ŋš6iĸ%JčÄɓZˇaŖ2224gæt‡ŋÚÄq•ä¤d8xH’-p;~ü„֎ߨ¤äd͟;›ã†åķĸ͓­učĐa-^ē\‘QŅzĸe y•đRdd”6lÜŦāā` 2HRîŽwĘ×Į'Į}čëãŖížÖ’e+ä_žŧ‚ƒ-Úŧu›~:ŠņoŊ!I*U˛¤ĸŖ)*:ZūåũíŖP$ÛäĩÃFŒÖAÔ¤q#™L&}´į_rssSÚĩrėßÕ¯§@^¸9TÉÔēESũúûZĩakžÖ“œrUž>Îũŋ'%9%Ģ�ä5— V$ÛSvŧK”Đ„ÉSõįŸļyúöîŠŪ=ģ;´ëØžâŽ\Qˇ{ęęÕkjōhc9ÜūūˆaCTŌ×WŗįĖĶ…‹uOŲ˛zŦIc ¸é¯žš5yÂģzûŊ‰<ô5ųøøčŲNÕæŠÖÚ÷\žS&“IīŊ3N 6Ж­ÛõŪÄÉēzõĒʗ/¯V-šĢWĪîō+SÆá3uÜΙŗgÕįĨW$Ų&Ŋˇ\9=ōČÃęĶĢGļOu˛SÔĪ‹qcßPũzõ´qķM>SiVĢ*ŪWA}z÷T—Νˇėėõãnpf<P2™4sö%&%ŠJh¨>˜3Ë>ZäųįžÕoWŪ/iÆÔIz¤áÃöĪ>ø@]Ŋ;ū-­XŊFķDČŨlVHH°fMŸĒ  @§úwõë)p7eĒHRdô ų)*ßkJNN‘¯“ˇ!&ĨŦ�@agĘČČȈ‰‰‘ÅbšĢĮĮįÍÜ#>ŪB˙ėúŧúõé•'ũŖpā8�nÅyÜāxAAķî´\fîTÛ­27ß ”S¨ą|ŌŦVc…ہ×öSPĨŠNĩųåŦfĖ‹0´ž‰cGäĒŊ¯¯¯Ąõ��˛į–s���Āĩ¸j¨"IIš¸Ŋ'™[� Đ#X��@ĄĶšC›,C•ë6ëųNí  *›Ü„%É)ÉyX � ?¸Ô+ÎøōŗŊ]\�Įp+Î äĮ ģJ÷U¸eYæH•WûvWhˆ%ß'­Í”œ‹ySr3ē�āšą��€B§té’¯˙zûOhˆĨ`Šē.W#VV� Đ+t#V����Ģ5] ‰‰ŠŊĸ3įÎkãļŨ6§ĘÍr3b%7m�މ`���…ΛīåūéBų%wsŦŦ�@aG°���—fĩZe6›í]vFAŽ^IHLtēmbbRV�ČĖą���—öŲ—_˚‹ Äjĩęķ/ŋÎÊ˛yė„NŸ9—cģĶgÎ)ō؉|¨�—ą���—ļsĪ^íÜSxžd•’rUĶį.,č2��ų„+������Ŧ������D°�����`Á �����€A+������Ŧ������D°���2&SAW��ČD°��€|ããí-edt…[F†|}| ē �Āu+���Č7];ˇg¸År3Š{×g ē �Āu+���Č7–ŠÔ¯g7ųx{ËDĀ’kŪŪ%Ôŋ× ¸ˇ\A—�¸Î”‘‘‘#‹ÅrW;ŽŋĢũ���0Îחۇ� /0b�����Ā ‚������ƒV������ "X�����0ˆ`�����Ā ‚������ƒV������ "X�����0ˆ`�����Ā ‚������ƒV������ rĪĢŽ/Æ&äU×����rÉ×ס K�€ŋĨ< V‚+äU×������.[������ "X�����0ˆ`�����Ā ‚������ƒV������ "X�����0ˆ`�����Ā ‚������ƒV������ "X�����0ˆ`�����Ā ‚������ƒV������ "X�����0ˆ`�����Ā ‚������ƒV������ rŠ`Ĩ_˙ Ģîđ¯JĩZjÚü Ŋ7q˛âãã ¤ŽēõÖŧų ōtËWŽR“f-UĩF5kõ¤ļmߙ§ë“$ĢÕĒÅK—Ģeë§UŊv]5kõ¤.Z"ĢÕZāĩ����P¸t7 ŦTI“'žk}íÚ5ųņ'-ŒXŦčcĮĩrŲb™LĻŦPZšz­ū{äˆĻO™tWú[ˇ~Ŗ&NžĻ׆ Öũuęčë˙ûFÃGŽ–¯¯š7{<Īj˜9{Ž/]ŽaCéū:ĩõ큃š:}ĻÜL&õíĶËpm�����.Ŧ”đ.Ąõë9,{´q#•+wFĢC‡ŋ̓Ô- ęl~üņ§ģÖWFF†æ/ˆĐ ŨēĒ_ŸŪ’¤z=¨'Oéƒf^Üi ŠŠŠZąjzõxQ/õĩ­ˇ~Ŋ‡­ŨīQß>Ŋ ×����@QáRˇeį÷ב$ũúëoöeiiiš=gžšĩzRá5ī×ã-ZkõÚuŸûöĀA=×õÕŠ[O5īPĪvéĻo´ŋ_ŖÎZ´xŠÃgFĢļ:ŨļŽ.Ũ^Ôæ­Û´uÛ…„U×ŅČČ×1eÚ …†×ŧm11§uîüyĩhîR4{ü1ũđß#ŠOHpdžk׎iŌ”ijظŠÂĒ×VŖ&Í4}ælĨĨĨŨvŊfŗYģwlŅËũú8,¯P!@ąqq†k���� ()4ÁĘĪ?ĮH˛ũđĪ4iĘtE,^ĒW_~I˙úh‡z÷ęŽw'LֆM[$IIIIę͝ŋBC+kËĻuÚļyŊÂĢVUŪũw=<Č­E >PÍÕÕæŠÖ:¸˙+UĒX1Įu„VŽŦĻ5šm§blÛXÉayP` $[¸‘S UÃÂ4vÜ;Ú¸yĢÆŒŠŊ˙ڭ׆ҊUĢ5yęôÛŽ×ÍÍM–  •*UĘž,--M˙ųękûˆ #ĩ����P”¸Ü­@’FY¤ĻĻęȏ?iÂäŠ ĢĒē˙¸_’Ÿ Õk׊˙K}ÕąC;I’%(H?ūø“,\¤įž}FįũU ‰‰jßöi…VŽ,I7vŒÚ<ÕZžžž†jķõõ•ŲŨ]žžžō+SF'NžĖqĪtl¯g:ļŋm ×G}øúø8,÷öövx?ģ._ŽÕÖí;ôú¨jķTkIļđãäÉSZē|ĨFŽ.OˇmڌY:söœæĪ›c¸6�����Š— VĸĸĸVŊļÃ2“ɤ&6ÖÄ÷ŪļO\ĨÔÔT5nôˆCÛúõëiÃĻ-JLLR°Åĸā`‹†Šn]ģ¨qŖGTŖz5Õ¯÷Đ]Ģ7?֑“Ȩ(Y­VûíR™jÕŦĄäädÅĜVX•Đlû˜2m†VŦ\­?˜Ģ`KP^– ����Ā߆Ë+Á– ͚1Íūz՚ĩúâ‹/5kú‡ÛV2į÷čúBũõAé’¤ /ȤëV+bҭߏIĶfĖR…€� :XÚˇŊ+õšÍæ;ZGIߒ’¤+ņņōõõĩ/ŋråŠíũ’%sė#s䈏ˇÃōĖ‘%‰‰‰Y~6==]oŒ§ŨīŅŌÅ Õđáwĩ6�����ūÎ\.X)Vŧ¸j×ē1ŅëŖGéĶĪ>×äŠĶ5iĮ0gŪž2kúU ģĨŸ ļšXĘúųéõQ#ôú¨:~â„/YŽá#G+4´˛jÕŦqÛG7§¤¤äĒæœÖ‘‹$Û|%÷U¨`_~ęįššš)$ؒãú3C„Į�%ķĩ¯¯Ī-ŸÉ4ū ú÷Ū}ZŗršÃ~ŋ[ĩ����đwæō“×–.]J#† ՆM[ž´S­Z¸<=<tņԟĒ\9Äū¯t™Ōōķķ“§§§Îœ9ĢŊû>ĩĻJh¨Ū{gœÜÜÜtėØqIļQWâã֝c]×GÆ8ŗŽėʤOöîsXžwß>Õ¯÷ŧŧŧrŦĄZx¸Ėfŗ:ėđūáīŋ—¯,AˇŋĩgëļÚ´eĢ–/YtK¨r§ĩ����P¸Üˆ•ÛyŽs'­ß¸IcƎĶĮģļËĶÃCž>>ęŌĨŗfĪ™+ŋ2eT§v-;^īL˜¤�˙ōZą@įũUũ Ö¨ÃÕŦéc2™LÚžs—ÜÜÜė“āÖĒQCŸėũTŊzt—ˇˇ/YĻËąą*īŊYÖSĒdI=ŠŖ‘‘:ūˇ׹mûN}˛oŸ>ŧ>)ėÍŧō˛FySūūūĒûûõŲį_čķ/žÔš•ËœĒ!Ā?@Īvę¨FȨęÕĢi˙ūZĩz­úõé%w÷[˙3§¤¤húĖŲzŦIc%%%é›ũß:ŧ_ˇî?äéáa¨6�����Š SFFFFLLŒ,KA×ĸ~ũčėšsúxįļ[Ūûá‡˙ĒÃŗ]4xāĢ<đUIļ§͙7_[ˇíĐ.¨Ü=÷¨yŗĻzmøPû­BÛļīÔĸĨËsZîfŗĒT ÕĢũ_ÖãMm?Ž9}Z#GŋĄŸŽFĒTɒzŽs']ŊzU_ūīWÚŊÃöØæēõV¯Ũ5ā•—%I_üĪ—6b”Ž^ŊĻįŊ¯K—ūĖvSĻÍĐĸ%Ët"ęĮ,ˇ}՚uZ´dŠ~ûíwY‚‚4tČ@ĩnÕ2Ëö7×Đ A}MŸ1K;w¤K—ūT@€ŋēt~Vũ_ę{Û۝ŽFFĒMģg˛ė˙W_Ē\š{ Õ����@QáRÁ �����@aâōsŦ������¸*‚������ƒV������ "X�����0ˆ`�����Ā ‚������ƒV������ "X�����0ˆ`�����Ā ‚������ƒV������ "X�����0ˆ`�����Ā ‚������ƒV������ "X�����0ˆ`�����Ā ‚������ƒV������ "X�����0ˆ`�����Ā ‚������ƒV������ "X�����0ˆ`�����Ā ‚������ƒV������ "X�����0ˆ`�����Ā ‚������ƒV������ "X�����0ˆ`�����Ā ‚������ƒV������ "X�����0ˆ`�����Ā ‚������ƒV������ "X�����0ˆ`�����Ā ‚������ƒV������ "X�����0ˆ`�����Ā ‚������ƒV������ "X�����0ˆ`�����Ā ‚������ƒÜ$ÉŨŨ]VĢĩ k�����(4ŦVĢ-X)^ŧ¸ ē�����€B#!!ÁŦøųų)..NąąąJOO/čē������\–ÕjÕå˗/SFFFFæÂK—.éęÕĢÜ�����ŗŲŦbŊŠlŲ˛7‚������äO�����0ˆ`�����Ā ‚������ƒūŌ—w$œ1}.����IENDŽB`‚��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/usecase-register-plugin.png�����������������������������������������0000664�0000000�0000000�00000202767�14156463140�0024144�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��â��–���3ő]���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨg|TeūūņĪL&Éˤ÷B „‚"EĒ E@Ē,"*ŦĢëēWŨŸu­ÛWw­#ŌA)‚ Ѝ ĸ ŠJ'RCBz&}’Ifū"Q¤%0”ëũræœûūž¯\sŸrÃÔŠS]ͧOGDDD.ŽiĶĻ €ŅÍĩˆˆˆ\ÕÄ"""ndĒËĘÅĨe¤Ļga/¯h¨zęäāáÃlŲ˛•2{šģK‘‹Ėböfė¨atjßÖŨĨˆˆ\:qjzV‹7!A UOĖ[°ˆ ‡ÃŨeˆ”ŲËYŧtĨ‚XD.{u:5m/¯Āb67T-uĻžēéLˆˆ\ tXDDčÄ"""n¤ q#ąˆˆˆ)ˆEDDÜHA\ -›7ŖKįŽî.ã’1áÖá4Iˆ¯Ķ6wÜ6ēĒšŧ™ŠŠŠęĨĄĘĘ*^zųU�:D\l žž^ ėߗļmZ×KįãwwNÂËËûOē˜ÍŪŦųčö8Të6öė˙ņŒŸøĶ¯wŪ}oõ×z%{kū;î.ADä’QTT„ÍfĀdĩZëĨQ“Ƀ?ũņ~�ž˙Û?škĘ„…†°äŨeŅŊ[WJJKųĮŋ^d@˙žlŲēø¸XŌgĐŖ{7:vhĪō•ĢČĘĖĻĘYEĮíčÖĨKŊÔöÎ{ĢIKĪ� MË\ßĨ3ûbp˙ ÆÃčÁŽ]{ŲöŨ$ÄĮ1d`2˛rđōōäxF&ÅÅĨ˛aĶWL;’2ģ__>øøSnčz-âbh×: — ē^ۑ’’RJívVŧ˙!:´ĨsĮvTUV‘< N§€ą#‡RYY‰Ũ^N“„x-[IvNŪ)5y˜<°˜ŊųlĶWŒ5Œ2{9+VČÍúpôX;vī KįŽøûų˛îĶΉŠgđ€>ŧ9wŖ† ÂĮjÅËĶ“Ŋ)ØôÕ×§Ũī‡˙đ[ŽMãĀá#lÛūCÍą{ėÁûØžc7^žž2gáģ5ŸŠĪÛÆŒĀ�äŲ čØļĪ˙ûč~žûį˙θßŋlsâ˜�lŲļyK–×Ë˙‘K‰ÕjÅ×רãĖZįŖwĪ$ŋ=‡îŨē˛íëočÖĨ ƒ+ˇŽŧ…ââūņŸ áCüé÷ãt:yō™įéÜŠžĻ /qÔĐA”ŲË $+'—•|Dtd ņqŧöæ <pÛwėbPŋŪŧģō22ŗ¸mô-¸\?ˇ†Á�‹—­ÂdōĀĮjåëoŋ§ŧĸ‚víåɇā¯˙yGe%“ƍĸQ\,UUUØËËy{Áģ'Õät:9–žÁ–mÛéÕŊ+­“šŗ˙ĮC§ÔôFō|Fß2˜Ī6}…ÕjÅbą�Фq#Ö}úų9÷Ŋe‹D^zmÅ%ĨÄFGqŋM&Ÿ|ž™ėœÜ“ļ7™Llũf;9šųÜ:|0-[$žĩŋFq1x{y‘<o1AôîŪõœûũŲϝj>߲m;�‰MÂ"rUhđ Ãhô ##“¯ŋŨÎÔģϰs×.ÂBCđõõĄ¸¨”Ŧœ\ōķō™™<�‹ÅLIq1\ÃŌU–žÁ ŨŽ%$(ˆœÜ|ÚļJ"(Đŋfôeˇ—ãcĩčO^~õé‚ôŒĖ“Ú9’zŒ=û~äŽIã¨Ēr˛jíĮ5Ÿ™ÍŪøX-Œ9�+~ž>�äæŲN[—ÍV�TĪæëc%$8蔚œÎ*ĖŪŪDF„““‹¯•ā @ŽZÍ,ļdųûŒ1ĢÅĖÆÍ[pš8í~WיĘö.×ĪõāwÖūüÉûétKž­�{ųŠŗ_ũzŋmËļí5,"rĨkđ †ęQņĒÕācĩâīW=ĪĖĘĀfŗáįīKXX(ááÜ=e2�ŠiiõÂŋ´yË7<ōĀ=|ąe9šydåäՌēĸ"Â),*ύ¸„�?˛sr‰ŠŒ ũøĪaĀžĢOī6m܈}{ōåÖo1 ØíåŗāŨ÷p:„†c+(¤MËæœ4Ŧ>‹3Õtčč1úöŧžmÛĀßߏAũodoĘÉ×­GÍ؃ Ÿæ÷ōōĸĸĸ‚äy‹ņōōâá?ü–7į,:mgb0@Xh0YŲš„ąs÷>âcĸĪØį‰ãwâx™ŊŊkĩī""WĢ‹Ä×tėĀ܅‹˜úSČBõ/ņų‹–pėX#† !.&†ØØhĻĪLÆQé **’¸˜˜z­Ãét˛æŖO9t3fĪ'=#“IãGáiō$#+›ÕŽįŖO62nÔ022ŗđöōNŅŅÇPRZŠ——'7o%;'—æÍšp]§ŦúācîüÍXĘ+*đ0™ˇdEę;ž™uښvīŨĪīîœÄ’åīããcåļҎđŅ'NÚ6åĀ!ēwŊ–!7õ— PYYI¯î]éĶ̓*g_nũæŒ}œIU•“m[ŒÕbaĪūi×:éŒ}:’JīērۘS\RZ§c "rĩ1ÜyįŽYŗfÕjåíģRˆ‰ Ģs'v{9˙ųīËüųĄ1 lūj 99š 2¸ÎmũŌŗ˙Īm:1Q‘Øívrķm ŊŠĮŌ3ØūÃÎzīįrqâ&ĢÚōđđ EbvīMÁËˋ˙wī]üíÅ×Ŧž—˙ņLƒĩ-"ŌPîēë.ĸĸĸ€‹0"Ūĩ{kÖ~ÄČ[†a4ēģ f2yđ›qŖČŗŲ0yxđÁēOŨ]ŌeĨĒNJö­[ŌŊËĩxgm‹ˆ˜ †† ĮÖ­ZŌēU˓–]ßĩ~KjGRĶxéõ7Ũ]Æ%Ŗ.Ŗá.]Ų�•ˆˆ\9 CõœZšYKDDčÄ"""n¤ qŖ:ąŲˋ2ģŊĄjŠ3OOOw— nd6›Ũ]‚ˆČĢĶ]Ķq1á¤Ļe‘g̟E\¨î×wåĢ-_cŋ„žČÅa6›7j¨ģË9/_~ũ->ž)@ƒØ×jĄebŖ)ę|tlČ¨›ûģģ ‘ķĻkÄ"""n¤ q#ąˆˆˆ)ˆEDDÜHA,""âFÆc›Ūuw """W-ˆEDDÜȘYáîDDDŽ^Ƃ\w— ""ruųå ˆMBjŋaqiŠéYØË5Œ9Á×ĮBldŗw­Ö÷øéĪØč(LŪg]÷$ŠéYX-Ū„ÔŊʋ$-#›Ž­Ũ]†ˆˆ\Eōl…ËČ&1!ļVëWŪĀąôãKë0¸ĩ—W`ŅoDDDNčOqIŲymk<bĢįjDDD¤ÖŒaQî.ADDäęeėpíîŽADDäĒĨ =DDDÜHA,""âF&wpšÛôå×8*+ņõą’›oãĻžŊÜ]’ˆˆ\Fę5ˆß_ŗ– Ÿo♧Ãbļ�đ—g_ā/O<ZŸŨ\°ŽĻ ņ'-ģ{ō<Mú^"""WŊ'Oˤ,\ôS&O:iųáÃGYöŪJüũ)(,äžiwōũ÷;Ųōõ6âãbIųņ�íÛļĄ´ŦŒũ)?ōÛŠwb6›Iž=ŗŲ›ĸĸbF B\\í–>o/O~w×Éõ•––ņßדšīîÛÉÉÍcÕڏų픉,^ļŠvmZŌĒÅĪ“„<|”ÕŽ':*’Âĸ"âãĒ—:ÂâeĢ8ž™Eß^ŨiÛ*é‚k‘+[ŊqÛ6­Ųąs7ß~÷=×th_ŗŧŦÜθ1ŖˆŽŠbîü…ėŲģ#ūūūŒ>”Ö­§  ŅˇŽ`ųĘUėOų›­€ØØ†ž‰ŦŦlæ.Xăüū‚k,¯p0cö‚šŸ##Â6¨?Cõcųûkɺؘ8f$ƒqŖ†˛ũ‡Ÿl`ÔđÁDGF°t嚚åŪŪ^Œ9”Âĸb^sŽ‚XDDΊūĪÅē\L{+˙ø÷iÚ¤qÍbO“k?\‡ˇˇ7‡!ąY3<<ŒúWâi" Ā˙§u=q8dįd“ššFFF�ƒáÔū΃ˇ—'S'O8ey‹fMXũázš$ÄāīwÆímļBB‚‚� ÂåĒ^€ŋŸ/Å%%õR̈ˆ\Ųäĸ¨Ųlfܘ‘ŧ=wFcõdÖ ŋË´ģī$<,”×§Ī…“sŨ´Nhpú÷Åáp››×åÖøöû4k’@úņL2ŗrˆ=íz~äæįAfváĄÕëåäæPXTŒŸ¯oƒÖ*""—¯n×^CTT4ЀwM7OLäûīwrøđQ�ÚĩiÃŧ‹ &:*ŠõŸl¤Oīgmã†ëģņ֜yŧųÖ éÕŖ;‘‘\[y…ƒ7’į´ŦOĪëŲ¸y ÷MLAA!s/㞊“Yēr íZĩ¤e‹f5븹'‹—­"2"œ˛2;„ēpš\TVU˛tåŌ3˛¸y`Ÿ ŽSDDŽ|†ģîēË5sæĖZ­ŧ}W 1‘a \Ō…ŅۗDDÄļīJŠuūÜ}÷Ũ5#bMč!""âF b72šNÜō+"""…ËåÂår‹ˆˆ¸•‚XDDčÄ"""nT§ 6{yQfˇ7T-"""—Ĩ<[!žVËym[§ =âbÂIMË"ĪVt^],ÛwĨ¸ģšŠøZ-ÄFß<u b_Ģ…–‰ÎĢ#9•Џ¸ØŨ5ˆˆˆ\UŠ‹‹)((�ĀāærDDDŽ.„„„�ēkZDDÄ­Ä"""n¤ q#ąˆˆˆ)ˆEDDÜHA,""âFušĐŖ¸´ŒÔô,ėå UˆˆČeĮ×ĮBldŗwˇ­S§Ļgaĩx¤gĪ%-#›Ž­Ũ]†ˆˆ\yļBŽed“˜[įmëtjÚ^^ÅlŽs'"""W˛ā@ŠKĘÎk[]#q#ąˆˆˆ‹JJŨ]ƒˆˆČUËčįcuw """W-šqŖ‹ÄĢÖŦ%5õX­×ßüÕVžŋĻ+’_ŗōōŒŲî.CDäĒR§įˆĪåũ5kųōË-„†…PUåÄĪח[GŨBHp0CßTŸ]Õųë?˙Í˙÷ÅđúôYŒŧeá­ŽsyđņįhšŌ˛ģ'OĀĶTģ–ÃGS™1{ņą1¸\.�öíE“_ĩųK3f/`ęä į_´ˆˆÔĢz b€^=o`@˙ž�ėŪŗ—W^Îũ™ŲsįqÃõ]ÉÎÎåÛīžĮËĶ/o/î˜4‘äŲs0=đķõ!õØ1&ŒsR›ķ,ĸ´´Œâ’ēvš_ļ}ķ-S&OāŲŋūƒßß3•ĀĀšm:´oĮÜ‹yøÁđōōŦY~øđQ–Ŋˇ’� šgڝTØ+øĪ˙^ĄC‡v¤ĨĨ‹ĶåäĐá#Ü4°?­’Z°|å*˛2ŗŠrVŅąC;ēuérÁĮĘÛ˓ßŨ5é”åĶߚĪMũzÆË3fķÛ)yíĮ´kĶ’V-Nž$$!>Ž&Xm…ŧ6kĶĻL¤´´”÷?\ŋ¯/…ÅÅÜqÛžÛą›fŨ§Ÿ“Ôŧé)Ÿ—°héJ ‹ˆ‰Š`ČMũØ˛m;?ėڃÉdĒūr5|0[ŋųŽvíÁĶĶ/OOÆß:œĖėŪ[ũ!ūū”–Ų7r(‹ž;9›zâ_jÕ2 “‰ôãé5ËžÚú5Ç ĄY“ƤĻÃét‚Á@“ƍčÕãļ}ŗõŸ}FBŖF�8*+‰‹Ŗgîķ¯_â/?Æâw–Qf/ŖĀVˆŸ¯īI! @§Žywųō“‚ŊŦÜθ1ŖˆŽŠbîü…ėŲģŸfM›R^^ÁˆaC9r4•ˇį.āÉĮaĪžũ|ķ͡øûúpāĀ!ūôĮûq:<ųĖķtîÔŠÖ#×3)¯p0cö‚šŸ##Â6¨?nÎė…īĘÍûāëceܨaįl/0ĀŸļ­“ØĩgQáŒ2ˆˆđP–ŦxŸũ?âēkÚŗ~Ãôŋą)ōy|l4Å%%Œ1ŖŅČķ˙~…>=ģc0¸}Âhŧ<=yū߯PfˇŗíģÜŋ ņą¤ĪĀét˛ęƒuôŋą'ÅņåÖoØôÕ×ôŋąĮ#‘+]ƒ1@UU%VĢĨæį‰ãĮ˛zíG,]ēœV-[W=XXh(�ūØlPÃ0“›Ëœy đđđ ĸ܁Ņh ËuŲēey6zõč~Jŋ.—‹n]¯cû÷ßŗ{Īۚåž&Ö~¸ooo>Bbŗf5ũxzš¨ūģ—ÉD…ÃAVN.ųyųĖLž €ÅbϤ¸ø”đ¯+o/ĪĶž&öķķ%!>–})3bHÚŦĒĒÂb6c2™øxÃ&ŧŊŧ8ššFĶ„F'­wĻĪÃBC0Ģo đŖ¨¸€wWŦÆÛۋ˛˛2ŽJFÂēĪ>gåëhŅŦ 1Q‘ääåŗņ‹-lúęk***ˆŧ„.ˆˆ\Ē4ˆwîڍ‡‡‰ā āše9ššLš}".—‹üû%Ží| �™YY´j™DvN.AAA5ëīŲģ—ÜÜ<îžs2éĮķÃÎ]�ôŧázۘ™ŒĶédØÍƒĪXÃonĪ‹˙}?_�.~—iwßIxX(¯OŸ… į9÷#,,”đˆpîž2€Ô´´ áŗÉÉÍãXÚqZ6oƖmÛéŌšc­ļˡ°sĪ>nėq=ŗæ,dō„Ņ„ņÖü%5א]Žęũ]ļęƒĶ~ž›—ËåÂ`0`+(Âbö惏?åɇ Ęéäû{pš\äåį3áÖá¸\.^™1›ŽíÚLßŪŨ‰Žĸ¨¨ƒQ7勈œKŊņ†M›Ųŗo?Ngfŗ…{īšû¤Ī>‡ëÖãįįKHH0aaÕ#á´´tæÎ_Húņ î¸ũ7üxā��11QĪČ$ųíyDF„c5[øjË×tír-VĢ•¨Č<<<ÎXŸ¯/ˇ ģ™˙÷*ãĮŽĻ]›6Ė[°ˆā`ĸŖĸX˙ÉÆšQņ™ÄÅÄÍô™É8*DEEsGĒúÔôÉķNZ6lP?–ŽZ˸‘à āåŗiÚ8õ7ŅŽUKZļ8šÖÊĮ˜1{.—‹ĒĒ*&ŽI€ŋ­’šŗdÅûÎÆÍ[h×Ļ%fo3ËV­=íįqąŅøûąbõ‡äæåsMģÖøųųĘÜEËđņą”Ø”uŸn$ĀߟO>ߌ¯AA„†1d`_VŽY‡ÅbϏ¸„‘ÃáĢįÔEDÎĘ0uęT×ôéĶkĩōö])ÄD†Õ{ÉoĪå†ëģŌ<ąno+úßĢo0nĖ­„˙æ—Ŋ}IDäę˛}WJ­īO›6ččhā"\#nyųyĖŋ˜‰‰—d‹ˆˆÔÖ%ÄSn˙MÖ æ÷ŨĶ@Ոˆˆ\<ē›FDDčÄ"""n¤ q#ąˆˆˆÕ)ˆÍ^^”Ųí U‹ˆˆČe)ĪVˆī/f‘Ŧ‹:Ũ5NjZyļĸķęėjŗ}WŠģK‘‹Ā×j!6ęüæŲ¨SûZ-´LltîEDD¤VtXDDčÄ"""n¤ q#ąˆˆˆ)ˆEDDÜHA,""âF b7R‹ˆˆ¸Q&ô(.-#5= {yECÕ#""rŲņõą†Åė]įmëÄŠéYX-Ū„Ôš#9ˇ´Œl�:ļNts%""RyļBŽed“˜[įmëtjÚ^^ÅlŽs'"""W˛ā@ŠKĘÎk[]#q#ąˆˆˆ)ˆEDDÜHA,""âF b7ē䃸°°ˆy ÷ö¯OŸuʲO>ÛĀGëÖ_p›7mæ¯˙øGS^P""rõĒĶsÄįbŗŲ˜3!&“ §ĶIĨŖ’É“n#00đŧÛô÷÷câ„qįŊũ=Ķî:īmiķW[đ4™¸ļs§š6wíŪø1Ŗ‰‹gâ„øzé§6Vŧ˙[žū“‡ĨĨetŊŽŖG?ãú?ũååX­�JJKųÍ¸Ņ´o׆ĮŸ~˛2;ËĪĨũfÂZ$6;k?wŪķ^˙īŋ0™<x}æ[äåÛđ4™(,*bÂØ[I?žÁÆM›)**&7/Ÿ„FqøķĐ÷1rüí´JjqROūßC<ķמąÎĶq:§íģM̤3öQ\RĖĢĶß<é˙čīw7glëĨW§sCˇ.tžĻĮŌŌ™=oååå¸\.ŦV+wüf<Q‘<ūô X­V}č�RĨąčå<ôĮûęø/,"W“z â-[ˇŅ"1‘ú°w_ 6[!,_šŠŦĖlǜUtėЎn]ē0ŅŠ‹‹Š¨pĐēUŨē\ĮĖ䡹Z-1|Č`‚‚™™<‡G|€w–.ĮVP€Ãá ŠEsúôîœĪ<Įĩ;c/-ãčącüážßa2yÔÔôįĮžâoĪ?MʏXļb%qq1Ø iÚ¤1å$Īž‹ŲėMQQ1#† !..ö”6īēc2k?Z‡/Ņ1ŅŧüĘÜ=åv:ÄĒÕ0ôæ›X˛t<øĀi÷ķ­9ķŠĒĒ$ĀߟҪF\Đ1ūhũgėÛŸÂŗOü&“UUUĖ[ô.9šy„†ŸqģßŪ=™ĻĒ˙žū†Ö}Rpŋ›6Ĩæŗēösčđrrķxę҇�ČĘÎ!åĮƒ čۛ}{ŗũû|˛ásŧ˙w5ۘŊÍ<÷ÔŖuŽķ×ÎÔ÷Ųúذv3mZĩdİ›Øąkyy6ōķmglë‡ÃÁs˙˙ŋßĶ$Ą�ģ÷ėãšŋ˙‡—˙ũW�ŦV ë?ÛHßŪ=O[ŗˆČ¯™ŠJJë­ąŽÚ3{î|ōm6š4nLRR üũ|9vėâOŧ§ĶɓĪ<O§kŽaĮŽ]<úȟđ÷÷ãȑŖ¤Ī�\Lēm•UŠ‹JjÚNM=FZZ:Ü/.—‹ĮŸ|†n]¯ŖĒŌI×ëŽ%,4„˙ŊúéĮwęĖ&ĢVĀ„ņcˆ‹‰aūĸ%�lüü bcc:ø&˛˛˛™ģ`>đûSÚ,(, ]Ûļ4Š‹%&* €ĻM›Đ$!ÁƒâįįpÚũėÜŠ&#5Ąw¯|ŒŋÚēąˇŪRķeÃÃÃÛo{Îíf$ĪÁĮj%3+›˜¨Hî˜4á”ĪNxü‘kŨO||žžžŧôętZļH¤]›ÖtīvŨYką—Ûyæ¯˙úš¸&OÎ:ëŌ÷™účz]g^~}&šyy´HL¤]ÛÖøûá¨Ŧ<į~<|„˜č¨šhÕ˛ūū~KK`â¸Ņüí_/ŅļuĢŗ‘L~>Ös¯UKááa<üādįärđāAfĖJĻ{ˇŽx›ŊÉĪËgfōl�,3Ĩ%%Lš8žšķRRZJŋ>ŊšĻcÚ´nÅ+¯OĮÃÃQ#†aōŦ>M™“CxD�ƒ€Ā�lļ�‚ƒĒO}{{yá¨8ũ<Øyųų„…„� .Ų9Ų¤ĻĻ‘‘‘QĶî ĩiķײrrOŲĪ’ââŸú ŠUįb0¨Ēr°/åGæ.X‚Ŋŧœ×weøAgÜnę”I4mœĀęĩëČČĖ"*2â”ĪΧO“‰Gz€Âĸ"ö§āå+ņ0šwڝgŦÅėmæÉ˙ûSëüĩŗõ}Ļ>ĸ"#xáéĮÉĖĘfßūūųâËôíŨ“{ŨPĢũpVUZˆË…ŅX}ģ…§§'÷LÂ˙^›ÁÔ;o?cí""'ÔëŠéuë?%)Š9q11„…†`ąXørËVHxD8wO™ @jZfŗo//îŊg*vģŋ<ûWâããh•”DŸŪŊØģ/…UĢ×2vtõŠÜ°ĐP>Û¸ �§ĶE~ž :\{ $;7—¸˜232ˆŒˆ <,œĐāôī‹Ãá 77īŒÛ ātšÎÚGXXč)ûyâúø/CūBôčŪ•Ĩ+V‘øāũ´HlÆsO=ĘGë?#+;ģVÛßÔŋŋ˙fЀžDGE^p?ßũ°“ÂÂBzŪp=¯é@Ģ–-¸÷‡/hk[įųôŊrõZÚĩmMB|áaX­V>Ũ¸‰  ĀsļÕ$ĄĮ3ŗHųņ ‰Íš�°{ī~ŠKJ‰ūéL @ĶÆ ´m͊UĢ×^ØA‘ĢBŊqRRs–ŧŗ “É„‡‡‘JG%cF$:*ŠØØhĻĪLÆQé **’[†aŨúOYûŅĮ=<čŲŖ;�ķ.Æ×Į‡ G}z÷Ēi;..–Fâ˜1ë-ƒ Ā\‡y¯o49sMYY).—‹ŽīÆ[sæņæ[s((,¤WîDžaÖ(.žUk>8ëČ6.&æ”ũŒ‹‰ŠuĩŅģGwJJJyü™ŋböŽ~ËGpP“&ŒŠÕöÜ6n43“įđÔcÕAķÆĖŲ57HôíŨŖÖũ$6kÂkĶ“Y˙Ųįxyybˇ—3í#A{š§žûûIËîŧũļsÖųkgëûL}´kۚ7gĪÃĶĶŗæ˙č”Ûo#88čœûáééÉ~ä9ķq8*0{{ķØÃÄh<ų‹ÖčÃxøņ§‰ ;ëą1L:Õ5}úôZ­ŧ}W 1‘úÅŌPôö%‘Ë×ö])ĩūũ=mÚ4ĸŖŖz‹{ÍY°˜â⒓–5IhÄMúēŠĸúąūĶėKųņ¤eŪŪŪ§ŒĸED.G â+Ȥ įž{úrÔ÷ƞôŊQ‰Č•é’ŸYKDDäJĻ q#ąˆˆˆ)ˆEDDܨNAlöōĸĖno¨ZDDD.KyļB|1C]ÔéŽé¸˜pR͞ȺWgR;ÛwĨ¸ģŠ_Ģ…Ø¨ķ›gŖNAėkĩĐ2ąŅšW‘ZŅ5b7R‹ˆˆ¸‘‚XDDčÄ"""n¤ q#ąˆˆˆ)ˆEDDŽž1t�� �IDATÜH¯A‘ĢRy…ƒƒGŽ‘_PHU•ķ´ëxx ô÷§q|4ŗwƒÔĄ ‘ĢNy…ƒīvî#&*ŒĻbņōō<íz˛rōøaw Ú´Āû ë]ˆ:qqiŠéYØË+ęŊ‘‹ÅÃ1‘aÄFEœu=//ObŖ#pûæ g|},ÄF†×¨šNAœšž…ÕâMHP@;’+[ZF6['ēģ ‘ZŲŧí{’š&ÔzũˆĐ`RĶ3šžsģĶ~žg+äXF6‰ ąuŽĨN7kŲË+°˜ÍuîDDDäRRUå<ãéčĶņōō¤ĒĒ挟úS\Rv^ĩčŽi‘Ÿ<÷Üs<÷ÜsĩOŨŦ%""<účŖĖš5 €ŌŌR^xá…‹Ō¯‚XDDŽz=öŗfÍbũúõ�ôíÛŖŅxQFĮ:5-""WŊũû÷ŗ~ũzÚļmKÛļmYŋ~=ûöíģ(}‹JJ/JGį̰°ˆy ÕK[¯OŸÅ‘ŖŠõÖÖųÚąsŸąš^ę¨oŸ|ļÖ­ww"" ĘÃÃHE…ŖæįwŪy‡ļmÛÖüÜļm[ŪyįšŸ+*˜<<¤“šNļŲlĖ™ŋ“É„Ķé¤ŌQÉäIˇxŪmúûû1q¸zĢņBlūj ž&×vîÄ=Ķî:īvÚļi]U]>žũî/]Ÿ¯/%ĨĨÜ7íNbĸŖ˜ŋø]~<pˆĸâb&ŽM‡vmXüîrvíŲGUUûŨHĪŽįxF&¯Ī| /// øã}÷`ĩZÜŊ["r đ'3'¸čŗ?G|BfN~ RKŊ^#Ū˛u-8 �{÷Ĩ`ŗČō•ĢČĘĖĻĘYEĮíčÖĨ ķ-Ą¸¸˜Š ­[%Ņ­ËuĖL~ĢÕBaaÇ &(8™ÉsxäÁxgérl8’Z4§Oī^<ųĖs\Ûš3öŌ2Ž;Æîû&ĶĪßZ>ūä3vîÚMDD8yųų�|ūÅf 2ø&RĶŌxoåûL?–—_›N`` Æ &õØ1žŨūž&Oüũũ¸eØPÖ~´_ĸcĸyų•7øÛķOs,-ĨËWāīOaQˇß6ž=ûöŗuÛ6ââb9rø(ŨģwãÚN×ÔÔ´ųĢ-ääävÖõŽ9Ęō÷VáëįKqQ1Sî¸oo/’gĪÅlöύ¨˜ÆËÂ%ī’““KiI Ç %ĄQoŊ=‹Å‚ÍfãæAILlvÚãučđa–­XI\\ ļBš6i|Úžũũ|/č˙‡ÉdâÉ˙û>V+s,ᇝģ)*.!-í8O=ú…dfe“–~œvîæųŋ<FUU÷˙éQē^w-s,aė­ˇĐēek×}Âû|ĘQÃ/¨&š:%ÄEķÃî úáŗÍŦ•™“GZFí[5oZę5ˆ;vhĪėšķɡŲhŌ¸1II-đ÷ķåØąc8pˆ?ũņ~œN'O>ķ<Žš†;vņč#Âßߏ#GŽ’v<p1éļ TV9(.*Ši;5õiié<p˙Ŋ¸\.ōēuŊŽĒJ']¯ģ–°Đū÷ę¤?N|\õÕN§‹×}ĖߟŖŅĀOŸųĸģŅÃD~žĮūü0FŖcĮԘv×ŧŊŊyėÉgqËPÚĩmKŖ¸Xbĸĸjļ[ļb%ÇÜLBB<_|ųë>ų„FņņX,F Jję1–¯\uRĀžāáa<ëz;wī&ąy3@NnFƒŸAll CßDVV6s,bä-ÃČÉÉå÷ŋ›Fn^‡aÃĻÍ4NHāρũČÍËãĩéŗxâ˙>íņZĩú&ŒC\L ķ-9cßĒ]›V¤üxéÉoãi2ņ—ĮaÍÚuøúúđúˎȎ0nô~<pˆÄfM~:FDF„‘ž‘Aʁƒ4Ol@ķÄĻ,\˛ô‚k‘Ģ“ÅėM‡6-8xäŠé™g|FØÃà�?Úˇj~yĖ5ÆÃ>@vN.dÆŦdēwëŠˇŲ›üŧ|f&ĪĀb1SZR¤‰ã™;!%ĨĨôëĶ›k:v MëVŧōút<<<5b&ĪęSŲ99„G„`0 Āf+� 8¨úÔˇˇ—ŽŠŸ§ß,++ÃĮjÅhŦ‘°ĐŗÖ\ŗ.ĀüEKđööϤ¤Gšã´ÛdįäZŊ˙ĄĄü°câã ĀËËë¤ëŋvļõúÜØ‹Õk>ä˙ų/aĄ!Œ3ŠėœlRSĶČČȨ9yyy„ū´o!ÁÁ„3ŅZ&ĩ¨Y–Ÿgûy?uŧōōķ ŠŪ>44\ŽĶö]›5á_/<͊UkXōî ŧŊŊ1™LL2‰ĖŦlūö¯˙rËĐÁ'mãtē0 ~ņeĀåtb0č^CŠŽŗ~æ:ÛĮõ ^ƒxŨúOIJjN\L aĄ!X,žÜ˛•ÁƒÎŨS&š–†ŲlÆÛˋ{ī™ŠŨnį/Īū•øø8Z%%ҧw/öîKaÕęĩŒ=€°ĐP>Û¸ ¨ūŜŸo#čמ- %ĨĨ8NŒF#™Y�xyzâ¨ŦŊŧÜŧšõOüž/¯¨āŊ÷WķˇįžĄĘYŎßnĮ‰ ƒœŋú åxF&M7æxF&ááĄ|O8~<“Áƒ`ĩXyoåjļnũ†đ°pBƒCĐŋ/‡ƒÜÜ<*6|ū�9ššėÚšû§ē2€ödeetÆ~‚ÉÎÍ%.&†ĖŒ "#"NÛwī^=.h^øĮ‹Lšũ6"# æxF&-“šŗíÛī�đąZŠŦĒ"ąY>üø�••deįIķfMŲģ/…ļ­[˛g_ IÍ5ĨψœŸËöĨį’”Ôœ%ī,Ãd2ááa¤ŌQɘŅ#‰ŽŠ"66šé3“qT:ˆŠŠä–ĄCXˇūSÖ~ô1FzöčĀŧ…‹ņõņĄÂQAŸŪŊjڎ‹‹ĨQŖ8fĖz ‡ÃÁāA0ŸcēMŖŅ@ŋ{ķī—^&44_?pšHjŅ‚Ī6|ÎģËV€Ápʡ/OO"#"˜™üžžž´nŨŠÕk֒ÔĸĢÖ|P3ú5r8K—­ĀĪĪŌ’R&ũf{÷ÖĪ-ī%%%ŧüÚ ‚ƒ)--cℱøX}xkÎ<Ū|k……ôęŅN×t$6&šW_ŸAqq1ˇ FŖø8ۚ3äˇįQTTÄø1ŖĪØĪ̓2gîĸŖŖ)++Åårļī uËĐÁü÷ĩX-fėörūpīTÂBCŲēí[^øįK”””0eŌĸŖ"šĻC{ž|öo8N&Ž§ÉÄ¤ÛÆđĘo˛rõx=øãī{Á5‰ČÕéā‘cu~éÃÁ#Įh™Ø¸Ūk1ÜqĮŽäääZ­ŧ}W 1‘aõ^„\ūôŌšœlŪö=ÛĩĒ =š'Ÿ|˛æĻ;vđĖ3ĪÔ<ÂTQá`Û{ÎøŌ¨ÎČÚūœ6mŅŅŅ€&ô‘ĢĐ¯_úĐŧysúöíˎ;Øąc}ûöĨE‹5ŸŸëĨBS\ŠˆČUīųįŸĮårҎo_�îē뮋öōąˆˆđ /`ĩZxüņĮ/Zŋ b‘Ÿ\Ė�>Áö‹ŪЈˆˆT3b/pw """Õ¯_úp. ųŌ#æÚMx `öōĸĖŽ´ˆˆ\ŪNŧôĄļÎõŌ‡<[!žįųš:]#Ž‹ '5-‹<[Ņyu&WļíģRÜ]‚ˆH­˜L¤gdĩ郏ÕįŒŋį|­bŖÎož Ô~hîkĩĐ2ąŅyu$""r))¯p\"/}ČÛÛ ‹ˆˆ\ĘŧŊ<dĘĘē2ÜÖŨ5ˆˆˆ\ĩ4ÅĨˆˆˆ)ˆEDDÜHA,""âF b7R‹ˆˆ¸‘‚XDDčÄ"""nT§).‹KËHMĪÂ^^ŅPõ\œ•v/]I™ŊÜm5XĖی5ŒNíõœˇˆČ•ŦNAœšž…ÕâMHP@CÕãviŲna€2{9‹—ŽT‹ˆ\áętjÚ^^Åln¨Z.îá.•:DD¤áčąˆˆˆ)ˆEDDÜHA,""âF b7R‹ˆˆ¸ŅUįOŸÅ‘ŖŠî.CDD¤Fž#>—÷×ŦåË/ˇ‚ËN§“)“'\ŸŨ¸]˙{pũuųÛK¯RūĶä&Ũ˙[ūųŋ7Ü\™ˆˆ\nę5ˆzõŧũû°iķ—Ŧũčc&ŒÃŧ‹(--Ŗ¸¤„Ž]ŽåúŽ]˜ŋh ÅÅÅTT8hŨ*‰n]ŽcfōÛX­ ‹>d0ŸlØČĀūũˆ‹åĪ=ÅØ1ˇŌą}[ūöĪ˙p÷w°äĨX,l67Hbb3žzöš$$Đ<ą%ĨĨėÜĩ›ˆˆpōōķëm?SdÔ°Á,xgÅIË##ÂzS? ‹ŠđķõeņōUĐĨsG&ŽĀ–mۙˇdyŊÕ#""—§zâ_*.*Æbļā¨Ŧ$>.Žž=ēSXTĖŋ^|‰n]ŽcĮŽ]<úȟđ÷÷ãȑŖ¤Ī�\Lēm•UŠ‹Jh×Ļ5{÷îÃėíMBŖxöėŨCËÍ1 lûv;¸i`?rķōxmú,žøŋ‡ŠtT2°_ÂÃÃyäą'øûķĪb4xâéįęmßvíKĄU‹DÚļJbĮîŊ5ˇŪԗ>ū”ciĮšöšöôîŪ•Uk?Ēà ąI‚BXDD€⠛6ŗgß~\.Œ3rrs™3oT”;0 Lš8žšķRRZJŋ>ŊšĻcÚ´nÅ+¯OĮÃÃQ#†ŅĻukۜ=“§‰ëģuáĶ Ÿŗ?%…V­Z’““Cˤ�„“ŸgĢŠ#44”˛˛2|ŦVŒF�aĄ!õļŸ`éĘøũÔÉ:ōķuᐠ rrķ�ČÍ˧uRķ“ļÛ˛m{M ‹ˆˆ˜ėŽzm°× ×לš>aĮÎ]äææq÷“I?~œvîÂnˇãíåÅŊ÷LÅnˇķ—g˙J||­’’čĶģ{÷Ĩ°jõZî™z'•••ėۗB÷É]Ųöívļ|Ŋũûąwß~Žgd�íÉĘĘ&$4¨ĻOƒ, %ĨĨ8NŒF#™Yõē¯,-ãF Ãét“—ODX(GRĶ %û§P9“ŸĩÁ;‰‰‰âxF&ÉoĪ#2"ĢŲ¡Ûŋį‡;YûŅĮ=<čŲŖ;�ķ.Æ×Į‡ G}z÷ YĶ&ėŲˇooo’Z4g؊•Ü=e2áaaŧ5gÉoĪŖ¨¨ˆņcFŸÔ¯Ņh ßŊų÷K/НŸ¸\õēoĨMËÄĮF°jí:† ęOqq ‹…EËVÖk""re1L:Õ5}úôZ­ŧ}W 1‘a \’{Ĩed“üö\w—Qãå<ãîDD¤žM›6ččęÜUõąˆˆČĨFA,""âF b7R‹ˆˆ¸‘‚XDDčęÄf//Ęėö†Ēå’a6›Ũ]péÔ!"" §N3kÅń“š–Ež­¨Ąęš$Œ5”EKWawã—ŗŲˏQCŨÖŋˆˆ\u b_Ģ…–‰Ē–KJ§ömŨ]‚ˆˆ\tXDDčŒ+ßûČŨ5ˆˆˆ\ĩŒ!IîŽADDäĒĨSĶ"""n¤ q#ąˆˆˆĢr÷ēģ‘Ģ–qØđîŽADDäĒĨSĶ"""nT§™ĩŠKËHMĪÂ^^ŅPõ\qœ•v/]I™ŊÜŨĨÔ‹Ų›ąŖ†iö1‘zP§ NMĪÂjņ&$( Ąęšĸ¤ed_q! Pf/gņŌ• b‘zP§SĶöō ,z#P\i!|•ē_""›Ž‹ˆˆ¸‘‚XDDčÄ"""n¤ q#ąˆˆˆ)ˆEDDܨNĪKũ3 Œžåfüũü¨ĒĒÄĮj像?ãĀĄ#î.MDD.ą›ÅDEāĪŒŲ � >6€ÎÚqí5í1 ėÚŗŸ _|ŰAũņ÷ķÅĶĶ“”ƒ‡Øôå×<öā}lßą/OO‚ƒ™ŗđ]‚ƒšåæaą˜Yŧl.—‹‰cGRfˇãīëËĘáŖĮ�čŌš#ĮŒ�`ËļíĖ[˛Ü=DDä*Ŗ vŗã™YTVV2áÖá:’JĘÁC|ŋsƒAũoäųŋŒËåĸGˇëˆŽŒ 2"œŗįđ؃÷ņõˇßc2™ØúÍvrrķšuø`ZļH¤K§|ôÉį>šJˇk¯Ą{×Τ8ŒÁ�‹—­ÂdōĀĮj­ŠcËļí�$6IP‹ˆ\D b7ĢĒĒ"yŪŦV âbč×ģN§“Uk?Ļŧĸ§Ķ ĀÆÍ[h×:‰œÜŧšm ‹đ÷Ãå‚Ü<ÛĪËü ĸW÷.ÜĐĩ3^^^ddes$õ{öũČ]“ÆQUUŨĮ/mŲļŊ&EDäâĐÍZnÖŧYŽi߆ŌŌ2öėû‘åī¯ĨU‹DĘĘė˜Ŋ͘<<0 î#9šų„‡…�Õז°a0@Xh0�!ÁAØl…ääæķņ†MĖ[˛œwVŧΆM_ĀžđFō<>ŲøûötįŽ‹ˆģŨŅciŒ1„ëŽé€Ŗ˛//O–Ž\ĀĒĩë¸ëöņFvîŪGzF&ŠiĮųÍØ‘xzz˛îŗĪЍ¨ ĒĘI‡ļ­ Æją°g˙dįæ2lĐ�ĘĘĘđķõeéĒ58•Œ>„’ŌRŧŧ<Ų¸yĢ›÷^DDÄĩđúôY 4Fņq5Ë>ųl•ŽJôī{AmÛíåĖY¸ô´Ÿm˙aÛØuŌ˛ÕŽ?uE|ôÉÆ“efå0ķí§ŦúڛsÎŋXŠwõÄ6›9ķb2™p:T:*™<é6ëŗ›ZI~{.ˇŽŋŸīEī[DD¤ļę5ˆˇlŨF‹ÄDčĀŪ})Øl…˛pÉģäääRZRÂđaCIhĮ[oĪÃbą`ŗŲ¸yĐ@‚C‚yņ¯ŌŽMkJJËđ÷÷eÔ-Ã9ž‘Áâw–HiY“&Ž'';‡åī­Â×Ī—âĸbĻÜq{Mč8pīžßÃáāî)w°tų l8’Z4§Oī^'Õ=oÁ"JKË(.)Ąk—kšžk>ūä3vîÚMDD8yųų�¤üx€e+VC­ĻM×įá;oĪũķî.ADDÎSŊqĮí™=w>ų6M7&)Šū~ž:|„œœ\~˙ģiäæåqčĐ6lÚLã„n؏Üŧ<^›>‹{{eĨĨŒ5ƒÁĀ“Ī<ĮĀ~ũxwŲ{ <fM›˛áķM|ļĄú4lbķf 8€œÜ<ŒCMM›6!"<ŒņcĮ––FZZ:Ü/.—‹ĮŸ|†n]¯Ãbļ�ā¨Ŧ$>.Žž=ēSXTĖŋ^|‰Ž×]Į‡ë>æīĪ?‹Ņh≧Ÿ`Õę˜0~ q11Ė_´¤>ˆˆ\ĨęõŽéđđ0~đúöš—ËɌYÉ|ųÕVōōō ­žÛ7$8˜Î:’““CxDXͲüŸŋ ÁđS¨úûųSPTHvN6¯˙Œ™ÉŗŲšs7úÜØ‹˛Ō2ūņŸ˙˛jõŒ†Ķ֔ũ‹~ Øl5Ÿ0“›Ëœy Xõūj*Ę”••ácĩb4VˇöSíyųų„…T˙=44´>ˆˆ\ĨęuDŧnũ§$%5'.&†°Đ, _nŲĘ Øđų�ä俞kįnÂBC9ž‘´'++›Đ �˛ŗsp:]ōķō ô <,ŒA7  Q|…ŒFŽĪdđ X-VŪ[šš­[ŋĄw¯?c�—ËIXh(ŸmÜ€Ķé"?ßFĐ/ŽYīŲģ—ÜÜ<îžs2éĮķÃÎ]X,JJKq:F23˛� $;7—¸˜232ˆŒˆ¨ĪÃ'""WĄz ⤤æ,yg&“ #•ŽJƌItTą1Ņŧúú Š‹‹šeø0ÅĮņ֜y$ŋ=ĸĸ"Ə €€?KŪ]JVv¯Ŋ+#nÆģKW`ĩZ(**füØ[)))áå×fHii'Œ=Š–Æ Ė|s6÷ūön5ŠcÆŦˇp8 4�ŗŲ\ŗ^LLĮ32I~{‘áXÍļ~Ŋ~7öæß/ŊLhh(ž~>ārqķ Ė™ģ€ččhĘĘJqš\õyøDDä*d˜:uĒkúôéĩZyûŽb"ÃŦ˜ŧü<f&Īá‘h°>.Ļ´Œl,~ģŨîîRęŲlæŸĪ<ęî2DD.KĶĻM#:ēúŊšÝ5ô¤ø•Āl63nÔPw—!"rE¸¤&ô žbFÃ'tjߖNíÛēģ šDiD,""âF—ÔˆøJ÷Ũqøãøėģ+Š?Ŋ˃ĄC”ģ+š<iD|]i! ÕûķĮ5îŽBDäōĨņEtš…pû˙XEĪ„“ĶÚxØĀCzđ}fõ„'—Û~‰ˆ\J4"žŒ,Īõģxũ.„z&T´ˆˆ\8ˆŨäŽkāîÎ`4ĀÄw KÜÛ xîĶęŸÛF€Õ åÁ’Đŋ¸€.ąđdđ4ž€ëãĄWcØxRm0¸øxÁË`Ūhø>ē7‚—ž€]Yđō0á… pŧčäļÖĻü\ã‰~ņĨ˙ҤI›ņō+¯ōúĢ/Ÿ6 ED¤îÄnōL_č1|ŊĒūû@č= ĸũá?ƒāũ}p ¯:,<ŋ] G āņuđčāã 9Ĩ0¸9ä•ÁötxâcH{>?fSõTN|˜Ÿ„ßt¨ķ_WnBÜßíäļ~Ä'Ü2|(SîšFyE9O?õäÅ=P""W8ą› ÕŖ_ĢWõčÔéúyyÕOˇŲĄÔQ=Zũ%°đXļ -p[ûę =ņŲSëĢÛ+°Ã”NÕíøz§ĮĪũzz@ųÔļN§ŧŧ‚ĘĒ*<<L”–•5ĀŅšzéą›<ĩŽŠ>M\V ū°ú4ō_TzĪæå/aęĩ°b"4:ųŗg?…9ˇBōHđ>Í×Ŧ—6Ão¯ƒeĒŋœ­­>Ũ°§žx”9oÍâķĪ??ŋ‘Ķ礿šžŌ¤edĶąubÍΆĮŨXĖyøhRå¯o<l`œŸ“ŪõÜÅĒJDäō§šĻĨVúЃ‡O}Īķ‰Į—DDäÂÕéąŲˋ2ģËöƒ‹ĨWl8ėî*jīûĖ“GŊgŌ+ĄákšRÕ)ˆãbÂIMË"ĪVÔPõ\Ņ^ēX}y…ņšôJ¨Ū/9?u b_Ģ…–‰Ē–+^‡(øė.wW!""—]#q#SQ‘N3‹ˆˆ\LEEEØl6�L~~~n.GDDäęâįįG``  SĶ"""nĨ q#ąˆˆˆ)ˆEDDÜHA,""âF b7ĒĶĖZééé U‡ˆˆČeīÄ•ęĸNA|>ˆˆˆČ™éÔ´ˆˆˆ)ˆEDDÜHA,""âF b7R‹ˆˆ¸‘‚XDDčÄ"""n¤ qŖ:MčņÍ÷;Xŧt%eöō†ĒįŠa1{3vÔ0:ĩoëîRDDäV§ąB¸öĘėå,^ēŌŨeˆˆČ%ŽNAŦŽ/9]#q#ąˆˆˆ)ˆEDDÜHA,""âF b7rK?ūĐũ§,ģãļŅÄDGÖ{_>V+üŨ]õŪŽˆˆH}h >x�Ŗ† jˆĻEDDŽ(ušYĢ6âccČĘÎÁ×ׇ°Đ˛srøÍؑ8Nl…xĢķŋįõ]HjŪ”ėœ<NiëēNhßĻ%••••°t嚓>5l>V+^žžėM9ĀϝžfЛ Äbĩ°æŖOČČĖÆË˓1#†@Ff6ī­ųˆļ­’čzmGJJJ)ĩÛYņū‡Œ9”ĘĘJ**DGE°˙ĮCøûųĖ›s˙?{÷ՙöqü; 3(ŊŖX5b‰ŊEŖÆ{ī=ú&›˛ģŲÍîĻnŒ1YS6=k4öŽØŖÆHėŊ÷ ŌačĶ`æũ%AĮrŽ+W`æœįšĪ9#ŋ9í9Kđ÷ķĄĪķ]ČÎÎEŖQŗtå: Fcy¯B!„Orß#nĶĸ ‡ŸbßÁ#´mŲ ( g•JÅÂåĢŲēk/Z…BÁŗm[1cîbV­ß„ĢJUb{s­`öÂåDFTG­vŊåŊ:‘Ŧ\ˇ‘™ķ—põZ<a!Áøxy2sūæ/‰ÆŨM €ģ›ËW˙ÄôŲ iÔ �}{teÎÂå,Zą†JîîT ÅjĩrõZ<ë6mÁjĩ‘œšĘš ›qwĶĸÕjčÕõ96˙ē“ĨĢÖqūâ%ZˇhRŪĢO!ÄĻ\÷ˆŊ<+SŊZU†ė @HPˇlÅŗr%Ō33ČĪ7`0™PĢ]É7°Ųl�Åī˙ŅĀŪŨ1™Íh5\\\0Ū4ZÕ˛UëÔˇ'Zš{ö Ō3ŗ�ČĘÎ!+;7­–ŒĖŦâ~lV+jĩ+nZ ƒûõĀÍM‹‡ģ�99š�XČÉÕ`ą rqÁĮĮ‹v­›ĶĻET*ÉŠiåšú„B<Ę5ˆŸmۊKWrõZ<�õęDŌēy.^ēŠ—�înhÔŽĻâ=c›Í†ŋ¯Ī-mš8;Ķ­S{&Oû'''ÔĢ‹“BQüžJĨÂl63kÁRT*oŧöĖ^°ŒVÍE_ ęÔĒÉņSgoĢĶh4‘Ŗ×ŗhÅŦV+ž>ŪdeįP'˛æ—O—žÉ–íģHHLÆÃŨ ĢÕVĻõ%„B”[ĢÕŽDFTgåēÅ¯Ŋđ=ēvä×{(,C]ô�� �IDAT,dėđAdį䒓ĢĮfŗącĪ>^?ŠôĖLôyų(ø=h-¤ĻĨ3rp?ōōķ9wá7ž{ļ-+֝'.(( ]ëtxFIĄĩŊ“”LRr*ãF ÆÍMˆÍŋ–Zīē[?r0&ŗĨ“ –­žë2Ž˙y ŊēuÆ`0āáîNôē äåį—a­ !„xŌ)&Nœh›>}ú=MüĘīVp9Ÿ¯§Mvt B!2“&M"88=„B‡’ B!H‚X!„p b!„$ˆ…B’ B!ČŽ VĢÕUĮcI֗Bˆģą+ˆ‡ôī)árÔj5Cú÷ttB!rvŦÕ¸ADUT-B!ÄGÎ !„$A,„B8ąBá@ÄB!„I !„$A,„B8ąBá@ÄB!„I !„d×ČZú|q‰ŠMæŠĒ§ÂŠ]U„ûsūâo,^‹ÁhrtI%Ō¨]Üŋ‘5ųunĩ̊ŒLkÖmz¨ļ͍íqķČr‡Ÿ`iôē Šŗ¨ŋž4nPŋÜÛB<\ė â¸ÄT´W|ŧ*WT=Î`4—˜úP‡0€ÁhbiôZƎņȝs{ŒF~œŗ“ŲâčRnqc{ÜÄKŖ×“o4Ą¨€ūō&–F¯— â `ץiŖÉŒæčƒF­Æh2?Ô!|ƒÁhz,Öš=4jõCÂ7üņ3c0+$„×ÛB<ūäąBá@ÄB!„ŲuŽXa?­FCũzuP($%§põZŧŖKB<D$ˆ…¨ yiÎÎÎx¸ģáåYtąŲb!9% €õ?oáėųßYĸâ!ā° >xč0ąqņ čÛÛQ%ˆ;HLJbyôj^{ųEG—ōČÚ{đÎJeŠī§ĻĨ?Āj„Ģr âŦŦ,æ-\Œŗŗ3VĢ•KcF ĮĶĶŗ<ģy`žmۊ§ęÔĸ°°gggŽ8ÍÎŊhҤO7ˆÂÍMCåJ•HLJAŸ—Įŧ%Ņ|úÁÛüvų*�jĩ+GOœbûîũZį§˙ũ 777^œ8žøĩŗæ ×ëųËĢ/WHŸ{öíĮÅŲ™ĻMWHûwĸVģōꤹLŸŊėœ\�FĀą“g8~ō˝§46›Áũz–øŪÅKWØĩīāŽHņ0*× Ūā‘téÜ €sį/’••ƒ§§'‹—­@§K'?/ŪŊŠū8ÅĮÅ3{Ū“’čú\'?ŨˆUkבš’FĄĩF ë͞ysæĖ[ˆ‹ĘW• ņņ‰ÔŽŒ$'7‡Ô´4^~qIÉÉ,]ž/OOō FŠ›V[ĻeiÕŦ1ĄÁ|7sVĢgĨ’ą#‘o0°īĐQö:JdÍę4}ē –­*žĪl6ķŋŲ �P(ŧķ÷W9pä8CÅۊ’››KNN.•*yoČ'33 —ĸÍģ`Ņōķ čķōhŅŧ)­ZüžN5j5ģÄčÃĐjĩüđãlüQ:˙ūŅØĩg/GŽÃÅŲ…J•<čĶĢ'›6˙‚››;Á!Á$&$˛īĀ!ÜÜ´€‚ąŖ†3gū" ¨\ŠįÎ_āÅIđõņáø‰“?q’Q#†Ũ÷˛&–¯ū‰áûōŨķxēA&“™3g/0zØ�Ė&3nnnlØü+‰É)ôīÕ 7­•‹ į.^z`h0Jī¸}Nņ`”ëUĶ6āøÉS,Yļ‚DxxŽ\E§Kį•—&1aüôz=�ŽŽŽŒ5‚C‡°kĪ>âããšté “^Į˙Ŋ0žŸ6nÆRP€RéDõjá č×…“ÁÁ ĐŊ^OŽ^Ί•kčŅŊ ŖGŖnHļmßQæeiP¯.1ÛwcĩZ((,$fûnÔĢsĪm¨T.( ‹_kŪ¤_O›Ė×Ķ&3bPß2×yC›Ö-Ųŗw�{ö e‹f�X ¨ÆÄ c™0n ›6˙€RéD•°PúõéU´ŨNfמŊ4lPŸÆQŗ¸m &MĮ‹“&pæėyNP?*ŠíÚĊUkøŋ‰ã7z$ØŦœ>sgĨ5ĢWg`˙ž´kۆ;w°wßžiĶĻĖË{%6ŽËWcéķ|ÚĩnNôڍ´jۘĤG¯eõO›čũ|g�ęDF°rŨFfÎ_rۅRĩ=�ōīđåëN!-„x˛”ëąŋŋoŧūgŌté\ž|™fÎĸu˨T.øúú�āã퍏ˇ7ÆĪß�ĩĢ+‹…T]:™™Ė˜5�FMŪõĐžqx[åâBåĘEž8;ģ`ą˜IĶĨą%f[ˇīÄl2T.Ëã¤ŧõ{ŠBĄĀjĩŨq•JÅ Ŗ‹ööœœŦXŗ‹å÷*ö: @Dõđ[ö¤ËĒ^Ũē˚3Ž]žãü…‹ 2C‡ @.=y ĄT*1›~¯ÅÛË̏fŊ^Ovv6uëÖĀ××÷–ö.Y†ĢĢ+yyųXnj#/?WW.×÷ ũ|ũHĶ鎡Q´Í›7kÂä˙Cˇ.Ī‘‘™Ixx•rYæŸŨÁ›¯ŋĖâk°X,øx{@€_Qí6[ŅļZļj=ƒúöDĢQŗcĪ~Ⓤۨ¨íÜņ(ˆ Ö!„¸Ą\ƒø—˜­ÔŽ]‹°ü|}Đh4ėŨ€n]:ŗũú‘.=Ķ§Î uģũĐąŸŸ/ūūŧ0n �q ÷t~ŲßĪn];SĩJŲŲ9(œĘžŖėäižkß†š‹ŖąZ­(•J:<͊CGOÜq>ŗŲˌš‹î8ÍūCG‹ Ü(ÔŠ[‡˜­ÛŠQ-Åõ1ŸÎž;Gzz/ŒCbR'N.ĩ OĪʤ§]@””œ €ÉlfÍúŸøxĘd ­…9r+6 °Úl¸iĩ&, ...$%'ĶĻFKâãP(Šjpuu%˛VM.YFķfMËm‘m6z}9šE_Ötéddfąm×^œ•Jŧŧ*ŖRŠ0›ÍĖZ°•Jůũ'NŸģĨ Ų@žšB܃r âÚĩkąlųJœQ*(°0h`?‚ƒ‚ æÛī@¯×ͧw/rr˛o›?,$„ĐĐ`ĻΘ…ĨĀBPP a!!wíˇoŸ^Ŧˆ^VĢ!7WĪĐÁ¨äá^ĻeŲ{đjĩ+zatqČ=qŠŖ'J2G{ĻuKŪû`*īžųO � ")9…YsāV­aßū’Ī‘ļiŲ’īgüHlė5Ô56› •‹ ˘5wwwžzĒ.?mØDíČHÖm؈¯¯ƒú÷ãû~DĢÕ Õj¨[§6‡ÜlĪļ{†>úCŦ°åßwđCöføĀ>Tōđ`÷ūC¤gdŅŽu :<Ŗ¤ĐZČŪ‡+Ŧ˙?ēãąšB\§˜8qĸmúôé÷4ņŅĶ ôĢā’*^BrŗæÎwt÷dÜ葏Å:ŋr5–­ÛwGž‹ūķų¨čū|=mrņΝŧņî]§ŸúÎx”đĨđŗpúÜģúB<>&MšDpp0 zˆ`ķ–_9qō/ŒíčR¸)Ÿ}¯ˇ×-¯M&š‡XQL‚XT¸Î:ĐšSG—áųųŽåËah!DéäĄB!„I !„$A,„B8ąBá@vąZĨzäG2¨U*ÔjĩŖKš+ĩZũXŦs{ŒFT*•ŖË(Ņ?3ĩš;ŗv˙l×ÛB<ūėēj:,ğ¸„T2˛r+Ēž §VŠ ņgH˙ž,‰^‡ņ! 9ĩZ͐ū=‹unĩJEŋŪŨXŊîį‡jÛÜØ7ÜŋKŖ×WČ%­ZÍāū=ĘŊ]!ÄÃĮŽ=„BQv7č!įˆ…B’ B!H‚X!„p b!„$ˆ…B’ B!H‚X!„p b!„$ˆ…B˛kˆK}ž¸ÄTŒ&sEÕSáÔŽ*‚ũ9ņ7–F¯Å`49ē¤2ҍ]Üŋ‘5ųmcĩ̊ŒLkÖmrč6ŧąū7ˆ*~íđņ,^W!uõ×“Æ ę—{ÛBĮ°+ˆãSŅj\ņņĒ\QõT8ƒŅH\bęcÂ�Ŗ‰ĨŅk;jÄ#ŋmėa0ųqÎLf‹ƒë(Z˙7ņŌčõäM(* ŋ|Ŗ‰ĨŅë%ˆ…xŒØuhÚh2?ōO„ҍÕMæĮ"„o0MÅļą‡F­vxßđĮĪ’ÁhŦP\o_ņøsÄB!„I !„$A,„B8]k !ʏBĄāéõPŠTäååsâôYG—$„p� b!°ŊģS­j\\œ đ/~=>! ĢÍÆ‰Sgøų×ŦPņ 9,ˆ:Ll\<úövT Ė›¯ŋĖĮ˙ũĢÕęčRĘŨ¯ÛļS`) ķs]Ę#ãôš $§¤•ú~brĘŦFáhåÄYYYĖ[¸gggŦV+–ƌާ§gyvķHhÚ¨– ĮNž)ņũāĀ�úöčŠÁhDŖVŗ`ŲJ˛sr+ŦžK—.ķá>áƒŋCHp0�ŠŠiüķíwyįÍR-ŧj…ô;kî|ôëK%÷ i˙n>Ÿú.Ģ×˙ĖŽ=ûhĶĸ)…V+{vH=�)Š:^?ĒÄ÷l6¯ūãŊ\‘‘Ę5ˆ÷8DdD]:wāÜų‹deåāééÉâe+ĐéŌÉĪËŖw¯ž�ÄĮÅ3{Ū“’čú\'?ŨˆUkבš’FĄĩF ë͞ysæĖ[ˆ‹ĘW• ņņ‰ÔŽŒ$'7‡Ô´4^~qIÉÉ,]ž/OOō FŠ›V[Ļe đ§g×NääæâáîÎŌUëČÍÕ3 ww4j5înn:z‚ƒGķöß_eĘ'_0~ä~Ũž›íZ‘—o )%ĢÕJ×NíņķņÆÍMË˙f- jXŅë6’œ’Ęŗm[ōtƒzlŨšˇ¸˙æM1bPßĸõzč( –­*Ķō�DÔŦÁöģ6x �;víĸFõę�äō™ņã\<<ÜŅéŌ2h�•=<øī×ßõ99šhÔ† ęĪÅß.ąrõZÂÂBČÎĘĄFõj%Îo1›9vü$‹…‘Ç2wŪB4 YYY<ß­ 5yīƒŠTG­V“—§gܘĸ€úāŖiŧōâÄ2‰KJNĄAT]Î]üÔ´ôâ×U*Ãö&ß`¤rĨJlŲļ“ËW¯ŋ_ë˙ƒĄôû€ĻĮįūv!ÄŊ)×ĢĻ5lĀņ“§X˛l&88ˆđđ*\š‹N—Î+/MbÂø1čõz�\]];j#†aמ}ÄĮĮséŌ&Ŋ0Ž˙{a<?m܌Ĩ �ĨŌ‰ęÕÂĐ¯/ ''‚ƒ4 zŊž\Ŋž+×ĐŖ{FFŨ:‘lÛ^öķk=ģvd㖭,]šŽã§ÎĐžu ”J%ņ‰ÉĖ_ē’yKĸéĐŽU‰ķM&NŸŊĀŽŊHIÕáääÄáŖ'˜ģxKAūė=x„ä”Tœœœˆ¨Qķ/ßŌÆ?ūåĄ!!¤¤¤`ąX(,,$1)ĨxīXŸ›GĮgÛ1nôHžmז]{ö P*Ņįåҝw/ƎÁáŖGX÷ĶF† İÁƒ¨TšRŠķרQ�?†ÄÎŨ{ŠΘQÃ9b(KVŦ ĀR@—į:2°_.]žŠÁh 99w÷r;’˛$z-Cû÷ÆÉé÷{ĢfOsõZKWŽcŲĒuôyžË-ķTÄúŋÁ`4bŗŲJ~ī!-„x<•ëąŋŋoŧūgŌté\ž|™fÎĸu˨T.øúú�āã퍏ˇ7ÆĪß�ĩĢ+‹…T]:™™Ė˜5�FMŪõĐžņGYåâBåĘEÃ8:;ģ`ą˜IĶĨą%f[ˇīÄl2TæeņņōB—ž@zF&OÕŽ…ÍfÃĮ˓A}{PXhÅÅÅåžÛKģŪ–ÉlÂÅšhĩ{{yŌ¯g7ļlÛYâyÁũ‡Ž˛˙ĐŅ2/ËÍZ4kÆá#ĮPšĒhܨ!ģ€ĢJÅá#Į8vü™YŲx¸JöōôDĄ('ęÆ˙323ņķ)ڞžžž`ŗ•:˙ :Ž:ĩ#ĸĪ@fFVņ{žžž89)hŪŦ ö"#;‹vm[—Ķ+HĶĨsäø)ēvlGNŽūz ^\øí �YŲ9x^˙BqŗŠX˙7&4šÛGB“ âÉSŽ{ÄŋÄl%.!?_š7kJįN8qę~~~$%% KOgûö%Îīįį‹€?/Œà ãÆ0jİ{Ú+ō÷ķŖ[×Îŧ0n #† Ąc‡gËŧ,ēŒLü|¯ˇīKZz‘5ĢãíåɲUëŲĩ÷�N׃Éjĩ‡”—gŅ— P”žzü|éßĢ;Kĸ×ÜrH´ĸ5iō4ĮOžäØņ“4kÚ¸øõÍ1ŋR­Z8#† áŠēĩąQō…sZzŅaŪ”ää;Ν�›ÍŠŸ¯/I×§MMMÃĮ×̏ŊëĢŽgÚ´bœ={žúQõĘk‘Øš÷�UBC )ú’ĻKĪ$Āŋhûúx{‘‘™u§ŲË]~)ÃTĘđ•B<yĘu¸víZ,[žggg”J' , Øā  BC‚ųöûĐëõôéŨ‹œœėÛæ !44˜é3fa)°HXHČ]ûíÛ§+ĸWŖÕjČÍÕ3tđ€2_´nĶ/ôęöz}†%+×ĸrqÁßĪ—Ąz“šĻÃ`4ōtƒ(>ÆČÁũĐedRPP€BĄ >!‘.ۑ‘™YrÍ=ēĸҍ: �į.ūÆÎ=ĘTķŊpqv&0 �ĢíÖ=úČZŦ]ŋ‘ĢW¯ČųķILJ*ąįģuaŪüEc0äcŗŲJœ?.!jááĖøqƎfņ˛˚ģ€ÜÜ\†x[ģžžžhĩZ‚P*•åžė‹Ŗ×đ¯ŋū‰Ģq ė=p˜Ą{3t@oÜŨÜXšnSš÷w'†|xŨū%S‚Xˆ'bâĉļéͧßĶÄGO_$$Đ¯‚KĒx Éi˚;ßŅe”ĢqŖG>ÛæĢo˙ĮAđŋ~4âN>øĪį ĸ{ķõ´ÉÅ?ŋōÆģw~ėđA<ŨāöŊū˜íģYũĶĪvõ'„xôLš4‰āë×čȀ⥐‘™Áü…K‰Œˆ¸§~ÔÍ_͖mģŠËZ­$&É=ÄB<i$ˆÅCÁÛ˛×^~ŅŅe<0……Ä%$:ē !ÄC@ú „B8ąBá@ÄB!„ŲÄj•ę‘ŋŊÂ`4ĸVŠPĢoLáQĨVĢ‹mcƒŅˆJĨrt�ˇ}–4jõîÂ.Ûõö…ģ.Ö ņ'.!•ŒŦŠ{8AESĢT„…ø3¤O–D¯Ãøˆ‡—Z­fH˙žÅļą‡ZĨĸ_īnŦ^÷ŗCˇáõŗÁũ{°4z}…|1ŌĒÕ îßŖÜÛB8Ž]÷ !„ĸėnžXÎ !„$A,„B8ąBá@ÄB!„I !„$A,„B8ąBá@ÄB!„I !„d×—ú|q‰ŠMæŠĒ§ÂŠ]UT âôšķ,^‹ÁhrtI”FíĘāūŊxĒv$Wâ’émiĩ̊ŒLkÖmrč6ŋąū7ˆ*~íđņ,^W!uõ×“Æ ę—{ÛBˆōaWĮ%ĻĸÕ¸âãUšĸęŠpŖ‘Ø„ä'2„ FKŖ×2~ĖČG~[ÚÃ`4ō㜠˜Ė×Q´ūoâĨŅëÉ7šPT@ųFKŖ×K ņŗëĐ´Ņd~äŸüĸQĢÉ7˜žČžÁ`4‘o0=ōÛŌĩÚá!|Ã?{ŖąBB@qŊ}!ÄÃKÎ !„$A,„B8ąBá@v]Ŧ%„pŦZ5ĢããíEaa!GOœÆby8Î{ !îŸąšfŌŽu �Bƒqr*:Õĩc{ F#™YŲ˜ˇØ‘% !ĘĀaA|đĐabãâСˇŖJ(WM5ĀÛۓŸcļ—š­}žįÄŠŗœ˙ír9TöpILJbyôj^{ųEG—ōȈOHbīÃĨžŸ—ox€Õ!Ę[šqVVķ.ÆŲŲĢÕJĨ€1Ŗ†ãééYžŨ<–<+WĸSûļŦX퓪K)6ņO¯1d`?:´o€ÕjãoŊC‡öĪĐ­Kį ésĪžũ¸8;Ķ´Iã i˙n^? ŖÉČŦË�đķĨK§vĖ[íz�“S˜4v8Ū^%˙;úōŗpEBˆōTŽAŧ˙Ā!"#"čŌš�įÎ_$++OOO/[N—N~^Ŋ{õ >.žŲķ˜”D×į:ŅøéFŦZģŽÔ”4 ­…4jXŸ–Í›3gŪB\T.¸Ē\ˆO¤vd$9š9¤ĻĨņō‹“HJNféō•xyz’o00jÄPÜ´Úû^o/Oú÷毌š‹iÖ¸!mZ4åķogPĢF5ęՍdמƒôyž ŲŲšh4j–Ž\@đĒ îۓĀ�?~Ũą›“gÎĶŦqCÔĢCAA9šyD¯ŨˆA}Ųĩī W¯ÅķLĢæ�T ĻjXõŸĒ @ŖõhŌ¨>ū~Ė[˛‚*ĄĄ4nX„ÄdBC‚Ųø(ĮOž)Žšy“FŒÔˇh;:ʂeĢî{ųo đįČŅãÅA|úĖ<+˙>�ȂEKČĪ7 ĪËŖEķĻ´jņûļŌ¨Õ\üíŖG CĢÕōÏŗ đGéė\ęü ĸĸØ´ųÜÜÜ  fĪž}degcąX¨Y‹íÛ1{ŪB ĐĒ5œ>wž)˙~…BÁŌå+Š^•fMËāFŖ‰ĻO7āā‘ãˇŧŪ0Ē.MÖ'ß`Ā,^ąĻøŊŠX˙73ŒāUĘ{rŸ°´r âF 0gūB2ŗ˛¨^­ĩkGRÉÝ+WcŅéŌyåĨI¤gdpåJ,�ŽŽŽŒ5‚ØkqŦ^ģž�?.]ēÂßūō*VĢ•w'H“ƍQ*¨^-œ–Í›ņå7ßHרN|üÉgäęõŦXš†ŨģPŗF ļīÜÅļí;xž[×û^ŽŒĖ,<ÜŨQ(DÔ¨Fzf&Z†Z58}öŊē>Įæ_wrõZ-›>MëMČÎÎÅh2ątÕ:<<ÜyiÜHNž9œE+°X,ŧųú˨Վ%öyđČqLf3'NŸ#2ĸņ IėÚwömZR¯Nm˛˛ŗ1Œløe+Á<ßĨÃ-Aŧ˙ĐQ�"LJ—[( dž…qåj,ÕÂ̞o˙AZ4o†ŲlÂRP@•°0žiۚœ\=Ÿū÷ ZĩhŽRéD•°PÚļnÅæ-ŋrüÔi hØ >:´įČŅc¤ĻĻ•:ũ¨(dž…b-($!!‘?ŋú'l6oŋ;™–-šáŦtĸZÕę´oזfÎæėšķÔŠəŗgéסWš,÷O›e܈Áüvųę-¯÷ęö~ö ……… ؇ÚĩjpîÂ% bÖ˙Íō Ĩ~6$ˆ…x”•kûûûņÆë&M—Îå˗ųaæ,ZˇlJ傯¯�>ŪŪøx{sđĐaüüũ�PģēbąXHÕĨ“™‘ÉŒYs�ĐhÔäéõ�ҡU..TžžWæėė‚Åb&M—Æ–˜mlŨžŗÉLppP™—åęĩ8B‚ŅjÔ?u–ˆᄇ…°ņ—­ôéŅ…v­›ĶĻET*ÉŠi�¤\˙nŽ7ˇß÷ČöîŽÉlFĢŅāâârOũ§éŌ0™Í¸_o++;�ŗÅ‚‹ķííė?t´8ĘËŗĪ>ƍ?ãããƒJĨÂMĢÅl6Ą@.=y ĄT*1›~ŋz×ÛĢh×MĨRĄ×ëÉÎÎĻnŨē�øúúÜqūĸå×áPôųP(TöŦLVVöõ6Š>KíÛĩá×­;P89Y įōų8˛lõz†öīMôē Ā‘šĖžž×­ģ¨ąūo¸Ķ^¯ąļr â_bļRģv-ÂBBđķõAŖŅ°w˙ēuéĖöģĐĨ§súÔ´nˇ:öķķÅ?Ÿƍ .!ážÎ/ûûųŅ­kgĒV #;;…SŲo>}î"íZ5')9• ŋ]Ļg×NäŒĸKĪdËö]$$&ãáî†ÕjŖndž>Ū�xx¸“ĢĪÃÅŲ™nÚ3yڗ899Ņ ^]œ ,–‚âĐđōŦLfV66ŠįaãëãƒÉdfĮÎ]<Ķļ5))Š�œ=wŽôô ^?†Ä¤$Nœ:]jžž•IO/úb‘”œ|Įų °Úlúú˛mĮ. čÜtff^×? 7ÖS­ˆ/YÁļm;čÕŗ{š.wBb2/_Ą]ĢĸĢ• F#Ž*Wœ•J ņ÷ķåĘĩcåÚįä—ļ6› ŖéÉŽUˆĮAšqíÚĩXļ|%ÎÎÎ(•NX 4°ÁAA„†ķí÷? ×ëéĶģ99ŲˇÍBhh0ĶgĖÂR`!((°ģöÛˇO/VD¯FĢՐ›ĢgčāTōp/͞\ē|•ņ#3cŪb˛˛s¨Ɲ;ŠžLŦ˙y ŊēuÆ`0āáîNôē (œ(•Jú÷ęFP`�?mŽÁRP@jZ:#÷#/?Ÿs~ãšgÛrôÄ):ĩoKDÍęTŽėAVv6iētjÕŦNŗÆ ËTwEhŨĒë7lĸG÷ŽÅADRr ŗæ. 0Ā­ZÞũKœŋM˖|?ãGbc¯ĄÖ¨ąŲlĨÎ_5Ŧ ë6ldԈĄT­Æ3gcąXčŪ­3毯nҞ9"$8¸Ü—{Ëļ]ŧöãČČĘ`͆Ÿ3ČaL�� �IDAT3|ŖƒŅąxUģĄ”CĶ&“›ÍöĀęB”?ÅĉmͧOŋ§‰žžHH _—Tņ’Ķ˜5wžŖËp¨qŖG>ÛrĶĪ[đ¨äNë–-î:í˙ųüTtožž6šøįWŪx÷ŽĶˇiŲ”Á}{Ūöúĩø>ųęî˙~oîOáx“&M"øú„ č!Yŗį-Ābļ0áúЌĮŲŽŊšt9—ß˙ÉÚlĒĶ9°*!Dy ŦąŖF8ē„*éúi!ÄãEú „B8ąBá@ÄB!„I !„dWĢUĒG~\[ƒŅˆVãZâ=ŠO ĩZVãúČoK{ŒFT*•ŖË�¸íŗ§QĢŠ¨;m×ÛB<ŧėēj:,ğ¸„T2˛r+Ēž §VЍV%ˆ!ũ{˛$zÆ'(Œ (†ôīIՐ@Ž\Kz¤ˇĨ=Ô*ũzwcõēŸēÍoŦ˙› î߃ĨŅë+䋑V­fp˙åŪŽĸüØ5 ‡B!Ęîæ=äąBá@ÄB!„I !„$A,„B8ąBá@ÄB!„I !„$A,„B8]#k%&&VTB!Ä#īÆ ö°+ˆī§!„B”NM !„$A,„B8ąBá@ÄB!„I !„$A,„B8ąBá@ÄB!„I !„$A,„B8ąBá@ÄB!„ŲõĐ‹ĩ€Ī/dsü6ŌM™÷4¯Ú›Î!í9 §{ënĮÎ]¤ĻĨ1 __ūųæ;X,>ûäc{J-é|ũí÷Lza<˙Į›,˜;ë× „âņfWĪ:ŋˆÅ—VŲՁΘÁĸK+Q(ÔH cÃÆM|6íÎĄúLÛ6Å?<tˆ˜Ííęŗŧøx{ķīwŪ")9Ų!ũ !„xüŲěļŨW'ã"‡ą6vn üëŋķ˖VŦ\—§'•*yđæ?ßāÍwŪÃbąāįëKDDM D§KįíwßįĐáÃl\ŋ…BÁGĶ>ĨAT=ēwëZÜĮ_}Clė5,<׊Ŋ{ö¸Ĩ†ˇŪũ7Ž*W45į/\ Eķæčté\‹ģÆw_ÉŠĶgøü‹¯đõņA—žÎW_|Fnn.˙Į›|ōŸŠ÷ĩÜB!ÄŨØuŽXgĖ°ģƒ ‘Ã1¨xŪ:uj3õãOøōķOøxęčté;~ggžnؐ7ūö×âyû÷냯Ÿ/S&ŋĮSuë˛wß~l6ģwīĨS§ŽÅĶ;ŖĮŽķßĪĻņåįŸ0ũ‡™˜-–[ępV*iذ>ũ/89)‰¨Yƒžņ:™dffĄ×ëyëŸo0íã aīŪ}v/ĢBa/ģöˆíĄ@ÁŸžĮĀj=‹_›0n,šššdegķö{™IzzQH‡……–ÚېÁY¸h NNN´hŪ•‹Kņ{qqq$%%ķˇü �wwwâãx˙ƒøĮߋÂŨßĪ�WWWüü|‹6šŒ¨T*~˜9 ­›–'OҤqãōZB!DŠĘÄ­šą7õ V›­ø5 ^­7~áĪß2ísŋžž>|4e2...\%(0;wŠRûiÚ¤1SĻ~ĖÂÅKyõåoy¯J•*„‡WåĶ˙|Āų Š^-œšŗfÜķrL™úžüüÂÂByåΝcĩYīy^!„â~•)ˆĮÖ˜ZCØôß`ĩŲJ áĨ—×Ü2ßßūōg^~í¯h5 ™öŅ”{ę¯OīžŦ˙i#5kŪōzd­jGÖâ/¯ŋÉlĸfD֊°kYÚˇkË{“§DDÍĖ_°ˆúQõėjC!„°—bâĉļéͧßĶÄíÖ÷)ūšM`s>lō¯âß7ÄmáĶßķרIô¨ŌšøõŗVņŋŗsØŪcu™Š9k>ŪŪôíĶĢLí!„Ž4iŌ$‚ƒƒ2ėīI9†¸-të@÷°N4đ~Ў âi–]^[Âeõ¯ˇßÅl2ËĖB!+÷ÄV›iĮŋÅb- wÕĸۈnáõ×6ķŨ™Ųe¯đēĻL.ˇļ„Bˆ‡E™†¸´aãŋ'§ŗ&vĶ-¯¯ŋö ŸžøļRæB!”ÃXĶ ã q[øėäwÂB!Ä=(—ûˆmØøâÔtÎd^`sÂÖ[neB!DéėÚ#öU{—úžÕfcSü¯Ĩ†°¯ÚĮžĘ„Bˆ'€]AÜ%ôŲûî¨kæB!Wvš[k(�?ĮoŊįq§}Õ>t }–1ĩ†Ø_Bņ˜ŗ+ˆ]œœ™X{$kŦ¨z„Bˆ'J™¯šB!Äũ“ B!H‚X!„p b!„$ˆ…B’ B!H‚X!„p ģî#ļX øņüB6Įo#Ũ”yOķøĒŊéŌžq‘Ãpq*—Ą­ˆWūü:_ņ™CúŪąsŠii´nՒŋ˙ãMĖå:„BT<ģ’qÖųE,ž´ĘŽtÆ ]Z‰BĄ …Ĩß|÷?~øūģÚpG…0Ā3mÛ�”œė°„B<vņæ„m÷ÕɸČaŦŨD‹€FůMžōYYYŒÚ´nÅđĄCøâĢoˆŊ†Ĩ €į:u wĪŧųÎ{X,ü|}ÉÎÉÁUåŠģģ‡eĘäS-ŧ*˙ūāCrsrÉČˤWĪįéÛģĪ÷îĮķŨē’œœ‚B•+W&99…jÕÂyqŌ ü˛%†+WãåéIĨJŧųĪ7nŠšc—îÄüŧ•ĢÖđKL jW5jšĻLžeē’jžŲ[īūW•+šķ.ĐĸystētŽÅ]ãģ¯ŋäÔé3|ūÅWøúø KOįĢ/>ã—-1$$$Ōŋ_ŸûZßB!vņŊŽ/}ŗ ‘Ã1YįŋfŗŲØžs'Ë/ĀĮۛĶgÎrîüŽ;ÎÜY3(,,¤gŸūtëÚggĸžzŠĄCņŪûPˇnmôë˜yķŲļ};!!CŠ[§6ƒô'#3“‘cÆĶˇw/ , č×?žn֒[cPĢ]éŲw�/NzŠÂÆõĢQĢÕüõo˙āØņã4lĐāļú׎˙‰?ŋú'6hĀšsį),,DŠT”ZŗĘÅå÷ŦTŌ°a}zõxž‰/žLDÍL7†!ÃG‘™™…^¯į­žAÕyīũØģwŸŨëX!ÄŖĢÂNÚ*Pđ§§Æ1°ZĪâ׊BĖ …BÁä÷ŪáŨ÷&““›ËČáCQ($%%ķˇü �www˛2ŗ� -n#0 �ZCFf& >>wŪ›Œŗ‹3FŖąxZ?�ŧŧŧpsĶÕPPHnn.YŲŲŧũŪû�ddf’ž^ō—ŒŋûĶ˜É´O˙KĢ–-¨];˛øŊ¸¸¸ÛjŽOāũ>ā˙kQ~Eu¸ēēâįį[üŗŅdDĨRņÃĖYhŨ´œ8yŠ&ÛŊŽ…B<ēĘÄ­šą7õā-Ī!V āÕzčūü-ĶîÚŊ‡ēuꐗ—VĢåÛ¯ŋ //Ÿ^ũđŨ×_^•O˙ķ�į/\,RP”Ú˙Ū}ûIHLäŗiséŌeļoßyך=<<đõõáŖ)“qqqájl,A%NŸČG~€ÍfcÄčq<ß­+ÕĒ…PĨJ•ÛjŽ^-œšŗfÜĩ†ĻLũ_~ū aaĄŧōį׹ÚŦ÷<¯BˆG_™‚xl­!ŒŠ5„q1L;ņ V›­Ä^zy � ‰ŧ˙ŪÛ(•JæÎ[ĀĖgãėâĖāũ‰ŦAíČZüåõ70™MÔŦQƒČZw­!"ĸ&—¯\å_oŊCĩjáxx¸ŗnũ†ģÎ÷ˇŋü™—_û+Z†‚ÂBĻ}4ĨÄéNž:ÅŦ9sņöö"88č–Ŋķû­ųfíÛĩåŊÉS "ĸf æ/XÄĀũėjC!ÄŖK1qâDÛôéĶīiâvëŋx¨M`s>lō¯âß7ÄmáĶßķרIô¨ŌšøõŗVņŋŗsØŪcu9•-„B<ē&MšDpp0P†=â=)Øˇ…îa�č։ŪOâT<ͲËk‹CX!„ˇģī‘ĩŦ6ĶŽ˚ØMůŨÂë¯mæģ3ŗËVBņ˜+Ķ—6lü÷äô[Â`ũĩ_øôÄ÷ذ•2§B! ƚūcoˆÛÂg'ŋ“B!îAšÜGlÃÆ§Ļs&ķ›ļŪr+“B!Jg׹¯ÚģÔ÷Ŧ6›â-5„}Õ>öU&„B<ė â.ĄĪŪwG]Ë0¯Bņ¸˛ëĐôØZCø9~ë=;íĢöĄk躌Š5Äūę„BˆĮœ]AėâäĖÄÚ#™X{dEÕ#„B<QĘ|Õ´B!îŸąBá@ÄB!„UØķˆ…Bˆ‡™Élárl<™Ų9–üZĨŌ ĪJ•¨V%ÚĩBę°+ˆõųâS1šĖRŒBņ (•Nō „ųQŖj(*•K‰Ķ™ÍRuœ8sVSj`ģģi ô읰ļ+ˆãSŅj\ņņĒlwGâᑐœ@Ŗ§ė{v˛B<.Î^ŧ‚w ĄAwœNĨr!48� Ī˧~í%N—‘•C|ráĄ%ž'v#6šĖhÔjģ;B!&™Ų9øû–>Zäøz“™[ęûŪž•ĐįîĢšXK!ħ°ĐZęá蒨T.VH-ÄB!ÄuSĻLaʔ)´OšjZ!„�Ū|ķMfΜ @~~>S§N} ũJ !„xâŊõÖ[˜9“˜˜�:vėˆ““ĶŲ;–CĶB!žx.\ &&†¨¨(ĸĸĸˆ‰‰áüųķ¤ī‡"ˆsrrY°h �ŗæÎᇙŗ9yę4;wīš¯öĘ2īũú~úLb¯ÅŨuē=ûöŗvũ†rí{ūÂŜ>sļ\ÛBˆĮ™Ré„Ųl)ū}ųōåDEE˙Åōåˋ7›-8+•RKšąŅhäŸoŊw˂åäęyķ÷ąZKžú†J•<1Ŧč1‰.üÆÄ c‰Ē÷m[ˇ˛Ģ†/ŋųāžæ}ÔddfyŠĮOžæƒ?åËoāũП˜Ta} !ăæUš)ē{{œ/@Š.ĪĘRKš#VĢÕ4nԐ}ōL›ĸÜžc'íÛĩ%%5•ĨËWâåéIžÁC9qōû÷DéėĖāũ™=o­[6';'›EK—^ĩ*:]:Ŋztgņ˛čtéäįåŅģWOĒT aƏsņđpG§KgČ Ä^쯅 ųiã&ŧŧŧĐéŌéÜŠŗį.@ŖŅ••ÅķŨēQ“w'OĄi“&ķ \‹įĩ—_ÂŲų÷o:{÷īgīžƒX­VęGÕŖs§,^EVv6‹…Ú‘ĩčĐž[~ŨÆŠĶgđ'#3�“ŲĖŦ9ķQĢ]ÉÍÕ͎WÂÂnŋÁģ´éJĒíjėUVŽ^GpP fŗ™ā`ŽÅĮsõJ,GŽāĀĄ#ėېÄäd&‹ŋŋ_™ļįöģéŅ­ DąrÍO9v‚ā 2ĩ)„‹đ°`NœšŨ#|§‘ĩRt$$§Ō n­ ŠĨ\MwėØž­ÛļPXXČž‡xĻM+VŦ\Cî]=ruëD˛mûœ•J\Õj^yiÎÎEe´iÕ’Ę•*3lđ â6¯\E§Kį•—&1aüôz=úÜ<:>ێqŖGōlģļėÚŗ‡V-šãééÉķŨēĪģ}×Ē…‡3fÔpFŽʒ+‹j+°ŌĸYS苋‹ ‰IŋīíY­VÖŦŨĀk/ŋČ__{'''âââIHHä…qcxqâbbļ‘—ŸĪĪŋláÕ?ŊČĐA0šL�ėØš›ĐĐƎÁũYŊĒÄuUÚt%ÕļvũF† ȈaC°Ų@ĄPĐĒEsj׎ÅĶPĩJ(ãĮŽĸy“Æ;~ĸĖÛ˛o¯įų~ÆlĻNû/ŋnßÁŗíڔšM!„xXhÔŽ4Ŧ‰>/ŸC'βs˙Ņ˙;tâ,úŧ|Ô­õpŒ5}7Ū^^pîüE˛ŗŗhXŋjĩš4][bļąuûNĖ&3Á×÷Ŧü|}îÚfFFž×§ķņöÆĮۛėŦl9Æąã'ČĖĘÆÃŨŊÄyu:ujGĪ›™‘uS­ž�¸ĒTXĖŋm0qUģĸŧ~. S‡ö9z ˙€ĸ=L…BAeĪʤ$§āĻÕâ䤸eYŌtiÄÅ%œœ\<}Iî4ŨkËČČĀ×§¨ũАāÛ đ÷@åęŠ^¯/q{Ė[´”ŋŊö'jEÔ fëĸW¯gôđÁenW!>ļ;žgģĶÛå Üo_ęÚų96lÚLNn.ĮĀßĪn];SĩJŲŲ9(œœŠŽF+%¤næįįĮöģĐĨ§súÔRu:ĒU į™6­øuÛvb¯ÅĄP(°ũamųųú’”œ 4 55 _¯ģö§Õj0 X,”J%kÖ˙D“FØļc�VĢĖĖ,ÉËĪĮjĩâääDJręõeõĮ×ۇÎĪuÄbąž^ō9ˆ{ĀŖR%˛˛˛ T“˜HhHH‰Ë[ž˛ŗs¨TŠč|ˆģģ999֗B<h&ŗ…c§ÎÛõЇ†õ"qĩc4Ž{UîA\ĩJųųųx{yâåY´gסO/VD¯FĢՐ›Ģgčā÷Ü^•°PBC‚ųöûĐëõôéŨ /o/ÖŽßČÕĢW  äüų‹$$%ĄVĢYŧlUĢ„đL›ÖĖžˇ€Ys››ËĐAīڟBĄ`@ß>|ũŨtŦV+ Ô',,”ĒUÃøaæl, ŨģuFĢÕĐéŲö|öÅ×øúúâîá6mZĩdöŧü8{Ų99´kۚĀĀÛŋ×é�zvīĘÜų Âh2aS(đ÷÷ãĖŲķėŪŗīž×Ĩ=FÂ˙fÎÁĶŗ299šŧ0vd…ô#„Žp96ž;úp96ž:ÕĘŊÅĉmͧOŋ§‰žžHH`Ų.öģZ­ÆßĪ—+WFķĻMîģ=yú’âIˇįĐqšÔ¯[ŧ'<pā@Ū}÷Ũâ[˜Nž<Éäɓ‹oa2›-:q–VMę—ÚæŅĶīųīę¤I“.:Õ(#k=,ĖZ‚ˇ7……ôîŲÃŅ% !Ä#í}¨UĢ;vŧed­‰'ŋ_‘} ~Ô¨V7˙ņ7G—!„­?ü›ÍFĮŽ˜0aÂ{øƒąBL:­V ĀÛoŋũĀú• B!Ž{|ÃC1Ö´Bņ¤˛+ˆÕ*ŖąĸjB!ˆ?>ôánîöЇŒŦÜĩšûĒÅŽCĶa!ūÄ%¤’‘•{_‰‡ËŅĶ]‚B8„ģ›–]aÁwžø†]nnšR˙nēk5„ŨßíŊvąģVCˆĒ÷ՑBņ°0M÷õЇŠoZ.ÖBņÄšņЇ˱ņÄ%Ļ”z°RŠÄ̞ĮŖķĐ!„âQáĒrА!+í%WM !„$A,„B8ąBá@ÄB!„I !„$A,„B8Üž$„â‰d2[¸Ofv……Ö§Q*đŦT‰jU‚ŽûˆõųâS1šĖRŒBņ (•Nō „ųQŖjčGÖJÕepâĖE4ZMЁíîĻ!4ĐīžÂÚŽ ŽKLEĢqÅĮ̞Ũ QŅ’ĶhôT„ŖËB<Î^ŧ‚w ĄAwkZĨr!48� Ī˧~í%N—‘•C|ráĄv×b×9bŖÉŒF­ļģ!„âa’™ƒŋ¯÷=OāëMfvé<ōöŦ„>Īp_ĩČÅZB!ž8……ÖRG—DĨr)u<겒 B!Ž›2e SĻLy }ĘUĶB!đæ›o2sæL�ōķķ™:uęéW‚X!Äī­ˇŪbæĖ™ÄÄÄ�ĐącGœœœČŪąšBņÄģpá111DEEELL įΟ }?Aœ““Ë‚EK�˜5w>?˜ÍÉS§Ųš{Ī}ĩW–yī×÷Ķg{-ÎŽy“’øō›īËĨ˙u6ßķ[ øâĢoÉĘĘ*—zėq§õpčđQV­Y÷€+B<î”J'ĖfKņī˗/'**Ēø÷¨¨(–/_^üģŲlÁYŠŦZĘíĐ´Ņhäß|Ää÷Ū.ž-'WĪĮĶ>cĘûīāäTzæWĒäÁˆaC�¸pá7>ūđũûĒáËožįĩ—_$ĒŪS÷5˙Ŗƒ 73bØzvīZĻļbbļōtŖ†xzz–SuåŖIãFėŪģÄ¤$‚ƒ‚ĘÜŪūƒ‡Yˇág iōtCú÷éÉĄ#ĮXšf=ŽŽŽ„1aĖ,|ūÕwX,F&ŒAxÕ*å°DBˆ‡WåJ¤č2 žķ}Ä7¤č2đŦėQ!ĩ”[ĢÕj7jČžyĻM+�ļīØIûvmIIMeéō•xyz’o00jÄPNœ<ÅūũQ:;3x`fĪ[@ë–ÍÉÎÉfŅŌe„W­ŠN—N¯ŨYŧl:]:ųyyôîՓ*UB˜ņã\<<ÜŅéŌ2h�ą×ŽqáÂE~Ú¸ ///tēt:węĀėš Đh4deeņ|ˇ.DDÔäŨÉShÚ¤ Æ|×âãyíå—pvūũ›ÎŪũûŲģī VĢ•úQõčÜŠËŖW‘•ÅbĄvd-:´oĮ–_ˇqęôüÉČĖĀd63kÎ|ÔjWrsõôíՃ°°ßođÎÉÉå‡gāŌšhõ§éŌ™;!ûËĢ�üķ­÷øøÃ÷yīƒŠT§VDM‚Yšf-•+U";'‡''zÕZŽ^‰åČŅc;q’6­ZP%,ėļeö÷ķãŋ_KTÔSäääĸQk2¨˙-ÛoמŊŧ÷Λ�,\˛ Ŋ^ŲláŠēĩéĐžĢÖŽ#5%Bk!Ö§eķæ%ާƒ‡ŗīĀ!ÜÜ´€‚ąŖ†3wū"\T.hÔj.ūv‰Ņ#†ĄÕjo[ĨõũLÛÖėØš›!ƒ”é3j2™X´4šO?zĨŌ™#ĮŽcŗŲ˜9g_LûĩڕOūû įÎ_äjė5Ē„…2t`?“ø~ÆlĻŧ÷f™úB<<ÂÂ9qæ"PtđFÖJŅeœJƒēĩ*¤–r=4Ũąc{ļnÛ@aa!ûâ™6­Xąr =ēwaôČaÔ­Éļí;pV*qUĢyåĨI8;•ŅĻUK*WĒ˰ÁƒŠÛŧr5.W^šÄ„ņcĐëõčsķčøl;ƍÉŗíÚ˛kĪZĩhާ§'Īwû}ĪpûŽ=T gˍáŒ1”%+VÕV`ĨEŗĻ Đ“’ŠįąZ­ŦYģ×^~‘ŋžö2NNNÄÅœČ ãÆđâÄ ÄÄl#/?ŸŸŲÂĢz‘Ąƒ`4™�Øąs7ĄĄ!Œ5‚!ûŗ,zÕ-ëhמŊ4lPŸÆQķŽëŗĀR@—į:Ō˛E3 &#˙ĪŪG7Uæ§ŲĶ´M÷ļ”ŌRŠ"ûž‹˛#ŽĀ8:.ŖŖŽûā6ΌŋqGAd‘UvDą TZ  Ũ›ļiŌ&mōûŒƒ,m %ˆß×9žc’û<Ī÷ŪrōÉŨž;vôHîžsĄ!Áúé]:u$%Ĩ%×Ĩĩģā:+”J,UUŒ6”;'MäûŊ{ΧŌbA¯×ŖVŠpš\8Î¸ŅŖøËŸ§’˜@NNGf1uō]üiōŨŦøz 5vûYÛ `Ņ—KųĶ”ģšëöÛĀå$ũā!”JšÅÆ0□¤ĩģ–ũ?ϟs;œkl€æqqÍĘĒį__ũrrķ0™øhūgŧōŸ˙bŗUc6—c0čŅž–.))‘ŒŖ™dÍt×ÉÉü‚K_qåĐë´´ģ&K••Ũ?bËÎŊįüo÷‡°TYš6ĩå•1×t}‚‰ˆį§Ã”—›i×öt:EÅEŦ[ŋ‘ ›ļ`¯ąuęchHpŊ}–––rzšā  ‚ƒ‚(7—ķũž}ėÛ˙eærüŒÆsļ-..ĻUJ˛ģmYé¯į?ƒO‚Õj48ėŋΝmŗUŖÕiQž>Đ¯O/öėŨGXx(� …‚�S�ųø øø(ÎX—ĸâ"˛ŗsÉĪĪw/˙ŋĘĘĘHMM $$¤Ūõ˙eĩJÉĒÕkŅjĩd;NR‹h5š¯s ÉäŽåˇ5ŲŦ6 ŊûŗIĮ1oū§TY­ôëĶ  ĘJ˘9{�zŊŽ’â’ŗļS•ÕŠVĢA}z74$”ĸââĶÛ;�FƒÅbĄŧŧüŦípŽąãâša0úbĩV×ģ­ęãpÔb6—ķܓŖÎéäÁGūÎS˙õŒe\N'Š&:$„¸Rš.ø™ëB7‚Fŋ}iЀūŦ\ĩ†ŠĘJĻÜ}�aĄĄÜ8h�qÍb)/¯@áãsęj´ßš„††˛iË6�ŠKJH˙ņ …ÅÅ$$ÄĶŖ[žŲ¸‰ã'˛Q(¸~ŗĩBCB8™Ÿ\KaaÁ!õŽg0čąŲl8”J%K—¯ }Z7oĀétQVf&""‚*̧͉ų…§×5Œ `ôī‹Ãá ¤¤ôŒūMĻ�JJJ�N×ĩšÚÚS Øl6lÕŋN“öË&úôŗEL|7aĄ!LŸ1 ÎF[g^ÕzjĖęęj´ ÷Ũ;Å}Ū˙ž?O!,<ŒÉwŨ@vn.‘‘gm§áCoĻēē‡ÃZ­æd~>Ũ;“““{֘įÚįûē´vXĢŦÆ9į�� �IDAT —>ĩjLtJĨ …ĩJ…Z­ÆßĪj[5V› ƒ^ĪĄÃŒ1 ZÍO‡3¸Ž][NdįĐ,&ú’ĮB\9jėöũxØŖ‡>´ģ&­ŗq5TŖq\ŗXŦV+A&O_ø3ü–Ą,úb ƒžĘJ ãÆ4ü\_ŗØbĸŖxgúûX,n6”Ā @žZū5ĮŽ#<"‚Ç3Č=yNĮ§Ÿ/"ŽY,�=ēuåÚ3ûŖŠŦŦdÜčQõާP(¸uø-ŧõî œN'íŽmKll qqąŧ?ëCƒo€Á §_ī^ŧöú[„„„`ôķ—‹n]:ķá܏ųāڔWTĐŗ{W""~Ŋ [įÎLŸųĮŸ@§×áršđĮh4ōÉg ņ50úžŊ‡ßöškøø“Éúo6s×9xč0Ûļīp/w1ëėīgÄV]ŖļĩZÍÚõXĩf>J%=ēw%6:š˜˜(f˜ŖÖAddąˇ ;k;Œ9‚éī€Á Į`ГÚ*…ī÷ė=kĖsm‡s yė8ÍOĻžFŖ/ƒú÷å/˙—ËEnņķ3rĪˇņâ?_CĢÕŌ<>މ ÄĮ5ãŋoOįÅW˙ģŨΔģ&]ōøBˆ+Gæņĸ=|čCæņZ%]úwŅo)ĻL™âš1cFƒŪ›žAtDhŖ!ŧoåĒ5øųéŪĩ‹ˇK9˛īŧĮČÈŽįĒiyú’ĸĄļīŪOûļŠî=áQŖFņėŗĪēoa:pā�ĶĻMsßÂdˇ;ØũÃ!ē´o{Ū>÷Ļg4ø;hęÔŠDEEWČ}ÄÂûú÷ëÃ÷{öyå>â Ųŗw1ŅQõ†°Bxâˇ}hŲ˛%}ûöå8p€ž}û’œœėūŧ)ú S\ �Ô*ũåĪŪ.ã,×Ĩĩ;ãĒp!„h /Ŋô.—‹ž}ûpĪ=÷\ļ‡?H !„ĀË/ŋŒÁ`�ā駟žlãJ !„§]Î�ū…œ#B!ŧH‚X!ÄÎoúPŸĻ|čƒGAŦĶh°U_ú GB!„7ũōЇ†ĒīĄĨæ Œ§g(ô”GįˆcŖÃČÎ-¤Ô\yQƒ ŅÔöĻgxģ!Äī€JĨ$/ŋhøC| žįũŽ1ôÄD^Ü<ąŅ §URÜE $„B\Ijė2įWpŪ{„•J%~ŋŸ‡>!„ŋZēIĻŦô”\Ŧ%„Bx‘ąBáEÄB!„I !„^$A,„Bx‘ąBáEÄB!„ytąÅj#;¯ę{SÕ#„Büî}õÄD„^Ô¤qv^!Ŋ–āĀ�ēÚåæ‘Ö:ÉÛe!„đ‚Rs9ųE$ÅĮxÜÖŖCĶÕ5vô:Įƒ!„Wŗ “?–*ÛEĩ•sÄB!„I !„^$A,„Bx‘ąBáEÄB!„É툝pßīŨOiYJĨ’‚Â"ƍá풄B4ĸF bŗŲĖÜųŸĸRŠp:Ô:jšcŌL&Sî{ąŖFЧWO�œN?õ }zõĀ××ČŽīvSeŠĸĖ\NLL~~FúõîÅīŧGB|3\.¨ŠŠæ–aCi•ܲ1Ëžĸ]Ÿv-�ßlÚâåJ„B4…F âģv“œ”ÄĀũ�øépfs&“‰Čđ0öėŨīâôƒ1œš¤Gˇ.ôčօôƒ‡Øžc'“ī瀪G3IlžĀƒ÷ß @qI ož3iĪ>íķøņ|štF?#–J wŨy;Z­†ŲsæĄĶiŠŦ´0|čüüŒŧõî L&­R’9‘Ã]ˇO`ÚK˙äÁû˙Ė7›6QXPDŗŽ´vméÜą#ĪŊđ2Íããi™Ô‚Ν:\Ōöš˙¯ĶŊkgŠ‹KP(ø—”Éč‘ÃXˇaßîüĩJÉĀŸîšƒo6mĄ °ˆđ°ĐK[!ĕŠQƒ8­ŨĩĖ™7Ÿ2ŗ™æ ¤¤$ãīg@ĄPKÖąã$ÄĮącįwtęØģŊĻÁũWVZΚPäĮƒIjŲ‚ÁP\RŠBÁæ-ۈ‰‰ææÁƒ(,,bŪ' ˜|÷”•™yę‰Į�Ī<˙"ĩĩuâīįGeeGfņčÃāt:yvÚK´ŋūzjĩ ėߗˆˆđKŪ>ĩĩuôīĶ“ Ā@ÆLē‡9īŋVŖá/üŅ#‡Ą@Ác˙­V˟x”*Ģõ’ĮBqekÔ  åąGĸ¨¸„ĖĖL۟5›Ž;š÷${÷îÁƝWŒFŖÁ×`¨7ˆ3ŗ˛xãíé¸\.Ôj5ˇß6áŒĪûôîɊ•Ģų×˙ŊAhH0cG¤¨¸ˆėė\ōķķS?�‚‚‚đņQ� Ú\“JúÁƒüœ™I÷n](,.ĄŦ´Œ™ŗį� ×먲X� i´m€ŋŸŸûGE]]ûķéŗæ ×é°XĒ°ËœŪBqÕkÔ ^ģ~))-‰Ž&4$Ŋ^Ύ;wšƒ8$8˜š;›ˇlĨG÷ŽÖÛgķ„_MŸËɓ žq�ŊĨ_­`׎ī #$(˜ũûâp8())ātĐ­K'V­]O^^>Æ ádA>aáaîÃâŲšš˜LĻŗÚ5•ģO>˙‚™īŧNŗŽí;vátšš~`!„^Õ¨Aœ’Ō’Ī.FĨRĄTúPë¨eôoŽōíÚĨËWŽbČāA âúTUUņÖģīhÂjĩ1qü| ž|8÷c>øp.åôėŪ•¤¤3ČÃÉüPŠ”ÄFGŌ™ŗqÔ:ˆŒŒ 6:ú’ëk(ZMtT$¯Ŋųū~~¤ĩkËį_,!še‹ËVƒBˆËO1eĘ׌3´đŪô ĸ#äĸĄs‘§/ !ÄÛŪôŒįĀÔŠS‰ŠŠdB!„ÂĢ$ˆ…B/’ B!ŧH‚X!„đ" b!„‹$ˆ…B/ō(ˆu ļęęĻĒE!„ø]*5W`4č/Ē­GzÄF‡‘[HŠšōĸģÚíMĪđv B!ŧĀhĐyqķlxÄFƒžVIq5B!Î&įˆ…B/’ B!ŧH‚X!„đ" b!„‹$ˆ…B/’ B!ŧH‚X!„đ"î#B!Ž5v™Įs(+¯ ŽÎyÎe”JLūū$4‹B¯Ķ6IąÅj#;¯ę{“#„B\JĨ6ĢčČPãbĐhÔį\ÎnwPX\Ę3Đôį lŖ¯ž˜ˆĐ‹ k‚8;¯ƒ^Kp`€Į ‘›_DZë$o—!„ĘČ"("”˜Čđ .§Ņ¨‰‰ ĮXĒŦ´MI<įrĨæ rō‹HŠņ¸ÎWרŅët"„B\IĘĘ+ jđōá!A”•Ÿ˙9 A&,Uļ‹ĒE.ÖBņ‡SWį<īáčsŅhÔÔÕÕ5I-ÄB!Äi/žø"/žøâeSޚB!€'Ÿ|’Yŗf`ĩZyųå—/˸ÄB!ūđžzę)f͚ÅúõëčÛˇ/>>>—eīXM !„øÃ;räëׯ§M›6´i͆õë×søđáË2ļņy,ø|?Ļôv—䉧ž;īg‡ƒ^~•5k×7¸ŋŌ˛R^}íõ3ŪÛũũ^ž\ēėĸkBoP*}°Ûî× .¤M›6î×mÚ´aá…î×vģ•RŲ$ĩ4ęĄéå+Wņíˇ; Æå§ĶÉ]wL$(°ūKĎZĨâ†ö×ģßs:|üÉĖå¨U**-†Ũ<„ä–-øĪßÄ×ח{§Üí^~æė9X,nz3_|š€ÃG2HnyęŪÕ{îœÄãO=GJōŠ×6[57´ŋžū}{ŸQKÖąãTVZ¸Ļu*?ø‘UkÖĄŅhˆgė著ŧ˛îM­¤¤?Ŗ‘ũû^TûŲÍãÖÃi}ÛžŨAŪɓDEF^r]Ÿ-ú’ôC‡ŠĢĢc`ŋŪôčÖå’ûBˆß đ§ ¸”ب ßGü‹‚âRL~MRKŖŸ#îŲŖ›ûË}ëöoYĩfãĮŒfá_b./Įáp’Ü’>ŊzōÜ /Ķ<>ž¸fą|ŗižžFĸĸŖˆ>ũ…žCi™™‡ūōg�ŠKJ8vė„{ŦĘĘJ***ņ÷÷ÃjŗRVfF­V‘ĮŖ?@]]?ų >ü€ģVĢåáîNũߟyž.:âëkp/ŗaã&zõč†Ëåâŗ…‹yæÉĮŅé´Ė˜9›ŖG3 0đūŦyōņGŨml63g„Á §ĸĸ’aCķÉg šwę=„ŗ˙‡ė˙á�‰Í›ŗgß~4j5­†Ņ#G°jÍZ÷ēīún7…EÔ9ëHkזÎ;2gî|Ô5Zšœœ<R’“ЍŦ °¨ˆûīzÆöŸųÁ||| 4átšæd~>Ÿ-\L É„ÕfcŌÄqŦYˇžœÜ\žŲ¸‰æņ ,^úūū”WTpīÔģŲũũ^ĘË+2xŲšš,ũj9ãĮŽāčŅLöí?€Ãá`ō]wŌŖ{W6oŲÆØŅˇ^ŌŋÜŧ“üđãA^zū)ęęęxāŅ'éÔán1Bˆ†ˆâ‡ƒĀŠ{„/4ŗVAq)šų…\›Ú˛IjiŌ‹ĩ,•ô:=ŲŲ9äææņĐ÷áršxúŲitîԁZG-û÷%""œâŌRâbcÜ! …Z­föGķHlž@ĢädÚ_Ÿæūŧ[×Îl˙vƒögûˇģčÜŠģŋßĶāújė5¸\.TĒ37dfcŌÄ T”W ĶëНž˛,>!žŦã'č—ØëŒČ=™¸˜4a<ĩu,•UôėŪÍ[ļ1□|ģcƒôį‹%K6t-š'ƒ^¯Ŗm›6ÄÅÆāĒĢãčŅ,}øœN'ĪN{‰ö×_RéCķ„x:wėĀoO'**‚AmúņĪŋFĨłŸŅčŽģÆ^Ãũ÷NĨŌbaũ†�,Zŧ”!ƒŌ"1‘M[ļ˛qĶfúõíM™šœ>ŊzrčđƎITd$ķæĘĄŸŽ\pģ%&6'<,”qcFããŖ y\+W­nđv?ŸŸf‘Ôĸ9�JĨ’ˆđPōōķ‰o{É} !Ä˙Ōë´´ģ&™Ėã9dįœ÷aĨRI`€×Ļļŧ2æšnˆM[ˇsčđ\.Ļ€�ƌÁĄC‡ @ĄP` Āl. $$äŧ}ŠU*îûĶd*-˛˛ŽąbÕ”> &MĀ5ŠŠĖž3—AûsøHƎĒ7ˆkjjxãíé�¸œNƏ…V{æÆ­­­;+œp:QœįA‹æ \Ķ:•ˇ§Ī@ŠT2røP:vhĪ´—^寁ũ)-+#>žĮaÅĒ5|ņŗ¤ļjElė¯ĶĄ—PVZÆĖŲs�ĐëuTY,�˜L&�4j5§ĻUŠÔ8ŋÎû]VVFHp0�~F#zŊ€ĸâ"Ö­ßȆM[°×؉Š:ķ˛ZĨdÕęĩhĩZ˛Ž'ŠE‹ nÃß2}ąZĢ=jĶN§ …ĸŅûBˆ3š.ø™ëB7‚Æ?4Ũ­ËYįCCBظy+pęËĩŦĖLāé`ųå{VĄ�įoÖöāĄŸ¨ŦŦ¤c‡hÛæ’’yæų˙š”\Ą Uj+ÖoØDbB< ę˙ŌÖjĩ<x˙Ŋ\FŠRQ[[G€)€šęjlÕ6ô:=?gfqĶ įlS\RBjJ }zõä§Ã,[ąŠ{§ÜMrËĖ_đ9;Üā^îŽÛ'âršø×k¯sCûëÜëBXx“īē€ėÜ\w�7D€ÉDqI �ååØŦ§Ļ[ åÆAˆkKyy ,–_§jûôŗEL|7aĄ!LŸ1 N4j5ŽÚS2”–”ž=˜\§}[ĢŦ —>õiR‹æŦ^÷ �ŽÚZ ‹Š‰ŒŒ¸ä~…âˇjėöũxØŖ‡>´ģ&mœ*ģ,÷ĮÆÆËûŗ>Äáp0øÆč~3gu\l3–­üš`�ˆoÆŧœē˜I­ĻĻÆÎ„ąŖĪh×Ŗkgž{áež}ō ęjkĨŪĄ8˛Ž#ŠE"cGßĘ[īĖ@ŖŅĐ,6†øøf—”0köG<ņˇŋžŅîãO?Ãčë‹Ũa§O¯ž�ôî؃^y•qcN_Í:vœÕk×ãįg$88ˆĐĐ÷ēOš8Ž˜˜(f˜ŖÖAddąŅŅԏRŠâŨ31™Ü{ÎÃoĘĸ/–`0čŠŦ´0nĖ™įrÛ^s ˛€ā  ĸ"#Y˙ÍfĻÜs'7maŅâ% Pœõ‹0!>ž™Ėáž?M&ķØqšŸū›]ЍČŽkw-ĪžđOœN'ĮB­’[Ũ…/ķxŅ>ô!ķx­’.ũģîˇSĻLq͘1ŖA īMĪ :"´Ņ‹¸Ōdf㛍›¸įÎÛ/š¯ŦcĮŲ°i3wŨ~[#Tvezķ÷9bØį÷ĪEžž$„¸RlßŊŸömSŨ{ÂŖFâŲgŸußÂtāĀĻM›æž…Énw°û‡Ctißöŧ}îMĪhđwÜÔŠS‰ŠŠä>âsjž¯¯/é]R?kÖ}Ã_.eä°ĄSØhĪŪ}ÄüĪ•îBņ{đۇ>´lŲ’ž}ûrāĀ8@ßž}INNvŪ”}ã~į1îoÅĐ¯úõi„jŽ\×Ĩĩãē´vŪ.C!.ÉK/Ŋ„ËåĸoßS×8ŨsĪ=—íáÄB!đōË/c0œšSâ駟žlãJ !„§]Î�ū…œ#B!ŧH‚X!ÄÎoúPŸĻ|čƒGAŦĶh°U7ū JB!ÄåôËCĒž‡>”š+0ôU‹GįˆcŖÃČÎ-¤Ô\Y˙ÂBœÃŪô o— „¨TJōō‹€†?ôÁ×ā{Ūī0ŖAOLäÅÍŗáQ zZ%Å]Ô@B!ĕ¤Æî¸:ú „Büh5ę&™˛ŌSrą–BáEÄB!„I !„^$A,„Bx‘ąBáEÄB!„I !„^$A,„Bx‘ąBáEÄB!„y4ÅĨĶé⧟‘s˛€ę{ƒÚč´bĸÂIIŒĮĮGqQE !„W+‚ø§ŖĮ¨´TŅŊcZƒ'ŋļU×°˙`‡ģ"æôüŊ))-å­wĻķü3Oyģ!„MĀŖCĶ9y´MMjp;‚‚|ŽMM";¯€üȔ{īŋ¨Bëŗđ‹ÅÜ~×d†E§n=šũŽÉüõŅĮčĐšûYËīún7wÜ=…ŋ<ô“§ū™§ž}ūŧOßđ†'ž|†Ō˛2‚ƒ‚$„…â*æŅquũĸĨ×iĪ:”=íÅW0›ÍØĒmtëڅ ãÆōú›osüø ĩĩôīׇa79ŖÍķ/ŧDeE%Ĩee Ŋų&†ęūlÔČŒ9‚­ÛžeÉW_ņŸW_š`M3?øŋ=ō0­S[°äĢe˜ËË  r/ŗtŲr–,]F]]}z÷äŽIˇąrÕj–-_A€� ŧüâ4ž~îh5ZŒF_žßŗ—§=Ī?Ļŗ|å×´JI&=ũ #F܍°vŨz-^B É„ŋŋO>ņ?ũt˜ūû5´Z-ūū~Œ=Šo6lÄnˇķČ_äņŋ?ÍĮÍnđØ ņō¨J!„øŊđĘc].›ļláķO?&8(ˆôƒ‡øéđöîÛĪGŗgRWWĮ͡ŒäÆAҍO=ŦŲîpÚ*…Ҏޤ´ŦŒÛî¸ûŒ öÔØ1ŖxåÕĶąÃ ´ģļ-7€Vû돌ēē:Ū|ëV¯\†BĄ`ū' �øĪk¯ŗjåWhÔj˙ûĶlŨļ•RIjj ˇŽΜšķظiáááøųyčûųé§Ãü÷͡šqā�^ūįŋųzųt:}ôqöíßĪÛīžĮ=JJrK–.[N||qqÍxúŠ'¨ŠŠq×ÔĐąâ']ôvBqy5iûøøārēܯëęęP*}P(L{îž}n••Ü6a …‚“'ķyôņŋ`41—™ @äääōĖsĶPŠUTWW_Rm}{÷ĸwĪ9’Áž}ûøŋ7Ūâ_¯ŧHR‹�X, *ÕŠM4éļ TTT 7čŨ?bccČÎÉ "<�ŊNOiY�‘§ŪĶéuTWWSYY‰šŧœ§Ÿû�Ĩee”””’—w’˜˜h€ŗŽüÂĶą…Bü>4iˇLJb÷ž=$$İuÛvR[ĩĸĒƊÁ`āˇ^§ĒĘĘСōî[oį>¤|øH†;„žŨą“Üŧ<^û×?9z4“M›ļ\t].—‹—^y•G~””dRR’ÉÎÎaĪŪũî ö÷÷ĮbŠÂnˇŖT*yëé<ôĀũXĢŦØív4 YYĮHkw-‡iи~~~„„ķʋĶPĢÕ;~œČˆ>_´˜ŖG3šļm>YđŊzö@ĄPāt:Ũmũũũ/il!„WĻ& âŋ>ô�O>ķ,+VŽbƉ俿ņįžFŠTōŅ܏™õÁ‡¨Ô*ƌIrË$R’[ōđ#Qc¯ĄEb"É-“Ü}%%ĩ 3ëęâņķ3˛lųJn2¸Ū:Ŧ6wOų“ûõŖ?Äu×Ĩ1ųO÷a4qš\øųųË}÷ē—Q(ü푇øĶ}P[[Kŋ>ŊxüođĀÏâįgÄĪĪH×.Yŗv]ƒˇÉŖ?Äũūƒ^Om]˙zåEyøA^yõß§Îû:GÜļÍ5<ōˇ'xúŠ'Üm/ul!„WŔ)S\3fĖhĐÂ_­ŲĖĐ=.j Ki+„B\MĻNJTT 3k !„^%A,„Bx‘ąBáEÄB!„yÄ:­[uMũ ū†­ēÖķš„BˆĢGAÎūƒ…ņ/}ˆ ķ¸8!„âjįŅ}Äɉņ>zŒ-;÷zđD-ąQa$'Æ_L}B!ÄUÍŖ öņQĐ*)Ag(„B4šXK!„đ" b!„‹$ˆ…B/’ B!ŧH‚X!„đ" b!„‹$ˆ…B/ōč>b—ËÅąė“—bw8ÔFŖQD|L$ …âĸŠB!ŽVņąœ“Xm6Ú]“ŒVŖnP›샌ŦĪ9I|l”ûũ—^{›ŋ?üg||<ß)˙nĪ~JĘĖ ęÛĶãļWĒ5ßlĻuĢ–„‡†đÆ{ŗšîÚ6;‘͝F{ģ4!„MČŖ,,.ĨEBŗ‡puu KWŦfûŽdžČáÍ÷fsėDöEzĩzÎ'� č̓čČJËĘņõ5Đģ{gCØ\^ÁÂ%+šĸL!„MÄŖ=bģŨŅāX¸tąŅQŒ>„-;÷2aĖļ~싸fąø(|Ŋn#ÅÅĨXŦUÜ{×m•”˛tÅjüũąÚĒ;âfôz‹—­ĸ´Ŧ Ģ­šÁũ{ģû¯ĢĢãƒyŸŅĢ['Zļhî~˙‹¯VRUeÃ’Ô‚no`åšo(*)ĨŽÎIÛÖ)´OkËĢoL'>6†˜čH6mŨÁ“ÜĀâe_Ķ<>vîۃ¯Á€^¯cøAėŪ÷ģ÷ū€JŠäÎ ŖQ*•�äžĖgéĘ5¨U*ôz=FŨƒ‡/Ø>5Ĩ%?gcí†-—Đą}ßī;@ŪÉļ~ûļnį™ŋ=xΞw}ŋŌĄRŠđ3šuØ`–­ZĮ‰ė\~H?(Î[!ĕ§I/Ö:œq”ž];ē_š6x��N—‹öíÚrûø[QĢÔäå°ėëĩôīŨƒ1#n&%Š9[w|Į‰œ<JËʸgŌ8n3‚Ē*+pę|õü…KčÚŠũ!ėrš8x8ƒCqΤqÄ7‹!/ŋ€c'r¸}Ü­Ü1ūVÖnØBm]ĩĩuôîŪ…ŽÛBvn.—‹Ã™\“šĖ—ËWqĮøQŒu ••U;‘ƒŌG‰NĢåžIãÜ! °lÕ:nši “oOJR"UVkŊí;ĩO#ĀߟūŊģģûéŲĩ1Q‘të|ÃûV(Ü>~wNÍáŸ3ąUWĶáēkIj‘@ÛÖ­Î9ļBˆ+G{ÄÃår÷"­Đ �´Z ĩĩĩ—–ąyÛNļîøģŨNDxeær‚4h āģ=ûŲļã;ŒžžŒ>äŒ> Ŗ‡ßĖg‹—aŗUĶŖkG@A™šœyŸ-@§Ķē=8ČāŪĩÚĒIJLĀáp`ĩÚX°x�k•–ĒĶmĪZ—˛˛r‚OõÕ>­-ļęjÚ_ČoûūÅĸ%+Đj5Øl6ŽZ÷û[!ĕĨIƒ85Ĩ%ë7msīņ•™ËYļj“ÆŽ<įō!ÁAôíՕ˜¨H*+-(||(/¯`ûŽŨ�”–™ųéČ΍Õj:ŨpqąŅ|ēhéįRkjėh5jîžm,55vūõætîžm,Ą!ÁÜ6f�yųøû¸$¤&'ąjŨ&ĒŦ6zvíˆ^§ÃĪĪ—ņˇCŠTRT\ŠÉäĪsޟ!ÁÍÖßŅ:ĨeƒÚģ\Îzˇãoûn™˜Ā×ë6đėcQįt˛˙ĮC¸\.P(pš\į­]!ĕ§IƒxäÍ7˛tåۜņ!iíÚąā‹¯zc˙ķ.?d`_žZšŊ^‡ÅRňĄ7ATD8Ė[@•ÕÆMúPZfFĨRŅ&5…Ÿ3ąqëzuë€Z­bĶ֝Ŧ¯Û†RФķ ×NTd8}ēGm-aĄDE„Ÿ1ļ qą;‘MLT$�7ęĪė?CŖŅāt7ųZ›�� �IDAT:™0zøųkԏ%ËWŖRŠ0čutíØžŪö>>>货΁_ČoûîŌázÂBB˜ˇ`1žžzR’Yģa3}{vãČĪ™ėú~ŸGĩ !„đŔ)S\3fĖhĐÂ[vîĨ{Į´‹čRÚ !„W“ŠS§uę–^™YK!„đ" b!„‹$ˆ…B/’ B!ŧČŖ ÖhÔÔØö°‡˙UcˇŖņ`F.!„âÂŖ  "#ë5v{ƒÛÔØídde~zō!„BüĘŖûˆãb"9žs’}éG°7pĪXŖQD\LäE(„B\Í< b…BA|lԏ3B!Äœ‹ĩ„B/’ B!ŧH‚X!„đ" b!„‹$ˆ…B/ōčĒi‹ÕFv^!Õ5 ŋX!„¸Ú}õÄD„ĸ×i=nëQgįbĐk  đx ņĮ’›_DZë$o—!„—EŠš‚œü"’âc<nëŅĄéę;zÎãA„BˆĢYÉK•íĸÚĘ9b!„‹$ˆ…B/’ B!ŧH‚X!„đ" b/Zđų"~L?Ȳ•ĢČÎÎ!''‡į_x™Ķ6Éx•|üɏÛå<ÉoOg÷÷{ųré˛&¨ėōąTUąhé Ėåŧõūo—#„žŨžTŗŲĖÜųŸĸRŠp:Ô:jšcŌL&Sî{ąŖFЧWO�œN?õ }zõĀ××ČŽīvSeŠĸĖ\NLL~~FúõîÅīŧGB|3\.¨ŠŠæ–aCi•ܲ1ËöŠŦcĮŠŦ´pMëTŽi ĀÆM[čÔá÷ëÆæīīĮÄņc/ē}ûëĶØöíōNž$*ōŌkųČĶ/’ßėŒ÷&ß1ĩĒQ˙YžÁčëË­ÃnÂ\^Ņdc!„'õoįŽŨ$'%1p@?�~:œŲ\Éd"2<Œ={÷ģƒ8ũāAL§îGîŅ­ =ēu!ũā!ļīØÉäģî�āčŅL›'đāũ÷P\R›īLgÚŗOģĮܲm;åå <ˆėÜ\–~ĩœûīĘüŸcąX°Û´NMĄO¯ž|ųÕ2 Ѝs֑֮-;väš^Ļy|<-“ZpøHjŊNGÆĪGš}âx""Âųø“X­6,UUtęx]:uäŲi/rCûö”•–ĸP(đõõĨŦĖLxxCbĪžũlŨļŖŅˆA¯gėč[ĪØV6nĸWn�ĖūhíĶÚąíÛ8NšÅ5#5%8õãæ­wg`2™:d0{öí;kŽdd°xÉ2ĸŖ"Ѝ´˜¯Ņ÷Ŧí2~ė(fΞËã<tÆzĮĮ7ãŗ…‹ 4™°ÚlLš8ŽēÚ:Ū˙āC"ÂÃPūO0öčŪ•Í[ļĩ>CĢQķį{&õūŒį3¨_/"ÂByëũ9ü鮉,_ĩŽļ×´"5ų×{“|ņjĩ ĩZM^~-›SiąPTRĘ=ˇ%;7åĢ×ão4Raąpį„ŅTW×0īŗÅÜ6fÄ9kš÷ŲbēwžøfąlÚļ€Äøf,_ŗŖ¯/UUV&ŒN•ÕĘŌĢ đ÷ĮjĢfėˆ›ą;˚û)ū~ ę׋˜(yˇĸ~ÄiíŽeÎŧų”™Í4OH %%?#pęYÆqąąd;NB|;v~G§Ž°ÛkÜeĨĨA÷1ģ\.HįÉĮÅßߏãĮO““ÃŅŖY<úđ8Nžöí¯ŋžZG-û÷%""œŸĨYl ŨģvaÍēoØ˙c:Á!Á4‹ĨG÷ŽTTZøĪ_§K§ŽÔÕ:éŪĨ&“‰ûz„˙üķ%4 ĪŊđ2CbÁį‹xáųgĐj4˘5›Ÿ3ŗhŅ<Á]cfÖ1&Mœā~­ĶëčÜŠĩŽZwø(U”•™yę‰ĮČËË=į:,_šš‰ãĮÍĮŸ,ĀĮ§ū3˙ģŪoŊ;ƒ!ƒŌ"1‘M[ļ˛qĶf Ú]ۖ~}zągī> ‹�hĮĘUĢü7ģģƒ÷į|â~ĘĐû3ūÖaĖųtáĄ!Ü4°F_cG=ĢŊŌ‡¸f1´oז÷į|BDx(}ztá÷fSeĩR]]È!7ÂįK–säį,šÅxū,íCG~ĻEB<}{vĨÔ\ŽBÁ˛¯×Ōŋwâbųv×÷lŨņ;\šŧ‚ŋŪ7…Bq)›FņŌ¨AĘc<DQq ™™™ŧ?k6];wĸs§�ôîŨƒ•_¯&88Fƒ¯ÁPogfeņÆÛĶqš\¨Õjnŋm—‡SĄ?iâ8æÍ˙”*Ģ•~}zBAYi3gĪ@¯×Qeą�ân€FŖÁbą @AqI s?ūĨR‰ŊÆá^Öd2`ô5ĸ;ũÁYįÄjŗRUUÅ܏O…ŒÅREeeå5ÖÖÖĄR)ë]€   ||—œsJËĘ  4, \ŽõûËząnũF6lڂŊÆNTT$VĢ•ÔÔÔŗļÁč‹ÕZŨ ūëŖÕ¨™rĮøŗŪ÷ķ3ß,†ÃG=|Čûđķ@­VšôŠU*ŽZT*ë6mEĢŅp";—Äø¸ŗÚWW×0ûãĪ�:¸˙9ĮčŪĨk7láí™hbøÍƒ(.-cķļlŨņvģˆđ0�MÂB4j¯]ŋ””–ÄFGŒ^¯į۝ģÜALMÍ[ļŌŖ{W ëíŗy¯‡ĻĪEŖVã¨=ŽĨ%Ĩ�TWWŖÕh¸īŪ)TWWķü ¯pߟ§æ>ė›ëŌ }oúé'JJJ™|÷ä<É?Ļ×[ŗAo Ā?€;'Ũ†JĨ¤  ĀĶ˙ ĨJÕā0ūĨžĐАsރ) €ââbbbbČË;ItdÄ9ˇËųú åÆAˆkKyy ļlŨJII �'ķķŨmŦUV †Ļ]­¸¤”œÜ“´jŲ‚ģ÷Ōą}ÚEõŗxŲ×Ü1~ÁA|8˙s\ḁĸĶiĪ8<ŽQĢq8j(3› 4™((,Ļ_¯nču:ž^ˇ‘=û$$8ˆžŊēIeĨÅéŖÂBO5j§¤´äķ…‹QŠT(•>Ô:j=ęĖsq]ģtbųĘU <¨AA\ī˜ÉÉlÜ´…E‹—€BËjĩšĩë7°jÍ:|”JztīJlt411Q˘9G­ƒČČbŖŖëí?::’“ųĖūčc"ÂÃ0čôėØų]ŊíFĘģ3ŪGĢŅât:šëŽÛÎø<1!ŽŦcĮHj‘Øāu=ß: <ˆ9ķ>%<,[u5DFœsģœĪđ[†˛č‹% z*+-Œs+Ũ:wfúĖ8~ü:ŊÎb™ĮŽĶ<!áüy ÆîāŊŲŸņŪĐûņŞUŒ1” Ā�Ūz‰ ņŦßŧ•ļŠ­h•ÜĸÁũ§Ļ´äķ%Ë 2™ˆcķö4‹Ŋđß<­mkÖmÜʑŖY”WTh Ājĩ1kîLūØlՌēå&âøjåZôzK#†Ūˆ¯Áp1›Aņ§˜2eŠkƌ ZxozŅĄM\ŌCfÖ1žŲ¸‰{îŧŊQûũfã&jĩ čߡQûũśīŧĮČÈŽįĒiyčƒâfozFƒŋ÷ĻNJTÔŠkVä>b/iž¯¯/éyģ”Ûŗw1ŅQõ†°Bˆ†kē6EŊÆ5Â-@ŋõËíaMáē´v\—ÖŽÉúBˆ?"Ų#B!ŧH‚X!„đ" b!„‹$ˆ…B/ō(ˆuÍŠûT…BáVjŽĀhĐ_T[ŽšŽ#;ˇRseũ ‹?ŧŊéŪ.A!. ŖAOLäÅÍŗáQ zZ%=_¯B!.Žœ#B!ŧH‚X!„đ" b!„‹$ˆ…B/’ B!ŧH‚X!„đ" b!„‹$ˆ…B/’ B!ŧČŖ™ĩ,VŲy…Tר›Ē!„âwĮčĢ'&"ŊNëq[‚8;¯ƒ^Kp`€Į‰?†Üü"�ŌZ'yš!„¸|JÍää‘ãq[MWרŅët"„B\͂LūXĒlÕVÎ !„^$A,„Bx‘ąBáEÄB!„I !„^$A܄žŲ¸‰5kןõūž}ûųré2ü˜Î–mÛ˜ũŅ<۟õa“Õ˛lå*˛ŗs<n7}Æ,Žfãõ7ßÁl67AeW—‚ÂbŪŸķ‰ˇËBüŽxtq}Ėf3sįŠJĨÂétRë¨åŽI0™LLšīAƎAŸ^=p:]<ūÔ3ôéÕ__#ģžÛM•ĨŠ2s911Qøųé×ģoŧķ ņÍpš ĻϚ[† ĨUrËÆ,û˛ĒŠŠaÅ×ĢyōąGP*•î÷ų™žô&÷æÁƒ.ē­JŠdØĐ!|ļp1S'ßuÉĩŦŨ°…ģ÷hrŋ׎mkēt¸ū’ûBˆß›F âģv“œ”ÄĀũ�øépfs&“‰Čđ0öėŨīâôƒ1œš¤Gˇ.ôčօôƒ‡Øžc'“ī瀪G3IlžĀƒ÷ß @qI ož3iĪ>ísÛˇ;Øšk7ÍbcČ;™O÷ŽIkw-[ˇ˞ŊûPĢÔøûû1aÜļmßÁž}ûҍÕh´îœ4‘ų >Įbą`ˇ;hš‚Z­ÆjĢf`ŋ>|8÷cô:=cGdņ’¯HˆãįŖ™˜ËËq8¤$ˇ¤O¯ž|8w>uuĩøûĶîÚļ,^ōąąŅ”›+HlžpÆ6ÚŊg/íÚ´AŠT˛}ĮNŠ‹K ¤ŧĸœO>ûœņcFģ—}iOˤÄĮ7ãŗ…‹ 4™°ÚlLš8ŽZG-3fÍ&44­VCaA=pO<õœ;Ôߞ>ƒaC‡°vŨ7tëŌ‰mßîr×:ô曘=g:–ĘJ Ç!66†ußläĮôƒ„‡‡QZV@B|ųTY­ø —üoĨk§čŨŊķīe;ÁúMۘ|û86nŨAMM ŽoĮGŸ.âĄ{īv/Wf.įŊŲ“š’„ÕVŸŅ—!û˛s÷^~H?„JĨÂĪhäÖaƒÉÉ=Éō5ë1úúRUeeÂčáüé?¤B­VŖQĢIˆoFuu5ŊēuæĶEKŅëtÜ2d +V¯§Yl4ĩĩu|ŋī z=(Œ9”‹—QWW‡ŋŸ‘>=ē0÷Ķ/ >ãĮ•B4DŖqZģk™3o>ef3ÍHIIÆßĪ€BĄ .6–ŦcĮIˆcĮÎīčÔąv{Mƒû¯Ŧ´œ5ĄˆBĄĀ××Ā­#nÁbŠâ_˙÷_ŌÚ]‹Sīš ­VËSĪNÃjŗ˛c×w :„ÍČÎÎĄŽŽŽŌyōņGņ÷÷ãøņ˜øhū' ėׇĒĒ*ŦUV�22~Ļm›kČÍÍãĄîÃårņôŗĶčÜŠ*Ĩ qÍéÕŗ;˙÷ÆÛŒ7šØčhæ/øüŦuČĘ:FZģkĪx¯[—Î,_ąęŒ¨uÔ2°_""ÂyëŨ <‰‰lÚ˛•›6ƒBÁuiíč×§ģŋßKQQIŊÛđk]ģ~11ŅÜ<x……EĖûd?p?Ģ׎ã՗^ĀĮGÁ3˙xŅŨ6ŽY3Ž?AjĢ”†ūÉÎëÛ]ߓq4Ëũúρ}hߌCG~fŨÆ­ddfņ§;'ĸP(Îa8õ7ˇUW3ôÆū( ūųúģôéŪ…BÁíãGĄQĢyéĩˇąUWsčČĪ´Hˆ§oĪŽ”šËņQ(ØŊī÷īC|ŗrOæãk0°pÉrzuëL•͆ÕvęĻüŖĮNĐŋwwūųúģ<ųČ_P)•Ė_¸„Ÿ2ŽĸTúĐ,&ŠŽÛŗnãVZˇJĻg׎ü~ˆâ’ŌKŪ>Bˆ?ŽF â°°P{ä!ŠŠKČĖĖäũYŗéÚš;u� wīŦüz5ÁÁÁh4| †zƒ83+‹7ŪžŽËåB­VsûmÎZ&4$�ŖŅKĨÕũūüŸŖÕjŠĒ˛â¨q0qÜVŦZÃ_|IjĢVÄÆÆ0iâ8æÍ˙”*Ģ•~}z× ›­šœÜ\ÂCèŦ˛PT\‚FĢĄĸĸ‚°đPāT˜0›Ë  ´ŦŒĐāāĶī…€ËuF­UV+ö(CN¯[QqëÖodÃĻ-ØkėDEEbĩZIMMuoû†÷ėî3;;—üü|÷:Ųl6| ||§ˇm°ģŅh Ęj=ģÃ‹ĐšÃõgíôíŅ•'§ŊĘŨ“ÆĸP(ÎÛ>(ĐäūÜĪčK…ÅĀĸ%+Đj5Øl6ŽZēwéĀÚ [x{æGš~ķ F Âڍ[øęëĩ$ˇhÎĀž=ŠŽŽádA!ĄÁATUY))3ŖQĢŠĢsĸŅhPŪĶ ¤¤ôÔQ‚āĀ@�Ė头L<ã=!„h¨F âĩë7’Ō’ØčhBC‚Ņëõ|ģs—;ˆC‚ƒŠŠąŗyËVztīJAAaŊ}6OøõĐôųžšßØl6ãįo¤ÆngéōüķÅiÔ9ëØŗg/N\—”p×íqš\üëĩ×iÛļ5Z†ûîBuu5Īŋđ ×Ĩĩ#ąyĢ×Ŧ§SĮ0——ŗtŲrRSS aãæ­ĀŠsÜeefM§Îsū &E%%ÄFGSŸODxøĩôzŦ„Ų/Yʍƒ×,–ōō >>lŪ˛•ââb�rķōÜm”J%N§JÎąwöK­aĄa„3 _%%ĨčõzĒŦVwû‚ü_˙FUUļF9,}!Kŋ^øQÃXˇq+-PĢÕį\ޏ¤—Ë…BĄĀlŽ@¯Ķōõē <ûØCÔ9ė˙ņ.—‹‚Âbúõę†^§ãëuŲŗ˙GB‚ë0\.oŋ?‡´ļ×Ë7›ļŅ>­-å•|Ŋv)-ŅëuÔÔØŠ­­EĨRQPTLĮ¸4ōō Ü›�?JJO]ČVPTܤÛGqõiÔ NIiÉį ŖRŠP*}¨uÔ2zԈ3–éÚĨËWŽbČāA â†p8Ė_đ999š :ZMDx83gˆŅh¤uëTVŦ\…ÉdbõÚõøų "2"‚ŲsæąjÍ:|”Jztī @Û6­yíõˇ™8a,UUfĪ™ĮN\\,īĪú‡ÃÁā ûÍĄō›nČÜyŸ…ÍfÅõ›=âæÍČĖ:FëÔV­ãđ[†˛č‹% z*+-Œs+ŨētâŊ™ŗ9z4ƒAī^ļKį˜=‡°ĐPTjŠßÔđ‹n]:ķá܏ųāڔWTĐŗ{W""Âé×ģ¯Ŋū!!!ũ|Ũ{õĮOœā֑ˇxT÷ųlßš›ÃGŨ¯cĸ"N]”įtqCÚĩ¨”Jž\žšūŊģ3oÁ<đ§3/ķķ3˛dÅjŠKĘHkÛ??ÂBB˜ˇ`1žžzR’Yģa3­S’™5wĻ�lļjFŨrģ÷ūĀ7[ļcôõ%0ĐDHp ŠÉIŧûÁ<FB•ÕÆü…Kا�ˇ ‡ķĸ×ëĐët$ˇhÎūēkéØ>9ķ?''7N œ{{ !Äš(ĻL™âš1cFƒŪ›žAtDÁ^ŋ\đ4tČ`o—Ō ÕÕ5üûŋoœuÕôĨĘ;y’…_,Š÷čÁÅ:~ü+W¯åŪ)w_pšËņô%syķ>[Ė_ĻÜŅdc!„§öĻg4øģoęÔŠDEErņe§Ķi<°?_­XéíRŦļļŽ/—.cėč‘Ū.E!Ž:zhÚētęčí<vũui\]ZŖöŲd{Ã*•’‡¸¯IúžĻ�ŲB\5dX!„đ" b!„‹$ˆ…B/ō(ˆu ļęęĻĒE!„ø]*5W`üŸÛH=áŅÅZąŅadįRjŽŧ¨ÁÄĮŪô o— „—Ņ '&ōânīõ(ˆ=­’â.j !„BœMÎ !„^$A,„Bx‘ąBáEÄB!„I !„^$A,„Bx‘ąBáEÄB!„I !„^äŅĖZĢėŧBĒkėMUBņģcôÕŠ^§õ¸­GAœWˆA¯%80Āいwåæ‘Ö:ÉÛe!ÄUŠÔ\AN~Iņ1ˇõčĐtuŊNįņ B!ÄÕ,ČäĨĘvQmåąBáEÄB!„I !„^$A,„Bx‘ąBáEŋ› ^ļrŲŲ9ĖūhG22šlœ'žzŽŅûĖ;y’7Ūž~YÆjlßlÚ§ {ÜŽ¸¤„ŋ?÷â%?ųž‡Ũ˙ītēXļr5īĖø€|Ä˙Ŋ5÷gĪĨĘjŊ`¯ŋ3ƒôC?y<öÅŽû˙ĘÎÉeÚ+˙9īį˙ģ~ŋXąj-K–­ŧ¤q…ŋŨG\Ÿå+Wąmû‚ƒƒÜīĩŋū:zõčvÉ}ß<xĐ%÷!~ßŪž1‹ž];sķāî÷ōNæķúÛīņÔcõbež).)aá⯸wōŪ.EqhÔ čŨŗ;ú÷Ŋā2ĪN{‘Úˇ§Ŧ´…B¯¯/eefÂÃÃ2x[ˇ˞ŊûPĢÔøûû1aÜf4n]:ŗŋmÛw°gß~4j5­†;'MdĪžũlŨļŖŅˆA¯gėč[™3w>jŊNGÆĪGš}âx""™ųÁ||| 4át9ÎŲūÚķŠĢĢ%ĀߟQ#‡ģĮ?vė‹—~E€ŋ?åÜ;õnöZŪ˙āC"ÂÃPĒ~ŨĖįëûzķMĖž3Nû˙ėŨwtUÆņīĻgĶ{o„PéÅ@čŌAz3´�R¤X°€" ž‚‚Ô€Ō;R‘Ū‹„ŪR’Ū{6Ų}˙ŦDH °ĸŋĪ9œCfæ–š“ėŗwvv†ÔÔ4:ĩo‹ŊŊ‹/CŠ4%%%•mßÂĀĀ€Í[ļanaNZjûciaū\ĮnÛÎŨœ 9ŗŗaáöíŠ]—“ÃĖ9 051!9%•>=ŪææŸˇIĪȤcÛÖü0o!JĨ’AīôaÅęõ89Ų°g߁"ˇQ(ÄÅĮķۈĄ�Œũč3>ũp,K–¯AOO{;[Ôęü1:vâåĘúâííŌæald„‰‰ )ŠŠTŽXk×CŠPŪīfĪGŖŅāäāĀ‘c'X0{†vÂ#îķ͞UØŲڐ–žÁˆĄ™0q ŗžũš¸øxŋ;†UK`hhȄ‰_Ōļu �fü0NíÛPÆÛ‹ÁīŽaP`_ęÔŦÁ‡ŸM&;+‹?ƒŖƒ§˙áԙŗôéŅ•ogÎÁÍÕƒĮ˙Ė­ZGhč-ŽŸ<­]våÚ VŦ^‡ˇ' IT(/7_âŋĸÔOM<rŒYsæk˙………?ļM^ޚFõëŌ¯O/Nž>Ã[­ZØŋ'OŸ@‚ A4ˆ+W¯“‘ųäS'NĻuĢ @ŗ&QĢÕŦ]˙3Aƒ2 _’SR¸ųįmôõõđôp§sĮöT¯ö:į/]æĪÛwČÎÉf``?š7 == Đōúz”-SĻ@dfgŅŖ[öÃÁŪŽĢ×npäØqĒŊ^•>ŊzPŪ¯,@‘m=ZīĄÃGqww#°_ztíÂú›‰ˆŒ4ôëŨ‹ĄC`aaÁĨ+Wđ+W–īôŖw¯č)ĪuÜÔj5ŋlÛÉgŪgp`?"Ŗĸá‘:wīŲ‡ˇ—'Ŗ†aĐ;}Xŧ|55ĢWãâĨË�¤ĻĨ ĀåĢרTĄĀˇéÜĄ×nÜ$#3“ˆû‘XYZ—@VvcFĨCģÖ¤ĻĨpėÄi7jĀKVĐąŨ[Œ:ĨŌ” åüđôp'â~$×Co’•™Å¸QÃiŅŦ ņ‰‰öqéĘĩtëԁCQũõ×øu÷ī”÷ķåöŨģ„œŋ@ƒēĩšxų*—¯\ãõ*¯iËÕĒQ /ŸoÎ_¸HVV6z oĩlÎî=ûØđ-›°gßAęÔzƒaƒy­R…ĮÆ:ĀŋUĢTĻ^ZÚek7l"hĐ; Đëį:–BˆWKŠĪˆũÖlF|#4”­Û`ėč‘�X[įŋؘ›™cōān]ę<ĩļĖĒĩë166&==Uļę‰möéŲģ~cãÆÍTĒX;{[ŌĶĶYžr5�iié¤ĻĻ`kc€‘‘iii$&&bog€…š9ĻĻĻddfYŪŪŪîąö ôŲĩ{ÆÆÆÜžsŋ˛eILL¤RĨJĘäĪ k롇õÆÆÅATT� …‚˛e|x­r%æĖF__Ÿ.ÚĐğ;wķŋīfá`oGn]ž8FO“žž™™=Ŋüđõô(x›ļ¨čhnßšGxÄ}mŋllŦÉČČäîŊ0\INM%:&cccLÜoõIÛ(•&ø7ĒĪĄÃĮˆKH eŗ&ÄÅĮãäč�€Ĩ…JĨ€• cîGFáëãũ`<“¨Sŗa÷ą´´ ..GĮüąvt°GųČø>܇m;vŗs÷īdgeãéáNÍ!{ķĪÛôčډģĮØČˆēujæŋjT{īįäĪ”7bįîßštå*Õ^¯‚ŖúŒ~˙ētlGl\<e}}ø}˙AĒWĢ €““cąÆ?.>AģßNNŽ ŅûØ !^mĨą)įįĮø1Å;Ֆ“Öí;˜6e2yę<Ξ A͓_”ââãĐŋ†˙͘I­š5°˛´"°__ ô‰ŽŽÁÆÆ†Kff˛˛ļ&.>€ää232Qš*‹,¯(dæšf]ūėŲŅÁžųÁ?ĸAĩĩņę|Ē…ĩõĐÃząˇĩŖEķύT*â㈋”bū2�� �IDAT§R… 4öįÚõPļíØEËæMyĢu ”ĻJļlŨÁŠSĐØŋQ௏0JĨ)iiéh44¸÷ˇ3.ÎÎ8:8ĐąŨ[¨T*bbã�¨PΏM[vĐøÍ$$&˛jŨĪÚzčIÛ´lքož›:OMĪŽ Ŋu›čŗæÄ¤d2Ō3´īÄŊđ˛s˛QĢՄߏäØÉĶöëÅ/Û~åŨ Üē}›ø„üYpLl™o7įâėD—Níđõņ&1)===ŒŒ ų}˙!45žîDĮĒ››K˙>=ĩAlnn†JĨââĨ+49”#ĮOrđČ1:ĩoƒąą1U*WdÁOKņoT�;[íĖ˙á—G) 4 Z;[ĸcbņöô "â>nŽ.%;€BˆWVŠņÁCG¸|õĒögŪîÔĄØå qvrbŅâ%˜››Sšr%vėÜõÄ2ˇīÜe÷žŊXX˜cgg‹ƒƒ=]:ĩg^đBŒŒQĢÕ x§oĄe}}ŧŅ×7`^đ"Ŧ­­°˛Ę EqËT}í5VŽ^‹­-Ž..ėŨwˆ!ßaņ˛ÜŊ{S4M‘m=Ēaũz,Yž’Ÿ–,'9%˙F đōōdåšu˜›™‘ŖĘ! ą?éééĖžˇÛ3Î>Ŋēwˆ Ĩ¯¯OëMųrÚ ėíląąą.đĻŖy@cfÍ æûŲ HLJĸUķ�Ü\]¨ųF5>›<•áAHMMeæœ`ēwéX î'mckcƒš™înŽčëëSŪĪ}}ĻNŸ‰­ö4mįöm˜5o!-›0'ø'Ü]]x§O~\˛‚V-011ĻbųōlŲž‹īfĪĮÆĘ K ‹ũčÛĢ;KWŦÁĖLIrr CôÃŨ͕ėėlíĮ6ÖV¨TšÚ3UŦPŽ —Ž`llLÕ×*ą|õzƍĀ[-›1æÃO2 �͚ø3íģ¸yë6JĨŠ6tß2‚ų?LĮÕřs.ąw˙!mũŨēt`ÎüExzzžžŽĢÆųšŽ§âÕĄ2dˆ&88¸X‡\ÅÍŲáwIŧÅyúŌ!įy­rEŒŒ÷ŅD>ũh6Ö/ūI[_N›ÁāĀž8?å4nJj*[wė"##P`nf†—§ ęÕ@•›ËšķŠõFu2ŗ˛÷ŅDæÍüß īč­?ŲąkīŊôÂÛBüs…\-öS‚puu^ŌŠiņjˆ‹gԗͰ´´¤zĩ*/<„ãâã™ˇp ¯UĒøÔ†üΌûôčZäzCŽŸ<ͯŋí%//~Ŋē•fw ĩeû¯œū#D;;Bˆ’’ Z­šĐĒyĀKkĪŪΎ‰ƗjŖ†)ÕúžĻCÛÖthÛúĨļ)„øwyeîŦ%„BüI !„:$A,„B萹BĄC% b##2ŗ˛^T_„BˆWRBR æJͧoXˆ]5íáæHXD IŠĪԘЭË/îņ‘Bņ_fŽ4ÅŨåŲîŗQĸ 6WšRŅĪë™B!Äãä3b!„B‡$ˆ…B’ B!tH‚X!„Đ! b!„B‡$ˆ…B’ B!tH‚X!„ĐĄŨĐ#-#“°û1deįŧ¨ū!„¯s3Sܝ051.qŲqØũ”ĻÆØŲX•¸Ą˙Šˆ¨XĒWöĶu7„BŧD I)„GÅâįí^â˛%:5•ƒŠ‰I‰B!ūÍl­-IKĪ|ϞōąBĄCÄB!„I !„:$A,„B萹BĄCį õˇpæėšB×͜ĖåĢ× ]—œœÂüEKžģũy rūâs×ķĸ¨T*Æ~ø)ŋlÛŠëŽ!ÄS•č{ÄÅņ۞Ŋ„œ;žYYŲŧŲ°>o6jPĸ:/[Á۝;aia^ė2ĮNœÄĐĀ€Z5ßĐ.ģ}į.7oāúPʗË˙~ī Ā~X[[—¨OĨiōÔéLœ0žØÛĮÅĮŗaĶV† āŌ•ĢthÛĒÄíZYYjëø'Ûwđ0F††4Ŧ_ˇDåfÎ &°o/ŌŌŌ°´´¤cģˇ Ŧ‹O`ūĸ%XZX’šBķ€ÆÔ­]ŗ{.„%WĒA|čČ1nūų'ãÆŒÆĀ@Ÿėœ6üŧ‰ĖĖLâظy+V––¤¤ĻŌŋwOŽ^ŋÁŠ3gđđpįî{4hP[kkΝŋˆJĨĸ_ŸŪ,]žcRSĶčÔž-îLœ<…Z5k’•‘ÉŊđpžÃŽßö`ffŽĢ›+n..�øx{1~Ė(ōōōøđãĪ?f�_~ũ ÂaogĮų 9á"ššš(ôôą07#,<œ^ŨģamcÍâĨ+k˙yėŲw€K—¯˛aĶÚˇmÍĖ9 051!9%•>=Ū&3+›ŋlãŗÆąeû¯dffIhč-ŽŸ<MjU066fčČq4Ŧ_—ŦėlbbbyĖm;7oŨfųęuØX[“˜”ÄGãG“‘‘ÁŒæķÁ{#˜ôÕ7ÔŦQ¤¤d”J%ƒŪéÃĄ#Į¸}įũûôĐ֓‘‘É÷sæŖ@AFf&ãF āđą8t”°ˆŪo.ÎNĖ_´„ôô RRSiüfü1rÜĘûųRšbÔj5ĮOÁØČcc#F ÂņSgø}ß,,,07SŌãíÎlÚ˛ <==đtw+t “éÖĨwjíēthĮŠĶgQŠT˜š˜rįî=vėÚC›VÍĩå.^ž‚§‡;ũ{wįæ­ÛŦÛ¸Y‚XĄsĨÄ!įÎĶļuK ô062ĸO¯üõMŋlĨCÛ6x{{rôø öėۇ—§'ĻĻĻtjߎ°°p6oŨƨw‡áäč@ĪîŨ8rôîîn´{Ģ11ąŦXŊ–qī$/WMŨÚĩp°ˇã‡š HNIĻj•*xy¸kCøIü5äĐáŖtîØžã'NŅĒEsö<H/ü5äĖ!ė=p�{‡BÛūØøËvēvîĀÖģđöō¤{—ŽDFE3oábžœ8ŗ^žlØŧ•‹—Žđų'rūâ%LML¨W§§˙ĄJåŠ�¨rsiÚäMmž 9¯m'#3“Á}ņpwcŪÂŜŋx?_�ôôõIIMŖoĪn(  Å wúäŸŊhXŋ@wíŲK•Ę•hßĻ—¯^#>!�_oÚ´jÎ/Ûvrōô´iŨ_oZ4kBrJ*Ÿ|>…�˙F¨T*:ĩoƒ›Ģ ŸMžJī]ŠPŽ,ˇīÜE­Vķã’Ė›õ-ÆFF|;sá÷#ŠõF |}ŧpuvfÚôYúSåĩJ>6ŽĘûáęâĐũIII!!1ą@4¨[‡m;žā̈î…E0fä°į:–BQJ5ˆ jĻĐuąqņ8:ŲāhoĪ…‹—ņōôÄÆÆ�###rrT+KXXQQQÚú˛ĩÉ?ĩlld„*į¯{_gff2wÁ"�ēv鄗§Įc}ŠSģ&“ŋú†Ö-›“˜ˆˇˇ'ûüūY[[’”” PdûĨ!*:šÛwîqŋ@ũ;´ĨWā>ų`,zzÛü#ä<͚økvrt�ĀÖֆ„! `hhĀÆ_ļabb›ˇ¨\ąBzėmm´í=iŋbbã¨ūz�mŋíŨĢ‹3�&&&¤¤¤ĸ@AtL,sƒB__ŸėGîGîäč°ÁlØ´•Ĩ+VS­jHMKc΂HII%99Y[ÎĀ@ŸÆ~ŦO{ö(˛ŋ7{ū"ĸcbiōfC223Š_ˇowjGtL,ĶgÎåÛ¯?/v]Bņ"”j×zŖ;wũÆđ A’“Ŗbéō•tīÚ{{"Ŗĸņõņ!2*GGûĸ+R€FŖÆŅÁ{[;Z4oŠJĨ">>Ąč" Pk4˜ššjOAÅØØ˜ōåʲjízęÔŽĨ]CĨŠˆ‹ĮÆÆ{ûbˇ_\ …̓7+.ÎÎ8:8ĐąŨ[¨T*bbã�X˛b5Ŗ‡ņķæ­T­Rš@™?oßÁw`m}÷#Ŗpws%:&īš„Ūú€E‹—ķÁØQ8;92mú,Ôõ3õ×ŲŲ‰°đûÔŠõį.\*2´Ī_ŧDLlãF',<ĸĀÅd‹DĮÄ2úŨ!h4>ž4…F ębcmÍčáAčs?2 {;;އŪD­Ņ››Į7ßœW}­2V–ä¨ōƒ>öÁ˜=lHķˇũ9l°ö˙+×lĀņÁss3RSSŸiL„ĸ4•j×Ģ[›Ô´4žũnÆÆÆäææŌØŋVV–té܁›~Á‚Œô úõíÅĩk× ­ĮĮۛE?-eP`ÖŦ˙™Ÿ–,'9%˙F pvv*´Œ—‡'ÛvūŠŊŊž>>Oík˙7ųrę7ôėŪUģ,"â>+V­á~dũûbiaÁ’å+‹Õ~qéééĄ45eŅ’ôíŲYķ‚ų~ö“’hÕ<€û‘Q¨Õjšø7ÄĀĀ€—Ŧ kįœģp‰Í[wbggĢ C}}}Ž?IdT4ŠiéÔ¨ū:ĮO fęĖ[¸G{<=ÜŲžķ7ʕ-ķÄž:zœģwÃčÛĢ›vY‹Ļ™9'˜ŠĶg’‘‘ÉؑÃ8ZHY/OwÂ#î3kîBÜ\]0S*9xøXmBoūÉæ­;°˛˛ÄÁÁg'Gú÷îÎÔéßcblB^^cFÅ×Į‡ĩ?oÆÉҁO>ûX[‰IÉėÜũ;ËVŽ}žųË˕õeúĖšôëÕŊĐũkĶĒ9ŗüȕk×IIIeā;}ž8Bņ2(† ĸ .ÖÆ!—CqsvxÁ]z9nßšËūƒ‡Đŋ/ĨvÃúu)į÷|ONz™O_üîÍũžÔęKNIå÷}čŌą]ŠÕ)„˙!—C‹ũú„ĢĢ+đžžô*øí÷}\¸x‰ÁũŸžņˆžžmZĩĐu7„â?å?Ä-šĐĸY@egƯ’Ōœ ˜›™•j}B!žNîŦ%„B萹BĄCÄB!„I !„:Tĸ 612"3+ëEõE!„x%%$Ĩ`Ž4}Ϟ%ējÚÃ͑°ˆ’äŽDOr9T×]Bņ™+Mqwyļûl”(ˆÍ•ĻTôķzφ„Bņ8ųŒX!„Đ! b!„B‡$ˆ…B’ B!tH‚X!„Đ! b!„B‡$ˆ…B’ B!t¨D7ôHËČ$ė~ YŲ9/Ē?B!Ä+ĮÜĖwgLMŒK\ļDAv?ĨŠ1v6V%nčŋ&"*€ę•ũtÜ!„/ZBR áQąøyģ—¸l‰NMgeį`jbRâF„Bˆ3[kKŌŌ3ŸŠŦ|F,„B萹BĄCÄB!„I !„:$A,„BčĐ+ÄÛvî",,œÅËVp#4ô™ę(ŦlJJ*+W¯-.ęĖo{đÁ'Ÿķįí;Ė_´äĨˇ˙GČyöė;PĒuŽÛø ˇīÜ-Õ:…⟨Dß#~ší;wqôØ ėėlĩËjžQƒÆo6|îēÛŊÕęšë(ŒĨĨ}zõ(Q™ÅËVđvįNXZ˜?wûŋlÛɉSg000 33‹–ÍšĐĸY“ÕrūƒôŖŒ7Ã>wŸJęę¯š..>ž ›ļģ_“§Ngâ„ņtīŌą´ē'„˙hĨÄ�MüŅĸyĶ'n3qōjÕŦIbB …333“prr¤í[­8rė8gCÎah`ˆĨĨŊ{vgņ˛4Ŧ_ˇĐúŽ;ÁŲsį124ÄČØˆĀ~}øč“ILûę �æĖĻCûļ�<|”ŖĮOɀūũ042`Ņâå|8î=6oŨFLt,yę<ĒWĢJŊ:u8~ō$ĮOœF­VSĩĘkøúxsîüET*ƒĸ§§xæąúmīŽ^ŋÁ”IŸ`` OvN‹—­"##“ظ8–­Z‡ĩIÉ)Œ¤¯žĄfj$%%ŖT*iX¯×oÜdí†ÍôčڑÅË×đū{ī2eÚ lmméŲ­3ßĪžOŖõˆ‹‹GĄP`anN\|nŽ.tëŌA۟„ÄÄ原<Mdd4šyšÔ­]“&o6äęõŦXŊw7˛ŗŗņōôĀĘʒč˜X:´i͌æafĻ$))™^Ũē°c÷BCoqüäiV¯ßDy?_*WŦ€‡ģËW¯ÃÆÚšÄ¤$>?šŖĮOréōU6lÚBDdÍü)ããÍŦ9Á˜™)IHH¤kį¸8;=6ƒŪéķĖĮA!tŠÔƒøā‘c\Ŋ~Cûsįíđđ(x§‘ŧ\5ę×ÅÚښīcú´¯022bŌ—_Ķö­V(P4h�ÆÆÆ|2q2™OlķÄŠĶthߖ˛e| G­Vš­ˇ7Ísâäiö8@ĢÍ�į֭ی3 ĩZÍÄÉ_QŖzuļlŨÉW“'ĸP(Øwāžžeprt g÷nĪÂųũ>CˇÎ00ĐĀØČH;s\žzŊēuĄŦ¯{bˎ_éÔž-)ŠiôíŲ …BÁ€ĄŖôNʕõĨk—XYZ� ¯o@\|ͧ~‰žž‚ÜÜ<šøckcC÷~ƒXēpÆFFŒ7Ą@?Zî^X׎‡2eŌĮ¨ÕjFŒũˆ†õë˛fũ&‚öĮË̓īg/@Ąøk î…G�F B•Ģ"%%•�˙F˜š˜P¯N-–­ZG§ömpsuáÂĨ+ 닇ģķ.æüÅËø7bã/ÛéÚš3į°û÷ũ”ķ+Kįmˆ‹gęô™LúøƒBĮA!^EĨÄū ë?6#žĘÖíŋ0vôH�Ŧ­­073ĮäÁŨēÔyčĒĩë166&==Uļę‰möéŲģ~cãÆÍTĒXņąā”Ģ‹3�ööļ„œ;¯]ObB"‹/ĀÔԄø¸xŒMŒŅ×ĪĘfŸ˛÷%ŖP(Pk4…Ž‹ŠŽÅÅÅ �''N˙q.ŋßļ6Úđ{4˙ÎÁŪžĀ[�,-,´wGËËË+˛\dt qqņĖøa�JĨ)ŠŠŠÄÅÅãčč�€ˇ—G˛ʕĨFĩיōÍ čßģûcõ;9:`hhĀÆ_ļabb›ˇ¨\ąBĄûCÕ*•ô͎¸¸„ƒBüĶ•zϜŸãĮīžËŲ99lŲžƒiS&“§ÎãėŲÔVÅÅĮ3 4 ˙›1“Z5k ¯¯Z­FOOøøíļą1ąPąąqņØØXk—;8ØãčäČāī�‹‹3™™™¨T*ôõõŲ˛}Úˇh4EĪē‹ĢaŊ:üŧy+ƏÆĐАœ?Ė[ČĀwúāėäHxÄ}Ęû•%<â>ŽBš¸ž5›–sqvÂÕՅqŖ†pį^ļ66XY[‘ˆ›Ģ wī…áíåŠ-KĩǝŅĻUs.^žĘڟ7͞Y�šGŪl<ŦŅâå|0vÎNŽL›> ĩFBĄ(°- €Č¨híŸmĮ„âĒôOM:ÂåĢWĩ?{xxđv§O(Q‘Ą!ÎNN,Zŧsss*WŽÄŽģžXæöģėŪŗ sėėlqp°§~ŊÚ,ZŧG Ph4hÔÂ##Yąj ÷#Ŗėß÷¯~ēšáîîJđĸŨrU¸¸8ãŅąowęČėyÁ¨ÕjĒŊ^Č?ŊŊč§Ĩŧ;t0ĻĻĻ%Ąŋ4ņoHJj*Oú crssiŨ˛6ÖVôī̓e+×`eiIZZ:#† ĸˆÉķ áí遡—ß~?‡Uînx÷ęN÷.™=žîdfe=–øķ-ÁŌ‚ėœlÚ´jĢ‹3į.\bīūCļĢYŖ:ķ.ÆŅÁOwļīüúujĄ45eŅ’ÚíZ6 `Öŧ`fÍ]HrJ CûŊ”ũBˆ—E1dČMpppą6šŠ›ŗÃ îŌËĪ’e+yėčR­÷ßúôĨ?īÜEijŠŗ“#KWŽĄŒ7o6¨§ën !„Î…\-ök~PPŽŽŽĀK:5ũOļtų**”+§ënŧ2T*3.ÁŅÁUn.Ŋģŋ­ë. !Ä+í?ÄãĮŒŌu^)åũĘōíןëēBņ¯ņĘÜYK!„ø7’ B!tH‚X!„Đ! b!„B‡JÄ&FFųßB!„VBR æĘgģ¯D‰Žšöps$,"†„¤Ôgjėŋ(äōŗ=˛Q!ÄĢÃ\iŠģËŗŨgŖDAlŽ4ĨĸŸ×35$„BˆĮÉgÄB!„I !„:$A,„B萹BĄCÄB!„I !„:$A,„B萹BĄCÄB!„•čÎZé™Y܋ˆ&+;įEõG!„xåXš+ņtsÂĐ Dą ”0ˆīEDŖ45ÆÎÆĒÄ ũ›DDÅRŊ˛ŸŽģ!„â".!™{Ņøzš•¸ląOMk4˛˛s051)q#B!Äŋ™Ŋ­Šé™ĪTļØAŦP(žŠ!„âŋ@ŖŅ<S9šXK!„Đ! b!„B‡$ˆ…B’ ~‰’SR?aâKikÚôYÜē}įŠÛũrž=û<u쏸xF˙˜c'N=įJ ô֟ŧ÷Á'\ŧ|õĨļ+„/KÉŋđ$žęˇŊ8t䊊iÄ'$âí偕•%Cô×u×ķFõ׋ĩŨĐ[Ô¯W›úukđËļœ8u23ŗhŲŦ -š5AŖŅ°yëNNŸ Á@_•JEÃúuiÛē�{ö§JåJh4˛srhÚøMZ4mĖĩëĄ|1õ[|}ŧĩmēšē0lp }ztåü…KTŠ\ą´w_!tN‚øhŅ´1-š6&äüEö<ˏQÁüqVv6sƒ"6.O7ôëÍņSgø}ß,,,07S2蝾ęÛčûA­VSëęthۚ%+֐˜HNNU_ĢL›VÍŲļs7gĪ]ĀÕř¸øx�˛sr˜9gĻ&&$§¤Ō§ĮÛøx{iëŪwđ0Ņ1ąÔ~Ŗ:+ÖlĀŌŌ‚Ô”TŪ9 +K ívjĩ}} ˙ÆÕë7˜2é ôÉÎÉaņ˛UdddrčČ1n߹˔‰ĐÄĶfĖÂÜ܌ƍ`blŸ~¨íÛÔogbĻTbgkCÅōå˜8aüc㊯¯OžZ]ēI!ū!$ˆ_˛ää† DĄP8túõæĮ%+˜7ë[ŒŒøvæŽŨ¸I…reü�\Ŋn# ~˜ŽBĄ`ĮŽ=Üžs—{÷˜ôÉh4†ŖlŪēƒįÍBOOÁģcōÃn÷ž}x{yŌŊKG"Ŗĸ™ˇp1_NœđXŋΞģ@åJxģc;bbãĐûÛ×Õ“’1S*8qę Ũ:wĀĀ ?˜Œ68€c'OØˇ'úBÛĐАÎÚ˛eû.7jP Nc##ÚˇiÅŽßöŌšC[ŽßeōÔéÚõÔĨɛ 155%99š4†_!ūq$ˆ_2'Gôôō?š××Ķ#=#ƒÔ´4æ,ø€””ÔĄ“‘‘‰‰‰‰6ØÚŊՒã'OãââäŋÛÖֆˆˆû˜››Ą§— ÎN�DEGsûÎ=Â#îkˇ/L›Ö-ذq ūNŽ ükV~íz([wėâû˙MŅÖĄ~Â÷åōō Î^5´ûüøļy(Íōž|9ŋBgÄåũ|YøĶRö<L€Ŗ"ÛBˆW‘\ŦĨcfJ%6Ö֌ĸQÃ68ę¯WũkŊ™’ŒŒ T*jĩš•k7āėäHxD$�jĩ†¸øÜŨŨHKKGũānăõ.ÎÎÔŠõãF gÔ°Á ,üsęđđûŧŨš=SŋøG{{9Ž]WĄŧm[ˇāČŅ�4ŦW‡Ÿ7oEĨR“ŖbúĖš$&%Ķ ^m~Ūŧ•ÜÜ<�TššlŪē>[~TvN›ˇî aŊ:OŖëĄˇđōô”Bü+Ɍø īîLū=&Æ&äåå1fäPí:…BÁ;}zōå´¨ÕjęÔzo/Ęúú0}æ\rrrčÚš=fJSÚŊՒĪ&OÅÉÉKK ĐhhИYķ‚ų~ö“’hÕ<�7W—Įúš–Æ”ožÃŪΖôô íŠæ‡ėlmˆŽ‰ ‰CRRSųxŌW˜˜“››Kë–ͰąļĸEĶ&dddōé_addDNNÔŖQƒē�deg1yęt4j5™YY4oژš5Ēqíz(׎‡2iĘ7Ú6õôô˜ôņûdffbmũßžŋšâßK1dČMpppą6šŠ›ŗÃ îŌ?ßņĄĮNœâÎŨ0zuīōŌÛ>~ę ˇnŨĻOĪŽ/Ŋm!„(ŽËĄÅΆ   \]]95-ŠŠœŸ/ĮOáČņ“/ĩŨЛ˛vũ&^¯úÚKmW!^95-ŠÅŪΎŲ3ĻžôvũʖaÖô¯_zģBņ˛ČŒX!„Đ! b!„B‡$ˆ…B’ B!tH‚X!„Đ! b!„B‡$ˆ…B’ B!tH‚X!„Đ! b!„B‡$ˆ…B’ B!tH‚X!„Đ! b!„B‡$ˆ…B’ B!tH‚X!„Đ! b!„B‡$ˆ…B’ ~’SR?abŠÕwíz(K–¯`ũÆ-ė=pˆ “Ļ<wŊûf͆M9v‚eĢÖ=w}/CnnŸ|ūPp\žÅo{đÁ'Ÿŗīāaöė;@\||ąÆõyoqÛ)ŠuáöģĨ^īĢ菐ķėŲw Të|‘ãû,ŋ/b˙ifÎ æōÕkēîÆ [á^´�� �IDATg ëü—ĖœL`ß^XYZ0yęt&N_ŦrgĪ] zĩĒ�\ēr• ũø}˙ĄŲÕytŋūnĪžŦZˇ‘ĨÁŗčÖw ˗ĀÍՙ!úķûūƒė?xCCCüʖĄw÷ˇ‰‰cöüE••Eûļ­ŠWģ&W¯ß |š˛@Áqy!į/0x@?ü|Ë�ų/†ĪcßÁÃŌ°~Ũb—Ųđ;vũ†‘‘FFFŒ5 ķgjŋ{—ŽĨÚˇ'ÉÉQ~˙>eŧŊJĨžŌöFõ׋\W’ąˆ‹gÃĻ­ øÄņՅ'íŖŽ„Ūüß2Ūčé=>Į[˛b $'§0rØ`,,ĖKũ÷ōU%Aü‚deg37ø'bãâņôpŖ~Úœ:}•JÅëU*séōU6lڂ­­ Ã×Į›{á4đ§níš 9Ž™ß~‰‰1WŽ]§kįödgg`llŦmįČą8| ss3(5|0wîŪcņōÕbffÆ{īeßÁC?yCCŦ­­:čũŊ}į.?Ė[ČŊđ:ˇoCũēĩ ­{߁Ã?uc##ŒhŅ´‰vŋƍz—áŖĮkû}?2Š?B.`ų X222ņöôā‹O?ÔļĢVkXŗ~ÁŗŋÃĀ@ŸÉS§qFCĐĀū¸ģšrūÂ%vũžzĩkröÜí ĐÃq:r ë×%+;›˜˜XŪ3‚Ŗ'NqāĐQ ũnsüˆ™™’„„Dēvž×oÜdí†Íŧ^Ĩ2é4đ×ökåÚ DFF“›—KŨÚ5iōfÃ'ßn;˛iËv,,,đôôāßũ@Ŗõˆ‹‹GĄP`anN\|nŽ.4ί+77“§˙`ę—140`ũÆ-8|”Ûwī‘››‹ĩ5Ŋēwaæœ˜š˜œ’JŸoããíÅwŗįŖŅhprpāČą,˜=ƒ™sƒiāOTTLc4 _ī};täØcûöÃüEÚ6ûö,ō÷:<â>ŋūļ—č˜XÚļnÁØ?åŖņŖqtpāô!œ:s–ļ­[°lÕ:lŦ­HJNaDĐ@rT*fĪ_ĔI0øŨ1,šû=#ĮM ŧŸ/•+V ‰ÃĮÚ;pø¨ö8~4n4kŪôXߡíÜÍؐķ8;;A`ßžÜ ':&–ÚoTgŚ XZZš’ĘāĀ~ÆâÛīįhÛ÷pwcųęuØX[“˜”ÄGãGŗlÕ:BCoqüäiNž9Kķ�ĖÍĖÛ?€I_}CÍÕHJJFŠT2č>…ŽáĖ9 ĐĶ×ĮĘ‚?īÜ%h`ŒŒ ˆŽ‰-tœŽ^ŋÁŠÕëņpw#;;/OŦŦ,‰Ž‰ĨUŗ€bˇ={ū"ŒŒŒPššråÚuF „›Ģ ķ-!==ƒ”ÔTŋŲ€�˙FŒûa‘ŋÃŨētāøŠ3üžī�˜›)ôN_îGFązŨĪ”-[†–ͰˇŗÕļ}ãæ-Ļ~ņ)sƒ"!1…BQāXÜģöØëÍCqņņ|;s.īŋ7‚ŦŦ,~Zļ ;[ŌŌ31t œ=ĪĄŖĮđņöâÖ­Û4 đ§aŊ:Eū˙ĶHŋ ÉÉ) ˆBĄ pč(ôëĢ‹CôĮÜLÉĻ-;čÚšûÆÂœū}zššÆ„I_RˇvMĖž䇗†††œū#„*•+hgéĘĩĖ˙a:†˜ŗ€ķŲēcû÷ÆÛ˓ũ‡ŽššŠŒ‰ąą1CG'=#Ŗ@=&ÆÆŒ>„[ˇī°jíĪÔ¯[ģĐē>Jī]ŠPŽ,ˇīÜÅËĶCģ_zz mŋssķX¸x9ībâ—Ķ�HKO#!1‰™sœ’JĶÆhXŋ. …‚œœ LŅh4Üš{†õ뒓ŖâŗÉS‰‰ãĶĮpíú zwģ¨rsiÚäM\œ˜ŋh gBÎc ¯ŠŠ ŽÅ/ÛĨœ_Y:whCl\<S§Īäģi_RŽŦ/]ģtāîŊ°ãqįî=Ž]eʤQĢՌû ë×ÅĐā¯?—ÂŽo­7jāëㅧģššy4đĮÖÆ†îũątግŒ9n‚6ˆ ôųhühíqžrí:}{v%,<?ß2´nŅ”­;váíåI÷.‰ŒŠfŪÂÅôéŲ•ŦĖ,>ū` 1ąqlŲņkcų÷c¤TšjûĻÎË+tß ôõĩm._ŊŽû÷Ŗ´õYYY2d@?Ļ|ķNŽŧÕĒ9žîn�ÄÄ4e÷žũôíՍũĐšC[–¯^G¯n](ëëÃŪ‡Ø˛ãWZˇhVč߉JĨĸSû6¸šēēūŅãXØqŠ_ˇ6ŋlÛÉĸš3üî{ Ph˟=wĘ•*đvĮvÄÄÆĄ45-pœm˙ÂĨ+ 닇ģķ.æüÅËø7ÂÔĄzujqōĖY€B÷¯Sû襤ĻҎg7 †Ž*2 Q((īįKËf=~ŠíŋîĻs‡ļ…oûšõ›Ø/OžŸŊ�Å#û¨§¯_ėļõõõ)ããE퀯lŲū+§ĪžÃŅŅ_oZ4kBrJ*Ÿ|>…�˙FOüîÖĨ?.YÁŧYßbldġ3įpíÆMüÕĮŋQ}Ž]eåšõ PđŪģAää¨ČÉÎaúĖš4~ŗŽŽ˜š˜8“ŋūöą×€”Ô4V¯ÛČØ‘ðˇŗeĘ7ßŅ­K*–/Įîß÷ņëîßqqvBŠTŌ§GWnßšËĘĩpuqfũĪŋØ˙6­›SĨrĨ'Žĩ.Hŋ NŽÚĶ3ú…œĻy”ŗŖ#�椤¤XwîÂEĒžVČ˙L¨Y“ŋfmié阘kÂŲɉ¨čbbãptt�(0“›˙ãRLMLHKK'';§@;..Î�˜š˜““SdŨÃ˛aĶV–ŽXMĩĒUđ)âÔäĘĩëiĶĒ9ÖVVÚeVVVL?ß2>dee3úũŠōZeF ÄĖ9 0ˇ0G__C##�ŒŒ ųrânŨžÃķ1qÂx”JS ô9uæ¯qy8Ū�ļļ6$$$beiĄ×ččĒVÉßÖÁŪŽ¸¸„'Ččâââ™ņÃ<�”JSRSSąĩą)ĐŪĶŽīÃí--,051 //īąíbãâ™57˜ ũņpwök÷'*:šÛwîq�…BA\\ŽŽö�8:ØŖ45-PߓŽQQûöčöëÕũą>ĒÕjŦŦ,IJN!667W ūę3úũOčŌąąqņ”õõ!*:'�\œœ8ũĮšÂZ;–ŽO\˙đ8Ö÷ččĖĖ”čéå“§‡{˛mZˇ`ÃÆ-|üųW89:08°o‘í°ņ—m˜˜˜pãæ-*WŦ€É#gŸ*j˙ėmm´ųhP>iŸlm­‰OH|âļ�qqņÚŋio/Į֗¤m{;; ˙ĖZJJū›ôč˜Xæ˙„žž>Ųŧ6õ;œž‘AjZsü@JJ*ÉÉÉ@ū›đ˜¸8RĶŌņtw#//)ßĖ KĮļüúÛ^ĒŧV‰ī˜ĪČGfŧEŊŪ�,YžoOÛvėfįîßÉÎĘÖķG÷+;;‡2Ū^Ú7ē˙tÄ/“BFŖFĄP Ņh´‹#"ķg ‰‰XYY(röÜÚžÕ€?oßÁw`âōƒÄÜˌĖĖ,T*†††„GܧYy\œ ŋO9?_vîūĒU*ŗzũF͝Iž:c'NĄ~¤ũÂUwtL,Ŗß‚FŖáãIShÔ ŽvŋĘÍÍãÂĨ+Ä'$rččqĸcbYĩîgšøŸ€oŒ000@ŖQ“ÍcGĄ§§ĮŸLĸœo6mŲŽ­­ 5ĀÎֆôôtBÎ_ ZÕ* ĀũČ(ÜŨ\‰Ž‰ÅģĻ*•J;3rvrÔYdT´6Ċââė„ĢĢ ãF āÎŊ°!\…‚§Žkarssĸ á‡u)ôÅG:ļ{ •JELlIÉÉÚ8223 ÔWØ1zØ7÷'ėÛÃ6—Ž\CÄũHm}ÖVVŧ4÷Ū "-=ß÷bû¯ŋā߈F ęRĨrEü´˙FõŋÆģŧ_YÂ#îãęℑ‘Qū1!öŸņČˆ§ä†vƒÂŽ‹›Ģ iiéh44¸^ hxø}ŪîÜ3Ĩ’Õë6rčČņĮŽĶÃö-^ÎcGáėäČ´éŗPōˇZÔū•TDd¯W}č˜Xm€�EŽ“•ĩ ‰¸šēp÷^Ū^ž%nŗ(į/^"&6ŽqŖ‡Á™ŗO~ã`ĻTbcmÍčáAčs?2 {;;ļl˙• —ŽPˇö|0v$ÆFFÄÅĮŖ§§GũēĩąĩąaâäŠTĒX3ĨR{,ŠzŊšqķÇ āô!l˙õ7Úļn‹ŗ]:ĩÃ×Į›Ä¤dôôô¸xéōc}ŧuûk7l*°ŦŨ[­¨úšĖˆŸŲļģ¨Vå5öėÛOÃúu)įį§]ˇx؊Į–ĮGŸLbÚW_”vW‹TŽŦ/ĶgÎåã÷Į 45eŅ’ø–ņFĨRüĶ2îÜŊGߞ]>ú}žûf wî…áíéAl\<vvļŊÛĐ¯7Ķfü€™™33%ÕĒž†ĩ?-[…ĄĄ!æff´l€›Ģ 3~˜‹Ĩ…ÕĢUeũÆ_´=Ĩ°ē×oÜÂæ­;°˛˛ÄÁÁg'Įû5~ÂDžûf ßMûR[΍ņčŨũmrrT8ŧ†ßö ;;›æūX[Y‘˜Äg“ŋÆĐАM›`ccM“72sn0‡'--A}9tä];w�Ў äŸn;zü$‘QҤĻĨSŖúëœ<uFÛ~Ëf˚ĖŦš INIaH`ŋ'§Ū^ųŸæ¨rđpwÃģWwöė;ĀŨ{atí\øE;ž>>Ŧũyŗö{q=wĩZ­=Ũû¨æ™5/˜īg/ 1)‰Vͨ[ģ[ļīâģŲķąą˛ÂŌĸāErĄ7˙|ė=ėۈĄ ŨˇGŊͧčΈÍÍĖčØļ5Ûļ&.>˙ á[-›1æÃO2 \û÷éÁ˛•k°˛´$--ÃaiaĨĨ /ĮÜÜ ‹Į/ė{šÂŽKŋ^ŨiŨĸ)_N›Ŋ-66ÖūFRĶԘōÍwØÛŲ’žžÁ°ÁÜŊUčqĒYŖ:ķ.ÆŅÁOwļīü÷FqîÂ%ö>rqdaûWŌ÷_wī…1oábÂÂ#ũnvšĩUĄãÔŊKGfĪ_„§‡;™YYÅx÷R|^žî„GÜgÖ܅¸šē`ĻTrđ𱧖ëßģ;S§‰ą yyyŒ9”õęĐĄmëÛŲÛŲáäčĀW˙û…B77WnŪú“u)đ7SØë́ÃG144 °o/>ų| åũĘŌˇWw–ŽXƒ™™’ääíīŨßųúxķÉcKcˆ^8Ő!C4ÁÁÁÅÚ8är(nÎEŋČlßš‹ŖĮN`÷Čô5ߨAã7ŋãYē/;ˆ#ĸbŠ^šdmeßÁÃDĮÄŌŗkįRŠīŋčá-˙ĒÜ\ΝŋH­7Ē“™•Ÿ&2oæ˙t֟Đ[˛c×Ū{$P^Ļ?BÎķZåŠ1îŖ‰|úŅ8lŦ­ž^PG^TWšb…b—ųķÎ]”ĻĻ8;9˛tåĘøxķfƒz/Ž“â™„\-v6áęę ŧ€q˙F´hŪô‰ÛLœ<…Z5k’˜€BĄĀĖˌÄÄ$œœiûV+Ž;ÎؐsbiiAīžŨĩa[”ƒ‡rôø)ĸĸ"ĐŋVV–,YļSSS’’’hĶē%~~eYôĶRôôô°ąąF­Q“žžÁ×˙›Á”Ī?EĄP°nÃ&|ŧŊ¨]ëŌ!J…ĄĮOžæ×ßö’——Gŋ^Ũt֗-Ûåô!ÚĶÅĪęęõė?xäąåŨēt,påmabãâ™ôå4,--Š^­Ę?&„÷î?ÄõЛ–ō™sq¨T*f,\‚Ŗƒ=ĒÜ\zwû™ÚØŋ÷3ĩ/^ŦRâƒGŽqõú íĪ;´ÃãoPäåĒiTŋ.ÖÖ֌xoͧ}…‘‘“žüšļoĩB‚ A066擉“ÉČĖø{3ņņöĻY@cNœ<ÍŪ°ŗŗĮĮۛV-›ŸĀŧāéŨŖŲ9،DjZ{÷ĀĖL‰—‡;W¯]§b…ō\šz•Νڗö°)ĀŋŅKkëßęŋ4~hÔđ!ēî�Úļ~ėTäŗ¨Xžœöûå%ÕĒy�­š<wJ[Ķ&oŌ´É›ĨRWyŋ˛|ûõį:i[ŧxĨÄū ë?6#žĘÖíų_ą;z$�ÖÖÖ�˜›™cōāj<uŪ_üŦZģcccŌĶ3PeĢžÚŽëƒĢ~íím 9w*V(€­-‰ I$&&j/Œ°07ĮôÁÕĻũ˛o˙!zz”/_ŽĀ×T„BˆéĨ$N9??Ə)ŪyķėœļlßÁ´)“ÉSįqöljž~%DlL,TŦ@l\<66ÖØÚÚŧNLL,vö6XY[kīž”œœBfFĻļkÖūˁ‡hßî­gŪO!„ĸ¤J˙ÔôĄ#\žzUûŗ‡‡owęPėōF††8;9ąhņĖÍÍŠ\š;vîzbZCxd$+V­á~dũûbiaÁ’å+Yŧl%ŠŠŠôėÖo/ôõ ˜ŧkk+ŦųŽkŨzu8uú n><B!^†RŊjúUļk÷īXXšĶ ŪĶīyZšWM !„øwxÖĢĻåéKĀ’å+šFŊ:¯ÎŊI…Bü;Hũú0dP ö6yĨáԙŗŒŸ0‘{áĨVįŗÚ{āŖĮLjjÚĶ7BņRÉåÁ/ČŲsøN˛ŗŗųô‹¯¸xųĒöĄ cG ###“Ĩ+גFŖAŠTØˇ'.ÎN|úÅ×ö녯7WŽŨ`Åęu|4n4‘QŅ|1õ[|}ŧĩmššē0lp Ÿ~ņ5JĨ’ß€°đÖnØĖûcFp÷^ŪšËëU*#„âŸC‚øQĢÕčāį[†)“>&//ÃFkoĻRŠøxŌW|0v¤ö™ŽWŽ^gĘ7ß1{ÆTm=!į/ōËļ|öQū"ŖĸŠXž\‘Ī2V*MŲ{āMüĄžžjĩēĐ2B!tG‚øILJBŠ4-rũŸwîâæęRāÁę•*–ĮŌŌBû€‚§ÎpôØIf~ûĩöyĨ�×o„2yętíĪÔÕ>eŠOŽL›>ķąG})MMHzđt!„˙Ä/ĀļģÉÎÎ)ōĢŠ y$öņz9Ų9TŦPŽģ~ŖSû6ÚMʗķ+rFlhhȰ!øaŪB† ė¯]ŪÄŋ!}ö%>^žĨúä!„ĪG.ÖzÚŊÕ##Ŗ’ûģ2Ū^DFĮzķOí˛+×n–žĢK~€ŋŲ¨>ī âęĩėū}_ąÛ÷õņĻĘk•Øļã¯ī_ī?x„ŪŨߖBˆ™ŋ ļ6ÖdddšŪĐАĪ>ĮâåĢPŠr016æ“Æ¸z[OOÁûcF0iĘ7˜ššâhoĪĩëĄLšōÍ#Ûč1éã÷ ÔßĩS{>øô œōŋ÷‘™…õ?äføB!ū"Aü‚čé鑕­ũY__ŸĨ ᨯÍՅĪ>*üķËē ?´ŋūâSíĪĢ—~–GËčéé1ũëŋņ˜Ĩ=å-„âŸC^™_ÕǞdų*ëē+üž˙ —Ž\+pa˜Bˆ™ŋ ĩkÖ vÍēî�͚øĶŦ‰ŋŽģ!„ĸ2#B!tH‚X!„Đ! b!„B‡$ˆ…B’ B!tH‚X!„Đ! b!„B‡$ˆ…B’ B!tH‚X!„Đ! b!„B‡$ˆ…B’ B!tH‚X!„Đ! b!„B‡$ˆ…B’ B!tH‚X!„Đ! b!„B‡$ˆ…B’ B!žÃƍ›ˆOxæōÄB!Ä3ZšrnnnØŲŲ>sÄB!Ä3Xšre˖ĨnŨ:ĪUąBQBC¸jÕ*|ķÍ˙ČČČxæē$ˆ…Bˆx4„gĪžƒŋŋ?JĨō™ë“ B!ŠŠ°öķķ{Ž:%ˆ…Bˆb(,„ŨŨŨ˜={6qqņĪ\¯ąBņE…đ?ūØÛÛ=sŨÄBˆ˙ˇwīAQ]yĮŋˇéšZh ›7"(> DQ‘8Áį:™D­ÔėdÕŨÄ$&›­Œ3k&ĩ5“ÍĖf&e’šÉLÍĻ&KÍf´˛šQ1"¨ˆ/Ÿ>xÉC y7ŨŊ4t@@bpˇ~Ÿ¨–{Īũs~ˇ÷ôŊ´Bˆ{¸_îęꖇĩ„Bˆ‡aįÎ]Î?QęO¸Ūĩk6›mÔĮPdcwWW::;Ņēģú€B!Ä˙™™™Î/ëØ´iŖķéč7âįg ŊŊ^Ø@sk^ēŅ=9=ĸBb䯭Z›ĖŖ:Ø˙'§Ī_ī„B|nÔ ũ ÖÍÚožÖŌÛ̓đ`ã¨ÚQ!ÖiŨ‰‹Ձ„B1˜Ü#B!Ƒb!„bI!B!Ƒb!„bI!B!Ƒb!„bI!B!Ƒb!„bI!B!Ƒb!„bI!B!ÆŅˆžkē䎙­…Éī÷E×B1œ4Ŗ/ےc‰÷ņīP„xdhE,EX1ųĩl-ŧ8ŪaņHQ!–",„)yßâŪFuxED šKŌŠxf!ĮWĖáãĖéŧšÅKĶ"Į$¨ã#YėĪ,˙ ütf˘´ 0Y¯ãôĘLæųąbb G—eP°<ƒ?fNG§va˛^ĮžE‘ŋ<ƒ}OĻ’dđ&ÃdāÚĶYä.IgN*&.ŠB͚'Č]’Nî’t>]0‹Œ 'ynĒŅŨv˙åcSXâ|­RāëĨŗŲ6+–Ü%é”ū`>ÅOÍ#wI:Z7>š“DÁō Ž.Ëā‘AΘ\…šr—¤cŌēņŏqbÅgŦAîÔŦy‚õS"�øķüd˜|9ē,ƒg'‡Ū7Ί>^ülf iF_v,œåüŲįî×ceWv )FŊ˙ōŋ›ā|=’8ûōq,sŧŋģ+§WfŽI[w÷ą˙ųSš:û[ĩq¯sîAį@1´Ũ#îŗģĸšŨÕXœÆ–cį9UßĖ̉QĖōŸĀ—O>NqC oŸžĖ{é ôØl7šyˇ¸ €Ų&_ū-e y5 $ôŦÜ‚Mņ‘$ŧņÔ¨yĨ „Į†qŗ­‹ÍŽQëÆge•ü86Æĩĸbũ‘3\ūa‡ĒęųōF-ëĻDpūŽ™Š>^ä|yŒ—&1Ũ0wĩŠMyÅÜlë� ZīInu=…uMœZ9—…{ ¨0ˇŗ&:“‡ŋxl*Ÿ\šÅ_Ę*YĀoŌ§ņ手”4šYēī8‰o>͚EŽC�,ؓÕnwŽË3Q!øk]šÕÖ @vˆ?kã"psQņ“ xjÔl3 ֏æ$Ą�ÁžZÎ6´8ÛŌk4¸ģ¨ØzŌņąŪ[ŗbšŨŲÅ{%ålIŒĸÃj%õķ#y¸spIy5ŽUGF /[gÄđÃŋ¤ĻŖ €W Îs¤÷?ļvQē,Ŧ‰åŗ˛J�ÚzŦŧ}ú2Ų!ūüéōM�VE‡ėáNYKےãˆ˙ė ĮVĖaËąĻúzą˙VŨ°ųaŌēķūėĸô:–ī;ÁļYąÎš[wä 6;(ĀÕ§søķS—Ų8uĸs›įķÎPöôBļ—V†#gmŋ–å˕ՋXfbut*EaCŪ9f›|É öĮCíÂæ‚VD˛(4€Ļn ÍŨ=ÃÆšę@‡—ÎfæÎ\ū!6wW~Vt “Ö͙šU rüĩãĨüûãSõ V“ô::zŦü(÷4[gÄįã…FĨ°éh1Ą:-o§ÄqĒžŲ1ߎę!Ī—0O-˙™> ‹ÍÆ5”›ÛåĐ珈!ûøæŒÉÎķĮfwäĐ÷ŒŦ:P„NãÂË “ШTl/­āPUũ€}ūZQÖ¤(ė@ˇÕÆs‡Ī�āĻráŋŸHágŽéí1`Ŧ7;ķfR&đá…ëė¸V5lŽ!ž1ĻOM—›ÛYsđË"Yn"ŌÛŗÅĘü`?į66ģã˙0[zH7hęļP×ŲM°N‹QëΊúf~}î{oÔō?7oĶiĩą4ÂDˇÕQ˜'yëĐŠ]xĨ „ęö.ėv؜_‚FĨ"ZīÉtÃŽ™ÛøčÂuj;:ĮÖģĒiˇX ōpįN—… s;�žr‹˛–vĸõ:gAûēēhoOTĘ7ũ;ÛЂĩn�ėĪIåĀâ4ļöŽ Ú{zđvÕ8ˇīčąQ×áˆ/+$�›ąfų‘`đæŲÜĶ\mn0–ŪŽjÚ{ŦCŽs´^ĮŅŪ8ĢÚ;šnî ŌÛ€_>6•+-mÎ" đÎãS8°8ŲÉ�Xm6ļ—: zŸöžôũb/¨i$ÉOOĒŅ—ƒ•udûsŖĩÛŊ2ĀĄËfcCŪ9Zē{øģˆĀså­ĀÎ9T`Ā6‘^:ÜT*~S|ĪËĢyjR°ŗíūš’āĢįĩ¤(ž?rŽ— J�x51ŠNĢ ĢŨNšŅ—įâÂy!īܐũãL2čÉĢi`n ‚ũųâz �5]Î|´30Į'zy ŲˇpO-ķ‚ũYšŋ÷ĪWŖ÷$'ÜČ÷ŋ*äpu?Š cUt^¨āũķå�Þ/v;TˇuŌØeaÅÄ A9­÷ļ}įĪ…ĻV4*…_+ão•uĖ 4đ\l8<b¨V��)IDATj5fK ‚ũíįãEc—…gáŠQ3/ČĶ[ÉąÔŪ!ŋļqĐXÛė[UĪģÅ×x2Ėx˙DB�c\ˆ:ģ1w÷ Q)`‡âÆ^.(á§'>ŦĄé­nŽ*vėlÃĮKiîļāĸ8~×ûƒŪĻččąōb~1o^āēšĢŨîŧúoąX wJáŸO”’WĶČÚ¸prÂL€Ŗxn×7kšÕցŪUC¤—Ŗxm˜A’Á›‹M­d�˜hāRsĢs…0Ũ Įj‡ÚŪ"—Ŋˇ€{ōŲVt €Ŋ×kų #Ņšũ‹Ķ"Ųq­Š3 ͨ{;twŦ ŽīëwŸį$ąįFíã|ąŠ•šŽ8ƒ<Ü õÔRÖ⸨ČüëQ nŽŦ‹‹pnŋåX) öäŗraīØ*|rõ)>„yj(ŧŨÄL?=Ë#ãuÍÜN‡;!:-ģĘĢY?5‚üš;CÆs7{ŋO lØÍ]Ÿž9j~Ԋ‚Ģ‹j@{wįŠJQPĐ珠Sģ`ˇÃ;g¯đ/'.p¨ĒP)Ę ņ*ÎŨåÕüũ¤`‚uîΕjŸžŨûįøpqĢ”žY/W5ę~Ws*EÁjˇŖô΃3ŽaΗīGQŲŪÉŽōę{äĐđ}ė;´Õn§ŠÛ‚šģuoėģËĢxũØy~ņú }lũÆĻ/f€•uä„ũŋkŦš-:zŦú,„¸ˇ‡öwÄ{nÔėĄeĮÂd2L†ŋķÔ¨ųSætÜ\T­i¤ ļ‘ßÎNāl}3+&rĒž™-‰Q\mneQ¨ošCUõėĘNáĨi‘Xė÷^–ũS|$OGc.5ˇŽâš­čß 5ŌÖcesA Ī›NÁō R|¸ŌÜÆëĮĪķTd0G—e°)~"/ä Ū׋ÃKĶy7-žįœuį‹Ķœ÷^U ä„Y×īcԂÚF6ÅGbąŲČ ņG}×hWļuRr§…OæĪÄäáF˙÷Ņĩ_Ÿaņ0̊íĨ( ä/Īā/Y3yãx)ˇ{/ēŦ6Ö9Ësqá¤}øUęTgœĶũŧĮ'??u™Ŋ'�É(ĒoæķŠįq.6ĩR×ŲEaŨđáØíû?tŖR@ĢváãĖéhÕ*v•Wßwî*ĖíƒļąÚíükr,Ë"L|VöÍGœwįĘ;gޞ=#‘ˇSâč´ZųsWy/=_§ÅãĒRņû‹7x?#t“ī€dŋ;ÎŖ5¨Ŧ#ŨäËž›ˇÄחšģ ŨPqƒcÕ|¸ēžŨŲ)<;9”‹M­ėŊ^ËÎėdԌž||éŸ\ŊÅķS"x6&•2üųRTßDvH�ņ>^øēkœNũ ×ĮŌF3‹B$øząĪuÖLåķf hŗoŸ˛–6 nŽ|ē`õ]ŽvÜÚØwë6ĩŦŸ1hŦ…ŖŖŦ]ģÖūÁ|ĢWė?AAíˇ[ 'ÍčËæ„Hžúęäĩ39aF˛Bü؜_ōPÚĪ]’ÎęƒEÎ{ÄÂĮUޜTRv=‘Ũ_N˜‘…!ūŧôÆf¤*Wgü_ûŋķã~•“ĘëĮK­ˆÅčĨ}؝2ŪaņHYˇnAAAĀWÄo%Į‘jôy(A}.7ˇ’čGVŋ{bce]\•Ęš2}PÍ V+¯öģû°xkÔüdÆdNÖ5=ôc=Ęū07‰Ģ-mR„ĮPĒ҇ˇ’ãÆ; !i#Z !„âÁzE,„Bˆą%…X!„GRˆ…Bˆqôŋø˛:‘Áĩ;“����IENDŽB`‚���������glewlwyd-2.6.1/docs/screenshots/usecase-register-scope.png������������������������������������������0000664�0000000�0000000�00000150256�14156463140�0023752�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR�� ��Ë���„æ���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨwtUÆņīn’M'„R()„ĒH¤÷^DŠAQAAėX_PTģHīˆTé ¨€HI! %^6Ā(ëõųœ“Ŗ{įÎŊŋŲ]ÎŲgįÎŦaôčŅyžžž<÷Üs DDDDDDŦÉl6ķĘ+¯œœŒ1--=z(|ˆˆˆˆˆČßÂh4Ō­[7ŌŌŌ0æääVÜ5‰ˆˆˆˆČŋXXX999 &“Џë‘1'''ōōō0w!"""""ōߥ�""""""6Ŗ�""""""6Ŗ�""""""6Ŗ�""""""6Ŗ�""""""6Ŗ�""""""6Ŗ�""""""6Ŗ�""""""6ņũ?*€ˆˆˆˆˆˆí(€ˆˆˆˆˆˆÍ(€ˆˆˆˆˆˆÍ(€ˆˆˆˆˆˆÍ(€ˆˆˆˆˆˆÍ(€ˆˆˆˆˆˆÍ(€ˆˆˆˆˆˆÍ(€ˆˆˆˆˆˆÍ(€ˆˆˆˆˆüåååąlŲ2víÚuĶ>{÷îeîÜšäææZmŪ;6€ô4„ŠUÉĖĖüÞ3>˜IHÅĒlŨļŨ•‰ˆˆˆˆüķ­ZĩŠŸū™uëÖŨ0„üđÃŦ^ŊšcĮŽąpáBĢÍûˇW§žAHÅĒÔŦSŒŒŒŋkų ÂÃÃqpp�(BöíÛĮęÕĢ0ÔŦYĶjķū-$+;›%˖c0¸’’¡kÖũ͈ˆˆˆˆČ_Bŋ~ũ°ˇˇ~ !û÷īį›ož!//ŖŅH¯^Ŋ¨ZĩĒÕæũ[ČÚuëšxņôíĀü…‹ūŽiDDDDDä6Ü(„ŦZĩĘ>zöėiÕđS�™7?ØārwŨ쨎˙G"ŖĸnØ7:úC~„đZuŠQûnĘņˆČöŊ’’ÂÄIS¨[ŋ•ÃkŅš[OÖ­ßø§j;pā ÃIz ŠXĩ÷6kاĮqút\‘ö?w.)/ŊBŗ–mŠRŊ6uę5¤ĪYŋĄpbčđÔĒۀšwÕįūžũŲŧe[Ą~?ėÛĪāĄÃŠUˇĢÖ Q“ŒnįÎ%č7üŅ‘„TŦJBB"ĪNxžģ4ĻRĩš´lہ9sį7))‰IS^ĸqĶ–TŦZƒģęßðsāāĄ">[""""ōoJŋ~ũ°ŗŗō/N7 ôėŲ“đđpĢĪgoíOœ8ɞŊ?P§v-‚ƒĘĶŖ[W~ØˇŸ 3qü¸}“““éĶ—““Đŋ•*U$""’!C‡SÂŖDĄą5ší;vŌĄ};îm܈¤¤$^~m*åʖ-Rm‡~9LīR˛¤ƒ ÄĮۛSąą|õõ\ļīØÉú5ßāéYōĻûįääĐoĀ Îœg`˙~„„„’’Š•ĢxäąQ˘ūÚˇ`ĪŪč?he˔ၾŊqwwgÅĘU<4ėŪ~s*Ũģv`Ķæ­ ôqüũũ6tĨ}}9vü8_ΙËöí;ųfÅRJ–ô�Ād20üŅĮiPŋ3?xŗŲĖ{ī¤)/á`ī@īûīāü… tŋ¯—/_Ļ_ßŪTŦÆŲŗņĖųzŊûöį‹Ī>Ļ~Ŋģ‹ôŧ‰ˆˆˆČŋە+W0›Í–Įyyy¤¤¤ü-sY=€Ė[öŖWĪ�tėØž^~•ĨËW0öé1˜Ž^č’ßwIII<ûĖS úĨŊfę<ūĘãū|ā�Ûwė¤YĶ&˘ūŽĨŊ[×δjÛąHĩ8x° ĄL?ŽõëYÚũJ—fĘK¯°ę›Õ đĀM÷?|øWNœŒaÄđ‡ûÔhKû€ūũxxøŖDFE[ڞ›üNNŽ,Y8Ījú?З­ÛķÚÔ7éŌŠ#FŖ‘)/ŊŒÉdbÁܯđ÷ķŗėĖ„į'ķŅĮŸđĖĶŸ‹råĘ2nėS–Įīŋ;ģ4fƇ3-dÚôğ;Į’…ķ¨QũˇäÚ­kgÚvčĖĢSß`ÅR-ų¯;pā�+VŦ°,ģ2 äææ˛fÍ�4h`ÕųŦē+++‹%˖ãččH‡ųgÜ\]ißļ /^bũī–Kíú~7�; íÚļÁÃÃãw}÷\íÛĄ@{`@�÷6nT¤úú÷ëËĒåK,á#;;›ĖĖL*TātÜ­—aŲ_ O‡=BNNŽĨŨäāĀŸÎfÔãpôč1ĸĸĸiŨ˛e3*nŽŽ|ō҇ŧ˙Ū4rÍf~=r”ͧãhŪŦiđųAÁŪŪžM›ˇĒŖsĮ‚΁ģģ;wß]—¸¸3$$$’——Įˇk×RšREüũüHLL˛ü9Ø;P§vmũr˜ÔÔ´"=o""""ōītđāA–/_ŽŲlļ\ķҧOËrŦ5kÖ°{÷nĢÎiÕ3 kŽ^|Ū­KgÜŨÜ,í÷õėβ+Y°h1:ĮƞÆÁÁĄĐ‡oŖŅHPPy8x]ßX€.ˇ fãwEĢqŲōü:Ž=ƕ+W lËÉšõŦTĢZ…6­[ą~ÃFšˇjGĢV-hP¯5ÄÍÕÕŌīXDD~]Ą!…ƨ^Íō˙QŅųgL*U +ÔĪÉɉ�NœŒ)´-8(¨P[éŌž�$&%a4¸xņ/^ĸ~Ŗ&7=ž3gĪVĄÂMˇ‹ˆˆˆČŋץC‡,áÃ`0ĐŖGË5Ŋ{÷fÁ‚˙̐kŸ×¯_“1ŋ}pöķķÃÛˋģžįTlŦ%D¤g¤c29Üp,'GĮĶĶķKÄņwí˙aŊ(Ū|û|8k6ÕÃĢņüÄg)[Ļ &“‰ã‘ŒŸø|‘Ƙ1ũ–­XÉÂEKøjÎ\žørŽŽŽôîՓņãÆâččHFFū'^ģfãfŌĶŌpvvžáv''G˛ŗŗÉÉɱܙ�ĀÉšđņē8ģ�pųōe\]ķ˙ŋJ•Ę<sŨRąß+íë{ËúDDDDäß+**ŠÜÜ\ Ũģw§zõę–m•*UâūûīgÁ‚˜Íf’’’Ŧ6¯ÕHtô öū°ā–æ.ZÂĶcžĀÉ҉‹/Ũ°_jjjĮ×Bƍ~ũ÷}o$33“O?˙??æ~õ…åC:PčLČ­ØÛÛĶĢgzõėArr2ÛļīäëyķųrÎ\Ž\Iáí7§âãí]¤q]\ōkHOOŋáö´ôtL&Sđqŗū׿ō,Y˛ĀؘĻMî-ōą‰ˆˆˆČG׎]1›Í„„„Üđ‡+WŽL¯^ŊˆŠŠĸS§NV›×jäÚÅįŊ{õäŪ{ڞ™™ÉØqX´d)OŽz{{{ü9Küšsø•.m雕Mô‰“öđ öôiîĒSģĀļŖĮŽũa}‰‰IdffRŊzxđųwŦú+<<<čÜŠ;´Ŗ}§nŦYˇžˇŪx˛e8r´p]ģžßMddŨēv&ėęĩ'‘…o;œ––Æ™3gŠTŠbĄm‘‘ŅT -Đv팓¯^ĨJáéY’¨¨h._žL‰ī(vūÂŧJ•úKĮ,""""˙ז]ŨJÕĒUīĖßšvņšÉÁ§ŸM‡vm ũuīڅ6­[’˜˜Äæ-[,ˇ]šę›ã-]眴´‚H˙ÖwuöˆČHžßŊ÷kôöö(ô{ŋ9ÂŌå+�ČĖ*|våz_|9‡ģęßCĖŠSÚ FŖ{;; a*Tž<Ûļī(.ŌĶĶ˙Ü$Ū™ū...TĒT‘ ōåųnĶâĪ+0æ‚E‹1›Í´oÛĻP‹–,)đøÄ‰“8xˆāā K°čĐŽYYY|ôņ§úžŋpöē1tøˆ[̈ˆˆˆČßÁ*g@ÖŦ[ĪĨKÉÜ×Ŗû-ŋY4 ?k×m`ūÂE´nՒ~}zķéį_ōÖ;Ķ9sŠ ĄĄDDD°uÛęŪU‡}û´ė[īîēÔŽU“-[ˇ1tøÔ¯ORŌy–._A‹æÍØđWĄ;99ŅĸyS6mŪĘÄIShP¯‘‘|9g.ĶŪ~ƒ‡yŒÍ›ˇ˛ō›Õ´jŅܲ<ęzMîmĖÛ˙›N¯ŪĐŗG7ʕ-KzF›ˇlåØņ1ÜŌwōķ:üQ0˜>Ŋ{áččČęo×{š×_{Ų˛Ŧę…ÉĪņаôî7€ū}ûāYʓ_˙ĘÜy ¨ʐĒ#++›ĄÃGĐĸy3Ėf3ŗf`š ĀŖcķ–­|0ķ#Š_īnÎ%$0wŪ.]ēăÜōųų;ØÕŠSgJ—.]nk)/žLܙ3ŧ9õU||ŧoÚ¯L` k×­įĮŸĐëž”.íK“{}‚-[ˇągī^\]ŨøßÛoÉŅcĮöđC8šL ZĩlARRģžßÃļí;HIIeü¸§ đgãw›éØž!!Á7ŋQŖ{8ŽÍ›ˇ˛ņģMäææōęK/Đ螆�ėÚŊ‡īwīĨWĪ…–ixz–¤yķfœįģM›Yģn‡ũB‰%xzĖ“ 4�ƒÁ�@Pųō4¨_¨čŦ[ˇīŋߍ¯žü":üv'°ōåĘqOÃDDFąbÕjÖ­ß@bR=ģwã­×_ÅŨŨŨŌwÍÚõDDDōŧŗ‰‰‰eÎÜyŦ[ŋo/oƏK÷nŋŊŽ...tîԑôôtļmßÁŠUßpđĐ/ÔĒYƒˇĻžJŊģëũą‚f~„ačĐĄyŗgĪ.îZ¤F>ųĢŋ]ÃÎm› ŨēXDDDDäN^ë.ëūĄˆˆˆˆˆČ­(€ˆˆˆˆˆˆÍ(€ˆˆˆˆˆˆÍ(€üƒŧ7ímĸ˙Ēë?DDDDäKDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlFDDDDDDlĸáŨu@DDDDDÄv@DDDDDÄf@DDDDDÄf@DDDDDÄf@DDDDDÄf@DDDDDÄf@DDDDDÄf@DDDDDÄfŒyyyÅ]ƒˆˆˆˆˆüäååé ˆˆˆˆˆˆØŽ=@ÂųKÅ]‡ˆˆˆˆˆüØøz•,î:DDDDDä?@K°DDDDDÄf@DDDDDÄf@DDDDDÄf@DDDDDÄf@DDDDDÄf@DDDDDÄf@DDDDDÄf@DDDDDÄfŒ)))Å]ƒˆˆˆˆˆü¤¤¤`ôđđ(î:DDDDDä?ĀÃÃCK°DDDDDÄv@DDDDDÄf@DDDDDÄf@DDDDDÄf@DDDDDÄfė­5PFf1qņdfe“›kļÖ°"""""RLLö8;9RÆß“ƒuĸƒUFÉČĖâXÔ)ÜŨ])áņŋ}b%.>‘ÚÕŠģ ‘Û’•Ãų‹ÉŒĄr…ōV !VI 1qņ¸ģģâæâüŸ"""""˙&{ü}Ŋđņ*Ééŗ VĶ*i!## g'k %"""""w/OŌ32­2–Uˆ9/ŖÁ`ĄDDDDDäcr°'+;Į*ciŊ”ˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØLąé3>äÁĄ°÷‡ũ…ļ%_JæÁĄpäØņb¨ėßmŅŌôø0÷÷¨¸K‘˙ã•Ô´â-Āh`áâ%dfek˙Ų99|ņõÖŋ›×_žTÜ刈ˆˆČȕÔ4ŒîŽ.ÅZD­š5HMKcíÚ ÅZĮEzz:fŗ™ģj× 8¨|q—#""""˙!îŽ.ØwÎÎÎtíԑe+VqīŊ )åYęĻ}ŋßŗ—ĩë6.{*„„Đ¯Īũøúú�°iËV–¯XňG†ņõŧ$&$âãëð‡s*6–UĢא|é2+V`čāA”(áĀåËW˜ŋp1GE’z…2eču_wĒTĒh“į⚠/ōҧ_ąīĮŸ0ÔĒQaCāíå@ŌųķĖūė+~úų™ĐĢGZ4Ŋ€Õk70gŪB&OĮĖ?ãôé3¸ģģҎWÚ´jÎO1qĘ+�ŧúæ4ėíYąhšššĖ[´”­Ûw‘˜„ˇŨģt¤cģÖ�ĜŠeÄc™<a,Ÿ}9G'GĻŋų*�[ˇībéŠo8u:g''š5š‡Aũûâh2Đ÷Áaôš¯;‰IIlŨž‹ôô ÂĢUá‰G‡áéYČ?+ķõüE|ˇy)Ši„—gČ ūT­œ˙ü˙Q}""""ōĪQėĄ›ÍfZĩl§§' -ģiŋč'™5ûSĒWgōsĪ2扑dffōŪ‡ŗ,}ŒF;ŌŌĶŲ˛u;ãŸyŠwۚJNvī}0“ŖĮŽķâäįxõåɜ<y’uë7^?wĻŋGdt4C‡ dōķ .Ī;ĶŪ#6.îo?ūkrss™ôŌTÎÆĮķü¸§˜4~,įÎ%0éÅŠ˜Íydįä0aō+œŽ;ËķãŸæÃéoҍa=ۚö>ģ÷îĀÎΎԴ4æ-\ÂsãÆ°čëOiŲŧ īÍü˜¤ķ¨^­*ŗß˙�O>ū_}ú!�Ÿ|1‡%ËVŅįžî|8ũMēwéČŦO>gŨÆM�ØÛįįÔ9ķĶŖ[gF?ū�ßīŨĮëīŧK­šÕy˙¯3fÔvėÚÃģ|d9.{;;/[Iš˛eųüŖĖ|ī-"ĸĸ™ģp‰ĨĪĮŸ}Åē ›xxČ@Ūxe2ūū~<÷Â+ğK(R}""""ōĪQė„<°ˇˇŖ÷ũ÷ągīDDDŪ°›ŋ_i&?7žî]:āīOHpmÚ´"6ö4ɗ/[úåääŌžMk\]\puqĄFp“¸ŋgM&JyzRŠR%bbc8ü믜Œ9ÅāA¨ZĨ2ūūôë͝RĨذqŗ-ž�:Lô‰FAÍá„W­Ė¨Į†Q&0€ /°īĮŸ9w†1ŖFPŊZüéß§U+WdåęĩŽŋwĪnx{ya0hÛĒšššœ8ƒŊŊnnn�¸8;QÂŨ´´tžYŗžžŨ;͞yüũčØŽ5-›5eá’� �jV¯J›–Í*_€EK–SŊZčK€ŋuëÔbđ€~lŪ烤ķį-5•-H›–ͰŗŗÃÛˋģëÔâxd�iééŦŨ°‰~ŊīŖIŖ†„…†0ęŅaÜUģ&gÎÆŠ>ųį(ö%X×ÔŽYęá՘3!“'Ž/´ŨŲŲ™¤¤$/]Κ„˛˛˛ÉÍÉ 55%,}ũüJ_ˇŸnnŽ–åV×Ú.\¸�@tôIėíŠ\1˞Ũh4Pąbą§b­~œ7ƒƒAåĘZÚBƒƒ˜đĖh�ÖŦû“Ɂß]ˇV!”Í[wh *gų77W�RRRo8oԉ“äääR§VÍí5Â̞nã&Ō32,m•¯[’f6įq<2š}{دzxU�ĸOÄX–ŽũūZ77WK=1§bÉÎÎ&ŦB¨eģƒŊ=ŸĀĄÃGū°>g'§›ˆˆˆˆÜyî˜�Ч÷}<?ų%ļīÜI­ęÕ lÛŗ÷>üč:wjO˙~ŊqqræXD$˚]h‡‚‡å`īP¨O^^ūĶ3ŌÉÎÉa؈‘ļįšsņ(áq›GTt))Š899Ūt{Zz:ÎNN–ŗ×8;;‘žž^ íÚõ×Ë#īĻã<ûü ømlķÕ'čâÅK–6W—ßnX™•‰ŲlæĢy‹˜ģāˇåT×\¸n?Ķ ęšæZšŲąĨ>gŋ›Ž/""""w–;*€úû͞yS–,[I•J• lÛ˛}'U*WĸgˇŽ–ļėėė۞ĶŲŲ^˜4ĄĐ6ŖÁv+Ô<J” ==ŧŧŧB!ō?ü§Ĩgڞ––ŽËmÜÉėZ¨ûäH‚Ę—-´ŨĮۋĤķ…ÚMŽØŲŲŅĩS{Úļj^hģgɒEš˙Ú™Ģ´´ôn/J}""""ōĪQü׀üNˇ.1›sYŗŽāmys˛ŗ-×/\ķũžŊ�7ųnŋhB‚ƒČÎÎÆl6āīoų3™Lx–ōŧ‘˙œĐ rrr9z<ÂŌv*ö4ŖžOĖŠXÂ*„Mdô‰û9vœŠ×-_úŗB‚Ęã`oĪĨädʖ ´ü•pwĮÃŖ…ĪAū2ĩ°Đ` ėįįW{{{ËŌ¯?R&0�G“‰C‡ĩ´™Íy<3q ßmŪö—ë‘;Ķ@\]]čŪĩ Ûvė,ĐĖá_%2úIįĪķÅWs)Y2‰Ôɓ1ų‡ ĢU­Bųreųč“Ī8zė8‰Iįų~Ī^&ŋđ ›6ošŨÃ)˛Z5 *W–é3fņãĪ9|ä(ī~đY™Y”  nZ”+ČģīÄņˆ(ÎÆŸãķ9툈ŒĻG—Žy^gÚĩiɜų‹Øļcņį8øË¯L˜ō oO˙ā–ûöėօģ÷˛pÉ âΜ%ęÄIۚ6ƒ§ĮO˛,*ĘümZ5gÁâe|ˇeQŅ˘9›ˆČhĒVŠt[õ‰ˆˆˆČįŽZ‚uMķĻ÷˛yËVNĮą´uîО„„DŪ|{NNN4oz/]:uäâÅK|ūÅŒÆŋ–ĨŒF#cžɂ…K˜ņá,23ŗđņöĻK§´mĶĘZ‡ô‡ SžĮĖ?į•×ßÁÎΎęáUxfĖHėėė�xyĘ>úäK&Ly™ŦŦ,ʗ+ĮķãŸĻfđۚ{ؐ¸šēōÉ_sáâ%<K–¤AŊģx°ß[îרa=Ǝ~œ…KV0gūB\]\¨RšS_š„‹ŗs‘įhP Ÿ|ū5ééé•ãÅIĪâõfĩ>šķ† –7kÖŦ?îy ?Ž ĐĪĮJ%ũķÅÅ'RģZØwų‡øépÄmÆ>|øˇKDDDDDūŊ@DDDDDÄf@DDDDDÄf@DDDDDÄf@DDDDDÄf@DDDDDÄf@DDDDDÄf@DDDDDÄfŦ@ æŧ<k %"""""w˜ŦėLöVË*ÄŲÉDJJš5†‘;Ėų‹É8;9Ze,̐ō~¤¤Ĩs%5\ŗŲCŠˆˆˆˆH1ËĘÎ!>ņ į/QÆß×*cZå<Š“Ŗ‰ĘĄåˆ‰‹'ņüErsB~:QÜ%ˆˆˆˆˆÜ“ƒ=ÎNŽTŠPŪjK°Ŧ3 ų!¤RH9k '"""""˙Bē –ˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒŊĩĘČĖ"&.žĖŦlrsÍÖVDDDDDЉÉÁg'GĘøûbr°Nt°Ę(™Y‹:…ģģ+%ÜŨ°3ęÄĘŋQ\|"ĩĢ…w""""b#YŲ9œŋ˜ĖŅČ*W(o•b•¤ģģ+n.Î """""˙&{ü}Ŋđņ*Ééŗ VĶ*i!## g'k %"""""w/OŌ32­2–Uˆ9/ŖÁ`ĄDDDDDäcr°'+;Į*ciŊ”ˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒˆˆˆˆˆˆØŒ}qN>}ƇüôķËc“Éo/oÂÃĢŌļuKŧJ•˛lÛ¸i sį/āĶ>´Úü?ųmZĩ¤K§VSn,//M[ˇŗnÃ&ĸOƐ›“‹7Ö§G—ޏģģYu>ŗŲĖÔˇßå‡ũ?Q§V žö)ĢŽ'ûāŖO9øË¯Ė|÷­â.EDDD¤b �žž> 4�€ĖŒ bcãØēcÛwėäɑSŠb�*W cāũŠŗTš oMŸÍ[wФQC:ļkƒƒƒ=Į"ĸXõíZvėÚÍ/OÆĶŗ¤Õæ;tø;víæņG†R§V ĢûoĩęÛuŒâŠQw)"""ō/WėÄŅŅ‘*•*Z×ĒYƒÖ­šķöô÷x˙ÙŧūÚK8;9S&02ÅXŠüUëŋÛÂæ­;9âaÚˇiiiŋ§A=Z6oÂčąųjŪBF=:Ėjs^II QÃúx”pˇÚ¸˙V‘Q'Šģų(ö�r#NNN<8°?ŸģöĐĒEŗBK°ŽdɲåĞ>ŲœGš˛ečŲŊ•+…0âņŅt樖ŗņį8xđ™™TĢZ•ÁƒPâ&Ë}žßŗ—ĩë6.{*„„Đ¯Īũøúúđë‘Ŗŧņö4&>;–° Ą–}NŞfŌ /ķԓ#Š^­Đ˜#ĮŒĨs‡ö\¸p={÷‘‘‘IĨŠ<°?%=�8q2†EK–q*6–Ŧėũũ¸¯G7ĒU­@ÜŲŗL|ūž=ŠÕkÖqōä)œéu_W|}}™ķõ|Ξ=‡¯7ƒö'$8€ÜÜ\VŽū–={öqūÂJ•ō¤m떴hÖÔJ¯TŅŦ\Ŋ†ŠaĄÂĮ5åĘōÆ+“)`iûõČ1>ûjQQ F*……2dāT ËŪW¯Ũœy ™<q3?ūŒĶ§ĪāîîFß^=hĶĒ9_|Ŋ€‹—ĐwĐÃÔŠUƒ—'O 9ų2ŗ?ûŠƒŋüĘå+— ._žÁûQ#ŧ*�1§bņÄX&OËg_ÎÅŅɑéožJnn.ķ-eë§ētĖ�� �IDATö]$$&áãíE÷.éØŽ5�§NĮņČȧxíÅįYņÍ9ŠŅh¤IŖ† hFcūĨV.^äŖOŋbߏ?a4ŠUŖ:Æ ĀÛË āëģ‘ķ.2ũũY8tW:´kU¨ĪĨäd>ūl?ü…+))øx{ŅšC[ēvjį^āĐá#�|ˇyīŊ3•āōåøzūbļlßIŌų ”pwŖAŊē<4¨?NNŽî """r;ö"ô@üJ—æØąã…ļedd2íŨ÷ đ÷įš ã˜4qeËōŋéī‘šš€Ŋ‘o׎§rĨŠL{ûM^˜4‘˜˜XæÍ_xÃųĸOœdÖėOŠ^=œÉĪ=˘'F’™™É{ΠJåJøøxŗëû=öûa˙~<K–¤ZÕH´7ÚąfízxëõWxåĨį9sŠßŦ ++›ˇ§Ŋ‹ÉÁącždōÄqT aúû3špņĸe €ĨËW2ā>ŧ˙îÛT å‹/įąlų*F=>‚÷ĻŊ‰›Ģ+sæ.°ĖŊ`ņÖŽŨ@įŽíyyĘķ´mŨ’šķ˛uûŽ?ķRܖԴ4ĸOÄPģæÍ—A…†ãč˜˙Ą6îĖYÆO~/¯Rüīõ—ygę‹8;;3~ŌK$?€ŠiiĖ[¸„įƍaŅןԞyŪ›ų1Iį/Đįžn<ųø#�Ė~˙L;ŗ9į_z#ĮŽķÔ¨ŧûöT*†…ōü‹¯qōT,�ööųy|ÎüÅôč֙ŅWĮøä‹9,YļŠ>÷uįÃéoŌŊKGf}ō9ë6nĘßĪ.˙õųčĶ/éÕŖ žü˜qcFąęÛuė¸ú~ÉÍÍeŌKS9ĪķãžbŌøąœ;—¤§b6įŠžykúûœŒ‰åÅįÆ1õåI\ž|…ŋ{ūīŊ™9vœqOâƒiopŽĖūėKžßķ�“&ŒĨBh0MßÃŧ/f\žËVŽfҞ z 7L{ƒŅ#G°{ī>žøzūŸxõEDDD ģc@ŠRž\ēœ\¨ũüÅ ¤g¤sOÃzúûĀ}{3ú‰Įąˇˇŗô+WŽīiˆŅhĀßĪæÍîeߏ?‘‘‘YhLŋŌL~n<Ũģt"Āߟā Ú´iElėi’/_Æ`0ФQ#öū°ėœË~û÷˙LŖ{ęc4nzūū~4i|vvv”ō,EÍęÕ8q2�ŖŅČŗOæĄ!ƒ(_Ž,ôčŪ…ŦĖL""Ŗ ŒS¯n]üũ1Ô¯W—ŒĖ šŪÛĪ’%qpp î]uˆ=}€ôôt6mŪJģv­itOJ—öĨEŗĻÜͰ!Ģ×ŦûS¯Ãí¸p!?Dų—ö-R˙Õk×ãėäÄĶO<FpPy‚ƒĘķ˘‘ä俞qĶ6Kŋœœ\z÷놎—ƒļ­Z››Ë‰“18::ââ뀛›..Îütā ‘Q'xâąáÔŦNš2 čA|}ŧYąj �CūkXŗzUÚ´lFPųr¤ĨĨķ͚õôėŪ™–Í›āīGĮv­iŲŦ) —Ŧ(PûŊ÷4°,'ŦU#ŋŌžDDFpāĐaĸOÄ0zäjÖ'ŧjeF=6Œ2\¸xĄHõũ^Ōų 8ø ÷÷ėjŲgÄÃqqq)ĐoøCƒxeĘDĒWĢB`€?mZ5'8(ˆ>€Ģ‹ vF;ėņ(áŽŅh¤EĶ{y÷­×hŌøüŠSĢMßcŲGDDDä¯ē#—`]c6›ą3Új÷/]ŋŌĨ™5ûSš7kJxÕĒ”/_–Ę×]KTŽ\ĮdggséŌ%üüJØæėėLRR‹—.į\BYYŲä^ ŠŠŠx”(AãÆ Yļr%âŽ:ĩ9Į™ŗgõØ#ˇ<ޞeËxėââBjZū™{{;˛sr™3w§NŒ–žF^–y¯įį÷ۇx'gįü6ŋëŽÁ‰ėėl˛ŗŗ‰9ušœœ\ÂwfĻJĨŠlÛžƒŒŒ œœœnYˇ5\ûPog_øuŧ‘ˆČT .$œ(@ô‰“úũöúēšš’Rđ9쿨ņHėíŠ^­ŠĨÍh4^ĩJĄq¯E8INN.ujÕ,ЧFxUÖmÜDzF†Ĩ-8¨āûÍÍÍÕRODT4•+kŲĄgF°~ã–"×wMėé8�*…U°´ *†…ũÛ>ÎNN,\炇“|ų2yæ<ޤ¤xŨ{į÷J”pgã–mL˙#Î_¸@NN.é8Ûā=#"""˙nwt�‰O ZÕʅڍF#Æ=͡k×ŗuû/]ŽW)OztīJŖ† ,ũœ ŽUw4™�HMO+4æžŊ?đáGŸĐšS{ú÷덋“3Į""ų`ÖlKĪ’%ŠÎÎīwsWÚėÛ˙BC …™ßspp¸Å1žãˇĻQĨJE† ŒgIĖyyŒ;žHã˜ė ˇååaų`üú[ī`⎺3æĢé&9ų˛MHŠRž Μ‰/R˙´ôôŪ ËŲŲ‰´ôôm×^Īëå‘w“qĶČÎÉĄÛũ ´įšsņ,Yp>×ëÎ \›ķŲį_¸áķxņâ%K›éõ¤¤¤Ūōډ?Sß5éWk3™ žŽ 99šL|áUĖšš ú eË`g´ã…×Ūŧi-�ÎūŒM[ˇķø#CŠZšŽ&‹–­`Ëļ]ˇÜODDDäÜąäxD—’/^íÆ×V”(áNŸû{ŌįūžÄ9Ãēõ™ũÉįøûT ĀˇĶ�ééų]] ˇeûNĒTŽDĪn]-mŲŲŲ…ú5iԘgÎ&=#ö˙D›ÍūâæÛģo?fs.<<ÔōAōÚĩˇÃåę’a ĄL™€BÛK•ōŧí9ŠZGhH7oĨO¯î7 Q;víÆÁÁúwß…Ģ‹3ii…bZZ:^ˇQŗ‹‹ ĖxgjĄm×.ŋ‘kadė“# *_ļĐvo/“ūøõō(Q‚ôôtōōō,g…nˇžk25­`0KIũíų;ÁɘSŧņƝ ķÉɗņķŊņ˛8ŗŲĖúī6ͧWZ4Ŋ×Ōžšš~Ãū""""Æy Hjj_Ė™ˇˇuëÖ)´=!1‰¯ûÃĀ€�ö�ŖŅ@ܙ3–öcĮ ^Ā~2æ$&GGJ]÷‡×ädgãæVđîXßīŲ Pā;õšÕÃqqueõˇëHLL ^Ŋēå-˛˛ŗqt4øûûŨ{okL€req°ˇįō•+øû[ūÜ\ŨpwwŋåYkëŪš#‰Iį™ģ`IĄm1§by÷ƒŲėŪ쀰 ĄDDE¸Î&%5•ĶqqTŧîîcVĨ° dggc6›)[&ĐōįčhÂÛÛëĻû…•ĮÁŪžKÉÉö+áG‰"?Ą!Aääärôx„ĨíTėiF==ž˜SąŠž2ū�–håäärč—ÖĮYYų!úú;ŋ9vœs ‰…Ī]}˜k6“›kĻ„ûoˇ/NKOg÷ûnz†IDDD¤¨ė¯¤ūļŲ–2339rõNWš99Ğ>͆[ČĖĖāŠŅŖp°/|’æÂ… Ėø`&÷÷ėA͚Õ1`āû={1ŒT ąôģt)™e+WҍAΜ=Ëw›ˇŌ ^ŨBKV�BB‚Ųžc'‘Ņ'(éQ‚ÕߎŖäÕÛäž<ƒˇ—Ž&vvv4ž§ߎ]Oũģëâ|õLÃ_Ė7Ģ×°mĮ.j„We˙Oˆ>q÷îœ:uÚ˛ĖæĪrvvĻi“Æ,_š w7W‚ƒƒ9ásį/¤”gIFzüļęū3š7mĖÁ_~eÁ’åDFŸ éŊ÷āääDdT4ß|ģžre:¸?�ÚˇaõÚõL›1“~÷÷$;;›Oŋœ‹‹ŗ ­š˙õÛ׎YĐā ۜ6ƒá Â×Į‡#ĮŽķÁŦOé}_7zvë|Ãũ\\œi×Ļ%sæ/ÂŖ„;Ã*˜ÄŦOžĀÛĢ/<7ŽHķ×ĒNPš˛LŸ1‹a ÂŅŅÄg_Î%+3‹2”-ø§ëķõņĄrĨ0.YN€ŋ%XņÍËŨŧāj€rp`Å7kx Ī}œŒ‰åŗ¯æQ§V NĮåRr2%=<pss%*úQ'NâãíEHpūYĢģj×$##ƒgÆŨujŗeûNNĮÁ߯4vvEģŽGDDDäš+ŠiÅŋ+!!‘×ß|Čŋč龜'ÕĢWŖsĮv–ßGøŊʕ*ōĐāAŦ]ŋ‘e+Va4ÚčĪČG‡¸ŖI“F¤ĨĻņâĢSÉÎĘĻVÍ<С÷ ĮėÜĄ= ‰ŧųö4œœœhŪô^ētęČŋ—øü‹9FÖ¯Ā]ujŗzÍ:îm|Īmíš5hßļ ‹/eŪ‚…Ô¨^‡Äē ßņíšõÚļ.üÛEҎw/\\\X¸x)—’/ãQƒÚĩĒs_nˇ]÷ŸõÄcè]3œÕk70ķãĪÉÍ5ã_ڗŪŊēĶĨC[ËmxũũJķę”įøô˯yôÉg°ŗ3R­Je^y%ūōüFŖ‘—&įãĪæđōë‘‰_i_úŪ߃î]:ŪrßaCâæęĘ'_|ͅ‹—đ,Y’õîâÁū}‹<ŋÁ``Ęsã˜ųņįŧōú;ØŲŲQ=ŧ Όių ˙Wę7fĶ۟Нŧ‹Ģ ÛļĻEŗ&ėēzÍÃŖcFŽāŗ9ķønË6ÂBCyjÔ’Î_`ę[Ķyöų—˜ųî[tíԞ7§Íāéņ“ynÜF|„i3fōȨ§)íëÃĀzSšb~=zŒ'žžĀĶߤ´¯O‘_DDDäðaÃōf͚u[ƒüt8‚@ŋ;ëÃČãO>E›V-éŌŠƒÕĮ^¸x)ūÂ+/N˛úØw˛¸øDjW +î2DDDD¤üt8âļ? >üÎŧäNu6>ž›ļ°vũîëiûŗ"""""˙tÅžëŸdĘK¯áâėLßŪ÷ßō—ŊEDDDDäÆūĩdÆ´ˇ­>æŦ÷§[}L‘˙-Á›Q�›Q�›Q�›Q�›ąJ�1 ˜ķōŦ1”ˆˆˆˆˆÜa˛˛s09XįēV ÎN&RRŌŦ1”ˆˆˆˆˆÜaÎ_LÆŲÉŅ*cY%€”ô#%-+ŠiäšÍÖRDDDDDŠYVvņ‰H8‰2ūžVĶ*įQœMT-GL\<‰į/’›ĢōoõĶáˆâ.ADDDDlÄä`ŗ“#U*”ˇÚ,Ģũ瓪‰J!åŦ5œˆˆˆˆˆü é.X"""""b3 """""b3 """""b3 """""b3 """""b3 """""b3 """""b3 """""b3 """""b3 """""b3 """""b3 """""b3 """""b3 """""b3 """""b3öÖ(#3‹˜¸x2ŗ˛ÉÍ5[kX)&&{œ)ãī‹ÉÁ:ŅÁ*Ŗddfq,ęîpwÃΨ+wš¸øDjW +î2DDDDä$+;‡ķ“9Cå å­BŦ’bââqwwÅÍÅYáCDDDDä_Âä`ŋ¯>^%9}6Á*cZ%-dddáâėdĄDDDDDäãåéAzFĻUÆ˛J�1įåa4Ŧ1”ˆˆˆˆˆÜaLödeįXe,­—›Q�›Q�›Q�›Q�›Q�›Q�›Q�›ą/Îɧ萟~>`yl29āíåMxxUÚļn‰WŠRÅX]žØ¸8žŸüÆ=E۰bŖ¸|ôų\ũzôĻÛkV¯ĘĐ}Ŧ6_ffßmŨÁ~áâĨdrÍfŧKyRˇNMZ5kŒŊ�_Ė[Ė…‹—ũčPĢÍ}ģ~:x˜Oį,āĩÉĪâæęRÜåɸ)¯ŅüŪ{hײ)gâĪņÚ;īķ䈇 .oĶ:RRĶ˙ÂT†ôīMíÕū–9ly| —CDÔI&>õøß:ˆˆČ?Qą�__�@fFąąqlŨąƒí;vōäČĮŠTąBąÖWǤ'û÷Ĩ´o‘÷9Į´wßį­×_ũËcÜIŧŊ<éĶŖË ˇšģģYuŽŲ_Îã\B"í[5#ĀßŗŲĖņ¨ŦŲ°™Ä¤ķ čŨÃĒķũ×õčԎ�˙ŌÅ]†ˆˆˆü‡{�qtt¤JĨŠ–ĮĩjÖ uĢæŧ=ũ=Ū˙p&¯ŋöÎNÎÅVŸĢĢ -š5ũSûÄÄÄŪöwG“#•ÂB˙öyΞKāXDCöĨfxK{HP9ėíėøųĐa˛˛˛0™L{-˙õëÖ.îDDDä?ĻØȍ899ņāĀūL|ūvîÚCĢÍ�8yō‹—-įdL 9ŲšT­Z™~}záíå@NN.‹—.cī?råĘeÜŨŨŠ[ˇŊzvĮÁ>˙P#ŖO°pŅNÆÄāæęFũzwĶŖ[gظi +žY͐ũųôË94jЀFX>5íŊ°ŗŗŖBh06náJĘü4ā‚ƒĘŗlå*VŦ\ ƒCĄ_ī^TŠZšĐŦ­Ûw°nũw$$&āččHõjÕčÛģ%�ø`Ölōō zx5ž]ŗŽ‹—.áįWšũúbÛ¤æ-Yɑc<7v&�ÖoÚÆēMۘøÔã”ō,É[ī}„ŖŖ‰‘Ãŧáš9šų˙ÍÍ)´­UŗÆ´jÖ¸@›ŅhäĀ/GXņíz.\ŧ„¯ˇũ{w§\™@�Ėf3k6lfßĪš”|WgjTĢBˇŽm,!æŲ)SiÛ˛)GGr<2šW'ÃŲɉظ3Ŧ\ŗS§Ī››KĨ ĄôėŌžRž%-c/^ų-û~<ˆ9ĪLx•JTĒđĮ¯KJj*KW­%"ꊊiøûŅĩCkÂBƒošßø^§M‹&ğKāį_~%ΜGÃzuhÕė^æ-^Nä‰M&:ļmIƒĢĄĸ(ĮũŦĸ8u:Ž•ßn öĖYr˛sđ+íK—ö­,uû÷{ųvũ&†îĪâĢ9—„Ģ‹3m[5ŖáŨu,ãėÜŗĩˇ’šFŲ@ē´oũ‡s'_žÂŌUkųõØqŒ#•ÂBčŅš=%¯ū›ųŖÚnd˙Ī‡Ø´m'ņįqt4qW­tiß ‡ĢīáOį,ō¨Z)Œõ›ˇ“|ų ž>^ôîŪ‰ re-uÍ]´œãQ'pvr¤qƒģ Ís%%•eßŦåxd4Šiéx–,A“{ĐŦq ?|ŋúö †=ø�+ŋ]Édbė¨áEzMDDDūiîØ‹Đũũņ+]šcĮŽpūÂ^ëFž}z4ãÆŽ&55…7ŪžFvv6�ߎ]ĮÎŨ{ō`^yq2ƒôcīŪũŦXš €Ä¤ķŧõöt|}}÷Ôhč{?;vîbūÂÅ�8ØŲ‘•‘Éúī6ķđāA´h^øC™ŊŊGŽ%!!‰Š¯ŧĀ´ˇ_ĮÍ͍ĖÄlÎŖCÛ6´n؜RžĨx÷oŅŦi“Bcėü~7Ÿ}1‡† ëķō”IŒ|ôbNÅōŋwg——€ŅŽã‘DGŸ`Ęķx÷oâîæÆ'Ÿõˇ<ߡ’——GvNÎ ˙ŽÕÛŊc[ōōōXģq �/%ŗîģ­tëĐÆōĄ=4¸ÁåĘÜt??_Jy–dÁ˛oØĩgŠiiˇŦëâÅKėØŊ—zucä°1 |9‰eûæíßŗaË:ˇkń1Ņ˙ū<|„Uk7ZúØÛÛąsĪø—fÔ#Cp4™¸x)™wg~†Ņ`ä‰G†0jø`RĶŌxīŖĪÉÉÉGë7ogמũôč܎qO<JhpyÖ\=ö[=|ü'bbĐģĪ<9‚ōeųⓝ8î–ûÚŲŲ´m'ÕĢUfęägéŌĄ5›ļíâƒOž¤uķ&ŧ>e<õëÖfáŌU¤Ĩ§ųø˙ŒėœŪ˙øKėė9ėAƎNpų˛|ôų\.%_žZ§é™ŦŨ¸…ĄûōƋ¨wW-æ/Yiéu"†ųKVRģF8ãG?Fģ–ÍXējí-į6›Í|øéW$ŋĀÃû2ėÁ~œŋp‘?ųĘōūüŖÚ~īāáŖ|>w•ÂByvtūķķķĄÃĖ[ŧ˛Āķu"†“§Nķė“#˜:ynŽ.ĖY¸ĖŌįËųK8ŽCú3ę‘!¤¤ĨņķĄ_ ĖõõÂeœˆ9Ńũz1~ôc´nŪ„ĨĢÖpđđ�ËĩMßnØDËĻxāūnōÕųį¸#Ī€\SĒ”'—.'°iķV0ä1üá!¸ēä_ā;lčž7‘}ûĸaƒzœŽ‹ŖL` áÕĒųח<ķô ų9këļí88:0dĐ�ŒÆüļŒĖLŽ Ī` 3+‹6­ZRŖz8ųī忚éÛģ&“&“];w䕩orôØ1ĒVЌÉÁ„Ņ%nr}Äē Š]Ģ&;´ĀΝ4ũûõæÍwĻEXXūu/Y™YôíĶ ĮĢßV7lPŲŸ|Nff&ŽŽŽˇ˙Ņ™øsŒ™đâ ˇ5œreqrr¤OĪ.ĖūbõëÖæ›ĩ)W6Æ û6¸{§vˇœĮŪΎĮ†bÎÂĨĖ[˛’yKVâWڗĘa!ÔģĢe ôŋ|%…ąO<by?4k܀š‹W‘‘‰““#w׊I•JđËŋÆÁĮۋ:5ĢķëÕP{ÉÁD×m,ˇŋ đ`ŋ^8;;0¨ī}L~í~:t˜ģk×dīūŸ¨Q­2 Ž~Ģīã]ĶqgŲĩw˙Mīčņ(bãÎ0jø`ˏûēvāhD$[vėĻß}]oųü” đ'ŧJ%�ęÖĒÁ‚ĨĢ._–āōe¯ļUgŨw[IHL"¨\Ų"Q žxd%Ü-Īy§v-Ųēs7Ņ'OQ§fūŋ™ÜÜ\Ú´hb93ҰŪ]ŦŲ¸…¸ŗņ”ô(ÁŪũ?ãîîFˇŽm0øúx‘–žÎķßtîã‘'ˆ;Īø1Yާī}]Y÷ŨV’/_ÁŨÍĩHĩ]oÃæmT ˛œ}ņņ.E—­ųrŪēthmŠ?++›ž]Ú[ΊÜ]ģ&_-XJVv6ii鏌æūn¨xõ X¯Ž9z<ĒĀ\=ģtĀh4āUĘ�_/ļíÚÑã‘Ô¨V0�P14Øōžųˇ˛ŋ’zëo™‹“ŲlÆÎ˜˙Í`tô B‚ƒ-.�ŧJ•ÂĮĮ›˜Są4lPZ5kđŅĮŸņáŦŠ{WmĒVŠB€ŋŋĨ˙ɘS•+g �6 QÃæ­đËaüũ0™,ķįHHH¤j•ʡÜ7''—ØØ8ęÕ­[ =((˙Ž<1ą§-ġ´%|�¸¸¸š–fĶ�âã]Ё}zŪp›Ÿ¯å˙ĢUŽHáĖúėk.%_f˜Į0 j._/Æ<ö0į’øõØqŽEFŗs÷>ļėØMķ{ŌŖsûßúúzx?¸šåžŒĖü�âæęÂŪũ?3wŅ ’/_&77—ĖĖ, ^Críü5'OĻ|Ų2–đāYŌ¯RžÄÅÅSģF8‰IhTŋāR› ren@NƞÆŪΎ !A–6ƒÁ@hpqgÎųw3›Í–íNNŽ–į°´wöü6ŸëÚōëMOĪĖ>ŠxüEeggGNN. —­&îĖYŌ22āę°´´ô}¯ŋ°ŨÅ9˙Žkgfâ)PāßaĐ-ΌAūō*{{{Kø€ü@öĐuw`+jm6ęÔé3thĶĸ@{XHūŋũ¸3ņ–�âí]Ę>�\\œ-ãžKH |š@ËvƒÁ@PŲ2Ä^}MMlØŧã‘'HIM%//˙ŗwßQQ\Įŋ,,KUĨˆ‚ĸ‚ĸˆŊwMŒ5ąFĶcbLōKīɛŪ{oĻcc×ÄØc7ą÷‚ƆˆŌë˛Ëû˛Š€ĸâĸÉķ9‡sؙ;wîÜ]tž{g˛˛sđ­éSb˙õƒƒÎŲ"""WģŒŦė+û HB š†ĐįäærčđÆŪ[ōļ–æ‚ŌŌŠŽ’tęĐW–._É7ßOÄbąĐĒe$ˇŪ<šęÕĒ‘••…Īųoí[|ÂT—ŗNūMÎE¯ŗrÎæōōķ(,,ÄõŦ}¸ž:yĖÍÍĩ-s>㤧¤Âķî§29mãŨΧs‡6lÜēđƍ¨Yž.ŸoMü|kŌŗk'rķō˜>gËW¯Ĩu‹‚ëŦšÎšŒ~v֙>gļlgÔĐA„Ô Âh4˛dųj6oÛQĸœĢKÉ÷377¸øc<úė+%–X,¤edŸŸ€ŅXōĪį|'öšššX,ĨŽ&YŦVÛÕ˛Īž™ČĄ#qļu/?û>§†°999–Ēķė6�žú|Tôø+*1)™ĪÆ˙@hÃúÜ6z8ÕĢyRXXČ oŧ_Ēl™ŸŨSÛÜÜŧRWĪ~/Ī–“S"ô_JÛ čdžÕjeūâe,\˛ŧÔú´ŒŒsË)šyş…’eÎü,X,žøöGŦV+Ã¯īŸo- ßLüĨT}gEDDūœ\ÎņŸzUÚCjZĒm8•ĢĢ+Ąpû­7—*[üÍ/@Ë‘´lInnÛwîdō”éü0ņ'yčxzz’““[jû •››WâuqŽįöƒÉŲ„Áā@NNÉoe‹ë(ūvõjTXXȜy‹kԀŊûöK“°ŠßFŲbąž‘‰Wę%–ģ˜L ēŽ6o#.>Á@ÎÅjĩ˛vãŽëŨļ­"mËĪ xåqu1R/ˆM<š�� �IDATŅÃJ‰2™œm'¤9g}˛ĪķŲruqÁÉɉgšŋÔ:ƒĄ(=>¸ÄįĢúEŪæøRŽŋ<›ˇīÂbĩrĮÍ#m7uHIMģāzœK÷ŨyÚåáîN^^>………e^UģĐļ9;1 ôėŌ‘ŽíZ—Zīéé^‘Cą…ĸŗ˙]9ķŗpđpņ ĮK=$33Ë6$KDDäŋÂÅŲxeNBĪĘĘæĮŸ§PŗĻmڍ‡Š_„ã‰øÖōĨv@€íĮā` FõĸoˆˇlÛNbR2P4DĨ}Û6ôčօ¸øx�‚‚ęōĪ?ÉĪ7Ûöõ×ÚuŧųÎûX­ŋĒOff–íõÁC‡�đ?c¸Wņäėŗ999Rˇn]bcKŽŨ_ôē~ŊznĮ•fŚĩœHJæÎ›GĐĢ['~ųšyyįßđ”YsōöG_’YÆ°Ā„ãEC]ʛWs6ka!VĢĩÄ­Üŧ<vėŲK9oMpP“’Šéãeģãį[ĒWķÄÉÉ o¯ļaSÅöÆė/§ÆSõÖ­CAAÖBk‰zF'ÛpŸĀ�Ôļũ89]ÜEĘK9ūō˜Í˜œļ|€ [Š$ZxWåü|k,ĄÄßČŲs&ÎV70�‹ÅÂÁç¯%?ÁģŸ|Íąã'.¸mÕŠMrJJ‰÷ÂĮĮ GGĮķ^ĩËŠaqGãlË, 1˙°Ŋ.žqû_.8t„ä”Ô‹~/DDDŽfU@ōōōˆŠŪGTô>víŪÂE‹yáå×IMIáūqcm'={t#/7ī&LäĐá#$$į÷?æķ܋¯pā@Ņö‹—,åĢņ߲7:†‰IDEīcÃÆÍ„…Ũúļg÷ŽXŦŒ˙n1ąûŲ˛m;ĶfĖ" Āö tE¸šš1áĮIįĀÁCL1 ??_BOŨîĶÍŨ•´ôtĸ÷Œ”œ\jûëŽíÃļ;Y¸čO’’“Ųŗ7š_ĻLŖqX#ę×ŗī¨+"7/=Ņ1eūDšĀŸ|2…š —2d@_ÜŨÜčwMOømŪb[=ŋÍ_ĖŧÅËĘŨO¯npträÃ/žaÍڍÄūs}ą˙°dųj~˜<ĀÚū„‡UėIōNŽŽÖögũæ­$%§pôX_Oø™ĻCÉÎÉáxbR‰šgęŌĄ-yųųü<u6qņĮHLJfáŌ•ŧņÁgļáQ­[D°c÷^ū^ŋ‰ø„ã,]õqG•Y_ą°F!ÔŠĀŋÎ öŸƒ$§¤˛iÛŪūøKVũŊĄBĮUQ—rüå ĒKfV6ë6n!-=ƒÕoāĐá8<ÜŨ9ŸPęĘ`yZˇˆ #3‹YsŸpœí;÷°aķÖsnÚ0„Úū~Lž>‡¨}ąė?pˆ_gūŽŲlƯV͋j[Ÿî]Øž+Š%ËWs"1™¸øcLúu&~ųm…ƒŗˇW ęÕeņōUDí‹%.ūŋÎüŨvW+€ĀÚū899ąbÍ:ŌŌ3ˆÚË´9Đ8´'“Č8ãË ‘˙‚*ŸrâD"īŧ÷!P4 ĨFu/""š2hĀuļį{�Ôôņáé'eÚĖŲŧųö{ ŽÖæ‘īˇ=ãžqwķë”é|ųõx˛sr¨^­‘Í#>Ŧ薖>ŪŪ<öđCL›1“w?øwwÚĩmÃđ!įžûĐŲkŲŦ}úŠŠŠխ˃÷ŗ iߎ-kūZĮ{~Ā~×ŲŽâëØžųųų,\ô'ĶgÍÆŨ͕-Zpãˆ+ķ)ßŎ;-‹ƒƒŸžķ “güFPÚļÛ9Œŧa _˙đ3-#›Ú >1ûžsž„ˇ˙o,KWūÅŌU‘–žAaa!Ū^ÕéÔŽ5×ö憪céyåšeÄ~™>‡7>ø ¯ ŧŽõ‚ęđĪÁÃŧ÷é×<ûØenįíUƒ‡Æáˇų‹ųđ‹īp4đ÷eÜ7ÛæÂôŋĻ'™YŲĖūcÖÂBš5 å†}ųū§)å^ũ2 Ü÷mĖūc!ßNú•ü|3>Ū5č×§=ģvĒđq]îã/ODxŊģwfÎŧŘģ€ĻCšuÔP–¯ú›%+Ö`0¨pŪzš„6dč ~,]š†5k7R'°6Ŗ‡_Ī;Unß988pī˜[˜>gß˙4ƒÁ@ŖzÜ>z8ƒĄBm;ķŽl�‘áÜ6zK–­fŪâe¸ē˜¨ÄÃãÆ”šįu.wÜ<‚ÉĶįđÍŋāââB—Žmi×*’mģŠnÅëáîÎ-#‡đû‚%lØŧ ēÜzãPRĶŌųáįi|:ūîšũĻ īODDäjįpįwN˜0á’*Ųē;†@˙Zį/ø/đųWãÉĘÎáéĮŠęĻ\Ŗ ‰´lZąĢ"""""gÛē;æ’Ī'ĮŒSõC°DDDDDäŋCDDDDDDėĻĘį€\m¸o\U7ADDDDäĒĨ+ """""b7 """""b7 """""b7 """""b7 """""b7•@°–ķcšēå› p6VÎ t+%€¸ē8“™™]U‰ˆˆˆˆČ&9% WSĨÔU)$8ПĖė2˛˛ąX­•QĨˆˆˆˆˆTą|s ‰'9‘œJ�ßJŠŗRŽŖ¸˜œiÜ ˆCGHLNÁbQšmŨSÕM‘ĢˆŗŅ WMWÚŦJ{ē‹É™° ĘĒNDDDDDū…t,ąąąąąąąąąąąąąąą§Ę¨$>>ž2Ē‘+XíÚĩ/šŽJ •Ņų÷Ķ,ąąąąąąąąąąąąąąąąąąąą›Ë@&ũ<™ĐpBBÃy˙Ï/įŽJ™ŋp!Ąá 1ĘŽû­ >ū!Ąá|5ūÛĒnŠÍÔi3 įö1cĢē)""""rģŦdĘ´éļßg˚ÅbŠô},X´˜ĐpöDEUzŨ—Û˯žNdĢvUŨŒRRR gÂÄIUŨųrē\oßžƒŊ{ŖŠ_/wwwvíŪʕĢčŨĢgĨîgĮŽ•ZŸ=mßšĢĖåīžũoŋųNN—íí)׎e÷įđaC¸áúA ĩ'""""ī˛M_ũčßī:ú÷ģîÔ˛•žŸåœÄ_éĖf3Q{Ęžjc41™L8::ÚšUå÷§ŖŖ#&“ ŖŅhį‰ˆˆˆČŋÉe ™YYĖˇ�€ëäúÁqpp`ÅĘUœ8‘Xæ6&N"$4œ{î{ Ėõ“~úĨÄúâ9 k×­`āõà į÷>(ąŖÁ€ÕjåÛī&pmŋA4nւæ-ÛrËícØŧek™ûJJJâ­wßįš~iÜŦÍZ´Ą˙ øôķ/ÉÎÎ.Uūņ§ž!$4œŲs~'ųäI^|ų5ēöčCXĶHÚvčÂŖ?EÂņãļō>ūaM#É7›ÉČĖ´Í“Yšjĩm}ys@ļmß΃<N‡.Ũ oN‡.Ũyöų9~üDОVĢ•ŠĶg2ęæÛhŲļ#šDĐŧe[†ŽÍä)SąZ­ļ˛"$4œ>ų €×ß|ģÄšsÍÉĘĘæË¯ŋaĐ ÃˆhŲ†°Ļ‘tî֋G{˛Ė@sĄũ%""""˙—eŒĪÜšķČÎÎ&2˛9 4� k—ÎŦZŊ†ŗfs˙Ŋ÷\ō>7cÜØģ˜8égōōō>t>>Ūth_rN…ĢĢ+O>ũ‹–üIĮí mƍ›ø{í:6mŪÂŋÍ´ĩ v˙~nēõN’’’Ē[—ú‘››ĮÆM›øøĶĪųcŪĻLž„ˇ——m“ɉÄDFŽž…Üœ\ZĩjIZZ›6oᡚ°c×.üņÎF#Ŋ{õÄd21mÆLœFîŧã6�‚ęÖ=į1Μ5‡§Ÿ{žÂÂBZD6§yD11ąL6ƒyķ0cÚ¯„6jh+˙ôsĪ3sÖŒF#íÛĩĨV͚$%'ŗvŨzļmßΎģxûÍ×�¨^­:ãÆŪÅâ?—rāĀA:uė@DŗĻÔŽ]ûœmJIIåĻ[o'z_ 5}|čÜŠÕĢU#&v?ŋ˙1?æ/āũwßâ†Áƒ.ēŋDDDDäßã˛âáW7f[6rÄ0V­^ôé3šoÜX.i‘Í#ˆlÁÔé3ČËËãŽÛo!ŧI“RåvėÜEí�ÍûĀĀĸ“éôôtFŒē…˜ØXĻLÎķĪ=]1xđáĮHJJbô¨‘ŧúŌ ļaP™YY<ōØ,[ž’W_{“?|Īļ'Įĸnüjüˇôč֕wŪzŨv’Ŋ/&–ƒ‡pāĀAV¯ų‹Ū={0p@?5jČ´31™L<ũäãį=ŪC‡ķüK¯āččČÄīŋĄc‡öļ6ŋõÎ{|˙Ï<ôČc,œ÷;P¤fΚƒƒƒͧüBķˆfļēvī‰bČđ™6c&wŪqaĄđōĒÁĶO>ÎĄÃ‡9pā Ŋzö`ĖŠ`t.¯ŊņŅûbhĶēŋ˙777Ûēi3fōĖs/đ/ŧLįŽŠUĢæEõ—ˆˆˆˆü{rķ͕Zឨ(vîڍ‹‹ ôŗ-īĶģ^^58|äëÖo¨Ô}žKFFoŧöŠ-|�TĢV!7 І[šj Ņûbđõ­ÅKĪ?Wb†‡ģ;oŊūŽŽŽü1'SRlëŠŗ”ÁāĀk¯žd;™mԐ.;ĩ÷ĸcúŒYäåå1xĐ�[ø(Ú§Įyˆ:u1 =€§‡'Ÿ}üŧ÷v‰đĐ4ŧ -[D°y˖‹nĶɔæÎ›ĀĢ/ŊP"|�Œ>Œđ&MČÉÉaÎīsmËíŅ_""""råÉÍ7cđtw;É đë”âÉį}ņôđ°-w6r}ŅI˙ÔË0Ŋ<5jT§UËĨ–ûųųœ|Ōļlíēu@Ņp1ggįRÛÔĒU“f͚bĩZŲ¸qsŠõíÚļ-qĖÅj×� #3ķâXļb%�:v(ĩÎÕՕU˖0î[Đōķķe@˙~ļĄOYYŲ9ĮÁC‡8xčîîî�¤§g\t›ļl؊ÅbÁĪĪ—ÆÃĘ,ĶšSQ{ˇnÛVjŨåė/šōxēģUîŦœœ~ŸûPôí÷Ųn9œ 'ąpņŌŌŌ¨^ŊzeîžLuëÔ)syņˇîg>›$îčQ�vîÜÅO?[ævI‰I@É+'Åę–=_Âéԕ”Â3&}_¨ŖqEm đ÷¯đ6ąû÷ķÉg_˛jõ22Ę………ŨĻâū*¯ˆ?VjŨåė/š2Uj�ųcūÛˇÖ~üi™eÉĪĪgÎosšũļ[*s÷e2\Ā­lŗ˛Šîpĩ/&–}1ąį,›YÆˇķ—ķĩyyy�~6HTÔ^FŒž…ėėl‡…ŌŋߝÔĀÅÕ€&N*÷.`•““ €‹‹ŠÜ2ÅA/77ˇÔ:ŨŌWDDDäŋ§RȔЧŸ|žaãĻs–:}Ɛ|såÎU)‹ûŠáh÷Ü}Ī<uū‰áöäęæF~Ziii*˙Ū‡“Mßkûđŧ—z€āœßæ–ŗeÅššē§ƒHYrsrŠĘēš_ōūDDDDäęWi$z_ [ˇmĮŅŅ‘ĩĢ—SŗfÍ2ËedfŌŽcWöFīcûŽD6�Š&$X­–2ˇ‹‹‹ĢŦĻ–+8(€Ŗņņ—}_*¨nvĻĨwj(ÖŲrssąXŦ˜LÎ899ąuÛv�n=ē˧—GīÛwÉmĒ{ęļÁ‡)ˇĖá#q§Ę^ōūDDDDäęWi",žõnn]Ë �žôŊĻPt¤Xņ¤č„„ŌÔ3›ÍŦ\ĩæœûŋ„Š 6ÅŧWŽ\Uîč‹ģ˙Ĩīė”ŠÎÁčpęÎW -.ĩÎbąĐš{/"ZļaĪYwŽ2™JOĻ_¸h GNƒōö_‘vĩjŲŖŅHbbģvī)ŗŽUkŠŪˇíڕZ/""""˙=•@ōōō˜=§čųÇ =oųáÆ�0÷yļ'‹‡…†EˇņŨ¸éôĻĖf3¯Ŋņ6Šå =*.Å'ԗĸs§Ž„…6"3+‹gž}žÔŧ…ŠĶgōŋáĻ[ī,sNÅđ8ÕîĖŦ,RRRĪ[ūĻQ#1lظ‰i3fږ[­V>úä3RRR  "ĸYS�5,z¸âŸË–—¨gįŽŨŧōú´kÛ€„„’OˇõgŽ8Õ¨QŨvgŗW_“œSíŠ}÷ũėß˙>ŪŪ 4āŧõ‰ˆˆˆČŋ_Ĩ ÁšŋpéééxyÕ WĪîį-ߊcüũ9–Āŧ 1l(Í#šŅ"2’mÛˇsķmwŌļMkŧjÔ`ĮŽ]˜Íf{ä!^zåĩRßĖ7o֌ŖGãyúŲ˙cĘ´éÔ äõW_ē¨ã0 |úņÜrû,ZĖÖmÛiߎ-PôđžØũûqqqáãŪÅÅÅåĸöQĖßߏš5k’””ÄāĄÃiÂĩ×ôæĻQ7–Y>8(ˆW_~‘g˙īžyî&ūøūÄÄî'.î(ŽŽŽŧ÷Λļ<Ž{›6oáÛī&°wo4õëķ΁ƒüŊvO?ņĩ|kŲŒÅjáÆÃiҌˆf͘9k?˙ō+ąąûÉËËcú”_Ę=Žįžy’ģwŗiķz^smÛ´ÁÅÅDTT4{ĸĸpuuå“Ūˇ.ųoĢ”+ ÅĪõ¸~Đ  ŨŲČ`00tČõ%ļøū›¯1l(5jÔ`ãĻÍlŪē•ŽíÛ3kúę× Nß ĒØŗO?IģļmČËĪgëļíX /íÖ­6dūÜ9Üs÷]xxx°pņæ/\Dn^.#† å÷ŲĶË|Į…rttäÃ÷ŪĄ~ũzœ8‘Čž=QįŊÃՍ#†1cędú^ۇ¤¤dV¯ų›ÜÜ<n<ˆšŗgĐĻu+[ŲŪŊzōŪÛoŌ8,”uë7đûķ0›ÍŒ˙ō3ÆŪ=†ūũŽc@˙~8::˛pŅb˛˛˛�5r8CnŒģ›[ļn+ķn_gĒV­3Ļü=BMŸš,[ž‚šsį‘ž‘ÎčQ#™?wvĨô—ˆˆˆˆü;8ÜsĪ=…ãĮ¯ęvˆˆˆˆˆČŋܸqã*o爈ˆˆˆČų(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ8UF%Vk!{cwė8šyųÚÆÅäLÚ~4nPƒÁĄÔú{î{€?—.+ąĖËĢÃÂxøÁ˙ŅŽm›ĘhúoūÂE<đĐŖlZ˙Ū^^UŨ‘KR)dīūƒddfŅĩ}K\]LÚ&'7í{bˆŪ&ę—Y&¨n]Ū~ķ5Û뉉Lūu*ŖošYͧŲ<ĸ2š/"""""vR)C°ââĶ<ŧQ…Ã€Ģ‹‰ČđF‰?^n7w7:´ogû<p�?Nø_ßZLüņ§Ęh爈ˆˆˆØQĨÜŧü Å\]L˛UĖd2Ņ8,Œƒ‡Û–íØš‹[Ví:Ō4˛57 ɚŋÖÚ֛ÍfŪ|û]:wëEãĻ‘tęړ×ß|›|ŗųŧë'O™Jãf-leūīŗ 'v˙~Û˛_~Bķ–m)(( ??ŸˇŪyN]{ۜ.Ũ{ķū‡SPP`+ßē}'&LœÄwŖqĶH222(((āĨW^#˛u{"ZļááĮž ##ķ‚ûUDDDDäJU)C°ėíH\Ą——ĮwĨUËüüãŒF#“§LcÜũ°tņ|üũü˙í÷Ėžķ;ž˙ÁÁAė߀gŸ“Éē?zÎõ#G #??ŸŨģ÷вE$�ë7l$Āߟ7ͰA�6lÜD‡íqrrâégŸgņŸKyí剈hĘÖmÛyáĨWČÍÍåųįžĀh4ōëÔiôîŲ“˙wŽŽŽ|ũÍwL™6ƒ×_}‰ļmZŗæ¯ŋųėķ/ĢĻ“EDDDD.ƒ+>€œyÕ )9™'ũĖ?˙āĨįŸĀ`00ų§ņķõÅËĢ�?ú0?Nú™Í›ˇ0 ?öFī#,,”Ž]:EsK~ųņ †ĸ @įZD``m6mÚLË‘$%%qčĐaîŋ÷6nÚĖčQ#ظq3÷ŽKJJ*ŗæüÆŗO?ÉĀũ� b˙ū˜0qO=ų8ÎF#¸ē¸đĖSÛŽoÖė߸Ļw/F  @Ŋā`vī‰bę´—ŗ‹EDDDD뿊ž īŪŊŅ„†7ˇũtęړ_§Nįˇ^ˇ…ŖŅˆŲlæĨW_ŖĪuh׊Ŋú\@jZ�Ŋ{õāīĩëxđ‘Į™ŋpiii4hBũúõ*´žsĮŽlÚ˛(ēúŅ4ŧ ;udãĻÍ�>r„„ãĮéÚšQ{÷bąXlWKŠE4kJNN˛-kÕ˛…í÷|ŗ™ƒ‡•šXß"˛d="""""Wŗ+ú HũzÁ|ôÁ{ļ׎Ž.Ô Æh4ږ8xˆ›oŊƒŽ;đŅûīâįë‹ĩĐJ§Ž=me†\?O&ũ<™Įžx‹ÅÂ5}zņęË/RĶĮįŧë;węČ+¯ŊĀú ›h×ļ ‘Í#8‘˜Hüąclظ‰ÚÔ¯_˜ØX�<<ÜK‹ģ{ŅëŦŦ,Û2OOOÛī9ŲŲ�˜\\Jnįæv =(""""rešĸˆÉŅæÍÎYfŪüX,|úŅû˜LEáÆĮ—*×§w/úôîEvv6ËWŦäÕ7ŪâŲį^āÛņ_žw}ĮŽí9™’Yˇ~O>ū&“‰fMÃŲ´y 6lĸKįNĀéP‘™™Ub˙ů===Ę<WWW�222J,OĪH?įņ‹ˆˆˆˆ\MŽč!X‘——‡›ĢĢ-|�Ėųm.�………�,ųs)qqGpssc@˙~Œ9‚ŊûöUh}M‡…˛øĪĨė˙įÚ´n @›6­Ų°a7mļ&ãččČĻÍ[J´sËļmxzxP/8¸Ėãpvv&0°6QQ{K,_ķ×ßß9"""""W˜+ú HE´ˆlÎ_gúĖYtīڕEKūdûŽøx{ĩ7šŒĖL&LœDnn.Ī>ũ$ūūgŪ‚…´o×āŧë:u뤟~ĄaƒļÉîm[ˇâåWßāXB:u� FęŒ>”¯ÆCŊā ÂÛ°~ũF~úy2÷Ü='§ōģ|đĀ|7a"SĻN§eËŦZŊ†Ũ{ĸ.cī‰ˆˆˆˆØ×U@z÷ęÉØģĮđöģīķúoĶŖG7>x÷-ž˙áGžūæ; Ž|öɇŧūÆÛÜ˙ĀÃddfRĢfMzõėÁ“?pŪõ�;=ˇãæŅŖlËZˇjEüąc„7i‚ˇ——mųË/>‡ģ;/ŧü*ÉÉ' đįûīåžqcĪy,?ø?Nž<ɛoŋ‹ĩ°ž=ēķĖSOđŋÁjĩVrΉˆˆˆˆØŸÃ=÷ÜS8~üøKĒä÷ÅĢ|m7ģo+"""""WqãÆUΓ39šyŧ]Nn.Ļ ‚ēˆˆˆˆˆ\*%€Ô­íĮö=1BrrķØž'†ēĩ}+Ŗ """""r¨”9 a ęŊ˙ Ģ×o%7/ŋBÛ¸˜LÔ­íKXƒz•Ņš TJ�1hŌ¨>MÕ¯ŒęDDDDDä_ęĒˆˆˆˆˆˆ\=@DDDDDÄn@DDDDDÄn@DDDDDÄn@DDDDDÄn@DDDDDÄn@DDDDDÄn@DDDDDÄn YŲUŨųČČĘÆāâlŦęvˆˆˆˆˆČ€‹ŗQC°DDDDDÄ~@DDDDDÄn@DDDDDÄn@DDDDDÄn@DDDDDÄn@DDDDDÄn@DDDDDÄn@DDDDDÄnœ*ĢĸÜŧ|M /ߌÅb­ŦjEDDDD¤Š8pu1Q'ĀgcåD‡JŠ%7/Ÿčũ‡ņôt§š§Ž]X‘Ŗ ‰ú×ĒęfˆˆˆØ]ĄĩÜü|öÆĸqÃāJ !•’MĀĶĶ7W…‘ ƒŽ.&ĒWķ îØ‰JŠŗRŌBnn>nŽ.•Q•ˆˆˆˆˆ\a\œÉÉÍĢ”ē*%€X 188TFU"""""r…q08o.¨”ē4^JDDDDDėĻŌî‚%"""""UŖĀbáäÉTŌ33ËŊ#­ŖŖOw|ŧĢãäTu1@DDDDDä*V`ąp$îūžŪÔĒąœ6ß/�� �IDAT;U™Í$%§pähuëāäčhį–Q�šŠ<™Š-oüÎ}ËxŖŅ‰�˙ZÉ'SņĢåcŸžEs@DDDDDŽb陙ÔôņĒpųZ>^ddf]Ɲ›ˆˆˆˆˆČUĖbą–;ėĒ,FŖ‹å2ļčÜ@DDDDDū…ļoßÎöíÛĢēĨTéO>˙Š­ÛJvЧ‡uęrũ 4kTE-ŗ¯›6ķÅ×ßōéGīSÍĶŖĒ›SĻ?—­`ō”ŠLøæ+ģī;=#“‡}‚˙Ũ;–ļmZÛ}˙<ō8×öéÍāũ/ûžŦV+ī}đ1ĩ|k1æö[Xüį2–,]FJJ 5}j2h`?:wėpŲÛrļŗ?Ÿ5žŦėž~ü‘‹ŽķrõíOŋüĘŪč^}é˙xûŊŠ['ÛnšŠR÷QQģ÷D1}ælŽĮÍŨ.;0tČ`Ģh⟈ˆü7lŪ˛…˜}û€ĸIę­[ĩĒâVå“Đ}}kqįŠ-€ÔÔTVŦ\Å;īČ Ī=MHũzUÖ69­qX#nģšjNāėíĄGŸā…˙{–Z5‹&f9œ:ĩí˛īßæÎ#5-Gú�+VŽfęô™ r=!õëŊ—oŋŸˆ›Ģ+-[DÚĨMåéŅ­+æ‚Kģ|{vߞŨ÷—ĘŅŅ‘{īš‹į_z•ĐFčĐžmĨÔ[Qqqq|ôéįt樑‘#†‘”˜ÄÔé3)°Z=r¸]Û"""˙[N…ž}û°hŅ"hÕ˛eˇŦH•“ÉD“°ĐËZˇlÉĶĪŊĀ’?—1nė˜*j™œŠN` uís^•’’“IĪČ,ąŦK§ŽvŲ÷ɔæ/XÄ=w߉Édĸ°°?æ/ w¯îôŋîZ (;–ĀÜy Ē<€4k~ÉuœŲˇeõ}eđņöĻī5}˜6c6­[ˇÄhĮûžĪ_¸„ĀÚĩšķļ›qpp€ÆaX ˜<eƒöĮŨÍÍnm‘˙Žô´4úö틗WŅÄôž}û˛}Ûļ*nÕiU@Ęâėl¤nŨ:?‘h[vāā!ĻΜÍá#GČ7āĪđĄ7Đ4ŧ �fĖšÍ†[ČČHĮĶĶ“6mZ1bØŒNNį\ŋæ¯ŋ™<e:_~ö‘íädâ¤_Xąj5oŧö�,[ą’é3æđų'īcĩZ™9ûw6lÜDZz:5ĒWŖc‡ö š~mhя<Á ũØĩ'Ѝ¨Ŋ|ōŅ옜MLž2ĩë7`ĩAx“°ķöɊUkXōį2“qvv&,4”›GĀÛË€ôô ĻL›ÁŪč2ŗ2¨X‡ÇØÂŨĨôŅÉŠĖ!X+W¯aŅâĨœH<Éd"ĸiSFß8‚ęÕĢ˙cO2¨?Nž<Éú ›ČÍÍ#,´!wŪv ÕkT/˙XWŽæ÷?‘™ApPÇ^_bũGŸ~ĀŖ=`[ö÷ēõ|ķŨ|ũųĮ¸¸¸đÅWßā`pĀßĪ…K–rß=wĶ22‚ĩë7°pŅŽŸĀhtĸaH7‰¯o-ĸĸ÷ņÎ{đä3˙GË‘<üĀ}Ĩ† ÅÄÄ2}Ö:Ô fÄđĄļĢu_Ž˙–ÂBˆh֔ų ‘’šŠŋŋˇŨ4Š BĘ=˙ÄËۋ6­‹.‘?~‚¤ä“´lŅĸDš‘ÍųæģČÉÉÁÕՕé3fŗ`ņâr‡Į8xˆW^‹—ž–úõ‚m˟zîZĩˆdÔČá=vŒ˙{ážzâQ–üšŒ˜ØX ÚˇiÍMŖFb0”ž.væŦâíŸxô!æ-XÄÁƒ‡quueÄđëņõõåį_ĻpėØqjųÖäÎÛnąõUqß6jÔ°ĖžˇX,ü>o>ë×o"ųäIŧŊŊč{MozõčnkGJj*?üø3Q{ŖquuĨgŽĨÚzMŸ^Ė[°ˆĩë6Đ­K§r߃Ęļ;j/=ēw) §´ˆŒ`ŌĪŋ˛o_L•‡Hųwpt4`6Ø&ĸ÷čŲŗÄz//¯ËĖæ‚*{\Ą 11‰ĀĀĸ˙ü|3|ü) CBxōąGprrdÅĘÕ|ōÅ×ŧũúËx{y1á"ūZˇžqwŨ‰o­ZKHā‡ÁŲčÄđĄCΚž[—.˜Íf:l;AŒŪƒˇ—71ûbm$z_,‡âččȏ?ũÂæ­Û¸íæŅÔ¯Wũ˙āĮŸ&“o6sĶ#�pttbÅĒ5´ˆŒ`đĀ~˜œMĖ[°ˆ•Ģ×pÛ-7Ö¨!ģŖĸø}îŧsöEôžX&Nú™;oŊ™&M“‘•É´é3ųâëīxáŲ§°Z ųđ“ĪČÎÉáî1ˇQŊFu–/_ɇƋĪ?CŨĀĀKęŸáC‡”jĶ_k×ņÏ?3lč ´mՒԴ4~üi2}ú9/=˙,8Y°p1CnĖûīŧAZz¯ŧūŋũ1¯ÜņøŅûb™øĶ/ôŊļ=ģuåDb"SĻ͸āĪŖŖ#‡!?/ŸĮz€ĀĀ�ū9pņßN`ā€~ŒێÜÜ<ĻΘÅg_įĩ—ž§QƒÜ7înž˙¯ŧđžeÜK;!á8ī~ø ­Zļā֛F0sÎīŧûūĮŧųú‹x{yãhpdĪŪhÜŨÜxų…įp08đŲ_ķũğxķĩ—Ęmķö;iŪŦŠídõØņ�øÖĒYĸ\ņë„ã'¨_/˜€Úū4ˆ¸ā>:““ĄčĄ_§Nįö[FͰÁ}ė‰ÚËģ|LŖF ißļM…ļŸ5įwîs;ū~~|ķŨü8éW6 áĄîÃÃŨ?ųœŸ'OåÅ˙{ēÄöåõũÔ3Yšr ˇŨr „°;*ŠÉSĻáččH÷Ž]�øæû‰O8ÁŖũę5ĒŗlŲ 6m؊‡ûéųTînn4 ŠĪö;í@rssIKKÃˇVÉĪ‘ˇ—7F''ŽŸzEDD.•§‡‰É)Ôö?÷s@Š%&§āéá~™[Už+â.X‹Åös2%…é3fs,!ŨŠžÉ4 <ķÄŖÜ5æv‚ƒęXģ6C‡ &?/˜Øũ�Ä=JĀ@š5 Įס‘Í#xꉇéŌŠĶy×ûúÖĸfMöÅÆ––Îņ'čÚĨ#Ņ11ļvÆÄÄŌ4ŧ ™™Ŧų{×@ûvmņõ­EĮíčͧ'+V­Á\P�€ƒCŅ՜‘Ã‡Ō°Aųkí:ZļhAˇ.đķķĨWî4kÖôœũs4ū(ÎÎF:w.jkƒúõšÜ=Ü4Ē(čėŪŗ‡ƒ‡sįíˇŪ¤1Ü4ęF|ŧŊYōįōK,Zō'-[D2¨˙uøûûŅ8,”[nē‘ƒ‡{ę=đ§[—N8::âíåMdDS<TîąūŊnÕĢWãÆáCņ÷÷ŖyD3ú^ÛûœũSN$&r÷˜;hÖOüũxéųg2x ĩŠ_k¯íÑ#q¤Ĩ§ãääˆĢ‹ �nîî¸ē¸–ĒwŲʕ¸˜LŒsuëÖĄnŨ:Ü;v Ģ…ŋūZo+—Ÿ—ĪčQ#pq1arvĻc‡vÄ;F^^^™íMOĪāøņ4jÔĐļ,77�WW—eM§Ú˜“›  czäÁû/¸ĘŌŽuk6h�@x“ÆÔĒU“ƒW|û6m¨€Á` }ģ6äæåŌŊkgŧjÔĀh4ŌĻu+ŽÄŕڎŦžĪÉÉaŲō•\wŨ5tîÔÁö÷ŌŠcGæ-X [‹ŠÚˀū}mŸũ[nUæ{רQCÛ߸=ŋ?ŽŽĨÛbr1Ų֋ˆˆ\*¯ęO<I|B"fsAšåĖæâIHLÆÛĢüŅ(—[•_9r$ŽģÆũ¯Ä2w77ÆÜq›mŒš““#æ ?OžĘáÃGČÎÉϰ°¨lVVŅCTЇĨ|5ū;Ú´nIx“&Ô>uåĸ"ëÛ4&&v?ũúÂŪ}ûĒKx“&üõ÷Z�NœHädJ Í›päHVĢ•!õK´;¤^0ųyy?~Ü6_ĸáCnĖ?~‚Ũē”ÜŽ~=VŽZSn5i†ŧõîûtë܉đĻáÔĒécęôĪ?1:9Ņ8ôô]à BCqäđ‘JéŸ3X8rä(íڔüVŧŪŠá=‡ŽÄŲN¤ëÖ­SĸŒ››YŲŲåk|ü1ę•ōĶ ~ųÖÎÅßߏ3ŌŊĢĢ+III˘5‡ã'NŸoÆr*,feeQŊZĩķÖyđāa‚ƒƒpr:}ŲŌÅÅ?9b[æëW “ŗŗíĩ›[Q;˛˛ŗ1™LĨęMOOĀëCĶėĄNŨ’ķ|ÜĪķ~Íßß×öģËŠo˙�Û2WWĖf3fŗŖŅxÎēŽŖ ĀBŗđ’sMš„…˛jõrss9v, ÄÍ*¨_?˜Ã‡K5Ē“‘žÅbҍDDä_ÅÉɉēuH>™Jüņ¤rŸņáččH5w‚p˛ãœČŗUy�ņ÷ķ+1ŅÜŲ؈Ÿ¯_‰ŧ„„ãŧûūĮ4iĘ=w߉WęX yėÉgme:uhĢ‹ K—¯ä›ī'bąXhÕ2’[oMõjÕÎģ>ŧqc&O™ @tô>ÂBQŋ^0Šii$Ÿ<ItL >Ū^øûûė�.g}3íręÜŧÜĶßrģœņíg~^>�FŖsÉíĘ8!=S€ŋ?Ī?ûķ.fÚŦŲdũô !õësķM#iPŋ>9š9˜ ¸įžKlgąZ¨^­zĨôĪ™ōōķ(,,,õÍnņ7Øšg|ŗ{ž“ĖŗåäæRŖzɓđ˛NØ+ÂíŦ÷gũ†|õÍ÷ ؏[nē7WĸcbųrüˇÔžęÕK‡—ŗžŅv.÷¸ ËŽ7'÷T›O÷Š›kŅåėėÛī�9§{ßŦ_ǞÛ]v›ËRÖûíėTzYaĒ,îĪwŪ˙NĪĄ°žÚ8--ŨöY;ûáK.Ļ’ī=œž‘““["˜^.ÅWar˛sJ,ˇZ ÉÍÍ-ņ^‹ˆˆT†ĸ˙-ĪõŸla…ūžÜĒ<€%&ƖeÃĻÍX­î{7ÎÎE'3IÉÉĨĘĩlIË‘äææą}įN&O™Î˛ŨŌô\ë›4 #=#“„„ãDGĮ0lØ 8; f_L,ûöÅŌôÔ™âP‘›SrENN҉†‹[Ų'Åm/.W,ûŦ×eŠ[ˇãÆŽÁjĩ˛/&–™ŗããOžāÃ÷ŪÂÕÕŖŅČ+/>Wj;ƒÃé+ —Ō?g29›0J‡í$ēœã¯“ÉTĒ?˛sJ~gũq™O…ģsYąú/š4cØ §'ĩ›Íæ jŸ›ĢKŠã†ĸc÷ĒQã‚ę:Sņ0Ģ3=ĀŋčĘÁ‰‰Ôô9}[Úc Į1đ÷÷ģčũäį]ØąÛ[ņ ú=wĄNÚĨÖ{{{‘˜”œūė+ëoĒø};{HÛåââbÂÛˋ„ĮK,OJJĸ ĀB``écš GâŽáīëMũ Úå>Ũl. )9…#G¨[' Ę&ĸ_s@Î'ßlÆdrļĀŦ]ˇĄD™-Ûļ“˜TJ\\L´oۆŨē_ĄõÕĢUŖN@6oÛF|Bƒmؐ}ûb‰Ž‰Ąi“Æ�ÕŠƒÁ`°Í?)ģ˙�ŽŽ.øûúRŖŅH͚>>RrhČî=Qį<ūØģŋh_ƒÆaĄ ša0™™¤Ĩ§RŋfŗĢÕJí€�ۏŗŗ3^Ū^•Ō?grrr¤nŨē%æzŅëúõęķxÎ%Āߏ#qGąZOŒŨ{ö–(ãââBöYß*.c^ÁŲ Ėf<<J>čqíúĸĪŅŲ_–ķõ@Ŋzõ8xčmž ĢJHH8o>ĪSW™RSĶmË|}káįįËæ-[K”Ũēu;aĄĄž2T|e*ûŒĄTiéé¤ĻĨ^t{/§âžĒˆŅɉôŒŒŸkw<==1ļvæßTA…ŊŅŅĨęMMMŖš§‡]‡_5k֔­[ˇ—ø<mŲē “ÉTbžˆˆČĨ8y2˙ZŪøÕ*7|@҈�˙ZøÕō&ųd՝\¤AH}Ō32YĩæoRSSYē|%˙8ˆg5OŽ#''‡ÅK–ōÕøoŲÉÄ$ĸĸ÷ąaãfÂN͋8ßz(š˛téJjāyęD5´QvėÜIbbMšŨō×ÃÝŽ]:ķĮ‚…lŲļ¤ädūú{˖¯āÚkzŸķ§Cģ6lŨļ•+WsäčQ.ZÂĄÃGĘ-°k×n>ũü+6nŪ‰‰:|„?—.ŖfM|ŧŊiŪ„ā ē|ķũėŪGbR2k×oāĨWŪ`Ųō:ūŠôĪ™ŽģļÛvėdáĸ?IJNfĪŪh~™2Æa.éDŧCģ蠟§ķë´éÄÅÅąiķVūúkm‰2õ‚ƒ9pđ GŽÄQXXȎģØš{Īyë ŠĪî={ˆũį�IÉÉüøĶdjœšsqđā!ōōķqs/šŗcį.Ž–žz÷čN~~>&N"!á8qqqŒ˙vŽ.ŽtîxņĪ Š^­~~žÄœ5Izđ€ū,_šš?æ/doô>ĻL›Éöģ<h€­Ė_k×ņŲ—ãË­ÛĮĮOūú{‹…Ŧėl~ųuĒ]†!]ˆŗûŪÕՕîŨē0į÷šŦß°Ņöš|īŖOønÂD�júøĐ Aķæ/d×î=:|„‰?ũ\æ7:1ąûiÔ¨ėĪķåŌ¯oN$&2aâOėŲÍŌå+™ũûôŋîÚs„DDD.Ezf&5}ŧl¯W,_NJJŠíuJJ +–/ˇŊŽåãEFf–]ÛxĻ*‚U-#›Ķ¯īĩLŸ1‹_§NŖyDcīēEK–2Áb îw7ŋN™Î—_';'‡ęÕĒŲ<‚áÃn�8īz€ĻMŗxÉRzEvŗ-kذÉ'SĒK5ĪĶߞßrĶ¸ē˜˜ô͝¤g¤ãííŁũدī9åúAIĪČbĘôYX ­´hÁČáCųâĢo(,´–šÍ ũ(((`ęô™¤ĻĻâæęJƒ xôápppĀÁÁĮyŠĶfōųWãÉË˧V͚ ؟ž×öŠĐņW¤ÎÔą};ōķķY¸čOĻʍģ›+-Z´āÆC+đŽ–¯YĶpnēqķ.aŊUqĮíˇđŌĢoØÆū÷ėŅ•C‡ķÖ{`0hÎđ!7œzūFųõīĮ‰‰ŧ÷AŅŗBzvīĘāHIIeâ?ŨšŠmšG4cĘ´é4jЀ§Ÿ|ŦDžžĩxō҇™6s6/ŧō:ƒĐF yúÉGŠVÍķ’Ž=˛yÛļīāĻQ#mˇâíÜŠšyy,X´˜ŲŋÍÅßחûī[âáņGąõ2ŒŊë&O™Î}=†—Ç^OōÉ[Ÿ^ Bę—ęûŅ7ŽĀÍ͍i3f‘š–NõjÕiŲ"‚áCO.ī{~ü‰O>û7WzuīFĮŽØ˛åtŸäääģ˙Û­“í%ĀߟG~ŠĶf°öãõxzxŌ¯ī5ļgʈˆˆT‹ÅZâĘGĩęÕY´hQ‰'Ą‡†~îœŅčTîDu{p¸ķÎ; 'L˜pI•lŨC`ī;,"e;™r’§Ÿ}‘qcīĸMë–UŨœ•ßæÎcåĒ5ŧķæĢ|c€Ŗ ‰ú7NDDŽXŅąhßĒY‰e›ˇl!fß>�…†ŌēUĢë×oŲEXÒwt=ŸŖ ‰´lziŖ ƌsu\ų/đöōĻoßk˜=įw""šjˆN%9™r’EKūä֛F_TøšĩnÕĘ6$922˛Š[SŌU1DäŋbčõƒđŦæÉ/ŋNĢęĻü+X,ž˙=íÛļĨc‡vUŨ슌ŒŧâÂ\%s@Dū+ Μ5īD.žŖŖ#˙÷Ė“UŨ 9ƒŽ€ˆˆˆˆˆ\Å ˜Íį/xŠŲ\PeĪ�‘Ģš§‡‰É)į/xJbr žUx;~‘Ģ˜WuŽ'ž$>!ņœWBĖæâIHLÆÛĢē[X’怈ˆˆˆˆ\Ŝœœ¨['€ä“ŠÄO*÷ŽŽŽTķp'(0�'§Ē‹ """""W9'GGüjųāWË§Ē›r^‚%"""""vS)ÄÁÁkaaeT%"""""W˜Bk!ÎÆĘ<U)ÄÕřĖĖėʨJDDDDDŽ0šųų¸ē˜*ĨŽJ Áūdfᐑ•Åj­Œ*EDDDD¤ŠZ ÉË7“šžI�ßJŠŗRŽŖ¸˜œiÜ ˆCGHLNÁbQ‘—Ŗ ‰UŨģs6:áębĸIÃāJ‚UiwÁr19TYՉˆˆˆˆČŋî‚%"""""vŖ�""""""vŖ�""""""vŖ�""""""vŖ�""""""vŖ�""""""vŖ�""""""vŖ�""""""vŖ�""""""vŖ�""""""vŖ�""""""vŖ�""""""vŖ�""""""vŖ�""""""vcČÍ7WuDDDDDä? 7ߌ“§ģÛ%W_ Í‘+YíÚĩ/i{Ow7œŽ„†ˆˆˆˆˆČƒæ€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ(€ˆˆˆˆˆˆŨ\Ö�2éįɄ„†Îû~|Iu͘5›Đpnš}L…ˇ‰‹;JHh8 7+ąüŅĮŸ"$4œ¯Æ[Ąz.´ü•ābúëJС˙`BBÃYšjuU7å‚<tˆĐp7ŦęĻ”ĐąkBBÃŲēm{U7ÅŽNϤØūíÉËËĢ濈ˆˆČ.k�™2mēí÷ŗfcąX.įî.Ų˯žNdĢvUŨŒ˙„ë æžû¨ęf\°””TBBÙ0qRU7Ĩ„Ģĩ?EDDäŋĮérUŧ}ûöîĻ~Ŋ`ÜŨŨŲĩ{+VŽĸw¯ž—k—öîÛođö›¯áäTōđˇīÜuAååâdggģ˙‚‚‚Ēē)lĮΝe. "jįVėÜĸs÷įĘĨ‹),,ÄŲŲŲîí)ËeģR|õŖŋëčßīēSËf\ŽŨ]ŖŅˆÉdÂŅŅŅļĖl6ĩ'ĒÂååâíÚŊĢÕZÕ͸(;Ę Š˜LĻ*9Ņ?W:;;c2™Ē$‰ˆˆˆ”垐ĖŦ,æÎ[�Āõƒrũā888°bå*NœH,wģ|ŗ™ĪŋüšŪ×öŖqĶHÚ´īĖũ>Ėž˜ØsîoŪü 1Šđ歈lՎoē•?—.+ˇüŲs:}ü)šF’o6“‘™i;^<á\s@ļlŨÆ=J‡.Ũ oNdĢv 1Šoŋ˙ÜÜÜRåģtīMHh8ņĮŽą'*Šq÷?HģŽ] oN÷Ū×ōŪ‘o6—Ú.#3“?ũœ×ĨY‹64jA›ö3ö^ÖüĩöœũSQVĢ•ŠĶg2ęæÛhŲļ#šDĐŧe[†ŽÍä)SË<ɍlՎĐpöD•ŪZĩëXb}ĮŽ=uķm�üštYšãô‰ŪÃŊ˙{ˆ6í;Ö4’nŊŽáŨ÷?$??ŋĖ}-ųs)wŪ=ŽÖí;ۜvēqßąaãĻRe‰#$4œîŊ¯`áĸ%Œu3‘­Û͏Y \?”ŗfÛĘĪņøč“Ī�xũ͡ gȈQ%֗5$??Ÿ¯ÆËĀë‡ŅŦE›ˇbČđųãÔßČŲ>ĖŗĪŋø˙ėŨw\Vå˙Įņp3PpáFTTÜ ÷Ö,5ˇæĘ†•ZŲŽo{û6~~–™eŽJs$æN-î‰[Ā=X˛įũûãĀ-ˇÜ(BØûųxôû:ךŽë\į†Îį\×u]{ôÁ¯QSę7lB‡ÎŨyö…—8f•÷fũ™ÛŗŲĖâĨË=îšĩ ¤^ƒÆ´lĶžûœ@Đ+ Ü_yÃÔ˙~JŸģûĶ qsü6Ąc—<6ų 6oŲjsŸ‹/ņÆÛī}Ͱ MZ´áÁ ˛wŸí‘)�{{{ÖŦ]ĮĐŖhÜŦūÍčÛ ŋ,\d3Jj*ß˙đ#ƒ†Ž$ YKü5ĨSˇžŧüÚœ={.Gū_.ĸvŊŧđŌ+$%%ņū‡SéĐš;~ ›Đš{/žüúĖf3� ~YHßūņhFĶ–my|Ę3šūM 9Ė3ĪŊHûNŨ¨ß° ÍZ2bÔX.Zl)¯ ũ)""Rn˜ĸe˖“@“&Š[§�;´gãĻŋøuŅoL|t‚Íũžzú9VŦ\…‹‹ =ztĮĩtiBfāáŒuŸÍ}fĪ™Įëož @‡öÔ¨QƒĐĐ0&>>…I=’§övīÖggg~ūu!NŽŽŒŋ߸ ĢYŖÆ ÷›3o>¯žū�͚6ĄK§NDĮİ}ĮŪ˙p*˃V0įĮY¸ē–ļėãėlÜ!ŪžƒW^{omۚķį/°s×nžšū-—/_áŖŪĩ듐Āa÷qėøqʔ)C‡öí(]ǧÂÂøsÃFūܰ‘˙~ôīퟧãÍÍ ˙y……‹ãččH›Ö­¨XĄW""Ø˛u{öîeßž|đŪÛĒcˍûØēmũŊ…ZŪŪôîÕ ĮčŌŠĐ0ô5kT§}û@.]ēĖļāí|ũÍ .^ēÄ?úĀ*˙[īŧĪŦgc2™hÆíZ$�� �IDATؖ*•Ŋ8ÆĘUkXšj oŧö cG_û9;;˜ČÜų xũÍwhŨĒ%;´įČŅŖ„„æų_ÆŪΞAPļLYyøAV­YËŠSĄ´ lK@Ŗ†T­Zõ†ĮŸĀ°ûFr˜ōåĘØļ ėÜš‹'žz†ŋūŪlÕ§‡e؈QÄÅĮSŖFuzõėŽŲ dŅoKXšj5‹~™oŨēųęĪėĖf3O=ķ<K_Ž““mZˇĸjÕ*œ;wž-[ˇąqĶ_Ü7b8īŧõú-÷×ÍÄÆÅ1pČpÂÂÊėåEˇŽ](UʅĐĐ0V­^ËĘUkøčƒw2hāĩž9|„‘cî'&&†Úĩ}čÜš/^dũŸY˙įF>ūđũu;88°hņR^~õuZĩlA׎9rä(‡ᅗ^`čāA–ü‰‰‰Œ÷�ģ÷ėĨL™2tîØ‘RĨKąoßæÍ˙™ĨË~göŦīhÚäZ ™Õ7ņ <ūäĶ<B‹Í9ū<ģvīáãO>ÃŅŅ‘¤¤$žüú:´ ¤Je/ļnÛÎō œ>}šÅ ļj÷ō <ũ܋¤ĻĻŌ¤Ic:ulĪĨË—Ų˛5˜āí;ذiŸũw*öööˇÜŸ"""Åf„ æÂÖāŗ¯ŋyŪüŸ-iËWüaöņõ7wîÖ˜‘‘‘cŸ›ū2ûøú›ë7lb>t(ÄjÛŗį˜ëú52ûøú›GoIŠŠ67lŌÂėãëož;oÕ>{÷í77jjlĢSŋĄÕļ)O?göņõ7ųõ7–´ÃGŽš}|ũ͍›ĩĘŅ6[ų?aöõ0ûøú›WŽZm•?&&ÆÜįîūf_ķģīhĩ­gŸ{Ė>žūæ&-ژŋ˙áGĢmsį-0ûøú›ëú52GGG[ŌgĪ™köņõ7wčŌŨmĩĪ?ūdöņõ7ˇlĶۜžžnI˙eáĸũu#ĮŽ7ûøú›k×k`ŪģoŋÕļYŽõđ‘ŖVÛ7keöņõ7<tČfšÍZĩÍą=ĢÍ?:)Gū^wõ3ÎCķÖæ3gYm›ŋāËųĖŪ?Yß­Ļ-ÛæøîlظÉ\ŋAcs]ŋFæcĮ[Ō¯DD˜}|ũÍõ46ˇißÉŧ-xģe[zzēųŠgž7ûøú›ûŨ;ØĒŧĮ&?aöņõ7÷ũVé§BCīoƒÆVé/žüĒŲĮ×ßüЄĮĖIIIVų›ˇ4ûøú›ƒūXiIŸôÄS–žÉ~>ĶŌŌ,Û›ü„U7ęĪļ:›}|ũÍģvīšÖ?˙jųΜ<j•?$ä°šAãæf_ķšuë Ü_š™õÃlŗ¯ŋyø}cĖŠŠŠVÛļn 6׊ßĐÜ*°Ŗe[ZZšš{īžf_ķ´/ž˛Ę˙ĮĘՖžŋpáĸŲl6›#"#Í>žūf_˙�s›öĖ›ˇlĩjīĶĪž`öņõ7ßŨUY¯Ŋņ–ŲĮ×ß<`ĐPĢßĩŒŒ ķįĶž0ûøú›;uëiNNIąl[´Âō=îs\\ŧeÛ?ũÜėãëonŅē9°CsČá#–m‡…˜}|ũÍ>žūVégΞ5û5jjöņõ7/úm‰UûBÃÂĖģõĘņ÷5ŋũ)""R\&L˜`.ô)X‡BBØā ...Ü}÷]–ôŨģáééAøéĶlŨœcŋ%Ë~ ˙=wãīīgĩmėčûđ­['Į>kÖ­#!!š5j0bøPĢm1ø6Ūí›3oiiiôę؃^={Xm+SĻ Ī<õ$�?˙爴´4ËļŦšør˙Ø1Vû 2W×Ō¤§§sėø KzË-øīÔøđŊwđđ(kĩĪČðˇˇ'"2’ĐëĻįä‡ģ›;Ķ>û/˙úŦ[ܰ?͚w|wîÚuËuäG“Æ<8~œUÚāA÷Rēti222?}ƒū͡ßđÔ“s|w:uėˆáCIOOgîŧkw™ŗÎCjj*cFŨGëV--Ûėíí7f`ŒHÜǏøx/YĀ믞lšSPËۛ‡w͚<xȒ~߈a|đŪÛŧđėĶ–ģÛ`ÜÉŋoÄ0�vėÜ}ËmøaöO�Lšø>ĩŧ­ļųųÕgdæīŌŧų ,é…Ũ_gΞŒķ|ũÃÚ´nÅü9?ōã÷3,õnŲē“'OQŖFõ#¨Ŋ{õ G÷nÔ¨Y#ĮTŗ´´4ƎE`Û6Ví3z$`LËÂĖujž˙ŽÕīšOLžH=ßēœ>}†?˙Ü@ļ�ÄÆÆōúĢ˙ąņ>l`<øĄĮãWŋže›ŋŋŸåwíXļiĻ?ū4—äädúôî™cTĶģfM^xū#ߜšˇÜŸ"""ÅŠĐyķŗŸ÷ÆŨÍ͒îäčČĀÆ˙LØXŒž˙A�Úļąũ܎ÚÛØĮXÜēUK›˙cíÔĄC>[ŸwYë ēvîds{`Ûļ�\Ŋz•'OåØŪ­k—i&“‰J•*gI÷Ģ_úĶ.Đ(3::†°đpBÃÂ8{îeʸuÅ^ŊåãņōĒÄŨ}īâŪūũ�cęĐéĶg #4, WW×Ėã‰Ŋå:ōŖ‡§Ĩ™L&*”/@DD`\ôí?`|wr{ÂZ×.Øēm›ÍíŨmœ‹ŦŠUiii$&&æ¯ņ™6oŪBrr25kÔ zõj9ļ?öČÃŦ_ķĪ>=ŒÖ.°-Æ ĻNÚ¤§§såĘË9Č d¯^ŊõķËáÃG�č’Ëwˇ]` `Ŧo˛Ĩ0ú+kjæÂE‹ųsÃÆkZļhŽ_ũz–ŠdëÖümÚXfYžųęŦ^ņ;}z÷ĖŲ^ß ///˜ۘĩVkĮŽŨ¤¤ĻR­ZUę×ķĩŲîŦīŌ7Q*UĒh9Ž,•3ë,Aŧ­vÄÅ]û}ߒš^Ŗ{WÛßįÎ;`ggĮáÃGˆ‰‰ōߟ"""ÅŠP׀$&&˛4s$cؐÁ9ļ6„™ŗ~äỦ‰‰ĄlŲkw/\ŧ@åĘ^9ö¨^ŊzŽ´ /ŨpŸ5r^ô–Ŧ;Žļ.,\]KãééATT4įΝĪqASŊšíũLÆ)É0[/øūsÃFĻûģwīąšHĀÆēÔ|9~âŸOû’›ū"6Öv qũ…Ííbë|Ãĩųöé™ âĪŋ`iĶ?ũ{‡œ§YAĶõ ¸ŗTŗqL×.Ô22ní˜Īd.XÎíûiKFF?ū4‡ų ~áØņļûģ�į û"ęÜžƒYßéčč)UĒ”ÕöÂč¯ÁƒîeŨŸXŊf-<ü(•*U¤}` íÛŌĩKg<==Ŧōgũžå§/ŗÔ°ŅŪėŖQééÆwéėŖŽ„„ž}á%›eeŨL°5ÚXĨråiŲ/øŊŧ*ŲØn|_ŗ˙žgëâĨËØ’KĐėh2‘’šJhhMš4ÎwŠˆˆ§B @~ZAlæŧO>û?›yHIIaņ’eŒ;ڒžu2û…Avļošu§5?û–¤Ėē]\\rÍãėd´+)9įͰķ\ׂ_ōŌ˯ƔĸΝ:Pž|yËT‹—^~-׀!¯BB3tähđĢ_žw§j•*¸”2ŽīûY?˛sWÁĻū䇃@–„„ËĪ‹—.ģaŪäädŌŌŌrLQqĘĮšČŦ'Qåį\?÷ÂømÉR\\\¸w@?š7kŠGŲ˛ØŲÛséŌ%Ūzįũĩ)1ķ÷Ėd2åú^›Ŧ%�$%%į@ ŖŋL&_ũīs‚VŦdÎŧųėØš‹ß–,åˇ%Kqpp ßŨ}yí•˙XĻAeõå­ŧ‹'¯wũã3ŋKQQŅ,úmÉ ķfąČrŗļ9šōÖo ņF;ūŪ|ķ§ÛÅÆÅ[ęÎOŠˆˆ§B @æ/¸öæs[>ÍnÁ/ŋZ ÎNN¤ĻĻæúˆÕ„„øiYGŽûÄ'ØL/ ĨJ—&%&†ÄĜÁE–Ŧ‹=×ŌĨsÍs3)ŠŠŧ˙áT�^}ųEƏ›#ĪK/ŋvËåg™úÉg$$$ĐģWžøŋĪrLsÉZː_ŠšŒÖ–ŌŲúöāŪ9.–‹SéĖļDGGį)˙Ū}ûųmÉRėííY0w6ZmĪíQĮų‘Õ?iii6ƒ1Āę;}=Caŗˇˇįžģīâžģī"6.ŽāāíŦ]˙'ŋ˙ÄâĨË8zė8Kû{{{J—6ڝ5åčvČú=mŌ8€ß~]p“ܡOi×Ō¤DĮ0ķÛ¯s&gK~úSDD¤8Ú˙‰Ž=Æî={qpp xķFN=dķŋŊģ‚qvvæđ‘ŖVĪî¯XŠ"�/]˛YūŠS9§<TĘÜįRnû„†đ¨rWŗ†1EčôéĶ6ˇĮÆÆZ.–jä2(/Nž<ÅÕĢWąŗŗcÔČ9ļŸ;žĀŖ€eņ#m^ 9j{qą]fŪŦi,ŲEEEƒ@0Ļ×dĩ÷ÜšķˇĩŽüĒ‘ųįķį/Ø|‡Jzz:ņņ –‘ŧ=™į YĶ&9‚€#GޏMÕĢWŗŦ—:m!vYßéŠ+Ų‹ŨŨÜčŪ­+īŊũ&AËSĄ|y…„°}ĮNāÚ#ąŗĻ']/%5•øø„\oF䅷ˇņ&y[īú(JŪŪÆƒΞģõvÜŦ?EDDŠSĄ Yo>īŌŠ#*TČ5Ÿģ›Ŋ3Ÿĩā—k‹Ņøû°-x{Ž}ŌĶĶYŋaCŽô™O<Ęm´eíē?ķÖølōēÆ!kąüēõļëØ°ņ/Ā’j]÷¤Ą[áāā`ķnõ73fZ~.ŒõŲ§ßdųcåjËÅęõu¸e.Nŋ˜š†'ģ5ër¤­˛nEéŌĨiŌ8�€e˃læ gíú?­ĻkT^ÚŪ˛E3‰ŒŠ˛ųŊūņ§94kÉä'Ÿ˛Jˇ5Ĩ0##ƒīž˙Á¨ģ�mrsuĨQÃ�ŦŊÉw7ˇBTFFĢV¯áÛīžˇšŊzõjøųÕŽ­ kÛĻĨmļ‚ÚĮŸ|š€f-ųáĮŸnš]͛5ÅŲŲ™+6ŸÔÆßšģv“žž~ËõÜLģĖ'v-ûŨö÷9%%…ß–,åōå+Ā­õ§ˆˆHq*”�$99™ß/`Hļ—zåfČ`ãņ¸Ë~_nš(ė{Wo˜ę“õT#0.Ē>Ÿö‘9ĘéŲŊ;ŽŽŽœ cöœyVÛ6lÜÄę5kķ| YŌqņņDEŨ|ĘĖ}#‡ãäčČÚõæ¨įōå+|üÉg�Ü?vL}YËģ&&“‰´´4˅a–…‹ŗvŨzjeŪ1ŊpáÂ-דõ˜ã5ëÖ[Ĩī?p7ßy×ōØÕ Ŧ/`ę×7×Ī™ˇĀjēÕūųôŗi–~Í.ë‰ZgÎØžŸ_>p?�3ŋ˙!Įŧų+<1å~d"ķm<}-ŋ˛Ú~:m/SόåÉoī}đ‘QQ–má§O[‚Į{îî @ŨĖs°wß>ËÅ%w÷ßxë]ËĶÎRSS‰ˆŧöûßūŧœņøį¯§KXx¸Õļģv[Ū>nĖčû{{{>ūäsŪ˙p*3gũ˜cûŠĐ0ËhŋŸq“Ąc‡vøøÔ"))‰ˇŪyĪ*�XģūOÖ­˙úôîuËírwwgØãī×ëožÃ™3ÖŖ-{÷íįŅI3tÄ(B2Ÿ$v;Ü7b8...oßÁˇŲn0€qî_{ãmžyîE^~í āÖúSDD¤8ʐ ?VrõęU<==čÖĩķMķoĒŽĖų Xžâ†DŸ^=iؖÍ[ļ2dø}´iŨgg9Btt4OLžČGbUNĨJ™ôØ#|ö˙ãõ7ßfŅo‹ŠRĨ2ĄĄa9zŒ×_}™7Ūz'OĮPš˛*Tāʕ+ô4„:ĩkĶĢgwî1Üf~īš5yëÍ×xéå×xdâã´nÕŸZ\š|…āí;ˆ‹Ŗ{×.<”yq|Ģ\\\7fß}˙Mzœ>Ŋ{áîîÎî={9ĘĖoŋfņ’Ĩ„†…ņŪS Ūžƒg˛=Ö5¯yøAvėÜŎ3frøđ|jysōT(›ˇlå…gŸĻbĨŠoßÁĪŋ.$=#áC‡Đ8 cGbũŸŲ°q=ú܍ŋŸņņņl ŪÎã“cÅĘU>|ÄęÁM2§9zŒ~÷ÆÃÃgŸžbÉȝž}zs˙Ø1Ėúq6ãx˜ļmZSĩJ""#ØŧeÉÉÉtī֕1ŖFŪRųŲ4jÄÂE‹ųiÎ<Ž?Arr2ŋ˟“kū—^x–}pđPģõ¤yŗf$''ŗ{Ī^RSSéÕŗúŨ`yģúūšįŪAtëڅô´tūú{3ÎÎÎü<ī'†MXx8=2‘î]ģ0yâŖųî΁úŗeË6~]ô}û $°mk*VŦHxøiļo'##ƒįŸ}šæÍš¸ŋrķîÛođāÏōÎ{đÃėŸhØĀŸRĨJqņâ%vėØIJj*cGßG=_ãī&“‰Ī?ų˜QcĮķËÂElúëo4đãâÅK<dŦyųĨ¨QãÖ§;<˙ė3 9ĖÎ]ģšĢß�Úˇk‡ģģáá§-Ķ—ž{æ)Ë(ŌíP­ZUĻ~đ.O=ûīô1‹—ũN?âØžc'W""Œŋ?oŧjŲ'ŋũ)""Rœ %�Éz¯Į€~ũōôÄ{{{ Ā_MgÁĪŋ2tđ ėėėøæĢ˙ņÅWĶų=hۂwāîæFĢV-xzĘ–;ÂYOÃÉōÄä‰xyy1û§š>r”'NâįWŸožú;u䍷Ū!###סYødę‡ŧūÖۜ9s–ä¤d˨Ln† L=ßē|;ã{vėÚÅŽŨ{(UĒūū~ t/ƒîP(ĪŨîŲ§qrrbé˛åũą’rå<iŲĸô>~õëQ­jU=FČĄÖoØČĶSžČwŨģueęīņŨ÷ŗØē-˜ũPŋ^=Ļ9îŨē’’šĘÚuƝæ?VŽâžžÆK&;węČĶ>ãëéßrôØq"""ŠSۇß{‡÷ögÃÆM€õyĢ_Ī——^xŽoŋûžŖĮŽãåUŠĀOVz핗hß>9sįŗoß~ļo§tŠR4jȀū÷0|č[z‚ŌõF ÂŪ}ûXŗfģvīÁģfæ/[ļ,‹~žĮŒ™ŗX´‚í;vb6›Š_Ī—ĄC3jäpËãwā Ū˙p*˙ú‹ß~[‚We/úŪ՛‰=B9OOŪzãU^~í  ąŧåVúķŖŪĨ}û@üü+;vî&>>OztīÆũcGßļéWYZĩlÁâ…?ķãOsøkķūū{ ‰‰”-S†Ö­[1|č`îî{—Õ>6āåKøâËélØ´‰MmÆÅŲ™íÛ1áĄčĐž]ÛåęZš9ŗg1ūĪ,Yö;›7o!1) OēwíÂčQ#éÜŠcëš™ģûŪEŨēuųfÆwlÛļĨ˖ãāā€wÍ :˜‡oõDĢ[éO‘âb7aÂķôéĶ‹ģ"""""r‡{ä‘G ˙Mč""""""šQ�""""""EFˆˆˆˆˆˆ """""Rd€ˆˆˆˆˆH‘Q�""""""EFˆˆˆˆˆˆ """""Rd€ˆˆˆˆˆH‘Q�""""""EFˆˆˆˆˆˆ """""Rd€ˆˆˆˆˆH‘Q�""""""EFˆˆˆˆˆˆ """""Rd€ˆˆˆˆˆH‘Q�""""""EFˆˆˆˆˆˆ """""Rd€ˆˆˆˆˆH‘Q�""""""EÆŸP(ĨĨĨIJJ iii…RψČõL&NNN”+W“ÉTÜÍ‘|ˆOĀäâäXā‚ŌŌŌ8wîē(‘Û*--¸¸8Ν;GÕĒUõ÷FDD¤qqr,œ)X‘‘‘xxxPĻL] ˆČme2™,o"##‹ģ9"""’O…€$%%áææVE‰ˆä‰››)))ÅŨ ɧB @222°ˇ×zv):&“IëÍDDDJ E """""Rd€ˆˆˆˆˆH‘Q�""""""EFˆˆˆˆˆˆ """""Rd€ˆˆˆˆˆH‘Q�""""""EFˆˆˆˆˆˆ """""Rd€ˆˆˆˆˆH‘Q�""""""EFˆˆˆˆˆˆ """""Rd€ˆˆˆˆˆH‘Q�""""""EFˆˆˆˆˆˆ """""Rd€ˆˆˆˆˆH‘Q�""""""EFˆˆˆˆˆˆSq7 ŋBŖāŠ ØsBŖ‹ģ5E§–4­Ÿö…Zžšį‹ˆŠfŅŌ Îœģ@dTÁ:¨œ§ÕĢVfP˙ž”÷ôČ5_I?'EŨˇyíW‘;Q‰ @BŖ ŲTÜ-)zĄŅÆž‚Ũ“l_(GDEķá§_’˜T8MdT4ĮN„ōÂSm^,ß į¤¨û6/ũ*"""r§*QS°Ļ•ė Ũdôƒ- —Zđ‘]bR —ØŽôN:'EŨˇ7ęW‘;U‰ŲpǏ[P8ŧ=`j¯4šx™ņÎeĘOXėŊhĮsĢL„]7ÛgīyÛû;yû:čėų 6Ķ˙Iᤠũ EߡšṏˆˆČĒD Ey§Ũˇ<ôŦ _n3>_x\„ßįĖ[Õž�} .~Ø /¯ŗrŊ=`ۄT<\n\ŋˇ'x{šéT+•6ß8Z],įļÎ"))ŲfēŊŊ==ģt MËf”ķô !1‰!GXžr-1WcoܐLš­yČíœTr…wzB??(_ Âcā§=đŪHI‡ãOCrļ÷mõŧŅ îŽ-íJŦ;OüãsîSũ ųīÛĻ čŅĨ#U+W"==ƒ“aá­ZGØéŗ7nHĻ‚ŽĶ)iJÔŦĸôH+˜Ø&oyŋęŊ}áŗÍ°ã,ŧÔ†6˛÷ãŪi7ŊHÎÎÃÅØ§ FČ=}zœœÂúM[ ?M`Ģæ<5éaJšäŖ1yäꆇ[ÂÎŗFŋD%ÂëŨāįFž÷7ĀsĀį›ĪëOŸŸûNgģ&i•‘ļü €īŲŽŗ8ú5°Us3‚˛eÜŲŧ“Ũûā[ģS{īÕ TˆČĒD€äf\3ãŽyĩ2p<ž‚?ŽÛkĀ´{ A%‹†'—ÃĒãFū×ģī'Æ>ļ6‰ŽßÁčĻđ` cķ;Pę ãį´ ø°ˇ˜œˆ„aķāhœŒ‚gWĀ´­āWúûC@eøų@Îļvōļ9r˜Ú>ĩpÉ .\ŧHFFUĢTąäi\ÉÖXJŪTöĒDĢæM8vâ˙ûö222�¸ĢGúöęFĮv­Yĩn#îŋÕǞuû.ēt äšWßŊå:Į5‡úāŨ?á•5Fšƒüq? đ‡v5áģFzËjđd;>˙•ŗŦĪ6CRfœāãizŽöšaīú~={æ,ŽNNTĒT€ÄÄDÂÂÂņķģ6ŦR~ĩŗŗŖ_Ÿ$$&ōág_o ËīÚ˓>Ā=ŊģķŌčÚ1AũîbáŌ úö毗3~$4üĖ-×+"""RŌ•øŪuaÖ`Ørî™ ã`Ų¨í vĀ/#ÁŲf˛s†é7ōözšl­ž‚äĖ āÕĀŨ ūˇšT†7ēéOÁĀ↯ŋģĪŲ.ģlļģôgĪžcȰ<6ų ’’’ eøČQLxdĸÕ>š­gČ ßÚĩ�ØŧĶ|�løÛ˜[V¯nm�RSĶps-MÕ*^ĖûuÉ­Wtņ1ūũ*øZZēžÎüÜ­ö­•“öv`˛ņ­-{ŨčĮũO`øČQœ>}š„„ylƒ‡āŌĨ˖<éW¯ŠpwwcĪūC–āāøÉPÎ]¸HŨ:ĩ°ŗŗ#5Íøō´nŅ”ŸûˆČ¨[¯TDDDäPâG@Æ63ū}âwc@| ü5Á˜Ž3uô™QIpö*Ŧ=Īt�/ˇ— )áhLŠĘ› —?l +Yī7ž9ŧŲ֜€ßŨŧíÕĒUeäˆaĖˇ€ N$,<œˆˆH^ųĪKy>ū›qqqājlœUzBb"iii¸–*•™bÆd2ņë’ ĸĸc TgV0páēå%g¯˙–+Ežy{`“*ƚœíg 13§Ļ<ū8ĪŋøãÆ?D•*•Ųĩ{÷c)¨ŦĢëû &&–Ē•Ŋpvr˛,ú흭ėØŊ¯Pę)ÉJ|�RĩŒą¨ųJ‚ņų|æEoUwČ0ÃøƔ*cØžƒž§˛ŨŧžnÎ×>?Ÿõ5Ļ~ g{ē-¯žüâããY˛ôw�>xī]zöč~k ´!:Ú¸ę÷ô(k•^Ē” &“‰Ø¸kwīÍfsƒ€Ķ™EÔ(kŊ¨ģĒģņī%‹ČssxĘĩŸĪĮ¤eyÛīîž}HJJä•×Ūāü… x//<˙lŪ+ž‰čã =˖ɱ­lYwRSSINIą¤iäCDDDÄPâ§`‰'¨PÚø\3ķngŽw˟n̓ÁåõkS€ĀZ�Ü3ƒˆŦ‹ã1įU l`söBŋŲŸš÷öŸ:uŠÍ[ļZ>˙ļd I…øž‰“aádddĐžMKėí¯îNíŒö!GYŌĖš`>e=šwrÛkivđHkãį•Įraķaāčų=Ôûvæ2ĩízņņ ,Yz-ZŲŧy áááy¯ø&ĸcŽr%"’& ([æÚ—§v­šT­ėÅác'Ŧúŗ°úVDDD¤¤+ņ# ?ė6F8>ŋfí2Öe$ĨÁü}Æg0Ļüô¨}3'OŪ3�� �IDATסŦGŽ?OnkäŊ¯‰uš‰iP¯‚ąhúŖš×īä�˙wˇ1Úr"žëh¤Ŋ‹n2 +"2’ąã"**ŠĪ?ũ/ۂƒ™;oSž~–¯ŋü_ū;ÃfQlÜL—myöņ „9NĨŠhЀ —.ķ÷Ö…ROvsöÂSíénõ*/îZÚրŲ{`w.īÚ°eŲák‹Đķcō“SØžc'÷CŖF,Ķą–üö+eĘäĩ¸‹—¯äĄą#yî‰GŲšg?NNŽ´nŪ„””~˙cmĄÔ!"""r§)ņȚđā"xļŖ,ē}0Ū;q1‚ŽÂƒ-ĄyUą�Ž„ĄĘFā1<�ŧŒ…åŸö59ĖÜi<=kÆ@đû4÷úË8CõĖŲM¯wģ–ž$Äv�“tm„‹ŗ ~~õ:xŊzö WΏ¸”ÂŪÎz™ŧ­æåĮĸe+¸K`Ģtīܞ„Ä$6īd؊դ¤æc¸&2ĖĐs&ŧßúûA_ã|ŧē>ØXčÕÖũ ā[§.M˜ōäã€ņÔĒeŋ˙Žŗķĩysí×ŊBøö‡yôîŪ‰ŽíZ“žžÎņ“a,ũc5į.\,Xá""""w(ģņãĮ›gΜY BBCCŠUĢVá´čė^šíUÜvŋ OŖ_ũüMĮYvĎĄ ŦcEķ;9ķ=ūükiÚMMûč­i˙”sRXũ EߡļúUōύūöˆˆˆHáxāJū’æŲ•&bōąÄ#&ÉØ'/˛žxu;\ŋˆũŸævö+Üžžũ§÷̈ˆˆHa+QHŲÛw}]dÂĸĄõ7Ž,;bwÃ)@aŅÆúÖß8æČįía{ßÚ>…×ĐëT¯ZÅfú?åœFŋBŅ÷mnũ*"""r§*Qk@ēÔ6ÖV”taŅØœú“WMsšf< /ĮNž"))ų–ËļÅÅřÁúÚÜöO:'íW(ÚžŊQŋŠˆˆˆÜŠJÔČg}˙9w܋KYgŖl)īéÁ‹OM" _ĄLíņô(K@?^|jå=m ÜIᤍú6/ũ*"""r§*Q# ĩ<aĪd˜{Îü)F%‰ˇ‡qwūŗžF?äĻŧ§îŋ¯ČÚu'œ“jߊˆˆˆÜ‰JT�ÆââQÅŨ ÉNįDDDDDōĒDMÁ‘’Mˆˆˆˆˆˆ """""Rd€ˆˆˆˆˆH‘)q‹ĐCŖāŠĖ'.…–Ā'.§Z™O{úô&O{ŠˆŠfŅŌ Îœģ@dTÁ:šœ§ÕĢVfP˙žzäŦˆˆˆˆ”Ŧ�$4 š}ŅIÅŨ’’)4ÚøīĪS°{’í $"*š?ũ’ĤÂéäȨh"Ŗĸ9v"”žš¨ DDDDä_ŽDMÁš¤āŖ0D'}iËÂ%A…|d—˜”ÄÂ%šT*""""˙%jdÊânÁ?‡ˇLí•F/3Ū6F2Âĸ`īE;ž[e˛ųrĀŊįm—{ėäíëäŗį/Üļ˛EDDD¤d(QČíũx´5|Õ:~ …^šžåĄg]ør›ņų‹pā"ôøžāe{{Āļ Šx¸Ü 'x{šéT+•6ß8æBr[?“””l3ŨÍՕ~}ē͍ŽĨKÃö]{Yšn#éééyjwAדˆˆˆˆHÉWĸĻ`•$´‚‰mnOŲ÷Nģa𑝇‹‘ŋ œ™2ņAÚĩiÉé3įXŋi ‰‰ÜÕŗ+Œ^ ˛EDDDäßĨD +ųĶÔøüe?ãķŖ­Īīõ„„×ÁÉÁ‘Xu?ÄŊĮž‚ö5­ËjSŽLČ—ũ˛րå„< Ŋęéot3ęĒ[Îøühkãs˜1žép­}.™ãLiđaoˆ} öL†zåôÅŖāĖķđpK¸ü8û<ÜëŸûqwō6[~6›ÍėŲģĪjû‘ŖĮHHH°|n\ÉLA´iŲ ¯ŠXšv_˙K‚Vņņ´o8rėúáã]ƒęUĢ0íŖˇ¸ģww^á)ztéP :EDDDäÎTĸC— 2ZT5>ˇ÷†31Đ.3¸hY ļô øu¤„tũ6…Áōąāęx­Ŧáé Øq^ęl(vĀ/#ÁŲf@RĖf¤ßČÛë!ä2œˆ„V_Aræ�D‹jāî˙Û M*ÃŨôä4¨ä ÍĢÂāš`o_ôËŊü˛ŲF?–.[ÎČQcøėķi�Ŧ^ŗ–!ÃFđņ'ŸYōØZ#’žuj°iK°%Íl6ŗiëv�ęÕ­MjZ*�í[ˇ`ŨÆŋ9r´`•ŠˆˆˆČŠD­šžØf\ؗu†F^đü×F@šW5Ö`xAãĘđūØ~â6ÁøæĐˇūĩ˛>ų–…˜dcíF÷:°õ4ô™QIpö*Ŧ=aŒlxšŨ¸]aҐŽF@“%6&.3~ŲØ!É:Gxm \N€Ĩ!0ĄxēußHˇn]hÚ¤1ĶŋAhhkׯĮĶ̓ąŖīËs?ی‹‹ņ\ŗJ‰š €kéRÆA�ĩ TDDDDD˛+Ņ# `,oV:xÃĨx˜ŊꖇÖÕĄ|iØ žĨŒŧĪt0Ļ`mĖøėí•gki.f^c—+fßvM‚ä7aJ;c›é{íTÔĩŸ¯Ä#+YŌ3ŒāŒ@ŦˇįÆŨ͍oĻEŨ:uXšz5&“‰Yß}K­Zĩn­‘6DGĮ�āéQÖ*ŊlwŖŊqņ–´-4‘¸#W'x üf!'"á‰@ãĸ~ËékÁÅô`hú?ã?ßO`æÎkåTÎÕ¨b\Ss)Ū yēŊąŸËëđuļû)™~rw6ū­ę~ķļš ļ#W[ļl%4Ėx„WRRK–ū^¨å; @§ö×VÕÛŲŲŅžm+�Bގ¤›o×AŠˆˆˆČĄÄ ÛĪk3øĀ–pÚvŸ‡¸8 û.SŽjzë=~i4<ĶzׅWģŸW7'ãįrĨ GkSļZVƒ#WŒŸ'ˇ…žuāž&ÖíJLƒšev9;Üžãßļ-˜§Ÿ}OO~ž?—ĻMķ͌ī˜ųũŦBĢcĮî}œ9{žîÚ3áūQôë̓§'=„ŊēīÜÙsšŧTDDDDDä:%>�II‡ígĀÁūÚ{<6‡OžÚj|Î0ÐyKFë+~Ø.ų�æė…é÷‹Ã_^ ;ĪÁō#tl ov‡ ŒEî3ë4æīƒáđnOca9€}æ õ™;Ÿg ŧČ–˜lëB<<=iŪŦ)?˜A@Ŗ†|3ũ+ēuíJÕĒU,ylŊˆ0?Ėf3ĶžÅæm;¨UŖŨ;ˇĮÍՕßWŽeÎ/‹ V¸ˆˆˆˆüĢØ?Ū<sæĖZ¨krc÷Ęm¯ĸDøexũęį}Ē͞#v ]sA‰ųœyūĩ‚4íĻĻ}ôÖm-_ū]ŠęoˆˆˆŽx�û¤”Ôân‡äĶŗ+MVŖ 7“däĪ+į[lÕÍ]ŋˆ]DDDDū]’RRąww-]ÜíČŗ˛ˇīÚ¸D ‹†Öß8˛ėˆ]ŽĶĢÂĸ‘Öß8ÚĖ“ũ `ŲųÖö)ŧ†^§zļia""""ōīãîZēdŊ¤KmXRÜ­øg‹Ææ´ĒŧjšK,0x@_Ž<ERRō-—m‹‹‹3ƒô-Ô2EDDD¤ä)Q‹Đ?ëĢQÂPÖŲčK[Ę{zđâS“hāW(SĻ<=ĘĐĀŸšDyĪ\†]DDDDä_ŖD€Ôō„=“aJė9_đ§;ũÛx{#Ÿõ5ú27å==˜páŊI]DDDD$K‰ @p^<Ǐ[!"""""ˇâ€Ėøqû!##Ŗ¸›"dooO@ƒú<4vdq7EDDDDŠŅ?v ȡ?ĖcīūwˆŒŒ öîáÛæwSDDDD¤ũc!GĀŽ¸[!…Ę.ķŧŠˆˆˆČŋÖ?6�ŅČĮIįUDDDäß퀈ˆˆˆˆČGˆˆˆˆˆˆ """""Rd€ˆˆˆˆˆH‘Q�""""""Eæ_€T(_ŽiŊ…ģģ›Í큭[đĘsOqĢŦõŋĢ'Ž]Ŧm)lwL�Rˇv-Ļ}ôc†*îψˆˆˆˆH.î˜�¤]ëėŪw€Ļââė\ÜÍLÅŨ€ÂPĘŅĻ ˜úĶŠTĄ-šđ÷ļ–íîînŒ>ˆÚŪ5‰ŠŽfÕúMVûûx×`ø ~TŦPžSĄá;qę†õĩjŪ„^Ũ:QŪĶƒØ¸xÖm܈ŋˇÚĖëéQ–ƒúãSĢvØq2,œyŋ.!:æ*�=ēt Sģ6”.UŠã§BY°hQŅ1�¤gdЧGēth‹ÉÁÄÆÍÛXēb5�ŽŽŽ ŧ§7 ü(]ēĄág˜ûķoDDEãėėÄĮoŋÂŦšŋĐŠ]*–/ĮésįųuICôĨR…ō$$&ņŨėųDFEPŋnmîé̓*^•HJNfŨÆŋYˇqķ­‘\ÜHĢæM¸t9‚ķ/ŧk­[X ÃīŊ{{^}÷cœœ7rˆe›ŊŊ=Éļģ Zũ ÕĒTæÁ1ÃIIMĩY—WÅ Œ>ˆofÍ%äčq|ŧk0ųáqœ ;Mø™ŗ9ōŧ§7ąqqŧōöTėííÔ¯ƒû÷åģŲķiŪ$€nÚ3ũû9\žÁ}?jŸ|ņ-�ĩkÕāTX8oŧ˙)u|ŧyôŅėØŊs.2¨_ĒxUâãi͉OH¤O÷ÎLžp?o}ô9ééÆÛƛ4jĀį_ĪÄŲɉˇūķ É˙}ũ=qņņLzh]:˛hŲ Ę{z0áūû˜ˇp);÷ėĮĢb&>4–¸ø‚wîą:žž=ģrWĪŽ6ûfÅęõ­^Ÿŋ“'""""˙*wÄŦĀÖÍ Ūe\(oßŊÕĒPĩ˛` ũXģáo“’ˆšËúM[,ûÖŽU7×ŌŦ\ˇ´´4ÂNŸaĪūCšÖuéJ¯ŧ3•!GHOOįøÉP.]‰ fõĒ6ķ—r)ErJ )ŠŠ$%'3oáRž›=€ļ­šąmĮnÂNŸ!!1‘ß–¯dãæmØŲŲŸČš?˙")9™ƒ‡s5–Ę^•ppp MËfŦXŊž˜Ģą¤ĨĨą|Õ:ʸģQ×ĮÛR÷ļģÉČČ 1)‰ —.søč ââã ?MĨ å#€;sî;vīÃl6sáŌe6nŪF`Ģæ9Ž'hõzVØ2|ˆˆˆˆH^”øšÕĢQĩ˛;vī 66Ž#ĮNØē —áîæŠŊŊ=QQ–}.]žbųŲŖlâINNąÚŪĀĪ×f}fŗ™V͛ĐļUsÜ\KcÎ0ãęZ“ÉvWŽXŗž‡Į¤Ą}BcįŪũ? @ÅōåØw Ē766Îr�W"#­ĘJMMÅŅŅDŲ2î8šLLžpŽúʗķäTø�bŽÆZŌĶŌԈ‹ģö9=“ŖŅæ åËQģVMĻ}ô–UYYSÁŽ—hd„(ø‘ŧ*ņHģ6-°ŗŗãõĻXŌđŽQ%ËW^ Ė×öqtt´ül2™Āœm#āč˜{ˇ´kĶ’^];ņÕĖŲ„f^čŋ0åą\ķŸ įĩ÷>Áŋ^]5¨ĪŖãGŗiK0K‚Va6cí°Él;95szØûŸ|Áš slĪ:fsnä(/ŊB˜ņãŧ<åŦ""""’W%:�qrt¤E“�~^ü;‡ž°¤;8ØķėäGhÜȟũ‡Ž`6›)W΃‹™#•*V°äšzW×Ō899‘’’’cûõj{×āđąã–āŖ”‹Ë ķģģšĪžƒ!ė;ÂÁ#Œ6ˆ%Ȁ‰WĨŠ–ŧnŽŽ´oĶ‚ĩūžáqĮÆÅ“”œLĩ*•­rž–Eåųq9"‚úžur´;1)™´´´\÷Sā!""""ųUĸ׀4o@FF[ƒwq%"ŌōßÅKWØšw?­ZššĘņ“ĄôčÜ7WWʗķ¤c`+K'CÃIIIĨO÷Î8;;Qˇv-úÕËĩÎȨhĒVöÂÅŲ™˛eÜ1¸?QŅ1”-ãž#¯Ī?ų=ētĀŅŅGGGjÕŦa™Zĩeû.ZˇhBũēĩqs-̀ž=iÔĀ´ôô›û_[ļsWĪ.xUŦ€ŊŊ=[ķ┉ˇôâā{)[ƍž];âh2QÎ̓‰ŽĨG—ų.KDDDDäFJôHģÖ-Ūĩ׿û–ā<3yå<=˜Ŋ`c†âÍ˙<MddKWŦĻníZØÛŲ‘œœÂˇ?ĖečŊwĶĨc 'O…ąvÃ_tnßÖfūŊ•Ú>ŪŧûęsDĮIJhŲ ĘucČŊwĪڍ×F/Ėf33~œĮ}šĢgWŌĶĶ ?ÃŦšŋ�°{ßʖqgėČ!8;9qâT3Z§cZĩggžšôœ=w/füHRrrŽëQrĪôYsxwoúöėJ\|;vīcÕēų*GDDDDäfė&L˜`ž>}z ĨV­Z…ĶĸL?˙ZĄ–'˙×/všUˇãoˆˆˆÜ><ōHɞ‚%"""""%‹)2 @DDDDD¤Č(�‘"Ŗ�DDDDDDŠŒ)2 @DDDDD¤Čüc;;ģân‚Ü:¯""""˙n˙Ø�$ fĖÅŨ )DfĖ4đ+îfˆˆˆˆH1úĮ I“† tĮüaggG“† xxÜČânŠˆˆˆˆ#Sq7āFtą*""""rgųĮŽ€ˆˆˆˆˆČGˆˆˆˆˆˆ """""Rd€ˆˆˆˆˆH‘Q�""""""EFˆˆˆˆˆˆ """""Rd€ˆˆˆˆˆH‘Q�""""""EFˆˆˆˆˆˆ """""Rd€ˆˆˆˆˆH‘Q�""""""EFˆˆˆˆˆˆ """""Rd€ˆˆˆˆˆH‘)”�ÄŪŪžŒŒŒÂ(JD$OŌŌŌ0™LÅŨ ɧB @\\\¸zõja%"’'qqq899w3DDD$Ÿ %�)WŽW¯^%::š´´´Â(RDÄĻ´´4ĸŖŖšzõ*åʕ+îæˆˆˆH>Ęü“ÉDÕĒU‰ŒŒä… BDäļ1™L899QĩjUMÁ) í˙ŪŗæūÂūCG´D¤€ėíí hPŸ‡ÆŽ,îψˆˆˆēB™‚5söĪėŨĸāC¤ddd°w3g˙\ÜM)t…€ė=xė Ŗ$Ā.ķ÷JDDDäSā�$.>^#"ˇAFF)))ÅŨ ‘BUā�ÄÍÕĩ0Ú!"6č1ŗ"""r§ąO(î6ˆˆˆˆˆČŋ@l|ö.NŽÅŨųpqr,œEč""""""yĄ�DDDDDDŠĖ?>�ŠPžĶ>z ww7›Û[ˇā•įž(âVYëWO?ēXې›§L¤Sģ6ÅŨ  ˜ēĩk1íŖˇ3|PqT˙¯2÷×Åė= ÷IˆˆˆˆČ?Cą íZˇ`÷ž4mÜgįâhÂŋFø™sÄ\-îfˆˆˆˆˆ�`*ę Kš¸Đ4 S˙o:•*T EĶ�ūŪļÃ˛ŨŨŨ1ÃQÛģ&QŅŅŦZŋÉjī ԏŠĘs*4œc'NŨ°žV͛ĐĢ['Ę{zĪē›Ųđ÷V›y==Ę2bP|jÕĀ;N†…3ī×%DĮ\ G—tj׆ŌĨJqüT( -#*:€ôŒ úôčB—m19˜Ø¸yKWŦĀŅŅ‘÷ô& ĨK—"4ü sūˆ¨hœøøíW˜5÷:ĩkCÅōå8}î<ŋ. bȀžTĒPž„Ä$ž›=ŸČ¨h�ę×­Í=}zPÅĢIÉÉŦÛø7ë6nļyL/N™ČæālÜŧ÷ôÁĩt)âŠ_ˇ6nnŽīÜciįõÜŨ\zī=øÖņÁdrāTčiæ/ZjiGvüë3d@_‚V¯§Sģ6”-ãÎéŗįųaŪ/$'/ĶëŌĄ-[SÎŖ,QŅüē$ˆ#ĮN0vä`âãX¸t�=ģv¤˙]=yé͉‹āÃ7^bÆėų9ΡģģãīŠw .^ēÂ⠕Lzh¯ŧ3•˜ĢąŧđäcėÜģŸ6-›qūÂ%fū´€ēĩkqīŨŊđĒT‘ØØxö8ÄŌĢ1›Í Ôgg'~œˇĐRĮoŧČÜ_ŗīāa†Ū{7ĨJ𐒒J]ŸZ¸ē–fí†ŋXķį_6ûPDDDDŦy�ŌĒy.]ŽāüÅKīÚC`ëVČđ{īÁÁŪžWßũ''GƍbŲfooĪCcG˛mĮn‚VCĩ*•ypĖpRRSmÖåUąc†â›Ys 9zīL~x§ÂN~ælŽüīéMl\¯ŧ={{{õëÃāū}ųnö|š7  [§öL˙~—¯D0d@_ƏÆ'_| @íZ58ÎīJo}`4;vīã܅‹ ęׇ*^•øxÚtâéĶŊ3“'ÜĪ[}Nzēņų&đų×3qvrâ­˙<ÃÃãFō_O\|<“G—,Zļ‚ōžL¸˙>æ-\ĘÎ=ûņĒX‰%.>ā{nØ÷ééé4kܐ™s~æˇß˙ĀĢR^~æqK;¯7¸_\K—â튟“‘‘ÁčĄvī=|ũũO9ËÎH§lw*WĒČĮĶĻãččČKOMĸmËælø{+͛ĐĢ[gžüîGΞģ@Cŋz<:~īūwGŸĸc`+KYu}jqáŌeęøÔdīĒxUÂŅŅÄŠĐđõŪ7d��/ŋ3•2îî–i}f3�iéé´kŨ‚ų —~öe˸3饹ü˛$ˆm;vSšRE&=4–ĢąqŦßd;ˆË.##ƒf ™ņã|æ/\ŠWĨ ŧđäc„…ŸáØÉPĢŧ}{v厞]m–ŗbõz‚V¯ŋi}""""wš"Ÿ‚Øē9ÁģŒ åíģ÷QŖZĒVö2coO@C?Önø›Ä¤$bŽÆ˛~ĶËžĩkÕÄÍĩ4+×m --°ĶgØŗ?÷õ —ŽDđĘ;S9r„ôôtŽŸ åŌ•jV¯j3)—R$§¤’šJRr2ķ.åģŲķhÛĒÛvė&ėôųmųJ6nŪ†�ņņ‰Ŧųķ/’’“9xø(1WcŠėU Ú´lƊÕ뉹KZZËW­ŖŒģu}ŧ-uoÛą›ŒŒ “’¸pé2‡ž°Üũ ?MĨ å#€;sî;vīÃl6sáŌe6nŪF`Ģæyę˙ËW"9r€‹—ŽdļŗĸÍŧķ.åÛ摐HRR2;öėËĩī�L&Ģ3GŦRSS =}šĘ•Œ˛ÛˇiÁæm;8sö<fŗ™!G8zâ­›7åČąT¯Z'GGėėėđŽY-Á;ŠãS €:ĩŊ9q*œ´ôtëúhPߗ5ūEBB".^â¯-Á9Úuüd(GOœ"))™V͛pņŌ6oÛAzz:gĪ_`Ëö]4 h§ūã{uđđĩ><~2Œ€†ū9ō­^Ī A†‚ų7+ԐšÕĢQĩ˛;vī 66Ž#ĮNØē —áîæŠŊŊ=QQ–}.]žbųŲŖlâ-Sz˛ļ7đķĩYŸŲlĻUķ&´mÕ7×Ԙ3ˏ疯d˛}Ø+ÖŦįáq#ič_ŸÃĮØšw?Į3ījW,_Ž}B,yccã,Įp%2ŌĒŦÔÔTM”-ãŽŖÉÄä ÷፝|9ON…Ÿ°Z§‘––Fl\ÜĩĪé阍6W(_ŽÚĩj2íŖˇŦĘƚ v3ŅW¯Z}NKKÃŅŅöË(˖ug@ß^ÔŦ^ {{{L`—{Ų)ŠŠ$&%]+;5 ĮlíŽWˇ6ŊģwļÚ'..ž¨čĸĸcđŽYääd._‰äđŅŒv/�u|ŧ9rüDŽúÜ2ŋ/—#ŽõũéŗįsäËžŊBųrœŋtÉjûÅËWh͞YîvˆČ(ĢĪ1W¯RļŒģÍŧYFÖHˆ‚ųˇ+Ō�¤]›ØŲŲņú S,ixרƒå+¯ækûdŋ86™L`Îļ,¸ļëkI¯ŽøjælB3/ô_˜ōXŽųO††ķÚ{Ÿā_¯.ÔįŅņŖŲ´%˜%AĢ0›ąŒvØdļœš9=ėũOž°9Í)ë˜ÍšŖŧ4öaƏķō”?¯íŧžŊŊ=“ĮÁŖŧ=īs’“ShāĮ˜7xr™9÷ÂSSĶøuIPŽëoŽ?IíZ5IIIåDhį/^ĸBšr899Q×§ë6䜕u>ŌŗŒ˜m´!#sšÛ˜Lšnŗģ.ęr°ˇÎkooĪ:6{ĀĄāCDDDūíŠ,�qrt¤E“�~^ü;‡^ģ›íā`Īŗ“Ąq#öú˙öî;8Š3OãøĶŖšAČY&“1ā›pkØŗØËŽmŧwˇåcY—okī\Wë­ģĩƒ9§c‚ &˜d“M62B&*!iFBBŖéûc@X+ õXđũTŠĻxģûí_ˇŠĒyôžo÷ ™ĻЍ¨]š1ōQŋ^tÉž9šš Ģ­āā`]ŋ~ŊĖöÔŧi?uē$|Ô Ŋíū{˜œŽ<%9Ϥ#ĮtäØ =3nŒVŦY¯ŒĖ,5¨kĒ’=,L=;kĶ–ˇŊn§+O……ŠmS*€DEF”ģ˜ģ2陙z¸eB™ē¯ʂ~Ia��øIDATívßu‰¯ŖČˆp}ŗ}WɈS|\ã*÷—ž™ŠØ† JĩEF„+;'WĻięäéŗęÚšŊŠ‹=ÚĩgŋLĶTęų‹ęÜĄ­‚ƒ‚tūbŲ‘ —+OĻi*:*˛ä^Æ6Ššm™YęÚŠ}Šļõĸ•žá%qģŨ˛‡…•l V­ZĄĨö¯UęßQ‘:WΚĸ#x���xYļ¤S‡ļōx<úvĪedf•ü\IËĐūCßĢ{×Î***Ōé3)Øˇ—ėaaĒYjqō™”T]ŋ^¤Ą÷UHH°Z4WëGĒđœYWŗÕ(ρBCB^ĮĄ cGéjvNšĶe ÃĐĢ/ũZûõRPP‚‚‚פdjÕŽŊÔ­s{īĶŖÂjkôđAjĶę‘2ëĘŗ}×^ ÔO ęEËfŗŠw÷nz}æŒ*=‚xĪūC ¯c× ūŊ¨¨ČÍxv˛öëu×}ŨŽĶéR‘Û­æÍšĘfŗŠ}›GÕĸyŧB‚ƒĢT÷ļ]{ÔĨc;ĩz¸Ĩl6›š5Õŋžü%ÜXs"ųŒâ7RŗĻMtæÆbķŗįR5 wwL>[îČF‘Û­äŗį4 oO…†„¨^t”z&všm{RŊčēęŪĩ“l6›šÄ6RÄÎÚŊī $•Ŧ ž1ō6°_ī2ŋc‡Ũމ]dŗŲôčC-Ô<>ŽÔô<���T˞Ũ:k΁Cå~aßĩgŋūųˇĶĄO-ͤņcôĮß˙NYYWĩríĩh/›a¨°đēæ~´@O?9Bũzwיŗį´iËvõíųXšįüfĮˇjŪŦŠūũß^QvŽSËV­U“v=õäš\yÚ´õÖč…išš÷ņB=5z¸† ę¯ââbĨ¤ž×‡ K’&Vx‡&O|J!ÁÁJ>{Nķ?]tGמfũf…†„čåßLS@@€.\ŧŦŋÍûX……ŽGЈ+/Os>\ Ÿĸáƒú˕—¯}“´~ķÖģę§2Enˇ>_ļJ#‡ ŌĪžĸÃĮNčũčÅį§č¯ŋŦ7ŪzģÔÔ§Ę=~J+ÖlĐø1#åpؕ••­%+הŦąq:]ršō$ÃP^~ž$)9%UÃõזģ+ėwÁ’š4~ŒŪzã]ŧ|Ek7~­ĪN–é)JTNŽS|ō™F  ąŖ†+'׊uˇhûˇ{%Iģ÷}§GjŠY¯Í”ĶéŌÖ]{”••­€€[ĶŽŽŸJVŖ˜úúķ^—)S+ÖŦ/ M���¸=cʔ)æüųķīŠ“^åŖr€ģc†Jϟ%4kĒßN˙Ĩ~÷û7Ë5šWcG SDxxÉĶŅĒÛ?>l��� &›:uĒõī|iĘĪĮŠVh¨>Z¸X†ahȀž:vâTĩ„���Ü;j´Å+Vk˜QšõÚL™S'“Ījņō/ũ]���*@�Aætē4÷Ŗ–oéĘĩ– ��ā~dų›Đ���<¸ ����,C����`����Ëø$€Øl†/ēđ#üŋ��÷#Ÿö­[Ëī]�|ŔŠö­[ûģ ���ŸķI�™:iœÚˇi%›Á_l{e3 ĩoĶJS'ķw)���>įŗ÷€üjōD_u���ā>Å"t����–!€����° ���€e ����,C����`����Ë@����X†����Ā2����–!€����° ���€e ����,C����`����Ë@����X†����Ā2����–!€����° ���€e ����,C����`����ËØ‚‚‚ü]���€@PP# ����ŦC����`����Ë@����X†����Ā2����–!€����° ���€e%)-3Ûßu����x�JRũēūŽ���Ā€)X����,C����`����Ë@����X†����Ā2����–!€����° ���€e ����,C����`����Ë@����X†����Ā2����–!€����°L ŋ ���@͖œ’ĒųŸ|&W^ž<ŋËąŒÍf“=ŦļĻNš „ø¸;:f{Š4î3)=Or›Õ[Ÿ/RŊ0éķ R¯xßôÉ���Ē,9%Uīŧ;OšN×>$Éãņ(7׊wŪ§ä”ÔJ÷ߞ"õ™']rՌđ!yëŧäôÖŊ=Å7}@���Pe|ü™ŋKđ/ÐLķŽîÃĶ‹¤’;Jķ^ĸž^ä›î ���¨2§ËåīüĪ0”—Ÿ_énWœÔR] )ÃGŋj���pîdúYũø_M#€����° ���€e ����,C����`����ËØœÎšü<0����5…Ķé”Íápøģ�����‡ÃĄ@��€W×NíÕŖ[gÅ6ŠQ@@€Ž^ÍŅwßŅæ­;•íš$ŠOD5L/Ŋ>ÛŋÅú‘!iRéŲ.RģŠV t>WZ}Bú­ŌÅMjšŲCúËp)ō-)ģāVûÃŅŌÎį¤MÉŌ„E’ĮO/&!€���Ā/&OĢ.Ûé`Ōam[ēWnˇ[ņqÕ§gĸ:ļkŖwūūœNŪ´n3¤…㤧ÛH “¤÷vKÎBoyĄģ4ą4pž”tĨâ>„Ik!%]–&-ö_ø ���đƒĮētT×NíĩpéJíÜŊ¯¤=éČ1íŪP˙ōÂszbđ�-\ēŌUū4ĖH”Æĩ•~ūš´ éVûę“ŌÜ}ŌŽéŌįĨÖ•ŠË aAŌęÉŪĐōä§Raąuĩ—‡����Ëõíõ˜ÎũpžTø¸éJZ†ūúŪ|]IĪ(÷ØĀ€�=1t :wh+‡=LšN—ö8¤5ë7ËãņH’š5ÕČĄÕ¨aŲ ›.\ēŦ•k7(ųė9I’ÍfĶĐĮûĒS‡ļŠŠŒPvvŽ6oÛŠíģöVßEWŅKŨĨ §K‡›2ōĨWÖI+ž‘†?,­:^z{€á 'ŅaR9RNĄ55ß���– QlíßŧĩÂ}Î_ŧTáļqcFĒ}ëGĩč‹UJ=AņqM4~ĖHjŲĒu  ŌķSžŅžī’´pé 2Ô§gĸfL›Ŧ7Ūz[׎čÉCÔķą.Z´l•Τ¤ę‘– ;z¸ŠŨÅÚĩ÷@u\v•4´K-ęJīß&m8íũė_6€ŧ7ZJl,õzŋô:"€���ĀRᇠÃPFÖÕģ>ļvíZJėÜA_|ų•:,IĘČŧǘúõÔŋww­XŗA‘‘á ŅŪ‡t%Í;ОdÅ8tXnwąBCBÔģG7mØŧU{ö'Iڞ™Ĩ&iP˙>?Š�îũLÉŽxŸkné˛KŠ­Sēũ~Ō¯ēxCÉņō“ü‚��ĀRĻŧ Š‹ī~1Bã†1˛ŲlJIũĄT{ęų VŊčēJKĪԕô ũōŸžÖ ūŊÕ8ļĄ<NŸIQQQ‘bÅ(0 @ĮNž.ÕĮŠäŗĒĨāāāĒ_œŨ˜QĻ Jžĩی˛ Ë'w”~ŋ^zâaī“ą~*��€Ĩrr2MSõŖëŪõąĄĄ!’¤‚‚Ō‹ ¯{ˇ‡Ë4MŊķî< ė×[=ēuҍaƒt5;GĢÖmÔŪ‡JúxņųŠ’yë[ģa’¤:ģ22ŗĒtmžöCŽ÷ŗYdÅûÔ ”ę‡IŠ9ĨÛûÎĨKöéíĄŌū ŌļsÕWë"€���ĀR……×õÅKJėŌQ_mÚ"w9#!Úļ’Û]ŦÃĮN”jŋv#xÜ 7…†„ÜØî}ņ…+/_ËWĨåĢŋRLũzС§&OĢËWŌKÂËĮ —čâĨ˛ĪŽÍÎÎ)Ķæ/éųŌ‘4iB;éO[¤ōžž;°…÷sSréöK7Ö|ĖÚ(õˆķ.Fīô?Ō%??ؘ)X���°Ü×Ûv*2"\Cõ/ŗ-ĻA}M|j´Úļ~¤Ėļ /Ëãņ¨y|\ŠöfM›čZAŌ3˛T72Bm[Ũ:örZē>[ēRG cęëÂÅËrģŨ˛ÛÃt%=Ŗä'/?_Îŧŧr‘?ũe‡ÔρôëIJÛęÖōŽnŧT6€ÜTlJy_f¸xbåĶšĒ# ���°ÜžƒIj™ĐLCôQ“؆Ú˙Ũ÷*ŧ~]qąÔ§Gĸ.§Ĩé‹/ו9.˙Ú5íÚ{@ƒôQzF–Î_ŧ¤– ÍÔ§gĸ6}ŗ]G‘‘š6y‚VŦY¯ÃĮNČ4Ŋo\7MSgĪũ ‚ÂBíØŊO#P^^žÎĨžWTd„ÆŽŽėœ\ũũ?õÊØüũRßfŌßFJŊ›JˏIŽBo(yĄģ7XŒū´üŅ‘›.ģŧ!dãTéŋ†I/ŽļĒú˛ ���đ‹…KVčÄŠdõîŪMOŽ�›MYWõÕæ-Ú˛cˇŠŠŠĘ=nņōÕ*(,Ôø1#將éjvŽÖmüFžŪ&I:}&EŸ~ū…īĶS#†<.OąG—ޤiîG •ž‘)IZļj]Éãxë8ėĘuēôũŅãZĩvŖe×§LIŋX"­;)Mī*Í-…zׇ,ú^úķīT­Ę|}VšŊIzs ´ûŧô‡ĒŊôrͧO7įĖ™ãŸŗ�� F{áÕYū.á'ãŋ˙ķÍÛn7Ū°¨jdžuoĮ?÷ÜsŦ���`����Ë@����X†����Ā2����–!€����° ���€e ����,C����`���p è| ę¨N>J���T™Ãn—LĶßeø—iĘVén ’ję­2ĨčĘ/ņŽ@���PeĪNž ŨÁ_˙īk†áŊ•X<žæŪ*ÃđÖī ���TYB|œfΘ&‡Ũ~G͐î'†aČaˇkæŒiJˆĢt˙^ņŌÖiRŒÃwĶ™Ē[ Í[īÖiŪú}Ō§oē��ƒ*!>NšõĒŋ˨zÅK—^ķwūUC˛���€û���€e ����,C����`����Ë@����X†����Ā2����–!€����° ���€e ����,c“$Įãī:����ÜĮnf[PPŽ=ęįr����ÜĪŽ=Ēāā`Ųj׎­•+WĘ4M×���ā>dšĻ–/_ŽÚĩk+`đāÁŗĶŌŌ”””¤ēuëĘáp(00Đß5���¨á tōäI͝;Wiii —1{ölS’œN§ōōōX���Āg&ģŨ.I*ęp8r8~+ ���Āũī˙ĀUî{ŧí4œ����IENDŽB`‚��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/user-delegate.png���������������������������������������������������0000664�0000000�0000000�00000030341�14156463140�0022107�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��_���¤���A­���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœíŨ|TõīņJ3ĮAÃÎTØNDËĢŦuÆî•ÉZe˛ę+šl­&ę.aw+qÛ\īJÚn ģŊ€í­ĄŨ-ą×6Ák7QŠI•ƒÖÍč>Öw‹­0(ã;s‰× ¨œAûĮ$a’ !„ Iāũ|<ōđá9gžßĪ™!į=ßī÷L:räČDDDDDDDD$'ÎëDDDDDDDDNg“ŗm<|ø0īŋ˙>ŠTŠÃ‡ŸęšDDDDDDDD&„ŗĪ>›ÍFAAg•}ŒË¤ĶŽ>Ėī˙{ ÃāÜsĪ=æEDDDDDDDÎtŸ~ú)ā\tŅEYs”AáKWWguįž{î)+TDDDDDDDd"ûčŖ�¸đ íĮX–Å9įœ“ûĒDDDDDDDDNįœsČēoPørčĐ!&Mš”ķĸDDDDDDDDNgu‡Ęžī×""""""""rFQø""""""""’C _DDDDDDDDrHወˆˆˆˆˆˆH)|É!…/""""""""9¤đEDDDDDDD$‡žˆˆˆˆˆˆˆˆäĐäSŅɤŗÎ>Ũˆˆˆˆˆˆˆˆ ۑOŸ’~4ōEDDDDDDD$‡žˆˆˆˆˆˆˆˆäÂ‘Rø""""""""’C _DDDDDDDDrHወˆˆˆˆˆˆH)|É!…/""""""""94yŦ ‘ĶÛÎwwŗū7ķáĮøôĶOĮē™Ā&MšÄųįËŊ•_ãs3.ër†M#_DDDDDD$gvžģ›ŸŦ{œ}~¤āENڑ#GØŋ˙Cę˙ųņŪīc]ΰ)|‘œizĸyŦKĶͤI9ō)?kúåXW2l _DDDDDD$göôŅX— §ŖI“Øˇ˙XW1l _DDDDDDDdÂ9räČX—0l _DDDDDDDDrHወˆˆˆˆˆˆH)|É!…/""""""""9¤đEDDDDDDD$‡žˆˆˆˆˆˆˆˆäĐäą.āT >p5mn–‡žĸŌyúö™Ũ&–^ą„6w-íOU1ĻĨˆˆˆˆˆˆœ(Ŗ€9_(ĸø’?ä‚)|ō!û>ø?lnŖãŨ$‡Æēž˜<Ŋˆ›nŧŸûŗLe?ģc¯ķüƗxcīD<9ž žX˜›Šo ‰ÆHv[¤l6ģ ×GyU eûX)""""""'Í đÚ[šëÆ98>“ąų“Oøä3WpÍŧ›ØgžÆĶÍųíD -ωo,)ãķįôn˜ÂĖËįąø’™üríãüûŪą,Nrab…/V”†EÁšH7Øō™í āuX$baÂmDښiĒ~ŒĻe>ŒąŽ÷ \r5AĢ˙F›Ã ˛fÕ%§Ķø›0˯ž‹hõfÚ˙Q‰ĒŽÅÛãĘ]qŖÂę  E1Iē­6#ģÅ7PŠĮy*?ië›0Ŋ5Tû&VČk^EK˛„%‹} Ē<ž‰ú†îĘû fĮÂDÂąôsØ ;ŽŲ>ü%zŸzŗuMŅԐũģʖQé1ŌuÄkØČw¸ņúKđŦŽ“`}3īN†x×đú?ĩD’pc=›)ci•§˙ŋ#ĪRßÃ^vÆyfœ‡éĄ˛ĻtŸįą|Į Ö7`WŸfŋ‡EDäôdį‹_ŋ—ģæNã3ö|ÛĀÃ˙{73ž4˛¯æŪ%Nfūĸ‰ÖV֖ƃ/Ū8?#xÉpÎŔŨx9ŋm|“§ŧŽ“eį‹ķ™ųģ´ūîÞm—rĪĘ*Žę}|ĀÛ¯of}ۛüŋ ”•† žÄi^tk"Ž+iXY{Ā5Ÿ{–Ĩ‹–˛šá>–ŋĀÚŌ‰uqvÚq”QŋĻ‚üž˙M%MBMĢXSÆlz…:ߘV7!%›ī$^L䑒SÖ§Õų, M1 O ŋ‹|;LĐj§­ŠĢr1žSv į¤8P†+˙LˆV“ÄZi‰Z8<>ü>v›E2Ŗ#ŌĘcÛŖĖ/¯Âį‡ŋ‚ĘâŪĮumk%– Âīčk͖’Ų‹)_,¤ē‰…ÛŲÜÔH÷Ā�(nbZ…x*ŊĖīû[æx}Œ“ÚGĎËå€H Ķōôûw&n&č&…ëOQæ“DĖLasšĮejEi0ũԔŽu)"""'`2—.¨äŪšĶ2ļ}ž¯ōø/6ķNĪß%īŧÚÆŪ|›;˙ǜųwßÁû˙ķqūũƒ1)øĖdÎ%Ų’—´s/ų<…ŧÉÖSXŅčpr•į Šŋ4“)ŋhā‰ž�&Ã9Ķøü—+¸ŸŠÛĐ9!§‹Ô„ _ŦMĢŠ‹X؊kiz¤"븆ûÖ>–byc'Ūáä.É(ÍĢĨ)ÁėļĀ–ËWJeM-ˆR7īvšėËŒNč¤aū|֘ Ū`MæuđĻû(Ž áĢōž#mį78>wīž;'ûē1ÉgŠžˇ”PÆZ.ÉöU,_ĶL¨ŗpāÜĪōÚ|ƄŨÛįË8_ūR7ĖģæĻM,÷•jtŌ ęwpĒŗ|3ŖÛQBeYƈ ģga>Ŧ b&’øœ§*č4pē=gÄēEÉh+­Q˜]ą˜ ÷Ņį×UX„Įã!ØØÄæÖM¸—â´âę;$Žiė\……Ųˇ9qöãr; žhdūÂĸžĪfŌ4I:ÜĖvf?tãĨö‘rē\ä‡bÄâāî+#‰i&q¸\$&&EGˇ%Mˤ W`|ž3fü”˙Ū9iŸŊŽ¯Í›ŪÛ{/ņŖŸžÂ˙xė‡Ûxâgšđ›e”-¸œŽÆ7ŲĒę”,ĻqÍŨÕđ‹žøŨ;üüáoķķž=į|îFXōes¯¤pC'īŒe™§Ø _,BmAR,¨Šú›Ewu+‡ŅdrK,Ą-éb~Å2ĒŊHDhkhĻöŽ0Û{å>~¯AS°¨ĩWßÕH„PÜĀfŗˆ„ļAÉŅo@ÃĄ0)Š™ī3 Øģu¤í|‘ģŋa°ęĄÍ­T˜Ō’ 5NxĢĘŌŖŅTT7bæ{)_Vß‘ 6Rŗ(Ÿ1Š_˛đāõ4™& čy-“„Ö=Ėę†f7āp¨ŠŖŽĸ÷"*ĖōĢaÖŦ%z˜Õa7u¯ũ tuĢ Æ¤č™Ō´Ŧ6cœāЇŠo¤ÛÍwá¯XÆōe%=īQ–_}fÍz*ÍUŦií nŨSAŨÚZüŊÍ$Ã4,_MS{ŒDĘ–ī&P]KŨâ˜Ú–lgõŌ4…M0\øĢk)tĖPũ$ižëjj#�‹pˇšXÔöËŖPېŦ!.Ú ,^<čx3ÜJ0ŌI2 ؝¸ũg_=é)Lab‰nRôN)ÅWh cļéIb›‚„ļ÷öiĮU PŌ{'XßHÂ[…7$´=NŌÃáĄŦĸ´īķxŧēN­8a\e”šŗ[F!@1ŅĻ(‘X)eîŅč͉Ëa#’ėƂž×ËÂ4ãØ]ÁSĨÆ}í'ĄĐ…ˈ`ÆâPبXqĖ„Ģ˃ŅƌƒĢw—#‰¯ãč”Ģã}Ō‡%‰mj%Ô'Ãå#Pæ;úo„ÕI¸5Dȧ§ûŲ¸ŊžÂžv:i]ŨDˇŋ†ĘŒéKfë*šâ%,YėÁl\M› ĐD]ԁŋē÷7…n&ę$ä÷û< ī3#""’;“štŪ\7_ vU ī÷íØÂK/ŊÄ Û“đÁōôĢ_æĄëžĖUĶŪäߌ~šiūuÜ8˙ēŦŊmÜüĪo~itOaHģŲēã�×\ž}ôËĮ;ŪĻķ8-Œ¯ķÉ&3€9:æĀģģH|ōe“ Ž=öįô4An5#ĩĀæ%0JSUÂĢWЖtŗôŠX[[EYi)eUĩ4´­ÁIS]#qĀđcKu g>¸(^|’ŅņžÛEēÁ]ÂĀiü#mgŪ 6ØŪŌLŦ_‹IBͤl~*vĀĸĩž•=A]Õ-J+¨~ä×Ŧv›lzI…S¨Ķ´°9}ŋLŖ+n§ēŪÄSˇž`h3MË\tŦ(§Ļĩ÷10 ŗŠžˇ–ϧj °‰å‹ę‰škiŲü2ímëYîŽQŋčZ“�Ąn§ĻŲÂŋĻ…`d3-u>‹¨ZíĢÆ0,ĸõ+zW|í :^^ƒ×ldɊöž#’´.]̘‹ĨëÛhmĻŠÎMGũ"–nîwÉIZ—,Ṙ‡ēõ/j[KYbk‚ŠūĮ ŲŠĮÚXęۂĩ„;~Í2÷hÔv<.ˇ[ĸ–Ö0ąøĐíÆ75ŌJâ TQ]ŗ˜J>‰`#­Ņdú�k­-í$*ĢkXR]EĀ‘ ÔŌJÔÆūA,ĖÖFZ:,Ü Ē¨^ļ˜Ę€‹T¤‰–MņŒãRÄ#Ab…eT×<Ȳš2\ŨšƒÃĢkÔXXVļŸ‡%MĖ8=îc …n\F ŗ3~Ŧ#NP’d2…ÍndôŲ‰i¸\'0ĸcÜÔ~2 qšlt›&ÉŪMņ&.\.Žü$fĸoĻ'åp÷ā9îį ˇęŽ ŧ,¨^Ėĸr†¤šu[Oāimmfs2åb–ÔÔPp5Ķî›ŌĀSQÍ|Ø<å,]V…ŋ70Š '‹{ú.ÁžČø<�ĮũˈˆˆäT—}nJ–ퟤ—˙xƒˇ˙ī~0™s{×1f2sĘ!>ÁɜΠū‹āųÍ/ą1K 16A…Åo7næíl‹ēØEëÆã¯÷2öį3“…ÕRŋę{?_§¸ßâ<é�æÎ/œ ę˙&ČȗxúÛCÃÎč,õĻĨ5…eøÉtÛ}<,đ„B›%̍đ•ā!H$…OúŅĄ0ŠÂÅT ¸4L‡U•^<2ŲAÄGĨô KŒˇSDyY>m-­´Ddš§§Ád;-Q [ Œ€‘>§PØG åúņU•ájiĀ§îdX„Ļ>–O`IĪ”#kk›Mf×lĻŽ´�gŲ#Ŧ_KU}#ą˛éũb<‘_FķâŌô¨•X3ÛSųø+Jq;œ8WާĨ"ŽÃ$7ŅLPŧė)–õ,*é,­ĨŽŖ˛ÆF‚ĩĐ—™{*ûúÆ^Jš7Ÿļí1LJpa'P׆‡ÂžošÎeT6´˛:†ŌaŦŊ’ Ō˙š•”y ĀI ļ–hhõ4Œ~ ;† ĀĀn€qōĩ ƒŨSA…ÕJ0¤%#‡Ë…ÛíĄØ“1ũÃÚF¨#Ã_C wăũņGi G‰{Jp&t[.OŊ3•ėĨUTz’Ø ~œũY1Âąn\ū*üŊ‹­ēK™ī5y,&VzKßû‡įh]FW>ņI ą¯ŽŅ’ņØęĐ1vfŒOŗ’$ągm;vĖî$œėD,+‰m%”ČĮ]’štš˜¸Nl•ņRûIé œLĶōa7 3ÁQ‚Ëpb¸ Âą8–ĮŽA'Ļ™"ļ+ũYÎį §—Tž‡ĨEéĮŲKXāą6ԁiá6 ܁Ÿ°cīũŲK(vtę?ę8§bô 20ŒŖĪŽeøôõíÃīn§Š÷ķĐ{ĐPŸ™‘=ą"""Ãt>L¸í�o?ßDÃŋíÎ& .ŊöKø>ˇÄŦųŗ/}–Ī�S§YÆo÷†Ŋ#FÆt„ČŪ˙ä'k?¤Ŧė:ޚÕsĢéoō|ëfŪ李Æö|ÎįÂ)—AÎ&û˜3Ņ _zįĀāIšoŋšÚށÛŊŦîx‚˛l‰Įc˜) Ö@™ˇá}šlO�n/~7Ŧ‰F‰ãÁŲ3*Åáõáņ¸SŒBĀV¸ų”Š7gy;žĒ \- ´6†YūHzčO2ÔL4•O ĸ´įœésrž–åöā˛qęרjŗW÷ßfs1ŋn=uĨ=/L,D4å ĖWØī0Ÿßƒ­%J4 Ŋû;2×úp—āw4ĐŧäNŦę*>~ˇwīmÆc‘tģžūvn¯[C”h'}”vW˙ 6Ãn€•ėûum B+VĐÚÃLZXV*ũFt ķÛįX“|<ũVˆ.ÂWlđØöŒ~GĐĪI×6,._Õ>‹xg 3ÖÉv3F¨ĩƒPČ͂ō <N i’°ōqģú_–š Ø"& œN.{ˆhK#xŊ¸]…¸œNgoztœũÅãYûtēØB ī#ßŅīuļŲ oZՉö;R‹íÕĄšo¤W†\XKYģ"Ø›á`vYŒ÷iÜ4ą\ž‘-";ÆĩŸ,ģۅ#Æ4ÁãNbšv ƒô{šÖ ŠpõŽ÷âîy¯ įsĐSfžÃÕ/İ;ØŦž÷­ #I4d{<N2–eĨ˙Ž´ŸüįÛp8úõmŗ œf8ägFDDäT{īe~Ų/x8ĶŽäĪĘƒî†t,™áÄXOÍ9´wĪülΜDãé|ŽmÅ'˙úģwΨvš á‹#ũÍ_ĸgLŋŋi \e,ȸØLDZ‰$†hÎęųãŅ]EũRöoJ ;.@!^¯š{FĻXDLīR8Áãč&Úž"ĸÁ0)[ ūŦSŖNĸwåîÖ› â#@’`s)G•ŊĮô„6#û°{ÛOGθ*¨_[Ųwáf؜8 íũëKZX$h*˙<Mƒ ;ãõ6ė™gácyÛSĖŽ_GKÃRZę,l/eËWĻGą$-, ģ1Ōˇ&Ou÷Û4„(uåwŅl”QWˇ¯ÛŽAœ–E ¨Ūŗ�IŌĩ xlF憑ô3 ĩgĄgĄ`Åôļ Ŗ¸Ģ<–…E7‘†D=֑žZc¨ŽÆŅĸ#ŌJ$˜Âfwá ”õ|Ã~ŧũ¤Ō¯ķāAé×9s:ĪĐ¯ķ ö;RY‹ ÃȘxˆŨ‰ŨƒáeHĻ×áČA}/åežžą6†aĮ4e'ŊĀŦĶí:ąŅ$ãĸöQ`wã˛‡ˆuv‚Ģ›íŨŽŪ@ÅåÆI;ą88& Ŗ°o:ÃúôÔ>hXUĪû€8ÁÆ&ĸTár$‰67pŦąS'bčĪÃđɍų�ąé“CY.ÜņN[=ÆĘYšč ÎMČž†a1Ļ!Åų…üņMķųã/8šđœCŧŋãļž—ä�\t)s.™Æg|ĀîßũĪ<˙*Ã,2~C—´Í 6ŸŲÁ L˜đĨ(Ŋ`m[ma‹˛Ō~ãđU­$3ī>$Ō6Dsv{ú’Āæĸ¸¤ä¸ƒŪ=~ųMíŖāOļŗ÷„ŧ^ƒæhI ÔŅÍįĮ?ęíR^éĨ~yÖM_ļ¨…ĢǜžF=ĄKĒ'Xęŋ¨ŖEj,Ö|1\¸ŨECkn70p¨_OÍĀ…7 ĮP/ŽŨCE폊¨íY,ĩaËkîÂhy…åöôELĸ{Āc’öÁĄĖąD›i5ķ)ky„˛ž';Nr`ģCIĪE 1ā5čļ26Œ¤ŸŅ¨m8, +K¨g8}øg‡yl{œ\†A>îŠ*üƒVGÃŪ÷@<ĨxJÁJv  67BõũœÃ؟ɖ~ģ~ĖPf'ŌoŽ.\Įb$}žėFgŒ„•tÄÅ ąãp:‡ž:b™lOØqN0 ĩ 'n—ˆ™ nƉã:°\ųĻ™Ä4ãØ‹Đ÷s�XɁoÜôûÖ0€x”X"Oõ-dāKi؉ˆˆœēxëŨũĖ˙ƒŒu_\_ĻĖķ˙í0úÅāķsgö/�qļž;N˙Á<ŋˆ{–|Ģ.čŨđ.ŧä ŽŊdĀqįLãâ/ŨÄŗūuk[xc\ÎÖéäųæ ģĻÃš}+įP|Ũœ~ %lyô§¯°įLO^˜0 î‚ŋĸ Ą5Ģ ˙đĄŲ]¸ōX˜Aŗ•€AĢ_–đÚēém# “*,Ąwv‹×W Ņv"éuZ܁’c{íØËĒđÛ,BmíĂÍDSnĘË3Ļ79¸l@"6xzQŦcĀbŊãˆÛ×ÖM"eĮUXxôĮi`ŗ;ũ\ÆŖÛ;ûū×(ôQļr[71Ķ-A4Ú*G,%es{š„tXãLŋ_úieŗÉđ§U¸<¸éÆėˇHf”H$ã˙GŌĪhÔv<É0MĢWĶNfŲi‘čļĀfOB°ģpŨ–Ũn?úc^¯ 'Öy´-Ã^ˆ§4€Û°H$ŦãīČéÄat“0û×īL2 ûØ'ÚoÎŲņúÜ`ļŒfyî­N‚Á,‡aŽJ0cÄ '˛ÖnÚ8¨}”8܅ØēM::ā*ĖøC‰Ëe7ÃÄé)J}ŋ̆ķ9ča%ũĻđ$ R†=Ŋn••%@ŒĮˆ%č7…Ũ0ū“•$7+Ŧ‹ˆˆŒĐ!ŪyųuúOf˜ÆUwŪĪĒUßãU—ŊSÎų—3˙KGˆųøÍ˙āŋÜéh|˜Ėœ›d/ÃpÁ|íĻKĮéˆ ‹=ŅWxáß6Ķēą÷g ‰OŽ‘-x9įsãø pČ:îĸ§› žā[Æōų`6R}û*BYo’‘$Úü�k‚é ÂcéícA R!Öuößeĩŗtū/zöč].đđÄŖ ´FēÉ/öôæ°{=8Sa‚ íÄpđuĩwíĨT Ríõ,oˆBq ûˇíõ`i‰fnˇ6>ËPŗ°Æ”QJeY>Ņ÷ązĶ6âÉ$fôY–—Ī'°$ķ5 ÖČō%wą¤ąX<I<žĐēFB8(v`”˛¨ĖAĮęhhī$žŒÛ´‚ĨÍ fWUstŌ [Œ–†M˜É$f¸‘%'(ö¤Ėčđî„ã P^ ĄÕĶî$Ū%øđ*BVÆ;tXũ¤œNmŠuwBmĮc÷ā÷:HiÚ&Ö'cvF 57ŒĖ.ņ¤G!éulâĄfBą8IË"l\GCk4}Ŋ˜ liL?I‹d2ŽŽ`bĮå0Žŋ ͯ83ÔJ¸3IŌJmĸ­#‰Ã{k•œhŋ§€á)ŖĖc`ļŽŖĄĩhŦŗsŅđ&ššˆ¤Ü*Ž1˛d˜ą88Ü#ZīeŦk-†Ë“Nĸ ėŽū͝\…N0ŖÄ’Ķ‘`xŸƒ^ŨaZÝ=Į„ Fäģ{Öírâ4t„ˇĨ÷w†i&q¸l¤’ Ō7sâpØHÄĸ=˙oéHeNiėaÆĶĶŊDDD&„ßŋÄ/_ΞōėgĻ=Äų|ąė:>ßģãĀ.ZÛŪd˙ŠŠđͤ؝íNC›ęžCáč“sGƒ—KšgåŅ;"­ZōeĀû¯o9îí´O7ã3DËĘNā‘§XÍ],ok Ú߄Ëë§ØeĮŽE2a GI¤Āæ*cõÚGzoŲJ„–ĐļfåąĘ.ŒD„ļ†fB  ęJ3. ėx.R̃SūĖE] K(Îo Ø"å¨Ā[8ô9œL;ūŠ2ōۚé0 ükĻKŲ)̰&ŌJĶĸÛIVWāĩ'ŲlĻÕōáĩŗŦ?0>øV>ÅZû V//§ŠÛ› O –Ļ•ˇûâŦäšV>L]ũ”×u“˛8 ũ”¯YĪ27€oåSÔŗféÖtƒÍá&PķËgYųXėÔ­ ŗdÅR-_`ųĘGÄĻcI•åĐŌvŧģ 9ŠXģ†íKWPˇ¨páĢĒeyÅ –“'Đ΃ø++p-m¤úög)ė?‡õ˜ŗšNŒĢ´ŠJg;Ąh˜ÖŽ ) lF>v— e)žÂŖ4WiF`°‘p2†§ģ”Ę@Ī"ŗ…ˇPØÄæp+MÁnR†üüB<e=S:ގ?{}å ĩ­cslv'nß Ų8n]cÁĀ]ļ˜Jw˜H8F¨5Lz ‘×ė2•xún}qbĻ…sÄCSÆ˛öQd¸˜ŸÂLä÷X Ŋî‹Ã4Ü œ=uÜĪ`Y6œū�ždˆĻú8Ũ8\ĘE=7'ō° ĐIK¨•ĩČwšņnÁmm"ŅĸŠ*—ā ”b6ˇĶTN¯UV Pœ¤šo¸ŖÛį%ŌĄŠ)†ˇŧ"§O™ˆˆČč9Ä;mM<>å^îš;­˙‚ē0‡ËĻŊÉî/”ķgs{ŋgķ/žäßĮå¨�ƒŠ#ųûĮ8˙č(ŸqīŸpœŠF>āí×7ķËļÎ3n ˜IGŽ9’šĄŗŗsÔīđ1éŦŗGĩŊx¸™Įšš …cÄģ-RØ<ž *)/-4ę%øĀÔ´šYzŠĘŪĶKFi^ũ(MÁfˇļ|\žR*k–Qáđ‡vlū $đ˛"ōGī¯KpŅÕԄ,ōŦ'ōˆoč>GĐÎQQęæŨNS2@ũk?Î.™›VPˇæYÂf7ظ÷ŗ|Ĩ›ļųˇĶd_F°mņČî\""""""2K—¯8‰G\zí­|íÆ9éé*Yė3_ãéæüvīxžœ/➕_įĒáŪ–Š×'o°îáŪČIMŖīœiLŪßÅūSøRŦŠĢ=ŠĮųôđ(U’Į),,´}B†/gŦx3•ķ&VļžČĘŦˇTWN.|éa0į Eģ˙ ĻpāCö}đØŨFĮģÉ 0ŠÂāŌ•TĪûÌŁį�ģ^~’në<ãÖG9%|™@ĶŽÎtI‚ĢWÁÍŌj/"""""rąēØú_¯°õŋÆē‘˛x§­ŋęŽŧrZSø2ŪÅô†bDZ×ŅIáĒ\IuáX%"""""""ÃĨđeŧ¤ij‰��gIDAT‹6ŗ|y+Ø\øĒW˛z™gŦ+‘ đeŧ+}„ŽíŒu"""""""2Bgu""""""""§3…/""""""""9¤đEDDDDDDD$‡žˆˆˆˆˆˆˆČ„3iŌXW0| _DDDDDD$gϜw92ÖeČéæČĻN™:ÖU ›ÂəĘ;+&Ö™ΚÄ_T}mŦĢ6…/""""""’3ŗ>7“o,ž—)įĮ$…02 Î?ī<ūæ¯q‘Ķ1ÖĨ Û¤#Gú˙ęėėÄétŽn'g=Ē퉈ˆˆˆˆˆˆœŦ#ŸÕöâņ8………ƒļk䋈ˆˆˆˆˆˆH)|É!…/""""""""9¤đEDDDDDDD$‡žˆˆˆˆˆˆˆˆäÂ‘Rø""""""""’C _DDDDDDDDrhōŠčäȧ‡OE7""""""""ãŽFžˆˆˆˆˆˆˆˆäÂ‘Rø""""""""’C _DDDDDDDDrHወˆˆˆˆˆˆH)|É!…/""""""""9¤đEDDDDDDD$‡žˆˆˆˆˆˆˆˆäÂ‘Rø""""""""’C“ŗm4MķT×!"""""""2ĄŲlļŦÛŗ†/nˇ;§Åˆˆˆˆˆˆˆˆœn:;;ŗn×´#‘Rø""""""""’C _DDDDDDDDrHወˆˆˆˆˆˆH)|É!…/""""""""9¤đEDDDDDDD$‡žˆˆˆˆˆˆˆˆäÂ‘Rø""""""""’C _DDDDDDDDrHወˆˆˆˆˆˆH)|É!…/""""""""9¤đEDDDDDDD$‡žˆˆˆˆˆˆˆˆäÂ‘Rø""""""""’C _DDDDDDDDrHወˆˆˆˆˆˆH)|É!…/""""""""9¤đEDDDDDDD$‡žˆˆˆˆˆˆˆˆäĐäSÖSęyîšj)ĄƒƒwåM)`úô‹¸ĖãÅķWYāŸÅÔQęvßs‹™û­WĀģ’××ß:jíž ô܉ˆˆˆˆˆˆœŧ1ų’GÁŦ"æõū\Ėt[ŠŊ;;Øŧá1jũ)Ū?y€'ļî;õĨ[žígÖUĪĢc]ˆˆˆˆˆˆˆˆœ´S7ōĨO ~ø ĩslŪˇ‡-/?ĪŋÔ˙˜Í;7R[ž‹?û9ĩž3mŧÅļlíë"DDDDDDDd”ŒŸ5_ĻÎāʛĢųéŗĪ°b^ÜÆãßú‚gÚ�˜Ô6^ß:ÖEˆˆˆˆˆˆˆČh?áK/Û,îüáwøĶ k#?zrį Cöm}žUßŧ›ūČGŅė˘ušŸyåKXņĖVN$Ģ9ņvöŌūĶXx]úøĸĢn`á7hߝ‚đwņΌYåėîß o=ķOÜ_~3ŪĢæ2köeõõí×Oû7į2ëŠ~}Ø˙+ž>û2fÍöą,”›ķOÛ3āœüÜü˙ÄSĮ™ö5ŧöōÄ]ésžų§ƒ_Į^;~ē0ũŧÜĩŊ'\ŋˆˆˆˆˆˆČø6ĶŽ†aęõüÕ‹ųõē]lŨđ";ūr—ôėÚģų~k#{æQPä%0ī"lûwņjč7<ūĐoh{a Īüä&f§‹og/m߸ŋyĄ ō ˜;īZ.)Hą;ōcîŊå5VÔäĨC› [ßcöŅūw šwÃ{WĀßĩ\S`ƒŽ]ŧN÷ ˙”įūĮ<Ļ3oŽâ^[„§6t°?ībæßq-3møfLŨC> ´}㎞sēˆš7\Īe°wëF–—ŋÆÖ…SNōš›N`Ą—‘WØú\˙×ņ¨ŸÛäqÍÂë™>ŦēEDDDDDD&Žņž�—Ũp5ëvŅĩû5ļėĢæ’ŠĀîåūomdĪÁæ˙I]8ãhĐą÷E–UÔđô Ξ'=<yĮŒc7>‚vRĄ˛â….Č+fYË/øë9ŊØCđ›wŗlMé9åmkëcŦØđäąŦåɌĮ@jG#wÜō}^ßđ}ūåžy<8.™˙ˇÔÎúí:ØoķpīÃ˙kN˛îĄ¤ÂĮ:§;žŦáŽīŧrŌĪŨôžĘ5y¯Úļ‘āŽjūz`ú˛ãEžŪLš–Ûn8ĶÖ÷‘3Áø›vÔkƜô荃]ė홋˛å „”buæE?ĀôëŠũû™ÂAÂŋøWv Ņô‰ˇ“âÕg^¤ (¸ųoųķ9™˜Aā{ߥdj–{hÜHí×°ę‡+šŗßcĀvÉWųú\€]lŲ:ŧÉ6Ŗuū :§ûœ“Kîxˆ{‹FĄ†Š×r› °_ŋ0xęŅŽ ŋb'0eŪ­”ŊˆˆˆˆˆˆČihü†/}#HRė?°“WÃīy\yķĩdģNŸęģž+ķ€Qļ3ĪI;{xkÛūôcüžūĀT/ˇÍË2EgúJæßÄíķį¤ûIícīî=ėŪŊ‡ŨģģHŲō�Øˇ/Kp3*ueomMŸĶeŲΉY”ø.…ĻRrëĩLôÔŖ~Goåév^›Ĩ‘‰oÜN;"ĩŋgáÖ)Lčbį€ƒėxō¸sļuõ,v쇭{ û"#i§‹{ ˜9#[D`ã˛šƎÁ§ąãE~Tß@ÛËėŲĖŗ†Ņ:˙Œöē�Ļ2ŗ û“éŗf�īt Sũ_ĨdĘF~=pęŅ֍w_á6Ÿĸ9=Ûđ%ĩ3šžˆĪ›ÁĖé�)RŠôž=‘ėōŅŲ—:fË#h§÷16lĮČlSŠ­Üq×÷y}?L)ē‘{oö2gæĻæŲ€áuKy<:d'Y÷qÚېwės´c„5ØŧÜvCŋېžzôח¤W~ëšŲ ˏųVŽQö"""""""§Šqž¤ØōÜkėōæzšŌ`Ã6č*āëM!ę|#m{díØlĀÁŖáàŠĨ{i[ķ^ß7ŦṟÜ4` J 6äņ8Ùr4ōē‡lĪ<xėsJ ŧŨôHk°qͯP°Ą9ãŽG[yúš]ĀÅ,XčéIˆˆˆˆˆˆˆŒ{ãs͗ŨŋâGĪuy\sĮWzB‹Ė)�ØĮî=C1’v ˜> ‹Ŋ{ŗ';ļî°eá×”Ü=0xØÃë;‡ŧĀč¯)=į´Ũ]Į:§÷ly 6ß­f�Û6ŌžxũW÷�E_åļ9'Z숈ˆˆˆˆČÄ1ū—}V|㟄ŧĸ*ŧš7ļ˜Á5ž‹€ƒŧúÜ+dŋôßÉĢ›#ė8F@2ōvf0gVp-‘mƒOŊĖĶ/t ÜØķß<Ļf™R“ 7ŌÖ{ķŸc =9éē‡2‹šES€ƒŧŠ2øQ6ŋ<đœNώŨp°Í/īaËs/˛˜ŗđ&Ū}ZDDDDDDät2~—Ô^ŪzîÜsËbßvĻ\Míīã˛ŒC.ģģ_|ųû,{fį€Ā`/íߎážûîaáCĪ3ÔÍ~NŧŠ\sƒ—<`Ī3?æŠŨũ~ûĻŪíhsfŧGû [ûõąok#ųĐkL÷¤ŗwOfČ1%}ןÔv8‰Ņ:˙4×ܜž Q×s?āmíW![ÖüOíÍô¨“Šáʅ71 ØōB#OŋüäķõfˇR‘‰lŌ‘#GŽdnčė뤰°pô{J=Ī=W-%t0‚Yŗ˜žq]ŸÚßÅŪŊ]=ˇ”†)ŗžJíŋÃí— 2˛ûš¸ãī6˛į`E^Ž™S€-ÕÅ[‘[ģŒ¯đŖĻĩ,˜™>~ßs‹™û­WĀģ’××ßÚw{äm‡ÔVūųŽ;X=yáģÁË%ļ;"/æzVŨŊŸå˙ø 3úŲûÜnøÖoØĪfÍģžkfĀžQÚ#]\ōĀĪytúšáĄW8˜w1ū…×ō§wüwnŸõ2ËŽûKžî‚ŧÅ\3ËÆÔ›˙‘GÎYŨCÚÃw-¤6˛ō ˜3ˇ˜™S˛wk”×÷^˞š~ôČo8čũáõÖ7ujä5le՟ܯŋôŒøÉķŽäåõˇ}S&‘ âX™ĘŲßũîwŋ›š!™LbˇÛGŋ‚ÃoķĢuAĖÇųøƒ.ēēŽū|:„męį¸ė,Ēųkž÷5Žŧ ûZĀSg—r{iS>H°ķíßąåˇŧi~�Ķ/į+åËÖü ×f\ͧŪnã_6Ŋ 3Žį¯ÎÁ6Âv˜<Ģ˙¤„ûvącĪ.یūŽX×a>ûGÕŦūე$ĶũÎčįŧŲטõ1æÛģx+ú[^ßž‡Ļ}ÛŋũĢ+fsÁŦK™ööküįÛģ0Íũ|ö†;)qš¸ōķûxãŋļņîž÷øũ0Ķ+ æLYŨCšJņÍ×3įpœŨ{Ūeûļoīũ˜isđā˙\ÁŨĶĸ4nøü1Õ—sŪHŸģ>Ķq~ô"M˙‘^Īįŋ-]Áŗu›#9=+S9u#_Ns{Ÿŧßw^#ī†zĸ?šE """""""g–ce*ãg͗ņnīV^Ũŧ'6oͲ8í>ļ„Ķ ņΜuą‚é“}n ļįW,읉=yEė\˙sjįNíÛĩ/üVŊ°(âļ[g]"""""""2î(|Žš÷Q÷ĩWøË_nãņŠų´{¯æ˛ûöDŲ}ũä1gņwøsŨ7YDDDDDDD2h͗˛ˇž{ŒũüEļlŨC×ÁƒäM)`æœĢšíîûøķųŗ4åHDDDDDDä uŦLEወˆˆˆˆˆˆČ(Ђģ""""""""c@ወˆˆˆˆˆˆH)|É!…/""""""""9¤đEDDDDDDD$‡žˆˆˆˆˆˆˆˆäÂ‘Rø""""""""’C _DDDDDDDDrHወˆˆˆˆˆˆH)|É!…/""""""""9¤đEDDDDDDD$‡žˆˆˆˆˆˆˆˆäÂ‘Rø""""""""’C _DDDDDDDDrHወˆˆˆˆˆˆH)|É!…/""""""""9¤đEDDDDDDD$‡žˆˆˆˆˆˆˆˆäÂ‘Rø""""""""’C _DDDDDDDDrHወˆˆˆˆˆˆH _&OžĖáÇĮĸ‘ éđáÃLž<9ëžAá‹a|ôŅG9/JDDDDDDDätņá‡rÎ9įdŨ7(|šā‚ čîî&™Lō駟æŧ8‘‰ęđáÃ|đÁėßŋŸiĶĻe=fŌ‘#GŽd{āûīŋO*•Ō$‘c8ûėŗąŲl\xᅜ}öŲYÉžˆˆˆˆˆˆˆˆČčĐŨŽDDDDDDDDrč˙•˜/ŗG����IENDŽB`‚�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/user-list-delegate.png����������������������������������������������0000664�0000000�0000000�00000056146�14156463140�0023073�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��%��)���ÉÍ���bKGD�˙�˙�˙ Ŋ§“��� pHYs�� �� �šœ���tIMEã ''•Ãáp�� �IDATxÚėŨ{XTåū˙˙§}bf§‚&Ļ‚yĀ<€&%–š”¸K1•Z–•™Z–•iG-3KS+53ÔKTöËķ<lšMm€)ãá3ÃøÅīPAA”“ž×åUĖŦY‡÷ēī{ŨīußkĻN~~~>""""""Õä&…@DDDDD””ˆˆˆˆˆČ Ë ų÷Š„ˆˆˆˆˆTŸÛ]ø˙:zĻDDDDDDĒ“Ļo‰ˆˆˆˆˆ’QR""""""ĸ¤DDDDDD””ˆˆˆˆˆˆ()%%""""""JJDDDDDDI‰ˆˆˆˆˆˆ’QR""""""R)‘ōËÉÍ#õÄ)˛˛määæ) "•¤í]M9–rœ¯—,#ķō×_)(—¸éĻ›pŊåfž<€&ŪíúĖÉ 3›ļî Ûf#˙¯üwL{ö˙Ē+R ÔŠSã-73bø ŧo÷ŧēuåįįį+¤"åKHū—ÛÔįV“+NŽĘíE*ÃŪ˙ü†éŸĖųRÁ(K~>ԊËŖŸ*319™a&fũÆ}8JJDjWûS§ÎMLxņŧ<nĢđj4}K¤œROœâļõiP˙V%$"•ėĢo–)ö¨SōķíŠ×Æ-;/šĻíO~ū_Ė[øíU­FI‰H9eū/‹úuM „H°ff*åčüīĪ?Ë\,;;[ą‘kŪūüŸÅǤD¤*ũũÅ˙ģIUGDj=s#"ÕåjŸQĪJDDDDDĒ•’ŠĨIÉąônÖßfCYšQÖÂ,Ôßf-č=/ŠÖĮ˛v$žÍZā;h–ķ¯EžöäÅ×DDDDD¤:’š*{ßčŠoģIÄ)""""rƒĶ÷™–“)|>IŊm`0\ÅZŌØ›`V0EDDDDĐHIÅ\UBØŲ— 0ŠˆˆˆˆÔˆ¤ÄvėgæNJī@üšĩŽY�mB‰˜¸€õĮl%~ƒ°Ž_J÷΅ŸiŲ•āGŸcjTÂeĪyœ¤Íĭ؎­cüŖ]ņkւā7öWhK{ĻÄŪãØüb�ž­Æ°&°ūČĀf-đmČø-*Œ""""rcĒÖé[ļ„„?:“„'Üü:ŌŖ¯›…ÔÄ]įšIüÚÆÉ3ūG&2bĮ>6†´ķŸ öĀ`M&nË/,|åĸ7Ė jN/ŧ —7œplÖDž~åsĸĶ<iŅŅw_§j9¯ŪCnØÉĢ`uō!4ĸ ^7}UEDDD¤ōš4ëËÄĄmŠĪYâĪeé‘ę˙QÕjLJ,lžũ9 9NŒ‹"r”/SĮ$üŨ]˚ąŽŋÕw€Ôå<?6†´7BߏäĶpĪ‹ŸÉø™ņaå†IŒlCd„gÁ녹GΞ%Ŧô}‰ĩ‡ĐÄP}ĮŅ$ô%&û~ĖæU°Ú0üĩW RŨ¸aXŽí`uė.ĨŸÃš‡ƒŗ+ =î¤KĪžtšÃĨėáīŦ|o‡Ú<Ãä^t¤V3úځˇbrq$7ËĘÉôŖl‹‰aûņšđĢæ^„ŋ2’ûį0-æÚ`W‚ēvĀ'ī?Ŧ]ôōo¤,ëũJčŌÜ=äU†ˇtŧė\k)‡w°zõnRĒüÔ{Ķōxoũ”6éyŌë‹÷>;‚žŽųvq4ŋž-īûÕØ>zxQß .>Žpc'%fŽĨæ�n4 (ڑ0ĐdØ "ũĶ1xú$$ĀŪÅ ˆĪc÷W˜^4!pŋŸÉ“z˛ūŲâ/įXÄK4‹Ë˜ôøęZ'$;š1ũy8ŠžJÄĨ]BŊŠg„Üŗđīą,ŸˇŦįFęqƒ'm;žæũãÁŧÛŋi‰‹ØËĨY_Æ=åGÖî,‹MáŒëŪÆŨ!Ą 5—Īæ˛á„âTQŽnÍš/¤5œ€ŧüâIGYīWĒ3{Y¸bo‘í9`ōhEhhĪģ;đūįqœĒîaĐp^iŧ•×VUaĒåIISnvėĀČŨøéĢH6ģáQÖûRT5>SâI_#`fũŒ÷Jx~ĝmhâe*ü;‰¸øt‰ļŊģ`*aĻĀûië$ígīĨŋâցŽū5á8䯔ËҏƒœiˆūhŨ¤^ áĶŧ-ũžėË=îy$˙7㆏Qęņ?Č+õo{9pgPKę§odÁŠ8~Múƒ´Œ?H9˛¨¯Vą-ß;ÜĻ vLž„Ũ_˜p”ûũ*AjR G/ü;Ęŋˇ­â͍rī&¤qõ—O¯ÆˇéëO¯ģ;!><<ę†ĩs+ųܖõžTg\ ôxųMÚ˙2kö/áéžË1úļĄ[Į.tíŪ…nū¸v0“”ÃąČ)<[Ō:ͤFBšpķŦ¤‘Šō‡Ü˜ōø3ˇāŋ—u˛›2`ėĨ#VmŒfõÖNfæáX÷6ZßۇđN¸ųÂ"ÉÄFŞíØœÉÎÃÅõ6üƒ{Ō?ħp™d–ŊŊ„“!´>Íę$7úOBÃYÄFŗnw '3ÁĨ‘Ąa}iRtúX6ŋmüŽer*΁zč˙x/ZK:ļdž}Ŗ`;÷œÚHĖūĀ’įLŊæđX0M]ėŲ_+Ûž˜Îō$€%ŒŲS?¯3$Ļž˙ۍî/>ĪÃeÅĨ¤c!eúBN† 'äl,?íûƒ3Ų`ēâ1Imŋ´ŨėXđßË.ryGYūÉėK^tÅ˙Ū>„{ĶĐč@î™?8°)š¨¸?Č簈7ŨÃCéŌä6ęģ8ō§õ [cXž)ĨpoúOL͑hŪ‡0_3˧.!>īVZ…öĄW{o!+=‘ØÕŅlJ*zˇÔ™;ī`@ȝ4tČãôņ},_CBfM‹Ģ3í:Ķͯ>† Ŋ_Ŋ˛Ōį wbĒë�Įķė>÷Fß.ôëŲēÜL§ §�nHĘîäņ)Chû1o;wa[w>ö*/xläÍOâ8sIYģgÔ4ÂėöÄΜÍF—+mãRŪ<>Ĩ ŦmoBĪģnÃäÍ™Ã;XļbGŗí-×Ĩ”Ųė lĢĖú¸ŪIX˙žÜã{+ŽŲf6Æ°ŅØ‡Ņw%ōҌXŌė­‹Eô ĄghH‰į;&v#ëb7Vm!sŦKģū#¨įņ= ĸb-īûJJ*Ü/dĢøÖŊzņéO~<ų% WũĖŪ¤]ŦIÚŚe3ÁčCčČW˜<*¸đĄuļÂMĨíŒ),ŧĨÉÁrén •×H–ë8äŊ…BĶģŧpXĩ‘ųKx8¤%ūFK\6—äÕ ™įĀ= fDcW,Ių6j!‹Ÿatû瀕¸åKX}ļ%?Ų—ĻF°ßʡ+"ųļîXF¸�8:æq&n# í{ō|/7ēäō[ÔBtŖW˙´6f“ŧ5šå_-GŌ đâũŸb›3čšPĪ&˛ri,‹Ö5åūMKÜg‡<R7Ær4<‚×û×kË?‹dÁŠ[ykh+n.s}†Ŧ/æÛāQ^ŋÎąŊČß..öÄĨ¤cÎ&Í1´1ëË+aî8ZøöŗČ+“ÔfŲüöŸTrÃC90Ÿ6$áDf)Ŗnx÷Îč <ļ­X‚㙘|C>œašs™ģįāJ`˙Á<\÷ ß~ĩŠßŦ`jĖ Į"tv& ögyäæ:R/(˙Ũ1|ēÎĖÉlîė;œá-ÍŦ[ž€Vg|‚ûĐ˙ÉÁđÉ6ŽēÜՓĐÃ[ųöŗXrëúŅīņ ëõ¯­8ZsF ˙ßßđīL G)Īž9ũ ˙{Žđ~MčđÔuĮD6)Ö<ûĪŊsK ĄŪžīųôģ?ČrpÅģK=Ųë{Kˆ/wâ˜Iüâ9¸<=šĐS+˜u”,Z2üéōm#/͐PdڊsāęG˙į2âąsŧõÍA˛ė*×%•ŲŠl˞úáĘ=GZ÷(ËæE’åJëž=āáŠcvšv×ÅâÎ'—&&ՒšŪûá•ë˜ŗôܕß_GZTōzÍÚsˇG]n.R=]ģ^øcķN„š9ųYYœN?Čö#įjKRb*œBe%ÍW†0“Vøl—ÉtÉmI“/=FŊKQ`ËH`īÎ]ÄŽũ‘6$ûŅ(Ĩ-bíÛ1aĀ`Ėn \˛…i5Ŧĩŗû8äFUŋSŖŗWą,6†ûcĀáV<}ŊņhK—6>…œY‰Äęņ À€‚ŠUŋ^OáĶģHmß/œiū >ÜJÃzŽ…Ë„ĐeÛ>b§BĀő—3.­x>ğú�Y{‰Ũ‰wøB›Ôņá}ÉĘہålœOJ\:Đ?ŦUÁgÜJhķ­ĖM˙ƒ34Ĩaičҁ°€ē|Ŗ?=ģxŗ—„ŦV´s){]œqpœ1š¸�ŲÅ˙ÎúՎ¸”pĖü^ęyt <ĀũÂūúē_Ö1I­u&.’9Î}ڋ‘Ŋ ÷,ŠI)$ėÛËļũ)œ9ßpöŖg;ŠąŸ˛|Á…ęĖžh–5ö慐xî‰%lDÍe*į8uļāƒgÎnd[—ļôlîû/>P?ëW>Ũ”XpwÜ9€ĐöޤD-`тžåЍU¸8tÂT×Î'%YģX}°ā3qÄf´ĮmÔãhĩ?ûPØeÁ˙ūût/ĨËāđ7ēßGũē5uRŠÆÆ-éÖĶŠ8⏗ãÜ×mDC—LíI$í,Ā9ÎŦZÂÉ=ŽX*øh@^v6yš@^6ÖėlhTÁm¤ībõūs‰kf"1Û~'¨g[üōoė)×%”ŲŠl+ێúQ׏Ā&phy4ۏgf6}‹įĢCh˜]žēH™‰Iõ&$Eē…Í{1îéX¯đū(Ēā§; Fģ+Ü}ģšIB›\z´g^ûž„*­Šåěͨģ?š\!+I=@\*€-üK_ÎāîOPo‚zá…øˇŧ‚¤¨/ŲürGú˜<ņwĖRĶ,Pƒģ÷W>un\.4 Čä.VR“Ž’đŸß8t,‘ +öąaŨô*‚.Žp*™Ôŧ[iį[´Ž8âŨė6âRIą‚—Ņ‘›3‰_ÃˇĮ͜ÎĘ#//Ül nņÛ.õ<ŧ ;į@úšŌÎŖČÍĮÛ yüöâeã"ŸÁŖ3¤Ÿŋ›UJëq[ąZYŋ.yФž…v.öīoŠėŠK Į|~˙Üvqęāāâ�yW>&ŠÍ˛9ē)’iÛ\ņôŊ˙ģšŌĸ‰ĄũÛÚë7–}ÉöyĐĀ/ĮŗėI*ú­Hy¤ųƒÜN^xģBZfYšŽöęÉ ÆnÔwqĀÁÁGālņËčéôԋ;ÛņrĖdOz‘[ŨyŠlúîûbŸą/ōōȲfƒ‡‹Fđ*ĘŖ“?ėqi‹ĶĮv3E,)į›{Î}Æo:L—ÃqÜļ‹ĮRøíD&iĮ¯áÜē nÒūGąé?gN™ųĶŅ ¯ēđoG{Ęu eļ"Û:aGũ¨{; 9Įöbu!…˙Í%¨^9ÎG)!)š„Ô„„DĒ:)Ą ũēģąr™™øų_˛ˇ÷Ģ´-q~Tëg|ΞŽũΗđ$6oŨÅ1§<zųt˜ī§­qIV ĀäIP $ώv–đ^%¤%IÄŚqhC“ĒzŖÜĮĄBwÃs4âÕŧ-^ÍÛ üųß,ZÃĘÕûhũtLŲŲäqŽMŸŋÁĻË>ėFVāü;+ŋXČ6‡�ú‡÷¤igÉ$~ņ\Ö]ZɝŠŪĸ#LeÔ|‡ ´ .FįËVâ@^ÁÁ\û÷ˇô>Ļq1–pĖWqLrČË$íČ>ŌŽėcāŌ8ˆaC{Ņ/,€ķvcuvƁē„<÷6!%\ŋ íŧú ēäíeyT ŋĘ&W‡ŽĻ×Ĩ›ËÎ+Zųp Kyw^˙6+ ›~ŌĻgåũû~ųŽjČô­Sģ˜ŋtWaGہ†Á2ŧy*+ĮđkąGyė8÷™)D}6Ÿ“!ÁvéC—0rĪ$ÍĘũæk3Ŋ.¯bÛȲf_Vōp(aļëØJ(ŗŲ–ƒõÃÅGō.ų)LĀë•į|”ž5-ą^ĮœåįčõęĀ‹3!.}iU|ÜQV/^Męe͎ÚĶĨđYŌ?í*ū5é…͎Ē8fWq™64æB7ŧLlŌ† ‚io?K˙‹Ŋn[ęN–Θ‡ëĖ€ũ&=E‹ oî⋉Sˆ7´Ææ>ŸgŠ÷Ö3ļÄgÜ|iQxc´ÅĐFN!~ëûŒōãĶđĸ_Á›Áæ7Æ0jY2†āwØpūˇM*[ŽŒûmK#5ô]Á7†Ü,+YFL—4N7ß҉ž-ˇ’pđ'“ŗ3ÜJāÁ„6¸ŧgmŦüw/{2\ |6œ ;Îŋ™ÉŸY@Ũ+ė„sa')ëÚßå­l˛Î_´Ō+¸ŋ—í{qš¸â’—‰õ’ūVÖņ8bĶĸe#Öėlō8KÜâ%Ğē<[°žˇĨ]+ņŸ­ē8õׂ üŲ˛iL.×A@sū„øŋv]čæW÷ōg4ŗËxŋJŅŗœ<ņĮ…Î^ÚęZHŋ°Ŋ]qôâĶöœ{€ĖTļGG˛=\ÜŊiŨĨ'á&÷ėLĸŽ—rīŠŧŊĢ lãōAθœŋ„ĮVá›NEļåaGũČÍ#LÎÅģ Å֛}m÷šúd‘ēõ{æDŊ_īW3GvŗáHņ×Üëw!)ąŪÁęđ:WwīĐŊŸ~efÔčŲ˛ /<ŧ„ņF7ÜMN`1“fÍ)XÎɇ~ĖgZ×"v÷G˜<f`úc]ųŽMGZxēaÂBFZ"qûĶÉÁƒ‡^KĐųÖÍĢ?Ķ?ØIÄÄb_ 'xQG‚üŨ0ØĖÚš“sxŪĮô)}ĢŽŸ_ĄãđŖ…$˜wņúcXãkĀÔû]> ÷TOâzufŸžCnĪ1ŧâ~Ų]ȓg˛ÁXˇ` ­Ūû8íLÃEĻYeå4u Ļ eᑇ+õ‹tvrĶå@ž )Qŧv“rü,Üq>øØ/bHm˙(ÃÛ_Å!˙ƒĶÜ~aÚÔÉt3ynxÕŽ—cķJų۞¸ˆ�Ô âųW{Ḏ¤ĢsĨa=g°ž-ø‹SɤäPß9›SEnÃ:ßJ=Ît`p “ĶE’y‡F­hŨ�Žx›ķT2Ššđn|+?˙ŨGõÄk÷÷,ÚSÛ›Múž_X{ļĄct(īûÕĩۉŦ^÷û÷!l÷l–'å]8?e{‡ē^øģg“p¤`Ä"+#…øčXüÛGāåî\ĐļåÁÍÅöV6°#-üHŲÛ(ųÁ’úoŖ§^5đpÃ1×LęY�;Ęu9\q[í¨'8CK<8ÉÂãqđĻõŽ\號ēļû\=wΞ'j ßî)e„ĢŦ÷opWũ;%˙!,úĮZNzŒĐ6>˜°––N&<ÛtĄß¸Ŧܸ–éŊ=/ûaÁŖŗöËWė‡!m?›×ũČĘuÛØ›f m¯§øđ§(>í]ŧįÕû#Öū4›ņŊÚ`2īgũĒYša?Ļ6<4rk~úŒ>Uú5W8C0ŪL §9iˆÛ—v5ßa&ĩAŊ�‚Ü8ŗĸv°įØī¤Ļ˙Îo‡÷ōĶâ…,?âL̐€‚Ž]üiīJĘēīųéā N[­œüī^–}1›–ū ˜Įíx:üAÜļNZ­œ<ļƒ…Qįđōu īĖī$g•ō¤„‹Ąmn%%v?üÔôdâĸĸYwŧ×ŊĒCt9ŗ‹•“9iĩrúØVnũã]mņwąwqq€ŦSŋq(=ƒĶY—üq8ģÕ;2hØk8/õ ân_/<yqgŗ�† §ķllÜWĐ_ĘNdãîLŧ{=JØ]ˇQĪՕč˙ô&>P0#0ũwŌrÔŏŽŽ4đ bxø­¤ËÅąŪíx;;”ÚŽŨŸĐž„Ũå…g#oûöĄWcH9~Ž–÷/,IņŦŪt€9yŋšî í‰!æŋŽt ÅÛáâų)ķÜģw`ĀĐÁ īr'žu]ŠW÷6üģtĸ)įHIĪˤ¤įŌ°y[ŧœņėŌ“@cî“ģŦ<¸šASüšQĪŗŦm”ėĪzčw¯7 \]ŠįDŋāFXūŗ—„l;­ޏ-{ęĮتħC‹ĐžÜŨčVŒîŪÜ;0ŸÜŦbõåZîs•ËJæ§ysYTZÂQÖûr~§ÄāIˇaoŅmXų;ô^]‡0­ër}ĘÔä~žųä~žągáĐĪH<RąÃ2õžORīK^ ŸORøÕ‡{×W‰ÜøĒJā ÅĻá#xžņFbãv°|÷9˛ōĀÁųV6ö&lT(!MÎ7šŽ4 ÎįVG-dSf68ģá}WOž k[0šb `@øī,\÷=ĶâĀØ¸%aá}im!uņ6>ũƍ-é×Bi>˜a.1Ŧ‹ZȆL06ē“^OöšđuĀÎģڇŌÎē‘9ĶS9“įLƒæ=î_0‚a×ūö E—ö4XĮÜĪrĪ“čuÉßƊ‹Ha§īčĒ|z<„Đ Nôo_—›!7ë,'§°ú‹X6]øũ‡<ŽŽZČ‚ėž„…į^Ŗ deōŸæŽŪWp7sËĸngx¯G™Ü ,˙=Čę¨U0öÄkh0Ī? }RŌėë<ŽF-aaVOz…'Ô–ôŖŦû*úÂ××Vš'ķĪ+A];ā“wŠ˙+įûUĪĖļčŨtyŽ=ũģėåƒMØwîD3'Ē'ũBú2.ˈcn§3Rˆ˙n ëN�d˛gu4w>Ęķ“ƒČÍ>GĘîVīvcDs‡R:YŲÚļ‹“ubôs-ŲöÕĖ2ļQJĸĩ;–=ÆF÷ĸžC6'¯cATbሂĮVž¤îJÛ˛Ģ~IJiņ* eĐØ—Āú;bcøÉÁČ;°ŋ.Ö8f~KĪâNĮƒ|ģ8š_Ī–÷})ĒN~~~žÂ bŋŊ˙ųļw5U .HfŲÛ I .ijšČÕÕĩ¯/Q Ęiö‡o_ņũEKŋ¯ņĮ°g˙¯:‘ĨōĻ˙ä'đŪZŌôÄŧ-g\ G‰ 8s÷“ãÄ*^ûę`혞uqi֗‰CÛRŸŗÄ-žËŌ#Ų×dŊeĩ?W,":-""""RynåžQcčįrå+ļō›Õz̓ kžGÂŌŖJHĒA֑UŧõÚĒĩOJJDDDD¤cûâH\ÂC {ōyL.šüy&•Q ‰ÚŸ­đˆ’š|đÆÛ ƒˆH•HaųÔ7jßļ2˛á›ŖlĐ ”Rܤˆˆˆˆˆˆ’QR""""""ĸ¤DDDDDD””ˆˆˆˆˆˆ()ŠÉ•æĻ›ø˙ūúK‘§N: ‚ˆTSûŖ¤D¤Joqá”ųŦ!RõÍÕōķ{äįãzË-e.æėė (Ļ"rm۟ŋ™LJJDĒ’WŖœ:}Ž?2ΐ“›§€ˆTĸ'‡ ¸úÛo7Š:u âU†Ž�ÅTDŽĄ›ę0ę‰AWׄåįë”Hyåäæ‘zâYŲ6%&"•¨í]M9–rœ¯žYFæ˙ū‡.Y%å"upŊåž2€&ŪíúĖÉ 3ˇė ۖ]#Möė˙U'V¤–0ēŪ¨'s‡—‡’ŠŊ4}KDDDDD””ˆˆˆˆˆˆ’%%"""""ĸ¤DDDDDDDI‰ˆˆˆˆˆ()QR""""""JJDDDDDD””ˆˆˆˆˆˆ’‘Já�üû EBDDDDDnjĪí.üŋFJDDDDD¤ZÕÉĪĪĪWDDDDD¤ēh¤DDDDDD””ˆˆˆˆˆˆ’%%"""""ĸ¤DDDDDDDI‰ˆˆˆˆˆ()QR""""""JJDDDDDD””ˆˆˆˆˆˆ’%%"""""ĸ¤DDDDDDDI‰ˆˆˆˆˆ()QR""""""JJDDDDDDĒ+)ąp(ę=F=ڛŽ-đmÖŋv]é>h_Įg(˛×‚mÃZļŽYWÆĮ_˙žloŗt|c§bUĨvōzႨûvžÄfKņw÷žĶßf-č=/IĄ’Ē+‹Ĩũ´ KĩėÛ~φ´ĀˇY�ĪĮ–ô÷ĩshFAķ{ņįk˛œ¨Ŧ—^ޝvškWžEŽ7Ž4ķ†ūQ"98áé׆Ž&ŠI‰$íü‘iƒ÷“ļ"ŠÉEXŽoæų0ō)ēōU,¤Z9=p7]ūēÁÍTM{ä†Į.t5;āŽķ#×sY‘jLJöķŨ₄$pĘZ"#< ^ļ%0÷ŅĻ'&ķCäN&Ŗ´DŽw ķ?&:â3účz(Õ(čí(õŽI…Đ“ŋ0ŸŋëÔČu_ÖEäZ¨Øô-›™ €w7ˇ‹¯üyâĢĩlŲŊ—ũMHl[ûŖÂēĶĻe |[üč8æ›æõ3ŖZļŽYo>L¸øęåØį‡p{3uËĪŧˆ_ģIlĀÂŪČID<Øŋf-đëܛˆ7Vq¨ØxnqķÆņ`WÚ´  MČ�ž_´ŋšĻ7T‚ŒĖ}q�Áí §ÔuîͰrĮ ´y=>č7 ļã׎;oüLją2’Dô;#éŨš`?ÚtgÔŧd”´øũ,}1œŽ-[ā×y�ã×ĻaËØĘ‡ƒēîßHæîŗÔĀsؚĐ`7°ūÂŦŲ ØŽXgn„xHísåŽĖ2]é,eް$îg�~íz3lÆ"P<�� �IDATΤ–TųėZîJ×;ę•=í­]m˛T^šžÚkU9 ü0ņ ëēü‚\v˛ˇ<‹())…Á‡@_�+k^‰āųËYŋ/ ‹ îžx™Š[4”đąKˆMĘĄI÷Gčč†e ĶG0~Ky[f€™ÍīžĮú?‚|0aãЌaDŧų#ņntëۓ SņË^#bôō†ÃFÜ;ÃøQ ņ6ú ëO1‘5īeØĸëaūsŸÉôu‰z1|čcôđ´˛eŲkDŒ]WØČÚƒŌb\äõw^fVš=ēûa˛Ļŋlã#ĶΡ˛ŦŸ8Œo#Õí~†퉗-‘؏F2jQÚåÛxs +iMŋ‘ķVNðŅīįŲ…nžŦiۘ>öcöÖĀs8ō)œ )r&ŅĨ>JuãÄCj›Š–;{ĘtyŲŗN ëßÅäu‰˜ žtëŨÖ)ŧžÖ|ųēėZŽôëIŲõʞö֞e¤RËõU_Ģ.Ú;{ ŗ’Üh苡p]ŖJ}vО2do9š1Tpú–/ŋ=–ØŅ3‰7'˛fūÖĖœÜđ ŧ#Gđx gÁH‰eÎ>€#ĄŦd^owĀÆĀwÂéˇ8™•3–ķt×4)÷>XIu{‰ ßöĮĢp;Ã%’ƒũfF2ŊĢ,?3ęÁqÄî[ĀÂ}ũ™ėšŽY‘É€ÃgÎ/xæe¨ÃB^cËü/Ųņ.Ũjķ|3Ë~ļ$æ€ą'æŧK`ëOčėmdøēÜÉĪ(O .‰1ûYSøz†×›l˜Ķ wlt%”áĢĖėŨ°K„'&Ë~ļ$ņõõĄßņŒ?sK$ôŖdö­ŨFưū¸9ŋąD}Œ)ߌ{ĻŸ“Č1ßElũ #†TOŽŨ7“„´ÄƒļĻv=aBø7 \ļYŗwŌįíŽ%Ÿ—%RmâŪ'xÆĨ¯ēŅgæ2&”Ũž–ĢÜš—§L—Ŗũ*k1,Ü`†ĸíŧ­/SŽ`aŅ~œŊ˕v=ÉXEDYõĘfG{kO›,•XÖ¯åĩ*‹įŗløĒ/îØ84#œ‡æ'“°l9{GŊJÛKwĮžk­ĨŧåTDIIÉ÷!Fšąqk׹fÃĪÄÅ'’d5“´uĶļÆ;%’Č_Øˇ8+āԁ‡ēģ_¸‹Ņļw<'“–¸Ŋ–4ŠĀôĐļá= ;ËĀžmėÍœZXØ 3ŨĪŧí츁-Û8”8ųâīf!#Ā“&^°%i?ņIĐÍŋ6ßō¤‰ěc˙°™5Á÷Ķ5¸ŨƌĀũ|Į4Ą|1(㠜hÛģKacm I€Ŧ2“c1cLĻ`Ļ­.vĮČäî$ƒÍ‚Š4ôN´íˆ ĀŨ/7ˆ7;Ņļ{›‚¤Öˏ&FH°Z0[´švMA`ÔâŖ>懑‹iqŲ"7R<¤ēäXĶIŗ^ö*ģzžå,wMĘSĻí­JvŦ3iayoM×ķíŧĄ utcaR‘^œŊ˕v=ą§ôĩŖŊĩ§M–J,ë×öZÕ6üū ëjŅŊnķ“1§%rĖm/íÃØS†,å/§"JJŽĐ  APøĀFFÂĪ|ņÆîˇ?ûâ"Ūĸ­ÕRp7Č`ĸØŦ.Ŗ& kÁķ)åNJœp7^üÍjž°C)ŊÍj-X&gîëvÉģi¤Ļ5˛gãō[j6l^;ߘudœWČxåsb“vą&ikN„ž6ŸO#|Ą\1(ãbũ§‹¯. ¸…Ŋ‹Ļ0uņ6ĨYÉ)Ģ/âtá ^x$L†K_ËÁVSĪĄ×#LˆX@ŋŘ5{Ķ/ Ų Š]gÆ]ņá_ËڑtģíBųsę>ƒũs<+TîĘ[ĻíSö:m–ķ×Cąë‰ÁdĖå^Žôë‰=õĒėöÖ`G›ŦÜäڗõĘšV0ŊL˜�36,%ôaė)C6Ę[NE””\~aČH nįŽŲüø{x›ÂēhĀŨŋ“_ÛÉúĮVfI'ÃŖ cŗÜÅ8_ņŦw*p2^ōÕ~E;Û`ąØwš3%nĮf)øÛ`2a2ē,ãԁ§g>Eā%í“{MëŧÜ îĻåX9–”]=‹„)™}…ĶRŊ|/~Ų€)`ķū1Kę~öîÜĪ– ËųaC2ąīLâ‡āe<^1°m™Â¨wc0;ų1đũą<äk‚ ī1pūĢI<‡ڎ|‰ŽQ/ŗeÕį|×ŨpƒĮCjǜk´žĘ(ĶöŦĶ`8=ąkį-Åo›ÛģÜÕÖĢ2Û[/û–‘jēŨWŽrlÃb-Z˜,…}&SÅʐ!áęĘŠČõĻbēīû’įĮNaÚ+“˜›QüŽÃ†īšûāe�ēdrvąfƒåBåŪģvi�ū÷d8Ÿœ˜9”T¸NÛNÖlĩķnÚ:9ˆ/܎e+ã "đžPĻÆÛĀŋ-œ :ųøĶ­k0Ũēúa˛YąQrÃRŊü 6„|ū{üpŦ0[ŗĨąų÷‰6N~<\˜Ŧ¤neéŧ÷˜•€ÉĢ Ũ‡0yÎlžörĖ$™Š’ۙXpĮ÷ž&(Ā[Fza¯Čzuķ¨kę9tīń ‘Ø Ɋ‡Ô8ĻŪķI:rčÂŋÄ9Ŋ*|—ž2Ę´]ëôoS0=2'‘Ø-Ûų•;/šNØģÜÕÔ+{Ú[{–‘jSžrœÃŪĩ?~s–C[w|ÖˏĻ –ĄĢ-§"י ”B_bBđ6&oMfåŗŨˆvķĄ‰XŌŌHŗæ�FēŽRđā—Š/Œų†Íī vb8ąņ˛îgũÖdpōaāˏÎãõŖkG#ß­ŗ˙Î(žOč�ûļqĖč˜ËžÅįŪ‹"ž$nq2kÆÃÖŨÛūŸŲb§6ĪōtWЋÂ?gā˛Dž<€Ôî~’ļŊ3ڌem×65­A—_ĸëÖ)l1˙„žm™ęæ3Ö�'ÆŧÉãž% 5ķ—oaī–žy%i҉€gBũCåĮĀËß'’ÉIü†×ß1Ķ$më3<đÅLRŌĪĖšŅ† /ģU¸ķ_SĪa‹‘/9†XĢâ!U¯ä‡CG&˙Tøõ5ReÚžuŪĮĀā‰ßjfÍ+Ø6´ÁļĮ0Ö‹× w;—ģĒzeG{›aĮ2RmeŊ|åØ Ãžé=čg‚ÜŌØŧ.šā< ˙åĪÚ[†ŽļœŠ\g*6R‚'Ī‰bá¤GčęįÁ’LBb2˜đīø¯Åŧđ‹SZ [LÔûƒéę›ÃŪ ?˛2ۊ{ĮĮ˜úm$ĶM;ā¯Íæé`ŒļDÖ¯Ũ…­÷ģ|áSøž­ŒģoÚžļˆE“z`JcķljËp#pĀ›D}5¤đÛŊ ŊÉÂq÷`Hfũ˛D'@ÛžoųUEžŦ xõgŪOķßĢžnFlf36ƒžaü—k‰ÕæâŨN¯ūĖûęMúu4’ēa _Ė_ÂņštŠ/–ŧJĄjb`ęũ&Ÿh§S:["$Îé>ũj6“ø`$™Íkw^ÅWaÖāshēŸ #ũŠ9ÖtŌŌJø—dļķa÷ę­ãö­Ķ>īĪf|°F[2›ˇî‡îīđéĐÂë„íüuÂŪåŽĸ^ŲĶŪÚÕ&Ku•uûĘÜų)å&zLųˆánÉlŪp�‹Ņ‡Ž#g3o˜įU´ÍW[NEŽ/uōķķķŠ.7)"""""ĸ¤DDDDDD””ˆˆˆˆˆˆ()%%""""""JJDDDDDDI‰ˆˆˆˆˆˆ’QR""""""ĸ¤DDDDDD””ˆˆˆˆˆˆ()%%""""""JJDDDDDDI‰ˆˆˆˆˆˆ’QR""""""r5�’?ĄHˆˆˆˆˆH•ņšŊŅ…˙×H‰ˆˆˆˆˆT+�ˇ[] Š)%%"""""ĸ¤DDDDDDDI‰ˆˆˆˆˆ()QR""""""JJDDDDDD””ˆˆˆˆˆˆ’%%"""""ĸ¤DDDDDD¤R8(""""Rš”Ôt"ŋ_Å˙ūĖâ¯ŋōëP:u¸åf÷įvĪFÕ˛)‘R’ų —bÍüS Éu,??ŸĖĖ˙ņÅ×K9q2CI‰ˆˆˆˆÔß­øQA¸QÔŠC~ū_,Ž\ФDDDDDjŽĖ˙ũOA¸ÁkfĻ’Š>ųųÕ3MOI‰ˆˆˆˆˆTĢkš”ėzˇ'm"“\Šģŧ‡Š=ylaŠÎ^Ĩž Åš4Û_ ĻMģĀ+ū{2ú4p äáŲG*qoŦüōęũ´i\¸Íš\>TĻD¤bĖ˙O—v=™ü/k īÚØõæ#´é6ž˜+‘ÚĒÚŋØüÃ3<öë~™ŌIgŖFņæáq“šĮģžBq‰V#?áË~į˙ú/ßOú€ŪŖųddKœ _5z×NTūÎXwđũŋĀÛە?l&­O8ž5ļ|”˙3×Ē}P;#Rģš=0–VfÚG x´ŨK´6IIÉ´5§i˙ętSŦD””TˆÃŋ!GįĄĒOëRJ`ōiGŸķŲnCŖfÜĶž]Õ'õ›VąÛЍÆ91}\˙Lį ŸšZ>Ęû™kÕ>¨Šũ<xôÕü4đs>\ΡŊ _Oįûw—‘Ūė)>ųģ‡Â$R‹U|ú–yŸ<÷]:ŌĄĮcŧ¸p—M1īáëqOĐŗ[Át—.=1aá,�œæû‘]Ŋ&ëšiĶî1>9\ÖgŠČ<Á/3žáánÁ´éԓ‡Į-f—ÕŪm.˛{1†>B—N´ét?=‡žÎ×ģ‹Åiļ/Īc=î§Cģ`ē<ô“8rųžTj¯ĶŽx”y.2ĩG0O~ˇ‡ŸŪ,XW‡N0hÆfĖæ=|=n÷u ĻCALøá6āōŠ6į×q° î=î§C§ûé9ōcļk¸ŧ Vv-_XVKŠYEËY:˙øá Æ{{sOį‡Š˙?­IŠ@]­Ēōqųô­Ōë`IíÞÂmėāÛįĄC§øÉZÁvƖÎ/3^āąÁ1ī1ˆgī ĸûZÂļD¤úøôeÂĀFü:o&?ļ+æÎ䋃xtŌ�|ĘŅĻ–}ũ‘Z’”œæ§IX˜ŌŒņķWŗha§ŋaîĻĖâËŧõ"ŗRŧyúŗ%Ŧ‹^ΜqÍ8<oüĶ Ô'ėŖ%ŧĐ Œ=ĻōķϝxĻyYŸ)Ō‹žÉRú2uŅ–ônģį0ö­Í:"eŽĮēžiãž$ĨųXž\šŠußÍâ…æ)ĖķvacgãŒg=˙­ÆÍâĮčåĖǻžabtz{âaĪš�ƒS‡ŋû’ÃLįĮÍ[‰yĢ))‘o3xĖ2œFÎå—ÍXáÄÆf–:'×ā”ÃáEŗąÕDVŦ˙™]ŅoĐ>}9gī(ė¨J‰)ÉĻ™|}:´°ŦÁãpҘ]E9KŽeõ‘ú<اZö¤ü3ŠÄr×Õj*WŦƒ%ĩ N9¤¯\ĀövcųjŅXBŒigŦlw4cŖsčüÖWüøĪå|ųęŨ˜x‘Ņ3îkIÛRYŠ^ZœH˜1ŽYŗw`ąî`ÖG[0>4–§[ŸĪeG›Zæõ_DĒCÅĻo™7ķũčüÖDnUpĨžīåąØ1˜…r%dŌ~¤>…§ĮS<úŨZžø×Ax Ŗ+NN€“7Ŗ°•ų™ ũ™Fáŧũr‚ųķ>Īōú°<2 ˙˛vãAcŲÛ&ũ7’3ësOŸnøy�xā9iŪ}NãfŦ›™ģōŋ4šœÉxŽc"¯˙úožZ´ŠÄ>ĪâWé§ĮŽã°ë\6ÕÍûōtįú�Cįn´b )í†2¨yÁį|îí†ĮgË8—2/×Ö,œņxc�pëFXģzŦ>r„t:áŖúTrRâÎëĘj#žčü ŖĪĮė*ĘYâšĩöåíVûõ §ųw‹ų~÷ŗLno(G]­ĻōQF4.m ?æÚƒ/‡w+|vÆVūvÆŧ†oן Õ˜9ŧØšpēGČKŧ~0Žū?,cû˜iÜg(i["RũyI;Ə eûË0ņ´ģéĘ/wÂtĄÁĩŖM-ëú/RfīŲĀĮŪ$“øå‘DŠžßõhĐe¯„ŨŽã…WrI^=—ˇÕÎėēbIIĘAŌŠĪƒÍŠÖŪ;čĐĖ•‹33 ˜œNŗzöL&ü/éV9ļl™€GNŠwAėũŒGĢ–Å: >ÍŊ1æ!1lnĮzŧƒ¸§Ņ7|˙ę3Øöåžvíhßŧ>~­ Â=ōoį4âÁöŪÅīŌtjŠĶʃüjŋJ ÎŽã°ë\ÆĖÛûbÃídÄāÍ؜#9ä\aōŊ›wŗ‹ë� Fädj¤ä ܊•UÆúF8Rŗ”Š–ŗƒ|˙Ī4ī×ûbŌâŅ•°fŸķuôŋ™ĐžSAbP“ËGYu°͋ĮŗÜíLĘÁ‚˜ˇ*>˙ܧ]3œá@:ÜįSŌļD¤&0…Œå…NņÆh˙Ö'ÜW´yŗ§M­`Û#öræÎ>ƒ|;7WxY$odnt Y5đøüÇ3(ĀÔ%dčp/dyĨ'&Ū<>å ‚\Ž´Œ#>aĪ3;Ŧh(÷2ëÍUŊn“kVœps*ہv2yÁvGæ{§ŪŧūęXÚ{1pšÕã3ˇ´õ–ã3ÆúŽÅ_prÅé|‡ÉžõÚ1aŅWø,ZĖOß}ĀeâÔčnÂÆLdÂŪŦ™äp‚ĨÃYzŲŽŪÕ TvRbĪqØs.JÎw †SšvÉÉ æļŧŽŗ –3ÛŋĸØx"‡ĶŸõ§Íg—vēײŨÖŠānM.eÕÁŌļqĩíŒ5ŗ &—4!'ׂ¤+ŗ”m‰H QŸ{h;āÁNhS+Ööˆũį.AW“�¸āԁ;ŖSøĩ†$PîûގV˜ņnî@ęÖ­üvW/šē�Žîty<”=ĩ¤ãũ%%F' ä`.v#Ō†ÕZäj~d ˙HŠGØĸ7x¸ÕųOcšR"YŽĪXO_r4'“œpr-ĮzÜZōčËĶyôe°$īacäLĻOzƒĮL0ēâD#Âf|ÂŪ—vϜ¨__ōaĪqØs.¤æĒP9ŗ˛=z3֖CørR(ÅfXw0}Ė7ŦŪd垌5ŋ|\ŠļēFu¤„˜ÉÁ|É26k&V\/KVDä:lS¯ļí‘+HdŅko°č:J ū<ļžšĢÍÜ=t8š;ŧn žXĮđ§ “?'sĻŌ÷5…ĨožQB˛}Ŗ'%Íđ&–ä#Vh~žKôģÍáBɚCõņ,rˇŽec piC‘SūȏÄLŗ 7‘“§`uēƒÖĀž˛×cK?ČöWîé\pWÄäĶŽ‡Į=Ëöč NąÂŊwĶÚi-é™F||Š éZĶIÃŖØ•JcO<ė9RƒÛà ”3ëžß”CëqčĐüŌék÷%Ķĸ7c~ā!Üjpų(ŗļē¤}¨hš´iŪ’æNĢ8đk:™Â•ŧį9Ž-iŽošŽÛÔ˛Û]<¯Ž#ŪHkĮĢ\Mnķ_‹Ŧä‘ žĮ(k[nÜ3t8Î?cŲk0#(HLÂ‚ķˆÚWI Ā {gíėmÖ¯Ėyķ{jIÉŠØˇoytåá–đ¯yđũîŌ’ōw?g{N‘žAŗ–4wúŸ"7“l>MōîeL|÷ÍÛ9‘“~V`Äh�kĘļN!ÍĶžĪŪđH‰bÚw{H6Ÿ&í×e|ųõī}ˆö;ˇÅ´q/2ņģ$ϟ&-ũÛŋ[ÆnŅĒ™ŒŨxŧO}Ė~O6!Í|šä_×0udúŋē†*y„Čžã°į\HÍUrfŪ´ŠŨ´äÁ{Kš˙l$äģaĪZ6šŠŲåŖŦ:xiû`­`št=tã‰>øuŪTžūW iæt7~Ė+OĐüīh¯š"×w›ZfÛ#RzBRĀŸ^ƒĐ`KWėæT^UíL6YŲåX</ģ>“s…PWėc<úŪ4’ßšÉŦ1ũųĐéÚ˙},úĖäÅķ_5ę֛×'dâė×yd%ÔoĘ “&rڕÃãžáɑ°$ōYî‰č‹÷[Ë=,–ŋĪūŅŽĪ‘csÂ{ÔXLų’Ņũ’žãJķÎcųdRˇ‚;Ëvm{"s&ÍdúĸˇüŅrœ\ņ𾛰w?á™æ�:LšÃ'ƙĖzī–žÎ×;h}īXæŧüUōŖąvưĖs!5XyËY:˙øáßĐj"!ĨBĶŊĄ´w*?­Oįҁ5ļ|:—U—´Ķ/Ÿë]Ąv&†Éãæ0ĶéfŊ5˜Y§Áب)G~„áÍ4Ÿ\äzoSËl{äšĘ=ÁļīžgõáLŒÍC>°^įīôįūÆÂ7—đīĸz‡–<3å1ZÔØ„¤€åp,?ũ'ģŠw(Ŧ,ėŸé•Mn-**uōķķķ­Vũ*˜ˆˆˆˆ7ięôr~ĸøô-ˎ¯ysU y‡wöË ėčUWÉô­Â}Ĩ„m]1!YÍŦÅU9BrŪ­ÜûėKôģÃÎÅ˙ģŽ7?ĢĐÔ˛w'¯’#2ųĘU7Šr5u¤¤F&$Pîé[Ų7Äô-‘+3ĩīIŋ#§oõk_¤ŖīؔáīžÍđ–ęëû‡_!!YžJ‹đ.Üŧb)Užoyü™› Ø÷¤{nn횾Ĩ¤DDDDD*‡c#ē }ž.%öškâH‰+ =J!IĄé“#č×8…ų+ĒcßōČĘÎļ;)ÉĘÎ&¯%%""""rÍYŽfAÔ>RŗŠįîLV†™,g/Ã#ĐŌXCGJnĨĄą¤„d7§ōüx¸ąKĩÆ4+Ëū4#7+ģV•%%""""reģ›”L€LN(üÆĮĖTļĮî#¤e0 kėžį’›•Í™ŗį8ž‹•QûĒé’öĖjĸ‘eÍĢU%FI‰ˆˆˆˆ\Ã.=€;Ącß&´ÂkŠŽu ˧Ney mÁô­kŋŦ’šŽ¤°-îwüƒoῊwŊIŽÛÅŅĒJ ũųáÛåøPõ@dY ^w´ãȲŦJJDDDD䆔ÍŅčLŒž^¨ĒK˜J’wlÛŌŊ ņ¸ōŗ-ĻīfãąÚ5}K?ž(""""%*˙'Ęõ :~<ņ&…]DDDDDĒ“’QR""""""JJDDDDDDĒ…ž}KDDDDJôŲ‡SĢd;ĪM˜Ŧ`+)‘ÉhĒ‘É%%""""ĸ¤ZMX” Tž:u””ˆˆˆˆˆģ”+QōRAųųĢŠœčAw%$"pS†ėW-›Ö/ē‹ˆˆˆ(š.iÄÄ~ˇÜr3CôãvÛĒl›EŅ]I‰ˆˆˆˆ’‘†ÅjQjˆĸI‰ž)Q2rCÆI JÍĄgJDDDDä†MP”ĖÕ )‘>99OŖ'ÕC#%"""""Ež()‘ŒĻo‰ˆˆˆˆ\âŌŅMëRR"""""vvžEj#Mß%%""""R>5ņėëyŠ“€¯\šž%""""×,)+1Šíûķû¯gL””ˆˆˆˆÜ°ĒŖS-;āį×ĨQš,)1ŸËT$DDDDjcÕn.é÷tĖ\ûžbŌštģ–ķŊŨŖF'‡Iŋ§ĢLV´(/æ:ųųųų ‰ˆˆˆHÍV‡:Uļ­|ōoČãŽíąĒÍ4}KDDDDjlģč>ÕÄĨu”˜()šžÕô‘‚ęNšŸëƒžXDDDDjuĸRŨ#JŒŽžFJDDDDj ut˟œ(vĩ—FJDDDDäēKPôœGíĸ‘‘Dwų¯mrRU1=ŋ %CŖ‘%$"ÕJ#%""""7¨åŽūĨĮY™ÉŸFL””ˆˆˆˆÔJU=BĸŗÔ4šž%""""RËÍÚN#%""""7ƒ‡”ÄåįįĢtŠˆˆˆTGGŦLÛ:–rœ¯—,#ķō×_]—įáŗ§VÚēŸ›0šæ—Ã:u0Ūr3#†ÂûvĪjŲMßšÎUôw;ŽĨį“9_bąf^ˇ ‰@~~>k&ĪžOjúՓi¤DDDD¤Š;`ĩäÁöIoˆ53ķ†8'•9ZĩcĄü|nŊõoL}íå*ß´ž)šN’Ģ}†äFIHä|áŦÃ˙YŦÕ˛iMß‘^ed|öáÔJš&‰l5MĸŌH‰ˆˆˆH¨M#$"U횎”ÄŊŅß°ĢÔ]ŪÉë[Đ{^Ō {ŌĒ&ÎR:ŅŖđmÖââŋ–t|p�ŖŪYN\F ÛO•ĩ•Pļ2âW1õÅĄtīˆß…:0”įgŦãĨ˛ÚíšļŠ*kŸŖMŗŽŒßb)ą<ÆMėŽoģįˆÎPŦäę<7ar•Œ˜H%'%jh"‡ŌqâV Š}<a֒E|ˇdßÍy— }}ąlxāÃx‹â#×iNžĀŌŅŊéūʏ؇0}Évūē—„EąhŌ#x%}NăCų:ÁĻXéēx͸÷~• mŦ|÷söÚ.-’Ÿķú*3/ŋJwÅĒĸ߲%rƒ'%6íKD—.Š•Œ>´ėHP`G‚ēŪĪßGŊKä?"ī›Ėc§°^y‰\‡ ÉÜAŖXčôQ˙XĖ´ˆûiÛÄ“Á€ÁäI‹Ž}™0'Ѝ‘0kô6ĢčēxÍxōø”g H]ÎÔČĸ#\i,}ãRũžeZ„g>ũHŸHš÷Ø~��ÛIDATe%%[ųđÉŪ´iŲŋÎŊ5o+—šfėdîčˇ+˜ęŌĻs8ĪĪÛIÁu*ƒĨƒÚ2|•ëĒQø6ë͇ e}ĻkëßJ÷vøļėJ÷Ņ ˆŗØģíÂEâđüŖŨi͞`ęAđŖã˜_ô(2Ø<ī9zwįY�mB0>2*ŊÎÚį+îgK ĀīÉU—뎅ž Ā÷ŅoHU=¸v ū<ķūSø›cø"*ÍÎōhī9ǤōhKcũ;#éŨ9 `ŊÃ5ck‘rą“ņíŸˇ•&ƒßeuΞeė9†ŧŪ9€ˆE[ųúÉîøĩÉęØÖ6ÂÆŪ/ŗĐ4–EŸôĸ iŦŸ1’î ÚāŪ/ŽâЖˇčØnŠ3˜ėų3ŗŠÖkÔnÛĩžŠÔ•2ëÁVÆˇkAøĸâĮ71ß°ĸíhŲÛ.ũÚSĘuąuļÄztĄŦā×9œQ‹v7Ŗ7~~ĖĄÚP֛ôgō0OöÍ~Ÿ yÆÚ÷˜ĩߓĮßB“krj7\;U1KŽIR’ÁcĮđE’“ŋeëŠô3ÉŦ –âËŧ2’éIžŧđU[~YÃĸ×ü84{ ã×Z�wū>g%ãũĀØkņģ#yÁŋŦĪ\”õ> éĪôQŦ™ķîņ3õĘĪž2×cYĮëŖ?į˜˙Ģü˙íŨ{TTįĄ÷ņ¯]uĻ=¯3=ĢÂÛ%CO�[ ÄZ•&*i‚&Š&^S5I5š !QÔ…¨A ņPMcc‚IŊÆ[ƒÚTŠˇˆFãu ÚrÉ[Āĩ ô=k†SŨŖkqū�å"2rđ÷Ér-ƒ{ö~fīįŲûųíįŲ›-_äč×”ĮšUžė ÎŊ=éiE„.ZĪĄÃûHāÜÛ͘SûBÛ|Ŋ 7öŗĢrڈˆ †SĒy×Ō~œŊ§`@ԓøĒ4ņ…s0ápåô…ĘģŽęŖ;Į¨šęŖ#‹§ōō.ƒđ[8tb[–ôŖxËLĻŊ}áîŨZŗŲɕõМēšCgĪsáĢDBŗR™š`˙Ũ ŊëeÜųfĖf'[×r´˙Bļ|ļ€ĢĒ”Gœ#Š÷ŗf—…——ŒÁ—b2æOdŪQb6äü×[ˆķŨ܇ąõ§‡Ų›ˆ¨`rÕ |Ūvk=i+îĩ7žëm×{íŠë稘˛ÖՎŠŲÍīrũ‰ûÃ.}瀧Wŋ¯§É‚šmÜõ!4:‘ąÖã¤Ŧ:†Ũ~Œ”ˇcŗ€˜sƒæ)ys’hʖ<ŧĄ¤ø0›OÃĐčDƅØđö "bŅ"ŦÎj Y‰X˛‹ƒ&2.$�_ß�B#_aRƒĖŖ*N/V+f3`ļāmĩbvã3wo”ŲÆŗrŅS„v  ĮĐ×IžˆãčîʋĒë)Ė&ĮáEXÔ0zøÚđíˍĨ°ëą„[û—ŦŲ’GPô*’#ƒņõĩ•Hr”…ŖëˇĩĖ]%wöŗåôūĄœfīąĒ ģũØ2 fäpMĀmz6ŧĀY\RyĮÚu}tyŒšĢ>ÉĮûЉ^FÜĐ |Ŋmôąä‰ūäîú„#Õ{cA㉋´aĖŪșÚĮŅ=5;˛õ-Ķ€īPhyŠä™Ã @™Ä3ÎÅĮöp.h<Ŗ|Á8ĩ–¤C>ÄŦ{‹QAŪX­6ÂįžB(%ø†ôưø`-ÉĢ1ûĀįmwÖ͘ļԐvPoÆwcÛ.Ž=÷^_Öí¨ā0;+ëĘä�|ģöį…ÔXB GÛĒķæū$ŧų$ė^œØdđ8 ‹†T'šāˆTįÉŖ%˙wđox7e)iw˙$đú`¯6ģ¯÷JāÜ āŨ ę­7€~—[uGÃj.aĮĒĖģKŨ‰aĀæŧī]w?c Žq‡ŋk?g6Y…0ĘęÆznûMąS1ĻŽgĀ~„yĶ#¤˛“žuš+NF ¨y—fH Ļ­8W =šģ?īÎ~Îu§œ32d9)ûžÁ9 +vŽė; ‰P&iFÕŨJwëĩĢctž™ęcõ†Ôœ‹Ũĩ Ļõ؜/€ˆĘ9ŪAT߄o@�į…Ę6įÆ2†ûßÁŦ<;G\9•‡ī`ŧĖ}‡ąˆeœoÍzaĄGpĩíÔē˙ĀįmwÖc4ĸ­4 Ô<ÜØļĢkOļŲí¨đ9ؘä]Ŗƒ?"ÄÄÎ66‡×:bqCFwĖÁ€ëkŽĻļÄ1h æ%ņĖŅ‘đŗQĪķ›!?åßŊŽäÛÂûųÜЙž’“—ŧ@Øë[Ļ#ūOĪ!íéęģōkwķˇvJėvĖx™j ŗĩÚåĮ¸@ŌķĶØdMō’„X0SŽŲcYsßžœûŸązYjå f ÃÍõ˜û“°} ëĪ΍KØŧˁÉ֏qsIˆ Ālw`PÄĮĪõāã{ ęŊbZķrg?ģUΊŠIoī'ĶF„qœŊG!lé0”IšCš…` öǏ{įVŊvqŒšĢ>ÚuŦvs2[°RŲ!ŧo›3Uĩ9w–iĀw0[MĒFuްc/ąã=Ā °SkĮģö(VÖqÎūL¯œFcĪ-ÂîÛŋFxxāķļ;ëiL[i@;p՞\oÛÅĩ§ Ûlvd70îųœĢ—•ļ÷`Ą7ᑁp Fąĩü1—įÁaH�~ˆX?~–‘Ī%Pi,Ջ <6”X͘1(qÖŧKfˇW›Į‘ĩ›Œ\/Æm_Ƹ;?,ÁQßÉŊŸą—Ô~' 3fKÖãĖäEŋeō"°įœæĀÆ$ÅÎĀl;D‚Õ‚"ÖŽįå�jŸũņn‰Û¸îėg7Ëé=d4ĄÆ öž2sė'“Á$×8us0Îīį@Ą‰Đšũ*.nnÖĮzQsÕGkEGϤVY ģ;Öû= UtŽĖf7—ņ„6ÕŪ´đ9¨ęËÖŖPLFÚrÍÃčę]ņ˙öeŲ¯F¯IÎÛŽÖC#ęYÚÁ=íŨ ÁûēŪkOHĶĩŲÚÛ7ßķ9{I;{“DKq!›ôE‹IoGę_9 kk—5ŸM‰‹Ų¤PR‹-Žė''Ëw§ dsę‚îœíN ŧ°U;AYâ@.PûDa4ü3ÅY)&čî ¯œŦ<&B|SŽ×c\āHŽ…đĄwEŦ]û3nQ,GwEs%×ÃûjÚCÃB׎ÕnĢŲ )ĀÖ2ķÜŨŲĪn–Ķ{0c¤ė;ÎãēLsg›ƒũ4)‹ˇQhÍĘ;:wëu}Į( ™ęcP0=LÛ9wžĒMÉ9ĶLj¸ÂķŲP5$'+§ÉŸ ›ģËx@›joZėaÅ׿EAVLؐ@ė?!Ŗ`æ\¤­`§)/sE :ˇk)…OōA¤w“žˇŨZŅˆzæV;¨×5Q!9šÕzųnėk—מÚ °ņm@|Ų_ą;uÅ8ÍÁķNđjGmĸIŽįŸ<ûĄö@~ķö$zw|ĀÕÜĘbũĸ-Í<RŨŲÖ÷ŊØ2Õ΄î–V1ųĶŪū5}ÜŨˇ7.ą.ņ3˛ÚHķm܃îžÃ GĶ–°éT.9ČXü.G K­g6;7~INq19§>aÎâBzô7á,¸Ā9ģX°šÁ‘{œ#Yšøēķ™ĘšÛˆO?MNq1į?!ic6^ÃĮfvsÛšÛˆŸ=ƒ9éĮ¸RPLAAGŌ?%ĄAV°cz”įVÍ%å`ÅÅäœßMü”H"cwĶ"/tg?ģ]NoÂ#ûc?ö.kŽAø˜Áę>(GWN&ķÔi2O##ũ-&ūj2oŨŠēčv[pqŒ¤>:ō8wôGjü9͕âŠõžåÃų´7y˙h.Ņ\9¸œy[ šø|Õw�,…ÛHú ˛ÍĒjsÕÃmŊËxB›joZđŅcxŒCģÉ4Āwâ2’ƒķHúU(ÁĪ,â`Đëü×Ō—åuœ9ƒF0īX +?\Hh­y0|Ūvg=ŠgnĩB‚LäŨÃ9;€+éËŲYbjØžvuíŠ}]´CΖW‰šō.įhX›­YW36ŽĻ­ #̐âœĶ|ģ–sfKûjMr Dj’_LÎ„îžĐVnrãfŋ}ŗM=“͏‘lLN]Mî‚å¤ŧ8’$ŗ?aĩ‚™‡*īy&yéæŦzƒ[Á+ø)â–.#ĸx9WfČÄ)°ëķ× Ÿú >eúŗbŌ†ƒn|f0†aĸkôFæŽeÚ¯.PhX 瀖̏ˆēĩíDŌ—.'iũ"ĸ–UŧŅЏqŠë+_Áh&léF>°.'%q•8ĀâOčđ…¤/ĶBĪb¸ąŸPNīáOļxGÍO’0TŗfXábžßSų?&,^„yÍsĮæ]m˙ēŲzÔ{Œ >î!îĨ=÷üxhj&é‘VÂmäwæ%¤,ˆbe Xl„G¯'afPŠ7žQąŒ,ŠjsÕۜ[ËxB›joZîaú/{M$ūíaėZ:„qīíb\­Ō$|~Š Ŗæ´> ‰ÎÛnœ˙UĪÜiیz3‘SąŠLô fĢĐ¨ÄEå1ķhÕË-\n{¨ĢkĩÖuņ(“ ŗ9Ū|÷m~îļŲZCŧ°nšąī2ī™=ā՛ˆč…ÄyEķōųöÔ&šâh”¤IŨēÎņ͟ņųÕ2,ŨG0}R?|īÜéŋõW>Nü”ooWī™öd֒įč᥁äÎūo“ÛܸAÕH¸Ë‘’›ÜjCUĨCyyš^l-"õ8Mü iœ›ē—}3`iķŠŋdŪķo’iO\ôxÂCîL‡2°įd“yl7›ˇ†;IRĖô8†;VĒۃ`'ãÅĄĖc§7<ĨŅķæęh5QįĩĩÂHtÜâ~ĸæô-ûɏHܝOEîø>?KĖ@7zÕ-2}̞ŦÔą­zFHėW?įGŨŸy -7îUÃ˙Î/_yą¸šøwûI\›É?ąĨ´”Ĩ-Ÿuēˇxcåˇp }-/ˆ"&×ĀdNđō'tČ0ÆŽØÉ¨ĪSČĻ)‘$9ž"yÅK„yû”cfÂS5WZ‰§Ž”¸$k6žáÕÛ ü6%Š… ÖĀé[7Šé[""ōP21s53ÃĀn7ĀZũîģx&“×ĨaOL%åő”8LXlÁD,I'!R‘ÄĶĩåßÖníû$c¯UMßÛˇZGŋãĪ™žl)Ķë ,­×5ŠĒ'l+ GÔ`÷}ĸĘv›ŨēEåãųŽ3ß­ļ5}KĄDD\čOō×Wš`iŌÕ[i¤ÍđÂŦuC˜Ĩ=!-ŠcOÃā:{͞8RŌ‰ŸøÜo„$ŸŸŋøÆūG~+•í67nŪt;”ܸy“Û %""""ō0ŗ_ūœßī:OÁÍđcīpŖ¸„?đe@ÔD&ô´xčHÉŋķK]ä ˙¸Č3˙ņC:tlŊČtã†û1ãVƒæz)”ˆˆˆˆČ}´ŨŠ[Ŝ:x†ü2€2ūqŊŦâĮeœ8xžĮzá'[ö[Üēq“ū˙˙Ļ´čvî:Ī?<dČá–Ãũ qÃqģMÕ…iÂ.=€7#b—2ĸŅki­u>ے’ØvŸ îØĄU÷mÅô­Ļ_VĄDDDDDęÔ6GIō9žųw‚†ü”k|כŧĖoø[K¨ŽAĖpķ¸ŋerë†GÅÃëŨøf7 %""""ōPēÉß2~ĪüŒ‡%@ĩŦÛ9'9^äĮc>?ŦwšáĢMßi7Ēå'ÉũŠœĪŽ÷–ŗĢīŠBŠˆˆˆxžĻúmđ"mFJDDDDDęā1#$ %""""ŌZ:ĐĄM˙Fw…Q(QQ(QŠōj\‚vzô ģˆˆˆˆˆ´*”ˆˆˆˆx°;oáŌŗ%MKSĩîSßZéĨo %""""R'K§N8ŽÖëŠļŗ�âņSˇĘËųŅ~Ô*›Öô-‘6 Cå-éÅ_OhķD’ :0ķ…)­SŋËËË5("""ŌLAĸŠĩô4ޜü˙Į†OļRö?˙C[ë6zŌ-O%ątú?Ė|áyņõQ(Q(ņĖpō0īk¯æ§gJDDDDDaDaDĄDDDDD×X;ŊžFDĄDDDDÄ#”SŽÎŗÂ‡(”ˆˆˆˆ<ŧöļ<ŠŌÖÈĻm)”ˆˆˆˆxTį´ĩ:ØumWeQ(Š'4ĩ‡* %""""ŌA Š;×íũŲ……iĨđō0SQ(isWuôF¤nßĶ.‘Ö¤‘‘ĸ“öqü¤™BIŪ߯kOˆˆˆˆ´”ŸjxēÜŋŨķŗ<ÔgnJū?ír÷īĘËˡöŠˆˆˆH ŗZŦÚ Ęî°k'43‹År÷īšž%""""ĸ0ŌĒJDDDDZšŦ……Q(Q'Y#& " %""""" ĸP""""ĸÎôũh$EaDĄDDDDD<ĒĶ­" %"""""­ĘDĄDDDDDÚP'Ŋ­Ē(€(”ˆˆˆˆČC`DZÚ÷´ DDDDDDĄDDDDDDJDDDDDDJDDDDDDĄDDDDDDDĄDDDDDDJDDDDDDZB#O‰Áo gū_œ÷ü‹Šķ#ô8˜šQôļ´ô×9KRÄl.MÜÆöé~Í´ w¤’’~„Ģם˜ũzōČųÄ=ᇹYŋ[)'>Nåũ'+ļÛåúŽ}…øéņjõ˛‰ˆˆˆˆ´x(ŠÔ%’wۊ¤ķŨ8)Ŋv>x‡ķlß8˙Vū‚%;fņÜĨ_sxÉĀ&Y_aF<ŗWĮ čdâzY(9ķ{’ßœ…ô”Į,ÍTƒ‹Ģf1{'<6c>1Ŋ:Szi+k~Įl6°}zˇF—MDDDD¤m‡Ë#tīÛ§fđč;A<õæFūpi ŊZķë\Ŋt g“­ī2›>ČÄ2vI“zVŒ>ôJ†kŖ‰MßGĖc°5GŒL>Úų~“>åŊĘ�Bߞ˜Ž=EėۃäMĘ&""""Ōúšå™kˇžøPFi‘Qí§ĨœøxĪE Ŗ_Ÿ! ų ;Ža¯ļDəÄMÍā8Œ'§ÆķŅ™ŌĘ=IBø�Ļl.Ēą­o‡<q+…÷”ĸ”Īf eöŪ2{_#¸ĪsŧwÕÕ6 ;m4Áã9\×Ë;˙ë?fĐ/{V›eĄī/{bēv’3÷ʀQÄáU1<1¤b_DLáĩ´“u|‡Jæ0–îÜÆ†iŨĒ˙¯Î0 *›ˆˆˆˆH;%Æõī(Ą;ßéWN?Z^oŦaOÆ6ÖÍôãâęYĖ΍ Ž$ŋņ!ųŨcųpįnöo^CL÷|Ū^ĘKSŠÎ<ŊúSbē%"‰/˙˛Y>ގaéö(ƒõŦöœF5×˙J]đ÷ŠÂ|ÁËųW‹Ü(Cw'–Í&6ÃÉ ˇ6°įĪÛøpáŖ”ėxŲĢ.cԝJ°úøáUcV>gÎ^ĮŌĢ~*›ˆˆˆˆˆgø~ĶŽÎ äŌARV¤ÔoĪܙēå8Âû;ŋŖûŒm$<á€Íg>ņ—žåĨôŨdz…Āĸŋ’W֙_Œ 'ĐĀۛkđUZĢ3î>ŗĨ&`˛āeąĀU×Û°=ą˜÷Ÿ¸Īˇs˜ąÔ.O'3& ‡e(ŲË\§Wô:^T™ {øË™Œßą•ŅÉ<nvŊŸ/ĻÅķ~ūŗ,k#Ë&""""ŌöCÉĩuŒîŗŽÖMøôCę[¯ĐûNį:˙[Ž:ģđĢž~ÕģëôøsL;/sŠũÂøE—Oølá,ŒIcxŧOúvīL`¯ÎM÷m[bŽä_ŽØŊjiø÷é†)ũ‹āņzßāā›´b7;yzõĻøĢ‹ˆˆˆČÃJüƐē,ŠŠîĩ“üÍņĖ?Ų“yĢ_įņęwėe8šÎĻiØtĪJÁá�ŧú—ž˙ôüqķ;ėX]†ŠËŖ<Ũ„¯´5?Ø6Ė fJ+Ę[ũû•9pbšw”ĸÎLQ†^j­ÛÔ Nœeõ}¸”Éŗ˜˙ĪĻm ޝĨiË&""""ŌæB‰Š ūŨģŨ}ûVāÜXcåĒHú.ˆõÎr–N˜čÂĶĢŪãŋÚ=}ī xõäŲš+yv.ØķÎōՖTVžųfŸ=ÄŨį-^ΆžÖĒÛ¨ aŨđádÅķÕ:ėų×)1uМæ‚Ĩ"|”Ô †Ŗ î +ÕŌ ß,›Åüŋt&fũ{Lénnú˛‰ˆˆˆˆ´‚Ļ}ĐŨNLtŽŊī°æLĩGļũĨˇŠ”ĸ2 ūū~UŧL˜,>ĪD]æđ×ųwôļú÷á™7^aŠ”ĢųĀ„É ŽŌę)¤ˆŧü2×åĒüˆëm¸āķ(ƒüūəgĢ=^Ęמ ŊÂékq]ē÷¤ģé:/Õ|ō<īė5œēŅũ>áĄ0#žØ “ë $Z6‘vJ�Û¨X^îYʎåkšhT…•ÉŖ:s1-ž÷žēFaI)y—ö’4c<ãîĨ ÉoŧÆüÍ'É.*Ĩ°č'6oå ]čÕÍøŅ웉ü¯÷qŅā {s*Ÿ—šęKIXĖāČ?ɉĢųešÚūy¯-ÜJvëëÆ”Cqd$1ķI.^:Ëiņ$Ÿ´đôŒČēߨUĢ …„ķ¨.\ú ‰žÎ§°¤ˆė¯ŪeņÎët7žuÍ!sœdMZ&ĻAQüÂq™oΜ­úséZÅūkTŲDDDDDZ_‡ōōōr‡ŖĄ¯f2øâáĖ/z‰=[ĻŪķ[ۍKīōÜ´Ũ˜_­úmãPĉ´TÖd|K^itz„Ūŋœ@ĖÜ(z[*֙‘ĘĘô#\Ė˙'NS'|üå‰ąĖzĖ3`äí%ųÍĩ|‘_†ŲŌ…^Ŗb™B*¯}Éö-Sņį,Iŗš4qÛ§ûPōÕ ^|k7ųÎ3.mĪ^¯ŲiŖŋš'Š'“yŧÎīî {G*ÉéG¸zŨ‰ŲīQž~u>qŨ~TÍ2ü‰„^EN{‡5ž%ŋ,]~Πą¯7ŊOŨááęZž™ô ųuŽũQâ˙ü>Īz5Žl"""""­ÁRíĄįF†‘Ļ %ßĶî‘Ö¤P"""""" %"""""ĸP""""""ĸP"""""" %"""""" %"""""ĸP""""""ĸP"""""" %"""""" %"""""ĸP""""""ĸP"""""" %"""""" %"""""ĸP""""""ĸP""""""m_‡ōōōríi-)…Q(Q(‘‡Ī˙5Ŗ{;ËkD����IENDŽB`‚��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/user-list.png�������������������������������������������������������0000664�0000000�0000000�00000100141�14156463140�0021304�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��^��˛���Ũú�Ą���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨwxTeŪÆņ{Ō;„ôĐBIĄˇ�!tŪE@AąŽk/[|-k[W×ļbGš"H)‚€ÔЛÔZH%ô:ī!#)2™žŸëâ‚9õwΜ 9÷<Īs FŖŅ(������T:+K������p̞šÖŒŧŧ<%&&*++KyyyUY�����@amm-{{{yzzĘĘĒhCi]ōōōtáÂ988ČÉÉŠÄJ������(ŸŸ¯ŒŒ eddČßßŋHŽRj𒐐 +++999UiĄ������5UZZš$ÉÃÃÃ4­ÔĻ,™™™rttŦšĒ������nŽŽŽĘČČ(2­Ôā%77WƒĄJŠ�����¸XYY)77ˇč4 Õ�����pË#x�����0‚������3!x�����0‚������3!x�����0‚������3!x�����0›ĒڑÁĘēĒv�����P.Æü<ŗnŸ/������fBđ�����`&/������fBđ�����`&/������fBđ�����`&/������fBđ�����`&6–.�����pë‹<sNŗæĖSjz†ōķķ-]j0ƒÁ g'=0qŧÔõˇt9eĸÅ ����ĀŦ"ΜĶg_|ĢËŠi„.¸iFŖQ))Šúøß(úBŦĨË)Á ����ĀŦfΞgépĢ1d4æëë™?Xē’2ŧ�����Ė*%-ÍŌ%āVd0črJŠĨĢ(Á �����¨‘ŒFŖĨK(Á �����€™ŧ������˜ Á �����€™ŧ������˜ Á �����€™ŧ������˜‰Ĩ ¨JO<ņ¸–-]ĒÍ[ļĘßß˙–Ũįĩ4mŌX­ZĩÖÂE‹,Z����Ü(g''ĩiŲ\M5”Ģ‹‹˛srŸ¨ƒÕÉS§-]^…x{yhÄā jÚH’tėÄ)-^ąJqņ‰Ž •ŠF/FŖQĢVũĸE‹i˙ž}JŧxQ’äíåĨ°°n?a‚Úĩkgá*����•Ą_¯p ėÛKöövĻiššš˛ļVŋ^ኌ:Ŗ9ķ)6.Á‚UŪo/=ûøÃrtt0MkŨ"XôīOĻß2áK-7WĨ¤Ļ)??ßŌĨXL ^’““õøcjķæÍrrrVXXWÕ­[Oiii:~ü¸æÍûQ?ũ4OĪ=÷ŧyôQK—{[{į˛ˇˇĶŦŲsJĖ;yâ„úõëĢO?ûLƒą@u•Ëh4ęÚģgbãb•——§ZnĩÔŧysuîŌEŽŽŽĻeß˙ß íŦîŨģ—úē&;Ĩ­Ûļé…hĨ§§ËŪŪ^ 4TXX˜ęÕĢgéō,bŪŧy:vôh‘iŽNŽōööQ=Ô°aC U��Pũ Ũ÷Xĩo͞ÄŧCGŽë‡‹Õą]k ĐWĪ>ū°>˙vViũ2rȀ"ĄK!GG<@_Î(yUŨX[[k耾 íĐVnŽ.’¤œœ=ũĘëĻeūūÜ_tęôYM˙nļrss-UĒEÕ¨āÅh4ę‰ĮĶæÍ›5tØ0ŊöÚërww/˛Ėž}ûôč#Ķôîģī(00Pũú÷ˇPĩ¸,^ŧHTHHˆ:tė(kk+EŸVÄÎ>|X“îģO...ĨŽÛ¯_y{{WzMēp!ZǏ¨ôm—ætT”fĪ™­-ZhÄđrtrŌå˗´eķ͚5S“'?h–ãŦ Üë¸k营Ļ×)ŠŠÚŊk—fÎü^<0YuëÖĩ`u7§Ē¯3��p{>¨‰ĐåDd”žœ1Wé’¤ß~ßĻũ‡Žč‰‡ĐÔû&čŨ˙~Ž‹Iɖ(÷†5i|íyWēUwÃöSߞŨŠN4Šŧ´ˇˇSpP=|˙=ˇmøRŖ×ũuíZũūûījßžƒūûߏJ„.’ÔĻMũīķé?~‚\\]ËÜf||ŧūņ÷ŋĢ[XW6Uûvm5uęíÛˇĪ´L—ÎĄ8p@‰uīčßOjũēuEĻ/Y˛Xj់L¯čvÂģ…ŠIãFŠŽŽ.ąnRR’›6ŅčQŖLĶÖ¯[§ĄC‡(8¸™:´o§ž^—.]*ķ\ böîŨ̃jđā!=zŒZ´hĄāāæę͎¯îŋ˙ĨĻĨjÆߎš~›6mäįįWéuÅÄ\¨ôm^OÄΝōōôԈ#Õ¸IųųųŠYŗ`Ũ}Ī=r¯íŽŗgĪVi=Չ­˜ū´lŲRwßs\]]ąÃŌåŨ”ĒžÎ��ĀíÃ×ÛK}z„™§Ļk ] ]LJÖôīfËÖÆF#‡ÜQ•eŪÖBÛˇ‘$}đŲWzâųč‰į˙Ą§_~­Ôe ÛÕūŖRÔ¨#^¸hĄ$é‘G‘•Õĩ3ŖÖ­[ĢuëÖen/11QŖFŽĐåË)ēûžģÕŦY3]ˆž YŗfęŽģÆjƌīÕĨK…‡w×Ī?/Prr˛j׎-IJHHĐņãĮåääŦí;ļĢwŸ>ĻínÛēMƒAááŨĩîĒ0ĨĸۙüāŊūÚ˙éįŸčņĮŸ(r +WŽTnnŽÆÜy§¤‚oŸ§N"OOO=ųēĒãáĄíÛˇię”)×=gÕÁŽ;ôī÷ŪĶ‘#G”——§=ûÜsęÜšŗ¤‚>œŸ|ōą–-]ĒsįĪËßĪOL~P'N4mŖ}ģļzüņ'´iĶFmŲēU;uäȑën÷fEėØ!ĩoßžÄ<///Mœ8Ižžž×\ŋxWŖüü|ũži“:¤äKÉĒåVKĄ;ĢcĮŽĻuūķŸ÷Ū]—/]ŌĄÃ‡”••Ĩ jčĐĄrqqŅĖīŋ×éĶM,÷īÛ¯)S§*++K~ûMąqąĘĪĪ—¯¯zõî]i]]ōōō”——WbēŊŊŊž6­Ä˛7lĐūû•™™)__õéÛWõëחTđ^˙ļ~Ŋ>¤´´4š¸¸¨UËVęŲĢ—é:~÷ŨwÔ­[¸tâÄ eggĢqã&:t¨œœœĘ}.OŸ>mÖķr-666ōööQŌÅ$Ķ´´´4­]ģF§NRFF†jšÕRĮNjZæũ÷˙­đđŒTTÔ)ũõ¯O+//¯ĖõūķŸ÷Õ­[¸âuäČåįįĢ]ÛvęĻe˖éėŲ3˛ŗŗSĪžŊÔĻM›rŋŌŽ3ooī2ĪyiĮ[î÷aㆠڰqCŠįĩgžęŅŗįMŧ3�� ēīZâÆ×Į[ŊķĻ×ĮOžŌúM[tāđQÅÄÆiÛÎ=ęŪ5TŽŽ.JII-˛îāūŊ5¨īR÷ĩrÍz­Xŗžōâ:ŽŒT̐āŌį8UæúÕáx\¯t/ŠŒ:#IjŌLŪך÷š][žTī;ņböîŲ#ƒÁ Žaae/\~đbcc5kölŊøâK5j´}ė1Í_đŗlmlõ¯Ŋ)I ī.ŖŅ¨ĻuˇnŨ" 2D;ļũÆzûömj"//¯"Ķ+ē &ČÕÕM?/XPâV,_&{ 6L’ôé'+//OĶŋøR>ö˜Æ¯>øPÁÁÁĘÉÉššfFééézpōdęį…?kŅâE Öũ÷ß§ää‚f‚oũë_š>}ēyôQũ˛ōM~pŠŪxũ5ũøãĻíØŲŲiîÜ9jŦ9sæJR™ÛŊ™™™Š‹UãÆ×n&čįį'[[Ûrosíš5Ú˛u‹ē…‡ëᇧŠs—.ZŊz•öėŲcZÆÚÚZ[ˇn‘§——žxâIM›öˆbb.hĶĻ’¤ąwŨ%???…´ŅĶĪ<ŖÚĩkëĮ§—§îŋ˙‚n?>ۚ;wŽ2Š}[PQJHHĐüųķuūüyÆk.ģfÍjíŲģGũûߥ‰'ÉŊŽģæĖ™­¤¤‚båĘÚģo¯úõë¯iĶQīŪ}ą3BŋŽ][â4l ŋūõiM™2U11´zõĒrŸËėėlŗŸ—ëINJ’Ģ››éõŌĨKuîÜ95Z=ô°Âēu͚5Ģuôč‘"ĮŊg÷ny{yéŪ{'ĘÎÎŽÜëmÛļUAAAzúégÔ§O_mÛļMsįĖQXX˜žyæYĩnÕZ+WŽ0{Y᝸uæíí]îë÷ęctCīCž=ÕŗGÉp…Đ�€[Kp`“R§;yJë7mUL\|‘éö˛ŗŗ“Á`PŗRēņŦXŗ^+K #,ēHŌâåĢ•‘‘YbzFFϝXUĘEUˇã‘¤)“&häĐEūw;ļ|ŠQGš˜(WW79;;ßôļŒFŖ–/_ĻfÁÁōõõU\\œiž:th¯7*--MŨē…Ë`0hGÄ͘1ÛļnSŗā`uéÚE ūŦôôt999)66V‘‘‘%žá—Táí8::jøđáš={–vîÜiúæ811QÛļmĶā!CäææĻüü|mßąC 64}c]hÜøņš9ķû›>oæ}^ŠŠ)9j”š6 ”$ũķÕW5dčPŲÛÛ+%%EŗfÍÔ#>Ē1c Z÷4j¤ƒčķ˙}ĻqãÆI*|ËÁŅQ/žø’$éĉã×ŨîÍJMM•ŒRíRēŊUDVV–vîÚŠđnáĻV[uęÔŅ… ŅÚ˛es‘§uyxxĒmÛļ’$7775iŌDĸ ē}888ČĘĘJ666rrrR||ŧ˛ŗ˛Õ˛e+S 8`Ā@…4Š´xíÛˇWFF†6oū]GūøCvövĒ_ŋš5kĻV­Z™Â§ŦŦ,íŲŗGũúõWHHˆ$iȐĄĘÎÎQŌŋ˛ˇˇ×ūũûÕ¯_ĩhŅÂt´}ĮvõéÛWÖÖ֒$__?ĶĩîééŠöí;hĶĻ<xHAČYÆšŧté’ŲĪKĄĢGqOMMÕΈ%&&jĀ€?˙3ēãŽ;d0LŨ(=<<´3"B‘‘‘jÖŦāÛƒÁ [[[õí×ī†Ö+<_A’¤–-[jåŠĒWŋžŠĨQ‹–-õûīŋ+11Q^^^ežŋâ×Yy¯ßâĮP‘ëŗ0`)lųBč�Ā­ĮŊļ[‰iËVũĒUŋ^iųē´ā¯^á]Đ žš5Q—Žŋo¸ģ×*u›…DaKK†ąņ z˙“/4bČĻņ^ŽŒÔĸååœtu:IåîeÔDŖ‡ Ôŧ…ËĖ\QõPŖ‚++Ģk>‚jėwjįΈĶ÷îÛ¯ZĩJ~č”””¤¤¤$uítÍ}FGŸW``š+bĮŸÛßļmĢzöėĨĐĐÎĘÍÍÕîŨģŽmÛļJ’ēwīQb[^^^Ūθņã5{ö,Í˙é'Sđ˛råJåååiėØģ$IąąąĘĖČ0ŨD]­I“ŌĶâęĸQŖÆjŌ¤‰žú˓ē÷Ū‰ęŪŖ‡Z´hĄ.]ēH’ļoߎėėl…‡}ōO—Ž]ôã?(--ÍČuhߥÜÛ­,•Ս+&&FųyųjTŦMÆÚģg¯˛ŗŗegWđ=ŸbÕ::8*#ŗôVōđđĐĸE ÕĄCG5nÜXžžžjP)uęÖ­›BCCuęT¤NEžRdd¤V,_ŽM›6ęîģī‘———ââ┗›Wd\kkkŨyĨģ\ÔŠS2æK 8ëįė%&&šéõķõ-˛Œ——§ōrķ”’’ĸÔÔÔ2ĪeU—ØØXũëÍ7‹LsptĐĐaÃÔøĒĪĻļlŪŦ¨¨(Ĩg¤Ëh4*##Cu<ęYˇnŊĸįĻŧëyÔųķuađčááYbZVfæ ]‹…ndĢĄĸīÃÕA Ą ��ˇž˜Ø¸?C—ĢÔrsĶ3.2íz­¯¯&,RHáËßŨÜĶ‹ĒĶņ”—ŅhÔņ“Q–.ŖĘÔ¨āÅ×ĮGQQQJJJ*1°n˙ūũdzŊyķīĻąJ“–ZĐß/¤E =˙Üķ×\ÎÛÛG’ŽīžũVéééē|ų˛"##õüķ/¨^Ŋzō÷÷×öíÛ “­Ûäčä¤NJs*ēV­ZŠE˖Zž|š^ũŋ˙“ƒƒƒV,_.uëV0Štæ•&ų%Iæāā CąŅĨÍ­ (+ũ^n^A>[›‚VÖÖÖúqŪOš>ũsũđÃ\Ŋûî;ō÷÷×3Ī>ĢŅŖĮ(5%E’tĪŨŠŒ’mŧÄÅĮÅÉšQÁČß.Ž>=¨ŦíŪ,WWWÉ %]ŧxĶے¤ėė,IŌĖ™ßyŋ ˙ãHMMU+7Ī7Ō}ÉĘĘJ“îģO[ˇlŅž=ģĩ~Ũ:šÕrS¯^ŊË5Ō°ĩĩUPP35“T¤Ė_0_k׎Մ ”•UМ˛øM{ĄŦ+į x‹$;Û‚åŗŗŗ˙ÜWąm.“™™YîsYįĨN:yÕ�ØļļļĒS§ŽŠåŽT0îÍœŲŗ•ŸŸ¯ČÃĶSVVVšwUWēBöZ¯´Ö#×jQr#×bEÖšúnæú$p�ā֕”|Y>Ū~I”Sú˜ ‹WŦVdÔ=t˙ŨĻiÉɗ¯ģmKŽŽ.>°ŸZ†ËÉŅA'"Ŗt.:F’TĪßWM(=#SŅ’_֖ĢĻ4Õ%pIŧ˜$:×ī `45kŪBíŲ°ŠĒ˛ŧŧtėØIQQQúíˇõ5jt‘y=üp‘×O<ņøuƒįĢíÛŗW¯2÷Ũ=ŧģžúōKíŪŊ[ ņ2 ęteāĘ;šÆgŲž}›ētî|͛ʛŲÎ]cīŌ?˙ų­[÷Ģ:vė¤íÛˇé‘G5ĩļ°ŋ¸df–ė'˜––vŨÔ×ęxÔŅąŖGKwöLÁnŧ}|LĶ<<<ôō˯čå—_ŅņãĮô՗_陧ŸV``iŒ˙|đ‚›•€ĘĪß˙šu\oģ­Zĩē™C”ŊŊŊü|ũ´o˙>u /õ&ö?ËÚÚÚD\]AØ0rä¨RŊėæV˛šey9;;Ģ_˙ūę×ŋŋâããĩmÛV-YŧXžžžōŋÎų+¯ÔÔTŲŲŲ•¸ö5Rpp°Nœ8!Irt,ø6++ĢÔíØ_9Åį—Čd_cĶ`]eKsŸŠ h)k[įΟW\\œ&NšTd@ŲôŒtÕv¯]éë•Ĩ"×âÍ\ŋUņ>��€šåČņ“E‚—úuũÔŠ}íÚ{ DOˆV-ūŧG0:z2˛ĘęŧŽŽ.zå^ûĪ^AM+¨iŅÃ.ÎNęŌŠŊ‚š6Öŋ?ųĸ\áKuđī|Xâ â÷ßø›é߅ĄËŽ]{Ģē4‹ĒQƒëŽ?^’ôņGŨôĀ—^^^rww×ɓ'K}ÔrbbŅ>uĄ;ËÎŪ^;wFh떭 2}sÛŠS¨öîÛĢ3gÎčÔŠSęQĘ •ą#GĘÁŅQ˖-Ķōe˔ŸŸ¯;¯Œw"IŪŪŪ˛ŗŗĶšsįJė÷?ū(˙ÉŠ$={öRdd¤6mÚTdz~~ž>ûė3ųųųŠe˖’¤3gÎhÍęÕĻeƒôƛoĘÚÚZGUķæÍego¯Ä„D5iÚÔô§ļģģęxx\sŧ–˛ļ[B;wÖåK—ĩiãÆķâââ´|ųr;vŦ\Ûōõõ•ĩĩŌŌŌäééiúãčč(''§w¤0lKJJ*2ĐĒ———"ƒ•Aņņņ×ZŊÜRSSõß˙~¨­[ļ”ZCbBĸ\œ ÂNOOOŲØÚ FFŖžŸ1Cû÷ī—¯¯ V†Ÿ>îŧėėåááašvæĖ™"Ë\ˆž [;[ÕĒUĢ\įŌÜįåFä] Š ŸČ$IgĪžUrRōuC͊ŽW–š ÷SŅëˇ:Ŋ�� úø}[D‘€Å`0hŌø1úīÛ¯jĘÄņĻéŽ.ÎęÚéĪ'Œî=p¨ÚÃõ/ē”ĨŽ{m دė-¤đ<7h Šā÷Âėėė" ŨŽĄ‹TãZŧtÔ¨QŖĩpáĪēoŌ$ũįƒT¯^Ŋ"Ëdeeiîœ9Zģv­œ]ŽŲōD*ĐsÖŦ™úâ‹ézîĒîF‰‰‰4p€Zˇn­¯žūFRÁ7č;tОŨģĨ^Ŋū|lWhh'ege雯ŋ–$ĶcKs3ÛŠUĢ–  UĢVéôéĶęØą“Žt¯‘ ē ´oßAÛļmÕž}ûŠ °k‰uĮŒŖøAL›Ļ)S§¨e‹–JŧxQsįĖŅÁƒôųįĶM]-ĸŖŖõČ#Ķô /Ēoßž’¤ÅKËĘĘJíÛĩ“ĢĢĢ&ŒŸ ?ü@îuÜÕĻM[?^¯ŋūšü|}õõ7ߖZCYÛ­ ­ZĩŌéĶQÚŧyŗ.ÄĨE‹˛ŗŗUĖ…E댐—§—úõë_ŽmŲÛÛĢ}ģöÚ¸qƒœœœäīī¯Ë—.iõęÕrusĶøņãËŪHáļ̘˜%''kÁ‚ųęͧ¯e0tđĀ †ŸĄŠpqqQ—Î]´é÷MJMKUPP•ššĸ}ûöëėšŗĻŽ]öööjÛļ­6oū]nnnōôôԞ=ģ}!ZC‡ “ŖŖŖÚļmĢ-[6ĢNwųúúéôé͊ØĄ°ŽaEÆĶIIMŅÆ ÔĒuk%$ÄkįŽjŅĸ…lll >eœË˗.iū|ķ—áíã#kkEDėP÷î=§uëÖŠQãÆē˜xąČ8F•ą^YĘ{-^}šššUčú­Nī��¨>bbã´nãõë^b^íĢŪ=léßééZ´|u‰åĢ‹V!eˇ‚/Žå59]ėØŊO}{vĶ_bš–““Ŗ§_yŊČrˇsč"Õ°āE’ŪzûmeÔĸ… ÕˇOo…††ĒQŖÆĘĪĪWtôyíØĄ´´TĩhŲRīŋ˙ž¯š­ŋ<õ”Ö­ûUŸ}úŠâãâÚšŗâbc5{ö,%''ëžû(˛|xxw}ōÉ'JOOShįPĶô  fĒ]ģļ~úé'ųûûĢIĶĻ×=†›ŲθqãĩxŅ">tHoŋķn‰ų?ü°ļoßĻ'? ąwŨ%÷ÚîÚącģ222äęZņn*akkĢŲsæč“O>Öʕ+5}útŲŲÚŠC‡öúáĮyĻA‚%ŠK—.z÷Ŋ÷ôÕW_é?üG6Ö6 ŌįŸO7 >úˇŋ˙]nĩÜôö[o)>>^^^ÆŗĪ=wÍĘŗŨĘ0tč05jÔXģwíŌęÕĢ”ŸŸ/÷Úî īŽN:ŨĐx,ũī¸Cúõ×ĩJMM•‹‹‹‚‚‚ÔģwŸĒ)4´“/^Ŧ3žĶcîÔ°aÃĩ}Û6mØđ›ŦŦŦäíå­ącĮiAr3úöë'/ooíŨŗGK.UFF†äįį¯ģ'Ü]ä|÷ë×_ôë¯k•••%oM?ÁÔúkāĀA˛ˇŗ×ʕ+•––ĻZĩj)<ŧģi<ŖBíÚļSFF†žųækåää(((H 4Í/ë\6 0ûy)/ggg >BëÖũĒũû÷ËßĪ_ÆWjJŠüŧ@ŗfÎ,õii]¯<Ęs-ŋÎ*rũV§÷��T/KVŽQ÷ÚjßĻe‘éū~žĒã^[íZĩPĮvcÂeffé‹st1)ŲĨ–‹ķU­”ËËÅųÆ×Š*KY+I mßFŽ…cmįžũ:øĮQíÚ{ ĒËĢ6 ÆRÚĸGEEɡØĶBnzGVÖe/tļnŨĒyķ~ÔΈÅ'$ČÚĘZ>>ŪjÛļŦžũú•Lö‰'ײĨKĩyËV͘qqqúøŖ´nŨ¯Š‹‹“““ŗB;‡ęąĮ7=ĒˇĐ4|ØPIŌŽˆĻĮžJŌÔ)jíÚĩ7nŧŪ~įëîŗ"ÛšZˇ°Žē˜”¤ˆˆršjŦšBK—.ҧŸ~ĒS‘‘rqqQŋ~ũõō+¯hđ r¯SG˖-/÷yĒĢ÷ß˙ˇBC;_ˇ…���LJg˙özŲ ]G˙ŪŨ5 OOŲÛ—ŪŖáÔ麚ũĶBÅÆ%ÜÔ~ĖíÃˇūYä 呛—§ŋžôfĒčÖđī7ū~Sëķķ*Š’111 ¸ę 56xš]EGGĢWĪēëŽqzŖØãiÛ Á ��@ÍqŗÁ‹TĐZ¤mĢ5h WWåää(>áĸūqT'"ŖnžČ*0|PõéVîđ%//Oë6nŅ’•kĖ\YÍVŨƒ—×Õčv÷æ›oH’&OžláJ���� ę¤Ĩ§kķöÚŧ}§ĨKа%+×ĸ܆^j€¨S§´iĶ&­YŗZ›6mŌ_žzĒRĮ'jĸgžyÖŌ%����@™^j€#GčŸ˙ü‡ÜŨŨõÜsĪë‘GĩtI����� ^j€)ōT”ĨË������7ČĘŌ������ÜĒ^������Ė„ā�����ĀL^�����@d0X炞ŧ�����ĖĘÕŲY2-]n5FŖÜ\Ũ,]E™^�����f5ņžģjFĶÔ,V=8iŧĨĢ(Á ����ĀŦ7¨§Gz@ŽÎÎ2Ā ¸8;ë/ĶĻČß×ĮŌĨ”É`4–lī%__ßĘŨ‘•uĨn�����āfķķ*u{111 0ŊĻÅ �����€™ŧ������˜ Á �����€™ŧ������˜ Á �����€™ŧ������˜ Á �����€™ŧ������˜‰MUíȘŸWUģ�����¨hņ�����`&/������fBđ�����`&/������fBđ�����`&/������fBđ�����`&/������fBđ�����`&/������fBđ�����`&6ךqúôéĒŦ�����:„ĻĨ�� �IDAT Æŗˇˇ/ōúšÁKŗfÍĖ^ �����Ā­$**ĒČkē�����˜ Á �����€™ŧ������˜ Á �����€™ŧ������˜ Á �����€™ŧ������˜ Á �����€™ŧ������˜ Á �����€™ŧ������˜ Á �����€™ŧ������˜ Á �����€™ŧ������˜ Á �����€™ŧ������˜ Á �����€™ŧ������˜‰MUíčÔŲ Uĩ+����PŖú~–.�Ē.xá‡�����¸ŨĐÕ�����ĀL^������Ė„ā�����ĀL^������Ė„ā�����ĀL^������Ė„ā�����ĀL^������Ė„ā�����ĀL^������Ė„ā�����ĀLl,]�€Ōeįäę܅8edf);'×Ōå�ˇŦv-u2ꌾ™ųƒRĶŌ•ŸŸoé’Ē+++š8;iōÄņjĐ \ëÄÆ'čˇM[•™•%cžŅĖŪ¸]ûXē�å`0äęė¤ŠÜĢ€úu-]�TˆÁh4–øm(**J(€Tē9qZžŪĒíæ";[2RĀö:.7g{}øŲW–.Ĩú3%ƒAO=:ĨĖđ%6>A+W¯¯ĸÂ*†ā¨AŒF VzūŠGTĪß×ŌÕ�@™Šg*t5ĒĄsâäëí!oÚ„.€™}ũũ–.Ąf0$Ŗą\įkũÆ­UP€Û†Á Ŗ1_ĶŋeéJ� B^€j(5-Cîn–.¸-¤¤ĻZē„šÃ`PZzz™‹effVA1�n+ƒ.]Nąt�P!/@5”—Ÿ/k+>ž�ĒÆĀ`)ĨŒ��5wv������fBđ�����`&Õ:x‰:}ZƒBÔ8(Dņņ e.?ūžIjĸO>ûŧ ĒĢķ^¨ÆA!ē÷žÉĻi?Λ¯ÆA!ēoōT V�����nVĩ^ps^}í ĩijé2�����¸mņœÚjčÎ1Ŗ4rÄ0YŨäāĒûŦ¤Š�����@EŧTCÖÖÖ˛ļļžŠmäääčÃČŪŪž’Ē�����7ęļčjtâäI=ûÂKęŪģŸ‚[´QËļÕ§˙ ũãÕ×uęTTŠëüņĮ=ķ܋ęÖŖšĩhŖvējü=“´āįE%e÷Ï?ŠqPˆ^zåī:}æŒîžxŋBZˇ×koŧUĄz¯5ÆKyã¯Ī<¯f-Ú(;'G)ŠŠĻqr6lÜTĄz�����@ÅÜō-^ö8¨ņ÷LRffϚ7VĮöíe0tāĐ!͚3WK–-×ܙߩyķ`Ķ:ËWŦÔĶĪŊ¨œœĩiĶZ=ēwS\|ŧļnÛĄ;ĩaĶ&}øū{ĻŽ@ö­JRĶŌõÔ3ĪëüšķęØĄŊ|}Ŋ-r}ûô–ŊŊŊæÍ_ ;[[=p˙$IRƒúõ+­���āZB‚5uŌIŌ—ßĪÕá#Į-\�XÎ-ŧüoúĘĖĖÔc<Ŧgūú—"ķ>ųėsũįÏôÉgŸëĶ?”$ŽÖŗ/ŧŦœœŊ˙îÛ5r¸iųĶgÎčžĻjŲō• ëŌEãĮ•$ŲŲÚJ’víÚ­F´qũ988Xė8†¤ĀĀϚ7ėííõÂsĪTj-¨ŪNDFiíoŋëĖųhĨĨĨËÁŪ^M5Ô}z( A=K—'IúÛīŠKĮv:°ŸĨKnJĶÆę×3\õëųËÅŲI™™Y:qę´V¯Û¨ĶgĪYē<IŌë¯<Ģí;÷hŲĒ_-]Ę sqqRīîaĘÉÉÕ/kģáųæ0õžģÕēEp‘iųųųJŧ˜ŦƒҊ5땙™U%ĩ\ííW_ÔúM[ĩę× Užo˜×_ĻM–ƒŊŊžú~Ž“’oxžĨÔ¯'Ķŋ ^�ÜÎnųāåÜšķ’¤vmۖ˜÷đÔÕļM5¸ęfôûYs”••Ĩú ]$ŠaƒzáųgôØOéûŲsLÁ‹áJ˗˜ØX}úņ‡•ēTä8p{:~ō”>ųr†:´mĨ‰ãFËÅŲIIɗ´fũ&}4ũ[=÷äÃōķŠŧ–X5ŅÆ-ÛuúėyM7ēÔ×@y6ĐcSīĶŽŊ4ķĮJMKWÚĩÔŋww=9íŊ÷ŅtÅÄÆYēĖËÛÛS}ē‡ÉÁĄôąĘƚoN ‰5į§EĻ×666Ē_Ī_ũ{u—ŋ¯>ųrF•×T\÷°P5ŦWWŗæ-´t)¸IMH’ž}ršžú~ŽNž:}Cķ�–wˏņŌ´IIŌ?ūDĮŽŸ(2ĪÖÖVáŨē邺uë6IRßŪŊKŨ^Īîá2 :rä¨.]ēTd^­ZĩÔļMëĘ,ßäFˇ§[ļËĪĮ[“ƏQķ ĻĒ_×_­[4×cSī“GwŒä—ąŗįĸ¯û(¯îa¯™?ūŦ#ĮNęÜų Účˆ>ųr†/&ŠiŖ†–.ąÆjÚ$@ûöŧf¨RÖ|sËĘĘÖņČ(͟?ŽĐęuĩ`éJ5 lĸÆ ,R×ÕÔõˇt ¨d.ÎNzüĄûÕĨSû Í�XÎ-ßâåĨžĶžũtāā! 2\M›*,Ŧ‹ēwëĻna]J<õįÜų‚–%‹–,ÕÖíÛKŨĻ­˛sruZmŽ Zŧ<=e0ĒÅqāö”——§ÜŧÜĶėíõĘ3™–ŸŸ¯_ÖūĻ]ûčbŌ%š×vSīîaęŪ5Ô´LJjš.ûEĮND*-=CîĩŨÔ#Ŧ‹z…w1-ķâĢok@ߞ:rė„ŽˆÔŋūų‚lmmĩbõ:íØĩW™ĒW×O#ßQäfÄ`eĨ•kĶĻ-ە‘‘Š Ļuī¸Ņruq.õ؞ûû›ēŖOÅÆ'čĐG•••­ā Ļē{ėHš8;•ĢŪ˙~ūNDFI’vėÚĢúõüMÁˎ]{õÂSČß×§ĖķRÚ1ŋöÎ5 oO%%'k×ŪĘĘĘV“Æ u÷#åæęr#o#jkY۔|]VVļūõū'EĻYYYi`ߞjßļ•ę¸×Vrō%­Û´Eŋo0-ãâėŦQC¨Y`999*9ų’6lŪŽ ›ˇ™–yëŸ/jÕ¯Ô<¨Š‚š6ŌK¯ŊŖœœ\ îß[ĄÚĘÉŅAįĸc´hų*:}Ö´^žŅ¨ũzŠG×P9::訉SšųãĪJMK3Ùš ŠcģÖjŲŧYÅæ[Xԕs^ģV-Ķ´ōŧ÷M5Ô°ũäīį#+ƒ•Î_ˆŅ’•kL-ŪãoZązŊ~Ũ¸Ų´ÎŨwŽP]?Ŋ÷Ņį%ęøË´ÉĻV;ļĶÛ~&{ûëîŖ¸÷^{EĢÖmˇ—Z6’Ŋū8vRs~Z¤´ôôr[i×lņŽXåŲWy>ĩÜ\5aĖ5m¤ôŒL­ß´EŽjĶ*Doūûãr×|ĩÁũ{kP˙ŌŋŒ[šfŊVŦY_ę<sąąļÖ=cGĘ×ĮK‹—¯.ņ‡˛æ�,ŖZ/…ƒ×J7‰eÉË˓¤"böööŌŌE 4{î\ũŧpąŽ?Ąã'NhÆ÷ŗäęâĸ'߯ĮfÚWzZÁđ›ˇl-s)ŠEaužÆ ce¸ŅãĀíŠeķfšģ`‰žšųƒú÷ Wƒzu¯.\ļJ›ˇīÔøŅÃÔ¨a=~Rķ—Ŧĩĩē†v$͞ˇPąņņē˙îąrsuÕɍ͚;ąę¸×RëÍ%Ü|nŪĄV!Áد—ėíė´`ÉJíŪw@cG•§GmØŧMŸ~õŊ^~ú1yÔq—$íŪw@ÁM5mōD%%%kÖO ĩbõ:=ŦÔz­Ŧ­´öˇß5zØ@Ũ3v¤âõÉßiÁ’ēoåĒ÷ĄûīÖĮĶŋ“—§‡ÆŽ"ƒAúôËīM¯Ęu^J;fkk+­ũm“†čĢ×^~F—SRõŪGŸkåÚõ7ĒôcBÍvđđQM¸s„œ8^kÛ¤3įĸ¯y“3rČ�uëŌQ?ūŧT‘QgØDcF V^nžļFė–$Ũ{×(ųx{ęÛŲķt9%UM5Ԅ1ՔœŦũ‡ŽH*øŽ[—Ž:xø¨VŽũMŲŲ9=l:´mŠy —+!ņĸzvëŦĮĻܧˇūķ‰iŧ‡ömZęČą“úß7ŗTĮŊ–îšk”†ÜŅG?.\Z5'ĢllmÔŗ[Õ¯ëWĄųՁˇ—‡$))ųĪq6Ęzīílm5í{ĩsī~Í]°XÔŖ[g=:e’ūöÆ{ĘČČŧá:Ļ7[O<ô€âõĶĸåĘÍÍÕ{î†ö‘—Ÿ§ūŊēkÁŌ•šķĶ"yyÖŅãSīטƒôũÜå:6Šôkļ"û*Īįc#TĪßOĶŋ›Ŗ””T ÔO>^^Ež(OÍW+ VЇ/–]ŽÖˇG7ųzyéÛ9ķƜŸ••mözB‚Pŋh—÷Ā&Šü{pąuĸΞcÜ�ˇjŧ¸šē™ū}éōeų”16EBBĸ$ŠŽģ{‘éÎÎNzhʃzhʃЉÕ–-Û´rÕj­˙mƒ>üč%'_Ō?ūö’$ÉÉŲIŲɗô͗ŸĢWĪ•|D7įFގ§°Î•–žĄUë6h߁Ãr°ˇWãF ÔēEsuj߯4tff–6mŨĄ;zwWh‡‚qƒŧ<ëčĖųh­^ŋÉ0Œ>XVVSXâíåĄ[ļëc'LÁ‹$ŲŲÚiÄā; ļ•Ĩ-;viäjßĻĨ¤‚_†ŗ˛ŗŸpŅ´-G9D’Ô žŋöüCQgŽ?iŊē~ęÜą$ÉĮËSá]CõËÚß4aLļėėėĘŦ×ŅÁAVVV˛ąą6ĩ’šúuyĪKņc.äëíejâ]ģ–›B‚ƒtæ,]™nU[vė’““ŖôíŠļ­B”™™Ĩ“Q§ĩ˙ĐEėŪ§œœ‚L{{u ՚uĩc×^IŌī‰ ÆéŨÃtŗ7É ķķMaI|Bĸz„…*8¨ŠéÆŌh4*;;G‹WŦ–$ŲÛÛ)Ŧs-ZļJ{ö”$Í]°Döööōôô0m+33Sķ/—$=­6-CĒÍ`ۅôí!/ ΝjWŅamm­õü5jč EĮĚZ•įŊww¯%{EėŪ§Ø¸IŌüÅ+´{ßAåææU¨ļĖĖ,åįį+77WiééōņöŦĐ>ÎF_0ÕŸ¨ßˇEh`ŋ^úaūYYY•ëē.~ÍVd_Ų99e~>\]œŌ,P?-ZŽŖĮOJ’ž›ķ“^ųY%_ž,ŠüŸÅ⊇/–] ĩh¤'ēŋĖų˙ūø ŗ×2uŌĶ@ēĨiÚ8ĀÔ ĢPnnŽūúōkfŽ �LJjŧÔŽ]Kuę(ņâEíŪŊGAM¯šėųķŅ:}æŒ$)$¤ų5—ķõņŅčQ#4zÔũļaŖ&OĻŲsĐK/<+[[[5lØPÉÉûu>ēzß,•u¸}õīŨ]=Ãģččņ“:zü¤Ž;Š,Ņ/kĶcS&É×Į[įĸ/(//OÁAE?SM´uĮ.eeeËŪŪNöövZŗ~ŖŽ8ĨÔ´4FĨĨgČÛŗčÍOŖ†Ž/t!&NšššjXŋŽišĩĩĻL_lĸc ¸¸8+ķĖõŸRü›n?oåææ*ųRŠŧŊ<Ę]īĩ”÷ŧ?æBū~žE^;9:(=#Ŗ\ûFÍ´öˇßĩaķv6VŗĀ& lĒ c†kPŋ^úôËЉ‹W]_ŲX[ëcEĮį:~ō”ÂB;ČÎÎNŲŲŲĘĘÎÖŊģ+°I#š8;ËĘ`““Ŗâ¯|ŠPčę.D~>>˛ĩąŅéŗįMĶōōōôõĖŠŦuļČë”Ô44 ‹jEÕõ÷Õß~ĩČ4ŖŅ¨ÃGkîüÅE–+ëŊ‹OTl|‚îŋ{Ŧ6mŨĄ?ŽĐšķLŨ"+CE÷Q| Ŧ 1q˛ĩąQ­Znrsu)×u-Ŋf+˛¯ø„Ä2?^ž2 ŠŒ:cÚFVVļŽ?)_/Iå{? k.îę Ĩ:„.�€šĨZ/’4hā�͚3W_~ķ­†"''§R—{īũ$I7RË!’¤ääKZŋaƒlŦm4lhņŽRîáĻ˙d/^L’ˇÂētÖž}ûĩtŲ Ũ3a|‰u˛ŗŗĩ|å/  “——g%éĩUä8 Ҏ÷ödgkĢV!ÁjRđČĶc'OéĢīįjá˛UzäÁ‰ĘĖ*8>šū­Žîˆ”åzšœ’Ē:6ĩôé—3”ŸŸ¯;G –ˇ—ŦŦŦôÅwŗKėĪņĒ. ƒ;ģë€öÅæôˆēūõZ|,ŖÂmddf(//¯Üõ^Ky΋—}IEšmi?Rų ŪęrrrtāđQ8|TRA“ú)“ÆkÔЁúß73MĀ>9m˛tÕĪäÂn€nŽ.ē˜”ŦĮĻL’ĩ••æ/^ĄØøååįëáûī.ąŋĖĖ?ģ…89<EīZ7‹…ŠĪ7Ę(3 IVaĢŨ¨×éJTÖüĒŸ¨s2ŊîÖY-‚ƒ4cîü"ŨvĘķŪ'$^ԇŸ}Ĩ~Ŋē+,´Ŗ†杤äKZúËZEėŪW)õÆ í#ĢØuSx99:”ûؤĸ×lEöeeeUæįÃųĘ?Į Ž#•˙ũ¸–ę¸:rLßÍūIīŊūĘuįW…/ŋŸ[jWŖÂV.'"ŖtüäŠ"ķŖÎ^ŋ•+�ÜJĒ}đōäãhÅĘ_tęT”&=0E˙üû+jŲ"ÄôŸäŠSQúđãO´tŲ YYYéÕŋ˙Í4/++K/ŧô7ŲÚÚĘ××G:v(˛í+W);;[žžžĻåîņãô팙ÚąS_~õĻN™lZ>''G˙xõu͛ŋ@ũúöŅ˙+:xĸšTä8\œ ƛIMKSRR˛ÜŨkWI­°ŦË)Оˇŗ3ĩĘ(Ô¤‘Úļ ŅĄ#Į$t푤û&Œ‘ŋ¯o‰í¸×vSԙsŠŽ‰ÕS<¨&W=%55Íԕ§4…×^ņÁ+CVąmîÃÉŅąÂõ^­<į(äęęĸŦŦėĄÆņ“§´īĀa…Iúķ:ũ~î|E_ˆ-ąääK hPOuũ|õá˙ž.2ØŠ‹ŗŗ/&]ŗ†ÔԂ›JK=Ũ§2åääę× ŋĢSû6jqåÜŨČüĒ”““Ŗ3WĩĐX¸ôĩ iĻ‘CiņRž÷^’RĶŌĩhų*-ZžJžŪ^ęĶŗ›&Ŗ˜Øx=­ŌžCšŅÖ­eíŖ4ÅÂîÂë,=#ÃÔ­¤Ŧc+¯ëíĢ<ŸÜ܂q\ėŠg'GĶŋËû~Ôë7mŅÂeĢŽų[Yķ+Ûá#ĮKŒ×2X>ęúøÉSÕ.¸€ĒTíƒOOOÍøö+M{ô íŪŗW#F•|}|Ÿ Œ+ߎ;;;é­7^WxˇŽĻu}|ŧõŌ Īęõ7ßÖ¸ģ'ĒUË h(ƒ :ĨÉĘĘJ˙üÛKĻžÚuëúëŊˇßÔ_Ÿ}AoŊûo-ZēL!̓•ž–Žˆģ”˜¨† čĩW˙^eį "Įáëë#OOO%$$høč;Õ¤qcŨŅŋ¯î?ŽĘęFÕJIIÕßßüˇîčŨ]Cô-2Īh4*6>AnŽŽ’¤ē~Í­SRĶäãũgË­Ô´4 ŲØØ˜~‰Ŋú—ÖS§Ī*1)Y ę_{l/OŲÚÚęDd”é)FFŖQ˙ũü……v0RĮ‹5‹?}îŧėlmUģv-]ŧŌī˙FëŊZyÎ IŽ.Îzã•gĩzŨF-_ŊŽÄ|o/OĨ¤¤J’ÎGĮ(77W..ΊO0-ãâė¤|ŖQšyy˛ŊrmĨĨũųí|@ƒúō¨ã^¤Qqqņ ĘÎÉQĶÆĻîƒAO>ü€ļFė6cQcĨˆ]û”œ|I]C;”0žŦų’ž‘Ą%+×jüčaÚąk¯)(Ī{īá^[ū~ž:p¸`Ÿ˜¸xũ°`‰:wh+?_o=­ĖŦ,9::Ų§ŋŸO™cĀ~Už}”Ļø˜ ęÕUvvļ’’/+%%­Ėcģ×ÛWÚ_]īķwĨËQÃõt!6NRÁHÍ›čŌåIå{?Ēģŧŧ<ũđķRmģÆx4eÍ�XF¸‹hŌ\+—/ÖŧŸhõšĩ:vü¸ÎGGËÉÉImÚ´VĪîáē{ü8y{{•X÷û&)(0PsüIûöī×ņ'•——'/OO :X“īŋOmZˇ*˛ÎÁƒÔ´iS}ņÕ×Úž=BK–.—ĩĩĩ6¨¯ģÆŽŅ”É¨víZ%öeN7zÖÖÖúĪ{ī蟝ŊŽsįÎ++3Kƒ ¨ŌšQĩ\]]Ô§G˜~ųuƒ.§¤ĒU‹`99:ęrJĒļíܭȨ3zāžģ$|“Ø­KG-_ŊN.ÎNjXŋž.&%kÁ’•Ē]ËMĶ&ß[ĐŪÆFŋũžMƒú÷VtLŦ–Ŧ\Ŗā &Š‹OPJjZŠ~vp°W×NíĩjŨFÕŽå&_omŪĄ3įĸuī]Ŗnę/]žŦĢ×)´C;ÅÆÅkĶÖęĐļ•lmlĘ]¯““ƒÎŋ sŅä^ģV‰×e@*#eŨÆ-С§Ü\]´˙đĨ§gČÍÍU]:ļSã€úöJ˙ĖŦ,mŪžSCîčŖ´´t>sNuÜkkĖđÁJžtYŸ;Kį/Ä('7W=Ãģhåšõō÷ķŅđAũõĮąōņō”‹ŗsŠ~ÎĖĘŌÖģ5 OO%_ēŦ˜Øx…wé¨õëjöO Ģú´Tšã'Ŗt)%U}ē‡•Úš§Ŧų–°uĮ.uëÜAƌĐ[|ĒŧŧŧrŊ÷îîĩ5eŌx-^ąZ˙8*ŖQęÔžŒFŖ)L;s.Z­[6×ēM[”••­>=Âäėäd J“ž‘ĄzuũT×ßWĩkÕ*sĨŠåæĒÁũ{kûŽŊōõöR÷°PíÚ{@šššĘÍÍ-ķØnÄõöUžĪGâÅ$=­}z*&6^>ø]ž€Jåû,VgŠiéúęûš×|xYķ�–S#‚Š 9éäû'iōũ“nxŨna]Õ-ŦkŲ ^ĨYP Ū÷ír-;xā�E;|ÃuIŌŖGéÎŅEoFĮŨu§ÆŨug‰eoô8ÂģuÕ¯ĢVT¨.ÔL#ß!?omŲąKûį-RzF†ėÕ°^]=:e’š_5hėčaƒäčā EËWëŌåššē¨U‹` Ø_RÁgîŪģFiÉĘ5Úąk¯Ô¯Ģ‰ãF+ųŌe};kž>šū­^yæņRë9t€ ƒ-_ĨĖŦlÕõķŅŖN”§G›:ž°ĐJĪČÔ{ޜœ\ĩ ifz2RyëíÕ­Ģfü0_|ú•ĻLšPâuYį(´xÅj]ˆS×NíuĪØQrvrTFfĻ̍Ög_¯#ĮNš–ũyé/ĘČČÔČ!äæęĸË)Š:pøˆ–Ž\+Šā†i֏ 5|puîĐVgÎEk֏ UĢ–Ģ&ß3NON{@˙zŋô–t'5d€ėė}!F˙ûzϝŨEŠ&ˆ‹KвUŋĒwŽĘÉÎŊáųUÍh4jŪÂezæņ‡tGīîZšö7Ieŋ÷'"Ŗ4kŪBõíŅMCôU~^ž.ÄÆéËsMĮūŧtĨîŊk”^ųĨgdjËöÚąko‘ŸéÅmø}›&MŖ§ĸ¯ž˙ĄĖ}”fëŽ]rttÔsO>,[[[<|D?-Znš_ÖąŨˆëíĢŧŸog˙¤{ƎÔ_Ļ= K—S´ę× jP¯Ž\5Ø{eÖ\UNDFÉÁÁ^_͘kzĒĶĖ�XžÁXJį΍¨(X �’´įĐqĩkhé2Ē•^}KŊģ‡i`ߞ–.ˇ=‡Žë›3-]Fķņģ×lU čy3ví;`éĒĩˇ_}Që7mÕĒ_7Ԙ}ŲÚÚĘÆÚZW æûÄC÷+-=CßĖúņfËÄ ÔÔI$ ž[| ˜Š*ëį�TÅ3•Ķâ���¸–i“ī•›‹ŗæ.Xĸ”ÔTĩlŪLAMëķoĒwĸ[Õá#Įõח I�@"x��Ā-āģŲķ4zø MŊo‚ėėėŸ¨™?ūlzĸ��–Bđ Fxį՗,]�Ü6^|ĩ|ãÜU§}Ĩ¤ĻiƜų•˛-��*Sõx#�����Ā-ˆā�����ĀL^������Ė„ā�����ĀL^������˄⍆ŦŦŦ”—Ÿoé2� ƒÁ`é�ÜĻøņ Ļ"xĒ!WgGÅ%$Yē āļāęâ"–.Ŗf0åâė\æb’8§�*‘ҍZnn–Ž�*„⍆ęųy+.1Y1ņ•“kér€[ڃ“Æķ5jy įĢ Ŋ{t•Ä9P‰Ŧ zxōŊ–Ž�*ÄÆŌ�(ÉÎÖF͛6Ôš qJLēDø˜Q“€zęŅ)úúû”š–&#­_J0 rqvփ“ÆĢI@ƒ2—÷ņōÔ ;zkũÆ­ĘĖʤņ €›âęâŦ‡'OT=?_K—�b0–ōfTT”,P�����@ÍU<SĄĢ�����€™ŧ������˜ Á �����€™ŧ������˜ Á �����€™ŧ������˜ Á �����€™ŧ������˜ Á �����€™ŧ������˜ Á �����€™ØTՎNŊPUģ����5Ēīgé�€/������æRe-^H›����Āí†/������fBđ�����`&/������fBđ�����`&/������fBđ�����`&/������fBđ�����`&/������fBđ�����`&/������fBđ�����`<ŋR+�� �IDAT&/������fBđ�����`&/������fBđ�����`&/�ūŸŊûŽŽĸęÃ8ūl6 ¤Ņ‘ĸBŊ HīŊ)ÂKiRiRDEzSEēôŪD¤ƒĸ‚ØÄĄ‰”N ¤“lōū˛ d2ŲÅ|?įp;{gîogîÎ&OîĖ���� âđÁËöģÔŠK7UŠVKEK”Q…ĘÕôb÷—ôĶÁCö.í?!::Z~%åPRß|ûũ}ĪwëŲ[~%5vüD;T—šUŦRM~%õtÕ Hō܄ISåPRsæ-°Suƒ%ŽÛäūuéÖĶnĩÕĒÛP~%õŲŪĪø8Ŋŧ3}†üJjāaéŌļą×ØŗuĨ×xcÜ��đøqļwŗbåjŸ4E’T˛D UāņãúöģīučđĪúxãZ•,QÂÎUÆ žqCK—¯ĐĀWúŲģĀfšs咇‡Į}Ëķå{ÂÕ$¨^í]ģ~Myķæĩ[ 0ž#Ž=��š9tđ’ø×üąoŊŠn]ģH’"##ÕŽã <Žuë?ŌÄņcėY"`8“ɤÅK–éÅ:+GŽėö.°ÉČ׆Š]Ûįė]FīLdīqė�€ĖÍĄ/5 šuK’”;wnë2777-]¸@?~{āžĐeËļíjũ\;•(SAĨË?­Ž/tÕ×ß|›¤Mâ”ãcÖešļûtÕō (Š_ŖįÚwT™ O[Ÿ[žr•5kŠâĨĘŠNƒÆšúÎt……‡[ŸˇX,šˇ`Ąšļh­e*¨VŨ†Z¸xIúėpõę5Ŋ1j´jÕm¨âĨĘŠfš8ųmEFFZÛØ˛’ÛĮ‰ËūYŖÆŒSšJUUļBeMöŽ,‹uũČČHM{÷=ÕkØT%ĘTPƒÆÍ´hÉ2ÅĮĮß×Į?ԀA¯Ēd؊ĒUˇĄvíŪŖË—¯čÅî/ŠxéōĒߨ™~ũí÷T՟Q7j Đ°0-X¸øĄí2ËūĀ[ZĮ¨-ã?ĩ—zØ˛ÍĐĐP >RĨË?­JUĢëíi3w×ķŠm—ÜyŅ–÷ -įf[Údéų9“(*:ZoŒ­r•ĒĒ\Å*7q˛bcc“­Á–cjë¸��ŽÍĄƒ—R%.#5zŦfŧ˙~:xHŅŅŅʗī å͛'IÛEK–ičđ‘ <~BõëÖŅĶ•*꧃‡ÔũĨ>úbßūT÷íęę*IzįŨ÷Ē åËK’Ū›9K&MՕËWÔŧYSšēējŅ’e0čUëēSŪžŽwߛа°põ~Їŧŧ<õö´Z´xi÷„cé?p°6|ôąŠõ׋]:+ū|ZļbĨFŧ>ĘÚÆ–}Ü>N\>nÂd]š|E ÔSXx¸-^ĒÍ[ļZ×kėx-X¸XîîęŪĩ‹Ž]ÖÔwĻkũ†îëc܄IĘę–U~žžúįÂŊöÆ[<t¸ ( _o=Ģ!CGX˜v¤cØŖ[W9;;kåę5ēzõZ˛í2ËūĀ[ZĮ¨-ã?ĩlŲæØņ“´eÛv™Íf5mŌXß|û>Ų˛ížmŲÚ.šķĸ-īA[ÎÍļ´É Ōķs&ŅėšķuęôŸĒRųi…†…iåĒ5ZļbU˛5ØrLm7��Āą9ôĨFƍQĪ^}u=8Xsį¨šķ?”Ģ‹‹žyĻĒzõėŽš5ĒK’BÃÂ4sÖIŌ¤ ãÔĄũķ’¤ąã'j՚ušņūjP¯nĒú6›Í’$w77mũxŖœuķfˆ.JøkÔģĶĻĒQà UíúõÃ?ę÷?ŽĒ`ÁZšz$éƒ÷ßUĨŠô⠝UŖN}Íûp‘zöčfŨöã(22R‡>"ggg-˜7[Ž..ŠÕŗįZ¯ŸŋlĶ>xĐ>–ūŨ÷ųōåÕĸįI’â,qÚē}‡öí˙JíŸoĢččh?qREũũõΔI*]Ǥ˛fÍĒfĪ՞ĪöĒSĮ˙%ŲVą€�ŊûÎT]ž|EÕjÕUDD„|}}4uŌũtV 7ĶŲsįtîüyyzz:Ô1,X €ÚˇkĢuë7jöÜųš0nô}m2ĶūĀãái34gîũ7žųŪ4•/W.ŲõŌ2FķįËgĶøO [ŪSÁ7nhĮŽŨ’¤éoOVãF u;&Fõ6M˛-[ÛŨũúī>/ÚrNŊ}ûvŠįf[Îß˙ļŒŊôüœIT ~­XēH&“I'ŋ­e+Vjũ†ęũRûjąå˜†Üēeķ¸��ŽÍĄƒ—2ĨKi˙ŸiĮÎ]úęë¯uđāa]ց¯ŋҁ¯ŋŅ”‰ãÕąC{9ō‹uštë–Í­ëˇhŪLĢÖŦ͉'u;&FŽ..ŠŽĄUËÖ@ā—_Õí˜IR:ĩ%I^^^:rđßoúbß~Y,™L&å{â ]ŧtI’TčŠ'õWĐYū팊MÛq�nnnz˛`Aũsႚĩh­† ęĢJå§õrŸŪōđp—$ũōëoŠÚwīãģ5iÜČú˙2eJkëöēråĒ$)K–,Úšuŗ$)..NŅŅŅÖYP—/_žo[5k&„tųō=ĄlŲ˛éÖ­[ĒQ­š$É×Į[Ž..ēŖë׃uúĪ3w žŌOoŪĸ mRŸ^÷3GfÛp|׃ƒu=8øžåQQŅ6­Ÿš1ę]¸pĒÆŋ-lyO>ũ§õR’ÚĩjJ’\]\Ô°A=­\ŊÖē-[ÛŨ-égmįԔÎÍļœŋ˙ R3öŌķsĻMĢ–2™L’¤õëj؊•ú+čėū°å˜†„„¤zÜ���ĮäĐÁ‹$yx¸ĢÃ˙ÚŠÃ˙ÚI’ŽüōĢÆŽŸ¨?ŽĶŦ9ķÔąC{߸!)á‡$777ëē9sæ$ÅĮĮëōĨË*TčŠT÷÷%M‰÷œÉš5k˛!έ;mâããUģ~Ŗûžŋ|ų˛Cũ’z÷L…čč¨ûžˆHøc—ģ^īÜŲ3õú¨Ņ:~ü„-YĻEK–ÉËĶScŪzSΎ}6ÕûāŪËÆåČūīdŗdI˜n‰‹ŗ.[ˇ~Ŗ/[ŽŗgĪ)îŽåēü=›——õ˙‰ĮÎËËķŽígŅí˜Y,‡<†ųķåS—ÎĩtųJŊ?kļråĖy_›Ė´?āøĻŊ=ųĄ78ŨüÉVŊöÆŋ—ˇ4jX_ķį˞>NÍ•R7ūm•Ō6oܸ))áü˜5kÖkĪ–-ÉvlmwˇģĪ‹ļžS:7K)Ÿŋ˙ R{wKĪĪ™\šrY˙ŸũŽí†Ü šīsΖc™đ™œšq��“Ã/.^ԏ?Tdd¤:wė`]^Ą|9Ŋ6|˜ēöxI—._VllŦræHXĸŖŖeũåúõ˙âuīˇÁDGßļū?8øF˛u˜ūŊ Nū|ų$IQQQē}ûļõņ›7C#OOeĪ–ĐŲlւyŗīÛ^ą€�Ûv@qvvVöėŲĸ'OŠAũzÖįĸĸĸtęôŸ’¤‚ Z——-SZģļ}ĸ ŗgõĶÁCÚûų—úâË}ųæ[ĒQŖZĒ÷ÁŨûØVûöШ1ãäęâĸÉÆÉßŋˆžÜˇ_ķ?\”ęmŨËQaŋ—ûhũÆMÚ˛uģš5iœäšĖ¸?đx‹‹Kō‹ŦÅ÷ÖgÄøˇe›‰á~LLŒ"##­Á˙ĩëדlËÖvwģûŧhë{0Ĩssū|ųljƒŠW7Cnūû˙› ˙7™LÖã7[ŽéŲsį$ĨnÜ���Įä°7×=s&HÃFŧŽŅc'hמO­Ë-‹ž:p@RBâėėŦŠĘËëÎ_GwíūˇíŽ ×F—+[Æú|ž;ߐôĮŅc’…¯žūÚϚŠXCÄöFDD¨~ãĻĒZŖļ~ųõ7•-[ZfŗY‹EķįWƒzuU§V̈́ŋnÅĮ'ų‹­Ŗ¨]̆$iŲō•úũŖ’î0fÜDŨēuKNNNjPŋŽ$éėšsš3o6lÜ$ooũ¯ŨķZ´`Ž|}ŧ§K—.eČ>øõˇß$IEü‹¨Ã˙ÚŠRÅ ēx1aĒļ%Îō°USä¨Į0wŽ\ęŅíEÅĮĮëŗĪŋHō\fÜxŧĩkûœÎœ<fũˇpūœ4oˈņoË6‹ņŗÎüōÎgBXx¸>ũlo’mŲÚ.9ļŧm97ÛŌ˙Jí¸Úyį~,’ôŗû%IEũx)­-ĮôQĮ ��p;ãĨFõgÔ¸QC}ļ÷s ôĒōæÍŖ\9sęŌå+ ‘$ 4@RÂ}V^<@&M՛ŖĮęÛīžWđúęĀ×rvvÖČ˙~MtŨ:ĩĩiķ'šūî{:ķ×_:tč°rįÎmŊĻûaräČŽ^=ģkÎŧųú(}šoŋŽ=Ļ›7CTĩJeU­RYNNNęÜņZĩfē÷ęŖÆ *đøqũ|äUŦP^õęÖ1f‡=‚Wԁ¯ŋÕõā`ĩiÛ^9rdWhh˜u ˙ ũå]¸°$ÉÃÃC .VTT”~:tHųžxBgū Ō_AgU¸P!•(^\Y˛d1|õ/"I:qâ¤&O}G.\´ūđėŲsz÷Ŋ™>tHšļ'wn‡=†Ŋ_ꡕĢ×*444ÉōĖē?⏒ģÁŠ$íŪą%ÉeĄĘˆņoë65Ŧ¯=ŸîÕëŖFkßWô˯ŋÉÃÃC7nÜ´~ãRî\šlj—[Ūƒļœ›ŋ–ūamū ŌkėŲ:b-ąwÚŌ ]{({ölÚķiB8Ōå…NÜļ-ĮÔŨŨũ‘Æ ��p;ãÅd2iöĖ7æ-•+WV–X‹N˙yFNN&ÕŽUSK.°~{‘$uīúĸŪž2Q~~žÚškˇ>Ŧš5ĒkŨęzĻjkģ7_MM7”ÉdŌî=ŸĒUËæęĐ>áū1ˇoßž¯Ž{ 4@#†ŊĒÜšskÛö ˆPn]ĩpū9Ũ™>æ­75xā+ruuÕú鯠 ŊĐŠŖ–,œomãH|ŧŊĩuķF=÷lkųúx+""RîîîĒ^í}8oļ čom›'wn­_ŗRĩjÖĐ_î×âĨËõëoŋéšg[kõŠĨʒ%‹$ã÷AķfMÕŗ{WeĪžMë7~$gg-œ?G/žĐIf''mŨžã‘ļī¨Į0[ļlŧšnfŨp\׃ƒuîüųū‹‹Kß_˙ļns¸1Ē_¯ŽbbbuāĀ7jŪ´‰õōØģ?Slm—œ”Ūƒļœ›m=?îŌkėŲ:ßØŅo*WŽ\Ú˙Õ×ʙ3‡úõíäRé{Ųr^}Ôq��ƒ)ū2 ’Ę�����x|Ũ›Šđ§j������ƒŧ������„ā�����Ā /������!x�����0Á �����€A^������ Bđ�����`‚������ƒŧ������„ā�����Ā /������!x�����0Á �����€A^������ Bđ�����`‚������ƒ8gTGŋ˜Q]���€| °w �Œ������Ŗd،—<9<3Ē+������‡ĀŒ������ƒŧ������„ā�����Ā /������!x�����0Á �����€A^������ Bđ�����`‚������ƒŧ������„ā�����Ā Îö.����˙=A_Đē>QxD¤âââí] `2™äáîĻ;´UĄ' Øģ�pXĖx��@ē úû‚.[ŖĐ°B—˙°øøx………kÁŌ5ēxųĒŊË�‡Eđ��€tĩvã{—€Œb2)>>N+Ö}līJ�Āaŧ��� ]……‡Ûģd$“IĄaaöŽ�Á ���€GĪ%e�‚������ƒ<vÁËÔwĻĢmûN†÷Sģ~#-\ŧÔđ~géq,ØĪöƨ1*WŠęC˙mܔp-uŖf-5gŪCëņú(•ĢTU›>ūÄæuė5>S@æŗįĶŊ*WŠĒžūæģ>?~âdÕŦÛP׃ƒ3¸2��€LōuŌë6|¤ŖĮŽiŌøąö.÷ūęųûąwįĨžŨÕēUKëãŅãÆËŋHu{ą‹u™ŸŸO†ÔrëÖ-í˙ꀊúkێj÷üsŌ¯”ļņ‘ÚuŌëüĀy°ŸĻMiķ–­š>ã=U­ZYŽ..ÖįŽ×'[ˇkäˆaʝ+—Ģ��™UĻ^Ûģ$ŖuĢö.Á!ųņ“?ëãŦY˛*OžÜĒöL• ¯e÷§Ÿ)kÖŦöę`õí?PgĪ“wáÂŌwZÆGj×I¯ķįĀžŪxm¸Úu|AkÖŽWn/JJ¸įÄÛĶŪUņbęĐūy;W��2+‡^Ž\šĒq'ëāĄÃōōôTûvmīks=8XīŊ?K?<¨[Ɵ/Ÿ:ul¯Î;H’zö~Y‡>"IÚžc—6Ŧ]Šŧyķ>tD‹EĶŪ}_;víRtômU{ĻĒÆĨ9˛ÛÔˇ$ūųˆæĖ[ S§N˧€ĸū4 ŋ*UŦ IŠÕĸ%Ë´ûĶĪtņâ%åĪ—O]^č¨íÛ˛OĖ×aËą¨×°Š^ęŲ]ž9Ŗ/žÜ§¸¸x=×Ļ•ēw{Qã'NŅ‘_~•ģ››ú÷ëŖ6wfsÔŽßH]:wRŸ^=­ÛčŨ̇.^ŧ¤=ŸíUDx„*V,¯ąoRž<š3f‡<†œœœ´`ábmüčc…††Ērå§5qüë_weœmÛžSM5TÕ*•U°@íÜĩGũ_#{×yØ{đAį‡žũĒ÷K=õŨ÷?čāÁCúrīnŨމIõyÆĪ×Wsæ-ОO÷ęzp°ōæÉŖ͛Ē_ßŪrvN8õÖiĐøžžŧŧŧRwđH’|}}ÔĨs'-Z˛T­Z4Wž<šĩs÷ũúÛīZĩ|‰œœŽŽļå|˜Ōg7��@j8ô=^Ū;^ūyFs>xO‹ÎĶ͛7ĩ÷‹}Iڌ?QŋūöģŪ™2I›Ö¯QĪ]5}ÆL}šoŋ$éƒ÷ĻĢd‰âjÚ¤‘öņЊúû§¸Nĸ-[ˇËgŅŧŲhÂØˇtđā!MžúŽÍ}GDDhАaōķķÕĘå‹ĩzÅ-Ēū‡($$D’ôŪĖŲZžrĩz÷ėĄMÖčÅ.4}ÆLmŪ˛Í°ũz/[ö‡-ĮÂŲÅY+W­QŨ:ĩõÕŸiđĀūZšz­^8D/õčĻ_~ĻÖ­ZhōÔiÖ×/gg-[ąJEŠøi÷ö-úøŖu:x\.Zbä.xėíųl¯nŪŧŠŲŧ§Š“'ęˇß~×ü‹ŦΧuœũõWū8zL­[ĩÉdRËÍ´c×îûžšĀQĮGJīÁ\\\´iķ'*ęī¯Å įËÍÍ-Mį™ÉS§é“­Û5tČ }˛iŊŧō˛Ömب÷?˜m­īA}HģžŊ{ĘĶÃSīΚ­đđ͜5GmŸm­˛eJ[Û¤t>´åŗ�� 5vÆËå+WôãOõÆČĒZĨ˛$éõ׆ëû~LŌnİWev2늧ž”$y{ֆ›ôũ?Š~Ŋēōōō’“Ų,åĖ‘ÃĻuåɓ[oŧ6\’TēTI8yJ+V­Qdd¤ÜÜÜRÜÎĨ˗Ž͚ĘĪ×W’4rÄP5iÜPŽŽŽ ĶÆMëĨŨÔĒeķ„m.ŦĀĀãZēl…Ú>ÛÚ Ŋ›TJ¯ÃÖc!Iŋ¨N­š’¤ĻMkŌ”wTŽl•+[æÎ˛„Ÿž=w^eËd`=~žžzļu+IRū|ųTŗzu L÷×ũ_âåéŠ×īŒÕR%KčË}ûõûG%鑯ؖm;äí]ØúKKĢ–ÍĩpņRũ|äë_~y|¤ôtssģīü`2™ä–5Ģ^<ĀēÔžgnŪ Ņöģ4tČ 5mŌH’T¸P!Õęĩë5xĐ�šē¸<°/�iįîîŽáÆhÄČ7ĸččÛ<đß÷—-įÔÎ@Z™Ífuíøŧü}ĩrũĮ <qÚ.u4Ŧ[S­›5’Éd’”pIŪļŨ{õųūoėR�dŧœ9$)!đHd2™TēT)?qŌēĖÃŨCK–­ĐĄÃ‡|ã†âãârë– ?äļŽSĄ|š$Ë•-ŖØØX˙ûõOq;Ū… ËÛģ°Ū|kŦÚˇkĢjĪTU‰âÅôtĨŠ’Ļ2ĮÄĨÚ3U“ôSŠREmŪ˛MááōđpOũÎKĨ”^‡­ĮB’||ŧ­˙÷ōôLXæũī2IRhhh˛õŊᯍ؞y)$äV^YæQŽlŲ$såĘŠß~˙C’tōäŠ43‹ÅĸģwëížWllŦ$Š`*_ŽŦļīØe ^y|¤ôLNbd­+•į™'OĘbą$ų+ģ$•,Q\‘‘‘:wîŧõ>÷öāŅ4nØ@›ŸIø†Ŗ1ŖŪ°^,Ųv>LëyŽuŗFĒWģēœÍæ4­kąh߁ī´m÷ŪtŽ,}tzžĩ*–Kø\čĶ­ŗ¯Z¯Ŗ'SXëŅŊ3ū š?dFĨÉdR›æÕĻyc번ˆH7ÕđÚ� ŗpØā%""B’”5kÖ$ËŨŨ˙ũ1&&Fũ RlŦE¯ *__o9›5xčđdˇ›šu<īüb˜(ą–¨¨(›ļc6›ĩ|ÉB-_ąJ›ˇlÕŦ9ķT ~ č˙˛ZļhϰđpIR¯žũeē̟¸;—q\ģ~MÆŪÄԖ×aËąHô ŋfɒåže÷^Ērˇ{ûAĘÜŨ“ū@•øW,Iigß}˙ƒŽ^ŊĻšķ?ÔÜų&yîôé?õúkÔ5kV‡)Ŋ“s÷{?-į™đ;ûüŪ@+qŸ$îŗ{û>ę×ĢŖīøQõë×M˛Ü–ķĄwáÂi:oāŅÕ¯]]æ4†.’äl6Ģ^íęŧØũtø­Û´UeJ×ūoP™RÅåîæ&gggŊØĄ­^÷ļáĩω)å&��Û9lđâæ–đËUXXX’åĄa˙ū%ü÷?ŽęäŠĶZēhA’ŪŨ¸ySO>ų䡛šu"#Ŗîyy§67›ˇ“+gN 2HC‡ ԟgÎhåĒĩ5fœ|}}äyį¯ûS&ŽSQ˙ûj-?˙_Cz˛åuØr,ā¸Ō:ÎļíØĨōåĘjİW“,‰‰Q¯žũõåū¯Ôŧi‡{–*Y"ÅõĶržI SÂÃ#’,O|ėéé‘ļ⑨z>|ÔķŌfČãí]‚Íl ‰NŸ Ōæí{Ô§û *QĖ_ģ÷îĶė…Ë5°OwšģšéLĐų ŠuäXfŽ�€Ŋ9lđ’xųÁņ'Už\Âe111:xčgåȞ0m8:úļ$%™FüëoŋëŸ.¨TɒIļ—øôÔŦsä—_’<>z,PŽ..*ôԓ:ōËo)nįīŋ˙ŅŠĶ§U¯nIR??ŊõæHmÛąSūyFõęÕ‘Ģ‹‹ŽßPc_ëv‚oܐ“É)CŽ%ˇeØr,ā¸ŠĻzœŨēuKûŋ: Æ$š„(QÕ*•ĩcįn5oÚÄĄĮGJīÁÄ_ 6Ã&-ᙀĸEe6›uä—_“\nôëīŋËËĶS… JŸ Ul9ÚzŪ@ú›=mBēlgākcŌe;“ē<Ŧ/gŗŲēHRŗFõ$Iŗ.WŊšÕ´ūãŒû"…÷§Œą~Ŗ^Jbbc5ôÍô9�€ŧ,X@e˔ÖŌe+T¸ĐSʕ+§ÖŦÛ Wk›bŊĘÕÕUk×oÔËŊ{éÔé͚5gžĒ=SUAgĪęzp°rįĘĨėŲ˛éĉ“:~â„ōįĪgĶ:’ôĪ… Z¸xК5mŦŋ˙ūG7}Ŧ† ę+kÖŦ6õ}éōe ņē†  :ĩjĘd2iįî=rrrRš˛eäåéŠļmŸÕü*gŽ*]Ǥ.^ē¤i3ŪWž'ōjÎīžŸmyļ 8Ž´ŒŗŨŸ~ĻØØX5¨_īÛlŌ¸ĄÆM˜ŦĢW¯9ôøHé=()éų!ßũŗŌtžÉ—_ĪļiĨ%˖ĢpĄ§TŦX€>ĸ 7Š{×.6˙đ }Ųr>´åŧ¤äŪĐ%QŗFõtņō­Ú°9C뉌Š–—§mŸ=Q÷Ėø�<:‡ūé˙íÉ5~Ō ~u¸<==Õž][ĩlŅLŸ™đ5ĩšræÔ„ąŖ5kî<mßąKĨJ–Đ„qctåʍ|ã-õîûŠ6´N:´×¨1ãÔũĨžš1mĒMëXb-ęÕŗ‡.\¸ Î/öĐíÛˇUĢFuŊ1rxĒúž8nŒVŦ^Ŗy ĘŲl–ŸŸ¯ŪwšŧŊîŠ1bčeķōŌĖYstõÚ5åɝ[uëÔŌ€WúeČ>ļõu¤t,āØR;ÎļŨšynby¯zujk‚ĶTíÚŊGŨēvqØņņtĨŠ)žī=?Ü+­į™×_.wwM~{š‚ƒo(ū|ęũRŊÔŖ[Fī�wIé|hËyƋĩX´rŨ&;qJ%‹U×Ní’ÜOeĘɊŠŠļ>v˚UĶ&ŧiRī“\č"I'Në÷ŖĮ3ŧĻČČ(yŲx™kDÁ �¤7SüæØÉĮĮ'];zØˇ”���āŋã͉ĶSŊÎŨ—}˙Ķa­Ũ´Õú¸sģ6ĒVĨ’MÛɈKkŊˇ¯”B—…Ë×(Öb1ŧž{ ØGŪ…ž˛ŠmĐšŋ5cÎÂ4õ3eôˆ4­g$///{—� ē7Sqč/���€LIŋfĮgŧ8jč"IЏ|čŪ/—��<:‚���8”Ę•Ę+đäi=~JĨŠUåŠå’<?}Â(;U–ŧ˙=×2ŲĐeÅēMęÔŽM†ßÛ%Qj”ȨH+€Ė‰ā���ÅŲlVĪ.’č ��ĪIDAT}Ūgŧz˛ā}ËgēŧŌģ›üũ|ėŧ¤âž-Š™�° Á ���ÆŅĀ“Zŋy›ĸĸĸ•7On]Ŋv]Y˛¸ĒCÛÖ*S˛˜$Įœņ’#Gļ$īžŧČßĪĮ>EŨ‘Ē//�î^���ā0vžO7CnI’Î˙sA’­=Ÿīŗ/ŽČb‰SXx¸nŪŧĨķ˙\ĐÆOvØíž.÷J͌—Ô´�؆ā���vgąXd6›5|`ß4oÞAĮ[“R˙MN%u÷x!x€ôFđ���ģûōĀwĒ_ģēĖfsšÖˇX,ÚwāģtŽ*ųžĖfs’¯ĀN‰=CĄ°đp›Û†‡GX �dN/���°ģmģ÷jÛîŊö.Ã&Š ‰22zĀ“§uöü?ō.ôäC۝=˙OžÎ Ē� ķ x���Ráq ‰$)**ZīÎūĐŪe�@Ļådī������ūĢ^������ Bđ�����`‚������ƒŧ������„ā�����Ā /����‰Édī �Āqŧ��� ]yzxHņņö.%>^^ž^öŽ�Á ���ŌUį˙=ˈĖÄɤnŸˇw�ā°^���Ž|ž*¨>=^§‡‡L0˙iîę×ķEx"¯ŊK�‡ålī���đßãķTAŊ9´ŋŊË��Āî˜ņ�����`‚������ƒŧ������„ā�����Ā /������!x�����0Á �����€A^������ Bđ�����`‚������ƒŧ������Ä9Ŗ:ēv3,Ŗē����yyyŲģ�ȸāŎPŒę �����Ā!pŠ�����€A^������ Bđ�����`‚������ƒŧ������„ā�����Ā /������!x�����0Á �����€A^������ Bđ�����`g{���€˙ž?ƒÎiéĒõ P\\œŊˁL&“ŧ<ÜÕģGųzŌŪå�€ÃbÆ ���Ō՟Aį4sŪbŨ #tų‹×­Đ0Ŋ7{ĄūžpÉŪå�€Ã"x��@ēZ˛rŊŊK@F1™§—­ļw%�ā°^���ŽBÃÂė]2’ɤ[ĄöŽ�Á ���€Goī�Āaŧ������äą ^ƎŸ¨&Í[ŪOÅ*Õ4gŪÃûqTĩŸ‘ŧ>ũČ/ ¤õ_‰2Ô¨YKMšōļ.\ŧhīōŦúô æ­Ÿŗw�2ą;wË/ ¤öí?đĀįß5Zå*UÕĩë×3¸2��€Į0xI‹•Ģ×jøČ7ė]j… ŌÚUËĩvÕr͝5SmZĩÔ§{?WŗmôĶÁCö.�BËÍTŊÚ3š4eĒnĮÄ$yîŖĮ´qĶf {u°ōäÎm§ �@f–)‚—?ū8jī€4q÷p×3UĢč™ĒUTŋ^ č˙˛öėØĻbÅÔĀ`……‡ÛģD�pãĮŧĨķ˙ŖeËWZ—ÅĮĮk܄I*Yĸ¸ētîhĮę��@fæĐÁËåËWÔŖW_/]^UĒÕŌĖYsîksíúu ņēžŠYGÅK—WũFÍ´|å*ëķ_čĒM›?ŅæOļĘ/ ¤ŽϏNĸØØXMœüļ*VŠĻ’e+Ēo˙ēqãĻÍ}KŌOŠCįUŽb•.˙´Úw|!ÉL…ØØX͜5G š4ˇncõÚuéąûlfË~NŠÎv:Ģ[ĪŪ÷­×ŖW_=˙ŋN†ÖŸŲxx¸kʤņ žqC›7oą.Oi<ÚrŒŒˇoßÖÔwĻĢz­z (YV5ë4ĐģīÍTllŦĩMŲ •ĩ`ábxũM=]ĩ†J”Š >ũ(øÆTĩąå5TĒZ]K—¯L÷ĨĘ)4”ob�wEŠøŠg÷nš3ožŽ^Ŋ&IÚ˛mģ~>ō‹&Œ#'§„yl9G¤ôŲ ��ŧ {íu<yJK-ĐÚÕËuãÆ íūto’6#ßĨÃ?ŅŦ÷gh÷ö-zšo/MšōŽ>Ûûš$iŅ‚š*]ǤZļhĻC?~Ģb)Ž“čŖM›e‰ŗhų’Ešūöd}˙ũ=vŧÍ}GDD¨WŸ~ō÷/ĸ?Z§O6­WņbÅÔũĨ> ‘$M}į]-\ŧT¯ŧÜW{vnÕK=ģiâäˇĩáŖÜĩIØ˛ŸSĒŗU‹æúū‡“üĒīž˙A­[ļȰגYø)"_oũøĶAë˛”ÆŖ-ĮȨņ8zėmÜ´YožūšöîŲĄáÆhÅĒÕz{ÚģÖ6Î.ÎúpŅ=SĨŠ~úūkíÜēYGĶÄISSÕÆ–×āââĸu6ĒX@€ÖŽ^!77ˇGz}�àũäåéĨˇ§ŋĢđđŊ3}†:´^ʗŗļIéaËg7��@j8Ûģ€ä\ē|Yß}˙ƒÆ­ę՞‘$ķ–žųöģ$íFzCf'ŗ zJ’äëëŖUĢ×ęëožSãF ååå%ŗŗŗ\]]•+gN›ÖI”7o=J’TļLi <ŽEK–)22Rnnn)nįÂŋ ×ŗ­[ÉŋHIŌØŅoĒe‹fruuUhX˜V¯]§~}{Ģísm$I>ŪŪúãŖZđá"uh˙|úīØ{Ø˛ŸmŠŗYĶ&š0yĒžÜ˙•Ú´j)IÚûų—˛X,jŅŧŠá¯#3*X° Ž^ģf}œŌxLé5oܸŠÍ[ļꍑ#Ô˛E3I’wáÂúķĪ3Zē|Ĩ^1LŽ..’¤R%KčųļĪJ’üü|ÕšS͞;_“'Ž“ģģ{Šm,qq6Ŋ“É$ˇŦYõúkÃŌôš�8&wwwŊ5ęu ôĒnŪŧŠččÛzmøŋīs[Îs)}vie6›Õĩãķ đ÷ÕĘõ+đÄiģÔҰnMĩnÖH&“IRÂ%yÛvīÕįûŋąK=�8lđrúôIRš˛eŦËL&“Ę•-ĢŖĮ­Ë<Ü=4˙ÃEúáĮu=8Xņqņē"Ÿdˇmë:•ŸŽ”äqÅ åĢŗįÎĢxą€ˇãëã#__Ŋ:ė5ŊĐšŖjÕŦĄR%K¨j•Ę’Ļ2ĮÄĨVÍIúŠZĩŠ6|ôąÂÃ#äáážē—JļėįĀĀã)ÖųÄyUĨōĶúėŗĪ­ŋÔīūô3U¯öŒōäÉcčkČŦbcce6›­S)#ŖÆcāņã˛X,Iūâ,IeJ—Rdd¤‚‚Î* ¨ŋ$ŠTŠ’IÚõ×íÛˇuųōųúú¤Øæęĩk6ŋ†Šʧúĩ�p|͛6QÍÕĩo˙M™8^9sæ°>gËįYJŸŨ0NëfT¯vu9ßõŲ–ą‹öøNÛvīMšątzžĩ*–+-IęĶ­ŗ¯Z¯Ŗ' ī÷ņoČũ!3;M&“Ú4oŦ6Í[—EDDj上Ɏ�H‡ ^ÂīÜ44kÖŦI–'ūÕ[’bbbÔ­g/ÅÆZ4æ­7U¤ˆ¯œÍÎęĶī•dˇ›šuŧŧŧ’<Nŧ!22ŌĻí˜Ífm\ˇZ -ŅúiúŒ÷U°@ {u°ž{ļĩBÃÂ$I_ė.Ķ]ũÄÅĮK’Ž^ģ*oöVÚŲ˛Ÿm­ŗe‹fšōötEGG+&6V_ķ­&Oghũ™Ų_AAĒQŊš$ÛĮõΑQã1ėÎv===’,÷đHx~× ‚=ܓ;nn oŨēeS›Ôŧ†{ßß�ū;š4n¨ožũN7L˛Ü–s„ˇ÷C?ģaœúĩĢ'ųƒBj9›ÍĒWģz†/ļ†D?ūEë6mU™RÅĩ˙ÛTĻTqšģšÉŲŲY/vhĢ×ĮŊmx­ibJš �Āvŧ¸ģ'„÷ŪôōVčŋŋ€ũōëo:~â¤Ö¯YŠ*•Ÿļ.ŋŦ§žzęÛMÍ:‘÷<ޏS›ģÍÛɝ+—Ū9BoŒĄS§Okņ’åöÚëō÷/"/OOIŌûīžŖb÷ÕZ°@ž†ôdË~ļĩÎĻMkėøIúú›o%I÷ũЋôqđĐa]šrÕúW[[ĮãΑQã11ā Kú L‰Ŋŧ<­ËÂÃīm“đKRļėŲmj}ûļ!¯Āƒ­įš‡}v—)]*CkÎL†ŧ1>åF–čô™ mŪžG}ēŋ Åüĩ{ī>Í^¸\ût—ģ››ÎĪZGŽeæ �؛Ã/~žž’¤cĮUŠbI Õ˙áĮƒĘ™#aÚpttÂ/YwO#ūųČ/úûīTļL™$ۋŋķ×ŦÔŦsčđá$ûũšē¸Čģp!:|$Åíœ?˙ˇŽŸ8ĄF H’ŠúûkŌ„ąúø“-:yō”7n(W]ģŦæEüŦÛš,'“S†\KnË~.Qĸ¸MuæÎ•KÕĢU՗ûŋRhh˜ę×Ģcũ!é'$$DŖĮŽ×“OTķf ÷Īąu\?ėŲzœSĢDņâ2›Í:tøį$—ũüË/ōōô”÷ŋŗhîžY°$ũūĮrssSÁųmj“'Onģŋ§�8.[Îs)}vŧgö´ 鲝¯I—í<Lbčō°žœÍfkč"IÍՓ$Í^¸\õjVĶúˇ^gĸ÷§Œ‘ŗŗm?öĮÄÆjč›és,�� 6xyōɂĒPžœæ/X(īÂʝ;—–­XeŊ §$•(QLŽŽŽZžrĩxE'NžÔ´wßSÍÕuæ¯ŋtíúuåɝ[ŲŗeĶąc:¨ōÛ´Ž$˙ûo͙ˇ@­[ĩĐšsįĩfŨz5kÚDYŗfĩŠī /Ē߀Á9b˜ÔĢ+“ɤ-ÛļËÉÉI+”——§§:vüŸfΚ­\9sĒ\Ų2úįÂM˜<UōįĶ’… b?§ĻÎ͛kÎÜųēĒwĻL2ŧū˙ēˆđũđãO’ąãĮOhųĘÕ ˆĐŠĨ‹ŦĮÉÖ÷‚”ü1z”ņޝ|}ßōbÅ”?_>ĩo×Vķ?Lc%K–Џ?ÔĒÕkÕ§WĪ$?^žrE3gÍQÛįÚčôé3ZŊfZĩhŽ,Y˛ØÔ&K–,vOp\ļœįRúėlqo蒨YŖzēxųŠVm؜ĄõDFEËËĶļûŖîˈ�¤S|âTģ=ôæ´åīŋ˙ŅëŖFëĐĄÃōōōRįN§=ŸíÕŪŨ;$IÛwėŌ´īéÚĩë*[Ļ´Æ­Ë—/kāāĄ*P €>ŨĩMûŋ: Ą#F*:úļæĪų@!!ˇR\§l…Ę4đũũ÷ßÚē}§ĸŖŖU¯NmM<AŲ˛eŗšīOļlĶĸĨËtVÎfŗŠõ×+ũ^Vũzu$%Ü u֜yÚüÉV]šzUyķäQÃõ4|ØĢ6[Ėũlk!!!Ē\­–ܲfÕÁža†Á#čĶo€>˙âKëc'''å{â ÕŠSK¯ôëĢ' LŌŪ–ņ(=üĨe<Ū[įŨĻŊ=YíÚ>§Û11zwÆûÚļc§Ž_VųÕņíÕ¯ooëˇ*TŦRM]ģŧ [ˇ´uÛvEEEĢAũzšūödë=ˆlicËk¨VĢŽÚĩ}NÃ^œ–CĀÁ­Yˇ^ŖĮNĐĄŋĩ~Ŗa"[Î)}v#ei™ur÷Œ—X‹E+×mŌą§T˛XQuíÔ.ÉũTFŒ™Ŧ¨¨hëcˇŦY5m›iî;­ĩ>¨¯äBI <qZ —¯QŦÅbxw=b°žČ›ÛĻļ—¯^ͤéŗŌÔOzÍZ€ĮŨŊ™ŠC/�2ŠUĒŠg÷nĐ˙åGj�°ŋG ^ž˙é°ÖnÚj}Üš]UĢRéAĢĨKߊ•\đ∥‹$ ØGŪ…|˙Ã{û[3æ,LS?/�āŪLÅa/5���$IϤ_ŗķ°/ö⨥‹$E¤âōĄH.5€tGđ���‡RšRyž<­ŖĮOŠTņĸĒ\ą\’į§Oe§Ę’÷ŋįZ&ēŦXˇIÚĩÉđ{ģ$JM˜™r#�@Ēŧ�p?˙ô}ē´�<ūœÍfõėŌ!ŲįqÆKĄ' Ūˇ,qĻË+ŊģÉßĪĮ~ÁK”íÁKjfĮ��lCđ���‡q4đ¤ÖoŪύ¨hå͓[W¯]W–,ŽęĐļĩʔ,&É1gŧäȑ-Éãģ//ō÷ķąOQw¤jÆ Á �¤;‚���8ŒŨŸīĶ͐[’¤ķ˙\$EEGkĪįûŦÁ‹#˛X⎛7oéü?´ņ“vģ§ËŊR3ã%5m�ļ!x��€ŨY,™Íf Ø7ÍÛ°gĐņÖ¤évë;%ŠģĮ Á �¤7‚���ØŨ—žSũÚÕe6›Ķ´žÅbŅžßĨsUÉ÷e6›SõõÉö …ÂÂÃmna`%�9ŧ���ĀîļíŪĢmģ÷Úģ ›¤6$ĘČPčAOžÖŲķ˙ČģГmwöü? <y:ƒĒ€Ėƒā���H…Į)$’¤¨¨hŊ;ûC{—�™–“Ŋ ������ø¯"x�����0Á �����€A^������ Bđ�����`‚������ƒŧ����x$&“Ŋ+��ĮEđ��€tååé)ÅĮÛģ d”øxeĪ–ÍŪU�€Ã"x��@ēzŠkGĻ@d&N&õíŲÅŪU�€Ã"x��@ē*âSXCú÷’—§§L0˙i^ž>đe=U ŋŊK�‡eŠŋhPP|||ėP�����ĀãëŪL…/������!x�����0Á �����€A^������ Bđ�����`‚������ƒŧ������„ā�����Ā /������!x�����0Á �����€Aœ3ĒŖŋÎ_ˍŽ����@ž… Øģ�ȸā%OΌę �����Ā!pŠ�����€A^������ Bđ�����`‚������ƒŧ������„ā�����Ā /������!x�����0Á �����€A^������ Bđ�����`‚������ƒŧ������„ā�����Ā /������!x�����0Á �����€Aœí]Āà :Bûŋ:d™ŲlVÁT§vMŊܧ—ŧŧŧ2ŧŽÚõŠKįNęĶ̧a}Ŧ]ŋAkÖmĐåËWT°`õyЧZļhfX’dąX´fŨ}˛e›.\ŧ¨|ųžĐsmZĢk—Î2›Ív­ ����€Į‘C/’T芧4vô›ÖĮˇoßÖąĀãZžb•N˙yF æÎ’Éd˛c…Ōē éčącš4~lēloĶĮŸhÆûŗ4đ•—UĻtiũtđF'OOÕ­SÛ°æÎ˙P+W¯Õ+ũúĒLéR:üķ}0{ޜL&uëÚ%Íĩ����Y9|đâæîĻĘOWJ˛ŦFõjʓ'ˇÆM˜Ŧ_~ũMʗŗSu §ÛļâããĩdŲ uhßNŨģž(IĒTą‚Îü¤EK–%n<j 111Zˇá#uéÜQ=ē%ôûtĨŠ:uę´>Ũûšēuí’æÚ�����ČŦÛ{ŧ”-SZ’téŌeë˛ØØXÍ˙p‘Zˇm¯ĘÕjŠÕŗí´áŖMIÖ;üķõčÕW5ë4PĩZõÔ­goūųˆõųgjÖՊ•Ģ“Ŧ3nÂduęŌíuôėũ˛ļnߥí;vŠ\ĨĒ:~âDŠ}˜5G*W{āöΝ;¯ /Ē^Ũ¤!FÚ5õĮŅc ŗŠ†ÛˇoëŊ™ŗÔ¸Y+UĒZCM[´ŅėšķûĀ~ÍfŗÖ¯Ya ]åΟO!ˇnĨš6�����2ŗĮ6x9{öœ$Š@üÖeī͜­å+WĢwĪÚ´a^ėŌIĶgĖÔæ-Û$I4d˜üü|ĩrųb­^ąDE‹Ē˙Ā! ISŧ7]%KWĶ&´˙‹OõdÁ‚)öáįëĢZ5k<p{Aį^×SO=™dyĄ§ž’”~¤TCQMž:MŸlŨŽĄCé“Më5ā•—ĩnÃFŊ˙Áėöëää$ī…•={vë˛ØØXũđãO*_Žlšk���� 3søK$%™ĨŖcĮ5cæ,ųņSš˛e$IĄaaÚ¸écŊÔŖ›Zĩl.Iō.\XĮĩtŲ ĩ}ļĩ.]žŦ°đpĩhÖT~žž’¤‘#†ĒIã†ruuMSm^^^r2›åââĸœ9rčĖ_ĨØGëV-ÔēU‹n/ėÎŦO$ËŨŨŨ%Iááá)ÖpķfˆļīÜĨĄCŠi“F’¤Â… )(čŦV¯]¯ÁƒČÕÅ%Å×6kÎ<ũsáĸfL'Íĩ����™9|đrōä)UǚtvˆÉdRęÕ4æ­7Ŧ7Ö=yō”bbbTí™ĒIÚVĒTQ›ˇlSxx„ŧ –ˇwaŊųÖXĩo×V՞ŠĒŋééJͭیč#%'Nž”Åbą^Ž•¨d‰âŠŒŒÔšsįå_ÄīĄÛ˜9kŽÖ­ß¨÷Ū&,����€˙,‡^ŧ Ō”IŦ7|´Iß|ķĻL—ä˛˜°;ŗ-zõí¯ģŋã(.>^’tíú5y.ŦåKjųŠUÚŧeĢf͙§ųķk@˙—ĶíëÍfķ#õ‘Í+›¤„<wUvhh¨$ŲôõŲ‰3O<<ܓ,Oœ™‘ėēqqqš8yĒ>ŨûšæĖz_UĢTN×Ú�����ČL>xɒ5ĢJ—*i}üԓƒõՁ¯5s֍=Ęē<ņō—)ĮЍŋ˙}Û)?á^0šræÔĐ!ƒ4tČ ũyæŒVŽZĢQcÆÉ××GĨJ–xāWSGGG§Ēæ”úxoī„Ų%įΝWÁŦ˃Ξ““““|ŧ §Øŋ§§§$)<<iĀ’øØĶĶãžuŊ=m†žØˇ_ įĪM˛ßĶĢ6�����2“Įîæē9rd× Wúkķ–mIž)(  ¨\]\t=ø†|}}Ŧ˙˛įČŽœ9sĘÕÕU˙ũöí˙ĘēN??ŊõæH999éĪ?ĪHJ˜%rīˇķœ<u*ÅēâīĖŦąĨ‡)\¨ *”d’´o˙~=]ŠĸÜÜÜRŦ! hQ™Ífųå×$Ī˙úûīōōôTáBžthûŽ]Ú˛mģæÍūāžĐåQk���� 3rø/Ōöš6ÚŧeĢ&LžĒÖ¯‘Ģ‹‹ŧ<=Õļíŗš˙áBåĖ‘CĨK•ÔÅK—4mÆûĘ÷D^Íųā}]ē|YCGŧŽ!ƒ¨N­š2™LÚš{œœœŦ7é-Yŧ¸žÜ÷•ētî$ww­\ĩF7CBôDŪŧÉ֓=[68qRĮOœĐĨKWRėcĮÎŨúr˙~ŊwįĻĩ÷ęĶ̧ÆN˜¤'žxBåƖҁ¯ŋŅ×ß|§E æÚTCū|ųõl›VZ˛lš zJŊčđá#Ú°q“ēwí"gįû{TT”fĪ¯š5Ē)22RNō|šreåęâ’ĻÚ�����ČŦLņ‰Ķ$î$Ÿtí(ņ> Š1xč]¸xQ­[}ßsŋ˙ū‡ētI/÷éĨ~}{KJøöŖ-ŅöģtõÚ5åɝ[uëÔŌ€WúÉëÎå7;vî֊ÕktîÜy9›ÍōķķUī—zĒv­„øž=wNcĮOŌņ'•ÍËKĪ=ÛZˇoßÖwß˙¨ kWJ’j×o¤.;ŠO¯ž’¤¯ŋųVŖÆŒĶíÛ1š1mĒnܸųĐ>fΚŖĢÖčČÁī“}íë7nŌĘÕktųō.THũûõQŖõ“mo •+?­Ųsæi÷§Ÿ)8ø†ōįΧļĪļŅK=ē=đrĒã'N¨CįŽÉn˙‹Ow)OžÜiĒ ���°îAĀîÍT:x���€´"x`÷f*Ũ=^������/������!x�����0Á �����€A^������ Bđ�����`‚������ƒŧ������„ā�����Ā /������!x�����0Á �����€A^������ Bđ�����`‚������ƒŧ������„ā�����Ā ÎՑ——WFu�����ā˜ņ�����`‚������ƒŧ������„ā�����Ā /������!x�����0Á �����€A^������ Bđ�����`‚������ƒŧ������„ā�����Ā /������!x�����0Á �����€A^������ Bđ�����`‚������ƒŧ������„ā�����Ā /������!x�����0Á �����€A^������ Bđ�����`‚������ƒŧ������„ā�����Ā /������!x�����0Á �����€A^������ ōĀāÅŲŲY‹%Ŗk�����xlY,9;;'YöĀā%kÖŦ ΐĸ������ū ÂÂÂäææ–dŲƒ—\šr)$$D7oŪT\\\†�����đ8˛X,ēqã†BCC•3gÎ$Ī™âããã“[éúõ늎Žæ˛#�����€d˜ÍfeɒEšsį–ŲlNō\˛ÁË˙Ûģc���aū]OÄÂ×Ęā���€Ģ���@Dx���ˆ/����‘*ųņÔŅ_0����IENDŽB`‚�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/docs/screenshots/user-new-property.png�����������������������������������������������0000664�0000000�0000000�00000116237�14156463140�0023021�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR��$��ƒ���ëdÎæ���sBIT|dˆ���tEXtSoftware�gnome-screenshotīŋ>�� �IDATxœėŨwxeŪÆņī99éŊ÷’�!€ôЋ€t"EDėb_}u×UW×]W×ļöîÚA”^Eē(Ŋ(HH'!Ŋįœ÷ĀŅH—āPîĪuq]™gžyæ7ĐsŸ™gÆôˇŋũ͆ˆˆˆˆˆˆĖF """""W.1Œå× åå唔”PSScT="""""r™ąX,xxxāęęzâēã?Ä­ˇŪJãÆOÚYDDDDDä\”——ŗ˙~žųærrrđööގŪPZZJPPûÛß0™L†*"""""—WWWˆįŲgŸĨ°°°ŪÅ3@YYŖFR‘ Âl63bÄĘĘĘęˇÔÔÔФIC ‘+C“&MN˜¯n1™Ė˜L&œœœ *KDDDDDŽ...Øl6LĻ_öĢĮūŠˆˆˆˆˆaHDDDDDÄ0 $"""""b1Œ‰ˆˆˆˆˆFDDDDDD Ŗ@""""""†Q ÃXŒ.@DDDDDŽ?l܂ģG˛}YWHDDDDDÄ0 $"""""b1Œ‰ˆˆˆˆˆFDDDDDD Ŗ@""""""†Q Ã(ˆˆˆˆˆˆaHDDDDDŽP6›YŗfąnŨēSöŲ°aS§NĨļļö‚ÔpY’¸ø–Œ3Ūč2DDDDD. ķæÍcÛļm,Y˛ä¤ĄdãÆ,X°€={ö0cƌ RCƒ’˙û11Møbڗ§í×ĸu{ģôhČ]7ˆwß˙iiF—!""""ō‡hŲ˛%ŽŽŽ�'„’M›6ą`Á�Ėf3­[ˇž 5\VWHÎGNN./ũįUŌŌ]ŠˆˆˆˆČ"&&† &`ąX€_BÉæÍ›™?>6› ŗŲ˘1cHHH¸ 5˜§žųk_ą/);~úÉčDDDDDūp' %ķæÍŗ‡‘ŅŖG_°0É’É÷ÜOLĶrrrųëãOŌąswšĩhM߃ų|ę´ú¯Xšša#F߲ :u㯏?IQQŅIĮŪž}“īšŸv‰]hšp=z÷ã˙y”ÇĶí}nģķ.îŧû>�nš}21Mظiŗ}ũ‘#GxęégčŪĢ/MŽĸ}§ŽÜy÷}lßqö!ælę8Ûîŧ‹˜Ļ 'gMM 1M˜xĶ­öļĒĒ*>øīG 6‚ĢÚv¤e› z-ü÷#ŦVkŊíĪöØîđabš&p$/ožø–mXöŨōŗ>~šxÅÆÆ2a€ēÉî&“‰ŅŖG͞eË ēoËũ,9990ųžûčÜ)‘÷ŪyĢÕʛoŋÃSO?ƒŖÅ‘qc¯`ãĻÍÜq×=øķĀ}÷āīįĮú šãŽ{1›ëį̟~Ūɸ&áããÍ-7M"0 €ƒ‡1勊ŦYû=KÍĮ×ׇûîšof͙Ëũ÷ŪM‹„æ4‰‹ /?Ÿ‘׍§¨¨ˆ ׏ŖiĶ&dffņųĶwũD>ûäŋtJėxÚã;Û:Ęߞú3ŋ™ÅđĄC¸aÂxL&Ģ×|Īķ/ū‡ôô ūņ÷'Īų؜ŽŨ[ø¯gŸĮbąđĀ}÷Ų`5‹ˆˆˆˆąŠ‹‹ë}ymŗŲ())šāûŊ(ÉqQQ‘<úį‡íËoŋņ;wį­wßŗ’ˇß}ĢÕĘûīŧEëĢZ0nėu<õô3õŽj�lßąƒ&qą<ņØŖtî”ho æégžeŪüLēņÚļi͏ë7�ĐŽmzõüeÂũk¯ŋEVv6_ΘÆU­~I‡#ŽƀÁÃxîų™ķÍW§=Žŗ­ŖĄĖ_¸ˆļmZķÚ+/ŲÛ&ŒĮŋž{žŒĖLjkkqpp8§c;~ /˙čQ>ũčƒŸˆˆˆˆ\ēļoßΜ9sėˇi™L&jkkY´h�;wn°}EE„ŅŠcûōEH† \oŲĶĶ“Ž;đũēČÉÉ% Ÿõ6i#ĮwŨ ˇwMœp='\o_ŽŽŽÆjĩ ĀáôĶß.eŗŲX¸x1ņ͚Bnîû:G‹#íÚļeÍÚī)--ÃŨŨí”ãœoįĘŅb!=#ƒ#yyøûÛÛ˙öø_í?Ÿëą™L&�FĄ0""""rŲącŗgĪÆjĩÚįŒ899ņå—_^Prđpūūˆ‹‰.˛@Ō8:ú„ļāā �rÁjŗRYYITTÄ ũbcbN:æŦŲs™ūÕL’vīĄ¸¸¸Ū皚ĶŋÜ%//ŖG 8z´€NŨzž˛_Ff†ũ¯S9Ÿ:ÎÕCŪĪ?˙õoúôČ5ũúŌšs"=ēw#$8ØŪį÷[LãÆ Z̈ˆˆˆ᧟~˛‡“ÉĨQŖėsFƍĮôéĶ/H(؛瀏˜č$ĮžEˇŲl§ífĩZ1™M'´ģ¸ēœĐææZwåĄ¨¨7ˇēŸœOčįė|bÛK˙y•wß˙V-[đä%2"'''ö&§đØOžņpJJKhŪ<žŋ<üĐ)ûvœķ­ã\Ũ<éFš6iÂgSž`ņŌo™5g.�Ŋ{õ䙧Ÿ"<<ėw›§§Gƒ×+""""ÆHMMĨļļ“ÉÄȑ#iÕę—ģš5kÆØąc™>}:VĢ•#GŽœf¤sw<”Xj€cĀīãéQ÷aĩ  đ”}Š‹‹ŠŦŦÄ×įÄ Ņååå'íāëニK]č¨ŦĒ<Ą_iiYŊåĘĘJ>ūô„†„0uĘgõnŠúíŠSņpwˇ˙üëy%įĸ!ę8ęęę“ļwíŌ™Ž]:SUUņ›™=w.ß˚ÃěneÉĸy rl""""riģöÚkąZ­ÄÄĜôŇņņņŒ3†ÔÔT†Úāûߛēs Î8š6Ė€ ÍønÅĘS^%ųvYŨŖbۜä€SRöĐvüÍéAāččČĄC'ΚØŊgOŊåÜÜ#TVVŌĒUËæwŦß°ņ,Žđõõ!5ußI+œ—ŸÆ1ĸ�‹Ĩî)WÕ55õڝáąÁNNNtīօ—_ø77\?ž´ƒIÚ•Ô Į&""""—ļãˇiĩiĶæ”}6lØĢÁ\ëM„˙™;žV-[ĐúĒVlßžƒ×ß|›ÚÚús#6oŲĘsĪŋˆŲlææIOØū̝ŋގŧ˙ļīø‰ÆŖņ÷ķÃbąĐŽmŌ<á=S>ŸZo9  î ~ûž]II|3{P˙J‹ƒCŨD튊úW_xėŊ×kĪËĪgĐĐÜ>ųŠß]ĮАšZ?´}3kNŊå­ÛļĶš{¯ÚĖĮn“ŗ{„īų›ˆˆˆˆČų˛4íՍ`æÛp“ÉÄë¯žĖ„‰7ķÆ[ī0oÁBÚ´ž gggöíÛΆ›pppāšũƒøøf'l_UUÍí“īĻĪÕŊëíûáG�<pß=ö>“ī¸õ6rûw3æēQøúø°~ãFĘË+뎌¸¸¸Đįę^,_ąŠ'žzšÎ‰‰$§¤đŋΧōÚ^äŽģîeŊUĖŋ€~}ŽļŋSãŊ>äĐáÃtėОÖWĩâOÜˊ•ĢxįŊČÉÉĨSbG˛sr˜:m:Ü<éÆĶž“s­ãø<™ß5ōZž˜ö%˙zîy{ôΏēēđí˛ålŨļ­Ū•—V-[āãíÍc{Š›7“Đŧ9&üôĶNf~3‹íۑĐ<āŧMDDDDä|9Ü:ĸīĶ%%Å >ŧAôööfĖuŖprr"íāA6mÚÂÎ]IX­Vú_Ķ——žî„9 ‹/%99…Ī>ū´´C|>uK–.#Ā?€Įũ3#GüR[tt#bcbØškËWŦ⧟wŌ"!ūũ/žšų NNNL¸~�Ũēu%;+›+VąėģåÔÖÖōÜ3˙ [×.�Ŧûq=?ü¸1ŖG‘Đ<ž”ÔT6mŪ–­ÛH먁&qq¸šš1lčĘËËYŊf-sæÍgĮO?ĶĻõUŧüüs$ūęʧr.uœęņÁĄ!!D„‡ŗ~ÃFĻ|1•ĨK—ĘKĪ?˗3fâááÁ˜ëFa6›:d0Ŧ^ŗ–%KŋåĮ7P^YÁ͓&ō÷'Ÿ°ŋˆō\ŽmŲw+Øš+‰›&MÄĮ§á^â(""""W–wŪû�§_=¤Ęôä“OŲ233øđà +ęūfÁÂE|ŋz9Ą!!†Õ!"""""VË6íq÷đ´/ë w"""""b1Œ‰ˆˆˆˆˆæĸ$ožööíŨĨų#"""""W˜‹"ˆˆˆˆˆČ•IDDDDDD Ŗ@""""""†Q Ã(ˆˆˆˆˆˆaHDDDDDÄ0 $"""""b1ŒÅčDDDDDäĘŅĨc;BCÃėËēB""""""†Q Ã(ˆˆˆˆˆˆaHDDDDDÄ0 $"""""b1Œ‰ˆˆˆˆˆFDDDDDD cąŲŦØl6Ŗë‘+€ÍfÃfŗÚ—u…DDDDDD c9ūCN^‘uˆˆˆˆˆČČH‚ü}ŒŦCDDDDDŽ@ēeKDDDDD Ŗ@""""""†Q Ã(ˆˆˆˆˆˆaHDDDDDÄ0 $"""""b1Œ‰ˆˆˆˆˆÆRXXHII‰ŅuˆˆˆˆˆČ ¤¤„ÂÂBû˛ÅßߟĘĘJK‘+…ˇˇ7ūūūöeŨ˛%"""""†Q Ã(ˆˆˆˆˆˆaHDDDDDÄ0 $"""""bKCVQYEZz•UÕÔÖZrč˒Ÿˇ;ī<…’Ō2l6›Ņ刨™L&<ŨŨ¸ã–‰DG†]Žˆˆˆ\Æ,TTVą'õ žžîxyzā`ÖŗĶŲ‘”Ė'Ÿ}Ž šøØl6ŠŠKxåÍøËƒwbtI"""r™j°Ô–ž…§§;nŽ #gáÛeß)ŒČÅÍdÂfŗōū'Ÿ]‰ˆˆˆ\Æ,9TTTáæęŌPÃ]öĘĘƌ.AäĖL& ‹ŠŽBDDD.c HŦ6f“ŠĄ†‘‹„æ7‰ˆˆČ…¤{ĢDDDDDÄ0 $"""""b1Œ‰ˆˆˆˆˆFDDDDDD Ŗ@""""""†Q Ã(ˆˆˆˆˆˆa,F�°{Ī^-Yʁ´ƒ—āęęBĶ&M2x q1.īĸrĮM¸ĒEŧ}šēēšŧŖ$íIfųęuũ!u<˙ô_Yąæ–|ˇęŲßšøŖj3ōŒ1„&ąyî?oũáûiH–‚ĸŠKË + iĪ^^~åu:uėĀˇŪŒ‡ģųGķY°h /Ŋü*O>ņ(áá†Õw1:’—ĪÔ¯fāääDDX]:u sĮvŧ˙ɤîO;§ņztM¤QD8ŸĪ˜u!Ę9%ŗ—žîn†°|ųJÂÂBšķö[hŲ"čč(ÚĩmÃ#˙÷�ėMN1Ŧļ‹UeeÉûŧī�;wīeÉōÕ<÷ŸˇČČĖæöIãqvv:§ņĸÂÃ.PĨ"""""§gø-[Õ55ÔÖԞĐîęâĘŗ˙|Ē~ßęjžž5— 7QXT„ˇ]:wbäĩÃppp�āÎ{˙ÄČáC4 ŋ}ģ?›ÂÁƒ‡xúÉĮ¸˙ÁG6d?īJ")i7¯ŋú"ŽŽNĖ™;ī×­§´ŧœF‘ŒŊnMâb¨­­eŦ_ŋ‰ŧü|üü|pM_úôîuĄNÍ9ŠĒĒbÚ×søÛ#ĐŠ}[V¯[€‡ģ;#‡ Y“XÜÜ\)((dÕ÷ëYõũ�üéŽ[‰‹‰ S‡ļ<˙Ú;Ÿv›ãĖfF DbûÖ8::˛{o _|5›˛˛ōŗÚ7@lãF ؏°Đ`Ė&3é™YĖ]ô­ũ*Ųlf`ß^´kĶ ?_ YžfkØxÚķŅĩ9880øšĢIlß7Wgd1{Áö§:é>ãbĸš÷Ž›øjöÖ­ßtVĩ?÷ÔŖ,ųnžžŪ´oŨ ggRö`ęĖ9—�āíåɄëFĐ$Ž1åŦũņôĮ>øšĢtÍÕ']ˇčÛ,üvÅiˇų#HÚ^ՊOĻ|Á[īžĪ ũiÜ(ŗŲtŌžSž˜Ææ­Û˜tÃõ4ŽŽ&uß~>›2•Ēęj&ŒsÖûtp°°rõZÚ´nÅđĄƒpvrfÚô¯Ø°q7LOP` ˖¯āåWßā™§Ÿ$(0€é3ŋfÕĒĩLš8¸Øv&%1õË888ĐĢG÷†:į%;į9šyÄÅDÛÉÄą#  ā“/fPT\BlãF\?z8G Øąs7īú÷ßy šGōøjöĘĘ˙|ķ §Ũæ¸ÎÛącgīü÷øûų1áēk?j>ãŦöíäčČ]ˇLdĶļLûz&Lôė։{nŸÄßūõååŒ2€n;0ũ›yė;pø&ąŒžv0ĩ5ĩü°qË)ĪÅųÖ0rč@ÚˇiɌY 8’—O¯n¸÷ö›ø÷+o‘w´ Ūūü¸cŌõ|ˇr-ëÖo8ĢÚkkké×ģ; –|Įßž‚—§Ü?™Aũz3cÖ|�n?š �Ūûh …ÅÅôėډ6­ZPZvō[-Žß†…šHzõęAIY)ķæ/fĶæ­¸ē¸Ō¤I,íÚ´ĻKįDœ(.)aíē?f4;Hzf&K—-gĖč‘8ZÎîpL&prrdėuŖ�(¯(g՚ī7f4:v�āæIŠŦŦ$''Ow–¯XŐÁéÖĩ3�ÁÁAH;ĂEK.š@p´ �/OûōĖš ąY­öĐšGōčŲ5‘øĻqėØš›ŠŠJŦV+555ö¸gÚæ¸ĸâfÎYĀÁÃD„…СW7ŠŽŽ>ã8žžŪ¸¸8ŗqËv˛sŽÔí{ÎBļl˙™ššZ\œéŅ5‘o—¯fÃæm�ŦÍË'2"ŒkŽîyÚ@ržĩ9;;ŅĩS{fĪ_ÂÖ?0íëš8;;ā_/¸ššr÷­7ōsŌæ/ųāœjĪÎÉåĮM[((,bמdĸ"ęæMy{yŌ,.†ŗæŗ7u?�_Í^@|“¸Ķū=øm(Q‘‹•á`Č ôës5ģ’vŗsW;wíæĶ)_0wūB~č~ÂÃÂ8tč0VĢ•Øß<u+&ēU••dggŸĶä÷¸ØûĪééTWWŨČŪæhąpßŨ“ØŊ'™ššZZ&$ÔŖyŗĻŦ^ŗ–ŠŠ \\\~ĪĄ78ŗŲŒÕjĩ/WVUŅ˙ę4‰mŒ‡ģ;f“ 77WrärŒŗŨæˇ“į÷§ÂÁÁ@?2˛˛Ī8NNnŲšG¸yÂÖü°¤Ŋ)NĪ$eß�ĸ7Ââā@ŌŪúķˆ’S÷Ķ5ą=NNNTUUôΡļĐā`-ŌĨÛĮ¨­­åŖ)_Ö×âāĀ“Žįha_{Đ�@xXČYמž™]¯OYy9nŽŽ�„včpŊ>i‡zŌc?î×DaDDDD.VE pvvĻm›Ö´mĶ€]ģ÷đö;ī3ũĢoøŋ?ŨGEE�.Žõ?ø••į´?—cø�J=eėT“Áˏíû…—_ÁÄ/ˇ“Ym6� ‹.š@ĀžäT .œÜ{û$ĖffÎYHvîj­V&ß<á”ÛŸË6ĮĪËqĮ?`;99žÕ86›×Ūų/ũz÷ kb†熪…Ė[ŧŒ[ļãâRwuėģn…cįĀdĒûxyzp$/˙¤ĮqžĩšššÔÛîTzwŗ™Ų9õÂāšÔ^]]}¸ĮēŲ¯VU×Ô[_YyúēŽS‘‹á¤° gį>Đ'Ä7Ŗ}ûļl?vģĖņ�QQ^˙ƒfyyŨ$eˇēõ'›}Ru†ožžž'û¸ãßVßyÛ­DDœøD*??ßĶŽ˙G‰‰ŽÂÛËĶū­|tTáĄ!ŧöîGõŽx¸ģ“—ô¤cœË6ÎNõÜņ@WYYuÖ㔔–1{Áf/XBHP }zucŌøŅdeįRq,dūoÚL2~s   đ”įâ|k+)Š ŠĮƒÅŠdåäđå7ķxđŽÛ¸vđ5|=wĀyÕūkUÕuw]S‡ĢëÅ€EDDDΗĄoj/,,âĄŋ<ÆÂÅKOXgŗŲČĖĘÂĮÛ €¨ˆĖf3É)ŠõúĨ¤îĮÕՅ   îé\ĨĮž¤tÜĄôŒĶÖŒŗ“ģ÷&ÛÛŦV˙~ņež_÷#Q‘á8Z,j˙ãá§'ŽŽŽŋëø’ĢĢ ãF #īh[wė°ĪŠ)ũÕ{fĸŖ"ņ÷ķåˇŅíø7÷į˛MLtTŊå¨Čjjj8’—VãøûúĐ*ᗗ<fåäōå×sąZ­„†‘ž‘EMM îdįą˙)-+Ŗ¸´”šÚŸÎÖPĩåäĄĒēÚū˛ãįčOwŨJbû6öļŸ“ö’ž‘ÅWsĐĢ[gâ›Ö=•í|j˙ĩãsk"Â~š=Ël6Ķ$V/ ‘˃ĄWHŧŊŊØŋsį/¤°¨ˆ6­[ãáîFAa!ß˙ÉÉŠÜ=ųv�<<ÜéŅŊķ-&(8ˆ¨ČöėIfųŠ• xũąŋŅ"Ųēu;Žé‹‹‹ ‹—.Ŗ¤¤_īSÖáęęJî]™ŋ`~ž>„…†˛rõ8Čm7ĮâęęJ¯žŨ™=wžî4nܘŧü|Ļ~9?_zāž?ä|įėėD“c”,„‡„ĐĢ{gœxûÃ˙Q{ėÃnzfÕ55ôęŪ™Eߎ ,4˜áƒŽ!io Áx¸ģSRZJYy9ᡄ‡…_PxVÛ�øûų2 o/6mŨA€ŋŨ;wdëO;ŠŽŠ9Ģ}ûúúpû¤ņĖY¸”Ÿ“ö`ŗAĮv­ąŲlėO;DEe%߯ßĐū}(--#íāaü|}=|0…Eŧ÷Éį§<Gį[[Ii)?lØÂ€>Ŋ((,"+;—î;Î_øÉ ›ˇŅ˛y3&ŽÅs¯ŧEYYųīŽũ׎˛?íũûô$7/â’Rzwīb˙‹ˆˆˆ\ęz÷îũtqq1Æ ;¯˛rķņōp?įíZ$4'0 €ģvąúûu,_ąŠ]IIxyy1iâõ´;6§ e‹JKJY°p)ķ.&íāAú_ĶáCÛŋáoÍOģv2ãëŲŦ^Ŋ–¨Č"ÂÃČÉÉåę^=X˛tQQ‘$4˙åÛųøfÍ(--aɡËYĩj �ˇŨrEŲëŦĒĒbņ’oY°h ;wîĻeB<“&Nø]WHV˙Ã9oĐžM+7ФS‡ļtęЖmŽ"$8ˆ]{’ųlęL˛srí}ĢĒĢÉ=’O÷.Ч'ūL›9—Ėėēuę@ëVÍYķÃĘĘĘéÔĄ Ũ;u`÷ŪT’ö¤œq›}zątųj|ŧŊ;bÛˇf÷ŪTĻ3šššŗÚ÷œ…KÉË?JÎ‰ôīÛ‹Ž‰íqqvfÆŦų8X7‰{wr*NŽŽôëŨū}zß4ŽŨÉŠĖ˜5˙”Wĸļ5?l`oę~ÜŨŨčĶŖ+ŨģÔ=Ųíķŗ8œ‘@ŋŪŨ9pđ°ũļ¯=)ûčŅ%‘ȰPļėøųŦjīĶŗ+éYö'hÄ7#$(ĐūN”=)ûˆ‰ŽâšŪ=h{UKöĻė##3›ā ÖŦÛđģūĢÁ§x¯‰ˆˆˆČšš?ž}Ę€é駟ļeddđūûīŸ×Ā[w&xžõ]1žyáŖK9kožøOŖK‘ËÄäɓ ûe^ļĄsHDDDDDäĘĻ@""""""†Q Ã(ˆˆˆˆˆˆaHDDDDDÄ0 $"""""b1Œ‰ˆˆˆˆˆĻÁ‰ÉdÂjŗ5Ôp"r‘0™ŒŽ@DDD.g H\]œ())k¨á.{nŽŽ �';› o//ŖĢ‘ËXƒ’Fá!””•S\ZF­ÕÚPÃ^ļúöšĖúęY.rf“oht"""rŗ4Ô@.ÎNÄĮF‘–žEnŪQjkJN'0Ÿ‡ī›ĖŸ|AqI‰Ņ刜ĀĶÃÉˇŪHDhˆŅĨˆˆˆČeŦÁ ԅ’f1Q 9äeīš§ūbt """""†ŅSļDDDDDÄ0 $"""""b1Œ‰ˆˆˆˆˆFDDDDDD Ŗ@""""""†Q Ã(ˆˆˆˆˆˆaHDDDDDÄ0 $"""""b1Œ‰ˆˆˆˆˆFDDDDDD Ŗ@""""""†Q ÃXr°ŠĘ*ŌŌŗ¨ŦĒĻļÖڐC‹ˆˆˆˆČŦm‹&| H**ĢØ“zOOwŧ<=p0ëâ‹QŌŗr  4ē š„Ĩgåū!ûi°@’–ž…§§;nŽ 5¤ˆˆˆˆˆ\æė2FEEnŽ. 5œˆˆˆˆˆ\,Xm6Ė&SC '"""""W�MôÃ(ˆˆˆˆˆˆaHDDDDDÄ0 $"""""b1Œ‰ˆˆˆˆˆFDDDDDD Ŗ@""""""†1<<ûüKŧđŸ×Nē.#3“›oŋ‹›6˙ÁUÉqSž˜ÆOũĶč2DDDDä2ex ‘+—‰ˆˆˆˆˆÆbtįjĪŪžž5›C‡cĩڈŠŒ`ôČÄ7k@mm-s,dũúMäåįãįįˀkúŌ§^Gœ�� �IDATw/û÷?øÆ âį]I$%íæõW_äŅ'ūΰÁƒČĪĪgũ†MTTTŌŦiˇLšˆˇ7�…EELŸņ5ģ’öPZVŠŸŸ/}¯îM˙~}~û˙ū˰AIĪĖdķ–­X­VzvũųäŗĪINNÁŲŲ…‘#†ŌŖ[×ŗŽųp´ €O>ûœ¤Ũ{puuåęŪ=NčsĻZ˙õī—pqq摇¨ˇŨ+¯ŋEiYO>ö—?äXDDDDäŌpI’ŠŠJ^{ãm:%vāĻI7€ÍÆwËWōęëoōĘKĪãîîÆô™_ŗjÕZ&Mœ@\l ;“’˜úå čÕŖ;�VŽ^K›Ö­>tÎNÎXĖ,Zŧ”‘#†ķō ĪRXTČ?ūõsæ/`ŌÄ �|üé˙ČĖĘæŽ;oÃĮۋŊÉ)|:åsüũh×ļ �ŗ‹—.ãÆ‰¸ųÆXšz ŸM™JŌžŊÜ8aą1wķ͜šLųbíÚ´9ëš˙|ô)ŲY9<ôĀŊxûxŗ|ųJ6m؊‡ģ‡ŊĪ™jí”Ø/gˤŦŧ 7W7�ĘĘËØ•”ĸ1×ũaĮ"""""—†KꖭŧŖų”W”ĶĩK"ᥥ„‡…qÃõãxčO÷aą8P^^ÎōĢ8đēuíLpp}z÷ĸk—.,X´Ä>ŽÉNNŽŒŊnqąą888�BĪî]qppĀĪ׏֭Z°˙@š}ģ ãÆōČC"žYBB‚éŲŖ‘‘‘üŧsWŊ:Ŗ"#hÛē&“‰Î‰ˆ‹iL\lŦŊ­ĒǚŦŦŦŗŽųBË?z”¤¤Ũ <€„æņ„‡†2qÂx\]\í}ÎĻ֎íÛbĩZŲžã'ûv[ˇîĀjĩ’ØĄũv<""""ri¸¤Ž„Ėû~ĖÕŊ{Ņ2!F"‰oÖ€Ũ{’ŠŠŠĨeBBŊíš7kĘę5kЍ¨ĀÅÅ€¸Ø˜ƏŒŒ¨ˇėææFiY™}ŲÅŲ™ų‹—´{ÅÅ%Øl6JKK ǎ]HHˆũgW×ēôĄĄĄŋjĢĢĄŦŧœ´ƒ‡Īēæ )33 €˜ÆŅö6“ÉDãÆ8xđ0ĀYÕęããCŗĻMØ˛e;]:u`ãæ-$4oގˇ×?š´HĖfX­']W{ŦŨÁb9Ö×Ėã>ÂÂÅKYĩf-3ŋ™ŋŸ/ŖF^Kˇ.)¯¨�ā…—_Á„É>ŽÕf °°ČūáŪÅՕßrtt<e55ĩŧôÚØj­L¸~,ĄĄÁ8˜xũíwOčkq<ņ´Z,'ļŲlļsĒųBĒ8V‡ãojwqūeßg[kbĮö|ųÕ7TUUSk­aįŽ$nēq…>šH<==9|8ã¤ëŽäĀĮÛÛŪæååÉøąŖ?v4é,YēŒ?ú”°ĐP܎…Œ;oģ•ˆˆ°ÆķķķũŨuîÛŋÃ‡Ķyė/ĐŦiœŊŊ¨¸˜Ā€€ß=î…Ŧų\8;;P^^Q¯ŊŦŧÜūķŲÖÚĄ];>Ÿú%;wíĸ˛˛ €öĮæØˆˆˆˆˆüšásHZļhAVvö ķ0ŦVķ.ÆĪחčFQ�ääaËļíö>áaaLšxfŗ‰ôŒ ĸ"Ãq´X(*.&,4ÔūĮÃŨOOĪĶ^9“ęę�<<Üėm)ŠŠ9’‡ Ûī÷BÖ|.BB‚8xč°Ŋ­ĻĻ–Ũ{öœs­^^ž4gێŸŲēm;­¯jiŋuMDDDDä×,E%—–šįŌŊkV¯ųžˇŪų€úEQq1ĢV¯å@Z÷ßsfs]nĘĪĪį­wŪcėčQ´nŨ &~Xŋ“ÉL\L ŽŽŽôęŲŲsįáéáNãÆÉËĪgę—3đķõáĄîûŨuFFDāččȡËV0bø§gđÕ7ŗiŲ"ĖŦl ‹Šđö:÷9˛æsāīOll .&8(OOOžũn9–cūĪĩÖĎí™7!eååÜzͤ?äDDDDäŌcø-[‹yøAæ/\ČĻÍÛXŧø[,ÄÅÆōØ_ĄI\ŦŊo|ŗĻÜvËM,^ēŒYsæa6;Ęũ÷LļÃũ¸1¸šš1cæ7áíåMÛ6­¸nԈķĒĶË˓ÛnžÄĖYŗY÷ãD7jġL"˙hīž˙/žüĪūķŠß5ö…Ēų\ŨuĮm|üŲ^ķ\Ü\éĶĢ']ētf˖mį\k‡víøßįĶprr¤õU-˙Đã‘K‡é駟ļeddđūûīŸ×@[w&Ø@eÉųHĪĘÕīBDDDDÎKzV.m[4iđq'OžLXØ/ķ‘ ŸC""""""W.1Œ‰ˆˆˆˆˆFDDDDDD Ŗ@""""""†Q Ã(ˆˆˆˆˆˆaHDDDDDÄ0 HL&V›­Ą†‘+@ƒW'JJĘj8š4X iBIY9ÅĨeÔZ­ 5Ŧˆˆˆˆˆ\Æ, 5‹ŗņąQ¤Ĩg‘›w”ÚZ…#Ĩgå]‚ˆˆˆˆČ5X ēPŌ,&Ē!‡‘˘ž˛%"""""†Q Ã(ˆˆˆˆˆˆaHDDDDDÄ0 $"""""b1Œ‰ˆˆˆˆˆÆŧüŖˇ™6}‘ŅuˆˆˆˆˆČČlq�¨0ēšģeĢÚØ*DDDDD䊤9$"""""b˜cÄÁØ*DDDDD䊤+$"""""b1Œ‰ˆˆˆˆˆÆŌƒUTU“v(“Ęęjjk­ 9´œ…Ms83‡ōŠJĒĒkŒ.Gä˛áähÁÕřˆĐ œô?›"""W<KĪ›ī%.#ãŧĒŦĒfOJžžîxyyā`Öŗ?Rzöv§¤ä¯M" ŦĒē†ŧŖ…ėNI#>Ž‘ū}‰ˆˆ4 K gáé鎇›ĢˆŦVB‚ü ō÷҇%‘æäh!4ȟ@gæ]ŽˆˆČeĨÁ’CEE%nŽ. 5œœ3ūž^F!rYķ÷õĻŧĸŌč2DDD.+ HŦ6f“ŠĄ†“seBWĻD.0'G‹æg‰ˆˆ40}‚Ã(ˆˆˆˆˆˆaHDDDDDÄ0 $"""""b1Œ‰ˆˆˆˆˆFDDDDDD Ŗ@""""""†ą]€ëƒO§ōĶŽŨ§\ßēUˇß8ūŦHNeÆėų$§≇ī3瑪@"øû2~Ôđ“Žķôô8§ąV¯[OÚĄtn7Ē!J‘Ëœ‰āėäLŗ&ą 2ÖĄÃ 2Žˆˆˆˆ\HäŦ=öСG Øŧí'*+̈iĄëFāåéÁëī}LĘž�lØŧGŧ›°`/[Éæí?‘´_/ŽîŅ•]íãūõéįСģ÷ϰ7eĪũũQ\]\ęíûĪO>K˙>=ÉÎ=ÂΤ=TVVß4Ž cFāáî@qI)ŗæ/foĘ>JËĘņõņĸg×ÎôîŪŲ>Nęū4æ-^FzfVĢˆ°† ėG\Lô×?ņĖ‹ôč’ČĀ~Ŋ(*.á‰g^¤íU-šuâXû>æEúôčJŋŪŨŠŠŠaūâīØŧũ'ŠŠKđöō¤cģÖ éßŗŲ|Ęã¯ĒĒfęWŗŲ›ēWgēwîØĐŋN‘‹‚‰`ŗŲ¨ŽŠ9é:‹ƒ&“ �3ËVŽa耾üķņ‡)*.áĨ7ŪcҞŒ9Œ;ožĀ›īJ`€?cF ÁÍՅYķ—đũúMŒ5ŒÆĸؓœĘĖš ą88Đ%ą}Ũ>,|ŋ~#­âد7ÎNN'Ôav0ŗlåZF Č cFs$ˇ>ø”¯į.äĻë¯ā‹ŗČÎÍåæ cđōô$õ@ĶfÎÁĪכĢZ4§ĒNJ÷>ūœömZ1~ôp°ŲXĩn=ī|4…ũí,§]ß,.–}ÚkJŲˇo/R÷°ˇåäæQ\\B|Ķē+NĶgÍgĮĪIŒ5”¨ˆpö<ÄôoæQ]]ͨaƒNyü˙ũߗääáî['âååÉęuëŲöĶ.ÜŨÜÎ˙."""rQ 2˛˛ųŋĮ˙yŌu~`2Qáö吠@:wl€ˇ ņM9x¨î6-WĖf3‹înTTT˛æ‡ ôŋē‰íÛ�āĮÁô –ŽXc$�NŽN\;¸˙iëŒĨS‡ļ�ĐŊK"‹—­äúŅU8991zø`Ėfū~ž�úŗzŨz’öĻpU‹æäRQYIĮv­ `ĖĩChßē‹…üŖ§]ßŦI 3į,Äfŗa2™HN=@‡ļWązŨŽäåāīGĘūx¸ģBiY6ocĐ´kŨ €�?˛ŗsY࿆îÅÁá„ã/(,boĘ>ƎJ͏{ģ÷ĻžÕīSDDDäRb)(*Ą¸´Ėč:Ä@~L?ú¤ëŽ0?.,4¤Ū˛›Ģ eåå'ŨöpF&ĩĩĩÄ7Ģ×Ū$6š6lϞ˛ gįēĢ!EžąÎČđĐzËĄÁAÔÔÔPPXLP ?ÎÎN|ģb5{SöSRZŠÍfŖ´Ŧœ ��‚ü ôįŗi3éŅ%‘øĻąD„…Úo×:ĶúfMbЍŦ$#3›đ°’÷ígäЁ<”NĘū´ē@˛ī�͚Äb2™HĪČÂjĩŌ¸QDŊēŖ"ÃŠĒŽ&÷HĄÁA'vN.�ĸ~ ‚&“‰čČedžņ<‰ˆˆˆ\Jt…Dprt":ęˁ ŽīÉūĘØNÚˇĸ˛€7Ū˙Ķ¯Ú­ļēūEÅ%:ûāęâ|Æ};;×īãėä@yE9ĩĩĩŧũágX­VŽģv0ÁA˜Íf>øô {ŗŲĖC÷Üβkų~ũ&æ.ú_o†čKbû6g\īãíEP ?ŠŌđōō$'7˜č(¤"u;´%eß_suŊãwųmŨĮ–+­˙íņWTVāččø›íNŧ•MDDDäRgqsqÂÅÉņĖ=EÎŅņ‰é7]?𰐐ÖûúxĶx••õ–+Ž-ģšērāāa2˛˛yđîۈmÜČŪ§¤¤Ô~ €‡ģ;#†`ÄĐdeįđŨęuL™ū !ÁADE„qũņy$žãęâBLãF|5{G 9ZPhb™Ëąã¯¨<yŨ.ŋ™¸œĶņ U^Q¯Ŋė7Ë""""—ŊŠ].˜đĐ,—”`˙ãî‡Ëš] K>ö¯ãŌ§ãä舏75Į&åģģšÚ×īO;DŪŅŽ]!/˙(;vūōȐ⠯†Éd"3+ûŒëĄîļ­}’ŧī�qŖēÛ­ŽäåŗeûĪāëã @Dhfŗ™Ô_M„¯Ģë ..Îö[É~+80�€ôŒ,{[mm-ÉûöŸå™štč–-Ąĸ˛’]{’OēÎd2Ņü7s@NĮÍͅÃé™ÎČÄ×Į›n;°`ér<ÜŨhAūŅžžģo/îēuâ9ÕYXTÄÂĨËIlߖėœ\Öü°ömZáhą‚ÅbaåÚtÍÕdde3wҎÄ7%'÷Å%Ĩ-(äŋ˙›ÆĩCú͞y3L˜Ø¸u;&“‰Æĸθ ilc ‹øiįnûS˛\œ  aÕ÷?rU‹ø_ W:wlĮŌåĢ ô÷'2<”äÔũŦ^ˇ~ŊēŲûû[~ž>DGE˛tÅjüđôpgåÚíāEDDD.' $B^ūQŪũhĘIיL&Ūxág=Vīn]øė˙ŧúöš}ŌõŒ6Wf/XJaQ1^ž´jĪđ×œs]ÛSV^ÁKožGuu ­š1fÄ îVŦ‰cG2wҎlØŧ¨Čpn7Š‚Â">ų|oŧ˙ O<|ĮŽdųęu,X˛ŗ™ā îŧiAuÚOˇĀÕՅČđ0N'îWˇ†Å6ŽbÕ÷ëOxÁä˜CpqvbÆŦy—”âëíÅĀžŊ¸æę§=֛oÃÔ¯fķÁ'_āââB÷.Ilךm?ī:įķ&"""r13=ūøãļĖĖL>ūøãķhëÎdÂCĪÜQ.ˆôŦ\ÚļhbtĖŖO˙›Ģ{te`ß^F—"W¸­;“/ëk"""Úäɓ ŗ/k‰ˆˆˆˆˆFDDDDDD Ŗ9$rIxáéĮŒ.ADDDD.�]!Ã(ˆˆˆˆˆˆaHDDDDDÄ0†Ī!yũ­wŲēm{Ŋ6O""švØPâ›]9×œōÅ4vīIæŲ>et)"""""à @PP ˇÜtŖ}š  €•ĢVķÂ˯đäãĶ8Ú°ÚDDDDDäš(nŲrvvĻyŗĻö?]:%ōđƒÂĮۛo—-7ēŧK‚ÍĩVĢŅeˆ\ÖĒĒkprŧ(žĮšl\´˙gurr$22‚ėœ\{[aQĶg|ÍŽ¤=”–•âįįKßĢ{Ķŋ_{Ÿ={SøzÖl>ŒÕj#*2‚Ņ#GØoũ:Ũú?=ü(}z÷äÚaCęöWPȟy”ÄŽíšgōö}üéáŋ0āš~ ؟ęęjžž5— 7QXT„ˇ]:wbäĩÃppp�āūaؐAüŧ+‰¤¤Ũŧūę‹TVVņÉgŸ“´{ŽŽŽ\ŨģĮy/6rŽ%4Č˙ŧÆ‘SË;ZˆĢ‹ŗŅeˆˆˆ\V.Ú@›{„đđPûōĮŸūĖŦlîēķ6|ŧŊ؛œÂ§S>'ĀߏvmÛPQQÉkoŧM§ÄÜ4é°ŲønųJ^}ũM^yéyNģ>Ąy3’SRíûÛŊw/~ž~ėŲ›boËĘĘϰ°ˆ ́ēy›ˇncŌ ×Ķ8:šÔ}ûųlĘTĒĒĢ™0n �VŽ^K›Ö­>tÎNÎŧųÎdgåđĐ÷âíãÍōå+Ų´e+îŋīd™Íää`2™đķņŌˇ¸" ¨Ēē†ü‚"rō h×ČčrDDD.+ͧÖÚÚZûĪ…EE|÷ŨJ2ŗ˛¸áúąöö ãÆb2› � $$˜īVŽâįģh×ļ yGķ)¯(§k—DÂCë‚Ė ׏#ąc,ŽäŸ~}‹„æ|1uVĢ ŗŲÄî=Étéԑe+V’““KPP ģ÷&ãåéATdÅ%%Ŧ]÷#ãĮŒĻSbG n>Lzf&K—-gĖč‘8Z,˜LuW|Æ^7 €üŖGIJÚ͍7\OBķx�&NĪÎ]ģ˙ ´ŲhŪ$šÃ™9ä-¤Ēēæ÷%"õ89ZpuqĻy\#…}‘vQüŸõĐĄÃÜ6ųŪzmînnÜzķ$ZļH°ˇš8;3ņ’vīĄ¸¸›ÍFii)!AA�„Ėû~ĖÕŊ{Ņ2!F"‰oÖôŦÖ'4oFyE9‡Ķ͉ŠŒ`ĪŪdƍÅžؓœLPP {÷&ĶŧysL&‡Æjĩ͏^í1Ņ¨ĒŦ$;;›ˆđp�âbcėë33ŗęú5Žļˇ™L&7nÄÁƒ‡椊ˆˆˆˆ\,Á–ŗ +"$8˜ÉwÜj_vrr$8(‹ÅÁŪVSSËK¯Ŋ­ÖʄëĮŒƒÉ×ß~×ŪĮl6ķøŖ°pņRV­YËĖofãīį˨‘×Ō­Kį3Ž÷ķõ#$8˜ŊÉ)x{{‘•E“&ą¤îÛOrr =ēueor2Ç  ĸĸ�W—zĮãâRˇ\YQųK›ĢĢũįãÛ9ūæ›Vįú㜓‰Ũ)i„ų¤oqEPUu yG Ų’FŧŽ’ˆˆˆ4( ø}U{C‹ptr¤qôéīËŪˇ‡§ķØ_ĄYĶ8{{Qq1öe//OƏÍøąŖIĪČ`ÉŌe|øŅ§„…†Ō8ēŅןGâåéIDD8nŽn4mĮįS§“—ŸĪ‘ŧ|Z$ÔŨfu<dT”WÔĢĩŧŧŧnŊ›+'ãėė|Ŧ_ũíʎm÷ģX­„„äīķûĮ‘“rr´Øq83‡˜¨0ƒ+š|˜Á ŋČ(Ŗë8Ŗęcs"<<Üėm)ŠŠ9’‡ �9šGØōĢ—,†‡…1i⠘Í&Ō32θ EBsRRRØŗw/͚Ô=™+6ļ19š9lظ‰Đüũü�ˆŠˆĀl6כ_W×~\]]뎒ũVHH0�ũr{VMM-ģ÷ėų}'�ūž^įąŊˆœ‰ŋ¯7åŋēō)"""įĪ\wËÖ,Ŗë8ŖČˆųvŲ øyį.ĻLNË dfeSXTD~~>oŊķ‹—|KfVYYŲĖ[°“ÉL\LĖ×ÄĮ7ãhA[ˇũDĶ&uWb\]\‰Œˆāģå+íWG�<<ÜéŅŊķ-fËļíÉËãûu?˛|ÅJú_Ķ×ūØßß đ÷'66† ķķÎ]¤<ħS>ĮrŠūgÅæ‹âĩ2"—-'G‹!""ŌĀ.™ĄŊŧ<šíæI˜5›u?ūHtŖFÜqË$ōđîûņâ˯ņė?Ÿâļ[nbņŌe˚3ŗŲ°đPîŋg2!!Á„„Ÿv=ÔMĻoÅūi4=v… I\,˖¯´?î÷¸‰ÆáęâĖ˙ĻLŖ¨¸??_†ĖĐAN{<wŨq6…×ß|7WúôęI—.Ų˛e[ß<‘‹”éņĮoļ-øīlļe=¯ļîL&<$°Ę’s•ž•KÛMÎÜQDÎË֝Éúˇ&""r&OžLXØ/ķ1uˆˆˆˆˆFDDDDDD Ŗ@""""""†Q Ã(ˆˆˆˆˆˆaHDDDDDÄ0ŖŨ‘F×!˛Ųllܲu6“ž™Em­?oÚ´J OĪn¸šš6ø>ķđņįĶIĪČbøāū,ūn%W÷čĘĀžŊ|_RߌŲķIN=Āßgt)""""—΋å™ōå7lÜēv­[ŌŖK"‹…´C‡Yĩn=[vėäÁģoÃËĶŖA÷ųÃÆ-dfįrī7€›Ģ aĄÁ ēšø)\á~ܸ…[ˇ3~ôpēuę`ooŨ˛9‰íÛđō›īŗ`Éw\Ũĩ ē߲˛rü|}ˆ‹‰ S‡ļ :žˆˆˆˆ\HŽp+ŋ˙‘F‘áõÂČq!A<x×mØÛjjj˜ŋø;6o˙‰ĸâŧŊ<éØŽ5Cú÷ÁlŽ›’ôØ?^`@ß^-(`ķŦŦ"6ĻŽ—§¯žķ_ö8ĀũyŠáƒŽaŲĒĩõnŲúëĶĪ3 o/vīMaoĘ>žûûŖüķ…×éß§'YŲ9lûy6Ģ.‰íč×ģĶfÎ&eÎNN ЗΧ8~ōYú÷éIvîv&íĄ˛˛ŠøĻqL3w7�ŠKJ™51{SöQZVޝ=ģvĻw÷ÎöqR÷§1oņ2Ō3ŗ°ZmD„…0l`?{Ā:Ũú'žy‘]د7�EÅ%<ņĖ‹´ŊĒ%ˇNkßĮãĪŧHŸ]é×ģûY÷“ŗĒĒjĻ~5›ŊŠûquqĻ{įŽŋᝉˆˆˆČŖ@r+¯¨ =#‹}zž˛ODxhŊåéŗæŗãį$ƍJTD8ûbú7ķ¨ŽŽfÔ°A�88˜Yļr Cô埏?LQq /Ŋņ‹–­`ÜČaÜuëDfÍ[ĖžyđžÛqvrdŲĒĩõöcą8đũúü?{÷Uĩöqü;“IfŌ ¤‡$zGéQAĨw rņŠŊˇkŊ×ÂĩW|QšvTTDš‚t‘ŪC -¤gRfæũc`  ÁĀ đûŦ•5™söŲįŲ{Ââ<ŗ÷>§Qũētŋē3VŧŧĖĖ™ŋA}{1¸ß ,\ēœo&˙Ė–m;ØûzFŨĮ/ŗæ0iōĪ4nP?ßSמ˜ŊĖĖū}}{ug؀Ū8t˜wÆĘ÷SĻqķū�|1éö<ČČĄ dÛΞúî'ÂBƒiÜ ………|đņį\Ų´ƒûŨ�.ķ-åŊ Ÿņ“bņō:ãū:5“< ĀÖí; bێžm&;;‡ēĩ“ĘÜīĨõŲ˙ũīk<Ę[‡ČüEKYĩvū~~åøK9t—­ËXVV�UĒ„•Š|n^ËūZE÷Ģ;sE“FT­F‹fMčÜŽ5 —,§Øá𔍊§u‹+0›Í„QŋnmvíŪ €¯Í†ÅۂÉl&ĀßooīRĪįãíÍ=ŽĨzB5Ī(@\L4 ëÕÁd2Ņŧic�Ē'TŖzBĩŖÛQT\ˁƒ‡Nێ¸ØhZ5o†Éd"2ŧ*íÛ´dÕÚ Đī†Ü5j$5k$^…6-Ž 6&Š[ļž‘‰Ŋ €W4!*"œ¨ČÜØ“;oģ ‹ÅrÖũujÕ`GĘn\.�ÉÛvŌŧYcė…:œĀÖ; đ÷#6:Ē\ũ~bŸeeį°eëvŽíŌ‘Ú5k΀{bŗZËôy‹ˆˆˆ\!šœ™Ü/^æ˛åĨŠ{Ķp:TOˆ+ą=žZ,…EE<t˜čČ�bĸŖJ”ņķĩ‘—Ÿ_ŽđĒ'T;e[døņéc6›õčļđļŲ�ČĪ/8mŊÕNõ‰ŽŒ ¸¸˜ŒĖl"ÂĢ`ĩúđëÜųlŲ烜Ü\\.šyųDT­@DÕ*D„WaâWßŅĄMKęÖN".&Ú3]ëlûëÔJÂ^PĀŪ}û‰‰"yûú\ߝ]ģSŲē#…ĒUÂØē}'uj%a2™ĘÕī'öŲūHˆõl3™L$V‹c÷Ū}§í‘ I Ée,8(“ÉÄÁC‡ËTŪ^āžČ?ųvëŅ÷Į“�Iō ú�� �IDATīŌū´\åŠĪ×vę7ų‹×)ÛŧK9—ë į˛žŋ{„&ߞÃáāŨ&ât:éc"#Â1›ÍŒ˙ô OyŗŲĖ}wŪÎėš X¸t9SĻ˙JhH0×wëJË+›žuHpáUØļ3…  @<LÄxvĻėfێZ7oÆÖí;éqM |ũ~bŸŲ ö÷IĮųœļoDDDD.4%$—1›ÕJ\L4K˙ZEˇŽ°XNũsXšf=‹…FõëxFė%GėöŖĖG÷WvöŌã÷ķõeįŽ=ėMÛĪŊcn#Šz‚§LNN.UÂB=īüũé}}7z_ߍ´ũømū">ûf2Q‘ÄĮŜu˙ąu$ūÄDGâkŗQŖzßūø G229’‘IZîõ#įÚī>Į­|{‰íy'Ŋ1’֐\æētlьLĻĪūũ”}ûöāĢībí†M�ÄEGa6›Ųv‚l€)ģ°ŲŦž)M•]ōö%Ū§ėIÅĮۛ`Š‹‹đ?áa;RvsøHG—|p8ũkÖoōėŠŒ`pß^˜L&öĨí?ë~pOÛÚžsÉÛwRŗz"āžnučp:+V¯#2ŧ*Ą!ÁĀš÷ûąémŠ{Ķ<ÛÉÛw”ą§DDDDÎ?\æZ4kBōļĖš3ŸŨŠ{š˛I#ŦV+ģö¤ōĮĸeDE†Ķįún�øųųŌēÅ˚3Ÿđ*U¨MōļĖ_´ŒĢ;ĩķ,<¯ė2ŗ˛˜6k-¯lÆūųcņ2ŽlÚo‹…ؘ(, ŋ/XÂu×taoÚ~ĻL˙•ēĩ“8pđŲ9šÉČä˙ū÷7öŧÖŊĀŽ\ÉdĸzBüY÷ÔNĒNFfk×oōÜ%ËfĩÅŧ…KhÜ Ž'Ūsí÷°ĐãĢ1kî|ĒV #0Ÿß,Áâuę´7Ŗ(!†öŋ‘ēĩjđĮâ?ųnĘ4œ'Ūҭk':ļk…Ī kôî‰Íęä~&;'—Đā ēwíÄ5]:؂ōiÛōJōōíŧúö͍~ôî ¸§b ؇)ĶeŲ_̈¯ËMƒú’‘™Å'ŸOâ­?á‰îbøĀ>Ė™ŋˆ_fÎÁËl&*2‚;nJD¸{Aû™öøúڨÎ=ŠÔ<ajXRõxæ-\ꙮuĖšöûČaøōÛ˙ÉØl6ÚˇiAË+š°jŨ† îU‘sczüņĮ]ûöíãã?ū[­\ŸLlTøŲ Ęy‘švf jFĨ÷Č3˙)ņ�F‘ōZš>Y˙ÖDDDū†ŅŖGãyqĖą‘K’1ŒÖČeååg3:9FHDDDDDÄ0JHDDDDDÄ0JHDDDDDÄ0JHDDDDDÄ0JHDDDDDÄ0–˜L&œ.WEU'åärÃé4: ‘KZaQ1>Ūē9ĄˆˆHEǰ„Ä×f%''¯ĸĒ“r2áâĀĄ#F‡!rI;|$_›Õč0DDD.)–$ÄF‘“—OvnžžŠ7‚ŲˁäL§°¨ØčhD.)…EŤLįĀá âĸ#ŒGDDä’RaslVoę&%’ēƒ‡āp()šĐęÕJdĪž>’ФD¤ųx[đĩYŠW3ASļDDD*X…ūĪjŗzS§F|EV)åT#>ÆčDDDDDĘLwŲÃ(!Ã(!Ã(!Ã(!Ã(!Øķė…Ø ‹ŒŽCDDDDD.C– �ōrüŒŽCDDDDD.Cš˛%"""""†QB""""""†QB""""""†QB""""""†QB""""""†QB""""""†QB""""""†ąTdeö‚BRRĶ((,ÂápVdÕ"""""r5kPëŧŸŖÂ{A!›ˇí"0П Ā�ŧĖ—÷āKjÚÁ ōŠˆˆˆˆœ+×'_ķTXB’’šF` ?~žUĨˆˆˆˆˆ\â*lÃn/ÄĪ×VQՉˆˆˆˆČe Â§Ë…ŲdǍęDDDDDä2py/ôC)!Ã(!Ã(!Ã(!Ã(!Ã(!Ã(!ÃXŒāÍwŪgåĒÕÜ9z-[\Yb_fF&÷<ø<t?õęÔ6( ëš˙Œcɲåž÷>>ŪDFDpeŗ&ôšĄ'áUĢŨqĪũgûäŨ×_6:šˆž�˜Í&&}÷=Mš4Âęãct8†‹ŽŠäžŽĀnˇŗ}G 3fĪaÖosyæ‰GhXŋŽÁŠˆˆˆˆTŒJ1eĢi“Æäæå1cƯF‡R)Øl67ŦOã†õiŲü čÃûožJbB</žü_ōōķQDDDD¤BTŠ___nŧž'?üô3:´!,4ė´e3ŗ˛øfŌ÷lظ™Üŧ\ÂÂBéÚĨ3×^}•§ĖØûĸ×uŨIŨˇŋVŦÄétŌą};Žë~-ŸLüœää­X­6úôžžíÚāp8˜ōË4–.]ÎáôtÂÂBévMWŽęÜéŧˇŋ,|m6îžķū1ö~›;Ÿ^=琑™É˙}ō9ĢÖŦ#;'‡đĒUčÕŖ7^�<ú4žž6^ø×ã%ę{úų—ČÉÍåĩ—žĮápđÕˇ“™÷Į"<DxÕ*ôšĄ'=ģ_ķˇb.**bâß0Á"ŽddÂU:0|Č�ŧŧŧ�č?ôõīÍîÔŊüš|ųv;W4mÂ=˙MpP`™Ë”Ĩ ƒGŒbđ€>ŦXĩ†ÕkÖņåÄņøûųũ­6ŠˆˆˆČßS)FHœN'WwŊŠĐĐPžųö‡3–ũøĶ˙ąuûvūqĮm<˙¯'éŲŊ_Oú–+WyĘXĖ^˘5›ĻMšđöëãĐ¯3fÍæĩ7ßáúŨxįÍ˙ŌŽ]+>ûâ+rsķ�øæģī™1ãWzõŧŽžyŠn×tå˯'1īįĩíåKlL4k×mđl{ũíظy <p7īŊņ ûŪČGŸüÅK˙ S‡ļŦ^ģžÜŧ<Ī1šyyŦZŗ–ÎÚ0aâį|˙ÃĪ îī‰ésCO>œđ)3gĪų[ņžûáfũ6—ÛGgü;¯1røĻü2ƒ ŋđ”ņ˛xņíShܰ>_|2žw^{™­Ûv0~ÂÄr•)K, ĶgũFbB5^záilVëßjŸˆˆˆˆü}•"!Á‹ƒögé˛?INŪzÚĸC äÁûîĄnZDEEŌąC;ĒUĢÆēõJ”‹¯Gŗ&0™L´nŲ€š5ĒS3)Éŗ­°°ˆ´´4ōķķ™3wŨģ_Cģļ­‰ŒŒāĒΝhÛĻ ŋLŸy^›^^áU̐ž‘áy?úļ›yņ™'hÔ ą1Ņ\{uĒ'&˛bÕ�Úˇm…ĶédŲōžc–,]ŽĶé¤Cģ6äåå3uú,úõéE×.‰‰Žĸg÷kčÚš“ž˙éœãĖĘÎföÜų ؏ŽíÛI—Níšáúë˜>ë7ŠŠ‹=e“jTįę.0›MÄÅÆĐŗûÕ,XŧģŊ LeĘÚ“ ŦVn1Œzuj{FiDDDDÄ8•bĘÖ1͚4ĸQÃ|ūõ$ūõÄcĨ–ąY­L1“›6“ƒËå"77—¨ˆˆåĸĸĸ<ŋûúú}Â6�yųų¤ėÚCqąƒ†õ뗨Ŗ^ÚĖ˙cvģ›ÍV!müģNg‰ i_›I“bõÚõdfeárēČÎÉ!6ÚŨū°ĐP6¨Įâ%ŌĨc{�,^JĶÆ fíú;¸ĸi“įiܰ>3gĪ!ßnĮ÷ÚžcG N§“ēĩk•Ø^ģf Øģw ņÕ�wĸxĸøøjq8=Ø˜čŗ–I?’Qæ6\.wkšXTĒ„`đ ū<õ¯įųcáBš6jTb_qąƒWßx —ÃÉĐ!‰ŽŽÄËäśīžJ=īS›fąœēÍår‘oˇđō¸×0aōėsē\�dffUš„$5u͚¸ûĨ¸ØÁĪū§ÃÁčÛGR-./ŗĪūįÕĮtl׆˙ûôs ‹(vŗbÕƎšĀŗ@ūҧž-ĩíGŽdāEyĢ×ĪΎÄöcÉa~žũøļ“úöØTǜÜÜ2•)Oü´fDDDD¤RŠt Ilt4]ģtâûĻP¯NûļīØÎž=Š<öđƒÔŠ]Ķŗ=+;›đĒUĪųœ~G/’ī¸íVââbNŲzÎuW¤õ7‘~äW4m ĀæädvĻė╟)q+āĖĖŦ#FíÛ´âũ>fÅĒÕĐļUK�Īĸî‡îKbBĩSÎyŽĪ=9vៗWōŽ`yGײøųO NžkØącüũËTύ¨øŧ´ADDDDÎŋJ—�ôžĄ‹—.cúĖ’ˇ>vápübvëļm:t˜ÕĪų|ņÕbņļXČĘÎ&æ„i]YYؘĖ&ŧŊŊĪšîŠ’““Ë;L ""œöíZPXX@P`€§ÜÆÍ[Øā ĩk%yļҤQC–ũĩ’ŧÜ<Z6o暨‘˜€ˇÅBFf&ÕâZ{ŽÉĖĖú[m¯‘˜€ŲlfÃĻÍÔ­s|ÚÖÆÍÉøųųzĻ”Ŧ=iũOōļmX­ÖIæ™Ę„†„œ—6ˆˆˆˆČųW)?úÜx_~=ŠÄöjqqx{{ķëėšôžĄ'{R÷ōíäiØ >ûŌö“™•EpPPšĪįëëK§ŽíųqĘĪøSŊzu§§ķåד ážģīǍϕ‰ŨngÍŅ;iŗcg ?MŨnį…=Ž÷ŅŠg5đööæ§ŠĶ6¸?;SvķÉg_qEĶÆėIŨGFf&!ÁÁ�tl߆¯&M&7/{˙ųĪšüü|é~mW>˙ú[‚ƒŠ]Ģ&âà ŠZ%ŒgŸ|ä´qæįįŗ|ÅĒSļ'&ÄSĩJ×^Ũ…ož˙‘čč(’Ē'˛vŨĻN›Iŋ>ŊJŦƒIO?Âį_K×ÎŲŊ'•ŠĶgŅŠC[||ŧËTÆĮĮûœÛ """"ÆĒ” @—N˜ûû<ö¤îõl äļ‘#øî‡Y´d ‰ ŒēeéG2x˙à ŧ2î ^|îés:ߐAđķķcŌw“ÉČĖ"8(˜fMŅŋoīŠjR™íKÛĪŖO=¸ŸbFķ+›2¸"q˙Ø1|ōųWüöû|j%%ņĀŨc8t8—ÆŊÉŖO=Īo ]ëVŧķÁŦVZ6oVâ|wÜ:‚�&Lü‚ô#„†„Đē啌>äŒqĻí?ĀĶĪŋtĘöûÆūƒkŽę˘QˇāįkãŨ&‘™IxÕ* ؗ}o,QžÛ5W‘““ËŊ?AaA!­Z\ɘQˇ”ĢĖšļADDDDŒezæ™g\{÷îåÃ?ü[­\ŸLlTx…uņKM;HŗĩÎ^đ27hÄíôîՃ!úū­2""""RąVŽO>/×ŗŖG&&æøēíĘņš,)!ÃTÚ5$ryøæ˙W!eDDDDäâ¤1Œ1Œ1Œ1Œ1L…%$&“ §ËUQՉˆˆˆˆČe Â_›99yUˆˆˆˆˆ\*,!Iˆ"'/ŸėÜ<NgEU+"""""—° {0ĸÍęCŨ¤xRRĶ8xø‡’’•듍ADDDD¤RĢĐ'ĩÛŦ>ÔŠ_‘UŠˆˆˆˆČ%LwŲÃ(!Ã(!Ã(!Ã(!Ã(!Ã(!Ã(!Ã(!Ã(!Ã(!Ã(!Ã(!Ã(!Ã(!ÃX2˛rČÎÍ̐Ęė…¤¤ĻQPX„ÃáŦ:EDDDDÄÍÔ:īį°øŲ|°ųx˙íŠė…lŪļ‹Ā@‚đ2kđår–švđ‚ü‹ˆˆˆČųąr}ō9Ĩĸ*JIM#0П�?ߊĒRDDDDD.q6Œaˇâįk̍ęDDDDDä2Pa ‰ĶåÂl2UTu"""""rĐB1Œ1Œ1Œ1Œ1Œ1Œ1Œ1ŒÅč�Ū|į}VŽZ}ÚũͯlÆ]cF_ˆÜîē÷ŽŊē+7\ßŖÔ÷åŗ/žbĶæd^|îéŋUæRöŪøYŗnŧ5îŧcKō6~ž6“5ëÖs$#oĸĸ"hŨĸ9Ŋ{õ ĀŋDųA#n§w¯ Đ÷oˇ˛Õ#"""rĄž�DD„sķđĄĨî  ŧĀҏ ؟¸˜XCÎ-Naaož÷!Ģ×Ŧį†žŨĐ÷ĒVŠBaQ!ÛļīdÚĖ_}÷<˙ôcÔHL0:܋ŌĪĶf˛eë6¸ûNŖC‘J¨R$$VĢ•õëF íÛļ1:9ĪO<ķ"AALx˙ ŦVĢgŸž\ŲŦ W6kÂäŸ~á™^სĮáįëk`ħ­Ûv‚ˆˆˆTb•"!ų;ÆŪ˙ŊŽëNęž}üĩb%N§“ŽíÛq]÷kųdâį$'oÅjĩҧ÷õth×€ĖŦ,ž™ô=6n&7/—°°PēvéĖĩW_åŠˇŧS´ĘRį‘Œ >™ø97mÆ×ח.;œROYƌŊ÷AzõŧŽu6˛qã&Ū|ũŦ>VĻü2ĨK—s8=°°Pē]Ķ•Ģ:wōˇyËVž˙áGvī؃Ķé"žZũúôĻnZeÚ!N?›ī~Čęĩëņ÷ķŖG÷ĢO)ãp8øęÛÉĖûc"ŧjúÜГžŨ¯āGŸÆ×ׯ ˙zŧÄqO?˙9ššŧöŌķ�|ųÍ÷˜Í&žxø~Žb>ųߗü:gö;íÛļĻcģ6ŧøĘë|˙å'üšb%ŗfĪĨw¯%â˙ņD~ûũ šĸYcîųįh‚ŨŖz™™üß'ŸŗjÍ:˛sr¯Z…^=ēqãõ×Ōž3Õsļö–ύ¨ˆ‰_|Ãü‹8’‘IXhWuęĀđ!đōō Īā›>¸?ũz÷ō÷Æģ˛}ĮNŪ÷Ÿ2Ÿ{Ũ†MLüükv¤¤āt:Š‘˜ĀÍÃ‡Đ¨A=yōYÖŽßĀosįķök/‘T=ąÜą1ŠÁú°bÕV¯YĮ—ĮcˇđÖ{ãYŊvūūūôéՓÜŧ<-YƇo˙÷´}#"""•KĨHH\.……EĨîķöļ`2™N{ŦÅėŌYŗšiøPFŪ4Œßį˙ÁÄĪždãæ-Ü4tI5Æ0ų§)|öÅW\Ņ´)ūū~|üé˙ؗļŸÜq!ÁAlIŪʧŸ}NÕ*a\ŅŦé9ĩĄ,uŽŸđ)ûĶpßŨ˙$8$˜9s~gųŠ•øxę)K// ŋĪ_@Ķ&¸áúë°úXųæģī™7o#†ĨfR ÖoÜȗ_OÂËˋNÚcˇđÆ[īŌĒesn1 \.~›ķ;¯ŋų6¯Ŋú^^^gÜīīīwNũR^ãŪ|—ÔÔ}<÷ä#„†…2uÚL.^J`āņŠ{&~Îô™ŋq×?n§^ŨÚŦ\Ŋ–'|ŠÅâEˇĢ¯ĸS‡ļ|ôÉgäæåáīįŽ;7/UkÖrûț�ČËËgō”_xįĩ—0›MŧōúûlÛžƒįŸ~”*UÂøūĮŠŧõŪxj×JÂl6sU§öĖ_°¸DB2köī´i՜ž~œ}û÷ķÖģãyრ<ūĐŊ�ŧūöėIŨË#ÜMXhë7lâ­÷Į^•6­Z”šžŗĩˇ4ī~8EK˙äŽŅˇQĢf›6'ķÎ˙GAa!wÜ:ĸ˟ĮŲÎmˇđĖ /ĶŠC[ÆŪ9 \.~ž6“§ŸûŸMxŸ§ˆĮž~ž˜čhƌē…Ā“Öâ”5V‹ÅÂôYŋŅĒÅ Ø›ÕĘË˙}‹í;Rxúą‡ fâß°{O*ŪŪŪenŸˆˆˆ¯R$${ö¤rĮcKŨ÷¯'ŖúYæîĮW‹ŖY“F�´nŲ‚‰Ÿ}IÍÕŠ™”äŲöķÔé¤ĨĨ‘”TƒĄƒb2›‰¯ @TT$ŋũ>uë7œsBrļ:ĶaãÆMÜ4lõëÕ`øĐÁŦß°ÉSGYĘ�˜LāããÍĀūîĖųųųĖ™;ž=ēĶŽmk�"##ؙ˛›_ĻΤS‡ö>’Nž=ŸļmZ °!ƒhŲĸ9‹‡ŌĪŧ˙B8t8ÕkÖqįˇŌ¤qC�ÆŒē…•Ģ×zĘäåå3uú,öëM×.ˆ‰ŽbëļLúū'ē]}íÛļâà Yļ|]:ļ`ÉŌå8N:´sOÅ[°x)I5‰‰fsōV,ZĖøw^'&: €[G eúĖŲÔ­íŠŽæāĄÃ%â fˍ[�¨UŗÛwėäû§RPP€Õjeôm7c6›‰ŠŒp×ÍĪĶgąb՚ əęq8œgmīɲ˛ŗ™=w>ˇNĮöîQÁč¨HvíIåĮŸ§qˈĄx[ÎūOŋ,}}āĐ!ōōķšĒsâãÜkŽūqûH:ļoƒˇˇĢՊ—Ų ooKŠëÁĘĢÉV̎ŽĀ‘ŒLūZšš1ŖnáŠĻxøūąÜ<ęŸT ;kÛDDD¤ō¨ IddwÜ:˛Ô}ŅQî D‡ÃŊ ĀŗŨâåå™ķu´ €īŅ9ūŅG/ĒŨÛl�äåį`ŗZ™:c&7m&;;—ËEnn.Qį܆ŗÕšo_�5Ē'zŽ1™LT¯žĀŽ]{Ę\昚I5<ŋ§ėÚCqąƒ†õë—(S¯Nmæ˙ą�ģŨNtd$Q‘‘|øŅĮté܉†õ듐Pēujœu˙…°{O*�ujÕôl3™LÔŽ•Äļí;Øļc'ÅÅŽhڤōÖgæė9äÛ턅†Ō°A=/ųĶ“,Xŧ”Ļ ĀÖmÛiÜĐŨ_ŗfĪĨqÞdä§Ë陮f/(Āl.y—ė†'­{ĒW§6‡ƒ}iûILˆĮ×fcŌäŸXŊv=™YY¸œ.˛srˆ=é<gĒ';'÷ŦíõĩŲJėÛąÃ=uęX2uLíš5(((`īŪ}$ÄWãlĘŌ×q1ŅÄÆDķĘkoĶŗûĩ\Ņ´1I5iÔ ūij-Š<ąÖ;áoqīž}¸\.ę×ĢãŲæįëKŗÆØuôīHDDD.•"!ņņņ!é„ ėŌŦß°‘×Ū|Įķž]Û֌:šÄXŧOm†Ĩ”o€].ÅÅ^}ã-\'C‡ $::/“ožûū9Į_–:ív;āž‚v"›ÕVŽ2žm',ŽÎ?zÜËã^ÃÄņémN— €ĖĖ,"##xü‘™6cķūXĀw“¤JX(}ûÜHģ6­1›ÍgÜ!äM}|JNš9ņ‚ûXRųčSĪ–ÚÖ#G2đŽĸcģ6üß§ŸSXXDઘĢÖ0vĖížō‡ĶĶILˆ`oÚ~Ēŕ8įÆÍÉØíÔŠåeÛ—ļŸØ˜’‰Äąé`Į؎&Čv{ÅÅžxöß8Fß>’jq1x™Ŋxö?¯žŌî3ÕSÖöžčØ1~~%āKÖķķí§ÄPš˛œ;&:Šq˙~–o˜ÂŒ_ãĶĪŋ"ŧjF D×ÎË|ޞÄęwB?eeį¸Ë”Œ """—J‘”ERR äĪû Ā sĒgûŽíėŲ“Ęc?HÚĮŋ‰ĪĘÎ&ŧjÕķVįąŅœ“/]•ĩLiŽŨųéŽÛn%..æ”ũaaĄ�2x`?ėGęŪŊ˜5›&|JLt4ÕÎē˙|ŗŊ¸ĖÍ+ŲۜÜ<ĪīĮ.Ūēw,‰ §~Ë^ĩ �íÛ´âũ>fÅĒÕĐļUKO9§ĶåY›dŗZq8%ęųúÛÉøûęîģßį/¤WĪn%ĘØ J~NĮC›¯ÍÉÉėLŲÅ+/>CÃúu=e23ŗN‰;S=ÅGã:[{OtėÂ=ī¤~ĖËs÷ŖßŅõ@Ĩ-Í*<ÚWPöžâö‘Ú}äpvíŪÃäŸĻōß7ß#žZĩÎōECYc=™ĪŅu"……%ļgįäžņ|"""Rų\4Oj÷÷ķŖv­ZžŸ¨¨Čsǧ¨¨€€€ã:[ˇmãĐĄÃ¸pˇ:Åģk÷ņŠWÅÅ6mŪėy_–2Ĩ‰¯‹ˇÅBVv61ŅŅžŸ�˙�ņööæĀÁCŦ8ᔹ11Œ> ŗŲDęŪŊgŨ!ÄÅē§ŲmßąĶŗ­¸ØÁÚuë=īk$&āmą‘™Iĩ¸XĪOP` ÁÁAžÍÁÁA4iԐe­dņŌ?iŲŧY‰oáŖŖ"ØŊĮŨĪM7dŅŌ?IŨģƒ‡ķæģã Âáp•Í7ßũ@Vv6íÛ´*īú %?—-[ˇámąéšICĐ ßØoÜŧ…ũžōwvĻzĘÚŪÕHLĀl6ŗaSÉz7nNÆĪĪ×3eĖĪׯD˛°#eWšú:m˙/[î9&žZwũcfŗ‰”]ģW|šZeõdĮĻ×mŲ˛Õŗ-/?ŸU'Ŧ7‘‹CĨ!ąÛíŦYģŽÔ}fŗ™†eœ^Õââđööæ×Ųsé}COö¤îåÛÉ?Ō°A}öĨí'3+‹ā ōž”ĨÎĒUǐ”Tƒ_ĻÍ 2"œĀĀ@~ũm¯ã ÆËRĻ4žžžtęØž§üL`€?ÕĢWįpz:_~=‰°Đîģû.ŌĶĶyįŊØŌÔúT�� �IDAT¯/Mš4„‰ÅK—a2™ŠYŖÆY÷_ááÔ­S‹Iß˙HLtÁÁAü4uz‰éw~~žtŋļ+Ÿũ-ÁAÔŽU“ņᄉT­ÆŗO>â)Ûą}žš4™Üŧ<îũį?JœĢųÍxũí÷šåĻĄôč~ )ģ÷pßÃOâįįˀž7rUįėŲŗ—›n쓯 ëķÂŋ?e Iځ|õíd:whĮž´ũL›1›öm[cõņq_Ė{{ķĶÔé ܟ)ģųäŗ¯¸ĸicö¤î##3“āāŗÖƒenī1\{užųūGĸŖŖHĒžČÚu˜:m&ũúôōÜJˇVRu/ũ“>7ôĀ×חÉ?M%+;›*GGÔĘŌ×âŗ˙Ë­#†Ņ˛ų˜L&æÎ[€ÉdöŦųđgÛölÛą“đĒU˜ŋ`1ŋĪ_˙<WæXOIRę|ũŨTĢK€ŋ?Ÿ|öĄĄ!åûŖÃUŠ„äāÁC%ևœČl6ņņøs_ßq˛  @n9‚ī~ø‘EK–˜Ā¨[F~$ƒ÷?œĀ+ãŪāÅįž>/uūcÔm|<ņ3Ū|û=l~ž\ÕŠ#mÚ´fŊUžēĘRĻ4C ĀĪĪIßM‹ā `š5mD˙žŊ¨[§6ˇŨr33fÍæ‡Ÿ~Ælö"&6šąwŽ&**’¨¨Č3îŋPš˙nŪx÷Cž}ņüüũčŲíŽęܑEK–yĘÜqëüũ™0ņ ŌdBë–W2røuĩkŨŠw>˜€ÕęCËæÍJėģĸic"#Â˙ņ˙¸ķŽ[š{Ė(î3ĒD™×_y§ĶIQQQ‰‡&8Š ęׇũpĪCSTXD‹+›1æ÷Ũ˛‚ƒƒ¸ė>ųü+~û}>ĩ’’xāî1:œÎKãŪäҧžįƒˇÆĩžō´÷DcFŨ‚Ÿ¯w?˜@Ff&áUĢ0x`_öŊŅSfÔ­#xíí÷šyÔ]øĶíšĢ¸ēK'VŦ<>Rvļs7jPŸûƎaōOŋđŲ—îÛLĮĮĮņÔŖãņēņúëxõwxđąņä#÷sđā!6mI.WŦĨyôģyũxôÉį eđ€>$oŨΖ­ÛÎxœˆˆˆT.ĻĮÜĩoß>>ūøãŋUŅĘõÉÄF…WPXrąKM;HŗįâĐátž|æEĒV­Âā}ŠW§^^^8NvíIå¯̘>ë7ôŊá´ĪûãPT\L€˙ņg›<öôķđøÃ÷™ˆˆČĨaåúäķr=7zôhbbޝ{Ž#$"F¨Z%Œ7^ũ7“§LåÍw?d_Z6Ģŧü|ÂĢVĄYĶÆÜ?vL‰[ËJåņ¯^&##“ąwŽ"$8˜eËW°zízžyâaŖC‘rPB"—5›ÍĘЁũ:°……Eäæåāī§§}_yāÆ<‘^ú/v{ŅŅQÜ÷Z6ŋÂčĐDDD¤”ˆåã㍏O°ŅaH…†ķČũw†ˆˆˆüMÍmEDDDDäŌŖ„DDDDDD Ŗ„DDDDDD Ŗ„DDDDDD Ŗ„DDDDDD Sa ‰ÉdÂérUTu"""""r¨°„Ä׿CNN^EU'"""""— KHbŖČÉË';7‡ĶYQՊˆˆˆˆČ%ŦÂŒhŗúP7)ž”Ô4>‚ÃĄ¤ärˇr}˛Ņ!ˆˆˆˆH%WĄOjˇY}¨S#ž"Ģ‘K˜î˛%"""""†QB""""""†QB""""""†QB""""""†QB""""""†QB""""""†QB""""""†QB""""""†QB""""""†QB""""""†1ûøøāíímt"""""rŌ‰ˆˆˆˆˆF ‰ˆˆˆˆˆF ‰ˆˆˆˆˆF ‰ˆˆˆˆˆÆR‘•Ų IIMŖ °‡ÃY‘U_ō‚ü­|üŲ×ääæát^\}g6› đ÷ã֛“”ot8""""rа„Ä^PČæmģ ô'(0�/ŗ_Ęę¯ĩøxâ ŖÃ8gN§“ŦŦlŪxī˙¸÷Îە”ˆˆˆˆH™UX֐’šF` ?~žJFĘiîœßáī3™ĀåbÂ˙ž6:šˆTXæ`ˇâįk̍ę.+ųvģŅ!T “‰Üŧ<ŖŖ‘‹H…MŲrē\˜MϊĒN.Rå]˙˛ķÜ7 V택į)¨‹\b4†×{@bčéË]Ž}YÖž‘ĘĢBĩ‹”ĮÎ#Đė]ȸDˆÎ—îŸßwĀĘ–~á}šöeYúFDDD*7-öÃÜ;íōģ€ū;2ėî>+ÍåŪ—gęŠÜ4B"†™ˇÃč.ŧ„xõÚbšDēH(åÛü”#°zŋ‰‡fYH)eÚÕę}Ĩ×{)ôåųęŠÜ”ˆa.ˇoôB`éE„œáŪ Ąęĸcb­Æ{Ÿrá}ēĩ!{_žĪž‘ĘMSļDJņ@;Čxԟ€úáāzžė uĢ˙ŊŧÆu+>ã÷‰Blîō“3õÍą}Ī\Uúą—z߈ˆˆČéi„D¤cZÁŽŖ Å}`ĀW°n˙ßĢŗc‚Ģ\åG”¯ŧŅRŗÎŊŸ.õž‘Ķ͉T:˙l{q˙ÜŅÜũÍús]Ī|LãHwš7zĀŌ€ũøiøyCŗh÷žį¯†m÷Ã#ÜĮ´Œƒ?Į@Áŗú0<ØLĀō1枝Ŧ눁o‡@˙†§žˇ{-X}ä?ķo‡¨€ĶĮ|Â@ęžT8čyŸŸŸĪĻM›K”/mEyŽgę¯3ĩĢi”û¸Į:ÂÜÛ ÷_Į‹ *ŲOcZ‘'ŨįíS˙ĖqŅ7"""R9(!š„Ô¯[‹×˙ũ4¯˙ûię×­et8į$) ŪžŪũmû¨`Pc÷vįYž/t¸_5†ū ˙ 7ԃ{ۂũčėžQÍáŋ á—ÍPÕfßŪ^pãį0yŧÚnj 7}û˛au´x§y´J|0|?vgB› Đ Ÿ ([;GŽēƒAC†ą{÷nōōō=æŸô8¸Ä…øßuĻ6žŠŋÎÔŽ‚ŖĮŨŲ ^š,s7ŦIÉsW…w{ÁÚ4ø5tŠQö¸/D߈ˆˆHåQ)’M›ˇđú[īpĪsëwōĪ{îįÍwŪgëöKāÖAPbĩ8, ‹…ÄjqF‡sNēÖ�“ ^žŋl˙Ė+ßņß­ƒåŠđæ"Č-„Ģ“Āu4™™ļŪ[ ë¸ŋą´ÂķsaF2<0ōŠ`hØxĐ}ÁžSāŽëtú6p ŧĩVĨÁø?ŨņGøŸ=Î{ĮŽåĀÁƒÜ|ËíŒ=†?—˙ňáȈ/_ƒĪāLm<Ļ´ū:SģŽõå”M0s+ŒûÃũžadÉs_“äūĮ-€…ģāßåø/D߈ˆˆHåaø’›ˇ0îĩ7iÕĸ9ŖnI€�éGŌųeúL^÷:O=ņqąąF‡)HÕŖķŠYî×Ũ™å;ū`ŽûÕá‚L;„ųߡķČņßcŨ¯{ž§Đ‡ķ &¨ėį õuŋū8Ė=‚ãev_„Į‡ĀÜ3ÛŗGwėö|ž|úöĨĨҎOoyøÁ˛Ÿŧ ĘŌÆŌúëLíĘ)pīۗí~Í.tŋZŊJžûØįxŦŽ•/‹ Ņ7"""RyžĖ™ķ;11ŅÜqû-žm‰‰ņÔĢW‡ūũ*[’ˇ*!šŒÉwŋFŊ˜Ž\žãc^l[ŊÜÅëßwâ´¯=Y%ËÛ,î_žŲŠG“ĨQ?ÂŌŨ'lĪ:ûąššyü4ågĪûE‹ŗk×.âããËĀY”ĨĨõיڕRļs{>ĮŖkOĘz\˜ž‘ĘÃđ„¤¨¸Gąã”íž6_^|îéSĘū4åg.ZJn~> ÕâØŋ/ĩj&š÷ņũSXöįr2ŗ˛ ĸMëVôšą^^î¯pĮÜu×÷ėÆž´ũŦYŗ{A ę×į–›o"(Đ}õäp8˜ōË4–.]ÎáôtÂÂBévMWŽęÜÉËæ-[ųū‡ŲŊgN§‹øjqôëĶ›ēu.Žĩ=ŽéÂu×t)ußô_į2íך4žšÛŨ¯v„‚bx¨CųŽīÛ�fm…ļņāã3“K/÷ãøīuđdČ*€~ ÜėŸŽ(ûšĻmü"ÔveĀíÍ!.zL<ûąwŨs/.˙‹‘#nĸaÆ<üčcÜ|ËíüôÃw•c˜æ ĘŌÆŌúëī´ë˜ßļš_îāž&öX§3—?Ņ…čŠ< OHš5nÄ'Ÿ}Á;īČuŨ¯ĨzB"fŗŠÔ˛ßLúŽe.gØĐÁD„‡3{Î\ÆŊūĪ?ķáUų닝økå*F BõÄDļmßÁÄĪž¤°¨ˆĄƒÜĢrŊ,fĻ͘ŐA¸õæė?°ŸW˙û_}=‰ŅŖnuŸįģī™7o#†ĨfR ÖoÜȗ_OÂËˋNÚcˇđÆ[īŌĒesn1 \.~›ķ;¯ŋų6¯Ŋúūū~ĨÆ_‘ę×­uĘ:‘ZIÕKüŪã¤cvîŪÆM%¯Đ%''%F$#�›ÁÃ3āáŽđÁđúBč\ũøÚ…ŗųf <ÔDÂWkāŨĨXʡķ‡ķĄûDxí:÷îŊY0v*LZWöX÷dAŋ/á•î0s$$v¯Ķ(85ŋÜSĸŽŨMĒVRMš4jÄŊ÷ŒĀd2ņķÔŠX­VOųŌžF^gjcŨĒî2Ĩõ—Ŋ¸|í*͖ÃĮ?Į }āššîĪŅÛĢôōēoDDD¤ō0<!éÔŠ9yšü<uË˙Z‰¯Í—Zĩ’¸ĸiÚ´néšɡį3ī… ЏV-š0rÄp 8pā�žž6,ZÂāũhÕ˛�á¤îÛĮŦŲsĐ¯Ūwsãããißļ �ŅQQté܁)S§qŗŊ�—ËɜšķčŲŖ;íÚļ 22‚)ģųeúL:uhĪá#éäÛķiÛĻ%ąŅŅ� 2ˆ–-šcąœæŠĢ‚1‹åô_͉ÔŦ‘Xb[qq1÷=ūÜ)eONJŒJFŽ˙'Lø ŌķŨˇ­…ŗ¯É8&-ZXrÛĻC`zōÔ˛KvCÛņĨד8îôĮŸøûôd÷OYĖO1ŅĢŽ;ŗzüą‡Kėëq]7z\×­Äļ5ûKOĖËãLm„Ōû Nߎ“û"§đô}ķę÷Ī1-?}FôˆˆˆT†'$�=¯ëÎÕWuaÃÆMŦß°‘õ6ņég_0eę4¸o,ą11¤ĻîĨ¨¨ˆÄÄĪqŪ w †›p:$Õ¨^ĸî‰ °˙~ĪZ”Ä“æĸĮÆÆPTTDFF™Y;hXŋäƒęÕŠÍü?`ˇÛ‰ŽŒ$*2’?ú˜.;Ұ~}ĒQˇNíķŅ=ĉ ˆ‘ÉH¤?ėyVî…ûĻÁíĄØ í=ũSž÷d‚” įšxpĻ…Ž E%žšq:™vwų˅úFDDäōUiūWˇZ­4kڄfMŨ÷$Ũ°i3īž÷!ß|;™ûīš‹ÜÜŧŖå|J=Ūnˇ`ķ-yEcŗšßØ Žo;aę€ÕĮ]gn~ųGëyyÜk˜8ū-Ŧķ蜡ĖĖ,"##xü‘™6cķūXĀw“¤JX(}ûÜHģ6­Ī­ĘéŖ˙}UꔭcŖ"[ˇī$y[ÉÛ&īÜŊįŒu^čD$Ø ™%ˇíĪ…a“Ü1œ}ĢûÎXC'Á˛=îŸĶ96Š2Kɀ–ãŊ×­˜Æ‘ŽRz§d¸ŋũpĻĨÔiI§[^Z_^LÎg߈ˆˆHåfxB’™‘‰Õfõ$ĮÔ¯[‡+¯lÆę5îIũîÛ.ŲķíĨÖcķõ-u~žûv?6?ßãÛė'—qŋ÷÷õŖ¸Č=QūŽÛn%..æ”ķ„…šČāũ<°Š{÷2sÖl>šđ)1ŅŅT?aį|Ų°)ų”õ =Ā“$oÛačHGYtŽ?m<uû¤uå[˧Ÿ–UŲ¤dĀ€oÎũŸ]ĶčŌˇŸŽ/O§2ö×ųęŠÜ }0bff÷=üĶfĖ:eŸËåb_Z!ÁîģęDGEbõņaĶ–ãáN§‹˙ŧ2Ž…‹–‡Ųl&yëļõlŨļ__Qžm›ˇl)QfgĘN|ŦVˆ¯‹ˇÅBVv61ŅŅžŸ�˙�ņööæĀÁCŦXĩÚs|lL #†Ãl6‘ēwo…ôÍåāîoöĨl‚­î>+ÍåŪ—gęŠÜ ! ĸûĩW3eę42ŗ˛hÚ¤ ū~ddf˛páb’“ˇ1fôí�øúúŌĄ}[Ļū2°ĐbĸŖų}ūėÜš‹ÛF&āO‡öí˜:}‘ÄW‹cķædæĖũîŨ¯ņÜö ##“ĻüLģÖ­Ųģoŋ͝Gë–ÍņņņŧéÔą=?Nų™Ā�ĒW¯ÎáôtžüzaĄ!Üw÷]¤§§ķÎ{0°__š4i„ ‹—.Ãd2SŗF ƒzķâ“ Ģî‚{§ÁĒ}ēsŌé$„¸ŋũŖ‡ģĪJsšöeYúFDDD*7çl ėߗؘæ/XĀĮĢV“›“‹ŸŸ/‰ <xßŨ4lP˙„˛ũ0™L|ķíd ėvââbšīžģˆˆ`øĐAøÚŦüīŗ¯ČĘÎ",,”ë¯īÁõ'ŨĄ§cĮväåæņÜŋ_ĸ¨°ˆĻM3lČ Īū!ƒāįįĮ¤ī&“‘™EpP0͚6ĸßŪ�Ô­S›Ûnš™ŗfķÃO?c6{ÍØ;GyzíŌ‘ę~"¸ü}ęKšž�´kÛÚs‹Ũ3ņņņfؐA%’‡y[, ؟ÁûŸą/ŗ×ëņōōĸoīčÛû†ĶĮÜĻõ[Ā^V;wīĄ¸¸Øķ숈ˆˆHeW)Š6%—úœ‘ĘĘĐEí"""""ryģėFHŪyãŋF‡ """""Gi„DDDDDD Ŗ„DDDDDD cÉČČ ;;Ûč8DDDDDä2MFÆņ‡ĻYBBBČËË30$š\ây¯)["""""b˜ KHL&N—ĢĸĒ“‹”Éd2:šˆTXBâkķ!'GSŋ΅¯Í—B2įrāīot""""rа„$!6Šœŧ|˛sķp8UíeĄËUáRY0™¸mÄ`ŖŖ‘‹H…=ŅfõĄnR<)Ši<|‡CIIYE…‡sīˇ3á_““›‹ë"-1™LøûsۈÁ$%ÆŽˆˆˆˆ\D*ôIí6Ģujč‚ô\ũû重ADDDDä‚Ō]ļDDDDDÄ0JHDDDDDÄ0JHDDDDDÄ0JHDDDDDÄ0JHDDDDDÄ0JHDDDDDÄ0JHDDDDDÄ0JHDDDDDÄ0JHDDDDDÄ0JHDDDDDÄ0JHDDDDDÄ0JHDDDDDÄ0JHDDDDDÄ0JHDDDDDÄ0JHDDDDDÄ0–ŠŦĖ^PHJj…E8ΊŦúĸU¯fBĨé//3VobŖ°Y} EDDDD*0!ą˛yÛ.ũ ĀËŦÁ—ũ‡Ō+UŸ8œNrsķŲŧmu’â•”ˆˆˆˆˆá*ė 9%5Ā@ü| ŋđŽ,L˜*UŸx™ÍúãīįKJjšŅለˆˆˆT\Bbˇâįk̍ę. ÅGĨė_ ‹ŒCDDDD¤â§Ë…ŲdǍę. ŽJÚ'^fŗáëYDDDDD@wŲ)!Ã(!Ã(!Ã(!Ã(!Ã(!ÃXŒ@Nårš(ĖÉĸ +§ËåŪ†ûՄûš&fŗ k`>A˜*áŗNDDDDDĘĸR%$ī}øËūü‹‘7 Ŗs§ģžģî}€k¯îĘ ×÷¨€č. §ÃÁá-kØ5ũKŌWÎÃUT„Ķéāh^‚ÉfŗfoB›v ūēĄTŠĶŗ——ą‹ˆˆˆˆœƒJ“äæåąrÕââbY°hÉ9%$ŗįüΎ;uëH�ėO\LlGz~åf‘ōÃ˙áØ¸>Nŧ|Āl2áerg$— §Ëƒ|¯šÍN{wŊ€-8ĖāČEDDDDƝŌŦ!YēėO||ŧ:h�[ˇmc˙ūåŽ#%eW‰÷íÛļ!11žĸBŧ ˛ŗČM^I¤ŋ…`_+žVül>X­VŦV÷īžVj ž›VĪ}JūÖUädˆˆČ9Š4#$ .ĻeķæÔĢ[‡ĒUÂX´d)}nėUĸLQq1?Mų™…‹–’›ŸOBĩ8öīK­šIŧôęŲ´9€…‹–đėĶOđękoxĻlŊđŸWąŲŦ<xßŨ%ę|íÍwČÍËãŠĮÆáp0å—i,]ēœÃé鄅…Ō횮\ÕšĶë�3.Ŧ>VĖ–S׆xųvŨHü;`õ[bÂGחˆˆˆˆˆ\l*ÅÉŪ}ûØžc'íÛļÆd2ŅĻM+.^‚ËUōBû›Iß1˙… ԟĮ矈ˆpÆŊūâî쯐˜OĢ–-xëõqT‹+9UĢUËælÜ´™ŧü<Īļŧü<6lÜHë–-Üõ÷=3füJ¯ž×ņÂ3OŅ횮|ųõ$æũąāüwÂQ.\˜Í^xųøÔĻ'>ŅÕ1Y|0Y|°WĨjßģđow#+?úŽKđ2{ŌO"""""‹J‘,X¸˜¨ČH’’j�ĐĄm[:Ėæ-[=eōíųĖûc!7ôēžV-šS=1‘#†Ķ¨a}8€Ÿ¯fŗ‹A˜Í%›ÖâĘf8NV¯YëŲļråœN'-›_I~~>sæÎŖ{÷kh×ļ5‘‘\ÕšmÛ´á—é3/LG�&ĀËˋāVŨ‰ņ$áCÂģj fß@"F<EPûŲ0õK|7Î'ØĪ†ŲËËsį-‘‹á ‰ĶédŅâe´mĶ ‡ÃÃá J•0jÕLbŅ’%žrŠŠ{)**"11ÁŗÍÛbáŽ1ŖiØ ūYĪBÚĩXąbĩg۟­ ~Ŋz‘˛kÅÅÖ/YWŊ:ĩ9pā vģŊZ{v&“‹ÅBŽÃ„ÍÚŪį.bĮžFĀŲũį<\Í$Đæƒ—‹Eˇũ‘‹–ákHÖ­ß@Ff“œÂä§”ØˇgĪ^† „ÕĮ‡Ü\÷T+ĢÕįœĪ՞ŕ|ũíd ‹p8‹Yŋa#7ß4€üŖ ĮËã^+1âpė9 ™™YØlļs>wY™�‹Å›Üu‹XõÉĢ4:–ĀÖŨ8¸e-˙ßŪ}GGqŨũ˙ėŽV} ŽŊ 0Ŋ0ÅĶ .`c;ļĶí'ų=ņ!~â$ŽÄŽ;¸WzĮtŊw°čE’(ĸ¨¤Ũũũ!(ōĸ‘āũ:‡#tī;ßÁ9ûŅܙI]1U~ĻB™Ŧ29œ˛Z¯ˆ<��€ĒĘđ@˛qķÕ¯­1ŖF”h/,(Đ_ßũ§vīŲĢŽíÛÉfŗI’ōrËĨĸmëÖúnętÅ<¨üü+’¤61­$IŪ^^’¤į'<ĢđđЛļ đ/÷~ī†É$Y­Vųųø(īÄžķКŽ|Q…Wōuäû÷˜{QVwĸÁ§ÜŦVސ��� Ę24\{÷Čč‘O(ę†ĨX×4mŌX›ˇlSĮöí$ww>zLõëEK’§Ū~÷īęÖĨ‹:węP´ŅmîīŽVÍĻÆiīū•—›Ģ–-šÉëj‰¨&Ģ››223RŧMFFĻLf“ŦVĢëü6,7Y=Ŋ$‹U6‹2ŽnSÜ|o]<~Põė—eņôē>¸Ā.̇—,f^Š��€ĒÉĐ{Hļmß!ģŊPmZĮ”Ú˙PÛ֊;xPiiiōōōR×.ôÃâĨÚ´eĢNÅ'čëīžW||bq@ņņņVBbĸO+3+ĢÔ9Û=ÔFqqq:§íÚˇ{yyŠ{ˇ.šŋp‘ļmߥķŠtčČQŊķĪ÷õŲ_šüØoÅâí#¯ē͔S`—Ũd–ÍËSŽ}kUģđ˛,V9-V9Ėn*”E9vyF5•›ˇ­Âę���\ÉĐ+$7mUÃúõåW­ZŠũ­cZéĢož×–mÛÕ˙‘‡5â‰a2™Lš1kŽōķōĻ_ūüe֒$õíŨSŸ|ūĨŪzû]ŊōâķĨÎŲļuk}ķŨ4šģ[Õ˛Eŗ}ŖG—ˇˇˇfÎžĢ´ô ųUķSLĢæzbčcŽ=đÛpķąÉŋ×pel^ĸ‹'÷Ëä(”ÃáPF^ž2$Éd*z‚˜ÅMn‘-Đi€,>žV���āJĻɓ';SRR4eʔŸ4Ņž¸c Žåĸ˛îÉgSË}NėšŲ*ĖJ—ĶáķŋÖĄ™d’Él–›¯Ÿ,^>åŽ-Ļiũrm ���”פI“zũžmÃojGé,^>å���@Uaø{H����<¸$���� C ���` ����ÃH����†@���Ā0����†qY 1™Lr8wø�ŠŦįÄîpČb!‹��Āx.ûTęåéŽŦŦWMw_pŗX*å9ÉÎΕ‡ģÕč2����×’:aÁĘĘÉUfvŽė‡ĢϭԜrVĒsbw8”™ŖŦœ\Õ 6ē���@nޚČĶÃ]ĸ#”|VŠ/Ën7ūxeи^JsN,ŗ<Ü­j!OwCk���$Š(”4ŦáĘ)ī œ��� tÜŲ ���Ā0����†!����0 ���€a$���� C ���` ����ÃH����†@đv†��IDAT���Ā0����†!����0 ���€a$���� C ���` ����øšr˛ŧü+JH>Ģü+˛Ûޜ���@‹iZ˙žīÃe$/˙ŠŽœH”ÍæŖj6_YĖ\|Šj’ĪĻVČ?:���T~{âŽUČ~\H’ĪĘf푝ˇ—ĢĻ���pŸsŲeŒŧŧ+ōöōtÕt�����. $§Sf“ÉUĶ���x�pŖ����ÃH����†@���Ā0����†!����0 ���€a$���� C ���`7Ŗ x˙Ïĩgīžm6__…‡‡iČ jÔ°žA•]÷ō/~­‡ûôÖān9ÆétjķÖmZŋaŖO'Éaw( @mÚĨ_ß>ōõõųÉu|ûũ4>rLoŊųÆOžË•>ųjĒ<|Ëū–Í›hâ¸Q?y?99šúĶß?Pƒč(==fx‰žŧü|ŊõîŠÕsO)nw8Z˛"VËc×kč ūęŲĩcqßî}ôå÷ŗôÂŗOĒiŖ7íoęėÚģ?Nxíផōķs8ZģqĢļlßĨ‹—Ķä_Ŋš:>ÔFŊēu’Ų\”õ_Ÿüåää–z<6_ũų×%IZ˛rví= ŒĖ,UŗųĒkĮvęŨŊsņ\���÷#É$ÖŌ3O+ū>--Mk×­×ÛīūCøũëĒé˛}­Š]ĢSņņzîŲ§]6§$}ōų—Ú˛uģÚ?ÔV=ģw—ÕęĻ“§âĩ:vvîÜ­ßũöWōĢîįŌ}V&5køkÔĐÁĨöŲlž.Ų‡ˇˇ—F ŦOŋžĒ6­Z¨y“†Å} —ŽÔ•+yC é™úję,efe•úĄžuËæÚ´m—æ,\džõŖåfą÷NNŅÖģõĐĨ†IúaųjÅŽßŦôVdD¸ŽŸŒ×ÂĨ+e2™Ôģ{gIŌsãGËnˇ—Ø.'7OßLŸŖFõëˇ}7sžŽ?ŠÁú*°f ?• EËVÉnˇĢ_Ÿå:_���UAĨ$jܰäo¨ÛÄÄčõß˙A+WÅjŌsĪēl_ ‰.›ëšõ7kËÖízzÜXõčŪĩ¸ŊMëuęÔA˙÷§ˇ5wá"=3ūI—īģ˛đp÷PÃúŅ÷|?-š6Rۘš1wĄęÕ}E^žžŠO<­[vhÜČĄĒvCøŲšgŋ|}ŧõÂ3Oę&˙ĨÔųF<6PūĮ‡Zģa‹úôč"Ščj×Ŧų‹ŦŽەēŨn×ēMÛÔŗkĮâíęՍTō™ŗÚŊī@q ŠW7ōĻmŋš:Kž>ŪūøŖ’¤ÜÜ<:rLO  vmZI’ĸŖę()9E{<H ��÷ĩJHJãînUíÚá:w>ĩ¸­  @sæ-Ôö;•ž‘Ąę~ÕÔąC{=>d,Wģ}äčq͙7_§“’äp8Q;\ÃLÖ×_ßųģ9&IÚ´yĢūøÆ˙Sõę~š1sŽ:ĸėœløĢwĪz¸O¯2×ē*6VuŖ"K„‘kÂBBôģ×~Ĩāā ģ:ŽËiiúōëītčđyyyŠg›įļÛíZ¸x‰ļmÛŠ‹—.) Ā_ôí­^=ē—šöŠ´sī~}3mŽ^ûų  ‘$ŒOÔ??úLƍRĢæM´`É ­^ˇI˙zûˇœgøcę­w?Đŧ–käã5uÖ5iT_ĩnYb\›V͋ƒÁ­ÖT¯n´lõZĩkĶJÕlžÚšgŋN%œÖ¯_~N&“ŠÔíĖfŗūį/ÉĮĮĢDģõę:|æ–ûÛĩ÷€ví= W&=#/OOI’——§ūöæīKŲ‡E–k�€û\Ĩ $’”šzAaa!Åßûũ4íÚŗWãĮŽVTd¤Nœ<Ĩ¯ŋĒ+3r¸ōōōõŪŋū­öíÚęŠņc%§SĢc×ęŸī ŧķWŊúō‹úÛģī)((HcG”¯ˇŪ˙đ#9{N/<?AÕũĒéčąãúęÛīTŗF€ZĮ´ēc9š9JLLŌĀGûßrL:%žŋĶqHŌ'ŸĨsgĪ뗯ūL~ÕũģV;wĪõ+�3fĪŅēu5ūÉ1Ē]Wq‡ięô™˛X,ęŪĩËŨžîŸÄétĒ °°Ô>7‹E&“Im[ĩĐîŊ4sŪúåK‹¯DÄ´hĒV͛H’‚kŠYã›īᏑˇ——F ĸOžú^ŲŲŲēœžŽ—&Žŋi\uŋjeĒŊŸÚšgŋ,YĄ Ԃ%+Ôą]EFÔžå6&“Iĩj”hs8:rė„ĸŖę”ēÍå´t͘ģHŊģuVƒč¨RĮ(7/_ûãé@Ü!ņx™Ž�� ĒĒ4äÆuöéZŊz­Îœ=ĢąŖGH’2ŗ˛´qķV>LíÛ=$ŠčŪ“ä3g´bUŦ†{\/_Rn^Ž:ul§°ĸ 3vôHĩ{¨­ÜÜ,ōđđŲl‘››ĨxiΘ‘#d2›XĢĻ$)88HĢ×ŽĶqËHŌŌŌ‹jŠYŗLĮY–ãČĖĖÔĄC‡5nėh5iÜH’ôä˜QŠģáÆņÜÜ\ÅŽY§GôSįN$IAAŠO8­ÅK—Wx I9{Nŋúũ›ĨöũöÕIŠ“$:XoŊûļíÜŖ+ēœV2L´oŖömcî¸ŋæMĒUķ&Ú{ā †ę_æđQwww Ô__|7CŲŲ9*((Ԑ}īzž…KVęÂĨ˚8ūæøN§ž1WūÕũ4°_ī[ÎņŅįßęøÉxyyyjˈĮÔĻUķģŽ�� *Ёäôé$M˜ôŗm>ŪŪzöéņjÖ´Iņ‡ÃĄčē%ŗ\7˛ŽŽäįëÜšs QpPĻ|ú…zöčŽfMš¨NÚjÔđÖŋq÷ôđĐ˖ëĐá#ĘĖĖ’ĶéTvvļ‚ËTûĩ%=7ËF^?Ö;GzzFQ[Td‰ũDEÕQbb’$)!1I……v5kŌ¤Ä<6Đú •——'ĪĢK‚*B­š?jXŠ}ÁĩŠ˙îWÍĻĮ>ĸKVČîphäãƒnyĶøíäæå)>1IVĢUû~<¨]:ÜryUYÄ´hĒF ĸwø¨F,oīģÚ~Á’ZˇiĢ&>5ZĩjÖ¸Š?vũfŠOÔo^$7ˇ[˙ˇūØŖJĪČÔŅã'õŨŒyĘÍÍģå},���÷ƒJH‚ƒ‚JܸîînUP`ÜnøŸ——'Iōô*ų!ûڇîüŧ|™ÍfũūõßhɲZˇaŖfĪ¯ūúøuîØáĻũÚõÎ{˙’ĶîИŅ#$‹Éĸ÷˙ũq™k¯^ŨO&“IįΝ/Ķø˛Įĩ1Vkɏ§Įõmr¯ŽyûŨȤëÄN§$)==ŖB‰ģÕũļKœnÔ6Ļ…æ,Z*‹Ųĸ–Í—ks.•ÅlÖ/^|V˙ø÷gÚ¸e‡ēvúiÜ[6mĸÃGO¨Eŗ&w|•ĶéÔ´9 ĩ{ßŊ8aœÔĢ{͘ä3gõÃ˛UØŋÂB‚o;_hpBƒƒÔ¸A=yzxhŪĸejßĻ•ÜŨŨīúx���Ē‚JHŦîVEE–žîūO¯ĸ›‡ķrķJ´įæŊãÁĶģ¨ŋZ5›FĻQ#†)9%EËWŦŌ§ŸĨА›öqōÔI%%%ëw¯ũF \kFfĻj•q –—§—ęDÔÖĻÍ[4čŅū˛Z­7Ųąs—ÜŦVÅ´lQĻãđ¸6r˙kLNîõ÷Yx_įų Ī*<<ôĻ}ø—Š~#,^ĢęÕĒŠĐnג•k4¸˙Ũ-Š;|TÛvîŅËĪ=Ĩˆđ0õéŅE –ŽPŗ& å_ÁVž5ąöũxP¯NzĻxYڍ õÍ´9ŠŠŒP¯ŽJ#-=CGŸTËfMäáq=x„…Ģ °P—Ķ2Xļ���UM•y„ODx¸ĖfŗŽ?Qĸũø‰SōōōTp` Î§^Đî^˛ĒņOŽ•ŲlRrJĘõŠ."¨  č&l__īæ;Ą .ĘymP<Ōˇ.^ēŦ‹ßԗ”œŦ¯žų^{öî/ķq\{"Wâé¤âūÂBģ9rũ|Ô“ÕÍM™™ )ūãëã+›ÍVj0Ē “’ĩfã:H#¨Õë6)1)åÎ^•››§iŗ¨}ۘâĮ ?ŌģģĒŲ|5m΂{UvŠļīÚĢ­;vëgĮ—F¤ĸ÷Ŗ\JK͏‘Coš¤,#3KßΘĢũ•h?|F&“Iū÷īûk���*Å’˛đõõQ×.õÃŌe TDíp9rLąkÖĒ_ŋž˛X,ēté’>üč?1l¨Zļl.“LÚ˛mģL&ŗęÕ-ZJãã㭄ÄD%$ž–ŋŋŋŦVĢVŽZŖĮ?Ǥä͚;_͚6Ņ™ŗį”ž‘!ŋjwžYēc‡v:tä¨~X˛L ‰§Õž][yxx(!!AĢc×+44XŖF -ķqÔŦQCŅŅuĩxÉ2֒ÍfĶĘÕą%^ÜįååĨîŨēhūÂE˛ųú(**J/]ŌÔé3ā_]ŋ|õå{탏…ŧü|ŧúHå˙f2™Ô¸A=Ųív}?sžÚÆ´PũĢO™jŲŦąžŸ9O¯ũüY,mßĩWûãiâøŅĨÎ5kūb9 د¸ÍęæĻQCëƒOžŌö]{‹ßåq:9Eyyų’Š–˛Ĩ^ŧ¨c'NI’"ëԖõ6÷rÜIAA-[Ĩ&ę+?˙Jņŧ×DEF(>á´ÖlØĸŽíÚčĖšķ:Sʲžˆđ0E„‡ĒQƒh͞ŋDųyW¨Ä¤d­\ŗAj]iÃ%��€+T™@"IOŽ)/O}ķí4edf( Ā_ĐĀūH’5l  Ī<Ĩe+ViŪ‚E2›- Ņ+/M*žęСwO}ōų—zëíwõʋĪkÂĶã5{Ū|mŪēU‘uęčšgÆëŌå4}<åsũíŨ÷ô֛o”ŠļgŸzRM7Ԛuëõũ´˛;œ ŦYCöSŸž=äááQæã¤ž› /žūVīđ‘<ŊŊÔĢ{7uėØAģwī-3zäpy{{kæėšJKΐ_5?Å´jŽ'†>æŠĶ}W.^ēŦ?˙ļÔ>“ɤŊũG­\ŗAiézeŌĶÅ}ÃПŪų—VÄŽW˙ž=uæÜyí;\ę<֎=ûôĖØōö.ųūõęĒ]›VšŗpŠ7¨'›ÍW3æū „Ž2mØŧ]6o—$MūŨ¯TÃŋzš÷\ęĨĨg(-=Cû~<tS˙[xMĮO%H’ļlßĨ-Ûw•:Ī‹ÆŠIÃúš8n´¯X­%ĢÖ(''Wū~ęŨ­“îÕ­Ü5��TĻɓ';SRR4eʔŸ4Ņž¸c Žu၍´’ĪĻ*Ļi}ŖË���@%°'îØ=ųl8iŌ$…†^ŋēĘÜC���āūC ���` ����ÃH����†@���Ā0����†!����0 ���€a\HL&“N§ĢĻ���đ�pY ņōtWVVŽĢĻ���đ�pY ŠŦŦœ\efįČîp¸jZ����÷17WMäéáŽFŅJH>Ģԋ—eˇJĒĸ=qĮŒ.���—Š(”4ŦáĘ)���ÜĮxĘ����ÃH����†@���Ā0����†!����0 ���€a$���� C ���` ����ÃH����†@���Ā0����†!����0 ���€a$���� C ���` ����ÃH����†@���Ā0����†!����0 ���€a$���� C ���` ����ÃH����†@���Ā0����†!����0 ���€a$���� ãví/į/ĻY���€Pq ŦQŨČ:����<€X˛���Ā0����†!����0 ���€a$���� C ���` ����ÃH����†@���Ā0����†!����0 ���€a$���� C ���` ����ø]����Ēļņ‰úâÛéĘĘΑÃá0ēœ e6›åëã­gĮRtdD™ļŲ/˜.ĨfK…Î{[Ÿ+¸™¤Z>ŌĖQR—H×ĪĪ���”Û‰øDŊ÷ŅgĘČĖzāˆ$9eddęŊ>͉øÄ;Žß/uûL:“U5ˆTTį™Ėĸē7Æģ~~ ���ĘíķoĻ]‚ņL&Éé,Ķš>CĒ"9¤¤ĸCÔđޟš@��€rËĖĘ2ē„ĘÁdRvN·ËŦ€Zî“táü¸ $���€ ”eÉZ•ŧ:rƒ{ąĖŒ@���Ā0����†!����0 ���€a$���� C ���` ����ÃH����ÆÍč���đāz¨uKuj×FaĄÁ˛X,ē|9]{Ä)vũfåäæûëä˙Қ [´|õ:Ģ5–IŌ¸VŌ„ļR‹`ÉËMJʐ‘Ū^/ĨÜđøųcĨHŠÕ‡%įŅLš>RzuąôáÖ -˙–$���0ÄøQÃÔ6Ļ…öė˙QæėPaaĄ"#ÂÕ­s{Å´hĻ÷ūķš23ŗŒ.ŗR0›¤i#¤áͤiûĨˇI™ųEÁ䕎ŌčRŸ/¤ũįn=G×:Ō7OHoo¨<aD"���Ā�ÚÆčĄÖ-5mÎBmŪļŗ¸}Ü!mÛĩGŋye’>ÜKĶæ,4°ĘĘãĨöԈæŌؙŌÔũ×Û•>Ũ)mz^š9ZjúždwŪŧ}ãZŌ‚'Ĩé¤ß­¨¸ē˂@��€ ×ŊK%œN*FŽ9wū‚Ū˙ø KŊpËíŨ, ė×GmZ5—Í×G™YÚž{Ÿ–Ŧˆ•Ãá$EGÕŅ ~}$ŗÉŦä3gĩpéJ8• I2›Íę×ģģZˇjŽ�˙ęJKKWė†ÍÚ¸eĮŊ9čŸāįĨ•ĮK†‘k.äHŋ]V84”.Ųė+-}JÚzZš8¯bęŊ���T(OO……kEėú[ŽIJ9sÛ9F ¤–MkÆŧEJLJVdDm:HîV7Í]´LîVĢ^xæIíÜģ_Ķæ,I&uëÜ^/M¯˙ũĶ;ĘÍÍĶc>ĸÎÚjÆÜE:Ÿ¨FõŖ5lČ�Ų íÚ˛cˇĢģÜB|Ĩz5¤On““V/úÚ=˛d ņu——ÎeIçI…Ž{ZjšH���PĄül6™L&]¸tš\Û{{{Š}›Vš÷ÃríŪ÷Ŗ$éÂÅË ŦĨž];jÁ’•ō÷÷“§§‡vėŪ§sį‹Ž´Ė^°Dģ÷ũ¨ÂBģ<=<ÔĩS;­Œ]¯íģöJ’6^ŧ¤ÚáĄęÛŗ[Ĩ $a~E_ãĶn=&ˇP:›%…UģŪæf–f–Z‡Jŋ^*eÜÛ:ˋĮū�� B9Ut“ƒŨn/×öá!Á2›ÍŠO<]ĸ=1)YîîîĒUŗ†Î§^ÔšÔ zzĖpõíŲUáa!r8:~2^ –›ÅĸCG—˜ã؉SĒU3@îîîå;¸{āę 4YīđÉŨl’7Ü?Ō4PĒé]tü_–Ú‡ßģ ސ��� BĨgdĘét*°frmīéé!IĘËË/Ņž—Ĩ¨ßÃ]N§Sī}ô™úôčĒNíÚjp˙žēœ–ŽEËViĮî}ÅsŧúÂŗ’ķú§x“É$IĒfķՅ‹—ĘUŸĢN/úåë1^nR ”˜~Ŋ-ū˛ÔíSéŠ]j$Í-ĩūˇ”šsoëŊ[���T¨üü+:|FíÛÆhųęu*,åJIĢæMTXh׏‡ŽÜԗ{5ˆ\ ×xzx\íĪ“$eeįhūâåšŋxš‚kŠW÷Î?j˜ÎžK-3ßL›­”37?+7--ũĻ6Ŗ¤æHqįĨQ-¤?¯“Jyˆ–úÔ+úēúÄõļôüĸĨ\’4jē´÷iú(éá/K—QX˛��€ ˇfÃfųW÷Sŋž=oę  Ôč'†¨yĶFĨn›œrV‡Cu##J´GÕŠ­Üŧ<Ĩ^¸¤ūÕÕŧÉõíĪžOÕô9 åp8¨ä”ŗ*,,”¯¯ÎĨ^(ū““ŖĖėėRC’‘ūšIj$ŊØūæž^Ō;ũ¤=gJ’É’ÆĖzD-ßĒL¸B��€ ˇsĪ~ՏŽŌ#ŊēŠvXˆví= ü+WĒnÚëėųķš÷Ã˛RˇÍÉÍՖģõp¯nJŊpII)gT?:JŨ:ˇ×ęĩåp8äī_]ĮŌ‚%+ôãĄ#r:‹Ū īt:u*á´ōōķĩiÛN=úp/egį(!1IūÕ5lđ�ĨĨgč?_~WÁgäöžØ%u’ū=¨č‡ķIYųE!啎Eoqō]éWOŽY}Rz3VšÜ[ږ$Í‰Ģ¨ęo@���CL›Ŋ@GŽP׎íôв˜Íēp鲖ĮŽĶēMÛTPpëĮB͚ŋXyųų9tlž>ēœ–ŽeĢÖjåš ’¤ã'ãõŨĖyęŨ­ŗ}¤ˇv‡Îœ;¯OŋžĻÔ %Is-+~üo5›¯22ŗtāāa-ZēĒBŽ˙n8%=5[ZvTzū!iĘÉĶ­čū’¤ŋŽ+ÛŊ!˙ˇVę\Gúr¨wN:|ëWŊTĶäɓ)))š2eŠŅĩ��� Šyåĩ7Œ.ĄRųāooŪļßôŋTČ=äüĶOÛ~Ō¤I -ūž{H����†@���Ā0����†!����0 ���€a$���� C ���` ����ÃH����0™LwSuÜKn÷ =H���Pn6__Éé4ē ã9ōõņšã° ›¤ĒzēœRÍ;â]#��� Ü&Œ%•áĘĀ}Īd*:w0kdÕ=]&SQũŽF ��@šEGFč/M”ÍסLK–î7&“I6__ũâĨ‰ŠŽŒ¸ãø.‘Ōú‰R°íŪ,ēÜĖEõޟXTŋËįwũ”���xDGFčĪoŧftUF—HéĖëFWQyT‘\���ā~D ���` ����ÃH����†@���Ā0����†!����0 ���€a$���� C ���` ����ÇÃad����îsĨeŗ$Y­V<x°Â ���đā8xđ ÜŨŨK´™%ÉÛÛ[ .”Ķé4¤0����÷7§ĶŠųķįËÛÛģDģĨG“ŨŨŨuūüyíßŋ_5jԐÍf“›››AĨ���¸_äååéčŅŖúôĶOuūüyųųų•č7Mž<šø˛HffϞŗŗšŸ���€ËX,ųøøČ××÷Ļž—Al6›l6[…���āÁÆc���æ˙LŽAšFE����IENDŽB`‚�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/���������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14156463140�0014151�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/Makefile�������������������������������������������������������������������������0000664�0000000�0000000�00000005701�14156463140�0015614�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # Glewlwyd SSO Server # # Makefile used to build the software # # Copyright 2016-2021 Nicolas Mora <mail@babelouest.org> # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU GENERAL PUBLIC LICENSE # License as published by the Free Software Foundation; # version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this program. If not, see <http://www.gnu.org/licenses/>. # CC=gcc CFLAGS=-c -Wall -Werror -Wextra -D_REENTRANT $(shell pkg-config --cflags liborcania) $(shell pkg-config --cflags libyder) $(shell pkg-config --cflags libulfius) $(shell pkg-config --cflags jansson) $(shell pkg-config --cflags libhoel) $(shell pkg-config --cflags gnutls) $(shell pkg-config --cflags libconfig) $(shell pkg-config --cflags nettle) $(shell pkg-config --cflags hogweed) $(ADDITIONALFLAGS) LIBS=$(shell pkg-config --libs liborcania) $(shell pkg-config --libs libyder) $(shell pkg-config --libs libulfius) $(shell pkg-config --libs libhoel) $(shell pkg-config --libs jansson) $(shell pkg-config --libs gnutls) $(shell pkg-config --libs libconfig) $(shell pkg-config --libs nettle) $(shell pkg-config --libs hogweed) -ldl -lpthread -lcrypt -lz OBJECTS=glewlwyd.o misc.o webservice.o session.o user.o scope.o plugin.o client.o module.o api_key.o metrics.o static_compressed_inmemory_website_callback.o http_compression_callback.o DESTDIR=/usr/local CONFIG_FILE=../glewlwyd.conf all: release clean: rm -f *.o glewlwyd valgrind.txt cd user && $(MAKE) clean cd user_middleware && $(MAKE) clean cd client && $(MAKE) clean cd scheme && $(MAKE) clean cd plugin && $(MAKE) clean debug: ADDITIONALFLAGS=-Wunreachable-code -DDEBUG -g -O0 debug: glewlwyd cd user && $(MAKE) debug cd user_middleware && $(MAKE) debug cd client && $(MAKE) debug cd scheme && $(MAKE) debug cd plugin && $(MAKE) debug release: ADDITIONALFLAGS=-O3 release: glewlwyd cd user && $(MAKE) cd user_middleware && $(MAKE) cd client && $(MAKE) cd scheme && $(MAKE) cd plugin && $(MAKE) %.o: %.c glewlwyd.h glewlwyd-common.h static_file_callback.h http_compression_callback.h $(CC) $(CFLAGS) $(CPPFLAGS) $< glewlwyd: $(OBJECTS) $(CC) -o $@ $^ $(LIBS) $(LDFLAGS) memcheck: debug valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --track-origins=yes ./glewlwyd --config-file $(CONFIG_FILE) 2>valgrind.txt test-debug: debug ./glewlwyd --config-file $(CONFIG_FILE) install: glewlwyd install glewlwyd $(DESTDIR)/bin cd user && $(MAKE) install DESTDIR=$(DESTDIR) cd user_middleware && $(MAKE) install DESTDIR=$(DESTDIR) cd client && $(MAKE) install DESTDIR=$(DESTDIR) cd scheme && $(MAKE) install DESTDIR=$(DESTDIR) cd plugin && $(MAKE) install DESTDIR=$(DESTDIR) ���������������������������������������������������������������glewlwyd-2.6.1/src/api_key.c������������������������������������������������������������������������0000664�0000000�0000000�00000017504�14156463140�0015745�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * API key functions definition * * Copyright 2016-2021 Nicolas Mora <mail@babelouest.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see <http://www.gnu.org/licenses/>. * */ #include "glewlwyd.h" int verify_api_key(struct config_elements * config, const char * token) { json_t * j_query, * j_result = NULL; int res, ret; char * token_hash = NULL, * tmp; if (o_strlen(token) == GLEWLWYD_API_KEY_LENGTH) { token_hash = generate_hash(config->hash_algorithm, token); tmp = str_replace(token_hash, "/", "_"); o_free(token_hash); token_hash = str_replace(tmp, "+", "-"); o_free(tmp); j_query = json_pack("{sss[ss]s{ss?si}}", "table", GLEWLWYD_TABLE_API_KEY, "columns", "gak_id", "gak_counter", "where", "gak_token_hash", token_hash, "gak_enabled", 1); o_free(token_hash); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_query = json_pack("{sss{sI}s{sO}}", "table", GLEWLWYD_TABLE_API_KEY, "set", "gak_counter", json_integer_value(json_object_get(json_array_get(j_result, 0), "gak_counter"))+1, "where", "gak_id", json_object_get(json_array_get(j_result, 0), "gak_id")); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "verify_api_key - Error executing j_query (2)"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { ret = G_ERROR_UNAUTHORIZED; } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "verify_api_key - Error executing j_query (1)"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { ret = G_ERROR_UNAUTHORIZED; } return ret; } json_t * get_api_key_list(struct config_elements * config, const char * pattern, size_t offset, size_t limit) { json_t * j_query, * j_result, * j_return, * j_element; int res; size_t index; char * pattern_escaped, * pattern_clause; j_query = json_pack("{sss[sssssss]siss}", "table", GLEWLWYD_TABLE_API_KEY, "columns", "gak_token_hash AS token_hash", "gak_counter AS counter", "gak_username AS username", SWITCH_DB_TYPE(config->conn->type, "UNIX_TIMESTAMP(gak_issued_at) AS issued_at", "strftime('%s', gak_issued_at) AS issued_at", "EXTRACT(EPOCH FROM gak_issued_at)::integer AS issued_at"), "gak_issued_for AS issued_for", "gak_user_agent AS user_agent", "gak_enabled", "offset", offset, "order_by", "gak_issued_at"); if (limit) { json_object_set_new(j_query, "limit", json_integer(limit)); } if (o_strlen(pattern)) { pattern_escaped = h_escape_string_with_quotes(config->conn, pattern); pattern_clause = msprintf("IN (SELECT gak_id FROM " GLEWLWYD_TABLE_API_KEY " WHERE gak_username LIKE '%%'||%s||'%%' OR gak_issued_for LIKE '%%'||%s||'%%' OR gak_user_agent LIKE '%%'||%s||'%%')", pattern_escaped, pattern_escaped, pattern_escaped); json_object_set_new(j_query, "where", json_pack("{s{ssss}}", "gak_id", "operator", "raw", "value", pattern_clause)); o_free(pattern_escaped); o_free(pattern_clause); } res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result, index, j_element) { json_object_set(j_element, "enabled", json_integer_value(json_object_get(j_element, "gak_enabled"))?json_true():json_false()); json_object_del(j_element, "gak_enabled"); } j_return = json_pack("{siso}", "result", G_OK, "api_key", j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_api_key_list - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } json_t * generate_api_key(struct config_elements * config, const char * username, const char * issued_for, const char * user_agent) { json_t * j_query, * j_return; int res; char token[GLEWLWYD_API_KEY_LENGTH+1] = {0}, * token_hash, * tmp; rand_string(token, GLEWLWYD_API_KEY_LENGTH); token_hash = generate_hash(config->hash_algorithm, token); tmp = str_replace(token_hash, "/", "_"); o_free(token_hash); token_hash = str_replace(tmp, "+", "-"); o_free(tmp); if (token_hash != NULL) { j_query = json_pack("{sss{ssssssss?}}", "table", GLEWLWYD_TABLE_API_KEY, "values", "gak_token_hash", token_hash, "gak_username", username, "gak_issued_for", issued_for, "gak_user_agent", user_agent); res = h_insert(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_return = json_pack("{sis{ss}}", "result", G_OK, "api_key", "key", token); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_api_key - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_api_key - Error generate_hash"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(token_hash); return j_return; } int disable_api_key(struct config_elements * config, const char * token_hash) { json_t * j_query; int res, ret; j_query = json_pack("{sss{si}s{sssi}}", "table", GLEWLWYD_TABLE_API_KEY, "set", "gak_enabled", 0, "where", "gak_token_hash", token_hash, "gak_enabled", 1); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "disable_api_key - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/client.c�������������������������������������������������������������������������0000664�0000000�0000000�00000051143�14156463140�0015577�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * client management functions definition * * Copyright 2016-2021 Nicolas Mora <mail@babelouest.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see <http://www.gnu.org/licenses/>. * */ #include "glewlwyd.h" json_t * auth_check_client_credentials(struct config_elements * config, const char * client_id, const char * password) { int res; json_t * j_return = NULL, * j_module_list = get_client_module_list(config), * j_module, * j_client; struct _client_module_instance * client_module; size_t index; if (check_result_value(j_module_list, G_OK)) { json_array_foreach(json_object_get(j_module_list, "module"), index, j_module) { if (j_return == NULL) { client_module = get_client_module_instance(config, json_string_value(json_object_get(j_module, "name"))); if (client_module != NULL) { if (client_module->enabled) { j_client = client_module->module->client_module_get(config->config_m, client_id, client_module->cls); if (check_result_value(j_client, G_OK)) { res = client_module->module->client_module_check_password(config->config_m, client_id, password, client_module->cls); if (res == G_OK) { j_return = json_pack("{si}", "result", G_OK); } else if (res == G_ERROR_UNAUTHORIZED) { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else if (res != G_ERROR_NOT_FOUND) { y_log_message(Y_LOG_LEVEL_ERROR, "auth_check_client_credentials - Error, client_module_check_password for module '%s', skip", client_module->name); } } else if (!check_result_value(j_client, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_ERROR, "auth_check_client_credentials - Error, client_module_get for module '%s', skip", client_module->name); } json_decref(j_client); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "auth_check_client_credentials - Error, client_module_instance %s is NULL", json_string_value(json_object_get(j_module, "name"))); } } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "auth_check_client_credentials - Error get_client_module_list"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_module_list); if (j_return == NULL) { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } return j_return; } json_t * get_client(struct config_elements * config, const char * client_id, const char * source) { int found = 0; json_t * j_return = NULL, * j_client, * j_module_list, * j_module; struct _client_module_instance * client_module; size_t index; if (!o_strlen(client_id)) { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } else if (source != NULL) { client_module = get_client_module_instance(config, source); if (client_module != NULL) { j_client = client_module->module->client_module_get(config->config_m, client_id, client_module->cls); if (check_result_value(j_client, G_OK)) { json_object_set_new(json_object_get(j_client, "client"), "source", json_string(source)); j_return = json_incref(j_client); } else if (check_result_value(j_client, G_ERROR_NOT_FOUND)) { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client - Error, client_module_get for module %s", client_module->name); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_client); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } else { j_module_list = get_client_module_list(config); if (check_result_value(j_module_list, G_OK)) { json_array_foreach(json_object_get(j_module_list, "module"), index, j_module) { if (!found) { client_module = get_client_module_instance(config, json_string_value(json_object_get(j_module, "name"))); if (client_module != NULL) { if (client_module->enabled) { j_client = client_module->module->client_module_get(config->config_m, client_id, client_module->cls); if (check_result_value(j_client, G_OK)) { json_object_set_new(json_object_get(j_client, "client"), "source", json_string(client_module->name)); j_return = json_incref(j_client); found = 1; } else if (!check_result_value(j_client, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_ERROR, "get_client - Error, client_module_get for module %s", client_module->name); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_client); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client - Error, client_module_instance %s is NULL", json_string_value(json_object_get(j_module, "name"))); } } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client - Error get_client_module_list"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_module_list); } if (j_return == NULL) { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } return j_return; } json_t * get_client_list(struct config_elements * config, const char * pattern, size_t offset, size_t limit, const char * source) { json_t * j_return, * j_module_list, * j_module, * j_list_parsed, * j_element; struct _client_module_instance * client_module; int result; size_t cur_offset, cur_limit, count_total, index, index_c; if (source != NULL) { client_module = get_client_module_instance(config, source); if (client_module != NULL && client_module->enabled) { result = G_ERROR; j_list_parsed = client_module->module->client_module_get_list(config->config_m, pattern, offset, limit, client_module->cls); if (check_result_value(j_list_parsed, G_OK)) { json_array_foreach(json_object_get(j_list_parsed, "list"), index, j_element) { json_object_set_new(j_element, "source", json_string(client_module->name)); } j_return = json_pack("{sisO}", "result", G_OK, "client", json_object_get(j_list_parsed, "list")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_list - Error client_module_get_list"); j_return = json_pack("{si}", "result", result); } json_decref(j_list_parsed); } else if (client_module != NULL && !client_module->enabled) { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_list - Error get_client_module_instance"); j_return = json_pack("{si}", "result", G_ERROR); } } else { j_module_list = get_client_module_list(config); if (check_result_value(j_module_list, G_OK)) { cur_offset = offset; cur_limit = limit; j_return = json_pack("{sis[]}", "result", G_OK, "client"); if (j_return != NULL) { json_array_foreach(json_object_get(j_module_list, "module"), index, j_module) { if (cur_limit) { client_module = get_client_module_instance(config, json_string_value(json_object_get(j_module, "name"))); if (client_module != NULL && client_module->enabled) { result = G_ERROR; if ((count_total = client_module->module->client_module_count_total(config->config_m, pattern, client_module->cls)) > cur_offset && cur_limit) { j_list_parsed = client_module->module->client_module_get_list(config->config_m, pattern, cur_offset, cur_limit, client_module->cls); if (check_result_value(j_list_parsed, G_OK)) { json_array_foreach(json_object_get(j_list_parsed, "list"), index_c, j_element) { json_object_set_new(j_element, "source", json_string(client_module->name)); } cur_offset = 0; if (cur_limit > json_array_size(json_object_get(j_list_parsed, "list"))) { cur_limit -= json_array_size(json_object_get(j_list_parsed, "list")); } else { cur_limit = 0; } json_array_extend(json_object_get(j_return, "client"), json_object_get(j_list_parsed, "list")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_list - Error client_module_get_list for module %s", json_string_value(json_object_get(j_module, "name"))); } json_decref(j_list_parsed); } else { cur_offset -= count_total; } } else if (client_module == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_list - Error, client_module_instance %s is NULL", json_string_value(json_object_get(j_module, "name"))); } } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_list - Error allocating resources for j_return"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_list - Error get_client_module_list"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_module_list); } return j_return; } json_t * is_client_valid(struct config_elements * config, const char * client_id, json_t * j_client, int add, const char * source) { int found = 0; json_t * j_return = NULL, * j_error_list, * j_module_list, * j_module; struct _client_module_instance * client_module; size_t index; if (source != NULL) { client_module = get_client_module_instance(config, source); if (client_module != NULL && client_module->enabled && !client_module->readonly) { j_error_list = client_module->module->client_module_is_valid(config->config_m, client_id, j_client, add?GLEWLWYD_IS_VALID_MODE_ADD:GLEWLWYD_IS_VALID_MODE_UPDATE, client_module->cls); if (check_result_value(j_error_list, G_ERROR_PARAM) || check_result_value(j_error_list, G_OK)) { j_return = json_incref(j_error_list); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_client_valid - Error, client_module_is_valid for module %s", client_module->name); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_error_list); } else if (client_module != NULL && client_module->readonly) { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "module is read-only"); } else if (client_module != NULL && !client_module->enabled) { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "module is unavailable"); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_client_valid - Error get_client_module_instance"); j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } else { j_module_list = get_client_module_list(config); if (check_result_value(j_module_list, G_OK)) { json_array_foreach(json_object_get(j_module_list, "module"), index, j_module) { if (!found) { client_module = get_client_module_instance(config, json_string_value(json_object_get(j_module, "name"))); if (client_module != NULL && client_module->enabled && !client_module->readonly) { found = 1; j_error_list = client_module->module->client_module_is_valid(config->config_m, client_id, j_client, add?GLEWLWYD_IS_VALID_MODE_ADD:GLEWLWYD_IS_VALID_MODE_UPDATE, client_module->cls); if (check_result_value(j_error_list, G_ERROR_PARAM) || check_result_value(j_error_list, G_OK)) { j_return = json_incref(j_error_list); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_client_valid - Error, client_module_is_valid for module %s", client_module->name); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_error_list); } else if (client_module == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "is_client_valid - Error, client_module_instance %s is NULL", json_string_value(json_object_get(j_module, "name"))); } } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_client_valid - Error get_client_module_list"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_module_list); if (j_return == NULL) { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } } return j_return; } int add_client(struct config_elements * config, json_t * j_client, const char * source) { int found = 0, result, ret; json_t * j_module_list, * j_module; struct _client_module_instance * client_module; size_t index; if (source != NULL) { client_module = get_client_module_instance(config, source); if (client_module != NULL && client_module->enabled && !client_module->readonly) { result = client_module->module->client_module_add(config->config_m, j_client, client_module->cls); if (result == G_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_client - Error client_module_add"); ret = result; } } else if (client_module != NULL && (client_module->readonly || !client_module->enabled)) { y_log_message(Y_LOG_LEVEL_ERROR, "add_client - Error module %s", source); ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_client - Error get_client_module_instance"); ret = G_ERROR; } } else { j_module_list = get_client_module_list(config); if (check_result_value(j_module_list, G_OK)) { json_array_foreach(json_object_get(j_module_list, "module"), index, j_module) { if (!found) { client_module = get_client_module_instance(config, json_string_value(json_object_get(j_module, "name"))); if (client_module != NULL && client_module->enabled && !client_module->readonly) { found = 1; result = client_module->module->client_module_add(config->config_m, j_client, client_module->cls); if (result == G_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_client - Error client_module_add"); ret = result; } } else if (client_module == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "add_client - Error, client_module_instance %s is NULL", json_string_value(json_object_get(j_module, "name"))); } } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_client - Error get_client_module_list"); ret = G_ERROR; } json_decref(j_module_list); if (!found) { y_log_message(Y_LOG_LEVEL_ERROR, "add_client - Error no module in write mode available"); ret = G_ERROR; } } return ret; } int set_client(struct config_elements * config, const char * client_id, json_t * j_client, const char * source) { int found = 0, ret, result; struct _client_module_instance * client_module; json_t * j_cur_client, * j_module_list, * j_module; size_t index; if (source != NULL) { client_module = get_client_module_instance(config, source); if (client_module != NULL && client_module->enabled && !client_module->readonly) { j_cur_client = client_module->module->client_module_get(config->config_m, client_id, client_module->cls); if (check_result_value(j_cur_client, G_OK)) { ret = client_module->module->client_module_update(config->config_m, client_id, j_client, client_module->cls); if (ret != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "set_client - Error client_module_update"); } } else if (check_result_value(j_cur_client, G_ERROR_NOT_FOUND)) { ret = G_ERROR_NOT_FOUND; } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_client - Error client_module_get"); ret = G_ERROR; } json_decref(j_cur_client); } else if (client_module != NULL && (client_module->readonly || !client_module->enabled)) { ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_client - Error get_client_module_instance"); ret = G_ERROR; } } else { j_module_list = get_client_module_list(config); if (check_result_value(j_module_list, G_OK)) { json_array_foreach(json_object_get(j_module_list, "module"), index, j_module) { if (!found) { client_module = get_client_module_instance(config, json_string_value(json_object_get(j_module, "name"))); if (client_module != NULL && client_module->enabled && !client_module->readonly) { found = 1; result = client_module->module->client_module_update(config->config_m, client_id, j_client, client_module->cls); if (result == G_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_client - Error client_module_update"); ret = result; } } else if (client_module == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "set_client - Error, client_module_instance %s is NULL", json_string_value(json_object_get(j_module, "name"))); } } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_client - Error get_client_module_list"); ret = G_ERROR; } json_decref(j_module_list); if (!found) { y_log_message(Y_LOG_LEVEL_ERROR, "set_client - Error no module in write mode available"); ret = G_ERROR; } } return ret; } int delete_client(struct config_elements * config, const char * client_id, const char * source) { int found = 0, ret, result; struct _client_module_instance * client_module; json_t * j_client, * j_module_list, * j_module; size_t index; if (source != NULL) { client_module = get_client_module_instance(config, source); if (client_module != NULL && client_module->enabled && !client_module->readonly) { j_client = client_module->module->client_module_get(config->config_m, client_id, client_module->cls); if (check_result_value(j_client, G_OK)) { result = client_module->module->client_module_delete(config->config_m, client_id, client_module->cls); if (result == G_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_client - Error client_module_delete"); ret = result; } } else if (check_result_value(j_client, G_ERROR_NOT_FOUND)) { ret = G_ERROR_NOT_FOUND; } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_client - Error client_module_get"); ret = G_ERROR; } json_decref(j_client); } else if (client_module != NULL && (client_module->readonly || !client_module->enabled)) { ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_client - Error get_client_module_instance"); ret = G_ERROR; } } else { j_module_list = get_client_module_list(config); if (check_result_value(j_module_list, G_OK)) { json_array_foreach(json_object_get(j_module_list, "module"), index, j_module) { if (!found) { client_module = get_client_module_instance(config, json_string_value(json_object_get(j_module, "name"))); if (client_module != NULL && client_module->enabled && !client_module->readonly) { found = 1; result = client_module->module->client_module_delete(config->config_m, client_id, client_module->cls); if (result == G_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_client - Error client_module_delete"); ret = result; } } else if (client_module == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "delete_client - Error, client_module_instance %s is NULL", json_string_value(json_object_get(j_module, "name"))); } } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_client - Error get_client_module_list"); ret = G_ERROR; } json_decref(j_module_list); if (!found) { y_log_message(Y_LOG_LEVEL_ERROR, "delete_client - Error no module in write mode available"); ret = G_ERROR; } } return ret; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/client/��������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14156463140�0015427�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/client/Makefile������������������������������������������������������������������0000664�0000000�0000000�00000004113�14156463140�0017066�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # Glewlwyd client backend # # Makefile used to build the software # # Copyright 2016-2019 Nicolas Mora <mail@babelouest.org> # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; # version 2.1 of the License. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this library. If not, see <http://www.gnu.org/licenses/>. # GLWD_SRC=.. DESTDIR=/usr/local MODULES_TARGET=$(DESTDIR)/lib/glewlwyd/client CC=gcc CFLAGS=-c -fPIC -Wall -Werror -Wextra -Wno-unknown-pragmas -D_REENTRANT -Wno-pragmas -I$(GLWD_SRC) $(shell pkg-config --cflags liborcania) $(shell pkg-config --cflags libyder) $(shell pkg-config --cflags jansson) $(shell pkg-config --cflags libhoel) $(ADDITIONALFLAGS) LIBS=$(shell pkg-config --libs liborcania) $(shell pkg-config --libs libyder) $(shell pkg-config --libs jansson) -ldl TARGET=libmoddatabase.so libmodldap.so all: release %.o: %.c ../glewlwyd-common.h $(CC) $(CFLAGS) $(CPPFLAGS) $< misc.o: $(GLWD_SRC)/misc.c $(GLWD_SRC)/glewlwyd-common.h $(CC) $(CFLAGS) $(CPPFLAGS) $(GLWD_SRC)/misc.c libmodmock.so: $(GLWD_SRC)/glewlwyd-common.h mock.o misc.o $(CC) -shared -Wl,-soname,libmodmock.so -o libmodmock.so mock.o misc.o $(LIBS) libmoddatabase.so: $(GLWD_SRC)/glewlwyd-common.h database.o misc.o $(CC) -shared -Wl,-soname,libmoddatabase.so -o libmoddatabase.so database.o misc.o $(LIBS) $(shell pkg-config --libs libhoel) libmodldap.so: $(GLWD_SRC)/glewlwyd-common.h ldap.o misc.o $(CC) -shared -Wl,-soname,libmodldap.so -o libmodldap.so ldap.o misc.o $(LIBS) -lldap -lcrypt clean: rm -f *.o *.so debug: ADDITIONALFLAGS=-DDEBUG -g -O0 debug: libmodmock.so $(TARGET) release: ADDITIONALFLAGS=-O3 release: $(TARGET) install: mkdir -p $(MODULES_TARGET) cp -f *.so $(MODULES_TARGET) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/client/README.md�����������������������������������������������������������������0000664�0000000�0000000�00000033444�14156463140�0016716�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Glewlwyd Client Backend Modules A Glewlwyd module is built as a library and loaded at startup. It must contain a specific set of functions available to glewlwyd to work properly. A Glewlwyd module can access the entire data and functions available to Glewlwyd service. There is no limitation to its access. Therefore, Glewlwyd modules must be carefully designed and considered friendly. All data returned as `json_t *` or `char *` must be dynamically allocated, because they will be cleaned up by Glewlwyd after use. Currently, the following client backend modules are available: - [Database backend](database.c) - [LDAP backend](ldap.c) A client backend module is used to manage clients in a specific backend environment. It is intended to: - list clients - get a specific client - get a client profile data - add a new client - update a client - delete a client - verify a client password A client is defined by attributes. The following attributes are mandatory for every client: ```javascript { "client_id": string, identifies the client, must be unique "enabled": boolean, set this value to false will make the client unable to authenticate or do anything in Glewlwyd "confidential": boolean, in OAuth2 and OIDC plugins, confidential clients can be authorized } ``` Other attributes can be added to a client, depending on the backend and the configuration. Any other attribute can be either a string or an array of strings. If another type is returned by the module, the behaviour is undefined. Typically, the OAuth2 and OIDC plugins require the following attributes: ```javascript { "authorization_type": array of strings, the following values are used: "code", "token", "id_token", "password", "client_credential", "refresh_token" "redirect_uri": array of strings, list of redirect uris allowed for this client } ``` Other attributes are commonly used, example: ```javascript { "name": string, display name for the client "description": string, description of the client "scope": array of strings, list of scopes available for the response type "client_credential" } ``` A Glewlwyd module requires the library [Jansson](https://github.com/akheron/Jansson). You can check out the existing modules for inspiration. You can start from the fake module [mock.c](mock.c) to build your own. A pointer of `struct config_module` is passed to all the mandatory functions. This pointer gives access to some Glewlwyd data and some callback functions used to achieve specific actions. The definition of the structure is the following: ```C struct config_module { /* External url to access to the Glewlwyd instance */ const char * external_url; /* relative url to access to the login page */ const char * login_url; /* value of the admin scope */ const char * admin_scope; /* Value of the profile scope */ const char * profile_scope; /* connection to the database via hoel library */ struct _h_connection * conn; /* Digest agorithm defined in the configuration file */ digest_algorithm hash_algorithm; /* General configuration of the Glewlwyd instance */ struct config_elements * glewlwyd_config; /* Callback function to retrieve a specific user */ json_t * (* glewlwyd_module_callback_get_user)(struct config_module * config, const char * username); /* Callback function to update a specific user */ int (* glewlwyd_module_callback_set_user)(struct config_module * config, const char * username, json_t * j_user); /* Callback function to validate a user password */ int (* glewlwyd_module_callback_check_user_password)(struct config_module * config, const char * username, const char * password); /* Callback function to validate a session */ json_t * (* glewlwyd_module_callback_check_user_session)(struct config_module * config, const struct _u_request * request, const char * username); }; ``` A client module must have the following functions defined and available: ```C /** * * client_module_load * * Executed once when Glewlwyd service is started * Used to identify the module and to show its parameters on init * You can also use it to load resources that are required once for all * instance modules for example * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * name: string, mandatory, name of the module, must be unique among other scheme modules * display_name: string, optional, long name of the module * description: string, optional, description for the module * parameters: object, optional, parameters description for the module * } * * Example: * { * result: G_OK, * name: "mock", * display_name: "Mock scheme module", * description: "Mock scheme module for glewlwyd tests", * parameters: { * mock-value: { * type: "string", * mandatory: true * } * } * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ json_t * client_module_load(struct config_module * config); ``` ```C /** * * client_module_unload * * Executed once when Glewlwyd service is stopped * You can use it to release resources that are required once for all * instance modules for example * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ int client_module_unload(struct config_module * config); ``` ```C /** * * client_module_init * * Initialize an instance of this module declared in Glewlwyd service. * If required, you must dynamically allocate a pointer to the configuration * for this instance and pass it to *cls * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, G_ERROR_PARAM on input parameters error, another value on error) * error: array of strings containg the list of input errors, mandatory on result G_ERROR_PARAM, ignored otherwise * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_parameters: used to initialize an instance in JSON format * The module must validate itself its parameters * @parameter cls: will contain an allocated void * pointer that will be sent back * as void * in all module functions * */ json_t * client_module_init(struct config_module * config, int readonly, json_t * j_parameters, void ** cls); ``` ```C /** * * client_module_close * * Close an instance of this module declared in Glewlwyd service. * You must free the memory previously allocated in * the client_module_init function as void * cls * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter cls: pointer to the void * cls value allocated in client_module_init * */ int client_module_close(struct config_module * config, void * cls); ``` ```C /** * * client_module_count_total * * Return the total number of clients handled by this module corresponding * to the given pattern * * @return value: The total of corresponding clients * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter pattern: The pattern to match for the clients. How the * pattern is used is up to the implementation. * Glewlwyd recommends to match the pattern with the * client_id, name and description value for each clients * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ size_t client_module_count_total(struct config_module * config, const char * pattern, void * cls); ``` ```C /** * * client_module_get_list * * Return a list of clients handled by this module corresponding * to the given pattern between the specified offset and limit * These are the client objects returned to the administrator * * @return value: A list of corresponding clients or an empty list * using the following JSON format: {"result":G_OK,"list":[{client object}]} * On error, this function must return another value for "result" * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter pattern: The pattern to match for the clients. How the * pattern is used is up to the implementation. * Glewlwyd recommends to match the pattern with the * client_id, name and description value for each clients * @pattern offset: The offset to reduce the returned list among the total list * @pattern limit: The maximum number of clients to return * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ json_t * client_module_get_list(struct config_module * config, const char * pattern, size_t offset, size_t limit, void * cls); ``` ```C /** * * client_module_get * * Return a client object handled by this module corresponding * to the client_id specified * * @return value: G_OK and the corresponding client * G_ERROR_NOT_FOUND if client_id is not found * The returned format is {"result":G_OK,"client":{client object}} * On error, this function must return another value for "result" * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter client_id: the client_id to match, must be case insensitive * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ json_t * client_module_get(struct config_module * config, const char * client_id, void * cls); ``` ```C /** * * client_module_is_valid * * Validate if a client is valid to save for the specified mode * * @return value: G_OK if the client is valid * G_ERROR_PARAM and an array containing the errors in string format * The returned format is {"result":G_OK} on success * {"result":G_ERROR_PARAM,"error":["error 1","error 2"]} on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter client_id: the client_id to match, must be case insensitive * @parameter j_client: The client to validate * @parameter mode: The mode corresponding to the context, values available are: * - GLEWLWYD_IS_VALID_MODE_ADD: Add a client by an administrator * Note: in this mode, the module musn't check for already existing client, * This is already handled by Glewlwyd * - GLEWLWYD_IS_VALID_MODE_UPDATE: Update a client by an administrator * @parameter cls: pointer to the void * cls value allocated in client_module_init * */ json_t * client_module_is_valid(struct config_module * config, const char * client_id, json_t * j_client, int mode, void * cls); ``` ```C /** * * client_module_add * * Add a new client by an administrator * * @return value: G_OK on success * Another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_client: The client to add * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int client_module_add(struct config_module * config, json_t * j_client, void * cls); ``` ```C /** * * client_module_update * * Update an existing client by an administrator * * @return value: G_OK on success * Another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter client_id: the client_id to match, must be case insensitive * @parameter j_client: The client to update. If this function must replace all values or * only the given ones or any other solution is up to the implementation * @parameter cls: pointer to the void * cls value allocated in client_module_init * */ int client_module_update(struct config_module * config, const char * client_id, json_t * j_client, void * cls); ``` ```C /** * * client_module_delete * * Delete an existing client by an administrator * * @return value: G_OK on success * Another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter client_id: the client_id to match, must be case insensitive * @parameter cls: pointer to the void * cls value allocated in client_module_init * */ int client_module_delete(struct config_module * config, const char * client_id, void * cls); ``` ```C /** * * client_module_check_password * * Validate the password of an existing client * * @return value: G_OK on success * Another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter client_id: the client_id to match, must be case insensitive * @parameter password: the password to validate * @parameter cls: pointer to the void * cls value allocated in client_module_init * */ int client_module_check_password(struct config_module * config, const char * client_id, const char * password, void * cls); ``` ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/client/database.c����������������������������������������������������������������0000664�0000000�0000000�00000165231�14156463140�0017347�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * * Glewlwyd SSO Server * * Authentiation server * Clients are authenticated via various backend available: database, ldap * * Database client module * * Copyright 2019-2020 Nicolas Mora <mail@babelouest.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see <http://www.gnu.org/licenses/>. * */ #include <string.h> #include <jansson.h> #include <yder.h> #include <orcania.h> #include <hoel.h> #include "glewlwyd-common.h" #define G_TABLE_CLIENT "g_client" #define G_TABLE_CLIENT_SCOPE "g_client_scope" #define G_TABLE_CLIENT_SCOPE_CLIENT "g_client_scope_client" #define G_TABLE_CLIENT_PROPERTY "g_client_property" #define G_PBKDF2_ITERATOR_SEP ',' struct mod_parameters { int use_glewlwyd_connection; digest_algorithm hash_algorithm; struct _h_connection * conn; json_t * j_params; unsigned int PBKDF2_iterations; struct config_module * config_glewlwyd; }; static json_t * is_client_database_parameters_valid(json_t * j_params) { json_t * j_return, * j_error = json_array(), * j_element = NULL; const char * field = NULL; if (j_error != NULL) { if (!json_is_object(j_params)) { json_array_append_new(j_error, json_string("parameters must be a JSON object")); } else { if (json_object_get(j_params, "use-glewlwyd-connection") != NULL && !json_is_boolean(json_object_get(j_params, "use-glewlwyd-connection"))) { json_array_append_new(j_error, json_string("use-glewlwyd-connection must be a boolean")); } if (json_object_get(j_params, "use-glewlwyd-connection") == json_false()) { if (json_object_get(j_params, "connection-type") == NULL || !json_is_string(json_object_get(j_params, "connection-type")) || (0 != o_strcmp("sqlite", json_string_value(json_object_get(j_params, "connection-type"))) && 0 != o_strcmp("mariadb", json_string_value(json_object_get(j_params, "connection-type"))) && 0 != o_strcmp("postgre", json_string_value(json_object_get(j_params, "connection-type"))))) { json_array_append_new(j_error, json_string("connection-type is mandatory and must be one of the following values: 'sqlite', 'mariadb', 'postgre'")); } else if (0 == o_strcmp("sqlite", json_string_value(json_object_get(j_params, "connection-type")))) { if (json_object_get(j_params, "sqlite-dbpath") == NULL || !json_is_string(json_object_get(j_params, "sqlite-dbpath"))) { json_array_append_new(j_error, json_string("sqlite-dbpath is mandatory and must be a string")); } } else if (0 == o_strcmp("mariadb", json_string_value(json_object_get(j_params, "connection-type")))) { if (json_object_get(j_params, "mariadb-host") == NULL || !json_is_string(json_object_get(j_params, "mariadb-host"))) { json_array_append_new(j_error, json_string("mariadb-host is mandatory and must be a string")); } if (json_object_get(j_params, "mariadb-user") == NULL || !json_is_string(json_object_get(j_params, "mariadb-user"))) { json_array_append_new(j_error, json_string("mariadb-user is mandatory and must be a string")); } if (json_object_get(j_params, "mariadb-password") == NULL || !json_is_string(json_object_get(j_params, "mariadb-password"))) { json_array_append_new(j_error, json_string("mariadb-password is mandatory and must be a string")); } if (json_object_get(j_params, "mariadb-dbname") == NULL || !json_is_string(json_object_get(j_params, "mariadb-dbname"))) { json_array_append_new(j_error, json_string("mariadb-dbname is mandatory and must be a string")); } if (json_object_get(j_params, "mariadb-port") != NULL && (!json_is_integer(json_object_get(j_params, "mariadb-port")) || json_integer_value(json_object_get(j_params, "mariadb-port")) < 0)) { json_array_append_new(j_error, json_string("mariadb-port is optional and must be a positive integer (default: 0)")); } } else if (0 == o_strcmp("postgre", json_string_value(json_object_get(j_params, "connection-type")))) { if (json_object_get(j_params, "postgre-conninfo") == NULL || !json_is_string(json_object_get(j_params, "postgre-conninfo"))) { json_array_append_new(j_error, json_string("postgre-conninfo is mandatory and must be a string")); } } } if (json_object_get(j_params, "data-format") != NULL) { if (!json_is_object(json_object_get(j_params, "data-format"))) { json_array_append_new(j_error, json_string("data-format is optional and must be a JSON object")); } else { json_object_foreach(json_object_get(j_params, "data-format"), field, j_element) { if (0 == o_strcmp(field, "client_id") || 0 == o_strcmp(field, "name") || 0 == o_strcmp(field, "description") || 0 == o_strcmp(field, "enabled") || 0 == o_strcmp(field, "confidential") || 0 == o_strcmp(field, "password")) { json_array_append_new(j_error, json_string("data-format can not have settings for properties 'client_id', 'name', 'description', 'enabled', 'confidential' or 'password'")); } else { if (json_object_get(j_element, "multiple") != NULL && !json_is_boolean(json_object_get(j_element, "multiple"))) { json_array_append_new(j_error, json_string("multiple is optional and must be a boolean (default: false)")); } if (json_object_get(j_element, "read") != NULL && !json_is_boolean(json_object_get(j_element, "read"))) { json_array_append_new(j_error, json_string("read is optional and must be a boolean (default: true)")); } if (json_object_get(j_element, "write") != NULL && !json_is_boolean(json_object_get(j_element, "write"))) { json_array_append_new(j_error, json_string("write is optional and must be a boolean (default: true)")); } if (json_object_get(j_element, "covert") != NULL && 0 != o_strcmp("jwks", json_string_value(json_object_get(j_element, "covert")))) { json_array_append_new(j_error, json_string("covert is optional and must be have the value 'jwks'")); } } } } } if (json_object_get(j_params, "pbkdf2-iterations") != NULL && json_integer_value(json_object_get(j_params, "pbkdf2-iterations")) <= 0) { json_array_append_new(j_error, json_string("pbkdf2-iterations is optional and must be a positive non null integer")); } } if (json_array_size(j_error)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_error); } else { j_return = json_pack("{si}", "result", G_OK); } json_decref(j_error); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_client_database_parameters_valid - Error allocating resources for j_error"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } return j_return; } static char * get_pattern_clause(struct mod_parameters * param, const char * pattern) { char * escape_pattern = h_escape_string_with_quotes(param->conn, pattern), * clause = NULL; if (escape_pattern != NULL) { clause = msprintf("IN (SELECT gc_id from " G_TABLE_CLIENT " WHERE gc_client_id LIKE '%%'||%s||'%%' OR gc_name LIKE '%%'||%s||'%%' OR gc_description LIKE '%%'||%s||'%%')", escape_pattern, escape_pattern, escape_pattern); } o_free(escape_pattern); return clause; } static int append_client_properties(struct mod_parameters * param, json_t * j_client, int profile) { json_t * j_query, * j_result, * j_element = NULL, * j_param_config, * j_value; int res, ret; size_t index = 0; if (param->conn->type == HOEL_DB_TYPE_MARIADB) { j_query = json_pack("{sss[ssss]s{sO}}", "table", G_TABLE_CLIENT_PROPERTY, "columns", "gcp_name AS name", "gcp_value_tiny AS value_tiny", "gcp_value_small AS value_small", "gcp_value_medium AS value_medium", "where", "gc_id", json_object_get(j_client, "gc_id")); res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result, index, j_element) { j_param_config = json_object_get(json_object_get(param->j_params, "data-format"), json_string_value(json_object_get(j_element, "name"))); if (!profile && json_object_get(j_param_config, "read") != json_false()) { if (json_object_get(j_element, "value_tiny") != json_null()) { if (0 == o_strcmp("jwks", json_string_value(json_object_get(j_param_config, "convert")))) { j_value = json_loads(json_string_value(json_object_get(j_element, "value_tiny")), JSON_DECODE_ANY, NULL); } else { j_value = json_incref(json_object_get(j_element, "value_tiny")); } if (j_value != NULL) { if (json_object_get(j_param_config, "multiple") == json_true()) { if (json_object_get(j_client, json_string_value(json_object_get(j_element, "name"))) == NULL) { json_object_set_new(j_client, json_string_value(json_object_get(j_element, "name")), json_array()); } json_array_append(json_object_get(j_client, json_string_value(json_object_get(j_element, "name"))), j_value); } else { json_object_set(j_client, json_string_value(json_object_get(j_element, "name")), j_value); } json_decref(j_value); } else { y_log_message(Y_LOG_LEVEL_ERROR, "append_client_properties - Error j_value is NULL (value_tiny)"); } } else if (json_object_get(j_element, "value_small") != json_null()) { if (0 == o_strcmp("jwks", json_string_value(json_object_get(j_param_config, "convert")))) { j_value = json_loads(json_string_value(json_object_get(j_element, "value_small")), JSON_DECODE_ANY, NULL); } else { j_value = json_incref(json_object_get(j_element, "value_small")); } if (j_value != NULL) { if (json_object_get(j_param_config, "multiple") == json_true()) { if (json_object_get(j_client, json_string_value(json_object_get(j_element, "name"))) == NULL) { json_object_set_new(j_client, json_string_value(json_object_get(j_element, "name")), json_array()); } json_array_append(json_object_get(j_client, json_string_value(json_object_get(j_element, "name"))), j_value); } else { json_object_set(j_client, json_string_value(json_object_get(j_element, "name")), j_value); } json_decref(j_value); } else { y_log_message(Y_LOG_LEVEL_ERROR, "append_client_properties - Error j_value is NULL (value_small)"); } } else if (json_object_get(j_element, "value_medium") != json_null()) { if (0 == o_strcmp("jwks", json_string_value(json_object_get(j_param_config, "convert")))) { j_value = json_loads(json_string_value(json_object_get(j_element, "value_medium")), JSON_DECODE_ANY, NULL); } else { j_value = json_incref(json_object_get(j_element, "value_medium")); } if (j_value != NULL) { if (json_object_get(j_param_config, "multiple") == json_true()) { if (json_object_get(j_client, json_string_value(json_object_get(j_element, "name"))) == NULL) { json_object_set_new(j_client, json_string_value(json_object_get(j_element, "name")), json_array()); } json_array_append(json_object_get(j_client, json_string_value(json_object_get(j_element, "name"))), j_value); } else { json_object_set(j_client, json_string_value(json_object_get(j_element, "name")), j_value); } json_decref(j_value); } else { y_log_message(Y_LOG_LEVEL_ERROR, "append_client_properties - Error j_value is NULL (value_medium)"); } } else { if (json_object_get(j_param_config, "multiple") == json_true()) { if (json_object_get(j_client, json_string_value(json_object_get(j_element, "name"))) == NULL) { json_object_set_new(j_client, json_string_value(json_object_get(j_element, "name")), json_array()); } json_array_append(json_object_get(j_client, json_string_value(json_object_get(j_element, "name"))), json_null()); } else { json_object_set(j_client, json_string_value(json_object_get(j_element, "name")), json_null()); } } } } ret = G_OK; json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "append_client_properties database - Error executing j_query"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { j_query = json_pack("{sss[ss]s{sO}}", "table", G_TABLE_CLIENT_PROPERTY, "columns", "gcp_name AS name", "gcp_value AS value", "where", "gc_id", json_object_get(j_client, "gc_id")); res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result, index, j_element) { j_param_config = json_object_get(json_object_get(param->j_params, "data-format"), json_string_value(json_object_get(j_element, "name"))); if (!profile && json_object_get(j_param_config, "read") != json_false()) { if (json_object_get(j_element, "value") != json_null()) { if (0 == o_strcmp("jwks", json_string_value(json_object_get(j_param_config, "convert")))) { j_value = json_loads(json_string_value(json_object_get(j_element, "value")), JSON_DECODE_ANY, NULL); } else { j_value = json_incref(json_object_get(j_element, "value")); } if (j_value != NULL) { if (json_object_get(j_param_config, "multiple") == json_true()) { if (json_object_get(j_client, json_string_value(json_object_get(j_element, "name"))) == NULL) { json_object_set_new(j_client, json_string_value(json_object_get(j_element, "name")), json_array()); } json_array_append(json_object_get(j_client, json_string_value(json_object_get(j_element, "name"))), j_value); } else { json_object_set(j_client, json_string_value(json_object_get(j_element, "name")), j_value); } json_decref(j_value); } else { y_log_message(Y_LOG_LEVEL_ERROR, "append_client_properties - Error j_value is NULL (value)"); } } else { if (json_object_get(j_param_config, "multiple") == json_true()) { if (json_object_get(j_client, json_string_value(json_object_get(j_element, "name"))) == NULL) { json_object_set_new(j_client, json_string_value(json_object_get(j_element, "name")), json_array()); } json_array_append(json_object_get(j_client, json_string_value(json_object_get(j_element, "name"))), json_null()); } else { json_object_set(j_client, json_string_value(json_object_get(j_element, "name")), json_null()); } } } } ret = G_OK; json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "append_client_properties database - Error executing j_query"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } return ret; } static json_t * database_client_scope_get(struct mod_parameters * param, json_int_t gc_id) { json_t * j_query, * j_result, * j_return, * j_array, * j_scope = NULL; int res; size_t index = 0; char * scope_clause = msprintf("IN (SELECT gcs_id from " G_TABLE_CLIENT_SCOPE_CLIENT " WHERE gc_id = %"JSON_INTEGER_FORMAT")", gc_id); j_query = json_pack("{sss[s]s{s{ssss}}ss}", "table", G_TABLE_CLIENT_SCOPE, "columns", "gcs_name AS name", "where", "gcs_id", "operator", "raw", "value", scope_clause, "order_by", "gcs_id"); o_free(scope_clause); res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { j_array = json_array(); if (j_array != NULL) { json_array_foreach(j_result, index, j_scope) { json_array_append(j_array, json_object_get(j_scope, "name")); } j_return = json_pack("{sisO}", "result", G_OK, "scope", j_array); json_decref(j_array); } else { y_log_message(Y_LOG_LEVEL_ERROR, "database_client_scope_get database - Error allocating resources for j_array"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "database_client_scope_get database - Error executing j_query"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } static json_t * database_client_get(const char * client_id, void * cls, int profile) { struct mod_parameters * param = (struct mod_parameters *)cls; json_t * j_query, * j_result, * j_scope, * j_return; int res; char * client_id_escaped, * client_id_clause; client_id_escaped = h_escape_string_with_quotes(param->conn, client_id); client_id_clause = msprintf(" = UPPER(%s)", client_id_escaped); j_query = json_pack("{sss[ssssss]s{s{ssss}}}", "table", G_TABLE_CLIENT, "columns", "gc_id", "gc_client_id AS client_id", "gc_name AS name", "gc_description AS description", "gc_enabled", "gc_confidential", "where", "UPPER(gc_client_id)", "operator", "raw", "value", client_id_clause); o_free(client_id_escaped); o_free(client_id_clause); res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_scope = database_client_scope_get(param, json_integer_value(json_object_get(json_array_get(j_result, 0), "gc_id"))); if (check_result_value(j_scope, G_OK)) { json_object_set(json_array_get(j_result, 0), "scope", json_object_get(j_scope, "scope")); json_object_set(json_array_get(j_result, 0), "enabled", (json_integer_value(json_object_get(json_array_get(j_result, 0), "gc_enabled"))?json_true():json_false())); json_object_set(json_array_get(j_result, 0), "confidential", (json_integer_value(json_object_get(json_array_get(j_result, 0), "gc_confidential"))?json_true():json_false())); if (append_client_properties(param, json_array_get(j_result, 0), profile) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "database_client_get database - Error append_client_properties"); } json_object_del(json_array_get(j_result, 0), "gc_enabled"); json_object_del(json_array_get(j_result, 0), "gc_confidential"); json_object_del(json_array_get(j_result, 0), "gc_id"); j_return = json_pack("{sisO}", "result", G_OK, "client", json_array_get(j_result, 0)); } else { j_return = json_pack("{si}", "result", G_ERROR); y_log_message(Y_LOG_LEVEL_ERROR, "database_client_get database - Error database_client_scope_get"); } json_decref(j_scope); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { j_return = json_pack("{si}", "result", G_ERROR_DB); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); y_log_message(Y_LOG_LEVEL_ERROR, "database_client_get database - Error executing j_query"); } return j_return; } static char * get_password_clause_write(struct mod_parameters * param, const char * password) { char * clause = NULL, * password_encoded, digest[1024] = {0}; if (!o_strlen(password)) { clause = o_strdup("''"); } else if (param->conn->type == HOEL_DB_TYPE_SQLITE) { if (generate_digest_pbkdf2(password, param->PBKDF2_iterations, NULL, digest)) { clause = msprintf("'%s%c%u'", digest, G_PBKDF2_ITERATOR_SEP, param->PBKDF2_iterations); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_password_clause_write database - Error generate_digest_pbkdf2"); } } else if (param->conn->type == HOEL_DB_TYPE_MARIADB) { password_encoded = h_escape_string_with_quotes(param->conn, password); if (password_encoded != NULL) { clause = msprintf("PASSWORD(%s)", password_encoded); o_free(password_encoded); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_password_clause_write database - Error h_escape_string_with_quotes (mariadb)"); } } else if (param->conn->type == HOEL_DB_TYPE_PGSQL) { password_encoded = h_escape_string_with_quotes(param->conn, password); if (password_encoded != NULL) { clause = msprintf("crypt(%s, gen_salt('bf'))", password_encoded); o_free(password_encoded); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_password_clause_write database - Error h_escape_string_with_quotes (postgre)"); } } return clause; } static char * get_salt_from_password_hash(struct mod_parameters * param, const char * client_id, unsigned int * iterations) { json_t * j_query, * j_result; int res; unsigned char password_b64_decoded[1024] = {0}; char * salt = NULL, * client_id_escaped, * client_id_clause, * str_iterator; size_t password_b64_decoded_len, gc_password_len; client_id_escaped = h_escape_string_with_quotes(param->conn, client_id); client_id_clause = msprintf(" = UPPER(%s)", client_id_escaped); j_query = json_pack("{sss[s]s{s{ssss}}}", "table", G_TABLE_CLIENT, "columns", "gc_password", "where", "UPPER(gc_client_id)", "operator", "raw", "value", client_id_clause); o_free(client_id_clause); o_free(client_id_escaped); res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result) && json_string_length(json_object_get(json_array_get(j_result, 0), "gc_password"))) { if ((str_iterator = o_strchr(json_string_value(json_object_get(json_array_get(j_result, 0), "gc_password")), G_PBKDF2_ITERATOR_SEP)) != NULL) { gc_password_len = o_strchr(json_string_value(json_object_get(json_array_get(j_result, 0), "gc_password")), G_PBKDF2_ITERATOR_SEP) - json_string_value(json_object_get(json_array_get(j_result, 0), "gc_password")); *iterations = (unsigned int)strtol(str_iterator+1, NULL, 10); } else { gc_password_len = json_string_length(json_object_get(json_array_get(j_result, 0), "gc_password")); } if (o_base64_decode((const unsigned char *)json_string_value(json_object_get(json_array_get(j_result, 0), "gc_password")), gc_password_len, password_b64_decoded, &password_b64_decoded_len)) { if ((salt = o_strdup((const char *)password_b64_decoded + password_b64_decoded_len - GLEWLWYD_DEFAULT_SALT_LENGTH)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "get_salt_from_password_hash - Error extracting salt"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_salt_from_password_hash - Error o_base64_decode"); } } else { salt = o_strdup(""); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_salt_from_password_hash - Error executing j_query"); } return salt; } static char * get_password_clause_check(struct mod_parameters * param, const char * client_id, const char * password) { char * clause = NULL, * password_encoded, digest[1024] = {0}, * salt; unsigned int iterations = 0; if (param->conn->type == HOEL_DB_TYPE_SQLITE) { if ((salt = get_salt_from_password_hash(param, client_id, &iterations)) != NULL) { if (generate_digest_pbkdf2(password, iterations?iterations:1000, salt, digest)) { if (iterations) { clause = msprintf(" = '%s%c%u'", digest, G_PBKDF2_ITERATOR_SEP, iterations); } else { clause = msprintf(" = '%s'", digest); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_password_clause_check database - Error generate_digest_pbkdf2"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_password_clause_check database - Error get_salt_from_password_hash"); } o_free(salt); } else if (param->conn->type == HOEL_DB_TYPE_MARIADB) { password_encoded = h_escape_string_with_quotes(param->conn, password); if (password_encoded != NULL) { clause = msprintf(" = PASSWORD(%s)", password_encoded); o_free(password_encoded); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_password_clause_write database - Error h_escape_string_with_quotes (mariadb)"); } } else if (param->conn->type == HOEL_DB_TYPE_PGSQL) { password_encoded = h_escape_string_with_quotes(param->conn, password); if (password_encoded != NULL) { clause = msprintf(" = crypt(%s, gc_password)", password_encoded); o_free(password_encoded); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_password_clause_write database - Error h_escape_string_with_quotes (postgre)"); } } return clause; } static json_t * get_property_value_db(struct mod_parameters * param, const char * name, json_t * j_property, json_int_t gc_id) { json_t * j_value, * j_return; char * tmp; if (0 == o_strcmp("jwks", json_string_value(json_object_get(json_object_get(json_object_get(param->j_params, "data-format"), name), "convert")))) { tmp = json_dumps(j_property, JSON_COMPACT); j_value = json_string(tmp); o_free(tmp); } else { j_value = json_incref(j_property); } if (param->conn->type == HOEL_DB_TYPE_MARIADB) { if (json_string_length(j_value) < 512) { j_return = json_pack("{sIsssOsOsO}", "gc_id", gc_id, "gcp_name", name, "gcp_value_tiny", j_value, "gcp_value_small", json_null(), "gcp_value_medium", json_null()); } else if (json_string_length(j_value) < 16*1024) { j_return = json_pack("{sIsssOsOsO}", "gc_id", gc_id, "gcp_name", name, "gcp_value_tiny", json_null(), "gcp_value_small", j_value, "gcp_value_medium", json_null()); } else if (json_string_length(j_value) < 16*1024*1024) { j_return = json_pack("{sIsssOsOsO}", "gc_id", gc_id, "gcp_name", name, "gcp_value_tiny", json_null(), "gcp_value_small", json_null(), "gcp_value_medium", j_value); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_property_value_db - Error value is too large"); j_return = NULL; } } else { j_return = json_pack("{sIsssO}", "gc_id", gc_id, "gcp_name", name, "gcp_value", j_value); } json_decref(j_value); return j_return; } static int save_client_properties(struct mod_parameters * param, json_t * j_client, json_int_t gc_id) { json_t * j_property = NULL, * j_query, * j_array = json_array(), * j_format, * j_property_value = NULL; const char * name = NULL; int ret, res; size_t index = 0; if (j_array != NULL) { json_object_foreach(j_client, name, j_property) { if (0 != o_strcmp(name, "client_id") && 0 != o_strcmp(name, "name") && 0 != o_strcmp(name, "password") && 0 != o_strcmp(name, "description") && 0 != o_strcmp(name, "enabled") && 0 != o_strcmp(name, "confidential") && 0 != o_strcmp(name, "scope")) { j_format = json_object_get(json_object_get(param->j_params, "data-format"), name); if (json_object_get(j_format, "write") != json_false()) { if (!json_is_array(j_property)) { json_array_append_new(j_array, get_property_value_db(param, name, j_property, gc_id)); } else { json_array_foreach(j_property, index, j_property_value) { if (j_property_value != json_null()) { json_array_append_new(j_array, get_property_value_db(param, name, j_property_value, gc_id)); } } } } } } // Delete old values j_query = json_pack("{sss{sI}}", "table", G_TABLE_CLIENT_PROPERTY, "where", "gc_id", gc_id); res = h_delete(param->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { // Add new values if (json_array_size(j_array)) { j_query = json_pack("{sssO}", "table", G_TABLE_CLIENT_PROPERTY, "values", j_array); res = h_insert(param->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "insert_client_properties database - Error executing j_query insert"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { ret = G_OK; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "insert_client_properties database - Error executing j_query delete"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } json_decref(j_array); } else { y_log_message(Y_LOG_LEVEL_ERROR, "insert_client_properties database - Error allocating resources for j_array"); ret = G_ERROR_MEMORY; } return ret; } static int save_client_scope(struct mod_parameters * param, json_t * j_scope, json_int_t gc_id) { json_t * j_query, * j_result, * j_element = NULL, * j_new_scope_id; int res, ret; char * scope_clause; size_t index = 0; j_query = json_pack("{sss{sI}}", "table", G_TABLE_CLIENT_SCOPE_CLIENT, "where", "gc_id", gc_id); res = h_delete(param->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; if (json_is_array(j_scope)) { json_array_foreach(j_scope, index, j_element) { j_query = json_pack("{sss[s]s{sO}}", "table", G_TABLE_CLIENT_SCOPE, "columns", "gcs_id", "where", "gcs_name", j_element); res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_query = json_pack("{sss{sIsO}}", "table", G_TABLE_CLIENT_SCOPE_CLIENT, "values", "gc_id", gc_id, "gcs_id", json_object_get(json_array_get(j_result, 0), "gcs_id")); res = h_insert(param->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "save_client_scope database - Error executing j_query insert scope_client (1)"); } } else { j_query = json_pack("{sss{sO}}", "table", G_TABLE_CLIENT_SCOPE, "values", "gcs_name", j_element); res = h_insert(param->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_new_scope_id = h_last_insert_id(param->conn); if (j_new_scope_id != NULL) { j_query = json_pack("{sss{sIsO}}", "table", G_TABLE_CLIENT_SCOPE_CLIENT, "values", "gc_id", gc_id, "gcs_id", j_new_scope_id); res = h_insert(param->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "save_client_scope database - Error executing j_query insert scope_client (2)"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "save_client_scope database - Error h_last_insert_id"); } json_decref(j_new_scope_id); } else { y_log_message(Y_LOG_LEVEL_ERROR, "save_client_scope database - Error executing j_query insert scope"); } } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "save_client_scope database - Error executing j_query select scope"); } } } // Clean orphan client_scope scope_clause = msprintf("NOT IN (SELECT DISTINCT(gcs_id) FROM " G_TABLE_CLIENT_SCOPE_CLIENT ")"); j_query = json_pack("{sss{s{ssss}}}", "table", G_TABLE_CLIENT_SCOPE, "where", "gcs_id", "operator", "raw", "value", scope_clause); o_free(scope_clause); res = h_delete(param->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "save_client_scope database - Error executing j_query delete empty scopes"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "save_client_scope database - Error executing j_query delete"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } json_t * client_module_load(struct config_module * config) { UNUSED(config); return json_pack("{si ss ss ss}", "result", G_OK, "name", "database", "display_name", "Database backend client module", "description", "Module to store clients in the database"); } int client_module_unload(struct config_module * config) { UNUSED(config); return G_OK; } json_t * client_module_init(struct config_module * config, int readonly, json_t * j_parameters, void ** cls) { UNUSED(readonly); json_t * j_result, * j_return; char * error_message; j_result = is_client_database_parameters_valid(j_parameters); if (check_result_value(j_result, G_OK)) { *cls = o_malloc(sizeof(struct mod_parameters)); if (*cls != NULL) { ((struct mod_parameters *)*cls)->j_params = json_incref(j_parameters); ((struct mod_parameters *)*cls)->hash_algorithm = config->hash_algorithm; ((struct mod_parameters *)*cls)->config_glewlwyd = config; if (json_object_get(j_parameters, "use-glewlwyd-connection") != json_false()) { ((struct mod_parameters *)*cls)->use_glewlwyd_connection = 0; ((struct mod_parameters *)*cls)->conn = config->conn; } else { ((struct mod_parameters *)*cls)->use_glewlwyd_connection = 1; if (0 == o_strcmp(json_string_value(json_object_get(j_parameters, "connection-type")), "sqlite")) { ((struct mod_parameters *)*cls)->conn = h_connect_sqlite(json_string_value(json_object_get(j_parameters, "sqlite-dbpath"))); } else if (0 == o_strcmp(json_string_value(json_object_get(j_parameters, "connection-type")), "mariadb")) { ((struct mod_parameters *)*cls)->conn = h_connect_mariadb(json_string_value(json_object_get(j_parameters, "mariadb-host")), json_string_value(json_object_get(j_parameters, "mariadb-user")), json_string_value(json_object_get(j_parameters, "mariadb-password")), json_string_value(json_object_get(j_parameters, "mariadb-dbname")), json_integer_value(json_object_get(j_parameters, "mariadb-port")), NULL); } else if (0 == o_strcmp(json_string_value(json_object_get(j_parameters, "connection-type")), "postgre")) { ((struct mod_parameters *)*cls)->conn = h_connect_pgsql(json_string_value(json_object_get(j_parameters, "postgre-conninfo"))); } } if (json_object_get(j_parameters, "pbkdf2-iterations") != NULL) { ((struct mod_parameters *)*cls)->PBKDF2_iterations = (unsigned int)json_integer_value(json_object_get(j_parameters, "pbkdf2-iterations")); } else { ((struct mod_parameters *)*cls)->PBKDF2_iterations = G_PBKDF2_ITERATOR_DEFAULT; } if (((struct mod_parameters *)*cls)->conn != NULL) { j_return = json_pack("{si}", "result", G_OK); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_init database - Error connecting to database"); j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "Error connecting to database"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_init database - Error allocating resources for cls"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } } else if (check_result_value(j_result, G_ERROR_PARAM)) { error_message = json_dumps(json_object_get(j_result, "error"), JSON_COMPACT); y_log_message(Y_LOG_LEVEL_ERROR, "user_module_init database - Error parsing parameters"); y_log_message(Y_LOG_LEVEL_ERROR, error_message); o_free(error_message); j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", json_object_get(j_result, "error")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_init database - Error is_user_database_parameters_valid"); j_return = json_pack("{sis[s]}", "result", G_ERROR, "error", "internal error"); } json_decref(j_result); return j_return; } int client_module_close(struct config_module * config, void * cls) { UNUSED(config); int ret; if (cls != NULL) { if (((struct mod_parameters *)cls)->use_glewlwyd_connection) { if (h_close_db(((struct mod_parameters *)cls)->conn) != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_close database - Error h_close_db"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } else { ret = G_OK; } } else { ret = G_OK; } json_decref(((struct mod_parameters *)cls)->j_params); o_free(cls); } else { ret = G_ERROR_PARAM; } return ret; } size_t client_module_count_total(struct config_module * config, const char * pattern, void * cls) { UNUSED(config); struct mod_parameters * param = (struct mod_parameters *)cls; json_t * j_query, * j_result = NULL; int res; size_t ret = 0; char * pattern_clause; j_query = json_pack("{sss[s]}", "table", G_TABLE_CLIENT, "columns", "count(gc_id) AS total"); if (o_strlen(pattern)) { pattern_clause = get_pattern_clause(param, pattern); json_object_set_new(j_query, "where", json_pack("{s{ssss}}", "gc_id", "operator", "raw", "value", pattern_clause)); o_free(pattern_clause); } res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { ret = (size_t)json_integer_value(json_object_get(json_array_get(j_result, 0), "total")); json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_count_total database - Error executing j_query"); } return ret; } json_t * client_module_get_list(struct config_module * config, const char * pattern, size_t offset, size_t limit, void * cls) { UNUSED(config); struct mod_parameters * param = (struct mod_parameters *)cls; json_t * j_query, * j_result, * j_element = NULL, * j_scope, * j_return; int res; char * pattern_clause; size_t index = 0; j_query = json_pack("{sss[ssssss]sisiss}", "table", G_TABLE_CLIENT, "columns", "gc_id", "gc_client_id AS client_id", "gc_name AS name", "gc_description AS description", "gc_enabled", "gc_confidential", "offset", offset, "limit", limit, "order_by", "gc_client_id"); if (o_strlen(pattern)) { pattern_clause = get_pattern_clause(param, pattern); json_object_set_new(j_query, "where", json_pack("{s{ssss}}", "gc_id", "operator", "raw", "value", pattern_clause)); o_free(pattern_clause); } res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result, index, j_element) { j_scope = database_client_scope_get(param, json_integer_value(json_object_get(j_element, "gc_id"))); if (check_result_value(j_scope, G_OK)) { json_object_set(j_element, "scope", json_object_get(j_scope, "scope")); json_object_set(j_element, "enabled", (json_integer_value(json_object_get(j_element, "gc_enabled"))?json_true():json_false())); json_object_set(j_element, "confidential", (json_integer_value(json_object_get(j_element, "gc_confidential"))?json_true():json_false())); if (append_client_properties(param, j_element, 0) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_get_list database - Error append_client_properties"); } json_object_del(j_element, "gc_enabled"); json_object_del(j_element, "gc_confidential"); json_object_del(j_element, "gc_id"); } else { j_return = json_pack("{si}", "result", G_ERROR); y_log_message(Y_LOG_LEVEL_ERROR, "client_module_get_list database - Error database_client_scope_get"); } json_decref(j_scope); } j_return = json_pack("{sisO}", "result", G_OK, "list", j_result); json_decref(j_result); } else { j_return = json_pack("{si}", "result", G_ERROR_DB); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); y_log_message(Y_LOG_LEVEL_ERROR, "client_module_get_list database - Error executing j_query"); } return j_return; } json_t * client_module_get(struct config_module * config, const char * client_id, void * cls) { UNUSED(config); return database_client_get(client_id, cls, 0); } json_t * client_module_is_valid(struct config_module * config, const char * client_id, json_t * j_client, int mode, void * cls) { UNUSED(config); struct mod_parameters * param = (struct mod_parameters *)cls; json_t * j_result = json_array(), * j_element, * j_format, * j_value, * j_return = NULL, * j_cur_client; char * message; size_t index = 0; const char * property; if (j_result != NULL) { if (mode == GLEWLWYD_IS_VALID_MODE_ADD) { if (!json_is_string(json_object_get(j_client, "client_id")) || json_string_length(json_object_get(j_client, "client_id")) > 128) { json_array_append_new(j_result, json_string("client_id is mandatory and must be a string (maximum 128 characters)")); } else { j_cur_client = client_module_get(config, json_string_value(json_object_get(j_client, "client_id")), cls); if (check_result_value(j_cur_client, G_OK)) { json_array_append_new(j_result, json_string("client_id already exist")); } else if (!check_result_value(j_cur_client, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_is_valid database - Error client_module_get"); } json_decref(j_cur_client); } } else if ((mode == GLEWLWYD_IS_VALID_MODE_UPDATE || mode == GLEWLWYD_IS_VALID_MODE_UPDATE_PROFILE) && client_id == NULL) { json_array_append_new(j_result, json_string("client_id is mandatory on update mode")); } if (mode != GLEWLWYD_IS_VALID_MODE_UPDATE_PROFILE) { if (json_object_get(j_client, "scope") != NULL) { if (!json_is_array(json_object_get(j_client, "scope"))) { json_array_append_new(j_result, json_string("scope must be a JSON array of string")); } else { json_array_foreach(json_object_get(j_client, "scope"), index, j_element) { if (!json_is_string(j_element) || !json_string_length(j_element)) { json_array_append_new(j_result, json_string("scope must be a JSON array of string")); } } } } } if (mode != GLEWLWYD_IS_VALID_MODE_UPDATE_PROFILE && json_object_get(j_client, "password") != NULL && !json_is_string(json_object_get(j_client, "password"))) { json_array_append_new(j_result, json_string("password must be a string")); } if (json_object_get(j_client, "name") != NULL && json_object_get(j_client, "name") != json_null() && (!json_is_string(json_object_get(j_client, "name")) || json_string_length(json_object_get(j_client, "name")) > 256)) { json_array_append_new(j_result, json_string("name must be a string (maximum 256 characters)")); } if (json_object_get(j_client, "description") != NULL && json_object_get(j_client, "description") != json_null() && (!json_is_string(json_object_get(j_client, "description")) || json_string_length(json_object_get(j_client, "description")) > 512)) { json_array_append_new(j_result, json_string("description must be a string (maximum 512 characters)")); } if (json_object_get(j_client, "enabled") != NULL && !json_is_boolean(json_object_get(j_client, "enabled"))) { json_array_append_new(j_result, json_string("enabled must be a boolean")); } if (json_object_get(j_client, "confidential") != NULL && !json_is_boolean(json_object_get(j_client, "confidential"))) { json_array_append_new(j_result, json_string("confidential must be a boolean")); } json_object_foreach(j_client, property, j_element) { if (0 != o_strcmp(property, "client_id") && 0 != o_strcmp(property, "name") && 0 != o_strcmp(property, "confidential") && 0 != o_strcmp(property, "enabled") && 0 != o_strcmp(property, "password") && 0 != o_strcmp(property, "source") && 0 != o_strcmp(property, "scope")) { j_format = json_object_get(json_object_get(param->j_params, "data-format"), property); if (json_object_get(j_format, "multiple") == json_true()) { if (!json_is_array(j_element)) { message = msprintf("property '%s' must be a JSON array", property); json_array_append_new(j_result, json_string(message)); o_free(message); } else { json_array_foreach(j_element, index, j_value) { if ((!json_is_string(j_value) || json_string_length(j_value) > 16*1024*1024) && 0 != o_strcmp("jwks", json_string_value(json_object_get(j_format, "convert")))) { message = msprintf("property '%s' must contain a string value (maximum 16M characters)", property); json_array_append_new(j_result, json_string(message)); o_free(message); } } } } else { if ((((!json_is_string(j_element) && json_object_get(j_client, "description") != json_null()) || json_string_length(j_element) > 16*1024*1024)) && 0 != o_strcmp("jwks", json_string_value(json_object_get(j_format, "convert")))) { message = msprintf("property '%s' must be a string value (maximum 16M characters)", property); json_array_append_new(j_result, json_string(message)); o_free(message); } } } } if (json_array_size(j_result)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_result); } else { j_return = json_pack("{si}", "result", G_OK); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_is_valid database - Error allocating resources for j_result"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } return j_return; } int client_module_add(struct config_module * config, json_t * j_client, void * cls) { UNUSED(config); struct mod_parameters * param = (struct mod_parameters *)cls; json_t * j_query, * j_gc_id; int res, ret; char * password_clause; j_query = json_pack("{sss{ss}}", "table", G_TABLE_CLIENT, "values", "gc_client_id", json_string_value(json_object_get(j_client, "client_id"))); if (json_object_get(j_client, "password") != NULL) { password_clause = get_password_clause_write(param, json_string_value(json_object_get(j_client, "password"))); json_object_set_new(json_object_get(j_query, "values"), "gc_password", json_pack("{ss}", "raw", password_clause)); o_free(password_clause); } if (json_object_get(j_client, "name") != NULL && json_object_get(j_client, "name") != json_null()) { json_object_set(json_object_get(j_query, "values"), "gc_name", json_object_get(j_client, "name")); } if (json_object_get(j_client, "description") != NULL && json_object_get(j_client, "description") != json_null()) { json_object_set(json_object_get(j_query, "values"), "gc_description", json_object_get(j_client, "description")); } if (json_object_get(j_client, "enabled") != NULL) { json_object_set_new(json_object_get(j_query, "values"), "gc_enabled", json_object_get(j_client, "enabled")==json_false()?json_integer(0):json_integer(1)); } if (json_object_get(j_client, "confidential") != NULL) { json_object_set_new(json_object_get(j_query, "values"), "gc_confidential", json_object_get(j_client, "confidential")==json_false()?json_integer(0):json_integer(1)); } res = h_insert(param->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_gc_id = h_last_insert_id(param->conn); if (save_client_properties(param, j_client, json_integer_value(j_gc_id)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_add database - Error save_client_properties"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } else if (json_object_get(j_client, "scope") != NULL && save_client_scope(param, json_object_get(j_client, "scope"), json_integer_value(j_gc_id)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_add database - Error save_client_scope"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } else { ret = G_OK; } json_decref(j_gc_id); } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_add database - Error executing j_query insert"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } int client_module_update(struct config_module * config, const char * client_id, json_t * j_client, void * cls) { UNUSED(config); struct mod_parameters * param = (struct mod_parameters *)cls; json_t * j_query, * j_result = NULL; int res, ret; char * password_clause; char * client_id_escaped, * client_id_clause; client_id_escaped = h_escape_string_with_quotes(param->conn, client_id); client_id_clause = msprintf(" = UPPER(%s)", client_id_escaped); j_query = json_pack("{sss[s]s{s{ssss}}}", "table", G_TABLE_CLIENT, "columns", "gc_id", "where", "UPPER(gc_client_id)", "operator", "raw", "value", client_id_clause); o_free(client_id_escaped); o_free(client_id_clause); res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK && json_array_size(j_result)) { j_query = json_pack("{sss{}s{sO}}", "table", G_TABLE_CLIENT, "set", "where", "gc_id", json_object_get(json_array_get(j_result, 0), "gc_id")); if (json_object_get(j_client, "password") != NULL) { password_clause = get_password_clause_write(param, json_string_value(json_object_get(j_client, "password"))); json_object_set_new(json_object_get(j_query, "set"), "gc_password", json_pack("{ss}", "raw", password_clause)); o_free(password_clause); } if (json_object_get(j_client, "name") != NULL && json_object_get(j_client, "name") != json_null()) { json_object_set(json_object_get(j_query, "set"), "gc_name", json_object_get(j_client, "name")); } if (json_object_get(j_client, "description") != NULL && json_object_get(j_client, "description") != json_null()) { json_object_set(json_object_get(j_query, "set"), "gc_description", json_object_get(j_client, "description")); } if (json_object_get(j_client, "enabled") != NULL) { json_object_set_new(json_object_get(j_query, "set"), "gc_enabled", json_object_get(j_client, "enabled")==json_false()?json_integer(0):json_integer(1)); } if (json_object_get(j_client, "confidential") != NULL) { json_object_set_new(json_object_get(j_query, "set"), "gc_confidential", json_object_get(j_client, "confidential")==json_false()?json_integer(0):json_integer(1)); } if (json_object_size(json_object_get(j_query, "set"))) { res = h_update(param->conn, j_query, NULL); } else { res = H_OK; } json_decref(j_query); if (res == H_OK) { if (save_client_properties(param, j_client, json_integer_value(json_object_get(json_array_get(j_result, 0), "gc_id"))) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_add database - Error save_client_properties"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } else if (json_object_get(j_client, "scope") != NULL && save_client_scope(param, json_object_get(j_client, "scope"), json_integer_value(json_object_get(json_array_get(j_result, 0), "gc_id"))) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_add database - Error save_client_scope"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } else { ret = G_OK; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_add database - Error executing j_query update"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { ret = G_ERROR_NOT_FOUND; } json_decref(j_result); return ret; } int client_module_delete(struct config_module * config, const char * client_id, void * cls) { UNUSED(config); struct mod_parameters * param = (struct mod_parameters *)cls; json_t * j_query; int res, ret; char * client_id_escaped, * client_id_clause; client_id_escaped = h_escape_string_with_quotes(param->conn, client_id); client_id_clause = msprintf(" = UPPER(%s)", client_id_escaped); j_query = json_pack("{sss{s{ssss}}}", "table", G_TABLE_CLIENT, "where", "UPPER(gc_client_id)", "operator", "raw", "value", client_id_clause); o_free(client_id_escaped); o_free(client_id_clause); res = h_delete(param->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_delete database - Error executing j_query"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } int client_module_check_password(struct config_module * config, const char * client_id, const char * password, void * cls) { UNUSED(config); struct mod_parameters * param = (struct mod_parameters *)cls; int ret, res; json_t * j_query, * j_result; char * clause = get_password_clause_check(param, client_id, password); char * client_id_escaped, * client_id_clause; client_id_escaped = h_escape_string_with_quotes(param->conn, client_id); client_id_clause = msprintf(" = UPPER(%s)", client_id_escaped); j_query = json_pack("{sss[s]s{s{ssss}s{ssss}}}", "table", G_TABLE_CLIENT, "columns", "gc_id", "where", "UPPER(gc_client_id)", "operator", "raw", "value", client_id_clause, "gc_password", "operator", "raw", "value", clause); o_free(client_id_escaped); o_free(client_id_clause); o_free(clause); res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { ret = G_OK; } else { ret = G_ERROR_UNAUTHORIZED; } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_check_password database - Error executing j_query"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/client/database.mariadb.sql������������������������������������������������������0000664�0000000�0000000�00000002310�14156463140�0021306�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������DROP TABLE IF EXISTS g_client_property; DROP TABLE IF EXISTS g_client_scope_client; DROP TABLE IF EXISTS g_client_scope; DROP TABLE IF EXISTS g_client; CREATE TABLE g_client ( gc_id INT(11) PRIMARY KEY AUTO_INCREMENT, gc_client_id VARCHAR(128) NOT NULL UNIQUE, gc_name VARCHAR(256) DEFAULT '', gc_description VARCHAR(512) DEFAULT '', gc_confidential TINYINT(1) DEFAULT 0, gc_password VARCHAR(256), gc_enabled TINYINT(1) DEFAULT 1 ); CREATE TABLE g_client_scope ( gcs_id INT(11) PRIMARY KEY AUTO_INCREMENT, gcs_name VARCHAR(128) NOT NULL UNIQUE ); CREATE TABLE g_client_scope_client ( gcsu_id INT(11) PRIMARY KEY AUTO_INCREMENT, gc_id INT(11), gcs_id INT(11), FOREIGN KEY(gc_id) REFERENCES g_client(gc_id) ON DELETE CASCADE, FOREIGN KEY(gcs_id) REFERENCES g_client_scope(gcs_id) ON DELETE CASCADE ); CREATE TABLE g_client_property ( gcp_id INT(11) PRIMARY KEY AUTO_INCREMENT, gc_id INT(11), gcp_name VARCHAR(128) NOT NULL, gcp_value_tiny VARCHAR(512) DEFAULT NULL, gcp_value_small BLOB DEFAULT NULL, gcp_value_medium MEDIUMBLOB DEFAULT NULL, FOREIGN KEY(gc_id) REFERENCES g_client(gc_id) ON DELETE CASCADE ); CREATE INDEX i_g_client_property_name ON g_client_property(gcp_name); ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/client/database.postgre.sql������������������������������������������������������0000664�0000000�0000000�00000002043�14156463140�0021375�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������DROP TABLE IF EXISTS g_client_property; DROP TABLE IF EXISTS g_client_scope_client; DROP TABLE IF EXISTS g_client_scope; DROP TABLE IF EXISTS g_client; CREATE TABLE g_client ( gc_id SERIAL PRIMARY KEY, gc_client_id VARCHAR(128) NOT NULL UNIQUE, gc_name VARCHAR(256) DEFAULT '', gc_description VARCHAR(512) DEFAULT '', gc_confidential SMALLINT DEFAULT 0, gc_password VARCHAR(256), gc_enabled SMALLINT DEFAULT 1 ); CREATE TABLE g_client_scope ( gcs_id SERIAL PRIMARY KEY, gcs_name VARCHAR(128) NOT NULL UNIQUE ); CREATE TABLE g_client_scope_client ( gcsu_id SERIAL PRIMARY KEY, gc_id SERIAL, gcs_id SERIAL, FOREIGN KEY(gc_id) REFERENCES g_client(gc_id) ON DELETE CASCADE, FOREIGN KEY(gcs_id) REFERENCES g_client_scope(gcs_id) ON DELETE CASCADE ); CREATE TABLE g_client_property ( gcp_id SERIAL PRIMARY KEY, gc_id SERIAL, gcp_name VARCHAR(128) NOT NULL, gcp_value TEXT DEFAULT NULL, FOREIGN KEY(gc_id) REFERENCES g_client(gc_id) ON DELETE CASCADE ); CREATE INDEX i_g_client_property_name ON g_client_property(gcp_name); ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/client/database.sqlite3.sql������������������������������������������������������0000664�0000000�0000000�00000002060�14156463140�0021275�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������DROP TABLE IF EXISTS g_client_property; DROP TABLE IF EXISTS g_client_scope_client; DROP TABLE IF EXISTS g_client_scope; DROP TABLE IF EXISTS g_client; CREATE TABLE g_client ( gc_id INTEGER PRIMARY KEY AUTOINCREMENT, gc_client_id TEXT NOT NULL UNIQUE, gc_name TEXT DEFAULT '', gc_description TEXT DEFAULT '', gc_confidential INTEGER DEFAULT 0, gc_password TEXT, gc_enabled INTEGER DEFAULT 1 ); CREATE TABLE g_client_scope ( gcs_id INTEGER PRIMARY KEY AUTOINCREMENT, gcs_name TEXT NOT NULL UNIQUE ); CREATE TABLE g_client_scope_client ( gcsu_id INTEGER PRIMARY KEY AUTOINCREMENT, gc_id INTEGER, gcs_id INTEGER, FOREIGN KEY(gc_id) REFERENCES g_client(gc_id) ON DELETE CASCADE, FOREIGN KEY(gcs_id) REFERENCES g_client_scope(gcs_id) ON DELETE CASCADE ); CREATE TABLE g_client_property ( gcp_id INTEGER PRIMARY KEY AUTOINCREMENT, gc_id INTEGER, gcp_name TEXT NOT NULL, gcp_value TEXT DEFAULT NULL, FOREIGN KEY(gc_id) REFERENCES g_client(gc_id) ON DELETE CASCADE ); CREATE INDEX i_g_client_property_name ON g_client_property(gcp_name); ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/client/ldap.c��������������������������������������������������������������������0000664�0000000�0000000�00000247624�14156463140�0016532�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * * Glewlwyd SSO Server * * Authentiation server * Clients are authenticated via various backend available: database, ldap * * LDAP client module * * Copyright 2019-2020 Nicolas Mora <mail@babelouest.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see <http://www.gnu.org/licenses/>. * */ #include <string.h> #include <ldap.h> #include <jansson.h> #include <yder.h> #include <orcania.h> #include "glewlwyd-common.h" #define LDAP_DEFAULT_PAGE_SIZE 50 static json_t * is_client_ldap_parameters_valid(json_t * j_params, int readonly) { json_t * j_return, * j_error = json_array(), * j_element = NULL, * j_element_p; size_t index = 0; const char * field = NULL; char * message; if (j_error != NULL) { if (!json_is_object(j_params)) { json_array_append_new(j_error, json_string("parameters must be a JSON object")); } else { if (!json_string_length(json_object_get(j_params, "uri"))) { json_array_append_new(j_error, json_string("uri is mandatory and must be a string")); } if (!json_string_length(json_object_get(j_params, "bind-dn"))) { json_array_append_new(j_error, json_string("bind-dn is mandatory and must be a string")); } if (!json_string_length(json_object_get(j_params, "bind-password"))) { json_array_append_new(j_error, json_string("bind-password is mandatory and must be a string")); } if (json_object_get(j_params, "search-scope") != NULL && !json_is_string(json_object_get(j_params, "search-scope"))) { json_array_append_new(j_error, json_string("search-scope is optional and must be a string")); } else if (json_object_get(j_params, "search-scope") == NULL) { json_object_set_new(j_params, "search-scope", json_string("one")); } else if (0 != o_strcmp("one", json_string_value(json_object_get(j_params, "search-scope"))) && 0 != o_strcmp("subtree", json_string_value(json_object_get(j_params, "search-scope"))) && 0 != o_strcmp("children", json_string_value(json_object_get(j_params, "search-scope")))) { json_array_append_new(j_error, json_string("search-scope must have one of the following values: 'one', 'subtree', 'children'")); } if (json_object_get(j_params, "page-size") != NULL && (!json_is_integer(json_object_get(j_params, "page-size")) || json_integer_value(json_object_get(j_params, "page-size")) <= 0)) { json_array_append_new(j_error, json_string("page-size is optional and must be a positive integer")); } else if (json_object_get(j_params, "page-size") == NULL) { json_object_set_new(j_params, "page-size", json_integer(LDAP_DEFAULT_PAGE_SIZE)); } if (!json_string_length(json_object_get(j_params, "base-search"))) { json_array_append_new(j_error, json_string("base-search is mandatory and must be a string")); } if (!json_string_length(json_object_get(j_params, "filter"))) { json_array_append_new(j_error, json_string("filter is mandatory and must be a string")); } if (readonly) { if (!json_string_length(json_object_get(j_params, "client_id-property"))) { json_array_append_new(j_error, json_string("client_id-property is mandatory and must be a non empty string")); } } else { if (json_object_get(j_params, "client_id-property") == NULL || (!json_is_string(json_object_get(j_params, "client_id-property")) && !json_is_array(json_object_get(j_params, "client_id-property")))) { json_array_append_new(j_error, json_string("client_id-property is mandatory and must be a non empty string or an array of non empty strings")); } else if (json_is_string(json_object_get(j_params, "client_id-property")) && !json_string_length(json_object_get(j_params, "client_id-property"))) { json_array_append_new(j_error, json_string("client_id-property is mandatory and must be a non empty string or an array of non empty strings")); } else if (json_is_array(json_object_get(j_params, "client_id-property"))) { json_array_foreach(json_object_get(j_params, "client_id-property"), index, j_element) { if (!json_string_length(j_element)) { json_array_append_new(j_error, json_string("client_id-property is mandatory and must be a non empty string or an array of non empty strings")); } } } } if (readonly) { if (!json_string_length(json_object_get(j_params, "scope-property"))) { json_array_append_new(j_error, json_string("scope-property is mandatory and must be a non empty string")); } } else { if (json_object_get(j_params, "scope-property") == NULL || (!json_is_string(json_object_get(j_params, "scope-property")) && !json_is_array(json_object_get(j_params, "scope-property")))) { json_array_append_new(j_error, json_string("scope-property is mandatory and must be a non empty string or an array of non empty strings")); } else if (json_is_string(json_object_get(j_params, "scope-property")) && !json_string_length(json_object_get(j_params, "scope-property"))) { json_array_append_new(j_error, json_string("scope-property is mandatory and must be a non empty string or an array of non empty strings")); } else if (json_is_array(json_object_get(j_params, "scope-property"))) { json_array_foreach(json_object_get(j_params, "scope-property"), index, j_element) { if (!json_string_length(j_element)) { json_array_append_new(j_error, json_string("scope-property is mandatory and must be a non empty string or an array of non empty strings")); } } } } if (json_object_get(j_params, "scope-match") != NULL && !json_is_array(json_object_get(j_params, "scope-match"))) { json_array_append_new(j_error, json_string("scope-match is optional and must be a JSON array")); } else if (json_object_get(j_params, "scope-match") != NULL) { json_array_foreach(json_object_get(j_params, "scope-property-match-correspondence"), index, j_element) { if (!json_is_string(json_object_get(j_element, "ldap-value"))) { json_array_append_new(j_error, json_string("ldap-value is mandatory and must be a string")); } if (!json_is_string(json_object_get(j_element, "scope-value"))) { json_array_append_new(j_error, json_string("scope-value is mandatory and must be a string")); } if (!json_is_string(json_object_get(j_element, "match")) || 0 != o_strcmp("equals", json_string_value(json_object_get(j_element, "match"))) || 0 != o_strcmp("contains", json_string_value(json_object_get(j_element, "match"))) || 0 != o_strcmp("startswith", json_string_value(json_object_get(j_element, "match"))) || 0 != o_strcmp("endswith", json_string_value(json_object_get(j_element, "match")))) { json_array_append_new(j_error, json_string("match is mandatory and must have one of the following values: 'equals', 'contains', 'startswith', 'endswith'")); } } } if (readonly) { if (!json_string_length(json_object_get(j_params, "name-property"))) { json_array_append_new(j_error, json_string("name-property is mandatory and must be a non empty string")); } } else { if (json_object_get(j_params, "name-property") == NULL || (!json_is_string(json_object_get(j_params, "name-property")) && !json_is_array(json_object_get(j_params, "name-property")))) { json_array_append_new(j_error, json_string("name-property is mandatory and must be a non empty string or an array of non empty strings")); } else if (json_is_string(json_object_get(j_params, "name-property")) && !json_string_length(json_object_get(j_params, "name-property"))) { json_array_append_new(j_error, json_string("name-property is mandatory and must be a non empty string or an array of non empty strings")); } else if (json_is_array(json_object_get(j_params, "name-property"))) { json_array_foreach(json_object_get(j_params, "name-property"), index, j_element) { if (!json_string_length(j_element)) { json_array_append_new(j_error, json_string("name-property is mandatory and must be a non empty string or an array of non empty strings")); } } } } if (readonly) { if (!json_is_string(json_object_get(j_params, "description-property"))) { json_array_append_new(j_error, json_string("description-property is optional and must be a string")); } } else { if (json_object_get(j_params, "description-property") != NULL && !json_is_string(json_object_get(j_params, "description-property")) && !json_is_array(json_object_get(j_params, "description-property"))) { json_array_append_new(j_error, json_string("description-property is optional and must be a string or an array of strings")); } else if (json_is_array(json_object_get(j_params, "description-property"))) { json_array_foreach(json_object_get(j_params, "description-property"), index, j_element) { if (!json_is_string(j_element)) { json_array_append_new(j_error, json_string("description-property is optional and must be a string or an array of strings")); } } } } if (readonly) { if (!json_string_length(json_object_get(j_params, "confidential-property"))) { json_array_append_new(j_error, json_string("confidential-property is mandatory and must be a non empty string")); } } else { if (json_object_get(j_params, "confidential-property") == NULL || (!json_string_length(json_object_get(j_params, "confidential-property")) && !json_is_array(json_object_get(j_params, "confidential-property")))) { json_array_append_new(j_error, json_string("confidential-property is mandatory and must be a non empty string or an array of non empty strings")); } else if (json_is_array(json_object_get(j_params, "confidential-property"))) { json_array_foreach(json_object_get(j_params, "confidential-property"), index, j_element) { if (!json_string_length(j_element)) { json_array_append_new(j_error, json_string("confidential-property is mandatory and must be a non empty string or an array of non empty strings")); } } } } if (!readonly) { if (!json_string_length(json_object_get(j_params, "rdn-property"))) { json_array_append_new(j_error, json_string("rdn-property is mandatory and must be a non empty string")); } if (!json_string_length(json_object_get(j_params, "password-property"))) { json_array_append_new(j_error, json_string("password-property is mandatory and must be a non empty string")); } if (json_object_get(j_params, "password-algorithm") == NULL || (0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "SHA") && //0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "SHA256") && //0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "SHA384") && //0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "SHA512") && 0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "SSHA") && //0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "SSHA256") && //0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "SSHA384") && //0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "SSHA512") && 0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "SMD5") && 0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "MD5") && //0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "PKCS5S2") && 0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "CRYPT") && 0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "CRYPT_MD5") && 0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "CRYPT_SHA256") && 0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "CRYPT_SHA512") && 0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "PLAIN"))) { //json_array_append_new(j_error, json_string("password-algorithm is mandatory and must have one of the following values: 'SHA', 'SHA256', 'SHA284', 'SHA512', 'SSHA', " // "'SSHA256', 'SSHA384', 'SSHA512', 'SMD5', 'MD5', 'PKCS5S2', 'PLAIN'")); json_array_append_new(j_error, json_string("password-algorithm is mandatory and must have one of the following values: 'SHA', 'SSHA', 'SMD5', 'MD5', 'CRYPT', 'CRYPT_MD5', 'CRYPT_SHA256', 'CRYPT_SHA512', 'PLAIN'")); } if (json_object_get(j_params, "object-class") == NULL || (!json_is_string(json_object_get(j_params, "object-class")) && !json_is_array(json_object_get(j_params, "object-class")))) { json_array_append_new(j_error, json_string("object-class is mandatory and must be a non empty string or an array of non empty strings")); } else if (json_is_string(json_object_get(j_params, "object-class")) && !json_string_length(json_object_get(j_params, "object-class"))) { json_array_append_new(j_error, json_string("object-class is mandatory and must be a non empty string or an array of non empty strings")); } else { json_array_foreach(json_object_get(j_params, "object-class"), index, j_element) { if (!json_string_length(j_element)) { json_array_append_new(j_error, json_string("object-class is mandatory and must be a non empty string or an array of non empty strings")); } } } } if (json_object_get(j_params, "data-format") != NULL) { if (!json_is_object(json_object_get(j_params, "data-format"))) { json_array_append_new(j_error, json_string("data-format is optional and must be a JSON object")); } else { json_object_foreach(json_object_get(j_params, "data-format"), field, j_element) { if (0 == o_strcmp(field, "client_id") || 0 == o_strcmp(field, "name") || 0 == o_strcmp(field, "description") || 0 == o_strcmp(field, "enabled") || 0 == o_strcmp(field, "confidential") || 0 == o_strcmp(field, "password") || 0 == o_strcmp(field, "scope")) { json_array_append_new(j_error, json_string("data-format can not have settings for properties 'client_id', 'name', 'description', 'enabled', 'confidential', 'scope' or 'password'")); } else { if (readonly) { if (json_object_get(j_element, "property") == NULL || !json_string_length(json_object_get(j_element, "property"))) { message = msprintf("property %s is mandatory and must be a non empty string or an array of non empty string", field); json_array_append_new(j_error, json_string(message)); o_free(message); } } else { if (json_object_get(j_element, "property") == NULL || ((!json_is_string(json_object_get(j_element, "property")) || !json_string_length(json_object_get(j_element, "property"))) && !json_is_array(json_object_get(j_element, "property")))) { message = msprintf("property %s is mandatory and must be a non empty string or an array of non empty string", field); json_array_append_new(j_error, json_string(message)); o_free(message); } else if (json_is_array(json_object_get(j_element, "property"))) { json_array_foreach(json_object_get(j_element, "property"), index, j_element_p) { if (!json_string_length(j_element_p)) { message = msprintf("property %s is mandatory and must be a non empty string or an array of non empty string", field); json_array_append_new(j_error, json_string(message)); o_free(message); } } } } if (json_object_get(j_element, "multiple") != NULL && !json_is_boolean(json_object_get(j_element, "multiple"))) { json_array_append_new(j_error, json_string("multiple is optional and must be a boolean (default: false)")); } if (json_object_get(j_element, "convert") != NULL && 0 != o_strcmp("base64", json_string_value(json_object_get(j_element, "convert"))) && 0 != o_strcmp("jwks", json_string_value(json_object_get(j_element, "convert")))) { json_array_append_new(j_error, json_string("convert is optional and must have one of the following values: 'base64', 'jwks'")); } if (json_object_get(j_element, "read") != NULL && !json_is_boolean(json_object_get(j_element, "read"))) { json_array_append_new(j_error, json_string("read is optional and must be a boolean (default: true)")); } if (!readonly && json_object_get(j_element, "write") != NULL && !json_is_boolean(json_object_get(j_element, "write"))) { json_array_append_new(j_error, json_string("write is optional and must be a boolean (default: true)")); } } } } } } if (json_array_size(j_error)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_error); } else { j_return = json_pack("{si}", "result", G_OK); } json_decref(j_error); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_client_database_parameters_valid - Error allocating resources for j_error"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } return j_return; } /** * * Escapes any special chars (RFC 4515) from a string representing a * a search filter assertion value. * * You must o_free the returned value after use * */ static char * escape_ldap(const char * input) { char * tmp, * to_return = NULL; size_t len, i; if (input != NULL) { to_return = strdup(""); len = o_strlen(input); for (i=0; i < len && to_return != NULL; i++) { unsigned char c = input[i]; if (c == '*') { // escape asterisk tmp = msprintf("%s\\2a", to_return); o_free(to_return); to_return = tmp; } else if (c == '(') { // escape left parenthesis tmp = msprintf("%s\\28", to_return); o_free(to_return); to_return = tmp; } else if (c == ')') { // escape right parenthesis tmp = msprintf("%s\\29", to_return); o_free(to_return); to_return = tmp; } else if (c == '\\') { // escape backslash tmp = msprintf("%s\\5c", to_return); o_free(to_return); to_return = tmp; } else if ((c & 0x80) == 0) { // regular 1-byte UTF-8 char tmp = msprintf("%s%c", to_return, c); o_free(to_return); to_return = tmp; } else if (((c & 0xE0) == 0xC0) && i < (len-2)) { // higher-order 2-byte UTF-8 chars tmp = msprintf("%s\\%02x\\%02x", to_return, input[i], input[i+1]); o_free(to_return); to_return = tmp; } else if (((c & 0xF0) == 0xE0) && i < (len-3)) { // higher-order 3-byte UTF-8 chars tmp = msprintf("%s\\%02x\\%02x\\%02x", to_return, input[i], input[i+1], input[i+2]); o_free(to_return); to_return = tmp; } else if (((c & 0xF8) == 0xF0) && i < (len-4)) { // higher-order 4-byte UTF-8 chars tmp = msprintf("%s\\%02x\\%02x\\%02x\\%02x", to_return, input[i], input[i+1], input[i+2], input[i+3]); o_free(to_return); to_return = tmp; } } } return to_return; } static LDAP * connect_ldap_server(json_t * j_params) { LDAP * ldap = NULL; int ldap_version = LDAP_VERSION3; int result; char * ldap_mech = LDAP_SASL_SIMPLE; struct berval cred, * servcred; cred.bv_val = (char*)json_string_value(json_object_get(j_params, "bind-password")); cred.bv_len = o_strlen(json_string_value(json_object_get(j_params, "bind-password"))); if (ldap_initialize(&ldap, json_string_value(json_object_get(j_params, "uri"))) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "connect_ldap_server ldap - Error initializing ldap"); ldap = NULL; } else if (ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &ldap_version) != LDAP_OPT_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "connect_ldap_server ldap - Error setting ldap protocol version"); ldap_unbind_ext(ldap, NULL, NULL); ldap = NULL; } else if ((result = ldap_sasl_bind_s(ldap, json_string_value(json_object_get(j_params, "bind-dn")), ldap_mech, &cred, NULL, NULL, &servcred)) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "connect_ldap_server client - Error binding to ldap server mode %s: %s", ldap_mech, ldap_err2string(result)); ldap_unbind_ext(ldap, NULL, NULL); ldap = NULL; } return ldap; } static const char * get_read_property(json_t * j_params, const char * property) { if (json_is_string(json_object_get(j_params, property))) { return json_string_value(json_object_get(j_params, property)); } else if (json_is_array(json_object_get(j_params, property))) { return json_string_value(json_array_get(json_object_get(j_params, property), 0)); } else { return NULL; } } static char * get_ldap_filter_pattern(json_t * j_params, const char * pattern) { char * pattern_escaped, * filter, * name_filter, * description_filter; if (o_strlen(pattern)) { pattern_escaped = escape_ldap(pattern); if (json_object_get(j_params, "name-property") != NULL) { name_filter = msprintf("(%s=*%s*)", get_read_property(j_params, "name-property"), pattern_escaped); } else { name_filter = o_strdup(""); } if (json_object_get(j_params, "description-property") != NULL) { description_filter = msprintf("(%s=*%s*)", get_read_property(j_params, "description-property"), pattern_escaped); } else { description_filter = o_strdup(""); } filter = msprintf("(&(%s)(|(%s=*%s*)%s%s))", json_string_value(json_object_get(j_params, "filter")), get_read_property(j_params, "client_id-property"), pattern_escaped, name_filter, description_filter); o_free(pattern_escaped); o_free(name_filter); o_free(description_filter); } else { filter = msprintf("(%s)", json_string_value(json_object_get(j_params, "filter"))); } return filter; } static char ** get_ldap_read_attributes(json_t * j_params, int profile, json_t * j_properties) { char ** attrs = NULL; size_t i, nb_attrs = 2; // Clientname, Scope json_t * j_element = NULL; const char * field = NULL; if (j_properties != NULL && json_is_object(j_properties) && !json_object_size(j_properties)) { nb_attrs += (json_object_get(j_params, "name-property") != NULL); nb_attrs += (json_object_get(j_params, "description-property") != NULL); nb_attrs += (json_object_get(j_params, "confidential-property") != NULL); if (json_object_get(j_params, "data-format") != NULL) { json_object_foreach(json_object_get(j_params, "data-format"), field, j_element) { nb_attrs += ((!profile && json_object_get(j_element, "read") != json_false()) || (profile && json_object_get(j_element, "profile-read") == json_true())); } } attrs = o_malloc((nb_attrs + 1) * sizeof(char *)); if (attrs != NULL) { attrs[nb_attrs] = NULL; attrs[0] = (char*)get_read_property(j_params, "client_id-property"); json_object_set_new(j_properties, "client_id", json_string(get_read_property(j_params, "client_id-property"))); attrs[1] = (char*)get_read_property(j_params, "scope-property"); json_object_set_new(j_properties, "scope", json_string(get_read_property(j_params, "scope-property"))); i = 2; if (json_object_get(j_params, "name-property") != NULL) { attrs[i++] = (char*)get_read_property(j_params, "name-property"); json_object_set_new(j_properties, "name", json_string(get_read_property(j_params, "name-property"))); } if (json_object_get(j_params, "description-property") != NULL) { attrs[i++] = (char*)get_read_property(j_params, "description-property"); json_object_set_new(j_properties, "description", json_string(get_read_property(j_params, "description-property"))); } if (json_object_get(j_params, "confidential-property") != NULL) { attrs[i++] = (char*)get_read_property(j_params, "confidential-property"); json_object_set_new(j_properties, "confidential", json_string(get_read_property(j_params, "confidential-property"))); } if (json_object_get(j_params, "data-format") != NULL) { json_object_foreach(json_object_get(j_params, "data-format"), field, j_element) { if ((!profile && json_object_get(j_element, "read") != json_false()) || (profile && json_object_get(j_element, "profile-read") == json_true())) { attrs[i++] = (char*)get_read_property(j_element, "property"); json_object_set_new(j_properties, field, json_string(get_read_property(j_element, "property"))); } } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_read_attributes - Error allocating resources for attrs"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_read_attributes - Error j_properties is not an empty JSON object"); } return attrs; } static size_t count_properties(json_t * j_params, const char * property) { if (json_object_get(j_params, property) != NULL) { if (json_is_string(json_object_get(j_params, property))) { return 1; } else { return json_array_size(json_object_get(j_params, property)); } } else { return 0; } } static digest_algorithm get_digest_algorithm(json_t * j_params) { if (0 == o_strcmp("SHA", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SHA1; } else if (0 == o_strcmp("SSHA", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SSHA1; } else if (0 == o_strcmp("SHA224", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SHA224; } else if (0 == o_strcmp("SSHA224", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SSHA224; } else if (0 == o_strcmp("SHA256", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SHA256; } else if (0 == o_strcmp("SSHA256", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SSHA256; } else if (0 == o_strcmp("SHA384", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SHA384; } else if (0 == o_strcmp("SSHA384", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SSHA384; } else if (0 == o_strcmp("SHA512", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SHA512; } else if (0 == o_strcmp("SSHA512", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SSHA512; } else if (0 == o_strcmp("PBKDF2", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_PBKDF2_SHA256; } else if (0 == o_strcmp("MD5", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_MD5; } else if (0 == o_strcmp("SMD5", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SMD5; } else if (0 == o_strcmp("CRYPT", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_CRYPT; } else if (0 == o_strcmp("CRYPT_MD5", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_CRYPT_MD5; } else if (0 == o_strcmp("CRYPT_SHA256", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_CRYPT_SHA256; } else if (0 == o_strcmp("CRYPT_SHA512", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_CRYPT_SHA512; } else { return digest_PLAIN; } } static LDAPMod ** get_ldap_write_mod(json_t * j_params, json_t * j_client, int add, json_t * j_mod_value_free_array) { LDAPMod ** mods = NULL; size_t nb_attr = 0; json_t * j_format, * j_property = NULL, * j_property_value, * j_scope; const char * field = NULL; unsigned int i; size_t index = 0, index_scope = 0; int has_error = 0; unsigned char * value_enc = NULL; size_t value_enc_len = 0; if (j_mod_value_free_array != NULL) { // Count attrs if (add) { nb_attr += count_properties(j_params, "client_id-property") + 2; } if (json_object_get(j_client, "name") != NULL) { nb_attr += count_properties(j_params, "name-property"); } if (json_object_get(j_client, "scope") != NULL) { nb_attr += count_properties(j_params, "scope-property"); } if (json_object_get(j_client, "description") != NULL) { nb_attr += count_properties(j_params, "description-property"); } if (json_object_get(j_client, "confidential") != NULL) { nb_attr += count_properties(j_params, "confidential-property"); } if (json_string_length(json_object_get(j_client, "password"))) { nb_attr++; } json_object_foreach(j_client, field, j_property) { if (0 != o_strcmp(field, "client_id") && 0 != o_strcmp(field, "name") && 0 != o_strcmp(field, "password") && 0 != o_strcmp(field, "scope") && 0 != o_strcmp(field, "description") && 0 != o_strcmp(field, "enabled") && 0 != o_strcmp(field, "confidential")) { if ((j_format = json_object_get(json_object_get(j_params, "data-format"), field)) != NULL) { if (json_object_get(j_format, "write") != json_false()) { nb_attr += count_properties(j_format, "property"); } } } } mods = o_malloc((nb_attr + 1)*sizeof(LDAPMod *)); for (i=0; i<(nb_attr+1); i++) { mods[i] = NULL; } // Fill mods i=0; if (mods != NULL) { if (add) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { if (json_is_array(json_object_get(j_params, "object-class"))) { mods[i]->mod_values = o_malloc((json_array_size(json_object_get(j_params, "object-class")) + 1) * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = LDAP_MOD_ADD; mods[i]->mod_type = "objectClass"; json_array_foreach(json_object_get(j_params, "object-class"), index, j_property_value) { mods[i]->mod_values[index] = (char *)json_string_value(j_property_value); } mods[i]->mod_values[json_array_size(json_object_get(j_params, "object-class"))] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d]->mod_values (objectClass)", i); has_error = 1; } } else { mods[i]->mod_values = o_malloc(2 * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = LDAP_MOD_ADD; mods[i]->mod_type = "objectClass"; mods[i]->mod_values[0] = (char *)json_string_value(json_object_get(j_params, "object-class")); mods[i]->mod_values[1] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d]->mod_values (objectClass)", i); has_error = 1; } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d] (objectClass)", i); has_error = 1; } i++; if (json_is_array(json_object_get(j_params, "client_id-property"))) { json_array_foreach(json_object_get(j_params, "client_id-property"), index, j_property) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc(2 * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = LDAP_MOD_ADD; mods[i]->mod_type = (char *)json_string_value(j_property); mods[i]->mod_values[0] = (char *)json_string_value(json_object_get(j_client, "client_id")); mods[i]->mod_values[1] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d]->mod_values (client_id)", i); has_error = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d] (client_id)", i); has_error = 1; } } } else { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc(2 * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = add?LDAP_MOD_ADD:LDAP_MOD_REPLACE; mods[i]->mod_type = (char *)get_read_property(j_params, "client_id-property"); mods[i]->mod_values[0] = (char *)json_string_value(json_object_get(j_client, "client_id")); mods[i]->mod_values[1] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d]->mod_values (client_id)", i); has_error = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d] (client_id)", i); has_error = 1; } } i++; } if (json_object_get(j_client, "name") != NULL) { if (json_is_array(json_object_get(j_params, "name-property"))) { json_array_foreach(json_object_get(j_params, "name-property"), index, j_property) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc(2 * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = add?LDAP_MOD_ADD:LDAP_MOD_REPLACE; mods[i]->mod_type = (char *)json_string_value(j_property); mods[i]->mod_values[0] = (char *)json_string_value(json_object_get(j_client, "name")); mods[i]->mod_values[1] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d]->mod_values (name)", i); has_error = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d] (name)", i); has_error = 1; } } } else { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc(2 * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = add?LDAP_MOD_ADD:LDAP_MOD_REPLACE; mods[i]->mod_type = (char *)json_string_value(json_object_get(j_params, "name-property")); mods[i]->mod_values[0] = (char *)json_string_value(json_object_get(j_client, "name")); mods[i]->mod_values[1] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d]->mod_values (name)", i); has_error = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d] (name)", i); has_error = 1; } } i++; } if (json_object_get(j_client, "description") != NULL) { if (json_is_array(json_object_get(j_params, "description-property"))) { json_array_foreach(json_object_get(j_params, "description-property"), index, j_property) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc(2 * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = add?LDAP_MOD_ADD:LDAP_MOD_REPLACE; mods[i]->mod_type = (char *)json_string_value(j_property); mods[i]->mod_values[0] = (char *)json_string_value(json_object_get(j_client, "description")); mods[i]->mod_values[1] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d]->mod_values (description)", i); has_error = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d] (description)", i); has_error = 1; } } } else { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc(2 * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = add?LDAP_MOD_ADD:LDAP_MOD_REPLACE; mods[i]->mod_type = (char *)json_string_value(json_object_get(j_params, "description-property")); mods[i]->mod_values[0] = (char *)json_string_value(json_object_get(j_client, "description")); mods[i]->mod_values[1] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d]->mod_values (description)", i); has_error = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d] (description)", i); has_error = 1; } } i++; } if (json_object_get(j_client, "confidential") != NULL) { if (json_is_array(json_object_get(j_params, "confidential-property"))) { json_array_foreach(json_object_get(j_params, "confidential-property"), index, j_property) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc(2 * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = add?LDAP_MOD_ADD:LDAP_MOD_REPLACE; mods[i]->mod_type = (char *)json_string_value(j_property); mods[i]->mod_values[0] = json_object_get(j_client, "confidential")==json_true()?"1":"0"; mods[i]->mod_values[1] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d]->mod_values (confidential)", i); has_error = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d] (confidential)", i); has_error = 1; } } } else { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc(2 * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = add?LDAP_MOD_ADD:LDAP_MOD_REPLACE; mods[i]->mod_type = (char *)json_string_value(json_object_get(j_params, "confidential-property")); mods[i]->mod_values[0] = json_object_get(j_client, "confidential")==json_true()?"1":"0"; mods[i]->mod_values[1] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d]->mod_values (confidential)", i); has_error = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d] (confidential)", i); has_error = 1; } } i++; } if (json_object_get(j_client, "scope") != NULL) { if (json_is_array(json_object_get(j_params, "scope-property"))) { json_array_foreach(json_object_get(j_params, "scope-property"), index, j_property) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc((json_array_size(json_object_get(j_client, "scope")) + 1) * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = add?LDAP_MOD_ADD:LDAP_MOD_REPLACE; mods[i]->mod_type = (char *)json_string_value(j_property); json_array_foreach(json_object_get(j_client, "scope"), index_scope, j_scope) { mods[i]->mod_values[index_scope] = (char *)json_string_value(j_scope); } mods[i]->mod_values[(json_array_size(json_object_get(j_client, "scope")))] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d]->mod_values (scope)", i); has_error = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d] (scope)", i); has_error = 1; } } } else { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc((json_array_size(json_object_get(j_client, "scope")) + 1) * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = add?LDAP_MOD_ADD:LDAP_MOD_REPLACE; mods[i]->mod_type = (char *)json_string_value(json_object_get(j_params, "scope-property")); json_array_foreach(json_object_get(j_client, "scope"), index_scope, j_scope) { mods[i]->mod_values[index_scope] = (char *)json_string_value(j_scope); } mods[i]->mod_values[(json_array_size(json_object_get(j_client, "scope")))] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d]->mod_values (scope)", i); has_error = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d] (scope)", i); has_error = 1; } } i++; } if (json_string_length(json_object_get(j_client, "password"))) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc(2 * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = add?LDAP_MOD_ADD:LDAP_MOD_REPLACE; mods[i]->mod_type = (char *)json_string_value(json_object_get(j_params, "password-property")); mods[i]->mod_values[0] = json_string_length(json_object_get(j_client, "password"))?generate_hash(get_digest_algorithm(j_params), json_string_value(json_object_get(j_client, "password"))):NULL; mods[i]->mod_values[1] = NULL; json_array_append_new(j_mod_value_free_array, json_integer(i)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d]->mod_values (password)", i); has_error = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d] (password)", i); has_error = 1; } i++; } json_object_foreach(j_client, field, j_property) { if (0 != o_strcmp(field, "client_id") && 0 != o_strcmp(field, "name") && 0 != o_strcmp(field, "password") && 0 != o_strcmp(field, "scope") && 0 != o_strcmp(field, "description") && 0 != o_strcmp(field, "enabled") && 0 != o_strcmp(field, "confidential")) { if ((j_format = json_object_get(json_object_get(j_params, "data-format"), field)) != NULL) { if (json_object_get(j_format, "write") != json_false()) { if (json_is_array(j_property) && json_object_get(json_object_get(json_object_get(j_params, "data-format"), field), "multiple") == json_true()) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc((json_array_size(j_property) + 1) * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = add?LDAP_MOD_ADD:LDAP_MOD_REPLACE; mods[i]->mod_type = (char *)json_string_value(json_object_get(j_format, "property")); json_array_foreach(j_property, index_scope, j_property_value) { if (0 == o_strcmp("base64", json_string_value(json_object_get(json_object_get(json_object_get(j_params, "data-format"), field), "convert")))) { if (o_base64_decode((const unsigned char *)json_string_value(j_property_value), json_string_length(j_property_value), NULL, &value_enc_len)) { if ((value_enc = o_malloc(value_enc_len+1)) != NULL) { if (o_base64_decode((const unsigned char *)json_string_value(j_property_value), json_string_length(j_property_value), value_enc, &value_enc_len)) { value_enc[value_enc_len] = '\0'; mods[i]->mod_values[index_scope] = (char *)value_enc; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error o_base64_decode for LDAP property '%s' (1-2)", json_string_value(j_property_value)); has_error = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for value_enc (1)"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error o_base64_decode for LDAP property '%s' (1-1)", json_string_value(j_property_value)); has_error = 1; } } else if (0 == o_strcmp("jwks", json_string_value(json_object_get(json_object_get(json_object_get(j_params, "data-format"), field), "convert")))) { mods[i]->mod_values[index_scope] = json_dumps(j_property_value, JSON_COMPACT); } else { mods[i]->mod_values[index_scope] = (char *)json_string_value(j_property_value); } } mods[i]->mod_values[json_array_size(j_property)] = NULL; if (0 == o_strcmp("base64", json_string_value(json_object_get(json_object_get(json_object_get(j_params, "data-format"), field), "convert"))) || 0 == o_strcmp("jwks", json_string_value(json_object_get(json_object_get(json_object_get(j_params, "data-format"), field), "convert")))) { json_array_append_new(j_mod_value_free_array, json_integer(i)); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d]->mod_values (%s)", json_string_value(json_object_get(j_format, "property")), i); has_error = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d] (%s)", json_string_value(json_object_get(j_format, "property")), i); has_error = 1; } } else if (json_object_get(json_object_get(json_object_get(j_params, "data-format"), field), "multiple") != json_true()) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc(2 * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = add?LDAP_MOD_ADD:LDAP_MOD_REPLACE; mods[i]->mod_type = (char *)json_string_value(json_object_get(j_format, "property")); if (0 == o_strcmp("base64", json_string_value(json_object_get(json_object_get(json_object_get(j_params, "data-format"), field), "convert")))) { if (o_base64_decode((const unsigned char *)json_string_value(j_property), json_string_length(j_property), NULL, &value_enc_len)) { if ((value_enc = o_malloc(value_enc_len+1)) != NULL) { if (o_base64_decode((const unsigned char *)json_string_value(j_property), json_string_length(j_property), value_enc, &value_enc_len)) { value_enc[value_enc_len] = '\0'; mods[i]->mod_values[0] = (char *)value_enc; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error o_base64_decode for LDAP property '%s' (2-2)", json_string_value(j_property)); has_error = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for value_enc (2)"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error o_base64_decode for LDAP property '%s' (2-1)", json_string_value(j_property)); has_error = 1; } json_array_append_new(j_mod_value_free_array, json_integer(i)); } else if (0 == o_strcmp("jwks", json_string_value(json_object_get(json_object_get(json_object_get(j_params, "data-format"), field), "convert")))) { mods[i]->mod_values[0] = json_dumps(j_property, JSON_COMPACT); json_array_append_new(j_mod_value_free_array, json_integer(i)); } else { mods[i]->mod_values[0] = (char *)json_string_value(j_property); } mods[i]->mod_values[1] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d]->mod_values (%s)", json_string_value(json_object_get(j_format, "property")), i); has_error = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%d] (%s)", json_string_value(json_object_get(j_format, "property")), i); has_error = 1; } } else { y_log_message(Y_LOG_LEVEL_WARNING, "get_ldap_write_mod - Error field '%s' has invalid format", field); } i++; } } } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods"); has_error = 1; } if (has_error) { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - mods has error, cleaning memory"); json_array_foreach(j_mod_value_free_array, index, j_property) { for (i=0; mods[json_integer_value(j_property)]->mod_values[i] != NULL; i++) { o_free(mods[json_integer_value(j_property)]->mod_values[i]); } } for (i=0; i<nb_attr; i++) { o_free(mods[i]); } o_free(mods); mods = NULL; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error j_mod_value_free_array is NULL"); } return mods; } static json_t * get_scope_from_ldap(json_t * j_params, const char * ldap_scope_value) { json_t * j_element = NULL; const char * key = NULL, * value; if (json_object_get(j_params, "scope-property-match-correspondence") != NULL) { json_object_foreach(json_object_get(j_params, "scope-property-match-correspondence"), key, j_element) { value = json_string_value(j_element); if ((0 == o_strcmp("equals", json_string_value(json_object_get(j_params, "scope-property-match"))) && 0 == o_strcmp(value, ldap_scope_value)) || (0 == o_strcmp("contains", json_string_value(json_object_get(j_params, "scope-property-match"))) && NULL != o_strstr(ldap_scope_value, value)) || (0 == o_strcmp("starts-with", json_string_value(json_object_get(j_params, "scope-property-match"))) && 0 != o_strncmp(ldap_scope_value, value, o_strlen(value))) || (0 == o_strcmp("ends-with", json_string_value(json_object_get(j_params, "scope-property-match"))) && 0 != strcmp(ldap_scope_value + o_strlen(ldap_scope_value) - o_strlen(value), value))) { return json_string(key); } } } return json_string(ldap_scope_value); } static json_t * get_client_from_result(json_t * j_params, json_t * j_properties_client, LDAP * ldap, LDAPMessage * entry) { json_t * j_client = json_object(), * j_property = NULL, * j_scope; const char * field = NULL; char * str_scope; struct berval ** result_values = NULL; int i; unsigned char * value_enc = NULL; size_t value_enc_len = 0; if (j_client != NULL) { json_object_foreach(j_properties_client, field, j_property) { result_values = ldap_get_values_len(ldap, entry, json_string_value(j_property)); if (ldap_count_values_len(result_values) > 0) { if (0 == o_strcmp(field, "scope")) { json_object_set_new(j_client, field, json_array()); for (i=0; i<ldap_count_values_len(result_values); i++) { str_scope = o_strndup(result_values[i]->bv_val, result_values[i]->bv_len); j_scope = get_scope_from_ldap(j_params, str_scope); o_free(str_scope); if (j_scope != NULL) { json_array_append_new(json_object_get(j_client, field), j_scope); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_from_result - Error get_scope_from_ldap"); } } } else if (0 == o_strcmp(field, "client_id") || 0 == o_strcmp(field, "name") || 0 == o_strcmp(field, "description") || (json_object_get(json_object_get(j_params, "data-format"), field) != NULL && json_object_get(json_object_get(json_object_get(j_params, "data-format"), field), "multiple") != json_true())) { if (0 == o_strcmp("base64", json_string_value(json_object_get(json_object_get(json_object_get(j_params, "data-format"), field), "convert")))) { if (o_base64_encode((const unsigned char *)result_values[0]->bv_val, result_values[0]->bv_len, NULL, &value_enc_len)) { if ((value_enc = o_malloc(value_enc_len+1)) != NULL) { if (o_base64_encode((const unsigned char *)result_values[0]->bv_val, result_values[0]->bv_len, value_enc, &value_enc_len)) { value_enc[value_enc_len] = '\0'; json_object_set_new(j_client, field, json_stringn((const char *)value_enc, value_enc_len)); } else { y_log_message(Y_LOG_LEVEL_WARNING, "get_client_from_result - Error o_base64_encode for LDAP property '%s' (2)", json_string_value(j_property)); } o_free(value_enc); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_from_result - Error allocating resources for value_enc"); } } else { y_log_message(Y_LOG_LEVEL_WARNING, "get_client_from_result - Error o_base64_encode for LDAP property '%s' (1)", json_string_value(j_property)); } } else if (0 == o_strcmp("jwks", json_string_value(json_object_get(json_object_get(json_object_get(j_params, "data-format"), field), "convert")))) { if (json_object_set_new(j_client, field, json_loads(result_values[0]->bv_val, JSON_DECODE_ANY, NULL))) { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_from_result - Error parsing value into JSON"); } } else { json_object_set_new(j_client, field, json_stringn(result_values[0]->bv_val, result_values[0]->bv_len)); } } else if (0 == o_strcmp(field, "confidential")) { json_object_set_new(j_client, field, (result_values[0]->bv_val[0]=='1'?json_true():json_false())); } else if (json_object_get(json_object_get(json_object_get(j_params, "data-format"), field), "multiple") == json_true()) { json_object_set_new(j_client, field, json_array()); for (i=0; i<ldap_count_values_len(result_values); i++) { if (0 == o_strcmp("base64", json_string_value(json_object_get(json_object_get(json_object_get(j_params, "data-format"), field), "convert")))) { if (o_base64_encode((const unsigned char *)result_values[i]->bv_val, result_values[i]->bv_len, NULL, &value_enc_len)) { if ((value_enc = o_malloc(value_enc_len+1)) != NULL) { if (o_base64_encode((const unsigned char *)result_values[i]->bv_val, result_values[i]->bv_len, value_enc, &value_enc_len)) { value_enc[value_enc_len] = '\0'; json_array_append_new(json_object_get(j_client, field), json_stringn((const char *)value_enc, value_enc_len)); } else { y_log_message(Y_LOG_LEVEL_WARNING, "get_client_from_result - Error o_base64_encode for LDAP property '%s' (2)", json_string_value(j_property)); } o_free(value_enc); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_from_result - Error allocating resources for value_enc"); } } else { y_log_message(Y_LOG_LEVEL_WARNING, "get_client_from_result - Error o_base64_encode for LDAP property '%s' (1)", json_string_value(j_property)); } } else if (0 == o_strcmp("jwks", json_string_value(json_object_get(json_object_get(json_object_get(j_params, "data-format"), field), "convert")))) { if (json_array_append_new(json_object_get(j_client, field), json_loads(result_values[i]->bv_val, JSON_DECODE_ANY, NULL))) { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_from_result - Error parsing value into JSON"); } } else { json_array_append_new(json_object_get(j_client, field), json_stringn(result_values[i]->bv_val, result_values[i]->bv_len)); } } } } // A ldap client is always enabled, until I find a standard way to do it json_object_set_new(j_client, "enabled", json_true()); ldap_value_free_len(result_values); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_from_result - Error allocating resources for j_client"); } return j_client; } static char * get_client_dn_from_client_id(json_t * j_params, LDAP * ldap, const char * client_id) { char * client_dn, * filter; int result; char * attrs[] = {NULL}; int attrsonly = 0; LDAPMessage * answer = NULL, * entry; char * str_result = NULL; int scope = LDAP_SCOPE_ONELEVEL; if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_SUBTREE; } else if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_CHILDREN; } filter = msprintf("(&(%s)(%s=%s))", json_string_value(json_object_get(j_params, "filter")), get_read_property(j_params, "client_id-property"), client_id); if ((result = ldap_search_ext_s(ldap, json_string_value(json_object_get(j_params, "base-search")), scope, filter, attrs, attrsonly, NULL, NULL, NULL, LDAP_NO_LIMIT, &answer)) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_dn_from_client_id - Error ldap search, base search: %s, filter, error message: %s: %s", json_string_value(json_object_get(j_params, "base-search")), filter, ldap_err2string(result)); } else { if (ldap_count_entries(ldap, answer) > 0) { entry = ldap_first_entry(ldap, answer); client_dn = ldap_get_dn(ldap, entry); str_result = o_strdup(client_dn); ldap_memfree(client_dn); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_dn_from_client_id - Error client_id not found '%s'", client_id); } ldap_msgfree(answer); } o_free(filter); return str_result; } json_t * client_module_load(struct config_module * config) { UNUSED(config); return json_pack("{si ss ss ss}", "result", G_OK, "name", "ldap", "display_name", "LDAP backend client module", "description", "Module to store clients in a LDAP server"); } int client_module_unload(struct config_module * config) { UNUSED(config); return G_OK; } json_t * client_module_init(struct config_module * config, int readonly, json_t * j_parameters, void ** cls) { UNUSED(config); json_t * j_properties, * j_return; char * error_message; j_properties = is_client_ldap_parameters_valid(j_parameters, readonly); if (check_result_value(j_properties, G_OK)) { *cls = json_incref(j_parameters); j_return = json_pack("{si}", "result", G_OK); } else if (check_result_value(j_properties, G_ERROR_PARAM)) { error_message = json_dumps(json_object_get(j_properties, "error"), JSON_COMPACT); y_log_message(Y_LOG_LEVEL_ERROR, "client_module_init database - Error parsing parameters"); y_log_message(Y_LOG_LEVEL_ERROR, error_message); o_free(error_message); j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", json_object_get(j_properties, "error")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_init database - Error is_client_database_parameters_valid"); j_return = json_pack("{sis[s]}", "result", G_ERROR, "error", "internal error"); } json_decref(j_properties); return j_return; } int client_module_close(struct config_module * config, void * cls) { UNUSED(config); json_decref((json_t *)cls); return G_OK; } size_t client_module_count_total(struct config_module * config, const char * pattern, void * cls) { UNUSED(config); json_t * j_params = (json_t *)cls; LDAP * ldap = connect_ldap_server(j_params); LDAPMessage * answer = NULL; char * attrs[] = { NULL }, * filter; int attrsonly = 0; size_t counter = 0; int result, scope = LDAP_SCOPE_ONELEVEL; if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_SUBTREE; } else if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_CHILDREN; } if (ldap != NULL) { filter = get_ldap_filter_pattern(j_params, pattern); if ((result = ldap_search_ext_s(ldap, json_string_value(json_object_get(j_params, "base-search")), scope, filter, attrs, attrsonly, NULL, NULL, NULL, LDAP_NO_LIMIT, &answer)) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_count_total ldap - Error ldap search, base search: %s, filter: %s: %s", json_string_value(json_object_get(j_params, "base-search")), filter, ldap_err2string(result)); } else { counter = ldap_count_entries(ldap, answer); } ldap_msgfree(answer); ldap_unbind_ext(ldap, NULL, NULL); o_free(filter); } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_count_total ldap - Error connect_ldap_server"); } return counter; } json_t * client_module_get_list(struct config_module * config, const char * pattern, size_t offset, size_t limit, void * cls) { UNUSED(config); json_t * j_params = (json_t *)cls, * j_properties_client = NULL, * j_client_list, * j_client, * j_return; LDAP * ldap = connect_ldap_server(j_params); LDAPMessage * entry; int ldap_result; int scope = LDAP_SCOPE_ONELEVEL; char * filter = NULL; char ** attrs = NULL; int attrsonly = 0; /* paged control variables */ struct berval new_cookie, * cookie = NULL; int more_page, l_errcode = 0; LDAPControl * page_control = NULL, * search_controls[2] = { NULL, NULL }, ** returned_controls = NULL; LDAPMessage * l_result = NULL; ber_int_t total_count; if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_SUBTREE; } else if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_CHILDREN; } if (ldap != NULL) { // Connection successful, doing ldap search filter = get_ldap_filter_pattern(j_params, pattern); attrs = get_ldap_read_attributes(j_params, 0, (j_properties_client = json_object())); j_client_list = json_array(); do { ldap_result = ldap_create_page_control(ldap, json_integer_value(json_object_get(j_params, "page-size")), cookie, 0, &page_control); if (ldap_result != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_get_list ldap - Error ldap_create_page_control, message: %s", ldap_err2string(ldap_result)); break; } search_controls[0] = page_control; ldap_result = ldap_search_ext_s(ldap, json_string_value(json_object_get(j_params, "base-search")), scope, filter, attrs, attrsonly, search_controls, NULL, NULL, 0, &l_result); if ((ldap_result != LDAP_SUCCESS) & (ldap_result != LDAP_PARTIAL_RESULTS)) { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_get_list ldap - Error ldap search, base search: %s, filter: %s, error message: %s", json_string_value(json_object_get(j_params, "base-search")), filter, ldap_err2string(ldap_result)); break; } ldap_result = ldap_parse_result(ldap, l_result, &l_errcode, NULL, NULL, NULL, &returned_controls, 0); if (ldap_result != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_get_list ldap - Error ldap_parse_result, message: %s", ldap_err2string(ldap_result)); break; } if (cookie != NULL) { ber_bvfree(cookie); cookie = NULL; } if (returned_controls != NULL) { ldap_result = ldap_parse_pageresponse_control(ldap, *returned_controls, &total_count, &new_cookie); if (ldap_result != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_get_list ldap - Error ldap_parse_pageresponse_control, message: %s", ldap_err2string(ldap_result)); break; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_get_list ldap - Error returned_controls is NULL"); break; } cookie = ber_memalloc( sizeof( struct berval ) ); if (cookie != NULL) { *cookie = new_cookie; if (cookie->bv_val != NULL && (o_strlen(cookie->bv_val) > 0)) { more_page = 1; } else { more_page = 0; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_get_list ldap - Error ber_malloc returned NULL"); break; } if (returned_controls != NULL) { ldap_controls_free(returned_controls); returned_controls = NULL; } search_controls[0] = NULL; ldap_control_free(page_control); page_control = NULL; entry = ldap_first_entry(ldap, l_result); for (;entry !=NULL && offset > 0; entry = ldap_next_entry(ldap, entry)) { offset--; } while (entry != NULL && limit) { j_client = get_client_from_result(j_params, j_properties_client, ldap, entry); if (j_client != NULL) { json_array_append_new(j_client_list, j_client); } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_get_list ldap - Error get_client_from_result"); } entry = ldap_next_entry(ldap, entry); limit--; } ldap_msgfree(l_result); l_result = NULL; } while (more_page && limit); ldap_msgfree(l_result); l_result = NULL; o_free(filter); ber_bvfree(cookie); cookie = NULL; ldap_unbind_ext(ldap, NULL, NULL); j_return = json_pack("{sisO}", "result", G_OK, "list", j_client_list); json_decref(j_client_list); json_decref(j_properties_client); o_free(attrs); } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_get_list ldap - Error connect_ldap_server"); j_return = json_pack("{si}", "result", G_ERROR); } return j_return; } json_t * client_module_get(struct config_module * config, const char * client_id, void * cls) { UNUSED(config); json_t * j_params = (json_t *)cls, * j_properties_client = NULL, * j_client, * j_return; LDAP * ldap = connect_ldap_server(j_params); LDAPMessage * entry, * answer; int ldap_result; int scope = LDAP_SCOPE_ONELEVEL; char * filter = NULL; char ** attrs = NULL; int attrsonly = 0; if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_SUBTREE; } else if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_CHILDREN; } if (ldap != NULL) { // Connection successful, doing ldap search filter = msprintf("(&(%s)(%s=%s))", json_string_value(json_object_get(j_params, "filter")), get_read_property(j_params, "client_id-property"), client_id); attrs = get_ldap_read_attributes(j_params, 0, (j_properties_client = json_object())); if ((ldap_result = ldap_search_ext_s(ldap, json_string_value(json_object_get(j_params, "base-search")), scope, filter, attrs, attrsonly, NULL, NULL, NULL, LDAP_NO_LIMIT, &answer)) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_get ldap - Error ldap search, base search: %s, filter: %s: %s", json_string_value(json_object_get(j_params, "base-search")), filter, ldap_err2string(ldap_result)); j_return = json_pack("{si}", "result", G_ERROR); } else { // Looping in results, staring at offset, until the end of the list if (ldap_count_entries(ldap, answer) > 0) { entry = ldap_first_entry(ldap, answer); j_client = get_client_from_result(j_params, j_properties_client, ldap, entry); if (j_client != NULL) { j_return = json_pack("{sisO}", "result", G_OK, "client", j_client); } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_get_list ldap - Error get_client_from_result"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_client); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } json_decref(j_properties_client); o_free(attrs); o_free(filter); ldap_msgfree(answer); ldap_unbind_ext(ldap, NULL, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_get_list ldap - Error connect_ldap_server"); j_return = json_pack("{si}", "result", G_ERROR); } return j_return; } json_t * client_module_is_valid(struct config_module * config, const char * client_id, json_t * j_client, int mode, void * cls) { UNUSED(config); json_t * j_params = (json_t *)cls; json_t * j_result = json_array(), * j_element, * j_format, * j_value, * j_return, * j_cur_client; char * message; size_t index = 0, len = 0; const char * property; if (j_result != NULL) { if (mode == GLEWLWYD_IS_VALID_MODE_ADD) { if (!json_is_string(json_object_get(j_client, "client_id")) || !json_string_length(json_object_get(j_client, "client_id"))) { json_array_append_new(j_result, json_string("client_id is mandatory and must be a non empty string")); } else { j_cur_client = client_module_get(config, json_string_value(json_object_get(j_client, "client_id")), cls); if (check_result_value(j_cur_client, G_OK)) { json_array_append_new(j_result, json_string("client_id already exist")); } else if (!check_result_value(j_cur_client, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_is_valid database - Error client_module_get"); } json_decref(j_cur_client); } } else if ((mode == GLEWLWYD_IS_VALID_MODE_UPDATE || mode == GLEWLWYD_IS_VALID_MODE_UPDATE_PROFILE) && client_id == NULL) { json_array_append_new(j_result, json_string("client_id is mandatory on update mode")); } if (mode != GLEWLWYD_IS_VALID_MODE_UPDATE_PROFILE && json_object_get(j_client, "scope") != NULL) { if (!json_is_array(json_object_get(j_client, "scope"))) { json_array_append_new(j_result, json_string("scope must be a JSON array of string")); } else { json_array_foreach(json_object_get(j_client, "scope"), index, j_element) { if (!json_is_string(j_element) || !json_string_length(j_element)) { json_array_append_new(j_result, json_string("scope must be a JSON array of string")); } } } } if (mode != GLEWLWYD_IS_VALID_MODE_UPDATE_PROFILE && json_object_get(j_client, "password") != NULL && !json_is_string(json_object_get(j_client, "password"))) { json_array_append_new(j_result, json_string("password must be a string")); } if (json_object_get(j_client, "name") != NULL && (!json_is_string(json_object_get(j_client, "name")) || !json_string_length(json_object_get(j_client, "name")))) { json_array_append_new(j_result, json_string("name must be a non empty string")); } if (json_object_get(j_client, "description") != NULL && !json_is_string(json_object_get(j_client, "description"))) { json_array_append_new(j_result, json_string("description must be a string")); } if (json_object_get(j_client, "enabled") != NULL && !json_is_boolean(json_object_get(j_client, "enabled"))) { json_array_append_new(j_result, json_string("enabled must be a boolean")); } if (json_object_get(j_client, "confidential") != NULL && !json_is_boolean(json_object_get(j_client, "confidential"))) { json_array_append_new(j_result, json_string("confidential must be a boolean")); } json_object_foreach(j_client, property, j_element) { if (0 != o_strcmp(property, "client_id") && 0 != o_strcmp(property, "name") && 0 != o_strcmp(property, "description") && 0 != o_strcmp(property, "enabled") && 0 != o_strcmp(property, "confidential") && 0 != o_strcmp(property, "password") && 0 != o_strcmp(property, "client_secret") && 0 != o_strcmp(property, "source") && 0 != o_strcmp(property, "scope")) { j_format = json_object_get(json_object_get(j_params, "data-format"), property); if (json_object_get(j_format, "multiple") == json_true()) { if (!json_is_array(j_element)) { message = msprintf("%s must be an array", property); json_array_append_new(j_result, json_string(message)); o_free(message); } else { json_array_foreach(j_element, index, j_value) { if ((!json_is_string(j_value) || !json_string_length(j_value)) && 0 != o_strcmp("jwks", json_string_value(json_object_get(j_format, "convert")))) { message = msprintf("%s must contain a non empty string value", property); json_array_append_new(j_result, json_string(message)); o_free(message); } else if (0 == o_strcmp("base64", json_string_value(json_object_get(j_format, "convert")))) { if (!o_base64_decode((const unsigned char *)json_string_value(j_value), json_string_length(j_value), NULL, &len)) { message = msprintf("%s must contain a base64 encoded string value", property); json_array_append_new(j_result, json_string(message)); o_free(message); } } } } } else { if (!json_is_string(j_element) && 0 != o_strcmp("jwks", json_string_value(json_object_get(j_format, "convert")))) { message = msprintf("%s must contain a string value", property); json_array_append_new(j_result, json_string(message)); o_free(message); } else if (0 == o_strcmp("base64", json_string_value(json_object_get(j_format, "convert"))) && json_string_length(j_element)) { if (!o_base64_decode((const unsigned char *)json_string_value(j_element), json_string_length(j_element), NULL, &len)) { message = msprintf("%s must contain a base64 encoded string value", property); json_array_append_new(j_result, json_string(message)); o_free(message); } } } } } if (json_array_size(j_result)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_result); } else { j_return = json_pack("{si}", "result", G_OK); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_is_valid ldap - Error allocating resources for j_result"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } return j_return; } int client_module_add(struct config_module * config, json_t * j_client, void * cls) { UNUSED(config); json_t * j_params = (json_t *)cls, * j_mod_value_free_array = NULL, * j_element = NULL; LDAP * ldap = connect_ldap_server(j_params); int ret, i, result; LDAPMod ** mods = NULL; char * new_dn; size_t index = 0; if (ldap != NULL) { mods = get_ldap_write_mod(j_params, j_client, 1, (j_mod_value_free_array = json_array())); if (mods != NULL) { new_dn = msprintf("%s=%s,%s", json_string_value(json_object_get(j_params, "rdn-property")), json_string_value(json_object_get(j_client, "client_id")), json_string_value(json_object_get(j_params, "base-search"))); if (new_dn != NULL) { if ((result = ldap_add_ext_s(ldap, new_dn, mods, NULL, NULL)) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_add ldap - Error adding new client %s in the ldap backend: %s", new_dn, ldap_err2string(result)); ret = G_ERROR; } else { ret = G_OK; } o_free(new_dn); } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_add ldap - Error allocating resources for new_dn"); ret = G_ERROR; } json_array_foreach(j_mod_value_free_array, index, j_element) { for (i=0; mods[json_integer_value(j_element)]->mod_values[i] != NULL; i++) { o_free(mods[json_integer_value(j_element)]->mod_values[i]); } } json_decref(j_mod_value_free_array); for (i=0; mods[i] != NULL; i++) { o_free(mods[i]->mod_values); o_free(mods[i]); } o_free(mods); } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_add ldap - Error get_ldap_write_mod"); ret = G_ERROR; } ldap_unbind_ext(ldap, NULL, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_add ldap - Error connect_ldap_server"); ret = G_ERROR; } return ret; } int client_module_update(struct config_module * config, const char * client_id, json_t * j_client, void * cls) { UNUSED(config); json_t * j_params = (json_t *)cls, * j_mod_value_free_array, * j_element = NULL; LDAP * ldap = connect_ldap_server(j_params); int ret, i, result; LDAPMod ** mods = NULL; char * cur_dn; size_t index = 0; if (ldap != NULL) { mods = get_ldap_write_mod(j_params, j_client, 0, (j_mod_value_free_array = json_array())); if (mods != NULL) { cur_dn = get_client_dn_from_client_id(j_params, ldap, client_id); if (cur_dn != NULL) { if ((result = ldap_modify_ext_s(ldap, cur_dn, mods, NULL, NULL)) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_update ldap - Error updating client %s in the ldap backend: %s", cur_dn, ldap_err2string(result)); ret = G_ERROR; } else { ret = G_OK; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_update ldap - Error get_client_dn_from_client_id"); ret = G_ERROR; } o_free(cur_dn); json_array_foreach(j_mod_value_free_array, index, j_element) { for (i=0; mods[json_integer_value(j_element)]->mod_values[i] != NULL; i++) { o_free(mods[json_integer_value(j_element)]->mod_values[i]); } } json_decref(j_mod_value_free_array); for (i=0; mods[i] != NULL; i++) { o_free(mods[i]->mod_values); o_free(mods[i]); } o_free(mods); } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_update ldap - Error get_ldap_write_mod"); ret = G_ERROR; } ldap_unbind_ext(ldap, NULL, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_update ldap - Error connect_ldap_server"); ret = G_ERROR; } return ret; } int client_module_delete(struct config_module * config, const char * client_id, void * cls) { UNUSED(config); json_t * j_params = (json_t *)cls; LDAP * ldap = connect_ldap_server(j_params); int ret, result; char * cur_dn; if (ldap != NULL) { cur_dn = get_client_dn_from_client_id(j_params, ldap, client_id); if (cur_dn != NULL) { if ((result = ldap_delete_ext_s(ldap, cur_dn, NULL, NULL)) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_delete ldap - Error deleting client %s in the ldap backend: %s", cur_dn, ldap_err2string(result)); ret = G_ERROR; } else { ret = G_OK; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_update ldap - Error get_client_dn_from_client_id"); ret = G_ERROR; } o_free(cur_dn); ldap_unbind_ext(ldap, NULL, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_update ldap - Error connect_ldap_server"); ret = G_ERROR; } return ret; } int client_module_check_password(struct config_module * config, const char * client_id, const char * password, void * cls) { UNUSED(config); json_t * j_params = (json_t *)cls; LDAP * ldap = connect_ldap_server(j_params); LDAPMessage * entry, * answer; int ldap_result, result_login, result; char * client_dn = NULL; int scope = LDAP_SCOPE_ONELEVEL; char * filter = NULL; char * attrs[] = {"memberOf", NULL, NULL}; int attrsonly = 0; char * ldap_mech = LDAP_SASL_SIMPLE; struct berval cred; struct berval *servcred; if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_SUBTREE; } else if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_CHILDREN; } if (ldap != NULL) { // Connection successful, doing ldap search filter = msprintf("(&(%s)(%s=%s))", json_string_value(json_object_get(j_params, "filter")), get_read_property(j_params, "client_id-property"), client_id); if ((ldap_result = ldap_search_ext_s(ldap, json_string_value(json_object_get(j_params, "base-search")), scope, filter, attrs, attrsonly, NULL, NULL, NULL, LDAP_NO_LIMIT, &answer)) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_check_password ldap - Error ldap search, base search: %s, filter: %s: %s", json_string_value(json_object_get(j_params, "base-search")), filter, ldap_err2string(ldap_result)); result = G_ERROR; } else { if (ldap_count_entries(ldap, answer) > 0) { // Testing the first result to client_id with the given password entry = ldap_first_entry(ldap, answer); client_dn = ldap_get_dn(ldap, entry); cred.bv_val = (char *)password; cred.bv_len = o_strlen(password); result_login = ldap_sasl_bind_s(ldap, client_dn, ldap_mech, &cred, NULL, NULL, &servcred); ldap_memfree(client_dn); if (result_login == LDAP_SUCCESS) { result = G_OK; } else { result = G_ERROR_UNAUTHORIZED; } } else { result = G_ERROR_NOT_FOUND; } } o_free(filter); ldap_msgfree(answer); ldap_unbind_ext(ldap, NULL, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_module_check_password ldap - Error connect_ldap_server"); result = G_ERROR; } return result; } ������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/client/mock.c��������������������������������������������������������������������0000664�0000000�0000000�00000061277�14156463140�0016541�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * Mock client module * * Copyright 2016-2020 Nicolas Mora <mail@babelouest.org> * * The MIT License (MIT) * * 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. * */ #include <string.h> #include <jansson.h> #include <yder.h> #include <orcania.h> #include "glewlwyd-common.h" /** * * Note on the client module * * The JSON object of the client has the following format concerning the reserved properties: * { * "client_id": string, client_id of the user (its login), must be at most 128 characters and unique among the current instance * "name": string, full name * "description": string, description for the client * "confidential": boolean, flag to set if the user is confidential or not (used in oauth2 and oidc) * "scope": array of strings, scopes available for the user, each scope must be a string of at most 128 characters * "enabled": boolean, if false, the user won't be able to connect * } * * - The username shouldbn't be updated after creation * - How the password is stored and encrypted is up to the implementation. * Although the password encrypted or not SHOULDN'T be returned in the user object * - The scope values mustn't be updated in profile mode, to avoid a user to change his or her own credential * - The "enabled" property is mandatory in the returned values of user_module_get_list or user_module_get * If a user doesn't have "enabled":true set, then it will be unavailable for connection * * The only mandatory values are username and anabled, other values are optional * Other values can be handled by the module, it's up to the implementation * * struct config_module { * const char * external_url; // Absolute url of the glewlwyd service * const char * login_url; // Relative url of the login page * const char * admin_scope; // Value of the g_admin scope * const char * profile_scope; // Value of the g_profile scope * struct _h_connection * conn; // Hoel structure to access to the database * digest_algorithm hash_algorithm; // Hash algorithm used in Glewlwyd * struct config_elements * glewlwyd_config; // Pointer to the global config structure * // Function used to return a user object * json_t * (* glewlwyd_module_callback_get_user)(struct config_module * config, const char * username); * // Function used to update a user * int (* glewlwyd_module_callback_set_user)(struct config_module * config, const char * username, json_t * j_user); * // Function used to check the validity of a user's password * int (* glewlwyd_module_callback_check_user_password)(struct config_module * config, const char * username, const char * password); * }; * */ static int json_has_str_pattern_case(json_t * j_source, const char * pattern) { const char * key = NULL; size_t index = 0; json_t * j_element = NULL; if (j_source != NULL) { if (json_is_string(j_source) && o_strcasestr(json_string_value(j_source), pattern) != NULL) { return 1; } else if (json_is_object(j_source)) { json_object_foreach(j_source, key, j_element) { if (json_has_str_pattern_case(j_element, pattern)) { return 1; } } return 0; } else if (json_is_array(j_source)) { json_array_foreach(j_source, index, j_element) { if (json_has_str_pattern_case(j_element, pattern)) { return 1; } } return 0; } else { return 0; } } else { return 0; } } /** * * client_module_load * * Executed once when Glewlwyd service is started * Used to identify the module and to show its parameters on init * You can also use it to load resources that are required once for all * instance modules for example * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * name: string, mandatory, name of the module, must be unique among other scheme modules * display_name: string, optional, long name of the module * description: string, optional, description for the module * parameters: object, optional, parameters description for the module * } * * Example: * { * result: G_OK, * name: "mock", * display_name: "Mock scheme module", * description: "Mock scheme module for glewlwyd tests", * parameters: { * mock-value: { * type: "string", * mandatory: true * } * } * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ json_t * client_module_load(struct config_module * config) { UNUSED(config); return json_pack("{sissssss}", "result", G_OK, "name", "mock", "display_name", "Mock scheme module", "description", "Mock scheme module for glewlwyd tests"); } /** * * client_module_unload * * Executed once when Glewlwyd service is stopped * You can use it to release resources that are required once for all * instance modules for example * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ int client_module_unload(struct config_module * config) { UNUSED(config); return G_OK; } /** * * client_module_init * * Initialize an instance of this module declared in Glewlwyd service. * If required, you must dynamically allocate a pointer to the configuration * for this instance and pass it to *cls * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, G_ERROR_PARAM on input parameters error, another value on error) * error: array of strings containg the list of input errors, mandatory on result G_ERROR_PARAM, ignored otherwise * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_parameters: used to initialize an instance in JSON format * The module must validate itself its parameters * @parameter cls: will contain an allocated void * pointer that will be sent back * as void * in all module functions * */ json_t * client_module_init(struct config_module * config, int readonly, json_t * j_parameters, void ** cls) { UNUSED(readonly); const char * prefix = ""; json_t * j_return; if (json_object_get(j_parameters, "error") == NULL) { if (json_string_length(json_object_get(j_parameters, "client-id-prefix"))) { prefix = json_string_value(json_object_get(j_parameters, "client-id-prefix")); } *cls = (void*)json_pack("[{ss+ ss ss so s[ssssss] s[sss] ss s[] so}{ss+ ss ss so s[s] s[s] s[] so}{ss+ ss ss so ss s[ssssssss] s[ss] ss s[ss] so}{ss+ ss ss so ss s[ssss] s[s] ss so ss}]", "client_id", prefix, "client1_id", "name", "client1", "description", "Client mock 1", "confidential", json_false(), "authorization_type", "code", "token", "id_token", "none", "refresh_token", "delete_token", "redirect_uri", "../../test-oauth2.html?param=client1_cb1", "../../test-oauth2.html?param=client1_cb2", "../../test-oidc.html?param=client1_cb1", "sector_identifier_uri", "https://sector1.glewlwyd.tld", "scope", "enabled", json_true(), "client_id", prefix, "client2_id", "name", "client2", "description", "Client mock 2", "confidential", json_false(), "authorization_type", "code", "redirect_uri", "../../test-oauth2.html?param=client2", "scope", "enabled", json_true(), "client_id", prefix, "client3_id", "name", "client3", "description", "Client mock 3", "confidential", json_true(), "password", "password", "authorization_type", "code", "token", "id_token", "none", "password", "client_credentials", "refresh_token", "delete_token", "redirect_uri", "../../test-oauth2.html?param=client3", "../../test-oidc.html?param=client3", "sector_identifier_uri", "https://sector1.glewlwyd.tld", "scope", "scope2", "scope3", "enabled", json_true(), "client_id", prefix, "client4_id", "name", "client4", "description", "Client mock 4", "confidential", json_true(), "client_secret", "secret", "authorization_type", "code", "token", "id_token", "client_credentials", "redirect_uri", "../../test-oidc.html?param=client4", "sector_identifier_uri", "https://sector4.glewlwyd.tld", "enabled", json_true(), "request_object_signing_alg", "HS256"); y_log_message(Y_LOG_LEVEL_DEBUG, "client_module_init - success %s %s, prefix: '%s'", config->profile_scope, config->admin_scope, prefix); j_return = json_pack("{si}", "result", G_OK); } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "Error input parameters"); } return j_return; } /** * * client_module_close * * Close an instance of this module declared in Glewlwyd service. * You must free the memory previously allocated in * the client_module_init function as void * cls * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter cls: pointer to the void * cls value allocated in client_module_init * */ int client_module_close(struct config_module * config, void * cls) { UNUSED(config); y_log_message(Y_LOG_LEVEL_DEBUG, "client_module_close - success"); json_decref((json_t *)cls); return G_OK; } /** * * client_module_count_total * * Return the total number of clients handled by this module corresponding * to the given pattern * * @return value: The total of corresponding clients * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter pattern: The pattern to match for the clients. How the * pattern is used is up to the implementation. * Glewlwyd recommends to match the pattern with the * client_id, name and description value for each clients * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ size_t client_module_count_total(struct config_module * config, const char * pattern, void * cls) { UNUSED(config); size_t index = 0, total; json_t * j_user = NULL; if (o_strlen(pattern)) { total = 0; json_array_foreach((json_t *)cls, index, j_user) { if (json_has_str_pattern_case(j_user, pattern)) { total++; } } } else { total = json_array_size((json_t *)cls); } return total; } /** * * client_module_get_list * * Return a list of clients handled by this module corresponding * to the given pattern between the specified offset and limit * These are the client objects returned to the administrator * * @return value: A list of corresponding clients or an empty list * using the following JSON format: {"result":G_OK,"list":[{client object}]} * On error, this function must return another value for "result" * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter pattern: The pattern to match for the clients. How the * pattern is used is up to the implementation. * Glewlwyd recommends to match the pattern with the * client_id, name and description value for each clients * @pattern offset: The offset to reduce the returned list among the total list * @pattern limit: The maximum number of clients to return * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ json_t * client_module_get_list(struct config_module * config, const char * pattern, size_t offset, size_t limit, void * cls) { UNUSED(config); json_t * j_user = NULL, * j_array, * j_array_pattern, * j_return, * j_user_copy; size_t index = 0, counter = 0; if (limit > 0) { if (o_strlen(pattern)) { j_array_pattern = json_array(); json_array_foreach((json_t *)cls, index, j_user) { if (json_has_str_pattern_case(j_user, pattern)) { json_array_append(j_array_pattern, j_user); } } } else { j_array_pattern = json_copy((json_t *)cls); } j_array = json_array(); if (j_array != NULL) { json_array_foreach(j_array_pattern, index, j_user) { if (index >= offset && (offset + counter) < json_array_size(j_array_pattern) && counter < limit) { j_user_copy = json_deep_copy(j_user); json_object_del(j_user_copy, "password"); json_array_append_new(j_array, j_user_copy); counter++; } } j_return = json_pack("{sisO}", "result", G_OK, "list", j_array); json_decref(j_array); } else { j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_array_pattern); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } /** * * client_module_get * * Return a client object handled by this module corresponding * to the client_id specified * * @return value: G_OK and the corresponding client * G_ERROR_NOT_FOUND if client_id is not found * The returned format is {"result":G_OK,"client":{client object}} * On error, this function must return another value for "result" * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter client_id: the client_id to match, must be case insensitive * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ json_t * client_module_get(struct config_module * config, const char * client_id, void * cls) { UNUSED(config); json_t * j_client = NULL, * j_return = NULL; size_t index = 0; if (client_id != NULL && o_strlen(client_id)) { json_array_foreach((json_t *)cls, index, j_client) { if (0 == o_strcmp(client_id, json_string_value(json_object_get(j_client, "client_id")))) { j_return = json_pack("{siso}", "result", G_OK, "client", json_deep_copy(j_client)); break; } } if (j_return == NULL) { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } else { j_return = json_pack("{si}", "result", G_ERROR); } return j_return; } /** * * client_module_is_valid * * Validate if a client is valid to save for the specified mode * * @return value: G_OK if the client is valid * G_ERROR_PARAM and an array containing the errors in string format * The returned format is {"result":G_OK} on success * {"result":G_ERROR_PARAM,"error":["error 1","error 2"]} on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter client_id: the client_id to match, must be case insensitive * @parameter j_client: The client to validate * @parameter mode: The mode corresponding to the context, values available are: * - GLEWLWYD_IS_VALID_MODE_ADD: Add a client by an administrator * Note: in this mode, the module musn't check for already existing client, * This is already handled by Glewlwyd * - GLEWLWYD_IS_VALID_MODE_UPDATE: Update a client by an administrator * @parameter cls: pointer to the void * cls value allocated in client_module_init * */ json_t * client_module_is_valid(struct config_module * config, const char * client_id, json_t * j_client, int mode, void * cls) { UNUSED(config); UNUSED(cls); json_t * j_return = NULL; if ((mode == GLEWLWYD_IS_VALID_MODE_UPDATE || mode == GLEWLWYD_IS_VALID_MODE_UPDATE_PROFILE) && client_id == NULL) { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "client_id is mandatory on update mode"); } else { if (json_is_object(j_client)) { if (mode == GLEWLWYD_IS_VALID_MODE_ADD) { if (json_is_string(json_object_get(j_client, "client_id")) && json_string_length(json_object_get(j_client, "client_id")) <= 128) { j_return = json_pack("{si}", "result", G_OK); } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "client_id must be a string value of maximum 128 characters"); } } else { j_return = json_pack("{si}", "result", G_OK); } } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "client must be a JSON object"); } } return j_return; } /** * * client_module_add * * Add a new client by an administrator * * @return value: G_OK on success * Another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_client: The client to add * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int client_module_add(struct config_module * config, json_t * j_client, void * cls) { UNUSED(config); json_array_append((json_t *)cls, j_client); return G_OK; } /** * * client_module_update * * Update an existing client by an administrator * * @return value: G_OK on success * Another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter client_id: the client_id to match, must be case insensitive * @parameter j_client: The client to update. If this function must replace all values or * only the given ones or any other solution is up to the implementation * @parameter cls: pointer to the void * cls value allocated in client_module_init * */ int client_module_update(struct config_module * config, const char * client_id, json_t * j_client, void * cls) { UNUSED(config); size_t index = 0; int ret, found = 0; json_t * j_element = NULL, * j_property; const char * key; json_array_foreach((json_t *)cls, index, j_element) { if (0 == o_strcmp(client_id, json_string_value(json_object_get(j_element, "client_id")))) { json_object_set_new(j_client, "client_id", json_string(client_id)); json_object_foreach(j_client, key, j_property) { if (j_property != json_null()) { json_object_set(j_element, key, j_property); } else { json_object_del(j_element, key); } } ret = G_OK; found = 1; break; } } if (!found) { ret = G_ERROR_NOT_FOUND; } return ret; } /** * * client_module_delete * * Delete an existing client by an administrator * * @return value: G_OK on success * Another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter client_id: the client_id to match, must be case insensitive * @parameter cls: pointer to the void * cls value allocated in client_module_init * */ int client_module_delete(struct config_module * config, const char * client_id, void * cls) { UNUSED(config); json_t * j_client = NULL; size_t index = 0; int ret, found = 0; json_array_foreach((json_t *)cls, index, j_client) { if (0 == o_strcmp(client_id, json_string_value(json_object_get(j_client, "client_id")))) { json_array_remove((json_t *)cls, index); ret = G_OK; found = 1; break; } } if (!found) { ret = G_ERROR_NOT_FOUND; } return ret; } /** * * client_module_check_password * * Validate the password of an existing client * * @return value: G_OK on success * Another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter client_id: the client_id to match, must be case insensitive * @parameter password: the password to validate * @parameter cls: pointer to the void * cls value allocated in client_module_init * */ int client_module_check_password(struct config_module * config, const char * client_id, const char * password, void * cls) { UNUSED(config); int ret; json_t * j_client = client_module_get(config, client_id, cls); if (check_result_value(j_client, G_OK)) { if (json_object_get(json_object_get(j_client, "client"), "confidential") == json_true() && 0 == o_strcmp(password, "password")) { ret = G_OK; } else { ret = G_ERROR_UNAUTHORIZED; } } else { ret = G_ERROR_NOT_FOUND; } json_decref(j_client); return ret; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/glewlwyd-common.h����������������������������������������������������������������0000664�0000000�0000000�00000073070�14156463140�0017455�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * Declarations for common constants and prototypes used in Glewlwyd main program and modules * * Copyright 2016-2021 Nicolas Mora <mail@babelouest.org> * * The MIT License (MIT) * * 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. * */ #ifndef __GLEWLWYD_COMMON_H_ #define __GLEWLWYD_COMMON_H_ #include <jansson.h> #include <ulfius.h> #include <yder.h> #include <hoel.h> #include <gnutls/gnutls.h> #include <gnutls/x509.h> #include "static_compressed_inmemory_website_callback.h" #include "http_compression_callback.h" /** * Result values used in the application */ #define G_OK 0 #define G_ERROR 1 #define G_ERROR_UNAUTHORIZED 2 #define G_ERROR_PARAM 3 #define G_ERROR_DB 4 #define G_ERROR_MEMORY 5 #define G_ERROR_NOT_FOUND 6 /** * Callback priority */ #define GLEWLWYD_CALLBACK_PRIORITY_ZERO 0 #define GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION 1 #define GLEWLWYD_CALLBACK_PRIORITY_PRE_APPLICATION 2 #define GLEWLWYD_CALLBACK_PRIORITY_APPLICATION 3 #define GLEWLWYD_CALLBACK_PRIORITY_COMPRESSION 4 #define GLEWLWYD_CALLBACK_PRIORITY_PLUGIN 5 #define GLEWLWYD_CALLBACK_PRIORITY_FILE 100 #define GLEWLWYD_CALLBACK_PRIORITY_POST_FILE 101 /** * Modes available when adding or modifying a user */ #define GLEWLWYD_IS_VALID_MODE_ADD 0 #define GLEWLWYD_IS_VALID_MODE_UPDATE 1 #define GLEWLWYD_IS_VALID_MODE_UPDATE_PROFILE 2 /** * Modes available of the availability of a scheme for a user */ #define GLEWLWYD_IS_NOT_AVAILABLE 0 #define GLEWLWYD_IS_AVAILABLE 1 #define GLEWLWYD_IS_REGISTERED 2 /** * Modes available to delete a profile */ #define GLEWLWYD_PROFILE_DELETE_UNAUTHORIZED 0x0000 #define GLEWLWYD_PROFILE_DELETE_AUTHORIZED 0x0001 #define GLEWLWYD_PROFILE_DELETE_DISABLE_PROFILE 0x0010 #define GLEWLWYD_DEFAULT_LIMIT_SIZE 100 #define GLEWLWYD_DEFAULT_SALT_LENGTH 16 #define G_PBKDF2_ITERATOR_DEFAULT 150000 #define SWITCH_DB_TYPE(T, M, S, P) \ ((T)==HOEL_DB_TYPE_MARIADB?\ (M):\ (T)==HOEL_DB_TYPE_SQLITE?\ (S):\ (P)\ ) #define MIN(A, B) ((A)>(B)?(B):(A)) #define MAX(A, B) ((A)>(B)?(A):(B)) /** Macro to avoid compiler warning when some parameters are unused and that's ok **/ #define UNUSED(x) (void)(x) /** * Digest format available */ typedef enum { digest_SHA1, digest_SSHA1, digest_SHA224, digest_SSHA224, digest_SHA256, digest_SSHA256, digest_SHA384, digest_SSHA384, digest_SHA512, digest_SSHA512, digest_MD5, digest_SMD5, digest_PBKDF2_SHA256, digest_CRYPT, digest_CRYPT_MD5, digest_CRYPT_SHA256, digest_CRYPT_SHA512, digest_PLAIN } digest_algorithm; struct config_module; /** * Structure used to store a user module */ struct _user_module { void * file_handle; char * name; char * display_name; char * description; double api_version; json_t * (* user_module_load)(struct config_module * config); int (* user_module_unload)(struct config_module * config); json_t * (* user_module_init)(struct config_module * config, int readonly, int multiple_passwords, json_t * j_parameters, void ** cls); int (* user_module_close)(struct config_module * config, void * cls); size_t (* user_module_count_total)(struct config_module * config, const char * pattern, void * cls); json_t * (* user_module_get_list)(struct config_module * config, const char * pattern, size_t offset, size_t limit, void * cls); json_t * (* user_module_get)(struct config_module * config, const char * username, void * cls); json_t * (* user_module_get_profile)(struct config_module * config, const char * username, void * cls); json_t * (* user_module_is_valid)(struct config_module * config, const char * username, json_t * j_user, int mode, void * cls); int (* user_module_add)(struct config_module * config, json_t * j_user, void * cls); int (* user_module_update)(struct config_module * config, const char * username, json_t * j_user, void * cls); int (* user_module_update_profile)(struct config_module * config, const char * username, json_t * j_user, void * cls); int (* user_module_delete)(struct config_module * config, const char * username, void * cls); int (* user_module_check_password)(struct config_module * config, const char * username, const char * password, void * cls); int (* user_module_update_password)(struct config_module * config, const char * username, const char ** new_passwords, size_t new_passwords_len, void * cls); }; /** * Structure used to store a user middleware module */ struct _user_middleware_module { void * file_handle; char * name; char * display_name; char * description; double api_version; json_t * (* user_middleware_module_load)(struct config_module * config); int (* user_middleware_module_unload)(struct config_module * config); json_t * (* user_middleware_module_init)(struct config_module * config, json_t * j_parameters, void ** cls); int (* user_middleware_module_close)(struct config_module * config, void * cls); int (* user_middleware_module_get_list)(struct config_module * config, json_t * j_user_list, void * cls); int (* user_middleware_module_get)(struct config_module * config, const char * username, json_t * j_user, void * cls); int (* user_middleware_module_get_profile)(struct config_module * config, const char * username, json_t * j_user, void * cls); int (* user_middleware_module_update)(struct config_module * config, const char * username, json_t * j_user, void * cls); int (* user_middleware_module_delete)(struct config_module * config, const char * username, json_t * j_user, void * cls); }; /** * Structure used to store a user module instance */ struct _user_module_instance { char * name; struct _user_module * module; void * cls; short int enabled; short int readonly; short int multiple_passwords; }; /** * Structure used to store a user middleware module instance */ struct _user_middleware_module_instance { char * name; struct _user_middleware_module * module; void * cls; short int enabled; }; /** * Structure used to store a client module */ struct _client_module { void * file_handle; char * name; char * display_name; char * description; double api_version; json_t * (* client_module_load)(struct config_module * config); int (* client_module_unload)(struct config_module * config); json_t * (* client_module_init)(struct config_module * config, int readonly, json_t * j_parameters, void ** cls); int (* client_module_close)(struct config_module * config, void * cls); size_t (* client_module_count_total)(struct config_module * config, const char * pattern, void * cls); json_t * (* client_module_get_list)(struct config_module * config, const char * pattern, size_t offset, size_t limit, void * cls); json_t * (* client_module_get)(struct config_module * config, const char * client_id, void * cls); json_t * (* client_module_is_valid)(struct config_module * config, const char * client_id, json_t * j_client, int mode, void * cls); int (* client_module_add)(struct config_module * config, json_t * j_client, void * cls); int (* client_module_update)(struct config_module * config, const char * client_id, json_t * j_client, void * cls); int (* client_module_delete)(struct config_module * config, const char * client_id, void * cls); int (* client_module_check_password)(struct config_module * config, const char * client_id, const char * password, void * cls); }; /** * Structure used to store a client module instance */ struct _client_module_instance { char * name; struct _client_module * module; void * cls; short int enabled; short int readonly; }; /** * Structure used to store a user auth schem module */ struct _user_auth_scheme_module { void * file_handle; char * name; char * display_name; char * description; double api_version; json_t * (* user_auth_scheme_module_load)(struct config_module * config); int (* user_auth_scheme_module_unload)(struct config_module * config); json_t * (* user_auth_scheme_module_init)(struct config_module * config, json_t * j_parameters, const char * mod_name, void ** cls); int (* user_auth_scheme_module_close)(struct config_module * config, void * cls); int (* user_auth_scheme_module_can_use)(struct config_module * config, const char * username, void * cls); json_t * (* user_auth_scheme_module_register)(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls); json_t * (* user_auth_scheme_module_register_get)(struct config_module * config, const struct _u_request * http_request, const char * username, void * cls); int (* user_auth_scheme_module_deregister)(struct config_module * config, const char * username, void * cls); json_t * (* user_auth_scheme_module_trigger)(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_trigger, void * cls); int (* user_auth_scheme_module_validate)(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls); json_t * (* user_auth_scheme_module_identify)(struct config_module * config, const struct _u_request * http_request, json_t * j_scheme_data, void * cls); }; /** * Structure used to store a user auth schem module instance */ struct _user_auth_scheme_module_instance { char * name; struct _user_auth_scheme_module * module; json_int_t guasmi_id; json_int_t guasmi_expiration; json_int_t guasmi_max_use; short int guasmi_allow_user_register; short int guasmi_forbid_user_profile; short int guasmi_forbid_user_reset_credential; void * cls; short int enabled; }; struct config_plugin; /** * Structure used to store a plugin module */ struct _plugin_module { void * file_handle; char * name; char * display_name; char * description; double api_version; json_t * (* plugin_module_load)(struct config_plugin * config); int (* plugin_module_unload)(struct config_plugin * config); json_t * (* plugin_module_init)(struct config_plugin * config, const char * name, json_t * j_parameters, void ** cls); int (* plugin_module_close)(struct config_plugin * config, const char * name, void * cls); int (* plugin_user_revoke)(struct config_plugin * config, const char * username, void * cls); }; /** * Structure used to store a plugin module instance */ struct _plugin_module_instance { char * name; struct _plugin_module * module; void * cls; short int enabled; }; #define GLWD_METRICS_AUTH_USER_VALID "glewlwyd_auth_user_valid" #define GLWD_METRICS_AUTH_USER_VALID_SCHEME "glewlwyd_auth_user_valid_scheme" #define GLWD_METRICS_AUTH_USER_INVALID "glewlwyd_auth_user_invalid" #define GLWD_METRICS_AUTH_USER_INVALID_SCHEME "glewlwyd_auth_user_invalid_scheme" #define GLWD_METRICS_DATABSE_ERROR "glewlwyd_database_error" /** * Structure used to store a prometheus metrics */ struct _glwd_metrics_data { char * label; size_t counter; }; struct _glwd_metric { char * name; char * help; struct _glwd_metrics_data * data; size_t data_size; }; /** * Structure used to store the global application config */ struct config_elements { char * config_file; unsigned int port; char * bind_address; char * bind_address_metrics; char * external_url; char * api_prefix; char * cookie_domain; unsigned int cookie_secure; unsigned int add_x_frame_option_header_deny; unsigned short log_mode_args; unsigned short log_level_args; unsigned long log_mode; unsigned long log_level; char * log_file; struct _u_compressed_inmemory_website_config * static_file_config; char * admin_scope; char * profile_scope; char * allow_origin; unsigned int use_secure_connection; char * secure_connection_key_file; char * secure_connection_pem_file; char * secure_connection_ca_file; struct _h_connection * conn; struct _u_instance * instance; unsigned int instance_initialized; struct _u_instance * instance_metrics; unsigned int instance_metrics_initialized; char * session_key; unsigned int session_expiration; unsigned int salt_length; digest_algorithm hash_algorithm; char * login_url; unsigned int delete_profile; pthread_mutex_t module_lock; char * user_module_path; struct _pointer_list * user_module_list; struct _pointer_list * user_module_instance_list; char * user_middleware_module_path; struct _pointer_list * user_middleware_module_list; struct _pointer_list * user_middleware_module_instance_list; char * client_module_path; struct _pointer_list * client_module_list; struct _pointer_list * client_module_instance_list; char * user_auth_scheme_module_path; struct _pointer_list * user_auth_scheme_module_list; struct _pointer_list * user_auth_scheme_module_instance_list; char * plugin_module_path; struct _pointer_list * plugin_module_list; struct _pointer_list * plugin_module_instance_list; struct config_plugin * config_p; struct config_module * config_m; unsigned short metrics_endpoint; unsigned int metrics_endpoint_port; unsigned short metrics_endpoint_admin_session; pthread_mutex_t metrics_lock; struct _pointer_list metrics_list; }; /** * Structure given to all plugin functions that will contain configuration on the * application host, and pointer to functions of the application host */ struct config_plugin { struct config_elements * glewlwyd_config; int (* glewlwyd_callback_add_plugin_endpoint)(struct config_plugin * config, const char * method, const char * name, const char * url, unsigned int priority, int (* callback)(const struct _u_request * request, struct _u_response * response, void * user_data), void * user_data); int (* glewlwyd_callback_remove_plugin_endpoint)(struct config_plugin * config, const char * method, const char * name, const char * url); // Session callback functions json_t * (* glewlwyd_callback_check_session_valid)(struct config_plugin * config, const struct _u_request * request, const char * scope_list); json_t * (* glewlwyd_callback_check_user_valid)(struct config_plugin * config, const char * username, const char * password, const char * scope_list); json_t * (* glewlwyd_callback_check_client_valid)(struct config_plugin * config, const char * client_id, const char * password); int (* glewlwyd_callback_trigger_session_used)(struct config_plugin * config, const struct _u_request * request, const char * scope_list); time_t (* glewlwyd_callback_get_session_age)(struct config_plugin * config, const struct _u_request * request, const char * scope_list); // Client callback functions json_t * (* glewlwyd_callback_get_client_granted_scopes)(struct config_plugin * config, const char * client_id, const char * username, const char * scope_list); // User CRUD json_t * (* glewlwyd_plugin_callback_get_user_list)(struct config_plugin * config, const char * pattern, size_t offset, size_t limit); json_t * (* glewlwyd_plugin_callback_get_user)(struct config_plugin * config, const char * username); json_t * (* glewlwyd_plugin_callback_get_user_profile)(struct config_plugin * config, const char * username); json_t * (* glewlwyd_plugin_callback_is_user_valid)(struct config_plugin * config, const char * username, json_t * j_user, int add); int (* glewlwyd_plugin_callback_add_user)(struct config_plugin * config, json_t * j_user); int (* glewlwyd_plugin_callback_set_user)(struct config_plugin * config, const char * username, json_t * j_user); int (* glewlwyd_plugin_callback_user_update_password)(struct config_plugin * config, const char * username, const char * password); int (* glewlwyd_plugin_callback_delete_user)(struct config_plugin * config, const char * username); // Client CRUD json_t * (* glewlwyd_plugin_callback_get_client_list)(struct config_plugin * config, const char * pattern, size_t offset, size_t limit); json_t * (* glewlwyd_plugin_callback_get_client)(struct config_plugin * config, const char * client_id); json_t * (* glewlwyd_plugin_callback_is_client_valid)(struct config_plugin * config, const char * client_id, json_t * j_client, int add); int (* glewlwyd_plugin_callback_add_client)(struct config_plugin * config, json_t * j_client); int (* glewlwyd_plugin_callback_set_client)(struct config_plugin * config, const char * client_id, json_t * j_client); int (* glewlwyd_plugin_callback_delete_client)(struct config_plugin * config, const char * client_id); // Register scheme functions json_t * (* glewlwyd_plugin_callback_get_scheme_module)(struct config_plugin * config, const char * mod_name); json_t * (* glewlwyd_plugin_callback_get_scheme_list)(struct config_plugin * config, const char * username); json_t * (* glewlwyd_plugin_callback_scheme_register)(struct config_plugin * config, const char * mod_name, const struct _u_request * http_request, const char * username, json_t * j_scheme_data); json_t * (* glewlwyd_plugin_callback_scheme_register_get)(struct config_plugin * config, const char * mod_name, const struct _u_request * http_request, const char * username); int (* glewlwyd_plugin_callback_scheme_deregister)(struct config_plugin * config, const char * mod_name, const char * username); int (* glewlwyd_plugin_callback_scheme_can_use)(struct config_plugin * config, const char * mod_name, const char * username); // Prometheus metrics functions int (* glewlwyd_plugin_callback_metrics_add_metric)(struct config_plugin * config, const char * name, const char * help); int (* glewlwyd_plugin_callback_metrics_increment_counter)(struct config_plugin * config, const char * name, size_t inc, ...); // Misc functions char * (* glewlwyd_callback_get_plugin_external_url)(struct config_plugin * config, const char * name); char * (* glewlwyd_callback_get_login_url)(struct config_plugin * config, const char * client_id, const char * scope_list, const char * callback_url, struct _u_map * additional_parameters); char * (* glewlwyd_callback_generate_hash)(struct config_plugin * config, const char * data); }; /** * Structure given to all module functions that will contain configuration on the * application host, and pointer to functions of the application host */ struct config_module { const char * external_url; const char * login_url; const char * admin_scope; const char * profile_scope; struct _h_connection * conn; digest_algorithm hash_algorithm; struct config_elements * glewlwyd_config; json_t * (* glewlwyd_module_callback_get_user)(struct config_module * config, const char * username); int (* glewlwyd_module_callback_set_user)(struct config_module * config, const char * username, json_t * j_user); int (* glewlwyd_module_callback_check_user_password)(struct config_module * config, const char * username, const char * password); json_t * (* glewlwyd_module_callback_check_user_session)(struct config_module * config, const struct _u_request * request, const char * username); int (* glewlwyd_module_callback_metrics_add_metric)(struct config_module * config, const char * name, const char * help); int (* glewlwyd_module_callback_metrics_increment_counter)(struct config_module * config, const char * name, size_t inc, ...); }; /** * Misc functions available in src/misc.c */ const char * get_ip_source(const struct _u_request * request); char * get_client_hostname(const struct _u_request * request); unsigned char random_at_most(unsigned char max, int nonce); char * rand_string(char * str, size_t str_size); char * rand_string_nonce(char * str, size_t str_size); char * rand_string_from_charset(char * str, size_t str_size, const char * charset); int rand_code(char * str, size_t str_size); char * join_json_string_array(json_t * j_array, const char * separator); int generate_digest(digest_algorithm digest, const char * data, int use_salt, char * out_digest); int generate_digest_raw(digest_algorithm digest, const unsigned char * data, size_t data_len, unsigned char * out_digest, size_t * out_digest_len); char * generate_hash(digest_algorithm digest, const char * data); int generate_digest_pbkdf2(const char * data, unsigned int iterations, const char * salt, char * out_digest); /** * Check if the result json object has a "result" element that is equal to value */ int check_result_value(json_t * result, const int value); /** * Modules functions prototypes */ /** * User functions prototypes */ json_t * user_module_load(struct config_module * config); int user_module_unload(struct config_module * config); json_t * user_module_init(struct config_module * config, int readonly, int multiple_passwords, json_t * j_parameters, void ** cls); int user_module_close(struct config_module * config, void * cls); size_t user_module_count_total(struct config_module * config, const char * pattern, void * cls); json_t * user_module_get_list(struct config_module * config, const char * pattern, size_t offset, size_t limit, void * cls); json_t * user_module_get(struct config_module * config, const char * username, void * cls); json_t * user_module_get_profile(struct config_module * config, const char * username, void * cls); json_t * user_module_is_valid(struct config_module * config, const char * username, json_t * j_user, int mode, void * cls); int user_module_add(struct config_module * config, json_t * j_user, void * cls); int user_module_update(struct config_module * config, const char * username, json_t * j_user, void * cls); int user_module_update_profile(struct config_module * config, const char * username, json_t * j_user, void * cls); int user_module_delete(struct config_module * config, const char * username, void * cls); int user_module_check_password(struct config_module * config, const char * username, const char * password, void * cls); int user_module_update_password(struct config_module * config, const char * username, const char ** new_passwords, size_t new_passwords_len, void * cls); /** * User middleware functions prototype */ json_t * user_middleware_module_load(struct config_module * config); int user_middleware_module_unload(struct config_module * config); json_t * user_middleware_module_init(struct config_module * config, json_t * j_parameters, void ** cls); int user_middleware_module_close(struct config_module * config, void * cls); int user_middleware_module_get_list(struct config_module * config, json_t * j_user_list, void * cls); int user_middleware_module_get(struct config_module * config, const char * username, json_t * j_user, void * cls); int user_middleware_module_get_profile(struct config_module * config, const char * username, json_t * j_user, void * cls); int user_middleware_module_update(struct config_module * config, const char * username, json_t * j_user, void * cls); int user_middleware_module_delete(struct config_module * config, const char * username, json_t * j_user, void * cls); /** * Client functions prototypes */ json_t * client_module_load(struct config_module * config); int client_module_unload(struct config_module * config); json_t * client_module_init(struct config_module * config, int readonly, json_t * j_parameters, void ** cls); int client_module_close(struct config_module * config, void * cls); size_t client_module_count_total(struct config_module * config, const char * pattern, void * cls); json_t * client_module_get_list(struct config_module * config, const char * pattern, size_t offset, size_t limit, void * cls); json_t * client_module_get(struct config_module * config, const char * client_id, void * cls); json_t * client_module_is_valid(struct config_module * config, const char * client_id, json_t * j_client, int mode, void * cls); int client_module_add(struct config_module * config, json_t * j_client, void * cls); int client_module_update(struct config_module * config, const char * client_id, json_t * j_client, void * cls); int client_module_delete(struct config_module * config, const char * client_id, void * cls); int client_module_check_password(struct config_module * config, const char * client_id, const char * password, void * cls); /** * Scheme functions prototypes */ json_t * user_auth_scheme_module_load(struct config_module * config); int user_auth_scheme_module_unload(struct config_module * config); json_t * user_auth_scheme_module_init(struct config_module * config, json_t * j_parameters, const char * mod_name, void ** cls); int user_auth_scheme_module_close(struct config_module * config, void * cls); int user_auth_scheme_module_can_use(struct config_module * config, const char * username, void * cls); json_t * user_auth_scheme_module_register(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls); json_t * user_auth_scheme_module_register_get(struct config_module * config, const struct _u_request * http_request, const char * username, void * cls); int user_auth_scheme_module_deregister(struct config_module * config, const char * username, void * cls); json_t * user_auth_scheme_module_trigger(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_trigger, void * cls); int user_auth_scheme_module_validate(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls); json_t * user_auth_scheme_module_identify(struct config_module * config, const struct _u_request * http_request, json_t * j_scheme_data, void * cls); /** * Plugin functions prototypes */ json_t * plugin_module_load(struct config_plugin * config); int plugin_module_unload(struct config_plugin * config); json_t * plugin_module_init(struct config_plugin * config, const char * name, json_t * j_parameters, void ** cls); int plugin_module_close(struct config_plugin * config, const char * name, void * cls); #endif ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/glewlwyd.c�����������������������������������������������������������������������0000664�0000000�0000000�00000532054�14156463140�0016164�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * main functions definitions * and main process start * * Copyright 2016-2021 Nicolas Mora <mail@babelouest.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see <http://www.gnu.org/licenses/>. * */ #include <string.h> #include <getopt.h> #include <libconfig.h> #include <signal.h> #include <dirent.h> #include <dlfcn.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include "glewlwyd.h" static pthread_mutex_t global_handler_close_lock; static pthread_cond_t global_handler_close_cond; /** * * Main function * * Initialize config structure, parse the arguments and the config file * Then run the webservice * */ int main (int argc, char ** argv) { struct config_elements * config = o_malloc(sizeof(struct config_elements)); struct _http_compression_config http_comression_config; int res, use_config_file = 0, use_config_env = 0; struct sockaddr_in bind_address, bind_address_metrics; pthread_t signal_thread_id; pthread_mutexattr_t mutexattr; static sigset_t close_signals; char * tmp, * tmp2; ulfius_global_init(); if (config == NULL) { fprintf(stderr, "Memory error - config\n"); return 1; } else if ((config->config_p = o_malloc(sizeof(struct config_plugin))) == NULL) { fprintf(stderr, "Memory error - config_p\n"); o_free(config); return 1; } else if ((config->config_m = o_malloc(sizeof(struct config_module))) == NULL) { fprintf(stderr, "Memory error - config_m\n"); o_free(config->config_p); o_free(config); return 1; } // Init plugin config structure config->config_p->glewlwyd_config = config; config->config_p->glewlwyd_callback_add_plugin_endpoint = &glewlwyd_callback_add_plugin_endpoint; config->config_p->glewlwyd_callback_remove_plugin_endpoint = &glewlwyd_callback_remove_plugin_endpoint; config->config_p->glewlwyd_callback_check_session_valid = &glewlwyd_callback_check_session_valid; config->config_p->glewlwyd_callback_check_user_valid = &glewlwyd_callback_check_user_valid; config->config_p->glewlwyd_callback_check_client_valid = &glewlwyd_callback_check_client_valid; config->config_p->glewlwyd_callback_get_client_granted_scopes = &glewlwyd_callback_get_client_granted_scopes; config->config_p->glewlwyd_callback_trigger_session_used = &glewlwyd_callback_trigger_session_used; config->config_p->glewlwyd_callback_get_session_age = &glewlwyd_callback_get_session_age; config->config_p->glewlwyd_callback_get_plugin_external_url = &glewlwyd_callback_get_plugin_external_url; config->config_p->glewlwyd_callback_get_login_url = &glewlwyd_callback_get_login_url; config->config_p->glewlwyd_callback_generate_hash = &glewlwyd_callback_generate_hash; config->config_p->glewlwyd_plugin_callback_get_user_list = &glewlwyd_plugin_callback_get_user_list; config->config_p->glewlwyd_plugin_callback_get_user = &glewlwyd_plugin_callback_get_user; config->config_p->glewlwyd_plugin_callback_get_user_profile = &glewlwyd_plugin_callback_get_user_profile; config->config_p->glewlwyd_plugin_callback_is_user_valid = &glewlwyd_plugin_callback_is_user_valid; config->config_p->glewlwyd_plugin_callback_add_user = &glewlwyd_plugin_callback_add_user; config->config_p->glewlwyd_plugin_callback_set_user = &glewlwyd_plugin_callback_set_user; config->config_p->glewlwyd_plugin_callback_user_update_password = &glewlwyd_plugin_callback_user_update_password; config->config_p->glewlwyd_plugin_callback_delete_user = &glewlwyd_plugin_callback_delete_user; config->config_p->glewlwyd_plugin_callback_get_client_list = &glewlwyd_plugin_callback_get_client_list; config->config_p->glewlwyd_plugin_callback_get_client = &glewlwyd_plugin_callback_get_client; config->config_p->glewlwyd_plugin_callback_is_client_valid = &glewlwyd_plugin_callback_is_client_valid; config->config_p->glewlwyd_plugin_callback_add_client = &glewlwyd_plugin_callback_add_client; config->config_p->glewlwyd_plugin_callback_set_client = &glewlwyd_plugin_callback_set_client; config->config_p->glewlwyd_plugin_callback_delete_client = &glewlwyd_plugin_callback_delete_client; config->config_p->glewlwyd_plugin_callback_scheme_register = &glewlwyd_plugin_callback_scheme_register; config->config_p->glewlwyd_plugin_callback_scheme_register_get = &glewlwyd_plugin_callback_scheme_register_get; config->config_p->glewlwyd_plugin_callback_scheme_deregister = &glewlwyd_plugin_callback_scheme_deregister; config->config_p->glewlwyd_plugin_callback_scheme_can_use = &glewlwyd_plugin_callback_scheme_can_use; config->config_p->glewlwyd_plugin_callback_get_scheme_list = &glewlwyd_plugin_callback_get_scheme_list; config->config_p->glewlwyd_plugin_callback_get_scheme_module = &glewlwyd_plugin_callback_get_scheme_module; config->config_p->glewlwyd_plugin_callback_metrics_add_metric = &glewlwyd_plugin_callback_metrics_add_metric; config->config_p->glewlwyd_plugin_callback_metrics_increment_counter = &glewlwyd_plugin_callback_metrics_increment_counter; // Init config structure with default values config->config_m->external_url = NULL; config->config_m->login_url = NULL; config->config_m->admin_scope = NULL; config->config_m->profile_scope = NULL; config->config_m->conn = NULL; config->config_m->glewlwyd_config = config; config->config_m->glewlwyd_module_callback_get_user = &glewlwyd_module_callback_get_user; config->config_m->glewlwyd_module_callback_set_user = &glewlwyd_module_callback_set_user; config->config_m->glewlwyd_module_callback_check_user_password = &glewlwyd_module_callback_check_user_password; config->config_m->glewlwyd_module_callback_check_user_session = &glewlwyd_module_callback_check_user_session; config->config_m->glewlwyd_module_callback_metrics_add_metric = &glewlwyd_module_callback_metrics_add_metric; config->config_m->glewlwyd_module_callback_metrics_increment_counter = &glewlwyd_module_callback_metrics_increment_counter; config->config_file = NULL; config->port = 0; config->bind_address = NULL; config->bind_address_metrics = NULL; config->instance = NULL; config->instance_metrics = NULL; config->instance_initialized = 0; config->instance_metrics_initialized = 0; config->api_prefix = o_strdup(GLEWLWYD_DEFAULT_API_PREFIX); config->external_url = NULL; config->cookie_domain = NULL; config->cookie_secure = 0; config->add_x_frame_option_header_deny = 1; config->log_mode_args = 0; config->log_level_args = 0; config->log_mode = Y_LOG_MODE_NONE; config->log_level = Y_LOG_LEVEL_NONE; config->log_file = NULL; config->allow_origin = o_strdup(GLEWLWYD_DEFAULT_ALLOW_ORIGIN); config->use_secure_connection = 0; config->secure_connection_key_file = NULL; config->secure_connection_pem_file = NULL; config->secure_connection_ca_file = NULL; config->conn = NULL; config->session_key = o_strdup(GLEWLWYD_DEFAULT_SESSION_KEY); config->session_expiration = GLEWLWYD_DEFAULT_SESSION_EXPIRATION_PASSWORD; config->salt_length = GLEWLWYD_DEFAULT_SALT_LENGTH; config->hash_algorithm = digest_SHA256; config->login_url = o_strdup(GLEWLWYD_DEFAULT_LOGIN_URL); config->delete_profile = GLEWLWYD_PROFILE_DELETE_UNAUTHORIZED; config->user_module_path = NULL; config->user_module_list = NULL; config->user_module_instance_list = NULL; config->user_middleware_module_path = NULL; config->user_middleware_module_list = NULL; config->user_middleware_module_instance_list = NULL; config->client_module_path = NULL; config->client_module_list = NULL; config->client_module_instance_list = NULL; config->user_auth_scheme_module_path = NULL; config->user_auth_scheme_module_list = NULL; config->user_auth_scheme_module_instance_list = NULL; config->plugin_module_path = NULL; config->plugin_module_list = NULL; config->plugin_module_instance_list = NULL; config->admin_scope = o_strdup(GLEWLWYD_DEFAULT_ADMIN_SCOPE); config->profile_scope = o_strdup(GLEWLWYD_DEFAULT_PROFILE_SCOPE); config->metrics_endpoint = 0; config->metrics_endpoint_port = GLEWLWYD_DEFAULT_METRICS_PORT; config->metrics_endpoint_admin_session = 0; http_comression_config.allow_gzip = 1; http_comression_config.allow_deflate = 1; config->static_file_config = o_malloc(sizeof(struct _u_compressed_inmemory_website_config)); if (config->static_file_config == NULL) { fprintf(stderr, "Error allocating resources for config->static_file_config, aborting\n"); return 2; } else if (u_init_compressed_inmemory_website_config(config->static_file_config) != U_OK) { fprintf(stderr, "Error u_init_compressed_inmemory_website_config for config->static_file_config, aborting\n"); return 2; } u_map_put(&config->static_file_config->mime_types, "*", "application/octet-stream"); config->instance = o_malloc(sizeof(struct _u_instance)); if (config->instance == NULL) { fprintf(stderr, "Error allocating resources for config->instance, aborting\n"); return 2; } if (pthread_mutex_init(&global_handler_close_lock, NULL) || pthread_cond_init(&global_handler_close_cond, NULL)) { y_log_message(Y_LOG_LEVEL_ERROR, "init - Error initializing global_handler_close_lock or global_handler_close_cond"); } // Process end signals on dedicated thread if (sigemptyset(&close_signals) == -1 || sigaddset(&close_signals, SIGQUIT) == -1 || sigaddset(&close_signals, SIGINT) == -1 || sigaddset(&close_signals, SIGTERM) == -1 || sigaddset(&close_signals, SIGHUP) == -1 || sigaddset(&close_signals, SIGBUS) == -1 || sigaddset(&close_signals, SIGSEGV) == -1 || sigaddset(&close_signals, SIGILL) == -1) { fprintf(stderr, "init - Error creating signal mask\n"); exit_server(&config, GLEWLWYD_ERROR); } if (pthread_sigmask(SIG_BLOCK, &close_signals, NULL)) { fprintf(stderr, "init - Error setting signal mask\n"); exit_server(&config, GLEWLWYD_ERROR); } if (pthread_create(&signal_thread_id, NULL, &signal_thread, &close_signals)) { fprintf(stderr, "init - Error creating signal thread\n"); exit_server(&config, GLEWLWYD_ERROR); return 1; } // Parse command line arguments if (build_config_from_args(argc, argv, config, &use_config_file, &use_config_env) != G_OK) { fprintf(stderr, "Error parsing command-line parameters\n"); print_help(stderr); exit_server(&config, GLEWLWYD_ERROR); } // Parse configuration file if (use_config_file && build_config_from_file(config) != G_OK) { fprintf(stderr, "Error parsing config file\n"); exit_server(&config, GLEWLWYD_ERROR); } // Parse environment variables if (use_config_env && build_config_from_env(config) != G_OK) { fprintf(stderr, "Error parsing environment variables\n"); exit_server(&config, GLEWLWYD_ERROR); } // Check if all mandatory configuration variables are present and correctly typed if (check_config(config) != G_OK) { fprintf(stderr, "Error - check the configuration\n"); exit_server(&config, GLEWLWYD_ERROR); } if (config->log_mode != Y_LOG_MODE_NONE && config->log_level != Y_LOG_LEVEL_NONE && !y_init_logs(GLEWLWYD_LOG_NAME, config->log_mode, config->log_level, config->log_file, "Starting Glewlwyd SSO authentication service")) { fprintf(stderr, "Error initializing logs\n"); return 0; } if (config->bind_address != NULL) { bind_address.sin_family = AF_INET; bind_address.sin_port = htons(config->port); inet_aton(config->bind_address, (struct in_addr *)&bind_address.sin_addr.s_addr); if (ulfius_init_instance(config->instance, config->port, &bind_address, NULL) != U_OK) { fprintf(stderr, "Error initializing webservice instance with bind address %s\n", config->bind_address); exit_server(&config, GLEWLWYD_ERROR); } } else { if (ulfius_init_instance(config->instance, config->port, NULL, NULL) != U_OK) { fprintf(stderr, "Error initializing webservice instance\n"); exit_server(&config, GLEWLWYD_ERROR); } } config->instance_initialized = 1; if (config->metrics_endpoint) { config->instance_metrics = o_malloc(sizeof(struct _u_instance)); if (config->instance_metrics == NULL) { fprintf(stderr, "Error allocating resources for config->instance_metrics, aborting\n"); exit_server(&config, GLEWLWYD_ERROR); } if (config->bind_address_metrics != NULL) { bind_address_metrics.sin_family = AF_INET; bind_address_metrics.sin_port = htons(config->metrics_endpoint_port); inet_aton(config->bind_address_metrics, (struct in_addr *)&bind_address_metrics.sin_addr.s_addr); if (ulfius_init_instance(config->instance_metrics, config->metrics_endpoint_port, &bind_address_metrics, NULL) != U_OK) { fprintf(stderr, "Error initializing metrics instance_metrics with bind address %s\n", config->bind_address_metrics); exit_server(&config, GLEWLWYD_ERROR); } } else { if (ulfius_init_instance(config->instance_metrics, config->metrics_endpoint_port, NULL, NULL) != U_OK) { fprintf(stderr, "Error initializing metrics instance_metrics\n"); exit_server(&config, GLEWLWYD_ERROR); } } if (glewlwyd_metrics_init(config) != 0) { fprintf(stderr, "Error initializing metrics\n"); exit_server(&config, GLEWLWYD_ERROR); } config->instance_metrics_initialized = 1; } glewlwyd_metrics_add_metric(config, GLWD_METRICS_AUTH_USER_VALID, "Total number of successful authentication"); glewlwyd_metrics_add_metric(config, GLWD_METRICS_AUTH_USER_VALID_SCHEME, "Total number of successful authentication by scheme"); glewlwyd_metrics_add_metric(config, GLWD_METRICS_AUTH_USER_INVALID, "Total number of invalid authentication"); glewlwyd_metrics_add_metric(config, GLWD_METRICS_AUTH_USER_INVALID_SCHEME, "Total number of invalid authentication by scheme"); glewlwyd_metrics_add_metric(config, GLWD_METRICS_DATABSE_ERROR, "Total number of database errors"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_AUTH_USER_VALID, 0, NULL); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_AUTH_USER_INVALID, 0, NULL); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_AUTH_USER_VALID_SCHEME, 0, "scheme_type", "password", NULL); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_AUTH_USER_INVALID_SCHEME, 0, "scheme_type", "password", NULL); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 0, NULL); // Initialize module lock pthread_mutexattr_init ( &mutexattr ); pthread_mutexattr_settype( &mutexattr, PTHREAD_MUTEX_RECURSIVE ); if (pthread_mutex_init(&config->module_lock, &mutexattr) != 0) { fprintf(stderr, "Error initializing modules mutex\n"); exit_server(&config, GLEWLWYD_ERROR); } pthread_mutexattr_destroy(&mutexattr); config->config_m->external_url = config->external_url; config->config_m->login_url = config->login_url; config->config_m->admin_scope = config->admin_scope; config->config_m->profile_scope = config->profile_scope; config->config_m->conn = config->conn; config->config_m->hash_algorithm = config->hash_algorithm; // Initialize user modules if (init_user_module_list(config) != G_OK) { fprintf(stderr, "Error initializing user modules\n"); exit_server(&config, GLEWLWYD_ERROR); } if (load_user_module_instance_list(config) != G_OK) { fprintf(stderr, "Error loading user modules instances\n"); exit_server(&config, GLEWLWYD_ERROR); } // Initialize user middleware modules if (init_user_middleware_module_list(config) != G_OK) { fprintf(stderr, "Error initializing user middleware modules\n"); exit_server(&config, GLEWLWYD_ERROR); } if (load_user_middleware_module_instance_list(config) != G_OK) { fprintf(stderr, "Error loading user middleware modules instances\n"); exit_server(&config, GLEWLWYD_ERROR); } // Initialize client modules if (init_client_module_list(config) != G_OK) { fprintf(stderr, "Error initializing client modules\n"); exit_server(&config, GLEWLWYD_ERROR); } if (load_client_module_instance_list(config) != G_OK) { fprintf(stderr, "Error loading client modules instances\n"); exit_server(&config, GLEWLWYD_ERROR); } // Initialize user auth scheme modules if (init_user_auth_scheme_module_list(config) != G_OK) { fprintf(stderr, "Error initializing user auth scheme modules\n"); exit_server(&config, GLEWLWYD_ERROR); } if (load_user_auth_scheme_module_instance_list(config) != G_OK) { fprintf(stderr, "Error loading user auth scheme modules instances\n"); exit_server(&config, GLEWLWYD_ERROR); } // Initialize plugins if (init_plugin_module_list(config) != G_OK) { fprintf(stderr, "Error initializing plugins modules\n"); exit_server(&config, GLEWLWYD_ERROR); } if (load_plugin_module_instance_list(config) != G_OK) { fprintf(stderr, "Error loading plugins modules instances\n"); exit_server(&config, GLEWLWYD_ERROR); } // At this point, we declare all API endpoints and configure // Authentication ulfius_add_endpoint_by_val(config->instance, "POST", config->api_prefix, "/auth/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_user_auth, (void*)config); ulfius_add_endpoint_by_val(config->instance, "POST", config->api_prefix, "/auth/scheme/trigger/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_user_auth_trigger, (void*)config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/auth/scheme/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_user_get_schemes_from_scopes, (void*)config); ulfius_add_endpoint_by_val(config->instance, "DELETE", config->api_prefix, "/auth/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_user_delete_session, (void*)config); // User profile ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/profile_list/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_user_get_profile, (void*)config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/profile_list/", GLEWLWYD_CALLBACK_PRIORITY_COMPRESSION, &callback_http_compression, &http_comression_config); ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/profile/", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_glewlwyd_check_user_profile_valid, (void*)config); ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/profile/password", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_glewlwyd_check_user_profile_valid, (void*)config); ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/profile/plugin", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_glewlwyd_check_user_session, (void*)config); ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/profile/grant", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_glewlwyd_check_user_profile_valid, (void*)config); ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/profile/scheme/*", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_glewlwyd_check_user_profile_valid, (void*)config); ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/profile/session/*", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_glewlwyd_check_user_session, (void*)config); ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/profile/*", GLEWLWYD_CALLBACK_PRIORITY_COMPRESSION, &callback_http_compression, &http_comression_config); ulfius_add_endpoint_by_val(config->instance, "PUT", config->api_prefix, "/profile/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_user_update_profile, (void*)config); ulfius_add_endpoint_by_val(config->instance, "DELETE", config->api_prefix, "/profile/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_user_delete_profile, (void*)config); ulfius_add_endpoint_by_val(config->instance, "PUT", config->api_prefix, "/profile/password", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_user_update_password, (void*)config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/profile/plugin", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_user_get_plugin_list, (void*)config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/profile/grant", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_user_get_client_grant_list, (void*)config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/profile/session", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_user_get_session_list, (void*)config); ulfius_add_endpoint_by_val(config->instance, "DELETE", config->api_prefix, "/profile/session/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_delete_session, (void*)config); ulfius_add_endpoint_by_val(config->instance, "DELETE", config->api_prefix, "/profile/session/:session_hash", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_delete_session, (void*)config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/profile/scheme", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_user_get_scheme_list, (void*)config); ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/profile/scheme/register/*", GLEWLWYD_CALLBACK_PRIORITY_PRE_APPLICATION, &callback_glewlwyd_scheme_check_forbid_profile, (void*)config); ulfius_add_endpoint_by_val(config->instance, "POST", config->api_prefix, "/profile/scheme/register/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_user_auth_register, (void*)config); ulfius_add_endpoint_by_val(config->instance, "PUT", config->api_prefix, "/profile/scheme/register/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_user_auth_register_get, (void*)config); // Grant scopes endpoints ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/auth/grant/*", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_glewlwyd_check_user_session, (void*)config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/auth/grant/:client_id/:scope_list", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_get_user_session_scope_grant, (void*)config); ulfius_add_endpoint_by_val(config->instance, "PUT", config->api_prefix, "/auth/grant/:client_id/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_set_user_session_scope_grant, (void*)config); ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/auth/grant/*", GLEWLWYD_CALLBACK_PRIORITY_COMPRESSION, &callback_http_compression, &http_comression_config); // User profile by delegation ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/delegate/:username/profile/*", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_glewlwyd_check_admin_session_delegate, (void*)config); ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/delegate/:username/profile/*", GLEWLWYD_CALLBACK_PRIORITY_COMPRESSION, &callback_http_compression, &http_comression_config); ulfius_add_endpoint_by_val(config->instance, "PUT", config->api_prefix, "/delegate/:username/profile/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_user_update_profile, (void*)config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/delegate/:username/profile/session", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_user_get_session_list, (void*)config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/delegate/:username/profile/plugin", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_user_get_plugin_list, (void*)config); ulfius_add_endpoint_by_val(config->instance, "DELETE", config->api_prefix, "/delegate/:username/profile/session/:session_hash", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_delete_session, (void*)config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/delegate/:username/profile/scheme", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_user_get_scheme_list, (void*)config); ulfius_add_endpoint_by_val(config->instance, "POST", config->api_prefix, "/delegate/:username/profile/scheme/register/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_user_auth_register_delegate, (void*)config); ulfius_add_endpoint_by_val(config->instance, "PUT", config->api_prefix, "/delegate/:username/profile/scheme/register/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_user_auth_register_get_delegate, (void*)config); // Modules check session ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/mod/*", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_glewlwyd_check_admin_session_or_api_key, (void*)config); ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/mod/*", GLEWLWYD_CALLBACK_PRIORITY_COMPRESSION, &callback_http_compression, &http_comression_config); // Get all module types available ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/mod/type/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_get_module_type_list, (void*)config); ulfius_add_endpoint_by_val(config->instance, "PUT", config->api_prefix, "/mod/reload/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_reload_modules, (void*)config); // User modules management ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/mod/user/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_get_user_module_list, (void*)config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/mod/user/:name", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_get_user_module, (void*)config); ulfius_add_endpoint_by_val(config->instance, "POST", config->api_prefix, "/mod/user/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_add_user_module, (void*)config); ulfius_add_endpoint_by_val(config->instance, "PUT", config->api_prefix, "/mod/user/:name", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_set_user_module, (void*)config); ulfius_add_endpoint_by_val(config->instance, "DELETE", config->api_prefix, "/mod/user/:name", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_delete_user_module, (void*)config); ulfius_add_endpoint_by_val(config->instance, "PUT", config->api_prefix, "/mod/user/:name/:action", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_manage_user_module, (void*)config); // User middleware modules management ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/mod/user_middleware/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_get_user_middleware_module_list, (void*)config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/mod/user_middleware/:name", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_get_user_middleware_module, (void*)config); ulfius_add_endpoint_by_val(config->instance, "POST", config->api_prefix, "/mod/user_middleware/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_add_user_middleware_module, (void*)config); ulfius_add_endpoint_by_val(config->instance, "PUT", config->api_prefix, "/mod/user_middleware/:name", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_set_user_middleware_module, (void*)config); ulfius_add_endpoint_by_val(config->instance, "DELETE", config->api_prefix, "/mod/user_middleware/:name", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_delete_user_middleware_module, (void*)config); ulfius_add_endpoint_by_val(config->instance, "PUT", config->api_prefix, "/mod/user_middleware/:name/:action", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_manage_user_middleware_module, (void*)config); // User auth scheme modules management ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/mod/scheme/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_get_user_auth_scheme_module_list, (void*)config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/mod/scheme/:name", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_get_user_auth_scheme_module, (void*)config); ulfius_add_endpoint_by_val(config->instance, "POST", config->api_prefix, "/mod/scheme/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_add_user_auth_scheme_module, (void*)config); ulfius_add_endpoint_by_val(config->instance, "PUT", config->api_prefix, "/mod/scheme/:name", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_set_user_auth_scheme_module, (void*)config); ulfius_add_endpoint_by_val(config->instance, "DELETE", config->api_prefix, "/mod/scheme/:name", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_delete_user_auth_scheme_module, (void*)config); ulfius_add_endpoint_by_val(config->instance, "PUT", config->api_prefix, "/mod/scheme/:name/:action", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_manage_user_auth_scheme_module, (void*)config); // Client modules management ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/mod/client/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_get_client_module_list, (void*)config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/mod/client/:name", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_get_client_module, (void*)config); ulfius_add_endpoint_by_val(config->instance, "POST", config->api_prefix, "/mod/client/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_add_client_module, (void*)config); ulfius_add_endpoint_by_val(config->instance, "PUT", config->api_prefix, "/mod/client/:name", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_set_client_module, (void*)config); ulfius_add_endpoint_by_val(config->instance, "DELETE", config->api_prefix, "/mod/client/:name", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_delete_client_module, (void*)config); ulfius_add_endpoint_by_val(config->instance, "PUT", config->api_prefix, "/mod/client/:name/:action", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_manage_client_module, (void*)config); // Plugin modules management ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/mod/plugin/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_get_plugin_module_list, (void*)config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/mod/plugin/:name", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_get_plugin_module, (void*)config); ulfius_add_endpoint_by_val(config->instance, "POST", config->api_prefix, "/mod/plugin/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_add_plugin_module, (void*)config); ulfius_add_endpoint_by_val(config->instance, "PUT", config->api_prefix, "/mod/plugin/:name", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_set_plugin_module, (void*)config); ulfius_add_endpoint_by_val(config->instance, "DELETE", config->api_prefix, "/mod/plugin/:name", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_delete_plugin_module, (void*)config); ulfius_add_endpoint_by_val(config->instance, "PUT", config->api_prefix, "/mod/plugin/:name/:action", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_manage_plugin_module, (void*)config); // Users CRUD ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/user/*", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_glewlwyd_check_admin_session_or_api_key, (void*)config); ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/user/*", GLEWLWYD_CALLBACK_PRIORITY_COMPRESSION, &callback_http_compression, &http_comression_config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/user/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_get_user_list, (void*)config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/user/:username", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_get_user, (void*)config); ulfius_add_endpoint_by_val(config->instance, "POST", config->api_prefix, "/user/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_add_user, (void*)config); ulfius_add_endpoint_by_val(config->instance, "PUT", config->api_prefix, "/user/:username", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_set_user, (void*)config); ulfius_add_endpoint_by_val(config->instance, "DELETE", config->api_prefix, "/user/:username", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_delete_user, (void*)config); // Clients CRUD ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/client/*", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_glewlwyd_check_admin_session_or_api_key, (void*)config); ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/client/*", GLEWLWYD_CALLBACK_PRIORITY_COMPRESSION, &callback_http_compression, &http_comression_config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/client/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_get_client_list, (void*)config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/client/:client_id", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_get_client, (void*)config); ulfius_add_endpoint_by_val(config->instance, "POST", config->api_prefix, "/client/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_add_client, (void*)config); ulfius_add_endpoint_by_val(config->instance, "PUT", config->api_prefix, "/client/:client_id", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_set_client, (void*)config); ulfius_add_endpoint_by_val(config->instance, "DELETE", config->api_prefix, "/client/:client_id", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_delete_client, (void*)config); // Scopes CRUD ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/scope/*", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_glewlwyd_check_admin_session_or_api_key, (void*)config); ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/scope/*", GLEWLWYD_CALLBACK_PRIORITY_COMPRESSION, &callback_http_compression, &http_comression_config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/scope/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_get_scope_list, (void*)config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/scope/:scope", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_get_scope, (void*)config); ulfius_add_endpoint_by_val(config->instance, "POST", config->api_prefix, "/scope/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_add_scope, (void*)config); ulfius_add_endpoint_by_val(config->instance, "PUT", config->api_prefix, "/scope/:scope", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_set_scope, (void*)config); ulfius_add_endpoint_by_val(config->instance, "DELETE", config->api_prefix, "/scope/:scope", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_delete_scope, (void*)config); // API key CRD ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/key/*", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_glewlwyd_check_admin_session, (void*)config); ulfius_add_endpoint_by_val(config->instance, "*", config->api_prefix, "/key/*", GLEWLWYD_CALLBACK_PRIORITY_COMPRESSION, &callback_http_compression, &http_comression_config); ulfius_add_endpoint_by_val(config->instance, "GET", config->api_prefix, "/key/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_get_api_key_list, (void*)config); ulfius_add_endpoint_by_val(config->instance, "DELETE", config->api_prefix, "/key/:key_hash", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_delete_api_key, (void*)config); ulfius_add_endpoint_by_val(config->instance, "POST", config->api_prefix, "/key/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_add_api_key, (void*)config); // Other configuration ulfius_add_endpoint_by_val(config->instance, "GET", "/config", NULL, GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_glewlwyd_server_configuration, (void*)config); ulfius_add_endpoint_by_val(config->instance, "GET", "/config", NULL, GLEWLWYD_CALLBACK_PRIORITY_COMPRESSION, &callback_http_compression, &http_comression_config); ulfius_add_endpoint_by_val(config->instance, "OPTIONS", NULL, "*", GLEWLWYD_CALLBACK_PRIORITY_ZERO, &callback_glewlwyd_options, (void*)config); ulfius_add_endpoint_by_val(config->instance, "GET", NULL, "*", GLEWLWYD_CALLBACK_PRIORITY_FILE, &callback_static_compressed_inmemory_website, (void*)config->static_file_config); ulfius_add_endpoint_by_val(config->instance, "GET", NULL, "*", GLEWLWYD_CALLBACK_PRIORITY_POST_FILE, &callback_404_if_necessary, NULL); ulfius_set_default_endpoint(config->instance, &callback_default, (void*)config); // Set default headers u_map_put(config->instance->default_headers, "Access-Control-Allow-Origin", config->allow_origin); u_map_put(config->instance->default_headers, "Access-Control-Allow-Credentials", "true"); u_map_put(config->instance->default_headers, "Cache-Control", "no-store"); u_map_put(config->instance->default_headers, "Pragma", "no-cache"); if (config->add_x_frame_option_header_deny) { u_map_put(config->instance->default_headers, "X-Frame-Options", "deny"); } // metrics endpoint configuration if (config->metrics_endpoint) { if (config->metrics_endpoint_admin_session) { ulfius_add_endpoint_by_val(config->instance_metrics, "GET", NULL, "*", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_glewlwyd_check_admin_session, (void*)config); } ulfius_add_endpoint_by_val(config->instance_metrics, "GET", NULL, "*", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_metrics, (void*)config); ulfius_add_endpoint_by_val(config->instance_metrics, "GET", NULL, "*", GLEWLWYD_CALLBACK_PRIORITY_COMPRESSION, &callback_http_compression, &http_comression_config); ulfius_set_default_endpoint(config->instance_metrics, &callback_default, (void*)config); if (ulfius_start_framework(config->instance_metrics) != U_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "Error starting metrics webservice instance_metrics"); exit_server(&config, GLEWLWYD_ERROR); } } // Check if cookie domain (if set) is the same domain as in external_url if (o_strlen(config->cookie_domain)) { if (0 == o_strncmp("http://", config->external_url, o_strlen("http://"))) { tmp = o_strdup(config->external_url); tmp2 = o_strchr(tmp+o_strlen("http://"), '/'); if (tmp2 != NULL) { *tmp2 = '\0'; } tmp2 = o_strchr(tmp+o_strlen("http://"), ':'); if (tmp2 != NULL) { *tmp2 = '\0'; } if (0 != o_strcmp(tmp+o_strlen("http://"), config->cookie_domain)) { y_log_message(Y_LOG_LEVEL_WARNING, "Configuration parameter cookie_domain '%s' does not seem to match the domain in external_url '%s'", config->cookie_domain, tmp+o_strlen("http://")); } o_free(tmp); } else if (0 == o_strncmp("https://", config->external_url, o_strlen("https://"))) { tmp = o_strdup(config->external_url); tmp2 = o_strchr(tmp+o_strlen("https://"), '/'); if (tmp2 != NULL) { *tmp2 = '\0'; } tmp2 = o_strchr(tmp+o_strlen("https://"), ':'); if (tmp2 != NULL) { *tmp2 = '\0'; } if (0 != o_strcmp(tmp+o_strlen("https://"), config->cookie_domain)) { y_log_message(Y_LOG_LEVEL_WARNING, "Configuration parameter cookie_domain '%s' does not seem to match the domain in external_url '%s'", config->cookie_domain, tmp+o_strlen("https://")); } o_free(tmp); } } y_log_message(Y_LOG_LEVEL_INFO, "Glewlwyd started on port %d, prefix: %s, secure: %s, bind address: %s, external URL: %s", config->instance->port, config->api_prefix, config->use_secure_connection?"true":"false", config->bind_address!=NULL?config->bind_address:"no", config->external_url); if (config->use_secure_connection) { char * key_file = get_file_content(config->secure_connection_key_file); char * pem_file = get_file_content(config->secure_connection_pem_file); if (key_file != NULL && pem_file != NULL) { if (config->secure_connection_ca_file != NULL) { char * ca_file = get_file_content(config->secure_connection_ca_file); if (ca_file != NULL) { res = ulfius_start_secure_ca_trust_framework(config->instance, key_file, pem_file, ca_file); } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error ca_file: %s", config->secure_connection_ca_file); res = U_ERROR_PARAMS; } o_free(ca_file); } else { res = ulfius_start_secure_framework(config->instance, key_file, pem_file); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error server certificate: %s - %s", config->secure_connection_key_file, config->secure_connection_pem_file); res = U_ERROR_PARAMS; } o_free(key_file); o_free(pem_file); } else { res = ulfius_start_framework(config->instance); } if (res == U_OK) { // Wait until stop signal is broadcasted pthread_mutex_lock(&global_handler_close_lock); pthread_cond_wait(&global_handler_close_cond, &global_handler_close_lock); pthread_mutex_unlock(&global_handler_close_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error starting glewlwyd webserver"); exit_server(&config, GLEWLWYD_ERROR); } if (pthread_mutex_destroy(&global_handler_close_lock) || pthread_cond_destroy(&global_handler_close_cond)) { y_log_message(Y_LOG_LEVEL_ERROR, "Error destroying global_handler_close_lock or global_handler_close_cond"); } exit_server(&config, 0); return 0; } /** * Exit properly the server by closing opened connections, databases and files */ void exit_server(struct config_elements ** config, int exit_value) { int close_logs = 0; if (config != NULL && *config != NULL) { close_logs = ((*config)->log_mode != Y_LOG_MODE_NONE && (*config)->log_level != Y_LOG_LEVEL_NONE); close_user_module_instance_list(*config); close_user_module_list(*config); close_user_middleware_module_instance_list(*config); close_user_middleware_module_list(*config); close_client_module_instance_list(*config); close_client_module_list(*config); close_user_auth_scheme_module_instance_list(*config); close_user_auth_scheme_module_list(*config); close_plugin_module_instance_list(*config); close_plugin_module_list(*config); pthread_mutex_destroy(&(*config)->module_lock); /* stop framework */ if ((*config)->instance_initialized) { ulfius_stop_framework((*config)->instance); ulfius_clean_instance((*config)->instance); } if ((*config)->instance_metrics_initialized) { ulfius_stop_framework((*config)->instance_metrics); ulfius_clean_instance((*config)->instance_metrics); } h_close_db((*config)->conn); h_clean_connection((*config)->conn); ulfius_global_close(); // Cleaning data o_free((*config)->instance); o_free((*config)->instance_metrics); y_log_message(Y_LOG_LEVEL_INFO, "Glewlwyd stopped"); o_free((*config)->config_file); o_free((*config)->api_prefix); o_free((*config)->cookie_domain); o_free((*config)->admin_scope); o_free((*config)->profile_scope); o_free((*config)->external_url); o_free((*config)->log_file); o_free((*config)->allow_origin); o_free((*config)->secure_connection_key_file); o_free((*config)->secure_connection_pem_file); o_free((*config)->secure_connection_ca_file); o_free((*config)->session_key); o_free((*config)->login_url); o_free((*config)->user_module_path); o_free((*config)->user_middleware_module_path); o_free((*config)->client_module_path); o_free((*config)->user_auth_scheme_module_path); o_free((*config)->plugin_module_path); o_free((*config)->bind_address); if ((*config)->static_file_config != NULL) { o_free((*config)->static_file_config->files_path); u_clean_compressed_inmemory_website_config((*config)->static_file_config); o_free((*config)->static_file_config); } glewlwyd_metrics_close((*config)); o_free((*config)->config_p); o_free((*config)->config_m); o_free(*config); (*config) = NULL; if (close_logs) { y_close_logs(); } } exit(exit_value); } /** * Initialize the application configuration based on the command line parameters */ int build_config_from_args(int argc, char ** argv, struct config_elements * config, int * use_config_file, int * use_config_env) { int next_option, ret = G_OK; const char * short_options = "c:e::p:m:l:f:h::v::"; char * tmp = NULL, * to_free = NULL, * one_log_mode = NULL; static const struct option long_options[]= { {"config-file", required_argument, NULL, 'c'}, {"env-variables", no_argument, NULL, 'e'}, {"port", required_argument, NULL, 'p'}, {"log-mode", required_argument, NULL, 'm'}, {"log-level", required_argument, NULL, 'l'}, {"log-file", required_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'v'}, {NULL, 0, NULL, 0} }; if (config != NULL) { do { next_option = getopt_long(argc, argv, short_options, long_options, NULL); switch (next_option) { case 'c': if (optarg != NULL) { config->config_file = o_strdup(optarg); if (config->config_file == NULL) { fprintf(stderr, "Error allocating config->config_file, exiting\n"); ret = G_ERROR_PARAM; } else { *use_config_file = 1; } } else { fprintf(stderr, "Error!\nNo config file specified\n"); ret = G_ERROR_PARAM; } break; case 'e': *use_config_env = 1; break; case 'p': if (optarg != NULL) { config->port = strtol(optarg, NULL, 10); if (config->port <= 0 || config->port > 65535) { fprintf(stderr, "Error!\nInvalid TCP Port number\n\tPlease specify an integer value between 1 and 65535"); ret = G_ERROR_PARAM; } } else { fprintf(stderr, "Error!\nNo TCP Port number specified\n"); ret = G_ERROR_PARAM; } break; case 'm': if (optarg != NULL) { config->log_mode_args = 1; tmp = o_strdup(optarg); if (tmp == NULL) { fprintf(stderr, "Error allocating log_mode, exiting\n"); ret = G_ERROR_PARAM; } config->log_mode = Y_LOG_MODE_NONE; one_log_mode = strtok(tmp, ","); while (one_log_mode != NULL) { if (0 == o_strcmp("console", one_log_mode)) { config->log_mode += Y_LOG_MODE_CONSOLE; } else if (0 == o_strcmp("syslog", one_log_mode)) { config->log_mode += Y_LOG_MODE_SYSLOG; } else if (0 == o_strcmp("journald", one_log_mode)) { config->log_mode += Y_LOG_MODE_JOURNALD; } else if (0 == o_strcmp("file", one_log_mode)) { config->log_mode += Y_LOG_MODE_FILE; } one_log_mode = strtok(NULL, ","); } o_free(to_free); } else { fprintf(stderr, "Error!\nNo mode specified\n"); ret = G_ERROR_PARAM; } break; case 'l': if (optarg != NULL) { config->log_level_args = 1; config->log_level = Y_LOG_LEVEL_NONE; if (0 == o_strcmp("NONE", optarg)) { config->log_level = Y_LOG_LEVEL_NONE; } else if (0 == o_strcmp("ERROR", optarg)) { config->log_level = Y_LOG_LEVEL_ERROR; } else if (0 == o_strcmp("WARNING", optarg)) { config->log_level = Y_LOG_LEVEL_WARNING; } else if (0 == o_strcmp("INFO", optarg)) { config->log_level = Y_LOG_LEVEL_INFO; } else if (0 == o_strcmp("DEBUG", optarg)) { config->log_level = Y_LOG_LEVEL_DEBUG; } } else { fprintf(stderr, "Error!\nNo log level specified\n"); ret = G_ERROR_PARAM; } break; case 'f': if (optarg != NULL) { o_free(config->log_file); config->log_file = o_strdup(optarg); if (config->log_file == NULL) { fprintf(stderr, "Error allocating config->log_file, exiting\n"); ret = G_ERROR_PARAM; } } else { fprintf(stderr, "Error!\nNo log file specified\n"); ret = G_ERROR_PARAM; } break; case 'h': print_help(stdout); exit_server(&config, 0); break; case 'v': fprintf(stdout, "%s\n", _GLEWLWYD_VERSION_); exit_server(&config, 0); break; } } while (next_option != -1); } else { ret = G_ERROR; } return ret; } /** * Initialize the application configuration based on the config file content * Read the config file, get mandatory variables and devices */ int build_config_from_file(struct config_elements * config) { config_t cfg; config_setting_t * root = NULL, * database = NULL, * mime_type_list = NULL, * mime_type = NULL; const char * str_value = NULL, * str_value_2 = NULL, * str_value_3 = NULL, * str_value_4 = NULL, * str_value_5 = NULL; int int_value = 0, int_value_2 = 0, int_value_3 = 0, i, ret = G_OK; char * one_log_mode; config_init(&cfg); do { if (!config_read_file(&cfg, config->config_file)) { fprintf(stderr, "Error parsing config file %s\nOn line %d error: %s, exiting\n", config_error_file(&cfg), config_error_line(&cfg), config_error_text(&cfg)); ret = G_ERROR_PARAM; break; } // Get Port number to listen to if (!config->port && config_lookup_int(&cfg, "port", &int_value) == CONFIG_TRUE) { config->port = (uint)int_value; } if (config_lookup_string(&cfg, "bind_address", &str_value) == CONFIG_TRUE) { config->bind_address = o_strdup(str_value); if (config->bind_address == NULL) { fprintf(stderr, "Error allocating config->bind_address, exiting\n"); ret = G_ERROR_PARAM; break; } } if (config_lookup_string(&cfg, "api_prefix", &str_value) == CONFIG_TRUE) { o_free(config->api_prefix); config->api_prefix = o_strdup(str_value); if (config->api_prefix == NULL) { fprintf(stderr, "Error allocating config->api_prefix, exiting\n"); ret = G_ERROR_PARAM; break; } } if (config_lookup_string(&cfg, "cookie_domain", &str_value) == CONFIG_TRUE) { o_free(config->cookie_domain); config->cookie_domain = o_strdup(str_value); if (config->cookie_domain == NULL) { fprintf(stderr, "Error allocating config->cookie_domain, exiting\n"); ret = G_ERROR_PARAM; break; } } if (config_lookup_int(&cfg, "cookie_secure", &int_value) == CONFIG_TRUE) { config->cookie_secure = (uint)int_value; } if (config_lookup_bool(&cfg, "add_x_frame_option_header_deny", &int_value) == CONFIG_TRUE) { config->add_x_frame_option_header_deny = (uint)int_value; } // Get log mode if (!config->log_mode_args && config_lookup_string(&cfg, "log_mode", &str_value) == CONFIG_TRUE) { config->log_mode = Y_LOG_MODE_NONE; one_log_mode = strtok((char *)str_value, ","); while (one_log_mode != NULL) { if (0 == o_strcmp("console", one_log_mode)) { config->log_mode |= Y_LOG_MODE_CONSOLE; } else if (0 == o_strcmp("syslog", one_log_mode)) { config->log_mode |= Y_LOG_MODE_SYSLOG; } else if (0 == o_strcmp("journald", one_log_mode)) { config->log_mode |= Y_LOG_MODE_JOURNALD; } else if (0 == o_strcmp("file", one_log_mode)) { config->log_mode |= Y_LOG_MODE_FILE; // Get log file path if (config->log_file == NULL) { if (config_lookup_string(&cfg, "log_file", &str_value_2) == CONFIG_TRUE) { config->log_file = o_strdup(str_value_2); if (config->log_file == NULL) { fprintf(stderr, "Error allocating config->log_file, exiting\n"); ret = G_ERROR_PARAM; break; } } } } else { fprintf(stderr, "Error - logging mode '%s' unknown, exiting\n", one_log_mode); ret = G_ERROR_PARAM; break; } one_log_mode = strtok(NULL, ","); } if (ret != G_OK) { break; } } // Get log level if (!config->log_level_args && config_lookup_string(&cfg, "log_level", &str_value) == CONFIG_TRUE) { config->log_level = Y_LOG_LEVEL_NONE; if (0 == o_strcmp("ERROR", str_value)) { config->log_level = Y_LOG_LEVEL_ERROR; } else if (0 == o_strcmp("WARNING", str_value)) { config->log_level = Y_LOG_LEVEL_WARNING; } else if (0 == o_strcmp("INFO", str_value)) { config->log_level = Y_LOG_LEVEL_INFO; } else if (0 == o_strcmp("DEBUG", str_value)) { config->log_level = Y_LOG_LEVEL_DEBUG; } } // Get allow-origin value for CORS if (config_lookup_string(&cfg, "allow_origin", &str_value) == CONFIG_TRUE) { o_free(config->allow_origin); config->allow_origin = o_strdup(str_value); if (config->allow_origin == NULL) { fprintf(stderr, "Error allocating config->allow_origin, exiting\n"); ret = G_ERROR_PARAM; break; } } if (config_lookup_string(&cfg, "session_key", &str_value) == CONFIG_TRUE) { o_free(config->session_key); config->session_key = o_strdup(str_value); } if (config_lookup_int(&cfg, "session_expiration", &int_value) == CONFIG_TRUE) { config->session_expiration = (uint)int_value; } if (config_lookup_string(&cfg, "external_url", &str_value) == CONFIG_TRUE) { o_free(config->external_url); config->external_url = o_strdup(str_value); if (config->external_url == NULL) { fprintf(stderr, "Error allocating resources for config->external_url, exiting\n"); ret = G_ERROR_PARAM; break; } } if (config_lookup_string(&cfg, "login_url", &str_value) == CONFIG_TRUE) { o_free(config->login_url); config->login_url = o_strdup(str_value); if (config->login_url == NULL) { fprintf(stderr, "Error allocating resources for config->login_url, exiting\n"); ret = G_ERROR_PARAM; break; } } if (config_lookup_string(&cfg, "delete_profile", &str_value) == CONFIG_TRUE) { if (0 == o_strcmp("no", str_value)) { config->delete_profile = GLEWLWYD_PROFILE_DELETE_UNAUTHORIZED; } else if (0 == o_strcmp("delete", str_value)) { config->delete_profile = GLEWLWYD_PROFILE_DELETE_AUTHORIZED; } else if (0 == o_strcmp("disable", str_value)) { config->delete_profile = GLEWLWYD_PROFILE_DELETE_AUTHORIZED | GLEWLWYD_PROFILE_DELETE_DISABLE_PROFILE; } else { fprintf(stderr, "Invalid value for delete_profile, expected 'no', 'delete' or 'disable', exiting\n"); ret = G_ERROR_PARAM; break; } } // Get path that serve static files if (config_lookup_string(&cfg, "static_files_path", &str_value) == CONFIG_TRUE) { o_free(config->static_file_config->files_path); config->static_file_config->files_path = o_strdup(str_value); if (config->static_file_config->files_path == NULL) { fprintf(stderr, "Error allocating config->files_path, exiting\n"); ret = G_ERROR_PARAM; break; } } // Populate mime types u_map mime_type_list = config_lookup(&cfg, "static_files_mime_types"); if (mime_type_list != NULL) { int len = config_setting_length(mime_type_list); for (i=0; i<len; i++) { mime_type = config_setting_get_elem(mime_type_list, i); if (mime_type != NULL) { if (config_setting_lookup_string(mime_type, "extension", &str_value) == CONFIG_TRUE && config_setting_lookup_string(mime_type, "mime_type", &str_value_2) == CONFIG_TRUE) { u_map_put(&config->static_file_config->mime_types, str_value, str_value_2); if (config_setting_lookup_int(mime_type, "compress", &int_value) == CONFIG_TRUE) { if (int_value && u_add_mime_types_compressed(config->static_file_config, str_value_2) != U_OK) { fprintf(stderr, "Error setting mime_type %s to compressed list, exiting\n", str_value_2); ret = G_ERROR_PARAM; break; } } } } } } if (config_lookup_bool(&cfg, "use_secure_connection", &int_value) == CONFIG_TRUE) { if (config_lookup_string(&cfg, "secure_connection_key_file", &str_value) == CONFIG_TRUE && config_lookup_string(&cfg, "secure_connection_pem_file", &str_value_2) == CONFIG_TRUE) { config->use_secure_connection = int_value; config->secure_connection_key_file = o_strdup(str_value); config->secure_connection_pem_file = o_strdup(str_value_2); if (config_lookup_string(&cfg, "secure_connection_ca_file", &str_value) == CONFIG_TRUE) { config->secure_connection_ca_file = o_strdup(str_value); } } else { fprintf(stderr, "Error secure connection is active but certificate is not valid, exiting\n"); ret = G_ERROR_PARAM; break; } } // Get token hash algorithm if (config_lookup_string(&cfg, "hash_algorithm", &str_value) == CONFIG_TRUE) { if (!o_strcmp("SHA1", str_value)) { config->hash_algorithm = digest_SHA1; } else if (!o_strcmp("SHA224", str_value)) { config->hash_algorithm = digest_SHA224; } else if (!o_strcmp("SHA256", str_value)) { config->hash_algorithm = digest_SHA256; } else if (!o_strcmp("SHA384", str_value)) { config->hash_algorithm = digest_SHA384; } else if (!o_strcmp("SHA512", str_value)) { config->hash_algorithm = digest_SHA512; } else if (!o_strcmp("MD5", str_value)) { config->hash_algorithm = digest_MD5; } else { fprintf(stderr, "Error token hash algorithm: %s, exiting\n", str_value); ret = G_ERROR_PARAM; break; } } root = config_root_setting(&cfg); database = config_setting_get_member(root, "database"); if (database != NULL) { if (config_setting_lookup_string(database, "type", &str_value) == CONFIG_TRUE) { if (0 == o_strcmp(str_value, "sqlite3")) { if (config_setting_lookup_string(database, "path", &str_value_2) == CONFIG_TRUE) { config->conn = h_connect_sqlite(str_value_2); if (config->conn == NULL) { fprintf(stderr, "Error opening sqlite database %s, exiting\n", str_value_2); ret = G_ERROR_PARAM; break; } else { if (h_exec_query_sqlite(config->conn, "PRAGMA foreign_keys = ON;") != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "Error executing sqlite3 query 'PRAGMA foreign_keys = ON;, exiting'"); ret = G_ERROR_PARAM; break; } } } else { fprintf(stderr, "Error - no sqlite database specified\n"); ret = G_ERROR_PARAM; break; } } else if (0 == o_strcmp(str_value, "mariadb")) { config_setting_lookup_string(database, "host", &str_value_2); config_setting_lookup_string(database, "user", &str_value_3); config_setting_lookup_string(database, "password", &str_value_4); config_setting_lookup_string(database, "dbname", &str_value_5); config_setting_lookup_int(database, "port", &int_value); config->conn = h_connect_mariadb(str_value_2, str_value_3, str_value_4, str_value_5, int_value, NULL); if (config->conn == NULL) { fprintf(stderr, "Error opening mariadb database %s\n", str_value_5); ret = G_ERROR_PARAM; break; } else { if (h_execute_query_mariadb(config->conn, "SET sql_mode='PIPES_AS_CONCAT';", NULL) != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "Error executing mariadb query 'SET sql_mode='PIPES_AS_CONCAT';', exiting"); ret = G_ERROR_PARAM; break; } } } else if (0 == o_strcmp(str_value, "postgre")) { config_setting_lookup_string(database, "conninfo", &str_value_2); config->conn = h_connect_pgsql(str_value_2); if (config->conn == NULL) { fprintf(stderr, "Error opening postgre database %s, exiting\n", str_value_2); ret = G_ERROR_PARAM; break; } } else { fprintf(stderr, "Error - database type unknown\n"); ret = G_ERROR_PARAM; break; } } else { fprintf(stderr, "Error - no database type found\n"); ret = G_ERROR_PARAM; break; } } else { fprintf(stderr, "Error - no database setting found\n"); ret = G_ERROR_PARAM; break; } if (config_lookup_string(&cfg, "admin_scope", &str_value) == CONFIG_TRUE) { o_free(config->admin_scope); config->admin_scope = o_strdup(str_value); } if (config_lookup_string(&cfg, "profile_scope", &str_value) == CONFIG_TRUE) { o_free(config->profile_scope); config->profile_scope = o_strdup(str_value); } if (config_lookup_string(&cfg, "user_module_path", &str_value) == CONFIG_TRUE) { o_free(config->user_module_path); config->user_module_path = o_strdup(str_value); } if (config_lookup_string(&cfg, "user_middleware_module_path", &str_value) == CONFIG_TRUE) { o_free(config->user_middleware_module_path); config->user_middleware_module_path = o_strdup(str_value); } if (config_lookup_string(&cfg, "client_module_path", &str_value) == CONFIG_TRUE) { o_free(config->client_module_path); config->client_module_path = o_strdup(str_value); } if (config_lookup_string(&cfg, "user_auth_scheme_module_path", &str_value) == CONFIG_TRUE) { o_free(config->user_auth_scheme_module_path); config->user_auth_scheme_module_path = o_strdup(str_value); } if (config_lookup_string(&cfg, "plugin_module_path", &str_value) == CONFIG_TRUE) { o_free(config->plugin_module_path); config->plugin_module_path = o_strdup(str_value); } if (config_lookup_bool(&cfg, "metrics_endpoint", &int_value) == CONFIG_TRUE) { config->metrics_endpoint = (ushort)int_value; if (config_lookup_string(&cfg, "metrics_bind_address", &str_value) == CONFIG_TRUE) { config->bind_address_metrics = o_strdup(str_value); if (config->bind_address_metrics == NULL) { fprintf(stderr, "Error allocating config->bind_address_metrics, exiting\n"); ret = G_ERROR_PARAM; break; } } if (config_lookup_int(&cfg, "metrics_endpoint_port", &int_value_2) == CONFIG_TRUE) { config->metrics_endpoint_port = (uint)int_value_2; } if (config_lookup_bool(&cfg, "metrics_endpoint_admin_session", &int_value_3) == CONFIG_TRUE) { config->metrics_endpoint_admin_session = (ushort)int_value_3; } } } while (0); config_destroy(&cfg); return ret; } /** * Initialize the application configuration based on the environment variables */ int build_config_from_env(struct config_elements * config) { char * value = NULL, * value2 = NULL, * endptr = NULL, * one_log_mode = NULL; long int lvalue; int ret = G_OK; json_t * j_mime_types, * j_element; size_t index; if (!config->port && (value = getenv(GLEWLWYD_ENV_PORT)) != NULL && o_strlen(value)) { endptr = NULL; lvalue = strtol(value, &endptr, 10); if (!(*endptr) && lvalue > 0 && lvalue < 65535) { config->port = (uint)lvalue; } else { fprintf(stderr, "Error invalid port number (env), exiting\n"); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_BIND_ADDRESS)) != NULL && o_strlen(value)) { o_free(config->bind_address); config->bind_address = o_strdup(value); if (config->bind_address == NULL) { fprintf(stderr, "Error allocating config->bind_address (env), exiting\n"); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_API_PREFIX)) != NULL && o_strlen(value)) { o_free(config->api_prefix); config->api_prefix = o_strdup(value); if (config->api_prefix == NULL) { fprintf(stderr, "Error allocating config->api_prefix (env), exiting\n"); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_EXTERNAL_URL)) != NULL && o_strlen(value)) { o_free(config->external_url); config->external_url = o_strdup(value); if (config->external_url == NULL) { fprintf(stderr, "Error allocating config->external_url (env), exiting\n"); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_LOGIN_URL)) != NULL && o_strlen(value)) { o_free(config->login_url); config->login_url = o_strdup(value); if (config->login_url == NULL) { fprintf(stderr, "Error allocating config->login_url (env), exiting\n"); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_PROFILE_DELETE)) != NULL && o_strlen(value)) { if (0 == o_strcmp("no", value)) { config->delete_profile = GLEWLWYD_PROFILE_DELETE_UNAUTHORIZED; } else if (0 == o_strcmp("delete", value)) { config->delete_profile = GLEWLWYD_PROFILE_DELETE_AUTHORIZED; } else if (0 == o_strcmp("disable", value)) { config->delete_profile = GLEWLWYD_PROFILE_DELETE_AUTHORIZED | GLEWLWYD_PROFILE_DELETE_DISABLE_PROFILE; } else { fprintf(stderr, "Invalid value for " GLEWLWYD_ENV_PROFILE_DELETE ", expected 'no', 'delete' or 'disable' (env), exiting\n"); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_STATIC_FILES_PATH)) != NULL && o_strlen(value)) { o_free(config->static_file_config->files_path); config->static_file_config->files_path = o_strdup(value); if (config->static_file_config->files_path == NULL) { fprintf(stderr, "Error allocating config->files_path (env), exiting\n"); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_STATIC_FILES_MIME_TYPES)) != NULL && o_strlen(value)) { j_mime_types = json_loads(value, JSON_DECODE_ANY, NULL); if (json_is_array(j_mime_types)) { json_array_foreach(j_mime_types, index, j_element) { if (json_string_length(json_object_get(j_element, "extension")) && json_string_length(json_object_get(j_element, "mime_type"))) { u_map_put(&config->static_file_config->mime_types, json_string_value(json_object_get(j_element, "extension")), json_string_value(json_object_get(j_element, "mime_type"))); if (json_object_get(j_element, "compress") == json_true()) { if (u_add_mime_types_compressed(config->static_file_config, json_string_value(json_object_get(j_element, "mime_type"))) != U_OK) { fprintf(stderr, "Error setting mime_type %s to compressed list (env), exiting\n", json_string_value(json_object_get(j_element, "mime_type"))); ret = G_ERROR_PARAM; break; } } } else { fprintf(stderr, "Error - variable "GLEWLWYD_ENV_STATIC_FILES_MIME_TYPES" must be a JSON array, example [{\"extension\":\".html\",\"mime_type\":\"text/html\"}] (env), exiting\n"); ret = G_ERROR_PARAM; break; } } } else { fprintf(stderr, "Error - variable "GLEWLWYD_ENV_STATIC_FILES_MIME_TYPES" must be a JSON array, example [{\"extension\":\".html\",\"mime_type\":\"text/html\"}] (env), exiting\n"); ret = G_ERROR_PARAM; } json_decref(j_mime_types); } if ((value = getenv(GLEWLWYD_ENV_ALLOW_ORIGIN)) != NULL && o_strlen(value)) { o_free(config->allow_origin); config->allow_origin = o_strdup(value); if (config->allow_origin == NULL) { fprintf(stderr, "Error allocating config->allow_origin (env), exiting\n"); ret = G_ERROR_PARAM; } } if (!config->log_mode_args && (value = getenv(GLEWLWYD_ENV_LOG_MODE)) != NULL && o_strlen(value)) { config->log_mode = Y_LOG_MODE_NONE; one_log_mode = strtok((char *)value, ","); while (one_log_mode != NULL && ret == G_OK) { if (0 == o_strcmp("console", one_log_mode)) { config->log_mode |= Y_LOG_MODE_CONSOLE; } else if (0 == o_strcmp("syslog", one_log_mode)) { config->log_mode |= Y_LOG_MODE_SYSLOG; } else if (0 == o_strcmp("journald", one_log_mode)) { config->log_mode |= Y_LOG_MODE_JOURNALD; } else if (0 == o_strcmp("file", one_log_mode)) { config->log_mode |= Y_LOG_MODE_FILE; // Get log file path if ((value2 = getenv(GLEWLWYD_ENV_LOG_FILE)) != NULL && o_strlen(value2)) { o_free(config->log_file); config->log_file = o_strdup(value2); if (config->log_file == NULL) { fprintf(stderr, "Error allocating config->log_file (env), exiting\n"); ret = G_ERROR_PARAM; } } } else { fprintf(stderr, "Error - logging mode '%s' unknown (env), exiting\n", one_log_mode); ret = G_ERROR_PARAM; } one_log_mode = strtok(NULL, ","); } } if (!config->log_level_args && (value = getenv(GLEWLWYD_ENV_LOG_LEVEL)) != NULL && o_strlen(value)) { if (0 == o_strcmp("NONE", value)) { config->log_level = Y_LOG_LEVEL_NONE; } else if (0 == o_strcmp("ERROR", value)) { config->log_level = Y_LOG_LEVEL_ERROR; } else if (0 == o_strcmp("WARNING", value)) { config->log_level = Y_LOG_LEVEL_WARNING; } else if (0 == o_strcmp("INFO", value)) { config->log_level = Y_LOG_LEVEL_INFO; } else if (0 == o_strcmp("DEBUG", value)) { config->log_level = Y_LOG_LEVEL_DEBUG; } } if ((value = getenv(GLEWLWYD_ENV_COOKIE_DOMAIN)) != NULL && o_strlen(value)) { config->cookie_domain = o_strdup(value); if (config->cookie_domain == NULL) { fprintf(stderr, "Error allocating config->cookie_domain (env), exiting\n"); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_COOKIE_SECURE)) != NULL) { config->cookie_secure = (uint)(o_strcmp(value, "1")==0); } if ((value = getenv(GLEWLWYD_ENV_ADD_X_FRAME_DENY)) != NULL) { config->add_x_frame_option_header_deny = (uint)(o_strcmp(value, "1")==0); } if ((value = getenv(GLEWLWYD_ENV_SESSION_EXPIRATION)) != NULL && o_strlen(value)) { endptr = NULL; lvalue = strtol(value, &endptr, 10); if (!(*endptr) && lvalue > 0) { config->session_expiration = (uint)lvalue; } else { fprintf(stderr, "Error invalid session_expiration number (env), exiting\n"); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_SESSION_KEY)) != NULL && o_strlen(value)) { o_free(config->session_key); config->session_key = o_strdup(value); if (config->session_key == NULL) { fprintf(stderr, "Error allocating config->session_key (env), exiting\n"); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_ADMIN_SCOPE)) != NULL && o_strlen(value)) { o_free(config->admin_scope); config->admin_scope = o_strdup(value); if (config->admin_scope == NULL) { fprintf(stderr, "Error allocating config->admin_scope (env), exiting\n"); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_PROFILE_SCOPE)) != NULL && o_strlen(value)) { o_free(config->profile_scope); config->profile_scope = o_strdup(value); if (config->profile_scope == NULL) { fprintf(stderr, "Error allocating config->profile_scope (env), exiting\n"); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_USER_MODULE_PATH)) != NULL && o_strlen(value)) { o_free(config->user_module_path); config->user_module_path = o_strdup(value); if (config->user_module_path == NULL) { fprintf(stderr, "Error allocating config->user_module_path (env), exiting\n"); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_USER_MIDDLEWARE_MODULE_PATH)) != NULL && o_strlen(value)) { o_free(config->user_middleware_module_path); config->user_middleware_module_path = o_strdup(value); if (config->user_middleware_module_path == NULL) { fprintf(stderr, "Error allocating config->user_middleware_module_path (env), exiting\n"); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_CLIENT_MODULE_PATH)) != NULL && o_strlen(value)) { o_free(config->client_module_path); config->client_module_path = o_strdup(value); if (config->client_module_path == NULL) { fprintf(stderr, "Error allocating config->client_module_path (env), exiting\n"); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_AUTH_SCHEME_MODULE_PATH)) != NULL && o_strlen(value)) { o_free(config->user_auth_scheme_module_path); config->user_auth_scheme_module_path = o_strdup(value); if (config->user_auth_scheme_module_path == NULL) { fprintf(stderr, "Error allocating config->user_auth_scheme_module_path (env), exiting\n"); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_PLUGIN_MODULE_PATH)) != NULL && o_strlen(value)) { o_free(config->plugin_module_path); config->plugin_module_path = o_strdup(value); if (config->plugin_module_path == NULL) { fprintf(stderr, "Error allocating config->plugin_module_path (env), exiting\n"); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_USE_SECURE_CONNECTION)) != NULL) { config->use_secure_connection = (uint)(o_strcmp(value, "1")==0); } if ((value = getenv(GLEWLWYD_ENV_SECURE_CONNECTION_KEY_FILE)) != NULL && o_strlen(value)) { o_free(config->secure_connection_key_file); config->secure_connection_key_file = o_strdup(value); if (config->secure_connection_key_file == NULL) { fprintf(stderr, "Error allocating config->secure_connection_key_file (env), exiting\n"); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_SECURE_CONNECTION_PEM_FILE)) != NULL && o_strlen(value)) { o_free(config->secure_connection_pem_file); config->secure_connection_pem_file = o_strdup(value); if (config->secure_connection_pem_file == NULL) { fprintf(stderr, "Error allocating config->secure_connection_pem_file (env), exiting\n"); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_SECURE_CONNECTION_CA_FILE)) != NULL && o_strlen(value)) { o_free(config->secure_connection_ca_file); config->secure_connection_ca_file = o_strdup(value); if (config->secure_connection_ca_file == NULL) { fprintf(stderr, "Error allocating config->secure_connection_ca_file (env), exiting\n"); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_HASH_ALGORITHM)) != NULL && o_strlen(value)) { if (!o_strcmp("SHA1", value)) { config->hash_algorithm = digest_SHA1; } else if (!o_strcmp("SHA224", value)) { config->hash_algorithm = digest_SHA224; } else if (!o_strcmp("SHA256", value)) { config->hash_algorithm = digest_SHA256; } else if (!o_strcmp("SHA384", value)) { config->hash_algorithm = digest_SHA384; } else if (!o_strcmp("SHA512", value)) { config->hash_algorithm = digest_SHA512; } else if (!o_strcmp("MD5", value)) { config->hash_algorithm = digest_MD5; } else { fprintf(stderr, "Error token hash algorithm: %s (env), exiting\n", value); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_DATABASE_TYPE)) != NULL && o_strlen(value)) { if (config->conn != NULL) { h_close_db(config->conn); h_clean_connection(config->conn); } if (0 == o_strcmp(value, "sqlite3")) { if ((config->conn = h_connect_sqlite(getenv(GLEWLWYD_ENV_DATABASE_SQLITE3_PATH))) == NULL) { fprintf(stderr, "Error opening sqlite database '%s' (env), exiting\n", getenv(GLEWLWYD_ENV_DATABASE_SQLITE3_PATH)); ret = G_ERROR_PARAM; } else { if (h_exec_query_sqlite(config->conn, "PRAGMA foreign_keys = ON;") != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "Error executing sqlite3 query 'PRAGMA foreign_keys = ON; (env), exiting'"); ret = G_ERROR_PARAM; } } } else if (0 == o_strcmp(value, "mariadb")) { lvalue = strtol(getenv(GLEWLWYD_ENV_DATABASE_MARIADB_PORT), &endptr, 10); if (!(*endptr) && lvalue > 0 && lvalue < 65535) { if ((config->conn = h_connect_mariadb(getenv(GLEWLWYD_ENV_DATABASE_MARIADB_HOST), getenv(GLEWLWYD_ENV_DATABASE_MARIADB_USER), getenv(GLEWLWYD_ENV_DATABASE_MARIADB_PASSWORD), getenv(GLEWLWYD_ENV_DATABASE_MARIADB_DBNAME), lvalue, NULL)) == NULL) { fprintf(stderr, "Error opening mariadb database '%s'\n", getenv(GLEWLWYD_ENV_DATABASE_MARIADB_DBNAME)); ret = G_ERROR_PARAM; } else { if (h_execute_query_mariadb(config->conn, "SET sql_mode='PIPES_AS_CONCAT';", NULL) != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "Error executing mariadb query 'SET sql_mode='PIPES_AS_CONCAT'; (env), exiting'"); ret = G_ERROR_PARAM; } } } } else if (0 == o_strcmp(value, "postgre")) { if ((config->conn = h_connect_pgsql(getenv(GLEWLWYD_ENV_DATABASE_POSTGRE_CONNINFO))) == NULL) { fprintf(stderr, "Error opening postgre database %s (env), exiting\n", getenv(GLEWLWYD_ENV_DATABASE_POSTGRE_CONNINFO)); ret = G_ERROR_PARAM; } } else { fprintf(stderr, "Error - database type unknown (env), exiting\n"); ret = G_ERROR_PARAM; } } if ((value = getenv(GLEWLWYD_ENV_METRICS)) != NULL) { config->metrics_endpoint = (ushort)(o_strcmp(value, "1")==0); } if ((value = getenv(GLEWLWYD_ENV_METRICS_PORT)) != NULL) { config->metrics_endpoint_port = (uint)(o_strcmp(value, "1")==0); } if ((value = getenv(GLEWLWYD_ENV_METRICS_ADMIN)) != NULL) { config->metrics_endpoint_admin_session = (ushort)(o_strcmp(value, "1")==0); } if ((value = getenv(GLEWLWYD_ENV_METRICS_BIND_ADDRESS)) != NULL && o_strlen(value)) { o_free(config->bind_address_metrics); config->bind_address_metrics = o_strdup(value); if (config->bind_address_metrics == NULL) { fprintf(stderr, "Error allocating config->bind_address_metrics (env), exiting\n"); ret = G_ERROR_PARAM; } } return ret; } /** * Check if all mandatory configuration parameters are present and correct * Initialize some parameters with default value if not set */ int check_config(struct config_elements * config) { int ret = G_OK; if (!o_strlen(config->external_url)) { fprintf(stderr, "Error - configuration external_url mandatory\n"); ret = G_ERROR_PARAM; } if (!o_strlen(config->user_module_path)) { fprintf(stderr, "Error - configuration user_module_path mandatory\n"); ret = G_ERROR_PARAM; } if (!o_strlen(config->client_module_path)) { fprintf(stderr, "Error - configuration client_module_path mandatory\n"); ret = G_ERROR_PARAM; } if (!o_strlen(config->user_auth_scheme_module_path)) { fprintf(stderr, "Error - configuration user_auth_scheme_module_path mandatory\n"); ret = G_ERROR_PARAM; } if (!o_strlen(config->plugin_module_path)) { fprintf(stderr, "Error - configuration plugin_module_path mandatory\n"); ret = G_ERROR_PARAM; } if (config->conn == NULL) { fprintf(stderr, "Error - no database configuration specified\n"); ret = G_ERROR_PARAM; } if (!config->port) { config->port = GLEWLWYD_DEFAULT_PORT; } return ret; } /** * Print help message to output file specified */ void print_help(FILE * output) { fprintf(output, "\nGlewlwyd Single-Sign-On (SSO) server with multiple factor authentication\n"); fprintf(output, "\n"); fprintf(output, "Version %s\n", _GLEWLWYD_VERSION_); fprintf(output, "\n"); fprintf(output, "Copyright 2016-2021 Nicolas Mora <mail@babelouest.org>\n"); fprintf(output, "\n"); fprintf(output, "This program is free software; you can redistribute it and/or\n"); fprintf(output, "modify it under the terms of the GNU GENERAL PUBLIC LICENSE\n"); fprintf(output, "License as published by the Free Software Foundation;\n"); fprintf(output, "version 3 of the License.\n"); fprintf(output, "\n"); fprintf(output, "Command-line options:\n"); fprintf(output, "\n"); fprintf(output, "-c --config-file PATH\n"); fprintf(output, "\tPath to configuration file\n"); fprintf(output, "-e --env-variables\n"); fprintf(output, "\tUse environment variables to configure Glewlwyd\n"); fprintf(output, "-p --port PORT\n"); fprintf(output, "\tPort to listen to\n"); fprintf(output, "-u --url-prefix PREFIX\n"); fprintf(output, "\tAPI URL prefix\n"); fprintf(output, "-m --log-mode MODE\n"); fprintf(output, "\tLog modes available:\n"); fprintf(output, "\tconsole, syslog or file\n"); fprintf(output, "\tIf you want multiple modes, separate them with a comma \",\"\n"); fprintf(output, "\tdefault: console\n"); fprintf(output, "-l --log-level LEVEL\n"); fprintf(output, "\tLog levels available:\n"); fprintf(output, "\tNONE, ERROR, WARNING, INFO, DEBUG\n"); fprintf(output, "\tdefault: INFO\n"); fprintf(output, "-f --log-file PATH\n"); fprintf(output, "\tPath for log file if log mode file is specified\n"); fprintf(output, "-v --version\n"); fprintf(output, "\tPrint Glewlwyd's current version\n\n"); fprintf(output, "-h --help\n"); fprintf(output, "\tPrint this message\n\n"); } /** * handles signal catch to exit properly when ^C is used for example * I don't like global variables but it looks fine to people who designed this */ void * signal_thread(void *arg) { sigset_t *sigs = arg; int res, signum; res = sigwait(sigs, &signum); if (res) { fprintf(stderr, "Glewlwyd - Waiting for signals failed\n"); exit(1); } if (signum == SIGQUIT || signum == SIGINT || signum == SIGTERM || signum == SIGHUP) { y_log_message(Y_LOG_LEVEL_INFO, "Glewlwyd - Received close signal: %s", strsignal(signum)); pthread_mutex_lock(&global_handler_close_lock); pthread_cond_signal(&global_handler_close_cond); pthread_mutex_unlock(&global_handler_close_lock); return NULL; } else if (signum == SIGBUS) { fprintf(stderr, "Glewlwyd - Received bus error signal\n"); exit(256-signum); } else if (signum == SIGSEGV) { fprintf(stderr, "Glewlwyd - Received segmentation fault signal\n"); exit(256-signum); } else if (signum == SIGILL) { fprintf(stderr, "Glewlwyd - Received illegal instruction signal\n"); exit(256-signum); } else { y_log_message(Y_LOG_LEVEL_WARNING, "Glewlwyd - Received unexpected signal: %s", strsignal(signum)); } return NULL; } int module_instance_parameters_check(const char * module_parameters, const char * instance_parameters) { json_t * j_parameters = json_loads(module_parameters, JSON_DECODE_ANY, NULL), * j_instance_parameters = json_loads(instance_parameters, JSON_DECODE_ANY, NULL), * j_parameter, * j_value; int ret = G_OK; const char * key; json_object_foreach(j_parameters, key, j_parameter) { if (json_object_get(j_parameter, "mandatory") == json_true() && json_object_get(j_instance_parameters, key) == NULL) { ret = G_ERROR_PARAM; break; } else if ((j_value = json_object_get(j_instance_parameters, key)) != NULL) { if ((0 == o_strcmp("string", json_string_value(json_object_get(j_parameter, "type"))) || 0 == o_strcmp("list", json_string_value(json_object_get(j_parameter, "type")))) && !json_is_string(j_value)) { ret = G_ERROR_PARAM; break; } else if (0 == o_strcmp("number", json_string_value(json_object_get(j_parameter, "type"))) && !json_is_number(j_value)) { ret = G_ERROR_PARAM; break; } else if (0 == o_strcmp("boolean", json_string_value(json_object_get(j_parameter, "type"))) && !json_is_boolean(j_value)) { ret = G_ERROR_PARAM; break; } } } json_decref(j_parameters); json_decref(j_instance_parameters); return ret; } static int load_user_module_file(struct config_elements * config, const char * file_path) { void * file_handle; struct _user_module * cur_user_module = NULL; int ret; json_t * j_parameters; file_handle = dlopen(file_path, RTLD_LAZY); if (file_handle != NULL) { cur_user_module = o_malloc(sizeof(struct _user_module)); if (cur_user_module != NULL) { cur_user_module->name = NULL; cur_user_module->file_handle = file_handle; cur_user_module->api_version = 0.0; *(void **) (&cur_user_module->user_module_load) = dlsym(file_handle, "user_module_load"); *(void **) (&cur_user_module->user_module_unload) = dlsym(file_handle, "user_module_unload"); *(void **) (&cur_user_module->user_module_init) = dlsym(file_handle, "user_module_init"); *(void **) (&cur_user_module->user_module_close) = dlsym(file_handle, "user_module_close"); *(void **) (&cur_user_module->user_module_count_total) = dlsym(file_handle, "user_module_count_total"); *(void **) (&cur_user_module->user_module_get_list) = dlsym(file_handle, "user_module_get_list"); *(void **) (&cur_user_module->user_module_get) = dlsym(file_handle, "user_module_get"); *(void **) (&cur_user_module->user_module_get_profile) = dlsym(file_handle, "user_module_get_profile"); *(void **) (&cur_user_module->user_module_is_valid) = dlsym(file_handle, "user_module_is_valid"); *(void **) (&cur_user_module->user_module_add) = dlsym(file_handle, "user_module_add"); *(void **) (&cur_user_module->user_module_update) = dlsym(file_handle, "user_module_update"); *(void **) (&cur_user_module->user_module_update_profile) = dlsym(file_handle, "user_module_update_profile"); *(void **) (&cur_user_module->user_module_delete) = dlsym(file_handle, "user_module_delete"); *(void **) (&cur_user_module->user_module_check_password) = dlsym(file_handle, "user_module_check_password"); *(void **) (&cur_user_module->user_module_update_password) = dlsym(file_handle, "user_module_update_password"); if (cur_user_module->user_module_load != NULL && cur_user_module->user_module_unload != NULL && cur_user_module->user_module_init != NULL && cur_user_module->user_module_close != NULL && cur_user_module->user_module_count_total != NULL && cur_user_module->user_module_get_list != NULL && cur_user_module->user_module_get != NULL && cur_user_module->user_module_get_profile != NULL && cur_user_module->user_module_is_valid != NULL && cur_user_module->user_module_add != NULL && cur_user_module->user_module_update != NULL && cur_user_module->user_module_update_profile != NULL && cur_user_module->user_module_delete != NULL && cur_user_module->user_module_check_password != NULL && cur_user_module->user_module_update_password != NULL) { j_parameters = cur_user_module->user_module_load(config->config_m); if (check_result_value(j_parameters, G_OK)) { cur_user_module->name = o_strdup(json_string_value(json_object_get(j_parameters, "name"))); cur_user_module->display_name = o_strdup(json_string_value(json_object_get(j_parameters, "display_name"))); cur_user_module->description = o_strdup(json_string_value(json_object_get(j_parameters, "description"))); cur_user_module->api_version = json_real_value(json_object_get(j_parameters, "api_version")); if (o_strlen(cur_user_module->name) && get_user_module_lib(config, cur_user_module->name) == NULL) { if (cur_user_module->api_version >= _GLEWLWYD_USER_MODULE_VERSION) { if (!pthread_mutex_lock(&config->module_lock)) { if (pointer_list_append(config->user_module_list, (void*)cur_user_module)) { y_log_message(Y_LOG_LEVEL_INFO, "Loading user module %s - %s", file_path, cur_user_module->name); ret = G_OK; } else { cur_user_module->user_module_unload(config->config_m); dlclose(file_handle); o_free(cur_user_module->name); o_free(cur_user_module->display_name); o_free(cur_user_module->description); o_free(cur_user_module); y_log_message(Y_LOG_LEVEL_ERROR, "load_user_module_file - Error pointer_list_append"); ret = G_ERROR; } pthread_mutex_unlock(&config->module_lock); } else { cur_user_module->user_module_unload(config->config_m); dlclose(file_handle); o_free(cur_user_module->name); o_free(cur_user_module->display_name); o_free(cur_user_module->description); o_free(cur_user_module); y_log_message(Y_LOG_LEVEL_ERROR, "load_user_module_file - Error pthread_mutex_lock"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_module_file - User module with name '%s' has invalid api_version: %.2f, minimum required: %.2f", cur_user_module->name, cur_user_module->api_version, _GLEWLWYD_USER_MODULE_VERSION); cur_user_module->user_module_unload(config->config_m); dlclose(file_handle); o_free(cur_user_module->name); o_free(cur_user_module->display_name); o_free(cur_user_module->description); o_free(cur_user_module); ret = G_ERROR_PARAM; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_module_file - User module with name '%s' already present or name empty", cur_user_module->name); cur_user_module->user_module_unload(config->config_m); dlclose(file_handle); o_free(cur_user_module->name); o_free(cur_user_module->display_name); o_free(cur_user_module->description); o_free(cur_user_module); ret = G_ERROR_PARAM; } } else { dlclose(file_handle); o_free(cur_user_module); y_log_message(Y_LOG_LEVEL_ERROR, "load_user_module_file - Error user_module_load for module %s", file_path); ret = G_ERROR_MEMORY; } json_decref(j_parameters); } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_module_file - Error module %s has not all required functions", file_path); y_log_message(Y_LOG_LEVEL_ERROR, " - user_module_load: %s", (cur_user_module->user_module_load != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_module_unload: %s", (cur_user_module->user_module_unload != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_module_init: %s", (cur_user_module->user_module_init != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_module_close: %s", (cur_user_module->user_module_close != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_module_count_total: %s", (cur_user_module->user_module_count_total != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_module_get_list: %s", (cur_user_module->user_module_get_list != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_module_get: %s", (cur_user_module->user_module_get != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_module_get_profile: %s", (cur_user_module->user_module_get_profile != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_module_is_valid: %s", (cur_user_module->user_module_is_valid != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_module_add: %s", (cur_user_module->user_module_add != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_module_update: %s", (cur_user_module->user_module_update != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_module_update_profile: %s", (cur_user_module->user_module_update_profile != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_module_delete: %s", (cur_user_module->user_module_delete != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_module_check_password: %s", (cur_user_module->user_module_check_password != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_module_update_password: %s", (cur_user_module->user_module_update_password != NULL?"found":"not found")); dlclose(file_handle); o_free(cur_user_module); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_module_file - Error allocating resources for cur_user_module"); dlclose(file_handle); ret = G_ERROR_MEMORY; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_module_file - Error opening module file %s, reason: %s", file_path, dlerror()); ret = G_ERROR; } return ret; } int init_user_module_list(struct config_elements * config) { int ret = G_OK, is_reg; DIR * modules_directory; struct dirent * in_file; char * file_path; struct stat u_stat; memset(&u_stat, 0, sizeof(struct stat)); config->user_module_list = o_malloc(sizeof(struct _pointer_list)); if (config->user_module_list != NULL) { pointer_list_init(config->user_module_list); // read module_path and load modules if (NULL == (modules_directory = opendir(config->user_module_path))) { y_log_message(Y_LOG_LEVEL_ERROR, "init_user_module_list - Error reading libraries folder %s", config->user_module_path); ret = G_ERROR; } else { while ((in_file = readdir(modules_directory))) { is_reg = 0; file_path = NULL; if (in_file->d_type == DT_REG) { is_reg = 1; file_path = msprintf("%s/%s", config->user_module_path, in_file->d_name); } else if (in_file->d_type == DT_UNKNOWN) { file_path = msprintf("%s/%s", config->user_module_path, in_file->d_name); if (!stat(file_path, &u_stat)) { if (S_ISREG(u_stat.st_mode)) { is_reg = 1; } } } if (is_reg) { if (load_user_module_file(config, file_path) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "init_user_module_list - Error opening module file %s", file_path); } } o_free(file_path); } closedir(modules_directory); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "init_user_module_list - Error allocating resources for config->user_module_list"); ret = G_ERROR_MEMORY; } return ret; } int load_user_module_instance_list(struct config_elements * config) { json_t * j_query, * j_result, * j_instance, * j_parameters, * j_init; int res, ret; size_t index, i; struct _user_module_instance * cur_instance; struct _user_module * module = NULL; char * message; config->user_module_instance_list = o_malloc(sizeof(struct _pointer_list)); if (config->user_module_instance_list != NULL) { pointer_list_init(config->user_module_instance_list); j_query = json_pack("{sss[sssssss]ss}", "table", GLEWLWYD_TABLE_USER_MODULE_INSTANCE, "columns", "gumi_module AS module", "gumi_name AS name", "gumi_order AS order_by", "gumi_parameters AS parameters", "gumi_readonly AS readonly", "gumi_multiple_passwords AS multiple_passwords", "gumi_enabled AS enabled", "order_by", "gumi_order"); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (!pthread_mutex_lock(&config->module_lock)) { ret = G_OK; json_array_foreach(j_result, index, j_instance) { module = NULL; for (i=0; i<pointer_list_size(config->user_module_list); i++) { module = (struct _user_module *)pointer_list_get_at(config->user_module_list, i); if (0 == o_strcmp(module->name, json_string_value(json_object_get(j_instance, "module")))) { break; } else { module = NULL; } } if (module != NULL) { cur_instance = o_malloc(sizeof(struct _user_module_instance)); if (cur_instance != NULL) { cur_instance->cls = NULL; cur_instance->name = o_strdup(json_string_value(json_object_get(j_instance, "name"))); cur_instance->module = module; cur_instance->readonly = json_integer_value(json_object_get(j_instance, "readonly")); cur_instance->multiple_passwords = json_integer_value(json_object_get(j_instance, "multiple_passwords")); if (pointer_list_append(config->user_module_instance_list, cur_instance)) { if (json_integer_value(json_object_get(j_instance, "enabled"))) { j_parameters = json_loads(json_string_value(json_object_get(j_instance, "parameters")), JSON_DECODE_ANY, NULL); if (j_parameters != NULL) { j_init = module->user_module_init(config->config_m, cur_instance->readonly, cur_instance->multiple_passwords, j_parameters, &cur_instance->cls); if (check_result_value(j_init, G_OK)) { cur_instance->enabled = 1; } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_module_instance_list - Error init module %s/%s", module->name, json_string_value(json_object_get(j_instance, "name"))); message = json_dumps(j_init, JSON_INDENT(2)); y_log_message(Y_LOG_LEVEL_DEBUG, message); o_free(message); cur_instance->enabled = 0; } json_decref(j_init); } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_module_instance_list - Error parsing module parameters %s/%s: %s", module->name, json_string_value(json_object_get(j_instance, "name")), json_string_value(json_object_get(j_instance, "parameters"))); cur_instance->enabled = 0; } json_decref(j_parameters); } else { cur_instance->enabled = 0; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_module_instance_list - Error reallocating resources for user_module_instance_list"); o_free(cur_instance->name); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_module_instance_list - Error allocating resources for cur_instance"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_module_instance_list - Error module %s not found", json_string_value(json_object_get(j_instance, "module"))); } } json_decref(j_result); pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_module_instance_list - Error pthread_mutex_lock"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_module_instance_list - Error executing j_query"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_module_instance_list - Error allocating resource for config->user_module_instance_list"); ret = G_ERROR_MEMORY; } return ret; } struct _user_module_instance * get_user_module_instance(struct config_elements * config, const char * name) { size_t i; struct _user_module_instance * cur_instance; for (i=0; i<pointer_list_size(config->user_module_instance_list); i++) { cur_instance = (struct _user_module_instance *)pointer_list_get_at(config->user_module_instance_list, i); if (cur_instance != NULL && 0 == o_strcmp(cur_instance->name, name)) { return cur_instance; } } return NULL; } struct _user_module * get_user_module_lib(struct config_elements * config, const char * name) { size_t i; struct _user_module * module; for (i=0; i<pointer_list_size(config->user_module_list); i++) { module = (struct _user_module *)pointer_list_get_at(config->user_module_list, i); if (module != NULL && 0 == o_strcmp(module->name, name)) { return module; } } return NULL; } void close_user_module_instance_list(struct config_elements * config) { size_t i; if (!pthread_mutex_lock(&config->module_lock)) { for (i=0; i<pointer_list_size(config->user_module_instance_list); i++) { struct _user_module_instance * instance = (struct _user_module_instance *)pointer_list_get_at(config->user_module_instance_list, i); if (instance != NULL) { if (instance->enabled && instance->module->user_module_close(config->config_m, instance->cls) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "close_user_module_instance_list - Error user_module_close for instance '%s'/'%s'", instance->module->name, instance->name); } o_free(instance->name); o_free(instance); } } pointer_list_clean(config->user_module_instance_list); o_free(config->user_module_instance_list); pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "close_user_module_instance_list - Error pthread_mutex_lock"); } } void close_user_module_list(struct config_elements * config) { size_t i; if (!pthread_mutex_lock(&config->module_lock)) { for (i=0; i<pointer_list_size(config->user_module_list); i++) { struct _user_module * module = (struct _user_module *)pointer_list_get_at(config->user_module_list, i); if (module != NULL) { if (module->user_module_unload(config->config_m) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "close_user_module_list - Error user_module_unload for module '%s'", module->name); } /* * dlclose() makes valgrind not useful when it comes to libraries * they say it's not relevant to use it anyway * I'll let it here until I'm sure */ #ifndef DEBUG if (dlclose(module->file_handle)) { y_log_message(Y_LOG_LEVEL_ERROR, "close_user_module_list - Error dlclose for module '%s'", module->name); } #endif o_free(module->name); o_free(module->display_name); o_free(module->description); o_free(module); } } pointer_list_clean(config->user_module_list); o_free(config->user_module_list); pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "close_user_module_list - Error pthread_mutex_lock"); } } static int load_user_middleware_module_file(struct config_elements * config, const char * file_path) { void * file_handle; struct _user_middleware_module * cur_user_middleware_module = NULL; int ret; json_t * j_parameters; file_handle = dlopen(file_path, RTLD_LAZY); if (file_handle != NULL) { cur_user_middleware_module = o_malloc(sizeof(struct _user_middleware_module)); if (cur_user_middleware_module != NULL) { cur_user_middleware_module->name = NULL; cur_user_middleware_module->file_handle = file_handle; cur_user_middleware_module->api_version = 0.0; *(void **) (&cur_user_middleware_module->user_middleware_module_load) = dlsym(file_handle, "user_middleware_module_load"); *(void **) (&cur_user_middleware_module->user_middleware_module_unload) = dlsym(file_handle, "user_middleware_module_unload"); *(void **) (&cur_user_middleware_module->user_middleware_module_init) = dlsym(file_handle, "user_middleware_module_init"); *(void **) (&cur_user_middleware_module->user_middleware_module_close) = dlsym(file_handle, "user_middleware_module_close"); *(void **) (&cur_user_middleware_module->user_middleware_module_get_list) = dlsym(file_handle, "user_middleware_module_get_list"); *(void **) (&cur_user_middleware_module->user_middleware_module_get) = dlsym(file_handle, "user_middleware_module_get"); *(void **) (&cur_user_middleware_module->user_middleware_module_get_profile) = dlsym(file_handle, "user_middleware_module_get_profile"); *(void **) (&cur_user_middleware_module->user_middleware_module_update) = dlsym(file_handle, "user_middleware_module_update"); *(void **) (&cur_user_middleware_module->user_middleware_module_delete) = dlsym(file_handle, "user_middleware_module_delete"); if (cur_user_middleware_module->user_middleware_module_load != NULL && cur_user_middleware_module->user_middleware_module_unload != NULL && cur_user_middleware_module->user_middleware_module_init != NULL && cur_user_middleware_module->user_middleware_module_close != NULL && cur_user_middleware_module->user_middleware_module_get_list != NULL && cur_user_middleware_module->user_middleware_module_get != NULL && cur_user_middleware_module->user_middleware_module_get_profile != NULL && cur_user_middleware_module->user_middleware_module_update != NULL && cur_user_middleware_module->user_middleware_module_delete != NULL) { j_parameters = cur_user_middleware_module->user_middleware_module_load(config->config_m); if (check_result_value(j_parameters, G_OK)) { cur_user_middleware_module->name = o_strdup(json_string_value(json_object_get(j_parameters, "name"))); cur_user_middleware_module->display_name = o_strdup(json_string_value(json_object_get(j_parameters, "display_name"))); cur_user_middleware_module->description = o_strdup(json_string_value(json_object_get(j_parameters, "description"))); cur_user_middleware_module->api_version = json_real_value(json_object_get(j_parameters, "api_version")); if (o_strlen(cur_user_middleware_module->name) && get_user_middleware_module_lib(config, cur_user_middleware_module->name) == NULL) { if (cur_user_middleware_module->api_version >= _GLEWLWYD_USER_MODULE_VERSION) { if (!pthread_mutex_lock(&config->module_lock)) { if (pointer_list_append(config->user_middleware_module_list, (void*)cur_user_middleware_module)) { y_log_message(Y_LOG_LEVEL_INFO, "Loading user middleware module %s - %s", file_path, cur_user_middleware_module->name); ret = G_OK; } else { cur_user_middleware_module->user_middleware_module_unload(config->config_m); dlclose(file_handle); o_free(cur_user_middleware_module->name); o_free(cur_user_middleware_module->display_name); o_free(cur_user_middleware_module->description); o_free(cur_user_middleware_module); y_log_message(Y_LOG_LEVEL_ERROR, "load_user_middleware_module_file - Error pointer_list_append"); ret = G_ERROR; } pthread_mutex_unlock(&config->module_lock); } else { cur_user_middleware_module->user_middleware_module_unload(config->config_m); dlclose(file_handle); o_free(cur_user_middleware_module->name); o_free(cur_user_middleware_module->display_name); o_free(cur_user_middleware_module->description); o_free(cur_user_middleware_module); y_log_message(Y_LOG_LEVEL_ERROR, "load_user_middleware_module_file - Error pthread_mutex_lock"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_middleware_module_file - User module with name '%s' has invalid api_version: %.2f, minimum required: %.2f", cur_user_middleware_module->name, cur_user_middleware_module->api_version, _GLEWLWYD_USER_MODULE_VERSION); cur_user_middleware_module->user_middleware_module_unload(config->config_m); dlclose(file_handle); o_free(cur_user_middleware_module->name); o_free(cur_user_middleware_module->display_name); o_free(cur_user_middleware_module->description); o_free(cur_user_middleware_module); ret = G_ERROR_PARAM; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_middleware_module_file - User module with name '%s' already present or name empty", cur_user_middleware_module->name); cur_user_middleware_module->user_middleware_module_unload(config->config_m); dlclose(file_handle); o_free(cur_user_middleware_module->name); o_free(cur_user_middleware_module->display_name); o_free(cur_user_middleware_module->description); o_free(cur_user_middleware_module); ret = G_ERROR_PARAM; } } else { dlclose(file_handle); o_free(cur_user_middleware_module); y_log_message(Y_LOG_LEVEL_ERROR, "load_user_middleware_module_file - Error user_middleware_module_load for module %s", file_path); ret = G_ERROR_MEMORY; } json_decref(j_parameters); } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_middleware_module_file - Error module %s has not all required functions", file_path); y_log_message(Y_LOG_LEVEL_ERROR, " - user_middleware_module_load: %s", (cur_user_middleware_module->user_middleware_module_load != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_middleware_module_unload: %s", (cur_user_middleware_module->user_middleware_module_unload != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_middleware_module_init: %s", (cur_user_middleware_module->user_middleware_module_init != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_middleware_module_close: %s", (cur_user_middleware_module->user_middleware_module_close != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_middleware_module_get_list: %s", (cur_user_middleware_module->user_middleware_module_get_list != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_middleware_module_get: %s", (cur_user_middleware_module->user_middleware_module_get != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_middleware_module_get_profile: %s", (cur_user_middleware_module->user_middleware_module_get_profile != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_middleware_module_update: %s", (cur_user_middleware_module->user_middleware_module_update != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_middleware_module_delete: %s", (cur_user_middleware_module->user_middleware_module_delete != NULL?"found":"not found")); dlclose(file_handle); o_free(cur_user_middleware_module); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_middleware_module_file - Error allocating resources for cur_user_middleware_module"); dlclose(file_handle); ret = G_ERROR_MEMORY; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_middleware_module_file - Error opening module file %s, reason: %s", file_path, dlerror()); ret = G_ERROR; } return ret; } int init_user_middleware_module_list(struct config_elements * config) { int ret = G_OK, is_reg; DIR * modules_directory; struct dirent * in_file; char * file_path; struct stat u_stat; memset(&u_stat, 0, sizeof(struct stat)); config->user_middleware_module_list = o_malloc(sizeof(struct _pointer_list)); if (config->user_middleware_module_list != NULL) { pointer_list_init(config->user_middleware_module_list); if (o_strlen(config->user_middleware_module_path)) { // read module_path and load modules if (NULL == (modules_directory = opendir(config->user_middleware_module_path))) { y_log_message(Y_LOG_LEVEL_ERROR, "init_user_middleware_module_list - Error reading libraries folder %s", config->user_middleware_module_path); } else { while ((in_file = readdir(modules_directory))) { is_reg = 0; file_path = NULL; if (in_file->d_type == DT_REG) { is_reg = 1; file_path = msprintf("%s/%s", config->user_middleware_module_path, in_file->d_name); } else if (in_file->d_type == DT_UNKNOWN) { file_path = msprintf("%s/%s", config->user_middleware_module_path, in_file->d_name); if (!stat(file_path, &u_stat)) { if (S_ISREG(u_stat.st_mode)) { is_reg = 1; } } } if (is_reg) { if (load_user_middleware_module_file(config, file_path) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "init_user_middleware_module_list - Error opening module file %s", file_path); } } o_free(file_path); } closedir(modules_directory); } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "init_user_middleware_module_list - Error allocating resources for config->user_middleware_module_list"); ret = G_ERROR_MEMORY; } return ret; } int load_user_middleware_module_instance_list(struct config_elements * config) { json_t * j_query, * j_result, * j_instance, * j_parameters, * j_init; int res, ret; size_t index, i; struct _user_middleware_module_instance * cur_instance; struct _user_middleware_module * module = NULL; char * message; config->user_middleware_module_instance_list = o_malloc(sizeof(struct _pointer_list)); if (config->user_middleware_module_instance_list != NULL) { pointer_list_init(config->user_middleware_module_instance_list); if (o_strlen(config->user_middleware_module_path)) { j_query = json_pack("{sss[sssss]ss}", "table", GLEWLWYD_TABLE_USER_MIDDLEWARE_MODULE_INSTANCE, "columns", "gummi_module AS module", "gummi_name AS name", "gummi_order AS order_by", "gummi_parameters AS parameters", "gummi_enabled AS enabled", "order_by", "gummi_order"); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; if (!pthread_mutex_lock(&config->module_lock)) { json_array_foreach(j_result, index, j_instance) { module = NULL; for (i=0; i<pointer_list_size(config->user_middleware_module_list); i++) { module = (struct _user_middleware_module *)pointer_list_get_at(config->user_middleware_module_list, i); if (0 == o_strcmp(module->name, json_string_value(json_object_get(j_instance, "module")))) { break; } else { module = NULL; } } if (module != NULL) { cur_instance = o_malloc(sizeof(struct _user_middleware_module_instance)); if (cur_instance != NULL) { cur_instance->cls = NULL; cur_instance->name = o_strdup(json_string_value(json_object_get(j_instance, "name"))); cur_instance->module = module; if (pointer_list_append(config->user_middleware_module_instance_list, cur_instance)) { if (json_integer_value(json_object_get(j_instance, "enabled"))) { j_parameters = json_loads(json_string_value(json_object_get(j_instance, "parameters")), JSON_DECODE_ANY, NULL); if (j_parameters != NULL) { j_init = module->user_middleware_module_init(config->config_m, j_parameters, &cur_instance->cls); if (check_result_value(j_init, G_OK)) { cur_instance->enabled = 1; } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_middleware_module_instance_list - Error init module %s/%s", module->name, json_string_value(json_object_get(j_instance, "name"))); message = json_dumps(j_init, JSON_INDENT(2)); y_log_message(Y_LOG_LEVEL_DEBUG, message); o_free(message); cur_instance->enabled = 0; ret = G_ERROR_PARAM; } json_decref(j_init); } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_middleware_module_instance_list - Error parsing module parameters %s/%s: %s", module->name, json_string_value(json_object_get(j_instance, "name")), json_string_value(json_object_get(j_instance, "parameters"))); cur_instance->enabled = 0; } json_decref(j_parameters); } else { cur_instance->enabled = 0; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_middleware_module_instance_list - Error reallocating resources for user_middleware_module_instance_list"); o_free(cur_instance->name); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_middleware_module_instance_list - Error allocating resources for cur_instance"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_middleware_module_instance_list - Error module %s not found", json_string_value(json_object_get(j_instance, "module"))); } } json_decref(j_result); pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_middleware_module_instance_list - Error pthread_mutex_lock"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_middleware_module_instance_list - Error executing j_query"); ret = G_ERROR_DB; } } else { // Do not return an error for backwards compatibility y_log_message(Y_LOG_LEVEL_WARNING, "Warning - user_middleware_module_path missing in config file"); ret = G_OK; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_middleware_module_instance_list - Error allocating resource for config->user_middleware_module_instance_list"); ret = G_ERROR_MEMORY; } return ret; } struct _user_middleware_module_instance * get_user_middleware_module_instance(struct config_elements * config, const char * name) { size_t i; struct _user_middleware_module_instance * cur_instance; for (i=0; i<pointer_list_size(config->user_middleware_module_instance_list); i++) { cur_instance = (struct _user_middleware_module_instance *)pointer_list_get_at(config->user_middleware_module_instance_list, i); if (cur_instance != NULL && 0 == o_strcmp(cur_instance->name, name)) { return cur_instance; } } return NULL; } struct _user_middleware_module * get_user_middleware_module_lib(struct config_elements * config, const char * name) { size_t i; struct _user_middleware_module * module; for (i=0; i<pointer_list_size(config->user_middleware_module_list); i++) { module = (struct _user_middleware_module *)pointer_list_get_at(config->user_middleware_module_list, i); if (module != NULL && 0 == o_strcmp(module->name, name)) { return module; } } return NULL; } void close_user_middleware_module_instance_list(struct config_elements * config) { size_t i; if (!pthread_mutex_lock(&config->module_lock)) { for (i=0; i<pointer_list_size(config->user_middleware_module_instance_list); i++) { struct _user_middleware_module_instance * instance = (struct _user_middleware_module_instance *)pointer_list_get_at(config->user_middleware_module_instance_list, i); if (instance != NULL) { if (instance->enabled && instance->module->user_middleware_module_close(config->config_m, instance->cls) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "close_user_middleware_module_instance_list - Error user_middleware_module_close for instance '%s'/'%s'", instance->module->name, instance->name); } o_free(instance->name); o_free(instance); } } pointer_list_clean(config->user_middleware_module_instance_list); o_free(config->user_middleware_module_instance_list); pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "close_user_middleware_module_instance_list - Error pthread_mutex_lock"); } } void close_user_middleware_module_list(struct config_elements * config) { size_t i; if (!pthread_mutex_lock(&config->module_lock)) { for (i=0; i<pointer_list_size(config->user_middleware_module_list); i++) { struct _user_middleware_module * module = (struct _user_middleware_module *)pointer_list_get_at(config->user_middleware_module_list, i); if (module != NULL) { if (module->user_middleware_module_unload(config->config_m) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "close_user_middleware_module_list - Error user_middleware_module_unload for module '%s'", module->name); } /* * dlclose() makes valgrind not useful when it comes to libraries * they say it's not relevant to use it anyway * I'll let it here until I'm sure */ #ifndef DEBUG if (dlclose(module->file_handle)) { y_log_message(Y_LOG_LEVEL_ERROR, "close_user_middleware_module_list - Error dlclose for module '%s'", module->name); } #endif o_free(module->name); o_free(module->display_name); o_free(module->description); o_free(module); } } pointer_list_clean(config->user_middleware_module_list); o_free(config->user_middleware_module_list); pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "close_user_middleware_module_list - Error pthread_mutex_lock"); } } static int load_user_auth_scheme_module_file(struct config_elements * config, const char * file_path) { void * file_handle; struct _user_auth_scheme_module * cur_user_auth_scheme_module = NULL; int ret; json_t * j_module; file_handle = dlopen(file_path, RTLD_LAZY); if (file_handle != NULL) { cur_user_auth_scheme_module = o_malloc(sizeof(struct _user_auth_scheme_module)); if (cur_user_auth_scheme_module != NULL) { cur_user_auth_scheme_module->name = NULL; cur_user_auth_scheme_module->file_handle = file_handle; cur_user_auth_scheme_module->api_version = 0.0; *(void **) (&cur_user_auth_scheme_module->user_auth_scheme_module_load) = dlsym(file_handle, "user_auth_scheme_module_load"); *(void **) (&cur_user_auth_scheme_module->user_auth_scheme_module_unload) = dlsym(file_handle, "user_auth_scheme_module_unload"); *(void **) (&cur_user_auth_scheme_module->user_auth_scheme_module_init) = dlsym(file_handle, "user_auth_scheme_module_init"); *(void **) (&cur_user_auth_scheme_module->user_auth_scheme_module_close) = dlsym(file_handle, "user_auth_scheme_module_close"); *(void **) (&cur_user_auth_scheme_module->user_auth_scheme_module_register) = dlsym(file_handle, "user_auth_scheme_module_register"); *(void **) (&cur_user_auth_scheme_module->user_auth_scheme_module_register_get) = dlsym(file_handle, "user_auth_scheme_module_register_get"); *(void **) (&cur_user_auth_scheme_module->user_auth_scheme_module_deregister) = dlsym(file_handle, "user_auth_scheme_module_deregister"); *(void **) (&cur_user_auth_scheme_module->user_auth_scheme_module_validate) = dlsym(file_handle, "user_auth_scheme_module_validate"); *(void **) (&cur_user_auth_scheme_module->user_auth_scheme_module_trigger) = dlsym(file_handle, "user_auth_scheme_module_trigger"); *(void **) (&cur_user_auth_scheme_module->user_auth_scheme_module_can_use) = dlsym(file_handle, "user_auth_scheme_module_can_use"); *(void **) (&cur_user_auth_scheme_module->user_auth_scheme_module_identify) = dlsym(file_handle, "user_auth_scheme_module_identify"); if (cur_user_auth_scheme_module->user_auth_scheme_module_load != NULL && cur_user_auth_scheme_module->user_auth_scheme_module_unload != NULL && cur_user_auth_scheme_module->user_auth_scheme_module_init != NULL && cur_user_auth_scheme_module->user_auth_scheme_module_close != NULL && cur_user_auth_scheme_module->user_auth_scheme_module_register != NULL && cur_user_auth_scheme_module->user_auth_scheme_module_register_get != NULL && cur_user_auth_scheme_module->user_auth_scheme_module_deregister != NULL && cur_user_auth_scheme_module->user_auth_scheme_module_validate != NULL && cur_user_auth_scheme_module->user_auth_scheme_module_trigger != NULL && cur_user_auth_scheme_module->user_auth_scheme_module_can_use != NULL && cur_user_auth_scheme_module->user_auth_scheme_module_identify != NULL) { j_module = cur_user_auth_scheme_module->user_auth_scheme_module_load(config->config_m); if (check_result_value(j_module, G_OK)) { cur_user_auth_scheme_module->name = o_strdup(json_string_value(json_object_get(j_module, "name"))); cur_user_auth_scheme_module->display_name = o_strdup(json_string_value(json_object_get(j_module, "display_name"))); cur_user_auth_scheme_module->description = o_strdup(json_string_value(json_object_get(j_module, "description"))); cur_user_auth_scheme_module->api_version = json_real_value(json_object_get(j_module, "api_version")); if (o_strlen(cur_user_auth_scheme_module->name) && get_user_auth_scheme_module_lib(config, cur_user_auth_scheme_module->name) == NULL) { if (!pthread_mutex_lock(&config->module_lock)) { if (pointer_list_append(config->user_auth_scheme_module_list, cur_user_auth_scheme_module)) { y_log_message(Y_LOG_LEVEL_INFO, "Loading user auth scheme module %s - %s", file_path, cur_user_auth_scheme_module->name); ret = G_OK; } else { cur_user_auth_scheme_module->user_auth_scheme_module_unload(config->config_m); dlclose(file_handle); o_free(cur_user_auth_scheme_module->name); o_free(cur_user_auth_scheme_module->display_name); o_free(cur_user_auth_scheme_module->description); o_free(cur_user_auth_scheme_module); y_log_message(Y_LOG_LEVEL_ERROR, "load_user_auth_scheme_module_file - Error reallocating resources for user_auth_scheme_module_list"); ret = G_ERROR_MEMORY; } pthread_mutex_unlock(&config->module_lock); } else { cur_user_auth_scheme_module->user_auth_scheme_module_unload(config->config_m); dlclose(file_handle); o_free(cur_user_auth_scheme_module->name); o_free(cur_user_auth_scheme_module->display_name); o_free(cur_user_auth_scheme_module->description); o_free(cur_user_auth_scheme_module); y_log_message(Y_LOG_LEVEL_ERROR, "load_user_auth_scheme_module_file - Error pthread_mutex_lock"); ret = G_ERROR_MEMORY; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_auth_scheme_module_file - User auth scheme module with name '%s' already present or name empty", cur_user_auth_scheme_module->name); cur_user_auth_scheme_module->user_auth_scheme_module_unload(config->config_m); dlclose(file_handle); o_free(cur_user_auth_scheme_module->name); o_free(cur_user_auth_scheme_module->display_name); o_free(cur_user_auth_scheme_module->description); o_free(cur_user_auth_scheme_module); ret = G_ERROR; } } else { dlclose(file_handle); o_free(cur_user_auth_scheme_module); y_log_message(Y_LOG_LEVEL_ERROR, "load_user_auth_scheme_module_file - Error user_auth_scheme_module_load for module %s", file_path); ret = G_ERROR_MEMORY; } json_decref(j_module); } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_auth_scheme_module_file - Error module %s has not all required functions", file_path); y_log_message(Y_LOG_LEVEL_ERROR, " - user_auth_scheme_module_load: %s", (cur_user_auth_scheme_module->user_auth_scheme_module_load != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_auth_scheme_module_unload: %s", (cur_user_auth_scheme_module->user_auth_scheme_module_unload != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_auth_scheme_module_init: %s", (cur_user_auth_scheme_module->user_auth_scheme_module_init != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_auth_scheme_module_close: %s", (cur_user_auth_scheme_module->user_auth_scheme_module_close != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_auth_scheme_module_register: %s", (cur_user_auth_scheme_module->user_auth_scheme_module_register != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_auth_scheme_module_register_get: %s", (cur_user_auth_scheme_module->user_auth_scheme_module_register_get != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_auth_scheme_module_deregister: %s", (cur_user_auth_scheme_module->user_auth_scheme_module_deregister != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_auth_scheme_module_validate: %s", (cur_user_auth_scheme_module->user_auth_scheme_module_validate != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_auth_scheme_module_trigger: %s", (cur_user_auth_scheme_module->user_auth_scheme_module_trigger != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_auth_scheme_module_can_use: %s", (cur_user_auth_scheme_module->user_auth_scheme_module_can_use != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - user_auth_scheme_module_identify: %s", (cur_user_auth_scheme_module->user_auth_scheme_module_identify != NULL?"found":"not found")); dlclose(file_handle); o_free(cur_user_auth_scheme_module); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_auth_scheme_module_file - Error allocating resources for cur_user_auth_scheme_module"); dlclose(file_handle); ret = G_ERROR_MEMORY; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_auth_scheme_module_file - Error opening module file %s, reason: %s", file_path, dlerror()); ret = G_ERROR; } return ret; } int init_user_auth_scheme_module_list(struct config_elements * config) { int ret = G_OK, is_reg; DIR * modules_directory; struct dirent * in_file; char * file_path; struct stat u_stat; memset(&u_stat, 0, sizeof(struct stat)); config->user_auth_scheme_module_list = o_malloc(sizeof(struct _pointer_list)); if (config->user_auth_scheme_module_list != NULL) { pointer_list_init(config->user_auth_scheme_module_list); // read module_path and load modules if (NULL == (modules_directory = opendir(config->user_auth_scheme_module_path))) { y_log_message(Y_LOG_LEVEL_ERROR, "init_user_auth_scheme_module_list - Error reading libraries folder %s", config->user_auth_scheme_module_path); ret = G_ERROR; } else { while ((in_file = readdir(modules_directory))) { is_reg = 0; file_path = NULL; if (in_file->d_type == DT_REG) { file_path = msprintf("%s/%s", config->user_auth_scheme_module_path, in_file->d_name); is_reg = 1; } else if (in_file->d_type == DT_UNKNOWN) { file_path = msprintf("%s/%s", config->user_auth_scheme_module_path, in_file->d_name); if (!stat(file_path, &u_stat)) { if (S_ISREG(u_stat.st_mode)) { is_reg = 1; } } } if (is_reg) { if (load_user_auth_scheme_module_file(config, file_path) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "init_user_auth_scheme_module_list - Error opening module file %s", file_path); } } o_free(file_path); } closedir(modules_directory); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "init_user_auth_scheme_module_list - Error allocating resources for config->user_auth_scheme_module_list"); ret = G_ERROR_MEMORY; } return ret; } int load_user_auth_scheme_module_instance_list(struct config_elements * config) { json_t * j_query, * j_result, * j_instance, * j_parameters, * j_init; int res, ret; size_t index, i; struct _user_auth_scheme_module_instance * cur_instance; struct _user_auth_scheme_module * module = NULL; char * message; config->user_auth_scheme_module_instance_list = o_malloc(sizeof(struct _pointer_list)); if (config->user_auth_scheme_module_instance_list != NULL) { pointer_list_init(config->user_auth_scheme_module_instance_list); j_query = json_pack("{sss[ssssssss]}", "table", GLEWLWYD_TABLE_USER_AUTH_SCHEME_MODULE_INSTANCE, "columns", "guasmi_id", "guasmi_module AS module", "guasmi_name AS name", "guasmi_expiration", "guasmi_max_use", "guasmi_allow_user_register", "guasmi_parameters AS parameters", "guasmi_enabled AS enabled"); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (!pthread_mutex_lock(&config->module_lock)) { ret = G_OK; json_array_foreach(j_result, index, j_instance) { module = NULL; for (i=0; i<pointer_list_size(config->user_auth_scheme_module_list); i++) { module = (struct _user_auth_scheme_module *)pointer_list_get_at(config->user_auth_scheme_module_list, i); if (0 == o_strcmp(module->name, json_string_value(json_object_get(j_instance, "module")))) { break; } else { module = NULL; } } if (module != NULL) { cur_instance = o_malloc(sizeof(struct _user_auth_scheme_module_instance)); if (cur_instance != NULL) { cur_instance->cls = NULL; cur_instance->name = o_strdup(json_string_value(json_object_get(j_instance, "name"))); cur_instance->module = module; cur_instance->guasmi_id = json_integer_value(json_object_get(j_instance, "guasmi_id")); cur_instance->guasmi_expiration = json_integer_value(json_object_get(j_instance, "guasmi_expiration")); cur_instance->guasmi_max_use = json_integer_value(json_object_get(j_instance, "guasmi_max_use")); cur_instance->guasmi_allow_user_register = json_integer_value(json_object_get(j_instance, "guasmi_allow_user_register")); if (pointer_list_append(config->user_auth_scheme_module_instance_list, cur_instance)) { if (json_integer_value(json_object_get(j_instance, "enabled"))) { j_parameters = json_loads(json_string_value(json_object_get(j_instance, "parameters")), JSON_DECODE_ANY, NULL); if (j_parameters != NULL) { j_init = module->user_auth_scheme_module_init(config->config_m, j_parameters, cur_instance->name, &cur_instance->cls); if (check_result_value(j_init, G_OK)) { glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_AUTH_USER_VALID_SCHEME, 0, "scheme_type", module->name, "scheme_name", cur_instance->name, NULL); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_AUTH_USER_INVALID_SCHEME, 0, "scheme_type", module->name, "scheme_name", cur_instance->name, NULL); cur_instance->enabled = 1; } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_auth_scheme_module_instance_list - Error init module %s/%s", module->name, json_string_value(json_object_get(j_instance, "name"))); if (check_result_value(j_init, G_ERROR_PARAM)) { message = json_dumps(json_object_get(j_init, "error"), JSON_INDENT(2)); y_log_message(Y_LOG_LEVEL_DEBUG, message); o_free(message); } cur_instance->enabled = 0; } json_decref(j_init); } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_auth_scheme_module_instance_list - Error parsing parameters for module %s: '%s'", cur_instance->name, json_string_value(json_object_get(j_instance, "parameters"))); o_free(cur_instance->name); } json_decref(j_parameters); } else { cur_instance->enabled = 0; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_auth_scheme_module_instance_list - Error reallocating resources for user_auth_scheme_module_instance_list"); o_free(cur_instance->name); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_auth_scheme_module_instance_list - Error allocating resources for cur_instance"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_auth_scheme_module_instance_list - Error module %s not found", json_string_value(json_object_get(j_instance, "module"))); } } json_decref(j_result); pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_auth_scheme_module_instance_list - Error pthread_mutex_lock"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_auth_scheme_module_instance_list - Error executing j_query"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_user_auth_scheme_module_instance_list - Error allocating resources for config->user_auth_scheme_module_instance_list"); ret = G_ERROR_MEMORY; } return ret; } struct _user_auth_scheme_module_instance * get_user_auth_scheme_module_instance(struct config_elements * config, const char * name) { size_t i; struct _user_auth_scheme_module_instance * cur_instance; for (i=0; i<pointer_list_size(config->user_auth_scheme_module_instance_list); i++) { cur_instance = pointer_list_get_at(config->user_auth_scheme_module_instance_list, i); if (0 == o_strcmp(cur_instance->name, name)) { return cur_instance; } } return NULL; } struct _user_auth_scheme_module * get_user_auth_scheme_module_lib(struct config_elements * config, const char * name) { size_t i; struct _user_auth_scheme_module * module; for (i=0; i<pointer_list_size(config->user_auth_scheme_module_list); i++) { module = (struct _user_auth_scheme_module *)pointer_list_get_at(config->user_auth_scheme_module_list, i); if (module != NULL && 0 == o_strcmp(module->name, name)) { return module; } } return NULL; } void close_user_auth_scheme_module_instance_list(struct config_elements * config) { size_t i; if (!pthread_mutex_lock(&config->module_lock)) { for (i=0; i<pointer_list_size(config->user_auth_scheme_module_instance_list); i++) { struct _user_auth_scheme_module_instance * instance = (struct _user_auth_scheme_module_instance *)pointer_list_get_at(config->user_auth_scheme_module_instance_list, i); if (instance != NULL) { if (instance->enabled && instance->module->user_auth_scheme_module_close(config->config_m, instance->cls) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "close_user_auth_scheme_module_instance_list - Error user_auth_scheme_module_close for instance '%s'/'%s'", instance->module->name, instance->name); } o_free(instance->name); o_free(instance); } } pointer_list_clean(config->user_auth_scheme_module_instance_list); o_free(config->user_auth_scheme_module_instance_list); pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "close_user_auth_scheme_module_instance_list - Error pthread_mutex_lock"); } } void close_user_auth_scheme_module_list(struct config_elements * config) { size_t i; if (!pthread_mutex_lock(&config->module_lock)) { for (i=0; i<pointer_list_size(config->user_auth_scheme_module_list); i++) { struct _user_auth_scheme_module * module = (struct _user_auth_scheme_module *)pointer_list_get_at(config->user_auth_scheme_module_list, i); if (module != NULL) { if (module->user_auth_scheme_module_unload(config->config_m) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "close_user_auth_scheme_module_list - Error user_auth_scheme_module_unload for module '%s'", module->name); } #ifndef DEBUG if (dlclose(module->file_handle)) { y_log_message(Y_LOG_LEVEL_ERROR, "close_user_auth_scheme_module_list - Error dlclose for module '%s'", module->name); } #endif o_free(module->name); o_free(module->display_name); o_free(module->description); o_free(module); } } pointer_list_clean(config->user_auth_scheme_module_list); o_free(config->user_auth_scheme_module_list); pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "close_user_auth_scheme_module_list - Error pthread_mutex_lock"); } } static int load_client_module_file(struct config_elements * config, const char * file_path) { void * file_handle; struct _client_module * cur_client_module = NULL; int ret; json_t * j_parameters; file_handle = dlopen(file_path, RTLD_LAZY); if (file_handle != NULL) { cur_client_module = o_malloc(sizeof(struct _client_module)); if (cur_client_module != NULL) { cur_client_module->name = NULL; cur_client_module->file_handle = file_handle; cur_client_module->api_version = 0.0; *(void **) (&cur_client_module->client_module_load) = dlsym(file_handle, "client_module_load"); *(void **) (&cur_client_module->client_module_unload) = dlsym(file_handle, "client_module_unload"); *(void **) (&cur_client_module->client_module_init) = dlsym(file_handle, "client_module_init"); *(void **) (&cur_client_module->client_module_close) = dlsym(file_handle, "client_module_close"); *(void **) (&cur_client_module->client_module_count_total) = dlsym(file_handle, "client_module_count_total"); *(void **) (&cur_client_module->client_module_get_list) = dlsym(file_handle, "client_module_get_list"); *(void **) (&cur_client_module->client_module_get) = dlsym(file_handle, "client_module_get"); *(void **) (&cur_client_module->client_module_is_valid) = dlsym(file_handle, "client_module_is_valid"); *(void **) (&cur_client_module->client_module_add) = dlsym(file_handle, "client_module_add"); *(void **) (&cur_client_module->client_module_update) = dlsym(file_handle, "client_module_update"); *(void **) (&cur_client_module->client_module_delete) = dlsym(file_handle, "client_module_delete"); *(void **) (&cur_client_module->client_module_check_password) = dlsym(file_handle, "client_module_check_password"); if (cur_client_module->client_module_load != NULL && cur_client_module->client_module_unload != NULL && cur_client_module->client_module_init != NULL && cur_client_module->client_module_close != NULL && cur_client_module->client_module_count_total != NULL && cur_client_module->client_module_get_list != NULL && cur_client_module->client_module_get != NULL && cur_client_module->client_module_is_valid != NULL && cur_client_module->client_module_add != NULL && cur_client_module->client_module_update != NULL && cur_client_module->client_module_delete != NULL && cur_client_module->client_module_check_password != NULL) { j_parameters = cur_client_module->client_module_load(config->config_m); if (check_result_value(j_parameters, G_OK)) { cur_client_module->name = o_strdup(json_string_value(json_object_get(j_parameters, "name"))); cur_client_module->display_name = o_strdup(json_string_value(json_object_get(j_parameters, "display_name"))); cur_client_module->description = o_strdup(json_string_value(json_object_get(j_parameters, "description"))); cur_client_module->api_version = json_real_value(json_object_get(j_parameters, "api_version")); if (o_strlen(cur_client_module->name) && get_client_module_lib(config, cur_client_module->name) == NULL) { if (!pthread_mutex_lock(&config->module_lock)) { if (pointer_list_append(config->client_module_list, cur_client_module)) { y_log_message(Y_LOG_LEVEL_INFO, "Loading client module %s - %s", file_path, cur_client_module->name); ret = G_OK; } else { cur_client_module->client_module_unload(config->config_m); dlclose(file_handle); o_free(cur_client_module->name); o_free(cur_client_module->display_name); o_free(cur_client_module->description); o_free(cur_client_module); y_log_message(Y_LOG_LEVEL_ERROR, "load_client_module_file - Error reallocating resources for client_module_list"); ret = G_ERROR_MEMORY; } pthread_mutex_unlock(&config->module_lock); } else { cur_client_module->client_module_unload(config->config_m); dlclose(file_handle); o_free(cur_client_module->name); o_free(cur_client_module->display_name); o_free(cur_client_module->description); o_free(cur_client_module); y_log_message(Y_LOG_LEVEL_ERROR, "load_client_module_file - Error pthread_mutex_lock"); ret = G_ERROR_MEMORY; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_client_module_file - Client module with name '%s' already present or name empty", cur_client_module->name); cur_client_module->client_module_unload(config->config_m); dlclose(file_handle); o_free(cur_client_module->name); o_free(cur_client_module->display_name); o_free(cur_client_module->description); o_free(cur_client_module); ret = G_ERROR; } } else { dlclose(file_handle); o_free(cur_client_module); y_log_message(Y_LOG_LEVEL_ERROR, "load_client_module_file - Error client_module_load for module %s", file_path); ret = G_ERROR_MEMORY; } json_decref(j_parameters); } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_client_module_file - Error module %s has not all required functions", file_path); y_log_message(Y_LOG_LEVEL_ERROR, " - client_module_load: %s", (cur_client_module->client_module_load != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - client_module_unload: %s", (cur_client_module->client_module_unload != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - client_module_init: %s", (cur_client_module->client_module_init != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - client_module_close: %s", (cur_client_module->client_module_close != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - client_module_count_total: %s", (cur_client_module->client_module_count_total != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - client_module_get_list: %s", (cur_client_module->client_module_get_list != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - client_module_get: %s", (cur_client_module->client_module_get != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - client_module_is_valid: %s", (cur_client_module->client_module_is_valid != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - client_module_add: %s", (cur_client_module->client_module_add != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - client_module_update: %s", (cur_client_module->client_module_update != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - client_module_delete: %s", (cur_client_module->client_module_delete != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - client_module_check_password: %s", (cur_client_module->client_module_check_password != NULL?"found":"not found")); dlclose(file_handle); o_free(cur_client_module); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_client_module_file - Error allocating resources for cur_client_module"); dlclose(file_handle); ret = G_ERROR_MEMORY; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_client_module_file - Error opening module file %s, reason: %s", file_path, dlerror()); ret = G_ERROR; } return ret; } int init_client_module_list(struct config_elements * config) { int ret = G_OK, is_reg; DIR * modules_directory; struct dirent * in_file; char * file_path; struct stat u_stat; memset(&u_stat, 0, sizeof(struct stat)); config->client_module_list = o_malloc(sizeof(struct _pointer_list)); if (config->client_module_list != NULL) { pointer_list_init(config->client_module_list); // read module_path and load modules if (NULL == (modules_directory = opendir(config->client_module_path))) { y_log_message(Y_LOG_LEVEL_ERROR, "init_client_module_list - Error reading libraries folder %s", config->client_module_path); ret = G_ERROR; } else { while ((in_file = readdir(modules_directory))) { is_reg = 0; file_path = NULL; if (in_file->d_type == DT_REG) { file_path = msprintf("%s/%s", config->client_module_path, in_file->d_name); is_reg = 1; } else if (in_file->d_type == DT_UNKNOWN) { file_path = msprintf("%s/%s", config->client_module_path, in_file->d_name); if (!stat(file_path, &u_stat)) { if (S_ISREG(u_stat.st_mode)) { is_reg = 1; } } } if (is_reg) { if (load_client_module_file(config, file_path) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "init_client_module_list - Error opening module file %s", file_path); } } o_free(file_path); } closedir(modules_directory); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "init_client_module_list - Error allocating resources for config->client_module_list"); ret = G_ERROR_MEMORY; } return ret; } int load_client_module_instance_list(struct config_elements * config) { json_t * j_query, * j_result, * j_instance, * j_parameters, * j_init; int res, ret; size_t index, i; struct _client_module_instance * cur_instance; struct _client_module * module = NULL; config->client_module_instance_list = o_malloc(sizeof(struct _pointer_list)); if (config->client_module_instance_list != NULL) { pointer_list_init(config->client_module_instance_list); j_query = json_pack("{sss[ssssss]ss}", "table", GLEWLWYD_TABLE_CLIENT_MODULE_INSTANCE, "columns", "gcmi_module AS module", "gcmi_name AS name", "gcmi_order AS order_by", "gcmi_parameters AS parameters", "gcmi_readonly AS readonly", "gcmi_enabled AS enabled", "order_by", "gcmi_order"); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (!pthread_mutex_lock(&config->module_lock)) { ret = G_OK; json_array_foreach(j_result, index, j_instance) { module = NULL; for (i=0; i<pointer_list_size(config->client_module_list); i++) { module = pointer_list_get_at(config->client_module_list, i); if (0 == o_strcmp(module->name, json_string_value(json_object_get(j_instance, "module")))) { break; } else { module = NULL; } } if (module != NULL) { cur_instance = o_malloc(sizeof(struct _client_module_instance)); if (cur_instance != NULL) { cur_instance->cls = NULL; cur_instance->name = o_strdup(json_string_value(json_object_get(j_instance, "name"))); cur_instance->readonly = json_integer_value(json_object_get(j_instance, "readonly")); cur_instance->module = module; if (pointer_list_append(config->client_module_instance_list, cur_instance)) { if (json_integer_value(json_object_get(j_instance, "enabled"))) { j_parameters = json_loads(json_string_value(json_object_get(j_instance, "parameters")), JSON_DECODE_ANY, NULL); if (j_parameters != NULL) { j_init = module->client_module_init(config->config_m, cur_instance->readonly, j_parameters, &cur_instance->cls); if (check_result_value(j_init, G_OK)) { cur_instance->enabled = 1; } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_client_module_instance_list - Error init module %s/%s", module->name, json_string_value(json_object_get(j_instance, "name"))); cur_instance->enabled = 0; } json_decref(j_init); } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_client_module_instance_list - Error parsing module parameters %s/%s: '%s'", module->name, json_string_value(json_object_get(j_instance, "name")), json_string_value(json_object_get(j_instance, "parameters"))); cur_instance->enabled = 0; } json_decref(j_parameters); } else { cur_instance->enabled = 0; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_client_module_instance_list - Error reallocating resources for client_module_instance_list"); o_free(cur_instance->name); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_client_module_instance_list - Error allocating resources for cur_instance"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_client_module_instance_list - Error module %s not found", json_string_value(json_object_get(j_instance, "module"))); } } json_decref(j_result); pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_client_module_instance_list - Error pthread_mutex_lock"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_client_module_instance_list - Error executing j_query"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_client_module_instance_list - Error allocating resources for config->client_module_instance_list"); ret = G_ERROR; } return ret; } struct _client_module_instance * get_client_module_instance(struct config_elements * config, const char * name) { size_t i; struct _client_module_instance * cur_instance; for (i=0; i<pointer_list_size(config->client_module_instance_list); i++) { cur_instance = pointer_list_get_at(config->client_module_instance_list, i); if (0 == o_strcmp(cur_instance->name, name)) { return cur_instance; } } return NULL; } struct _client_module * get_client_module_lib(struct config_elements * config, const char * name) { size_t i; struct _client_module * module; for (i=0; i<pointer_list_size(config->client_module_list); i++) { module = (struct _client_module *)pointer_list_get_at(config->client_module_list, i); if (module != NULL && 0 == o_strcmp(module->name, name)) { return module; } } return NULL; } void close_client_module_instance_list(struct config_elements * config) { size_t i; if (!pthread_mutex_lock(&config->module_lock)) { for (i=0; i<pointer_list_size(config->client_module_instance_list); i++) { struct _client_module_instance * instance = (struct _client_module_instance *)pointer_list_get_at(config->client_module_instance_list, i); if (instance != NULL) { if (instance->enabled && instance->module->client_module_close(config->config_m, instance->cls) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "close_client_module_instance_list - Error client_module_close for instance '%s'/'%s'", instance->module->name, instance->name); } o_free(instance->name); o_free(instance); } } pointer_list_clean(config->client_module_instance_list); o_free(config->client_module_instance_list); pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "close_client_module_instance_list - Error pthread_mutex_lock"); } } void close_client_module_list(struct config_elements * config) { size_t i; if (!pthread_mutex_lock(&config->module_lock)) { for (i=0; i<pointer_list_size(config->client_module_list); i++) { struct _client_module * module = (struct _client_module *)pointer_list_get_at(config->client_module_list, i); if (module != NULL) { if (module->client_module_unload(config->config_m) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "close_client_module_list - Error client_module_unload for module '%s'", module->name); } /* * dlclose() makes valgrind not useful when it comes to libraries * they say it's not relevant to use it anyway * I'll let it here until I'm sure */ #ifndef DEBUG if (dlclose(module->file_handle)) { y_log_message(Y_LOG_LEVEL_ERROR, "close_client_module_list - Error dlclose for module '%s'", module->name); } #endif o_free(module->name); o_free(module->display_name); o_free(module->description); o_free(module); } } pointer_list_clean(config->client_module_list); o_free(config->client_module_list); pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "close_client_module_list - Error close_client_module_list"); } } static int load_plugin_module_file(struct config_elements * config, const char * file_path) { void * file_handle; struct _plugin_module * cur_plugin_module = NULL; int ret; json_t * j_result; file_handle = dlopen(file_path, RTLD_LAZY); if (file_handle != NULL) { cur_plugin_module = o_malloc(sizeof(struct _plugin_module)); if (cur_plugin_module != NULL) { cur_plugin_module->name = NULL; cur_plugin_module->file_handle = file_handle; cur_plugin_module->api_version = 0.0; *(void **) (&cur_plugin_module->plugin_module_load) = dlsym(file_handle, "plugin_module_load"); *(void **) (&cur_plugin_module->plugin_module_unload) = dlsym(file_handle, "plugin_module_unload"); *(void **) (&cur_plugin_module->plugin_module_init) = dlsym(file_handle, "plugin_module_init"); *(void **) (&cur_plugin_module->plugin_module_close) = dlsym(file_handle, "plugin_module_close"); *(void **) (&cur_plugin_module->plugin_user_revoke) = dlsym(file_handle, "plugin_user_revoke"); if (cur_plugin_module->plugin_module_load != NULL && cur_plugin_module->plugin_module_unload != NULL && cur_plugin_module->plugin_module_init != NULL && cur_plugin_module->plugin_module_close != NULL && cur_plugin_module->plugin_user_revoke != NULL) { j_result = cur_plugin_module->plugin_module_load(config->config_p); if (check_result_value(j_result, G_OK)) { cur_plugin_module->name = o_strdup(json_string_value(json_object_get(j_result, "name"))); cur_plugin_module->display_name = o_strdup(json_string_value(json_object_get(j_result, "display_name"))); cur_plugin_module->description = o_strdup(json_string_value(json_object_get(j_result, "description"))); cur_plugin_module->api_version = json_real_value(json_object_get(j_result, "api_version")); if (o_strlen(cur_plugin_module->name) && get_plugin_module_lib(config, cur_plugin_module->name) == NULL) { if (!pthread_mutex_lock(&config->module_lock)) { if (pointer_list_append(config->plugin_module_list, cur_plugin_module)) { y_log_message(Y_LOG_LEVEL_INFO, "Loading plugin module %s - %s", file_path, cur_plugin_module->name); ret = G_OK; } else { cur_plugin_module->plugin_module_unload(config->config_p); dlclose(file_handle); o_free(cur_plugin_module->name); o_free(cur_plugin_module->display_name); o_free(cur_plugin_module->description); o_free(cur_plugin_module); y_log_message(Y_LOG_LEVEL_ERROR, "load_plugin_module_file - Error reallocating resources for client_module_list"); ret = G_ERROR_MEMORY; } pthread_mutex_unlock(&config->module_lock); } else { cur_plugin_module->plugin_module_unload(config->config_p); dlclose(file_handle); o_free(cur_plugin_module->name); o_free(cur_plugin_module->display_name); o_free(cur_plugin_module->description); o_free(cur_plugin_module); y_log_message(Y_LOG_LEVEL_ERROR, "load_plugin_module_file - Error pthread_mutex_lock"); ret = G_ERROR_MEMORY; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_plugin_module_file - Plugin module with name '%s' already present or name empty", cur_plugin_module->name); cur_plugin_module->plugin_module_unload(config->config_p); dlclose(file_handle); o_free(cur_plugin_module->name); o_free(cur_plugin_module->display_name); o_free(cur_plugin_module->description); o_free(cur_plugin_module); ret = G_ERROR; } } else { dlclose(file_handle); o_free(cur_plugin_module->name); o_free(cur_plugin_module->display_name); o_free(cur_plugin_module->description); o_free(cur_plugin_module); y_log_message(Y_LOG_LEVEL_ERROR, "load_plugin_module_file - Error client_module_init for module %s", file_path); ret = G_ERROR_MEMORY; } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_plugin_module_file - Error module %s has not all required functions", file_path); y_log_message(Y_LOG_LEVEL_ERROR, " - plugin_module_load: %s", (cur_plugin_module->plugin_module_load != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - plugin_module_unload: %s", (cur_plugin_module->plugin_module_unload != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - plugin_module_init: %s", (cur_plugin_module->plugin_module_init != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - plugin_module_close: %s", (cur_plugin_module->plugin_module_close != NULL?"found":"not found")); y_log_message(Y_LOG_LEVEL_ERROR, " - plugin_user_revoke: %s", (cur_plugin_module->plugin_user_revoke != NULL?"found":"not found")); dlclose(file_handle); o_free(cur_plugin_module); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_plugin_module_file - Error allocating resources for cur_client_module"); dlclose(file_handle); ret = G_ERROR_MEMORY; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_plugin_module_file - Error opening module file %s, reason: %s", file_path, dlerror()); ret = G_ERROR; } return ret; } int init_plugin_module_list(struct config_elements * config) { int ret = G_OK, is_reg; DIR * modules_directory; struct dirent * in_file; char * file_path; struct stat u_stat; memset(&u_stat, 0, sizeof(struct stat)); config->plugin_module_list = o_malloc(sizeof(struct _pointer_list)); if (config->plugin_module_list != NULL) { pointer_list_init(config->plugin_module_list); // read module_path and load modules if (NULL == (modules_directory = opendir(config->plugin_module_path))) { y_log_message(Y_LOG_LEVEL_ERROR, "init_plugin_module_list - Error reading libraries folder %s", config->plugin_module_path); ret = G_ERROR; } else { while ((in_file = readdir(modules_directory))) { is_reg = 0; file_path = NULL; if (in_file->d_type == DT_REG) { file_path = msprintf("%s/%s", config->plugin_module_path, in_file->d_name); is_reg = 1; } else if (in_file->d_type == DT_UNKNOWN) { file_path = msprintf("%s/%s", config->plugin_module_path, in_file->d_name); if (!stat(file_path, &u_stat)) { if (S_ISREG(u_stat.st_mode)) { is_reg = 1; } } } if (is_reg) { if (load_plugin_module_file(config, file_path) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "init_client_module_list - Error opening module file %s", file_path); } } o_free(file_path); } closedir(modules_directory); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "init_plugin_module_list - Error allocating resources for config->client_module_list"); ret = G_ERROR_MEMORY; } return ret; } int load_plugin_module_instance_list(struct config_elements * config) { json_t * j_query, * j_result, * j_instance, * j_parameters, * j_init; int res, ret; size_t index, i; struct _plugin_module_instance * cur_instance; struct _plugin_module * module = NULL; char * message; config->plugin_module_instance_list = o_malloc(sizeof(struct _pointer_list)); if (config->plugin_module_instance_list != NULL) { pointer_list_init(config->plugin_module_instance_list); j_query = json_pack("{sss[ssss]}", "table", GLEWLWYD_TABLE_PLUGIN_MODULE_INSTANCE, "columns", "gpmi_module AS module", "gpmi_name AS name", "gpmi_parameters AS parameters", "gpmi_enabled AS enabled"); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (!pthread_mutex_lock(&config->module_lock)) { ret = G_OK; json_array_foreach(j_result, index, j_instance) { module = NULL; for (i=0; i<pointer_list_size(config->plugin_module_list); i++) { module = pointer_list_get_at(config->plugin_module_list, i); if (0 == o_strcmp(module->name, json_string_value(json_object_get(j_instance, "module")))) { break; } else { module = NULL; } } if (module != NULL) { cur_instance = o_malloc(sizeof(struct _plugin_module_instance)); if (cur_instance != NULL) { cur_instance->cls = NULL; cur_instance->name = o_strdup(json_string_value(json_object_get(j_instance, "name"))); cur_instance->module = module; if (pointer_list_append(config->plugin_module_instance_list, cur_instance)) { if (json_integer_value(json_object_get(j_instance, "enabled"))) { j_parameters = json_loads(json_string_value(json_object_get(j_instance, "parameters")), JSON_DECODE_ANY, NULL); if (j_parameters != NULL) { j_init = module->plugin_module_init(config->config_p, cur_instance->name, j_parameters, &cur_instance->cls); if (check_result_value(j_init, G_OK)) { cur_instance->enabled = 1; } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_plugin_module_instance_list - Error init module %s/%s", module->name, json_string_value(json_object_get(j_instance, "name"))); if (check_result_value(j_init, G_ERROR_PARAM)) { message = json_dumps(json_object_get(j_init, "error"), JSON_INDENT(2)); y_log_message(Y_LOG_LEVEL_DEBUG, message); o_free(message); } cur_instance->enabled = 0; } json_decref(j_init); } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_plugin_module_instance_list - Error parsing parameters for module %s/%s: '%s'", module->name, json_string_value(json_object_get(j_instance, "name")), json_string_value(json_object_get(j_instance, "parameters"))); cur_instance->enabled = 0; } json_decref(j_parameters); } else { cur_instance->enabled = 0; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_plugin_module_instance_list - Error reallocating resources for client_module_instance_list"); o_free(cur_instance->name); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_plugin_module_instance_list - Error allocating resources for cur_instance"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_plugin_module_instance_list - Error module %s not found", json_string_value(json_object_get(j_instance, "module"))); } } json_decref(j_result); pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_plugin_module_instance_list - Error pthread_mutex_lock"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_plugin_module_instance_list - Error executing j_query"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "load_plugin_module_instance_list - Error allocating resources for config->client_module_instance_list"); ret = G_ERROR; } return ret; } struct _plugin_module_instance * get_plugin_module_instance(struct config_elements * config, const char * name) { size_t i; struct _plugin_module_instance * cur_instance; for (i=0; i<pointer_list_size(config->plugin_module_instance_list); i++) { cur_instance = (struct _plugin_module_instance *)pointer_list_get_at(config->plugin_module_instance_list, i); if (cur_instance != NULL && 0 == o_strcmp(cur_instance->name, name)) { return cur_instance; } } return NULL; } struct _plugin_module * get_plugin_module_lib(struct config_elements * config, const char * name) { size_t i; struct _plugin_module * module; for (i=0; i<pointer_list_size(config->plugin_module_list); i++) { module = (struct _plugin_module *)pointer_list_get_at(config->plugin_module_list, i); if (module != NULL && 0 == o_strcmp(module->name, name)) { return module; } } return NULL; } void close_plugin_module_instance_list(struct config_elements * config) { size_t i; if (!pthread_mutex_lock(&config->module_lock)) { for (i=0; i<pointer_list_size(config->plugin_module_instance_list); i++) { struct _plugin_module_instance * instance = (struct _plugin_module_instance *)pointer_list_get_at(config->plugin_module_instance_list, i); if (instance != NULL) { if (instance->enabled && instance->module->plugin_module_close(config->config_p, instance->name, instance->cls) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "close_plugin_module_instance_list - Error plugin_module_close for instance '%s'/'%s'", instance->module->name, instance->name); } o_free(instance->name); o_free(instance); } } pointer_list_clean(config->plugin_module_instance_list); o_free(config->plugin_module_instance_list); pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "close_plugin_module_instance_list - Error pthread_mutex_lock"); } } void close_plugin_module_list(struct config_elements * config) { size_t i; if (!pthread_mutex_lock(&config->module_lock)) { for (i=0; i<pointer_list_size(config->plugin_module_list); i++) { struct _plugin_module * module = (struct _plugin_module *)pointer_list_get_at(config->plugin_module_list, i); if (module != NULL) { if (module->plugin_module_unload(config->config_p) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "close_plugin_module_list - Error plugin_module_unload for module '%s'", module->name); } #ifndef DEBUG if (dlclose(module->file_handle)) { y_log_message(Y_LOG_LEVEL_ERROR, "close_plugin_module_list - Error dlclose for module '%s'", module->name); } #endif o_free(module->name); o_free(module->display_name); o_free(module->description); o_free(module); } } pointer_list_clean(config->plugin_module_list); o_free(config->plugin_module_list); pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "close_plugin_module_list - Error pthread_mutex_lock"); } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/glewlwyd.h�����������������������������������������������������������������������0000664�0000000�0000000�00000107177�14156463140�0016175�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * Declarations for constants and prototypes * * Copyright 2016-2021 Nicolas Mora <mail@babelouest.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see <http://www.gnu.org/licenses/>. * */ #ifndef __GLEWLWYD_H_ #define __GLEWLWYD_H_ #define _GLEWLWYD_VERSION_ "2.6.1" #include <jansson.h> #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #ifndef __USE_GNU #define __USE_GNU #endif #include <stdio.h> /** Angharad libraries **/ #include <ulfius.h> #include <yder.h> #include <hoel.h> #include "glewlwyd-common.h" #if MHD_VERSION < 0x00093800 #error Libmicrohttpd version 0.9.38 minimum is required, you can download it at http://ftp.gnu.org/gnu/libmicrohttpd/ #endif #define GLEWLWYD_LOG_NAME "Glewlwyd" #define _GLEWLWYD_USER_MODULE_VERSION 2.5 // Configuration default values #define GLEWLWYD_DEFAULT_PORT 4593 #define GLEWLWYD_DEFAULT_METRICS_PORT 4594 #define GLEWLWYD_DEFAULT_API_PREFIX "api" #define GLEWLWYD_DEFAULT_ALLOW_ORIGIN "*" #define GLEWLWYD_DEFAULT_ADMIN_SCOPE "g_admin" #define GLEWLWYD_DEFAULT_PROFILE_SCOPE "g_profile" #define GLEWLWYD_DEFAULT_HASH_ALGORITHM digest_SHA256 #define GLEWLWYD_DEFAULT_LOGIN_URL "login.html" #define GLEWLWYD_DEFAULT_SESSION_KEY "GLEWLWYD2_SESSION_ID" #define GLEWLWYD_DEFAULT_SESSION_EXPIRATION_COOKIE 5256000 // 10 years #define GLEWLWYD_DEFAULT_SESSION_EXPIRATION_PASSWORD 40320 // 4 weeks #define GLEWLWYD_RESET_PASSWORD_DEFAULT_SESSION_EXPIRATION 2592000 // 30 days #define GLEWLWYD_SESSION_ID_LENGTH 128 #define GLEWLWYD_API_KEY_HEADER_KEY "Authorization" #define GLEWLWYD_API_KEY_HEADER_PREFIX "token " #define GLEWLWYD_API_KEY_LENGTH 32 #define GLEWLWYD_RUNNING 0 #define GLEWLWYD_STOP 1 #define GLEWLWYD_ERROR 2 // Data tables #define GLEWLWYD_TABLE_USER_MODULE_INSTANCE "g_user_module_instance" #define GLEWLWYD_TABLE_USER_MIDDLEWARE_MODULE_INSTANCE "g_user_middleware_module_instance" #define GLEWLWYD_TABLE_USER_AUTH_SCHEME_MODULE_INSTANCE "g_user_auth_scheme_module_instance" #define GLEWLWYD_TABLE_CLIENT_MODULE_INSTANCE "g_client_module_instance" #define GLEWLWYD_TABLE_PLUGIN_MODULE_INSTANCE "g_plugin_module_instance" #define GLEWLWYD_TABLE_USER_SESSION "g_user_session" #define GLEWLWYD_TABLE_USER_SESSION_SCHEME "g_user_session_scheme" #define GLEWLWYD_TABLE_SCOPE "g_scope" #define GLEWLWYD_TABLE_SCOPE_GROUP "g_scope_group" #define GLEWLWYD_TABLE_SCOPE_GROUP_AUTH_SCHEME_MODULE_INSTANCE "g_scope_group_auth_scheme_module_instance" #define GLEWLWYD_TABLE_CLIENT_USER_SCOPE "g_client_user_scope" #define GLEWLWYD_TABLE_API_KEY "g_api_key" // Module management #define GLEWLWYD_MODULE_ACTION_STOP 0 #define GLEWLWYD_MODULE_ACTION_START 1 // Environment variables names #define GLEWLWYD_ENV_PORT "GLWD_PORT" #define GLEWLWYD_ENV_BIND_ADDRESS "GLWD_BIND_ADDRESS" #define GLEWLWYD_ENV_API_PREFIX "GLWD_API_PREFIX" #define GLEWLWYD_ENV_EXTERNAL_URL "GLWD_EXTERNAL_URL" #define GLEWLWYD_ENV_LOGIN_URL "GLWD_LOGIN_URL" #define GLEWLWYD_ENV_PROFILE_DELETE "GLWD_PROFILE_DELETE" #define GLEWLWYD_ENV_STATIC_FILES_PATH "GLWD_STATIC_FILES_PATH" #define GLEWLWYD_ENV_STATIC_FILES_MIME_TYPES "GLWD_STATIC_FILES_MIME_TYPES" #define GLEWLWYD_ENV_ALLOW_ORIGIN "GLWD_ALLOW_ORIGIN" #define GLEWLWYD_ENV_LOG_MODE "GLWD_LOG_MODE" #define GLEWLWYD_ENV_LOG_LEVEL "GLWD_LOG_LEVEL" #define GLEWLWYD_ENV_LOG_FILE "GLWD_LOG_FILE" #define GLEWLWYD_ENV_COOKIE_DOMAIN "GLWD_COOKIE_DOMAIN" #define GLEWLWYD_ENV_COOKIE_SECURE "GLWD_COOKIE_SECURE" #define GLEWLWYD_ENV_ADD_X_FRAME_DENY "GLWD_ADD_X_FRAME_DENY" #define GLEWLWYD_ENV_SESSION_EXPIRATION "GLWD_SESSION_EXPIRATION" #define GLEWLWYD_ENV_SESSION_KEY "GLWD_SESSION_KEY" #define GLEWLWYD_ENV_ADMIN_SCOPE "GLWD_ADMIN_SCOPE" #define GLEWLWYD_ENV_PROFILE_SCOPE "GLWD_PROFILE_SCOPE" #define GLEWLWYD_ENV_USER_MODULE_PATH "GLWD_USER_MODULE_PATH" #define GLEWLWYD_ENV_USER_MIDDLEWARE_MODULE_PATH "GLWD_USER_MIDDLEWARE_MODULE_PATH" #define GLEWLWYD_ENV_CLIENT_MODULE_PATH "GLWD_CLIENT_MODULE_PATH" #define GLEWLWYD_ENV_AUTH_SCHEME_MODULE_PATH "GLWD_AUTH_SCHEME_MODULE_PATH" #define GLEWLWYD_ENV_PLUGIN_MODULE_PATH "GLWD_PLUGIN_MODULE_PATH" #define GLEWLWYD_ENV_USE_SECURE_CONNECTION "GLWD_USE_SECURE_CONNECTION" #define GLEWLWYD_ENV_SECURE_CONNECTION_KEY_FILE "GLWD_SECURE_CONNECTION_KEY_FILE" #define GLEWLWYD_ENV_SECURE_CONNECTION_PEM_FILE "GLWD_SECURE_CONNECTION_PEM_FILE" #define GLEWLWYD_ENV_SECURE_CONNECTION_CA_FILE "GLWD_SECURE_CONNECTION_CA_FILE" #define GLEWLWYD_ENV_HASH_ALGORITHM "GLWD_HASH_ALGORITHM" #define GLEWLWYD_ENV_DATABASE_TYPE "GLWD_DATABASE_TYPE" #define GLEWLWYD_ENV_DATABASE_MARIADB_HOST "GLWD_DATABASE_MARIADB_HOST" #define GLEWLWYD_ENV_DATABASE_MARIADB_USER "GLWD_DATABASE_MARIADB_USER" #define GLEWLWYD_ENV_DATABASE_MARIADB_PASSWORD "GLWD_DATABASE_MARIADB_PASSWORD" #define GLEWLWYD_ENV_DATABASE_MARIADB_DBNAME "GLWD_DATABASE_MARIADB_DBNAME" #define GLEWLWYD_ENV_DATABASE_MARIADB_PORT "GLWD_DATABASE_MARIADB_PORT" #define GLEWLWYD_ENV_DATABASE_SQLITE3_PATH "GLWD_DATABASE_SQLITE3_PATH" #define GLEWLWYD_ENV_DATABASE_POSTGRE_CONNINFO "GLWD_DATABASE_POSTGRE_CONNINFO" #define GLEWLWYD_ENV_METRICS "GLWD_METRICS" #define GLEWLWYD_ENV_METRICS_PORT "GLWD_METRICS_PORT" #define GLEWLWYD_ENV_METRICS_ADMIN "GLWD_METRICS_ADMIN" #define GLEWLWYD_ENV_METRICS_BIND_ADDRESS "GLWD_METRICS_BIND_ADDRESS" // Main functions and misc functions int build_config_from_env(struct config_elements * config); int build_config_from_file(struct config_elements * config); int build_config_from_args(int argc, char ** argv, struct config_elements * config, int * use_config_file, int * use_config_env); int check_config(struct config_elements * config); void* signal_thread(void *arg); void exit_server(struct config_elements ** config, int exit_value); void print_help(FILE * output); char * get_file_content(const char * file_path); int load_user_module_instance_list(struct config_elements * config); int init_user_module_list(struct config_elements * config); int load_user_middleware_module_instance_list(struct config_elements * config); int init_user_middleware_module_list(struct config_elements * config); int load_user_auth_scheme_module_instance_list(struct config_elements * config); int init_user_auth_scheme_module_list(struct config_elements * config); int init_client_module_list(struct config_elements * config); int load_client_module_instance_list(struct config_elements * config); int init_plugin_module_list(struct config_elements * config); int load_plugin_module_instance_list(struct config_elements * config); struct _client_module_instance * get_client_module_instance(struct config_elements * config, const char * name); struct _client_module * get_client_module_lib(struct config_elements * config, const char * name); struct _user_module_instance * get_user_module_instance(struct config_elements * config, const char * name); struct _user_module * get_user_module_lib(struct config_elements * config, const char * name); struct _user_middleware_module_instance * get_user_middleware_module_instance(struct config_elements * config, const char * name); struct _user_middleware_module * get_user_middleware_module_lib(struct config_elements * config, const char * name); struct _user_auth_scheme_module_instance * get_user_auth_scheme_module_instance(struct config_elements * config, const char * name); struct _user_auth_scheme_module * get_user_auth_scheme_module_lib(struct config_elements * config, const char * name); struct _plugin_module_instance * get_plugin_module_instance(struct config_elements * config, const char * name); struct _plugin_module * get_plugin_module_lib(struct config_elements * config, const char * name); // Modules generic functions int module_parameters_check(const char * module_parameters); int module_instance_parameters_check(const char * module_parameters, const char * instance_parameters); // Validate user login/password credentials json_t * auth_check_user_credentials(struct config_elements * config, const char * username, const char * password); json_t * auth_check_user_scheme(struct config_elements * config, const char * scheme_type, const char * scheme_name, const char * username, json_t * scheme_parameters, const struct _u_request * request); json_t * auth_check_identify_scheme(struct config_elements * config, const char * scheme_type, const char * scheme_name, json_t * j_scheme_value, const struct _u_request * request); json_t * auth_register_user_scheme(struct config_elements * config, const char * scheme_type, const char * scheme_name, const char * username, int delegate, json_t * j_register_parameters, const struct _u_request * request); json_t * auth_register_get_user_scheme(struct config_elements * config, const char * scheme_type, const char * scheme_name, const char * username, const struct _u_request * request); json_t * auth_trigger_user_scheme(struct config_elements * config, const char * scheme_type, const char * scheme_name, const char * username, json_t * register_parameters, const struct _u_request * request); json_t * auth_trigger_identify_scheme(struct config_elements * config, const char * scheme_type, const char * scheme_name, json_t * j_trigger_parameters, const struct _u_request * request); // Session int user_session_update(struct config_elements * config, const char * session_uid, const char * user_agent, const char * issued_for, const char * username, const char * scheme_name, int update_login); json_t * get_session_for_username(struct config_elements * config, const char * session_uid, const char * username); json_t * get_current_user_for_session(struct config_elements * config, const char * session_uid); json_t * get_users_for_session(struct config_elements * config, const char * session_uid); int user_session_delete(struct config_elements * config, const char * session_uid, const char * username); char * get_session_id(struct config_elements * config, const struct _u_request * request); char * generate_session_id(); json_t * get_user_session_list(struct config_elements * config, const char * username, const char * pattern, size_t offset, size_t limit, const char * sort); int delete_user_session_from_hash(struct config_elements * config, const char * username, const char * session_hash); // Profile json_t * user_set_profile(struct config_elements * config, const char * username, json_t * j_profile); int user_delete_profile(struct config_elements * config, const char * username); json_t * user_get_profile(struct config_elements * config, const char * username); int user_update_password(struct config_elements * config, const char * username, const char * old_password, const char ** new_passwords, size_t new_passwords_len); int user_set_password(struct config_elements * config, const char * username, const char ** new_passwords, size_t new_passwords_len); json_t * get_scheme_list_for_user(struct config_elements * config, const char * username); // User int user_has_scope(json_t * j_user, const char * scope); int user_has_scheme(struct config_elements * config, const char * username, const char * scheme_name); // Client json_t * auth_check_client_credentials(struct config_elements * config, const char * client_id, const char * password); // Scope json_t * get_auth_scheme_list_from_scope(struct config_elements * config, const char * scope); json_t * get_auth_scheme_list_from_scope_list(struct config_elements * config, const char * scope_list); json_t * get_validated_auth_scheme_list_from_scope_list(struct config_elements * config, const char * scope_list, const char * session_uid); int is_scope_list_valid_for_session(struct config_elements * config, const char * scope_list, const char * session_uid); json_t * get_client_user_scope_grant(struct config_elements * config, const char * client_id, const char * username, const char * scope_list); json_t * get_granted_scopes_for_client(struct config_elements * config, json_t * j_user, const char * client_id, const char * scope_list); json_t * get_client_grant_list(struct config_elements * config, const char * username, size_t offset, size_t limit); int set_granted_scopes_for_client(struct config_elements * config, json_t * j_user, const char * client_id, const char * scope_list); json_t * get_scope_list_allowed_for_session(struct config_elements * config, const char * scope_list, const char * session_uid); // Module types json_t * get_module_type_list(struct config_elements * config); // User module functions json_t * get_user_module_list(struct config_elements * config); json_t * get_user_module(struct config_elements * config, const char * name); json_t * is_user_module_valid(struct config_elements * config, json_t * j_module, int add); json_t * add_user_module(struct config_elements * config, json_t * j_module); int set_user_module(struct config_elements * config, const char * name, json_t * j_module); int delete_user_module(struct config_elements * config, const char * name); json_t * manage_user_module(struct config_elements * config, const char * name, int action); void close_user_module_instance_list(struct config_elements * config); void close_user_module_list(struct config_elements * config); // User middleware module functions json_t * get_user_middleware_module_list(struct config_elements * config); json_t * get_user_middleware_module(struct config_elements * config, const char * name); json_t * is_user_middleware_module_valid(struct config_elements * config, json_t * j_module, int add); json_t * add_user_middleware_module(struct config_elements * config, json_t * j_module); int set_user_middleware_module(struct config_elements * config, const char * name, json_t * j_module); int delete_user_middleware_module(struct config_elements * config, const char * name); json_t * manage_user_middleware_module(struct config_elements * config, const char * name, int action); void close_user_middleware_module_instance_list(struct config_elements * config); void close_user_middleware_module_list(struct config_elements * config); // User auth scheme module functions json_t * get_user_auth_scheme_module_list(struct config_elements * config); json_t * get_user_auth_scheme_module(struct config_elements * config, const char * name); json_t * is_user_auth_scheme_module_valid(struct config_elements * config, json_t * j_module, int add); json_t * add_user_auth_scheme_module(struct config_elements * config, json_t * j_module); int set_user_auth_scheme_module(struct config_elements * config, const char * name, json_t * j_module); int delete_user_auth_scheme_module(struct config_elements * config, const char * name); json_t * manage_user_auth_scheme_module(struct config_elements * config, const char * name, int action); void close_user_auth_scheme_module_instance_list(struct config_elements * config); void close_user_auth_scheme_module_list(struct config_elements * config); // Client module functions json_t * get_client_module_list(struct config_elements * config); json_t * get_client_module(struct config_elements * config, const char * name); json_t * is_client_module_valid(struct config_elements * config, json_t * j_module, int add); json_t * add_client_module(struct config_elements * config, json_t * j_module); int set_client_module(struct config_elements * config, const char * name, json_t * j_module); int delete_client_module(struct config_elements * config, const char * name); json_t * manage_client_module(struct config_elements * config, const char * name, int action); void close_client_module_instance_list(struct config_elements * config); void close_client_module_list(struct config_elements * config); // Plugin module functions json_t * get_plugin_module_list_for_user(struct config_elements * config); json_t * get_plugin_module_list(struct config_elements * config); json_t * get_plugin_module(struct config_elements * config, const char * name); json_t * is_plugin_module_valid(struct config_elements * config, json_t * j_module, int add); json_t * add_plugin_module(struct config_elements * config, json_t * j_module); int set_plugin_module(struct config_elements * config, const char * name, json_t * j_module); int delete_plugin_module(struct config_elements * config, const char * name); json_t * manage_plugin_module(struct config_elements * config, const char * name, int action); void close_plugin_module_instance_list(struct config_elements * config); void close_plugin_module_list(struct config_elements * config); // Plugin functions int glewlwyd_callback_add_plugin_endpoint(struct config_plugin * config, const char * method, const char * name, const char * url, unsigned int priority, int (* callback)(const struct _u_request * request, struct _u_response * response, void * user_data), void * user_data); int glewlwyd_callback_remove_plugin_endpoint(struct config_plugin * config, const char * method, const char * name, const char * url); json_t * glewlwyd_callback_check_session_valid(struct config_plugin * config, const struct _u_request * request, const char * scope_list); json_t * glewlwyd_callback_check_user_valid(struct config_plugin * config, const char * username, const char * password, const char * scope_list); json_t * glewlwyd_callback_check_client_valid(struct config_plugin * config, const char * client_id, const char * password); json_t * glewlwyd_callback_get_client_granted_scopes(struct config_plugin * config, const char * client_id, const char * username, const char * scope_list); int glewlwyd_callback_trigger_session_used(struct config_plugin * config, const struct _u_request * request, const char * scope_list); time_t glewlwyd_callback_get_session_age(struct config_plugin * config, const struct _u_request * request, const char * scope_list); char * glewlwyd_callback_get_login_url(struct config_plugin * config, const char * client_id, const char * scope_list, const char * callback_url, struct _u_map * additional_parameters); char * glewlwyd_callback_get_plugin_external_url(struct config_plugin * config, const char * name); char * glewlwyd_callback_generate_hash(struct config_plugin * config, const char * data); json_t * glewlwyd_plugin_callback_get_user_list(struct config_plugin * config, const char * pattern, size_t offset, size_t limit); json_t * glewlwyd_plugin_callback_get_user(struct config_plugin * config, const char * username); json_t * glewlwyd_plugin_callback_get_user_profile(struct config_plugin * config, const char * username); json_t * glewlwyd_plugin_callback_is_user_valid(struct config_plugin * config, const char * username, json_t * j_user, int add); int glewlwyd_plugin_callback_add_user(struct config_plugin * config, json_t * j_user); int glewlwyd_plugin_callback_set_user(struct config_plugin * config, const char * username, json_t * j_user); int glewlwyd_plugin_callback_user_update_password(struct config_plugin * config, const char * username, const char * password); int glewlwyd_plugin_callback_delete_user(struct config_plugin * config, const char * username); json_t * glewlwyd_plugin_callback_get_client_list(struct config_plugin * config, const char * pattern, size_t offset, size_t limit); json_t * glewlwyd_plugin_callback_get_client(struct config_plugin * config, const char * client_id); json_t * glewlwyd_plugin_callback_is_client_valid(struct config_plugin * config, const char * client_id, json_t * j_client, int add); int glewlwyd_plugin_callback_add_client(struct config_plugin * config, json_t * j_client); int glewlwyd_plugin_callback_set_client(struct config_plugin * config, const char * client_id, json_t * j_client); int glewlwyd_plugin_callback_delete_client(struct config_plugin * config, const char * client_id); json_t * glewlwyd_plugin_callback_get_scheme_module(struct config_plugin * config, const char * mod_name); json_t * glewlwyd_plugin_callback_get_scheme_list(struct config_plugin * config, const char * username); json_t * glewlwyd_plugin_callback_scheme_register(struct config_plugin * config, const char * mod_name, const struct _u_request * http_request, const char * username, json_t * j_scheme_data); json_t * glewlwyd_plugin_callback_scheme_register_get(struct config_plugin * config, const char * mod_name, const struct _u_request * http_request, const char * username); int glewlwyd_plugin_callback_scheme_can_use(struct config_plugin * config, const char * mod_name, const char * username); int glewlwyd_plugin_callback_scheme_deregister(struct config_plugin * config, const char * mod_name, const char * username); int glewlwyd_plugin_callback_metrics_add_metric(struct config_plugin * config, const char * name, const char * help); int glewlwyd_plugin_callback_metrics_increment_counter(struct config_plugin * config, const char * name, size_t inc, ...); // User CRUD functions json_t * get_user_list(struct config_elements * config, const char * pattern, size_t offset, size_t limit, const char * source); json_t * get_user(struct config_elements * config, const char * username, const char * source); json_t * get_user_profile(struct config_elements * config, const char * username, const char * source); json_t * is_user_valid(struct config_elements * config, const char * username, json_t * j_user, int add, const char * source); int add_user(struct config_elements * config, json_t * j_user, const char * source); int set_user(struct config_elements * config, const char * username, json_t * j_user, const char * source); int delete_user(struct config_elements * config, const char * username, const char * source); json_t * glewlwyd_module_callback_get_user(struct config_module * config, const char * username); int glewlwyd_module_callback_set_user(struct config_module * config, const char * username, json_t * j_user); int glewlwyd_module_callback_check_user_password(struct config_module * config, const char * username, const char * password); json_t * glewlwyd_module_callback_check_user_session(struct config_module * config, const struct _u_request * request, const char * username); int glewlwyd_module_metrics_increment_counter(struct config_module * config, const char * metrics_name, size_t inc, const char * module_type, const char * module_name); int glewlwyd_module_callback_metrics_add_metric(struct config_module * config, const char * name, const char * help); int glewlwyd_module_callback_metrics_increment_counter(struct config_module * config, const char * name, size_t inc, ...); // Client CRUD functions json_t * get_client_list(struct config_elements * config, const char * pattern, size_t offset, size_t limit, const char * source); json_t * get_client(struct config_elements * config, const char * client_id, const char * source); json_t * is_client_valid(struct config_elements * config, const char * client_id, json_t * j_client, int add, const char * source); int add_client(struct config_elements * config, json_t * j_client, const char * source); int set_client(struct config_elements * config, const char * client_id, json_t * j_client, const char * source); int delete_client(struct config_elements * config, const char * client_id, const char * source); // Scope CRUD functions json_t * get_scope_list(struct config_elements * config, const char * pattern, size_t offset, size_t limit); json_t * get_scope(struct config_elements * config, const char * scope); json_t * is_scope_valid(struct config_elements * config, json_t * j_scope, int add); int add_scope(struct config_elements * config, json_t * j_scope); int set_scope(struct config_elements * config, const char * scope, json_t * j_scope); int delete_scope(struct config_elements * config, const char * scope); // API key CRD functions int verify_api_key(struct config_elements * config, const char * api_key); json_t * get_api_key_list(struct config_elements * config, const char * pattern, size_t offset, size_t limit); json_t * generate_api_key(struct config_elements * config, const char * username, const char * issued_for, const char * user_agent); int disable_api_key(struct config_elements * config, const char * token_hash); // Metrics functions void glewlwyd_metrics_close(struct config_elements * config); int glewlwyd_metrics_init(struct config_elements * config); void free_glwd_metrics(void * data); int glewlwyd_metrics_add_metric(struct config_elements * config, const char * name, const char * help); int glewlwyd_metrics_increment_counter_va(struct config_elements * config, const char * name, size_t inc, ...); int glewlwyd_metrics_increment_counter(struct config_elements * config, const char * name, const char * label, size_t inc); char * glewlwyd_metrics_build_label(va_list vl_label); // Callback functions int callback_glewlwyd_check_user_session (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_check_admin_session (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_check_admin_session_or_api_key (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_check_admin_session_delegate (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_check_user_profile_valid (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_user_auth (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_user_auth_trigger (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_user_get_schemes_from_scopes (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_user_delete_session (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_user_get_profile (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_user_update_profile (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_user_delete_profile (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_user_update_password (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_user_get_plugin_list (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_user_get_client_grant_list (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_user_get_session_list (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_user_get_scheme_list (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_delete_session (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_user_auth_register (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_user_auth_register_get (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_scheme_check_forbid_profile (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_user_auth_register_delegate (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_user_auth_register_get_delegate (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_get_user_session_scope_grant (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_set_user_session_scope_grant (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_options (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_server_configuration (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_get_module_type_list (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_reload_modules (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_get_user_module_list (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_get_user_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_add_user_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_set_user_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_delete_user_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_manage_user_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_get_user_middleware_module_list (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_get_user_middleware_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_add_user_middleware_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_set_user_middleware_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_delete_user_middleware_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_manage_user_middleware_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_get_user_auth_scheme_module_list (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_get_user_auth_scheme_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_add_user_auth_scheme_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_set_user_auth_scheme_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_delete_user_auth_scheme_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_manage_user_auth_scheme_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_get_client_module_list (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_get_client_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_add_client_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_set_client_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_delete_client_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_manage_client_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_get_plugin_module_list (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_get_plugin_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_add_plugin_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_set_plugin_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_delete_plugin_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_manage_plugin_module (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_get_user_list (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_get_user (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_add_user (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_set_user (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_delete_user (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_get_client_list (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_get_client (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_add_client (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_set_client (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_delete_client (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_get_scope_list (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_get_scope (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_add_scope (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_set_scope (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_delete_scope (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_get_api_key_list (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_delete_api_key (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_glewlwyd_add_api_key (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_metrics (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_default (const struct _u_request * request, struct _u_response * response, void * user_data); int callback_404_if_necessary (const struct _u_request * request, struct _u_response * response, void * user_data); #endif �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/http_compression_callback.c������������������������������������������������������0000664�0000000�0000000�00000012663�14156463140�0021541�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * * Response body compression callback function for Ulfius Framework * * Copyright 2020 Nicolas Mora <mail@babelouest.org> * * Version 20201213 * * Compress the response body using `deflate` or `gzip` depending on the request header `Accept-Encoding` and the callback configuration. * The rest of the response, status, headers, cookies won't change. * After compressing response body, the response header Content-Encoding will be set accordingly. * * The MIT License (MIT) * * 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. * */ #include <zlib.h> #include <string.h> #include <ulfius.h> #include "http_compression_callback.h" #define U_COMPRESS_NONE 0 #define U_COMPRESS_GZIP 1 #define U_COMPRESS_DEFL 2 #define U_ACCEPT_HEADER "Accept-Encoding" #define U_CONTENT_HEADER "Content-Encoding" #define U_ACCEPT_GZIP "gzip" #define U_ACCEPT_DEFLATE "deflate" #define U_GZIP_WINDOW_BITS 15 #define U_GZIP_ENCODING 16 #define CHUNK 0x4000 static void * u_zalloc(void * q, unsigned n, unsigned m) { (void)q; return o_malloc((size_t) n * m); } static void u_zfree(void *q, void *p) { (void)q; o_free(p); } int callback_http_compression (const struct _u_request * request, struct _u_response * response, void * user_data) { struct _http_compression_config * config = (struct _http_compression_config *)user_data; char ** accept_list = NULL; int ret = U_CALLBACK_IGNORE, compress_mode = U_COMPRESS_NONE, res; z_stream defstream; char * data_zip = NULL; size_t data_zip_len = 0; if (response->binary_body_length && u_map_has_key_case(request->map_header, U_ACCEPT_HEADER)) { if (split_string(u_map_get_case(request->map_header, U_ACCEPT_HEADER), ",", &accept_list)) { if ((config == NULL || config->allow_gzip) && string_array_has_trimmed_value((const char **)accept_list, U_ACCEPT_GZIP)) { compress_mode = U_COMPRESS_GZIP; } else if ((config == NULL || config->allow_deflate) && string_array_has_trimmed_value((const char **)accept_list, U_ACCEPT_DEFLATE)) { compress_mode = U_COMPRESS_DEFL; } if (compress_mode != U_COMPRESS_NONE) { defstream.zalloc = u_zalloc; defstream.zfree = u_zfree; defstream.opaque = Z_NULL; defstream.avail_in = (uInt)response->binary_body_length; defstream.next_in = (Bytef *)response->binary_body; if (compress_mode == U_COMPRESS_GZIP) { if (deflateInit2(&defstream, Z_BEST_COMPRESSION, Z_DEFLATED, U_GZIP_WINDOW_BITS | U_GZIP_ENCODING, 8, Z_DEFAULT_STRATEGY) != Z_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_http_compression - Error deflateInit (gzip)"); ret = U_CALLBACK_ERROR; } } else { if (deflateInit(&defstream, Z_BEST_COMPRESSION) != Z_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_http_compression - Error deflateInit (deflate)"); ret = U_CALLBACK_ERROR; } } if (ret == U_CALLBACK_IGNORE) { do { if ((data_zip = o_realloc(data_zip, data_zip_len+_U_C_BLOCK_SIZE)) != NULL) { defstream.avail_out = _U_C_BLOCK_SIZE; defstream.next_out = ((Bytef *)data_zip)+data_zip_len; switch ((res = deflate(&defstream, Z_FINISH))) { case Z_OK: case Z_STREAM_END: case Z_BUF_ERROR: break; default: y_log_message(Y_LOG_LEVEL_ERROR, "callback_http_compression - Error deflate %d", res); ret = U_CALLBACK_ERROR; break; } data_zip_len += _U_C_BLOCK_SIZE - defstream.avail_out; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_http_compression - Error allocating resources for data_zip"); ret = U_CALLBACK_ERROR; } } while (U_CALLBACK_IGNORE == ret && defstream.avail_out == 0); if (ret == U_CALLBACK_IGNORE) { ulfius_set_binary_body_response(response, response->status, (const char *)data_zip, defstream.total_out); u_map_put(response->map_header, U_CONTENT_HEADER, compress_mode==U_COMPRESS_GZIP?U_ACCEPT_GZIP:U_ACCEPT_DEFLATE); } deflateEnd(&defstream); o_free(data_zip); } } } free_string_array(accept_list); } return ret; } �����������������������������������������������������������������������������glewlwyd-2.6.1/src/http_compression_callback.h������������������������������������������������������0000664�0000000�0000000�00000004416�14156463140�0021543�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * * Response body compression callback function for Ulfius Framework * * Copyright 2020 Nicolas Mora <mail@babelouest.org> * * Version 20201213 * * Compress the response body using `deflate` or `gzip` depending on the request header `Accept-Encoding` and the callback configuration. * The rest of the response, status, headers, cookies won't change. * After compressing response body, the response header Content-Encoding will be set accordingly. * * The MIT License (MIT) * * 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. * */ #ifndef _U_HTTP_COMPRESSION #define _U_HTTP_COMPRESSION #define _U_C_BLOCK_SIZE 256 /** * If both values are set to true, the first compression algorithm used will be gzip */ struct _http_compression_config { int allow_gzip; int allow_deflate; }; /** * Compress response->binary_body using gzip or deflate algorithm * depending on the request header Accept-Encoding and the * struct _http_compression_config configuration value * If user_data is NULL, it will considered as allow_gzip and allow_deflate to true * After compressing response body, will set response header Content-Encoding accordingly */ int callback_http_compression (const struct _u_request * request, struct _u_response * response, void * user_data); #endif ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/metrics.c������������������������������������������������������������������������0000664�0000000�0000000�00000016102�14156463140�0015763�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * Prometheus metrics functions definitions * * Copyright 2016-2021 Nicolas Mora <mail@babelouest.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see <http://www.gnu.org/licenses/>. * */ #include <sched.h> #include "glewlwyd.h" struct _glwd_increment_counter_data { struct config_elements * config; char * name; char * label; size_t inc; }; void * glewlwyd_metrics_increment_counter_thread(void * args) { struct _glwd_increment_counter_data * data = (struct _glwd_increment_counter_data *)args; struct _glwd_metric * metric; size_t i, j; int found; if (!pthread_mutex_lock(&data->config->metrics_lock)) { for (i=0; i<pointer_list_size(&data->config->metrics_list); i++) { metric = (struct _glwd_metric *)pointer_list_get_at(&data->config->metrics_list, i); if (0 == o_strcmp(data->name, metric->name)) { found = 0; for (j=0; j<metric->data_size; j++) { if ((data->label == NULL && metric->data[j].label == NULL) || 0 == o_strcasecmp(data->label, metric->data[j].label)) { metric->data[j].counter += data->inc; found = 1; } } if (!found) { if ((metric->data = o_realloc(metric->data, (metric->data_size+1)*sizeof(struct _glwd_metrics_data))) != NULL) { metric->data[metric->data_size].label = o_strdup(data->label); metric->data[metric->data_size].counter = data->inc; metric->data_size++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_metrics_increment_counter_thread - Error realloc metric->data"); } } } } pthread_mutex_unlock(&data->config->metrics_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_metrics_increment_counter_thread - Error lock"); } o_free(data->name); o_free(data->label); o_free(data); pthread_exit(NULL); } /** * Runs a single thread with a low priority to increment a metrics value */ int glewlwyd_metrics_increment_counter(struct config_elements * config, const char * name, const char * label, size_t inc) { struct _glwd_increment_counter_data * data; pthread_t thread_metrics; int thread_ret, thread_detach; pthread_attr_t attr; struct sched_param param; int ret; if (config->metrics_endpoint) { if (config != NULL && o_strlen(name)) { if ((data = o_malloc(sizeof(struct _glwd_increment_counter_data))) != NULL) { data->config = config; data->name = o_strdup(name); data->label = o_strdup(label); data->inc = inc; pthread_attr_init (&attr); pthread_attr_getschedparam (&attr, ¶m); param.sched_priority = 0; pthread_attr_setschedparam (&attr, ¶m); thread_ret = pthread_create(&thread_metrics, &attr, glewlwyd_metrics_increment_counter_thread, (void *)data); thread_detach = pthread_detach(thread_metrics); if (thread_ret || thread_detach) { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_metrics_increment_counter - Error thread"); o_free(data->name); o_free(data->label); o_free(data); ret = G_ERROR; } else { ret = G_OK; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_metrics_increment_counter - Error allocating resources for struct _glwd_increment_counter_data"); ret = G_ERROR_MEMORY; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_metrics_increment_counter - Error input values"); ret = G_ERROR_PARAM; } } else { ret = G_OK; } return ret; } int glewlwyd_metrics_increment_counter_va(struct config_elements * config, const char * name, size_t inc, ...) { va_list vl; char * label = NULL; int ret = G_OK; if (config->metrics_endpoint) { if (config != NULL && o_strlen(name)) { va_start(vl, inc); label = glewlwyd_metrics_build_label(vl); va_end(vl); ret = glewlwyd_metrics_increment_counter(config, name, label, inc); o_free(label); } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_metrics_increment_counter_va - Error input values"); ret = G_ERROR_PARAM; } } return ret; } void free_glwd_metrics(void * data) { struct _glwd_metric * glwd_metrics = (struct _glwd_metric *)data; size_t i; if (glwd_metrics != NULL) { o_free(glwd_metrics->name); o_free(glwd_metrics->help); for (i=0; i<glwd_metrics->data_size; i++) { o_free(glwd_metrics->data[i].label); } o_free(glwd_metrics->data); o_free(glwd_metrics); } } int glewlwyd_metrics_add_metric(struct config_elements * config, const char * name, const char * help) { struct _glwd_metric * glwd_metrics; int ret; if (config->metrics_endpoint) { if (o_strlen(name)) { if ((glwd_metrics = o_malloc(sizeof(struct _glwd_metric))) != NULL) { glwd_metrics->name = o_strdup(name); glwd_metrics->help = o_strdup(help); glwd_metrics->data_size = 0; glwd_metrics->data = NULL; pointer_list_append(&config->metrics_list, glwd_metrics); ret = G_OK; } else { ret = G_ERROR_MEMORY; } } else { ret = G_ERROR_PARAM; } } else { ret = G_OK; } return ret; } int glewlwyd_metrics_init(struct config_elements * config) { pthread_mutexattr_t mutexattr; int ret = G_OK; pointer_list_init(&config->metrics_list); pthread_mutexattr_init ( &mutexattr ); pthread_mutexattr_settype( &mutexattr, PTHREAD_MUTEX_RECURSIVE ); if (pthread_mutex_init(&config->metrics_lock, &mutexattr) != 0) { ret = GLEWLWYD_ERROR; } pthread_mutexattr_destroy(&mutexattr); return ret; } void glewlwyd_metrics_close(struct config_elements * config) { if (config->metrics_endpoint) { pointer_list_clean_free(&config->metrics_list, &free_glwd_metrics); pthread_mutex_destroy(&config->metrics_lock); } } char * glewlwyd_metrics_build_label(va_list vl_label) { const char * label_arg; char * label = NULL; int flag = 0; for (label_arg = va_arg(vl_label, const char *); label_arg != NULL; label_arg = va_arg(vl_label, const char *)) { if (!flag) { if (label == NULL) { label = msprintf("%s=", label_arg); } else { label = mstrcatf(label, ", %s=", label_arg); } } else { label = mstrcatf(label, "\"%s\"", label_arg); } flag = !flag; } return label; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/misc.c���������������������������������������������������������������������������0000664�0000000�0000000�00000042655�14156463140�0015264�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * Miscellaneous functions definitions * * Copyright 2016-2021 Nicolas Mora <mail@babelouest.org> * * The MIT License (MIT) * * 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. * */ #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <arpa/inet.h> #include <string.h> #include <ctype.h> #include <gnutls/gnutls.h> #include <gnutls/crypto.h> #include <nettle/pbkdf2.h> #include "glewlwyd-common.h" /** * * Read the content of a file and return it as a char * * returned value must be o_free'd after use * */ char * get_file_content(const char * file_path) { char * buffer = NULL; size_t length, res; FILE * f; f = fopen (file_path, "rb"); if (f) { fseek (f, 0, SEEK_END); length = ftell (f); fseek (f, 0, SEEK_SET); buffer = o_malloc((length+1)*sizeof(char)); if (buffer) { res = fread (buffer, 1, length, f); if (res != length) { y_log_message(Y_LOG_LEVEL_ERROR, "get_file_content - fread warning, reading %zu while expecting %zu", res, length); } // Add null character at the end of buffer, just in case buffer[length] = '\0'; } fclose (f); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_file_content - error opening file %s\n", file_path); } return buffer; } /** * Return the source ip address of the request * Based on the header value "X-Forwarded-For" if set, which means the request is forwarded by a proxy * otherwise the call is direct, return the client_address */ const char * get_ip_source(const struct _u_request * request) { const char * ip_source = u_map_get_case(request->map_header, "X-Forwarded-For"); if (ip_source == NULL) { struct sockaddr_in * in_source = (struct sockaddr_in *)request->client_address; if (in_source != NULL) { ip_source = inet_ntoa(in_source->sin_addr); } else { ip_source = "NOT_FOUND"; } } return ip_source; }; char * get_client_hostname(const struct _u_request * request) { const char * ip_source = get_ip_source(request); struct addrinfo hints; struct addrinfo * lookup = NULL; char * hostname = NULL; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_CANONNAME; hints.ai_canonname = NULL; if (ip_source != NULL) { hostname = o_strdup(ip_source); if (!getaddrinfo(ip_source, NULL, &hints, &lookup)) { if (o_strlen(lookup->ai_canonname)) { hostname = mstrcatf(hostname, " - %s", lookup->ai_canonname); } freeaddrinfo(lookup); lookup = NULL; } } return hostname; } /** * * Generates a random long integer between 0 and max * */ unsigned char random_at_most(unsigned char max, int nonce) { unsigned char num_bins = (unsigned char) max + 1, num_rand = (unsigned char) 0xff, bin_size = num_rand / num_bins, defect = num_rand % num_bins; unsigned char x[1]; do { gnutls_rnd(nonce?GNUTLS_RND_NONCE:GNUTLS_RND_KEY, x, sizeof(x)); } // This is carefully written not to overflow while (num_rand - defect <= (unsigned char)x[0]); // Truncated division is intentional return x[0]/bin_size; } /** * Generates a random string and store it in str */ char * rand_string(char * str, size_t str_size) { return rand_string_from_charset(str, str_size, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); } /** * Generates a random string and store it in str */ char * rand_string_from_charset(char * str, size_t str_size, const char * charset) { size_t n; if (str_size && str != NULL) { for (n = 0; n < str_size; n++) { str[n] = charset[random_at_most((o_strlen(charset)) - 2, 0)]; } str[str_size] = '\0'; return str; } else { return NULL; } } /** * Generates a random string used as nonce and store it in str */ char * rand_string_nonce(char * str, size_t str_size) { const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; size_t n; if (str_size && str != NULL) { for (n = 0; n < str_size; n++) { str[n] = charset[random_at_most((sizeof(charset)) - 2, 1)]; } str[str_size] = '\0'; return str; } else { return NULL; } } int rand_code(char * str, size_t str_size) { const char charset[] = "0123456789"; size_t n; if (str_size && str != NULL) { for (n = 0; n < str_size; n++) { unsigned char key = random_at_most((sizeof(charset)) - 2, 0); str[n] = charset[key]; } str[str_size] = '\0'; return 1; } else { return 0; } } char * join_json_string_array(json_t * j_array, const char * separator) { char * str_result = NULL, * tmp; json_t * j_element; size_t index; if (j_array != NULL && json_is_array(j_array)) { json_array_foreach(j_array, index, j_element) { if (json_is_string(j_element) && json_string_length(j_element)) { if (str_result == NULL) { str_result = o_strdup(json_string_value(j_element)); } else { tmp = msprintf("%s%s%s", str_result, separator, json_string_value(j_element)); o_free(str_result); str_result = tmp; } } } } return str_result; } /** * Converts an integer value to its hex character */ char to_hex(char code) { static char hex[] = "0123456789abcdef"; return hex[code & 15]; } /** * Generates a digest using the digest_algorithm specified from data and add a salt if specified, stores it in out_digest */ int generate_digest(digest_algorithm digest, const char * data, int use_salt, char * out_digest) { unsigned int res = 0; int alg, dig_res; gnutls_datum_t key_data; char * intermediate = NULL, salt[GLEWLWYD_DEFAULT_SALT_LENGTH + 1] = {0}; unsigned char encoded_key[128 + GLEWLWYD_DEFAULT_SALT_LENGTH + 1] = {0}; size_t encoded_key_size = (128 + GLEWLWYD_DEFAULT_SALT_LENGTH), encoded_key_size_base64; if (data != NULL && out_digest != NULL) { switch (digest) { case digest_SHA1: alg = GNUTLS_DIG_SHA1; break; case digest_SHA224: alg = GNUTLS_DIG_SHA224; break; case digest_SHA256: alg = GNUTLS_DIG_SHA256; break; case digest_SHA384: alg = GNUTLS_DIG_SHA384; break; case digest_SHA512: alg = GNUTLS_DIG_SHA512; break; case digest_MD5: alg = GNUTLS_DIG_MD5; break; default: alg = GNUTLS_DIG_UNKNOWN; break; } if(alg != GNUTLS_DIG_UNKNOWN) { if (o_strlen(data) > 0) { if (use_salt) { rand_string_nonce(salt, GLEWLWYD_DEFAULT_SALT_LENGTH); intermediate = msprintf("%s%s", data, salt); } else { intermediate = o_strdup(data); } key_data.data = (unsigned char*)intermediate; key_data.size = o_strlen(intermediate); if (key_data.data != NULL && (dig_res = gnutls_fingerprint(alg, &key_data, encoded_key, &encoded_key_size)) == GNUTLS_E_SUCCESS) { if (use_salt) { memcpy(encoded_key+encoded_key_size, salt, GLEWLWYD_DEFAULT_SALT_LENGTH); encoded_key_size += GLEWLWYD_DEFAULT_SALT_LENGTH; } if (o_base64_encode(encoded_key, encoded_key_size, (unsigned char *)out_digest, &encoded_key_size_base64)) { res = 1; } else{ res = 0; } } else { res = 0; } o_free(intermediate); } else { // No data, then out_digest becomes an empty string out_digest[0] = '\0'; res = 1; } } else { res = 0; } } else { res = 0; } return res; } /** * Generates a digest using the digest_algorithm specified from data and add a salt if specified, stores it in out_digest as raw output */ int generate_digest_raw(digest_algorithm digest, const unsigned char * data, size_t data_len, unsigned char * out_digest, size_t * out_digest_len) { unsigned int res = 0; int alg, dig_res; gnutls_datum_t key_data; if (data != NULL && out_digest != NULL) { switch (digest) { case digest_SHA1: alg = GNUTLS_DIG_SHA1; break; case digest_SHA224: alg = GNUTLS_DIG_SHA224; break; case digest_SHA256: alg = GNUTLS_DIG_SHA256; break; case digest_SHA384: alg = GNUTLS_DIG_SHA384; break; case digest_SHA512: alg = GNUTLS_DIG_SHA512; break; case digest_MD5: alg = GNUTLS_DIG_MD5; break; default: alg = GNUTLS_DIG_UNKNOWN; break; } if(alg != GNUTLS_DIG_UNKNOWN) { if (data_len > 0) { key_data.data = (unsigned char *)data; key_data.size = data_len; if (key_data.data != NULL) { if ((dig_res = gnutls_fingerprint(alg, &key_data, out_digest, out_digest_len)) == GNUTLS_E_SUCCESS) { res = 1; } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_digest_raw - Error gnutls_fingerprint: %d", dig_res); res = 0; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_digest_raw - Error key_data.data"); res = 0; } } else { // No data, then out_digest is empty *out_digest_len = 0; res = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_digest_raw - Error alg"); res = 0; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_digest_raw - Error param"); res = 0; } return res; } /** * Generates a digest using the PBKDF2 algorithm from data and a salt if specified, otherwise generates a salt, stores it in out_digest */ int generate_digest_pbkdf2(const char * data, unsigned int iterations, const char * salt, char * out_digest) { char my_salt[GLEWLWYD_DEFAULT_SALT_LENGTH + 1] = {0}; uint8_t cur_salt[GLEWLWYD_DEFAULT_SALT_LENGTH], dst[32 + GLEWLWYD_DEFAULT_SALT_LENGTH] = {0}; int res; size_t encoded_key_size_base64; if (salt != NULL) { memcpy(cur_salt, salt, GLEWLWYD_DEFAULT_SALT_LENGTH); } else { rand_string_nonce(my_salt, GLEWLWYD_DEFAULT_SALT_LENGTH); memcpy(cur_salt, my_salt, GLEWLWYD_DEFAULT_SALT_LENGTH); } pbkdf2_hmac_sha256(o_strlen(data), (const uint8_t *)data, iterations, GLEWLWYD_DEFAULT_SALT_LENGTH, cur_salt, 32, dst); memcpy(dst+32, cur_salt, GLEWLWYD_DEFAULT_SALT_LENGTH); if (o_base64_encode(dst, 32 + GLEWLWYD_DEFAULT_SALT_LENGTH, (unsigned char *)out_digest, &encoded_key_size_base64)) { res = 1; } else{ res = 0; } return res; } /** * Generates a digest using crypt library * uses a 16-bytes random salt */ int generate_digest_crypt(const char * data, const char * method, char * out_digest) { char salt[GLEWLWYD_DEFAULT_SALT_LENGTH+4] = {0}, * out_crypt; int res; if (method != NULL) { o_strcpy(salt, method); } rand_string_nonce(salt+o_strlen(method), GLEWLWYD_DEFAULT_SALT_LENGTH); if ((out_crypt = crypt(data, salt)) != NULL) { o_strcpy(out_digest, out_crypt); res = 1; } else { res = 0; } return res; } /** * Generates a hash from the specified string data, using the digest method specified * returned value must be 'd after user */ char * generate_hash(digest_algorithm digest, const char * data) { char * to_return = NULL, buffer[1024] = {0}; if (data != NULL) { switch (digest) { case digest_SSHA1: if (generate_digest(digest_SHA1, data, 1, buffer)) { to_return = msprintf("{SSHA}%s", buffer); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_hash - Error generating digest SSHA"); } break; case digest_SHA1: if (generate_digest(digest_SHA1, data, 0, buffer)) { to_return = msprintf("{SHA}%s", buffer); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_hash - Error generating digest SHA"); } break; case digest_SSHA224: if (generate_digest(digest_SHA224, data, 1, buffer)) { to_return = msprintf("{SSHA224}%s", buffer); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_hash - Error generating digest SSHA224"); } break; case digest_SHA224: if (generate_digest(digest_SHA224, data, 0, buffer)) { to_return = msprintf("{SHA224}%s", buffer); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_hash - Error generating digest SHA224"); } break; case digest_SSHA256: if (generate_digest(digest_SHA256, data, 1, buffer)) { to_return = msprintf("{SSHA256}%s", buffer); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_hash - Error generating digest SSHA256"); } break; case digest_SHA256: if (generate_digest(digest_SHA256, data, 0, buffer)) { to_return = msprintf("{SHA256}%s", buffer); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_hash - Error generating digest SHA256"); } break; case digest_SSHA384: if (generate_digest(digest_SHA384, data, 1, buffer)) { to_return = msprintf("{SSHA384}%s", buffer); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_hash - Error generating digest SSHA384"); } break; case digest_SHA384: if (generate_digest(digest_SHA384, data, 0, buffer)) { to_return = msprintf("{SHA384}%s", buffer); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_hash - Error generating digest SHA384"); } break; case digest_SSHA512: if (generate_digest(digest_SHA512, data, 1, buffer)) { to_return = msprintf("{SSHA512}%s", buffer); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_hash - Error generating digest SSHA512"); } break; case digest_SHA512: if (generate_digest(digest_SHA512, data, 0, buffer)) { to_return = msprintf("{SHA512}%s", buffer); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_hash - Error generating digest SHA512"); } break; case digest_SMD5: if (generate_digest(digest_MD5, data, 1, buffer)) { to_return = msprintf("{SMD5}%s", buffer); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_hash - Error generating digest SMD5"); } break; case digest_MD5: if (generate_digest(digest_MD5, data, 0, buffer)) { to_return = msprintf("{MD5}%s", buffer); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_hash - Error generating digest MD5"); } break; case digest_PBKDF2_SHA256: if (generate_digest_pbkdf2(data, G_PBKDF2_ITERATOR_DEFAULT, NULL, buffer)) { to_return = msprintf("{PBKDF2}%s", buffer); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_hash - Error generating digest PBKDF2"); } break; case digest_CRYPT: if (generate_digest_crypt(data, NULL, buffer)) { to_return = msprintf("{CRYPT}%s", buffer); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_hash - Error generating digest CRYPT"); } break; case digest_CRYPT_MD5: if (generate_digest_crypt(data, "$1$", buffer)) { to_return = msprintf("{CRYPT}%s", buffer); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_hash - Error generating digest CRYPT_MD5"); } break; case digest_CRYPT_SHA256: if (generate_digest_crypt(data, "$5$", buffer)) { to_return = msprintf("{CRYPT}%s", buffer); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_hash - Error generating digest CRYPT_SHA256"); } break; case digest_CRYPT_SHA512: if (generate_digest_crypt(data, "$6$", buffer)) { to_return = msprintf("{CRYPT}%s", buffer); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_hash - Error generating digest CRYPT_SHA512"); } break; default: y_log_message(Y_LOG_LEVEL_ERROR, "generate_hash - Error algorithm not found"); to_return = NULL; break; } } return to_return; } /** * Check if the result json object has a "result" element that is equal to value */ int check_result_value(json_t * result, const int value) { return (json_is_integer(json_object_get(result, "result")) && json_integer_value(json_object_get(result, "result")) == value); } �����������������������������������������������������������������������������������glewlwyd-2.6.1/src/module.c�������������������������������������������������������������������������0000664�0000000�0000000�00000344314�14156463140�0015613�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * Modules management functions definitions * * Copyright 2016-2021 Nicolas Mora <mail@babelouest.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see <http://www.gnu.org/licenses/>. * */ #include "glewlwyd.h" json_t * get_module_type_list(struct config_elements * config) { struct _user_module * user_module; struct _user_middleware_module * user_middleware_module; struct _client_module * client_module; struct _user_auth_scheme_module * scheme_module; struct _plugin_module * plugin_module; size_t i; json_t * j_return; if ((j_return = json_pack("{sis{s[]s[]s[]s[]s[]}}", "result", G_OK, "module", "user", "user_middleware", "client", "scheme", "plugin")) != NULL) { // Gathering user modules for (i=0; i<pointer_list_size(config->user_module_list); i++) { user_module = (struct _user_module *)pointer_list_get_at(config->user_module_list, i); if (user_module != NULL) { json_array_append_new(json_object_get(json_object_get(j_return, "module"), "user"), json_pack("{ssss?ss?}", "name", user_module->name, "display_name", user_module->display_name, "description", user_module->description)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_module_type_list - Error pointer_list_get_at for user module at index %d", i); } } // Gathering user middleware modules for (i=0; i<pointer_list_size(config->user_middleware_module_list); i++) { user_middleware_module = (struct _user_middleware_module *)pointer_list_get_at(config->user_middleware_module_list, i); if (user_middleware_module != NULL) { json_array_append_new(json_object_get(json_object_get(j_return, "module"), "user_middleware"), json_pack("{ssss?ss?}", "name", user_middleware_module->name, "display_name", user_middleware_module->display_name, "description", user_middleware_module->description)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_module_type_list - Error pointer_list_get_at for user_middleware module at index %d", i); } } // Gathering client modules for (i=0; i<pointer_list_size(config->client_module_list); i++) { client_module = (struct _client_module *)pointer_list_get_at(config->client_module_list, i); if (client_module != NULL) { json_array_append_new(json_object_get(json_object_get(j_return, "module"), "client"), json_pack("{ssss?ss?}", "name", client_module->name, "display_name", client_module->display_name, "description", client_module->description)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_module_type_list - Error pointer_list_get_at for client module at index %d", i); } } // Gathering user auth scheme modules for (i=0; i<pointer_list_size(config->user_auth_scheme_module_list); i++) { scheme_module = (struct _user_auth_scheme_module *)pointer_list_get_at(config->user_auth_scheme_module_list, i); if (scheme_module != NULL) { json_array_append_new(json_object_get(json_object_get(j_return, "module"), "scheme"), json_pack("{ssss?ss?}", "name", scheme_module->name, "display_name", scheme_module->display_name, "description", scheme_module->description)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_module_type_list - Error pointer_list_get_at for user auth scheme module at index %d", i); } } // Gathering plugin modules for (i=0; i<pointer_list_size(config->plugin_module_list); i++) { plugin_module = (struct _plugin_module *)pointer_list_get_at(config->plugin_module_list, i); if (plugin_module != NULL) { json_array_append_new(json_object_get(json_object_get(j_return, "module"), "plugin"), json_pack("{ssss?ss?}", "name", plugin_module->name, "display_name", plugin_module->display_name, "description", plugin_module->description)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_module_type_list - Error pointer_list_get_at for plugin module at index %d", i); } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_module_type_list - Error allocating resources for j_return"); } return j_return; } json_t * get_user_module_list(struct config_elements * config) { int res; json_t * j_query, * j_result = NULL, * j_return, * j_parameters, * j_element; size_t index; j_query = json_pack("{sss[ssssssss]ss}", "table", GLEWLWYD_TABLE_USER_MODULE_INSTANCE, "columns", "gumi_module AS module", "gumi_name AS name", "gumi_display_name AS display_name", "gumi_parameters", "gumi_order AS order_rank", "gumi_readonly", "gumi_multiple_passwords", "gumi_enabled", "order_by", "gumi_order"); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result, index, j_element) { j_parameters = json_loads(json_string_value(json_object_get(j_element, "gumi_parameters")), JSON_DECODE_ANY, NULL); if (j_parameters != NULL) { json_object_set_new(j_element, "parameters", j_parameters); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_module_list - Error parsing parameters for module %s %s", json_string_value(json_object_get(j_element, "name")), json_string_value(json_object_get(j_element, "gumi_parameters"))); json_object_set_new(j_element, "parameters", json_null()); } json_object_del(j_element, "gumi_parameters"); json_object_set_new(j_element, "enabled", json_integer_value(json_object_get(j_element, "gumi_enabled"))?json_true():json_false()); json_object_del(j_element, "gumi_enabled"); json_object_set_new(j_element, "readonly", json_integer_value(json_object_get(j_element, "gumi_readonly"))?json_true():json_false()); json_object_del(j_element, "gumi_readonly"); json_object_set_new(j_element, "multiple_passwords", json_integer_value(json_object_get(j_element, "gumi_multiple_passwords"))?json_true():json_false()); json_object_del(j_element, "gumi_multiple_passwords"); } j_return = json_pack("{sisO}", "result", G_OK, "module", j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_module_list - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_result); return j_return; } json_t * get_user_module(struct config_elements * config, const char * name) { int res; json_t * j_query, * j_result = NULL, * j_return, * j_parameters; j_query = json_pack("{sss[ssssssss]s{ss}}", "table", GLEWLWYD_TABLE_USER_MODULE_INSTANCE, "columns", "gumi_module AS module", "gumi_name AS name", "gumi_display_name AS display_name", "gumi_parameters", "gumi_order AS order_rank", "gumi_enabled", "gumi_readonly", "gumi_multiple_passwords", "where", "gumi_name", name); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result) > 0) { j_parameters = json_loads(json_string_value(json_object_get(json_array_get(j_result, 0), "gumi_parameters")), JSON_DECODE_ANY, NULL); if (j_parameters != NULL) { json_object_set_new(json_array_get(j_result, 0), "parameters", j_parameters); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_module - Error parsing parameters for module %s", json_string_value(json_object_get(json_array_get(j_result, 0), "name"))); json_object_set_new(json_array_get(j_result, 0), "parameters", json_null()); } json_object_del(json_array_get(j_result, 0), "gumi_parameters"); json_object_set_new(json_array_get(j_result, 0), "enabled", json_integer_value(json_object_get(json_array_get(j_result, 0), "gumi_enabled"))?json_true():json_false()); json_object_del(json_array_get(j_result, 0), "gumi_enabled"); json_object_set_new(json_array_get(j_result, 0), "readonly", json_integer_value(json_object_get(json_array_get(j_result, 0), "gumi_readonly"))?json_true():json_false()); json_object_del(json_array_get(j_result, 0), "gumi_readonly"); json_object_set_new(json_array_get(j_result, 0), "multiple_passwords", json_integer_value(json_object_get(json_array_get(j_result, 0), "gumi_multiple_passwords"))?json_true():json_false()); json_object_del(json_array_get(j_result, 0), "gumi_multiple_passwords"); j_return = json_pack("{sisO}", "result", G_OK, "module", json_array_get(j_result, 0)); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_module - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_result); return j_return; } json_t * is_user_module_valid(struct config_elements * config, json_t * j_module, int add) { json_t * j_return, * j_cur_module, * j_error_list; size_t i; int found; struct _user_module * module; char * parameters; if (j_module != NULL && json_is_object(j_module)) { if ((j_error_list = json_array()) != NULL) { if (add) { if (json_object_get(j_module, "name") != NULL && json_is_string(json_object_get(j_module, "name")) && json_string_length(json_object_get(j_module, "name")) > 0 && json_string_length(json_object_get(j_module, "name")) <= 128) { j_cur_module = get_user_module(config, json_string_value(json_object_get(j_module, "name"))); if (check_result_value(j_cur_module, G_OK)) { json_array_append_new(j_error_list, json_string("A module instance with this name already exist")); } else if (!check_result_value(j_cur_module, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_ERROR, "is_user_module_valid - Error json_array_append_new"); } json_decref(j_cur_module); } else { json_array_append_new(j_error_list, json_string("Module instance name is mandatory and must be a non empty string of at most 128 characters")); } if (json_object_get(j_module, "module") != NULL && json_is_string(json_object_get(j_module, "module")) && json_string_length(json_object_get(j_module, "module")) > 0 && json_string_length(json_object_get(j_module, "module")) <= 128) { found = 0; for (i=0; i<pointer_list_size(config->user_module_list); i++) { module = (struct _user_module *)pointer_list_get_at(config->user_module_list, i); if (module != NULL) { if (0 == o_strcmp(module->name, json_string_value(json_object_get(j_module, "module")))) { found = 1; break; } } } if (!found) { json_array_append_new(j_error_list, json_string("Module name doesn't exist")); } } else { json_array_append_new(j_error_list, json_string("Module is mandatory and must be a non empty string of at most 128 characters")); } } else { if (json_object_get(j_module, "enabled") != NULL && !json_is_boolean(json_object_get(j_module, "enabled"))) { json_array_append_new(j_error_list, json_string("enabled is optional and must be a boolean")); } } if (json_object_get(j_module, "display_name") != NULL && (!json_is_string(json_object_get(j_module, "display_name")) || json_string_length(json_object_get(j_module, "display_name")) > 256)) { json_array_append_new(j_error_list, json_string("display_name is optional and must be a string of at most 256 characters")); } if (json_object_get(j_module, "parameters") == NULL || !json_is_object(json_object_get(j_module, "parameters"))) { json_array_append_new(j_error_list, json_string("Parameters is mandatory and must be a json object of at most 16k characters")); } else { parameters = json_dumps(json_object_get(j_module, "parameters"), JSON_COMPACT); if (parameters == NULL || o_strlen(parameters) > 16*1024) { json_array_append_new(j_error_list, json_string("Parameters is mandatory and must be a json object of at most 16k characters")); } o_free(parameters); } if (json_object_get(j_module, "order_rank") != NULL && (!json_is_integer(json_object_get(j_module, "order_rank")) || json_integer_value(json_object_get(j_module, "order_rank")) < 0)) { json_array_append_new(j_error_list, json_string("order_rank is optional and must be a positive integer")); } if (json_object_get(j_module, "readonly") != NULL && !json_is_boolean(json_object_get(j_module, "readonly"))) { json_array_append_new(j_error_list, json_string("readonly is optional and must be a boolean")); } if (json_object_get(j_module, "multiple_passwords") != NULL && !json_is_boolean(json_object_get(j_module, "multiple_passwords"))) { json_array_append_new(j_error_list, json_string("multiple_passwords is optional and must be a boolean")); } if (json_array_size(j_error_list) > 0) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_error_list); } else { j_return = json_pack("{si}", "result", G_OK); } json_decref(j_error_list); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_user_module_valid - Error allocating resources for j_error_list"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "errors", "module must be a JSON object"); } return j_return; } json_t * add_user_module(struct config_elements * config, json_t * j_module) { struct _user_module * module; struct _user_module_instance * cur_instance; json_t * j_query; int res; size_t i; json_t * j_return, * j_result; char * parameters = json_dumps(json_object_get(j_module, "parameters"), JSON_COMPACT); j_query = json_pack("{sss{sOsOsOsisisiss}}", "table", GLEWLWYD_TABLE_USER_MODULE_INSTANCE, "values", "gumi_module", json_object_get(j_module, "module"), "gumi_name", json_object_get(j_module, "name"), "gumi_display_name", json_object_get(j_module, "display_name")!=NULL?json_object_get(j_module, "display_name"):json_null(), "gumi_readonly", json_object_get(j_module, "readonly")==json_true()?1:0, "gumi_multiple_passwords", json_object_get(j_module, "multiple_passwords")==json_true()?1:0, "gumi_enabled", 1, "gumi_parameters", parameters); if (json_object_get(j_module, "order_rank") != NULL) { json_object_set(json_object_get(j_query, "values"), "gumi_order", json_object_get(j_module, "order_rank")); } else { json_object_set_new(json_object_get(j_query, "values"), "gumi_order", json_integer(pointer_list_size(config->user_module_list))); } res = h_insert(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { module = NULL; for (i=0; i<pointer_list_size(config->user_module_list); i++) { module = (struct _user_module *)pointer_list_get_at(config->user_module_list, i); if (0 == o_strcmp(module->name, json_string_value(json_object_get(j_module, "module")))) { break; } else { module = NULL; } } if (module != NULL) { if (!pthread_mutex_lock(&config->module_lock)) { cur_instance = o_malloc(sizeof(struct _user_module_instance)); if (cur_instance != NULL) { cur_instance->cls = NULL; cur_instance->name = o_strdup(json_string_value(json_object_get(j_module, "name"))); cur_instance->module = module; cur_instance->enabled = 0; cur_instance->readonly = json_object_get(j_module, "readonly")==json_true()?1:0; cur_instance->multiple_passwords = json_object_get(j_module, "multiple_passwords")==json_true()?1:0; if (pointer_list_append(config->user_module_instance_list, cur_instance)) { j_result = module->user_module_init(config->config_m, cur_instance->readonly, cur_instance->multiple_passwords, json_object_get(j_module, "parameters"), &cur_instance->cls); if (check_result_value(j_result, G_OK)) { cur_instance->enabled = 1; j_return = json_pack("{si}", "result", G_OK); } else if (check_result_value(j_result, G_ERROR_PARAM)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", json_object_get(j_result, "error")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user_module - Error init module %s/%s", module->name, json_string_value(json_object_get(j_module, "name"))); j_return = json_pack("{sis[s]}", "result", G_ERROR, "error", "internal error"); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user_module - Error reallocating resources for user_module_instance_list"); o_free(cur_instance->name); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user_module - Error allocating resources for cur_instance"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user_module - Error pthread_mutex_lock"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user_module - Module '%s' not found", json_string_value(json_object_get(j_module, "module"))); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user_module - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } o_free(parameters); return j_return; } int set_user_module(struct config_elements * config, const char * name, json_t * j_module) { json_t * j_query; int res, ret; char * parameters = json_dumps(json_object_get(j_module, "parameters"), JSON_COMPACT); struct _user_module_instance * cur_instance; j_query = json_pack("{sss{sOsisisiss}s{ss}}", "table", GLEWLWYD_TABLE_USER_MODULE_INSTANCE, "set", "gumi_display_name", json_object_get(j_module, "display_name")!=NULL?json_object_get(j_module, "display_name"):json_null(), "gumi_readonly", json_object_get(j_module, "readonly")==json_true()?1:0, "gumi_enabled", json_object_get(j_module, "enabled")==json_false()?0:1, "gumi_multiple_passwords", json_object_get(j_module, "multiple_passwords")==json_true()?1:0, "gumi_parameters", parameters, "where", "gumi_name", name); if (json_object_get(j_module, "order_rank") != NULL) { json_object_set(json_object_get(j_query, "set"), "gumi_order", json_object_get(j_module, "order_rank")); } else { json_object_set_new(json_object_get(j_query, "set"), "gumi_order", json_integer(pointer_list_size(config->user_module_list))); } if (json_object_get(j_module, "readonly") != NULL) { json_object_set_new(json_object_get(j_query, "set"), "gumi_readonly", json_object_get(j_module, "readonly")==json_true()?json_integer(1):json_integer(0)); } res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { if (!pthread_mutex_lock(&config->module_lock)) { if ((cur_instance = get_user_module_instance(config, name)) != NULL) { cur_instance->readonly = json_object_get(j_module, "readonly")==json_true()?1:0; cur_instance->multiple_passwords = json_object_get(j_module, "multiple_passwords")==json_true()?1:0; ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_user_module - Error get_user_module_instance"); ret = G_ERROR; } pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_user_module - Error pthread_mutex_lock"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_user_module - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } o_free(parameters); return ret; } int delete_user_module(struct config_elements * config, const char * name) { int ret, res, error = 0; json_t * j_query, * j_result; struct _user_module_instance * instance; if (!pthread_mutex_lock(&config->module_lock)) { instance = get_user_module_instance(config, name); if (instance != NULL) { if (instance->enabled) { j_result = manage_user_module(config, name, GLEWLWYD_MODULE_ACTION_STOP); error = (!check_result_value(j_result, G_OK)); json_decref(j_result); } if (!error) { if (pointer_list_remove_pointer(config->user_module_instance_list, instance)) { o_free(instance->name); o_free(instance); j_query = json_pack("{sss{ss}}", "table", GLEWLWYD_TABLE_USER_MODULE_INSTANCE, "where", "gumi_name", name); res = h_delete(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_user_module - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_user_module - Error pointer_list_remove_pointer"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_user_module - Error manage_user_module"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_user_module - Error module not found"); ret = G_ERROR; } pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_user_module - Error pthread_mutex_lock"); ret = G_ERROR; } return ret; } json_t * manage_user_module(struct config_elements * config, const char * name, int action) { struct _user_module_instance * instance = get_user_module_instance(config, name); json_t * j_module = get_user_module(config, name), * j_return, * j_result; if (check_result_value(j_module, G_OK) && instance != NULL) { if (action == GLEWLWYD_MODULE_ACTION_START) { if (!instance->enabled) { if (!pthread_mutex_lock(&config->module_lock)) { j_result = instance->module->user_module_init(config->config_m, instance->readonly, instance->multiple_passwords, json_object_get(json_object_get(j_module, "module"), "parameters"), &instance->cls); if (check_result_value(j_result, G_OK)) { instance->enabled = 1; json_object_set(json_object_get(j_module, "module"), "enabled", json_true()); if (set_user_module(config, name, json_object_get(j_module, "module")) == G_OK) { j_return = json_pack("{si}", "result", G_OK); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_module - Error set_user_module module %s/%s", instance->module->name, json_string_value(json_object_get(json_object_get(j_module, "module"), "name"))); j_return = json_pack("{si}", "result", G_ERROR); } } else if (check_result_value(j_result, G_ERROR_PARAM)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", json_object_get(j_result, "error")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_module - Error init module %s/%s", instance->module->name, json_string_value(json_object_get(json_object_get(j_module, "module"), "name"))); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_module - Error pthread_mutex_lock (1)"); j_return = json_pack("{si}", "result", G_ERROR); } } else { j_return = json_pack("{si}", "result", G_OK); } } else if (action == GLEWLWYD_MODULE_ACTION_STOP) { if (instance->enabled) { if (!pthread_mutex_lock(&config->module_lock)) { if (instance->module->user_module_close(config->config_m, instance->cls) == G_OK) { instance->enabled = 0; json_object_set(json_object_get(j_module, "module"), "enabled", json_false()); if (set_user_module(config, name, json_object_get(j_module, "module")) == G_OK) { j_return = json_pack("{si}", "result", G_OK); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_module - Error set_user_module module %s/%s", instance->module->name, json_string_value(json_object_get(json_object_get(j_module, "module"), "name"))); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_module - Error close module %s/%s", instance->module->name, json_string_value(json_object_get(json_object_get(j_module, "module"), "name"))); j_return = json_pack("{si}", "result", G_ERROR); } pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_module - Error pthread_mutex_lock (2)"); j_return = json_pack("{si}", "result", G_ERROR); } } else { j_return = json_pack("{si}", "result", G_OK); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_module - Error action not found"); j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "Error action not found"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_module - Error module not found"); j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "Error module not found"); } json_decref(j_module); return j_return; } json_t * get_user_middleware_module_list(struct config_elements * config) { int res; json_t * j_query, * j_result = NULL, * j_return, * j_parameters, * j_element; size_t index; j_query = json_pack("{sss[ssssss]ss}", "table", GLEWLWYD_TABLE_USER_MIDDLEWARE_MODULE_INSTANCE, "columns", "gummi_module AS module", "gummi_name AS name", "gummi_display_name AS display_name", "gummi_parameters", "gummi_order AS order_rank", "gummi_enabled", "order_by", "gummi_order"); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result, index, j_element) { j_parameters = json_loads(json_string_value(json_object_get(j_element, "gummi_parameters")), JSON_DECODE_ANY, NULL); if (j_parameters != NULL) { json_object_set_new(j_element, "parameters", j_parameters); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_middleware_module_list - Error parsing parameters for module %s %s", json_string_value(json_object_get(j_element, "name")), json_string_value(json_object_get(j_element, "gummi_parameters"))); json_object_set_new(j_element, "parameters", json_null()); } json_object_del(j_element, "gummi_parameters"); json_object_set_new(j_element, "enabled", json_integer_value(json_object_get(j_element, "gummi_enabled"))?json_true():json_false()); json_object_del(j_element, "gummi_enabled"); } j_return = json_pack("{sisO}", "result", G_OK, "module", j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_middleware_module_list - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_result); return j_return; } json_t * get_user_middleware_module(struct config_elements * config, const char * name) { int res; json_t * j_query, * j_result = NULL, * j_return, * j_parameters; j_query = json_pack("{sss[ssssss]s{ss}}", "table", GLEWLWYD_TABLE_USER_MIDDLEWARE_MODULE_INSTANCE, "columns", "gummi_module AS module", "gummi_name AS name", "gummi_display_name AS display_name", "gummi_parameters", "gummi_order AS order_rank", "gummi_enabled", "where", "gummi_name", name); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result) > 0) { j_parameters = json_loads(json_string_value(json_object_get(json_array_get(j_result, 0), "gummi_parameters")), JSON_DECODE_ANY, NULL); if (j_parameters != NULL) { json_object_set_new(json_array_get(j_result, 0), "parameters", j_parameters); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_middleware_module - Error parsing parameters for module %s", json_string_value(json_object_get(json_array_get(j_result, 0), "name"))); json_object_set_new(json_array_get(j_result, 0), "parameters", json_null()); } json_object_del(json_array_get(j_result, 0), "gummi_parameters"); json_object_set_new(json_array_get(j_result, 0), "enabled", json_integer_value(json_object_get(json_array_get(j_result, 0), "gummi_enabled"))?json_true():json_false()); json_object_del(json_array_get(j_result, 0), "gummi_enabled"); j_return = json_pack("{sisO}", "result", G_OK, "module", json_array_get(j_result, 0)); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_middleware_module - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_result); return j_return; } json_t * is_user_middleware_module_valid(struct config_elements * config, json_t * j_module, int add) { json_t * j_return, * j_cur_module, * j_error_list; size_t i; int found; struct _user_middleware_module * module; char * parameters; if (j_module != NULL && json_is_object(j_module)) { if ((j_error_list = json_array()) != NULL) { if (add) { if (json_object_get(j_module, "name") != NULL && json_is_string(json_object_get(j_module, "name")) && json_string_length(json_object_get(j_module, "name")) > 0 && json_string_length(json_object_get(j_module, "name")) <= 128) { j_cur_module = get_user_middleware_module(config, json_string_value(json_object_get(j_module, "name"))); if (check_result_value(j_cur_module, G_OK)) { json_array_append_new(j_error_list, json_string("A module instance with this name already exist")); } else if (!check_result_value(j_cur_module, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_ERROR, "is_user_middleware_module_valid - Error json_array_append_new"); } json_decref(j_cur_module); } else { json_array_append_new(j_error_list, json_string("Module instance name is mandatory and must be a non empty string of at most 128 characters")); } if (json_object_get(j_module, "module") != NULL && json_is_string(json_object_get(j_module, "module")) && json_string_length(json_object_get(j_module, "module")) > 0 && json_string_length(json_object_get(j_module, "module")) <= 128) { found = 0; for (i=0; i<pointer_list_size(config->user_middleware_module_list); i++) { module = (struct _user_middleware_module *)pointer_list_get_at(config->user_middleware_module_list, i); if (module != NULL) { if (0 == o_strcmp(module->name, json_string_value(json_object_get(j_module, "module")))) { found = 1; break; } } } if (!found) { json_array_append_new(j_error_list, json_string("Module name doesn't exist")); } } else { json_array_append_new(j_error_list, json_string("Module is mandatory and must be a non empty string of at most 128 characters")); } } else { if (json_object_get(j_module, "enabled") != NULL && !json_is_boolean(json_object_get(j_module, "enabled"))) { json_array_append_new(j_error_list, json_string("enabled is optional and must be a boolean")); } } if (json_object_get(j_module, "display_name") != NULL && (!json_is_string(json_object_get(j_module, "display_name")) || json_string_length(json_object_get(j_module, "display_name")) > 256)) { json_array_append_new(j_error_list, json_string("display_name is optional and must be a string of at most 256 characters")); } if (json_object_get(j_module, "parameters") == NULL || !json_is_object(json_object_get(j_module, "parameters"))) { json_array_append_new(j_error_list, json_string("Parameters is mandatory and must be a json object of at most 16k characters")); } else { parameters = json_dumps(json_object_get(j_module, "parameters"), JSON_COMPACT); if (parameters == NULL || o_strlen(parameters) > 16*1024) { json_array_append_new(j_error_list, json_string("Parameters is mandatory and must be a json object of at most 16k characters")); } o_free(parameters); } if (json_object_get(j_module, "order_rank") != NULL && (!json_is_integer(json_object_get(j_module, "order_rank")) || json_integer_value(json_object_get(j_module, "order_rank")) < 0)) { json_array_append_new(j_error_list, json_string("order_rank is optional and must be a positive integer")); } if (json_array_size(j_error_list) > 0) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_error_list); } else { j_return = json_pack("{si}", "result", G_OK); } json_decref(j_error_list); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_user_middleware_module_valid - Error allocating resources for j_error_list"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "errors", "module must be a JSON object"); } return j_return; } json_t * add_user_middleware_module(struct config_elements * config, json_t * j_module) { json_t * j_query; int res; json_t * j_return; char * parameters = json_dumps(json_object_get(j_module, "parameters"), JSON_COMPACT); j_query = json_pack("{sss{sOsOsOsiss}}", "table", GLEWLWYD_TABLE_USER_MIDDLEWARE_MODULE_INSTANCE, "values", "gummi_module", json_object_get(j_module, "module"), "gummi_name", json_object_get(j_module, "name"), "gummi_display_name", json_object_get(j_module, "display_name")!=NULL?json_object_get(j_module, "display_name"):json_null(), "gummi_enabled", 1, "gummi_parameters", parameters); if (json_object_get(j_module, "order_rank") != NULL) { json_object_set(json_object_get(j_query, "values"), "gummi_order", json_object_get(j_module, "order_rank")); } else { json_object_set_new(json_object_get(j_query, "values"), "gummi_order", json_integer(pointer_list_size(config->user_middleware_module_list))); } res = h_insert(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { close_user_middleware_module_instance_list(config); j_return = json_pack("{si}", "result", load_user_middleware_module_instance_list(config)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user_middleware_module - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } o_free(parameters); return j_return; } int set_user_middleware_module(struct config_elements * config, const char * name, json_t * j_module) { json_t * j_query; int res, ret; char * parameters = json_dumps(json_object_get(j_module, "parameters"), JSON_COMPACT); j_query = json_pack("{sss{sOsiss}s{ss}}", "table", GLEWLWYD_TABLE_USER_MIDDLEWARE_MODULE_INSTANCE, "set", "gummi_display_name", json_object_get(j_module, "display_name")!=NULL?json_object_get(j_module, "display_name"):json_null(), "gummi_enabled", json_object_get(j_module, "enabled")==json_false()?0:1, "gummi_parameters", parameters, "where", "gummi_name", name); if (json_object_get(j_module, "order_rank") != NULL) { json_object_set(json_object_get(j_query, "set"), "gummi_order", json_object_get(j_module, "order_rank")); } else { json_object_set_new(json_object_get(j_query, "set"), "gummi_order", json_integer(pointer_list_size(config->user_middleware_module_list))); } res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { close_user_middleware_module_instance_list(config); load_user_middleware_module_instance_list(config); ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user_middleware_module - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } o_free(parameters); return ret; } int delete_user_middleware_module(struct config_elements * config, const char * name) { int ret, res; json_t * j_query; struct _user_middleware_module_instance * instance; instance = get_user_middleware_module_instance(config, name); if (instance != NULL) { j_query = json_pack("{sss{ss}}", "table", GLEWLWYD_TABLE_USER_MIDDLEWARE_MODULE_INSTANCE, "where", "gummi_name", name); res = h_delete(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { close_user_middleware_module_instance_list(config); load_user_middleware_module_instance_list(config); ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_user_middleware_module - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_user_middleware_module - Error module not found"); ret = G_ERROR; } return ret; } json_t * manage_user_middleware_module(struct config_elements * config, const char * name, int action) { struct _user_middleware_module_instance * instance = get_user_middleware_module_instance(config, name); json_t * j_module = get_user_middleware_module(config, name), * j_return, * j_result; if (check_result_value(j_module, G_OK) && instance != NULL) { if (action == GLEWLWYD_MODULE_ACTION_START) { if (!instance->enabled) { if (!pthread_mutex_lock(&config->module_lock)) { j_result = instance->module->user_middleware_module_init(config->config_m, json_object_get(json_object_get(j_module, "module"), "parameters"), &instance->cls); if (check_result_value(j_result, G_OK)) { instance->enabled = 1; json_object_set(json_object_get(j_module, "module"), "enabled", json_true()); if (set_user_middleware_module(config, name, json_object_get(j_module, "module")) == G_OK) { j_return = json_pack("{si}", "result", G_OK); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_middleware_module - Error set_user_middleware_module module %s/%s", instance->module->name, json_string_value(json_object_get(json_object_get(j_module, "module"), "name"))); j_return = json_pack("{si}", "result", G_ERROR); } } else if (check_result_value(j_result, G_ERROR_PARAM)) { j_return = json_pack("{sisO*}", "result", G_ERROR_PARAM, "error", json_object_get(j_result, "error")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_middleware_module - Error init module %s/%s", instance->module->name, json_string_value(json_object_get(json_object_get(j_module, "module"), "name"))); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_middleware_module - Error pthread_mutex_lock (1)"); j_return = json_pack("{si}", "result", G_ERROR); } } else { j_return = json_pack("{si}", "result", G_OK); } } else if (action == GLEWLWYD_MODULE_ACTION_STOP) { if (instance->enabled) { if (!pthread_mutex_lock(&config->module_lock)) { if (instance->module->user_middleware_module_close(config->config_m, instance->cls) == G_OK) { instance->enabled = 0; json_object_set(json_object_get(j_module, "module"), "enabled", json_false()); if (set_user_middleware_module(config, name, json_object_get(j_module, "module")) == G_OK) { j_return = json_pack("{si}", "result", G_OK); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_middleware_module - Error set_user_middleware_module module %s/%s", instance->module->name, json_string_value(json_object_get(json_object_get(j_module, "module"), "name"))); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_middleware_module - Error close module %s/%s", instance->module->name, json_string_value(json_object_get(json_object_get(j_module, "module"), "name"))); j_return = json_pack("{si}", "result", G_ERROR); } pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_middleware_module - Error pthread_mutex_lock (2)"); j_return = json_pack("{si}", "result", G_ERROR); } } else { j_return = json_pack("{si}", "result", G_OK); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_middleware_module - Error action not found"); j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "Error action not found"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_middleware_module - Error module not found"); j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "Error module not found"); } json_decref(j_module); return j_return; } json_t * get_user_auth_scheme_module_list(struct config_elements * config) { int res; json_t * j_query, * j_result = NULL, * j_return, * j_parameters, * j_element; size_t index; j_query = json_pack("{sss[ssssssssss]ss}", "table", GLEWLWYD_TABLE_USER_AUTH_SCHEME_MODULE_INSTANCE, "columns", "guasmi_module AS module", "guasmi_name AS name", "guasmi_display_name AS display_name", "guasmi_parameters", "guasmi_expiration AS expiration", "guasmi_max_use AS max_use", "guasmi_allow_user_register", "guasmi_forbid_user_profile", "guasmi_forbid_user_reset_credential", "guasmi_enabled", "order_by", "guasmi_module"); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result, index, j_element) { j_parameters = json_loads(json_string_value(json_object_get(j_element, "guasmi_parameters")), JSON_DECODE_ANY, NULL); if (j_parameters != NULL) { json_object_set_new(j_element, "parameters", j_parameters); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_auth_scheme_module_list - Error parsing parameters for module %s", json_string_value(json_object_get(j_element, "name"))); json_object_set_new(j_element, "parameters", json_null()); } json_object_set(j_element, "allow_user_register", json_integer_value(json_object_get(j_element, "guasmi_allow_user_register"))?json_true():json_false()); json_object_set(j_element, "forbid_user_profile", json_integer_value(json_object_get(j_element, "guasmi_forbid_user_profile"))?json_true():json_false()); json_object_set(j_element, "forbid_user_reset_credential", json_integer_value(json_object_get(j_element, "guasmi_forbid_user_reset_credential"))?json_true():json_false()); json_object_del(j_element, "guasmi_parameters"); json_object_del(j_element, "guasmi_allow_user_register"); json_object_del(j_element, "guasmi_forbid_user_profile"); json_object_del(j_element, "guasmi_forbid_user_reset_credential"); json_object_set_new(j_element, "enabled", json_integer_value(json_object_get(j_element, "guasmi_enabled"))?json_true():json_false()); json_object_del(j_element, "guasmi_enabled"); } j_return = json_pack("{sisO}", "result", G_OK, "module", j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_auth_scheme_module_list - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_result); return j_return; } json_t * get_user_auth_scheme_module(struct config_elements * config, const char * name) { int res; json_t * j_query, * j_result = NULL, * j_return, * j_parameters; if (o_strlen(name)) { j_query = json_pack("{sss[ssssssssss]s{ss}}", "table", GLEWLWYD_TABLE_USER_AUTH_SCHEME_MODULE_INSTANCE, "columns", "guasmi_module AS module", "guasmi_name AS name", "guasmi_display_name AS display_name", "guasmi_parameters", "guasmi_expiration AS expiration", "guasmi_max_use AS max_use", "guasmi_allow_user_register", "guasmi_forbid_user_profile", "guasmi_forbid_user_reset_credential", "guasmi_enabled", "where", "guasmi_name", name); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result) > 0) { j_parameters = json_loads(json_string_value(json_object_get(json_array_get(j_result, 0), "guasmi_parameters")), JSON_DECODE_ANY, NULL); if (j_parameters != NULL) { json_object_set_new(json_array_get(j_result, 0), "parameters", j_parameters); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_auth_scheme_module_list - Error parsing parameters for module %s", json_string_value(json_object_get(json_array_get(j_result, 0), "name"))); json_object_set_new(json_array_get(j_result, 0), "parameters", json_null()); } json_object_set(json_array_get(j_result, 0), "allow_user_register", json_integer_value(json_object_get(json_array_get(j_result, 0), "guasmi_allow_user_register"))?json_true():json_false()); json_object_set(json_array_get(j_result, 0), "forbid_user_profile", json_integer_value(json_object_get(json_array_get(j_result, 0), "guasmi_forbid_user_profile"))?json_true():json_false()); json_object_set(json_array_get(j_result, 0), "forbid_user_reset_credential", json_integer_value(json_object_get(json_array_get(j_result, 0), "guasmi_forbid_user_reset_credential"))?json_true():json_false()); json_object_del(json_array_get(j_result, 0), "guasmi_parameters"); json_object_del(json_array_get(j_result, 0), "guasmi_allow_user_register"); json_object_del(json_array_get(j_result, 0), "guasmi_forbid_user_profile"); json_object_del(json_array_get(j_result, 0), "guasmi_forbid_user_reset_credential"); json_object_set_new(json_array_get(j_result, 0), "enabled", json_integer_value(json_object_get(json_array_get(j_result, 0), "guasmi_enabled"))?json_true():json_false()); json_object_del(json_array_get(j_result, 0), "guasmi_enabled"); j_return = json_pack("{sisO}", "result", G_OK, "module", json_array_get(j_result, 0)); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_auth_scheme_module_list - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } json_t * is_user_auth_scheme_module_valid(struct config_elements * config, json_t * j_module, int add) { json_t * j_return, * j_cur_module, * j_error_list; size_t i; int found; struct _user_auth_scheme_module * module; char * parameters; if (j_module != NULL && json_is_object(j_module)) { if ((j_error_list = json_array()) != NULL) { if (add) { if (json_object_get(j_module, "name") != NULL && json_is_string(json_object_get(j_module, "name")) && json_string_length(json_object_get(j_module, "name")) > 0 && json_string_length(json_object_get(j_module, "name")) <= 128) { j_cur_module = get_user_auth_scheme_module(config, json_string_value(json_object_get(j_module, "name"))); if (check_result_value(j_cur_module, G_OK)) { json_array_append_new(j_error_list, json_string("A module instance with this name already exist")); } else if (!check_result_value(j_cur_module, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_ERROR, "is_user_auth_scheme_module_valid - Error json_array_append_new"); } json_decref(j_cur_module); } else { json_array_append_new(j_error_list, json_string("Module instance name is mandatory and must be a non empty string of at most 128 characters")); } if (json_object_get(j_module, "module") != NULL && json_is_string(json_object_get(j_module, "module")) && json_string_length(json_object_get(j_module, "module")) > 0 && json_string_length(json_object_get(j_module, "module")) <= 128) { found = 0; for (i=0; i<pointer_list_size(config->user_auth_scheme_module_list); i++) { module = (struct _user_auth_scheme_module *)pointer_list_get_at(config->user_auth_scheme_module_list, i); if (module != NULL) { if (0 == o_strcmp(module->name, json_string_value(json_object_get(j_module, "module")))) { found = 1; break; } } } if (!found) { json_array_append_new(j_error_list, json_string("Module name doesn't exist")); } } else { json_array_append_new(j_error_list, json_string("Module is mandatory and must be a non empty string of at most 128 characters")); } } else { if (json_object_get(j_module, "enabled") != NULL && !json_is_boolean(json_object_get(j_module, "enabled"))) { json_array_append_new(j_error_list, json_string("enabled is optional and must be a boolean")); } } if (json_object_get(j_module, "display_name") != NULL && (!json_is_string(json_object_get(j_module, "display_name")) || json_string_length(json_object_get(j_module, "display_name")) > 256)) { json_array_append_new(j_error_list, json_string("display_name is optional and must be a string of at most 256 characters")); } if (json_string_length(json_object_get(j_module, "expiration")) || !json_is_integer(json_object_get(j_module, "expiration")) || json_integer_value(json_object_get(j_module, "expiration")) <= 0) { json_array_append_new(j_error_list, json_string("expiration is mandatory and must be a non null positive integer")); } if (json_object_get(j_module, "max_use") == NULL || !json_is_integer(json_object_get(j_module, "max_use")) || json_integer_value(json_object_get(j_module, "max_use")) < 0) { json_array_append_new(j_error_list, json_string("max_use is mandatory and must be a positive integer")); } if (json_object_get(j_module, "allow_user_register") != NULL && !json_is_boolean(json_object_get(j_module, "allow_user_register"))) { json_array_append_new(j_error_list, json_string("allow_user_register is optional and must be a boolean")); } if (json_object_get(j_module, "parameters") == NULL || !json_is_object(json_object_get(j_module, "parameters"))) { json_array_append_new(j_error_list, json_string("Parameters is mandatory and must be a json object of at most 16k characters")); } else { parameters = json_dumps(json_object_get(j_module, "parameters"), JSON_COMPACT); if (parameters == NULL || o_strlen(parameters) > 16*1024) { json_array_append_new(j_error_list, json_string("Parameters is mandatory and must be a json object of at most 16k characters")); } o_free(parameters); } if (json_array_size(j_error_list) > 0) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_error_list); } else { j_return = json_pack("{si}", "result", G_OK); } json_decref(j_error_list); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_user_auth_scheme_module_valid - Error allocating resources for j_error_list"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "errors", "module must be a JSON object"); } return j_return; } json_t * add_user_auth_scheme_module(struct config_elements * config, json_t * j_module) { struct _user_auth_scheme_module * module; struct _user_auth_scheme_module_instance * cur_instance; json_t * j_query, * j_last_id, * j_result, * j_return; int res; size_t i; char * parameters = json_dumps(json_object_get(j_module, "parameters"), JSON_COMPACT); j_query = json_pack("{sss{sOsOsOsssOsOsisisisi}}", "table", GLEWLWYD_TABLE_USER_AUTH_SCHEME_MODULE_INSTANCE, "values", "guasmi_module", json_object_get(j_module, "module"), "guasmi_name", json_object_get(j_module, "name"), "guasmi_display_name", json_object_get(j_module, "display_name")!=NULL?json_object_get(j_module, "display_name"):json_null(), "guasmi_parameters", parameters, "guasmi_expiration", json_object_get(j_module, "expiration"), "guasmi_max_use", json_object_get(j_module, "max_use"), "guasmi_allow_user_register", json_object_get(j_module, "allow_user_register")==json_false()?0:1, "guasmi_forbid_user_profile", json_object_get(j_module, "forbid_user_profile")==json_true()?1:0, "guasmi_forbid_user_reset_credential", json_object_get(j_module, "forbid_user_reset_credential")==json_true()?1:0, "guasmi_enabled", 1); res = h_insert(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_last_id = h_last_insert_id(config->conn); if (j_last_id != NULL) { module = NULL; for (i=0; i<pointer_list_size(config->user_auth_scheme_module_list); i++) { module = (struct _user_auth_scheme_module *)pointer_list_get_at(config->user_auth_scheme_module_list, i); if (0 == o_strcmp(module->name, json_string_value(json_object_get(j_module, "module")))) { break; } else { module = NULL; } } if (module != NULL) { if (!pthread_mutex_lock(&config->module_lock)) { cur_instance = o_malloc(sizeof(struct _user_auth_scheme_module_instance)); if (cur_instance != NULL) { cur_instance->cls = NULL; cur_instance->name = o_strdup(json_string_value(json_object_get(j_module, "name"))); cur_instance->module = module; cur_instance->guasmi_id = json_integer_value(j_last_id); cur_instance->guasmi_expiration = json_integer_value(json_object_get(j_module, "expiration")); cur_instance->guasmi_max_use = json_integer_value(json_object_get(j_module, "max_use")); cur_instance->guasmi_allow_user_register = json_object_get(j_module, "allow_user_register")!=json_false(); cur_instance->guasmi_forbid_user_profile = json_object_get(j_module, "forbid_user_profile")==json_true(); cur_instance->guasmi_forbid_user_reset_credential = json_object_get(j_module, "forbid_user_reset_credential")==json_true(); cur_instance->enabled = 0; if (pointer_list_append(config->user_auth_scheme_module_instance_list, cur_instance)) { j_result = module->user_auth_scheme_module_init(config->config_m, json_object_get(j_module, "parameters"), cur_instance->name, &cur_instance->cls); if (check_result_value(j_result, G_OK)) { glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_AUTH_USER_VALID_SCHEME, 0, "scheme_type", module->name, "scheme_name", cur_instance->name, NULL); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_AUTH_USER_INVALID_SCHEME, 0, "scheme_type", module->name, "scheme_name", cur_instance->name, NULL); cur_instance->enabled = 1; j_return = json_pack("{si}", "result", G_OK); } else if (check_result_value(j_result, G_ERROR_PARAM)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", json_object_get(j_result, "error")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_auth_scheme_module - Error init module %s/%s", module->name, json_string_value(json_object_get(j_module, "name"))); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user_auth_scheme_module - Error reallocating resources for user_auth_scheme_module_instance_list"); o_free(cur_instance->name); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user_auth_scheme_module - Error allocating resources for cur_instance"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user_auth_scheme_module - Error pthread_mutex_lock"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user_auth_scheme_module - Module '%s' not found", json_string_value(json_object_get(j_module, "module"))); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user_auth_scheme_module - Error h_last_insert_id"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_last_id); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user_auth_scheme_module - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } o_free(parameters); return j_return; } int set_user_auth_scheme_module(struct config_elements * config, const char * name, json_t * j_module) { json_t * j_query; int res, ret; char * parameters = json_dumps(json_object_get(j_module, "parameters"), JSON_COMPACT); struct _user_auth_scheme_module_instance * scheme_instance = NULL; j_query = json_pack("{sss{sOsssOsOsisisisi}s{ss}}", "table", GLEWLWYD_TABLE_USER_AUTH_SCHEME_MODULE_INSTANCE, "set", "guasmi_display_name", json_object_get(j_module, "display_name")!=NULL?json_object_get(j_module, "display_name"):json_null(), "guasmi_parameters", parameters, "guasmi_expiration", json_object_get(j_module, "expiration"), "guasmi_max_use", json_object_get(j_module, "max_use"), "guasmi_allow_user_register", json_object_get(j_module, "allow_user_register")==json_false()?0:1, "guasmi_forbid_user_profile", json_object_get(j_module, "forbid_user_profile")==json_true()?1:0, "guasmi_forbid_user_reset_credential", json_object_get(j_module, "forbid_user_reset_credential")==json_true()?1:0, "guasmi_enabled", json_object_get(j_module, "enabled")==json_false()?0:1, "where", "guasmi_name", name); o_free(parameters); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { if (!pthread_mutex_lock(&config->module_lock)) { scheme_instance = get_user_auth_scheme_module_instance(config, name); if (scheme_instance != NULL) { scheme_instance->guasmi_expiration = json_integer_value(json_object_get(j_module, "expiration")); scheme_instance->guasmi_max_use = json_integer_value(json_object_get(j_module, "max_use")); scheme_instance->guasmi_allow_user_register = json_object_get(j_module, "allow_user_register")!=json_false(); scheme_instance->guasmi_forbid_user_profile = json_object_get(j_module, "forbid_user_profile")==json_true(); scheme_instance->guasmi_forbid_user_reset_credential = json_object_get(j_module, "forbid_user_reset_credential")==json_true(); ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_user_auth_scheme_module - Error get_user_auth_scheme_module_instance"); ret = G_ERROR; } pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_user_auth_scheme_module - Error pthread_mutex_lock"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_user_auth_scheme_module - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } int delete_user_auth_scheme_module(struct config_elements * config, const char * name) { int ret, res; json_t * j_query, * j_result = manage_user_auth_scheme_module(config, name, GLEWLWYD_MODULE_ACTION_STOP); struct _user_auth_scheme_module_instance * instance; if (check_result_value(j_result, G_OK)) { if (!pthread_mutex_lock(&config->module_lock)) { instance = get_user_auth_scheme_module_instance(config, name); if (pointer_list_remove_pointer(config->user_auth_scheme_module_instance_list, instance)) { o_free(instance->name); o_free(instance); j_query = json_pack("{sss{ss}}", "table", GLEWLWYD_TABLE_USER_AUTH_SCHEME_MODULE_INSTANCE, "where", "guasmi_name", name); res = h_delete(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_user_auth_scheme_module - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_user_auth_scheme_module - Error pointer_list_remove_pointer"); ret = G_ERROR; } pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_user_auth_scheme_module - Error pthread_mutex_lock"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_user_auth_scheme_module - Error action not found"); ret = G_ERROR; } json_decref(j_result); return ret; } json_t * manage_user_auth_scheme_module(struct config_elements * config, const char * name, int action) { struct _user_auth_scheme_module_instance * instance = get_user_auth_scheme_module_instance(config, name); json_t * j_module = get_user_auth_scheme_module(config, name), * j_result, * j_return; if (check_result_value(j_module, G_OK) && instance != NULL) { if (action == GLEWLWYD_MODULE_ACTION_START) { if (!instance->enabled) { if (!pthread_mutex_lock(&config->module_lock)) { j_result = instance->module->user_auth_scheme_module_init(config->config_m, json_object_get(json_object_get(j_module, "module"), "parameters"), instance->name, &instance->cls); if (check_result_value(j_result, G_OK)) { glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_AUTH_USER_VALID_SCHEME, 0, "scheme_type", instance->module->name, "scheme_name", instance->name, NULL); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_AUTH_USER_INVALID_SCHEME, 0, "scheme_type", instance->module->name, "scheme_name", instance->name, NULL); instance->enabled = 1; json_object_set(json_object_get(j_module, "module"), "enabled", json_true()); if (set_user_auth_scheme_module(config, name, json_object_get(j_module, "module")) == G_OK) { j_return = json_pack("{si}", "result", G_OK); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_auth_scheme_module - Error set_user_auth_scheme_module module %s/%s", instance->module->name, json_string_value(json_object_get(json_object_get(j_module, "module"), "name"))); j_return = json_pack("{si}", "result", G_ERROR); } } else if (check_result_value(j_result, G_ERROR_PARAM)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", json_object_get(j_result, "error")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_auth_scheme_module - Error init module %s/%s", instance->module->name, json_string_value(json_object_get(json_object_get(j_module, "module"), "name"))); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_auth_scheme_module - Error pthread_mutex_lock (1)"); j_return = json_pack("{si}", "result", G_ERROR); } } else { j_return = json_pack("{si}", "result", G_OK); } } else if (action == GLEWLWYD_MODULE_ACTION_STOP) { if (instance->enabled) { if (!pthread_mutex_lock(&config->module_lock)) { if (instance->module->user_auth_scheme_module_close(config->config_m, instance->cls) == G_OK) { instance->enabled = 0; json_object_set(json_object_get(j_module, "module"), "enabled", json_false()); if (set_user_auth_scheme_module(config, name, json_object_get(j_module, "module")) == G_OK) { j_return = json_pack("{si}", "result", G_OK); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_auth_scheme_module - Error set_user_auth_scheme_module module %s/%s", instance->module->name, json_string_value(json_object_get(json_object_get(j_module, "module"), "name"))); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_auth_scheme_module - Error close module %s/%s", instance->module->name, json_string_value(json_object_get(json_object_get(j_module, "module"), "name"))); j_return = json_pack("{si}", "result", G_ERROR); } pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_auth_scheme_module - Error pthread_mutex_lock (2)"); j_return = json_pack("{si}", "result", G_ERROR); } } else { j_return = json_pack("{si}", "result", G_OK); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_auth_scheme_module - Error action not found"); j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "action not found"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_user_auth_scheme_module - Error module not found"); j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "action not found"); } json_decref(j_module); return j_return; } json_t * get_client_module_list(struct config_elements * config) { int res; json_t * j_query, * j_result = NULL, * j_return, * j_parameters, * j_element; size_t index; j_query = json_pack("{sss[sssssss]ss}", "table", GLEWLWYD_TABLE_CLIENT_MODULE_INSTANCE, "columns", "gcmi_module AS module", "gcmi_name AS name", "gcmi_display_name AS display_name", "gcmi_parameters", "gcmi_order AS order_rank", "gcmi_readonly", "gcmi_enabled", "order_by", "gcmi_order"); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result, index, j_element) { j_parameters = json_loads(json_string_value(json_object_get(j_element, "gcmi_parameters")), JSON_DECODE_ANY, NULL); if (j_parameters != NULL) { json_object_set_new(j_element, "parameters", j_parameters); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_module_list - Error parsing parameters for module %s", json_string_value(json_object_get(j_element, "name"))); json_object_set_new(j_element, "parameters", json_null()); } json_object_del(j_element, "gcmi_parameters"); json_object_set_new(j_element, "readonly", json_integer_value(json_object_get(j_element, "gcmi_readonly"))?json_true():json_false()); json_object_del(j_element, "gcmi_readonly"); json_object_set_new(j_element, "enabled", json_integer_value(json_object_get(j_element, "gcmi_enabled"))?json_true():json_false()); json_object_del(j_element, "gcmi_enabled"); } j_return = json_pack("{sisO}", "result", G_OK, "module", j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_module_list - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_result); return j_return; } json_t * get_client_module(struct config_elements * config, const char * name) { int res; json_t * j_query, * j_result = NULL, * j_return, * j_parameters; j_query = json_pack("{sss[sssssss]s{ss}}", "table", GLEWLWYD_TABLE_CLIENT_MODULE_INSTANCE, "columns", "gcmi_module AS module", "gcmi_name AS name", "gcmi_display_name AS display_name", "gcmi_parameters", "gcmi_order AS order_rank", "gcmi_readonly", "gcmi_enabled", "where", "gcmi_name", name); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result) > 0) { j_parameters = json_loads(json_string_value(json_object_get(json_array_get(j_result, 0), "gcmi_parameters")), JSON_DECODE_ANY, NULL); if (j_parameters != NULL) { json_object_set_new(json_array_get(j_result, 0), "parameters", j_parameters); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_module_list - Error parsing parameters for module %s", json_string_value(json_object_get(json_array_get(j_result, 0), "name"))); json_object_set_new(json_array_get(j_result, 0), "parameters", json_null()); } json_object_del(json_array_get(j_result, 0), "gcmi_parameters"); json_object_set_new(json_array_get(j_result, 0), "readonly", json_integer_value(json_object_get(json_array_get(j_result, 0), "gcmi_readonly"))?json_true():json_false()); json_object_del(json_array_get(j_result, 0), "gcmi_readonly"); json_object_set_new(json_array_get(j_result, 0), "enabled", json_integer_value(json_object_get(json_array_get(j_result, 0), "gcmi_enabled"))?json_true():json_false()); json_object_del(json_array_get(j_result, 0), "gcmi_enabled"); j_return = json_pack("{sisO}", "result", G_OK, "module", json_array_get(j_result, 0)); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_module_list - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_result); return j_return; } json_t * is_client_module_valid(struct config_elements * config, json_t * j_module, int add) { json_t * j_return, * j_cur_module, * j_error_list; size_t i;; int found; struct _client_module * module; char * parameters; if (j_module != NULL && json_is_object(j_module)) { if ((j_error_list = json_array()) != NULL) { if (add) { if (json_object_get(j_module, "name") != NULL && json_is_string(json_object_get(j_module, "name")) && json_string_length(json_object_get(j_module, "name")) > 0 && json_string_length(json_object_get(j_module, "name")) <= 128) { j_cur_module = get_client_module(config, json_string_value(json_object_get(j_module, "name"))); if (check_result_value(j_cur_module, G_OK)) { json_array_append_new(j_error_list, json_string("A module instance with this name already exist")); } else if (!check_result_value(j_cur_module, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_ERROR, "is_client_module_valid - Error json_array_append_new"); } json_decref(j_cur_module); } else { json_array_append_new(j_error_list, json_string("Module instance name is mandatory and must be a non empty string of at most 128 characters")); } if (json_object_get(j_module, "module") != NULL && json_is_string(json_object_get(j_module, "module")) && json_string_length(json_object_get(j_module, "module")) > 0 && json_string_length(json_object_get(j_module, "module")) <= 128) { found = 0; for (i=0; i<pointer_list_size(config->client_module_list); i++) { module = (struct _client_module *)pointer_list_get_at(config->client_module_list, i); if (module != NULL) { if (0 == o_strcmp(module->name, json_string_value(json_object_get(j_module, "module")))) { found = 1; break; } } } if (!found) { json_array_append_new(j_error_list, json_string("Module name doesn't exist")); } } else { json_array_append_new(j_error_list, json_string("Module is mandatory and must be a non empty string of at most 128 characters")); } } else { if (json_object_get(j_module, "enabled") != NULL && !json_is_boolean(json_object_get(j_module, "enabled"))) { json_array_append_new(j_error_list, json_string("enabled is optional and must be a boolean")); } } if (json_object_get(j_module, "display_name") != NULL && (!json_is_string(json_object_get(j_module, "display_name")) || json_string_length(json_object_get(j_module, "display_name")) > 256)) { json_array_append_new(j_error_list, json_string("display_name is optional and must be a string of at most 256 characters")); } if (json_object_get(j_module, "parameters") == NULL || !json_is_object(json_object_get(j_module, "parameters"))) { json_array_append_new(j_error_list, json_string("Parameters is mandatory and must be a json object of at most 16k characters")); } else { parameters = json_dumps(json_object_get(j_module, "parameters"), JSON_COMPACT); if (parameters == NULL || o_strlen(parameters) > 16*1024) { json_array_append_new(j_error_list, json_string("Parameters is mandatory and must be a json object of at most 16k characters")); } o_free(parameters); } if (json_object_get(j_module, "order_rank") != NULL && (!json_is_integer(json_object_get(j_module, "order_rank")) || json_integer_value(json_object_get(j_module, "order_rank")) < 0)) { json_array_append_new(j_error_list, json_string("order_rank is optional and must be a positive integer")); } if (json_object_get(j_module, "readonly") != NULL && !json_is_boolean(json_object_get(j_module, "readonly"))) { json_array_append_new(j_error_list, json_string("readonly is optional and must be a boolean")); } if (json_array_size(j_error_list) > 0) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_error_list); } else { j_return = json_pack("{si}", "result", G_OK); } json_decref(j_error_list); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_client_module_valid - Error allocating resources for j_error_list"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "errors", "module must be a JSON object"); } return j_return; } json_t * add_client_module(struct config_elements * config, json_t * j_module) { struct _client_module * module; struct _client_module_instance * cur_instance; json_t * j_query, * j_result, * j_return; int res; size_t i; char * parameters = json_dumps(json_object_get(j_module, "parameters"), JSON_COMPACT); j_query = json_pack("{sss{sOsOsOsisiss}}", "table", GLEWLWYD_TABLE_CLIENT_MODULE_INSTANCE, "values", "gcmi_module", json_object_get(j_module, "module"), "gcmi_name", json_object_get(j_module, "name"), "gcmi_display_name", json_object_get(j_module, "display_name")!=NULL?json_object_get(j_module, "display_name"):json_null(), "gcmi_readonly", json_object_get(j_module, "readonly")==json_true()?1:0, "gcmi_enabled", 1, "gcmi_parameters", parameters); if (json_object_get(j_module, "order_rank") != NULL) { json_object_set(json_object_get(j_query, "values"), "gcmi_order", json_object_get(j_module, "order_rank")); } else { json_object_set_new(json_object_get(j_query, "values"), "gcmi_order", json_integer(pointer_list_size(config->client_module_list))); } res = h_insert(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { module = NULL; for (i=0; i<pointer_list_size(config->client_module_list); i++) { module = (struct _client_module *)pointer_list_get_at(config->client_module_list, i); if (0 == o_strcmp(module->name, json_string_value(json_object_get(j_module, "module")))) { break; } else { module = NULL; } } if (module != NULL) { if (!pthread_mutex_lock(&config->module_lock)) { cur_instance = o_malloc(sizeof(struct _client_module_instance)); if (cur_instance != NULL) { cur_instance->cls = NULL; cur_instance->name = o_strdup(json_string_value(json_object_get(j_module, "name"))); cur_instance->module = module; cur_instance->enabled = 0; cur_instance->readonly = json_object_get(j_module, "readonly")==json_true()?1:0; if (pointer_list_append(config->client_module_instance_list, cur_instance)) { j_result = module->client_module_init(config->config_m, cur_instance->readonly, json_object_get(j_module, "parameters"), &cur_instance->cls); if (check_result_value(j_result, G_OK)) { cur_instance->enabled = 1; j_return = json_pack("{si}", "result", G_OK); } else if (check_result_value(j_result, G_ERROR_PARAM)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", json_object_get(j_result, "error")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_client_module - Error init module %s/%s", module->name, json_string_value(json_object_get(j_module, "name"))); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_client_module - Error reallocating resources for client_module_instance_list"); o_free(cur_instance->name); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_client_module - Error allocating resources for cur_instance"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_client_module - Error pthread_mutex_lock"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_client_module - Module '%s' not found", json_string_value(json_object_get(j_module, "module"))); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_client_module - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } o_free(parameters); return j_return; } int set_client_module(struct config_elements * config, const char * name, json_t * j_module) { json_t * j_query; size_t res; int ret; char * parameters = json_dumps(json_object_get(j_module, "parameters"), JSON_COMPACT); struct _client_module_instance * cur_instance; j_query = json_pack("{sss{sOsisiss}s{ss}}", "table", GLEWLWYD_TABLE_CLIENT_MODULE_INSTANCE, "set", "gcmi_display_name", json_object_get(j_module, "display_name")!=NULL?json_object_get(j_module, "display_name"):json_null(), "gcmi_readonly", json_object_get(j_module, "readonly")==json_true()?1:0, "gcmi_enabled", json_object_get(j_module, "enabled")==json_false()?0:1, "gcmi_parameters", parameters, "where", "gcmi_name", name); if (json_object_get(j_module, "order_rank") != NULL) { json_object_set(json_object_get(j_query, "set"), "gcmi_order", json_object_get(j_module, "order_rank")); } else { json_object_set_new(json_object_get(j_query, "set"), "gcmi_order", json_integer(pointer_list_size(config->client_module_list))); } if (json_object_get(j_module, "readonly") != NULL) { json_object_set_new(json_object_get(j_query, "set"), "gcmi_readonly", json_object_get(j_module, "readonly")==json_true()?json_integer(1):json_integer(0)); } o_free(parameters); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { if (!pthread_mutex_lock(&config->module_lock)) { if ((cur_instance = get_client_module_instance(config, name)) != NULL) { cur_instance->readonly = json_object_get(j_module, "readonly")==json_true()?1:0; ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_client_module - Error get_user_module_instance"); ret = G_ERROR; } pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_client_module - Error pthread_mutex_lock"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_client_module - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } int delete_client_module(struct config_elements * config, const char * name) { int ret, res, error = 0; json_t * j_query, * j_result; struct _client_module_instance * instance; instance = get_client_module_instance(config, name); if (instance != NULL) { if (instance->enabled) { j_result = manage_client_module(config, name, GLEWLWYD_MODULE_ACTION_STOP); error = (!check_result_value(j_result, G_OK)); json_decref(j_result); } if (!error) { if (!pthread_mutex_lock(&config->module_lock)) { if (pointer_list_remove_pointer(config->client_module_instance_list, instance)) { o_free(instance->name); o_free(instance); j_query = json_pack("{sss{ss}}", "table", GLEWLWYD_TABLE_CLIENT_MODULE_INSTANCE, "where", "gcmi_name", name); res = h_delete(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_client_module - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_client_module - Error pointer_list_remove_pointer"); ret = G_ERROR; } pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_client_module - Error pthread_mutex_lock"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_client_module - Error manage_client_module"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_client_module - Error instance not found"); ret = G_ERROR; } return ret; } json_t * manage_client_module(struct config_elements * config, const char * name, int action) { struct _client_module_instance * instance = get_client_module_instance(config, name); json_t * j_module = get_client_module(config, name), * j_return, * j_result; if (check_result_value(j_module, G_OK) && instance != NULL) { if (action == GLEWLWYD_MODULE_ACTION_START) { if (!instance->enabled) { if (!pthread_mutex_lock(&config->module_lock)) { j_result = instance->module->client_module_init(config->config_m, instance->readonly, json_object_get(json_object_get(j_module, "module"), "parameters"), &instance->cls); if (check_result_value(j_result, G_OK)) { instance->enabled = 1; json_object_set(json_object_get(j_module, "module"), "enabled", json_true()); if (set_client_module(config, name, json_object_get(j_module, "module")) == G_OK) { j_return = json_pack("{si}", "result", G_OK); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_client_module - Error set_client_module module %s/%s", instance->module->name, json_string_value(json_object_get(json_object_get(j_module, "module"), "name"))); j_return = json_pack("{si}", "result", G_ERROR); } } else if (check_result_value(j_result, G_ERROR_PARAM)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", json_object_get(j_result, "error")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_client_module - Error init module %s/%s", instance->module->name, json_string_value(json_object_get(json_object_get(j_module, "module"), "name"))); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_client_module - Error pthread_mutex_lock (1)"); j_return = json_pack("{si}", "result", G_ERROR); } } else { j_return = json_pack("{si}", "result", G_OK); } } else if (action == GLEWLWYD_MODULE_ACTION_STOP) { if (instance->enabled) { if (!pthread_mutex_lock(&config->module_lock)) { if (instance->module->client_module_close(config->config_m, instance->cls) == G_OK) { instance->enabled = 0; json_object_set(json_object_get(j_module, "module"), "enabled", json_false()); if (set_client_module(config, name, json_object_get(j_module, "module")) == G_OK) { j_return = json_pack("{si}", "result", G_OK); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_client_module - Error set_client_module module %s/%s", instance->module->name, json_string_value(json_object_get(json_object_get(j_module, "module"), "name"))); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_client_module - Error close module %s/%s", instance->module->name, json_string_value(json_object_get(json_object_get(j_module, "module"), "name"))); j_return = json_pack("{si}", "result", G_ERROR); } pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_client_module - Error pthread_mutex_lock (2)"); j_return = json_pack("{si}", "result", G_ERROR); } } else { j_return = json_pack("{si}", "result", G_OK); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_client_module - Error action not found"); j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "Error action not found"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_client_module - Error module not found"); j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "Error module not found"); } json_decref(j_module); return j_return; } json_t * get_plugin_module_list(struct config_elements * config) { int res; json_t * j_query, * j_result = NULL, * j_return, * j_parameters, * j_element; size_t index; j_query = json_pack("{sss[sssss]ss}", "table", GLEWLWYD_TABLE_PLUGIN_MODULE_INSTANCE, "columns", "gpmi_module AS module", "gpmi_name AS name", "gpmi_display_name AS display_name", "gpmi_parameters", "gpmi_enabled", "order_by", "gpmi_module,gpmi_name"); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result, index, j_element) { j_parameters = json_loads(json_string_value(json_object_get(j_element, "gpmi_parameters")), JSON_DECODE_ANY, NULL); if (j_parameters != NULL) { json_object_set_new(j_element, "parameters", j_parameters); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_plugin_module_list - Error parsing parameters for module %s", json_string_value(json_object_get(j_element, "name"))); json_object_set_new(j_element, "parameters", json_null()); } json_object_del(j_element, "gpmi_parameters"); json_object_set_new(j_element, "enabled", json_integer_value(json_object_get(j_element, "gpmi_enabled"))?json_true():json_false()); json_object_del(j_element, "gpmi_enabled"); } j_return = json_pack("{sisO}", "result", G_OK, "module", j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_plugin_module_list - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_result); return j_return; } json_t * get_plugin_module_list_for_user(struct config_elements * config) { json_t * j_module_list = get_plugin_module_list(config), * j_element, * j_return; size_t index; if (check_result_value(j_module_list, G_OK)) { j_return = json_pack("{sis[]}", "result", G_OK, "module"); if (j_return != NULL) { json_array_foreach(json_object_get(j_module_list, "module"), index, j_element) { if (json_object_get(j_element, "enabled") == json_true()) { json_object_del(j_element, "parameters"); json_object_del(j_element, "enabled"); json_array_append(json_object_get(j_return, "module"), j_element); } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_plugin_module_list_for_user - Error allocating resources for j_return"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_plugin_module_list_for_user - Error get_plugin_module_list"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_module_list); return j_return; } json_t * get_plugin_module(struct config_elements * config, const char * name) { int res; json_t * j_query, * j_result = NULL, * j_return, * j_parameters; j_query = json_pack("{sss[sssss]s{ss}}", "table", GLEWLWYD_TABLE_PLUGIN_MODULE_INSTANCE, "columns", "gpmi_module AS module", "gpmi_name AS name", "gpmi_display_name AS display_name", "gpmi_parameters", "gpmi_enabled", "where", "gpmi_name", name); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result) > 0) { j_parameters = json_loads(json_string_value(json_object_get(json_array_get(j_result, 0), "gpmi_parameters")), JSON_DECODE_ANY, NULL); if (j_parameters != NULL) { json_object_set_new(json_array_get(j_result, 0), "parameters", j_parameters); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_plugin_module_list - Error parsing parameters for module %s", json_string_value(json_object_get(json_array_get(j_result, 0), "name"))); json_object_set_new(json_array_get(j_result, 0), "parameters", json_null()); } json_object_del(json_array_get(j_result, 0), "gpmi_parameters"); json_object_set_new(json_array_get(j_result, 0), "enabled", json_integer_value(json_object_get(json_array_get(j_result, 0), "gpmi_enabled"))?json_true():json_false()); json_object_del(json_array_get(j_result, 0), "gpmi_enabled"); j_return = json_pack("{sisO}", "result", G_OK, "module", json_array_get(j_result, 0)); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_plugin_module_list - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_result); return j_return; } json_t * is_plugin_module_valid(struct config_elements * config, json_t * j_module, int add) { json_t * j_return, * j_cur_module, * j_error_list; size_t i; int found; struct _plugin_module * module; char * parameters; if (j_module != NULL && json_is_object(j_module)) { if ((j_error_list = json_array()) != NULL) { if (add) { if (json_object_get(j_module, "name") != NULL && json_is_string(json_object_get(j_module, "name")) && json_string_length(json_object_get(j_module, "name")) > 0 && json_string_length(json_object_get(j_module, "name")) <= 128) { j_cur_module = get_plugin_module(config, json_string_value(json_object_get(j_module, "name"))); if (check_result_value(j_cur_module, G_OK)) { json_array_append_new(j_error_list, json_string("A module instance with this name already exist")); } else if (!check_result_value(j_cur_module, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_ERROR, "is_plugin_module_valid - Error json_array_append_new"); } json_decref(j_cur_module); } else { json_array_append_new(j_error_list, json_string("Module instance name is mandatory and must be a non empty string of at most 128 characters")); } if (json_object_get(j_module, "module") != NULL && json_is_string(json_object_get(j_module, "module")) && json_string_length(json_object_get(j_module, "module")) > 0 && json_string_length(json_object_get(j_module, "module")) <= 128) { found = 0; for (i=0; i<pointer_list_size(config->plugin_module_list); i++) { module = (struct _plugin_module *)pointer_list_get_at(config->plugin_module_list, i); if (module != NULL) { if (0 == o_strcmp(module->name, json_string_value(json_object_get(j_module, "module")))) { found = 1; break; } } } if (!found) { json_array_append_new(j_error_list, json_string("Module name doesn't exist")); } } else { json_array_append_new(j_error_list, json_string("Module is mandatory and must be a non empty string of at most 128 characters")); } } else { if (json_object_get(j_module, "enabled") != NULL && !json_is_boolean(json_object_get(j_module, "enabled"))) { json_array_append_new(j_error_list, json_string("enabled is optional and must be a boolean")); } } if (json_object_get(j_module, "display_name") != NULL && (!json_is_string(json_object_get(j_module, "display_name")) || json_string_length(json_object_get(j_module, "display_name")) > 256)) { json_array_append_new(j_error_list, json_string("display_name is optional and must be a string of at most 256 characters")); } if (json_object_get(j_module, "parameters") == NULL || !json_is_object(json_object_get(j_module, "parameters"))) { json_array_append_new(j_error_list, json_string("Parameters is mandatory and must be a json object of at most 16k characters")); } else { parameters = json_dumps(json_object_get(j_module, "parameters"), JSON_COMPACT); if (parameters == NULL || o_strlen(parameters) > 16*1024) { json_array_append_new(j_error_list, json_string("Parameters is mandatory and must be a json object of at most 16k characters")); } o_free(parameters); } if (json_array_size(j_error_list) > 0) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_error_list); } else { j_return = json_pack("{si}", "result", G_OK); } json_decref(j_error_list); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_plugin_module_valid - Error allocating resources for j_error_list"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "errors", "module must be a JSON object"); } return j_return; } json_t * add_plugin_module(struct config_elements * config, json_t * j_module) { struct _plugin_module * module; struct _plugin_module_instance * cur_instance; json_t * j_query, * j_return, * j_result; int res; size_t i; char * parameters = json_dumps(json_object_get(j_module, "parameters"), JSON_COMPACT); j_query = json_pack("{sss{sOsOsOsiss}}", "table", GLEWLWYD_TABLE_PLUGIN_MODULE_INSTANCE, "values", "gpmi_module", json_object_get(j_module, "module"), "gpmi_name", json_object_get(j_module, "name"), "gpmi_display_name", json_object_get(j_module, "display_name")!=NULL?json_object_get(j_module, "display_name"):json_null(), "gpmi_enabled", 1, "gpmi_parameters", parameters); res = h_insert(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { module = NULL; for (i=0; i<pointer_list_size(config->plugin_module_list); i++) { module = (struct _plugin_module *)pointer_list_get_at(config->plugin_module_list, i); if (0 == o_strcmp(module->name, json_string_value(json_object_get(j_module, "module")))) { break; } else { module = NULL; } } if (module != NULL) { if (!pthread_mutex_lock(&config->module_lock)) { cur_instance = o_malloc(sizeof(struct _plugin_module_instance)); if (cur_instance != NULL) { cur_instance->cls = NULL; cur_instance->name = o_strdup(json_string_value(json_object_get(j_module, "name"))); cur_instance->module = module; cur_instance->enabled = 0; if (pointer_list_append(config->plugin_module_instance_list, cur_instance)) { j_result = module->plugin_module_init(config->config_p, cur_instance->name, json_object_get(j_module, "parameters"), &cur_instance->cls); if (check_result_value(j_result, G_OK)) { cur_instance->enabled = 1; j_return = json_pack("{si}", "result", G_OK); } else if (check_result_value(j_result, G_ERROR_PARAM)) { j_return = json_pack("{sisO*}", "result", G_ERROR_PARAM, "error", json_object_get(j_result, "error")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_plugin_module - Error init module %s/%s", module->name, json_string_value(json_object_get(j_module, "name"))); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_plugin_module - Error reallocating resources for plugin_module_instance_list"); o_free(cur_instance->name); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_plugin_module - Error allocating resources for cur_instance"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_plugin_module - Error pthread_mutex_lock"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_plugin_module - Module '%s' not found", json_string_value(json_object_get(j_module, "module"))); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_plugin_module - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } o_free(parameters); return j_return; } int set_plugin_module(struct config_elements * config, const char * name, json_t * j_module) { json_t * j_query; int res, ret; char * parameters = json_dumps(json_object_get(j_module, "parameters"), JSON_COMPACT); j_query = json_pack("{sss{sOsiss}s{ss}}", "table", GLEWLWYD_TABLE_PLUGIN_MODULE_INSTANCE, "set", "gpmi_display_name", json_object_get(j_module, "display_name")!=NULL?json_object_get(j_module, "display_name"):json_null(), "gpmi_enabled", json_object_get(j_module, "enabled")==json_false()?0:1, "gpmi_parameters", parameters, "where", "gpmi_name", name); o_free(parameters); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_plugin_module - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } int delete_plugin_module(struct config_elements * config, const char * name) { int ret, res; json_t * j_query, * j_result = manage_plugin_module(config, name, GLEWLWYD_MODULE_ACTION_STOP); struct _plugin_module_instance * instance; if (check_result_value(j_result, G_OK)) { if (!pthread_mutex_lock(&config->module_lock)) { instance = get_plugin_module_instance(config, name); if (pointer_list_remove_pointer(config->plugin_module_instance_list, instance)) { o_free(instance->name); o_free(instance); j_query = json_pack("{sss{ss}}", "table", GLEWLWYD_TABLE_PLUGIN_MODULE_INSTANCE, "where", "gpmi_name", name); res = h_delete(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_plugin_module - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_plugin_module - Error pointer_list_remove_pointer"); ret = G_ERROR; } pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_plugin_module - Error pthread_mutex_lock"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_plugin_module - Error stopping plugin"); ret = G_ERROR; } json_decref(j_result); return ret; } json_t * manage_plugin_module(struct config_elements * config, const char * name, int action) { struct _plugin_module_instance * instance = get_plugin_module_instance(config, name); json_t * j_module = get_plugin_module(config, name), * j_return, * j_result; if (check_result_value(j_module, G_OK) && instance != NULL) { if (action == GLEWLWYD_MODULE_ACTION_START) { if (!instance->enabled) { if (!pthread_mutex_lock(&config->module_lock)) { j_result = instance->module->plugin_module_init(config->config_p, instance->name, json_object_get(json_object_get(j_module, "module"), "parameters"), &instance->cls); if (check_result_value(j_result, G_OK)) { instance->enabled = 1; json_object_set(json_object_get(j_module, "module"), "enabled", json_true()); if (set_plugin_module(config, name, json_object_get(j_module, "module")) == G_OK) { j_return = json_pack("{si}", "result", G_OK); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_plugin_module - Error set_client_module module %s/%s", instance->module->name, json_string_value(json_object_get(json_object_get(j_module, "module"), "name"))); j_return = json_pack("{si}", "result", G_ERROR); } } else if (check_result_value(j_result, G_ERROR_PARAM)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", json_object_get(j_result, "error")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_plugin_module - Error init module %s/%s", instance->module->name, json_string_value(json_object_get(json_object_get(j_module, "module"), "name"))); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_plugin_module - Error pthread_mutex_lock (1)"); j_return = json_pack("{si}", "result", G_ERROR); } } else { j_return = json_pack("{si}", "result", G_OK); } } else if (action == GLEWLWYD_MODULE_ACTION_STOP) { if (instance->enabled) { if (!pthread_mutex_lock(&config->module_lock)) { if (instance->module->plugin_module_close(config->config_p, instance->name, instance->cls) == G_OK) { instance->enabled = 0; instance->cls = NULL; json_object_set(json_object_get(j_module, "module"), "enabled", json_false()); if (set_plugin_module(config, name, json_object_get(j_module, "module")) == G_OK) { j_return = json_pack("{si}", "result", G_OK); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_plugin_module - Error set_client_module module %s/%s", instance->module->name, json_string_value(json_object_get(json_object_get(j_module, "module"), "name"))); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_plugin_module - Error close module %s/%s", instance->module->name, json_string_value(json_object_get(json_object_get(j_module, "module"), "name"))); j_return = json_pack("{si}", "result", G_ERROR); } pthread_mutex_unlock(&config->module_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_plugin_module - Error pthread_mutex_lock (2)"); j_return = json_pack("{si}", "result", G_ERROR); } } else { j_return = json_pack("{si}", "result", G_OK); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_plugin_module - Error action not found"); j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", "action not found"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "manage_plugin_module - Error module not found"); j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "module not found"); } json_decref(j_module); return j_return; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/plugin.c�������������������������������������������������������������������������0000664�0000000�0000000�00000076442�14156463140�0015630�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * Definitions for functions used in plugin modules * * Copyright 2016-2021 Nicolas Mora <mail@babelouest.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see <http://www.gnu.org/licenses/>. * */ #include <string.h> #include <ctype.h> #include "glewlwyd.h" int glewlwyd_callback_add_plugin_endpoint(struct config_plugin * config, const char * method, const char * name, const char * url, unsigned int priority, int (* callback)(const struct _u_request * request, struct _u_response * response, void * user_data), void * user_data) { int ret; char * p_url; if (config != NULL && config->glewlwyd_config != NULL && config->glewlwyd_config->instance != NULL && method != NULL && name != NULL && url != NULL && callback != NULL && 0 != o_strncasecmp(name, "auth", o_strlen("auth"))) { p_url = msprintf("%s/%s", name, url); if (p_url != NULL) { if ((ret = ulfius_add_endpoint_by_val(config->glewlwyd_config->instance, method, config->glewlwyd_config->api_prefix, p_url, GLEWLWYD_CALLBACK_PRIORITY_PLUGIN + priority, callback, user_data)) != U_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_add_plugin_endpoint - Error %d ulfius_add_endpoint_by_val %s - %s/%s",ret, method, config->glewlwyd_config->api_prefix, p_url); ret = G_ERROR; } else { y_log_message(Y_LOG_LEVEL_INFO, "Add endpoint %s %s/%s", method, config->glewlwyd_config->api_prefix, p_url); ret = G_OK; } o_free(p_url); } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_add_plugin_endpoint - Error allocating resources for p_url"); ret = G_ERROR_MEMORY; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_add_plugin_endpoint - Error input parameters"); ret = G_ERROR_PARAM; } return ret; } int glewlwyd_callback_remove_plugin_endpoint(struct config_plugin * config, const char * method, const char * name, const char * url) { int ret; char * p_url; if (config != NULL && config->glewlwyd_config != NULL && config->glewlwyd_config->instance != NULL && method != NULL && name != NULL && url != NULL) { p_url = msprintf("%s/%s", name, url); if (p_url != NULL) { if ((ret = ulfius_remove_endpoint_by_val(config->glewlwyd_config->instance, method, config->glewlwyd_config->api_prefix, p_url)) != U_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_remove_plugin_endpoint - Error %d ulfius_remove_endpoint_by_val %s - %s/%s", ret, method, config->glewlwyd_config->api_prefix, p_url); ret = G_ERROR; } else { y_log_message(Y_LOG_LEVEL_INFO, "Remove endpoint %s %s/%s", method, config->glewlwyd_config->api_prefix, p_url); ret = G_OK; } o_free(p_url); } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_remove_plugin_endpoint - Error allocating resources for p_url"); ret = G_ERROR_MEMORY; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_remove_plugin_endpoint - Error input parameters"); ret = G_ERROR_PARAM; } return ret; } json_t * glewlwyd_callback_check_session_valid(struct config_plugin * config, const struct _u_request * request, const char * scope_list) { json_t * j_user, * j_return, * j_scope_allowed; char * session_uid = NULL; if (config != NULL && request != NULL) { session_uid = get_session_id(config->glewlwyd_config, request); j_user = get_current_user_for_session(config->glewlwyd_config, session_uid); // Check if session is valid if (check_result_value(j_user, G_OK)) { if (o_strlen(scope_list)) { // For all allowed scope, check that the current session has a valid session j_scope_allowed = get_validated_auth_scheme_list_from_scope_list(config->glewlwyd_config, scope_list, session_uid); if (check_result_value(j_scope_allowed, G_OK)) { j_return = json_pack("{sis{sOsO}}", "result", G_OK, "session", "scope", json_object_get(j_scope_allowed, "scheme"), "user", json_object_get(j_user, "user")); } else if (check_result_value(j_scope_allowed, G_ERROR_UNAUTHORIZED) || check_result_value(j_scope_allowed, G_ERROR_NOT_FOUND)) { j_return = json_pack("{sis{sO}}", "result", G_ERROR_UNAUTHORIZED, "session", "user", json_object_get(j_user, "user")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_check_session_valid - Error get_validated_auth_scheme_list_from_scope_list"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_scope_allowed); } else { j_return = json_pack("{sis{sO}}", "result", G_OK, "session", "user", json_object_get(j_user, "user")); } } else if (check_result_value(j_user, G_ERROR_NOT_FOUND)) { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_check_session_valid - Error get_current_user_for_session"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_user); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } o_free(session_uid); return j_return; } json_t * glewlwyd_callback_check_user_valid(struct config_plugin * config, const char * username, const char * password, const char * scope) { json_t * j_user, * j_return, * j_auth, * j_element, * j_scope; int check_password, check_scope; char ** scope_array = NULL, * scope_list = NULL, * tmp; size_t index; if (config != NULL && username != NULL) { j_user = get_user(config->glewlwyd_config, username, NULL); if (check_result_value(j_user, G_OK)) { check_password = 1; if (password != NULL) { j_auth = auth_check_user_credentials(config->glewlwyd_config, username, password); if (!check_result_value(j_auth, G_OK)) { check_password = 0; } json_decref(j_auth); } check_scope = 1; if (scope != NULL) { if (split_string(scope, " ", &scope_array) > 0) { json_array_foreach(json_object_get(json_object_get(j_user, "user"), "scope"), index, j_element) { if (string_array_has_value((const char **)scope_array, json_string_value(j_element))) { // Check if scope has no scheme but password j_scope = get_scope(config->glewlwyd_config, json_string_value(j_element)); if (check_result_value(j_scope, G_OK)) { if (json_object_size(json_object_get(json_object_get(j_scope, "scope"), "scheme")) == 0) { if (scope_list == NULL) { scope_list = o_strdup(json_string_value(j_element)); } else { tmp = msprintf("%s %s", scope_list, json_string_value(j_element)); o_free(scope_list); scope_list = tmp; } } } else if (!check_result_value(j_scope, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_check_user_valid - Error get_scope"); } json_decref(j_scope); } } if (scope_list != NULL) { json_object_set_new(json_object_get(j_user, "user"), "scope_list", json_string(scope_list)); } else { check_scope = 0; } o_free(scope_list); } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_check_user_valid - Error split_string"); check_scope = 0; } free_string_array(scope_array); } if (check_password && check_scope) { j_return = json_pack("{sisO}", "result", G_OK, "user", json_object_get(j_user, "user")); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else if (check_result_value(j_user, G_ERROR_NOT_FOUND) || (check_result_value(j_user, G_OK) && json_object_get(json_object_get(j_user, "user"), "enabled") != json_true())) { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_check_user_valid - Error get_user"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } json_decref(j_user); } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_check_user_valid - Error input parameters"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } json_t * glewlwyd_callback_check_client_valid(struct config_plugin * config, const char * client_id, const char * password) { json_t * j_return, * j_client, * j_client_credentials; int password_checked = 1; if (config != NULL && client_id != NULL) { j_client = get_client(config->glewlwyd_config, client_id, NULL); if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true()) { if (password != NULL) { if (json_string_length(json_object_get(json_object_get(j_client, "client"), "client_secret"))) { if (0 != o_strcmp(password, json_string_value(json_object_get(json_object_get(j_client, "client"), "client_secret")))) { password_checked = 0; } } else { j_client_credentials = auth_check_client_credentials(config->glewlwyd_config, client_id, password); if (!check_result_value(j_client_credentials, G_OK)) { password_checked = 0; } json_decref(j_client_credentials); } } if (password_checked) { j_return = json_pack("{sisO}", "result", G_OK, "client", json_object_get(j_client, "client")); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else if (check_result_value(j_client, G_ERROR_NOT_FOUND) || (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") != json_true())) { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_check_client_valid - Error get_client"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_client); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } json_t * glewlwyd_callback_get_client_granted_scopes(struct config_plugin * config, const char * client_id, const char * username, const char * scope_list) { json_t * j_user = get_user(config->glewlwyd_config, username, NULL), * j_grant = NULL; if (check_result_value(j_user, G_OK)) { j_grant = get_granted_scopes_for_client(config->glewlwyd_config, json_object_get(j_user, "user"), client_id, scope_list); } else if (check_result_value(j_user, G_ERROR_NOT_FOUND)){ j_grant = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_get_client_granted_scopes - Error get_user"); j_grant = json_pack("{si}", "result", G_ERROR); } json_decref(j_user); return j_grant; } int glewlwyd_callback_trigger_session_used(struct config_plugin * config, const struct _u_request * request, const char * scope_list) { json_t * j_session = glewlwyd_callback_check_session_valid(config, request, scope_list), * j_query, * j_scope, * j_scheme_processed, * j_group, * j_scheme; char * session_uid = get_session_id(config->glewlwyd_config, request), * session_hash = NULL, * clause_session, * username_escaped, * clause_scheme, * escape_scheme_module, * escape_scheme_name; int ret, res, password_processed = 0; const char * key_scope, * key_group; size_t index; if (check_result_value(j_session, G_OK) || session_uid == NULL) { if ((session_hash = generate_hash(config->glewlwyd_config->hash_algorithm, session_uid)) != NULL) { j_scheme_processed = json_object(); if (j_scheme_processed != NULL) { ret = G_OK; username_escaped = h_escape_string_with_quotes(config->glewlwyd_config->conn, json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username"))); clause_session = msprintf("IN (SELECT gus_id FROM " GLEWLWYD_TABLE_USER_SESSION " WHERE gus_session_hash='%s' AND gus_username=%s AND gus_expiration %s AND gus_enabled=1 AND gus_current=1)", session_hash, username_escaped, SWITCH_DB_TYPE(config->glewlwyd_config->conn->type, "> NOW()", "> (strftime('%s','now'))", "> NOW()")); json_object_foreach(json_object_get(json_object_get(j_session, "session"), "scope"), key_scope, j_scope) { if (!password_processed && json_object_get(j_scope, "password_authenticated") == json_true()) { password_processed = 1; // Increment guss_use_counter for the password scheme on the specified session j_query = json_pack("{sss{s{ss}}s{sOs{ssss}sis{ssss}}}", "table", GLEWLWYD_TABLE_USER_SESSION_SCHEME, "set", "guss_use_counter", "raw", "(guss_use_counter + 1)", "where", "guasmi_id", json_null(), "gus_id", "operator", "raw", "value", clause_session, "guss_enabled", 1, "guss_expiration", "operator", "raw", "value", SWITCH_DB_TYPE(config->glewlwyd_config->conn->type, "> NOW()", "> (strftime('%s','now'))", "> NOW()")); res = h_update(config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_trigger_session_used - Error h_update for password scheme"); glewlwyd_metrics_increment_counter_va(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } json_object_foreach(json_object_get(j_scope, "schemes"), key_group, j_group) { json_array_foreach(j_group, index, j_scheme) { if (json_object_get(j_scheme, "scheme_authenticated") == json_true() && json_object_get(j_scheme_processed, json_string_value(json_object_get(j_scheme, "scheme_name"))) == NULL) { json_object_set_new(j_scheme_processed, json_string_value(json_object_get(j_scheme, "scheme_name")), json_object()); // Increment guss_use_counter for the specified scheme on the specified session escape_scheme_module = h_escape_string_with_quotes(config->glewlwyd_config->conn, json_string_value(json_object_get(j_scheme, "scheme_type"))); escape_scheme_name = h_escape_string_with_quotes(config->glewlwyd_config->conn, json_string_value(json_object_get(j_scheme, "scheme_name"))); clause_scheme = msprintf("IN (SELECT guasmi_id FROM " GLEWLWYD_TABLE_USER_AUTH_SCHEME_MODULE_INSTANCE " WHERE guasmi_module=%s AND guasmi_name=%s)", escape_scheme_module, escape_scheme_name); j_query = json_pack("{sss{s{ss}}s{s{ssss}s{ssss}sis{ssss}}}", "table", GLEWLWYD_TABLE_USER_SESSION_SCHEME, "set", "guss_use_counter", "raw", "(guss_use_counter + 1)", "where", "guasmi_id", "operator", "raw", "value", clause_scheme, "gus_id", "operator", "raw", "value", clause_session, "guss_enabled", 1, "guss_expiration", "operator", "raw", "value", SWITCH_DB_TYPE(config->glewlwyd_config->conn->type, "> NOW()", "> (strftime('%s','now'))", "> NOW()")); o_free(clause_scheme); o_free(escape_scheme_name); o_free(escape_scheme_module); res = h_update(config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_trigger_session_used - Error h_update for scheme %s/%s", json_string_value(json_object_get(j_scheme, "scheme_type")), json_string_value(json_object_get(j_scheme, "scheme_name"))); glewlwyd_metrics_increment_counter_va(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } } } } o_free(username_escaped); o_free(clause_session); json_decref(j_scheme_processed); } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_trigger_session_used - Error allocating resources for j_scheme_processed"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_trigger_session_used - Error generate_hash"); ret = G_ERROR; } o_free(session_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_trigger_session_used - Error glewlwyd_callback_check_session_valid or session_uid NULL"); ret = G_ERROR; } json_decref(j_session); o_free(session_uid); return ret; } time_t glewlwyd_callback_get_session_age(struct config_plugin * config, const struct _u_request * request, const char * scope_list) { time_t age = 0; char * session_uid = get_session_id(config->glewlwyd_config, request), * session_uid_hash, * query, ** scope_array = NULL, * scope_escaped, * scope_list_clause = NULL; json_t * j_result; if (session_uid != NULL) { session_uid_hash = generate_hash(config->glewlwyd_config->hash_algorithm, session_uid); if (session_uid_hash != NULL) { if (split_string(scope_list, " ", &scope_array)) { for (int i=0; scope_array[i] != NULL; i++) { scope_escaped = h_escape_string_with_quotes(config->glewlwyd_config->conn, scope_array[i]); if (scope_list_clause == NULL) { scope_list_clause = msprintf("%s", scope_escaped); } else { scope_list_clause = mstrcatf(scope_list_clause, ",%s", scope_escaped); } o_free(scope_escaped); } if (scope_list_clause != NULL) { // Quey to retreive the most recent, enabled and succeeded authentication date for the current session query = msprintf("SELECT %s FROM " GLEWLWYD_TABLE_USER_SESSION_SCHEME " WHERE guss_enabled=1 AND gus_id IN (SELECT gus_id FROM " GLEWLWYD_TABLE_USER_SESSION " WHERE gus_session_hash='%s' AND gus_current=1) AND (guasmi_id IS NULL OR guasmi_id IN (SELECT guasmi_id FROM " GLEWLWYD_TABLE_SCOPE_GROUP_AUTH_SCHEME_MODULE_INSTANCE " WHERE gsg_id IN (SELECT gsg_id FROM " GLEWLWYD_TABLE_SCOPE_GROUP " WHERE gs_id IN (SELECT gs_id FROM " GLEWLWYD_TABLE_SCOPE " WHERE gs_name IN (%s))))) ORDER BY guss_last_login DESC LIMIT 1;", (SWITCH_DB_TYPE(config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(guss_last_login) AS guss_last_login", "guss_last_login", "EXTRACT(EPOCH FROM guss_last_login)::integer AS guss_last_login")), session_uid_hash, scope_list_clause); if (h_execute_query_json(config->glewlwyd_config->conn, query, &j_result) == H_OK) { if (json_array_size(j_result)) { age = (time_t)json_integer_value(json_object_get(json_array_get(j_result, 0), "guss_last_login")); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_get_session_age - Error executig query"); } o_free(query); } o_free(scope_list_clause); free_string_array(scope_array); } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_get_session_age - Error split_string"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_get_session_age - Error generate_hash for session_uid"); } o_free(session_uid_hash); } o_free(session_uid); return age; } char * glewlwyd_callback_get_login_url(struct config_plugin * config, const char * client_id, const char * scope_list, const char * callback_url, struct _u_map * additional_parameters) { char * encoded_callback_url = NULL, * encoded_client_id = NULL, * encoded_scope_list = NULL, * login_url, * query_additional_parameters = NULL, * value_encoded; const char ** keys; int i; if (callback_url != NULL) { encoded_callback_url = ulfius_url_encode(callback_url); } if (client_id != NULL) { encoded_client_id = ulfius_url_encode(client_id); } if (scope_list != NULL) { encoded_scope_list = ulfius_url_encode(scope_list); } if (additional_parameters != NULL) { keys = u_map_enum_keys(additional_parameters); for (i=0; keys[i]!=NULL; i++) { if (u_map_get(additional_parameters, keys[i]) == NULL) { query_additional_parameters = mstrcatf(query_additional_parameters, "&%s", keys[i]); } else { value_encoded = ulfius_url_encode(u_map_get(additional_parameters, keys[i])); query_additional_parameters = mstrcatf(query_additional_parameters, "&%s=%s", keys[i], value_encoded); o_free(value_encoded); } } } login_url = msprintf("%s/%s?%s%s%s%s%s%s%s", config->glewlwyd_config->external_url, config->glewlwyd_config->login_url, (encoded_client_id!=NULL?"client_id=":""), (encoded_client_id!=NULL?encoded_client_id:""), (encoded_scope_list!=NULL?"&scope=":""), (encoded_scope_list!=NULL?encoded_scope_list:""), (encoded_callback_url!=NULL?"&callback_url=":""), (encoded_callback_url!=NULL?encoded_callback_url:""), (query_additional_parameters!=NULL?query_additional_parameters:"")); o_free(query_additional_parameters); o_free(encoded_callback_url); o_free(encoded_client_id); o_free(encoded_scope_list); return login_url; } char * glewlwyd_callback_get_plugin_external_url(struct config_plugin * config, const char * name) { return msprintf("%s/%s/%s", config->glewlwyd_config->external_url, config->glewlwyd_config->api_prefix, name); } char * glewlwyd_callback_generate_hash(struct config_plugin * config, const char * data) { return generate_hash(config->glewlwyd_config->hash_algorithm, data); } json_t * glewlwyd_plugin_callback_get_user_list(struct config_plugin * config, const char * pattern, size_t offset, size_t limit) { return get_user_list(config->glewlwyd_config, pattern, offset, limit, NULL); } json_t * glewlwyd_plugin_callback_get_user(struct config_plugin * config, const char * username) { return get_user(config->glewlwyd_config, username, NULL); } json_t * glewlwyd_plugin_callback_get_user_profile(struct config_plugin * config, const char * username) { return get_user_profile(config->glewlwyd_config, username, NULL); } json_t * glewlwyd_plugin_callback_is_user_valid(struct config_plugin * config, const char * username, json_t * j_user, int add) { return is_user_valid(config->glewlwyd_config, username, j_user, add, NULL); } int glewlwyd_plugin_callback_add_user(struct config_plugin * config, json_t * j_user) { return add_user(config->glewlwyd_config, j_user, NULL); } int glewlwyd_plugin_callback_set_user(struct config_plugin * config, const char * username, json_t * j_user) { json_t * j_cur_user = get_user(config->glewlwyd_config, username, NULL); int ret; if (check_result_value(j_cur_user, G_OK)) { ret = set_user(config->glewlwyd_config, username, j_user, json_string_value(json_object_get(json_object_get(j_cur_user, "user"), "source"))); } else if (check_result_value(j_cur_user, G_ERROR_NOT_FOUND)) { ret = U_ERROR_NOT_FOUND; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "glewlwyd_plugin_callback_set_user - Error get_user"); ret = G_ERROR; } json_decref(j_cur_user); return ret; } int glewlwyd_plugin_callback_user_update_password(struct config_plugin * config, const char * username, const char * password) { json_t * j_user = get_user(config->glewlwyd_config, username, NULL); int ret; const char *passwords[1]; passwords[0] = password; if (check_result_value(j_user, G_OK)) { ret = user_set_password(config->glewlwyd_config, username, passwords, 1); } else if (check_result_value(j_user, G_ERROR_NOT_FOUND)) { ret = U_ERROR_NOT_FOUND; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "glewlwyd_plugin_callback_user_update_password - Error get_user"); ret = G_ERROR; } json_decref(j_user); return ret; } int glewlwyd_plugin_callback_delete_user(struct config_plugin * config, const char * username) { json_t * j_user = get_user(config->glewlwyd_config, username, NULL); int ret; if (check_result_value(j_user, G_OK)) { ret = delete_user(config->glewlwyd_config, username, json_string_value(json_object_get(json_object_get(j_user, "user"), "source"))); } else if (check_result_value(j_user, G_ERROR_NOT_FOUND)) { ret = U_ERROR_NOT_FOUND; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "glewlwyd_plugin_callback_set_user - Error get_user"); ret = G_ERROR; } json_decref(j_user); return ret; } json_t * glewlwyd_plugin_callback_get_client_list(struct config_plugin * config, const char * pattern, size_t offset, size_t limit) { return get_client_list(config->glewlwyd_config, pattern, offset, limit, NULL); } json_t * glewlwyd_plugin_callback_get_client(struct config_plugin * config, const char * client_id) { return get_client(config->glewlwyd_config, client_id, NULL); } json_t * glewlwyd_plugin_callback_is_client_valid(struct config_plugin * config, const char * client_id, json_t * j_client, int add) { return is_client_valid(config->glewlwyd_config, client_id, j_client, add, NULL); } int glewlwyd_plugin_callback_add_client(struct config_plugin * config, json_t * j_client) { return add_client(config->glewlwyd_config, j_client, NULL); } int glewlwyd_plugin_callback_set_client(struct config_plugin * config, const char * client_id, json_t * j_client) { return set_client(config->glewlwyd_config, client_id, j_client, NULL); } int glewlwyd_plugin_callback_delete_client(struct config_plugin * config, const char * client_id) { return delete_client(config->glewlwyd_config, client_id, NULL); } json_t * glewlwyd_plugin_callback_get_scheme_module(struct config_plugin * config, const char * mod_name) { if (o_strlen(mod_name)) { return get_user_auth_scheme_module(config->glewlwyd_config, mod_name); } else { return json_pack("{si}", "result", G_ERROR_PARAM); } } json_t * glewlwyd_plugin_callback_get_scheme_list(struct config_plugin * config, const char * username) { if (o_strlen(username)) { return get_scheme_list_for_user(config->glewlwyd_config, username); } else { return json_pack("{si}", "result", G_ERROR_PARAM); } } json_t * glewlwyd_plugin_callback_scheme_register(struct config_plugin * config, const char * mod_name, const struct _u_request * http_request, const char * username, json_t * j_scheme_data) { json_t * j_return; struct _user_auth_scheme_module_instance * scheme_instance = get_user_auth_scheme_module_instance(config->glewlwyd_config, mod_name); if (scheme_instance != NULL && scheme_instance->enabled) { j_return = scheme_instance->module->user_auth_scheme_module_register(config->glewlwyd_config->config_m, http_request, username, j_scheme_data, scheme_instance->cls); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } json_t * glewlwyd_plugin_callback_scheme_register_get(struct config_plugin * config, const char * mod_name, const struct _u_request * http_request, const char * username) { json_t * j_return; struct _user_auth_scheme_module_instance * scheme_instance = get_user_auth_scheme_module_instance(config->glewlwyd_config, mod_name); if (scheme_instance != NULL && scheme_instance->enabled) { j_return = scheme_instance->module->user_auth_scheme_module_register_get(config->glewlwyd_config->config_m, http_request, username, scheme_instance->cls); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } int glewlwyd_plugin_callback_scheme_can_use(struct config_plugin * config, const char * mod_name, const char * username) { int ret; struct _user_auth_scheme_module_instance * scheme_instance = get_user_auth_scheme_module_instance(config->glewlwyd_config, mod_name); if (scheme_instance != NULL && scheme_instance->enabled) { ret = scheme_instance->module->user_auth_scheme_module_can_use(config->glewlwyd_config->config_m, username, scheme_instance->cls); } else { ret = G_ERROR_PARAM; } return ret; } int glewlwyd_plugin_callback_scheme_deregister(struct config_plugin * config, const char * mod_name, const char * username) { int ret; struct _user_auth_scheme_module_instance * scheme_instance = get_user_auth_scheme_module_instance(config->glewlwyd_config, mod_name); if (scheme_instance != NULL && scheme_instance->enabled) { ret = scheme_instance->module->user_auth_scheme_module_deregister(config->glewlwyd_config->config_m, username, scheme_instance->cls); } else { ret = G_ERROR_PARAM; } return ret; } int glewlwyd_plugin_callback_metrics_add_metric(struct config_plugin * config, const char * name, const char * help) { if (config != NULL) { return glewlwyd_metrics_add_metric(config->glewlwyd_config, name, help); } else { return G_ERROR_PARAM; } } int glewlwyd_plugin_callback_metrics_increment_counter(struct config_plugin * config, const char * name, size_t inc, ...) { va_list vl; char * label = NULL; int ret = G_OK; if (config != NULL && o_strlen(name)) { va_start(vl, inc); label = glewlwyd_metrics_build_label(vl); va_end(vl); ret = glewlwyd_metrics_increment_counter(config->glewlwyd_config, name, label, inc); o_free(label); } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_plugin_callback_metrics_increment_counter - Error input values"); ret = G_ERROR_PARAM; } return ret; } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/plugin/��������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14156463140�0015447�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/plugin/Makefile������������������������������������������������������������������0000664�0000000�0000000�00000006245�14156463140�0017116�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # Glewlwyd potocol backend # # Makefile used to build the software # # Copyright 2016-2019 Nicolas Mora <mail@babelouest.org> # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; # version 2.1 of the License. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this library. If not, see <http://www.gnu.org/licenses/>. # RESOURCES_ULFIUS=../../docs/resources/ulfius GLWD_SRC=.. DESTDIR=/usr/local MODULES_TARGET=$(DESTDIR)/lib/glewlwyd/plugin CC=gcc CFLAGS=-c -fPIC -Wall -Werror -Wextra -Wno-unknown-pragmas -D_REENTRANT -Wno-pragmas -I$(GLWD_SRC) -I$(RESOURCES_ULFIUS) $(shell pkg-config --cflags liborcania) $(shell pkg-config --cflags libyder) $(shell pkg-config --cflags jansson) $(shell pkg-config --cflags libhoel) $(shell pkg-config --cflags librhonabwy) $(shell pkg-config --cflags libiddawc) $(shell pkg-config --cflags gnutls) $(ADDITIONALFLAGS) LIBS=$(shell pkg-config --libs liborcania) $(shell pkg-config --libs libyder) $(shell pkg-config --libs libulfius) $(shell pkg-config --libs libhoel) $(shell pkg-config --libs librhonabwy) $(shell pkg-config --libs libiddawc) $(shell pkg-config --libs jansson) -ldl TARGET_RELEASE=libprotocol_oauth2.so libprotocol_oidc.so libprotocol_register.so TARGET_DEBUG=libprotocol_mock.so all: release %.o: %.c ../glewlwyd-common.h $(CC) $(CFLAGS) $(CPPFLAGS) $< misc.o: ../misc.c ../glewlwyd-common.h $(CC) $(CFLAGS) $(CPPFLAGS) ../misc.c glewlwyd_resource.o: $(RESOURCES_ULFIUS)/glewlwyd_resource.c $(RESOURCES_ULFIUS)/glewlwyd_resource.h $(CC) $(CFLAGS) $(CPPFLAGS) $(RESOURCES_ULFIUS)/glewlwyd_resource.c oidc_resource.o: $(RESOURCES_ULFIUS)/oidc_resource.c $(RESOURCES_ULFIUS)/oidc_resource.h $(CC) $(CFLAGS) $(CPPFLAGS) $(RESOURCES_ULFIUS)/oidc_resource.c libprotocol_oauth2.so: protocol_oauth2.o misc.o glewlwyd_resource.o $(GLWD_SRC)/glewlwyd-common.h $(RESOURCES_ULFIUS)/glewlwyd_resource.h $(CC) -shared -Wl,-soname,libprotocol_oauth2.so -o libprotocol_oauth2.so protocol_oauth2.o misc.o glewlwyd_resource.o $(LIBS) libprotocol_oidc.so: protocol_oidc.o misc.o oidc_resource.o $(GLWD_SRC)/glewlwyd-common.h $(RESOURCES_ULFIUS)/oidc_resource.h $(CC) -shared -Wl,-soname,libprotocol_oidc.so -o libprotocol_oidc.so protocol_oidc.o misc.o oidc_resource.o $(LIBS) $(shell pkg-config --libs gnutls) libprotocol_mock.so: mock.o misc.o $(GLWD_SRC)/glewlwyd-common.h $(CC) -shared -Wl,-soname,libprotocol_mock.so -o libprotocol_mock.so mock.o misc.o $(LIBS) libprotocol_register.so: register.o misc.o $(GLWD_SRC)/glewlwyd-common.h $(CC) -shared -Wl,-soname,libprotocol_register.so -o libprotocol_register.so register.o misc.o $(LIBS) clean: rm -f *.o *.so debug: ADDITIONALFLAGS=-DDEBUG -g -O0 debug: $(TARGET_RELEASE) $(TARGET_DEBUG) release: ADDITIONALFLAGS=-O3 release: $(TARGET_RELEASE) install: mkdir -p $(MODULES_TARGET) cp -f *.so $(MODULES_TARGET) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/plugin/README.md�����������������������������������������������������������������0000664�0000000�0000000�00000026176�14156463140�0016742�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Glewlwyd Plugins A Glewlwyd plugin is built as a library and loaded at startup. It must contain a specific set of functions available to glewlwyd to work properly. A plugin is designed to build workflows of any kind. Currently, the workflows [OAuth 2](../../docs/OAUTH2.md) and [OpenID Connect](../../docs/OIDC.md) are available. Plugins can be use to perform (but not only): - Authentication workflows (OAuth, OAuth 2, SAML, etc.) - New user registration workflow - Forgot password workflow A Glewlwyd plugin can access the entire data and functions available to Glewlwyd service. There is no limitation to its access. Therefore, Glewlwyd plugins must be carefully designed and considered friendly. All data returned as `json_t *` or `char *` must be dynamically allocated, because they will be cleaned up by Glewlwyd after use. Currently, the following user backend plugins are available: - [OAuth 2](protocol_oauth2.c) - [OpenID Connect](protocol_oidc.c) - [Register new user](register.c) Technically, a plugin is a small web application that is loaded and instaciated by Glewlwyd service. Therefore a plugin has only 4 functions to implement. The plugin has access to Glewlwyd's callback function such as managing users and clients, or managing new API endpoints. The goal is to create a set of APIs, then the APIs will perform actions specific for the plugin. The endpoints must be added in the init function, and removed in the close function. A Glewlwyd plugin requires the library [Jansson](https://github.com/akheron/Jansson). You can check out the existing plugins for inspiration. You can start from the fake plugin [mock.c](mock.c) to build your own. A pointer of `struct config_plugin` is passed to all the mandatory functions. This pointer gives access to some Glewlwyd data and some callback functions used to achieve specific actions. The definition of the structure is the following: ```C /** * Structure given to all plugin functions that will contain configuration on the * application host, and pointer to functions of the application host */ struct config_plugin { /* General configuration of the Glewlwyd instance */ struct config_elements * glewlwyd_config; /* Add a new HTTP endpoint, this endopoint will be available at the address http[s]://<glewlwyd_server>/<glewlwyd_api_prefix>/<name>/<url>, example: https://localhost:4953/api/glwd/auth */ int (* glewlwyd_callback_add_plugin_endpoint)(struct config_plugin * config, const char * method, const char * name, const char * url, unsigned int priority, int (* callback)(const struct _u_request * request, struct _u_response * response, void * user_data), void * user_data); /* Remove en existing endpoint */ int (* glewlwyd_callback_remove_plugin_endpoint)(struct config_plugin * config, const char * method, const char * name, const char * url); // Session callback functions /* Return the validity of a session with the specified scopes, the result will tell if scopes require registration or scheme authentication */ json_t * (* glewlwyd_callback_check_session_valid)(struct config_plugin * config, const struct _u_request * request, const char * scope_list); /* Check if a password is valid for the user and if the scope list is available to the user */ json_t * (* glewlwyd_callback_check_user_valid)(struct config_plugin * config, const char * username, const char * password, const char * scope_list); /* Check if a password is valid for the client and if the scope list is available to the client */ json_t * (* glewlwyd_callback_check_client_valid)(struct config_plugin * config, const char * client_id, const char * password); /* Tell Glewlwyd that the current session has been triggered, to invalidate some scheme session if necessary */ int (* glewlwyd_callback_trigger_session_used)(struct config_plugin * config, const struct _u_request * request, const char * scope_list); /* Return the last successful authentication with any scheme of the current session */ time_t (* glewlwyd_callback_get_session_age)(struct config_plugin * config, const struct _u_request * request, const char * scope_list); // Client callback functions /* Return the scopes the user had granted access to the client */ json_t * (* glewlwyd_callback_get_client_granted_scopes)(struct config_plugin * config, const char * client_id, const char * username, const char * scope_list); // User CRUD json_t * (* glewlwyd_plugin_callback_get_user_list)(struct config_plugin * config, const char * pattern, size_t offset, size_t limit); json_t * (* glewlwyd_plugin_callback_get_user)(struct config_plugin * config, const char * username); json_t * (* glewlwyd_plugin_callback_get_user_profile)(struct config_plugin * config, const char * username); json_t * (* glewlwyd_plugin_callback_is_user_valid)(struct config_plugin * config, const char * username, json_t * j_user, int add); int (* glewlwyd_plugin_callback_add_user)(struct config_plugin * config, json_t * j_user); int (* glewlwyd_plugin_callback_set_user)(struct config_plugin * config, const char * username, json_t * j_user); int (* glewlwyd_plugin_callback_user_update_password)(struct config_plugin * config, const char * username, const char * password); int (* glewlwyd_plugin_callback_delete_user)(struct config_plugin * config, const char * username); // Client CRUD json_t * (* glewlwyd_plugin_callback_get_client_list)(struct config_plugin * config, const char * pattern, size_t offset, size_t limit); json_t * (* glewlwyd_plugin_callback_get_client)(struct config_plugin * config, const char * client_id); json_t * (* glewlwyd_plugin_callback_is_client_valid)(struct config_plugin * config, const char * client_id, json_t * j_client, int add); int (* glewlwyd_plugin_callback_add_client)(struct config_plugin * config, json_t * j_client); int (* glewlwyd_plugin_callback_set_client)(struct config_plugin * config, const char * client_id, json_t * j_client); int (* glewlwyd_plugin_callback_delete_client)(struct config_plugin * config, const char * client_id); // Register scheme functions json_t * (* glewlwyd_plugin_callback_scheme_register)(struct config_plugin * config, const char * mod_name, const struct _u_request * http_request, const char * username, json_t * j_scheme_data); json_t * (* glewlwyd_plugin_callback_scheme_register_get)(struct config_plugin * config, const char * mod_name, const struct _u_request * http_request, const char * username); int (* glewlwyd_plugin_callback_scheme_deregister)(struct config_plugin * config, const char * mod_name, const char * username); int (* glewlwyd_plugin_callback_scheme_can_use)(struct config_plugin * config, const char * mod_name, const char * username); // Misc functions /* Return this plugin external url root */ char * (* glewlwyd_callback_get_plugin_external_url)(struct config_plugin * config, const char * name); /* Return the external url of the login page */ char * (* glewlwyd_callback_get_login_url)(struct config_plugin * config, const char * client_id, const char * scope_list, const char * callback_url, struct _u_map * additional_parameters); /* Generates a hashed value of the data given using the format "{hash_type}<hash_value>", the hash type is specified in the config file */ char * (* glewlwyd_callback_generate_hash)(struct config_plugin * config, const char * data); }; ``` A plugin must have the following functions defined and available: ```C /** * * plugin_module_load * * Executed once when Glewlwyd service is started * Used to identify the module * You can also use it to load resources that are required once for all * instance modules for example * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * name: string, mandatory, name of the module, must be unique among other scheme modules * display_name: string, optional, long name of the module * description: string, optional, description for the module * } * * Example: * { * result: G_OK, * name: "mock", * display_name: "Mock scheme module", * description: "Mock scheme module for glewlwyd tests" * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ json_t * plugin_module_load(struct config_plugin * config); ``` ```C /** * * plugin_module_unload * * Executed once when Glewlwyd service is stopped * You can use it to release resources that are required once for all * instance modules for example * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ int plugin_module_unload(struct config_plugin * config); ``` ```C /** * * plugin_module_init * * Initialize an instance of this module declared in Glewlwyd service. * If required, you must dynamically allocate a pointer to the configuration * for this instance and pass it to *cls * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, G_ERROR_PARAM on input parameters error, another value on error) * error: array of strings containg the list of input errors, mandatory on result G_ERROR_PARAM, ignored otherwise * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_parameters: used to initialize an instance in JSON format * The module must validate itself its parameters * @parameter cls: will contain an allocated void * pointer that will be sent back * as void * in all module functions * */ json_t * plugin_module_init(struct config_plugin * config, const char * name, json_t * j_parameters, void ** cls); ``` ```C /** * * plugin_module_close * * Close an instance of this module declared in Glewlwyd service. * You must free the memory previously allocated in * the client_module_init function as void * cls * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter cls: pointer to the void * cls value allocated in client_module_init * */ int plugin_module_close(struct config_plugin * config, const char * name, void * cls); ``` ```C /** * * plugin_user_revoke * * Revoke data created for the user 'username' before removing the user * from the system * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: the username whose data must be revoked * @parameter cls: pointer to the void * cls value allocated in client_module_init * */ int plugin_user_revoke(struct config_plugin * config, const char * username, void * cls) { UNUSED(config); UNUSED(username); UNUSED(cls); return G_OK; } ``` ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glewlwyd-2.6.1/src/plugin/check_session_iframe_template.html����������������������������������������0000664�0000000�0000000�00000004161�14156463140�0024375�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!-- This file is the template used to generate (manually-ish) the check_session_iframe content in OIDC plugin To apply update, minimise the code below, ^C^V in the function generate_check_session_iframe in the source file plugin_oidc.c, and recompile the plugin Copyright 2020 Nicolas Mora <mail@babelouest.org> The MIT License (MIT) --> <html> <head> <meta charset="utf-8"> <title>Glewlwyd check_session_iframe iframe glewlwyd-2.6.1/src/plugin/mock.c000066400000000000000000000232341415646314000165500ustar00rootroot00000000000000/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * mock plugin * * Copyright 2019-2020 Nicolas Mora * * The MIT License (MIT) * * 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. * */ #include "glewlwyd-common.h" /** * * Note on plugin * * By design a plugin has on those 4 functions mandatory: * - json_t * plugin_module_load(struct config_plugin * config) * - int plugin_module_unload(struct config_plugin * config) * - int plugin_module_init(struct config_plugin * config, const char * name, json_t * j_parameters, void ** cls) * - int plugin_module_close(struct config_plugin * config, const char * name, void * cls) * * The purpose of Glewlwyd core is to provide a environment to manage users, clients, connections and scopes, and to provide webservices as modules * Although, the purpose of a plugin is to provide web services for specific goals. * * To achieve this purpose, the structure config_plugin mostly contains pointer to glewlwyd functions: * * struct config_plugin { * struct config_elements * glewlwyd_config; * int (* glewlwyd_callback_add_plugin_endpoint)(struct config_plugin * config, const char * method, const char * name, const char * url, unsigned int priority, int (* callback)(const struct _u_request * request, struct _u_response * response, void * user_data), void * user_data); * int (* glewlwyd_callback_remove_plugin_endpoint)(struct config_plugin * config, const char * method, const char * name, const char * url); * * // Session callback functions * json_t * (* glewlwyd_callback_check_session_valid)(struct config_plugin * config, const struct _u_request * request, const char * scope_list); * json_t * (* glewlwyd_callback_check_user_valid)(struct config_plugin * config, const char * username, const char * password, const char * scope_list); * json_t * (* glewlwyd_callback_check_client_valid)(struct config_plugin * config, const char * client_id, const char * password); * int (* glewlwyd_callback_trigger_session_used)(struct config_plugin * config, const struct _u_request * request, const char * scope_list); * * // Client callback functions * json_t * (* glewlwyd_callback_get_client_granted_scopes)(struct config_plugin * config, const char * client_id, const char * username, const char * scope_list); * * // User CRUD * json_t * (* glewlwyd_plugin_callback_get_user_list)(struct config_plugin * config, const char * pattern, size_t offset, size_t limit); * json_t * (* glewlwyd_plugin_callback_get_user)(struct config_plugin * config, const char * username); * json_t * (* glewlwyd_plugin_callback_get_user_profile)(struct config_plugin * config, const char * username); * int (* glewlwyd_plugin_callback_add_user)(struct config_plugin * config, json_t * j_user); * int (* glewlwyd_plugin_callback_set_user)(struct config_plugin * config, const char * username, json_t * j_user); * int (* glewlwyd_plugin_callback_delete_user)(struct config_plugin * config, const char * username); * * // Misc functions * char * (* glewlwyd_callback_get_plugin_external_url)(struct config_plugin * config, const char * name); * char * (* glewlwyd_callback_get_login_url)(struct config_plugin * config, const char * client_id, const char * scope_list, const char * callback_url, struct _u_map * additional_parameters); * char * (* glewlwyd_callback_generate_hash)(struct config_plugin * config, const char * data); * }; * * The functions glewlwyd_callback_add_plugin_endpoint and glewlwyd_callback_remove_plugin_endpoint exist to dynamically add or remove endpoints * A plugin endpoint url parameter is relative to glewlwyd's api prefix, e.g. /api/, this api prefix will be added to the endpoint url by Glewlwyd * Also, a plugin endpoint priority is relative to the value GLEWLWYD_CALLBACK_PRIORITY_PLUGIN, so the endpoint is supposed to have a lower priority than Glewlwyd's core endpoints * */ /** * * plugin_module_load * * Executed once when Glewlwyd service is started * Used to identify the module * You can also use it to load resources that are required once for all * instance modules for example * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * name: string, mandatory, name of the module, must be unique among other scheme modules * display_name: string, optional, long name of the module * description: string, optional, description for the module * } * * Example: * { * result: G_OK, * name: "mock", * display_name: "Mock scheme module", * description: "Mock scheme module for glewlwyd tests" * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ json_t * plugin_module_load(struct config_plugin * config) { UNUSED(config); return json_pack("{sissssss}", "result", G_OK, "name", "mock", "display_name", "Mock plugin", "description", "Mock plugin description"); } /** * * plugin_module_unload * * Executed once when Glewlwyd service is stopped * You can use it to release resources that are required once for all * instance modules for example * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ int plugin_module_unload(struct config_plugin * config) { UNUSED(config); y_log_message(Y_LOG_LEVEL_DEBUG, "plugin_module_unload - success"); return G_OK; } /** * * plugin_module_init * * Initialize an instance of this module declared in Glewlwyd service. * If required, you must dynamically allocate a pointer to the configuration * for this instance and pass it to *cls * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, G_ERROR_PARAM on input parameters error, another value on error) * error: array of strings containg the list of input errors, mandatory on result G_ERROR_PARAM, ignored otherwise * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_parameters: used to initialize an instance in JSON format * The module must validate itself its parameters * @parameter cls: will contain an allocated void * pointer that will be sent back * as void * in all module functions * */ json_t * plugin_module_init(struct config_plugin * config, const char * name, json_t * j_parameters, void ** cls) { UNUSED(config); UNUSED(name); UNUSED(cls); json_t * j_return; if (json_object_get(j_parameters, "error") == NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "plugin_module_init - success"); j_return = json_pack("{si}", "result", G_OK); } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "Error input parameters"); } return j_return; } /** * * plugin_module_close * * Close an instance of this module declared in Glewlwyd service. * You must free the memory previously allocated in * the client_module_init function as void * cls * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter cls: pointer to the void * cls value allocated in client_module_init * */ int plugin_module_close(struct config_plugin * config, const char * name, void * cls) { UNUSED(config); UNUSED(name); UNUSED(cls); y_log_message(Y_LOG_LEVEL_DEBUG, "plugin_module_close - success"); return G_OK; } /** * * plugin_user_revoke * * Revoke data created for the user 'username' before removing the user * from the system * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: the username whose data must be revoked * @parameter cls: pointer to the void * cls value allocated in client_module_init * */ int plugin_user_revoke(struct config_plugin * config, const char * username, void * cls) { UNUSED(config); UNUSED(username); UNUSED(cls); return G_OK; } glewlwyd-2.6.1/src/plugin/protocol_oauth2.c000066400000000000000000007243371415646314000207560ustar00rootroot00000000000000/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * Legacy (Glewlwyd 1.x) OAuth2 plugin * * Copyright 2016-2020 Nicolas Mora * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include "glewlwyd-common.h" #include "glewlwyd_resource.h" #define OAUTH2_SALT_LENGTH 16 #define GLEWLWYD_ACCESS_TOKEN_EXP_DEFAULT 3600 #define GLEWLWYD_REFRESH_TOKEN_EXP_DEFAULT 1209600 #define GLEWLWYD_CODE_EXP_DEFAULT 600 #define GLEWLWYD_CODE_CHALLENGE_MAX_LENGTH 128 #define GLEWLWYD_CODE_CHALLENGE_S256_PREFIX "{SHA256}" #define GLEWLWYD_CHECK_JWT_USERNAME "myrddin" #define GLEWLWYD_CHECK_JWT_SCOPE "caledonia" #define GLEWLWYD_PLUGIN_OAUTH2_TABLE_CODE "gpg_code" #define GLEWLWYD_PLUGIN_OAUTH2_TABLE_CODE_SCOPE "gpg_code_scope" #define GLEWLWYD_PLUGIN_OAUTH2_TABLE_REFRESH_TOKEN "gpg_refresh_token" #define GLEWLWYD_PLUGIN_OAUTH2_TABLE_REFRESH_TOKEN_SCOPE "gpg_refresh_token_scope" #define GLEWLWYD_PLUGIN_OAUTH2_TABLE_ACCESS_TOKEN "gpg_access_token" #define GLEWLWYD_PLUGIN_OAUTH2_TABLE_ACCESS_TOKEN_SCOPE "gpg_access_token_scope" #define GLEWLWYD_PLUGIN_OAUTH2_TABLE_DEVICE_AUTHORIZATION "gpg_device_authorization" #define GLEWLWYD_PLUGIN_OAUTH2_TABLE_DEVICE_AUTHORIZATION_SCOPE "gpg_device_authorization_scope" #define GLWD_METRICS_OAUTH2_CODE "glewlwyd_oauth2_code" #define GLWD_METRICS_OAUTH2_DEVICE_CODE "glewlwyd_oauth2_device_code" #define GLWD_METRICS_OAUTH2_REFRESH_TOKEN "glewlwyd_oauth2_refresh_token" #define GLWD_METRICS_OAUTH2_USER_ACCESS_TOKEN "glewlwyd_oauth2_access_token" #define GLWD_METRICS_OAUTH2_CLIENT_ACCESS_TOKEN "glewlwyd_oauth2_client_token" #define GLWD_METRICS_OAUTH2_UNAUTHORIZED_CLIENT "glewlwyd_oauth2_unauthorized_client" #define GLWD_METRICS_OAUTH2_INVALID_CODE "glewlwyd_oauth2_invalid_code" #define GLWD_METRICS_OAUTH2_INVALID_DEVICE_CODE "glewlwyd_oauth2_invalid_device_code" #define GLWD_METRICS_OAUTH2_INVALID_REFRESH_TOKEN "glewlwyd_oauth2_invalid_refresh_token" #define GLWD_METRICS_OAUTH2_INVALID_ACCESS_TOKEN "glewlwyd_oauth2_invalid_acccess_token" // Authorization types available #define GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE 0 #define GLEWLWYD_AUTHORIZATION_TYPE_IMPLICIT 1 #define GLEWLWYD_AUTHORIZATION_TYPE_RESOURCE_OWNER_PASSWORD_CREDENTIALS 2 #define GLEWLWYD_AUTHORIZATION_TYPE_CLIENT_CREDENTIALS 3 #define GLEWLWYD_AUTHORIZATION_TYPE_REFRESH_TOKEN 4 #define GLEWLWYD_AUTHORIZATION_TYPE_DELETE_TOKEN 5 #define GLEWLWYD_AUTHORIZATION_TYPE_DEVICE_AUTHORIZATION 6 #define GLEWLWYD_DEVICE_AUTH_DEFAUT_EXPIRATION 600 #define GLEWLWYD_DEVICE_AUTH_DEFAUT_INTERVAL 5 #define GLEWLWYD_DEVICE_AUTH_DEVICE_CODE_LENGTH 32 #define GLEWLWYD_DEVICE_AUTH_USER_CODE_LENGTH 8 struct _oauth2_config { struct config_plugin * glewlwyd_config; jwt_t * jwt_key; const char * name; json_t * j_params; json_int_t access_token_duration; json_int_t refresh_token_duration; json_int_t code_duration; unsigned short int refresh_token_rolling; unsigned short int auth_type_enabled[5]; pthread_mutex_t insert_lock; struct _glewlwyd_resource_config * glewlwyd_resource_config; struct _glewlwyd_resource_config * introspect_revoke_resource_config; }; static json_t * check_parameters (json_t * j_params) { json_t * j_element = NULL, * j_return, * j_error = json_array(); size_t index = 0; int ret = G_OK; if (j_error != NULL) { if (j_params == NULL) { json_array_append_new(j_error, json_string("parameters invalid")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "jwt-type") == NULL || !json_is_string(json_object_get(j_params, "jwt-type"))) { json_array_append_new(j_error, json_string("jwt-type must be a string and have one of the following values: 'rsa', 'ecdsa', 'sha', 'rsa-pss', 'eddsa'")); ret = G_ERROR_PARAM; } if (0 != o_strcmp("rsa", json_string_value(json_object_get(j_params, "jwt-type"))) && 0 != o_strcmp("ecdsa", json_string_value(json_object_get(j_params, "jwt-type"))) && 0 != o_strcmp("sha", json_string_value(json_object_get(j_params, "jwt-type"))) && 0 != o_strcmp("rsa-pss", json_string_value(json_object_get(j_params, "jwt-type"))) && 0 != o_strcmp("eddsa", json_string_value(json_object_get(j_params, "jwt-type")))) { json_array_append_new(j_error, json_string("jwt-type must be a string and have one of the following values: 'rsa', 'ecdsa', 'sha', 'rsa-pss', 'eddsa'")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "jwt-key-size") == NULL || !json_is_string(json_object_get(j_params, "jwt-key-size"))) { json_array_append_new(j_error, json_string("jwt-key-size must be a string and have one of the following values: '256', '384', '512'")); ret = G_ERROR_PARAM; } if (0 != o_strcmp("256", json_string_value(json_object_get(j_params, "jwt-key-size"))) && 0 != o_strcmp("384", json_string_value(json_object_get(j_params, "jwt-key-size"))) && 0 != o_strcmp("512", json_string_value(json_object_get(j_params, "jwt-key-size")))) { json_array_append_new(j_error, json_string("jwt-key-size must be a string and have one of the following values: '256', '384', '512'")); ret = G_ERROR_PARAM; } if ((0 == o_strcmp("rsa", json_string_value(json_object_get(j_params, "jwt-type"))) || 0 == o_strcmp("ecdsa", json_string_value(json_object_get(j_params, "jwt-type")))) && (json_object_get(j_params, "key") == NULL || json_object_get(j_params, "cert") == NULL || !json_is_string(json_object_get(j_params, "key")) || !json_is_string(json_object_get(j_params, "cert")) || !json_string_length(json_object_get(j_params, "key")) || !json_string_length(json_object_get(j_params, "cert")))) { json_array_append_new(j_error, json_string("Properties 'cert' and 'key' are mandatory and must be strings")); ret = G_ERROR_PARAM; } if (0 == o_strcmp("sha", json_string_value(json_object_get(j_params, "jwt-type"))) && (json_object_get(j_params, "key") == NULL || !json_is_string(json_object_get(j_params, "key")) || !json_string_length(json_object_get(j_params, "key")))) { json_array_append_new(j_error, json_string("Property 'key' is mandatory and must be a string")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "access-token-duration") == NULL || !json_is_integer(json_object_get(j_params, "access-token-duration")) || json_integer_value(json_object_get(j_params, "access-token-duration")) <= 0) { json_array_append_new(j_error, json_string("Property 'access-token-duration' is mandatory and must be a non null positive integer")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "refresh-token-duration") == NULL || !json_is_integer(json_object_get(j_params, "refresh-token-duration")) || json_integer_value(json_object_get(j_params, "refresh-token-duration")) <= 0) { json_array_append_new(j_error, json_string("Property 'refresh-token-duration' is mandatory and must be a non null positive integer")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "refresh-token-rolling") != NULL && !json_is_boolean(json_object_get(j_params, "refresh-token-rolling"))) { json_array_append_new(j_error, json_string("Property 'refresh-token-rolling' is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "auth-type-code-enabled") == NULL || !json_is_boolean(json_object_get(j_params, "auth-type-code-enabled"))) { json_array_append_new(j_error, json_string("Property 'auth-type-code-enabled' is mandatory and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "auth-type-implicit-enabled") == NULL || !json_is_boolean(json_object_get(j_params, "auth-type-implicit-enabled"))) { json_array_append_new(j_error, json_string("Property 'auth-type-implicit-enabled' is mandatory and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "auth-type-password-enabled") == NULL || !json_is_boolean(json_object_get(j_params, "auth-type-password-enabled"))) { json_array_append_new(j_error, json_string("Property 'auth-type-password-enabled' is mandatory and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "auth-type-client-enabled") == NULL || !json_is_boolean(json_object_get(j_params, "auth-type-client-enabled"))) { json_array_append_new(j_error, json_string("Property 'auth-type-client-enabled' is mandatory and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "auth-type-device-enabled") != NULL && !json_is_boolean(json_object_get(j_params, "auth-type-device-enabled"))) { json_array_append_new(j_error, json_string("Property 'auth-type-device-enabled' is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "auth-type-refresh-enabled") == NULL || !json_is_boolean(json_object_get(j_params, "auth-type-refresh-enabled"))) { json_array_append_new(j_error, json_string("Property 'auth-type-refresh-enabled' is mandatory and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "scope") != NULL) { if (!json_is_array(json_object_get(j_params, "scope"))) { json_array_append_new(j_error, json_string("Property 'scope' is optional and must be an array")); ret = G_ERROR_PARAM; } else { json_array_foreach(json_object_get(j_params, "scope"), index, j_element) { if (!json_is_object(j_element)) { json_array_append_new(j_error, json_string("'scope' element must be a JSON object")); ret = G_ERROR_PARAM; } else { if (json_object_get(j_element, "name") == NULL || !json_is_string(json_object_get(j_element, "name")) || !json_string_length(json_object_get(j_element, "name"))) { json_array_append_new(j_error, json_string("'scope' element must have a property 'name' of type string and non empty")); ret = G_ERROR_PARAM; } else if (json_object_get(j_element, "refresh-token-rolling") != NULL && !json_is_boolean(json_object_get(j_element, "refresh-token-rolling"))) { json_array_append_new(j_error, json_string("'scope' element can have a property 'refresh-token-rolling' of type boolean")); ret = G_ERROR_PARAM; } else if (json_object_get(j_element, "refresh-token-duration") != NULL && (!json_is_integer(json_object_get(j_element, "refresh-token-duration")) || json_integer_value(json_object_get(j_element, "refresh-token-duration")) < 0)) { json_array_append_new(j_error, json_string("'scope' element can have a property 'refresh-token-duration' of type integer and non null positive value")); ret = G_ERROR_PARAM; } } } } } else if (json_object_get(j_params, "additional-parameters") != NULL) { if (!json_is_array(json_object_get(j_params, "additional-parameters"))) { json_array_append_new(j_error, json_string("Property 'additional-parameters' is optional and must be an array")); ret = G_ERROR_PARAM; } else { json_array_foreach(json_object_get(j_params, "additional-parameters"), index, j_element) { if (!json_is_object(j_element)) { json_array_append_new(j_error, json_string("'additional-parameters' element must be a JSON object")); ret = G_ERROR_PARAM; } else { if (json_object_get(j_element, "user-parameter") == NULL || !json_is_string(json_object_get(j_element, "user-parameter")) || !json_string_length(json_object_get(j_element, "user-parameter"))) { json_array_append_new(j_error, json_string("'additional-parameters' element must have a property 'user-parameter' of type string and non empty")); ret = G_ERROR_PARAM; } else if (json_object_get(j_element, "token-parameter") == NULL || !json_is_string(json_object_get(j_element, "token-parameter")) || !json_string_length(json_object_get(j_element, "token-parameter"))) { json_array_append_new(j_error, json_string("'additional-parameters' element must have a property 'token-parameter' of type string and non empty, forbidden values are: 'username', 'salt', 'type', 'iat', 'expires_in', 'scope'")); ret = G_ERROR_PARAM; } else if (0 == o_strcmp(json_string_value(json_object_get(j_element, "token-parameter")), "username") || 0 == o_strcmp(json_string_value(json_object_get(j_element, "token-parameter")), "salt") || 0 == o_strcmp(json_string_value(json_object_get(j_element, "token-parameter")), "type") || 0 == o_strcmp(json_string_value(json_object_get(j_element, "token-parameter")), "iat") || 0 == o_strcmp(json_string_value(json_object_get(j_element, "token-parameter")), "expires_in") || 0 == o_strcmp(json_string_value(json_object_get(j_element, "token-parameter")), "scope")) { json_array_append_new(j_error, json_string("'additional-parameters' element must have a property 'token-parameter' of type string and non empty, forbidden values are: 'username', 'salt', 'type', 'iat', 'expires_in', 'scope'")); ret = G_ERROR_PARAM; } } } } } if (json_object_get(j_params, "pkce-allowed") != NULL && !json_is_boolean(json_object_get(j_params, "pkce-allowed"))) { json_array_append_new(j_error, json_string("Property 'pkce-allowed' is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "pkce-method-plain-allowed") != NULL && json_object_get(j_params, "pkce-allowed") == json_true() && !json_is_boolean(json_object_get(j_params, "pkce-method-plain-allowed"))) { json_array_append_new(j_error, json_string("Property 'pkce-method-plain-allowed' is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "introspection-revocation-allowed") != NULL && !json_is_boolean(json_object_get(j_params, "introspection-revocation-allowed"))) { json_array_append_new(j_error, json_string("Property 'introspection-revocation-allowed' is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "introspection-revocation-allowed") == json_true()) { if (json_object_get(j_params, "introspection-revocation-auth-scope") != NULL && !json_is_array(json_object_get(j_params, "introspection-revocation-auth-scope"))) { json_array_append_new(j_error, json_string("Property 'introspection-revocation-auth-scope' is optional and must be a JSON array of strings, maximum 128 characters")); ret = G_ERROR_PARAM; } else { json_array_foreach(json_object_get(j_params, "introspection-revocation-auth-scope"), index, j_element) { if (!json_is_string(j_element) || json_string_length(j_element) > 128) { json_array_append_new(j_error, json_string("Property 'introspection-revocation-auth-scope' is optional and must be a JSON array of strings, maximum 128 characters")); ret = G_ERROR_PARAM; } } } if (json_object_get(j_params, "introspection-revocation-allow-target-client") != NULL && !json_is_boolean(json_object_get(j_params, "introspection-revocation-allow-target-client"))) { json_array_append_new(j_error, json_string("Property 'introspection-revocation-allow-target-client' is optional and must be a boolean")); ret = G_ERROR_PARAM; } } if (json_object_get(j_params, "auth-type-device-enabled") == json_true()) { if (json_object_get(j_params, "device-authorization-expiration") != NULL && json_integer_value(json_object_get(j_params, "device-authorization-expiration")) <= 0) { json_array_append_new(j_error, json_string("Property 'device-authorization-expiration' is optional and must be a non null positive integer")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "device-authorization-interval") != NULL && json_integer_value(json_object_get(j_params, "device-authorization-interval")) <= 0) { json_array_append_new(j_error, json_string("Property 'device-authorization-interval' is optional and must be a non null positive integer")); ret = G_ERROR_PARAM; } } if (json_array_size(j_error) && ret == G_ERROR_PARAM) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_error); } else { j_return = json_pack("{si}", "result", ret); } json_decref(j_error); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_parameters oauth2 - Error allocating resources for j_error"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } return j_return; } /** * * Generates a query string based on url and post parameters of a request * Returned value must be o_free'd after use * */ static char * generate_query_parameters(struct _u_map * map_url, struct _u_map * map_post_body) { char * query = NULL, * param, * tmp, * value; const char ** keys; int i; if (map_url == NULL && map_post_body == NULL) { return NULL; } else { if (map_url != NULL) { keys = u_map_enum_keys(map_url); for (i=0; keys[i] != NULL; i++) { value = ulfius_url_encode((char *)u_map_get(map_url, keys[i])); param = msprintf("%s=%s", keys[i], value); o_free(value); if (query == NULL) { query = o_strdup(param); } else { tmp = msprintf("%s&%s", query, param); o_free(query); query = tmp; } o_free(param); } } if (map_post_body != NULL) { keys = u_map_enum_keys(map_post_body); for (i=0; keys[i] != NULL; i++) { value = ulfius_url_encode((char *)u_map_get(map_post_body, keys[i])); param = msprintf("%s=%s", keys[i], value); o_free(value); if (query == NULL) { query = o_strdup(param); } else { tmp = msprintf("%s&%s", query, param); o_free(query); query = tmp; } o_free(param); } } } return query; } static int serialize_access_token(struct _oauth2_config * config, uint auth_type, json_int_t gpgr_id, const char * username, const char * client_id, const char * scope_list, time_t now, const char * issued_for, const char * user_agent, const char * access_token) { json_t * j_query, * j_last_id; int res, ret, i; char * issued_at_clause, ** scope_array = NULL, * access_token_hash = NULL; if (pthread_mutex_lock(&config->insert_lock)) { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_access_token - oauth2 - Error pthread_mutex_lock"); ret = G_ERROR; } else { if ((access_token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, access_token)) != NULL) { if (issued_for != NULL && now > 0) { if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { issued_at_clause = msprintf("FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { issued_at_clause = msprintf("TO_TIMESTAMP(%u)", (now)); } else { // HOEL_DB_TYPE_SQLITE issued_at_clause = msprintf("%u", (now)); } j_query = json_pack("{sss{sssisososos{ss}ssssss}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_ACCESS_TOKEN, "values", "gpga_plugin_name", config->name, "gpga_authorization_type", auth_type, "gpgr_id", gpgr_id?json_integer(gpgr_id):json_null(), "gpga_username", username!=NULL?json_string(username):json_null(), "gpga_client_id", client_id!=NULL?json_string(client_id):json_null(), "gpga_issued_at", "raw", issued_at_clause, "gpga_issued_for", issued_for, "gpga_user_agent", user_agent!=NULL?user_agent:"", "gpga_token_hash", access_token_hash); o_free(issued_at_clause); res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_last_id = h_last_insert_id(config->glewlwyd_config->glewlwyd_config->conn); if (j_last_id != NULL) { if (split_string(scope_list, " ", &scope_array) > 0) { j_query = json_pack("{sss[]}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_ACCESS_TOKEN_SCOPE, "values"); if (j_query != NULL) { for (i=0; scope_array[i] != NULL; i++) { json_array_append_new(json_object_get(j_query, "values"), json_pack("{sOss}", "gpga_id", j_last_id, "gpgas_scope", scope_array[i])); } res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_access_token - oauth2 - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_access_token - oauth2 - Error json_pack"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_access_token - oauth2 - Error split_string"); ret = G_ERROR; } free_string_array(scope_array); } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_access_token - oauth2 - Error h_last_insert_id"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } json_decref(j_last_id); } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_access_token - oauth2 - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { ret = G_ERROR_PARAM; } o_free(access_token_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_access_token - oauth2 - Error glewlwyd_callback_generate_hash"); ret = G_ERROR; } pthread_mutex_unlock(&config->insert_lock); } return ret; } /** * Generates a client_access_token from the specified parameters that are considered valid */ static char * generate_client_access_token(struct _oauth2_config * config, const char * client_id, const char * scope_list, time_t now, const char * ip_source) { jwt_t * jwt; char * token = NULL; char salt[OAUTH2_SALT_LENGTH + 1] = {0}; jwt = r_jwt_copy(config->jwt_key); if (jwt != NULL) { // Build jwt payload rand_string_nonce(salt, OAUTH2_SALT_LENGTH); r_jwt_set_claim_str_value(jwt, "salt", salt); r_jwt_set_claim_str_value(jwt, "client_id", client_id); r_jwt_set_claim_str_value(jwt, "type", "client_token"); r_jwt_set_claim_str_value(jwt, "scope", scope_list); r_jwt_set_claim_int_value(jwt, "iat", now); r_jwt_set_claim_int_value(jwt, "expires_in", config->access_token_duration); r_jwt_set_claim_int_value(jwt, "exp", (((json_int_t)now)+config->access_token_duration)); r_jwt_set_claim_int_value(jwt, "nbf", now); token = r_jwt_serialize_signed(jwt, NULL, 0); if (token == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "generate_client_access_token - oauth2 - Error generating token"); } else { y_log_message(Y_LOG_LEVEL_INFO, "Event oauth2 - Plugin '%s' - Access token generated for client '%s' with scope list '%s', origin: %s", config->name, client_id, scope_list, ip_source); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_client_access_token - oauth2 - Error cloning jwt"); } r_jwt_free(jwt); return token; } static char * generate_access_token(struct _oauth2_config * config, const char * username, const char * client_id, json_t * j_user, const char * scope_list, time_t now, const char * ip_source) { char salt[OAUTH2_SALT_LENGTH + 1] = {0}; jwt_t * jwt = NULL; char * token = NULL, * property = NULL; json_t * j_element = NULL, * j_value; size_t index = 0, index_p = 0; if ((jwt = r_jwt_copy(config->jwt_key)) != NULL) { rand_string_nonce(salt, OAUTH2_SALT_LENGTH); r_jwt_set_claim_str_value(jwt, "username", username); r_jwt_set_claim_str_value(jwt, "salt", salt); r_jwt_set_claim_str_value(jwt, "type", "access_token"); r_jwt_set_claim_int_value(jwt, "iat", now); r_jwt_set_claim_int_value(jwt, "expires_in", config->access_token_duration); r_jwt_set_claim_int_value(jwt, "exp", (((json_int_t)now)+config->access_token_duration)); r_jwt_set_claim_int_value(jwt, "nbf", now); if (scope_list != NULL) { r_jwt_set_claim_str_value(jwt, "scope", scope_list); } if (json_object_get(config->j_params, "additional-parameters") != NULL && j_user != NULL) { json_array_foreach(json_object_get(config->j_params, "additional-parameters"), index, j_element) { if (json_is_string(json_object_get(j_user, json_string_value(json_object_get(j_element, "user-parameter")))) && json_string_length(json_object_get(j_user, json_string_value(json_object_get(j_element, "user-parameter"))))) { r_jwt_set_claim_str_value(jwt, json_string_value(json_object_get(j_element, "token-parameter")), json_string_value(json_object_get(j_user, json_string_value(json_object_get(j_element, "user-parameter"))))); } else if (json_is_array(json_object_get(j_user, json_string_value(json_object_get(j_element, "user-parameter"))))) { json_array_foreach(json_object_get(j_user, json_string_value(json_object_get(j_element, "user-parameter"))), index_p, j_value) { property = mstrcatf(property, ",%s", json_string_value(j_value)); } if (o_strlen(property)) { r_jwt_set_claim_str_value(jwt, json_string_value(json_object_get(j_element, "token-parameter")), property+1); // Skip first ',' } else { r_jwt_set_claim_str_value(jwt, json_string_value(json_object_get(j_element, "token-parameter")), ""); } o_free(property); property = NULL; } } } token = r_jwt_serialize_signed(jwt, NULL, 0); if (token == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "generate_access_token - oauth2 - oauth2 - Error jwt_encode_str"); } else { y_log_message(Y_LOG_LEVEL_INFO, "Event oauth2 - Plugin '%s' - Access token generated for client '%s' granted by user '%s' with scope list '%s', origin: %s", config->name, client_id, username, scope_list, ip_source); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_access_token - oauth2 - Error jwt_dup"); } r_jwt_free(jwt); return token; } static json_t * serialize_refresh_token(struct _oauth2_config * config, uint auth_type, json_int_t gpgc_id, const char * username, const char * client_id, const char * scope_list, time_t now, json_int_t duration, uint rolling, const char * token, const char * issued_for, const char * user_agent) { char * token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, token); json_t * j_query, * j_return, * j_last_id; int res, i; char * issued_at_clause, * expires_at_clause, * last_seen_clause, ** scope_array = NULL; if (pthread_mutex_lock(&config->insert_lock)) { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_refresh_token - oauth2 - Error pthread_mutex_lock"); j_return = json_pack("{si}", "result", G_ERROR); } else { if (token_hash != NULL && username != NULL && issued_for != NULL && now > 0 && duration > 0) { json_error_t error; if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { issued_at_clause = msprintf("FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { issued_at_clause = msprintf("TO_TIMESTAMP(%u)", (now)); } else { // HOEL_DB_TYPE_SQLITE issued_at_clause = msprintf("%u", (now)); } if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { last_seen_clause = msprintf("FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { last_seen_clause = msprintf("TO_TIMESTAMP(%u)", (now)); } else { // HOEL_DB_TYPE_SQLITE last_seen_clause = msprintf("%u", (now)); } if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("FROM_UNIXTIME(%u)", (now + (unsigned int)duration)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("TO_TIMESTAMP(%u)", (now + (unsigned int)duration )); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("%u", (now + (unsigned int)duration)); } j_query = json_pack_ex(&error, 0, "{sss{ss si so ss so s{ss} s{ss} s{ss} sI si ss ss ss}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_REFRESH_TOKEN, "values", "gpgr_plugin_name", config->name, "gpgr_authorization_type", auth_type, "gpgc_id", gpgc_id?json_integer(gpgc_id):json_null(), "gpgr_username", username, "gpgr_client_id", client_id!=NULL?json_string(client_id):json_null(), "gpgr_issued_at", "raw", issued_at_clause, "gpgr_last_seen", "raw", last_seen_clause, "gpgr_expires_at", "raw", expires_at_clause, "gpgr_duration", duration, "gpgr_rolling_expiration", rolling, "gpgr_token_hash", token_hash, "gpgr_issued_for", issued_for, "gpgr_user_agent", user_agent!=NULL?user_agent:""); o_free(issued_at_clause); o_free(expires_at_clause); o_free(last_seen_clause); res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_last_id = h_last_insert_id(config->glewlwyd_config->glewlwyd_config->conn); if (j_last_id != NULL) { if (split_string(scope_list, " ", &scope_array) > 0) { j_query = json_pack("{sss[]}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_REFRESH_TOKEN_SCOPE, "values"); if (j_query != NULL) { for (i=0; scope_array[i] != NULL; i++) { json_array_append_new(json_object_get(j_query, "values"), json_pack("{sOss}", "gpgr_id", j_last_id, "gpgrs_scope", scope_array[i])); } res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_return = json_pack("{sisO}", "result", G_OK, "gpgr_id", j_last_id); } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_refresh_token - oauth2 - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_refresh_token - oauth2 - Error json_pack"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_refresh_token - oauth2 - Error split_string"); j_return = json_pack("{si}", "result", G_ERROR); } free_string_array(scope_array); } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_refresh_token - oauth2 - Error h_last_insert_id"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_last_id); } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_refresh_token - oauth2 - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } o_free(token_hash); pthread_mutex_unlock(&config->insert_lock); } return j_return; } static char * generate_refresh_token(struct _oauth2_config * config, const char * client_id, const char * username, const char * scope_list, time_t now, const char * ip_source) { jwt_t * jwt; char * token = NULL; char salt[OAUTH2_SALT_LENGTH + 1] = {0}; if ((jwt = r_jwt_copy(config->jwt_key)) != NULL) { // Build jwt payload rand_string_nonce(salt, OAUTH2_SALT_LENGTH); r_jwt_set_claim_str_value(jwt, "salt", salt); r_jwt_set_claim_str_value(jwt, "username", username); r_jwt_set_claim_str_value(jwt, "type", "refresh_token"); r_jwt_set_claim_int_value(jwt, "iat", now); if (scope_list != NULL) { r_jwt_set_claim_str_value(jwt, "scope", scope_list); } if (client_id != NULL) { r_jwt_set_claim_str_value(jwt, "client_id", client_id); } token = r_jwt_serialize_signed(jwt, NULL, 0); if (token == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "generate_refresh_token - oauth2 - generating token"); } else { y_log_message(Y_LOG_LEVEL_INFO, "Event oauth2 - Plugin '%s' - Refresh token generated for client '%s' granted by user '%s' with scope list '%s', origin: %s", config->name, client_id, username, scope_list, ip_source); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oauth2 generate_refresh_token - Error cloning jwt"); } r_jwt_free(jwt); return token; } static int is_authorization_type_enabled(struct _oauth2_config * config, uint authorization_type) { return (authorization_type <= 4)?config->auth_type_enabled[authorization_type]:0; } static json_t * check_client_valid(struct _oauth2_config * config, const char * client_id, const char * client_header_login, const char * client_header_password, const char * redirect_uri, unsigned short authorization_type, int implicit_flow, const char * ip_source) { json_t * j_client, * j_element = NULL, * j_return; int uri_found, authorization_type_enabled; size_t index = 0; if (client_id == NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_client_valid - oauth2 - Error client_id is NULL, origin: %s", ip_source); return json_pack("{si}", "result", G_ERROR_PARAM); } else if (client_header_login != NULL && 0 != o_strcmp(client_header_login, client_id)) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_client_valid - oauth2 - Error, client_id specified is different from client_id in the basic auth header, origin: %s", ip_source); return json_pack("{si}", "result", G_ERROR_PARAM); } j_client = config->glewlwyd_config->glewlwyd_callback_check_client_valid(config->glewlwyd_config, client_id, client_header_password); if (check_result_value(j_client, G_OK)) { if (!implicit_flow && client_header_password == NULL && json_object_get(json_object_get(j_client, "client"), "confidential") == json_true()) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_client_valid - oauth2 - Error, confidential client must be authentified with its password, origin: %s", ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else { if (redirect_uri != NULL) { uri_found = 0; json_array_foreach(json_object_get(json_object_get(j_client, "client"), "redirect_uri"), index, j_element) { if (0 == o_strcmp(json_string_value(j_element), redirect_uri)) { uri_found = 1; } } } else { uri_found = 1; } authorization_type_enabled = 0; json_array_foreach(json_object_get(json_object_get(j_client, "client"), "authorization_type"), index, j_element) { if (authorization_type == GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE && 0 == o_strcmp(json_string_value(j_element), "code")) { authorization_type_enabled = 1; } else if (authorization_type == GLEWLWYD_AUTHORIZATION_TYPE_IMPLICIT && 0 == o_strcmp(json_string_value(j_element), "token")) { authorization_type_enabled = 1; } else if (authorization_type == GLEWLWYD_AUTHORIZATION_TYPE_RESOURCE_OWNER_PASSWORD_CREDENTIALS && 0 == o_strcmp(json_string_value(j_element), "password")) { authorization_type_enabled = 1; uri_found = 1; // bypass redirect_uri check for client credentials since it's not needed } else if (authorization_type == GLEWLWYD_AUTHORIZATION_TYPE_REFRESH_TOKEN && 0 == o_strcmp(json_string_value(j_element), "refresh_token")) { authorization_type_enabled = 1; uri_found = 1; // bypass redirect_uri check for client credentials since it's not needed } else if (authorization_type == GLEWLWYD_AUTHORIZATION_TYPE_DELETE_TOKEN && 0 == o_strcmp(json_string_value(j_element), "delete_token")) { authorization_type_enabled = 1; uri_found = 1; // bypass redirect_uri check for client credentials since it's not needed } else if (authorization_type == GLEWLWYD_AUTHORIZATION_TYPE_DEVICE_AUTHORIZATION && 0 == o_strcmp(json_string_value(j_element), "device_authorization")) { authorization_type_enabled = 1; uri_found = 1; // bypass redirect_uri check for client credentials since it's not needed } } if (!uri_found) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_client_valid - oauth2 - Error, redirect_uri '%s' is invalid for the client '%s', origin: %s", redirect_uri, client_id, ip_source); } if (!authorization_type_enabled) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_client_valid - oauth2 - Error, authorization type is not enabled for the client '%s', origin: %s", client_id, ip_source); } if (uri_found && authorization_type_enabled) { j_return = json_pack("{sisO}", "result", G_OK, "client", json_object_get(j_client, "client")); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "check_client_valid - oauth2 - Error, client '%s' is invalid, origin: %s", client_id, ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } json_decref(j_client); return j_return; } static char * generate_authorization_code(struct _oauth2_config * config, const char * username, const char * client_id, const char * scope_list, const char * redirect_uri, const char * issued_for, const char * user_agent, const char * code_challenge) { char * code = NULL, * code_hash = NULL, * expiration_clause, ** scope_array = NULL; json_t * j_query, * j_code_id; int res, i; time_t now; if (pthread_mutex_lock(&config->insert_lock)) { y_log_message(Y_LOG_LEVEL_ERROR, "generate_authorization_code - oauth2 - Error pthread_mutex_lock"); } else { code = o_malloc(33*sizeof(char)); if (code != NULL) { if (rand_string_nonce(code, 32) != NULL) { code_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, code); if (code_hash != NULL) { time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expiration_clause = msprintf("FROM_UNIXTIME(%u)", (now + (unsigned int)config->code_duration )); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expiration_clause = msprintf("TO_TIMESTAMP(%u)", (now + (unsigned int)config->code_duration )); } else { // HOEL_DB_TYPE_SQLITE expiration_clause = msprintf("%u", (now + (unsigned int)config->code_duration )); } j_query = json_pack("{sss{sssssssssssssss{ss}ss}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_CODE, "values", "gpgc_plugin_name", config->name, "gpgc_username", username, "gpgc_client_id", client_id, "gpgc_redirect_uri", redirect_uri, "gpgc_code_hash", code_hash, "gpgc_issued_for", issued_for, "gpgc_user_agent", user_agent!=NULL?user_agent:"", "gpgc_expires_at", "raw", expiration_clause, "gpgc_code_challenge", code_challenge); o_free(expiration_clause); res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "generate_authorization_code - oauth2 - Error executing j_query (1)"); o_free(code); code = NULL; } else { if (scope_list != NULL) { j_code_id = h_last_insert_id(config->glewlwyd_config->glewlwyd_config->conn); if (j_code_id != NULL) { j_query = json_pack("{sss[]}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_CODE_SCOPE, "values"); if (split_string(scope_list, " ", &scope_array) > 0) { for (i=0; scope_array[i] != NULL; i++) { json_array_append_new(json_object_get(j_query, "values"), json_pack("{sOss}", "gpgc_id", j_code_id, "gpgcs_scope", scope_array[i])); } res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "generate_authorization_code - oauth2 - Error executing j_query (2)"); o_free(code); code = NULL; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_authorization_code - oauth2 - Error split_string"); o_free(code); code = NULL; } free_string_array(scope_array); json_decref(j_code_id); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_authorization_code - oauth2 - Error h_last_insert_id"); o_free(code); code = NULL; } } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_authorization_code - oauth2 - Error glewlwyd_callback_generate_hash"); o_free(code); code = NULL; } o_free(code_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_authorization_code - oauth2 - Error rand_string"); o_free(code); code = NULL; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_authorization_code - oauth2 - Error allocating resources for code"); } pthread_mutex_unlock(&config->insert_lock); } return code; } static char * get_login_url(struct _oauth2_config * config, const struct _u_request * request, const char * url, const char * client_id, const char * scope_list, struct _u_map * additional_parameters) { char * plugin_url = config->glewlwyd_config->glewlwyd_callback_get_plugin_external_url(config->glewlwyd_config, json_string_value(json_object_get(config->j_params, "name"))), * url_params = generate_query_parameters(request->map_url, NULL), * url_callback = msprintf("%s/%s%s%s", plugin_url, url, o_strlen(url_params)?"?":"", url_params), * login_url = config->glewlwyd_config->glewlwyd_callback_get_login_url(config->glewlwyd_config, client_id, scope_list, url_callback, additional_parameters); o_free(plugin_url); o_free(url_params); o_free(url_callback); return login_url; } static json_t * get_scope_parameters(struct _oauth2_config * config, const char * scope) { json_t * j_element = NULL, * j_return = NULL; size_t index = 0; json_array_foreach(json_object_get(config->j_params, "scope"), index, j_element) { if (0 == o_strcmp(scope, json_string_value(json_object_get(j_element, "name")))) { j_return = json_incref(j_element); } } return j_return; } static int disable_authorization_code(struct _oauth2_config * config, json_int_t gpgc_id) { json_t * j_query; int res; j_query = json_pack("{sss{si}s{sssI}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_CODE, "set", "gpgc_enabled", 0, "where", "gpgc_plugin_name", config->name, "gpgc_id", gpgc_id); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { return G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "disable_authorization_code - oauth2 - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); return G_ERROR_DB; } } /** * Characters allowed according to RFC 7636 * [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~" */ static int is_pkce_char_valid(const char * code_challenge) { size_t i; if (o_strlen(code_challenge) >= 43 && o_strlen(code_challenge) <= 128) { for (i=0; code_challenge[i] != '\0'; i++) { if (code_challenge[i] == 0x2d || code_challenge[i] == 0x2e || code_challenge[i] == 0x5f || code_challenge[i] == 0x7e || (code_challenge[i] >= 0x30 && code_challenge[i] <= 0x39) || (code_challenge[i] >= 0x41 && code_challenge[i] <= 0x5a) || (code_challenge[i] >= 0x61 && code_challenge[i] <= 0x7a)) { continue; } else { return 0; } } return 1; } else { return 0; } } static int validate_code_challenge(json_t * j_result_code, const char * code_verifier) { int ret; unsigned char code_verifier_hash[32] = {0}, code_verifier_hash_b64[64] = {0}; size_t code_verifier_hash_len = 32, code_verifier_hash_b64_len = 0; gnutls_datum_t key_data; if (json_string_length(json_object_get(j_result_code, "code_challenge"))) { if (is_pkce_char_valid(code_verifier)) { if (0 == o_strncmp(GLEWLWYD_CODE_CHALLENGE_S256_PREFIX, json_string_value(json_object_get(j_result_code, "code_challenge")), o_strlen(GLEWLWYD_CODE_CHALLENGE_S256_PREFIX))) { key_data.data = (unsigned char *)code_verifier; key_data.size = o_strlen(code_verifier); if (gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, code_verifier_hash, &code_verifier_hash_len) == GNUTLS_E_SUCCESS) { if (o_base64url_encode(code_verifier_hash, code_verifier_hash_len, code_verifier_hash_b64, &code_verifier_hash_b64_len)) { code_verifier_hash_b64[code_verifier_hash_b64_len] = '\0'; if (0 == o_strcmp(json_string_value(json_object_get(j_result_code, "code_challenge"))+o_strlen(GLEWLWYD_CODE_CHALLENGE_S256_PREFIX), (const char *)code_verifier_hash_b64)) { ret = G_OK; } else { ret = G_ERROR_UNAUTHORIZED; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_code_challenge - Error o_base64url_encode"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_code_challenge - Error gnutls_fingerprint"); ret = G_ERROR; } } else { if (0 == o_strcmp(json_string_value(json_object_get(j_result_code, "code_challenge")), code_verifier)) { ret = G_OK; } else { ret = G_ERROR_PARAM; } } } else { ret = G_ERROR_PARAM; } } else { ret = G_OK; } return ret; } static int revoke_tokens_from_code(struct _oauth2_config * config, json_int_t gpgc_id, const char * ip_source) { int ret, res; char * query; json_t * j_result, * j_result_r, * j_element = NULL; size_t index = 0; query = msprintf("SELECT gpga_client_id AS client_id FROM " GLEWLWYD_PLUGIN_OAUTH2_TABLE_ACCESS_TOKEN " WHERE gpgr_id IN (SELECT gpgr_id FROM " GLEWLWYD_PLUGIN_OAUTH2_TABLE_REFRESH_TOKEN " WHERE gpgc_id=%" JSON_INTEGER_FORMAT ") AND gpga_enabled=1", gpgc_id); res = h_execute_query_json(config->glewlwyd_config->glewlwyd_config->conn, query, &j_result); o_free(query); if (res == H_OK) { json_array_foreach(j_result, index, j_element) { y_log_message(Y_LOG_LEVEL_INFO, "Event oauth2 - Plugin '%s' - Access token generated for client '%s' revoked, origin: %s", config->name, json_string_value(json_object_get(j_element, "client_id")), ip_source); } json_decref(j_result); query = msprintf("SELECT gpgr_client_id AS client_id FROM " GLEWLWYD_PLUGIN_OAUTH2_TABLE_REFRESH_TOKEN " WHERE gpgc_id=%" JSON_INTEGER_FORMAT " AND gpgr_enabled=1", gpgc_id); res = h_execute_query_json(config->glewlwyd_config->glewlwyd_config->conn, query, &j_result_r); o_free(query); if (res == H_OK) { if (json_array_size(j_result_r)) { y_log_message(Y_LOG_LEVEL_INFO, "Event oauth2 - Plugin '%s' - Refresh token generated for client '%s' revoked, origin: %s", config->name, json_string_value(json_object_get(json_array_get(j_result_r, 0), "client_id")), ip_source); } json_decref(j_result_r); query = msprintf("UPDATE " GLEWLWYD_PLUGIN_OAUTH2_TABLE_ACCESS_TOKEN " SET gpga_enabled='0' WHERE gpgr_id IN (SELECT gpgr_id FROM " GLEWLWYD_PLUGIN_OAUTH2_TABLE_REFRESH_TOKEN " WHERE gpgc_id=%" JSON_INTEGER_FORMAT ")", gpgc_id); res = h_execute_query(config->glewlwyd_config->glewlwyd_config->conn, query, NULL, H_OPTION_EXEC); o_free(query); if (res == H_OK) { query = msprintf("UPDATE " GLEWLWYD_PLUGIN_OAUTH2_TABLE_REFRESH_TOKEN " SET gpgr_enabled='0' WHERE gpgc_id=%" JSON_INTEGER_FORMAT, gpgc_id); res = h_execute_query(config->glewlwyd_config->glewlwyd_config->conn, query, NULL, H_OPTION_EXEC); o_free(query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "revoke_tokens_from_code - oauth2 - Error executing query (4)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "revoke_tokens_from_code - oauth2 - Error executing query (3)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc revoke_tokens_from_code - Error executing query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc revoke_tokens_from_code - Error executing query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static json_t * validate_authorization_code(struct _oauth2_config * config, const char * code, const char * client_id, const char * redirect_uri, const char * code_verifier, const char * ip_source) { char * code_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, code), * expiration_clause = NULL, * scope_list = NULL, * tmp; json_t * j_query, * j_result = NULL, * j_result_scope = NULL, * j_return, * j_element = NULL, * j_scope_param; int res; size_t index = 0; json_int_t maximum_duration = config->refresh_token_duration, maximum_duration_override = -1; int rolling_refresh = config->refresh_token_rolling, rolling_refresh_override = -1; if (code_hash != NULL) { if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expiration_clause = o_strdup("> NOW()"); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expiration_clause = o_strdup("> NOW()"); } else { // HOEL_DB_TYPE_SQLITE expiration_clause = o_strdup("> (strftime('%s','now'))"); } j_query = json_pack("{sss[ssss]s{sssssssss{ssss}}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_CODE, "columns", "gpgc_username AS username", "gpgc_id", "gpgc_code_challenge AS code_challenge", "gpgc_enabled AS enabled", "where", "gpgc_plugin_name", config->name, "gpgc_client_id", client_id, "gpgc_redirect_uri", redirect_uri, "gpgc_code_hash", code_hash, "gpgc_expires_at", "operator", "raw", "value", expiration_clause); o_free(expiration_clause); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { if (json_integer_value(json_object_get(json_array_get(j_result, 0), "enabled"))) { if ((res = validate_code_challenge(json_array_get(j_result, 0), code_verifier)) == G_OK) { j_query = json_pack("{sss[s]s{sO}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_CODE_SCOPE, "columns", "gpgcs_scope AS name", "where", "gpgc_id", json_object_get(json_array_get(j_result, 0), "gpgc_id")); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result_scope, NULL); json_decref(j_query); if (res == H_OK && json_array_size(j_result_scope) > 0) { if (!json_object_set_new(json_array_get(j_result, 0), "scope", json_array())) { json_array_foreach(j_result_scope, index, j_element) { if (scope_list == NULL) { scope_list = o_strdup(json_string_value(json_object_get(j_element, "name"))); } else { tmp = msprintf("%s %s", scope_list, json_string_value(json_object_get(j_element, "name"))); o_free(scope_list); scope_list = tmp; } if ((j_scope_param = get_scope_parameters(config, json_string_value(json_object_get(j_element, "name")))) != NULL) { json_object_update(j_element, j_scope_param); json_decref(j_scope_param); } if (json_object_get(j_element, "refresh-token-rolling") != NULL && rolling_refresh_override != 0) { rolling_refresh_override = json_object_get(j_element, "refresh-token-rolling")==json_true(); } if (json_integer_value(json_object_get(j_element, "refresh-token-duration")) && (json_integer_value(json_object_get(j_element, "refresh-token-duration")) < maximum_duration_override || maximum_duration_override == -1)) { maximum_duration_override = json_integer_value(json_object_get(j_element, "refresh-token-duration")); } json_array_append(json_object_get(json_array_get(j_result, 0), "scope"), j_element); } if (rolling_refresh_override > -1) { rolling_refresh = rolling_refresh_override; } if (maximum_duration_override > -1) { maximum_duration = maximum_duration_override; } json_object_set_new(json_array_get(j_result, 0), "scope_list", json_string(scope_list)); json_object_set_new(json_array_get(j_result, 0), "refresh-token-rolling", rolling_refresh?json_true():json_false()); json_object_set_new(json_array_get(j_result, 0), "refresh-token-duration", json_integer(maximum_duration)); j_return = json_pack("{sisO}", "result", G_OK, "code", json_array_get(j_result, 0)); o_free(scope_list); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_authorization_code - oauth2 - Error allocating resources for json_array()"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_authorization_code - oauth2 - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_result_scope); } else if (res == G_ERROR_UNAUTHORIZED) { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else if (res == G_ERROR_PARAM) { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_authorization_code - oauth2 - Error validate_code_challenge"); j_return = json_pack("{si}", "result", G_ERROR); } } else { if (json_true() == json_object_get(config->j_params, "auth-type-code-revoke-replayed")) { if (revoke_tokens_from_code(config, json_integer_value(json_object_get(json_array_get(j_result, 0), "gpgc_id")), ip_source) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "validate_authorization_code - oauth2 - Error revoke_tokens_from_code"); } } j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_authorization_code - oauth2 - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_authorization_code - oauth2 - Error glewlwyd_callback_generate_hash"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(code_hash); return j_return; } static json_t * validate_session_client_scope(struct _oauth2_config * config, const struct _u_request * request, const char * client_id, const char * scope) { json_t * j_session, * j_grant, * j_return, * j_scope_session, * j_scope_grant = NULL, * j_group = NULL, * j_scheme; const char * scope_session, * group = NULL; char * scope_filtered = NULL, * tmp; size_t index = 0; json_int_t scopes_authorized = 0, scopes_granted = 0, group_allowed; j_session = config->glewlwyd_config->glewlwyd_callback_check_session_valid(config->glewlwyd_config, request, scope); if (check_result_value(j_session, G_OK)) { j_grant = config->glewlwyd_config->glewlwyd_callback_get_client_granted_scopes(config->glewlwyd_config, client_id, json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username")), scope); if (check_result_value(j_grant, G_OK)) { if (json_array_size(json_object_get(json_object_get(j_grant, "grant"), "scope"))) { // Count and store the number of granted scopes json_array_foreach(json_object_get(json_object_get(j_grant, "grant"), "scope"), index, j_scope_grant) { scopes_granted += json_object_get(j_scope_grant, "granted")==json_true(); } json_object_set_new(json_object_get(j_session, "session"), "scopes_granted", json_integer(scopes_granted)); json_object_foreach(json_object_get(json_object_get(j_session, "session"), "scope"), scope_session, j_scope_session) { // Evaluate if the scope is granted for the client json_array_foreach(json_object_get(json_object_get(j_grant, "grant"), "scope"), index, j_scope_grant) { if (0 == o_strcmp(scope_session, json_string_value(json_object_get(j_scope_grant, "name")))) { json_object_set(j_scope_session, "granted", json_object_get(j_scope_grant, "granted")); } } // Evaluate if the scope is authorized if (json_object_get(j_scope_session, "available") == json_true()) { if (json_object_get(j_scope_session, "password_required") == json_true() && json_object_get(j_scope_session, "password_authenticated") == json_false()) { json_object_set_new(j_scope_session, "authorized", json_false()); } else if ((json_object_get(j_scope_session, "password_required") == json_true() && json_object_get(j_scope_session, "password_authenticated") == json_true()) || json_object_get(j_scope_session, "password_required") == json_false()) { json_object_foreach(json_object_get(j_scope_session, "schemes"), group, j_group) { group_allowed = 0; json_array_foreach(j_group, index, j_scheme) { if (json_object_get(j_scheme, "scheme_authenticated") == json_true()) { group_allowed++; } } if (group_allowed < json_integer_value(json_object_get(json_object_get(j_scope_session, "scheme_required"), group))) { json_object_set_new(j_scope_session, "authorized", json_false()); } } if (json_object_get(j_scope_session, "authorized") == NULL) { json_object_set_new(j_scope_session, "authorized", json_true()); scopes_authorized++; if (json_object_get(j_scope_session, "granted") == json_true()) { if (scope_filtered == NULL) { scope_filtered = o_strdup(scope_session); } else { tmp = msprintf("%s %s", scope_filtered, scope_session); o_free(scope_filtered); scope_filtered = tmp; } } } else if (json_object_get(j_scope_session, "granted") == json_true()) { json_object_set_new(json_object_get(j_session, "session"), "authorization_required", json_true()); } } else { json_object_set_new(j_scope_session, "authorized", json_false()); } } else { json_object_set_new(j_scope_session, "authorized", json_false()); } } json_object_set_new(json_object_get(j_session, "session"), "scopes_authorized", json_integer(scopes_authorized)); if (json_object_get(json_object_get(j_session, "session"), "authorization_required") == NULL) { json_object_set_new(json_object_get(j_session, "session"), "authorization_required", json_false()); } if (scope_filtered != NULL) { json_object_set_new(json_object_get(j_session, "session"), "scope_filtered", json_string(scope_filtered)); o_free(scope_filtered); } else { json_object_set_new(json_object_get(j_session, "session"), "scope_filtered", json_string("")); json_object_set_new(json_object_get(j_session, "session"), "authorization_required", json_true()); } if (scopes_authorized && scopes_granted) { j_return = json_pack("{sisO}", "result", G_OK, "session", json_object_get(j_session, "session")); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_session_client_scope - oauth2 - Error glewlwyd_callback_get_client_granted_scopes"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_grant); } else if (check_result_value(j_session, G_ERROR_NOT_FOUND)) { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } else if (check_result_value(j_session, G_ERROR_UNAUTHORIZED)) { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_session_client_scope - oauth2 - Error glewlwyd_callback_check_session_valid"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_session); return j_return; } static json_t * validate_refresh_token(struct _oauth2_config * config, const char * refresh_token) { json_t * j_return, * j_query, * j_result, * j_result_scope, * j_element = NULL; char * token_hash, * expires_at_clause; int res; size_t index = 0; time_t now; if (refresh_token != NULL) { token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, refresh_token); if (token_hash != NULL) { time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } j_query = json_pack("{sss[sssssssss]s{sssssis{ssss}}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_REFRESH_TOKEN, "columns", "gpgr_id", "gpgc_id", "gpgr_username AS username", "gpgr_client_id AS client_id", SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpgr_issued_at) AS issued_at", "gpgr_issued_at AS issued_at", "EXTRACT(EPOCH FROM gpgr_issued_at)::integer AS issued_at"), SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpgr_expires_at) AS expired_at", "gpgr_expires_at AS expired_at", "EXTRACT(EPOCH FROM gpgr_expires_at)::integer AS expired_at"), SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpgr_last_seen) AS last_seen", "gpgr_last_seen AS last_seen", "EXTRACT(EPOCH FROM gpgr_last_seen)::integer AS last_seen"), "gpgr_duration AS duration", "gpgr_rolling_expiration", "where", "gpgr_plugin_name", config->name, "gpgr_token_hash", token_hash, "gpgr_enabled", 1, "gpgr_expires_at", "operator", "raw", "value", expires_at_clause); o_free(expires_at_clause); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result) > 0) { json_object_set(json_array_get(j_result, 0), "rolling_expiration", json_integer_value(json_object_get(json_array_get(j_result, 0), "gpgr_rolling_expiration"))?json_true():json_false()); json_object_del(json_array_get(j_result, 0), "gpgr_rolling_expiration"); j_query = json_pack("{sss[s]s{sO}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_REFRESH_TOKEN_SCOPE, "columns", "gpgrs_scope AS scope", "where", "gpgr_id", json_object_get(json_array_get(j_result, 0), "gpgr_id")); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result_scope, NULL); json_decref(j_query); if (res == H_OK) { if (!json_object_set_new(json_array_get(j_result, 0), "scope", json_array())) { json_array_foreach(j_result_scope, index, j_element) { json_array_append(json_object_get(json_array_get(j_result, 0), "scope"), json_object_get(j_element, "scope")); } j_return = json_pack("{sisO}", "result", G_OK, "token", json_array_get(j_result, 0)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_refresh_token - oauth2 - Error json_object_set_new"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result_scope); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_refresh_token - oauth2 - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_refresh_token - oauth2 - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_refresh_token - oauth2 - Error glewlwyd_callback_generate_hash"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(token_hash); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } static json_t * refresh_token_list_get(struct _oauth2_config * config, const char * username, const char * pattern, size_t offset, size_t limit, const char * sort) { json_t * j_query, * j_result, * j_return, * j_element = NULL; int res; size_t index = 0, token_hash_dec_len = 0; char * pattern_escaped, * pattern_clause, * name_escaped; unsigned char token_hash_dec[128]; j_query = json_pack("{sss[ssssssssss]s{ssss}sisiss}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_REFRESH_TOKEN, "columns", "gpgr_token_hash", "gpgr_authorization_type", "gpgr_client_id AS client_id", SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpgr_issued_at) AS issued_at", "gpgr_issued_at AS issued_at", "EXTRACT(EPOCH FROM gpgr_issued_at)::integer AS issued_at"), SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpgr_expires_at) AS expires_at", "gpgr_expires_at AS expires_at", "EXTRACT(EPOCH FROM gpgr_expires_at)::integer AS expires_at"), SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpgr_last_seen) AS last_seen", "gpgr_last_seen AS last_seen", "EXTRACT(EPOCH FROM gpgr_last_seen)::integer AS last_seen"), "gpgr_rolling_expiration", "gpgr_issued_for AS issued_for", "gpgr_user_agent AS user_agent", "gpgr_enabled", "where", "gpgr_plugin_name", config->name, "gpgr_username", username, "offset", offset, "limit", limit, "order_by", "gpgr_last_seen DESC"); if (sort != NULL) { json_object_set_new(j_query, "order_by", json_string(sort)); } if (pattern != NULL) { pattern_escaped = h_escape_string_with_quotes(config->glewlwyd_config->glewlwyd_config->conn, pattern); name_escaped = h_escape_string_with_quotes(config->glewlwyd_config->glewlwyd_config->conn, config->name); pattern_clause = msprintf("IN (SELECT gpgr_id FROM "GLEWLWYD_PLUGIN_OAUTH2_TABLE_REFRESH_TOKEN" WHERE (gpgr_user_agent LIKE '%%'||%s||'%%' OR gpgr_issued_for LIKE '%%'||%s||'%%') AND gpgr_plugin_name=%s)", pattern_escaped, pattern_escaped, name_escaped); json_object_set_new(json_object_get(j_query, "where"), "gpgr_id", json_pack("{ssss}", "operator", "raw", "value", pattern_clause)); o_free(pattern_clause); o_free(pattern_escaped); o_free(name_escaped); } res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result, index, j_element) { json_object_set(j_element, "rolling_expiration", (json_integer_value(json_object_get(j_element, "gpgr_rolling_expiration"))?json_true():json_false())); json_object_set(j_element, "enabled", (json_integer_value(json_object_get(j_element, "gpgr_enabled"))?json_true():json_false())); json_object_del(j_element, "gpgr_rolling_expiration"); json_object_del(j_element, "gpgr_enabled"); if (o_base64_2_base64url((unsigned char *)json_string_value(json_object_get(j_element, "gpgr_token_hash")), json_string_length(json_object_get(j_element, "gpgr_token_hash")), token_hash_dec, &token_hash_dec_len)) { json_object_set_new(j_element, "token_hash", json_stringn((char *)token_hash_dec, token_hash_dec_len)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "refresh_token_list_get - Error o_base64_2_base64url"); json_object_set_new(j_element, "token_hash", json_string("error")); } json_object_del(j_element, "gpgr_token_hash"); switch(json_integer_value(json_object_get(j_element, "gpgr_authorization_type"))) { case GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE: json_object_set_new(j_element, "authorization_type", json_string("code")); break; case GLEWLWYD_AUTHORIZATION_TYPE_RESOURCE_OWNER_PASSWORD_CREDENTIALS: json_object_set_new(j_element, "authorization_type", json_string("password")); break; default: json_object_set_new(j_element, "authorization_type", json_string("unknown")); break; } json_object_del(j_element, "gpgr_authorization_type"); } j_return = json_pack("{sisO}", "result", G_OK, "refresh_token", j_result); json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "refresh_token_list_get - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } static int refresh_token_disable(struct _oauth2_config * config, const char * username, const char * token_hash, const char * ip_source) { json_t * j_query, * j_result, * j_element = NULL; int res, ret = G_OK; unsigned char token_hash_dec[128]; size_t token_hash_dec_len = 0, index = 0; j_query = json_pack("{sss[ss]s{ssss}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_REFRESH_TOKEN, "columns", "gpgr_id", "gpgr_enabled", "where", "gpgr_plugin_name", config->name, "gpgr_username", username); if (token_hash != NULL) { if (o_base64url_2_base64((unsigned char *)token_hash, o_strlen(token_hash), token_hash_dec, &token_hash_dec_len)) { json_object_set_new(json_object_get(j_query, "where"), "gpgr_token_hash", json_stringn((const char *)token_hash_dec, token_hash_dec_len)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "update_refresh_token - oauth2 - Error o_base64url_2_base64"); ret = G_ERROR_PARAM; } } res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK && ret == G_OK) { if (json_array_size(j_result)) { json_array_foreach(j_result, index, j_element) { if (json_integer_value(json_object_get(j_element, "gpgr_enabled"))) { j_query = json_pack("{sss{si}s{sssO}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_REFRESH_TOKEN, "set", "gpgr_enabled", 0, "where", "gpgr_plugin_name", config->name, "gpgr_id", json_object_get(j_element, "gpgr_id")); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { if (token_hash != NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "update_refresh_token - oauth2 - token '[...%s]' disabled, origin: %s", token_hash + (o_strlen(token_hash) - (o_strlen(token_hash)>=8?8:o_strlen(token_hash))), ip_source); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "update_refresh_token - oauth2 - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else if (token_hash != NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "update_refresh_token - oauth2 - Error token '[...%s]' already disabled, origin: %s", token_hash + (o_strlen(token_hash) - (o_strlen(token_hash)>=8?8:o_strlen(token_hash))), ip_source); ret = G_ERROR_PARAM; } } } else if (token_hash != NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "update_refresh_token - oauth2 - Error token '[...%s]' not found, origin: %s", token_hash + (o_strlen(token_hash) - (o_strlen(token_hash)>=8?8:o_strlen(token_hash))), ip_source); ret = G_ERROR_NOT_FOUND; } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "update_refresh_token - oauth2 - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static int update_refresh_token(struct _oauth2_config * config, json_int_t gpgr_id, json_int_t refresh_token_duration, int disable, time_t now) { json_t * j_query; int res, ret; char * expires_at_clause, * last_seen_clause; if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { last_seen_clause = msprintf("FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { last_seen_clause = msprintf("TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE last_seen_clause = msprintf("%u", (now)); } j_query = json_pack("{sss{s{ss}}s{sssI}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_REFRESH_TOKEN, "set", "gpgr_last_seen", "raw", last_seen_clause, "where", "gpgr_plugin_name", config->name, "gpgr_id", gpgr_id); o_free(last_seen_clause); if (refresh_token_duration) { if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("FROM_UNIXTIME(%u)", (now + (unsigned int)refresh_token_duration)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("TO_TIMESTAMP(%u)", (now + (unsigned int)refresh_token_duration)); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("%u", (now + (unsigned int)refresh_token_duration)); } json_object_set_new(json_object_get(j_query, "set"), "gpgr_expires_at", json_pack("{ss}", "raw", expires_at_clause)); o_free(expires_at_clause); } if (disable) { json_object_set_new(json_object_get(j_query, "set"), "gpgr_enabled", json_integer(0)); } res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "update_refresh_token - oauth2 - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static json_t * get_refresh_token_duration_rolling(struct _oauth2_config * config, const char * scope_list) { json_t * j_return, * j_element = NULL; char ** scope_array = NULL; size_t i, index = 0; json_int_t maximum_duration = config->refresh_token_duration, maximum_duration_override = -1; int rolling_refresh = config->refresh_token_rolling, rolling_refresh_override = -1; if (split_string(scope_list, " ", &scope_array)) { json_array_foreach(json_object_get(config->j_params, "scope"), index, j_element) { for (i=0; scope_array[i]!=NULL; i++) { if (0 == o_strcmp(json_string_value(json_object_get(j_element, "name")), scope_array[i])) { if (json_integer_value(json_object_get(j_element, "refresh-token-duration")) && (json_integer_value(json_object_get(j_element, "refresh-token-duration")) < maximum_duration_override || maximum_duration_override == -1)) { maximum_duration_override = json_integer_value(json_object_get(j_element, "refresh-token-duration")); } if (json_object_get(j_element, "refresh-token-rolling") != NULL && rolling_refresh_override != 0) { rolling_refresh_override = json_object_get(j_element, "refresh-token-rolling")==json_true(); } } } } free_string_array(scope_array); if (maximum_duration_override != -1) { maximum_duration = maximum_duration_override; } if (rolling_refresh_override != -1) { rolling_refresh = rolling_refresh_override; } j_return = json_pack("{sis{sosI}}", "result", G_OK, "refresh-token", "refresh-token-rolling", rolling_refresh?json_true():json_false(), "refresh-token-duration", maximum_duration); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_refresh_token_duration_rolling - Error split_string"); j_return = json_pack("{si}", "result", G_ERROR); } return j_return; } static int is_code_challenge_valid(struct _oauth2_config * config, const char * code_challenge, const char * code_challenge_method, char * code_challenge_stored) { int ret; if (o_strlen(code_challenge)) { if (json_object_get(config->j_params, "pkce-allowed") == json_true()) { if (!o_strlen(code_challenge_method) || 0 == o_strcmp("plain", code_challenge_method)) { if (json_object_get(config->j_params, "pkce-method-plain-allowed") == json_true()) { if (is_pkce_char_valid(code_challenge)) { o_strcpy(code_challenge_stored, code_challenge); ret = G_OK; } else { ret = G_ERROR_PARAM; } } else { ret = G_ERROR_PARAM; } } else if (0 == o_strcmp("S256", code_challenge_method)) { if (o_strlen(code_challenge) == 43) { o_strcpy(code_challenge_stored, GLEWLWYD_CODE_CHALLENGE_S256_PREFIX); o_strcpy(code_challenge_stored + o_strlen(GLEWLWYD_CODE_CHALLENGE_S256_PREFIX), code_challenge); ret = G_OK; } else { ret = G_ERROR_PARAM; } } else { ret = G_ERROR_PARAM; } } else { ret = G_ERROR_PARAM; } } else { // No pkce ret = G_OK; } return ret; } static int revoke_refresh_token(struct _oauth2_config * config, const char * token) { json_t * j_query; int res, ret; char * token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, token); j_query = json_pack("{sss{si}s{ssss}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_REFRESH_TOKEN, "set", "gpgr_enabled", 0, "where", "gpgr_plugin_name", config->name, "gpgr_token_hash", token_hash); o_free(token_hash); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "revoke_refresh_token - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static int revoke_access_token(struct _oauth2_config * config, const char * token) { json_t * j_query; int res, ret; char * token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, token); j_query = json_pack("{sss{si}s{ssss}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_ACCESS_TOKEN, "set", "gpga_enabled", 0, "where", "gpga_plugin_name", config->name, "gpga_token_hash", token_hash); o_free(token_hash); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "revoke_access_token - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static json_t * get_token_metadata(struct _oauth2_config * config, const char * token, const char * token_type_hint, const char * client_id) { json_t * j_query, * j_result, * j_result_scope, * j_return = NULL, * j_element = NULL; int res, found_refresh = 0, found_access = 0; size_t index = 0; char * token_hash = NULL, * scope_list = NULL, * expires_at_clause; time_t now; if (o_strlen(token)) { token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, token); time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } if (token_type_hint == NULL || 0 == o_strcmp("refresh_token", token_type_hint)) { j_query = json_pack("{sss[sssssss]s{sssss{ssss}}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_REFRESH_TOKEN, "columns", "gpgr_id", "gpgr_username AS username", "gpgr_client_id AS client_id", SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpgr_issued_at) AS iat", "gpgr_issued_at AS iat", "EXTRACT(EPOCH FROM gpgr_issued_at)::integer AS iat"), SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpgr_issued_at) AS nbf", "gpgr_issued_at AS nbf", "EXTRACT(EPOCH FROM gpgr_issued_at)::integer AS nbf"), SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpgr_expires_at) AS exp", "gpgr_expires_at AS exp", "EXTRACT(EPOCH FROM gpgr_expires_at)::integer AS exp"), "gpgr_enabled", "where", "gpgr_plugin_name", config->name, "gpgr_token_hash", token_hash, "gpgr_expires_at", "operator", "raw", "value", expires_at_clause); if (client_id != NULL) { json_object_set_new(json_object_get(j_query, "where"), "gpgr_client_id", json_string(client_id)); } res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { found_refresh = 1; if (json_integer_value(json_object_get(json_array_get(j_result, 0), "gpgr_enabled"))) { json_object_set_new(json_array_get(j_result, 0), "active", json_true()); json_object_set_new(json_array_get(j_result, 0), "token_type", json_string("refresh_token")); json_object_del(json_array_get(j_result, 0), "gpgr_enabled"); if (json_object_get(json_array_get(j_result, 0), "client_id") == json_null()) { json_object_del(json_array_get(j_result, 0), "client_id"); } if (json_object_get(json_array_get(j_result, 0), "username") == json_null()) { json_object_del(json_array_get(j_result, 0), "username"); } j_query = json_pack("{sss[s]s{sO}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_REFRESH_TOKEN_SCOPE, "columns", "gpgrs_scope AS scope", "where", "gpgr_id", json_object_get(json_array_get(j_result, 0), "gpgr_id")); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result_scope, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result_scope, index, j_element) { if (scope_list == NULL) { scope_list = o_strdup(json_string_value(json_object_get(j_element, "scope"))); } else { scope_list = mstrcatf(scope_list, " %s", json_string_value(json_object_get(j_element, "scope"))); } } json_object_set_new(json_array_get(j_result, 0), "scope", json_string(scope_list)); o_free(scope_list); json_decref(j_result_scope); json_object_del(json_array_get(j_result, 0), "gpgr_id"); j_return = json_pack("{sisO}", "result", G_OK, "token", json_array_get(j_result, 0)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_refresh_token - oauth2 - Error executing j_query scope refresh_token"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { j_return = json_pack("{sis{so}}", "result", G_OK, "token", "active", json_false()); } } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_token_metadata - Error executing j_query refresh_token"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } if ((token_type_hint == NULL && !found_refresh) || 0 == o_strcmp("access_token", token_type_hint)) { j_query = json_pack("{sss[ssssss]s{ssss}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_ACCESS_TOKEN, "columns", "gpga_id", "gpga_username AS username", "gpga_client_id AS client_id", SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpga_issued_at) AS iat", "gpga_issued_at AS iat", "EXTRACT(EPOCH FROM gpga_issued_at)::integer AS iat"), SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpga_issued_at) AS nbf", "gpga_issued_at AS nbf", "EXTRACT(EPOCH FROM gpga_issued_at)::integer AS nbf"), "gpga_enabled", "where", "gpga_plugin_name", config->name, "gpga_token_hash", token_hash); if (client_id != NULL) { json_object_set_new(json_object_get(j_query, "where"), "gpga_client_id", json_string(client_id)); } res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { found_access = 1; if (json_integer_value(json_object_get(json_array_get(j_result, 0), "gpga_enabled")) && json_integer_value(json_object_get(json_array_get(j_result, 0), "iat")) + json_integer_value(json_object_get(config->j_params, "access-token-duration")) > (json_int_t)now) { json_object_set_new(json_array_get(j_result, 0), "active", json_true()); json_object_set_new(json_array_get(j_result, 0), "token_type", json_string("access_token")); json_object_set_new(json_array_get(j_result, 0), "exp", json_integer(json_integer_value(json_object_get(json_array_get(j_result, 0), "iat")) + json_integer_value(json_object_get(config->j_params, "access-token-duration")))); json_object_del(json_array_get(j_result, 0), "gpga_enabled"); if (json_object_get(json_array_get(j_result, 0), "client_id") == json_null()) { json_object_del(json_array_get(j_result, 0), "client_id"); } if (json_object_get(json_array_get(j_result, 0), "username") == json_null()) { json_object_del(json_array_get(j_result, 0), "username"); } j_query = json_pack("{sss[s]s{sO}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_ACCESS_TOKEN_SCOPE, "columns", "gpgas_scope AS scope", "where", "gpga_id", json_object_get(json_array_get(j_result, 0), "gpga_id")); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result_scope, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result_scope, index, j_element) { if (scope_list == NULL) { scope_list = o_strdup(json_string_value(json_object_get(j_element, "scope"))); } else { scope_list = mstrcatf(scope_list, " %s", json_string_value(json_object_get(j_element, "scope"))); } } json_object_set_new(json_array_get(j_result, 0), "scope", json_string(scope_list)); o_free(scope_list); json_decref(j_result_scope); json_object_del(json_array_get(j_result, 0), "gpga_id"); j_return = json_pack("{sisO}", "result", G_OK, "token", json_array_get(j_result, 0)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oauth2 validate_refresh_token - Error executing j_query scope access_token"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { j_return = json_pack("{sis{so}}", "result", G_OK, "token", "active", json_false()); } } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_token_metadata - Error executing j_query access_token"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } if (!found_refresh && !found_access && j_return == NULL) { j_return = json_pack("{sis{so}}", "result", G_OK, "token", "active", json_false()); } o_free(token_hash); o_free(expires_at_clause); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } static const char * get_client_id_for_introspection(struct _oauth2_config * config, const struct _u_request * request) { if (u_map_get_case(request->map_header, HEADER_AUTHORIZATION) != NULL && config->introspect_revoke_resource_config->oauth_scope != NULL) { return NULL; } else if (json_object_get(config->j_params, "introspection-revocation-allow-target-client") == json_true()) { return request->auth_basic_user; } else { return NULL; } } static json_t * generate_device_authorization(struct _oauth2_config * config, const char * client_id, const char * scope_list, const char * ip_source) { char device_code[GLEWLWYD_DEVICE_AUTH_DEVICE_CODE_LENGTH+1] = {0}, user_code[GLEWLWYD_DEVICE_AUTH_USER_CODE_LENGTH+2] = {0}, * device_code_hash = NULL, * user_code_hash = NULL; json_t * j_return, * j_query, * j_device_auth_id; int res; time_t now, expiration = (time_t)json_integer_value(json_object_get(config->j_params, "device-authorization-expiration")); char * expires_at_clause = NULL, * last_check_clause = NULL, ** scope_array = NULL; size_t i; if (pthread_mutex_lock(&config->insert_lock)) { y_log_message(Y_LOG_LEVEL_ERROR, "generate_device_authorization oauth2 - Error pthread_mutex_lock"); j_return = json_pack("{si}", "result", G_ERROR); } else { if (rand_string(device_code, 32) != NULL && rand_string_from_charset(user_code, GLEWLWYD_DEVICE_AUTH_USER_CODE_LENGTH+1, "ABCDEFGHJKLMNOPQRSTUVWXYZ0123456789") != NULL) { user_code[4] = '-'; device_code_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, device_code); user_code_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, user_code); time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("FROM_UNIXTIME(%u)", (now + expiration)); last_check_clause = msprintf("FROM_UNIXTIME(%u)", (now - (2*expiration))); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("TO_TIMESTAMP(%u)", (now + expiration)); last_check_clause = msprintf("TO_TIMESTAMP(%u)", (now - (2*expiration))); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("%u", (now + expiration)); last_check_clause = msprintf("%u", (now - (2*expiration))); } j_query = json_pack("{sss{sssss{ss}sssssss{ss}}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_DEVICE_AUTHORIZATION, "values", "gpgda_plugin_name", config->name, "gpgda_client_id", client_id, "gpgda_expires_at", "raw", expires_at_clause, "gpgda_issued_for", ip_source, "gpgda_device_code_hash", device_code_hash, "gpgda_user_code_hash", user_code_hash, "gpgda_last_check", "raw", last_check_clause); o_free(expires_at_clause); o_free(last_check_clause); o_free(device_code_hash); o_free(user_code_hash); res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_device_auth_id = h_last_insert_id(config->glewlwyd_config->glewlwyd_config->conn); if (j_device_auth_id != NULL) { if (split_string(scope_list, " ", &scope_array)) { j_query = json_pack("{sss[]}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_DEVICE_AUTHORIZATION_SCOPE, "values"); for (i=0; scope_array[i]!=NULL; i++) { json_array_append_new(json_object_get(j_query, "values"), json_pack("{sOss}", "gpgda_id", j_device_auth_id, "gpgdas_scope", scope_array[i])); } res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_return = json_pack("{sis{ssss}}", "result", G_OK, "authorization", "device_code", device_code, "user_code", user_code); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_device_authorization - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_device_authorization - Error split_string scope"); j_return = json_pack("{si}", "result", G_ERROR); } free_string_array(scope_array); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_device_authorization - Error h_last_insert_id"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_device_auth_id); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_device_authorization - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_device_authorization - Error generating random code"); j_return = json_pack("{si}", "result", G_ERROR); } pthread_mutex_unlock(&config->insert_lock); } return j_return; } static int validate_device_authorization_scope(struct _oauth2_config * config, json_int_t gpgda_id, const char * username, const char * scope_list) { char * query, * scope_clause = NULL, * scope_escaped, ** scope_array = NULL, * username_escaped; int res, i, ret; if (split_string(scope_list, " ", &scope_array)) { for (i=0; scope_array[i]!=NULL; i++) { scope_escaped = h_escape_string_with_quotes(config->glewlwyd_config->glewlwyd_config->conn, scope_array[i]); if (scope_clause == NULL) { scope_clause = o_strdup(scope_escaped); } else { scope_clause = mstrcatf(scope_clause, ",%s", scope_escaped); } o_free(scope_escaped); } free_string_array(scope_array); } if (o_strlen(scope_clause)) { query = msprintf("UPDATE %s set gpgdas_allowed=1 WHERE gpgdas_scope IN (%s) AND gpgda_id=%"JSON_INTEGER_FORMAT, GLEWLWYD_PLUGIN_OAUTH2_TABLE_DEVICE_AUTHORIZATION_SCOPE, scope_clause, gpgda_id); res = h_execute_query(config->glewlwyd_config->glewlwyd_config->conn, query, NULL, H_OPTION_EXEC); o_free(query); if (res == H_OK) { username_escaped = h_escape_string_with_quotes(config->glewlwyd_config->glewlwyd_config->conn, username); query = msprintf("UPDATE %s set gpgda_status=1, gpgda_username=%s WHERE gpgda_id=%"JSON_INTEGER_FORMAT, GLEWLWYD_PLUGIN_OAUTH2_TABLE_DEVICE_AUTHORIZATION, username_escaped, gpgda_id); res = h_execute_query(config->glewlwyd_config->glewlwyd_config->conn, query, NULL, H_OPTION_EXEC); o_free(username_escaped); o_free(query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_device_authorization_scope - Error executing query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_device_authorization_scope - Error executing query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_device_authorization_scope - Error scope invalid"); ret = G_ERROR_PARAM; } o_free(scope_clause); return ret; } static json_t * validate_device_auth_user_code(struct _oauth2_config * config, const char * user_code) { json_t * j_query = NULL, * j_result = NULL, * j_result_scope = NULL, * j_return, * j_element = NULL; int res; char * scope = NULL, * expires_at_clause, * user_code_hash, user_code_ucase[GLEWLWYD_DEVICE_AUTH_USER_CODE_LENGTH+2] = {0}; time_t now; size_t index = 0; if (o_strlen(user_code) == GLEWLWYD_DEVICE_AUTH_USER_CODE_LENGTH+1 && user_code[4] == '-') { for (index=0; index<(GLEWLWYD_DEVICE_AUTH_USER_CODE_LENGTH+1); index++) { user_code_ucase[index] = toupper(user_code[index]); } user_code_ucase[GLEWLWYD_DEVICE_AUTH_USER_CODE_LENGTH+1] = '\0'; time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } user_code_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, user_code_ucase); j_query = json_pack("{sss[ss]s{sss{ssss}sssi}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_DEVICE_AUTHORIZATION, "columns", "gpgda_id", "gpgda_client_id", "where", "gpgda_plugin_name", config->name, "gpgda_expires_at", "operator", "raw", "value", expires_at_clause, "gpgda_user_code_hash", user_code_hash, "gpgda_status", 0); o_free(expires_at_clause); o_free(user_code_hash); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_query = json_pack("{sss[s]s{sO}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_DEVICE_AUTHORIZATION_SCOPE, "columns", "gpgdas_scope", "where", "gpgda_id", json_object_get(json_array_get(j_result, 0), "gpgda_id")); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result_scope, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result_scope, index, j_element) { if (scope == NULL) { scope = o_strdup(json_string_value(json_object_get(j_element, "gpgdas_scope"))); } else { scope = mstrcatf(scope, " %s", json_string_value(json_object_get(j_element, "gpgdas_scope"))); } } j_return = json_pack("{sis{sOsssO}}", "result", G_OK, "device_auth", "client_id", json_object_get(json_array_get(j_result, 0), "gpgda_client_id"), "scope", scope, "gpgda_id", json_object_get(json_array_get(j_result, 0), "gpgda_id")); o_free(scope); json_decref(j_result_scope); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_device_auth_user_code - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_device_auth_user_code - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } return j_return; } static int check_auth_type_device_code(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oauth2_config * config = (struct _oauth2_config *)user_data; json_t * j_body, * j_client, * j_query, * j_result = NULL, * j_result_scope = NULL, * j_element = NULL, * j_user = NULL, * j_refresh_token = NULL, * j_user_only = NULL; const char * device_code = u_map_get(request->map_post_body, "device_code"), * client_id = request->auth_basic_user, * client_secret = request->auth_basic_password, * ip_source = get_ip_source(request), * username = NULL; int res; char * device_code_hash, * refresh_token, * access_token, * scope = NULL, * issued_for = get_client_hostname(request); time_t now; size_t index = 0; if (client_id == NULL && u_map_get(request->map_post_body, "client_id") != NULL) { client_id = u_map_get(request->map_post_body, "client_id"); } if (client_secret == NULL && u_map_get(request->map_post_body, "client_secret") != NULL) { client_secret = u_map_get(request->map_post_body, "client_secret"); } if (o_strlen(device_code) == GLEWLWYD_DEVICE_AUTH_DEVICE_CODE_LENGTH) { j_client = check_client_valid(config, client_id, client_id, client_secret, NULL, GLEWLWYD_AUTHORIZATION_TYPE_DEVICE_AUTHORIZATION, 0, ip_source); if (check_result_value(j_client, G_OK)) { device_code_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, device_code); j_query = json_pack("{sss[sssss]s{sssOs{ssss}}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_DEVICE_AUTHORIZATION, "columns", "gpgda_id", "gpgda_username AS username", "gpgda_status", SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpgda_expires_at) AS expires_at", "gpgda_expires_at AS expires_at", "EXTRACT(EPOCH FROM gpgda_expires_at)::integer AS expires_at"), SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpgda_last_check) AS last_check", "gpgda_last_check AS last_check", "EXTRACT(EPOCH FROM gpgda_last_check)::integer AS last_check"), "where", "gpgda_device_code_hash", device_code_hash, "gpgda_client_id", json_object_get(json_object_get(j_client, "client"), "client_id"), "gpgda_status", "operator", "raw", "value", "<= 1"); o_free(device_code_hash); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { time(&now); if (json_integer_value(json_object_get(json_array_get(j_result, 0), "expires_at")) >= (json_int_t)now) { if (json_integer_value(json_object_get(json_array_get(j_result, 0), "gpgda_status")) == 1) { j_query = json_pack("{sss[s]s{sOsi}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_DEVICE_AUTHORIZATION_SCOPE, "columns", "gpgdas_scope", "where", "gpgda_id", json_object_get(json_array_get(j_result, 0), "gpgda_id"), "gpgdas_allowed", 1); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result_scope, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result_scope, index, j_element) { if (scope == NULL) { scope = o_strdup(json_string_value(json_object_get(j_element, "gpgdas_scope"))); } else { scope = mstrcatf(scope, " %s", json_string_value(json_object_get(j_element, "gpgdas_scope"))); } } // All clear, please send back tokens username = json_string_value(json_object_get(json_array_get(j_result, 0), "username")); j_user = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, username); if (check_result_value(j_user, G_OK)) { time(&now); if ((refresh_token = generate_refresh_token(config, client_id, username, json_string_value(json_object_get(json_object_get(j_user, "user"), "scope_list")), now, ip_source)) != NULL) { j_refresh_token = serialize_refresh_token(config, GLEWLWYD_AUTHORIZATION_TYPE_DEVICE_AUTHORIZATION, 0, username, client_id, scope, now, config->refresh_token_duration, config->refresh_token_rolling, refresh_token, issued_for, u_map_get_case(request->map_header, "user-agent")); if (check_result_value(j_refresh_token, G_OK)) { j_user_only = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, username); if (check_result_value(j_user_only, G_OK)) { if ((access_token = generate_access_token(config, username, client_id, json_object_get(j_user_only, "user"), json_string_value(json_object_get(json_object_get(j_user, "user"), "scope_list")), now, ip_source)) != NULL) { if (serialize_access_token(config, GLEWLWYD_AUTHORIZATION_TYPE_DEVICE_AUTHORIZATION, json_integer_value(json_object_get(j_refresh_token, "gpgr_id")), username, client_id, scope, now, issued_for, u_map_get_case(request->map_header, "user-agent"), access_token) == G_OK) { j_body = json_pack("{sssssssisIss}", "token_type", "bearer", "access_token", access_token, "refresh_token", refresh_token, "iat", now, "expires_in", config->access_token_duration, "scope", scope); ulfius_set_json_body_response(response, 200, j_body); json_decref(j_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_REFRESH_TOKEN, 1, "plugin", config->name, "response_type", "device_code", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_REFRESH_TOKEN, 1, "plugin", config->name, NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_USER_ACCESS_TOKEN, 1, "plugin", config->name, "response_type", "device_code", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_USER_ACCESS_TOKEN, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oauth2 - Error serialize_access_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oauth2 - Error generate_access_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } o_free(access_token); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oauth2 - Error glewlwyd_plugin_callback_get_user"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_user_only); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oauth2 - Error serialize_refresh_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_refresh_token); o_free(refresh_token); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oauth2 - Error generate_refresh_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oauth2 - Error getting user %s", username); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_user); o_free(scope); json_decref(j_result_scope); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - Error executing j_query (2)"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else { j_query = json_pack("{sss{s{ss}}s{sO}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_DEVICE_AUTHORIZATION, "set", "gpgda_last_check", "raw", SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "CURRENT_TIMESTAMP", "strftime('%s','now')", "NOW()"), "where", "gpgda_id", json_object_get(json_array_get(j_result, 0), "gpgda_id")); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { if (((json_int_t)now - json_integer_value(json_object_get(json_array_get(j_result, 0), "last_check"))) >= json_integer_value(json_object_get(config->j_params, "device-authorization-interval"))) { // Wait for it! j_body = json_pack("{ss}", "error", "authorization_pending"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } else { // Slow down dammit! j_body = json_pack("{ss}", "error", "slow_down"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - Error executing j_query (3)"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } } else { // Code expired j_body = json_pack("{ss}", "error", "expired_token"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "check_auth_type_device_code - Invalid code"); j_body = json_pack("{ss}", "error", "access_denied"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - Error executing j_query (1)"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else { j_body = json_pack("{ss}", "error", "unauthorized_client"); ulfius_set_json_body_response(response, 403, j_body); json_decref(j_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } json_decref(j_client); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "check_auth_type_device_code - Missing code"); j_body = json_pack("{ss}", "error", "access_denied"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } o_free(issued_for); return U_CALLBACK_CONTINUE; } static int callback_revocation(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oauth2_config * config = (struct _oauth2_config *)user_data; json_t * j_result = get_token_metadata(config, u_map_get(request->map_post_body, "token"), u_map_get(request->map_post_body, "token_type_hint"), get_client_id_for_introspection(config, request)); if (check_result_value(j_result, G_OK)) { if (json_object_get(json_object_get(j_result, "token"), "active") == json_true()) { if (0 == o_strcmp("refresh_token", json_string_value(json_object_get(json_object_get(j_result, "token"), "token_type")))) { if (revoke_refresh_token(config, u_map_get(request->map_post_body, "token")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_revocation - Error revoke_refresh_token"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event oauth2 - Plugin '%s' - Refresh token generated for client '%s' revoked, origin: %s", config->name, json_string_value(json_object_get(json_object_get(j_result, "token"), "client_id")), get_ip_source(request)); } } else { if (revoke_access_token(config, u_map_get(request->map_post_body, "token")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_revocation - Error revoke_access_token"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event oauth2 - Plugin '%s' - Access token generated for client '%s' revoked, origin: %s", config->name, json_string_value(json_object_get(json_object_get(j_result, "token"), "client_id")), get_ip_source(request)); } } } } else if (check_result_value(j_result, G_ERROR_PARAM)) { response->status = 400; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_introspection - Error get_token_metadata"); response->status = 500; } json_decref(j_result); return U_CALLBACK_CONTINUE; } static int callback_introspection(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oauth2_config * config = (struct _oauth2_config *)user_data; json_t * j_result = get_token_metadata(config, u_map_get(request->map_post_body, "token"), u_map_get(request->map_post_body, "token_type_hint"), get_client_id_for_introspection(config, request)); if (check_result_value(j_result, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_result, "token")); } else if (check_result_value(j_result, G_ERROR_PARAM)) { response->status = 400; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_introspection - Error get_token_metadata"); response->status = 500; } json_decref(j_result); return U_CALLBACK_CONTINUE; } static int callback_check_intropect_revoke(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oauth2_config * config = (struct _oauth2_config *)user_data; json_t * j_client, * j_element = NULL, * j_introspect; size_t index = 0; int ret = U_CALLBACK_UNAUTHORIZED; if (u_map_get_case(request->map_header, HEADER_AUTHORIZATION) != NULL && config->introspect_revoke_resource_config->oauth_scope != NULL) { j_introspect = get_token_metadata(config, (u_map_get_case(request->map_header, HEADER_AUTHORIZATION) + o_strlen(HEADER_PREFIX_BEARER)), "access_token", NULL); if (check_result_value(j_introspect, G_OK) && json_object_get(json_object_get(j_introspect, "token"), "active") == json_true()) { ret = callback_check_glewlwyd_access_token(request, response, (void*)config->introspect_revoke_resource_config); } json_decref(j_introspect); } else if (json_object_get(config->j_params, "introspection-revocation-allow-target-client") == json_true()) { j_client = config->glewlwyd_config->glewlwyd_callback_check_client_valid(config->glewlwyd_config, request->auth_basic_user, request->auth_basic_password); if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "confidential") == json_true()) { json_array_foreach(json_object_get(json_object_get(j_client, "client"), "authorization_type"), index, j_element) { if (0 == o_strcmp(json_string_value(j_element), "client_credentials")) { ret = U_CALLBACK_CONTINUE; } } } json_decref(j_client); } return ret; } /** * The most used authorization type: if client is authorized and has been granted access to scope, * glewlwyd redirects to redirect_uri with a code in the uri * If necessary, an intermediate step can be used: login page */ static int check_auth_type_auth_code_grant (const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oauth2_config * config = (struct _oauth2_config *)user_data; char * authorization_code = NULL, * redirect_url, * issued_for, * state_param = NULL, * state_encoded, code_challenge_stored[GLEWLWYD_CODE_CHALLENGE_MAX_LENGTH + 1] = {0}; const char * ip_source = get_ip_source(request); json_t * j_session, * j_client = check_client_valid(config, u_map_get(request->map_url, "client_id"), request->auth_basic_user, request->auth_basic_password, u_map_get(request->map_url, "redirect_uri"), GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE, 1, ip_source); int res; if (u_map_get(request->map_url, "state") != NULL) { state_encoded = ulfius_url_encode(u_map_get(request->map_url, "state")); state_param = msprintf("&state=%s", state_encoded); o_free(state_encoded); } else { state_param = o_strdup(""); } // Check if client is allowed to perform this request if (check_result_value(j_client, G_OK)) { // Client is allowed to use auth_code grant with this redirection_uri if (u_map_has_key(request->map_url, "g_continue")) { if (o_strlen(u_map_get(request->map_url, "scope"))) { j_session = validate_session_client_scope(config, request, u_map_get(request->map_url, "client_id"), u_map_get(request->map_url, "scope")); if (check_result_value(j_session, G_OK)) { if (json_object_get(json_object_get(j_session, "session"), "authorization_required") == json_false()) { // User has granted access to the cleaned scope list for this client // Generate code, generate the url and redirect to it issued_for = get_client_hostname(request); if (issued_for != NULL) { if (config->glewlwyd_config->glewlwyd_callback_trigger_session_used(config->glewlwyd_config, request, json_string_value(json_object_get(json_object_get(j_session, "session"), "scope_filtered"))) == G_OK) { if ((res = is_code_challenge_valid(config, u_map_get(request->map_url, "code_challenge"), u_map_get(request->map_url, "code_challenge_method"), code_challenge_stored)) == G_OK) { if ((authorization_code = generate_authorization_code(config, json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username")), u_map_get(request->map_url, "client_id"), json_string_value(json_object_get(json_object_get(j_session, "session"), "scope_filtered")), u_map_get(request->map_url, "redirect_uri"), issued_for, u_map_get_case(request->map_header, "user-agent"), code_challenge_stored)) != NULL) { redirect_url = msprintf("%s%scode=%s%s", u_map_get(request->map_url, "redirect_uri"), (o_strchr(u_map_get(request->map_url, "redirect_uri"), '?')!=NULL?"&":"?"), authorization_code, state_param); ulfius_add_header_to_response(response, "Location", redirect_url); response->status = 302; o_free(redirect_url); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_CODE, 1, "plugin", config->name, NULL); } else { redirect_url = msprintf("%s%serror=server_error", u_map_get(request->map_url, "redirect_uri"), (o_strchr(u_map_get(request->map_url, "redirect_uri"), '?')!=NULL?"&":"?")); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_auth_code_grant - oauth2 - Error generate_authorization_code"); response->status = 302; } o_free(authorization_code); } else if (res == G_ERROR_PARAM) { redirect_url = msprintf("%s%serror=invalid_request", u_map_get(request->map_url, "redirect_uri"), (o_strchr(u_map_get(request->map_url, "redirect_uri"), '?')!=NULL?"&":"?")); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); y_log_message(Y_LOG_LEVEL_DEBUG, "check_auth_type_auth_code_grant - oauth2 - Invalid code_challenge or code_challenge_method, origin: %s", get_ip_source(request)); response->status = 302; } else { redirect_url = msprintf("%s%serror=server_error", u_map_get(request->map_url, "redirect_uri"), (o_strchr(u_map_get(request->map_url, "redirect_uri"), '?')!=NULL?"&":"?")); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_auth_code_grant - oauth2 - Error is_code_challenge_valid"); response->status = 302; } } else { redirect_url = msprintf("%s%serror=server_error", u_map_get(request->map_url, "redirect_uri"), (o_strchr(u_map_get(request->map_url, "redirect_uri"), '?')!=NULL?"&":"?")); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_auth_code_grant - oauth2 - Error glewlwyd_callback_trigger_session_used"); response->status = 302; } } else { redirect_url = msprintf("%s%serror=server_error", u_map_get(request->map_url, "redirect_uri"), (o_strchr(u_map_get(request->map_url, "redirect_uri"), '?')!=NULL?"&":"?")); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_auth_code_grant - oauth2 - Error get_client_hostname"); response->status = 302; } o_free(issued_for); } else { // Redirect to login page redirect_url = get_login_url(config, request, "auth", u_map_get(request->map_url, "client_id"), u_map_get(request->map_url, "scope"), NULL); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); response->status = 302; } } else if (check_result_value(j_session, G_ERROR_NOT_FOUND)) { // Redirect to login page redirect_url = get_login_url(config, request, "auth", u_map_get(request->map_url, "client_id"), u_map_get(request->map_url, "scope"), NULL); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); response->status = 302; } else if (check_result_value(j_session, G_ERROR_UNAUTHORIZED)) { // Scope is not allowed for this user response->status = 302; y_log_message(Y_LOG_LEVEL_DEBUG, "check_auth_type_auth_code_grant - oauth2 - scope list '%s' is invalid for user '%s', origin: %s", u_map_get(request->map_url, "scope"), json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username")), ip_source); redirect_url = msprintf("%s%serror=invalid_scope%s", u_map_get(request->map_url, "redirect_uri"), (o_strchr(u_map_get(request->map_url, "redirect_uri"), '?')!=NULL?"&":"?"), state_param); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } else { redirect_url = msprintf("%s%serror=server_error", u_map_get(request->map_url, "redirect_uri"), (o_strchr(u_map_get(request->map_url, "redirect_uri"), '?')!=NULL?"&":"?")); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_auth_code_grant - oauth2 - Error validate_session_client_scope"); response->status = 302; } json_decref(j_session); } else { // Scope is not allowed for this user y_log_message(Y_LOG_LEVEL_DEBUG, "check_auth_type_auth_code_grant - oauth2 - scope list is missing or empty, origin: %s", ip_source); response->status = 302; redirect_url = msprintf("%s%serror=invalid_scope%s", u_map_get(request->map_url, "redirect_uri"), (o_strchr(u_map_get(request->map_url, "redirect_uri"), '?')!=NULL?"&":"?"), state_param); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } } else { // Redirect to login page redirect_url = get_login_url(config, request, "auth", u_map_get(request->map_url, "client_id"), u_map_get(request->map_url, "scope"), NULL); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); response->status = 302; } } else { // client is not authorized response->status = 302; redirect_url = msprintf("%s%serror=unauthorized_client%s%s", u_map_get(request->map_url, "redirect_uri"), (o_strchr(u_map_get(request->map_url, "redirect_uri"), '?')!=NULL?"&":"?"), (u_map_get(request->map_url, "state")!=NULL?"&state=":""), (u_map_get(request->map_url, "state")!=NULL?u_map_get(request->map_url, "state"):"")); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } o_free(state_param); json_decref(j_client); return U_CALLBACK_CONTINUE; } /** * The second step of authentiation code * Validates if code, client_id and redirect_uri sent are valid, then returns a token set */ static int check_auth_type_access_token_request (const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oauth2_config * config = (struct _oauth2_config *)user_data; const char * code = u_map_get(request->map_post_body, "code"), * client_id = u_map_get(request->map_post_body, "client_id"), * redirect_uri = u_map_get(request->map_post_body, "redirect_uri"), * code_verifier = u_map_get(request->map_post_body, "code_verifier"), * ip_source = get_ip_source(request); char * issued_for = get_client_hostname(request); json_t * j_code, * j_body, * j_refresh_token, * j_client, * j_user; time_t now; char * refresh_token = NULL, * access_token = NULL; if (client_id == NULL && request->auth_basic_user != NULL) { client_id = request->auth_basic_user; } if (code == NULL || client_id == NULL || redirect_uri == NULL) { response->status = 400; } else { j_client = check_client_valid(config, client_id, request->auth_basic_user, request->auth_basic_password, redirect_uri, GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE, 0, ip_source); if (check_result_value(j_client, G_OK)) { j_code = validate_authorization_code(config, code, client_id, redirect_uri, code_verifier, ip_source); if (check_result_value(j_code, G_OK)) { j_user = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, json_string_value(json_object_get(json_object_get(j_code, "code"), "username"))); if (check_result_value(j_user, G_OK)) { time(&now); if ((refresh_token = generate_refresh_token(config, client_id, json_string_value(json_object_get(json_object_get(j_code, "code"), "username")), json_string_value(json_object_get(json_object_get(j_code, "code"), "scope_list")), now, ip_source)) != NULL) { j_refresh_token = serialize_refresh_token(config, GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE, json_integer_value(json_object_get(json_object_get(j_code, "code"), "gpgc_id")), json_string_value(json_object_get(json_object_get(j_code, "code"), "username")), client_id, json_string_value(json_object_get(json_object_get(j_code, "code"), "scope_list")), now, json_integer_value(json_object_get(json_object_get(j_code, "code"), "refresh-token-duration")), json_object_get(json_object_get(j_code, "code"), "refresh-token-rolling")==json_true(), refresh_token, issued_for, u_map_get_case(request->map_header, "user-agent")); if (check_result_value(j_refresh_token, G_OK)) { if ((access_token = generate_access_token(config, json_string_value(json_object_get(json_object_get(j_code, "code"), "username")), client_id, json_object_get(j_user, "user"), json_string_value(json_object_get(json_object_get(j_code, "code"), "scope_list")), now, ip_source)) != NULL) { if (serialize_access_token(config, GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE, json_integer_value(json_object_get(j_refresh_token, "gpgr_id")), json_string_value(json_object_get(json_object_get(j_code, "code"), "username")), client_id, json_string_value(json_object_get(json_object_get(j_code, "code"), "scope_list")), now, issued_for, u_map_get_case(request->map_header, "user-agent"), access_token) == G_OK) { if (disable_authorization_code(config, json_integer_value(json_object_get(json_object_get(j_code, "code"), "gpgc_id"))) == G_OK) { j_body = json_pack("{sssssssisIss}", "token_type", "bearer", "access_token", access_token, "refresh_token", refresh_token, "iat", now, "expires_in", config->access_token_duration, "scope", json_string_value(json_object_get(json_object_get(j_code, "code"), "scope_list"))); ulfius_set_json_body_response(response, 200, j_body); json_decref(j_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_REFRESH_TOKEN, 1, "plugin", config->name, "response_type", "code", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_REFRESH_TOKEN, 1, "plugin", config->name, NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_USER_ACCESS_TOKEN, 1, "plugin", config->name, "response_type", "code", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_USER_ACCESS_TOKEN, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_access_token_request - oauth2 - Error disable_authorization_code"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_access_token_request - oauth2 - Error serialize_access_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_access_token_request - oauth2 - Error generate_access_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } o_free(access_token); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_access_token_request - oauth2 - Error serialize_refresh_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_refresh_token); o_free(refresh_token); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_access_token_request - oauth2 - Error generate_refresh_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_access_token_request - oauth2 - Error glewlwyd_plugin_callback_get_user"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_user); } else if (check_result_value(j_code, G_ERROR_UNAUTHORIZED)) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Code invalid at IP Address %s", get_ip_source(request)); j_body = json_pack("{ss}", "error", "invalid_code"); ulfius_set_json_body_response(response, 403, j_body); json_decref(j_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_INVALID_CODE, 1, "plugin", config->name, NULL); } else if (check_result_value(j_code, G_ERROR_PARAM)) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Code invalid at IP Address %s", get_ip_source(request)); j_body = json_pack("{ss}", "error", "invalid_request"); ulfius_set_json_body_response(response, 403, j_body); json_decref(j_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_INVALID_CODE, 1, "plugin", config->name, NULL); } else { j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_code); } else { j_body = json_pack("{ss}", "error", "unauthorized_client"); ulfius_set_json_body_response(response, 403, j_body); json_decref(j_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } json_decref(j_client); } o_free(issued_for); return U_CALLBACK_CONTINUE; } /** * The second more simple authorization type: client redirects user to login page, * Then if authorized, glewlwyd redirects to redirect_uri with the access_token in the uri * If necessary, an intermediate step can be used: login page */ static int check_auth_type_implicit_grant (const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oauth2_config * config = (struct _oauth2_config *)user_data; const char * ip_source = get_ip_source(request); char * redirect_url, * issued_for, * state_encoded = NULL, * state_param = NULL; json_t * j_session, * j_client = check_client_valid(config, u_map_get(request->map_url, "client_id"), request->auth_basic_user, request->auth_basic_password, u_map_get(request->map_url, "redirect_uri"), GLEWLWYD_AUTHORIZATION_TYPE_IMPLICIT, 1, ip_source); char * access_token; time_t now; if (u_map_get(request->map_url, "state") != NULL) { state_encoded = ulfius_url_encode(u_map_get(request->map_url, "state")); state_param = msprintf("&state=%s", state_encoded); o_free(state_encoded); } else { state_param = o_strdup(""); } // Check if client is allowed to perform this request if (check_result_value(j_client, G_OK)) { // Client is allowed to use auth_code grant with this redirection_uri if (u_map_has_key(request->map_url, "g_continue")) { if (o_strlen(u_map_get(request->map_url, "scope"))) { j_session = validate_session_client_scope(config, request, u_map_get(request->map_url, "client_id"), u_map_get(request->map_url, "scope")); if (check_result_value(j_session, G_OK)) { if (json_object_get(json_object_get(j_session, "session"), "authorization_required") == json_false()) { // User has granted access to the cleaned scope list for this client // Generate access token issued_for = get_client_hostname(request); if (issued_for != NULL) { time(&now); if ((access_token = generate_access_token(config, json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username")), u_map_get(request->map_url, "client_id"), json_object_get(json_object_get(j_session, "session"), "user"), json_string_value(json_object_get(json_object_get(j_session, "session"), "scope_filtered")), now, ip_source)) != NULL) { if (serialize_access_token(config, GLEWLWYD_AUTHORIZATION_TYPE_IMPLICIT, 0, json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username")), u_map_get(request->map_url, "client_id"), json_string_value(json_object_get(json_object_get(j_session, "session"), "scope_filtered")), now, issued_for, u_map_get_case(request->map_header, "user-agent"), access_token) == G_OK) { if (config->glewlwyd_config->glewlwyd_callback_trigger_session_used(config->glewlwyd_config, request, json_string_value(json_object_get(json_object_get(j_session, "session"), "scope_filtered"))) == G_OK) { redirect_url = msprintf("%s%saccess_token=%s&token_type=bearer&expires_in=%" JSON_INTEGER_FORMAT "&scope=%s%s", u_map_get(request->map_url, "redirect_uri"), (o_strchr(u_map_get(request->map_url, "redirect_uri"), '#')!=NULL?"&":"#"), access_token, config->access_token_duration, json_string_value(json_object_get(json_object_get(j_session, "session"), "scope_filtered")), state_param); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); response->status = 302; config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_USER_ACCESS_TOKEN, 1, "plugin", config->name, "response_type", "code", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_USER_ACCESS_TOKEN, 1, "plugin", config->name, NULL); } else { redirect_url = msprintf("%s%serror=server_error", u_map_get(request->map_url, "redirect_uri"), (o_strchr(u_map_get(request->map_url, "redirect_uri"), '?')!=NULL?"&":"?")); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_implicit_grant - oauth2 - Error glewlwyd_callback_trigger_session_used"); response->status = 302; } } else { redirect_url = msprintf("%s%serror=server_error", u_map_get(request->map_url, "redirect_uri"), (o_strchr(u_map_get(request->map_url, "redirect_uri"), '?')!=NULL?"&":"?")); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_implicit_grant - oauth2 - Error serialize_access_token"); response->status = 302; } } else { redirect_url = msprintf("%s%serror=server_error", u_map_get(request->map_url, "redirect_uri"), (o_strchr(u_map_get(request->map_url, "redirect_uri"), '?')!=NULL?"&":"?")); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_implicit_grant - oauth2 - Error generate_access_token"); response->status = 302; } o_free(access_token); } else { redirect_url = msprintf("%s%serror=server_error", u_map_get(request->map_url, "redirect_uri"), (o_strchr(u_map_get(request->map_url, "redirect_uri"), '?')!=NULL?"&":"?")); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_implicit_grant - oauth2 - Error get_client_hostname"); response->status = 302; } o_free(issued_for); } else { // Redirect to login page redirect_url = get_login_url(config, request, "auth", u_map_get(request->map_url, "client_id"), u_map_get(request->map_url, "scope"), NULL); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); response->status = 302; } } else if (check_result_value(j_session, G_ERROR_UNAUTHORIZED)) { // Scope is not allowed for this user y_log_message(Y_LOG_LEVEL_DEBUG, "check_auth_type_implicit_grant - oauth2 - Scope list '%s' is not allowed for user '%s', origin: %s", u_map_get(request->map_url, "scope"), json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username")), ip_source); response->status = 302; redirect_url = msprintf("%s%serror=invalid_scope%s", u_map_get(request->map_url, "redirect_uri"), (o_strchr(u_map_get(request->map_url, "redirect_uri"), '?')!=NULL?"&":"?"), state_param); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } else { redirect_url = msprintf("%s%serror=server_error", u_map_get(request->map_url, "redirect_uri"), (o_strchr(u_map_get(request->map_url, "redirect_uri"), '?')!=NULL?"&":"?")); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_implicit_grant - oauth2 - Error validate_session_client_scope"); response->status = 302; } json_decref(j_session); } else { // Empty scope is not allowed response->status = 302; redirect_url = msprintf("%s%serror=invalid_scope%s", u_map_get(request->map_url, "redirect_uri"), (o_strchr(u_map_get(request->map_url, "redirect_uri"), '?')!=NULL?"&":"?"), state_param); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } } else { // Redirect to login page redirect_url = get_login_url(config, request, "auth", u_map_get(request->map_url, "client_id"), u_map_get(request->map_url, "scope"), NULL); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); response->status = 302; } } else { // client is not authorized response->status = 302; redirect_url = msprintf("%s%serror=unauthorized_client%s%s", u_map_get(request->map_url, "redirect_uri"), (o_strchr(u_map_get(request->map_url, "redirect_uri"), '?')!=NULL?"&":"?"), (u_map_get(request->map_url, "state")!=NULL?"&state=":""), (u_map_get(request->map_url, "state")!=NULL?u_map_get(request->map_url, "state"):"")); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } o_free(state_param); json_decref(j_client); return U_CALLBACK_CONTINUE; } /** * The more simple authorization type * username and password are given in the POST parameters, * the access_token and refresh_token in a json object are returned */ static int check_auth_type_resource_owner_pwd_cred (const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oauth2_config * config = (struct _oauth2_config *)user_data; json_t * j_user, * j_client, * j_refresh_token, * j_body, * j_user_only, * j_element = NULL, * j_refresh = NULL; int ret = G_OK, auth_type_allowed = 0; const char * username = u_map_get(request->map_post_body, "username"), * password = u_map_get(request->map_post_body, "password"), * scope = u_map_get(request->map_post_body, "scope"), * client_id = NULL, * ip_source = get_ip_source(request); char * issued_for = get_client_hostname(request), * refresh_token, * access_token; time_t now; size_t index = 0; if (scope == NULL || username == NULL || password == NULL || issued_for == NULL) { ret = G_ERROR_PARAM; } else if (request->auth_basic_user != NULL && request->auth_basic_password != NULL) { j_client = config->glewlwyd_config->glewlwyd_callback_check_client_valid(config->glewlwyd_config, request->auth_basic_user, request->auth_basic_password); if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "confidential") != json_true()) { ret = G_ERROR_PARAM; } else if (check_result_value(j_client, G_OK)) { json_array_foreach(json_object_get(json_object_get(j_client, "client"), "authorization_type"), index, j_element) { if (0 == o_strcmp(json_string_value(j_element), "password")) { auth_type_allowed = 1; } } if (!auth_type_allowed) { ret = G_ERROR_PARAM; } else { client_id = request->auth_basic_user; } } else if (check_result_value(j_client, G_ERROR_NOT_FOUND) || check_result_value(j_client, G_ERROR_UNAUTHORIZED)) { ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_resource_owner_pwd_cred - oauth2 - Error glewlwyd_callback_check_client_valid"); ret = G_ERROR; } json_decref(j_client); } if (ret == G_OK) { j_user = config->glewlwyd_config->glewlwyd_callback_check_user_valid(config->glewlwyd_config, username, password, scope); if (check_result_value(j_user, G_OK)) { j_refresh = get_refresh_token_duration_rolling(config, json_string_value(json_object_get(json_object_get(j_user, "user"), "scope_list"))); if (check_result_value(j_refresh, G_OK)) { time(&now); if ((refresh_token = generate_refresh_token(config, client_id, username, json_string_value(json_object_get(json_object_get(j_user, "user"), "scope_list")), now, ip_source)) != NULL) { j_refresh_token = serialize_refresh_token(config, GLEWLWYD_AUTHORIZATION_TYPE_RESOURCE_OWNER_PASSWORD_CREDENTIALS, 0, username, client_id, json_string_value(json_object_get(json_object_get(j_user, "user"), "scope_list")), now, json_integer_value(json_object_get(json_object_get(j_refresh, "refresh-token"), "refresh-token-duration")), json_object_get(json_object_get(j_refresh, "refresh-token"), "refresh-token-rolling")==json_true(), refresh_token, issued_for, u_map_get_case(request->map_header, "user-agent")); if (check_result_value(j_refresh_token, G_OK)) { j_user_only = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, username); if (check_result_value(j_user_only, G_OK)) { if ((access_token = generate_access_token(config, username, client_id, json_object_get(j_user_only, "user"), json_string_value(json_object_get(json_object_get(j_user, "user"), "scope_list")), now, ip_source)) != NULL) { if (serialize_access_token(config, GLEWLWYD_AUTHORIZATION_TYPE_RESOURCE_OWNER_PASSWORD_CREDENTIALS, json_integer_value(json_object_get(j_refresh_token, "gpgr_id")), username, client_id, json_string_value(json_object_get(json_object_get(j_user, "user"), "scope_list")), now, issued_for, u_map_get_case(request->map_header, "user-agent"), access_token) == G_OK) { j_body = json_pack("{sssssssisIss}", "token_type", "bearer", "access_token", access_token, "refresh_token", refresh_token, "iat", now, "expires_in", config->access_token_duration, "scope", json_string_value(json_object_get(json_object_get(j_user, "user"), "scope_list"))); ulfius_set_json_body_response(response, 200, j_body); json_decref(j_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_REFRESH_TOKEN, 1, "plugin", config->name, "response_type", "password", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_REFRESH_TOKEN, 1, "plugin", config->name, NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_USER_ACCESS_TOKEN, 1, "plugin", config->name, "response_type", "password", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_USER_ACCESS_TOKEN, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_resource_owner_pwd_cred - oauth2 - Error serialize_access_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_resource_owner_pwd_cred - oauth2 - Error generate_access_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } o_free(access_token); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_resource_owner_pwd_cred - oauth2 - Error glewlwyd_plugin_callback_get_user"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_user_only); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_resource_owner_pwd_cred - oauth2 - Error serialize_refresh_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_refresh_token); o_free(refresh_token); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_resource_owner_pwd_cred - oauth2 - Error generate_refresh_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_resource_owner_pwd_cred - oauth2 - Error get_refresh_token_duration_rolling"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_refresh); } else if (check_result_value(j_user, G_ERROR_NOT_FOUND) || check_result_value(j_user, G_ERROR_UNAUTHORIZED)) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_auth_type_resource_owner_pwd_cred - oauth2 - Error user '%s'", username); y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for username %s at IP Address %s", username, ip_source); response->status = 403; } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_resource_owner_pwd_cred - oauth2 - glewlwyd_callback_check_user_valid"); response->status = 403; } json_decref(j_user); } else if (ret == G_ERROR_PARAM) { response->status = 400; } else { response->status = 500; } o_free(issued_for); return U_CALLBACK_CONTINUE; } /** * Send an access_token to a confidential client */ static int check_auth_type_client_credentials_grant (const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oauth2_config * config = (struct _oauth2_config *)user_data; json_t * j_client, * j_element = NULL, * json_body; char ** scope_array, ** scope_allowed = NULL, * scope_joined, * access_token, * issued_for = get_client_hostname(request); size_t index = 0; int i, i_scope_allowed = 0, auth_type_allowed = 0; time_t now; const char * ip_source = get_ip_source(request); if (issued_for == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_client_credentials_grant - oauth2 - Error get_client_hostname"); response->status = 500; } else if (request->auth_basic_user != NULL && request->auth_basic_password != NULL && o_strlen(u_map_get(request->map_post_body, "scope")) > 0) { j_client = config->glewlwyd_config->glewlwyd_callback_check_client_valid(config->glewlwyd_config, request->auth_basic_user, request->auth_basic_password); if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "confidential") == json_true()) { json_array_foreach(json_object_get(json_object_get(j_client, "client"), "authorization_type"), index, j_element) { if (0 == o_strcmp(json_string_value(j_element), "client_credentials")) { auth_type_allowed = 1; } } if (split_string(u_map_get(request->map_post_body, "scope"), " ", &scope_array) > 0) { for (i=0; scope_array[i]!=NULL; i++) { json_array_foreach(json_object_get(json_object_get(j_client, "client"), "scope"), index, j_element) { if (0 == o_strcmp(json_string_value(j_element), scope_array[i])) { if (scope_allowed == NULL) { scope_allowed = o_malloc(2 * sizeof(char*)); } else { scope_allowed = o_realloc(scope_allowed, (2 + i_scope_allowed) * sizeof(char*)); } scope_allowed[i_scope_allowed] = scope_array[i]; scope_allowed[i_scope_allowed+1] = NULL; i_scope_allowed++; } } } if (!i_scope_allowed) { json_body = json_pack("{ss}", "error", "scope_invalid"); ulfius_set_json_body_response(response, 400, json_body); json_decref(json_body); } else if (!auth_type_allowed) { json_body = json_pack("{ss}", "error", "authorization_type_invalid"); ulfius_set_json_body_response(response, 400, json_body); json_decref(json_body); } else { scope_joined = string_array_join((const char **)scope_allowed, " "); time(&now); if ((access_token = generate_client_access_token(config, request->auth_basic_user, scope_joined, now, ip_source)) != NULL) { if (serialize_access_token(config, GLEWLWYD_AUTHORIZATION_TYPE_CLIENT_CREDENTIALS, 0, NULL, request->auth_basic_user, scope_joined, now, issued_for, u_map_get_case(request->map_header, "user-agent"), access_token) == G_OK) { json_body = json_pack("{sssssIss}", "access_token", access_token, "token_type", "bearer", "expires_in", config->access_token_duration, "scope", scope_joined); ulfius_set_json_body_response(response, 200, json_body); json_decref(json_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_CLIENT_ACCESS_TOKEN, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_client_credentials_grant - oauth2 - Error serialize_access_token"); response->status = 500; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_client_credentials_grant - oauth2 - Error generate_client_access_token"); response->status = 500; } o_free(access_token); o_free(scope_joined); o_free(scope_allowed); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_client_credentials_grant - oauth2 - Error split_string"); response->status = 500; } free_string_array(scope_array); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "check_auth_type_client_credentials_grant - oauth2 - Error client_id '%s' invalid", request->auth_basic_user); y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for username %s at IP Address %s", request->auth_basic_user, ip_source); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); response->status = 403; } json_decref(j_client); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "oauth2 check_auth_type_client_credentials_grant - Error invalid input parameters. client_id: '%s', scope: '%s', origin: %s", request->auth_basic_user, u_map_get(request->map_post_body, "scope"), get_ip_source(request)); response->status = 403; } o_free(issued_for); return U_CALLBACK_CONTINUE; } /** * Get a new access_token from a valid refresh_token */ static int get_access_token_from_refresh (const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oauth2_config * config = (struct _oauth2_config *)user_data; const char * refresh_token = u_map_get(request->map_post_body, "refresh_token"), * ip_source = get_ip_source(request); json_t * j_refresh, * json_body, * j_client, * j_user; time_t now; char * access_token, * scope_joined = NULL, * issued_for; int has_error = 0, has_issues = 0; if (refresh_token != NULL && o_strlen(refresh_token)) { j_refresh = validate_refresh_token(config, refresh_token); if (check_result_value(j_refresh, G_OK)) { if (json_object_get(json_object_get(j_refresh, "token"), "client_id") != json_null()) { j_client = check_client_valid(config, json_string_value(json_object_get(json_object_get(j_refresh, "token"), "client_id")), request->auth_basic_user, request->auth_basic_password, NULL, GLEWLWYD_AUTHORIZATION_TYPE_REFRESH_TOKEN, 0, ip_source); if (!check_result_value(j_client, G_OK)) { has_issues = 1; } else if (request->auth_basic_user == NULL && request->auth_basic_password == NULL && json_object_get(json_object_get(j_client, "client"), "confidential") == json_true()) { y_log_message(Y_LOG_LEVEL_DEBUG, "get_access_token_from_refresh - oauth2 - client '%s' is invalid or is not confidential, origin: %s", request->auth_basic_user, ip_source); has_issues = 1; } json_decref(j_client); } time(&now); issued_for = get_client_hostname(request); scope_joined = join_json_string_array(json_object_get(json_object_get(j_refresh, "token"), "scope"), " "); if (scope_joined == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "get_access_token_from_refresh - oauth2 - Error join_json_string_array"); has_error = 1; } if (update_refresh_token(config, json_integer_value(json_object_get(json_object_get(j_refresh, "token"), "gpgr_id")), (json_object_get(json_object_get(j_refresh, "token"), "rolling_expiration") == json_true())?json_integer_value(json_object_get(json_object_get(j_refresh, "token"), "duration")):0, 0, now) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "get_access_token_from_refresh - oauth2 - Error update_refresh_token"); has_error = 1; } if (!has_error && !has_issues) { j_user = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, json_string_value(json_object_get(json_object_get(j_refresh, "token"), "username"))); if (check_result_value(j_user, G_OK)) { if ((access_token = generate_access_token(config, json_string_value(json_object_get(json_object_get(j_refresh, "token"), "username")), json_string_value(json_object_get(json_object_get(j_refresh, "token"), "client_id")), json_object_get(j_user, "user"), scope_joined, now, ip_source)) != NULL) { if (serialize_access_token(config, GLEWLWYD_AUTHORIZATION_TYPE_REFRESH_TOKEN, json_integer_value(json_object_get(json_object_get(j_refresh, "token"), "gpgr_id")), json_string_value(json_object_get(json_object_get(j_refresh, "token"), "username")), json_string_value(json_object_get(json_object_get(j_refresh, "token"), "client_id")), scope_joined, now, issued_for, u_map_get_case(request->map_header, "user-agent"), access_token) == G_OK) { json_body = json_pack("{sssssIsssi}", "access_token", access_token, "token_type", "bearer", "expires_in", config->access_token_duration, "scope", scope_joined, "iat", now); ulfius_set_json_body_response(response, 200, json_body); json_decref(json_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_USER_ACCESS_TOKEN, 1, "plugin", config->name, "response_type", "refresh_token", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_USER_ACCESS_TOKEN, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_access_token_from_refresh - oauth2 - Error serialize_access_token"); response->status = 500; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_access_token_from_refresh - oauth2 - Error generate_client_access_token"); response->status = 500; } o_free(access_token); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "get_access_token_from_refresh - oauth2 - Error glewlwyd_plugin_callback_get_user, origin: %s", ip_source); response->status = 500; } json_decref(j_user); } else if (has_issues) { response->status = 400; } else { response->status = 500; } o_free(issued_for); } else if (check_result_value(j_refresh, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Token invalid at IP Address %s", get_ip_source(request)); response->status = 400; config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_INVALID_REFRESH_TOKEN, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_access_token_from_refresh - oauth2 - Error validate_refresh_token"); response->status = 500; } json_decref(j_refresh); o_free(scope_joined); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "get_access_token_from_refresh - oauth2 - Error token empty or missing, origin: %s", ip_source); response->status = 400; } return U_CALLBACK_CONTINUE; } /** * Invalidate a refresh token */ static int delete_refresh_token (const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oauth2_config * config = (struct _oauth2_config *)user_data; const char * refresh_token = u_map_get(request->map_post_body, "refresh_token"), * ip_source = get_ip_source(request); json_t * j_refresh, * j_client; time_t now; char * issued_for; int has_issues = 0; if (refresh_token != NULL && o_strlen(refresh_token)) { j_refresh = validate_refresh_token(config, refresh_token); if (check_result_value(j_refresh, G_OK)) { if (json_object_get(json_object_get(j_refresh, "token"), "client_id") != json_null()) { j_client = check_client_valid(config, json_string_value(json_object_get(json_object_get(j_refresh, "token"), "client_id")), request->auth_basic_user, request->auth_basic_password, NULL, GLEWLWYD_AUTHORIZATION_TYPE_REFRESH_TOKEN, 0, ip_source); if (!check_result_value(j_client, G_OK)) { y_log_message(Y_LOG_LEVEL_DEBUG, "delete_refresh_token - oauth2 - client '%s' is invalid, origin: %s", request->auth_basic_user, ip_source); has_issues = 1; } else if (request->auth_basic_user == NULL && request->auth_basic_password == NULL && json_object_get(json_object_get(j_client, "client"), "confidential") == json_true()) { y_log_message(Y_LOG_LEVEL_DEBUG, "delete_refresh_token - oauth2 - client '%s' is invalid or is not confidential, origin: %s", request->auth_basic_user, ip_source); has_issues = 1; } json_decref(j_client); } if (!has_issues) { time(&now); issued_for = get_client_hostname(request); if (update_refresh_token(config, json_integer_value(json_object_get(json_object_get(j_refresh, "token"), "gpgr_id")), 0, 1, now) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "delete_refresh_token - oauth2 - Error update_refresh_token"); response->status = 500; } o_free(issued_for); } else { response->status = 400; } } else if (check_result_value(j_refresh, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Token invalid at IP Address %s", get_ip_source(request)); response->status = 400; config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_INVALID_REFRESH_TOKEN, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_refresh_token - oauth2 - Error validate_refresh_token"); response->status = 500; } json_decref(j_refresh); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "delete_refresh_token - oauth2 - token missing or empty, origin: %s", get_ip_source(request)); response->status = 400; } return U_CALLBACK_CONTINUE; } static int callback_check_glewlwyd_session_or_token(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oauth2_config * config = (struct _oauth2_config *)user_data; json_t * j_session, * j_user, * j_introspect; int ret = U_CALLBACK_UNAUTHORIZED; if (u_map_get_case(request->map_header, HEADER_AUTHORIZATION) != NULL && o_strlen(u_map_get_case(request->map_header, HEADER_AUTHORIZATION)) >= o_strlen(HEADER_PREFIX_BEARER)) { j_introspect = get_token_metadata(config, (u_map_get_case(request->map_header, HEADER_AUTHORIZATION) + o_strlen(HEADER_PREFIX_BEARER)), "access_token", NULL); if (check_result_value(j_introspect, G_OK) && json_object_get(json_object_get(j_introspect, "token"), "active") == json_true()) { ret = callback_check_glewlwyd_access_token(request, response, (void*)config->glewlwyd_resource_config); } json_decref(j_introspect); } else { if (o_strlen(u_map_get(request->map_url, "impersonate"))) { j_session = config->glewlwyd_config->glewlwyd_callback_check_session_valid(config->glewlwyd_config, request, config->glewlwyd_config->glewlwyd_config->admin_scope); if (check_result_value(j_session, G_OK)) { j_user = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, u_map_get(request->map_url, "impersonate")); if (check_result_value(j_user, G_OK)) { if (ulfius_set_response_shared_data(response, json_pack("{ss}", "username", u_map_get(request->map_url, "impersonate")), (void (*)(void *))&json_decref) != U_OK) { ret = U_CALLBACK_ERROR; } else { ret = U_CALLBACK_CONTINUE; } } json_decref(j_user); } json_decref(j_session); } else { j_session = config->glewlwyd_config->glewlwyd_callback_check_session_valid(config->glewlwyd_config, request, NULL); if (check_result_value(j_session, G_OK)) { if (ulfius_set_response_shared_data(response, json_pack("{ss}", "username", json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username"))), (void (*)(void *))&json_decref) != U_OK) { ret = U_CALLBACK_ERROR; } else { ret = U_CALLBACK_CONTINUE; } } json_decref(j_session); } } return ret; } static int callback_oauth2_authorization(const struct _u_request * request, struct _u_response * response, void * user_data) { const char * response_type = u_map_get(request->map_url, "response_type"); int result = U_CALLBACK_CONTINUE; char * redirect_url, * state_encoded = NULL, * state_param = NULL; u_map_put(response->map_header, "Cache-Control", "no-store"); u_map_put(response->map_header, "Pragma", "no-cache"); u_map_put(response->map_header, "Referrer-Policy", "no-referrer"); if (u_map_get(request->map_url, "state") != NULL) { state_encoded = ulfius_url_encode(u_map_get(request->map_url, "state")); state_param = msprintf("&state=%s", state_encoded); o_free(state_encoded); } else { state_param = o_strdup(""); } if (0 == o_strcmp("code", response_type)) { if (is_authorization_type_enabled((struct _oauth2_config *)user_data, GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE) && u_map_get(request->map_url, "redirect_uri") != NULL) { result = check_auth_type_auth_code_grant(request, response, user_data); } else { if (u_map_get(request->map_url, "redirect_uri") != NULL) { response->status = 302; redirect_url = msprintf("%s#error=unsupported_response_type%s", u_map_get(request->map_url, "redirect_uri"), state_param); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } else { response->status = 403; } } } else if (0 == o_strcmp("token", response_type)) { if (is_authorization_type_enabled((struct _oauth2_config *)user_data, GLEWLWYD_AUTHORIZATION_TYPE_IMPLICIT) && u_map_get(request->map_url, "redirect_uri") != NULL) { result = check_auth_type_implicit_grant(request, response, user_data); } else { if (u_map_get(request->map_url, "redirect_uri") != NULL) { response->status = 302; redirect_url = msprintf("%s#error=unsupported_response_type%s", u_map_get(request->map_url, "redirect_uri"), state_param); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } else { response->status = 403; } } } else { if (u_map_get(request->map_url, "redirect_uri") != NULL) { response->status = 302; redirect_url = msprintf("%s#error=unsupported_response_type%s", u_map_get(request->map_url, "redirect_uri"), state_param); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } else { response->status = 403; } } o_free(state_param); return result; } static int callback_oauth2_token(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oauth2_config * config = (struct _oauth2_config *)user_data; const char * grant_type = u_map_get(request->map_post_body, "grant_type"); int result = U_CALLBACK_CONTINUE; u_map_put(response->map_header, "Cache-Control", "no-store"); u_map_put(response->map_header, "Pragma", "no-cache"); u_map_put(response->map_header, "Referrer-Policy", "no-referrer"); if (0 == o_strcmp("authorization_code", grant_type)) { if (is_authorization_type_enabled(config, GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE)) { result = check_auth_type_access_token_request(request, response, user_data); } else { response->status = 403; } } else if (0 == o_strcmp("password", grant_type)) { if (is_authorization_type_enabled(config, GLEWLWYD_AUTHORIZATION_TYPE_RESOURCE_OWNER_PASSWORD_CREDENTIALS)) { result = check_auth_type_resource_owner_pwd_cred(request, response, user_data); } else { response->status = 403; } } else if (0 == o_strcmp("client_credentials", grant_type)) { if (is_authorization_type_enabled(config, GLEWLWYD_AUTHORIZATION_TYPE_CLIENT_CREDENTIALS)) { result = check_auth_type_client_credentials_grant(request, response, user_data); } else { response->status = 403; } } else if (0 == o_strcmp("refresh_token", grant_type)) { result = get_access_token_from_refresh(request, response, user_data); } else if (0 == o_strcmp("delete_token", grant_type)) { result = delete_refresh_token(request, response, user_data); } else if (0 == o_strcmp("urn:ietf:params:oauth:grant-type:device_code", grant_type)) { result = check_auth_type_device_code(request, response, user_data); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_oauth2_token - oauth2 - Unknown grant_type '%s', origin: %s", grant_type, get_ip_source(request)); response->status = 400; } return result; } static int callback_oauth2_get_profile(const struct _u_request * request, struct _u_response * response, void * user_data) { UNUSED(request); struct _oauth2_config * config = (struct _oauth2_config *)user_data; json_t * j_profile = config->glewlwyd_config->glewlwyd_plugin_callback_get_user_profile(config->glewlwyd_config, json_string_value(json_object_get((json_t *)response->shared_data, "username"))); u_map_put(response->map_header, "Cache-Control", "no-store"); u_map_put(response->map_header, "Pragma", "no-cache"); u_map_put(response->map_header, "Referrer-Policy", "no-referrer"); if (check_result_value(j_profile, G_OK)) { json_object_del(json_object_get(j_profile, "user"), "scope"); json_object_del(json_object_get(j_profile, "user"), "enabled"); json_object_del(json_object_get(j_profile, "user"), "source"); json_object_del(json_object_get(j_profile, "user"), "last_login"); ulfius_set_json_body_response(response, 200, json_object_get(j_profile, "user")); } else { response->status = 404; } json_decref(j_profile); return U_CALLBACK_CONTINUE; } static int callback_oauth2_refresh_token_list_get(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oauth2_config * config = (struct _oauth2_config *)user_data; size_t offset = 0, limit = GLEWLWYD_DEFAULT_LIMIT_SIZE; long int l_converted = 0; char * endptr = NULL, * sort = NULL; json_t * j_refresh_list; u_map_put(response->map_header, "Cache-Control", "no-store"); u_map_put(response->map_header, "Pragma", "no-cache"); u_map_put(response->map_header, "Referrer-Policy", "no-referrer"); if (u_map_get(request->map_url, "offset") != NULL) { l_converted = strtol(u_map_get(request->map_url, "offset"), &endptr, 10); if (!(*endptr) && l_converted > 0) { offset = (size_t)l_converted; } } if (u_map_get(request->map_url, "limit") != NULL) { l_converted = strtol(u_map_get(request->map_url, "limit"), &endptr, 10); if (!(*endptr) && l_converted > 0) { limit = (size_t)l_converted; } } if (0 == o_strcmp(u_map_get(request->map_url, "sort"), "authorization_type") || 0 == o_strcmp(u_map_get(request->map_url, "sort"), "client_id") || 0 == o_strcmp(u_map_get(request->map_url, "sort"), "issued_at") || 0 == o_strcmp(u_map_get(request->map_url, "sort"), "last_seen") || 0 == o_strcmp(u_map_get(request->map_url, "sort"), "expires_at") || 0 == o_strcmp(u_map_get(request->map_url, "sort"), "issued_for") || 0 == o_strcmp(u_map_get(request->map_url, "sort"), "user_agent") || 0 == o_strcmp(u_map_get(request->map_url, "sort"), "enabled") || 0 == o_strcmp(u_map_get(request->map_url, "sort"), "rolling_expiration")) { sort = msprintf("gpgr_%s%s", u_map_get(request->map_url, "sort"), (u_map_get_case(request->map_url, "desc")!=NULL?" DESC":" ASC")); } j_refresh_list = refresh_token_list_get(config, json_string_value(json_object_get((json_t *)response->shared_data, "username")), u_map_get(request->map_url, "pattern"), offset, limit, sort); if (check_result_value(j_refresh_list, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_refresh_list, "refresh_token")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oauth2_refresh_token_list_get - Error refresh_token_list_get"); response->status = 500; } o_free(sort); json_decref(j_refresh_list); return U_CALLBACK_CONTINUE; } static int callback_oauth2_disable_refresh_token(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oauth2_config * config = (struct _oauth2_config *)user_data; int res; u_map_put(response->map_header, "Cache-Control", "no-store"); u_map_put(response->map_header, "Pragma", "no-cache"); u_map_put(response->map_header, "Referrer-Policy", "no-referrer"); if ((res = refresh_token_disable(config, json_string_value(json_object_get((json_t *)response->shared_data, "username")), u_map_get(request->map_url, "token_hash"), get_ip_source(request))) == G_ERROR_NOT_FOUND) { response->status = 404; } else if (res == G_ERROR_PARAM) { response->status = 400; } else if (res != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oauth2_disable_refresh_token - Error refresh_token_disable"); response->status = 500; } return U_CALLBACK_CONTINUE; } /** * Generates a new device_authorization if the client is allowed */ static int callback_oauth2_device_authorization(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oauth2_config * config = (struct _oauth2_config *)user_data; const char * ip_source = get_ip_source(request), * client_id = request->auth_basic_user, * client_secret = request->auth_basic_password; char * verification_uri, * verification_uri_complete, * plugin_url = config->glewlwyd_config->glewlwyd_callback_get_plugin_external_url(config->glewlwyd_config, json_string_value(json_object_get(config->j_params, "name"))); json_t * j_client, * j_body, * j_result; if (client_id == NULL && u_map_get(request->map_post_body, "client_id") != NULL) { client_id = u_map_get(request->map_post_body, "client_id"); } if (client_secret == NULL && u_map_get(request->map_post_body, "client_secret") != NULL) { client_secret = u_map_get(request->map_post_body, "client_secret"); } if (o_strlen(u_map_get(request->map_post_body, "scope"))) { j_client = check_client_valid(config, client_id, client_id, client_secret, NULL, GLEWLWYD_AUTHORIZATION_TYPE_DEVICE_AUTHORIZATION, 0, ip_source); if (check_result_value(j_client, G_OK)) { client_id = json_string_value(json_object_get(json_object_get(j_client, "client"), "client_id")); j_result = generate_device_authorization(config, client_id, u_map_get(request->map_post_body, "scope"), ip_source); if (check_result_value(j_result, G_OK)) { verification_uri = msprintf("%s/device", plugin_url); verification_uri_complete = msprintf("%s/device?code=%s", plugin_url, json_string_value(json_object_get(json_object_get(j_result, "authorization"), "user_code"))); j_body = json_pack("{sOsOsssssOsO}", "device_code", json_object_get(json_object_get(j_result, "authorization"), "device_code"), "user_code", json_object_get(json_object_get(j_result, "authorization"), "user_code"), "verification_uri", verification_uri, "verification_uri_complete", verification_uri_complete, "expires_in", json_object_get(config->j_params, "device-authorization-expiration"), "interval", json_object_get(config->j_params, "device-authorization-interval")); ulfius_set_json_body_response(response, 200, j_body); json_decref(j_body); o_free(verification_uri); o_free(verification_uri_complete); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_DEVICE_CODE, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oauth2_device_authorization oauth2 - Error generate_device_authorization"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_result); } else { j_body = json_pack("{ss}", "error", "unauthorized_client"); ulfius_set_json_body_response(response, 403, j_body); json_decref(j_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } json_decref(j_client); } else { j_body = json_pack("{ss}", "error", "invalid_scope"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } o_free(plugin_url); return U_CALLBACK_CONTINUE; } /** * Verifies the device code by the user */ static int callback_oauth2_device_verification(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oauth2_config * config = (struct _oauth2_config *)user_data; char * redirect_url = NULL; struct _u_map param; json_t * j_result, * j_session; if (!o_strlen(u_map_get(request->map_url, "code"))) { if (u_map_init(¶m) == U_OK) { u_map_put(¶m, "prompt", "device"); response->status = 302; redirect_url = get_login_url(config, request, "device", NULL, NULL, ¶m); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); u_map_clean(¶m); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oauth2_device_verification - Error u_map_init"); response->status = 500; } } else if (o_strlen(u_map_get(request->map_url, "code")) != (GLEWLWYD_DEVICE_AUTH_USER_CODE_LENGTH+1)) { if (u_map_init(¶m) == U_OK) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Code invalid at IP Address %s", get_ip_source(request)); u_map_put(¶m, "prompt", "deviceCodeError"); response->status = 302; redirect_url = get_login_url(config, request, "device", NULL, NULL, ¶m); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); u_map_clean(¶m); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_INVALID_DEVICE_CODE, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oauth2_device_verification - Error u_map_init"); response->status = 500; } } else { if (u_map_init(¶m) == U_OK) { j_result = validate_device_auth_user_code(config, u_map_get(request->map_url, "code")); if (check_result_value(j_result, G_OK)) { if (u_map_has_key(request->map_url, "g_continue")) { j_session = validate_session_client_scope(config, request, json_string_value(json_object_get(json_object_get(j_result, "device_auth"), "client_id")), json_string_value(json_object_get(json_object_get(j_result, "device_auth"), "scope"))); if (check_result_value(j_session, G_OK)) { if (validate_device_authorization_scope(config, json_integer_value(json_object_get(json_object_get(j_result, "device_auth"), "gpgda_id")), json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username")), json_string_value(json_object_get(json_object_get(j_session, "session"), "scope_filtered"))) == G_OK) { response->status = 302; u_map_put(¶m, "prompt", "deviceComplete"); redirect_url = get_login_url(config, request, "device", NULL, NULL, ¶m); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_REFRESH_TOKEN, 1, "plugin", config->name, "response_type", "device_code", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_REFRESH_TOKEN, 1, "plugin", config->name, NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_USER_ACCESS_TOKEN, 1, "plugin", config->name, "response_type", "device_code", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_USER_ACCESS_TOKEN, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oauth2_device_verification - Error validate_device_authorization_scope"); response->status = 302; u_map_put(¶m, "prompt", "deviceServerError"); redirect_url = get_login_url(config, request, "device", NULL, NULL, ¶m); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } } else if (check_result_value(j_session, G_ERROR_NOT_FOUND) || check_result_value(j_session, G_ERROR_UNAUTHORIZED)) { // Redirect to login page response->status = 302; redirect_url = get_login_url(config, request, "device", json_string_value(json_object_get(json_object_get(j_result, "device_auth"), "client_id")), json_string_value(json_object_get(json_object_get(j_result, "device_auth"), "scope")), NULL); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oauth2_device_verification - Error validate_session_client_scope"); response->status = 302; u_map_put(¶m, "prompt", "deviceServerError"); redirect_url = get_login_url(config, request, "device", NULL, NULL, ¶m); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } json_decref(j_session); } else { // Redirect to login page response->status = 302; redirect_url = get_login_url(config, request, "device", json_string_value(json_object_get(json_object_get(j_result, "device_auth"), "client_id")), json_string_value(json_object_get(json_object_get(j_result, "device_auth"), "scope")), NULL); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Code invalid at IP Address %s", get_ip_source(request)); response->status = 302; u_map_put(¶m, "prompt", "deviceCodeError"); redirect_url = get_login_url(config, request, "device", NULL, NULL, ¶m); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OAUTH2_INVALID_DEVICE_CODE, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oauth2_device_verification - Error validate_device_auth_user_code"); response->status = 302; u_map_put(¶m, "prompt", "deviceServerError"); redirect_url = get_login_url(config, request, "device", NULL, NULL, ¶m); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } json_decref(j_result); u_map_clean(¶m); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oauth2_device_verification - Error u_map_init"); response->status = 500; } } return U_CALLBACK_CONTINUE; } static int jwt_autocheck(struct _oauth2_config * config) { time_t now; char * token; jwt_t * jwt = NULL; int ret; time(&now); token = generate_access_token(config, GLEWLWYD_CHECK_JWT_USERNAME, NULL, NULL, GLEWLWYD_CHECK_JWT_SCOPE, now, NULL); if (token != NULL) { jwt = r_jwt_copy(config->glewlwyd_resource_config->jwt); if (r_jwt_advanced_parse(jwt, token, R_PARSE_NONE, 0) == RHN_OK && r_jwt_verify_signature(jwt, NULL, 0) == RHN_OK) { ret = RHN_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "jwt_autocheck - oauth2 - Error verifying signature"); ret = G_ERROR_PARAM; } r_jwt_free(jwt); } else { y_log_message(Y_LOG_LEVEL_ERROR, "jwt_autocheck - oauth2 - Error generate_access_token"); ret = G_ERROR; } o_free(token); return ret; } static int disable_user_data(struct _oauth2_config * config, const char * username) { json_t * j_query; int res, ret = G_OK; do { j_query = json_pack("{sss{si}s{sssssi}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_CODE, "set", "gpgc_enabled", 0, "where", "gpgc_plugin_name", config->name, "gpgc_username", username, "gpgc_enabled", 1); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "disable_user_data - Error disable codes"); ret = G_ERROR; break; } j_query = json_pack("{sss{si}s{sssssi}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_REFRESH_TOKEN, "set", "gpgr_enabled", 0, "where", "gpgr_plugin_name", config->name, "gpgr_username", username, "gpgr_enabled", 1); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "disable_user_data - Error disable refresh tokens"); ret = G_ERROR; break; } j_query = json_pack("{sss{si}s{sssssi}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_ACCESS_TOKEN, "set", "gpga_enabled", 0, "where", "gpga_plugin_name", config->name, "gpga_username", username, "gpga_enabled", 1); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "disable_user_data - Error disable access tokens"); ret = G_ERROR; break; } j_query = json_pack("{sss{si}s{sssss{ssss}}}", "table", GLEWLWYD_PLUGIN_OAUTH2_TABLE_DEVICE_AUTHORIZATION, "set", "gpgda_status", 3, "where", "gpgda_plugin_name", config->name, "gpgda_username", username, "gpgda_status", "operator", "raw", "value", "in (0, 1)"); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "disable_user_data - Error disable device auth tokens"); ret = G_ERROR; break; } } while (0); return ret; } json_t * plugin_module_load(struct config_plugin * config) { UNUSED(config); return json_pack("{si ss ss ss}", "result", G_OK, "name", "oauth2-glewlwyd", "display_name", "OAuth2 plugin", "description", "Plugin for legacy OAuth2 workflow"); } int plugin_module_unload(struct config_plugin * config) { UNUSED(config); return G_OK; } json_t * plugin_module_init(struct config_plugin * config, const char * name, json_t * j_parameters, void ** cls) { const unsigned char * key; jwa_alg alg = R_JWA_ALG_UNKNOWN; pthread_mutexattr_t mutexattr; json_t * j_return = NULL, * j_result = NULL, * j_element = NULL; size_t index = 0; struct _oauth2_config * p_config = NULL; jwk_t * key_priv = NULL, * key_pub = NULL; y_log_message(Y_LOG_LEVEL_INFO, "Init plugin Glewlwyd Oauth2 '%s'", name); *cls = o_malloc(sizeof(struct _oauth2_config)); if (*cls != NULL) { p_config = (struct _oauth2_config *)*cls; p_config->glewlwyd_resource_config = NULL; do { pthread_mutexattr_init ( &mutexattr ); pthread_mutexattr_settype( &mutexattr, PTHREAD_MUTEX_RECURSIVE ); if (pthread_mutex_init(&p_config->insert_lock, &mutexattr) != 0) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init - oauth2 - Error initializing insert_lock"); j_return = json_pack("{si}", "result", G_ERROR); break; } pthread_mutexattr_destroy(&mutexattr); p_config->name = name; p_config->jwt_key = NULL; p_config->j_params = json_incref(j_parameters); json_object_set_new(p_config->j_params, "name", json_string(name)); p_config->glewlwyd_config = config; p_config->introspect_revoke_resource_config = NULL; if ((p_config->glewlwyd_resource_config = o_malloc(sizeof(struct _glewlwyd_resource_config))) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init - oauth2 - Error initializing glewlwyd_resource_config"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); break; } p_config->glewlwyd_resource_config->method = G_METHOD_HEADER; p_config->glewlwyd_resource_config->oauth_scope = NULL; p_config->glewlwyd_resource_config->realm = NULL; p_config->glewlwyd_resource_config->accept_access_token = 1; p_config->glewlwyd_resource_config->accept_client_token = 0; j_result = check_parameters(p_config->j_params); if (check_result_value(j_result, G_ERROR_PARAM)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", json_object_get(j_result, "error")); break; } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init - oauth2 - Error check_parameters"); j_return = json_pack("{si}", "result", G_ERROR); break; } p_config->access_token_duration = json_integer_value(json_object_get(p_config->j_params, "access-token-duration")); if (!p_config->access_token_duration) { p_config->access_token_duration = GLEWLWYD_ACCESS_TOKEN_EXP_DEFAULT; } p_config->refresh_token_duration = json_integer_value(json_object_get(p_config->j_params, "refresh-token-duration")); if (!p_config->refresh_token_duration) { p_config->refresh_token_duration = GLEWLWYD_REFRESH_TOKEN_EXP_DEFAULT; } p_config->code_duration = json_integer_value(json_object_get(p_config->j_params, "code-duration")); if (!p_config->code_duration) { p_config->code_duration = GLEWLWYD_CODE_EXP_DEFAULT; } if (json_object_get(p_config->j_params, "refresh-token-rolling") != NULL) { p_config->refresh_token_rolling = json_object_get(p_config->j_params, "refresh-token-rolling")==json_true()?1:0; } else { p_config->refresh_token_rolling = 0; } p_config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE] = json_object_get(p_config->j_params, "auth-type-code-enabled")==json_true()?1:0; p_config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_IMPLICIT] = json_object_get(p_config->j_params, "auth-type-implicit-enabled")==json_true()?1:0; p_config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_RESOURCE_OWNER_PASSWORD_CREDENTIALS] = json_object_get(p_config->j_params, "auth-type-password-enabled")==json_true()?1:0; p_config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_CLIENT_CREDENTIALS] = json_object_get(p_config->j_params, "auth-type-client-enabled")==json_true()?1:0; p_config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_REFRESH_TOKEN] = json_object_get(p_config->j_params, "auth-type-refresh-enabled")==json_true()?1:0; if (r_jwt_init(&p_config->jwt_key) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init - oauth2 - Error allocating resources for jwt_key"); j_return = json_pack("{si}", "result", G_ERROR); break; } if (r_jwt_init(&p_config->glewlwyd_resource_config->jwt) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init - oauth2 - Error allocating resources for jwt"); j_return = json_pack("{si}", "result", G_ERROR); break; } key = (const unsigned char *)json_string_value(json_object_get(p_config->j_params, "key")); if (0 == o_strcmp("rsa", json_string_value(json_object_get(p_config->j_params, "jwt-type")))) { if (0 == o_strcmp("256", json_string_value(json_object_get(p_config->j_params, "jwt-key-size")))) { alg = R_JWA_ALG_RS256; } else if (0 == o_strcmp("256", json_string_value(json_object_get(p_config->j_params, "jwt-key-size")))) { alg = R_JWA_ALG_RS384; } else { // 512 alg = R_JWA_ALG_RS512; } } else if (0 == o_strcmp("ecdsa", json_string_value(json_object_get(p_config->j_params, "jwt-type")))) { if (0 == o_strcmp("256", json_string_value(json_object_get(p_config->j_params, "jwt-key-size")))) { alg = R_JWA_ALG_ES256; } else if (0 == o_strcmp("256", json_string_value(json_object_get(p_config->j_params, "jwt-key-size")))) { alg = R_JWA_ALG_ES384; } else { // 512 alg = R_JWA_ALG_ES512; } } else if (0 == o_strcmp("sha", json_string_value(json_object_get(p_config->j_params, "jwt-type")))) { if (0 == o_strcmp("256", json_string_value(json_object_get(p_config->j_params, "jwt-key-size")))) { alg = R_JWA_ALG_HS256; } else if (0 == o_strcmp("256", json_string_value(json_object_get(p_config->j_params, "jwt-key-size")))) { alg = R_JWA_ALG_HS384; } else { // 512 alg = R_JWA_ALG_HS512; } } else if (0 == o_strcmp("rsa-pss", json_string_value(json_object_get(p_config->j_params, "jwt-type")))) { // SHA if (0 == o_strcmp("256", json_string_value(json_object_get(p_config->j_params, "jwt-key-size")))) { alg = R_JWA_ALG_PS256; } else if (0 == o_strcmp("256", json_string_value(json_object_get(p_config->j_params, "jwt-key-size")))) { alg = R_JWA_ALG_PS384; } else { // 512 alg = R_JWA_ALG_PS512; } } else { alg = R_JWA_ALG_EDDSA; } if (r_jwt_set_sign_alg(p_config->jwt_key, alg) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init - oauth2 - Error r_jwt_set_sign_alg"); j_return = json_pack("{si}", "result", G_ERROR); break; } if (r_jwt_set_sign_alg(p_config->glewlwyd_resource_config->jwt, alg) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init - oauth2 - Error r_jwt_set_sign_alg (2)"); j_return = json_pack("{si}", "result", G_ERROR); break; } if (0 == o_strcmp("sha", json_string_value(json_object_get(p_config->j_params, "jwt-type")))) { if (r_jwt_add_sign_key_symmetric(p_config->jwt_key, key, o_strlen((const char *)key)) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init - oauth2 - Error r_jwt_add_sign_key_symmetric"); j_return = json_pack("{si}", "result", G_ERROR); break; } if (r_jwt_add_sign_key_symmetric(p_config->glewlwyd_resource_config->jwt, key, o_strlen((const char *)key)) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init - oauth2 - Error r_jwt_add_sign_key_symmetric (2)"); j_return = json_pack("{si}", "result", G_ERROR); break; } } else { if (r_jwk_init(&key_priv) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init - oauth2 - Error r_jwk_init key_priv"); j_return = json_pack("{si}", "result", G_ERROR); break; } if (r_jwk_init(&key_pub) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init - oauth2 - Error r_jwk_init key_pub"); j_return = json_pack("{si}", "result", G_ERROR); break; } if (r_jwk_import_from_pem_der(key_priv, R_X509_TYPE_PRIVKEY, R_FORMAT_PEM, key, o_strlen((const char *)key)) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init - oauth2 - Error r_jwk_import_from_pem_der key_priv"); j_return = json_pack("{si}", "result", G_ERROR); break; } if (r_jwk_import_from_pem_der(key_pub, R_X509_TYPE_PUBKEY, R_FORMAT_PEM, (const unsigned char *)json_string_value(json_object_get(p_config->j_params, "cert")), json_string_length(json_object_get(p_config->j_params, "cert"))) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init - oauth2 - Error r_jwk_import_from_pem_der key_pub"); j_return = json_pack("{si}", "result", G_ERROR); break; } r_jwk_delete_property_str(key_priv, "kid"); r_jwk_delete_property_str(key_pub, "kid"); if (r_jwt_add_sign_keys(p_config->jwt_key, key_priv, NULL) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init - oauth2 - Error r_jwt_add_sign_keys"); j_return = json_pack("{si}", "result", G_ERROR); break; } if (r_jwt_add_sign_keys(p_config->glewlwyd_resource_config->jwt, NULL, key_pub) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init - oauth2 - Error r_jwt_add_sign_keys_pem_der (2)"); j_return = json_pack("{si}", "result", G_ERROR); break; } } if (jwt_autocheck(p_config) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init - oauth2 - Error jwt_autocheck"); j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "Error jwt_autocheck"); break; } p_config->glewlwyd_resource_config->alg = alg; // Add endpoints y_log_message(Y_LOG_LEVEL_INFO, "Add endpoints with plugin prefix %s", name); if (config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, "auth/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oauth2_authorization, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "token/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oauth2_token, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "*", name, "profile/*", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_check_glewlwyd_session_or_token, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, "profile/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oauth2_get_profile, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, "profile/token/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oauth2_refresh_token_list_get, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "DELETE", name, "profile/token/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oauth2_disable_refresh_token, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "DELETE", name, "profile/token/:token_hash", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oauth2_disable_refresh_token, (void*)*cls) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init - oauth2 - Error adding endpoints"); j_return = json_pack("{si}", "result", G_ERROR); break; } if (json_object_get(p_config->j_params, "introspection-revocation-allowed") == json_true()) { if ((p_config->introspect_revoke_resource_config = o_malloc(sizeof(struct _glewlwyd_resource_config))) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init - oauth2 - Error allocatig resources for introspect_revoke_resource_config"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); break; } p_config->introspect_revoke_resource_config->method = G_METHOD_HEADER; p_config->introspect_revoke_resource_config->oauth_scope = NULL; json_array_foreach(json_object_get(p_config->j_params, "introspection-revocation-auth-scope"), index, j_element) { if (p_config->introspect_revoke_resource_config->oauth_scope == NULL) { p_config->introspect_revoke_resource_config->oauth_scope = o_strdup(json_string_value(j_element)); } else { p_config->introspect_revoke_resource_config->oauth_scope = mstrcatf(p_config->introspect_revoke_resource_config->oauth_scope, " %s", json_string_value(j_element)); } } p_config->introspect_revoke_resource_config->realm = NULL; p_config->introspect_revoke_resource_config->accept_access_token = 1; p_config->introspect_revoke_resource_config->accept_client_token = 1; p_config->introspect_revoke_resource_config->jwt = r_jwt_copy(p_config->glewlwyd_resource_config->jwt); p_config->introspect_revoke_resource_config->alg = alg; if ( config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "introspect/", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_check_intropect_revoke, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "introspect/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_introspection, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "revoke/", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_check_intropect_revoke, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "revoke/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_revocation, (void*)*cls) != G_OK ) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init - oauth2 - oauth2 - Error adding introspect/revoke endpoints"); j_return = json_pack("{si}", "result", G_ERROR); break; } } if (json_object_get(p_config->j_params, "auth-type-device-enabled") == json_true()) { if ( config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "device_authorization/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oauth2_device_authorization, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, "device/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oauth2_device_verification, (void*)*cls) != G_OK ) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init - oauth2 - oauth2 - Error adding device-authorization endpoints"); j_return = json_pack("{si}", "result", G_ERROR); break; } if (json_object_get(p_config->j_params, "device-authorization-expiration") == NULL) { json_object_set_new(p_config->j_params, "device-authorization-expiration", json_integer(GLEWLWYD_DEVICE_AUTH_DEFAUT_EXPIRATION)); } if (json_object_get(p_config->j_params, "device-authorization-interval") == NULL) { json_object_set_new(p_config->j_params, "device-authorization-interval", json_integer(GLEWLWYD_DEVICE_AUTH_DEFAUT_INTERVAL)); } } config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_OAUTH2_CODE, "Total number of code provided"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_OAUTH2_DEVICE_CODE, "Total number of device code provided"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_OAUTH2_REFRESH_TOKEN, "Total number of refresh tokens provided"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_OAUTH2_USER_ACCESS_TOKEN, "Total number of access tokens provided"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_OAUTH2_CLIENT_ACCESS_TOKEN, "Total number of client tokens provided"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_OAUTH2_UNAUTHORIZED_CLIENT, "Total number of unauthorized client attempt"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_OAUTH2_INVALID_CODE, "Total number of invalid code"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_OAUTH2_INVALID_DEVICE_CODE, "Total number of invalid device code"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_OAUTH2_INVALID_REFRESH_TOKEN, "Total number of invalid refresh token"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_OAUTH2_INVALID_ACCESS_TOKEN, "Total number of invalid access token"); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OAUTH2_CODE, 0, "plugin", name, NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OAUTH2_REFRESH_TOKEN, 0, "plugin", name, NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OAUTH2_USER_ACCESS_TOKEN, 0, "plugin", name, NULL); if (json_object_get(p_config->j_params, "auth-type-code-enabled") == json_true()) { config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OAUTH2_REFRESH_TOKEN, 0, "plugin", name, "response_type", "code", NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OAUTH2_USER_ACCESS_TOKEN, 0, "plugin", name, "response_type", "code", NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OAUTH2_INVALID_CODE, 0, "plugin", name, NULL); } if (json_object_get(p_config->j_params, "auth-type-password-enabled") == json_true()) { config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OAUTH2_REFRESH_TOKEN, 0, "plugin", name, "response_type", "password", NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OAUTH2_USER_ACCESS_TOKEN, 0, "plugin", name, "response_type", "password", NULL); } if (json_object_get(p_config->j_params, "auth-type-client-enabled") == json_true()) { config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OAUTH2_CLIENT_ACCESS_TOKEN, 0, "plugin", name, NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OAUTH2_UNAUTHORIZED_CLIENT, 0, "plugin", name, NULL); } if (json_object_get(p_config->j_params, "auth-type-implicit-enabled") == json_true()) { config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OAUTH2_USER_ACCESS_TOKEN, 0, "plugin", name, "response_type", "token", NULL); } if (json_object_get(p_config->j_params, "auth-type-device-enabled") == json_true()) { config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OAUTH2_DEVICE_CODE, 0, "plugin", name, NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OAUTH2_INVALID_DEVICE_CODE, 0, "plugin", name, NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OAUTH2_REFRESH_TOKEN, 0, "plugin", name, "response_type", "device_code", NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OAUTH2_USER_ACCESS_TOKEN, 0, "plugin", name, "response_type", "device_code", NULL); } if (json_object_get(p_config->j_params, "auth-type-refresh-enabled") == json_true()) { config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OAUTH2_USER_ACCESS_TOKEN, 0, "plugin", name, "response_type", "refresh_token", NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OAUTH2_INVALID_REFRESH_TOKEN, 0, "plugin", name, NULL); } if (json_object_get(p_config->j_params, "introspection-revocation-allowed") == json_true()) { config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OAUTH2_INVALID_ACCESS_TOKEN, 0, "plugin", name, NULL); } } while (0); json_decref(j_result); r_jwk_free(key_priv); r_jwk_free(key_pub); if (j_return == NULL) { j_return = json_pack("{si}", "result", G_OK); } else { if (p_config != NULL) { if (p_config->introspect_revoke_resource_config != NULL) { o_free(p_config->introspect_revoke_resource_config->oauth_scope); o_free(p_config->introspect_revoke_resource_config->realm); r_jwt_free(p_config->introspect_revoke_resource_config->jwt); o_free(p_config->introspect_revoke_resource_config); } if (p_config->glewlwyd_resource_config != NULL) { o_free(p_config->glewlwyd_resource_config->oauth_scope); o_free(p_config->glewlwyd_resource_config->realm); r_jwt_free(p_config->glewlwyd_resource_config->jwt); o_free(p_config->glewlwyd_resource_config); } r_jwt_free(p_config->jwt_key); json_decref(p_config->j_params); pthread_mutex_destroy(&p_config->insert_lock); o_free(p_config); } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init - oauth2 - Error allocating resources for cls"); o_free(*cls); *cls = NULL; j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } return j_return; } int plugin_module_close(struct config_plugin * config, const char * name, void * cls) { UNUSED(name); if (cls != NULL) { y_log_message(Y_LOG_LEVEL_INFO, "Close plugin Glewlwyd Oauth2 '%s'", name); config->glewlwyd_callback_remove_plugin_endpoint(config, "GET", name, "auth/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "token/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "GET", name, "profile/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "GET", name, "profile/token/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "DELETE", name, "profile/token/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "DELETE", name, "profile/token/:token_hash"); config->glewlwyd_callback_remove_plugin_endpoint(config, "*", name, "profile/*"); if (((struct _oauth2_config *)cls)->introspect_revoke_resource_config != NULL) { config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "introspect/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "revoke/"); o_free(((struct _oauth2_config *)cls)->introspect_revoke_resource_config->oauth_scope); r_jwt_free(((struct _oauth2_config *)cls)->introspect_revoke_resource_config->jwt); o_free(((struct _oauth2_config *)cls)->introspect_revoke_resource_config); } if (((struct _oauth2_config *)cls)->glewlwyd_resource_config != NULL) { o_free(((struct _oauth2_config *)cls)->glewlwyd_resource_config->oauth_scope); r_jwt_free(((struct _oauth2_config *)cls)->glewlwyd_resource_config->jwt); o_free(((struct _oauth2_config *)cls)->glewlwyd_resource_config); } if (json_object_get(((struct _oauth2_config *)cls)->j_params, "auth-type-device-enabled") == json_true()) { config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "device_authorization/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "GET", name, "device/"); } r_jwt_free(((struct _oauth2_config *)cls)->jwt_key); json_decref(((struct _oauth2_config *)cls)->j_params); pthread_mutex_destroy(&((struct _oauth2_config *)cls)->insert_lock); o_free(cls); } return G_OK; } int plugin_user_revoke(struct config_plugin * config, const char * username, void * cls) { UNUSED(config); // Disable all data for user 'username', then remove entry in subject identifier table if (disable_user_data((struct _oauth2_config *)cls, username) == G_OK) { return G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_user_revoke - oauth2 - Error disable_user_data"); return G_ERROR; } } glewlwyd-2.6.1/src/plugin/protocol_oauth2.mariadb.sql000066400000000000000000000107231415646314000227140ustar00rootroot00000000000000DROP TABLE IF EXISTS gpg_access_token_scope; DROP TABLE IF EXISTS gpg_access_token; DROP TABLE IF EXISTS gpg_refresh_token_scope; DROP TABLE IF EXISTS gpg_refresh_token; DROP TABLE IF EXISTS gpg_code_scope; DROP TABLE IF EXISTS gpg_code; DROP TABLE IF EXISTS gpg_device_authorization_scope; DROP TABLE IF EXISTS gpg_device_authorization; CREATE TABLE gpg_code ( gpgc_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpgc_plugin_name VARCHAR(256) NOT NULL, gpgc_username VARCHAR(256) NOT NULL, gpgc_client_id VARCHAR(256) NOT NULL, gpgc_redirect_uri VARCHAR(512) NOT NULL, gpgc_code_hash VARCHAR(512) NOT NULL, gpgc_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpgc_issued_for VARCHAR(256), -- IP address or hostname gpgc_user_agent VARCHAR(256), gpgc_code_challenge VARCHAR(128), gpgc_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gpgc_code_hash ON gpg_code(gpgc_code_hash); CREATE INDEX i_gpgc_code_challenge ON gpg_code(gpgc_code_challenge); CREATE TABLE gpg_code_scope ( gpgcs_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpgc_id INT(11), gpgcs_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpgc_id) REFERENCES gpg_code(gpgc_id) ON DELETE CASCADE ); CREATE TABLE gpg_refresh_token ( gpgr_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpgr_plugin_name VARCHAR(256) NOT NULL, gpgr_authorization_type INT(2) NOT NULL, -- 0: Authorization Code Grant, 1: Implicit Grant, 2: Resource Owner Password Credentials Grant, 3: Client Credentials Grant gpgc_id INT(11) DEFAULT NULL, gpgr_username VARCHAR(256) NOT NULL, gpgr_client_id VARCHAR(256), gpgr_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpgr_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpgr_last_seen TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpgr_duration INT(11), gpgr_rolling_expiration TINYINT(1) DEFAULT 0, gpgr_issued_for VARCHAR(256), -- IP address or hostname gpgr_user_agent VARCHAR(256), gpgr_token_hash VARCHAR(512) NOT NULL, gpgr_enabled TINYINT(1) DEFAULT 1, FOREIGN KEY(gpgc_id) REFERENCES gpg_code(gpgc_id) ON DELETE CASCADE ); CREATE INDEX i_gpgr_token_hash ON gpg_refresh_token(gpgr_token_hash); CREATE TABLE gpg_refresh_token_scope ( gpgrs_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpgr_id INT(11), gpgrs_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpgr_id) REFERENCES gpg_refresh_token(gpgr_id) ON DELETE CASCADE ); -- Access token table, to store meta information on access token sent CREATE TABLE gpg_access_token ( gpga_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpga_plugin_name VARCHAR(256) NOT NULL, gpga_authorization_type INT(2) NOT NULL, -- 0: Authorization Code Grant, 1: Implicit Grant, 2: Resource Owner Password Credentials Grant, 3: Client Credentials Grant gpgr_id INT(11) DEFAULT NULL, gpga_username VARCHAR(256), gpga_client_id VARCHAR(256), gpga_issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpga_issued_for VARCHAR(256), -- IP address or hostname gpga_user_agent VARCHAR(256), gpga_token_hash VARCHAR(512) NOT NULL, gpga_enabled TINYINT(1) DEFAULT 1, FOREIGN KEY(gpgr_id) REFERENCES gpg_refresh_token(gpgr_id) ON DELETE CASCADE ); CREATE INDEX i_gpga_token_hash ON gpg_access_token(gpga_token_hash); CREATE TABLE gpg_access_token_scope ( gpgas_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpga_id INT(11), gpgas_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpga_id) REFERENCES gpg_access_token(gpga_id) ON DELETE CASCADE ); -- store device authorization requests CREATE TABLE gpg_device_authorization ( gpgda_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpgda_plugin_name VARCHAR(256) NOT NULL, gpgda_client_id VARCHAR(256) NOT NULL, gpgda_username VARCHAR(256), gpgda_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpgda_expires_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpgda_issued_for VARCHAR(256), -- IP address or hostname of the deice client gpgda_device_code_hash VARCHAR(512) NOT NULL, gpgda_user_code_hash VARCHAR(512) NOT NULL, gpgda_status TINYINT(1) DEFAULT 0, -- 0: created, 1: user verified, 2 device completed, 3 disabled gpgda_last_check TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX i_gpgda_device_code_hash ON gpg_device_authorization(gpgda_device_code_hash); CREATE INDEX i_gpgda_user_code_hash ON gpg_device_authorization(gpgda_user_code_hash); CREATE TABLE gpg_device_authorization_scope ( gpgdas_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpgda_id INT(11), gpgdas_scope VARCHAR(128) NOT NULL, gpgdas_allowed TINYINT(1) DEFAULT 0, FOREIGN KEY(gpgda_id) REFERENCES gpg_device_authorization(gpgda_id) ON DELETE CASCADE ); glewlwyd-2.6.1/src/plugin/protocol_oauth2.postgre.sql000066400000000000000000000103731415646314000230010ustar00rootroot00000000000000DROP TABLE IF EXISTS gpg_access_token_scope; DROP TABLE IF EXISTS gpg_access_token; DROP TABLE IF EXISTS gpg_refresh_token_scope; DROP TABLE IF EXISTS gpg_refresh_token; DROP TABLE IF EXISTS gpg_code_scope; DROP TABLE IF EXISTS gpg_code; DROP TABLE IF EXISTS gpg_device_authorization_scope; DROP TABLE IF EXISTS gpg_device_authorization; CREATE TABLE gpg_code ( gpgc_id SERIAL PRIMARY KEY, gpgc_plugin_name VARCHAR(256) NOT NULL, gpgc_username VARCHAR(256) NOT NULL, gpgc_client_id VARCHAR(256) NOT NULL, gpgc_redirect_uri VARCHAR(512) NOT NULL, gpgc_code_hash VARCHAR(512) NOT NULL, gpgc_expires_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gpgc_issued_for VARCHAR(256), -- IP address or hostname gpgc_user_agent VARCHAR(256), gpgc_code_challenge VARCHAR(128), gpgc_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gpgc_code_hash ON gpg_code(gpgc_code_hash); CREATE INDEX i_gpgc_code_challenge ON gpg_code(gpgc_code_challenge); CREATE TABLE gpg_code_scope ( gpgcs_id SERIAL PRIMARY KEY, gpgc_id INTEGER, gpgcs_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpgc_id) REFERENCES gpg_code(gpgc_id) ON DELETE CASCADE ); CREATE TABLE gpg_refresh_token ( gpgr_id SERIAL PRIMARY KEY, gpgr_plugin_name VARCHAR(256) NOT NULL, gpgr_authorization_type SMALLINT NOT NULL, -- 0: Authorization Code Grant, 1: Implicit Grant, 2: Resource Owner Password Credentials Grant, 3: Client Credentials Grant gpgc_id INTEGER DEFAULT NULL, gpgr_username VARCHAR(256) NOT NULL, gpgr_client_id VARCHAR(256), gpgr_issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gpgr_expires_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gpgr_last_seen TIMESTAMPTZ NOT NULL DEFAULT NOW(), gpgr_duration INTEGER, gpgr_rolling_expiration SMALLINT DEFAULT 0, gpgr_issued_for VARCHAR(256), -- IP address or hostname gpgr_user_agent VARCHAR(256), gpgr_token_hash VARCHAR(512) NOT NULL, gpgr_enabled SMALLINT DEFAULT 1, FOREIGN KEY(gpgc_id) REFERENCES gpg_code(gpgc_id) ON DELETE CASCADE ); CREATE INDEX i_gpgr_token_hash ON gpg_refresh_token(gpgr_token_hash); CREATE TABLE gpg_refresh_token_scope ( gpgrs_id SERIAL PRIMARY KEY, gpgr_id INTEGER, gpgrs_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpgr_id) REFERENCES gpg_refresh_token(gpgr_id) ON DELETE CASCADE ); -- Access token table, to store meta information on access token sent CREATE TABLE gpg_access_token ( gpga_id SERIAL PRIMARY KEY, gpga_plugin_name VARCHAR(256) NOT NULL, gpga_authorization_type SMALLINT NOT NULL, -- 0: Authorization Code Grant, 1: Implicit Grant, 2: Resource Owner Password Credentials Grant, 3: Client Credentials Grant gpgr_id INTEGER DEFAULT NULL, gpga_username VARCHAR(256), gpga_client_id VARCHAR(256), gpga_issued_at TIMESTAMPTZ DEFAULT NOW(), gpga_issued_for VARCHAR(256), -- IP address or hostname gpga_user_agent VARCHAR(256), gpga_token_hash VARCHAR(512) NOT NULL, gpga_enabled SMALLINT DEFAULT 1, FOREIGN KEY(gpgr_id) REFERENCES gpg_refresh_token(gpgr_id) ON DELETE CASCADE ); CREATE INDEX i_gpga_token_hash ON gpg_access_token(gpga_token_hash); CREATE TABLE gpg_access_token_scope ( gpgas_id SERIAL PRIMARY KEY, gpga_id INTEGER, gpgas_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpga_id) REFERENCES gpg_access_token(gpga_id) ON DELETE CASCADE ); -- store device authorization requests CREATE TABLE gpg_device_authorization ( gpgda_id SERIAL PRIMARY KEY, gpgda_plugin_name VARCHAR(256) NOT NULL, gpgda_client_id VARCHAR(256) NOT NULL, gpgda_username VARCHAR(256), gpgda_created_at TIMESTAMPTZ DEFAULT NOW(), gpgda_expires_at TIMESTAMPTZ DEFAULT NOW(), gpgda_issued_for VARCHAR(256), -- IP address or hostname of the deice client gpgda_device_code_hash VARCHAR(512) NOT NULL, gpgda_user_code_hash VARCHAR(512) NOT NULL, gpgda_status SMALLINT DEFAULT 0, -- 0: created, 1: user verified, 2 device completed, 3 disabled gpgda_last_check TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX i_gpgda_device_code_hash ON gpg_device_authorization(gpgda_device_code_hash); CREATE INDEX i_gpgda_user_code_hash ON gpg_device_authorization(gpgda_user_code_hash); CREATE TABLE gpg_device_authorization_scope ( gpgdas_id SERIAL PRIMARY KEY, gpgda_id INTEGER, gpgdas_scope VARCHAR(128) NOT NULL, gpgdas_allowed SMALLINT DEFAULT 0, FOREIGN KEY(gpgda_id) REFERENCES gpg_device_authorization(gpgda_id) ON DELETE CASCADE ); glewlwyd-2.6.1/src/plugin/protocol_oauth2.sqlite3.sql000066400000000000000000000103131415646314000226740ustar00rootroot00000000000000DROP TABLE IF EXISTS gpg_access_token_scope; DROP TABLE IF EXISTS gpg_access_token; DROP TABLE IF EXISTS gpg_refresh_token_scope; DROP TABLE IF EXISTS gpg_refresh_token; DROP TABLE IF EXISTS gpg_code_scope; DROP TABLE IF EXISTS gpg_code; DROP TABLE IF EXISTS gpg_device_authorization_scope; DROP TABLE IF EXISTS gpg_device_authorization; CREATE TABLE gpg_code ( gpgc_id INTEGER PRIMARY KEY AUTOINCREMENT, gpgc_plugin_name TEXT NOT NULL, gpgc_username TEXT NOT NULL, gpgc_client_id TEXT NOT NULL, gpgc_redirect_uri TEXT NOT NULL, gpgc_code_hash TEXT NOT NULL, gpgc_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpgc_issued_for TEXT, -- IP address or hostname gpgc_user_agent TEXT, gpgc_code_challenge TEXT, gpgc_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gpgc_code_hash ON gpg_code(gpgc_code_hash); CREATE INDEX i_gpgc_code_challenge ON gpg_code(gpgc_code_challenge); CREATE TABLE gpg_code_scope ( gpgcs_id INTEGER PRIMARY KEY AUTOINCREMENT, gpgc_id INTEGER, gpgcs_scope TEXT NOT NULL, FOREIGN KEY(gpgc_id) REFERENCES gpg_code(gpgc_id) ON DELETE CASCADE ); CREATE TABLE gpg_refresh_token ( gpgr_id INTEGER PRIMARY KEY AUTOINCREMENT, gpgr_plugin_name TEXT NOT NULL, gpgr_authorization_type INTEGER NOT NULL, -- 0: Authorization Code Grant, 1: Implicit Grant, 2: Resource Owner Password Credentials Grant, 3: Client Credentials Grant gpgc_id INTEGER DEFAULT NULL, gpgr_username TEXT NOT NULL, gpgr_client_id TEXT, gpgr_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpgr_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpgr_last_seen TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpgr_duration INTEGER, gpgr_rolling_expiration INTEGER DEFAULT 0, gpgr_issued_for TEXT, -- IP address or hostname gpgr_user_agent TEXT, gpgr_token_hash TEXT NOT NULL, gpgr_enabled INTEGER DEFAULT 1, FOREIGN KEY(gpgc_id) REFERENCES gpg_code(gpgc_id) ON DELETE CASCADE ); CREATE INDEX i_gpgr_token_hash ON gpg_refresh_token(gpgr_token_hash); CREATE TABLE gpg_refresh_token_scope ( gpgrs_id INTEGER PRIMARY KEY AUTOINCREMENT, gpgr_id INTEGER, gpgrs_scope TEXT NOT NULL, FOREIGN KEY(gpgr_id) REFERENCES gpg_refresh_token(gpgr_id) ON DELETE CASCADE ); -- Access token table, to store meta information on access token sent CREATE TABLE gpg_access_token ( gpga_id INTEGER PRIMARY KEY AUTOINCREMENT, gpga_plugin_name TEXT NOT NULL, gpga_authorization_type INTEGER NOT NULL, -- 0: Authorization Code Grant, 1: Implicit Grant, 2: Resource Owner Password Credentials Grant, 3: Client Credentials Grant gpgr_id INTEGER DEFAULT NULL, gpga_username TEXT, gpga_client_id TEXT, gpga_issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpga_issued_for TEXT, -- IP address or hostname gpga_user_agent TEXT, gpga_token_hash TEXT NOT NULL, gpga_enabled INTEGER DEFAULT 1, FOREIGN KEY(gpgr_id) REFERENCES gpg_refresh_token(gpgr_id) ON DELETE CASCADE ); CREATE INDEX i_gpga_token_hash ON gpg_access_token(gpga_token_hash); CREATE TABLE gpg_access_token_scope ( gpgas_id INTEGER PRIMARY KEY AUTOINCREMENT, gpga_id INT(11), gpgas_scope TEXT NOT NULL, FOREIGN KEY(gpga_id) REFERENCES gpg_access_token(gpga_id) ON DELETE CASCADE ); -- store device authorization requests CREATE TABLE gpg_device_authorization ( gpgda_id INTEGER PRIMARY KEY AUTOINCREMENT, gpgda_plugin_name TEXT NOT NULL, gpgda_client_id TEXT NOT NULL, gpgda_username TEXT, gpgda_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpgda_expires_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpgda_issued_for TEXT, -- IP address or hostname of the deice client gpgda_device_code_hash TEXT NOT NULL, gpgda_user_code_hash TEXT NOT NULL, gpgda_status INTEGER DEFAULT 0, -- 0: created, 1: user verified, 2 device completed, 3 disabled gpgda_last_check TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX i_gpgda_device_code_hash ON gpg_device_authorization(gpgda_device_code_hash); CREATE INDEX i_gpgda_user_code_hash ON gpg_device_authorization(gpgda_user_code_hash); CREATE TABLE gpg_device_authorization_scope ( gpgdas_id INTEGER PRIMARY KEY AUTOINCREMENT, gpgda_id INTEGER, gpgdas_scope TEXT NOT NULL, gpgdas_allowed INTEGER DEFAULT 0, FOREIGN KEY(gpgda_id) REFERENCES gpg_device_authorization(gpgda_id) ON DELETE CASCADE ); glewlwyd-2.6.1/src/plugin/protocol_oidc.c000066400000000000000000034750661415646314000204770ustar00rootroot00000000000000/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * OpenID Connect plugin * * Copyright 2019-2021 Nicolas Mora * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "glewlwyd-common.h" #include "oidc_resource.h" #define OIDC_SALT_LENGTH 16 #define OIDC_JTI_LENGTH 32 #define OIDC_REFRESH_TOKEN_LENGTH 128 #define OIDC_REQUEST_URI_SUFFIX_LENGTH 32 #define OIDC_SID_LENGTH 32 #define GLEWLWYD_ACCESS_TOKEN_EXP_DEFAULT 3600 #define GLEWLWYD_REFRESH_TOKEN_EXP_DEFAULT 1209600 #define GLEWLWYD_CODE_EXP_DEFAULT 600 #define GLEWLWYD_CODE_CHALLENGE_MAX_LENGTH 128 #define GLEWLWYD_CODE_CHALLENGE_S256_PREFIX "{SHA256}" #define GLEWLWYD_REQUEST_URI_EXP_DEFAULT 90 #define GLEWLWYD_CHECK_JWT_USERNAME "myrddin" #define GLEWLWYD_CHECK_JWT_SCOPE "caledonia" #define GLEWLWYD_PLUGIN_OIDC_TABLE_CODE "gpo_code" #define GLEWLWYD_PLUGIN_OIDC_TABLE_CODE_SCOPE "gpo_code_scope" #define GLEWLWYD_PLUGIN_OIDC_TABLE_CODE_SHEME "gpo_code_scheme" #define GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN "gpo_refresh_token" #define GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN_SCOPE "gpo_refresh_token_scope" #define GLEWLWYD_PLUGIN_OIDC_TABLE_ACCESS_TOKEN "gpo_access_token" #define GLEWLWYD_PLUGIN_OIDC_TABLE_ACCESS_TOKEN_SCOPE "gpo_access_token_scope" #define GLEWLWYD_PLUGIN_OIDC_TABLE_ID_TOKEN "gpo_id_token" #define GLEWLWYD_PLUGIN_OIDC_TABLE_SUBJECT_IDENTIFIER "gpo_subject_identifier" #define GLEWLWYD_PLUGIN_OIDC_TABLE_CLIENT_REGISTRATION "gpo_client_registration" #define GLEWLWYD_PLUGIN_OIDC_TABLE_CLIENT_TOKEN_REQUEST "gpo_client_token_request" #define GLEWLWYD_PLUGIN_OIDC_TABLE_DEVICE_AUTHORIZATION "gpo_device_authorization" #define GLEWLWYD_PLUGIN_OIDC_TABLE_DEVICE_AUTHORIZATION_SCOPE "gpo_device_authorization_scope" #define GLEWLWYD_PLUGIN_OIDC_TABLE_DEVICE_SCHEME "gpo_device_scheme" #define GLEWLWYD_PLUGIN_OIDC_TABLE_DPOP "gpo_dpop" #define GLEWLWYD_PLUGIN_OIDC_TABLE_RAR "gpo_rar" #define GLEWLWYD_PLUGIN_OIDC_TABLE_PAR "gpo_par" #define GLEWLWYD_PLUGIN_OIDC_TABLE_PAR_SCOPE "gpo_par_scope" #define GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA "gpo_ciba" #define GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA_SCOPE "gpo_ciba_scope" #define GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA_SCHEME "gpo_ciba_scheme" // Authorization types available #define GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE 0 #define GLEWLWYD_AUTHORIZATION_TYPE_TOKEN 1 #define GLEWLWYD_AUTHORIZATION_TYPE_ID_TOKEN 2 #define GLEWLWYD_AUTHORIZATION_TYPE_NONE 3 #define GLEWLWYD_AUTHORIZATION_TYPE_RESOURCE_OWNER_PASSWORD_CREDENTIALS 4 #define GLEWLWYD_AUTHORIZATION_TYPE_CLIENT_CREDENTIALS 5 #define GLEWLWYD_AUTHORIZATION_TYPE_REFRESH_TOKEN 6 #define GLEWLWYD_AUTHORIZATION_TYPE_DELETE_TOKEN 7 #define GLEWLWYD_AUTHORIZATION_TYPE_DEVICE_AUTHORIZATION 8 #define GLEWLWYD_AUTHORIZATION_TYPE_CIBA 9 #define GLEWLWYD_AUTHORIZATION_TYPE_NULL_FLAG 0 #define GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE_FLAG 1 #define GLEWLWYD_AUTHORIZATION_TYPE_TOKEN_FLAG 2 #define GLEWLWYD_AUTHORIZATION_TYPE_ID_TOKEN_FLAG 4 #define GLEWLWYD_AUTHORIZATION_TYPE_NONE_FLAG 8 #define GLEWLWYD_AUTHORIZATION_TYPE_RESOURCE_OWNER_PASSWORD_CREDENTIALS_FLAG 16 #define GLEWLWYD_AUTHORIZATION_TYPE_CLIENT_CREDENTIALS_FLAG 32 #define GLEWLWYD_AUTHORIZATION_TYPE_REFRESH_TOKEN_FLAG 64 #define GLEWLWYD_AUTHORIZATION_TYPE_DELETE_TOKEN_FLAG 128 #define GLEWLWYD_AUTHORIZATION_TYPE_DEVICE_AUTHORIZATION_FLAG 256 #define GLEWLWYD_AUTHORIZATION_TYPE_CIBA_FLAG 512 #define GLEWLWYD_AUTH_REQUEST_OBJECT 1 #define GLEWLWYD_AUTH_TOKEN_ENDPOINT 2 #define GLEWLWYD_AUTH_CIBA 3 #define GLEWLWYD_RESPONSE_MODE_QUERY 1 #define GLEWLWYD_RESPONSE_MODE_FRAGMENT 2 #define GLEWLWYD_RESPONSE_MODE_FORM_POST 3 #define GLEWLWYD_RESPONSE_MODE_QUERY_JWT 4 #define GLEWLWYD_RESPONSE_MODE_FRAGMENT_JWT 5 #define GLEWLWYD_RESPONSE_MODE_FORM_POST_JWT 6 #define GLEWLWYD_CLIENT_AUTH_METHOD_NONE 0 #define GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_POST 1 #define GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_BASIC 2 #define GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_JWT 3 #define GLEWLWYD_CLIENT_AUTH_METHOD_PRIVATE_KEY_JWT 4 #define GLEWLWYD_CLIENT_AUTH_METHOD_TLS 5 #define GLEWLWYD_CLIENT_AUTH_METHOD_SELF_SIGNED_TLS 6 #define GLEWLWYD_OIDC_SUBJECT_TYPE_PUBLIC 1 #define GLEWLWYD_OIDC_SUBJECT_TYPE_PAIRWISE 3 #define GLEWLWYD_SUB_LENGTH 32 #define GLEWLWYD_CLIENT_ID_LENGTH 16 #define GLEWLWYD_CLIENT_SECRET_LENGTH 32 #define GLEWLWYD_CLIENT_MANAGEMENT_AT_LENGTH 32 #define GLEWLWYD_TOKEN_TYPE_CODE 0 #define GLEWLWYD_TOKEN_TYPE_ACCESS_TOKEN 1 #define GLEWLWYD_TOKEN_TYPE_USERINFO 2 #define GLEWLWYD_TOKEN_TYPE_ID_TOKEN 3 #define GLEWLWYD_TOKEN_TYPE_REFRESH_TOKEN 4 #define GLEWLWYD_TOKEN_TYPE_INTROSPECTION 5 #define GLEWLWYD_TOKEN_TYPE_CIBA 6 #define GLEWLWYD_TOKEN_TYPE_AUTH 7 #define GLEWLWYD_AUTH_TOKEN_DEFAULT_MAX_AGE 3600 #define GLEWLWYD_AUTH_TOKEN_ASSERTION_TYPE "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" #define GLEWLWYD_REDIRECT_URI_LOOPBACK_1 "http://localhost" #define GLEWLWYD_REDIRECT_URI_LOOPBACK_2 "http://127.0.0.1" #define GLEWLWYD_REDIRECT_URI_LOOPBACK_3 "http://[::1]" #define GLEWLWYD_DEVICE_AUTH_DEFAUT_EXPIRATION 600 #define GLEWLWYD_DEVICE_AUTH_DEFAUT_INTERVAL 5 #define GLEWLWYD_DEVICE_AUTH_DEVICE_CODE_LENGTH 32 #define GLEWLWYD_DEVICE_AUTH_USER_CODE_LENGTH 8 #define GLEWLWYD_DEVICE_AUTH_GRANT_TYPE "urn:ietf:params:oauth:grant-type:device_code" #define GLEWLWYD_REFRESH_TOKEN_ONE_USE_NEVER 0 #define GLEWLWYD_REFRESH_TOKEN_ONE_USE_CLIENT_DRIVEN 1 #define GLEWLWYD_REFRESH_TOKEN_ONE_USE_ALWAYS 2 #define GLEWLWYD_CIBA_DEFAULT_EXPIRATION 600 #define GLEWLWYD_CIBA_REQ_ID_LENGTH 32 #define GLEWLWYD_CIBA_GRANT_TYPE "urn:openid:params:grant-type:ciba" #define GLWD_METRICS_OIDC_CODE "glewlwyd_oidc_code" #define GLWD_METRICS_OIDC_DEVICE_CODE "glewlwyd_oidc_device_code" #define GLWD_METRICS_OIDC_ID_TOKEN "glewlwyd_oidc_id_token" #define GLWD_METRICS_OIDC_REFRESH_TOKEN "glewlwyd_oidc_refresh_token" #define GLWD_METRICS_OIDC_USER_ACCESS_TOKEN "glewlwyd_oidc_access_token" #define GLWD_METRICS_OIDC_CLIENT_ACCESS_TOKEN "glewlwyd_oidc_client_token" #define GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT "glewlwyd_oidc_unauthorized_client" #define GLWD_METRICS_OIDC_INVALID_CODE "glewlwyd_oidc_invalid_code" #define GLWD_METRICS_OIDC_INVALID_DEVICE_CODE "glewlwyd_oidc_invalid_device_code" #define GLWD_METRICS_OIDC_INVALID_REFRESH_TOKEN "glewlwyd_oidc_invalid_refresh_token" #define GLWD_METRICS_OIDC_INVALID_ACCESS_TOKEN "glewlwyd_oidc_invalid_acccess_token" /** * Structure used to store all the plugin parameters and data duringexecution */ struct _oidc_config { struct config_plugin * glewlwyd_config; const char * name; json_t * j_params; jwks_t * jwks_sign; jwks_t * jwks_public; int x5u_flags; char * discovery_str; char * jwks_str; char * check_session_iframe; json_int_t access_token_duration; json_int_t refresh_token_duration; json_int_t code_duration; json_int_t auth_token_max_age; json_int_t request_uri_duration; unsigned short int allow_non_oidc; unsigned short int refresh_token_rolling; unsigned short int refresh_token_one_use; unsigned short int auth_type_enabled[7]; unsigned short int subject_type; pthread_mutex_t insert_lock; struct _oidc_resource_config * oidc_resource_config; struct _oidc_resource_config * introspect_revoke_resource_config; struct _oidc_resource_config * client_register_resource_config; }; static size_t get_enc_key_size(jwa_enc enc) { size_t size = 0; switch (enc) { case R_JWA_ENC_A128CBC: case R_JWA_ENC_A128GCM: case R_JWA_ENC_A192GCM: case R_JWA_ENC_A256GCM: size = 32; break; case R_JWA_ENC_A192CBC: size = 48; break; case R_JWA_ENC_A256CBC: size = 64; break; default: size = 0; break; } return size; } static int get_key_size_from_alg(const char * str_alg) { if (0 == o_strcmp("HS256", str_alg)) { return 256; } else if (0 == o_strcmp("HS384", str_alg)) { return 384; } else if (0 == o_strcmp("HS512", str_alg)) { return 512; } else if (0 == o_strcmp("RS256", str_alg)) { return 256; } else if (0 == o_strcmp("RS384", str_alg)) { return 384; } else if (0 == o_strcmp("RS512", str_alg)) { return 512; } else if (0 == o_strcmp("ES256", str_alg)) { return 256; } else if (0 == o_strcmp("ES384", str_alg)) { return 384; } else if (0 == o_strcmp("ES512", str_alg)) { return 512; } else if (0 == o_strcmp("PS256", str_alg)) { return 256; } else if (0 == o_strcmp("PS384", str_alg)) { return 384; } else if (0 == o_strcmp("PS512", str_alg)) { return 512; } else if (0 == o_strcmp("EdDSA", str_alg)) { return 256; } else { return 0; } } static int str_has_valid_charset(const char * str, const char * charset) { size_t i; int is_valid = 1; for (i=0; i 128) { json_array_append_new(j_error, json_string("Property 'introspection-revocation-auth-scope' is optional and must be a JSON array of strings, maximum 128 characters")); ret = G_ERROR_PARAM; } } } if (json_object_get(j_params, "introspection-revocation-allow-target-client") != NULL && !json_is_boolean(json_object_get(j_params, "introspection-revocation-allow-target-client"))) { json_array_append_new(j_error, json_string("Property 'introspection-revocation-allow-target-client' is optional and must be a boolean")); ret = G_ERROR_PARAM; } } if (json_object_get(j_params, "register-client-allowed") != NULL && !json_is_boolean(json_object_get(j_params, "register-client-allowed"))) { json_array_append_new(j_error, json_string("Property 'register-client-allowed' is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "register-client-allowed") == json_true()) { if (json_object_get(j_params, "register-client-auth-scope") != NULL && !json_is_array(json_object_get(j_params, "register-client-auth-scope"))) { json_array_append_new(j_error, json_string("Property 'register-client-auth-scope' is optional and must be a JSON array of strings, maximum 128 characters")); ret = G_ERROR_PARAM; } else { json_array_foreach(json_object_get(j_params, "register-client-auth-scope"), index, j_element) { if (!json_string_length(j_element) || json_string_length(j_element) > 128) { json_array_append_new(j_error, json_string("Property 'register-client-auth-scope' is optional and must be a JSON array of strings, maximum 128 characters")); ret = G_ERROR_PARAM; } } } if (json_object_get(j_params, "register-client-credentials-scope") != NULL && !json_is_array(json_object_get(j_params, "register-client-credentials-scope"))) { json_array_append_new(j_error, json_string("Property 'register-client-credentials-scope' is optional and must be a JSON array of strings, maximum 128 characters")); ret = G_ERROR_PARAM; } else { json_array_foreach(json_object_get(j_params, "register-client-credentials-scope"), index, j_element) { if (!json_string_length(j_element) || json_string_length(j_element) > 128) { json_array_append_new(j_error, json_string("Property 'register-client-credentials-scope' is optional and must be a JSON array of strings, maximum 128 characters")); ret = G_ERROR_PARAM; } } } if (json_object_get(j_params, "register-client-token-one-use") != NULL && !json_is_boolean(json_object_get(j_params, "register-client-token-one-use"))) { json_array_append_new(j_error, json_string("Property 'register-client-token-one-use' is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "register-client-management-allowed") != NULL && !json_is_boolean(json_object_get(j_params, "register-client-management-allowed"))) { json_array_append_new(j_error, json_string("Property 'register-client-management-allowed' is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "register-resource-specify-allowed") != NULL && !json_is_boolean(json_object_get(j_params, "register-resource-specify-allowed"))) { json_array_append_new(j_error, json_string("Property 'register-resource-specify-allowed' is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "register-resource-specify-allowed") == json_false()) { if (json_object_get(j_params, "register-resource-default") != NULL && !json_is_array(json_object_get(j_params, "register-resource-default"))) { json_array_append_new(j_error, json_string("Property 'register-resource-default' is optional and must be a JSON array of strings")); ret = G_ERROR_PARAM; } else { json_array_foreach(json_object_get(j_params, "register-resource-default"), index, j_element) { if (!json_string_length(j_element)) { json_array_append_new(j_error, json_string("Property 'register-resource-default' is optional and must be a JSON array of strings")); ret = G_ERROR_PARAM; } } } } json_object_foreach(json_object_get(j_params, "register-default-properties"), key, j_property) { if (json_is_array(json_object_get(j_property, "value"))) { json_array_foreach(json_object_get(j_property, "value"), index, j_element) { if (!json_string_length(j_element)) { json_array_append_new(j_error, json_string("Property values in a 'register-default-properties' object is mandatory and must be a non empty string")); ret = G_ERROR_PARAM; } } } else if (!json_string_length(json_object_get(j_property, "value"))) { json_array_append_new(j_error, json_string("Property value in a 'register-default-properties' object is mandatory and must be a non empty string")); ret = G_ERROR_PARAM; } } } if (json_object_get(j_params, "auth-type-device-enabled") == json_true()) { if (json_object_get(j_params, "device-authorization-expiration") != NULL && json_integer_value(json_object_get(j_params, "device-authorization-expiration")) <= 0) { json_array_append_new(j_error, json_string("Property 'device-authorization-expiration' is optional and must be a non null positive integer")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "device-authorization-interval") != NULL && json_integer_value(json_object_get(j_params, "device-authorization-interval")) <= 0) { json_array_append_new(j_error, json_string("Property 'device-authorization-interval' is optional and must be a non null positive integer")); ret = G_ERROR_PARAM; } } if (json_string_length(json_object_get(j_params, "client-cert-source")) && 0 != o_strcmp("TLS", json_string_value(json_object_get(j_params, "client-cert-source"))) && 0 != o_strcmp("header", json_string_value(json_object_get(j_params, "client-cert-source"))) && 0 != o_strcmp("both", json_string_value(json_object_get(j_params, "client-cert-source")))) { json_array_append_new(j_error, json_string("client-cert-source is optional and must be one of the following values: 'TLS', 'header' or 'both'")); } if ((0 == o_strcmp("header", json_string_value(json_object_get(j_params, "client-cert-source"))) || 0 == o_strcmp("both", json_string_value(json_object_get(j_params, "client-cert-source"))))) { if (!json_string_length(json_object_get(j_params, "client-cert-header-name"))) { json_array_append_new(j_error, json_string("client-cert-header-name is mandatory when client-cert-source is 'header' or 'both' and must be a non empty string")); } } if (json_object_get(j_params, "client-cert-source") != NULL && json_object_get(j_params, "client-cert-use-endpoint-aliases") != NULL && !json_is_boolean(json_object_get(j_params, "client-cert-use-endpoint-aliases"))) { json_array_append_new(j_error, json_string("client-cert-use-endpoint-aliases is optional and must be a boolean")); } if (json_object_get(j_params, "client-cert-source") != NULL && json_object_get(j_params, "client-cert-self-signed-allowed") != NULL && !json_is_boolean(json_object_get(j_params, "client-cert-self-signed-allowed"))) { json_array_append_new(j_error, json_string("Property 'client-cert-self-signed-allowed' is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "oauth-dpop-allowed") != NULL && !json_is_boolean(json_object_get(j_params, "oauth-dpop-allowed"))) { json_array_append_new(j_error, json_string("Property 'oauth-dpop-allowed' is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "oauth-dpop-allowed") == json_true() && json_integer_value(json_object_get(j_params, "oauth-dpop-iat-duration")) <= 0) { json_array_append_new(j_error, json_string("Property 'oauth-dpop-iat-duration' is mandatory and must be a non null positive integer")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "resource-allowed") != NULL && !json_is_boolean(json_object_get(j_params, "resource-allowed"))) { json_array_append_new(j_error, json_string("Property 'resource-allowed' is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "resource-allowed") == json_true()) { json_object_foreach(json_object_get(j_params, "resource-scope"), key, j_scope) { if (!json_is_array(j_scope)) { json_array_append_new(j_error, json_string("resource-scope must contain JSON arrays")); ret = G_ERROR_PARAM; } else { json_array_foreach(j_scope, index, j_element) { if (!json_string_length(j_element)) { json_array_append_new(j_error, json_string("A resource url must be a non empty string")); ret = G_ERROR_PARAM; } } } } if (json_object_get(j_params, "resource-client-property") != NULL && !json_is_string(json_object_get(j_params, "resource-client-property"))) { json_array_append_new(j_error, json_string("resource-client-property is optional must be a string")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "resource-scope-and-client-property") != NULL && !json_is_boolean(json_object_get(j_params, "resource-scope-and-client-property"))) { json_array_append_new(j_error, json_string("Property 'resource-scope-and-client-property' is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "resource-change-allowed") != NULL && !json_is_boolean(json_object_get(j_params, "resource-change-allowed"))) { json_array_append_new(j_error, json_string("Property 'resource-change-allowed' is optional and must be a boolean")); ret = G_ERROR_PARAM; } } if (json_object_get(j_params, "oauth-rar-allowed") != NULL && !json_is_boolean(json_object_get(j_params, "oauth-rar-allowed"))) { json_array_append_new(j_error, json_string("Property 'oauth-rar-allowed' is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "oauth-rar-allowed") == json_true()) { if (!json_string_length(json_object_get(j_params, "rar-types-client-property"))) { json_array_append_new(j_error, json_string("Property 'rar-types-client-property' is mandatory and must be a non empty string")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "rar-allow-auth-unsigned") != NULL && !json_is_boolean(json_object_get(j_params, "rar-allow-auth-unsigned"))) { json_array_append_new(j_error, json_string("Property 'rar-allow-auth-unsigned' is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "rar-allow-auth-unencrypted") != NULL && !json_is_boolean(json_object_get(j_params, "rar-allow-auth-unencrypted"))) { json_array_append_new(j_error, json_string("Property 'rar-allow-auth-unencrypted' is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "rar-types") != NULL) { if (!json_is_object(json_object_get(j_params, "rar-types"))) { json_array_append_new(j_error, json_string("Property 'rar-types' is optional and must be a JSON object")); ret = G_ERROR_PARAM; } else { json_object_foreach(json_object_get(j_params, "rar-types"), key, j_rar_type) { if (o_strlen(key) > 256) { json_array_append_new(j_error, json_string("Key 'rar-types' must maximum 256 characters")); ret = G_ERROR_PARAM; } for (index=0; index 65535)) { json_array_append_new(j_error, json_string("oauth-ciba-email-port is optional and must be a integer between 0 and 65535")); ret = G_ERROR_PARAM; } else if (json_object_get(j_params, "oauth-ciba-email-port") == NULL) { json_object_set_new(j_params, "oauth-ciba-email-port", json_integer(0)); } if (json_object_get(j_params, "oauth-ciba-email-use-tls") != NULL && !json_is_boolean(json_object_get(j_params, "oauth-ciba-email-use-tls"))) { json_array_append_new(j_error, json_string("oauth-ciba-email-use-tls is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "oauth-ciba-email-check-certificate") != NULL && !json_is_boolean(json_object_get(j_params, "oauth-ciba-email-check-certificate"))) { json_array_append_new(j_error, json_string("oauth-ciba-email-check-certificate is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "oauth-ciba-email-user") != NULL && !json_is_string(json_object_get(j_params, "oauth-ciba-email-user"))) { json_array_append_new(j_error, json_string("oauth-ciba-email-user is optional and must be a string")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "oauth-ciba-email-password") != NULL && !json_is_string(json_object_get(j_params, "oauth-ciba-email-password"))) { json_array_append_new(j_error, json_string("oauth-ciba-email-password is optional and must be a string")); ret = G_ERROR_PARAM; } if (!json_string_length(json_object_get(j_params, "oauth-ciba-email-from"))) { json_array_append_new(j_error, json_string("oauth-ciba-email-from is mandatory and must be a non empty string")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "oauth-ciba-email-content-type") != NULL && !json_string_length(json_object_get(j_params, "oauth-ciba-email-content-type"))) { json_array_append_new(j_error, json_string("oauth-ciba-email-content-type is optional and must be a string")); ret = G_ERROR_PARAM; } if (!json_string_length(json_object_get(j_params, "oauth-ciba-email-user-lang-property"))) { json_array_append_new(j_error, json_string("oauth-ciba-email-user-lang-property is mandatory and must be a non empty string")); ret = G_ERROR_PARAM; } if (!json_is_object(json_object_get(j_params, "oauth-ciba-email-templates"))) { json_array_append_new(j_error, json_string("oauth-ciba-email-templates is mandatory and must be a JSON object")); ret = G_ERROR_PARAM; } else { json_object_foreach(json_object_get(j_params, "oauth-ciba-email-templates"), lang, j_template) { if (!json_is_object(j_template)) { json_array_append_new(j_error, json_string("template content must be a JSON object")); ret = G_ERROR_PARAM; } else { if (!json_is_boolean(json_object_get(j_template, "oauth-ciba-email-defaultLang"))) { json_array_append_new(j_error, json_string("oauth-ciba-email-defaultLang is madatory in a template and must be a boolean")); ret = G_ERROR_PARAM; } if (!json_string_length(json_object_get(j_template, "oauth-ciba-email-subject"))) { json_array_append_new(j_error, json_string("oauth-ciba-email-subject is mandatory for default lang and must be a non empty string")); ret = G_ERROR_PARAM; } if (json_object_get(j_template, "oauth-ciba-email-body-pattern") != NULL && !json_string_length(json_object_get(j_template, "oauth-ciba-email-body-pattern"))) { json_array_append_new(j_error, json_string("oauth-ciba-email-body-pattern is mandatory for default lang and must be a non empty string")); ret = G_ERROR_PARAM; } if (o_strstr(json_string_value(json_object_get(j_template, "oauth-ciba-email-body-pattern")), "{CONNECT_URL}") == NULL) { json_array_append_new(j_error, json_string("oauth-ciba-email-body-pattern must contain the string {CONNECT_URL}")); ret = G_ERROR_PARAM; } if (json_object_get(j_template, "oauth-ciba-email-defaultLang") == json_true()) { nb_default_lang++; } } } if (nb_default_lang != 1) { json_array_append_new(j_error, json_string("template list must have only one oauth-ciba-email-defaultLang set to true")); ret = G_ERROR_PARAM; } } } if (json_object_get(j_params, "oauth-fapi-check-all") != NULL && !json_is_boolean(json_object_get(j_params, "oauth-fapi-check-all"))) { json_array_append_new(j_error, json_string("oauth-fapi-check-all is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "oauth-fapi-allow-jarm") != NULL && !json_is_boolean(json_object_get(j_params, "oauth-fapi-allow-jarm"))) { json_array_append_new(j_error, json_string("oauth-fapi-allow-jarm is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "oauth-fapi-add-s_hash") != NULL && !json_is_boolean(json_object_get(j_params, "oauth-fapi-add-s_hash"))) { json_array_append_new(j_error, json_string("oauth-fapi-add-s_hash is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "oauth-fapi-verify-nbf") != NULL && !json_is_boolean(json_object_get(j_params, "oauth-fapi-verify-nbf"))) { json_array_append_new(j_error, json_string("oauth-fapi-verify-nbf is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "oauth-fapi-allow-restrict-alg") != NULL && !json_is_boolean(json_object_get(j_params, "oauth-fapi-allow-restrict-alg"))) { json_array_append_new(j_error, json_string("oauth-fapi-allow-restrict-alg is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "oauth-fapi-allow-restrict-alg") == json_true()) { if (json_object_get(j_params, "oauth-fapi-restrict-alg") == NULL || !json_array_size(json_object_get(j_params, "oauth-fapi-restrict-alg"))) { json_array_append_new(j_error, json_string("oauth-fapi-restrict-alg is mandatory and must be an array of strings")); ret = G_ERROR_PARAM; } else { j_allowed_algs = json_pack("[sssssssssssssss]", "RSA-OAEP", "RSA-OAEP-256", "A128KW", "A192KW", "A256KW", "ECDH-ES", "ECDH-ES+A128KW", "ECDH-ES+A192KW", "ECDH-ES+A256KW", "A128GCMKW", "A192GCMKW", "A256GCMKW", "PBES2-HS256+A128KW", "PBES2-HS384+A192KW", "PBES2-HS512+A256KW"); json_array_foreach(json_object_get(j_params, "oauth-fapi-restrict-alg"), index, j_element) { if (!json_string_length(j_element) || !json_array_has_string(j_allowed_algs, json_string_value(j_element))) { json_array_append_new(j_error, json_string("oauth-fapi-restrict-alg list invalid")); ret = G_ERROR_PARAM; } } } } if (json_object_get(j_params, "oauth-fapi-allow-multiple-kid") != NULL && !json_is_boolean(json_object_get(j_params, "oauth-fapi-allow-multiple-kid"))) { json_array_append_new(j_error, json_string("oauth-fapi-allow-multiple-kid is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "oauth-fapi-ciba-confidential-client") != NULL && !json_is_boolean(json_object_get(j_params, "oauth-fapi-ciba-confidential-client"))) { json_array_append_new(j_error, json_string("oauth-fapi-ciba-confidential-client is optional and must be a boolean")); ret = G_ERROR_PARAM; } if (json_object_get(j_params, "oauth-fapi-ciba-push-forbidden") != NULL && !json_is_boolean(json_object_get(j_params, "oauth-fapi-ciba-push-forbidden"))) { json_array_append_new(j_error, json_string("oauth-fapi-ciba-push-forbidden is optional and must be a boolean")); ret = G_ERROR_PARAM; } } if (json_array_size(j_error) && ret == G_ERROR_PARAM) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_error); } else { j_return = json_pack("{si}", "result", ret); } json_decref(j_error); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_parameters oidc - Error allocating resources for j_error"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } return j_return; } /** * Return the struct _u_map corresponding to the * request context (POST or GET) to retrieve parameters */ static struct _u_map * get_map(const struct _u_request * request) { if (0 == o_strcmp(request->http_verb, "POST")) { return request->map_post_body; } else { return request->map_url; } } static int verify_resource(struct _oidc_config * config, const char * resource, json_t * j_client, const char * scope_list) { char ** scope_array = NULL; int resource_scope = 0, resource_client = 0, ret; const char * key = NULL; size_t index = 0; json_t * j_scope = NULL, * j_element = NULL; if ((0 == o_strncmp("https://", resource, o_strlen("https://")) || 0 == o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_1, resource, o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_1)) || 0 == o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_2, resource, o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_2)) || 0 == o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_3, resource, o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_3))) && o_strchr(resource, '#') == NULL) { // URL with fragment not allowed if (split_string(scope_list, " ", &scope_array) > 0) { json_object_foreach(json_object_get(config->j_params, "resource-scope"), key, j_scope) { if (string_array_has_value((const char **)scope_array, key)) { json_array_foreach(j_scope, index, j_element) { if (0 == o_strcmp(resource, json_string_value(j_element))) { resource_scope = 1; break; } } } if (resource_scope) { break; } } if (json_string_length(json_object_get(config->j_params, "resource-client-property")) && (j_scope = json_object_get(j_client, json_string_value(json_object_get(config->j_params, "resource-client-property")))) != NULL) { json_array_foreach(j_scope, index, j_element) { if (0 == o_strcmp(resource, json_string_value(j_element))) { resource_client = 1; break; } } } if (json_object_get(config->j_params, "resource-scope-and-client-property") == json_true()) { if (resource_scope && resource_client) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_resource oidc - resource invalid in scopes and client property"); ret = G_ERROR_PARAM; } } else { if (resource_scope || resource_client) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_resource oidc - resource invalid in scopes or client property"); ret = G_ERROR_PARAM; } } free_string_array(scope_array); } else { y_log_message(Y_LOG_LEVEL_ERROR, "verify_resource oidc - Error split_string"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_resource oidc - resource must be a https:// or http://locahlost uri"); ret = G_ERROR_PARAM; } return ret; } /** * Parse the DPoP header and extract its jkt value if the DPoP is valid */ static json_t * oidc_verify_dpop_proof(struct _oidc_config * config, const struct _u_request * request, const char * htm, const char * url) { json_t * j_return = NULL, * j_header = NULL, * j_claims = NULL; const char * dpop_header; jwt_t * dpop_jwt = NULL; jwa_alg alg; jwk_t * jwk_header = NULL; char * jkt = NULL, * external_url = config->glewlwyd_config->glewlwyd_callback_get_plugin_external_url(config->glewlwyd_config, config->name), * htu = msprintf("%s%s", external_url, url); time_t now; if ((dpop_header = u_map_get_case(request->map_header, "DPoP")) != NULL) { if (r_jwt_init(&dpop_jwt) == RHN_OK) { if (r_jwt_advanced_parse(dpop_jwt, dpop_header, R_PARSE_HEADER_JWK, R_FLAG_IGNORE_REMOTE) == RHN_OK) { if (r_jwt_verify_signature(dpop_jwt, NULL, R_FLAG_IGNORE_REMOTE) == RHN_OK) { do { if (0 != o_strcmp("dpop+jwt", r_jwt_get_header_str_value(dpop_jwt, "typ"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc_verify_dpop_proof - Invalid typ"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); break; } if ((alg = r_jwt_get_sign_alg(dpop_jwt)) != R_JWA_ALG_RS256 && alg != R_JWA_ALG_RS384 && alg != R_JWA_ALG_RS512 && alg != R_JWA_ALG_ES256 && alg != R_JWA_ALG_ES384 && alg != R_JWA_ALG_ES512 && alg != R_JWA_ALG_PS256 && alg != R_JWA_ALG_PS384 && alg != R_JWA_ALG_PS512 && alg != R_JWA_ALG_EDDSA && alg != R_JWA_ALG_ES256K) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc_verify_dpop_proof - Invalid sign_alg"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); break; } if ((j_header = r_jwt_get_full_header_json_t(dpop_jwt)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "oidc_verify_dpop_proof - Error r_jwt_get_full_header_json_t"); j_return = json_pack("{si}", "result", G_ERROR); break; } if ((j_claims = r_jwt_get_full_claims_json_t(dpop_jwt)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "oidc_verify_dpop_proof - Error r_jwt_get_full_claims_json_t"); j_return = json_pack("{si}", "result", G_TOKEN_ERROR); break; } if (json_object_get(j_header, "x5c") != NULL || json_object_get(j_header, "x5u") != NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc_verify_dpop_proof - Invalid header, x5c or x5u present"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); break; } if (r_jwk_init(&jwk_header) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "oidc_verify_dpop_proof - Error r_jwk_init"); j_return = json_pack("{si}", "result", G_ERROR); break; } if (r_jwk_import_from_json_t(jwk_header, json_object_get(j_header, "jwk")) != RHN_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc_verify_dpop_proof - Invalid jwk property in header"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); break; } if (!o_strlen(r_jwt_get_claim_str_value(dpop_jwt, "jti"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc_verify_dpop_proof - Invalid jti"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); break; } if (0 != o_strcmp(htm, r_jwt_get_claim_str_value(dpop_jwt, "htm"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc_verify_dpop_proof - Invalid htm"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); break; } if (0 != o_strcmp(htu, r_jwt_get_claim_str_value(dpop_jwt, "htu"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc_verify_dpop_proof - Invalid htu"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); break; } time(&now); if ((time_t)r_jwt_get_claim_int_value(dpop_jwt, "iat") > now || ((time_t)r_jwt_get_claim_int_value(dpop_jwt, "iat"))+((time_t)json_integer_value(json_object_get(config->j_params, "oauth-dpop-iat-duration"))) < now) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc_verify_dpop_proof - Invalid iat"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); break; } if ((jkt = r_jwk_thumbprint(jwk_header, R_JWK_THUMB_SHA256, R_FLAG_IGNORE_REMOTE)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "oidc_verify_dpop_proof - Error r_jwk_thumbprint"); j_return = json_pack("{si}", "result", G_ERROR); break; } } while (0); if (j_return == NULL) { j_return = json_pack("{siss*sO*sO*}", "result", G_OK, "jkt", jkt, "header", j_header, "claims", j_claims); } json_decref(j_header); json_decref(j_claims); r_jwk_free(jwk_header); o_free(jkt); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc_verify_dpop_proof - Invalid signature"); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc_verify_dpop_proof - Invalid DPoP token"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc_verify_dpop_proof - Error r_jwt_init"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } r_jwt_free(dpop_jwt); } else { j_return = json_pack("{si}", "result", G_OK); } o_free(external_url); o_free(htu); return j_return; } /** * Verifies that this jti has not been used for another DPoP * If so, stores its metadata */ static int check_dpop_jti(struct _oidc_config * config, const char * jti, const char * htm, const char * htu, json_int_t iat, const char * client_id, const char * jkt, const char * ip_source) { char * jti_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, jti), * iat_clause; json_t * j_query, * j_result; int res, ret; j_query = json_pack("{sss[s]s{ssssss}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_DPOP, "columns", "gpod_id", "where", "gpod_plugin_name", config->name, "gpod_jti_hash", jti_hash, "gpod_client_id", client_id); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (!json_array_size(j_result)) { if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { iat_clause = msprintf("FROM_UNIXTIME(%"JSON_INTEGER_FORMAT")", iat); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { iat_clause = msprintf("TO_TIMESTAMP(%"JSON_INTEGER_FORMAT")", iat); } else { // HOEL_DB_TYPE_SQLITE iat_clause = msprintf("%"JSON_INTEGER_FORMAT, iat); } j_query = json_pack("{sss{sssssssssssss{ss}}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_DPOP, "values", "gpod_plugin_name", config->name, "gpod_client_id", client_id, "gpod_jti_hash", jti_hash, "gpod_jkt", jkt, "gpod_htm", htm, "gpod_htu", htu, "gpod_iat", "raw", iat_clause); o_free(iat_clause); res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_dpop_jti - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_WARNING, "jti already used for client %s at IP Address %s", client_id, ip_source); ret = G_ERROR_UNAUTHORIZED; config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_dpop_jti - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } o_free(jti_hash); return ret; } /** * Verifies that this jti has not been used for another DPoP * If so, stores its metadata */ static int check_ciba_jti(struct _oidc_config * config, const char * jti, const char * client_id, const char * ip_source) { char * jti_hash; json_t * j_query, * j_result; int res, ret; if (o_strlen(jti)) { jti_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, jti); j_query = json_pack("{sss[s]s{ssssss}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA, "columns", "gpob_id", "where", "gpob_plugin_name", config->name, "gpob_jti_hash", jti_hash, "gpob_client_id", client_id); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); o_free(jti_hash); if (res == H_OK) { if (!json_array_size(j_result)) { ret = RHN_OK; } else { y_log_message(Y_LOG_LEVEL_WARNING, "jti already used for client %s at IP Address %s", client_id, ip_source); ret = G_ERROR_UNAUTHORIZED; config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_ciba_jti - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { ret = G_ERROR_PARAM; } return ret; } /** * Get sub associated with username in public mode * Or create one and store it in the database if it doesn't exist */ static char * get_sub_public(struct _oidc_config * config, const char * username) { json_t * j_query, * j_result; int res; char * sub = NULL; j_query = json_pack("{sss[s]s{sssssoso}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_SUBJECT_IDENTIFIER, "columns", "gposi_sub", "where", "gposi_plugin_name", config->name, "gposi_username", username, "gposi_client_id", json_null(), "gposi_sector_identifier_uri", json_null()); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { sub = o_strdup(json_string_value(json_object_get(json_array_get(j_result, 0), "gposi_sub"))); } else { sub = o_malloc((GLEWLWYD_SUB_LENGTH+1)*sizeof(char)); if (sub != NULL) { *sub = '\0'; rand_string(sub, GLEWLWYD_SUB_LENGTH); j_query = json_pack("{sss{sssssssoso}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_SUBJECT_IDENTIFIER, "values", "gposi_plugin_name", config->name, "gposi_sub", sub, "gposi_username", username, "gposi_client_id", json_null(), "gposi_sector_identifier_uri", json_null()); if (h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL) != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "get_sub_public - Error executing h_insert"); o_free(sub); sub = NULL; } json_decref(j_query); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_sub_public - Error allocating resources for sub"); } } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_sub_public - Error executing h_select"); } return sub; } /** * Get sub associated with username and client in public mode * Or create one and store it in the database if it doesn't exist */ static char * get_sub_pairwise(struct _oidc_config * config, const char * username, json_t * j_client) { json_t * j_query, * j_result; int res; char * sub = NULL; j_query = json_pack("{sss[s]s{ssss}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_SUBJECT_IDENTIFIER, "columns", "gposi_sub", "where", "gposi_plugin_name", config->name, "gposi_username", username); if (json_string_length(json_object_get(j_client, "sector_identifier_uri"))) { json_object_set(json_object_get(j_query, "where"), "gposi_sector_identifier_uri", json_object_get(j_client, "sector_identifier_uri")); json_object_set(json_object_get(j_query, "where"), "gposi_client_id", json_null()); } else { json_object_set(json_object_get(j_query, "where"), "gposi_sector_identifier_uri", json_null()); json_object_set(json_object_get(j_query, "where"), "gposi_client_id", json_object_get(j_client, "client_id")); } res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { sub = o_strdup(json_string_value(json_object_get(json_array_get(j_result, 0), "gposi_sub"))); } else { sub = o_malloc((GLEWLWYD_SUB_LENGTH+1)*sizeof(char)); if (sub != NULL) { *sub = '\0'; rand_string(sub, GLEWLWYD_SUB_LENGTH); j_query = json_pack("{sss{ssssss}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_SUBJECT_IDENTIFIER, "values", "gposi_plugin_name", config->name, "gposi_sub", sub, "gposi_username", username); if (json_string_length(json_object_get(j_client, "sector_identifier_uri"))) { json_object_set(json_object_get(j_query, "values"), "gposi_sector_identifier_uri", json_object_get(j_client, "sector_identifier_uri")); json_object_set(json_object_get(j_query, "where"), "gposi_client_id", json_null()); } else { json_object_set(json_object_get(j_query, "values"), "gposi_sector_identifier_uri", json_null()); json_object_set(json_object_get(j_query, "where"), "gposi_client_id", json_object_get(j_client, "client_id")); } if (h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL) != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "get_sub_pairwise - Error executing h_insert"); o_free(sub); sub = NULL; } json_decref(j_query); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_sub_pairwise - Error allocating resources for sub"); } } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_sub_pairwise - Error executing h_select"); } return sub; } /** * Get sub associated with username and client * Or create one and store it in the database if it doesn't exist */ static char * get_sub(struct _oidc_config * config, const char * username, json_t * j_client) { if (config->subject_type == GLEWLWYD_OIDC_SUBJECT_TYPE_PUBLIC || j_client == NULL) { return get_sub_public(config, username); } else { return get_sub_pairwise(config, username, j_client); } } /** * Get username associated with a sub * Return NULL if not exist */ static char * get_username_from_sub(struct _oidc_config * config, const char * sub, json_t * j_client) { json_t * j_query, * j_result; int res; char * username = NULL; j_query = json_pack("{sss[s]s{ssss}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_SUBJECT_IDENTIFIER, "columns", "gposi_username", "where", "gposi_plugin_name", config->name, "gposi_sub", sub); if (j_client == NULL) { if (config->subject_type == GLEWLWYD_OIDC_SUBJECT_TYPE_PAIRWISE) { if (json_string_length(json_object_get(j_client, "sector_identifier_uri"))) { json_object_set(json_object_get(j_query, "where"), "gposi_sector_identifier_uri", json_object_get(j_client, "sector_identifier_uri")); json_object_set(json_object_get(j_query, "where"), "gposi_client_id", json_null()); } else { json_object_set(json_object_get(j_query, "where"), "gposi_sector_identifier_uri", json_null()); json_object_set(json_object_get(j_query, "where"), "gposi_client_id", json_object_get(j_client, "client_id")); } } } res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { username = o_strdup(json_string_value(json_object_get(json_array_get(j_result, 0), "gposi_username"))); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_username_from_sub - Error executing h_select"); } return username; } /** * Parse a single claim from a claim request */ static int is_claim_parameter_valid(json_t * j_claim) { json_t * j_element = NULL; size_t index = 0; if (json_is_null(j_claim)) { return G_OK; } else if (!json_is_object(j_claim)) { return G_ERROR_PARAM; } else { if (json_object_get(j_claim, "value") != NULL && !json_string_length(json_object_get(j_claim, "value"))) { return G_ERROR_PARAM; } else if (json_object_get(j_claim, "values")) { if (!json_is_array(json_object_get(j_claim, "values"))) { return G_ERROR_PARAM; } else { json_array_foreach(json_object_get(j_claim, "values"), index, j_element) { if (!json_string_length(j_element)) { return G_ERROR_PARAM; } } } } return G_OK; } } /** * parse claims parameter to validate that it has the correct format */ static int parse_claims_request(json_t * j_claims) { int ret = G_OK; json_t * j_claim_object, * j_element = NULL; const char * claim = NULL; if (json_is_object(j_claims)) { if ((j_claim_object = json_object_get(j_claims, "userinfo")) != NULL) { json_object_foreach(j_claim_object, claim, j_element) { if (is_claim_parameter_valid(j_element) != G_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "parse_claims_request - Error claim %s in userinfo is not a valid claim parameter", claim); ret = G_ERROR_PARAM; } } } if ((j_claim_object = json_object_get(j_claims, "id_token")) != NULL) { json_object_foreach(j_claim_object, claim, j_element) { if (is_claim_parameter_valid(j_element) != G_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "parse_claims_request - Error claim %s in id_token is not a valid claim parameter", claim); ret = G_ERROR_PARAM; } } } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "parse_claims_request - Error j_claims not a JSON object"); ret = G_ERROR_PARAM; } return ret; } static int is_true(const char * value) { return (0 == o_strcmp("1", value) || 0 == o_strcasecmp("yes", value) || 0 == o_strcasecmp("true", value) || 0 == o_strcasecmp("indeed, my friend", value)); } static int is_encrypt_token_allowed(struct _oidc_config * config, json_t * j_client, int type) { int ret; switch (type) { case GLEWLWYD_TOKEN_TYPE_CODE: ret = is_true(json_string_value(json_object_get(j_client, json_string_value(json_object_get(config->j_params, "client-encrypt_code-parameter"))))); break; case GLEWLWYD_TOKEN_TYPE_ACCESS_TOKEN: ret = is_true(json_string_value(json_object_get(j_client, json_string_value(json_object_get(config->j_params, "client-encrypt_at-parameter"))))); break; case GLEWLWYD_TOKEN_TYPE_USERINFO: ret = is_true(json_string_value(json_object_get(j_client, json_string_value(json_object_get(config->j_params, "client-encrypt_userinfo-parameter"))))); break; case GLEWLWYD_TOKEN_TYPE_ID_TOKEN: ret = is_true(json_string_value(json_object_get(j_client, json_string_value(json_object_get(config->j_params, "client-encrypt_id_token-parameter"))))); break; case GLEWLWYD_TOKEN_TYPE_REFRESH_TOKEN: ret = is_true(json_string_value(json_object_get(j_client, json_string_value(json_object_get(config->j_params, "client-encrypt_refresh_token-parameter"))))); break; case GLEWLWYD_TOKEN_TYPE_INTROSPECTION: ret = is_true(json_string_value(json_object_get(j_client, json_string_value(json_object_get(config->j_params, "client-encrypt_introspection-parameter"))))); break; case GLEWLWYD_TOKEN_TYPE_CIBA: ret = (json_object_get(j_client, "backchannel_authentication_request_encryption_alg") != NULL); break; case GLEWLWYD_TOKEN_TYPE_AUTH: ret = (json_object_get(j_client, "authorization_encrypted_response_alg") != NULL); break; default: ret = 0; break; } return ret; } static jwa_alg get_token_sign_alg(struct _oidc_config * config, json_t * j_client, int type) { const char * sign_kid = json_string_value(json_object_get(config->j_params, "client-sign_kid-parameter")); jwk_t * jwk = NULL; jwa_alg alg = R_JWA_ALG_UNKNOWN; if (j_client != NULL) { if (json_string_length(json_object_get(j_client, sign_kid))) { jwk = r_jwks_get_by_kid(config->jwks_sign, json_string_value(json_object_get(j_client, sign_kid))); alg = r_str_to_jwa_alg(r_jwk_get_property_str(jwk, "alg")); r_jwk_free(jwk); } else { switch (type) { case GLEWLWYD_TOKEN_TYPE_ACCESS_TOKEN: if (json_object_get(j_client, "access_token_signing_alg") != NULL) { alg = r_str_to_jwa_alg(json_string_value(json_object_get(j_client, "access_token_signing_alg"))); } break; case GLEWLWYD_TOKEN_TYPE_ID_TOKEN: if (json_object_get(j_client, "id_token_signing_alg") != NULL) { alg = r_str_to_jwa_alg(json_string_value(json_object_get(j_client, "id_token_signing_alg"))); } break; case GLEWLWYD_TOKEN_TYPE_USERINFO: if (json_object_get(j_client, "userinfo_signing_alg") != NULL) { alg = r_str_to_jwa_alg(json_string_value(json_object_get(j_client, "userinfo_signing_alg"))); } break; case GLEWLWYD_TOKEN_TYPE_AUTH: if (json_object_get(j_client, "authorization_signed_response_alg") != NULL) { alg = r_str_to_jwa_alg(json_string_value(json_object_get(j_client, "authorization_signed_response_alg"))); } break; default: alg = R_JWA_ALG_UNKNOWN; break; } } } if (alg == R_JWA_ALG_UNKNOWN) { jwk = r_jwks_get_at(config->jwks_sign, 0); alg = r_str_to_jwa_alg(r_jwk_get_property_str(jwk, "alg")); r_jwk_free(jwk); } return alg; } static jwa_alg get_token_enc_alg(struct _oidc_config * config, json_t * j_client, int type) { jwa_alg alg = R_JWA_ALG_UNKNOWN; const char * alg_p = json_string_value(json_object_get(config->j_params, "client-alg-parameter")); if (json_object_get(j_client, alg_p) != NULL) { alg = r_str_to_jwa_alg(json_string_value(json_object_get(j_client, alg_p))); } else { switch (type) { case GLEWLWYD_TOKEN_TYPE_ACCESS_TOKEN: alg = r_str_to_jwa_alg(json_string_value(json_object_get(j_client, "access_token_encryption_alg"))); break; case GLEWLWYD_TOKEN_TYPE_ID_TOKEN: alg = r_str_to_jwa_alg(json_string_value(json_object_get(j_client, "id_token_encryption_alg"))); break; case GLEWLWYD_TOKEN_TYPE_USERINFO: alg = r_str_to_jwa_alg(json_string_value(json_object_get(j_client, "userinfo_encryption_alg"))); break; case GLEWLWYD_TOKEN_TYPE_AUTH: alg = r_str_to_jwa_alg(json_string_value(json_object_get(j_client, "authorization_encrypted_response_alg"))); break; default: break; } } return alg; } static jwa_enc get_token_enc(struct _oidc_config * config, json_t * j_client, int type) { jwa_enc enc = R_JWA_ENC_A128CBC; const char * enc_p = json_string_value(json_object_get(config->j_params, "client-enc-parameter")); if (json_object_get(j_client, enc_p) != NULL) { enc = r_str_to_jwa_enc(json_string_value(json_object_get(j_client, enc_p))); } else { switch (type) { case GLEWLWYD_TOKEN_TYPE_ACCESS_TOKEN: enc = r_str_to_jwa_enc(json_string_value(json_object_get(j_client, "access_token_encryption_enc"))); break; case GLEWLWYD_TOKEN_TYPE_ID_TOKEN: enc = r_str_to_jwa_enc(json_string_value(json_object_get(j_client, "id_token_encryption_enc"))); break; case GLEWLWYD_TOKEN_TYPE_USERINFO: enc = r_str_to_jwa_enc(json_string_value(json_object_get(j_client, "userinfo_encryption_enc"))); break; case GLEWLWYD_TOKEN_TYPE_AUTH: enc = r_str_to_jwa_enc(json_string_value(json_object_get(j_client, "authorization_encrypted_response_enc"))); break; default: break; } } return enc; } static jwk_t * get_jwk_sign(struct _oidc_config * config, json_t * j_client, jwa_alg alg) { jwks_t * jwks_subset = NULL; jwk_t * jwk = NULL; const char * sign_kid = json_string_value(json_object_get(config->j_params, "client-sign_kid-parameter")); if (r_jwks_size(config->jwks_sign) == 1 || j_client == NULL) { return r_jwks_get_at(config->jwks_sign, 0); } else if (json_string_length(json_object_get(j_client, sign_kid))) { return r_jwks_get_by_kid(config->jwks_sign, json_string_value(json_object_get(j_client, sign_kid))); } else { if (alg == R_JWA_ALG_HS256 || alg == R_JWA_ALG_HS384 || alg == R_JWA_ALG_HS512) { jwks_subset = r_jwks_search_json_str(config->jwks_sign, "{\"kty\":\"oct\"}"); jwk = r_jwks_get_at(jwks_subset, 0); r_jwks_free(jwks_subset); } else if (alg == R_JWA_ALG_RS256 || alg == R_JWA_ALG_RS384 || alg == R_JWA_ALG_RS512 || alg == R_JWA_ALG_PS256 || alg == R_JWA_ALG_PS384 || alg == R_JWA_ALG_PS512) { jwks_subset = r_jwks_search_json_str(config->jwks_sign, "{\"kty\":\"RSA\"}"); jwk = r_jwks_get_at(jwks_subset, 0); r_jwks_free(jwks_subset); } else if (alg == R_JWA_ALG_ES256 || alg == R_JWA_ALG_ES384 || alg == R_JWA_ALG_ES512) { jwks_subset = r_jwks_search_json_str(config->jwks_sign, "{\"kty\":\"EC\"}"); jwk = r_jwks_get_at(jwks_subset, 0); r_jwks_free(jwks_subset); } else if (alg == R_JWA_ALG_EDDSA || alg == R_JWA_ALG_ES256K) { jwks_subset = r_jwks_search_json_str(config->jwks_sign, "{\"kty\":\"OKP\"}"); jwk = r_jwks_get_at(jwks_subset, 0); r_jwks_free(jwks_subset); } } return jwk; } static jwk_t * get_jwk_enc(struct _oidc_config * config, json_t * j_client, jwa_alg alg, jwa_enc enc) { jwks_t * jwks_pub, * jwks_subset; jwk_t * jwk = NULL, * jwk_import = NULL; const char * alg_kid_p = json_string_value(json_object_get(config->j_params, "client-alg_kid-parameter")); unsigned char key[64] = {0}; size_t key_len = 64; if (r_jwks_init(&jwks_pub) == RHN_OK) { if (json_string_length(json_object_get(j_client, json_string_value(json_object_get(config->j_params, "client-pubkey-parameter"))))) { if ((jwk_import = r_jwk_quick_import(R_IMPORT_PEM, R_X509_TYPE_UNSPECIFIED, json_string_value(json_object_get(j_client, json_string_value(json_object_get(config->j_params, "client-pubkey-parameter")))), json_string_length(json_object_get(j_client, json_string_value(json_object_get(config->j_params, "client-pubkey-parameter")))))) != NULL) { r_jwks_append_jwk(jwks_pub, jwk_import); r_jwk_free(jwk_import); } } if (json_object_get(j_client, json_string_value(json_object_get(config->j_params, "client-jwks-parameter"))) != NULL) { if (r_jwks_import_from_json_t(jwks_pub, json_object_get(j_client, json_string_value(json_object_get(config->j_params, "client-jwks-parameter")))) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "get_jwk_enc - Error r_jwks_import_from_json_t"); } } if (json_string_length(json_object_get(j_client, json_string_value(json_object_get(config->j_params, "client-jwks_uri-parameter"))))) { if (r_jwks_import_from_uri(jwks_pub, json_string_value(json_object_get(j_client, json_string_value(json_object_get(config->j_params, "client-jwks_uri-parameter")))), config->x5u_flags) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "get_jwk_enc - Error r_jwks_import_from_uri"); } } if (json_string_length(json_object_get(j_client, alg_kid_p))) { jwk = r_jwks_get_by_kid(jwks_pub, json_string_value(json_object_get(j_client, alg_kid_p))); if (jwk == NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "Error, kid '%s' specified for client '%s' is invalid", json_string_value(json_object_get(j_client, alg_kid_p)), json_string_value(json_object_get(j_client, "client_id"))); } } else if (alg == R_JWA_ALG_RSA1_5 || alg == R_JWA_ALG_RSA_OAEP || alg == R_JWA_ALG_RSA_OAEP_256) { jwks_subset = r_jwks_search_json_str(jwks_pub, "{\"kty\":\"RSA\"}"); jwk = r_jwks_get_at(jwks_subset, 0); r_jwks_free(jwks_subset); } else if (alg == R_JWA_ALG_ECDH_ES || alg == R_JWA_ALG_ECDH_ES_A128KW || alg == R_JWA_ALG_ECDH_ES_A192KW || alg == R_JWA_ALG_ECDH_ES_A256KW) { jwks_subset = r_jwks_search_json_str(jwks_pub, "{\"kty\":\"EC\"}"); jwk = r_jwks_get_at(jwks_subset, 0); r_jwks_free(jwks_subset); if (jwk == NULL) { jwks_subset = r_jwks_search_json_str(jwks_pub, "{\"kty\":\"OKP\"}"); jwk = r_jwks_get_at(jwks_subset, 0); r_jwks_free(jwks_subset); } } else if (alg == R_JWA_ALG_A128KW || alg == R_JWA_ALG_A192KW || alg == R_JWA_ALG_A256KW || alg == R_JWA_ALG_A128GCMKW || alg == R_JWA_ALG_A192GCMKW || alg == R_JWA_ALG_A256GCMKW || alg == R_JWA_ALG_PBES2_H256 || alg == R_JWA_ALG_PBES2_H384 || alg == R_JWA_ALG_PBES2_H512) { if (json_string_length(json_object_get(j_client, "client_secret"))) { if (generate_digest_raw((alg == R_JWA_ALG_DIR?digest_SHA512:digest_SHA256), (const unsigned char *)json_string_value(json_object_get(j_client, "client_secret")), json_string_length(json_object_get(j_client, "client_secret")), key, &key_len)) { if (alg == R_JWA_ALG_DIR) { key_len = get_enc_key_size(enc); } else if (alg == R_JWA_ALG_A128GCMKW || alg == R_JWA_ALG_A128KW) { key_len = 16; } else if (alg == R_JWA_ALG_A192GCMKW || alg == R_JWA_ALG_A192KW) { key_len = 24; } jwk = r_jwk_quick_import(R_IMPORT_SYMKEY, key, key_len); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_jwk_enc - Error generate_digest_raw"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_jwk_enc - Client '%s' has no secret available", json_string_value(json_object_get(j_client, "client_id"))); } } r_jwks_free(jwks_pub); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_jwk_enc - Error r_jwks_init"); } return jwk; } static char * encrypt_token_if_required(struct _oidc_config * config, const char * token, json_t * j_client, int type, int * result) { char * token_out = NULL; jwe_t * jwe = NULL; jwa_alg alg = get_token_enc_alg(config, j_client, type); jwa_enc enc = get_token_enc(config, j_client, type); jwk_t * jwk = get_jwk_enc(config, j_client, alg, enc); if (alg != R_JWA_ALG_UNKNOWN && json_object_get(config->j_params, "oauth-fapi-allow-restrict-alg") == json_true() && !json_array_has_string(json_object_get(config->j_params, "oauth-fapi-restrict-alg"), r_jwa_alg_to_str(alg))) { *result = G_ERROR_UNAUTHORIZED; } else { if (j_client != NULL && json_object_get(j_client, "confidential") == json_true() && is_encrypt_token_allowed(config, j_client, type) && json_object_get(config->j_params, "encrypt-out-token-allow") == json_true() && jwk != NULL && alg != R_JWA_ALG_UNKNOWN) { if (r_jwe_init(&jwe) == RHN_OK && r_jwe_set_payload(jwe, (const unsigned char *)token, o_strlen(token)) == RHN_OK && r_jwe_set_enc(jwe, enc) == RHN_OK && r_jwe_set_alg(jwe, alg) == RHN_OK) { if (type == GLEWLWYD_TOKEN_TYPE_ACCESS_TOKEN) { r_jwe_set_header_str_value(jwe, "typ", "at+jwt"); } else if (type == GLEWLWYD_TOKEN_TYPE_INTROSPECTION) { r_jwe_set_header_str_value(jwe, "typ", "token-introspection+jwt"); } else if (type == GLEWLWYD_TOKEN_TYPE_USERINFO) { r_jwe_set_header_str_value(jwe, "typ", "token-userinfo+jwt"); } if (type != GLEWLWYD_TOKEN_TYPE_REFRESH_TOKEN && type != GLEWLWYD_TOKEN_TYPE_CODE) { r_jwe_set_header_str_value(jwe, "cty", "JWT"); } if (jwk != NULL) { token_out = r_jwe_serialize(jwe, jwk, 0); if (token_out == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "encrypt_token_if_required - Error r_jwe_serialize"); } } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "encrypt_token_if_required - Error setting values enc or alg for client_id %s", json_string_value(json_object_get(j_client, "client_id"))); } r_jwe_free(jwe); } else { token_out = o_strdup(token); } *result = G_OK; } r_jwk_free(jwk); return token_out; } static int get_session_token(struct _oidc_config * config, const struct _u_request * request, struct _u_response * response, char * sid) { int ret = G_OK; char expires[129]; time_t now; struct tm ts; time(&now); now += (time_t)json_integer_value(json_object_get(config->j_params, "session-cookie-expiration")); gmtime_r(&now, &ts); strftime(expires, 128, "%a, %d %b %Y %T %Z", &ts); if (json_object_get(config->j_params, "session-management-allowed") == json_true()) { if (u_map_get(request->map_cookie, json_string_value(json_object_get(config->j_params, "session-cookie-name"))) != NULL && o_strlen(u_map_get(request->map_cookie, json_string_value(json_object_get(config->j_params, "session-cookie-name")))) == OIDC_SID_LENGTH) { if (o_strncpy(sid, u_map_get(request->map_cookie, json_string_value(json_object_get(config->j_params, "session-cookie-name"))), OIDC_SID_LENGTH) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "get_session_token - Error o_strncpy"); ret = G_ERROR; } else { if (ulfius_add_cookie_to_response(response, json_string_value(json_object_get(config->j_params, "session-cookie-name")), sid, expires, 0, config->glewlwyd_config->glewlwyd_config->cookie_domain, "/", config->glewlwyd_config->glewlwyd_config->cookie_secure, 0) != U_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "get_session_token - Error ulfius_add_cookie_to_response (1)"); ret = G_ERROR; } } } else { if (rand_string_nonce(sid, OIDC_SID_LENGTH) == NULL) { ret = G_ERROR; } else { if (ulfius_add_cookie_to_response(response, json_string_value(json_object_get(config->j_params, "session-cookie-name")), sid, expires, 0, config->glewlwyd_config->glewlwyd_config->cookie_domain, "/", config->glewlwyd_config->glewlwyd_config->cookie_secure, 0) != U_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "get_session_token - Error ulfius_add_cookie_to_response (2)"); ret = G_ERROR; } } } } return ret; } static char * generate_x_hash(struct _oidc_config * config, json_t * j_client, const char * value) { int key_size; int dig_alg = GNUTLS_DIG_UNKNOWN; gnutls_datum_t hash_data; unsigned char x_hash[128] = {0}; size_t x_hash_len = 128, x_hash_encoded_len = 0; char * to_return = NULL, x_hash_encoded[128] = {0}; if (value != NULL && j_client != NULL) { key_size = get_key_size_from_alg(r_jwa_alg_to_str(get_token_sign_alg(config, j_client, GLEWLWYD_TOKEN_TYPE_ID_TOKEN))); if (key_size == 256) dig_alg = GNUTLS_DIG_SHA256; else if (key_size == 384) dig_alg = GNUTLS_DIG_SHA384; else if (key_size == 512) dig_alg = GNUTLS_DIG_SHA512; if (dig_alg != GNUTLS_DIG_UNKNOWN) { hash_data.data = (unsigned char*)value; hash_data.size = o_strlen(value); if (gnutls_fingerprint(dig_alg, &hash_data, x_hash, &x_hash_len) == GNUTLS_E_SUCCESS) { if (o_base64url_encode(x_hash, x_hash_len/2, (unsigned char *)x_hash_encoded, &x_hash_encoded_len)) { to_return = o_strndup(x_hash_encoded, x_hash_encoded_len); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_x_hash - Error o_base64url_encode x_hash"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_x_hash - Error gnutls_fingerprint x_hash"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_x_hash - Error digest algorithm size '%d' not supported x_hash", key_size); } } return to_return; } /** * Generates a client_access_token from the specified parameters that are considered valid */ static char * generate_client_access_token(struct _oidc_config * config, json_t * j_client, const char * scope_list, const char * resource, time_t now, char * jti, const char * x5t_s256, const char * ip_source) { jwt_t * jwt; jwa_alg alg = get_token_sign_alg(config, j_client, GLEWLWYD_TOKEN_TYPE_ACCESS_TOKEN); jwk_t * jwk = get_jwk_sign(config, j_client, alg); char * token = NULL; json_t * j_cnf; if (jwk != NULL && alg != R_JWA_ALG_UNKNOWN) { if (r_jwt_init(&jwt) == RHN_OK) { r_jwt_set_sign_alg(jwt, alg); rand_string_nonce(jti, OIDC_JTI_LENGTH); r_jwt_set_header_str_value(jwt, "typ", "at+jwt"); // Build jwt payload r_jwt_set_claim_str_value(jwt, "iss", json_string_value(json_object_get(config->j_params, "iss"))); if (resource != NULL) { r_jwt_set_claim_str_value(jwt, "aud", resource); } else { r_jwt_set_claim_str_value(jwt, "aud", scope_list); } r_jwt_set_claim_str_value(jwt, "client_id", json_string_value(json_object_get(j_client, "client_id"))); r_jwt_set_claim_int_value(jwt, "iat", now); r_jwt_set_claim_int_value(jwt, "exp", (((json_int_t)now) + config->access_token_duration)); r_jwt_set_claim_int_value(jwt, "nbf", now); r_jwt_set_claim_str_value(jwt, "jti", jti); r_jwt_set_claim_str_value(jwt, "type", "client_token"); r_jwt_set_claim_str_value(jwt, "scope", scope_list); if (x5t_s256 != NULL) { j_cnf = json_pack("{ss}", "x5t#S256", x5t_s256); r_jwt_set_claim_json_t_value(jwt, "cnf", j_cnf); json_decref(j_cnf); } token = r_jwt_serialize_signed(jwt, jwk, 0); if (token == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "generate_client_access_token - oidc - Error generating token"); } else { y_log_message(Y_LOG_LEVEL_INFO, "Event oidc - Plugin '%s' - Access token generated for client '%s' with scope list '%s', origin: %s", config->name, json_string_value(json_object_get(j_client, "client_id")), scope_list, ip_source); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_client_access_token - oidc - Error r_jwt_init"); } r_jwt_free(jwt); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_client_access_token - oidc - Error no jwk available"); } r_jwk_free(jwk); return token; } /** * Extract address claim values from user properties */ static json_t * get_address_claim(struct _oidc_config * config, json_t * j_user) { json_t * j_return, * j_address, * j_value; if ((j_address = json_object()) != NULL) { if (json_string_length(json_object_get(json_object_get(config->j_params, "address-claim"), "formatted")) && (j_value = json_object_get(j_user, json_string_value(json_object_get(json_object_get(config->j_params, "address-claim"), "formatted")))) != NULL) { json_object_set(j_address, "formatted", j_value); } if (json_string_length(json_object_get(json_object_get(config->j_params, "address-claim"), "street_address")) && (j_value = json_object_get(j_user, json_string_value(json_object_get(json_object_get(config->j_params, "address-claim"), "street_address")))) != NULL) { json_object_set(j_address, "street_address", j_value); } if (json_string_length(json_object_get(json_object_get(config->j_params, "address-claim"), "locality")) && (j_value = json_object_get(j_user, json_string_value(json_object_get(json_object_get(config->j_params, "address-claim"), "locality")))) != NULL) { json_object_set(j_address, "locality", j_value); } if (json_string_length(json_object_get(json_object_get(config->j_params, "address-claim"), "region")) && (j_value = json_object_get(j_user, json_string_value(json_object_get(json_object_get(config->j_params, "address-claim"), "region")))) != NULL) { json_object_set(j_address, "region", j_value); } if (json_string_length(json_object_get(json_object_get(config->j_params, "address-claim"), "postal_code")) && (j_value = json_object_get(j_user, json_string_value(json_object_get(json_object_get(config->j_params, "address-claim"), "postal_code")))) != NULL) { json_object_set(j_address, "postal_code", j_value); } if (json_string_length(json_object_get(json_object_get(config->j_params, "address-claim"), "country")) && (j_value = json_object_get(j_user, json_string_value(json_object_get(json_object_get(config->j_params, "address-claim"), "country")))) != NULL) { json_object_set(j_address, "country", j_value); } if (json_object_size(j_address)) { j_return = json_pack("{siso}", "result", G_OK, "address", j_address); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); json_decref(j_address); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_address_claim - Error allocating resources for j_address"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } /** * Return the claim value if possible */ static json_t * get_claim_value_from_request(struct _oidc_config * config, const char * claim, json_t * j_claim_request, json_t * j_user) { json_t * j_element = NULL, * j_user_property, * j_claim_value = NULL, * j_return = NULL, * j_values_element; size_t index = 0, index_values = 0; char * endptr = NULL; int return_claim = 1, tmp_claim; long int lvalue; json_array_foreach(json_object_get(config->j_params, "claims"), index, j_element) { if (j_return == NULL && 0 == o_strcmp(json_string_value(json_object_get(j_element, "name")), claim) && json_object_get(j_element, "on-demand") == json_true()) { if ((j_user_property = json_object_get(j_user, json_string_value(json_object_get(j_element, "user-property")))) != NULL && (json_string_length(j_user_property) || json_array_size(j_user_property))) { if (json_object_get(j_claim_request, "value") != NULL) { if (!json_equal(json_object_get(j_claim_request, "value"), j_user_property)) { return_claim = 0; } } else if (json_object_get(j_claim_request, "values") != NULL) { tmp_claim = 0; json_array_foreach(json_object_get(j_claim_request, "values"), index_values, j_values_element) { if (json_equal(j_values_element, j_user_property)) { tmp_claim = 1; break; } } if (!tmp_claim) { return_claim = 0; } } else if (j_claim_request != json_null()) { return_claim = 0; } } else { return_claim = 0; } if (return_claim) { if (json_is_string(j_user_property)) { if (0 == o_strcmp("boolean", json_string_value(json_object_get(j_element, "type")))) { if (0 == o_strcmp(json_string_value(j_user_property), json_string_value(json_object_get(j_element, "boolean-value-true")))) { j_claim_value = json_true(); } else if (0 == o_strcmp(json_string_value(j_user_property), json_string_value(json_object_get(j_element, "boolean-value-false")))) { j_claim_value = json_false(); } } else if (0 == o_strcmp("number", json_string_value(json_object_get(j_element, "type")))) { endptr = NULL; lvalue = strtol(json_string_value(j_user_property), &endptr, 10); if (!(*endptr)) { j_claim_value = json_integer(lvalue); } } else { j_claim_value = json_incref(j_user_property); } } else { j_claim_value = json_array(); json_array_foreach(j_user_property, index_values, j_values_element) { if (0 == o_strcmp("boolean", json_string_value(json_object_get(j_element, "type")))) { if (0 == o_strcmp(json_string_value(j_values_element), json_string_value(json_object_get(j_element, "boolean-value-true")))) { json_array_append(j_claim_value, json_true()); } else if (0 == o_strcmp(json_string_value(j_values_element), json_string_value(json_object_get(j_element, "boolean-value-false")))) { json_array_append(j_claim_value, json_false()); } } else if (0 == o_strcmp("number", json_string_value(json_object_get(j_element, "type")))) { endptr = NULL; lvalue = strtol(json_string_value(j_values_element), &endptr, 10); if (!(*endptr)) { json_array_append_new(j_claim_value, json_integer(lvalue)); } } else { json_array_append(j_claim_value, j_values_element); } } } if (j_claim_value != NULL) { j_return = json_pack("{sisO}", "result", G_OK, "claim", j_claim_value); json_decref(j_claim_value); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } break; } } } if (j_return == NULL) { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } return j_return; } /** * build a userinfo in JSON format */ static json_t * get_userinfo(struct _oidc_config * config, const char * sub, json_t * j_user, json_t * j_claims_request, const char * scopes) { json_t * j_userinfo = json_pack("{ss}", "sub", sub), * j_claim = NULL, * j_user_property, * j_address, * j_scope, * j_claim_request = NULL, * j_claim_value, * j_value = NULL; char ** scopes_array = NULL, * endptr; const char * claim = NULL; long int lvalue; size_t index = 0, index_scope = 0, index_value = 0; if (scopes != NULL && !split_string(scopes, " ", &scopes_array)) { y_log_message(Y_LOG_LEVEL_ERROR, "get_userinfo - Error split_string scopes"); } // Append name if mandatory if (0 == o_strcmp("mandatory", json_string_value(json_object_get(config->j_params, "name-claim")))) { if (json_object_get(j_user, "name") != NULL) { json_object_set(j_userinfo, "name", json_object_get(j_user, "name")); } } // Append e-mail if mandatory if (0 == o_strcmp("mandatory", json_string_value(json_object_get(config->j_params, "email-claim")))) { if (json_object_get(j_user, "email") != NULL) { json_object_set(j_userinfo, "email", json_object_get(j_user, "email")); } } // Append scope if mandatory if (0 == o_strcmp("mandatory", json_string_value(json_object_get(config->j_params, "scope-claim")))) { if (json_object_get(j_user, "scope") != NULL) { json_object_set_new(j_userinfo, "scope", json_array()); for (index=0; scopes_array[index] != NULL; index++) { json_array_append_new(json_object_get(j_userinfo, "scope"), json_string(scopes_array[index])); } } } // Append address if mandatory if (0 == o_strcmp("mandatory", json_string_value(json_object_get(json_object_get(config->j_params, "address-claim"), "type")))) { j_address = get_address_claim(config, j_user); if (check_result_value(j_address, G_OK)) { json_object_set(j_userinfo, "address", json_object_get(j_address, "address")); } else if (!check_result_value(j_address, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_ERROR, "get_userinfo - Error get_address_claim"); } json_decref(j_address); } // Append claims request if (j_claims_request != NULL) { json_object_foreach(j_claims_request, claim, j_claim_request) { // Append name if on demand if (0 == o_strcmp("on-demand", json_string_value(json_object_get(config->j_params, "name-claim"))) && json_null() == j_claim_request && 0 == o_strcmp("name", claim)) { if (json_object_get(j_user, "name") != NULL) { json_object_set(j_userinfo, "name", json_object_get(j_user, "name")); } } // Append e-mail if on demand if (0 == o_strcmp("on-demand", json_string_value(json_object_get(config->j_params, "email-claim"))) && json_null() == j_claim_request && 0 == o_strcmp("email", claim)) { if (json_object_get(j_user, "email") != NULL) { json_object_set(j_userinfo, "email", json_object_get(j_user, "email")); } } // Append scope if on demand if (0 == o_strcmp("on-demand", json_string_value(json_object_get(config->j_params, "scope-claim"))) && json_null() == j_claim_request && 0 == o_strcmp("scope", claim)) { if (json_object_get(j_user, "scope") != NULL) { json_object_set_new(j_userinfo, "scope", json_array()); for (index=0; scopes_array[index] != NULL; index++) { json_array_append_new(json_object_get(j_userinfo, "scope"), json_string(scopes_array[index])); } } } if (0 == o_strcmp("address", claim)) { if (0 == o_strcmp("on-demand", json_string_value(json_object_get(json_object_get(config->j_params, "address-claim"), "type")))) { j_address = get_address_claim(config, j_user); if (check_result_value(j_address, G_OK)) { json_object_set(j_userinfo, "address", json_object_get(j_address, "address")); } else if (!check_result_value(j_address, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_ERROR, "get_userinfo - Error get_address_claim"); } json_decref(j_address); } } else { j_claim_value = get_claim_value_from_request(config, claim, j_claim_request, j_user); if (check_result_value(j_claim_value, G_OK)) { json_object_set(j_userinfo, claim, json_object_get(j_claim_value, "claim")); } json_decref(j_claim_value); } } } // Append scopes claims if (scopes_array != NULL) { json_array_foreach(json_object_get(config->j_params, "name-claim-scope"), index, j_scope) { if (string_array_has_value((const char **)scopes_array, json_string_value(j_scope))) { if (json_object_get(j_user, "name") != NULL) { json_object_set(j_userinfo, "name", json_object_get(j_user, "name")); } } } json_array_foreach(json_object_get(config->j_params, "email-claim-scope"), index, j_scope) { if (string_array_has_value((const char **)scopes_array, json_string_value(j_scope))) { if (json_object_get(j_user, "email") != NULL) { json_object_set(j_userinfo, "email", json_object_get(j_user, "email")); } } } json_array_foreach(json_object_get(config->j_params, "claims"), index, j_claim) { if (json_object_get(j_userinfo, json_string_value(json_object_get(j_claim, "name"))) == NULL) { json_array_foreach(json_object_get(j_claim, "scope"), index_scope, j_scope) { if (string_array_has_value((const char **)scopes_array, json_string_value(j_scope))) { j_user_property = json_object_get(j_user, json_string_value(json_object_get(j_claim, "user-property"))); if (json_string_length(j_user_property)) { if (0 == o_strcmp("boolean", json_string_value(json_object_get(j_claim, "type")))) { if (0 == o_strcmp(json_string_value(j_user_property), json_string_value(json_object_get(j_claim, "boolean-value-true")))) { json_object_set(j_userinfo, json_string_value(json_object_get(j_claim, "name")), json_true()); } else if (0 == o_strcmp(json_string_value(j_user_property), json_string_value(json_object_get(j_claim, "boolean-value-false")))) { json_object_set(j_userinfo, json_string_value(json_object_get(j_claim, "name")), json_false()); } } else if (0 == o_strcmp("number", json_string_value(json_object_get(j_claim, "type")))) { endptr = NULL; lvalue = strtol(json_string_value(j_user_property), &endptr, 10); if (!(*endptr)) { json_object_set_new(j_userinfo, json_string_value(json_object_get(j_claim, "name")), json_integer(lvalue)); } } else { json_object_set(j_userinfo, json_string_value(json_object_get(j_claim, "name")), j_user_property); } } else if (json_array_size(j_user_property)) { json_object_set_new(j_userinfo, json_string_value(json_object_get(j_claim, "name")), json_array()); json_array_foreach(j_user_property, index_value, j_value) { if (0 == o_strcmp("boolean", json_string_value(json_object_get(j_claim, "type")))) { if (0 == o_strcmp(json_string_value(j_value), json_string_value(json_object_get(j_claim, "boolean-value-true")))) { json_array_append(json_object_get(j_userinfo, json_string_value(json_object_get(j_claim, "name"))), json_true()); } else if (0 == o_strcmp(json_string_value(j_value), json_string_value(json_object_get(j_claim, "boolean-value-false")))) { json_array_append(json_object_get(j_userinfo, json_string_value(json_object_get(j_claim, "name"))), json_false()); } } else if (0 == o_strcmp("number", json_string_value(json_object_get(j_claim, "type")))) { endptr = NULL; lvalue = strtol(json_string_value(j_value), &endptr, 10); if (!(*endptr)) { json_array_append_new(json_object_get(j_userinfo, json_string_value(json_object_get(j_claim, "name"))), json_integer(lvalue)); } } else { json_array_append(json_object_get(j_userinfo, json_string_value(json_object_get(j_claim, "name"))), j_value); } } } } } } } } // Append mandatory claims json_array_foreach(json_object_get(config->j_params, "claims"), index, j_claim) { if (json_object_get(j_claim, "mandatory") == json_true()) { j_user_property = json_object_get(j_user, json_string_value(json_object_get(j_claim, "user-property"))); if (json_string_length(j_user_property)) { if (0 == o_strcmp("boolean", json_string_value(json_object_get(j_claim, "type")))) { if (0 == o_strcmp(json_string_value(j_user_property), json_string_value(json_object_get(j_claim, "boolean-value-true")))) { json_object_set(j_userinfo, json_string_value(json_object_get(j_claim, "name")), json_true()); } else if (0 == o_strcmp(json_string_value(j_user_property), json_string_value(json_object_get(j_claim, "boolean-value-false")))) { json_object_set(j_userinfo, json_string_value(json_object_get(j_claim, "name")), json_false()); } } else if (0 == o_strcmp("number", json_string_value(json_object_get(j_claim, "type")))) { endptr = NULL; lvalue = strtol(json_string_value(j_user_property), &endptr, 10); if (!(*endptr)) { json_object_set_new(j_userinfo, json_string_value(json_object_get(j_claim, "name")), json_integer(lvalue)); } } else { json_object_set(j_userinfo, json_string_value(json_object_get(j_claim, "name")), j_user_property); } } else if (json_array_size(j_user_property)) { json_object_set_new(j_userinfo, json_string_value(json_object_get(j_claim, "name")), json_array()); json_array_foreach(j_user_property, index_value, j_value) { if (0 == o_strcmp("boolean", json_string_value(json_object_get(j_claim, "type")))) { if (0 == o_strcmp(json_string_value(j_value), json_string_value(json_object_get(j_claim, "boolean-value-true")))) { json_array_append(json_object_get(j_userinfo, json_string_value(json_object_get(j_claim, "name"))), json_true()); } else if (0 == o_strcmp(json_string_value(j_value), json_string_value(json_object_get(j_claim, "boolean-value-false")))) { json_array_append(json_object_get(j_userinfo, json_string_value(json_object_get(j_claim, "name"))), json_false()); } } else if (0 == o_strcmp("number", json_string_value(json_object_get(j_claim, "type")))) { endptr = NULL; lvalue = strtol(json_string_value(j_value), &endptr, 10); if (!(*endptr)) { json_array_append_new(json_object_get(j_userinfo, json_string_value(json_object_get(j_claim, "name"))), json_integer(lvalue)); } } else { json_array_append(json_object_get(j_userinfo, json_string_value(json_object_get(j_claim, "name"))), j_value); } } } } } free_string_array(scopes_array); return j_userinfo; } /** * Return the id_token_hash of the last id_token provided to the client for the user */ static json_t * get_last_id_token(struct _oidc_config * config, const char * username, const char * client_id) { json_t * j_query, * j_result = NULL, * j_return; int res; j_query = json_pack("{sss[sss]s{ssssss}sssi}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_ID_TOKEN, "columns", "gpoi_authorization_type AS authorization_type", SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpoi_issued_at) AS issued_at", "gpoi_issued_at AS issued_at", "EXTRACT(EPOCH FROM gpoi_issued_at)::integer AS issued_at"), "gpoi_hash AS token_hash", "where", "gpoi_plugin_name", config->name, "gpoi_username", username, "gpoi_client_id", client_id, "order_by", "gpoi_id DESC", "limit", 1); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_return = json_pack("{sisO}", "result", G_OK, "id_token", json_array_get(j_result, 0)); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_last_id_token - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } static json_t * reduce_scope(const char * scope, json_t * scope_list) { char * scope_reduced = NULL, ** scope_array = NULL; json_t * j_return; size_t i; if (split_string(scope, " ", &scope_array)) { for (i=0; scope_array[i]!=NULL; i++) { if (json_array_has_string(scope_list, scope_array[i])) { if (scope_reduced == NULL) { scope_reduced = o_strdup(scope_array[i]); } else { scope_reduced = mstrcatf(scope_reduced, " %s", scope_array[i]); } } } if (scope_reduced != NULL) { j_return = json_pack("{siss}", "result", G_OK, "scope", scope_reduced); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } o_free(scope_reduced); } else { y_log_message(Y_LOG_LEVEL_ERROR, "reduce_scope - Error split_string"); j_return = json_pack("{si}", "result", G_ERROR); } free_string_array(scope_array); return j_return; } static int serialize_pushed_request_uri(struct _oidc_config * config, const char * request_uri, const char * response_type, const char * client_id, const char * state, const char * scope_list, const char * nonce, const char * resource, const char * redirect_uri, const char * issued_for, const char * user_agent, json_t * j_claims, const char * code_challenge, json_t * j_authorization_details, struct _u_map * additional_parameters) { json_t * j_query, * j_last_id, * j_additional_parameters = NULL; int ret, res, i; char * request_uri_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, request_uri), ** scope_array = NULL, * str_claims_request = NULL, * str_authorization_details = NULL, * expires_at_clause, * str_additional_parameters = NULL; const char ** keys; time_t now; time(&now); if (request_uri_hash != NULL) { if (split_string(scope_list, " ", &scope_array)) { if (pthread_mutex_lock(&config->insert_lock)) { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_pushed_request_uri oidc - Error pthread_mutex_lock"); ret = G_ERROR; } else { if (j_claims != NULL) { str_claims_request = json_dumps(j_claims, JSON_COMPACT); } if (j_authorization_details != NULL) { str_authorization_details = json_dumps(j_authorization_details, JSON_COMPACT); } if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("FROM_UNIXTIME(%u)", (now + (unsigned int)config->request_uri_duration)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("TO_TIMESTAMP(%u)", (now + (unsigned int)config->request_uri_duration )); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("%u", (now + (unsigned int)config->request_uri_duration)); } if (u_map_count(additional_parameters)) { if ((j_additional_parameters = json_object()) != NULL) { keys = u_map_enum_keys(additional_parameters); for (i=0; keys[i]!=NULL; i++) { json_object_set_new(j_additional_parameters, keys[i], json_string(u_map_get(additional_parameters, keys[i]))); } str_additional_parameters = json_dumps(j_additional_parameters, JSON_COMPACT); } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_pushed_request_uri oidc - Error allocating resources for j_additional_parameters"); } json_decref(j_additional_parameters); } j_query = json_pack("{sss{ss ss ss* ss ss ss ss* ss* ss* ss* ss* ss* s{ss} ss ss*}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_PAR, "values", "gpop_plugin_name", config->name, "gpop_response_type", response_type, "gpop_state", state, "gpop_client_id", client_id, "gpop_redirect_uri", redirect_uri, "gpop_request_uri_hash", request_uri_hash, "gpop_nonce", nonce, "gpop_code_challenge", code_challenge, "gpop_resource", resource, "gpop_claims_request", str_claims_request, "gpop_authorization_details", str_authorization_details, "gpop_additional_parameters", str_additional_parameters, "gpop_expires_at", "raw", expires_at_clause, "gpop_issued_for", issued_for, "gpop_user_agent", user_agent); o_free(expires_at_clause); o_free(str_claims_request); o_free(str_authorization_details); o_free(str_additional_parameters); res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_last_id = h_last_insert_id(config->glewlwyd_config->glewlwyd_config->conn); j_query = json_pack("{sss[]}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_PAR_SCOPE, "values"); for (i=0; scope_array[i]!= NULL; i++) { json_array_append_new(json_object_get(j_query, "values"), json_pack("{sOss}", "gpop_id", j_last_id, "gpops_scope", scope_array[i])); } res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_pushed_request_uri oidc - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } json_decref(j_last_id); } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_pushed_request_uri oidc - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } pthread_mutex_unlock(&config->insert_lock); } free_string_array(scope_array); } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_pushed_request_uri oidc - Error split_string"); ret = G_ERROR; } o_free(request_uri_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_pushed_request_uri oidc - Error glewlwyd_callback_generate_hash"); ret = G_ERROR; } return ret; } static char * generate_pushed_request_uri(struct _oidc_config * config) { char * request_uri = o_malloc((json_string_length(json_object_get(config->j_params, "oauth-par-request_uri-prefix"))+OIDC_REQUEST_URI_SUFFIX_LENGTH+1)*sizeof(char)); if (request_uri != NULL) { if (json_string_length(json_object_get(config->j_params, "oauth-par-request_uri-prefix"))) { o_strcpy(request_uri, json_string_value(json_object_get(config->j_params, "oauth-par-request_uri-prefix"))); if (rand_string(request_uri+json_string_length(json_object_get(config->j_params, "oauth-par-request_uri-prefix")), OIDC_REQUEST_URI_SUFFIX_LENGTH) == NULL) { o_free(request_uri); request_uri = NULL; } } } return request_uri; } /** * Store a signature of the id_token in the database */ static int serialize_id_token(struct _oidc_config * config, uint auth_type, const char * id_token, const char * username, const char * client_id, const char * sid, json_int_t gpoc_id, json_int_t gpor_id, time_t now, const char * issued_for, const char * user_agent) { json_t * j_query; int res, ret; char * issued_at_clause, * id_token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, id_token); if (pthread_mutex_lock(&config->insert_lock)) { y_log_message(Y_LOG_LEVEL_ERROR, "oidc serialize_id_token - Error pthread_mutex_lock"); ret = G_ERROR; } else { if (issued_for != NULL && now > 0 && id_token_hash != NULL) { if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { issued_at_clause = msprintf("FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { issued_at_clause = msprintf("TO_TIMESTAMP(%u)", (now)); } else { // HOEL_DB_TYPE_SQLITE issued_at_clause = msprintf("%u", (now)); } j_query = json_pack("{sss{sssisosos{ss}ssssssss*soso}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_ID_TOKEN, "values", "gpoi_plugin_name", config->name, "gpoi_authorization_type", auth_type, "gpoi_username", username!=NULL?json_string(username):json_null(), "gpoi_client_id", client_id!=NULL?json_string(client_id):json_null(), "gpoi_issued_at", "raw", issued_at_clause, "gpoi_issued_for", issued_for, "gpoi_user_agent", user_agent!=NULL?user_agent:"", "gpoi_hash", id_token_hash, "gpoi_sid", sid, "gpoc_id", gpoc_id?json_integer(gpoc_id):json_null(), "gpor_id", gpor_id?json_integer(gpor_id):json_null()); o_free(issued_at_clause); res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc serialize_id_token - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { ret = G_ERROR_PARAM; } pthread_mutex_unlock(&config->insert_lock); o_free(id_token_hash); } return ret; } /** * Builds an id_token from the given parameters */ static char * generate_id_token(struct _oidc_config * config, const char * username, json_t * j_user, json_t * j_client, time_t now, time_t auth_time, const char * nonce, json_t * j_amr, const char * access_token, const char * code, const char * scopes, json_t * j_claims_request, const char * auth_req_id, const char * refresh_token, const char * s_hash, const char * sid, const char * ip_source) { jwt_t * jwt; jwa_alg alg = get_token_sign_alg(config, j_client, GLEWLWYD_TOKEN_TYPE_ID_TOKEN); jwk_t * jwk = get_jwk_sign(config, j_client, alg); int key_size = get_key_size_from_alg(r_jwa_alg_to_str(alg)); char * token = NULL, at_hash_encoded[128] = {0}, c_hash_encoded[128] = {0}, rt_hash_encoded[128] = {0}, * sub = get_sub(config, username, j_client); unsigned char at_hash[128] = {0}, c_hash[128] = {0}, rt_hash[128] = {0}; json_t * j_user_info; size_t at_hash_len = 128, at_hash_encoded_len = 0, c_hash_len = 128, c_hash_encoded_len = 0, rt_hash_len = 128, rt_hash_encoded_len = 0; int dig_alg = GNUTLS_DIG_UNKNOWN; gnutls_datum_t hash_data; if (jwk != NULL && alg != R_JWA_ALG_UNKNOWN) { if (r_jwt_init(&jwt) == RHN_OK) { r_jwt_set_sign_alg(jwt, alg); if (sub != NULL) { if (key_size) { if ((j_user_info = get_userinfo(config, sub, j_user, j_claims_request, scopes)) != NULL) { json_object_set(j_user_info, "iss", json_object_get(config->j_params, "iss")); json_object_set(j_user_info, "aud", json_object_get(j_client, "client_id")); json_object_set_new(j_user_info, "exp", json_integer(((json_int_t)now) + config->access_token_duration)); json_object_set_new(j_user_info, "iat", json_integer(now)); json_object_set_new(j_user_info, "auth_time", json_integer(auth_time)); json_object_set(j_user_info, "azp", json_object_get(j_client, "client_id")); if (o_strlen(nonce)) { json_object_set_new(j_user_info, "nonce", json_string(nonce)); } if (j_amr != NULL && json_array_size(j_amr)) { json_object_set(j_user_info, "amr", j_amr); } if (key_size == 256) dig_alg = GNUTLS_DIG_SHA256; else if (key_size == 384) dig_alg = GNUTLS_DIG_SHA384; else if (key_size == 512) dig_alg = GNUTLS_DIG_SHA512; if (access_token != NULL) { // Hash access_token using the key size for the hash size (SHA style of course!) // take the half left of the has, then encode in base64-url it if (dig_alg != GNUTLS_DIG_UNKNOWN) { hash_data.data = (unsigned char*)access_token; hash_data.size = o_strlen(access_token); if (gnutls_fingerprint(dig_alg, &hash_data, at_hash, &at_hash_len) == GNUTLS_E_SUCCESS) { if (o_base64url_encode(at_hash, at_hash_len/2, (unsigned char *)at_hash_encoded, &at_hash_encoded_len)) { json_object_set_new(j_user_info, "at_hash", json_stringn(at_hash_encoded, at_hash_encoded_len)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_id_token - Error o_base64url_encode at_hash"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_id_token - Error gnutls_fingerprint at_hash"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_id_token - Error digest algorithm size '%d' not supported at_hash", key_size); } } if (code != NULL) { // Hash access_token using the key size for the hash size (SHA style of course!) // take the half left of the has, then encode in base64-url it if (dig_alg != GNUTLS_DIG_UNKNOWN) { hash_data.data = (unsigned char*)code; hash_data.size = o_strlen(code); if (gnutls_fingerprint(dig_alg, &hash_data, c_hash, &c_hash_len) == GNUTLS_E_SUCCESS) { if (o_base64url_encode(c_hash, c_hash_len/2, (unsigned char *)c_hash_encoded, &c_hash_encoded_len)) { json_object_set_new(j_user_info, "c_hash", json_stringn(c_hash_encoded, c_hash_encoded_len)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_id_token - Error o_base64url_encode c_hash"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_id_token - Error gnutls_fingerprint c_hash"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_id_token - Error digest algorithm size '%d' not supported c_hash", key_size); } } if (s_hash != NULL) { json_object_set_new(j_user_info, "s_hash", json_string(s_hash)); } if (auth_req_id != NULL) { json_object_set_new(j_user_info, "urn:openid:params:jwt:claim:auth_req_id", json_string(auth_req_id)); } if (refresh_token != NULL) { // Hash access_token using the key size for the hash size (SHA style of course!) // take the half left of the has, then encode in base64-url it if (dig_alg != GNUTLS_DIG_UNKNOWN) { hash_data.data = (unsigned char*)refresh_token; hash_data.size = o_strlen(refresh_token); if (gnutls_fingerprint(dig_alg, &hash_data, rt_hash, &rt_hash_len) == GNUTLS_E_SUCCESS) { if (o_base64url_encode(rt_hash, rt_hash_len/2, (unsigned char *)rt_hash_encoded, &rt_hash_encoded_len)) { json_object_set_new(j_user_info, "urn:openid:params:jwt:claim:rt_hash", json_stringn(rt_hash_encoded, rt_hash_encoded_len)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_id_token - Error o_base64url_encode rt_hash"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_id_token - Error gnutls_fingerprint rt_hash"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_id_token - Error digest algorithm size '%d' not supported rt_hash", key_size); } } if (o_strlen(sid)) { json_object_set_new(j_user_info, "sid", json_string(sid)); } //jwt_add_grant(jwt, "acr", "plop"); // TODO? if (r_jwt_set_full_claims_json_t(jwt, j_user_info) == RHN_OK) { token = r_jwt_serialize_signed(jwt, jwk, 0); if (token == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "generate_id_token - oidc - Error r_jwt_serialize_signed"); } else { y_log_message(Y_LOG_LEVEL_INFO, "Event oidc - Plugin '%s' - id_token generated for client '%s' granted by user '%s', origin: %s", config->name, json_string_value(json_object_get(j_client, "client_id")), username, ip_source); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_id_token - oidc - Error jwt_add_grants_json"); } json_decref(j_user_info); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_id_token - oidc - Error get_userinfo"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_id_token - oidc - Error key_size"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_id_token - oidc - Error get_sub"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_id_token - oidc - Error r_jwt_init"); } r_jwt_free(jwt); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_id_token - oidc - Error no sign jwk available"); } r_jwk_free(jwk); o_free(sub); return token; } /** * Store a signature of the acces token in the database */ static int serialize_access_token(struct _oidc_config * config, uint auth_type, json_int_t gpor_id, const char * username, const char * client_id, const char * scope_list, const char * resource, time_t now, const char * issued_for, const char * user_agent, const char * access_token, const char * jti, json_t * j_authorization_details) { json_t * j_query, * j_last_id; int res, ret, i; char * issued_at_clause, ** scope_array = NULL, * access_token_hash = NULL, * str_authorization_details = NULL; if (pthread_mutex_lock(&config->insert_lock)) { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_access_token - oidc - Error pthread_mutex_lock"); ret = G_ERROR; } else { if ((access_token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, access_token)) != NULL) { if (issued_for != NULL && now > 0) { if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { issued_at_clause = msprintf("FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { issued_at_clause = msprintf("TO_TIMESTAMP(%u)", (now)); } else { // HOEL_DB_TYPE_SQLITE issued_at_clause = msprintf("%u", (now)); } if (j_authorization_details != NULL) { str_authorization_details = json_dumps(j_authorization_details, JSON_COMPACT); } j_query = json_pack("{sss{sssisososos{ss}ssssssss#ss?ss?}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_ACCESS_TOKEN, "values", "gpoa_plugin_name", config->name, "gpoa_authorization_type", auth_type, "gpor_id", gpor_id?json_integer(gpor_id):json_null(), "gpoa_username", username!=NULL?json_string(username):json_null(), "gpoa_client_id", client_id!=NULL?json_string(client_id):json_null(), "gpoa_issued_at", "raw", issued_at_clause, "gpoa_issued_for", issued_for, "gpoa_user_agent", user_agent!=NULL?user_agent:"", "gpoa_token_hash", access_token_hash, "gpoa_jti", jti, OIDC_JTI_LENGTH, "gpoa_resource", resource, "gpoa_authorization_details", str_authorization_details); o_free(issued_at_clause); o_free(str_authorization_details); res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_last_id = h_last_insert_id(config->glewlwyd_config->glewlwyd_config->conn); if (j_last_id != NULL) { if (split_string(scope_list, " ", &scope_array) > 0) { j_query = json_pack("{sss[]}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_ACCESS_TOKEN_SCOPE, "values"); if (j_query != NULL) { for (i=0; scope_array[i] != NULL; i++) { json_array_append_new(json_object_get(j_query, "values"), json_pack("{sOss}", "gpoa_id", j_last_id, "gpoas_scope", scope_array[i])); } res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_access_token - oidc - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_access_token - oidc - Error json_pack"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_access_token - oidc - Error split_string"); ret = G_ERROR; } free_string_array(scope_array); } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_access_token - oidc - Error h_last_insert_id"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } json_decref(j_last_id); } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_access_token - oidc - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { ret = G_ERROR_PARAM; } o_free(access_token_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc serialize_access_token - Error glewlwyd_callback_generate_hash"); ret = G_ERROR; } pthread_mutex_unlock(&config->insert_lock); } return ret; } /** * Builds an acces token from the given parameters */ static char * generate_access_token(struct _oidc_config * config, const char * username, json_t * j_client, json_t * j_user, const char * scope_list, json_t * j_claims, const char * resource, time_t now, char * jti, const char * x5t_s256, const char * dpop_jkt, json_t * j_authorization_details, const char * ip_source) { jwt_t * jwt; jwa_alg alg = get_token_sign_alg(config, j_client, GLEWLWYD_TOKEN_TYPE_ACCESS_TOKEN); jwk_t * jwk = get_jwk_sign(config, j_client, alg); char * token = NULL, * property = NULL, * sub = get_sub(config, username, j_client); json_t * j_element = NULL, * j_value, * j_cnf; size_t index = 0, index_p = 0; if (jwk != NULL && alg != R_JWA_ALG_UNKNOWN) { if (r_jwt_init(&jwt) == RHN_OK) { if (sub != NULL) { r_jwt_set_sign_alg(jwt, alg); r_jwt_set_header_str_value(jwt, "typ", "at+jwt"); rand_string_nonce(jti, OIDC_JTI_LENGTH); r_jwt_set_claim_str_value(jwt, "iss", json_string_value(json_object_get(config->j_params, "iss"))); if (j_client != NULL) { r_jwt_set_claim_str_value(jwt, "client_id", json_string_value(json_object_get(j_client, "client_id"))); } if (resource != NULL) { r_jwt_set_claim_str_value(jwt, "aud", resource); } else { r_jwt_set_claim_str_value(jwt, "aud", scope_list); } r_jwt_set_claim_str_value(jwt, "sub", sub); r_jwt_set_claim_str_value(jwt, "jti", jti); r_jwt_set_claim_str_value(jwt, "type", "access_token"); r_jwt_set_claim_int_value(jwt, "iat", now); r_jwt_set_claim_int_value(jwt, "exp", (((json_int_t)now) + config->access_token_duration)); r_jwt_set_claim_int_value(jwt, "nbf", now); if (scope_list != NULL) { r_jwt_set_claim_str_value(jwt, "scope", scope_list); } if (j_claims != NULL) { r_jwt_set_claim_json_t_value(jwt, "claims", j_claims); } j_cnf = json_object(); if (x5t_s256 != NULL) { json_object_set_new(j_cnf, "x5t#S256", json_string(x5t_s256)); } if (dpop_jkt != NULL) { json_object_set_new(j_cnf, "jkt", json_string(dpop_jkt)); } if (json_object_size(j_cnf)) { r_jwt_set_claim_json_t_value(jwt, "cnf", j_cnf); } if (j_authorization_details != NULL) { r_jwt_set_claim_json_t_value(jwt, "authorization_details", j_authorization_details); } json_decref(j_cnf); if (json_object_get(config->j_params, "additional-parameters") != NULL && j_user != NULL) { json_array_foreach(json_object_get(config->j_params, "additional-parameters"), index, j_element) { if (json_is_string(json_object_get(j_user, json_string_value(json_object_get(j_element, "user-parameter")))) && json_string_length(json_object_get(j_user, json_string_value(json_object_get(j_element, "user-parameter"))))) { r_jwt_set_claim_str_value(jwt, json_string_value(json_object_get(j_element, "token-parameter")), json_string_value(json_object_get(j_user, json_string_value(json_object_get(j_element, "user-parameter"))))); } else if (json_is_array(json_object_get(j_user, json_string_value(json_object_get(j_element, "user-parameter"))))) { json_array_foreach(json_object_get(j_user, json_string_value(json_object_get(j_element, "user-parameter"))), index_p, j_value) { property = mstrcatf(property, ",%s", json_string_value(j_value)); } if (o_strlen(property)) { r_jwt_set_claim_str_value(jwt, json_string_value(json_object_get(j_element, "token-parameter")), property+1); // Skip first ',' } else { r_jwt_set_claim_str_value(jwt, json_string_value(json_object_get(j_element, "token-parameter")), ""); } o_free(property); property = NULL; } } } if (jwk != NULL) { if (r_jwk_get_property_str(jwk, "alg") != NULL) { r_jwt_set_sign_alg(jwt, r_str_to_jwa_alg(r_jwk_get_property_str(jwk, "alg"))); } if ((token = r_jwt_serialize_signed(jwt, jwk, 0)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "generate_access_token - oidc - Error r_jwt_serialize_signed"); } else { y_log_message(Y_LOG_LEVEL_INFO, "Event oidc - Plugin '%s' - Access token generated for client '%s' granted by user '%s' with scope list '%s', origin: %s", config->name, json_string_value(json_object_get(j_client, "client_id")), username, scope_list, ip_source); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_access_token - oidc - Error no jwk to sign"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_access_token - oidc - Error get_sub"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_access_token - oidc - Error r_jwt_init"); } r_jwt_free(jwt); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_access_token - oidc - Error no jwk available"); } o_free(sub); r_jwk_free(jwk); return token; } /** * Store a signature of the refresh token in the database */ static json_t * serialize_refresh_token(struct _oidc_config * config, uint auth_type, json_int_t gpoc_id, const char * username, const char * client_id, const char * scope_list, const char * resource, time_t now, json_int_t duration, uint rolling, json_t * j_claims_request, const char * token, const char * issued_for, const char * user_agent, char * jti, const char * dpop_jkt, json_t * j_authorization_details) { char * token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, token); json_t * j_query, * j_return, * j_last_id; int res, i; char * issued_at_clause, * expires_at_clause, * last_seen_clause, ** scope_array = NULL, * str_claims_request = NULL, * str_authorization_details = NULL; if (pthread_mutex_lock(&config->insert_lock)) { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_refresh_token - oidc - Error pthread_mutex_lock"); j_return = json_pack("{si}", "result", G_ERROR); } else { if (token_hash != NULL && username != NULL && issued_for != NULL && now > 0 && duration > 0) { json_error_t error; if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { issued_at_clause = msprintf("FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { issued_at_clause = msprintf("TO_TIMESTAMP(%u)", (now)); } else { // HOEL_DB_TYPE_SQLITE issued_at_clause = msprintf("%u", (now)); } if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { last_seen_clause = msprintf("FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { last_seen_clause = msprintf("TO_TIMESTAMP(%u)", (now)); } else { // HOEL_DB_TYPE_SQLITE last_seen_clause = msprintf("%u", (now)); } if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("FROM_UNIXTIME(%u)", (now + (unsigned int)duration)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("TO_TIMESTAMP(%u)", (now + (unsigned int)duration )); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("%u", (now + (unsigned int)duration)); } if (j_claims_request != NULL) { if ((str_claims_request = json_dumps(j_claims_request, JSON_COMPACT)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_refresh_token - oidc - Error dumping JSON claims request"); } } if (j_authorization_details != NULL) { str_authorization_details = json_dumps(j_authorization_details, JSON_COMPACT); } j_query = json_pack_ex(&error, 0, "{sss{ss si so ss so s{ss} s{ss} s{ss} sI si ss ss ss ss ss? ss? ss?}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN, "values", "gpor_plugin_name", config->name, "gpor_authorization_type", auth_type, "gpoc_id", gpoc_id?json_integer(gpoc_id):json_null(), "gpor_username", username, "gpor_client_id", client_id!=NULL?json_string(client_id):json_null(), "gpor_issued_at", "raw", issued_at_clause, "gpor_last_seen", "raw", last_seen_clause, "gpor_expires_at", "raw", expires_at_clause, "gpor_duration", duration, "gpor_rolling_expiration", rolling, "gpor_claims_request", str_claims_request!=NULL?str_claims_request:"", "gpor_token_hash", token_hash, "gpor_issued_for", issued_for, "gpor_user_agent", user_agent!=NULL?user_agent:"", "gpor_resource", resource, "gpor_dpop_jkt", dpop_jkt, "gpor_authorization_details", str_authorization_details); if (config->refresh_token_one_use) { if (!o_strlen(jti)) { rand_string_nonce(jti, OIDC_JTI_LENGTH); } json_object_set_new(json_object_get(j_query, "values"), "gpor_jti", json_string(jti)); } o_free(issued_at_clause); o_free(expires_at_clause); o_free(last_seen_clause); o_free(str_claims_request); o_free(str_authorization_details); res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_last_id = h_last_insert_id(config->glewlwyd_config->glewlwyd_config->conn); if (j_last_id != NULL) { if (split_string(scope_list, " ", &scope_array) > 0) { j_query = json_pack("{sss[]}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN_SCOPE, "values"); if (j_query != NULL) { for (i=0; scope_array[i] != NULL; i++) { json_array_append_new(json_object_get(j_query, "values"), json_pack("{sOss}", "gpor_id", j_last_id, "gpors_scope", scope_array[i])); } res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_return = json_pack("{sisO}", "result", G_OK, "gpor_id", j_last_id); } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_refresh_token - oidc - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_refresh_token - oidc - Error json_pack"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_refresh_token - oidc - Error split_string"); j_return = json_pack("{si}", "result", G_ERROR); } free_string_array(scope_array); } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_refresh_token - oidc - Error h_last_insert_id"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_last_id); } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_refresh_token - oidc - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } o_free(token_hash); pthread_mutex_unlock(&config->insert_lock); } return j_return; } /** * Builds an refresh token from the given parameters */ static char * generate_refresh_token() { char * token = o_malloc((OIDC_REFRESH_TOKEN_LENGTH+1)*sizeof(char)); if (token != NULL) { if (rand_string(token, OIDC_REFRESH_TOKEN_LENGTH) == NULL) { o_free(token); token = NULL; } } return token; } /** * Return true if the auth type is enabled in this plugin instance */ static int is_authorization_type_enabled(struct _oidc_config * config, uint authorization_type) { return (authorization_type <= 7)?config->auth_type_enabled[authorization_type]:0; } /** * Verify if a client is valid without checking its secret */ static json_t * check_client_valid_without_secret(struct _oidc_config * config, const char * client_id, const char * redirect_uri, unsigned short authorization_type, const char * ip_source) { json_t * j_client, * j_element = NULL, * j_return; int uri_found = 0, authorization_type_enabled; size_t index = 0; j_client = config->glewlwyd_config->glewlwyd_plugin_callback_get_client(config->glewlwyd_config, client_id); if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true()) { if (redirect_uri != NULL) { uri_found = 0; json_array_foreach(json_object_get(json_object_get(j_client, "client"), "redirect_uri"), index, j_element) { if (0 == o_strcmp(json_string_value(j_element), redirect_uri)) { uri_found = 1; } } } else { uri_found = 1; } authorization_type_enabled = 0; json_array_foreach(json_object_get(json_object_get(j_client, "client"), "authorization_type"), index, j_element) { if (authorization_type & GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE_FLAG && 0 == o_strcmp(json_string_value(j_element), "code")) { authorization_type_enabled = 1; } else if (authorization_type & GLEWLWYD_AUTHORIZATION_TYPE_TOKEN_FLAG && 0 == o_strcmp(json_string_value(j_element), "token")) { authorization_type_enabled = 1; } else if (authorization_type & GLEWLWYD_AUTHORIZATION_TYPE_ID_TOKEN_FLAG && 0 == o_strcmp(json_string_value(j_element), "id_token")) { authorization_type_enabled = 1; } else if (authorization_type & GLEWLWYD_AUTHORIZATION_TYPE_NONE_FLAG && 0 == o_strcmp(json_string_value(j_element), "none")) { authorization_type_enabled = 1; } else if (authorization_type & GLEWLWYD_AUTHORIZATION_TYPE_REFRESH_TOKEN_FLAG && 0 == o_strcmp(json_string_value(j_element), "refresh_token")) { authorization_type_enabled = 1; uri_found = 1; // bypass redirect_uri check for client credentials since it's not needed } else if (authorization_type & GLEWLWYD_AUTHORIZATION_TYPE_CLIENT_CREDENTIALS_FLAG && 0 == o_strcmp(json_string_value(j_element), "client_credentials")) { authorization_type_enabled = 1; uri_found = 1; // bypass redirect_uri check for client credentials since it's not needed } else if (authorization_type & GLEWLWYD_AUTHORIZATION_TYPE_RESOURCE_OWNER_PASSWORD_CREDENTIALS_FLAG && 0 == o_strcmp(json_string_value(j_element), "password")) { authorization_type_enabled = 1; uri_found = 1; // bypass redirect_uri check for client credentials since it's not needed } else if (authorization_type & GLEWLWYD_AUTHORIZATION_TYPE_DELETE_TOKEN_FLAG && 0 == o_strcmp(json_string_value(j_element), "delete_token")) { authorization_type_enabled = 1; uri_found = 1; // bypass redirect_uri check for client credentials since it's not needed } } if (!uri_found) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_client_valid_without_secret - oidc - Error, redirect_uri '%s' is invalid for the client '%s', origin: %s", redirect_uri, client_id, ip_source); } if (!authorization_type_enabled) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_client_valid_without_secret - oidc - Error, authorization type %d is not enabled for the client '%s', origin: %s", authorization_type, client_id, ip_source); } if (uri_found && authorization_type_enabled) { j_return = json_pack("{sisO}", "result", G_OK, "client", json_object_get(j_client, "client")); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "check_client_valid_without_secret - oidc - Error, client '%s' is invalid, origin: %s", client_id, ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } json_decref(j_client); return j_return; } static int is_client_auth_method_allowed(json_t * j_client, int client_auth_method) { int ret = 0; if (json_string_length(json_object_get(j_client, "token_endpoint_auth_method")) || json_array_size(json_object_get(j_client, "token_endpoint_auth_method"))) { switch (client_auth_method) { case GLEWLWYD_CLIENT_AUTH_METHOD_NONE: ret = 1; break; case GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_POST: if (json_is_array(json_object_get(j_client, "token_endpoint_auth_method")) && json_array_has_string(json_object_get(j_client, "token_endpoint_auth_method"), "client_secret_post")) { ret = 1; } if (json_is_string(json_object_get(j_client, "token_endpoint_auth_method")) && 0 == o_strcmp("client_secret_post", json_string_value(json_object_get(j_client, "token_endpoint_auth_method")))) { ret = 1; } break; case GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_BASIC: if (json_is_array(json_object_get(j_client, "token_endpoint_auth_method")) && json_array_has_string(json_object_get(j_client, "token_endpoint_auth_method"), "client_secret_basic")) { ret = 1; } if (json_is_string(json_object_get(j_client, "token_endpoint_auth_method")) && 0 == o_strcmp("client_secret_basic", json_string_value(json_object_get(j_client, "token_endpoint_auth_method")))) { ret = 1; } break; case GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_JWT: if (json_is_array(json_object_get(j_client, "token_endpoint_auth_method")) && json_array_has_string(json_object_get(j_client, "token_endpoint_auth_method"), "client_secret_jwt")) { ret = 1; } if (json_is_string(json_object_get(j_client, "token_endpoint_auth_method")) && 0 == o_strcmp("client_secret_jwt", json_string_value(json_object_get(j_client, "token_endpoint_auth_method")))) { ret = 1; } break; case GLEWLWYD_CLIENT_AUTH_METHOD_PRIVATE_KEY_JWT: if (json_is_array(json_object_get(j_client, "token_endpoint_auth_method")) && json_array_has_string(json_object_get(j_client, "token_endpoint_auth_method"), "private_key_jwt")) { ret = 1; } if (json_is_string(json_object_get(j_client, "token_endpoint_auth_method")) && 0 == o_strcmp("private_key_jwt", json_string_value(json_object_get(j_client, "token_endpoint_auth_method")))) { ret = 1; } break; case GLEWLWYD_CLIENT_AUTH_METHOD_TLS: if (json_is_array(json_object_get(j_client, "token_endpoint_auth_method")) && json_array_has_string(json_object_get(j_client, "token_endpoint_auth_method"), "tls_client_auth")) { ret = 1; } if (json_is_string(json_object_get(j_client, "token_endpoint_auth_method")) && 0 == o_strcmp("tls_client_auth", json_string_value(json_object_get(j_client, "token_endpoint_auth_method")))) { ret = 1; } break; case GLEWLWYD_CLIENT_AUTH_METHOD_SELF_SIGNED_TLS: if (json_is_array(json_object_get(j_client, "token_endpoint_auth_method")) && json_array_has_string(json_object_get(j_client, "token_endpoint_auth_method"), "self_signed_tls_client_auth")) { ret = 1; } if (json_is_string(json_object_get(j_client, "token_endpoint_auth_method")) && 0 == o_strcmp("self_signed_tls_client_auth", json_string_value(json_object_get(j_client, "token_endpoint_auth_method")))) { ret = 1; } break; default: ret = 0; break; } } else { ret = 1; } return ret; } /** * Verify if a client is valid */ static json_t * check_client_valid(struct _oidc_config * config, const char * client_id, const char * client_secret, const char * redirect_uri, unsigned short authorization_type, int implicit_flow, const char * ip_source) { json_t * j_client, * j_return; int uri_found = 0, authorization_type_enabled; const char * error_description = NULL; if (client_secret != NULL) { j_client = config->glewlwyd_config->glewlwyd_callback_check_client_valid(config->glewlwyd_config, client_id, client_secret); } else { j_client = config->glewlwyd_config->glewlwyd_plugin_callback_get_client(config->glewlwyd_config, client_id); } if (check_result_value(j_client, G_OK)) { if (!implicit_flow && client_secret == NULL && json_object_get(json_object_get(j_client, "client"), "confidential") == json_true()) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_client_valid - oidc - Error, confidential client must be authentified with its password, origin: %s", ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else { if (redirect_uri != NULL) { uri_found = json_array_has_string(json_object_get(json_object_get(j_client, "client"), "redirect_uri"), redirect_uri); } else { uri_found = 1; } authorization_type_enabled = 1; if (!authorization_type) { authorization_type_enabled = 0; } if (authorization_type & GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE_FLAG) { if (!json_array_has_string(json_object_get(json_object_get(j_client, "client"), "authorization_type"), "code")) { authorization_type_enabled = 0; } } else if (authorization_type & GLEWLWYD_AUTHORIZATION_TYPE_TOKEN_FLAG) { if (!json_array_has_string(json_object_get(json_object_get(j_client, "client"), "authorization_type"), "token")) { authorization_type_enabled = 0; } } else if (authorization_type & GLEWLWYD_AUTHORIZATION_TYPE_ID_TOKEN_FLAG) { if (!json_array_has_string(json_object_get(json_object_get(j_client, "client"), "authorization_type"), "id_token")) { authorization_type_enabled = 0; } } else if (authorization_type & GLEWLWYD_AUTHORIZATION_TYPE_NONE_FLAG) { if (!json_array_has_string(json_object_get(json_object_get(j_client, "client"), "authorization_type"), "none")) { authorization_type_enabled = 0; } } else if (authorization_type & GLEWLWYD_AUTHORIZATION_TYPE_REFRESH_TOKEN_FLAG) { if (!json_array_has_string(json_object_get(json_object_get(j_client, "client"), "authorization_type"), "refresh_token")) { authorization_type_enabled = 0; } uri_found = 1; // bypass redirect_uri check for client credentials since it's not needed } else if (authorization_type & GLEWLWYD_AUTHORIZATION_TYPE_CLIENT_CREDENTIALS_FLAG) { if (!json_array_has_string(json_object_get(json_object_get(j_client, "client"), "authorization_type"), "client_credentials")) { authorization_type_enabled = 0; } uri_found = 1; // bypass redirect_uri check for client credentials since it's not needed } else if (authorization_type & GLEWLWYD_AUTHORIZATION_TYPE_RESOURCE_OWNER_PASSWORD_CREDENTIALS_FLAG) { if (!json_array_has_string(json_object_get(json_object_get(j_client, "client"), "authorization_type"), "password")) { authorization_type_enabled = 0; } uri_found = 1; // bypass redirect_uri check for client credentials since it's not needed } else if (authorization_type & GLEWLWYD_AUTHORIZATION_TYPE_DELETE_TOKEN_FLAG) { if (!json_array_has_string(json_object_get(json_object_get(j_client, "client"), "authorization_type"), "delete_token")) { authorization_type_enabled = 0; } uri_found = 1; // bypass redirect_uri check for client credentials since it's not needed } else if (authorization_type & GLEWLWYD_AUTHORIZATION_TYPE_DEVICE_AUTHORIZATION_FLAG) { if (!json_array_has_string(json_object_get(json_object_get(j_client, "client"), "authorization_type"), "device_authorization")) { authorization_type_enabled = 0; } uri_found = 1; // bypass redirect_uri check for client credentials since it's not needed } else if (authorization_type & GLEWLWYD_AUTHORIZATION_TYPE_CIBA_FLAG) { if (!json_array_has_string(json_object_get(json_object_get(j_client, "client"), "authorization_type"), "urn:openid:params:grant-type:ciba")) { authorization_type_enabled = 0; } uri_found = 1; // bypass redirect_uri check for client credentials since it's not needed } if (!uri_found) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_client_valid - oidc - Error, redirect_uri '%s' is invalid for the client '%s', origin: %s", redirect_uri, client_id, ip_source); error_description = "redirect_uri invalid"; } if (!authorization_type_enabled) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_client_valid - oidc - Error, authorization type %d is not enabled for the client '%s', origin: %s", authorization_type, client_id, ip_source); error_description = "authorization type invalid"; } if (uri_found && authorization_type_enabled) { j_return = json_pack("{sisO}", "result", G_OK, "client", json_object_get(j_client, "client")); } else { j_return = json_pack("{siss*}", "result", G_ERROR_PARAM, "error_description", error_description); } } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "check_client_valid - oidc - Error, client '%s' is invalid, origin: %s", client_id, ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } json_decref(j_client); return j_return; } /** * builds the amr list based on the code */ static int set_amr_list_for_code(struct _oidc_config * config, json_int_t gpoc_id, json_t * j_amr) { json_t * j_query, * j_element = NULL; int ret; size_t index = 0; if (j_amr != NULL) { if (json_array_size(j_amr)) { j_query = json_pack("{sss[]}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CODE_SHEME, "values"); if (j_query != NULL) { json_array_foreach(j_amr, index, j_element) { json_array_append_new(json_object_get(j_query, "values"), json_pack("{sIsO}", "gpoc_id", gpoc_id, "gpoch_scheme_module", j_element)); } if (h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL) == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_amr_list_for_code - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } json_decref(j_query); } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_amr_list_for_code - Error allocating resources for j_query"); ret = G_ERROR_MEMORY; } } else { j_query = json_pack("{sss{sIss}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CODE_SHEME, "values", "gpoc_id", gpoc_id, "gpoch_scheme_module", "session"); if (h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL) == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_amr_list_for_code - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } json_decref(j_query); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_amr_list_for_code - Error param %s", json_dumps(j_amr, JSON_ENCODE_ANY)); ret = G_ERROR_PARAM; } return ret; } /** * Builds an authorization code from the given parameters * Store a signature of the authorization code in the database */ static json_t * generate_authorization_code(struct _oidc_config * config, const char * username, const char * client_id, const char * scope_list, const char * redirect_uri, const char * issued_for, const char * user_agent, const char * nonce, const char * resource, json_t * j_amr, json_t * j_claims, int auth_type, const char * code_challenge, json_t * j_authorization_details, const char * s_hash, const char * sid) { char * code = NULL, * code_hash = NULL, * expiration_clause, ** scope_array = NULL, * str_claims = NULL, * str_authorization_details = NULL; json_t * j_query, * j_code_id, * j_return; int res, i; time_t now; if (pthread_mutex_lock(&config->insert_lock)) { y_log_message(Y_LOG_LEVEL_ERROR, "generate_authorization_code - oidc - Error pthread_mutex_lock"); j_return = json_pack("{si}", "result", G_ERROR); } else { if ((code = o_malloc(33*sizeof(char))) != NULL) { if (rand_string_nonce(code, 32) != NULL) { code_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, code); if (code_hash != NULL) { if (j_claims != NULL) { str_claims = json_dumps(j_claims, JSON_COMPACT); if (str_claims == NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "generate_authorization_code - oidc - Error dumping claims"); } } time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expiration_clause = msprintf("FROM_UNIXTIME(%u)", (now + (unsigned int)config->code_duration )); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expiration_clause = msprintf("TO_TIMESTAMP(%u)", (now + (unsigned int)config->code_duration )); } else { // HOEL_DB_TYPE_SQLITE expiration_clause = msprintf("%u", (now + (unsigned int)config->code_duration )); } if (j_authorization_details != NULL) { str_authorization_details = json_dumps(j_authorization_details, JSON_COMPACT); } j_query = json_pack("{sss{ss ss ss ss ss ss ss ss ss? ss ss? si s{ss} ss? ss? ss?}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CODE, "values", "gpoc_plugin_name", config->name, "gpoc_username", username, "gpoc_client_id", client_id, "gpoc_redirect_uri", redirect_uri, "gpoc_code_hash", code_hash, "gpoc_issued_for", issued_for, "gpoc_user_agent", user_agent!=NULL?user_agent:"", "gpoc_nonce", nonce!=NULL?nonce:"", "gpoc_resource", resource, "gpoc_claims_request", str_claims!=NULL?str_claims:"", "gpoc_authorization_details", str_authorization_details, "gpoc_authorization_type", auth_type, "gpoc_expires_at", "raw", expiration_clause, "gpoc_code_challenge", code_challenge, "gpoc_s_hash", s_hash, "gpoc_sid", sid); o_free(expiration_clause); o_free(str_claims); o_free(str_authorization_details); res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "generate_authorization_code - oidc - Error executing j_query (1)"); j_return = json_pack("{si}", "result", G_ERROR_DB); } else { if (scope_list != NULL) { j_code_id = h_last_insert_id(config->glewlwyd_config->glewlwyd_config->conn); if (j_code_id != NULL) { j_query = json_pack("{sss[]}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CODE_SCOPE, "values"); if (split_string(scope_list, " ", &scope_array) > 0) { for (i=0; scope_array[i] != NULL; i++) { json_array_append_new(json_object_get(j_query, "values"), json_pack("{sOss}", "gpoc_id", j_code_id, "gpocs_scope", scope_array[i])); } res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_return = json_pack("{sisssO}", "result", G_OK, "code", code, "gpoc_id", j_code_id); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_authorization_code - oidc - Error executing j_query (2)"); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_authorization_code - oidc - Error split_string"); j_return = json_pack("{si}", "result", G_ERROR); } free_string_array(scope_array); if (set_amr_list_for_code(config, json_integer_value(j_code_id), j_amr) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "generate_authorization_code - oidc - Error set_amr_list_for_code"); } json_decref(j_code_id); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_authorization_code - oidc - Error h_last_insert_id"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_authorization_code - oidc - scope_list is empty"); j_return = json_pack("{si}", "result", G_ERROR); } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_authorization_code - oidc - Error glewlwyd_callback_generate_hash"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(code_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_authorization_code - oidc - Error rand_string"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(code); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_authorization_code - oidc - Error allocating resources for code"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } pthread_mutex_unlock(&config->insert_lock); } return j_return; } /** * * Generates a query string based on url and post parameters of a request * Returned value must be o_free'd after use * */ static char * generate_query_parameters(struct _u_map * map) { char * query = NULL, * param, * value; const char ** keys; int i; if (map == NULL) { query = o_strdup(""); } else { keys = u_map_enum_keys(map); for (i=0; keys[i] != NULL; i++) { if (u_map_get(map, keys[i]) != NULL) { value = ulfius_url_encode((char *)u_map_get(map, keys[i])); param = msprintf("%s=%s", keys[i], value); o_free(value); if (query == NULL) { query = o_strdup(param); } else { query = mstrcatf(query, "&%s", param); } o_free(param); } else { if (query == NULL) { query = o_strdup(keys[i]); } else { query = mstrcatf(query, "&%s", keys[i]); } } } if (query == NULL) { query = o_strdup(""); } } return query; } /** * Return the login url based on the curret context */ static char * get_login_url(struct _oidc_config * config, const struct _u_request * request, const char * url, const char * client_id, const char * scope_list, struct _u_map * additional_parameters) { char * plugin_url = config->glewlwyd_config->glewlwyd_callback_get_plugin_external_url(config->glewlwyd_config, json_string_value(json_object_get(config->j_params, "name"))), * url_params = generate_query_parameters(get_map(request)), * url_callback = msprintf("%s/%s%s%s", plugin_url, url, o_strlen(url_params)?"?":"", url_params), * login_url = config->glewlwyd_config->glewlwyd_callback_get_login_url(config->glewlwyd_config, client_id, scope_list, url_callback, additional_parameters); o_free(plugin_url); o_free(url_params); o_free(url_callback); return login_url; } /** * return the scope parameters if set in the parameters */ static json_t * get_scope_parameters(struct _oidc_config * config, const char * scope) { json_t * j_element = NULL, * j_return = NULL; size_t index = 0; json_array_foreach(json_object_get(config->j_params, "scope"), index, j_element) { if (0 == o_strcmp(scope, json_string_value(json_object_get(j_element, "name")))) { j_return = json_incref(j_element); } } return j_return; } /** * disable an authoriation code */ static int disable_authorization_code(struct _oidc_config * config, json_int_t gpoc_id) { json_t * j_query; int res; j_query = json_pack("{sss{si}s{sssI}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CODE, "set", "gpoc_enabled", 0, "where", "gpoc_plugin_name", config->name, "gpoc_id", gpoc_id); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { return G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "disable_authorization_code - oidc - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); return G_ERROR_DB; } } /** * return the amr list based on the code */ static json_t * get_amr_list_from_code(struct _oidc_config * config, json_int_t gpoc_id) { json_t * j_query, * j_result, * j_return, * j_element = NULL; int ret; size_t index = 0; j_query = json_pack("{sss[s]s{sI}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CODE_SHEME, "columns", "gpoch_scheme_module", "where", "gpoc_id", gpoc_id); ret = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (ret == H_OK) { if (json_array_size(j_result)) { j_return = json_pack("{sis[]}", "result", G_OK, "amr"); if (j_return != NULL) { json_array_foreach(j_result, index, j_element) { json_array_append(json_object_get(j_return, "amr"), json_object_get(j_element, "gpoch_scheme_module")); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_amr_list_from_code - Error allocating resources for j_return"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_amr_list_from_code - Error executing query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } /** * Characters allowed according to RFC 7636 * [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~" */ static int is_pkce_char_valid(const char * code_challenge) { size_t i; if (o_strlen(code_challenge) >= 43 && o_strlen(code_challenge) <= 128) { for (i=0; code_challenge[i] != '\0'; i++) { if (code_challenge[i] == 0x2d || code_challenge[i] == 0x2e || code_challenge[i] == 0x5f || code_challenge[i] == 0x7e || (code_challenge[i] >= 0x30 && code_challenge[i] <= 0x39) || (code_challenge[i] >= 0x41 && code_challenge[i] <= 0x5a) || (code_challenge[i] >= 0x61 && code_challenge[i] <= 0x7a)) { continue; } else { return 0; } } return 1; } else { return 0; } } static int validate_code_challenge(json_t * j_result_code, const char * code_verifier) { int ret; unsigned char code_verifier_hash[32] = {0}, code_verifier_hash_b64[64] = {0}; size_t code_verifier_hash_len = 32, code_verifier_hash_b64_len = 0; gnutls_datum_t key_data; if (json_string_length(json_object_get(j_result_code, "code_challenge"))) { if (is_pkce_char_valid(code_verifier)) { if (0 == o_strncmp(GLEWLWYD_CODE_CHALLENGE_S256_PREFIX, json_string_value(json_object_get(j_result_code, "code_challenge")), o_strlen(GLEWLWYD_CODE_CHALLENGE_S256_PREFIX))) { key_data.data = (unsigned char *)code_verifier; key_data.size = o_strlen(code_verifier); if (gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, code_verifier_hash, &code_verifier_hash_len) == GNUTLS_E_SUCCESS) { if (o_base64url_encode(code_verifier_hash, code_verifier_hash_len, code_verifier_hash_b64, &code_verifier_hash_b64_len)) { code_verifier_hash_b64[code_verifier_hash_b64_len] = '\0'; if (0 == o_strcmp(json_string_value(json_object_get(j_result_code, "code_challenge"))+o_strlen(GLEWLWYD_CODE_CHALLENGE_S256_PREFIX), (const char *)code_verifier_hash_b64)) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_code_challenge - Invalid code_challenge value"); ret = G_ERROR_UNAUTHORIZED; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_code_challenge - Error o_base64url_encode"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_code_challenge - Error gnutls_fingerprint"); ret = G_ERROR; } } else { if (0 == o_strcmp(json_string_value(json_object_get(j_result_code, "code_challenge")), code_verifier)) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_code_challenge - Invalid code_challenge value"); ret = G_ERROR_PARAM; } } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_code_challenge - Invalid code_challenge character set"); ret = G_ERROR_PARAM; } } else { ret = G_OK; } return ret; } static int is_code_challenge_valid(struct _oidc_config * config, const char * scope, const char * code_challenge, const char * code_challenge_method, char * code_challenge_stored, int client_confidential) { int ret; char ** scope_list = NULL; size_t index = 0; json_t * j_scope = NULL; if (o_strlen(code_challenge)) { if (json_object_get(config->j_params, "pkce-allowed") == json_true()) { if (!o_strlen(code_challenge_method) || 0 == o_strcmp("plain", code_challenge_method)) { if (json_object_get(config->j_params, "pkce-method-plain-allowed") == json_true()) { if (is_pkce_char_valid(code_challenge)) { o_strcpy(code_challenge_stored, code_challenge); ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc is_code_challenge_valid - pkce has invalid characters"); ret = G_ERROR_PARAM; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc is_code_challenge_valid - pkce plain not allowed"); ret = G_ERROR_PARAM; } } else if (0 == o_strcmp("S256", code_challenge_method)) { if (o_strlen(code_challenge) == 43) { o_strcpy(code_challenge_stored, GLEWLWYD_CODE_CHALLENGE_S256_PREFIX); o_strcpy(code_challenge_stored + o_strlen(GLEWLWYD_CODE_CHALLENGE_S256_PREFIX), code_challenge); ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc is_code_challenge_valid - pkce code challenge has invalid length"); ret = G_ERROR_PARAM; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc is_code_challenge_valid - pkce invalid method"); ret = G_ERROR_PARAM; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc is_code_challenge_valid - pkce not allowed"); ret = G_ERROR_PARAM; } } else { if (json_object_get(config->j_params, "pkce-required") == json_true() || (!client_confidential && json_object_get(config->j_params, "pkce-required-public-client") == json_true())) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc is_code_challenge_valid - pkce required"); ret = G_ERROR_PARAM; } else if (json_array_size(json_object_get(config->j_params, "pkce-scopes"))) { if (split_string(scope, " ", &scope_list)) { ret = G_OK; json_array_foreach(json_object_get(config->j_params, "pkce-scopes"), index, j_scope) { if (string_array_has_value((const char **)scope_list, json_string_value(j_scope))) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc is_code_challenge_valid - pkce required to use with scope %s", json_string_value(j_scope)); ret = G_ERROR_PARAM; } } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc is_code_challenge_valid - Error split_string"); ret = G_ERROR; } free_string_array(scope_list); } else { // No pkce ret = G_OK; } } return ret; } static json_t * get_refresh_token_duration_rolling(struct _oidc_config * config, const char * scope_list) { json_t * j_return, * j_element = NULL; char ** scope_array = NULL; size_t i, index = 0; json_int_t maximum_duration = config->refresh_token_duration, maximum_duration_override = -1; int rolling_refresh = config->refresh_token_rolling, rolling_refresh_override = -1; if (split_string(scope_list, " ", &scope_array) > 0) { json_array_foreach(json_object_get(config->j_params, "scope"), index, j_element) { for (i=0; scope_array[i]!=NULL; i++) { if (0 == o_strcmp(json_string_value(json_object_get(j_element, "name")), scope_array[i])) { if (json_integer_value(json_object_get(j_element, "refresh-token-duration")) && (json_integer_value(json_object_get(j_element, "refresh-token-duration")) < maximum_duration_override || maximum_duration_override == -1)) { maximum_duration_override = json_integer_value(json_object_get(j_element, "refresh-token-duration")); } if (json_object_get(j_element, "refresh-token-rolling") != NULL && rolling_refresh_override != 0) { rolling_refresh_override = json_object_get(j_element, "refresh-token-rolling")==json_true(); } } } } free_string_array(scope_array); if (maximum_duration_override != -1) { maximum_duration = maximum_duration_override; } if (rolling_refresh_override != -1) { rolling_refresh = rolling_refresh_override; } j_return = json_pack("{sis{sosI}}", "result", G_OK, "refresh-token", "refresh-token-rolling", rolling_refresh?json_true():json_false(), "refresh-token-duration", maximum_duration); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_refresh_token_duration_rolling - Error split_string"); j_return = json_pack("{si}", "result", G_ERROR); } return j_return; } static int revoke_tokens_from_code(struct _oidc_config * config, json_int_t gpoc_id, const char * ip_source) { int ret, res; char * query; json_t * j_result, * j_result_r, * j_element = NULL; size_t index = 0; query = msprintf("SELECT gpoa_jti AS jti, gpoa_client_id AS client_id FROM " GLEWLWYD_PLUGIN_OIDC_TABLE_ACCESS_TOKEN " WHERE gpor_id IN (SELECT gpor_id FROM " GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN " WHERE gpoc_id=%" JSON_INTEGER_FORMAT ") AND gpoa_enabled=1", gpoc_id); res = h_execute_query_json(config->glewlwyd_config->glewlwyd_config->conn, query, &j_result); o_free(query); if (res == H_OK) { json_array_foreach(j_result, index, j_element) { y_log_message(Y_LOG_LEVEL_INFO, "Event oidc - Plugin '%s' - Access token jti '%s' generated for client '%s' revoked, origin: %s", config->name, json_string_value(json_object_get(j_element, "jti")), json_string_value(json_object_get(j_element, "client_id")), ip_source); } json_decref(j_result); query = msprintf("SELECT gpor_client_id AS client_id FROM " GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN " WHERE gpoc_id=%" JSON_INTEGER_FORMAT " AND gpor_enabled=1", gpoc_id); res = h_execute_query_json(config->glewlwyd_config->glewlwyd_config->conn, query, &j_result_r); o_free(query); if (res == H_OK) { if (json_array_size(j_result_r)) { y_log_message(Y_LOG_LEVEL_INFO, "Event oidc - Plugin '%s' - Refresh token generated for client '%s' revoked, origin: %s", config->name, json_string_value(json_object_get(json_array_get(j_result_r, 0), "client_id")), ip_source); } json_decref(j_result_r); query = msprintf("UPDATE " GLEWLWYD_PLUGIN_OIDC_TABLE_ACCESS_TOKEN " SET gpoa_enabled='0' WHERE gpor_id IN (SELECT gpor_id FROM " GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN " WHERE gpoc_id=%" JSON_INTEGER_FORMAT ")", gpoc_id); res = h_execute_query(config->glewlwyd_config->glewlwyd_config->conn, query, NULL, H_OPTION_EXEC); o_free(query); if (res == H_OK) { query = msprintf("UPDATE " GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN " SET gpor_enabled='0' WHERE gpoc_id=%" JSON_INTEGER_FORMAT, gpoc_id); res = h_execute_query(config->glewlwyd_config->glewlwyd_config->conn, query, NULL, H_OPTION_EXEC); o_free(query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc revoke_tokens_from_code - Error executing query (4)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc revoke_tokens_from_code - Error executing query (3)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc revoke_tokens_from_code - Error executing query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc revoke_tokens_from_code - Error executing query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } /** * verify that the auth code is valid */ static json_t * validate_authorization_code(struct _oidc_config * config, const char * code, const char * client_id, const char * redirect_uri, const char * code_verifier, const char * ip_source) { char * code_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, code), * expiration_clause = NULL, * scope_list = NULL, * tmp; json_t * j_query, * j_result = NULL, * j_result_scope = NULL, * j_return, * j_element = NULL, * j_scope_param; int res, has_scope_openid = 0; size_t index = 0; json_int_t maximum_duration = config->refresh_token_duration, maximum_duration_override = -1; int rolling_refresh = config->refresh_token_rolling, rolling_refresh_override = -1; if (code_hash != NULL) { if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expiration_clause = o_strdup("> NOW()"); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expiration_clause = o_strdup("> NOW()"); } else { // HOEL_DB_TYPE_SQLITE expiration_clause = o_strdup("> (strftime('%s','now'))"); } j_query = json_pack("{sss[ssssssssss]s{sssssssss{ssss}}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CODE, "columns", "gpoc_username AS username", "gpoc_nonce AS nonce", "gpoc_claims_request AS claims_request", "gpoc_id", "gpoc_code_challenge AS code_challenge", "gpoc_resource AS resource", "gpoc_enabled AS enabled", "gpoc_authorization_details", "gpoc_s_hash AS s_hash", "gpoc_sid AS sid", "where", "gpoc_plugin_name", config->name, "gpoc_client_id", client_id, "gpoc_redirect_uri", redirect_uri, "gpoc_code_hash", code_hash, "gpoc_expires_at", "operator", "raw", "value", expiration_clause); o_free(expiration_clause); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { if (json_integer_value(json_object_get(json_array_get(j_result, 0), "enabled"))) { if (json_object_get(json_array_get(j_result, 0), "gpoc_authorization_details") != json_null()) { json_object_set_new(json_array_get(j_result, 0), "authorization_details", json_loads(json_string_value(json_object_get(json_array_get(j_result, 0), "gpoc_authorization_details")), JSON_DECODE_ANY, NULL)); } json_object_del(json_array_get(j_result, 0), "gpoc_authorization_details"); if ((res = validate_code_challenge(json_array_get(j_result, 0), code_verifier)) == G_OK) { j_query = json_pack("{sss[s]s{sO}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CODE_SCOPE, "columns", "gpocs_scope AS name", "where", "gpoc_id", json_object_get(json_array_get(j_result, 0), "gpoc_id")); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result_scope, NULL); json_decref(j_query); if (res == H_OK && json_array_size(j_result_scope) > 0) { if (!json_object_set_new(json_array_get(j_result, 0), "scope", json_array())) { json_array_foreach(j_result_scope, index, j_element) { if (0 == o_strcmp("openid", json_string_value(json_object_get(j_element, "name")))) { has_scope_openid = 1; } if (scope_list == NULL) { scope_list = o_strdup(json_string_value(json_object_get(j_element, "name"))); } else { tmp = msprintf("%s %s", scope_list, json_string_value(json_object_get(j_element, "name"))); o_free(scope_list); scope_list = tmp; } if ((j_scope_param = get_scope_parameters(config, json_string_value(json_object_get(j_element, "name")))) != NULL) { json_object_update(j_element, j_scope_param); json_decref(j_scope_param); } if (json_object_get(j_element, "refresh-token-rolling") != NULL && rolling_refresh_override != 0) { rolling_refresh_override = json_object_get(j_element, "refresh-token-rolling")==json_true(); } if (json_integer_value(json_object_get(j_element, "refresh-token-duration")) && (json_integer_value(json_object_get(j_element, "refresh-token-duration")) < maximum_duration_override || maximum_duration_override == -1)) { maximum_duration_override = json_integer_value(json_object_get(j_element, "refresh-token-duration")); } json_array_append(json_object_get(json_array_get(j_result, 0), "scope"), j_element); } if (rolling_refresh_override > -1) { rolling_refresh = rolling_refresh_override; } if (maximum_duration_override > -1) { maximum_duration = maximum_duration_override; } json_object_set_new(json_array_get(j_result, 0), "scope_list", json_string(scope_list)); json_object_set_new(json_array_get(j_result, 0), "refresh-token-rolling", rolling_refresh?json_true():json_false()); json_object_set_new(json_array_get(j_result, 0), "refresh-token-duration", json_integer(maximum_duration)); json_object_set(json_array_get(j_result, 0), "has-scope-openid", has_scope_openid?json_true():json_false()); j_return = json_pack("{sisO}", "result", G_OK, "code", json_array_get(j_result, 0)); o_free(scope_list); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_authorization_code - Error allocating resources for json_array()"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_authorization_code - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else if (res == G_ERROR_UNAUTHORIZED) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_authorization_code - validate_code_challenge invalid code_verifier"); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else if (res == G_ERROR_PARAM) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_authorization_code - validate_code_challenge invalid parameter"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_authorization_code - Error validate_code_challenge"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result_scope); } else { if (json_true() == json_object_get(config->j_params, "auth-type-code-revoke-replayed")) { if (revoke_tokens_from_code(config, json_integer_value(json_object_get(json_array_get(j_result, 0), "gpoc_id")), ip_source) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_authorization_code - Error revoke_tokens_from_code"); } } j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_authorization_code - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_authorization_code - Error glewlwyd_callback_generate_hash"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(code_hash); return j_return; } /** * Verify that the session is valid based on the client_id and the scope requested * The scope list must be at least partially authenticated and granted for the client */ static json_t * validate_session_client_scope(struct _oidc_config * config, const struct _u_request * request, const char * client_id, const char * scope) { json_t * j_session, * j_grant, * j_return, * j_scope_session, * j_scope_grant = NULL, * j_group = NULL, * j_scheme; const char * scope_session, * group = NULL; char * scope_filtered = NULL, * tmp; size_t index = 0; json_int_t scopes_authorized = 0, scopes_granted = 0, group_allowed; j_session = config->glewlwyd_config->glewlwyd_callback_check_session_valid(config->glewlwyd_config, request, scope); if (check_result_value(j_session, G_OK)) { j_grant = config->glewlwyd_config->glewlwyd_callback_get_client_granted_scopes(config->glewlwyd_config, client_id, json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username")), scope); if (check_result_value(j_grant, G_OK)) { if (json_array_size(json_object_get(json_object_get(j_grant, "grant"), "scope"))) { // Count and store the number of granted scopes, we assume the scope openid is granted json_array_foreach(json_object_get(json_object_get(j_grant, "grant"), "scope"), index, j_scope_grant) { scopes_granted += json_object_get(j_scope_grant, "granted")==json_true()||(0 == o_strcmp("openid", json_string_value(json_object_get(j_scope_grant, "name")))?1:0); } json_object_set_new(json_object_get(j_session, "session"), "scopes_granted", json_integer(scopes_granted)); json_object_set_new(json_object_get(j_session, "session"), "amr", json_array()); json_object_foreach(json_object_get(json_object_get(j_session, "session"), "scope"), scope_session, j_scope_session) { // Evaluate if the scope is granted for the client, we assume the scope openid is granted json_array_foreach(json_object_get(json_object_get(j_grant, "grant"), "scope"), index, j_scope_grant) { if (0 == o_strcmp("openid", json_string_value(json_object_get(j_scope_grant, "name")))) { json_object_set(j_scope_session, "granted", json_true()); } else if (0 == o_strcmp(scope_session, json_string_value(json_object_get(j_scope_grant, "name")))) { json_object_set(j_scope_session, "granted", json_object_get(j_scope_grant, "granted")); } } // Evaluate if the scope is authorized if (json_object_get(j_scope_session, "available") == json_true()) { if (json_object_get(j_scope_session, "password_required") == json_true() && json_object_get(j_scope_session, "password_authenticated") == json_true()) { if (!json_array_has_string(json_object_get(json_object_get(j_session, "session"), "amr"), "password")) { json_array_append_new(json_object_get(json_object_get(j_session, "session"), "amr"), json_string("password")); } } if (json_object_get(j_scope_session, "password_required") == json_true() && json_object_get(j_scope_session, "password_authenticated") == json_false()) { json_object_set_new(j_scope_session, "authorized", json_false()); } else if ((json_object_get(j_scope_session, "password_required") == json_true() && json_object_get(j_scope_session, "password_authenticated") == json_true()) || json_object_get(j_scope_session, "password_required") == json_false()) { json_object_foreach(json_object_get(j_scope_session, "schemes"), group, j_group) { group_allowed = 0; json_array_foreach(j_group, index, j_scheme) { if (json_object_get(j_scheme, "scheme_authenticated") == json_true()) { if (!json_array_has_string(json_object_get(json_object_get(j_session, "session"), "amr"), json_string_value(json_object_get(j_scheme, "scheme_type")))) { json_array_append(json_object_get(json_object_get(j_session, "session"), "amr"), json_object_get(j_scheme, "scheme_type")); } group_allowed++; } } if (group_allowed < json_integer_value(json_object_get(json_object_get(j_scope_session, "scheme_required"), group))) { json_object_set_new(j_scope_session, "authorized", json_false()); } } if (json_object_get(j_scope_session, "authorized") == NULL) { json_object_set_new(j_scope_session, "authorized", json_true()); scopes_authorized++; if (json_object_get(j_scope_session, "granted") == json_true()) { if (scope_filtered == NULL) { scope_filtered = o_strdup(scope_session); } else { tmp = msprintf("%s %s", scope_filtered, scope_session); o_free(scope_filtered); scope_filtered = tmp; } } } else if (json_object_get(j_scope_session, "granted") == json_true()) { json_object_set_new(json_object_get(j_session, "session"), "authorization_required", json_true()); } } else { json_object_set_new(j_scope_session, "authorized", json_false()); } } else { json_object_set_new(j_scope_session, "authorized", json_false()); } } json_object_set_new(json_object_get(j_session, "session"), "scopes_authorized", json_integer(scopes_authorized)); if (json_object_get(json_object_get(j_session, "session"), "authorization_required") == NULL) { json_object_set_new(json_object_get(j_session, "session"), "authorization_required", json_false()); } if (scope_filtered != NULL) { json_object_set_new(json_object_get(j_session, "session"), "scope_filtered", json_string(scope_filtered)); o_free(scope_filtered); } else { json_object_set_new(json_object_get(j_session, "session"), "scope_filtered", json_string("")); json_object_set_new(json_object_get(j_session, "session"), "authorization_required", json_true()); } if (scopes_authorized && scopes_granted) { j_return = json_pack("{sisO}", "result", G_OK, "session", json_object_get(j_session, "session")); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_session_client_scope - Error glewlwyd_callback_get_client_granted_scopes"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_grant); } else if (check_result_value(j_session, G_ERROR_NOT_FOUND)) { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } else if (check_result_value(j_session, G_ERROR_UNAUTHORIZED)) { j_return = json_pack("{sisO*}", "result", G_ERROR_UNAUTHORIZED, "session", json_object_get(j_session, "session")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_session_client_scope - Error glewlwyd_callback_check_session_valid"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_session); return j_return; } /** * Verify that the refresh token is still valid to get an access token */ static json_t * validate_refresh_token(struct _oidc_config * config, const char * refresh_token) { json_t * j_return, * j_query, * j_result, * j_result_scope, * j_element = NULL; char * token_hash, * expires_at_clause; int res, enabled; size_t index = 0; time_t now; if (refresh_token != NULL) { token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, refresh_token); if (token_hash != NULL) { time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } j_query = json_pack("{sss[ssssssssssssssss]s{sssss{ssss}}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN, "columns", "gpor_id", "gpor_authorization_type AS authorization_type", "gpoc_id", "gpor_username AS username", "gpor_client_id AS client_id", SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpor_issued_at) AS issued_at", "gpor_issued_at AS issued_at", "EXTRACT(EPOCH FROM gpor_issued_at)::integer AS issued_at"), SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpor_expires_at) AS expired_at", "gpor_expires_at AS expired_at", "EXTRACT(EPOCH FROM gpor_expires_at)::integer AS expired_at"), SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpor_last_seen) AS last_seen", "gpor_last_seen AS last_seen", "EXTRACT(EPOCH FROM gpor_last_seen)::integer AS last_seen"), "gpor_duration AS duration", "gpor_rolling_expiration", "gpor_claims_request AS claims_request", "gpor_jti AS jti", "gpor_dpop_jkt AS dpop_jkt", "gpor_resource AS resource", "gpor_authorization_details", "gpor_enabled", "where", "gpor_plugin_name", config->name, "gpor_token_hash", token_hash, "gpor_expires_at", "operator", "raw", "value", expires_at_clause); o_free(expires_at_clause); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result) > 0) { enabled = json_integer_value(json_object_get(json_array_get(j_result, 0), "gpor_enabled")); json_object_set(json_array_get(j_result, 0), "rolling_expiration", json_integer_value(json_object_get(json_array_get(j_result, 0), "gpor_rolling_expiration"))?json_true():json_false()); json_object_del(json_array_get(j_result, 0), "gpor_rolling_expiration"); json_object_del(json_array_get(j_result, 0), "gpor_enabled"); if (json_object_get(json_array_get(j_result, 0), "gpor_authorization_details") != json_null()) { json_object_set_new(json_array_get(j_result, 0), "authorization_details", json_loads(json_string_value(json_object_get(json_array_get(j_result, 0), "gpor_authorization_details")), JSON_DECODE_ANY, NULL)); } json_object_del(json_array_get(j_result, 0), "gpor_authorization_details"); j_query = json_pack("{sss[s]s{sO}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN_SCOPE, "columns", "gpors_scope AS scope", "where", "gpor_id", json_object_get(json_array_get(j_result, 0), "gpor_id")); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result_scope, NULL); if (res == H_OK) { if (!json_object_set_new(json_array_get(j_result, 0), "scope", json_array())) { json_array_foreach(j_result_scope, index, j_element) { json_array_append(json_object_get(json_array_get(j_result, 0), "scope"), json_object_get(j_element, "scope")); } j_return = json_pack("{sisO}", "result", enabled?G_OK:G_ERROR_UNAUTHORIZED, "token", json_array_get(j_result, 0)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_refresh_token - Error json_object_set_new"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result_scope); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_refresh_token - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_query); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_refresh_token - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_refresh_token - Error glewlwyd_callback_generate_hash"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(token_hash); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } /** * get a list of refresh token for a specified user */ static json_t * refresh_token_list_get(struct _oidc_config * config, const char * username, const char * pattern, size_t offset, size_t limit, const char * sort) { json_t * j_query, * j_result, * j_return, * j_element = NULL; int res; size_t index = 0, token_hash_dec_len = 0; char * pattern_escaped, * pattern_clause, * name_escaped = NULL; unsigned char token_hash_dec[128]; j_query = json_pack("{sss[ssssssssss]s{ssss}sisiss}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN, "columns", "gpor_token_hash", "gpor_authorization_type", "gpor_client_id AS client_id", SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpor_issued_at) AS issued_at", "gpor_issued_at AS issued_at", "EXTRACT(EPOCH FROM gpor_issued_at)::integer AS issued_at"), SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpor_expires_at) AS expires_at", "gpor_expires_at AS expires_at", "EXTRACT(EPOCH FROM gpor_expires_at)::integer AS expires_at"), SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpor_last_seen) AS last_seen", "gpor_last_seen AS last_seen", "EXTRACT(EPOCH FROM gpor_last_seen)::integer AS last_seen"), "gpor_rolling_expiration", "gpor_issued_for AS issued_for", "gpor_user_agent AS user_agent", "gpor_enabled", "where", "gpor_plugin_name", config->name, "gpor_username", username, "offset", offset, "limit", limit, "order_by", "gpor_last_seen DESC"); if (sort != NULL) { json_object_set_new(j_query, "order_by", json_string(sort)); } if (pattern != NULL) { name_escaped = h_escape_string_with_quotes(config->glewlwyd_config->glewlwyd_config->conn, config->name); pattern_escaped = h_escape_string_with_quotes(config->glewlwyd_config->glewlwyd_config->conn, pattern); pattern_clause = msprintf("IN (SELECT gpor_id FROM "GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN" WHERE (gpor_user_agent LIKE '%%'||%s||'%%' OR gpor_issued_for LIKE '%%'||%s||'%%') AND gpor_plugin_name=%s)", pattern_escaped, pattern_escaped, name_escaped); json_object_set_new(json_object_get(j_query, "where"), "gpor_id", json_pack("{ssss}", "operator", "raw", "value", pattern_clause)); o_free(pattern_clause); o_free(pattern_escaped); o_free(name_escaped); } res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result, index, j_element) { json_object_set(j_element, "rolling_expiration", (json_integer_value(json_object_get(j_element, "gpor_rolling_expiration"))?json_true():json_false())); json_object_set(j_element, "enabled", (json_integer_value(json_object_get(j_element, "gpor_enabled"))?json_true():json_false())); json_object_del(j_element, "gpor_rolling_expiration"); json_object_del(j_element, "gpor_enabled"); if (o_base64_2_base64url((unsigned char *)json_string_value(json_object_get(j_element, "gpor_token_hash")), json_string_length(json_object_get(j_element, "gpor_token_hash")), token_hash_dec, &token_hash_dec_len)) { json_object_set_new(j_element, "token_hash", json_stringn((char *)token_hash_dec, token_hash_dec_len)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "refresh_token_list_get - Error o_base64_2_base64url"); json_object_set_new(j_element, "token_hash", json_string("error")); } json_object_del(j_element, "gpor_token_hash"); switch(json_integer_value(json_object_get(j_element, "gpor_authorization_type"))) { case GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE: json_object_set_new(j_element, "authorization_type", json_string("code")); break; default: json_object_set_new(j_element, "authorization_type", json_string("unknown")); break; } json_object_del(j_element, "gpor_authorization_type"); } j_return = json_pack("{sisO}", "result", G_OK, "refresh_token", j_result); json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "refresh_token_list_get - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } /** * disable a refresh token based on its signature */ static int refresh_token_disable(struct _oidc_config * config, const char * username, const char * token_hash, const char * ip_source) { json_t * j_query, * j_result, * j_element = NULL; int res, ret = G_OK; unsigned char token_hash_dec[128]; size_t token_hash_dec_len = 0, index = 0; j_query = json_pack("{sss[ss]s{ssss}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN, "columns", "gpor_id", "gpor_enabled", "where", "gpor_plugin_name", config->name, "gpor_username", username); if (token_hash != NULL) { if (o_base64url_2_base64((unsigned char *)token_hash, o_strlen(token_hash), token_hash_dec, &token_hash_dec_len)) { json_object_set_new(json_object_get(j_query, "where"), "gpor_token_hash", json_stringn((const char *)token_hash_dec, token_hash_dec_len)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "refresh_token_disable oidc - Error o_base64url_2_base64"); ret = G_ERROR_PARAM; } } res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { json_array_foreach(j_result, index, j_element) { if (json_integer_value(json_object_get(j_element, "gpor_enabled"))) { j_query = json_pack("{sss{si}s{sssO}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN, "set", "gpor_enabled", 0, "where", "gpor_plugin_name", config->name, "gpor_id", json_object_get(j_element, "gpor_id")); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { if (token_hash != NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "refresh_token_disable - token '[...%s]' disabled, origin: %s", token_hash + (o_strlen(token_hash) - (o_strlen(token_hash)>=8?8:o_strlen(token_hash))), ip_source); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "refresh_token_disable - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else if (token_hash != NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "refresh_token_disable - Error token '[...%s]' already disabled, origin: %s", token_hash + (o_strlen(token_hash) - (o_strlen(token_hash)>=8?8:o_strlen(token_hash))), ip_source); ret = G_ERROR_PARAM; } } } else if (token_hash != NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "refresh_token_disable - Error token '[...%s]' not found, origin: %s", token_hash + (o_strlen(token_hash) - (o_strlen(token_hash)>=8?8:o_strlen(token_hash))), ip_source); ret = G_ERROR_NOT_FOUND; } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "refresh_token_disable - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } if (ret == G_OK && token_hash == NULL) { j_query = json_pack("{sss{si}s{sssi}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_ACCESS_TOKEN, "set", "gpoa_enabled", 0, "where", "gpoa_plugin_name", config->name, "gpoa_enabled", 1); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "refresh_token_disable - Error executing j_query (3)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } if (ret == G_OK && token_hash == NULL) { j_query = json_pack("{sss{si}s{sssi}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_ID_TOKEN, "set", "gpoi_enabled", 0, "where", "gpoi_plugin_name", config->name, "gpoi_enabled", 1); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "refresh_token_disable - Error executing j_query (4)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } return ret; } /** * update settings for a refresh token */ static int update_refresh_token(struct _oidc_config * config, json_int_t gpor_id, json_int_t refresh_token_duration, int disable, time_t now) { json_t * j_query; int res, ret; char * expires_at_clause, * last_seen_clause; if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { last_seen_clause = msprintf("FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { last_seen_clause = msprintf("TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE last_seen_clause = msprintf("%u", (now)); } j_query = json_pack("{sss{s{ss}}s{sssI}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN, "set", "gpor_last_seen", "raw", last_seen_clause, "where", "gpor_plugin_name", config->name, "gpor_id", gpor_id); o_free(last_seen_clause); if (refresh_token_duration) { if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("FROM_UNIXTIME(%u)", (now + (unsigned int)refresh_token_duration)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("TO_TIMESTAMP(%u)", (now + (unsigned int)refresh_token_duration)); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("%u", (now + (unsigned int)refresh_token_duration)); } json_object_set_new(json_object_get(j_query, "set"), "gpor_expires_at", json_pack("{ss}", "raw", expires_at_clause)); o_free(expires_at_clause); } if (disable) { json_object_set_new(json_object_get(j_query, "set"), "gpor_enabled", json_integer(0)); } res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc update_refresh_token - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } /** * Download a request object from an URI */ static char * get_request_from_uri(struct _oidc_config * config, const char * request_uri) { struct _u_request req; struct _u_response resp; char * str_request = NULL; int valid_ct = 1; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("GET"); req.http_url = o_strdup(request_uri); if (json_object_get(config->j_params, "request-uri-allow-https-non-secure") == json_true()) { req.check_server_certificate = 0; } if (ulfius_send_http_request(&req, &resp) != U_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "get_request_from_uri - Error ulfius_send_http_request"); } else if (resp.status == 200) { if (json_object_get(config->j_params, "request-parameter-ietf-strict") == json_true()) { valid_ct = !o_strcmp(u_map_get(resp.map_header, ULFIUS_HTTP_HEADER_CONTENT), "application/oauth-authz-req+jwt") || !o_strcmp(u_map_get(resp.map_header, ULFIUS_HTTP_HEADER_CONTENT), "application/jwt"); } if (valid_ct) { str_request = o_malloc(resp.binary_body_length + 1); if (str_request != NULL) { memcpy(str_request, resp.binary_body, resp.binary_body_length); str_request[resp.binary_body_length] = '\0'; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_request_from_uri - Error allocating resources for str_request"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_request_from_uri - Error invalid content type"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_request_from_uri - Error ulfius_send_http_request response status is %d", resp.status); } ulfius_clean_request(&req); ulfius_clean_response(&resp); return str_request; } static int is_enc_alg_valid(struct _oidc_config * config, json_t * j_client, jwt_t * jwt, int auth_type) { int is_valid = 1; jwa_alg alg = r_jwt_get_enc_alg(jwt); jwa_enc enc = r_jwt_get_enc(jwt); if (alg != R_JWA_ALG_UNKNOWN && json_object_get(config->j_params, "oauth-fapi-allow-restrict-alg") == json_true() && !json_array_has_string(json_object_get(config->j_params, "oauth-fapi-restrict-alg"), r_jwa_alg_to_str(alg))) { is_valid = 0; } else { switch (auth_type) { case GLEWLWYD_AUTH_REQUEST_OBJECT: if (json_object_get(j_client, "request_object_encryption_alg") != NULL && json_object_get(j_client, "request_object_encryption_enc") != NULL) { if (r_str_to_jwa_alg(json_string_value(json_object_get(j_client, "request_object_encryption_alg"))) != alg || r_str_to_jwa_enc(json_string_value(json_object_get(j_client, "request_object_encryption_enc"))) != enc) { y_log_message(Y_LOG_LEVEL_DEBUG, "is_enc_alg_valid - Error request_object_encryption_alg or request_object_encryption_alg invalid for client %s", json_string_value(json_object_get(j_client, "client_id"))); is_valid = 0; } } else if (json_object_get(config->j_params, "request-parameter-ietf-strict") == json_true()) { y_log_message(Y_LOG_LEVEL_DEBUG, "is_sig_alg_valid - Error client %s has no property request_object_encryption_alg or request_object_encryption_enc", json_string_value(json_object_get(j_client, "client_id"))); is_valid = 0; } break; case GLEWLWYD_AUTH_TOKEN_ENDPOINT: if (json_object_get(j_client, "token_endpoint_encryption_alg") != NULL && json_object_get(j_client, "token_endpoint_encryption_enc") != NULL) { if (r_str_to_jwa_alg(json_string_value(json_object_get(j_client, "token_endpoint_encryption_alg"))) != alg || r_str_to_jwa_enc(json_string_value(json_object_get(j_client, "token_endpoint_encryption_enc"))) != enc) { y_log_message(Y_LOG_LEVEL_DEBUG, "is_enc_alg_valid - Error token_endpoint_encryption_alg or token_endpoint_encryption_enc invalid for client %s", json_string_value(json_object_get(j_client, "client_id"))); is_valid = 0; } } else if (json_object_get(config->j_params, "request-parameter-ietf-strict") == json_true()) { y_log_message(Y_LOG_LEVEL_DEBUG, "is_sig_alg_valid - Error client %s has no property token_endpoint_encryption_alg or token_endpoint_encryption_enc", json_string_value(json_object_get(j_client, "client_id"))); is_valid = 0; } break; case GLEWLWYD_AUTH_CIBA: if (json_object_get(j_client, "backchannel_authentication_request_encryption_alg") != NULL && json_object_get(j_client, "backchannel_authentication_request_encryption_enc") != NULL) { if (r_str_to_jwa_alg(json_string_value(json_object_get(j_client, "backchannel_authentication_request_encryption_alg"))) != alg || r_str_to_jwa_enc(json_string_value(json_object_get(j_client, "backchannel_authentication_request_encryption_enc"))) != enc) { y_log_message(Y_LOG_LEVEL_DEBUG, "is_enc_alg_valid - Error backchannel_authentication_request_encryption_alg or backchannel_authentication_request_encryption_enc invalid for client %s", json_string_value(json_object_get(j_client, "client_id"))); is_valid = 0; } } else if (json_object_get(config->j_params, "request-parameter-ietf-strict") == json_true()) { y_log_message(Y_LOG_LEVEL_DEBUG, "is_sig_alg_valid - Error client %s has no property backchannel_authentication_request_encryption_alg or backchannel_authentication_request_encryption_enc", json_string_value(json_object_get(j_client, "client_id"))); is_valid = 0; } break; default: is_valid = 0; break; } } return is_valid; } static int is_sig_alg_valid(struct _oidc_config * config, json_t * j_client, jwa_alg alg, int auth_type) { int is_valid = 1; switch (auth_type) { case GLEWLWYD_AUTH_REQUEST_OBJECT: if (json_object_get(j_client, "request_object_signing_alg") != NULL) { if (r_str_to_jwa_alg(json_string_value(json_object_get(j_client, "request_object_signing_alg"))) != alg) { y_log_message(Y_LOG_LEVEL_DEBUG, "is_sig_alg_valid - Error request_object_signing_alg invalid for client %s", json_string_value(json_object_get(j_client, "client_id"))); is_valid = 0; } } else if (json_object_get(config->j_params, "request-parameter-ietf-strict") == json_true()) { y_log_message(Y_LOG_LEVEL_DEBUG, "is_sig_alg_valid - Error client %s has no property request_object_signing_alg", json_string_value(json_object_get(j_client, "client_id"))); is_valid = 0; } break; case GLEWLWYD_AUTH_TOKEN_ENDPOINT: if (json_object_get(j_client, "token_endpoint_signing_alg") != NULL) { if (r_str_to_jwa_alg(json_string_value(json_object_get(j_client, "token_endpoint_signing_alg"))) == alg) { y_log_message(Y_LOG_LEVEL_DEBUG, "is_sig_alg_valid - Error token_endpoint_signing_alg invalid for client %s", json_string_value(json_object_get(j_client, "client_id"))); is_valid = 0; } } else if (json_object_get(config->j_params, "request-parameter-ietf-strict") == json_true()) { y_log_message(Y_LOG_LEVEL_DEBUG, "is_sig_alg_valid - Error client %s has no property token_endpoint_signing_alg", json_string_value(json_object_get(j_client, "client_id"))); is_valid = 0; } break; case GLEWLWYD_AUTH_CIBA: if (json_object_get(j_client, "backchannel_authentication_request_signing_alg") != NULL) { if (r_str_to_jwa_alg(json_string_value(json_object_get(j_client, "backchannel_authentication_request_signing_alg"))) == alg) { y_log_message(Y_LOG_LEVEL_DEBUG, "is_sig_alg_valid - Error backchannel_authentication_request_signing_alg invalid for client %s", json_string_value(json_object_get(j_client, "client_id"))); is_valid = 0; } } else if (json_object_get(config->j_params, "request-parameter-ietf-strict") == json_true()) { y_log_message(Y_LOG_LEVEL_DEBUG, "is_sig_alg_valid - Error client %s has no property backchannel_authentication_request_signing_alg", json_string_value(json_object_get(j_client, "client_id"))); is_valid = 0; } break; default: is_valid = 0; break; } return is_valid; } static json_t * get_jwk_search_pattern(jwt_t * jwt, jwks_t * jwks) { json_t * j_search = json_object(); jwa_alg alg = r_jwt_get_sign_alg(jwt); jwk_t * jwk; int s_alg = 1; size_t i; if (alg == R_JWA_ALG_HS256 || alg == R_JWA_ALG_HS384 || alg == R_JWA_ALG_HS512) { json_object_set_new(j_search, "kty", json_string("oct")); } else if (alg == R_JWA_ALG_RS256 || alg == R_JWA_ALG_RS384 || alg == R_JWA_ALG_RS512 || alg == R_JWA_ALG_PS256 || alg == R_JWA_ALG_PS384 || alg == R_JWA_ALG_PS512) { json_object_set_new(j_search, "kty", json_string("RSA")); } else if (alg == R_JWA_ALG_ES256 || alg == R_JWA_ALG_ES384 || alg == R_JWA_ALG_ES512 || alg == R_JWA_ALG_ES256K) { json_object_set_new(j_search, "kty", json_string("EC")); } else if (alg == R_JWA_ALG_EDDSA) { json_object_set_new(j_search, "kty", json_string("OKP")); } json_object_set_new(j_search, "use", json_string("sig")); for (i=0; ij_params, "client-pubkey-parameter")); j_client = config->glewlwyd_config->glewlwyd_plugin_callback_get_client(config->glewlwyd_config, client_id); if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true()) { // Client must have a non empty client_secret, a public key available, a jwks, or be non confidential alg = r_jwt_get_sign_alg(jwt); if (r_jwt_get_type(jwt) == R_JWT_TYPE_SIGN || is_enc_alg_valid(config, json_object_get(j_client, "client"), jwt, auth_type)) { if (alg == R_JWA_ALG_NONE || is_sig_alg_valid(config, json_object_get(j_client, "client"), alg, auth_type)) { if (json_object_get(json_object_get(j_client, "client"), "confidential") == json_true()) { if (alg == R_JWA_ALG_HS256 || alg == R_JWA_ALG_HS384 || alg == R_JWA_ALG_HS512) { if (json_string_length(json_object_get(json_object_get(j_client, "client"), "client_secret"))) { if (r_jwk_init(&jwk) == RHN_OK && r_jwk_import_from_symmetric_key(jwk, (const unsigned char *)json_string_value(json_object_get(json_object_get(j_client, "client"), "client_secret")), json_string_length(json_object_get(json_object_get(j_client, "client"), "client_secret"))) == RHN_OK && r_jwt_verify_signature(jwt, jwk, 0) == RHN_OK) { j_return = json_pack("{sisOsi}", "result", G_OK, "client", json_object_get(j_client, "client"), "client_auth_method", GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_JWT); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_request_signature - jwt has an invalid signature (client_secret), origin: %s", ip_source); y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for client_id %s at IP Address %s", client_id, ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } r_jwk_free(jwk); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_request_signature - client has no attribute 'client_secret', origin: %s", ip_source); y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for client_id %s at IP Address %s", client_id, ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } } else if (alg == R_JWA_ALG_ES256 || alg == R_JWA_ALG_ES384 || alg == R_JWA_ALG_ES512 || alg == R_JWA_ALG_RS256 || alg == R_JWA_ALG_RS384 || alg == R_JWA_ALG_RS512 || alg == R_JWA_ALG_PS256 || alg == R_JWA_ALG_PS384 || alg == R_JWA_ALG_PS512 || alg == R_JWA_ALG_EDDSA) { if (json_string_length(json_object_get(json_object_get(j_client, "client"), json_string_value(json_object_get(config->j_params, "client-jwks_uri-parameter")))) && o_strlen(kid)) { if (r_jwks_init(&jwks) == RHN_OK && r_jwks_import_from_uri(jwks, json_string_value(json_object_get(json_object_get(j_client, "client"), json_string_value(json_object_get(config->j_params, "client-jwks_uri-parameter")))), config->x5u_flags) == RHN_OK) { if (json_object_get(config->j_params, "oauth-fapi-allow-multiple-kid") == json_true()) { j_search = json_pack("{ss*}", "kid", kid); jwks_kid = r_jwks_search_json_t(jwks, j_search); json_decref(j_search); if (r_jwks_size(jwks_kid) == 1) { jwk = r_jwks_get_at(jwks_kid, 0); } else if (r_jwks_size(jwks_kid) > 1) { j_search = get_jwk_search_pattern(jwt, jwks_kid); jwks_multiple_kids = r_jwks_search_json_t(jwks_kid, j_search); json_decref(j_search); if (r_jwks_size(jwks_multiple_kids) == 1) { jwk = r_jwks_get_at(jwks_multiple_kids, 0); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_request_signature - unable to get pubkey from jwks_uri, muliple kid, pattern invalid, origin: %s", ip_source); } r_jwks_free(jwks_multiple_kids); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_request_signature - unable to get pubkey from jwks_uri (fapi), origin: %s", ip_source); } r_jwks_free(jwks_kid); } else { if ((jwk = r_jwks_get_by_kid(jwks, kid)) == NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_request_signature - unable to get pubkey from jwks_uri, origin: %s", ip_source); } } } r_jwks_free(jwks); } else if (json_is_object(json_object_get(json_object_get(j_client, "client"), json_string_value(json_object_get(config->j_params, "client-jwks-parameter")))) && o_strlen(kid)) { if (r_jwks_init(&jwks) == RHN_OK && r_jwks_import_from_json_t(jwks, json_object_get(json_object_get(j_client, "client"), json_string_value(json_object_get(config->j_params, "client-jwks-parameter")))) == RHN_OK) { if (json_object_get(config->j_params, "oauth-fapi-allow-multiple-kid") == json_true()) { j_search = json_pack("{ss*}", "kid", kid); jwks_kid = r_jwks_search_json_t(jwks, j_search); json_decref(j_search); if (r_jwks_size(jwks_kid) == 1) { jwk = r_jwks_get_at(jwks_kid, 0); } else if (r_jwks_size(jwks_kid) > 1) { j_search = get_jwk_search_pattern(jwt, jwks_kid); jwks_multiple_kids = r_jwks_search_json_t(jwks_kid, j_search); json_decref(j_search); if (r_jwks_size(jwks_multiple_kids) == 1) { jwk = r_jwks_get_at(jwks_multiple_kids, 0); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_request_signature - unable to get pubkey from jwks, muliple kid, pattern invalid, origin: %s", ip_source); } r_jwks_free(jwks_multiple_kids); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_request_signature - unable to get pubkey from jwks (fapi), origin: %s", ip_source); } r_jwks_free(jwks_kid); } else { if ((jwk = r_jwks_get_by_kid(jwks, kid)) == NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_request_signature - unable to get pubkey from jwks, origin: %s", ip_source); } } } r_jwks_free(jwks); } else if (json_string_length(json_object_get(json_object_get(j_client, "client"), pubkey_param))) { if (r_jwk_init(&jwk) != RHN_OK || r_jwk_import_from_pem_der(jwk, R_X509_TYPE_PUBKEY, R_FORMAT_PEM, (const unsigned char *)json_string_value(json_object_get(json_object_get(j_client, "client"), pubkey_param)), json_string_length(json_object_get(json_object_get(j_client, "client"), pubkey_param))) != RHN_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_request_signature - unable to get pubkey from client, origin: %s", ip_source); r_jwk_free(jwk); jwk = NULL; } } if (jwk != NULL) { if (r_jwt_verify_signature(jwt, jwk, 0) == RHN_OK) { j_return = json_pack("{sisOsi}", "result", G_OK, "client", json_object_get(j_client, "client"), "client_auth_method", GLEWLWYD_CLIENT_AUTH_METHOD_PRIVATE_KEY_JWT); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_request_signature - jwt has an invalid signature (pubkey)", ip_source); y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for client_id %s at IP Address %s", client_id, ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } r_jwk_free(jwk); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_request_signature - invalid pubkey, origin: %s", ip_source); y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for client_id %s at IP Address %s", client_id, ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_request_signature - jwt has unsupported algorithm: %s, origin: %s", r_jwa_alg_to_str(alg), ip_source); y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for client_id %s at IP Address %s", client_id, ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } } else { // jwt_header must have alg set to "none" if (alg == R_JWA_ALG_NONE) { j_return = json_pack("{sisOsi}", "result", G_OK, "client", json_object_get(j_client, "client"), "client_auth_method", GLEWLWYD_CLIENT_AUTH_METHOD_NONE); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_request_signature - jwt alg is not none although the client is not confidential, origin: %s", ip_source); y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for client_id %s at IP Address %s", client_id, ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } } } else { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for client_id %s at IP Address %s", client_id, ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for client_id %s at IP Address %s", client_id, ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else if (check_result_value(j_client, G_ERROR_NOT_FOUND) || check_result_value(j_client, G_ERROR_PARAM)) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for client_id %s at IP Address %s", client_id, ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "verify_request_signature - Error getting header or payload, origin: %s", ip_source); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_client); return j_return; } static int decrypt_request_token(struct _oidc_config * config, jwt_t * jwt) { int ret, res; jwk_t * jwk = NULL; unsigned char * key = NULL, key_hash[64] = {0}; size_t key_len = 0, key_hash_len = 64; jwa_alg alg; jwa_enc enc; unsigned int bits = 0; if (r_jwt_get_type(jwt) == R_JWT_TYPE_SIGN) { // Not encrypted ret = G_OK; } else if (r_jwt_get_type(jwt) == R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT) { if (json_object_get(config->j_params, "request-parameter-allow-encrypted") == json_true()) { alg = r_jwt_get_enc_alg(jwt); enc = r_jwt_get_enc(jwt); if (r_jwks_size(config->jwks_sign) == 1) { jwk = r_jwks_get_at(config->jwks_sign, 0); } else if (r_jwt_get_header_str_value(jwt, "kid") != NULL) { jwk = r_jwks_get_by_kid(config->jwks_sign, r_jwt_get_header_str_value(jwt, "kid")); } else if (json_string_length(json_object_get(config->j_params, "default-kid"))) { jwk = r_jwks_get_by_kid(config->jwks_sign, json_string_value(json_object_get(config->j_params, "default-kid"))); } if (jwk != NULL) { if (r_jwk_key_type(jwk, &bits, 0) & R_KEY_TYPE_SYMMETRIC) { if (alg == R_JWA_ALG_A128GCMKW || alg == R_JWA_ALG_A128KW || alg == R_JWA_ALG_A192GCMKW || alg == R_JWA_ALG_A192KW || alg == R_JWA_ALG_A256GCMKW || alg == R_JWA_ALG_A256KW || alg == R_JWA_ALG_DIR) { key_len = (size_t)bits; if (key_len && (key = o_malloc(key_len)) != NULL) { if (r_jwk_export_to_symmetric_key(jwk, key, &key_len) == RHN_OK) { if (generate_digest_raw((alg == R_JWA_ALG_DIR?digest_SHA512:digest_SHA256), key, key_len, key_hash, &key_hash_len)) { if (alg == R_JWA_ALG_DIR) { key_hash_len = get_enc_key_size(enc); } else if (alg == R_JWA_ALG_A128GCMKW || alg == R_JWA_ALG_A128KW) { key_hash_len = 16; } else if (alg == R_JWA_ALG_A192GCMKW || alg == R_JWA_ALG_A192KW) { key_hash_len = 24; } r_jwk_free(jwk); jwk = NULL; if (r_jwk_init(&jwk) != RHN_OK || r_jwk_import_from_symmetric_key(jwk, key_hash, key_hash_len) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "decrypt_request_token - Error setting jwk"); r_jwk_free(jwk); jwk = NULL; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "decrypt_request_token - Error generate_digest_raw"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "decrypt_request_token - Error r_jwk_export_to_symmetric_key"); } o_free(key); } else { y_log_message(Y_LOG_LEVEL_ERROR, "decrypt_request_token - Error allocating resources for key"); } } else { // Key type differs r_jwk_free(jwk); jwk = NULL; } } else { if (alg == R_JWA_ALG_A128GCMKW || alg == R_JWA_ALG_A128KW || alg == R_JWA_ALG_A192GCMKW || alg == R_JWA_ALG_A192KW || alg == R_JWA_ALG_A256GCMKW || alg == R_JWA_ALG_A256KW || alg == R_JWA_ALG_DIR) { // Key type differs r_jwk_free(jwk); jwk = NULL; } } } if (jwk != NULL) { if ((res = r_jwt_decrypt_nested(jwt, jwk, 0)) == RHN_OK) { ret = G_OK; } else if (res == RHN_ERROR_INVALID) { y_log_message(Y_LOG_LEVEL_DEBUG, "decrypt_request_token - invalid decrypt key"); ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "decrypt_request_token - Error r_jwt_decrypt_nested"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "decrypt_request_token - No key to decrypt"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "decrypt_request_token - Encrypted requests not allowed"); ret = G_ERROR_PARAM; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "decrypt_request_token - invalid nested JWT type"); ret = G_ERROR_PARAM; } r_jwk_free(jwk); return ret; } /** * validate a request object in jwt format */ static json_t * validate_jwt_auth_request(struct _oidc_config * config, const char * jwt_request, const char * client_id, const char * ip_source) { json_t * j_return, * j_result; jwt_t * jwt = NULL; int valid_ietf = 1, valid_fapi = 1; json_int_t j_now = (json_int_t)time(NULL); if (jwt_request != NULL) { if (r_jwt_init(&jwt) == RHN_OK && r_jwt_advanced_parse(jwt, jwt_request, R_PARSE_UNSIGNED, 0) == RHN_OK && decrypt_request_token(config, jwt) == G_OK) { // request or request_uri must not be present in the payload if (r_jwt_get_claim_str_value(jwt, "request") == NULL && r_jwt_get_claim_str_value(jwt, "request_uri") == NULL) { j_result = verify_request_signature(config, jwt, r_jwt_get_claim_str_value(jwt, "client_id"), GLEWLWYD_AUTH_REQUEST_OBJECT, ip_source); if (check_result_value(j_result, G_OK)) { if (json_object_get(config->j_params, "request-parameter-ietf-strict") == json_true()) { if (0 != o_strcmp(client_id, r_jwt_get_claim_str_value(jwt, "client_id")) || 0 != o_strcmp("oauth-authz-req+jwt", r_jws_get_header_str_value(jwt->jws, "typ"))) { valid_ietf = 0; } } if (json_object_get(config->j_params, "oauth-fapi-verify-nbf") == json_true() && (r_jwt_validate_claims(jwt, R_JWT_CLAIM_NBF, R_JWT_CLAIM_NOW, R_JWT_CLAIM_EXP, R_JWT_CLAIM_NOW, R_JWT_CLAIM_NOP) != RHN_OK || ((r_jwt_get_claim_int_value(jwt, "exp") - j_now) > MIN(config->auth_token_max_age, 3600)))) { valid_fapi = 0; } if (valid_ietf && valid_fapi) { j_return = json_pack("{sisosOsOsi}", "result", G_OK, "request", r_jwt_get_full_claims_json_t(jwt), "client", json_object_get(j_result, "client"), "client_auth_method", json_object_get(j_result, "client_auth_method"), "type", r_jwt_get_type(jwt)); } else { if (!valid_ietf) { y_log_message(Y_LOG_LEVEL_ERROR, "validate_jwt_auth_request - Error jwt_request is not compatible with IETF format, origin: %s", ip_source); } if (!valid_fapi) { y_log_message(Y_LOG_LEVEL_ERROR, "validate_jwt_auth_request - Error jwt_request is not compatible with FAPI recommendations, origin: %s", ip_source); } j_return = json_pack("{si}", "result", G_ERROR_PARAM); } } else if (check_result_value(j_result, G_ERROR_UNAUTHORIZED)) { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_jwt_auth_request - Error verify_request_signature"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_jwt_auth_request - jwt has an invalid payload with attribute request or request_uri, origin: %s", ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_jwt_auth_request - Error jwt_request is not a valid jwt, origin: %s", ip_source); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } r_jwt_free(jwt); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_jwt_auth_request - Error jwt_request is NULL"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } static json_t * validate_ciba_jwt_request(struct _oidc_config * config, const char * jwt_request, const char * ip_source) { json_t * j_return, * j_result, * j_claims; jwt_t * jwt = NULL; char * requested_expiry = NULL; int res; const char * client_id = NULL; if (jwt_request != NULL) { if (r_jwt_init(&jwt) == RHN_OK && r_jwt_advanced_parse(jwt, jwt_request, R_PARSE_NONE, 0) == RHN_OK && decrypt_request_token(config, jwt) == G_OK) { // request or request_uri must not be present in the payload if (r_jwt_get_claim_str_value(jwt, "request") == NULL && r_jwt_get_claim_str_value(jwt, "request_uri") == NULL) { j_result = verify_request_signature(config, jwt, r_jwt_get_claim_str_value(jwt, "iss"), GLEWLWYD_AUTH_CIBA, ip_source); if (check_result_value(j_result, G_OK)) { // Verify mandatory claims if (o_strlen(client_id = r_jwt_get_claim_str_value(jwt, "iss"))) { if (r_jwt_validate_claims(jwt, R_JWT_CLAIM_AUD, json_string_value(json_object_get(config->j_params, "iss")), R_JWT_CLAIM_EXP, R_JWT_CLAIM_PRESENT, R_JWT_CLAIM_IAT, R_JWT_CLAIM_PRESENT, R_JWT_CLAIM_NBF, R_JWT_CLAIM_PRESENT, R_JWT_CLAIM_JTI, NULL, R_JWT_CLAIM_NOP) == RHN_OK && o_strnstr(r_jwt_get_claim_str_value(jwt, "aud"), json_string_value(json_object_get(config->j_params, "iss")), json_string_length(json_object_get(config->j_params, "iss"))) != NULL) { if ((res = check_ciba_jti(config, r_jwt_get_claim_str_value(jwt, "jti"), client_id, ip_source)) == RHN_OK) { j_claims = r_jwt_get_full_claims_json_t(jwt); if (json_object_get(j_claims, "requested_expiry") != NULL) { if (json_is_integer(json_object_get(j_claims, "requested_expiry"))) { requested_expiry = msprintf("%"JSON_INTEGER_FORMAT, json_integer_value(json_object_get(j_claims, "requested_expiry"))); r_jwt_set_claim_str_value(jwt, "requested_expiry", requested_expiry); o_free(requested_expiry); } } json_decref(j_claims); j_return = json_pack("{sisosOsOsiss}", "result", G_OK, "request", r_jwt_get_full_claims_json_t(jwt), "client", json_object_get(j_result, "client"), "client_auth_method", json_object_get(j_result, "client_auth_method"), "type", r_jwt_get_type(jwt), "jti", r_jwt_get_claim_str_value(jwt, "jti")); } else if (res == G_ERROR_UNAUTHORIZED) { y_log_message(Y_LOG_LEVEL_ERROR, "validate_ciba_jwt_request - Error jti already used for client_id '%s', origin: %s", client_id, ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_ciba_jwt_request - Error check_ciba_jti"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_ciba_jwt_request - Error invalid jwt claims, origin: %s", ip_source); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_ciba_jwt_request - Error jwt_request does not contain iss value, origin: %s", ip_source); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } } else if (check_result_value(j_result, G_ERROR_UNAUTHORIZED)) { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_ciba_jwt_request - Error verify_request_signature"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_ciba_jwt_request - jwt has an invalid payload with attribute request or request_uri, origin: %s", ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_ciba_jwt_request - Error jwt_request is not a valid jwt, origin: %s", ip_source); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } r_jwt_free(jwt); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_ciba_jwt_request - Error jwt_request is NULL"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } static int check_ciba_user_code(struct _oidc_config * config, json_t * j_user, const char * user_code) { if (0 == o_strcmp(user_code, json_string_value(json_object_get(j_user, json_string_value(json_object_get(config->j_params, "oauth-ciba-user-code-property")))))) { return G_OK; } else { return G_ERROR_UNAUTHORIZED; } } static json_t * generate_ciba_token_response(struct _oidc_config * config, json_t * j_client, json_t * j_user, json_t * j_ciba_request, const char * scope_list, const char * sid) { json_t * j_return, * j_refresh_token, * j_amr, * j_refresh; time_t now; char * refresh_token, * refresh_token_out = NULL, * access_token, * access_token_out = NULL, * id_token, * id_token_out = NULL, jti_r[OIDC_JTI_LENGTH+1] = {0}, jti[OIDC_JTI_LENGTH+1] = {0}, ** scope_array = NULL; const char * client_id = json_string_value(json_object_get(j_client, "client_id")), * x5t_s256 = json_string_value(json_object_get(j_ciba_request, "x5t_s256")), * username = json_string_value(json_object_get(j_user, "username")); int has_openid = 0, r_enc_res = G_OK, a_enc_res = G_OK, i_enc_res = G_OK; if (split_string(scope_list, " ", &scope_array)) { has_openid = string_array_has_value((const char **)scope_array, "openid"); } free_string_array(scope_array); j_refresh = get_refresh_token_duration_rolling(config, scope_list); time(&now); if (check_result_value(j_refresh, G_OK)) { if ((refresh_token = generate_refresh_token()) != NULL) { y_log_message(Y_LOG_LEVEL_INFO, "Event oidc - Plugin '%s' - Refresh token generated for client '%s' granted by user '%s' with scope list '%s'", config->name, client_id, username, scope_list); j_refresh_token = serialize_refresh_token(config, GLEWLWYD_AUTHORIZATION_TYPE_RESOURCE_OWNER_PASSWORD_CREDENTIALS, 0, username, client_id, scope_list, NULL, now, json_integer_value(json_object_get(json_object_get(j_refresh, "refresh-token"), "refresh-token-duration")), json_object_get(json_object_get(j_refresh, "refresh-token"), "refresh-token-rolling")==json_true(), NULL, refresh_token, json_string_value(json_object_get(j_ciba_request, "issued_for")), json_string_value(json_object_get(j_ciba_request, "user_agent")), jti_r, NULL, NULL); if (check_result_value(j_refresh_token, G_OK)) { if ((access_token = generate_access_token(config, username, j_client, j_user, scope_list, NULL, NULL, now, jti, x5t_s256, NULL, NULL, json_string_value(json_object_get(j_ciba_request, "issued_for")))) != NULL) { if (serialize_access_token(config, GLEWLWYD_AUTHORIZATION_TYPE_RESOURCE_OWNER_PASSWORD_CREDENTIALS, json_integer_value(json_object_get(j_refresh_token, "gpgr_id")), username, client_id, scope_list, NULL, now, json_string_value(json_object_get(j_ciba_request, "issued_for")), json_string_value(json_object_get(j_ciba_request, "user_agent")), access_token, jti, NULL) == G_OK) { if (has_openid) { j_amr = json_object_get(j_ciba_request, "amr"); if ((id_token = generate_id_token(config, username, json_object_get(j_user, "user"), j_client, now, now, NULL, j_amr, access_token, NULL, scope_list, NULL, json_string_value(json_object_get(j_ciba_request, "auth_req_id")), refresh_token, NULL, sid, json_string_value(json_object_get(j_ciba_request, "issued_for")))) != NULL) { if (serialize_id_token(config, GLEWLWYD_AUTHORIZATION_TYPE_RESOURCE_OWNER_PASSWORD_CREDENTIALS, id_token, username, client_id, sid, 0, json_integer_value(json_object_get(j_refresh_token, "gpgr_id")), now, json_string_value(json_object_get(j_ciba_request, "issued_for")), json_string_value(json_object_get(j_ciba_request, "user_agent"))) == G_OK) { if ((access_token_out = encrypt_token_if_required(config, access_token, j_client, GLEWLWYD_TOKEN_TYPE_ACCESS_TOKEN, &a_enc_res)) != NULL && (refresh_token_out = encrypt_token_if_required(config, refresh_token, j_client, GLEWLWYD_TOKEN_TYPE_REFRESH_TOKEN, &r_enc_res)) != NULL && (id_token_out = encrypt_token_if_required(config, id_token, j_client, GLEWLWYD_TOKEN_TYPE_ID_TOKEN, &i_enc_res)) != NULL) { j_return = json_pack("{sis{sOsssssssssisIss}}", "result", G_OK, "token", "auth_req_id", json_object_get(j_ciba_request, "auth_req_id"), "token_type", "bearer", "access_token", access_token_out, "refresh_token", refresh_token_out, "id_token", id_token_out, "iat", now, "expires_in", config->access_token_duration, "scope", scope_list); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_ID_TOKEN, 1, "plugin", config->name, "response_type", "ciba", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_ID_TOKEN, 1, "plugin", config->name, NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_REFRESH_TOKEN, 1, "plugin", config->name, "response_type", "ciba", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_REFRESH_TOKEN, 1, "plugin", config->name, NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 1, "plugin", config->name, "response_type", "ciba", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 1, "plugin", config->name, NULL); } else if (r_enc_res == G_ERROR_UNAUTHORIZED || a_enc_res == G_ERROR_UNAUTHORIZED || i_enc_res == G_ERROR_UNAUTHORIZED) { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error encrypt_token_if_required"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(access_token_out); o_free(refresh_token_out); o_free(id_token_out); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error serialize_id_token"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error generate_id_token"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(id_token); } else { if ((access_token_out = encrypt_token_if_required(config, access_token, j_client, GLEWLWYD_TOKEN_TYPE_ACCESS_TOKEN, &a_enc_res)) != NULL && (refresh_token_out = encrypt_token_if_required(config, refresh_token, j_client, GLEWLWYD_TOKEN_TYPE_REFRESH_TOKEN, &r_enc_res)) != NULL) { j_return = json_pack("{sis{sOsssssssisIss}}", "result", G_OK, "token", "auth_req_id", json_object_get(j_ciba_request, "auth_req_id"), "token_type", "bearer", "access_token", access_token_out, "refresh_token", refresh_token_out, "iat", now, "expires_in", config->access_token_duration, "scope", scope_list); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_REFRESH_TOKEN, 1, "plugin", config->name, "response_type", "password", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_REFRESH_TOKEN, 1, "plugin", config->name, NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 1, "plugin", config->name, "response_type", "password", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 1, "plugin", config->name, NULL); } else if (r_enc_res == G_ERROR_UNAUTHORIZED || a_enc_res == G_ERROR_UNAUTHORIZED) { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error encrypt_token_if_required"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(access_token_out); o_free(refresh_token_out); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error serialize_access_token"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error generate_access_token"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(access_token); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error serialize_refresh_token"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_refresh_token); o_free(refresh_token); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error generate_refresh_token"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error get_refresh_token_duration_rolling"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_refresh); return j_return; } static int close_ciba_request(struct _oidc_config * config, json_int_t gpob_id) { json_t * j_query; int res, ret; j_query = json_pack("{sss{si}s{sI}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA, "set", "gpob_status", 3, "where", "gpob_id", gpob_id); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "close_ciba_request - Error executing j_query"); ret = G_ERROR_DB; } return ret; } static int send_ciba_client_notification(struct _oidc_config * config, json_t * j_client, json_t * j_user, json_t * j_ciba_request, const char * scope_list, int status, const char * sid) { int ret; struct _u_request req; struct _u_response resp; char * bearer_token; json_t * j_body, * j_ciba_token; if (0 == o_strcmp(json_string_value(json_object_get(j_client, "backchannel_token_delivery_mode")), "poll")) { ret = G_OK; // Nothing to do, client will poll token endpoint } else if (0 == o_strcmp(json_string_value(json_object_get(j_client, "backchannel_token_delivery_mode")), "ping")) { // send ping request if (ulfius_init_request(&req) == U_OK) { if (ulfius_init_response(&resp) == U_OK) { bearer_token = msprintf("Bearer %s", json_string_value(json_object_get(j_ciba_request, "client_notification_token"))); j_body = json_pack("{ss}", "auth_req_id", json_string_value(json_object_get(j_ciba_request, "auth_req_id"))); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, json_string_value(json_object_get(j_client, "backchannel_client_notification_endpoint")), U_OPT_JSON_BODY, j_body, U_OPT_HEADER_PARAMETER, "Authorization", bearer_token, U_OPT_CHECK_SERVER_CERTIFICATE, json_object_get(config->j_params, "oauth-ciba-allow-https-non-secure")==json_true()?0:1, U_OPT_CHECK_PROXY_CERTIFICATE, json_object_get(config->j_params, "oauth-ciba-allow-https-non-secure")==json_true()?0:1, U_OPT_NONE); o_free(bearer_token); json_decref(j_body); if (ulfius_send_http_request(&req, &resp) == U_OK) { if (resp.status == 200 || resp.status == 204) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "send_ciba_client_notification ping - Invalid response status: %d", resp.status); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "send_ciba_client_notification ping - Error ulfius_send_http_request"); ret = G_ERROR; } ulfius_clean_response(&resp); } else { y_log_message(Y_LOG_LEVEL_ERROR, "send_ciba_client_notification ping - Error ulfius_init_request"); ret = G_ERROR; } ulfius_clean_request(&req); } else { y_log_message(Y_LOG_LEVEL_ERROR, "send_ciba_client_notification ping - Error ulfius_init_request"); ret = G_ERROR; } } else if (0 == o_strcmp(json_string_value(json_object_get(j_client, "backchannel_token_delivery_mode")), "push")) { if (status == 1) { j_ciba_token = generate_ciba_token_response(config, j_client, j_user, j_ciba_request, scope_list, sid); if (check_result_value(j_ciba_token, G_OK)) { if (close_ciba_request(config, json_integer_value(json_object_get(j_ciba_request, "gpob_id"))) == G_OK) { if (ulfius_init_request(&req) == U_OK) { if (ulfius_init_response(&resp) == U_OK) { bearer_token = msprintf("Bearer %s", json_string_value(json_object_get(j_ciba_request, "client_notification_token"))); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, json_string_value(json_object_get(j_client, "backchannel_client_notification_endpoint")), U_OPT_JSON_BODY, json_object_get(j_ciba_token, "token"), U_OPT_HEADER_PARAMETER, "Authorization", bearer_token, U_OPT_CHECK_SERVER_CERTIFICATE, json_object_get(config->j_params, "oauth-ciba-allow-https-non-secure")==json_true()?0:1, U_OPT_CHECK_PROXY_CERTIFICATE, json_object_get(config->j_params, "oauth-ciba-allow-https-non-secure")==json_true()?0:1, U_OPT_NONE); o_free(bearer_token); if (ulfius_send_http_request(&req, &resp) == U_OK) { if (resp.status == 200 || resp.status == 204) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "send_ciba_client_notification push - Invalid response status: %d", resp.status); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "send_ciba_client_notification push - Error ulfius_send_http_request"); ret = G_ERROR; } ulfius_clean_response(&resp); } else { y_log_message(Y_LOG_LEVEL_ERROR, "send_ciba_client_notification push - Error ulfius_init_request"); ret = G_ERROR; } ulfius_clean_request(&req); } else { y_log_message(Y_LOG_LEVEL_ERROR, "send_ciba_client_notification push - Error ulfius_init_request"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "send_ciba_client_notification push - Error close_ciba_request"); ret = G_ERROR; } } else if (check_result_value(j_ciba_token, G_ERROR_PARAM)) { ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "send_ciba_client_notification push - Error generate_ciba_token_response"); ret = G_ERROR; } json_decref(j_ciba_token); } else { if (ulfius_init_request(&req) == U_OK) { if (ulfius_init_response(&resp) == U_OK) { bearer_token = msprintf("Bearer %s", json_string_value(json_object_get(j_ciba_request, "client_notification_token"))); j_body = json_pack("{ss}", "error", "access_denied"); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, json_string_value(json_object_get(j_client, "backchannel_client_notification_endpoint")), U_OPT_JSON_BODY, j_body, U_OPT_HEADER_PARAMETER, "Authorization", bearer_token, U_OPT_CHECK_SERVER_CERTIFICATE, json_object_get(config->j_params, "oauth-ciba-allow-https-non-secure")==json_true()?0:1, U_OPT_CHECK_PROXY_CERTIFICATE, json_object_get(config->j_params, "oauth-ciba-allow-https-non-secure")==json_true()?0:1, U_OPT_NONE); o_free(bearer_token); json_decref(j_body); if (ulfius_send_http_request(&req, &resp) == U_OK) { if (resp.status == 200 || resp.status == 204) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "send_ciba_client_notification ping - Invalid response status: %d", resp.status); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "send_ciba_client_notification push - Error ulfius_send_http_request"); ret = G_ERROR; } ulfius_clean_response(&resp); } else { y_log_message(Y_LOG_LEVEL_ERROR, "send_ciba_client_notification ping - Error ulfius_init_request"); ret = G_ERROR; } ulfius_clean_request(&req); } else { y_log_message(Y_LOG_LEVEL_ERROR, "send_ciba_client_notification ping - Error ulfius_init_request"); ret = G_ERROR; } } } else { ret = G_ERROR_PARAM; } return ret; } static int check_request_jti_unused(struct _oidc_config * config, const char * jti, const char * iss, const char * ip_source) { json_t * j_query, * j_result = NULL; int ret, res; char * jti_hash = NULL; if (o_strlen(jti)) { jti_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, jti); j_query = json_pack("{sss[s]s{ssssss}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CLIENT_TOKEN_REQUEST, "columns", "gpoctr_id", "where", "gpoctr_plugin_name", config->name, "gpoctr_cient_id", iss, "gpoctr_jti_hash", jti_hash); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_request_jti_unused - jti already used for client '%s', origin %s", iss, ip_source); ret = G_ERROR_UNAUTHORIZED; } else { j_query = json_pack("{sss{ssssssss}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CLIENT_TOKEN_REQUEST, "values", "gpoctr_plugin_name", config->name, "gpoctr_cient_id", iss, "gpoctr_issued_for", ip_source, "gpoctr_jti_hash", jti_hash); res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_request_jti_unused - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_request_jti_unused - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } o_free(jti_hash); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "check_request_jti_unused - no jti in jwt request for client '%s', origin %s", iss, ip_source); ret = G_ERROR_PARAM; } return ret; } /** * validate a assertion object in jwt format */ static json_t * validate_jwt_assertion_request(struct _oidc_config * config, const char * jwt_assertion, const char * url, const char * ip_source) { json_t * j_return, * j_result; jwt_t * jwt = NULL; char * endpoint, * plugin_url = config->glewlwyd_config->glewlwyd_callback_get_plugin_external_url(config->glewlwyd_config, config->name); json_int_t j_now = (json_int_t)time(NULL); int auth_type = GLEWLWYD_AUTH_TOKEN_ENDPOINT; endpoint = msprintf("%s/%s", plugin_url, url); if (jwt_assertion != NULL) { if (r_jwt_init(&jwt) == RHN_OK && r_jwt_advanced_parse(jwt, jwt_assertion, R_PARSE_NONE, 0) == RHN_OK && decrypt_request_token(config, jwt) == G_OK) { // Extract header and payload if (0 == o_strcmp(url, "ciba")) { auth_type = GLEWLWYD_AUTH_CIBA; } j_result = verify_request_signature(config, jwt, r_jwt_get_claim_str_value(jwt, "iss"), auth_type, ip_source); if (check_result_value(j_result, G_OK)) { if (r_jwt_validate_claims(jwt, R_JWT_CLAIM_ISS, json_string_value(json_object_get(json_object_get(j_result, "client"), "client_id")), R_JWT_CLAIM_SUB, json_string_value(json_object_get(json_object_get(j_result, "client"), "client_id")), R_JWT_CLAIM_AUD, endpoint, R_JWT_CLAIM_EXP, R_JWT_CLAIM_NOW, R_JWT_CLAIM_NOP) == RHN_OK && (json_object_get(config->j_params, "oauth-fapi-verify-nbf") != json_true() || r_jwt_validate_claims(jwt, R_JWT_CLAIM_NBF, R_JWT_CLAIM_NOW, R_JWT_CLAIM_NOP) == RHN_OK) && ((r_jwt_get_claim_int_value(jwt, "exp") - j_now) <= config->auth_token_max_age) && check_request_jti_unused(config, r_jwt_get_claim_str_value(jwt, "jti"), r_jwt_get_claim_str_value(jwt, "iss"), ip_source) == G_OK) { j_return = json_pack("{sisosOsO}", "result", G_OK, "request", r_jwt_get_full_claims_json_t(jwt), "client", json_object_get(j_result, "client"), "client_auth_method", json_object_get(j_result, "client_auth_method")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "invalid jwt assertion content"); y_log_message(Y_LOG_LEVEL_DEBUG, " - iss: '%s'", r_jwt_get_claim_str_value(jwt, "iss")); y_log_message(Y_LOG_LEVEL_DEBUG, " - sub: '%s'", r_jwt_get_claim_str_value(jwt, "sub")); y_log_message(Y_LOG_LEVEL_DEBUG, " - nbf: %"RHONABWY_INTEGER_FORMAT, r_jwt_get_claim_int_value(jwt, "nbf")); y_log_message(Y_LOG_LEVEL_DEBUG, " - exp: %"RHONABWY_INTEGER_FORMAT, r_jwt_get_claim_int_value(jwt, "exp")); y_log_message(Y_LOG_LEVEL_DEBUG, " - aud: '%s'", r_jwt_get_claim_str_value(jwt, "aud")); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else if (check_result_value(j_result, G_ERROR_UNAUTHORIZED)) { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_jwt_assertion_request - Error verify_request_signature"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_jwt_assertion_request - Error jwt_assertion is not a valid jwt, origin: %s", ip_source); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } r_jwt_free(jwt); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_jwt_assertion_request - Error jwt_assertion is NULL"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } o_free(endpoint); o_free(plugin_url); return j_return; } static int revoke_refresh_token(struct _oidc_config * config, const char * token) { json_t * j_query; int res, ret; char * token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, token); j_query = json_pack("{sss{si}s{ssss}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN, "set", "gpor_enabled", 0, "where", "gpor_plugin_name", config->name, "gpor_token_hash", token_hash); o_free(token_hash); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "revoke_refresh_token - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static int revoke_access_token(struct _oidc_config * config, const char * token) { json_t * j_query; int res, ret; char * token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, token); j_query = json_pack("{sss{si}s{ssss}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_ACCESS_TOKEN, "set", "gpoa_enabled", 0, "where", "gpoa_plugin_name", config->name, "gpoa_token_hash", token_hash); o_free(token_hash); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "revoke_access_token - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static int revoke_id_token(struct _oidc_config * config, const char * token) { json_t * j_query; int res, ret; char * token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, token); j_query = json_pack("{sss{si}s{ssss}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_ID_TOKEN, "set", "gpoi_enabled", 0, "where", "gpoi_plugin_name", config->name, "gpoi_hash", token_hash); o_free(token_hash); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "revoke_id_token - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static json_t * get_token_metadata(struct _oidc_config * config, const char * token, const char * token_type_hint, const char * client_id) { json_t * j_query, * j_result, * j_result_scope, * j_return = NULL, * j_element = NULL, * j_client = NULL, * j_cnf = NULL; int res, found_refresh = 0, found_access = 0, found_id_token = 0; size_t index = 0; char * token_hash = NULL, * scope_list = NULL, * expires_at_clause, * sub = NULL; time_t now; jwt_t * jwt = NULL; if (o_strlen(token)) { token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, token); time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } if (token_type_hint == NULL || 0 == o_strcmp("refresh_token", token_type_hint)) { j_query = json_pack("{sss[ssssssss]s{sssss{ssss}}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN, "columns", "gpor_id", "gpor_username AS username", "gpor_client_id AS client_id", "gpor_client_id AS aud", SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpor_issued_at) AS iat", "gpor_issued_at AS iat", "EXTRACT(EPOCH FROM gpor_issued_at)::integer AS iat"), SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpor_issued_at) AS nbf", "gpor_issued_at AS nbf", "EXTRACT(EPOCH FROM gpor_issued_at)::integer AS nbf"), SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpor_expires_at) AS exp", "gpor_expires_at AS exp", "EXTRACT(EPOCH FROM gpor_expires_at)::integer AS exp"), "gpor_enabled", "where", "gpor_plugin_name", config->name, "gpor_token_hash", token_hash, "gpor_expires_at", "operator", "raw", "value", expires_at_clause); if (client_id != NULL) { json_object_set_new(json_object_get(j_query, "where"), "gpor_client_id", json_string(client_id)); } res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { found_refresh = 1; if (json_integer_value(json_object_get(json_array_get(j_result, 0), "gpor_enabled"))) { json_object_set_new(json_array_get(j_result, 0), "active", json_true()); json_object_set_new(json_array_get(j_result, 0), "token_type", json_string("refresh_token")); json_object_del(json_array_get(j_result, 0), "gpor_enabled"); if (json_object_get(json_array_get(j_result, 0), "client_id") == json_null()) { json_object_del(json_array_get(j_result, 0), "client_id"); json_object_del(json_array_get(j_result, 0), "aud"); sub = get_sub(config, json_string_value(json_object_get(json_array_get(j_result, 0), "username")), NULL); } else { j_client = config->glewlwyd_config->glewlwyd_plugin_callback_get_client(config->glewlwyd_config, json_string_value(json_object_get(json_array_get(j_result, 0), "client_id"))); if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true()) { sub = get_sub(config, json_string_value(json_object_get(json_array_get(j_result, 0), "username")), json_object_get(j_client, "client")); } } if (sub != NULL) { json_object_set_new(json_array_get(j_result, 0), "sub", json_string(sub)); o_free(sub); } if (json_object_get(json_array_get(j_result, 0), "username") == json_null()) { json_object_del(json_array_get(j_result, 0), "username"); } j_query = json_pack("{sss[s]s{sO}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN_SCOPE, "columns", "gpors_scope AS scope", "where", "gpor_id", json_object_get(json_array_get(j_result, 0), "gpor_id")); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result_scope, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result_scope, index, j_element) { if (scope_list == NULL) { scope_list = o_strdup(json_string_value(json_object_get(j_element, "scope"))); } else { scope_list = mstrcatf(scope_list, " %s", json_string_value(json_object_get(j_element, "scope"))); } } json_object_set_new(json_array_get(j_result, 0), "scope", json_string(scope_list)); o_free(scope_list); json_decref(j_result_scope); json_object_del(json_array_get(j_result, 0), "gpor_id"); j_return = json_pack("{sisO}", "result", G_OK, "token", json_array_get(j_result, 0)); if (j_client != NULL) { json_object_set(j_return, "client", json_object_get(j_client, "client")); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_refresh_token - Error executing j_query scope refresh_token"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_client); } else { j_return = json_pack("{sis{so}}", "result", G_OK, "token", "active", json_false()); } } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_token_metadata - Error executing j_query refresh_token"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } if ((token_type_hint == NULL && !found_refresh) || 0 == o_strcmp("access_token", token_type_hint)) { j_query = json_pack("{sss[sssssssss]s{ssss}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_ACCESS_TOKEN, "columns", "gpoa_id", "gpoa_username AS username", "gpoa_client_id AS client_id", "gpoa_resource AS aud", SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpoa_issued_at) AS iat", "gpoa_issued_at AS iat", "EXTRACT(EPOCH FROM gpoa_issued_at)::integer AS iat"), SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpoa_issued_at) AS nbf", "gpoa_issued_at AS nbf", "EXTRACT(EPOCH FROM gpoa_issued_at)::integer AS nbf"), "gpoa_jti as jti", "gpoa_authorization_details", "gpoa_enabled", "where", "gpoa_plugin_name", config->name, "gpoa_token_hash", token_hash); if (client_id != NULL) { json_object_set_new(json_object_get(j_query, "where"), "gpoa_client_id", json_string(client_id)); } res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { found_access = 1; if (json_integer_value(json_object_get(json_array_get(j_result, 0), "gpoa_enabled")) && json_integer_value(json_object_get(json_array_get(j_result, 0), "iat")) + json_integer_value(json_object_get(config->j_params, "access-token-duration")) > now) { json_object_set_new(json_array_get(j_result, 0), "sub", json_string(sub)); json_object_set_new(json_array_get(j_result, 0), "active", json_true()); json_object_set_new(json_array_get(j_result, 0), "token_type", json_string("access_token")); json_object_set_new(json_array_get(j_result, 0), "exp", json_integer(json_integer_value(json_object_get(json_array_get(j_result, 0), "iat")) + json_integer_value(json_object_get(config->j_params, "access-token-duration")))); json_object_del(json_array_get(j_result, 0), "gpoa_enabled"); if (json_object_get(json_array_get(j_result, 0), "gpoa_authorization_details") != json_null()) { json_object_set_new(json_array_get(j_result, 0), "authorization_details", json_loads(json_string_value(json_object_get(json_array_get(j_result, 0), "gpoa_authorization_details")), JSON_DECODE_ANY, NULL)); } json_object_del(json_array_get(j_result, 0), "gpoa_authorization_details"); if (json_object_get(json_array_get(j_result, 0), "client_id") == json_null()) { json_object_del(json_array_get(j_result, 0), "client_id"); sub = get_sub(config, json_string_value(json_object_get(json_array_get(j_result, 0), "username")), NULL); } else if (json_object_get(json_array_get(j_result, 0), "username") != json_null()) { j_client = config->glewlwyd_config->glewlwyd_plugin_callback_get_client(config->glewlwyd_config, json_string_value(json_object_get(json_array_get(j_result, 0), "client_id"))); if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true()) { sub = get_sub(config, json_string_value(json_object_get(json_array_get(j_result, 0), "username")), json_object_get(j_client, "client")); } } if (sub != NULL) { json_object_set_new(json_array_get(j_result, 0), "sub", json_string(sub)); o_free(sub); } if (json_object_get(json_array_get(j_result, 0), "username") == json_null()) { json_object_del(json_array_get(j_result, 0), "username"); } j_query = json_pack("{sss[s]s{sO}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_ACCESS_TOKEN_SCOPE, "columns", "gpoas_scope AS scope", "where", "gpoa_id", json_object_get(json_array_get(j_result, 0), "gpoa_id")); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result_scope, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result_scope, index, j_element) { if (scope_list == NULL) { scope_list = o_strdup(json_string_value(json_object_get(j_element, "scope"))); } else { scope_list = mstrcatf(scope_list, " %s", json_string_value(json_object_get(j_element, "scope"))); } } json_object_set_new(json_array_get(j_result, 0), "scope", json_string(scope_list)); o_free(scope_list); json_decref(j_result_scope); json_object_del(json_array_get(j_result, 0), "gpoa_id"); j_return = json_pack("{sisO}", "result", G_OK, "token", json_array_get(j_result, 0)); if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true()) { json_object_set(j_return, "client", json_object_get(j_client, "client")); } if (r_jwt_init(&jwt) == RHN_OK) { if (r_jwt_advanced_parse(jwt, token, R_PARSE_NONE, config->x5u_flags) == RHN_OK) { if ((j_cnf = r_jwt_get_claim_json_t_value(jwt, "cnf")) != NULL) { json_object_set_new(json_object_get(j_return, "token"), "cnf", j_cnf); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_token_metadata - Error r_jwt_advanced_parse"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_token_metadata - Error r_jwt_init"); j_return = json_pack("{si}", "result", G_ERROR); } r_jwt_free(jwt); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_refresh_token - Error executing j_query scope access_token"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_client); } else { j_return = json_pack("{sis{so}}", "result", G_OK, "token", "active", json_false()); } } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_token_metadata - Error executing j_query access_token"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } if ((token_type_hint == NULL && !found_refresh && !found_access) || 0 == o_strcmp("id_token", token_type_hint)) { j_query = json_pack("{sss[sssssss]s{ssss}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_ID_TOKEN, "columns", "gpoi_username AS username", "gpoi_client_id AS client_id", "gpoi_client_id AS aud", "gpoi_sid AS sid", SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpoi_issued_at) AS iat", "gpoi_issued_at AS iat", "EXTRACT(EPOCH FROM gpoi_issued_at)::integer AS iat"), SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpoi_issued_at) AS nbf", "gpoi_issued_at AS nbf", "EXTRACT(EPOCH FROM gpoi_issued_at)::integer AS nbf"), "gpoi_enabled", "where", "gpoi_plugin_name", config->name, "gpoi_hash", token_hash); if (client_id != NULL) { json_object_set_new(json_object_get(j_query, "where"), "gpoi_client_id", json_string(client_id)); } res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { found_id_token = 1; if (json_integer_value(json_object_get(json_array_get(j_result, 0), "gpoi_enabled")) && json_integer_value(json_object_get(json_array_get(j_result, 0), "iat")) + json_integer_value(json_object_get(config->j_params, "access-token-duration")) > now) { json_object_set_new(json_array_get(j_result, 0), "sub", json_string(sub)); json_object_set_new(json_array_get(j_result, 0), "active", json_true()); json_object_set_new(json_array_get(j_result, 0), "token_type", json_string("id_token")); json_object_set_new(json_array_get(j_result, 0), "exp", json_integer(json_integer_value(json_object_get(json_array_get(j_result, 0), "iat")) + json_integer_value(json_object_get(config->j_params, "access-token-duration")))); json_object_del(json_array_get(j_result, 0), "gpoi_enabled"); if (json_object_get(json_array_get(j_result, 0), "client_id") == json_null()) { json_object_del(json_array_get(j_result, 0), "client_id"); json_object_del(json_array_get(j_result, 0), "aud"); sub = get_sub(config, json_string_value(json_object_get(json_array_get(j_result, 0), "username")), NULL); } else { j_client = config->glewlwyd_config->glewlwyd_plugin_callback_get_client(config->glewlwyd_config, json_string_value(json_object_get(json_array_get(j_result, 0), "client_id"))); if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true()) { sub = get_sub(config, json_string_value(json_object_get(json_array_get(j_result, 0), "username")), json_object_get(j_client, "client")); } } if (sub != NULL) { json_object_set_new(json_array_get(j_result, 0), "sub", json_string(sub)); o_free(sub); } if (json_object_get(json_array_get(j_result, 0), "username") == json_null()) { json_object_del(json_array_get(j_result, 0), "username"); } j_return = json_pack("{sisO}", "result", G_OK, "token", json_array_get(j_result, 0)); if (j_client != NULL) { json_object_set(j_return, "client", json_object_get(j_client, "client")); } } else { j_return = json_pack("{sis{so}}", "result", G_OK, "token", "active", json_false()); } json_decref(j_client); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_token_metadata - Error executing j_query id_token"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } if (!found_refresh && !found_access && !found_id_token && j_return == NULL) { j_return = json_pack("{sis{so}}", "result", G_OK, "token", "active", json_false()); } o_free(token_hash); o_free(expires_at_clause); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } static const char * get_client_id_for_introspection(struct _oidc_config * config, const struct _u_request * request) { if (u_map_get_case(request->map_header, HEADER_AUTHORIZATION) != NULL && config->introspect_revoke_resource_config->oauth_scope != NULL) { return NULL; } else if (json_object_get(config->j_params, "introspection-revocation-allow-target-client") == json_true()) { return request->auth_basic_user; } else { return NULL; } } static json_t * convert_client_glewlwyd_to_registration(json_t * j_client) { json_t * j_registration = json_deep_copy(j_client), * j_element = NULL; size_t index = 0; if (j_registration != NULL) { json_object_set(j_registration, "redirect_uris", json_object_get(j_client, "redirect_uri")); json_object_set(j_registration, "client_name", json_object_get(j_client, "name")); json_object_set_new(j_registration, "response_types", json_array()); json_array_foreach(json_object_get(j_client, "authorization_type"), index, j_element) { if (0 == o_strcmp(json_string_value(j_element), "code") || 0 == o_strcmp(json_string_value(j_element), "token") || 0 == o_strcmp(json_string_value(j_element), "id_token")) { json_array_append_new(json_object_get(j_registration, "response_types"), json_copy(j_element)); } } json_object_set_new(j_registration, "grant_types", json_array()); json_array_foreach(json_object_get(j_client, "authorization_type"), index, j_element) { if (0 == o_strcmp(json_string_value(j_element), "code")) { json_array_append_new(json_object_get(j_registration, "grant_types"), json_string("authorization_code")); } else if ((0 == o_strcmp(json_string_value(j_element), "token") || 0 == o_strcmp(json_string_value(j_element), "id_token")) && !json_array_has_string(json_object_get(j_registration, "grant_types"), "implicit")) { json_array_append_new(json_object_get(j_registration, "grant_types"), json_string("implicit")); } else if (0 == o_strcmp(json_string_value(j_element), "password") || 0 == o_strcmp(json_string_value(j_element), "client_credentials") || 0 == o_strcmp(json_string_value(j_element), "refresh_token") || 0 == o_strcmp(json_string_value(j_element), "delete_token") || 0 == o_strcmp(json_string_value(j_element), "device_authorization") || 0 == o_strcmp(json_string_value(j_element), "none")) { json_array_append_new(json_object_get(j_registration, "grant_types"), json_copy(j_element)); } } json_object_del(j_registration, "redirect_uri"); json_object_del(j_registration, "name"); json_object_del(j_registration, "confidential"); json_object_del(j_registration, "scope"); json_object_del(j_registration, "source"); json_object_del(j_registration, "enabled"); json_object_del(j_registration, "authorization_type"); json_object_del(j_registration, "redirect_uri"); } return j_registration; } static json_t * convert_client_registration_to_glewlwyd(json_t * j_registration) { json_t * j_client = json_deep_copy(j_registration), * j_element = NULL; size_t index = 0; if (j_client != NULL) { json_object_set(j_client, "redirect_uri", json_object_get(j_registration, "redirect_uris")); json_object_set(j_client, "name", json_object_get(j_registration, "client_name")); json_object_set_new(j_client, "authorization_type", json_array()); json_array_foreach(json_object_get(j_client, "response_types"), index, j_element) { if (0 == o_strcmp(json_string_value(j_element), "code") || 0 == o_strcmp(json_string_value(j_element), "token") || 0 == o_strcmp(json_string_value(j_element), "id_token")) { json_array_append_new(json_object_get(j_client, "authorization_type"), json_copy(j_element)); } } json_array_foreach(json_object_get(j_registration, "grant_types"), index, j_element) { if (0 == o_strcmp(json_string_value(j_element), "authorization_code") && !json_array_has_string(json_object_get(j_client, "authorization_type"), "code")) { json_array_append_new(json_object_get(j_client, "authorization_type"), json_string("code")); } else if (0 == o_strcmp(json_string_value(j_element), "password") || 0 == o_strcmp(json_string_value(j_element), "client_credentials") || 0 == o_strcmp(json_string_value(j_element), "refresh_token") || 0 == o_strcmp(json_string_value(j_element), "delete_token") || 0 == o_strcmp(json_string_value(j_element), "device_authorization") || 0 == o_strcmp(json_string_value(j_element), "none")) { json_array_append_new(json_object_get(j_client, "authorization_type"), json_copy(j_element)); } } if (json_array_has_string(json_object_get(j_client, "token_endpoint_auth_method"), "none") || json_object_get(j_client, "token_endpoint_auth_method") == NULL) { json_object_set(j_client, "confidential", json_false()); } else { json_object_set(j_client, "confidential", json_true()); } json_object_del(j_client, "redirect_uris"); json_object_del(j_client, "client_name"); json_object_del(j_client, "response_types"); json_object_del(j_client, "grant_types"); json_object_del(j_client, "registration_access_token"); json_object_del(j_client, "registration_client_uri"); } return j_client; } static int client_registration_management_delete(struct _oidc_config * config, json_int_t gpocr_id, json_t * j_client) { int ret, res; json_t * j_query; json_object_set(j_client, "enabled", json_false()); if ((config->glewlwyd_config->glewlwyd_plugin_callback_set_client(config->glewlwyd_config, json_string_value(json_object_get(j_client, "client_id")), j_client)) == G_OK) { j_query = json_pack("{sss{ss}s{sI}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CLIENT_REGISTRATION, "set", "gpocr_management_at_hash", "disabled", "where", "gpocr_id", gpocr_id); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "client_registration_management_delete - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } else { ret = G_OK; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_registration_management_delete - Error glewlwyd_plugin_callback_set_client"); ret = G_ERROR; } return ret; } static json_t * check_client_registration_management_at(struct _oidc_config * config, const char * client_id, const char * management_at) { json_t * j_query, * j_result = NULL, * j_client, * j_return; int res; char * management_at_hash; if (o_strlen(management_at) == GLEWLWYD_CLIENT_MANAGEMENT_AT_LENGTH) { management_at_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, management_at); j_query = json_pack("{sss[ss]s{ss}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CLIENT_REGISTRATION, "columns", "gpocr_id", "gpocr_cient_id AS client_id", "where", "gpocr_management_at_hash", management_at_hash); o_free(management_at_hash); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { if (0 == o_strcmp(client_id, json_string_value(json_object_get(json_array_get(j_result, 0), "client_id")))) { j_client = config->glewlwyd_config->glewlwyd_plugin_callback_get_client(config->glewlwyd_config, client_id); if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true()) { j_return = json_pack("{sis{sOsO}}", "result", G_OK, "registration", "gpocr_id", json_object_get(json_array_get(j_result, 0), "gpocr_id"), "client", json_object_get(j_client, "client")); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "check_client_registration_management_at - client missing or disabled"); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } json_decref(j_client); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "check_client_registration_management_at - Invalid client_id for the access token, disabling token"); j_query = json_pack("{sss{ss}s{sO}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CLIENT_REGISTRATION, "set", "gpocr_management_at_hash", "disabled", "where", "gpocr_id", json_object_get(json_array_get(j_result, 0), "gpocr_id")); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_client_registration_management_at - Error executing j_query (2)"); } j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "check_client_registration_management_at - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "check_client_registration_management_at - Missing or invalid access token"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } static int serialize_client_register(struct _oidc_config * config, const struct _u_request * request, json_t * j_client, const char * client_management_at) { json_t * j_query, * j_result; int res, ret = G_OK; char * issued_for = get_client_hostname(request), * access_token_hash = NULL, * management_at_hash = NULL; json_int_t gpoa_id = 0; if (json_array_size(json_object_get(config->j_params, "register-client-auth-scope"))) { access_token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, (u_map_get_case(request->map_header, HEADER_AUTHORIZATION) + o_strlen(HEADER_PREFIX_BEARER))); j_query = json_pack("{sss[s]s{ssss}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_ACCESS_TOKEN, "columns", "gpoa_id", "where", "gpoa_plugin_name", config->name, "gpoa_token_hash", access_token_hash); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { gpoa_id = json_integer_value(json_object_get(json_array_get(j_result, 0), "gpoa_id")); } else { ret = G_ERROR_PARAM; } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_client_register - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } if (ret == G_OK) { management_at_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, client_management_at); j_query = json_pack("{sss{sssOssss*ss?}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CLIENT_REGISTRATION, "values", "gpocr_plugin_name", config->name, "gpocr_cient_id", json_object_get(j_client, "client_id"), "gpocr_issued_for", issued_for, "gpocr_user_agent", u_map_get_case(request->map_header, "user-agent"), "gpocr_management_at_hash", management_at_hash); if (gpoa_id) { json_object_set_new(json_object_get(j_query, "values"), "gpoa_id", json_integer(gpoa_id)); } res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_client_register - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } o_free(issued_for); o_free(access_token_hash); o_free(management_at_hash); return ret; } static json_t * client_register(struct _oidc_config * config, const struct _u_request * request, json_t * j_registration, int update) { json_t * j_client, * j_return = NULL, * j_element = NULL; char client_id[GLEWLWYD_CLIENT_ID_LENGTH+1] = {}, client_secret[GLEWLWYD_CLIENT_SECRET_LENGTH+1] = {}, client_management_at[GLEWLWYD_CLIENT_MANAGEMENT_AT_LENGTH+1] = {}; char * plugin_url = config->glewlwyd_config->glewlwyd_callback_get_plugin_external_url(config->glewlwyd_config, config->name); const char * token_endpoint_auth_method, * key = NULL; if (!update) { rand_string_from_charset(client_id, GLEWLWYD_CLIENT_ID_LENGTH, "abcdefghijklmnopqrstuvwxyz0123456789"); if (o_strlen(client_id)) { json_object_foreach(json_object_get(config->j_params, "register-default-properties"), key, j_element) { json_object_set(j_registration, key, json_object_get(j_element, "value")); } json_object_set_new(j_registration, "client_id", json_string(client_id)); if (json_object_get(config->j_params, "register-client-management-allowed") == json_true()) { rand_string(client_management_at, GLEWLWYD_CLIENT_SECRET_LENGTH); if (!o_strlen(client_management_at)) { y_log_message(Y_LOG_LEVEL_ERROR, "client_register - Error generating client_management_at"); j_return = json_pack("{si}", "result", G_ERROR); } else { json_object_set_new(j_registration, "registration_access_token", json_string(client_management_at)); json_object_set_new(j_registration, "registration_client_uri", json_pack("s++", plugin_url, "/register/", client_id)); } } token_endpoint_auth_method = json_string_value(json_object_get(j_registration, "token_endpoint_auth_method")); if (j_return == NULL && (0 == o_strcmp("client_secret_post", token_endpoint_auth_method) || 0 == o_strcmp("client_secret_basic", token_endpoint_auth_method) || 0 == o_strcmp("client_secret_jwt", token_endpoint_auth_method))) { rand_string(client_secret, GLEWLWYD_CLIENT_SECRET_LENGTH); if (!o_strlen(client_secret)) { y_log_message(Y_LOG_LEVEL_ERROR, "client_register - Error generating client_secret"); j_return = json_pack("{si}", "result", G_ERROR); } else { json_object_set_new(j_registration, "client_secret", json_string(client_secret)); } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_register - Error generating client_id"); j_return = json_pack("{si}", "result", G_ERROR); } } if (json_object_get(j_registration, "application_type") == NULL) { json_object_set_new(j_registration, "application_type", json_string("web")); } if (!json_array_size(json_object_get(j_registration, "response_types"))) { json_object_set_new(j_registration, "response_types", json_pack("[s]", "code")); } if (!json_array_size(json_object_get(j_registration, "grant_types"))) { json_object_set_new(j_registration, "grant_types", json_pack("[s]", "authorization_code")); } if (json_object_get(config->j_params, "register-resource-specify-allowed") != json_true()) { json_object_del(j_registration, "resource"); if (json_array_size(json_object_get(config->j_params, "register-resource-default"))) { json_object_set(j_registration, "resource", json_object_get(config->j_params, "register-resource-default")); } } if (j_return == NULL) { j_client = convert_client_registration_to_glewlwyd(j_registration); json_object_set(j_client, "enabled", json_true()); if (json_object_get(config->j_params, "register-client-credentials-scope") != NULL) { json_object_set(j_client, "scope", json_object_get(config->j_params, "register-client-credentials-scope")); } else { json_object_set_new(j_client, "scope", json_array()); } if (!update) { json_object_set_new(j_registration, "client_id_issued_at", json_integer(time(NULL))); json_object_set_new(j_registration, "client_secret_expires_at", json_integer(0)); if (serialize_client_register(config, request, j_client, client_management_at) == G_OK) { if ((config->glewlwyd_config->glewlwyd_plugin_callback_add_client(config->glewlwyd_config, j_client)) == G_OK) { j_return = json_pack("{sisO}", "result", G_OK, "client", j_registration); } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_register - Error glewlwyd_plugin_callback_add_client"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_register - Error serialize_client_register"); j_return = json_pack("{si}", "result", G_ERROR); } } else { if ((config->glewlwyd_config->glewlwyd_plugin_callback_set_client(config->glewlwyd_config, json_string_value(json_object_get(j_registration, "client_id")), j_client)) == G_OK) { j_return = json_pack("{sisO}", "result", G_OK, "client", j_registration); } else { y_log_message(Y_LOG_LEVEL_ERROR, "client_register - Error glewlwyd_plugin_callback_set_client"); j_return = json_pack("{si}", "result", G_ERROR); } } json_decref(j_client); } o_free(plugin_url); return j_return; } static int is_redirect_uri_valid_without_credential(const char * redirect_uri) { int ret; size_t len; const char * after_slash = o_strstr(redirect_uri, "://")+o_strlen("://"); if (o_strstr(redirect_uri, "://") != NULL) { after_slash = o_strstr(redirect_uri, "://")+o_strlen("://"); // Detect redirect_uri has no user[:pwd]@url if (o_strchr(after_slash, '/') != NULL) { len = o_strchr(after_slash, '/') - after_slash; } else { len = o_strlen(after_slash); } if (o_strnchr(after_slash, len, '@') != NULL) { ret = 0; } else { ret = 1; } } else { ret = 0; } return ret; } static json_t * is_client_registration_valid(struct _oidc_config * config, json_t * j_registration, const char * client_id) { json_t * j_error = NULL, * j_return, * j_element = NULL, * j_resp = NULL, * j_info = r_library_info_json_t(), * j_response_modes_supported = NULL; size_t index = 0; jwks_t * jwks = NULL; const char * resource = NULL, * response_mode = NULL; struct _u_request req; struct _u_response resp; memset(&req, 0, sizeof(struct _u_request)); memset(&resp, 0, sizeof(struct _u_response)); do { if (!json_is_object(j_registration)) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "registration parameter must be a JSON object"); break; } if (json_object_get(j_registration, "token_endpoint_auth_method") != NULL && 0 != o_strcmp("none", json_string_value(json_object_get(j_registration, "token_endpoint_auth_method"))) && 0 != o_strcmp("client_secret_post", json_string_value(json_object_get(j_registration, "token_endpoint_auth_method"))) && 0 != o_strcmp("client_secret_basic", json_string_value(json_object_get(j_registration, "token_endpoint_auth_method"))) && 0 != o_strcmp("client_secret_jwt", json_string_value(json_object_get(j_registration, "token_endpoint_auth_method"))) && 0 != o_strcmp("private_key_jwt", json_string_value(json_object_get(j_registration, "token_endpoint_auth_method")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "token_endpoint_auth_method must have one of the following values: 'none', 'client_secret_post', 'client_secret_basic', 'client_secret_jwt', 'private_key_jwt'"); break; } if (client_id != NULL && 0 != o_strcmp(client_id, json_string_value(json_object_get(j_registration, "client_id")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "Invalid client_id"); break; } if (json_object_get(j_registration, "grant_types") != NULL && !json_is_array(json_object_get(j_registration, "grant_types"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "grant_types is optional and must be an array of strings"); break; } json_array_foreach(json_object_get(j_registration, "grant_types"), index, j_element) { if (0 != o_strcmp("authorization_code", json_string_value(j_element)) && 0 != o_strcmp("implicit", json_string_value(j_element)) && 0 != o_strcmp("password", json_string_value(j_element)) && 0 != o_strcmp("client_credentials", json_string_value(j_element)) && 0 != o_strcmp("refresh_token", json_string_value(j_element)) && 0 != o_strcmp("delete_token", json_string_value(j_element)) && 0 != o_strcmp("device_authorization", json_string_value(j_element)) && 0 != o_strcmp(GLEWLWYD_CIBA_GRANT_TYPE, json_string_value(j_element))) { if (j_error == NULL) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "grant_types must have one of the following values: 'authorization_code', 'implicit', 'password', 'client_credentials', 'refresh_token', 'delete_token', 'device_authorization', '" GLEWLWYD_CIBA_GRANT_TYPE "'"); } } } if (j_error != NULL) { break; } if (json_object_get(j_registration, "response_types") != NULL && !json_is_array(json_object_get(j_registration, "response_types"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "response_types is optional and must be an array of strings"); break; } json_array_foreach(json_object_get(j_registration, "response_types"), index, j_element) { if (0 != o_strcmp("code", json_string_value(j_element)) && 0 != o_strcmp("token", json_string_value(j_element)) && 0 != o_strcmp("id_token", json_string_value(j_element))) { if (j_error == NULL) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "response_types must have one of the following values: 'code', 'token', 'id_token'"); } } } if (j_error != NULL) { break; } if (json_array_size(json_object_get(j_registration, "response_types"))) { if (!json_array_size(json_object_get(j_registration, "redirect_uris"))) { j_error = json_pack("{ssss}", "error", "invalid_redirect_uri", "error_description", "redirect_uris is mandatory and must be an array of strings"); break; } json_array_foreach(json_object_get(j_registration, "redirect_uris"), index, j_element) { if (!is_redirect_uri_valid_without_credential(json_string_value(j_element)) || (0 != o_strncmp("https://", json_string_value(j_element), o_strlen("https://")) && 0 != o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_1, json_string_value(j_element), o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_1)) && 0 != o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_2, json_string_value(j_element), o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_2)) && 0 != o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_3, json_string_value(j_element), o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_3)))) { if (j_error == NULL) { j_error = json_pack("{ssss}", "error", "invalid_redirect_uri", "error_description", "a redirect_uri must be a 'https://' uri or a 'http://localhost' uri without credentials"); } } } if (j_error != NULL) { break; } } if (json_object_get(j_registration, "application_type") != NULL && 0 != o_strcmp("web", json_string_value(json_object_get(j_registration, "application_type"))) && 0 != o_strcmp("native", json_string_value(json_object_get(j_registration, "application_type")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "application_type is optional and must have one of the following values: 'web', 'native'"); break; } if (json_object_get(j_registration, "contacts") != NULL && !json_is_array(json_object_get(j_registration, "contacts"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "contacts is optional and must be an array of strings"); break; } json_array_foreach(json_object_get(j_registration, "contacts"), index, j_element) { if (!json_string_length(j_element)) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "contact value must be a non empty string"); } } if (j_error != NULL) { break; } if (json_object_get(j_registration, "client_confidential") != NULL && !json_is_boolean(json_object_get(j_registration, "client_confidential"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "client_confidential is optional and must be a boolean"); break; } if (json_object_get(j_registration, "client_name") != NULL && !json_is_string(json_object_get(j_registration, "client_name"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "client_name is optional and must be a string"); break; } if (json_object_get(j_registration, "logo_uri") != NULL && 0 != o_strncmp("https://", json_string_value(json_object_get(j_registration, "logo_uri")), o_strlen("https://")) && 0 != o_strncmp("http://", json_string_value(json_object_get(j_registration, "logo_uri")), o_strlen("http://"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "logo_uri is optional and must be a string"); break; } if (json_object_get(j_registration, "client_uri") != NULL && 0 != o_strncmp("https://", json_string_value(json_object_get(j_registration, "client_uri")), o_strlen("https://")) && 0 != o_strncmp("http://", json_string_value(json_object_get(j_registration, "client_uri")), o_strlen("http://"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "client_uri is optional and must be a string"); break; } if (json_object_get(j_registration, "policy_uri") != NULL && 0 != o_strncmp("https://", json_string_value(json_object_get(j_registration, "policy_uri")), o_strlen("https://")) && 0 != o_strncmp("http://", json_string_value(json_object_get(j_registration, "policy_uri")), o_strlen("http://"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "policy_uri is optional and must be a string"); break; } if (json_object_get(j_registration, "tos_uri") != NULL && 0 != o_strncmp("https://", json_string_value(json_object_get(j_registration, "tos_uri")), o_strlen("https://")) && 0 != o_strncmp("http://", json_string_value(json_object_get(j_registration, "tos_uri")), o_strlen("http://"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "tos_uri is optional and must be a string"); break; } if (0 == o_strcmp("private_key_jwt", json_string_value(json_object_get(j_registration, "token_endpoint_auth_method")))) { if (json_object_get(j_registration, "jwks_uri") != NULL && json_object_get(j_registration, "jwks") != NULL) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "jwks_uri and jwks can't coexist"); break; } if (json_object_get(j_registration, "jwks_uri") != NULL) { if (0 != o_strncmp("https://", json_string_value(json_object_get(j_registration, "jwks_uri")), o_strlen("https://"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "jwks_uri is optional and must be an https:// uri"); break; } r_jwks_init(&jwks); if (r_jwks_import_from_uri(jwks, json_string_value(json_object_get(j_registration, "jwks_uri")), config->x5u_flags) != RHN_OK) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "Invalid JWKS pointed by jwks_uri"); } r_jwks_free(jwks); if (j_error != NULL) { break; } } if (json_object_get(j_registration, "jwks") != NULL) { r_jwks_init(&jwks); if (r_jwks_import_from_json_t(jwks, json_object_get(j_registration, "jwks")) != RHN_OK) { if (j_error == NULL) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "Invalid JWKS"); } } r_jwks_free(jwks); if (j_error != NULL) { break; } } } if (json_object_get(j_registration, "sector_identifier_uri") != NULL && 0 != o_strncmp("https://", json_string_value(json_object_get(j_registration, "sector_identifier_uri")), o_strlen("https://"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "sector_identifier_uri is optional and must be an https:// uri"); break; } if (json_object_get(j_registration, "sector_identifier_uri") != NULL && 0 == o_strcmp("pairwise", json_string_value(json_object_get(config->j_params, "subject-type")))) { if (ulfius_init_request(&req) == U_OK && ulfius_init_response(&resp) == U_OK) { if (ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, json_string_value(json_object_get(j_registration, "sector_identifier_uri")), U_OPT_CHECK_SERVER_CERTIFICATE, (json_object_get(config->j_params, "request-uri-allow-https-non-secure")==json_true())?0:1, U_OPT_CHECK_PROXY_CERTIFICATE, (json_object_get(config->j_params, "request-uri-allow-https-non-secure")==json_true())?0:1, U_OPT_NONE) == U_OK) { if (ulfius_send_http_request(&req, &resp) == U_OK) { if (resp.status >= 200 && resp.status < 300) { if ((j_resp = ulfius_get_json_body_response(&resp, NULL)) != NULL && json_is_array(j_resp)) { json_array_foreach(j_resp, index, j_element) { if (!is_redirect_uri_valid_without_credential(json_string_value(j_element)) || (0 != o_strncmp("https://", json_string_value(j_element), o_strlen("https://")) && 0 != o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_1, json_string_value(j_element), o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_1)) && 0 != o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_2, json_string_value(j_element), o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_2)) && 0 != o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_3, json_string_value(j_element), o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_3)))) { if (j_error == NULL) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "a redirect_uri pointed by sector_identifier_uri must be a 'https://' uri or a 'http://localhost' uri without credentials"); } } } if (j_error != NULL) { break; } json_array_foreach(json_object_get(j_registration, "redirect_uris"), index, j_element) { if (!json_array_has_string(j_resp, json_string_value(j_element))) { if (j_error == NULL) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "a redirect_uri isn't present in the array pointed by sector_identifier_uri"); } } } if (j_error != NULL) { break; } if (json_string_value(json_object_get(j_registration, "backchannel_client_notification_endpoint")) != NULL && !json_array_has_string(j_resp, json_string_value(json_object_get(j_registration, "backchannel_client_notification_endpoint")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "backchannel_client_notification_endpoint is not present in sector_identifier_uri content"); break; } if (json_string_value(json_object_get(j_registration, "post_logout_redirect_uri")) != NULL && !json_array_has_string(j_resp, json_string_value(json_object_get(j_registration, "post_logout_redirect_uri")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "post_logout_redirect_uri is not present in sector_identifier_uri content"); break; } if (json_string_value(json_object_get(j_registration, "frontchannel_logout_uri")) != NULL && !json_array_has_string(j_resp, json_string_value(json_object_get(j_registration, "frontchannel_logout_uri")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "frontchannel_logout_uri is not present in sector_identifier_uri content"); break; } } else { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "Invalid sector_identifier_uri response data, must be a JSON array"); break; } } else { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "Invalid sector_identifier_uri response status"); break; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_client_registration_valid - Error ulfius_send_http_request"); j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "Invalid sector_identifier_uri"); break; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_client_registration_valid - Error ulfius_set_request_properties"); j_error = json_pack("{ss}", "error", "server_error"); break; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_client_registration_valid - Error initializing request or response objects"); j_error = json_pack("{ss}", "error", "server_error"); break; } } if (json_object_get(config->j_params, "register-resource-specify-allowed") == json_true()) { if (json_object_get(j_registration, "resource") != NULL) { if (json_array_size(json_object_get(j_registration, "resource"))) { json_array_foreach(json_object_get(j_registration, "resource"), index, j_element) { resource = json_string_value(j_element); if (!is_redirect_uri_valid_without_credential(resource) || (0 != o_strncmp("https://", resource, o_strlen("https://")) && 0 != o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_1, resource, o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_1)) && 0 != o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_2, resource, o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_2)) && 0 != o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_3, resource, o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_3))) || o_strchr(resource, '#') != NULL) { // URL with fragment not allowed if (j_error == NULL) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "resource is optional and must be an array of urls"); } } } if (j_error != NULL) { break; } } } } if (json_object_get(config->j_params, "oauth-ciba-allowed") == json_true()) { if (json_object_get(j_registration, "backchannel_token_delivery_mode") != NULL && 0 != o_strcmp("poll", json_string_value(json_object_get(j_registration, "backchannel_token_delivery_mode"))) && 0 != o_strcmp("ping", json_string_value(json_object_get(j_registration, "backchannel_token_delivery_mode"))) && 0 != o_strcmp("push", json_string_value(json_object_get(j_registration, "backchannel_token_delivery_mode")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "backchannel_token_delivery_mode must have one of the following values: 'poll', 'ping', 'push'"); break; } if (json_object_get(config->j_params, "oauth-fapi-ciba-push-forbidden") == json_true()) { if (0 == o_strcmp("push", json_string_value(json_object_get(j_registration, "backchannel_token_delivery_mode")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "backchannel_token_delivery_mode value 'push' forbidden"); break; } } if (json_object_get(j_registration, "backchannel_token_delivery_mode") != NULL) { if (json_object_get(j_registration, "backchannel_client_notification_endpoint") != NULL) { resource = json_string_value(json_object_get(j_registration, "backchannel_client_notification_endpoint")); if ((0 != o_strncmp("https://", resource, o_strlen("https://")) && 0 != o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_1, resource, o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_1)) && 0 != o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_2, resource, o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_2)) && 0 != o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_3, resource, o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_3))) || o_strchr(resource, '#') != NULL) { // URL with fragment not allowed if (j_error == NULL) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "resource is optional and must be an array of urls"); } } } if (json_object_get(j_registration, "backchannel_client_notification_endpoint") == NULL && (0 == o_strcmp("ping", json_string_value(json_object_get(j_registration, "backchannel_token_delivery_mode"))) || 0 == o_strcmp("push", json_string_value(json_object_get(j_registration, "backchannel_token_delivery_mode"))))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "backchannel_client_notification_endpoint is mandatory when using backchannel_client_notification_endpoint 'ping' or 'push'"); break; } } if (json_object_get(j_registration, "backchannel_user_code_parameter") != NULL && !json_is_boolean(json_object_get(j_registration, "backchannel_user_code_parameter"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "backchannel_user_code_parameter is optional and must be a boolean"); break; } } if (json_object_get(j_registration, "access_token_signing_alg") != NULL) { if (!json_string_length(json_object_get(j_registration, "access_token_signing_alg")) || 0 == o_strcmp("none", json_string_value(json_object_get(j_registration, "access_token_signing_alg")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_access_token_signing_alg", "access_token_signing_alg is invalid"); break; } if (!json_array_has_string(json_object_get(json_object_get(j_info, "jws"), "alg"), json_string_value(json_object_get(j_registration, "access_token_signing_alg")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_access_token_signing_alg", "access_token_signing_alg not supported"); break; } } if (json_object_get(j_registration, "access_token_encryption_alg") != NULL) { if (!json_string_length(json_object_get(j_registration, "access_token_encryption_alg"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_access_token_encryption_alg", "access_token_encryption_alg is invalid"); break; } if (!json_array_has_string(json_object_get(json_object_get(j_info, "jwe"), "alg"), json_string_value(json_object_get(j_registration, "access_token_encryption_alg")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_access_token_encryption_alg", "access_token_encryption_alg not supported"); break; } } if (json_object_get(j_registration, "access_token_encryption_enc") != NULL) { if (!json_string_length(json_object_get(j_registration, "access_token_encryption_enc"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_access_token_encryption_enc", "access_token_encryption_enc is invalid"); break; } if (!json_array_has_string(json_object_get(json_object_get(j_info, "jwe"), "enc"), json_string_value(json_object_get(j_registration, "access_token_encryption_enc")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_access_token_encryption_enc", "access_token_encryption_enc not supported"); break; } if (json_object_get(j_registration, "access_token_encryption_alg") == NULL) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_access_token_encryption_enc", "access_token_encryption_alg is mandatory"); break; } } if (json_object_get(j_registration, "id_token_signing_alg") != NULL) { if (!json_string_length(json_object_get(j_registration, "id_token_signing_alg")) || 0 == o_strcmp("none", json_string_value(json_object_get(j_registration, "id_token_signing_alg")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_id_token_signing_alg", "id_token_signing_alg is invalid"); break; } if (!json_array_has_string(json_object_get(json_object_get(j_info, "jws"), "alg"), json_string_value(json_object_get(j_registration, "id_token_signing_alg")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_id_token_signing_alg", "id_token_signing_alg not supported"); break; } } if (json_object_get(j_registration, "id_token_encryption_alg") != NULL) { if (!json_string_length(json_object_get(j_registration, "id_token_encryption_alg"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_id_token_encryption_alg", "id_token_encryption_alg is invalid"); break; } if (!json_array_has_string(json_object_get(json_object_get(j_info, "jwe"), "alg"), json_string_value(json_object_get(j_registration, "id_token_encryption_alg")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_id_token_encryption_alg", "id_token_encryption_alg not supported"); break; } } if (json_object_get(j_registration, "id_token_encryption_enc") != NULL) { if (!json_string_length(json_object_get(j_registration, "id_token_encryption_enc"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_id_token_encryption_enc", "id_token_encryption_enc is invalid"); break; } if (!json_array_has_string(json_object_get(json_object_get(j_info, "jwe"), "enc"), json_string_value(json_object_get(j_registration, "id_token_encryption_enc")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_id_token_encryption_enc", "id_token_encryption_enc not supported"); break; } if (json_object_get(j_registration, "id_token_encryption_alg") == NULL) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_id_token_encryption_enc", "id_token_encryption_alg is mandatory"); break; } } if (json_object_get(j_registration, "userinfo_signing_alg") != NULL) { if (!json_string_length(json_object_get(j_registration, "userinfo_signing_alg")) || 0 == o_strcmp("none", json_string_value(json_object_get(j_registration, "userinfo_signing_alg")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_userinfo_signing_alg", "userinfo_signing_alg is invalid"); break; } if (!json_array_has_string(json_object_get(json_object_get(j_info, "jws"), "alg"), json_string_value(json_object_get(j_registration, "userinfo_signing_alg")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_userinfo_signing_alg", "userinfo_signing_alg not supported"); break; } } if (json_object_get(j_registration, "userinfo_encryption_alg") != NULL) { if (!json_string_length(json_object_get(j_registration, "userinfo_encryption_alg"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_userinfo_encryption_alg", "userinfo_encryption_alg is invalid"); break; } if (!json_array_has_string(json_object_get(json_object_get(j_info, "jwe"), "alg"), json_string_value(json_object_get(j_registration, "userinfo_encryption_alg")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_userinfo_encryption_alg", "userinfo_encryption_alg not supported"); break; } } if (json_object_get(j_registration, "userinfo_encryption_enc") != NULL) { if (!json_string_length(json_object_get(j_registration, "userinfo_encryption_enc"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_userinfo_encryption_enc", "userinfo_encryption_enc is invalid"); break; } if (!json_array_has_string(json_object_get(json_object_get(j_info, "jwe"), "enc"), json_string_value(json_object_get(j_registration, "userinfo_encryption_enc")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_userinfo_encryption_enc", "userinfo_encryption_enc not supported"); break; } if (json_object_get(j_registration, "userinfo_encryption_alg") == NULL) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_userinfo_encryption_enc", "userinfo_encryption_alg is mandatory"); break; } } if (json_object_get(j_registration, "request_object_signing_alg") != NULL) { if (!json_string_length(json_object_get(j_registration, "request_object_signing_alg")) || 0 == o_strcmp("none", json_string_value(json_object_get(j_registration, "request_object_signing_alg")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_request_object_signing_alg", "request_object_signing_alg is invalid"); break; } if (!json_array_has_string(json_object_get(json_object_get(j_info, "jws"), "alg"), json_string_value(json_object_get(j_registration, "request_object_signing_alg")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_request_object_signing_alg", "request_object_signing_alg not supported"); break; } } if (json_object_get(j_registration, "request_object_encryption_alg") != NULL) { if (!json_string_length(json_object_get(j_registration, "request_object_encryption_alg"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_request_object_encryption_alg", "request_object_encryption_alg is invalid"); break; } if (!json_array_has_string(json_object_get(json_object_get(j_info, "jwe"), "alg"), json_string_value(json_object_get(j_registration, "request_object_encryption_alg")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_request_object_encryption_alg", "request_object_encryption_alg not supported"); break; } } if (json_object_get(j_registration, "request_object_encryption_enc") != NULL) { if (!json_string_length(json_object_get(j_registration, "request_object_encryption_enc"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_request_object_encryption_enc", "request_object_encryption_enc is invalid"); break; } if (!json_array_has_string(json_object_get(json_object_get(j_info, "jwe"), "enc"), json_string_value(json_object_get(j_registration, "request_object_encryption_enc")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_request_object_encryption_enc", "request_object_encryption_enc not supported"); break; } if (json_object_get(j_registration, "request_object_encryption_alg") == NULL) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_request_object_encryption_enc", "request_object_encryption_alg is mandatory"); break; } } if (json_object_get(j_registration, "token_endpoint_signing_alg") != NULL) { if (!json_string_length(json_object_get(j_registration, "token_endpoint_signing_alg")) || 0 == o_strcmp("none", json_string_value(json_object_get(j_registration, "token_endpoint_signing_alg")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_token_endpoint_signing_alg", "token_endpoint_signing_alg is invalid"); break; } if (!json_array_has_string(json_object_get(json_object_get(j_info, "jws"), "alg"), json_string_value(json_object_get(j_registration, "token_endpoint_signing_alg")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_token_endpoint_signing_alg", "token_endpoint_signing_alg not supported"); break; } } if (json_object_get(j_registration, "token_endpoint_encryption_alg") != NULL) { if (!json_string_length(json_object_get(j_registration, "token_endpoint_encryption_alg"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_token_endpoint_encryption_alg", "token_endpoint_encryption_alg is invalid"); break; } if (!json_array_has_string(json_object_get(json_object_get(j_info, "jwe"), "alg"), json_string_value(json_object_get(j_registration, "token_endpoint_encryption_alg")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_token_endpoint_encryption_alg", "token_endpoint_encryption_alg not supported"); break; } } if (json_object_get(j_registration, "token_endpoint_encryption_enc") != NULL) { if (!json_string_length(json_object_get(j_registration, "token_endpoint_encryption_enc"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_token_endpoint_encryption_enc", "token_endpoint_encryption_enc is invalid"); break; } if (!json_array_has_string(json_object_get(json_object_get(j_info, "jwe"), "enc"), json_string_value(json_object_get(j_registration, "token_endpoint_encryption_enc")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_token_endpoint_encryption_enc", "token_endpoint_encryption_enc not supported"); break; } if (json_object_get(j_registration, "token_endpoint_encryption_alg") == NULL) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_token_endpoint_encryption_enc", "token_endpoint_encryption_alg is mandatory"); break; } } if (json_object_get(j_registration, "backchannel_authentication_request_signing_alg") != NULL) { if (!json_string_length(json_object_get(j_registration, "backchannel_authentication_request_signing_alg")) || 0 == o_strcmp("none", json_string_value(json_object_get(j_registration, "backchannel_authentication_request_signing_alg")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_backchannel_authentication_request_signing_alg", "backchannel_authentication_request_signing_alg is invalid"); break; } if (!json_array_has_string(json_object_get(json_object_get(j_info, "jws"), "alg"), json_string_value(json_object_get(j_registration, "backchannel_authentication_request_signing_alg")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_backchannel_authentication_request_signing_alg", "backchannel_authentication_request_signing_alg not supported"); break; } } if (json_object_get(j_registration, "backchannel_authentication_request_encryption_alg") != NULL) { if (!json_string_length(json_object_get(j_registration, "backchannel_authentication_request_encryption_alg"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_backchannel_authentication_request_encryption_alg", "backchannel_authentication_request_encryption_alg is invalid"); break; } if (!json_array_has_string(json_object_get(json_object_get(j_info, "jwe"), "alg"), json_string_value(json_object_get(j_registration, "backchannel_authentication_request_encryption_alg")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_backchannel_authentication_request_encryption_alg", "backchannel_authentication_request_encryption_alg not supported"); break; } } if (json_object_get(j_registration, "backchannel_authentication_request_encryption_enc") != NULL) { if (!json_string_length(json_object_get(j_registration, "backchannel_authentication_request_encryption_enc"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_backchannel_authentication_request_encryption_enc", "backchannel_authentication_request_encryption_enc is invalid"); break; } if (!json_array_has_string(json_object_get(json_object_get(j_info, "jwe"), "enc"), json_string_value(json_object_get(j_registration, "backchannel_authentication_request_encryption_enc")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_backchannel_authentication_request_encryption_enc", "backchannel_authentication_request_encryption_enc not supported"); break; } if (json_object_get(j_registration, "backchannel_authentication_request_encryption_alg") == NULL) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_backchannel_authentication_request_encryption_enc", "backchannel_authentication_request_encryption_alg is mandatory"); break; } } if (json_object_get(j_registration, "authorization_signed_response_alg") != NULL) { if (!json_string_length(json_object_get(j_registration, "authorization_signed_response_alg")) || 0 == o_strcmp("none", json_string_value(json_object_get(j_registration, "authorization_signed_response_alg")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_authorization_signing_alg", "authorization_signed_response_alg is invalid"); break; } if (!json_array_has_string(json_object_get(json_object_get(j_info, "jws"), "alg"), json_string_value(json_object_get(j_registration, "authorization_signed_response_alg")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_authorization_signing_alg", "authorization_signed_response_alg not supported"); break; } } if (json_object_get(j_registration, "authorization_encrypted_response_alg") != NULL) { if (!json_string_length(json_object_get(j_registration, "authorization_encrypted_response_alg"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_authorization_encryption_alg", "authorization_encrypted_response_alg is invalid"); break; } if (!json_array_has_string(json_object_get(json_object_get(j_info, "jwe"), "alg"), json_string_value(json_object_get(j_registration, "authorization_encrypted_response_alg")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_authorization_encryption_alg", "authorization_encrypted_response_alg not supported"); break; } } if (json_object_get(j_registration, "authorization_encrypted_response_enc") != NULL) { if (!json_string_length(json_object_get(j_registration, "authorization_encrypted_response_enc"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_authorization_encryption_enc", "authorization_encrypted_response_enc is invalid"); break; } if (!json_array_has_string(json_object_get(json_object_get(j_info, "jwe"), "enc"), json_string_value(json_object_get(j_registration, "authorization_encrypted_response_enc")))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_authorization_encryption_enc", "authorization_encrypted_response_enc not supported"); break; } if (json_object_get(j_registration, "authorization_encrypted_response_alg") == NULL) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_authorization_encryption_enc", "authorization_encrypted_response_alg is mandatory"); break; } } if (json_object_get(j_registration, "response_mode") != NULL) { response_mode = json_string_value(json_object_get(j_registration, "response_mode")); j_response_modes_supported = json_pack("[sss]", "query", "fragment", "form_post"); if (json_object_get(config->j_params, "oauth-fapi-allow-jarm") == json_true()) { json_array_append_new(j_response_modes_supported, json_string("jwt")); json_array_append_new(j_response_modes_supported, json_string("query.jwt")); json_array_append_new(j_response_modes_supported, json_string("fragment.jwt")); json_array_append_new(j_response_modes_supported, json_string("form_post.jwt")); } if (!json_array_has_string(j_response_modes_supported, response_mode)) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_response_mode", "response_mode value not supported"); break; } } if (json_object_get(j_registration, "post_logout_redirect_uri") != NULL) { resource = json_string_value(json_object_get(j_registration, "post_logout_redirect_uri")); if (!is_redirect_uri_valid_without_credential(resource) || (0 != o_strncmp("https://", resource, o_strlen("https://")) && 0 != o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_1, resource, o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_1)) && 0 != o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_2, resource, o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_2)) && 0 != o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_3, resource, o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_3)))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "post_logout_redirect_uri is not a valid uri"); break; } } if (json_object_get(j_registration, "frontchannel_logout_uri") != NULL) { resource = json_string_value(json_object_get(j_registration, "frontchannel_logout_uri")); if (!is_redirect_uri_valid_without_credential(resource) || (0 != o_strncmp("https://", resource, o_strlen("https://")) && 0 != o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_1, resource, o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_1)) && 0 != o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_2, resource, o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_2)) && 0 != o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_3, resource, o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_3)))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "frontchannel_logout_uri is not a valid uri"); break; } } if (json_object_get(j_registration, "frontchannel_logout_session_required") != NULL) { if (!json_is_boolean(json_object_get(j_registration, "frontchannel_logout_session_required"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "frontchannel_logout_session_required is not a boolean"); break; } if (json_object_get(j_registration, "frontchannel_logout_session_required") == json_true()) { json_object_set_new(j_registration, "frontchannel_logout_session_required", json_string("1")); } else { json_object_set_new(j_registration, "frontchannel_logout_session_required", json_string("0")); } } if (json_object_get(j_registration, "backchannel_logout_uri") != NULL) { resource = json_string_value(json_object_get(j_registration, "backchannel_logout_uri")); if (!is_redirect_uri_valid_without_credential(resource) || (0 != o_strncmp("https://", resource, o_strlen("https://")) && 0 != o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_1, resource, o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_1)) && 0 != o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_2, resource, o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_2)) && 0 != o_strncmp(GLEWLWYD_REDIRECT_URI_LOOPBACK_3, resource, o_strlen(GLEWLWYD_REDIRECT_URI_LOOPBACK_3)))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "backchannel_logout_uri is not a valid uri"); break; } } if (json_object_get(j_registration, "backchannel_logout_session_required") != NULL) { if (!json_is_boolean(json_object_get(j_registration, "backchannel_logout_session_required"))) { j_error = json_pack("{ssss}", "error", "invalid_client_metadata", "error_description", "backchannel_logout_session_required is not a boolean"); break; } if (json_object_get(j_registration, "backchannel_logout_session_required") == json_true()) { json_object_set_new(j_registration, "backchannel_logout_session_required", json_string("1")); } else { json_object_set_new(j_registration, "backchannel_logout_session_required", json_string("0")); } } } while(0); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_resp); json_decref(j_info); json_decref(j_response_modes_supported); if (j_error != NULL) { j_return = json_pack("{siso}", "result", G_ERROR_PARAM, "error", j_error); } else { j_return = json_pack("{si}", "result", G_OK); } return j_return; } static char * build_jwt_auth_response(struct _oidc_config * config, json_t * j_client, struct _u_map * map_query, int * enc_res) { jwt_t * jwt; jwa_alg alg = get_token_sign_alg(config, j_client, GLEWLWYD_TOKEN_TYPE_AUTH); jwk_t * jwk = get_jwk_sign(config, j_client, alg); time_t now; char * token = NULL, * out_token = NULL; const char ** keys, * value; size_t i; time(&now); if (jwk != NULL && alg != R_JWA_ALG_UNKNOWN) { if (r_jwt_init(&jwt) == RHN_OK) { if (r_jwt_set_properties(jwt, RHN_OPT_SIG_ALG, alg, RHN_OPT_CLAIM_JSON_T_VALUE, "iss", json_object_get(config->j_params, "iss"), RHN_OPT_CLAIM_JSON_T_VALUE, "aud", json_object_get(j_client, "client_id"), RHN_OPT_CLAIM_RHN_INT_VALUE, "exp", ((rhn_int_t)now)+config->access_token_duration, RHN_OPT_NONE) == RHN_OK) { keys = u_map_enum_keys(map_query); for (i=0; keys[i]!=NULL; i++) { value = u_map_get(map_query, keys[i]); if (o_strlen(value)) { r_jwt_set_claim_str_value(jwt, keys[i], value); } } if ((token = r_jwt_serialize_signed(jwt, jwk, 0)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "build_jwt_auth_response - Error r_jwt_serialize_signed"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "build_jwt_auth_response - Error r_jwt_set_properties"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "build_jwt_auth_response - oidc - Error r_jwt_init"); } r_jwt_free(jwt); } else { y_log_message(Y_LOG_LEVEL_ERROR, "build_jwt_auth_response - oidc - Error no jwk available"); } r_jwk_free(jwk); if (token != NULL) { out_token = encrypt_token_if_required(config, token, j_client, GLEWLWYD_TOKEN_TYPE_AUTH, enc_res); o_free(token); } return out_token; } static void build_auth_response(struct _oidc_config * config, struct _u_response * response, int response_mode, json_t * j_client, const char * redirect_uri, struct _u_map * map_query) { const char ** keys, * value, * sep; size_t i; char * redirect_url = NULL, * key_encoded, * value_encoded, * token = NULL; int enc_res = G_OK, has_param = 0;; if (o_strlen(redirect_uri)) { if (response_mode == GLEWLWYD_RESPONSE_MODE_QUERY_JWT || response_mode == GLEWLWYD_RESPONSE_MODE_FRAGMENT_JWT || response_mode == GLEWLWYD_RESPONSE_MODE_FORM_POST_JWT) { token = build_jwt_auth_response(config, j_client, map_query, &enc_res); } switch (response_mode) { case GLEWLWYD_RESPONSE_MODE_QUERY: redirect_url = o_strdup(redirect_uri); if (json_object_get(config->j_params, "oauth-as-iss-id") == json_true()) { value_encoded = ulfius_url_encode(json_string_value(json_object_get(config->j_params, "iss"))); redirect_url = mstrcatf(redirect_url, "%ciss=%s", (o_strchr(redirect_url, '?')!=NULL?'&':'?'), value_encoded); o_free(value_encoded); } keys = u_map_enum_keys(map_query); for (i=0; keys[i]!=NULL; i++) { value = u_map_get(map_query, keys[i]); if (o_strlen(value)) { value_encoded = ulfius_url_encode(value); redirect_url = mstrcatf(redirect_url, "%c%s=%s", (o_strchr(redirect_url, '?')!=NULL?'&':'?'), keys[i], value_encoded); o_free(value_encoded); } else { redirect_url = mstrcatf(redirect_url, "%c%s", (o_strchr(redirect_url, '?')!=NULL?'&':'?'), keys[i]); } } response->status = 302; ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); break; case GLEWLWYD_RESPONSE_MODE_FRAGMENT: redirect_url = msprintf("%s#", redirect_uri); if (json_object_get(config->j_params, "oauth-as-iss-id") == json_true()) { value_encoded = ulfius_url_encode(json_string_value(json_object_get(config->j_params, "iss"))); redirect_url = mstrcatf(redirect_url, "iss=%s", value_encoded); o_free(value_encoded); has_param = 1; } keys = u_map_enum_keys(map_query); for (i=0; keys[i]!=NULL; i++) { value = u_map_get(map_query, keys[i]); if (has_param) { sep = "&"; } else { sep = ""; has_param = 1; } if (o_strlen(value)) { value_encoded = ulfius_url_encode(value); redirect_url = mstrcatf(redirect_url, "%s%s=%s", sep, keys[i], value_encoded); o_free(value_encoded); } else { redirect_url = mstrcatf(redirect_url, "%s%s", sep, keys[i]); } } response->status = 302; ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); break; case GLEWLWYD_RESPONSE_MODE_FORM_POST: redirect_url = msprintf("Glewlwyd
", redirect_uri); if (json_object_get(config->j_params, "oauth-as-iss-id") == json_true()) { value_encoded = ulfius_url_encode(json_string_value(json_object_get(config->j_params, "iss"))); redirect_url = mstrcatf(redirect_url, "", value_encoded); o_free(value_encoded); } keys = u_map_enum_keys(map_query); for (i=0; keys[i]!=NULL; i++) { value = u_map_get(map_query, keys[i]); key_encoded = ulfius_url_encode(keys[i]); if (o_strlen(value)) { value_encoded = ulfius_url_encode(value); redirect_url = mstrcatf(redirect_url, "", key_encoded, value_encoded); o_free(value_encoded); } else { redirect_url = mstrcatf(redirect_url, "", key_encoded); } o_free(key_encoded); } redirect_url = mstrcatf(redirect_url, "
"); ulfius_set_string_body_response(response, 200, redirect_url); o_free(redirect_url); break; case GLEWLWYD_RESPONSE_MODE_QUERY_JWT: if (token != NULL) { redirect_url = msprintf("%s%cresponse=%s", redirect_uri, (o_strchr(redirect_uri, '?')!=NULL?'&':'?'), token); response->status = 302; ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } else if (enc_res == G_ERROR_UNAUTHORIZED) { redirect_url = msprintf("%s%cerror=invalid_request&error_description=invalid+encryption+parameters", redirect_uri, (o_strchr(redirect_uri, '?')!=NULL?'&':'?')); response->status = 302; ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } else { redirect_url = msprintf("%s%cerror=server_error", redirect_uri, (o_strchr(redirect_uri, '?')!=NULL?'&':'?')); response->status = 302; ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } break; case GLEWLWYD_RESPONSE_MODE_FRAGMENT_JWT: if (token != NULL) { redirect_url = msprintf("%s#response=%s", redirect_uri, token); response->status = 302; ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } else if (enc_res == G_ERROR_UNAUTHORIZED) { redirect_url = msprintf("%s#error=invalid_request&error_description=invalid+encryption+parameters", redirect_uri); response->status = 302; ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } else { redirect_url = msprintf("%s#error=server_error", redirect_uri, token); response->status = 302; ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } break; case GLEWLWYD_RESPONSE_MODE_FORM_POST_JWT: if (token != NULL) { redirect_url = msprintf("Glewlwyd
", redirect_uri, token); ulfius_set_string_body_response(response, 200, redirect_url); o_free(redirect_url); } else if (enc_res == G_ERROR_UNAUTHORIZED) { redirect_url = msprintf("Glewlwyd
", redirect_uri); ulfius_set_string_body_response(response, 200, redirect_url); o_free(redirect_url); } else { redirect_url = msprintf("Glewlwyd
", redirect_uri); ulfius_set_string_body_response(response, 200, redirect_url); o_free(redirect_url); } break; } } else { response->status = 403; } o_free(token); } static int generate_check_session_iframe(struct _oidc_config * config) { if ((config->check_session_iframe = msprintf(" Glewlwydcheck_session_iframe iframe ", config->glewlwyd_config->glewlwyd_config->external_url, config->glewlwyd_config->glewlwyd_config->api_prefix)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "generate_check_session_iframe oidc - Error generating check_session_iframe"); return G_ERROR; } else { return G_OK; } } static char * generate_session_state(const char * client_id, const char * redirect_uri, const char * username) { char salt[GLEWLWYD_DEFAULT_SALT_LENGTH+1] = {0}, * session_state = NULL, * origin = NULL, * intermediate = NULL; unsigned char intermediate_hash[32] = {0}, intermediate_hash_b64[64] = {0}; size_t intermediate_hash_len = 32, intermediate_hash_b64_len = 0; if (o_strlen(client_id) && (0 == o_strncmp(redirect_uri, "http://", o_strlen("http://")) || 0 == o_strncmp(redirect_uri, "https://", o_strlen("https://"))) && o_strlen(username)) { origin = o_strdup(redirect_uri); *(o_strchr(o_strstr(origin, "://")+3, '/')) = '\0'; rand_string_nonce(salt, GLEWLWYD_DEFAULT_SALT_LENGTH); intermediate = msprintf("%s %s %s %s", client_id, origin, username, salt); if (generate_digest_raw(digest_SHA256, (const unsigned char *)intermediate, o_strlen(intermediate), intermediate_hash, &intermediate_hash_len)) { if (o_base64_encode(intermediate_hash, intermediate_hash_len, intermediate_hash_b64, &intermediate_hash_b64_len)) { intermediate_hash_b64[intermediate_hash_b64_len] = '\0'; session_state = msprintf("%s.%s", intermediate_hash_b64, salt); } } o_free(intermediate); o_free(origin); } return session_state; } static json_t * generate_device_authorization(struct _oidc_config * config, const char * client_id, const char * scope_list, const char * resource, json_t * j_authorization_details, const char * ip_source) { char device_code[GLEWLWYD_DEVICE_AUTH_DEVICE_CODE_LENGTH+1] = {0}, user_code[GLEWLWYD_DEVICE_AUTH_USER_CODE_LENGTH+2] = {0}, * device_code_hash = NULL, * user_code_hash = NULL; json_t * j_return, * j_query, * j_device_auth_id; int res; time_t now, expiration = (time_t)json_integer_value(json_object_get(config->j_params, "device-authorization-expiration")); char * expires_at_clause = NULL, * last_check_clause = NULL, ** scope_array = NULL, * str_authorization_details = NULL; size_t i; if (pthread_mutex_lock(&config->insert_lock)) { y_log_message(Y_LOG_LEVEL_ERROR, "generate_device_authorization oidc - Error pthread_mutex_lock"); j_return = json_pack("{si}", "result", G_ERROR); } else { if (rand_string(device_code, 32) != NULL && rand_string_from_charset(user_code, GLEWLWYD_DEVICE_AUTH_USER_CODE_LENGTH+1, "ABCDEFGHJKLMNOPQRSTUVWXYZ0123456789") != NULL) { user_code[4] = '-'; device_code_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, device_code); user_code_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, user_code); time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("FROM_UNIXTIME(%u)", (now + expiration)); last_check_clause = msprintf("FROM_UNIXTIME(%u)", (now - (2*expiration))); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("TO_TIMESTAMP(%u)", (now + expiration)); last_check_clause = msprintf("TO_TIMESTAMP(%u)", (now - (2*expiration))); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("%u", (now + expiration)); last_check_clause = msprintf("%u", (now - (2*expiration))); } if (j_authorization_details != NULL) { str_authorization_details = json_dumps(j_authorization_details, JSON_COMPACT); } j_query = json_pack("{sss{sssss{ss}sssssss{ss}ss?ss?}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_DEVICE_AUTHORIZATION, "values", "gpoda_plugin_name", config->name, "gpoda_client_id", client_id, "gpoda_expires_at", "raw", expires_at_clause, "gpoda_issued_for", ip_source, "gpoda_device_code_hash", device_code_hash, "gpoda_user_code_hash", user_code_hash, "gpoda_last_check", "raw", last_check_clause, "gpoda_resource", resource, "gpoda_authorization_details", str_authorization_details); o_free(expires_at_clause); o_free(last_check_clause); o_free(device_code_hash); o_free(user_code_hash); o_free(str_authorization_details); res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_device_auth_id = h_last_insert_id(config->glewlwyd_config->glewlwyd_config->conn); if (j_device_auth_id != NULL) { if (split_string(scope_list, " ", &scope_array) > 0) { j_query = json_pack("{sss[]}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_DEVICE_AUTHORIZATION_SCOPE, "values"); for (i=0; scope_array[i]!=NULL; i++) { json_array_append_new(json_object_get(j_query, "values"), json_pack("{sOss}", "gpoda_id", j_device_auth_id, "gpodas_scope", scope_array[i])); } res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_return = json_pack("{sis{ssss}}", "result", G_OK, "authorization", "device_code", device_code, "user_code", user_code); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_device_authorization - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_device_authorization - Error split_string scope"); j_return = json_pack("{si}", "result", G_ERROR); } free_string_array(scope_array); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_device_authorization - Error h_last_insert_id"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_device_auth_id); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_device_authorization - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_device_authorization - Error generating random code"); j_return = json_pack("{si}", "result", G_ERROR); } pthread_mutex_unlock(&config->insert_lock); } return j_return; } static int validate_device_authorization_scope(struct _oidc_config * config, json_int_t gpoda_id, const char * username, const char * scope_list, json_t * j_amr, const char * sid) { char * query, * scope_clause = NULL, * scope_escaped, ** scope_array = NULL, * username_escaped, * sid_sescaped; int res, i, ret; json_t * j_query, * j_element = NULL; size_t index = 0; if (split_string(scope_list, " ", &scope_array) > 0) { for (i=0; scope_array[i]!=NULL; i++) { scope_escaped = h_escape_string_with_quotes(config->glewlwyd_config->glewlwyd_config->conn, scope_array[i]); if (scope_clause == NULL) { scope_clause = o_strdup(scope_escaped); } else { scope_clause = mstrcatf(scope_clause, ",%s", scope_escaped); } o_free(scope_escaped); } free_string_array(scope_array); } if (o_strlen(scope_clause)) { query = msprintf("UPDATE %s set gpodas_allowed=1 WHERE gpodas_scope IN (%s) AND gpoda_id=%"JSON_INTEGER_FORMAT, GLEWLWYD_PLUGIN_OIDC_TABLE_DEVICE_AUTHORIZATION_SCOPE, scope_clause, gpoda_id); res = h_execute_query(config->glewlwyd_config->glewlwyd_config->conn, query, NULL, H_OPTION_EXEC); o_free(query); if (res == H_OK) { username_escaped = h_escape_string_with_quotes(config->glewlwyd_config->glewlwyd_config->conn, username); sid_sescaped = h_escape_string_with_quotes(config->glewlwyd_config->glewlwyd_config->conn, sid); query = msprintf("UPDATE %s set gpoda_status=1, gpoda_username=%s, gpoda_sid=%s WHERE gpoda_id=%"JSON_INTEGER_FORMAT, GLEWLWYD_PLUGIN_OIDC_TABLE_DEVICE_AUTHORIZATION, username_escaped, sid_sescaped, gpoda_id); res = h_execute_query(config->glewlwyd_config->glewlwyd_config->conn, query, NULL, H_OPTION_EXEC); o_free(username_escaped); o_free(sid_sescaped); o_free(query); if (res == H_OK) { if (json_array_size(j_amr)) { j_query = json_pack("{sss[]}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_DEVICE_SCHEME, "values"); json_array_foreach(j_amr, index, j_element) { json_array_append_new(json_object_get(j_query, "values"), json_pack("{sIsO}", "gpoda_id", gpoda_id, "gpodh_scheme_module", j_element)); } res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_device_authorization_scope - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { ret = G_OK; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_device_authorization_scope - Error executing query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_device_authorization_scope - Error executing query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_device_authorization_scope - Error scope invalid"); ret = G_ERROR_PARAM; } o_free(scope_clause); return ret; } static json_t * validate_device_auth_user_code(struct _oidc_config * config, const char * user_code) { json_t * j_query = NULL, * j_result = NULL, * j_result_scope = NULL, * j_return, * j_element = NULL; int res; char * scope = NULL, * expires_at_clause, * user_code_hash, user_code_ucase[GLEWLWYD_DEVICE_AUTH_USER_CODE_LENGTH+2] = {0}; time_t now; size_t index = 0; if (o_strlen(user_code) == GLEWLWYD_DEVICE_AUTH_USER_CODE_LENGTH+1 && user_code[4] == '-') { for (index=0; index<(GLEWLWYD_DEVICE_AUTH_USER_CODE_LENGTH+1); index++) { user_code_ucase[index] = toupper(user_code[index]); } user_code_ucase[GLEWLWYD_DEVICE_AUTH_USER_CODE_LENGTH+1] = '\0'; time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } user_code_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, user_code_ucase); j_query = json_pack("{sss[ss]s{sss{ssss}sssi}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_DEVICE_AUTHORIZATION, "columns", "gpoda_id", "gpoda_client_id", "where", "gpoda_plugin_name", config->name, "gpoda_expires_at", "operator", "raw", "value", expires_at_clause, "gpoda_user_code_hash", user_code_hash, "gpoda_status", 0); o_free(expires_at_clause); o_free(user_code_hash); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_query = json_pack("{sss[s]s{sO}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_DEVICE_AUTHORIZATION_SCOPE, "columns", "gpodas_scope", "where", "gpoda_id", json_object_get(json_array_get(j_result, 0), "gpoda_id")); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result_scope, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result_scope, index, j_element) { if (scope == NULL) { scope = o_strdup(json_string_value(json_object_get(j_element, "gpodas_scope"))); } else { scope = mstrcatf(scope, " %s", json_string_value(json_object_get(j_element, "gpodas_scope"))); } } j_return = json_pack("{sis{sOsssO}}", "result", G_OK, "device_auth", "client_id", json_object_get(json_array_get(j_result, 0), "gpoda_client_id"), "scope", scope, "gpoda_id", json_object_get(json_array_get(j_result, 0), "gpoda_id")); o_free(scope); json_decref(j_result_scope); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_device_auth_user_code - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_device_auth_user_code - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } return j_return; } static int check_auth_type_device_code(const struct _u_request * request, struct _u_response * response, void * user_data, json_t * j_assertion_client, const char * x5t_s256, int client_auth_method) { struct _oidc_config * config = (struct _oidc_config *)user_data; json_t * j_body, * j_client, * j_query, * j_result = NULL, * j_result_scope = NULL, * j_result_sheme = NULL, * j_element = NULL, * j_user = NULL, * j_refresh_token = NULL, * j_refresh = NULL, * j_amr = NULL, * j_jkt = NULL; const char * device_code = u_map_get(request->map_post_body, "device_code"), * client_id = request->auth_basic_user, * client_secret = request->auth_basic_password, * ip_source = get_ip_source(request), * username = NULL, * resource = NULL; int res, resource_checked = 0, r_enc_res = G_OK, a_enc_res = G_OK, i_enc_res = G_OK; char * device_code_hash = NULL, * refresh_token = NULL, * refresh_token_out = NULL, * access_token = NULL, * access_token_out = NULL, * id_token = NULL, * id_token_out = NULL, jti[OIDC_JTI_LENGTH+1] = {0}, jti_r[OIDC_JTI_LENGTH+1] = {0}, * scope = NULL, * issued_for = get_client_hostname(request); time_t now; size_t index = 0; if (client_id == NULL && u_map_get(request->map_post_body, "client_id") != NULL) { client_id = u_map_get(request->map_post_body, "client_id"); } if (client_secret == NULL && u_map_get(request->map_post_body, "client_secret") != NULL) { client_secret = u_map_get(request->map_post_body, "client_secret"); client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_POST; } else if (client_secret != NULL) { client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_BASIC; } if (json_object_get(config->j_params, "resource-allowed") == json_true()) { resource = u_map_get(request->map_post_body, "resource"); } if (o_strlen(device_code) == GLEWLWYD_DEVICE_AUTH_DEVICE_CODE_LENGTH) { if (j_assertion_client != NULL) { j_client = json_pack("{sisO}", "result", G_OK, "client", j_assertion_client); } else { j_client = check_client_valid(config, client_id, client_secret, NULL, GLEWLWYD_AUTHORIZATION_TYPE_DEVICE_AUTHORIZATION_FLAG, 0, ip_source); } if (check_result_value(j_client, G_OK) && is_client_auth_method_allowed(json_object_get(j_client, "client"), client_auth_method)) { device_code_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, device_code); j_query = json_pack("{sss[ssssssss]s{sssOs{ssss}}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_DEVICE_AUTHORIZATION, "columns", "gpoda_id", "gpoda_username AS username", "gpoda_status", SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpoda_expires_at) AS expires_at", "gpoda_expires_at AS expires_at", "EXTRACT(EPOCH FROM gpoda_expires_at)::integer AS expires_at"), SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpoda_last_check) AS last_check", "gpoda_last_check AS last_check", "EXTRACT(EPOCH FROM gpoda_last_check)::integer AS last_check"), "gpoda_resource AS resource", "gpoda_authorization_details", "gpoda_sid AS sid", "where", "gpoda_device_code_hash", device_code_hash, "gpoda_client_id", json_object_get(json_object_get(j_client, "client"), "client_id"), "gpoda_status", "operator", "raw", "value", "<= 1"); o_free(device_code_hash); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { time(&now); if ((time_t)json_integer_value(json_object_get(json_array_get(j_result, 0), "expires_at")) >= now) { if (json_integer_value(json_object_get(json_array_get(j_result, 0), "gpoda_status")) == 1) { if (json_object_get(json_array_get(j_result, 0), "gpoda_authorization_details") != json_null()) { json_object_set_new(json_array_get(j_result, 0), "authorization_details", json_loads(json_string_value(json_object_get(json_array_get(j_result, 0), "gpoda_authorization_details")), JSON_DECODE_ANY, NULL)); } json_object_del(json_array_get(j_result, 0), "gpoda_authorization_details"); j_query = json_pack("{sss[s]s{sOsi}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_DEVICE_AUTHORIZATION_SCOPE, "columns", "gpodas_scope", "where", "gpoda_id", json_object_get(json_array_get(j_result, 0), "gpoda_id"), "gpodas_allowed", 1); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result_scope, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result_scope, index, j_element) { if (scope == NULL) { scope = o_strdup(json_string_value(json_object_get(j_element, "gpodas_scope"))); } else { scope = mstrcatf(scope, " %s", json_string_value(json_object_get(j_element, "gpodas_scope"))); } } j_query = json_pack("{sss[s]s{sO}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_DEVICE_SCHEME, "columns", "gpodh_scheme_module AS scheme_module", "where", "gpoda_id", json_object_get(json_array_get(j_result, 0), "gpoda_id")); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result_sheme, NULL); json_decref(j_query); if (res == H_OK) { if ((j_amr = json_array()) != NULL) { json_array_foreach(j_result_sheme, index, j_element) { json_array_append(j_amr, json_object_get(j_element, "scheme_module")); } j_refresh = get_refresh_token_duration_rolling(config, scope); if (check_result_value(j_refresh, G_OK)) { // All clear, please send back tokens username = json_string_value(json_object_get(json_array_get(j_result, 0), "username")); j_user = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, username); if (check_result_value(j_user, G_OK)) { time(&now); j_jkt = oidc_verify_dpop_proof(config, request, "POST", "/token"); if (check_result_value(j_jkt, G_OK)) { if (json_object_get(j_jkt, "jkt") == NULL || (res = check_dpop_jti(config, json_string_value(json_object_get(json_object_get(j_jkt, "claims"), "jti")), json_string_value(json_object_get(json_object_get(j_jkt, "claims"), "htm")), json_string_value(json_object_get(json_object_get(j_jkt, "claims"), "htu")), json_integer_value(json_object_get(json_object_get(j_jkt, "claims"), "iat")), client_id, json_string_value(json_object_get(j_jkt, "jkt")), ip_source)) == G_OK) { if ((refresh_token = generate_refresh_token()) != NULL) { y_log_message(Y_LOG_LEVEL_INFO, "Event oidc - Plugin '%s' - Refresh token generated for client '%s' granted by user '%s' with scope list '%s', origin: %s", config->name, client_id, username, scope, get_ip_source(request)); if (o_strlen(resource)) { if ((res = verify_resource(config, resource, json_object_get(j_client, "client"), scope)) == G_OK) { resource_checked = 1; } else if (res == G_ERROR_PARAM) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_auth_type_device_code - oidc - Error resource unauthorized"); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "check_auth_type_device_code - oidc - Error verify_resource"); } if (resource_checked && 0 != o_strcmp(resource, json_string_value(json_object_get(json_array_get(j_result, 0), "resource")))) { resource_checked = 0; y_log_message(Y_LOG_LEVEL_DEBUG, "check_auth_type_device_code - oidc - Error resource change unauthorized"); } else { resource_checked = 1; } } else { if (json_object_get(json_array_get(j_result, 0), "resource") != json_null()) { resource = json_string_value(json_object_get(json_array_get(j_result, 0), "resource")); } resource_checked = 1; } if (resource_checked) { j_refresh_token = serialize_refresh_token(config, GLEWLWYD_AUTHORIZATION_TYPE_DEVICE_AUTHORIZATION, 0, username, client_id, scope, resource, now, json_integer_value(json_object_get(json_object_get(j_refresh, "refresh-token"), "refresh-token-duration")), json_object_get(json_object_get(j_refresh, "refresh-token"), "refresh-token-rolling")==json_true(), NULL, refresh_token, issued_for, u_map_get_case(request->map_header, "user-agent"), jti_r, json_string_value(json_object_get(j_jkt, "jkt")), json_object_get(json_array_get(j_result, 0), "authorization_details")); if (check_result_value(j_refresh_token, G_OK)) { if ((access_token = generate_access_token(config, username, json_object_get(j_client, "client"), json_object_get(j_user, "user"), scope, NULL, resource, now, jti, x5t_s256, json_string_value(json_object_get(j_jkt, "jkt")), json_object_get(json_array_get(j_result, 0), "authorization_details"), get_ip_source(request))) != NULL) { if (serialize_access_token(config, GLEWLWYD_AUTHORIZATION_TYPE_DEVICE_AUTHORIZATION, json_integer_value(json_object_get(j_refresh_token, "gpgr_id")), username, client_id, scope, resource, now, issued_for, u_map_get_case(request->map_header, "user-agent"), access_token, jti, json_object_get(json_array_get(j_result, 0), "authorization_details")) == G_OK) { if ((id_token = generate_id_token(config, username, json_object_get(j_user, "user"), json_object_get(j_client, "client"), now, now, NULL, j_amr, access_token, NULL, scope, NULL, NULL, NULL, NULL, json_string_value(json_object_get(json_array_get(j_result, 0), "sid")), ip_source)) != NULL) { if (serialize_id_token(config, GLEWLWYD_AUTHORIZATION_TYPE_DEVICE_AUTHORIZATION, id_token, username, client_id, json_string_value(json_object_get(json_array_get(j_result, 0), "sid")), 0, json_integer_value(json_object_get(j_refresh_token, "gpgr_id")), now, issued_for, u_map_get_case(request->map_header, "user-agent")) == G_OK) { } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oidc - Error serialize_id_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oidc - Error generate_id_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } if ((access_token_out = encrypt_token_if_required(config, access_token, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_ACCESS_TOKEN, &a_enc_res)) != NULL && (refresh_token_out = encrypt_token_if_required(config, refresh_token, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_REFRESH_TOKEN, &r_enc_res)) != NULL && (id_token_out = encrypt_token_if_required(config, id_token, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_ID_TOKEN, &i_enc_res)) != NULL) { j_body = json_pack("{sssssssssisIsssO*}", "token_type", "bearer", "access_token", access_token_out, "refresh_token", refresh_token_out, "id_token", id_token_out, "iat", now, "expires_in", config->access_token_duration, "scope", scope, "authorization_details", json_object_get(json_array_get(j_result, 0), "authorization_details")); ulfius_set_json_body_response(response, 200, j_body); json_decref(j_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_ID_TOKEN, 1, "plugin", "response_type", "device_code", config->name, NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_ID_TOKEN, 1, "plugin", config->name, NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_REFRESH_TOKEN, 1, "plugin", "response_type", "device_code", config->name, NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_REFRESH_TOKEN, 1, "plugin", config->name, NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 1, "plugin", "response_type", "device_code", config->name, NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 1, "plugin", config->name, NULL); } else if (r_enc_res == G_ERROR_UNAUTHORIZED || a_enc_res == G_ERROR_UNAUTHORIZED || i_enc_res == G_ERROR_UNAUTHORIZED) { j_body = json_pack("{ss}", "error", "server_error"); j_body = json_pack("{ss}", "error_description", "Invalid encryption parameters"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oidc - Error encrypt_token_if_required"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } o_free(id_token_out); o_free(access_token_out); o_free(refresh_token_out); o_free(id_token); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oidc - Error serialize_access_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oidc - Error generate_access_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } o_free(access_token); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oidc - Error serialize_refresh_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_refresh_token); } else { j_body = json_pack("{ssss}", "error", "invalid_target", "error_description", "Invalid Resource"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } o_free(refresh_token); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oidc - Error generate_refresh_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else if (res == G_ERROR_UNAUTHORIZED) { j_body = json_pack("{ssss}", "error", "access_denied", "error_description", "Invalid DPoP"); ulfius_set_json_body_response(response, 403, j_body); json_decref(j_body); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oidc - Error check_dpop_jti"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else if (check_result_value(j_jkt, G_ERROR_PARAM) || check_result_value(j_jkt, G_ERROR_UNAUTHORIZED)) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - DPoP invalid at IP Address %s", get_ip_source(request)); j_body = json_pack("{ssss}", "error", "access_denied", "error_description", "Invalid DPoP"); ulfius_set_json_body_response(response, 403, j_body); json_decref(j_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oidc - Error oidc_verify_dpop_proof"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_jkt); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oidc - Error getting user %s", username); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_user); o_free(scope); json_decref(j_result_scope); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oidc - Error get_refresh_token_duration_rolling"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_refresh); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oidc - Error allocating resources for j_amr"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_amr); json_decref(j_result_sheme); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oidc - Error executing j_query (3)"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oidc - Error executing j_query (2)"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else { j_query = json_pack("{sss{s{ss}}s{sO}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_DEVICE_AUTHORIZATION, "set", "gpoda_last_check", "raw", SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "CURRENT_TIMESTAMP", "strftime('%s','now')", "NOW()"), "where", "gpoda_id", json_object_get(json_array_get(j_result, 0), "gpoda_id")); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { if ((now - json_integer_value(json_object_get(json_array_get(j_result, 0), "last_check"))) >= json_integer_value(json_object_get(config->j_params, "device-authorization-interval"))) { // Wait for it! j_body = json_pack("{ss}", "error", "authorization_pending"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } else { // Slow down dammit! j_body = json_pack("{ss}", "error", "slow_down"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oidc - Error executing j_query (3)"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } } else { // Code expired j_body = json_pack("{ss}", "error", "expired_token"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "check_auth_type_device_code - oidc - Invalid code"); j_body = json_pack("{ss}", "error", "access_denied"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_device_code - oidc - Error executing j_query (1)"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for client_id %s at IP Address %s", client_id, ip_source); j_body = json_pack("{ss}", "error", "unauthorized_client"); ulfius_set_json_body_response(response, 403, j_body); json_decref(j_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } json_decref(j_client); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "check_auth_type_device_code - oidc - Missing code"); j_body = json_pack("{ss}", "error", "access_denied"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } o_free(issued_for); return U_CALLBACK_CONTINUE; } static int get_certificate_id(gnutls_x509_crt_t cert, unsigned char * cert_id, size_t * cert_id_len) { int ret; unsigned char cert_digest[64]; size_t cert_digest_len = 64; gnutls_datum_t dat; dat.data = NULL; if (gnutls_x509_crt_export2(cert, GNUTLS_X509_FMT_DER, &dat) >= 0) { if (gnutls_fingerprint(GNUTLS_DIG_SHA256, &dat, cert_digest, &cert_digest_len) == GNUTLS_E_SUCCESS) { if (o_base64url_encode(cert_digest, cert_digest_len, cert_id, cert_id_len)) { cert_id[*cert_id_len] = '\0'; ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_certificate_id - Error o_base64_encode"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_certificate_id - Error gnutls_fingerprint"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_certificate_id - Error gnutls_x509_crt_export2"); ret = G_ERROR; } gnutls_free(dat.data); return ret; } static json_t * check_client_certificate_valid(struct _oidc_config * config, const struct _u_request * http_request) { json_t * j_return = NULL, * j_client; const char * header_cert = NULL, * san_value = NULL, * ip_source = get_ip_source(http_request); gnutls_x509_crt_t cert = NULL, self_cert = NULL; gnutls_datum_t cert_dat, cert_dn = {NULL, 0}; int clean_cert = 0, san_found = 0, crt_found = 0, res; unsigned int san_type = 0, san_type_expected = 0, seq; unsigned char cert_id[64] = {0}, self_cert_id[64] = {0}, * san = NULL; size_t cert_id_len = 0, self_cert_id_len = 0, san_size = 0, index; jwks_t * jwks = NULL; jwk_t * jwk = NULL; char * mtls_prefix = msprintf("/%s/%s/mtls/", config->glewlwyd_config->glewlwyd_config->api_prefix, config->name), * mtls_prefix_fixed = str_replace(mtls_prefix, "//", "/"), * http_url_fixed = str_replace(http_request->http_url, "//", "/"), * p = NULL; if (json_object_get(config->j_params, "client-cert-use-endpoint-aliases") != json_true() || 0 == o_strncmp(mtls_prefix_fixed, http_url_fixed, o_strlen(mtls_prefix_fixed))) { if (json_object_get(config->j_params, "client-cert-source") != NULL) { if ((0 == o_strcmp("TLS", json_string_value(json_object_get(config->j_params, "client-cert-source"))) || 0 == o_strcmp("both", json_string_value(json_object_get(config->j_params, "client-cert-source"))))) { cert = http_request->client_cert; } if (cert == NULL && (0 == o_strcmp("header", json_string_value(json_object_get(config->j_params, "client-cert-source"))) || 0 == o_strcmp("both", json_string_value(json_object_get(config->j_params, "client-cert-source")))) && (header_cert = u_map_get(http_request->map_header, json_string_value(json_object_get(config->j_params, "client-cert-header-name")))) != NULL) { if (!gnutls_x509_crt_init(&cert)) { clean_cert = 1; cert_dat.data = (unsigned char *)header_cert; cert_dat.size = o_strlen(header_cert); if (gnutls_x509_crt_import(cert, &cert_dat, GNUTLS_X509_FMT_PEM) < 0) { y_log_message(Y_LOG_LEVEL_ERROR, "check_client_certificate_valid - Error gnutls_x509_crt_import"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_client_certificate_valid - Error gnutls_x509_crt_init"); } } if (cert != NULL) { if (get_certificate_id(cert, cert_id, &cert_id_len) == G_OK) { j_client = config->glewlwyd_config->glewlwyd_plugin_callback_get_client(config->glewlwyd_config, u_map_get(http_request->map_post_body, "client_id")); if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true()) { if (is_client_auth_method_allowed(json_object_get(j_client, "client"), GLEWLWYD_CLIENT_AUTH_METHOD_TLS)) { if (json_string_length(json_object_get(json_object_get(j_client, "client"), "tls_client_auth_subject_dn"))) { #if GNUTLS_VERSION_NUMBER >= 0x030702 if (gnutls_x509_crt_get_dn3(cert, &cert_dn, 0) == GNUTLS_E_SUCCESS) #else if (gnutls_x509_crt_get_dn2(cert, &cert_dn) == GNUTLS_E_SUCCESS) #endif { if (cert_dn.size == json_string_length(json_object_get(json_object_get(j_client, "client"), "tls_client_auth_subject_dn")) && 0 == o_strncasecmp(json_string_value(json_object_get(json_object_get(j_client, "client"), "tls_client_auth_subject_dn")), (const char *)cert_dn.data, cert_dn.size)) { j_return = json_pack("{sisOss#si}", "result", G_OK, "client", json_object_get(j_client, "client"), "x5t#S256", (const char *)cert_id, cert_id_len, "client_auth_method", GLEWLWYD_CLIENT_AUTH_METHOD_TLS); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for client_id %s at IP Address %s", u_map_get(http_request->map_post_body, "client_id"), ip_source); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } gnutls_free(cert_dn.data); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_client_certificate_valid - Error gnutls_x509_crt_get_dn3"); j_return = json_pack("{si}", "result", G_ERROR); } } else { if (json_string_length(json_object_get(json_object_get(j_client, "client"), "tls_client_auth_san_dns"))) { san_type_expected = GNUTLS_SAN_DNSNAME; san_value = json_string_value(json_object_get(json_object_get(j_client, "client"), "tls_client_auth_san_dns")); } else if (json_string_length(json_object_get(json_object_get(j_client, "client"), "tls_client_auth_san_uri"))) { san_type_expected = GNUTLS_SAN_URI; san_value = json_string_value(json_object_get(json_object_get(j_client, "client"), "tls_client_auth_san_uri")); } else if (json_string_length(json_object_get(json_object_get(j_client, "client"), "tls_client_auth_san_ip"))) { san_type_expected = GNUTLS_SAN_IPADDRESS; san_value = json_string_value(json_object_get(json_object_get(j_client, "client"), "tls_client_auth_san_ip")); } else if (json_string_length(json_object_get(json_object_get(j_client, "client"), "tls_client_auth_san_email"))) { san_type_expected = GNUTLS_SAN_RFC822NAME; san_value = json_string_value(json_object_get(json_object_get(j_client, "client"), "tls_client_auth_san_email")); } seq = 0; while ((res = gnutls_x509_crt_get_subject_alt_name2(cert, seq, NULL, &san_size, &san_type, NULL)) == GNUTLS_E_SHORT_MEMORY_BUFFER && res != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE && !san_found) { if ((san = o_malloc(san_size)) != NULL) { if ((res = gnutls_x509_crt_get_subject_alt_name2(cert, seq, san, &san_size, &san_type, NULL)) >= 0) { if (san_type_expected == GNUTLS_SAN_IPADDRESS && san_type == san_type_expected) { struct in_addr ipv4; struct in6_addr ipv6; if ((p = o_strchr(san_value, ':')) != NULL || inet_pton(AF_INET, san_value, &ipv4) != 0) { if (p != NULL) { if (inet_pton(AF_INET6, san_value, &ipv6) == 1) { if (san_size == 16 && memcmp(san, &ipv6, 16) == 0) { san_found = 1; } } } else { if (san_size == 4 && memcmp(san, &ipv4, 4) == 0) { san_found = 1; } } } } else if (san_type == san_type_expected && san_size == o_strlen(san_value) && 0 == o_strncasecmp(san_value, (const char *)san, san_size)) { san_found = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_client_certificate_valid - Error gnutls_x509_crt_get_subject_alt_name2: %s", gnutls_strerror(res)); } o_free(san); san_size = 0; } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_client_certificate_valid - Error allocating resources for san"); } seq++; } if (san_found) { j_return = json_pack("{sisOss#si}", "result", G_OK, "client", json_object_get(j_client, "client"), "x5t#S256", (const char *)cert_id, cert_id_len, "client_auth_method", GLEWLWYD_CLIENT_AUTH_METHOD_TLS); } else { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for client_id %s at IP Address %s", u_map_get(http_request->map_post_body, "client_id"), ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } } } else if (is_client_auth_method_allowed(json_object_get(j_client, "client"), GLEWLWYD_CLIENT_AUTH_METHOD_SELF_SIGNED_TLS) && json_object_get(config->j_params, "client-cert-self-signed-allowed") == json_true()) { if (r_jwks_init(&jwks) == RHN_OK) { if (json_object_get(json_object_get(j_client, "client"), "jwks") != NULL) { if (r_jwks_import_from_json_t(jwks, json_object_get(json_object_get(j_client, "client"), "jwks")) == RHN_OK) { for (index = 0; index < r_jwks_size(jwks); index++) { jwk = r_jwks_get_at(jwks, index); if ((self_cert = r_jwk_export_to_gnutls_crt(jwk, config->x5u_flags)) != NULL) { if (get_certificate_id(self_cert, self_cert_id, &self_cert_id_len) == G_OK) { if (0 == o_strncmp((const char *)self_cert_id, (const char *)cert_id, self_cert_id_len)) { crt_found = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_client_certificate_valid - Error get_certificate_id (1)"); } gnutls_x509_crt_deinit(self_cert); } r_jwk_free(jwk); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "check_client_certificate_valid - Error r_jwks_import_from_json_t"); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else if (json_string_length(json_object_get(json_object_get(j_client, "client"), "jwks_uri"))) { if (r_jwks_import_from_uri(jwks, json_string_value(json_object_get(json_object_get(j_client, "client"), "jwks_uri")), config->x5u_flags) == RHN_OK) { for (index = 0; index < r_jwks_size(jwks); index++) { jwk = r_jwks_get_at(jwks, index); if ((self_cert = r_jwk_export_to_gnutls_crt(jwk, config->x5u_flags)) != NULL) { if (get_certificate_id(self_cert, self_cert_id, &self_cert_id_len) == G_OK) { if (0 == o_strncmp((const char *)self_cert_id, (const char *)cert_id, self_cert_id_len)) { crt_found = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_client_certificate_valid - Error get_certificate_id (2)"); } gnutls_x509_crt_deinit(self_cert); } r_jwk_free(jwk); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "check_client_certificate_valid - Error r_jwks_import_from_uri"); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_client_certificate_valid - Error r_jwks_init"); j_return = json_pack("{si}", "result", G_ERROR); } r_jwks_free(jwks); if (j_return == NULL) { if (crt_found) { j_return = json_pack("{sisOss#si}", "result", G_OK, "client", json_object_get(j_client, "client"), "x5t#S256", (const char *)cert_id, cert_id_len, "client_auth_method", GLEWLWYD_CLIENT_AUTH_METHOD_SELF_SIGNED_TLS); } else { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for client_id %s at IP Address %s", u_map_get(http_request->map_post_body, "client_id"), ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } } } } else if (check_result_value(j_client, G_ERROR_NOT_FOUND) || json_object_get(json_object_get(j_client, "client"), "enabled") != json_true()) { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_client_certificate_valid - Error glewlwyd_plugin_callback_get_client"); j_return = json_incref(j_client); } json_decref(j_client); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_client_certificate_valid - Error get_certificate_id"); j_return = json_pack("{si}", "result", G_ERROR); } } if (clean_cert) { gnutls_x509_crt_deinit(cert); } } } o_free(mtls_prefix); o_free(mtls_prefix_fixed); o_free(http_url_fixed); return j_return; } static int generate_discovery_content(struct _oidc_config * config) { json_t * j_discovery = json_object(), * j_element = NULL, * j_rhon_info = r_library_info_json_t(), * j_sign_pubkey = json_array(), * j_signing_alg = json_array(); jwks_t * jwks_res; char * plugin_url = config->glewlwyd_config->glewlwyd_callback_get_plugin_external_url(config->glewlwyd_config, config->name); size_t index = 0; int ret = G_OK; const char * key = NULL; json_array_foreach(json_object_get(json_object_get(j_rhon_info, "jws"), "alg"), index, j_element) { if (0 != o_strncmp("HS", json_string_value(j_element), 2) && 0 != o_strcmp("none", json_string_value(j_element))) { json_array_append(j_sign_pubkey, j_element); } } if (j_discovery != NULL && j_sign_pubkey != NULL && j_signing_alg != NULL && plugin_url != NULL) { jwks_res = r_jwks_search_json_str(config->jwks_sign, "{\"kty\":\"oct\"}"); if (r_jwks_size(jwks_res)) { if (json_array_has_string(json_object_get(j_rhon_info, "jws"), "alg"), "HS256") { json_array_append_new(j_signing_alg, json_string("HS256")); } if (json_array_has_string(json_object_get(j_rhon_info, "jws"), "alg"), "HS384") { json_array_append_new(j_signing_alg, json_string("HS384")); } if (json_array_has_string(json_object_get(j_rhon_info, "jws"), "alg"), "HS512") { json_array_append_new(j_signing_alg, json_string("HS512")); } } r_jwks_free(jwks_res); jwks_res = r_jwks_search_json_str(config->jwks_sign, "{\"kty\":\"RSA\"}"); if (r_jwks_size(jwks_res)) { if (json_array_has_string(json_object_get(j_rhon_info, "jws"), "alg"), "RS256") { json_array_append_new(j_signing_alg, json_string("RS256")); } if (json_array_has_string(json_object_get(j_rhon_info, "jws"), "alg"), "RS384") { json_array_append_new(j_signing_alg, json_string("RS384")); } if (json_array_has_string(json_object_get(j_rhon_info, "jws"), "alg"), "RS512") { json_array_append_new(j_signing_alg, json_string("RS512")); } if (json_array_has_string(json_object_get(j_rhon_info, "jws"), "alg"), "PS256") { json_array_append_new(j_signing_alg, json_string("PS256")); } if (json_array_has_string(json_object_get(j_rhon_info, "jws"), "alg"), "PS384") { json_array_append_new(j_signing_alg, json_string("PS384")); } if (json_array_has_string(json_object_get(j_rhon_info, "jws"), "alg"), "PS512") { json_array_append_new(j_signing_alg, json_string("PS512")); } } r_jwks_free(jwks_res); jwks_res = r_jwks_search_json_str(config->jwks_sign, "{\"kty\":\"EC\"}"); if (r_jwks_size(jwks_res)) { if (json_array_has_string(json_object_get(j_rhon_info, "jws"), "alg"), "ES256") { json_array_append_new(j_signing_alg, json_string("ES256")); } if (json_array_has_string(json_object_get(j_rhon_info, "jws"), "alg"), "ES384") { json_array_append_new(j_signing_alg, json_string("ES384")); } if (json_array_has_string(json_object_get(j_rhon_info, "jws"), "alg"), "ES512") { json_array_append_new(j_signing_alg, json_string("ES512")); } } r_jwks_free(jwks_res); jwks_res = r_jwks_search_json_str(config->jwks_sign, "{\"kty\":\"OKP\"}"); if (r_jwks_size(jwks_res)) { if (json_array_has_string(json_object_get(j_rhon_info, "jws"), "alg"), "EdDSA") { json_array_append_new(j_signing_alg, json_string("EdDSA")); } if (json_array_has_string(json_object_get(j_rhon_info, "jws"), "alg"), "ES256K") { json_array_append_new(j_signing_alg, json_string("ES256K")); } } r_jwks_free(jwks_res); json_object_set(j_discovery, "issuer", json_object_get(config->j_params, "iss")); json_object_set_new(j_discovery, "authorization_endpoint", json_pack("s+", plugin_url, "/auth")); json_object_set_new(j_discovery, "token_endpoint", json_pack("s+", plugin_url, "/token")); json_object_set_new(j_discovery, "userinfo_endpoint", json_pack("s+", plugin_url, "/userinfo")); json_object_set_new(j_discovery, "jwks_uri", json_pack("s+", plugin_url, "/jwks")); json_object_set_new(j_discovery, "token_endpoint_auth_methods_supported", json_pack("[ss]", "client_secret_basic", "client_secret_post")); json_object_set(j_discovery, "id_token_signing_alg_values_supported", j_signing_alg); json_object_set(j_discovery, "userinfo_signing_alg_values_supported", j_signing_alg); json_object_set(j_discovery, "access_token_signing_alg_values_supported", j_signing_alg); if (json_object_get(config->j_params, "encrypt-out-token-allow") == json_true()) { json_object_set(j_discovery, "id_token_encryption_alg_values_supported", json_object_get(json_object_get(j_rhon_info, "jwe"), "alg")); json_object_set(j_discovery, "id_token_encryption_enc_values_supported", json_object_get(json_object_get(j_rhon_info, "jwe"), "enc")); json_object_set(j_discovery, "userinfo_encryption_alg_values_supported", json_object_get(json_object_get(j_rhon_info, "jwe"), "alg")); json_object_set(j_discovery, "userinfo_encryption_enc_values_supported", json_object_get(json_object_get(j_rhon_info, "jwe"), "enc")); json_object_set(j_discovery, "access_token_encryption_alg_values_supported", json_object_get(json_object_get(j_rhon_info, "jwe"), "alg")); json_object_set(j_discovery, "access_token_encryption_enc_values_supported", json_object_get(json_object_get(j_rhon_info, "jwe"), "enc")); } if (json_object_get(config->j_params, "request-parameter-allow") == json_true()) { json_object_set(j_discovery, "request_object_signing_alg_values_supported", j_signing_alg); if (json_object_get(config->j_params, "request-parameter-allow-encrypted") == json_true()) { json_object_set(j_discovery, "request_object_encryption_alg_values_supported", json_object_get(json_object_get(j_rhon_info, "jwe"), "alg")); json_object_set(j_discovery, "request_object_encryption_enc_values_supported", json_object_get(json_object_get(j_rhon_info, "jwe"), "enc")); } json_array_append_new(json_object_get(j_discovery, "token_endpoint_auth_methods_supported"), json_string("client_secret_jwt")); json_object_set(j_discovery, "token_endpoint_auth_signing_alg_values_supported", j_signing_alg); if (json_string_length(json_object_get(config->j_params, "client-pubkey-parameter")) || json_string_length(json_object_get(config->j_params, "client-jwks-parameter")) || json_string_length(json_object_get(config->j_params, "client-jwks_uri-parameter"))) { json_array_append_new(json_object_get(j_discovery, "token_endpoint_auth_methods_supported"), json_string("private_key_jwt")); } } if (json_object_get(config->j_params, "oauth-dpop-allowed") == json_true()) { json_object_set(j_discovery, "dpop_signing_alg_values_supported", j_sign_pubkey); } if (json_object_get(config->j_params, "allowed-scope") != NULL && json_array_size(json_object_get(config->j_params, "allowed-scope"))) { json_object_set(j_discovery, "scopes_supported", json_object_get(config->j_params, "allowed-scope")); } else { json_object_set_new(j_discovery, "scopes_supported", json_pack("[s]", "openid")); } json_object_set_new(j_discovery, "response_types_supported", json_array()); if (config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE]) { json_array_append_new(json_object_get(j_discovery, "response_types_supported"), json_string("code")); } if (config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_ID_TOKEN]) { json_array_append_new(json_object_get(j_discovery, "response_types_supported"), json_string("id_token")); } if (config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_ID_TOKEN] && config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_TOKEN]) { json_array_append_new(json_object_get(j_discovery, "response_types_supported"), json_string("token id_token")); } if (config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_ID_TOKEN] && config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE]) { json_array_append_new(json_object_get(j_discovery, "response_types_supported"), json_string("code id_token")); } if (config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_ID_TOKEN] && config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE] && config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_TOKEN]) { json_array_append_new(json_object_get(j_discovery, "response_types_supported"), json_string("code token id_token")); } if (config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_NONE]) { json_array_append_new(json_object_get(j_discovery, "response_types_supported"), json_string("none")); } if (config->allow_non_oidc && config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_RESOURCE_OWNER_PASSWORD_CREDENTIALS]) { json_array_append_new(json_object_get(j_discovery, "response_types_supported"), json_string("password")); } if (config->allow_non_oidc && config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_TOKEN]) { json_array_append_new(json_object_get(j_discovery, "response_types_supported"), json_string("token")); } if (config->allow_non_oidc && config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_CLIENT_CREDENTIALS]) { json_array_append_new(json_object_get(j_discovery, "response_types_supported"), json_string("client_credentials")); } if (config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_REFRESH_TOKEN]) { json_array_append_new(json_object_get(j_discovery, "response_types_supported"), json_string("refresh_token")); } json_object_set_new(j_discovery, "response_modes_supported", json_pack("[sss]", "query", "fragment", "form_post")); json_object_set_new(j_discovery, "grant_types_supported", json_pack("[ss]", "authorization_code", "implicit")); json_object_set_new(j_discovery, "display_values_supported", json_pack("[ssss]", "page", "popup", "touch", "wap")); json_object_set_new(j_discovery, "claim_types_supported", json_pack("[s]", "normal")); json_object_set_new(j_discovery, "claims_parameter_supported", json_true()); json_object_set_new(j_discovery, "claims_supported", json_array()); json_array_foreach(json_object_get(config->j_params, "claims"), index, j_element) { json_array_append(json_object_get(j_discovery, "claims_supported"), json_object_get(j_element, "name")); } if (0 == o_strcmp("on-demand", json_string_value(json_object_get(config->j_params, "name-claim"))) || 0 == o_strcmp("mandatory", json_string_value(json_object_get(config->j_params, "name-claim")))) { json_array_append_new(json_object_get(j_discovery, "claims_supported"), json_string("name")); } if (0 == o_strcmp("on-demand", json_string_value(json_object_get(config->j_params, "email-claim"))) || 0 == o_strcmp("mandatory", json_string_value(json_object_get(config->j_params, "email-claim")))) { json_array_append_new(json_object_get(j_discovery, "claims_supported"), json_string("email")); } if (0 == o_strcmp("on-demand", json_string_value(json_object_get(config->j_params, "scope-claim"))) || 0 == o_strcmp("mandatory", json_string_value(json_object_get(config->j_params, "scope-claim")))) { json_array_append_new(json_object_get(j_discovery, "claims_supported"), json_string("scope")); } if (0 == o_strcmp("on-demand", json_string_value(json_object_get(json_object_get(config->j_params, "address-claim"), "type"))) || 0 == o_strcmp("mandatory", json_string_value(json_object_get(json_object_get(config->j_params, "address-claim"), "type")))) { json_array_append_new(json_object_get(j_discovery, "claims_supported"), json_string("address")); } if (json_string_length(json_object_get(config->j_params, "service-documentation"))) { json_object_set(j_discovery, "service_documentation", json_object_get(config->j_params, "service-documentation")); } json_object_set_new(j_discovery, "ui_locales_supported", json_pack("[ssss]", "en", "fr", "nl", "de")); json_object_set(j_discovery, "request_parameter_supported", json_object_get(config->j_params, "request-parameter-allow")==json_false()?json_false():json_true()); json_object_set(j_discovery, "request_uri_parameter_supported", json_object_get(config->j_params, "request-parameter-allow")==json_false()?json_false():json_true()); json_object_set_new(j_discovery, "require_request_uri_registration", json_false()); if (json_string_length(json_object_get(config->j_params, "op-policy-uri"))) { json_object_set(j_discovery, "op_policy_uri", json_object_get(config->j_params, "op-policy-uri")); } if (json_string_length(json_object_get(config->j_params, "op-tos-uri"))) { json_object_set(j_discovery, "op_tos_uri", json_object_get(config->j_params, "op-tos-uri")); } if (config->subject_type == GLEWLWYD_OIDC_SUBJECT_TYPE_PAIRWISE) { json_object_set_new(j_discovery, "subject_types_supported", json_pack("[s]", "pairwise")); } else { json_object_set_new(j_discovery, "subject_types_supported", json_pack("[s]", "public")); } if (json_object_get(config->j_params, "pkce-allowed") == json_true()) { json_object_set_new(j_discovery, "code_challenge_methods_supported", json_pack("[s]", "S256")); if (json_object_get(config->j_params, "pkce-method-plain-allowed") == json_true()) { json_array_append_new(json_object_get(j_discovery, "code_challenge_methods_supported"), json_string("plain")); } if (json_object_get(config->j_params, "pkce-required") == json_true()) { json_object_set_new(j_discovery, "require_code_challenge", json_true()); } } if (json_object_get(config->j_params, "introspection-revocation-allowed") == json_true()) { json_object_set_new(j_discovery, "revocation_endpoint", json_pack("s+", plugin_url, "/revoke")); json_object_set_new(j_discovery, "introspection_endpoint", json_pack("s+", plugin_url, "/introspect")); json_object_set_new(j_discovery, "revocation_endpoint_auth_methods_supported", json_array()); json_object_set_new(j_discovery, "introspection_endpoint_auth_methods_supported", json_array()); json_object_set(j_discovery, "introspection_signing_alg_values_supported", j_signing_alg); if (json_object_get(config->j_params, "request-parameter-allow-encrypted") == json_true() || json_object_get(config->j_params, "encrypt-out-token-allow") == json_true()) { json_object_set(j_discovery, "introspection_encryption_alg_values_supported", json_object_get(json_object_get(j_rhon_info, "jwe"), "alg")); json_object_set(j_discovery, "introspection_encryption_enc_values_supported", json_object_get(json_object_get(j_rhon_info, "jwe"), "enc")); } if (json_object_get(config->j_params, "introspection-revocation-allow-target-client") == json_true()) { json_array_append_new(json_object_get(j_discovery, "revocation_endpoint_auth_methods_supported"), json_string("client_secret_basic")); json_array_append_new(json_object_get(j_discovery, "introspection_endpoint_auth_methods_supported"), json_string("client_secret_basic")); } if (o_strlen(config->introspect_revoke_resource_config->oauth_scope)) { json_array_append_new(json_object_get(j_discovery, "revocation_endpoint_auth_methods_supported"), json_string("bearer")); json_array_append_new(json_object_get(j_discovery, "introspection_endpoint_auth_methods_supported"), json_string("bearer")); } } if (json_object_get(config->j_params, "register-client-allowed") == json_true()) { json_object_set_new(j_discovery, "registration_endpoint", json_pack("s+", plugin_url, "/register")); } if (json_object_get(config->j_params, "session-management-allowed") == json_true()) { json_object_set_new(j_discovery, "end_session_endpoint", json_pack("s+", plugin_url, "/end_session")); json_object_set_new(j_discovery, "check_session_iframe", json_pack("s+", plugin_url, "/check_session_iframe")); } if (json_object_get(config->j_params, "auth-type-device-enabled") == json_true()) { json_object_set_new(j_discovery, "device_authorization_endpoint", json_pack("s+", plugin_url, "/device_authorization")); json_array_append_new(json_object_get(j_discovery, "grant_types_supported"), json_string(GLEWLWYD_DEVICE_AUTH_GRANT_TYPE)); } if (json_string_length(json_object_get(config->j_params, "client-cert-source"))) { if (json_object_get(config->j_params, "client-cert-use-endpoint-aliases") == json_true()) { json_object_set_new(j_discovery, "mtls_endpoint_aliases", json_pack("{ss+}", "token_endpoint", plugin_url, "/mtls/token")); if (json_object_get(config->j_params, "auth-type-device-enabled") == json_true()) { json_object_set_new(json_object_get(j_discovery, "mtls_endpoint_aliases"), "device_authorization_endpoint", json_pack("s+", plugin_url, "/mtls/device_authorization")); } if (json_object_get(config->j_params, "introspection-revocation-allowed") == json_true()) { json_object_set_new(json_object_get(j_discovery, "mtls_endpoint_aliases"), "revocation_endpoint", json_pack("s+", plugin_url, "/mtls/revoke")); json_object_set_new(json_object_get(j_discovery, "mtls_endpoint_aliases"), "introspection_endpoint", json_pack("s+", plugin_url, "/mtls/introspect")); } if (json_object_get(config->j_params, "oauth-par-allowed") == json_true()) { json_object_set_new(json_object_get(j_discovery, "mtls_endpoint_aliases"), "pushed_authorization_request_endpoint", json_pack("s+", plugin_url, "/mtls/par")); } if (json_object_get(config->j_params, "oauth-ciba-allowed") == json_true()) { json_object_set_new(json_object_get(j_discovery, "mtls_endpoint_aliases"), "backchannel_authentication_endpoint", json_pack("s+", plugin_url, "/mtls/ciba")); } } json_array_append_new(json_object_get(j_discovery, "token_endpoint_auth_methods_supported"), json_string("tls_client_auth")); if (json_object_get(config->j_params, "client-cert-self-signed-allowed") == json_true()) { json_array_append_new(json_object_get(j_discovery, "token_endpoint_auth_methods_supported"), json_string("self_signed_tls_client_auth")); } } if (json_object_get(config->j_params, "oauth-rar-allowed") == json_true()) { json_object_set_new(j_discovery, "authorization_details_supported", json_true()); json_object_set_new(j_discovery, "authorization_data_types_supported", json_array()); json_object_foreach(json_object_get(config->j_params, "rar-types"), key, j_element) { json_array_append_new(json_object_get(j_discovery, "authorization_data_types_supported"), json_string(key)); } } if (json_object_get(config->j_params, "oauth-par-allowed") == json_true()) { json_object_set_new(j_discovery, "pushed_authorization_request_endpoint", json_pack("s+", plugin_url, "/par")); if (json_object_get(config->j_params, "oauth-par-required") == json_true()) { json_object_set(j_discovery, "require_pushed_authorization_requests", json_true()); } else { json_object_set(j_discovery, "require_pushed_authorization_requests", json_false()); } } if (json_object_get(config->j_params, "oauth-ciba-allowed") == json_true()) { json_object_set_new(j_discovery, "backchannel_token_delivery_modes_supported", json_array()); if (json_object_get(config->j_params, "oauth-ciba-mode-poll-allowed") == json_true()) { json_array_append_new(json_object_get(j_discovery, "backchannel_token_delivery_modes_supported"), json_string("poll")); } if (json_object_get(config->j_params, "oauth-ciba-mode-ping-allowed") == json_true()) { json_array_append_new(json_object_get(j_discovery, "backchannel_token_delivery_modes_supported"), json_string("ping")); } if (json_object_get(config->j_params, "oauth-ciba-mode-push-allowed") == json_true()) { json_array_append_new(json_object_get(j_discovery, "backchannel_token_delivery_modes_supported"), json_string("push")); } json_object_set_new(j_discovery, "backchannel_authentication_endpoint", json_pack("s+", plugin_url, "/ciba")); json_object_set(j_discovery, "backchannel_authentication_request_signing_alg_values_supported", j_signing_alg); if (json_object_get(config->j_params, "encrypt-out-token-allow") == json_true()) { json_object_set(j_discovery, "backchannel_authentication_request_encryption_alg_values_supported", json_object_get(json_object_get(j_rhon_info, "jwe"), "alg")); json_object_set(j_discovery, "backchannel_authentication_request_encryption_enc_values_supported", json_object_get(json_object_get(j_rhon_info, "jwe"), "enc")); } if (json_object_get(config->j_params, "oauth-ciba-user-code-allowed") == json_true()) { json_object_set_new(j_discovery, "backchannel_user_code_parameter_supported", json_true()); } else { json_object_set_new(j_discovery, "backchannel_user_code_parameter_supported", json_false()); } json_array_append_new(json_object_get(j_discovery, "grant_types_supported"), json_string(GLEWLWYD_CIBA_GRANT_TYPE)); } if (json_object_get(config->j_params, "oauth-fapi-allow-jarm") == json_true()) { json_array_append_new(json_object_get(j_discovery, "response_modes_supported"), json_string("query.jwt")); json_array_append_new(json_object_get(j_discovery, "response_modes_supported"), json_string("fragment.jwt")); json_array_append_new(json_object_get(j_discovery, "response_modes_supported"), json_string("form_post.jwt")); json_array_append_new(json_object_get(j_discovery, "response_modes_supported"), json_string("jwt")); json_object_set(j_discovery, "authorization_signing_alg_values_supported", j_signing_alg); if (json_object_get(config->j_params, "encrypt-out-token-allow") == json_true()) { json_object_set(j_discovery, "authorization_encryption_alg_values_supported", json_object_get(json_object_get(j_rhon_info, "jwe"), "alg")); json_object_set(j_discovery, "authorization_encryption_enc_values_supported", json_object_get(json_object_get(j_rhon_info, "jwe"), "enc")); } } config->discovery_str = json_dumps(j_discovery, JSON_COMPACT); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_discovery_content - Error allocating resources for j_discovery"); ret = G_ERROR; } json_decref(j_discovery); json_decref(j_sign_pubkey); json_decref(j_rhon_info); json_decref(j_signing_alg); o_free(plugin_url); return ret; } static json_t * authorization_details_process_resource(json_t * j_authorization_details, const char * resource, int auth) { json_t * j_element = NULL, * j_return = json_array(), * j_copy, * j_location = NULL; size_t index = 0, index_loc = 0; int location_found; json_array_foreach(j_authorization_details, index, j_element) { if (auth) { j_copy = json_deep_copy(j_element); if (!json_array_size(json_object_get(j_element, "locations")) && o_strlen(resource)) { json_object_set_new(j_element, "locations", json_array()); json_array_append_new(json_object_get(j_element, "locations"), json_string(resource)); } json_array_append_new(j_return, j_copy); } else { if (json_array_size(json_object_get(j_element, "locations")) && o_strlen(resource)) { location_found = 0; json_array_foreach(json_object_get(j_element, "locations"), index_loc, j_location) { if (0 == o_strcmp(resource, json_string_value(j_location))) { location_found = 1; } } if (location_found) { json_array_append_new(j_return, json_deep_copy(j_element)); } } else { json_array_append_new(j_return, json_deep_copy(j_element)); } } } if (!json_array_size(j_return)) { json_decref(j_return); j_return = NULL; } return j_return; } static json_t * authorization_details_element_access_enrich(json_t * j_rar_element, json_t * j_user) { json_t * j_access = NULL; const char * key = NULL; if (json_object_size(json_object_get(j_rar_element, "access"))) { json_object_foreach(json_object_get(j_rar_element, "access"), key, j_access) { json_object_set(json_object_get(j_rar_element, "access"), key, json_object_get(j_user, key)); } } return j_rar_element; } static int authorization_details_set_consent(struct _oidc_config * config, const char * type, const char * client_id, const char * username, int consent, const char * ip_source) { json_t * j_query; int res, ret; j_query = json_pack("{sss{si}s{ssssssss}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_RAR, "set", "gporar_consent", consent, "where", "gporar_plugin_name", config->name, "gporar_client_id", client_id, "gporar_type", type, "gporar_username", username); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { y_log_message(Y_LOG_LEVEL_INFO, "Event oidc - Plugin '%s' - Rich Authorization Request consent type '%s' set to %s by user '%s' to client '%s', origin: %s", config->name, type, consent?"true":"false", username, client_id, ip_source); ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "authorization_details_set_consent - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static int authorization_details_add_consent(struct _oidc_config * config, const char * type, const char * client_id, const char * username, int consent, const char * ip_source) { json_t * j_query; int res, ret; j_query = json_pack("{sss{sissssssss}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_RAR, "values", "gporar_consent", consent, "gporar_plugin_name", config->name, "gporar_client_id", client_id, "gporar_type", type, "gporar_username", username); res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { y_log_message(Y_LOG_LEVEL_INFO, "Event oidc - Plugin '%s' - Rich Authorization Request consent type '%s' set to %s by user '%s' to client '%s', origin: %s", config->name, type, consent?"true":"false", username, client_id, ip_source); ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "authorization_details_add_consent - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static int authorization_details_delete_consent(struct _oidc_config * config, const char * type, const char * client_id, const char * username, const char * ip_source) { json_t * j_query; int res, ret; j_query = json_pack("{sss{ssssssss}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_RAR, "where", "gporar_plugin_name", config->name, "gporar_client_id", client_id, "gporar_type", type, "gporar_username", username); res = h_delete(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { y_log_message(Y_LOG_LEVEL_INFO, "Event oidc - Plugin '%s' - Rich Authorization Request consent type '%s' deleted by user '%s' to client '%s', origin: %s", config->name, type, username, client_id, ip_source); ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "authorization_details_delete_consent - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static json_t * authorization_details_get_consent(struct _oidc_config * config, const char * type, const char * client_id, const char * username) { json_t * j_query = NULL, * j_result = NULL, * j_return; int res; j_query = json_pack("{sss[s]s{sssssssssi}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_RAR, "columns", "gporar_consent AS consent", "where", "gporar_plugin_name", config->name, "gporar_client_id", client_id, "gporar_type", type, "gporar_username", username, "gporar_enabled", 1); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_return = json_pack("{sis{sO}}", "result", G_OK, "rar_consent", "consent", json_integer_value(json_object_get(json_array_get(j_result, 0), "consent"))?json_true():json_false()); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "authorization_details_get_consent - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } static json_t * authorization_details_requires_consent(struct _oidc_config * config, const char * type, const char * client_id, const char * username) { json_t * j_result = authorization_details_get_consent(config, type, client_id, username), * j_return; if (check_result_value(j_result, G_OK)) { j_return = json_pack("{siso}", "result", G_OK, "requires_consent", json_false()); } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { j_return = json_pack("{siso}", "result", G_OK, "requires_consent", json_true()); } else { y_log_message(Y_LOG_LEVEL_ERROR, "authorization_details_requires_consent - Error authorization_details_get_consent"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_result); return j_return; } static json_t * authorization_details_filter(struct _oidc_config * config, json_t * j_authorization_details, const char * scope_filtered, json_t * j_client, json_t * j_user, const char * ip_source) { json_t * j_return = NULL, * j_rar_element = NULL, * j_rar_allowed = NULL, * j_consent_result, * j_rar_config; size_t index = 0, i; char ** scope_list = NULL; int requires_consent = 0; // Check if the client is allowed for all the required rar types json_array_foreach(j_authorization_details, index, j_rar_element) { if (!json_array_has_string(json_object_get(j_client, json_string_value(json_object_get(config->j_params, "rar-types-client-property"))), json_string_value(json_object_get(j_rar_element, "type")))) { y_log_message(Y_LOG_LEVEL_DEBUG, "authorization_details_filter - Error client %s isn't authorized to use the rar type %s, origin: %s", json_string_value(json_object_get(j_client, "client_id")), json_string_value(json_object_get(j_rar_element, "type")), ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); break; } } if (j_return == NULL) { // Reduce the rar types list to the ones compatible with the filtered scopes if (split_string(scope_filtered, " ", &scope_list)) { if ((j_rar_allowed = json_array()) != NULL) { json_array_foreach(j_authorization_details, index, j_rar_element) { if ((j_rar_config = json_object_get(json_object_get(config->j_params, "rar-types"), json_string_value(json_object_get(j_rar_element, "type")))) != NULL) { if (!json_array_size(json_object_get(j_rar_config, "scopes"))) { j_consent_result = authorization_details_requires_consent(config, json_string_value(json_object_get(j_rar_element, "type")), json_string_value(json_object_get(j_client, "client_id")), json_string_value(json_object_get(j_user, "username"))); if (check_result_value(j_consent_result, G_OK)) { if (json_object_get(j_consent_result, "requires_consent") == json_true()) { requires_consent = 1; } json_array_append(j_rar_allowed, authorization_details_element_access_enrich(j_rar_element, j_user)); } else if (j_return == NULL) { j_return = json_pack("{sO}", "result", json_object_get(j_consent_result, "result")); } json_decref(j_consent_result); } else { for (i=0; scope_list[i]!=NULL; i++) { if (json_array_has_string(json_object_get(j_rar_config, "scopes"), scope_list[i])) { j_consent_result = authorization_details_requires_consent(config, json_string_value(json_object_get(j_rar_element, "type")), json_string_value(json_object_get(j_client, "client_id")), json_string_value(json_object_get(j_user, "username"))); if (check_result_value(j_consent_result, G_OK)) { if (json_object_get(j_consent_result, "requires_consent") == json_true()) { requires_consent = 1; } json_array_append(j_rar_allowed, authorization_details_element_access_enrich(j_rar_element, j_user)); } else if (j_return == NULL) { j_return = json_pack("{sO}", "result", json_object_get(j_consent_result, "result")); } json_decref(j_consent_result); break; } } } } else if (j_return == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "authorization_details_filter - Error getting rar-type '%s'", json_string_value(json_object_get(j_rar_element, "type"))); j_return = json_pack("{si}", "result", G_ERROR); } } if (j_return == NULL) { j_return = json_pack("{sisosO}", "result", G_OK, "requires_consent", requires_consent?json_true():json_false(), "authorization_details", j_rar_allowed); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "authorization_details_filter - Error allocating resources for j_rar_allowed"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } json_decref(j_rar_allowed); } else { y_log_message(Y_LOG_LEVEL_ERROR, "authorization_details_filter - Error split_string '%s'", scope_filtered); j_return = json_pack("{si}", "result", G_ERROR); } free_string_array(scope_list); } return j_return; } static int authorization_details_validate(struct _oidc_config * config, json_t * j_authorization_details, const char * client_id, const char * scope) { int ret, scope_found; json_t * j_rar_element = NULL, * j_rar_type = NULL, * j_element = NULL, * j_client = config->glewlwyd_config->glewlwyd_plugin_callback_get_client(config->glewlwyd_config, client_id), * j_client_auth_types; size_t index = 0, index_param = 0; char ** scope_list = NULL; const char * key = NULL; if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true()) { j_client_auth_types = json_object_get(json_object_get(j_client, "client"), json_string_value(json_object_get(config->j_params, "rar-types-client-property"))); if (json_array_size(j_authorization_details)) { ret = G_OK; if (split_string(scope, " ", &scope_list)) { json_array_foreach(j_authorization_details, index, j_rar_element) { if (json_is_object(j_rar_element)) { if (json_string_length(json_object_get(j_rar_element, "type"))) { if (json_array_has_string(j_client_auth_types, json_string_value(json_object_get(j_rar_element, "type")))) { if ((j_rar_type = json_object_get(json_object_get(config->j_params, "rar-types"), json_string_value(json_object_get(j_rar_element, "type")))) != NULL) { if (json_array_size(json_object_get(j_rar_type, "locations"))) { if (json_is_array(json_object_get(j_rar_element, "locations"))) { json_array_foreach(json_object_get(j_rar_element, "locations"), index_param, j_element) { if (json_string_length(j_element)) { if (!json_array_has_string(json_object_get(j_rar_type, "locations"), json_string_value(j_element))) { y_log_message(Y_LOG_LEVEL_DEBUG, "authorization_details_validate - Error authorization_details type %s has unauthorized locations", json_string_value(json_object_get(j_rar_element, "type"))); ret = G_ERROR_PARAM; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "authorization_details_validate - Error authorization_details type %s has invalid locations", json_string_value(json_object_get(j_rar_element, "type"))); ret = G_ERROR_PARAM; } } } } else if (json_array_size(json_object_get(j_rar_type, "locations"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "authorization_details_validate - Error authorization_details type %s locations is mandatory", json_string_value(json_object_get(j_rar_element, "type"))); ret = G_ERROR_PARAM; } if (json_array_size(json_object_get(j_rar_type, "actions"))) { if (json_is_array(json_object_get(j_rar_element, "actions"))) { json_array_foreach(json_object_get(j_rar_element, "actions"), index_param, j_element) { if (json_string_length(j_element)) { if (!json_array_has_string(json_object_get(j_rar_type, "actions"), json_string_value(j_element))) { y_log_message(Y_LOG_LEVEL_DEBUG, "authorization_details_validate - Error authorization_details type %s has unauthorized actions", json_string_value(json_object_get(j_rar_element, "type"))); ret = G_ERROR_PARAM; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "authorization_details_validate - Error authorization_details type %s has invalid actions", json_string_value(json_object_get(j_rar_element, "type"))); ret = G_ERROR_PARAM; } } } } else if (json_array_size(json_object_get(j_rar_type, "actions"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "authorization_details_validate - Error authorization_details type %s actions is mandatory", json_string_value(json_object_get(j_rar_element, "type"))); ret = G_ERROR_PARAM; } if (json_array_size(json_object_get(j_rar_type, "datatypes"))) { if (json_is_array(json_object_get(j_rar_element, "datatypes"))) { json_array_foreach(json_object_get(j_rar_element, "datatypes"), index_param, j_element) { if (json_string_length(j_element)) { if (!json_array_has_string(json_object_get(j_rar_type, "datatypes"), json_string_value(j_element))) { y_log_message(Y_LOG_LEVEL_DEBUG, "authorization_details_validate - Error authorization_details type %s has unauthorized datatypes", json_string_value(json_object_get(j_rar_element, "type"))); ret = G_ERROR_PARAM; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "authorization_details_validate - Error authorization_details type %s has invalid datatypes", json_string_value(json_object_get(j_rar_element, "type"))); ret = G_ERROR_PARAM; } } } } else if (json_array_size(json_object_get(j_rar_type, "datatypes"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "authorization_details_validate - Error authorization_details type %s datatypes is mandatory", json_string_value(json_object_get(j_rar_element, "type"))); ret = G_ERROR_PARAM; } if (json_array_size(json_object_get(j_rar_type, "scopes"))) { scope_found = 0; json_array_foreach(json_object_get(j_rar_type, "scopes"), index_param, j_element) { if (string_array_has_value((const char **)scope_list, json_string_value(j_element))) { scope_found = 1; } } if (!scope_found) { y_log_message(Y_LOG_LEVEL_DEBUG, "authorization_details_validate - Error authorization_details type %s doesn't match required scopes", json_string_value(json_object_get(j_rar_element, "type"))); ret = G_ERROR_PARAM; } } if (json_object_size(json_object_get(j_rar_element, "access"))) { json_object_foreach(json_object_get(j_rar_element, "access"), key, j_element) { if (!json_array_has_string(json_object_get(j_rar_type, "enriched"), key)) { y_log_message(Y_LOG_LEVEL_DEBUG, "authorization_details_validate - Error authorization_details type %s requires access to user property %s when authorization_details forbids it", json_string_value(json_object_get(j_rar_element, "type")), key); ret = G_ERROR_PARAM; } } } if (json_array_size(json_object_get(j_rar_type, "privileges"))) { if (json_is_array(json_object_get(j_rar_element, "privileges"))) { json_array_foreach(json_object_get(j_rar_element, "privileges"), index_param, j_element) { if (json_string_length(j_element)) { if (!json_array_has_string(json_object_get(j_rar_type, "privileges"), json_string_value(j_element))) { y_log_message(Y_LOG_LEVEL_DEBUG, "authorization_details_validate - Error authorization_details type %s has unauthorized privileges", json_string_value(json_object_get(j_rar_element, "type"))); ret = G_ERROR_PARAM; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "authorization_details_validate - Error authorization_details type %s has invalid privileges", json_string_value(json_object_get(j_rar_element, "type"))); ret = G_ERROR_PARAM; } } } } else if (json_array_size(json_object_get(j_rar_type, "privileges"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "authorization_details_validate - Error authorization_details type %s privileges is mandatory", json_string_value(json_object_get(j_rar_element, "type"))); ret = G_ERROR_PARAM; } if (json_object_get(j_rar_element, "identifier") != NULL && !json_string_length(json_object_get(j_rar_element, "identifier"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "authorization_details_validate - Error authorization_details type %s invalid identifier", json_string_value(json_object_get(j_rar_element, "type"))); ret = G_ERROR_PARAM; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "authorization_details_validate - Error authorization_details type %s is not allowed", json_string_value(json_object_get(j_rar_element, "type"))); ret = G_ERROR_PARAM; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "authorization_details_validate - Error client %s isn't allowed to use authorization_details type %s", client_id, json_string_value(json_object_get(j_rar_element, "type"))); ret = G_ERROR_PARAM; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "authorization_details_validate - Error authorization_details at index %zu has no type", index); ret = G_ERROR_PARAM; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "authorization_details_validate - Error authorization_details at index %zu is not a JSON object", index); ret = G_ERROR_PARAM; } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "authorization_details_validate - Error split_string scope"); ret = G_ERROR_PARAM; } free_string_array(scope_list); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "authorization_details_validate - Error authorization_details is not a JSON array with elements"); ret = G_ERROR_PARAM; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "authorization_details_validate - Error invalid client_id"); ret = G_ERROR_PARAM; } json_decref(j_client); return ret; } static int callback_client_registration_management_read(const struct _u_request * request, struct _u_response * response, void * user_data) { json_t * j_client = convert_client_glewlwyd_to_registration(json_object_get((json_t *)response->shared_data, "client")); UNUSED(request); UNUSED(user_data); if (j_client != NULL) { ulfius_set_json_body_response(response, 200, j_client); json_decref(j_client); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_client_registration_management_read - Error json_deep_copy"); response->status = 500; } return U_CALLBACK_CONTINUE; } static int callback_client_registration_management_update(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; json_t * j_result_check, * j_result, * j_registration = ulfius_get_json_body_request(request, NULL); char * redirect_uri; j_result_check = is_client_registration_valid(config, j_registration, u_map_get(request->map_url, "client_id")); if (check_result_value(j_result_check, G_OK)) { j_result = client_register(config, request, j_registration, 1); if (check_result_value(j_result, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_result, "client")); redirect_uri = json_dumps(json_object_get(json_object_get(j_result, "client"), "redirect_uris"), JSON_COMPACT); y_log_message(Y_LOG_LEVEL_INFO, "Event oidc - Plugin '%s' - client '%s' registration updated with redirect_uri %s, origin: %s", config->name, u_map_get(request->map_url, "client_id"), redirect_uri, get_ip_source(request)); o_free(redirect_uri); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_client_registration_management_update - Error client_register"); response->status = 500; } json_decref(j_result); } else if (check_result_value(j_result_check, G_ERROR_PARAM)) { ulfius_set_json_body_response(response, 400, json_object_get(j_result_check, "error")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_client_registration_management_update - Error is_client_registration_valid"); response->status = 500; } json_decref(j_result_check); json_decref(j_registration); return U_CALLBACK_CONTINUE; } static int callback_client_registration_management_delete(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; UNUSED(request); if (client_registration_management_delete(config, json_integer_value(json_object_get((json_t *)response->shared_data, "gpocr_id")), json_object_get((json_t *)response->shared_data, "client")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_client_registration_management_read - Error registration_management_delete"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event oidc - Plugin '%s' - client '%s' deleted, origin: %s", config->name, u_map_get(request->map_url, "client_id"), get_ip_source(request)); } return U_CALLBACK_CONTINUE; } static int callback_check_registration_management(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; int ret = U_CALLBACK_UNAUTHORIZED; json_t * j_result; if (u_map_get_case(request->map_header, HEADER_AUTHORIZATION)) { j_result = check_client_registration_management_at(config, u_map_get(request->map_url, "client_id"), (u_map_get_case(request->map_header, HEADER_AUTHORIZATION) + o_strlen(HEADER_PREFIX_BEARER))); if (check_result_value(j_result, G_OK)) { if (ulfius_set_response_shared_data(response, json_incref(json_object_get(j_result, "registration")), (void (*)(void *))&json_decref) != U_OK) { ret = U_CALLBACK_ERROR; } else { ret = U_CALLBACK_CONTINUE; } } json_decref(j_result); } if (ret == U_CALLBACK_UNAUTHORIZED) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Token invalid at IP Address %s", get_ip_source(request)); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_INVALID_ACCESS_TOKEN, 1, "plugin", config->name, NULL); } return ret; } static int callback_client_registration(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; json_t * j_result_check, * j_result, * j_registration = ulfius_get_json_body_request(request, NULL); char * redirect_uri; j_result_check = is_client_registration_valid(config, j_registration, NULL); if (check_result_value(j_result_check, G_OK)) { j_result = client_register(config, request, j_registration, 0); if (check_result_value(j_result, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_result, "client")); redirect_uri = json_dumps(json_object_get(json_object_get(j_result, "client"), "redirect_uris"), JSON_COMPACT); y_log_message(Y_LOG_LEVEL_INFO, "Event oidc - Plugin '%s' - client '%s' registered with redirect_uri %s, origin: %s", config->name, json_string_value(json_object_get(json_object_get(j_result, "client"), "client_id")), redirect_uri, get_ip_source(request)); o_free(redirect_uri); if (config->client_register_resource_config->oauth_scope != NULL && json_object_get(config->j_params, "register-client-token-one-use") == json_true()) { if (revoke_access_token(config, (u_map_get_case(request->map_header, HEADER_AUTHORIZATION) + o_strlen(HEADER_PREFIX_BEARER))) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_client_registration - Error revoke_access_token"); response->status = 500; } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_client_registration - Error client_register"); response->status = 500; } json_decref(j_result); } else if (check_result_value(j_result_check, G_ERROR_PARAM)) { ulfius_set_json_body_response(response, 400, json_object_get(j_result_check, "error")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_client_registration - Error is_client_registration_valid"); response->status = 500; } json_decref(j_result_check); json_decref(j_registration); return U_CALLBACK_CONTINUE; } static int callback_check_registration(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; json_t * j_introspect; int ret = U_CALLBACK_UNAUTHORIZED; if (config->client_register_resource_config->oauth_scope == NULL) { ret = U_CALLBACK_CONTINUE; } else if (u_map_get_case(request->map_header, HEADER_AUTHORIZATION)) { j_introspect = get_token_metadata(config, (u_map_get_case(request->map_header, HEADER_AUTHORIZATION) + o_strlen(HEADER_PREFIX_BEARER)), "access_token", NULL); if (check_result_value(j_introspect, G_OK) && json_object_get(json_object_get(j_introspect, "token"), "active") == json_true()) { ret = callback_check_glewlwyd_oidc_access_token(request, response, (void*)config->client_register_resource_config); if (ret == U_CALLBACK_UNAUTHORIZED) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Token invalid at IP Address %s", get_ip_source(request)); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_INVALID_ACCESS_TOKEN, 1, "plugin", config->name, "endpoint", "register", NULL); } } json_decref(j_introspect); } return ret; } static int callback_revocation(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; json_t * j_result; j_result = get_token_metadata(config, u_map_get(request->map_post_body, "token"), u_map_get(request->map_post_body, "token_type_hint"), get_client_id_for_introspection(config, request)); if (check_result_value(j_result, G_OK)) { if (json_object_get(json_object_get(j_result, "token"), "active") == json_true()) { if (0 == o_strcmp("refresh_token", json_string_value(json_object_get(json_object_get(j_result, "token"), "token_type")))) { if (revoke_refresh_token(config, u_map_get(request->map_post_body, "token")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_revocation - Error revoke_refresh_token"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event oidc - Plugin '%s' - Refresh token generated for client '%s' revoked, origin: %s", config->name, json_string_value(json_object_get(json_object_get(j_result, "token"), "client_id")), get_ip_source(request)); } } else if (0 == o_strcmp("access_token", json_string_value(json_object_get(json_object_get(j_result, "token"), "token_type")))) { if (revoke_access_token(config, u_map_get(request->map_post_body, "token")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_revocation - Error revoke_access_token"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event oidc - Plugin '%s' - Access token jti '%s' generated for client '%s' revoked, origin: %s", config->name, json_string_value(json_object_get(json_object_get(j_result, "token"), "jti")), json_string_value(json_object_get(json_object_get(j_result, "token"), "client_id")), get_ip_source(request)); } } else { if (revoke_id_token(config, u_map_get(request->map_post_body, "token")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_revocation - Error revoke_id_token"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event oidc - Plugin '%s' - id_token generated for client '%s' revoked, origin: %s", config->name, json_string_value(json_object_get(json_object_get(j_result, "token"), "client_id")), get_ip_source(request)); } } } } else if (check_result_value(j_result, G_ERROR_PARAM)) { response->status = 400; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_revocation - Error get_token_metadata"); response->status = 500; } json_decref(j_result); return U_CALLBACK_CONTINUE; } static int callback_introspection(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; json_t * j_result; jwt_t * jwt = NULL; jwa_alg alg = get_token_sign_alg(config, json_object_get((json_t *)response->shared_data, "client"), GLEWLWYD_TOKEN_TYPE_INTROSPECTION); jwk_t * jwk = get_jwk_sign(config, json_object_get((json_t *)response->shared_data, "client"), alg); time_t now; char * token = NULL, * token_out; int jwt_ok, enc_res = G_OK; u_map_put(response->map_header, "Cache-Control", "no-store"); u_map_put(response->map_header, "Pragma", "no-cache"); u_map_put(response->map_header, "Referrer-Policy", "no-referrer"); j_result = get_token_metadata(config, u_map_get(request->map_post_body, "token"), u_map_get(request->map_post_body, "token_type_hint"), get_client_id_for_introspection(config, request)); if (check_result_value(j_result, G_OK)) { if (0 == o_strcmp("jwt", u_map_get(request->map_url, "format")) || 0 == o_strcmp("jwt", u_map_get(request->map_post_body, "format")) || 0 == o_strcasecmp("application/jwt", u_map_get_case(request->map_header, "Accept")) || 0 == o_strcasecmp("application/token-introspection+jwt", u_map_get_case(request->map_header, "Accept"))) { if (0 == o_strcmp("access_token", json_string_value(json_object_get(json_object_get(j_result, "token"), "token_type")))) { if (jwk != NULL && alg != R_JWA_ALG_UNKNOWN) { if (r_jwt_init(&jwt) == RHN_OK) { r_jwt_set_sign_alg(jwt, alg); time(&now); r_jwt_set_claim_json_t_value(jwt, "iss", json_object_get(config->j_params, "iss")); json_object_set(json_object_get(j_result, "token"), "iss", json_object_get(config->j_params, "iss")); if (json_object_get(json_object_get(j_result, "token"), "aud") != json_null()) { r_jwt_set_claim_json_t_value(jwt, "aud", json_object_get(json_object_get(j_result, "token"), "aud")); } else { r_jwt_set_claim_json_t_value(jwt, "aud", json_object_get(json_object_get(j_result, "token"), "scope")); } r_jwt_set_claim_int_value(jwt, "iat", now); r_jwt_set_header_str_value(jwt, "typ", "token-introspection+jwt"); if (0 == o_strcasecmp("application/token-introspection+jwt", u_map_get_case(request->map_header, "Accept"))) { u_map_put(response->map_header, "Content-Type", "application/token-introspection+jwt"); if (r_jwt_set_claim_json_t_value(jwt, "token_introspection", json_object_get(j_result, "token")) == RHN_OK) { jwt_ok = 1; } else { jwt_ok = 0; } } else { u_map_put(response->map_header, "Content-Type", "application/jwt"); if (r_jwt_set_full_claims_json_t(jwt, json_object_get(j_result, "token")) == RHN_OK) { jwt_ok = 1; } else { jwt_ok = 0; } } if (jwt_ok) { token = r_jwt_serialize_signed(jwt, jwk, 0); if (token != NULL) { if ((token_out = encrypt_token_if_required(config, token, json_object_get(j_result, "client"), GLEWLWYD_TOKEN_TYPE_INTROSPECTION, &enc_res)) != NULL) { ulfius_set_string_body_response(response, 200, token_out); } else if (enc_res == G_ERROR_UNAUTHORIZED) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_introspection oidc - Error invalid encryption parameters"); response->status = 400; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_introspection oidc - Error encrypt_token_if_required"); response->status = 500; } o_free(token_out); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_introspection oidc - Error r_jwt_serialize_signed"); response->status = 500; } o_free(token); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_introspection - Error setting jwt claims"); response->status = 500; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_introspection - oidc - Error r_jwt_init"); } r_jwt_free(jwt); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_introspection - oidc - Error no jwk available"); } } else { // token introspection forbidden if token_type isn't access_token response->status = 400; } } else { ulfius_set_json_body_response(response, 200, json_object_get(j_result, "token")); } } else if (check_result_value(j_result, G_ERROR_PARAM)) { response->status = 400; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_introspection - Error get_token_metadata"); response->status = 500; } json_decref(j_result); r_jwk_free(jwk); return U_CALLBACK_CONTINUE; } static int callback_check_intropect_revoke(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; json_t * j_client, * j_introspect, * j_assertion; int ret = U_CALLBACK_UNAUTHORIZED, client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_NONE; const char * client_id, * client_secret; if (0 == o_strncmp(HEADER_PREFIX_BEARER, u_map_get_case(request->map_header, HEADER_AUTHORIZATION), o_strlen(HEADER_PREFIX_BEARER)) && config->introspect_revoke_resource_config->oauth_scope != NULL) { j_introspect = get_token_metadata(config, (u_map_get_case(request->map_header, HEADER_AUTHORIZATION) + o_strlen(HEADER_PREFIX_BEARER)), "access_token", NULL); if (check_result_value(j_introspect, G_OK) && json_object_get(json_object_get(j_introspect, "token"), "active") == json_true()) { ret = callback_check_glewlwyd_oidc_access_token(request, response, (void*)config->introspect_revoke_resource_config); if (ret == U_CALLBACK_CONTINUE) { json_object_set((json_t *)response->shared_data, "client", json_object_get(j_introspect, "client")); } } json_decref(j_introspect); } else if (json_object_get(config->j_params, "introspection-revocation-allow-target-client") == json_true()) { if (u_map_get(request->map_post_body, "client_secret") != NULL) { client_id = u_map_get(request->map_post_body, "client_id"); client_secret = u_map_get(request->map_post_body, "client_secret"); client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_POST; } else { client_id = request->auth_basic_user; client_secret = request->auth_basic_password; client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_BASIC; } j_assertion = check_client_certificate_valid(config, request); if (check_result_value(j_assertion, G_ERROR_UNAUTHORIZED)) { ret = U_CALLBACK_UNAUTHORIZED; } else if (j_assertion != NULL && !check_result_value(j_assertion, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_check_intropect_revoke - Error check_client_certificate_valid"); ret = U_CALLBACK_ERROR; } else if (check_result_value(j_assertion, G_OK)) { ret = U_CALLBACK_CONTINUE; } if (j_assertion == NULL) { if (o_strlen(u_map_get(request->map_post_body, "client_assertion")) && 0 == o_strcmp(GLEWLWYD_AUTH_TOKEN_ASSERTION_TYPE, u_map_get(request->map_post_body, "client_assertion_type"))) { if (json_object_get(config->j_params, "request-parameter-allow") == json_true()) { j_assertion = validate_jwt_assertion_request(config, u_map_get(request->map_post_body, "client_assertion"), o_strstr(request->url_path, "/introspect")!=NULL?"introspect":"revoke", get_ip_source(request)); if (check_result_value(j_assertion, G_ERROR_UNAUTHORIZED) || check_result_value(j_assertion, G_ERROR_PARAM)) { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_check_intropect_revoke - Error validating client_assertion"); ret = U_CALLBACK_UNAUTHORIZED; } else if (!check_result_value(j_assertion, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_check_intropect_revoke - Error validate_jwt_assertion_request"); ret = U_CALLBACK_ERROR; } else { if (is_client_auth_method_allowed(json_object_get(j_assertion, "client"), (int)json_integer_value(json_object_get(j_assertion, "client_auth_method")))) { ret = U_CALLBACK_CONTINUE; } } json_decref(j_assertion); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_check_intropect_revoke - unauthorized request parameter"); ret = U_CALLBACK_UNAUTHORIZED; } } else { j_client = config->glewlwyd_config->glewlwyd_callback_check_client_valid(config->glewlwyd_config, client_id, client_secret); if (check_result_value(j_client, G_OK) && is_client_auth_method_allowed(json_object_get(j_client, "client"), client_auth_method)) { ret = U_CALLBACK_CONTINUE; } ulfius_set_response_shared_data(response, json_pack("{sO}", "client", json_object_get(j_client, "client")), (void (*)(void *))&json_decref); json_decref(j_client); } } else { json_object_set((json_t *)response->shared_data, "client", json_object_get(j_assertion, "client")); } json_decref(j_assertion); } if (ret == U_CALLBACK_UNAUTHORIZED) { config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_INVALID_ACCESS_TOKEN, 1, "plugin", config->name, "endpoint", o_strstr(request->url_path, "/introspect")!=NULL?"introspect":"revoke", NULL); } return ret; } /** * The second step of authentiation code * Validates if code, client_id and redirect_uri sent are valid, then returns a token set */ static int check_auth_type_access_token_request (const struct _u_request * request, struct _u_response * response, void * user_data, json_t * j_assertion_client, const char * x5t_s256, int client_auth_method) { struct _oidc_config * config = (struct _oidc_config *)user_data; const char * code = u_map_get(request->map_post_body, "code"), * client_id = request->auth_basic_user, * client_secret = request->auth_basic_password, * redirect_uri = u_map_get(request->map_post_body, "redirect_uri"), * code_verifier = u_map_get(request->map_post_body, "code_verifier"), * resource = NULL, * ip_source = get_ip_source(request); char * issued_for = get_client_hostname(request), * id_token = NULL, * id_token_out = NULL, * refresh_token = NULL, * refresh_token_out = NULL, * access_token = NULL, * access_token_out = NULL, jti[OIDC_JTI_LENGTH+1] = {0}, jti_r[OIDC_JTI_LENGTH+1] = {0}; json_t * j_code, * j_body, * j_refresh_token, * j_client = NULL, * j_user, * j_amr, * j_claims_request = NULL, * j_jkt = NULL, * j_authorization_details_processed = NULL; time_t now; int res, resource_checked = 0, r_enc_res = G_OK, a_enc_res = G_OK, i_enc_res = G_OK; if (client_id == NULL && u_map_get(request->map_post_body, "client_id") != NULL) { client_id = u_map_get(request->map_post_body, "client_id"); } if (client_secret == NULL && u_map_get(request->map_post_body, "client_secret") != NULL) { client_secret = u_map_get(request->map_post_body, "client_secret"); client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_POST; } else if (client_secret != NULL) { client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_BASIC; } if (json_object_get(config->j_params, "resource-allowed") == json_true()) { resource = u_map_get(request->map_post_body, "resource"); } if (code == NULL || client_id == NULL || redirect_uri == NULL) { response->status = 400; } else { if (j_assertion_client != NULL) { j_client = json_pack("{sisO}", "result", G_OK, "client", j_assertion_client); } else { j_client = check_client_valid(config, client_id, client_secret, redirect_uri, GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE_FLAG, 0, ip_source); } if (check_result_value(j_client, G_OK) && is_client_auth_method_allowed(json_object_get(j_client, "client"), client_auth_method)) { j_code = validate_authorization_code(config, code, client_id, redirect_uri, code_verifier, ip_source); if (check_result_value(j_code, G_OK)) { j_jkt = oidc_verify_dpop_proof(config, request, "POST", "/token"); if (check_result_value(j_jkt, G_OK)) { if (json_object_get(j_jkt, "jkt") == NULL || (res = check_dpop_jti(config, json_string_value(json_object_get(json_object_get(j_jkt, "claims"), "jti")), json_string_value(json_object_get(json_object_get(j_jkt, "claims"), "htm")), json_string_value(json_object_get(json_object_get(j_jkt, "claims"), "htu")), json_integer_value(json_object_get(json_object_get(j_jkt, "claims"), "iat")), client_id, json_string_value(json_object_get(j_jkt, "jkt")), ip_source)) == G_OK) { if (o_strlen(resource)) { if ((res = verify_resource(config, resource, json_object_get(j_client, "client"), json_string_value(json_object_get(json_object_get(j_code, "code"), "scope_list")))) == G_OK) { resource_checked = 1; } else if (res == G_ERROR_PARAM) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc check_auth_type_access_token_request - Error resource unauthorized"); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc check_auth_type_access_token_request - Error verify_resource"); } if (resource_checked && 0 != o_strcmp(resource, json_string_value(json_object_get(json_object_get(j_code, "code"), "resource")))) { resource_checked = 0; y_log_message(Y_LOG_LEVEL_DEBUG, "oidc check_auth_type_access_token_request - Error resource change unauthorized"); } else { resource_checked = 1; } } else { if (json_object_get(json_object_get(j_code, "code"), "resource") != json_null()) { resource = json_string_value(json_object_get(json_object_get(j_code, "code"), "resource")); } resource_checked = 1; } if (resource_checked) { if (json_string_length(json_object_get(json_object_get(j_code, "code"), "claims_request"))) { if ((j_claims_request = json_loads(json_string_value(json_object_get(json_object_get(j_code, "code"), "claims_request")), JSON_DECODE_ANY, NULL)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_access_token_request - Error loading JSON claims_request"); } } j_user = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, json_string_value(json_object_get(json_object_get(j_code, "code"), "username"))); if (check_result_value(j_user, G_OK)) { time(&now); if ((refresh_token = generate_refresh_token()) != NULL) { y_log_message(Y_LOG_LEVEL_INFO, "Event oidc - Plugin '%s' - Refresh token generated for client '%s' granted by user '%s' with scope list '%s', origin: %s", config->name, client_id, json_string_value(json_object_get(json_object_get(j_code, "code"), "username")), json_string_value(json_object_get(json_object_get(j_code, "code"), "scope_list")), get_ip_source(request)); j_refresh_token = serialize_refresh_token(config, GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE, json_integer_value(json_object_get(json_object_get(j_code, "code"), "gpoc_id")), json_string_value(json_object_get(json_object_get(j_code, "code"), "username")), client_id, json_string_value(json_object_get(json_object_get(j_code, "code"), "scope_list")), resource, now, json_integer_value(json_object_get(json_object_get(j_code, "code"), "refresh-token-duration")), json_object_get(json_object_get(j_code, "code"), "refresh-token-rolling")==json_true(), json_object_get(j_claims_request, "userinfo"), refresh_token, issued_for, u_map_get_case(request->map_header, "user-agent"), jti_r, json_string_value(json_object_get(j_jkt, "jkt")), json_object_get(json_object_get(j_code, "code"), "authorization_details")); if (check_result_value(j_refresh_token, G_OK)) { j_authorization_details_processed = authorization_details_process_resource(json_object_get(json_object_get(j_code, "code"), "authorization_details"), resource, 0); if ((access_token = generate_access_token(config, json_string_value(json_object_get(json_object_get(j_code, "code"), "username")), json_object_get(j_client, "client"), json_object_get(j_user, "user"), json_string_value(json_object_get(json_object_get(j_code, "code"), "scope_list")), json_object_get(j_claims_request, "userinfo"), resource, now, jti, x5t_s256, json_string_value(json_object_get(j_jkt, "jkt")), j_authorization_details_processed, get_ip_source(request))) != NULL) { if (serialize_access_token(config, GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE, json_integer_value(json_object_get(j_refresh_token, "gpor_id")), json_string_value(json_object_get(json_object_get(j_code, "code"), "username")), client_id, json_string_value(json_object_get(json_object_get(j_code, "code"), "scope_list")), resource, now, issued_for, u_map_get_case(request->map_header, "user-agent"), access_token, jti, j_authorization_details_processed) == G_OK) { if (json_object_get(json_object_get(j_code, "code"), "has-scope-openid") == json_true()) { j_amr = get_amr_list_from_code(config, json_integer_value(json_object_get(json_object_get(j_code, "code"), "gpoc_id"))); if (check_result_value(j_amr, G_OK)) { if ((id_token = generate_id_token(config, json_string_value(json_object_get(json_object_get(j_code, "code"), "username")), json_object_get(j_user, "user"), json_object_get(j_client, "client"), now, config->glewlwyd_config->glewlwyd_callback_get_session_age(config->glewlwyd_config, request, json_string_value(json_object_get(json_object_get(j_code, "code"), "scope_list"))), json_string_value(json_object_get(json_object_get(j_code, "code"), "nonce")), json_object_get(j_amr, "amr"), access_token, code, json_string_value(json_object_get(json_object_get(j_code, "code"), "scope_list")), json_object_get(j_claims_request, "id_token"), NULL, NULL, json_string_value(json_object_get(json_object_get(j_code, "code"), "s_hash")), json_string_value(json_object_get(json_object_get(j_code, "code"), "sid")), ip_source)) != NULL) { if (serialize_id_token(config, GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE, id_token, json_string_value(json_object_get(json_object_get(j_code, "code"), "username")), client_id, json_string_value(json_object_get(json_object_get(j_code, "code"), "sid")), json_integer_value(json_object_get(json_object_get(j_code, "code"), "gpoc_id")), json_integer_value(json_object_get(j_refresh_token, "gpgr_id")), now, issued_for, u_map_get_case(request->map_header, "user-agent")) == G_OK) { if (disable_authorization_code(config, json_integer_value(json_object_get(json_object_get(j_code, "code"), "gpoc_id"))) == G_OK) { if ((id_token_out = encrypt_token_if_required(config, id_token, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_ID_TOKEN, &i_enc_res)) != NULL && (access_token_out = encrypt_token_if_required(config, access_token, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_ACCESS_TOKEN, &a_enc_res)) != NULL && (refresh_token_out = encrypt_token_if_required(config, refresh_token, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_REFRESH_TOKEN, &r_enc_res)) != NULL) { j_body = json_pack("{sssssssisIsssssO*}", "token_type", "bearer", "access_token", access_token_out, "refresh_token", refresh_token_out, "iat", now, "expires_in", config->access_token_duration, "scope", json_string_value(json_object_get(json_object_get(j_code, "code"), "scope_list")), "id_token", id_token_out, "authorization_details", j_authorization_details_processed); ulfius_set_json_body_response(response, 200, j_body); json_decref(j_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_ID_TOKEN, 1, "plugin", config->name, "response_type", "code", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_ID_TOKEN, 1, "plugin", config->name, NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_REFRESH_TOKEN, 1, "plugin", config->name, "response_type", "code", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_REFRESH_TOKEN, 1, "plugin", config->name, NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 1, "plugin", config->name, "response_type", "code", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 1, "plugin", config->name, NULL); } else if (r_enc_res == G_ERROR_UNAUTHORIZED || a_enc_res == G_ERROR_UNAUTHORIZED || i_enc_res == G_ERROR_UNAUTHORIZED) { j_body = json_pack("{ss}", "error", "server_error"); j_body = json_pack("{ss}", "error_description", "Invalid encryption parameters"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_access_token_request - Error encrypt_token_if_required"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } o_free(id_token_out); o_free(access_token_out); o_free(refresh_token_out); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_access_token_request - Error disable_authorization_code"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_access_token_request - Error serialize_id_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_access_token_request - Error serialize_access_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } o_free(id_token); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_access_token_request - Error generate_id_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_amr); } else { if (disable_authorization_code(config, json_integer_value(json_object_get(json_object_get(j_code, "code"), "gpoc_id"))) == G_OK) { j_body = json_pack("{sssssssisIsssO*}", "token_type", "bearer", "access_token", access_token, "refresh_token", refresh_token, "iat", now, "expires_in", config->access_token_duration, "scope", json_string_value(json_object_get(json_object_get(j_code, "code"), "scope_list")), "authorization_details", j_authorization_details_processed); ulfius_set_json_body_response(response, 200, j_body); json_decref(j_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_REFRESH_TOKEN, 1, "plugin", config->name, "response_type", "code", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_REFRESH_TOKEN, 1, "plugin", config->name, NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 1, "plugin", config->name, "response_type", "code", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_access_token_request - Error disable_authorization_code"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_access_token_request - Error get_amr_list_from_code"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_access_token_request - Error generate_access_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } o_free(access_token); json_decref(j_authorization_details_processed); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_access_token_request - Error serialize_refresh_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_refresh_token); o_free(refresh_token); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_access_token_request - Error generate_refresh_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_access_token_request - Error glewlwyd_plugin_callback_get_user"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_user); } else { j_body = json_pack("{ssss}", "error", "invalid_target", "error_description", "Invalid Resource"); ulfius_set_json_body_response(response, 403, j_body); json_decref(j_body); } } else if (res == G_ERROR_UNAUTHORIZED) { j_body = json_pack("{ssss}", "error", "access_denied", "error_description", "Invalid DPoP"); ulfius_set_json_body_response(response, 403, j_body); json_decref(j_body); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_access_token_request - oidc - Error check_dpop_jti"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else if (check_result_value(j_jkt, G_ERROR_PARAM) || check_result_value(j_jkt, G_ERROR_UNAUTHORIZED)) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - DPoP invalid at IP Address %s", get_ip_source(request)); j_body = json_pack("{ssss}", "error", "access_denied", "error_description", "Invalid DPoP"); ulfius_set_json_body_response(response, 403, j_body); json_decref(j_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_auth_type_access_token_request - Error oidc_verify_dpop_proof"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_jkt); } else { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Code invalid at IP Address %s", get_ip_source(request)); j_body = json_pack("{ss}", "error", "invalid_code"); ulfius_set_json_body_response(response, 403, j_body); json_decref(j_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_INVALID_CODE, 1, "plugin", config->name, NULL); } json_decref(j_code); json_decref(j_claims_request); } else { j_body = json_pack("{ss}", "error", "unauthorized_client"); ulfius_set_json_body_response(response, 403, j_body); json_decref(j_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_INVALID_CODE, 1, "plugin", config->name, NULL); } json_decref(j_client); } o_free(issued_for); return U_CALLBACK_CONTINUE; } /** * The more simple authorization type * username and password are given in the POST parameters, * the access_token and refresh_token in a json object are returned */ static int check_auth_type_resource_owner_pwd_cred (const struct _u_request * request, struct _u_response * response, void * user_data, json_t * j_assertion_client, const char * x5t_s256, int client_auth_method) { struct _oidc_config * config = (struct _oidc_config *)user_data; json_t * j_user, * j_client = NULL, * j_refresh_token, * j_body, * j_user_only, * j_client_for_sub = NULL, * j_element = NULL, * j_refresh = NULL, * j_amr = NULL; int ret = G_OK, auth_type_allowed = 0, has_openid = 0, r_enc_res = G_OK, a_enc_res = G_OK, i_enc_res = G_OK; const char * username = u_map_get(request->map_post_body, "username"), * password = u_map_get(request->map_post_body, "password"), * scope = u_map_get(request->map_post_body, "scope"), * client_id = request->auth_basic_user, * client_secret = request->auth_basic_password, * ip_source = get_ip_source(request); char * issued_for = get_client_hostname(request), * refresh_token = NULL, * refresh_token_out = NULL, * access_token = NULL, * access_token_out = NULL, * id_token = NULL, * id_token_out = NULL, jti[OIDC_JTI_LENGTH+1] = {0}, jti_r[OIDC_JTI_LENGTH+1] = {0}, ** scope_array = NULL; time_t now; size_t index = 0; if (client_id == NULL && u_map_get(request->map_post_body, "client_id") != NULL) { client_id = u_map_get(request->map_post_body, "client_id"); } if (client_secret == NULL && u_map_get(request->map_post_body, "client_secret") != NULL) { client_secret = u_map_get(request->map_post_body, "client_secret"); client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_POST; } else if (client_secret != NULL) { client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_BASIC; } if (scope == NULL || username == NULL || password == NULL || issued_for == NULL) { ret = G_ERROR_PARAM; } else if (client_id != NULL && client_secret == NULL && j_assertion_client == NULL) { ret = G_ERROR_UNAUTHORIZED; } else if ((client_id != NULL && client_secret != NULL) || j_assertion_client != NULL) { if (j_assertion_client != NULL) { j_client = json_pack("{sisO}", "result", G_OK, "client", j_assertion_client); } else { j_client = config->glewlwyd_config->glewlwyd_callback_check_client_valid(config->glewlwyd_config, client_id, client_secret); } if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "confidential") != json_true()) { ret = G_ERROR_PARAM; } else if (check_result_value(j_client, G_OK) && is_client_auth_method_allowed(json_object_get(j_client, "client"), client_auth_method)) { json_array_foreach(json_object_get(json_object_get(j_client, "client"), "authorization_type"), index, j_element) { if (0 == o_strcmp(json_string_value(j_element), "password")) { auth_type_allowed = 1; } } if (!auth_type_allowed) { ret = G_ERROR_PARAM; } } else if (check_result_value(j_client, G_ERROR_NOT_FOUND) || check_result_value(j_client, G_ERROR_UNAUTHORIZED)) { ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error glewlwyd_callback_check_client_valid"); ret = G_ERROR; } json_decref(j_client); j_client = NULL; } if (ret == G_OK) { j_user = config->glewlwyd_config->glewlwyd_callback_check_user_valid(config->glewlwyd_config, username, password, scope); if (check_result_value(j_user, G_OK)) { if (client_id != NULL) { if (j_assertion_client != NULL) { j_client = json_pack("{sisO}", "result", G_OK, "client", j_assertion_client); } else { j_client = config->glewlwyd_config->glewlwyd_plugin_callback_get_client(config->glewlwyd_config, client_id); } if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true()) { j_client_for_sub = json_incref(json_object_get(j_client, "client")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error glewlwyd_plugin_callback_get_client"); ret = G_ERROR; } } if (ret == G_OK) { if (split_string(json_string_value(json_object_get(json_object_get(j_user, "user"), "scope_list")), " ", &scope_array) > 0) { for (index=0; scope_array[index]!=NULL; index++) { if (0 == o_strcmp("openid", scope_array[index])) { has_openid = 1; } } free_string_array(scope_array); j_refresh = get_refresh_token_duration_rolling(config, json_string_value(json_object_get(json_object_get(j_user, "user"), "scope_list"))); time(&now); if (check_result_value(j_refresh, G_OK)) { if ((refresh_token = generate_refresh_token()) != NULL) { y_log_message(Y_LOG_LEVEL_INFO, "Event oidc - Plugin '%s' - Refresh token generated for client '%s' granted by user '%s' with scope list '%s', origin: %s", config->name, client_id, username, json_string_value(json_object_get(json_object_get(j_user, "user"), "scope_list")), get_ip_source(request)); j_refresh_token = serialize_refresh_token(config, GLEWLWYD_AUTHORIZATION_TYPE_RESOURCE_OWNER_PASSWORD_CREDENTIALS, 0, username, client_id, json_string_value(json_object_get(json_object_get(j_user, "user"), "scope_list")), NULL, now, json_integer_value(json_object_get(json_object_get(j_refresh, "refresh-token"), "refresh-token-duration")), json_object_get(json_object_get(j_refresh, "refresh-token"), "refresh-token-rolling")==json_true(), NULL, refresh_token, issued_for, u_map_get_case(request->map_header, "user-agent"), jti_r, NULL, NULL); if (check_result_value(j_refresh_token, G_OK)) { j_user_only = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, username); if (check_result_value(j_user_only, G_OK)) { if ((access_token = generate_access_token(config, username, j_client_for_sub, json_object_get(j_user_only, "user"), json_string_value(json_object_get(json_object_get(j_user, "user"), "scope_list")), NULL, NULL, now, jti, x5t_s256, NULL, NULL, get_ip_source(request))) != NULL) { if (serialize_access_token(config, GLEWLWYD_AUTHORIZATION_TYPE_RESOURCE_OWNER_PASSWORD_CREDENTIALS, json_integer_value(json_object_get(j_refresh_token, "gpgr_id")), username, client_id, json_string_value(json_object_get(json_object_get(j_user, "user"), "scope_list")), NULL, now, issued_for, u_map_get_case(request->map_header, "user-agent"), access_token, jti, NULL) == G_OK) { if (has_openid) { j_amr = json_pack("[s]", "password"); if ((id_token = generate_id_token(config, username, json_object_get(j_user, "user"), json_object_get(j_client, "client"), now, now, u_map_get(request->map_post_body, "nonce"), j_amr, access_token, NULL, json_string_value(json_object_get(json_object_get(j_user, "user"), "scope_list")), NULL, NULL, NULL, NULL, NULL, ip_source)) != NULL) { if (serialize_id_token(config, GLEWLWYD_AUTHORIZATION_TYPE_RESOURCE_OWNER_PASSWORD_CREDENTIALS, id_token, username, client_id, NULL, 0, json_integer_value(json_object_get(j_refresh_token, "gpgr_id")), now, issued_for, u_map_get_case(request->map_header, "user-agent")) == G_OK) { if ((access_token_out = encrypt_token_if_required(config, access_token, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_ACCESS_TOKEN, &a_enc_res)) != NULL && (refresh_token_out = encrypt_token_if_required(config, refresh_token, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_REFRESH_TOKEN, &r_enc_res)) != NULL && (id_token_out = encrypt_token_if_required(config, id_token, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_ID_TOKEN, &i_enc_res)) != NULL) { j_body = json_pack("{sssssssssisIss}", "token_type", "bearer", "access_token", access_token_out, "refresh_token", refresh_token_out, "id_token", id_token_out, "iat", now, "expires_in", config->access_token_duration, "scope", json_string_value(json_object_get(json_object_get(j_user, "user"), "scope_list"))); ulfius_set_json_body_response(response, 200, j_body); json_decref(j_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_ID_TOKEN, 1, "plugin", config->name, "response_type", "password", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_ID_TOKEN, 1, "plugin", config->name, NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_REFRESH_TOKEN, 1, "plugin", config->name, "response_type", "password", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_REFRESH_TOKEN, 1, "plugin", config->name, NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 1, "plugin", config->name, "response_type", "password", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 1, "plugin", config->name, NULL); } else if (r_enc_res == G_ERROR_UNAUTHORIZED || a_enc_res == G_ERROR_UNAUTHORIZED || i_enc_res == G_ERROR_UNAUTHORIZED) { j_body = json_pack("{ss}", "error", "server_error"); j_body = json_pack("{ss}", "error_description", "Invalid encryption parameters"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error encrypt_token_if_required"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } o_free(access_token_out); o_free(refresh_token_out); o_free(id_token_out); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error serialize_id_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error generate_id_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_amr); o_free(id_token); } else { if ((access_token_out = encrypt_token_if_required(config, access_token, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_ACCESS_TOKEN, &a_enc_res)) != NULL && (refresh_token_out = encrypt_token_if_required(config, refresh_token, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_REFRESH_TOKEN, &r_enc_res)) != NULL) { j_body = json_pack("{sssssssisIss}", "token_type", "bearer", "access_token", access_token_out, "refresh_token", refresh_token_out, "iat", now, "expires_in", config->access_token_duration, "scope", json_string_value(json_object_get(json_object_get(j_user, "user"), "scope_list"))); ulfius_set_json_body_response(response, 200, j_body); json_decref(j_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_REFRESH_TOKEN, 1, "plugin", config->name, "response_type", "password", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_REFRESH_TOKEN, 1, "plugin", config->name, NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 1, "plugin", config->name, "response_type", "password", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 1, "plugin", config->name, NULL); } else if (r_enc_res == G_ERROR_UNAUTHORIZED || a_enc_res == G_ERROR_UNAUTHORIZED) { j_body = json_pack("{ss}", "error", "server_error"); j_body = json_pack("{ss}", "error_description", "Invalid encryption parameters"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error encrypt_token_if_required"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } o_free(access_token_out); o_free(refresh_token_out); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error serialize_access_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error generate_access_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } o_free(access_token); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error glewlwyd_plugin_callback_get_user"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_user_only); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error serialize_refresh_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_refresh_token); o_free(refresh_token); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error generate_refresh_token"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error get_refresh_token_duration_rolling"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_refresh); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - Error split_string"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } } json_decref(j_client_for_sub); } else if (check_result_value(j_user, G_ERROR_NOT_FOUND) || check_result_value(j_user, G_ERROR_UNAUTHORIZED)) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc check_auth_type_resource_owner_pwd_cred - Error user '%s'", username); y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for username %s at IP Address %s", username, ip_source); response->status = 403; config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_resource_owner_pwd_cred - glewlwyd_callback_check_user_valid"); response->status = 403; } json_decref(j_user); json_decref(j_client); } else if (ret == G_ERROR_PARAM) { response->status = 400; } else if (ret == G_ERROR_UNAUTHORIZED) { response->status = 403; } else { response->status = 500; } o_free(issued_for); return U_CALLBACK_CONTINUE; } /** * Send an access_token to a confidential client */ static int check_auth_type_client_credentials_grant (const struct _u_request * request, struct _u_response * response, void * user_data, json_t * j_assertion_client, const char * x5t_s256, int client_auth_method) { struct _oidc_config * config = (struct _oidc_config *)user_data; json_t * j_client, * j_element = NULL, * json_body; char ** scope_array = NULL, * scope_joined = NULL, * access_token = NULL, * access_token_out = NULL, * issued_for = get_client_hostname(request), jti[OIDC_JTI_LENGTH+1] = {0}; size_t index = 0; int i, auth_type_allowed = 0, res, enc_res = G_OK; time_t now; const char * ip_source = get_ip_source(request), * client_id = request->auth_basic_user, * client_secret = request->auth_basic_password, * resource = NULL; if (client_id == NULL && u_map_get(request->map_post_body, "client_id") != NULL) { client_id = u_map_get(request->map_post_body, "client_id"); } if (client_secret == NULL && u_map_get(request->map_post_body, "client_secret") != NULL) { client_secret = u_map_get(request->map_post_body, "client_secret"); client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_POST; } else if (client_secret != NULL) { client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_BASIC; } if (json_object_get(config->j_params, "resource-allowed") == json_true()) { resource = u_map_get(request->map_post_body, "resource"); } if (issued_for == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_client_credentials_grant - Error get_client_hostname"); response->status = 500; } else if (((client_id != NULL && client_secret != NULL) || j_assertion_client != NULL) && o_strlen(u_map_get(request->map_post_body, "scope")) > 0) { if (j_assertion_client != NULL) { j_client = json_pack("{sisO}", "result", G_OK, "client", j_assertion_client); } else { if (request->auth_basic_user != NULL) { j_client = config->glewlwyd_config->glewlwyd_callback_check_client_valid(config->glewlwyd_config, request->auth_basic_user, request->auth_basic_password); } else { j_client = config->glewlwyd_config->glewlwyd_callback_check_client_valid(config->glewlwyd_config, u_map_get(request->map_post_body, "client_id"), u_map_get(request->map_post_body, "client_secret")); } } if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "confidential") == json_true() && is_client_auth_method_allowed(json_object_get(j_client, "client"), client_auth_method)) { json_array_foreach(json_object_get(json_object_get(j_client, "client"), "authorization_type"), index, j_element) { if (0 == o_strcmp(json_string_value(j_element), "client_credentials")) { auth_type_allowed = 1; } } if (split_string(u_map_get(request->map_post_body, "scope"), " ", &scope_array) > 0) { for (i=0; scope_array[i]!=NULL; i++) { json_array_foreach(json_object_get(json_object_get(j_client, "client"), "scope"), index, j_element) { if (0 == o_strcmp(json_string_value(j_element), scope_array[i])) { if (scope_joined == NULL) { scope_joined = o_strdup(scope_array[i]); } else { scope_joined = mstrcatf(scope_joined, " %s", scope_array[i]); } } } } if (!o_strlen(scope_joined)) { json_body = json_pack("{ss}", "error", "scope_invalid"); ulfius_set_json_body_response(response, 400, json_body); json_decref(json_body); } else if (!auth_type_allowed) { json_body = json_pack("{ss}", "error", "authorization_type_invalid"); ulfius_set_json_body_response(response, 400, json_body); json_decref(json_body); } else { if (o_strlen(resource)) { if ((res = verify_resource(config, resource, json_object_get(j_client, "client"), scope_joined)) == G_ERROR_PARAM) { json_body = json_pack("{ss}", "error", "invalid_target"); ulfius_set_json_body_response(response, 400, json_body); json_decref(json_body); } else if (res != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_client_credentials_grant - Error verify_resource"); json_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, json_body); json_decref(json_body); } } else { res = G_OK; } if (res == G_OK) { time(&now); if ((access_token = generate_client_access_token(config, json_object_get(j_client, "client"), scope_joined, resource, now, jti, x5t_s256, ip_source)) != NULL) { if (serialize_access_token(config, GLEWLWYD_AUTHORIZATION_TYPE_CLIENT_CREDENTIALS, 0, NULL, request->auth_basic_user, scope_joined, resource, now, issued_for, u_map_get_case(request->map_header, "user-agent"), access_token, jti, NULL) == G_OK) { if ((access_token_out = encrypt_token_if_required(config, access_token, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_ACCESS_TOKEN, &enc_res)) != NULL) { json_body = json_pack("{sssssIss}", "access_token", access_token_out, "token_type", "bearer", "expires_in", config->access_token_duration, "scope", scope_joined); ulfius_set_json_body_response(response, 200, json_body); json_decref(json_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_CLIENT_ACCESS_TOKEN, 1, "plugin", config->name, NULL); } else if (enc_res == G_ERROR_UNAUTHORIZED) { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_client_credentials_grant - Error invalid encryption parameters"); response->status = 400; } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_client_credentials_grant - Error encrypt_token_if_required"); response->status = 500; } o_free(access_token_out); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_client_credentials_grant - Error serialize_access_token"); response->status = 500; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_client_credentials_grant - Error generate_client_access_token"); response->status = 500; } o_free(access_token); } o_free(scope_joined); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_client_credentials_grant - Error split_string"); response->status = 500; } free_string_array(scope_array); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc check_auth_type_client_credentials_grant - Error client_id '%s' invalid", request->auth_basic_user); y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for username %s at IP Address %s", request->auth_basic_user, ip_source); response->status = 403; config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } json_decref(j_client); } else { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for client_id %s at IP Address %s", client_id, ip_source); response->status = 403; config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } o_free(issued_for); return U_CALLBACK_CONTINUE; } static int check_pushed_authorization_request (const struct _u_request * request, struct _u_response * response, void * user_data, json_t * j_assertion_client, int client_auth_method) { struct _oidc_config * config = (struct _oidc_config *)user_data; const char * response_type = u_map_get(request->map_post_body, "response_type"), * state = u_map_get(request->map_post_body, "state"), * redirect_uri = u_map_get(request->map_post_body, "redirect_uri"), * client_id = request->auth_basic_user, * scope = u_map_get(request->map_post_body, "scope"), * nonce = u_map_get(request->map_post_body, "nonce"), * resource = u_map_get(request->map_post_body, "resource"), * code_challenge = u_map_get(request->map_post_body, "code_challenge"), * code_challenge_method = u_map_get(request->map_post_body, "code_challenge_method"), * user_agent = u_map_get_case(request->map_header, "user-agent"), * client_secret = request->auth_basic_password, * ip_source = get_ip_source(request); json_t * j_client = NULL, * j_claims = NULL, * j_request = NULL, * j_authorization_details = NULL, * j_response = NULL, * j_result = NULL; char code_challenge_stored[GLEWLWYD_CODE_CHALLENGE_MAX_LENGTH + 1] = {0}, * request_uri = NULL, ** response_type_array = NULL, * scope_reduced = NULL; int res, auth_type = GLEWLWYD_AUTHORIZATION_TYPE_NULL_FLAG; struct _u_map * additional_parameters = NULL; if (j_assertion_client != NULL) { client_id = json_string_value(json_object_get(j_assertion_client, "client_id")); } if (client_id == NULL && u_map_get(request->map_post_body, "client_id") != NULL) { client_id = u_map_get(request->map_post_body, "client_id"); } if (client_secret == NULL && u_map_get(request->map_post_body, "client_secret") != NULL) { client_secret = u_map_get(request->map_post_body, "client_secret"); client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_POST; } else if (client_secret != NULL) { client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_BASIC; } response->status = 201; do { if ((additional_parameters = u_map_copy(request->map_post_body)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "check_pushed_authorization_request oidc - error u_map_init"); response->status = 500; break; } u_map_remove_from_key(additional_parameters, "response_type"); u_map_remove_from_key(additional_parameters, "state"); u_map_remove_from_key(additional_parameters, "redirect_uri"); u_map_remove_from_key(additional_parameters, "client_id"); u_map_remove_from_key(additional_parameters, "client_secret"); u_map_remove_from_key(additional_parameters, "scope"); u_map_remove_from_key(additional_parameters, "nonce"); u_map_remove_from_key(additional_parameters, "resource"); u_map_remove_from_key(additional_parameters, "code_challenge"); u_map_remove_from_key(additional_parameters, "code_challenge_method"); if (u_map_has_key(request->map_post_body, "claims") && o_strlen(u_map_get(request->map_post_body, "claims"))) { u_map_remove_from_key(additional_parameters, "claims"); j_claims = json_loads(u_map_get(request->map_post_body, "claims"), JSON_DECODE_ANY, NULL); if (j_claims == NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_pushed_authorization_request oidc - error claims parameter not in JSON format, origin: %s", ip_source); response->status = 403; break; } } if (o_strlen(u_map_get(request->map_post_body, "authorization_details")) && json_object_get(config->j_params, "oauth-rar-allowed") == json_true() && json_object_get(config->j_params, "rar-allow-auth-unsigned") == json_true()) { u_map_remove_from_key(additional_parameters, "authorization_details"); if ((j_authorization_details = json_loads(u_map_get(request->map_post_body, "authorization_details"), JSON_DECODE_ANY, NULL)) == NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_pushed_authorization_request oidc - Invalid authorization_details, origin: %s", ip_source); response->status = 403; break; } } if (json_object_get(config->j_params, "request-parameter-allow") != json_false()) { if (o_strlen(u_map_get(request->map_post_body, "request_uri"))) { response->status = 403; break; } else if (o_strlen(u_map_get(request->map_post_body, "request"))) { u_map_remove_from_key(additional_parameters, "request"); j_request = validate_jwt_auth_request(config, u_map_get(request->map_post_body, "request"), u_map_get(request->map_post_body, "client_id"), ip_source); if (check_result_value(j_request, G_ERROR_UNAUTHORIZED) || check_result_value(j_request, G_ERROR_PARAM)) { response->status = 403; break; } else if (!check_result_value(j_request, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "check_pushed_authorization_request oidc - error validate_jwt_auth_request"); response->status = 500; break; } else { client_auth_method = (int)json_integer_value(json_object_get(j_request, "client_auth_method")); if (!json_string_length(json_object_get(json_object_get(j_request, "request"), "client_id")) || (client_id != NULL && 0 != o_strcmp(json_string_value(json_object_get(json_object_get(j_request, "request"), "client_id")), client_id))) { // url parameter client_id can't differ from request parameter if set and must be present in request y_log_message(Y_LOG_LEVEL_DEBUG, "check_pushed_authorization_request oidc - client_id missing or invalid, origin: %s", ip_source); response->status = 403; break; } else if (!json_string_length(json_object_get(json_object_get(j_request, "request"), "response_type")) || (u_map_has_key(request->map_post_body, "response_type") && 0 != o_strcmp(json_string_value(json_object_get(json_object_get(j_request, "request"), "response_type")), u_map_get(request->map_post_body, "response_type")))) { // url parameter response_type can't differ from request parameter if set and must be present in request y_log_message(Y_LOG_LEVEL_DEBUG, "check_pushed_authorization_request oidc - response_type missing or invalid, origin: %s", ip_source); response->status = 403; break; } else if (!json_string_length(json_object_get(json_object_get(j_request, "request"), "redirect_uri"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_pushed_authorization_request oidc - redirect_uri missing, origin: %s", ip_source); // redirect_uri is mandatory response->status = 403; break; } else { response_type = json_string_value(json_object_get(json_object_get(j_request, "request"), "response_type")); redirect_uri = json_string_value(json_object_get(json_object_get(j_request, "request"), "redirect_uri")); client_id = json_string_value(json_object_get(json_object_get(j_request, "request"), "client_id")); scope = json_string_value(json_object_get(json_object_get(j_request, "request"), "scope")); if (code_challenge == NULL) { code_challenge = json_string_value(json_object_get(json_object_get(j_request, "request"), "code_challenge")); } if (code_challenge_method == NULL) { code_challenge_method = json_string_value(json_object_get(json_object_get(j_request, "request"), "code_challenge_method")); } if (nonce == NULL) { nonce = json_string_value(json_object_get(json_object_get(j_request, "request"), "nonce")); } if (state == NULL) { state = json_string_value(json_object_get(json_object_get(j_request, "request"), "state")); } if (resource == NULL && json_object_get(config->j_params, "resource-allowed") == json_true()) { resource = json_string_value(json_object_get(json_object_get(j_request, "request"), "resource")); } if (j_authorization_details == NULL && json_object_get(json_object_get(j_request, "request"), "authorization_details") != NULL) { if (json_object_get(config->j_params, "oauth-rar-allowed") == json_true()) { if ((json_integer_value(json_object_get(j_request, "type")) != R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT && json_object_get(config->j_params, "rar-allow-auth-unencrypted") == json_true()) || json_integer_value(json_object_get(j_request, "type")) == R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT) { j_authorization_details = json_incref(json_object_get(json_object_get(j_request, "request"), "authorization_details")); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "check_pushed_authorization_request oidc - unencrypted authorization_details fobidden, origin: %s", ip_source); // redirect_uri is mandatory response->status = 403; break; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "check_pushed_authorization_request oidc - authorization_details fobidden, origin: %s", ip_source); // redirect_uri is mandatory response->status = 403; break; } } } } } } if (!o_strlen(scope) || !o_strlen(client_id) || !o_strlen(response_type) || !o_strlen(redirect_uri)) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_pushed_authorization_request oidc - client '%s' invalid parameters, origin: %s", client_id, ip_source); response->status = 403; break; } if (split_string(response_type, " ", &response_type_array)) { if (string_array_has_value((const char **)response_type_array, "code")) { auth_type |= GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE_FLAG; } if (string_array_has_value((const char **)response_type_array, "token")) { auth_type |= GLEWLWYD_AUTHORIZATION_TYPE_TOKEN_FLAG; } if (string_array_has_value((const char **)response_type_array, "id_token")) { auth_type |= GLEWLWYD_AUTHORIZATION_TYPE_ID_TOKEN_FLAG; } if (string_array_has_value((const char **)response_type_array, "none")) { auth_type |= GLEWLWYD_AUTHORIZATION_TYPE_NONE_FLAG; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "check_pushed_authorization_request oidc - error split_string response_type"); response->status = 500; break; } if (j_assertion_client != NULL) { j_client = json_pack("{sisO}", "result", G_OK, "client", j_assertion_client); } else if (j_request != NULL) { j_client = json_pack("{sisO}", "result", G_OK, "client", json_object_get(j_request, "client")); } else { j_client = check_client_valid(config, client_id, client_secret, redirect_uri, auth_type, 0, ip_source); } if (!check_result_value(j_client, G_OK)) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_pushed_authorization_request oidc - client '%s' is invalid, origin: %s", client_id, ip_source); response->status = 403; break; } if (json_string_length(json_object_get(config->j_params, "restrict-scope-client-property"))) { j_result = reduce_scope(scope, json_object_get(json_object_get(j_client, "client"), json_string_value(json_object_get(config->j_params, "restrict-scope-client-property")))); if (check_result_value(j_result, G_OK)) { scope_reduced = o_strdup(json_string_value(json_object_get(j_result, "scope"))); } else if (check_result_value(j_result, G_ERROR_UNAUTHORIZED)) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_pushed_authorization_request - error client %s is not allowed to claim scopes '%s'", client_id, scope); y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for client_id %s at IP Address %s", client_id, ip_source); response->status = 403; config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_pushed_authorization_request - error reduce_scope"); response->status = 500; } } else { scope_reduced = o_strdup(scope); } if (!is_client_auth_method_allowed(json_object_get(j_client, "client"), client_auth_method)) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_pushed_authorization_request oidc - client '%s' authentication method is invalid, origin: %s", client_id, ip_source); response->status = 403; break; } if (client_id == NULL && client_secret == NULL && json_object_get(json_object_get(j_client, "client"), "confidential") == json_true()) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_pushed_authorization_request oidc - client '%s' is invalid or is not confidential, origin: %s", client_id, ip_source); response->status = 403; break; } // Check code_challenge if necessary if ((res = is_code_challenge_valid(config, scope, code_challenge, code_challenge_method, code_challenge_stored, (json_object_get(json_object_get(j_client, "client"), "confidential") == json_true()))) == G_ERROR_PARAM) { response->status = 403; break; } else if (res != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "check_pushed_authorization_request oidc - error is_code_challenge_valid"); response->status = 403; break; } } while (0); if (response->status == 201) { if ((request_uri = generate_pushed_request_uri(config)) != NULL) { if (serialize_pushed_request_uri(config, request_uri, response_type, client_id, state, scope_reduced, nonce, resource, redirect_uri, ip_source, user_agent, j_claims, code_challenge_stored, j_authorization_details, additional_parameters) == G_OK) { j_response = json_pack("{sssI}", "request_uri", request_uri, "expires_in", config->request_uri_duration); ulfius_set_json_body_response(response, 201, j_response); json_decref(j_response); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_pushed_authorization_request oidc - error serialize_pushed_request_uri"); response->status = 500; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_pushed_authorization_request oidc - error generate_pushed_request_uri"); response->status = 500; } } json_decref(j_client); json_decref(j_claims); json_decref(j_request); json_decref(j_authorization_details); json_decref(j_result); o_free(request_uri); o_free(scope_reduced); free_string_array(response_type_array); u_map_clean_full(additional_parameters); return U_CALLBACK_CONTINUE; } static json_t * get_ciba_email_content_from_template(struct _oidc_config * config, json_t * j_user, json_t * j_client, const char * user_req_id, const char * binding_message) { char * body = NULL, * tmp, * str_client, * external_url = config->glewlwyd_config->glewlwyd_callback_get_plugin_external_url(config->glewlwyd_config, config->name), * ciba_user_url_connect = msprintf("%s/ciba_user_check?user_req_id=%s", external_url, user_req_id), * ciba_user_url_cancel = msprintf("%s/ciba_user_check?user_req_id=%s&cancel", external_url, user_req_id); const char * lang = json_string_value(json_object_get(j_user, json_string_value(json_object_get(config->j_params, "oauth-ciba-email-user-lang-property")))); json_t * j_template = NULL, * j_element = NULL, * j_return; o_free(external_url); if (!o_strlen(lang)) { json_object_foreach(json_object_get(config->j_params, "oauth-ciba-email-templates"), lang, j_element) { if (json_object_get(j_element, "oauth-ciba-email-defaultLang") == json_true()) { j_template = j_element; break; } } } else { if ((j_template = json_object_get(json_object_get(config->j_params, "oauth-ciba-email-templates"), lang)) == NULL) { json_object_foreach(json_object_get(config->j_params, "oauth-ciba-email-templates"), lang, j_element) { if (json_object_get(j_element, "oauth-ciba-email-defaultLang") == json_true()) { j_template = j_element; break; } } } } if (j_template != NULL) { body = str_replace(json_string_value(json_object_get(j_template, "oauth-ciba-email-body-pattern")), "{CONNECT_URL}", ciba_user_url_connect); if (o_strstr(body, "{CANCEL_URL}") != NULL) { tmp = str_replace(body, "{CANCEL_URL}", ciba_user_url_cancel); o_free(body); body = tmp; tmp = NULL; } if (o_strstr(body, "{BINDING_MESSAGE}") != NULL) { tmp = str_replace(body, "{BINDING_MESSAGE}", o_strlen(binding_message)?binding_message:""); o_free(body); body = tmp; tmp = NULL; } if (o_strstr(body, "{CLIENT}") != NULL) { if (json_string_length(json_object_get(j_client, "name"))) { str_client = msprintf("%s (%s)", json_string_value(json_object_get(j_client, "name")), json_string_value(json_object_get(j_client, "client_id"))); } else { str_client = msprintf("%s", json_string_value(json_object_get(j_client, "client_id"))); } tmp = str_replace(body, "{CLIENT}", str_client); o_free(body); body = tmp; tmp = NULL; o_free(str_client); } j_return = json_pack("{sissss}", "result", G_OK, "subject", json_string_value(json_object_get(j_template, "oauth-ciba-email-subject")), "body", body); o_free(body); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ciba_email_content_from_template - Invalid lang"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } o_free(ciba_user_url_connect); o_free(ciba_user_url_cancel); return j_return; } static int send_ciba_email(struct _oidc_config * config, json_t * j_user, json_t * j_client, const char * user_req_id, const char * binding_message) { int ret; json_t * j_email_template; if (json_string_length(json_object_get(j_user, "email"))) { j_email_template = get_ciba_email_content_from_template(config, j_user, j_client, user_req_id, binding_message); if (check_result_value(j_email_template, G_OK)) { if (ulfius_send_smtp_rich_email(json_string_value(json_object_get(config->j_params, "oauth-ciba-email-host")), json_integer_value(json_object_get(config->j_params, "oauth-ciba-email-port")), json_object_get(config->j_params, "oauth-ciba-email-use-tls")==json_true()?1:0, json_object_get(config->j_params, "oauth-ciba-email-verify-certificate")==json_false()?0:1, json_string_length(json_object_get(config->j_params, "oauth-ciba-email-user"))?json_string_value(json_object_get(config->j_params, "oauth-ciba-email-user")):NULL, json_string_length(json_object_get(config->j_params, "oauth-ciba-email-password"))?json_string_value(json_object_get(config->j_params, "oauth-ciba-email-password")):NULL, json_string_value(json_object_get(config->j_params, "oauth-ciba-email-from")), json_string_value(json_object_get(j_user, "email")), NULL, NULL, json_string_length(json_object_get(config->j_params, "oauth-ciba-email-content-type"))?json_string_value(json_object_get(config->j_params, "oauth-ciba-email-content-type")):"text/plain; charset=utf-8", json_string_value(json_object_get(j_email_template, "subject")), json_string_value(json_object_get(j_email_template, "body"))) == U_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "send_ciba_email - Error ulfius_send_smtp_rich_email"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "send_ciba_email - Error get_ciba_email_content_from_template"); ret = G_ERROR; } json_decref(j_email_template); } else { ret = G_ERROR_PARAM; } return ret; } static int serialize_ciba_request(struct _oidc_config * config, const char * client_id, json_t * j_user, const char * client_notification_token, const char * auth_req_id, const char * user_req_id, const char * binding_message, long int requested_expiry, const char * ip_source, const char * user_agent, const char * scope, const char * x5t_s256, const char * jti_hash) { int ret, res; json_t * j_query, * j_last_id; char ** scope_array = NULL, * expires_at_clause; size_t i; time_t now; if (pthread_mutex_lock(&config->insert_lock)) { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_ciba_request oidc - Error pthread_mutex_lock"); ret = G_ERROR; } else { // disable all other enabled ciba requests from the same client_id to the same username j_query = json_pack("{sss{si}s{ss ss sO si}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA, "set", "gpob_enabled", 0, "where", "gpob_plugin_name", config->name, "gpob_client_id", client_id, "gpob_username", json_object_get(j_user, "username"), "gpob_enabled", 1); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("FROM_UNIXTIME(%u)", (now + (unsigned int)requested_expiry)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("TO_TIMESTAMP(%u)", (now + (unsigned int)requested_expiry )); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("%u", (now + (unsigned int)requested_expiry)); } j_query = json_pack("{sss{ss ss ss* sO ss* ss* ss ss ss* s{ss} ss ss*}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA, "values", "gpob_plugin_name", config->name, "gpob_client_id", client_id, "gpob_x5t_s256", x5t_s256, "gpob_username", json_object_get(j_user, "username"), "gpob_client_notification_token", client_notification_token, "gpob_jti_hash", jti_hash, "gpob_auth_req_id", auth_req_id, "gpob_user_req_id", user_req_id, "gpob_binding_message", binding_message, "gpob_expires_at", "raw", expires_at_clause, "gpob_issued_for", ip_source, "gpob_user_agent", user_agent); res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); o_free(expires_at_clause); if (res == H_OK) { j_last_id = h_last_insert_id(config->glewlwyd_config->glewlwyd_config->conn); j_query = json_pack("{sss[]}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA_SCOPE, "values"); if (split_string(scope, " ", &scope_array)) { for (i=0; scope_array[i] != NULL; i++) { json_array_append_new(json_object_get(j_query, "values"), json_pack("{sOss}", "gpob_id", j_last_id, "gpops_scope", scope_array[i])); } res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_ciba_request - Error executing j_query (3)"); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_ciba_request - Error split_string"); ret = G_ERROR; } free_string_array(scope_array); json_decref(j_last_id); json_decref(j_query); } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_ciba_request - Error executing j_query (2)"); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "serialize_ciba_request - Error executing j_query (1)"); ret = G_ERROR_DB; } pthread_mutex_unlock(&config->insert_lock); } return ret; } static json_t * check_ciba_login_hint(struct _oidc_config * config, json_t * j_client, const char * login_hint_token, const char * id_token_hint, const char * login_hint, const char * ip_source) { json_t * j_return = NULL, * j_login_hint = NULL, * j_result, * j_user; jwt_t * j_login_hint_token = NULL; char * username_from_sub = NULL; // expected values in the login_hint: sub or username, nothing else can be used as an identifier if (o_strlen(login_hint_token)) { if ((j_login_hint_token = r_jwt_quick_parse(login_hint_token, R_PARSE_NONE, 0)) != NULL && decrypt_request_token(config, j_login_hint_token) == G_OK) { j_result = verify_request_signature(config, j_login_hint_token, json_string_value(json_object_get(j_client, "client_id")), GLEWLWYD_AUTH_CIBA, ip_source); if (check_result_value(j_result, G_OK)) { j_login_hint = r_jwt_get_full_claims_json_t(j_login_hint_token); if (!json_string_length(json_object_get(j_login_hint, "username")) && !json_string_length(json_object_get(j_login_hint, "sub"))) { json_decref(j_login_hint); j_login_hint = NULL; j_return = json_pack("{si}", "result", G_ERROR_PARAM); } } else if (check_result_value(j_result, G_ERROR_UNAUTHORIZED)) { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_ciba_login_hint - Error verify_request_signature"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_ciba_login_hint - Invalid token"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } r_jwt_free(j_login_hint_token); } else if (o_strlen(login_hint)) { j_login_hint = json_loads(login_hint, JSON_DECODE_ANY, NULL); if (j_login_hint == NULL || (!json_string_length(json_object_get(j_login_hint, "username")) && !json_string_length(json_object_get(j_login_hint, "sub")))) { json_decref(j_login_hint); j_login_hint = NULL; j_return = json_pack("{si}", "result", G_ERROR_PARAM); } } else if (o_strlen(id_token_hint)) { j_result = get_token_metadata(config, id_token_hint, "id_token", json_string_value(json_object_get(j_client, "client_id"))); if (check_result_value(j_result, G_OK)) { if (json_object_get(json_object_get(j_result, "token"), "active") == json_true()) { j_login_hint = json_pack("{sO*sO*}", "sub", json_object_get(json_object_get(j_result, "token"), "sub"), "username", json_object_get(json_object_get(j_result, "token"), "username")); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_ciba_login_hint - This should not happen (1)"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_ciba_login_hint - This should not happen (2)"); j_return = json_pack("{si}", "result", G_ERROR); } if (j_return == NULL && j_login_hint != NULL) { if (json_string_length(json_object_get(j_login_hint, "sub"))) { if ((username_from_sub = get_username_from_sub(config, json_string_value(json_object_get(j_login_hint, "sub")), j_client)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "check_ciba_login_hint - Invalid sub '%s' for client_id '%s'", json_string_value(json_object_get(j_login_hint, "sub")), json_string_value(json_object_get(j_client, "client_id"))); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } else if (json_string_length(json_object_get(j_login_hint, "username")) && 0 != o_strcmp(username_from_sub, json_string_value(json_object_get(j_login_hint, "username")))) { y_log_message(Y_LOG_LEVEL_ERROR, "check_ciba_login_hint - sub '%s' does not match username '%s' for client_id '%s'", json_string_value(json_object_get(j_login_hint, "sub")), json_string_value(json_object_get(j_login_hint, "username")), json_string_value(json_object_get(j_client, "client_id"))); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } } else { username_from_sub = o_strdup(json_string_value(json_object_get(j_login_hint, "username"))); } if (j_return == NULL) { j_user = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, username_from_sub); if (check_result_value(j_user, G_OK) && json_object_get(json_object_get(j_user, "user"), "enabled") == json_true()) { j_return = json_pack("{sisO}", "result", G_OK, "user", json_object_get(j_user, "user")); } else if (check_result_value(j_user, G_ERROR_NOT_FOUND) || json_object_get(json_object_get(j_user, "user"), "enabled") != json_true()) { y_log_message(Y_LOG_LEVEL_ERROR, "check_ciba_login_hint - invalid username '%s' for client_id '%s'", username_from_sub, json_string_value(json_object_get(j_login_hint, "username")), json_string_value(json_object_get(j_client, "client_id"))); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_ciba_login_hint - Error glewlwyd_plugin_callback_get_user"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_user); } o_free(username_from_sub); } else if (j_return == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "check_ciba_login_hint - This should not happen (3)"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_login_hint); return j_return; } static int process_ciba_request (const struct _u_request * request, struct _u_response * response, void * user_data, json_t * j_assertion_client, int client_auth_method) { struct _oidc_config * config = (struct _oidc_config *)user_data; const char * scope = u_map_get(request->map_post_body, "scope"), * client_notification_token = u_map_get(request->map_post_body, "client_notification_token"), * login_hint_token = u_map_get(request->map_post_body, "login_hint_token"), * id_token_hint = u_map_get(request->map_post_body, "id_token_hint"), * login_hint = u_map_get(request->map_post_body, "login_hint"), * binding_message = u_map_get(request->map_post_body, "binding_message"), * user_code = u_map_get(request->map_post_body, "user_code"), * requested_expiry = u_map_get(request->map_post_body, "requested_expiry"), * client_id = request->auth_basic_user, * client_secret = request->auth_basic_password, * user_agent = u_map_get_case(request->map_header, "user-agent"), * ip_source = get_ip_source(request), * x5t_s256 = NULL; json_t * j_user_hint = NULL, * j_request = NULL, * j_client = NULL, * j_result = NULL, * j_return = NULL; long int l_requested_expiry = 0; char * scope_reduced = NULL, * jti_hash = NULL, auth_req_id[GLEWLWYD_CIBA_REQ_ID_LENGTH+1] = {0}, user_req_id[GLEWLWYD_CIBA_REQ_ID_LENGTH+1] = {0}; int hint_count = 0, res; if (j_assertion_client != NULL) { client_id = json_string_value(json_object_get(j_assertion_client, "client_id")); x5t_s256 = json_string_value(json_object_get(j_assertion_client, "x5t#S256")); } if (client_id == NULL && u_map_get(request->map_post_body, "client_id") != NULL) { client_id = u_map_get(request->map_post_body, "client_id"); } if (client_secret == NULL && u_map_get(request->map_post_body, "client_secret") != NULL) { client_secret = u_map_get(request->map_post_body, "client_secret"); client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_POST; } else if (client_secret != NULL) { client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_BASIC; } do { if (json_object_get(config->j_params, "request-parameter-allow") != json_false()) { if (o_strlen(u_map_get(request->map_post_body, "request_uri"))) { j_return = json_pack("{ss}", "error", "invalid_request"); ulfius_set_json_body_response(response, 400, j_return); json_decref(j_return); break; } else if (o_strlen(u_map_get(request->map_post_body, "request"))) { j_request = validate_ciba_jwt_request(config, u_map_get(request->map_post_body, "request"), ip_source); if (check_result_value(j_request, G_ERROR_UNAUTHORIZED)) { j_return = json_pack("{ss}", "error", "invalid_client"); ulfius_set_json_body_response(response, 401, j_return); json_decref(j_return); break; } else if (check_result_value(j_request, G_ERROR_PARAM)) { j_return = json_pack("{ss}", "error", "invalid_request"); ulfius_set_json_body_response(response, 401, j_return); json_decref(j_return); break; } else if (!check_result_value(j_request, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "process_ciba_request oidc - error validate_ciba_jwt_request"); j_return = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_return); json_decref(j_return); break; } else { client_auth_method = (int)json_integer_value(json_object_get(j_request, "client_auth_method")); client_id = json_string_value(json_object_get(json_object_get(j_request, "client"), "client_id")); jti_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, json_string_value(json_object_get(j_request, "jti"))); scope = json_string_value(json_object_get(json_object_get(j_request, "request"), "scope")); client_notification_token = json_string_value(json_object_get(json_object_get(j_request, "request"), "client_notification_token")); login_hint_token = json_string_value(json_object_get(json_object_get(j_request, "request"), "login_hint_token")); id_token_hint = json_string_value(json_object_get(json_object_get(j_request, "request"), "id_token_hint")); login_hint = json_string_value(json_object_get(json_object_get(j_request, "request"), "login_hint")); binding_message = json_string_value(json_object_get(json_object_get(j_request, "request"), "binding_message")); user_code = json_string_value(json_object_get(json_object_get(j_request, "request"), "user_code")); requested_expiry = json_string_value(json_object_get(json_object_get(j_request, "request"), "requested_expiry")); } } } if (j_assertion_client != NULL) { j_client = json_pack("{sisO}", "result", G_OK, "client", j_assertion_client); } else if (j_request != NULL) { j_client = json_pack("{sisO}", "result", G_OK, "client", json_object_get(j_request, "client")); } else { j_client = check_client_valid(config, client_id, client_secret, NULL, GLEWLWYD_AUTHORIZATION_TYPE_CIBA_FLAG, 0, ip_source); } if (!check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true()) { j_return = json_pack("{ss}", "error", "invalid_client"); ulfius_set_json_body_response(response, 401, j_return); json_decref(j_return); y_log_message(Y_LOG_LEVEL_DEBUG, "process_ciba_request oidc - client '%s' is invalid, origin: %s", client_id, ip_source); break; } if (!o_strlen(scope)) { j_return = json_pack("{ss}", "error", "invalid_scope"); ulfius_set_json_body_response(response, 401, j_return); json_decref(j_return); y_log_message(Y_LOG_LEVEL_DEBUG, "process_ciba_request oidc - client '%s', scope is mandatory, origin: %s", client_id, ip_source); break; } if (json_object_get(config->j_params, "oauth-fapi-ciba-push-forbidden") == json_true()) { if (0 == o_strcmp(json_string_value(json_object_get(json_object_get(j_client, "client"), "backchannel_token_delivery_mode")), "push")) { j_return = json_pack("{ss}", "error", "invalid_client"); ulfius_set_json_body_response(response, 401, j_return); json_decref(j_return); y_log_message(Y_LOG_LEVEL_DEBUG, "process_ciba_request oidc - client '%s' uses ciba mode push, which is forbidden, origin: %s", client_id, ip_source); break; } } if (json_object_get(config->j_params, "oauth-fapi-ciba-confidential-client") == json_true() && json_object_get(json_object_get(j_client, "client"), "confidential") != json_true()) { j_return = json_pack("{ss}", "error", "invalid_client"); ulfius_set_json_body_response(response, 401, j_return); json_decref(j_return); y_log_message(Y_LOG_LEVEL_DEBUG, "process_ciba_request oidc - client '%s' is not confidential, which is forbidden, origin: %s", client_id, ip_source); break; } if (0 == o_strcmp(json_string_value(json_object_get(json_object_get(j_client, "client"), "backchannel_token_delivery_mode")), "poll") && json_true() != json_object_get(config->j_params, "oauth-ciba-mode-poll-allowed")) { j_return = json_pack("{ss}", "error", "invalid_request"); ulfius_set_json_body_response(response, 401, j_return); json_decref(j_return); y_log_message(Y_LOG_LEVEL_DEBUG, "process_ciba_request oidc - client '%s', mode poll unauthorized, origin: %s", client_id, ip_source); break; } if (0 == o_strcmp(json_string_value(json_object_get(json_object_get(j_client, "client"), "backchannel_token_delivery_mode")), "ping") && json_true() != json_object_get(config->j_params, "oauth-ciba-mode-ping-allowed")) { j_return = json_pack("{ss}", "error", "invalid_request"); ulfius_set_json_body_response(response, 401, j_return); json_decref(j_return); y_log_message(Y_LOG_LEVEL_DEBUG, "process_ciba_request oidc - client '%s', mode ping unauthorized, origin: %s", client_id, ip_source); break; } if (0 == o_strcmp(json_string_value(json_object_get(json_object_get(j_client, "client"), "backchannel_token_delivery_mode")), "push") && json_true() != json_object_get(config->j_params, "oauth-ciba-mode-push-allowed")) { j_return = json_pack("{ss}", "error", "invalid_request"); ulfius_set_json_body_response(response, 401, j_return); json_decref(j_return); y_log_message(Y_LOG_LEVEL_DEBUG, "process_ciba_request oidc - client '%s', mode push unauthorized, origin: %s", client_id, ip_source); break; } if (json_string_length(json_object_get(config->j_params, "restrict-scope-client-property"))) { j_result = reduce_scope(scope, json_object_get(json_object_get(j_client, "client"), json_string_value(json_object_get(config->j_params, "restrict-scope-client-property")))); if (check_result_value(j_result, G_OK)) { scope_reduced = o_strdup(json_string_value(json_object_get(j_result, "scope"))); } else if (check_result_value(j_result, G_ERROR_UNAUTHORIZED)) { y_log_message(Y_LOG_LEVEL_DEBUG, "process_ciba_request oidc - error client %s is not allowed to claim scopes '%s'", client_id, scope); y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for client_id %s at IP Address %s", client_id, ip_source); j_return = json_pack("{ss}", "error", "invalid_request"); ulfius_set_json_body_response(response, 400, j_return); json_decref(j_return); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "process_ciba_request oidc - error reduce_scope"); j_return = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_return); json_decref(j_return); } } else { scope_reduced = o_strdup(scope); } if (!is_client_auth_method_allowed(json_object_get(j_client, "client"), client_auth_method)) { y_log_message(Y_LOG_LEVEL_DEBUG, "process_ciba_request oidc - client '%s' authentication method is invalid, origin: %s", client_id, ip_source); j_return = json_pack("{ss}", "error", "invalid_client"); ulfius_set_json_body_response(response, 401, j_return); json_decref(j_return); break; } if (client_id == NULL && client_secret == NULL && json_object_get(json_object_get(j_client, "client"), "confidential") == json_true()) { y_log_message(Y_LOG_LEVEL_DEBUG, "process_ciba_request oidc - client '%s' is invalid or is not confidential, origin: %s", client_id, ip_source); j_return = json_pack("{ss}", "error", "invalid_client"); ulfius_set_json_body_response(response, 401, j_return); json_decref(j_return); break; } // Check client_notification_token if (0 == o_strcmp(json_string_value(json_object_get(json_object_get(j_client, "client"), "backchannel_token_delivery_mode")), "ping") || 0 == o_strcmp(json_string_value(json_object_get(json_object_get(j_client, "client"), "backchannel_token_delivery_mode")), "push")) { if (o_strlen(client_notification_token) > 1024 || o_strlen(client_notification_token) < 22) { y_log_message(Y_LOG_LEVEL_DEBUG, "process_ciba_request oidc - client_notification_token invalid length for the client '%s', origin: %s", client_id, ip_source); j_return = json_pack("{ss}", "error", "invalid_request"); ulfius_set_json_body_response(response, 400, j_return); json_decref(j_return); break; } if (!str_has_valid_charset(client_notification_token, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~+/=")) { y_log_message(Y_LOG_LEVEL_DEBUG, "process_ciba_request oidc - client_notification_token invalid charset for the client '%s', origin: %s", client_id, ip_source); j_return = json_pack("{ss}", "error", "invalid_request"); ulfius_set_json_body_response(response, 400, j_return); json_decref(j_return); break; } } // Validate login hints if (!o_strlen(login_hint_token) && !o_strlen(id_token_hint) && !o_strlen(login_hint)) { y_log_message(Y_LOG_LEVEL_DEBUG, "process_ciba_request oidc - missing login hint for the client '%s', origin: %s", client_id, ip_source); j_return = json_pack("{ss}", "error", "invalid_request"); ulfius_set_json_body_response(response, 400, j_return); json_decref(j_return); break; } hint_count = (o_strlen(login_hint_token)?1:0) + (o_strlen(id_token_hint)?1:0) + (o_strlen(login_hint)?1:0); if (hint_count > 1) { y_log_message(Y_LOG_LEVEL_DEBUG, "process_ciba_request oidc - too many login hints for the client '%s', origin: %s", client_id, ip_source); j_return = json_pack("{ss}", "error", "invalid_request"); ulfius_set_json_body_response(response, 400, j_return); json_decref(j_return); break; } j_user_hint = check_ciba_login_hint(config, json_object_get(j_client, "client"), login_hint_token, id_token_hint, login_hint, ip_source); if (check_result_value(j_user_hint, G_ERROR_UNAUTHORIZED)) { y_log_message(Y_LOG_LEVEL_DEBUG, "process_ciba_request oidc - login hint unauthorized for the client '%s', origin: %s", client_id, ip_source); j_return = json_pack("{ss}", "error", "invalid_request"); ulfius_set_json_body_response(response, 400, j_return); json_decref(j_return); break; } else if (check_result_value(j_user_hint, G_ERROR_PARAM)) { y_log_message(Y_LOG_LEVEL_DEBUG, "process_ciba_request oidc - login hint invalid for the client '%s', origin: %s", client_id, ip_source); j_return = json_pack("{ss}", "error", "invalid_request"); ulfius_set_json_body_response(response, 400, j_return); json_decref(j_return); break; } else if (!check_result_value(j_user_hint, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "process_ciba_request oidc - Error check_ciba_login_hint"); j_return = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_return); json_decref(j_return); break; } // Validate binding_message if (o_strlen(binding_message) > 256) { y_log_message(Y_LOG_LEVEL_DEBUG, "process_ciba_request oidc - binding_message too long for the client '%s', maximum 256 characters, origin: %s", client_id, ip_source); j_return = json_pack("{ss}", "error", "invalid_binding_message"); ulfius_set_json_body_response(response, 400, j_return); json_decref(j_return); break; } // Validate user_code if (json_object_get(config->j_params, "oauth-ciba-user-code-allowed") == json_true()) { if (o_strlen(user_code) && check_ciba_user_code(config, json_object_get(j_user_hint, "user"), user_code) != G_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "process_ciba_request oidc - invalid user_code for the client '%s', origin: %s", client_id, ip_source); j_return = json_pack("{ss}", "error", "invalid_user_code"); ulfius_set_json_body_response(response, 400, j_return); json_decref(j_return); break; } } else { user_code = NULL; } // Validate requested_expiry if (o_strlen(requested_expiry)) { l_requested_expiry = strtol(requested_expiry, NULL, 10); if (l_requested_expiry <= 0) { y_log_message(Y_LOG_LEVEL_DEBUG, "process_ciba_request oidc - invalid requested_expiry for the client '%s', must be a positive integer, origin: %s", client_id, ip_source); j_return = json_pack("{ss}", "error", "invalid_request"); ulfius_set_json_body_response(response, 400, j_return); json_decref(j_return); break; } else if (l_requested_expiry > json_integer_value(json_object_get(config->j_params, "oauth-ciba-maximum-expiry"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "process_ciba_request oidc - invalid requested_expiry for the client '%s', must be a positive integer, maximum %"JSON_INTEGER_FORMAT", origin: %s", client_id, json_integer_value(json_object_get(config->j_params, "oauth-ciba-maximum-expiry")), ip_source); j_return = json_pack("{ss}", "error", "invalid_request"); ulfius_set_json_body_response(response, 400, j_return); json_decref(j_return); break; } } if (!l_requested_expiry) { l_requested_expiry = (long int)json_integer_value(json_object_get(config->j_params, "oauth-ciba-default-expiry")); } // Generate auth_req_id if (rand_string(auth_req_id, GLEWLWYD_CIBA_REQ_ID_LENGTH) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "process_ciba_request oidc - Error rand_string auth_req_id"); j_return = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_return); json_decref(j_return); break; } // Generate user_req_id if (rand_string(user_req_id, GLEWLWYD_CIBA_REQ_ID_LENGTH) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "process_ciba_request oidc - Error rand_string user_req_id"); j_return = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_return); json_decref(j_return); break; } if (serialize_ciba_request(config, client_id, json_object_get(j_user_hint, "user"), client_notification_token, auth_req_id, user_req_id, binding_message, l_requested_expiry, ip_source, user_agent, scope_reduced, x5t_s256, jti_hash) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "process_ciba_request oidc - Error serialize_ciba_request"); j_return = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_return); json_decref(j_return); break; } if (json_object_get(config->j_params, "oauth-ciba-email-allowed") == json_true()) { if ((res = send_ciba_email(config, json_object_get(j_user_hint, "user"), json_object_get(j_client, "client"), user_req_id, binding_message)) == G_ERROR_PARAM) { y_log_message(Y_LOG_LEVEL_INFO, "Send ciba e-mail, user '%s' has no e-mail address", json_string_value(json_object_get(json_object_get(j_user_hint, "user"), "email"))); } else if (res != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "process_ciba_request oidc - Error send_ciba_email"); } } j_return = json_pack("{sssi}", "auth_req_id", auth_req_id, "expires_in", l_requested_expiry); if (0 == o_strcmp(json_string_value(json_object_get(json_object_get(j_client, "client"), "backchannel_token_delivery_mode")), "poll")) { // This is only to avoid clients that would expect an interval value, // but Glewlwyd's ciba implementation doesn't care about the interval json_object_set_new(j_return, "interval", json_integer(5)); } ulfius_set_json_body_response(response, 200, j_return); json_decref(j_return); } while (0); json_decref(j_user_hint); json_decref(j_client); json_decref(j_request); json_decref(j_result); o_free(scope_reduced); o_free(jti_hash); return U_CALLBACK_CONTINUE; } static json_t * get_ciba_requests_for_user(struct _oidc_config * config, const char * username) { json_t * j_query, * j_result, * j_result_scope, * j_return, * j_element = NULL, * j_scope = NULL, * j_client; int res; char * expires_at_clause; time_t now; size_t index = 0, index_scope = 0; char * external_url = config->glewlwyd_config->glewlwyd_callback_get_plugin_external_url(config->glewlwyd_config, config->name); time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } j_query = json_pack("{sss[ssss]s{sssssis{ssss}si}ss}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA, "columns", "gpob_id", "gpob_client_id AS client_id", "gpob_user_req_id AS user_req_id", "gpob_binding_message AS binding_message", "where", "gpob_plugin_name", config->name, "gpob_username", username, "gpob_status", 0, "gpob_expires_at", "operator", "raw", "value", expires_at_clause, "gpob_enabled", 1, "order_by", "gpob_id DESC"); o_free(expires_at_clause); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result, index, j_element) { json_object_set_new(j_element, "scopes", json_array()); j_query = json_pack("{sss[s]s{sO}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA_SCOPE, "columns", "gpops_scope AS scope", "where", "gpob_id", json_object_get(j_element, "gpob_id")); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result_scope, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result_scope, index_scope, j_scope) { json_array_append(json_object_get(j_element, "scopes"), json_object_get(j_scope, "scope")); } json_decref(j_result_scope); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ciba_requests_for_user - Error executing j_query (2) at index %zu", index); } json_object_del(j_element, "gpob_id"); j_client = config->glewlwyd_config->glewlwyd_plugin_callback_get_client(config->glewlwyd_config, json_string_value(json_object_get(j_element, "client_id"))); if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true()) { json_object_set(j_element, "client_name", json_object_get(json_object_get(j_client, "client"), "name")); json_object_set(j_element, "client_description", json_object_get(json_object_get(j_client, "client"), "description")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ciba_requests_for_user - Error glewlwyd_plugin_callback_get_client '%s'", json_string_value(json_object_get(j_element, "client_id"))); } json_decref(j_client); json_object_set_new(j_element, "connect_uri", json_pack("s++", external_url, "/ciba_user_check?user_req_id=", json_string_value(json_object_get(j_element, "user_req_id")))); json_object_set_new(j_element, "cancel_uri", json_pack("s+++", external_url, "/ciba_user_check?user_req_id=", json_string_value(json_object_get(j_element, "user_req_id")), "&cancel")); } j_return = json_pack("{siso}", "result", G_OK, "ciba", j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ciba_requests_for_user - Error executing j_query (1)"); j_return = json_pack("{si}", "result", G_ERROR_DB); } o_free(external_url); return j_return; } static int update_ciba_request(struct _oidc_config * config, json_int_t gpob_id, const char * scopes_granted, json_t * j_amr, int status, const char * sid) { json_t * j_query, * j_element = NULL; int res, ret; char ** scope_array = NULL, * scope_escaped, * scope_clause = NULL; size_t i = 0; j_query = json_pack("{sss{siss?}s{sI}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA, "set", "gpob_status", status, "gpob_sid", sid, "where", "gpob_id", gpob_id); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { if (scopes_granted != NULL) { if (split_string(scopes_granted, " ", &scope_array)) { j_query = json_pack("{sss{si}s{sI}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA_SCOPE, "set", "gpobs_granted", 0, "where", "gpob_id", gpob_id); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { for (i=0; scope_array[i]!=NULL; i++) { scope_escaped = h_escape_string_with_quotes(config->glewlwyd_config->glewlwyd_config->conn, scope_array[i]); if (scope_clause == NULL) { scope_clause = msprintf("IN (%s", scope_escaped); } else { scope_clause = mstrcatf(scope_clause, ",%s", scope_escaped); } o_free(scope_escaped); } j_query = json_pack("{sss{si}s{sIs{ssss+}}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA_SCOPE, "set", "gpobs_granted", 1, "where", "gpob_id", gpob_id, "gpops_scope", "operator", "raw", "value", scope_clause, ")"); o_free(scope_clause); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_query = json_pack("{sss{sI}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA_SCHEME, "where", "gpob_id", gpob_id); res = h_delete(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_query = json_pack("{sss[]}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA_SCHEME, "values"); json_array_foreach(j_amr, i, j_element) { json_array_append_new(json_object_get(j_query, "values"), json_pack("{sIsO}", "gpob_id", gpob_id, "gpobh_scheme_module", j_element)); } if (json_array_size(json_object_get(j_query, "values"))) { res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "update_ciba_request - Error executing j_query (5)"); ret = G_ERROR_DB; } } else { ret = G_OK; } json_decref(j_query); } else { y_log_message(Y_LOG_LEVEL_ERROR, "update_ciba_request - Error executing j_query (4)"); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "update_ciba_request - Error executing j_query (3)"); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "update_ciba_request - Error executing j_query (2)"); ret = G_ERROR_DB; } free_string_array(scope_array); } else { y_log_message(Y_LOG_LEVEL_ERROR, "update_ciba_request - Error split_string"); ret = G_ERROR; } } else { ret = G_OK; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "update_ciba_request - Error executing j_query (1)"); ret = G_ERROR_DB; } return ret; } static json_t * get_ciba_request_from_user_req_id(struct _oidc_config * config, const char * user_req_id) { json_t * j_query, * j_result, * j_result_scope, * j_result_scheme, * j_return, * j_element = NULL, * j_client; int res; char * expires_at_clause, * scope_list = NULL; time_t now; size_t index = 0; if (o_strlen(user_req_id)) { time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } j_query = json_pack("{sss[ssssssssss]s{sssssis{ssss}si}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA, "columns", "gpob_id", "gpob_client_id AS client_id", "gpob_x5t_s256 AS x5t_s256", "gpob_username AS username", "gpob_status AS status", "gpob_binding_message AS binding_message", "gpob_client_notification_token AS client_notification_token", "gpob_auth_req_id AS auth_req_id", "gpob_issued_for AS issued_for", "gpob_user_agent AS user_agent", "where", "gpob_plugin_name", config->name, "gpob_user_req_id", user_req_id, "gpob_status", 0, "gpob_expires_at", "operator", "raw", "value", expires_at_clause, "gpob_enabled", 1); o_free(expires_at_clause); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_query = json_pack("{sss[ss]s{sO}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA_SCOPE, "columns", "gpops_scope AS scope", "gpobs_granted", "where", "gpob_id", json_object_get(json_array_get(j_result, 0), "gpob_id")); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result_scope, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result_scope, index, j_element) { if (json_integer_value(json_object_get(j_element, "gpobs_granted"))) { json_object_set(j_element, "granted", json_true()); } else { json_object_set(j_element, "granted", json_false()); } json_object_del(j_element, "gpobs_granted"); if (scope_list == NULL) { scope_list = o_strdup(json_string_value(json_object_get(j_element, "scope"))); } else { scope_list = mstrcatf(scope_list, " %s", json_string_value(json_object_get(j_element, "scope"))); } } json_object_set_new(json_array_get(j_result, 0), "scope", json_string(scope_list)); json_object_set(json_array_get(j_result, 0), "scopes", j_result_scope); json_decref(j_result_scope); o_free(scope_list); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ciba_request_from_user_req_id - Error executing j_query (2)"); } j_client = config->glewlwyd_config->glewlwyd_plugin_callback_get_client(config->glewlwyd_config, json_string_value(json_object_get(json_array_get(j_result, 0), "client_id"))); if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true()) { json_object_set(json_array_get(j_result, 0), "client", json_object_get(j_client, "client")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ciba_request_from_user_req_id - Error glewlwyd_plugin_callback_get_client '%s'", json_string_value(json_object_get(json_array_get(j_result, 0), "client_id"))); } json_decref(j_client); json_object_set_new(json_array_get(j_result, 0), "amr", json_array()); j_query = json_pack("{sss[s]s{sO}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA_SCHEME, "columns", "gpobh_scheme_module AS scheme_module", "where", "gpob_id", json_object_get(json_array_get(j_result, 0), "gpob_id")); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result_scheme, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result_scheme, index, j_element) { json_array_append(json_object_get(json_array_get(j_result, 0), "amr"), json_object_get(j_element, "scheme_module")); } json_decref(j_result_scheme); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ciba_request_from_user_req_id - Error executing j_query (3)"); } j_return = json_pack("{sisO}", "result", G_OK, "ciba", json_array_get(j_result, 0)); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ciba_request_from_user_req_id - Error executing j_query (1)"); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } return j_return; } static json_t * get_ciba_request_from_auth_req_id(struct _oidc_config * config, const char * auth_req_id) { json_t * j_query, * j_result, * j_result_scope, * j_result_scheme, * j_return, * j_element = NULL, * j_client; int res; char * scope_list = NULL; size_t index = 0; j_query = json_pack("{sss[ssssssssssss]s{sssssi}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA, "columns", "gpob_id", "gpob_client_id AS client_id", "gpob_x5t_s256 AS x5t_s256", "gpob_username AS username", "gpob_auth_req_id AS auth_req_id", "gpob_status AS status", "gpob_binding_message AS binding_message", "gpob_client_notification_token AS client_notification_token", "gpob_issued_for AS issued_for", "gpob_user_agent AS user_agent", SWITCH_DB_TYPE(config->glewlwyd_config->glewlwyd_config->conn->type, "UNIX_TIMESTAMP(gpob_expires_at) AS expires_at", "gpob_expires_at AS expires_at", "EXTRACT(EPOCH FROM gpob_expires_at)::integer AS expires_at"), "gpob_sid AS sid", "where", "gpob_plugin_name", config->name, "gpob_auth_req_id", auth_req_id, "gpob_enabled", 1); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_query = json_pack("{sss[s]s{sOsi}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA_SCOPE, "columns", "gpops_scope AS scope", "where", "gpob_id", json_object_get(json_array_get(j_result, 0), "gpob_id"), "gpobs_granted", 1); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result_scope, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result_scope, index, j_element) { if (scope_list == NULL) { scope_list = o_strdup(json_string_value(json_object_get(j_element, "scope"))); } else { scope_list = mstrcatf(scope_list, " %s", json_string_value(json_object_get(j_element, "scope"))); } } json_object_set_new(json_array_get(j_result, 0), "scope", json_string(scope_list)); json_object_set(json_array_get(j_result, 0), "scopes", j_result_scope); json_decref(j_result_scope); o_free(scope_list); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ciba_request_from_auth_req_id - Error executing j_query (2)"); } j_client = config->glewlwyd_config->glewlwyd_plugin_callback_get_client(config->glewlwyd_config, json_string_value(json_object_get(json_array_get(j_result, 0), "client_id"))); if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true()) { json_object_set(json_array_get(j_result, 0), "client", json_object_get(j_client, "client")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ciba_request_from_auth_req_id - Error glewlwyd_plugin_callback_get_client '%s'", json_string_value(json_object_get(json_array_get(j_result, 0), "client_id"))); } json_decref(j_client); json_object_set_new(json_array_get(j_result, 0), "amr", json_array()); j_query = json_pack("{sss[s]s{sO}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA_SCHEME, "columns", "gpobh_scheme_module AS scheme_module", "where", "gpob_id", json_object_get(json_array_get(j_result, 0), "gpob_id")); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result_scheme, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result_scheme, index, j_element) { json_array_append(json_object_get(json_array_get(j_result, 0), "amr"), json_object_get(j_element, "scheme_module")); } json_decref(j_result_scheme); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ciba_request_from_auth_req_id - Error executing j_query (3)"); } j_return = json_pack("{sisO}", "result", G_OK, "ciba", json_array_get(j_result, 0)); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ciba_request_from_auth_req_id - Error executing j_query (1)"); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } static int check_ciba_auth_req_id(struct _oidc_config * config, const struct _u_request * request, struct _u_response * response, json_t * j_assertion_client, const char * x5t_s256, int client_auth_method) { const char * auth_req_id = u_map_get(request->map_post_body, "auth_req_id"), * client_id = request->auth_basic_user, * client_secret = request->auth_basic_password, * ip_source = get_ip_source(request); json_t * j_ciba_request = get_ciba_request_from_auth_req_id(config, auth_req_id), * j_response, * j_client = NULL, * j_user = NULL, * j_token; time_t now; if (check_result_value(j_ciba_request, G_OK)) { if (client_id == NULL && u_map_get(request->map_post_body, "client_id") != NULL) { client_id = u_map_get(request->map_post_body, "client_id"); } if (client_secret == NULL && u_map_get(request->map_post_body, "client_secret") != NULL) { client_secret = u_map_get(request->map_post_body, "client_secret"); client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_POST; } else if (client_secret != NULL) { client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_BASIC; } do { // Check if client is valid if (j_assertion_client != NULL) { j_client = json_pack("{sisO}", "result", G_OK, "client", j_assertion_client); } else { if (client_id != NULL && 0 != o_strcmp(json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "client_id")), client_id)) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_ciba_auth_req_id oidc - client_id invalid"); j_client = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else { j_client = check_client_valid(config, client_id, client_secret, NULL, GLEWLWYD_AUTHORIZATION_TYPE_CIBA_FLAG, 0, ip_source); } } if (!check_result_value(j_client, G_OK) && is_client_auth_method_allowed(json_object_get(j_client, "client"), client_auth_method)) { j_response = json_pack("{ss}", "error", "unauthorized_client"); ulfius_set_json_body_response(response, 400, j_response); json_decref(j_response); break; } else if (client_id == NULL && client_secret == NULL && json_object_get(json_object_get(j_client, "client"), "confidential") == json_true()) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_ciba_auth_req_id oidc - client '%s' is invalid or is not confidential, origin: %s", client_id, ip_source); j_response = json_pack("{ss}", "error", "unauthorized_client"); ulfius_set_json_body_response(response, 400, j_response); json_decref(j_response); break; } // Check if x5t_s256 matches if ((x5t_s256 != NULL || json_string_length(json_object_get(json_object_get(j_ciba_request, "ciba"), "x5t_s256"))) && 0 != o_strcmp(x5t_s256, json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "x5t_s256")))) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_ciba_auth_req_id oidc - client '%s' mismatch x5t#s256, origin: %s", client_id, ip_source); j_response = json_pack("{ss}", "error", "unauthorized_client"); ulfius_set_json_body_response(response, 400, j_response); json_decref(j_response); break; } // Check that client delivery mode is allowed if (0 != o_strcmp(json_string_value(json_object_get(json_object_get(j_client, "client"), "backchannel_token_delivery_mode")), "ping") && 0 != o_strcmp(json_string_value(json_object_get(json_object_get(j_client, "client"), "backchannel_token_delivery_mode")), "poll")) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_ciba_auth_req_id oidc - client '%s' invalid backchannel_token_delivery_mode, origin: %s", client_id, ip_source); j_response = json_pack("{ss}", "error", "unauthorized_client"); ulfius_set_json_body_response(response, 400, j_response); json_decref(j_response); break; } // Check if request has expired time(&now); if (now > (time_t)json_integer_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "expires_at"))) { j_response = json_pack("{ss}", "error", "expired_token"); ulfius_set_json_body_response(response, 400, j_response); json_decref(j_response); break; } // Check if authorization request is still pending if (0 == json_integer_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "status"))) { j_response = json_pack("{ss}", "error", "authorization_pending"); ulfius_set_json_body_response(response, 400, j_response); json_decref(j_response); break; } // Check if authorization request is denied or error if (1 != json_integer_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "status"))) { j_response = json_pack("{ss}", "error", "access_denied"); ulfius_set_json_body_response(response, 400, j_response); json_decref(j_response); break; } // If we arrive here, request is accepted and valid, let's send the tokens! j_user = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "username"))); if (check_result_value(j_user, G_OK)) { j_token = generate_ciba_token_response(config, json_object_get(j_client, "client"), json_object_get(j_user, "user"), json_object_get(j_ciba_request, "ciba"), json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "scope")), json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "sid"))); if (check_result_value(j_token, G_OK)) { if (close_ciba_request(config, json_integer_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "gpob_id"))) == G_OK) { ulfius_set_json_body_response(response, 200, json_object_get(j_token, "token")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_ciba_auth_req_id oidc - Error close_ciba_request"); j_response = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_response); json_decref(j_response); } } else if(check_result_value(j_token, G_ERROR_PARAM)) { j_response = json_pack("{ss}", "error", "invalid_request"); ulfius_set_json_body_response(response, 400, j_response); json_decref(j_response); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_ciba_auth_req_id oidc - Error generate_ciba_token_response"); j_response = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_response); json_decref(j_response); } json_decref(j_token); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_ciba_auth_req_id oidc - Error glewlwyd_plugin_callback_get_user"); j_response = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 400, j_response); json_decref(j_response); } json_decref(j_user); } while (0); json_decref(j_client); } else if (check_result_value(j_ciba_request, G_ERROR_NOT_FOUND)) { j_response = json_pack("{ss}", "error", "invalid_grant"); ulfius_set_json_body_response(response, 400, j_response); json_decref(j_response); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_ciba_auth_req_id oidc - Error get_ciba_request_from_auth_req_id"); j_response = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 400, j_response); json_decref(j_response); } json_decref(j_ciba_request); return U_CALLBACK_CONTINUE; } static json_t * verify_pushed_authorization_request(struct _oidc_config * config, const char * request_uri, const char * client_id, const char * ip_source) { json_t * j_query, * j_result = NULL, * j_result_scope = NULL, * j_return, * j_element = NULL, * j_client = NULL; int res; char * request_uri_hash = NULL, * expires_at_clause = NULL, * scope_list = NULL, * tmp; time_t now; size_t index = 0; if (o_strlen(client_id)) { time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("((gpop_status=0 AND gpop_expires_at> FROM_UNIXTIME(%u)) OR gpop_status=1)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("((gpop_status=0 AND gpop_expires_at> TO_TIMESTAMP(%u)) OR gpop_status=1)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("((gpop_status=0 AND gpop_expires_at> %u) OR gpop_status=1)", (now)); } request_uri_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, request_uri); j_query = json_pack("{sss[ssssssssssss]s{ss ss ss s{ss ss}}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_PAR, "columns", "gpop_id", "gpop_client_id AS client_id", "gpop_response_type AS response_type", "gpop_state AS state", "gpop_redirect_uri AS redirect_uri", "gpop_nonce AS nonce", "gpop_code_challenge AS code_challenge", "gpop_resource AS resource", "gpop_claims_request", "gpop_authorization_details", "gpop_additional_parameters", "gpop_status", "where", "gpop_plugin_name", config->name, "gpop_client_id", client_id, "gpop_request_uri_hash", request_uri_hash, "1=1 AND", "operator", "raw", "value", expires_at_clause); o_free(request_uri_hash); o_free(expires_at_clause); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { if (!json_integer_value(json_object_get(json_array_get(j_result, 0), "gpop_status"))) { j_query = json_pack("{sss{si}s{sO}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_PAR, "set", "gpop_status", 1, "where", "gpop_id", json_object_get(json_array_get(j_result, 0), "gpop_id")); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "verify_pushed_authorization_request oidc - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } if (res == H_OK) { j_query = json_pack("{sss[s]s{sO}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_PAR_SCOPE, "columns", "gpops_scope AS scope", "where", "gpop_id", json_object_get(json_array_get(j_result, 0), "gpop_id")); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result_scope, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result_scope, index, j_element) { if (scope_list == NULL) { scope_list = o_strdup(json_string_value(json_object_get(j_element, "scope"))); } else { scope_list = mstrcatf(scope_list, " %s", json_string_value(json_object_get(j_element, "scope"))); } } json_object_set_new(json_array_get(j_result, 0), "scope", json_string(scope_list)); if (json_object_get(json_array_get(j_result, 0), "gpop_claims_request") != json_null()) { json_object_set_new(json_array_get(j_result, 0), "claims_request", json_loads(json_string_value(json_object_get(json_array_get(j_result, 0), "gpop_claims_request")), JSON_DECODE_ANY, NULL)); } if (json_object_get(json_array_get(j_result, 0), "gpop_authorization_details") != json_null()) { json_object_set_new(json_array_get(j_result, 0), "authorization_details", json_loads(json_string_value(json_object_get(json_array_get(j_result, 0), "gpop_authorization_details")), JSON_DECODE_ANY, NULL)); } if (json_object_get(json_array_get(j_result, 0), "gpop_additional_parameters") != json_null()) { json_object_set_new(json_array_get(j_result, 0), "additional_parameters", json_loads(json_string_value(json_object_get(json_array_get(j_result, 0), "gpop_additional_parameters")), JSON_DECODE_ANY, NULL)); } json_object_del(json_array_get(j_result, 0), "gpop_claims_request"); json_object_del(json_array_get(j_result, 0), "gpop_authorization_details"); json_object_del(json_array_get(j_result, 0), "gpop_additional_parameters"); json_object_set_new(json_array_get(j_result, 0), "type", json_integer(R_JWT_TYPE_NONE)); json_decref(j_result_scope); if (0 == o_strncmp(json_string_value(json_object_get(json_array_get(j_result, 0), "code_challenge")), GLEWLWYD_CODE_CHALLENGE_S256_PREFIX, o_strlen(GLEWLWYD_CODE_CHALLENGE_S256_PREFIX))) { tmp = o_strdup(json_string_value(json_object_get(json_array_get(j_result, 0), "code_challenge"))+o_strlen(GLEWLWYD_CODE_CHALLENGE_S256_PREFIX)); json_object_del(json_array_get(j_result, 0), "code_challenge"); json_object_set_new(json_array_get(j_result, 0), "code_challenge", json_string(tmp)); json_object_set_new(json_array_get(j_result, 0), "code_challenge_method", json_string("S256")); // TODO: remove when /auth will be refactored o_free(tmp); } else { json_object_set_new(json_array_get(j_result, 0), "code_challenge_method", json_string("plain")); } j_client = config->glewlwyd_config->glewlwyd_plugin_callback_get_client(config->glewlwyd_config, client_id); if (check_result_value(j_client, G_OK)) { j_return = json_pack("{sisOsO}", "result", G_OK, "request", json_array_get(j_result, 0), "client", json_object_get(j_client, "client")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "verify_pushed_authorization_request oidc - Error glewlwyd_plugin_callback_get_client"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(scope_list); json_decref(j_client); } else { y_log_message(Y_LOG_LEVEL_ERROR, "verify_pushed_authorization_request oidc - Error executing j_query (3)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } } else { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for client_id %s at IP Address %s", client_id, ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "verify_pushed_authorization_request oidc - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for client_id %s at IP Address %s", "(none)", ip_source); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } return j_return; } static int complete_pushed_authorization_request(struct _oidc_config * config, json_int_t gpop_id, const char * username) { json_t * j_query; int res, ret; j_query = json_pack("{sss{siss}s{sI}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_PAR, "set", "gpop_status", 2, "gpop_username", username, "where", "gpop_id", gpop_id); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_pushed_authorization_request oidc - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static int disable_refresh_token_by_jti(struct _oidc_config * config, const char * jti) { json_t * j_query; int res, ret; j_query = json_pack("{sss{si}s{sssi}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN, "set", "gpor_enabled", 0, "where", "gpor_jti", jti, "gpor_enabled", 1); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "disable_refresh_token_by_jti - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static int is_refresh_token_one_use(struct _oidc_config * config, json_t * j_client) { if (config->refresh_token_one_use == GLEWLWYD_REFRESH_TOKEN_ONE_USE_ALWAYS) { return 1; } else if (config->refresh_token_one_use == GLEWLWYD_REFRESH_TOKEN_ONE_USE_NEVER) { return 0; } else { if (j_client != NULL) { return is_true(json_string_value(json_object_get(j_client, json_string_value(json_object_get(config->j_params, "client-refresh-token-one-use-parameter"))))); } else { return 0; } } } struct _backchannel_elements { struct _oidc_config * config; char * username; char * sid; json_t * j_client_id_list; }; static void * run_backchannel_logout_thread(void * args) { struct _backchannel_elements * elt = (struct _backchannel_elements *)args; json_t * j_element = NULL, * j_client, * j_events = json_pack("{s{}}", "http://schemas.openid.net/event/backchannel-logout");; size_t index = 0; int res; jwt_t * jwt; char * sub, jti[OIDC_JTI_LENGTH+1] = {0}, * token, * out_token; jwa_alg alg; jwk_t * jwk = NULL; struct _u_request req; struct _u_response resp; json_array_foreach(elt->j_client_id_list, index, j_element) { j_client = elt->config->glewlwyd_config->glewlwyd_plugin_callback_get_client(elt->config->glewlwyd_config, json_string_value(json_object_get(j_element, "client_id"))); if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true() && json_string_length(json_object_get(json_object_get(j_client, "client"), "backchannel_logout_uri"))) { alg = get_token_sign_alg(elt->config, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_ID_TOKEN); jwk = get_jwk_sign(elt->config, json_object_get(j_client, "client"), alg); if (alg != R_JWA_ALG_UNKNOWN && alg != R_JWA_ALG_NONE && jwk != NULL) { r_jwt_init(&jwt); r_jwt_set_claim_str_value(jwt, "iss", json_string_value(json_object_get(elt->config->j_params, "iss"))); r_jwt_set_claim_str_value(jwt, "aud", json_string_value(json_object_get(json_object_get(j_client, "client"), "client_id"))); sub = get_sub(elt->config, elt->username, json_object_get(j_client, "client")); r_jwt_set_claim_str_value(jwt, "sub", sub); o_free(sub); r_jwt_set_claim_int_value(jwt, "iat", (rhn_int_t)time(NULL)); rand_string_nonce(jti, OIDC_JTI_LENGTH); r_jwt_set_claim_str_value(jwt, "jti", jti); if (is_true(json_string_value(json_object_get(json_object_get(j_client, "client"), "backchannel_logout_session_required")))) { r_jwt_set_claim_str_value(jwt, "sid", elt->sid); } r_jwt_set_claim_json_t_value(jwt, "events", j_events); r_jwt_set_sign_alg(jwt, alg); token = r_jwt_serialize_signed(jwt, jwk, 0); out_token = encrypt_token_if_required(elt->config, token, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_ID_TOKEN, &res); r_jwt_free(jwt); if (out_token != NULL) { ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_URL, json_string_value(json_object_get(json_object_get(j_client, "client"), "backchannel_logout_uri")), U_OPT_HTTP_VERB, "POST", U_OPT_POST_BODY_PARAMETER, "logout_token", out_token, U_OPT_CHECK_SERVER_CERTIFICATE, json_object_get(elt->config->j_params, "request-uri-allow-https-non-secure")==json_true()?0:1, U_OPT_CHECK_PROXY_CERTIFICATE, json_object_get(elt->config->j_params, "request-uri-allow-https-non-secure")==json_true()?0:1, U_OPT_NONE); if (ulfius_send_http_request(&req, &resp) == U_OK) { if (resp.status == 200) { y_log_message(Y_LOG_LEVEL_DEBUG, "Send backchannel_logout successfully for client %s", json_string_value(json_object_get(json_object_get(j_client, "client"), "client_id"))); } else { y_log_message(Y_LOG_LEVEL_ERROR, "run_backchannel_logout_thread - Error backchannel_logout response for client %s, response status %d", json_string_value(json_object_get(json_object_get(j_client, "client"), "client_id")), resp.status); y_log_message(Y_LOG_LEVEL_DEBUG, " - response body %.*s", resp.binary_body_length, resp.binary_body); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "run_backchannel_logout_thread - Error ulfius_send_http_request for client %s", json_string_value(json_object_get(json_object_get(j_client, "client"), "client_id"))); } ulfius_clean_request(&req); ulfius_clean_response(&resp); } else { y_log_message(Y_LOG_LEVEL_ERROR, "run_backchannel_logout_thread - Error serializing JWT for client %s", json_string_value(json_object_get(json_object_get(j_client, "client"), "client_id"))); } o_free(token); o_free(out_token); } else { y_log_message(Y_LOG_LEVEL_ERROR, "run_backchannel_logout_thread - Invalid alg or sign key for client %s", json_string_value(json_object_get(json_object_get(j_client, "client"), "client_id"))); } r_jwk_free(jwk); } json_decref(j_client); } json_decref(j_events); json_decref(elt->j_client_id_list); o_free(elt->username); o_free(elt->sid); o_free(elt); pthread_exit(NULL); } static int run_backchannel_logout(struct _oidc_config * config, const char * username, const char * sid) { json_t * j_query, * j_result = NULL; int ret, res; pthread_t thread_logout; int thread_ret, thread_detach; pthread_attr_t attr; struct sched_param param; struct _backchannel_elements * elt; if (json_object_get(config->j_params, "back-channel-logout-allowed") == json_true()) { j_query = json_pack("{sss[s]s{sssssssi}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_ID_TOKEN, "columns", "DISTINCT(gpoi_client_id) AS client_id", "where", "gpoi_plugin_name", config->name, "gpoi_username", username, "gpoi_sid", sid, "gpoi_enabled", 1); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if ((elt = o_malloc(sizeof(struct _backchannel_elements))) != NULL) { elt->config = config; elt->username = o_strdup(username); elt->sid = o_strdup(sid); elt->j_client_id_list = j_result; pthread_attr_init (&attr); pthread_attr_getschedparam (&attr, ¶m); param.sched_priority = 0; pthread_attr_setschedparam (&attr, ¶m); thread_ret = pthread_create(&thread_logout, &attr, run_backchannel_logout_thread, (void *)elt); thread_detach = pthread_detach(thread_logout); if (thread_ret || thread_detach) { y_log_message(Y_LOG_LEVEL_ERROR, "run_backchannel_logout - Error thread"); o_free(elt->username); o_free(elt->sid); o_free(elt); json_decref(j_result); ret = G_ERROR; } else { ret = G_OK; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "run_backchannel_logout - Error allocating resources for elt"); json_decref(j_result); ret = G_ERROR_MEMORY; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "run_backchannel_logout - Error executing j_query"); ret = G_ERROR_DB; } } else { ret = G_OK; } return ret; } static int disable_tokens_from_session(struct _oidc_config * config, const char * username, const char * sid) { json_t * j_query; int res, ret = G_OK; char * query, * expires_at_clause, * sid_escaped, * name_escaped, * username_escaped; time_t now; time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } sid_escaped = h_escape_string_with_quotes(config->glewlwyd_config->glewlwyd_config->conn, sid); name_escaped = h_escape_string_with_quotes(config->glewlwyd_config->glewlwyd_config->conn, config->name); username_escaped = h_escape_string_with_quotes(config->glewlwyd_config->glewlwyd_config->conn, username); // Disable access tokens query = msprintf("UPDATE "GLEWLWYD_PLUGIN_OIDC_TABLE_ACCESS_TOKEN" SET gpoa_enabled=0 WHERE gpoa_enabled=1 AND gpor_id IN (SELECT gpor_id FROM "GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN" WHERE gpor_enabled=1 AND gpor_expires_at %s AND gpoc_id IN (SELECT gpoc_id FROM "GLEWLWYD_PLUGIN_OIDC_TABLE_CODE" WHERE gpoc_plugin_name=%s AND gpoc_username=%s AND gpoc_sid=%s))", expires_at_clause, name_escaped, username_escaped, sid_escaped); res = h_execute_query(config->glewlwyd_config->glewlwyd_config->conn, query, NULL, H_OPTION_EXEC); o_free(query); if (res == H_OK) { // Disable refresh tokens query = msprintf("UPDATE "GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN" SET gpor_enabled=0 WHERE gpor_enabled=1 AND gpor_expires_at %s AND gpoc_id IN (SELECT gpoc_id FROM "GLEWLWYD_PLUGIN_OIDC_TABLE_CODE" WHERE gpoc_plugin_name=%s AND gpoc_username=%s AND gpoc_sid=%s)", expires_at_clause, name_escaped, username_escaped, sid_escaped); res = h_execute_query(config->glewlwyd_config->glewlwyd_config->conn, query, NULL, H_OPTION_EXEC); o_free(query); if (res == H_OK) { j_query = json_pack("{sss{si}s{sssssssi}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_ID_TOKEN, "set", "gpoi_enabled", 0, "where", "gpoi_plugin_name", config->name, "gpoi_username", username, "gpoi_sid", sid, "gpoi_enabled", 1); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "disable_tokens_from_session - Error executing j_query (3)"); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "disable_tokens_from_session - Error executing j_query (2)"); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "disable_tokens_from_session - Error executing j_query (1)"); ret = G_ERROR_DB; } o_free(expires_at_clause); o_free(sid_escaped); o_free(name_escaped); o_free(username_escaped); return ret; } static json_t * get_session_front_client_list(struct _oidc_config * config, const char * username, const char * sid, const char * client_id, const char * post_redirect_to) { json_t * j_query, * j_result = NULL, * j_client, * j_client_alpha, * j_return, * j_element = NULL; int res; size_t index = 0; if (o_strlen(sid) && o_strlen(client_id)) { j_client_alpha = config->glewlwyd_config->glewlwyd_plugin_callback_get_client(config->glewlwyd_config, client_id); if (check_result_value(j_client_alpha, G_OK) && json_object_get(json_object_get(j_client_alpha, "client"), "enabled") == json_true()) { j_return = json_pack("{sis{sssssssO*s[]}}", "result", G_OK, "session", "iss",json_string_value(json_object_get(config->j_params, "iss")), "sid", sid, "client_id", client_id, "client_name", json_object_get(json_object_get(j_client_alpha, "client"), "name"), "client"); if (post_redirect_to != NULL && json_array_has_string(json_object_get(json_object_get(j_client_alpha, "client"), "post_logout_redirect_uris"), post_redirect_to)) { json_object_set_new(json_object_get(j_return, "session"), "post_redirect_to", json_string(post_redirect_to)); } if (json_object_get(config->j_params, "front-channel-logout-allowed") == json_true()) { j_query = json_pack("{sss[s]s{sssssssi}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_ID_TOKEN, "columns", "DISTINCT(gpoi_client_id) AS client_id", "where", "gpoi_plugin_name", config->name, "gpoi_username", username, "gpoi_sid", sid, "gpoi_enabled", 1); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { json_array_foreach(j_result, index, j_element) { j_client = config->glewlwyd_config->glewlwyd_plugin_callback_get_client(config->glewlwyd_config, json_string_value(json_object_get(j_element, "client_id"))); if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true()) { if (json_string_length(json_object_get(json_object_get(j_client, "client"), "frontchannel_logout_uri"))) { json_array_append_new(json_object_get(json_object_get(j_return, "session"), "client"), json_pack("{sOsOso}", "client_id", json_object_get(j_element, "client_id"), "frontchannel_logout_uri", json_object_get(json_object_get(j_client, "client"), "frontchannel_logout_uri"), "frontchannel_logout_session_required", is_true(json_string_value(json_object_get(json_object_get(j_client, "client"), "frontchannel_logout_session_required")))?json_true():json_false())); } } json_decref(j_client); } } else { json_decref(j_return); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_session_front_client_list - Error executing j_query"); json_decref(j_return); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { json_decref(j_return); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_session_front_client_list - Error get_client"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } json_decref(j_client_alpha); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_session_front_client_list - Invalid input parameters"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } /** * Get a new access_token from a valid refresh_token */ static int get_access_token_from_refresh (const struct _u_request * request, struct _u_response * response, void * user_data, json_t * j_assertion_client, const char * x5t_s256, int client_auth_method) { struct _oidc_config * config = (struct _oidc_config *)user_data; const char * refresh_token = u_map_get(request->map_post_body, "refresh_token"), * ip_source = get_ip_source(request), * client_id = request->auth_basic_user, * client_secret = request->auth_basic_password, * resource = NULL; json_t * j_refresh, * j_refresh_serialize, * json_body, * j_client = NULL, * j_user, * j_client_for_sub = NULL, * j_claims_request = NULL, * j_refresh_scope = NULL, * j_authorization_details_processed = NULL; time_t now; char * access_token = NULL, * access_token_out = NULL, * new_refresh_token = NULL, * new_refresh_token_out = NULL, * scope_joined = NULL, * issued_for = NULL, jti[OIDC_JTI_LENGTH+1] = {0}; int has_error = 0, has_issues = 0, resource_checked = 0, res, enc_res = G_OK; json_int_t gpor_id = 0; if (client_id == NULL && u_map_get(request->map_post_body, "client_id") != NULL) { client_id = u_map_get(request->map_post_body, "client_id"); } if (client_secret == NULL && u_map_get(request->map_post_body, "client_secret") != NULL) { client_secret = u_map_get(request->map_post_body, "client_secret"); client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_POST; } else if (client_secret != NULL) { client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_BASIC; } if (json_object_get(config->j_params, "resource-allowed") == json_true()) { resource = u_map_get(request->map_post_body, "resource"); } if (refresh_token != NULL && o_strlen(refresh_token) == OIDC_REFRESH_TOKEN_LENGTH) { j_refresh = validate_refresh_token(config, refresh_token); if (check_result_value(j_refresh, G_OK)) { if (json_string_length(json_object_get(json_object_get(j_refresh, "token"), "claims_request"))) { if ((j_claims_request = json_loads(json_string_value(json_object_get(json_object_get(j_refresh, "token"), "claims_request")), JSON_DECODE_ANY, NULL)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "get_access_token_from_refresh oidc - Error loading JSON claims request"); } } scope_joined = join_json_string_array(json_object_get(json_object_get(j_refresh, "token"), "scope"), " "); if (scope_joined == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "get_access_token_from_refresh oidc - Error join_json_string_array"); has_error = 1; } if (json_object_get(json_object_get(j_refresh, "token"), "client_id") != json_null()) { if (client_id == NULL) { client_id = json_string_value(json_object_get(json_object_get(j_refresh, "token"), "client_id")); } if (j_assertion_client != NULL) { j_client = json_pack("{sisO}", "result", G_OK, "client", j_assertion_client); } else { if (client_id != NULL && 0 != o_strcmp(json_string_value(json_object_get(json_object_get(j_refresh, "token"), "client_id")), client_id)) { y_log_message(Y_LOG_LEVEL_DEBUG, "get_access_token_from_refresh oidc - client_id invalid"); j_client = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else { j_client = check_client_valid(config, client_id, client_secret, NULL, GLEWLWYD_AUTHORIZATION_TYPE_REFRESH_TOKEN_FLAG, 0, ip_source); } } if (!check_result_value(j_client, G_OK) && is_client_auth_method_allowed(json_object_get(j_client, "client"), client_auth_method)) { has_issues = 1; } else if (client_id == NULL && client_secret == NULL && json_object_get(json_object_get(j_client, "client"), "confidential") == json_true()) { y_log_message(Y_LOG_LEVEL_DEBUG, "get_access_token_from_refresh oidc - client '%s' is invalid or is not confidential, origin: %s", client_id, ip_source); has_issues = 1; } j_client_for_sub = json_incref(json_object_get(j_client, "client")); } if (o_strlen(resource)) { if ((res = verify_resource(config, resource, json_object_get(j_client, "client"), scope_joined)) == G_OK) { resource_checked = 1; } else if (res == G_ERROR_PARAM) { y_log_message(Y_LOG_LEVEL_DEBUG, "get_access_token_from_refresh oidc - Error resource unauthorized"); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "get_access_token_from_refresh oidc - Error verify_resource"); } if (resource_checked && 0 != o_strcmp(resource, json_string_value(json_object_get(json_object_get(j_refresh, "token"), "resource"))) && json_object_get(config->j_params, "resource-change-allowed") != json_true()) { resource_checked = 0; y_log_message(Y_LOG_LEVEL_DEBUG, "get_access_token_from_refresh oidc - Error resource change unauthorized"); } } else { if (json_object_get(json_object_get(j_refresh, "token"), "resource") != json_null()) { resource = json_string_value(json_object_get(json_object_get(j_refresh, "token"), "resource")); } resource_checked = 1; } if (resource_checked) { time(&now); issued_for = get_client_hostname(request); if (is_refresh_token_one_use(config, json_object_get(j_client, "client"))) { if (update_refresh_token(config, json_integer_value(json_object_get(json_object_get(j_refresh, "token"), "gpor_id")), 0, 1, now) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "get_access_token_from_refresh oidc - Error update_refresh_token"); has_error = 1; } if ((new_refresh_token = generate_refresh_token()) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "get_access_token_from_refresh oidc - Error generate_refresh_token"); has_error = 1; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event oidc - Plugin '%s' - Refresh token generated for client '%s' granted by user '%s' with scope list '%s', origin: %s", config->name, json_string_value(json_object_get(json_object_get(j_refresh, "token"), "client_id")), json_string_value(json_object_get(json_object_get(j_refresh, "token"), "username")), scope_joined, get_ip_source(request)); j_refresh_scope = get_refresh_token_duration_rolling(config, scope_joined); if (check_result_value(j_refresh_scope, G_OK)) { j_refresh_serialize = serialize_refresh_token(config, (uint)json_integer_value(json_object_get(json_object_get(j_refresh, "token"), "authorization_type")), 0, json_string_value(json_object_get(json_object_get(j_refresh, "token"), "username")), json_string_value(json_object_get(json_object_get(j_refresh, "token"), "client_id")), scope_joined, resource, now, json_integer_value(json_object_get(json_object_get(j_refresh_scope, "refresh-token"), "refresh-token-duration")), 0, NULL, new_refresh_token, issued_for, u_map_get_case(request->map_header, "user-agent"), (char *)json_string_value(json_object_get(json_object_get(j_refresh, "token"), "jti")), json_string_value(json_object_get(json_object_get(j_refresh, "token"), "dpop_jkt")), json_object_get(json_object_get(j_refresh, "token"), "authorization_details")); if (!check_result_value(j_refresh_serialize, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "get_access_token_from_refresh oidc - Error serialize_refresh_token"); has_error = 1; } else { gpor_id = json_integer_value(json_object_get(json_object_get(j_refresh_serialize, "token"), "gpor_id")); } json_decref(j_refresh_serialize); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_access_token_from_refresh oidc - Error get_refresh_token_duration_rolling"); has_error = 1; } json_decref(j_refresh_scope); } } else { if (update_refresh_token(config, json_integer_value(json_object_get(json_object_get(j_refresh, "token"), "gpor_id")), (json_object_get(json_object_get(j_refresh, "token"), "rolling_expiration") == json_true())?json_integer_value(json_object_get(json_object_get(j_refresh, "token"), "duration")):0, 0, now) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "get_access_token_from_refresh oidc - Error update_refresh_token"); has_error = 1; } gpor_id = json_integer_value(json_object_get(json_object_get(j_refresh, "token"), "gpor_id")); } if (!has_error && !has_issues) { j_user = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, json_string_value(json_object_get(json_object_get(j_refresh, "token"), "username"))); if (check_result_value(j_user, G_OK)) { j_authorization_details_processed = authorization_details_process_resource(json_object_get(json_object_get(j_refresh, "token"), "authorization_details"), resource, 0); if ((access_token = generate_access_token(config, json_string_value(json_object_get(json_object_get(j_refresh, "token"), "username")), j_client_for_sub, json_object_get(j_user, "user"), scope_joined, j_claims_request, resource, now, jti, x5t_s256, json_string_value(json_object_get(json_object_get(j_refresh, "token"), "dpop_jkt")), j_authorization_details_processed, get_ip_source(request))) != NULL) { if (serialize_access_token(config, GLEWLWYD_AUTHORIZATION_TYPE_REFRESH_TOKEN, gpor_id, json_string_value(json_object_get(json_object_get(j_refresh, "token"), "username")), json_string_value(json_object_get(json_object_get(j_refresh, "token"), "client_id")), scope_joined, resource, now, issued_for, u_map_get_case(request->map_header, "user-agent"), access_token, jti, j_authorization_details_processed) == G_OK) { if (config->refresh_token_one_use) { if ((access_token_out = encrypt_token_if_required(config, access_token, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_ACCESS_TOKEN, &enc_res)) != NULL) { json_body = json_pack("{sssssIsssisO*}", "access_token", access_token_out, "token_type", "bearer", "expires_in", config->access_token_duration, "scope", scope_joined, "iat", now, "authorization_details", j_authorization_details_processed); if (new_refresh_token != NULL) { if ((new_refresh_token_out = encrypt_token_if_required(config, new_refresh_token, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_REFRESH_TOKEN, &enc_res)) != NULL) { json_object_set_new(json_body, "refresh_token", json_string(new_refresh_token_out)); ulfius_set_json_body_response(response, 200, json_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 1, "plugin", config->name, NULL); } else if (enc_res == G_ERROR_UNAUTHORIZED) { y_log_message(Y_LOG_LEVEL_DEBUG, "get_access_token_from_refresh oidc - Error invalid encryption parameters (1)"); response->status = 400; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_access_token_from_refresh oidc - Error encrypt_token_if_required (1)"); response->status = 500; } } else { ulfius_set_json_body_response(response, 200, json_body); } json_decref(json_body); } else if (enc_res == G_ERROR_UNAUTHORIZED) { y_log_message(Y_LOG_LEVEL_DEBUG, "get_access_token_from_refresh oidc - Error invalid encryption parameters (2)"); response->status = 400; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_access_token_from_refresh oidc - Error encrypt_token_if_required (2)"); response->status = 500; } o_free(access_token_out); o_free(new_refresh_token_out); } else { if ((access_token_out = encrypt_token_if_required(config, access_token, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_ACCESS_TOKEN, &enc_res)) != NULL) { json_body = json_pack("{sssssIsssisO*}", "access_token", access_token_out, "token_type", "bearer", "expires_in", config->access_token_duration, "scope", scope_joined, "iat", now, "authorization_details", j_authorization_details_processed); ulfius_set_json_body_response(response, 200, json_body); json_decref(json_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 1, "plugin", config->name, NULL); } else if (enc_res == G_ERROR_UNAUTHORIZED) { y_log_message(Y_LOG_LEVEL_DEBUG, "get_access_token_from_refresh oidc - Error invalid encryption parameters"); response->status = 400; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_access_token_from_refresh oidc - Error encrypt_token_if_required (3)"); response->status = 500; } o_free(access_token_out); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_access_token_from_refresh oidc - Error serialize_access_token"); response->status = 500; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_access_token_from_refresh oidc - Error generate_client_access_token"); response->status = 500; } o_free(access_token); json_decref(j_authorization_details_processed); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_access_token_from_refresh oidc - Error glewlwyd_plugin_callback_get_user"); response->status = 500; } json_decref(j_user); } else if (has_issues) { response->status = 400; } else { response->status = 500; } o_free(issued_for); o_free(new_refresh_token); json_decref(j_claims_request); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_access_token_from_refresh oidc - Error invalid resource"); response->status = 400; } json_decref(j_client); } else if (check_result_value(j_refresh, G_ERROR_UNAUTHORIZED)) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Token invalid at IP Address %s", get_ip_source(request)); response->status = 400; if (j_assertion_client != NULL) { j_client = json_pack("{sisO}", "result", G_OK, "client", j_assertion_client); } else { if (client_id != NULL && 0 != o_strcmp(json_string_value(json_object_get(json_object_get(j_refresh, "token"), "client_id")), client_id)) { y_log_message(Y_LOG_LEVEL_DEBUG, "get_access_token_from_refresh oidc - client_id invalid"); j_client = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else { j_client = check_client_valid(config, json_string_value(json_object_get(json_object_get(j_refresh, "token"), "client_id")), client_secret, NULL, GLEWLWYD_AUTHORIZATION_TYPE_REFRESH_TOKEN_FLAG, 0, ip_source); } } if (is_refresh_token_one_use(config, json_object_get(j_client, "client")) && disable_refresh_token_by_jti(config, json_string_value(json_object_get(json_object_get(j_refresh, "token"), "jti"))) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "get_access_token_from_refresh oidc - Error disable_refresh_token_by_jti"); } json_decref(j_client); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_INVALID_REFRESH_TOKEN, 1, "plugin", config->name, NULL); } else if (check_result_value(j_refresh, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Token invalid at IP Address %s", get_ip_source(request)); response->status = 400; config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_INVALID_REFRESH_TOKEN, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_access_token_from_refresh oidc - Error validate_refresh_token"); response->status = 500; } json_decref(j_refresh); o_free(scope_joined); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "get_access_token_from_refresh oidc - Error token empty or missing, origin: %s", ip_source); response->status = 400; } json_decref(j_client_for_sub); return U_CALLBACK_CONTINUE; } /** * Invalidate a refresh token */ static int delete_refresh_token (const struct _u_request * request, struct _u_response * response, void * user_data, json_t * j_assertion_client, int client_auth_method) { struct _oidc_config * config = (struct _oidc_config *)user_data; const char * refresh_token = u_map_get(request->map_post_body, "refresh_token"), * ip_source = get_ip_source(request), * client_id = request->auth_basic_user, * client_secret = request->auth_basic_password; json_t * j_refresh, * j_client = NULL; time_t now; char * issued_for; int has_issues = 0; if (client_id == NULL && u_map_get(request->map_post_body, "client_id") != NULL) { client_id = u_map_get(request->map_post_body, "client_id"); } if (client_secret == NULL && u_map_get(request->map_post_body, "client_secret") != NULL) { client_secret = u_map_get(request->map_post_body, "client_secret"); client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_POST; } else if (client_secret != NULL) { client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_BASIC; } if (refresh_token != NULL && o_strlen(refresh_token)) { j_refresh = validate_refresh_token(config, refresh_token); if (check_result_value(j_refresh, G_OK)) { if (json_object_get(json_object_get(j_refresh, "token"), "client_id") != json_null()) { if (j_assertion_client != NULL) { j_client = json_pack("{sisO}", "result", G_OK, "client", j_assertion_client); } else { if (0 == o_strcmp(json_string_value(json_object_get(json_object_get(j_refresh, "token"), "client_id")), client_id)) { j_client = check_client_valid(config, client_id, client_secret, NULL, GLEWLWYD_AUTHORIZATION_TYPE_DELETE_TOKEN_FLAG, 0, ip_source); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "delete_refresh_token oidc - client_id invalid"); j_client = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } if (!check_result_value(j_client, G_OK) && is_client_auth_method_allowed(json_object_get(j_client, "client"), client_auth_method)) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc delete_refresh_token - client '%s' is invalid, origin: %s", request->auth_basic_user, ip_source); has_issues = 1; config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } else if (request->auth_basic_user == NULL && request->auth_basic_password == NULL && json_object_get(json_object_get(j_client, "client"), "confidential") == json_true()) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc delete_refresh_token - client '%s' is invalid or is not confidential, origin: %s", request->auth_basic_user, ip_source); has_issues = 1; config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } json_decref(j_client); } if (!has_issues) { time(&now); issued_for = get_client_hostname(request); if (update_refresh_token(config, json_integer_value(json_object_get(json_object_get(j_refresh, "token"), "gpor_id")), 0, 1, now) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "oidc delete_refresh_token - Error update_refresh_token"); response->status = 500; } o_free(issued_for); } else { response->status = 400; } } else if (check_result_value(j_refresh, G_ERROR_NOT_FOUND) || check_result_value(j_refresh, G_ERROR_UNAUTHORIZED)) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Token invalid at IP Address %s", get_ip_source(request)); response->status = 400; config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_INVALID_REFRESH_TOKEN, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc delete_refresh_token - Error validate_refresh_token"); response->status = 500; } json_decref(j_refresh); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc delete_refresh_token - token missing or empty, origin: %s", ip_source); response->status = 400; } return U_CALLBACK_CONTINUE; } /** * verify that the http request is authorized based on the access token */ static int callback_check_userinfo(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; json_t * j_introspect; int ret = U_CALLBACK_UNAUTHORIZED; if (0 == o_strncmp(HEADER_PREFIX_BEARER, u_map_get_case(request->map_header, HEADER_AUTHORIZATION), o_strlen(HEADER_PREFIX_BEARER))) { j_introspect = get_token_metadata(config, (u_map_get_case(request->map_header, HEADER_AUTHORIZATION) + o_strlen(HEADER_PREFIX_BEARER)), "access_token", NULL); if (check_result_value(j_introspect, G_OK) && json_object_get(json_object_get(j_introspect, "token"), "active") == json_true()) { ret = callback_check_glewlwyd_oidc_access_token(request, response, (void*)config->oidc_resource_config); if (ret == U_CALLBACK_CONTINUE) { json_object_set((json_t *)response->shared_data, "client", json_object_get(j_introspect, "client")); } } else { config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_INVALID_ACCESS_TOKEN, 1, "plugin", config->name, "endpoint", "userinfo", NULL); } json_decref(j_introspect); } return ret; } /** * verify that the http request is authorized based on the session or the access token */ static int callback_check_glewlwyd_session_or_token(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; json_t * j_session, * j_user, * j_introspect; int ret = U_CALLBACK_UNAUTHORIZED; char * username; if (0 == o_strncmp(HEADER_PREFIX_BEARER, u_map_get_case(request->map_header, HEADER_AUTHORIZATION), o_strlen(HEADER_PREFIX_BEARER))) { j_introspect = get_token_metadata(config, (u_map_get_case(request->map_header, HEADER_AUTHORIZATION) + o_strlen(HEADER_PREFIX_BEARER)), "access_token", NULL); if (check_result_value(j_introspect, G_OK) && json_object_get(json_object_get(j_introspect, "token"), "active") == json_true()) { ret = callback_check_glewlwyd_oidc_access_token(request, response, (void*)config->oidc_resource_config); } json_decref(j_introspect); if (ret == U_CALLBACK_CONTINUE) { username = get_username_from_sub(config, json_string_value(json_object_get((json_t *)response->shared_data, "sub")), NULL); if (username != NULL) { json_object_set_new((json_t *)response->shared_data, "username", json_string(username)); o_free(username); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_check_glewlwyd_session_or_token - Error get_username_from_sub, origin: %s", get_ip_source(request)); ret = U_CALLBACK_UNAUTHORIZED; } } return ret; } else { if (o_strlen(u_map_get(request->map_url, "impersonate"))) { j_session = config->glewlwyd_config->glewlwyd_callback_check_session_valid(config->glewlwyd_config, request, config->glewlwyd_config->glewlwyd_config->admin_scope); if (check_result_value(j_session, G_OK)) { j_user = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, u_map_get(request->map_url, "impersonate")); if (check_result_value(j_user, G_OK)) { if (ulfius_set_response_shared_data(response, json_pack("{ss}", "username", u_map_get(request->map_url, "impersonate")), (void (*)(void *))&json_decref) != U_OK) { ret = U_CALLBACK_ERROR; } else { ret = U_CALLBACK_CONTINUE; } } json_decref(j_user); } json_decref(j_session); } else { j_session = config->glewlwyd_config->glewlwyd_callback_check_session_valid(config->glewlwyd_config, request, NULL); if (check_result_value(j_session, G_OK)) { if (ulfius_set_response_shared_data(response, json_pack("{sssO}", "username", json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username")), "scope", json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "scope")), (void (*)(void *))&json_decref) != U_OK) { ret = U_CALLBACK_ERROR; } else { ret = U_CALLBACK_CONTINUE; } } json_decref(j_session); } return ret; } } /** * /auth callback */ static int callback_oidc_authorization(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; const char * response_type = NULL, * redirect_uri = NULL, * client_id = NULL, * scope = NULL, * nonce = NULL, * resource = NULL, * claims = NULL, * code_challenge = NULL, * code_challenge_method = NULL, * display = NULL, * ui_locales = NULL, * login_hint = NULL, * prompt = NULL, * id_token_hint = NULL, * ip_source = get_ip_source(request), * key = NULL, * max_age = NULL, * str_response_mode = NULL, * state = NULL, * authorization_code = NULL; char * redirect_url = NULL, ** resp_type_array = NULL, ** scope_list = NULL, * authorization_code_out = NULL, * access_token = NULL, * id_token = NULL, * id_token_out = NULL, * expires_in_str = NULL, * iat_str = NULL, * str_request = NULL, * access_token_out = NULL, * session_state = NULL, * rar_list = NULL, * scope_reduced = NULL, * id_token_hash = NULL, * issued_for = NULL, * endptr = NULL, * s_hash = NULL, jti[OIDC_JTI_LENGTH+1] = {0}, sid[OIDC_SID_LENGTH+1] = {0}, code_challenge_stored[GLEWLWYD_CODE_CHALLENGE_MAX_LENGTH + 1] = {0}; json_t * j_request = NULL, * j_client = NULL, * j_client_checked = NULL, * j_authorization_details = NULL, * j_authorization_details_processed = NULL, * j_authorization_code = NULL, * j_element = NULL, * j_claims = NULL, * j_session = NULL, * j_reduced_scope = NULL, * j_last_token = NULL, * j_rar_filtered_result = NULL; time_t now = 0; int auth_type = GLEWLWYD_AUTHORIZATION_TYPE_NULL_FLAG, check_request = 0, request_par = 0, has_code = 0, has_token = 0, has_id_token = 0, has_none = 0, res, enc_res = G_OK; struct _u_map map_redirect, map_login, * map = get_map(request); int response_mode = GLEWLWYD_RESPONSE_MODE_QUERY; size_t index = 0; long int l_max_age; jwk_t * jwk_id_token = NULL; jwt_t * jwt = NULL; jwa_alg alg; u_map_put(response->map_header, "Cache-Control", "no-store"); u_map_put(response->map_header, "Pragma", "no-cache"); u_map_put(response->map_header, "Referrer-Policy", "no-referrer"); time(&now); if (u_map_init(&map_redirect) != U_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_authorization - Error u_map_init map_redirect"); response->status = 500; return U_CALLBACK_CONTINUE; } if (u_map_init(&map_login) != U_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_authorization - Error u_map_init map_login"); response->status = 500; return U_CALLBACK_CONTINUE; } do { if (u_map_has_key(map, "state")) { u_map_put(&map_redirect, "state", u_map_get(map, "state")); state = u_map_get(map, "state"); } if (u_map_has_key(map, "redirect_uri")) { redirect_uri = u_map_get(map, "redirect_uri"); } if (u_map_has_key(map, "client_id")) { client_id = u_map_get(map, "client_id"); } if (u_map_has_key(map, "scope")) { scope = u_map_get(map, "scope"); } if (u_map_has_key(map, "nonce")) { nonce = u_map_get(map, "nonce"); } if (u_map_has_key(map, "claims")) { claims = u_map_get(map, "claims"); } if (u_map_has_key(map, "display")) { display = u_map_get(map, "display"); } if (u_map_has_key(map, "ui_locales")) { ui_locales = u_map_get(map, "ui_locales"); } if (u_map_has_key(map, "login_hint")) { login_hint = u_map_get(map, "login_hint"); } if (u_map_has_key(map, "prompt")) { prompt = u_map_get(map, "prompt"); } if (u_map_has_key(map, "id_token_hint")) { id_token_hint = u_map_get(map, "id_token_hint"); } if (u_map_has_key(map, "max_age")) { max_age = u_map_get(map, "max_age"); } if (u_map_has_key(map, "code_challenge")) { code_challenge = u_map_get(map, "code_challenge"); } if (u_map_has_key(map, "code_challenge_method")) { code_challenge_method = u_map_get(map, "code_challenge_method"); } if (u_map_has_key(map, "resource") && json_object_get(config->j_params, "resource-allowed") == json_true()) { resource = u_map_get(map, "resource"); } if (u_map_has_key(map, "response_type")) { response_type = u_map_get(map, "response_type"); if (o_strstr(response_type, "code") == NULL) { response_mode = GLEWLWYD_RESPONSE_MODE_FRAGMENT; } } if (u_map_has_key(map, "response_mode")) { str_response_mode = u_map_get(map, "response_mode"); if (0 == o_strcmp("query", str_response_mode)) { response_mode = GLEWLWYD_RESPONSE_MODE_QUERY; } else if (0 == o_strcmp("fragment", str_response_mode)) { response_mode = GLEWLWYD_RESPONSE_MODE_FRAGMENT; } else if (0 == o_strcmp("form_post", str_response_mode)) { response_mode = GLEWLWYD_RESPONSE_MODE_FORM_POST; } else if (json_object_get(config->j_params, "oauth-fapi-allow-jarm") == json_true() && (0 == o_strcmp("query.jwt", str_response_mode) || 0 == o_strcmp("jwt", str_response_mode))) { response_mode = GLEWLWYD_RESPONSE_MODE_QUERY_JWT; } else if (json_object_get(config->j_params, "oauth-fapi-allow-jarm") == json_true() && 0 == o_strcmp("fragment.jwt", str_response_mode)) { response_mode = GLEWLWYD_RESPONSE_MODE_FRAGMENT_JWT; } else if (json_object_get(config->j_params, "oauth-fapi-allow-jarm") == json_true() && 0 == o_strcmp("form_post.jwt", str_response_mode)) { response_mode = GLEWLWYD_RESPONSE_MODE_FORM_POST_JWT; } } if (o_strlen(u_map_get(map, "authorization_details")) && json_object_get(config->j_params, "oauth-rar-allowed") == json_true() && json_object_get(config->j_params, "rar-allow-auth-unsigned") == json_true()) { if ((j_authorization_details = json_loads(u_map_get(map, "authorization_details"), JSON_DECODE_ANY, NULL)) == NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_oidc_authorization - Invalid authorization_details, origin: %s", ip_source); u_map_put(&map_redirect, "error", "invalid_request"); u_map_put(&map_redirect, "error_description", "Invalid authorization_details"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } } if (json_object_get(config->j_params, "oauth-par-allowed") == json_true()) { if (o_strlen(u_map_get(map, "request_uri")) > json_string_length(json_object_get(config->j_params, "oauth-par-request_uri-prefix")) && 0 == o_strncmp(u_map_get(map, "request_uri"), json_string_value(json_object_get(config->j_params, "oauth-par-request_uri-prefix")), json_string_length(json_object_get(config->j_params, "oauth-par-request_uri-prefix")))) { j_request = verify_pushed_authorization_request(config, u_map_get(map, "request_uri"), client_id, ip_source); check_request = 1; request_par = 1; } else if (json_object_get(config->j_params, "oauth-par-required") == json_true()) { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_oidc_authorization - Pushed authorization request is mandatory, origin: %s", ip_source); response->status = 403; break; } } if (j_request == NULL && (o_strlen(u_map_get(map, "request")) || o_strlen(u_map_get(map, "request_uri")))) { if (json_object_get(config->j_params, "request-parameter-allow") != json_false()) { if (o_strlen(u_map_get(map, "request")) && o_strlen(u_map_get(map, "request_uri"))) { // parameters request and request_uri at the same time is forbidden u_map_put(&map_redirect, "error", "invalid_request"); u_map_put(&map_redirect, "error_description", "request_uri forbidden"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); } else if (o_strlen(u_map_get(map, "request_uri"))) { if ((str_request = get_request_from_uri(config, u_map_get(map, "request_uri"))) == NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_oidc_authorization - Error getting request from uri %s, origin: %s", u_map_get(map, "request_uri"), ip_source); u_map_put(&map_redirect, "error", "invalid_request_uri"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } else { j_request = validate_jwt_auth_request(config, str_request, client_id, ip_source); check_request = 1; } o_free(str_request); } else if (o_strlen(u_map_get(map, "request"))) { j_request = validate_jwt_auth_request(config, u_map_get(map, "request"), client_id, ip_source); check_request = 1; } } else if (o_strlen(u_map_get(map, "request"))) { u_map_put(&map_redirect, "error", "request_not_supported"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } else if (o_strlen(u_map_get(map, "request_uri"))) { u_map_put(&map_redirect, "error", "request_uri_not_supported"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } } if (check_request) { if (check_result_value(j_request, G_ERROR_UNAUTHORIZED) || check_result_value(j_request, G_ERROR_PARAM)) { u_map_put(&map_redirect, "error", "invalid_request_object"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } else if (!check_result_value(j_request, G_OK)) { response->status = 500; } else { if (!json_string_length(json_object_get(json_object_get(j_request, "request"), "client_id")) || (u_map_has_key(map, "client_id") && 0 != o_strcmp(json_string_value(json_object_get(json_object_get(j_request, "request"), "client_id")), u_map_get(map, "client_id")))) { // url parameter client_id can't differ from request parameter if set and must be present in request y_log_message(Y_LOG_LEVEL_DEBUG, "callback_oidc_authorization - client_id missing or invalid, origin: %s", ip_source); response->status = 403; } else if (!json_string_length(json_object_get(json_object_get(j_request, "request"), "response_type")) || (u_map_has_key(map, "response_type") && 0 != o_strcmp(json_string_value(json_object_get(json_object_get(j_request, "request"), "response_type")), u_map_get(map, "response_type")))) { // url parameter response_type can't differ from request parameter if set and must be present in request y_log_message(Y_LOG_LEVEL_DEBUG, "callback_oidc_authorization - response_type missing or invalid, origin: %s", ip_source); response->status = 403; } else if (!json_string_length(json_object_get(json_object_get(j_request, "request"), "redirect_uri"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_oidc_authorization - redirect_uri missing, origin: %s", ip_source); // redirect_uri is mandatory response->status = 403; } else { response_type = json_string_value(json_object_get(json_object_get(j_request, "request"), "response_type")); redirect_uri = json_string_value(json_object_get(json_object_get(j_request, "request"), "redirect_uri")); client_id = json_string_value(json_object_get(json_object_get(j_request, "request"), "client_id")); scope = json_string_value(json_object_get(json_object_get(j_request, "request"), "scope")); display = json_string_value(json_object_get(json_object_get(j_request, "request"), "display")); ui_locales = json_string_value(json_object_get(json_object_get(j_request, "request"), "ui_locales")); login_hint = json_string_value(json_object_get(json_object_get(j_request, "request"), "login_hint")); prompt = json_string_value(json_object_get(json_object_get(j_request, "request"), "prompt")); max_age = json_string_value(json_object_get(json_object_get(j_request, "request"), "max_age")); if (code_challenge == NULL || request_par) { code_challenge = json_string_value(json_object_get(json_object_get(j_request, "request"), "code_challenge")); } if (code_challenge_method == NULL || request_par) { code_challenge_method = json_string_value(json_object_get(json_object_get(j_request, "request"), "code_challenge_method")); } if (nonce == NULL || request_par) { nonce = json_string_value(json_object_get(json_object_get(j_request, "request"), "nonce")); } if (!u_map_has_key(&map_redirect, "state")) { u_map_put(&map_redirect, "state", json_string_value(json_object_get(json_object_get(j_request, "request"), "state"))); state = json_string_value(json_object_get(json_object_get(j_request, "request"), "state")); } if ((resource == NULL || request_par) && json_object_get(config->j_params, "resource-allowed") == json_true()) { resource = json_string_value(json_object_get(json_object_get(j_request, "request"), "resource")); } if ((j_authorization_details == NULL || request_par) && json_object_get(json_object_get(j_request, "request"), "authorization_details") != NULL) { if (json_object_get(config->j_params, "oauth-rar-allowed") == json_true()) { if ((json_integer_value(json_object_get(j_request, "type")) != R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT && json_object_get(config->j_params, "rar-allow-auth-unencrypted") == json_true()) || json_integer_value(json_object_get(j_request, "type")) == R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT) { j_authorization_details = json_incref(json_object_get(json_object_get(j_request, "request"), "authorization_details")); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_oidc_authorization - unencrypted authorization_details fobidden, origin: %s", ip_source); // redirect_uri is mandatory response->status = 403; break; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_oidc_authorization - authorization_details fobidden, origin: %s", ip_source); // redirect_uri is mandatory response->status = 403; break; } } if (json_object_get(json_object_get(j_request, "request"), "response_mode") != NULL) { str_response_mode = json_string_value(json_object_get(json_object_get(j_request, "request"), "response_mode")); if (0 == o_strcmp("query", str_response_mode)) { response_mode = GLEWLWYD_RESPONSE_MODE_QUERY; } else if (0 == o_strcmp("fragment", str_response_mode)) { response_mode = GLEWLWYD_RESPONSE_MODE_FRAGMENT; } else if (0 == o_strcmp("form_post", str_response_mode)) { response_mode = GLEWLWYD_RESPONSE_MODE_FORM_POST; } else if (json_object_get(config->j_params, "oauth-fapi-allow-jarm") == json_true() && (0 == o_strcmp("query.jwt", str_response_mode) || 0 == o_strcmp("jwt", str_response_mode))) { response_mode = GLEWLWYD_RESPONSE_MODE_QUERY_JWT; } else if (json_object_get(config->j_params, "oauth-fapi-allow-jarm") == json_true() && 0 == o_strcmp("fragment.jwt", str_response_mode)) { response_mode = GLEWLWYD_RESPONSE_MODE_FRAGMENT_JWT; } else if (json_object_get(config->j_params, "oauth-fapi-allow-jarm") == json_true() && 0 == o_strcmp("form_post.jwt", str_response_mode)) { response_mode = GLEWLWYD_RESPONSE_MODE_FORM_POST_JWT; } } } } } if (j_authorization_details!= NULL && authorization_details_validate(config, j_authorization_details, client_id, scope) != G_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_oidc_authorization - Invalid authorization_details content, origin: %s", ip_source); u_map_put(&map_redirect, "error", "invalid_request"); u_map_put(&map_redirect, "error_description", "Invalid authorization_details content"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } if (j_request != NULL) { j_client = json_pack("{sisO}", "result", G_OK, "client", json_object_get(j_request, "client")); } else { j_client = config->glewlwyd_config->glewlwyd_plugin_callback_get_client(config->glewlwyd_config, client_id); if (check_result_value(j_client, G_ERROR_NOT_FOUND) || check_result_value(j_client, G_ERROR_PARAM) || json_object_get(json_object_get(j_client, "client"), "enabled") != json_true()) { u_map_put(&map_redirect, "error", "unauthorized_client"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } else if (!check_result_value(j_client, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_authorization - Error glewlwyd_plugin_callback_get_client"); u_map_put(&map_redirect, "error", "server_error"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } } if (json_object_get(json_object_get(j_client, "client"), "response_mode") != NULL) { str_response_mode = json_string_value(json_object_get(json_object_get(j_client, "client"), "response_mode")); if (0 == o_strcmp("query", str_response_mode)) { response_mode = GLEWLWYD_RESPONSE_MODE_QUERY; } else if (0 == o_strcmp("fragment", str_response_mode)) { response_mode = GLEWLWYD_RESPONSE_MODE_FRAGMENT; } else if (0 == o_strcmp("form_post", str_response_mode)) { response_mode = GLEWLWYD_RESPONSE_MODE_FORM_POST; } else if (json_object_get(config->j_params, "oauth-fapi-allow-jarm") == json_true() && (0 == o_strcmp("query.jwt", str_response_mode) || 0 == o_strcmp("jwt", str_response_mode))) { response_mode = GLEWLWYD_RESPONSE_MODE_QUERY_JWT; } else if (json_object_get(config->j_params, "oauth-fapi-allow-jarm") == json_true() && 0 == o_strcmp("fragment.jwt", str_response_mode)) { response_mode = GLEWLWYD_RESPONSE_MODE_FRAGMENT_JWT; } else if (json_object_get(config->j_params, "oauth-fapi-allow-jarm") == json_true() && 0 == o_strcmp("form_post.jwt", str_response_mode)) { response_mode = GLEWLWYD_RESPONSE_MODE_FORM_POST_JWT; } } if (!o_strlen(response_type)) { u_map_put(&map_redirect, "error", "invalid_request"); u_map_put(&map_redirect, "error_description", "response_type missing"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } if (!split_string(response_type, " ", &resp_type_array)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_authorization - Error split_string"); u_map_put(&map_redirect, "error", "server_error"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } has_code = string_array_has_value((const char **)resp_type_array, "code"); has_token = string_array_has_value((const char **)resp_type_array, "token"); has_id_token = string_array_has_value((const char **)resp_type_array, "id_token"); has_none = string_array_has_value((const char **)resp_type_array, "none"); if (request_par && json_object_get(json_object_get(j_request, "request"), "additional_parameters") != NULL) { json_object_foreach(json_object_get(json_object_get(j_request, "request"), "additional_parameters"), key, j_element) { u_map_put(&map_redirect, key, json_string_value(j_element)); } } if (!has_code && !has_token && !has_id_token && !has_none) { u_map_put(&map_redirect, "error", "unsupported_response_type"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } if (string_array_size(resp_type_array) == 1 && has_token && !config->allow_non_oidc) { u_map_put(&map_redirect, "error", "unsupported_response_type"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } if (has_code) { auth_type |= GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE_FLAG; } if (has_token && config->allow_non_oidc) { auth_type |= GLEWLWYD_AUTHORIZATION_TYPE_TOKEN_FLAG; } if (has_id_token) { auth_type |= GLEWLWYD_AUTHORIZATION_TYPE_ID_TOKEN_FLAG; } if (has_none) { auth_type |= GLEWLWYD_AUTHORIZATION_TYPE_NONE_FLAG; } if (json_object_get(json_object_get(j_request, "request"), "claims") != NULL) { j_claims = json_incref(json_object_get(json_object_get(j_request, "request"), "claims")); } else if (o_strlen(claims)) { j_claims = json_loads(claims, JSON_DECODE_ANY, NULL); if (j_claims == NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_endpoint_auth - error claims parameter not in JSON format, origin: %s", ip_source); u_map_put(&map_redirect, "error", "invalid_request"); u_map_put(&map_redirect, "error_description", "claims parameter not in JSON format"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } } j_client_checked = check_client_valid_without_secret(config, client_id, redirect_uri, auth_type, ip_source); if (!check_result_value(j_client_checked, G_OK)) { u_map_put(&map_redirect, "error", "unauthorized_client"); u_map_put(&map_redirect, "error_description", json_string_value(json_object_get(j_client, "error_description"))); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } if (display != NULL) { u_map_put(&map_login, "display", display); } if (ui_locales != NULL) { u_map_put(&map_login, "ui_locales", ui_locales); } if (login_hint != NULL) { u_map_put(&map_login, "login_hint", login_hint); } if (j_claims != NULL && parse_claims_request(j_claims) != G_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_endpoint_auth - error parsing claims parameter, origin: %s", ip_source); u_map_put(&map_redirect, "error", "invalid_request"); u_map_put(&map_redirect, "error_description", "claims parameter invalid format"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } // Check code_challenge if necessary if (has_code) { if ((res = is_code_challenge_valid(config, scope, code_challenge, code_challenge_method, code_challenge_stored, (json_object_get(json_object_get(j_client, "client"), "confidential") == json_true()))) == G_ERROR_PARAM) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_endpoint_auth - code challenge invalid"); u_map_put(&map_redirect, "error", "invalid_request"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } else if (res != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_endpoint_auth - error is_code_challenge_valid"); u_map_put(&map_redirect, "error", "server_error"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } } if (j_authorization_details != NULL) { json_array_foreach(j_authorization_details, index, j_element) { if (rar_list == NULL) { rar_list = o_strdup(json_string_value(json_object_get(j_element, "type"))); } else { rar_list = mstrcatf(rar_list, ",%s", json_string_value(json_object_get(j_element, "type"))); } } u_map_put(&map_redirect, "authorization_details", rar_list); u_map_put(&map_redirect, "plugin", config->name); o_free(rar_list); } if (!u_map_has_key(map, "g_continue") && (0 == o_strcmp("login", prompt) || 0 == o_strcmp("consent", prompt) || 0 == o_strcmp("select_account", prompt))) { // Redirect to login page u_map_put(&map_login, "prompt", prompt); redirect_url = get_login_url(config, request, "auth", client_id, scope, &map_login); response->status = 302; ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); break; } // Check if the query parameter 'g_continue' exists, otherwise redirect to login page if (!u_map_has_key(map, "g_continue") && 0 != o_strcmp("none", prompt)) { // Redirect to login page redirect_url = get_login_url(config, request, "auth", client_id, scope, &map_login); response->status = 302; ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); break; } // Check if at least one scope has been provided if (!o_strlen(scope)) { // Scope is not allowed for this user y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_endpoint_auth - scope list is missing or empty or scope 'openid' missing, origin: %s", ip_source); u_map_put(&map_redirect, "error", "invalid_scope"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } if (json_string_length(json_object_get(config->j_params, "restrict-scope-client-property"))) { j_reduced_scope = reduce_scope(scope, json_object_get(json_object_get(j_client, "client"), json_string_value(json_object_get(config->j_params, "restrict-scope-client-property")))); if (check_result_value(j_reduced_scope, G_OK)) { scope_reduced = o_strdup(json_string_value(json_object_get(j_reduced_scope, "scope"))); } else if (check_result_value(j_reduced_scope, G_ERROR_UNAUTHORIZED)) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_endpoint_auth - error client %s is not allowed to claim scopes '%s'", client_id, scope); y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for client_id %s at IP Address %s", client_id, ip_source); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); u_map_put(&map_redirect, "error", "invalid_scope"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_endpoint_auth - error reduce_scope"); u_map_put(&map_redirect, "error", "server_error"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } } else { scope_reduced = o_strdup(scope); } // Split scope list into scope array if (!split_string(scope_reduced, " ", &scope_list)) { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_endpoint_auth - Error split_string"); u_map_put(&map_redirect, "error", "server_error"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } // Check that the scope 'openid' is provided, otherwise return error if ((!string_array_has_value((const char **)scope_list, "openid") && !config->allow_non_oidc) || (auth_type & GLEWLWYD_AUTHORIZATION_TYPE_ID_TOKEN_FLAG && !string_array_has_value((const char **)scope_list, "openid"))) { // Scope openid missing y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_endpoint_auth - scope 'openid' missing, origin: %s", ip_source); u_map_put(&map_redirect, "error", "invalid_scope"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } // Check that the session is valid for this user with this scope list j_session = validate_session_client_scope(config, request, client_id, scope_reduced); if (check_result_value(j_session, G_ERROR_NOT_FOUND)) { if (0 == o_strcmp("none", prompt)) { // Scope is not allowed for this user y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_endpoint_auth - prompt 'none', avoid login page, origin: %s", ip_source); u_map_put(&map_redirect, "error", "interaction_required"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); } else { // Redirect to login page response->status = 302; redirect_url = get_login_url(config, request, "auth", client_id, scope_reduced, &map_login); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } break; } else if (check_result_value(j_session, G_ERROR_UNAUTHORIZED)) { if (0 == o_strcmp("none", prompt)) { // Scope is not allowed for this user y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_endpoint_auth - prompt 'none', avoid login page, origin: %s", ip_source); u_map_put(&map_redirect, "error", "interaction_required"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); } else { if (json_object_get(json_object_get(j_session, "session"), "user") != NULL) { // Scope is not allowed for this user y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_endpoint_auth - scope list '%s' is invalid for user '%s', origin: %s", scope, json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username")), ip_source); u_map_put(&map_redirect, "error", "invalid_scope"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); } else { // Redirect to login page u_map_put(&map_login, "prompt", prompt); redirect_url = get_login_url(config, request, "auth", client_id, scope_reduced, &map_login); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); response->status = 302; } } break; } else if (!check_result_value(j_session, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_endpoint_auth - Error validate_session_client_scope %s", json_dumps(j_session, JSON_ENCODE_ANY)); u_map_put(&map_redirect, "error", "server_error"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } // If parameter prompt=none is set, id_token_hint must be set and correspond to the last id_token provided by the client for the current user if (0 == o_strcmp("none", prompt)) { if (o_strlen(id_token_hint)) { alg = get_token_sign_alg(config, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_ID_TOKEN); jwk_id_token = get_jwk_sign(config, json_object_get(j_client, "client"), alg); if ((jwt = r_jwt_quick_parse(id_token_hint, R_PARSE_NONE, 0)) != NULL && r_jwt_verify_signature(jwt, jwk_id_token, 0) == RHN_OK) { j_last_token = get_last_id_token(config, json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username")), client_id); if (check_result_value(j_last_token, G_OK)) { id_token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, id_token_hint); if (0 != o_strcmp(id_token_hash, json_string_value(json_object_get(json_object_get(j_last_token, "id_token"), "token_hash")))) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_endpoint_auth - id_token_hint was not the last one provided to client '%s' for user '%s', origin: %s", client_id, json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username")), ip_source); u_map_put(&map_redirect, "error", "invalid_request"); u_map_put(&map_redirect, "error_description", "id_token invalid"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } } else if (check_result_value(j_last_token, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_endpoint_auth - no id_token was provided to client '%s' for user '%s', origin: %s", client_id, json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username")), ip_source); u_map_put(&map_redirect, "error", "invalid_request"); u_map_put(&map_redirect, "error_description", "id_token mandatory"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_endpoint_auth - Error get_last_id_token"); u_map_put(&map_redirect, "error", "server_error"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_endpoint_auth - id_token has invalid content or signature, origin: %s", ip_source); u_map_put(&map_redirect, "error", "invalid_request"); u_map_put(&map_redirect, "error_description", "id_token invalid"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_endpoint_auth - no id_token provided in the request, origin: %s", ip_source); u_map_put(&map_redirect, "error", "invalid_request"); u_map_put(&map_redirect, "error_description", "id_token mandatory"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } } // Session may be valid but another level of authentication may be requested if (json_object_get(json_object_get(j_session, "session"), "authorization_required") == json_true()) { if (0 == o_strcmp("none", prompt)) { // Scope is not allowed for this user y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_endpoint_auth - prompt 'none', avoid login page, origin: %s", ip_source); u_map_put(&map_redirect, "error", "interaction_required"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } else { // Redirect to login page redirect_url = get_login_url(config, request, "auth", client_id, scope_reduced, &map_login); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); response->status = 302; } break; } issued_for = get_client_hostname(request); if (issued_for == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_endpoint_auth - Error get_client_hostname"); u_map_put(&map_redirect, "error", "interaction_required"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } // Trigger the use of this session to reset use of some schemes if (config->glewlwyd_config->glewlwyd_callback_trigger_session_used(config->glewlwyd_config, request, json_string_value(json_object_get(json_object_get(j_session, "session"), "scope_filtered"))) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_endpoint_auth - Error glewlwyd_callback_trigger_session_used"); u_map_put(&map_redirect, "error", "interaction_required"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } // nonce parameter is required for some authorization types or when openid scope is granted if (((auth_type & GLEWLWYD_AUTHORIZATION_TYPE_ID_TOKEN_FLAG) || string_array_has_value((const char **)scope_list, "openid")) && !o_strlen(nonce)) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_endpoint_auth - nonce required, origin: %s", ip_source); u_map_put(&map_redirect, "error", "invalid_request"); u_map_put(&map_redirect, "error_description", "nonce required"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } if (j_authorization_details != NULL) { j_rar_filtered_result = authorization_details_filter(config, j_authorization_details, json_string_value(json_object_get(json_object_get(j_session, "session"), "scope_filtered")), json_object_get(j_client, "client"), json_object_get(json_object_get(j_session, "session"), "user"), ip_source); if (check_result_value(j_rar_filtered_result, G_ERROR_UNAUTHORIZED)) { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_endpoint_auth - authorization_details is invalid for client %s, origin: %s", client_id, ip_source); u_map_put(&map_redirect, "error", "invalid_request"); u_map_put(&map_redirect, "error_description", "authorization_details is invalid for client"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } else if (check_result_value(j_rar_filtered_result, G_OK)) { if (json_object_get(j_rar_filtered_result, "requires_consent") == json_true()) { // Redirect to login page u_map_put(&map_login, "prompt", "consent"); redirect_url = get_login_url(config, request, "auth", client_id, scope_reduced, &map_login); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); response->status = 302; break; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc validate_endpoint_auth - Error authorization_details_filter"); u_map_put(&map_redirect, "error", "server_error"); u_map_put(&map_redirect, "error_description", "authorization_details invalid"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } } if (o_strlen(max_age)) { l_max_age = strtol(max_age, &endptr, 10); if (!(*endptr) && l_max_age > 0) { time(&now); if (l_max_age < (now - (time_t)config->glewlwyd_config->glewlwyd_callback_get_session_age(config->glewlwyd_config, request, json_string_value(json_object_get(json_object_get(j_session, "session"), "scope_filtered"))))) { // Redirect to login page u_map_put(&map_login, "refresh_login", "true"); redirect_url = get_login_url(config, request, "auth", client_id, scope_reduced, &map_login); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); response->status = 302; break; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc validate_endpoint_auth - nonce required, origin: %s", ip_source); u_map_put(&map_redirect, "error", "invalid_request"); u_map_put(&map_redirect, "error_description", "nonce required"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } } if (json_object_get(config->j_params, "session-management-allowed") == json_true()) { session_state = generate_session_state(client_id, redirect_uri, json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username"))); if (o_strlen(session_state)) { u_map_put(&map_redirect, "session_state", session_state); } o_free(session_state); } if (o_strlen(resource)) { if ((res = verify_resource(config, resource, json_object_get(j_client, "client"), json_string_value(json_object_get(json_object_get(j_session, "session"), "scope_filtered")))) == G_ERROR_PARAM) { u_map_put(&map_redirect, "error", "invalid_target"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } else if (res != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_authorization - Error verify_resource"); u_map_put(&map_redirect, "error", "server_error"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } } if (json_object_get(config->j_params, "oauth-fapi-add-s_hash") == json_true()) { s_hash = generate_x_hash(config, json_object_get(j_client, "client"), state); } if (get_session_token(config, request, response, sid) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_authorization - Error get_session_token"); u_map_put(&map_redirect, "error", "server_error"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } if (has_code) { if (is_authorization_type_enabled((struct _oidc_config *)user_data, GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE) && redirect_uri != NULL) { // Generates authorization code j_authorization_code = generate_authorization_code(config, json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username")), client_id, json_string_value(json_object_get(json_object_get(j_session, "session"), "scope_filtered")), redirect_uri, issued_for, u_map_get_case(request->map_header, "user-agent"), nonce, resource, json_object_get(json_object_get(j_session, "session"), "amr"), j_claims, auth_type, code_challenge_stored, json_object_get(j_rar_filtered_result, "authorization_details"), s_hash, sid); if (check_result_value(j_authorization_code, G_OK)) { authorization_code = json_string_value(json_object_get(j_authorization_code, "code")); if ((authorization_code_out = encrypt_token_if_required(config, authorization_code, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_CODE, &enc_res)) != NULL) { u_map_put(&map_redirect, "code", authorization_code_out); } else if (enc_res == G_ERROR_UNAUTHORIZED) { u_map_put(&map_redirect, "error", "invalid_request"); u_map_put(&map_redirect, "error_description", "Invalid encryption parameters"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_auth_code_grant - encrypt_token_if_required for code"); u_map_put(&map_redirect, "error", "server_error"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } o_free(authorization_code_out); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_CODE, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_auth_code_grant - Error generate_authorization_code"); u_map_put(&map_redirect, "error", "server_error"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); } } else { u_map_put(&map_redirect, "error", "unsupported_response_type"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } } if (has_token) { if (is_authorization_type_enabled((struct _oidc_config *)user_data, GLEWLWYD_AUTHORIZATION_TYPE_TOKEN) && redirect_uri != NULL) { j_authorization_details_processed = authorization_details_process_resource(json_object_get(j_rar_filtered_result, "authorization_details"), resource, 1); if ((access_token = generate_access_token(config, json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username")), json_object_get(j_client, "client"), json_object_get(json_object_get(j_session, "session"), "user"), json_string_value(json_object_get(json_object_get(j_session, "session"), "scope_filtered")), json_object_get(j_claims, "userinfo"), resource, now, jti, NULL, NULL, j_authorization_details_processed, get_ip_source(request))) != NULL) { if (serialize_access_token(config, auth_type, 0, json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username")), client_id, json_string_value(json_object_get(json_object_get(j_session, "session"), "scope_filtered")), resource, now, issued_for, u_map_get_case(request->map_header, "user-agent"), access_token, jti, j_authorization_details_processed) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_implicit_grant - Error serialize_access_token"); u_map_put(&map_redirect, "error", "server_error"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } else { if ((access_token_out = encrypt_token_if_required(config, access_token, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_ACCESS_TOKEN, &enc_res)) != NULL) { expires_in_str = msprintf("%" JSON_INTEGER_FORMAT, config->access_token_duration); iat_str = msprintf("%ld", now); u_map_put(&map_redirect, "access_token", access_token_out); u_map_put(&map_redirect, "token_type", "bearer"); u_map_put(&map_redirect, "expires_in", expires_in_str); u_map_put(&map_redirect, "iat", iat_str); u_map_put(&map_redirect, "scope", json_string_value(json_object_get(json_object_get(j_session, "session"), "scope_filtered"))); o_free(expires_in_str); o_free(iat_str); } else if (enc_res == G_ERROR_UNAUTHORIZED) { u_map_put(&map_redirect, "error", "invalid_request"); u_map_put(&map_redirect, "error_description", "Invalid encryption parameters"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } else { u_map_put(&map_redirect, "error", "server_error"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } o_free(access_token_out); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 1, "plugin", config->name, NULL); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_implicit_grant - Error generate_access_token"); u_map_put(&map_redirect, "error", "server_error"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } } else { u_map_put(&map_redirect, "error", "unsupported_response_type"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } } if (has_id_token) { if (is_authorization_type_enabled((struct _oidc_config *)user_data, GLEWLWYD_AUTHORIZATION_TYPE_ID_TOKEN) && redirect_uri != NULL) { if ((id_token = generate_id_token(config, json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username")), json_object_get(json_object_get(j_session, "session"), "user"), json_object_get(j_client, "client"), now, config->glewlwyd_config->glewlwyd_callback_get_session_age(config->glewlwyd_config, request, json_string_value(json_object_get(json_object_get(j_session, "session"), "scope_filtered"))), nonce, json_object_get(json_object_get(j_session, "session"), "amr"), access_token, authorization_code, json_string_value(json_object_get(json_object_get(j_session, "session"), "scope_filtered")), json_object_get(j_claims, "id_token"), NULL, NULL, s_hash, sid, ip_source)) != NULL) { if (serialize_id_token(config, GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE, id_token, json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username")), client_id, sid, json_integer_value(json_object_get(j_authorization_code, "gpoc_id")), 0, now, issued_for, u_map_get_case(request->map_header, "user-agent")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_access_token_request - Error serialize_id_token"); u_map_put(&map_redirect, "error", "server_error"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } else { if ((id_token_out = encrypt_token_if_required(config, id_token, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_ID_TOKEN, &enc_res)) != NULL) { u_map_put(&map_redirect, "id_token", id_token_out); } else if (enc_res == G_ERROR_UNAUTHORIZED) { u_map_put(&map_redirect, "error", "invalid_request"); u_map_put(&map_redirect, "error_description", "Invalid encryption parameters"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } else { u_map_put(&map_redirect, "error", "server_error"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } o_free(id_token_out); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_ID_TOKEN, 1, "plugin", config->name, "response_type", response_type, NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_ID_TOKEN, 1, "plugin", config->name, NULL); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "oidc check_auth_type_access_token_request - Error generate_id_token"); u_map_put(&map_redirect, "error", "server_error"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } } else { u_map_put(&map_redirect, "error", "unsupported_response_type"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } } if (has_none) { if (!is_authorization_type_enabled((struct _oidc_config *)user_data, GLEWLWYD_AUTHORIZATION_TYPE_NONE)) { u_map_put(&map_redirect, "error", "unsupported_response_type"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } } // Redirect to redirect_uri with results build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); if (request_par) { if (complete_pushed_authorization_request(config, json_integer_value(json_object_get(json_object_get(j_request, "request"), "gpop_id")), json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username"))) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_authorization - Error complete_pushed_authorization_request"); u_map_put(&map_redirect, "error", "server_error"); build_auth_response(config, response, response_mode, json_object_get(j_client, "client"), redirect_uri, &map_redirect); break; } } } while (0); o_free(access_token); o_free(id_token); o_free(scope_reduced); o_free(issued_for); o_free(id_token_hash); o_free(s_hash); free_string_array(resp_type_array); free_string_array(scope_list); u_map_clean(&map_redirect); u_map_clean(&map_login); json_decref(j_request); json_decref(j_client); json_decref(j_client_checked); json_decref(j_authorization_details); json_decref(j_authorization_details_processed); json_decref(j_claims); json_decref(j_session); json_decref(j_reduced_scope); json_decref(j_last_token); json_decref(j_rar_filtered_result); json_decref(j_authorization_code); r_jwt_free(jwt); r_jwk_free(jwk_id_token); return U_CALLBACK_CONTINUE; } /** * /token callback */ static int callback_oidc_token(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; const char * grant_type = u_map_get(request->map_post_body, "grant_type"), * ip_source = get_ip_source(request); int result = U_CALLBACK_CONTINUE, client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_NONE; json_t * j_assertion = NULL, * j_assertion_client = NULL; const char * x5t_s256 = NULL; if (o_strlen(u_map_get(request->map_post_body, "client_assertion")) && 0 == o_strcmp(GLEWLWYD_AUTH_TOKEN_ASSERTION_TYPE, u_map_get(request->map_post_body, "client_assertion_type"))) { if (json_object_get(config->j_params, "request-parameter-allow") == json_true()) { j_assertion = validate_jwt_assertion_request(config, u_map_get(request->map_post_body, "client_assertion"), "token", ip_source); if (check_result_value(j_assertion, G_ERROR_UNAUTHORIZED) || check_result_value(j_assertion, G_ERROR_PARAM)) { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_oidc_token - Error validating client_assertion"); result = U_CALLBACK_UNAUTHORIZED; } else if (!check_result_value(j_assertion, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_token - Error validate_jwt_assertion_request"); result = U_CALLBACK_ERROR; } else { j_assertion_client = json_object_get(j_assertion, "client"); client_auth_method = (int)json_integer_value(json_object_get(j_assertion, "client_auth_method")); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_oidc_token - unauthorized request parameter"); result = U_CALLBACK_UNAUTHORIZED; } } else { j_assertion = check_client_certificate_valid(config, request); if (check_result_value(j_assertion, G_ERROR_UNAUTHORIZED)) { result = U_CALLBACK_UNAUTHORIZED; } else if (j_assertion != NULL && !check_result_value(j_assertion, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_token - Error check_client_certificate_valid"); result = U_CALLBACK_ERROR; } else if (check_result_value(j_assertion, G_OK)) { j_assertion_client = json_object_get(j_assertion, "client"); x5t_s256 = json_string_value(json_object_get(j_assertion, "x5t#S256")); client_auth_method = (int)json_integer_value(json_object_get(j_assertion, "client_auth_method")); } } if (result == U_CALLBACK_CONTINUE) { if (0 == o_strcmp("authorization_code", grant_type)) { if (is_authorization_type_enabled(config, GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE)) { result = check_auth_type_access_token_request(request, response, user_data, j_assertion_client, x5t_s256, client_auth_method); } else { response->status = 403; } } else if (0 == o_strcmp("password", grant_type)) { if (is_authorization_type_enabled(config, GLEWLWYD_AUTHORIZATION_TYPE_RESOURCE_OWNER_PASSWORD_CREDENTIALS) && config->allow_non_oidc) { result = check_auth_type_resource_owner_pwd_cred(request, response, user_data, j_assertion_client, x5t_s256, client_auth_method); } else { response->status = 403; } } else if (0 == o_strcmp("client_credentials", grant_type)) { if (is_authorization_type_enabled(config, GLEWLWYD_AUTHORIZATION_TYPE_CLIENT_CREDENTIALS) && config->allow_non_oidc) { result = check_auth_type_client_credentials_grant(request, response, user_data, j_assertion_client, x5t_s256, client_auth_method); } else { response->status = 403; } } else if (0 == o_strcmp("refresh_token", grant_type)) { if (is_authorization_type_enabled(config, GLEWLWYD_AUTHORIZATION_TYPE_REFRESH_TOKEN)) { result = get_access_token_from_refresh(request, response, user_data, j_assertion_client, x5t_s256, client_auth_method); } else { response->status = 403; } } else if (0 == o_strcmp("delete_token", grant_type)) { result = delete_refresh_token(request, response, user_data, j_assertion_client, client_auth_method); } else if (0 == o_strcmp(GLEWLWYD_DEVICE_AUTH_GRANT_TYPE, grant_type)) { result = check_auth_type_device_code(request, response, user_data, j_assertion_client, x5t_s256, client_auth_method); } else if (0 == o_strcmp(GLEWLWYD_CIBA_GRANT_TYPE, grant_type)) { result = check_ciba_auth_req_id(config, request, response, j_assertion_client, x5t_s256, client_auth_method); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "oidc callback_oidc_token - Unknown grant_type '%s', origin: %s", grant_type, get_ip_source(request)); response->status = 400; } } else if (result == U_CALLBACK_UNAUTHORIZED) { result = U_CALLBACK_CONTINUE; response->status = 403; } json_decref(j_assertion); u_map_put(response->map_header, "Cache-Control", "no-store"); u_map_put(response->map_header, "Pragma", "no-cache"); u_map_put(response->map_header, "Referrer-Policy", "no-referrer"); return result; } /** * /userinfo callback */ static int callback_oidc_get_userinfo(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; char * username = get_username_from_sub(config, json_string_value(json_object_get((json_t *)response->shared_data, "sub")), NULL), * token = NULL, * token_out = NULL; json_t * j_user, * j_userinfo, * j_client = config->glewlwyd_config->glewlwyd_plugin_callback_get_client(config->glewlwyd_config, json_string_value(json_object_get((json_t *)response->shared_data, "client_id"))); jwt_t * jwt = NULL; jwa_alg alg = get_token_sign_alg(config, json_object_get((json_t *)response->shared_data, "client"), GLEWLWYD_TOKEN_TYPE_USERINFO); jwk_t * jwk = get_jwk_sign(config, json_object_get((json_t *)response->shared_data, "client"), alg); json_t * j_jkt = NULL; int jkt_continue = 1, enc_res = G_OK; char * external_url, * htu; u_map_put(response->map_header, "Cache-Control", "no-store"); u_map_put(response->map_header, "Pragma", "no-cache"); u_map_put(response->map_header, "Referrer-Policy", "no-referrer"); if (json_object_get((json_t *)response->shared_data, "jkt") != NULL) { external_url = config->glewlwyd_config->glewlwyd_callback_get_plugin_external_url(config->glewlwyd_config, config->name); htu = msprintf("%s/userinfo", external_url); j_jkt = verify_dpop_proof(request, request->http_verb, htu, (time_t)json_integer_value(json_object_get(config->j_params, "oauth-dpop-iat-duration")), json_string_value(json_object_get((json_t *)response->shared_data, "jkt")), (u_map_get_case(request->map_header, HEADER_AUTHORIZATION) + o_strlen(HEADER_PREFIX_BEARER))); if (!check_result_value(j_jkt, G_TOKEN_OK)) { jkt_continue = 0; } else if (check_dpop_jti(config, json_string_value(json_object_get(json_object_get(j_jkt, "claims"), "jti")), json_string_value(json_object_get(json_object_get(j_jkt, "claims"), "htm")), json_string_value(json_object_get(json_object_get(j_jkt, "claims"), "htu")), json_integer_value(json_object_get(json_object_get(j_jkt, "claims"), "iat")), json_string_value(json_object_get((json_t *)response->shared_data, "client_id")), json_string_value(json_object_get((json_t *)response->shared_data, "jkt")), get_ip_source(request)) != G_OK) { jkt_continue = 0; } json_decref(j_jkt); o_free(htu); o_free(external_url); } if (jkt_continue) { if (username != NULL) { j_user = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, username); if (check_result_value(j_user, G_OK)) { j_userinfo = get_userinfo(config, json_string_value(json_object_get((json_t *)response->shared_data, "sub")), json_object_get(j_user, "user"), json_object_get((json_t *)response->shared_data, "claims"), json_string_value(json_object_get((json_t *)response->shared_data, "scope"))); if (j_userinfo != NULL) { if (0 == o_strcmp("jwt", u_map_get(request->map_url, "format")) || 0 == o_strcmp("jwt", u_map_get(request->map_post_body, "format")) || 0 == o_strcasecmp("application/jwt", u_map_get_case(request->map_header, "Accept")) || 0 == o_strcasecmp("application/token-userinfo+jwt", u_map_get_case(request->map_header, "Accept"))) { if (jwk != NULL && alg != R_JWA_ALG_UNKNOWN) { if (r_jwt_init(&jwt) == RHN_OK) { r_jwt_set_sign_alg(jwt, alg); json_object_set(j_userinfo, "iss", json_object_get(config->j_params, "iss")); if (r_jwt_set_full_claims_json_t(jwt, j_userinfo) == RHN_OK) { r_jwt_set_header_str_value(jwt, "typ", "token-userinfo+jwt"); token = r_jwt_serialize_signed(jwt, jwk, 0); if (token != NULL) { if ((token_out = encrypt_token_if_required(config, token, json_object_get(j_client, "client"), GLEWLWYD_TOKEN_TYPE_USERINFO, &enc_res)) != NULL) { ulfius_set_string_body_response(response, 200, token_out); u_map_put(response->map_header, "Content-Type", "application/jwt"); } else if (enc_res == G_ERROR_UNAUTHORIZED) { response->status = 400; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_get_userinfo oidc - Error encrypt_token_if_required"); response->status = 500; } o_free(token_out); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_get_userinfo oidc - Error r_jwt_serialize_signed"); response->status = 500; } o_free(token); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_get_userinfo oidc - Error r_jwt_set_full_claims_json_t"); response->status = 500; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_introspection - oidc - Error r_jwt_init"); } r_jwt_free(jwt); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_introspection - oidc - Error no jwk available"); } } else { ulfius_set_json_body_response(response, 200, j_userinfo); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_get_userinfo oidc - Error get_userinfo"); response->status = 500; } json_decref(j_userinfo); } else if (check_result_value(j_user, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_get_userinfo oidc - Error glewlwyd_plugin_callback_get_user_profile"); response->status = 500; } json_decref(j_user); } else { response->status = 404; } } else { response->status = 401; } o_free(username); json_decref(j_client); r_jwk_free(jwk); return U_CALLBACK_CONTINUE; } /** * GET /token callback */ static int callback_oidc_refresh_token_list_get(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; size_t offset = 0, limit = GLEWLWYD_DEFAULT_LIMIT_SIZE; long int l_converted = 0; char * endptr = NULL, * sort = NULL, * external_url, * htu; json_t * j_refresh_list, * j_jkt = NULL; int jkt_continue = 1; u_map_put(response->map_header, "Cache-Control", "no-store"); u_map_put(response->map_header, "Pragma", "no-cache"); u_map_put(response->map_header, "Referrer-Policy", "no-referrer"); if (json_object_get((json_t *)response->shared_data, "jkt") != NULL) { external_url = config->glewlwyd_config->glewlwyd_callback_get_plugin_external_url(config->glewlwyd_config, config->name); htu = msprintf("%s/token", external_url); j_jkt = verify_dpop_proof(request, request->http_verb, htu, (time_t)json_integer_value(json_object_get(config->j_params, "oauth-dpop-iat-duration")), json_string_value(json_object_get((json_t *)response->shared_data, "jkt")), (u_map_get_case(request->map_header, HEADER_AUTHORIZATION) + o_strlen(HEADER_PREFIX_BEARER))); if (!check_result_value(j_jkt, G_TOKEN_OK)) { jkt_continue = 0; } else if (check_dpop_jti(config, json_string_value(json_object_get(json_object_get(j_jkt, "claims"), "jti")), json_string_value(json_object_get(json_object_get(j_jkt, "claims"), "htm")), json_string_value(json_object_get(json_object_get(j_jkt, "claims"), "htu")), json_integer_value(json_object_get(json_object_get(j_jkt, "claims"), "iat")), json_string_value(json_object_get((json_t *)response->shared_data, "client_id")), json_string_value(json_object_get((json_t *)response->shared_data, "jkt")), get_ip_source(request)) != G_OK) { jkt_continue = 0; } json_decref(j_jkt); o_free(htu); o_free(external_url); } if (jkt_continue) { if (u_map_get(request->map_url, "offset") != NULL) { l_converted = strtol(u_map_get(request->map_url, "offset"), &endptr, 10); if (!(*endptr) && l_converted > 0) { offset = (size_t)l_converted; } } if (u_map_get(request->map_url, "limit") != NULL) { l_converted = strtol(u_map_get(request->map_url, "limit"), &endptr, 10); if (!(*endptr) && l_converted > 0) { limit = (size_t)l_converted; } } if (0 == o_strcmp(u_map_get(request->map_url, "sort"), "authorization_type") || 0 == o_strcmp(u_map_get(request->map_url, "sort"), "client_id") || 0 == o_strcmp(u_map_get(request->map_url, "sort"), "issued_at") || 0 == o_strcmp(u_map_get(request->map_url, "sort"), "last_seen") || 0 == o_strcmp(u_map_get(request->map_url, "sort"), "expires_at") || 0 == o_strcmp(u_map_get(request->map_url, "sort"), "issued_for") || 0 == o_strcmp(u_map_get(request->map_url, "sort"), "user_agent") || 0 == o_strcmp(u_map_get(request->map_url, "sort"), "enabled") || 0 == o_strcmp(u_map_get(request->map_url, "sort"), "rolling_expiration")) { sort = msprintf("gpor_%s%s", u_map_get(request->map_url, "sort"), (u_map_get_case(request->map_url, "desc")!=NULL?" DESC":" ASC")); } j_refresh_list = refresh_token_list_get(config, json_string_value(json_object_get((json_t *)response->shared_data, "username")), u_map_get(request->map_url, "pattern"), offset, limit, sort); if (check_result_value(j_refresh_list, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_refresh_list, "refresh_token")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_refresh_token_list_get - Error refresh_token_list_get"); response->status = 500; } o_free(sort); json_decref(j_refresh_list); } else { response->status = 401; } return U_CALLBACK_CONTINUE; } /** * DELETE /token callback */ static int callback_oidc_disable_refresh_token(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; int res; json_t * j_jkt = NULL; int jkt_continue = 1; char * external_url, * htu; u_map_put(response->map_header, "Cache-Control", "no-store"); u_map_put(response->map_header, "Pragma", "no-cache"); u_map_put(response->map_header, "Referrer-Policy", "no-referrer"); if (json_object_get((json_t *)response->shared_data, "jkt") != NULL) { external_url = config->glewlwyd_config->glewlwyd_callback_get_plugin_external_url(config->glewlwyd_config, config->name); htu = msprintf("%s/token/%s", external_url, u_map_get(request->map_url, "token_hash")); j_jkt = verify_dpop_proof(request, request->http_verb, htu, (time_t)json_integer_value(json_object_get(config->j_params, "oauth-dpop-iat-duration")), json_string_value(json_object_get((json_t *)response->shared_data, "jkt")), (u_map_get_case(request->map_header, HEADER_AUTHORIZATION) + o_strlen(HEADER_PREFIX_BEARER))); if (!check_result_value(j_jkt, G_TOKEN_OK)) { jkt_continue = 0; } else if (check_dpop_jti(config, json_string_value(json_object_get(json_object_get(j_jkt, "claims"), "jti")), json_string_value(json_object_get(json_object_get(j_jkt, "claims"), "htm")), json_string_value(json_object_get(json_object_get(j_jkt, "claims"), "htu")), json_integer_value(json_object_get(json_object_get(j_jkt, "claims"), "iat")), json_string_value(json_object_get((json_t *)response->shared_data, "client_id")), json_string_value(json_object_get((json_t *)response->shared_data, "jkt")), get_ip_source(request)) != G_OK) { jkt_continue = 0; } json_decref(j_jkt); o_free(htu); o_free(external_url); } if (jkt_continue) { if ((res = refresh_token_disable(config, json_string_value(json_object_get((json_t *)response->shared_data, "username")), u_map_get(request->map_url, "token_hash"), get_ip_source(request))) == G_ERROR_NOT_FOUND) { response->status = 404; } else if (res == G_ERROR_PARAM) { response->status = 400; } else if (res != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_disable_refresh_token - Error refresh_token_disable"); response->status = 500; } } else { response->status = 401; } return U_CALLBACK_CONTINUE; } /** * /.well-known/openid-configuration callback */ static int callback_oidc_discovery(const struct _u_request * request, struct _u_response * response, void * user_data) { UNUSED(request); u_map_put(response->map_header, "Cache-Control", "no-store"); u_map_put(response->map_header, "Pragma", "no-cache"); u_map_put(response->map_header, "Referrer-Policy", "no-referrer"); u_map_put(response->map_header, ULFIUS_HTTP_HEADER_CONTENT, ULFIUS_HTTP_ENCODING_JSON); ulfius_set_string_body_response(response, 200, ((struct _oidc_config *)user_data)->discovery_str); return U_CALLBACK_CONTINUE; } /** * /jwks allback */ static int callback_oidc_get_jwks(const struct _u_request * request, struct _u_response * response, void * user_data) { UNUSED(request); struct _oidc_config * config = (struct _oidc_config *)user_data; u_map_put(response->map_header, "Cache-Control", "no-store"); u_map_put(response->map_header, "Pragma", "no-cache"); u_map_put(response->map_header, "Referrer-Policy", "no-referrer"); if (config->jwks_str != NULL) { u_map_put(response->map_header, ULFIUS_HTTP_HEADER_CONTENT, ULFIUS_HTTP_ENCODING_JSON); ulfius_set_string_body_response(response, 200, config->jwks_str); } else { ulfius_set_string_body_response(response, 403, "JWKS unavailable"); } return U_CALLBACK_CONTINUE; } /** * OP Iframe to validate session_state */ static int callback_oidc_check_session_iframe(const struct _u_request * request, struct _u_response * response, void * user_data) { UNUSED(request); struct _oidc_config * config = (struct _oidc_config *)user_data; u_map_put(response->map_header, "Content-Type", "text/html; charset=utf-8"); ulfius_set_string_body_response(response, 200, config->check_session_iframe); return U_CALLBACK_CONTINUE; } static int callback_oidc_end_session_list(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; time_t now; struct tm ts; char expires[129]; time(&now); now += (time_t)json_integer_value(json_object_get(config->j_params, "session-cookie-expiration")); gmtime_r(&now, &ts); strftime(expires, 128, "%a, %d %b %Y %T %Z", &ts); if (o_strlen(u_map_get(request->map_url, "sid")) == OIDC_SID_LENGTH) { if (run_backchannel_logout(config, json_string_value(json_object_get((json_t *)response->shared_data, "username")), u_map_get(request->map_url, "sid")) == G_OK && disable_tokens_from_session(config, json_string_value(json_object_get((json_t *)response->shared_data, "username")), u_map_get(request->map_url, "sid")) == G_OK) { if (ulfius_add_cookie_to_response(response, json_string_value(json_object_get(config->j_params, "session-cookie-name")), "", expires, 0, config->glewlwyd_config->glewlwyd_config->cookie_domain, "/", config->glewlwyd_config->glewlwyd_config->cookie_secure, 0) != U_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_oidc_end_session_list - Error ulfius_add_cookie_to_response"); response->status = 500; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_end_session_list - Error run_backchannel_logout or disable_tokens_from_session"); response->status = 500; } } else { response->status = 400; } return U_CALLBACK_CONTINUE; } static int callback_oidc_get_session_list(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; json_t * j_session = get_session_front_client_list(config, json_string_value(json_object_get((json_t *)response->shared_data, "username")), u_map_get(request->map_url, "sid"), u_map_get(request->map_url, "client_id"), u_map_get(request->map_url, "post_redirect_to")); if (check_result_value(j_session, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_session, "session")); } else if (check_result_value(j_session, G_ERROR_PARAM)) { response->status = 400; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_get_session_list - Error get_session_client_list"); response->status = 500; } json_decref(j_session); return U_CALLBACK_CONTINUE; } /** * Redirects the user to an end session prompt */ static int callback_oidc_end_session(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; struct _u_map map; char * logout_url, * redirect_uri = NULL, * state; json_t * j_metadata, * j_client; const char * post_logout_redirect_uri; u_map_init(&map); if (u_map_get(request->map_url, "id_token_hint") != NULL) { j_metadata = get_token_metadata(config, u_map_get(request->map_url, "id_token_hint"), "id_token", NULL); if (check_result_value(j_metadata, G_OK) && json_object_get(json_object_get(j_metadata, "token"), "active") == json_true()) { u_map_put(&map, "sid", json_string_value(json_object_get(json_object_get(j_metadata, "token"), "sid"))); u_map_put(&map, "plugin", config->name); u_map_put(&map, "client_id", json_string_value(json_object_get(json_object_get(j_metadata, "token"), "client_id"))); j_client = config->glewlwyd_config->glewlwyd_plugin_callback_get_client(config->glewlwyd_config, json_string_value(json_object_get(json_object_get(j_metadata, "token"), "client_id"))); if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true()) { if (o_strlen(post_logout_redirect_uri = u_map_get(request->map_url, "post_logout_redirect_uri"))) { if (json_array_has_string(json_object_get(json_object_get(j_client, "client"), "post_logout_redirect_uris"), u_map_get(request->map_url, "post_logout_redirect_uri"))) { if (u_map_get(request->map_url, "state") != NULL) { if (o_strlen(u_map_get(request->map_url, "state"))) { state = msprintf("state=%s", u_map_get(request->map_url, "state")); } else { state = o_strdup(""); } u_map_put(&map, "post_redirect_to", post_logout_redirect_uri); if (o_strrchr(post_logout_redirect_uri, '?') != NULL || o_strrchr(post_logout_redirect_uri, '#') != NULL) { redirect_uri = msprintf("%s&%s", post_logout_redirect_uri, state); } else { redirect_uri = msprintf("%s?%s", post_logout_redirect_uri, state); } o_free(state); } else { redirect_uri = o_strdup(post_logout_redirect_uri); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_oidc_end_session - Invalid post_logout_redirect_uris"); } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_end_session - Error getting client_id %s", json_string_value(json_object_get(json_object_get(j_metadata, "token"), "client_id"))); } json_decref(j_client); u_map_put(&map, "prompt", "end_session"); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_oidc_end_session - Invalid id_token"); u_map_put(&map, "prompt", "single_logout"); } json_decref(j_metadata); logout_url = config->glewlwyd_config->glewlwyd_callback_get_login_url(config->glewlwyd_config, NULL, NULL, redirect_uri, &map); response->status = 302; ulfius_add_header_to_response(response, "Location", logout_url); u_map_clean(&map); o_free(logout_url); } o_free(redirect_uri); return U_CALLBACK_CONTINUE; } /** * Generates a new device_authorization if the client is allowed */ static int callback_oidc_device_authorization(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; const char * ip_source = get_ip_source(request), * client_id = request->auth_basic_user, * client_secret = request->auth_basic_password, * resource = NULL; char * verification_uri, * verification_uri_complete, * scope_reduced = NULL, * plugin_url = config->glewlwyd_config->glewlwyd_callback_get_plugin_external_url(config->glewlwyd_config, json_string_value(json_object_get(config->j_params, "name"))); json_t * j_client, * j_body, * j_result, * j_assertion = NULL, * j_assertion_client = NULL, * j_authorization_details = NULL; int result = U_CALLBACK_CONTINUE, client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_NONE, res, resource_valid = 1, authorization_details_valid = 1; if (o_strlen(u_map_get(request->map_post_body, "client_assertion")) && 0 == o_strcmp(GLEWLWYD_AUTH_TOKEN_ASSERTION_TYPE, u_map_get(request->map_post_body, "client_assertion_type"))) { if (json_object_get(config->j_params, "request-parameter-allow") == json_true()) { j_assertion = validate_jwt_assertion_request(config, u_map_get(request->map_post_body, "client_assertion"), "device_authorization", ip_source); if (check_result_value(j_assertion, G_ERROR_UNAUTHORIZED) || check_result_value(j_assertion, G_ERROR_PARAM)) { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_oidc_device_authorization - Error validating client_assertion"); result = U_CALLBACK_UNAUTHORIZED; } else if (!check_result_value(j_assertion, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_device_authorization - Error validate_jwt_assertion_request"); result = U_CALLBACK_ERROR; } else { j_assertion_client = json_object_get(j_assertion, "client"); client_auth_method = (int)json_integer_value(json_object_get(j_assertion, "client_auth_method")); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_oidc_device_authorization - unauthorized request parameter"); result = U_CALLBACK_UNAUTHORIZED; } } else { j_assertion = check_client_certificate_valid(config, request); if (check_result_value(j_assertion, G_ERROR_UNAUTHORIZED)) { result = U_CALLBACK_UNAUTHORIZED; } else if (j_assertion != NULL && !check_result_value(j_assertion, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_device_authorization - Error check_client_certificate_valid"); result = U_CALLBACK_ERROR; } else if (check_result_value(j_assertion, G_OK)) { j_assertion_client = json_object_get(j_assertion, "client"); client_auth_method = (int)json_integer_value(json_object_get(j_assertion, "client_auth_method")); } } if (client_id == NULL && u_map_get(request->map_post_body, "client_id") != NULL) { client_id = u_map_get(request->map_post_body, "client_id"); } if (client_secret == NULL && u_map_get(request->map_post_body, "client_secret") != NULL) { client_secret = u_map_get(request->map_post_body, "client_secret"); client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_POST; } else if (client_secret != NULL) { client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_SECRET_BASIC; } if (result == U_CALLBACK_CONTINUE) { if (o_strlen(u_map_get(request->map_post_body, "scope"))) { if (j_assertion_client != NULL) { j_client = json_pack("{sisO}", "result", G_OK, "client", j_assertion_client); } else { j_client = check_client_valid(config, client_id, client_secret, NULL, GLEWLWYD_AUTHORIZATION_TYPE_DEVICE_AUTHORIZATION_FLAG, 0, ip_source); } if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true() && is_client_auth_method_allowed(json_object_get(j_client, "client"), client_auth_method)) { if (json_string_length(json_object_get(config->j_params, "restrict-scope-client-property"))) { j_result = reduce_scope(u_map_get(request->map_post_body, "scope"), json_object_get(json_object_get(j_client, "client"), json_string_value(json_object_get(config->j_params, "restrict-scope-client-property")))); if (check_result_value(j_result, G_OK)) { scope_reduced = o_strdup(json_string_value(json_object_get(j_result, "scope"))); } else if (check_result_value(j_result, G_ERROR_UNAUTHORIZED)) { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_oidc_device_authorization - error client %s is not allowed to claim scopes '%s'", client_id, u_map_get(request->map_post_body, "scope")); y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for client_id %s at IP Address %s", client_id, ip_source); j_body = json_pack("{ss}", "error", "invalid_scope"); ulfius_set_json_body_response(response, 403, j_body); json_decref(j_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_device_authorization - error reduce_scope"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_result); } else { scope_reduced = o_strdup(u_map_get(request->map_post_body, "scope")); } if (scope_reduced != NULL) { client_id = json_string_value(json_object_get(json_object_get(j_client, "client"), "client_id")); if (u_map_has_key(request->map_post_body, "resource") && json_object_get(config->j_params, "resource-allowed") == json_true()) { resource = u_map_get(request->map_post_body, "resource"); } if (o_strlen(resource)) { if ((res = verify_resource(config, resource, json_object_get(j_client, "client"), scope_reduced)) == G_ERROR_PARAM) { j_body = json_pack("{ss}", "error", "invalid_target"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); resource_valid = 0; } else if (res != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_device_authorization - Error verify_resource"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); resource_valid = 0; } } if (o_strlen(u_map_get(request->map_post_body, "authorization_details")) && json_object_get(config->j_params, "oauth-rar-allowed") == json_true() && json_object_get(config->j_params, "rar-allow-auth-unsigned") == json_true()) { if ((j_authorization_details = json_loads(u_map_get(request->map_post_body, "authorization_details"), JSON_DECODE_ANY, NULL)) == NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_oidc_device_authorization oidc - Invalid authorization_details format, origin: %s", ip_source); j_body = json_pack("{ss}", "error", "invalid_request"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); authorization_details_valid = 0; } else if (authorization_details_validate(config, j_authorization_details, client_id, scope_reduced) != G_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_oidc_device_authorization oidc - Invalid authorization_details request, origin: %s", ip_source); j_body = json_pack("{ss}", "error", "invalid_request"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); authorization_details_valid = 0; } } if (resource_valid && authorization_details_valid) { j_result = generate_device_authorization(config, client_id, scope_reduced, resource, j_authorization_details, ip_source); if (check_result_value(j_result, G_OK)) { verification_uri = msprintf("%s/device", plugin_url); verification_uri_complete = msprintf("%s/device?code=%s", plugin_url, json_string_value(json_object_get(json_object_get(j_result, "authorization"), "user_code"))); j_body = json_pack("{sOsOsssssOsO}", "device_code", json_object_get(json_object_get(j_result, "authorization"), "device_code"), "user_code", json_object_get(json_object_get(j_result, "authorization"), "user_code"), "verification_uri", verification_uri, "verification_uri_complete", verification_uri_complete, "expires_in", json_object_get(config->j_params, "device-authorization-expiration"), "interval", json_object_get(config->j_params, "device-authorization-interval")); ulfius_set_json_body_response(response, 200, j_body); json_decref(j_body); o_free(verification_uri); o_free(verification_uri_complete); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_DEVICE_CODE, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_device_authorization oidc - Error generate_device_authorization"); j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 500, j_body); json_decref(j_body); } json_decref(j_result); } json_decref(j_authorization_details); } } else { j_body = json_pack("{ss}", "error", "unauthorized_client"); ulfius_set_json_body_response(response, 403, j_body); json_decref(j_body); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 1, "plugin", config->name, NULL); } json_decref(j_client); } else { j_body = json_pack("{ss}", "error", "invalid_scope"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } } else if (result == U_CALLBACK_UNAUTHORIZED) { j_body = json_pack("{ss}", "error", "unauthorized_client"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } else { j_body = json_pack("{ss}", "error", "server_error"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } o_free(plugin_url); o_free(scope_reduced); json_decref(j_assertion); return result; } /** * Verifies the device code by the user */ static int callback_oidc_device_verification(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; char * redirect_url = NULL, sid[OIDC_SID_LENGTH+1] = {0}; struct _u_map param; json_t * j_result, * j_session; if (get_session_token(config, request, response, sid) == G_OK) { if (!o_strlen(u_map_get(request->map_url, "code"))) { if (u_map_init(¶m) == U_OK) { u_map_put(¶m, "prompt", "device"); response->status = 302; redirect_url = get_login_url(config, request, "device", NULL, NULL, ¶m); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); u_map_clean(¶m); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_device_verification - Error u_map_init"); response->status = 500; } } else if (o_strlen(u_map_get(request->map_url, "code")) != (GLEWLWYD_DEVICE_AUTH_USER_CODE_LENGTH+1)) { if (u_map_init(¶m) == U_OK) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Code invalid at IP Address %s", get_ip_source(request)); u_map_put(¶m, "prompt", "deviceCodeError"); response->status = 302; redirect_url = get_login_url(config, request, "device", NULL, NULL, ¶m); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); u_map_clean(¶m); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_INVALID_DEVICE_CODE, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_device_verification - Error u_map_init"); response->status = 500; } } else { if (u_map_init(¶m) == U_OK) { j_result = validate_device_auth_user_code(config, u_map_get(request->map_url, "code")); if (check_result_value(j_result, G_OK)) { if (u_map_has_key(request->map_url, "g_continue")) { j_session = validate_session_client_scope(config, request, json_string_value(json_object_get(json_object_get(j_result, "device_auth"), "client_id")), json_string_value(json_object_get(json_object_get(j_result, "device_auth"), "scope"))); if (check_result_value(j_session, G_OK)) { if (validate_device_authorization_scope(config, json_integer_value(json_object_get(json_object_get(j_result, "device_auth"), "gpoda_id")), json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username")), json_string_value(json_object_get(json_object_get(j_session, "session"), "scope_filtered")), json_object_get(json_object_get(j_session, "session"), "amr"), sid) == G_OK) { u_map_put(¶m, "prompt", "deviceComplete"); response->status = 302; redirect_url = get_login_url(config, request, "device", NULL, NULL, ¶m); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_device_verification - Error validate_device_authorization_scope"); u_map_put(¶m, "prompt", "deviceServerError"); response->status = 302; redirect_url = get_login_url(config, request, "device", NULL, NULL, ¶m); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } } else if (check_result_value(j_session, G_ERROR_NOT_FOUND) || check_result_value(j_session, G_ERROR_UNAUTHORIZED)) { // Redirect to login page response->status = 302; redirect_url = get_login_url(config, request, "device", json_string_value(json_object_get(json_object_get(j_result, "device_auth"), "client_id")), json_string_value(json_object_get(json_object_get(j_result, "device_auth"), "scope")), NULL); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_device_verification - Error validate_session_client_scope"); u_map_put(¶m, "prompt", "deviceServerError"); response->status = 302; redirect_url = get_login_url(config, request, "device", NULL, NULL, ¶m); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } json_decref(j_session); } else { // Redirect to login page response->status = 302; redirect_url = get_login_url(config, request, "device", json_string_value(json_object_get(json_object_get(j_result, "device_auth"), "client_id")), json_string_value(json_object_get(json_object_get(j_result, "device_auth"), "scope")), NULL); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Code invalid at IP Address %s", get_ip_source(request)); u_map_put(¶m, "prompt", "deviceCodeError"); response->status = 302; redirect_url = get_login_url(config, request, "device", NULL, NULL, ¶m); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_OIDC_INVALID_DEVICE_CODE, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_device_verification - Error validate_device_auth_user_code"); u_map_put(¶m, "prompt", "deviceServerError"); response->status = 302; redirect_url = get_login_url(config, request, "device", NULL, NULL, ¶m); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } json_decref(j_result); u_map_clean(¶m); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_device_verification - Error u_map_init"); response->status = 500; } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_oidc_device_verification - Error get_session_token"); response->status = 500; } return U_CALLBACK_CONTINUE; } static int callback_rar_get_consent(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; json_t * j_consent = authorization_details_get_consent(config, u_map_get(request->map_url, "type"), u_map_get(request->map_url, "client_id"), json_string_value(json_object_get((json_t *)response->shared_data, "username"))), * j_rar_config, * j_rar_output, * j_element = NULL; size_t index = 0; int has_scope = 0; if (check_result_value(j_consent, G_OK)) { j_rar_config = json_deep_copy(json_object_get(json_object_get(config->j_params, "rar-types"), u_map_get(request->map_url, "type"))); json_object_set_new(j_rar_config, "type", json_string(u_map_get(request->map_url, "type"))); json_object_set(j_rar_config, "consent", json_object_get(json_object_get(j_consent, "rar_consent"), "consent")); ulfius_set_json_body_response(response, 200, j_rar_config); json_decref(j_rar_config); } else if (check_result_value(j_consent, G_ERROR_NOT_FOUND)) { if ((j_rar_config = json_object_get(json_object_get(config->j_params, "rar-types"), u_map_get(request->map_url, "type"))) != NULL) { if (json_array_size(json_object_get(j_rar_config, "scopes"))) { json_array_foreach(json_object_get(j_rar_config, "scopes"), index, j_element) { if (json_array_has_string(json_object_get((json_t *)response->shared_data, "scope"), json_string_value(j_element))) { has_scope = 1; } } if (has_scope) { j_rar_output = json_deep_copy(json_object_get(json_object_get(config->j_params, "rar-types"), u_map_get(request->map_url, "type"))); json_object_set_new(j_rar_output, "type", json_string(u_map_get(request->map_url, "type"))); json_object_set(j_rar_output, "consent", json_false()); ulfius_set_json_body_response(response, 200, j_rar_output); json_decref(j_rar_output); if (authorization_details_add_consent(config, u_map_get(request->map_url, "type"), u_map_get(request->map_url, "client_id"), json_string_value(json_object_get((json_t *)response->shared_data, "username")), 0, get_ip_source(request)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_rar_get_consent - Error authorization_details_add_consent (1)"); response->status = 500; } } else { response->status = 404; } } else { j_rar_output = json_deep_copy(json_object_get(json_object_get(config->j_params, "rar-types"), u_map_get(request->map_url, "type"))); json_object_set_new(j_rar_output, "type", json_string(u_map_get(request->map_url, "type"))); json_object_set(j_rar_output, "consent", json_false()); ulfius_set_json_body_response(response, 200, j_rar_output); json_decref(j_rar_output); } } else { response->status = 404; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_rar_get_consent - Error authorization_details_get_consent"); response->status = 500; } json_decref(j_consent); return U_CALLBACK_CONTINUE; } static int callback_rar_set_consent(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; json_t * j_consent = authorization_details_get_consent(config, u_map_get(request->map_url, "type"), u_map_get(request->map_url, "client_id"), json_string_value(json_object_get((json_t *)response->shared_data, "username"))), * j_rar_config, * j_element = NULL; size_t index = 0; int has_scope = 0; if (check_result_value(j_consent, G_OK)) { if (authorization_details_set_consent(config, u_map_get(request->map_url, "type"), u_map_get(request->map_url, "client_id"), json_string_value(json_object_get((json_t *)response->shared_data, "username")), 0==o_strcmp("1", u_map_get(request->map_url, "consent")), get_ip_source(request)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_rar_get_consent - Error authorization_details_set_consent"); response->status = 500; } } else if (check_result_value(j_consent, G_ERROR_NOT_FOUND)) { if ((j_rar_config = json_object_get(json_object_get(config->j_params, "rar-types"), u_map_get(request->map_url, "type"))) != NULL) { if (json_array_size(json_object_get(j_rar_config, "scopes"))) { json_array_foreach(json_object_get(j_rar_config, "scopes"), index, j_element) { if (json_array_has_string(json_object_get((json_t *)response->shared_data, "scope"), json_string_value(j_element))) { has_scope = 1; } } if (has_scope) { if (authorization_details_add_consent(config, u_map_get(request->map_url, "type"), u_map_get(request->map_url, "client_id"), json_string_value(json_object_get((json_t *)response->shared_data, "username")), 0==o_strcmp("1", u_map_get(request->map_url, "consent")), get_ip_source(request)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_rar_get_consent - Error authorization_details_add_consent (1)"); response->status = 500; } } else { response->status = 404; } } else { if (authorization_details_add_consent(config, u_map_get(request->map_url, "type"), u_map_get(request->map_url, "client_id"), json_string_value(json_object_get((json_t *)response->shared_data, "username")), 0==o_strcmp("1", u_map_get(request->map_url, "consent")), get_ip_source(request)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_rar_get_consent - Error authorization_details_add_consent (2)"); response->status = 500; } } } else { response->status = 404; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_rar_get_consent - Error authorization_details_get_consent"); response->status = 500; } json_decref(j_consent); return U_CALLBACK_CONTINUE; } static int callback_rar_delete_consent(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; json_t * j_consent = authorization_details_get_consent(config, u_map_get(request->map_url, "type"), u_map_get(request->map_url, "client_id"), json_string_value(json_object_get((json_t *)response->shared_data, "username"))); if (check_result_value(j_consent, G_OK)) { if (authorization_details_delete_consent(config, u_map_get(request->map_url, "type"), u_map_get(request->map_url, "client_id"), json_string_value(json_object_get((json_t *)response->shared_data, "username")), get_ip_source(request)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_rar_delete_consent - Error authorization_details_delete_consent"); response->status = 500; } } else if (check_result_value(j_consent, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_rar_delete_consent - Error authorization_details_get_consent"); response->status = 500; } json_decref(j_consent); return U_CALLBACK_CONTINUE; } static int callback_pushed_authorization_request(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; const char * ip_source = get_ip_source(request); int result = U_CALLBACK_CONTINUE, client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_NONE; json_t * j_assertion = NULL, * j_assertion_client = NULL; if (o_strlen(u_map_get(request->map_post_body, "client_assertion")) && 0 == o_strcmp(GLEWLWYD_AUTH_TOKEN_ASSERTION_TYPE, u_map_get(request->map_post_body, "client_assertion_type"))) { if (json_object_get(config->j_params, "request-parameter-allow") == json_true()) { j_assertion = validate_jwt_assertion_request(config, u_map_get(request->map_post_body, "client_assertion"), "par", ip_source); if (check_result_value(j_assertion, G_ERROR_UNAUTHORIZED) || check_result_value(j_assertion, G_ERROR_PARAM)) { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_pushed_authorization_request - Error validating client_assertion"); result = U_CALLBACK_UNAUTHORIZED; } else if (!check_result_value(j_assertion, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_pushed_authorization_request - Error validate_jwt_assertion_request"); result = U_CALLBACK_ERROR; } else { j_assertion_client = json_object_get(j_assertion, "client"); client_auth_method = (int)json_integer_value(json_object_get(j_assertion, "client_auth_method")); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_pushed_authorization_request - unauthorized request parameter"); result = U_CALLBACK_UNAUTHORIZED; } } else { j_assertion = check_client_certificate_valid(config, request); if (check_result_value(j_assertion, G_ERROR_UNAUTHORIZED)) { result = U_CALLBACK_UNAUTHORIZED; } else if (j_assertion != NULL && !check_result_value(j_assertion, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_pushed_authorization_request - Error check_client_certificate_valid"); result = U_CALLBACK_ERROR; } else if (check_result_value(j_assertion, G_OK)) { j_assertion_client = json_object_get(j_assertion, "client"); client_auth_method = (int)json_integer_value(json_object_get(j_assertion, "client_auth_method")); } } if (result == U_CALLBACK_CONTINUE) { result = check_pushed_authorization_request(request, response, user_data, j_assertion_client, client_auth_method); } json_decref(j_assertion); u_map_put(response->map_header, "Cache-Control", "no-store"); u_map_put(response->map_header, "Pragma", "no-cache"); u_map_put(response->map_header, "Referrer-Policy", "no-referrer"); return result; } static int callback_ciba_request(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; const char * ip_source = get_ip_source(request); int result = U_CALLBACK_CONTINUE, client_auth_method = GLEWLWYD_CLIENT_AUTH_METHOD_NONE; json_t * j_assertion = NULL, * j_assertion_client = NULL; if (o_strlen(u_map_get(request->map_post_body, "client_assertion")) && 0 == o_strcmp(GLEWLWYD_AUTH_TOKEN_ASSERTION_TYPE, u_map_get(request->map_post_body, "client_assertion_type"))) { if (json_object_get(config->j_params, "request-parameter-allow") == json_true()) { j_assertion = validate_jwt_assertion_request(config, u_map_get(request->map_post_body, "client_assertion"), "ciba", ip_source); if (check_result_value(j_assertion, G_ERROR_UNAUTHORIZED) || check_result_value(j_assertion, G_ERROR_PARAM)) { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_ciba_request - Error validating client_assertion"); result = U_CALLBACK_UNAUTHORIZED; } else if (!check_result_value(j_assertion, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_ciba_request - Error validate_jwt_assertion_request"); result = U_CALLBACK_ERROR; } else { j_assertion_client = json_object_get(j_assertion, "client"); client_auth_method = (int)json_integer_value(json_object_get(j_assertion, "client_auth_method")); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_ciba_request - unauthorized request parameter"); result = U_CALLBACK_UNAUTHORIZED; } } else { j_assertion = check_client_certificate_valid(config, request); if (check_result_value(j_assertion, G_ERROR_UNAUTHORIZED)) { result = U_CALLBACK_UNAUTHORIZED; } else if (j_assertion != NULL && !check_result_value(j_assertion, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_ciba_request - Error check_client_certificate_valid"); result = U_CALLBACK_ERROR; } else if (check_result_value(j_assertion, G_OK)) { j_assertion_client = json_object_get(j_assertion, "client"); client_auth_method = (int)json_integer_value(json_object_get(j_assertion, "client_auth_method")); } } if (result == U_CALLBACK_CONTINUE) { result = process_ciba_request(request, response, user_data, j_assertion_client, client_auth_method); } json_decref(j_assertion); u_map_put(response->map_header, "Cache-Control", "no-store"); u_map_put(response->map_header, "Pragma", "no-cache"); u_map_put(response->map_header, "Referrer-Policy", "no-referrer"); return result; } static int callback_ciba_user_list(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; json_t * j_session = config->glewlwyd_config->glewlwyd_callback_check_session_valid(config->glewlwyd_config, request, NULL), * j_result; if (check_result_value(j_session, G_OK)) { j_result = get_ciba_requests_for_user(config, json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username"))); if (check_result_value(j_result, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_result, "ciba")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_ciba_user_list - Error get_ciba_requests_for_user"); response->status = 500; } json_decref(j_result); } else { response->status = 401; } json_decref(j_session); return U_CALLBACK_CONTINUE; } static int callback_ciba_user_check(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _oidc_config * config = (struct _oidc_config *)user_data; json_t * j_ciba_request = get_ciba_request_from_user_req_id(config, u_map_get(request->map_url, "user_req_id")), * j_session, * j_client; struct _u_map additional_parameters; char * redirect_url, sid[OIDC_SID_LENGTH+1] = {0}; u_map_init(&additional_parameters); if (check_result_value(j_ciba_request, G_OK)) { if (get_session_token(config, request, response, sid) == G_OK) { if (!u_map_has_key(request->map_url, "cancel")) { u_map_put(&additional_parameters, "login_hint", json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "username"))); u_map_put(&additional_parameters, "ciba_binding_message", json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "binding_message"))); u_map_put(&additional_parameters, "ciba_login_hint", "true"); if (u_map_has_key(request->map_url, "g_continue")) { if (0 == json_integer_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "status"))) { j_client = config->glewlwyd_config->glewlwyd_plugin_callback_get_client(config->glewlwyd_config, json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "client_id"))); if (check_result_value(j_client, G_OK)) { j_session = validate_session_client_scope(config, request, json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "client_id")), json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "scope"))); if (check_result_value(j_session, G_OK)) { if (0 == o_strcmp(json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username")), json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "username")))) { if (json_object_get(json_object_get(j_session, "session"), "authorization_required") == json_false()) { if (pthread_mutex_lock(&config->insert_lock)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_ciba_user_check oidc - Error pthread_mutex_lock"); response->status = 302; u_map_put(&additional_parameters, "ciba_message", "server_error"); redirect_url = get_login_url(config, request, "ciba_user_check", json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "client_id")), NULL, &additional_parameters); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } else { if (update_ciba_request(config, json_integer_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "gpob_id")), json_string_value(json_object_get(json_object_get(j_session, "session"), "scope_filtered")), json_object_get(json_object_get(j_session, "session"), "amr"), 1, sid) == G_OK) { if (send_ciba_client_notification(config, json_object_get(j_client, "client"), json_object_get(json_object_get(j_session, "session"), "user"), json_object_get(j_ciba_request, "ciba"), json_string_value(json_object_get(json_object_get(j_session, "session"), "scope_filtered")), 1, sid) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_ciba_user_check - Error send_ciba_client_notification"); } // Redirect to login page with a success message response->status = 302; u_map_put(&additional_parameters, "ciba_message", "complete"); redirect_url = get_login_url(config, request, "ciba_user_check", json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "client_id")), NULL, &additional_parameters); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_ciba_user_check - Error update_ciba_request status 1"); response->status = 302; u_map_put(&additional_parameters, "ciba_message", "server_error"); redirect_url = get_login_url(config, request, "ciba_user_check", json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "client_id")), NULL, &additional_parameters); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } pthread_mutex_unlock(&config->insert_lock); } } else { // Redirect to login page response->status = 302; redirect_url = get_login_url(config, request, "ciba_user_check", json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "client_id")), json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "scope")), &additional_parameters); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } } else { // Redirect to login page response->status = 302; redirect_url = get_login_url(config, request, "ciba_user_check", json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "client_id")), json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "scope")), &additional_parameters); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } } else if (check_result_value(j_session, G_ERROR_NOT_FOUND)) { // Redirect to login page response->status = 302; redirect_url = get_login_url(config, request, "ciba_user_check", json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "client_id")), json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "scope")), &additional_parameters); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } else if (check_result_value(j_session, G_ERROR_UNAUTHORIZED)) { if (pthread_mutex_lock(&config->insert_lock)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_ciba_user_check oidc - Error pthread_mutex_lock"); response->status = 302; u_map_put(&additional_parameters, "ciba_message", "server_error"); redirect_url = get_login_url(config, request, "ciba_user_check", json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "client_id")), NULL, &additional_parameters); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } else { if (update_ciba_request(config, json_integer_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "gpob_id")), json_string_value(json_object_get(j_session, "scope_filtered")), json_object_get(j_session, "amr"), 2, NULL) == G_OK) { // Redirect to login page with a error message response->status = 302; u_map_put(&additional_parameters, "ciba_message", "invalid"); redirect_url = get_login_url(config, request, "ciba_user_check", json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "client_id")), NULL, &additional_parameters); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_ciba_user_check - Error update_ciba_request status 2"); response->status = 302; u_map_put(&additional_parameters, "ciba_message", "server_error"); redirect_url = get_login_url(config, request, "ciba_user_check", json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "client_id")), NULL, &additional_parameters); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } pthread_mutex_unlock(&config->insert_lock); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_ciba_user_check - Error validate_session_client_scope"); response->status = 302; u_map_put(&additional_parameters, "ciba_message", "server_error"); redirect_url = get_login_url(config, request, "ciba_user_check", json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "client_id")), NULL, &additional_parameters); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } json_decref(j_session); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_ciba_user_check - Error glewlwyd_plugin_callback_get_client"); response->status = 302; u_map_put(&additional_parameters, "ciba_message", "server_error"); redirect_url = get_login_url(config, request, "ciba_user_check", json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "client_id")), NULL, &additional_parameters); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } json_decref(j_client); } else { // Redirect to login page with a error message response->status = 302; u_map_put(&additional_parameters, "ciba_message", "invalid"); redirect_url = get_login_url(config, request, "ciba_user_check", json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "client_id")), NULL, &additional_parameters); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } } else { // Redirect to login page response->status = 302; redirect_url = get_login_url(config, request, "ciba_user_check", json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "client_id")), json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "scope")), &additional_parameters); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } } else { if (pthread_mutex_lock(&config->insert_lock)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_ciba_user_check oidc - Error pthread_mutex_lock"); response->status = 302; u_map_put(&additional_parameters, "ciba_message", "server_error"); redirect_url = get_login_url(config, request, "ciba_user_check", json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "client_id")), NULL, &additional_parameters); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } else { if (update_ciba_request(config, json_integer_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "gpob_id")), NULL, NULL, 3, NULL) == G_OK) { response->status = 302; u_map_put(&additional_parameters, "ciba_message", "cancelled"); redirect_url = get_login_url(config, request, "ciba_user_check", json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "client_id")), NULL, &additional_parameters); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_ciba_user_check - Error update_ciba_request status 3"); response->status = 302; u_map_put(&additional_parameters, "ciba_message", "server_error"); redirect_url = get_login_url(config, request, "ciba_user_check", json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "client_id")), NULL, &additional_parameters); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } pthread_mutex_unlock(&config->insert_lock); } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_ciba_user_check - Error get_session_token"); response->status = 302; u_map_put(&additional_parameters, "ciba_message", "server_error"); redirect_url = get_login_url(config, request, "ciba_user_check", json_string_value(json_object_get(json_object_get(j_ciba_request, "ciba"), "client_id")), NULL, &additional_parameters); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } } else if (check_result_value(j_ciba_request, G_ERROR_NOT_FOUND)) { response->status = 302; u_map_put(&additional_parameters, "ciba_message", "not_found"); redirect_url = get_login_url(config, request, "ciba_user_check", NULL, NULL, &additional_parameters); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_ciba_user_check - Error get_ciba_request_from_user_req_id"); response->status = 302; u_map_put(&additional_parameters, "ciba_message", "server_error"); redirect_url = get_login_url(config, request, "ciba_user_check", NULL, NULL, &additional_parameters); ulfius_add_header_to_response(response, "Location", redirect_url); o_free(redirect_url); } json_decref(j_ciba_request); u_map_clean(&additional_parameters); return U_CALLBACK_CONTINUE; } /** * verify the private key and public key are valid to build and verify jwts */ static int jwt_autocheck(struct _oidc_config * config) { time_t now; char * token, jti[OIDC_JTI_LENGTH+1] = {0}; jwt_t * jwt = NULL; int ret; time(&now); token = generate_access_token(config, GLEWLWYD_CHECK_JWT_USERNAME, NULL, NULL, GLEWLWYD_CHECK_JWT_SCOPE, NULL, GLEWLWYD_CHECK_JWT_SCOPE, now, jti, NULL, NULL, NULL, NULL); if (token != NULL) { if ((jwt = r_jwt_quick_parse(token, R_PARSE_NONE, 0)) != NULL && r_jwt_add_sign_jwks(jwt, NULL, config->jwks_public) == RHN_OK) { if (r_jwt_verify_signature(jwt, NULL, 0) == RHN_OK) { ret = RHN_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "jwt_autocheck - oidc - Error verifying signature %s", token); y_log_message(Y_LOG_LEVEL_DEBUG, "pubkey %s", r_jwks_export_to_json_str(config->jwks_public, 1)); ret = G_ERROR_PARAM; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "jwt_autocheck - oidc - Error r_jwt_init"); ret = G_ERROR; } r_jwt_free(jwt); } else { y_log_message(Y_LOG_LEVEL_ERROR, "jwt_autocheck - oidc - Error generate_access_token"); ret = G_ERROR; } o_free(token); return ret; } static int build_sign_keys_from_params(struct _oidc_config * config) { int ret = G_OK; jwk_t * jwk = NULL, * jwk_pub; jwks_t * jwks_pub_export = NULL; jwa_alg alg; size_t i; int type; char * kid; do { if (r_jwks_init(&config->jwks_sign) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - Error r_jwks_init jwks_sign"); ret = G_ERROR; break; } if (r_jwks_init(&config->jwks_public) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - Error r_jwks_init jwks_public"); ret = G_ERROR; break; } if (r_jwks_init(&jwks_pub_export) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - Error r_jwks_init jwks_pub_export"); ret = G_ERROR; break; } config->x5u_flags = R_FLAG_FOLLOW_REDIRECT|(json_object_get(config->j_params, "request-uri-allow-https-non-secure")==json_true()?R_FLAG_IGNORE_SERVER_CERTIFICATE:0); // Set specified alg in config parameters if (0 == o_strcmp("rsa", json_string_value(json_object_get(config->j_params, "jwt-type")))) { if (0 == o_strcmp("256", json_string_value(json_object_get(config->j_params, "jwt-key-size")))) { alg = R_JWA_ALG_RS256; } else if (0 == o_strcmp("256", json_string_value(json_object_get(config->j_params, "jwt-key-size")))) { alg = R_JWA_ALG_RS384; } else { // 512 alg = R_JWA_ALG_RS512; } if (json_string_length(json_object_get(config->j_params, "key")) && json_string_length(json_object_get(config->j_params, "cert"))) { if ((jwk = r_jwk_quick_import(R_IMPORT_PEM, R_X509_TYPE_PRIVKEY, json_string_value(json_object_get(config->j_params, "key")), json_string_length(json_object_get(config->j_params, "key")))) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwk_quick_import key rsa"); ret = G_ERROR_PARAM; } r_jwk_set_property_str(jwk, "alg", r_jwa_alg_to_str(alg)); if (r_jwks_append_jwk(config->jwks_sign, jwk) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwks_append_jwk jwks_sign rsa"); ret = G_ERROR; } r_jwk_free(jwk); if (ret != G_OK) { break; } if ((jwk = r_jwk_quick_import(R_IMPORT_PEM, R_X509_TYPE_UNSPECIFIED, json_string_value(json_object_get(config->j_params, "cert")), json_string_length(json_object_get(config->j_params, "cert")))) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwk_quick_import public rsa"); ret = G_ERROR_PARAM; } r_jwk_set_property_str(jwk, "alg", r_jwa_alg_to_str(alg)); if (r_jwks_append_jwk(config->jwks_public, jwk) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwks_append_jwk jwks_public rsa"); ret = G_ERROR; } if (r_jwks_append_jwk(jwks_pub_export, jwk) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwks_append_jwk jwks_pub_export rsa"); ret = G_ERROR; } r_jwk_free(jwk); if (ret != G_OK) { break; } } } else if (0 == o_strcmp("ecdsa", json_string_value(json_object_get(config->j_params, "jwt-type")))) { if (0 == o_strcmp("256", json_string_value(json_object_get(config->j_params, "jwt-key-size")))) { alg = R_JWA_ALG_ES256; } else if (0 == o_strcmp("256", json_string_value(json_object_get(config->j_params, "jwt-key-size")))) { alg = R_JWA_ALG_ES384; } else { // 512 alg = R_JWA_ALG_ES512; } if (json_string_length(json_object_get(config->j_params, "key")) && json_string_length(json_object_get(config->j_params, "cert"))) { if ((jwk = r_jwk_quick_import(R_IMPORT_PEM, R_X509_TYPE_PRIVKEY, json_string_value(json_object_get(config->j_params, "key")), json_string_length(json_object_get(config->j_params, "key")))) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwk_quick_import key ecdsa"); ret = G_ERROR_PARAM; } r_jwk_set_property_str(jwk, "alg", r_jwa_alg_to_str(alg)); if (r_jwks_append_jwk(config->jwks_sign, jwk) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwks_append_jwk jwks_sign ecdsa"); ret = G_ERROR; } r_jwk_free(jwk); if (ret != G_OK) { break; } if ((jwk = r_jwk_quick_import(R_IMPORT_PEM, R_X509_TYPE_UNSPECIFIED, json_string_value(json_object_get(config->j_params, "cert")), json_string_length(json_object_get(config->j_params, "cert")))) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwk_quick_import public ecdsa"); ret = G_ERROR_PARAM; } r_jwk_set_property_str(jwk, "alg", r_jwa_alg_to_str(alg)); if (r_jwks_append_jwk(config->jwks_public, jwk) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwks_append_jwk jwks_public ecdsa"); ret = G_ERROR; } if (r_jwks_append_jwk(jwks_pub_export, jwk) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwks_append_jwk jwks_pub_export ecdsa"); ret = G_ERROR; } r_jwk_free(jwk); if (ret != G_OK) { break; } } } else if (0 == o_strcmp("rsa-pss", json_string_value(json_object_get(config->j_params, "jwt-type")))) { if (0 == o_strcmp("256", json_string_value(json_object_get(config->j_params, "jwt-key-size")))) { alg = R_JWA_ALG_PS256; } else if (0 == o_strcmp("256", json_string_value(json_object_get(config->j_params, "jwt-key-size")))) { alg = R_JWA_ALG_PS384; } else { // 512 alg = R_JWA_ALG_PS512; } if (json_string_length(json_object_get(config->j_params, "key")) && json_string_length(json_object_get(config->j_params, "cert"))) { if ((jwk = r_jwk_quick_import(R_IMPORT_PEM, R_X509_TYPE_PRIVKEY, json_string_value(json_object_get(config->j_params, "key")), json_string_length(json_object_get(config->j_params, "key")))) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwk_quick_import key rsa-pss"); ret = G_ERROR_PARAM; } r_jwk_set_property_str(jwk, "alg", r_jwa_alg_to_str(alg)); if (r_jwks_append_jwk(config->jwks_sign, jwk) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwks_append_jwk jwks_sign rsa-pss"); ret = G_ERROR; } r_jwk_free(jwk); if (ret != G_OK) { break; } if ((jwk = r_jwk_quick_import(R_IMPORT_PEM, R_X509_TYPE_UNSPECIFIED, json_string_value(json_object_get(config->j_params, "cert")), json_string_length(json_object_get(config->j_params, "cert")))) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwk_quick_import public rsa-pss"); ret = G_ERROR_PARAM; } r_jwk_set_property_str(jwk, "alg", r_jwa_alg_to_str(alg)); if (r_jwks_append_jwk(config->jwks_public, jwk) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwks_append_jwk jwks_public rsa-pss"); ret = G_ERROR; } if (r_jwks_append_jwk(jwks_pub_export, jwk) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwks_append_jwk jwks_pub_export rsa-pss"); ret = G_ERROR; } r_jwk_free(jwk); if (ret != G_OK) { break; } } } else if (0 == o_strcmp("eddsa", json_string_value(json_object_get(config->j_params, "jwt-type")))) { alg = R_JWA_ALG_EDDSA; if (json_string_length(json_object_get(config->j_params, "key")) && json_string_length(json_object_get(config->j_params, "cert"))) { if ((jwk = r_jwk_quick_import(R_IMPORT_PEM, R_X509_TYPE_PRIVKEY, json_string_value(json_object_get(config->j_params, "key")), json_string_length(json_object_get(config->j_params, "key")))) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwk_quick_import key eddsa"); ret = G_ERROR_PARAM; } r_jwk_set_property_str(jwk, "alg", r_jwa_alg_to_str(alg)); if (r_jwks_append_jwk(config->jwks_sign, jwk) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwks_append_jwk jwks_sign eddsa"); ret = G_ERROR; } r_jwk_free(jwk); if (ret != G_OK) { break; } if ((jwk = r_jwk_quick_import(R_IMPORT_PEM, R_X509_TYPE_UNSPECIFIED, json_string_value(json_object_get(config->j_params, "cert")), json_string_length(json_object_get(config->j_params, "cert")))) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwk_quick_import public eddsa"); ret = G_ERROR_PARAM; } r_jwk_set_property_str(jwk, "alg", r_jwa_alg_to_str(alg)); if (r_jwks_append_jwk(config->jwks_public, jwk) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwks_append_jwk jwks_public eddsa"); ret = G_ERROR; } if (r_jwks_append_jwk(jwks_pub_export, jwk) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwks_append_jwk jwks_pub_export eddsa"); ret = G_ERROR; } r_jwk_free(jwk); if (ret != G_OK) { break; } } } else { // SHA if (0 == o_strcmp("256", json_string_value(json_object_get(config->j_params, "jwt-key-size")))) { alg = R_JWA_ALG_HS256; } else if (0 == o_strcmp("256", json_string_value(json_object_get(config->j_params, "jwt-key-size")))) { alg = R_JWA_ALG_HS384; } else { // 512 alg = R_JWA_ALG_HS512; } if (json_string_length(json_object_get(config->j_params, "key"))) { if ((jwk = r_jwk_quick_import(R_IMPORT_SYMKEY, json_string_value(json_object_get(config->j_params, "key")), json_string_length(json_object_get(config->j_params, "key")))) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwk_quick_import sha"); ret = G_ERROR_PARAM; } kid = r_jwk_thumbprint(jwk, R_JWK_THUMB_SHA256, 0); r_jwk_set_property_str(jwk, "alg", r_jwa_alg_to_str(alg)); r_jwk_set_property_str(jwk, "kid", kid); o_free(kid); if (r_jwks_append_jwk(config->jwks_sign, jwk) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwks_append_jwk jwks_sign sha"); ret = G_ERROR; } if (r_jwks_append_jwk(config->jwks_public, jwk) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error r_jwks_append_jwk jwks_public sha"); ret = G_ERROR; } r_jwk_free(jwk); if (ret != G_OK) { break; } } } if (json_string_length(json_object_get(config->j_params, "jwks-private")) || json_string_length(json_object_get(config->j_params, "jwks-uri"))) { r_jwks_empty(config->jwks_sign); if (json_string_length(json_object_get(config->j_params, "jwks-uri"))) { if (r_jwks_import_from_uri(config->jwks_sign, json_string_value(json_object_get(config->j_params, "jwks-uri")), config->x5u_flags) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error importing jwks_sign from uri"); ret = G_ERROR_PARAM; break; } } else { if (r_jwks_import_from_json_str(config->jwks_sign, json_string_value(json_object_get(config->j_params, "jwks-private"))) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error importing jwks_sign from data"); ret = G_ERROR_PARAM; break; } } if (!r_jwks_size(config->jwks_sign)) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error jwks_sign is empty"); ret = G_ERROR_PARAM; break; } for (i=0; ret == G_OK && ijwks_sign); i++) { jwk = r_jwks_get_at(config->jwks_sign, i); type = r_jwk_key_type(jwk, NULL, config->x5u_flags); if (!(type & R_KEY_TYPE_PRIVATE) && !(type & R_KEY_TYPE_SYMMETRIC)) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error jwk at index %zu is not a private or a symmetric key", i); ret = G_ERROR_PARAM; } else if (!o_strlen(r_jwk_get_property_str(jwk, "kid"))) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error jwk at index %zu is missing 'kid' property", i); ret = G_ERROR_PARAM; } else if (r_str_to_jwa_alg(r_jwk_get_property_str(jwk, "alg")) == R_JWA_ALG_UNKNOWN) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error jwk at index %zu invalid 'alg' property: '%s'", i, r_jwk_get_property_str(jwk, "alg")); ret = G_ERROR_PARAM; } else { if (!(type & R_KEY_TYPE_SYMMETRIC)) { jwk_pub = NULL; if (r_jwk_init(&jwk_pub) != RHN_OK || r_jwk_extract_pubkey(jwk, jwk_pub, config->x5u_flags) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error extracting public key at index %zu", i); ret = G_ERROR; } else { r_jwks_append_jwk(config->jwks_public, jwk_pub); r_jwks_append_jwk(jwks_pub_export, jwk_pub); } r_jwk_free(jwk_pub); } else { r_jwks_append_jwk(config->jwks_public, jwk); } } r_jwk_free(jwk); } if (ret != G_OK) { break; } } if (json_string_length(json_object_get(config->j_params, "jwks-public-uri")) || json_string_length(json_object_get(config->j_params, "jwks-public"))) { r_jwks_empty(jwks_pub_export); if (json_string_length(json_object_get(config->j_params, "jwks-public-uri"))) { if (r_jwks_import_from_uri(jwks_pub_export, json_string_value(json_object_get(config->j_params, "jwks-public-uri")), config->x5u_flags) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error importing jwks-public from uri"); ret = G_ERROR; break; } } else { if (r_jwks_import_from_json_str(jwks_pub_export, json_string_value(json_object_get(config->j_params, "jwks-public"))) != RHN_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error importing jwks-public from data"); ret = G_ERROR; break; } } } if (!r_jwks_size(config->jwks_sign)) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error jwks_sign empty"); ret = G_ERROR; break; } if (!r_jwks_size(config->jwks_public)) { y_log_message(Y_LOG_LEVEL_ERROR, "build_sign_keys_from_params - oidc - Error jwks_public empty"); ret = G_ERROR; break; } if (r_jwks_size(jwks_pub_export)) { config->jwks_str = r_jwks_export_to_json_str(jwks_pub_export, 0); } } while (0); r_jwks_free(jwks_pub_export); return ret; } static int remove_subject_identifier(struct _oidc_config * config, const char * username) { json_t * j_query; int res, ret = G_OK; j_query = json_pack("{sss{ssss}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_SUBJECT_IDENTIFIER, "where", "gposi_plugin_name", config->name, "gposi_username", username); res = h_delete(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "remove_subject_identifier - Error executing j_query"); ret = G_ERROR_DB; } return ret; } static int disable_user_data(struct _oidc_config * config, const char * username) { json_t * j_query; int res, ret = G_OK; do { j_query = json_pack("{sss{si}s{sssssi}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CODE, "set", "gpoc_enabled", 0, "where", "gpoc_plugin_name", config->name, "gpoc_username", username, "gpoc_enabled", 1); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "disable_user_data - Error disable codes"); ret = G_ERROR; break; } j_query = json_pack("{sss{si}s{sssssi}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_REFRESH_TOKEN, "set", "gpor_enabled", 0, "where", "gpor_plugin_name", config->name, "gpor_username", username, "gpor_enabled", 1); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "disable_user_data - Error disable refresh tokens"); ret = G_ERROR; break; } j_query = json_pack("{sss{si}s{sssssi}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_ACCESS_TOKEN, "set", "gpoa_enabled", 0, "where", "gpoa_plugin_name", config->name, "gpoa_username", username, "gpoa_enabled", 1); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "disable_user_data - Error disable access tokens"); ret = G_ERROR; break; } j_query = json_pack("{sss{si}s{sssssi}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_ID_TOKEN, "set", "gpoi_enabled", 0, "where", "gpoi_plugin_name", config->name, "gpoi_username", username, "gpoi_enabled", 1); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "disable_user_data - Error disable id tokens"); ret = G_ERROR; break; } j_query = json_pack("{sss{si}s{sssss{ssss}}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_DEVICE_AUTHORIZATION, "set", "gpoda_status", 3, "where", "gpoda_plugin_name", config->name, "gpoda_username", username, "gpoda_status", "operator", "raw", "value", "in (0, 1)"); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "disable_user_data - Error disable device auth tokens"); ret = G_ERROR; break; } j_query = json_pack("{sss{si}s{sssssi}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_RAR, "set", "gporar_enabled", 0, "where", "gporar_plugin_name", config->name, "gporar_username", username, "gporar_enabled", 1); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "disable_user_data - Error disable rar"); ret = G_ERROR; break; } j_query = json_pack("{sss{si}s{sssss{ssss}}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_PAR, "set", "gpop_status", 2, "where", "gpop_plugin_name", config->name, "gpop_username", username, "gpop_status", "operator", "raw", "value", "in (0, 1)"); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "disable_user_data - Error disable par"); ret = G_ERROR; break; } j_query = json_pack("{sss{si}s{sssssi}}", "table", GLEWLWYD_PLUGIN_OIDC_TABLE_CIBA, "set", "gpob_enabled", 0, "where", "gpob_plugin_name", config->name, "gpob_username", username, "gpob_enabled", 1); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "disable_user_data - Error disable ciba"); ret = G_ERROR; break; } } while (0); return ret; } json_t * plugin_module_load(struct config_plugin * config) { UNUSED(config); r_global_init(); return json_pack("{si ss ss ss}", "result", G_OK, "name", "oidc", "display_name", "OpenID Connect plugin", "description", "Plugin for OpenID Connect workflow"); } int plugin_module_unload(struct config_plugin * config) { UNUSED(config); r_global_close(); return G_OK; } json_t * plugin_module_init(struct config_plugin * config, const char * name, json_t * j_parameters, void ** cls) { pthread_mutexattr_t mutexattr; json_t * j_return = NULL, * j_result = NULL, * j_element = NULL; size_t index = 0; struct _oidc_config * p_config = NULL; jwk_t * jwk = NULL, * jwk_pub = NULL; jwks_t * jwks_privkey = NULL, * jwks_pubkey = NULL, * jwks_published = NULL, * jwks_specified = NULL; int res; y_log_message(Y_LOG_LEVEL_INFO, "Init plugin Glewlwyd OpenID Connect '%s'", name); *cls = o_malloc(sizeof(struct _oidc_config)); if (*cls != NULL) { p_config = *cls; do { pthread_mutexattr_init ( &mutexattr ); pthread_mutexattr_settype( &mutexattr, PTHREAD_MUTEX_RECURSIVE ); if (pthread_mutex_init(&((struct _oidc_config *)*cls)->insert_lock, &mutexattr) != 0) { y_log_message(Y_LOG_LEVEL_ERROR, "oidc plugin_module_init - Error initializing insert_lock"); j_return = json_pack("{si}", "result", G_ERROR); break; } pthread_mutexattr_destroy(&mutexattr); // Initialize empty vaiables p_config->name = name; p_config->glewlwyd_config = config; p_config->j_params = json_incref(j_parameters); json_object_set_new(p_config->j_params, "name", json_string(name)); p_config->oidc_resource_config = NULL; p_config->introspect_revoke_resource_config = NULL; p_config->client_register_resource_config = NULL; p_config->discovery_str = NULL; p_config->jwks_str = NULL; p_config->check_session_iframe = NULL; p_config->request_uri_duration = 0; p_config->jwks_sign = NULL; p_config->jwks_public = NULL; p_config->x5u_flags = 0; j_result = check_parameters(((struct _oidc_config *)*cls)->j_params); if (check_result_value(j_result, G_ERROR_PARAM)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", json_object_get(j_result, "error")); break; } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error check_parameters"); j_return = json_pack("{si}", "result", G_ERROR); break; } if ((res = build_sign_keys_from_params(p_config)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error build_sign_keys_from_params"); j_return = json_pack("{si}", "result", res); break; } if ((p_config->oidc_resource_config = o_malloc(sizeof(struct _oidc_resource_config))) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "oidc plugin_module_init - Error initializing oidc_resource_config"); j_return = json_pack("{si}", "result", G_ERROR); break; } p_config->oidc_resource_config->method = G_METHOD_HEADER; p_config->oidc_resource_config->oauth_scope = NULL; p_config->oidc_resource_config->x5u_flags = p_config->x5u_flags; p_config->oidc_resource_config->jwks_public = r_jwks_copy(p_config->jwks_public); p_config->oidc_resource_config->realm = NULL; p_config->oidc_resource_config->accept_access_token = 1; p_config->oidc_resource_config->accept_client_token = 0; p_config->access_token_duration = json_integer_value(json_object_get(p_config->j_params, "access-token-duration")); if (!p_config->access_token_duration) { p_config->access_token_duration = GLEWLWYD_ACCESS_TOKEN_EXP_DEFAULT; } p_config->refresh_token_duration = json_integer_value(json_object_get(p_config->j_params, "refresh-token-duration")); if (!p_config->refresh_token_duration) { p_config->refresh_token_duration = GLEWLWYD_REFRESH_TOKEN_EXP_DEFAULT; } p_config->code_duration = json_integer_value(json_object_get(p_config->j_params, "code-duration")); if (!p_config->code_duration) { p_config->code_duration = GLEWLWYD_CODE_EXP_DEFAULT; } if (json_object_get(p_config->j_params, "refresh-token-rolling") != NULL) { p_config->refresh_token_rolling = json_object_get(p_config->j_params, "refresh-token-rolling")==json_true()?1:0; } else { p_config->refresh_token_rolling = 0; } if (0 == o_strcmp("always", json_string_value(json_object_get(p_config->j_params, "refresh-token-one-use")))) { p_config->refresh_token_one_use = GLEWLWYD_REFRESH_TOKEN_ONE_USE_ALWAYS; } else if (0 == o_strcmp("client-driven", json_string_value(json_object_get(p_config->j_params, "refresh-token-one-use")))) { p_config->refresh_token_one_use = GLEWLWYD_REFRESH_TOKEN_ONE_USE_CLIENT_DRIVEN; } else { p_config->refresh_token_one_use = GLEWLWYD_REFRESH_TOKEN_ONE_USE_NEVER; } if (json_object_get(p_config->j_params, "allow-non-oidc") != NULL) { p_config->allow_non_oidc = json_object_get(p_config->j_params, "allow-non-oidc")==json_true()?1:0; } else { p_config->allow_non_oidc = 0; } p_config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_AUTHORIZATION_CODE] = json_object_get(p_config->j_params, "auth-type-code-enabled")==json_true()?1:0; p_config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_TOKEN] = json_object_get(p_config->j_params, "auth-type-token-enabled")==json_true()?1:0; p_config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_ID_TOKEN] = 1; // Force allow this auth type, otherwise use the other plugin p_config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_NONE] = json_object_get(p_config->j_params, "auth-type-none-enabled")==json_true()?1:0; p_config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_RESOURCE_OWNER_PASSWORD_CREDENTIALS] = json_object_get(p_config->j_params, "auth-type-password-enabled")==json_true()?1:0; p_config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_CLIENT_CREDENTIALS] = json_object_get(p_config->j_params, "auth-type-client-enabled")==json_true()?1:0; p_config->auth_type_enabled[GLEWLWYD_AUTHORIZATION_TYPE_REFRESH_TOKEN] = json_object_get(p_config->j_params, "auth-type-refresh-enabled")==json_true()?1:0; p_config->subject_type = 0==o_strcmp("pairwise", json_string_value(json_object_get(p_config->j_params, "subject-type")))?GLEWLWYD_OIDC_SUBJECT_TYPE_PAIRWISE:GLEWLWYD_OIDC_SUBJECT_TYPE_PUBLIC; p_config->auth_token_max_age = json_integer_value(json_object_get(p_config->j_params, "request-maximum-exp")); if (!p_config->auth_token_max_age) { p_config->auth_token_max_age = GLEWLWYD_AUTH_TOKEN_DEFAULT_MAX_AGE; } if (jwt_autocheck(p_config) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error jwt_autocheck"); j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "Error jwt_autocheck"); break; } // Add endpoints y_log_message(Y_LOG_LEVEL_INFO, "Add endpoints with plugin prefix %s", name); if (config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, "auth/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oidc_authorization, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "auth/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oidc_authorization, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "token/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oidc_token, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "*", name, "userinfo/", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_check_userinfo, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, "userinfo/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oidc_get_userinfo, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "userinfo/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oidc_get_userinfo, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, "token/", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_check_glewlwyd_session_or_token, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, "token/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oidc_refresh_token_list_get, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "DELETE", name, "token/*", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_check_glewlwyd_session_or_token, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "DELETE", name, "token/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oidc_disable_refresh_token, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "DELETE", name, "token/:token_hash", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oidc_disable_refresh_token, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, ".well-known/openid-configuration", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oidc_discovery, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, "jwks", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oidc_get_jwks, (void*)*cls) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error adding endpoints"); j_return = json_pack("{si}", "result", G_ERROR); break; } if (json_object_get(p_config->j_params, "introspection-revocation-allowed") == json_true()) { if ((p_config->introspect_revoke_resource_config = o_malloc(sizeof(struct _oidc_resource_config))) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error allocating resources for introspect_revoke_resource_config"); j_return = json_pack("{si}", "result", G_ERROR); break; } p_config->introspect_revoke_resource_config->method = G_METHOD_HEADER; p_config->introspect_revoke_resource_config->oauth_scope = NULL; json_array_foreach(json_object_get(p_config->j_params, "introspection-revocation-auth-scope"), index, j_element) { if (p_config->introspect_revoke_resource_config->oauth_scope == NULL) { p_config->introspect_revoke_resource_config->oauth_scope = o_strdup(json_string_value(j_element)); } else { p_config->introspect_revoke_resource_config->oauth_scope = mstrcatf(p_config->introspect_revoke_resource_config->oauth_scope, " %s", json_string_value(j_element)); } } p_config->introspect_revoke_resource_config->realm = NULL; p_config->introspect_revoke_resource_config->accept_access_token = 1; p_config->introspect_revoke_resource_config->accept_client_token = 1; p_config->introspect_revoke_resource_config->x5u_flags = p_config->x5u_flags; p_config->introspect_revoke_resource_config->jwks_public = r_jwks_copy(p_config->jwks_public); if ( config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "introspect/", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_check_intropect_revoke, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "introspect/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_introspection, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "revoke/", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_check_intropect_revoke, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "revoke/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_revocation, (void*)*cls) != G_OK ) { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error adding introspect/revoke endpoints"); j_return = json_pack("{si}", "result", G_ERROR); break; } } if (json_object_get(p_config->j_params, "register-client-allowed") == json_true()) { if ((p_config->client_register_resource_config = o_malloc(sizeof(struct _oidc_resource_config))) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error allocating resources for client_register_resource_config"); j_return = json_pack("{si}", "result", G_ERROR); break; } p_config->client_register_resource_config->method = G_METHOD_HEADER; p_config->client_register_resource_config->oauth_scope = NULL; json_array_foreach(json_object_get(p_config->j_params, "register-client-auth-scope"), index, j_element) { if (p_config->client_register_resource_config->oauth_scope == NULL) { p_config->client_register_resource_config->oauth_scope = o_strdup(json_string_value(j_element)); } else { p_config->client_register_resource_config->oauth_scope = mstrcatf(p_config->client_register_resource_config->oauth_scope, " %s", json_string_value(j_element)); } } p_config->client_register_resource_config->realm = NULL; p_config->client_register_resource_config->accept_access_token = 1; p_config->client_register_resource_config->accept_client_token = 1; p_config->client_register_resource_config->x5u_flags = p_config->x5u_flags; p_config->client_register_resource_config->jwks_public = r_jwks_copy(p_config->jwks_public); if ( config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "register/", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_check_registration, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "register/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_client_registration, (void*)*cls) != G_OK ) { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error adding register endpoints"); j_return = json_pack("{si}", "result", G_ERROR); break; } if (json_object_get(p_config->j_params, "register-client-management-allowed") == json_true()) { if ( config->glewlwyd_callback_add_plugin_endpoint(config, "*", name, "register/:client_id", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_check_registration_management, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, "register/:client_id", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_client_registration_management_read, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "PUT", name, "register/:client_id", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_client_registration_management_update, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "DELETE", name, "register/:client_id", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_client_registration_management_delete, (void*)*cls) != G_OK ) { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error adding register endpoints"); j_return = json_pack("{si}", "result", G_ERROR); break; } } } if (json_object_get(p_config->j_params, "session-management-allowed") == json_true()) { if ( config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, "end_session/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oidc_end_session, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, "session/:sid/:client_id", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_check_glewlwyd_session_or_token, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, "session/:sid/:client_id", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oidc_get_session_list, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "DELETE", name, "session/:sid", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_check_glewlwyd_session_or_token, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "DELETE", name, "session/:sid", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oidc_end_session_list, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, "check_session_iframe/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oidc_check_session_iframe, (void*)*cls) != G_OK ) { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error adding session-management endpoints"); j_return = json_pack("{si}", "result", G_ERROR); break; } if (generate_check_session_iframe(p_config) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error generate_check_session_iframe"); j_return = json_pack("{si}", "result", G_ERROR); break; } } if (json_object_get(p_config->j_params, "auth-type-device-enabled") == json_true()) { if ( config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "device_authorization/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oidc_device_authorization, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, "device/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oidc_device_verification, (void*)*cls) != G_OK ) { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error adding device-authorization endpoints"); j_return = json_pack("{si}", "result", G_ERROR); break; } if (json_object_get(p_config->j_params, "device-authorization-expiration") == NULL) { json_object_set_new(p_config->j_params, "device-authorization-expiration", json_integer(GLEWLWYD_DEVICE_AUTH_DEFAUT_EXPIRATION)); } if (json_object_get(p_config->j_params, "device-authorization-interval") == NULL) { json_object_set_new(p_config->j_params, "device-authorization-interval", json_integer(GLEWLWYD_DEVICE_AUTH_DEFAUT_INTERVAL)); } } if (json_object_get(p_config->j_params, "client-cert-use-endpoint-aliases") == json_true()) { if (config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "mtls/token/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oidc_token, (void*)*cls) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error adding mtls token endpoint"); j_return = json_pack("{si}", "result", G_ERROR); break; } if (json_object_get(p_config->j_params, "auth-type-device-enabled") == json_true()) { if (config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "mtls/device_authorization/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_oidc_device_authorization, (void*)*cls) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error adding mtls device-authorization endpoints"); j_return = json_pack("{si}", "result", G_ERROR); break; } } if (json_object_get(p_config->j_params, "introspection-revocation-allowed") == json_true()) { if ( config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "mtls/introspect/", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_check_intropect_revoke, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "mtls/introspect/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_introspection, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "mtls/revoke/", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_check_intropect_revoke, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "mtls/revoke/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_revocation, (void*)*cls) != G_OK ) { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error adding mtls introspect/revoke endpoints"); j_return = json_pack("{si}", "result", G_ERROR); break; } } if (json_object_get(p_config->j_params, "oauth-par-allowed") == json_true()) { if (config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "mtls/par/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_pushed_authorization_request, (void*)*cls) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error adding mtls device-authorization endpoints"); j_return = json_pack("{si}", "result", G_ERROR); break; } } if (json_object_get(p_config->j_params, "oauth-ciba-allowed") == json_true()) { if (config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "mtls/ciba/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_ciba_request, (void*)*cls) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error adding mtls device-authorization endpoints"); j_return = json_pack("{si}", "result", G_ERROR); break; } } } if (json_object_get(p_config->j_params, "oauth-rar-allowed") == json_true()) { if ( config->glewlwyd_callback_add_plugin_endpoint(config, "*", name, "rar/*", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_check_glewlwyd_session_or_token, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, "rar/:client_id/:type", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_rar_get_consent, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "PUT", name, "rar/:client_id/:type/:consent", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_rar_set_consent, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "DELETE", name, "rar/:client_id/:type", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_rar_delete_consent, (void*)*cls) != G_OK ) { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error adding rar endpoints"); j_return = json_pack("{si}", "result", G_ERROR); break; } } if (json_object_get(p_config->j_params, "oauth-par-allowed") == json_true()) { if (config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "par/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_pushed_authorization_request, (void*)*cls) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error adding par endpoints"); j_return = json_pack("{si}", "result", G_ERROR); break; } p_config->request_uri_duration = json_integer_value(json_object_get(p_config->j_params, "oauth-par-duration")); if (!p_config->request_uri_duration) { p_config->request_uri_duration = GLEWLWYD_REQUEST_URI_EXP_DEFAULT; } } if (json_object_get(p_config->j_params, "oauth-ciba-allowed") == json_true()) { if (config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "ciba/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_ciba_request, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, "ciba_user_list/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_ciba_user_list, (void*)*cls) != G_OK || config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, "ciba_user_check/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_ciba_user_check, (void*)*cls) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error adding ciba endpoint"); j_return = json_pack("{si}", "result", G_ERROR); break; } } if (json_object_get(p_config->j_params, "oauth-fapi-check-all") == json_true()) { json_object_set(p_config->j_params, "oauth-fapi-allow-jarm", json_true()); json_object_set(p_config->j_params, "oauth-fapi-add-s_hash", json_true()); json_object_set(p_config->j_params, "oauth-fapi-verify-nbf", json_true()); json_object_set(p_config->j_params, "oauth-fapi-allow-restrict-alg", json_true()); json_object_del(p_config->j_params, "oauth-fapi-restrict-alg"); json_object_set_new(p_config->j_params, "oauth-fapi-restrict-alg", json_pack("[sssssssssssssss]", "RSA-OAEP", "RSA-OAEP-256", "A128KW", "A192KW", "A256KW", "ECDH-ES", "ECDH-ES+A128KW", "ECDH-ES+A192KW", "ECDH-ES+A256KW", "A128GCMKW", "A192GCMKW", "A256GCMKW", "PBES2-HS256+A128KW", "PBES2-HS384+A192KW", "PBES2-HS512+A256KW")); json_object_set(p_config->j_params, "oauth-fapi-allow-multiple-kid", json_true()); json_object_set(p_config->j_params, "oauth-fapi-ciba-confidential-client", json_true()); json_object_set(p_config->j_params, "oauth-fapi-ciba-push-forbidden", json_true()); } // .well-known/openid-configuration content generation if (generate_discovery_content(p_config) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error generate_discovery_content"); j_return = json_pack("{si}", "result", G_ERROR); break; } config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_OIDC_CODE, "Total number of code provided"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_OIDC_DEVICE_CODE, "Total number of device code provided"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_OIDC_ID_TOKEN, "Total number of id_token provided"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_OIDC_REFRESH_TOKEN, "Total number of refresh tokens provided"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, "Total number of access tokens provided"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_OIDC_CLIENT_ACCESS_TOKEN, "Total number of client tokens provided"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, "Total number of unauthorized client attempt"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_OIDC_INVALID_CODE, "Total number of invalid code"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_OIDC_INVALID_DEVICE_CODE, "Total number of invalid device code"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_OIDC_INVALID_REFRESH_TOKEN, "Total number of invalid refresh token"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_OIDC_INVALID_ACCESS_TOKEN, "Total number of invalid access token"); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_CODE, 0, "plugin", name, NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_ID_TOKEN, 0, "plugin", name, NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_REFRESH_TOKEN, 0, "plugin", name, NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 0, "plugin", name, NULL); if (json_object_get(p_config->j_params, "auth-type-code-enabled") == json_true()) { config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_ID_TOKEN, 0, "plugin", name, "response_type", "code", NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_REFRESH_TOKEN, 0, "plugin", name, "response_type", "code", NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 0, "plugin", name, "response_type", "code", NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_INVALID_CODE, 0, "plugin", name, NULL); } if (json_object_get(p_config->j_params, "auth-type-password-enabled") == json_true()) { config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_ID_TOKEN, 0, "plugin", name, "response_type", "password", NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_REFRESH_TOKEN, 0, "plugin", name, "response_type", "password", NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 0, "plugin", name, "response_type", "password", NULL); } if (json_object_get(p_config->j_params, "auth-type-client-enabled") == json_true()) { config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_CLIENT_ACCESS_TOKEN, 0, "plugin", name, NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_UNAUTHORIZED_CLIENT, 0, "plugin", name, NULL); } if (json_object_get(p_config->j_params, "auth-type-implicit-enabled") == json_true()) { config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 0, "plugin", name, "response_type", "token", NULL); } if (json_object_get(p_config->j_params, "auth-type-device-enabled") == json_true()) { config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_DEVICE_CODE, 0, "plugin", name, NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_INVALID_DEVICE_CODE, 0, "plugin", name, NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_ID_TOKEN, 0, "plugin", name, "response_type", "device_code", NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_REFRESH_TOKEN, 0, "plugin", name, "response_type", "device_code", NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 0, "plugin", name, "response_type", "device_code", NULL); } if (json_object_get(p_config->j_params, "auth-type-refresh-enabled") == json_true()) { config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 0, "plugin", name, "response_type", "refresh_token", NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_INVALID_REFRESH_TOKEN, 0, "plugin", name, NULL); } if (json_object_get(p_config->j_params, "introspection-revocation-allowed") == json_true()) { config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_INVALID_ACCESS_TOKEN, 0, "plugin", name, NULL); } if (json_object_get(p_config->j_params, "oauth-ciba-allowed") == json_true()) { config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_ID_TOKEN, 0, "plugin", name, "response_type", "ciba", NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_REFRESH_TOKEN, 0, "plugin", name, "response_type", "ciba", NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_OIDC_USER_ACCESS_TOKEN, 0, "plugin", name, "response_type", "ciba", NULL); } } while (0); json_decref(j_result); r_jwk_free(jwk_pub); r_jwk_free(jwk); r_jwks_free(jwks_privkey); r_jwks_free(jwks_pubkey); r_jwks_free(jwks_published); r_jwks_free(jwks_specified); if (j_return == NULL) { j_return = json_pack("{si}", "result", G_OK); } else { if (p_config != NULL) { if (p_config->introspect_revoke_resource_config != NULL) { o_free(p_config->introspect_revoke_resource_config->oauth_scope); o_free(p_config->introspect_revoke_resource_config->realm); r_jwks_free(p_config->introspect_revoke_resource_config->jwks_public); o_free(p_config->introspect_revoke_resource_config); } if (p_config->client_register_resource_config != NULL) { o_free(p_config->client_register_resource_config->oauth_scope); o_free(p_config->client_register_resource_config->realm); r_jwks_free(p_config->client_register_resource_config->jwks_public); o_free(p_config->client_register_resource_config); } if (p_config->oidc_resource_config != NULL) { o_free(p_config->oidc_resource_config->oauth_scope); o_free(p_config->oidc_resource_config->realm); r_jwks_free(p_config->oidc_resource_config->jwks_public); o_free(p_config->oidc_resource_config); } r_jwks_free(p_config->jwks_sign); r_jwks_free(p_config->jwks_public); json_decref(p_config->j_params); pthread_mutex_destroy(&p_config->insert_lock); o_free(p_config->discovery_str); o_free(p_config->jwks_str); o_free(p_config->check_session_iframe); o_free(p_config); } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "protocol_init - oidc - Error allocating resources for cls"); o_free(*cls); *cls = NULL; j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } return j_return; } int plugin_module_close(struct config_plugin * config, const char * name, void * cls) { if (cls != NULL) { y_log_message(Y_LOG_LEVEL_INFO, "Close plugin Glewlwyd OpenID Connect '%s'", name); config->glewlwyd_callback_remove_plugin_endpoint(config, "GET", name, "auth/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "auth/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "token/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "*", name, "userinfo/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "GET", name, "userinfo/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "userinfo/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "GET", name, "token/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "DELETE", name, "token/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "DELETE", name, "token/:token_hash"); config->glewlwyd_callback_remove_plugin_endpoint(config, "DELETE", name, "token/*"); config->glewlwyd_callback_remove_plugin_endpoint(config, "GET", name, ".well-known/openid-configuration"); config->glewlwyd_callback_remove_plugin_endpoint(config, "GET", name, "jwks"); if (json_object_get(((struct _oidc_config *)cls)->j_params, "session-management-allowed") == json_true()) { config->glewlwyd_callback_remove_plugin_endpoint(config, "GET", name, "end_session/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "GET", name, "session/:sid/:client_id"); config->glewlwyd_callback_remove_plugin_endpoint(config, "DELETE", name, "session/:sid/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "GET", name, "check_session_iframe/"); } if (((struct _oidc_config *)cls)->introspect_revoke_resource_config != NULL) { config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "introspect/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "revoke/"); o_free(((struct _oidc_config *)cls)->introspect_revoke_resource_config->oauth_scope); o_free(((struct _oidc_config *)cls)->introspect_revoke_resource_config->realm); r_jwks_free(((struct _oidc_config *)cls)->introspect_revoke_resource_config->jwks_public); o_free(((struct _oidc_config *)cls)->introspect_revoke_resource_config); } if (((struct _oidc_config *)cls)->client_register_resource_config != NULL) { config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "register/"); o_free(((struct _oidc_config *)cls)->client_register_resource_config->oauth_scope); o_free(((struct _oidc_config *)cls)->client_register_resource_config->realm); r_jwks_free(((struct _oidc_config *)cls)->client_register_resource_config->jwks_public); o_free(((struct _oidc_config *)cls)->client_register_resource_config); if (json_object_get(((struct _oidc_config *)cls)->j_params, "register-client-management-allowed") == json_true()) { config->glewlwyd_callback_remove_plugin_endpoint(config, "*", name, "register/:client_id"); config->glewlwyd_callback_remove_plugin_endpoint(config, "GET", name, "register/:client_id"); config->glewlwyd_callback_remove_plugin_endpoint(config, "PUT", name, "register/:client_id"); config->glewlwyd_callback_remove_plugin_endpoint(config, "DELETE", name, "register/:client_id"); } } if (((struct _oidc_config *)cls)->oidc_resource_config != NULL) { o_free(((struct _oidc_config *)cls)->oidc_resource_config->oauth_scope); o_free(((struct _oidc_config *)cls)->oidc_resource_config->realm); r_jwks_free(((struct _oidc_config *)cls)->oidc_resource_config->jwks_public); o_free(((struct _oidc_config *)cls)->oidc_resource_config); } if (json_object_get(((struct _oidc_config *)cls)->j_params, "auth-type-device-enabled") == json_true()) { config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "device_authorization/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "GET", name, "device/"); } if (json_object_get(((struct _oidc_config *)cls)->j_params, "client-cert-use-endpoint-aliases") == json_true()) { config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "mtls/token/"); if (json_object_get(((struct _oidc_config *)cls)->j_params, "introspection-revocation-allowed") == json_true()) { config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "mtls/introspect/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "mtls/revoke/"); } if (json_object_get(((struct _oidc_config *)cls)->j_params, "auth-type-device-enabled") == json_true()) { config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "mtls/device_authorization/"); } } if (json_object_get(((struct _oidc_config *)cls)->j_params, "oauth-rar-allowed") == json_true()) { config->glewlwyd_callback_remove_plugin_endpoint(config, "*", name, "rar/*"); config->glewlwyd_callback_remove_plugin_endpoint(config, "GET", name, "rar/:client_id/:type"); config->glewlwyd_callback_remove_plugin_endpoint(config, "PUT", name, "rar/:client_id/:type/:consent"); config->glewlwyd_callback_remove_plugin_endpoint(config, "DELETE", name, "rar/:client_id/:type"); } if (json_object_get(((struct _oidc_config *)cls)->j_params, "oauth-par-allowed") == json_true()) { config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "par/"); } if (json_object_get(((struct _oidc_config *)cls)->j_params, "oauth-ciba-allowed") == json_true()) { config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "ciba/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "GET", name, "ciba_user_list/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "GET", name, "ciba_user_check/"); } r_jwks_free(((struct _oidc_config *)cls)->jwks_sign); r_jwks_free(((struct _oidc_config *)cls)->jwks_public); json_decref(((struct _oidc_config *)cls)->j_params); pthread_mutex_destroy(&((struct _oidc_config *)cls)->insert_lock); o_free(((struct _oidc_config *)cls)->discovery_str); o_free(((struct _oidc_config *)cls)->jwks_str); o_free(((struct _oidc_config *)cls)->check_session_iframe); o_free(cls); } return G_OK; } int plugin_user_revoke(struct config_plugin * config, const char * username, void * cls) { UNUSED(config); // Disable all data for user 'username', then remove entry in subject identifier table if (disable_user_data((struct _oidc_config *)cls, username) == G_OK) { if (remove_subject_identifier((struct _oidc_config *)cls, username) == G_OK) { return G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_user_revoke - oidc - Error remove_subject_identifier"); return G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_user_revoke - oidc - Error disable_user_data"); return G_ERROR; } } glewlwyd-2.6.1/src/plugin/protocol_oidc.mariadb.sql000066400000000000000000000270361415646314000224350ustar00rootroot00000000000000DROP TABLE IF EXISTS gpo_ciba_scope; DROP TABLE IF EXISTS gpo_ciba_scheme; DROP TABLE IF EXISTS gpo_ciba; DROP TABLE IF EXISTS gpo_par_scope; DROP TABLE IF EXISTS gpo_par; DROP TABLE IF EXISTS gpo_rar; DROP TABLE IF EXISTS gpo_dpop; DROP TABLE IF EXISTS gpo_client_registration; DROP TABLE IF EXISTS gpo_subject_identifier; DROP TABLE IF EXISTS gpo_id_token; DROP TABLE IF EXISTS gpo_access_token_scope; DROP TABLE IF EXISTS gpo_access_token; DROP TABLE IF EXISTS gpo_refresh_token_scope; DROP TABLE IF EXISTS gpo_refresh_token; DROP TABLE IF EXISTS gpo_code_scheme; DROP TABLE IF EXISTS gpo_code_scope; DROP TABLE IF EXISTS gpo_code; DROP TABLE IF EXISTS gpo_client_token_request; DROP TABLE IF EXISTS gpo_device_scheme; DROP TABLE IF EXISTS gpo_device_authorization_scope; DROP TABLE IF EXISTS gpo_device_authorization; CREATE TABLE gpo_code ( gpoc_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoc_plugin_name VARCHAR(256) NOT NULL, gpoc_authorization_type INT(2) NOT NULL, gpoc_username VARCHAR(256) NOT NULL, gpoc_client_id VARCHAR(256) NOT NULL, gpoc_redirect_uri VARCHAR(512) NOT NULL, gpoc_code_hash VARCHAR(512) NOT NULL, gpoc_nonce VARCHAR(512), gpoc_resource VARCHAR(512), gpoc_claims_request BLOB DEFAULT NULL, gpoc_authorization_details BLOB DEFAULT NULL, gpoc_s_hash VARCHAR(512), gpoc_sid VARCHAR(128), gpoc_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpoc_issued_for VARCHAR(256), -- IP address or hostname gpoc_user_agent VARCHAR(256), gpoc_code_challenge VARCHAR(128), gpoc_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gpoc_code_hash ON gpo_code(gpoc_code_hash); CREATE INDEX i_gpoc_code_challenge ON gpo_code(gpoc_code_challenge); CREATE TABLE gpo_code_scope ( gpocs_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoc_id INT(11), gpocs_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE ); CREATE TABLE gpo_code_scheme ( gpoch_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoc_id INT(11), gpoch_scheme_module VARCHAR(128) NOT NULL, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE ); CREATE TABLE gpo_refresh_token ( gpor_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpor_plugin_name VARCHAR(256) NOT NULL, gpor_authorization_type INT(2) NOT NULL, gpoc_id INT(11) DEFAULT NULL, gpor_username VARCHAR(256) NOT NULL, gpor_client_id VARCHAR(256), gpor_resource VARCHAR(512), gpor_claims_request BLOB DEFAULT NULL, gpor_authorization_details BLOB DEFAULT NULL, gpor_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpor_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpor_last_seen TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpor_duration INT(11), gpor_rolling_expiration TINYINT(1) DEFAULT 0, gpor_issued_for VARCHAR(256), -- IP address or hostname gpor_user_agent VARCHAR(256), gpor_token_hash VARCHAR(512) NOT NULL, gpor_jti VARCHAR(128), gpor_dpop_jkt VARCHAR(512), gpor_enabled TINYINT(1) DEFAULT 1, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE ); CREATE INDEX i_gpor_token_hash ON gpo_refresh_token(gpor_token_hash); CREATE INDEX i_gpor_jti ON gpo_refresh_token(gpor_jti); CREATE TABLE gpo_refresh_token_scope ( gpors_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpor_id INT(11), gpors_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpor_id) REFERENCES gpo_refresh_token(gpor_id) ON DELETE CASCADE ); -- Access token table, to store meta information on access token sent CREATE TABLE gpo_access_token ( gpoa_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoa_plugin_name VARCHAR(256) NOT NULL, gpoa_authorization_type INT(2) NOT NULL, gpor_id INT(11) DEFAULT NULL, gpoa_username VARCHAR(256), gpoa_client_id VARCHAR(256), gpoa_resource VARCHAR(512), gpoa_issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoa_issued_for VARCHAR(256), -- IP address or hostname gpoa_user_agent VARCHAR(256), gpoa_token_hash VARCHAR(512) NOT NULL, gpoa_jti VARCHAR(128), gpoa_authorization_details BLOB DEFAULT NULL, gpoa_enabled TINYINT(1) DEFAULT 1, FOREIGN KEY(gpor_id) REFERENCES gpo_refresh_token(gpor_id) ON DELETE CASCADE ); CREATE INDEX i_gpoa_token_hash ON gpo_access_token(gpoa_token_hash); CREATE INDEX i_gpoa_jti ON gpo_access_token(gpoa_jti); CREATE TABLE gpo_access_token_scope ( gpoas_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoa_id INT(11), gpoas_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpoa_id) REFERENCES gpo_access_token(gpoa_id) ON DELETE CASCADE ); -- Id token table, to store meta information on id token sent CREATE TABLE gpo_id_token ( gpoi_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoc_id INT(11), gpor_id INT(11), gpoi_plugin_name VARCHAR(256) NOT NULL, gpoi_authorization_type INT(2) NOT NULL, gpoi_username VARCHAR(256), gpoi_client_id VARCHAR(256), gpoi_issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoi_issued_for VARCHAR(256), -- IP address or hostname gpoi_user_agent VARCHAR(256), gpoi_hash VARCHAR(512), gpoi_sid VARCHAR(128), gpoi_enabled TINYINT(1) DEFAULT 1, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE, FOREIGN KEY(gpor_id) REFERENCES gpo_refresh_token(gpor_id) ON DELETE CASCADE ); CREATE INDEX i_gpoi_hash ON gpo_id_token(gpoi_hash); -- subject identifier table to store subs and their relations to usernames, client_id and sector_identifier CREATE TABLE gpo_subject_identifier ( gposi_id INT(11) PRIMARY KEY AUTO_INCREMENT, gposi_plugin_name VARCHAR(256) NOT NULL, gposi_username VARCHAR(256) NOT NULL, gposi_client_id VARCHAR(256), gposi_sector_identifier_uri VARCHAR(256), gposi_sub VARCHAR(256) NOT NULL ); CREATE INDEX i_gposi_sub ON gpo_subject_identifier(gposi_sub); -- store meta information on client registration CREATE TABLE gpo_client_registration ( gpocr_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpocr_plugin_name VARCHAR(256) NOT NULL, gpocr_cient_id VARCHAR(256) NOT NULL, gpocr_management_at_hash VARCHAR(512), gpocr_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoa_id INT(11), gpocr_issued_for VARCHAR(256), -- IP address or hostname gpocr_user_agent VARCHAR(256), FOREIGN KEY(gpoa_id) REFERENCES gpo_access_token(gpoa_id) ON DELETE CASCADE ); CREATE INDEX i_gpocr_management_at_hash ON gpo_client_registration(gpocr_management_at_hash); -- store meta information about client request on token endpoint CREATE TABLE gpo_client_token_request ( gpoctr_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoctr_plugin_name VARCHAR(256) NOT NULL, gpoctr_cient_id VARCHAR(256) NOT NULL, gpoctr_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoctr_issued_for VARCHAR(256), -- IP address or hostname gpoctr_jti_hash VARCHAR(512) ); -- store device authorization requests CREATE TABLE gpo_device_authorization ( gpoda_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoda_plugin_name VARCHAR(256) NOT NULL, gpoda_client_id VARCHAR(256) NOT NULL, gpoda_resource VARCHAR(512), gpoda_username VARCHAR(256), gpoda_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoda_expires_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoda_issued_for VARCHAR(256), -- IP address or hostname of the deice client gpoda_device_code_hash VARCHAR(512) NOT NULL, gpoda_user_code_hash VARCHAR(512) NOT NULL, gpoda_sid VARCHAR(128), gpoda_status TINYINT(1) DEFAULT 0, -- 0: created, 1: user verified, 2 device completed, 3 disabled gpoda_authorization_details BLOB DEFAULT NULL, gpoda_last_check TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX i_gpoda_device_code_hash ON gpo_device_authorization(gpoda_device_code_hash); CREATE INDEX i_gpoda_user_code_hash ON gpo_device_authorization(gpoda_user_code_hash); CREATE TABLE gpo_device_authorization_scope ( gpodas_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoda_id INT(11), gpodas_scope VARCHAR(128) NOT NULL, gpodas_allowed TINYINT(1) DEFAULT 0, FOREIGN KEY(gpoda_id) REFERENCES gpo_device_authorization(gpoda_id) ON DELETE CASCADE ); CREATE TABLE gpo_device_scheme ( gpodh_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpoda_id INT(11), gpodh_scheme_module VARCHAR(128) NOT NULL, FOREIGN KEY(gpoda_id) REFERENCES gpo_device_authorization(gpoda_id) ON DELETE CASCADE ); CREATE TABLE gpo_dpop ( gpod_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpod_plugin_name VARCHAR(256) NOT NULL, gpod_client_id VARCHAR(256) NOT NULL, gpod_jti_hash VARCHAR(512) NOT NULL, gpod_jkt VARCHAR(512) NOT NULL, gpod_htm VARCHAR(128) NOT NULL, gpod_htu VARCHAR(512) NOT NULL, gpod_iat TIMESTAMP NOT NULL, gpod_last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX i_gpod_jti_hash ON gpo_dpop(gpod_jti_hash); CREATE TABLE gpo_rar ( gporar_id INT(11) PRIMARY KEY AUTO_INCREMENT, gporar_plugin_name VARCHAR(256) NOT NULL, gporar_client_id VARCHAR(256) NOT NULL, gporar_type VARCHAR(256) NOT NULL, gporar_username VARCHAR(256), gporar_consent TINYINT(1) DEFAULT 0, gporar_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gporar_client_id ON gpo_rar(gporar_client_id); CREATE INDEX i_gporar_type ON gpo_rar(gporar_type); CREATE INDEX i_gporar_username ON gpo_rar(gporar_username); CREATE TABLE gpo_par ( gpop_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpop_plugin_name VARCHAR(256) NOT NULL, gpop_response_type VARCHAR(128) NOT NULL, gpop_state BLOB, gpop_username VARCHAR(256), gpop_client_id VARCHAR(256) NOT NULL, gpop_redirect_uri VARCHAR(512) NOT NULL, gpop_request_uri_hash VARCHAR(512) NOT NULL, gpop_nonce VARCHAR(512), gpop_code_challenge VARCHAR(128), gpop_resource VARCHAR(512), gpop_claims_request BLOB DEFAULT NULL, gpop_authorization_details BLOB DEFAULT NULL, gpop_additional_parameters BLOB DEFAULT NULL, gpop_status TINYINT(1) DEFAULT 0, -- 0 created, 1 validated, 2 completed gpop_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpop_issued_for VARCHAR(256), -- IP address or hostname gpop_user_agent VARCHAR(256) ); CREATE INDEX i_gpop_client_id ON gpo_par(gpop_client_id); CREATE INDEX i_gpop_request_uri_hash ON gpo_par(gpop_request_uri_hash); CREATE INDEX i_gpop_code_challenge ON gpo_par(gpop_code_challenge); CREATE TABLE gpo_par_scope ( gpops_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpop_id INT(11), gpops_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpop_id) REFERENCES gpo_par(gpop_id) ON DELETE CASCADE ); CREATE TABLE gpo_ciba ( gpob_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpob_plugin_name VARCHAR(256) NOT NULL, gpob_client_id VARCHAR(256) NOT NULL, gpob_x5t_s256 VARCHAR(64), gpob_username VARCHAR(256) NOT NULL, gpob_client_notification_token VARCHAR(1024), gpob_jti_hash VARCHAR(512), gpob_auth_req_id VARCHAR(128), gpob_user_req_id VARCHAR(128), gpob_binding_message VARCHAR(256), gpob_sid VARCHAR(128), gpob_status TINYINT(1) DEFAULT 0, -- 0: created, 1: accepted, 2: error, 3: closed gpob_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpob_issued_for VARCHAR(256), -- IP address or hostname gpob_user_agent VARCHAR(256), gpob_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gpob_client_id ON gpo_ciba(gpob_client_id); CREATE INDEX i_gpob_jti_hash ON gpo_ciba(gpob_jti_hash); CREATE INDEX i_gpob_client_notification_token ON gpo_ciba(gpob_client_notification_token); CREATE INDEX i_gpob_auth_req_id ON gpo_ciba(gpob_auth_req_id); CREATE INDEX i_gpob_user_req_id ON gpo_ciba(gpob_user_req_id); CREATE TABLE gpo_ciba_scope ( gpocs_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpob_id INT(11), gpops_scope VARCHAR(128) NOT NULL, gpobs_granted TINYINT(1) DEFAULT 0, FOREIGN KEY(gpob_id) REFERENCES gpo_ciba(gpob_id) ON DELETE CASCADE ); CREATE TABLE gpo_ciba_scheme ( gpobh_id INT(11) PRIMARY KEY AUTO_INCREMENT, gpob_id INT(11), gpobh_scheme_module VARCHAR(128) NOT NULL, FOREIGN KEY(gpob_id) REFERENCES gpo_ciba(gpob_id) ON DELETE CASCADE ); glewlwyd-2.6.1/src/plugin/protocol_oidc.postgre.sql000066400000000000000000000261261415646314000225200ustar00rootroot00000000000000DROP TABLE IF EXISTS gpo_ciba_scope; DROP TABLE IF EXISTS gpo_ciba_scheme; DROP TABLE IF EXISTS gpo_ciba; DROP TABLE IF EXISTS gpo_par_scope; DROP TABLE IF EXISTS gpo_par; DROP TABLE IF EXISTS gpo_rar; DROP TABLE IF EXISTS gpo_dpop; DROP TABLE IF EXISTS gpo_client_registration; DROP TABLE IF EXISTS gpo_subject_identifier; DROP TABLE IF EXISTS gpo_id_token; DROP TABLE IF EXISTS gpo_access_token_scope; DROP TABLE IF EXISTS gpo_access_token; DROP TABLE IF EXISTS gpo_refresh_token_scope; DROP TABLE IF EXISTS gpo_refresh_token; DROP TABLE IF EXISTS gpo_code_scheme; DROP TABLE IF EXISTS gpo_code_scope; DROP TABLE IF EXISTS gpo_code; DROP TABLE IF EXISTS gpo_client_token_request; DROP TABLE IF EXISTS gpo_device_scheme; DROP TABLE IF EXISTS gpo_device_authorization_scope; DROP TABLE IF EXISTS gpo_device_authorization; CREATE TABLE gpo_code ( gpoc_id SERIAL PRIMARY KEY, gpoc_plugin_name VARCHAR(256) NOT NULL, gpoc_authorization_type SMALLINT NOT NULL, gpoc_username VARCHAR(256) NOT NULL, gpoc_client_id VARCHAR(256) NOT NULL, gpoc_resource VARCHAR(512), gpoc_redirect_uri VARCHAR(512) NOT NULL, gpoc_code_hash VARCHAR(512) NOT NULL, gpoc_nonce VARCHAR(512), gpoc_claims_request TEXT DEFAULT NULL, gpoc_authorization_details TEXT DEFAULT NULL, gpoc_s_hash VARCHAR(512), gpoc_sid VARCHAR(128), gpoc_expires_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gpoc_issued_for VARCHAR(256), -- IP address or hostname gpoc_user_agent VARCHAR(256), gpoc_code_challenge VARCHAR(128), gpoc_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gpoc_code_hash ON gpo_code(gpoc_code_hash); CREATE INDEX i_gpoc_code_challenge ON gpo_code(gpoc_code_challenge); CREATE TABLE gpo_code_scope ( gpocs_id SERIAL PRIMARY KEY, gpoc_id INTEGER, gpocs_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE ); CREATE TABLE gpo_code_scheme ( gpoch_id SERIAL PRIMARY KEY, gpoc_id INTEGER, gpoch_scheme_module VARCHAR(128) NOT NULL, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE ); CREATE TABLE gpo_refresh_token ( gpor_id SERIAL PRIMARY KEY, gpor_plugin_name VARCHAR(256) NOT NULL, gpor_authorization_type SMALLINT NOT NULL, gpoc_id INTEGER DEFAULT NULL, gpor_username VARCHAR(256) NOT NULL, gpor_client_id VARCHAR(256), gpor_resource VARCHAR(512), gpor_claims_request TEXT DEFAULT NULL, gpor_authorization_details TEXT DEFAULT NULL, gpor_issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gpor_expires_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gpor_last_seen TIMESTAMPTZ NOT NULL DEFAULT NOW(), gpor_duration INTEGER, gpor_rolling_expiration SMALLINT DEFAULT 0, gpor_issued_for VARCHAR(256), -- IP address or hostname gpor_user_agent VARCHAR(256), gpor_token_hash VARCHAR(512) NOT NULL, gpor_jti VARCHAR(128), gpor_dpop_jkt VARCHAR(512), gpor_enabled SMALLINT DEFAULT 1, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE ); CREATE INDEX i_gpor_token_hash ON gpo_refresh_token(gpor_token_hash); CREATE INDEX i_gpor_jti ON gpo_refresh_token(gpor_jti); CREATE TABLE gpo_refresh_token_scope ( gpors_id SERIAL PRIMARY KEY, gpor_id INTEGER, gpors_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpor_id) REFERENCES gpo_refresh_token(gpor_id) ON DELETE CASCADE ); -- Access token table, to store meta information on access token sent CREATE TABLE gpo_access_token ( gpoa_id SERIAL PRIMARY KEY, gpoa_plugin_name VARCHAR(256) NOT NULL, gpoa_authorization_type SMALLINT NOT NULL, gpor_id INTEGER DEFAULT NULL, gpoa_username VARCHAR(256), gpoa_client_id VARCHAR(256), gpoa_resource VARCHAR(512), gpoa_issued_at TIMESTAMPTZ DEFAULT NOW(), gpoa_issued_for VARCHAR(256), -- IP address or hostname gpoa_user_agent VARCHAR(256), gpoa_token_hash VARCHAR(512) NOT NULL, gpoa_jti VARCHAR(128), gpoa_authorization_details TEXT DEFAULT NULL, gpoa_enabled SMALLINT DEFAULT 1, FOREIGN KEY(gpor_id) REFERENCES gpo_refresh_token(gpor_id) ON DELETE CASCADE ); CREATE INDEX i_gpoa_token_hash ON gpo_access_token(gpoa_token_hash); CREATE INDEX i_gpoa_jti ON gpo_access_token(gpoa_jti); CREATE TABLE gpo_access_token_scope ( gpoas_id SERIAL PRIMARY KEY, gpoa_id INTEGER, gpoas_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpoa_id) REFERENCES gpo_access_token(gpoa_id) ON DELETE CASCADE ); -- Id token table, to store meta information on id token sent CREATE TABLE gpo_id_token ( gpoi_id SERIAL PRIMARY KEY, gpoc_id INTEGER, gpor_id INTEGER, gpoi_plugin_name VARCHAR(256) NOT NULL, gpoi_authorization_type SMALLINT NOT NULL, gpoi_username VARCHAR(256), gpoi_client_id VARCHAR(256), gpoi_issued_at TIMESTAMPTZ DEFAULT NOW(), gpoi_issued_for VARCHAR(256), -- IP address or hostname gpoi_user_agent VARCHAR(256), gpoi_hash VARCHAR(512), gpoi_sid VARCHAR(128), gpoi_enabled SMALLINT DEFAULT 1, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE, FOREIGN KEY(gpor_id) REFERENCES gpo_refresh_token(gpor_id) ON DELETE CASCADE ); CREATE INDEX i_gpoi_hash ON gpo_id_token(gpoi_hash); -- subject identifier table to store subs and their relations to usernames, client_id and sector_identifier CREATE TABLE gpo_subject_identifier ( gposi_id SERIAL PRIMARY KEY, gposi_plugin_name VARCHAR(256) NOT NULL, gposi_username VARCHAR(256) NOT NULL, gposi_client_id VARCHAR(256), gposi_sector_identifier_uri VARCHAR(256), gposi_sub VARCHAR(256) NOT NULL ); CREATE INDEX i_gposi_sub ON gpo_subject_identifier(gposi_sub); -- store meta information on client registration CREATE TABLE gpo_client_registration ( gpocr_id SERIAL PRIMARY KEY, gpocr_plugin_name VARCHAR(256) NOT NULL, gpocr_cient_id VARCHAR(256) NOT NULL, gpocr_management_at_hash VARCHAR(512), gpocr_created_at TIMESTAMPTZ DEFAULT NOW(), gpoa_id INTEGER, gpocr_issued_for VARCHAR(256), -- IP address or hostname gpocr_user_agent VARCHAR(256), FOREIGN KEY(gpoa_id) REFERENCES gpo_access_token(gpoa_id) ON DELETE CASCADE ); CREATE INDEX i_gpocr_management_at_hash ON gpo_client_registration(gpocr_management_at_hash); -- store meta information about client request on token endpoint CREATE TABLE gpo_client_token_request ( gpoctr_id SERIAL PRIMARY KEY, gpoctr_plugin_name VARCHAR(256) NOT NULL, gpoctr_cient_id VARCHAR(256) NOT NULL, gpoctr_created_at TIMESTAMPTZ DEFAULT NOW(), gpoctr_issued_for VARCHAR(256), -- IP address or hostname gpoctr_jti_hash VARCHAR(512) ); -- store device authorization requests CREATE TABLE gpo_device_authorization ( gpoda_id SERIAL PRIMARY KEY, gpoda_plugin_name VARCHAR(256) NOT NULL, gpoda_client_id VARCHAR(256) NOT NULL, gpoda_resource VARCHAR(512), gpoda_username VARCHAR(256), gpoda_created_at TIMESTAMPTZ DEFAULT NOW(), gpoda_expires_at TIMESTAMPTZ DEFAULT NOW(), gpoda_issued_for VARCHAR(256), -- IP address or hostname of the deice client gpoda_device_code_hash VARCHAR(512) NOT NULL, gpoda_user_code_hash VARCHAR(512) NOT NULL, gpoda_sid VARCHAR(128), gpoda_status SMALLINT DEFAULT 0, -- 0: created, 1: user verified, 2 device completed, 3 disabled gpoda_authorization_details TEXT DEFAULT NULL, gpoda_last_check TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX i_gpoda_device_code_hash ON gpo_device_authorization(gpoda_device_code_hash); CREATE INDEX i_gpoda_user_code_hash ON gpo_device_authorization(gpoda_user_code_hash); CREATE TABLE gpo_device_authorization_scope ( gpodas_id SERIAL PRIMARY KEY, gpoda_id INTEGER, gpodas_scope VARCHAR(128) NOT NULL, gpodas_allowed SMALLINT DEFAULT 0, FOREIGN KEY(gpoda_id) REFERENCES gpo_device_authorization(gpoda_id) ON DELETE CASCADE ); CREATE TABLE gpo_device_scheme ( gpodh_id SERIAL PRIMARY KEY, gpoda_id INTEGER, gpodh_scheme_module VARCHAR(128) NOT NULL, FOREIGN KEY(gpoda_id) REFERENCES gpo_device_authorization(gpoda_id) ON DELETE CASCADE ); CREATE TABLE gpo_dpop ( gpod_id SERIAL PRIMARY KEY, gpod_plugin_name VARCHAR(256) NOT NULL, gpod_client_id VARCHAR(256) NOT NULL, gpod_jti_hash VARCHAR(512) NOT NULL, gpod_jkt VARCHAR(512) NOT NULL, gpod_htm VARCHAR(128) NOT NULL, gpod_htu VARCHAR(512) NOT NULL, gpod_iat TIMESTAMPTZ NOT NULL, gpod_last_seen TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX i_gpod_jti_hash ON gpo_dpop(gpod_jti_hash); CREATE TABLE gpo_rar ( gporar_id SERIAL PRIMARY KEY, gporar_plugin_name VARCHAR(256) NOT NULL, gporar_client_id VARCHAR(256) NOT NULL, gporar_type VARCHAR(256) NOT NULL, gporar_username VARCHAR(256), gporar_consent SMALLINT DEFAULT 0, gporar_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gporar_client_id ON gpo_rar(gporar_client_id); CREATE INDEX i_gporar_type ON gpo_rar(gporar_type); CREATE INDEX i_gporar_username ON gpo_rar(gporar_username); CREATE TABLE gpo_par ( gpop_id SERIAL PRIMARY KEY, gpop_plugin_name VARCHAR(256) NOT NULL, gpop_response_type VARCHAR(128) NOT NULL, gpop_state TEXT, gpop_username VARCHAR(256), gpop_client_id VARCHAR(256) NOT NULL, gpop_redirect_uri VARCHAR(512) NOT NULL, gpop_request_uri_hash VARCHAR(512) NOT NULL, gpop_nonce VARCHAR(512), gpop_code_challenge VARCHAR(128), gpop_resource VARCHAR(512), gpop_claims_request TEXT DEFAULT NULL, gpop_authorization_details TEXT DEFAULT NULL, gpop_additional_parameters TEXT DEFAULT NULL, gpop_status SMALLINT DEFAULT 0, -- 0 created, 1 validated, 2 completed gpop_expires_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, gpop_issued_for VARCHAR(256), -- IP address or hostname gpop_user_agent VARCHAR(256) ); CREATE INDEX i_gpop_client_id ON gpo_par(gpop_client_id); CREATE INDEX i_gpop_request_uri_hash ON gpo_par(gpop_request_uri_hash); CREATE INDEX i_gpop_code_challenge ON gpo_par(gpop_code_challenge); CREATE TABLE gpo_par_scope ( gpops_id SERIAL PRIMARY KEY, gpop_id INTEGER, gpops_scope VARCHAR(128) NOT NULL, FOREIGN KEY(gpop_id) REFERENCES gpo_par(gpop_id) ON DELETE CASCADE ); CREATE TABLE gpo_ciba ( gpob_id SERIAL PRIMARY KEY, gpob_plugin_name VARCHAR(256) NOT NULL, gpob_client_id VARCHAR(256) NOT NULL, gpob_x5t_s256 VARCHAR(64), gpob_username VARCHAR(256) NOT NULL, gpob_client_notification_token VARCHAR(1024), gpob_jti_hash VARCHAR(512), gpob_auth_req_id VARCHAR(128), gpob_user_req_id VARCHAR(128), gpob_binding_message VARCHAR(256), gpob_sid VARCHAR(128), gpob_status SMALLINT DEFAULT 0, -- 0: created, 1: accepted, 2: error, 3: closed gpob_expires_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, gpob_issued_for VARCHAR(256), -- IP address or hostname gpob_user_agent VARCHAR(256), gpob_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gpob_client_id ON gpo_ciba(gpob_client_id); CREATE INDEX i_gpob_jti_hash ON gpo_ciba(gpob_jti_hash); CREATE INDEX i_gpob_client_notification_token ON gpo_ciba(gpob_client_notification_token); CREATE INDEX i_gpob_auth_req_id ON gpo_ciba(gpob_auth_req_id); CREATE INDEX i_gpob_user_req_id ON gpo_ciba(gpob_user_req_id); CREATE TABLE gpo_ciba_scope ( gpocs_id SERIAL PRIMARY KEY, gpob_id INTEGER, gpops_scope VARCHAR(128) NOT NULL, gpobs_granted SMALLINT DEFAULT 0, FOREIGN KEY(gpob_id) REFERENCES gpo_ciba(gpob_id) ON DELETE CASCADE ); CREATE TABLE gpo_ciba_scheme ( gpobh_id SERIAL PRIMARY KEY, gpob_id INTEGER, gpobh_scheme_module VARCHAR(128) NOT NULL, FOREIGN KEY(gpob_id) REFERENCES gpo_ciba(gpob_id) ON DELETE CASCADE ); glewlwyd-2.6.1/src/plugin/protocol_oidc.sqlite3.sql000066400000000000000000000253061415646314000224200ustar00rootroot00000000000000DROP TABLE IF EXISTS gpo_ciba_scope; DROP TABLE IF EXISTS gpo_ciba_scheme; DROP TABLE IF EXISTS gpo_ciba; DROP TABLE IF EXISTS gpo_par_scope; DROP TABLE IF EXISTS gpo_par; DROP TABLE IF EXISTS gpo_rar; DROP TABLE IF EXISTS gpo_dpop; DROP TABLE IF EXISTS gpo_client_registration; DROP TABLE IF EXISTS gpo_subject_identifier; DROP TABLE IF EXISTS gpo_id_token; DROP TABLE IF EXISTS gpo_access_token_scope; DROP TABLE IF EXISTS gpo_access_token; DROP TABLE IF EXISTS gpo_refresh_token_scope; DROP TABLE IF EXISTS gpo_refresh_token; DROP TABLE IF EXISTS gpo_code_scheme; DROP TABLE IF EXISTS gpo_code_scope; DROP TABLE IF EXISTS gpo_code; DROP TABLE IF EXISTS gpo_client_token_request; DROP TABLE IF EXISTS gpo_device_scheme; DROP TABLE IF EXISTS gpo_device_authorization_scope; DROP TABLE IF EXISTS gpo_device_authorization; CREATE TABLE gpo_code ( gpoc_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoc_plugin_name TEXT NOT NULL, gpoc_authorization_type INTEGER NOT NULL, gpoc_username TEXT NOT NULL, gpoc_client_id TEXT NOT NULL, gpoc_redirect_uri TEXT NOT NULL, gpoc_code_hash TEXT NOT NULL, gpoc_nonce TEXT, gpoc_resource TEXT, gpoc_claims_request TEXT DEFAULT NULL, gpoc_authorization_details TEXT DEFAULT NULL, gpoc_s_hash TEXT, gpoc_sid TEXT, gpoc_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpoc_issued_for TEXT, -- IP address or hostname gpoc_user_agent TEXT, gpoc_code_challenge TEXT, gpoc_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gpoc_code_hash ON gpo_code(gpoc_code_hash); CREATE INDEX i_gpoc_code_challenge ON gpo_code(gpoc_code_challenge); CREATE TABLE gpo_code_scope ( gpocs_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoc_id INTEGER, gpocs_scope TEXT NOT NULL, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE ); CREATE TABLE gpo_code_scheme ( gpoch_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoc_id INTEGER, gpoch_scheme_module TEXT NOT NULL, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE ); CREATE TABLE gpo_refresh_token ( gpor_id INTEGER PRIMARY KEY AUTOINCREMENT, gpor_plugin_name TEXT NOT NULL, gpor_authorization_type INTEGER NOT NULL, gpoc_id INTEGER DEFAULT NULL, gpor_username TEXT NOT NULL, gpor_client_id TEXT, gpor_resource TEXT, gpor_claims_request TEXT DEFAULT NULL, gpor_authorization_details TEXT DEFAULT NULL, gpor_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpor_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpor_last_seen TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpor_duration INTEGER, gpor_rolling_expiration INTEGER DEFAULT 0, gpor_issued_for TEXT, -- IP address or hostname gpor_user_agent TEXT, gpor_token_hash TEXT NOT NULL, gpor_jti TEXT, gpor_dpop_jkt TEXT, gpor_enabled INTEGER DEFAULT 1, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE ); CREATE INDEX i_gpor_token_hash ON gpo_refresh_token(gpor_token_hash); CREATE INDEX i_gpor_jti ON gpo_refresh_token(gpor_jti); CREATE TABLE gpo_refresh_token_scope ( gpors_id INTEGER PRIMARY KEY AUTOINCREMENT, gpor_id INTEGER, gpors_scope TEXT NOT NULL, FOREIGN KEY(gpor_id) REFERENCES gpo_refresh_token(gpor_id) ON DELETE CASCADE ); -- Access token table, to store meta information on access token sent CREATE TABLE gpo_access_token ( gpoa_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoa_plugin_name TEXT NOT NULL, gpoa_authorization_type INTEGER NOT NULL, gpor_id INTEGER DEFAULT NULL, gpoa_username TEXT, gpoa_client_id TEXT, gpoa_resource TEXT, gpoa_issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoa_issued_for TEXT, -- IP address or hostname gpoa_user_agent TEXT, gpoa_token_hash TEXT NOT NULL, gpoa_jti TEXT, gpoa_authorization_details TEXT DEFAULT NULL, gpoa_enabled INTEGER DEFAULT 1, FOREIGN KEY(gpor_id) REFERENCES gpo_refresh_token(gpor_id) ON DELETE CASCADE ); CREATE INDEX i_gpoa_token_hash ON gpo_access_token(gpoa_token_hash); CREATE INDEX i_gpoa_jti ON gpo_access_token(gpoa_jti); CREATE TABLE gpo_access_token_scope ( gpoas_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoa_id INTEGER, gpoas_scope TEXT NOT NULL, FOREIGN KEY(gpoa_id) REFERENCES gpo_access_token(gpoa_id) ON DELETE CASCADE ); -- Id token table, to store meta information on id token sent CREATE TABLE gpo_id_token ( gpoi_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoc_id INTEGER, gpor_id INTEGER, gpoi_plugin_name TEXT NOT NULL, gpoi_authorization_type INTEGER NOT NULL, gpoi_username TEXT, gpoi_client_id TEXT, gpoi_issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoi_issued_for TEXT, -- IP address or hostname gpoi_user_agent TEXT, gpoi_hash TEXT, gpoi_sid TEXT, gpoi_enabled INTEGER DEFAULT 1, FOREIGN KEY(gpoc_id) REFERENCES gpo_code(gpoc_id) ON DELETE CASCADE, FOREIGN KEY(gpor_id) REFERENCES gpo_refresh_token(gpor_id) ON DELETE CASCADE ); CREATE INDEX i_gpoi_hash ON gpo_id_token(gpoi_hash); -- subject identifier table to store subs and their relations to usernames, client_id and sector_identifier CREATE TABLE gpo_subject_identifier ( gposi_id INTEGER PRIMARY KEY AUTOINCREMENT, gposi_plugin_name TEXT NOT NULL, gposi_username TEXT NOT NULL, gposi_client_id TEXT, gposi_sector_identifier_uri TEXT, gposi_sub TEXT NOT NULL ); CREATE INDEX i_gposi_sub ON gpo_subject_identifier(gposi_sub); -- store meta information on client registration CREATE TABLE gpo_client_registration ( gpocr_id INTEGER PRIMARY KEY AUTOINCREMENT, gpocr_plugin_name TEXT NOT NULL, gpocr_cient_id TEXT NOT NULL, gpocr_management_at_hash TEXT, gpocr_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoa_id INTEGER, gpocr_issued_for TEXT, -- IP address or hostname gpocr_user_agent TEXT, FOREIGN KEY(gpoa_id) REFERENCES gpo_access_token(gpoa_id) ON DELETE CASCADE ); CREATE INDEX i_gpocr_management_at_hash ON gpo_client_registration(gpocr_management_at_hash); -- store meta information about client request on token endpoint CREATE TABLE gpo_client_token_request ( gpoctr_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoctr_plugin_name TEXT NOT NULL, gpoctr_cient_id TEXT NOT NULL, gpoctr_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoctr_issued_for TEXT, -- IP address or hostname gpoctr_jti_hash TEXT ); -- store device authorization requests CREATE TABLE gpo_device_authorization ( gpoda_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoda_plugin_name TEXT NOT NULL, gpoda_client_id TEXT NOT NULL, gpoda_resource TEXT, gpoda_username TEXT, gpoda_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoda_expires_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gpoda_issued_for TEXT, -- IP address or hostname of the deice client gpoda_device_code_hash TEXT NOT NULL, gpoda_user_code_hash TEXT NOT NULL, gpoda_sid TEXT, gpoda_status INTEGER DEFAULT 0, -- 0: created, 1: user verified, 2 device completed, 3 disabled gpoda_authorization_details TEXT DEFAULT NULL, gpoda_last_check TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX i_gpoda_device_code_hash ON gpo_device_authorization(gpoda_device_code_hash); CREATE INDEX i_gpoda_user_code_hash ON gpo_device_authorization(gpoda_user_code_hash); CREATE TABLE gpo_device_authorization_scope ( gpodas_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoda_id INTEGER, gpodas_scope TEXT NOT NULL, gpodas_allowed INTEGER DEFAULT 0, FOREIGN KEY(gpoda_id) REFERENCES gpo_device_authorization(gpoda_id) ON DELETE CASCADE ); CREATE TABLE gpo_device_scheme ( gpodh_id INTEGER PRIMARY KEY AUTOINCREMENT, gpoda_id INTEGER, gpodh_scheme_module TEXT NOT NULL, FOREIGN KEY(gpoda_id) REFERENCES gpo_device_authorization(gpoda_id) ON DELETE CASCADE ); CREATE TABLE gpo_dpop ( gpod_id INTEGER PRIMARY KEY AUTOINCREMENT, gpod_plugin_name TEXT NOT NULL, gpod_client_id TEXT NOT NULL, gpod_jti_hash TEXT NOT NULL, gpod_jkt TEXT NOT NULL, gpod_htm TEXT NOT NULL, gpod_htu TEXT NOT NULL, gpod_iat TIMESTAMP NOT NULL, gpod_last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX i_gpod_jti_hash ON gpo_dpop(gpod_jti_hash); CREATE TABLE gpo_rar ( gporar_id INTEGER PRIMARY KEY AUTOINCREMENT, gporar_plugin_name TEXT NOT NULL, gporar_client_id TEXT NOT NULL, gporar_type TEXT NOT NULL, gporar_username TEXT, gporar_consent INTEGER DEFAULT 0, gporar_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gporar_client_id ON gpo_rar(gporar_client_id); CREATE INDEX i_gporar_type ON gpo_rar(gporar_type); CREATE INDEX i_gporar_username ON gpo_rar(gporar_username); CREATE TABLE gpo_par ( gpop_id INTEGER PRIMARY KEY AUTOINCREMENT, gpop_plugin_name TEXT NOT NULL, gpop_response_type TEXT NOT NULL, gpop_state TEXT, gpop_username TEXT, gpop_client_id TEXT NOT NULL, gpop_redirect_uri TEXT NOT NULL, gpop_request_uri_hash TEXT NOT NULL, gpop_nonce TEXT, gpop_code_challenge TEXT, gpop_resource TEXT, gpop_claims_request TEXT DEFAULT NULL, gpop_authorization_details TEXT DEFAULT NULL, gpop_additional_parameters TEXT DEFAULT NULL, gpop_status INTEGER DEFAULT 0, -- 0 created, 1 validated, 2 completed gpop_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpop_issued_for TEXT, -- IP address or hostname gpop_user_agent TEXT ); CREATE INDEX i_gpop_client_id ON gpo_par(gpop_client_id); CREATE INDEX i_gpop_request_uri_hash ON gpo_par(gpop_request_uri_hash); CREATE INDEX i_gpop_code_challenge ON gpo_par(gpop_code_challenge); CREATE TABLE gpo_par_scope ( gpops_id INTEGER PRIMARY KEY AUTOINCREMENT, gpop_id INTEGER, gpops_scope TEXT NOT NULL, FOREIGN KEY(gpop_id) REFERENCES gpo_par(gpop_id) ON DELETE CASCADE ); CREATE TABLE gpo_ciba ( gpob_id INTEGER PRIMARY KEY AUTOINCREMENT, gpob_plugin_name TEXT NOT NULL, gpob_client_id TEXT NOT NULL, gpob_x5t_s256 TEXT, gpob_username TEXT NOT NULL, gpob_client_notification_token TEXT, gpob_jti_hash TEXT, gpob_auth_req_id TEXT, gpob_user_req_id TEXT, gpob_binding_message TEXT, gpob_sid TEXT, gpob_status INTEGER DEFAULT 0, -- 0: created, 1: accepted, 2: error, 3: closed gpob_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gpob_issued_for TEXT, -- IP address or hostname gpob_user_agent TEXT, gpob_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gpob_client_id ON gpo_ciba(gpob_client_id); CREATE INDEX i_gpob_jti_hash ON gpo_ciba(gpob_jti_hash); CREATE INDEX i_gpob_client_notification_token ON gpo_ciba(gpob_client_notification_token); CREATE INDEX i_gpob_auth_req_id ON gpo_ciba(gpob_auth_req_id); CREATE INDEX i_gpob_user_req_id ON gpo_ciba(gpob_user_req_id); CREATE TABLE gpo_ciba_scope ( gpocs_id INTEGER PRIMARY KEY AUTOINCREMENT, gpob_id INTEGER, gpops_scope TEXT NOT NULL, gpobs_granted INTEGER DEFAULT 0, FOREIGN KEY(gpob_id) REFERENCES gpo_ciba(gpob_id) ON DELETE CASCADE ); CREATE TABLE gpo_ciba_scheme ( gpobh_id INTEGER PRIMARY KEY AUTOINCREMENT, gpob_id INTEGER, gpobh_scheme_module TEXT NOT NULL, FOREIGN KEY(gpob_id) REFERENCES gpo_ciba(gpob_id) ON DELETE CASCADE ); glewlwyd-2.6.1/src/plugin/register.c000066400000000000000000004753541415646314000174610ustar00rootroot00000000000000/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * register plugin * Allow unauthentified users to register a new account * * Copyright 2019-2020 Nicolas Mora * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . * */ #include #include #include "glewlwyd-common.h" #define GLEWLWYD_PLUGIN_REGSITER_DEFAULT_SESSION_DURATION 3600 #define GLEWLWYD_PLUGIN_REGSITER_DEFAULT_CODE_LENGTH 8 #define GLEWLWYD_PLUGIN_REGSITER_DEFAULT_CODE_DURATION 600 #define GLEWLWYD_PLUGIN_REGSITER_DEFAULT_CONTENT_TYPE "text/plain; charset=utf-8" #define GLEWLWYD_DATE_BUFFER 128 #define GLEWLWYD_SESSION_ID_LENGTH 32 #define GLEWLWYD_TOKEN_LENGTH 32 #define GLEWLWYD_RESET_CREDENTIALS_CODE_LENGTH 16 #define GLEWLWYD_PLUGIN_REGISTER_TABLE_SESSION "gpr_session" #define GLEWLWYD_PLUGIN_REGISTER_TABLE_UPDATE_EMAIL "gpr_update_email" #define GLEWLWYD_PLUGIN_REGISTER_TABLE_RESET_CREDENTIALS_SESSION "gpr_reset_credentials_session" #define GLEWLWYD_PLUGIN_REGISTER_TABLE_RESET_CREDENTIALS_EMAIL "gpr_reset_credentials_email" #define GLWD_METRICS_REGISTRATION_STARTED "glewlwyd_registration_started" #define GLWD_METRICS_REGISTRATION_COMPLETED "glewlwyd_registration_completed" #define GLWD_METRICS_REGISTRATION_CANCELLED "glewlwyd_registration_cancelled" #define GLWD_METRICS_EMAIL_UPDATED "glewlwyd_email_updated" #define GLWD_METRICS_RESET_CREDENTIALS_STARTED "glewlwyd_reset_credentials_started" #define GLWD_METRICS_RESET_CREDENTIALS_COMPLETED "glewlwyd_reset_credentials_completed" struct _register_config { struct config_plugin * glewlwyd_config; pthread_mutex_t insert_lock; char * name; json_t * j_parameters; }; static const char * get_template_property(json_t * j_params, const char * user_lang, const char * property_field) { json_t * j_template = NULL; const char * property = NULL, * property_default = NULL, * lang = NULL; if (json_object_get(j_params, "templates") == NULL) { property = json_string_value(json_object_get(j_params, property_field)); } else { json_object_foreach(json_object_get(j_params, "templates"), lang, j_template) { if (0 == o_strcmp(user_lang, lang)) { property = json_string_value(json_object_get(j_template, property_field)); } if (json_object_get(j_template, "defaultLang") == json_true()) { property_default = json_string_value(json_object_get(j_template, property_field)); } } if (property == NULL) { property = property_default; } } return property; } static const char * get_template_email_update_property(json_t * j_params, const char * user_lang, const char * property_field) { json_t * j_template = NULL; const char * property = NULL, * property_default = NULL, * lang = NULL; if (json_object_get(j_params, "templatesUpdateEmail") == NULL) { property = json_string_value(json_object_get(j_params, property_field)); } else { json_object_foreach(json_object_get(j_params, "templatesUpdateEmail"), lang, j_template) { if (0 == o_strcmp(user_lang, lang)) { property = json_string_value(json_object_get(j_template, property_field)); } if (json_object_get(j_template, "defaultLang") == json_true()) { property_default = json_string_value(json_object_get(j_template, property_field)); } } if (property == NULL) { property = property_default; } } return property; } static const char * get_template_reset_credentials_property(json_t * j_params, const char * user_lang, const char * property_field) { json_t * j_template = NULL; const char * property = NULL, * property_default = NULL, * lang = NULL; if (json_object_get(j_params, "templatesResetCredentials") == NULL) { property = json_string_value(json_object_get(j_params, property_field)); } else { json_object_foreach(json_object_get(j_params, "templatesResetCredentials"), lang, j_template) { if (0 == o_strcmp(user_lang, lang)) { property = json_string_value(json_object_get(j_template, property_field)); } if (json_object_get(j_template, "defaultLang") == json_true()) { property_default = json_string_value(json_object_get(j_template, property_field)); } } if (property == NULL) { property = property_default; } } return property; } static int can_register_scheme(struct _register_config * config, const struct _u_request * request, const char * scheme_name) { json_t * j_element = NULL; size_t index = 0; if (o_strstr(request->url_path, "reset-credentials") == NULL) { if (json_object_get(config->j_parameters, "schemes") != NULL) { json_array_foreach(json_object_get(config->j_parameters, "schemes"), index, j_element) { if (0 == o_strcmp(json_string_value(json_object_get(j_element, "name")), scheme_name)) { return 1; } } } return 0; } else { return 1; } } static json_t * register_generate_email_verification_code(struct _register_config * config, const char * username, const char * email, const char * lang, const char * callback_url, const char * issued_for, const char * user_agent, const char * ip_source) { char * code, * code_hash, * expires_at_clause, * tmp_body, * body, token[GLEWLWYD_TOKEN_LENGTH+1], * token_hash; json_t * j_return, * j_query; int res; size_t code_len; time_t now; if (pthread_mutex_lock(&config->insert_lock)) { y_log_message(Y_LOG_LEVEL_ERROR, "register_generate_email_verification_code - Error pthread_mutex_lock"); j_return = json_pack("{si}", "result", G_ERROR); } else { // Disable existing sessions for the specified e-mail address time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } j_query = json_pack("{sss{si}s{sssss{ssss}si}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_SESSION, "set", "gprs_enabled", 0, "where", "gprs_plugin_name", config->name, "gprs_email", email, "gprs_expires_at", "operator", "raw", "value", expires_at_clause, "gprs_enabled", 1); o_free(expires_at_clause); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { code_len = json_integer_value(json_object_get(config->j_parameters, "verification-code-length")); if ((code = o_malloc((code_len+1)*sizeof(char))) != NULL) { if (rand_code(code, code_len)) { if ((code_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, code)) != NULL) { if (rand_string_nonce(token, GLEWLWYD_TOKEN_LENGTH)) { if ((token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, token)) != NULL) { if ((tmp_body = str_replace(get_template_property(config->j_parameters, lang, "body-pattern"), "{TOKEN}", token)) != NULL) { if ((body = str_replace(tmp_body, "{CODE}", code)) != NULL) { if (ulfius_send_smtp_rich_email(json_string_value(json_object_get(config->j_parameters, "host")), json_integer_value(json_object_get(config->j_parameters, "port")), json_object_get(config->j_parameters, "use-tls")==json_true()?1:0, json_object_get(config->j_parameters, "verify-certificate")==json_false()?0:1, json_string_length(json_object_get(config->j_parameters, "user"))?json_string_value(json_object_get(config->j_parameters, "user")):NULL, json_string_length(json_object_get(config->j_parameters, "password"))?json_string_value(json_object_get(config->j_parameters, "password")):NULL, json_string_value(json_object_get(config->j_parameters, "from")), email, NULL, NULL, json_string_length(json_object_get(config->j_parameters, "content-type"))?json_string_value(json_object_get(config->j_parameters, "content-type")):"text/plain; charset=utf-8", get_template_property(config->j_parameters, lang, "subject"), body) == U_OK) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Register new user - code sent to email %s at IP Address %s", email, ip_source); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("FROM_UNIXTIME(%u)", (now + (unsigned int)json_integer_value(json_object_get(config->j_parameters, "verification-code-duration")))); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("TO_TIMESTAMP(%u)", (now + (unsigned int)json_integer_value(json_object_get(config->j_parameters, "verification-code-duration")))); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("%u", (now + (unsigned int)json_integer_value(json_object_get(config->j_parameters, "verification-code-duration")))); } j_query = json_pack("{sss{ssssssssss?sss{ss}ssss}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_SESSION, "values", "gprs_plugin_name", config->name, "gprs_username", username, "gprs_email", email, "gprs_code_hash", code_hash, "gprs_callback_url", callback_url, "gprs_token_hash", token_hash, "gprs_expires_at", "raw", expires_at_clause, "gprs_issued_for", issued_for, "gprs_user_agent", user_agent!=NULL?user_agent:""); o_free(expires_at_clause); res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_return = json_pack("{siss}", "result", G_OK, "code", code); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_generate_email_verification_code - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_generate_email_verification_code - Error ulfius_send_smtp_rich_email"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } o_free(body); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_generate_email_verification_code - Error str_replace"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } o_free(tmp_body); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_generate_email_verification_code - Error str_replace tmp_body"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } o_free(token_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_generate_email_verification_code - Error glewlwyd_callback_generate_hash rand_string_nonce token"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_generate_email_verification_code - Error rand_string_nonce token"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(code_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_generate_email_verification_code - Error glewlwyd_callback_generate_hash rand_code code"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_generate_email_verification_code - Error rand_code code"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(code); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_generate_email_verification_code - Error allocating resources for code"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_generate_email_verification_code - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } pthread_mutex_unlock(&config->insert_lock); } return j_return; } static json_t * register_verify_email_token(struct _register_config * config, const char * token, const char * ip_source) { json_t * j_query, * j_result = NULL, * j_return, * j_new_user; int res; char * token_hash = NULL, * expires_at_clause = NULL, session[GLEWLWYD_SESSION_ID_LENGTH+1] = {}, * session_hash = NULL; time_t now; if ((token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, token)) != NULL) { time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } j_query = json_pack("{sss[sss]s{sssss{ssss}si}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_SESSION, "columns", "gprs_id", "gprs_username AS username", "gprs_email AS email", "where", "gprs_plugin_name", config->name, "gprs_token_hash", token_hash, "gprs_expires_at", "operator", "raw", "value", expires_at_clause, "gprs_enabled", 1); o_free(expires_at_clause); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_new_user = json_pack("{sOsOsosO}", "username", json_object_get(json_array_get(j_result, 0), "username"), "email", json_object_get(json_array_get(j_result, 0), "email"), "enabled", json_false(), "scope", json_object_get(config->j_parameters, "scope")); if (config->glewlwyd_config->glewlwyd_plugin_callback_add_user(config->glewlwyd_config, j_new_user) == G_OK) { if (rand_string_nonce(session, GLEWLWYD_SESSION_ID_LENGTH) != NULL) { if ((session_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, session)) != NULL) { time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("FROM_UNIXTIME(%u)", (now + (unsigned int)json_integer_value(json_object_get(config->j_parameters, "session-duration")))); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("TO_TIMESTAMP(%u)", (now + (unsigned int)json_integer_value(json_object_get(config->j_parameters, "session-duration")))); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("%u", (now + (unsigned int)json_integer_value(json_object_get(config->j_parameters, "session-duration")))); } j_query = json_pack("{sss{sss{ss}ss}s{sssO}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_SESSION, "set", "gprs_session_hash", session_hash, "gprs_expires_at", "raw", expires_at_clause, "gprs_token_hash", "VERIFIED", "where", "gprs_plugin_name", config->name, "gprs_id", json_object_get(json_array_get(j_result, 0), "gprs_id")); o_free(expires_at_clause); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_return = json_pack("{siss}", "result", G_OK, "session", session); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_verify_email_code - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } o_free(session_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_verify_email_code - Error glewlwyd_callback_generate_hash"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_verify_email_code - Error rand_string_nonce"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_verify_email_code - Error glewlwyd_plugin_callback_add_user"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_new_user); } else { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Verify e-mail - code invalid at IP Address %s", ip_source); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_verify_email_code - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_verify_email_code - Error generate hash for session"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(token_hash); return j_return; } static json_t * register_verify_email_code(struct _register_config * config, const char * username, const char * email, const char * code, const char * ip_source) { json_t * j_query, * j_result = NULL, * j_return, * j_new_user; int res; char * code_hash = NULL, * expires_at_clause = NULL, session[GLEWLWYD_SESSION_ID_LENGTH+1] = {}, * session_hash = NULL; time_t now; if ((code_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, code)) != NULL) { time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } j_query = json_pack("{sss[s]s{sssssssss{ssss}si}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_SESSION, "columns", "gprs_id", "where", "gprs_plugin_name", config->name, "gprs_code_hash", code_hash, "gprs_username", username, "gprs_email", email, "gprs_expires_at", "operator", "raw", "value", expires_at_clause, "gprs_enabled", 1); o_free(expires_at_clause); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_new_user = json_pack("{sssssosO}", "username", username, "email", email, "enabled", json_false(), "scope", json_object_get(config->j_parameters, "scope")); if (config->glewlwyd_config->glewlwyd_plugin_callback_add_user(config->glewlwyd_config, j_new_user) == G_OK) { if (rand_string_nonce(session, GLEWLWYD_SESSION_ID_LENGTH) != NULL) { if ((session_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, session)) != NULL) { time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("FROM_UNIXTIME(%u)", (now + (unsigned int)json_integer_value(json_object_get(config->j_parameters, "session-duration")))); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("TO_TIMESTAMP(%u)", (now + (unsigned int)json_integer_value(json_object_get(config->j_parameters, "session-duration")))); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("%u", (now + (unsigned int)json_integer_value(json_object_get(config->j_parameters, "session-duration")))); } j_query = json_pack("{sss{sss{ss}ss}s{sssO}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_SESSION, "set", "gprs_session_hash", session_hash, "gprs_expires_at", "raw", expires_at_clause, "gprs_code_hash", "VERIFIED", "where", "gprs_plugin_name", config->name, "gprs_id", json_object_get(json_array_get(j_result, 0), "gprs_id")); o_free(expires_at_clause); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_return = json_pack("{siss}", "result", G_OK, "session", session); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_verify_email_code - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } o_free(session_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_verify_email_code - Error glewlwyd_callback_generate_hash"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_verify_email_code - Error rand_string_nonce"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_verify_email_code - Error glewlwyd_plugin_callback_add_user"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_new_user); } else { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Verify e-mail - code invalid at IP Address %s", ip_source); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_verify_email_code - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_verify_email_code - Error generate hash for session"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(code_hash); return j_return; } static json_t * register_check_session(struct _register_config * config, const char * session) { json_t * j_query, * j_result = NULL, * j_return; int res; char * session_hash = NULL, * expires_at_clause = NULL; time_t now; if (o_strlen(session)) { session_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, session); if (session_hash != NULL) { time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } j_query = json_pack("{sss[sssss]s{sssss{ssss}si}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_SESSION, "columns", "gprs_username AS username", "gprs_name AS name", "gprs_email AS email", "gprs_callback_url AS callback_url", "gprs_password_set", "where", "gprs_plugin_name", config->name, "gprs_session_hash", session_hash, "gprs_expires_at", "operator", "raw", "value", expires_at_clause, "gprs_enabled", 1); o_free(expires_at_clause); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { if (json_integer_value(json_object_get(json_array_get(j_result, 0), "gprs_password_set"))) { json_object_set(json_array_get(j_result, 0), "password_set", json_true()); } else { json_object_set(json_array_get(j_result, 0), "password_set", json_false()); } json_object_del(json_array_get(j_result, 0), "gprs_password_set"); j_return = json_pack("{sisO}", "result", G_OK, "user", json_array_get(j_result, 0)); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_check_session - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_check_session - Error generate hash for session"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(session_hash); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } return j_return; } static json_t * register_check_username(struct _register_config * config, const char * username) { json_t * j_query, * j_result = NULL, * j_return; int res; char * expires_at_clause = NULL; time_t now; if (o_strlen(username)) { time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } j_query = json_pack("{sss[s]s{sssss{ssss}si}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_SESSION, "columns", "gprs_username", "where", "gprs_plugin_name", config->name, "gprs_username", username, "gprs_expires_at", "operator", "raw", "value", expires_at_clause, "gprs_enabled", 1); o_free(expires_at_clause); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_return = json_pack("{si}", "result", G_OK); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_check_username - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } static json_t * register_new_user(struct _register_config * config, const char * username, const char * issued_for, const char * user_agent) { json_t * j_query, * j_return, * j_user, * j_new_user; int res; char * expires_at_clause, session[GLEWLWYD_SESSION_ID_LENGTH+1] = {}, * session_hash = NULL; time_t now; if (pthread_mutex_lock(&config->insert_lock)) { y_log_message(Y_LOG_LEVEL_ERROR, "register_new_user - Error pthread_mutex_lock"); j_return = json_pack("{si}", "result", G_ERROR); } else { j_user = register_check_username(config, username); if (check_result_value(j_user, G_ERROR_NOT_FOUND)) { j_new_user = json_pack("{sssosO}", "username", username, "enabled", json_false(), "scope", json_object_get(config->j_parameters, "scope")); if (config->glewlwyd_config->glewlwyd_plugin_callback_add_user(config->glewlwyd_config, j_new_user) == G_OK) { if (rand_string_nonce(session, GLEWLWYD_SESSION_ID_LENGTH) != NULL) { if ((session_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, session)) != NULL) { time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("FROM_UNIXTIME(%u)", (now + (unsigned int)json_integer_value(json_object_get(config->j_parameters, "session-duration")))); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("TO_TIMESTAMP(%u)", (now + (unsigned int)json_integer_value(json_object_get(config->j_parameters, "session-duration")))); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("%u", (now + (unsigned int)json_integer_value(json_object_get(config->j_parameters, "session-duration")))); } j_query = json_pack("{sss{sssssss{ss}ssss}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_SESSION, "values", "gprs_plugin_name", config->name, "gprs_username", username, "gprs_session_hash", session_hash, "gprs_expires_at", "raw", expires_at_clause, "gprs_issued_for", issued_for, "gprs_user_agent", user_agent!=NULL?user_agent:""); o_free(expires_at_clause); res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_return = json_pack("{siss}", "result", G_OK, "session", session); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_new_user - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } o_free(session_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_new_user - Error glewlwyd_callback_generate_hash"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_new_user - Error rand_string_nonce"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_new_user - Error glewlwyd_plugin_callback_add_user"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_new_user); } else if (check_result_value(j_user, G_OK) || check_result_value(j_user, G_ERROR_PARAM)) { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_new_user - Error register_check_username"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_user); pthread_mutex_unlock(&config->insert_lock); } return j_return; } static int register_user_set(struct _register_config * config, const char * username, json_t * j_user) { json_t * j_query; int res, ret; char * expires_at_clause = NULL; time_t now; time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } j_query = json_pack("{sss{sO}s{sssss{ssss}si}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_SESSION, "set", "gprs_name", json_object_get(j_user, "name"), "where", "gprs_plugin_name", config->name, "gprs_username", username, "gprs_expires_at", "operator", "raw", "value", expires_at_clause, "gprs_enabled", 1); o_free(expires_at_clause); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_user_set - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static int register_user_password_set(struct _register_config * config, const char * username) { json_t * j_query; int res, ret; char * expires_at_clause = NULL; time_t now; time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } j_query = json_pack("{sss{si}s{sssss{ssss}si}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_SESSION, "set", "gprs_password_set", 1, "where", "gprs_plugin_name", config->name, "gprs_username", username, "gprs_expires_at", "operator", "raw", "value", expires_at_clause, "gprs_enabled", 1); o_free(expires_at_clause); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_user_password_set - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static int register_user_complete(struct _register_config * config, const char * username) { json_t * j_query; int res, ret; char * expires_at_clause = NULL; time_t now; time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } j_query = json_pack("{sss{si}s{sssss{ssss}si}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_SESSION, "set", "gprs_enabled", 0, "where", "gprs_plugin_name", config->name, "gprs_username", username, "gprs_expires_at", "operator", "raw", "value", expires_at_clause, "gprs_enabled", 1); o_free(expires_at_clause); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_user_complete - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static int register_delete_new_user(struct _register_config * config, const char * username) { json_t * j_element = NULL; size_t index = 0; int ret = G_OK; if (register_user_complete(config, username) == G_OK) { if (config->glewlwyd_config->glewlwyd_plugin_callback_delete_user(config->glewlwyd_config, username) == G_OK) { if (json_object_get(config->j_parameters, "schemes") != NULL) { json_array_foreach(json_object_get(config->j_parameters, "schemes"), index, j_element) { if (config->glewlwyd_config->glewlwyd_plugin_callback_scheme_deregister(config->glewlwyd_config, json_string_value(json_object_get(j_element, "name")), username) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "register_delete_new_user - Error glewlwyd_plugin_callback_scheme_deregister for user %s on scheme %s/%s", username, json_string_value(json_object_get(j_element, "module")), json_string_value(json_object_get(j_element, "name"))); ret = G_ERROR; } } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_delete_new_user - Error glewlwyd_plugin_callback_delete_user"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_delete_new_user - Error register_user_complete"); ret = G_ERROR; } return ret; } static int register_update_email_trigger(struct _register_config * config, const char * username, const char * email, const char * lang, const char * issued_for, const char * user_agent, const char * ip_source) { json_t * j_query; int ret, res; char token[GLEWLWYD_TOKEN_LENGTH+1] = {0}, * token_hash = NULL, * body = NULL, * expires_at_clause; time_t now; // Disable existing sessions for the specified e-mail address time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } j_query = json_pack("{sss{si}s{sssssis{ssss}}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_UPDATE_EMAIL, "set", "gprue_enabled", 0, "where", "gprue_plugin_name", config->name, "gprue_username", username, "gprue_enabled", 1, "gprue_expires_at", "operator", "raw", "value", expires_at_clause); o_free(expires_at_clause); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { if (rand_string(token, GLEWLWYD_TOKEN_LENGTH) != NULL) { if ((token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, token)) != NULL) { if ((body = str_replace(get_template_email_update_property(config->j_parameters, lang, "body-pattern"), "{TOKEN}", token)) != NULL) { if (ulfius_send_smtp_rich_email(json_string_value(json_object_get(config->j_parameters, "host")), json_integer_value(json_object_get(config->j_parameters, "port")), json_object_get(config->j_parameters, "use-tls")==json_true()?1:0, json_object_get(config->j_parameters, "verify-certificate")==json_false()?0:1, json_string_length(json_object_get(config->j_parameters, "user"))?json_string_value(json_object_get(config->j_parameters, "user")):NULL, json_string_length(json_object_get(config->j_parameters, "password"))?json_string_value(json_object_get(config->j_parameters, "password")):NULL, json_string_value(json_object_get(config->j_parameters, "update-email-from")), email, NULL, NULL, json_string_length(json_object_get(config->j_parameters, "update-email-content-type"))?json_string_value(json_object_get(config->j_parameters, "update-email-content-type")):"text/plain; charset=utf-8", get_template_email_update_property(config->j_parameters, lang, "subject"), body) == U_OK) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Update e-mail - token sent to email %s at IP Address %s", email, ip_source); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("FROM_UNIXTIME(%u)", (now + (unsigned int)json_integer_value(json_object_get(config->j_parameters, "update-email-token-duration")))); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("TO_TIMESTAMP(%u)", (now + (unsigned int)json_integer_value(json_object_get(config->j_parameters, "update-email-token-duration")))); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("%u", (now + (unsigned int)json_integer_value(json_object_get(config->j_parameters, "update-email-token-duration")))); } j_query = json_pack("{sss{sssssssss{ss}ssss}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_UPDATE_EMAIL, "values", "gprue_plugin_name", config->name, "gprue_username", username, "gprue_email", email, "gprue_token_hash", token_hash, "gprue_expires_at", "raw", expires_at_clause, "gprue_issued_for", issued_for, "gprue_user_agent", user_agent!=NULL?user_agent:""); o_free(expires_at_clause); res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_update_email_trigger - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_update_email_trigger - Error ulfius_send_smtp_rich_email"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_update_email_trigger - Error get_template_email_update_property"); ret = G_ERROR; } o_free(body); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_update_email_trigger - Error glewlwyd_callback_generate_hash"); ret = G_ERROR; } o_free(token_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_update_email_trigger - Error rand_string"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_update_email_trigger - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static int register_update_email_verify(struct _register_config * config, const char * token, const char * ip_source) { json_t * j_query, * j_result = NULL, * j_updated_user = NULL; int res, ret; char * token_hash = NULL, * expires_at_clause = NULL; time_t now; if (o_strlen(token) == GLEWLWYD_TOKEN_LENGTH) { if ((token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, token)) != NULL) { time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } j_query = json_pack("{sss[sss]s{sssss{ssss}si}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_UPDATE_EMAIL, "columns", "gprue_id", "gprue_username AS username", "gprue_email AS email", "where", "gprue_plugin_name", config->name, "gprue_token_hash", token_hash, "gprue_expires_at", "operator", "raw", "value", expires_at_clause, "gprue_enabled", 1); o_free(expires_at_clause); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_updated_user = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, json_string_value(json_object_get(json_array_get(j_result, 0), "username"))); if (check_result_value(j_updated_user, G_OK)) { json_object_set(json_object_get(j_updated_user, "user"), "email", json_object_get(json_array_get(j_result, 0), "email")); if (config->glewlwyd_config->glewlwyd_plugin_callback_set_user(config->glewlwyd_config, json_string_value(json_object_get(json_array_get(j_result, 0), "username")), json_object_get(j_updated_user, "user")) == G_OK) { j_query = json_pack("{sss{si}s{sO}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_UPDATE_EMAIL, "set", "gprue_enabled", 0, "where", "gprue_id", json_object_get(json_array_get(j_result, 0), "gprue_id")); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; y_log_message(Y_LOG_LEVEL_INFO, "Event register - Plugin '%s' - user '%s' updated its e-mail address to '%s', origin: %s", config->name, json_string_value(json_object_get(json_array_get(j_result, 0), "username")), json_string_value(json_object_get(json_array_get(j_result, 0), "email")), ip_source); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_update_email_verify - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_update_email_verify - Error glewlwyd_plugin_callback_set_user"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_update_email_verify - Error glewlwyd_plugin_callback_get_user"); ret = G_ERROR; } json_decref(j_updated_user); } else { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Update e-mail - token invalid at IP Address %s", ip_source); ret = G_ERROR_PARAM; } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_update_email_verify - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_update_email_verify - Error generate hash"); ret = G_ERROR; } o_free(token_hash); } else { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Update e-mail - token invalid at IP Address %s", ip_source); ret = G_ERROR_PARAM; } return ret; } static json_t * reset_credentials_check_session(struct _register_config * config, const char * session) { json_t * j_query, * j_result = NULL, * j_return, * j_user; int res; char * session_hash = NULL, * expires_at_clause = NULL; time_t now; if (o_strlen(session)) { session_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, session); if (session_hash != NULL) { time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } j_query = json_pack("{sss[ss]s{sssss{ssss}si}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_RESET_CREDENTIALS_SESSION, "columns", "gprrcs_username AS username", "gprrcs_callback_url AS callback_url", "where", "gprrcs_plugin_name", config->name, "gprrcs_session_hash", session_hash, "gprrcs_expires_at", "operator", "raw", "value", expires_at_clause, "gprrcs_enabled", 1); o_free(expires_at_clause); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_user = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, json_string_value(json_object_get(json_array_get(j_result, 0), "username"))); if (check_result_value(j_user, G_OK)) { j_return = json_pack("{sisO}", "result", G_OK, "user", json_object_get(j_user, "user")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "reset_credentials_check_session - Error glewlwyd_plugin_callback_get_user"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_user); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "reset_credentials_check_session - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "reset_credentials_check_session - Error generate hash for session"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(session_hash); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } return j_return; } static int reset_credentials_remove_session(struct _register_config * config, const char * session) { json_t * j_query; int res, ret; char * session_hash = NULL; session_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, session); if (session_hash != NULL) { j_query = json_pack("{sss{si}s{ssss}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_RESET_CREDENTIALS_SESSION, "set", "gprrcs_enabled", 0, "where", "gprrcs_plugin_name", config->name, "gprrcs_session_hash", session_hash); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "reset_credentials_remove_session - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "reset_credentials_remove_session - Error generate hash for session"); ret = G_ERROR; } o_free(session_hash); return ret; } static int register_reset_credentials_trigger(struct _register_config * config, const char * username, const char * lang, const char * callback_url, const char * issued_for, const char * user_agent, const char * ip_source) { json_t * j_query, * j_user = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, username); int ret, res; char token[GLEWLWYD_TOKEN_LENGTH+1] = {0}, * token_hash = NULL, * body = NULL, * expires_at_clause; time_t now; const char * email = NULL; if (check_result_value(j_user, G_OK) && json_string_length(json_object_get(json_object_get(j_user, "user"), "email"))) { email = json_string_value(json_object_get(json_object_get(j_user, "user"), "email")); // Disable existing sessions for the specified e-mail address time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } j_query = json_pack("{sss{si}s{sssssis{ssss}}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_RESET_CREDENTIALS_EMAIL, "set", "gprrct_enabled", 0, "where", "gprrct_plugin_name", config->name, "gprrct_username", username, "gprrct_enabled", 1, "gprrct_expires_at", "operator", "raw", "value", expires_at_clause); o_free(expires_at_clause); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { if (rand_string(token, GLEWLWYD_TOKEN_LENGTH) != NULL) { if ((token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, token)) != NULL) { if ((body = str_replace(get_template_reset_credentials_property(config->j_parameters, lang, "body-pattern"), "{TOKEN}", token)) != NULL) { if (ulfius_send_smtp_rich_email(json_string_value(json_object_get(config->j_parameters, "host")), json_integer_value(json_object_get(config->j_parameters, "port")), json_object_get(config->j_parameters, "use-tls")==json_true()?1:0, json_object_get(config->j_parameters, "verify-certificate")==json_false()?0:1, json_string_length(json_object_get(config->j_parameters, "user"))?json_string_value(json_object_get(config->j_parameters, "user")):NULL, json_string_length(json_object_get(config->j_parameters, "password"))?json_string_value(json_object_get(config->j_parameters, "password")):NULL, json_string_value(json_object_get(config->j_parameters, "reset-credentials-from")), email, NULL, NULL, json_string_length(json_object_get(config->j_parameters, "reset-credentials-content-type"))?json_string_value(json_object_get(config->j_parameters, "reset-credentials-content-type")):"text/plain; charset=utf-8", get_template_reset_credentials_property(config->j_parameters, lang, "subject"), body) == U_OK) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Reset credentials - token sent to email %s at IP Address %s", email, ip_source); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("FROM_UNIXTIME(%u)", (now + (unsigned int)json_integer_value(json_object_get(config->j_parameters, "reset-credentials-token-duration")))); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("TO_TIMESTAMP(%u)", (now + (unsigned int)json_integer_value(json_object_get(config->j_parameters, "reset-credentials-token-duration")))); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("%u", (now + (unsigned int)json_integer_value(json_object_get(config->j_parameters, "reset-credentials-token-duration")))); } j_query = json_pack("{sss{ssssssss?s{ss}ssss}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_RESET_CREDENTIALS_EMAIL, "values", "gprrct_plugin_name", config->name, "gprrct_username", username, "gprrct_token_hash", token_hash, "gprrct_callback_url", callback_url, "gprrct_expires_at", "raw", expires_at_clause, "gprrct_issued_for", issued_for, "gprrct_user_agent", user_agent!=NULL?user_agent:""); o_free(expires_at_clause); res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_reset_credentials_trigger - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_reset_credentials_trigger - Error ulfius_send_smtp_rich_email"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_reset_credentials_trigger - Error get_template_email_update_property"); ret = G_ERROR; } o_free(body); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_reset_credentials_trigger - Error glewlwyd_callback_generate_hash"); ret = G_ERROR; } o_free(token_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_reset_credentials_trigger - Error rand_string"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_reset_credentials_trigger - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else if (check_result_value(j_user, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Reset credentials - user '%s' not found at IP Address %s", email, ip_source); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_reset_credentials_trigger - Error glewlwyd_plugin_callback_get_user"); ret = G_ERROR; } json_decref(j_user); return ret; } static json_t * register_reset_credentials_check_token(struct _register_config * config, const char * token) { json_t * j_return, * j_query, * j_result = NULL; int res; char * token_hash = NULL, * expires_at_clause = NULL; time_t now; if ((token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, token)) != NULL) { time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } j_query = json_pack("{sss[sss]s{sssss{ssss}si}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_RESET_CREDENTIALS_EMAIL, "columns", "gprrct_id", "gprrct_username AS username", "gprrct_callback_url AS callback_url", "where", "gprrct_plugin_name", config->name, "gprrct_token_hash", token_hash, "gprrct_expires_at", "operator", "raw", "value", expires_at_clause, "gprrct_enabled", 1); o_free(expires_at_clause); res = h_select(config->glewlwyd_config->glewlwyd_config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_query = json_pack("{sss{si}s{sO}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_RESET_CREDENTIALS_EMAIL, "set", "gprrct_enabled", 0, "where", "gprrct_id", json_object_get(json_array_get(j_result, 0), "gprrct_id")); res = h_update(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_return = json_pack("{sisO}", "result", G_OK, "username", json_object_get(json_array_get(j_result, 0), "username")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_reset_credentials_check_token - Error executing j_query (2)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_reset_credentials_check_token - Error executing j_query (1)"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_reset_credentials_check_token - Error glewlwyd_callback_generate_hash"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(token_hash); return j_return; } static json_t * reset_credentials_create_session(struct _register_config * config, const char * username, const char * callback_url, const char * issued_for, const char * user_agent) { json_t * j_return, * j_query; int res; char token[GLEWLWYD_TOKEN_LENGTH+1] = {}, * token_hash = NULL, * expires_at_clause; time_t now; if (rand_string_nonce(token, GLEWLWYD_TOKEN_LENGTH)) { if ((token_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, token)) != NULL) { time(&now); if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("FROM_UNIXTIME(%u)", (now + (unsigned int)json_integer_value(json_object_get(config->j_parameters, "reset-credentials-session-duration")))); } else if (config->glewlwyd_config->glewlwyd_config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("TO_TIMESTAMP(%u)", (now + (unsigned int)json_integer_value(json_object_get(config->j_parameters, "reset-credentials-session-duration")))); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("%u", (now + (unsigned int)json_integer_value(json_object_get(config->j_parameters, "reset-credentials-session-duration")))); } j_query = json_pack("{sss{ssssssss?s{ss}ssss}}", "table", GLEWLWYD_PLUGIN_REGISTER_TABLE_RESET_CREDENTIALS_SESSION, "values", "gprrcs_plugin_name", config->name, "gprrcs_username", username, "gprrcs_session_hash", token_hash, "gprrcs_callback_url", callback_url, "gprrcs_expires_at", "raw", expires_at_clause, "gprrcs_issued_for", issued_for, "gprrcs_user_agent", user_agent!=NULL?user_agent:""); o_free(expires_at_clause); res = h_insert(config->glewlwyd_config->glewlwyd_config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_return = json_pack("{siss}", "result", G_OK, "session", token); } else { y_log_message(Y_LOG_LEVEL_ERROR, "reset_credentials_create_session - Error executing j_query"); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "reset_credentials_create_session - Error glewlwyd_callback_generate_hash"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(token_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "reset_credentials_create_session - Error rand_string_nonce"); j_return = json_pack("{si}", "result", G_ERROR); } return j_return; } static json_t * reset_credentials_code_generate(struct _register_config * config, const char * username) { json_t * j_user = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, username), * j_return, * j_code_list; char code[GLEWLWYD_RESET_CREDENTIALS_CODE_LENGTH+1] = {}, code_formatted[GLEWLWYD_RESET_CREDENTIALS_CODE_LENGTH+(GLEWLWYD_RESET_CREDENTIALS_CODE_LENGTH/4)+1] = {}, * code_hash = NULL, * code_formatted_offset; json_int_t i; int res, j; if (check_result_value(j_user, G_OK)) { res = G_OK; if ((j_code_list = json_array()) != NULL) { if (!json_is_array(json_object_get(json_object_get(j_user, "user"), json_string_value(json_object_get(config->j_parameters, "reset-credentials-code-property"))))) { json_object_set_new(json_object_get(j_user, "user"), json_string_value(json_object_get(config->j_parameters, "reset-credentials-code-property")), json_array()); } for (i=0; res == G_OK && ij_parameters, "reset-credentials-code-list-size")); i++) { if (rand_string_from_charset(code, GLEWLWYD_RESET_CREDENTIALS_CODE_LENGTH, "abcdefghijklmnopqrstuvwxyz0123456789") != NULL) { code_formatted_offset = code_formatted; for (j=0; jglewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, code)) != NULL) { json_array_append_new(json_object_get(json_object_get(j_user, "user"), json_string_value(json_object_get(config->j_parameters, "reset-credentials-code-property"))), json_string(code_hash)); json_array_append_new(j_code_list, json_string(code_formatted)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "reset_credentials_code_generate - Error glewlwyd_callback_generate_hash"); res = G_ERROR; } o_free(code_hash); code_hash = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "reset_credentials_code_generate - Error rand_string_from_charset"); res = G_ERROR; } } if (res == G_OK) { if (config->glewlwyd_config->glewlwyd_plugin_callback_set_user(config->glewlwyd_config, username, json_object_get(j_user, "user")) == G_OK) { j_return = json_pack("{sisO}", "result", G_OK, "code", j_code_list); } else { y_log_message(Y_LOG_LEVEL_ERROR, "reset_credentials_code_generate - Error glewlwyd_plugin_callback_set_user"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "reset_credentials_code_generate - Error generating or storing code"); j_return = json_pack("{si}", "result", res); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "reset_credentials_code_generate - Error allocating resources for j_code_list"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_code_list); } else { y_log_message(Y_LOG_LEVEL_ERROR, "reset_credentials_code_generate - Error glewlwyd_plugin_callback_get_user"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_user); return j_return; } static int reset_credentials_code_verify(struct _register_config * config, const char * username, const char * code) { int ret, found = 0; json_t * j_user; char * code_deformatted = NULL, * code_deformatted_offset, * code_hash = NULL; size_t i, array_size; const char * code_property = json_string_value(json_object_get(config->j_parameters, "reset-credentials-code-property")); if (o_strlen(username) && o_strlen(code)) { j_user = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, username); if (check_result_value(j_user, G_OK) && (array_size = json_array_size(json_object_get(json_object_get(j_user, "user"), code_property))) >= (size_t)json_integer_value(json_object_get(config->j_parameters, "reset-credentials-code-list-size"))) { if ((code_deformatted = str_replace(code, "-", "")) != NULL) { if (o_strlen(code_deformatted) == GLEWLWYD_RESET_CREDENTIALS_CODE_LENGTH) { code_deformatted_offset = code_deformatted; while (*code_deformatted_offset != '\0') { *code_deformatted_offset = tolower(*code_deformatted_offset); code_deformatted_offset++; } if ((code_hash = config->glewlwyd_config->glewlwyd_callback_generate_hash(config->glewlwyd_config, code_deformatted)) != NULL) { for (i=(array_size - json_integer_value(json_object_get(config->j_parameters, "reset-credentials-code-list-size"))); !found && iglewlwyd_config->glewlwyd_plugin_callback_set_user(config->glewlwyd_config, username, json_object_get(j_user, "user")) == G_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "reset_credentials_code_verify - Error glewlwyd_plugin_callback_set_user"); ret = G_ERROR; } } else { ret = G_ERROR_UNAUTHORIZED; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "reset_credentials_code_verify - Error glewlwyd_callback_generate_hash"); ret = G_ERROR; } o_free(code_hash); } else { ret = G_ERROR_UNAUTHORIZED; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "reset_credentials_code_verify - Error str_replace"); ret = G_ERROR; } o_free(code_deformatted); } else { ret = G_ERROR_UNAUTHORIZED; } json_decref(j_user); } else { ret = G_ERROR_UNAUTHORIZED; } return ret; } static int callback_register_config(const struct _u_request * request, struct _u_response * response, void * user_data) { UNUSED(request); struct _register_config * config = (struct _register_config *)user_data; json_t * j_config, * j_template = NULL, * j_config_register; const char * lang = NULL; if (json_object_get(config->j_parameters, "registration") != json_false()) { j_config_register = json_pack("{sOsOsOsOs[]}", "set-password", json_object_get(config->j_parameters, "set-password"), "schemes", json_object_get(config->j_parameters, "schemes")!=NULL?json_object_get(config->j_parameters, "schemes"):json_null(), "verify-email", json_object_get(config->j_parameters, "verify-email")!=NULL?json_object_get(config->j_parameters, "verify-email"):json_false(), "email-is-username", json_object_get(config->j_parameters, "email-is-username")!=NULL?json_object_get(config->j_parameters, "email-is-username"):json_false(), "languages"); // Add default lang on top of the list json_object_foreach(json_object_get(config->j_parameters, "templates"), lang, j_template) { if (json_object_get(j_template, "defaultLang") == json_true()) { json_array_append_new(json_object_get(j_config_register, "languages"), json_string(lang)); } } json_object_foreach(json_object_get(config->j_parameters, "templates"), lang, j_template) { if (json_object_get(j_template, "defaultLang") != json_true()) { json_array_append_new(json_object_get(j_config_register, "languages"), json_string(lang)); } } } else { j_config_register = json_false(); } j_config = json_pack("{sosOs{sOsO}}", "registration", j_config_register, "update-email", json_object_get(config->j_parameters, "update-email")==json_true()?json_true():json_false(), "reset-credentials", "email", json_object_get(config->j_parameters, "reset-credentials")==json_true()&&json_object_get(config->j_parameters, "reset-credentials-email")==json_true()?json_true():json_false(), "code", json_object_get(config->j_parameters, "reset-credentials")==json_true()&&json_object_get(config->j_parameters, "reset-credentials-code")==json_true()?json_true():json_false()); if (ulfius_set_json_body_response(response, 200, j_config) != U_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_config - Error ulfius_set_json_body_response"); response->status = 500; } json_decref(j_config); return U_CALLBACK_CONTINUE; } static int callback_register_update_email_check_session(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; int ret = U_CALLBACK_CONTINUE; json_t * j_session = register_check_session(config, u_map_get(request->map_cookie, json_string_value(json_object_get(config->j_parameters, "session-key")))); if (check_result_value(j_session, G_OK)) { if (ulfius_set_response_shared_data(response, json_deep_copy(json_object_get(j_session, "user")), (void (*)(void *))&json_decref) != U_OK) { ret = U_CALLBACK_ERROR; } } else if (check_result_value(j_session, G_ERROR_NOT_FOUND)) { ret = U_CALLBACK_UNAUTHORIZED; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_update_email_check_session - Error register_check_session"); ret = U_CALLBACK_ERROR; } json_decref(j_session); return ret; } static int callback_register_get_data(const struct _u_request * request, struct _u_response * response, void * user_data) { UNUSED(request); UNUSED(user_data); if (ulfius_set_json_body_response(response, 200, (json_t *)response->shared_data) != U_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_get_data - Error ulfius_set_json_body_response"); response->status = 500; } return U_CALLBACK_CONTINUE; } static int callback_register_check_username(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; json_t * j_params = ulfius_get_json_body_request(request, NULL), * j_user, * j_user_reg, * j_return; if (j_params != NULL && json_string_length(json_object_get(j_params, "username"))) { j_user = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, json_string_value(json_object_get(j_params, "username"))); if (check_result_value(j_user, G_OK)) { j_return = json_pack("{ss}", "error", "username already taken"); ulfius_set_json_body_response(response, 400, j_return); json_decref(j_return); } else if (check_result_value(j_user, G_ERROR_NOT_FOUND)) { j_user_reg = register_check_username(config, json_string_value(json_object_get(j_params, "username"))); if (check_result_value(j_user_reg, G_OK)) { j_return = json_pack("{ss}", "error", "username already taken"); ulfius_set_json_body_response(response, 400, j_return); json_decref(j_return); } else if (!check_result_value(j_user_reg, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_check_username - Error register_check_username"); response->status = 500; } json_decref(j_user_reg); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_check_username - Error glewlwyd_plugin_callback_get_user"); response->status = 500; } json_decref(j_user); } else { j_return = json_pack("{ss}", "result", "username invalid"); ulfius_set_json_body_response(response, 400, j_return); json_decref(j_return); } json_decref(j_params); return U_CALLBACK_CONTINUE; } static int callback_register_register_user(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; json_t * j_result, * j_parameters = ulfius_get_json_body_request(request, NULL); char * issued_for, expires[GLEWLWYD_DATE_BUFFER+1]; time_t now; struct tm ts; time(&now); now += json_integer_value(json_object_get(config->j_parameters, "session-duration")); gmtime_r(&now, &ts); strftime(expires, GLEWLWYD_DATE_BUFFER, "%a, %d %b %Y %T %Z", &ts); if (json_object_get(config->j_parameters, "verify-email") != json_true()) { if (json_string_length(json_object_get(j_parameters, "username"))) { issued_for = get_client_hostname(request); if (issued_for != NULL) { j_result = register_new_user(config, json_string_value(json_object_get(j_parameters, "username")), issued_for, u_map_get_case(request->map_header, "user-agent")); if (check_result_value(j_result, G_OK)) { ulfius_add_cookie_to_response(response, json_string_value(json_object_get(config->j_parameters, "session-key")), json_string_value(json_object_get(j_result, "session")), expires, 0, config->glewlwyd_config->glewlwyd_config->cookie_domain, "/", config->glewlwyd_config->glewlwyd_config->cookie_secure, 0); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_REGISTRATION_STARTED, 1, "plugin", config->name, NULL); } else if (check_result_value(j_result, G_ERROR_PARAM)) { response->status = 400; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_register_user - Error register_new_user"); response->status = 500; } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_register_user - Error get_client_hostname"); response->status = 500; } o_free(issued_for); } else { response->status = 400; } } else { response->status = 403; } json_decref(j_parameters); return U_CALLBACK_CONTINUE; } static int callback_register_send_email_verification(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; json_t * j_result, * j_parameters = ulfius_get_json_body_request(request, NULL); const char * username, * email; char * issued_for; if (json_object_get(config->j_parameters, "verify-email") == json_true()) { email = json_string_value(json_object_get(j_parameters, "email")); if (json_object_get(config->j_parameters, "email-is-username") == json_true()) { username = json_string_value(json_object_get(j_parameters, "email")); } else { username = json_string_value(json_object_get(j_parameters, "username")); } if (o_strlen(email) && o_strlen(username)) { issued_for = get_client_hostname(request); if (issued_for != NULL) { j_result = register_generate_email_verification_code(config, username, email, json_string_value(json_object_get(j_parameters, "lang")), json_string_value(json_object_get(j_parameters, "callback_url")), issued_for, u_map_get_case(request->map_header, "user-agent"), get_ip_source(request)); if (check_result_value(j_result, G_ERROR_PARAM)) { response->status = 400; } else if(!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_register_user - Error register_new_user"); response->status = 500; } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_register_user - Error get_client_hostname"); response->status = 500; } o_free(issued_for); } else { response->status = 400; } } else { response->status = 403; } json_decref(j_parameters); return U_CALLBACK_CONTINUE; } static int callback_register_check_email(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; json_t * j_parameters = ulfius_get_json_body_request(request, NULL), * j_result; const char * username, * email; char expires[GLEWLWYD_DATE_BUFFER+1]; time_t now; struct tm ts; if (json_object_get(config->j_parameters, "verify-email") == json_true()) { email = json_string_value(json_object_get(j_parameters, "email")); if (json_object_get(config->j_parameters, "email-is-username") == json_true()) { username = json_string_value(json_object_get(j_parameters, "email")); } else { username = json_string_value(json_object_get(j_parameters, "username")); } if (json_string_length(json_object_get(j_parameters, "token")) == GLEWLWYD_TOKEN_LENGTH) { j_result = register_verify_email_token(config, json_string_value(json_object_get(j_parameters, "token")), get_ip_source(request)); if (check_result_value(j_result, G_OK)) { time(&now); now += json_integer_value(json_object_get(config->j_parameters, "session-duration")); gmtime_r(&now, &ts); strftime(expires, GLEWLWYD_DATE_BUFFER, "%a, %d %b %Y %T %Z", &ts); ulfius_add_cookie_to_response(response, json_string_value(json_object_get(config->j_parameters, "session-key")), json_string_value(json_object_get(j_result, "session")), expires, 0, config->glewlwyd_config->glewlwyd_config->cookie_domain, "/", config->glewlwyd_config->glewlwyd_config->cookie_secure, 0); } else if (check_result_value(j_result, G_ERROR_PARAM)) { response->status = 401; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_check_email - Error register_verify_email_token"); response->status = 500; } json_decref(j_result); } else if (o_strlen(email) && o_strlen(username)) { if ((json_int_t)json_string_length(json_object_get(j_parameters, "code")) == json_integer_value(json_object_get(config->j_parameters, "verification-code-length"))) { j_result = register_verify_email_code(config, username, email, json_string_value(json_object_get(j_parameters, "code")), get_ip_source(request)); if (check_result_value(j_result, G_OK)) { time(&now); now += json_integer_value(json_object_get(config->j_parameters, "session-duration")); gmtime_r(&now, &ts); strftime(expires, GLEWLWYD_DATE_BUFFER, "%a, %d %b %Y %T %Z", &ts); ulfius_add_cookie_to_response(response, json_string_value(json_object_get(config->j_parameters, "session-key")), json_string_value(json_object_get(j_result, "session")), expires, 0, config->glewlwyd_config->glewlwyd_config->cookie_domain, "/", config->glewlwyd_config->glewlwyd_config->cookie_secure, 0); } else if (check_result_value(j_result, G_ERROR_PARAM)) { response->status = 401; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_check_email - Error register_verify_email_code"); response->status = 500; } json_decref(j_result); } else { response->status = 401; } } else { response->status = 403; } } else { response->status = 403; } json_decref(j_parameters); return U_CALLBACK_CONTINUE; } static int callback_register_update_password(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; json_t * j_parameters = ulfius_get_json_body_request(request, NULL); if (0 != o_strcmp("no", json_string_value(json_object_get(config->j_parameters, "set-password")))) { if (json_string_length(json_object_get(j_parameters, "password"))) { if (config->glewlwyd_config->glewlwyd_plugin_callback_user_update_password(config->glewlwyd_config, json_string_value(json_object_get((json_t *)response->shared_data, "username")), json_string_value(json_object_get(j_parameters, "password"))) == G_OK) { if (register_user_password_set(config, json_string_value(json_object_get((json_t *)response->shared_data, "username"))) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_update_password - Error register_user_password_set"); response->status = 500; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_update_password - Error glewlwyd_plugin_callback_user_update_password"); response->status = 500; } } else { response->status = 400; } } else { response->status = 403; } json_decref(j_parameters); return U_CALLBACK_CONTINUE; } static int callback_register_update_data(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; json_t * j_parameters = ulfius_get_json_body_request(request, NULL), * j_user = NULL; if (json_is_string(json_object_get(j_parameters, "name")) || json_object_get(j_parameters, "name") == json_null()) { j_user = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, json_string_value(json_object_get((json_t *)response->shared_data, "username"))); if (check_result_value(j_user, G_OK)) { json_object_set_new(json_object_get(j_user, "user"), "name", json_is_string(json_object_get(j_parameters, "name"))?json_incref(json_object_get(j_parameters, "name")):json_string("")); if (config->glewlwyd_config->glewlwyd_plugin_callback_set_user(config->glewlwyd_config, json_string_value(json_object_get((json_t *)response->shared_data, "username")), json_object_get(j_user, "user")) == G_OK) { if (register_user_set(config, json_string_value(json_object_get((json_t *)response->shared_data, "username")), json_object_get(j_user, "user")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_update_data - Error register_user_set"); response->status = 500; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_update_data - Error glewlwyd_plugin_callback_set_user"); response->status = 500; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_update_data - Error glewlwyd_plugin_callback_get_user"); response->status = 500; } json_decref(j_user); } else { response->status = 400; } json_decref(j_parameters); return U_CALLBACK_CONTINUE; } static int callback_register_cancel(const struct _u_request * request, struct _u_response * response, void * user_data) { UNUSED(request); struct _register_config * config = (struct _register_config *)user_data; if (register_delete_new_user(config, json_string_value(json_object_get((json_t *)response->shared_data, "username"))) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_cancel - Error register_delete_new_user"); response->status = 500; } else { ulfius_add_cookie_to_response(response, json_string_value(json_object_get(config->j_parameters, "session-key")), "", 0, 0, config->glewlwyd_config->glewlwyd_config->cookie_domain, "/", config->glewlwyd_config->glewlwyd_config->cookie_secure, 0); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_REGISTRATION_CANCELLED, 1, "plugin", config->name, NULL); } return U_CALLBACK_CONTINUE; } static int callback_register_get_scheme_registration(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; json_t * j_parameters = ulfius_get_json_body_request(request, NULL), * j_response; if (json_string_length(json_object_get(j_parameters, "scheme_name")) && json_string_length(json_object_get(j_parameters, "username")) && 0 == o_strcmp(json_string_value(json_object_get(j_parameters, "username")), json_string_value(json_object_get((json_t *)response->shared_data, "username"))) && can_register_scheme(config, request, json_string_value(json_object_get(j_parameters, "scheme_name")))) { j_response = config->glewlwyd_config->glewlwyd_plugin_callback_scheme_register_get(config->glewlwyd_config, json_string_value(json_object_get(j_parameters, "scheme_name")), request, json_string_value(json_object_get((json_t *)response->shared_data, "username"))); if (check_result_value(j_response, G_OK)) { if (json_object_get(j_response, "response") != NULL) { ulfius_set_json_body_response(response, 200, json_object_get(j_response, "response")); } else { response->status = 200; } } else if (check_result_value(j_response, G_ERROR_PARAM)) { response->status = 400; } else if (check_result_value(j_response, G_ERROR_UNAUTHORIZED)) { response->status = 401; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_get_scheme_registration - Error glewlwyd_plugin_callback_scheme_register_get"); response->status = 500; } json_decref(j_response); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "callback_register_get_scheme_registration - Invalid input parameters"); response->status = 400; } json_decref(j_parameters); return U_CALLBACK_CONTINUE; } static int callback_register_check_forbid_reset_credential(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; json_t * j_parameters = ulfius_get_json_body_request(request, NULL), * j_result = config->glewlwyd_config->glewlwyd_plugin_callback_get_scheme_module(config->glewlwyd_config, json_string_value(json_object_get(j_parameters, "scheme_name"))); int ret = U_CALLBACK_CONTINUE; if (check_result_value(j_result, G_OK)) { if (json_object_get(json_object_get(j_result, "module"), "forbid_user_reset_credential") == json_true()) { response->status = 403; ret = U_CALLBACK_COMPLETE; } } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_check_forbid_reset_credential - Error auth_register_get_user_scheme"); response->status = 500; } json_decref(j_parameters); json_decref(j_result); return ret; } static int callback_register_update_scheme_registration(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; json_t * j_parameters = ulfius_get_json_body_request(request, NULL), * j_response; if (json_string_length(json_object_get(j_parameters, "scheme_name")) && json_is_object(json_object_get(j_parameters, "value")) && json_string_length(json_object_get(j_parameters, "username")) && 0 == o_strcmp(json_string_value(json_object_get(j_parameters, "username")), json_string_value(json_object_get((json_t *)response->shared_data, "username"))) && can_register_scheme(config, request, json_string_value(json_object_get(j_parameters, "scheme_name")))) { j_response = config->glewlwyd_config->glewlwyd_plugin_callback_scheme_register(config->glewlwyd_config, json_string_value(json_object_get(j_parameters, "scheme_name")), request, json_string_value(json_object_get((json_t *)response->shared_data, "username")), json_object_get(j_parameters, "value")); if (check_result_value(j_response, G_ERROR_PARAM)) { if (json_object_get(j_response, "response") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_response, "response")); } else { ulfius_set_string_body_response(response, 400, "bad scheme response"); } } else if (check_result_value(j_response, G_ERROR_NOT_FOUND)) { response->status = 404; } else if (check_result_value(j_response, G_ERROR_UNAUTHORIZED)) { response->status = 401; } else if (check_result_value(j_response, G_OK)) { if (json_object_get(j_response, "response") != NULL) { ulfius_set_json_body_response(response, 200, json_object_get(j_response, "response")); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_auth_register - Error auth_check_user_scheme"); response->status = 500; } json_decref(j_response); } else { response->status = 400; } json_decref(j_parameters); return U_CALLBACK_CONTINUE; } static int callback_register_canuse_scheme_registration(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; json_t * j_parameters = ulfius_get_json_body_request(request, NULL); int ret; if (json_string_length(json_object_get(j_parameters, "scheme_name")) && json_string_length(json_object_get(j_parameters, "username")) && 0 == o_strcmp(json_string_value(json_object_get(j_parameters, "username")), json_string_value(json_object_get((json_t *)response->shared_data, "username"))) && can_register_scheme(config, request, json_string_value(json_object_get(j_parameters, "scheme_name")))) { ret = config->glewlwyd_config->glewlwyd_plugin_callback_scheme_can_use(config->glewlwyd_config, json_string_value(json_object_get(j_parameters, "scheme_name")), json_string_value(json_object_get((json_t *)response->shared_data, "username"))); if (ret == GLEWLWYD_IS_NOT_AVAILABLE) { response->status = 403; } else if (ret == GLEWLWYD_IS_AVAILABLE) { response->status = 402; } else if (ret != GLEWLWYD_IS_REGISTERED) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_canuse_scheme_registration - Error glewlwyd_plugin_callback_scheme_can_use"); response->status = 500; } } else { response->status = 400; } json_decref(j_parameters); return U_CALLBACK_CONTINUE; } static int callback_register_complete_registration(const struct _u_request * request, struct _u_response * response, void * user_data) { UNUSED(request); struct _register_config * config = (struct _register_config *)user_data; json_t * j_user, * j_element = NULL, * j_error = json_array(); size_t index = 0; char * message; if (j_error != NULL) { // Does the user need to set password? if (0 == o_strcmp("always", json_string_value(json_object_get(config->j_parameters, "set-password")))) { if (json_object_get((json_t *)response->shared_data, "password_set") != json_true()) { json_array_append_new(j_error, json_string("Password not set")); } } // Has the user registered all its required schemes? json_array_foreach(json_object_get(config->j_parameters, "schemes"), index, j_element) { if (0 == o_strcmp("always", json_string_value(json_object_get(j_element, "register")))) { if (config->glewlwyd_config->glewlwyd_plugin_callback_scheme_can_use(config->glewlwyd_config, json_string_value(json_object_get(j_element, "name")), json_string_value(json_object_get((json_t *)response->shared_data, "username"))) != GLEWLWYD_IS_REGISTERED) { message = msprintf("Scheme '%s' not registered", json_string_value(json_object_get(j_element, "name"))); json_array_append_new(j_error, json_string(message)); o_free(message); } } } if (!json_array_size(j_error)) { j_user = config->glewlwyd_config->glewlwyd_plugin_callback_get_user(config->glewlwyd_config, json_string_value(json_object_get((json_t *)response->shared_data, "username"))); if (check_result_value(j_user, G_OK)) { json_object_set(json_object_get(j_user, "user"), "enabled", json_true()); if (config->glewlwyd_config->glewlwyd_plugin_callback_set_user(config->glewlwyd_config, json_string_value(json_object_get((json_t *)response->shared_data, "username")), json_object_get(j_user, "user")) == G_OK) { if (register_user_complete(config, json_string_value(json_object_get((json_t *)response->shared_data, "username"))) == G_OK) { ulfius_add_cookie_to_response(response, json_string_value(json_object_get(config->j_parameters, "session-key")), "", 0, 0, config->glewlwyd_config->glewlwyd_config->cookie_domain, "/", config->glewlwyd_config->glewlwyd_config->cookie_secure, 0); y_log_message(Y_LOG_LEVEL_INFO, "Event register - Plugin '%s' - user '%s' registered, origin: %s", config->name, json_string_value(json_object_get((json_t *)response->shared_data, "username")), get_ip_source(request)); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_REGISTRATION_COMPLETED, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_complete_registration - Error register_user_set"); response->status = 500; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_complete_registration - Error glewlwyd_plugin_callback_set_user"); response->status = 500; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_complete_registration - Error glewlwyd_plugin_callback_get_user"); response->status = 500; } json_decref(j_user); } else { ulfius_set_json_body_response(response, 400, j_error); } json_decref(j_error); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_complete_registration - Error allocating resources for j_error"); response->status = 500; } return U_CALLBACK_CONTINUE; } /** * verify that the http request is authorized based on the session */ static int callback_check_glewlwyd_session(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; json_t * j_session; int ret = U_CALLBACK_UNAUTHORIZED; j_session = config->glewlwyd_config->glewlwyd_callback_check_session_valid(config->glewlwyd_config, request, NULL); if (check_result_value(j_session, G_OK)) { if (ulfius_set_response_shared_data(response, json_pack("{ss}", "username", json_string_value(json_object_get(json_object_get(json_object_get(j_session, "session"), "user"), "username"))), (void (*)(void *))&json_decref) != U_OK) { ret = U_CALLBACK_ERROR; } else { ret = U_CALLBACK_CONTINUE; } } json_decref(j_session); return ret; } static int callback_register_update_email_trigger(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; json_t * j_parameters = ulfius_get_json_body_request(request, NULL); char * issued_for = NULL; if (json_string_length(json_object_get(j_parameters, "email"))) { issued_for = get_client_hostname(request); if (issued_for != NULL) { if (register_update_email_trigger(config, json_string_value(json_object_get((json_t *)response->shared_data, "username")), json_string_value(json_object_get(j_parameters, "email")), json_string_value(json_object_get(j_parameters, "lang")), issued_for, u_map_get_case(request->map_header, "user-agent"), get_ip_source(request)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_update_email_trigger - Error register_update_email_trigger"); response->status = 500; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_update_email_trigger - Error get_client_hostname"); response->status = 500; } o_free(issued_for); } else { response->status = 400; } json_decref(j_parameters); return U_CALLBACK_CONTINUE; } static int callback_register_update_email_verify(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; int ret; if ((ret = register_update_email_verify(config, u_map_get_case(request->map_url, "token"), get_ip_source(request))) == G_ERROR_PARAM) { response->status = 400; } else if (ret != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_update_email_verify - Error register_update_email_verify"); response->status = 500; } else { config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_EMAIL_UPDATED, 1, "plugin", config->name, NULL); } return U_CALLBACK_CONTINUE; } static int callback_register_reset_credentials_check_session(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; int ret = U_CALLBACK_CONTINUE; json_t * j_session = reset_credentials_check_session(config, u_map_get(request->map_cookie, json_string_value(json_object_get(config->j_parameters, "reset-credentials-session-key")))); if (check_result_value(j_session, G_OK)) { if (ulfius_set_response_shared_data(response, json_deep_copy(json_object_get(j_session, "user")), (void (*)(void *))&json_decref) != U_OK) { ret = U_CALLBACK_ERROR; } } else if (check_result_value(j_session, G_ERROR_NOT_FOUND)) { ret = U_CALLBACK_UNAUTHORIZED; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_reset_credentials_check_session - Error reset_credentials_check_session"); ret = U_CALLBACK_ERROR; } json_decref(j_session); return ret; } static int callback_reset_credentials_update_password(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; json_t * j_parameters = ulfius_get_json_body_request(request, NULL); if (json_string_length(json_object_get(j_parameters, "password"))) { if (config->glewlwyd_config->glewlwyd_plugin_callback_user_update_password(config->glewlwyd_config, json_string_value(json_object_get((json_t *)response->shared_data, "username")), json_string_value(json_object_get(j_parameters, "password"))) == G_OK) { if (register_user_password_set(config, json_string_value(json_object_get((json_t *)response->shared_data, "username"))) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_reset_credentials_update_password - Error register_user_password_set"); response->status = 500; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_reset_credentials_update_password - Error glewlwyd_plugin_callback_user_update_password"); response->status = 500; } } else { response->status = 400; } json_decref(j_parameters); return U_CALLBACK_CONTINUE; } static int callback_register_get_profile(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; json_t * j_result = config->glewlwyd_config->glewlwyd_plugin_callback_get_scheme_list(config->glewlwyd_config, json_string_value(json_object_get((json_t *)response->shared_data, "username"))), * j_return; UNUSED(request); if (check_result_value(j_result, G_OK)) { j_return = json_pack("{s{ss}sO}", "user", "username", json_string_value(json_object_get((json_t *)response->shared_data, "username")), "scheme", json_object_get(j_result, "scheme")); ulfius_set_response_properties(response, U_OPT_JSON_BODY, j_return); json_decref(j_return); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_get_profile - Error glewlwyd_plugin_callback_get_scheme_list"); response->status = 500; } json_decref(j_result); return U_CALLBACK_CONTINUE; } static int callback_reset_credentials_complete_registration(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; int res; char expires[GLEWLWYD_DATE_BUFFER+1]; time_t now; struct tm ts; UNUSED(request); if ((res = reset_credentials_remove_session(config, u_map_get(request->map_cookie, json_string_value(json_object_get(config->j_parameters, "reset-credentials-session-key"))))) == G_OK) { time(&now); now -= 3600; gmtime_r(&now, &ts); strftime(expires, GLEWLWYD_DATE_BUFFER, "%a, %d %b %Y %T %Z", &ts); ulfius_add_cookie_to_response(response, json_string_value(json_object_get(config->j_parameters, "reset-credentials-session-key")), "disabled", expires, 0, config->glewlwyd_config->glewlwyd_config->cookie_domain, "/", config->glewlwyd_config->glewlwyd_config->cookie_secure, 0); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_RESET_CREDENTIALS_COMPLETED, 1, "plugin", config->name, NULL); } else if (res == G_ERROR_PARAM) { response->status = 400; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_reset_credentials_complete_registration - Error reset_credentials_remove_session"); response->status = 500; } return U_CALLBACK_CONTINUE; } static int callback_register_reset_credentials_email_trigger(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; json_t * j_parameters = ulfius_get_json_body_request(request, NULL); char * issued_for = NULL; if (json_string_length(json_object_get(j_parameters, "username"))) { issued_for = get_client_hostname(request); if (issued_for != NULL) { if (register_reset_credentials_trigger(config, json_string_value(json_object_get(j_parameters, "username")), json_string_value(json_object_get(j_parameters, "lang")), json_string_value(json_object_get(j_parameters, "callback_url")), issued_for, u_map_get_case(request->map_header, "user-agent"), get_ip_source(request)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_reset_credentials_email_trigger - Error register_reset_credentials_trigger"); response->status = 500; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_reset_credentials_email_trigger - Error get_client_hostname"); response->status = 500; } o_free(issued_for); } else { response->status = 400; } json_decref(j_parameters); return U_CALLBACK_CONTINUE; } static int callback_register_reset_credentials_email_verify(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; json_t * j_result = register_reset_credentials_check_token(config, u_map_get(request->map_url, "token")), * j_session; time_t now; struct tm ts; char expires[GLEWLWYD_DATE_BUFFER+1], * issued_for; if (check_result_value(j_result, G_OK)) { issued_for = get_client_hostname(request); j_session = reset_credentials_create_session(config, json_string_value(json_object_get(j_result, "username")), json_string_value(json_object_get(j_result, "callback_url")), issued_for, u_map_get_case(request->map_header, "user-agent")); if (check_result_value(j_session, G_OK)) { time(&now); now += json_integer_value(json_object_get(config->j_parameters, "reset-credentials-session-duration")); gmtime_r(&now, &ts); strftime(expires, GLEWLWYD_DATE_BUFFER, "%a, %d %b %Y %T %Z", &ts); ulfius_add_cookie_to_response(response, json_string_value(json_object_get(config->j_parameters, "reset-credentials-session-key")), json_string_value(json_object_get(j_session, "session")), expires, 0, config->glewlwyd_config->glewlwyd_config->cookie_domain, "/", config->glewlwyd_config->glewlwyd_config->cookie_secure, 0); y_log_message(Y_LOG_LEVEL_INFO, "Event register - Plugin '%s' - user '%s' opened a reset credential session with e-mail token, origin: %s", config->name, json_string_value(json_object_get(j_result, "username")), get_ip_source(request)); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_RESET_CREDENTIALS_STARTED, 1, "plugin", config->name, "verification", "email", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_RESET_CREDENTIALS_STARTED, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_reset_credentials_email_verify - Error reset_credentials_create_session"); response->status = 500; } json_decref(j_session); o_free(issued_for); } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Reset credentials - token invalid at IP Address %s", get_ip_source(request)); response->status = 403; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_reset_credentials_email_verify - Error register_reset_credentials_check_token"); response->status = 500; } json_decref(j_result); return U_CALLBACK_CONTINUE; } static int callback_register_reset_credentials_code_verify(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; json_t * j_parameters = ulfius_get_json_body_request(request, NULL), * j_session; time_t now; struct tm ts; char expires[GLEWLWYD_DATE_BUFFER+1], * issued_for; int res; if ((res = reset_credentials_code_verify(config, json_string_value(json_object_get(j_parameters, "username")), json_string_value(json_object_get(j_parameters, "code")))) == G_OK) { issued_for = get_client_hostname(request); j_session = reset_credentials_create_session(config, json_string_value(json_object_get(j_parameters, "username")), NULL, issued_for, u_map_get_case(request->map_header, "user-agent")); if (check_result_value(j_session, G_OK)) { time(&now); now += json_integer_value(json_object_get(config->j_parameters, "reset-credentials-session-duration")); gmtime_r(&now, &ts); strftime(expires, GLEWLWYD_DATE_BUFFER, "%a, %d %b %Y %T %Z", &ts); ulfius_add_cookie_to_response(response, json_string_value(json_object_get(config->j_parameters, "reset-credentials-session-key")), json_string_value(json_object_get(j_session, "session")), expires, 0, config->glewlwyd_config->glewlwyd_config->cookie_domain, "/", config->glewlwyd_config->glewlwyd_config->cookie_secure, 0); y_log_message(Y_LOG_LEVEL_INFO, "Event register - Plugin '%s' - user '%s' opened a reset credential session with code, origin: %s", config->name, json_string_value(json_object_get(j_parameters, "username")), get_ip_source(request)); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_RESET_CREDENTIALS_STARTED, 1, "plugin", config->name, "verification", "code", NULL); config->glewlwyd_config->glewlwyd_plugin_callback_metrics_increment_counter(config->glewlwyd_config, GLWD_METRICS_RESET_CREDENTIALS_STARTED, 1, "plugin", config->name, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_reset_credentials_code_verify - Error reset_credentials_create_session"); response->status = 500; } json_decref(j_session); o_free(issued_for); } else if (res == G_ERROR_UNAUTHORIZED) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Reset credentials - code invalid at IP Address %s", get_ip_source(request)); response->status = 403; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_reset_credentials_code_verify - Error reset_credentials_code_verify"); response->status = 500; } json_decref(j_parameters); return U_CALLBACK_CONTINUE; } static int callback_register_reset_credentials_code_generate(const struct _u_request * request, struct _u_response * response, void * user_data) { struct _register_config * config = (struct _register_config *)user_data; json_t * j_result = reset_credentials_code_generate(config, json_string_value(json_object_get((json_t *)response->shared_data, "username"))); UNUSED(request); if (check_result_value(j_result, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_result, "code")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_register_reset_credentials_code_generate - Error reset_credentials_code_generate"); response->status = 500; } json_decref(j_result); return U_CALLBACK_CONTINUE; } json_t * is_plugin_parameters_valid(json_t * j_params) { json_t * j_return, * j_errors = json_array(), * j_element = NULL, * j_template = NULL; size_t index = 0, has_mandatory = 0; const char * lang = NULL; int nb_default_lang = 0; if (j_errors != NULL) { if (!json_is_object(j_params)) { json_array_append_new(j_errors, json_string("parameters must be a JSON object")); } else { if (json_object_get(j_params, "registration") == json_true() || json_object_get(j_params, "registration") == NULL) { if (!json_string_length(json_object_get(j_params, "session-key"))) { json_array_append_new(j_errors, json_string("session-key is mandatory and must be a non empty string")); } if (json_integer_value(json_object_get(j_params, "session-duration")) <= 0) { json_array_append_new(j_errors, json_string("session-duration is optional and must be a positive integer")); } if (json_object_get(j_params, "verify-email") != NULL && !json_is_boolean(json_object_get(j_params, "verify-email"))) { json_array_append_new(j_errors, json_string("verify-email is optional and must be boolean")); } if (json_object_get(j_params, "email-is-username") != NULL && !json_is_boolean(json_object_get(j_params, "email-is-username"))) { json_array_append_new(j_errors, json_string("email-is-username is optional and must be boolean")); } if (0 != o_strcmp(json_string_value(json_object_get(j_params, "set-password")), "always") && 0 != o_strcmp(json_string_value(json_object_get(j_params, "set-password")), "yes") && 0 != o_strcmp(json_string_value(json_object_get(j_params, "set-password")), "no")) { json_array_append_new(j_errors, json_string("set-password is mandatory and must have one of the following string values: 'always', 'yes', 'no'")); } else if (0 == o_strcmp(json_string_value(json_object_get(j_params, "set-password")), "always")) { has_mandatory = 1; } if (!json_is_array(json_object_get(j_params, "scope")) || !json_array_size(json_object_get(j_params, "scope"))) { json_array_append_new(j_errors, json_string("scope is mandatory and must be a non empty array of non empty strings")); } else { json_array_foreach(json_object_get(j_params, "scope"), index, j_element) { if (!json_string_length(j_element)) { json_array_append_new(j_errors, json_string("scope is mandatory and must be a non empty array of non empty strings")); } } } if (json_object_get(j_params, "schemes") != NULL && !json_is_array(json_object_get(j_params, "schemes"))) { json_array_append_new(j_errors, json_string("schemes is optional and must be an array of objects")); } else { json_array_foreach(json_object_get(j_params, "schemes"), index, j_element) { if (!json_string_length(json_object_get(j_element, "module"))) { json_array_append_new(j_errors, json_string("scheme object must have a attribute 'module' with a non empty string value")); } if (!json_string_length(json_object_get(j_element, "name"))) { json_array_append_new(j_errors, json_string("scheme object must have a attribute 'name' with a non empty string value")); } if (0 != o_strcmp("always", json_string_value(json_object_get(j_element, "register"))) && 0 != o_strcmp("yes", json_string_value(json_object_get(j_element, "register")))) { json_array_append_new(j_errors, json_string("scheme object must have a attribute 'register' with one of the following values: 'yes', 'always'")); } if (0 == o_strcmp("always", json_string_value(json_object_get(j_element, "register")))) { has_mandatory = 1; } } } if (!has_mandatory) { json_array_append_new(j_errors, json_string("At least one authentication method must be mandatory")); } } if (json_object_get(j_params, "update-email") != NULL && !json_is_boolean(json_object_get(j_params, "update-email"))) { json_array_append_new(j_errors, json_string("update-email is optional and must be a boolean")); } if (json_object_get(j_params, "update-email") == json_true()) { if (json_integer_value(json_object_get(j_params, "update-email-token-duration")) <= 0) { json_array_append_new(j_errors, json_string("update-email-token-duration is mandatory and must be a positive integer")); } if (json_object_get(j_params, "update-email-from") != NULL && !json_string_length(json_object_get(j_params, "update-email-from"))) { json_array_append_new(j_errors, json_string("update-email-from is mandatory and must be a non empty string")); } if (json_object_get(j_params, "update-email-content-type") != NULL && !json_string_length(json_object_get(j_params, "update-email-content-type"))) { json_array_append_new(j_errors, json_string("update-email-content-type is optional and must be a string")); } if (!json_is_object(json_object_get(j_params, "templatesUpdateEmail"))) { json_array_append_new(j_errors, json_string("templatesUpdateEmail is mandatory and must be a JSON object")); } else { nb_default_lang = 0; json_object_foreach(json_object_get(j_params, "templatesUpdateEmail"), lang, j_template) { if (!json_is_object(j_template)) { json_array_append_new(j_errors, json_string("templatesUpdateEmail content must be a JSON object")); } else { if (!json_is_boolean(json_object_get(j_template, "defaultLang"))) { json_array_append_new(j_errors, json_string("defaultLang is madatory in a templatesUpdateEmail and must be a JSON object")); } if (json_object_get(j_template, "defaultLang") == json_true()) { nb_default_lang++; if (!json_string_length(json_object_get(j_template, "subject"))) { json_array_append_new(j_errors, json_string("subject is mandatory for default lang and must be a non empty string")); } if (json_object_get(j_template, "body") != NULL && !json_string_length(json_object_get(j_template, "body"))) { json_array_append_new(j_errors, json_string("body is mandatory for default lang and must be a non empty string")); } } } } if (nb_default_lang != 1) { json_array_append_new(j_errors, json_string("templatesUpdateEmail list must have only one defaultLang set to true")); } } } if (json_object_get(j_params, "verify-email") == json_true()) { if (json_integer_value(json_object_get(j_params, "verification-code-duration")) <= 0) { json_array_append_new(j_errors, json_string("verification-code-duration is mandatory and must be a positive integer")); } if (json_integer_value(json_object_get(j_params, "verification-code-length")) <= 0) { json_array_append_new(j_errors, json_string("verification-code-length is mandatory and must be a positive integer")); } if (!json_is_boolean(json_object_get(j_params, "email-is-username"))) { json_array_append_new(j_errors, json_string("email-is-username is optional and must be boolean")); } if (json_object_get(j_params, "content-type") != NULL && !json_string_length(json_object_get(j_params, "content-type"))) { json_array_append_new(j_errors, json_string("content-type is optional and must be a string")); } if (json_object_get(j_params, "from") != NULL && !json_string_length(json_object_get(j_params, "from"))) { json_array_append_new(j_errors, json_string("from is mandatory and must be a non empty string")); } if (json_object_get(j_params, "templates") == NULL) { if (json_object_get(j_params, "subject") != NULL && !json_string_length(json_object_get(j_params, "subject"))) { json_array_append_new(j_errors, json_string("subject is mandatory and must be a non empty string")); } if (json_object_get(j_params, "body-pattern") != NULL && !json_string_length(json_object_get(j_params, "body-pattern"))) { json_array_append_new(j_errors, json_string("body-pattern is mandatory and must be a non empty string")); } } else { if (!json_is_object(json_object_get(j_params, "templates"))) { json_array_append_new(j_errors, json_string("templates is mandatory and must be a JSON object")); } else { nb_default_lang = 0; json_object_foreach(json_object_get(j_params, "templates"), lang, j_template) { if (!json_is_object(j_template)) { json_array_append_new(j_errors, json_string("template content must be a JSON object")); } else { if (!json_is_boolean(json_object_get(j_template, "defaultLang"))) { json_array_append_new(j_errors, json_string("defaultLang is madatory in a template and must be a JSON object")); } if (json_object_get(j_template, "defaultLang") == json_true()) { nb_default_lang++; if (!json_string_length(json_object_get(j_template, "subject"))) { json_array_append_new(j_errors, json_string("subject is mandatory for default lang and must be a non empty string")); } if (json_object_get(j_template, "body") != NULL && !json_string_length(json_object_get(j_template, "body"))) { json_array_append_new(j_errors, json_string("body is mandatory for default lang and must be a non empty string")); } } } } if (nb_default_lang != 1) { json_array_append_new(j_errors, json_string("template list must have only one defaultLang set to true")); } } } } if (json_object_get(j_params, "reset-credentials") != NULL && !json_is_boolean(json_object_get(j_params, "reset-credentials"))) { json_array_append_new(j_errors, json_string("reset-credentials is optional and must be a boolean")); } if (json_object_get(j_params, "reset-credentials") == json_true()) { if (!json_string_length(json_object_get(j_params, "reset-credentials-session-key"))) { json_array_append_new(j_errors, json_string("reset-credentials-session-key is mandatory and must be a non empty string")); } if (json_integer_value(json_object_get(j_params, "reset-credentials-session-duration")) <= 0) { json_array_append_new(j_errors, json_string("reset-credentials-session-duration is optional and must be a positive integer")); } if (json_object_get(j_params, "reset-credentials-email") != NULL && !json_is_boolean(json_object_get(j_params, "reset-credentials-email"))) { json_array_append_new(j_errors, json_string("reset-credentials-email is optional and must be a boolean")); } if (json_object_get(j_params, "reset-credentials-email") == json_true()) { if (json_integer_value(json_object_get(j_params, "reset-credentials-token-duration")) <= 0) { json_array_append_new(j_errors, json_string("reset-credentials-token-duration is mandatory and must be a positive integer")); } if (json_object_get(j_params, "reset-credentials-from") != NULL && !json_string_length(json_object_get(j_params, "reset-credentials-from"))) { json_array_append_new(j_errors, json_string("reset-credentials-from is mandatory and must be a non empty string")); } if (json_object_get(j_params, "reset-credentials-content-type") != NULL && !json_string_length(json_object_get(j_params, "reset-credentials-content-type"))) { json_array_append_new(j_errors, json_string("reset-credentials-content-type is optional and must be a string")); } if (!json_is_object(json_object_get(j_params, "templatesResetCredentials"))) { json_array_append_new(j_errors, json_string("templatesResetCredentials is mandatory and must be a JSON object")); } else { nb_default_lang = 0; json_object_foreach(json_object_get(j_params, "templatesResetCredentials"), lang, j_template) { if (!json_is_object(j_template)) { json_array_append_new(j_errors, json_string("templatesResetCredentials content must be a JSON object")); } else { if (!json_is_boolean(json_object_get(j_template, "defaultLang"))) { json_array_append_new(j_errors, json_string("defaultLang is madatory in a templatesResetCredentials and must be a JSON object")); } if (json_object_get(j_template, "defaultLang") == json_true()) { nb_default_lang++; if (!json_string_length(json_object_get(j_template, "subject"))) { json_array_append_new(j_errors, json_string("subject is mandatory for default lang and must be a non empty string")); } if (json_object_get(j_template, "body") != NULL && !json_string_length(json_object_get(j_template, "body"))) { json_array_append_new(j_errors, json_string("body is mandatory for default lang and must be a non empty string")); } } } } if (nb_default_lang != 1) { json_array_append_new(j_errors, json_string("templatesUpdateEmail list must have only one defaultLang set to true")); } } } if (json_object_get(j_params, "reset-credentials-code") != NULL && !json_is_boolean(json_object_get(j_params, "reset-credentials-code"))) { json_array_append_new(j_errors, json_string("reset-credentials-code is optional and must be a boolean")); } if (json_object_get(j_params, "reset-credentials-code") == json_true()) { if (!json_string_length(json_object_get(j_params, "reset-credentials-code-property"))) { json_array_append_new(j_errors, json_string("reset-credentials-code-property is mandatory and must be a non empty string")); } if (json_integer_value(json_object_get(j_params, "reset-credentials-code-list-size")) <= 0) { json_array_append_new(j_errors, json_string("reset-credentials-code-list-size is optional and must be a positive integer")); } } if (json_object_get(j_params, "reset-credentials-email") != json_true() && json_object_get(j_params, "reset-credentials-code") != json_true()) { json_array_append_new(j_errors, json_string("At least one reset-credentials action must be enabled")); } } if (json_object_get(j_params, "update-email") == json_true() || json_object_get(j_params, "verify-email") == json_true() || (json_object_get(j_params, "reset-credentials") == json_true() && json_object_get(j_params, "reset-credentials-email") == json_true())) { if (!json_string_length(json_object_get(j_params, "host"))) { json_array_append_new(j_errors, json_string("host is mandatory and must be a non empty string")); } if (json_object_get(j_params, "port") != NULL && (!json_is_integer(json_object_get(j_params, "port")) || json_integer_value(json_object_get(j_params, "port")) < 0 || json_integer_value(json_object_get(j_params, "port")) > 65535)) { json_array_append_new(j_errors, json_string("port is optional and must be a integer between 0 and 65535")); } else if (json_object_get(j_params, "port") == NULL) { json_object_set_new(j_params, "port", json_integer(0)); } if (json_object_get(j_params, "use-tls") != NULL && !json_is_boolean(json_object_get(j_params, "use-tls"))) { json_array_append_new(j_errors, json_string("use-tls is optional and must be a boolean")); } if (json_object_get(j_params, "check-certificate") != NULL && !json_is_boolean(json_object_get(j_params, "check-certificate"))) { json_array_append_new(j_errors, json_string("check-certificate is optional and must be a boolean")); } if (json_object_get(j_params, "user") != NULL && !json_is_string(json_object_get(j_params, "user"))) { json_array_append_new(j_errors, json_string("user is optional and must be a string")); } if (json_object_get(j_params, "password") != NULL && !json_is_string(json_object_get(j_params, "password"))) { json_array_append_new(j_errors, json_string("password is optional and must be a string")); } } if (json_object_get(j_params, "update-email") != json_true() && json_object_get(j_params, "reset-credentials") != json_true() && json_object_get(j_params, "registration") == json_false()) { json_array_append_new(j_errors, json_string("At least one action must be enabled")); } } if (json_array_size(j_errors)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_errors); } else { j_return = json_pack("{si}", "result", G_OK); } json_decref(j_errors); } else { y_log_message(Y_LOG_LEVEL_ERROR, "plugin register - is_parameter_valid error allocating resources for j_errors"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } return j_return; } json_t * plugin_module_load(struct config_plugin * config) { UNUSED(config); return json_pack("{sissssss}", "result", G_OK, "name", "register", "display_name", "Register/Update e-mail/Reset credentials plugin", "description", "Adds self registered users in the user backend"); } int plugin_module_unload(struct config_plugin * config) { UNUSED(config); return G_OK; } json_t * plugin_module_init(struct config_plugin * config, const char * name, json_t * j_parameters, void ** cls) { json_t * j_return, * j_result; struct _register_config * register_config; pthread_mutexattr_t mutexattr; int registration_ok = 1, update_email_ok = 1, reset_credentials_ok = 1; y_log_message(Y_LOG_LEVEL_INFO, "Init plugin Glewlwyd register '%s'", name); j_result = is_plugin_parameters_valid(j_parameters); if (check_result_value(j_result, G_OK)) { register_config = o_malloc(sizeof(struct _register_config)); if (register_config != NULL) { pthread_mutexattr_init ( &mutexattr ); pthread_mutexattr_settype( &mutexattr, PTHREAD_MUTEX_RECURSIVE ); if (!pthread_mutex_init(®ister_config->insert_lock, &mutexattr)) { register_config->glewlwyd_config = config; register_config->name = o_strdup(name); register_config->j_parameters = json_incref(j_parameters); *cls = (void*)register_config; if (config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, "config", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_config, (void*)register_config) == G_OK) { if (json_object_get(j_parameters, "registration") == json_true() || json_object_get(j_parameters, "registration") == NULL) { y_log_message(Y_LOG_LEVEL_INFO, "Add registration endpoints with plugin prefix %s", name); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_REGISTRATION_STARTED, "Total number of registration started"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_REGISTRATION_COMPLETED, "Total number of registration completed"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_REGISTRATION_CANCELLED, "Total number of registration cancelled"); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_REGISTRATION_STARTED, 0, "plugin", name, NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_REGISTRATION_COMPLETED, 0, "plugin", name, NULL); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_REGISTRATION_CANCELLED, 0, "plugin", name, NULL); if (config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "username", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_check_username, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "register", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_register_user, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "PUT", name, "verify", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_send_email_verification, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "verify", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_check_email, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "*", name, "profile/*", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_register_update_email_check_session, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "profile/password", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_update_password, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, "profile/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_get_data, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "PUT", name, "profile/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_update_data, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "DELETE", name, "profile/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_cancel, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "PUT", name, "profile/scheme/register", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_get_scheme_registration, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "profile/scheme/register", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_update_scheme_registration, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "PUT", name, "profile/scheme/register/canuse", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_canuse_scheme_registration, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "profile/complete", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_complete_registration, (void*)register_config) == G_OK) { } else { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init register - Error glewlwyd_callback_add_plugin_endpoint"); registration_ok = 0; } } if (json_object_get(j_parameters, "update-email") == json_true()) { config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_EMAIL_UPDATED, "Total number of e-mails updated"); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_EMAIL_UPDATED, 0, "plugin", name, NULL); if (config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "update-email", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_check_glewlwyd_session, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "update-email", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_update_email_trigger, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "PUT", name, "update-email/:token", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_update_email_verify, (void*)register_config) == G_OK) { } else { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init update-email - Error glewlwyd_callback_add_plugin_endpoint"); update_email_ok = 0; } } if (json_object_get(j_parameters, "reset-credentials") == json_true()) { config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_RESET_CREDENTIALS_STARTED, "Total number of reset credentials started"); config->glewlwyd_plugin_callback_metrics_add_metric(config, GLWD_METRICS_RESET_CREDENTIALS_COMPLETED, "Total number of reset credentials completed"); config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_RESET_CREDENTIALS_COMPLETED, 0, "plugin", name, NULL); if (config->glewlwyd_callback_add_plugin_endpoint(config, "*", name, "reset-credentials/profile/*", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_register_reset_credentials_check_session, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "reset-credentials/profile/password", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_reset_credentials_update_password, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "GET", name, "reset-credentials/profile/", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_get_profile, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "*", name, "reset-credentials/profile/scheme/register/*", GLEWLWYD_CALLBACK_PRIORITY_PRE_APPLICATION, &callback_register_check_forbid_reset_credential, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "PUT", name, "reset-credentials/profile/scheme/register", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_get_scheme_registration, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "reset-credentials/profile/scheme/register", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_update_scheme_registration, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "PUT", name, "reset-credentials/profile/scheme/register/canuse", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_canuse_scheme_registration, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "reset-credentials/profile/complete", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_reset_credentials_complete_registration, (void*)register_config) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init reset-credentials - Error glewlwyd_callback_add_plugin_endpoint"); reset_credentials_ok = 0; } if (json_object_get(j_parameters, "reset-credentials-email") == json_true()) { config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_RESET_CREDENTIALS_STARTED, 0, "plugin", name, "verification", "email", NULL); if (config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "reset-credentials-email", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_reset_credentials_email_trigger, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "PUT", name, "reset-credentials-email/:token", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_reset_credentials_email_verify, (void*)register_config) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init reset-credentials-email - Error glewlwyd_callback_add_plugin_endpoint"); reset_credentials_ok = 0; } } if (json_object_get(j_parameters, "reset-credentials-code") == json_true()) { config->glewlwyd_plugin_callback_metrics_increment_counter(config, GLWD_METRICS_RESET_CREDENTIALS_STARTED, 0, "plugin", name, "verification", "code", NULL); if (config->glewlwyd_callback_add_plugin_endpoint(config, "POST", name, "reset-credentials-code", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_reset_credentials_code_verify, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "PUT", name, "reset-credentials-code", GLEWLWYD_CALLBACK_PRIORITY_AUTHENTICATION, &callback_check_glewlwyd_session, (void*)register_config) == G_OK && config->glewlwyd_callback_add_plugin_endpoint(config, "PUT", name, "reset-credentials-code", GLEWLWYD_CALLBACK_PRIORITY_APPLICATION, &callback_register_reset_credentials_code_generate, (void*)register_config) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init reset-credentials-code - Error glewlwyd_callback_add_plugin_endpoint"); reset_credentials_ok = 0; } } } if (registration_ok && update_email_ok && reset_credentials_ok) { j_return = json_pack("{si}", "result", G_OK); } else { j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init register - Error setting config endpoint"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init register - Error pthread_mutex_init"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init register - Error allocating resources for register_config"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "plugin_module_init register - Error input parameters"); j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", json_object_get(j_result, "error")); } json_decref(j_result); return j_return; } int plugin_module_close(struct config_plugin * config, const char * name, void * cls) { y_log_message(Y_LOG_LEVEL_INFO, "Close plugin Glewlwyd register '%s'", name); if (cls != NULL) { config->glewlwyd_callback_remove_plugin_endpoint(config, "GET", name, "config"); if (json_object_get(((struct _register_config *)cls)->j_parameters, "registration") == json_true() || json_object_get(((struct _register_config *)cls)->j_parameters, "registration") == NULL) { config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "username"); config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "register"); config->glewlwyd_callback_remove_plugin_endpoint(config, "PUT", name, "verify"); config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "verify"); config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "profile/password"); config->glewlwyd_callback_remove_plugin_endpoint(config, "GET", name, "profile"); config->glewlwyd_callback_remove_plugin_endpoint(config, "PUT", name, "profile"); config->glewlwyd_callback_remove_plugin_endpoint(config, "DELETE", name, "profile"); config->glewlwyd_callback_remove_plugin_endpoint(config, "*", name, "profile/*"); config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "profile/scheme/register"); config->glewlwyd_callback_remove_plugin_endpoint(config, "PUT", name, "profile/scheme/register"); config->glewlwyd_callback_remove_plugin_endpoint(config, "PUT", name, "profile/scheme/register/canuse"); config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "profile/complete"); } if (json_object_get(((struct _register_config *)cls)->j_parameters, "update-email") == json_true()) { config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "update-email"); config->glewlwyd_callback_remove_plugin_endpoint(config, "PUT", name, "update-email/:token"); } if (json_object_get(((struct _register_config *)cls)->j_parameters, "reset-credentials") == json_true()) { config->glewlwyd_callback_remove_plugin_endpoint(config, "*", name, "reset-credentials/profile/scheme/register/*"); config->glewlwyd_callback_remove_plugin_endpoint(config, "*", name, "reset-credentials/profile/*"); config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "reset-credentials/profile/password"); config->glewlwyd_callback_remove_plugin_endpoint(config, "GET", name, "reset-credentials/profile/"); config->glewlwyd_callback_remove_plugin_endpoint(config, "PUT", name, "reset-credentials/profile/scheme/register"); config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "reset-credentials/profile/scheme/register"); config->glewlwyd_callback_remove_plugin_endpoint(config, "PUT", name, "reset-credentials/profile/scheme/register/canuse"); config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "reset-credentials/profile/complete"); if (json_object_get(((struct _register_config *)cls)->j_parameters, "reset-credentials-email") == json_true()) { config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "reset-credentials-email"); config->glewlwyd_callback_remove_plugin_endpoint(config, "PUT", name, "reset-credentials-email/:token"); } if (json_object_get(((struct _register_config *)cls)->j_parameters, "reset-credentials-code") == json_true()) { config->glewlwyd_callback_remove_plugin_endpoint(config, "POST", name, "reset-credentials-code"); config->glewlwyd_callback_remove_plugin_endpoint(config, "PUT", name, "reset-credentials-code"); } } o_free(((struct _register_config *)cls)->name); pthread_mutex_destroy(&((struct _register_config *)cls)->insert_lock); json_decref(((struct _register_config *)cls)->j_parameters); ((struct _register_config *)cls)->j_parameters = NULL; ((struct _register_config *)cls)->name = NULL; o_free(cls); } return G_OK; } int plugin_user_revoke(struct config_plugin * config, const char * username, void * cls) { UNUSED(config); UNUSED(username); UNUSED(cls); return G_OK; } glewlwyd-2.6.1/src/plugin/register.mariadb.sql000066400000000000000000000047531415646314000214230ustar00rootroot00000000000000DROP TABLE IF EXISTS gpr_reset_credentials_email; DROP TABLE IF EXISTS gpr_reset_credentials_session; DROP TABLE IF EXISTS gpr_update_email; DROP TABLE IF EXISTS gpr_session; CREATE TABLE gpr_session ( gprs_id INT(11) PRIMARY KEY AUTO_INCREMENT, gprs_plugin_name VARCHAR(256) NOT NULL, gprs_username VARCHAR(256) NOT NULL, gprs_name VARCHAR(512), gprs_email VARCHAR(512), gprs_code_hash VARCHAR(512), gprs_callback_url BLOB DEFAULT NULL, gprs_password_set TINYINT(1) DEFAULT 0, gprs_session_hash VARCHAR(512), gprs_token_hash VARCHAR(512), gprs_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprs_issued_for VARCHAR(256), -- IP address or hostname gprs_user_agent VARCHAR(256), gprs_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gprs_session_hash ON gpr_session(gprs_session_hash); CREATE INDEX i_gprs_gprs_token_hash ON gpr_session(gprs_token_hash); CREATE INDEX i_gprs_gprs_gprs_code_hash ON gpr_session(gprs_code_hash); CREATE TABLE gpr_update_email ( gprue_id INT(11) PRIMARY KEY AUTO_INCREMENT, gprue_plugin_name VARCHAR(256) NOT NULL, gprue_username VARCHAR(256) NOT NULL, gprue_email VARCHAR(512), gprue_token_hash VARCHAR(512), gprue_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprue_issued_for VARCHAR(256), -- IP address or hostname gprue_user_agent VARCHAR(256), gprue_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gprue_token_hash ON gpr_update_email(gprue_token_hash); CREATE TABLE gpr_reset_credentials_session ( gprrcs_id INT(11) PRIMARY KEY AUTO_INCREMENT, gprrcs_plugin_name VARCHAR(256) NOT NULL, gprrcs_username VARCHAR(256) NOT NULL, gprrcs_session_hash VARCHAR(512), gprrcs_callback_url BLOB DEFAULT NULL, gprrcs_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprrcs_issued_for VARCHAR(256), -- IP address or hostname gprrcs_user_agent VARCHAR(256), gprrcs_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gprrcs_session_hash ON gpr_reset_credentials_session(gprrcs_session_hash); CREATE TABLE gpr_reset_credentials_email ( gprrct_id INT(11) PRIMARY KEY AUTO_INCREMENT, gprrct_plugin_name VARCHAR(256) NOT NULL, gprrct_username VARCHAR(256) NOT NULL, gprrct_token_hash VARCHAR(512), gprrct_callback_url BLOB DEFAULT NULL, gprrct_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprrct_issued_for VARCHAR(256), -- IP address or hostname gprrct_user_agent VARCHAR(256), gprrct_enabled TINYINT(1) DEFAULT 1 ); CREATE INDEX i_gprrct_token_hash ON gpr_reset_credentials_email(gprrct_token_hash); glewlwyd-2.6.1/src/plugin/register.postgresql.sql000066400000000000000000000046511415646314000222240ustar00rootroot00000000000000DROP TABLE IF EXISTS gpr_reset_credentials_email; DROP TABLE IF EXISTS gpr_reset_credentials_session; DROP TABLE IF EXISTS gpr_update_email; DROP TABLE IF EXISTS gpr_session; CREATE TABLE gpr_session ( gprs_id SERIAL PRIMARY KEY, gprs_plugin_name VARCHAR(256) NOT NULL, gprs_username VARCHAR(256) NOT NULL, gprs_name VARCHAR(512), gprs_email VARCHAR(512), gprs_code_hash VARCHAR(512), gprs_callback_url TEXT DEFAULT NULL, gprs_password_set SMALLINT DEFAULT 0, gprs_session_hash VARCHAR(512), gprs_token_hash VARCHAR(512), gprs_expires_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, gprs_issued_for VARCHAR(256), -- IP address or hostname gprs_user_agent VARCHAR(256), gprs_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gprs_session_hash ON gpr_session(gprs_session_hash); CREATE INDEX i_gprs_gprs_token_hash ON gpr_session(gprs_token_hash); CREATE INDEX i_gprs_gprs_gprs_code_hash ON gpr_session(gprs_code_hash); CREATE TABLE gpr_update_email ( gprue_id SERIAL PRIMARY KEY, gprue_plugin_name VARCHAR(256) NOT NULL, gprue_username VARCHAR(256) NOT NULL, gprue_email VARCHAR(512), gprue_token_hash VARCHAR(512), gprue_expires_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, gprue_issued_for VARCHAR(256), -- IP address or hostname gprue_user_agent VARCHAR(256), gprue_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gprue_token_hash ON gpr_update_email(gprue_token_hash); CREATE TABLE gpr_reset_credentials_session ( gprrcs_id SERIAL PRIMARY KEY, gprrcs_plugin_name VARCHAR(256) NOT NULL, gprrcs_username VARCHAR(256) NOT NULL, gprrcs_session_hash VARCHAR(512), gprrcs_callback_url TEXT DEFAULT NULL, gprrcs_expires_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, gprrcs_issued_for VARCHAR(256), -- IP address or hostname gprrcs_user_agent VARCHAR(256), gprrcs_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gprrcs_session_hash ON gpr_reset_credentials_session(gprrcs_session_hash); CREATE TABLE gpr_reset_credentials_email ( gprrct_id SERIAL PRIMARY KEY, gprrct_plugin_name VARCHAR(256) NOT NULL, gprrct_username VARCHAR(256) NOT NULL, gprrct_token_hash VARCHAR(512), gprrct_callback_url TEXT DEFAULT NULL, gprrct_expires_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, gprrct_issued_for VARCHAR(256), -- IP address or hostname gprrct_user_agent VARCHAR(256), gprrct_enabled SMALLINT DEFAULT 1 ); CREATE INDEX i_gprrct_token_hash ON gpr_reset_credentials_email(gprrct_token_hash); glewlwyd-2.6.1/src/plugin/register.sqlite3.sql000066400000000000000000000044201415646314000213770ustar00rootroot00000000000000DROP TABLE IF EXISTS gpr_reset_credentials_email; DROP TABLE IF EXISTS gpr_reset_credentials_session; DROP TABLE IF EXISTS gpr_update_email; DROP TABLE IF EXISTS gpr_session; CREATE TABLE gpr_session ( gprs_id INTEGER PRIMARY KEY AUTOINCREMENT, gprs_plugin_name TEXT NOT NULL, gprs_username TEXT NOT NULL, gprs_name TEXT, gprs_email TEXT, gprs_code_hash TEXT, gprs_callback_url TEXT DEFAULT NULL, gprs_password_set INTEGER DEFAULT 0, gprs_session_hash TEXT, gprs_token_hash TEXT, gprs_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprs_issued_for TEXT, -- IP address or hostname gprs_user_agent TEXT, gprs_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gprs_session_hash ON gpr_session(gprs_session_hash); CREATE INDEX i_gprs_gprs_token_hash ON gpr_session(gprs_token_hash); CREATE INDEX i_gprs_gprs_gprs_code_hash ON gpr_session(gprs_code_hash); CREATE TABLE gpr_update_email ( gprue_id INTEGER PRIMARY KEY AUTOINCREMENT, gprue_plugin_name TEXT NOT NULL, gprue_username TEXT NOT NULL, gprue_email TEXT, gprue_token_hash TEXT, gprue_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprue_issued_for TEXT, -- IP address or hostname gprue_user_agent TEXT, gprue_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gprue_token_hash ON gpr_update_email(gprue_token_hash); CREATE TABLE gpr_reset_credentials_session ( gprrcs_id INTEGER PRIMARY KEY AUTOINCREMENT, gprrcs_plugin_name TEXT NOT NULL, gprrcs_username TEXT NOT NULL, gprrcs_session_hash TEXT, gprrcs_callback_url TEXT DEFAULT NULL, gprrcs_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprrcs_issued_for TEXT, -- IP address or hostname gprrcs_user_agent TEXT, gprrcs_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gprrcs_session_hash ON gpr_reset_credentials_session(gprrcs_session_hash); CREATE TABLE gpr_reset_credentials_email ( gprrct_id INTEGER PRIMARY KEY AUTOINCREMENT, gprrct_plugin_name TEXT NOT NULL, gprrct_username TEXT NOT NULL, gprrct_token_hash TEXT, gprrct_callback_url TEXT DEFAULT NULL, gprrct_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gprrct_issued_for TEXT, -- IP address or hostname gprrct_user_agent TEXT, gprrct_enabled INTEGER DEFAULT 1 ); CREATE INDEX i_gprrct_token_hash ON gpr_reset_credentials_email(gprrct_token_hash); glewlwyd-2.6.1/src/plugin/test-oauth2.html000066400000000000000000000340541415646314000205220ustar00rootroot00000000000000 Glewlwyd test page
glewlwyd-2.6.1/src/plugin/test-oidc.html000066400000000000000000000713221415646314000202350ustar00rootroot00000000000000 Glewlwyd test page
glewlwyd-2.6.1/src/scheme/000077500000000000000000000000001415646314000154155ustar00rootroot00000000000000glewlwyd-2.6.1/src/scheme/Makefile000066400000000000000000000056631415646314000170670ustar00rootroot00000000000000# # Glewlwyd authentication scheme # # Makefile used to build the software # # Copyright 2016-2019 Nicolas Mora # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; # version 2.1 of the License. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this library. If not, see . # GLWD_SRC=.. DESTDIR=/usr/local MODULES_TARGET=$(DESTDIR)/lib/glewlwyd/scheme CC=gcc CFLAGS=-c -fPIC -Wall -Werror -Wextra -Wno-unknown-pragmas -D_REENTRANT -Wno-pragmas -I$(GLWD_SRC) $(shell pkg-config --cflags liborcania) $(shell pkg-config --cflags libyder) $(shell pkg-config --cflags jansson) $(shell pkg-config --cflags libhoel) $(shell pkg-config --cflags librhonabwy) $(shell pkg-config --cflags libiddawc) $(shell pkg-config --cflags libcbor) $(ADDITIONALFLAGS) LIBS=$(shell pkg-config --libs liborcania) $(shell pkg-config --libs libyder) $(shell pkg-config --libs libhoel) $(shell pkg-config --libs jansson) -ldl TARGET=libmodemail.so libmodwebauthn.so libmodotp.so libmodpassword.so libmodcertificate.so libmodhttp.so libmodoauth2.so all: release %.o: %.c $(GLWD_SRC)/glewlwyd.h $(CC) $(CFLAGS) $(CPPFLAGS) $< misc.o: $(GLWD_SRC)/misc.c $(GLWD_SRC)/glewlwyd-common.h $(CC) $(CFLAGS) $(CPPFLAGS) $(GLWD_SRC)/misc.c libmodmock.so: mock.o misc.o $(CC) -shared -Wl,-soname,libmodmock.so -o libmodmock.so mock.o misc.o $(LIBS) libmodemail.so: email.o misc.o $(CC) -shared -Wl,-soname,libmodemail.so -o libmodemail.so email.o misc.o $(LIBS) libmodwebauthn.so: webauthn.o misc.o $(CC) -shared -Wl,-soname,libmodwebauthn.so -o libmodwebauthn.so webauthn.o misc.o $(LIBS) $(shell pkg-config --libs libcbor) $(shell pkg-config --libs librhonabwy) -lldap libmodotp.so: otp.o misc.o $(CC) -shared -Wl,-soname,libmodotp.so -o libmodotp.so otp.o misc.o $(LIBS) $(shell pkg-config --libs liboath) libmodpassword.so: password.o misc.o $(CC) -shared -Wl,-soname,libmodpassword.so -o libmodpassword.so password.o misc.o $(LIBS) libmodcertificate.so: certificate.o misc.o $(CC) -shared -Wl,-soname,libmodcertificate.so -o libmodcertificate.so certificate.o misc.o $(LIBS) libmodhttp.so: http.o misc.o $(CC) -shared -Wl,-soname,libmodhttp.so -o libmodhttp.so http.o misc.o $(LIBS) libmodoauth2.so: oauth2.o misc.o $(CC) -shared -Wl,-soname,libmodoauth2.so -o libmodoauth2.so oauth2.o misc.o $(LIBS) $(shell pkg-config --libs libiddawc) clean: rm -f *.o *.so debug: ADDITIONALFLAGS=-DDEBUG -g -O0 debug: libmodmock.so $(TARGET) release: ADDITIONALFLAGS=-O3 release: $(TARGET) install: mkdir -p $(MODULES_TARGET) cp -f *.so $(MODULES_TARGET) glewlwyd-2.6.1/src/scheme/README.md000066400000000000000000000315051415646314000167000ustar00rootroot00000000000000# Glewlwyd Authentication Scheme Modules A Glewlwyd module is built as a library and loaded at startup. It must contain a specific set of functions available to glewlwyd to work properly. A Glewlwyd module can access the entire data and functions available to Glewlwyd service. There is no limitation to its access. Therefore, Glewlwyd modules must be carefully designed and considered friendly. All data returned as `json_t *` or `char *` must be dynamically allocated, because they will be cleaned up by Glewlwyd after use. An authentication scheme module is independent from the user backends. The scheme module can use the user attributes to get or update data for its own purpose, or it can use a dedicated data storage. Currently, the following schemes are available: - [Random code sent by e-mail](email.c) - [HOTP/TOTP](otp.c) - [WebAuthn](webauthn.c) - [Short session password](password.c) - [TLS Certificate](certificate.c) A Glewlwyd module requires the library [Jansson](https://github.com/akheron/Jansson). You can check out the existing modules for inspiration. You can also start from the fake module [mock.c](mock.c) to build your own. A pointer of `struct config_module` is passed to all the mandatory functions. This pointer gives access to some Glewlwyd data and some callback functions used to achieve specific actions. The definition of the structure is the following: ```C struct config_module { /* External url to access to the Glewlwyd instance */ const char * external_url; /* relative url to access to the login page */ const char * login_url; /* value of the admin scope */ const char * admin_scope; /* Value of the profile scope */ const char * profile_scope; /* connection to the database via hoel library */ struct _h_connection * conn; /* Digest agorithm defined in the configuration file */ digest_algorithm hash_algorithm; /* General configuration of the Glewlwyd instance */ struct config_elements * glewlwyd_config; /* Callback function to retrieve a specific user */ json_t * (* glewlwyd_module_callback_get_user)(struct config_module * config, const char * username); /* Callback function to update a specific user */ int (* glewlwyd_module_callback_set_user)(struct config_module * config, const char * username, json_t * j_user); /* Callback function to validate a user password */ int (* glewlwyd_module_callback_check_user_password)(struct config_module * config, const char * username, const char * password); /* Callback function to validate a session */ json_t * (* glewlwyd_module_callback_check_user_session)(struct config_module * config, const struct _u_request * request, const char * username); }; ``` A authentication scheme module must have the following functions defined and available: ```C /** * * user_auth_scheme_module_load * * Executed once when Glewlwyd service is started * Used to identify the module and to show its parameters on init * You can also use it to load resources that are required once for all * instance modules for example * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * name: string, mandatory, name of the module, must be unique among other scheme modules * display_name: string, optional, long name of the module * description: string, optional, description for the module * parameters: object, optional, parameters description for the module * } * * Example: * { * result: G_OK, * name: "mock", * display_name: "Mock scheme module", * description: "Mock scheme module for glewlwyd tests", * parameters: { * mock-value: { * type: "string", * mandatory: true * } * } * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ json_t * user_auth_scheme_module_load(struct config_module * config); ``` ```C /** * * user_auth_scheme_module_unload * * Executed once when Glewlwyd service is stopped * You can also use it to release resources that are required once for all * instance modules for example * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ int user_auth_scheme_module_unload(struct config_module * config); ``` ```C /** * * user_auth_scheme_module_init * * Initialize an instance of this module declared in Glewlwyd service. * If required, you must dynamically allocate a pointer to the configuration * for this instance and pass it to *cls * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, G_ERROR_PARAM on input parameters error, another value on error) * error: array of strings containg the list of input errors, mandatory on result G_ERROR_PARAM, ignored otherwise * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_parameters: used to initialize an instance in JSON format * The module must validate itself its parameters * @parameter mod_name: module name in glewlwyd service * @parameter cls: will contain an allocated void * pointer that will be sent back * as void * in all module functions * */ json_t * user_auth_scheme_module_init(struct config_module * config, json_t * j_parameters, const char * mod_name, void ** cls); ``` ```C /** * * user_auth_scheme_module_close * * Close an instance of this module declared in Glewlwyd service. * You must free the memory previously allocated in * the user_auth_scheme_module_init function as void * cls * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_close(struct config_module * config, void * cls); ``` ```C /** * * user_auth_scheme_module_can_use * * Validate if the user is allowed to use this scheme prior to the * authentication or registration * * @return value: GLEWLWYD_IS_REGISTERED - User can use scheme and has registered * GLEWLWYD_IS_AVAILABLE - User can use scheme but hasn't registered * GLEWLWYD_IS_NOT_AVAILABLE - User can't use scheme * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_can_use(struct config_module * config, const char * username, void * cls); ``` ```C /** * * user_auth_scheme_module_register * * Register the scheme for a user * Ex: add a certificate, add new TOTP values, etc. * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the HTTP API * @parameter username: username to identify the user * @parameter j_scheme_data: additional data used to register the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_register(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls); ``` ```C /** * * user_auth_scheme_module_deregister * * Deregister the scheme for a user * Ex: remove certificates, TOTP values, etc. * * @return value: G_OK on success, even if no data has been removed * G_ERROR on another error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_deregister(struct config_module * config, const char * username, void * cls); ``` ```C /** * * user_auth_scheme_module_register_get * * Get the registration value(s) of the scheme for a user * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_register_get(struct config_module * config, const struct _u_request * http_request, const char * username, void * cls); ``` ```C /** * * user_auth_scheme_module_trigger * * Trigger the scheme for a user * Ex: send the code to a device, generate a challenge, etc. * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter scheme_trigger: data sent to trigger the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_trigger(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_trigger, void * cls); ``` ```C /** * * user_auth_scheme_module_validate * * Validate the scheme for a user * Ex: check the code sent to a device, verify the challenge, etc. * * @return value: G_OK on success * G_ERROR_UNAUTHORIZED if validation fails * G_ERROR_PARAM if error in parameters * G_ERROR on another error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter j_scheme_data: data sent to validate the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_validate(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls); ``` ```C /** * * user_auth_scheme_module_identify * * Identify the user using the scheme without the username to be previously given * This functionality isn't available for all schemes, because the scheme authentification * must be triggered without username and the authentication result must contain the username * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * username: string value of the user identified - if the function is called within /auth * response: JSON object, optional - if the function is called within /auth/scheme/trigger * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter j_scheme_data: data sent to validate the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_identify(struct config_module * config, const struct _u_request * http_request, json_t * j_scheme_data, void * cls); ``` glewlwyd-2.6.1/src/scheme/certificate.c000066400000000000000000002471761415646314000200640ustar00rootroot00000000000000/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * TLS client certificate authentication scheme module * * Copyright 2019-2020 Nicolas Mora * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include "glewlwyd-common.h" #define GLEWLWYD_SCHEME_CERTIFICATE_TABLE_USER_CERTIFICATE "gs_user_certificate" #define G_CERT_SOURCE_TLS 0x01 #define G_CERT_SOURCE_HEADER 0x10 int user_auth_scheme_module_validate(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls); static int add_user_certificate_scheme_storage(struct config_module * config, json_t * j_parameters, const char * x509_data, const char * username, const char * user_agent); static json_t * parse_certificate(const char * x509_data, int der_format); static json_t * get_user_certificate_from_id_scheme_storage(struct config_module * config, json_t * j_parameters, const char * username, const char * cert_id); /** * * How-To generate a cert chain with cient certificates * * OpenSSL * ======= * * Root cert/key * openssl genrsa -out root.key 4096 * openssl req -x509 -new -nodes -key root.key -sha256 -days 1024 -out root.crt * * Client cert/key/pfx * openssl genrsa -out client.key 4096 * openssl req -new -key client.key -out client.csr * openssl x509 -req -in client.csr -CA root.crt -CAkey root.key -CAcreateserial -out client.crt -days 500 -sha256 * openssl pkcs12 -export -out client.pfx -inkey client.key -in client.crt * * GnuTLS * ====== * * Root cert/key * certtool --generate-privkey --outfile root.key --bits=4096 * certtool --generate-request --load-privkey root.key --outfile root.csr * certtool --generate-self-signed --load-privkey root.key --outfile root.crt * * Client cert/key/pfx * certtool --generate-privkey --outfile client.key --bits=4096 * certtool --generate-request --load-privkey client.key --outfile client.csr * certtool --generate-certificate --load-request client.csr --load-ca-certificate root.crt --load-ca-privkey root.key --outfile client.crt * certtool --load-certificate client.crt --load-privkey client.key --to-p12 --outder --outfile client.pfx * */ struct _cert_chain_element { gnutls_x509_crt_t cert; char * dn; struct _cert_chain_element * issuer_cert; char * issuer_dn; }; struct _cert_param { json_t * j_parameters; size_t cert_array_len; struct _cert_chain_element ** cert_array; ushort cert_source; pthread_mutex_t cert_request_lock; }; static int get_certificate_id(gnutls_x509_crt_t cert, unsigned char * cert_id, size_t * cert_id_len) { int ret; unsigned char cert_digest[64]; size_t cert_digest_len = 64; gnutls_datum_t dat; dat.data = NULL; if (gnutls_x509_crt_export2(cert, GNUTLS_X509_FMT_DER, &dat) >= 0) { if (gnutls_fingerprint(GNUTLS_DIG_SHA256, &dat, cert_digest, &cert_digest_len) == GNUTLS_E_SUCCESS) { if (o_base64_encode(cert_digest, cert_digest_len, cert_id, cert_id_len)) { cert_id[*cert_id_len] = '\0'; ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_certificate_id - Error o_base64_encode"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_certificate_id - Error gnutls_fingerprint"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_certificate_id - Error gnutls_x509_crt_export2"); ret = G_ERROR; } gnutls_free(dat.data); return ret; } static json_t * parse_certificate(const char * x509_data, int der_format) { json_t * j_return; gnutls_x509_crt_t cert = NULL; gnutls_datum_t cert_dat; char * dn = NULL, * issuer_dn = NULL; size_t key_id_enc_len = 256, dn_len = 0, issuer_dn_len = 0; time_t expires_at = 0, issued_at = 0; int ret; unsigned char * der_dec = NULL, key_id_enc[257] = {0}; size_t der_dec_len = 0; if (o_strlen(x509_data)) { if (!gnutls_x509_crt_init(&cert)) { if (der_format) { cert_dat.data = NULL; cert_dat.size = 0; if (o_base64_decode((const unsigned char *)x509_data, o_strlen(x509_data), NULL, &der_dec_len)) { if ((der_dec = o_malloc(der_dec_len+4)) != NULL) { if (o_base64_decode((const unsigned char *)x509_data, o_strlen(x509_data), der_dec, &der_dec_len)) { cert_dat.data = der_dec; cert_dat.size = der_dec_len; } else { y_log_message(Y_LOG_LEVEL_ERROR, "parse_certificate - Error o_base64_decode (2)"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "parse_certificate - Error allocating resources for der_dec"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "parse_certificate - Error o_base64_decode (1)"); } } else { cert_dat.data = (unsigned char *)x509_data; cert_dat.size = o_strlen(x509_data); } if (gnutls_x509_crt_import(cert, &cert_dat, der_format?GNUTLS_X509_FMT_DER:GNUTLS_X509_FMT_PEM) >= 0) { ret = gnutls_x509_crt_get_issuer_dn(cert, NULL, &issuer_dn_len); if (gnutls_x509_crt_get_dn(cert, NULL, &dn_len) == GNUTLS_E_SHORT_MEMORY_BUFFER && (ret == GNUTLS_E_SHORT_MEMORY_BUFFER || ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)) { if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) { if ((issuer_dn = o_malloc(issuer_dn_len +1)) != NULL) { if (gnutls_x509_crt_get_issuer_dn(cert, issuer_dn, &issuer_dn_len) < 0) { y_log_message(Y_LOG_LEVEL_ERROR, "parse_certificate - Error gnutls_x509_crt_get_issuer_dn"); o_free(issuer_dn); issuer_dn = NULL; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "parse_certificate - Error o_malloc issuer_dn"); } } if ((dn = o_malloc(dn_len +1)) != NULL) { if (gnutls_x509_crt_get_dn(cert, dn, &dn_len) >= 0) { dn[dn_len] = '\0'; if (get_certificate_id(cert, key_id_enc, &key_id_enc_len) == G_OK && (expires_at = gnutls_x509_crt_get_expiration_time(cert)) != (time_t)-1 && (issued_at = gnutls_x509_crt_get_activation_time(cert)) != (time_t)-1) { j_return = json_pack("{sis{sssisisssssissss}}", "result", G_OK, "certificate", "certificate_id", key_id_enc, "activation", issued_at, "expiration", expires_at, "certificate_dn", dn, "certificate_issuer_dn", issuer_dn!=NULL?issuer_dn:"", "last_used", 0, "last_user_agent", "", "x509", x509_data); } else { y_log_message(Y_LOG_LEVEL_ERROR, "parse_certificate - Error gnutls_x509_crt_get_key_id or gnutls_x509_crt_get_expiration_time or gnutls_x509_crt_get_activation_time"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "parse_certificate - Error gnutls_x509_crt_get_dn (2)"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "parse_certificate - Error o_malloc dn"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(dn); o_free(issuer_dn); } else { y_log_message(Y_LOG_LEVEL_ERROR, "parse_certificate - Error gnutls_x509_crt_get_dn (1)"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "parse_certificate - Error gnutls_x509_crt_import"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } gnutls_x509_crt_deinit(cert); } else { y_log_message(Y_LOG_LEVEL_ERROR, "parse_certificate - Error gnutls_x509_crt_init"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(der_dec); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } static int update_user_certificate_enabled_scheme_storage(struct config_module * config, json_t * j_parameters, const char * username, const char * cert_id, int enabled) { json_t * j_query; int res, ret; j_query = json_pack("{sss{si}s{sOssss}}", "table", GLEWLWYD_SCHEME_CERTIFICATE_TABLE_USER_CERTIFICATE, "set", "gsuc_enabled", enabled, "where", "gsuc_mod_name", json_object_get(j_parameters, "mod_name"), "gsuc_username", username, "gsuc_x509_certificate_id", cert_id); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "toggle_enabled_user_certificate_scheme_storage - Error executing j_query"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static int update_user_certificate_last_used_scheme_storage(struct config_module * config, json_t * j_parameters, const char * username, const char * cert_id, const char * user_agent) { json_t * j_query; int res, ret; char * last_used_clause; if (config->conn->type==HOEL_DB_TYPE_MARIADB) { last_used_clause = msprintf("FROM_UNIXTIME(%u)", time(NULL)); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { last_used_clause = msprintf("TO_TIMESTAMP(%u)", time(NULL)); } else { // HOEL_DB_TYPE_SQLITE last_used_clause = msprintf("%u", time(NULL)); } j_query = json_pack("{sss{s{ss}ss}s{sOssss}}", "table", GLEWLWYD_SCHEME_CERTIFICATE_TABLE_USER_CERTIFICATE, "set", "gsuc_last_used", "raw", last_used_clause, "gsuc_last_user_agent", user_agent!=NULL?user_agent:"", "where", "gsuc_mod_name", json_object_get(j_parameters, "mod_name"), "gsuc_username", username, "gsuc_x509_certificate_id", cert_id); o_free(last_used_clause); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "toggle_enabled_user_certificate_scheme_storage - Error executing j_query"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static int delete_user_certificate_scheme_storage(struct config_module * config, json_t * j_parameters, const char * username, const char * cert_id) { json_t * j_query; int res, ret; j_query = json_pack("{sss{sOssss}}", "table", GLEWLWYD_SCHEME_CERTIFICATE_TABLE_USER_CERTIFICATE, "where", "gsuc_mod_name", json_object_get(j_parameters, "mod_name"), "gsuc_username", username, "gsuc_x509_certificate_id", cert_id); res = h_delete(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "delete_user_certificate_scheme_storage - Error executing j_query"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static json_t * get_user_certificate_from_id_user_property(struct config_module * config, json_t * j_parameters, const char * username, const char * cert_id) { json_t * j_user, * j_user_certificate, * j_parsed_certificate, * j_return = NULL, * j_element = NULL; size_t index = 0; j_user = config->glewlwyd_module_callback_get_user(config, username); if (check_result_value(j_user, G_OK)) { j_user_certificate = json_object_get(json_object_get(j_user, "user"), json_string_value(json_object_get(j_parameters, "user-certificate-property"))); if (json_is_string(j_user_certificate)) { j_parsed_certificate = parse_certificate(json_string_value(j_user_certificate), (0 == o_strcmp("DER", json_string_value(json_object_get(j_parameters, "user-certificate-format"))))); if (check_result_value(j_parsed_certificate, G_OK)) { if (0 == o_strcmp(cert_id, json_string_value(json_object_get(json_object_get(j_parsed_certificate, "certificate"), "certificate_id")))) { j_return = json_pack("{sisO}", "result", G_OK, "certificate", json_object_get(j_parsed_certificate, "certificate")); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_certificate_from_id_user_property certificate - Error parse_certificate (1)"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_parsed_certificate); } else if (json_is_array(j_user_certificate)) { json_array_foreach(j_user_certificate, index, j_element) { j_parsed_certificate = parse_certificate(json_string_value(j_element), (0 == o_strcmp("DER", json_string_value(json_object_get(j_parameters, "user-certificate-format"))))); if (check_result_value(j_parsed_certificate, G_OK)) { if (0 == o_strcmp(cert_id, json_string_value(json_object_get(json_object_get(j_parsed_certificate, "certificate"), "certificate_id")))) { j_return = json_pack("{sisO}", "result", G_OK, "certificate", json_object_get(j_parsed_certificate, "certificate")); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_certificate_from_id_user_property certificate - Error parse_certificate (2)"); } json_decref(j_parsed_certificate); } if (j_return == NULL) { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } else { j_return = json_pack("{sis[]}", "result", G_OK, "certificate"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_certificate_from_id_user_property certificate - Error glewlwyd_module_callback_get_user"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_user); return j_return; } static json_t * get_user_certificate_list_user_property(struct config_module * config, json_t * j_parameters, const char * username) { json_t * j_user, * j_user_certificate, * j_parsed_certificate = NULL, * j_certificate_array = NULL, * j_return = NULL, * j_element = NULL, * j_user_dn = NULL; size_t index = 0; j_user = config->glewlwyd_module_callback_get_user(config, username); if (check_result_value(j_user, G_OK)) { if (json_string_length(json_object_get(j_parameters, "user-certificate-property"))) { if ((j_certificate_array = json_array()) != NULL) { j_user_certificate = json_object_get(json_object_get(j_user, "user"), json_string_value(json_object_get(j_parameters, "user-certificate-property"))); if (json_is_string(j_user_certificate)) { j_parsed_certificate = parse_certificate(json_string_value(j_user_certificate), (0 == o_strcmp("DER", json_string_value(json_object_get(j_parameters, "user-certificate-format"))))); if (check_result_value(j_parsed_certificate, G_OK)) { json_object_del(json_object_get(j_parsed_certificate, "certificate"), "x509"); json_array_append(j_certificate_array, json_object_get(j_parsed_certificate, "certificate")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_can_use certificate - Error parse_certificate (1)"); } json_decref(j_parsed_certificate); } else if (json_is_array(j_user_certificate)) { json_array_foreach(j_user_certificate, index, j_element) { j_parsed_certificate = parse_certificate(json_string_value(j_element), (0 == o_strcmp("DER", json_string_value(json_object_get(j_parameters, "user-certificate-format"))))); if (check_result_value(j_parsed_certificate, G_OK)) { json_object_del(json_object_get(j_parsed_certificate, "certificate"), "x509"); json_array_append(j_certificate_array, json_object_get(j_parsed_certificate, "certificate")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_can_use certificate - Error parse_certificate (2)"); } json_decref(j_parsed_certificate); } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_can_use certificate - Error allocating resources for j_certificate_array"); } } if (json_string_length(json_object_get(j_parameters, "user-dn-property"))) { j_user_dn = json_object_get(json_object_get(j_user, "user"), json_string_value(json_object_get(j_parameters, "user-dn-property"))); if (!json_string_length(j_user_dn)) { j_user_dn = NULL; } } if (json_array_size(j_certificate_array) || json_string_length(j_user_dn)) { j_return = json_pack("{si}", "result", G_OK); if (json_array_size(j_certificate_array)) { json_object_set(j_return, "certificate", j_certificate_array); } if (json_string_length(j_user_dn)) { json_object_set(j_return, "dn", j_user_dn); } } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } json_decref(j_certificate_array); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_can_use certificate - Error glewlwyd_module_callback_get_user"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_user); return j_return; } static json_t * get_user_certificate_from_id_scheme_storage(struct config_module * config, json_t * j_parameters, const char * username, const char * cert_id) { json_t * j_query, * j_result, * j_return; int res; j_query = json_pack("{sss[ssssssss]s{sOssss}}", "table", GLEWLWYD_SCHEME_CERTIFICATE_TABLE_USER_CERTIFICATE, "columns", "gsuc_x509_certificate_dn AS certificate_dn", "gsuc_x509_certificate_issuer_dn AS certificate_issuer_dn", "gsuc_x509_certificate_id AS certificate_id", SWITCH_DB_TYPE(config->conn->type, "UNIX_TIMESTAMP(gsuc_activation) AS activation", "strftime('%s', gsuc_activation) AS activation", "EXTRACT(EPOCH FROM gsuc_activation)::integer AS activation"), SWITCH_DB_TYPE(config->conn->type, "UNIX_TIMESTAMP(gsuc_expiration) AS expiration", "strftime('%s', gsuc_expiration) AS expiration", "EXTRACT(EPOCH FROM gsuc_expiration)::integer AS expiration"), "gsuc_enabled", SWITCH_DB_TYPE(config->conn->type, "UNIX_TIMESTAMP(gsuc_last_used) AS last_used", "strftime('%s', gsuc_last_used) AS last_used", "EXTRACT(EPOCH FROM gsuc_last_used)::integer AS last_used"), "gsuc_last_user_agent AS last_user_agent", "where", "gsuc_mod_name", json_object_get(j_parameters, "mod_name"), "gsuc_username", username, "gsuc_x509_certificate_id", cert_id); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { if (json_integer_value(json_object_get(json_array_get(j_result, 0), "gsuc_enabled"))) { json_object_set(json_array_get(j_result, 0), "enabled", json_true()); } else { json_object_set(json_array_get(j_result, 0), "enabled", json_false()); } json_object_del(json_array_get(j_result, 0), "gsuc_enabled"); j_return = json_pack("{sisO}", "result", G_OK, "certificate", json_array_get(j_result, 0)); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "get_user_certificate_from_id_scheme_storage - Error executing j_query"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } static json_t * get_user_certificate_list_scheme_storage(struct config_module * config, json_t * j_parameters, const char * username, int enabled) { json_t * j_query, * j_result, * j_return, * j_element = NULL; int res; size_t index = 0; j_query = json_pack("{sss[ssssssss]s{sOss}ss}", "table", GLEWLWYD_SCHEME_CERTIFICATE_TABLE_USER_CERTIFICATE, "columns", "gsuc_x509_certificate_dn AS certificate_dn", "gsuc_x509_certificate_issuer_dn AS certificate_issuer_dn", "gsuc_x509_certificate_id AS certificate_id", SWITCH_DB_TYPE(config->conn->type, "UNIX_TIMESTAMP(gsuc_activation) AS activation", "strftime('%s', gsuc_activation) AS activation", "EXTRACT(EPOCH FROM gsuc_activation)::integer AS activation"), SWITCH_DB_TYPE(config->conn->type, "UNIX_TIMESTAMP(gsuc_expiration) AS expiration", "strftime('%s', gsuc_expiration) AS expiration", "EXTRACT(EPOCH FROM gsuc_expiration)::integer AS expiration"), "gsuc_enabled", SWITCH_DB_TYPE(config->conn->type, "UNIX_TIMESTAMP(gsuc_last_used) AS last_used", "strftime('%s', gsuc_last_used) AS last_used", "EXTRACT(EPOCH FROM gsuc_last_used)::integer AS last_used"), "gsuc_last_user_agent AS last_user_agent", "where", "gsuc_mod_name", json_object_get(j_parameters, "mod_name"), "gsuc_username", username, "order_by", "gsuc_id"); if (enabled) { json_object_set_new(json_object_get(j_query, "where"), "gsuc_enabled", json_integer(1)); } res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result, index, j_element) { if (json_integer_value(json_object_get(j_element, "gsuc_enabled"))) { json_object_set(j_element, "enabled", json_true()); } else { json_object_set(j_element, "enabled", json_false()); } json_object_del(j_element, "gsuc_enabled"); } j_return = json_pack("{sisO}", "result", G_OK, "certificate", j_result); json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_certificate_list - Error executing j_query"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } static struct _cert_chain_element * get_cert_chain_element_from_dn(struct _cert_param * cert_params, const char * dn) { size_t i; struct _cert_chain_element * cert_chain_element = NULL; for (i=0; icert_array_len; i++) { if (0 == o_strcmp(dn, cert_params->cert_array[i]->dn)) { cert_chain_element = cert_params->cert_array[i]; break; } } return cert_chain_element; } static int add_user_certificate_scheme_storage(struct config_module * config, json_t * j_parameters, const char * x509_data, const char * username, const char * user_agent) { json_t * j_query, * j_parsed_certificate, * j_result; char * expiration_clause, * activation_clause; int res, ret; if (o_strlen(x509_data)) { j_parsed_certificate = parse_certificate(x509_data, 0); if (check_result_value(j_parsed_certificate, G_OK)) { j_result = get_user_certificate_from_id_scheme_storage(config, j_parameters, username, json_string_value(json_object_get(json_object_get(j_parsed_certificate, "certificate"), "certificate_id"))); if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { if (config->conn->type==HOEL_DB_TYPE_MARIADB) { expiration_clause = msprintf("FROM_UNIXTIME(%"JSON_INTEGER_FORMAT")", json_integer_value(json_object_get(json_object_get(j_parsed_certificate, "certificate"), "expiration"))); activation_clause = msprintf("FROM_UNIXTIME(%"JSON_INTEGER_FORMAT")", json_integer_value(json_object_get(json_object_get(j_parsed_certificate, "certificate"), "activation"))); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { expiration_clause = msprintf("TO_TIMESTAMP(%"JSON_INTEGER_FORMAT")", json_integer_value(json_object_get(json_object_get(j_parsed_certificate, "certificate"), "expiration"))); activation_clause = msprintf("TO_TIMESTAMP(%"JSON_INTEGER_FORMAT")", json_integer_value(json_object_get(json_object_get(j_parsed_certificate, "certificate"), "activation"))); } else { // HOEL_DB_TYPE_SQLITE expiration_clause = msprintf("%"JSON_INTEGER_FORMAT"", json_integer_value(json_object_get(json_object_get(j_parsed_certificate, "certificate"), "expiration"))); activation_clause = msprintf("%"JSON_INTEGER_FORMAT"", json_integer_value(json_object_get(json_object_get(j_parsed_certificate, "certificate"), "activation"))); } j_query = json_pack("{ss s{sO ss sO sO sO sO s{ss} s{ss} so}}", "table", GLEWLWYD_SCHEME_CERTIFICATE_TABLE_USER_CERTIFICATE, "values", "gsuc_mod_name", json_object_get(j_parameters, "mod_name"), "gsuc_username", username, "gsuc_x509_certificate_id", json_object_get(json_object_get(j_parsed_certificate, "certificate"), "certificate_id"), "gsuc_x509_certificate_content", json_object_get(json_object_get(j_parsed_certificate, "certificate"), "x509"), "gsuc_x509_certificate_dn", json_object_get(json_object_get(j_parsed_certificate, "certificate"), "certificate_dn"), "gsuc_x509_certificate_issuer_dn", json_object_get(json_object_get(j_parsed_certificate, "certificate"), "certificate_issuer_dn"), "gsuc_expiration", "raw", expiration_clause, "gsuc_activation", "raw", activation_clause, "gsuc_last_used", json_null()); o_free(expiration_clause); o_free(activation_clause); if (o_strlen(user_agent)) { json_object_set_new(json_object_get(j_query, "values"), "gsuc_last_user_agent", json_string(user_agent)); } res = h_insert(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user_certificate_scheme_storage - Error executing j_query"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else if (check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_DEBUG, "add_user_certificate_scheme_storage - get_user_certificate_from_id_scheme_storage error param"); ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user_certificate_scheme_storage - Error get_user_certificate_from_id_scheme_storage"); ret = G_ERROR; } json_decref(j_result); } else if (check_result_value(j_parsed_certificate, G_ERROR_PARAM)) { y_log_message(Y_LOG_LEVEL_DEBUG, "add_user_certificate_scheme_storage - parse_certificate error param"); ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user_certificate_scheme_storage - Error parse_certificate"); ret = G_ERROR; } json_decref(j_parsed_certificate); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "add_user_certificate_scheme_storage - x509 empty"); ret = G_ERROR_PARAM; } return ret; } static void scm_gnutls_certificate_status_to_c_string (gnutls_certificate_status_t c_obj) { static const struct { gnutls_certificate_status_t value; const char* name; } table[] = { { GNUTLS_CERT_INVALID, "invalid" }, { GNUTLS_CERT_REVOKED, "revoked" }, { GNUTLS_CERT_SIGNER_NOT_FOUND, "signer-not-found" }, { GNUTLS_CERT_SIGNER_NOT_CA, "signer-not-ca" }, { GNUTLS_CERT_INSECURE_ALGORITHM, "insecure-algorithm" }, }; unsigned i; for (i = 0; i < 5; i++) { if (table[i].value & c_obj) { y_log_message(Y_LOG_LEVEL_DEBUG, "%s", table[i].name); } } } static int is_certificate_valid_from_ca_chain(struct _cert_param * cert_params, gnutls_x509_crt_t cert) { int ret = G_OK, res; unsigned int result = 0; gnutls_x509_crt_t * cert_chain = NULL, root_x509 = NULL; gnutls_x509_trust_list_t tlist = NULL; size_t cert_chain_len = 0, issuer_dn_len = 0; char * issuer_dn = NULL; struct _cert_chain_element * cert_chain_element; if ((res = gnutls_x509_crt_get_issuer_dn(cert, NULL, &issuer_dn_len)) == GNUTLS_E_SHORT_MEMORY_BUFFER) { if ((issuer_dn = o_malloc(issuer_dn_len+1)) != NULL && gnutls_x509_crt_get_issuer_dn(cert, issuer_dn, &issuer_dn_len) >= 0) { // Calculate ca chain length cert_chain_len = 1; cert_chain_element = get_cert_chain_element_from_dn(cert_params, issuer_dn); while (cert_chain_element != NULL) { if (cert_chain_element->issuer_cert == NULL) { root_x509 = cert_chain_element->cert; } cert_chain_len++; cert_chain_element = cert_chain_element->issuer_cert; } if (root_x509 != NULL) { if ((cert_chain = o_malloc(cert_chain_len*sizeof(gnutls_x509_crt_t))) != NULL) { cert_chain[0] = cert; cert_chain_len = 1; cert_chain_element = get_cert_chain_element_from_dn(cert_params, issuer_dn); while (cert_chain_element != NULL) { cert_chain[cert_chain_len] = cert_chain_element->cert; cert_chain_len++; cert_chain_element = cert_chain_element->issuer_cert; } if (!gnutls_x509_trust_list_init(&tlist, 0)) { if (gnutls_x509_trust_list_add_cas(tlist, &root_x509, 1, 0) >= 0) { if (gnutls_x509_trust_list_verify_crt(tlist, cert_chain, cert_chain_len, 0, &result, NULL) >= 0) { if (!result) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "is_certificate_valid_from_ca_chain - certificate chain invalid"); scm_gnutls_certificate_status_to_c_string(result); ret = G_ERROR_UNAUTHORIZED; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_certificate_valid_from_ca_chain - Error gnutls_x509_trust_list_verify_crt"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_certificate_valid_from_ca_chain - Error gnutls_x509_trust_list_add_cas"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_certificate_valid_from_ca_chain - Error gnutls_x509_trust_list_init"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_certificate_valid_from_ca_chain - Error allocating resources for cert_chain"); ret = G_ERROR; } o_free(cert_chain); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "is_certificate_valid_from_ca_chain - no root certificate found"); ret = G_ERROR_UNAUTHORIZED; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_certificate_valid_from_ca_chain - Error gnutls_x509_crt_get_issuer_dn (2)"); ret = G_ERROR; } } else if (res == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { ret = G_ERROR_UNAUTHORIZED; } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_certificate_valid_from_ca_chain - Error gnutls_x509_crt_get_issuer_dn (1)"); ret = G_ERROR; } o_free(issuer_dn); gnutls_x509_trust_list_deinit(tlist, 0); return ret; } static int is_user_certificate_valid_user_property(struct config_module * config, json_t * j_parameters, const char * username, gnutls_x509_crt_t cert) { json_t * j_user_list = get_user_certificate_list_user_property(config, j_parameters, username), * j_element = NULL; int ret; unsigned char key_id_enc[256] = {0}; size_t index = 0, key_id_enc_len = 0; gnutls_datum_t cert_dn = {NULL, 0}; if (check_result_value(j_user_list, G_OK)) { if (json_string_length(json_object_get(j_user_list, "dn"))) { #if GNUTLS_VERSION_NUMBER >= 0x030702 if (gnutls_x509_crt_get_dn3(cert, &cert_dn, 0) == GNUTLS_E_SUCCESS) { #else if (gnutls_x509_crt_get_dn2(cert, &cert_dn) == GNUTLS_E_SUCCESS) { #endif if (cert_dn.size == json_string_length(json_object_get(j_user_list, "dn")) && 0 == o_strncasecmp(json_string_value(json_object_get(j_user_list, "dn")), (const char *)cert_dn.data, cert_dn.size)) { ret = G_OK; } else { ret = G_ERROR_UNAUTHORIZED; } gnutls_free(cert_dn.data); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_user_certificate_valid_user_property - Error gnutls_x509_crt_get_dn3"); ret = G_ERROR; } } else { if (get_certificate_id(cert, key_id_enc, &key_id_enc_len) == G_OK) { ret = G_ERROR_UNAUTHORIZED; json_array_foreach(json_object_get(j_user_list, "certificate"), index, j_element) { if (0 == o_strcmp((const char *)key_id_enc, json_string_value(json_object_get(j_element, "certificate_id")))) { ret = G_OK; } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_user_certificate_valid_user_property - Error gnutls_x509_crt_get_key_id"); ret = G_ERROR; } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_user_certificate_valid_user_property - Error get_user_certificate_list_user_property"); ret = G_ERROR; } json_decref(j_user_list); return ret; } static int is_user_certificate_valid_scheme_storage(struct config_module * config, json_t * j_parameters, const char * username, gnutls_x509_crt_t cert) { int ret, res; json_t * j_query, * j_result; unsigned char key_id_enc[256] = {0}; size_t key_id_enc_len = 0; if (get_certificate_id(cert, key_id_enc, &key_id_enc_len) == G_OK) { key_id_enc[key_id_enc_len] = '\0'; j_query = json_pack("{sss[s]s{sOsssssi}}", "table", GLEWLWYD_SCHEME_CERTIFICATE_TABLE_USER_CERTIFICATE, "columns", "gsuc_id", "where", "gsuc_mod_name", json_object_get(j_parameters, "mod_name"), "gsuc_username", username, "gsuc_x509_certificate_id", key_id_enc, "gsuc_enabled", 1); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { ret = G_OK; } else { ret = G_ERROR_UNAUTHORIZED; } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_user_certificate_valid_scheme_storage - Error executing j_query"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_user_certificate_valid_scheme_storage - Error get_certificate_id"); ret = G_ERROR; } return ret; } static int is_user_certificate_valid(struct config_module * config, json_t * j_parameters, const char * username, gnutls_x509_crt_t cert) { time_t now, exp; time(&now); exp = gnutls_x509_crt_get_expiration_time(cert); if (exp != (time_t)-1 && now < exp) { if (json_object_get(j_parameters, "use-scheme-storage") == json_true()) { return is_user_certificate_valid_scheme_storage(config, j_parameters, username, cert); } else { return is_user_certificate_valid_user_property(config, j_parameters, username, cert); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "is_user_certificate_valid - Certificate expired"); return G_ERROR_UNAUTHORIZED; } } static json_t * identify_certificate(struct config_module * config, json_t * j_parameters, gnutls_x509_crt_t cert) { time_t now, exp; int res; json_t * j_query, * j_result, * j_return; unsigned char key_id_enc[256] = {0}; size_t key_id_enc_len = 0; time(&now); exp = gnutls_x509_crt_get_expiration_time(cert); if (exp != (time_t)-1 && now < exp) { if (json_object_get(j_parameters, "use-scheme-storage") == json_true()) { if (get_certificate_id(cert, key_id_enc, &key_id_enc_len) == G_OK) { key_id_enc[key_id_enc_len] = '\0'; j_query = json_pack("{sss[s]s{sOsssi}}", "table", GLEWLWYD_SCHEME_CERTIFICATE_TABLE_USER_CERTIFICATE, "columns", "gsuc_username AS username", "where", "gsuc_mod_name", json_object_get(j_parameters, "mod_name"), "gsuc_x509_certificate_id", key_id_enc, "gsuc_enabled", 1); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result) == 1) { j_return = json_pack("{sisO}", "result", G_OK, "username", json_object_get(json_array_get(j_result, 0), "username")); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "identify_certificate - Error executing j_query"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "identify_certificate - Error get_certificate_id"); j_return = json_pack("{si}", "result", G_ERROR); } } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "identify_certificate - Certificate expired"); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } return j_return; } static void update_cert_chain_issuer(struct _cert_chain_element ** ca_chain, size_t cert_array_len, struct _cert_chain_element * cur_ca) { size_t i; for (i=0; idn, cur_ca->issuer_dn)) { cur_ca->issuer_cert = ca_chain[i]; } if (0 == o_strcmp(ca_chain[i]->issuer_dn, cur_ca->dn)) { ca_chain[i]->issuer_cert = cur_ca; } } } static int parse_ca_chain(json_t * j_ca_chain, struct _cert_chain_element *** ca_chain, size_t * cert_array_len) { json_t * j_element = NULL; size_t index = 0, len = 0; int ret = G_OK, cur_status, res; gnutls_x509_crt_t cert; struct _cert_chain_element * cur_ca; gnutls_datum_t cert_dat; *ca_chain = NULL; *cert_array_len = 0; UNUSED(j_ca_chain); if (j_ca_chain != NULL) { json_array_foreach(j_ca_chain, index, j_element) { cert = NULL; cur_status = G_OK; if (!gnutls_x509_crt_init(&cert)) { cert_dat.data = (unsigned char *)json_string_value(json_object_get(j_element, "cert-file")); cert_dat.size = json_string_length(json_object_get(j_element, "cert-file")); if ((res = gnutls_x509_crt_import(cert, &cert_dat, GNUTLS_X509_FMT_PEM)) < 0) { y_log_message(Y_LOG_LEVEL_ERROR, "parse_ca_chain - Error gnutls_x509_crt_import: %d", res); cur_status = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "parse_ca_chain - Error gnutls_x509_crt_init"); cur_status = G_ERROR; } if (cur_status == G_OK) { cur_ca = o_malloc(sizeof(struct _cert_chain_element)); cur_ca->cert = cert; cur_ca->dn = NULL; cur_ca->issuer_dn = NULL; cur_ca->issuer_cert = NULL; len = 0; if (gnutls_x509_crt_get_dn(cert, NULL, &len) == GNUTLS_E_SHORT_MEMORY_BUFFER) { if ((cur_ca->dn = o_malloc(len+1)) == NULL || gnutls_x509_crt_get_dn(cert, cur_ca->dn, &len) < 0) { y_log_message(Y_LOG_LEVEL_ERROR, "parse_ca_chain - Error gnutls_x509_crt_get_dn (2) on cert at index %zu", index); cur_status = G_ERROR; } else { cur_ca->dn[len] = '\0'; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "parse_ca_chain - Error gnutls_x509_crt_get_dn (1) on cert at index %zu", index); cur_status = G_ERROR; } if (cur_status == G_OK) { len = 0; if ((res = gnutls_x509_crt_get_issuer_dn(cert, NULL, &len)) == GNUTLS_E_SHORT_MEMORY_BUFFER) { if ((cur_ca->issuer_dn = o_malloc(len+1)) == NULL || gnutls_x509_crt_get_issuer_dn(cert, cur_ca->issuer_dn, &len) < 0) { y_log_message(Y_LOG_LEVEL_ERROR, "parse_ca_chain - Error gnutls_x509_crt_get_issuer_dn (2) on cert at index %zu", index); cur_status = G_ERROR; } else { cur_ca->issuer_dn[len] = '\0'; } } else if (res != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { y_log_message(Y_LOG_LEVEL_ERROR, "parse_ca_chain - Error gnutls_x509_crt_get_issuer_dn (1) on cert at index %zu", index); cur_status = G_ERROR; } } if (cur_status == G_OK) { update_cert_chain_issuer(*ca_chain, *cert_array_len, cur_ca); *ca_chain = o_realloc(*ca_chain, ((*cert_array_len)+1)*sizeof(struct _cert_chain_element *)); if (*ca_chain != NULL) { (*ca_chain)[*cert_array_len] = cur_ca; (*cert_array_len)++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "parse_ca_chain - Error alocatig resources for ca_chain at index %zu", index); gnutls_x509_crt_deinit(cert); o_free(cur_ca->issuer_dn); o_free(cur_ca->dn); o_free(cur_ca); } } else { gnutls_x509_crt_deinit(cert); o_free(cur_ca->issuer_dn); o_free(cur_ca->dn); o_free(cur_ca); } } else { gnutls_x509_crt_deinit(cert); } if (cur_status != G_OK) { ret = cur_status; break; } } } return ret; } static json_t * is_certificate_parameters_valid(json_t * j_parameters) { json_t * j_array = json_array(), * j_return, * j_element = NULL; size_t index = 0; if (j_array != NULL) { if (json_is_object(j_parameters)) { if (json_object_get(j_parameters, "cert-source") != NULL && 0 != o_strcmp("TLS", json_string_value(json_object_get(j_parameters, "cert-source"))) && 0 != o_strcmp("header", json_string_value(json_object_get(j_parameters, "cert-source"))) && 0 != o_strcmp("both", json_string_value(json_object_get(j_parameters, "cert-source")))) { json_array_append_new(j_array, json_string("cert-source is optional and must be one of the following values: 'TLS', 'header' or 'both'")); } if ((0 == o_strcmp("header", json_string_value(json_object_get(j_parameters, "cert-source"))) || 0 == o_strcmp("both", json_string_value(json_object_get(j_parameters, "cert-source")))) && !json_string_length(json_object_get(j_parameters, "header-name"))) { json_array_append_new(j_array, json_string("header-name is mandatory when cert-source is 'header' or 'both' and must be a non empty string")); } if (json_object_get(j_parameters, "use-scheme-storage") != NULL && !json_is_boolean(json_object_get(j_parameters, "use-scheme-storage"))) { json_array_append_new(j_array, json_string("use-scheme-storage is optional and must be a boolean")); } if (json_object_get(j_parameters, "use-scheme-storage") != json_true()) { if (!json_string_length(json_object_get(j_parameters, "user-certificate-property")) && !json_string_length(json_object_get(j_parameters, "user-dn-property"))) { json_array_append_new(j_array, json_string("user-certificate-property or user-dn-property is mandatory and must be a non empty string")); } if (json_string_length(json_object_get(j_parameters, "user-certificate-property")) && json_object_get(j_parameters, "user-certificate-format") != NULL && 0 != o_strcmp("PEM", json_string_value(json_object_get(j_parameters, "user-certificate-format"))) && 0 != o_strcmp("DER", json_string_value(json_object_get(j_parameters, "user-certificate-format")))) { json_array_append_new(j_array, json_string("user-certificate-format is optional and must be one of the following values: 'PEM' or 'DER'")); } } if (json_object_get(j_parameters, "ca-chain") != NULL && !json_is_array(json_object_get(j_parameters, "ca-chain"))) { json_array_append_new(j_array, json_string("ca-chain is optional and must be an array of JSON objects")); } else { json_array_foreach(json_object_get(j_parameters, "ca-chain"), index, j_element) { if (!json_is_object(j_element) || !json_string_length(json_object_get(j_element, "file-name")) || !json_string_length(json_object_get(j_element, "cert-file"))) { json_array_append_new(j_array, json_string("A ca-chain object must have the format {file-name: '', cert-file: ''} with non empty string values")); } } } } else { json_array_append_new(j_array, json_string("certificate parameters must be a JSON object")); } if (!json_array_size(j_array)) { j_return = json_pack("{si}", "result", G_OK); } else { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_array); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_certificate_parameters_valid - Error allocating resources for j_array"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } json_decref(j_array); return j_return; } /** * * user_auth_scheme_module_load * * Executed once when Glewlwyd service is started * Used to identify the module and to show its parameters on init * You can also use it to load resources that are required once for all * instance modules for example * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * name: string, mandatory, name of the module, must be unique among other scheme modules * display_name: string, optional, long name of the module * description: string, optional, description for the module * parameters: object, optional, parameters description for the module * } * * Example: * { * result: G_OK, * name: "mock", * display_name: "Mock scheme module", * description: "Mock scheme module for glewlwyd tests", * parameters: { * mock-value: { * type: "string", * mandatory: true * } * } * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ json_t * user_auth_scheme_module_load(struct config_module * config) { UNUSED(config); return json_pack("{si ss ss ss}", "result", G_OK, "name", "certificate", "display_name", "Client certificate", "description", "Client certificate scheme module"); } /** * * user_auth_scheme_module_unload * * Executed once when Glewlwyd service is stopped * You can also use it to release resources that are required once for all * instance modules for example * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ int user_auth_scheme_module_unload(struct config_module * config) { UNUSED(config); return G_OK; } /** * * user_auth_scheme_module_init * * Initialize an instance of this module declared in Glewlwyd service. * If required, you must dynamically allocate a pointer to the configuration * for this instance and pass it to *cls * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_parameters: used to initialize an instance in JSON format * The module must validate itself its parameters * @parameter mod_name: module name in glewlwyd service * @parameter cls: will contain an allocated void * pointer that will be sent back * as void * in all module functions * */ json_t * user_auth_scheme_module_init(struct config_module * config, json_t * j_parameters, const char * mod_name, void ** cls) { UNUSED(config); UNUSED(mod_name); pthread_mutexattr_t mutexattr; json_t * j_result = is_certificate_parameters_valid(j_parameters), * j_return; if (check_result_value(j_result, G_OK)) { json_object_set_new(j_parameters, "mod_name", json_string(mod_name)); if ((*cls = o_malloc(sizeof(struct _cert_param))) != NULL) { pthread_mutexattr_init ( &mutexattr ); pthread_mutexattr_settype( &mutexattr, PTHREAD_MUTEX_RECURSIVE ); if (!pthread_mutex_init(&((struct _cert_param *)*cls)->cert_request_lock, &mutexattr)) { ((struct _cert_param *)*cls)->cert_source = 0; ((struct _cert_param *)*cls)->cert_array_len = 0; ((struct _cert_param *)*cls)->cert_array = NULL; if (json_object_get(j_parameters, "cert-source") == NULL || 0 == o_strcmp("TLS", json_string_value(json_object_get(j_parameters, "cert-source")))) { ((struct _cert_param *)*cls)->cert_source = G_CERT_SOURCE_TLS; } else if (0 == o_strcmp("header", json_string_value(json_object_get(j_parameters, "cert-source")))) { ((struct _cert_param *)*cls)->cert_source = G_CERT_SOURCE_HEADER; } else { ((struct _cert_param *)*cls)->cert_source = G_CERT_SOURCE_TLS|G_CERT_SOURCE_HEADER; } if (parse_ca_chain(json_object_get(j_parameters, "ca-chain"), &(((struct _cert_param *)*cls)->cert_array), &(((struct _cert_param *)*cls)->cert_array_len)) == G_OK) { ((struct _cert_param *)*cls)->j_parameters = json_incref(j_parameters); j_return = json_pack("{si}", "result", G_OK); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init certificate - Error parse_ca_chain"); o_free(*cls); *cls = NULL; j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init certificate - Error pthread_mutex_init"); o_free(*cls); *cls = NULL; j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init certificate - Error allocating resources for cls"); j_return = json_pack("{si}", "result", G_ERROR); } } else if (check_result_value(j_result, G_ERROR_PARAM)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", json_object_get(j_result, "error")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init certificate - Error is_certificate_parameters_valid"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); return j_return; } /** * * user_auth_scheme_module_close * * Close an instance of this module declared in Glewlwyd service. * You must free the memory previously allocated in * the user_auth_scheme_module_init function as void * cls * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_close(struct config_module * config, void * cls) { UNUSED(config); size_t i; pthread_mutex_destroy(&((struct _cert_param *)cls)->cert_request_lock); json_decref(((struct _cert_param *)cls)->j_parameters); for (i=0; i<((struct _cert_param *)cls)->cert_array_len; i++) { o_free(((struct _cert_param *)cls)->cert_array[i]->dn); o_free(((struct _cert_param *)cls)->cert_array[i]->issuer_dn); gnutls_x509_crt_deinit(((struct _cert_param *)cls)->cert_array[i]->cert); o_free(((struct _cert_param *)cls)->cert_array[i]); } o_free(((struct _cert_param *)cls)->cert_array); o_free(((struct _cert_param *)cls)); return G_OK; } /** * * user_auth_scheme_module_can_use * * Validate if the user is allowed to use this scheme prior to the * authentication or registration * * @return value: GLEWLWYD_IS_REGISTERED - User can use scheme and has registered * GLEWLWYD_IS_AVAILABLE - User can use scheme but hasn't registered * GLEWLWYD_IS_NOT_AVAILABLE - User can't use scheme * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_can_use(struct config_module * config, const char * username, void * cls) { UNUSED(config); json_t * j_user_certificate = NULL; int ret = GLEWLWYD_IS_NOT_AVAILABLE; if (json_object_get(((struct _cert_param *)cls)->j_parameters, "use-scheme-storage") != json_true()) { j_user_certificate = get_user_certificate_list_user_property(config, ((struct _cert_param *)cls)->j_parameters, username); ret = (check_result_value(j_user_certificate, G_OK) && (json_array_size(json_object_get(j_user_certificate, "certificate")) || json_string_length(json_object_get(j_user_certificate, "dn"))))?GLEWLWYD_IS_REGISTERED:GLEWLWYD_IS_AVAILABLE; json_decref(j_user_certificate); } else { j_user_certificate = get_user_certificate_list_scheme_storage(config, ((struct _cert_param *)cls)->j_parameters, username, 1); ret = (check_result_value(j_user_certificate, G_OK) && json_array_size(json_object_get(j_user_certificate, "certificate")))?GLEWLWYD_IS_REGISTERED:GLEWLWYD_IS_AVAILABLE; json_decref(j_user_certificate); } return ret; } /** * * user_auth_scheme_module_register * * Register the scheme for a user * Ex: add a certificate, add new TOTP values, etc. * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the HTTP API * @parameter username: username to identify the user * @parameter j_scheme_data: additional data used to register the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_register(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls) { json_t * j_return, * j_result; int ret, clean_cert = 0; char * x509_data = NULL; const char * header_cert = NULL; unsigned char key_id_enc[257] = {0}; size_t key_id_enc_len = 256; gnutls_x509_crt_t cert = NULL; gnutls_datum_t cert_dat; if (0 == o_strcmp("test-certificate", json_string_value(json_object_get(j_scheme_data, "register")))) { ret = user_auth_scheme_module_validate(config, http_request, username, NULL, cls); if (ret == G_OK) { if ((((struct _cert_param *)cls)->cert_source & G_CERT_SOURCE_TLS) && http_request->client_cert != NULL) { cert = http_request->client_cert; } else if ((((struct _cert_param *)cls)->cert_source & G_CERT_SOURCE_HEADER) && (header_cert = u_map_get(http_request->map_header, json_string_value(json_object_get(((struct _cert_param *)cls)->j_parameters, "header-name")))) != NULL) { if (!gnutls_x509_crt_init(&cert)) { clean_cert = 1; cert_dat.data = (unsigned char *)header_cert; cert_dat.size = o_strlen(header_cert); if (gnutls_x509_crt_import(cert, &cert_dat, GNUTLS_X509_FMT_PEM) < 0) { y_log_message(Y_LOG_LEVEL_DEBUG, "user_auth_scheme_module_validate certificate - Error gnutls_x509_crt_import"); ret = G_ERROR_UNAUTHORIZED; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "user_auth_scheme_module_validate certificate - Error gnutls_x509_crt_init"); ret = G_ERROR_UNAUTHORIZED; } ret = G_ERROR_UNAUTHORIZED; } if (cert != NULL) { if (get_certificate_id(cert, key_id_enc, &key_id_enc_len) == G_OK) { key_id_enc[key_id_enc_len] = '\0'; if (json_object_get(((struct _cert_param *)cls)->j_parameters, "use-scheme-storage") == json_true()) { j_result = get_user_certificate_from_id_scheme_storage(config, ((struct _cert_param *)cls)->j_parameters, username, (const char *)key_id_enc); if (check_result_value(j_result, G_OK)) { j_return = json_pack("{sisO}", "result", G_OK, "response", json_object_get(j_result, "certificate")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register - Error get_user_certificate_from_id_scheme_storage"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } else { j_result = get_user_certificate_from_id_user_property(config, ((struct _cert_param *)cls)->j_parameters, username, (const char *)key_id_enc); if (check_result_value(j_result, G_OK)) { j_return = json_pack("{sisO}", "result", G_OK, "response", json_object_get(j_result, "certificate")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register - Error get_user_certificate_from_id_user_property"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register - Error get_certificate_id"); j_return = json_pack("{si}", "result", G_ERROR); } if (clean_cert) { gnutls_x509_crt_deinit(cert); } } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } } else if (json_object_get(((struct _cert_param *)cls)->j_parameters, "use-scheme-storage") == json_true()) { if (0 == o_strcmp("upload-certificate", json_string_value(json_object_get(j_scheme_data, "register")))) { if ((ret = add_user_certificate_scheme_storage(config, ((struct _cert_param *)cls)->j_parameters, json_string_value(json_object_get(j_scheme_data, "x509")), username, u_map_get_case(http_request->map_header, "user-agent"))) == G_OK) { j_return = json_pack("{si}", "result", G_OK); } else if (ret == G_ERROR_PARAM) { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register certificate - Error add_user_certificate_scheme_storage (1)"); j_return = json_pack("{si}", "result", G_ERROR); } } else if (0 == o_strcmp("use-certificate", json_string_value(json_object_get(j_scheme_data, "register")))) { if ((((struct _cert_param *)cls)->cert_source & G_CERT_SOURCE_TLS) && http_request->client_cert != NULL) { if ((x509_data = ulfius_export_client_certificate_pem(http_request)) != NULL) { if ((ret = add_user_certificate_scheme_storage(config, ((struct _cert_param *)cls)->j_parameters, x509_data, username, u_map_get_case(http_request->map_header, "user-agent"))) == G_OK) { j_return = json_pack("{si}", "result", G_OK); } else if (ret == G_ERROR_PARAM) { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register certificate - Error add_user_certificate_scheme_storage (2) "); j_return = json_pack("{si}", "result", G_ERROR); } o_free(x509_data); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register certificate - Error ulfius_export_client_certificate_pem"); j_return = json_pack("{si}", "result", G_ERROR); } } else if ((((struct _cert_param *)cls)->cert_source & G_CERT_SOURCE_HEADER) && (header_cert = u_map_get(http_request->map_header, json_string_value(json_object_get(((struct _cert_param *)cls)->j_parameters, "header-name")))) != NULL) { if ((ret = add_user_certificate_scheme_storage(config, ((struct _cert_param *)cls)->j_parameters, header_cert, username, u_map_get_case(http_request->map_header, "user-agent"))) == G_OK) { j_return = json_pack("{si}", "result", G_OK); } else if (ret == G_ERROR_PARAM) { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register certificate - Error add_user_certificate_scheme_storage (2) "); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "user_auth_scheme_module_register certificate - No certificate"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } } else if (0 == o_strcmp("toggle-certificate", json_string_value(json_object_get(j_scheme_data, "register")))) { if (json_string_length(json_object_get(j_scheme_data, "certificate_id"))) { j_result = get_user_certificate_from_id_scheme_storage(config, ((struct _cert_param *)cls)->j_parameters, username, json_string_value(json_object_get(j_scheme_data, "certificate_id"))); if (check_result_value(j_result, G_OK)) { if (update_user_certificate_enabled_scheme_storage(config, ((struct _cert_param *)cls)->j_parameters, username, json_string_value(json_object_get(j_scheme_data, "certificate_id")), json_object_get(j_scheme_data, "enabled") == json_true()) == G_OK) { j_return = json_pack("{si}", "result", G_OK); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register certificate - Error update_user_certificate_enabled_scheme_storage"); j_return = json_pack("{si}", "result", G_ERROR); } } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register certificate - Error get_user_certificate_from_id_scheme_storage"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } } else if (0 == o_strcmp("delete-certificate", json_string_value(json_object_get(j_scheme_data, "register")))) { if (json_string_length(json_object_get(j_scheme_data, "certificate_id"))) { j_result = get_user_certificate_from_id_scheme_storage(config, ((struct _cert_param *)cls)->j_parameters, username, json_string_value(json_object_get(j_scheme_data, "certificate_id"))); if (check_result_value(j_result, G_OK)) { if (delete_user_certificate_scheme_storage(config, ((struct _cert_param *)cls)->j_parameters, username, json_string_value(json_object_get(j_scheme_data, "certificate_id"))) == G_OK) { j_return = json_pack("{si}", "result", G_OK); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register certificate - Error delete_user_certificate_scheme_storage"); j_return = json_pack("{si}", "result", G_ERROR); } } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register certificate - Error get_user_certificate_from_id_scheme_storage"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } /** * * user_auth_scheme_module_register_get * * Get the registration value(s) of the scheme for a user * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_register_get(struct config_module * config, const struct _u_request * http_request, const char * username, void * cls) { UNUSED(http_request); json_t * j_return, * j_result; if (json_object_get(((struct _cert_param *)cls)->j_parameters, "use-scheme-storage") == json_true()) { j_result = get_user_certificate_list_scheme_storage(config, ((struct _cert_param *)cls)->j_parameters, username, 0); if (check_result_value(j_result, G_OK)) { j_return = json_pack("{sis{sOso}}", "result", G_OK, "response", "certificate", json_object_get(j_result, "certificate"), "add-certificate", (json_object_get(((struct _cert_param *)cls)->j_parameters, "use-scheme-storage")==json_true()?json_true():json_false())); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register_get certificate - Error get_user_certificate_list_scheme_storage"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } else { j_result = get_user_certificate_list_user_property(config, ((struct _cert_param *)cls)->j_parameters, username); if (check_result_value(j_result, G_OK)) { json_object_del(j_result, "result"); json_object_set(j_result, "add-certificate", (json_object_get(((struct _cert_param *)cls)->j_parameters, "use-scheme-storage")==json_true()?json_true():json_false())); j_return = json_pack("{sisO}", "result", G_OK, "response", j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register_get certificate - Error get_user_certificate_list_user_property"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } return j_return; } /** * * user_auth_scheme_module_deregister * * Deregister the scheme for a user * Ex: remove certificates, TOTP values, etc. * * @return value: G_OK on success, even if no data has been removed * G_ERROR on another error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_deregister(struct config_module * config, const char * username, void * cls) { json_t * j_result, * j_element = NULL; int ret; size_t index = 0; if (json_object_get(((struct _cert_param *)cls)->j_parameters, "use-scheme-storage") == json_true()) { j_result = get_user_certificate_list_scheme_storage(config, ((struct _cert_param *)cls)->j_parameters, username, 0); if (check_result_value(j_result, G_OK)) { json_array_foreach(json_object_get(j_result, "certificate"), index, j_element) { if (delete_user_certificate_scheme_storage(config, ((struct _cert_param *)cls)->j_parameters, username, json_string_value(json_object_get(j_element, "certificate_id"))) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register certificate - Error delete_user_certificate_scheme_storage"); } } ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_deregister certificate - Error get_user_certificate_list_scheme_storage"); ret = G_ERROR; } json_decref(j_result); } else { ret = G_OK; } return ret; } /** * * user_auth_scheme_module_trigger * * Trigger the scheme for a user * Ex: send the code to a device, generate a challenge, etc. * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter scheme_trigger: data sent to trigger the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_trigger(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_trigger, void * cls) { UNUSED(config); UNUSED(http_request); UNUSED(username); UNUSED(j_scheme_trigger); UNUSED(cls); json_t * j_return = json_pack("{si}", "result", G_OK); return j_return; } /** * * user_auth_scheme_module_validate * * Validate the scheme for a user * Ex: check the code sent to a device, verify the challenge, etc. * * @return value: G_OK on success * G_ERROR_UNAUTHORIZED if validation fails * G_ERROR_PARAM if error in parameters * G_ERROR on another error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter j_scheme_data: data sent to validate the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_validate(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls) { UNUSED(j_scheme_data); int ret = G_OK, res, clean_cert = 0; const char * header_cert = NULL; gnutls_x509_crt_t cert = NULL; gnutls_datum_t cert_dat; unsigned char cert_id[257] = {}; size_t cert_id_len = 256; // Get or parse certificate if ((((struct _cert_param *)cls)->cert_source & G_CERT_SOURCE_TLS) && http_request->client_cert != NULL) { cert = http_request->client_cert; } else if ((((struct _cert_param *)cls)->cert_source & G_CERT_SOURCE_HEADER) && (header_cert = u_map_get(http_request->map_header, json_string_value(json_object_get(((struct _cert_param *)cls)->j_parameters, "header-name")))) != NULL) { if (!gnutls_x509_crt_init(&cert)) { clean_cert = 1; cert_dat.data = (unsigned char *)header_cert; cert_dat.size = o_strlen(header_cert); if (gnutls_x509_crt_import(cert, &cert_dat, GNUTLS_X509_FMT_PEM) < 0) { y_log_message(Y_LOG_LEVEL_DEBUG, "user_auth_scheme_module_validate certificate - Error gnutls_x509_crt_import"); ret = G_ERROR_UNAUTHORIZED; } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "user_auth_scheme_module_validate certificate - Error gnutls_x509_crt_init"); ret = G_ERROR; } } // Validate certificate if (ret == G_OK && cert != NULL) { if ((res = is_user_certificate_valid(config, ((struct _cert_param *)cls)->j_parameters, username, cert)) == G_OK) { if (((struct _cert_param *)cls)->cert_array_len) { ret = is_certificate_valid_from_ca_chain((struct _cert_param *)cls, cert); if (ret != G_OK && ret != G_ERROR_UNAUTHORIZED) { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate certificate - Error is_certificate_valid_from_ca_chain"); ret = G_ERROR; } else if (ret == G_ERROR_UNAUTHORIZED) { y_log_message(Y_LOG_LEVEL_DEBUG, "user_auth_scheme_module_validate certificate - is_certificate_valid_from_ca_chain unauthorized"); } else if (json_object_get(((struct _cert_param *)cls)->j_parameters, "use-scheme-storage") == json_true()) { if (get_certificate_id(cert, cert_id, &cert_id_len) == G_OK) { cert_id[cert_id_len] = '\0'; if (update_user_certificate_last_used_scheme_storage(config, ((struct _cert_param *)cls)->j_parameters, username, (const char *)cert_id, u_map_get_case(http_request->map_header, "user-agent")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate certificate - Error update_user_certificate_last_used_scheme_storage"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate certificate - Error get_certificate_id"); ret = G_ERROR; } } } else { if (json_object_get(((struct _cert_param *)cls)->j_parameters, "use-scheme-storage") == json_true()) { if (get_certificate_id(cert, cert_id, &cert_id_len) == G_OK) { cert_id[cert_id_len] = '\0'; if (update_user_certificate_last_used_scheme_storage(config, ((struct _cert_param *)cls)->j_parameters, username, (const char *)cert_id, u_map_get_case(http_request->map_header, "user-agent")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate certificate - Error update_user_certificate_last_used_scheme_storage"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate certificate - Error get_certificate_id"); ret = G_ERROR; } } } } else if (res == G_ERROR_UNAUTHORIZED || res == G_ERROR_PARAM) { y_log_message(Y_LOG_LEVEL_DEBUG, "user_auth_scheme_module_validate certificate - is_user_certificate_valid unauthorized"); ret = G_ERROR_UNAUTHORIZED; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate certificate - Error is_user_certificate_valid"); ret = G_ERROR; } if (clean_cert) { gnutls_x509_crt_deinit(cert); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "user_auth_scheme_module_validate certificate - No certificate"); ret = G_ERROR_UNAUTHORIZED; } return ret; } /** * * user_auth_scheme_module_identify * * Identify the user using the scheme without the username to be previously given * This functionality isn't available for all schemes, because the scheme authentification * must be triggered without username and the authentication result must contain the username * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * username: string value of the user identified - if the function is called within /auth * response: JSON object, optional - if the function is called within /auth/scheme/trigger * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter j_scheme_data: data sent to validate the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_identify(struct config_module * config, const struct _u_request * http_request, json_t * j_scheme_data, void * cls) { UNUSED(j_scheme_data); int res, clean_cert = 0; const char * header_cert = NULL; gnutls_x509_crt_t cert = NULL; gnutls_datum_t cert_dat; unsigned char cert_id[257] = {}; size_t cert_id_len = 256; json_t * j_result, * j_return; // Get or parse certificate if ((((struct _cert_param *)cls)->cert_source & G_CERT_SOURCE_TLS) && http_request->client_cert != NULL) { cert = http_request->client_cert; } else if ((((struct _cert_param *)cls)->cert_source & G_CERT_SOURCE_HEADER) && (header_cert = u_map_get(http_request->map_header, json_string_value(json_object_get(((struct _cert_param *)cls)->j_parameters, "header-name")))) != NULL) { if (!gnutls_x509_crt_init(&cert)) { clean_cert = 1; cert_dat.data = (unsigned char *)header_cert; cert_dat.size = o_strlen(header_cert); if (gnutls_x509_crt_import(cert, &cert_dat, GNUTLS_X509_FMT_PEM) < 0) { y_log_message(Y_LOG_LEVEL_DEBUG, "user_auth_scheme_module_identify certificate - Error gnutls_x509_crt_import"); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "user_auth_scheme_module_identify certificate - Error gnutls_x509_crt_init"); } } // Validate certificate if (cert != NULL) { j_result = identify_certificate(config, ((struct _cert_param *)cls)->j_parameters, cert); if (check_result_value(j_result, G_OK)) { if (((struct _cert_param *)cls)->cert_array_len) { res = is_certificate_valid_from_ca_chain((struct _cert_param *)cls, cert); if (res != G_OK && res != G_ERROR_UNAUTHORIZED) { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_identify certificate - Error is_certificate_valid_from_ca_chain"); j_return = json_pack("{si}", "result", G_ERROR); } else if (res == G_ERROR_UNAUTHORIZED) { y_log_message(Y_LOG_LEVEL_DEBUG, "user_auth_scheme_module_identify certificate - is_certificate_valid_from_ca_chain unauthorized"); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else if (json_object_get(((struct _cert_param *)cls)->j_parameters, "use-scheme-storage") == json_true()) { if (get_certificate_id(cert, cert_id, &cert_id_len) == G_OK) { cert_id[cert_id_len] = '\0'; if (update_user_certificate_last_used_scheme_storage(config, ((struct _cert_param *)cls)->j_parameters, json_string_value(json_object_get(j_result, "username")), (const char *)cert_id, u_map_get_case(http_request->map_header, "user-agent")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_identify certificate - Error update_user_certificate_last_used_scheme_storage"); j_return = json_pack("{si}", "result", G_ERROR); } else { j_return = json_pack("{sisO}", "result", G_OK, "username", json_object_get(j_result, "username")); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_identify certificate - Error get_certificate_id"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "user_auth_scheme_module_identify certificate - use-scheme-storage isn't set"); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else { if (json_object_get(((struct _cert_param *)cls)->j_parameters, "use-scheme-storage") == json_true()) { if (get_certificate_id(cert, cert_id, &cert_id_len) == G_OK) { cert_id[cert_id_len] = '\0'; if (update_user_certificate_last_used_scheme_storage(config, ((struct _cert_param *)cls)->j_parameters, json_string_value(json_object_get(j_result, "username")), (const char *)cert_id, u_map_get_case(http_request->map_header, "user-agent")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_identify certificate - Error update_user_certificate_last_used_scheme_storage"); j_return = json_pack("{si}", "result", G_ERROR); } else { j_return = json_pack("{sisO}", "result", G_OK, "username", json_object_get(j_result, "username")); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_identify certificate - Error get_certificate_id"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "user_auth_scheme_module_identify certificate - use-scheme-storage isn't set"); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } } else if (check_result_value(j_result, G_ERROR_UNAUTHORIZED) || check_result_value(j_result, G_ERROR_PARAM)) { y_log_message(Y_LOG_LEVEL_DEBUG, "user_auth_scheme_module_identify certificate - is_user_certificate_valid unauthorized"); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_identify certificate - Error is_user_certificate_valid"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); if (clean_cert) { gnutls_x509_crt_deinit(cert); } } else { y_log_message(Y_LOG_LEVEL_DEBUG, "user_auth_scheme_module_identify certificate - No certificate"); j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } return j_return; } glewlwyd-2.6.1/src/scheme/certificate.mariadb.sql000066400000000000000000000015201415646314000220140ustar00rootroot00000000000000DROP TABLE IF EXISTS gs_user_certificate; CREATE TABLE gs_user_certificate ( gsuc_id INT(11) PRIMARY KEY AUTO_INCREMENT, gsuc_mod_name VARCHAR(128) NOT NULL, gsuc_username VARCHAR(128) NOT NULL, gsuc_enabled TINYINT(1) DEFAULT 1, gsuc_x509_certificate_content BLOB DEFAULT NULL, gsuc_x509_certificate_id VARCHAR(128) NOT NULL, gsuc_x509_certificate_dn VARCHAR(512) NOT NULL, gsuc_x509_certificate_issuer_dn VARCHAR(512) NOT NULL, gsuc_activation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gsuc_expiration TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gsuc_last_used TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gsuc_last_user_agent VARCHAR(512) DEFAULT NULL ); CREATE INDEX i_gsuc_username ON gs_user_certificate(gsuc_username); CREATE INDEX i_gsuc_x509_certificate_id ON gs_user_certificate(gsuc_x509_certificate_id); glewlwyd-2.6.1/src/scheme/certificate.postgre.sql000066400000000000000000000014401415646314000221010ustar00rootroot00000000000000DROP TABLE IF EXISTS gs_user_certificate; CREATE TABLE gs_user_certificate ( gsuc_id SERIAL PRIMARY KEY, gsuc_mod_name VARCHAR(128) NOT NULL, gsuc_username VARCHAR(128) NOT NULL, gsuc_enabled SMALLINT DEFAULT 1, gsuc_x509_certificate_content TEXT DEFAULT NULL, gsuc_x509_certificate_id VARCHAR(128) NOT NULL, gsuc_x509_certificate_dn VARCHAR(512) NOT NULL, gsuc_x509_certificate_issuer_dn VARCHAR(512) NOT NULL, gsuc_activation TIMESTAMPTZ NOT NULL DEFAULT NOW(), gsuc_expiration TIMESTAMPTZ DEFAULT NOW(), gsuc_last_used TIMESTAMPTZ DEFAULT NOW(), gsuc_last_user_agent VARCHAR(512) DEFAULT NULL ); CREATE INDEX i_gsuc_username ON gs_user_certificate(gsuc_username); CREATE INDEX i_gsuc_x509_certificate_id ON gs_user_certificate(gsuc_x509_certificate_id); glewlwyd-2.6.1/src/scheme/certificate.sqlite.sql000066400000000000000000000014341415646314000217220ustar00rootroot00000000000000DROP TABLE IF EXISTS gs_user_certificate; CREATE TABLE gs_user_certificate ( gsuc_id INTEGER PRIMARY KEY AUTOINCREMENT, gsuc_mod_name TEXT NOT NULL, gsuc_username TEXT NOT NULL, gsuc_enabled INTEGER DEFAULT 1, gsuc_x509_certificate_content TEXT DEFAULT NULL, gsuc_x509_certificate_id TEXT NOT NULL, gsuc_x509_certificate_dn TEXT NOT NULL, gsuc_x509_certificate_issuer_dn TEXT NOT NULL, gsuc_activation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gsuc_expiration TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gsuc_last_used TIMESTAMP DEFAULT CURRENT_TIMESTAMP, gsuc_last_user_agent TEXT DEFAULT NULL ); CREATE INDEX i_gsuc_username ON gs_user_certificate(gsuc_username); CREATE INDEX i_gsuc_x509_certificate_id ON gs_user_certificate(gsuc_x509_certificate_id); glewlwyd-2.6.1/src/scheme/email.c000066400000000000000000000736411415646314000166630ustar00rootroot00000000000000/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * Random code sent by e-mail authentication scheme module * * Copyright 2019-2020 Nicolas Mora * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . * */ #include #include #include #include #include #include "glewlwyd-common.h" #define GLEWLWYD_SCHEME_CODE_TABLE "gs_code" #define GLEWLWYD_SCHEME_CODE_DEFAULT_LENGTH 6 #define GLEWLWYD_SCHEME_CODE_DURATION 900 static int generate_new_code(struct config_module * config, json_t * j_param, const char * username, char * code, size_t len) { json_t * j_query; int res, ret; char * code_hash = NULL; j_query = json_pack("{sss{si}s{sssO}}", "table", GLEWLWYD_SCHEME_CODE_TABLE, "set", "gsc_enabled", 0, "where", "gsc_username", username, "gsc_mod_name", json_object_get(j_param, "mod_name")); res = h_delete(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { if (rand_code(code, len)) { if ((code_hash = generate_hash(config->hash_algorithm, code)) != NULL) { j_query = json_pack("{sss{sOssss}}", "table", GLEWLWYD_SCHEME_CODE_TABLE, "values", "gsc_mod_name", json_object_get(j_param, "mod_name"), "gsc_username", username, "gsc_code_hash", code_hash); res = h_insert(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_new_code - Error executing j_query (1)"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } o_free(code_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_new_code - Error generate_hash"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_new_code - Error rand_code"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_new_code - Error executing j_query (2)"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static int check_code(struct config_module * config, json_t * j_param, const char * username, const char * code) { json_t * j_query, * j_result; int res, ret; char * code_hash = NULL, * issued_at_clause = NULL; time_t now; if ((code_hash = generate_hash(config->hash_algorithm, code)) != NULL) { time(&now); if (config->conn->type==HOEL_DB_TYPE_MARIADB) { issued_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now - (time_t)json_integer_value(json_object_get(j_param, "code-duration")))); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { issued_at_clause = msprintf("> TO_TIMESTAMP(%u)", (now - (time_t)json_integer_value(json_object_get(j_param, "code-duration")))); } else { // HOEL_DB_TYPE_SQLITE issued_at_clause = msprintf("> %u", (now - (time_t)json_integer_value(json_object_get(j_param, "code-duration")))); } j_query = json_pack("{sss{sOsssssis{ssss}}}", "table", GLEWLWYD_SCHEME_CODE_TABLE, "where", "gsc_mod_name", json_object_get(j_param, "mod_name"), "gsc_username", username, "gsc_code_hash", code_hash, "gsc_enabled", 1, "gsc_issued_at", "operator", "raw", "value", issued_at_clause); res = h_select(config->conn, j_query, &j_result, NULL); o_free(issued_at_clause); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_query = json_pack("{sss{si}s{sOssss}}", "table", GLEWLWYD_SCHEME_CODE_TABLE, "set", "gsc_enabled", 0, "where", "gsc_mod_name", json_object_get(j_param, "mod_name"), "gsc_username", username, "gsc_code_hash", code_hash); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_code - Error executing j_query (2)"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { ret = G_ERROR_UNAUTHORIZED; } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_code - Error executing j_query (1)"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } o_free(code_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_code - Error generate_hash"); ret = G_ERROR; } return ret; } static json_t * is_scheme_parameters_valid(json_t * j_params) { json_t * j_errors = json_array(), * j_result, * j_template = NULL; const char * lang = NULL; int nb_default_lang = 0; if (j_errors != NULL) { if (!json_is_object(j_params)) { json_array_append_new(j_errors, json_string("parameters must be a JSON object")); } else { if (json_object_get(j_params, "code-length") != NULL && (!json_is_integer(json_object_get(j_params, "code-length")) || json_integer_value(json_object_get(j_params, "code-length")) <= 0)) { json_array_append_new(j_errors, json_string("code-length is optional and must be a positive integer")); } else if (json_object_get(j_params, "code-length") == NULL) { json_object_set_new(j_params, "code-length", json_integer(GLEWLWYD_SCHEME_CODE_DEFAULT_LENGTH)); } if (json_object_get(j_params, "code-duration") != NULL && (!json_is_integer(json_object_get(j_params, "code-duration")) || json_integer_value(json_object_get(j_params, "code-duration")) <= 0)) { json_array_append_new(j_errors, json_string("code-duration is optional and must be a positive integer")); } else if (json_object_get(j_params, "code-duration") == NULL) { json_object_set_new(j_params, "code-duration", json_integer(GLEWLWYD_SCHEME_CODE_DEFAULT_LENGTH)); } if (!json_string_length(json_object_get(j_params, "host"))) { json_array_append_new(j_errors, json_string("host is mandatory and must be a non empty string")); } if (json_object_get(j_params, "port") != NULL && (!json_is_integer(json_object_get(j_params, "port")) || json_integer_value(json_object_get(j_params, "port")) < 0 || json_integer_value(json_object_get(j_params, "port")) > 65535)) { json_array_append_new(j_errors, json_string("port is optional and must be a integer between 0 and 65535")); } else if (json_object_get(j_params, "port") == NULL) { json_object_set_new(j_params, "port", json_integer(0)); } if (json_object_get(j_params, "use-tls") != NULL && !json_is_boolean(json_object_get(j_params, "use-tls"))) { json_array_append_new(j_errors, json_string("use-tls is optional and must be a boolean")); } if (json_object_get(j_params, "check-certificate") != NULL && !json_is_boolean(json_object_get(j_params, "check-certificate"))) { json_array_append_new(j_errors, json_string("check-certificate is optional and must be a boolean")); } if (json_object_get(j_params, "user") != NULL && !json_is_string(json_object_get(j_params, "user"))) { json_array_append_new(j_errors, json_string("user is optional and must be a string")); } if (json_object_get(j_params, "password") != NULL && !json_is_string(json_object_get(j_params, "password"))) { json_array_append_new(j_errors, json_string("password is optional and must be a string")); } if (!json_string_length(json_object_get(j_params, "from"))) { json_array_append_new(j_errors, json_string("from is mandatory and must be a non empty string")); } if (json_object_get(j_params, "templates") == NULL) { if (json_object_get(j_params, "subject") != NULL && !json_string_length(json_object_get(j_params, "subject"))) { json_array_append_new(j_errors, json_string("subject is mandatory and must be a non empty string")); } if (json_object_get(j_params, "body-pattern") != NULL && !json_string_length(json_object_get(j_params, "body-pattern"))) { json_array_append_new(j_errors, json_string("body-pattern is mandatory and must be a non empty string")); } } else { if (json_object_get(j_params, "content-type") != NULL && !json_string_length(json_object_get(j_params, "content-type"))) { json_array_append_new(j_errors, json_string("content-type is optional and must be a string")); } if (!json_string_length(json_object_get(j_params, "user-lang-property"))) { json_array_append_new(j_errors, json_string("user-lang-property is mandatory and must be a non empty string")); } if (!json_is_object(json_object_get(j_params, "templates"))) { json_array_append_new(j_errors, json_string("templates is mandatory and must be a JSON object")); } else { json_object_foreach(json_object_get(j_params, "templates"), lang, j_template) { if (!json_is_object(j_template)) { json_array_append_new(j_errors, json_string("template content must be a JSON object")); } else { if (!json_is_boolean(json_object_get(j_template, "defaultLang"))) { json_array_append_new(j_errors, json_string("defaultLang is madatory in a template and must be a JSON object")); } if (!json_string_length(json_object_get(j_template, "subject"))) { json_array_append_new(j_errors, json_string("subject is mandatory for default lang and must be a non empty string")); } if (json_object_get(j_template, "body") != NULL && !json_string_length(json_object_get(j_template, "body"))) { json_array_append_new(j_errors, json_string("body is mandatory for default lang and must be a non empty string")); } if (json_object_get(j_template, "defaultLang") == json_true()) { nb_default_lang++; } } } if (nb_default_lang != 1) { json_array_append_new(j_errors, json_string("template list must have only one defaultLang set to true")); } } } } if (json_array_size(j_errors)) { j_result = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_errors); } else { j_result = json_pack("{si}", "result", G_OK); } json_decref(j_errors); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_scheme_parameters_valid - Error allocating resources for j_errors"); j_result = json_pack("{si}", "result", G_ERROR_MEMORY); } return j_result; } static const char * get_template_property(json_t * j_params, json_t * j_user, const char * property_field) { json_t * j_template = NULL; const char * property = NULL, * property_default = NULL, * lang = NULL, * user_lang = json_string_value(json_object_get(j_user, json_string_value(json_object_get(j_params, "user-lang-property")))); if (json_object_get(j_params, "templates") == NULL) { property = json_string_value(json_object_get(j_params, property_field)); } else { json_object_foreach(json_object_get(j_params, "templates"), lang, j_template) { if (0 == o_strcmp(user_lang, lang)) { property = json_string_value(json_object_get(j_template, property_field)); } if (json_object_get(j_template, "defaultLang") == json_true()) { property_default = json_string_value(json_object_get(j_template, property_field)); } } if (property == NULL) { property = property_default; } } return property; } /** * * user_auth_scheme_module_load * * Executed once when Glewlwyd service is loaded * Used to identify the module and to show its parameters on init * You can also use it to load resources that are required once for all * instance modules for example * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * name: string, mandatory, name of the module, must be unique among other scheme modules * display_name: string, optional, long name of the module * description: string, optional, description for the module * parameters: object, optional, parameters description for the module * } * * Example: * { * result: G_OK, * name: "mock", * display_name: "Mock scheme module", * description: "Mock scheme module for glewlwyd tests", * parameters: { * mock-value: { * type: "string", * mandatory: true * } * } * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ json_t * user_auth_scheme_module_load(struct config_module * config) { UNUSED(config); return json_pack("{sissssss}", "result", G_OK, "name", "email", "display_name", "Email code", "description", "Send a code via email to authenticate the user"); } /** * * user_auth_scheme_module_unload * * Executed once when Glewlwyd service is stopped * You can also use it to release resources that are required once for all * instance modules for example * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ int user_auth_scheme_module_unload(struct config_module * config) { UNUSED(config); return G_OK; } /** * * user_auth_scheme_module_init * * Initialize an instance of this module declared in Glewlwyd service. * If required, you must dynamically allocate a pointer to the configuration * for this instance and pass it to *cls * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, G_ERROR_PARAM on input parameters error, another value on error) * error: array of strings containg the list of input errors, mandatory on result G_ERROR_PARAM, ignored otherwise * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_parameters: used to initialize an instance in JSON format * The module must validate itself its parameters * @parameter mod_name: module name in glewlwyd service * @parameter cls: must return an allocated void * pointer that will be sent back * as void * in all module functions * */ json_t * user_auth_scheme_module_init(struct config_module * config, json_t * j_parameters, const char * mod_name, void ** cls) { UNUSED(config); json_t * j_result, * j_return; char * str_error; j_result = is_scheme_parameters_valid(j_parameters); if (check_result_value(j_result, G_OK)) { json_object_set_new(j_parameters, "mod_name", json_string(mod_name)); *cls = json_incref(j_parameters); j_return = json_pack("{si}", "result", G_OK); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init email - Error in parameters"); str_error = json_dumps(json_object_get(j_result, "error"), JSON_ENCODE_ANY); y_log_message(Y_LOG_LEVEL_ERROR, str_error); o_free(str_error); j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", json_object_get(j_result, "error")); } json_decref(j_result); return j_return; } /** * * user_auth_scheme_module_close * * Close an instance of this module declared in Glewlwyd service. * You must free the memory previously allocated in * the user_auth_scheme_module_init function as void * cls * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_close(struct config_module * config, void * cls) { UNUSED(config); json_decref((json_t *)cls); return G_OK; } /** * * user_auth_scheme_module_can_use * * Validate if the user is allowed to use this scheme prior to the * authentication or registration * * @return value: GLEWLWYD_IS_REGISTERED - User can use scheme and has registered * GLEWLWYD_IS_AVAILABLE - User can use scheme but hasn't registered * GLEWLWYD_IS_NOT_AVAILABLE - User can't use scheme * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_can_use(struct config_module * config, const char * username, void * cls) { UNUSED(cls); json_t * j_user; int ret; j_user = config->glewlwyd_module_callback_get_user(config, username); if (check_result_value(j_user, G_OK)) { ret = json_object_get(json_object_get(j_user, "user"), "email") != NULL?GLEWLWYD_IS_REGISTERED:GLEWLWYD_IS_NOT_AVAILABLE; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_can_use mail - Error glewlwyd_module_callback_get_user"); ret = GLEWLWYD_IS_NOT_AVAILABLE; } json_decref(j_user); return ret; } /** * * user_auth_scheme_module_register * * Register the scheme for a user * Ex: add a certificate, add new TOTP values, etc. * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the HTTP API * @parameter username: username to identify the user * @parameter j_scheme_data: additional data used to register the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_register(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls) { UNUSED(config); UNUSED(http_request); UNUSED(j_scheme_data); return json_pack("{si}", "result", (user_auth_scheme_module_can_use(config, username, cls) == GLEWLWYD_IS_REGISTERED?G_OK:G_ERROR_PARAM)); } /** * * user_auth_scheme_module_register_get * * Get the registration value(s) of the scheme for a user * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_register_get(struct config_module * config, const struct _u_request * http_request, const char * username, void * cls) { UNUSED(config); UNUSED(http_request); return json_pack("{si}", "result", (user_auth_scheme_module_can_use(config, username, cls) == GLEWLWYD_IS_REGISTERED)?G_OK:G_ERROR_PARAM); } /** * * user_auth_scheme_module_deregister * * Deregister the scheme for a user * Ex: remove certificates, TOTP values, etc. * * @return value: G_OK on success, even if no data has been removed * G_ERROR on another error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_deregister(struct config_module * config, const char * username, void * cls) { UNUSED(config); UNUSED(username); UNUSED(cls); return G_OK; } /** * * user_auth_scheme_module_trigger * * Trigger the scheme for a user * Ex: send the code to a device, generate a challenge, etc. * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter scheme_trigger: data sent to trigger the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_trigger(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_trigger, void * cls) { UNUSED(j_scheme_trigger); json_t * j_user, * j_param = (json_t *)cls; int ret; char * code = NULL, * body; const char * ip_source = get_ip_source(http_request); if (user_auth_scheme_module_can_use(config, username, cls) == GLEWLWYD_IS_REGISTERED) { j_user = config->glewlwyd_module_callback_get_user(config, username); if (check_result_value(j_user, G_OK)) { if ((code = o_malloc((json_integer_value(json_object_get(j_param, "code-length")) + 1)*sizeof(char))) != NULL) { memset(code, 0, (json_integer_value(json_object_get(j_param, "code-length")) + 1)); if (generate_new_code(config, j_param, username, code, json_integer_value(json_object_get(j_param, "code-length"))) == G_OK) { if ((body = str_replace(get_template_property(j_param, json_object_get(j_user, "user"), "body-pattern"), "{CODE}", code)) != NULL) { if (ulfius_send_smtp_rich_email(json_string_value(json_object_get(j_param, "host")), json_integer_value(json_object_get(j_param, "port")), json_object_get(j_param, "use-tls")==json_true()?1:0, json_object_get(j_param, "verify-certificate")==json_false()?0:1, json_string_length(json_object_get(j_param, "user"))?json_string_value(json_object_get(j_param, "user")):NULL, json_string_length(json_object_get(j_param, "password"))?json_string_value(json_object_get(j_param, "password")):NULL, json_string_value(json_object_get(j_param, "from")), json_string_value(json_object_get(json_object_get(j_user, "user"), "email")), NULL, NULL, json_string_length(json_object_get(j_param, "content-type"))?json_string_value(json_object_get(j_param, "content-type")):"text/plain; charset=utf-8", get_template_property(j_param, json_object_get(j_user, "user"), "subject"), body) == G_OK) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Scheme email - code sent for username %s at IP Address %s", username, ip_source); ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_trigger mail - Error ulfius_send_smtp_email"); ret = G_ERROR_MEMORY; } o_free(body); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_trigger mail - Error str_replace"); ret = G_ERROR_MEMORY; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_trigger mail - Error generate_new_code"); ret = G_ERROR_MEMORY; } o_free(code); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_trigger mail - Error allocating resources for code"); ret = G_ERROR_MEMORY; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_trigger mail - Error glewlwyd_module_callback_get_user"); ret = G_ERROR; } json_decref(j_user); } else { ret = G_ERROR_PARAM; } return json_pack("{si}", "result", ret); } /** * * user_auth_scheme_module_validate * * Validate the scheme for a user * Ex: check the code sent to a device, verify the challenge, etc. * * @return value: G_OK on success * G_ERROR_UNAUTHORIZED if validation fails * G_ERROR_PARAM if error in parameters * G_ERROR on another error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter j_scheme_data: data sent to validate the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_validate(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls) { UNUSED(config); UNUSED(http_request); int ret, res; json_t * j_param = (json_t *)cls; if (user_auth_scheme_module_can_use(config, username, cls) != GLEWLWYD_IS_REGISTERED) { ret = G_ERROR_UNAUTHORIZED; } else if (json_object_get(j_scheme_data, "code") != NULL && json_is_string(json_object_get(j_scheme_data, "code")) && (unsigned int)json_integer_value(json_object_get(j_param, "code-length")) == json_string_length(json_object_get(j_scheme_data, "code"))) { if ((res = check_code(config, j_param, username, json_string_value(json_object_get(j_scheme_data, "code")))) == G_OK) { ret = G_OK; } else if (res == G_ERROR_UNAUTHORIZED) { ret = G_ERROR_UNAUTHORIZED; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate mail - Error check_code"); ret = res; } } else { ret = G_ERROR_PARAM; } return ret; } /** * * user_auth_scheme_module_identify * * Identify the user using the scheme without the username to be previously given * This functionality isn't available for all schemes, because the scheme authentification * must be triggered without username and the authentication result must contain the username * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * username: string value of the user identified - if the function is called within /auth * response: JSON object, optional - if the function is called within /auth/scheme/trigger * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter j_scheme_data: data sent to validate the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_identify(struct config_module * config, const struct _u_request * http_request, json_t * j_scheme_data, void * cls) { UNUSED(config); UNUSED(http_request); UNUSED(j_scheme_data); UNUSED(cls); return json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } glewlwyd-2.6.1/src/scheme/email.mariadb.sql000066400000000000000000000006271415646314000206300ustar00rootroot00000000000000DROP TABLE IF EXISTS gs_code; CREATE TABLE gs_code ( gsc_id INT(11) PRIMARY KEY AUTO_INCREMENT, gsc_mod_name VARCHAR(128) NOT NULL, gsc_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gsc_username VARCHAR(128) NOT NULL, gsc_enabled TINYINT(1) DEFAULT 1, gsc_code_hash VARCHAR(128), gsc_result TINYINT(1) DEFAULT 0 ); CREATE INDEX i_gssc_username ON gs_code(gsc_username); glewlwyd-2.6.1/src/scheme/email.postgre.sql000066400000000000000000000005711415646314000207120ustar00rootroot00000000000000DROP TABLE IF EXISTS gss_code; CREATE TABLE gs_code ( gsc_id SERIAL PRIMARY KEY, gsc_mod_name VARCHAR(128) NOT NULL, gsc_issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gsc_username VARCHAR(128) NOT NULL, gsc_enabled SMALLINT DEFAULT 1, gsc_code_hash VARCHAR(128), gsc_result SMALLINT DEFAULT 0 ); CREATE INDEX i_gsc_username ON gs_code(gsc_username); glewlwyd-2.6.1/src/scheme/email.sqlite.sql000066400000000000000000000005671415646314000205350ustar00rootroot00000000000000DROP TABLE IF EXISTS gs_code; CREATE TABLE gs_code ( gsc_id INTEGER PRIMARY KEY AUTOINCREMENT, gsc_mod_name TEXT NOT NULL, gsc_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gsc_username TEXT NOT NULL, gsc_enabled INTEGER DEFAULT 1, gsc_code_hash TEXT, gsc_result INTEGER DEFAULT 0 ); CREATE INDEX i_gsc_username ON gs_code(gsc_username); glewlwyd-2.6.1/src/scheme/http.c000066400000000000000000000472631415646314000165540ustar00rootroot00000000000000/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * HTTP Basic authentication scheme module * * Copyright 2019-2020 Nicolas Mora * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . * */ #include #include #include #include #include "glewlwyd-common.h" /** * * Note on the user auth scheme module * * It's possible for the scheme module to get or store any value in the user object returned by the functions * struct config_module.glewlwyd_module_callback_get_user() * struct config_module.glewlwyd_module_callback_set_user() * * Although, the module can't know if any value, other than "name", "password", "email" or "enabled" can be added or updated by the scheme module * The scheme module can store its specific data for each user by itself or store the data in the user object, or both, this will depend on the implementation * * The format of the structure config_module is: * * struct config_module { * const char * external_url; // Absolute url of the glewlwyd service * const char * login_url; // Relative url of the login page * const char * admin_scope; // Value of the g_admin scope * const char * profile_scope; // Value of the g_profile scope * struct _h_connection * conn; // Hoel structure to access to the database * digest_algorithm hash_algorithm; // Hash algorithm used in Glewlwyd * struct config_elements * glewlwyd_config; // Pointer to the global config structure * // Function used to return a user object * json_t * (* glewlwyd_module_callback_get_user)(struct config_module * config, const char * username); * // Function used to update a user * int (* glewlwyd_module_callback_set_user)(struct config_module * config, const char * username, json_t * j_user); * // Function used to check the validity of a user's password * int (* glewlwyd_module_callback_check_user_password)(struct config_module * config, const char * username, const char * password); * }; * */ /** * Builds the auth_basic_user from the format given in parameter and the user data */ static char * format_auth_basic_user(const char * format, json_t * j_user) { const char * format_offset = format; char * result = NULL, * sub; int ret = G_OK; while (o_strchr(format_offset, '{') != NULL && o_strchr(format_offset, '}') != NULL && ret == G_OK) { // Append until '{' if (o_strchr(format_offset, '{') != format_offset) { result = mstrcatf(result, "%.*s", (o_strchr(format_offset, '{') - format_offset), format_offset); } // extract string between '{' and '}' sub = o_strndup(o_strchr(format_offset, '{')+1, (o_strchr(format_offset, '}')-o_strchr(format_offset, '{')-1)); // Get value from user if (json_is_string(json_object_get(j_user, sub))) { result = mstrcatf(result, "%s", json_string_value(json_object_get(j_user, sub))); format_offset = o_strchr(format_offset, '}') + 1; } else { y_log_message(Y_LOG_LEVEL_ERROR, "format_auth_basic_user - Error, property %s missing or invalid for user %s", sub, json_string_value(json_object_get(j_user, "username"))); ret = G_ERROR; } o_free(sub); } result = mstrcatf(result, "%s", format_offset); if (ret != G_OK) { o_free(result); result = NULL; } return result; } /** * * user_auth_scheme_module_load * * Executed once when Glewlwyd service is started * Used to identify the module and to show its parameters on init * You can also use it to load resources that are required once for all * instance modules for example * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * name: string, mandatory, name of the module, must be unique among other scheme modules * display_name: string, optional, long name of the module * description: string, optional, description for the module * parameters: object, optional, parameters description for the module * } * * Example: * { * result: G_OK, * name: "mock", * display_name: "Mock scheme module", * description: "Mock scheme module for glewlwyd tests", * parameters: { * mock-value: { * type: "string", * mandatory: true * } * } * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ json_t * user_auth_scheme_module_load(struct config_module * config) { UNUSED(config); return json_pack("{sissssss}", "result", G_OK, "name", "http", "display_name", "HTTP Basic Authentication", "description", "Authenticate users against a HTTP webservice that uses HTTP Basic Authentication"); } /** * * user_auth_scheme_module_unload * * Executed once when Glewlwyd service is stopped * You can also use it to release resources that are required once for all * instance modules for example * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ int user_auth_scheme_module_unload(struct config_module * config) { UNUSED(config); return G_OK; } /** * * user_auth_scheme_module_init * * Initialize an instance of this module declared in Glewlwyd service. * If required, you must dynamically allocate a pointer to the configuration * for this instance and pass it to *cls * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, G_ERROR_PARAM on input parameters error, another value on error) * error: array of strings containg the list of input errors, mandatory on result G_ERROR_PARAM, ignored otherwise * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_parameters: used to initialize an instance in JSON format * The module must validate itself its parameters * @parameter mod_name: module name in glewlwyd service * @parameter cls: will contain an allocated void * pointer that will be sent back * as void * in all module functions * */ json_t * user_auth_scheme_module_init(struct config_module * config, json_t * j_parameters, const char * mod_name, void ** cls) { UNUSED(config); UNUSED(mod_name); json_t * j_return = NULL; int ret; if (json_is_object(j_parameters)) { ret = G_OK; if (!json_string_length(json_object_get(j_parameters, "url"))) { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init http - parameter url is mandatory must be a non empty string"); j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "parameter url is mandatory must be a non empty string"); ret = G_ERROR_PARAM; } else if (json_object_get(j_parameters, "check-server-certificate") != NULL && !json_is_boolean(json_object_get(j_parameters, "check-server-certificate"))) { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init http - parameter check-server-certificate is optional and must be a boolean"); j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "parameter check-server-certificate is optional and must be a boolean"); } else if (json_string_length(json_object_get(j_parameters, "username-format")) && (o_strchr(json_string_value(json_object_get(j_parameters, "username-format")), '{') == NULL || o_strchr(json_string_value(json_object_get(j_parameters, "username-format")), '}') == NULL)) { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init http - parameter username-format is optional and must contain a property name, e.g. {username}"); j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "parameter username-format is optional and must contain a property name, e.g. {username}"); ret = G_ERROR_PARAM; } if (ret == G_OK) { j_return = json_pack("{si}", "result", G_OK); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init http - parameters must be a JSON object"); ret = G_ERROR_PARAM; j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "parameters must be a JSON object"); } if (ret == G_OK) { *cls = json_incref(j_parameters); } return j_return; } /** * * user_auth_scheme_module_close * * Close an instance of this module declared in Glewlwyd service. * You must free the memory previously allocated in * the user_auth_scheme_module_init function as void * cls * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_close(struct config_module * config, void * cls) { UNUSED(config); json_decref((json_t *)cls); return G_OK; } /** * * user_auth_scheme_module_can_use * * Validate if the user is allowed to use this scheme prior to the * authentication or registration * * @return value: GLEWLWYD_IS_REGISTERED - User can use scheme and has registered * GLEWLWYD_IS_AVAILABLE - User can use scheme but hasn't registered * GLEWLWYD_IS_NOT_AVAILABLE - User can't use scheme * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_can_use(struct config_module * config, const char * username, void * cls) { UNUSED(config); UNUSED(username); UNUSED(cls); return GLEWLWYD_IS_REGISTERED; } /** * * user_auth_scheme_module_register * * Register the scheme for a user * Ex: add a certificate, add new TOTP values, etc. * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the HTTP API * @parameter username: username to identify the user * @parameter j_scheme_data: additional data used to register the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_register(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls) { UNUSED(config); UNUSED(http_request); UNUSED(username); UNUSED(j_scheme_data); UNUSED(cls); return json_pack("{siso}", "result", G_OK, "response", json_true()); } /** * * user_auth_scheme_module_register_get * * Get the registration value(s) of the scheme for a user * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_register_get(struct config_module * config, const struct _u_request * http_request, const char * username, void * cls) { UNUSED(config); UNUSED(http_request); UNUSED(username); UNUSED(cls); return json_pack("{sisO}", "result", G_OK, "response", json_true()); } /** * * user_auth_scheme_module_deregister * * Deregister the scheme for a user * Ex: remove certificates, TOTP values, etc. * * @return value: G_OK on success, even if no data has been removed * G_ERROR on another error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_deregister(struct config_module * config, const char * username, void * cls) { UNUSED(config); UNUSED(username); UNUSED(cls); return G_OK; } /** * * user_auth_scheme_module_trigger * * Trigger the scheme for a user * Ex: send the code to a device, generate a challenge, etc. * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter scheme_trigger: data sent to trigger the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_trigger(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_trigger, void * cls) { UNUSED(config); UNUSED(http_request); UNUSED(username); UNUSED(j_scheme_trigger); UNUSED(cls); return json_pack("{si}", "result", G_OK); } /** * * user_auth_scheme_module_validate * * Validate the scheme for a user * Ex: check the code sent to a device, verify the challenge, etc. * * @return value: G_OK on success * G_ERROR_UNAUTHORIZED if validation fails * G_ERROR_PARAM if error in parameters * G_ERROR on another error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter j_scheme_data: data sent to validate the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_validate(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls) { UNUSED(config); UNUSED(http_request); struct _u_request request; struct _u_response response; int res, ret; json_t * j_user = NULL; if (json_string_length(json_object_get(j_scheme_data, "password"))) { ulfius_init_request(&request); ulfius_init_response(&response); request.http_verb = o_strdup("GET"); request.http_url = o_strdup(json_string_value(json_object_get((json_t *)cls, "url"))); if (json_object_get((json_t *)cls, "check-server-certificate") == json_false()) { request.check_server_certificate = 0; } if (json_string_length(json_object_get((json_t *)cls, "username-format"))) { j_user = config->glewlwyd_module_callback_get_user(config, username); if (check_result_value(j_user, G_OK)) { request.auth_basic_user = format_auth_basic_user(json_string_value(json_object_get((json_t *)cls, "username-format")), json_object_get(j_user, "user")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate http - Error glewlwyd_module_callback_get_user for username %s", username); } json_decref(j_user); } else { request.auth_basic_user = o_strdup(username); } request.auth_basic_password = o_strdup(json_string_value(json_object_get(j_scheme_data, "password"))); if (request.auth_basic_user != NULL && request.auth_basic_password != NULL) { res = ulfius_send_http_request(&request, &response); if (res == H_OK) { if (response.status == 200) { ret = G_OK; } else { if (response.status != 401 && response.status != 403) { y_log_message(Y_LOG_LEVEL_WARNING, "user_auth_scheme_module_validate http - Error connecting to webservice %s, response status is %d", request.http_url, response.status); } ret = G_ERROR_UNAUTHORIZED; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate http - Error ulfius_send_http_request"); ret = G_ERROR_UNAUTHORIZED; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate http - Error invalid auth_basic values"); ret = G_ERROR_UNAUTHORIZED; } ulfius_clean_request(&request); ulfius_clean_response(&response); } else { ret = G_ERROR_UNAUTHORIZED; } return ret; } /** * * user_auth_scheme_module_identify * * Identify the user using the scheme without the username to be previously given * This functionality isn't available for all schemes, because the scheme authentification * must be triggered without username and the authentication result must contain the username * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * username: string value of the user identified - if the function is called within /auth * response: JSON object, optional - if the function is called within /auth/scheme/trigger * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter j_scheme_data: data sent to validate the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_identify(struct config_module * config, const struct _u_request * http_request, json_t * j_scheme_data, void * cls) { UNUSED(config); UNUSED(http_request); UNUSED(j_scheme_data); UNUSED(cls); return json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } glewlwyd-2.6.1/src/scheme/mock.c000066400000000000000000000435771415646314000165320ustar00rootroot00000000000000/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * Mock authentication scheme module * * Copyright 2018-2020 Nicolas Mora * * The MIT License (MIT) * * 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. * */ #include #include #include #include #include "glewlwyd-common.h" /** * * Note on the user auth scheme module * * It's possible for the scheme module to get or store any value in the user object returned by the functions * struct config_module.glewlwyd_module_callback_get_user() * struct config_module.glewlwyd_module_callback_set_user() * * Although, the module can't know if any value, other than "name", "password", "email" or "enabled" can be added or updated by the scheme module * The scheme module can store its specific data for each user by itself or store the data in the user object, or both, this will depend on the implementation * * The format of the structure config_module is: * * struct config_module { * const char * external_url; // Absolute url of the glewlwyd service * const char * login_url; // Relative url of the login page * const char * admin_scope; // Value of the g_admin scope * const char * profile_scope; // Value of the g_profile scope * struct _h_connection * conn; // Hoel structure to access to the database * digest_algorithm hash_algorithm; // Hash algorithm used in Glewlwyd * struct config_elements * glewlwyd_config; // Pointer to the global config structure * // Function used to return a user object * json_t * (* glewlwyd_module_callback_get_user)(struct config_module * config, const char * username); * // Function used to update a user * int (* glewlwyd_module_callback_set_user)(struct config_module * config, const char * username, json_t * j_user); * // Function used to check the validity of a user's password * int (* glewlwyd_module_callback_check_user_password)(struct config_module * config, const char * username, const char * password); * }; * */ /** * * Config structure specific for the module * An instance of this structure will be created in the init function * and passed as void * parameter in each function * So every function will have access to the module configuration * */ struct mock_config { json_t * j_param; json_t * j_users; }; /** * * user_auth_scheme_module_load * * Executed once when Glewlwyd service is started * Used to identify the module and to show its parameters on init * You can also use it to load resources that are required once for all * instance modules for example * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * name: string, mandatory, name of the module, must be unique among other scheme modules * display_name: string, optional, long name of the module * description: string, optional, description for the module * parameters: object, optional, parameters description for the module * } * * Example: * { * result: G_OK, * name: "mock", * display_name: "Mock scheme module", * description: "Mock scheme module for glewlwyd tests", * parameters: { * mock-value: { * type: "string", * mandatory: true * } * } * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ json_t * user_auth_scheme_module_load(struct config_module * config) { UNUSED(config); return json_pack("{sissssss}", "result", G_OK, "name", "mock", "display_name", "Mock", "description", "Mock scheme module for glewlwyd tests"); } /** * * user_auth_scheme_module_unload * * Executed once when Glewlwyd service is stopped * You can also use it to release resources that are required once for all * instance modules for example * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ int user_auth_scheme_module_unload(struct config_module * config) { UNUSED(config); return G_OK; } /** * * user_auth_scheme_module_init * * Initialize an instance of this module declared in Glewlwyd service. * If required, you must dynamically allocate a pointer to the configuration * for this instance and pass it to *cls * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, G_ERROR_PARAM on input parameters error, another value on error) * error: array of strings containg the list of input errors, mandatory on result G_ERROR_PARAM, ignored otherwise * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_parameters: used to initialize an instance in JSON format * The module must validate itself its parameters * @parameter mod_name: module name in glewlwyd service * @parameter cls: will contain an allocated void * pointer that will be sent back * as void * in all module functions * */ json_t * user_auth_scheme_module_init(struct config_module * config, json_t * j_parameters, const char * mod_name, void ** cls) { UNUSED(config); UNUSED(mod_name); json_t * j_return; if (json_object_get(j_parameters, "error") == NULL) { *cls = o_malloc(sizeof(struct mock_config)); ((struct mock_config *)*cls)->j_param = json_incref(j_parameters); ((struct mock_config *)*cls)->j_users = json_object(); j_return = json_pack("{si}", "result", G_OK); } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "Error in parameters"); } return j_return; } /** * * user_auth_scheme_module_close * * Close an instance of this module declared in Glewlwyd service. * You must free the memory previously allocated in * the user_auth_scheme_module_init function as void * cls * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_close(struct config_module * config, void * cls) { UNUSED(config); json_decref(((struct mock_config *)cls)->j_param); json_decref(((struct mock_config *)cls)->j_users); o_free(cls); return G_OK; } /** * * user_auth_scheme_module_can_use * * Validate if the user is allowed to use this scheme prior to the * authentication or registration * * @return value: GLEWLWYD_IS_REGISTERED - User can use scheme and has registered * GLEWLWYD_IS_AVAILABLE - User can use scheme but hasn't registered * GLEWLWYD_IS_NOT_AVAILABLE - User can't use scheme * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_can_use(struct config_module * config, const char * username, void * cls) { UNUSED(config); if (json_object_get(((struct mock_config *)cls)->j_users, username) != NULL) { return GLEWLWYD_IS_REGISTERED; } else { return GLEWLWYD_IS_AVAILABLE; } } /** * * user_auth_scheme_module_register * * Register the scheme for a user * Ex: add a certificate, add new TOTP values, etc. * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the HTTP API * @parameter username: username to identify the user * @parameter j_scheme_data: additional data used to register the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_register(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls) { UNUSED(config); UNUSED(http_request); json_t * j_return; if (json_object_get(j_scheme_data, "register") == json_true()) { if (user_auth_scheme_module_can_use(config, username, cls) == GLEWLWYD_IS_AVAILABLE) { json_object_set(((struct mock_config *)cls)->j_users, username, json_true()); } j_return = json_pack("{siso}", "result", G_OK, "response", json_true()); } else if (json_object_get(j_scheme_data, "register") == json_false()) { if (user_auth_scheme_module_can_use(config, username, cls) == GLEWLWYD_IS_REGISTERED) { json_object_del(((struct mock_config *)cls)->j_users, username); } j_return = json_pack("{siso}", "result", G_OK, "response", json_true()); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } /** * * user_auth_scheme_module_deregister * * Deregister all the scheme data for a user * Ex: remove certificates, TOTP values, etc. * * @return value: G_OK on success, even if no data has been removed * G_ERROR on another error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_deregister(struct config_module * config, const char * username, void * cls) { if (user_auth_scheme_module_can_use(config, username, cls) == GLEWLWYD_IS_REGISTERED) { json_object_del(((struct mock_config *)cls)->j_users, username); } return G_OK; } /** * * user_auth_scheme_module_register_get * * Get the registration value(s) of the scheme for a user * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_register_get(struct config_module * config, const struct _u_request * http_request, const char * username, void * cls) { UNUSED(config); UNUSED(http_request); json_t * j_return; if (user_auth_scheme_module_can_use(config, username, cls) == GLEWLWYD_IS_REGISTERED) { j_return = json_pack("{sisO}", "result", G_OK, "response", json_true()); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } /** * * user_auth_scheme_module_trigger * * Trigger the scheme for a user * Ex: send the code to a device, generate a challenge, etc. * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter scheme_trigger: data sent to trigger the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_trigger(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_trigger, void * cls) { UNUSED(config); UNUSED(http_request); UNUSED(j_scheme_trigger); json_t * j_return; if (user_auth_scheme_module_can_use(config, username, cls) == GLEWLWYD_IS_REGISTERED) { j_return = json_pack("{sis{ss}}", "result", G_OK, "response", "code", json_string_value(json_object_get(((struct mock_config *)cls)->j_param, "mock-value"))); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } return j_return; } /** * * user_auth_scheme_module_validate * * Validate the scheme for a user * Ex: check the code sent to a device, verify the challenge, etc. * * @return value: G_OK on success * G_ERROR_UNAUTHORIZED if validation fails * G_ERROR_PARAM if error in parameters * G_ERROR on another error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter j_scheme_data: data sent to validate the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_validate(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls) { UNUSED(config); UNUSED(http_request); int ret; if (user_auth_scheme_module_can_use(config, username, cls) != GLEWLWYD_IS_REGISTERED) { ret = G_ERROR_UNAUTHORIZED; } else if (json_object_get(j_scheme_data, "code") != NULL && json_is_string(json_object_get(j_scheme_data, "code")) && 0 == o_strcmp(json_string_value(json_object_get(j_scheme_data, "code")), json_string_value(json_object_get(((struct mock_config *)cls)->j_param, "mock-value")))) { ret = G_OK; } else { ret = G_ERROR_UNAUTHORIZED; } return ret; } /** * * user_auth_scheme_module_identify * * Identify the user using the scheme without the username to be previously given * This functionality isn't available for all schemes, because the scheme authentification * must be triggered without username and the authentication result must contain the username * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * username: string value of the user identified - if the function is called within /auth * response: JSON object, optional - if the function is called within /auth/scheme/trigger * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter j_scheme_data: data sent to validate the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_identify(struct config_module * config, const struct _u_request * http_request, json_t * j_scheme_data, void * cls) { UNUSED(config); UNUSED(http_request); json_t * j_return; if (0 == o_strcmp(json_string_value(json_object_get(j_scheme_data, "code")), json_string_value(json_object_get(((struct mock_config *)cls)->j_param, "mock-value")))) { if (user_auth_scheme_module_can_use(config, json_string_value(json_object_get(j_scheme_data, "username")), cls) == GLEWLWYD_IS_REGISTERED) { j_return = json_pack("{siss}", "result", G_OK, "username", json_string_value(json_object_get(j_scheme_data, "username"))); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else { j_return = json_pack("{sis{ss}}", "result", G_OK, "response", "code", json_string_value(json_object_get(((struct mock_config *)cls)->j_param, "mock-value"))); } return j_return; } glewlwyd-2.6.1/src/scheme/oauth2.c000066400000000000000000002737331415646314000170020ustar00rootroot00000000000000/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * OAuth2 authentication scheme module * * Copyright 2020 Nicolas Mora * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . * */ #include #include #include #include #include #include #include "glewlwyd-common.h" #define GLEWLWYD_SCHEME_OAUTH2_REGISTRATION_TABLE "gs_oauth2_registration" #define GLEWLWYD_SCHEME_OAUTH2_SESSION_TABLE "gs_oauth2_session" #define GLEWLWYD_SCHEME_OAUTH2_STATE_ID_LENGTH 32 #define GLEWLWYD_SCHEME_OAUTH2_NONCE_LENGTH 16 #define GLEWLWYD_SCHEME_OAUTH2_STATE_REGISTRATION "registration" #define GLEWLWYD_SCHEME_OAUTH2_STATE_AUTHENTICATION "authentication" #define GLEWLWYD_SCHEME_OAUTH2_SESSION_REGISTRATION 0 #define GLEWLWYD_SCHEME_OAUTH2_SESSION_AUTHENTICATION 1 #define GLEWLWYD_SCHEME_OAUTH2_SESSION_VERIFIED 2 #define GLEWLWYD_SCHEME_OAUTH2_SESSION_CANCELLED 3 struct _oauth2_config { pthread_mutex_t insert_lock; json_t * j_parameters; }; static int get_response_type(const char * str_type) { if (0 == o_strcmp("code", str_type)) { return I_RESPONSE_TYPE_CODE; } else if (0 == o_strcmp("token", str_type)) { return I_RESPONSE_TYPE_TOKEN; } else if (0 == o_strcmp("id_token", str_type)) { return I_RESPONSE_TYPE_ID_TOKEN; } else { return I_RESPONSE_TYPE_NONE; } } static json_t * is_scheme_parameters_valid(json_t * j_params) { json_t * j_errors = json_array(), * j_return, * j_element = NULL, * j_param = NULL; size_t index = 0, indexParam = 0; char * message; const char * name; int is_oidc = 0; if (j_errors != NULL) { if (json_is_object(j_params)) { if (!json_string_length(json_object_get(j_params, "redirect_uri"))) { json_array_append_new(j_errors, json_string("redirect_uri is mandatory and must be a non empty string")); } if (json_integer_value(json_object_get(j_params, "session_expiration")) <= 0) { json_array_append_new(j_errors, json_string("session_expiration is mandatory and must be a non null positive integer")); } if (!json_is_array(json_object_get(j_params, "provider_list"))) { json_array_append_new(j_errors, json_string("provider_list is mandatory and must be a JSON array")); } else { json_array_foreach(json_object_get(j_params, "provider_list"), index, j_element) { if (!json_string_length(json_object_get(j_element, "name"))) { message = msprintf("name value is missing for provider at index %zu", index); json_array_append_new(j_errors, json_string(message)); o_free(message); name = NULL; } else if (json_string_length(json_object_get(j_element, "name")) > 128) { message = msprintf("name value must be 128 charcters maximum for provider at index %zu", index); json_array_append_new(j_errors, json_string(message)); o_free(message); name = NULL; } else { name = json_string_value(json_object_get(j_element, "name")); } if (0 != o_strcmp("oauth2", json_string_value(json_object_get(j_element, "provider_type"))) && 0 != o_strcmp("oidc", json_string_value(json_object_get(j_element, "provider_type")))) { message = msprintf("provider_type string value for provider '%s' at index %zu is mandatory and must have one of the following values: 'oauth2' or 'oidc'", name, index); json_array_append_new(j_errors, json_string(message)); o_free(message); } is_oidc = o_strcmp("oauth2", json_string_value(json_object_get(j_element, "provider_type"))); if (json_object_get(j_element, "logo_uri") != NULL && !json_is_string(json_object_get(j_element, "logo_uri"))) { message = msprintf("logo_uri is optional and must be a string for provider '%s' at index %zu", name, index); json_array_append_new(j_errors, json_string(message)); o_free(message); } if (json_object_get(j_element, "logo_fa") != NULL && !json_is_string(json_object_get(j_element, "logo_fa"))) { message = msprintf("logo_fa is optional and must be a string for provider '%s' at index %zu", name, index); json_array_append_new(j_errors, json_string(message)); o_free(message); } if (!json_string_length(json_object_get(j_element, "client_id"))) { message = msprintf("client_id string is missing for provider '%s' at index %zu", name, index); json_array_append_new(j_errors, json_string(message)); o_free(message); } if (json_object_get(j_element, "response_type") != NULL && 0 != o_strcmp("code", json_string_value(json_object_get(j_element, "response_type"))) && 0 != o_strcmp("token", json_string_value(json_object_get(j_element, "response_type"))) && 0 != o_strcmp("id_token", json_string_value(json_object_get(j_element, "response_type")))) { message = msprintf("response_type string value for provider '%s' at index %zu is optional and must have one of the following values: 'code', 'token' or 'id_token'", name, index); json_array_append_new(j_errors, json_string(message)); o_free(message); } if (!is_oidc && !json_string_length(json_object_get(j_element, "userid_property"))) { message = msprintf("userid_property string is missing for provider '%s' at index %zu", name, index); json_array_append_new(j_errors, json_string(message)); o_free(message); } if (json_object_get(j_element, "client_secret") != NULL && !json_is_string(json_object_get(j_element, "client_secret"))) { message = msprintf("client_secret is optional and must be a string for provider '%s' at index %zu", name, index); json_array_append_new(j_errors, json_string(message)); o_free(message); } if (is_oidc && json_object_get(j_element, "config_endpoint") != NULL && !json_is_string(json_object_get(j_element, "config_endpoint"))) { message = msprintf("config_endpoint is optional and must be a string for provider '%s' at index %zu", name, index); json_array_append_new(j_errors, json_string(message)); o_free(message); } if (json_object_get(j_element, "auth_endpoint") != NULL && !json_is_string(json_object_get(j_element, "auth_endpoint"))) { message = msprintf("auth_endpoint is optional and must be a string for provider '%s' at index %zu", name, index); json_array_append_new(j_errors, json_string(message)); o_free(message); } if (json_object_get(j_element, "token_endpoint") != NULL && !json_is_string(json_object_get(j_element, "token_endpoint"))) { message = msprintf("token_endpoint is optional and must be a string for provider '%s' at index %zu", name, index); json_array_append_new(j_errors, json_string(message)); o_free(message); } if (json_object_get(j_element, "userinfo_endpoint") != NULL && !json_is_string(json_object_get(j_element, "userinfo_endpoint"))) { message = msprintf("userinfo_endpoint is optional and must be a string for provider '%s' at index %zu", name, index); json_array_append_new(j_errors, json_string(message)); o_free(message); } if (json_object_get(j_element, "scope") != NULL && !json_is_string(json_object_get(j_element, "scope"))) { message = msprintf("scope is optional and must be a string for provider '%s' at index %zu", name, index); json_array_append_new(j_errors, json_string(message)); o_free(message); } if (!json_string_length(json_object_get(j_element, "config_endpoint")) && (!json_string_length(json_object_get(j_element, "auth_endpoint")) || !json_string_length(json_object_get(j_element, "userinfo_endpoint")))) { message = msprintf("You must set config_endpoint or auth_endpoint is mandatory for provider '%s' at index %zu", name, index); json_array_append_new(j_errors, json_string(message)); o_free(message); } if (json_object_get(j_element, "additional_parameters") != NULL) { if (!json_is_array(json_object_get(j_element, "additional_parameters"))) { message = msprintf("additional_parameters is optional and must be a JSON array for provider '%s' at index %zu", name, index); json_array_append_new(j_errors, json_string(message)); o_free(message); } else { json_array_foreach(json_object_get(j_element, "additional_parameters"), indexParam, j_param) { if (!json_string_length(json_object_get(j_param, "key"))) { message = msprintf("additional_parameters key must be a non empty string for provider '%s' at index %zu", name, index); json_array_append_new(j_errors, json_string(message)); o_free(message); } if (!json_string_length(json_object_get(j_param, "value"))) { message = msprintf("additional_parameters value must be a non empty string for provider '%s' at index %zu", name, index); json_array_append_new(j_errors, json_string(message)); o_free(message); } } } } if (json_object_get(j_element, "enabled") != NULL && !json_is_boolean(json_object_get(j_element, "enabled"))) { message = msprintf("enabled is optional and must be a boolean for provider '%s' at index %zu", name, index); json_array_append_new(j_errors, json_string(message)); o_free(message); } } } } else { json_array_append_new(j_errors, json_string("parameters must be a JSON object")); } if (json_array_size(j_errors)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_errors); } else { j_return = json_pack("{si}", "result", G_OK); } json_decref(j_errors); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_scheme_parameters_valid oauth2 - Error allocating resources for j_errors"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } return j_return; } static json_t * complete_session_identify(struct config_module * config, struct _oauth2_config * oauth2_config, json_t * j_provider, const char * redirect_uri, const char * redirect_to, const char * state) { json_t * j_query, * j_result = NULL, * j_result_sub = NULL, * j_return; int res, ret; time_t now; char * expires_at_clause, * sub = NULL; struct _i_session i_session; time(&now); if (config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } j_query = json_pack("{sss[ss]s{sss{ssss}sisO}}", "table", GLEWLWYD_SCHEME_OAUTH2_SESSION_TABLE, "columns", "gsos_id", "gsos_session_export", "where", "gsos_state", state, "gsos_expires_at", "operator", "raw", "value", expires_at_clause, "gsos_status", GLEWLWYD_SCHEME_OAUTH2_SESSION_AUTHENTICATION, "gsor_id", json_null()); o_free(expires_at_clause); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { if (i_init_session(&i_session) == I_OK) { if (i_import_session_str(&i_session, json_string_value(json_object_get(json_array_get(j_result, 0), "gsos_session_export"))) == I_OK) { i_set_str_parameter(&i_session, I_OPT_REDIRECT_TO, redirect_to); if ((res = i_parse_redirect_to(&i_session)) == I_OK) { switch (i_get_response_type(&i_session)) { case I_RESPONSE_TYPE_CODE: if ((res = i_run_token_request(&i_session)) == I_OK) { ret = G_OK; if (0 == o_strcmp("oidc", json_string_value(json_object_get(j_provider, "provider_type")))) { if (json_string_length(json_object_get(i_session.id_token_payload, "sub"))) { sub = o_strdup(json_string_value(json_object_get(i_session.id_token_payload, "sub"))); ret = o_strlen(sub)?G_OK:G_ERROR_PARAM; } else if (json_is_integer(json_object_get(i_session.id_token_payload, "sub"))) { sub = msprintf("%"JSON_INTEGER_FORMAT, json_integer_value(json_object_get(i_session.id_token_payload, "sub"))); ret = o_strlen(sub)?G_OK:G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_identify - Invalid userid_property format (1)"); ret = G_ERROR_PARAM; } } else { if ((res = i_get_userinfo(&i_session, 0)) == I_OK && i_session.j_userinfo != NULL) { if (json_string_length((json_object_get(i_session.j_userinfo, json_string_value(json_object_get(j_provider, "userid_property")))))) { sub = o_strdup(json_string_value(json_object_get(i_session.j_userinfo, json_string_value(json_object_get(j_provider, "userid_property"))))); ret = o_strlen(sub)?G_OK:G_ERROR_PARAM; } else if (json_is_integer(json_object_get(i_session.j_userinfo, json_string_value(json_object_get(j_provider, "userid_property"))))) { sub = msprintf("%"JSON_INTEGER_FORMAT, json_integer_value(json_object_get(i_session.j_userinfo, json_string_value(json_object_get(j_provider, "userid_property"))))); ret = o_strlen(sub)?G_OK:G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_identify - Invalid userid_property format (2)"); ret = G_ERROR_PARAM; } } else if (res == I_ERROR_PARAM || res == I_ERROR_SERVER || res == I_ERROR_UNAUTHORIZED || i_session.j_userinfo == NULL) { ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_identify - Error i_get_userinfo (1)"); ret = G_ERROR; } } } else if (res == I_ERROR_PARAM || res == I_ERROR_SERVER) { ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_identify - Error i_run_token_request"); ret = G_ERROR; } break; case I_RESPONSE_TYPE_TOKEN: if ((res = i_get_userinfo(&i_session, 0)) == I_OK && i_session.j_userinfo != NULL) { if (json_string_length(json_object_get(i_session.j_userinfo, json_string_value(json_object_get(j_provider, "userid_property"))))) { sub = o_strdup(json_string_value(json_object_get(i_session.j_userinfo, json_string_value(json_object_get(j_provider, "userid_property"))))); ret = o_strlen(sub)?G_OK:G_ERROR_PARAM; } else if (json_is_integer(json_object_get(i_session.j_userinfo, json_string_value(json_object_get(j_provider, "userid_property"))))) { sub = msprintf("%"JSON_INTEGER_FORMAT, json_integer_value(json_object_get(i_session.j_userinfo, json_string_value(json_object_get(j_provider, "userid_property"))))); ret = o_strlen(sub)?G_OK:G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_identify - Invalid userid_property format (3)"); ret = G_ERROR_PARAM; } } else if (res == I_ERROR_PARAM || res == I_ERROR_SERVER || res == I_ERROR_UNAUTHORIZED || i_session.j_userinfo == NULL) { ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_identify - Error i_get_userinfo (2)"); ret = G_ERROR; } break; case I_RESPONSE_TYPE_ID_TOKEN: if (json_string_length(json_object_get(i_session.id_token_payload, "sub"))) { sub = o_strdup(json_string_value(json_object_get(i_session.id_token_payload, "sub"))); ret = o_strlen(sub)?G_OK:G_ERROR_PARAM; } else if (json_is_integer(json_object_get(i_session.id_token_payload, "sub"))) { sub = msprintf("%"JSON_INTEGER_FORMAT, json_integer_value(json_object_get(i_session.id_token_payload, "sub"))); ret = o_strlen(sub)?G_OK:G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_identify - Invalid userid_property format (4)"); ret = G_ERROR_PARAM; } break; default: y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_identify - unsupported response_type"); ret = G_ERROR_PARAM; break; } if (ret == G_OK && sub != NULL && 0 == o_strncmp(redirect_to, redirect_uri, o_strlen(redirect_uri))) { j_query = json_pack("{sss[ss]s{sOsOss}}", "table", GLEWLWYD_SCHEME_OAUTH2_REGISTRATION_TABLE, "columns", "gsor_username AS username", "gsor_id", "where", "gsor_mod_name", json_object_get(oauth2_config->j_parameters, "name"), "gsor_provider", json_object_get(j_provider, "name"), "gsor_userinfo_sub", sub); res = h_select(config->conn, j_query, &j_result_sub, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result_sub) == 1) { j_query = json_pack("{sss{sisO}s{sO}}", "table", GLEWLWYD_SCHEME_OAUTH2_SESSION_TABLE, "set", "gsos_status", GLEWLWYD_SCHEME_OAUTH2_SESSION_VERIFIED, "gsor_id", json_object_get(json_array_get(j_result_sub, 0), "gsor_id"), "where", "gsos_id", json_object_get(json_array_get(j_result, 0), "gsos_id")); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_identify - Error executing j_query (1)"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } else { j_return = json_pack("{sisO}", "result", G_OK, "username", json_object_get(json_array_get(j_result_sub, 0), "username")); } } else { // unable to identify only one user j_query = json_pack("{sss{si}s{sO}}", "table", GLEWLWYD_SCHEME_OAUTH2_SESSION_TABLE, "set", "gsos_status", GLEWLWYD_SCHEME_OAUTH2_SESSION_CANCELLED, "where", "gsos_id", json_object_get(json_array_get(j_result, 0), "gsos_id")); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_identify - Error executing j_query (2)"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } json_decref(j_result_sub); } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_identify - Error executing j_query (3)"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { j_query = json_pack("{sss{si}s{sO}}", "table", GLEWLWYD_SCHEME_OAUTH2_SESSION_TABLE, "set", "gsos_status", GLEWLWYD_SCHEME_OAUTH2_SESSION_CANCELLED, "where", "gsos_id", json_object_get(json_array_get(j_result, 0), "gsos_id")); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_identify - Error executing j_query (4)"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } o_free(sub); } else if (res == I_ERROR_PARAM || res == I_ERROR_SERVER) { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_identify - Error parsing redirect_to"); j_return = json_pack("{si}", "result", G_ERROR_PARAM); } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_identify - Error i_parse_redirect_to"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_identify - Error i_import_session_json_t"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_identify - Error i_init_session"); j_return = json_pack("{si}", "result", G_ERROR); } i_clean_session(&i_session); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "complete_session_identify - state not found"); j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_identify - Error executing j_query (5)"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } static json_t * add_session_identify(struct config_module * config, struct _oauth2_config * oauth2_config, json_t * j_provider, const char * callback_url) { json_t * j_query, * j_state = NULL, * j_return; int res; time_t now; char * expires_at_clause, * i_export, * state_export = NULL, * state_export_b64 = NULL; struct _i_session i_session; size_t state_export_b64_len = 0; if (i_init_session(&i_session) == I_OK) { if (i_import_session_json_t(&i_session, json_object_get(j_provider, "export")) == I_OK) { if (i_set_int_parameter(&i_session, I_OPT_STATE_GENERATE, GLEWLWYD_SCHEME_OAUTH2_STATE_ID_LENGTH) == I_OK && i_set_int_parameter(&i_session, I_OPT_NONCE_GENERATE, GLEWLWYD_SCHEME_OAUTH2_NONCE_LENGTH) == I_OK) { j_state = json_pack("{sssssOsOss*}", "id", i_get_str_parameter(&i_session, I_OPT_STATE), "type", GLEWLWYD_SCHEME_OAUTH2_STATE_AUTHENTICATION, "module", json_object_get(oauth2_config->j_parameters, "name"), "provider", json_object_get(j_provider, "name"), "callback_url", callback_url); state_export = json_dumps(j_state, JSON_COMPACT); if ((state_export_b64 = o_malloc(2*o_strlen(state_export))) != NULL) { if (o_base64url_encode((const unsigned char *)state_export, o_strlen(state_export), (unsigned char *)state_export_b64, &state_export_b64_len)) { i_set_str_parameter(&i_session, I_OPT_STATE, state_export_b64); if (i_build_auth_url_get(&i_session) == I_OK) { time(&now); if (config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("FROM_UNIXTIME(%u)", (now + (unsigned int)json_integer_value(json_object_get(oauth2_config->j_parameters, "session_expiration")))); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("TO_TIMESTAMP(%u)", (now + (unsigned int)json_integer_value(json_object_get(oauth2_config->j_parameters, "session_expiration")))); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("%u", (now + (unsigned int)json_integer_value(json_object_get(oauth2_config->j_parameters, "session_expiration")))); } i_export = i_export_session_str(&i_session); j_query = json_pack("{sss{sOs{ss}sssssi}}", "table", GLEWLWYD_SCHEME_OAUTH2_SESSION_TABLE, "values", "gsor_id", json_null(), "gsos_expires_at", "raw", expires_at_clause, "gsos_state", state_export_b64, "gsos_session_export", i_export, "gsos_status", GLEWLWYD_SCHEME_OAUTH2_SESSION_AUTHENTICATION); o_free(expires_at_clause); res = h_insert(config->conn, j_query, NULL); json_decref(j_query); o_free(i_export); if (res == H_OK) { j_return = json_pack("{siss}", "result", G_OK, "session", i_get_str_parameter(&i_session, I_OPT_REDIRECT_TO)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_session_for_user - Error executing j_query (2)"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_session_for_user - Error i_build_auth_url_get"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_session_for_user - Error o_base64url_encode"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_session_for_user - Error o_malloc state_export_b64"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } o_free(state_export); o_free(state_export_b64); json_decref(j_state); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_session_for_user - Error i_set_int_parameter I_OPT_STATE_GENERATE"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_session_for_user - Error i_import_session_json_t"); j_return = json_pack("{si}", "result", G_ERROR); } i_clean_session(&i_session); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_session_for_user - Error i_init_session"); j_return = json_pack("{si}", "result", G_ERROR); } return j_return; } static json_t * add_session_for_user(struct config_module * config, struct _oauth2_config * oauth2_config, const char * username, json_t * j_registration, json_t * j_provider, const char * callback_url) { json_t * j_query, * j_state = NULL, * j_return; int res; time_t now; char * expires_at_clause, * i_export, * state_export = NULL, * state_export_b64 = NULL; struct _i_session i_session; size_t state_export_b64_len = 0; time(&now); if (config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } j_query = json_pack("{sss{si}s{sOsis{ssss}}}", "table", GLEWLWYD_SCHEME_OAUTH2_SESSION_TABLE, "set", "gsos_status", GLEWLWYD_SCHEME_OAUTH2_SESSION_CANCELLED, "where", "gsor_id", json_object_get(j_registration, "gsor_id"), "gsos_status", GLEWLWYD_SCHEME_OAUTH2_SESSION_AUTHENTICATION, "gsos_expires_at", "operator", "raw", "value", expires_at_clause); o_free(expires_at_clause); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { if (i_init_session(&i_session) == I_OK) { if (i_import_session_json_t(&i_session, json_object_get(j_provider, "export")) == I_OK) { if (i_set_int_parameter(&i_session, I_OPT_STATE_GENERATE, GLEWLWYD_SCHEME_OAUTH2_STATE_ID_LENGTH) == I_OK && i_set_int_parameter(&i_session, I_OPT_NONCE_GENERATE, GLEWLWYD_SCHEME_OAUTH2_NONCE_LENGTH) == I_OK) { j_state = json_pack("{sssssOsOssss*}", "id", i_get_str_parameter(&i_session, I_OPT_STATE), "type", GLEWLWYD_SCHEME_OAUTH2_STATE_AUTHENTICATION, "module", json_object_get(oauth2_config->j_parameters, "name"), "provider", json_object_get(j_provider, "name"), "username", username, "callback_url", callback_url); state_export = json_dumps(j_state, JSON_COMPACT); if ((state_export_b64 = o_malloc(2*o_strlen(state_export))) != NULL) { if (o_base64url_encode((const unsigned char *)state_export, o_strlen(state_export), (unsigned char *)state_export_b64, &state_export_b64_len)) { i_set_str_parameter(&i_session, I_OPT_STATE, state_export_b64); if (i_build_auth_url_get(&i_session) == I_OK) { time(&now); if (config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("FROM_UNIXTIME(%u)", (now + (unsigned int)json_integer_value(json_object_get(oauth2_config->j_parameters, "session_expiration")))); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("TO_TIMESTAMP(%u)", (now + (unsigned int)json_integer_value(json_object_get(oauth2_config->j_parameters, "session_expiration")))); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("%u", (now + (unsigned int)json_integer_value(json_object_get(oauth2_config->j_parameters, "session_expiration")))); } i_export = i_export_session_str(&i_session); j_query = json_pack("{sss{sOs{ss}sssssi}}", "table", GLEWLWYD_SCHEME_OAUTH2_SESSION_TABLE, "values", "gsor_id", json_object_get(j_registration, "gsor_id"), "gsos_expires_at", "raw", expires_at_clause, "gsos_state", state_export_b64, "gsos_session_export", i_export, "gsos_status", GLEWLWYD_SCHEME_OAUTH2_SESSION_AUTHENTICATION); o_free(expires_at_clause); res = h_insert(config->conn, j_query, NULL); json_decref(j_query); o_free(i_export); if (res == H_OK) { j_return = json_pack("{siss}", "result", G_OK, "session", i_get_str_parameter(&i_session, I_OPT_REDIRECT_TO)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_session_for_user - Error executing j_query (2)"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_session_for_user - Error i_build_auth_url_get"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_session_for_user - Error o_base64url_encode"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_session_for_user - Error o_malloc state_export_b64"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } o_free(state_export); o_free(state_export_b64); json_decref(j_state); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_session_for_user - Error i_set_int_parameter I_OPT_STATE_GENERATE"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_session_for_user - Error i_import_session_json_t"); j_return = json_pack("{si}", "result", G_ERROR); } i_clean_session(&i_session); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_session_for_user - Error i_init_session"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_session_for_user - Error executing j_query"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } static json_t * get_last_session_for_registration(struct config_module * config, json_int_t gsor_id) { json_t * j_query, * j_result = NULL, * j_return; int res; j_query = json_pack("{sss[s]s{sIsi}sssi}", "table", GLEWLWYD_SCHEME_OAUTH2_SESSION_TABLE, "columns", SWITCH_DB_TYPE(config->conn->type, "UNIX_TIMESTAMP(gsos_created_at) AS last_session", "strftime('%s', gsos_created_at) AS last_session", "EXTRACT(EPOCH FROM gsos_created_at)::integer AS last_session"), "where", "gsor_id", gsor_id, "gsos_status", GLEWLWYD_SCHEME_OAUTH2_SESSION_VERIFIED, "order_by", "gsos_created_at DESC", "limit", 1); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_return = json_pack("{sisO}", "result", G_OK, "last_session", json_object_get(json_array_get(j_result, 0), "last_session")); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_last_session_for_registration - Error executing j_query"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } static json_t * get_registration_for_user(struct config_module * config, struct _oauth2_config * oauth2_config, const char * username, const char * provider) { json_t * j_query, * j_result = NULL, * j_return, * j_element = NULL, * j_session; int res; size_t index = 0; j_query = json_pack("{sss[ssss]s{sOss}}", "table", GLEWLWYD_SCHEME_OAUTH2_REGISTRATION_TABLE, "columns", "gsor_id", "gsor_provider AS provider", SWITCH_DB_TYPE(config->conn->type, "UNIX_TIMESTAMP(gsor_created_at) AS created_at", "strftime('%s', gsor_created_at) AS created_at", "EXTRACT(EPOCH FROM gsor_created_at)::integer AS created_at"), "gsor_userinfo_sub AS sub", "where", "gsor_mod_name", json_object_get(oauth2_config->j_parameters, "name"), "gsor_username", username); if (provider != NULL) { json_object_set_new(json_object_get(j_query, "where"), "gsor_provider", json_string(provider)); } res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { json_array_foreach(j_result, index, j_element) { j_session = get_last_session_for_registration(config, json_integer_value(json_object_get(j_element, "gsor_id"))); if (check_result_value(j_session, G_OK)) { json_object_set(j_element, "last_session", json_object_get(j_session, "last_session")); } else { if (!check_result_value(j_session, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_ERROR, "get_registration_for_user - Error get_last_session_for_registration for provider %s", json_string_value(json_object_get(j_element, "provider"))); } json_object_set(j_element, "last_session", json_null()); } json_decref(j_session); if (provider == NULL) { json_object_del(j_element, "gsor_id"); json_object_del(j_element, "sub"); } } j_return = json_pack("{sisO}", "result", G_OK, "registration", j_result); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_registration_for_user - Error executing j_query"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } static json_t * add_registration_for_user(struct config_module * config, struct _oauth2_config * oauth2_config, const char * username, json_t * j_provider, const char * register_url, const char * complete_url) { json_t * j_query, * j_return, * j_state = NULL, * j_last_id = NULL; int res; time_t now; char * expires_at_clause, * i_export, * state_export = NULL, * state_export_b64 = NULL; struct _i_session i_session; size_t state_export_b64_len = 0; if (!pthread_mutex_lock(&oauth2_config->insert_lock)) { if (i_init_session(&i_session) == I_OK) { if (i_import_session_json_t(&i_session, json_object_get(j_provider, "export")) == I_OK) { if (i_set_int_parameter(&i_session, I_OPT_STATE_GENERATE, GLEWLWYD_SCHEME_OAUTH2_STATE_ID_LENGTH) == I_OK && i_set_int_parameter(&i_session, I_OPT_NONCE_GENERATE, GLEWLWYD_SCHEME_OAUTH2_NONCE_LENGTH) == I_OK) { j_state = json_pack("{sssssOsOssss*ss*}", "id", i_get_str_parameter(&i_session, I_OPT_STATE), "type", GLEWLWYD_SCHEME_OAUTH2_STATE_REGISTRATION, "module", json_object_get(oauth2_config->j_parameters, "name"), "provider", json_object_get(j_provider, "name"), "username", username, "register_url", register_url, "complete_url", complete_url); state_export = json_dumps(j_state, JSON_COMPACT); if ((state_export_b64 = o_malloc(2*o_strlen(state_export))) != NULL) { if (o_base64url_encode((const unsigned char *)state_export, o_strlen(state_export), (unsigned char *)state_export_b64, &state_export_b64_len)) { i_set_str_parameter(&i_session, I_OPT_STATE, state_export_b64); if (i_build_auth_url_get(&i_session) == I_OK) { j_query = json_pack("{sss{sOsOssss}}", "table", GLEWLWYD_SCHEME_OAUTH2_REGISTRATION_TABLE, "values", "gsor_mod_name", json_object_get(oauth2_config->j_parameters, "name"), "gsor_provider", json_object_get(j_provider, "name"), "gsor_username", username, "gsor_userinfo_sub", ""); res = h_insert(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { time(&now); if (config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("FROM_UNIXTIME(%u)", (now + (unsigned int)json_integer_value(json_object_get(oauth2_config->j_parameters, "session_expiration")))); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("TO_TIMESTAMP(%u)", (now + (unsigned int)json_integer_value(json_object_get(oauth2_config->j_parameters, "session_expiration")))); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("%u", (now + (unsigned int)json_integer_value(json_object_get(oauth2_config->j_parameters, "session_expiration")))); } j_last_id = h_last_insert_id(config->conn); i_export = i_export_session_str(&i_session); j_query = json_pack("{sss{sOs{ss}sssssi}}", "table", GLEWLWYD_SCHEME_OAUTH2_SESSION_TABLE, "values", "gsor_id", j_last_id, "gsos_expires_at", "raw", expires_at_clause, "gsos_state", state_export_b64, "gsos_session_export", i_export, "gsos_status", GLEWLWYD_SCHEME_OAUTH2_SESSION_REGISTRATION); o_free(expires_at_clause); res = h_insert(config->conn, j_query, NULL); json_decref(j_query); json_decref(j_last_id); o_free(i_export); if (res == H_OK) { j_return = json_pack("{siss}", "result", G_OK, "registration", i_get_str_parameter(&i_session, I_OPT_REDIRECT_TO)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_registration_for_user - Error executing j_query (2)"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_registration_for_user - Error executing j_query (1)"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_registration_for_user - Error i_build_auth_url_get"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_registration_for_user - Error o_base64url_encode"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_registration_for_user - Error o_malloc state_export_b64"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } o_free(state_export); o_free(state_export_b64); json_decref(j_state); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_registration_for_user - Error i_set_int_parameter I_OPT_STATE_GENERATE"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_registration_for_user - Error i_import_session_json_t"); j_return = json_pack("{si}", "result", G_ERROR); } i_clean_session(&i_session); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_registration_for_user - Error i_init_session"); j_return = json_pack("{si}", "result", G_ERROR); } pthread_mutex_unlock(&oauth2_config->insert_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_registration_for_user - Error pthread_mutex_lock"); j_return = json_pack("{si}", "result", G_ERROR); } return j_return; } static int delete_registration_for_user(struct config_module * config, struct _oauth2_config * oauth2_config, const char * username, const char * provider) { json_t * j_query; int res, ret; j_query = json_pack("{sss{sOss}}", "table", GLEWLWYD_SCHEME_OAUTH2_REGISTRATION_TABLE, "where", "gsor_mod_name", json_object_get(oauth2_config->j_parameters, "name"), "gsor_username", username); if (provider != NULL) { json_object_set_new(json_object_get(j_query, "where"), "gsor_provider", json_string(provider)); } res = h_delete(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_registration_for_user - Error executing j_query"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static int complete_session_for_user(struct config_module * config, const char * redirect_uri, json_t * j_registration, json_t * j_provider, const char * redirect_to, const char * state, int status) { json_t * j_query, * j_result = NULL; int res, ret; time_t now; char * expires_at_clause, * sub = NULL; struct _i_session i_session; time(&now); if (config->conn->type==HOEL_DB_TYPE_MARIADB) { expires_at_clause = msprintf("> FROM_UNIXTIME(%u)", (now)); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { expires_at_clause = msprintf("> TO_TIMESTAMP(%u)", now); } else { // HOEL_DB_TYPE_SQLITE expires_at_clause = msprintf("> %u", (now)); } j_query = json_pack("{sss[ss]s{sss{ssss}sisO}}", "table", GLEWLWYD_SCHEME_OAUTH2_SESSION_TABLE, "columns", "gsos_id", "gsos_session_export", "where", "gsos_state", state, "gsos_expires_at", "operator", "raw", "value", expires_at_clause, "gsos_status", status, "gsor_id", json_object_get(j_registration, "gsor_id")); o_free(expires_at_clause); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { if (i_init_session(&i_session) == I_OK) { if (i_import_session_str(&i_session, json_string_value(json_object_get(json_array_get(j_result, 0), "gsos_session_export"))) == I_OK) { i_set_str_parameter(&i_session, I_OPT_REDIRECT_TO, redirect_to); if ((res = i_parse_redirect_to(&i_session)) == I_OK) { switch (i_get_response_type(&i_session)) { case I_RESPONSE_TYPE_CODE: if ((res = i_run_token_request(&i_session)) == I_OK) { ret = G_OK; if (0 == o_strcmp("oidc", json_string_value(json_object_get(j_provider, "provider_type")))) { if (json_string_length(json_object_get(i_session.id_token_payload, "sub"))) { sub = o_strdup(json_string_value(json_object_get(i_session.id_token_payload, "sub"))); ret = o_strlen(sub)?G_OK:G_ERROR_PARAM; } else if (json_is_integer(json_object_get(i_session.id_token_payload, "sub"))) { sub = msprintf("%"JSON_INTEGER_FORMAT, json_integer_value(json_object_get(i_session.id_token_payload, "sub"))); ret = o_strlen(sub)?G_OK:G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_for_user - Invalid userid_property format (1)"); ret = G_ERROR_PARAM; } } else { if ((res = i_get_userinfo(&i_session, 0)) == I_OK && i_session.j_userinfo != NULL) { if (json_string_length((json_object_get(i_session.j_userinfo, json_string_value(json_object_get(j_provider, "userid_property")))))) { sub = o_strdup(json_string_value(json_object_get(i_session.j_userinfo, json_string_value(json_object_get(j_provider, "userid_property"))))); ret = o_strlen(sub)?G_OK:G_ERROR_PARAM; } else if (json_is_integer(json_object_get(i_session.j_userinfo, json_string_value(json_object_get(j_provider, "userid_property"))))) { sub = msprintf("%"JSON_INTEGER_FORMAT, json_integer_value(json_object_get(i_session.j_userinfo, json_string_value(json_object_get(j_provider, "userid_property"))))); ret = o_strlen(sub)?G_OK:G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_for_user - Invalid userid_property format (2)"); ret = G_ERROR_PARAM; } } else if (res == I_ERROR_PARAM || res == I_ERROR_SERVER || res == I_ERROR_UNAUTHORIZED || i_session.j_userinfo == NULL) { ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_for_user - Error i_get_userinfo (1)"); ret = G_ERROR; } } } else if (res == I_ERROR_PARAM || res == I_ERROR_SERVER) { ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_for_user - Error i_run_token_request"); ret = G_ERROR; } break; case I_RESPONSE_TYPE_TOKEN: if ((res = i_get_userinfo(&i_session, 0)) == I_OK && i_session.j_userinfo != NULL) { if (json_string_length(json_object_get(i_session.j_userinfo, json_string_value(json_object_get(j_provider, "userid_property"))))) { sub = o_strdup(json_string_value(json_object_get(i_session.j_userinfo, json_string_value(json_object_get(j_provider, "userid_property"))))); ret = o_strlen(sub)?G_OK:G_ERROR_PARAM; } else if (json_is_integer(json_object_get(i_session.j_userinfo, json_string_value(json_object_get(j_provider, "userid_property"))))) { sub = msprintf("%"JSON_INTEGER_FORMAT, json_integer_value(json_object_get(i_session.j_userinfo, json_string_value(json_object_get(j_provider, "userid_property"))))); ret = o_strlen(sub)?G_OK:G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_for_user - Invalid userid_property format (3)"); ret = G_ERROR_PARAM; } } else if (res == I_ERROR_PARAM || res == I_ERROR_SERVER || res == I_ERROR_UNAUTHORIZED || i_session.j_userinfo == NULL) { ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_for_user - Error i_get_userinfo (2)"); ret = G_ERROR; } break; case I_RESPONSE_TYPE_ID_TOKEN: if (json_string_length(json_object_get(i_session.id_token_payload, "sub"))) { sub = o_strdup(json_string_value(json_object_get(i_session.id_token_payload, "sub"))); ret = o_strlen(sub)?G_OK:G_ERROR_PARAM; } else if (json_is_integer(json_object_get(i_session.id_token_payload, "sub"))) { sub = msprintf("%"JSON_INTEGER_FORMAT, json_integer_value(json_object_get(i_session.id_token_payload, "sub"))); ret = o_strlen(sub)?G_OK:G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_for_user - Invalid userid_property format (4)"); ret = G_ERROR_PARAM; } break; default: y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_for_user - unsupported response_type"); ret = G_ERROR_PARAM; break; } if (ret == G_OK && sub != NULL && 0 == o_strncmp(redirect_to, redirect_uri, o_strlen(redirect_uri))) { if (status == GLEWLWYD_SCHEME_OAUTH2_SESSION_REGISTRATION) { j_query = json_pack("{sss{ss}s{sO}}", "table", GLEWLWYD_SCHEME_OAUTH2_REGISTRATION_TABLE, "set", "gsor_userinfo_sub", sub, "where", "gsor_id", json_object_get(j_registration, "gsor_id")); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_for_user - Error executing j_query (2)"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { if (0 != o_strcmp(sub, json_string_value(json_object_get(j_registration, "sub")))) { ret = G_ERROR_UNAUTHORIZED; } } j_query = json_pack("{sss{si}s{sO}}", "table", GLEWLWYD_SCHEME_OAUTH2_SESSION_TABLE, "set", "gsos_status", GLEWLWYD_SCHEME_OAUTH2_SESSION_VERIFIED, "where", "gsos_id", json_object_get(json_array_get(j_result, 0), "gsos_id")); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_for_user - Error executing j_query (3)"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { j_query = json_pack("{sss{si}s{sO}}", "table", GLEWLWYD_SCHEME_OAUTH2_SESSION_TABLE, "set", "gsos_status", GLEWLWYD_SCHEME_OAUTH2_SESSION_CANCELLED, "where", "gsos_id", json_object_get(json_array_get(j_result, 0), "gsos_id")); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_for_user - Error executing j_query (3)"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } o_free(sub); } else if (res == I_ERROR_PARAM || res == I_ERROR_SERVER) { ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_for_user - Error i_parse_redirect_to"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_for_user - Error i_import_session_json_t"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_for_user - Error i_init_session"); ret = G_ERROR; } i_clean_session(&i_session); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "complete_session_for_user - state not found"); ret = G_ERROR_NOT_FOUND; } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "complete_session_for_user - Error executing j_query (1)"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static json_t * get_provider(struct _oauth2_config * oauth2_config, const char * provider_name) { json_t * j_element = NULL, * j_return = NULL; size_t index = 0; json_array_foreach(json_object_get(oauth2_config->j_parameters, "provider_list"), index, j_element) { if (j_return == NULL && 0 == o_strcmp(json_string_value(json_object_get(j_element, "name")), provider_name) && json_object_get(j_element, "enabled") != json_false()) { j_return = json_pack("{sisO}", "result", G_OK, "provider", j_element); } } if (j_return == NULL) { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } return j_return; } /** * * user_auth_scheme_module_load * * Executed once when Glewlwyd service is started * Used to identify the module and to show its parameters on init * You can also use it to load resources that are required once for all * instance modules for example * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * name: string, mandatory, name of the module, must be unique among other scheme modules * display_name: string, optional, long name of the module * description: string, optional, description for the module * parameters: object, optional, parameters description for the module * } * * Example: * { * result: G_OK, * name: "mock", * display_name: "Mock scheme module", * description: "Mock scheme module for glewlwyd tests", * parameters: { * mock-value: { * type: "string", * mandatory: true * } * } * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ json_t * user_auth_scheme_module_load(struct config_module * config) { UNUSED(config); r_global_init(); i_global_init(); return json_pack("{sissssss}", "result", G_OK, "name", "oauth2", "display_name", "OAuth2 Client", "description", "OAuth2 Client scheme"); } /** * * user_auth_scheme_module_unload * * Executed once when Glewlwyd service is stopped * You can also use it to release resources that are required once for all * instance modules for example * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ int user_auth_scheme_module_unload(struct config_module * config) { UNUSED(config); r_global_close(); i_global_close(); return G_OK; } /** * * user_auth_scheme_module_init * * Initialize an instance of this module declared in Glewlwyd service. * If required, you must dynamically allocate a pointer to the configuration * for this instance and pass it to *cls * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, G_ERROR_PARAM on input parameters error, another value on error) * error: array of strings containg the list of input errors, mandatory on result G_ERROR_PARAM, ignored otherwise * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_parameters: used to initialize an instance in JSON format * The module must validate itself its parameters * @parameter mod_name: module name in glewlwyd service * @parameter cls: will contain an allocated void * pointer that will be sent back * as void * in all module functions * */ json_t * user_auth_scheme_module_init(struct config_module * config, json_t * j_parameters, const char * mod_name, void ** cls) { UNUSED(config); UNUSED(mod_name); UNUSED(config); json_t * j_result, * j_return, * j_element = NULL, * j_export = NULL, * j_param; char * str_error; size_t index = 0, indexParam = 0; struct _i_session i_session; pthread_mutexattr_t mutexattr; int is_oidc; j_result = is_scheme_parameters_valid(j_parameters); if (check_result_value(j_result, G_OK)) { *cls = o_malloc(sizeof(struct _oauth2_config)); if (*cls != NULL) { ((struct _oauth2_config *)*cls)->j_parameters = json_pack("{sssOsOs[]}", "name", mod_name, "redirect_uri", json_object_get(j_parameters, "redirect_uri"), "session_expiration", json_object_get(j_parameters, "session_expiration"), "provider_list"); pthread_mutexattr_init ( &mutexattr ); pthread_mutexattr_settype( &mutexattr, PTHREAD_MUTEX_RECURSIVE ); if (!pthread_mutex_init(&((struct _oauth2_config *)*cls)->insert_lock, &mutexattr)) { json_array_foreach(json_object_get(j_parameters, "provider_list"), index, j_element) { if (json_object_get(j_element, "enabled") != json_false()) { is_oidc = o_strcmp("oauth2", json_string_value(json_object_get(j_element, "provider_type"))); if (i_init_session(&i_session) == I_OK) { json_array_foreach(json_object_get(j_element, "additional_parameters"), indexParam, j_param) { i_set_additional_parameter(&i_session, json_string_value(json_object_get(j_param, "key")), json_string_value(json_object_get(j_param, "value"))); } if (json_string_length(json_object_get(j_element, "config_endpoint"))) { if (i_set_parameter_list(&i_session, I_OPT_RESPONSE_TYPE, get_response_type(json_string_value(json_object_get(j_element, "response_type"))), I_OPT_OPENID_CONFIG_ENDPOINT, json_string_value(json_object_get(j_element, "config_endpoint")), I_OPT_CLIENT_ID, json_string_value(json_object_get(j_element, "client_id")), I_OPT_CLIENT_SECRET, json_string_value(json_object_get(j_element, "client_secret")), I_OPT_REDIRECT_URI, json_string_value(json_object_get(j_parameters, "redirect_uri")), I_OPT_SCOPE, is_oidc?"openid":json_string_value(json_object_get(j_element, "scope")), I_OPT_TOKEN_METHOD, I_TOKEN_AUTH_METHOD_SECRET_BASIC, I_OPT_NONE) != I_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init oauth2 - Error setting parameters for provider %s", json_string_value(json_object_get(j_element, "name"))); } else if (i_get_openid_config(&i_session) != I_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init oauth2 - Error loading openid-configuration for provider %s", json_string_value(json_object_get(j_element, "name"))); } else if ((j_export = i_export_session_json_t(&i_session)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init oauth2 - Error exporting session for provider %s", json_string_value(json_object_get(j_element, "name"))); } else { // Overwrite endpoints if specified if (json_object_get(j_element, "auth_endpoint") != NULL) { i_set_str_parameter(&i_session, I_OPT_AUTH_ENDPOINT, json_string_value(json_object_get(j_element, "auth_endpoint"))); } if (json_object_get(j_element, "token_endpoint") != NULL) { i_set_str_parameter(&i_session, I_OPT_TOKEN_ENDPOINT, json_string_value(json_object_get(j_element, "token_endpoint"))); } if (json_object_get(j_element, "userinfo_endpoint") != NULL) { i_set_str_parameter(&i_session, I_OPT_USERINFO_ENDPOINT, json_string_value(json_object_get(j_element, "userinfo_endpoint"))); } json_object_set(j_element, "export", j_export); json_array_append(json_object_get(((struct _oauth2_config *)*cls)->j_parameters, "provider_list"), j_element); } json_decref(j_export); } else { if (i_set_parameter_list(&i_session, I_OPT_RESPONSE_TYPE, get_response_type(json_string_value(json_object_get(j_element, "response_type"))), I_OPT_AUTH_ENDPOINT, json_string_value(json_object_get(j_element, "auth_endpoint")), I_OPT_TOKEN_ENDPOINT, json_string_value(json_object_get(j_element, "token_endpoint")), I_OPT_USERINFO_ENDPOINT, json_string_value(json_object_get(j_element, "userinfo_endpoint")), I_OPT_CLIENT_ID, json_string_value(json_object_get(j_element, "client_id")), I_OPT_CLIENT_SECRET, json_string_value(json_object_get(j_element, "client_secret")), I_OPT_REDIRECT_URI, json_string_value(json_object_get(j_parameters, "redirect_uri")), I_OPT_SCOPE, is_oidc?"openid":json_string_value(json_object_get(j_element, "scope")), I_OPT_TOKEN_METHOD, I_TOKEN_AUTH_METHOD_SECRET_BASIC, I_OPT_NONE) != I_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init oauth2 - Error setting parameters for provider %s", json_string_value(json_object_get(j_element, "name"))); } else if ((j_export = i_export_session_json_t(&i_session)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init oauth2 - Error exporting session for provider %s", json_string_value(json_object_get(j_element, "name"))); } else { json_object_set(j_element, "export", j_export); json_array_append(json_object_get(((struct _oauth2_config *)*cls)->j_parameters, "provider_list"), j_element); } json_decref(j_export); } i_clean_session(&i_session); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init oauth2 - Error i_init_session"); } } } j_return = json_pack("{si}", "result", G_OK); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init oauth2 - Error pthread_mutex_init"); j_return = json_pack("{si}", "result", G_ERROR); json_decref(((struct _oauth2_config *)*cls)->j_parameters); o_free(*cls); *cls = NULL; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init oauth2 - Error allocating resources for *cls"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init oauth2 - Error in parameters"); str_error = json_dumps(json_object_get(j_result, "error"), JSON_ENCODE_ANY); y_log_message(Y_LOG_LEVEL_ERROR, str_error); o_free(str_error); j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", json_object_get(j_result, "error")); } json_decref(j_result); return j_return; } /** * * user_auth_scheme_module_close * * Close an instance of this module declared in Glewlwyd service. * You must free the memory previously allocated in * the user_auth_scheme_module_init function as void * cls * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_close(struct config_module * config, void * cls) { UNUSED(config); json_decref(((struct _oauth2_config *)cls)->j_parameters); pthread_mutex_destroy(&((struct _oauth2_config *)cls)->insert_lock); o_free(cls); return G_OK; } /** * * user_auth_scheme_module_can_use * * Validate if the user is allowed to use this scheme prior to the * authentication or registration * * @return value: GLEWLWYD_IS_REGISTERED - User can use scheme and has registered * GLEWLWYD_IS_AVAILABLE - User can use scheme but hasn't registered * GLEWLWYD_IS_NOT_AVAILABLE - User can't use scheme * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_can_use(struct config_module * config, const char * username, void * cls) { int ret; json_t * j_registration = get_registration_for_user(config, (struct _oauth2_config *)cls, username, NULL); if (check_result_value(j_registration, G_OK)) { ret = GLEWLWYD_IS_REGISTERED; } else if (check_result_value(j_registration, G_ERROR_NOT_FOUND)) { ret = GLEWLWYD_IS_AVAILABLE; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_can_use - Error get_registration_for_user"); ret = GLEWLWYD_IS_NOT_AVAILABLE; } json_decref(j_registration); return ret; } /** * * user_auth_scheme_module_register * * Register the scheme for a user * Ex: add a certificate, add new TOTP values, etc. * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the HTTP API * @parameter username: username to identify the user * @parameter j_scheme_data: additional data used to register the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_register(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls) { UNUSED(http_request); json_t * j_return, * j_result, * j_provider, * j_register; int res; struct _oauth2_config * oauth2_config = (struct _oauth2_config *)cls; if (json_is_object(j_scheme_data)) { j_provider = get_provider(oauth2_config, json_string_value(json_object_get(j_scheme_data, "provider"))); if (check_result_value(j_provider, G_OK)) { if (0 == o_strcmp(json_string_value(json_object_get(j_scheme_data, "action")), "new")) { j_result = get_registration_for_user(config, oauth2_config, username, json_string_value(json_object_get(j_scheme_data, "provider"))); if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { j_register = add_registration_for_user(config, oauth2_config, username, json_object_get(j_provider, "provider"), json_string_value(json_object_get(j_scheme_data, "register_url")), json_string_value(json_object_get(j_scheme_data, "complete_url"))); if (check_result_value(j_register, G_OK)) { j_return = json_pack("{sis{sO}}", "result", G_OK, "response", "redirect_to", json_object_get(j_register, "registration")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register oauth2 - Error add_registration_for_user"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_register); } else if (check_result_value(j_result, G_OK)) { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "response", "provider already registered"); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register oauth2 - Error get_registration_for_user"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } else if (0 == o_strcmp(json_string_value(json_object_get(j_scheme_data, "action")), "callback")) { j_result = get_registration_for_user(config, oauth2_config, username, json_string_value(json_object_get(j_scheme_data, "provider"))); if (check_result_value(j_result, G_OK)) { if ((res = complete_session_for_user(config, json_string_value(json_object_get(oauth2_config->j_parameters, "redirect_uri")), json_array_get(json_object_get(j_result, "registration"), 0), json_object_get(j_provider, "provider"), json_string_value(json_object_get(j_scheme_data, "redirect_to")), json_string_value(json_object_get(j_scheme_data, "state")), GLEWLWYD_SCHEME_OAUTH2_SESSION_REGISTRATION)) == G_OK) { j_return = json_pack("{si}", "result", G_OK); } else if (res == G_ERROR_PARAM || res == G_ERROR_UNAUTHORIZED || res == G_ERROR_NOT_FOUND) { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "response", "Registration completion invalid"); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register oauth2 - Error complete_session_for_user"); j_return = json_pack("{si}", "result", G_ERROR); } } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register oauth2 - Error get_registration_for_user"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } else if (0 == o_strcmp(json_string_value(json_object_get(j_scheme_data, "action")), "delete")) { j_result = get_registration_for_user(config, oauth2_config, username, json_string_value(json_object_get(j_scheme_data, "provider"))); if (check_result_value(j_result, G_OK)) { if (delete_registration_for_user(config, oauth2_config, username, json_string_value(json_object_get(j_scheme_data, "provider"))) == G_OK) { j_return = json_pack("{si}", "result", G_OK); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register oauth2 - Error delete_registration_for_user"); j_return = json_pack("{si}", "result", G_ERROR); } } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register oauth2 - Error get_registration_for_user"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "response", "action invalid"); } } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "response", "provider invalid"); } json_decref(j_provider); } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "response", "data must be a JSON object"); } return j_return; } /** * * user_auth_scheme_module_deregister * * Deregister all the scheme data for a user * Ex: remove certificates, TOTP values, etc. * * @return value: G_OK on success, even if no data has been removed * G_ERROR on another error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_deregister(struct config_module * config, const char * username, void * cls) { int ret; struct _oauth2_config * oauth2_config = (struct _oauth2_config *)cls; if (delete_registration_for_user(config, oauth2_config, username, NULL) == G_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_deregister oauth2 - Error delete_registration_for_user"); ret = G_ERROR; } return ret; } /** * * user_auth_scheme_module_register_get * * Get the registration value(s) of the scheme for a user * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_register_get(struct config_module * config, const struct _u_request * http_request, const char * username, void * cls) { UNUSED(http_request); struct _oauth2_config * oauth2_config = (struct _oauth2_config *)cls; json_t * j_result, * j_return, * j_element = NULL, * j_register = NULL; size_t index = 0, index_r = 0; int found; j_result = get_registration_for_user(config, oauth2_config, username, NULL); if (check_result_value(j_result, G_OK)) { j_return = json_pack("{sis[]}", "result", G_OK, "response"); json_array_foreach(json_object_get(oauth2_config->j_parameters, "provider_list"), index, j_element) { found = 0; json_array_foreach(json_object_get(j_result, "registration"), index_r, j_register) { if (0 == o_strcmp(json_string_value(json_object_get(j_element, "name")), json_string_value(json_object_get(j_register, "provider")))) { json_object_set(j_register, "logo_uri", json_object_get(j_element, "logo_uri")); json_object_set(j_register, "logo_fa", json_object_get(j_element, "logo_fa")); json_array_append(json_object_get(j_return, "response"), j_register); found = 1; } } if (!found) { json_array_append_new(json_object_get(j_return, "response"), json_pack("{sOsOsOsoso}", "provider", json_object_get(j_element, "name"), "logo_uri", json_object_get(j_element, "logo_uri"), "logo_fa", json_object_get(j_element, "logo_fa"), "enabled", json_false(), "created_at", json_null())); } } } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { j_return = json_pack("{sis[]}", "result", G_OK, "response"); json_array_foreach(json_object_get(oauth2_config->j_parameters, "provider_list"), index, j_element) { json_array_append_new(json_object_get(j_return, "response"), json_pack("{sOsOsOsoso}", "provider", json_object_get(j_element, "name"), "logo_uri", json_object_get(j_element, "logo_uri"), "logo_fa", json_object_get(j_element, "logo_fa"), "enabled", json_false(), "created_at", json_null())); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register_get oauth2 - Error get_registration_for_user"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); return j_return; } /** * * user_auth_scheme_module_trigger * * Trigger the scheme for a user * Ex: send the code to a device, generate a challenge, etc. * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter scheme_trigger: data sent to trigger the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_trigger(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_trigger, void * cls) { json_t * j_return = NULL, * j_session = NULL, * j_result, * j_element = NULL, * j_register = NULL, * j_provider; size_t index = 0, index_r = 0; struct _oauth2_config * oauth2_config = (struct _oauth2_config *)cls; if (json_object_get(j_scheme_trigger, "provider_list") == json_true()) { j_session = config->glewlwyd_module_callback_check_user_session(config, http_request, username); if (check_result_value(j_session, G_OK)) { j_result = get_registration_for_user(config, oauth2_config, username, NULL); if (check_result_value(j_result, G_OK)) { j_return = json_pack("{sis[]}", "result", G_OK, "response"); json_array_foreach(json_object_get(oauth2_config->j_parameters, "provider_list"), index, j_element) { json_array_foreach(json_object_get(j_result, "registration"), index_r, j_register) { if (0 == o_strcmp(json_string_value(json_object_get(j_element, "name")), json_string_value(json_object_get(j_register, "provider")))) { json_array_append_new(json_object_get(j_return, "response"), json_pack("{sOsOsOsO}", "provider", json_object_get(j_register, "provider"), "logo_uri", json_object_get(j_element, "logo_uri"), "logo_fa", json_object_get(j_element, "logo_fa"), "created_at", json_object_get(j_register, "created_at"))); } } } } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_trigger oauth2 - Error get_registration_for_user"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } else { j_return = json_pack("{sis[]}", "result", G_OK, "response"); json_array_foreach(json_object_get(oauth2_config->j_parameters, "provider_list"), index, j_element) { json_array_append_new(json_object_get(j_return, "response"), json_pack("{sOsOsOso}", "provider", json_object_get(j_element, "name"), "logo_uri", json_object_get(j_element, "logo_uri"), "logo_fa", json_object_get(j_element, "logo_fa"), "created_at", json_null())); } } json_decref(j_session); } else { j_register = get_registration_for_user(config, oauth2_config, username, json_string_value(json_object_get(j_scheme_trigger, "provider"))); if (check_result_value(j_register, G_OK)) { j_provider = get_provider(oauth2_config, json_string_value(json_object_get(j_scheme_trigger, "provider"))); if (check_result_value(j_provider, G_OK)) { j_result = add_session_for_user(config, oauth2_config, username, json_array_get(json_object_get(j_register, "registration"), 0), json_object_get(j_provider, "provider"), json_string_value(json_object_get(j_scheme_trigger, "callback_url"))); if (check_result_value(j_result, G_OK)) { j_return = json_pack("{sis{sO}}", "result", G_OK, "response", "redirect_to", json_object_get(j_result, "session")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_trigger oauth2 - Error add_session_for_user"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "response", "provider invalid"); } json_decref(j_provider); } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "response", "provider invalid"); } json_decref(j_register); } return j_return; } /** * * user_auth_scheme_module_validate * * Validate the scheme for a user * Ex: check the code sent to a device, verify the challenge, etc. * * @return value: G_OK on success * G_ERROR_UNAUTHORIZED if validation fails * G_ERROR_PARAM if error in parameters * G_ERROR on another error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter j_scheme_data: data sent to validate the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_validate(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls) { UNUSED(http_request); json_t * j_result, * j_provider; int res, ret; struct _oauth2_config * oauth2_config = (struct _oauth2_config *)cls; j_result = get_registration_for_user(config, oauth2_config, username, json_string_value(json_object_get(j_scheme_data, "provider"))); if (check_result_value(j_result, G_OK)) { j_provider = get_provider(oauth2_config, json_string_value(json_object_get(j_scheme_data, "provider"))); if (check_result_value(j_provider, G_OK)) { if ((res = complete_session_for_user(config, json_string_value(json_object_get(oauth2_config->j_parameters, "redirect_uri")), json_array_get(json_object_get(j_result, "registration"), 0), json_object_get(j_provider, "provider"), json_string_value(json_object_get(j_scheme_data, "redirect_to")), json_string_value(json_object_get(j_scheme_data, "state")), GLEWLWYD_SCHEME_OAUTH2_SESSION_AUTHENTICATION)) == G_OK) { ret = G_OK; } else if (res == G_ERROR_PARAM || res == G_ERROR_UNAUTHORIZED || res == G_ERROR_NOT_FOUND) { ret = G_ERROR_UNAUTHORIZED; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate oauth2 - Error complete_session_for_user"); ret = G_ERROR; } } else { ret = G_ERROR_UNAUTHORIZED; } json_decref(j_provider); } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { ret = G_ERROR_UNAUTHORIZED; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate oauth2 - Error get_registration_for_user"); ret = G_ERROR; } json_decref(j_result); return ret; } /** * * user_auth_scheme_module_identify * * Identify the user using the scheme without the username to be previously given * This functionality isn't available for all schemes, because the scheme authentification * must be triggered without username and the authentication result must contain the username * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * username: string value of the user identified - if the function is called within /auth * response: JSON object, optional - if the function is called within /auth/scheme/trigger * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter j_scheme_data: data sent to validate the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_identify(struct config_module * config, const struct _u_request * http_request, json_t * j_scheme_data, void * cls) { UNUSED(http_request); struct _oauth2_config * oauth2_config = (struct _oauth2_config *)cls; json_t * j_return, * j_result, * j_provider, * j_element = NULL; size_t index = 0; if (0 == o_strcmp("trigger", json_string_value(json_object_get(j_scheme_data, "action")))) { j_provider = get_provider(oauth2_config, json_string_value(json_object_get(j_scheme_data, "provider"))); if (check_result_value(j_provider, G_OK)) { j_result = add_session_identify(config, oauth2_config, json_object_get(j_provider, "provider"), json_string_value(json_object_get(j_scheme_data, "callback_url"))); if (check_result_value(j_result, G_OK)) { j_return = json_pack("{sis{sO}}", "result", G_OK, "response", "redirect_to", json_object_get(j_result, "session")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_identify oauth2 - Error add_session_identify"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "response", "provider invalid"); } json_decref(j_provider); } else if (0 == o_strcmp("verify", json_string_value(json_object_get(j_scheme_data, "action")))) { j_provider = get_provider(oauth2_config, json_string_value(json_object_get(j_scheme_data, "provider"))); if (check_result_value(j_provider, G_OK)) { j_result = complete_session_identify(config, oauth2_config, json_object_get(j_provider, "provider"), json_string_value(json_object_get(oauth2_config->j_parameters, "redirect_uri")), json_string_value(json_object_get(j_scheme_data, "redirect_to")), json_string_value(json_object_get(j_scheme_data, "state"))); if (check_result_value(j_result, G_OK)) { j_return = json_pack("{sisO}", "result", G_OK, "username", json_object_get(j_result, "username")); } else if (!check_result_value(j_result, G_ERROR)) { j_return = json_incref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_identify oauth2 - Error complete_session_identify"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } json_decref(j_provider); } else if (0 == o_strcmp("provider_list", json_string_value(json_object_get(j_scheme_data, "action")))) { j_return = json_pack("{sis[]}", "result", G_OK, "response"); json_array_foreach(json_object_get(oauth2_config->j_parameters, "provider_list"), index, j_element) { json_array_append_new(json_object_get(j_return, "response"), json_pack("{sOsOsOso}", "provider", json_object_get(j_element, "name"), "logo_uri", json_object_get(j_element, "logo_uri"), "logo_fa", json_object_get(j_element, "logo_fa"), "created_at", json_null())); } } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } glewlwyd-2.6.1/src/scheme/oauth2.mariadb.sql000066400000000000000000000015761415646314000207470ustar00rootroot00000000000000DROP TABLE IF EXISTS gs_oauth2_session; DROP TABLE IF EXISTS gs_oauth2_registration; CREATE TABLE gs_oauth2_registration ( gsor_id INT(11) PRIMARY KEY AUTO_INCREMENT, gsor_mod_name VARCHAR(128) NOT NULL, gsor_provider VARCHAR(128) NOT NULL, gsor_created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gsor_username VARCHAR(128) NOT NULL, gsor_userinfo_sub VARCHAR(128) ); CREATE INDEX i_gsor_username ON gs_oauth2_registration(gsor_username); CREATE TABLE gs_oauth2_session ( gsos_id INT(11) PRIMARY KEY AUTO_INCREMENT, gsor_id INT(11), gsos_created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gsos_expires_at TIMESTAMP, gsos_state TEXT NOT NULL, gsos_session_export TEXT, gsos_status TINYINT(1) DEFAULT 0, -- 0: registration, 1: authentication, 2: verified, 3: cancelled FOREIGN KEY(gsor_id) REFERENCES gs_oauth2_registration(gsor_id) ON DELETE CASCADE ); glewlwyd-2.6.1/src/scheme/oauth2.postgre.sql000066400000000000000000000015121415646314000210210ustar00rootroot00000000000000DROP TABLE IF EXISTS gs_oauth2_session; DROP TABLE IF EXISTS gs_oauth2_registration; CREATE TABLE gs_oauth2_registration ( gsor_id SERIAL PRIMARY KEY, gsor_mod_name VARCHAR(128) NOT NULL, gsor_provider VARCHAR(128) NOT NULL, gsor_created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gsor_username VARCHAR(128) NOT NULL, gsor_userinfo_sub VARCHAR(128) ); CREATE INDEX i_gsor_username ON gs_oauth2_registration(gsor_username); CREATE TABLE gs_oauth2_session ( gsos_id SERIAL PRIMARY KEY, gsor_id INTEGER, gsos_created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gsos_expires_at TIMESTAMPTZ, gsos_state TEXT NOT NULL, gsos_session_export TEXT, gsos_status SMALLINT DEFAULT 0, -- 0: registration, 1: authentication, 2: verified, 3: cancelled FOREIGN KEY(gsor_id) REFERENCES gs_oauth2_registration(gsor_id) ON DELETE CASCADE ); glewlwyd-2.6.1/src/scheme/oauth2.sqlite.sql000066400000000000000000000015311415646314000206400ustar00rootroot00000000000000DROP TABLE IF EXISTS gs_oauth2_session; DROP TABLE IF EXISTS gs_oauth2_registration; CREATE TABLE gs_oauth2_registration ( gsor_id INTEGER PRIMARY KEY AUTOINCREMENT, gsor_mod_name TEXT NOT NULL, gsor_provider TEXT NOT NULL, gsor_created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gsor_username TEXT NOT NULL, gsor_userinfo_sub TEXT ); CREATE INDEX i_gsor_username ON gs_oauth2_registration(gsor_username); CREATE TABLE gs_oauth2_session ( gsos_id INTEGER PRIMARY KEY AUTOINCREMENT, gsor_id INTEGER, gsos_created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gsos_expires_at TIMESTAMP, gsos_state TEXT NOT NULL, gsos_session_export TEXT, gsos_status INTEGER DEFAULT 0, -- 0: registration, 1: authentication, 2: verified, 3: cancelled FOREIGN KEY(gsor_id) REFERENCES gs_oauth2_registration(gsor_id) ON DELETE CASCADE ); glewlwyd-2.6.1/src/scheme/otp.c000066400000000000000000001123331415646314000163660ustar00rootroot00000000000000/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * HOTP/TOTP authentication scheme module * * Copyright 2019-2020 Nicolas Mora * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . * */ #include #include #include #include #include #include #include #include "glewlwyd-common.h" #define GLEWLWYD_TABLE_OTP "gs_otp" #define G_TOTP_DEFAULT_TIME_STEP_SIZE 30 #define G_TOTP_DEFAULT_START_OFFSET 0 static int is_current_otp_available(struct config_module * config, json_t * j_params, const char * username) { time_t now; json_t * j_query, * j_result; int res, ret; char * username_escaped, * username_clause, * last_used_clause; time(&now); username_escaped = h_escape_string_with_quotes(config->conn, username); username_clause = msprintf(" = UPPER(%s)", username_escaped); if (config->conn->type==HOEL_DB_TYPE_MARIADB) { last_used_clause = msprintf("< (FROM_UNIXTIME(%u-gso_totp_time_step_size))", now); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { last_used_clause = msprintf("< (TO_TIMESTAMP(%u-gso_totp_time_step_size))", now); } else { // HOEL_DB_TYPE_SQLITE last_used_clause = msprintf("< (%u-gso_totp_time_step_size)", now); } j_query = json_pack("{sss[s]s{sOs{ssss}s{ssss}}}", "table", GLEWLWYD_TABLE_OTP, "columns", "gso_id", "where", "gso_mod_name", json_object_get(j_params, "mod_name"), "UPPER(gso_username)", "operator", "raw", "value", username_clause, "gso_last_used", "operator", "raw", "value", last_used_clause); o_free(last_used_clause); o_free(username_clause); o_free(username_escaped); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { ret = G_OK; } else { ret = G_ERROR_UNAUTHORIZED; } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "is_current_otp_possible - Error executing j_query"); ret = G_ERROR_PARAM; } return ret; } static json_t * is_scheme_parameters_valid(json_t * j_params) { json_t * j_return, * j_error; if (json_is_object(j_params)) { j_error = json_array(); if (j_error != NULL) { if (json_integer_value(json_object_get(j_params, "otp-length")) != 6 && json_integer_value(json_object_get(j_params, "otp-length")) != 7 && json_integer_value(json_object_get(j_params, "otp-length")) != 8) { json_array_append_new(j_error, json_string("otp-length is mandatory and must be 6, 7 or 8")); } if (!json_string_length(json_object_get(j_params, "issuer"))) { json_array_append_new(j_error, json_string("issuer is mandatory and must be a non empty string")); } if (json_integer_value(json_object_get(j_params, "secret-minimum-size")) <= 0 || json_integer_value(json_object_get(j_params, "secret-minimum-size")) > 128) { json_array_append_new(j_error, json_string("secret-minimum-size is mandatory and must be between 0 and 128")); } if (json_object_get(j_params, "hotp-allow") != NULL && !json_is_boolean(json_object_get(j_params, "hotp-allow"))) { json_array_append_new(j_error, json_string("hotp-allow is optional and must be a boolean")); } if (json_object_get(j_params, "hotp-window") != NULL && json_integer_value(json_object_get(j_params, "hotp-window")) < 0) { json_array_append_new(j_error, json_string("hotp-window is optional and must be a positive integer")); } if (json_object_get(j_params, "totp-allow") != NULL && !json_is_boolean(json_object_get(j_params, "totp-allow"))) { json_array_append_new(j_error, json_string("totp-allow is optional and must be a boolean")); } if (json_object_get(j_params, "totp-window") != NULL && json_integer_value(json_object_get(j_params, "totp-window")) < 0) { json_array_append_new(j_error, json_string("totp-window is optional and must be a positive integer")); } if (json_object_get(j_params, "totp-start-offset") != NULL && json_integer_value(json_object_get(j_params, "totp-start-offset")) < 0) { json_array_append_new(j_error, json_string("totp-start-offset is optional and must be a positive integer")); } if (json_array_size(j_error)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_error); } else { j_return = json_pack("{si}", "result", G_OK); } json_decref(j_error); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_scheme_parameters_valid - Error allocating resources for j_error"); j_return = json_pack("{si}", "result", G_ERROR); } } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "parameters must be a JSON object"); } return j_return; } static json_t * get_otp(struct config_module * config, json_t * j_params, const char * username) { json_t * j_query, * j_result, * j_return; int res; char * username_escaped, * username_clause; username_escaped = h_escape_string_with_quotes(config->conn, username); username_clause = msprintf(" = UPPER(%s)", username_escaped); j_query = json_pack("{sss[sssss]s{s{ssss}sO}}", "table", GLEWLWYD_TABLE_OTP, "columns", SWITCH_DB_TYPE(config->conn->type, "UNIX_TIMESTAMP(gso_issued_at) AS issued_at", "gso_issued_at AS issued_at", "EXTRACT(EPOCH FROM gso_issued_at)::integer AS issued_at"), "gso_otp_type", "gso_secret AS secret", "gso_hotp_moving_factor AS moving_factor", "gso_totp_time_step_size AS time_step_size", "where", "UPPER(gso_username)", "operator", "raw", "value", username_clause, "gso_mod_name", json_object_get(j_params, "mod_name")); o_free(username_clause); o_free(username_escaped); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { if (json_integer_value(json_object_get(json_array_get(j_result, 0), "gso_otp_type")) == 0) { json_object_set_new(json_array_get(j_result, 0), "type", json_string("HOTP")); json_object_del(json_array_get(j_result, 0), "time_step_size"); json_object_del(json_array_get(j_result, 0), "start_offset"); } else { json_object_set_new(json_array_get(j_result, 0), "type", json_string("TOTP")); json_object_del(json_array_get(j_result, 0), "moving_factor"); } json_object_del(json_array_get(j_result, 0), "gso_otp_type"); j_return = json_pack("{sisO}", "result", G_OK, "otp", json_array_get(j_result, 0)); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_otp - Error executing j_query"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } static int update_otp(struct config_module * config, json_t * j_params, const char * username, int increment_moving_factor) { char * username_escaped, * username_clause, * last_login_clause; json_t * j_query; int ret; username_escaped = h_escape_string_with_quotes(config->conn, username); username_clause = msprintf(" = UPPER(%s)", username_escaped); if (config->conn->type==HOEL_DB_TYPE_MARIADB) { last_login_clause = msprintf("FROM_UNIXTIME(%u)", (time(NULL))); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { last_login_clause = msprintf("TO_TIMESTAMP(%u)", (time(NULL))); } else { // HOEL_DB_TYPE_SQLITE last_login_clause = msprintf("%u", (time(NULL))); } j_query = json_pack("{sss{s{ss}}s{s{ssss}sO}}", "table", GLEWLWYD_TABLE_OTP, "set", "gso_last_used", "raw", last_login_clause, "where", "UPPER(gso_username)", "operator", "raw", "value", username_clause, "gso_mod_name", json_object_get(j_params, "mod_name")); o_free(username_clause); o_free(username_escaped); o_free(last_login_clause); if (increment_moving_factor) { json_object_set_new(json_object_get(j_query, "set"), "gso_hotp_moving_factor", json_pack("{ss}", "raw", "gso_hotp_moving_factor+1")); } if (h_update(config->conn, j_query, NULL) == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "update_otp - Error executing j_query"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } json_decref(j_query); return ret; } static int set_otp(struct config_module * config, json_t * j_params, const char * username, json_t * j_scheme_data) { json_t * j_query, * j_otp; int ret, res, type = (0==o_strcmp(json_string_value(json_object_get(j_scheme_data, "type")), "HOTP")?0:1); char * username_escaped, * username_clause; if (0 != o_strcmp(json_string_value(json_object_get(j_scheme_data, "type")), "NONE")) { j_otp = get_otp(config, j_params, username); if (check_result_value(j_otp, G_OK)) { username_escaped = h_escape_string_with_quotes(config->conn, username); username_clause = msprintf(" = UPPER(%s)", username_escaped); j_query = json_pack("{sss{sisOsOso}s{s{ssss}sO}}", "table", GLEWLWYD_TABLE_OTP, "set", "gso_otp_type", type, "gso_secret", json_object_get(j_scheme_data, "secret"), "gso_hotp_moving_factor", type==0?(json_object_get(j_scheme_data, "moving_factor")!=NULL?json_object_get(j_scheme_data, "moving_factor"):0):json_null(), "gso_totp_time_step_size", type==1?(json_object_get(j_scheme_data, "time_step_size")!=NULL?json_integer(json_integer_value(json_object_get(j_scheme_data, "time_step_size"))):json_integer(G_TOTP_DEFAULT_TIME_STEP_SIZE)):json_null(), "where", "UPPER(gso_username)", "operator", "raw", "value", username_clause, "gso_mod_name", json_object_get(j_params, "mod_name")); o_free(username_clause); o_free(username_escaped); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_otp - Error h_update"); ret = G_ERROR_NOT_FOUND; } } else if (check_result_value(j_otp, G_ERROR_NOT_FOUND)) { j_query = json_pack("{sss{sisOsOsosssOs{ss}}}", "table", GLEWLWYD_TABLE_OTP, "values", "gso_otp_type", type, "gso_secret", json_object_get(j_scheme_data, "secret"), "gso_hotp_moving_factor", type==0?(json_object_get(j_scheme_data, "moving_factor")!=NULL?json_object_get(j_scheme_data, "moving_factor"):0):json_null(), "gso_totp_time_step_size", type==1?(json_object_get(j_scheme_data, "time_step_size")!=NULL?json_integer(json_integer_value(json_object_get(j_scheme_data, "time_step_size"))):json_integer(G_TOTP_DEFAULT_TIME_STEP_SIZE)):json_null(), "gso_username", username, "gso_mod_name", json_object_get(j_params, "mod_name"), "gso_last_used", "raw", SWITCH_DB_TYPE(config->conn->type, "FROM_UNIXTIME(1)", "1", "TO_TIMESTAMP(1)")); // Let's hope no one will use Glewlwyd before January 1st, 1970! res = h_insert(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_otp - Error h_insert"); ret = G_ERROR_NOT_FOUND; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_otp - Error get_otp"); ret = G_ERROR; } json_decref(j_otp); } else { username_escaped = h_escape_string_with_quotes(config->conn, username); username_clause = msprintf(" = UPPER(%s)", username_escaped); j_query = json_pack("{sss{s{ssss}sO}}", "table", GLEWLWYD_TABLE_OTP, "where", "UPPER(gso_username)", "operator", "raw", "value", username_clause, "gso_mod_name", json_object_get(j_params, "mod_name")); o_free(username_clause); o_free(username_escaped); res = h_delete(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_otp - Error h_delete"); ret = G_ERROR_NOT_FOUND; } } return ret; } /** * * user_auth_scheme_module_load * * Executed once when Glewlwyd service is started * Used to identify the module and to show its parameters on init * You can also use it to load resources that are required once for all * instance modules for example * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * name: string, mandatory, name of the module, must be unique among other scheme modules * display_name: string, optional, long name of the module * description: string, optional, description for the module * parameters: object, optional, parameters description for the module * } * * Example: * { * result: G_OK, * name: "mock", * display_name: "Mock scheme module", * description: "Mock scheme module for glewlwyd tests", * parameters: { * mock-value: { * type: "string", * mandatory: true * } * } * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ json_t * user_auth_scheme_module_load(struct config_module * config) { UNUSED(config); return json_pack("{sissssss}", "result", G_OK, "name", "otp", "display_name", "HOTP/TOTP", "description", "HOTP/TOTP scheme module for glewlwyd"); } /** * * user_auth_scheme_module_unload * * Executed once when Glewlwyd service is stopped * You can also use it to release resources that are required once for all * instance modules for example * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ int user_auth_scheme_module_unload(struct config_module * config) { UNUSED(config); return G_OK; } /** * * user_auth_scheme_module_init * * Initialize an instance of this module declared in Glewlwyd service. * If required, you must dynamically allocate a pointer to the configuration * for this instance and pass it to *cls * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, G_ERROR_PARAM on input parameters error, another value on error) * error: array of strings containg the list of input errors, mandatory on result G_ERROR_PARAM, ignored otherwise * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_parameters: used to initialize an instance in JSON format * The module must validate itself its parameters * @parameter mod_name: module name in glewlwyd service * @parameter cls: will contain an allocated void * pointer that will be sent back * as void * in all module functions * */ json_t * user_auth_scheme_module_init(struct config_module * config, json_t * j_parameters, const char * mod_name, void ** cls) { UNUSED(config); json_t * j_result = is_scheme_parameters_valid(j_parameters), * j_return; char * message; if (check_result_value(j_result, G_OK)) { json_object_set_new(j_parameters, "mod_name", json_string(mod_name)); *cls = json_incref(j_parameters); j_return = json_pack("{si}", "result", G_OK); } else if (check_result_value(j_result, G_ERROR_PARAM)) { message = json_dumps(json_object_get(j_result, "error"), JSON_COMPACT); y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init otp - Error input parameters: %s", message); o_free(message); j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", json_object_get(j_result, "error")); } else { j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); return j_return; } /** * * user_auth_scheme_module_close * * Close an instance of this module declared in Glewlwyd service. * You must free the memory previously allocated in * the user_auth_scheme_module_init function as void * cls * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_close(struct config_module * config, void * cls) { UNUSED(config); json_decref((json_t *)cls); return G_OK; } /** * * user_auth_scheme_module_can_use * * Validate if the user is allowed to use this scheme prior to the * authentication or registration * * @return value: GLEWLWYD_IS_REGISTERED - User can use scheme and has registered * GLEWLWYD_IS_AVAILABLE - User can use scheme but hasn't registered * GLEWLWYD_IS_NOT_AVAILABLE - User can't use scheme * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_can_use(struct config_module * config, const char * username, void * cls) { json_t * j_otp; int ret; j_otp = get_otp(config, (json_t *)cls, username); if (check_result_value(j_otp, G_OK)) { ret = GLEWLWYD_IS_REGISTERED; } else if (check_result_value(j_otp, G_ERROR_NOT_FOUND)) { ret = GLEWLWYD_IS_AVAILABLE; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_can_use otp - Error get_otp"); ret = GLEWLWYD_IS_NOT_AVAILABLE; } json_decref(j_otp); return ret; } /** * * user_auth_scheme_module_register * * Register the scheme for a user * Ex: add a certificate, add new TOTP values, etc. * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the HTTP API * @parameter username: username to identify the user * @parameter j_scheme_data: additional data used to register the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_register(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls) { UNUSED(config); UNUSED(http_request); json_t * j_return = NULL; char * secret = NULL, * secret_b32 = NULL; size_t secret_len = 0, secret_b32_len = 0; if (json_is_object(j_scheme_data)) { if (json_object_get(j_scheme_data, "generate-secret") == json_true()) { secret_len = json_integer_value(json_object_get((json_t *)cls, "secret-minimum-size"))*sizeof(unsigned char); if ((secret = o_malloc(secret_len)) != NULL) { if (!gnutls_rnd(GNUTLS_RND_KEY, secret, secret_len)) { if (oath_base32_encode(secret, secret_len, &secret_b32, &secret_b32_len) == OATH_OK) { j_return = json_pack("{sis{ss%}}", "result", G_OK, "response", "secret", secret_b32, secret_b32_len); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register otp - Error oath_base32_encode"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(secret_b32); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register otp - Error gnutls_rnd"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register otp - Error allocating resources for secret"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(secret); } else { if (0 == o_strcmp(json_string_value(json_object_get(j_scheme_data, "type")), "NONE") || 0 == o_strcmp(json_string_value(json_object_get(j_scheme_data, "type")), "HOTP") || 0 == o_strcmp(json_string_value(json_object_get(j_scheme_data, "type")), "TOTP")) { if (0 != o_strcmp(json_string_value(json_object_get(j_scheme_data, "type")), "NONE")) { if (oath_base32_decode(json_string_value(json_object_get(j_scheme_data, "secret")), json_string_length(json_object_get(j_scheme_data, "secret")), &secret, &secret_len) == OATH_OK) { if (secret_len >= (size_t)json_integer_value(json_object_get((json_t *)cls, "secret_minimum_size")) && json_string_length(json_object_get(j_scheme_data, "secret")) < 256) { if (json_string_length(json_object_get(j_scheme_data, "secret")) >= 8) { if (0 == o_strcmp(json_string_value(json_object_get(j_scheme_data, "type")), "HOTP")) { if (json_object_get((json_t *)cls, "hotp-allow") == json_false()) { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "response", "HOTP Type not allowed"); } else if (!json_is_integer(json_object_get(j_scheme_data, "moving_factor")) || json_integer_value(json_object_get(j_scheme_data, "moving_factor")) < 0) { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "response", "moving_factor is optional and must be a positive integer or zero"); } } else if (0 == o_strcmp(json_string_value(json_object_get(j_scheme_data, "type")), "TOTP")) { if (json_object_get((json_t *)cls, "totp-allow") == json_false()) { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "response", "TOTP Type not allowed"); } else if (json_integer_value(json_object_get(j_scheme_data, "time_step_size")) <= 0 || json_integer_value(json_object_get(j_scheme_data, "time_step_size")) > 120) { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "response", "time_step_size is optional and must be a positive integer up to 120"); } } } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "response", "secret is mandatory and must be at least 8 characters"); } } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "response", "shared secret invalid size"); } } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "response", "shared secret must be base32 encoded"); } o_free(secret); } } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "response", "invalid type, type must be 'HOTP' 'TOTP' or 'NONE'"); } } } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "response", "data must be a JSON object"); } if (j_return == NULL) { return json_pack("{si}", "result", set_otp(config, (json_t *)cls, username, j_scheme_data)==G_OK?G_OK:G_ERROR); } return j_return; } /** * * user_auth_scheme_module_register_get * * Get the registration value(s) of the scheme for a user * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_register_get(struct config_module * config, const struct _u_request * http_request, const char * username, void * cls) { UNUSED(config); UNUSED(http_request); json_t * j_otp, * j_return; j_otp = get_otp(config, (json_t *)cls, username); if (check_result_value(j_otp, G_OK)) { json_object_set(json_object_get(j_otp, "otp"), "digits", json_object_get((json_t *)cls, "otp-length")); json_object_set(json_object_get(j_otp, "otp"), "issuer", json_object_get((json_t *)cls, "issuer")); json_object_set(json_object_get(j_otp, "otp"), "hotp-allow", json_object_get((json_t *)cls, "hotp-allow")==json_false()?json_false():json_true()); json_object_set(json_object_get(j_otp, "otp"), "totp-allow", json_object_get((json_t *)cls, "totp-allow")==json_false()?json_false():json_true()); j_return = json_pack("{sisO}", "result", G_OK, "response", json_object_get(j_otp, "otp")); } else if (check_result_value(j_otp, G_ERROR_NOT_FOUND)) { j_return = json_pack("{sis{sssososIsIsI}}", "result", G_OK, "response", "type", "NONE", "hotp-allow", json_object_get((json_t *)cls, "hotp-allow")==json_false()?json_false():json_true(), "totp-allow", json_object_get((json_t *)cls, "totp-allow")==json_false()?json_false():json_true(), "hotp-window", json_integer_value(json_object_get((json_t *)cls, "hotp-window")), "totp-window", json_object_get((json_t *)cls, "totp-window")!=NULL?json_integer_value(json_object_get((json_t *)cls, "totp-window")):G_TOTP_DEFAULT_TIME_STEP_SIZE, "totp-start-offset", json_object_get((json_t *)cls, "totp-start-offset")!=NULL?json_integer_value(json_object_get((json_t *)cls, "totp-start-offset")):G_TOTP_DEFAULT_START_OFFSET); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register_get otp - Error get_otp"); } json_decref(j_otp); return j_return; } /** * * user_auth_scheme_module_deregister * * Deregister the scheme for a user * Ex: remove certificates, TOTP values, etc. * * @return value: G_OK on success, even if no data has been removed * G_ERROR on another error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_deregister(struct config_module * config, const char * username, void * cls) { json_t * j_scheme_data = json_pack("{ss}", "type", "NONE"); int ret = set_otp(config, (json_t *)cls, username, j_scheme_data); json_decref(j_scheme_data); return ret; } /** * * user_auth_scheme_module_trigger * * Trigger the scheme for a user * Ex: send the code to a device, generate a challenge, etc. * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter scheme_trigger: data sent to trigger the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_trigger(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_trigger, void * cls) { UNUSED(config); UNUSED(http_request); UNUSED(username); UNUSED(j_scheme_trigger); UNUSED(cls); return json_pack("{si}", "result", G_OK); } /** * * user_auth_scheme_module_validate * * Validate the scheme for a user * Ex: check the code sent to a device, verify the challenge, etc. * * @return value: G_OK on success * G_ERROR_UNAUTHORIZED if validation fails * G_ERROR_PARAM if error in parameters * G_ERROR on another error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter j_scheme_data: data sent to validate the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_validate(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls) { UNUSED(config); UNUSED(http_request); int ret, res; json_t * j_otp; char * secret_decoded = NULL; size_t secret_decoded_len; if (!json_string_length(json_object_get(j_scheme_data, "value")) || json_string_length(json_object_get(j_scheme_data, "value")) != (size_t)json_integer_value(json_object_get((json_t *)cls, "otp-length"))) { ret = G_ERROR_UNAUTHORIZED; } else if (user_auth_scheme_module_can_use(config, username, cls) == GLEWLWYD_IS_REGISTERED) { j_otp = get_otp(config, (json_t *)cls, username); if (check_result_value(j_otp, G_OK)) { if (oath_base32_decode(json_string_value(json_object_get(json_object_get(j_otp, "otp"), "secret")), json_string_length(json_object_get(json_object_get(j_otp, "otp"), "secret")), &secret_decoded, &secret_decoded_len) == OATH_OK) { if (0 == o_strcmp(json_string_value(json_object_get(json_object_get(j_otp, "otp"), "type")), "HOTP")) { if ((ret = oath_hotp_validate(secret_decoded, secret_decoded_len, json_integer_value(json_object_get(json_object_get(j_otp, "otp"), "moving_factor")), json_integer_value(json_object_get((json_t *)cls, "window")), json_string_value(json_object_get(j_scheme_data, "value")))) >= 0) { if (update_otp(config, (json_t *)cls, username, 1) == G_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate otp - Error update_otp (1)"); ret = G_ERROR; } } else if (ret == OATH_INVALID_OTP) { ret = G_ERROR_UNAUTHORIZED; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate otp - Error oath_hotp_validate: '%s'", oath_strerror(ret)); ret = G_ERROR; } } else { if ((res = is_current_otp_available(config, (json_t *)cls, username)) == G_OK) { if ((ret = oath_totp_validate(secret_decoded, secret_decoded_len, time(NULL), json_integer_value(json_object_get(json_object_get(j_otp, "otp"), "time_step_size")), json_integer_value(json_object_get((json_t *)cls, "totp-start-offset")), json_integer_value(json_object_get((json_t *)cls, "window")), json_string_value(json_object_get(j_scheme_data, "value")))) >= 0) { if (update_otp(config, (json_t *)cls, username, 0) == G_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate otp - Error update_otp (1)"); ret = G_ERROR; } } else if (ret == OATH_INVALID_OTP) { ret = G_ERROR_UNAUTHORIZED; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate otp - Error oath_hotp_validate: '%s'", oath_strerror(ret)); ret = G_ERROR; } } else if (res == G_ERROR_UNAUTHORIZED) { ret = G_ERROR_UNAUTHORIZED; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate otp - Error is_current_otp_available"); ret = G_ERROR; } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate otp - Error oath_base32_decode"); ret = G_ERROR; } o_free(secret_decoded); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate otp - Error get_otp"); ret = G_ERROR; } json_decref(j_otp); } else { ret = G_ERROR_UNAUTHORIZED; } return ret; } /** * * user_auth_scheme_module_identify * * Identify the user using the scheme without the username to be previously given * This functionality isn't available for all schemes, because the scheme authentification * must be triggered without username and the authentication result must contain the username * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * username: string value of the user identified - if the function is called within /auth * response: JSON object, optional - if the function is called within /auth/scheme/trigger * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter j_scheme_data: data sent to validate the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_identify(struct config_module * config, const struct _u_request * http_request, json_t * j_scheme_data, void * cls) { UNUSED(config); UNUSED(http_request); UNUSED(j_scheme_data); UNUSED(cls); return json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } glewlwyd-2.6.1/src/scheme/otp.mariadb.sql000066400000000000000000000010011415646314000203260ustar00rootroot00000000000000DROP TABLE IF EXISTS gs_otp; CREATE TABLE gs_otp ( gso_id INT(11) PRIMARY KEY AUTO_INCREMENT, gso_mod_name VARCHAR(128) NOT NULL, gso_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gso_last_used TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gso_username VARCHAR(128) NOT NULL, gso_otp_type TINYINT(1) DEFAULT 0, -- 0 HOTP, 1 TOTP gso_secret VARCHAR(128) NOT NULL, gso_hotp_moving_factor INT(11), gso_totp_time_step_size INT(11) ); CREATE INDEX i_gsso_username ON gs_otp(gso_username); glewlwyd-2.6.1/src/scheme/otp.postgre.sql000066400000000000000000000007331415646314000204250ustar00rootroot00000000000000DROP TABLE IF EXISTS gs_otp; CREATE TABLE gs_otp ( gso_id SERIAL PRIMARY KEY, gso_mod_name VARCHAR(128) NOT NULL, gso_issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gso_last_used TIMESTAMPTZ NOT NULL DEFAULT NOW(), gso_username VARCHAR(128) NOT NULL, gso_otp_type SMALLINT DEFAULT 0, -- 0 HOTP, 1 TOTP gso_secret VARCHAR(128) NOT NULL, gso_hotp_moving_factor INTEGER, gso_totp_time_step_size INTEGER ); CREATE INDEX i_gsso_username ON gs_otp(gso_username); glewlwyd-2.6.1/src/scheme/otp.sqlite.sql000066400000000000000000000007451415646314000202460ustar00rootroot00000000000000DROP TABLE IF EXISTS gs_otp; CREATE TABLE gs_otp ( gso_id INTEGER PRIMARY KEY AUTOINCREMENT, gso_mod_name TEXT NOT NULL, gso_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gso_last_used TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gso_username TEXT NOT NULL, gso_otp_type INTEGER DEFAULT 0, -- 0 HOTP, 1 TOTP gso_secret TEXT NOT NULL, gso_hotp_moving_factor INTEGER, gso_totp_time_step_size INTEGER ); CREATE INDEX i_gsso_username ON gs_otp(gso_username); glewlwyd-2.6.1/src/scheme/password.c000066400000000000000000000353721415646314000174350ustar00rootroot00000000000000/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * Password authentication scheme module * * Copyright 2019-2020 Nicolas Mora * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . * */ #include #include #include #include #include "glewlwyd-common.h" /** * * Note on the user auth scheme module * * It's possible for the scheme module to get or store any value in the user object returned by the functions * struct config_module.glewlwyd_module_callback_get_user() * struct config_module.glewlwyd_module_callback_set_user() * * Although, the module can't know if any value, other than "name", "password", "email" or "enabled" can be added or updated by the scheme module * The scheme module can store its specific data for each user by itself or store the data in the user object, or both, this will depend on the implementation * * The format of the structure config_module is: * * struct config_module { * const char * external_url; // Absolute url of the glewlwyd service * const char * login_url; // Relative url of the login page * const char * admin_scope; // Value of the g_admin scope * const char * profile_scope; // Value of the g_profile scope * struct _h_connection * conn; // Hoel structure to access to the database * digest_algorithm hash_algorithm; // Hash algorithm used in Glewlwyd * struct config_elements * glewlwyd_config; // Pointer to the global config structure * // Function used to return a user object * json_t * (* glewlwyd_module_callback_get_user)(struct config_module * config, const char * username); * // Function used to update a user * int (* glewlwyd_module_callback_set_user)(struct config_module * config, const char * username, json_t * j_user); * // Function used to check the validity of a user's password * int (* glewlwyd_module_callback_check_user_password)(struct config_module * config, const char * username, const char * password); * }; * */ /** * * user_auth_scheme_module_load * * Executed once when Glewlwyd service is started * Used to identify the module and to show its parameters on init * You can also use it to load resources that are required once for all * instance modules for example * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * name: string, mandatory, name of the module, must be unique among other scheme modules * display_name: string, optional, long name of the module * description: string, optional, description for the module * parameters: object, optional, parameters description for the module * } * * Example: * { * result: G_OK, * name: "mock", * display_name: "Mock scheme module", * description: "Mock scheme module for glewlwyd tests", * parameters: { * mock-value: { * type: "string", * mandatory: true * } * } * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ json_t * user_auth_scheme_module_load(struct config_module * config) { UNUSED(config); return json_pack("{sissssss}", "result", G_OK, "name", "retype-password", "display_name", "Short session password", "description", "Glewlwyd authentication via user password with a short session duration"); } /** * * user_auth_scheme_module_unload * * Executed once when Glewlwyd service is stopped * You can also use it to release resources that are required once for all * instance modules for example * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ int user_auth_scheme_module_unload(struct config_module * config) { UNUSED(config); return G_OK; } /** * * user_auth_scheme_module_init * * Initialize an instance of this module declared in Glewlwyd service. * If required, you must dynamically allocate a pointer to the configuration * for this instance and pass it to *cls * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, G_ERROR_PARAM on input parameters error, another value on error) * error: array of strings containg the list of input errors, mandatory on result G_ERROR_PARAM, ignored otherwise * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_parameters: used to initialize an instance in JSON format * The module must validate itself its parameters * @parameter mod_name: module name in glewlwyd service * @parameter cls: will contain an allocated void * pointer that will be sent back * as void * in all module functions * */ json_t * user_auth_scheme_module_init(struct config_module * config, json_t * j_parameters, const char * mod_name, void ** cls) { UNUSED(config); UNUSED(j_parameters); UNUSED(mod_name); UNUSED(cls); return json_pack("{si}", "result", G_OK); } /** * * user_auth_scheme_module_close * * Close an instance of this module declared in Glewlwyd service. * You must free the memory previously allocated in * the user_auth_scheme_module_init function as void * cls * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_close(struct config_module * config, void * cls) { UNUSED(config); UNUSED(cls); return G_OK; } /** * * user_auth_scheme_module_can_use * * Validate if the user is allowed to use this scheme prior to the * authentication or registration * * @return value: GLEWLWYD_IS_REGISTERED - User can use scheme and has registered * GLEWLWYD_IS_AVAILABLE - User can use scheme but hasn't registered * GLEWLWYD_IS_NOT_AVAILABLE - User can't use scheme * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_can_use(struct config_module * config, const char * username, void * cls) { UNUSED(config); UNUSED(username); UNUSED(username); UNUSED(cls); return GLEWLWYD_IS_REGISTERED; } /** * * user_auth_scheme_module_register * * Register the scheme for a user * Ex: add a certificate, add new TOTP values, etc. * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the HTTP API * @parameter username: username to identify the user * @parameter j_scheme_data: additional data used to register the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_register(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls) { UNUSED(config); UNUSED(http_request); UNUSED(username); UNUSED(j_scheme_data); UNUSED(cls); return json_pack("{si}", "result", G_OK); } /** * * user_auth_scheme_module_register_get * * Get the registration value(s) of the scheme for a user * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_register_get(struct config_module * config, const struct _u_request * http_request, const char * username, void * cls) { UNUSED(config); UNUSED(http_request); UNUSED(username); UNUSED(cls); return json_pack("{si}", "result", G_OK); } /** * * user_auth_scheme_module_deregister * * Deregister the scheme for a user * Ex: remove certificates, TOTP values, etc. * * @return value: G_OK on success, even if no data has been removed * G_ERROR on another error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: username to identify the user * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_deregister(struct config_module * config, const char * username, void * cls) { UNUSED(config); UNUSED(username); UNUSED(cls); return G_OK; } /** * * user_auth_scheme_module_trigger * * Trigger the scheme for a user * Ex: send the code to a device, generate a challenge, etc. * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * response: JSON object, optional * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter scheme_trigger: data sent to trigger the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_trigger(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_trigger, void * cls) { UNUSED(config); UNUSED(http_request); UNUSED(username); UNUSED(j_scheme_trigger); UNUSED(cls); return json_pack("{si}", "result", G_OK); } /** * * user_auth_scheme_module_validate * * Validate the scheme for a user * Ex: check the code sent to a device, verify the challenge, etc. * * @return value: G_OK on success * G_ERROR_UNAUTHORIZED if validation fails * G_ERROR_PARAM if error in parameters * G_ERROR on another error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter j_scheme_data: data sent to validate the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_validate(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls) { UNUSED(config); UNUSED(http_request); UNUSED(cls); int ret; if (json_string_length(json_object_get(j_scheme_data, "password"))) { ret = config->glewlwyd_module_callback_check_user_password(config, username, json_string_value(json_object_get(j_scheme_data, "password"))); if (ret != G_OK && ret != G_ERROR_UNAUTHORIZED) { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate password - Error glewlwyd_module_callback_check_user_password"); } } else { ret = G_ERROR_UNAUTHORIZED; } return ret; } /** * * user_auth_scheme_module_identify * * Identify the user using the scheme without the username to be previously given * This functionality isn't available for all schemes, because the scheme authentification * must be triggered without username and the authentication result must contain the username * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * username: string value of the user identified - if the function is called within /auth * response: JSON object, optional - if the function is called within /auth/scheme/trigger * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter j_scheme_data: data sent to validate the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_identify(struct config_module * config, const struct _u_request * http_request, json_t * j_scheme_data, void * cls) { UNUSED(config); UNUSED(http_request); UNUSED(j_scheme_data); UNUSED(cls); return json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } glewlwyd-2.6.1/src/scheme/webauthn.c000066400000000000000000005440771415646314000174170ustar00rootroot00000000000000/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * WebAuthn scheme module * * Copyright 2019-2020 Nicolas Mora * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include "glewlwyd-common.h" const char * iso_3166_list[] = {"AF", "AX", "AL", "DZ", "AS", "AD", "AO", "AI", "AQ", "AG", "AR", "AM", "AW", "AU", "AT", "AZ", "BH", "BS", "BD", "BB", "BY", "BE", "BZ", "BJ", "BM", "BT", "BO", "BQ", "BA", "BW", "BV", "BR", "IO", "BN", "BG", "BF", "BI", "KH", "CM", "CA", "CV", "KY", "CF", "TD", "CL", "CN", "CX", "CC", "CO", "KM", "CG", "CD", "CK", "CR", "CI", "HR", "CU", "CW", "CY", "CZ", "DK", "DJ", "DM", "DO", "EC", "EG", "SV", "GQ", "ER", "EE", "ET", "FK", "FO", "FJ", "FI", "FR", "GF", "PF", "TF", "GA", "GM", "GE", "DE", "GH", "GI", "GR", "GL", "GD", "GP", "GU", "GT", "GG", "GN", "GW", "GY", "HT", "HM", "VA", "HN", "HK", "HU", "IS", "IN", "ID", "IR", "IQ", "IE", "IM", "IL", "IT", "JM", "JP", "JE", "JO", "KZ", "KE", "KI", "KP", "KR", "KW", "KG", "LA", "LV", "LB", "LS", "LR", "LY", "LI", "LT", "LU", "MO", "MK", "MG", "MW", "MY", "MV", "ML", "MT", "MH", "MQ", "MR", "MU", "YT", "MX", "FM", "MD", "MC", "MN", "ME", "MS", "MA", "MZ", "MM", "NA", "NR", "NP", "NL", "NC", "NZ", "NI", "NE", "NG", "NU", "NF", "MP", "NO", "OM", "PK", "PW", "PS", "PA", "PG", "PY", "PE", "PH", "PN", "PL", "PT", "PR", "QA", "RE", "RO", "RU", "RW", "BL", "SH", "KN", "LC", "MF", "PM", "VC", "WS", "SM", "ST", "SA", "SN", "RS", "SC", "SL", "SG", "SX", "SK", "SI", "SB", "SO", "ZA", "GS", "SS", "ES", "LK", "SD", "SR", "SJ", "SZ", "SE", "CH", "SY", "TW", "TJ", "TZ", "TH", "TL", "TG", "TK", "TO", "TT", "TN", "TR", "TM", "TC", "TV", "UG", "UA", "AE", "GB", "US", "UM", "UY", "UZ", "VU", "VE", "VN", "VG", "VI", "WF", "EH", "YE", "ZM", "ZW", NULL}; #define G_PACKED_CERT_O_KEY "O=" #define G_PACKED_CERT_OU_KEY "OU=" #define G_PACKED_CERT_C_KEY "C=" #define G_PACKED_CERT_CN_KEY "CN=" #define G_PACKED_CERT_OU_VALUE "Authenticator Attestation" #define G_PACKED_OID_AAGUID "1.3.6.1.4.1.45724.1.1.4" #define G_APPLE_ANONYMOUS_ATTESTATION_OID "1.2.840.113635.100.8.2" #define G_TABLE_WEBAUTHN_USER "gs_webauthn_user" #define G_TABLE_WEBAUTHN_CREDENTIAL "gs_webauthn_credential" #define G_TABLE_WEBAUTHN_ASSERTION "gs_webauthn_assertion" #define SESSION_LENGTH 32 #define USER_ID_LENGTH 32 #define FLAG_USER_PRESENT 0x01 #define FLAG_USER_VERIFY 0x04 #define FLAG_AT 0x40 #define FLAG_ED 0x80 #define COUNTER_LEN 4 #define AAGUID_LEN 16 #define CRED_ID_L_LEN 2 #define FLAGS_OFFSET 32 #define COUNTER_OFFSET (FLAGS_OFFSET+1) #define ATTESTED_CRED_DATA_OFFSET (COUNTER_OFFSET+COUNTER_LEN) #define CRED_ID_L_OFFSET (ATTESTED_CRED_DATA_OFFSET+AAGUID_LEN) #define CREDENTIAL_ID_OFFSET (ATTESTED_CRED_DATA_OFFSET+AAGUID_LEN+CRED_ID_L_LEN) #define ECDSA256 -7 #define ECDSA384 -35 #define ECDSA512 -36 #define SAFETYNET_ISSUED_TO "CN=attest.android.com" static json_t * get_cert_from_file_path(const char * path) { gnutls_x509_crt_t cert = NULL; gnutls_datum_t cert_dat = {NULL, 0}, export_dat = {NULL, 0}; FILE * fl; size_t len, issued_for_len = 128; char * cert_content, issued_for[128] = {}; json_t * j_return = NULL; fl = fopen(path, "r"); if (fl != NULL) { fseek(fl, 0, SEEK_END); len = ftell(fl); cert_content = o_malloc(len); if (cert_content != NULL) { if (fseek(fl, 0, SEEK_SET) == -1) { y_log_message(Y_LOG_LEVEL_ERROR, "get_cert_from_file_path - Error fseek"); j_return = json_pack("{si}", "result", G_ERROR); } else if (fread(cert_content, 1, len, fl) != len) { y_log_message(Y_LOG_LEVEL_ERROR, "get_cert_from_file_path - Error fread"); j_return = json_pack("{si}", "result", G_ERROR); } else { cert_dat.data = (unsigned char *)cert_content; cert_dat.size = len; if (!gnutls_x509_crt_init(&cert)) { if (gnutls_x509_crt_import(cert, &cert_dat, GNUTLS_X509_FMT_DER) >= 0 || gnutls_x509_crt_import(cert, &cert_dat, GNUTLS_X509_FMT_PEM) >= 0) { if (!gnutls_x509_crt_get_dn(cert, issued_for, &issued_for_len)) { if (gnutls_x509_crt_export2(cert, GNUTLS_X509_FMT_PEM, &export_dat) >= 0) { j_return = json_pack("{sis{ss%ss%}}", "result", G_OK, "certificate", "dn", issued_for, issued_for_len, "x509", export_dat.data, export_dat.size); gnutls_free(export_dat.data); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_cert_from_file_path - Error gnutls_x509_crt_export2"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_cert_from_file_path - Error gnutls_x509_crt_get_dn"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_cert_from_file_path - Error gnutls_x509_crt_import"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_cert_from_file_path - Error gnutls_x509_crt_init"); j_return = json_pack("{si}", "result", G_ERROR); } gnutls_x509_crt_deinit(cert); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_cert_from_file_path - Error o_malloc cert_content"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } o_free(cert_content); fclose(fl); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_cert_from_file_path - Error fopen %s", path); j_return = json_pack("{si}", "result", G_ERROR); } return j_return; } static json_t * is_scheme_parameters_valid(json_t * j_params) { json_t * j_return, * j_error, * j_element = NULL, * j_cert; size_t index = 0; json_int_t pubkey; char * message; if (json_is_object(j_params)) { j_error = json_array(); if (j_error != NULL) { if (!json_is_boolean(json_object_get(j_params, "session-mandatory"))) { json_array_append_new(j_error, json_string("session-mandatory is mandatory and must be a boolean")); } if (json_object_get(j_params, "seed") != NULL && !json_is_string(json_object_get(j_params, "seed"))) { json_array_append_new(j_error, json_string("seed is optional and must be a string")); } if (json_integer_value(json_object_get(j_params, "challenge-length")) <= 0) { json_array_append_new(j_error, json_string("challenge-length is mandatory and must be a positive integer")); } if (json_integer_value(json_object_get(j_params, "credential-expiration")) <= 0) { json_array_append_new(j_error, json_string("credential-expiration is mandatory and must be a positive integer")); } if (json_integer_value(json_object_get(j_params, "credential-assertion")) <= 0) { json_array_append_new(j_error, json_string("credential-assertion is mandatory and must be a positive integer")); } if (!json_string_length(json_object_get(j_params, "rp-origin"))) { json_array_append_new(j_error, json_string("rp-origin is mandatory and must be a non empty string")); } if (!json_array_size(json_object_get(j_params, "pubKey-cred-params"))) { json_array_append_new(j_error, json_string("pubKey-cred-params is mandatory and must be a non empty JSON array")); } else { json_array_foreach(json_object_get(j_params, "pubKey-cred-params"), index, j_element) { pubkey = json_integer_value(j_element); //if (pubkey != -7 && pubkey != -35 && pubkey != -36 && pubkey != -257 && pubkey != -258 && pubkey != -259) { if (pubkey != ECDSA256 && pubkey != ECDSA384 && pubkey != ECDSA512) { //json_array_append_new(j_error, json_string("pubKey-cred-params elements values available are -7, -35, -36 (ECDSA) or -257, -258, -259 (RSA)")); json_array_append_new(j_error, json_string("pubKey-cred-params elements values available are -7, -35, -36 (ECDSA)")); } } } if (json_object_get(j_params, "ctsProfileMatch") != NULL && (!json_is_integer(json_object_get(j_params, "ctsProfileMatch")) || json_integer_value(json_object_get(j_params, "ctsProfileMatch")) < -1 || json_integer_value(json_object_get(j_params, "ctsProfileMatch")) > 1)) { json_array_append_new(j_error, json_string("ctsProfileMatch is optional and must be an integer between -1 and 1")); } if (json_object_get(j_params, "basicIntegrity") != NULL && (!json_is_integer(json_object_get(j_params, "basicIntegrity")) || json_integer_value(json_object_get(j_params, "basicIntegrity")) < -1 || json_integer_value(json_object_get(j_params, "basicIntegrity")) > 1)) { json_array_append_new(j_error, json_string("basicIntegrity is optional and must be an integer between -1 and 1")); } if (json_object_get(j_params, "google-root-ca-r2") != NULL && !json_is_string(json_object_get(j_params, "google-root-ca-r2"))) { json_array_append_new(j_error, json_string("google-root-ca-r2 is optional and must be a string")); } else if (json_string_length(json_object_get(j_params, "google-root-ca-r2"))) { j_cert = get_cert_from_file_path(json_string_value(json_object_get(j_params, "google-root-ca-r2"))); if (check_result_value(j_cert, G_OK)) { json_object_set(j_params, "google-root-ca-r2-content", json_object_get(j_cert, "certificate")); } else { message = msprintf("Error parsing google-root-ca-r2 certificate file %s", json_string_value(json_object_get(j_params, "google-root-ca-r2"))); json_array_append_new(j_error, json_string(message)); o_free(message); } json_decref(j_cert); } if (json_object_get(j_params, "apple-root-ca") != NULL && !json_is_string(json_object_get(j_params, "apple-root-ca"))) { json_array_append_new(j_error, json_string("apple-root-ca is optional and must be a string")); } else if (json_string_length(json_object_get(j_params, "apple-root-ca"))) { j_cert = get_cert_from_file_path(json_string_value(json_object_get(j_params, "apple-root-ca"))); if (check_result_value(j_cert, G_OK)) { json_object_set(j_params, "apple-root-ca-content", json_object_get(j_cert, "certificate")); } else { message = msprintf("Error parsing apple-root-ca certificate file %s", json_string_value(json_object_get(j_params, "apple-root-ca"))); json_array_append_new(j_error, json_string(message)); o_free(message); } json_decref(j_cert); } if (json_object_get(j_params, "root-ca-list") != NULL) { if (!json_is_array(json_object_get(j_params, "root-ca-list"))) { json_array_append_new(j_error, json_string("root-ca-list is optional and must be an array of strings")); } else { json_object_set_new(j_params, "root-ca-array", json_array()); json_array_foreach(json_object_get(j_params, "root-ca-list"), index, j_element) { if (!json_string_length(j_element)) { json_array_append_new(j_error, json_string("root-ca-list is optional and must be an array of strings")); } else { j_cert = get_cert_from_file_path(json_string_value(j_element)); if (check_result_value(j_cert, G_OK)) { json_array_append(json_object_get(j_params, "root-ca-array"), json_object_get(j_cert, "certificate")); } else { message = msprintf("Error parsing certificate file %s", json_string_value(j_element)); json_array_append_new(j_error, json_string(message)); o_free(message); } json_decref(j_cert); } } } } if (json_object_get(j_params, "force-fmt-none") != NULL && !json_is_boolean(json_object_get(j_params, "force-fmt-none"))) { json_array_append_new(j_error, json_string("allow-fmt-none is optional and must be a boolean")); } if (json_object_get(j_params, "fmt") != NULL && (!json_is_object(json_object_get(j_params, "fmt")) || (json_object_get(json_object_get(j_params, "fmt"), "packed") != json_true() && json_object_get(json_object_get(j_params, "fmt"), "tpm") != json_true() && json_object_get(json_object_get(j_params, "fmt"), "android-key") != json_true() && json_object_get(json_object_get(j_params, "fmt"), "android-safetynet") != json_true() && json_object_get(json_object_get(j_params, "fmt"), "fido-u2f") != json_true() && json_object_get(json_object_get(j_params, "fmt"), "none") != json_true()))) { json_array_append_new(j_error, json_string("fmt must be a JSON object filled with supported formats: 'packed' 'tpm', 'android-key', 'android-safetynet', 'fido-u2f', 'none'")); } if (json_array_size(j_error)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_error); } else { j_return = json_pack("{si}", "result", G_OK); } json_decref(j_error); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_scheme_parameters_valid - Error allocating resources for j_error"); j_return = json_pack("{si}", "result", G_ERROR); } } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "parameters must be a JSON object"); } return j_return; } /** * Get the user_id associated with the username in the table G_TABLE_WEBAUTHN_USER * If user_id doesn't exist, create one, stores it, and return the new user_id */ static json_t * get_user_id_from_username(struct config_module * config, json_t * j_param, const char * username, int create) { json_t * j_query, * j_result, * j_return; int res; char * username_escaped, * username_clause; unsigned char new_user_id[USER_ID_LENGTH] = {0}, new_user_id_b64[USER_ID_LENGTH*2] = {0}; size_t new_user_id_b64_len; username_escaped = h_escape_string_with_quotes(config->conn, username); username_clause = msprintf(" = UPPER(%s)", username_escaped); j_query = json_pack("{sss[s]s{s{ssss}sO}}", "table", G_TABLE_WEBAUTHN_USER, "columns", "gswu_user_id AS user_id", "where", "UPPER(gswu_username)", "operator", "raw", "value", username_clause, "gswu_mod_name", json_object_get(j_param, "mod_name")); o_free(username_clause); o_free(username_escaped); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_return = json_pack("{siss}", "result", G_OK, "user_id", json_string_value(json_object_get(json_array_get(j_result, 0), "user_id"))); } else if (create) { // Generates a new user_id, and stores it in the database gnutls_rnd(GNUTLS_RND_KEY, new_user_id, USER_ID_LENGTH); if (o_base64_encode(new_user_id, USER_ID_LENGTH, new_user_id_b64, &new_user_id_b64_len)) { j_query = json_pack("{sss{sOssss}}", "table", G_TABLE_WEBAUTHN_USER, "values", "gswu_mod_name", json_object_get(j_param, "mod_name"), "gswu_username", username, "gswu_user_id", new_user_id_b64); res = h_insert(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_return = json_pack("{siss}", "result", G_OK, "user_id", new_user_id_b64); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_id_from_username - Error executing j_query insert"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_id_from_username - Error o_base64_encode"); j_return = json_pack("{si}", "result", G_ERROR); } } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_id_from_username - Error executing j_query select"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } static json_t * get_credential_list(struct config_module * config, json_t * j_params, const char * username, int restrict_to_registered) { json_t * j_query, * j_result, * j_return, * j_element = NULL; int res; char * username_escaped, * mod_name_escaped, * username_clause; size_t index = 0; username_escaped = h_escape_string_with_quotes(config->conn, username); mod_name_escaped = h_escape_string_with_quotes(config->conn, json_string_value(json_object_get(j_params, "mod_name"))); username_clause = msprintf(" = (SELECT gswu_id FROM "G_TABLE_WEBAUTHN_USER" WHERE UPPER(gswu_username) = UPPER(%s) AND gswu_mod_name = %s)", username_escaped, mod_name_escaped); j_query = json_pack("{sss[ssss]s{s{ssss}}}", "table", G_TABLE_WEBAUTHN_CREDENTIAL, "columns", "gswc_credential_id AS credential_id", "gswc_name AS name", SWITCH_DB_TYPE(config->conn->type, "UNIX_TIMESTAMP(gswc_created_at) AS created_at", "strftime('%s', gswc_created_at) AS created_at", "EXTRACT(EPOCH FROM gswc_created_at)::integer AS created_at"), "gswc_status", "where", "gswu_id", "operator", "raw", "value", username_clause); o_free(username_clause); o_free(username_escaped); o_free(mod_name_escaped); if (restrict_to_registered) { json_object_set_new(json_object_get(j_query, "where"), "gswc_status", json_integer(1)); } else { json_object_set_new(json_object_get(j_query, "where"), "gswc_status", json_pack("{ssss}", "operator", "raw", "value", " IN (1,3)")); } res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_return = json_pack("{sis[]}", "result", G_OK, "credential"); if (j_return != NULL) { json_array_foreach(j_result, index, j_element) { switch (json_integer_value(json_object_get(j_element, "gswc_status"))) { case 1: json_object_set_new(j_element, "status", json_string("registered")); break; case 3: json_object_set_new(j_element, "status", json_string("disabled")); break; default: break; } json_object_del(j_element, "gswc_status"); json_array_append(json_object_get(j_return, "credential"), j_element); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_credential_list - Error json_pack"); j_return = json_pack("{si}", "result", G_ERROR); } } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_credential_list - Error executing j_query"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } static json_t * generate_new_credential(struct config_module * config, json_t * j_params, const char * username) { json_t * j_query, * j_return; char * username_escaped, * mod_name_escaped, * username_clause, * challenge_hash; int res; size_t challenge_b64_len, challenge_len = (size_t)json_integer_value(json_object_get(j_params, "challenge-length")); unsigned char challenge_b64[challenge_len*2], challenge[challenge_len+1]; char session[SESSION_LENGTH+1] = {0}, * session_hash; gnutls_rnd(GNUTLS_RND_NONCE, challenge, challenge_len); if (o_base64_encode(challenge, challenge_len, challenge_b64, &challenge_b64_len)) { challenge_b64[challenge_b64_len] = '\0'; if ((challenge_hash = generate_hash(config->hash_algorithm, (const char *)challenge_b64)) != NULL) { rand_string(session, SESSION_LENGTH); if ((session_hash = generate_hash(config->hash_algorithm, session)) != NULL) { username_escaped = h_escape_string_with_quotes(config->conn, username); mod_name_escaped = h_escape_string_with_quotes(config->conn, json_string_value(json_object_get(j_params, "mod_name"))); username_clause = msprintf(" (SELECT gswu_id FROM "G_TABLE_WEBAUTHN_USER" WHERE UPPER(gswu_username) = UPPER(%s) AND gswu_mod_name = %s)", username_escaped, mod_name_escaped); // Disable all credential with status 0 (new) of the same user j_query = json_pack("{sss{si}s{s{ssss+}si}}", "table", G_TABLE_WEBAUTHN_CREDENTIAL, "set", "gswc_status", 2, "where", "gswu_id", "operator", "raw", "value", " =", username_clause, "gswc_status", 0); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { // Insert new credential j_query = json_pack("{sss{s{ss}sssssi}}", "table", G_TABLE_WEBAUTHN_CREDENTIAL, "values", "gswu_id", "raw", username_clause, "gswc_session_hash", session_hash, "gswc_challenge_hash", challenge_hash, "gswc_status", 0); res = h_insert(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_return = json_pack("{sis{ssss}}", "result", G_OK, "credential", "session", session, "challenge", challenge_b64); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_new_credential - Error executing j_query insert"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_new_credential - Error executing j_query update"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } o_free(username_clause); o_free(username_escaped); o_free(mod_name_escaped); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_new_credential - Error generate_hash session"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(session_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_new_credential - Error generate_hash challenge"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(challenge_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_new_credential - Error o_base64_encode challenge"); j_return = json_pack("{si}", "result", G_ERROR); } return j_return; } static json_t * generate_new_assertion(struct config_module * config, json_t * j_params, const char * username, int mock) { json_t * j_query, * j_return; char * username_escaped, * username_clause, * mod_name_escaped, * challenge_hash; int res; size_t challenge_b64_len, challenge_len = (size_t)json_integer_value(json_object_get(j_params, "challenge-length")); unsigned char challenge_b64[challenge_len*2], challenge[challenge_len+1]; char session[SESSION_LENGTH+1] = {0}, * session_hash; gnutls_rnd(GNUTLS_RND_NONCE, challenge, challenge_len); if (o_base64_encode(challenge, challenge_len, challenge_b64, &challenge_b64_len)) { challenge_b64[challenge_b64_len] = '\0'; if ((challenge_hash = generate_hash(config->hash_algorithm, (const char *)challenge_b64)) != NULL) { rand_string(session, SESSION_LENGTH); if ((session_hash = generate_hash(config->hash_algorithm, session)) != NULL) { if (mock < 2) { username_escaped = h_escape_string_with_quotes(config->conn, username); mod_name_escaped = h_escape_string_with_quotes(config->conn, json_string_value(json_object_get(j_params, "mod_name"))); username_clause = msprintf(" (SELECT gswu_id FROM "G_TABLE_WEBAUTHN_USER" WHERE UPPER(gswu_username) = UPPER(%s) AND gswu_mod_name = %s)", username_escaped, mod_name_escaped); // Disable all assertions with status 0 (new) of the same user j_query = json_pack("{sss{si}s{s{ssss+}si}}", "table", G_TABLE_WEBAUTHN_ASSERTION, "set", "gswa_status", 3, "where", "gswu_id", "operator", "raw", "value", " =", username_clause, "gswa_status", 0); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { // Insert new assertion j_query = json_pack("{sss{s{ss}sssssisi}}", "table", G_TABLE_WEBAUTHN_ASSERTION, "values", "gswu_id", "raw", username_clause, "gswa_session_hash", session_hash, "gswa_challenge_hash", challenge_hash, "gswa_status", 0, "gswa_mock", mock); res = h_insert(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_return = json_pack("{sis{ssss}}", "result", G_OK, "assertion", "session", session, "challenge", challenge_b64); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_new_assertion - Error executing j_query insert"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_new_assertion - Error executing j_query update"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } o_free(username_clause); o_free(mod_name_escaped); o_free(username_escaped); } else { j_return = json_pack("{sis{ssss}}", "result", G_OK, "assertion", "session", session, "challenge", challenge_b64); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_new_assertion - Error generate_hash session"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(session_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_new_assertion - Error generate_hash challenge"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(challenge_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_new_assertion - Error o_base64_encode challenge"); j_return = json_pack("{si}", "result", G_ERROR); } return j_return; } static json_t * get_credential_from_session(struct config_module * config, json_t * j_params, const char * username, const char * session) { json_t * j_query, * j_result, * j_return; char * username_escaped, * mod_name_escaped, * username_clause, * expiration_clause; char * session_hash; int res; time_t now; if (o_strlen(session)) { session_hash = generate_hash(config->hash_algorithm, session); if (session_hash != NULL) { time(&now); username_escaped = h_escape_string_with_quotes(config->conn, username); mod_name_escaped = h_escape_string_with_quotes(config->conn, json_string_value(json_object_get(j_params, "mod_name"))); username_clause = msprintf(" = (SELECT gswu_id FROM "G_TABLE_WEBAUTHN_USER" WHERE UPPER(gswu_username) = UPPER(%s) AND gswu_mod_name = %s)", username_escaped, mod_name_escaped); if (config->conn->type==HOEL_DB_TYPE_MARIADB) { expiration_clause = msprintf("> FROM_UNIXTIME(%u)", (now - (unsigned int)json_integer_value(json_object_get(j_params, "credential-expiration")))); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { expiration_clause = msprintf("> TO_TIMESTAMP(%u)", (now - (unsigned int)json_integer_value(json_object_get(j_params, "credential-expiration")))); } else { // HOEL_DB_TYPE_SQLITE expiration_clause = msprintf("> %u", (now - (unsigned int)json_integer_value(json_object_get(j_params, "credential-expiration")))); } j_query = json_pack("{sss[ssssss]s{sss{ssss}sis{ssss}}}", "table", G_TABLE_WEBAUTHN_CREDENTIAL, "columns", "gswc_id", "gswu_id", "gswc_session_hash AS session_hash", "gswc_challenge_hash AS challenge_hash", "gswc_credential_id AS credential_id", "gswc_public_key AS public_key", "where", "gswc_session_hash", session_hash, "gswu_id", "operator", "raw", "value", username_clause, "gswc_status", 0, "gswc_created_at", "operator", "raw", "value", expiration_clause); o_free(username_clause); o_free(username_escaped); o_free(mod_name_escaped); o_free(expiration_clause); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_return = json_pack("{sisO}", "result", G_OK, "credential", json_array_get(j_result, 0)); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_credential_from_session - Error executing j_query"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_credential_from_session - Error generate_hash"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(session_hash); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } static json_t * get_credential(struct config_module * config, json_t * j_params, const char * username, const char * credential_id) { json_t * j_query, * j_result, * j_return; char * username_escaped, * mod_name_escaped, * username_clause; int res; username_escaped = h_escape_string_with_quotes(config->conn, username); mod_name_escaped = h_escape_string_with_quotes(config->conn, json_string_value(json_object_get(j_params, "mod_name"))); username_clause = msprintf(" = (SELECT gswu_id FROM "G_TABLE_WEBAUTHN_USER" WHERE UPPER(gswu_username) = UPPER(%s) AND gswu_mod_name = %s)", username_escaped, mod_name_escaped); j_query = json_pack("{sss[sss]s{sss{ssss}s{ssss}}}", "table", G_TABLE_WEBAUTHN_CREDENTIAL, "columns", "gswc_id", "gswc_public_key AS public_key", "gswc_counter AS counter", "where", "gswc_credential_id", credential_id, "gswu_id", "operator", "raw", "value", username_clause, "gswc_status", "operator", "raw", "value", " IN (1,3)"); o_free(username_clause); o_free(username_escaped); o_free(mod_name_escaped); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_return = json_pack("{sisO}", "result", G_OK, "credential", json_array_get(j_result, 0)); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_credential - Error executing j_query"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } static int update_credential(struct config_module * config, json_t * j_params, const char * username, const char * credential_id, int status) { json_t * j_query; char * username_escaped, * mod_name_escaped, * username_clause; int res, ret; username_escaped = h_escape_string_with_quotes(config->conn, username); mod_name_escaped = h_escape_string_with_quotes(config->conn, json_string_value(json_object_get(j_params, "mod_name"))); username_clause = msprintf(" = (SELECT gswu_id FROM "G_TABLE_WEBAUTHN_USER" WHERE UPPER(gswu_username) = UPPER(%s) AND gswu_mod_name = %s)", username_escaped, mod_name_escaped); j_query = json_pack("{sss{si}s{sss{ssss}}}", "table", G_TABLE_WEBAUTHN_CREDENTIAL, "set", "gswc_status", status, "where", "gswc_credential_id", credential_id, "gswu_id", "operator", "raw", "value", username_clause); o_free(username_clause); o_free(username_escaped); o_free(mod_name_escaped); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_credential - Error executing j_query"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static int update_credential_name(struct config_module * config, json_t * j_params, const char * username, const char * credential_id, const char * name) { json_t * j_query; char * username_escaped, * mod_name_escaped, * username_clause; int res, ret; username_escaped = h_escape_string_with_quotes(config->conn, username); mod_name_escaped = h_escape_string_with_quotes(config->conn, json_string_value(json_object_get(j_params, "mod_name"))); username_clause = msprintf(" = (SELECT gswu_id FROM "G_TABLE_WEBAUTHN_USER" WHERE UPPER(gswu_username) = UPPER(%s) AND gswu_mod_name = %s)", username_escaped, mod_name_escaped); j_query = json_pack("{sss{ss}s{sss{ssss}}}", "table", G_TABLE_WEBAUTHN_CREDENTIAL, "set", "gswc_name", name, "where", "gswc_credential_id", credential_id, "gswu_id", "operator", "raw", "value", username_clause); o_free(username_clause); o_free(username_escaped); o_free(mod_name_escaped); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_credential - Error executing j_query"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static json_t * get_assertion_from_session(struct config_module * config, json_t * j_params, const char * username, const char * session, int mock) { json_t * j_query, * j_result, * j_return; char * username_escaped, * mod_name_escaped, * username_clause, * expiration_clause; char * session_hash; int res; time_t now; if (o_strlen(session)) { session_hash = generate_hash(config->hash_algorithm, session); if (session_hash != NULL) { time(&now); username_escaped = h_escape_string_with_quotes(config->conn, username); mod_name_escaped = h_escape_string_with_quotes(config->conn, json_string_value(json_object_get(j_params, "mod_name"))); username_clause = msprintf(" = (SELECT gswu_id FROM "G_TABLE_WEBAUTHN_USER" WHERE UPPER(gswu_username) = UPPER(%s) AND gswu_mod_name = %s)", username_escaped, mod_name_escaped); if (config->conn->type==HOEL_DB_TYPE_MARIADB) { expiration_clause = msprintf("> FROM_UNIXTIME(%u)", (now - (unsigned int)json_integer_value(json_object_get(j_params, "credential-assertion")))); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { expiration_clause = msprintf("> TO_TIMESTAMP(%u)", (now - (unsigned int)json_integer_value(json_object_get(j_params, "credential-assertion")))); } else { // HOEL_DB_TYPE_SQLITE expiration_clause = msprintf("> %u", (now - (unsigned int)json_integer_value(json_object_get(j_params, "credential-assertion")))); } j_query = json_pack("{sss[ssss]s{sss{ssss}sis{ssss}si}}", "table", G_TABLE_WEBAUTHN_ASSERTION, "columns", "gswa_id", "gswu_id", "gswa_session_hash AS session_hash", "gswa_challenge_hash AS challenge_hash", "where", "gswa_session_hash", session_hash, "gswu_id", "operator", "raw", "value", username_clause, "gswa_status", 0, "gswa_issued_at", "operator", "raw", "value", expiration_clause, "gswa_mock", mock); o_free(username_clause); o_free(username_escaped); o_free(mod_name_escaped); o_free(expiration_clause); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_return = json_pack("{sisO}", "result", G_OK, "assertion", json_array_get(j_result, 0)); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_assertion_from_session - Error executing j_query"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_assertion_from_session - Error generate_hash"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(session_hash); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } static int check_certificate(struct config_module * config, json_t * j_params, const char * credential_id, json_int_t gswu_id) { json_t * j_query, * j_result; int res, ret; char * credential_id_escaped, * mod_name_escaped, * where_clause; credential_id_escaped = h_escape_string_with_quotes(config->conn, credential_id); mod_name_escaped = h_escape_string_with_quotes(config->conn, json_string_value(json_object_get(j_params, "mod_name"))); where_clause = msprintf(" IN (SELECT gswu_id FROM " G_TABLE_WEBAUTHN_CREDENTIAL " WHERE gswc_credential_id=%s AND gswc_status=1 AND gswu_id IN (SELECT gswu_id FROM " G_TABLE_WEBAUTHN_USER " WHERE gswu_mod_name=%s))", credential_id_escaped, mod_name_escaped); j_query = json_pack("{sss[s]s{s{ssss}si}}", "table", G_TABLE_WEBAUTHN_CREDENTIAL, "columns", "gswu_id", "where", "gswu_id", "operator", "raw", "value", where_clause, "gswc_status", 1); o_free(where_clause); o_free(mod_name_escaped); o_free(credential_id_escaped); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { if (json_integer_value(json_object_get(json_array_get(j_result, 0), "gswu_id")) == gswu_id) { ret = G_OK; } else { ret = G_ERROR_UNAUTHORIZED; } } else { ret = G_ERROR_NOT_FOUND; } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_credential_id - Error executing j_query"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } static int validate_apple_certificate_chain(json_t * j_params, gnutls_x509_crt_t cert_leaf, cbor_item_t * x5c_array) { int ret = G_OK; size_t i, x5c_array_size = cbor_array_size(x5c_array); gnutls_x509_crt_t cert_x509[x5c_array_size], root_x509 = NULL; gnutls_x509_trust_list_t tlist = NULL; gnutls_datum_t cert_dat; cbor_item_t * cbor_cert = NULL; unsigned int result; cert_x509[0] = cert_leaf; for (i=1; i= 0) { if (gnutls_x509_trust_list_verify_crt(tlist, cert_x509, x5c_array_size, 0, &result, NULL) >= 0) { if (result) { y_log_message(Y_LOG_LEVEL_DEBUG, "validate_apple_certificate_chain - certificate chain invalid"); ret = G_ERROR_UNAUTHORIZED; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_apple_certificate_chain - Error gnutls_x509_trust_list_verify_crt"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_certificate_from_root - Error gnutls_x509_trust_list_add_cas"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_certificate_from_root - Error gnutls_x509_trust_list_init"); ret = G_ERROR; } } gnutls_x509_crt_deinit(root_x509); for (i=1; i= 0) { issuer = o_strndup((const char *)issuer_dat.data, issuer_dat.size); json_array_foreach(json_object_get(j_params, "root-ca-array"), index, j_cert) { if (0 == o_strcmp(issuer, json_string_value(json_object_get(j_cert, "dn")))) { cert_dat.data = (unsigned char *)json_string_value(json_object_get(j_cert, "x509")); cert_dat.size = json_string_length(json_object_get(j_cert, "x509")); if (!gnutls_x509_crt_init(&root_x509) && !gnutls_x509_crt_import(root_x509, &cert_dat, GNUTLS_X509_FMT_PEM)) { cert_x509[0] = cert_leaf; for (i=1; i= 0) { if (gnutls_x509_trust_list_verify_crt(tlist, cert_x509, x5c_array_size+1, 0, &result, NULL) >= 0) { if (result) { y_log_message(Y_LOG_LEVEL_DEBUG, "validate_certificate_from_root - certificate chain invalid"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_certificate_from_root - Error gnutls_x509_trust_list_verify_crt"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_certificate_from_root - Error gnutls_x509_trust_list_add_cas"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_certificate_from_root - Error gnutls_x509_trust_list_init"); ret = G_ERROR; } } gnutls_x509_crt_deinit(root_x509); for (i=1; i= 0) { if (gnutls_x509_trust_list_verify_crt(tlist, cert_x509, (json_array_size(j_header_x5c)+1), 0, &result, NULL) >= 0) { if (!result) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "validate_safetynet_ca_root - certificate chain invalid"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_safetynet_ca_root - Error gnutls_x509_trust_list_verify_crt"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_safetynet_ca_root - Error gnutls_x509_trust_list_add_cas"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_safetynet_ca_root - Error gnutls_x509_trust_list_init"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_safetynet_ca_root - Error import root cert"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "validate_safetynet_ca_root - Error import last cert"); ret = G_ERROR; } } // Clean after me for (i=1; i 2) { o_valid = 1; } else if (0 == o_strncasecmp(G_PACKED_CERT_CN_KEY, dn_exploded[i], o_strlen(G_PACKED_CERT_CN_KEY)) && o_strlen(dn_exploded[i]) > 3) { cn_valid = 1; } else if (0 == o_strncasecmp(G_PACKED_CERT_OU_KEY, dn_exploded[i], o_strlen(G_PACKED_CERT_OU_KEY)) && 0 == o_strcmp(G_PACKED_CERT_OU_VALUE, dn_exploded[i]+o_strlen(G_PACKED_CERT_OU_KEY))) { ou_valid = 1; } } ber_memvfree((void **)dn_exploded); if (!c_valid || !o_valid || !cn_valid || !ou_valid) { ret = G_ERROR_PARAM; y_log_message(Y_LOG_LEVEL_DEBUG, "validate_packed_leaf_certificate - Invalid dn - C:%s - O:%s - OU:%s - CN:%s", c_valid?"valid":"invalid", o_valid?"valid":"invalid", ou_valid?"valid":"invalid", cn_valid?"valid":"invalid"); break; } if (gnutls_x509_crt_get_basic_constraints(cert, &critial, &ca, NULL) < 0) { ret = G_ERROR; y_log_message(Y_LOG_LEVEL_DEBUG, "validate_packed_leaf_certificate - Error gnutls_x509_crt_get_basic_constraints"); break; } if (ca) { ret = G_ERROR_PARAM; y_log_message(Y_LOG_LEVEL_DEBUG, "validate_packed_leaf_certificate - Error basic constraints for CA is set to true"); break; } if (gnutls_x509_crt_get_extension_by_oid(cert, G_PACKED_OID_AAGUID, 0, aaguid_oid, &aaguid_oid_len, NULL) >= 0) { if (aaguid_oid_len != AAGUID_LEN+2) { ret = G_ERROR_PARAM; y_log_message(Y_LOG_LEVEL_DEBUG, "validate_packed_leaf_certificate - Invalid aaguid_oid_len size %zu", aaguid_oid_len); break; } if (memcmp(aaguid_oid+2, aaguid, AAGUID_LEN)) { ret = G_ERROR_PARAM; y_log_message(Y_LOG_LEVEL_DEBUG, "validate_packed_leaf_certificate - Invalid aaguid_oid match"); break; } } } while (0); return ret; } /** * * Validate the attStmt object under the apple format * https://www.w3.org/TR/webauthn-2/#sctn-apple-anonymous-attestation * (Step) And girl, yes, it's true * You got to know that you drive me wild * */ static json_t * check_attestation_apple(json_t * j_params, cbor_item_t * auth_data, cbor_item_t * att_stmt, const unsigned char * client_data, gnutls_pubkey_t g_key) { json_t * j_error = json_array(), * j_return; cbor_item_t * key, * x5c_array = NULL, * cert_leaf = NULL; size_t client_data_hash_len = 32, nonce_base_len = 0, expected_nonce_len = 32, cert_nonce_len = 64, cert_export_len = 128, cert_export_b64_len = 0; char * message; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_datum_t cert_dat; int ret; unsigned char client_data_hash[32], * nonce_base = NULL, expected_nonce[32], cert_nonce[64], cert_export[128], cert_export_b64[256]; const unsigned char apple_aaguid[] = {0xf2, 0x4a, 0x8e, 0x70, 0xd0, 0xd3, 0xf8, 0x2c, 0x29, 0x37, 0x32, 0x52, 0x3c, 0xc4, 0xde, 0x5a}; unsigned int bits = 0; gnutls_datum_t x, y, g_x, g_y; gnutls_ecc_curve_t curve, g_curve; if (j_error != NULL) { do { if (cbor_map_size(att_stmt) != 1) { json_array_append_new(j_error, json_string("Internal error")); y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_apple - Error attStmt has not only one element"); break; } key = cbor_map_handle(att_stmt)[0].key; if (cbor_isa_string(key)) { if (0 == o_strncmp((const char *)cbor_string_handle(key), "x5c", MIN(o_strlen("x5c"), cbor_string_length(key))) && cbor_isa_array(cbor_map_handle(att_stmt)[0].value) && cbor_array_size(cbor_map_handle(att_stmt)[0].value)) { x5c_array = cbor_map_handle(att_stmt)[0].value; } } else { message = msprintf("attStmt map element 0 key is not a string"); json_array_append_new(j_error, json_string(message)); o_free(message); break; } if (!generate_digest_raw(digest_SHA256, client_data, o_strlen((char *)client_data), client_data_hash, &client_data_hash_len)) { json_array_append_new(j_error, json_string("Internal error")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_apple - Error generate_digest_raw client_data"); break; } nonce_base_len = cbor_bytestring_length(auth_data) + client_data_hash_len; if ((nonce_base = o_malloc(nonce_base_len)) == NULL) { json_array_append_new(j_error, json_string("Internal error")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_apple - Error o_malloc nonce_base"); break; } memcpy(nonce_base, cbor_bytestring_handle(auth_data), cbor_bytestring_length(auth_data)); memcpy(nonce_base+cbor_bytestring_length(auth_data), client_data_hash, client_data_hash_len); if (!generate_digest_raw(digest_SHA256, nonce_base, nonce_base_len, expected_nonce, &expected_nonce_len)) { json_array_append_new(j_error, json_string("Internal error")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_apple - Error generate_digest_raw expected_nonce"); break; } if (x5c_array == NULL) { json_array_append_new(j_error, json_string("Invalid x5c")); break; } if (gnutls_x509_crt_init(&cert)) { json_array_append_new(j_error, json_string("check_attestation_apple - Error gnutls_x509_crt_init")); break; } cert_leaf = cbor_array_get(x5c_array, 0); cert_dat.data = cbor_bytestring_handle(cert_leaf); cert_dat.size = cbor_bytestring_length(cert_leaf); if ((ret = gnutls_x509_crt_import(cert, &cert_dat, GNUTLS_X509_FMT_DER)) < 0) { json_array_append_new(j_error, json_string("Error importing x509 certificate")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_apple - Error gnutls_x509_crt_import: %d", ret); break; } if (gnutls_pubkey_init(&pubkey)) { json_array_append_new(j_error, json_string("check_attestation_apple - Error gnutls_pubkey_init")); break; } if ((ret = gnutls_pubkey_import_x509(pubkey, cert, 0)) < 0) { json_array_append_new(j_error, json_string("Error importing x509 certificate")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_apple - Error gnutls_pubkey_import_x509: %d", ret); break; } if (gnutls_pubkey_get_pk_algorithm(pubkey, &bits) != GNUTLS_PK_ECDSA) { json_array_append_new(j_error, json_string("Error parsing x509 certificate")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_apple - Error invalid public key type, not ECDSA"); break; } if (gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &x, &y, GNUTLS_EXPORT_FLAG_NO_LZ) < 0) { json_array_append_new(j_error, json_string("Error parsing x509 certificate")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_apple - Error gnutls_pubkey_export_ecc_raw2 pubkey"); break; } if (gnutls_pubkey_export_ecc_raw2(g_key, &g_curve, &g_x, &g_y, GNUTLS_EXPORT_FLAG_NO_LZ) < 0) { json_array_append_new(j_error, json_string("Error parsing x509 certificate")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_apple - Error gnutls_pubkey_export_ecc_raw2 "); break; } if (x.size != g_x.size || memcmp(x.data, g_x.data, x.size)) { json_array_append_new(j_error, json_string("Error invalid x509 certificate")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_apple - Error match cert x value "); break; } if (y.size != g_y.size || memcmp(y.data, g_y.data, y.size)) { json_array_append_new(j_error, json_string("Error invalid x509 certificate")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_apple - Error match cert y value "); break; } if (gnutls_x509_crt_get_extension_by_oid(cert, G_APPLE_ANONYMOUS_ATTESTATION_OID, 0, cert_nonce, &cert_nonce_len, NULL) >= 0) { if (cert_nonce_len != expected_nonce_len+6) { json_array_append_new(j_error, json_string("Error invalid x509 certificate")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_apple - Invalid cert_nonce_len %zu", cert_nonce_len); break; } if (memcmp(expected_nonce, cert_nonce+6, expected_nonce_len)) { json_array_append_new(j_error, json_string("Error invalid x509 certificate")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_apple - Invalid cert_nonce match"); break; } } else { json_array_append_new(j_error, json_string("Error invalid x509 certificate")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_apple - Error getting AppleAnonymousAttestation OID 1.2.840.113635.100.8.2 extension"); break; } if (memcmp((cbor_bytestring_handle(auth_data)+ATTESTED_CRED_DATA_OFFSET), apple_aaguid, AAGUID_LEN)) { json_array_append_new(j_error, json_string("Error invalid certificate")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_apple - Invalid aaguid match"); break; } if (json_object_get(j_params, "apple-root-ca-content") != json_null()) { if (validate_apple_certificate_chain(j_params, cert, x5c_array) != G_OK) { json_array_append_new(j_error, json_string("Invalid certificate chain")); break; } } if ((ret = gnutls_x509_crt_get_key_id(cert, GNUTLS_KEYID_USE_SHA256, cert_export, &cert_export_len)) < 0) { json_array_append_new(j_error, json_string("Error exporting x509 certificate")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_packed - Error gnutls_x509_crt_get_key_id: %d", ret); break; } if (!o_base64_encode(cert_export, cert_export_len, cert_export_b64, &cert_export_b64_len)) { json_array_append_new(j_error, json_string("Internal error")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_packed - Error o_base64_encode cert_export"); break; } } while (0); if (json_array_size(j_error)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_error); } else { j_return = json_pack("{sis{ss%}}", "result", G_OK, "data", "certificate", cert_export_b64, cert_export_b64_len); } json_decref(j_error); gnutls_x509_crt_deinit(cert); gnutls_pubkey_deinit(pubkey); gnutls_free(x.data); gnutls_free(y.data); gnutls_free(g_x.data); gnutls_free(g_y.data); o_free(nonce_base); if (cert_leaf != NULL) { cbor_decref(&cert_leaf); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_packed - Error allocating resources for j_error"); j_return = json_pack("{si}", "result", G_ERROR); } return j_return; } /** * * Validate the attStmt object under the packed format * https://w3c.github.io/webauthn/#sctn-packed-attestation * (Step) Hey girl, when you smile * You got to know that you drive me wild * */ static json_t * check_attestation_packed(json_t * j_params, cbor_item_t * auth_data, cbor_item_t * att_stmt, const unsigned char * client_data, gnutls_pubkey_t g_key) { json_t * j_error = json_array(), * j_return; cbor_item_t * key, * alg = NULL, * sig = NULL, * x5c_array = NULL, * cert_leaf = NULL; size_t i, client_data_hash_len = 32, cert_export_len = 128, cert_export_b64_len = 0; char * message; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_datum_t cert_dat, data, signature, cert_issued_by; int ret, sig_alg = GNUTLS_SIGN_UNKNOWN; unsigned char client_data_hash[32], cert_export[128], cert_export_b64[256]; data.data = NULL; UNUSED(j_params); if (j_error != NULL) { do { for (i=0; i= 0) { message = msprintf("Unrecognized certificate autohority: %.*s", cert_issued_by.size, cert_issued_by.data); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_packed - %s", message); o_free(message); gnutls_free(cert_issued_by.data); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_packed - Unrecognized certificate autohority (unable to get issuer dn)"); } break; } if (!o_base64_encode(cert_export, cert_export_len, cert_export_b64, &cert_export_b64_len)) { json_array_append_new(j_error, json_string("Internal error")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_packed - Error o_base64_encode cert_export"); break; } } } while (0); if (json_array_size(j_error)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_error); } else { j_return = json_pack("{sis{ss%}}", "result", G_OK, "data", "certificate", cert_export_b64, cert_export_b64_len); } json_decref(j_error); gnutls_x509_crt_deinit(cert); gnutls_pubkey_deinit(pubkey); o_free(data.data); if (cert_leaf != NULL) { cbor_decref(&cert_leaf); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_packed - Error allocating resources for j_error"); j_return = json_pack("{si}", "result", G_ERROR); } return j_return; } /** * * Validate the attStmt object under the Android SafetyNet format * https://w3c.github.io/webauthn/#sctn-android-safetynet-attestation * (step) hey girl, in your eyes * I see a picture of me all the time * */ static json_t * check_attestation_android_safetynet(json_t * j_params, cbor_item_t * auth_data, cbor_item_t * att_stmt, const unsigned char * client_data) { json_t * j_error = json_array(), * j_return; unsigned char pubkey_export[1024] = {0}, cert_export[32] = {0}, cert_export_b64[64], client_data_hash[32], * nonce_base = NULL, nonce_base_hash[32], * nonce_base_hash_b64 = NULL, * header_cert_decoded = NULL; char * message = NULL, * response_token = NULL, issued_to[128] = {0}, * jwt_header = NULL; size_t pubkey_export_len = 1024, cert_export_len = 32, cert_export_b64_len, issued_to_len = 128, client_data_hash_len = 32, nonce_base_hash_len = 32, nonce_base_hash_b64_len = 0, header_cert_decoded_len = 0; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; cbor_item_t * key, * response = NULL; int i, ret; jwt_t * j_response = NULL; json_t * j_header_x5c = NULL, * j_cert = NULL, * j_header = NULL, * j_value = NULL; gnutls_datum_t cert_dat; int has_ver = 0; if (j_error != NULL) { do { // Step 1 if (!cbor_isa_map(att_stmt) || cbor_map_size(att_stmt) != 2) { json_array_append_new(j_error, json_string("CBOR map value 'attStmt' invalid format")); break; } for (i=0; i<2; i++) { key = cbor_map_handle(att_stmt)[i].key; if (cbor_isa_string(key)) { if (0 == o_strncmp((const char *)cbor_string_handle(key), "ver", MIN(o_strlen("ver"), cbor_string_length(key))) && cbor_isa_string(cbor_map_handle(att_stmt)[i].value)) { has_ver = 1; } else if (0 == o_strncmp((const char *)cbor_string_handle(key), "response", MIN(o_strlen("response"), cbor_string_length(key))) && cbor_isa_bytestring(cbor_map_handle(att_stmt)[i].value)) { response = cbor_map_handle(att_stmt)[i].value; } else { message = msprintf("attStmt map element %d key is not valid: '%.*s'", i, cbor_string_length(key), cbor_string_handle(key)); json_array_append_new(j_error, json_string(message)); o_free(message); break; } } else { message = msprintf("attStmt map element %d key is not a string", i); json_array_append_new(j_error, json_string(message)); o_free(message); break; } } if (!has_ver) { json_array_append_new(j_error, json_string("version invalid")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error ver missing"); break; } if (!generate_digest_raw(digest_SHA256, client_data, o_strlen((char *)client_data), client_data_hash, &client_data_hash_len)) { json_array_append_new(j_error, json_string("Internal error")); y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_android_safetynet - Error generate_digest_raw client_data"); break; } if ((nonce_base = o_malloc(32 + cbor_bytestring_length(auth_data))) == NULL) { json_array_append_new(j_error, json_string("Internal error")); y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_android_safetynet - Error allocating resources for nonce_base"); break; } memcpy(nonce_base, cbor_bytestring_handle(auth_data), cbor_bytestring_length(auth_data)); memcpy(nonce_base+cbor_bytestring_length(auth_data), client_data_hash, client_data_hash_len); if (!generate_digest_raw(digest_SHA256, nonce_base, 32 + cbor_bytestring_length(auth_data), nonce_base_hash, &nonce_base_hash_len)) { json_array_append_new(j_error, json_string("Internal error")); y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_android_safetynet - Error generate_digest_raw nonce_base"); break; } if ((nonce_base_hash_b64 = o_malloc(64)) == NULL) { json_array_append_new(j_error, json_string("Internal error")); y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_android_safetynet - Error allocating resources for nonce_base_hash_b64"); break; } if (!o_base64_encode(nonce_base_hash, 32, nonce_base_hash_b64, &nonce_base_hash_b64_len)) { json_array_append_new(j_error, json_string("Internal error")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error o_base64_encode for nonce_base_hash_b64"); break; } if (response == NULL) { json_array_append_new(j_error, json_string("response invalid")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error response missing"); break; } if ((response_token = o_strndup((const char *)cbor_bytestring_handle(response), cbor_bytestring_length(response))) == NULL) { json_array_append_new(j_error, json_string("Internal error")); y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_android_safetynet - Error o_strndup for response_token"); break; } if (r_jwt_init(&j_response) != RHN_OK) { json_array_append_new(j_error, json_string("Internal error")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error r_jwt_init"); break; } if (r_jwt_advanced_parse(j_response, response_token, R_PARSE_HEADER_X5C, 0) != RHN_OK) { json_array_append_new(j_error, json_string("response invalid")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error r_jwt_parse"); break; } if (o_strcmp(r_jwt_get_claim_str_value(j_response, "nonce"), (const char *)nonce_base_hash_b64)) { json_array_append_new(j_error, json_string("response invalid")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error nonce invalid"); break; } if (json_integer_value(json_object_get(j_params, "ctsProfileMatch")) != -1 && json_integer_value(json_object_get(j_params, "ctsProfileMatch")) != ((j_value = r_jwt_get_claim_json_t_value(j_response, "ctsProfileMatch"))==json_true()?1:0)) { json_array_append_new(j_error, json_string("response invalid")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error ctsProfileMatch invalid"); json_decref(j_value); j_value = NULL; break; } json_decref(j_value); j_value = NULL; if (json_integer_value(json_object_get(j_params, "basicIntegrity")) != -1 && json_integer_value(json_object_get(j_params, "basicIntegrity")) != ((j_value = r_jwt_get_claim_json_t_value(j_response, "basicIntegrity"))==json_true()?1:0)) { json_array_append_new(j_error, json_string("response invalid")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error basicIntegrity invalid"); j_value = NULL; break; } json_decref(j_value); j_value = NULL; if ((j_header_x5c = r_jwt_get_header_json_t_value(j_response, "x5c")) == NULL) { json_array_append_new(j_error, json_string("response invalid")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error parsing x5c JSON"); break; } if (!json_is_string((j_cert = json_array_get(j_header_x5c, 0)))) { json_array_append_new(j_error, json_string("response invalid")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error x5c leaf not a string"); break; } if ((header_cert_decoded = o_malloc(json_string_length(j_cert))) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_android_safetynet - Error allocating resources for header_cert_decoded"); break; } if (!o_base64_decode((const unsigned char *)json_string_value(j_cert), json_string_length(j_cert), header_cert_decoded, &header_cert_decoded_len)) { json_array_append_new(j_error, json_string("response invalid")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error o_base64_decode x5c leaf"); break; } if (gnutls_x509_crt_init(&cert)) { json_array_append_new(j_error, json_string("internal error")); y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_android_safetynet - Error gnutls_x509_crt_init"); break; } if (gnutls_pubkey_init(&pubkey)) { json_array_append_new(j_error, json_string("internal error")); y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_android_safetynet - Error gnutls_pubkey_init"); break; } cert_dat.data = header_cert_decoded; cert_dat.size = header_cert_decoded_len; if ((ret = gnutls_x509_crt_import(cert, &cert_dat, GNUTLS_X509_FMT_DER)) < 0) { json_array_append_new(j_error, json_string("Error importing x509 certificate")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error gnutls_pcert_import_x509_raw: %d", ret); break; } if (r_jwt_verify_signature(j_response, NULL, 0) != RHN_OK) { json_array_append_new(j_error, json_string("Invalid signature")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error r_jwt_verify_signature"); break; } if ((ret = gnutls_pubkey_import_x509(pubkey, cert, 0)) < 0) { json_array_append_new(j_error, json_string("Error importing x509 certificate")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error gnutls_pubkey_import_x509: %d", ret); break; } if ((ret = gnutls_x509_crt_get_key_id(cert, GNUTLS_KEYID_USE_SHA256, cert_export, &cert_export_len)) < 0) { json_array_append_new(j_error, json_string("Error exporting x509 certificate")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error gnutls_x509_crt_get_key_id: %d", ret); break; } if ((ret = gnutls_x509_crt_get_dn(cert, issued_to, &issued_to_len)) < 0) { json_array_append_new(j_error, json_string("Error x509 dn")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error gnutls_x509_crt_get_dn: %d", ret); break; } if (o_strnstr(issued_to, SAFETYNET_ISSUED_TO, issued_to_len) == NULL) { json_array_append_new(j_error, json_string("Error x509 dn")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - safetynet certificate issued for %.*s", issued_to_len, issued_to); break; } if (json_object_get(j_params, "google-root-ca-r2") != json_null()) { if ((ret = validate_safetynet_ca_root(j_params, cert, j_header_x5c)) == G_ERROR_UNAUTHORIZED) { json_array_append_new(j_error, json_string("Error x509 certificate chain validation")); break; } else if (ret != G_OK) { json_array_append_new(j_error, json_string("response invalid")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - safetynet certificate chain certificate validation error"); break; } } if (!o_base64_encode(cert_export, cert_export_len, cert_export_b64, &cert_export_b64_len)) { json_array_append_new(j_error, json_string("response invalid")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error o_base64_encode cert_export"); break; } if ((ret = gnutls_pubkey_export(pubkey, GNUTLS_X509_FMT_PEM, pubkey_export, &pubkey_export_len)) < 0) { json_array_append_new(j_error, json_string("response invalid")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error gnutls_pubkey_export: %d", ret); break; } } while (0); if (json_array_size(j_error)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_error); } else { j_return = json_pack("{sis{ss%}}", "result", G_OK, "data", "certificate", cert_export_b64, cert_export_b64_len); } json_decref(j_error); json_decref(j_header); json_decref(j_header_x5c); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); r_jwt_free(j_response); o_free(nonce_base); o_free(nonce_base_hash_b64); o_free(response_token); o_free(header_cert_decoded); o_free(jwt_header); } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_android_safetynet - Error allocating resources for j_error"); j_return = json_pack("{si}", "result", G_ERROR); } return j_return; } /** * * Validate the attStmt object under the fido-u2f format * https://w3c.github.io/webauthn/#sctn-fido-u2f-attestation * Gonna get to you girl * Really want you in my world * */ static json_t * check_attestation_fido_u2f(json_t * j_params, unsigned char * credential_id, size_t credential_id_len, unsigned char * cert_x, size_t cert_x_len, unsigned char * cert_y, size_t cert_y_len, cbor_item_t * att_stmt, unsigned char * rpid_hash, size_t rpid_hash_len, const unsigned char * client_data) { json_t * j_error = json_array(), * j_return; cbor_item_t * key = NULL, * x5c = NULL, * sig = NULL, * att_cert = NULL; int i, ret; char * message = NULL; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_datum_t cert_dat, data, signature, cert_issued_by; unsigned char * data_signed = NULL, client_data_hash[32], cert_export[32], cert_export_b64[64]; size_t data_signed_offset = 0, client_data_hash_len = 32, cert_export_len = 32, cert_export_b64_len = 0; if (j_error != NULL) { do { if (gnutls_x509_crt_init(&cert)) { json_array_append_new(j_error, json_string("check_attestation_fido_u2f - Error gnutls_x509_crt_init")); break; } if (gnutls_pubkey_init(&pubkey)) { json_array_append_new(j_error, json_string("check_attestation_fido_u2f - Error gnutls_pubkey_init")); break; } // Step 1 if (att_stmt == NULL || !cbor_isa_map(att_stmt) || cbor_map_size(att_stmt) != 2) { json_array_append_new(j_error, json_string("CBOR map value 'attStmt' invalid format")); break; } for (i=0; i<2; i++) { key = cbor_map_handle(att_stmt)[i].key; if (cbor_isa_string(key)) { if (0 == o_strncmp((const char *)cbor_string_handle(key), "x5c", MIN(o_strlen("x5c"), cbor_string_length(key)))) { x5c = cbor_map_handle(att_stmt)[i].value; } else if (0 == o_strncmp((const char *)cbor_string_handle(key), "sig", MIN(o_strlen("sig"), cbor_string_length(key)))) { sig = cbor_map_handle(att_stmt)[i].value; } else { message = msprintf("attStmt map element %d key is not valid: '%.*s'", i, cbor_string_length(key), cbor_string_handle(key)); json_array_append_new(j_error, json_string(message)); o_free(message); break; } } else { message = msprintf("attStmt map element %d key is not a string", i); json_array_append_new(j_error, json_string(message)); o_free(message); break; } } if (x5c == NULL || !cbor_isa_array(x5c) || cbor_array_size(x5c) != 1) { json_array_append_new(j_error, json_string("CBOR map value 'x5c' invalid format")); break; } att_cert = cbor_array_get(x5c, 0); cert_dat.data = cbor_bytestring_handle(att_cert); cert_dat.size = cbor_bytestring_length(att_cert); if ((ret = gnutls_x509_crt_import(cert, &cert_dat, GNUTLS_X509_FMT_DER)) < 0) { json_array_append_new(j_error, json_string("Error importing x509 certificate")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_fido_u2f - Error gnutls_pcert_import_x509_raw: %d", ret); break; } if (json_object_get(j_params, "root-ca-list") != json_null() && validate_certificate_from_root(j_params, cert, x5c) != G_OK) { json_array_append_new(j_error, json_string("Unrecognized certificate authority")); if (gnutls_x509_crt_get_issuer_dn2(cert, &cert_issued_by) >= 0) { message = msprintf("Unrecognized certificate autohority: %.*s", cert_issued_by.size, cert_issued_by.data); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_fido_u2f - %s", message); o_free(message); gnutls_free(cert_issued_by.data); } else { y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_fido_u2f - Unrecognized certificate autohority (unable to get issuer dn)"); } break; } if ((ret = gnutls_pubkey_import_x509(pubkey, cert, 0)) < 0) { json_array_append_new(j_error, json_string("Error importing x509 certificate")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_fido_u2f - Error gnutls_pubkey_import_x509: %d", ret); break; } if ((ret = gnutls_x509_crt_get_key_id(cert, GNUTLS_KEYID_USE_SHA256, cert_export, &cert_export_len)) < 0) { json_array_append_new(j_error, json_string("Error exporting x509 certificate")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_fido_u2f - Error gnutls_x509_crt_get_key_id: %d", ret); break; } if (!o_base64_encode(cert_export, cert_export_len, cert_export_b64, &cert_export_b64_len)) { json_array_append_new(j_error, json_string("Internal error")); y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_fido_u2f - Error o_base64_encode cert_export"); break; } if (!generate_digest_raw(digest_SHA256, client_data, o_strlen((char *)client_data), client_data_hash, &client_data_hash_len)) { json_array_append_new(j_error, json_string("Internal error")); y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_fido_u2f - Error generate_digest_raw client_data"); break; } if (sig == NULL || !cbor_isa_bytestring(sig)) { json_array_append_new(j_error, json_string("Error sig is not a bytestring")); break; } if ((data_signed = o_malloc(rpid_hash_len+client_data_hash_len+credential_id_len+cert_x_len+cert_y_len+2)) == NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_fido_u2f - Error allocating data_signed"); json_array_append_new(j_error, json_string("Internal error")); break; } // Build bytestring to verify signature data_signed[0] = 0x0; data_signed_offset = 1; memcpy(data_signed+data_signed_offset, rpid_hash, rpid_hash_len); data_signed_offset += rpid_hash_len; memcpy(data_signed+data_signed_offset, client_data_hash, client_data_hash_len); data_signed_offset+=client_data_hash_len; memcpy(data_signed+data_signed_offset, credential_id, credential_id_len); data_signed_offset+=credential_id_len; data_signed[data_signed_offset] = 0x04; data_signed_offset++; memcpy(data_signed+data_signed_offset, cert_x, cert_x_len); data_signed_offset+=cert_x_len; memcpy(data_signed+data_signed_offset, cert_y, cert_y_len); data_signed_offset+=cert_y_len; // Let's verify sig over data_signed data.data = data_signed; data.size = data_signed_offset; signature.data = cbor_bytestring_handle(sig); signature.size = cbor_bytestring_length(sig); if (gnutls_pubkey_verify_data2(pubkey, GNUTLS_SIGN_ECDSA_SHA256, 0, &data, &signature)) { json_array_append_new(j_error, json_string("Invalid signature")); } } while (0); o_free(data_signed); if (json_array_size(j_error)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_error); } else { j_return = json_pack("{sis{ss%}}", "result", G_OK, "data", "certificate", cert_export_b64, cert_export_b64_len); } json_decref(j_error); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); if (att_cert != NULL) { cbor_decref(&att_cert); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_fido_u2f - Error allocating resources for j_error"); j_return = json_pack("{si}", "result", G_ERROR); } return j_return; } /** * * It's like the New Kids On The Block * * You have to validate the credential by following * the registration procedure step by step * Because the w3c said so * https://w3c.github.io/webauthn/#registering-a-new-credential * */ static json_t * register_new_attestation(struct config_module * config, json_t * j_params, json_t * j_scheme_data, json_t * j_credential) { json_t * j_return, * j_client_data = NULL, * j_error, * j_result, * j_pubkey = NULL, * j_cert = NULL, * j_query, * j_element = NULL; unsigned char * client_data = NULL, * challenge_b64 = NULL, * att_obj = NULL, * cbor_bs_handle = NULL, rpid_hash[32], * fmt = NULL, * credential_id_b64 = NULL, * cbor_auth_data, * cred_pub_key, cert_x[256], cert_y[256], pubkey_export[1024]; char * challenge_hash = NULL, * message = NULL; const char * rpid = NULL; size_t client_data_len = 0, challenge_b64_len = 0, att_obj_len = 0, rpid_hash_len = 32, fmt_len = 0, credential_id_len = 0, credential_id_b64_len, cbor_auth_data_len, cred_pub_key_len, cert_x_len = 0, cert_y_len = 0, pubkey_export_len = 1024, index = 0, cbor_bs_handle_len, rpid_len; uint32_t counter = 0; int ret = G_OK, res, status, has_x = 0, has_y = 0, key_type_valid = 0, key_alg_valid = 0; unsigned int i; struct cbor_load_result cbor_result; cbor_item_t * item = NULL, * key = NULL, * auth_data = NULL, * att_stmt = NULL, * cbor_cose = NULL, * cbor_key, * cbor_value; gnutls_pubkey_t g_key = NULL; gnutls_datum_t g_x, g_y; gnutls_ecc_curve_t curve = GNUTLS_ECC_CURVE_INVALID; if (j_scheme_data != NULL) { j_error = json_array(); if (j_error != NULL) { do { if (!json_string_length(json_object_get(json_object_get(j_scheme_data, "credential"), "rawId"))) { json_array_append_new(j_error, json_string("rawId mandatory")); ret = G_ERROR_PARAM; break; } if (!json_string_length(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "clientDataJSON"))) { json_array_append_new(j_error, json_string("clientDataJSON mandatory")); ret = G_ERROR_PARAM; break; } if ((client_data = o_malloc(json_string_length(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "clientDataJSON"))+1)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error allocating resources for client_data"); json_array_append_new(j_error, json_string("Internal error")); ret = G_ERROR_MEMORY; break; } if (!o_base64_decode((const unsigned char *)json_string_value(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "clientDataJSON")), json_string_length(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "clientDataJSON")), client_data, &client_data_len)) { y_log_message(Y_LOG_LEVEL_DEBUG, "register_new_attestation - Error o_base64_decode client_data"); json_array_append_new(j_error, json_string("Internal error")); ret = G_ERROR_PARAM; break; } client_data[client_data_len] = '\0'; j_client_data = json_loads((const char *)client_data, JSON_DECODE_ANY, NULL); if (j_client_data == NULL) { json_array_append_new(j_error, json_string("Error parsing JSON client data")); ret = G_ERROR_PARAM; break; } // Step 3 if (0 != o_strcmp(json_string_value(json_object_get(j_client_data, "type")), "webauthn.create")) { json_array_append_new(j_error, json_string("clientDataJSON.type invalid")); ret = G_ERROR_PARAM; break; } // Step 4 if (!json_string_length(json_object_get(j_client_data, "challenge"))) { json_array_append_new(j_error, json_string("clientDataJSON.challenge mandatory")); ret = G_ERROR_PARAM; break; } if ((challenge_b64 = o_malloc(json_string_length(json_object_get(j_client_data, "challenge"))+3)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error allocating resources for challenge_b64"); json_array_append_new(j_error, json_string("Internal error")); ret = G_ERROR_MEMORY; break; } if (!o_base64url_2_base64((unsigned char *)json_string_value(json_object_get(j_client_data, "challenge")), json_string_length(json_object_get(j_client_data, "challenge")), challenge_b64, &challenge_b64_len)) { json_array_append_new(j_error, json_string("clientDataJSON.challenge invalid format")); ret = G_ERROR_PARAM; break; } challenge_b64[challenge_b64_len] = '\0'; if ((challenge_hash = generate_hash(config->hash_algorithm, (const char *)challenge_b64)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error generate_hash for challenge_b64"); json_array_append_new(j_error, json_string("Internal error")); ret = G_ERROR; break; } if (0 != o_strcmp(challenge_hash, json_string_value(json_object_get(j_credential, "challenge_hash")))) { json_array_append_new(j_error, json_string("clientDataJSON.challenge invalid")); ret = G_ERROR_PARAM; break; } // Step 5 if (!json_string_length(json_object_get(j_client_data, "origin"))) { json_array_append_new(j_error, json_string("clientDataJSON.origin mandatory")); ret = G_ERROR_PARAM; break; } if (0 != o_strcmp(json_string_value(json_object_get(j_params, "rp-origin")), json_string_value(json_object_get(j_client_data, "origin")))) { message = msprintf("clientDataJSON.origin invalid - Client send %s, required %s", json_string_value(json_object_get(j_client_data, "origin")), json_string_value(json_object_get(j_params, "rp-origin"))); json_array_append_new(j_error, json_string(message)); o_free(message); ret = G_ERROR_PARAM; break; } // Step 6 ?? if (!json_string_length(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "attestationObject"))) { json_array_append_new(j_error, json_string("attestationObject required")); ret = G_ERROR_PARAM; break; } if ((att_obj = o_malloc(json_string_length(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "attestationObject")))) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error allocating resources for o_malloc"); ret = G_ERROR_MEMORY; break; } if (!o_base64_decode((unsigned char *)json_string_value(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "attestationObject")), json_string_length(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "attestationObject")), att_obj, &att_obj_len)) { json_array_append_new(j_error, json_string("attestationObject invalid base64")); ret = G_ERROR_PARAM; break; } // Step 7 item = cbor_load(att_obj, att_obj_len, &cbor_result); if (cbor_result.error.code != CBOR_ERR_NONE) { json_array_append_new(j_error, json_string("attestationObject invalid cbor")); ret = G_ERROR_PARAM; break; } if (!cbor_isa_map(item)) { json_array_append_new(j_error, json_string("attestationObject invalid cbor item")); ret = G_ERROR_PARAM; break; } // Check attestation object if (cbor_map_size(item) != 3) { json_array_append_new(j_error, json_string("attestationObject invalid cbor item")); ret = G_ERROR_PARAM; break; } for (i=0; i<3; i++) { key = cbor_map_handle(item)[i].key; if (cbor_isa_string(key)) { if (0 == o_strncmp((const char *)cbor_string_handle(key), "fmt", MIN(o_strlen("fmt"), cbor_string_length(key)))) { if (!cbor_isa_string(cbor_map_handle(item)[i].value)) { json_array_append_new(j_error, json_string("CBOR map value 'fmt' isnt't a string")); ret = G_ERROR_PARAM; break; } else { fmt_len = cbor_string_length(cbor_map_handle(item)[i].value); fmt = cbor_string_handle(cbor_map_handle(item)[i].value); } } else if (0 == o_strncmp((const char *)cbor_string_handle(key), "attStmt", MIN(o_strlen("attStmt"), cbor_string_length(key)))) { att_stmt = cbor_map_handle(item)[i].value; } else if (0 == o_strncmp((const char *)cbor_string_handle(key), "authData", MIN(o_strlen("authData"), cbor_string_length(key)))) { auth_data = cbor_map_handle(item)[i].value; if (!cbor_isa_bytestring(auth_data) || cbor_bytestring_length(auth_data) < 56 || cbor_bytestring_is_indefinite(auth_data)) { json_array_append_new(j_error, json_string("CBOR map value 'authData' is invalid")); ret = G_ERROR_PARAM; break; } } else { message = msprintf("CBOR map element %d is not an expected item", i); json_array_append_new(j_error, json_string(message)); o_free(message); ret = G_ERROR_PARAM; break; } } } // Step 9 if (auth_data == NULL) { json_array_append_new(j_error, json_string("authData invalid")); ret = G_ERROR_PARAM; break; } cbor_bs_handle = cbor_bytestring_handle(auth_data); cbor_bs_handle_len = cbor_bytestring_length(auth_data); if (o_strstr(json_string_value(json_object_get(j_params, "rp-origin")), "://") == NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "register_new_attestation - rp-origin invalid"); json_array_append_new(j_error, json_string("Internal error")); ret = G_ERROR_PARAM; break; } if (o_strstr(json_string_value(json_object_get(j_params, "rp-origin")), "://") != NULL) { rpid = o_strstr(json_string_value(json_object_get(j_params, "rp-origin")), "://")+3; } else { rpid = json_string_value(json_object_get(j_params, "rp-origin")); } if (o_strchr(rpid, ':') != NULL) { rpid_len = o_strchr(rpid, ':') - rpid; } else { rpid_len = o_strlen(rpid); } if (!generate_digest_raw(digest_SHA256, (unsigned char *)rpid, rpid_len, rpid_hash, &rpid_hash_len)) { y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error generate_digest_raw"); json_array_append_new(j_error, json_string("Internal error")); ret = G_ERROR_PARAM; break; } if (0 != memcmp(cbor_bs_handle, rpid_hash, rpid_hash_len)) { json_array_append_new(j_error, json_string("authData.rpIdHash invalid")); ret = G_ERROR_PARAM; break; } // Step 10 if (!(cbor_bs_handle[FLAGS_OFFSET] & FLAG_USER_PRESENT)) { json_array_append_new(j_error, json_string("authData.userPresent not set")); ret = G_ERROR_PARAM; break; } if (!(cbor_bs_handle[FLAGS_OFFSET] & FLAG_AT)) { json_array_append_new(j_error, json_string("authData.Attested credential data not set")); ret = G_ERROR_PARAM; break; } // Step 11 ignored for now //y_log_message(Y_LOG_LEVEL_DEBUG, "authData.userVerified: %d", !!(cbor_bs_handle[FLAGS_OFFSET] & FLAG_USER_VERIFY)); // Step 12 ignored for now (no extension) //y_log_message(Y_LOG_LEVEL_DEBUG, "authData.Extension: %d", !!(cbor_bs_handle[FLAGS_OFFSET] & FLAG_ED)); credential_id_len = cbor_bs_handle[CRED_ID_L_OFFSET+1] | (cbor_bs_handle[CRED_ID_L_OFFSET] << 8); if (cbor_bs_handle_len < CRED_ID_L_OFFSET+2+credential_id_len) { json_array_append_new(j_error, json_string("auth_data invalid size")); ret = G_ERROR_PARAM; break; } credential_id_b64 = o_malloc(credential_id_len*2); if (credential_id_b64 == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error o_malloc for credential_id_b64"); json_array_append_new(j_error, json_string("Internal error")); ret = G_ERROR_PARAM; break; } if (!o_base64_encode(cbor_bs_handle+CRED_ID_L_OFFSET+2, credential_id_len, credential_id_b64, &credential_id_b64_len)) { y_log_message(Y_LOG_LEVEL_DEBUG, "register_new_attestation - Error o_base64_encode for credential_id_b64"); json_array_append_new(j_error, json_string("Internal error")); ret = G_ERROR_PARAM; break; } // Compare credential_id_b64 with rawId if (memcmp(credential_id_b64, json_string_value(json_object_get(json_object_get(j_scheme_data, "credential"), "rawId")), MIN(json_string_length(json_object_get(json_object_get(j_scheme_data, "credential"), "rawId")), credential_id_b64_len))) { json_array_append_new(j_error, json_string("Invalid rawId")); ret = G_ERROR_PARAM; break; } // Extract public key from auth_data COSE structure // Extract credential ID cbor_auth_data_len = cbor_bytestring_length(auth_data); cbor_auth_data = cbor_bytestring_handle(auth_data); cred_pub_key = cbor_auth_data+CREDENTIAL_ID_OFFSET+credential_id_len; cred_pub_key_len = cbor_auth_data_len-CREDENTIAL_ID_OFFSET-credential_id_len; cbor_cose = cbor_load(cred_pub_key, cred_pub_key_len, &cbor_result); if (cbor_result.error.code != CBOR_ERR_NONE) { json_array_append_new(j_error, json_string("Invalid COSE key")); y_log_message(Y_LOG_LEVEL_DEBUG, "register_new_attestation - Error cbor_load cbor_cose"); ret = G_ERROR_PARAM; break; } if (!cbor_isa_map(cbor_cose)) { json_array_append_new(j_error, json_string("Invalid COSE key")); y_log_message(Y_LOG_LEVEL_DEBUG, "register_new_attestation - Error cbor_cose not a map"); ret = G_ERROR_PARAM; break; } for (i=0; iconn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error h_update"); } } json_decref(j_error); json_decref(j_client_data); json_decref(j_pubkey); json_decref(j_cert); o_free(client_data); o_free(challenge_b64); o_free(challenge_hash); o_free(att_obj); o_free(credential_id_b64); gnutls_pubkey_deinit(g_key); if (item != NULL) { cbor_decref(&item); } if (cbor_cose != NULL) { cbor_decref(&cbor_cose); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error allocating resources for j_error"); j_return = json_pack("{si}", "result", G_ERROR); } } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "scheme_data mandatory"); } return j_return; } /** * */ static int check_assertion(struct config_module * config, json_t * j_params, const char * username, json_t * j_scheme_data, json_t * j_assertion) { int ret, res; unsigned char * client_data = NULL, * challenge_b64 = NULL, * auth_data = NULL, rpid_hash[32] = {0}, * flags, cdata_hash[32] = {0}, data_signed[128] = {0}, sig[128] = {0}, * counter; char * challenge_hash = NULL; const char * rpid = NULL; size_t client_data_len, challenge_b64_len, auth_data_len, rpid_hash_len = 32, cdata_hash_len = 32, sig_len = 128, counter_value = 0, rpid_len = 0; json_t * j_client_data = NULL, * j_credential = NULL, * j_query; gnutls_pubkey_t pubkey = NULL; gnutls_datum_t pubkey_dat, data, signature; if (j_scheme_data != NULL && j_assertion != NULL) { do { ret = G_OK; if (!json_is_string(json_object_get(json_object_get(j_scheme_data, "credential"), "rawId")) || !json_string_length(json_object_get(json_object_get(j_scheme_data, "credential"), "rawId"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - rawId missing"); ret = G_ERROR_PARAM; break; } j_credential = get_credential(config, j_params, username, json_string_value(json_object_get(json_object_get(j_scheme_data, "credential"), "rawId"))); if (check_result_value(j_credential, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - credential ID not found"); ret = G_ERROR_UNAUTHORIZED; break; } if (!json_is_string(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "clientDataJSON")) || !json_string_length(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "clientDataJSON"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - clientDataJSON mandatory"); ret = G_ERROR_PARAM; break; } if ((client_data = o_malloc(json_string_length(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "clientDataJSON"))+1)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "check_assertion - Error allocating resources for client_data"); ret = G_ERROR_MEMORY; break; } if (!o_base64_decode((const unsigned char *)json_string_value(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "clientDataJSON")), json_string_length(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "clientDataJSON")), client_data, &client_data_len)) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - Error o_base64_decode client_data"); ret = G_ERROR_PARAM; break; } client_data[client_data_len] = '\0'; j_client_data = json_loads((const char *)client_data, JSON_DECODE_ANY, NULL); if (j_client_data == NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - Error parsing JSON client data %s", client_data); ret = G_ERROR_PARAM; break; } // Step 7 if (0 != o_strcmp("webauthn.get", json_string_value(json_object_get(j_client_data, "type")))) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - clientDataJSON.type invalid"); ret = G_ERROR_PARAM; break; } // Step 8 if (!json_string_length(json_object_get(j_client_data, "challenge"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - clientDataJSON.challenge mandatory"); ret = G_ERROR_PARAM; break; } if ((challenge_b64 = o_malloc(json_string_length(json_object_get(j_client_data, "challenge"))+3)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error allocating resources for challenge_b64"); ret = G_ERROR_MEMORY; break; } if (!o_base64url_2_base64((unsigned char *)json_string_value(json_object_get(j_client_data, "challenge")), json_string_length(json_object_get(j_client_data, "challenge")), challenge_b64, &challenge_b64_len)) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - clientDataJSON.challenge invalid base64"); ret = G_ERROR_PARAM; break; } challenge_b64[challenge_b64_len] = '\0'; if ((challenge_hash = generate_hash(config->hash_algorithm, (const char *)challenge_b64)) == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error generate_hash for challenge_b64"); ret = G_ERROR; break; } if (0 != o_strcmp(challenge_hash, json_string_value(json_object_get(j_assertion, "challenge_hash")))) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - clientDataJSON.challenge invalid"); ret = G_ERROR_PARAM; break; } // Step 9 if (!json_string_length(json_object_get(j_client_data, "origin"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - clientDataJSON.origin mandatory"); ret = G_ERROR_PARAM; break; } if (0 != o_strcmp(json_string_value(json_object_get(j_params, "rp-origin")), json_string_value(json_object_get(j_client_data, "origin")))) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - clientDataJSON.origin invalid - Client send %s, required %s", json_string_value(json_object_get(j_client_data, "origin")), json_string_value(json_object_get(j_params, "rp-origin"))); ret = G_ERROR_PARAM; break; } // Step 10 ?? // Step 11 if (!json_string_length(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "authenticatorData"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - authenticatorData mandatory"); ret = G_ERROR_PARAM; break; } if ((auth_data = o_malloc(json_string_length(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "authenticatorData"))+1)) == NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - Error allocating resources for auth_data"); ret = G_ERROR_PARAM; break; } if (!o_base64_decode((const unsigned char *)json_string_value(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "authenticatorData")), json_string_length(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "authenticatorData")), auth_data, &auth_data_len)) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - Error o_base64_decode auth_data"); ret = G_ERROR_PARAM; break; } if (auth_data_len < 37) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - Error authenticatorData invalid"); ret = G_ERROR_PARAM; break; } if (o_strstr(json_string_value(json_object_get(j_params, "rp-origin")), "://") != NULL) { rpid = o_strstr(json_string_value(json_object_get(j_params, "rp-origin")), "://")+3; } else { rpid = json_string_value(json_object_get(j_params, "rp-origin")); } if (o_strchr(rpid, ':') != NULL) { rpid_len = o_strchr(rpid, ':') - rpid; } else { rpid_len = o_strlen(rpid); } if (!generate_digest_raw(digest_SHA256, (unsigned char *)rpid, rpid_len, rpid_hash, &rpid_hash_len)) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - Error generate_digest_raw for rpid_hash"); ret = G_ERROR_PARAM; break; } if (0 != memcmp(auth_data, rpid_hash, rpid_hash_len)) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - authData.rpIdHash invalid"); ret = G_ERROR_PARAM; break; } flags = auth_data + FLAGS_OFFSET; // Step 12 if (!(*flags & FLAG_USER_PRESENT)) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - authData.userPresent not set"); ret = G_ERROR_PARAM; break; } // Step 13 ignored for now //y_log_message(Y_LOG_LEVEL_DEBUG, "authData.userVerified: %d", !!(*flags & FLAG_USER_VERIFY)); // Step 14 ignored for now (no extension) //y_log_message(Y_LOG_LEVEL_DEBUG, "authData.Extension: %d", !!(*flags & FLAG_ED)); // Step 15 if (!generate_digest_raw(digest_SHA256, client_data, client_data_len, cdata_hash, &cdata_hash_len)) { y_log_message(Y_LOG_LEVEL_ERROR, "check_assertion - Error generate_digest_raw for cdata_hash"); ret = G_ERROR_PARAM; break; } counter = auth_data + COUNTER_OFFSET; counter_value = counter[3] | (counter[2] << 8) | (counter[1] << 16) | (counter[0] << 24); if (gnutls_pubkey_init(&pubkey) < 0) { y_log_message(Y_LOG_LEVEL_ERROR, "check_assertion - Error gnutls_pubkey_init"); ret = G_ERROR; break; } pubkey_dat.data = (unsigned char *)json_string_value(json_object_get(json_object_get(j_credential, "credential"), "public_key")); pubkey_dat.size = json_string_length(json_object_get(json_object_get(j_credential, "credential"), "public_key")); if ((ret = gnutls_pubkey_import(pubkey, &pubkey_dat, GNUTLS_X509_FMT_PEM)) < 0) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - Error gnutls_pubkey_import: %d", ret); ret = G_ERROR; break; } if (!o_base64url_decode((const unsigned char *)json_string_value(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "signature")), json_string_length(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "signature")), sig, &sig_len)) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - Error o_base64url_decode signature"); ret = G_ERROR_PARAM; break; } memcpy(data_signed, auth_data, auth_data_len); memcpy(data_signed+auth_data_len, cdata_hash, cdata_hash_len); // Let's verify sig over data_signed data.data = data_signed; data.size = (auth_data_len+cdata_hash_len); signature.data = sig; signature.size = sig_len; if ((res = gnutls_pubkey_verify_data2(pubkey, GNUTLS_SIGN_ECDSA_SHA256, 0, &data, &signature)) < 0) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - Invalid signature: %d", res); ret = G_ERROR_UNAUTHORIZED; break; } if ((json_integer_value(json_object_get(json_object_get(j_credential, "credential"), "counter")) || counter_value) && counter_value <= (size_t)json_integer_value(json_object_get(json_object_get(j_credential, "credential"), "counter"))) { y_log_message(Y_LOG_LEVEL_DEBUG, "check_assertion - counter invalid"); ret = G_ERROR_UNAUTHORIZED; break; } } while (0); // This is not a loop, but a structure where you can easily cancel the rest of the process with breaks if (ret == G_OK) { // Update assertion j_query = json_pack("{sss{sisi}s{sO}}", "table", G_TABLE_WEBAUTHN_ASSERTION, "set", "gswa_counter", counter_value, "gswa_status", 1, "where", "gswa_id", json_object_get(j_assertion, "gswa_id")); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "check_assertion - Error executing j_query (1)"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } else { // Update counter in credential if necessary if (counter) { j_query = json_pack("{sss{si}s{sO}}", "table", G_TABLE_WEBAUTHN_CREDENTIAL, "set", "gswc_counter", counter_value, "where", "gswc_id", json_object_get(json_object_get(j_credential, "credential"), "gswc_id")); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "check_assertion - Error executing j_query (2)"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } } } else if (ret == G_ERROR_PARAM) { j_query = json_pack("{sss{sisi}s{sO}}", "table", G_TABLE_WEBAUTHN_ASSERTION, "set", "gswa_counter", counter_value, "gswa_status", 2, "where", "gswa_id", json_object_get(j_assertion, "gswa_id")); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "check_assertion - Error executing j_query (3)"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { j_query = json_pack("{sss{sisi}s{sO}}", "table", G_TABLE_WEBAUTHN_ASSERTION, "set", "gswa_counter", counter_value, "gswa_status", 3, "where", "gswa_id", json_object_get(j_assertion, "gswa_id")); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "check_assertion - Error executing j_query (4)"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } o_free(client_data); o_free(challenge_b64); o_free(challenge_hash); o_free(auth_data); json_decref(j_client_data); json_decref(j_credential); gnutls_pubkey_deinit(pubkey); } else { ret = G_ERROR_PARAM; } return ret; } /** * Generates a fake credential based on the seed * the fake credential has the following form: * { * credential_id: string, base64 encoding of 64 a bytes string * name: string * created_at: number, epoch time * status: string, always "registered" * } */ static json_t * generate_credential_fake_from_seed(const char * seed) { unsigned char credential_id[64] = {0}, credential_id_b64[129], created_at[32], name_hash[32]; char * seed_credential_id, * seed_name, * seed_created_at, name[32]; time_t created_at_t; size_t credential_id_len = 64, credential_id_b64_len, name_hash_len = 32, created_at_len = 32; json_t * j_return; if ((seed_credential_id = msprintf("%s-credential_id", seed)) != NULL) { if (generate_digest_raw(digest_SHA512, (unsigned char *)seed_credential_id, o_strlen(seed_credential_id), credential_id, &credential_id_len)) { if (o_base64_encode(credential_id, credential_id_len, credential_id_b64, &credential_id_b64_len)) { if ((seed_name = msprintf("%s-name", seed)) != NULL) { if (generate_digest_raw(digest_SHA256, (unsigned char *)seed_name, o_strlen(seed_name), name_hash, &name_hash_len)) { if (name_hash[0]%2) { o_strcpy(name, "fido-u2f"); } else { o_strcpy(name, "android-safetynet"); } if ((seed_created_at = msprintf("%s-created_at", seed)) != NULL) { if (generate_digest_raw(digest_SHA256, (unsigned char *)seed_created_at, o_strlen(seed_created_at), created_at, &created_at_len)) { time(&created_at_t); created_at_t -= created_at[0] - (created_at[1] << 8); j_return = json_pack("{sis{sssssiss}}", "result", G_OK, "credential", "credential_id", credential_id_b64, "name", name, "created_at", created_at_t, "status", "registered"); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_credential_fake_from_seed - Error generate_digest_raw for seed_created_at"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_credential_fake_from_seed - Error allocating resources for seed_created_at"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } o_free(seed_created_at); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_credential_fake_from_seed - Error generate_digest_raw for seed_name"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_credential_fake_from_seed - Error allocating resources for seed_name"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } o_free(seed_name); } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_credential_fake_from_seed - Error o_base64_encode for seed_credential_id"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_credential_fake_from_seed - Error generate_digest_raw for seed_credential_id"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "generate_credential_fake_from_seed - Error allocating resources for seed_credential_id"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } o_free(seed_credential_id); return j_return; } /** * Generates 1 to 3 random credentials using the seed and the username */ static json_t * generate_credential_fake_list(json_t * j_params, const char * username) { json_t * j_credential, * j_credential_sub, * j_return; char * seed; unsigned char seed_hash[32] = {0}; size_t seed_hash_len = 32; unsigned int i; if ((seed = msprintf("%s%s0", username, json_string_value(json_object_get(j_params, "seed")))) != NULL) { j_credential = generate_credential_fake_from_seed(seed); if (check_result_value(j_credential, G_OK)) { j_return = json_pack("{sis[O]}", "result", G_OK, "credential", json_object_get(j_credential, "credential")); if (j_return != NULL) { if (generate_digest_raw(digest_SHA256, (unsigned char *)seed, o_strlen(seed), seed_hash, &seed_hash_len)) { for (i=0; iglewlwyd_module_callback_check_user_session(config, http_request, username), * j_credential, * j_assertion, * j_user_id, * j_credential_fake; unsigned char user_id_fake[64]; if (check_result_value(j_session, G_OK) || json_object_get((json_t *)cls, "session-mandatory") == json_false()) { j_credential_fake = generate_credential_fake_list((json_t *)cls, username); if (check_result_value(j_credential_fake, G_OK)) { j_user_id = get_user_id_from_username(config, (json_t *)cls, username, 0); if (check_result_value(j_user_id, G_OK)) { j_credential = get_credential_list(config, (json_t *)cls, username, 1); if (check_result_value(j_credential, G_OK)) { j_assertion = generate_new_assertion(config, (json_t *)cls, username, 0); if (check_result_value(j_assertion, G_OK)) { j_return = json_pack("{sis{sOsOsOs{sOss}sOsssi}}", "result", G_OK, "response", "allowCredentials", json_object_get(j_credential, "credential"), "session", json_object_get(json_object_get(j_assertion, "assertion"), "session"), "challenge", json_object_get(json_object_get(j_assertion, "assertion"), "challenge"), "user", "id", json_object_get(j_user_id, "user_id"), "name", username, "rpId", json_object_get((json_t *)cls, "rp-origin"), "attestation-required", json_object_get((json_t *)cls, "force-fmt-none")==json_true()?"none":"direct", "timeout", 60000 ); if (json_object_get((json_t *)cls, "session-mandatory") == json_false()) { json_array_extend(json_object_get(json_object_get(j_return, "response"), "allowCredentials"), json_object_get(j_credential_fake, "credential")); } } else if (check_result_value(j_assertion, G_ERROR_UNAUTHORIZED)) { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_trigger webauthn - Error register_new_assertion"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_assertion); } else if (check_result_value(j_credential, G_ERROR_NOT_FOUND)) { if (json_object_get((json_t *)cls, "session-mandatory") == json_false()) { j_assertion = generate_new_assertion(config, (json_t *)cls, username, 2); if (check_result_value(j_assertion, G_OK)) { j_return = json_pack("{sis{sOsOsOs{sOss}sO}}", "result", G_OK, "response", "allowCredentials", json_object_get(j_credential_fake, "credential"), "session", json_object_get(json_object_get(j_assertion, "assertion"), "session"), "challenge", json_object_get(json_object_get(j_assertion, "assertion"), "challenge"), "user", "id", json_object_get(j_user_id, "user_id"), "name", username, "rpId", json_object_get((json_t *)cls, "rp-origin") ); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_trigger webauthn - Error register_new_assertion"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_assertion); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_trigger webauthn - Error get_credential_list"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_credential); } else if (check_result_value(j_user_id, G_ERROR_NOT_FOUND)) { if (json_object_get((json_t *)cls, "session-mandatory") == json_false()) { if (generate_fake_user_id((json_t *)cls, username, user_id_fake) == G_OK) { j_assertion = generate_new_assertion(config, (json_t *)cls, username, 2); if (check_result_value(j_assertion, G_OK)) { j_return = json_pack("{sis{sOsOsOs{ssss}sO}}", "result", G_OK, "response", "allowCredentials", json_object_get(j_credential_fake, "credential"), "session", json_object_get(json_object_get(j_assertion, "assertion"), "session"), "challenge", json_object_get(json_object_get(j_assertion, "assertion"), "challenge"), "user", "id", user_id_fake, "name", username, "rpId", json_object_get((json_t *)cls, "rp-origin") ); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_trigger webauthn - Error register_new_assertion"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_assertion); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register webauthn - Error generate_fake_user_id"); j_return = json_pack("{si}", "result", G_ERROR); } } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register webauthn - Error get_user_id_from_username"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_user_id); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_trigger webauthn - Error generate_credential_fake"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_credential_fake); } else if (check_result_value(j_session, G_ERROR_UNAUTHORIZED)) { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_trigger webauthn - Error glewlwyd_module_callback_check_user_session"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_session); return j_return; } /** * * user_auth_scheme_module_validate * * Validate the scheme for a user * Ex: check the code sent to a device, verify the challenge, etc. * * @return value: G_OK on success * G_ERROR_UNAUTHORIZED if validation fails * G_ERROR_PARAM if error in parameters * G_ERROR on another error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter username: username to identify the user * @parameter j_scheme_data: data sent to validate the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ int user_auth_scheme_module_validate(struct config_module * config, const struct _u_request * http_request, const char * username, json_t * j_scheme_data, void * cls) { UNUSED(http_request); int ret, res; json_t * j_user_id, * j_assertion; j_user_id = get_user_id_from_username(config, (json_t *)cls, username, 0); if (check_result_value(j_user_id, G_OK)) { j_assertion = get_assertion_from_session(config, (json_t *)cls, username, json_string_value(json_object_get(j_scheme_data, "session")), 0); if (check_result_value(j_assertion, G_OK)) { if ((res = check_assertion(config, (json_t *)cls, username, j_scheme_data, json_object_get(j_assertion, "assertion"))) == G_OK) { ret = G_OK; } else if (res == G_ERROR_UNAUTHORIZED) { ret = G_ERROR_UNAUTHORIZED; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate webauthn - Error check_assertion"); ret = G_ERROR; } } else if (check_result_value(j_assertion, G_ERROR_NOT_FOUND)) { ret = G_ERROR_UNAUTHORIZED; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_register webauthn - Error get_credential"); ret = G_ERROR; } json_decref(j_assertion); } else if (check_result_value(j_user_id, G_ERROR_NOT_FOUND)) { ret = G_ERROR_UNAUTHORIZED; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_validate webauthn - Error get_user_id_from_username"); ret = G_ERROR; } json_decref(j_user_id); return ret; } /** * * user_auth_scheme_module_identify * * Identify the user using the scheme without the username to be previously given * This functionality isn't available for all schemes, because the scheme authentification * must be triggered without username and the authentication result must contain the username * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * username: string value of the user identified - if the function is called within /auth * response: JSON object, optional - if the function is called within /auth/scheme/trigger * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter http_request: the original struct _u_request from the API, must be casted to be available * @parameter j_scheme_data: data sent to validate the scheme for the user * in JSON format * @parameter cls: pointer to the void * cls value allocated in user_auth_scheme_module_init * */ json_t * user_auth_scheme_module_identify(struct config_module * config, const struct _u_request * http_request, json_t * j_scheme_data, void * cls) { UNUSED(config); UNUSED(http_request); UNUSED(j_scheme_data); UNUSED(cls); return json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } glewlwyd-2.6.1/src/scheme/webauthn.mariadb.sql000066400000000000000000000034751415646314000213620ustar00rootroot00000000000000DROP TABLE IF EXISTS gs_webauthn_assertion; DROP TABLE IF EXISTS gs_webauthn_credential; DROP TABLE IF EXISTS gs_webauthn_user; CREATE TABLE gs_webauthn_user ( gswu_id INT(11) PRIMARY KEY AUTO_INCREMENT, gswu_mod_name VARCHAR(128) NOT NULL, gswu_username VARCHAR(128) NOT NULL, gswu_user_id VARCHAR(128) NOT NULL ); CREATE INDEX i_gswu_username ON gs_webauthn_user(gswu_username); CREATE TABLE gs_webauthn_credential ( gswc_id INT(11) PRIMARY KEY AUTO_INCREMENT, gswu_id INT(11) NOT NULL, gswc_session_hash VARCHAR(128) NOT NULL, gswc_name VARCHAR(128), gswc_challenge_hash VARCHAR(128), gswc_credential_id VARCHAR(256), gswc_certificate VARCHAR(128), gswc_public_key TEXT DEFAULT NULL, gswc_counter INT(11) DEFAULT 0, gswc_created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gswc_status TINYINT(1) DEFAULT 0, -- 0 new, 1 registered, 2 error, 3 disabled, 4 removed FOREIGN KEY(gswu_id) REFERENCES gs_webauthn_user(gswu_id) ON DELETE CASCADE ); CREATE INDEX i_gswc_credential_id ON gs_webauthn_credential(gswc_credential_id); CREATE INDEX i_gswc_session_hash ON gs_webauthn_credential(gswc_session_hash); CREATE TABLE gs_webauthn_assertion ( gswa_id INT(11) PRIMARY KEY AUTO_INCREMENT, gswu_id INT(11) NOT NULL, gswc_id INT(11), gswa_session_hash VARCHAR(128) NOT NULL, gswa_challenge_hash VARCHAR(128), gswa_counter INT(11) DEFAULT 0, gswa_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gswa_status TINYINT(1) DEFAULT 0, -- 0 new, 1 verified, 2 not verified, 3 error gswa_mock TINYINT(1) DEFAULT 0, FOREIGN KEY(gswu_id) REFERENCES gs_webauthn_user(gswu_id) ON DELETE CASCADE, FOREIGN KEY(gswc_id) REFERENCES gs_webauthn_credential(gswc_id) ON DELETE CASCADE ); CREATE INDEX i_gswa_session_hash ON gs_webauthn_assertion(gswa_session_hash); glewlwyd-2.6.1/src/scheme/webauthn.postgre.sql000066400000000000000000000033631415646314000214420ustar00rootroot00000000000000DROP TABLE IF EXISTS gs_webauthn_assertion; DROP TABLE IF EXISTS gs_webauthn_credential; DROP TABLE IF EXISTS gs_webauthn_user; CREATE TABLE gs_webauthn_user ( gswu_id SERIAL PRIMARY KEY, gswu_mod_name VARCHAR(128) NOT NULL, gswu_username VARCHAR(128) NOT NULL, gswu_user_id VARCHAR(128) NOT NULL ); CREATE INDEX i_gswu_username ON gs_webauthn_user(gswu_username); CREATE TABLE gs_webauthn_credential ( gswc_id SERIAL PRIMARY KEY, gswu_id INTEGER NOT NULL, gswc_session_hash VARCHAR(128) NOT NULL, gswc_name VARCHAR(128), gswc_challenge_hash VARCHAR(128), gswc_credential_id VARCHAR(256), gswc_certificate VARCHAR(128), gswc_public_key TEXT DEFAULT NULL, gswc_counter INTEGER DEFAULT 0, gswc_created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gswc_status SMALLINT DEFAULT 0, -- 0 new, 1 registered, 2 error, 3 disabled, 4 removed FOREIGN KEY(gswu_id) REFERENCES gs_webauthn_user(gswu_id) ON DELETE CASCADE ); CREATE INDEX i_gswc_credential_id ON gs_webauthn_credential(gswc_credential_id); CREATE INDEX i_gswc_session_hash ON gs_webauthn_credential(gswc_session_hash); CREATE TABLE gs_webauthn_assertion ( gswa_id SERIAL PRIMARY KEY, gswu_id INTEGER NOT NULL, gswc_id INTEGER, gswa_session_hash VARCHAR(128) NOT NULL, gswa_challenge_hash VARCHAR(128), gswa_counter INTEGER DEFAULT 0, gswa_issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), gswa_status SMALLINT DEFAULT 0, -- 0 new, 1 verified, 2 not verified, 3 error gswa_mock SMALLINT DEFAULT 0, FOREIGN KEY(gswu_id) REFERENCES gs_webauthn_user(gswu_id) ON DELETE CASCADE, FOREIGN KEY(gswc_id) REFERENCES gs_webauthn_credential(gswc_id) ON DELETE CASCADE ); CREATE INDEX i_gswa_session_hash ON gs_webauthn_assertion(gswa_session_hash); glewlwyd-2.6.1/src/scheme/webauthn.sqlite.sql000066400000000000000000000033601415646314000212550ustar00rootroot00000000000000DROP TABLE IF EXISTS gs_webauthn_assertion; DROP TABLE IF EXISTS gs_webauthn_credential; DROP TABLE IF EXISTS gs_webauthn_user; CREATE TABLE gs_webauthn_user ( gswu_id INTEGER PRIMARY KEY AUTOINCREMENT, gswu_mod_name TEXT NOT NULL, gswu_username TEXT NOT NULL, gswu_user_id TEXT NOT NULL ); CREATE INDEX i_gswu_username ON gs_webauthn_user(gswu_username); CREATE TABLE gs_webauthn_credential ( gswc_id INTEGER PRIMARY KEY AUTOINCREMENT, gswu_id INTEGER NOT NULL, gswc_session_hash TEXT NOT NULL, gswc_name TEXT, gswc_challenge_hash TEXT, gswc_credential_id TEXT, gswc_certificate TEXT DEFAULT NULL, gswc_public_key TEXT DEFAULT NULL, gswc_counter INTEGER DEFAULT 0, gswc_created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gswc_status INTEGER DEFAULT 0, -- 0 new, 1 registered, 2 error, 3 disabled, 4 removed FOREIGN KEY(gswu_id) REFERENCES gs_webauthn_user(gswu_id) ON DELETE CASCADE ); CREATE INDEX i_gswc_credential_id ON gs_webauthn_credential(gswc_credential_id); CREATE INDEX i_gswc_session_hash ON gs_webauthn_credential(gswc_session_hash); CREATE TABLE gs_webauthn_assertion ( gswa_id INTEGER PRIMARY KEY AUTOINCREMENT, gswu_id INTEGER NOT NULL, gswc_id INTEGER, gswa_session_hash TEXT NOT NULL, gswa_challenge_hash TEXT, gswa_counter INTEGER DEFAULT 0, gswa_issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, gswa_status SMALLINT DEFAULT 0, -- 0 new, 1 verified, 2 not verified, 3 error gswa_mock SMALLINT DEFAULT 0, FOREIGN KEY(gswu_id) REFERENCES gs_webauthn_user(gswu_id) ON DELETE CASCADE, FOREIGN KEY(gswc_id) REFERENCES gs_webauthn_credential(gswc_id) ON DELETE CASCADE ); CREATE INDEX i_gswa_session_hash ON gs_webauthn_assertion(gswa_session_hash); glewlwyd-2.6.1/src/scope.c000066400000000000000000001555761415646314000154510ustar00rootroot00000000000000/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * scope management functions definition * * Copyright 2016-2021 Nicolas Mora * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . * */ #include "glewlwyd.h" static json_t * get_current_session(struct config_elements * config, const char * session_hash) { json_t * j_query, * j_result = NULL, * j_return; int res; char * expire_clause; if (config->conn->type==HOEL_DB_TYPE_MARIADB) { expire_clause = o_strdup("> NOW()"); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { expire_clause = o_strdup("> NOW()"); } else { // HOEL_DB_TYPE_SQLITE expire_clause = o_strdup("> (strftime('%s','now'))"); } j_query = json_pack("{sss[ss]s{sssis{ssss}si}sssi}", "table", GLEWLWYD_TABLE_USER_SESSION, "columns", "gus_id", "gus_username AS username", "where", "gus_session_hash", session_hash, "gus_enabled", 1, "gus_expiration", "operator", "raw", "value", expire_clause, "gus_current", 1, "order_by", "gus_current DESC", "limit", 1); o_free(expire_clause); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result) > 0) { j_return = json_pack("{sisO}", "result", G_OK, "session", json_array_get(j_result, 0)); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_current_session - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_result); return j_return; } static json_t * get_current_user_from_session(struct config_elements * config, const char * session_uid) { char * session_hash; json_t * j_session, * j_return, * j_user; if (session_uid != NULL && o_strlen(session_uid)) { if ((session_hash = generate_hash(config->hash_algorithm, session_uid)) != NULL) { j_session = get_current_session(config, session_hash); if (check_result_value(j_session, G_OK)) { j_user = get_user(config, json_string_value(json_object_get(json_object_get(j_session, "session"), "username")), NULL); if (check_result_value(j_user, G_OK)) { j_return = json_pack("{sisO}", "result", G_OK, "user", json_object_get(j_user, "user")); } else if (check_result_value(j_user, G_ERROR_NOT_FOUND)) { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_current_user_from_session - Error get_user"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_user); } else if (check_result_value(j_session, G_ERROR_NOT_FOUND)) { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_current_user_from_session - Error get_current_session"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_session); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_current_user_from_session - Error generate_hash"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(session_hash); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } return j_return; } json_t * get_scope_list(struct config_elements * config, const char * pattern, size_t offset, size_t limit) { json_t * j_query, * j_result, * j_return, * j_element, * j_scheme; int res; size_t index; char * pattern_escaped, * pattern_clause; j_query = json_pack("{sss[sssss]siss}", "table", GLEWLWYD_TABLE_SCOPE, "columns", "gs_name AS name", "gs_display_name AS display_name", "gs_description AS description", "gs_password_required", "gs_password_max_age AS password_max_age", "offset", offset, "order_by", "gs_name"); if (limit) { json_object_set_new(j_query, "limit", json_integer(limit)); } if (o_strlen(pattern)) { pattern_escaped = h_escape_string_with_quotes(config->conn, pattern); pattern_clause = msprintf("IN (SELECT gs_id FROM " GLEWLWYD_TABLE_SCOPE " WHERE gs_name LIKE '%%'||%s||'%%' OR gs_display_name LIKE '%%'||%s||'%%' OR gs_description LIKE '%%'||%s||'%%')", pattern_escaped, pattern_escaped, pattern_escaped); json_object_set_new(j_query, "where", json_pack("{s{ssss}}", "gs_id", "operator", "raw", "value", pattern_clause)); o_free(pattern_escaped); o_free(pattern_clause); } res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result, index, j_element) { json_object_set(j_element, "password_required", json_integer_value(json_object_get(j_element, "gs_password_required"))?json_true():json_false()); json_object_del(j_element, "gs_password_required"); j_scheme = get_auth_scheme_list_from_scope(config, json_string_value(json_object_get(j_element, "name"))); if (check_result_value(j_scheme, G_OK)) { json_object_set(j_element, "scheme", json_object_get(j_scheme, "scheme")); json_object_set(j_element, "scheme_required", json_object_get(j_scheme, "scheme_required")); } else if (check_result_value(j_scheme, G_ERROR_NOT_FOUND)) { json_object_set_new(j_element, "scheme", json_object()); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_scope_list - Error get_auth_scheme_list_from_scope for scope %s", json_string_value(json_object_get(j_element, "name"))); } json_decref(j_scheme); } j_return = json_pack("{siso}", "result", G_OK, "scope", j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_scope_list - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } json_t * get_scope(struct config_elements * config, const char * scope) { json_t * j_query, * j_result = NULL, * j_return, * j_scheme; int res; j_query = json_pack("{sss[sssss]s{ss}}", "table", GLEWLWYD_TABLE_SCOPE, "columns", "gs_name AS name", "gs_display_name AS display_name", "gs_description AS description", "gs_password_required", "gs_password_max_age AS password_max_age", "where", "gs_name", scope); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { json_object_set(json_array_get(j_result, 0), "password_required", json_integer_value(json_object_get(json_array_get(j_result, 0), "gs_password_required"))?json_true():json_false()); json_object_del(json_array_get(j_result, 0), "gs_password_required"); j_scheme = get_auth_scheme_list_from_scope(config, scope); if (check_result_value(j_scheme, G_OK)) { json_object_set(json_array_get(j_result, 0), "scheme", json_object_get(j_scheme, "scheme")); json_object_set(json_array_get(j_result, 0), "scheme_required", json_object_get(j_scheme, "scheme_required")); j_return = json_pack("{sisO}", "result", G_OK, "scope", json_array_get(j_result, 0)); } else if (check_result_value(j_scheme, G_ERROR_NOT_FOUND)) { json_object_set_new(json_array_get(j_result, 0), "scheme", json_object()); j_return = json_pack("{sisO}", "result", G_OK, "scope", json_array_get(j_result, 0)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_scope - Error get_auth_scheme_list_from_scope"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_scheme); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_scope - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_result); return j_return; } json_t * get_auth_scheme_list_from_scope(struct config_elements * config, const char * scope) { const char * str_query_pattern = "SELECT \ gsg_name AS group_name, \ gsg_scheme_required AS scheme_required, \ guasmi_module AS scheme_type, \ guasmi_name AS scheme_name, \ guasmi_display_name AS scheme_display_name \ FROM \ " GLEWLWYD_TABLE_SCOPE_GROUP ", \ " GLEWLWYD_TABLE_USER_AUTH_SCHEME_MODULE_INSTANCE ", \ " GLEWLWYD_TABLE_SCOPE_GROUP_AUTH_SCHEME_MODULE_INSTANCE " \ WHERE \ " GLEWLWYD_TABLE_SCOPE_GROUP_AUTH_SCHEME_MODULE_INSTANCE ".guasmi_id = " GLEWLWYD_TABLE_USER_AUTH_SCHEME_MODULE_INSTANCE ".guasmi_id AND \ " GLEWLWYD_TABLE_SCOPE_GROUP ".gsg_id = " GLEWLWYD_TABLE_SCOPE_GROUP_AUTH_SCHEME_MODULE_INSTANCE ".gsg_id AND \ " GLEWLWYD_TABLE_SCOPE_GROUP_AUTH_SCHEME_MODULE_INSTANCE ".gsg_id IN \ (SELECT gsg_id FROM " GLEWLWYD_TABLE_SCOPE_GROUP " WHERE gs_id = \ (SELECT gs_id FROM " GLEWLWYD_TABLE_SCOPE " WHERE gs_name=%s)) \ ORDER BY \ " GLEWLWYD_TABLE_SCOPE_GROUP ".gsg_id, \ " GLEWLWYD_TABLE_USER_AUTH_SCHEME_MODULE_INSTANCE ".guasmi_name;"; char * scope_escape = h_escape_string_with_quotes(config->conn, scope), * str_query = NULL; json_t * j_return, * j_result = NULL, * j_element; int res; size_t index; if (scope_escape != NULL) { str_query = msprintf(str_query_pattern, scope_escape); if (str_query != NULL) { res = h_execute_query_json(config->conn, str_query, &j_result); if (res == H_OK) { if (json_array_size(j_result)) { j_return = json_pack("{sis{}s{}}", "result", G_OK, "scheme", "scheme_required"); if (j_return != NULL) { json_array_foreach(j_result, index, j_element) { if (json_object_get(json_object_get(j_return, "scheme"), json_string_value(json_object_get(j_element, "group_name"))) == NULL) { json_object_set_new(json_object_get(j_return, "scheme"), json_string_value(json_object_get(j_element, "group_name")), json_array()); json_object_set(json_object_get(j_return, "scheme_required"), json_string_value(json_object_get(j_element, "group_name")), json_object_get(j_element, "scheme_required")); } if (json_object_get(json_object_get(j_return, "scheme"), json_string_value(json_object_get(j_element, "group_name"))) != NULL) { json_array_append_new(json_object_get(json_object_get(j_return, "scheme"), json_string_value(json_object_get(j_element, "group_name"))), json_pack("{ssssss?}", "scheme_type", json_string_value(json_object_get(j_element, "scheme_type")), "scheme_name", json_string_value(json_object_get(j_element, "scheme_name")), "scheme_display_name", json_string_value(json_object_get(j_element, "scheme_display_name")))); } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_auth_scheme_list_from_scope - Error allocating resources for j_return"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_auth_scheme_list_from_scope - Error executing str_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_auth_scheme_list_from_scope - Error allocating resources for str_query"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(str_query); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_auth_scheme_list_from_scope - Error h_escape_string"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(scope_escape); return j_return; } json_t * get_auth_scheme_list_from_scope_list(struct config_elements * config, const char * scope_list) { char ** scope_array = NULL; int i; json_t * j_result, * j_scheme_list, * j_scope; if (split_string(scope_list, " ", &scope_array) > 0) { j_result = json_pack("{sis{}}", "result", G_OK, "scheme"); if (j_result != NULL) { for (i=0; scope_array[i] != NULL; i++) { if (json_object_get(json_object_get(j_result, "scheme"), scope_array[i]) == NULL) { j_scope = get_scope(config, scope_array[i]); if (check_result_value(j_scope, G_OK)) { j_scheme_list = get_auth_scheme_list_from_scope(config, scope_array[i]); if (check_result_value(j_scheme_list, G_OK)) { json_object_set_new(json_object_get(j_result, "scheme"), scope_array[i], json_pack("{sOsOsOsO}", "password_required", json_object_get(json_object_get(j_scope, "scope"), "password_required"), "password_max_age", json_object_get(json_object_get(j_scope, "scope"), "password_max_age"), "schemes", json_object_get(j_scheme_list, "scheme"), "scheme_required", json_object_get(j_scheme_list, "scheme_required"))); } else if (check_result_value(j_scheme_list, G_ERROR_NOT_FOUND)) { json_object_set_new(json_object_get(j_result, "scheme"), scope_array[i], json_pack("{sOsOs{}s{}}", "password_required", json_object_get(json_object_get(j_scope, "scope"), "password_required"), "password_max_age", json_object_get(json_object_get(j_scope, "scope"), "password_max_age"), "schemes", "scheme_required")); } json_decref(j_scheme_list); } json_decref(j_scope); } } if (!json_object_size(json_object_get(j_result, "scheme"))) { json_decref(j_result); j_result = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_auth_scheme_list_from_scope_list - Error allocating resources for j_result"); j_result = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_auth_scheme_list_from_scope_list - Error split_string"); j_result = json_pack("{si}", "result", G_ERROR); } free_string_array(scope_array); return j_result; } static json_t * is_scheme_valid_for_session(struct config_elements * config, json_int_t guasmi_id, json_int_t max_use, json_int_t password_max_age, const char * session_hash) { json_t * j_query, * j_result = NULL, * j_session = get_current_session(config, session_hash), * j_return; int res; time_t now; if (check_result_value(j_session, G_OK)) { j_query = json_pack("{sss[sss]s{sOsosi}}", "table", GLEWLWYD_TABLE_USER_SESSION_SCHEME, "columns", "guss_id", SWITCH_DB_TYPE(config->conn->type, "UNIX_TIMESTAMP(guss_last_login) AS guss_last_login", "guss_last_login AS guss_last_login", "EXTRACT(EPOCH FROM guss_last_login)::integer AS guss_last_login"), SWITCH_DB_TYPE(config->conn->type, "UNIX_TIMESTAMP(guss_expiration) AS guss_expiration", "guss_expiration AS guss_expiration", "EXTRACT(EPOCH FROM guss_expiration)::integer AS guss_expiration"), "where", "gus_id", json_object_get(json_object_get(j_session, "session"), "gus_id"), "guasmi_id", guasmi_id?json_integer(guasmi_id):json_null(), "guss_enabled", 1, "order_by", "guss_last_login DESC"); if (max_use > 0) { json_object_set_new(json_object_get(j_query, "where"), "guss_use_counter", json_pack("{sssI}", "operator", "<", "value", max_use)); } res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { time(&now); if (guasmi_id || !password_max_age) { if (json_array_size(j_result)) { j_return = json_pack("{sisbsO}", "result", G_OK, "valid", (json_integer_value(json_object_get(json_array_get(j_result, 0), "guss_expiration")) > (json_int_t)now), "last_login", json_object_get(json_array_get(j_result, 0), "guss_last_login")); } else { j_return = json_pack("{sisOsi}", "result", G_OK, "valid", json_false(), "last_login", 0); } } else { if (json_array_size(j_result)) { j_return = json_pack("{sisbsO}", "result", G_OK, "valid", (json_integer_value(json_object_get(json_array_get(j_result, 0), "guss_last_login")) + (json_int_t)password_max_age > (json_int_t)now), "last_login", json_object_get(json_array_get(j_result, 0), "guss_last_login")); } else { j_return = json_pack("{sisOsi}", "result", G_OK, "valid", json_false(), "last_login", 0); } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_password_valid_for_session - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_password_valid_for_session - Error get_current_session"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_session); return j_return; } int is_scope_list_valid_for_session(struct config_elements * config, const char * scope_list, const char * session_uid) { json_t * j_validated_scope_list = get_validated_auth_scheme_list_from_scope_list(config, scope_list, session_uid), * j_scope, * j_group, * j_scheme; int ret = G_OK; size_t index_scheme; json_int_t ret_group; const char * key_group, * key_scope; if (check_result_value(j_validated_scope_list, G_OK)) { json_object_foreach(json_object_get(j_validated_scope_list, "scheme"), key_scope, j_scope) { if (ret == G_OK) { if ((json_object_get(j_scope, "available") == json_true() && json_object_get(j_scope, "password_required") == json_true() && json_object_get(j_scope, "password_authenticated") == json_false()) || json_object_get(j_scope, "available") == json_false()) { ret = G_ERROR_UNAUTHORIZED; } else { json_object_foreach(json_object_get(j_scope, "schemes"), key_group, j_group) { ret_group = 0; json_array_foreach(j_group, index_scheme, j_scheme) { if (json_object_get(j_scheme, "scheme_authenticated") == json_true()) { ret_group++; } } if (ret_group < json_integer_value(json_object_get(json_object_get(j_scope, "scheme_required"), key_group))) { ret = G_ERROR_UNAUTHORIZED; } } } } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_scope_list_valid_for_session - Error get_validated_auth_scheme_list_from_scope_list"); ret = G_ERROR; } json_decref(j_validated_scope_list); return ret; } json_t * get_validated_auth_scheme_list_from_scope_list(struct config_elements * config, const char * scope_list, const char * session_uid) { char * session_hash = generate_hash(config->hash_algorithm, session_uid); json_t * j_scheme_list = get_auth_scheme_list_from_scope_list(config, scope_list), * j_cur_scope, * j_scope, * j_scheme, * j_group, * j_user = get_current_user_from_session(config, session_uid), * j_scheme_remove, * j_scheme_password_valid, * j_scheme_valid; const char * key_scope, * key_group; size_t index_scheme; struct _user_auth_scheme_module_instance * scheme; int can_use_scheme; if (check_result_value(j_scheme_list, G_OK)) { json_object_foreach(json_object_get(j_scheme_list, "scheme"), key_scope, j_cur_scope) { j_scope = get_scope(config, key_scope); if (check_result_value(j_scope, G_OK)) { if (check_result_value(j_user, G_OK)) { j_scheme_password_valid = is_scheme_valid_for_session(config, 0, 0, json_object_get(j_cur_scope, "password_required")==json_true()?json_integer_value(json_object_get(j_cur_scope, "password_max_age")):0, session_hash); if (check_result_value(j_scheme_password_valid, G_OK)) { json_object_set(j_cur_scope, "display_name", json_object_get(json_object_get(j_scope, "scope"), "display_name")); json_object_set(j_cur_scope, "description", json_object_get(json_object_get(j_scope, "scope"), "description")); json_object_set(j_cur_scope, "password_authenticated", json_object_get(j_scheme_password_valid, "valid")); json_object_set(j_cur_scope, "password_last_login", json_object_get(j_scheme_password_valid, "last_login")); if (user_has_scope(json_object_get(j_user, "user"), key_scope)) { json_object_set(j_cur_scope, "available", json_true()); json_object_foreach(json_object_get(j_cur_scope, "schemes"), key_group, j_group) { j_scheme_remove = json_array(); if (j_scheme_remove != NULL) { json_array_foreach(j_group, index_scheme, j_scheme) { scheme = get_user_auth_scheme_module_instance(config, json_string_value(json_object_get(j_scheme, "scheme_name"))); if (scheme != NULL) { if (scheme->enabled && (can_use_scheme = scheme->module->user_auth_scheme_module_can_use(config->config_m, json_string_value(json_object_get(json_object_get(j_user, "user"), "username")), scheme->cls)) != GLEWLWYD_IS_NOT_AVAILABLE) { if (can_use_scheme == GLEWLWYD_IS_REGISTERED) { j_scheme_valid = is_scheme_valid_for_session(config, scheme->guasmi_id, scheme->guasmi_max_use, 0, session_hash); if (check_result_value(j_scheme_valid, G_OK)) { json_object_set(j_scheme, "scheme_authenticated", json_object_get(j_scheme_valid, "valid")); json_object_set(j_scheme, "scheme_last_login", json_object_get(j_scheme_valid, "last_login")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_validated_auth_scheme_list_from_scope_list - Error is_scheme_valid_for_session for scheme '%s'", json_string_value(json_object_get(j_scheme, "scheme_name"))); } json_decref(j_scheme_valid); json_object_set(j_scheme, "scheme_registered", json_true()); } else { json_object_set(j_scheme, "scheme_authenticated", json_false()); json_object_set(j_scheme, "scheme_registered", json_false()); } } else { json_array_append_new(j_scheme_remove, json_integer(index_scheme)); } } else { json_array_append_new(j_scheme_remove, json_integer(index_scheme)); y_log_message(Y_LOG_LEVEL_ERROR, "get_validated_auth_scheme_list_from_scope_list - Error get_user_auth_scheme_module_instance"); } } if (json_array_size(j_scheme_remove) > 0) { index_scheme = json_array_size(j_scheme_remove); do { index_scheme--; json_array_remove(j_group, json_integer_value(json_array_get(j_scheme_remove, index_scheme))); } while (index_scheme != 0); } json_decref(j_scheme_remove); if (!json_array_size(j_group)) { json_object_set(j_cur_scope, "available", json_false()); json_object_del(j_cur_scope, "password_required"); json_object_del(j_cur_scope, "password_authenticated"); json_object_clear(json_object_get(j_cur_scope, "schemes")); break; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_validated_auth_scheme_list_from_scope_list - Error allocating resources for j_scheme_remove"); } } } else { json_object_set(j_cur_scope, "available", json_false()); json_object_del(j_cur_scope, "password_required"); json_object_del(j_cur_scope, "password_authenticated"); json_object_clear(json_object_get(j_cur_scope, "schemes")); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_validated_auth_scheme_list_from_scope_list - Error is_scheme_valid_for_session for scheme 'password'"); } json_decref(j_scheme_password_valid); } else { json_object_del(j_cur_scope, "schemes"); json_object_del(j_cur_scope, "scheme_required"); json_object_del(j_cur_scope, "password_required"); } json_object_del(j_cur_scope, "password_max_age"); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_validated_auth_scheme_list_from_scope_list - Error get_scope"); } json_decref(j_scope); } } json_decref(j_user); o_free(session_hash); return j_scheme_list; } json_t * get_client_user_scope_grant(struct config_elements * config, const char * client_id, const char * username, const char * scope_list) { char ** scope_array = NULL; json_t * j_query, * j_result = NULL, * j_return, * j_element; int res, i; char * scope_clause, * scope_name_list = NULL, * scope_escaped, * username_escaped, * client_id_escaped; size_t index; if (split_string(scope_list, " ", &scope_array) > 0) { for (i=0; scope_array[i] != NULL; i++) { scope_escaped = h_escape_string_with_quotes(config->conn, scope_array[i]); if (scope_name_list == NULL) { scope_name_list = o_strdup(scope_escaped); } else { scope_name_list = mstrcatf(scope_name_list, ",%s", scope_escaped); } o_free(scope_escaped); } if (scope_name_list != NULL) { username_escaped = h_escape_string_with_quotes(config->conn, username); client_id_escaped = h_escape_string_with_quotes(config->conn, client_id); scope_clause = msprintf("IN (SELECT gs_id FROM " GLEWLWYD_TABLE_CLIENT_USER_SCOPE " WHERE gs_id IN (SELECT gs_id FROM " GLEWLWYD_TABLE_SCOPE " WHERE gs_name IN (%s)) AND gcus_username=%s AND gcus_client_id=%s AND gcus_enabled=1)", scope_name_list, username_escaped, client_id_escaped); j_query = json_pack("{sss[ssss]s{s{ssss}}}", "table", GLEWLWYD_TABLE_SCOPE, "columns", "gs_name AS name", "gs_display_name AS display_name", "gs_description AS description", "gs_password_required", "where", "gs_id", "operator", "raw", "value", scope_clause); o_free(scope_name_list); o_free(username_escaped); o_free(client_id_escaped); o_free(scope_clause); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result, index, j_element) { json_object_set(j_element, "password_required", json_integer_value(json_object_get(j_element, "gs_password_required"))?json_true():json_false()); json_object_del(j_element, "gs_password_required"); } j_return = json_pack("{sisO}", "result", G_OK, "scope", j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_user_scope_grant - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_user_scope_grant - Error scope_name_list"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_user_scope_grant - Error split_string"); j_return = json_pack("{si}", "result", G_ERROR); } free_string_array(scope_array); return j_return; } json_t * get_granted_scopes_for_client(struct config_elements * config, json_t * j_user, const char * client_id, const char * scope_list) { json_t * j_scope_list, * j_element, * j_scope, * j_client, * j_return; char ** scope_array; int i, found; size_t index; j_client = get_client(config, client_id, NULL); if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true()) { j_scope_list = get_client_user_scope_grant(config, client_id, json_string_value(json_object_get(j_user, "username")), scope_list); if (check_result_value(j_scope_list, G_OK)) { if (split_string(scope_list, " ", &scope_array) > 0) { for (i=0; scope_array[i] != NULL; i++) { found = 0; json_array_foreach(json_object_get(j_scope_list, "scope"), index, j_element) { if (0 == o_strcmp(json_string_value(json_object_get(j_element, "name")), scope_array[i])) { json_object_set(j_element, "granted", json_true()); found = 1; } } if (!found) { json_array_foreach(json_object_get(j_user, "scope"), index, j_element) { if (0 == o_strcmp(scope_array[i], json_string_value(j_element))) { j_scope = get_scope(config, scope_array[i]); if (check_result_value(j_scope, G_OK)) { json_object_set(json_object_get(j_scope, "scope"), "granted", json_false()); json_array_append(json_object_get(j_scope_list, "scope"), json_object_get(j_scope, "scope")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_user_session_scope_grant - Error get_scope"); } json_decref(j_scope); } } } } j_return = json_pack("{sis{s{sOsO*}sO}}", "result", G_OK, "grant", "client", "client_id", json_object_get(json_object_get(j_client, "client"), "client_id"), "name", json_object_get(json_object_get(j_client, "client"), "name"), "scope", json_object_get(j_scope_list, "scope")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_user_session_scope_grant - Error split_string"); j_return = json_pack("{si}", "result", G_ERROR); } free_string_array(scope_array); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_user_session_scope_grant - Error get_client_user_scope_grant"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_scope_list); } else if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") != json_true()) { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } else if (check_result_value(j_client, G_ERROR_NOT_FOUND)) { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_user_session_scope_grant - Error get_client"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_client); return j_return; } json_t * get_client_grant_list(struct config_elements * config, const char * username, size_t offset, size_t limit) { json_t * j_query, * j_result = NULL, * j_result_scope = NULL, * j_return, * j_client, * j_element = NULL; int res; size_t index = 0; char * scope_clause, * username_escaped, * client_id_escaped; j_query = json_pack("{sss[s]s{sssi}siss}", "table", GLEWLWYD_TABLE_CLIENT_USER_SCOPE, "columns", "DISTINCT(gcus_client_id) AS client_id", "where", "gcus_username", username, "gcus_enabled", 1, "offset", offset, "order_by", "client_id"); if (limit) { json_object_set_new(j_query, "limit", json_integer(limit)); } res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { j_return = json_pack("{sis[]}", "result", G_OK, "client_grant"); json_array_foreach(j_result, index, j_element) { j_client = get_client(config, json_string_value(json_object_get(j_element, "client_id")), NULL); if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true()) { username_escaped = h_escape_string_with_quotes(config->conn, username); client_id_escaped = h_escape_string_with_quotes(config->conn, json_string_value(json_object_get(j_element, "client_id"))); scope_clause = msprintf("IN (SELECT gs_id FROM " GLEWLWYD_TABLE_CLIENT_USER_SCOPE " WHERE gcus_username=%s AND gcus_client_id=%s AND gcus_enabled=1)", username_escaped, client_id_escaped); j_query = json_pack("{sss[sss]s{s{ssss}}}", "table", GLEWLWYD_TABLE_SCOPE, "columns", "gs_name AS name", "gs_display_name AS display_name", "gs_description AS description", "where", "gs_id", "operator", "raw", "value", scope_clause); o_free(scope_clause); o_free(client_id_escaped); o_free(username_escaped); res = h_select(config->conn, j_query, &j_result_scope, NULL); json_decref(j_query); if (res == H_OK) { json_array_append_new(json_object_get(j_return, "client_grant"), json_pack("{sOsOsOsO}", "client_id", json_object_get(json_object_get(j_client, "client"), "client_id"), "name", json_object_get(json_object_get(j_client, "client"), "name"), "description", json_object_get(json_object_get(j_client, "client"), "description"), "scope", j_result_scope)); json_decref(j_result_scope); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_grant_list - Error executing j_query (2) for client_id '%s'", json_string_value(json_object_get(j_element, "client_id"))); } } else if (!check_result_value(j_client, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_grant_list - Error get_client for client_id '%s'", json_string_value(json_object_get(j_element, "client_id"))); } json_decref(j_client); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_client_grant_list - Error executing j_query (1)"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } int set_granted_scopes_for_client(struct config_elements * config, json_t * j_user, const char * client_id, const char * scope_list) { json_t * j_query, * j_element; char * scope_clause = NULL, * scope_escaped, ** scope_array = NULL; int res, ret = G_OK, i, has_granted; size_t index; j_query = json_pack("{sss{si}s{ssss}}", "table", GLEWLWYD_TABLE_CLIENT_USER_SCOPE, "set", "gcus_enabled", 0, "where", "gcus_username", json_string_value(json_object_get(j_user, "username")), "gcus_client_id", client_id); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { if (scope_list != NULL && o_strlen(scope_list)) { if (split_string(scope_list, " ", &scope_array) > 0) { has_granted = 0; for (i=0; scope_array[i] != NULL && ret != G_ERROR_DB; i++) { json_array_foreach(json_object_get(j_user, "scope"), index, j_element) { if (0 == o_strcmp(scope_array[i], json_string_value(j_element)) && ret != G_ERROR_DB) { has_granted = 1; scope_escaped = h_escape_string_with_quotes(config->conn, scope_array[i]); scope_clause = msprintf("(SELECT gs_id FROM " GLEWLWYD_TABLE_SCOPE " WHERE gs_name=%s)", scope_escaped); j_query = json_pack("{sss{s{ss}ssss}}", "table", GLEWLWYD_TABLE_CLIENT_USER_SCOPE, "values", "gs_id", "raw", scope_clause, "gcus_username", json_string_value(json_object_get(j_user, "username")), "gcus_client_id", client_id); o_free(scope_clause); res = h_insert(config->conn, j_query, NULL); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "set_granted_scopes_for_client - Error executing j_query (2)"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } json_decref(j_query); o_free(scope_escaped); } } } if (!has_granted) { ret = G_ERROR_UNAUTHORIZED; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_granted_scopes_for_client - Error split_string"); } free_string_array(scope_array); } else { ret = G_OK; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_granted_scopes_for_client - Error executing j_query (1)"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } json_t * get_scope_list_allowed_for_session(struct config_elements * config, const char * scope_list, const char * session_uid) { json_t * j_scheme_list = get_validated_auth_scheme_list_from_scope_list(config, scope_list, session_uid), * j_scope, * j_group, * j_scheme, * j_scope_allowed = NULL; int ret, scope_allowed; const char * scope, * group; size_t index; json_int_t group_allowed; if (check_result_value(j_scheme_list, G_OK)) { j_scope_allowed = json_array(); if (j_scope_allowed != NULL) { ret = G_OK; // Iterate in each scopes json_object_foreach(json_object_get(j_scheme_list, "scheme"), scope, j_scope) { scope_allowed = 1; if (json_object_get(j_scope, "available") == json_true()) { if (json_object_get(j_scope, "password_required") == json_true() && json_object_get(j_scope, "password_authenticated") == json_false()) { ret = G_ERROR_UNAUTHORIZED; scope_allowed = 0; } else { json_object_foreach(json_object_get(j_scope, "schemes"), group, j_group) { group_allowed = 0; json_array_foreach(j_group, index, j_scheme) { if (json_object_get(j_scheme, "scheme_authenticated") == json_true()) { group_allowed++; } } if (group_allowed < json_integer_value(json_object_get(json_object_get(j_scope, "scheme_required"), group))) { ret = G_ERROR_UNAUTHORIZED; scope_allowed = 0; } } } if (scope_allowed) { json_array_append_new(j_scope_allowed, json_string(scope)); } } } if (ret == G_OK && !json_array_size(j_scope_allowed)) { ret = G_ERROR_UNAUTHORIZED; json_decref(j_scope_allowed); j_scope_allowed = NULL; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_scope_list_allowed_for_session - Error iallocating resources for j_scope_allowed"); ret = G_ERROR_MEMORY; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_scope_list_allowed_for_session - Error get_validated_auth_scheme_list_from_scope_list"); ret = G_ERROR; } json_decref(j_scheme_list); if (ret == G_OK) { return json_pack("{sisO}", "result", ret, "scope", j_scope_allowed); } else { return json_pack("{si}", "result", ret); } } json_t * is_scope_valid(struct config_elements * config, json_t * j_scope, int add) { json_t * j_return, * j_array, * j_group, * j_scheme, * j_module; size_t index, nb_scheme; const char * key; char * message; if (json_is_object(j_scope)) { j_array = json_array(); if (j_array != NULL) { if (add) { if (!json_is_string(json_object_get(j_scope, "name")) || !json_string_length(json_object_get(j_scope, "name")) || json_string_length(json_object_get(j_scope, "name")) > 128) { json_array_append_new(j_array, json_string("name is mandatory and must be string between 1 and 128 characters")); } } if (json_object_get(j_scope, "display_name") != NULL && (!json_is_string(json_object_get(j_scope, "display_name")) || !json_string_length(json_object_get(j_scope, "display_name")) || json_string_length(json_object_get(j_scope, "display_name")) > 256)) { json_array_append_new(j_array, json_string("display_name is optional and must be string between 1 and 256 characters")); } if (json_object_get(j_scope, "description") != NULL && (!json_is_string(json_object_get(j_scope, "description")) || !json_string_length(json_object_get(j_scope, "description")) || json_string_length(json_object_get(j_scope, "description")) > 512)) { json_array_append_new(j_array, json_string("description is optional and must be string between 1 and 512 characters")); } if (json_object_get(j_scope, "password_required") != NULL && !json_is_boolean(json_object_get(j_scope, "password_required"))) { json_array_append_new(j_array, json_string("password_required is optional and must be a boolean")); } if (json_object_get(j_scope, "password_max_age") != NULL && (!json_is_integer(json_object_get(j_scope, "password_max_age")) || json_integer_value(json_object_get(j_scope, "password_max_age")) < 0)) { json_array_append_new(j_array, json_string("password_max_age is optional and must be a positive integer")); } if (json_object_get(j_scope, "scheme_required") != NULL && !json_is_object(json_object_get(j_scope, "scheme_required"))) { json_array_append_new(j_array, json_string("scheme_required is optional and must be a JSON object")); } else if (json_object_get(j_scope, "scheme") != NULL && !json_is_object(json_object_get(j_scope, "scheme"))) { json_array_append_new(j_array, json_string("scheme is optional and must be a JSON object")); } else { json_object_foreach(json_object_get(j_scope, "scheme"), key, j_group) { nb_scheme = json_array_size(j_group); if (!json_is_array(j_group) || !json_array_size(j_group)) { json_array_append_new(j_array, json_string("scheme group must be a non empty JSON array")); } else if (json_object_get(json_object_get(j_scope, "scheme_required"), key) != NULL && (json_integer_value(json_object_get(json_object_get(j_scope, "scheme_required"), key)) < 1 || json_integer_value(json_object_get(json_object_get(j_scope, "scheme_required"), key)) > (json_int_t)nb_scheme)) { json_array_append_new(j_array, json_string("scheme_required group value must be an integer between 1 and the number of schemes for the schemes in the group")); } else { json_array_foreach(j_group, index, j_scheme) { if (!json_is_object(j_scheme) || !json_object_size(j_scheme)) { json_array_append_new(j_array, json_string("scheme must be a non empty JSON object")); } else { if (!json_is_string(json_object_get(j_scheme, "scheme_name")) || !json_string_length(json_object_get(j_scheme, "scheme_name"))) { json_array_append_new(j_array, json_string("scheme_name must be a non empty string")); } else { j_module = get_user_auth_scheme_module(config, json_string_value(json_object_get(j_scheme, "scheme_name"))); if (check_result_value(j_module, G_ERROR_NOT_FOUND)) { message = msprintf("scheme_name '%s' does not exist", json_string_value(json_object_get(j_scheme, "scheme_name"))); json_array_append_new(j_array, json_string(message)); o_free(message); } else if (!check_result_value(j_module, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "is_scope_valid - Error get_user_auth_scheme_module"); } json_decref(j_module); } } } } } } if (json_array_size(j_array)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_array); } else { j_return = json_pack("{si}", "result", G_OK); } json_decref(j_array); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_scope_valid - Error allocating resources for j_array"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "Parameter must be a JSON object"); } return j_return; } static int add_scope_scheme_groups(struct config_elements * config, const char * scope, json_t * j_scheme, json_t * j_scheme_required) { json_t * j_query, * j_scope_group, * j_scope_group_id, * j_scheme_module; int res, ret = G_OK; char * scope_escaped, * scope_clause, * scheme_escaped, * scheme_module_clause; const char * group_name; size_t index; scope_escaped = h_escape_string_with_quotes(config->conn, scope); scope_clause = msprintf("(SELECT gs_id FROM " GLEWLWYD_TABLE_SCOPE " WHERE gs_name=%s)", scope_escaped); json_object_foreach(j_scheme, group_name, j_scope_group) { j_query = json_pack("{sss{s{ss}ss}}", "table", GLEWLWYD_TABLE_SCOPE_GROUP, "values", "gs_id", "raw", scope_clause, "gsg_name", group_name); if (json_object_get(j_scheme_required, group_name) != NULL) { json_object_set(json_object_get(j_query, "values"), "gsg_scheme_required", json_object_get(j_scheme_required, group_name)); } res = h_insert(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_scope_group_id = h_last_insert_id(config->conn); if (j_scope_group_id != NULL && json_integer_value(j_scope_group_id) > 0) { json_array_foreach(j_scope_group, index, j_scheme_module) { scheme_escaped = h_escape_string_with_quotes(config->conn, json_string_value(json_object_get(j_scheme_module, "scheme_name"))); scheme_module_clause = msprintf("(SELECT guasmi_id FROM " GLEWLWYD_TABLE_USER_AUTH_SCHEME_MODULE_INSTANCE " WHERE guasmi_name=%s)", scheme_escaped); j_query = json_pack("{sss{sOs{ss}}}", "table", GLEWLWYD_TABLE_SCOPE_GROUP_AUTH_SCHEME_MODULE_INSTANCE, "values", "gsg_id", j_scope_group_id, "guasmi_id", "raw", scheme_module_clause); o_free(scheme_module_clause); o_free(scheme_escaped); res = h_insert(config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "add_scope_scheme_groups - Error executing j_query (2)"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_scope_scheme_groups - Error h_last_insert_id"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } json_decref(j_scope_group_id); } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_scope_scheme_groups - Error executing j_query (1)"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } o_free(scope_escaped); o_free(scope_clause); return ret; } int add_scope(struct config_elements * config, json_t * j_scope) { json_t * j_query; int res, ret; j_query = json_pack("{sss{sOsOsOsisI}}", "table", GLEWLWYD_TABLE_SCOPE, "values", "gs_name", json_object_get(j_scope, "name"), "gs_display_name", json_object_get(j_scope, "display_name")!=NULL?json_object_get(j_scope, "display_name"):json_null(), "gs_description", json_object_get(j_scope, "description")!=NULL?json_object_get(j_scope, "description"):json_null(), "gs_password_required", json_object_get(j_scope, "password_required")==json_false()?0:1, "gs_password_max_age", json_object_get(j_scope, "password_max_age")!=NULL?json_integer_value(json_object_get(j_scope, "password_max_age")):0); res = h_insert(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { if (json_object_get(j_scope, "scheme") != NULL && json_object_size(json_object_get(j_scope, "scheme"))) { if (add_scope_scheme_groups(config, json_string_value(json_object_get(j_scope, "name")), json_object_get(j_scope, "scheme"), json_object_get(j_scope, "scheme_required")) == G_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_scope - Error add_scope_scheme_groups"); ret = G_ERROR; } } else { ret = G_OK; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_scope - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } int set_scope(struct config_elements * config, const char * scope, json_t * j_scope) { json_t * j_query; char * scope_escaped, * scope_clause; int res, ret; scope_escaped = h_escape_string_with_quotes(config->conn, scope); scope_clause = msprintf("IN (SELECT gs_id FROM " GLEWLWYD_TABLE_SCOPE " WHERE gs_name=%s)", scope_escaped); j_query = json_pack("{sss{s{ssss}}}", "table", GLEWLWYD_TABLE_SCOPE_GROUP, "where", "gs_id", "operator", "raw", "value", scope_clause); o_free(scope_clause); o_free(scope_escaped); res = h_delete(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_query = json_pack("{sss{sOsOsisI}s{ss}}", "table", GLEWLWYD_TABLE_SCOPE, "set", "gs_display_name", json_object_get(j_scope, "display_name")!=NULL?json_object_get(j_scope, "display_name"):json_null(), "gs_description", json_object_get(j_scope, "description")!=NULL?json_object_get(j_scope, "description"):json_null(), "gs_password_required", json_object_get(j_scope, "password_required")==json_false()?0:1, "gs_password_max_age", json_object_get(j_scope, "password_max_age")!=NULL?json_integer_value(json_object_get(j_scope, "password_max_age")):0, "where", "gs_name", scope); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { if (add_scope_scheme_groups(config, scope, json_object_get(j_scope, "scheme"), json_object_get(j_scope, "scheme_required")) == G_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_scope - Error add_scope_scheme_groups"); ret = G_ERROR; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_scope - Error executing j_query (2)"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_scope - Error executing j_query (1)"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } int delete_scope(struct config_elements * config, const char * scope) { json_t * j_query; int res, ret; j_query = json_pack("{sss{ss}}", "table", GLEWLWYD_TABLE_SCOPE, "where", "gs_name", scope); res = h_delete(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_scope - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } glewlwyd-2.6.1/src/session.c000066400000000000000000001112301415646314000157760ustar00rootroot00000000000000/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * HTTP Session functions definition * * Copyright 2016-2021 Nicolas Mora * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . * */ #include "glewlwyd.h" json_t * get_session_scheme(struct config_elements * config, json_int_t gus_id) { json_t * j_query, * j_result, * j_return; int res; char * expire_clause; if (config->conn->type==HOEL_DB_TYPE_MARIADB) { expire_clause = o_strdup("> NOW()"); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { expire_clause = o_strdup("> NOW()"); } else { // HOEL_DB_TYPE_SQLITE expire_clause = o_strdup("> (strftime('%s','now'))"); } j_query = json_pack("{sss[ss]s{sIsis{ssss}}}", "table", GLEWLWYD_TABLE_USER_SESSION_SCHEME, "columns", "guasmi_id", SWITCH_DB_TYPE(config->conn->type, "UNIX_TIMESTAMP(guss_expiration) AS expiration", "guss_expiration AS expiration", "EXTRACT(EPOCH FROM guss_expiration)::integer AS expiration"), "where", "gus_id", gus_id, "guss_enabled", 1, "guss_expiration", "operator", "raw", "value", expire_clause); o_free(expire_clause); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { j_return = json_pack("{siso}", "result", G_OK, "scheme", j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_session_scheme - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } json_t * get_session_for_username(struct config_elements * config, const char * session_uid, const char * username) { json_t * j_query, * j_result, * j_return, * j_session_scheme; int res; char * expire_clause; char * session_uid_hash = generate_hash(config->hash_algorithm, session_uid); if (config->conn->type==HOEL_DB_TYPE_MARIADB) { expire_clause = o_strdup("> NOW()"); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { expire_clause = o_strdup("> NOW()"); } else { // HOEL_DB_TYPE_SQLITE expire_clause = o_strdup("> (strftime('%s','now'))"); } if (session_uid_hash != NULL) { j_query = json_pack("{sss[ss]s{sssssis{ssss}}}", "table", GLEWLWYD_TABLE_USER_SESSION, "columns", "gus_id", SWITCH_DB_TYPE(config->conn->type, "UNIX_TIMESTAMP(gus_expiration) AS expiration", "gus_expiration AS expiration", "EXTRACT(EPOCH FROM gus_expiration)::integer AS expiration"), "where", "gus_session_hash", session_uid_hash, "gus_username", username, "gus_enabled", 1, "gus_expiration", "operator", "raw", "value", expire_clause); o_free(expire_clause); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result) > 0) { j_session_scheme = get_session_scheme(config, json_integer_value(json_object_get(json_array_get(j_result, 0), "gus_id"))); if (check_result_value(j_session_scheme, G_OK)) { j_return = json_pack("{sis{sssOsOsI}}", "result", G_OK, "session", "username", username, "expiration", json_object_get(json_array_get(j_result, 0), "expiration"), "scheme", json_object_get(j_session_scheme, "scheme"), "gus_id", json_integer_value(json_object_get(json_array_get(j_result, 0), "gus_id"))); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_session_for_username - Error get_session_scheme"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_session_scheme); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_session_for_username - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } o_free(session_uid_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_session_for_username - Error generate_hash"); j_return = json_pack("{si}", "result", G_ERROR); } return j_return; } json_t * get_users_for_session(struct config_elements * config, const char * session_uid) { json_t * j_query, * j_result, * j_return, * j_element, * j_user, * j_session_array; int res; size_t index; char * expire_clause, * session_uid_hash; if (session_uid != NULL && o_strlen(session_uid)) { if (config->conn->type==HOEL_DB_TYPE_MARIADB) { expire_clause = o_strdup("> NOW()"); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { expire_clause = o_strdup("> NOW()"); } else { // HOEL_DB_TYPE_SQLITE expire_clause = o_strdup("> (strftime('%s','now'))"); } session_uid_hash = generate_hash(config->hash_algorithm, session_uid); if (session_uid_hash != NULL) { j_query = json_pack("{sss[ss]s{sssis{ssss}}ss}", "table", GLEWLWYD_TABLE_USER_SESSION, "columns", "gus_username", SWITCH_DB_TYPE(config->conn->type, "UNIX_TIMESTAMP(gus_last_login) AS last_login", "gus_last_login AS last_login", "EXTRACT(EPOCH FROM gus_last_login)::integer AS last_login"), "where", "gus_session_hash", session_uid_hash, "gus_enabled", 1, "gus_expiration", "operator", "raw", "value", expire_clause, "order_by", "gus_current DESC"); o_free(expire_clause); o_free(session_uid_hash); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result) > 0) { j_session_array = json_array(); if (j_session_array != NULL) { json_array_foreach(j_result, index, j_element) { j_user = get_user_profile(config, json_string_value(json_object_get(j_element, "gus_username")), NULL); if (check_result_value(j_user, G_OK) && json_object_get(json_object_get(j_user, "user"), "enabled") == json_true()) { json_object_set(json_object_get(j_user, "user"), "last_login", json_object_get(j_element, "last_login")); json_array_append(j_session_array, json_object_get(j_user, "user")); } else if (!check_result_value(j_user, G_ERROR_NOT_FOUND) && !check_result_value(j_user, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "get_users_for_session - Error get_user_profile"); } json_decref(j_user); } if (json_array_size(j_session_array)) { j_return = json_pack("{sisO}", "result", G_OK, "session", j_session_array); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_users_for_session - Error allocating resources for j_session_array"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } json_decref(j_session_array); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_users_for_session - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_users_for_session - Error generate_hash"); j_return = json_pack("{si}", "result", G_ERROR); } } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } return j_return; } json_t * get_current_user_for_session(struct config_elements * config, const char * session_uid) { json_t * j_query, * j_result, * j_return; int res; char * expire_clause, * session_uid_hash; if (o_strlen(session_uid)) { if (config->conn->type==HOEL_DB_TYPE_MARIADB) { expire_clause = o_strdup("> NOW()"); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { expire_clause = o_strdup("> NOW()"); } else { // HOEL_DB_TYPE_SQLITE expire_clause = o_strdup("> (strftime('%s','now'))"); } session_uid_hash = generate_hash(config->hash_algorithm, session_uid); if (session_uid_hash != NULL) { j_query = json_pack("{sss[ss]s{sssis{ssss}si}sssi}", "table", GLEWLWYD_TABLE_USER_SESSION, "columns", "gus_username", SWITCH_DB_TYPE(config->conn->type, "UNIX_TIMESTAMP(gus_expiration) AS expiration", "gus_expiration AS expiration", "EXTRACT(EPOCH FROM gus_expiration)::integer AS expiration"), "where", "gus_session_hash", session_uid_hash, "gus_enabled", 1, "gus_expiration", "operator", "raw", "value", expire_clause, "gus_current", 1, "order_by", "gus_current DESC", "limit", 1); res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result) > 0) { j_return = get_user(config, json_string_value(json_object_get(json_array_get(j_result, 0), "gus_username")), NULL); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_current_user_for_session - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } } else if (session_uid == NULL) { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_current_user_for_session - Error generate_hash"); j_return = json_pack("{si}", "result", G_ERROR); } o_free(session_uid_hash); o_free(expire_clause); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } return j_return; } int user_session_update(struct config_elements * config, const char * session_uid, const char * user_agent, const char * issued_for, const char * username, const char * scheme_name, int update_login) { json_t * j_query, * j_session = get_session_for_username(config, session_uid, username); struct _user_auth_scheme_module_instance * scheme_instance = NULL; int res, ret; time_t now; char * expiration_clause, * last_login_clause; char * session_uid_hash = generate_hash(config->hash_algorithm, session_uid); time(&now); if (session_uid_hash != NULL) { if (check_result_value(j_session, G_ERROR_NOT_FOUND)) { j_query = json_pack("{sss{si}s{ss}}", "table", GLEWLWYD_TABLE_USER_SESSION, "set", "gus_current", 0, "where", "gus_session_hash", session_uid_hash); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { // Create session for user if not exist j_query = json_pack("{sss{sssssssssi}}", "table", GLEWLWYD_TABLE_USER_SESSION, "values", "gus_session_hash", session_uid_hash, "gus_username", username, "gus_user_agent", user_agent!=NULL?user_agent:"", "gus_issued_for", issued_for!=NULL?issued_for:"", "gus_current", 1); if (update_login) { if (config->conn->type==HOEL_DB_TYPE_MARIADB) { expiration_clause = msprintf("FROM_UNIXTIME(%u)", (now + config->session_expiration)); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { expiration_clause = msprintf("TO_TIMESTAMP(%u)", (now + config->session_expiration)); } else { // HOEL_DB_TYPE_SQLITE expiration_clause = msprintf("%u", (now + config->session_expiration)); } if (config->conn->type==HOEL_DB_TYPE_MARIADB) { last_login_clause = msprintf("FROM_UNIXTIME(%u)", (now)); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { last_login_clause = msprintf("TO_TIMESTAMP(%u)", (now)); } else { // HOEL_DB_TYPE_SQLITE last_login_clause = msprintf("%u", (now)); } json_object_set_new(json_object_get(j_query, "values"), "gus_last_login", json_pack("{ss}", "raw", last_login_clause)); json_object_set_new(json_object_get(j_query, "values"), "gus_expiration", json_pack("{ss}", "raw", expiration_clause)); o_free(last_login_clause); o_free(expiration_clause); } res = h_insert(config->conn, j_query, NULL); json_decref(j_query); json_decref(j_session); if (res == H_OK) { j_session = get_session_for_username(config, session_uid, username); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_session_update - Error h_insert session"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_session = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_session_update - Error h_update session (0)"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_session = json_pack("{si}", "result", G_ERROR_DB); } } else { j_query = json_pack("{sss{si}s{ss}}", "table", GLEWLWYD_TABLE_USER_SESSION, "set", "gus_current", 0, "where", "gus_session_hash", session_uid_hash); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_query = json_pack("{sss{sssi}s{ssss}}", "table", GLEWLWYD_TABLE_USER_SESSION, "set", "gus_user_agent", user_agent!=NULL?user_agent:"", "gus_current", 1, "where", "gus_session_hash", session_uid_hash, "gus_username", username); if (update_login) { // Refresh session for user if (config->conn->type==HOEL_DB_TYPE_MARIADB) { expiration_clause = msprintf("FROM_UNIXTIME(%u)", (now + config->session_expiration)); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { expiration_clause = msprintf("TO_TIMESTAMP(%u)", (now + config->session_expiration)); } else { // HOEL_DB_TYPE_SQLITE expiration_clause = msprintf("%u", (now + config->session_expiration)); } if (config->conn->type==HOEL_DB_TYPE_MARIADB) { last_login_clause = msprintf("FROM_UNIXTIME(%u)", (now)); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { last_login_clause = msprintf("TO_TIMESTAMP(%u)", (now)); } else { // HOEL_DB_TYPE_SQLITE last_login_clause = msprintf("%u", (now)); } json_object_set_new(json_object_get(j_query, "set"), "gus_last_login", json_pack("{ss}", "raw", last_login_clause)); json_object_set_new(json_object_get(j_query, "set"), "gus_expiration", json_pack("{ss}", "raw", expiration_clause)); o_free(last_login_clause); o_free(expiration_clause); } res = h_update(config->conn, j_query, NULL); json_decref(j_query); json_decref(j_session); if (res == H_OK) { j_session = get_session_for_username(config, session_uid, username); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_session_update - Error h_update session (2)"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_session = json_pack("{si}", "result", G_ERROR_DB); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_session_update - Error h_update session (1)"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_session = json_pack("{si}", "result", G_ERROR_DB); } } if (check_result_value(j_session, G_OK)) { if (update_login) { if (scheme_name != NULL) { scheme_instance = get_user_auth_scheme_module_instance(config, scheme_name); if (scheme_instance != NULL && scheme_instance->enabled) { // Disable all session schemes with this scheme instance j_query = json_pack("{sss{si}s{sOsI}}", "table", GLEWLWYD_TABLE_USER_SESSION_SCHEME, "set", "guss_enabled", 0, "where", "gus_id", json_object_get(json_object_get(j_session, "session"), "gus_id"), "guasmi_id", scheme_instance->guasmi_id); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { // Set session scheme for this scheme with the timeout if (config->conn->type==HOEL_DB_TYPE_MARIADB) { expiration_clause = msprintf("FROM_UNIXTIME(%u)", (now + (unsigned int)scheme_instance->guasmi_expiration)); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { expiration_clause = msprintf("TO_TIMESTAMP(%u)", (now + (unsigned int)scheme_instance->guasmi_expiration)); } else { // HOEL_DB_TYPE_SQLITE expiration_clause = msprintf("%u", (now + (unsigned int)scheme_instance->guasmi_expiration)); } j_query = json_pack("{sss{sOsIs{ss}}}", "table", GLEWLWYD_TABLE_USER_SESSION_SCHEME, "values", "gus_id", json_object_get(json_object_get(j_session, "session"), "gus_id"), "guasmi_id", scheme_instance->guasmi_id, "guss_expiration", "raw", expiration_clause); if (update_login) { if (config->conn->type==HOEL_DB_TYPE_MARIADB) { last_login_clause = msprintf("FROM_UNIXTIME(%u)", (now)); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { last_login_clause = msprintf("TO_TIMESTAMP(%u)", (now)); } else { // HOEL_DB_TYPE_SQLITE last_login_clause = msprintf("%u", (now)); } json_object_set_new(json_object_get(j_query, "values"), "guss_last_login", json_pack("{ss}", "raw", last_login_clause)); o_free(last_login_clause); } o_free(expiration_clause); res = h_insert(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_session_update - Error executing j_query (1)"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_session_update - Error executing j_query (2)"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { ret = G_ERROR_PARAM; } } else { // Disable all session schemes with the scheme password j_query = json_pack("{sss{si}s{sOsn}}", "table", GLEWLWYD_TABLE_USER_SESSION_SCHEME, "set", "guss_enabled", 0, "where", "gus_id", json_object_get(json_object_get(j_session, "session"), "gus_id"), "guasmi_id"); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { // Set session scheme password with the timeout if (config->conn->type==HOEL_DB_TYPE_MARIADB) { expiration_clause = msprintf("FROM_UNIXTIME(%u)", (now + GLEWLWYD_RESET_PASSWORD_DEFAULT_SESSION_EXPIRATION)); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { expiration_clause = msprintf("TO_TIMESTAMP(%u)", (now + GLEWLWYD_RESET_PASSWORD_DEFAULT_SESSION_EXPIRATION)); } else { // HOEL_DB_TYPE_SQLITE expiration_clause = msprintf("%u", (now + GLEWLWYD_RESET_PASSWORD_DEFAULT_SESSION_EXPIRATION)); } j_query = json_pack("{sss{sOsns{ss}}}", "table", GLEWLWYD_TABLE_USER_SESSION_SCHEME, "values", "gus_id", json_object_get(json_object_get(j_session, "session"), "gus_id"), "guasmi_id", "guss_expiration", "raw", expiration_clause); if (update_login) { if (config->conn->type==HOEL_DB_TYPE_MARIADB) { last_login_clause = msprintf("FROM_UNIXTIME(%u)", (now)); } else if (config->conn->type==HOEL_DB_TYPE_PGSQL) { last_login_clause = msprintf("TO_TIMESTAMP(%u)", (now)); } else { // HOEL_DB_TYPE_SQLITE last_login_clause = msprintf("%u", (now)); } json_object_set_new(json_object_get(j_query, "values"), "guss_last_login", json_pack("{ss}", "raw", last_login_clause)); o_free(last_login_clause); } o_free(expiration_clause); res = h_insert(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_session_update - Error executing j_query (3)"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_session_update - Error executing j_query (4)"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } } else { ret = G_OK; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_session_update - Error get_session_for_username"); ret = G_ERROR; } o_free(session_uid_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_session_update - Error generate_hash"); ret = G_ERROR; } json_decref(j_session); return ret; } int user_session_delete(struct config_elements * config, const char * session_uid, const char * username) { json_t * j_query; int res, ret; char * session_uid_hash = generate_hash(config->hash_algorithm, session_uid); if (session_uid_hash != NULL) { j_query = json_pack("{sss{sisi}s{ss}}", "table", GLEWLWYD_TABLE_USER_SESSION, "set", "gus_enabled", 0, "gus_current", 0, "where", "gus_session_hash", session_uid_hash); if (username != NULL) { json_object_set_new(json_object_get(j_query, "where"), "gus_username", json_string(username)); } res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { if (username != NULL) { j_query = json_pack("{sss{si}s{siss}si}", "table", GLEWLWYD_TABLE_USER_SESSION, "set", "gus_current", 1, "where", "gus_enabled", 1, "gus_session_hash", session_uid_hash, "limit", 1); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_session_delete - Error executing j_query (2)"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { ret = G_OK; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_session_delete - Error executing j_query (1)"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } o_free(session_uid_hash); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_session_delete - Error generate_hash"); ret = G_ERROR; } return ret; } char * get_session_id(struct config_elements * config, const struct _u_request * request) { if (o_strlen(u_map_get(request->map_cookie, config->session_key)) == GLEWLWYD_SESSION_ID_LENGTH) { return o_strdup(u_map_get(request->map_cookie, config->session_key)); } else { return NULL; } } char * generate_session_id() { char session_id_str_array[GLEWLWYD_SESSION_ID_LENGTH + 1] = {}; return o_strdup(rand_string(session_id_str_array, GLEWLWYD_SESSION_ID_LENGTH)); } json_t * get_user_session_list(struct config_elements * config, const char * username, const char * pattern, size_t offset, size_t limit, const char * sort) { json_t * j_query, * j_result, * j_return, * j_element; int res; size_t index, session_hash_url_len = 0; char * pattern_escaped, * pattern_clause; unsigned char session_hash_url[128]; j_query = json_pack("{sss[ssssss]s{ss}sisiss}", "table", GLEWLWYD_TABLE_USER_SESSION, "columns", "gus_session_hash", "gus_user_agent AS user_agent", "gus_issued_for AS issued_for", SWITCH_DB_TYPE(config->conn->type, "UNIX_TIMESTAMP(gus_expiration) AS expiration", "gus_expiration AS expiration", "EXTRACT(EPOCH FROM gus_expiration)::integer AS expiration"), SWITCH_DB_TYPE(config->conn->type, "UNIX_TIMESTAMP(gus_last_login) AS last_login", "gus_last_login AS last_login", "EXTRACT(EPOCH FROM gus_last_login)::integer AS last_login"), "gus_enabled", "where", "gus_username", username, "offset", offset, "limit", limit, "order_by", "gus_last_login DESC"); if (sort != NULL) { json_object_set_new(j_query, "order_by", json_string(sort)); } if (pattern != NULL) { pattern_escaped = h_escape_string_with_quotes(config->conn, pattern); pattern_clause = msprintf("IN (SELECT gus_id FROM "GLEWLWYD_TABLE_USER_SESSION" WHERE gus_user_agent LIKE '%%'||%s||'%%' OR gus_issued_for LIKE '%%'||%s||'%%')", pattern_escaped, pattern_escaped); json_object_set_new(json_object_get(j_query, "where"), "gus_id", json_pack("{ssss}", "operator", "raw", "value", pattern_clause)); o_free(pattern_clause); o_free(pattern_escaped); } res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result, index, j_element) { json_object_set_new(j_element, "enabled", json_integer_value(json_object_get(j_element, "gus_enabled"))?json_true():json_false()); json_object_del(j_element, "gus_enabled"); if (o_base64_2_base64url((unsigned char *)json_string_value(json_object_get(j_element, "gus_session_hash")), json_string_length(json_object_get(j_element, "gus_session_hash")), session_hash_url, &session_hash_url_len)) { json_object_set_new(j_element, "session_hash", json_stringn((char *)session_hash_url, session_hash_url_len)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_session_list - Error o_base64_2_base64url"); json_object_set_new(j_element, "session_hash", json_string("error")); } json_object_del(j_element, "gus_session_hash"); } j_return = json_pack("{sisO}", "result", G_OK, "session", j_result); json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_session_delete - Error executing j_query"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } int delete_user_session_from_hash(struct config_elements * config, const char * username, const char * session_hash) { json_t * j_query, * j_result, * j_element = NULL; int res, ret = G_OK; unsigned char session_hash_dec[128]; size_t session_hash_dec_len = 0, index = 0; j_query = json_pack("{sss[s]s{ss}}", "table", GLEWLWYD_TABLE_USER_SESSION, "columns", "gus_id", "where", "gus_username", username); if (session_hash != NULL) { if (o_base64url_2_base64((unsigned char *)session_hash, o_strlen(session_hash), session_hash_dec, &session_hash_dec_len)) { json_object_set_new(json_object_get(j_query, "where"), "gus_session_hash", json_stringn((const char *)session_hash_dec, session_hash_dec_len)); } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_user_session_from_hash - Error o_base64url_2_base64"); ret = G_ERROR_PARAM; } } if (ret == G_OK) { res = h_select(config->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { json_array_foreach(j_result, index, j_element) { j_query = json_pack("{sss{si}s{sO}}", "table", GLEWLWYD_TABLE_USER_SESSION, "set", "gus_enabled", 0, "where", "gus_id", json_object_get(j_element, "gus_id")); res = h_update(config->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "delete_user_session_from_hash - Error executing j_query (2)"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } } else { ret = G_ERROR_NOT_FOUND; } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_user_session_from_hash - Error executing j_query (1)"); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } return ret; } json_t * get_scheme_list_for_user(struct config_elements * config, const char * username) { json_t * j_scheme_modules = get_user_auth_scheme_module_list(config), * j_return, * j_module_array = NULL, * j_element = NULL; size_t index = 0; struct _user_auth_scheme_module_instance * instance = NULL; int can_use, has_scheme; if (check_result_value(j_scheme_modules, G_OK)) { j_module_array = json_array(); if (j_module_array != NULL) { json_array_foreach(json_object_get(j_scheme_modules, "module"), index, j_element) { instance = get_user_auth_scheme_module_instance(config, json_string_value(json_object_get(j_element, "name"))); if (instance != NULL) { can_use = instance->module->user_auth_scheme_module_can_use(config->config_m, username, instance->cls); if (can_use != GLEWLWYD_IS_NOT_AVAILABLE) { if ((has_scheme = user_has_scheme(config, username, json_string_value(json_object_get(j_element, "name")))) == G_OK) { json_array_append_new(j_module_array, json_pack("{sOsOsO}", "module", json_object_get(j_element, "module"), "name", json_object_get(j_element, "name"), "display_name", json_object_get(j_element, "display_name"))); } else if (has_scheme != G_ERROR_NOT_FOUND) { y_log_message(Y_LOG_LEVEL_ERROR, "get_scheme_list_for_user - Error user_has_scheme"); } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_scheme_list_for_user - Error instance %s/%s not found", json_string_value(json_object_get(j_element, "module")), json_string_value(json_object_get(j_element, "name"))); } } j_return = json_pack("{sisO}", "result", G_OK, "scheme", j_module_array); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_scheme_list_for_user - Error allocating resources for j_module_array"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } json_decref(j_module_array); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_scheme_list_for_user - Error get_user_auth_scheme_module_list"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_scheme_modules); return j_return; } glewlwyd-2.6.1/src/static_compressed_inmemory_website_callback.c000066400000000000000000000452531415646314000253160ustar00rootroot00000000000000/** * * Static file server with compression Ulfius callback * * Copyright 2020-2021 Nicolas Mora * * Version 20211026 * * The MIT License (MIT) * * 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. * * `files_path`: path to the DocumentRoot folder, can be relative or absolute * `url_prefix`: prefix used to access the callback function * `mime_types`: a `struct _u_map` containing a set of mime-types with file extension as key and mime-type as value * `mime_types_compressed`: A `string_array` structure containing the list of mime-types allowed for compression * `mime_types_compressed_size`: The number of elements in `mime_types_compressed` * `map_header`: a `struct _u_map` containing a set of headers that will be added to all responses within the `static_file_callback` * `redirect_on_404`: redirct uri on error 404, if NULL, send 404 * `allow_gzip`: Set to true if you want to allow gzip compression (default true) * `allow_deflate`: Set to true if you want to allow deflate compression (default true) * `allow_cache_compressed`: set to true if you want to allow memory cache for compressed files (default true) * `lock`: mutex lock (do not touch this variable) * `gzip_files`: a `struct _u_map` containing cached gzip files * `deflate_files`: a `struct _u_map` containing cached deflate files * * example of mime-types used in Hutch: * { * key = ".html" * value = "text/html" * }, * { * key = ".css" * value = "text/css" * }, * { * key = ".js" * value = "application/javascript" * }, * { * key = ".png" * value = "image/png" * }, * { * key = ".jpg" * value = "image/jpeg" * }, * { * key = ".jpeg" * value = "image/jpeg" * }, * { * key = ".ttf" * value = "font/ttf" * }, * { * key = ".woff" * value = "font/woff" * }, * { * key = ".woff2" * value = "font/woff2" * }, * { * key = ".map" * value = "application/octet-stream" * }, * { * key = "*" * value = "application/octet-stream" * } * */ #include #include #include #include #include "static_compressed_inmemory_website_callback.h" #define U_COMPRESS_NONE 0 #define U_COMPRESS_GZIP 1 #define U_COMPRESS_DEFL 2 #define U_ACCEPT_HEADER "Accept-Encoding" #define U_CONTENT_HEADER "Content-Encoding" #define U_ACCEPT_GZIP "gzip" #define U_ACCEPT_DEFLATE "deflate" #define U_GZIP_WINDOW_BITS 15 #define U_GZIP_ENCODING 16 #define CHUNK 0x4000 static void * u_zalloc(void * q, unsigned n, unsigned m) { (void)q; return o_malloc((size_t) n * m); } static void u_zfree(void *q, void *p) { (void)q; o_free(p); } /** * Return the filename extension */ static const char * get_filename_ext(const char *path) { const char *dot = strrchr(path, '.'); if(!dot || dot == path) return "*"; if (strchr(dot, '?') != NULL) { *strchr(dot, '?') = '\0'; } return dot; } /** * Streaming callback function to ease sending large files */ static ssize_t callback_static_file_uncompressed_stream(void * cls, uint64_t pos, char * buf, size_t max) { (void)(pos); if (cls != NULL) { return fread (buf, sizeof(char), max, (FILE *)cls); } else { return U_STREAM_END; } } /** * Cleanup FILE* structure when streaming is complete */ static void callback_static_file_uncompressed_stream_free(void * cls) { if (cls != NULL) { fclose((FILE *)cls); } } /** * static file callback endpoint */ static int callback_static_file_uncompressed (const struct _u_request * request, struct _u_response * response, void * user_data) { size_t length; FILE * f; char * file_requested, * file_path, * url_dup_save; const char * content_type; int ret = U_CALLBACK_CONTINUE; if (user_data != NULL && ((struct _u_compressed_inmemory_website_config *)user_data)->files_path != NULL) { file_requested = o_strdup(request->http_url); url_dup_save = file_requested; file_requested += o_strlen(((struct _u_compressed_inmemory_website_config *)user_data)->url_prefix); while (file_requested[0] == '/') { file_requested++; } if (strchr(file_requested, '#') != NULL) { *strchr(file_requested, '#') = '\0'; } if (strchr(file_requested, '?') != NULL) { *strchr(file_requested, '?') = '\0'; } if (file_requested == NULL || o_strlen(file_requested) == 0 || 0 == o_strcmp("/", file_requested)) { o_free(url_dup_save); url_dup_save = file_requested = o_strdup("index.html"); } file_path = msprintf("%s/%s", ((struct _u_compressed_inmemory_website_config *)user_data)->files_path, file_requested); f = fopen (file_path, "rb"); if (f) { fseek (f, 0, SEEK_END); length = ftell (f); fseek (f, 0, SEEK_SET); content_type = u_map_get_case(&((struct _u_compressed_inmemory_website_config *)user_data)->mime_types, get_filename_ext(file_requested)); if (content_type == NULL) { content_type = u_map_get(&((struct _u_compressed_inmemory_website_config *)user_data)->mime_types, "*"); y_log_message(Y_LOG_LEVEL_WARNING, "Static File Server - Unknown mime type for extension %s", get_filename_ext(file_requested)); } u_map_put(response->map_header, "Content-Type", content_type); u_map_copy_into(response->map_header, &((struct _u_compressed_inmemory_website_config *)user_data)->map_header); if (ulfius_set_stream_response(response, 200, callback_static_file_uncompressed_stream, callback_static_file_uncompressed_stream_free, length, CHUNK, f) != U_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "Static File Server - Error ulfius_set_stream_response"); } } else { if (((struct _u_compressed_inmemory_website_config *)user_data)->redirect_on_404 == NULL) { ret = U_CALLBACK_IGNORE; } else { ulfius_add_header_to_response(response, "Location", ((struct _u_compressed_inmemory_website_config *)user_data)->redirect_on_404); response->status = 302; } } o_free(file_path); o_free(url_dup_save); } else { y_log_message(Y_LOG_LEVEL_ERROR, "Static File Server - Error, user_data is NULL or inconsistent"); ret = U_CALLBACK_ERROR; } return ret; } int u_init_compressed_inmemory_website_config(struct _u_compressed_inmemory_website_config * config) { int ret = U_OK; pthread_mutexattr_t mutexattr; if (config != NULL) { config->files_path = NULL; config->url_prefix = NULL; config->redirect_on_404 = NULL; config->allow_gzip = 1; config->allow_deflate = 1; config->mime_types_compressed = NULL; config->mime_types_compressed_size = 0; config->allow_cache_compressed = 1; if ((ret = u_map_init(&(config->mime_types))) != U_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "u_init_compressed_inmemory_website_config - Error u_map_init mime_types"); } else if ((ret = u_map_init(&(config->map_header))) != U_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "u_init_compressed_inmemory_website_config - Error u_map_init map_header"); } else if ((ret = u_map_init(&(config->gzip_files))) != U_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "u_init_compressed_inmemory_website_config - Error u_map_init gzip_files"); } else if ((ret = u_map_init(&(config->deflate_files))) != U_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "u_init_compressed_inmemory_website_config - Error u_map_init deflate_files"); } else { pthread_mutexattr_init (&mutexattr); pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_RECURSIVE); if (pthread_mutex_init(&(config->lock), &mutexattr) != 0) { y_log_message(Y_LOG_LEVEL_ERROR, "u_init_compressed_inmemory_website_config - Error pthread_mutex_init"); ret = U_ERROR; } } } return ret; } void u_clean_compressed_inmemory_website_config(struct _u_compressed_inmemory_website_config * config) { if (config != NULL) { u_map_clean(&(config->mime_types)); u_map_clean(&(config->map_header)); u_map_clean(&(config->gzip_files)); u_map_clean(&(config->deflate_files)); free_string_array(config->mime_types_compressed); pthread_mutex_destroy(&(config->lock)); } } int u_add_mime_types_compressed(struct _u_compressed_inmemory_website_config * config, const char * mime_type) { int ret; if (config != NULL && o_strlen(mime_type)) { if ((config->mime_types_compressed = o_realloc(config->mime_types_compressed, (config->mime_types_compressed_size+2)*sizeof(char*))) != NULL) { config->mime_types_compressed[config->mime_types_compressed_size] = o_strdup(mime_type); config->mime_types_compressed[config->mime_types_compressed_size+1] = NULL; config->mime_types_compressed_size++; ret = U_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "u_add_mime_types_compressed - Error allocating resources for mime_types_compressed"); ret = U_ERROR; } } else { ret = U_ERROR_PARAMS; } return ret; } int callback_static_compressed_inmemory_website (const struct _u_request * request, struct _u_response * response, void * user_data) { struct _u_compressed_inmemory_website_config * config = (struct _u_compressed_inmemory_website_config *)user_data; char ** accept_list = NULL; int ret = U_CALLBACK_CONTINUE, compress_mode = U_COMPRESS_NONE, res; z_stream defstream; unsigned char * file_content, * file_content_orig = NULL; size_t length, read_length, offset, data_zip_len = 0; FILE * f; char * file_requested, * file_path, * url_dup_save, * data_zip = NULL; const char * content_type; /* * Comment this if statement if you don't access static files url from root dir, like /app */ if (request->callback_position > 0) { return U_CALLBACK_IGNORE; } else { file_requested = o_strdup(request->http_url); url_dup_save = file_requested; while (file_requested[0] == '/') { file_requested++; } file_requested += o_strlen((config->url_prefix)); while (file_requested[0] == '/') { file_requested++; } if (strchr(file_requested, '#') != NULL) { *strchr(file_requested, '#') = '\0'; } if (strchr(file_requested, '?') != NULL) { *strchr(file_requested, '?') = '\0'; } if (file_requested == NULL || o_strlen(file_requested) == 0 || 0 == o_strcmp("/", file_requested)) { o_free(url_dup_save); url_dup_save = file_requested = o_strdup("index.html"); } if (!u_map_has_key_case(response->map_header, U_CONTENT_HEADER)) { if (split_string(u_map_get_case(request->map_header, U_ACCEPT_HEADER), ",", &accept_list)) { if (config->allow_gzip && string_array_has_trimmed_value((const char **)accept_list, U_ACCEPT_GZIP)) { compress_mode = U_COMPRESS_GZIP; } else if (config->allow_deflate && string_array_has_trimmed_value((const char **)accept_list, U_ACCEPT_DEFLATE)) { compress_mode = U_COMPRESS_DEFL; } content_type = u_map_get_case(&config->mime_types, get_filename_ext(file_requested)); if (content_type == NULL) { content_type = u_map_get(&config->mime_types, "*"); y_log_message(Y_LOG_LEVEL_WARNING, "Static File Server - Unknown mime type for extension %s", get_filename_ext(file_requested)); } if (!string_array_has_value((const char **)config->mime_types_compressed, content_type)) { compress_mode = U_COMPRESS_NONE; } u_map_put(response->map_header, "Content-Type", content_type); u_map_copy_into(response->map_header, &config->map_header); if (compress_mode != U_COMPRESS_NONE) { if (compress_mode == U_COMPRESS_GZIP && config->allow_cache_compressed && u_map_has_key(&config->gzip_files, file_requested)) { ulfius_set_binary_body_response(response, 200, u_map_get(&config->gzip_files, file_requested), u_map_get_length(&config->gzip_files, file_requested)); u_map_put(response->map_header, U_CONTENT_HEADER, U_ACCEPT_GZIP); } else if (compress_mode == U_COMPRESS_DEFL && config->allow_cache_compressed && u_map_has_key(&config->deflate_files, file_requested)) { ulfius_set_binary_body_response(response, 200, u_map_get(&config->deflate_files, file_requested), u_map_get_length(&config->deflate_files, file_requested)); u_map_put(response->map_header, U_CONTENT_HEADER, U_ACCEPT_DEFLATE); } else { file_path = msprintf("%s/%s", ((struct _u_compressed_inmemory_website_config *)user_data)->files_path, file_requested); if (!pthread_mutex_lock(&config->lock)) { f = fopen (file_path, "rb"); if (f) { fseek (f, 0, SEEK_END); offset = length = ftell (f); fseek (f, 0, SEEK_SET); if ((file_content_orig = file_content = o_malloc(length)) != NULL && (data_zip = o_malloc((2*length)+20)) != NULL) { defstream.zalloc = u_zalloc; defstream.zfree = u_zfree; defstream.opaque = Z_NULL; defstream.avail_in = (uInt)length; defstream.next_in = (Bytef *)file_content; while ((read_length = fread(file_content, sizeof(char), offset, f))) { file_content += read_length; offset -= read_length; } if (compress_mode == U_COMPRESS_GZIP) { if (deflateInit2(&defstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, U_GZIP_WINDOW_BITS | U_GZIP_ENCODING, 8, Z_DEFAULT_STRATEGY) != Z_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_static_compressed_inmemory_website - Error deflateInit (gzip)"); ret = U_CALLBACK_ERROR; } } else { if (deflateInit(&defstream, Z_BEST_COMPRESSION) != Z_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_static_compressed_inmemory_website - Error deflateInit (deflate)"); ret = U_CALLBACK_ERROR; } } if (ret == U_CALLBACK_CONTINUE) { do { if ((data_zip = o_realloc(data_zip, data_zip_len+_U_W_BLOCK_SIZE)) != NULL) { defstream.avail_out = _U_W_BLOCK_SIZE; defstream.next_out = ((Bytef *)data_zip)+data_zip_len; switch ((res = deflate(&defstream, Z_FINISH))) { case Z_OK: case Z_STREAM_END: case Z_BUF_ERROR: break; default: y_log_message(Y_LOG_LEVEL_ERROR, "callback_static_compressed_inmemory_website - Error deflate %d", res); ret = U_CALLBACK_ERROR; break; } data_zip_len += _U_W_BLOCK_SIZE - defstream.avail_out; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_static_compressed_inmemory_website - Error allocating resources for data_zip"); ret = U_CALLBACK_ERROR; } } while (U_CALLBACK_CONTINUE == ret && defstream.avail_out == 0); if (ret == U_CALLBACK_CONTINUE) { if (compress_mode == U_COMPRESS_GZIP) { if (config->allow_cache_compressed) { u_map_put_binary(&config->gzip_files, file_requested, data_zip, 0, defstream.total_out); } ulfius_set_binary_body_response(response, 200, u_map_get(&config->gzip_files, file_requested), u_map_get_length(&config->gzip_files, file_requested)); } else { if (config->allow_cache_compressed) { u_map_put_binary(&config->deflate_files, file_requested, data_zip, 0, defstream.total_out); } ulfius_set_binary_body_response(response, 200, u_map_get(&config->deflate_files, file_requested), u_map_get_length(&config->deflate_files, file_requested)); } u_map_put(response->map_header, U_CONTENT_HEADER, compress_mode==U_COMPRESS_GZIP?U_ACCEPT_GZIP:U_ACCEPT_DEFLATE); } } deflateEnd(&defstream); o_free(data_zip); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_static_compressed_inmemory_website - Error allocating resource for file_content or data_zip"); ret = U_CALLBACK_ERROR; } o_free(file_content_orig); fclose(f); } else { if (((struct _u_compressed_inmemory_website_config *)user_data)->redirect_on_404 == NULL) { ret = U_CALLBACK_IGNORE; } else { ulfius_add_header_to_response(response, "Location", ((struct _u_compressed_inmemory_website_config *)user_data)->redirect_on_404); response->status = 302; } } pthread_mutex_unlock(&config->lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_static_compressed_inmemory_website - Error pthread_lock_mutex"); ret = U_CALLBACK_ERROR; } o_free(file_path); } } else { ret = callback_static_file_uncompressed(request, response, user_data); } free_string_array(accept_list); } } o_free(url_dup_save); } return ret; } glewlwyd-2.6.1/src/static_compressed_inmemory_website_callback.h000066400000000000000000000100451415646314000253120ustar00rootroot00000000000000/** * * Static file server with compression Ulfius callback * * Copyright 2020-2021 Nicolas Mora * * Version 20211026 * * The MIT License (MIT) * * 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. * * `files_path`: path to the DocumentRoot folder, can be relative or absolute * `url_prefix`: prefix used to access the callback function * `mime_types`: a `struct _u_map` containing a set of mime-types with file extension as key and mime-type as value * `mime_types_compressed`: A `string_array` structure containing the list of mime-types allowed for compression * `mime_types_compressed_size`: The number of elements in `mime_types_compressed` * `map_header`: a `struct _u_map` containing a set of headers that will be added to all responses within the `static_file_callback` * `redirect_on_404`: redirct uri on error 404, if NULL, send 404 * `allow_gzip`: Set to true if you want to allow gzip compression (default true) * `allow_deflate`: Set to true if you want to allow deflate compression (default true) * `allow_cache_compressed`: set to true if you want to allow memory cache for compressed files (default true) * `lock`: mutex lock (do not touch this variable) * `gzip_files`: a `struct _u_map` containing cached gzip files * `deflate_files`: a `struct _u_map` containing cached deflate files * * example of mime-types used in Hutch: * { * key = ".html" * value = "text/html" * }, * { * key = ".css" * value = "text/css" * }, * { * key = ".js" * value = "application/javascript" * }, * { * key = ".png" * value = "image/png" * }, * { * key = ".jpg" * value = "image/jpeg" * }, * { * key = ".jpeg" * value = "image/jpeg" * }, * { * key = ".ttf" * value = "font/ttf" * }, * { * key = ".woff" * value = "font/woff" * }, * { * key = ".woff2" * value = "font/woff2" * }, * { * key = ".map" * value = "application/octet-stream" * }, * { * key = "*" * value = "application/octet-stream" * } * */ #ifndef _U_STATIC_COMPRESSED_INMEMORY_WEBSITE #define _U_STATIC_COMPRESSED_INMEMORY_WEBSITE #define _U_W_BLOCK_SIZE 256 struct _u_compressed_inmemory_website_config { char * files_path; char * url_prefix; struct _u_map mime_types; char ** mime_types_compressed; size_t mime_types_compressed_size; struct _u_map map_header; char * redirect_on_404; int allow_gzip; int allow_deflate; int allow_cache_compressed; pthread_mutex_t lock; struct _u_map gzip_files; struct _u_map deflate_files; }; int u_init_compressed_inmemory_website_config(struct _u_compressed_inmemory_website_config * config); void u_clean_compressed_inmemory_website_config(struct _u_compressed_inmemory_website_config * config); int u_add_mime_types_compressed(struct _u_compressed_inmemory_website_config * config, const char * mime_type); int callback_static_compressed_inmemory_website (const struct _u_request * request, struct _u_response * response, void * user_data); #endif glewlwyd-2.6.1/src/user.c000066400000000000000000001546131415646314000153050ustar00rootroot00000000000000/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * user management functions definition * * Copyright 2016-2021 Nicolas Mora * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . * */ #include "glewlwyd.h" json_t * auth_check_user_credentials(struct config_elements * config, const char * username, const char * password) { int res; json_t * j_return = NULL, * j_module_list = get_user_module_list(config), * j_module, * j_user; struct _user_module_instance * user_module; size_t index; if (check_result_value(j_module_list, G_OK)) { json_array_foreach(json_object_get(j_module_list, "module"), index, j_module) { if (j_return == NULL) { user_module = get_user_module_instance(config, json_string_value(json_object_get(j_module, "name"))); if (user_module != NULL) { if (user_module->enabled) { j_user = user_module->module->user_module_get(config->config_m, username, user_module->cls); if (check_result_value(j_user, G_OK) && json_object_get(json_object_get(j_user, "user"), "enabled") == json_true()) { res = user_module->module->user_module_check_password(config->config_m, username, password, user_module->cls); if (res == G_OK) { j_return = json_pack("{si}", "result", G_OK); } else if (res == G_ERROR_UNAUTHORIZED) { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } else if (res != G_ERROR_NOT_FOUND) { y_log_message(Y_LOG_LEVEL_ERROR, "auth_check_user_credentials - Error, user_module_check_password for module '%s', skip", user_module->name); } } else if (!check_result_value(j_user, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_ERROR, "auth_check_user_credentials - Error, user_module_get for module '%s', skip", user_module->name); } json_decref(j_user); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "auth_check_user_credentials - Error, user_module_instance %s is NULL", json_string_value(json_object_get(j_module, "name"))); } } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "auth_check_user_credentials - Error get_user_module_list"); j_return = json_pack("{si}", "result", G_ERROR); } if (j_return == NULL) { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } json_decref(j_module_list); return j_return; } json_t * auth_check_user_scheme(struct config_elements * config, const char * scheme_type, const char * scheme_name, const char * username, json_t * j_scheme_value, const struct _u_request * request) { struct _user_auth_scheme_module_instance * scheme_instance; json_t * j_return = NULL, * j_user; int res; if ((res = user_has_scheme(config, username, scheme_name)) == G_OK) { j_user = get_user(config, username, NULL); if (check_result_value(j_user, G_OK) && json_object_get(json_object_get(j_user, "user"), "enabled") == json_true()) { scheme_instance = get_user_auth_scheme_module_instance(config, scheme_name); if (scheme_instance != NULL && 0 == o_strcmp(scheme_type, scheme_instance->module->name) && scheme_instance->enabled) { res = scheme_instance->module->user_auth_scheme_module_validate(config->config_m, request, username, j_scheme_value, scheme_instance->cls); if (res == G_OK || res == G_ERROR_UNAUTHORIZED || res == G_ERROR_PARAM || res == G_ERROR_NOT_FOUND || res == G_ERROR) { j_return = json_pack("{si}", "result", res); } else { y_log_message(Y_LOG_LEVEL_ERROR, "auth_check_user_scheme - Error unrecognize return value for user_auth_scheme_module_validate: %d", res); j_return = json_pack("{si}", "result", G_ERROR); } } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } json_decref(j_user); } else if (res != G_ERROR_NOT_FOUND) { y_log_message(Y_LOG_LEVEL_ERROR, "auth_check_user_scheme - Error user_has_scheme"); j_return = json_pack("{si}", "result", G_ERROR); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } return j_return; } json_t * auth_check_identify_scheme(struct config_elements * config, const char * scheme_type, const char * scheme_name, json_t * j_scheme_value, const struct _u_request * request) { struct _user_auth_scheme_module_instance * scheme_instance; json_t * j_return = NULL, * j_user, * j_response; scheme_instance = get_user_auth_scheme_module_instance(config, scheme_name); if (scheme_instance != NULL && 0 == o_strcmp(scheme_type, scheme_instance->module->name) && scheme_instance->enabled) { j_response = scheme_instance->module->user_auth_scheme_module_identify(config->config_m, request, j_scheme_value, scheme_instance->cls); if (check_result_value(j_response, G_OK)) { j_user = get_user(config, json_string_value(json_object_get(j_response, "username")), NULL); if (check_result_value(j_user, G_OK) && json_object_get(json_object_get(j_user, "user"), "enabled") == json_true()) { j_return = json_pack("{sisO}", "result", G_OK, "username", json_object_get(j_response, "username")); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } json_decref(j_user); } else if (!check_result_value(j_response, G_ERROR)) { j_return = json_incref(j_response); } else { y_log_message(Y_LOG_LEVEL_ERROR, "auth_check_identify_scheme - Error user_auth_scheme_module_identify"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_response); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } return j_return; } json_t * auth_trigger_user_scheme(struct config_elements * config, const char * scheme_type, const char * scheme_name, const char * username, json_t * j_trigger_parameters, const struct _u_request * request) { struct _user_auth_scheme_module_instance * scheme_instance; json_t * j_return = NULL, * j_response = NULL; scheme_instance = get_user_auth_scheme_module_instance(config, scheme_name); if (scheme_instance != NULL && 0 == o_strcmp(scheme_type, scheme_instance->module->name) && scheme_instance->enabled) { j_response = scheme_instance->module->user_auth_scheme_module_trigger(config->config_m, request, username, j_trigger_parameters, scheme_instance->cls); if (check_result_value(j_response, G_OK)) { if (json_object_get(j_response, "response") != NULL) { j_return = json_pack("{sisO}", "result", G_OK, "trigger", json_object_get(j_response, "response")); } else { j_return = json_pack("{si}", "result", G_OK); } } else if (!check_result_value(j_response, G_ERROR)) { j_return = json_incref(j_response); } else { y_log_message(Y_LOG_LEVEL_ERROR, "auth_trigger_user_scheme - Error user_auth_scheme_module_trigger"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_response); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } return j_return; } json_t * auth_trigger_identify_scheme(struct config_elements * config, const char * scheme_type, const char * scheme_name, json_t * j_trigger_parameters, const struct _u_request * request) { struct _user_auth_scheme_module_instance * scheme_instance; json_t * j_return = NULL, * j_response = NULL; scheme_instance = get_user_auth_scheme_module_instance(config, scheme_name); if (scheme_instance != NULL && 0 == o_strcmp(scheme_type, scheme_instance->module->name) && scheme_instance->enabled) { j_response = scheme_instance->module->user_auth_scheme_module_identify(config->config_m, request, j_trigger_parameters, scheme_instance->cls); if (check_result_value(j_response, G_OK)) { if (json_object_get(j_response, "response") != NULL) { j_return = json_pack("{sisO}", "result", G_OK, "trigger", json_object_get(j_response, "response")); } else { j_return = json_pack("{si}", "result", G_OK); } } else if (!check_result_value(j_response, G_ERROR)) { j_return = json_incref(j_response); } else { y_log_message(Y_LOG_LEVEL_ERROR, "auth_trigger_identify_scheme - Error user_auth_scheme_module_trigger"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_response); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } return j_return; } json_t * auth_register_user_scheme(struct config_elements * config, const char * scheme_type, const char * scheme_name, const char * username, int delegate, json_t * j_register_parameters, const struct _u_request * request) { struct _user_auth_scheme_module_instance * scheme_instance; json_t * j_return = NULL, * j_response = NULL; int res; if ((res = user_has_scheme(config, username, scheme_name)) == G_OK) { if (json_is_object(j_register_parameters)) { scheme_instance = get_user_auth_scheme_module_instance(config, scheme_name); if (scheme_instance != NULL && 0 == o_strcmp(scheme_type, scheme_instance->module->name) && scheme_instance->enabled) { if (delegate || scheme_instance->guasmi_allow_user_register) { j_response = scheme_instance->module->user_auth_scheme_module_register(config->config_m, request, username, j_register_parameters, scheme_instance->cls); if (check_result_value(j_response, G_OK)) { if (json_object_get(j_response, "response") != NULL) { j_return = json_pack("{sisO}", "result", G_OK, "register", json_object_get(j_response, "response")); } else { j_return = json_pack("{si}", "result", G_OK); } } else if (j_response != NULL && !check_result_value(j_response, G_ERROR)) { if (json_object_get(j_response, "response") != NULL) { j_return = json_pack("{sIsO}", "result", json_integer_value(json_object_get(j_response, "result")), "register", json_object_get(j_response, "response")); } else { j_return = json_pack("{sI}", "result", json_integer_value(json_object_get(j_response, "result"))); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "auth_register_user_scheme - Error user_auth_scheme_module_register"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_response); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } } else if (res != G_ERROR_NOT_FOUND) { y_log_message(Y_LOG_LEVEL_ERROR, "auth_register_user_scheme - Error user_has_scheme"); j_return = json_pack("{si}", "result", G_ERROR); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } return j_return; } json_t * auth_register_get_user_scheme(struct config_elements * config, const char * scheme_type, const char * scheme_name, const char * username, const struct _u_request * request) { struct _user_auth_scheme_module_instance * scheme_instance; json_t * j_return = NULL, * j_response = NULL; int res; if ((res = user_has_scheme(config, username, scheme_name)) == G_OK) { scheme_instance = get_user_auth_scheme_module_instance(config, scheme_name); if (scheme_instance != NULL && 0 == o_strcmp(scheme_type, scheme_instance->module->name) && scheme_instance->enabled) { j_response = scheme_instance->module->user_auth_scheme_module_register_get(config->config_m, request, username, scheme_instance->cls); if (check_result_value(j_response, G_OK)) { if (json_object_get(j_response, "response") != NULL) { j_return = json_pack("{sisO}", "result", G_OK, "register", json_object_get(j_response, "response")); } else { j_return = json_pack("{si}", "result", G_OK); } } else if (!check_result_value(j_response, G_ERROR)) { j_return = json_incref(j_response); } else { y_log_message(Y_LOG_LEVEL_ERROR, "auth_register_get_user_scheme - Error user_auth_scheme_module_register_get"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_response); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else if (res != G_ERROR_NOT_FOUND) { y_log_message(Y_LOG_LEVEL_ERROR, "auth_register_get_user_scheme - Error user_has_scheme"); j_return = json_pack("{si}", "result", G_ERROR); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } return j_return; } int user_has_scope(json_t * j_user, const char * scope) { json_t * j_element; size_t index; json_array_foreach(json_object_get(j_user, "scope"), index, j_element) { if (0 == o_strcmp(scope, json_string_value(j_element))) { return 1; } } return 0; } int user_has_scheme(struct config_elements * config, const char * username, const char * scheme_name) { json_t * j_user, * j_element = NULL, * j_group = NULL, * j_scheme = NULL, * j_scope = NULL; size_t index = 0, index_s = 0; const char * group = NULL; int ret = G_ERROR_NOT_FOUND; j_user = get_user(config, username, NULL); if (check_result_value(j_user, G_OK)) { json_array_foreach(json_object_get(json_object_get(j_user, "user"), "scope"), index, j_element) { j_scope = get_scope(config, json_string_value(j_element)); if (check_result_value(j_scope, G_OK)) { json_object_foreach(json_object_get(json_object_get(j_scope, "scope"), "scheme"), group, j_group) { json_array_foreach(j_group, index_s, j_scheme) { if (0 == o_strcmp(json_string_value(json_object_get(j_scheme, "scheme_name")), scheme_name)) { ret = G_OK; } } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_has_scheme - Error get_scope '%s'", json_string_value(j_element)); } json_decref(j_scope); } } else if (!check_result_value(j_user, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_ERROR, "user_has_scheme - Error get_user"); ret = G_ERROR; } json_decref(j_user); return ret; } json_t * get_user(struct config_elements * config, const char * username, const char * source) { int found = 0, result; json_t * j_return = NULL, * j_user, * j_module_list, * j_module; struct _user_module_instance * user_module; struct _user_middleware_module_instance * user_middleware_module; size_t index, i; if (!o_strlen(username)) { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } else if (source != NULL) { user_module = get_user_module_instance(config, source); if (user_module != NULL) { j_user = user_module->module->user_module_get(config->config_m, username, user_module->cls); if (check_result_value(j_user, G_OK)) { result = G_OK; for (i=0; iuser_middleware_module_instance_list); i++) { user_middleware_module = (struct _user_middleware_module_instance *)pointer_list_get_at(config->user_middleware_module_instance_list, i); if (user_middleware_module != NULL && user_middleware_module->enabled) { if ((result = user_middleware_module->module->user_middleware_module_get(config->config_m, username, json_object_get(j_user, "user"), user_middleware_module->cls)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "get_user - Error user_middleware_module_get at index %zu for user %s", i, username); break; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user - Error pointer_list_get_at for user_middleware module at index %zu", i); } } if (result == G_OK) { json_object_set_new(json_object_get(j_user, "user"), "source", json_string(source)); j_return = json_incref(j_user); } else { j_return = json_pack("{si}", "result", result); } } else if (check_result_value(j_user, G_ERROR_NOT_FOUND)) { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user - Error, user_module_get for module %s", user_module->name); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_user); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } else { j_module_list = get_user_module_list(config); if (check_result_value(j_module_list, G_OK)) { json_array_foreach(json_object_get(j_module_list, "module"), index, j_module) { if (!found) { user_module = get_user_module_instance(config, json_string_value(json_object_get(j_module, "name"))); if (user_module != NULL) { if (user_module->enabled) { j_user = user_module->module->user_module_get(config->config_m, username, user_module->cls); if (check_result_value(j_user, G_OK)) { found = 1; result = G_OK; for (i=0; iuser_middleware_module_instance_list); i++) { user_middleware_module = (struct _user_middleware_module_instance *)pointer_list_get_at(config->user_middleware_module_instance_list, i); if (user_middleware_module != NULL && user_middleware_module->enabled) { if ((result = user_middleware_module->module->user_middleware_module_get(config->config_m, username, json_object_get(j_user, "user"), user_middleware_module->cls)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "get_user - Error user_middleware_module_get at index %zu for user %s", i, username); break; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user - Error pointer_list_get_at for user_middleware module at index %zu", i); } } if (result == G_OK) { json_object_set_new(json_object_get(j_user, "user"), "source", json_string(user_module->name)); j_return = json_incref(j_user); } else { j_return = json_pack("{si}", "result", result); } } else if (!check_result_value(j_user, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_ERROR, "get_user - Error, user_module_get for module %s", user_module->name); } json_decref(j_user); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user - Error, user_module_instance %s is NULL", json_string_value(json_object_get(j_module, "name"))); } } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user - Error get_user_module_list"); j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_module_list); } if (j_return == NULL) { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } return j_return; } json_t * get_user_profile(struct config_elements * config, const char * username, const char * source) { int found = 0, result; json_t * j_return = NULL, * j_module_list, * j_module, * j_profile; struct _user_module_instance * user_module; struct _user_middleware_module_instance * user_middleware_module; size_t index, i; if (source != NULL) { user_module = get_user_module_instance(config, source); if (user_module != NULL) { j_profile = user_module->module->user_module_get_profile(config->config_m, username, user_module->cls); if (check_result_value(j_profile, G_OK)) { result = G_OK; for (i=0; iuser_middleware_module_instance_list); i++) { user_middleware_module = (struct _user_middleware_module_instance *)pointer_list_get_at(config->user_middleware_module_instance_list, i); if (user_middleware_module != NULL && user_middleware_module->enabled) { if ((result = user_middleware_module->module->user_middleware_module_get_profile(config->config_m, username, json_object_get(j_profile, "user"), user_middleware_module->cls)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_profile - Error user_middleware_module_get_profile at index %zu for user %s", i, username); break; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_profile - Error pointer_list_get_at for user_middleware module at index %zu", i); } } if (result == G_OK) { j_return = json_incref(j_profile); } else { j_return = json_pack("{si}", "result", result); } } else if (check_result_value(j_profile, G_ERROR_NOT_FOUND)) { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_get_profile - Error user_module_get_profile"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_profile); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } else { j_module_list = get_user_module_list(config); if (check_result_value(j_module_list, G_OK)) { json_array_foreach(json_object_get(j_module_list, "module"), index, j_module) { if (!found) { user_module = get_user_module_instance(config, json_string_value(json_object_get(j_module, "name"))); if (user_module != NULL) { if (user_module->enabled) { j_profile = user_module->module->user_module_get_profile(config->config_m, username, user_module->cls); if (check_result_value(j_profile, G_OK)) { result = G_OK; for (i=0; iuser_middleware_module_instance_list); i++) { user_middleware_module = (struct _user_middleware_module_instance *)pointer_list_get_at(config->user_middleware_module_instance_list, i); if (user_middleware_module != NULL && user_middleware_module->enabled) { if ((result = user_middleware_module->module->user_middleware_module_get_profile(config->config_m, username, json_object_get(j_profile, "user"), user_middleware_module->cls)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_profile - Error user_middleware_module_get_profile at index %d for user %s", i, username); break; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_profile - Error pointer_list_get_at for user_middleware module at index %d", i); } } if (result == G_OK) { j_return = json_incref(j_profile); } else { j_return = json_pack("{si}", "result", result); } found = 1; } json_decref(j_profile); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_profile - Error, user_module_instance %s is NULL", json_string_value(json_object_get(j_module, "name"))); } } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_profile - Error get_user_module_list"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_module_list); } if (j_return == NULL) { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } return j_return; } json_t * get_user_list(struct config_elements * config, const char * pattern, size_t offset, size_t limit, const char * source) { json_t * j_return, * j_module_list, * j_module, * j_element, * j_result; struct _user_module_instance * user_module; struct _user_middleware_module_instance * user_middleware_module; size_t cur_offset, cur_limit, count_total, index, index_u, i; int result; if (source != NULL) { user_module = get_user_module_instance(config, source); if (user_module != NULL && user_module->enabled) { j_result = user_module->module->user_module_get_list(config->config_m, pattern, offset, limit, user_module->cls); if (check_result_value(j_result, G_OK)) { json_array_foreach(json_object_get(j_result, "list"), index, j_element) { json_object_set_new(j_element, "source", json_string(source)); } j_return = json_pack("{sisO}", "result", G_OK, "user", json_object_get(j_result, "list")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_list - Error user_module_get_list"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_result); } else if (user_module != NULL && !user_module->enabled) { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_list - Error get_user_module_instance"); j_return = json_pack("{si}", "result", G_ERROR); } } else { j_module_list = get_user_module_list(config); if (check_result_value(j_module_list, G_OK)) { cur_offset = offset; cur_limit = limit; j_return = json_pack("{sis[]}", "result", G_OK, "user"); if (j_return != NULL) { json_array_foreach(json_object_get(j_module_list, "module"), index, j_module) { if (cur_limit) { user_module = get_user_module_instance(config, json_string_value(json_object_get(j_module, "name"))); if (user_module != NULL && user_module->enabled) { if ((count_total = user_module->module->user_module_count_total(config->config_m, pattern, user_module->cls)) > cur_offset && cur_limit) { j_result = user_module->module->user_module_get_list(config->config_m, pattern, cur_offset, cur_limit, user_module->cls); if (check_result_value(j_result, G_OK)) { json_array_foreach(json_object_get(j_result, "list"), index_u, j_element) { json_object_set_new(j_element, "source", json_string(user_module->name)); } cur_offset = 0; if (cur_limit > json_array_size(json_object_get(j_result, "list"))) { cur_limit -= json_array_size(json_object_get(j_result, "list")); } else { cur_limit = 0; } json_array_extend(json_object_get(j_return, "user"), json_object_get(j_result, "list")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_list - Error user_module_get_list for module %s", json_string_value(json_object_get(j_module, "name"))); } json_decref(j_result); } else { cur_offset -= count_total; } } else if (user_module == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_list - Error, user_module_instance %s is NULL", json_string_value(json_object_get(j_module, "name"))); } } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_list - Error allocating resources for j_return"); j_return = json_pack("{si}", "result", G_ERROR); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_list - Error get_user_module_list"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_module_list); } if (check_result_value(j_return, G_OK)) { result = G_OK; for (i=0; iuser_middleware_module_instance_list); i++) { user_middleware_module = (struct _user_middleware_module_instance *)pointer_list_get_at(config->user_middleware_module_instance_list, i); if (user_middleware_module != NULL && user_middleware_module->enabled) { if ((result = user_middleware_module->module->user_middleware_module_get_list(config->config_m, json_object_get(j_return, "user"), user_middleware_module->cls)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_list - Error user_middleware_module_get_list at index %zu", i); json_decref(j_return); j_return = json_pack("{si}", "result", result); break; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_list - Error pointer_list_get_at for user_middleware module at index %zu", i); } } } return j_return; } json_t * is_user_valid(struct config_elements * config, const char * username, json_t * j_user, int add, const char * source) { int found = 0; json_t * j_return = NULL, * j_error_list, * j_module_list, * j_module, * j_user_copy = json_deep_copy(j_user); struct _user_module_instance * user_module; struct _user_middleware_module_instance * user_middleware_module; size_t index, i; for (i=0; iuser_middleware_module_instance_list); i++) { user_middleware_module = (struct _user_middleware_module_instance *)pointer_list_get_at(config->user_middleware_module_instance_list, i); if (user_middleware_module != NULL && user_middleware_module->enabled) { if (user_middleware_module->module->user_middleware_module_update(config->config_m, username, j_user_copy, user_middleware_module->cls) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "is_user_valid - Error user_middleware_module_update at index %zu for user %s", i, username); break; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_user_valid - Error pointer_list_get_at for user_middleware module at index %zu", i); } } if (source != NULL) { user_module = get_user_module_instance(config, source); if (user_module != NULL && user_module->enabled && !user_module->readonly) { j_error_list = user_module->module->user_module_is_valid(config->config_m, username, j_user_copy, add?GLEWLWYD_IS_VALID_MODE_ADD:GLEWLWYD_IS_VALID_MODE_UPDATE, user_module->cls); if (check_result_value(j_error_list, G_ERROR_PARAM)) { j_return = json_incref(j_error_list); } else if (check_result_value(j_error_list, G_OK)) { j_return = json_pack("{si}", "result", G_OK); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_user_valid - Error user_module_is_valid"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_error_list); } else if (user_module != NULL && user_module->readonly) { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "module is read-only"); } else if (user_module != NULL && !user_module->enabled) { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "module is unavailable"); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_user_valid - Error get_user_module_instance"); j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } else if (add) { j_module_list = get_user_module_list(config); if (check_result_value(j_module_list, G_OK)) { json_array_foreach(json_object_get(j_module_list, "module"), index, j_module) { if (!found) { user_module = get_user_module_instance(config, json_string_value(json_object_get(j_module, "name"))); if (user_module != NULL && user_module->enabled && !user_module->readonly) { found = 1; j_error_list = user_module->module->user_module_is_valid(config->config_m, username, j_user_copy, add?GLEWLWYD_IS_VALID_MODE_ADD:GLEWLWYD_IS_VALID_MODE_UPDATE, user_module->cls); if (check_result_value(j_error_list, G_ERROR_PARAM)) { j_return = json_incref(j_error_list); } else if (check_result_value(j_error_list, G_OK)) { j_return = json_pack("{si}", "result", G_OK); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_user_valid - Error user_module_is_valid"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_error_list); } else if (user_module == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "is_user_valid - Error, user_module_instance %s is NULL", json_string_value(json_object_get(j_module, "name"))); } } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_user_valid - Error get_user_module_list"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_module_list); if (j_return == NULL) { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "user", "no writeable source"); } } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "user", "source parameter is mandatory"); } json_decref(j_user_copy); return j_return; } int add_user(struct config_elements * config, json_t * j_user, const char * source) { int found = 0, result = G_OK, ret; json_t * j_module_list, * j_module; struct _user_module_instance * user_module; struct _user_middleware_module_instance * user_middleware_module; size_t index, i; if (source != NULL) { user_module = get_user_module_instance(config, source); if (user_module != NULL && user_module->enabled && !user_module->readonly) { for (i=0; iuser_middleware_module_instance_list); i++) { user_middleware_module = (struct _user_middleware_module_instance *)pointer_list_get_at(config->user_middleware_module_instance_list, i); if (user_middleware_module != NULL && user_middleware_module->enabled) { if ((result = user_middleware_module->module->user_middleware_module_update(config->config_m, json_string_value(json_object_get(j_user, "username")), j_user, user_middleware_module->cls)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "add_user - Error user_middleware_module_get_list at index %zu for user %s", i, json_string_value(json_object_get(j_user, "username"))); break; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user - Error pointer_list_get_at for user_middleware module at index %zu", i); } } if (result == G_OK) { result = user_module->module->user_module_add(config->config_m, j_user, user_module->cls); if (result == G_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user - Error user_module_add"); ret = result; } } else { ret = G_ERROR; } } else if (user_module != NULL && (user_module->readonly || !user_module->enabled)) { y_log_message(Y_LOG_LEVEL_ERROR, "add_user - Error module %s not allowed", user_module->name); ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user - Error get_user_module_instance"); ret = G_ERROR; } } else { j_module_list = get_user_module_list(config); if (check_result_value(j_module_list, G_OK)) { json_array_foreach(json_object_get(j_module_list, "module"), index, j_module) { if (!found) { user_module = get_user_module_instance(config, json_string_value(json_object_get(j_module, "name"))); if (user_module != NULL && user_module->enabled && !user_module->readonly) { for (i=0; iuser_middleware_module_instance_list); i++) { user_middleware_module = (struct _user_middleware_module_instance *)pointer_list_get_at(config->user_middleware_module_instance_list, i); if (user_middleware_module != NULL && user_middleware_module->enabled) { if ((result = user_middleware_module->module->user_middleware_module_update(config->config_m, json_string_value(json_object_get(j_user, "username")), j_user, user_middleware_module->cls)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "add_user - Error user_middleware_module_get_list at index %zu for user %s", i, json_string_value(json_object_get(j_user, "username"))); break; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user - Error pointer_list_get_at for user_middleware module at index %zu", i); } } found = 1; if (result == G_OK) { result = user_module->module->user_module_add(config->config_m, j_user, user_module->cls); if (result == G_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user - Error user_module_add"); ret = result; } } } else if (user_module == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "add_user - Error, user_module_instance %s is NULL", json_string_value(json_object_get(j_module, "name"))); } } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "add_user - Error get_user_module_list"); ret = G_ERROR; } json_decref(j_module_list); if (!found) { ret = G_ERROR_NOT_FOUND; } } return ret; } int set_user(struct config_elements * config, const char * username, json_t * j_user, const char * source) { int ret, result = G_OK; struct _user_module_instance * user_module; struct _user_middleware_module_instance * user_middleware_module; json_t * j_cur_user; size_t i; if (source != NULL) { user_module = get_user_module_instance(config, source); if (user_module != NULL && user_module->enabled && !user_module->readonly) { for (i=0; iuser_middleware_module_instance_list); i++) { user_middleware_module = (struct _user_middleware_module_instance *)pointer_list_get_at(config->user_middleware_module_instance_list, i); if (user_middleware_module != NULL && user_middleware_module->enabled) { if ((result = user_middleware_module->module->user_middleware_module_update(config->config_m, username, j_user, user_middleware_module->cls)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "set_user - Error user_middleware_module_update at index %zu for user %s", i, username); break; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_user - Error pointer_list_get_at for user_middleware module at index %zu", i); } } if (result == G_OK) { j_cur_user = user_module->module->user_module_get(config->config_m, username, user_module->cls); if (check_result_value(j_cur_user, G_OK)) { ret = user_module->module->user_module_update(config->config_m, username, j_user, user_module->cls); if (ret != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "set_user - Error user_module_update"); } } else if (check_result_value(j_cur_user, G_ERROR_NOT_FOUND)) { ret = G_ERROR_NOT_FOUND; } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_user - Error user_module_get"); ret = G_ERROR; } json_decref(j_cur_user); } else { ret = G_ERROR; } } else if (user_module != NULL && (user_module->readonly || !user_module->enabled)) { ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_user - Error get_user_module_instance"); ret = G_ERROR; } } else { ret = G_ERROR_PARAM; } return ret; } int delete_user(struct config_elements * config, const char * username, const char * source) { int ret; struct _user_module_instance * user_module; struct _user_middleware_module_instance * user_middleware_module; struct _user_auth_scheme_module_instance * scheme_module; struct _plugin_module_instance * plugin_module; json_t * j_cur_user; int result; size_t i; if (source != NULL) { user_module = get_user_module_instance(config, source); if (user_module != NULL && user_module->enabled && !user_module->readonly) { j_cur_user = user_module->module->user_module_get(config->config_m, username, user_module->cls); if (check_result_value(j_cur_user, G_OK)) { for (i=0; iuser_middleware_module_instance_list); i++) { user_middleware_module = (struct _user_middleware_module_instance *)pointer_list_get_at(config->user_middleware_module_instance_list, i); if (user_middleware_module != NULL && user_middleware_module->enabled) { if ((result = user_middleware_module->module->user_middleware_module_delete(config->config_m, username, j_cur_user, user_middleware_module->cls)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "delete_user - Error user_middleware_module_delete at index %zu for user %s", i, username); break; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_user - Error pointer_list_get_at for user_middleware module at index %zu", i); } } result = user_module->module->user_module_delete(config->config_m, username, user_module->cls); if (result == G_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_user - Error user_module_delete"); ret = result; } } else if (check_result_value(j_cur_user, G_ERROR_NOT_FOUND)) { ret = G_ERROR_NOT_FOUND; } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_user - Error user_module_get"); ret = G_ERROR; } if (ret == G_OK) { for (i = 0; i < pointer_list_size(config->user_auth_scheme_module_instance_list); i++) { scheme_module = pointer_list_get_at(config->user_auth_scheme_module_instance_list, i); if (scheme_module != NULL && scheme_module->enabled) { if ((ret = scheme_module->module->user_auth_scheme_module_deregister(config->config_m, username, scheme_module->cls)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "delete_user - Error user_auth_scheme_module_deregister for scheme %s", scheme_module->name); break; } } } } if (ret == G_OK) { for (i = 0; i < pointer_list_size(config->plugin_module_instance_list); i++) { plugin_module = pointer_list_get_at(config->plugin_module_instance_list, i); if (plugin_module != NULL && plugin_module->enabled) { if ((ret = plugin_module->module->plugin_user_revoke(config->config_p, username, plugin_module->cls)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "delete_user - Error plugin_user_revoke for plugin %s", plugin_module->name); break; } } } } json_decref(j_cur_user); } else if (user_module != NULL && (user_module->readonly || !user_module->enabled)) { ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "delete_user - Error get_user_module_instance"); ret = G_ERROR; } } else { ret = G_ERROR_PARAM; } return ret; } json_t * user_get_profile(struct config_elements * config, const char * username) { json_t * j_user = get_user(config, username, NULL), * j_return, * j_profile; struct _user_module_instance * user_module; if (check_result_value(j_user, G_OK)) { user_module = get_user_module_instance(config, json_string_value(json_object_get(json_object_get(j_user, "user"), "source"))); if (user_module != NULL && user_module->enabled) { j_profile = user_module->module->user_module_get_profile(config->config_m, username, user_module->cls); if (check_result_value(j_profile, G_OK)) { j_return = json_pack("{sisO}", "result", G_OK, "profile", j_profile); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_get_profile - Error user_module_get_profile"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_profile); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_get_profile - Error get_user_module_instance"); j_return = json_pack("{si}", "result", G_ERROR); } } else if (check_result_value(j_user, G_ERROR_NOT_FOUND)) { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_get_profile - Error get_user"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_user); return j_return; } json_t * user_set_profile(struct config_elements * config, const char * username, json_t * j_profile) { json_t * j_user = get_user(config, username, NULL), * j_return; struct _user_module_instance * user_module; if (check_result_value(j_user, G_OK)) { user_module = get_user_module_instance(config, json_string_value(json_object_get(json_object_get(j_user, "user"), "source"))); if (user_module != NULL && user_module->enabled && !user_module->readonly) { j_return = json_pack("{si}", "result", user_module->module->user_module_update_profile(config->config_m, username, j_profile, user_module->cls)); } else if (user_module != NULL && (user_module->readonly || !user_module->enabled)) { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "profile update is not allowed"); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_set_profile - Error get_user_module_instance"); j_return = json_pack("{si}", "result", G_ERROR); } } else if (check_result_value(j_user, G_ERROR_NOT_FOUND)) { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_set_profile - Error get_user"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_user); return j_return; } int user_delete_profile(struct config_elements * config, const char * username) { json_t * j_user = get_user(config, username, NULL); struct _user_module_instance * user_module; struct _user_auth_scheme_module_instance * scheme_module; struct _plugin_module_instance * plugin_module; int ret; size_t i; if (check_result_value(j_user, G_OK)) { user_module = get_user_module_instance(config, json_string_value(json_object_get(json_object_get(j_user, "user"), "source"))); if (config->delete_profile & GLEWLWYD_PROFILE_DELETE_AUTHORIZED && user_module != NULL && user_module->enabled && !user_module->readonly) { ret = G_OK; if (config->delete_profile & GLEWLWYD_PROFILE_DELETE_DISABLE_PROFILE) { json_object_set(json_object_get(j_user, "user"), "enabled", json_false()); if ((ret = user_module->module->user_module_update(config->config_m, username, json_object_get(j_user, "user"), user_module->cls)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "user_delete_profile - Error user_module_update_profile"); } } else { if ((ret = user_module->module->user_module_delete(config->config_m, username, user_module->cls)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "user_delete_profile - Error user_module_delete"); } } if (ret == G_OK && !(config->delete_profile & GLEWLWYD_PROFILE_DELETE_DISABLE_PROFILE)) { for (i = 0; i < pointer_list_size(config->user_auth_scheme_module_instance_list); i++) { scheme_module = pointer_list_get_at(config->user_auth_scheme_module_instance_list, i); if (scheme_module != NULL && scheme_module->enabled) { if ((ret = scheme_module->module->user_auth_scheme_module_deregister(config->config_m, username, scheme_module->cls)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "user_delete_profile - Error user_auth_scheme_module_deregister for scheme %s", scheme_module->name); break; } } } } if (ret == G_OK && !(config->delete_profile & GLEWLWYD_PROFILE_DELETE_DISABLE_PROFILE)) { for (i = 0; i < pointer_list_size(config->plugin_module_instance_list); i++) { plugin_module = pointer_list_get_at(config->plugin_module_instance_list, i); if (plugin_module != NULL && plugin_module->enabled) { if ((ret = plugin_module->module->plugin_user_revoke(config->config_p, username, plugin_module->cls)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "user_delete_profile - Error plugin_user_revoke for plugin %s", plugin_module->name); break; } } } } } else if (!(config->delete_profile & GLEWLWYD_PROFILE_DELETE_AUTHORIZED) || (user_module != NULL && user_module->readonly)) { ret = G_ERROR_UNAUTHORIZED; } else { ret = G_ERROR; } } else if (check_result_value(j_user, G_ERROR_NOT_FOUND)) { ret = G_ERROR_NOT_FOUND; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_delete_profile - Error get_user"); ret = G_ERROR; } json_decref(j_user); return ret; } int user_update_password(struct config_elements * config, const char * username, const char * old_password, const char ** new_passwords, size_t new_passwords_len) { json_t * j_user = get_user(config, username, NULL); struct _user_module_instance * user_module; int ret; if (check_result_value(j_user, G_OK)) { user_module = get_user_module_instance(config, json_string_value(json_object_get(json_object_get(j_user, "user"), "source"))); if (user_module != NULL && user_module->enabled && !user_module->readonly) { if ((ret = user_module->module->user_module_check_password(config->config_m, username, old_password, user_module->cls)) == G_OK) { ret = user_module->module->user_module_update_password(config->config_m, username, new_passwords, new_passwords_len, user_module->cls); } else if (ret == G_ERROR_UNAUTHORIZED) { ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_set_profile - Error user_module_check_password"); ret = G_ERROR; } } else if (user_module != NULL && (user_module->readonly || !user_module->enabled)) { ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_set_profile - Error get_user_module_instance"); ret = G_ERROR; } } else if (check_result_value(j_user, G_ERROR_NOT_FOUND)) { ret = G_ERROR_NOT_FOUND; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_set_profile - Error get_user"); ret = G_ERROR; } json_decref(j_user); return ret; } int user_set_password(struct config_elements * config, const char * username, const char ** new_passwords, size_t new_passwords_len) { json_t * j_user = get_user(config, username, NULL); struct _user_module_instance * user_module; int ret; if (check_result_value(j_user, G_OK)) { user_module = get_user_module_instance(config, json_string_value(json_object_get(json_object_get(j_user, "user"), "source"))); if (user_module != NULL && user_module->enabled && !user_module->readonly) { ret = user_module->module->user_module_update_password(config->config_m, username, new_passwords, new_passwords_len, user_module->cls); } else if (user_module != NULL && (user_module->readonly || !user_module->enabled)) { ret = G_ERROR_PARAM; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_set_profile - Error get_user_module_instance"); ret = G_ERROR; } } else if (check_result_value(j_user, G_ERROR_NOT_FOUND)) { ret = G_ERROR_NOT_FOUND; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_set_profile - Error get_user"); ret = G_ERROR; } json_decref(j_user); return ret; } json_t * glewlwyd_module_callback_get_user(struct config_module * config, const char * username) { return get_user(config->glewlwyd_config, username, NULL); } int glewlwyd_module_callback_set_user(struct config_module * config, const char * username, json_t * j_user_data) { json_t * j_user; int ret; j_user = get_user(config->glewlwyd_config, username, NULL); if (check_result_value(j_user, G_OK)) { ret = set_user(config->glewlwyd_config, username, j_user_data, json_string_value(json_object_get(json_object_get(j_user, "user"), "source"))); } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_callback_set_user - Error get_user"); ret = G_ERROR; } json_decref(j_user); return ret; } int glewlwyd_module_callback_check_user_password(struct config_module * config, const char * username, const char * password) { int ret; json_t * j_result; j_result = auth_check_user_credentials(config->glewlwyd_config, username, password); if (json_is_integer(json_object_get(j_result, "result"))) { ret = json_integer_value(json_object_get(j_result, "result")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_module_callback_check_user_password - Error auth_check_user_credentials"); ret = G_ERROR; } json_decref(j_result); return ret; } json_t * glewlwyd_module_callback_check_user_session(struct config_module * config, const struct _u_request * request, const char * username) { char * session_uid = get_session_id(config->glewlwyd_config, request); json_t * j_return, * j_result; if (session_uid != NULL) { j_result = get_current_user_for_session(config->glewlwyd_config, session_uid); if (check_result_value(j_result, G_OK)) { if (0 == o_strcmp(username, json_string_value(json_object_get(json_object_get(j_result, "user"), "username")))) { j_return = json_incref(j_result); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } } else if (!check_result_value(j_result, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_module_callback_check_user_password - Error get_current_user_for_session"); j_return = json_pack("{si}", "result", G_ERROR); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } json_decref(j_result); } else { j_return = json_pack("{si}", "result", G_ERROR_UNAUTHORIZED); } o_free(session_uid); return j_return; } int glewlwyd_module_callback_metrics_add_metric(struct config_module * config, const char * name, const char * help) { if (config != NULL) { return glewlwyd_metrics_add_metric(config->glewlwyd_config, name, help); } else { return G_ERROR_PARAM; } } int glewlwyd_module_callback_metrics_increment_counter(struct config_module * config, const char * name, size_t inc, ...) { va_list vl; char * label = NULL; int ret = G_OK; if (config != NULL && o_strlen(name)) { va_start(vl, inc); label = glewlwyd_metrics_build_label(vl); va_end(vl); ret = glewlwyd_metrics_increment_counter(config->glewlwyd_config, name, label, inc); o_free(label); } else { y_log_message(Y_LOG_LEVEL_ERROR, "glewlwyd_module_callback_metrics_increment_counter - Error input values"); ret = G_ERROR_PARAM; } return ret; } glewlwyd-2.6.1/src/user/000077500000000000000000000000001415646314000151275ustar00rootroot00000000000000glewlwyd-2.6.1/src/user/Makefile000066400000000000000000000043621415646314000165740ustar00rootroot00000000000000# # Glewlwyd user backend # # Makefile used to build the software # # Copyright 2016-2019 Nicolas Mora # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; # version 2.1 of the License. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this library. If not, see . # GLWD_SRC=.. DESTDIR=/usr/local MODULES_TARGET=$(DESTDIR)/lib/glewlwyd/user CC=gcc CFLAGS=-c -fPIC -Wall -Werror -Wextra -Wno-unknown-pragmas -D_REENTRANT -Wno-pragmas -I$(GLWD_SRC) $(shell pkg-config --cflags liborcania) $(shell pkg-config --cflags libyder) $(shell pkg-config --cflags jansson) $(shell pkg-config --cflags libhoel) $(ADDITIONALFLAGS) LIBS=$(shell pkg-config --libs liborcania) $(shell pkg-config --libs libyder) $(shell pkg-config --libs jansson) -ldl LDFLAGS += -Wl,-R '-Wl,-R$$ORIGIN' TARGET=libmoddatabase.so libmodldap.so libmodhttp.so all: release misc.o: $(GLWD_SRC)/misc.c $(GLWD_SRC)/glewlwyd-common.h $(CC) $(CFLAGS) $(CPPFLAGS) $(GLWD_SRC)/misc.c %.o: %.c $(GLWD_SRC)/glewlwyd.h $(CC) $(CFLAGS) $(CPPFLAGS) $< libmodmock.so: mock.o misc.o $(CC) -shared -Wl,-soname,libmodmock.so mock.o misc.o -o libmodmock.so $(LIBS) $(LDFLAGS) libmoddatabase.so: $(GLWD_SRC)/glewlwyd-common.h database.o misc.o $(CC) -shared -Wl,-soname,libmoddatabase.so -o libmoddatabase.so database.o misc.o $(LIBS) $(shell pkg-config --libs libhoel) libmodldap.so: $(GLWD_SRC)/glewlwyd-common.h ldap.o misc.o $(CC) -shared -Wl,-soname,libmodldap.so -o libmodldap.so ldap.o misc.o $(LIBS) -lldap -lcrypt libmodhttp.so: $(GLWD_SRC)/glewlwyd-common.h http.o misc.o $(CC) -shared -Wl,-soname,libmodhttp.so -o libmodhttp.so http.o misc.o $(LIBS) clean: rm -f *.o *.so debug: ADDITIONALFLAGS=-DDEBUG -g -O0 debug: libmodmock.so $(TARGET) release: ADDITIONALFLAGS=-O3 release: $(TARGET) install: mkdir -p $(MODULES_TARGET) cp -f *.so $(MODULES_TARGET) glewlwyd-2.6.1/src/user/README.md000066400000000000000000000373111415646314000164130ustar00rootroot00000000000000# Glewlwyd User Backend Modules A Glewlwyd module is built as a library and loaded at startup. It must contain a specific set of functions available to glewlwyd to work properly. A Glewlwyd module can access the entire data and functions available to Glewlwyd service. There is no limitation to its access. Therefore, Glewlwyd modules must be carefully designed and considered friendly. All data returned as `json_t *` or `char *` must be dynamically allocated, because they will be cleaned up by Glewlwyd after use. Currently, the following user backend modules are available: - [Database backend](database.c) - [LDAP backend](ldap.c) - [HTTP backend](http.c) A user backend module is used to manage users in a specific backend environment. It is intended to: - list users - get a specific user - get a user profile data - add a new user - update a user - delete a user - verify a user password - update a user password A user is defined by attributes. The following attributes are mandatory for every user: ```javascript { "username": string, identifies the user, must be unique "scope": array of string, list of scopes available to the user "enabled": boolean, set this value to false will make the user unable to authenticate or do anything in Glewlwyd } ``` Other attributes can be added to a user, depending on the backend and the configuration. Any other attribute can be either a string or an array of strings. If another type is returned by the module, the behaviour is undefined. Glewlwyd uses two other attributes if they are returned by the module: `email` and `name`. A Glewlwyd module requires the library [Jansson](https://github.com/akheron/Jansson). You can check out the existing modules for inspiration. You can start from the fake module [mock.c](mock.c) to build your own. A pointer of `struct config_module` is passed to all the mandatory functions. This pointer gives access to some Glewlwyd data and some callback functions used to achieve specific actions. The definition of the structure is the following: ```C struct config_module { /* External url to access to the Glewlwyd instance */ const char * external_url; /* relative url to access to the login page */ const char * login_url; /* value of the admin scope */ const char * admin_scope; /* Value of the profile scope */ const char * profile_scope; /* connection to the database via hoel library */ struct _h_connection * conn; /* Digest agorithm defined in the configuration file */ digest_algorithm hash_algorithm; /* General configuration of the Glewlwyd instance */ struct config_elements * glewlwyd_config; /* Callback function to retrieve a specific user */ json_t * (* glewlwyd_module_callback_get_user)(struct config_module * config, const char * username); /* Callback function to update a specific user */ int (* glewlwyd_module_callback_set_user)(struct config_module * config, const char * username, json_t * j_user); /* Callback function to validate a user password */ int (* glewlwyd_module_callback_check_user_password)(struct config_module * config, const char * username, const char * password); /* Callback function to validate a session */ json_t * (* glewlwyd_module_callback_check_user_session)(struct config_module * config, const struct _u_request * request, const char * username); }; ``` A user module must have the following functions defined and available: ```C /** * * user_module_load * * Executed once when Glewlwyd service is started * Used to identify the module and to show its parameters on init * You can also use it to load resources that are required once for all * instance modules for example * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * name: string, mandatory, name of the module, must be unique among other scheme modules * display_name: string, optional, long name of the module * description: string, optional, description for the module * parameters: object, optional, parameters description for the module * } * * Example: * { * result: G_OK, * name: "mock", * display_name: "Mock scheme module", * description: "Mock scheme module for glewlwyd tests", * parameters: { * mock-value: { * type: "string", * mandatory: true * } * } * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ json_t * user_module_load(struct config_module * config); ``` ```C /** * * user_module_unload * * Executed once when Glewlwyd service is stopped * You can use it to release resources that are required once for all * instance modules for example * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ int user_module_unload(struct config_module * config); ``` ```C /** * * user_module_init * * Initialize an instance of this module declared in Glewlwyd service. * If required, you must dynamically allocate a pointer to the configuration * for this instance and pass it to *cls * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, G_ERROR_PARAM on input parameters error, another value on error) * error: array of strings containg the list of input errors, mandatory on result G_ERROR_PARAM, ignored otherwise * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_parameters: used to initialize an instance in JSON format * The module must validate itself its parameters * @parameter cls: will contain an allocated void * pointer that will be sent back * as void * in all module functions * */ json_t * user_module_init(struct config_module * config, int readonly, json_t * j_parameters, void ** cls); ``` ```C /** * * user_module_close * * Close an instance of this module declared in Glewlwyd service. * You must free the memory previously allocated in * the user_module_init function as void * cls * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int user_module_close(struct config_module * config, void * cls); ``` ```C /** * * user_module_count_total * * Return the total number of users handled by this module corresponding * to the given pattern * * @return value: The total of corresponding users * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter pattern: The pattern to match for the users. How the * pattern is used is up to the implementation. * Glewlwyd recommends to match the pattern with the * username, name and e-mail value for each users * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ size_t user_module_count_total(struct config_module * config, const char * pattern, void * cls); ``` ```C /** * * user_module_get_list * * Return a list of users handled by this module corresponding * to the given pattern between the specified offset and limit * These are the user objects returned to the administrator * * @return value: A list of corresponding users or an empty list * using the following JSON format: {"result":G_OK,"list":[{user object}]} * On error, this function must return another value for "result" * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter pattern: The pattern to match for the users. How the * pattern is used is up to the implementation. * Glewlwyd recommends to match the pattern with the * username, name and e-mail value for each users * @pattern offset: The offset to reduce the returned list among the total list * @pattern limit: The maximum number of users to return * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ json_t * user_module_get_list(struct config_module * config, const char * pattern, size_t offset, size_t limit, void * cls); ``` ```C /** * * user_module_get * * Return a user object handled by this module corresponding * to the username specified * This is the user object returned to the administrator * * @return value: G_OK and the corresponding user * G_ERROR_NOT_FOUND if username is not found * The returned format is {"result":G_OK,"user":{user object}} * On error, this function must return another value for "result" * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: the username to match, must be case insensitive * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ json_t * user_module_get(struct config_module * config, const char * username, void * cls); ``` ```C /** * * user_module_get_profile * * Return a user object handled by this module corresponding * to the username specified. * This is the user object returned to the connected user, may be different from the * user_module_get object format if a connected user must have access to different data * * @return value: G_OK and the corresponding user * G_ERROR_NOT_FOUND if username is not found * The returned format is {"result":G_OK,"user":{user object}} * On error, this function must return another value for "result" * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: the username to match, must be case insensitive * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ json_t * user_module_get_profile(struct config_module * config, const char * username, void * cls); ``` ```C /** * * user_module_is_valid * * Validate if a user is valid to be saved for the specified mode * * @return value: G_OK if the user is valid * G_ERROR_PARAM and an array containing the errors in string format * The returned format is {"result":G_OK} on success * {"result":G_ERROR_PARAM,"error":["error 1","error 2"]} on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: the username to match, must be case insensitive * @parameter j_user: The user to validate * @parameter mode: The mode corresponding to the context, values available are: * - GLEWLWYD_IS_VALID_MODE_ADD: Add a user by an administrator * Note: in this mode, the module musn't check for already existing user, * This is already handled by Glewlwyd * - GLEWLWYD_IS_VALID_MODE_UPDATE: Update a user by an administrator * - GLEWLWYD_IS_VALID_MODE_UPDATE_PROFILE: Update a user by him or * herself in the profile context * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ json_t * user_module_is_valid(struct config_module * config, const char * username, json_t * j_user, int mode, void * cls); ``` ```C /** * * user_module_add * * Add a new user by an administrator * * @return value: G_OK on success * Another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_user: The user to add * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int user_module_add(struct config_module * config, json_t * j_user, void * cls); ``` ```C /** * * user_module_update * * Update an existing user by an administrator * * @return value: G_OK on success * Another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: the username to match, must be case insensitive * @parameter j_user: The user to update. If this function must replace all values or * only the given ones or any other solution is up to the implementation * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int user_module_update(struct config_module * config, const char * username, json_t * j_user, void * cls); ``` ```C /** * * user_module_update_profile * * Update an existing user in the profile context * * @return value: G_OK on success * Another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: the username to match, must be case insensitive * @parameter j_user: The user to update. If this function must replace all values or * only the given ones or any other solution is up to the implementation * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int user_module_update_profile(struct config_module * config, const char * username, json_t * j_user, void * cls); ``` ```C /** * * user_module_delete * * Delete an existing user by an administrator * * @return value: G_OK on success * Another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: the username to match, must be case insensitive * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int user_module_delete(struct config_module * config, const char * username, void * cls); ``` ```C /** * * user_module_check_password * * Validate the password of an existing user * * @return value: G_OK on success * Another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: the username to match, must be case insensitive * @parameter password: the password to validate * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int user_module_check_password(struct config_module * config, const char * username, const char * password, void * cls); ``` ```C /** * * user_module_update_password * * Update the password only of an existing user * * @return value: G_OK on success * Another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: the username to match, must be case insensitive * @parameter new_password: the new password * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int user_module_update_password(struct config_module * config, const char * username, const char * new_password, void * cls); ``` glewlwyd-2.6.1/src/user/database.c000066400000000000000000002106541415646314000170470ustar00rootroot00000000000000/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * Database user module * * Copyright 2016-2020 Nicolas Mora * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . * */ #include #include #include #include #include #include "glewlwyd-common.h" #define G_TABLE_USER "g_user" #define G_TABLE_USER_SCOPE "g_user_scope" #define G_TABLE_USER_SCOPE_USER "g_user_scope_user" #define G_TABLE_USER_PROPERTY "g_user_property" #define G_TABLE_USER_PASSWORD "g_user_password" #define G_PBKDF2_ITERATOR_SEP ',' struct mod_parameters { int use_glewlwyd_connection; digest_algorithm hash_algorithm; struct _h_connection * conn; json_t * j_params; int multiple_passwords; unsigned int PBKDF2_iterations; struct config_module * config_glewlwyd; }; static json_t * is_user_database_parameters_valid(json_t * j_params) { json_t * j_return, * j_error = json_array(), * j_element = NULL; const char * field = NULL; if (j_error != NULL) { if (!json_is_object(j_params)) { json_array_append_new(j_error, json_string("parameters must be a JSON object")); } else { if (json_object_get(j_params, "use-glewlwyd-connection") != NULL && !json_is_boolean(json_object_get(j_params, "use-glewlwyd-connection"))) { json_array_append_new(j_error, json_string("use-glewlwyd-connection must be a boolean")); } if (json_object_get(j_params, "use-glewlwyd-connection") == json_false()) { if (json_object_get(j_params, "connection-type") == NULL || !json_is_string(json_object_get(j_params, "connection-type")) || (0 != o_strcmp("sqlite", json_string_value(json_object_get(j_params, "connection-type"))) && 0 != o_strcmp("mariadb", json_string_value(json_object_get(j_params, "connection-type"))) && 0 != o_strcmp("postgre", json_string_value(json_object_get(j_params, "connection-type"))))) { json_array_append_new(j_error, json_string("connection-type is mandatory and must be one of the following values: 'sqlite', 'mariadb', 'postgre'")); } else if (0 == o_strcmp("sqlite", json_string_value(json_object_get(j_params, "connection-type")))) { if (json_object_get(j_params, "sqlite-dbpath") == NULL || !json_is_string(json_object_get(j_params, "sqlite-dbpath"))) { json_array_append_new(j_error, json_string("sqlite-dbpath is mandatory and must be a string")); } } else if (0 == o_strcmp("mariadb", json_string_value(json_object_get(j_params, "connection-type")))) { if (json_object_get(j_params, "mariadb-host") == NULL || !json_is_string(json_object_get(j_params, "mariadb-host"))) { json_array_append_new(j_error, json_string("mariadb-host is mandatory and must be a string")); } if (json_object_get(j_params, "mariadb-user") == NULL || !json_is_string(json_object_get(j_params, "mariadb-user"))) { json_array_append_new(j_error, json_string("mariadb-user is mandatory and must be a string")); } if (json_object_get(j_params, "mariadb-password") == NULL || !json_is_string(json_object_get(j_params, "mariadb-password"))) { json_array_append_new(j_error, json_string("mariadb-password is mandatory and must be a string")); } if (json_object_get(j_params, "mariadb-dbname") == NULL || !json_is_string(json_object_get(j_params, "mariadb-dbname"))) { json_array_append_new(j_error, json_string("mariadb-dbname is mandatory and must be a string")); } if (json_object_get(j_params, "mariadb-port") != NULL && (!json_is_integer(json_object_get(j_params, "mariadb-port")) || json_integer_value(json_object_get(j_params, "mariadb-port")) < 0)) { json_array_append_new(j_error, json_string("mariadb-port is optional and must be a positive integer (default: 0)")); } } else if (0 == o_strcmp("postgre", json_string_value(json_object_get(j_params, "connection-type")))) { if (json_object_get(j_params, "postgre-conninfo") == NULL || !json_is_string(json_object_get(j_params, "postgre-conninfo"))) { json_array_append_new(j_error, json_string("postgre-conninfo is mandatory and must be a string")); } } } if (json_object_get(j_params, "data-format") != NULL) { if (!json_is_object(json_object_get(j_params, "data-format"))) { json_array_append_new(j_error, json_string("data-format is optional and must be a JSON object")); } else { json_object_foreach(json_object_get(j_params, "data-format"), field, j_element) { if (0 == o_strcmp(field, "username") || 0 == o_strcmp(field, "name") || 0 == o_strcmp(field, "email") || 0 == o_strcmp(field, "enabled") || 0 == o_strcmp(field, "password")) { json_array_append_new(j_error, json_string("data-format can not have settings for properties 'username', 'name', 'email', 'enabled' or 'password'")); } else { if (json_object_get(j_element, "multiple") != NULL && !json_is_boolean(json_object_get(j_element, "multiple"))) { json_array_append_new(j_error, json_string("multiple is optional and must be a boolean (default: false)")); } if (json_object_get(j_element, "read") != NULL && !json_is_boolean(json_object_get(j_element, "read"))) { json_array_append_new(j_error, json_string("read is optional and must be a boolean (default: true)")); } if (json_object_get(j_element, "write") != NULL && !json_is_boolean(json_object_get(j_element, "write"))) { json_array_append_new(j_error, json_string("write is optional and must be a boolean (default: true)")); } if (json_object_get(j_element, "profile-read") != NULL && !json_is_boolean(json_object_get(j_element, "profile-read"))) { json_array_append_new(j_error, json_string("profile-read is optional and must be a boolean (default: false)")); } if (json_object_get(j_element, "profile-write") != NULL && !json_is_boolean(json_object_get(j_element, "profile-write"))) { json_array_append_new(j_error, json_string("profile-write is optional and must be a boolean (default: false)")); } } } } } if (json_object_get(j_params, "pbkdf2-iterations") != NULL && json_integer_value(json_object_get(j_params, "pbkdf2-iterations")) <= 0) { json_array_append_new(j_error, json_string("pbkdf2-iterations is optional and must be a positive non null integer")); } } if (json_array_size(j_error)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_error); } else { j_return = json_pack("{si}", "result", G_OK); } json_decref(j_error); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_user_database_parameters_valid - Error allocating resources for j_error"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } return j_return; } static char * get_pattern_clause(struct mod_parameters * param, const char * pattern) { char * escape_pattern = h_escape_string_with_quotes(param->conn, pattern), * clause = NULL; if (escape_pattern != NULL) { clause = msprintf("IN (SELECT gu_id from " G_TABLE_USER " WHERE gu_username LIKE '%%'||%s||'%%' OR gu_name LIKE '%%'||%s||'%%' OR gu_email LIKE '%%'||%s||'%%')", escape_pattern, escape_pattern, escape_pattern); } o_free(escape_pattern); return clause; } static json_int_t get_user_nb_passwords(struct mod_parameters * param, json_int_t gu_id) { json_t * j_query, * j_result = NULL; int res; json_int_t result = 0; j_query = json_pack("{sss[s]s{sI}}", "table", G_TABLE_USER_PASSWORD, "columns", "COUNT(guw_password) AS nb_passwords", "where", "gu_id", gu_id); res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { result = json_integer_value(json_object_get(json_array_get(j_result, 0), "nb_passwords")); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_nb_passwords database - Error executing j_query"); } return result; } static int append_user_properties(struct mod_parameters * param, json_t * j_user, int profile) { json_t * j_query, * j_result, * j_element = NULL, * j_param_config; int res, ret; size_t index = 0; if (param->conn->type == HOEL_DB_TYPE_MARIADB) { j_query = json_pack("{sss[ssss]s{sO}}", "table", G_TABLE_USER_PROPERTY, "columns", "gup_name AS name", "gup_value_tiny AS value_tiny", "gup_value_small AS value_small", "gup_value_medium AS value_medium", "where", "gu_id", json_object_get(j_user, "gu_id")); res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result, index, j_element) { j_param_config = json_object_get(json_object_get(param->j_params, "data-format"), json_string_value(json_object_get(j_element, "name"))); if ((!profile && json_object_get(j_param_config, "read") != json_false()) || (profile && json_object_get(j_param_config, "profile-read") != json_false())) { if (json_object_get(j_element, "value_tiny") != json_null()) { if (json_object_get(j_param_config, "multiple") == json_true()) { if (json_object_get(j_user, json_string_value(json_object_get(j_element, "name"))) == NULL) { json_object_set_new(j_user, json_string_value(json_object_get(j_element, "name")), json_array()); } json_array_append(json_object_get(j_user, json_string_value(json_object_get(j_element, "name"))), json_object_get(j_element, "value_tiny")); } else { json_object_set(j_user, json_string_value(json_object_get(j_element, "name")), json_object_get(j_element, "value_tiny")); } } else if (json_object_get(j_element, "value_small") != json_null()) { if (json_object_get(j_param_config, "multiple") == json_true()) { if (json_object_get(j_user, json_string_value(json_object_get(j_element, "name"))) == NULL) { json_object_set_new(j_user, json_string_value(json_object_get(j_element, "name")), json_array()); } json_array_append(json_object_get(j_user, json_string_value(json_object_get(j_element, "name"))), json_object_get(j_element, "value_small")); } else { json_object_set(j_user, json_string_value(json_object_get(j_element, "name")), json_object_get(j_element, "value_small")); } } else if (json_object_get(j_element, "value_medium") != json_null()) { if (json_object_get(j_param_config, "multiple") == json_true()) { if (json_object_get(j_user, json_string_value(json_object_get(j_element, "name"))) == NULL) { json_object_set_new(j_user, json_string_value(json_object_get(j_element, "name")), json_array()); } json_array_append(json_object_get(j_user, json_string_value(json_object_get(j_element, "name"))), json_object_get(j_element, "value_medium")); } else { json_object_set(j_user, json_string_value(json_object_get(j_element, "name")), json_object_get(j_element, "value_medium")); } } else { if (json_object_get(j_param_config, "multiple") == json_true()) { if (json_object_get(j_user, json_string_value(json_object_get(j_element, "name"))) == NULL) { json_object_set_new(j_user, json_string_value(json_object_get(j_element, "name")), json_array()); } json_array_append(json_object_get(j_user, json_string_value(json_object_get(j_element, "name"))), json_null()); } else { json_object_set(j_user, json_string_value(json_object_get(j_element, "name")), json_null()); } } } } ret = G_OK; json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "append_user_properties database - Error executing j_query"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { j_query = json_pack("{sss[ss]s{sO}}", "table", G_TABLE_USER_PROPERTY, "columns", "gup_name AS name", "gup_value AS value", "where", "gu_id", json_object_get(j_user, "gu_id")); res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result, index, j_element) { j_param_config = json_object_get(json_object_get(param->j_params, "data-format"), json_string_value(json_object_get(j_element, "name"))); if ((!profile && json_object_get(j_param_config, "read") != json_false()) || (profile && json_object_get(j_param_config, "profile-read") != json_false())) { if (json_object_get(j_element, "value") != json_null()) { if (json_object_get(j_param_config, "multiple") == json_true()) { if (json_object_get(j_user, json_string_value(json_object_get(j_element, "name"))) == NULL) { json_object_set_new(j_user, json_string_value(json_object_get(j_element, "name")), json_array()); } json_array_append(json_object_get(j_user, json_string_value(json_object_get(j_element, "name"))), json_object_get(j_element, "value")); } else { json_object_set(j_user, json_string_value(json_object_get(j_element, "name")), json_object_get(j_element, "value")); } } else { if (json_object_get(j_param_config, "multiple") == json_true()) { if (json_object_get(j_user, json_string_value(json_object_get(j_element, "name"))) == NULL) { json_object_set_new(j_user, json_string_value(json_object_get(j_element, "name")), json_array()); } json_array_append(json_object_get(j_user, json_string_value(json_object_get(j_element, "name"))), json_null()); } else { json_object_set(j_user, json_string_value(json_object_get(j_element, "name")), json_null()); } } } } ret = G_OK; json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "append_user_properties database - Error executing j_query"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } return ret; } static json_t * database_user_scope_get(struct mod_parameters * param, json_int_t gu_id) { json_t * j_query, * j_result, * j_return, * j_array, * j_scope = NULL; int res; size_t index = 0; char * scope_clause = msprintf("IN (SELECT gus_id from " G_TABLE_USER_SCOPE_USER " WHERE gu_id = %"JSON_INTEGER_FORMAT")", gu_id); j_query = json_pack("{sss[s]s{s{ssss}}ss}", "table", G_TABLE_USER_SCOPE, "columns", "gus_name AS name", "where", "gus_id", "operator", "raw", "value", scope_clause, "order_by", "gus_id"); o_free(scope_clause); res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { j_array = json_array(); if (j_array != NULL) { json_array_foreach(j_result, index, j_scope) { json_array_append(j_array, json_object_get(j_scope, "name")); } j_return = json_pack("{sisO}", "result", G_OK, "scope", j_array); json_decref(j_array); } else { y_log_message(Y_LOG_LEVEL_ERROR, "database_user_scope_get database - Error allocating resources for j_array"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "database_user_scope_get database - Error executing j_query"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } static json_t * database_user_get(const char * username, void * cls, int profile) { struct mod_parameters * param = (struct mod_parameters *)cls; json_t * j_query, * j_result, * j_scope, * j_return; int res; char * username_escaped, * username_clause; username_escaped = h_escape_string_with_quotes(param->conn, username); username_clause = msprintf(" = UPPER(%s)", username_escaped); j_query = json_pack("{sss[sssss]s{s{ssss}}}", "table", G_TABLE_USER, "columns", "gu_id", "gu_username AS username", "gu_name AS name", "gu_email AS email", "gu_enabled", "where", "UPPER(gu_username)", "operator", "raw", "value", username_clause); o_free(username_clause); o_free(username_escaped); res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_scope = database_user_scope_get(param, json_integer_value(json_object_get(json_array_get(j_result, 0), "gu_id"))); if (check_result_value(j_scope, G_OK)) { json_object_set(json_array_get(j_result, 0), "scope", json_object_get(j_scope, "scope")); json_object_set(json_array_get(j_result, 0), "enabled", (json_integer_value(json_object_get(json_array_get(j_result, 0), "gu_enabled"))?json_true():json_false())); if (param->multiple_passwords) { json_object_set_new(json_array_get(j_result, 0), "password", json_integer(get_user_nb_passwords(param, json_integer_value(json_object_get(json_array_get(j_result, 0), "gu_id"))))); } if (append_user_properties(param, json_array_get(j_result, 0), profile) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "database_user_get database - Error append_user_properties"); } json_object_del(json_array_get(j_result, 0), "gu_enabled"); json_object_del(json_array_get(j_result, 0), "gu_id"); j_return = json_pack("{sisO}", "result", G_OK, "user", json_array_get(j_result, 0)); } else { j_return = json_pack("{si}", "result", G_ERROR); y_log_message(Y_LOG_LEVEL_ERROR, "database_user_get database - Error database_user_scope_get"); } json_decref(j_scope); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "database_user_get database - Error executing j_query"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } static char * get_password_clause_write(struct mod_parameters * param, const char * password) { char * clause = NULL, * password_encoded, digest[1024] = {0}; if (!o_strlen(password)) { clause = o_strdup("''"); } else if (param->conn->type == HOEL_DB_TYPE_SQLITE) { if (generate_digest_pbkdf2(password, param->PBKDF2_iterations, NULL, digest)) { clause = msprintf("'%s%c%u'", digest, G_PBKDF2_ITERATOR_SEP, param->PBKDF2_iterations); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_password_clause_write database - Error generate_digest_pbkdf2"); } } else if (param->conn->type == HOEL_DB_TYPE_MARIADB) { password_encoded = h_escape_string_with_quotes(param->conn, password); if (password_encoded != NULL) { clause = msprintf("PASSWORD(%s)", password_encoded); o_free(password_encoded); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_password_clause_write database - Error h_escape_string_with_quotes (mariadb)"); } } else if (param->conn->type == HOEL_DB_TYPE_PGSQL) { password_encoded = h_escape_string_with_quotes(param->conn, password); if (password_encoded != NULL) { clause = msprintf("crypt(%s, gen_salt('bf'))", password_encoded); o_free(password_encoded); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_password_clause_write database - Error h_escape_string_with_quotes (postgre)"); } } return clause; } static int update_password_list(struct mod_parameters * param, json_int_t gu_id, const char ** new_passwords, size_t new_passwords_len, int add) { json_t * j_query, * j_result; int res, ret; size_t i; char * clause_password; if (add) { j_query = json_pack("{sss[]}", "table", G_TABLE_USER_PASSWORD, "values"); for (i=0; iconn, j_query, NULL); } else { res = H_OK; } json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "update_password_list - Error executing j_query (1)"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { j_query = json_pack("{sss[s]s{sI}}", "table", G_TABLE_USER_PASSWORD, "columns", "guw_password", "where", "gu_id", gu_id); res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { j_query = json_pack("{sss{sI}}", "table", G_TABLE_USER_PASSWORD, "where", "gu_id", gu_id); res = h_delete(param->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_query = json_pack("{sss[]}", "table", G_TABLE_USER_PASSWORD, "values"); for (i=0; iconn, j_query, NULL); } else { res = H_OK; } json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "update_password_list - Error executing j_query (4)"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "update_password_list - Error executing j_query (3)"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "update_password_list - Error executing j_query (2)"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } return ret; } static char ** get_salt_from_password_hash(struct mod_parameters * param, const char * username, json_t * j_iterations) { json_t * j_query, * j_result, * j_element = 0; int res; unsigned char password_b64_decoded[1024] = {0}; char * salt = NULL, * username_escaped, * username_clause, ** salt_list = NULL, * str_iterator; size_t password_b64_decoded_len, index = 0, gc_password_len; if (j_iterations != NULL) { username_escaped = h_escape_string_with_quotes(param->conn, username); username_clause = msprintf("IN (SELECT gu_id FROM "G_TABLE_USER" WHERE UPPER(gu_username) = UPPER(%s))", username_escaped); j_query = json_pack("{sss[s]s{s{ssss}}}", "table", G_TABLE_USER_PASSWORD, "columns", "guw_password", "where", "gu_id", "operator", "raw", "value", username_clause); o_free(username_clause); o_free(username_escaped); res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { if ((salt_list = o_malloc((json_array_size(j_result)+1)*sizeof(char *))) != NULL) { json_array_foreach(j_result, index, j_element) { if ((str_iterator = o_strchr(json_string_value(json_object_get(j_element, "guw_password")), G_PBKDF2_ITERATOR_SEP)) != NULL) { gc_password_len = o_strchr(json_string_value(json_object_get(j_element, "guw_password")), G_PBKDF2_ITERATOR_SEP) - json_string_value(json_object_get(j_element, "guw_password")); json_array_append_new(j_iterations, json_integer(strtol(str_iterator+1, NULL, 10))); } else { gc_password_len = json_string_length(json_object_get(j_element, "guw_password")); json_array_append_new(j_iterations, json_integer(0)); } if (json_string_length(json_object_get(j_element, "guw_password")) && o_base64_decode((const unsigned char *)json_string_value(json_object_get(j_element, "guw_password")), gc_password_len, password_b64_decoded, &password_b64_decoded_len)) { if ((salt = o_strdup((const char *)password_b64_decoded + password_b64_decoded_len - GLEWLWYD_DEFAULT_SALT_LENGTH)) != NULL) { salt_list[index] = salt; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_salt_from_password_hash - Error extracting salt"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_salt_from_password_hash - Error o_base64_decode"); } } salt_list[json_array_size(j_result)] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_salt_from_password_hash - Error allocatig resources for salt_list (1)"); } } else { if ((salt_list = o_malloc(sizeof(char *))) != NULL) { salt_list[0] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_salt_from_password_hash - Error allocatig resources for salt_list (2)"); } } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_salt_from_password_hash - Error executing j_query"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_salt_from_password_hash - Error j_iterations is NULL"); } return salt_list; } static char * get_password_clause_check(struct mod_parameters * param, const char * username, const char * password) { char * clause = NULL, * password_encoded, digest[1024] = {0}, ** salt_list, * username_escaped = h_escape_string_with_quotes(param->conn, username); size_t i; json_t * j_iterations = json_array(); unsigned int iterations; if (param->conn->type == HOEL_DB_TYPE_SQLITE) { if ((salt_list = get_salt_from_password_hash(param, username, j_iterations)) != NULL) { clause = o_strdup("IN ("); for (i=0; salt_list[i]!=NULL; i++) { iterations = (unsigned int)json_integer_value(json_array_get(j_iterations, i)); if (generate_digest_pbkdf2(password, iterations?iterations:1000, salt_list[i], digest)) { if (!i) { if (iterations) { clause = mstrcatf(clause, "'%s%c%u'", digest, G_PBKDF2_ITERATOR_SEP, iterations); } else { clause = mstrcatf(clause, "'%s'", digest); } } else { if (iterations) { clause = mstrcatf(clause, ",'%s%c%u'", digest, G_PBKDF2_ITERATOR_SEP, iterations); } else { clause = mstrcatf(clause, ",'%s'", digest); } } digest[0] = '\0'; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_password_clause_check database - Error generate_digest_pbkdf2"); } } clause = mstrcatf(clause, ")"); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_password_clause_check database - Error get_salt_from_password_hash"); } free_string_array(salt_list); } else if (param->conn->type == HOEL_DB_TYPE_MARIADB) { password_encoded = h_escape_string_with_quotes(param->conn, password); if (password_encoded != NULL) { clause = msprintf("= PASSWORD(%s)", password_encoded); o_free(password_encoded); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_password_clause_check database - Error h_escape_string_with_quotes (mariadb)"); } } else if (param->conn->type == HOEL_DB_TYPE_PGSQL) { password_encoded = h_escape_string_with_quotes(param->conn, password); if (password_encoded != NULL) { clause = msprintf("= crypt(%s, guw_password)", password_encoded); o_free(password_encoded); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_password_clause_check database - Error h_escape_string_with_quotes (postgre)"); } } o_free(username_escaped); json_decref(j_iterations); return clause; } static json_t * get_property_value_db(struct mod_parameters * param, const char * name, json_t * j_property, json_int_t gu_id) { if (param->conn->type == HOEL_DB_TYPE_MARIADB) { if (json_string_length(j_property) < 512) { return json_pack("{sIsssOsOsO}", "gu_id", gu_id, "gup_name", name, "gup_value_tiny", j_property, "gup_value_small", json_null(), "gup_value_medium", json_null()); } else if (json_string_length(j_property) < 16*1024) { return json_pack("{sIsssOsOsO}", "gu_id", gu_id, "gup_name", name, "gup_value_tiny", json_null(), "gup_value_small", j_property, "gup_value_medium", json_null()); } else if (json_string_length(j_property) < 16*1024*1024) { return json_pack("{sIsssOsOsO}", "gu_id", gu_id, "gup_name", name, "gup_value_tiny", json_null(), "gup_value_small", json_null(), "gup_value_medium", j_property); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_property_value_db - Error value is too large"); return NULL; } } else { return json_pack("{sIsssO}", "gu_id", gu_id, "gup_name", name, "gup_value", j_property); } } static int save_user_properties(struct mod_parameters * param, json_t * j_user, json_int_t gu_id, int profile) { json_t * j_property = NULL, * j_query, * j_array = json_array(), * j_format, * j_property_value = NULL; const char * name = NULL; int ret, res; size_t index = 0; if (j_array != NULL) { json_object_foreach(j_user, name, j_property) { if (0 != o_strcmp(name, "username") && 0 != o_strcmp(name, "name") && 0 != o_strcmp(name, "password") && 0 != o_strcmp(name, "email") && 0 != o_strcmp(name, "enabled") && 0 != o_strcmp(name, "scope")) { j_format = json_object_get(json_object_get(param->j_params, "data-format"), name); if ((!profile && json_object_get(j_format, "write") != json_false()) || (profile && json_object_get(j_format, "profile-write") == json_true())) { if (!json_is_array(j_property)) { json_array_append_new(j_array, get_property_value_db(param, name, j_property, gu_id)); } else { json_array_foreach(j_property, index, j_property_value) { if (j_property_value != json_null()) { json_array_append_new(j_array, get_property_value_db(param, name, j_property_value, gu_id)); } } } } } } // Delete old values j_query = json_pack("{sss{sI}}", "table", G_TABLE_USER_PROPERTY, "where", "gu_id", gu_id); res = h_delete(param->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { // Add new values if (json_array_size(j_array)) { j_query = json_pack("{sssO}", "table", G_TABLE_USER_PROPERTY, "values", j_array); res = h_insert(param->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "insert_user_properties database - Error executing j_query insert"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { ret = G_OK; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "insert_user_properties database - Error executing j_query delete"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } json_decref(j_array); } else { y_log_message(Y_LOG_LEVEL_ERROR, "insert_user_properties database - Error allocating resources for j_array"); ret = G_ERROR_MEMORY; } return ret; } static int save_user_scope(struct mod_parameters * param, json_t * j_scope, json_int_t gu_id) { json_t * j_query, * j_result, * j_element = NULL, * j_new_scope_id; int res, ret; char * scope_clause; size_t index = 0; j_query = json_pack("{sss{sI}}", "table", G_TABLE_USER_SCOPE_USER, "where", "gu_id", gu_id); res = h_delete(param->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; if (json_is_array(j_scope)) { json_array_foreach(j_scope, index, j_element) { j_query = json_pack("{sss[s]s{sO}}", "table", G_TABLE_USER_SCOPE, "columns", "gus_id", "where", "gus_name", j_element); res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { j_query = json_pack("{sss{sIsO}}", "table", G_TABLE_USER_SCOPE_USER, "values", "gu_id", gu_id, "gus_id", json_object_get(json_array_get(j_result, 0), "gus_id")); res = h_insert(param->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "save_user_scope database - Error executing j_query insert scope_user (1)"); } } else { j_query = json_pack("{sss{sO}}", "table", G_TABLE_USER_SCOPE, "values", "gus_name", j_element); res = h_insert(param->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_new_scope_id = h_last_insert_id(param->conn); if (j_new_scope_id != NULL) { j_query = json_pack("{sss{sIsO}}", "table", G_TABLE_USER_SCOPE_USER, "values", "gu_id", gu_id, "gus_id", j_new_scope_id); res = h_insert(param->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "save_user_scope database - Error executing j_query insert scope_user (2)"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "save_user_scope database - Error h_last_insert_id"); } json_decref(j_new_scope_id); } else { y_log_message(Y_LOG_LEVEL_ERROR, "save_user_scope database - Error executing j_query insert scope"); } } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "save_user_scope database - Error executing j_query select scope"); } } } // Clean orphan user_scope scope_clause = msprintf("NOT IN (SELECT DISTINCT(gus_id) FROM " G_TABLE_USER_SCOPE_USER ")"); j_query = json_pack("{sss{s{ssss}}}", "table", G_TABLE_USER_SCOPE, "where", "gus_id", "operator", "raw", "value", scope_clause); o_free(scope_clause); res = h_delete(param->conn, j_query, NULL); json_decref(j_query); if (res != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "save_user_scope database - Error executing j_query delete empty scopes"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "save_user_scope database - Error executing j_query delete"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } json_t * user_module_load(struct config_module * config) { UNUSED(config); return json_pack("{si ss ss ss sf}", "result", G_OK, "name", "database", "display_name", "Database backend user module", "description", "Module to store users in the database", "api_version", 2.5); } int user_module_unload(struct config_module * config) { UNUSED(config); return G_OK; } json_t * user_module_init(struct config_module * config, int readonly, int multiple_passwords, json_t * j_parameters, void ** cls) { UNUSED(readonly); json_t * j_result, * j_return; char * error_message; j_result = is_user_database_parameters_valid(j_parameters); if (check_result_value(j_result, G_OK)) { *cls = o_malloc(sizeof(struct mod_parameters)); if (*cls != NULL) { ((struct mod_parameters *)*cls)->j_params = json_incref(j_parameters); ((struct mod_parameters *)*cls)->hash_algorithm = config->hash_algorithm; ((struct mod_parameters *)*cls)->multiple_passwords = multiple_passwords; ((struct mod_parameters *)*cls)->config_glewlwyd = config; if (json_object_get(j_parameters, "use-glewlwyd-connection") != json_false()) { ((struct mod_parameters *)*cls)->use_glewlwyd_connection = 0; ((struct mod_parameters *)*cls)->conn = config->conn; } else { ((struct mod_parameters *)*cls)->use_glewlwyd_connection = 1; if (0 == o_strcmp(json_string_value(json_object_get(j_parameters, "connection-type")), "sqlite")) { ((struct mod_parameters *)*cls)->conn = h_connect_sqlite(json_string_value(json_object_get(j_parameters, "sqlite-dbpath"))); } else if (0 == o_strcmp(json_string_value(json_object_get(j_parameters, "connection-type")), "mariadb")) { ((struct mod_parameters *)*cls)->conn = h_connect_mariadb(json_string_value(json_object_get(j_parameters, "mariadb-host")), json_string_value(json_object_get(j_parameters, "mariadb-user")), json_string_value(json_object_get(j_parameters, "mariadb-password")), json_string_value(json_object_get(j_parameters, "mariadb-dbname")), json_integer_value(json_object_get(j_parameters, "mariadb-port")), NULL); } else if (0 == o_strcmp(json_string_value(json_object_get(j_parameters, "connection-type")), "postgre")) { ((struct mod_parameters *)*cls)->conn = h_connect_pgsql(json_string_value(json_object_get(j_parameters, "postgre-conninfo"))); } } if (((struct mod_parameters *)*cls)->conn != NULL) { j_return = json_pack("{si}", "result", G_OK); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_init database - Error connecting to database"); j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "Error connecting to database"); } if (json_object_get(j_parameters, "pbkdf2-iterations") != NULL) { ((struct mod_parameters *)*cls)->PBKDF2_iterations = (unsigned int)json_integer_value(json_object_get(j_parameters, "pbkdf2-iterations")); } else { ((struct mod_parameters *)*cls)->PBKDF2_iterations = G_PBKDF2_ITERATOR_DEFAULT; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_init database - Error allocating resources for cls"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } } else if (check_result_value(j_result, G_ERROR_PARAM)) { error_message = json_dumps(json_object_get(j_result, "error"), JSON_COMPACT); y_log_message(Y_LOG_LEVEL_ERROR, "user_module_init database - Error parsing parameters"); y_log_message(Y_LOG_LEVEL_ERROR, error_message); o_free(error_message); j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", json_object_get(j_result, "error")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_init database - Error is_user_database_parameters_valid"); j_return = json_pack("{sis[s]}", "result", G_ERROR, "error", "internal error"); } json_decref(j_result); return j_return; } int user_module_close(struct config_module * config, void * cls) { UNUSED(config); int ret; if (cls != NULL) { if (((struct mod_parameters *)cls)->use_glewlwyd_connection) { if (h_close_db(((struct mod_parameters *)cls)->conn) != H_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_close database - Error h_close_db"); config->glewlwyd_module_callback_metrics_increment_counter(config, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } else { ret = G_OK; } } else { ret = G_OK; } json_decref(((struct mod_parameters *)cls)->j_params); o_free(cls); } else { ret = G_ERROR_PARAM; } return ret; } size_t user_module_count_total(struct config_module * config, const char * pattern, void * cls) { UNUSED(config); struct mod_parameters * param = (struct mod_parameters *)cls; json_t * j_query, * j_result = NULL; int res; size_t ret = 0; char * pattern_clause; j_query = json_pack("{sss[s]}", "table", G_TABLE_USER, "columns", "count(gu_id) AS total"); if (o_strlen(pattern)) { pattern_clause = get_pattern_clause(param, pattern); json_object_set_new(j_query, "where", json_pack("{s{ssss}}", "gu_id", "operator", "raw", "value", pattern_clause)); o_free(pattern_clause); } res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { ret = (size_t)json_integer_value(json_object_get(json_array_get(j_result, 0), "total")); json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_count_total database - Error executing j_query"); } return ret; } json_t * user_module_get_list(struct config_module * config, const char * pattern, size_t offset, size_t limit, void * cls) { UNUSED(config); struct mod_parameters * param = (struct mod_parameters *)cls; json_t * j_query, * j_result, * j_element = NULL, * j_scope, * j_return; int res; char * pattern_clause; size_t index = 0; j_query = json_pack("{sss[sssss]sisiss}", "table", G_TABLE_USER, "columns", "gu_id", "gu_username AS username", "gu_name AS name", "gu_email AS email", "gu_enabled", "offset", offset, "limit", limit, "order_by", "gu_username"); if (o_strlen(pattern)) { pattern_clause = get_pattern_clause(param, pattern); json_object_set_new(j_query, "where", json_pack("{s{ssss}}", "gu_id", "operator", "raw", "value", pattern_clause)); o_free(pattern_clause); } res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { json_array_foreach(j_result, index, j_element) { j_scope = database_user_scope_get(param, json_integer_value(json_object_get(j_element, "gu_id"))); if (check_result_value(j_scope, G_OK)) { json_object_set(j_element, "scope", json_object_get(j_scope, "scope")); json_object_set(j_element, "enabled", (json_integer_value(json_object_get(j_element, "gu_enabled"))?json_true():json_false())); if (param->multiple_passwords) { json_object_set_new(j_element, "password", json_integer(get_user_nb_passwords(param, json_integer_value(json_object_get(j_element, "gu_id"))))); } if (append_user_properties(param, j_element, 0) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_get_list database - Error append_user_properties"); } json_object_del(j_element, "gu_enabled"); json_object_del(j_element, "gu_id"); } else { j_return = json_pack("{si}", "result", G_ERROR); y_log_message(Y_LOG_LEVEL_ERROR, "user_module_get_list database - Error database_user_scope_get"); } json_decref(j_scope); } j_return = json_pack("{sisO}", "result", G_OK, "list", j_result); json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_get_list database - Error executing j_query"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); j_return = json_pack("{si}", "result", G_ERROR_DB); } return j_return; } json_t * user_module_get(struct config_module * config, const char * username, void * cls) { UNUSED(config); return database_user_get(username, cls, 0); } json_t * user_module_get_profile(struct config_module * config, const char * username, void * cls) { UNUSED(config); return database_user_get(username, cls, 1); } json_t * user_module_is_valid(struct config_module * config, const char * username, json_t * j_user, int mode, void * cls) { struct mod_parameters * param = (struct mod_parameters *)cls; json_t * j_result = json_array(), * j_element, * j_format, * j_value, * j_return = NULL, * j_cur_user; char * message; size_t index = 0; const char * property; if (j_result != NULL) { if (mode == GLEWLWYD_IS_VALID_MODE_ADD) { if (!json_is_string(json_object_get(j_user, "username")) || json_string_length(json_object_get(j_user, "username")) > 128) { json_array_append_new(j_result, json_string("username is mandatory and must be a string (maximum 128 characters)")); } else { j_cur_user = user_module_get(config, json_string_value(json_object_get(j_user, "username")), cls); if (check_result_value(j_cur_user, G_OK)) { json_array_append_new(j_result, json_string("username already exist")); } else if (!check_result_value(j_cur_user, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_is_valid database - Error user_module_get"); } json_decref(j_cur_user); } } else if ((mode == GLEWLWYD_IS_VALID_MODE_UPDATE || mode == GLEWLWYD_IS_VALID_MODE_UPDATE_PROFILE) && username == NULL) { json_array_append_new(j_result, json_string("username is mandatory on update mode")); } if (mode != GLEWLWYD_IS_VALID_MODE_UPDATE_PROFILE) { if (json_object_get(j_user, "scope") != NULL) { if (!json_is_array(json_object_get(j_user, "scope"))) { json_array_append_new(j_result, json_string("scope must be a JSON array of string")); } else { json_array_foreach(json_object_get(j_user, "scope"), index, j_element) { if (!json_is_string(j_element) || !json_string_length(j_element)) { json_array_append_new(j_result, json_string("scope must be a JSON array of string")); } } } } } if (param->multiple_passwords) { if (mode != GLEWLWYD_IS_VALID_MODE_UPDATE_PROFILE && json_object_get(j_user, "password") != NULL && !json_is_array(json_object_get(j_user, "password"))) { json_array_append_new(j_result, json_string("password must be an array")); } } else { if (mode != GLEWLWYD_IS_VALID_MODE_UPDATE_PROFILE && json_object_get(j_user, "password") != NULL && !json_is_string(json_object_get(j_user, "password"))) { json_array_append_new(j_result, json_string("password must be a string")); } } if (json_object_get(j_user, "name") != NULL && (!json_is_string(json_object_get(j_user, "name")) || json_string_length(json_object_get(j_user, "name")) > 256)) { json_array_append_new(j_result, json_string("name must be a string (maximum 256 characters)")); } if (json_object_get(j_user, "email") != NULL && (!json_is_string(json_object_get(j_user, "email")) || json_string_length(json_object_get(j_user, "email")) > 512)) { json_array_append_new(j_result, json_string("email must be a string (maximum 512 characters)")); } if (json_object_get(j_user, "enabled") != NULL && !json_is_boolean(json_object_get(j_user, "enabled"))) { json_array_append_new(j_result, json_string("enabled must be a boolean")); } json_object_foreach(j_user, property, j_element) { if (0 != o_strcmp(property, "username") && 0 != o_strcmp(property, "name") && 0 != o_strcmp(property, "email") && 0 != o_strcmp(property, "enabled") && 0 != o_strcmp(property, "password") && 0 != o_strcmp(property, "source") && 0 != o_strcmp(property, "scope")) { j_format = json_object_get(json_object_get(param->j_params, "data-format"), property); if (json_object_get(j_format, "multiple") == json_true()) { if (!json_is_array(j_element)) { message = msprintf("property '%s' must be a JSON array", property); json_array_append_new(j_result, json_string(message)); o_free(message); } else { json_array_foreach(j_element, index, j_value) { if (!json_is_string(j_value) || json_string_length(j_value) > 16*1024*1024) { message = msprintf("property '%s' must contain a string value (maximum 16M characters)", property); json_array_append_new(j_result, json_string(message)); o_free(message); } } } } else { if (!json_is_string(j_element) || json_string_length(j_element) > 16*1024*1024) { message = msprintf("property '%s' must be a string value (maximum 16M characters)", property); json_array_append_new(j_result, json_string(message)); o_free(message); } } } } if (json_array_size(j_result)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_result); } else { j_return = json_pack("{si}", "result", G_OK); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_is_valid database - Error allocating resources for j_result"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } return j_return; } int user_module_add(struct config_module * config, json_t * j_user, void * cls) { UNUSED(config); struct mod_parameters * param = (struct mod_parameters *)cls; json_t * j_query, * j_gu_id; int res, ret; const char ** passwords = NULL; size_t i; j_query = json_pack("{sss{ss}}", "table", G_TABLE_USER, "values", "gu_username", json_string_value(json_object_get(j_user, "username"))); if (json_object_get(j_user, "name") != NULL) { json_object_set(json_object_get(j_query, "values"), "gu_name", json_object_get(j_user, "name")); } if (json_object_get(j_user, "email") != NULL) { json_object_set(json_object_get(j_query, "values"), "gu_email", json_object_get(j_user, "email")); } if (json_object_get(j_user, "enabled") != NULL) { json_object_set_new(json_object_get(j_query, "values"), "gu_enabled", json_object_get(j_user, "enabled")==json_false()?json_integer(0):json_integer(1)); } res = h_insert(param->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { j_gu_id = h_last_insert_id(param->conn); if (save_user_properties(param, j_user, json_integer_value(j_gu_id), 0) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_add database - Error save_user_properties"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } else if (json_object_get(j_user, "scope") != NULL && save_user_scope(param, json_object_get(j_user, "scope"), json_integer_value(j_gu_id)) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_add database - Error save_user_scope"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } else { ret = G_OK; if (param->multiple_passwords) { if (json_array_size(json_object_get(j_user, "password"))) { if ((passwords = o_malloc(json_array_size(json_object_get(j_user, "password"))*sizeof(char *))) != NULL) { for (i=0; iconfig_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } int user_module_update(struct config_module * config, const char * username, json_t * j_user, void * cls) { UNUSED(config); struct mod_parameters * param = (struct mod_parameters *)cls; json_t * j_query, * j_result = NULL; int res, ret; char * username_escaped, * username_clause; const char ** passwords = NULL; size_t i; username_escaped = h_escape_string_with_quotes(param->conn, username); username_clause = msprintf(" = UPPER(%s)", username_escaped); j_query = json_pack("{sss[s]s{s{ssss}}}", "table", G_TABLE_USER, "columns", "gu_id", "where", "UPPER(gu_username)", "operator", "raw", "value", username_clause); res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); o_free(username_clause); o_free(username_escaped); if (res == H_OK && json_array_size(j_result)) { j_query = json_pack("{sss{}s{sO}}", "table", G_TABLE_USER, "set", "where", "gu_id", json_object_get(json_array_get(j_result, 0), "gu_id")); if (json_object_get(j_user, "name") != NULL) { json_object_set(json_object_get(j_query, "set"), "gu_name", json_object_get(j_user, "name")); } if (json_object_get(j_user, "email") != NULL) { json_object_set(json_object_get(j_query, "set"), "gu_email", json_object_get(j_user, "email")); } if (json_object_get(j_user, "enabled") != NULL) { json_object_set_new(json_object_get(j_query, "set"), "gu_enabled", json_object_get(j_user, "enabled")==json_false()?json_integer(0):json_integer(1)); } if (json_object_size(json_object_get(j_query, "set"))) { res = h_update(param->conn, j_query, NULL); } else { res = H_OK; } json_decref(j_query); if (res == H_OK) { if (save_user_properties(param, j_user, json_integer_value(json_object_get(json_array_get(j_result, 0), "gu_id")), 0) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_add database - Error save_user_properties"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } else if (json_object_get(j_user, "scope") != NULL && save_user_scope(param, json_object_get(j_user, "scope"), json_integer_value(json_object_get(json_array_get(j_result, 0), "gu_id"))) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_add database - Error save_user_scope"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } else { ret = G_OK; if (param->multiple_passwords) { if (json_array_size(json_object_get(j_user, "password"))) { if ((passwords = o_malloc(json_array_size(json_object_get(j_user, "password"))*sizeof(char *))) != NULL) { for (i=0; iconfig_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { ret = G_ERROR_NOT_FOUND; } json_decref(j_result); return ret; } int user_module_update_profile(struct config_module * config, const char * username, json_t * j_user, void * cls) { UNUSED(config); struct mod_parameters * param = (struct mod_parameters *)cls; json_t * j_query, * j_result = NULL; int res, ret; char * username_escaped, * username_clause; username_escaped = h_escape_string_with_quotes(param->conn, username); username_clause = msprintf(" = UPPER(%s)", username_escaped); j_query = json_pack("{sss[s]s{s{ssss}}}", "table", G_TABLE_USER, "columns", "gu_id", "where", "UPPER(gu_username)", "operator", "raw", "value", username_clause); res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); o_free(username_clause); o_free(username_escaped); if (res == H_OK) { if (json_array_size(j_result)) { j_query = json_pack("{sss{}sO}", "table", G_TABLE_USER, "set", "where", json_array_get(j_result, 0)); if (json_object_get(j_user, "name") != NULL) { json_object_set(json_object_get(j_query, "set"), "gu_name", json_object_get(j_user, "name")); } if (json_object_size(json_object_get(j_query, "set"))) { if (h_update(param->conn, j_query, NULL) == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update_profile database - Error executing j_query update"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } } else { res = G_OK; } json_decref(j_query); if (res == H_OK) { if (save_user_properties(param, j_user, json_integer_value(json_object_get(json_array_get(j_result, 0), "gu_id")), 0) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update_profile database - Error save_user_properties"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } else { ret = G_OK; } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update_profile database - Error username '%s' not found", username); ret = G_ERROR_NOT_FOUND; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update_profile database - Error executing j_query select"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } json_decref(j_result); return ret; } int user_module_delete(struct config_module * config, const char * username, void * cls) { UNUSED(config); struct mod_parameters * param = (struct mod_parameters *)cls; json_t * j_query; int res, ret; char * username_escaped, * username_clause; username_escaped = h_escape_string_with_quotes(param->conn, username); username_clause = msprintf(" = UPPER(%s)", username_escaped); j_query = json_pack("{sss{s{ssss}}}", "table", G_TABLE_USER, "where", "UPPER(gu_username)", "operator", "raw", "value", username_clause); o_free(username_clause); o_free(username_escaped); res = h_delete(param->conn, j_query, NULL); json_decref(j_query); if (res == H_OK) { ret = G_OK; } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_delete database - Error executing j_query"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } int user_module_check_password(struct config_module * config, const char * username, const char * password, void * cls) { UNUSED(config); struct mod_parameters * param = (struct mod_parameters *)cls; int ret, res; json_t * j_query, * j_result; char * clause = get_password_clause_check(param, username, password); char * username_escaped, * username_clause; username_escaped = h_escape_string_with_quotes(param->conn, username); username_clause = msprintf("IN (SELECT gu_id FROM "G_TABLE_USER" WHERE UPPER(gu_username) = UPPER(%s))", username_escaped); j_query = json_pack("{sss[s]s{s{ssss}s{ssss}}}", "table", G_TABLE_USER_PASSWORD, "columns", "gu_id", "where", "gu_id", "operator", "raw", "value", username_clause, "guw_password", "operator", "raw", "value", clause); o_free(clause); o_free(username_clause); o_free(username_escaped); res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { ret = G_OK; } else { ret = G_ERROR_UNAUTHORIZED; } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_check_password database - Error executing j_query"); param->config_glewlwyd->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } int user_module_update_password(struct config_module * config, const char * username, const char ** new_passwords, size_t new_passwords_len, void * cls) { UNUSED(config); struct mod_parameters * param = (struct mod_parameters *)cls; json_t * j_query, * j_result; int res, ret; char * username_escaped, * username_clause; username_escaped = h_escape_string_with_quotes(param->conn, username); username_clause = msprintf(" = UPPER(%s)", username_escaped); j_query = json_pack("{sss[s]s{s{ssss}}}", "table", G_TABLE_USER, "columns", "gu_id", "where", "UPPER(gu_username)", "operator", "raw", "value", username_clause); o_free(username_clause); o_free(username_escaped); res = h_select(param->conn, j_query, &j_result, NULL); json_decref(j_query); if (res == H_OK) { if (json_array_size(j_result)) { ret = update_password_list(param, json_integer_value(json_object_get(json_array_get(j_result, 0), "gu_id")), new_passwords, new_passwords_len, 0); } else { ret = G_ERROR_UNAUTHORIZED; } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update_password database - Error executing j_query"); config->glewlwyd_module_callback_metrics_increment_counter(param->config_glewlwyd, GLWD_METRICS_DATABSE_ERROR, 1, NULL); ret = G_ERROR_DB; } return ret; } glewlwyd-2.6.1/src/user/database.mariadb.sql000066400000000000000000000025031415646314000210120ustar00rootroot00000000000000DROP TABLE IF EXISTS g_user_property; DROP TABLE IF EXISTS g_user_scope_user; DROP TABLE IF EXISTS g_user_scope; DROP TABLE IF EXISTS g_user_password; DROP TABLE IF EXISTS g_user; CREATE TABLE g_user ( gu_id INT(11) PRIMARY KEY AUTO_INCREMENT, gu_username VARCHAR(128) NOT NULL UNIQUE, gu_name VARCHAR(256) DEFAULT '', gu_email VARCHAR(512) DEFAULT '', gu_enabled TINYINT(1) DEFAULT 1 ); CREATE TABLE g_user_scope ( gus_id INT(11) PRIMARY KEY AUTO_INCREMENT, gus_name VARCHAR(128) NOT NULL UNIQUE ); CREATE TABLE g_user_scope_user ( gusu_id INT(11) PRIMARY KEY AUTO_INCREMENT, gu_id INT(11), gus_id INT(11), FOREIGN KEY(gu_id) REFERENCES g_user(gu_id) ON DELETE CASCADE, FOREIGN KEY(gus_id) REFERENCES g_user_scope(gus_id) ON DELETE CASCADE ); CREATE TABLE g_user_property ( gup_id INT(11) PRIMARY KEY AUTO_INCREMENT, gu_id INT(11), gup_name VARCHAR(128) NOT NULL, gup_value_tiny VARCHAR(512) DEFAULT NULL, gup_value_small BLOB DEFAULT NULL, gup_value_medium MEDIUMBLOB DEFAULT NULL, FOREIGN KEY(gu_id) REFERENCES g_user(gu_id) ON DELETE CASCADE ); CREATE INDEX i_g_user_property_name ON g_user_property(gup_name); CREATE TABLE g_user_password ( guw_id INT(11) PRIMARY KEY AUTO_INCREMENT, gu_id INT(11), guw_password VARCHAR(256), FOREIGN KEY(gu_id) REFERENCES g_user(gu_id) ON DELETE CASCADE ); glewlwyd-2.6.1/src/user/database.postgre.sql000066400000000000000000000022171415646314000211000ustar00rootroot00000000000000DROP TABLE IF EXISTS g_user_property; DROP TABLE IF EXISTS g_user_scope_user; DROP TABLE IF EXISTS g_user_scope; DROP TABLE IF EXISTS g_user_password; DROP TABLE IF EXISTS g_user; CREATE TABLE g_user ( gu_id SERIAL PRIMARY KEY, gu_username VARCHAR(128) NOT NULL UNIQUE, gu_name VARCHAR(256) DEFAULT '', gu_email VARCHAR(512) DEFAULT '', gu_enabled SMALLINT DEFAULT 1 ); CREATE TABLE g_user_scope ( gus_id SERIAL PRIMARY KEY, gus_name VARCHAR(128) NOT NULL UNIQUE ); CREATE TABLE g_user_scope_user ( gusu_id SERIAL PRIMARY KEY, gu_id SERIAL, gus_id SERIAL, FOREIGN KEY(gu_id) REFERENCES g_user(gu_id) ON DELETE CASCADE, FOREIGN KEY(gus_id) REFERENCES g_user_scope(gus_id) ON DELETE CASCADE ); CREATE TABLE g_user_property ( gup_id SERIAL PRIMARY KEY, gu_id SERIAL, gup_name VARCHAR(128) NOT NULL, gup_value TEXT DEFAULT NULL, FOREIGN KEY(gu_id) REFERENCES g_user(gu_id) ON DELETE CASCADE ); CREATE INDEX i_g_user_property_name ON g_user_property(gup_name); CREATE TABLE g_user_password ( guw_id SERIAL PRIMARY KEY, gu_id SERIAL, guw_password VARCHAR(256), FOREIGN KEY(gu_id) REFERENCES g_user(gu_id) ON DELETE CASCADE ); glewlwyd-2.6.1/src/user/database.sqlite3.sql000066400000000000000000000022551415646314000210030ustar00rootroot00000000000000DROP TABLE IF EXISTS g_user_property; DROP TABLE IF EXISTS g_user_scope_user; DROP TABLE IF EXISTS g_user_scope; DROP TABLE IF EXISTS g_user_password; DROP TABLE IF EXISTS g_user; CREATE TABLE g_user ( gu_id INTEGER PRIMARY KEY AUTOINCREMENT, gu_username TEXT NOT NULL UNIQUE, gu_name TEXT DEFAULT '', gu_email TEXT DEFAULT '', gu_enabled INTEGER DEFAULT 1 ); CREATE TABLE g_user_scope ( gus_id INTEGER PRIMARY KEY AUTOINCREMENT, gus_name TEXT NOT NULL UNIQUE ); CREATE TABLE g_user_scope_user ( gusu_id INTEGER PRIMARY KEY AUTOINCREMENT, gu_id INTEGER, gus_id INTEGER, FOREIGN KEY(gu_id) REFERENCES g_user(gu_id) ON DELETE CASCADE, FOREIGN KEY(gus_id) REFERENCES g_user_scope(gus_id) ON DELETE CASCADE ); CREATE TABLE g_user_property ( gup_id INTEGER PRIMARY KEY AUTOINCREMENT, gu_id INTEGER, gup_name TEXT NOT NULL, gup_value TEXT DEFAULT NULL, FOREIGN KEY(gu_id) REFERENCES g_user(gu_id) ON DELETE CASCADE ); CREATE INDEX i_g_user_property_name ON g_user_property(gup_name); CREATE TABLE g_user_password ( guw_id INTEGER PRIMARY KEY AUTOINCREMENT, gu_id INTEGER, guw_password TEXT, FOREIGN KEY(gu_id) REFERENCES g_user(gu_id) ON DELETE CASCADE ); glewlwyd-2.6.1/src/user/http.c000066400000000000000000000205721415646314000162600ustar00rootroot00000000000000/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * HTTP basic auth user module * * Copyright 2017-2020 Nicolas Mora * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . * */ #include #include #include #include #include #include "glewlwyd-common.h" json_t * user_module_load(struct config_module * config) { UNUSED(config); return json_pack("{sisssssssf}", "result", G_OK, "name", "http", "display_name", "HTTP auth backend user module", "description", "Module to store users in the database", "api_version", 2.5); } int user_module_unload(struct config_module * config) { UNUSED(config); return G_OK; } json_t * user_module_init(struct config_module * config, int readonly, int multiple_passwords, json_t * j_params, void ** cls) { UNUSED(config); UNUSED(readonly); UNUSED(multiple_passwords); size_t index = 0; json_t * j_element = NULL, * j_return = NULL; int ret; if (json_is_object(j_params)) { ret = G_OK; if (!json_string_length(json_object_get(j_params, "url"))) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_init http - parameter url is mandatory must be a non empty string"); j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "parameter url is mandatory must be a non empty string"); ret = G_ERROR_PARAM; } else if (json_object_get(j_params, "check-server-certificate") != NULL && !json_is_boolean(json_object_get(j_params, "check-server-certificate"))) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_init http - parameter check-server-certificate is optional and must be a boolean"); j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "parameter check-server-certificate is optional and must be a boolean"); } else if (json_object_get(j_params, "default-scope") == NULL || !json_is_array(json_object_get(j_params, "default-scope"))) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_init http - parameter default-scope is mandatory must be an array of non empty strings"); j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "parameter default-scope is mandatory must be an array of non empty strings"); ret = G_ERROR_PARAM; } else if (json_string_length(json_object_get(j_params, "username-format")) && o_strstr(json_string_value(json_object_get(j_params, "username-format")), "{username}") == NULL) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_init http - parameter username-format is optional and must contain {username}"); j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "parameter username-format is optional and must contain {username}"); ret = G_ERROR_PARAM; } else { json_array_foreach(json_object_get(j_params, "default-scope"), index, j_element) { if (!json_string_length(j_element)) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_init http - parameter default-scope is mandatory must be an array of non empty strings"); if (ret == G_OK) { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "parameter default-scope is mandatory must be an array of non empty strings"); ret = G_ERROR_PARAM; } } } } if (ret == G_OK) { j_return = json_pack("{si}", "result", G_OK); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_init http - parameters must be a JSON object"); ret = G_ERROR_PARAM; j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "parameters must be a JSON object"); } if (ret == G_OK) { *cls = json_incref(j_params); } return j_return; } int user_module_close(struct config_module * config, void * cls) { UNUSED(config); json_decref((json_t *)cls); return G_OK; } size_t user_module_count_total(struct config_module * config, const char * pattern, void * cls) { UNUSED(config); UNUSED(pattern); UNUSED(cls); return 0; } json_t * user_module_get_list(struct config_module * config, const char * pattern, size_t offset, size_t limit, void * cls) { UNUSED(config); UNUSED(pattern); UNUSED(offset); UNUSED(limit); UNUSED(cls); return json_pack("{sis[]}", "result", G_OK, "list"); } json_t * user_module_get(struct config_module * config, const char * username, void * cls) { UNUSED(config); UNUSED(username); UNUSED(cls); return json_pack("{sis{sssOso}}", "result", G_OK, "user", "username", username, "scope", json_object_get((json_t *)cls, "default-scope"), "enabled", json_true()); } json_t * user_module_get_profile(struct config_module * config, const char * username, void * cls) { UNUSED(config); UNUSED(username); UNUSED(cls); return json_pack("{sis{sssOso}}", "result", G_OK, "user", "username", username, "scope", json_object_get((json_t *)cls, "default-scope"), "enabled", json_true()); } json_t * user_module_is_valid(struct config_module * config, const char * username, json_t * j_user, int mode, void * cls) { UNUSED(config); UNUSED(username); UNUSED(j_user); UNUSED(mode); UNUSED(cls); return json_pack("{si}", "result", G_ERROR_PARAM); } int user_module_add(struct config_module * config, json_t * j_user, void * cls) { UNUSED(config); UNUSED(j_user); UNUSED(cls); return G_ERROR_PARAM; } int user_module_update(struct config_module * config, const char * username, json_t * j_user, void * cls) { UNUSED(config); UNUSED(username); UNUSED(j_user); UNUSED(cls); return G_ERROR_PARAM; } int user_module_update_profile(struct config_module * config, const char * username, json_t * j_user, void * cls) { UNUSED(config); UNUSED(username); UNUSED(j_user); UNUSED(cls); return G_ERROR_PARAM; } int user_module_delete(struct config_module * config, const char * username, void * cls) { UNUSED(config); UNUSED(username); UNUSED(cls); return G_ERROR_PARAM; } int user_module_check_password(struct config_module * config, const char * username, const char * password, void * cls) { UNUSED(config); struct _u_request request; struct _u_response response; int res, ret; ulfius_init_request(&request); ulfius_init_response(&response); request.http_verb = o_strdup("GET"); request.http_url = o_strdup(json_string_value(json_object_get((json_t *)cls, "url"))); if (json_object_get((json_t *)cls, "check-server-certificate") == json_false()) { request.check_server_certificate = 0; } if (json_string_length(json_object_get((json_t *)cls, "username-format"))) { request.auth_basic_user = str_replace(json_string_value(json_object_get((json_t *)cls, "username-format")), "{username}", username); } else { request.auth_basic_user = o_strdup(username); } request.auth_basic_password = o_strdup(password); res = ulfius_send_http_request(&request, &response); if (res == H_OK) { if (response.status == 200) { ret = G_OK; } else { if (response.status != 401 && response.status != 403) { y_log_message(Y_LOG_LEVEL_WARNING, "user_module_check_password http - Error connecting to webservice %s, response status is %d", request.http_url, response.status); } ret = G_ERROR_UNAUTHORIZED; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_check_password http - Error ulfius_send_http_request"); ret = G_ERROR; } ulfius_clean_request(&request); ulfius_clean_response(&response); return ret; } int user_module_update_password(struct config_module * config, const char * username, const char ** new_passwords, size_t new_passwords_len, void * cls) { UNUSED(config); UNUSED(username); UNUSED(new_passwords); UNUSED(new_passwords_len); UNUSED(cls); return G_ERROR_PARAM; } glewlwyd-2.6.1/src/user/ldap.c000066400000000000000000003000501415646314000162110ustar00rootroot00000000000000/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * LDAP user module * * Copyright 2016-2020 Nicolas Mora * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . * */ #include #include #include #include #include #include "glewlwyd-common.h" #define LDAP_DEFAULT_PAGE_SIZE 50 /** * * Escapes any special chars (RFC 4515) from a string representing a * a search filter assertion value. * * You must o_free the returned value after use * */ static char * escape_ldap(const char * input) { char * tmp, * to_return = NULL; size_t len, i; if (input != NULL) { to_return = strdup(""); len = o_strlen(input); for (i=0; i < len && to_return != NULL; i++) { unsigned char c = input[i]; if (c == '*') { // escape asterisk tmp = msprintf("%s\\2a", to_return); o_free(to_return); to_return = tmp; } else if (c == '(') { // escape left parenthesis tmp = msprintf("%s\\28", to_return); o_free(to_return); to_return = tmp; } else if (c == ')') { // escape right parenthesis tmp = msprintf("%s\\29", to_return); o_free(to_return); to_return = tmp; } else if (c == '\\') { // escape backslash tmp = msprintf("%s\\5c", to_return); o_free(to_return); to_return = tmp; } else if ((c & 0x80) == 0) { // regular 1-byte UTF-8 char tmp = msprintf("%s%c", to_return, c); o_free(to_return); to_return = tmp; } else if (((c & 0xE0) == 0xC0) && i < (len-2)) { // higher-order 2-byte UTF-8 chars tmp = msprintf("%s\\%02x\\%02x", to_return, input[i], input[i+1]); o_free(to_return); to_return = tmp; } else if (((c & 0xF0) == 0xE0) && i < (len-3)) { // higher-order 3-byte UTF-8 chars tmp = msprintf("%s\\%02x\\%02x\\%02x", to_return, input[i], input[i+1], input[i+2]); o_free(to_return); to_return = tmp; } else if (((c & 0xF8) == 0xF0) && i < (len-4)) { // higher-order 4-byte UTF-8 chars tmp = msprintf("%s\\%02x\\%02x\\%02x\\%02x", to_return, input[i], input[i+1], input[i+2], input[i+3]); o_free(to_return); to_return = tmp; } } } return to_return; } static json_t * is_user_ldap_parameters_valid(json_t * j_params, int readonly) { json_t * j_return, * j_error = json_array(), * j_element = NULL, * j_element_p = NULL; size_t index = 0; const char * field; if (j_error != NULL) { if (!json_is_object(j_params)) { json_array_append_new(j_error, json_string("parameters must be a JSON object")); } else { if (json_object_get(j_params, "uri") == NULL || !json_is_string(json_object_get(j_params, "uri")) || !json_string_length(json_object_get(j_params, "uri"))) { json_array_append_new(j_error, json_string("uri is mandatory and must be a string")); } if (json_object_get(j_params, "bind-dn") == NULL || !json_is_string(json_object_get(j_params, "bind-dn")) || !json_string_length(json_object_get(j_params, "bind-dn"))) { json_array_append_new(j_error, json_string("bind-dn is mandatory and must be a string")); } if (json_object_get(j_params, "bind-password") == NULL || !json_is_string(json_object_get(j_params, "bind-password")) || !json_string_length(json_object_get(j_params, "bind-password"))) { json_array_append_new(j_error, json_string("bind-password is mandatory and must be a string")); } if (json_object_get(j_params, "search-scope") != NULL && !json_is_string(json_object_get(j_params, "search-scope"))) { json_array_append_new(j_error, json_string("search-scope is optional and must be a string")); } else if (json_object_get(j_params, "search-scope") == NULL) { json_object_set_new(j_params, "search-scope", json_string("one")); } else if (0 != o_strcmp("one", json_string_value(json_object_get(j_params, "search-scope"))) && 0 != o_strcmp("subtree", json_string_value(json_object_get(j_params, "search-scope"))) && 0 != o_strcmp("children", json_string_value(json_object_get(j_params, "search-scope")))) { json_array_append_new(j_error, json_string("search-scope must have one of the following values: 'one', 'subtree', 'children'")); } if (json_object_get(j_params, "page-size") != NULL && (!json_is_integer(json_object_get(j_params, "page-size")) || json_integer_value(json_object_get(j_params, "page-size")) <= 0)) { json_array_append_new(j_error, json_string("page-size is optional and must be a positive integer")); } else if (json_object_get(j_params, "page-size") == NULL) { json_object_set_new(j_params, "page-size", json_integer(LDAP_DEFAULT_PAGE_SIZE)); } if (json_object_get(j_params, "base-search") == NULL || !json_is_string(json_object_get(j_params, "base-search")) || !json_string_length(json_object_get(j_params, "base-search"))) { json_array_append_new(j_error, json_string("base-search is mandatory and must be a string")); } if (json_object_get(j_params, "filter") == NULL || !json_is_string(json_object_get(j_params, "filter")) || !json_string_length(json_object_get(j_params, "filter"))) { json_array_append_new(j_error, json_string("filter is mandatory and must be a string")); } if (readonly) { if (json_object_get(j_params, "username-property") == NULL || !json_string_length(json_object_get(j_params, "username-property"))) { json_array_append_new(j_error, json_string("username-property is mandatory and must be a non empty string")); } } else { if (json_object_get(j_params, "username-property") == NULL || (!json_is_string(json_object_get(j_params, "username-property")) && !json_is_array(json_object_get(j_params, "username-property")))) { json_array_append_new(j_error, json_string("username-property is mandatory and must be a non empty string or an array of non empty strings")); } else if (json_is_string(json_object_get(j_params, "username-property")) && !json_string_length(json_object_get(j_params, "username-property"))) { json_array_append_new(j_error, json_string("username-property is mandatory and must be a non empty string or an array of non empty strings")); } else if (json_is_array(json_object_get(j_params, "username-property"))) { json_array_foreach(json_object_get(j_params, "username-property"), index, j_element) { if (!json_string_length(j_element)) { json_array_append_new(j_error, json_string("username-property is mandatory and must be a non empty string or an array of non empty strings")); } } } } if (readonly) { if (!json_string_length(json_object_get(j_params, "scope-property"))) { json_array_append_new(j_error, json_string("scope-property is mandatory and must be a non empty string")); } } else { if (json_object_get(j_params, "scope-property") == NULL || (!json_is_string(json_object_get(j_params, "scope-property")) && !json_is_array(json_object_get(j_params, "scope-property")))) { json_array_append_new(j_error, json_string("scope-property is mandatory and must be a non empty string or an array of non empty strings")); } else if (json_is_string(json_object_get(j_params, "scope-property")) && !json_string_length(json_object_get(j_params, "scope-property"))) { json_array_append_new(j_error, json_string("scope-property is mandatory and must be a non empty string or an array of non empty strings")); } else if (json_is_array(json_object_get(j_params, "scope-property"))) { json_array_foreach(json_object_get(j_params, "scope-property"), index, j_element) { if (!json_string_length(j_element)) { json_array_append_new(j_error, json_string("scope-property is mandatory and must be a non empty string or an array of non empty strings")); } } } } if (json_object_get(j_params, "scope-match") != NULL && !json_is_array(json_object_get(j_params, "scope-match"))) { json_array_append_new(j_error, json_string("scope-match is optional and must be a JSON array")); } else if (json_object_get(j_params, "scope-match") != NULL) { json_array_foreach(json_object_get(j_params, "scope-property-match-correspondence"), index, j_element) { if (!json_is_string(json_object_get(j_element, "ldap-value"))) { json_array_append_new(j_error, json_string("ldap-value is mandatory and must be a string")); } if (!json_is_string(json_object_get(j_element, "scope-value"))) { json_array_append_new(j_error, json_string("scope-value is mandatory and must be a string")); } if (!json_is_string(json_object_get(j_element, "match")) || 0 != o_strcmp("equals", json_string_value(json_object_get(j_element, "match"))) || 0 != o_strcmp("contains", json_string_value(json_object_get(j_element, "match"))) || 0 != o_strcmp("startswith", json_string_value(json_object_get(j_element, "match"))) || 0 != o_strcmp("endswith", json_string_value(json_object_get(j_element, "match")))) { json_array_append_new(j_error, json_string("match is mandatory and must have one of the following values: 'equals', 'contains', 'startswith', 'endswith'")); } } } if (readonly) { if (json_object_get(j_params, "name-property") == NULL || !json_string_length(json_object_get(j_params, "name-property"))) { json_array_append_new(j_error, json_string("name-property is mandatory and must be a non empty string")); } } else { if (json_object_get(j_params, "name-property") == NULL || (!json_is_string(json_object_get(j_params, "name-property")) && !json_is_array(json_object_get(j_params, "name-property")))) { json_array_append_new(j_error, json_string("name-property is mandatory and must be a non empty string or an array of non empty strings")); } else if (json_is_string(json_object_get(j_params, "name-property")) && !json_string_length(json_object_get(j_params, "name-property"))) { json_array_append_new(j_error, json_string("name-property is mandatory and must be a non empty string or an array of non empty strings")); } else if (json_is_array(json_object_get(j_params, "name-property"))) { json_array_foreach(json_object_get(j_params, "name-property"), index, j_element) { if (!json_string_length(j_element)) { json_array_append_new(j_error, json_string("name-property is mandatory and must be a non empty string or an array of non empty strings")); } } } } if (readonly) { if (json_object_get(j_params, "email-property") == NULL || !json_is_string(json_object_get(j_params, "email-property"))) { json_array_append_new(j_error, json_string("email-property is optional and must be a string")); } } else { if (json_object_get(j_params, "email-property") != NULL && !json_is_string(json_object_get(j_params, "email-property")) && !json_is_array(json_object_get(j_params, "email-property"))) { json_array_append_new(j_error, json_string("email-property is optional and must be a string or an array of strings")); } else if (json_is_array(json_object_get(j_params, "email-property"))) { json_array_foreach(json_object_get(j_params, "email-property"), index, j_element) { if (!json_is_string(j_element)) { json_array_append_new(j_error, json_string("email-property is optional and must be a string or an array of strings")); } } } } if (!readonly) { if (json_object_get(j_params, "rdn-property") == NULL || !json_string_length(json_object_get(j_params, "rdn-property"))) { json_array_append_new(j_error, json_string("rdn-property is mandatory and must be a non empty string")); } if (json_object_get(j_params, "password-property") == NULL || !json_string_length(json_object_get(j_params, "password-property"))) { json_array_append_new(j_error, json_string("password-property is mandatory and must be a non empty string")); } if (json_object_get(j_params, "password-algorithm") == NULL || (0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "SHA") && //0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "SHA256") && //0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "SHA384") && //0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "SHA512") && 0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "SSHA") && //0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "SSHA256") && //0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "SSHA384") && //0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "SSHA512") && 0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "SMD5") && 0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "MD5") && //0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "PKCS5S2") && 0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "CRYPT") && 0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "CRYPT_MD5") && 0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "CRYPT_SHA256") && 0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "CRYPT_SHA512") && 0 != o_strcmp(json_string_value(json_object_get(j_params, "password-algorithm")), "PLAIN"))) { //json_array_append_new(j_error, json_string("password-property is mandatory and must have one of the following values: 'SHA', 'SHA256', 'SHA284', 'SHA512', 'SSHA', " // "'SSHA256', 'SSHA384', 'SSHA512', 'SMD5', 'MD5', 'PKCS5S2', 'PLAIN'")); json_array_append_new(j_error, json_string("password-property is mandatory and must have one of the following values: 'SHA', 'SSHA', 'SMD5', 'MD5', 'CRYPT', 'CRYPT_MD5', 'CRYPT_SHA256', 'CRYPT_SHA512', 'PLAIN'")); } if (json_object_get(j_params, "object-class") == NULL || (!json_is_string(json_object_get(j_params, "object-class")) && !json_is_array(json_object_get(j_params, "object-class")))) { json_array_append_new(j_error, json_string("object-class is mandatory and must be a non empty string or an array of non empty strings")); } else if (json_is_string(json_object_get(j_params, "object-class")) && !json_string_length(json_object_get(j_params, "object-class"))) { json_array_append_new(j_error, json_string("object-class is mandatory and must be a non empty string or an array of non empty strings")); } else { json_array_foreach(json_object_get(j_params, "object-class"), index, j_element) { if (!json_string_length(j_element)) { json_array_append_new(j_error, json_string("object-class is mandatory and must be a non empty string or an array of non empty strings")); } } } } if (json_object_get(j_params, "data-format") != NULL) { if (!json_is_object(json_object_get(j_params, "data-format"))) { json_array_append_new(j_error, json_string("data-format is optional and must be a JSON object")); } else { json_object_foreach(json_object_get(j_params, "data-format"), field, j_element) { if (0 == o_strcmp(field, "username") || 0 == o_strcmp(field, "name") || 0 == o_strcmp(field, "email") || 0 == o_strcmp(field, "enabled") || 0 == o_strcmp(field, "password") || 0 == o_strcmp(field, "scope")) { json_array_append_new(j_error, json_string("data-format can't have settings for properties 'username', 'name', 'email', 'enabled', 'scope' or 'password'")); } else { if (readonly) { if (json_object_get(j_element, "property") == NULL || !json_string_length(json_object_get(j_element, "property"))) { json_array_append_new(j_error, json_string("property is mandatory and must be a non empty string")); } } else { if (json_object_get(j_element, "property") == NULL || ((!json_is_string(json_object_get(j_element, "property")) || !json_string_length(json_object_get(j_element, "property"))) && !json_is_array(json_object_get(j_element, "property")))) { json_array_append_new(j_error, json_string("property is mandatory and must be a non empty string or an array of non empty string")); } else if (json_is_array(json_object_get(j_element, "property"))) { json_array_foreach(json_object_get(j_element, "property"), index, j_element_p) { if (!json_string_length(j_element_p)) { json_array_append_new(j_error, json_string("property is mandatory and must be a non empty string or an array of non empty string")); } } } } if (json_object_get(j_element, "multiple") != NULL && !json_is_boolean(json_object_get(j_element, "multiple"))) { json_array_append_new(j_error, json_string("multiple is optional and must be a boolean (default: false)")); } if (json_object_get(j_element, "convert") != NULL && 0 != o_strcmp("base64", json_string_value(json_object_get(j_element, "convert")))) { json_array_append_new(j_error, json_string("convert is optional and must have one of the following values: 'base64'")); } if (json_object_get(j_element, "read") != NULL && !json_is_boolean(json_object_get(j_element, "read"))) { json_array_append_new(j_error, json_string("read is optional and must be a boolean (default: true)")); } if (!readonly && json_object_get(j_element, "write") != NULL && !json_is_boolean(json_object_get(j_element, "write"))) { json_array_append_new(j_error, json_string("write is optional and must be a boolean (default: true)")); } if (json_object_get(j_element, "profile-read") != NULL && !json_is_boolean(json_object_get(j_element, "profile-read"))) { json_array_append_new(j_error, json_string("profile-read is optional and must be a boolean (default: false)")); } if (!readonly && json_object_get(j_element, "profile-write") != NULL && !json_is_boolean(json_object_get(j_element, "profile-write"))) { json_array_append_new(j_error, json_string("profile-write is optional and must be a boolean (default: false)")); } } } } } } if (json_array_size(j_error)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_error); } else { j_return = json_pack("{si}", "result", G_OK); } json_decref(j_error); } else { y_log_message(Y_LOG_LEVEL_ERROR, "is_user_database_parameters_valid - Error allocating resources for j_error"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } return j_return; } static LDAP * connect_ldap_server(json_t * j_params) { LDAP * ldap = NULL; int ldap_version = LDAP_VERSION3; int result; char * ldap_mech = LDAP_SASL_SIMPLE; struct berval cred, * servcred; cred.bv_val = (char*)json_string_value(json_object_get(j_params, "bind-password")); cred.bv_len = o_strlen(json_string_value(json_object_get(j_params, "bind-password"))); if (ldap_initialize(&ldap, json_string_value(json_object_get(j_params, "uri"))) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "connect_ldap_server ldap - Error initializing ldap"); ldap = NULL; } else if (ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &ldap_version) != LDAP_OPT_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "connect_ldap_server ldap - Error setting ldap protocol version"); ldap_unbind_ext(ldap, NULL, NULL); ldap = NULL; } else if ((result = ldap_sasl_bind_s(ldap, json_string_value(json_object_get(j_params, "bind-dn")), ldap_mech, &cred, NULL, NULL, &servcred)) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "connect_ldap_server - Error binding to ldap server mode %s: %s", ldap_mech, ldap_err2string(result)); ldap_unbind_ext(ldap, NULL, NULL); ldap = NULL; } return ldap; } static const char * get_read_property(json_t * j_params, const char * property) { if (json_is_string(json_object_get(j_params, property))) { return json_string_value(json_object_get(j_params, property)); } else if (json_is_array(json_object_get(j_params, property))) { return json_string_value(json_array_get(json_object_get(j_params, property), 0)); } else { return NULL; } } static char * get_ldap_filter_pattern(json_t * j_params, const char * pattern) { char * pattern_escaped, * filter, * name_filter, * email_filter; if (o_strlen(pattern)) { pattern_escaped = escape_ldap(pattern); if (json_object_get(j_params, "name-property") != NULL) { name_filter = msprintf("(%s=*%s*)", get_read_property(j_params, "name-property"), pattern_escaped); } else { name_filter = o_strdup(""); } if (json_object_get(j_params, "email-property") != NULL) { email_filter = msprintf("(%s=*%s*)", get_read_property(j_params, "email-property"), pattern_escaped); } else { email_filter = o_strdup(""); } filter = msprintf("(&(%s)(|(%s=*%s*)%s%s))", json_string_value(json_object_get(j_params, "filter")), get_read_property(j_params, "username-property"), pattern_escaped, name_filter, email_filter); o_free(pattern_escaped); o_free(name_filter); o_free(email_filter); } else { filter = msprintf("(%s)", json_string_value(json_object_get(j_params, "filter"))); } return filter; } static char ** get_ldap_read_attributes(json_t * j_params, int profile, json_t * j_properties) { char ** attrs = NULL; size_t i, nb_attrs = 2; // Username, Scope json_t * j_element = NULL; const char * field = NULL; if (j_properties != NULL && json_is_object(j_properties) && !json_object_size(j_properties)) { nb_attrs += (json_object_get(j_params, "name-property") != NULL); nb_attrs += (json_object_get(j_params, "email-property") != NULL); nb_attrs += (json_object_get(j_params, "multiple_passwords") == json_true() && json_object_get(j_params, "password-property") != NULL); if (json_object_get(j_params, "data-format") != NULL) { json_object_foreach(json_object_get(j_params, "data-format"), field, j_element) { nb_attrs += ((!profile && json_object_get(j_element, "read") != json_false()) || (profile && json_object_get(j_element, "profile-read") == json_true())); } } attrs = o_malloc((nb_attrs + 1) * sizeof(char *)); if (attrs != NULL) { attrs[nb_attrs] = NULL; attrs[0] = (char*)get_read_property(j_params, "username-property"); json_object_set_new(j_properties, "username", json_string(get_read_property(j_params, "username-property"))); attrs[1] = (char*)get_read_property(j_params, "scope-property"); json_object_set_new(j_properties, "scope", json_string(get_read_property(j_params, "scope-property"))); i = 2; if (json_object_get(j_params, "name-property") != NULL) { attrs[i++] = (char*)get_read_property(j_params, "name-property"); json_object_set_new(j_properties, "name", json_string(get_read_property(j_params, "name-property"))); } if (json_object_get(j_params, "email-property") != NULL) { attrs[i++] = (char*)get_read_property(j_params, "email-property"); json_object_set_new(j_properties, "email", json_string(get_read_property(j_params, "email-property"))); } if (json_object_get(j_params, "multiple_passwords") == json_true() && json_object_get(j_params, "password-property") != NULL) { attrs[i++] = (char*)get_read_property(j_params, "password-property"); } if (json_object_get(j_params, "data-format") != NULL) { json_object_foreach(json_object_get(j_params, "data-format"), field, j_element) { if ((!profile && json_object_get(j_element, "read") != json_false()) || (profile && json_object_get(j_element, "profile-read") == json_true())) { attrs[i++] = (char*)get_read_property(j_element, "property"); json_object_set_new(j_properties, field, json_string(get_read_property(j_element, "property"))); } } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_read_attributes - Error allocating resources for attrs"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_read_attributes - Error j_properties is not an empty JSON object"); } return attrs; } static size_t count_properties(json_t * j_params, const char * property) { if (json_object_get(j_params, property) != NULL) { if (json_is_string(json_object_get(j_params, property))) { return 1; } else { return json_array_size(json_object_get(j_params, property)); } } else { return 0; } } static digest_algorithm get_digest_algorithm(json_t * j_params) { if (0 == o_strcmp("SHA", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SHA1; } else if (0 == o_strcmp("SSHA", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SSHA1; } else if (0 == o_strcmp("SHA224", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SHA224; } else if (0 == o_strcmp("SSHA224", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SSHA224; } else if (0 == o_strcmp("SHA256", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SHA256; } else if (0 == o_strcmp("SSHA256", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SSHA256; } else if (0 == o_strcmp("SHA384", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SHA384; } else if (0 == o_strcmp("SSHA384", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SSHA384; } else if (0 == o_strcmp("SHA512", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SHA512; } else if (0 == o_strcmp("SSHA512", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SSHA512; } else if (0 == o_strcmp("PBKDF2", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_PBKDF2_SHA256; } else if (0 == o_strcmp("MD5", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_MD5; } else if (0 == o_strcmp("SMD5", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_SMD5; } else if (0 == o_strcmp("CRYPT", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_CRYPT; } else if (0 == o_strcmp("CRYPT_MD5", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_CRYPT_MD5; } else if (0 == o_strcmp("CRYPT_SHA256", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_CRYPT_SHA256; } else if (0 == o_strcmp("CRYPT_SHA512", json_string_value(json_object_get(j_params, "password-algorithm")))) { return digest_CRYPT_SHA512; } else { return digest_PLAIN; } } static int set_update_password_mod(json_t * j_params, LDAP * ldap, const char * username, const char ** new_passwords, size_t new_passwords_len, LDAPMod * mod, int add) { LDAPMessage * entry, * answer; int ldap_result, nb_values, ret = G_OK, i; struct berval ** result_values = NULL; size_t counter; int scope = LDAP_SCOPE_ONELEVEL; char * filter = NULL; char * attrs[2] = {(char *)json_string_value(json_object_get(j_params, "password-property")), NULL}; int attrsonly = 0; if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_SUBTREE; } else if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_CHILDREN; } if (!add) { // Connection successful, doing ldap search filter = msprintf("(&(%s)(%s=%s))", json_string_value(json_object_get(j_params, "filter")), get_read_property(j_params, "username-property"), username); if ((ldap_result = ldap_search_ext_s(ldap, json_string_value(json_object_get(j_params, "base-search")), scope, filter, attrs, attrsonly, NULL, NULL, NULL, LDAP_NO_LIMIT, &answer)) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "set_update_password_mod - Error ldap search, base search: %s, filter: %s: %s", json_string_value(json_object_get(j_params, "base-search")), filter, ldap_err2string(ldap_result)); ret = G_ERROR; } else { // Looping in results, staring at offset, until the end of the list if (ldap_count_entries(ldap, answer) > 0) { entry = ldap_first_entry(ldap, answer); result_values = ldap_get_values_len(ldap, entry, json_string_value(json_object_get(j_params, "password-property"))); nb_values = ldap_count_values_len(result_values); if ((mod->mod_values = o_malloc((new_passwords_len+1)*sizeof(char *))) != NULL) { for (i=0; i<(int)new_passwords_len+1; i++) { mod->mod_values[i] = NULL; } counter = 0; for (i=0; i<(int)new_passwords_len; i++) { if (o_strlen(new_passwords[i])) { mod->mod_values[counter] = generate_hash(get_digest_algorithm(j_params), new_passwords[i]); counter++; } else if (new_passwords[i] != NULL && i < nb_values) { mod->mod_values[counter] = o_strndup(result_values[i]->bv_val, result_values[i]->bv_len); counter++; } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_update_password_mod - Error allocating resources for mod->mod_values"); ret = G_ERROR_MEMORY; } ldap_value_free_len(result_values); } } o_free(filter); ldap_msgfree(answer); } else { if ((mod->mod_values = o_malloc((new_passwords_len+1)*sizeof(char *))) != NULL) { for (i=0; i<(int)new_passwords_len+1; i++) { mod->mod_values[i] = NULL; } counter = 0; for (i=0; i<(int)new_passwords_len; i++) { if (o_strlen(new_passwords[i])) { mod->mod_values[counter] = generate_hash(get_digest_algorithm(j_params), new_passwords[i]); counter++; } else { mod->mod_values[counter] = o_strdup(""); counter++; } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "set_update_password_mod - Error allocating resources for mod->mod_values"); ret = G_ERROR_MEMORY; } } return ret; } static LDAPMod ** get_ldap_write_mod(json_t * j_params, LDAP * ldap, const char * username, json_t * j_user, int profile, int add) { LDAPMod ** mods = NULL; size_t nb_attr = 0; json_t * j_format, * j_property = NULL, * j_property_value, * j_scope; const char * field = NULL; size_t index = 0, index_scope = 0, i, j; int has_error = 0, mod_op = add?LDAP_MOD_ADD:LDAP_MOD_REPLACE; unsigned char * value_dec = NULL; size_t value_dec_len = 0; const char ** passwords = NULL; // Count attrs if (add) { nb_attr += count_properties(j_params, "username-property") + 1; } if (!add || json_string_length(json_object_get(j_user, "name"))) { nb_attr += count_properties(j_params, "name-property"); } if (!profile) { if (!add || json_array_size(json_object_get(j_user, "scope"))) { nb_attr += count_properties(j_params, "scope-property"); } if (!add || json_string_length(json_object_get(j_user, "email"))) { nb_attr += count_properties(j_params, "email-property"); } if (!add || json_string_length(json_object_get(j_user, "password")) || json_array_size(json_object_get(j_user, "password"))) { nb_attr++; } } json_object_foreach(j_user, field, j_property) { if (0 != o_strcmp(field, "username") && 0 != o_strcmp(field, "name") && 0 != o_strcmp(field, "password") && 0 != o_strcmp(field, "scope") && 0 != o_strcmp(field, "email") && 0 != o_strcmp(field, "enabled")) { if (!add || json_string_length(j_property) || json_array_size(j_property)) { if ((j_format = json_object_get(json_object_get(j_params, "data-format"), field)) != NULL) { if (!profile || (profile && json_object_get(j_format, "profile-write") == json_true())) { nb_attr += count_properties(j_format, "property"); } } } } } mods = o_malloc((nb_attr + 1)*sizeof(LDAPMod *)); for (i=0; i<=nb_attr; i++) { mods[i] = NULL; } // Fill mods i=0; if (mods != NULL) { if (add) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc((json_array_size(json_object_get(j_params, "object-class")) + 1) * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = mod_op; mods[i]->mod_type = "objectClass"; json_array_foreach(json_object_get(j_params, "object-class"), index, j_property_value) { mods[i]->mod_values[index] = o_strdup(json_string_value(j_property_value)); } mods[i]->mod_values[json_array_size(json_object_get(j_params, "object-class"))] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu]->mod_values (objectClass)", i); has_error = 1; } i++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu] (objectClass)", i); has_error = 1; } if (json_is_array(json_object_get(j_params, "username-property"))) { json_array_foreach(json_object_get(j_params, "username-property"), index, j_property) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc(2 * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = mod_op; mods[i]->mod_type = (char *)json_string_value(j_property); mods[i]->mod_values[0] = o_strdup(json_string_value(json_object_get(j_user, "username"))); mods[i]->mod_values[1] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu]->mod_values (username)", i); has_error = 1; } i++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu] (username)", i); has_error = 1; } } } else { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc(2 * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = mod_op; mods[i]->mod_type = (char *)get_read_property(j_params, "username-property"); mods[i]->mod_values[0] = o_strdup(json_string_value(json_object_get(j_user, "username"))); mods[i]->mod_values[1] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu]->mod_values (username)", i); has_error = 1; } i++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu] (username)", i); has_error = 1; } } } if (json_string_length(json_object_get(j_user, "name"))) { if (json_is_array(json_object_get(j_params, "name-property"))) { json_array_foreach(json_object_get(j_params, "name-property"), index, j_property) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc(2 * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = mod_op; mods[i]->mod_type = (char *)json_string_value(j_property); mods[i]->mod_values[0] = o_strdup(json_string_value(json_object_get(j_user, "name"))); mods[i]->mod_values[1] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu]->mod_values (name)", i); has_error = 1; } i++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu] (name)", i); has_error = 1; } } } else { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc(2 * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = mod_op; mods[i]->mod_type = (char *)json_string_value(json_object_get(j_params, "name-property")); mods[i]->mod_values[0] = o_strdup(json_string_value(json_object_get(j_user, "name"))); mods[i]->mod_values[1] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu]->mod_values (name)", i); has_error = 1; } i++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu] (name)", i); has_error = 1; } } } else if (!add) { if (json_is_array(json_object_get(j_params, "name-property"))) { json_array_foreach(json_object_get(j_params, "name-property"), index, j_property) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { if ((mods[i]->mod_values = o_malloc(sizeof(char *))) != NULL) { mods[i]->mod_op = mod_op; mods[i]->mod_type = o_strdup(json_string_value(j_property)); mods[i]->mod_values[0] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu]->mod_values (name)", i); has_error = 1; } i++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu] (name)", i); has_error = 1; } } } else { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { if ((mods[i]->mod_values = o_malloc(sizeof(char *))) != NULL) { mods[i]->mod_op = mod_op; mods[i]->mod_type = o_strdup(json_string_value(json_object_get(j_params, "name-property"))); mods[i]->mod_values[0] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu]->mod_values (name)", i); has_error = 1; } i++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu] (name)", i); has_error = 1; } } } if (!profile) { if (json_string_length(json_object_get(j_user, "email"))) { if (json_is_array(json_object_get(j_params, "email-property"))) { json_array_foreach(json_object_get(j_params, "email-property"), index, j_property) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc(2 * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = mod_op; mods[i]->mod_type = (char *)json_string_value(j_property); mods[i]->mod_values[0] = o_strdup(json_string_value(json_object_get(j_user, "email"))); mods[i]->mod_values[1] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu]->mod_values (email)", i); has_error = 1; } i++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu] (email)", i); has_error = 1; } } } else { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc(2 * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = mod_op; mods[i]->mod_type = (char *)json_string_value(json_object_get(j_params, "email-property")); mods[i]->mod_values[0] = o_strdup(json_string_value(json_object_get(j_user, "email"))); mods[i]->mod_values[1] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu]->mod_values (email)", i); has_error = 1; } i++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu] (email)", i); has_error = 1; } } } else if (!add) { if (json_is_array(json_object_get(j_params, "email-property"))) { json_array_foreach(json_object_get(j_params, "email-property"), index, j_property) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { if ((mods[i]->mod_values = o_malloc(sizeof(char *))) != NULL) { mods[i]->mod_op = mod_op; mods[i]->mod_type = (char *)json_string_value(j_property); mods[i]->mod_values[0] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu]->mod_values (email)", i); has_error = 1; } i++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu] (email)", i); has_error = 1; } } } else { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { if ((mods[i]->mod_values = o_malloc(sizeof(char *))) != NULL) { mods[i]->mod_op = mod_op; mods[i]->mod_type = (char *)json_string_value(json_object_get(j_params, "email-property")); mods[i]->mod_values[0] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu]->mod_values (email)", i); has_error = 1; } i++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu] (email)", i); has_error = 1; } } } if (json_array_size(json_object_get(j_user, "scope"))) { if (json_is_array(json_object_get(j_params, "scope-property"))) { json_array_foreach(json_object_get(j_params, "scope-property"), index, j_property) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc((json_array_size(json_object_get(j_user, "scope")) + 1) * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = mod_op; mods[i]->mod_type = (char *)json_string_value(j_property); json_array_foreach(json_object_get(j_user, "scope"), index_scope, j_scope) { mods[i]->mod_values[index_scope] = o_strdup(json_string_value(j_scope)); } mods[i]->mod_values[(json_array_size(json_object_get(j_user, "scope")))] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu]->mod_values (scope)", i); has_error = 1; } i++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu] (scope)", i); has_error = 1; } } } else { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc((json_array_size(json_object_get(j_user, "scope")) + 1) * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = mod_op; mods[i]->mod_type = (char *)json_string_value(json_object_get(j_params, "scope-property")); json_array_foreach(json_object_get(j_user, "scope"), index_scope, j_scope) { mods[i]->mod_values[index_scope] = o_strdup(json_string_value(j_scope)); } mods[i]->mod_values[(json_array_size(json_object_get(j_user, "scope")))] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu]->mod_values (scope)", i); has_error = 1; } i++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu] (scope)", i); has_error = 1; } } } else if (!add) { if (json_is_array(json_object_get(j_params, "scope-property"))) { json_array_foreach(json_object_get(j_params, "scope-property"), index, j_property) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { if ((mods[i]->mod_values = o_malloc(sizeof(char *))) != NULL) { mods[i]->mod_op = mod_op; mods[i]->mod_type = (char *)json_string_value(j_property); mods[i]->mod_values[0] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu]->mod_values (scope)", i); has_error = 1; } i++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu] (scope)", i); has_error = 1; } } } else { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { if ((mods[i]->mod_values = o_malloc(sizeof(char *))) != NULL) { mods[i]->mod_op = mod_op; mods[i]->mod_type = (char *)json_string_value(json_object_get(j_params, "scope-property")); mods[i]->mod_values[0] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu]->mod_values (scope)", i); has_error = 1; } i++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu] (scope)", i); has_error = 1; } } } if (json_object_get(j_params, "multiple_passwords") == json_true()) { if ((passwords = o_malloc(json_array_size(json_object_get(j_user, "password"))*sizeof(char *))) != NULL) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { json_array_foreach(json_object_get(j_user, "password"), index, j_property) { passwords[index] = json_string_value(j_property); } if (set_update_password_mod(j_params, ldap, username, passwords, json_array_size(json_object_get(j_user, "password")), mods[i], add) == G_OK) { mods[i]->mod_op = mod_op; mods[i]->mod_type = (char *)json_string_value(json_object_get(j_params, "password-property")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error set_update_password_mod for mods[%zu]", i); has_error = 1; } i++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu] (password)", i); has_error = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu] (passwords)", i); has_error = 1; } o_free(passwords); } else { if (json_string_length(json_object_get(j_user, "password"))) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc(2 * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = mod_op; mods[i]->mod_type = (char *)json_string_value(json_object_get(j_params, "password-property")); mods[i]->mod_values[0] = json_string_length(json_object_get(j_user, "password"))?generate_hash(get_digest_algorithm(j_params), json_string_value(json_object_get(j_user, "password"))):NULL; mods[i]->mod_values[1] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu]->mod_values (password)", i); has_error = 1; } i++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu] (password)", i); has_error = 1; } } } } json_object_foreach(j_user, field, j_property) { if (0 != o_strcmp(field, "username") && 0 != o_strcmp(field, "name") && 0 != o_strcmp(field, "password") && 0 != o_strcmp(field, "scope") && 0 != o_strcmp(field, "email") && 0 != o_strcmp(field, "enabled")) { if ((j_format = json_object_get(json_object_get(j_params, "data-format"), field)) != NULL) { if (!profile || (profile && json_object_get(j_format, "profile-write") == json_true())) { if (json_array_size(j_property) && json_object_get(json_object_get(json_object_get(j_params, "data-format"), field), "multiple") == json_true()) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc((json_array_size(j_property) + 1) * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = mod_op; mods[i]->mod_type = (char *)json_string_value(json_object_get(j_format, "property")); json_array_foreach(j_property, index_scope, j_property_value) { if (0 == o_strcmp("base64", json_string_value(json_object_get(json_object_get(json_object_get(j_params, "data-format"), field), "convert")))) { if (o_base64_decode((const unsigned char *)json_string_value(j_property_value), json_string_length(j_property_value), NULL, &value_dec_len)) { if ((value_dec = o_malloc(value_dec_len+1)) != NULL) { if (o_base64_decode((const unsigned char *)json_string_value(j_property_value), json_string_length(j_property_value), value_dec, &value_dec_len)) { value_dec[value_dec_len] = '\0'; mods[i]->mod_values[index_scope] = (char *)value_dec; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error o_base64_decode for LDAP property '%s' (1-2)", json_string_value(j_property_value)); has_error = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for value_dec (1)"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error o_base64_decode for LDAP property '%s' (1-1)", json_string_value(j_property_value)); has_error = 1; } } else { mods[i]->mod_values[index_scope] = o_strdup(json_string_value(j_property_value)); } } mods[i]->mod_values[json_array_size(j_property)] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu]->mod_values (%s)", i, json_string_value(json_object_get(j_format, "property"))); has_error = 1; } i++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu] (%s)", i, json_string_value(json_object_get(j_format, "property"))); has_error = 1; } } else if (json_string_length(j_property) && json_object_get(json_object_get(json_object_get(j_params, "data-format"), field), "multiple") != json_true()) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { mods[i]->mod_values = o_malloc(2 * sizeof(char *)); if (mods[i]->mod_values != NULL) { mods[i]->mod_op = mod_op; mods[i]->mod_type = (char *)json_string_value(json_object_get(j_format, "property")); if (0 == o_strcmp("base64", json_string_value(json_object_get(json_object_get(json_object_get(j_params, "data-format"), field), "convert")))) { if (o_base64_decode((const unsigned char *)json_string_value(j_property), json_string_length(j_property), NULL, &value_dec_len)) { if ((value_dec = o_malloc(value_dec_len+1)) != NULL) { if (o_base64_decode((const unsigned char *)json_string_value(j_property), json_string_length(j_property), value_dec, &value_dec_len)) { value_dec[value_dec_len] = '\0'; mods[i]->mod_values[0] = (char *)value_dec; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error o_base64_decode for LDAP property '%s' (2-2)", json_string_value(j_property)); has_error = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for value_dec (2)"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error o_base64_decode for LDAP property '%s' (2-1)", json_string_value(j_property)); has_error = 1; } } else { mods[i]->mod_values[0] = o_strdup(json_string_value(j_property)); } mods[i]->mod_values[1] = NULL; i++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu]->mod_values (%s)", i, json_string_value(json_object_get(j_format, "property"))); has_error = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu] (%s)", i, json_string_value(json_object_get(j_format, "property"))); has_error = 1; } } else if (!add) { mods[i] = o_malloc(sizeof(LDAPMod)); if (mods[i] != NULL) { if ((mods[i]->mod_values = o_malloc(sizeof(char *))) != NULL) { mods[i]->mod_op = mod_op; mods[i]->mod_type = (char *)json_string_value(json_object_get(j_format, "property")); mods[i]->mod_values[0] = NULL; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu]->mod_values (%s)", i, json_string_value(json_object_get(j_format, "property"))); has_error = 1; } i++; } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods[%zu] (%s)", i, json_string_value(json_object_get(j_format, "property"))); has_error = 1; } } } } } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - Error allocating resources for mods"); has_error = 1; } if (has_error) { y_log_message(Y_LOG_LEVEL_ERROR, "get_ldap_write_mod - mods has error, cleaning memory"); for (i=0; mods[i]!=NULL; i++) { for (j=0; mods[i]->mod_values[j] != NULL; j++) { o_free(mods[i]->mod_values[j]); } o_free(mods[i]->mod_values); o_free(mods[i]); } o_free(mods); mods = NULL; } return mods; } static json_t * get_scope_from_ldap(json_t * j_params, const char * ldap_scope_value) { json_t * j_element = NULL; const char * key = NULL, * value; if (json_object_get(j_params, "scope-property-match-correspondence") != NULL) { json_object_foreach(json_object_get(j_params, "scope-property-match-correspondence"), key, j_element) { value = json_string_value(j_element); if ((0 == o_strcmp("equals", json_string_value(json_object_get(j_params, "scope-property-match"))) && 0 == o_strcmp(value, ldap_scope_value)) || (0 == o_strcmp("contains", json_string_value(json_object_get(j_params, "scope-property-match"))) && NULL != o_strstr(ldap_scope_value, value)) || (0 == o_strcmp("starts-with", json_string_value(json_object_get(j_params, "scope-property-match"))) && 0 != o_strncmp(ldap_scope_value, value, o_strlen(value))) || (0 == o_strcmp("ends-with", json_string_value(json_object_get(j_params, "scope-property-match"))) && 0 != strcmp(ldap_scope_value + o_strlen(ldap_scope_value) - o_strlen(value), value))) { return json_string(key); } } } return json_string(ldap_scope_value); } static json_t * get_user_from_result(json_t * j_params, json_t * j_properties_user, LDAP * ldap, LDAPMessage * entry) { json_t * j_user = json_object(), * j_property = NULL, * j_scope; const char * field = NULL; char * str_scope; struct berval ** result_values = NULL; int i; unsigned char * value_enc = NULL; size_t value_enc_len = 0; if (j_user != NULL) { json_object_foreach(j_properties_user, field, j_property) { result_values = ldap_get_values_len(ldap, entry, json_string_value(j_property)); if (ldap_count_values_len(result_values) > 0) { if (0 == o_strcmp(field, "scope")) { json_object_set_new(j_user, field, json_array()); for (i=0; ibv_val, result_values[i]->bv_len); j_scope = get_scope_from_ldap(j_params, str_scope); o_free(str_scope); if (j_scope != NULL) { json_array_append_new(json_object_get(j_user, field), j_scope); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_from_result - Error get_scope_from_ldap"); } } } else if (0 == o_strcmp(field, "username") || 0 == o_strcmp(field, "name") || 0 == o_strcmp(field, "email") || (json_object_get(json_object_get(j_params, "data-format"), field) != NULL &&json_object_get(json_object_get(json_object_get(j_params, "data-format"), field), "multiple") != json_true())) { json_object_set_new(j_user, field, json_stringn(result_values[0]->bv_val, result_values[0]->bv_len)); if (0 == o_strcmp("base64", json_string_value(json_object_get(json_object_get(json_object_get(j_params, "data-format"), field), "convert")))) { if (o_base64_encode((const unsigned char *)result_values[0]->bv_val, result_values[0]->bv_len, NULL, &value_enc_len)) { if ((value_enc = o_malloc(value_enc_len+1)) != NULL) { if (o_base64_encode((const unsigned char *)result_values[0]->bv_val, result_values[0]->bv_len, value_enc, &value_enc_len)) { value_enc[value_enc_len] = '\0'; json_object_set_new(j_user, field, json_stringn((const char *)value_enc, value_enc_len)); } else { y_log_message(Y_LOG_LEVEL_WARNING, "get_user_from_result - Error o_base64_encode for LDAP property '%s' (2)", json_string_value(j_property)); } o_free(value_enc); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_from_result - Error allocating resources for value_enc"); } } else { y_log_message(Y_LOG_LEVEL_WARNING, "get_user_from_result - Error o_base64_encode for LDAP property '%s' (1)", json_string_value(j_property)); } } else { json_object_set_new(j_user, field, json_stringn(result_values[0]->bv_val, result_values[0]->bv_len)); } } else if (json_object_get(json_object_get(json_object_get(j_params, "data-format"), field), "multiple") == json_true()) { json_object_set_new(j_user, field, json_array()); for (i=0; ibv_val, result_values[i]->bv_len, NULL, &value_enc_len)) { if ((value_enc = o_malloc(value_enc_len+1)) != NULL) { if (o_base64_encode((const unsigned char *)result_values[i]->bv_val, result_values[i]->bv_len, value_enc, &value_enc_len)) { value_enc[value_enc_len] = '\0'; json_array_append_new(json_object_get(j_user, field), json_stringn((const char *)value_enc, value_enc_len)); } else { y_log_message(Y_LOG_LEVEL_WARNING, "get_user_from_result - Error o_base64_encode for LDAP property '%s' (2)", json_string_value(j_property)); } o_free(value_enc); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_from_result - Error allocating resources for value_enc"); } } else { y_log_message(Y_LOG_LEVEL_WARNING, "get_user_from_result - Error o_base64_encode for LDAP property '%s' (1)", json_string_value(j_property)); } } else { json_array_append_new(json_object_get(j_user, field), json_stringn(result_values[i]->bv_val, result_values[i]->bv_len)); } } } } // A ldap user is always enabled, until I find a standard way to do it json_object_set_new(j_user, "enabled", json_true()); ldap_value_free_len(result_values); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_from_result - Error allocating resources for j_user"); } return j_user; } static char * get_user_dn_from_username(json_t * j_params, LDAP * ldap, const char * username) { char * user_dn, * filter; int result; char * attrs[] = {NULL}; int attrsonly = 0; LDAPMessage * answer = NULL, * entry; char * str_result = NULL; int scope = LDAP_SCOPE_ONELEVEL; if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_SUBTREE; } else if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_CHILDREN; } filter = msprintf("(&(%s)(%s=%s))", json_string_value(json_object_get(j_params, "filter")), get_read_property(j_params, "username-property"), username); if ((result = ldap_search_ext_s(ldap, json_string_value(json_object_get(j_params, "base-search")), scope, filter, attrs, attrsonly, NULL, NULL, NULL, LDAP_NO_LIMIT, &answer)) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_dn_from_username - Error ldap search, base search: %s, filter, error message: %s: %s", json_string_value(json_object_get(j_params, "base-search")), filter, ldap_err2string(result)); } else { if (ldap_count_entries(ldap, answer) > 0) { entry = ldap_first_entry(ldap, answer); user_dn = ldap_get_dn(ldap, entry); str_result = o_strdup(user_dn); ldap_memfree(user_dn); } else { y_log_message(Y_LOG_LEVEL_ERROR, "get_user_dn_from_username - Error username not found '%s'", username); } ldap_msgfree(answer); } o_free(filter); return str_result; } json_t * user_module_load(struct config_module * config) { UNUSED(config); return json_pack("{si ss ss ss sf}", "result", G_OK, "name", "ldap", "display_name", "LDAP backend user module", "description", "Module to store users in a LDAP server", "api_version", 2.5); } int user_module_unload(struct config_module * config) { UNUSED(config); return G_OK; } json_t * user_module_init(struct config_module * config, int readonly, int multiple_passwords, json_t * j_parameters, void ** cls) { UNUSED(config); json_t * j_properties, * j_return; char * error_message; j_properties = is_user_ldap_parameters_valid(j_parameters, readonly); if (check_result_value(j_properties, G_OK)) { json_object_set(j_parameters, "multiple_passwords", multiple_passwords?json_true():json_false()); *cls = json_incref(j_parameters); j_return = json_pack("{si}", "result", G_OK); } else if (check_result_value(j_properties, G_ERROR_PARAM)) { error_message = json_dumps(json_object_get(j_properties, "error"), JSON_COMPACT); y_log_message(Y_LOG_LEVEL_ERROR, "user_module_init database - Error parsing parameters"); y_log_message(Y_LOG_LEVEL_ERROR, error_message); o_free(error_message); j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", json_object_get(j_properties, "error")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_init database - Error is_user_database_parameters_valid"); j_return = json_pack("{sis[s]}", "result", G_ERROR, "error", "internal error"); } json_decref(j_properties); return j_return; } int user_module_close(struct config_module * config, void * cls) { UNUSED(config); json_decref((json_t *)cls); return G_OK; } size_t user_module_count_total(struct config_module * config, const char * pattern, void * cls) { UNUSED(config); json_t * j_params = (json_t *)cls; LDAP * ldap = connect_ldap_server(j_params); LDAPMessage * answer = NULL; char * attrs[] = { NULL }, * filter; int attrsonly = 0; size_t counter = 0; int result, scope = LDAP_SCOPE_ONELEVEL; if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_SUBTREE; } else if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_CHILDREN; } if (ldap != NULL) { filter = get_ldap_filter_pattern(j_params, pattern); if ((result = ldap_search_ext_s(ldap, json_string_value(json_object_get(j_params, "base-search")), scope, filter, attrs, attrsonly, NULL, NULL, NULL, LDAP_NO_LIMIT, &answer)) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_count_total ldap - Error ldap search, base search: %s, filter: %s: %s", json_string_value(json_object_get(j_params, "base-search")), filter, ldap_err2string(result)); } else { counter = ldap_count_entries(ldap, answer); } ldap_msgfree(answer); ldap_unbind_ext(ldap, NULL, NULL); o_free(filter); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_count_total ldap - Error connect_ldap_server"); } return counter; } json_t * user_module_get_list(struct config_module * config, const char * pattern, size_t offset, size_t limit, void * cls) { UNUSED(config); json_t * j_params = (json_t *)cls, * j_properties_user = NULL, * j_user_list, * j_user, * j_return; LDAP * ldap = connect_ldap_server(j_params); LDAPMessage * entry; int ldap_result; int scope = LDAP_SCOPE_ONELEVEL; char * filter = NULL; char ** attrs = NULL; int attrsonly = 0; struct berval ** result_values = NULL; /* paged control variables */ struct berval new_cookie, * cookie = NULL; int more_page, l_errcode = 0; LDAPControl * page_control = NULL, * search_controls[2] = { NULL, NULL }, ** returned_controls = NULL; LDAPMessage * l_result = NULL; ber_int_t total_count; if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_SUBTREE; } else if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_CHILDREN; } if (ldap != NULL) { // Connection successful, doing ldap search filter = get_ldap_filter_pattern(j_params, pattern); attrs = get_ldap_read_attributes(j_params, 0, (j_properties_user = json_object())); j_user_list = json_array(); do { ldap_result = ldap_create_page_control(ldap, json_integer_value(json_object_get(j_params, "page-size")), cookie, 0, &page_control); if (ldap_result != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_get_list ldap - Error ldap_create_page_control, message: %s", ldap_err2string(ldap_result)); break; } search_controls[0] = page_control; ldap_result = ldap_search_ext_s(ldap, json_string_value(json_object_get(j_params, "base-search")), scope, filter, attrs, attrsonly, search_controls, NULL, NULL, 0, &l_result); if ((ldap_result != LDAP_SUCCESS) & (ldap_result != LDAP_PARTIAL_RESULTS)) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_get_list ldap - Error ldap search, base search: %s, filter: %s, error message: %s", json_string_value(json_object_get(j_params, "base-search")), filter, ldap_err2string(ldap_result)); break; } ldap_result = ldap_parse_result(ldap, l_result, &l_errcode, NULL, NULL, NULL, &returned_controls, 0); if (ldap_result != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_get_list ldap - Error ldap_parse_result, message: %s", ldap_err2string(ldap_result)); break; } if (cookie != NULL) { ber_bvfree(cookie); cookie = NULL; } if (returned_controls != NULL) { ldap_result = ldap_parse_pageresponse_control(ldap, *returned_controls, &total_count, &new_cookie); if (ldap_result != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_get_list ldap - Error ldap_parse_pageresponse_control, message: %s", ldap_err2string(ldap_result)); break; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_get_list ldap - Error returned_controls is NULL"); break; } cookie = ber_memalloc( sizeof( struct berval ) ); if (cookie != NULL) { *cookie = new_cookie; if (cookie->bv_val != NULL && (o_strlen(cookie->bv_val) > 0)) { more_page = 1; } else { more_page = 0; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_get_list ldap - Error ber_malloc returned NULL"); break; } if (returned_controls != NULL) { ldap_controls_free(returned_controls); returned_controls = NULL; } search_controls[0] = NULL; ldap_control_free(page_control); page_control = NULL; entry = ldap_first_entry(ldap, l_result); for (;entry !=NULL && offset > 0; entry = ldap_next_entry(ldap, entry)) { offset--; } while (entry != NULL && limit) { j_user = get_user_from_result(j_params, j_properties_user, ldap, entry); if (j_user != NULL) { if (json_object_get(j_params, "multiple_passwords") == json_true()) { result_values = ldap_get_values_len(ldap, entry, json_string_value(json_object_get(j_params, "password-property"))); json_object_set_new(j_user, "password", json_integer(ldap_count_values_len(result_values))); ldap_value_free_len(result_values); } json_array_append_new(j_user_list, j_user); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_get_list ldap - Error get_user_from_result"); } entry = ldap_next_entry(ldap, entry); limit--; } ldap_msgfree(l_result); l_result = NULL; } while (more_page && limit); ldap_msgfree(l_result); l_result = NULL; o_free(filter); ber_bvfree(cookie); cookie = NULL; ldap_unbind_ext(ldap, NULL, NULL); j_return = json_pack("{sisO}", "result", G_OK, "list", j_user_list); json_decref(j_user_list); json_decref(j_properties_user); o_free(attrs); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_get_list ldap - Error connect_ldap_server"); j_return = json_pack("{si}", "result", G_ERROR); } return j_return; } json_t * user_module_get(struct config_module * config, const char * username, void * cls) { UNUSED(config); json_t * j_params = (json_t *)cls, * j_properties_user = NULL, * j_user, * j_return; LDAP * ldap = connect_ldap_server(j_params); LDAPMessage * entry, * answer; int ldap_result; struct berval ** result_values = NULL; int scope = LDAP_SCOPE_ONELEVEL; char * filter = NULL; char ** attrs = NULL; int attrsonly = 0; if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_SUBTREE; } else if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_CHILDREN; } if (ldap != NULL) { // Connection successful, doing ldap search filter = msprintf("(&(%s)(%s=%s))", json_string_value(json_object_get(j_params, "filter")), get_read_property(j_params, "username-property"), username); attrs = get_ldap_read_attributes(j_params, 0, (j_properties_user = json_object())); if ((ldap_result = ldap_search_ext_s(ldap, json_string_value(json_object_get(j_params, "base-search")), scope, filter, attrs, attrsonly, NULL, NULL, NULL, LDAP_NO_LIMIT, &answer)) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_get user - Error ldap search, base search: %s, filter: %s: %s", json_string_value(json_object_get(j_params, "base-search")), filter, ldap_err2string(ldap_result)); j_return = json_pack("{si}", "result", G_ERROR); } else { // Looping in results, staring at offset, until the end of the list if (ldap_count_entries(ldap, answer) > 0) { entry = ldap_first_entry(ldap, answer); j_user = get_user_from_result(j_params, j_properties_user, ldap, entry); if (j_user != NULL) { if (json_object_get(j_params, "multiple_passwords") == json_true()) { result_values = ldap_get_values_len(ldap, entry, json_string_value(json_object_get(j_params, "password-property"))); json_object_set_new(j_user, "password", json_integer(ldap_count_values_len(result_values))); ldap_value_free_len(result_values); } j_return = json_pack("{sisO}", "result", G_OK, "user", j_user); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_get_list ldap user - Error get_user_from_result"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_user); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } json_decref(j_properties_user); o_free(attrs); o_free(filter); ldap_msgfree(answer); ldap_unbind_ext(ldap, NULL, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_get ldap user - Error connect_ldap_server"); j_return = json_pack("{si}", "result", G_ERROR); } return j_return; } json_t * user_module_get_profile(struct config_module * config, const char * username, void * cls) { UNUSED(config); json_t * j_params = (json_t *)cls, * j_properties_user = NULL, * j_user, * j_return; LDAP * ldap = connect_ldap_server(j_params); LDAPMessage * entry, * answer; int ldap_result; struct berval ** result_values = NULL; int scope = LDAP_SCOPE_ONELEVEL; char * filter = NULL; char ** attrs = NULL; int attrsonly = 0; if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_SUBTREE; } else if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_CHILDREN; } if (ldap != NULL) { // Connection successful, doing ldap search filter = msprintf("(&(%s)(%s=%s))", json_string_value(json_object_get(j_params, "filter")), get_read_property(j_params, "username-property"), username); attrs = get_ldap_read_attributes(j_params, 1, (j_properties_user = json_object())); if ((ldap_result = ldap_search_ext_s(ldap, json_string_value(json_object_get(j_params, "base-search")), scope, filter, attrs, attrsonly, NULL, NULL, NULL, LDAP_NO_LIMIT, &answer)) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_get_profile ldap user - Error ldap search, base search: %s, filter: %s: %s", json_string_value(json_object_get(j_params, "base-search")), filter, ldap_err2string(ldap_result)); j_return = json_pack("{si}", "result", G_ERROR); } else { if (ldap_count_entries(ldap, answer) > 0) { entry = ldap_first_entry(ldap, answer); j_user = get_user_from_result(j_params, j_properties_user, ldap, entry); if (j_user != NULL) { if (json_object_get(j_params, "multiple_passwords") == json_true()) { result_values = ldap_get_values_len(ldap, entry, json_string_value(json_object_get(j_params, "password-property"))); json_object_set_new(j_user, "password", json_integer(ldap_count_values_len(result_values))); ldap_value_free_len(result_values); } j_return = json_pack("{sisO}", "result", G_OK, "user", j_user); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_get_list ldap user - Error get_user_from_result"); j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_user); } else { j_return = json_pack("{si}", "result", G_ERROR_NOT_FOUND); } } json_decref(j_properties_user); o_free(attrs); o_free(filter); ldap_msgfree(answer); ldap_unbind_ext(ldap, NULL, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_get_profile ldap user - Error connect_ldap_server"); j_return = json_pack("{si}", "result", G_ERROR); } return j_return; } json_t * user_module_is_valid(struct config_module * config, const char * username, json_t * j_user, int mode, void * cls) { json_t * j_params = (json_t *)cls; json_t * j_result = json_array(), * j_element = NULL, * j_format, * j_value, * j_return, * j_cur_user; char * message; size_t index = 0, len = 0; const char * property; if (j_result != NULL) { if (mode == GLEWLWYD_IS_VALID_MODE_ADD) { if (!json_string_length(json_object_get(j_user, "username"))) { json_array_append_new(j_result, json_string("username is mandatory and must be a non empty string")); } else { j_cur_user = user_module_get(config, json_string_value(json_object_get(j_user, "username")), cls); if (check_result_value(j_cur_user, G_OK)) { json_array_append_new(j_result, json_string("username already exist")); } else if (!check_result_value(j_cur_user, G_ERROR_NOT_FOUND)) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_is_valid database - Error user_module_get"); } json_decref(j_cur_user); } } else if ((mode == GLEWLWYD_IS_VALID_MODE_UPDATE || mode == GLEWLWYD_IS_VALID_MODE_UPDATE_PROFILE) && username == NULL) { json_array_append_new(j_result, json_string("username is mandatory on update mode")); } if (mode != GLEWLWYD_IS_VALID_MODE_UPDATE_PROFILE) { if (json_object_get(j_user, "scope") != NULL) { if (!json_is_array(json_object_get(j_user, "scope"))) { json_array_append_new(j_result, json_string("scope must be a JSON array of string")); } else { json_array_foreach(json_object_get(j_user, "scope"), index, j_element) { if (!json_is_string(j_element) || !json_string_length(j_element)) { json_array_append_new(j_result, json_string("scope must be a JSON array of string")); } } } } } if (json_object_get(j_params, "multiple_passwords") == json_true()) { if (mode != GLEWLWYD_IS_VALID_MODE_UPDATE_PROFILE && json_object_get(j_user, "password") != NULL && !json_is_array(json_object_get(j_user, "password"))) { json_array_append_new(j_result, json_string("password must be an array")); } } else { if (mode != GLEWLWYD_IS_VALID_MODE_UPDATE_PROFILE && json_object_get(j_user, "password") != NULL && !json_is_string(json_object_get(j_user, "password"))) { json_array_append_new(j_result, json_string("password must be a string")); } } if (json_object_get(j_user, "name") != NULL && !json_is_string(json_object_get(j_user, "name"))) { json_array_append_new(j_result, json_string("name must be a non empty string")); } if (json_object_get(j_user, "email") != NULL && !json_is_string(json_object_get(j_user, "email"))) { json_array_append_new(j_result, json_string("email must be a non empty string")); } if (json_object_get(j_user, "enabled") != NULL && !json_is_boolean(json_object_get(j_user, "enabled"))) { json_array_append_new(j_result, json_string("enabled must be a boolean")); } json_object_foreach(j_user, property, j_element) { if (0 != o_strcmp(property, "username") && 0 != o_strcmp(property, "name") && 0 != o_strcmp(property, "email") && 0 != o_strcmp(property, "enabled") && 0 != o_strcmp(property, "password") && 0 != o_strcmp(property, "source") && 0 != o_strcmp(property, "scope")) { j_format = json_object_get(json_object_get(j_params, "data-format"), property); if (json_object_get(j_format, "multiple") == json_true()) { if (!json_is_array(j_element)) { message = msprintf("%s must be an array", property); json_array_append_new(j_result, json_string(message)); o_free(message); } else { json_array_foreach(j_element, index, j_value) { if (!json_is_string(j_value)) { message = msprintf("%s must contain a non empty string value", property); json_array_append_new(j_result, json_string(message)); o_free(message); } else if (json_string_length(j_value) && 0 == o_strcmp("base64", json_string_value(json_object_get(j_format, "convert")))) { if (!o_base64_decode((const unsigned char *)json_string_value(j_value), json_string_length(j_value), NULL, &len)) { message = msprintf("%s must contain a base64 encoded string value", property); json_array_append_new(j_result, json_string(message)); o_free(message); } } } } } else { if (!json_is_string(j_element)) { message = msprintf("%s must contain a string value", property); json_array_append_new(j_result, json_string(message)); o_free(message); } else if (json_string_length(j_element) && 0 == o_strcmp("base64", json_string_value(json_object_get(j_format, "convert")))) { if (!o_base64_decode((const unsigned char *)json_string_value(j_element), json_string_length(j_element), NULL, &len)) { message = msprintf("%s must contain a base64 encoded string value", property); json_array_append_new(j_result, json_string(message)); o_free(message); } } } } } if (json_array_size(j_result)) { j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_result); } else { j_return = json_pack("{si}", "result", G_OK); } json_decref(j_result); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_is_valid ldap - Error allocating resources for j_result"); j_return = json_pack("{si}", "result", G_ERROR_MEMORY); } return j_return; } int user_module_add(struct config_module * config, json_t * j_user, void * cls) { UNUSED(config); json_t * j_params = (json_t *)cls; LDAP * ldap = connect_ldap_server(j_params); int ret, result; LDAPMod ** mods = NULL; char * new_dn; size_t i, j; if (ldap != NULL) { mods = get_ldap_write_mod(j_params, ldap, json_string_value(json_object_get(j_user, "username")), j_user, 0, 1); if (mods != NULL) { new_dn = msprintf("%s=%s,%s", json_string_value(json_object_get(j_params, "rdn-property")), json_string_value(json_object_get(j_user, "username")), json_string_value(json_object_get(j_params, "base-search"))); if (new_dn != NULL) { if ((result = ldap_add_ext_s(ldap, new_dn, mods, NULL, NULL)) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_add ldap - Error adding new user %s in the ldap backend: %s", new_dn, ldap_err2string(result)); ret = G_ERROR; } else { ret = G_OK; } o_free(new_dn); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_add ldap - Error allocating resources for new_dn"); ret = G_ERROR; } for (i=0; mods[i]!=NULL; i++) { for (j=0; mods[i]->mod_values[j] != NULL; j++) { o_free(mods[i]->mod_values[j]); } o_free(mods[i]->mod_values); o_free(mods[i]); } o_free(mods); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_add ldap - Error get_ldap_write_mod"); ret = G_ERROR; } ldap_unbind_ext(ldap, NULL, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_add ldap - Error connect_ldap_server"); ret = G_ERROR; } return ret; } int user_module_update(struct config_module * config, const char * username, json_t * j_user, void * cls) { UNUSED(config); json_t * j_params = (json_t *)cls; LDAP * ldap = connect_ldap_server(j_params); int ret, result; LDAPMod ** mods = NULL; char * cur_dn; size_t i, j; if (ldap != NULL) { mods = get_ldap_write_mod(j_params, ldap, username, j_user, 0, 0); if (mods != NULL) { cur_dn = get_user_dn_from_username(j_params, ldap, username); if (cur_dn != NULL) { if ((result = ldap_modify_ext_s(ldap, cur_dn, mods, NULL, NULL)) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update user - Error update user %s in the ldap backend: %s", cur_dn, ldap_err2string(result)); ret = G_ERROR; } else { ret = G_OK; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update ldap - Error get_user_dn_from_username"); ret = G_ERROR; } o_free(cur_dn); for (i=0; mods[i]!=NULL; i++) { for (j=0; mods[i]->mod_values[j] != NULL; j++) { o_free(mods[i]->mod_values[j]); } o_free(mods[i]->mod_values); o_free(mods[i]); } o_free(mods); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update ldap - Error get_ldap_write_mod"); ret = G_ERROR; } ldap_unbind_ext(ldap, NULL, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update ldap - Error connect_ldap_server"); ret = G_ERROR; } return ret; } int user_module_update_profile(struct config_module * config, const char * username, json_t * j_user, void * cls) { UNUSED(config); json_t * j_params = (json_t *)cls; LDAP * ldap = connect_ldap_server(j_params); int ret, result; LDAPMod ** mods = NULL; char * cur_dn; size_t i, j; if (ldap != NULL) { mods = get_ldap_write_mod(j_params, ldap, username, j_user, 1, 0); if (mods != NULL) { cur_dn = get_user_dn_from_username(j_params, ldap, username); if (cur_dn != NULL) { if ((result = ldap_modify_ext_s(ldap, cur_dn, mods, NULL, NULL)) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update_profile user - Error update user profile %s in the ldap backend: %s", cur_dn, ldap_err2string(result)); ret = G_ERROR; } else { ret = G_OK; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update ldap - Error get_user_dn_from_username"); ret = G_ERROR; } o_free(cur_dn); for (i=0; mods[i]!=NULL; i++) { for (j=0; mods[i]->mod_values[j] != NULL; j++) { o_free(mods[i]->mod_values[j]); } o_free(mods[i]->mod_values); o_free(mods[i]); } o_free(mods); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update ldap - Error get_ldap_write_mod"); ret = G_ERROR; } ldap_unbind_ext(ldap, NULL, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update ldap - Error connect_ldap_server"); ret = G_ERROR; } return ret; } int user_module_delete(struct config_module * config, const char * username, void * cls) { UNUSED(config); json_t * j_params = (json_t *)cls; LDAP * ldap = connect_ldap_server(j_params); int ret, result; char * cur_dn; if (ldap != NULL) { cur_dn = get_user_dn_from_username(j_params, ldap, username); if (cur_dn != NULL) { if ((result = ldap_delete_ext_s(ldap, cur_dn, NULL, NULL)) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_delete user - Error delete user %s in the ldap backend: %s", cur_dn, ldap_err2string(result)); ret = G_ERROR; } else { ret = G_OK; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update ldap - Error get_user_dn_from_username"); ret = G_ERROR; } o_free(cur_dn); ldap_unbind_ext(ldap, NULL, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update ldap - Error connect_ldap_server"); ret = G_ERROR; } return ret; } int user_module_check_password(struct config_module * config, const char * username, const char * password, void * cls) { UNUSED(config); json_t * j_params = (json_t *)cls; LDAP * ldap = connect_ldap_server(j_params); LDAPMessage * entry, * answer; int ldap_result, result_login, result; char * user_dn = NULL; int scope = LDAP_SCOPE_ONELEVEL; char * filter = NULL; char * attrs[] = {"memberOf", NULL, NULL}; int attrsonly = 0; char * ldap_mech = LDAP_SASL_SIMPLE; struct berval cred; struct berval *servcred; if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_SUBTREE; } else if (0 == o_strcmp(json_string_value(json_object_get(j_params, "search-scope")), "subtree")) { scope = LDAP_SCOPE_CHILDREN; } if (ldap != NULL) { // Connection successful, doing ldap search filter = msprintf("(&(%s)(%s=%s))", json_string_value(json_object_get(j_params, "filter")), get_read_property(j_params, "username-property"), username); if ((ldap_result = ldap_search_ext_s(ldap, json_string_value(json_object_get(j_params, "base-search")), scope, filter, attrs, attrsonly, NULL, NULL, NULL, LDAP_NO_LIMIT, &answer)) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_check_password ldap - Error ldap search, base search: %s, filter: %s: %s", json_string_value(json_object_get(j_params, "base-search")), filter, ldap_err2string(ldap_result)); result = G_ERROR; } else { if (ldap_count_entries(ldap, answer) > 0) { // Testing the first result to username with the given password entry = ldap_first_entry(ldap, answer); user_dn = ldap_get_dn(ldap, entry); cred.bv_val = (char *)password; cred.bv_len = o_strlen(password); result_login = ldap_sasl_bind_s(ldap, user_dn, ldap_mech, &cred, NULL, NULL, &servcred); ldap_memfree(user_dn); if (result_login == LDAP_SUCCESS) { result = G_OK; } else { result = G_ERROR_UNAUTHORIZED; } } else { result = G_ERROR_NOT_FOUND; } } o_free(filter); ldap_msgfree(answer); ldap_unbind_ext(ldap, NULL, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_check_password ldap - Error connect_ldap_server"); result = G_ERROR; } return result; } int user_module_update_password(struct config_module * config, const char * username, const char ** new_passwords, size_t new_passwords_len, void * cls) { UNUSED(config); json_t * j_params = (json_t *)cls; LDAP * ldap = connect_ldap_server(j_params); int ret, result, i; LDAPMod * mods[2] = {NULL, NULL}; char * cur_dn; if (ldap != NULL) { mods[0] = o_malloc(sizeof(LDAPMod)); if (mods[0] != NULL) { if (json_object_get(j_params, "multiple_passwords") == json_true()) { mods[0]->mod_op = LDAP_MOD_REPLACE; mods[0]->mod_type = (char *)json_string_value(json_object_get(j_params, "password-property")); mods[1] = NULL; if (set_update_password_mod(j_params, ldap, username, new_passwords, new_passwords_len, mods[0], 0) == G_OK) { cur_dn = get_user_dn_from_username(j_params, ldap, username); if (cur_dn != NULL) { if ((result = ldap_modify_ext_s(ldap, cur_dn, mods, NULL, NULL)) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update_password ldap - Error setting new user %s in the ldap backend: %s", cur_dn, ldap_err2string(result)); ret = G_ERROR; } else { ret = G_OK; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update_password ldap - Error get_user_dn_from_username"); ret = G_ERROR; } o_free(cur_dn); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update_password ldap - Error set_update_password_mod"); ret = G_ERROR; } for (i=0; mods[0]->mod_values[i]!=NULL; i++) { o_free(mods[0]->mod_values[i]); } o_free(mods[0]->mod_values); o_free(mods[0]); } else { mods[0]->mod_values = o_malloc(2 * sizeof(char *)); mods[0]->mod_op = LDAP_MOD_REPLACE; mods[0]->mod_type = (char *)json_string_value(json_object_get(j_params, "password-property")); mods[0]->mod_values[0] = o_strlen(new_passwords[0])?generate_hash(get_digest_algorithm(j_params), new_passwords[0]):NULL; mods[0]->mod_values[1] = NULL; mods[1] = NULL; cur_dn = get_user_dn_from_username(j_params, ldap, username); if (cur_dn != NULL) { if ((result = ldap_modify_ext_s(ldap, cur_dn, mods, NULL, NULL)) != LDAP_SUCCESS) { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update_password ldap - Error setting new user %s in the ldap backend: %s", cur_dn, ldap_err2string(result)); ret = G_ERROR; } else { ret = G_OK; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update_password ldap - Error get_user_dn_from_username"); ret = G_ERROR; } o_free(cur_dn); o_free(mods[0]->mod_values[0]); o_free(mods[0]->mod_values); o_free(mods[0]); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update_password ldap - Error allocating resources for mods"); ret = G_ERROR; } ldap_unbind_ext(ldap, NULL, NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "user_module_update_password ldap - Error connect_ldap_server"); ret = G_ERROR; } return ret; } glewlwyd-2.6.1/src/user/mock.c000066400000000000000000000735231415646314000162360ustar00rootroot00000000000000/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * Mock user module * * Copyright 2019-2020 Nicolas Mora * * The MIT License (MIT) * * 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. * */ #include #include #include #include #include "glewlwyd-common.h" /** * * Note on the user module * * The JSON object of the user has the following format concerning the reserved properties: * { * "username": string, username of the user (its login), must be at most 128 characters and unique among the current instance * "name": string, full name * "scope": array of strings, scopes available for the user, each scope must be a string of at most 128 characters * "enabled": boolean, if false, the user won't be able to connect * } * * - The username shouldn't be updated after creation * - How the password is stored and encrypted is up to the implementation. * Although the password encrypted or not SHOULDN'T be returned in the user object * - The scope values mustn't be updated in profile mode, to avoid a user to change his or her own credential * - The "enabled" property is mandatory in the returned values of user_module_get_list or user_module_get * If a user doesn't have "enabled":true set, then it will be unavailable for connection * * The only mandatory values are username and anabled, other values are optional * Other values can be handled by the module, it's up to the implementation * * struct config_module { * const char * external_url; // Absolute url of the glewlwyd service * const char * login_url; // Relative url of the login page * const char * admin_scope; // Value of the g_admin scope * const char * profile_scope; // Value of the g_profile scope * struct _h_connection * conn; // Hoel structure to access to the database * digest_algorithm hash_algorithm; // Hash algorithm used in Glewlwyd * struct config_elements * glewlwyd_config; // Pointer to the global config structure * // Function used to return a user object * json_t * (* glewlwyd_module_callback_get_user)(struct config_module * config, const char * username); * // Function used to update a user * int (* glewlwyd_module_callback_set_user)(struct config_module * config, const char * username, json_t * j_user); * // Function used to check the validity of a user's password * int (* glewlwyd_module_callback_check_user_password)(struct config_module * config, const char * username, const char * password); * }; * */ static int json_has_str_pattern_case(json_t * j_source, const char * pattern) { const char * key = NULL; size_t index = 0; json_t * j_element = NULL; if (j_source != NULL) { if (json_is_string(j_source) && o_strcasestr(json_string_value(j_source), pattern) != NULL) { return 1; } else if (json_is_object(j_source)) { json_object_foreach(j_source, key, j_element) { if (json_has_str_pattern_case(j_element, pattern)) { return 1; } } return 0; } else if (json_is_array(j_source)) { json_array_foreach(j_source, index, j_element) { if (json_has_str_pattern_case(j_element, pattern)) { return 1; } } return 0; } else { return 0; } } else { return 0; } } /** * * user_module_load * * Executed once when Glewlwyd service is started * Used to identify the module and to show its parameters on init * You can also use it to load resources that are required once for all * instance modules for example * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * name: string, mandatory, name of the module, must be unique among other scheme modules * display_name: string, optional, long name of the module * description: string, optional, description for the module * parameters: object, optional, parameters description for the module * } * * Example: * { * result: G_OK, * name: "mock", * display_name: "Mock scheme module", * description: "Mock scheme module for glewlwyd tests", * parameters: { * mock-value: { * type: "string", * mandatory: true * } * } * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ json_t * user_module_load(struct config_module * config) { UNUSED(config); return json_pack("{sisssssssf}", "result", G_OK, "name", "mock", "display_name", "Mock user module", "description", "Mock user module for glewlwyd tests", "api_version", 2.5); } /** * * user_module_unload * * Executed once when Glewlwyd service is stopped * You can use it to release resources that are required once for all * instance modules for example * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ int user_module_unload(struct config_module * config) { UNUSED(config); return G_OK; } /** * * user_module_init * * Initialize an instance of this module declared in Glewlwyd service. * If required, you must dynamically allocate a pointer to the configuration * for this instance and pass it to *cls * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, G_ERROR_PARAM on input parameters error, another value on error) * error: array of strings containg the list of input errors, mandatory on result G_ERROR_PARAM, ignored otherwise * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_parameters: used to initialize an instance in JSON format * The module must validate itself its parameters * @parameter cls: will contain an allocated void * pointer that will be sent back * as void * in all module functions * */ json_t * user_module_init(struct config_module * config, int readonly, int multiple_passwords, json_t * j_parameters, void ** cls) { UNUSED(readonly); json_t * j_return, * j_password = NULL; const char * password = json_string_value(json_object_get(j_parameters, "password")); if (json_object_get(j_parameters, "error") == NULL) { const char * prefix = ""; if (json_string_length(json_object_get(j_parameters, "username-prefix"))) { prefix = json_string_value(json_object_get(j_parameters, "username-prefix")); } if (password == NULL) { password = "password"; } if (multiple_passwords) { j_password = json_pack("[s]", password); } else { j_password = json_string(password); } *cls = (void*)json_pack("{sos[{ss+ ss ss so s[sss]}{ss+ ss ss so s[sssss]}{ss+ ss ss so s[sss]}{ss+ ss ss so s[ssss]}]}", "password", j_password, "list", "username", prefix, "admin", "name", "The Boss", "email", "boss@glewlwyd.domain", "enabled",json_true(), "scope", config->admin_scope, config->profile_scope, "openid", "username", prefix, "user1", "name", "Dave Lopper 1", "email", "dev1@glewlwyd", "enabled", json_true(), "scope", config->profile_scope, "openid", "scope1", "scope2", "scope3", "username", prefix, "user2", "name", "Dave Lopper 2", "email", "dev2@glewlwyd", "enabled", json_true(), "scope", config->profile_scope, "openid", "scope1", "username", prefix, "user3", "name", "Dave Lopper 3", "email", "dev3@glewlwyd", "enabled", json_true(), "scope", config->profile_scope, "scope1", "scope2", "scope3"); y_log_message(Y_LOG_LEVEL_DEBUG, "user_module_init - success prefix: '%s', profile_scope: '%s', admin_scope: '%s'", prefix, config->profile_scope, config->admin_scope); j_return = json_pack("{si}", "result", G_OK); } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "Error input parameters"); } return j_return; } /** * * user_module_close * * Close an instance of this module declared in Glewlwyd service. * You must free the memory previously allocated in * the user_module_init function as void * cls * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int user_module_close(struct config_module * config, void * cls) { UNUSED(config); y_log_message(Y_LOG_LEVEL_DEBUG, "user_module_close - success"); json_decref((json_t *)cls); return G_OK; } /** * * user_module_count_total * * Return the total number of users handled by this module corresponding * to the given pattern * * @return value: The total of corresponding users * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter pattern: The pattern to match for the users. How the * pattern is used is up to the implementation. * Glewlwyd recommends to match the pattern with the * username, name and e-mail value for each users * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ size_t user_module_count_total(struct config_module * config, const char * pattern, void * cls) { UNUSED(config); json_t * j_user = NULL; size_t index = 0, total; if (o_strlen(pattern)) { total = 0; json_array_foreach(json_object_get((json_t *)cls, "list"), index, j_user) { if (json_has_str_pattern_case(j_user, pattern)) { total++; } } } else { total = json_array_size(json_object_get((json_t *)cls, "list")); } return total; } /** * * user_module_get_list * * Return a list of users handled by this module corresponding * to the given pattern between the specified offset and limit * These are the user objects returned to the administrator * * @return value: A list of corresponding users or an empty list * using the following JSON format: {"result":G_OK,"list":[{user object}]} * On error, this function must return another value for "result" * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter pattern: The pattern to match for the users. How the * pattern is used is up to the implementation. * Glewlwyd recommends to match the pattern with the * username, name and e-mail value for each users * @pattern offset: The offset to reduce the returned list among the total list * @pattern limit: The maximum number of users to return * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ json_t * user_module_get_list(struct config_module * config, const char * pattern, size_t offset, size_t limit, void * cls) { UNUSED(config); json_t * j_user = NULL, * j_array, * j_pattern_array, * j_return; size_t index = 0, counter = 0; json_int_t password = -1; if (json_is_array(json_object_get((json_t *)cls, "password"))) { password = (json_int_t)json_array_size(json_object_get((json_t *)cls, "password")); } if (limit) { if (o_strlen(pattern)) { j_pattern_array = json_array(); json_array_foreach(json_object_get((json_t *)cls, "list"), index, j_user) { if (json_has_str_pattern_case(j_user, pattern)) { json_array_append_new(j_pattern_array, json_deep_copy(j_user)); } } } else { j_pattern_array = json_deep_copy(json_object_get((json_t *)cls, "list")); } j_array = json_array(); if (j_array != NULL) { json_array_foreach(j_pattern_array, index, j_user) { if (index >= offset && (offset + counter) < json_array_size(j_pattern_array) && counter < limit) { if (password > -1) { json_object_set_new(j_user, "password", json_integer(password)); } json_array_append(j_array, j_user); counter++; } } j_return = json_pack("{sisO}", "result", G_OK, "list", j_array); json_decref(j_array); } else { j_return = json_pack("{si}", "result", G_ERROR); } json_decref(j_pattern_array); } else { j_return = json_pack("{si}", "result", G_ERROR_PARAM); } return j_return; } /** * * user_module_get * * Return a user object handled by this module corresponding * to the username specified * This is the user object returned to the administrator * * @return value: G_OK and the corresponding user * G_ERROR_NOT_FOUND if username is not found * The returned format is {"result":G_OK,"user":{user object}} * On error, this function must return another value for "result" * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: the username to match, must be case insensitive * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ json_t * user_module_get(struct config_module * config, const char * username, void * cls) { UNUSED(config); json_t * j_user = NULL, * j_new_user; size_t index = 0; if (username != NULL && o_strlen(username)) { json_array_foreach(json_object_get((json_t *)cls, "list"), index, j_user) { if (0 == o_strcmp(username, json_string_value(json_object_get(j_user, "username")))) { j_new_user = json_deep_copy(j_user); if (json_is_array(json_object_get((json_t *)cls, "password"))) { json_object_set_new(j_new_user, "password", json_integer(json_array_size(json_object_get((json_t *)cls, "password")))); } return json_pack("{siso}", "result", G_OK, "user", j_new_user); break; } } return json_pack("{si}", "result", G_ERROR_NOT_FOUND); } else { return json_pack("{si}", "result", G_ERROR); } } /** * * user_module_get_profile * * Return a user object handled by this module corresponding * to the username specified. * This is the user object returned to the connected user, may be different from the * user_module_get object format if a connected user must have access to different data * * @return value: G_OK and the corresponding user * G_ERROR_NOT_FOUND if username is not found * The returned format is {"result":G_OK,"user":{user object}} * On error, this function must return another value for "result" * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: the username to match, must be case insensitive * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ json_t * user_module_get_profile(struct config_module * config, const char * username, void * cls) { return user_module_get(config, username, cls); } /** * * user_module_is_valid * * Validate if a user is valid to save for the specified mode * * @return value: G_OK if the user is valid * G_ERROR_PARAM and an array containing the errors in string format * The returned format is {"result":G_OK} on success * {"result":G_ERROR_PARAM,"error":["error 1","error 2"]} on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: the username to match, must be case insensitive * @parameter j_user: The user to validate * @parameter mode: The mode corresponding to the context, values available are: * - GLEWLWYD_IS_VALID_MODE_ADD: Add a user by an administrator * Note: in this mode, the module musn't check for already existing user, * This is already handled by Glewlwyd * - GLEWLWYD_IS_VALID_MODE_UPDATE: Update a user by an administrator * - GLEWLWYD_IS_VALID_MODE_UPDATE_PROFILE: Update a user by him or * herself in the profile context * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ json_t * user_module_is_valid(struct config_module * config, const char * username, json_t * j_user, int mode, void * cls) { UNUSED(config); json_t * j_return = NULL; if ((mode == GLEWLWYD_IS_VALID_MODE_UPDATE || mode == GLEWLWYD_IS_VALID_MODE_UPDATE_PROFILE) && username == NULL) { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "username is mandatory on update mode"); } else { if (mode == GLEWLWYD_IS_VALID_MODE_ADD) { if (json_is_string(json_object_get(j_user, "username")) && json_string_length(json_object_get(j_user, "username")) <= 128) { if (json_object_get(j_user, "password") != NULL) { if (json_is_array(json_object_get((json_t *)cls, "password")) && json_is_array(json_object_get(j_user, "password"))) { j_return = json_pack("{si}", "result", G_OK); } else if (!json_is_array(json_object_get((json_t *)cls, "password")) && json_string_length(json_object_get(j_user, "password"))) { j_return = json_pack("{si}", "result", G_OK); } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "invalid password"); } } else { j_return = json_pack("{si}", "result", G_OK); } } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "username must be a string value of maximum 128 characters"); } } else { if (json_object_get(j_user, "password") != NULL) { if (json_is_array(json_object_get((json_t *)cls, "password")) && json_is_array(json_object_get(j_user, "password"))) { j_return = json_pack("{si}", "result", G_OK); } else if (!json_is_array(json_object_get((json_t *)cls, "password")) && json_string_length(json_object_get(j_user, "password"))) { j_return = json_pack("{si}", "result", G_OK); } else { j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "invalid password"); } } else { j_return = json_pack("{si}", "result", G_OK); } } } return j_return; } /** * * user_module_add * * Add a new user by an administrator * * @return value: G_OK on success * Another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_user: The user to add * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int user_module_add(struct config_module * config, json_t * j_user, void * cls) { UNUSED(config); json_t * j_password, * j_pwd_cur, * j_pwd_next; size_t index = 0, pwd_max = 0; if (json_object_get(j_user, "password") != NULL) { if (json_is_array(json_object_get((json_t *)cls, "password"))) { j_password = json_array(); pwd_max = MAX(json_array_size(json_object_get(j_user, "password")), json_array_size(json_object_get((json_t *)cls, "password"))); for (index=0; index # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; # version 2.1 of the License. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this library. If not, see . # GLWD_SRC=.. DESTDIR=/usr/local MODULES_TARGET=$(DESTDIR)/lib/glewlwyd/user_middleware CC=gcc CFLAGS=-c -fPIC -Wall -Werror -Wextra -Wno-unknown-pragmas -D_REENTRANT -Wno-pragmas -I$(GLWD_SRC) $(shell pkg-config --cflags liborcania) $(shell pkg-config --cflags libyder) $(shell pkg-config --cflags jansson) $(shell pkg-config --cflags libhoel) $(ADDITIONALFLAGS) LIBS=$(shell pkg-config --libs liborcania) $(shell pkg-config --libs libyder) $(shell pkg-config --libs jansson) -ldl LDFLAGS += -Wl,-R '-Wl,-R$$ORIGIN' TARGET= all: release misc.o: $(GLWD_SRC)/misc.c $(GLWD_SRC)/glewlwyd-common.h $(CC) $(CFLAGS) $(CPPFLAGS) $(GLWD_SRC)/misc.c %.o: %.c $(GLWD_SRC)/glewlwyd.h $(CC) $(CFLAGS) $(CPPFLAGS) $< libmodmock.so: mock.o misc.o $(CC) -shared -Wl,-soname,libmodmock.so mock.o misc.o -o libmodmock.so $(LIBS) $(LDFLAGS) clean: rm -f *.o *.so debug: ADDITIONALFLAGS=-DDEBUG -g -O0 debug: libmodmock.so $(TARGET) release: ADDITIONALFLAGS=-O3 release: $(TARGET) install: mkdir -p $(MODULES_TARGET) cp -f *.so $(MODULES_TARGET) glewlwyd-2.6.1/src/user_middleware/README.md000066400000000000000000000245101415646314000206050ustar00rootroot00000000000000# Glewlwyd User Middleware Backend Modules A Glewlwyd module is built as a library and loaded at startup. It must contain a specific set of functions available to glewlwyd to work properly. A Glewlwyd module can access the entire data and functions available to Glewlwyd service. There is no limitation to its access. Therefore, Glewlwyd modules must be carefully designed and considered friendly. All data returned as `json_t *` or `char *` must be dynamically allocated, because they will be cleaned up by Glewlwyd after use. A user middleware module is intended to improve and extend a user object by updating its attributes (add, update or remove attributes), for example, adding groups and access-levels to an existing user when these metadata are not stored in the LDAP backend but in a different location. Currently, the following user middleware backend modules are available: A user middleware backend module is used to manage users in a specific backend environment. It is intended to: - update a list user object returned by the user backend - update a specific user object returned by the user backend - update a user object returned by the API to be stored by the user backend A user is defined by attributes. The following attributes are mandatory for every user: ```javascript { "username": string, identifies the user, must be unique "scope": array of string, list of scopes available to the user "enabled": boolean, set this value to false will make the user unable to authenticate or do anything in Glewlwyd } ``` A user middleware module is allowed to change any attribute of the user, except for the `username` attribute, it is allowed to add or remove attributes too, but be careful to respect the mandatory format below after the middleware process. See user modules for more information. A Glewlwyd module requires the library [Jansson](https://github.com/akheron/Jansson). You can check out the existing modules for inspiration. You can start from the fake module [mock.c](mock.c) to build your own. ```C struct config_module { /* External url to access to the Glewlwyd instance */ const char * external_url; /* relative url to access to the login page */ const char * login_url; /* value of the admin scope */ const char * admin_scope; /* Value of the profile scope */ const char * profile_scope; /* connection to the database via hoel library */ struct _h_connection * conn; /* Digest agorithm defined in the configuration file */ digest_algorithm hash_algorithm; /* General configuration of the Glewlwyd instance */ struct config_elements * glewlwyd_config; /* Callback function to retrieve a specific user */ json_t * (* glewlwyd_module_callback_get_user)(struct config_module * config, const char * username); /* Callback function to update a specific user */ int (* glewlwyd_module_callback_set_user)(struct config_module * config, const char * username, json_t * j_user); /* Callback function to validate a user password */ int (* glewlwyd_module_callback_check_user_password)(struct config_module * config, const char * username, const char * password); /* Callback function to validate a session */ json_t * (* glewlwyd_module_callback_check_user_session)(struct config_module * config, const struct _u_request * request, const char * username); }; ``` A user middleware module must have the following functions defined and available: ```C /** * * user_middleware_module_load * * Executed once when Glewlwyd service is started * Used to identify the module and to show its parameters on init * You can also use it to load resources that are required once for all * instance modules for example * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * name: string, mandatory, name of the module, must be unique among other scheme modules * display_name: string, optional, long name of the module * description: string, optional, description for the module * parameters: object, optional, parameters description for the module * } * * Example: * { * result: G_OK, * name: "mock", * display_name: "Mock scheme module", * description: "Mock scheme module for glewlwyd tests", * parameters: { * mock-value: { * type: "string", * mandatory: true * } * } * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ json_t * user_middleware_module_load(struct config_module * config); ``` ```C /** * * user_middleware_module_unload * * Executed once when Glewlwyd service is stopped * You can use it to release resources that are required once for all * instance modules for example * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ int user_middleware_module_unload(struct config_module * config); ``` ```C /** * * user_middleware_module_init * * Initialize an instance of this module declared in Glewlwyd service. * If required, you must dynamically allocate a pointer to the configuration * for this instance and pass it to *cls * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, G_ERROR_PARAM on input parameters error, another value on error) * error: array of strings containg the list of input errors, mandatory on result G_ERROR_PARAM, ignored otherwise * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_parameters: used to initialize an instance in JSON format * The module must validate itself its parameters * @parameter cls: will contain an allocated void * pointer that will be sent back * as void * in all module functions * */ json_t * user_middleware_module_init(struct config_module * config, json_t * j_parameters, void ** cls); ``` ```C /** * * user_middleware_module_close * * Close an instance of this module declared in Glewlwyd service. * You must free the memory previously allocated in * the user_module_init function as void * cls * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int user_middleware_module_close(struct config_module * config, void * cls); ``` ```C /** * * user_middleware_module_get_list * * Update a list of users returned by user_get_list * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_user_list: The list of users returned by the user backend modules * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int user_middleware_module_get_list(struct config_module * config, json_t * j_user_list, void * cls); ``` ```C /** * * user_middleware_module_get * * Update a user returned by user_get * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: the username corresponding to the j_user object * @parameter j_user: The user returned by the user backend module * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int user_middleware_module_get(struct config_module * config, const char * username, json_t * j_user, void * cls); ``` ```C /** * * user_middleware_module_get_profile * * Update a user returned by user_get_profile * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: the username corresponding to the j_user object * @parameter j_user: The user returned by the user backend module * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int user_middleware_module_get_profile(struct config_module * config, const char * username, json_t * j_user, void * cls); ``` ```C /** * * user_middleware_module_update * * Update a user before being passed to the backend functions * user_module_add, user_module_update, user_module_update_profile * or user_module_is_valid * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: the username corresponding to the j_user object * @parameter j_user: The user returned by the API * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int user_middleware_module_update(struct config_module * config, const char * username, json_t * j_user, void * cls); ``` ```C /** * * user_middleware_module_delete * * Update a user before being passed to the backend function user_module_delete * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: the username corresponding to the j_user object * @parameter j_user: The user returned by the API * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int user_middleware_module_delete(struct config_module * config, const char * username, json_t * j_user, void * cls); ``` glewlwyd-2.6.1/src/user_middleware/mock.c000066400000000000000000000311011415646314000204150ustar00rootroot00000000000000/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * Mock user middleware module * * Copyright 2021 Nicolas Mora * * The MIT License (MIT) * * 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. * */ #include #include #include #include #include "glewlwyd-common.h" /** * Note on the user middleware module * * A middleware module is intended to improve and extend a user object * The first use case was the integration with casbin (https://github.com/casbin/casbin-cpp) * * The user middleware modules are "placed" between the user backend and the outside world. * Therefore, the user middleware modules are executed _after_ a get_user_list, get_user * or a get_user_profile from the backend, and _before_ a add_user, set_user, set_user_profile * or delete_user * * You can instaciate any number of user middleware modules, they will be excuted accordingly to * thei "order" parameter * * The beckend user modules will never know that a middleware module exist on top of them, so on a * add/set/delete user, the middleware user should "clean its passage" to avoid undefined behaviour * on the user backend * * What a middleware can do? * - Update the entire user object * * What a middleware must not do? * - Change the username * - Use other values types than string or array of strings, and boolean for the reserved property "enabled" * * Other than that, the possibilities are limitless and can be specialized for any use case. * Beware that those functions should be stateless, since the same function can potentially be * executed mutiple times for one process * * A typical example is an ACL middleware module that will add access level and group properties to the user object * * Definition of the struct config_module: * * struct config_module { * const char * external_url; // Absolute url of the glewlwyd service * const char * login_url; // Relative url of the login page * const char * admin_scope; // Value of the g_admin scope * const char * profile_scope; // Value of the g_profile scope * struct _h_connection * conn; // Hoel structure to access to the database * digest_algorithm hash_algorithm; // Hash algorithm used in Glewlwyd * struct config_elements * glewlwyd_config; // Pointer to the global config structure * // Function used to return a user object * json_t * (* glewlwyd_module_callback_get_user)(struct config_module * config, const char * username); * // Function used to update a user * int (* glewlwyd_module_callback_set_user)(struct config_module * config, const char * username, json_t * j_user); * // Function used to check the validity of a user's password * int (* glewlwyd_module_callback_check_user_password)(struct config_module * config, const char * username, const char * password); * }; * */ /** * * user_middleware_module_load * * Executed once when Glewlwyd service is started * Used to identify the module and to show its parameters on init * You can also use it to load resources that are required once for all * instance modules for example * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, another value on error) * name: string, mandatory, name of the module, must be unique among other scheme modules * display_name: string, optional, long name of the module * description: string, optional, description for the module * parameters: object, optional, parameters description for the module * } * * Example: * { * result: G_OK, * name: "mock", * display_name: "Mock scheme module", * description: "Mock scheme module for glewlwyd tests", * parameters: { * mock-value: { * type: "string", * mandatory: true * } * } * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ json_t * user_middleware_module_load(struct config_module * config) { UNUSED(config); return json_pack("{sisssssssf}", "result", G_OK, "name", "mock", "display_name", "Mock user middleware module", "description", "Mock user middleware module for glewlwyd tests", "api_version", 2.6); } /** * * user_middleware_module_unload * * Executed once when Glewlwyd service is stopped * You can use it to release resources that are required once for all * instance modules for example * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * */ int user_middleware_module_unload(struct config_module * config) { UNUSED(config); return G_OK; } /** * * user_middleware_module_init * * Initialize an instance of this module declared in Glewlwyd service. * If required, you must dynamically allocate a pointer to the configuration * for this instance and pass it to *cls * * @return value: a json_t * value with the following pattern: * { * result: number (G_OK on success, G_ERROR_PARAM on input parameters error, another value on error) * error: array of strings containg the list of input errors, mandatory on result G_ERROR_PARAM, ignored otherwise * } * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_parameters: used to initialize an instance in JSON format * The module must validate itself its parameters * @parameter cls: will contain an allocated void * pointer that will be sent back * as void * in all module functions * */ json_t * user_middleware_module_init(struct config_module * config, json_t * j_parameters, void ** cls) { UNUSED(config); if (json_object_get(j_parameters, "middleware") != NULL && !json_is_string(json_object_get(j_parameters, "middleware"))) { return json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "middleware must be a string"); } else { *cls = json_incref(j_parameters); y_log_message(Y_LOG_LEVEL_DEBUG, "user_middleware_module_init - middleware value: '%s'", json_string_value(json_object_get(j_parameters, "middleware"))); return json_pack("{si}", "result", G_OK); } } /** * * user_middleware_module_close * * Close an instance of this module declared in Glewlwyd service. * You must free the memory previously allocated in * the user_module_init function as void * cls * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int user_middleware_module_close(struct config_module * config, void * cls) { UNUSED(config); json_decref((json_t *)cls); return G_OK; } /** * * user_middleware_module_get_list * * Update a list of users returned by user_get_list * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter j_user_list: The list of users returned by the user backend modules * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int user_middleware_module_get_list(struct config_module * config, json_t * j_user_list, void * cls) { UNUSED(config); json_t * j_element = NULL; size_t index = 0; json_array_foreach(j_user_list, index, j_element) { json_object_set_new(j_element, "middleware", json_pack("s++", json_string_value(json_object_get((json_t *)cls, "middleware")), "-", json_string_value(json_object_get(j_element, "username")))); } return G_OK; } /** * * user_middleware_module_get * * Update a user returned by user_get * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: the username corresponding to the j_user object * @parameter j_user: The user returned by the user backend module * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int user_middleware_module_get(struct config_module * config, const char * username, json_t * j_user, void * cls) { UNUSED(config); UNUSED(username); json_object_set_new(j_user, "middleware", json_pack("s++", json_string_value(json_object_get((json_t *)cls, "middleware")), "-", username)); return G_OK; } /** * * user_middleware_module_get_profile * * Update a user returned by user_get_profile * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: the username corresponding to the j_user object * @parameter j_user: The user returned by the user backend module * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int user_middleware_module_get_profile(struct config_module * config, const char * username, json_t * j_user, void * cls) { UNUSED(config); UNUSED(username); json_object_set_new(j_user, "middleware", json_pack("s+++", json_string_value(json_object_get((json_t *)cls, "middleware")), "-", username, "-profile")); return G_OK; } /** * * user_middleware_module_update * * Update a user before being passed to the backend functions * user_module_add, user_module_update, user_module_update_profile * or user_module_is_valid * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: the username corresponding to the j_user object * @parameter j_user: The user returned by the API * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int user_middleware_module_update(struct config_module * config, const char * username, json_t * j_user, void * cls) { UNUSED(config); UNUSED(username); UNUSED(cls); json_object_del(j_user, "middleware"); return G_OK; } /** * * user_middleware_module_delete * * Update a user before being passed to the backend function user_module_delete * * @return value: G_OK on success, another value on error * * @parameter config: a struct config_module with acess to some Glewlwyd * service and data * @parameter username: the username corresponding to the j_user object * @parameter j_user: The user returned by the API * @parameter cls: pointer to the void * cls value allocated in user_module_init * */ int user_middleware_module_delete(struct config_module * config, const char * username, json_t * j_user, void * cls) { UNUSED(config); UNUSED(username); UNUSED(cls); json_object_del(j_user, "middleware"); return G_OK; } glewlwyd-2.6.1/src/webservice.c000066400000000000000000004053151415646314000164630ustar00rootroot00000000000000/** * * Glewlwyd SSO Server * * Authentiation server * Users are authenticated via various backend available: database, ldap * Using various authentication methods available: password, OTP, send code, etc. * * Callback functions definition * * Copyright 2016-2021 Nicolas Mora * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; * version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see . * */ #include #include "glewlwyd.h" int callback_glewlwyd_options (const struct _u_request * request, struct _u_response * response, void * user_data) { UNUSED(request); UNUSED(user_data); ulfius_add_header_to_response(response, "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); ulfius_add_header_to_response(response, "Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Bearer, Authorization"); ulfius_add_header_to_response(response, "Access-Control-Max-Age", "1800"); return U_CALLBACK_COMPLETE; } int callback_glewlwyd_server_configuration (const struct _u_request * request, struct _u_response * response, void * user_data) { UNUSED(request); json_t * json_body = json_pack("{ssssssss}", "api_prefix", ((struct config_elements *)user_data)->api_prefix, "admin_scope", ((struct config_elements *)user_data)->admin_scope, "profile_scope", ((struct config_elements *)user_data)->profile_scope, "delete_profile", ((struct config_elements *)user_data)->delete_profile==GLEWLWYD_PROFILE_DELETE_UNAUTHORIZED?"no":"yes"); ulfius_set_json_body_response(response, 200, json_body); json_decref(json_body); return U_CALLBACK_CONTINUE; } int callback_default (const struct _u_request * request, struct _u_response * response, void * user_data) { UNUSED(request); UNUSED(user_data); json_t * json_body = json_pack("{ssss}", "error", "resource not found", "message", "no resource available at this address"); ulfius_set_json_body_response(response, 404, json_body); json_decref(json_body); return U_CALLBACK_CONTINUE; } int callback_404_if_necessary (const struct _u_request * request, struct _u_response * response, void * user_data) { UNUSED(user_data); if (!request->callback_position) { response->status = 404; } return U_CALLBACK_COMPLETE; } int callback_glewlwyd_check_user_profile_valid (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; char * session_uid; json_t * j_user; int ret, res; if ((session_uid = get_session_id(config, request)) != NULL) { j_user = get_current_user_for_session(config, session_uid); if (check_result_value(j_user, G_OK) && json_object_get(json_object_get(j_user, "user"), "enabled") == json_true()) { if ((res = is_scope_list_valid_for_session(config, config->profile_scope, session_uid)) == G_OK) { if (ulfius_set_response_shared_data(response, json_deep_copy(json_object_get(j_user, "user")), (void (*)(void *))&json_decref) != U_OK) { ret = U_CALLBACK_ERROR; } else { ret = U_CALLBACK_IGNORE; } } else { if (res == G_ERROR) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_check_user_session - Error is_scope_list_valid_for_session"); } ret = U_CALLBACK_UNAUTHORIZED; } } else { ret = U_CALLBACK_UNAUTHORIZED; } json_decref(j_user); } else { ret = U_CALLBACK_UNAUTHORIZED; } o_free(session_uid); return ret; } int callback_glewlwyd_check_user_session (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; char * session_uid; json_t * j_user; int ret; if ((session_uid = get_session_id(config, request)) != NULL) { j_user = get_current_user_for_session(config, session_uid); if (check_result_value(j_user, G_OK) && json_object_get(json_object_get(j_user, "user"), "enabled") == json_true()) { if (ulfius_set_response_shared_data(response, json_deep_copy(json_object_get(j_user, "user")), (void (*)(void *))&json_decref) != U_OK) { ret = U_CALLBACK_ERROR; } else { ret = U_CALLBACK_IGNORE; } } else { ret = U_CALLBACK_UNAUTHORIZED; } json_decref(j_user); } else { ret = U_CALLBACK_UNAUTHORIZED; } o_free(session_uid); return ret; } int callback_glewlwyd_check_admin_session (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; char * session_uid; json_t * j_user; int ret, res; if ((session_uid = get_session_id(config, request)) != NULL) { j_user = get_current_user_for_session(config, session_uid); if (check_result_value(j_user, G_OK) && json_object_get(json_object_get(j_user, "user"), "enabled") == json_true()) { if ((res = is_scope_list_valid_for_session(config, config->admin_scope, session_uid)) == G_OK) { if (ulfius_set_response_shared_data(response, json_deep_copy(json_object_get(j_user, "user")), (void (*)(void *))&json_decref) != U_OK) { ret = U_CALLBACK_ERROR; } else { ret = U_CALLBACK_IGNORE; } } else { if (res == G_ERROR) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_check_admin_session - Error is_scope_list_valid_for_session"); } ret = U_CALLBACK_UNAUTHORIZED; } } else { ret = U_CALLBACK_UNAUTHORIZED; } json_decref(j_user); } else { ret = U_CALLBACK_UNAUTHORIZED; } o_free(session_uid); return ret; } int callback_glewlwyd_check_admin_session_or_api_key (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; char * session_uid = NULL; json_t * j_user; int ret, res; const char * api_key = u_map_get_case(request->map_header, GLEWLWYD_API_KEY_HEADER_KEY), * ip_source = get_ip_source(request); if (NULL != api_key && 0 == o_strncmp(GLEWLWYD_API_KEY_HEADER_PREFIX, api_key, o_strlen(GLEWLWYD_API_KEY_HEADER_PREFIX))) { if ((res = verify_api_key(config, api_key + o_strlen(GLEWLWYD_API_KEY_HEADER_PREFIX))) == G_OK) { if (ulfius_set_response_shared_data(response, json_pack("{so}", "username", json_null()), (void (*)(void *))&json_decref) != U_OK) { ret = U_CALLBACK_ERROR; } else { ret = U_CALLBACK_IGNORE; } } else if (res == G_ERROR_UNAUTHORIZED) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - API key invalid at IP Address %s", ip_source); ret = U_CALLBACK_UNAUTHORIZED; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_check_admin_session_or_api_key - Error verify_api_key"); ret = U_CALLBACK_ERROR; } } else if ((session_uid = get_session_id(config, request)) != NULL) { j_user = get_current_user_for_session(config, session_uid); if (check_result_value(j_user, G_OK) && json_object_get(json_object_get(j_user, "user"), "enabled") == json_true()) { if ((res = is_scope_list_valid_for_session(config, config->admin_scope, session_uid)) == G_OK) { if (ulfius_set_response_shared_data(response, json_deep_copy(json_object_get(j_user, "user")), (void (*)(void *))&json_decref) != U_OK) { ret = U_CALLBACK_ERROR; } else { ret = U_CALLBACK_IGNORE; } } else { if (res == G_ERROR) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_check_admin_session_or_api_key - Error is_scope_list_valid_for_session"); } ret = U_CALLBACK_UNAUTHORIZED; } } else { ret = U_CALLBACK_UNAUTHORIZED; } json_decref(j_user); o_free(session_uid); } else { ret = U_CALLBACK_UNAUTHORIZED; } return ret; } int callback_glewlwyd_check_admin_session_delegate (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; char * session_uid; json_t * j_user, * j_delegate; int ret; if ((session_uid = get_session_id(config, request)) != NULL) { j_user = get_current_user_for_session(config, session_uid); if (check_result_value(j_user, G_OK) && json_object_get(json_object_get(j_user, "user"), "enabled") == json_true()) { if (is_scope_list_valid_for_session(config, config->admin_scope, session_uid) == G_OK) { j_delegate = get_user(config, u_map_get(request->map_url, "username"), NULL); if (check_result_value(j_delegate, G_OK)) { if (ulfius_set_response_shared_data(response, json_deep_copy(json_object_get(j_delegate, "user")), (void (*)(void *))&json_decref) != U_OK) { ret = U_CALLBACK_ERROR; } else { ret = U_CALLBACK_IGNORE; } } else { ret = U_CALLBACK_UNAUTHORIZED; } json_decref(j_delegate); } else { ret = U_CALLBACK_UNAUTHORIZED; } } else { ret = U_CALLBACK_UNAUTHORIZED; } json_decref(j_user); } else { ret = U_CALLBACK_UNAUTHORIZED; } o_free(session_uid); return ret; } int callback_glewlwyd_user_auth (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_param = ulfius_get_json_body_request(request, NULL), * j_result = NULL; const char * ip_source = get_ip_source(request); char * issued_for = get_client_hostname(request); char * session_uid, expires[129]; time_t now; struct tm ts; time(&now); now += GLEWLWYD_DEFAULT_SESSION_EXPIRATION_COOKIE; gmtime_r(&now, &ts); strftime(expires, 128, "%a, %d %b %Y %T %Z", &ts); if (j_param != NULL) { if (json_string_length(json_object_get(j_param, "username"))) { if (json_object_get(j_param, "scheme_type") == NULL || 0 == o_strcmp(json_string_value(json_object_get(j_param, "scheme_type")), "password")) { if (json_string_length(json_object_get(j_param, "password"))) { j_result = auth_check_user_credentials(config, json_string_value(json_object_get(j_param, "username")), json_string_value(json_object_get(j_param, "password"))); if (check_result_value(j_result, G_OK)) { if ((session_uid = get_session_id(config, request)) == NULL) { session_uid = generate_session_id(); } if (user_session_update(config, session_uid, u_map_get_case(request->map_header, "user-agent"), issued_for, json_string_value(json_object_get(j_param, "username")), NULL, 1) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_auth - Error user_session_update (1)"); response->status = 500; } else { ulfius_add_cookie_to_response(response, config->session_key, session_uid, expires, 0, config->cookie_domain, "/", config->cookie_secure, 0); y_log_message(Y_LOG_LEVEL_INFO, "Event - User '%s' authenticated with password", json_string_value(json_object_get(j_param, "username"))); } o_free(session_uid); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_AUTH_USER_VALID, 1, NULL); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_AUTH_USER_VALID_SCHEME, 1, "scheme_type", "password", NULL); } else { if (check_result_value(j_result, G_ERROR_UNAUTHORIZED)) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for username %s at IP Address %s", json_string_value(json_object_get(j_param, "username")), ip_source); } response->status = 401; glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_AUTH_USER_INVALID, 1, NULL); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_AUTH_USER_INVALID_SCHEME, 1, "scheme_type", "password", NULL); } json_decref(j_result); } else if (json_object_get(j_param, "password") != NULL && !json_is_string(json_object_get(j_param, "password"))) { ulfius_set_string_body_response(response, 400, "password must be a string"); } else { session_uid = get_session_id(config, request); j_result = get_users_for_session(config, session_uid); if (check_result_value(j_result, G_OK)) { // Refresh username to set as default if (user_session_update(config, u_map_get(request->map_cookie, config->session_key), u_map_get_case(request->map_header, "user-agent"), issued_for, json_string_value(json_object_get(j_param, "username")), NULL, 0) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_auth - Error user_session_update (2)"); response->status = 500; } else { ulfius_add_cookie_to_response(response, config->session_key, session_uid, expires, 0, config->cookie_domain, "/", config->cookie_secure, 0); } } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { response->status = 401; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_auth - Error get_users_for_session"); response->status = 500; } o_free(session_uid); json_decref(j_result); } } else { if (json_string_length(json_object_get(j_param, "scheme_type")) && json_string_length(json_object_get(j_param, "scheme_name")) && json_is_object(json_object_get(j_param, "value"))) { j_result = auth_check_user_scheme(config, json_string_value(json_object_get(j_param, "scheme_type")), json_string_value(json_object_get(j_param, "scheme_name")), json_string_value(json_object_get(j_param, "username")), json_object_get(j_param, "value"), request); if (check_result_value(j_result, G_ERROR_PARAM)) { ulfius_set_string_body_response(response, 400, "bad scheme response"); } else if (check_result_value(j_result, G_ERROR_UNAUTHORIZED)) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for username %s at IP Address %s", json_string_value(json_object_get(j_param, "username")), ip_source); response->status = 401; glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_AUTH_USER_INVALID, 1, NULL); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_AUTH_USER_INVALID_SCHEME, 1, "scheme_type", json_string_value(json_object_get(j_param, "scheme_type")), "scheme_name", json_string_value(json_object_get(j_param, "scheme_name")), NULL); } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { response->status = 404; } else if (check_result_value(j_result, G_OK)) { if ((session_uid = get_session_id(config, request)) == NULL) { session_uid = generate_session_id(); } if (user_session_update(config, session_uid, u_map_get_case(request->map_header, "user-agent"), issued_for, json_string_value(json_object_get(j_param, "username")), json_string_value(json_object_get(j_param, "scheme_name")), 1) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_auth - Error user_session_update (3)"); response->status = 500; } else { ulfius_add_cookie_to_response(response, config->session_key, session_uid, expires, 0, config->cookie_domain, "/", config->cookie_secure, 0); y_log_message(Y_LOG_LEVEL_INFO, "Event - User '%s' authenticated with scheme '%s/%s'", json_string_value(json_object_get(j_param, "username")), json_string_value(json_object_get(j_param, "scheme_type")), json_string_value(json_object_get(j_param, "scheme_name"))); } o_free(session_uid); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_AUTH_USER_VALID, 1, NULL); glewlwyd_metrics_increment_counter_va(config, GLWD_METRICS_AUTH_USER_VALID_SCHEME, 1, "scheme_type", json_string_value(json_object_get(j_param, "scheme_type")), "scheme_name", json_string_value(json_object_get(j_param, "scheme_name")), NULL); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_auth - Error auth_check_user_scheme"); response->status = 500; } json_decref(j_result); } else { ulfius_set_string_body_response(response, 400, "scheme_type, scheme_name and value are mandatory"); } } } else { if (json_string_length(json_object_get(j_param, "scheme_type")) && json_string_length(json_object_get(j_param, "scheme_name")) && json_is_object(json_object_get(j_param, "value"))) { j_result = auth_check_identify_scheme(config, json_string_value(json_object_get(j_param, "scheme_type")), json_string_value(json_object_get(j_param, "scheme_name")), json_object_get(j_param, "value"), request); if (check_result_value(j_result, G_ERROR_PARAM)) { ulfius_set_string_body_response(response, 400, "bad scheme response"); } else if (check_result_value(j_result, G_ERROR_UNAUTHORIZED)) { y_log_message(Y_LOG_LEVEL_WARNING, "Security - Authorization invalid for username at IP Address %s", ip_source); response->status = 401; } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { response->status = 404; } else if (check_result_value(j_result, G_OK)) { if ((session_uid = get_session_id(config, request)) == NULL) { session_uid = generate_session_id(); } if (user_session_update(config, session_uid, u_map_get_case(request->map_header, "user-agent"), issued_for, json_string_value(json_object_get(j_result, "username")), json_string_value(json_object_get(j_param, "scheme_name")), 1) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_auth - Error user_session_update (4)"); response->status = 500; } else { ulfius_add_cookie_to_response(response, config->session_key, session_uid, expires, 0, config->cookie_domain, "/", config->cookie_secure, 0); y_log_message(Y_LOG_LEVEL_INFO, "Event - User '%s' authenticated with scheme '%s/%s'", json_string_value(json_object_get(j_result, "username")), json_string_value(json_object_get(j_param, "scheme_type")), json_string_value(json_object_get(j_param, "scheme_name"))); } o_free(session_uid); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_auth - Error auth_check_user_scheme"); response->status = 500; } json_decref(j_result); } else { ulfius_set_string_body_response(response, 400, "username is mandatory"); } } } else { ulfius_set_string_body_response(response, 400, "Input parameters must be in JSON format"); } json_decref(j_param); o_free(issued_for); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_user_auth_trigger (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_param = ulfius_get_json_body_request(request, NULL), * j_result = NULL; if (j_param != NULL) { if (json_string_length(json_object_get(j_param, "scheme_type")) && json_string_length(json_object_get(j_param, "scheme_name"))) { if (json_string_length(json_object_get(j_param, "username"))) { j_result = auth_trigger_user_scheme(config, json_string_value(json_object_get(j_param, "scheme_type")), json_string_value(json_object_get(j_param, "scheme_name")), json_string_value(json_object_get(j_param, "username")), json_object_get(j_param, "value"), request); if (check_result_value(j_result, G_ERROR_PARAM)) { ulfius_set_string_body_response(response, 400, "bad scheme response"); } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { response->status = 404; } else if (check_result_value(j_result, G_ERROR_UNAUTHORIZED)) { response->status = 401; } else if (check_result_value(j_result, G_OK)) { if (json_object_get(j_result, "trigger") != NULL) { ulfius_set_json_body_response(response, 200, json_object_get(j_result, "trigger")); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_auth_trigger - Error auth_trigger_user_scheme"); response->status = 500; } json_decref(j_result); } else { j_result = auth_trigger_identify_scheme(config, json_string_value(json_object_get(j_param, "scheme_type")), json_string_value(json_object_get(j_param, "scheme_name")), json_object_get(j_param, "value"), request); if (check_result_value(j_result, G_ERROR_PARAM)) { ulfius_set_string_body_response(response, 400, "bad scheme response"); } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { response->status = 404; } else if (check_result_value(j_result, G_ERROR_UNAUTHORIZED)) { response->status = 401; } else if (check_result_value(j_result, G_OK)) { if (json_object_get(j_result, "trigger") != NULL) { ulfius_set_json_body_response(response, 200, json_object_get(j_result, "trigger")); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_auth_trigger - Error auth_trigger_identify_scheme"); response->status = 500; } json_decref(j_result); } } else { ulfius_set_string_body_response(response, 400, "scheme is mandatory"); } } else { ulfius_set_string_body_response(response, 400, "Input parameters must be in JSON format"); } json_decref(j_param); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_user_auth_register (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_param = ulfius_get_json_body_request(request, NULL), * j_result = NULL; if (j_param != NULL) { if (json_object_get(j_param, "username") != NULL && json_is_string(json_object_get(j_param, "username")) && json_string_length(json_object_get(j_param, "username"))) { if (0 == o_strcasecmp(json_string_value(json_object_get((json_t *)response->shared_data, "username")), json_string_value(json_object_get(j_param, "username")))) { if (json_object_get(j_param, "scheme_type") != NULL && json_is_string(json_object_get(j_param, "scheme_type")) && json_string_length(json_object_get(j_param, "scheme_type")) && json_object_get(j_param, "scheme_name") != NULL && json_is_string(json_object_get(j_param, "scheme_name")) && json_string_length(json_object_get(j_param, "scheme_name"))) { j_result = auth_register_user_scheme(config, json_string_value(json_object_get(j_param, "scheme_type")), json_string_value(json_object_get(j_param, "scheme_name")), json_string_value(json_object_get(j_param, "username")), 0, json_object_get(j_param, "value"), request); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "register") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "register")); } else { ulfius_set_string_body_response(response, 400, "bad scheme response"); } } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { response->status = 404; } else if (check_result_value(j_result, G_ERROR_UNAUTHORIZED)) { response->status = 401; } else if (check_result_value(j_result, G_OK)) { if (json_object_get(j_result, "register") != NULL) { ulfius_set_json_body_response(response, 200, json_object_get(j_result, "register")); } y_log_message(Y_LOG_LEVEL_INFO, "Event - User '%s' registered scheme '%s/%s'", json_string_value(json_object_get(j_param, "username")), json_string_value(json_object_get(j_param, "scheme_type")), json_string_value(json_object_get(j_param, "scheme_name"))); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_auth_register - Error auth_check_user_scheme"); response->status = 500; } json_decref(j_result); } else { ulfius_set_string_body_response(response, 400, "scheme is mandatory"); } } else { ulfius_set_string_body_response(response, 400, "username invalid"); } } else { ulfius_set_string_body_response(response, 400, "username is mandatory"); } } else { ulfius_set_string_body_response(response, 400, "Input parameters must be in JSON format"); } json_decref(j_param); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_user_auth_register_get (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_param = ulfius_get_json_body_request(request, NULL), * j_result = NULL; if (j_param != NULL) { if (json_object_get(j_param, "username") != NULL && json_string_length(json_object_get(j_param, "username"))) { if (0 == o_strcasecmp(json_string_value(json_object_get((json_t *)response->shared_data, "username")), json_string_value(json_object_get(j_param, "username")))) { if (json_object_get(j_param, "scheme_type") != NULL && json_string_length(json_object_get(j_param, "scheme_type")) && json_object_get(j_param, "scheme_name") != NULL && json_string_length(json_object_get(j_param, "scheme_name"))) { j_result = auth_register_get_user_scheme(config, json_string_value(json_object_get(j_param, "scheme_type")), json_string_value(json_object_get(j_param, "scheme_name")), json_string_value(json_object_get(j_param, "username")), request); if (check_result_value(j_result, G_ERROR_PARAM)) { ulfius_set_string_body_response(response, 400, "bad scheme response"); } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { response->status = 404; } else if (check_result_value(j_result, G_ERROR_UNAUTHORIZED)) { response->status = 401; } else if (check_result_value(j_result, G_OK)) { if (json_object_get(j_result, "register") != NULL) { ulfius_set_json_body_response(response, 200, json_object_get(j_result, "register")); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_auth_register_get - Error auth_register_get_user_scheme"); response->status = 500; } json_decref(j_result); } else { ulfius_set_string_body_response(response, 400, "scheme is mandatory"); } } else { ulfius_set_string_body_response(response, 400, "username invalid"); } } else { ulfius_set_string_body_response(response, 400, "username is mandatory"); } } else { ulfius_set_string_body_response(response, 400, "Input parameters must be in JSON format"); } json_decref(j_param); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_scheme_check_forbid_profile (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_param = ulfius_get_json_body_request(request, NULL), * j_scheme = get_user_auth_scheme_module(config, json_string_value(json_object_get(j_param, "scheme_name"))); int ret = U_CALLBACK_CONTINUE; if (check_result_value(j_scheme, G_OK)) { if (json_object_get(json_object_get(j_scheme, "module"), "forbid_user_profile") == json_true()) { response->status = 403; ret = U_CALLBACK_COMPLETE; } } else if (check_result_value(j_scheme, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_scheme_check_forbid_profile - Error auth_register_get_user_scheme"); response->status = 500; } json_decref(j_param); json_decref(j_scheme); return ret; } int callback_glewlwyd_user_auth_register_delegate (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_param = ulfius_get_json_body_request(request, NULL), * j_result = NULL; if (j_param != NULL) { if (json_object_get(j_param, "username") != NULL && json_is_string(json_object_get(j_param, "username")) && json_string_length(json_object_get(j_param, "username"))) { if (0 == o_strcasecmp(json_string_value(json_object_get((json_t *)response->shared_data, "username")), json_string_value(json_object_get(j_param, "username")))) { if (json_object_get(j_param, "scheme_type") != NULL && json_is_string(json_object_get(j_param, "scheme_type")) && json_string_length(json_object_get(j_param, "scheme_type")) && json_object_get(j_param, "scheme_name") != NULL && json_is_string(json_object_get(j_param, "scheme_name")) && json_string_length(json_object_get(j_param, "scheme_name"))) { j_result = auth_register_user_scheme(config, json_string_value(json_object_get(j_param, "scheme_type")), json_string_value(json_object_get(j_param, "scheme_name")), json_string_value(json_object_get(j_param, "username")), 1, json_object_get(j_param, "value"), request); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "register") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "register")); } else { ulfius_set_string_body_response(response, 400, "bad scheme response"); } } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { response->status = 404; } else if (check_result_value(j_result, G_ERROR_UNAUTHORIZED)) { response->status = 401; } else if (check_result_value(j_result, G_OK)) { if (json_object_get(j_result, "register") != NULL) { ulfius_set_json_body_response(response, 200, json_object_get(j_result, "register")); } y_log_message(Y_LOG_LEVEL_INFO, "Event - User '%s' registered scheme '%s/%s' (delegation)", json_string_value(json_object_get(j_param, "username")), json_string_value(json_object_get(j_param, "scheme_type")), json_string_value(json_object_get(j_param, "scheme_name"))); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_auth_register_delegate - Error auth_check_user_scheme"); response->status = 500; } json_decref(j_result); } else { ulfius_set_string_body_response(response, 400, "scheme is mandatory"); } } else { ulfius_set_string_body_response(response, 400, "username invalid"); } } else { ulfius_set_string_body_response(response, 400, "username is mandatory"); } } else { ulfius_set_string_body_response(response, 400, "Input parameters must be in JSON format"); } json_decref(j_param); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_user_auth_register_get_delegate (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_param = ulfius_get_json_body_request(request, NULL), * j_result = NULL; if (j_param != NULL) { if (json_object_get(j_param, "username") != NULL && json_string_length(json_object_get(j_param, "username"))) { if (0 == o_strcasecmp(json_string_value(json_object_get((json_t *)response->shared_data, "username")), json_string_value(json_object_get(j_param, "username")))) { if (json_object_get(j_param, "scheme_type") != NULL && json_string_length(json_object_get(j_param, "scheme_type")) && json_object_get(j_param, "scheme_name") != NULL && json_string_length(json_object_get(j_param, "scheme_name"))) { j_result = auth_register_get_user_scheme(config, json_string_value(json_object_get(j_param, "scheme_type")), json_string_value(json_object_get(j_param, "scheme_name")), json_string_value(json_object_get(j_param, "username")), request); if (check_result_value(j_result, G_ERROR_PARAM)) { ulfius_set_string_body_response(response, 400, "bad scheme response"); } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { response->status = 404; } else if (check_result_value(j_result, G_ERROR_UNAUTHORIZED)) { response->status = 401; } else if (check_result_value(j_result, G_OK)) { if (json_object_get(j_result, "register") != NULL) { ulfius_set_json_body_response(response, 200, json_object_get(j_result, "register")); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_auth_register_get_delegate - Error auth_register_get_user_scheme"); response->status = 500; } json_decref(j_result); } else { ulfius_set_string_body_response(response, 400, "scheme is mandatory"); } } else { ulfius_set_string_body_response(response, 400, "username invalid"); } } else { ulfius_set_string_body_response(response, 400, "username is mandatory"); } } else { ulfius_set_string_body_response(response, 400, "Input parameters must be in JSON format"); } json_decref(j_param); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_user_delete_session (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_session, * j_cur_session; char * session_uid = get_session_id(config, request), expires[129]; size_t index; time_t now; struct tm ts; time(&now); now += GLEWLWYD_DEFAULT_SESSION_EXPIRATION_COOKIE; gmtime_r(&now, &ts); strftime(expires, 128, "%a, %d %b %Y %T %Z", &ts); if (session_uid != NULL && o_strlen(session_uid)) { j_session = get_users_for_session(config, session_uid); if (check_result_value(j_session, G_ERROR_NOT_FOUND)) { response->status = 404; } else if (!check_result_value(j_session, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_delete_session - Error get_current_user_for_session"); response->status = 500; } else { if (u_map_get(request->map_url, "username") != NULL) { json_array_foreach(json_object_get(j_session, "session"), index, j_cur_session) { if (0 == o_strcasecmp(u_map_get(request->map_url, "username"), json_string_value(json_object_get(j_cur_session, "username")))) { if (user_session_delete(config, session_uid, u_map_get(request->map_url, "username")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_delete_session - Error user_session_delete"); response->status = 500; } } } if (json_array_size(json_object_get(j_session, "session")) == 1) { // Delete session cookie on the client browser ulfius_add_cookie_to_response(response, config->session_key, "", expires, 0, config->cookie_domain, "/", config->cookie_secure, 0); } else { ulfius_add_cookie_to_response(response, config->session_key, session_uid, expires, 0, config->cookie_domain, "/", config->cookie_secure, 0); } } else { if (user_session_delete(config, session_uid, NULL) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_delete_session - Error user_session_delete"); response->status = 500; } // Delete session cookie on the client browser ulfius_add_cookie_to_response(response, config->session_key, "", expires, 0, config->cookie_domain, "/", config->cookie_secure, 0); } } json_decref(j_session); } else { response->status = 401; } o_free(session_uid); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_user_get_schemes_from_scopes (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_result; char * session_uid = get_session_id(config, request); if (u_map_get(request->map_url, "scope") != NULL) { j_result = get_validated_auth_scheme_list_from_scope_list(config, u_map_get(request->map_url, "scope"), session_uid); if (check_result_value(j_result, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_result, "scheme")); } else if (check_result_value(j_result, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_get_schemes_from_scopes - Error get_validated_auth_scheme_list_from_scope_list"); response->status = 500; } json_decref(j_result); } else { response->status = 400; } o_free(session_uid); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_get_user_session_scope_grant (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_user = (json_t *)response->shared_data, * j_scope_list; if (config != NULL && j_user != NULL) { j_scope_list = get_granted_scopes_for_client(config, j_user, u_map_get(request->map_url, "client_id"), u_map_get(request->map_url, "scope_list")); if (check_result_value(j_scope_list, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_scope_list, "grant")); } else if (check_result_value(j_scope_list, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_user_session_scope_grant - Error get_granted_scopes_for_client"); response->status = 500; } json_decref(j_scope_list); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_user_session_scope_grant - Error config or j_user is NULL"); response->status = 500; } return U_CALLBACK_CONTINUE; } int callback_glewlwyd_set_user_session_scope_grant (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_user = (json_t *)response->shared_data, * j_body = ulfius_get_json_body_request(request, NULL), * j_client; int res; if (config != NULL && j_user != NULL) { if (json_object_get(j_body, "scope") != NULL && json_is_string(json_object_get(j_body, "scope"))) { j_client = get_client(config, u_map_get(request->map_url, "client_id"), NULL); if (check_result_value(j_client, G_OK) && json_object_get(json_object_get(j_client, "client"), "enabled") == json_true()) { res = set_granted_scopes_for_client(config, j_user, u_map_get(request->map_url, "client_id"), json_string_value(json_object_get(j_body, "scope"))); if (res == G_ERROR_NOT_FOUND) { response->status = 404; } else if (res == G_ERROR_PARAM) { response->status = 400; } else if (res == G_ERROR_UNAUTHORIZED) { response->status = 401; } else if (res != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_user_session_scope_grant - Error set_granted_scopes_for_client"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - User '%s' granted scope list '%s' for client '%s'", json_string_value(json_object_get(j_user, "username")), json_string_value(json_object_get(j_body, "scope")), u_map_get(request->map_url, "client_id")); } } else if (check_result_value(j_client, G_ERROR_NOT_FOUND) || json_object_get(json_object_get(j_client, "client"), "enabled") != json_true()) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_user_session_scope_grant - Error get_client"); response->status = 500; } json_decref(j_client); } else { response->status = 400; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_user_session_scope_grant - Error config or j_user is NULL"); response->status = 500; } json_decref(j_body); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_get_module_type_list (const struct _u_request * request, struct _u_response * response, void * user_data) { UNUSED(request); struct config_elements * config = (struct config_elements *)user_data; json_t * j_module_type; j_module_type = get_module_type_list(config); if (check_result_value(j_module_type, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_module_type, "module")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_module_type_list - Error get_module_type_list"); response->status = 500; } json_decref(j_module_type); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_reload_modules (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; UNUSED(request); close_user_module_instance_list(config); close_user_module_list(config); close_user_middleware_module_instance_list(config); close_user_middleware_module_list(config); close_client_module_instance_list(config); close_client_module_list(config); close_user_auth_scheme_module_instance_list(config); close_user_auth_scheme_module_list(config); close_plugin_module_instance_list(config); close_plugin_module_list(config); // Initialize user modules if (init_user_module_list(config) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "Error initializing user modules"); response->status = 500; } if (load_user_module_instance_list(config) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "Error loading user modules instances"); response->status = 500; } // Initialize user modules if (init_user_middleware_module_list(config) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "Error initializing user middleware modules"); response->status = 500; } if (load_user_middleware_module_instance_list(config) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "Error loading user middleware modules instances"); response->status = 500; } // Initialize client modules if (init_client_module_list(config) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "Error initializing client modules"); response->status = 500; } if (load_client_module_instance_list(config) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "Error loading client modules instances"); response->status = 500; } // Initialize user auth scheme modules if (init_user_auth_scheme_module_list(config) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "Error initializing user auth scheme modules"); response->status = 500; } if (load_user_auth_scheme_module_instance_list(config) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "Error loading user auth scheme modules instances"); response->status = 500; } // Initialize plugins if (init_plugin_module_list(config) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "Error initializing plugins modules"); response->status = 500; } if (load_plugin_module_instance_list(config) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "Error loading plugins modules instances"); response->status = 500; } return U_CALLBACK_CONTINUE; } int callback_glewlwyd_get_user_module_list (const struct _u_request * request, struct _u_response * response, void * user_data) { UNUSED(request); struct config_elements * config = (struct config_elements *)user_data; json_t * j_module; j_module = get_user_module_list(config); if (check_result_value(j_module, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_module, "module")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_user_module_list - Error get_user_module_list"); response->status = 500; } json_decref(j_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_get_user_module (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_module; j_module = get_user_module(config, u_map_get(request->map_url, "name")); if (check_result_value(j_module, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_module, "module")); } else if (check_result_value(j_module, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_user_module - Error get_user_module"); response->status = 500; } json_decref(j_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_add_user_module (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_module, * j_module_valid, * j_result; j_module = ulfius_get_json_body_request(request, NULL); if (j_module != NULL) { j_module_valid = is_user_module_valid(config, j_module, 1); if (check_result_value(j_module_valid, G_OK)) { j_result = add_user_module(config, j_module); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_add_user_module - Error add_user_module"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - User backend module '%s' added (%s)", json_string_value(json_object_get(j_module, "name")), json_string_value(json_object_get(j_module, "module"))); } json_decref(j_result); } else if (check_result_value(j_module_valid, G_ERROR_PARAM)) { if (json_object_get(j_module_valid, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_module_valid, "error")); } else { response->status = 400; } } else if (!check_result_value(j_module_valid, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_add_user_module - Error is_user_module_valid"); response->status = 500; } json_decref(j_module_valid); } else { response->status = 400; } json_decref(j_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_set_user_module (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_module, * j_module_valid, * j_search_module; j_search_module = get_user_module(config, u_map_get(request->map_url, "name")); if (check_result_value(j_search_module, G_OK)) { j_module = ulfius_get_json_body_request(request, NULL); if (j_module != NULL) { json_object_del(j_module, "enabled"); j_module_valid = is_user_module_valid(config, j_module, 0); if (check_result_value(j_module_valid, G_OK)) { if (set_user_module(config, u_map_get(request->map_url, "name"), j_module) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_user_module - Error set_user_module"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - User backend module '%s' updated", u_map_get(request->map_url, "name")); } } else if (check_result_value(j_module_valid, G_ERROR_PARAM)) { if (json_object_get(j_module_valid, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_module_valid, "error")); } else { response->status = 400; } } else if (!check_result_value(j_module_valid, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_user_module - Error is_user_module_valid"); response->status = 500; } json_decref(j_module_valid); } else { response->status = 400; } json_decref(j_module); } else if (check_result_value(j_search_module, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_user_module - Error get_user_module"); response->status = 500; } json_decref(j_search_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_delete_user_module (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_search_module; j_search_module = get_user_module(config, u_map_get(request->map_url, "name")); if (check_result_value(j_search_module, G_OK)) { if (delete_user_module(config, u_map_get(request->map_url, "name")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_delete_user_module - Error delete_user_module"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - User backend module '%s' removed", u_map_get(request->map_url, "name")); } } else if (check_result_value(j_search_module, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_delete_user_module - Error get_user_module"); response->status = 500; } json_decref(j_search_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_manage_user_module (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_search_module, * j_result, * j_result2; j_search_module = get_user_module(config, u_map_get(request->map_url, "name")); if (check_result_value(j_search_module, G_OK)) { if (0 == o_strcmp("enable", u_map_get(request->map_url, "action"))) { j_result = manage_user_module(config, u_map_get(request->map_url, "name"), GLEWLWYD_MODULE_ACTION_START); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_user_module - Error manage_user_module enable"); response->status = 500; } json_decref(j_result); } else if (0 == o_strcmp("disable", u_map_get(request->map_url, "action"))) { j_result = manage_user_module(config, u_map_get(request->map_url, "name"), GLEWLWYD_MODULE_ACTION_STOP); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_user_module - Error manage_user_module disable"); response->status = 500; } json_decref(j_result); } else if (0 == o_strcmp("reset", u_map_get(request->map_url, "action"))) { j_result = manage_user_module(config, u_map_get(request->map_url, "name"), GLEWLWYD_MODULE_ACTION_STOP); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_user_module - Error manage_user_module reset (1)"); response->status = 500; } else { j_result2 = manage_user_module(config, u_map_get(request->map_url, "name"), GLEWLWYD_MODULE_ACTION_START); if (check_result_value(j_result2, G_ERROR_PARAM)) { if (json_object_get(j_result2, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result2, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result2, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_user_module - Error manage_user_module reset (2)"); response->status = 500; } json_decref(j_result2); } json_decref(j_result); } else { response->status = 400; } } else if (check_result_value(j_search_module, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_user_module - Error get_user_module"); response->status = 500; } json_decref(j_search_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_get_user_middleware_module_list (const struct _u_request * request, struct _u_response * response, void * user_middleware_data) { UNUSED(request); struct config_elements * config = (struct config_elements *)user_middleware_data; json_t * j_module; j_module = get_user_middleware_module_list(config); if (check_result_value(j_module, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_module, "module")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_user_middleware_module_list - Error get_user_middleware_module_list"); response->status = 500; } json_decref(j_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_get_user_middleware_module (const struct _u_request * request, struct _u_response * response, void * user_middleware_data) { struct config_elements * config = (struct config_elements *)user_middleware_data; json_t * j_module; j_module = get_user_middleware_module(config, u_map_get(request->map_url, "name")); if (check_result_value(j_module, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_module, "module")); } else if (check_result_value(j_module, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_user_middleware_module - Error get_user_middleware_module"); response->status = 500; } json_decref(j_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_add_user_middleware_module (const struct _u_request * request, struct _u_response * response, void * user_middleware_data) { struct config_elements * config = (struct config_elements *)user_middleware_data; json_t * j_module, * j_module_valid, * j_result; j_module = ulfius_get_json_body_request(request, NULL); if (j_module != NULL) { j_module_valid = is_user_middleware_module_valid(config, j_module, 1); if (check_result_value(j_module_valid, G_OK)) { j_result = add_user_middleware_module(config, j_module); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_add_user_middleware_module - Error add_user_middleware_module"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - User backend module '%s' added (%s)", json_string_value(json_object_get(j_module, "name")), json_string_value(json_object_get(j_module, "module"))); } json_decref(j_result); } else if (check_result_value(j_module_valid, G_ERROR_PARAM)) { if (json_object_get(j_module_valid, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_module_valid, "error")); } else { response->status = 400; } } else if (!check_result_value(j_module_valid, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_add_user_middleware_module - Error is_user_middleware_module_valid"); response->status = 500; } json_decref(j_module_valid); } else { response->status = 400; } json_decref(j_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_set_user_middleware_module (const struct _u_request * request, struct _u_response * response, void * user_middleware_data) { struct config_elements * config = (struct config_elements *)user_middleware_data; json_t * j_module, * j_module_valid, * j_search_module; j_search_module = get_user_middleware_module(config, u_map_get(request->map_url, "name")); if (check_result_value(j_search_module, G_OK)) { j_module = ulfius_get_json_body_request(request, NULL); if (j_module != NULL) { json_object_del(j_module, "enabled"); j_module_valid = is_user_middleware_module_valid(config, j_module, 0); if (check_result_value(j_module_valid, G_OK)) { if (set_user_middleware_module(config, u_map_get(request->map_url, "name"), j_module) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_user_middleware_module - Error set_user_middleware_module"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - User backend module '%s' updated", u_map_get(request->map_url, "name")); } } else if (check_result_value(j_module_valid, G_ERROR_PARAM)) { if (json_object_get(j_module_valid, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_module_valid, "error")); } else { response->status = 400; } } else if (!check_result_value(j_module_valid, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_user_middleware_module - Error is_user_middleware_module_valid"); response->status = 500; } json_decref(j_module_valid); } else { response->status = 400; } json_decref(j_module); } else if (check_result_value(j_search_module, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_user_middleware_module - Error get_user_middleware_module"); response->status = 500; } json_decref(j_search_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_delete_user_middleware_module (const struct _u_request * request, struct _u_response * response, void * user_middleware_data) { struct config_elements * config = (struct config_elements *)user_middleware_data; json_t * j_search_module; j_search_module = get_user_middleware_module(config, u_map_get(request->map_url, "name")); if (check_result_value(j_search_module, G_OK)) { if (delete_user_middleware_module(config, u_map_get(request->map_url, "name")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_delete_user_middleware_module - Error delete_user_middleware_module"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - User backend module '%s' removed", u_map_get(request->map_url, "name")); } } else if (check_result_value(j_search_module, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_delete_user_middleware_module - Error get_user_middleware_module"); response->status = 500; } json_decref(j_search_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_manage_user_middleware_module (const struct _u_request * request, struct _u_response * response, void * user_middleware_data) { struct config_elements * config = (struct config_elements *)user_middleware_data; json_t * j_search_module, * j_result, * j_result2; j_search_module = get_user_middleware_module(config, u_map_get(request->map_url, "name")); if (check_result_value(j_search_module, G_OK)) { if (0 == o_strcmp("enable", u_map_get(request->map_url, "action"))) { j_result = manage_user_middleware_module(config, u_map_get(request->map_url, "name"), GLEWLWYD_MODULE_ACTION_START); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_user_middleware_module - Error manage_user_middleware_module enable"); response->status = 500; } json_decref(j_result); } else if (0 == o_strcmp("disable", u_map_get(request->map_url, "action"))) { j_result = manage_user_middleware_module(config, u_map_get(request->map_url, "name"), GLEWLWYD_MODULE_ACTION_STOP); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_user_middleware_module - Error manage_user_middleware_module disable"); response->status = 500; } json_decref(j_result); } else if (0 == o_strcmp("reset", u_map_get(request->map_url, "action"))) { j_result = manage_user_middleware_module(config, u_map_get(request->map_url, "name"), GLEWLWYD_MODULE_ACTION_STOP); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_user_middleware_module - Error manage_user_middleware_module reset (1)"); response->status = 500; } else { j_result2 = manage_user_middleware_module(config, u_map_get(request->map_url, "name"), GLEWLWYD_MODULE_ACTION_START); if (check_result_value(j_result2, G_ERROR_PARAM)) { if (json_object_get(j_result2, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result2, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result2, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_user_middleware_module - Error manage_user_middleware_module reset (2)"); response->status = 500; } json_decref(j_result2); } json_decref(j_result); } else { response->status = 400; } } else if (check_result_value(j_search_module, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_user_middleware_module - Error get_user_middleware_module"); response->status = 500; } json_decref(j_search_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_get_user_auth_scheme_module_list (const struct _u_request * request, struct _u_response * response, void * user_auth_scheme_data) { UNUSED(request); struct config_elements * config = (struct config_elements *)user_auth_scheme_data; json_t * j_module; j_module = get_user_auth_scheme_module_list(config); if (check_result_value(j_module, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_module, "module")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_user_auth_scheme_module_list - Error get_user_auth_scheme_module_list"); response->status = 500; } json_decref(j_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_get_user_auth_scheme_module (const struct _u_request * request, struct _u_response * response, void * user_auth_scheme_data) { struct config_elements * config = (struct config_elements *)user_auth_scheme_data; json_t * j_module; j_module = get_user_auth_scheme_module(config, u_map_get(request->map_url, "name")); if (check_result_value(j_module, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_module, "module")); } else if (check_result_value(j_module, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_user_auth_scheme_module - Error get_user_auth_scheme_module"); response->status = 500; } json_decref(j_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_add_user_auth_scheme_module (const struct _u_request * request, struct _u_response * response, void * user_auth_scheme_data) { struct config_elements * config = (struct config_elements *)user_auth_scheme_data; json_t * j_module, * j_module_valid, * j_result; j_module = ulfius_get_json_body_request(request, NULL); if (j_module != NULL) { j_module_valid = is_user_auth_scheme_module_valid(config, j_module, 1); if (check_result_value(j_module_valid, G_OK)) { j_result = add_user_auth_scheme_module(config, j_module); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_add_user_auth_scheme_module - Error add_user_auth_scheme_module"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - User auth scheme module '%s' added (%s)", json_string_value(json_object_get(j_module, "name")), json_string_value(json_object_get(j_module, "module"))); } json_decref(j_result); } else if (check_result_value(j_module_valid, G_ERROR_PARAM)) { if (json_object_get(j_module_valid, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_module_valid, "error")); } else { response->status = 400; } } else if (!check_result_value(j_module_valid, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_add_user_auth_scheme_module - Error is_user_auth_scheme_module_valid"); response->status = 500; } json_decref(j_module_valid); } else { response->status = 400; } json_decref(j_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_set_user_auth_scheme_module (const struct _u_request * request, struct _u_response * response, void * user_auth_scheme_data) { struct config_elements * config = (struct config_elements *)user_auth_scheme_data; json_t * j_module, * j_module_valid, * j_search_module; j_search_module = get_user_auth_scheme_module(config, u_map_get(request->map_url, "name")); if (check_result_value(j_search_module, G_OK)) { j_module = ulfius_get_json_body_request(request, NULL); if (j_module != NULL) { json_object_del(j_module, "enabled"); j_module_valid = is_user_auth_scheme_module_valid(config, j_module, 0); if (check_result_value(j_module_valid, G_OK)) { if (set_user_auth_scheme_module(config, u_map_get(request->map_url, "name"), j_module) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_user_auth_scheme_module - Error set_user_auth_scheme_module"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - User auth scheme module '%s' updated", u_map_get(request->map_url, "name")); } } else if (check_result_value(j_module_valid, G_ERROR_PARAM)) { if (json_object_get(j_module_valid, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_module_valid, "error")); } else { response->status = 400; } } else if (!check_result_value(j_module_valid, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_user_auth_scheme_module - Error is_user_auth_scheme_module_valid"); response->status = 500; } json_decref(j_module_valid); } else { response->status = 400; } json_decref(j_module); } else if (check_result_value(j_search_module, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_user_auth_scheme_module - Error get_user_auth_scheme_module"); response->status = 500; } json_decref(j_search_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_delete_user_auth_scheme_module (const struct _u_request * request, struct _u_response * response, void * user_auth_scheme_data) { struct config_elements * config = (struct config_elements *)user_auth_scheme_data; json_t * j_search_module; j_search_module = get_user_auth_scheme_module(config, u_map_get(request->map_url, "name")); if (check_result_value(j_search_module, G_OK)) { if (delete_user_auth_scheme_module(config, u_map_get(request->map_url, "name")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_delete_user_auth_scheme_module - Error delete_user_auth_scheme_module"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - User auth scheme module '%s' removed", u_map_get(request->map_url, "name")); } } else if (check_result_value(j_search_module, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_delete_user_auth_scheme_module - Error get_user_auth_scheme_module"); response->status = 500; } json_decref(j_search_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_manage_user_auth_scheme_module (const struct _u_request * request, struct _u_response * response, void * user_auth_scheme_data) { struct config_elements * config = (struct config_elements *)user_auth_scheme_data; json_t * j_search_module, * j_result, * j_result2; j_search_module = get_user_auth_scheme_module(config, u_map_get(request->map_url, "name")); if (check_result_value(j_search_module, G_OK)) { if (0 == o_strcmp("enable", u_map_get(request->map_url, "action"))) { j_result = manage_user_auth_scheme_module(config, u_map_get(request->map_url, "name"), GLEWLWYD_MODULE_ACTION_START); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_user_auth_scheme_module - Error manage_user_auth_scheme_module enable"); response->status = 500; } json_decref(j_result); } else if (0 == o_strcmp("disable", u_map_get(request->map_url, "action"))) { j_result = manage_user_auth_scheme_module(config, u_map_get(request->map_url, "name"), GLEWLWYD_MODULE_ACTION_STOP); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_user_auth_scheme_module - Error manage_user_auth_scheme_module disable"); response->status = 500; } json_decref(j_result); } else if (0 == o_strcmp("reset", u_map_get(request->map_url, "action"))) { j_result = manage_user_auth_scheme_module(config, u_map_get(request->map_url, "name"), GLEWLWYD_MODULE_ACTION_STOP); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_user_auth_scheme_module - Error manage_user_auth_scheme_module reset (1)"); response->status = 500; } else { j_result2 = manage_user_auth_scheme_module(config, u_map_get(request->map_url, "name"), GLEWLWYD_MODULE_ACTION_START); if (check_result_value(j_result2, G_ERROR_PARAM)) { if (json_object_get(j_result2, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result2, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_user_auth_scheme_module - Error manage_user_auth_scheme_module reset (2)"); response->status = 500; } json_decref(j_result2); } json_decref(j_result); } else { response->status = 400; } } else if (check_result_value(j_search_module, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_user_auth_scheme_module - Error get_user_auth_scheme_module"); response->status = 500; } json_decref(j_search_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_get_client_module_list (const struct _u_request * request, struct _u_response * response, void * client_data) { UNUSED(request); struct config_elements * config = (struct config_elements *)client_data; json_t * j_module; j_module = get_client_module_list(config); if (check_result_value(j_module, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_module, "module")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_client_module_list - Error get_client_module_list"); response->status = 500; } json_decref(j_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_get_client_module (const struct _u_request * request, struct _u_response * response, void * client_data) { struct config_elements * config = (struct config_elements *)client_data; json_t * j_module; j_module = get_client_module(config, u_map_get(request->map_url, "name")); if (check_result_value(j_module, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_module, "module")); } else if (check_result_value(j_module, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_client_module - Error get_client_module"); response->status = 500; } json_decref(j_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_add_client_module (const struct _u_request * request, struct _u_response * response, void * client_data) { struct config_elements * config = (struct config_elements *)client_data; json_t * j_module, * j_module_valid, * j_result; j_module = ulfius_get_json_body_request(request, NULL); if (j_module != NULL) { j_module_valid = is_client_module_valid(config, j_module, 1); if (check_result_value(j_module_valid, G_OK)) { j_result = add_client_module(config, j_module); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_add_client_module - Error add_client_module"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - Client backend module '%s' added (%s)", json_string_value(json_object_get(j_module, "name")), json_string_value(json_object_get(j_module, "module"))); } json_decref(j_result); } else if (check_result_value(j_module_valid, G_ERROR_PARAM)) { if (json_object_get(j_module_valid, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_module_valid, "error")); } else { response->status = 400; } } else if (!check_result_value(j_module_valid, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_add_client_module - Error is_client_module_valid"); response->status = 500; } json_decref(j_module_valid); } else { response->status = 400; } json_decref(j_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_set_client_module (const struct _u_request * request, struct _u_response * response, void * client_data) { struct config_elements * config = (struct config_elements *)client_data; json_t * j_module, * j_module_valid, * j_search_module; j_search_module = get_client_module(config, u_map_get(request->map_url, "name")); if (check_result_value(j_search_module, G_OK)) { j_module = ulfius_get_json_body_request(request, NULL); if (j_module != NULL) { json_object_del(j_module, "enabled"); j_module_valid = is_client_module_valid(config, j_module, 0); if (check_result_value(j_module_valid, G_OK)) { if (set_client_module(config, u_map_get(request->map_url, "name"), j_module) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_client_module - Error set_client_module"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - Client backend module '%s' updated", u_map_get(request->map_url, "name")); } } else if (check_result_value(j_module_valid, G_ERROR_PARAM)) { if (json_object_get(j_module_valid, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_module_valid, "error")); } else { response->status = 400; } } else if (!check_result_value(j_module_valid, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_client_module - Error is_client_module_valid"); response->status = 500; } json_decref(j_module_valid); } else { response->status = 400; } json_decref(j_module); } else if (check_result_value(j_search_module, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_client_module - Error get_client_module"); response->status = 500; } json_decref(j_search_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_delete_client_module (const struct _u_request * request, struct _u_response * response, void * client_data) { struct config_elements * config = (struct config_elements *)client_data; json_t * j_search_module; j_search_module = get_client_module(config, u_map_get(request->map_url, "name")); if (check_result_value(j_search_module, G_OK)) { if (delete_client_module(config, u_map_get(request->map_url, "name")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_delete_client_module - Error delete_client_module"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - Client backend module '%s' removed", u_map_get(request->map_url, "name")); } } else if (check_result_value(j_search_module, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_delete_client_module - Error get_client_module"); response->status = 500; } json_decref(j_search_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_manage_client_module (const struct _u_request * request, struct _u_response * response, void * client_data) { struct config_elements * config = (struct config_elements *)client_data; json_t * j_search_module, * j_result, * j_result2; j_search_module = get_client_module(config, u_map_get(request->map_url, "name")); if (check_result_value(j_search_module, G_OK)) { if (0 == o_strcmp("enable", u_map_get(request->map_url, "action"))) { j_result = manage_client_module(config, u_map_get(request->map_url, "name"), GLEWLWYD_MODULE_ACTION_START); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_client_module - Error manage_client_module enable"); response->status = 500; } json_decref(j_result); } else if (0 == o_strcmp("disable", u_map_get(request->map_url, "action"))) { j_result = manage_client_module(config, u_map_get(request->map_url, "name"), GLEWLWYD_MODULE_ACTION_STOP); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_client_module - Error manage_client_module disable"); response->status = 500; } json_decref(j_result); } else if (0 == o_strcmp("reset", u_map_get(request->map_url, "action"))) { j_result = manage_client_module(config, u_map_get(request->map_url, "name"), GLEWLWYD_MODULE_ACTION_STOP); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_client_module - Error manage_client_module reset (1)"); response->status = 500; } else { j_result2 = manage_client_module(config, u_map_get(request->map_url, "name"), GLEWLWYD_MODULE_ACTION_START); if (check_result_value(j_result2, G_ERROR_PARAM)) { if (json_object_get(j_result2, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result2, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result2, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_client_module - Error manage_client_module reset (2)"); response->status = 500; } json_decref(j_result2); } json_decref(j_result); } else { response->status = 400; } } else if (check_result_value(j_search_module, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_client_module - Error get_client_module"); response->status = 500; } json_decref(j_search_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_get_plugin_module_list (const struct _u_request * request, struct _u_response * response, void * plugin_data) { UNUSED(request); struct config_elements * config = (struct config_elements *)plugin_data; json_t * j_module; j_module = get_plugin_module_list(config); if (check_result_value(j_module, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_module, "module")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_plugin_module_list - Error get_plugin_module_list"); response->status = 500; } json_decref(j_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_get_plugin_module (const struct _u_request * request, struct _u_response * response, void * plugin_data) { struct config_elements * config = (struct config_elements *)plugin_data; json_t * j_module; j_module = get_plugin_module(config, u_map_get(request->map_url, "name")); if (check_result_value(j_module, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_module, "module")); } else if (check_result_value(j_module, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_plugin_module - Error get_plugin_module"); response->status = 500; } json_decref(j_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_add_plugin_module (const struct _u_request * request, struct _u_response * response, void * plugin_data) { struct config_elements * config = (struct config_elements *)plugin_data; json_t * j_module, * j_module_valid, * j_result; j_module = ulfius_get_json_body_request(request, NULL); if (j_module != NULL) { j_module_valid = is_plugin_module_valid(config, j_module, 1); if (check_result_value(j_module_valid, G_OK)) { j_result = add_plugin_module(config, j_module); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_add_plugin_module - Error add_plugin_module"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - Plugin module '%s' added (%s)", json_string_value(json_object_get(j_module, "name")), json_string_value(json_object_get(j_module, "module"))); } json_decref(j_result); } else if (check_result_value(j_module_valid, G_ERROR_PARAM)) { if (json_object_get(j_module_valid, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_module_valid, "error")); } else { response->status = 400; } } else if (!check_result_value(j_module_valid, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_add_plugin_module - Error is_plugin_module_valid"); response->status = 500; } json_decref(j_module_valid); } else { response->status = 400; } json_decref(j_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_set_plugin_module (const struct _u_request * request, struct _u_response * response, void * plugin_data) { struct config_elements * config = (struct config_elements *)plugin_data; json_t * j_module, * j_module_valid, * j_search_module; j_search_module = get_plugin_module(config, u_map_get(request->map_url, "name")); if (check_result_value(j_search_module, G_OK)) { j_module = ulfius_get_json_body_request(request, NULL); if (j_module != NULL) { json_object_del(j_module, "enabled"); j_module_valid = is_plugin_module_valid(config, j_module, 0); if (check_result_value(j_module_valid, G_OK)) { if (set_plugin_module(config, u_map_get(request->map_url, "name"), j_module) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_plugin_module - Error set_plugin_module"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - Plugin module '%s' updated", u_map_get(request->map_url, "name")); } } else if (check_result_value(j_module_valid, G_ERROR_PARAM)) { if (json_object_get(j_module_valid, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_module_valid, "error")); } else { response->status = 400; } } else if (!check_result_value(j_module_valid, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_plugin_module - Error is_plugin_module_valid"); response->status = 500; } json_decref(j_module_valid); } else { response->status = 400; } json_decref(j_module); } else if (check_result_value(j_search_module, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_plugin_module - Error get_plugin_module"); response->status = 500; } json_decref(j_search_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_delete_plugin_module (const struct _u_request * request, struct _u_response * response, void * plugin_data) { struct config_elements * config = (struct config_elements *)plugin_data; json_t * j_search_module; j_search_module = get_plugin_module(config, u_map_get(request->map_url, "name")); if (check_result_value(j_search_module, G_OK)) { if (delete_plugin_module(config, u_map_get(request->map_url, "name")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_delete_plugin_module - Error delete_plugin_module"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - Plugin module '%s' removed", u_map_get(request->map_url, "name")); } } else if (check_result_value(j_search_module, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_delete_plugin_module - Error get_plugin_module"); response->status = 500; } json_decref(j_search_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_manage_plugin_module (const struct _u_request * request, struct _u_response * response, void * plugin_data) { struct config_elements * config = (struct config_elements *)plugin_data; json_t * j_search_module, * j_result, * j_result2; j_search_module = get_plugin_module(config, u_map_get(request->map_url, "name")); if (check_result_value(j_search_module, G_OK)) { if (0 == o_strcmp("enable", u_map_get(request->map_url, "action"))) { j_result = manage_plugin_module(config, u_map_get(request->map_url, "name"), GLEWLWYD_MODULE_ACTION_START); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_plugin_module - Error manage_plugin_module enable"); response->status = 500; } json_decref(j_result); } else if (0 == o_strcmp("disable", u_map_get(request->map_url, "action"))) { j_result = manage_plugin_module(config, u_map_get(request->map_url, "name"), GLEWLWYD_MODULE_ACTION_STOP); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_plugin_module - Error manage_plugin_module disable"); response->status = 500; } json_decref(j_result); } else if (0 == o_strcmp("reset", u_map_get(request->map_url, "action"))) { j_result = manage_plugin_module(config, u_map_get(request->map_url, "name"), GLEWLWYD_MODULE_ACTION_STOP); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_plugin_module - Error manage_plugin_module reset (1)"); response->status = 500; } else { j_result2 = manage_plugin_module(config, u_map_get(request->map_url, "name"), GLEWLWYD_MODULE_ACTION_START); if (check_result_value(j_result2, G_ERROR_PARAM)) { if (json_object_get(j_result2, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result2, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result2, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_plugin_module - Error manage_plugin_module reset (1)"); response->status = 500; } json_decref(j_result2); } json_decref(j_result); } else { response->status = 400; } } else if (check_result_value(j_search_module, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_manage_plugin_module - Error get_plugin_module"); response->status = 500; } json_decref(j_search_module); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_get_user_list (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_user_list; size_t offset = 0, limit = GLEWLWYD_DEFAULT_LIMIT_SIZE; long int l_converted = 0; char * endptr = NULL; if (u_map_get(request->map_url, "offset") != NULL) { l_converted = strtol(u_map_get(request->map_url, "offset"), &endptr, 10); if (!(*endptr) && l_converted > 0) { offset = (size_t)l_converted; } } if (u_map_get(request->map_url, "limit") != NULL) { l_converted = strtol(u_map_get(request->map_url, "limit"), &endptr, 10); if (!(*endptr) && l_converted > 0) { limit = (size_t)l_converted; } } j_user_list = get_user_list(config, u_map_get(request->map_url, "pattern"), offset, limit, u_map_get(request->map_url, "source")); if (check_result_value(j_user_list, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_user_list, "user")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_user_list - Error get_user_list"); response->status = 500; } json_decref(j_user_list); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_get_user (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_user; j_user = get_user(config, u_map_get(request->map_url, "username"), u_map_get(request->map_url, "source")); if (check_result_value(j_user, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_user, "user")); } else if (check_result_value(j_user, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_user - Error j_user"); response->status = 500; } json_decref(j_user); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_add_user (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_user, * j_user_valid, * j_search_user, * j_body; j_user = ulfius_get_json_body_request(request, NULL); if (j_user != NULL) { j_user_valid = is_user_valid(config, NULL, j_user, 1, u_map_get(request->map_url, "source")); if (check_result_value(j_user_valid, G_OK)) { j_search_user = get_user(config, json_string_value(json_object_get(j_user, "username")), u_map_get(request->map_url, "source")); if (check_result_value(j_search_user, G_ERROR_NOT_FOUND)) { if (add_user(config, j_user, u_map_get(request->map_url, "source")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_add_user - Error add_user"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - User '%s' added", json_string_value(json_object_get(j_user, "username"))); } } else if (check_result_value(j_search_user, G_OK)) { j_body = json_pack("{s[s]}", "error", "username already exists"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_add_user - Error get_user"); response->status = 500; } json_decref(j_search_user); } else if (check_result_value(j_user_valid, G_ERROR_PARAM)) { if (json_object_get(j_user_valid, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_user_valid, "error")); } else { response->status = 400; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_add_user - Error is_user_valid"); response->status = 500; } json_decref(j_user_valid); } else { response->status = 400; } json_decref(j_user); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_set_user (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_user, * j_user_valid, * j_search_user; j_search_user = get_user(config, u_map_get(request->map_url, "username"), u_map_get(request->map_url, "source")); if (check_result_value(j_search_user, G_OK)) { j_user = ulfius_get_json_body_request(request, NULL); if (j_user != NULL) { j_user_valid = is_user_valid(config, u_map_get(request->map_url, "username"), j_user, 0, json_string_value(json_object_get(json_object_get(j_search_user, "user"), "source"))); if (check_result_value(j_user_valid, G_OK)) { if (set_user(config, u_map_get(request->map_url, "username"), j_user, json_string_value(json_object_get(json_object_get(j_search_user, "user"), "source"))) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_user - Error set_user"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - User '%s' updated", u_map_get(request->map_url, "username")); } } else if (check_result_value(j_user_valid, G_ERROR_PARAM)) { if (json_object_get(j_user_valid, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_user_valid, "error")); } else { response->status = 400; } } else if (!check_result_value(j_user_valid, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_user - Error is_user_valid"); response->status = 500; } json_decref(j_user_valid); } else { response->status = 400; } json_decref(j_user); } else if (check_result_value(j_search_user, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_user - Error get_user"); response->status = 500; } json_decref(j_search_user); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_delete_user (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_search_user; j_search_user = get_user(config, u_map_get(request->map_url, "username"), u_map_get(request->map_url, "source")); if (check_result_value(j_search_user, G_OK)) { if (delete_user(config, u_map_get(request->map_url, "username"), json_string_value(json_object_get(json_object_get(j_search_user, "user"), "source"))) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_delete_user - Error delete_user"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - User '%s' removed", u_map_get(request->map_url, "username")); } } else if (check_result_value(j_search_user, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_delete_user - Error get_user"); response->status = 500; } json_decref(j_search_user); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_get_client_list (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_client_list; size_t offset = 0, limit = GLEWLWYD_DEFAULT_LIMIT_SIZE; long int l_converted = 0; char * endptr = NULL; if (u_map_get(request->map_url, "offset") != NULL) { l_converted = strtol(u_map_get(request->map_url, "offset"), &endptr, 10); if (!(*endptr) && l_converted > 0) { offset = (size_t)l_converted; } } if (u_map_get(request->map_url, "limit") != NULL) { l_converted = strtol(u_map_get(request->map_url, "limit"), &endptr, 10); if (!(*endptr) && l_converted > 0) { limit = (size_t)l_converted; } } j_client_list = get_client_list(config, u_map_get(request->map_url, "pattern"), offset, limit, u_map_get(request->map_url, "source")); if (check_result_value(j_client_list, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_client_list, "client")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_client_list - Error get_client_list"); response->status = 500; } json_decref(j_client_list); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_get_client (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_client; j_client = get_client(config, u_map_get(request->map_url, "client_id"), u_map_get(request->map_url, "source")); if (check_result_value(j_client, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_client, "client")); } else if (check_result_value(j_client, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_client - Error j_client"); response->status = 500; } json_decref(j_client); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_add_client (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_client, * j_client_valid, * j_search_client, * j_body; j_client = ulfius_get_json_body_request(request, NULL); if (j_client != NULL) { j_client_valid = is_client_valid(config, NULL, j_client, 1, u_map_get(request->map_url, "source")); if (check_result_value(j_client_valid, G_OK)) { j_search_client = get_client(config, json_string_value(json_object_get(j_client, "client_id")), u_map_get(request->map_url, "source")); if (check_result_value(j_search_client, G_ERROR_NOT_FOUND)) { if (add_client(config, j_client, u_map_get(request->map_url, "source")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_add_client - Error add_client"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - Client '%s' added", json_string_value(json_object_get(j_client, "client_id"))); } } else if (check_result_value(j_search_client, G_OK)) { j_body = json_pack("{s[s]}", "error", "client_id already exists"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_add_client - Error get_client"); response->status = 500; } json_decref(j_search_client); } else if (check_result_value(j_client_valid, G_ERROR_PARAM)) { if (json_object_get(j_client_valid, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_client_valid, "error")); } else { response->status = 400; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_add_client - Error is_client_valid"); response->status = 500; } json_decref(j_client_valid); } else { response->status = 400; } json_decref(j_client); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_set_client (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_client, * j_client_valid, * j_search_client; j_search_client = get_client(config, u_map_get(request->map_url, "client_id"), u_map_get(request->map_url, "source")); if (check_result_value(j_search_client, G_OK)) { j_client = ulfius_get_json_body_request(request, NULL); if (j_client != NULL) { j_client_valid = is_client_valid(config, u_map_get(request->map_url, "client_id"), j_client, 0, json_string_value(json_object_get(json_object_get(j_search_client, "client"), "source"))); if (check_result_value(j_client_valid, G_OK)) { if (set_client(config, u_map_get(request->map_url, "client_id"), j_client, json_string_value(json_object_get(json_object_get(j_search_client, "client"), "source"))) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_client - Error set_client"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - Client '%s' updated", u_map_get(request->map_url, "client_id")); } } else if (check_result_value(j_client_valid, G_ERROR_PARAM)) { if (json_object_get(j_client_valid, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_client_valid, "error")); } else { response->status = 400; } } else if (!check_result_value(j_client_valid, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_client - Error is_client_valid"); response->status = 500; } json_decref(j_client_valid); } else { response->status = 400; } json_decref(j_client); } else if (check_result_value(j_search_client, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_client - Error get_client"); response->status = 500; } json_decref(j_search_client); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_delete_client (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_search_client; j_search_client = get_client(config, u_map_get(request->map_url, "client_id"), u_map_get(request->map_url, "source")); if (check_result_value(j_search_client, G_OK)) { if (delete_client(config, u_map_get(request->map_url, "client_id"), json_string_value(json_object_get(json_object_get(j_search_client, "client"), "source"))) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_delete_client - Error delete_client"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - Client '%s' removed", u_map_get(request->map_url, "client_id")); } } else if (check_result_value(j_search_client, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_delete_client - Error get_client"); response->status = 500; } json_decref(j_search_client); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_get_scope_list (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_scope_list; size_t offset = 0, limit = GLEWLWYD_DEFAULT_LIMIT_SIZE; long int l_converted = 0; char * endptr = NULL; if (u_map_get(request->map_url, "offset") != NULL) { l_converted = strtol(u_map_get(request->map_url, "offset"), &endptr, 10); if (!(*endptr) && l_converted > 0) { offset = (size_t)l_converted; } } if (u_map_get(request->map_url, "limit") != NULL) { l_converted = strtol(u_map_get(request->map_url, "limit"), &endptr, 10); if (!(*endptr) && l_converted >= 0) { limit = (size_t)l_converted; } } j_scope_list = get_scope_list(config, u_map_get(request->map_url, "pattern"), offset, limit); if (check_result_value(j_scope_list, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_scope_list, "scope")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_scope_list - Error get_scope_list"); response->status = 500; } json_decref(j_scope_list); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_get_scope (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_scope; j_scope = get_scope(config, u_map_get(request->map_url, "scope")); if (check_result_value(j_scope, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_scope, "scope")); } else if (check_result_value(j_scope, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_scope - Error get_scope"); response->status = 500; } json_decref(j_scope); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_add_scope (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_scope, * j_scope_valid, * j_search_scope, * j_body; j_scope = ulfius_get_json_body_request(request, NULL); if (j_scope != NULL) { j_scope_valid = is_scope_valid(config, j_scope, 1); if (check_result_value(j_scope_valid, G_OK)) { j_search_scope = get_scope(config, json_string_value(json_object_get(j_scope, "name"))); if (check_result_value(j_search_scope, G_ERROR_NOT_FOUND)) { if (add_scope(config, j_scope) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_add_scope - Error add_scope"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - Scope '%s' added", json_string_value(json_object_get(j_scope, "name"))); } } else if (check_result_value(j_search_scope, G_OK)) { j_body = json_pack("{s[s]}", "error", "scope already exists"); ulfius_set_json_body_response(response, 400, j_body); json_decref(j_body); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_add_scope - Error get_scope"); response->status = 500; } json_decref(j_search_scope); } else if (check_result_value(j_scope_valid, G_ERROR_PARAM)) { ulfius_set_json_body_response(response, 400, json_object_get(j_scope_valid, "error")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_add_scope - Error is_scope_valid"); response->status = 500; } json_decref(j_scope_valid); } else { response->status = 400; } json_decref(j_scope); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_set_scope (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_scope, * j_scope_valid, * j_search_scope; j_search_scope = get_scope(config, u_map_get(request->map_url, "scope")); if (check_result_value(j_search_scope, G_OK)) { j_scope = ulfius_get_json_body_request(request, NULL); if (j_scope != NULL) { j_scope_valid = is_scope_valid(config, j_scope, 0); if (check_result_value(j_scope_valid, G_OK)) { if (set_scope(config, u_map_get(request->map_url, "scope"), j_scope) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_scope - Error set_scope"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - Scope '%s' updated", u_map_get(request->map_url, "scope")); } } else if (check_result_value(j_scope_valid, G_ERROR_PARAM)) { ulfius_set_json_body_response(response, 400, json_object_get(j_scope_valid, "error")); } else if (!check_result_value(j_scope_valid, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_scope - Error is_scope_valid"); response->status = 500; } json_decref(j_scope_valid); } else { response->status = 400; } json_decref(j_scope); } else if (check_result_value(j_search_scope, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_set_scope - Error get_scope"); response->status = 500; } json_decref(j_search_scope); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_delete_scope (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_search_scope; j_search_scope = get_scope(config, u_map_get(request->map_url, "scope")); if (check_result_value(j_search_scope, G_OK)) { if (delete_scope(config, u_map_get(request->map_url, "scope")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_delete_scope - Error delete_scope"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - Scope '%s' removed", u_map_get(request->map_url, "scope")); } } else if (check_result_value(j_search_scope, G_ERROR_NOT_FOUND)) { response->status = 404; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_delete_scope - Error get_scope"); response->status = 500; } json_decref(j_search_scope); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_user_get_profile (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_session; char * session_uid, expires[129]; time_t now; struct tm ts; time(&now); now += GLEWLWYD_DEFAULT_SESSION_EXPIRATION_COOKIE; gmtime_r(&now, &ts); strftime(expires, 128, "%a, %d %b %Y %T %Z", &ts); if (!o_strlen(u_map_get(request->map_url, "username"))) { session_uid = get_session_id(config, request); if (session_uid != NULL && o_strlen(session_uid)) { j_session = get_users_for_session(config, session_uid); if (check_result_value(j_session, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_session, "session")); ulfius_add_cookie_to_response(response, config->session_key, session_uid, expires, 0, config->cookie_domain, "/", config->cookie_secure, 0); } else if (check_result_value(j_session, G_ERROR_NOT_FOUND)) { response->status = 401; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_get_session - Error get_current_user_for_session"); response->status = 500; } json_decref(j_session); } else { response->status = 401; } o_free(session_uid); } else { // Can't impersonate this endpoint response->status = 400; } return U_CALLBACK_CONTINUE; } int callback_glewlwyd_user_update_profile (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_profile, * j_result; j_profile = ulfius_get_json_body_request(request, NULL); if (j_profile != NULL && json_is_object(j_profile)) { j_result = user_set_profile(config, json_string_value(json_object_get((json_t *)response->shared_data, "username")), j_profile); if (check_result_value(j_result, G_ERROR_PARAM)) { if (json_object_get(j_result, "error") != NULL) { ulfius_set_json_body_response(response, 400, json_object_get(j_result, "error")); } else { response->status = 400; } } else if (!check_result_value(j_result, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_update_profile - Error user_set_profile"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - User '%s' updated (profile)", json_string_value(json_object_get((json_t *)response->shared_data, "username"))); } json_decref(j_result); } else { response->status = 400; } json_decref(j_profile); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_user_delete_profile (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; int ret = G_OK; const char * username = json_string_value(json_object_get((json_t *)response->shared_data, "username")); json_t * j_session, * j_cur_session; char * session_uid = get_session_id(config, request); size_t index; j_session = get_current_user_for_session(config, session_uid); if (check_result_value(j_session, G_ERROR_NOT_FOUND)) { response->status = 404; } else if (!check_result_value(j_session, G_OK)) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_delete_profile - Error get_current_user_for_session"); response->status = 500; } else { json_array_foreach(json_object_get(j_session, "session"), index, j_cur_session) { if (0 == o_strcasecmp(username, json_string_value(json_object_get(j_cur_session, "username")))) { if (delete_user_session_from_hash(config, json_string_value(json_object_get(j_cur_session, "username")), NULL) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_delete_profile - Error delete_user_session_from_hash"); response->status = 500; ret = G_ERROR; } else { if (user_session_delete(config, session_uid, json_string_value(json_object_get(j_cur_session, "username"))) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_delete_profile - Error user_session_delete"); response->status = 500; ret = G_ERROR; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - User '%s' removed (profile)", json_string_value(json_object_get((json_t *)response->shared_data, "username"))); } } } } json_decref(j_session); if (ret == G_OK) { ret = user_delete_profile(config, username); if (ret == G_ERROR_UNAUTHORIZED) { response->status = 403; } else if (ret != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_delete_profile - Error user_delete_profile"); response->status = 500; } } } o_free(session_uid); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_user_update_password (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_session, * j_password, * j_element = NULL; char * session_uid = get_session_id(config, request); const char ** passwords = NULL; int res; struct _user_module_instance * user_module; size_t index = 0; if (session_uid != NULL && o_strlen(session_uid)) { j_session = get_current_user_for_session(config, session_uid); if (check_result_value(j_session, G_OK)) { j_password = ulfius_get_json_body_request(request, NULL); user_module = get_user_module_instance(config, json_string_value(json_object_get(json_object_get(j_session, "user"), "source"))); if (user_module && user_module->multiple_passwords) { if (json_string_length(json_object_get(j_password, "old_password")) && json_is_array(json_object_get(j_password, "password"))) { if ((passwords = o_malloc(json_array_size(json_object_get(j_password, "password")) * sizeof(char *))) != NULL) { json_array_foreach(json_object_get(j_password, "password"), index, j_element) { passwords[index] = json_string_value(j_element); } if ((res = user_update_password(config, json_string_value(json_object_get(json_object_get(j_session, "user"), "username")), json_string_value(json_object_get(j_password, "old_password")), passwords, json_array_size(json_object_get(j_password, "password")))) == G_ERROR_PARAM) { response->status = 400; } else if (res != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_update_password - Error user_update_password (1)"); response->status = 500; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_update_password - Error allocating resources for passwords (1)"); response->status = 500; } o_free(passwords); } else { response->status = 400; } } else { if (json_string_length(json_object_get(j_password, "old_password")) && json_string_length(json_object_get(j_password, "password"))) { if ((passwords = o_malloc(sizeof(char *))) != NULL) { passwords[0] = json_string_value(json_object_get(j_password, "password")); if ((res = user_update_password(config, json_string_value(json_object_get(json_object_get(j_session, "user"), "username")), json_string_value(json_object_get(j_password, "old_password")), passwords, 1)) == G_ERROR_PARAM) { response->status = 400; } else if (res != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_update_password - Error user_update_password (2)"); response->status = 500; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_update_password - Error allocating resources for passwords (2)"); response->status = 500; } o_free(passwords); } else { response->status = 400; } } json_decref(j_password); } else if (check_result_value(j_session, G_ERROR_NOT_FOUND)) { response->status = 401; } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_update_password - Error get_current_user_for_session"); response->status = 500; } json_decref(j_session); } else { response->status = 401; } o_free(session_uid); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_user_get_client_grant_list (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_client_grant_list; size_t offset = 0, limit = GLEWLWYD_DEFAULT_LIMIT_SIZE; long int l_converted = 0; char * endptr = NULL; if (u_map_get(request->map_url, "offset") != NULL) { l_converted = strtol(u_map_get(request->map_url, "offset"), &endptr, 10); if (!(*endptr) && l_converted > 0) { offset = (size_t)l_converted; } } if (u_map_get(request->map_url, "limit") != NULL) { l_converted = strtol(u_map_get(request->map_url, "limit"), &endptr, 10); if (!(*endptr) && l_converted > 0) { limit = (size_t)l_converted; } } j_client_grant_list = get_client_grant_list(config, json_string_value(json_object_get((json_t *)response->shared_data, "username")), offset, limit); if (check_result_value(j_client_grant_list, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_client_grant_list, "client_grant")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_get_session_list - Error get_user_session_list"); response->status = 500; } json_decref(j_client_grant_list); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_user_get_session_list (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_session_list; size_t offset = 0, limit = GLEWLWYD_DEFAULT_LIMIT_SIZE; long int l_converted = 0; char * endptr = NULL, * sort = NULL; if (u_map_get(request->map_url, "offset") != NULL) { l_converted = strtol(u_map_get(request->map_url, "offset"), &endptr, 10); if (!(*endptr) && l_converted > 0) { offset = (size_t)l_converted; } } if (u_map_get(request->map_url, "limit") != NULL) { l_converted = strtol(u_map_get(request->map_url, "limit"), &endptr, 10); if (!(*endptr) && l_converted > 0) { limit = (size_t)l_converted; } } if (0 == o_strcmp(u_map_get(request->map_url, "sort"), "session_hash") || 0 == o_strcmp(u_map_get(request->map_url, "sort"), "user_agent") || 0 == o_strcmp(u_map_get(request->map_url, "sort"), "issued_for") || 0 == o_strcmp(u_map_get(request->map_url, "sort"), "expiration") || 0 == o_strcmp(u_map_get(request->map_url, "sort"), "last_login") || 0 == o_strcmp(u_map_get(request->map_url, "sort"), "enabled")) { sort = msprintf("gpgr_%s%s", u_map_get(request->map_url, "sort"), (u_map_get_case(request->map_url, "desc")!=NULL?" DESC":" ASC")); } j_session_list = get_user_session_list(config, json_string_value(json_object_get((json_t *)response->shared_data, "username")), u_map_get(request->map_url, "pattern"), offset, limit, sort); if (check_result_value(j_session_list, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_session_list, "session")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_get_session_list - Error get_user_session_list"); response->status = 500; } json_decref(j_session_list); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_user_get_plugin_list (const struct _u_request * request, struct _u_response * response, void * user_data) { UNUSED(request); struct config_elements * config = (struct config_elements *)user_data; json_t * j_plugin_list = get_plugin_module_list_for_user(config); if (check_result_value(j_plugin_list, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_plugin_list, "module")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_get_plugin_list - Error j_plugin_list"); response->status = 500; } json_decref(j_plugin_list); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_delete_session (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; int res = delete_user_session_from_hash(config, json_string_value(json_object_get((json_t *)response->shared_data, "username")), u_map_get(request->map_url, "session_hash")); if (res == G_ERROR_NOT_FOUND) { response->status = 404; } else if (res != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_delete_session - Error delete_user_session_from_hash"); response->status = 500; } return U_CALLBACK_CONTINUE; } int callback_glewlwyd_user_get_scheme_list (const struct _u_request * request, struct _u_response * response, void * user_data) { UNUSED(request); struct config_elements * config = (struct config_elements *)user_data; json_t * j_scheme_list = get_scheme_list_for_user(config, json_string_value(json_object_get((json_t *)response->shared_data, "username"))), * j_element; size_t index; if (check_result_value(j_scheme_list, G_OK)) { json_array_foreach(json_object_get(j_scheme_list, "scheme"), index, j_element) { json_object_del(j_element, "parameters"); } ulfius_set_json_body_response(response, 200, json_object_get(j_scheme_list, "scheme")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_user_get_scheme_list - Error get_scheme_list_for_user"); response->status = 500; } json_decref(j_scheme_list); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_get_api_key_list (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; json_t * j_api_key_list; size_t offset = 0, limit = GLEWLWYD_DEFAULT_LIMIT_SIZE; long int l_converted = 0; char * endptr = NULL; if (u_map_get(request->map_url, "offset") != NULL) { l_converted = strtol(u_map_get(request->map_url, "offset"), &endptr, 10); if (!(*endptr) && l_converted > 0) { offset = (size_t)l_converted; } } if (u_map_get(request->map_url, "limit") != NULL) { l_converted = strtol(u_map_get(request->map_url, "limit"), &endptr, 10); if (!(*endptr) && l_converted >= 0) { limit = (size_t)l_converted; } } j_api_key_list = get_api_key_list(config, u_map_get(request->map_url, "pattern"), offset, limit); if (check_result_value(j_api_key_list, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_api_key_list, "api_key")); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_get_api_key_list - Error get_api_key_list"); response->status = 500; } json_decref(j_api_key_list); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_add_api_key (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; const char * issued_for = get_ip_source(request), * username = json_string_value(json_object_get((json_t *)response->shared_data, "username")), * user_agent = u_map_get_case(request->map_header, "user-agent"); json_t * j_api_key = generate_api_key(config, username, issued_for, user_agent); if (check_result_value(j_api_key, G_OK)) { ulfius_set_json_body_response(response, 200, json_object_get(j_api_key, "api_key")); y_log_message(Y_LOG_LEVEL_INFO, "Event - API key created for user '%s'", username); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_add_api_key - Error generate_api_key"); response->status = 500; } json_decref(j_api_key); return U_CALLBACK_CONTINUE; } int callback_glewlwyd_delete_api_key (const struct _u_request * request, struct _u_response * response, void * user_data) { struct config_elements * config = (struct config_elements *)user_data; if (disable_api_key(config, u_map_get(request->map_url, "key_hash")) != G_OK) { y_log_message(Y_LOG_LEVEL_ERROR, "callback_glewlwyd_delete_api_key - Error disable_api_key"); response->status = 500; } else { y_log_message(Y_LOG_LEVEL_INFO, "Event - API key disabled by user '%s'", json_string_value(json_object_get((json_t *)response->shared_data, "username"))); } return U_CALLBACK_CONTINUE; } int callback_metrics (const struct _u_request * request, struct _u_response * response, void * user_data) { UNUSED(request); struct config_elements * config = (struct config_elements *)user_data; size_t i, j; char * content = o_strdup("# We have seen handsome noble-looking men but I have never seen a man like the one who now stands at the entrance of the gate.\n"); struct _glwd_metric * metric; if (!pthread_mutex_lock(&config->metrics_lock)) { u_map_put(response->map_header, ULFIUS_HTTP_HEADER_CONTENT, "text/plain; charset=utf-8"); for (i=0; imetrics_list); i++) { metric = (struct _glwd_metric *)pointer_list_get_at(&config->metrics_list, i); content = mstrcatf(content, "# HELP %s_total %s\n", metric->name, metric->help); content = mstrcatf(content, "# TYPE %s_total counter\n", metric->name); for (j=0; jdata_size; j++) { if (metric->data[j].label != NULL) { content = mstrcatf(content, "%s_total{%s} %zu\n", metric->name, metric->data[j].label, metric->data[j].counter); } else { content = mstrcatf(content, "%s_total %zu\n", metric->name, metric->data[j].counter); } } } ulfius_set_string_body_response(response, 200, content); o_free(content); pthread_mutex_unlock(&config->metrics_lock); } else { y_log_message(Y_LOG_LEVEL_ERROR, "callback_metrics - Error lock"); response->status = 500; } return U_CALLBACK_CONTINUE; } glewlwyd-2.6.1/test/000077500000000000000000000000001415646314000143415ustar00rootroot00000000000000glewlwyd-2.6.1/test/.gitignore000066400000000000000000000066231415646314000163400ustar00rootroot00000000000000glewlwyd_oauth2_auth_code glewlwyd_oauth2_client_cred glewlwyd_oauth2_code glewlwyd_oauth2_implicit glewlwyd_oauth2_resource_owner_pwd_cred glewlwyd_oauth2_code_client_confidential glewlwyd_oauth2_refresh_token glewlwyd_oauth2_refresh_token_client_confidential glewlwyd_oauth2_resource_owner_pwd_cred_client_confidential glewlwyd_oauth2_delete_token glewlwyd_oauth2_delete_token_client_confidential glewlwyd_oauth2_profile glewlwyd_oauth2_refresh_manage glewlwyd_oauth2_refresh_manage_session glewlwyd_oauth2_profile_impersonate glewlwyd_oauth2_additional_parameters glewlwyd_oauth2_client_secret glewlwyd_oauth2_code_challenge glewlwyd_oauth2_token_introspection glewlwyd_oauth2_token_revocation glewlwyd_oauth2_device_authorization glewlwyd_oauth2_code_replay glewlwyd_oauth2_scheme_required glewlwyd_oidc_auth_code glewlwyd_oidc_code glewlwyd_oidc_code_client_confidential glewlwyd_oidc_token glewlwyd_oidc_resource_owner_pwd_cred glewlwyd_oidc_resource_owner_pwd_cred_client_confidential glewlwyd_oidc_client_cred glewlwyd_oidc_code_idtoken glewlwyd_oidc_implicit_id_token_token glewlwyd_oidc_hybrid_id_token_token_code glewlwyd_oidc_hybrid_id_token_code glewlwyd_oidc_hybrid_token_code glewlwyd_oidc_implicit_id_token glewlwyd_oidc_implicit_none glewlwyd_oidc_optional_request_parameters glewlwyd_oidc_refresh_token glewlwyd_oidc_refresh_token_client_confidential glewlwyd_oidc_delete_token glewlwyd_oidc_delete_token_client_confidential glewlwyd_oidc_refresh_manage glewlwyd_oidc_refresh_manage_session glewlwyd_oidc_userinfo glewlwyd_oidc_additional_parameters glewlwyd_oidc_only_no_refresh glewlwyd_oidc_discovery glewlwyd_oidc_client_secret glewlwyd_oidc_request_jwt glewlwyd_oidc_subject_type glewlwyd_oidc_address_claim glewlwyd_oidc_claims_scopes glewlwyd_oidc_claim_request glewlwyd_oidc_code_challenge glewlwyd_oidc_token_introspection glewlwyd_oidc_token_revocation glewlwyd_oidc_client_registration glewlwyd_oidc_jwt_encrypted glewlwyd_oidc_jwks_config glewlwyd_oidc_session_management glewlwyd_oidc_device_authorization glewlwyd_oidc_refresh_token_one_use glewlwyd_oidc_client_registration_management glewlwyd_oidc_client_certificate glewlwyd_oidc_code_replay glewlwyd_oidc_scheme_required glewlwyd_oidc_dpop glewlwyd_oidc_resource glewlwyd_oidc_rich_auth_requests glewlwyd_oidc_pushed_auth_requests glewlwyd_oidc_reduced_scope glewlwyd_oidc_all_algs glewlwyd_oidc_ciba glewlwyd_oidc_auth_iss_is glewlwyd_oidc_jarm glewlwyd_oidc_fapi glewlwyd_register glewlwyd_admin_mod_type glewlwyd_admin_mod_user glewlwyd_admin_mod_user_auth_scheme glewlwyd_admin_mod_client glewlwyd_admin_mod_plugin glewlwyd_admin_check_scope glewlwyd_admin_api_key glewlwyd_auth_grant glewlwyd_auth_password glewlwyd_auth_scheme glewlwyd_auth_check_scheme glewlwyd_auth_scheme_trigger glewlwyd_auth_scheme_register glewlwyd_auth_profile glewlwyd_auth_session_manage glewlwyd_auth_profile_get_scheme_available glewlwyd_auth_profile_impersonate glewlwyd_profile_delete glewlwyd_crud_user glewlwyd_crud_user_middleware glewlwyd_crud_client glewlwyd_crud_scope glewlwyd_mod_user_http glewlwyd_mod_user_irl glewlwyd_admin_mod_user_middleware glewlwyd_mod_user_multiple_password_irl glewlwyd_mod_client_irl glewlwyd_oauth2_irl glewlwyd_oidc_irl glewlwyd_scheme_mail glewlwyd_scheme_otp glewlwyd_scheme_webauthn glewlwyd_scheme_retype_password glewlwyd_scheme_certificate glewlwyd_scheme_http glewlwyd_scheme_oauth2 glewlwyd_scheme_forbidden glewlwyd_prometheus valgrind-*.txt *.json glewlwyd-2.6.1/test/Makefile000066400000000000000000000246121415646314000160060ustar00rootroot00000000000000# # Glewlwyd SSO # # Makefile used to build the tests # # Public domain, no copyright. Use at your own risk. # CC=gcc CFLAGS=-Wall -D_REENTRANT -DDEBUG -g -O0 LDFLAGS=-lc $(shell pkg-config --libs liborcania) $(shell pkg-config --libs libyder) $(shell pkg-config --libs libulfius) $(shell pkg-config --libs libhoel) $(shell pkg-config --libs librhonabwy) $(shell pkg-config --libs libiddawc) $(shell pkg-config --libs jansson) $(shell pkg-config --libs check) $(shell pkg-config --libs gnutls) $(shell pkg-config --libs liboath) $(shell pkg-config --libs libcbor) TARGET_ADMIN=glewlwyd_admin_mod_type glewlwyd_admin_mod_user glewlwyd_admin_mod_user_auth_scheme glewlwyd_admin_mod_client glewlwyd_admin_mod_plugin glewlwyd_admin_check_scope glewlwyd_admin_api_key glewlwyd_admin_mod_user_middleware TARGET_AUTH=glewlwyd_auth_password glewlwyd_auth_scheme glewlwyd_auth_grant glewlwyd_auth_check_scheme glewlwyd_auth_scheme_trigger glewlwyd_auth_scheme_register glewlwyd_auth_profile glewlwyd_auth_session_manage glewlwyd_auth_profile_get_scheme_available glewlwyd_auth_profile_impersonate glewlwyd_scheme_forbidden TARGET_CRUD=glewlwyd_crud_user glewlwyd_crud_client glewlwyd_crud_scope glewlwyd_crud_user_middleware TARGET_OAUTH2=glewlwyd_oauth2_auth_code glewlwyd_oauth2_code glewlwyd_oauth2_code_client_confidential glewlwyd_oauth2_implicit glewlwyd_oauth2_resource_owner_pwd_cred glewlwyd_oauth2_resource_owner_pwd_cred_client_confidential glewlwyd_oauth2_client_cred glewlwyd_oauth2_refresh_token glewlwyd_oauth2_refresh_token_client_confidential glewlwyd_oauth2_delete_token glewlwyd_oauth2_delete_token_client_confidential glewlwyd_oauth2_profile glewlwyd_oauth2_refresh_manage glewlwyd_oauth2_refresh_manage_session glewlwyd_oauth2_profile_impersonate glewlwyd_oauth2_additional_parameters glewlwyd_oauth2_client_secret glewlwyd_oauth2_code_challenge glewlwyd_oauth2_token_introspection glewlwyd_oauth2_token_revocation glewlwyd_oauth2_device_authorization glewlwyd_oauth2_code_replay glewlwyd_oauth2_scheme_required TARGET_OIDC=glewlwyd_oidc_auth_code glewlwyd_oidc_code glewlwyd_oidc_code_client_confidential glewlwyd_oidc_token glewlwyd_oidc_resource_owner_pwd_cred glewlwyd_oidc_resource_owner_pwd_cred_client_confidential glewlwyd_oidc_client_cred glewlwyd_oidc_code_idtoken glewlwyd_oidc_implicit_id_token_token glewlwyd_oidc_implicit_none glewlwyd_oidc_hybrid_id_token_token_code glewlwyd_oidc_hybrid_id_token_code glewlwyd_oidc_hybrid_token_code glewlwyd_oidc_implicit_id_token glewlwyd_oidc_optional_request_parameters glewlwyd_oidc_refresh_token glewlwyd_oidc_refresh_token_client_confidential glewlwyd_oidc_delete_token glewlwyd_oidc_delete_token_client_confidential glewlwyd_oidc_refresh_manage glewlwyd_oidc_refresh_manage_session glewlwyd_oidc_userinfo glewlwyd_oidc_additional_parameters glewlwyd_oidc_only_no_refresh glewlwyd_oidc_discovery glewlwyd_oidc_client_secret glewlwyd_oidc_request_jwt glewlwyd_oidc_subject_type glewlwyd_oidc_address_claim glewlwyd_oidc_claims_scopes glewlwyd_oidc_claim_request glewlwyd_oidc_code_challenge glewlwyd_oidc_token_introspection glewlwyd_oidc_token_revocation glewlwyd_oidc_client_registration glewlwyd_oidc_jwt_encrypted glewlwyd_oidc_jwks_config glewlwyd_oidc_session_management glewlwyd_oidc_device_authorization glewlwyd_oidc_refresh_token_one_use glewlwyd_oidc_client_registration_management glewlwyd_oidc_code_replay glewlwyd_oidc_scheme_required glewlwyd_oidc_dpop glewlwyd_oidc_resource glewlwyd_oidc_rich_auth_requests glewlwyd_oidc_pushed_auth_requests glewlwyd_oidc_reduced_scope glewlwyd_oidc_all_algs glewlwyd_oidc_ciba glewlwyd_oidc_auth_iss_is glewlwyd_oidc_jarm glewlwyd_oidc_fapi TARGET_REGISTER=glewlwyd_register TARGET_IRL=glewlwyd_mod_user_irl glewlwyd_mod_client_irl glewlwyd_mod_user_multiple_password_irl glewlwyd_mod_user_http glewlwyd_oauth2_irl glewlwyd_oidc_irl glewlwyd_scheme_mail glewlwyd_scheme_otp glewlwyd_scheme_webauthn glewlwyd_scheme_retype_password glewlwyd_scheme_http glewlwyd_scheme_oauth2 TARGET_CERTIFICATE=glewlwyd_scheme_certificate glewlwyd_oidc_client_certificate TARGET_PROFILE_DELETE=glewlwyd_profile_delete TARGET_PROMETHEUS=glewlwyd_prometheus VERBOSE=0 MEMCHECK=0 RUN=1 PARAM_FILE=param.json VALGRIND_COMMAND=valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all CERT=cert all: test clean: rm -f *.o *.log valgrind.txt valgrind-*.txt $(TARGET_ADMIN) $(TARGET_AUTH) $(TARGET_CRUD) $(TARGET_OAUTH2) $(TARGET_OIDC) $(TARGET_IRL) $(TARGET_CERTIFICATE) $(TARGET_REGISTER) $(TARGET_PROFILE_DELETE) $(TARGET_PROMETHEUS) rm -f $(CERT)/server.* $(CERT)/root* $(CERT)/client* $(CERT)/user* $(CERT)/packed* $(CERT)/apple* $(CERT)/certtool.log build: $(TARGET_ADMIN) $(TARGET_AUTH) $(TARGET_CRUD) $(TARGET_OAUTH2) $(TARGET_OIDC) $(TARGET_IRL) $(TARGET_CERTIFICATE) $(TARGET_REGISTER) $(TARGET_PROFILE_DELETE) $(TARGET_PROMETHEUS) unit-tests.o: unit-tests.c unit-tests.h $(CC) $(CFLAGS) -c unit-tests.c %: %.c unit-tests.o $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) test: build test-admin test-auth test-crud test-oauth2 test-oidc test-irl test-register test-profile-delete test-auth: $(TARGET_AUTH) test_glewlwyd_auth_password test_glewlwyd_auth_scheme test_glewlwyd_auth_grant test_glewlwyd_auth_check_scheme test_glewlwyd_auth_scheme_trigger test_glewlwyd_auth_scheme_register test_glewlwyd_auth_profile test_glewlwyd_auth_session_manage test_glewlwyd_auth_profile_get_scheme_available test_glewlwyd_auth_profile_impersonate test-admin: $(TARGET_ADMIN) test_glewlwyd_admin_mod_type test_glewlwyd_admin_mod_user test_glewlwyd_admin_mod_user_auth_scheme test_glewlwyd_admin_mod_client test_glewlwyd_admin_mod_plugin test_glewlwyd_admin_check_scope test_glewlwyd_admin_api_key test_glewlwyd_admin_mod_user_middleware test-crud: $(TARGET_CRUD) test_glewlwyd_crud_user test_glewlwyd_crud_client test_glewlwyd_crud_scope test_glewlwyd_crud_user_middleware test-oauth2: $(TARGET_OAUTH2) test_glewlwyd_oauth2_auth_code test_glewlwyd_oauth2_code test_glewlwyd_oauth2_code_client_confidential test_glewlwyd_oauth2_implicit test_glewlwyd_oauth2_resource_owner_pwd_cred test_glewlwyd_oauth2_resource_owner_pwd_cred_client_confidential test_glewlwyd_oauth2_client_cred test_glewlwyd_oauth2_refresh_token test_glewlwyd_oauth2_refresh_token_client_confidential test_glewlwyd_oauth2_delete_token test_glewlwyd_oauth2_delete_token_client_confidential test_glewlwyd_oauth2_profile test_glewlwyd_oauth2_refresh_manage test_glewlwyd_oauth2_refresh_manage test_glewlwyd_oauth2_refresh_manage_session test_glewlwyd_oauth2_profile_impersonate test_glewlwyd_oauth2_additional_parameters test_glewlwyd_oauth2_client_secret test_glewlwyd_oauth2_code_challenge test_glewlwyd_oauth2_token_introspection test_glewlwyd_oauth2_token_revocation test_glewlwyd_oauth2_device_authorization test_glewlwyd_oauth2_code_replay test_glewlwyd_oauth2_scheme_required test-oidc: $(TARGET_OIDC) test_glewlwyd_oidc_auth_code test_glewlwyd_oidc_code test_glewlwyd_oidc_code_client_confidential test_glewlwyd_oidc_token test_glewlwyd_oidc_resource_owner_pwd_cred test_glewlwyd_oidc_resource_owner_pwd_cred_client_confidential test_glewlwyd_oidc_client_cred test_glewlwyd_oidc_code_idtoken test_glewlwyd_oidc_implicit_id_token_token test_glewlwyd_oidc_implicit_id_token test_glewlwyd_oidc_implicit_none test_glewlwyd_oidc_hybrid_id_token_token_code test_glewlwyd_oidc_hybrid_token_code test_glewlwyd_oidc_hybrid_id_token_code test_glewlwyd_oidc_optional_request_parameters test_glewlwyd_oidc_refresh_token test_glewlwyd_oidc_refresh_token_client_confidential test_glewlwyd_oidc_delete_token test_glewlwyd_oidc_delete_token_client_confidential test_glewlwyd_oidc_refresh_manage test_glewlwyd_oidc_refresh_manage test_glewlwyd_oidc_refresh_manage_session test_glewlwyd_oidc_userinfo test_glewlwyd_oidc_additional_parameters test_glewlwyd_oidc_only_no_refresh test_glewlwyd_oidc_discovery test_glewlwyd_oidc_client_secret test_glewlwyd_oidc_request_jwt test_glewlwyd_oidc_subject_type test_glewlwyd_oidc_address_claim test_glewlwyd_oidc_claims_scopes test_glewlwyd_oidc_claim_request test_glewlwyd_oidc_code_challenge test_glewlwyd_oidc_token_introspection test_glewlwyd_oidc_token_revocation test_glewlwyd_oidc_client_registration test_glewlwyd_oidc_jwt_encrypted test_glewlwyd_oidc_jwks_config test_glewlwyd_oidc_session_management test_glewlwyd_oidc_device_authorization test_glewlwyd_oidc_refresh_token_one_use test_glewlwyd_oidc_client_registration_management test_glewlwyd_oidc_code_replay test_glewlwyd_oidc_scheme_required test_glewlwyd_oidc_dpop test_glewlwyd_oidc_resource test_glewlwyd_oidc_rich_auth_requests test_glewlwyd_oidc_pushed_auth_requests test_glewlwyd_oidc_reduced_scope test_glewlwyd_oidc_all_algs test_glewlwyd_oidc_ciba test_glewlwyd_oidc_auth_iss_is test_glewlwyd_oidc_jarm test_glewlwyd_oidc_fapi test-certificate: $(TARGET_CERTIFICATE) test_glewlwyd_scheme_certificate test_glewlwyd_oidc_client_certificate test-register: $(TARGET_REGISTER) test_glewlwyd_register test-profile-delete: $(TARGET_PROFILE_DELETE) test_glewlwyd_profile_delete test-prometheus: $(TARGET_PROMETHEUS) test_glewlwyd_prometheus test-irl: $(TARGET_IRL) test_glewlwyd_mod_user_http test_glewlwyd_scheme_http test_glewlwyd_scheme_mail test_glewlwyd_scheme_otp test_glewlwyd_scheme_webauthn test_glewlwyd_scheme_retype_password test_glewlwyd_scheme_oauth2 @for JSON_FILE in mod_user_*.json; \ do $(MAKE) test_glewlwyd_mod_user_irl PARAM_FILE=$$JSON_FILE $*; \ done @for JSON_FILE in mod_multiple_password_*.json; \ do $(MAKE) test_glewlwyd_mod_user_multiple_password_irl PARAM_FILE=$$JSON_FILE $*; \ done @for JSON_FILE in mod_client_*.json; \ do $(MAKE) test_glewlwyd_mod_client_irl PARAM_FILE=$$JSON_FILE $*; \ done @for JSON_FILE in plugin_oauth2_*.json; \ do $(MAKE) test_glewlwyd_oauth2_irl PARAM_FILE=$$JSON_FILE $*; \ done @for JSON_FILE in plugin_oidc_*.json; \ do $(MAKE) test_glewlwyd_oidc_irl PARAM_FILE=$$JSON_FILE $*; \ done test_%_irl: %_irl @if [ "$(VERBOSE)" = "0" ] && [ "$(MEMCHECK)" = "0" ]; then \ echo "Run $^ with file $(PARAM_FILE)"; \ LD_LIBRARY_PATH=. ./run_test.sh ./$^ $(PARAM_FILE); \ mv $^.log $^-$(PARAM_FILE).log; \ elif [ "$(MEMCHECK)" = "0" ]; then \ LD_LIBRARY_PATH=. ./$^ $(PARAM_FILE); \ else \ CK_FORK=no LD_LIBRARY_PATH=. $(VALGRIND_COMMAND) ./$^ $(PARAM_FILE) 2>valgrind-$@.txt; \ fi test_%: % @if [ "$(VERBOSE)" = "0" ] && [ "$(MEMCHECK)" = "0" ]; then \ LD_LIBRARY_PATH=. ./run_test.sh ./$^ $(PARAM); \ elif [ "$(MEMCHECK)" = "0" ]; then \ LD_LIBRARY_PATH=. ./$^ $(PARAM); \ else \ CK_FORK=no LD_LIBRARY_PATH=. $(VALGRIND_COMMAND) ./$^ $(PARAM) 2>valgrind-$@.txt; \ fi glewlwyd-2.6.1/test/README.md000066400000000000000000000010731415646314000156210ustar00rootroot00000000000000# Glewlwyd unit tests These unit tests are based on the [check framework](http://check.sourceforge.net/). You must install check library first (on Debian/Ubuntu you can do this with `apt-get install check`). All the unit tests test the behavior of the functionalities available in the REST API. Which means to run a valid test case, you must have a running instance of Glewlwyd on localhost with the data initialized by the script `init.sql`. When the valid test instance is available, you can build and run each test case. Run `make test` to run all automatic tests. glewlwyd-2.6.1/test/cert/000077500000000000000000000000001415646314000152765ustar00rootroot00000000000000glewlwyd-2.6.1/test/cert/create-cert.sh000077500000000000000000000432031415646314000200350ustar00rootroot00000000000000#!/bin/sh # # Glewlwyd SSO # # Create certificates # # Public domain, no copyright. Use at your own risk. # DEST=../test/cert RET=0 case "$OSTYPE" in *"darwin"*) # Apple has its own certtool which is incompatible. GnuTLS' certtool is renamed as # gnutls-certtool in MacPorts/homebrew. CERTTOOL=gnutls-certtool;; *) CERTTOOL=certtool;; esac $CERTTOOL --generate-privkey --key-type=ecdsa >/dev/null 2>&1 if [ $? -eq 0 ]; then ECDSA="--key-type=ecdsa" else ECDSA="--ecdsa" fi # clean old certs rm -f $DEST/server.* $DEST/root* $DEST/client* $DEST/user* $DEST/packed* $DEST/apple* echo >> $DEST/certtool.log echo Generate Glewlwyd test certificates >> $DEST/certtool.log echo >> $DEST/certtool.log # www cert $CERTTOOL --generate-privkey --outfile $DEST/server.key --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "server.key \033[0;32mOK\033[0m\n" else printf "server.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-self-signed --load-privkey $DEST/server.key --outfile $DEST/server.crt --template $DEST/template-server.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "server.crt \033[0;32mOK\033[0m\n" else printf "server.crt \033[0;31mError\033[0m\n" RET=$STATUS fi # CA root $CERTTOOL --generate-privkey --outfile $DEST/root1.key --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "root1.key \033[0;32mOK\033[0m\n" else printf "root1.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-self-signed --load-privkey $DEST/root1.key --outfile $DEST/root1.crt --template $DEST/template-ca.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "root1.crt \033[0;32mOK\033[0m\n" else printf "root1.crt \033[0;31mError\033[0m\n" RET=$STATUS fi # user 1 $CERTTOOL --generate-privkey --outfile $DEST/user1.key $ECDSA --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "user1.key \033[0;32mOK\033[0m\n" else printf "user1.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-certificate --load-privkey $DEST/user1.key --load-ca-certificate $DEST/root1.crt --load-ca-privkey $DEST/root1.key --outfile $DEST/user1.crt --template $DEST/template-user.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "user1.crt \033[0;32mOK\033[0m\n" else printf "user1.crt \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --certificate-info --infile $DEST/user1.crt --outder | base64 > $DEST/user1.crt.der 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "user1.crt.der \033[0;32mOK\033[0m\n" else printf "user1.crt.der \033[0;31mError\033[0m\n" RET=$STATUS fi # user 2 $CERTTOOL --generate-privkey --outfile $DEST/user2.key $ECDSA --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "user2.key \033[0;32mOK\033[0m\n" else printf "user2.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-certificate --load-privkey $DEST/user2.key --load-ca-certificate $DEST/root1.crt --load-ca-privkey $DEST/root1.key --outfile $DEST/user2.crt --template $DEST/template-user.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "user2.crt \033[0;32mOK\033[0m\n" else printf "user2.crt \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --certificate-info --infile $DEST/user2.crt --outder | base64 > $DEST/user2.crt.der 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "user2.crt.der \033[0;32mOK\033[0m\n" else printf "user2.crt.der \033[0;31mError\033[0m\n" RET=$STATUS fi # CA root 2 $CERTTOOL --generate-privkey --outfile $DEST/root2.key --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "root2.key \033[0;32mOK\033[0m\n" else printf "root2.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-self-signed --load-privkey $DEST/root2.key --outfile $DEST/root2.crt --template $DEST/template-ca2.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "root2.crt \033[0;32mOK\033[0m\n" else printf "root2.crt \033[0;31mError\033[0m\n" RET=$STATUS fi # user 3 $CERTTOOL --generate-privkey --outfile $DEST/user3.key $ECDSA --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "user3.key \033[0;32mOK\033[0m\n" else printf "user3.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-certificate --load-privkey $DEST/user3.key --load-ca-certificate $DEST/root2.crt --load-ca-privkey $DEST/root2.key --outfile $DEST/user3.crt --template $DEST/template-user.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "user3.crt \033[0;32mOK\033[0m\n" else printf "user3.crt \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --certificate-info --infile $DEST/user3.crt --outder | base64 > $DEST/user3.crt.der 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "user3.crt.der \033[0;32mOK\033[0m\n" else printf "user3.crt.der \033[0;31mError\033[0m\n" RET=$STATUS fi # CA packed $CERTTOOL --generate-privkey --outfile $DEST/packed.key --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "packed.key \033[0;32mOK\033[0m\n" else printf "packed.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-self-signed --load-privkey $DEST/packed.key --outfile $DEST/packed.crt --template $DEST/template-ca-packed.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "packed.crt \033[0;32mOK\033[0m\n" else printf "packed.crt \033[0;31mError\033[0m\n" RET=$STATUS fi # CA packed 2 $CERTTOOL --generate-privkey --outfile $DEST/packed-2.key --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "packed-2.key \033[0;32mOK\033[0m\n" else printf "packed-2.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-self-signed --load-privkey $DEST/packed-2.key --outfile $DEST/packed-2.crt --template $DEST/template-ca-packed.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "packed-2.crt \033[0;32mOK\033[0m\n" else printf "packed-2.crt \033[0;31mError\033[0m\n" RET=$STATUS fi # client packed valid $CERTTOOL --generate-privkey --outfile $DEST/client-p-v.key $ECDSA --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client-p-v.key \033[0;32mOK\033[0m\n" else printf "client-p-v.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-certificate --load-privkey $DEST/client-p-v.key --load-ca-certificate $DEST/packed.crt --load-ca-privkey $DEST/packed.key --outfile $DEST/client-p-v.crt --template $DEST/template-client-packed.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client-p-v.crt \033[0;32mOK\033[0m\n" else printf "client-p-v.crt \033[0;31mError\033[0m\n" RET=$STATUS fi # client packed invalid ou $CERTTOOL --generate-privkey --outfile $DEST/client-p-iu.key $ECDSA --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client-p-iu.key \033[0;32mOK\033[0m\n" else printf "client-p-iu.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-certificate --load-privkey $DEST/client-p-iu.key --load-ca-certificate $DEST/packed.crt --load-ca-privkey $DEST/packed.key --outfile $DEST/client-p-iu.crt --template $DEST/template-client-packed-invalid-ou.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client-p-iu.crt \033[0;32mOK\033[0m\n" else printf "client-p-iu.crt \033[0;31mError\033[0m\n" RET=$STATUS fi # client packed invalid c $CERTTOOL --generate-privkey --outfile $DEST/client-p-ic.key $ECDSA --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client-p-ic.key \033[0;32mOK\033[0m\n" else printf "client-p-ic.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-certificate --load-privkey $DEST/client-p-ic.key --load-ca-certificate $DEST/packed.crt --load-ca-privkey $DEST/packed.key --outfile $DEST/client-p-ic.crt --template $DEST/template-client-packed-invalid-c.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client-p-ic.crt \033[0;32mOK\033[0m\n" else printf "client-p-ic.crt \033[0;31mError\033[0m\n" RET=$STATUS fi # client packed c not present $CERTTOOL --generate-privkey --outfile $DEST/client-p-mc.key $ECDSA --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client-p-mc.key \033[0;32mOK\033[0m\n" else printf "client-p-mc.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-certificate --load-privkey $DEST/client-p-mc.key --load-ca-certificate $DEST/packed.crt --load-ca-privkey $DEST/packed.key --outfile $DEST/client-p-mc.crt --template $DEST/template-client-packed-missing-c.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client-p-mc.crt \033[0;32mOK\033[0m\n" else printf "client-p-mc.crt \033[0;31mError\033[0m\n" RET=$STATUS fi # client packed o not present $CERTTOOL --generate-privkey --outfile $DEST/client-p-mo.key $ECDSA --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client-p-mo.key \033[0;32mOK\033[0m\n" else printf "client-p-mo.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-certificate --load-privkey $DEST/client-p-mo.key --load-ca-certificate $DEST/packed.crt --load-ca-privkey $DEST/packed.key --outfile $DEST/client-p-mo.crt --template $DEST/template-client-packed-missing-o.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client-p-mo.crt \033[0;32mOK\033[0m\n" else printf "client-p-mo.crt \033[0;31mError\033[0m\n" RET=$STATUS fi # client packed cn not present $CERTTOOL --generate-privkey --outfile $DEST/client-p-mcn.key $ECDSA --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client-p-mcn.key \033[0;32mOK\033[0m\n" else printf "client-p-mcn.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-certificate --load-privkey $DEST/client-p-mcn.key --load-ca-certificate $DEST/packed.crt --load-ca-privkey $DEST/packed.key --outfile $DEST/client-p-mcn.crt --template $DEST/template-client-packed-missing-cn.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client-p-mcn.crt \033[0;32mOK\033[0m\n" else printf "client-p-mcn.crt \033[0;31mError\033[0m\n" RET=$STATUS fi # client packed invalid extension aaguid $CERTTOOL --generate-privkey --outfile $DEST/client-p-ia.key $ECDSA --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client-p-ia.key \033[0;32mOK\033[0m\n" else printf "client-p-ia.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-certificate --load-privkey $DEST/client-p-ia.key --load-ca-certificate $DEST/packed.crt --load-ca-privkey $DEST/packed.key --outfile $DEST/client-p-ia.crt --template $DEST/template-client-packed-invalid-aaguid.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client-p-ia.crt \033[0;32mOK\033[0m\n" else printf "client-p-ia.crt \033[0;31mError\033[0m\n" RET=$STATUS fi # client 1 $CERTTOOL --generate-privkey --outfile $DEST/client1.key --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client1.key \033[0;32mOK\033[0m\n" else printf "client1.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-certificate --load-privkey $DEST/client1.key --load-ca-certificate $DEST/root1.crt --load-ca-privkey $DEST/root1.key --outfile $DEST/client1.crt --template $DEST/template-client.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client1.crt \033[0;32mOK\033[0m\n" else printf "client1.crt \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --certificate-info --infile $DEST/client1.crt --outder | base64 > $DEST/client1.crt.der 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client1.crt.der \033[0;32mOK\033[0m\n" else printf "client1.crt.der \033[0;31mError\033[0m\n" RET=$STATUS fi # client 2 $CERTTOOL --generate-privkey --outfile $DEST/client2.key --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client2.key \033[0;32mOK\033[0m\n" else printf "client2.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-certificate --load-privkey $DEST/client2.key --load-ca-certificate $DEST/root2.crt --load-ca-privkey $DEST/root2.key --outfile $DEST/client2.crt --template $DEST/template-client.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client2.crt \033[0;32mOK\033[0m\n" else printf "client2.crt \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --certificate-info --infile $DEST/client2.crt --outder | base64 > $DEST/client2.crt.der 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client2.crt.der \033[0;32mOK\033[0m\n" else printf "client2.crt.der \033[0;31mError\033[0m\n" RET=$STATUS fi # client self-signed $CERTTOOL --generate-privkey --outfile $DEST/client3.key --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client3.key \033[0;32mOK\033[0m\n" else printf "client3.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-certificate --load-privkey $DEST/client3.key --generate-self-signed --outfile $DEST/client3.crt --template $DEST/template-client.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client3.crt \033[0;32mOK\033[0m\n" else printf "client3.crt \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --certificate-info --infile $DEST/client3.crt --outder | base64 > $DEST/client3.crt.der 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "client3.crt.der \033[0;32mOK\033[0m\n" else printf "client3.crt.der \033[0;31mError\033[0m\n" RET=$STATUS fi # CA apple $CERTTOOL --generate-privkey --outfile $DEST/apple.key $ECDSA --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "apple.key \033[0;32mOK\033[0m\n" else printf "apple.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-self-signed --load-privkey $DEST/apple.key --outfile $DEST/apple.crt --template $DEST/template-ca-apple.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "apple.crt \033[0;32mOK\033[0m\n" else printf "apple.crt \033[0;31mError\033[0m\n" RET=$STATUS fi # CA intermediate apple $CERTTOOL --generate-privkey --outfile $DEST/apple-int.key $ECDSA --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "apple-int.key \033[0;32mOK\033[0m\n" else printf "apple-int.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-certificate --load-privkey $DEST/apple-int.key --load-ca-certificate $DEST/apple.crt --load-ca-privkey $DEST/apple.key --outfile $DEST/apple-int.crt --template $DEST/template-int-apple.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "apple-int.crt \033[0;32mOK\033[0m\n" else printf "apple-int.crt \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --certificate-info --infile $DEST/apple-int.crt --outder | base64 > $DEST/apple-int.crt.der 2>>$DEST/certtool.log # CA intermediate 2 apple $CERTTOOL --generate-privkey --outfile $DEST/apple-int2.key $ECDSA --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "apple-int2.key \033[0;32mOK\033[0m\n" else printf "apple-int2.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-certificate --load-privkey $DEST/apple-int2.key --load-ca-certificate $DEST/apple-int.crt --load-ca-privkey $DEST/apple-int.key --outfile $DEST/apple-int2.crt --template $DEST/template-int-apple.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "apple-int2.crt \033[0;32mOK\033[0m\n" else printf "apple-int2.crt \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --certificate-info --infile $DEST/apple-int.crt --outder | base64 > $DEST/apple-int.crt.der 2>>$DEST/certtool.log # CA apple 2 $CERTTOOL --generate-privkey --outfile $DEST/apple2.key $ECDSA --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "apple2.key \033[0;32mOK\033[0m\n" else printf "apple2.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-self-signed --load-privkey $DEST/apple2.key --outfile $DEST/apple2.crt --template $DEST/template-ca-apple.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "apple2.crt \033[0;32mOK\033[0m\n" else printf "apple2.crt \033[0;31mError\033[0m\n" RET=$STATUS fi # CA intermediate apple 2 $CERTTOOL --generate-privkey --outfile $DEST/apple2-int.key $ECDSA --sec-param High 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "apple2-int.key \033[0;32mOK\033[0m\n" else printf "apple2-int.key \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --generate-certificate --load-privkey $DEST/apple2-int.key --load-ca-certificate $DEST/apple2.crt --load-ca-privkey $DEST/apple2.key --outfile $DEST/apple2-int.crt --template $DEST/template-int-apple.cfg 2>>$DEST/certtool.log STATUS=$? if [ $STATUS -eq 0 ]; then printf "apple2-int.crt \033[0;32mOK\033[0m\n" else printf "apple2-int.crt \033[0;31mError\033[0m\n" RET=$STATUS fi $CERTTOOL --certificate-info --infile $DEST/apple-int.crt --outder | base64 > $DEST/apple-int.crt.der 2>>$DEST/certtool.log exit $RET glewlwyd-2.6.1/test/cert/glewlwyd-cert-ci.conf000066400000000000000000000057501415646314000213360ustar00rootroot00000000000000# # # Glewlwyd SSO Authorization Server # # Copyright 2016-2019 Nicolas Mora # Gnu Public License V3 # # # port to open for remote commands port=4593 # external url to access to this instance external_url="https://localhost:4593" # login url relative to external url login_url="login.html" # url prefix url_prefix="api" # path to static files for /webapp url static_files_path="../webapp" # access-control-allow-origin value allow_origin="*" # log mode (console, syslog, journald, file) log_mode="file" # log level: NONE, ERROR, WARNING, INFO, DEBUG log_level="DEBUG" # output to log file (required if log_mode is file) log_file="/tmp/glewlwyd-https.log" # cookie domain #cookie_domain="localhost" # cookie_secure, this options SHOULD be set to 1, set this to 0 to test glewlwyd on insecure connection http instead of https cookie_secure=0 # session expiration, default is 4 weeks session_expiration=2419200 # session key session_key="GLEWLWYD2_SESSION_ID" # admin scope name admin_scope="g_admin" # profile scope name profile_scope="g_profile" # user_module path user_module_path="/usr/lib/glewlwyd/user" # user_middleware_module path user_middleware_module_path="/usr/lib/glewlwyd/user_middleware" # client_module path client_module_path="/usr/lib/glewlwyd/client" # user_auth_scheme_module path user_auth_scheme_module_path="/usr/lib/glewlwyd/scheme" # plugin_module path plugin_module_path="/usr/lib/glewlwyd/plugin" # TLS/SSL configuration values use_secure_connection=true secure_connection_key_file="../test/cert/server.key" secure_connection_pem_file="../test/cert/server.crt" secure_connection_ca_file="../test/cert/root1.crt" # Algorithms available are SHA1, SHA256, SHA512, MD5, default is SHA256 hash_algorithm = "SHA256" # MariaDB/Mysql database connection #database = #{ # type = "mariadb" # host = "localhost" # user = "glewlwyd" # password = "glewlwyd" # dbname = "glewlwyd" # port = 0 #} # SQLite database connection database = { type = "sqlite3" path = "/tmp/glewlwyd.db" }; # SQLite database connection #database = #{ # type = "postgre" # conninfo = "host=localhost dbname=glewlwyd user=glewlwyd password=glewlwyd" #}; # mime types for webapp files static_files_mime_types = ( { extension = ".html" mime_type = "text/html" }, { extension = ".css" mime_type = "text/css" }, { extension = ".js" mime_type = "application/javascript" }, { extension = ".json" mime_type = "application/json" }, { extension = ".png" mime_type = "image/png" }, { extension = ".jpg" mime_type = "image/jpeg" }, { extension = ".jpeg" mime_type = "image/jpeg" }, { extension = ".ttf" mime_type = "font/ttf" }, { extension = ".woff" mime_type = "font/woff" }, { extension = ".woff2" mime_type = "font/woff2" }, { extension = ".map" mime_type = "application/octet-stream" }, { extension = ".ico" mime_type = "image/x-icon" } ) glewlwyd-2.6.1/test/cert/template-ca-apple.cfg000066400000000000000000000016561415646314000212620ustar00rootroot00000000000000# X.509 Certificate options # # Source: https://help.ubuntu.com/community/GnuTLS # # DN options # The organization of the subject. organization = "not-apple" # The common name of the certificate owner. cn = "glewlwyd_apple_ca" # In how many days, counting from today, this certificate will expire. # Use -1 if there is no expiration date. expiration_days = 700 # Whether this is a CA certificate or not ca # Whether this certificate will be used to sign data (needed # in TLS DHE ciphersuites). This is the digitalSignature flag # in RFC5280 terminology. signing_key # Whether this certificate will be used to encrypt data (needed # in TLS RSA ciphersuites). Note that it is preferred to use different # keys for encryption and signing. This is the keyEncipherment flag # in RFC5280 terminology. encryption_key # Whether this key will be used to sign other certificates. The # keyCertSign flag in RFC5280 terminology. cert_signing_key glewlwyd-2.6.1/test/cert/template-ca-packed.cfg000066400000000000000000000016601415646314000214030ustar00rootroot00000000000000# X.509 Certificate options # # Source: https://help.ubuntu.com/community/GnuTLS # # DN options # The organization of the subject. organization = "babelouest" # The common name of the certificate owner. cn = "glewlwyd_packed_ca" # In how many days, counting from today, this certificate will expire. # Use -1 if there is no expiration date. expiration_days = 700 # Whether this is a CA certificate or not ca # Whether this certificate will be used to sign data (needed # in TLS DHE ciphersuites). This is the digitalSignature flag # in RFC5280 terminology. signing_key # Whether this certificate will be used to encrypt data (needed # in TLS RSA ciphersuites). Note that it is preferred to use different # keys for encryption and signing. This is the keyEncipherment flag # in RFC5280 terminology. encryption_key # Whether this key will be used to sign other certificates. The # keyCertSign flag in RFC5280 terminology. cert_signing_key glewlwyd-2.6.1/test/cert/template-ca.cfg000066400000000000000000000026421415646314000201570ustar00rootroot00000000000000# X.509 Certificate options # # Source: https://help.ubuntu.com/community/GnuTLS # # DN options # The organization of the subject. organization = "babelouest" # The common name of the certificate owner. cn = "glewlwyd_1" # In how many days, counting from today, this certificate will expire. # Use -1 if there is no expiration date. expiration_days = 700 # Whether this is a CA certificate or not ca # Whether this certificate will be used to sign data (needed # in TLS DHE ciphersuites). This is the digitalSignature flag # in RFC5280 terminology. signing_key # Whether this certificate will be used to encrypt data (needed # in TLS RSA ciphersuites). Note that it is preferred to use different # keys for encryption and signing. This is the keyEncipherment flag # in RFC5280 terminology. encryption_key # Whether this key will be used to sign other certificates. The # keyCertSign flag in RFC5280 terminology. cert_signing_key # Whether this key will be used to sign CRLs. The # cRLSign flag in RFC5280 terminology. #crl_signing_key # The keyAgreement flag of RFC5280. It's purpose is loosely # defined. Not use it unless required by a protocol. #key_agreement # The dataEncipherment flag of RFC5280. It's purpose is loosely # defined. Not use it unless required by a protocol. #data_encipherment # The nonRepudiation flag of RFC5280. It's purpose is loosely # defined. Not use it unless required by a protocol. #non_repudiation glewlwyd-2.6.1/test/cert/template-ca2.cfg000066400000000000000000000026421415646314000202410ustar00rootroot00000000000000# X.509 Certificate options # # Source: https://help.ubuntu.com/community/GnuTLS # # DN options # The organization of the subject. organization = "babelouest" # The common name of the certificate owner. cn = "glewlwyd_2" # In how many days, counting from today, this certificate will expire. # Use -1 if there is no expiration date. expiration_days = 700 # Whether this is a CA certificate or not ca # Whether this certificate will be used to sign data (needed # in TLS DHE ciphersuites). This is the digitalSignature flag # in RFC5280 terminology. signing_key # Whether this certificate will be used to encrypt data (needed # in TLS RSA ciphersuites). Note that it is preferred to use different # keys for encryption and signing. This is the keyEncipherment flag # in RFC5280 terminology. encryption_key # Whether this key will be used to sign other certificates. The # keyCertSign flag in RFC5280 terminology. cert_signing_key # Whether this key will be used to sign CRLs. The # cRLSign flag in RFC5280 terminology. #crl_signing_key # The keyAgreement flag of RFC5280. It's purpose is loosely # defined. Not use it unless required by a protocol. #key_agreement # The dataEncipherment flag of RFC5280. It's purpose is loosely # defined. Not use it unless required by a protocol. #data_encipherment # The nonRepudiation flag of RFC5280. It's purpose is loosely # defined. Not use it unless required by a protocol. #non_repudiation glewlwyd-2.6.1/test/cert/template-client-packed-invalid-aaguid.cfg000066400000000000000000000017071415646314000251540ustar00rootroot00000000000000# X.509 Certificate options # # Source: https://help.ubuntu.com/community/GnuTLS # # DN options # The organization of the subject. organization = "babelouest" unit = "Authenticator Attestation" country = CA # The common name of the certificate owner. cn = "glewlwyd_packed" # In how many days, counting from today, this certificate will expire. # Use -1 if there is no expiration date. expiration_days = 350 #### Key usage # Whether this certificate will be used to sign data (needed # in TLS DHE ciphersuites). This is the digitalSignature flag # in RFC5280 terminology. signing_key # Whether this certificate will be used to encrypt data (needed # in TLS RSA ciphersuites). Note that it is preferred to use different # keys for encryption and signing. This is the keyEncipherment flag # in RFC5280 terminology. encryption_key #### Extended key usage (key purposes) add_extension = "1.3.6.1.4.1.45724.1.1.4 octet_string(0x42434445464748495051525354555658)" glewlwyd-2.6.1/test/cert/template-client-packed-invalid-c.cfg000066400000000000000000000017071415646314000241440ustar00rootroot00000000000000# X.509 Certificate options # # Source: https://help.ubuntu.com/community/GnuTLS # # DN options # The organization of the subject. organization = "babelouest" unit = "Authenticator Attestation" country = ZZ # The common name of the certificate owner. cn = "glewlwyd_packed" # In how many days, counting from today, this certificate will expire. # Use -1 if there is no expiration date. expiration_days = 350 #### Key usage # Whether this certificate will be used to sign data (needed # in TLS DHE ciphersuites). This is the digitalSignature flag # in RFC5280 terminology. signing_key # Whether this certificate will be used to encrypt data (needed # in TLS RSA ciphersuites). Note that it is preferred to use different # keys for encryption and signing. This is the keyEncipherment flag # in RFC5280 terminology. encryption_key #### Extended key usage (key purposes) add_extension = "1.3.6.1.4.1.45724.1.1.4 octet_string(0x42434445464748495051525354555657)" glewlwyd-2.6.1/test/cert/template-client-packed-invalid-ou.cfg000066400000000000000000000017211415646314000243410ustar00rootroot00000000000000# X.509 Certificate options # # Source: https://help.ubuntu.com/community/GnuTLS # # DN options # The organization of the subject. organization = "babelouest" unit = "Authenticator Attestation Cornholio" country = CA # The common name of the certificate owner. cn = "glewlwyd_packed" # In how many days, counting from today, this certificate will expire. # Use -1 if there is no expiration date. expiration_days = 350 #### Key usage # Whether this certificate will be used to sign data (needed # in TLS DHE ciphersuites). This is the digitalSignature flag # in RFC5280 terminology. signing_key # Whether this certificate will be used to encrypt data (needed # in TLS RSA ciphersuites). Note that it is preferred to use different # keys for encryption and signing. This is the keyEncipherment flag # in RFC5280 terminology. encryption_key #### Extended key usage (key purposes) add_extension = "1.3.6.1.4.1.45724.1.1.4 octet_string(0x42434445464748495051525354555657)" glewlwyd-2.6.1/test/cert/template-client-packed-missing-c.cfg000066400000000000000000000017101415646314000241610ustar00rootroot00000000000000# X.509 Certificate options # # Source: https://help.ubuntu.com/community/GnuTLS # # DN options # The organization of the subject. organization = "babelouest" unit = "Authenticator Attestation" #country = CA # The common name of the certificate owner. cn = "glewlwyd_packed" # In how many days, counting from today, this certificate will expire. # Use -1 if there is no expiration date. expiration_days = 350 #### Key usage # Whether this certificate will be used to sign data (needed # in TLS DHE ciphersuites). This is the digitalSignature flag # in RFC5280 terminology. signing_key # Whether this certificate will be used to encrypt data (needed # in TLS RSA ciphersuites). Note that it is preferred to use different # keys for encryption and signing. This is the keyEncipherment flag # in RFC5280 terminology. encryption_key #### Extended key usage (key purposes) add_extension = "1.3.6.1.4.1.45724.1.1.4 octet_string(0x42434445464748495051525354555657)" glewlwyd-2.6.1/test/cert/template-client-packed-missing-cn.cfg000066400000000000000000000017101415646314000243370ustar00rootroot00000000000000# X.509 Certificate options # # Source: https://help.ubuntu.com/community/GnuTLS # # DN options # The organization of the subject. organization = "babelouest" unit = "Authenticator Attestation" country = CA # The common name of the certificate owner. #cn = "glewlwyd_packed" # In how many days, counting from today, this certificate will expire. # Use -1 if there is no expiration date. expiration_days = 350 #### Key usage # Whether this certificate will be used to sign data (needed # in TLS DHE ciphersuites). This is the digitalSignature flag # in RFC5280 terminology. signing_key # Whether this certificate will be used to encrypt data (needed # in TLS RSA ciphersuites). Note that it is preferred to use different # keys for encryption and signing. This is the keyEncipherment flag # in RFC5280 terminology. encryption_key #### Extended key usage (key purposes) add_extension = "1.3.6.1.4.1.45724.1.1.4 octet_string(0x42434445464748495051525354555657)" glewlwyd-2.6.1/test/cert/template-client-packed-missing-o.cfg000066400000000000000000000017101415646314000241750ustar00rootroot00000000000000# X.509 Certificate options # # Source: https://help.ubuntu.com/community/GnuTLS # # DN options # The organization of the subject. #organization = "babelouest" unit = "Authenticator Attestation" country = CA # The common name of the certificate owner. cn = "glewlwyd_packed" # In how many days, counting from today, this certificate will expire. # Use -1 if there is no expiration date. expiration_days = 350 #### Key usage # Whether this certificate will be used to sign data (needed # in TLS DHE ciphersuites). This is the digitalSignature flag # in RFC5280 terminology. signing_key # Whether this certificate will be used to encrypt data (needed # in TLS RSA ciphersuites). Note that it is preferred to use different # keys for encryption and signing. This is the keyEncipherment flag # in RFC5280 terminology. encryption_key #### Extended key usage (key purposes) add_extension = "1.3.6.1.4.1.45724.1.1.4 octet_string(0x42434445464748495051525354555657)" glewlwyd-2.6.1/test/cert/template-client-packed.cfg000066400000000000000000000017071415646314000223000ustar00rootroot00000000000000# X.509 Certificate options # # Source: https://help.ubuntu.com/community/GnuTLS # # DN options # The organization of the subject. organization = "babelouest" unit = "Authenticator Attestation" country = CA # The common name of the certificate owner. cn = "glewlwyd_packed" # In how many days, counting from today, this certificate will expire. # Use -1 if there is no expiration date. expiration_days = 350 #### Key usage # Whether this certificate will be used to sign data (needed # in TLS DHE ciphersuites). This is the digitalSignature flag # in RFC5280 terminology. signing_key # Whether this certificate will be used to encrypt data (needed # in TLS RSA ciphersuites). Note that it is preferred to use different # keys for encryption and signing. This is the keyEncipherment flag # in RFC5280 terminology. encryption_key #### Extended key usage (key purposes) add_extension = "1.3.6.1.4.1.45724.1.1.4 octet_string(0x42434445464748495051525354555657)" glewlwyd-2.6.1/test/cert/template-client.cfg000066400000000000000000000055761415646314000210630ustar00rootroot00000000000000# X.509 Certificate options # # Source: https://help.ubuntu.com/community/GnuTLS # # DN options # The organization of the subject. organization = "babelouest" # The common name of the certificate owner. cn = "client-tls" # In how many days, counting from today, this certificate will expire. # Use -1 if there is no expiration date. expiration_days = 350 dns_name = "client.glewlwyd.tld" uri = "https://client.glewlwyd.tld" ip_address = "1.2.3.4" ip_address = "2001:db8:85a3:8d3:1319:8a2e:370:7348" ip_address = "::1" email = "client-tls@client.glewlwyd.tld" #### Key usage # The following key usage flags are used by CAs and end certificates # Whether this certificate will be used to sign data (needed # in TLS DHE ciphersuites). This is the digitalSignature flag # in RFC5280 terminology. signing_key # Whether this certificate will be used to encrypt data (needed # in TLS RSA ciphersuites). Note that it is preferred to use different # keys for encryption and signing. This is the keyEncipherment flag # in RFC5280 terminology. encryption_key # Whether this key will be used to sign other certificates. The # keyCertSign flag in RFC5280 terminology. #cert_signing_key # Whether this key will be used to sign CRLs. The # cRLSign flag in RFC5280 terminology. #crl_signing_key # The keyAgreement flag of RFC5280. It's purpose is loosely # defined. Not use it unless required by a protocol. #key_agreement # The dataEncipherment flag of RFC5280. It's purpose is loosely # defined. Not use it unless required by a protocol. #data_encipherment # The nonRepudiation flag of RFC5280. It's purpose is loosely # defined. Not use it unless required by a protocol. #non_repudiation #### Extended key usage (key purposes) # The following extensions are used in an end certificate # to clarify its purpose. Some CAs also use it to indicate # the types of certificates they are purposed to sign. # Whether this certificate will be used for a TLS client; # this sets the id-kp-serverAuth (1.3.6.1.5.5.7.3.1) of # extended key usage. tls_www_client # Whether this certificate will be used for a TLS server; # This sets the id-kp-clientAuth (1.3.6.1.5.5.7.3.2) of # extended key usage. #tls_www_server # Whether this key will be used to sign code. This sets the # id-kp-codeSigning (1.3.6.1.5.5.7.3.3) of extended key usage # extension. #code_signing_key # Whether this key will be used to sign OCSP data. This sets the # id-kp-OCSPSigning (1.3.6.1.5.5.7.3.9) of extended key usage extension. #ocsp_signing_key # Whether this key will be used for time stamping. This sets the # id-kp-timeStamping (1.3.6.1.5.5.7.3.8) of extended key usage extension. #time_stamping_key # Whether this key will be used for email protection. This sets the # id-kp-emailProtection (1.3.6.1.5.5.7.3.4) of extended key usage extension. #email_protection_key # Whether this key will be used for IPsec IKE operations (1.3.6.1.5.5.7.3.17). #ipsec_ike_key glewlwyd-2.6.1/test/cert/template-int-apple.cfg000066400000000000000000000016611415646314000214650ustar00rootroot00000000000000# X.509 Certificate options # # Source: https://help.ubuntu.com/community/GnuTLS # # DN options # The organization of the subject. organization = "not-ipod" # The common name of the certificate owner. cn = "glewlwyd_apple_int_ca" # In how many days, counting from today, this certificate will expire. # Use -1 if there is no expiration date. expiration_days = 700 # Whether this is a CA certificate or not ca # Whether this certificate will be used to sign data (needed # in TLS DHE ciphersuites). This is the digitalSignature flag # in RFC5280 terminology. signing_key # Whether this certificate will be used to encrypt data (needed # in TLS RSA ciphersuites). Note that it is preferred to use different # keys for encryption and signing. This is the keyEncipherment flag # in RFC5280 terminology. encryption_key # Whether this key will be used to sign other certificates. The # keyCertSign flag in RFC5280 terminology. cert_signing_key glewlwyd-2.6.1/test/cert/template-server.cfg000066400000000000000000000025661415646314000211070ustar00rootroot00000000000000# X.509 Certificate options # # Source: https://help.ubuntu.com/community/GnuTLS # # DN options # The organization of the subject. organization = "babelouest" # The common name of the certificate owner. cn = "glewlwyd_www" # In how many days, counting from today, this certificate will expire. # Use -1 if there is no expiration date. expiration_days = 700 # Whether this certificate will be used to sign data (needed # in TLS DHE ciphersuites). This is the digitalSignature flag # in RFC5280 terminology. signing_key # Whether this certificate will be used to encrypt data (needed # in TLS RSA ciphersuites). Note that it is preferred to use different # keys for encryption and signing. This is the keyEncipherment flag # in RFC5280 terminology. encryption_key # Whether this key will be used to sign other certificates. The # keyCertSign flag in RFC5280 terminology. cert_signing_key # Whether this key will be used to sign CRLs. The # cRLSign flag in RFC5280 terminology. #crl_signing_key # The keyAgreement flag of RFC5280. It's purpose is loosely # defined. Not use it unless required by a protocol. #key_agreement # The dataEncipherment flag of RFC5280. It's purpose is loosely # defined. Not use it unless required by a protocol. #data_encipherment # The nonRepudiation flag of RFC5280. It's purpose is loosely # defined. Not use it unless required by a protocol. #non_repudiation glewlwyd-2.6.1/test/cert/template-user.cfg000066400000000000000000000052571415646314000205570ustar00rootroot00000000000000# X.509 Certificate options # # Source: https://help.ubuntu.com/community/GnuTLS # # DN options # The organization of the subject. organization = "babelouest" # The common name of the certificate owner. cn = "Dave Lopper" # In how many days, counting from today, this certificate will expire. # Use -1 if there is no expiration date. expiration_days = 350 #### Key usage # The following key usage flags are used by CAs and end certificates # Whether this certificate will be used to sign data (needed # in TLS DHE ciphersuites). This is the digitalSignature flag # in RFC5280 terminology. signing_key # Whether this certificate will be used to encrypt data (needed # in TLS RSA ciphersuites). Note that it is preferred to use different # keys for encryption and signing. This is the keyEncipherment flag # in RFC5280 terminology. encryption_key # Whether this key will be used to sign other certificates. The # keyCertSign flag in RFC5280 terminology. #cert_signing_key # Whether this key will be used to sign CRLs. The # cRLSign flag in RFC5280 terminology. #crl_signing_key # The keyAgreement flag of RFC5280. It's purpose is loosely # defined. Not use it unless required by a protocol. #key_agreement # The dataEncipherment flag of RFC5280. It's purpose is loosely # defined. Not use it unless required by a protocol. #data_encipherment # The nonRepudiation flag of RFC5280. It's purpose is loosely # defined. Not use it unless required by a protocol. #non_repudiation #### Extended key usage (key purposes) # The following extensions are used in an end certificate # to clarify its purpose. Some CAs also use it to indicate # the types of certificates they are purposed to sign. # Whether this certificate will be used for a TLS client; # this sets the id-kp-serverAuth (1.3.6.1.5.5.7.3.1) of # extended key usage. tls_www_client # Whether this certificate will be used for a TLS server; # This sets the id-kp-clientAuth (1.3.6.1.5.5.7.3.2) of # extended key usage. #tls_www_server # Whether this key will be used to sign code. This sets the # id-kp-codeSigning (1.3.6.1.5.5.7.3.3) of extended key usage # extension. #code_signing_key # Whether this key will be used to sign OCSP data. This sets the # id-kp-OCSPSigning (1.3.6.1.5.5.7.3.9) of extended key usage extension. #ocsp_signing_key # Whether this key will be used for time stamping. This sets the # id-kp-timeStamping (1.3.6.1.5.5.7.3.8) of extended key usage extension. #time_stamping_key # Whether this key will be used for email protection. This sets the # id-kp-emailProtection (1.3.6.1.5.5.7.3.4) of extended key usage extension. #email_protection_key # Whether this key will be used for IPsec IKE operations (1.3.6.1.5.5.7.3.17). #ipsec_ike_key glewlwyd-2.6.1/test/glewlwyd-ci.conf000066400000000000000000000064601415646314000174450ustar00rootroot00000000000000# # # Glewlwyd SSO Authorization Server # # Copyright 2016-2020 Nicolas Mora # License MIT # # # port to open for remote commands port=4593 # external url to access to this instance external_url="http://localhost:4593" # login url relative to external url login_url="login.html" # url prefix url_prefix="api" # path to static files for /webapp url static_files_path="/usr/share/glewlwyd/webapp/" # access-control-allow-origin value allow_origin="*" # log mode (console, syslog, journald, file) log_mode="file" # log level: NONE, ERROR, WARNING, INFO, DEBUG log_level="DEBUG" # output to log file (required if log_mode is file) log_file="/tmp/glewlwyd.log" # cookie domain #cookie_domain="localhost" # cookie_secure, this options SHOULD be set to 1, set this to 0 to test glewlwyd on insecure connection http instead of https cookie_secure=0 # session expiration, default is 4 weeks session_expiration=2419200 # session key session_key="GLEWLWYD2_SESSION_ID" # admin scope name admin_scope="g_admin" # profile scope name profile_scope="g_profile" # user_module path user_module_path="/usr/lib/glewlwyd/user" # user_middleware_module path user_middleware_module_path="/usr/lib/glewlwyd/user_middleware" # client_module path client_module_path="/usr/lib/glewlwyd/client" # user_auth_scheme_module path user_auth_scheme_module_path="/usr/lib/glewlwyd/scheme" # plugin_module path plugin_module_path="/usr/lib/glewlwyd/plugin" # TLS/SSL configuration values use_secure_connection=false secure_connection_key_file="/usr/local/etc/glewlwyd/cert.key" secure_connection_pem_file="/usr/local/etc/glewlwyd/cert.pem" # Algorithms available are SHA1, SHA256, SHA512, MD5, default is SHA256 hash_algorithm = "SHA256" # MariaDB/Mysql database connection #database = #{ # type = "mariadb" # host = "localhost" # user = "glewlwyd" # password = "glewlwyd" # dbname = "glewlwyd" # port = 0 #} # SQLite database connection database = { type = "sqlite3" path = "/tmp/glewlwyd.db" }; # SQLite database connection #database = #{ # type = "postgre" # conninfo = "host=localhost dbname=glewlwyd user=glewlwyd password=glewlwyd" #}; # mime types for webapp files static_files_mime_types = ( { extension = ".html" mime_type = "text/html" compress = 1 }, { extension = ".css" mime_type = "text/css" compress = 1 }, { extension = ".js" mime_type = "application/javascript" compress = 1 }, { extension = ".json" mime_type = "application/json" compress = 1 }, { extension = ".png" mime_type = "image/png" compress = 0 }, { extension = ".jpg" mime_type = "image/jpeg" compress = 0 }, { extension = ".jpeg" mime_type = "image/jpeg" compress = 0 }, { extension = ".ttf" mime_type = "font/ttf" compress = 0 }, { extension = ".woff" mime_type = "font/woff" compress = 0 }, { extension = ".woff2" mime_type = "font/woff2" compress = 0 }, { extension = ".otf" mime_type = "font/otf" compress = 0 }, { extension = ".eot" mime_type = "application/vnd.ms-fontobject" compress = 0 }, { extension = ".map" mime_type = "application/octet-stream" compress = 0 }, { extension = ".ico" mime_type = "image/x-icon" compress = 0 } ) glewlwyd-2.6.1/test/glewlwyd-profile-delete-disable.conf000066400000000000000000000066441415646314000233570ustar00rootroot00000000000000# # # Glewlwyd SSO Authorization Server # # Copyright 2016-2020 Nicolas Mora # License MIT # # # port to open for remote commands port=4593 # external url to access to this instance external_url="http://localhost:4593" # login url relative to external url login_url="login.html" # url prefix url_prefix="api" # path to static files for /webapp url static_files_path="/usr/share/glewlwyd/webapp/" # access-control-allow-origin value allow_origin="*" # log mode (console, syslog, journald, file) log_mode="file" # log level: NONE, ERROR, WARNING, INFO, DEBUG log_level="DEBUG" # output to log file (required if log_mode is file) log_file="/tmp/glewlwyd-disable.log" # cookie domain #cookie_domain="localhost" # cookie_secure, this options SHOULD be set to 1, set this to 0 to test glewlwyd on insecure connection http instead of https cookie_secure=0 # session expiration, default is 4 weeks session_expiration=2419200 # session key session_key="GLEWLWYD2_SESSION_ID" # admin scope name admin_scope="g_admin" # profile scope name profile_scope="g_profile" # user_module path user_module_path="/usr/lib/glewlwyd/user" # user_middleware_module path user_middleware_module_path="/usr/lib/glewlwyd/user_middleware" # client_module path client_module_path="/usr/lib/glewlwyd/client" # user_auth_scheme_module path user_auth_scheme_module_path="/usr/lib/glewlwyd/scheme" # plugin_module path plugin_module_path="/usr/lib/glewlwyd/plugin" # can a user delete its account. Values available are "no", "delete" or "disable" delete_profile="disable" # TLS/SSL configuration values use_secure_connection=false secure_connection_key_file="/usr/local/etc/glewlwyd/cert.key" secure_connection_pem_file="/usr/local/etc/glewlwyd/cert.pem" # Algorithms available are SHA1, SHA256, SHA512, MD5, default is SHA256 hash_algorithm = "SHA256" # MariaDB/Mysql database connection #database = #{ # type = "mariadb" # host = "localhost" # user = "glewlwyd" # password = "glewlwyd" # dbname = "glewlwyd" # port = 0 #} # SQLite database connection database = { type = "sqlite3" path = "/tmp/glewlwyd.db" }; # SQLite database connection #database = #{ # type = "postgre" # conninfo = "host=localhost dbname=glewlwyd user=glewlwyd password=glewlwyd" #}; # mime types for webapp files static_files_mime_types = ( { extension = ".html" mime_type = "text/html" compress = 1 }, { extension = ".css" mime_type = "text/css" compress = 1 }, { extension = ".js" mime_type = "application/javascript" compress = 1 }, { extension = ".json" mime_type = "application/json" compress = 1 }, { extension = ".png" mime_type = "image/png" compress = 0 }, { extension = ".jpg" mime_type = "image/jpeg" compress = 0 }, { extension = ".jpeg" mime_type = "image/jpeg" compress = 0 }, { extension = ".ttf" mime_type = "font/ttf" compress = 0 }, { extension = ".woff" mime_type = "font/woff" compress = 0 }, { extension = ".woff2" mime_type = "font/woff2" compress = 0 }, { extension = ".otf" mime_type = "font/otf" compress = 0 }, { extension = ".eot" mime_type = "application/vnd.ms-fontobject" compress = 0 }, { extension = ".map" mime_type = "application/octet-stream" compress = 0 }, { extension = ".ico" mime_type = "image/x-icon" compress = 0 } ) glewlwyd-2.6.1/test/glewlwyd-profile-delete-yes.conf000066400000000000000000000066411415646314000225510ustar00rootroot00000000000000# # # Glewlwyd SSO Authorization Server # # Copyright 2016-2020 Nicolas Mora # License MIT # # # port to open for remote commands port=4593 # external url to access to this instance external_url="http://localhost:4593" # login url relative to external url login_url="login.html" # url prefix url_prefix="api" # path to static files for /webapp url static_files_path="/usr/share/glewlwyd/webapp/" # access-control-allow-origin value allow_origin="*" # log mode (console, syslog, journald, file) log_mode="file" # log level: NONE, ERROR, WARNING, INFO, DEBUG log_level="DEBUG" # output to log file (required if log_mode is file) log_file="/tmp/glewlwyd-delete.log" # cookie domain #cookie_domain="localhost" # cookie_secure, this options SHOULD be set to 1, set this to 0 to test glewlwyd on insecure connection http instead of https cookie_secure=0 # session expiration, default is 4 weeks session_expiration=2419200 # session key session_key="GLEWLWYD2_SESSION_ID" # admin scope name admin_scope="g_admin" # profile scope name profile_scope="g_profile" # user_module path user_module_path="/usr/lib/glewlwyd/user" # user_middleware_module path user_middleware_module_path="/usr/lib/glewlwyd/user_middleware" # client_module path client_module_path="/usr/lib/glewlwyd/client" # user_auth_scheme_module path user_auth_scheme_module_path="/usr/lib/glewlwyd/scheme" # plugin_module path plugin_module_path="/usr/lib/glewlwyd/plugin" # can a user delete its account. Values available are "no", "delete" or "disable" delete_profile="delete" # TLS/SSL configuration values use_secure_connection=false secure_connection_key_file="/usr/local/etc/glewlwyd/cert.key" secure_connection_pem_file="/usr/local/etc/glewlwyd/cert.pem" # Algorithms available are SHA1, SHA256, SHA512, MD5, default is SHA256 hash_algorithm = "SHA256" # MariaDB/Mysql database connection #database = #{ # type = "mariadb" # host = "localhost" # user = "glewlwyd" # password = "glewlwyd" # dbname = "glewlwyd" # port = 0 #} # SQLite database connection database = { type = "sqlite3" path = "/tmp/glewlwyd.db" }; # SQLite database connection #database = #{ # type = "postgre" # conninfo = "host=localhost dbname=glewlwyd user=glewlwyd password=glewlwyd" #}; # mime types for webapp files static_files_mime_types = ( { extension = ".html" mime_type = "text/html" compress = 1 }, { extension = ".css" mime_type = "text/css" compress = 1 }, { extension = ".js" mime_type = "application/javascript" compress = 1 }, { extension = ".json" mime_type = "application/json" compress = 1 }, { extension = ".png" mime_type = "image/png" compress = 0 }, { extension = ".jpg" mime_type = "image/jpeg" compress = 0 }, { extension = ".jpeg" mime_type = "image/jpeg" compress = 0 }, { extension = ".ttf" mime_type = "font/ttf" compress = 0 }, { extension = ".woff" mime_type = "font/woff" compress = 0 }, { extension = ".woff2" mime_type = "font/woff2" compress = 0 }, { extension = ".otf" mime_type = "font/otf" compress = 0 }, { extension = ".eot" mime_type = "application/vnd.ms-fontobject" compress = 0 }, { extension = ".map" mime_type = "application/octet-stream" compress = 0 }, { extension = ".ico" mime_type = "image/x-icon" compress = 0 } ) glewlwyd-2.6.1/test/glewlwyd-prometheus.conf000066400000000000000000000067331415646314000212500ustar00rootroot00000000000000# # # Glewlwyd SSO Authorization Server # # Copyright 2016-2020 Nicolas Mora # License MIT # # # port to open for remote commands port=4593 # external url to access to this instance external_url="http://localhost:4593" # login url relative to external url login_url="login.html" # url prefix url_prefix="api" # path to static files for /webapp url static_files_path="/usr/share/glewlwyd/webapp/" # access-control-allow-origin value allow_origin="*" # log mode (console, syslog, journald, file) log_mode="file" # log level: NONE, ERROR, WARNING, INFO, DEBUG log_level="DEBUG" # output to log file (required if log_mode is file) log_file="/tmp/glewlwyd-prometheus.log" # cookie domain #cookie_domain="localhost" # cookie_secure, this options SHOULD be set to 1, set this to 0 to test glewlwyd on insecure connection http instead of https cookie_secure=0 # session expiration, default is 4 weeks session_expiration=2419200 # session key session_key="GLEWLWYD2_SESSION_ID" # admin scope name admin_scope="g_admin" # profile scope name profile_scope="g_profile" # user_module path user_module_path="/usr/lib/glewlwyd/user" # user_middleware_module path user_middleware_module_path="/usr/lib/glewlwyd/user_middleware" # client_module path client_module_path="/usr/lib/glewlwyd/client" # user_auth_scheme_module path user_auth_scheme_module_path="/usr/lib/glewlwyd/scheme" # plugin_module path plugin_module_path="/usr/lib/glewlwyd/plugin" # TLS/SSL configuration values use_secure_connection=false secure_connection_key_file="/usr/local/etc/glewlwyd/cert.key" secure_connection_pem_file="/usr/local/etc/glewlwyd/cert.pem" # Algorithms available are SHA1, SHA256, SHA512, MD5, default is SHA256 hash_algorithm = "SHA256" # MariaDB/Mysql database connection #database = #{ # type = "mariadb" # host = "localhost" # user = "glewlwyd" # password = "glewlwyd" # dbname = "glewlwyd" # port = 0 #} # SQLite database connection database = { type = "sqlite3" path = "/tmp/glewlwyd.db" }; # SQLite database connection #database = #{ # type = "postgre" # conninfo = "host=localhost dbname=glewlwyd user=glewlwyd password=glewlwyd" #}; # Prometheus metrics parameters metrics_endpoint = true #metrics_bind_address = "127.0.0.1" metrics_endpoint_port = 4594 metrics_endpoint_admin_session = false # mime types for webapp files static_files_mime_types = ( { extension = ".html" mime_type = "text/html" compress = 1 }, { extension = ".css" mime_type = "text/css" compress = 1 }, { extension = ".js" mime_type = "application/javascript" compress = 1 }, { extension = ".json" mime_type = "application/json" compress = 1 }, { extension = ".png" mime_type = "image/png" compress = 0 }, { extension = ".jpg" mime_type = "image/jpeg" compress = 0 }, { extension = ".jpeg" mime_type = "image/jpeg" compress = 0 }, { extension = ".ttf" mime_type = "font/ttf" compress = 0 }, { extension = ".woff" mime_type = "font/woff" compress = 0 }, { extension = ".woff2" mime_type = "font/woff2" compress = 0 }, { extension = ".otf" mime_type = "font/otf" compress = 0 }, { extension = ".eot" mime_type = "application/vnd.ms-fontobject" compress = 0 }, { extension = ".map" mime_type = "application/octet-stream" compress = 0 }, { extension = ".ico" mime_type = "image/x-icon" compress = 0 } ) glewlwyd-2.6.1/test/glewlwyd-test.sql000066400000000000000000000143011415646314000176740ustar00rootroot00000000000000DELETE FROM g_user_session_scheme; DELETE FROM g_user_session; DELETE FROM g_scope_group_auth_scheme_module_instance; DELETE FROM g_scope_group; DELETE FROM g_scope; DELETE FROM g_user_middleware_module_instance; DELETE FROM g_user_module_instance; DELETE FROM g_user_auth_scheme_module_instance; DELETE FROM g_client_module_instance; DELETE FROM g_client_module_instance; DELETE FROM g_plugin_module_instance; DELETE FROM gpr_session; DELETE FROM gs_webauthn_assertion; DELETE FROM gs_webauthn_credential; DELETE FROM gs_webauthn_user; DELETE FROM gpo_ciba; DELETE FROM gpo_par; DELETE FROM gpo_dpop; DELETE FROM gpo_device_authorization; DELETE FROM gpo_client_token_request; DELETE FROM gpo_client_registration; DELETE FROM gpo_subject_identifier; DELETE FROM gpo_id_token; DELETE FROM gpo_access_token; DELETE FROM gpo_refresh_token; DELETE FROM gpo_code; DELETE FROM gpg_device_authorization; DELETE FROM gpg_access_token; DELETE FROM gpg_refresh_token; DELETE FROM gpg_code; INSERT INTO g_user_module_instance (gumi_module, gumi_name, gumi_display_name, gumi_order, gumi_parameters) VALUES ('mock', 'mock', 'Mock user module', 0, '{"username-prefix":"","password":"password"}'); INSERT INTO g_user_auth_scheme_module_instance (guasmi_module, guasmi_name, guasmi_display_name, guasmi_expiration, guasmi_parameters) VALUES ('mock', 'mock_scheme_42', 'Mock 42', 600, '{"mock-value":"42"}'); INSERT INTO g_user_auth_scheme_module_instance (guasmi_module, guasmi_name, guasmi_display_name, guasmi_expiration, guasmi_parameters, guasmi_max_use) VALUES ('mock', 'mock_scheme_88', 'Mock 88', 600, '{"mock-value":"88"}', 1); INSERT INTO g_user_auth_scheme_module_instance (guasmi_module, guasmi_name, guasmi_display_name, guasmi_expiration, guasmi_parameters) VALUES ('mock', 'mock_scheme_95', 'Mock 95', 300, '{"mock-value":"95"}'); INSERT INTO g_client_module_instance (gcmi_module, gcmi_name, gcmi_display_name, gcmi_order, gcmi_parameters) VALUES ('mock', 'mock', 'Mock client module', 0, '{"username-prefix":"","password":"password"}'); INSERT INTO g_scope (gs_name, gs_display_name, gs_description, gs_password_required, gs_password_max_age) VALUES ('g_admin', 'Glewlwyd administration', 'Access to Glewlwyd''s administration API', 1, 600); INSERT INTO g_scope (gs_name, gs_display_name, gs_description, gs_password_required, gs_password_max_age) VALUES ('g_profile', 'Glewlwyd profile', 'Access to the user''s profile API', 1, 600); INSERT INTO g_scope (gs_name, gs_display_name, gs_description, gs_password_required, gs_password_max_age) VALUES ('openid', 'id_token scope', 'OpenID Connect used for getting id_token in code response type', 0, 0); INSERT INTO g_scope (gs_name, gs_display_name, gs_description, gs_password_required, gs_password_max_age) VALUES ('scope1', 'Glewlwyd mock scope with password', 'Glewlwyd scope 1 scope description', 1, 0); INSERT INTO g_scope (gs_name, gs_display_name, gs_description, gs_password_required, gs_password_max_age) VALUES ('scope2', 'Glewlwyd mock scope without password', 'Glewlwyd scope 2 scope description', 0, 0); INSERT INTO g_scope (gs_name, gs_display_name, gs_description, gs_password_required, gs_password_max_age) VALUES ('scope3', 'Glewlwyd mock scope with password', 'Glewlwyd scope 3 scope description', 1, 0); INSERT INTO g_scope_group (gs_id, gsg_name) VALUES ((SELECT gs_id FROM g_scope WHERE gs_name = 'scope1'), '0'); INSERT INTO g_scope_group (gs_id, gsg_name) VALUES ((SELECT gs_id FROM g_scope WHERE gs_name = 'scope1'), '1'); INSERT INTO g_scope_group (gs_id, gsg_name) VALUES ((SELECT gs_id FROM g_scope WHERE gs_name = 'scope2'), '2'); INSERT INTO g_scope_group (gs_id, gsg_name) VALUES ((SELECT gs_id FROM g_scope WHERE gs_name = 'scope3'), '3'); INSERT INTO g_scope_group_auth_scheme_module_instance (gsg_id, guasmi_id) VALUES ((SELECT gsg_id FROM g_scope_group WHERE gsg_name = '0'), (SELECT guasmi_id FROM g_user_auth_scheme_module_instance WHERE guasmi_name = 'mock_scheme_42')); INSERT INTO g_scope_group_auth_scheme_module_instance (gsg_id, guasmi_id) VALUES ((SELECT gsg_id FROM g_scope_group WHERE gsg_name = '0'), (SELECT guasmi_id FROM g_user_auth_scheme_module_instance WHERE guasmi_name = 'mock_scheme_88')); INSERT INTO g_scope_group_auth_scheme_module_instance (gsg_id, guasmi_id) VALUES ((SELECT gsg_id FROM g_scope_group WHERE gsg_name = '1'), (SELECT guasmi_id FROM g_user_auth_scheme_module_instance WHERE guasmi_name = 'mock_scheme_95')); INSERT INTO g_scope_group_auth_scheme_module_instance (gsg_id, guasmi_id) VALUES ((SELECT gsg_id FROM g_scope_group WHERE gsg_name = '2'), (SELECT guasmi_id FROM g_user_auth_scheme_module_instance WHERE guasmi_name = 'mock_scheme_95')); INSERT INTO g_scope_group_auth_scheme_module_instance (gsg_id, guasmi_id) VALUES ((SELECT gsg_id FROM g_scope_group WHERE gsg_name = '3'), (SELECT guasmi_id FROM g_user_auth_scheme_module_instance WHERE guasmi_name = 'mock_scheme_88')); INSERT INTO g_plugin_module_instance (gpmi_module, gpmi_name, gpmi_display_name, gpmi_parameters) VALUES ('oauth2-glewlwyd', 'glwd', 'OAuth2 Glewlwyd plugin', '{"jwt-type":"sha","jwt-key-size":"256","key":"secret","access-token-duration":3600,"refresh-token-duration":1209600,"code-duration":600,"refresh-token-rolling":true,"auth-type-code-enabled":true,"auth-type-implicit-enabled":true,"auth-type-password-enabled":true,"auth-type-client-enabled":true,"auth-type-refresh-enabled":true,"scope":[{"name":"g_profile","refresh-token-rolling":true},{"name":"scope1","refresh-token-rolling":true},{"name":"scope2","refresh-token-rolling":false,"refresh-token-duration":7200}]}'); INSERT INTO g_plugin_module_instance (gpmi_module, gpmi_name, gpmi_display_name, gpmi_parameters) VALUES ('oidc', 'oidc', 'OpenID Connect Glewlwyd plugin', '{"iss":"https://glewlwyd.tld","jwt-type":"sha","jwt-key-size":"256","key":"secret","access-token-duration":3600,"refresh-token-duration":1209600,"code-duration":600,"refresh-token-rolling":true, "allow-non-oidc":true,"auth-type-code-enabled":true,"auth-type-token-enabled":true,"auth-type-none-enabled":true,"auth-type-password-enabled":true,"auth-type-client-enabled":true,"auth-type-refresh-enabled":true,"scope":[{"name":"g_profile","refresh-token-rolling":true},{"name":"scope1","refresh-token-rolling":true},{"name":"scope2","refresh-token-rolling":false,"refresh-token-duration":7200}]}'); glewlwyd-2.6.1/test/glewlwyd-travis.conf000066400000000000000000000056531415646314000203650ustar00rootroot00000000000000# # # Glewlwyd SSO Authorization Server # # Copyright 2016-2020 Nicolas Mora # License MIT # # # port to open for remote commands port=4593 # external url to access to this instance external_url="http://localhost:4593" # login url relative to external url login_url="login.html" # url prefix url_prefix="api" # path to static files for /webapp url static_files_path="/usr/share/glewlwyd/webapp/" # access-control-allow-origin value allow_origin="*" # log mode (console, syslog, journald, file) log_mode="file" # log level: NONE, ERROR, WARNING, INFO, DEBUG log_level="DEBUG" # output to log file (required if log_mode is file) log_file="/tmp/glewlwyd.log" # cookie domain #cookie_domain="localhost" # cookie_secure, this options SHOULD be set to 1, set this to 0 to test glewlwyd on insecure connection http instead of https cookie_secure=0 # session expiration, default is 4 weeks session_expiration=2419200 # session key session_key="GLEWLWYD2_SESSION_ID" # admin scope name admin_scope="g_admin" # profile scope name profile_scope="g_profile" # user_module path user_module_path="/usr/lib/glewlwyd/user" # user_middleware_module path user_middleware_module_path="/usr/lib/glewlwyd/user_middleware" # client_module path client_module_path="/usr/lib/glewlwyd/client" # user_auth_scheme_module path user_auth_scheme_module_path="/usr/lib/glewlwyd/scheme" # plugin_module path plugin_module_path="/usr/lib/glewlwyd/plugin" # TLS/SSL configuration values use_secure_connection=false secure_connection_key_file="/usr/etc/glewlwyd/cert.key" secure_connection_pem_file="/usr/etc/glewlwyd/cert.pem" # Algorithms available are SHA1, SHA256, SHA512, MD5, default is SHA256 hash_algorithm = "SHA256" # MariaDB/Mysql database connection #database = #{ # type = "mariadb" # host = "localhost" # user = "glewlwyd" # password = "glewlwyd" # dbname = "glewlwyd" # port = 0 #} # SQLite database connection database = { type = "sqlite3" path = "/tmp/glewlwyd.db" }; # SQLite database connection #database = #{ # type = "postgre" # conninfo = "host=localhost dbname=glewlwyd user=glewlwyd password=glewlwyd" #}; # mime types for webapp files static_files_mime_types = ( { extension = ".html" mime_type = "text/html" }, { extension = ".css" mime_type = "text/css" }, { extension = ".js" mime_type = "application/javascript" }, { extension = ".json" mime_type = "application/json" }, { extension = ".png" mime_type = "image/png" }, { extension = ".jpg" mime_type = "image/jpeg" }, { extension = ".jpeg" mime_type = "image/jpeg" }, { extension = ".ttf" mime_type = "font/ttf" }, { extension = ".woff" mime_type = "font/woff" }, { extension = ".woff2" mime_type = "font/woff2" }, { extension = ".map" mime_type = "application/octet-stream" }, { extension = ".ico" mime_type = "image/x-icon" } ) glewlwyd-2.6.1/test/glewlwyd_admin_api_key.c000066400000000000000000000151401415646314000212150ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "admin" #define PASSWORD "password" struct _u_request admin_req; START_TEST(test_glwd_admin_api_key_add) { ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/key", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/key", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/key", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/key", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "DELETE", SERVER_URI "/key/fake", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/key/fake", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_admin_api_key_use) { struct _u_request req, req_api; struct _u_response resp; json_t * j_body; char * header; ulfius_init_request(&req); ulfius_init_request(&req_api); ulfius_copy_request(&req, &admin_req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/key", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_gt(json_string_length(json_object_get(j_body, "key")), 0); header = msprintf("token %s", json_string_value(json_object_get(j_body, "key"))); json_decref(j_body); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req_api, U_OPT_HEADER_PARAMETER, "Authorization", header, U_OPT_NONE), U_OK); ck_assert_int_eq(run_simple_test(&req_api, "GET", SERVER_URI "/mod/type", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&req_api, "GET", SERVER_URI "/user", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&req_api, "GET", SERVER_URI "/client", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&req_api, "GET", SERVER_URI "/scope", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&req_api, "GET", SERVER_URI "/key", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); o_free(header); ulfius_clean_request(&req); ulfius_clean_request(&req_api); } END_TEST START_TEST(test_glwd_admin_api_key_disable) { struct _u_request req, req_api; struct _u_response resp; json_t * j_body; char * header, * url; ulfius_init_request(&req); ulfius_init_request(&req_api); ulfius_copy_request(&req, &admin_req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/key", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_gt(json_string_length(json_object_get(j_body, "key")), 0); header = msprintf("token %s", json_string_value(json_object_get(j_body, "key"))); json_decref(j_body); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req_api, U_OPT_HEADER_PARAMETER, "Authorization", header, U_OPT_NONE), U_OK); ck_assert_int_eq(run_simple_test(&req_api, "GET", SERVER_URI "/mod/type", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); url = msprintf(SERVER_URI "/key/%s", json_string_value(json_object_get(json_array_get(j_body, json_array_size(j_body)-1), "token_hash"))); ck_assert_int_eq(run_simple_test(&req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&req_api, "GET", SERVER_URI "/mod/type", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); ulfius_clean_response(&resp); o_free(header); o_free(url); ulfius_clean_request(&req); ulfius_clean_request(&req_api); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd admin API key"); tc_core = tcase_create("test_glwd_admin_api_key"); tcase_add_test(tc_core, test_glwd_admin_api_key_add); tcase_add_test(tc_core, test_glwd_admin_api_key_use); tcase_add_test(tc_core, test_glwd_admin_api_key_disable); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0, i; json_t * j_body; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api/" #define USERNAME "user1" #define PASSWORD "password" #define SCHEME_TYPE "mock" #define SCHEME_NAME "mock_scheme_42" #define SCHEME_VALUE "42" #define MODULE_MODULE "mock" #define MODULE_NAME "test" #define MODULE_DISPLAY_NAME "test name" #define NEW_CLIENT_ID "test" #define NEW_CLIENT_NAME "Client 1" #define NEW_DESCRIPTION "Description for Client 1" #define NEW_SCOPE_1 "scope1" #define NEW_SCOPE_2 "scope2" #define SCOPE_NAME "My New Scope" #define DESCRIPTION "Description for test-scope" #define GROUP1 "group1" #define GROUP1_DESC "Group1 description" #define GROUP2 "group2" #define GROUP2_DESC "Group2 description" #define SCHEME1 "mock_scheme_42" #define SCHEME2 "mock_scheme_88" #define SCHEME3 "mock_scheme_95" #define NEW_USERNAME "test" #define NEW_NAME "Dave Lopper" #define NEW_EMAIL "test@glewlwyd" #define NEW_SCOPE_1 "scope1" #define NEW_SCOPE_2 "scope2" struct _u_request user_req; START_TEST(test_glwd_admin_check_scope_delegate) { json_t * j_body; j_body = json_pack("{ss}", "name", "new name"); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/delegate/" USERNAME "/profile/", NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/delegate/" USERNAME "/profile/session", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/delegate/" USERNAME "/profile/plugin", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "DELETE", SERVER_URI "/delegate/" USERNAME "/profile/session/my_session", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/delegate/" USERNAME "/profile/scheme", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value", "code", SCHEME_VALUE); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/delegate/" USERNAME "/profile/scheme/register/", NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/delegate/" USERNAME "/profile/scheme/register/", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_admin_check_scope_mod_user) { json_t * j_body; ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/mod/user", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/mod/user/mock", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); j_body = json_pack("{sssssssisos{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/mod/user", NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{sssisos{ss}}", "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/mod/user/" MODULE_NAME, NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); ck_assert_int_eq(run_simple_test(&user_req, "DELETE", SERVER_URI "/mod/user/" MODULE_NAME, NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/mod/user/" MODULE_NAME "/enable", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/mod/user/" MODULE_NAME "/disable", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_admin_check_scope_mod_client) { json_t * j_body; ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/mod/client", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/mod/client/mock", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); j_body = json_pack("{sssssssisos{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/mod/client", NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{sssisos{ss}}", "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/mod/client/" MODULE_NAME, NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); ck_assert_int_eq(run_simple_test(&user_req, "DELETE", SERVER_URI "/mod/client/" MODULE_NAME, NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/mod/client/" MODULE_NAME "/enable", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/mod/client/" MODULE_NAME "/disable", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_admin_check_scope_mod_scheme) { json_t * j_body; ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/mod/scheme", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/mod/scheme/mock", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); j_body = json_pack("{sssssssisisos{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", 600, "max_use", 0, "allow_user_register", json_true(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/mod/scheme", NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{sssisisos{ss}}", "display_name", MODULE_DISPLAY_NAME, "expiration", 600, "max_use", 0, "allow_user_register", json_true(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); ck_assert_int_eq(run_simple_test(&user_req, "DELETE", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/mod/scheme/" MODULE_NAME "/enable", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/mod/scheme/" MODULE_NAME "/disable", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_admin_check_scope_mod_plugin) { json_t * j_body; ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/mod/plugin", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/mod/plugin/mock", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); j_body = json_pack("{sssssss{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/mod/plugin", NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{sss{ss}}", "display_name", MODULE_DISPLAY_NAME, "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/mod/plugin/" MODULE_NAME, NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); ck_assert_int_eq(run_simple_test(&user_req, "DELETE", SERVER_URI "/mod/plugin/" MODULE_NAME, NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/mod/plugin/" MODULE_NAME "/enable", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/mod/plugin/" MODULE_NAME "/disable", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_admin_check_scope_user) { json_t * j_body; ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/user", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/user/user1", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); j_body = json_pack("{sssssss[ss]}", "username", NEW_USERNAME, "name", NEW_NAME, "email", NEW_EMAIL, "scope", NEW_SCOPE_1, NEW_SCOPE_2); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/user", NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{sssss[ss]}", "name", NEW_NAME, "email", NEW_EMAIL, "scope", NEW_SCOPE_1, NEW_SCOPE_2); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/user/" NEW_USERNAME, NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); ck_assert_int_eq(run_simple_test(&user_req, "DELETE", SERVER_URI "/user/" NEW_USERNAME, NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_admin_check_scope_client) { json_t * j_body; ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/user", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/user/user1", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); j_body = json_pack("{ssssss}", "client_id", NEW_CLIENT_ID, "name", NEW_CLIENT_NAME, "description", NEW_DESCRIPTION); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/user", NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ssss}", "name", NEW_CLIENT_NAME, "description", NEW_DESCRIPTION); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/user/" NEW_CLIENT_ID, NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); ck_assert_int_eq(run_simple_test(&user_req, "DELETE", SERVER_URI "/user/" NEW_CLIENT_ID, NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_admin_check_scope_scope) { json_t * j_body; ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/scope", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/scope/scope1", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); j_body = json_pack("{ss ss ss so s{s[{ssssss}]}}", "name", SCOPE_NAME, "display_name", SCOPE_NAME, "description", DESCRIPTION, "password_required", json_true(), "scheme", GROUP1, "scheme_name", SCHEME1, "scheme_display_name", "Mock 42", "scheme_type", "mock"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/scope", NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ss ss so s{s[{ssssss}]}}", "display_name", SCOPE_NAME, "description", DESCRIPTION, "password_required", json_true(), "scheme", GROUP1, "scheme_name", SCHEME1, "scheme_display_name", "Mock 42", "scheme_type", "mock"); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); ck_assert_int_eq(run_simple_test(&user_req, "DELETE", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd admin check scope credentials"); tc_core = tcase_create("test_glwd_admin_check_scope"); tcase_add_test(tc_core, test_glwd_admin_check_scope_delegate); tcase_add_test(tc_core, test_glwd_admin_check_scope_mod_user); tcase_add_test(tc_core, test_glwd_admin_check_scope_mod_client); tcase_add_test(tc_core, test_glwd_admin_check_scope_mod_scheme); tcase_add_test(tc_core, test_glwd_admin_check_scope_mod_plugin); tcase_add_test(tc_core, test_glwd_admin_check_scope_user); tcase_add_test(tc_core, test_glwd_admin_check_scope_client); tcase_add_test(tc_core, test_glwd_admin_check_scope_scope); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "admin" #define PASSWORD "password" #define MODULE_MODULE "mock" #define MODULE_NAME "test" #define MODULE_DISPLAY_NAME "test name" struct _u_request admin_req; START_TEST(test_glwd_admin_get_mod_client_get_list) { char * url = msprintf("%s/mod/client/", SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_client_add_error_json) { char * url = msprintf("%s/mod/client/", SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_client_add_error_param) { char * url = msprintf("%s/mod/client/", SERVER_URI); json_t * j_parameters = json_pack("{ss}", "error", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssisos{ss}}", "module", "error", "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssisssisos{ss}}", "module", MODULE_MODULE, "name", 42, "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssisisos{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", 42, "order_rank", 0, "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssssos{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", "error", "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssisos{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", -42, "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssisss{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "readonly", "error", "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssisoss}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "readonly", json_false(), "parameters", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_client_add_OK) { char * url = msprintf("%s/mod/client/", SERVER_URI); json_t * j_parameters = json_pack("{sssssssisos{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/mod/client/%s", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_admin_get_mod_client_get) { char * url = msprintf("%s/mod/client/%s", SERVER_URI, MODULE_NAME), * url_404 = msprintf("%s/mod/client/error", SERVER_URI); json_t * j_parameters = json_pack("{sssssssisos{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url_404, NULL, NULL, NULL, NULL, 404, NULL, NULL, NULL), 1); o_free(url); o_free(url_404); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_admin_get_mod_client_set_error_param) { char * url = msprintf("%s/mod/client/%s", SERVER_URI, MODULE_NAME); json_t * j_parameters = json_pack("{ss}", "error", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sis{ss}}", "display_name", 42, "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssisos{ss}}", "display_name", MODULE_DISPLAY_NAME, "order_rank", -42, "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssisss{ss}}", "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "readonly", "error", "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssos{ss}}", "display_name", MODULE_DISPLAY_NAME, "order_rank", "error", "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssisoss}", "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "readonly", json_false(), "parameters", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_client_set_OK) { char * url = msprintf("%s/mod/client/%s", SERVER_URI, MODULE_NAME); json_t * j_parameters = json_pack("{sssis{ss}}", "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/mod/client/%s", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_admin_get_mod_client_action) { char * url = msprintf("%s/mod/client/%s/disable", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/mod/client/%s/enable", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/mod/client/%s/error", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_client_delete_error) { char * url = msprintf("%s/mod/client/error", SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 404, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_client_delete_OK) { char * url = msprintf("%s/mod/client/%s", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_admin_mod_client_with_errors) { const char * name = "test_with_errors"; json_t * j_parameters = json_pack("{sssssssisos{so}}", "module", MODULE_MODULE, "name", name, "display_name", MODULE_DISPLAY_NAME, "order_rank", 1, "readonly", json_false(), "parameters", "error", json_true()); char * url; ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/client/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); url = msprintf(SERVER_URI "/mod/client/%s", name); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf(SERVER_URI "/mod/client/%s/enable", name); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); o_free(url); url = msprintf(SERVER_URI "/mod/client/%s", name); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd admin module client"); tc_core = tcase_create("test_glwd_admin_mod_client"); tcase_add_test(tc_core, test_glwd_admin_get_mod_client_get_list); tcase_add_test(tc_core, test_glwd_admin_get_mod_client_add_error_json); tcase_add_test(tc_core, test_glwd_admin_get_mod_client_add_error_param); tcase_add_test(tc_core, test_glwd_admin_get_mod_client_add_OK); tcase_add_test(tc_core, test_glwd_admin_get_mod_client_get); tcase_add_test(tc_core, test_glwd_admin_get_mod_client_set_error_param); tcase_add_test(tc_core, test_glwd_admin_get_mod_client_set_OK); tcase_add_test(tc_core, test_glwd_admin_get_mod_client_action); tcase_add_test(tc_core, test_glwd_admin_get_mod_client_delete_error); tcase_add_test(tc_core, test_glwd_admin_get_mod_client_delete_OK); tcase_add_test(tc_core, test_glwd_admin_mod_client_with_errors); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0, i; json_t * j_body; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "admin" #define PASSWORD "password" #define MODULE_MODULE "mock" #define MODULE_NAME "test" #define MODULE_DISPLAY_NAME "test name" struct _u_request admin_req; START_TEST(test_glwd_admin_get_mod_plugin_get_list) { char * url = msprintf("%s/mod/plugin/", SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_plugin_add_error_json) { char * url = msprintf("%s/mod/plugin/", SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_plugin_add_error_param) { char * url = msprintf("%s/mod/plugin/", SERVER_URI); json_t * j_parameters = json_pack("{ss}", "error", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssss{ss}}", "module", "error", "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssisss{ss}}", "module", MODULE_MODULE, "name", 42, "display_name", MODULE_DISPLAY_NAME, "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssis{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", 42, "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{ssssssss}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "parameters", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_plugin_add_OK) { char * url = msprintf("%s/mod/plugin/", SERVER_URI); json_t * j_parameters = json_pack("{sssssss{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/mod/plugin/%s", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_admin_get_mod_plugin_get) { char * url = msprintf("%s/mod/plugin/%s", SERVER_URI, MODULE_NAME), * url_404 = msprintf("%s/mod/plugin/error", SERVER_URI); json_t * j_parameters = json_pack("{sssssss{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url_404, NULL, NULL, NULL, NULL, 404, NULL, NULL, NULL), 1); o_free(url); o_free(url_404); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_admin_get_mod_plugin_set_error_param) { char * url = msprintf("%s/mod/plugin/%s", SERVER_URI, MODULE_NAME); json_t * j_parameters = json_pack("{ss}", "error", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sis{ss}}", "display_name", 42, "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{ssss}", "display_name", MODULE_DISPLAY_NAME, "parameters", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_plugin_set_OK) { char * url = msprintf("%s/mod/plugin/%s", SERVER_URI, MODULE_NAME); json_t * j_parameters = json_pack("{sss{ss}}", "display_name", MODULE_DISPLAY_NAME, "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/mod/plugin/%s", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_admin_get_mod_plugin_action) { char * url = msprintf("%s/mod/plugin/%s/disable", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/mod/plugin/%s/enable", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/mod/plugin/%s/error", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_plugin_delete_error) { char * url = msprintf("%s/mod/plugin/error", SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 404, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_plugin_delete_OK) { char * url = msprintf("%s/mod/plugin/%s", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_admin_mod_plugin_with_errors) { const char * name = "test_with_errors"; json_t * j_parameters = json_pack("{sssssssisos{so}}", "module", MODULE_MODULE, "name", name, "display_name", MODULE_DISPLAY_NAME, "order_rank", 1, "readonly", json_false(), "parameters", "error", json_true()); char * url; ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); url = msprintf(SERVER_URI "/mod/plugin/%s", name); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf(SERVER_URI "/mod/plugin/%s/enable", name); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); o_free(url); url = msprintf(SERVER_URI "/mod/plugin/%s", name); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd admin module plugin"); tc_core = tcase_create("test_glwd_admin_mod_plugin"); tcase_add_test(tc_core, test_glwd_admin_get_mod_plugin_get_list); tcase_add_test(tc_core, test_glwd_admin_get_mod_plugin_add_error_json); tcase_add_test(tc_core, test_glwd_admin_get_mod_plugin_add_error_param); tcase_add_test(tc_core, test_glwd_admin_get_mod_plugin_add_OK); tcase_add_test(tc_core, test_glwd_admin_get_mod_plugin_get); tcase_add_test(tc_core, test_glwd_admin_get_mod_plugin_set_error_param); tcase_add_test(tc_core, test_glwd_admin_get_mod_plugin_set_OK); tcase_add_test(tc_core, test_glwd_admin_get_mod_plugin_action); tcase_add_test(tc_core, test_glwd_admin_get_mod_plugin_delete_error); tcase_add_test(tc_core, test_glwd_admin_get_mod_plugin_delete_OK); tcase_add_test(tc_core, test_glwd_admin_mod_plugin_with_errors); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0, i; json_t * j_body; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "admin" #define PASSWORD "password" struct _u_request admin_req; START_TEST(test_glwd_admin_get_mod_types) { ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/mod/type/", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_admin_reload_mods) { ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/mod/reload/", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd admin module types"); tc_core = tcase_create("test_glwd_admin_mod_types"); tcase_add_test(tc_core, test_glwd_admin_get_mod_types); tcase_add_test(tc_core, test_glwd_admin_reload_mods); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0, i; json_t * j_body; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "admin" #define PASSWORD "password" #define MODULE_MODULE "mock" #define MODULE_NAME "test" #define MODULE_DISPLAY_NAME "test name" struct _u_request admin_req; START_TEST(test_glwd_admin_get_mod_user_get_list) { char * url = msprintf("%s/mod/user/", SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_user_add_error_json) { char * url = msprintf("%s/mod/user/", SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_user_add_error_param) { char * url = msprintf("%s/mod/user/", SERVER_URI); json_t * j_parameters = json_pack("{ss}", "error", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssisos{ss}}", "module", "error", "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssisssisos{ss}}", "module", MODULE_MODULE, "name", 42, "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssisisos{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", 42, "order_rank", 0, "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssssos{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", "error", "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssisos{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", -42, "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssisss{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "readonly", "error", "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssisoss}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "readonly", json_false(), "parameters", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_user_add_OK) { char * url = msprintf("%s/mod/user/", SERVER_URI); json_t * j_parameters = json_pack("{sssssssisos{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/mod/user/%s", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_admin_get_mod_user_get) { char * url = msprintf("%s/mod/user/%s", SERVER_URI, MODULE_NAME), * url_404 = msprintf("%s/mod/user/error", SERVER_URI); json_t * j_parameters = json_pack("{sssssssisos{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url_404, NULL, NULL, NULL, NULL, 404, NULL, NULL, NULL), 1); o_free(url); o_free(url_404); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_admin_get_mod_user_set_error_param) { char * url = msprintf("%s/mod/user/%s", SERVER_URI, MODULE_NAME); json_t * j_parameters = json_pack("{ss}", "error", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sis{ss}}", "display_name", 42, "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssisos{ss}}", "display_name", MODULE_DISPLAY_NAME, "order_rank", -42, "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssisss{ss}}", "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "readonly", "error", "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssos{ss}}", "display_name", MODULE_DISPLAY_NAME, "order_rank", "error", "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssisoss}", "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "readonly", json_false(), "parameters", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_user_set_OK) { char * url = msprintf("%s/mod/user/%s", SERVER_URI, MODULE_NAME); json_t * j_parameters = json_pack("{sssisos{ss}}", "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "readonly", json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/mod/user/%s", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_admin_get_mod_user_action) { char * url = msprintf("%s/mod/user/%s/disable", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/mod/user/%s/enable", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/mod/user/%s/error", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_user_delete_error) { char * url = msprintf("%s/mod/user/error", SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 404, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_user_delete_OK) { char * url = msprintf("%s/mod/user/%s", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_admin_mod_user_with_errors) { const char * name = "test_with_errors"; json_t * j_parameters = json_pack("{sssssssisos{so}}", "module", MODULE_MODULE, "name", name, "display_name", MODULE_DISPLAY_NAME, "order_rank", 1, "readonly", json_false(), "parameters", "error", json_true()); char * url; ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/user/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); url = msprintf(SERVER_URI "/mod/user/%s", name); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf(SERVER_URI "/mod/user/%s/enable", name); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); o_free(url); url = msprintf(SERVER_URI "/mod/user/%s", name); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd admin module user"); tc_core = tcase_create("test_glwd_admin_mod_user"); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_get_list); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_add_error_json); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_add_error_param); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_add_OK); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_get); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_set_error_param); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_set_OK); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_action); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_delete_error); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_delete_OK); tcase_add_test(tc_core, test_glwd_admin_mod_user_with_errors); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0, i; json_t * j_body; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "admin" #define PASSWORD "password" #define MODULE_MODULE "mock" #define MODULE_NAME "test" #define MODULE_DISPLAY_NAME "test name" struct _u_request admin_req; START_TEST(test_glwd_admin_get_mod_user_auth_scheme_get_list) { char * url = msprintf("%s/mod/scheme/", SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_user_auth_scheme_add_error_json) { char * url = msprintf("%s/mod/scheme/", SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_user_auth_scheme_add_error_param) { char * url = msprintf("%s/mod/scheme/", SERVER_URI); json_t * j_parameters = json_pack("{ss}", "error", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssisis{ss}}", "module", "error", "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", 600, "max_use", 0, "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssisssisis{ss}}", "module", MODULE_MODULE, "name", 42, "display_name", MODULE_DISPLAY_NAME, "expiration", 600, "max_use", 0, "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssisisis{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", 42, "expiration", 600, "max_use", 0, "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssisis{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", 0, "max_use", 0, "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssisis{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", 90, "max_use", -1, "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssssis{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", "error", "max_use", -1, "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssisss{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", 90, "max_use", "error", "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssisiss}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", 600, "max_use", 0, "parameters", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_user_auth_scheme_add_OK) { char * url = msprintf("%s/mod/scheme/", SERVER_URI); json_t * j_parameters = json_pack("{sssssssisisos{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", 600, "max_use", 0, "allow_user_register", json_true(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/mod/scheme/%s", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_admin_get_mod_user_auth_scheme_get) { char * url = msprintf("%s/mod/scheme/%s", SERVER_URI, MODULE_NAME), * url_404 = msprintf("%s/mod/scheme/error", SERVER_URI); json_t * j_parameters = json_pack("{sssssssos{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "allow_user_register", json_true(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url_404, NULL, NULL, NULL, NULL, 404, NULL, NULL, NULL), 1); o_free(url); o_free(url_404); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_admin_get_mod_user_auth_scheme_set_error_param) { char * url = msprintf("%s/mod/scheme/%s", SERVER_URI, MODULE_NAME); json_t * j_parameters = json_pack("{ss}", "error", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sis{ss}}", "display_name", 42, "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{ssss}", "display_name", MODULE_DISPLAY_NAME, "parameters", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssss{ss}}", "display_name", MODULE_DISPLAY_NAME, "allow_user_register", "error", "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_user_auth_scheme_set_OK) { char * url = msprintf("%s/mod/scheme/%s", SERVER_URI, MODULE_NAME); json_t * j_parameters = json_pack("{sssisisos{ss}}", "display_name", MODULE_DISPLAY_NAME, "expiration", 600, "max_use", 0, "allow_user_register", json_true(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/mod/scheme/%s", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_admin_get_mod_user_auth_scheme_action) { char * url = msprintf("%s/mod/scheme/%s/disable", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/mod/scheme/%s/enable", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/mod/scheme/%s/error", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_user_auth_scheme_delete_error) { char * url = msprintf("%s/mod/scheme/error", SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 404, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_admin_get_mod_user_auth_scheme_delete_OK) { char * url = msprintf("%s/mod/scheme/%s", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_admin_mod_user_auth_scheme_with_errors) { const char * name = "test_with_errors"; json_t * j_parameters = json_pack("{sssssssisosisis{so}}", "module", MODULE_MODULE, "name", name, "display_name", MODULE_DISPLAY_NAME, "order_rank", 1, "readonly", json_false(), "expiration", 600, "max_use", 0, "parameters", "error", json_true()); char * url; ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); url = msprintf(SERVER_URI "/mod/scheme/%s", name); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf(SERVER_URI "/mod/scheme/%s/enable", name); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); o_free(url); url = msprintf(SERVER_URI "/mod/scheme/%s", name); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd admin module user_auth_scheme"); tc_core = tcase_create("test_glwd_admin_mod_user_auth_scheme"); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_auth_scheme_get_list); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_auth_scheme_add_error_json); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_auth_scheme_add_error_param); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_auth_scheme_add_OK); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_auth_scheme_get); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_auth_scheme_set_error_param); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_auth_scheme_set_OK); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_auth_scheme_action); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_auth_scheme_delete_error); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_auth_scheme_delete_OK); tcase_add_test(tc_core, test_glwd_admin_mod_user_auth_scheme_with_errors); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0, i; json_t * j_body; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "admin" #define PASSWORD "password" #define MODULE_MODULE "mock" #define MODULE_NAME "test_middleware" #define MODULE_NAME_WITH_ERRORS "test_middleware_with_errors" #define MODULE_DISPLAY_NAME "test_middleware name" #define MODULE_MIDDLEWARE_VALUE "wizard" struct _u_request admin_req; START_TEST(test_glwd_admin_get_mod_user_middleware_get_list) { ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/mod/user_middleware/", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_admin_get_mod_user_middleware_add_error_json) { ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/user_middleware/", NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_admin_get_mod_user_middleware_add_error_param) { json_t * j_parameters = json_pack("{ss}", "error", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/user_middleware/", NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/user_middleware/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssis{ss}}", "module", "error", "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "parameters", "middleware", MODULE_MIDDLEWARE_VALUE); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/user_middleware/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssisssis{ss}}", "module", MODULE_MODULE, "name", 42, "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "parameters", "middleware", MODULE_MIDDLEWARE_VALUE); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/user_middleware/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssisis{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", 42, "order_rank", 0, "parameters", "middleware", MODULE_MIDDLEWARE_VALUE); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/user_middleware/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssss{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", "error", "parameters", "middleware", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/user_middleware/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssis{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", -42, "parameters", "middleware", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/user_middleware/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssiss}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "parameters", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/user_middleware/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_admin_get_mod_user_middleware_add_OK) { json_t * j_parameters = json_pack("{sssssssis{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "parameters", "middleware", MODULE_MIDDLEWARE_VALUE); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/user_middleware/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/mod/user_middleware/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_admin_get_mod_user_middleware_get) { json_t * j_parameters = json_pack("{sssssssis{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "parameters", "middleware", MODULE_MIDDLEWARE_VALUE); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/mod/user_middleware/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/mod/user_middleware/error", NULL, NULL, NULL, NULL, 404, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_admin_get_mod_user_middleware_set_error_param) { json_t * j_parameters = json_pack("{ss}", "error", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/mod/user_middleware/" MODULE_NAME, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sis{ss}}", "display_name", 42, "parameters", "middleware", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/mod/user_middleware/" MODULE_NAME, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssis{ss}}", "display_name", MODULE_DISPLAY_NAME, "order_rank", -42, "parameters", "middleware", MODULE_MIDDLEWARE_VALUE); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/mod/user_middleware/" MODULE_NAME, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssss{ss}}", "display_name", MODULE_DISPLAY_NAME, "order_rank", json_false(), "parameters", "middleware", MODULE_MIDDLEWARE_VALUE); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/mod/user_middleware/" MODULE_NAME, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssiss}", "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "parameters", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/mod/user_middleware/" MODULE_NAME, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_admin_get_mod_user_middleware_set_OK) { json_t * j_parameters = json_pack("{sssis{ss}}", "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "parameters", "middleware", MODULE_MIDDLEWARE_VALUE); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/mod/user_middleware/" MODULE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/mod/user_middleware/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_admin_get_mod_user_middleware_action) { ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/mod/user_middleware/" MODULE_NAME "/disable", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/mod/user_middleware/" MODULE_NAME "/disable", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/mod/user_middleware/" MODULE_NAME "/enable", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/mod/user_middleware/" MODULE_NAME "/enable", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/mod/user_middleware/" MODULE_NAME "/error", NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_admin_get_mod_user_middleware_delete_error) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/user_middleware/error", NULL, NULL, NULL, NULL, 404, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_admin_get_mod_user_middleware_delete_OK) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/user_middleware/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_admin_mod_user_middleware_with_errors) { json_t * j_parameters = json_pack("{sssssssis{so}}", "module", MODULE_MODULE, "name", MODULE_NAME_WITH_ERRORS, "display_name", MODULE_DISPLAY_NAME, "order_rank", 1, "parameters", "middleware", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/user_middleware/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/mod/user_middleware/" MODULE_NAME_WITH_ERRORS, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/mod/user_middleware/" MODULE_NAME_WITH_ERRORS "/enable", NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/user_middleware/" MODULE_NAME_WITH_ERRORS, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd admin module user_middleware"); tc_core = tcase_create("test_glwd_admin_mod_user_middleware"); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_middleware_get_list); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_middleware_add_error_json); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_middleware_add_error_param); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_middleware_add_OK); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_middleware_get); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_middleware_set_error_param); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_middleware_set_OK); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_middleware_action); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_middleware_delete_error); tcase_add_test(tc_core, test_glwd_admin_get_mod_user_middleware_delete_OK); tcase_add_test(tc_core, test_glwd_admin_mod_user_middleware_with_errors); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0, i; json_t * j_body; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST_ALLOWED "scope1 scope2 scope3" #define SCOPE_LIST_FORBIDDEN "g_admin" #define SCOPE_LIST_MISSING "scope4" START_TEST(test_glwd_auth_check_scheme_no_session) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_element = NULL; const char * key = NULL; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("GET"); req.http_url = msprintf("%s/auth/scheme/?scope=%s", SERVER_URI, SCOPE_LIST_ALLOWED); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_object_size(j_body), 3); json_object_foreach(j_body, key, j_element) { ck_assert_int_eq(json_object_size(j_element), 0); } json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_glwd_auth_check_scheme_session_password) { struct _u_request auth_req, check_req; struct _u_response auth_resp, check_resp; json_t * j_body, * j_element = NULL, * j_group; char * cookie; const char * key = NULL; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&check_req); ulfius_init_response(&check_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(check_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); check_req.http_verb = strdup("GET"); check_req.http_url = msprintf("%s/auth/scheme/?scope=%s", SERVER_URI, SCOPE_LIST_ALLOWED); ck_assert_int_eq(ulfius_send_http_request(&check_req, &check_resp), U_OK); ck_assert_int_eq(check_resp.status, 200); j_body = ulfius_get_json_body_response(&check_resp, NULL); ck_assert_int_eq(json_object_size(j_body), 3); json_object_foreach(j_body, key, j_element) { ck_assert_ptr_eq(json_object_get(j_element, "password_authenticated"), json_true()); if (0 == o_strcmp("scope2", key)) { ck_assert_ptr_eq(json_object_get(j_element, "password_required"), json_false()); } else { ck_assert_ptr_eq(json_object_get(j_element, "password_required"), json_true()); } ck_assert_ptr_eq(json_object_get(j_element, "available"), json_true()); if (0 == o_strcmp("scope1", key)) { ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 2); j_group = json_object_get(json_object_get(j_element, "schemes"), "0"); ck_assert_int_eq(json_array_size(j_group), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_42"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 1), "scheme_name")), "mock_scheme_88"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 1), "scheme_authenticated"), json_false()); j_group = json_object_get(json_object_get(j_element, "schemes"), "1"); ck_assert_int_eq(json_array_size(j_group), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_95"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); } else if (0 == o_strcmp("scope2", key)) { ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 1); j_group = json_object_get(json_object_get(j_element, "schemes"), "2"); ck_assert_int_eq(json_array_size(j_group), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_95"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); } else if (0 == o_strcmp("scope3", key)) { ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 1); j_group = json_object_get(json_object_get(j_element, "schemes"), "3"); ck_assert_int_eq(json_array_size(j_group), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_88"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); } else { // This should not happen, end test ck_assert_int_eq(0, 1); } } json_decref(j_body); ulfius_clean_request(&check_req); ulfius_clean_response(&check_resp); } END_TEST START_TEST(test_glwd_auth_check_scheme_session_password_schemes) { struct _u_request auth_req, check_req; struct _u_response auth_resp, check_resp; json_t * j_body, * j_element = NULL, * j_group, * j_register; char * cookie; const char * key = NULL; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&check_req); ulfius_init_response(&check_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(check_req.map_header, "Cookie", cookie); u_map_put(auth_req.map_header, "Cookie", cookie); o_free(cookie); check_req.http_verb = strdup("GET"); check_req.http_url = msprintf("%s/auth/scheme/?scope=%s", SERVER_URI, SCOPE_LIST_ALLOWED); ck_assert_int_eq(ulfius_send_http_request(&check_req, &check_resp), U_OK); ck_assert_int_eq(check_resp.status, 200); j_body = ulfius_get_json_body_response(&check_resp, NULL); ulfius_clean_response(&check_resp); ck_assert_int_eq(json_object_size(j_body), 3); json_object_foreach(j_body, key, j_element) { ck_assert_ptr_eq(json_object_get(j_element, "password_authenticated"), json_true()); if (0 == o_strcmp("scope2", key)) { ck_assert_ptr_eq(json_object_get(j_element, "password_required"), json_false()); } else { ck_assert_ptr_eq(json_object_get(j_element, "password_required"), json_true()); } ck_assert_ptr_eq(json_object_get(j_element, "available"), json_true()); if (0 == o_strcmp("scope1", key)) { ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 2); j_group = json_object_get(json_object_get(j_element, "schemes"), "0"); ck_assert_int_eq(json_array_size(j_group), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_42"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 1), "scheme_name")), "mock_scheme_88"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 1), "scheme_authenticated"), json_false()); j_group = json_object_get(json_object_get(j_element, "schemes"), "1"); ck_assert_int_eq(json_array_size(j_group), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_95"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); } else if (0 == o_strcmp("scope2", key)) { ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 1); j_group = json_object_get(json_object_get(j_element, "schemes"), "2"); ck_assert_int_eq(json_array_size(j_group), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_95"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); } else if (0 == o_strcmp("scope3", key)) { ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 1); j_group = json_object_get(json_object_get(j_element, "schemes"), "3"); ck_assert_int_eq(json_array_size(j_group), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_88"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); } else { // This should not happen, end test ck_assert_int_eq(0, 1); } } json_decref(j_body); j_register = json_pack("{sssssss{so}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); // Authenticate scheme mock 42 j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_init_response(&check_resp); ck_assert_int_eq(ulfius_send_http_request(&check_req, &check_resp), U_OK); ck_assert_int_eq(check_resp.status, 200); j_body = ulfius_get_json_body_response(&check_resp, NULL); ulfius_clean_response(&check_resp); ck_assert_int_eq(json_object_size(j_body), 3); json_object_foreach(j_body, key, j_element) { ck_assert_ptr_eq(json_object_get(j_element, "password_authenticated"), json_true()); if (0 == o_strcmp("scope2", key)) { ck_assert_ptr_eq(json_object_get(j_element, "password_required"), json_false()); } else { ck_assert_ptr_eq(json_object_get(j_element, "password_required"), json_true()); } ck_assert_ptr_eq(json_object_get(j_element, "available"), json_true()); if (0 == o_strcmp("scope1", key)) { ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 2); j_group = json_object_get(json_object_get(j_element, "schemes"), "0"); ck_assert_int_eq(json_array_size(j_group), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_42"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_true()); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 1), "scheme_name")), "mock_scheme_88"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 1), "scheme_authenticated"), json_false()); j_group = json_object_get(json_object_get(j_element, "schemes"), "1"); ck_assert_int_eq(json_array_size(j_group), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_95"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); } else if (0 == o_strcmp("scope2", key)) { ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 1); j_group = json_object_get(json_object_get(j_element, "schemes"), "2"); ck_assert_int_eq(json_array_size(j_group), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_95"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); } else if (0 == o_strcmp("scope3", key)) { ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 1); j_group = json_object_get(json_object_get(j_element, "schemes"), "3"); ck_assert_int_eq(json_array_size(j_group), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_88"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); } else { // This should not happen, end test ck_assert_int_eq(0, 1); } } json_decref(j_body); j_register = json_pack("{sssssss{so}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "register", json_false()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); j_register = json_pack("{sssssss{so}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); // Authenticate scheme mock 88 j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_init_response(&check_resp); ck_assert_int_eq(ulfius_send_http_request(&check_req, &check_resp), U_OK); ck_assert_int_eq(check_resp.status, 200); j_body = ulfius_get_json_body_response(&check_resp, NULL); ulfius_clean_response(&check_resp); ck_assert_int_eq(json_object_size(j_body), 3); json_object_foreach(j_body, key, j_element) { ck_assert_ptr_eq(json_object_get(j_element, "password_authenticated"), json_true()); if (0 == o_strcmp("scope2", key)) { ck_assert_ptr_eq(json_object_get(j_element, "password_required"), json_false()); } else { ck_assert_ptr_eq(json_object_get(j_element, "password_required"), json_true()); } ck_assert_ptr_eq(json_object_get(j_element, "available"), json_true()); if (0 == o_strcmp("scope1", key)) { ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 2); j_group = json_object_get(json_object_get(j_element, "schemes"), "0"); ck_assert_int_eq(json_array_size(j_group), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_42"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 1), "scheme_name")), "mock_scheme_88"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 1), "scheme_authenticated"), json_true()); j_group = json_object_get(json_object_get(j_element, "schemes"), "1"); ck_assert_int_eq(json_array_size(j_group), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_95"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); } else if (0 == o_strcmp("scope2", key)) { ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 1); j_group = json_object_get(json_object_get(j_element, "schemes"), "2"); ck_assert_int_eq(json_array_size(j_group), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_95"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); } else if (0 == o_strcmp("scope3", key)) { ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 1); j_group = json_object_get(json_object_get(j_element, "schemes"), "3"); ck_assert_int_eq(json_array_size(j_group), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_88"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_true()); } else { // This should not happen, end test ck_assert_int_eq(0, 1); } } json_decref(j_body); j_register = json_pack("{sssssss{so}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "register", json_false()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); j_register = json_pack("{sssssss{so}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); // Authenticate scheme mock 95 j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_init_response(&check_resp); ck_assert_int_eq(ulfius_send_http_request(&check_req, &check_resp), U_OK); ck_assert_int_eq(check_resp.status, 200); j_body = ulfius_get_json_body_response(&check_resp, NULL); ck_assert_int_eq(json_object_size(j_body), 3); json_object_foreach(j_body, key, j_element) { ck_assert_ptr_eq(json_object_get(j_element, "password_authenticated"), json_true()); if (0 == o_strcmp("scope2", key)) { ck_assert_ptr_eq(json_object_get(j_element, "password_required"), json_false()); } else { ck_assert_ptr_eq(json_object_get(j_element, "password_required"), json_true()); } ck_assert_ptr_eq(json_object_get(j_element, "available"), json_true()); if (0 == o_strcmp("scope1", key)) { ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 2); j_group = json_object_get(json_object_get(j_element, "schemes"), "0"); ck_assert_int_eq(json_array_size(j_group), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_42"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 1), "scheme_name")), "mock_scheme_88"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 1), "scheme_authenticated"), json_false()); j_group = json_object_get(json_object_get(j_element, "schemes"), "1"); ck_assert_int_eq(json_array_size(j_group), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_95"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_true()); } else if (0 == o_strcmp("scope2", key)) { ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 1); j_group = json_object_get(json_object_get(j_element, "schemes"), "2"); ck_assert_int_eq(json_array_size(j_group), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_95"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_true()); } else if (0 == o_strcmp("scope3", key)) { ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 1); j_group = json_object_get(json_object_get(j_element, "schemes"), "3"); ck_assert_int_eq(json_array_size(j_group), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_88"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); } else { // This should not happen, end test ck_assert_int_eq(0, 1); } } json_decref(j_body); j_register = json_pack("{sssssss{so}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "register", json_false()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); ulfius_clean_request(&check_req); ulfius_clean_response(&check_resp); } END_TEST START_TEST(test_glwd_auth_check_scheme_forbidden) { struct _u_request auth_req, check_req; struct _u_response auth_resp, check_resp; json_t * j_body, * j_element = NULL, * j_group; char * cookie; const char * key = NULL; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&check_req); ulfius_init_response(&check_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(check_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); check_req.http_verb = strdup("GET"); check_req.http_url = msprintf("%s/auth/scheme/?scope=%s", SERVER_URI, SCOPE_LIST_ALLOWED " " SCOPE_LIST_FORBIDDEN); ck_assert_int_eq(ulfius_send_http_request(&check_req, &check_resp), U_OK); ck_assert_int_eq(check_resp.status, 200); j_body = ulfius_get_json_body_response(&check_resp, NULL); ck_assert_int_eq(json_object_size(j_body), 4); json_object_foreach(j_body, key, j_element) { if (0 != o_strcmp(SCOPE_LIST_FORBIDDEN, key)) { ck_assert_ptr_eq(json_object_get(j_element, "password_authenticated"), json_true()); if (0 == o_strcmp("scope2", key)) { ck_assert_ptr_eq(json_object_get(j_element, "password_required"), json_false()); } else { ck_assert_ptr_eq(json_object_get(j_element, "password_required"), json_true()); } ck_assert_ptr_eq(json_object_get(j_element, "available"), json_true()); } else { ck_assert_ptr_eq(json_object_get(j_element, "password_authenticated"), NULL); ck_assert_ptr_eq(json_object_get(j_element, "available"), json_false()); ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 0); } if (0 == o_strcmp("scope1", key)) { ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 2); j_group = json_object_get(json_object_get(j_element, "schemes"), "0"); ck_assert_int_eq(json_array_size(j_group), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_42"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 1), "scheme_name")), "mock_scheme_88"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 1), "scheme_authenticated"), json_false()); j_group = json_object_get(json_object_get(j_element, "schemes"), "1"); ck_assert_int_eq(json_array_size(j_group), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_95"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); } else if (0 == o_strcmp("scope2", key)) { ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 1); j_group = json_object_get(json_object_get(j_element, "schemes"), "2"); ck_assert_int_eq(json_array_size(j_group), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_95"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); } else if (0 == o_strcmp("scope3", key)) { ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 1); j_group = json_object_get(json_object_get(j_element, "schemes"), "3"); ck_assert_int_eq(json_array_size(j_group), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_88"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); } } json_decref(j_body); ulfius_clean_request(&check_req); ulfius_clean_response(&check_resp); } END_TEST START_TEST(test_glwd_auth_check_scheme_missing) { struct _u_request auth_req, check_req; struct _u_response auth_resp, check_resp; json_t * j_body, * j_element = NULL, * j_group; char * cookie; const char * key = NULL; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&check_req); ulfius_init_response(&check_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(check_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); check_req.http_verb = strdup("GET"); check_req.http_url = msprintf("%s/auth/scheme/?scope=%s", SERVER_URI, SCOPE_LIST_ALLOWED " " SCOPE_LIST_MISSING); ck_assert_int_eq(ulfius_send_http_request(&check_req, &check_resp), U_OK); ck_assert_int_eq(check_resp.status, 200); j_body = ulfius_get_json_body_response(&check_resp, NULL); ck_assert_int_eq(json_object_size(j_body), 3); json_object_foreach(j_body, key, j_element) { ck_assert_ptr_eq(json_object_get(j_element, "password_authenticated"), json_true()); if (0 == o_strcmp("scope2", key)) { ck_assert_ptr_eq(json_object_get(j_element, "password_required"), json_false()); } else { ck_assert_ptr_eq(json_object_get(j_element, "password_required"), json_true()); } ck_assert_ptr_eq(json_object_get(j_element, "available"), json_true()); if (0 == o_strcmp("scope1", key)) { ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 2); j_group = json_object_get(json_object_get(j_element, "schemes"), "0"); ck_assert_int_eq(json_array_size(j_group), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_42"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 1), "scheme_name")), "mock_scheme_88"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 1), "scheme_authenticated"), json_false()); j_group = json_object_get(json_object_get(j_element, "schemes"), "1"); ck_assert_int_eq(json_array_size(j_group), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_95"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); } else if (0 == o_strcmp("scope2", key)) { ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 1); j_group = json_object_get(json_object_get(j_element, "schemes"), "2"); ck_assert_int_eq(json_array_size(j_group), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_95"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); } else if (0 == o_strcmp("scope3", key)) { ck_assert_int_eq(json_object_size(json_object_get(j_element, "schemes")), 1); j_group = json_object_get(json_object_get(j_element, "schemes"), "3"); ck_assert_int_eq(json_array_size(j_group), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_group, 0), "scheme_name")), "mock_scheme_88"); ck_assert_ptr_eq(json_object_get(json_array_get(j_group, 0), "scheme_authenticated"), json_false()); } else { // This shouldn't happen ck_assert_int_eq(0, 1); } } json_decref(j_body); o_free(check_req.http_url); check_req.http_url = msprintf("%s/auth/scheme/?scope=%s", SERVER_URI, SCOPE_LIST_MISSING); ck_assert_int_eq(ulfius_send_http_request(&check_req, &check_resp), U_OK); ck_assert_int_eq(check_resp.status, 404); ulfius_clean_request(&check_req); ulfius_clean_response(&check_resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd auth check cheme"); tc_core = tcase_create("test_glwd_auth_check_scheme"); tcase_add_test(tc_core, test_glwd_auth_check_scheme_no_session); tcase_add_test(tc_core, test_glwd_auth_check_scheme_session_password); tcase_add_test(tc_core, test_glwd_auth_check_scheme_session_password_schemes); tcase_add_test(tc_core, test_glwd_auth_check_scheme_missing); tcase_add_test(tc_core, test_glwd_auth_check_scheme_forbidden); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed; Suite *s; SRunner *sr; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_auth_grant.c000066400000000000000000000121251415646314000205600ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api/" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "scope1 scope2" #define CLIENT "client1_id" struct _u_request user_req; START_TEST(test_glwd_auth_grant_error_parameters) { struct _u_response resp; json_t * j_body = NULL; ulfius_init_response(&resp); o_free(user_req.http_verb); o_free(user_req.http_url); user_req.http_verb = strdup("PUT"); user_req.http_url = msprintf("%s/auth/grant/error", SERVER_URI); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&user_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 404); ck_assert_int_eq(resp.nb_cookies, 0); ulfius_clean_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); ulfius_init_response(&resp); j_body = json_pack("{s[]}", "scope"); ulfius_set_json_body_request(&user_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 400); ck_assert_int_eq(resp.nb_cookies, 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); j_body = json_pack("{ss}", "scope", "error"); ulfius_set_json_body_request(&user_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ck_assert_int_eq(resp.nb_cookies, 0); ulfius_clean_response(&resp); } END_TEST START_TEST(test_glwd_auth_grant_success) { struct _u_response resp; json_t * j_body = NULL; ulfius_init_response(&resp); o_free(user_req.http_verb); o_free(user_req.http_url); user_req.http_verb = strdup("PUT"); user_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&user_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_eq(resp.nb_cookies, 0); ulfius_clean_response(&resp); } END_TEST START_TEST(test_glwd_auth_grant_remove_success) { struct _u_response resp; json_t * j_body = NULL; ulfius_init_response(&resp); o_free(user_req.http_verb); o_free(user_req.http_url); user_req.http_verb = strdup("PUT"); user_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&user_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_eq(resp.nb_cookies, 0); ulfius_clean_response(&resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd auth grant"); tc_core = tcase_create("test_glwd_auth_grant"); tcase_add_test(tc_core, test_glwd_auth_grant_error_parameters); tcase_add_test(tc_core, test_glwd_auth_grant_success); tcase_add_test(tc_core, test_glwd_auth_grant_remove_success); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req; struct _u_response auth_resp, scope_resp; json_t * j_body; int res, do_test = 0, i; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_request(&scope_req); ulfius_init_response(&auth_resp); ulfius_init_response(&scope_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define USERNAME2 "user2" #define USERNAME_ADMIN "admin" #define PASSWORD "password" #define PASSWORD_ADMIN "password" #define SCOPE_MAX_AGE "g_profile" struct _u_request admin_req; START_TEST(test_glwd_auth_password_error_parameters) { struct _u_request req; struct _u_response resp; json_t * j_body = NULL; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/", SERVER_URI); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 400); ck_assert_int_eq(resp.nb_cookies, 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); j_body = json_pack("{}"); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 400); ck_assert_int_eq(resp.nb_cookies, 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); j_body = json_pack("{ss}", "username", USERNAME); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ck_assert_int_eq(resp.nb_cookies, 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); j_body = json_pack("{ss}", "username", ""); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 400); ck_assert_int_eq(resp.nb_cookies, 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); j_body = json_pack("{ss}", "password", PASSWORD); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 400); ck_assert_int_eq(resp.nb_cookies, 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); j_body = json_pack("{sisi}", "username", 42, "password", 84); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 400); ck_assert_int_eq(resp.nb_cookies, 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); j_body = json_pack("{ssss}", "username", "", "password", ""); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 400); ck_assert_int_eq(resp.nb_cookies, 0); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_glwd_auth_password_error_login) { struct _u_request req; struct _u_response resp; json_t * j_body = NULL; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", "error"); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ck_assert_int_eq(resp.nb_cookies, 0); j_body = json_pack("{ssss}", "username", "error", "password", PASSWORD); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ck_assert_int_eq(resp.nb_cookies, 0); j_body = json_pack("{ssss}", "username", "error", "password", "error"); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ck_assert_int_eq(resp.nb_cookies, 0); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_glwd_auth_password_login_success) { struct _u_request req; struct _u_response resp; json_t * j_body = NULL; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_eq(resp.nb_cookies, 1); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_glwd_auth_password_login_multiple) { struct _u_request req, auth_req; struct _u_response resp, auth_resp; json_t * j_body = NULL; char * cookie; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_url = msprintf("%s/profile_list/", SERVER_URI); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 401); ulfius_clean_response(&auth_resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_eq(resp.nb_cookies, 1); cookie = msprintf("%s=%s", resp.map_cookie[0].key, resp.map_cookie[0].value); u_map_put(req.map_header, "Cookie", cookie); u_map_put(auth_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&resp); ulfius_init_response(&auth_resp); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); j_body = ulfius_get_json_body_response(&auth_resp, NULL); ck_assert_int_eq(json_array_size(j_body), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_body, 0), "username")), USERNAME); json_decref(j_body); ulfius_clean_response(&auth_resp); ulfius_init_response(&resp); j_body = json_pack("{ssss}", "username", USERNAME2, "password", PASSWORD); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_eq(resp.nb_cookies, 1); ulfius_clean_response(&resp); sleep(2); ulfius_init_response(&auth_resp); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); j_body = ulfius_get_json_body_response(&auth_resp, NULL); ck_assert_int_eq(json_array_size(j_body), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_body, 0), "username")), USERNAME2); json_decref(j_body); ulfius_clean_response(&auth_resp); ulfius_clean_request(&req); ulfius_clean_request(&auth_req); } END_TEST START_TEST(test_glwd_auth_password_login_multiple_toggle_current_user) { struct _u_request req, auth_req; struct _u_response resp, auth_resp; json_t * j_body = NULL; char * cookie; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_init_request(&auth_req); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_eq(resp.nb_cookies, 1); cookie = msprintf("%s=%s", resp.map_cookie[0].key, resp.map_cookie[0].value); u_map_put(req.map_header, "Cookie", cookie); u_map_put(auth_req.map_header, "Cookie", cookie); o_free(cookie); j_body = json_pack("{ssss}", "username", USERNAME2, "password", PASSWORD); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_eq(resp.nb_cookies, 1); ulfius_init_response(&auth_resp); auth_req.http_url = msprintf("%s/profile_list/", SERVER_URI); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); j_body = ulfius_get_json_body_response(&auth_resp, NULL); ck_assert_int_eq(json_array_size(j_body), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_body, 0), "username")), USERNAME2); json_decref(j_body); ulfius_clean_response(&auth_resp); sleep(1); j_body = json_pack("{ss}", "username", USERNAME); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_eq(resp.nb_cookies, 1); ulfius_init_response(&auth_resp); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); j_body = ulfius_get_json_body_response(&auth_resp, NULL); ck_assert_int_eq(json_array_size(j_body), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_body, 0), "username")), USERNAME); json_decref(j_body); ulfius_clean_response(&auth_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); ulfius_clean_request(&auth_req); } END_TEST START_TEST(test_glwd_auth_password_max_age_scope_set_OK) { char * url = msprintf("%s/scope/%s", SERVER_URI, SCOPE_MAX_AGE); json_t * j_parameters = json_pack("{ss ss ss so si}", "name", SCOPE_MAX_AGE, "display_name", "Glewlwyd profile", "description", "Access to the user's profile API", "password_required", json_true(), "password_max_age", 1); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_auth_password_max_age) { struct _u_request req, scope_req; struct _u_response resp, scope_resp; json_t * j_body = NULL; char * cookie; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_init_request(&scope_req); ulfius_init_response(&scope_resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_eq(resp.nb_cookies, 1); cookie = msprintf("%s=%s", resp.map_cookie[0].key, resp.map_cookie[0].value); u_map_put(scope_req.map_header, "Cookie", cookie); o_free(cookie); // First check for scope 3, password_authenticated should be true scope_req.http_verb = strdup("GET"); scope_req.http_url = msprintf("%s/auth/scheme/?scope=%s", SERVER_URI, SCOPE_MAX_AGE); ck_assert_int_eq(ulfius_send_http_request(&scope_req, &scope_resp), U_OK); ck_assert_int_eq(scope_resp.status, 200); ck_assert_ptr_ne(j_body = ulfius_get_json_body_response(&scope_resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(json_object_get(j_body, SCOPE_MAX_AGE), "password_authenticated"), json_true()); json_decref(j_body); ulfius_clean_response(&scope_resp); sleep(2); // Second check for scope 3, password_authenticated should be false ulfius_init_response(&scope_resp); ck_assert_int_eq(ulfius_send_http_request(&scope_req, &scope_resp), U_OK); ck_assert_int_eq(scope_resp.status, 200); ck_assert_ptr_ne(j_body = ulfius_get_json_body_response(&scope_resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(json_object_get(j_body, SCOPE_MAX_AGE), "password_authenticated"), json_false()); json_decref(j_body); ulfius_clean_response(&scope_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); ulfius_clean_request(&scope_req); } END_TEST START_TEST(test_glwd_auth_password_max_age_scope_reset_OK) { char * url = msprintf("%s/scope/%s", SERVER_URI, SCOPE_MAX_AGE); json_t * j_parameters = json_pack("{ss ss ss so si}", "name", SCOPE_MAX_AGE, "display_name", "Glewlwyd profile", "description", "Access to the user's profile API", "password_required", json_true(), "password_max_age", 600); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd auth password"); tc_core = tcase_create("test_glwd_auth_password"); tcase_add_test(tc_core, test_glwd_auth_password_error_parameters); tcase_add_test(tc_core, test_glwd_auth_password_error_login); tcase_add_test(tc_core, test_glwd_auth_password_login_success); tcase_add_test(tc_core, test_glwd_auth_password_login_multiple); tcase_add_test(tc_core, test_glwd_auth_password_login_multiple_toggle_current_user); tcase_add_test(tc_core, test_glwd_auth_password_max_age_scope_set_OK); tcase_add_test(tc_core, test_glwd_auth_password_max_age); tcase_add_test(tc_core, test_glwd_auth_password_max_age_scope_reset_OK); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0, i; json_t * j_body; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME_ADMIN, "password", PASSWORD_ADMIN); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api/" #define USERNAME "user1" #define PASSWORD "password" #define NAME "Dave Lopper1" #define EMAIL "user1@glewlwyd" struct _u_request user_req; START_TEST(test_glwd_auth_profile_get_error) { ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "profile_list/", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_auth_profile_get) { json_t * j_result = json_string(USERNAME); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "profile_list/", NULL, NULL, NULL, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_result); } END_TEST START_TEST(test_glwd_auth_update_error) { json_t * j_profile = json_pack("[{ss}]", "name", NAME); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "profile/", NULL, NULL, j_profile, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_profile); } END_TEST START_TEST(test_glwd_auth_update_ok) { json_t * j_profile = json_pack("{ssss}", "name", NAME "-new", "email", EMAIL "-new"); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "profile/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssssss}", "username", USERNAME, "name", NAME "-new", "email", EMAIL "-new"); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "profile_list/", NULL, NULL, NULL, NULL, 200, j_profile, NULL, NULL), 1); json_decref(j_profile); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd auth profile"); tc_core = tcase_create("test_glwd_auth_profile"); tcase_add_test(tc_core, test_glwd_auth_profile_get_error); tcase_add_test(tc_core, test_glwd_auth_profile_get); tcase_add_test(tc_core, test_glwd_auth_update_error); tcase_add_test(tc_core, test_glwd_auth_update_ok); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCHEME_TYPE "mock" #define SCHEME_NAME "mock_scheme_42" #define SCHEME_DISPLAY_NAME "Mock 42" #define SCHEME_VALUE "42" #define SCOPE_LIST "scope1" struct _u_request user_req; START_TEST(test_glwd_auth_profile_get_scheme_available_error_auth) { ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/profile/scheme/", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_auth_profile_get_scheme_available_success) { json_t * j_expected = json_pack("{ssssss}", "module", "mock", "name", "mock_scheme_42", "display_name", "Mock 42"); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/profile/scheme/", NULL, NULL, NULL, NULL, 200, j_expected, NULL, NULL), 1); json_decref(j_expected); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd auth profile get scheme available"); tc_core = tcase_create("test_glwd_auth_profile_get_scheme_available"); tcase_add_test(tc_core, test_glwd_auth_profile_get_scheme_available_error_auth); tcase_add_test(tc_core, test_glwd_auth_profile_get_scheme_available_success); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req; struct _u_response auth_resp, scope_resp; json_t * j_body; int res, do_test = 0, i; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_request(&scope_req); ulfius_init_response(&auth_resp); ulfius_init_response(&scope_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "admin" #define PASSWORD "password" #define SCOPE_LIST "g_admin" #define USERNAME_IMPERSONATE "user1" #define NAME "Dave Lopper1" #define EMAIL "user1@glewlwyd" #define PROFILE_PASSWORD "password" #define PROFILE_NEW_PASSWORD "newpassword" struct _u_request admin_req; START_TEST(test_glwd_auth_profile_impersonate_update_ok) { json_t * j_profile = json_pack("{ssss}", "name", NAME "-new", "email", EMAIL "-new"); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/delegate/" USERNAME_IMPERSONATE "/profile", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssssss}", "username", USERNAME_IMPERSONATE, "name", NAME "-new", "email", EMAIL "-new"); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/user/" USERNAME_IMPERSONATE, NULL, NULL, NULL, NULL, 200, j_profile, NULL, NULL), 1); json_decref(j_profile); } END_TEST START_TEST(test_glwd_auth_profile_impersonate_profile_get_scheme_available_success) { json_t * j_expected = json_pack("{ssssss}", "module", "mock", "name", "mock_scheme_42", "display_name", "Mock 42"); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/delegate/" USERNAME_IMPERSONATE "/profile/scheme", NULL, NULL, NULL, NULL, 200, j_expected, NULL, NULL), 1); json_decref(j_expected); } END_TEST START_TEST(test_glwd_auth_profile_admin_profile_get_scheme_available_empty_success) { json_t * j_response; struct _u_request req; struct _u_response resp; ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_copy_request(&req, &admin_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/profile/scheme", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_response = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_int_eq(0, json_array_size(j_response)); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_glwd_auth_profile_impersonate_session_manage_list) { ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/delegate/" USERNAME_IMPERSONATE "/profile/session", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd auth profile impersonate"); tc_core = tcase_create("test_glwd_auth_profile_impersonate"); tcase_add_test(tc_core, test_glwd_auth_profile_impersonate_update_ok); tcase_add_test(tc_core, test_glwd_auth_profile_impersonate_profile_get_scheme_available_success); tcase_add_test(tc_core, test_glwd_auth_profile_admin_profile_get_scheme_available_empty_success); tcase_add_test(tc_core, test_glwd_auth_profile_impersonate_session_manage_list); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0, i; json_t * j_body; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define USERNAME2 "user3" #define SCHEME_TYPE "mock" #define SCHEME_NAME "mock_scheme_42" #define SCHEME_VALUE "42" #define PASSWORD "password" START_TEST(test_glwd_auth_scheme_error_parameters) { struct _u_request req; struct _u_response resp; json_t * j_body = NULL; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/", SERVER_URI); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 400); ck_assert_int_eq(resp.nb_cookies, 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); j_body = json_pack("{sssss{}}", "username", USERNAME, "scheme_type", SCHEME_TYPE, "value"); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 400); ck_assert_int_eq(resp.nb_cookies, 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); j_body = json_pack("{sssssss[]}", "username", USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value"); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 400); ck_assert_int_eq(resp.nb_cookies, 0); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_glwd_auth_scheme_error_login) { struct _u_request req; struct _u_response resp; json_t * j_body = NULL; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value", "code", "error"); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ck_assert_int_eq(resp.nb_cookies, 0); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_glwd_auth_scheme_login_success) { struct _u_request req; struct _u_response resp; json_t * j_body = NULL; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value", "code", SCHEME_VALUE); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_eq(resp.nb_cookies, 1); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_glwd_auth_scheme_login_multiple) { struct _u_request req, auth_req; struct _u_response resp, auth_resp; json_t * j_body = NULL; char * cookie; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_url = msprintf("%s/profile_list/", SERVER_URI); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 401); ulfius_clean_response(&auth_resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value", "code", SCHEME_VALUE); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_eq(resp.nb_cookies, 1); cookie = msprintf("%s=%s", resp.map_cookie[0].key, resp.map_cookie[0].value); u_map_put(req.map_header, "Cookie", cookie); u_map_put(auth_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&resp); ulfius_init_response(&auth_resp); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); j_body = ulfius_get_json_body_response(&auth_resp, NULL); ck_assert_int_eq(json_array_size(j_body), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_body, 0), "username")), USERNAME); json_decref(j_body); ulfius_clean_response(&auth_resp); ulfius_init_response(&resp); j_body = json_pack("{sssssss{ss}}", "username", USERNAME2, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value", "code", SCHEME_VALUE); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_eq(resp.nb_cookies, 1); ulfius_clean_response(&resp); ulfius_init_response(&auth_resp); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); j_body = ulfius_get_json_body_response(&auth_resp, NULL); ck_assert_int_eq(json_array_size(j_body), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_body, 0), "username")), USERNAME2); json_decref(j_body); ulfius_clean_response(&auth_resp); ulfius_clean_request(&req); ulfius_clean_request(&auth_req); } END_TEST START_TEST(test_glwd_auth_scheme_error_identify) { struct _u_request req; struct _u_response resp; json_t * j_body = NULL; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssss{ssss}}", "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value", "username", USERNAME, "code", "error"); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ck_assert_int_eq(resp.nb_cookies, 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); j_body = json_pack("{sssss{ssss}}", "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value", "username", "error", "code", SCHEME_VALUE); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ck_assert_int_eq(resp.nb_cookies, 0); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_glwd_auth_scheme_identify_success) { struct _u_request req; struct _u_response resp; json_t * j_body = NULL; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssss{ssss}}", "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value", "username", USERNAME, "code", SCHEME_VALUE); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_eq(resp.nb_cookies, 1); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd auth scheme"); tc_core = tcase_create("test_glwd_auth_scheme"); tcase_add_test(tc_core, test_glwd_auth_scheme_error_parameters); tcase_add_test(tc_core, test_glwd_auth_scheme_error_login); tcase_add_test(tc_core, test_glwd_auth_scheme_login_success); tcase_add_test(tc_core, test_glwd_auth_scheme_login_multiple); tcase_add_test(tc_core, test_glwd_auth_scheme_error_identify); tcase_add_test(tc_core, test_glwd_auth_scheme_identify_success); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, user_req; struct _u_response auth_resp; json_t * j_body, * j_register; int res, do_test = 0, i; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCHEME_TYPE "mock" #define SCHEME_NAME "mock_scheme_42" #define SCHEME_DISPLAY_NAME "Mock 42" #define SCHEME_VALUE "42" #define SCOPE_LIST "scope1" struct _u_request user_req; START_TEST(test_glwd_auth_scheme_register_error_parameters) { json_t * j_register; j_register = json_pack("{ssssssss}", "username", USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value", "register"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_register); j_register = json_pack("{sssisss{so}}", "username", USERNAME, "scheme_type", 42, "scheme_name", SCHEME_NAME, "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_register); j_register = json_pack("{sssssis{so}}", "username", USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", 42, "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_register); j_register = json_pack("{sssssss{so}}", "username", "user2", "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_register); j_register = json_pack("{sssss{so}}", "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_register); j_register = json_pack("{sssssss{so}}", "username", USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value", "register", json_true()); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_register); } END_TEST START_TEST(test_glwd_auth_scheme_register_success) { json_t * j_body, * j_expected; struct _u_response resp; json_t * j_canuse = json_pack("{ssss}", "module", SCHEME_TYPE, "name", SCHEME_NAME); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/profile/scheme/", NULL, NULL, NULL, NULL, 200, j_canuse, NULL, NULL), 1); ulfius_init_response(&resp); o_free(user_req.http_verb); o_free(user_req.http_url); user_req.http_verb = o_strdup("GET"); user_req.http_url = msprintf("%s/auth/scheme/?scope=%s", SERVER_URI, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); j_body = ulfius_get_json_body_response(&resp, NULL); ulfius_clean_response(&resp); j_expected = json_pack("{sssssssoso}", "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "scheme_display_name", SCHEME_DISPLAY_NAME, "scheme_authenticated", json_false(), "scheme_registered", json_false()); ck_assert_int_eq(json_equal(json_array_get(json_object_get(json_object_get(json_object_get(j_body, "scope1"), "schemes"), "0"), 0), j_expected), 1); json_decref(j_body); json_decref(j_expected); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value", "code", SCHEME_VALUE); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/auth/", NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ssssss}", "username", USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{sssssss{so}}", "username", USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ssssss}", "username", USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); j_body = ulfius_get_json_body_response(&resp, NULL); ulfius_clean_response(&resp); j_expected = json_pack("{sssssssososi}", "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "scheme_display_name", SCHEME_DISPLAY_NAME, "scheme_authenticated", json_false(), "scheme_registered", json_true(), "scheme_last_login", 0); ck_assert_int_eq(json_equal(json_array_get(json_object_get(json_object_get(json_object_get(j_body, "scope1"), "schemes"), "0"), 0), j_expected), 1); json_decref(j_body); json_decref(j_expected); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value", "code", SCHEME_VALUE); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/auth/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); j_body = ulfius_get_json_body_response(&resp, NULL); ulfius_clean_response(&resp); j_expected = json_pack("{sssssssoso}", "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "scheme_display_name", SCHEME_DISPLAY_NAME, "scheme_authenticated", json_true(), "scheme_registered", json_true()); ck_assert_ptr_ne(json_search(json_array_get(json_object_get(json_object_get(json_object_get(j_body, "scope1"), "schemes"), "0"), 0), j_expected), NULL); json_decref(j_body); json_decref(j_expected); json_decref(j_canuse); } END_TEST START_TEST(test_glwd_auth_scheme_deregister_success) { json_t * j_body, * j_expected; struct _u_response resp; ulfius_init_response(&resp); o_free(user_req.http_verb); o_free(user_req.http_url); user_req.http_verb = o_strdup("GET"); user_req.http_url = msprintf("%s/auth/scheme/?scope=%s", SERVER_URI, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); j_body = ulfius_get_json_body_response(&resp, NULL); ulfius_clean_response(&resp); j_expected = json_pack("{sssssssoso}", "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "scheme_display_name", SCHEME_DISPLAY_NAME, "scheme_authenticated", json_true(), "scheme_registered", json_true()); ck_assert_ptr_ne(json_search(json_array_get(json_object_get(json_object_get(json_object_get(j_body, "scope1"), "schemes"), "0"), 0), j_expected), NULL); json_decref(j_body); json_decref(j_expected); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value", "code", SCHEME_VALUE); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/auth/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{sssssss{so}}", "username", USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value", "register", json_false()); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); j_body = ulfius_get_json_body_response(&resp, NULL); ulfius_clean_response(&resp); j_expected = json_pack("{sssssssoso}", "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "scheme_display_name", SCHEME_DISPLAY_NAME, "scheme_authenticated", json_false(), "scheme_registered", json_false()); ck_assert_int_eq(json_equal(json_array_get(json_object_get(json_object_get(json_object_get(j_body, "scope1"), "schemes"), "0"), 0), j_expected), 1); json_decref(j_body); json_decref(j_expected); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value", "code", SCHEME_VALUE); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/auth/", NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd auth scheme register"); tc_core = tcase_create("test_glwd_auth_scheme_register"); tcase_add_test(tc_core, test_glwd_auth_scheme_register_error_parameters); tcase_add_test(tc_core, test_glwd_auth_scheme_register_success); tcase_add_test(tc_core, test_glwd_auth_scheme_deregister_success); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req; struct _u_response auth_resp, scope_resp; json_t * j_body; int res, do_test = 0, i; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_request(&scope_req); ulfius_init_response(&auth_resp); ulfius_init_response(&scope_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define USERNAME_ERROR_USE "user2" #define SCHEME_TYPE "mock" #define SCHEME_NAME "mock_scheme_42" START_TEST(test_glwd_auth_scheme_trigger_error_parameters) { struct _u_request req; struct _u_response resp; json_t * j_body = NULL; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/scheme/trigger/", SERVER_URI); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 400); ulfius_clean_response(&resp); ulfius_init_response(&resp); j_body = json_pack("{sssss{ss}}", "username", USERNAME, "scheme_type", SCHEME_TYPE, "value", "data", "grut"); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 400); ulfius_clean_response(&resp); ulfius_init_response(&resp); j_body = json_pack("{sssss{ss}}", "username", USERNAME, "scheme_name", SCHEME_NAME, "value", "data", "grut"); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 400); ulfius_clean_response(&resp); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_auth_scheme_trigger_error_login) { struct _u_request req; struct _u_response resp; json_t * j_body = NULL; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/scheme/trigger/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME_ERROR_USE, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value", "data", "grut"); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_glwd_auth_scheme_trigger_success) { struct _u_request req; struct _u_response resp; json_t * j_body = NULL; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/scheme/trigger/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value", "data", "grut"); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_glwd_auth_scheme_trigger_identify_success) { struct _u_request req; struct _u_response resp; json_t * j_body = NULL; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/scheme/trigger/", SERVER_URI); j_body = json_pack("{sssss{ss}}", "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME, "value", "data", "grut"); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd auth scheme trigger"); tc_core = tcase_create("test_glwd_auth_scheme_trigger"); tcase_add_test(tc_core, test_glwd_auth_scheme_trigger_error_parameters); tcase_add_test(tc_core, test_glwd_auth_scheme_trigger_error_login); tcase_add_test(tc_core, test_glwd_auth_scheme_trigger_success); tcase_add_test(tc_core, test_glwd_auth_scheme_trigger_identify_success); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, user_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile" struct _u_request user_req; char user_agent[33]; START_TEST(test_glwd_auth_session_manage_endpoints_noauth) { ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/profile/session/", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "DELETE", SERVER_URI "/profile/session/test", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_auth_session_manage_list) { struct _u_response resp; json_t * j_body = NULL; ulfius_init_response(&resp); user_req.http_url = o_strdup(SERVER_URI "/profile/session/"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); json_decref(j_body); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/profile/session/?limit=1"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(json_array_size(j_body), 1); json_decref(j_body); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/profile/session/?offset=1"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); json_decref(j_body); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/profile/session/?sort=authorization_type"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); json_decref(j_body); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/profile/session/?sort=issued_at&desc"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); json_decref(j_body); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/profile/session/?sort=issued_at&desc&limit=1"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(json_array_size(j_body), 1); json_decref(j_body); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/profile/session/?pattern=%s", user_agent); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); json_decref(j_body); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/profile/session/?pattern=glwd-auth-test-"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); json_decref(j_body); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/profile/session/?pattern=error"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(json_array_size(j_body), 0); ulfius_clean_response(&resp); json_decref(j_body); } END_TEST START_TEST(test_glwd_auth_session_manage_delete_not_found) { struct _u_response resp; ulfius_init_response(&resp); o_free(user_req.http_url); o_free(user_req.http_verb); user_req.http_url = o_strdup(SERVER_URI "/profile/session/error"); user_req.http_verb = o_strdup("DELETE"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 404); ulfius_clean_response(&resp); } END_TEST START_TEST(test_glwd_auth_session_manage_delete_ok) { struct _u_request req, test_req; struct _u_response resp; char user_agent[33], * session_hash = NULL, * session_hash_encoded = NULL, * cookie = NULL; json_t * j_body; int x[1]; ulfius_init_request(&req); ulfius_init_request(&test_req); ulfius_init_response(&resp); test_req.http_url = o_strdup(SERVER_URI "/profile_list/"); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/", SERVER_URI); gnutls_rnd(GNUTLS_RND_NONCE, x, sizeof(int)); snprintf(user_agent, 32, "glwd-auth-test-%04d", x[0]); u_map_put(req.map_header, "User-Agent", user_agent); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); cookie = msprintf("%s=%s", resp.map_cookie[0].key, resp.map_cookie[0].value); u_map_put(test_req.map_header, "Cookie", cookie); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); o_free(user_req.http_verb); user_req.http_verb = strdup("GET"); user_req.http_url = msprintf(SERVER_URI "/profile/session/?pattern=%s", user_agent); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(json_array_size(j_body), 1); session_hash = o_strdup(json_string_value(json_object_get(json_array_get(j_body, 0), "session_hash"))); session_hash_encoded = ulfius_url_encode(session_hash); ulfius_clean_response(&resp); json_decref(j_body); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_send_http_request(&test_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_verb); o_free(user_req.http_url); user_req.http_verb = strdup("DELETE"); user_req.http_url = msprintf(SERVER_URI "/profile/session/%s", session_hash_encoded); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_send_http_request(&test_req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_clean_request(&test_req); o_free(session_hash); o_free(session_hash_encoded); o_free(cookie); } END_TEST START_TEST(test_glwd_auth_session_manage_delete_all_ok) { struct _u_request req, test_req; struct _u_response resp; char user_agent[33], * cookie = NULL; json_t * j_body; int x[1]; ulfius_init_request(&req); ulfius_init_request(&test_req); ulfius_init_response(&resp); test_req.http_url = o_strdup(SERVER_URI "/profile_list/"); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/", SERVER_URI); gnutls_rnd(GNUTLS_RND_NONCE, x, sizeof(int)); snprintf(user_agent, 32, "glwd-auth-test-%04d", x[0]); u_map_put(req.map_header, "User-Agent", user_agent); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); cookie = msprintf("%s=%s", resp.map_cookie[0].key, resp.map_cookie[0].value); u_map_put(test_req.map_header, "Cookie", cookie); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); o_free(user_req.http_verb); user_req.http_verb = strdup("GET"); user_req.http_url = msprintf(SERVER_URI "/profile/session/?pattern=%s", user_agent); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(json_array_size(j_body), 1); ulfius_clean_response(&resp); json_decref(j_body); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_send_http_request(&test_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_verb); o_free(user_req.http_url); user_req.http_verb = strdup("DELETE"); user_req.http_url = msprintf(SERVER_URI "/profile/session/"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_send_http_request(&test_req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_clean_request(&test_req); o_free(cookie); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd auth session manage"); tc_core = tcase_create("test_glwd_auth_session_manage"); tcase_add_test(tc_core, test_glwd_auth_session_manage_endpoints_noauth); tcase_add_test(tc_core, test_glwd_auth_session_manage_list); tcase_add_test(tc_core, test_glwd_auth_session_manage_delete_not_found); tcase_add_test(tc_core, test_glwd_auth_session_manage_delete_ok); tcase_add_test(tc_core, test_glwd_auth_session_manage_delete_all_ok); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i, x[1]; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); gnutls_rnd(GNUTLS_RND_NONCE, x, sizeof(int)); snprintf(user_agent, 32, "glwd-auth-test-%d", x[0]); u_map_put(auth_req.map_header, "User-Agent", user_agent); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "admin" #define PASSWORD "password" #define NEW_CLIENT_ID "test" #define NEW_NAME "Client 1" #define NEW_DESCRIPTION "Description for Client 1" #define NEW_SCOPE_1 "scope1" #define NEW_SCOPE_2 "scope2" #define MODULE_MODULE "mock" #define MODULE_NAME_1 "test1" #define MODULE_NAME_2 "test2" #define MODULE_DISPLAY_NAME "test name" #define MODULE_PREFIX_1 "mock1-" #define MODULE_PREFIX_2 "mock2-" struct _u_request admin_req; START_TEST(test_glwd_crud_client_get_list) { char * url = msprintf("%s/client/", SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_crud_client_add_error_json) { char * url = msprintf("%s/client/", SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_crud_client_add_error_param) { char * url = msprintf("%s/client/", SERVER_URI); json_t * j_parameters = json_pack("{ss}", "error", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("[{ss}]", "client_id", "test"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{si}", "client_id", 42); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(url); } END_TEST START_TEST(test_glwd_crud_client_add_OK) { json_t * j_parameters = json_pack("{ssssss}", "client_id", NEW_CLIENT_ID, "name", NEW_NAME, "description", NEW_DESCRIPTION); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/client/" NEW_CLIENT_ID, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_crud_client_add_already_present) { json_t * j_parameters = json_pack("{ss}", "client_id", NEW_CLIENT_ID); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_crud_client_get) { char * url = msprintf("%s/client/%s", SERVER_URI, NEW_CLIENT_ID), * url_404 = msprintf("%s/client/error", SERVER_URI); json_t * j_parameters = json_pack("{ssssss}", "client_id", NEW_CLIENT_ID, "name", NEW_NAME, "description", NEW_DESCRIPTION); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url_404, NULL, NULL, NULL, NULL, 404, NULL, NULL, NULL), 1); o_free(url); o_free(url_404); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_crud_client_set_OK) { char * url = msprintf("%s/client/%s", SERVER_URI, NEW_CLIENT_ID); json_t * j_parameters = json_pack("{sssss[s]}", "name", NEW_NAME "-new", "description", NEW_DESCRIPTION "-new", "scope", NEW_SCOPE_1); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_object_set_new(j_parameters, "client_id", json_string(NEW_CLIENT_ID)); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_crud_client_delete_error) { char * url = msprintf("%s/client/error", SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 404, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_crud_client_delete_OK) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" NEW_CLIENT_ID, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_crud_client_list_pattern) { json_t * j_result; int res; struct _u_response resp; ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/client/?pattern=client1", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "client_id")), "client1_id"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/client/?limit=2&pattern=client", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "client_id")), "client1_id"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "client_id")), "client2_id"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/client/?pattern=error", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 0); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/client/?pattern=client&limit=2&offset=1", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "client_id")), "client2_id"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "client_id")), "client3_id"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); } END_TEST START_TEST(test_glwd_crud_client_list_limit) { json_t * j_result; int res; struct _u_response resp; o_free(admin_req.http_url); o_free(admin_req.http_verb); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/client/?offset=1", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 3); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "client_id")), "client2_id"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "client_id")), "client3_id"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/client/?limit=2", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "client_id")), "client1_id"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "client_id")), "client2_id"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/client/?offset=1&limit=1", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "client_id")), "client2_id"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/client/?offset=2&limit=3", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "client_id")), "client3_id"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); } END_TEST START_TEST(test_glwd_crud_client_list_add_user_module_instances) { char * url = msprintf("%s/mod/client/", SERVER_URI); json_t * j_parameters = json_pack("{sssssssisos{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME_1, "display_name", MODULE_DISPLAY_NAME, "order_rank", 1, "readonly", json_true(), "parameters", "client-id-prefix", MODULE_PREFIX_1); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssisos{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME_2, "display_name", MODULE_DISPLAY_NAME, "order_rank", 2, "readonly", json_true(), "parameters", "client-id-prefix", MODULE_PREFIX_2); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_crud_client_list_page_multiple_source) { json_t * j_result; int res; struct _u_response resp; o_free(admin_req.http_url); o_free(admin_req.http_verb); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/client/?offset=2&limit=4", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 4); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "client_id")), "client3_id"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "client_id")), "client4_id"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 2), "client_id")), "mock1-client1_id"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 3), "client_id")), "mock1-client2_id"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/client/?offset=2&limit=6", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 6); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "client_id")), "client3_id"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "client_id")), "client4_id"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 2), "client_id")), "mock1-client1_id"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 3), "client_id")), "mock1-client2_id"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 4), "client_id")), "mock1-client3_id"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 5), "client_id")), "mock1-client4_id"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); } END_TEST START_TEST(test_glwd_crud_client_list_pattern_multiple_source) { json_t * j_result; int res; struct _u_response resp; ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/client/?pattern=client1", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 3); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "client_id")), "client1_id"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "client_id")), "mock1-client1_id"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 2), "client_id")), "mock2-client1_id"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/client/?offset=1&pattern=client1", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "client_id")), "mock1-client1_id"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "client_id")), "mock2-client1_id"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/client/?offset=1&pattern=client1&limit=1", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "client_id")), "mock1-client1_id"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/client/?pattern=error", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 0); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); } END_TEST START_TEST(test_glwd_crud_client_list_delete_user_module_instances) { char * url = msprintf("%s/mod/client/%s", SERVER_URI, MODULE_NAME_1); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/mod/client/%s", SERVER_URI, MODULE_NAME_2); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd CRUD client"); tc_core = tcase_create("test_glwd_crud_client"); tcase_add_test(tc_core, test_glwd_crud_client_get_list); tcase_add_test(tc_core, test_glwd_crud_client_add_error_json); tcase_add_test(tc_core, test_glwd_crud_client_add_error_param); tcase_add_test(tc_core, test_glwd_crud_client_add_OK); tcase_add_test(tc_core, test_glwd_crud_client_add_already_present); tcase_add_test(tc_core, test_glwd_crud_client_get); tcase_add_test(tc_core, test_glwd_crud_client_set_OK); tcase_add_test(tc_core, test_glwd_crud_client_delete_error); tcase_add_test(tc_core, test_glwd_crud_client_delete_OK); tcase_add_test(tc_core, test_glwd_crud_client_list_limit); tcase_add_test(tc_core, test_glwd_crud_client_list_pattern); tcase_add_test(tc_core, test_glwd_crud_client_list_add_user_module_instances); tcase_add_test(tc_core, test_glwd_crud_client_list_page_multiple_source); tcase_add_test(tc_core, test_glwd_crud_client_list_pattern_multiple_source); tcase_add_test(tc_core, test_glwd_crud_client_list_delete_user_module_instances); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0, i; json_t * j_body; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "admin" #define PASSWORD "password" #define SCOPE "test-scope" #define NAME "My New Scope" #define DESCRIPTION "Description for test-scope" #define GROUP1 "group1" #define GROUP2 "group2" #define SCHEME1 "mock_scheme_42" #define SCHEME2 "mock_scheme_88" #define SCHEME3 "mock_scheme_95" struct _u_request admin_req; START_TEST(test_glwd_crud_scope_get_list) { char * url = msprintf("%s/scope/", SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_crud_scope_add_error_json) { char * url = msprintf("%s/scope/", SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_crud_scope_add_error_param) { char * url = msprintf("%s/scope/", SERVER_URI); json_t * j_parameters = json_pack("{ss}", "error", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("[{ss}]", "name", SCOPE); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{si}", "name", 42); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssi}", "name", SCOPE, "display_name", 42); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssi}", "name", SCOPE, "display_name", NAME, "description", 42); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssi}", "name", SCOPE, "display_name", NAME, "description", DESCRIPTION, "password_required", 42); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{ss ss ss so s[]}", "name", SCOPE, "display_name", NAME, "description", DESCRIPTION, "password_required", json_true(), "scheme"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{ss ss ss so s{s{}}}", "name", SCOPE, "display_name", NAME, "description", DESCRIPTION, "password_required", json_true(), "scheme", GROUP1); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{ss ss ss so s{s[]}}", "name", SCOPE, "display_name", NAME, "description", DESCRIPTION, "password_required", json_true(), "scheme", GROUP1); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{ss ss ss so s{s[{ss}]}}", "name", SCOPE, "display_name", NAME, "description", DESCRIPTION, "password_required", json_true(), "scheme", GROUP1, "scheme_name", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{ss ss ss so s{s[{ss}]}ss}", "name", SCOPE, "display_name", NAME, "description", DESCRIPTION, "password_required", json_true(), "scheme", GROUP1, "scheme_name", SCHEME1, "scheme_required", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{ss ss ss so s{s[{ss}]}s{ss}}", "name", SCOPE, "display_name", NAME, "description", DESCRIPTION, "password_required", json_true(), "scheme", GROUP1, "scheme_name", SCHEME1, "scheme_required", GROUP1, "error"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(url); } END_TEST START_TEST(test_glwd_crud_scope_add_OK) { char * url = msprintf("%s/scope/", SERVER_URI); json_t * j_parameters = json_pack("{ss ss ss so s{s[{ssssss}]}s{si}}", "name", SCOPE, "display_name", NAME, "description", DESCRIPTION, "password_required", json_true(), "scheme", GROUP1, "scheme_name", SCHEME1, "scheme_display_name", "Mock 42", "scheme_type", "mock", "scheme_required", GROUP1, 1); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/scope/%s", SERVER_URI, SCOPE); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_crud_scope_add_already_present) { char * url = msprintf("%s/scope/", SERVER_URI); json_t * j_parameters = json_pack("{ss}", "scope", SCOPE); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_crud_scope_get) { json_t * j_parameters = json_pack("{ss ss ss so s{s[{ssssss}]}}", "name", SCOPE, "display_name", NAME, "description", DESCRIPTION, "password_required", json_true(), "scheme", GROUP1, "scheme_name", SCHEME1, "scheme_display_name", "Mock 42", "scheme_type", "mock"); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/scope/" SCOPE, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/scope/error", NULL, NULL, NULL, NULL, 404, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_crud_scope_set_OK) { char * url = msprintf("%s/scope/%s", SERVER_URI, SCOPE); json_t * j_parameters = json_pack("{ss ss so si s{s[{ss}]s[{ss}{ss}]}}", "display_name", NAME "-new", "description", DESCRIPTION "-new", "password_required", json_false(), "password_max_age", 0, "scheme", GROUP1 "-new", "scheme_name", SCHEME1, GROUP2, "scheme_name", SCHEME2, "scheme_name", SCHEME3); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_object_set_new(j_parameters, "name", json_string(SCOPE)); json_object_set_new(j_parameters, "scheme", json_pack("{s[{ssssss}]s[{ssssss}{ssssss}]}", GROUP1 "-new", "scheme_name", SCHEME1, "scheme_type", "mock", "scheme_display_name", "Mock 42", GROUP2, "scheme_name", SCHEME2, "scheme_type", "mock", "scheme_display_name", "Mock 88", "scheme_name", SCHEME3, "scheme_type", "mock", "scheme_display_name", "Mock 95")); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_crud_scope_delete_error) { char * url = msprintf("%s/scope/error", SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 404, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_crud_scope_delete_OK) { char * url = msprintf("%s/scope/%s", SERVER_URI, SCOPE); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_crud_scope_list_pattern) { json_t * j_result; int res; struct _u_response resp; ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/scope/?pattern=scope1", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "name")), "scope1"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/scope/?limit=2&pattern=scope", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "name")), "openid"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "name")), "scope1"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/scope/?pattern=error", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 0); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/scope/?pattern=scope&limit=2&offset=1", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "name")), "scope1"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "name")), "scope2"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); } END_TEST START_TEST(test_glwd_crud_scope_list_limit) { json_t * j_result; int res; struct _u_response resp; ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/scope/?offset=1", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 5); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "name")), "g_profile"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "name")), "openid"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 2), "name")), "scope1"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 3), "name")), "scope2"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 4), "name")), "scope3"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/scope/?limit=2", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "name")), "g_admin"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "name")), "g_profile"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/scope/?offset=1&limit=1", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "name")), "g_profile"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/scope/?offset=2&limit=3", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 3); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "name")), "scope1"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 2), "name")), "scope2"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "name")), "openid"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd CRUD scope"); tc_core = tcase_create("test_glwd_crud_scope"); tcase_add_test(tc_core, test_glwd_crud_scope_get_list); tcase_add_test(tc_core, test_glwd_crud_scope_add_error_json); tcase_add_test(tc_core, test_glwd_crud_scope_add_error_param); tcase_add_test(tc_core, test_glwd_crud_scope_add_OK); tcase_add_test(tc_core, test_glwd_crud_scope_add_already_present); tcase_add_test(tc_core, test_glwd_crud_scope_get); tcase_add_test(tc_core, test_glwd_crud_scope_set_OK); tcase_add_test(tc_core, test_glwd_crud_scope_delete_error); tcase_add_test(tc_core, test_glwd_crud_scope_delete_OK); tcase_add_test(tc_core, test_glwd_crud_scope_list_limit); tcase_add_test(tc_core, test_glwd_crud_scope_list_pattern); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0, i; json_t * j_body; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "admin" #define PASSWORD "password" #define NEW_USERNAME "test" #define NEW_NAME "Dave Lopper" #define NEW_EMAIL "test@glewlwyd" #define NEW_SCOPE_1 "scope1" #define NEW_SCOPE_2 "scope2" #define MODULE_MODULE "mock" #define MODULE_NAME_1 "test1" #define MODULE_NAME_2 "test2" #define MODULE_DISPLAY_NAME "test name" #define MODULE_PREFIX_1 "mock1-" #define MODULE_PREFIX_2 "mock2-" struct _u_request admin_req; START_TEST(test_glwd_crud_user_get_list) { char * url = msprintf("%s/user/", SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_crud_user_add_error_json) { char * url = msprintf("%s/user/", SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_crud_user_add_error_param) { char * url = msprintf("%s/user/", SERVER_URI); json_t * j_parameters = json_pack("{ss}", "error", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("[{ss}]", "username", "test"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{si}", "username", 42); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(url); } END_TEST START_TEST(test_glwd_crud_user_add_OK) { char * url = msprintf("%s/user/", SERVER_URI); json_t * j_parameters = json_pack("{sssssss[ss]}", "username", NEW_USERNAME, "name", NEW_NAME, "email", NEW_EMAIL, "scope", NEW_SCOPE_1, NEW_SCOPE_2); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/user/%s", SERVER_URI, NEW_USERNAME); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_crud_user_add_already_present) { char * url = msprintf("%s/user/", SERVER_URI); json_t * j_parameters = json_pack("{ss}", "username", NEW_USERNAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_crud_user_get) { char * url = msprintf("%s/user/%s", SERVER_URI, NEW_USERNAME), * url_404 = msprintf("%s/user/error", SERVER_URI); json_t * j_parameters = json_pack("{sssssss[ss]}", "username", NEW_USERNAME, "name", NEW_NAME, "email", NEW_EMAIL, "scope", NEW_SCOPE_1, NEW_SCOPE_2); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url_404, NULL, NULL, NULL, NULL, 404, NULL, NULL, NULL), 1); o_free(url); o_free(url_404); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_crud_user_set_OK) { char * url = msprintf("%s/user/%s", SERVER_URI, NEW_USERNAME); json_t * j_parameters = json_pack("{sssss[s]}", "name", NEW_NAME "-new", "email", NEW_EMAIL "-new", "scope", NEW_SCOPE_1); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_object_set_new(j_parameters, "username", json_string(NEW_USERNAME)); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_crud_user_delete_error) { char * url = msprintf("%s/user/error", SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 404, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_crud_user_delete_OK) { char * url = msprintf("%s/user/%s", SERVER_URI, NEW_USERNAME); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_crud_user_list_limit) { json_t * j_result; int res; struct _u_response resp; ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/user/?offset=2", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "username")), "user2"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "username")), "user3"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/user/?limit=2", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "username")), "admin"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "username")), "user1"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/user/?offset=1&limit=2", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "username")), "user1"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "username")), "user2"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/user/?offset=3&limit=3", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 1); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "username")), "user3"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); } END_TEST START_TEST(test_glwd_crud_user_list_pattern) { json_t * j_result; int res; struct _u_response resp; ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/user/?pattern=user", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 3); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "username")), "user1"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "username")), "user2"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 2), "username")), "user3"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/user/?pattern=error", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 0); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/user/?pattern=user&limit=2", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "username")), "user1"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "username")), "user2"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); } END_TEST START_TEST(test_glwd_crud_user_list_add_user_module_instances) { char * url = msprintf("%s/mod/user/", SERVER_URI); json_t * j_parameters = json_pack("{sssssssisos{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME_1, "display_name", MODULE_DISPLAY_NAME, "order_rank", 1, "readonly", json_true(), "parameters", "username-prefix", MODULE_PREFIX_1); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssisos{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME_2, "display_name", MODULE_DISPLAY_NAME, "order_rank", 2, "readonly", json_true(), "parameters", "username-prefix", MODULE_PREFIX_2); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_crud_user_list_pattern_multiple_source) { json_t * j_result; int res; struct _u_response resp; ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/user/?offset=2&limit=4&pattern=user", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 4); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "username")), "user3"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "username")), "mock1-user1"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 2), "username")), "mock1-user2"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 3), "username")), "mock1-user3"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/user/?offset=1&limit=8&pattern=user1", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 2); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "username")), "mock1-user1"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "username")), "mock2-user1"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); } END_TEST START_TEST(test_glwd_crud_user_list_page_multiple_source) { json_t * j_result; int res; struct _u_response resp; ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/user/?offset=3&limit=4", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 4); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "username")), "user3"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "username")), "mock1-admin"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 2), "username")), "mock1-user1"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 3), "username")), "mock1-user2"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); ulfius_init_response(&resp); admin_req.http_url = msprintf("%s/user/?offset=2&limit=8", SERVER_URI); admin_req.http_verb = o_strdup("GET"); res = ulfius_send_http_request(&admin_req, &resp); ck_assert_int_eq(res, U_OK); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_result), 8); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 0), "username")), "user2"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 1), "username")), "user3"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 2), "username")), "mock1-admin"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 3), "username")), "mock1-user1"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 4), "username")), "mock1-user2"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 5), "username")), "mock1-user3"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 6), "username")), "mock2-admin"); ck_assert_str_eq(json_string_value(json_object_get(json_array_get(j_result, 7), "username")), "mock2-user1"); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = NULL; admin_req.http_verb = NULL; ulfius_clean_response(&resp); json_decref(j_result); } END_TEST START_TEST(test_glwd_crud_user_list_delete_user_module_instances) { char * url = msprintf("%s/mod/user/%s", SERVER_URI, MODULE_NAME_1); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/mod/user/%s", SERVER_URI, MODULE_NAME_2); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd CRUD user"); tc_core = tcase_create("test_glwd_crud_user"); tcase_add_test(tc_core, test_glwd_crud_user_get_list); tcase_add_test(tc_core, test_glwd_crud_user_add_error_json); tcase_add_test(tc_core, test_glwd_crud_user_add_error_param); tcase_add_test(tc_core, test_glwd_crud_user_add_OK); tcase_add_test(tc_core, test_glwd_crud_user_add_already_present); tcase_add_test(tc_core, test_glwd_crud_user_get); tcase_add_test(tc_core, test_glwd_crud_user_set_OK); tcase_add_test(tc_core, test_glwd_crud_user_delete_error); tcase_add_test(tc_core, test_glwd_crud_user_delete_OK); tcase_add_test(tc_core, test_glwd_crud_user_list_limit); tcase_add_test(tc_core, test_glwd_crud_user_list_pattern); tcase_add_test(tc_core, test_glwd_crud_user_list_add_user_module_instances); tcase_add_test(tc_core, test_glwd_crud_user_list_page_multiple_source); tcase_add_test(tc_core, test_glwd_crud_user_list_pattern_multiple_source); tcase_add_test(tc_core, test_glwd_crud_user_list_delete_user_module_instances); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0, i; json_t * j_body; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "admin" #define PASSWORD "password" #define NEW_USERNAME "test" #define NEW_NAME "Dave Lopper" #define NEW_EMAIL "test@glewlwyd" #define NEW_SCOPE_1 "scope1" #define NEW_SCOPE_2 "scope2" #define MODULE_MODULE "mock" #define MODULE_NAME "test_middleware" #define MODULE_DISPLAY_NAME "test_middleware name" #define MODULE_MIDDLEWARE_VALUE "wizard" struct _u_request admin_req; START_TEST(test_glwd_admin_mod_user_middleware_add_OK) { json_t * j_parameters = json_pack("{sssssssis{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "order_rank", 0, "parameters", "middleware", MODULE_MIDDLEWARE_VALUE); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/user_middleware/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_admin_mod_user_middleware_delete_OK) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/user_middleware/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_crud_user_middleware_add_OK) { json_t * j_parameters = json_pack("{sssssss[ss]ss}", "username", NEW_USERNAME, "name", NEW_NAME, "email", NEW_EMAIL, "scope", NEW_SCOPE_1, NEW_SCOPE_2, "middleware", MODULE_MIDDLEWARE_VALUE); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/user/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_object_set_new(j_parameters, "middleware", json_string(MODULE_MIDDLEWARE_VALUE "-" NEW_USERNAME)); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/user/" NEW_USERNAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_crud_user_middleware_get_list) { json_t * j_parameters = json_pack("{sssssss[ss]ss}", "username", NEW_USERNAME, "name", NEW_NAME, "email", NEW_EMAIL, "scope", NEW_SCOPE_1, NEW_SCOPE_2, "middleware", MODULE_MIDDLEWARE_VALUE "-" NEW_USERNAME); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/user/", NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_crud_user_middleware_get) { json_t * j_parameters = json_pack("{sssssss[ss]ss}", "username", NEW_USERNAME, "name", NEW_NAME, "email", NEW_EMAIL, "scope", NEW_SCOPE_1, NEW_SCOPE_2, "middleware", MODULE_MIDDLEWARE_VALUE "-" NEW_USERNAME); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/user/" NEW_USERNAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/user/error", NULL, NULL, NULL, NULL, 404, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_crud_user_middleware_get_profile_impersonate) { json_t * j_parameters = json_pack("{ssss}", "username", USERNAME, "middleware", MODULE_MIDDLEWARE_VALUE "-" USERNAME "-profile"); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/profile_list", NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_crud_user_middleware_set_OK) { json_t * j_parameters = json_pack("{sssss[s]ss}", "name", NEW_NAME "-new", "email", NEW_EMAIL "-new", "scope", NEW_SCOPE_1, "middleware", MODULE_MIDDLEWARE_VALUE); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" NEW_USERNAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_object_set_new(j_parameters, "username", json_string(NEW_USERNAME)); json_object_set_new(j_parameters, "middleware", json_string(MODULE_MIDDLEWARE_VALUE "-" NEW_USERNAME)); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/user/" NEW_USERNAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_crud_user_middleware_set_then_disable_module_OK) { json_t * j_parameters = json_pack("{sssss[s]ss}", "name", NEW_NAME "-new", "email", NEW_EMAIL "-new", "scope", NEW_SCOPE_1, "middleware", MODULE_MIDDLEWARE_VALUE); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" NEW_USERNAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_object_set_new(j_parameters, "username", json_string(NEW_USERNAME)); json_object_set_new(j_parameters, "middleware", json_string(MODULE_MIDDLEWARE_VALUE "-" NEW_USERNAME)); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/user/" NEW_USERNAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/mod/user_middleware/" MODULE_NAME "/disable", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssss[s]ss}", "username", NEW_USERNAME, "name", NEW_NAME "-new", "email", NEW_EMAIL "-new", "scope", NEW_SCOPE_1, "middleware", MODULE_MIDDLEWARE_VALUE); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" NEW_USERNAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/user/" NEW_USERNAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/mod/user_middleware/" MODULE_NAME "/enable", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); json_object_set_new(j_parameters, "middleware", json_string(MODULE_MIDDLEWARE_VALUE "-" NEW_USERNAME)); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/user/" NEW_USERNAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_crud_user_middleware_delete_OK) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/user/" NEW_USERNAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd CRUD user"); tc_core = tcase_create("test_glwd_crud_user_middleware"); tcase_add_test(tc_core, test_glwd_admin_mod_user_middleware_add_OK); tcase_add_test(tc_core, test_glwd_crud_user_middleware_add_OK); tcase_add_test(tc_core, test_glwd_crud_user_middleware_get_list); tcase_add_test(tc_core, test_glwd_crud_user_middleware_get); tcase_add_test(tc_core, test_glwd_crud_user_middleware_get_profile_impersonate); tcase_add_test(tc_core, test_glwd_crud_user_middleware_set_OK); tcase_add_test(tc_core, test_glwd_crud_user_middleware_set_then_disable_module_OK); tcase_add_test(tc_core, test_glwd_crud_user_middleware_delete_OK); tcase_add_test(tc_core, test_glwd_admin_mod_user_middleware_delete_OK); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0, i; json_t * j_body; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #include "../src/glewlwyd-common.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define MOD_NAME "mod_irl" #define CLIENT_PASSWORD "password" #define CLIENT_NAME "Client test" #define CLIENT_DESCRIPTION "Client test description" #define CLIENT_SCOPE_1 "scope1" #define CLIENT_SCOPE_2 "scope2" struct _u_request admin_req; json_t * j_params; char * client_id = NULL, * client_id_case = NULL, * client_id_upper = NULL, * client_id_pattern = NULL; const char jwks_pubkey_ecdsa_str[] = "{\"keys\":[{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4\","\ "\"y\":\"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM\",\"use\":\"enc\",\"kid\":\"1\"}]}"; START_TEST(test_glwd_mod_client_irl_module_add) { char * url = SERVER_URI "/mod/client"; ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_mod_client_irl_add_error_param) { char * url = msprintf("%s/client?source=" MOD_NAME, SERVER_URI); json_t * j_parameters = json_pack("{ss}", "error", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("[{ss}]", "client_id", "test"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{si}", "client_id", 42); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(url); } END_TEST START_TEST(test_glwd_mod_client_irl_client_add) { json_t * j_client = json_pack("{sssssssss[ss]sos[ss]so}", "client_id", client_id, "password", CLIENT_PASSWORD, "name", CLIENT_NAME, "description", CLIENT_DESCRIPTION, "scope", CLIENT_SCOPE_1, CLIENT_SCOPE_2, "confidential", json_true(), "redirect-uri", "https://glewlwyd.local/", "https://glewlwyd.remote", "jwks", json_loads(jwks_pubkey_ecdsa_str, JSON_DECODE_ANY, NULL)); char * url = SERVER_URI "/client?source=" MOD_NAME; ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); } END_TEST START_TEST(test_glwd_mod_client_irl_client_add_case) { json_t * j_client = json_pack("{sssssssss[ss]sos[ss]so}", "client_id", client_id_case, "password", CLIENT_PASSWORD, "name", CLIENT_NAME, "description", CLIENT_DESCRIPTION, "scope", CLIENT_SCOPE_1, CLIENT_SCOPE_2, "confidential", json_true(), "redirect-uri", "https://glewlwyd.local/", "https://glewlwyd.remote", "jwks", json_loads(jwks_pubkey_ecdsa_str, JSON_DECODE_ANY, NULL)); char * url = SERVER_URI "/client?source=" MOD_NAME; ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); } END_TEST START_TEST(test_glwd_mod_client_irl_add_already_present) { char * url = msprintf("%s/client?source=" MOD_NAME, SERVER_URI); json_t * j_parameters =json_pack("{sssssssss[ss]sos[ss]so}", "client_id", client_id, "password", CLIENT_PASSWORD, "name", CLIENT_NAME, "description", CLIENT_DESCRIPTION, "scope", CLIENT_SCOPE_1, CLIENT_SCOPE_2, "confidential", json_true(), "redirect-uri", "https://glewlwyd.local/", "https://glewlwyd.remote", "jwks", json_loads(jwks_pubkey_ecdsa_str, JSON_DECODE_ANY, NULL)); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_mod_client_irl_add_case_already_present) { char * url = msprintf("%s/client?source=" MOD_NAME, SERVER_URI); json_t * j_parameters = json_pack("{sssssssss[ss]sos[ss]so}", "client_id", client_id_upper, "password", CLIENT_PASSWORD, "name", CLIENT_NAME, "description", CLIENT_DESCRIPTION, "scope", CLIENT_SCOPE_1, CLIENT_SCOPE_2, "confidential", json_true(), "redirect-uri", "https://glewlwyd.local/", "https://glewlwyd.remote", "jwks", json_loads(jwks_pubkey_ecdsa_str, JSON_DECODE_ANY, NULL)); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_mod_client_irl_delete_error) { char * url = msprintf("%s/client/error?source=" MOD_NAME, SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 404, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_mod_client_irl_client_get_list) { json_t * j_client = json_pack("{sssssss[ss]sos[ss]so}", "client_id", client_id, "name", CLIENT_NAME, "description", CLIENT_DESCRIPTION, "scope", CLIENT_SCOPE_1, CLIENT_SCOPE_2, "confidential", json_true(), "redirect-uri", "https://glewlwyd.local/", "https://glewlwyd.remote", "jwks", json_loads(jwks_pubkey_ecdsa_str, JSON_DECODE_ANY, NULL)); char * url = SERVER_URI "/client?source=" MOD_NAME; ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_client, NULL, NULL), 1); json_decref(j_client); } END_TEST START_TEST(test_glwd_mod_client_irl_client_get) { json_t * j_client = json_pack("{sssssss[ss]sos[ss]so}", "client_id", client_id, "name", CLIENT_NAME, "description", CLIENT_DESCRIPTION, "scope", CLIENT_SCOPE_1, CLIENT_SCOPE_2, "confidential", json_true(), "redirect-uri", "https://glewlwyd.local/", "https://glewlwyd.remote", "jwks", json_loads(jwks_pubkey_ecdsa_str, JSON_DECODE_ANY, NULL)); char * url = msprintf(SERVER_URI "/client/%s?source=" MOD_NAME, client_id); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_client, NULL, NULL), 1); json_decref(j_client); o_free(url); } END_TEST START_TEST(test_glwd_mod_client_irl_client_get_case) { json_t * j_client = json_pack("{sssssss[ss]sos[ss]so}", "client_id", client_id_case, "name", CLIENT_NAME, "description", CLIENT_DESCRIPTION, "scope", CLIENT_SCOPE_1, CLIENT_SCOPE_2, "confidential", json_true(), "redirect-uri", "https://glewlwyd.local/", "https://glewlwyd.remote", "jwks", json_loads(jwks_pubkey_ecdsa_str, JSON_DECODE_ANY, NULL)); char * url = msprintf(SERVER_URI "/client/%s?source=" MOD_NAME, client_id_upper); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_client, NULL, NULL), 1); json_decref(j_client); o_free(url); } END_TEST START_TEST(test_glwd_mod_client_irl_client_update) { json_t * j_client = json_pack("{sssss[ss]s[ss]so}", "name", CLIENT_NAME "-updated", "description", CLIENT_DESCRIPTION "-updated", "scope", CLIENT_SCOPE_1, CLIENT_SCOPE_2, "redirect-uri", "https://glewlwyd.local/updated", "https://glewlwyd.remote/updated", "jwks", json_loads(jwks_pubkey_ecdsa_str, JSON_DECODE_ANY, NULL)); char * url = msprintf(SERVER_URI "/client/%s?source=" MOD_NAME, client_id); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); o_free(url); } END_TEST START_TEST(test_glwd_mod_client_irl_client_update_case) { json_t * j_client = json_pack("{sssss[ss]s[ss]so}", "name", CLIENT_NAME "-updated", "description", CLIENT_DESCRIPTION "-updated", "scope", CLIENT_SCOPE_1, CLIENT_SCOPE_2, "redirect-uri", "https://glewlwyd.local/updated", "https://glewlwyd.remote/updated", "jwks", json_loads(jwks_pubkey_ecdsa_str, JSON_DECODE_ANY, NULL)); char * url = msprintf(SERVER_URI "/client/%s?source=" MOD_NAME, client_id_upper); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); o_free(url); } END_TEST START_TEST(test_glwd_mod_client_irl_client_get_updated) { json_t * j_client = json_pack("{sssssssss[ss]s[ss]so}", "client_id", client_id, "name", CLIENT_NAME "-updated", "description", CLIENT_DESCRIPTION "-updated", "source", MOD_NAME, "scope", CLIENT_SCOPE_1, CLIENT_SCOPE_2, "redirect-uri", "https://glewlwyd.local/updated", "https://glewlwyd.remote/updated", "jwks", json_loads(jwks_pubkey_ecdsa_str, JSON_DECODE_ANY, NULL)); char * url = msprintf(SERVER_URI "/client/%s?source=" MOD_NAME, client_id); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_client, NULL, NULL), 1); json_decref(j_client); o_free(url); } END_TEST START_TEST(test_glwd_mod_client_irl_client_get_case_updated) { json_t * j_client = json_pack("{sssssssss[ss]s[ss]so}", "client_id", client_id_case, "name", CLIENT_NAME "-updated", "description", CLIENT_DESCRIPTION "-updated", "source", MOD_NAME, "scope", CLIENT_SCOPE_1, CLIENT_SCOPE_2, "redirect-uri", "https://glewlwyd.local/updated", "https://glewlwyd.remote/updated", "jwks", json_loads(jwks_pubkey_ecdsa_str, JSON_DECODE_ANY, NULL)); char * url = msprintf(SERVER_URI "/client/%s?source=" MOD_NAME, client_id_case); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_client, NULL, NULL), 1); json_decref(j_client); o_free(url); } END_TEST START_TEST(test_glwd_mod_client_irl_client_delete) { char * url = msprintf(SERVER_URI "/client/%s?source=" MOD_NAME, client_id); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_mod_client_irl_client_delete_case) { char * url = msprintf(SERVER_URI "/client/%s?source=" MOD_NAME, client_id_upper); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_mod_client_irl_client_large_list_add) { int i; char * cur_client_id; json_t * j_client; for (i=0; i < 100; i++) { cur_client_id = msprintf("%s%d", client_id_pattern, i); j_client = json_pack("{sssssssss[ss]so}", "client_id", cur_client_id, "password", CLIENT_PASSWORD, "name", CLIENT_NAME, "description", CLIENT_DESCRIPTION, "scope", CLIENT_SCOPE_1, CLIENT_SCOPE_2, "jwks", json_loads(jwks_pubkey_ecdsa_str, JSON_DECODE_ANY, NULL)); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client?source=" MOD_NAME, NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); o_free(cur_client_id); } } END_TEST START_TEST(test_glwd_mod_client_irl_client_large_list_get) { json_t * j_client; struct _u_response resp; ulfius_init_response(&resp); o_free(admin_req.http_verb); o_free(admin_req.http_url); admin_req.http_verb = strdup("GET"); admin_req.http_url = msprintf(SERVER_URI "/client?source=" MOD_NAME "&pattern=%s", client_id_pattern); ck_assert_int_eq(ulfius_send_http_request(&admin_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_client = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_client), 100); json_decref(j_client); ulfius_clean_response(&resp); } END_TEST START_TEST(test_glwd_mod_client_irl_client_large_list_delete) { int i; char * url; for (i=0; i < 100; i++) { url = msprintf(SERVER_URI "/client/%s%d?source=" MOD_NAME, client_id_pattern, i); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } } END_TEST START_TEST(test_glwd_mod_client_irl_module_delete) { char * url = SERVER_URI "/mod/client/" MOD_NAME; ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd mod client irl"); tc_core = tcase_create("test_glwd_mod_client_irl"); tcase_add_test(tc_core, test_glwd_mod_client_irl_module_add); tcase_add_test(tc_core, test_glwd_mod_client_irl_client_add); tcase_add_test(tc_core, test_glwd_mod_client_irl_client_add_case); tcase_add_test(tc_core, test_glwd_mod_client_irl_add_error_param); tcase_add_test(tc_core, test_glwd_mod_client_irl_add_already_present); tcase_add_test(tc_core, test_glwd_mod_client_irl_add_case_already_present); tcase_add_test(tc_core, test_glwd_mod_client_irl_delete_error); tcase_add_test(tc_core, test_glwd_mod_client_irl_client_get_list); tcase_add_test(tc_core, test_glwd_mod_client_irl_client_get); tcase_add_test(tc_core, test_glwd_mod_client_irl_client_get_case); tcase_add_test(tc_core, test_glwd_mod_client_irl_client_update); tcase_add_test(tc_core, test_glwd_mod_client_irl_client_update_case); tcase_add_test(tc_core, test_glwd_mod_client_irl_client_get_updated); tcase_add_test(tc_core, test_glwd_mod_client_irl_client_get_case_updated); tcase_add_test(tc_core, test_glwd_mod_client_irl_client_delete); tcase_add_test(tc_core, test_glwd_mod_client_irl_client_delete_case); tcase_add_test(tc_core, test_glwd_mod_client_irl_client_large_list_add); tcase_add_test(tc_core, test_glwd_mod_client_irl_client_large_list_get); tcase_add_test(tc_core, test_glwd_mod_client_irl_client_large_list_delete); tcase_add_test(tc_core, test_glwd_mod_client_irl_module_delete); tcase_set_timeout(tc_core, 90); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0, i, x[1]; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); j_params = json_load_file(argv[1], JSON_DECODE_ANY, NULL); ulfius_init_request(&admin_req); if (j_params != NULL) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); gnutls_rnd(GNUTLS_RND_NONCE, x, sizeof(int)); client_id = msprintf("client_irl%04d", (x[0]%1000)); gnutls_rnd(GNUTLS_RND_NONCE, x, sizeof(int)); client_id_case = msprintf("client_irl_case%04d", (x[0]%1000)); client_id_upper = o_malloc(o_strlen(client_id_case) + sizeof(char)); for (i=0; i #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define CLIENT "client1_id" #define HOST "localhost" #define PORT 2884 #define PORT_UNAVAILABLE 4882 #define PREFIX "/auth" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define HTTP_USER "httpuser1" #define HTTP_USER_FORMAT "prefix/httpuser1@suffix" #define HTTP_PASSWORD "http_user_password" #define USERNAME_FORMAT "prefix/{username}@suffix" #define MOD_NAME "mod_irl" char * host = NULL; struct _u_request user_req, admin_req; char * code; /** * Auth function for basic authentication */ int auth_basic (const struct _u_request * request, struct _u_response * response, void * user_data) { if (request->auth_basic_user != NULL && request->auth_basic_password != NULL) { if (0 == o_strcmp(request->auth_basic_user, HTTP_USER) && 0 == o_strcmp(request->auth_basic_password, HTTP_PASSWORD)) { return U_CALLBACK_CONTINUE; } else { return U_CALLBACK_UNAUTHORIZED; } } else { return U_CALLBACK_UNAUTHORIZED; } } /** * Auth function for basic authentication with formatted username */ int auth_basic_format (const struct _u_request * request, struct _u_response * response, void * user_data) { if (request->auth_basic_user != NULL && request->auth_basic_password != NULL) { if (0 == o_strcmp(request->auth_basic_user, HTTP_USER_FORMAT) && 0 == o_strcmp(request->auth_basic_password, HTTP_PASSWORD)) { return U_CALLBACK_CONTINUE; } else { return U_CALLBACK_UNAUTHORIZED; } } else { return U_CALLBACK_UNAUTHORIZED; } } START_TEST(test_glwd_http_auth_module_add) { char * param_url; if (host == NULL) { param_url = msprintf("http://%s:%d/auth/", HOST, PORT); } else { param_url = msprintf("http://%s:%d/auth/", host, PORT); } json_t * j_params = json_pack("{sssssssis{sssos[ss]}}", "module", "http", "name", "mod_irl", "display_name", "HTTP", "order_rank", 1, "parameters", "url", param_url, "check-server-certificate", json_true(), "default-scope", "g_profile", "scope1"); char * url = SERVER_URI "/mod/user"; ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); o_free(param_url); } END_TEST START_TEST(test_glwd_http_auth_http_auth_success) { struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body, * j_register; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", HTTP_USER, "password", HTTP_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_gt(auth_resp.nb_cookies, 0); ck_assert_ptr_ne((cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value)), NULL); ck_assert_int_eq(u_map_put(auth_req.map_header, "Cookie", cookie), U_OK); ulfius_clean_response(&auth_resp); ulfius_init_response(&auth_resp); j_register = json_pack("{sssssss{so}}", "username", HTTP_USER, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); j_register = json_pack("{sssssss{so}}", "username", HTTP_USER, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); j_body = json_pack("{sssssss{ss}}", "username", HTTP_USER, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); ulfius_init_response(&auth_resp); j_body = json_pack("{sssssss{ss}}", "username", HTTP_USER, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); j_register = json_pack("{sssssss{so}}", "username", HTTP_USER, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "register", json_false()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); j_register = json_pack("{sssssss{so}}", "username", HTTP_USER, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "register", json_false()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); o_free(cookie); } END_TEST START_TEST(test_glwd_http_auth_http_auth_fail) { struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", HTTP_USER, "password", "invalid"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ulfius_send_http_request(&auth_req, &auth_resp); ck_assert_int_eq(auth_resp.status, 401); ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); } END_TEST START_TEST(test_glwd_http_auth_module_delete) { char * url = SERVER_URI "/mod/user/" MOD_NAME; ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_http_auth_module_format_add) { char * param_url; if (host == NULL) { param_url = msprintf("http://%s:%d/auth/format/", HOST, PORT); } else { param_url = msprintf("http://%s:%d/auth/format/", host, PORT); } json_t * j_params = json_pack("{sssssssis{sssos[ss]ss}}", "module", "http", "name", "mod_irl", "display_name", "HTTP", "order_rank", 1, "parameters", "url", param_url, "check-server-certificate", json_true(), "default-scope", "g_profile", "scope1", "username-format", USERNAME_FORMAT); char * url = SERVER_URI "/mod/user"; ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); o_free(param_url); } END_TEST START_TEST(test_glwd_http_auth_module_unavailable_add) { char * param_url; if (host == NULL) { param_url = msprintf("http://%s:%d/", HOST, PORT_UNAVAILABLE); } else { param_url = msprintf("http://%s:%d/", host, PORT_UNAVAILABLE); } json_t * j_params = json_pack("{sssssssis{sssos[ss]ss}}", "module", "http", "name", "mod_irl", "display_name", "HTTP", "order_rank", 1, "parameters", "url", param_url, "check-server-certificate", json_true(), "default-scope", "g_profile", "scope1", "username-format", USERNAME_FORMAT); char * url = SERVER_URI "/mod/user"; ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); o_free(param_url); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd mod HTTP Auth"); tc_core = tcase_create("test_glwd_http_auth"); tcase_add_test(tc_core, test_glwd_http_auth_module_add); tcase_add_test(tc_core, test_glwd_http_auth_http_auth_success); tcase_add_test(tc_core, test_glwd_http_auth_http_auth_fail); tcase_add_test(tc_core, test_glwd_http_auth_module_delete); tcase_add_test(tc_core, test_glwd_http_auth_module_format_add); tcase_add_test(tc_core, test_glwd_http_auth_http_auth_success); tcase_add_test(tc_core, test_glwd_http_auth_http_auth_fail); tcase_add_test(tc_core, test_glwd_http_auth_module_delete); tcase_add_test(tc_core, test_glwd_http_auth_module_unavailable_add); tcase_add_test(tc_core, test_glwd_http_auth_http_auth_fail); tcase_add_test(tc_core, test_glwd_http_auth_module_delete); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; struct _u_instance instance; int res, do_test = 0; json_t * j_body; char * cookie; if (argc > 1) { host = argv[1]; } y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); if (ulfius_init_instance(&instance, PORT, NULL, "auth_basic_default") != U_OK) { y_log_message(Y_LOG_LEVEL_INFO, "Error ulfius_init_instance, abort"); return(1); } ulfius_add_endpoint_by_val(&instance, "GET", PREFIX, NULL, 0, &auth_basic, NULL); ulfius_add_endpoint_by_val(&instance, "GET", PREFIX, "/format", 0, &auth_basic_format, NULL); if (ulfius_start_framework(&instance) == U_OK) { y_log_message(Y_LOG_LEVEL_INFO, "Start framework on port %d", instance.port); } else { y_log_message(Y_LOG_LEVEL_INFO, "Error starting framework"); return(1); } ulfius_init_request(&admin_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } ulfius_clean_request(&admin_req); y_close_logs(); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_mod_user_irl.c000066400000000000000000001036031415646314000211110ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ /** * * This test is used to validate one user backend module that will be created upon start and deleted after * The user backend must be in write mode * */ #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #include "../src/glewlwyd-common.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define MOD_NAME "mod_irl" #define PROFILE_PASSWORD "password" #define PROFILE_NEW_PASSWORD "newpassword" #define PROFILE_NAME "Dave Lopper" #define PROFILE_MAIL "dev@glewlwyd" #define PROFILE_SCOPE_1 "g_profile" #define PROFILE_SCOPE_2 "scope1" #define PROFILE_MOCK_42 "42" #define PROFILE_MOCK_95 "95" struct _u_request admin_req; json_t * j_params; char * username = NULL, * username_case = NULL, * username_upper = NULL, * username_pattern = NULL; START_TEST(test_glwd_mod_user_irl_module_add) { char * url = SERVER_URI "/mod/user"; ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_mod_user_irl_add_error_param) { char * url = msprintf("%s/user?source=" MOD_NAME, SERVER_URI); json_t * j_parameters = json_pack("{ss}", "error", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("[{ss}]", "username", "test"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{si}", "username", 42); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sss[s]}", "username", "test", "password", PROFILE_PASSWORD); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(url); } END_TEST START_TEST(test_glwd_mod_user_irl_user_add) { json_t * j_user = json_pack("{sssssssss[ss]sss[s]}", "username", username, "password", PROFILE_PASSWORD, "name", PROFILE_NAME, "email", PROFILE_MAIL, "scope", PROFILE_SCOPE_1, PROFILE_SCOPE_2, "mock-42", PROFILE_MOCK_42, "mock-95", PROFILE_MOCK_95); char * url = SERVER_URI "/user?source=" MOD_NAME; ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_user, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_user); } END_TEST START_TEST(test_glwd_mod_user_irl_user_add_empty_values) { json_t * j_user = json_pack("{sssssssss[ss]sss[]}", "username", username, "password", PROFILE_PASSWORD, "name", PROFILE_NAME, "email", PROFILE_MAIL, "scope", PROFILE_SCOPE_1, PROFILE_SCOPE_2, "mock-42", "", "mock-95"); char * url = SERVER_URI "/user?source=" MOD_NAME; ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_user, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_user); } END_TEST START_TEST(test_glwd_mod_user_irl_user_add_case) { json_t * j_user = json_pack("{sssssssss[ss]}", "username", username_case, "password", PROFILE_PASSWORD, "name", PROFILE_NAME, "email", PROFILE_MAIL, "scope", PROFILE_SCOPE_1, PROFILE_SCOPE_2); char * url = SERVER_URI "/user?source=" MOD_NAME; ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_user, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_user); } END_TEST START_TEST(test_glwd_mod_user_irl_add_already_present) { char * url = msprintf("%s/user?source=" MOD_NAME, SERVER_URI); json_t * j_parameters = json_pack("{sssssssss[ss]}", "username", username, "password", PROFILE_PASSWORD, "name", PROFILE_NAME, "email", PROFILE_MAIL, "scope", PROFILE_SCOPE_1, PROFILE_SCOPE_2); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_mod_user_irl_add_case_already_present) { char * url = msprintf("%s/user?source=" MOD_NAME, SERVER_URI); json_t * j_parameters = json_pack("{sssssssss[ss]}", "username", username_upper, "password", PROFILE_PASSWORD, "name", PROFILE_NAME, "email", PROFILE_MAIL, "scope", PROFILE_SCOPE_1, PROFILE_SCOPE_2); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_mod_user_irl_delete_error) { char * url = msprintf("%s/user/error?source=" MOD_NAME, SERVER_URI); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 404, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_mod_user_irl_user_get_list) { json_t * j_user = json_pack("{sssssssss[ss]}", "username", username, "name", PROFILE_NAME, "email", PROFILE_MAIL, "source", MOD_NAME, "scope", PROFILE_SCOPE_1, PROFILE_SCOPE_2); char * url = SERVER_URI "/user?source=" MOD_NAME; ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_user, NULL, NULL), 1); json_decref(j_user); } END_TEST START_TEST(test_glwd_mod_user_irl_user_get) { json_t * j_user = json_pack("{sssssssss[ss]}", "username", username, "name", PROFILE_NAME, "email", PROFILE_MAIL, "source", MOD_NAME, "scope", PROFILE_SCOPE_1, PROFILE_SCOPE_2); char * url = msprintf(SERVER_URI "/user/%s?source=" MOD_NAME, username); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_user, NULL, NULL), 1); json_decref(j_user); o_free(url); } END_TEST START_TEST(test_glwd_mod_user_irl_user_get_case) { json_t * j_user = json_pack("{sssssssss[ss]}", "username", username_case, "name", PROFILE_NAME, "email", PROFILE_MAIL, "source", MOD_NAME, "scope", PROFILE_SCOPE_1, PROFILE_SCOPE_2); char * url = msprintf(SERVER_URI "/user/%s?source=" MOD_NAME, username_upper); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_user, NULL, NULL), 1); json_decref(j_user); o_free(url); } END_TEST START_TEST(test_glwd_mod_user_irl_user_auth) { struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body, * j_register; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", username, "password", PROFILE_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_gt(auth_resp.nb_cookies, 0); ck_assert_ptr_ne((cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value)), NULL); ck_assert_int_eq(u_map_put(auth_req.map_header, "Cookie", cookie), U_OK); ulfius_clean_response(&auth_resp); ulfius_init_response(&auth_resp); j_register = json_pack("{sssssss{so}}", "username", username, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); j_register = json_pack("{sssssss{so}}", "username", username, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); j_body = json_pack("{sssssss{ss}}", "username", username, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); ulfius_init_response(&auth_resp); j_body = json_pack("{sssssss{ss}}", "username", username, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); j_register = json_pack("{sssssss{so}}", "username", username, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "register", json_false()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); j_register = json_pack("{sssssss{so}}", "username", username, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "register", json_false()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); o_free(cookie); } END_TEST START_TEST(test_glwd_mod_user_irl_user_auth_case) { struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body, * j_register; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", username_upper, "password", PROFILE_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_gt(auth_resp.nb_cookies, 0); ck_assert_ptr_ne((cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value)), NULL); ck_assert_int_eq(u_map_put(auth_req.map_header, "Cookie", cookie), U_OK); ulfius_clean_response(&auth_resp); ulfius_init_response(&auth_resp); j_register = json_pack("{sssssss{so}}", "username", username_upper, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); j_register = json_pack("{sssssss{so}}", "username", username_upper, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); j_body = json_pack("{sssssss{ss}}", "username", username_upper, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); ulfius_init_response(&auth_resp); j_body = json_pack("{sssssss{ss}}", "username", username_upper, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); j_register = json_pack("{sssssss{so}}", "username", username_upper, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "register", json_false()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); j_register = json_pack("{sssssss{so}}", "username", username_upper, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "register", json_false()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); o_free(cookie); } END_TEST START_TEST(test_glwd_mod_user_irl_user_update_profile) { struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body, * j_profile; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", username, "password", PROFILE_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_gt(auth_resp.nb_cookies, 0); ck_assert_ptr_ne((cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value)), NULL); ck_assert_int_eq(u_map_put(auth_req.map_header, "Cookie", cookie), U_OK); ulfius_clean_response(&auth_resp); ulfius_init_response(&auth_resp); j_profile = json_pack("{sssssss[ss]}", "username", username, "name", PROFILE_NAME, "email", PROFILE_MAIL, "scope", PROFILE_SCOPE_1, PROFILE_SCOPE_2); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/profile_list/", NULL, NULL, NULL, NULL, 200, j_profile, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ss}", "name", PROFILE_NAME " Profile Updated"); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", SERVER_URI "/profile/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{sssssss[ss]}", "username", username, "name", PROFILE_NAME " Profile Updated", "email", PROFILE_MAIL, "scope", PROFILE_SCOPE_1, PROFILE_SCOPE_2); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/profile_list/", NULL, NULL, NULL, NULL, 200, j_profile, NULL, NULL), 1); json_decref(j_profile); ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); o_free(cookie); } END_TEST START_TEST(test_glwd_mod_user_irl_user_update_profile_empty_values) { struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body, * j_profile; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", username, "password", PROFILE_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_gt(auth_resp.nb_cookies, 0); ck_assert_ptr_ne((cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value)), NULL); ck_assert_int_eq(u_map_put(auth_req.map_header, "Cookie", cookie), U_OK); ulfius_clean_response(&auth_resp); ulfius_init_response(&auth_resp); j_profile = json_pack("{sssssss[ss]}", "username", username, "name", PROFILE_NAME, "email", PROFILE_MAIL, "scope", PROFILE_SCOPE_1, PROFILE_SCOPE_2); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/profile_list/", NULL, NULL, NULL, NULL, 200, j_profile, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{sss[s]}", "name", PROFILE_NAME " Profile Updated", "mock-95", PROFILE_MOCK_95); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", SERVER_URI "/profile/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{sssssss[ss]s[s]}", "username", username, "name", PROFILE_NAME " Profile Updated", "email", PROFILE_MAIL, "scope", PROFILE_SCOPE_1, PROFILE_SCOPE_2, "mock-95", PROFILE_MOCK_95); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/profile_list/", NULL, NULL, NULL, NULL, 200, j_profile, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ss}", "name", PROFILE_NAME " Profile Updated"); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", SERVER_URI "/profile/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); o_free(cookie); } END_TEST START_TEST(test_glwd_mod_user_irl_user_update_profile_case) { struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body, * j_profile; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", username_upper, "password", PROFILE_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_gt(auth_resp.nb_cookies, 0); ck_assert_ptr_ne((cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value)), NULL); ck_assert_int_eq(u_map_put(auth_req.map_header, "Cookie", cookie), U_OK); ulfius_clean_response(&auth_resp); ulfius_init_response(&auth_resp); j_profile = json_pack("{sssssss[ss]}", "username", username_case, "name", PROFILE_NAME, "email", PROFILE_MAIL, "scope", PROFILE_SCOPE_1, PROFILE_SCOPE_2); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/profile_list/", NULL, NULL, NULL, NULL, 200, j_profile, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ss}", "name", PROFILE_NAME " Profile Updated"); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", SERVER_URI "/profile/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{sssssss[ss]}", "username", username_case, "name", PROFILE_NAME " Profile Updated", "email", PROFILE_MAIL, "scope", PROFILE_SCOPE_1, PROFILE_SCOPE_2); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/profile_list/", NULL, NULL, NULL, NULL, 200, j_profile, NULL, NULL), 1); json_decref(j_profile); ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); o_free(cookie); } END_TEST START_TEST(test_glwd_mod_user_irl_user_update_password) { struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body, * j_profile; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", username, "password", PROFILE_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_gt(auth_resp.nb_cookies, 0); ck_assert_ptr_ne((cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value)), NULL); ck_assert_int_eq(u_map_put(auth_req.map_header, "Cookie", cookie), U_OK); ulfius_clean_response(&auth_resp); j_profile = json_pack("{sss[s]}", "old_password", PROFILE_PASSWORD, "password", PROFILE_NEW_PASSWORD); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", SERVER_URI "/profile/password/", NULL, NULL, j_profile, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", username, "password", PROFILE_NEW_PASSWORD); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", username, "password", PROFILE_PASSWORD); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "old_password", PROFILE_PASSWORD, "password", PROFILE_NEW_PASSWORD); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", SERVER_URI "/profile/password/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", username, "password", PROFILE_NEW_PASSWORD); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", username, "password", PROFILE_PASSWORD); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_profile); ulfius_clean_request(&auth_req); o_free(cookie); } END_TEST START_TEST(test_glwd_mod_user_irl_user_update_password_case) { struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body, * j_profile; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", username_upper, "password", PROFILE_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_gt(auth_resp.nb_cookies, 0); ck_assert_ptr_ne((cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value)), NULL); ck_assert_int_eq(u_map_put(auth_req.map_header, "Cookie", cookie), U_OK); ulfius_clean_response(&auth_resp); j_profile = json_pack("{ssss}", "username", username_upper, "password", PROFILE_NEW_PASSWORD); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", username_upper, "password", PROFILE_PASSWORD); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "old_password", PROFILE_PASSWORD, "password", PROFILE_NEW_PASSWORD); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", SERVER_URI "/profile/password/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", username_upper, "password", PROFILE_NEW_PASSWORD); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", username_upper, "password", PROFILE_PASSWORD); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_profile); ulfius_clean_request(&auth_req); o_free(cookie); } END_TEST START_TEST(test_glwd_mod_user_irl_user_update) { json_t * j_user = json_pack("{sssss[ss]}", "name", PROFILE_NAME "-updated", "email", PROFILE_MAIL "-updated", "scope", PROFILE_SCOPE_1, PROFILE_SCOPE_2); char * url = msprintf(SERVER_URI "/user/%s?source=" MOD_NAME, username); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_user, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_user); o_free(url); } END_TEST START_TEST(test_glwd_mod_user_irl_user_update_empty_values) { json_t * j_user = json_pack("{sssss[ss]sss[s]}", "name", PROFILE_NAME "-updated", "email", PROFILE_MAIL "-updated", "scope", PROFILE_SCOPE_1, PROFILE_SCOPE_2, "mock-42", "", "mock-95", PROFILE_MOCK_95); char * url = msprintf(SERVER_URI "/user/%s?source=" MOD_NAME, username); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_user, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_user); o_free(url); } END_TEST START_TEST(test_glwd_mod_user_irl_user_update_empty_values_only) { json_t * j_user = json_pack("{ss}", "name", PROFILE_NAME "-updated"); char * url = msprintf(SERVER_URI "/user/%s?source=" MOD_NAME, username); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_user, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_user); o_free(url); } END_TEST START_TEST(test_glwd_mod_user_irl_user_update_case) { json_t * j_user = json_pack("{sssss[ss]}", "name", PROFILE_NAME "-updated", "email", PROFILE_MAIL "-updated", "scope", PROFILE_SCOPE_1, PROFILE_SCOPE_2); char * url = msprintf(SERVER_URI "/user/%s?source=" MOD_NAME, username_upper); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", url, NULL, NULL, j_user, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_user); o_free(url); } END_TEST START_TEST(test_glwd_mod_user_irl_user_get_updated) { json_t * j_user = json_pack("{sssssssss[ss]}", "username", username, "name", PROFILE_NAME "-updated", "email", PROFILE_MAIL "-updated", "source", MOD_NAME, "scope", PROFILE_SCOPE_1, PROFILE_SCOPE_2); char * url = msprintf(SERVER_URI "/user/%s?source=" MOD_NAME, username); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_user, NULL, NULL), 1); json_decref(j_user); o_free(url); } END_TEST START_TEST(test_glwd_mod_user_irl_user_get_updated_empty_values) { json_t * j_user = json_pack("{sssssss[ss]s[s]}", "username", username, "email", PROFILE_MAIL "-updated", "source", MOD_NAME, "scope", PROFILE_SCOPE_1, PROFILE_SCOPE_2, "mock-95", PROFILE_MOCK_95); char * url = msprintf(SERVER_URI "/user/%s?source=" MOD_NAME, username); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_user, NULL, NULL), 1); json_decref(j_user); o_free(url); } END_TEST START_TEST(test_glwd_mod_user_irl_user_get_updated_empty_values_only) { json_t * j_user = json_pack("{ss}", "username", username); char * url = msprintf(SERVER_URI "/user/%s?source=" MOD_NAME, username); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_user, NULL, NULL), 1); json_decref(j_user); o_free(url); } END_TEST START_TEST(test_glwd_mod_user_irl_user_get_case_updated) { json_t * j_user = json_pack("{sssssssss[ss]}", "username", username_case, "name", PROFILE_NAME "-updated", "email", PROFILE_MAIL "-updated", "source", MOD_NAME, "scope", PROFILE_SCOPE_1, PROFILE_SCOPE_2); char * url = msprintf(SERVER_URI "/user/%s?source=" MOD_NAME, username_upper); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_user, NULL, NULL), 1); json_decref(j_user); o_free(url); } END_TEST START_TEST(test_glwd_mod_user_irl_user_delete) { char * url = msprintf(SERVER_URI "/user/%s?source=" MOD_NAME, username); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_mod_user_irl_user_delete_case) { char * url = msprintf(SERVER_URI "/user/%s?source=" MOD_NAME, username_upper); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_mod_user_irl_user_large_list_add) { int i; char * cur_username; json_t * j_user; for (i=0; i < 100; i++) { cur_username = msprintf("%s%d", username_pattern, i); j_user = json_pack("{sssssssss[ss]}", "username", cur_username, "password", PROFILE_PASSWORD, "name", PROFILE_NAME, "email", PROFILE_MAIL, "scope", PROFILE_SCOPE_1, PROFILE_SCOPE_2); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/user?source=" MOD_NAME, NULL, NULL, j_user, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_user); o_free(cur_username); } } END_TEST START_TEST(test_glwd_mod_user_irl_user_large_list_get) { json_t * j_user; struct _u_response resp; ulfius_init_response(&resp); o_free(admin_req.http_verb); o_free(admin_req.http_url); admin_req.http_verb = strdup("GET"); admin_req.http_url = msprintf(SERVER_URI "/user?source=" MOD_NAME "&pattern=%s", username_pattern); ck_assert_int_eq(ulfius_send_http_request(&admin_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_user = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(json_array_size(j_user), 100); json_decref(j_user); ulfius_clean_response(&resp); } END_TEST START_TEST(test_glwd_mod_user_irl_user_large_list_delete) { int i; char * url; for (i=0; i < 100; i++) { url = msprintf(SERVER_URI "/user/%s%d?source=" MOD_NAME, username_pattern, i); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } } END_TEST START_TEST(test_glwd_mod_user_irl_module_delete) { char * url = SERVER_URI "/mod/user/" MOD_NAME; ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd mod user irl"); tc_core = tcase_create("test_glwd_mod_user_irl"); tcase_add_test(tc_core, test_glwd_mod_user_irl_module_add); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_add); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_add_case); tcase_add_test(tc_core, test_glwd_mod_user_irl_add_error_param); tcase_add_test(tc_core, test_glwd_mod_user_irl_add_already_present); tcase_add_test(tc_core, test_glwd_mod_user_irl_add_case_already_present); tcase_add_test(tc_core, test_glwd_mod_user_irl_delete_error); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_get_list); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_get); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_get_case); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_auth); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_auth_case); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_update_profile); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_update_profile_case); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_update_password); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_update_password_case); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_update); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_update_case); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_get_updated); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_get_case_updated); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_delete); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_delete_case); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_add_empty_values); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_get); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_update_profile_empty_values); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_update_empty_values); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_get_updated_empty_values); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_update_empty_values_only); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_get_updated_empty_values_only); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_delete); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_large_list_add); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_large_list_get); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_large_list_delete); tcase_add_test(tc_core, test_glwd_mod_user_irl_module_delete); tcase_set_timeout(tc_core, 90); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0, i, x[1]; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); j_params = json_load_file(argv[1], JSON_DECODE_ANY, NULL); ulfius_init_request(&admin_req); if (j_params != NULL) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); gnutls_rnd(GNUTLS_RND_NONCE, x, sizeof(int)); username = msprintf("user_irl%04d", (x[0]%1000)); gnutls_rnd(GNUTLS_RND_NONCE, x, sizeof(int)); username_case = msprintf("user_irl_case%04d", (x[0]%1000)); username_upper = o_malloc(o_strlen(username_case) + sizeof(char)); for (i=0; i #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #include "../src/glewlwyd-common.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define MOD_NAME "mod_mp" #define NAME "Dave Lopper" #define MAIL "dev@glewlwyd.tld" #define SCOPE "g_profile" #define USERNAME "user_mp" #define PASSWORD1 "password1" #define PASSWORD2 "password2" #define PASSWORD3 "password3" struct _u_request admin_req; json_t * j_params; START_TEST(test_glwd_mod_user_irl_module_add) { ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/user", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_mod_user_irl_user_add) { json_t * j_user = json_pack("{sss[ss]sssss[s]so}", "username", USERNAME, "password", PASSWORD1, PASSWORD2, "name", NAME, "email", MAIL, "scope", SCOPE, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/user?source=" MOD_NAME, NULL, NULL, j_user, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_user); } END_TEST START_TEST(test_glwd_mod_user_irl_user_get) { json_t * j_user = json_pack("{sssssssss[s]si}", "username", USERNAME, "name", NAME, "email", MAIL, "source", MOD_NAME, "scope", SCOPE, "password", 2); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/user/" USERNAME "?source=" MOD_NAME, NULL, NULL, NULL, NULL, 200, j_user, NULL, NULL), 1); json_decref(j_user); } END_TEST START_TEST(test_glwd_mod_user_irl_user_auth) { json_t * j_body; j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD2); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD3); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); } END_TEST START_TEST(test_glwd_mod_user_irl_profile_update_password) { struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body, * j_profile; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD1); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_gt(auth_resp.nb_cookies, 0); ck_assert_ptr_ne((cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value)), NULL); ck_assert_int_eq(u_map_put(auth_req.map_header, "Cookie", cookie), U_OK); ulfius_clean_response(&auth_resp); j_profile = json_pack("{si}", "password", 2); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/profile_list/", NULL, NULL, NULL, NULL, 200, j_profile, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD3); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "old_password", PASSWORD1, "password", PASSWORD3); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", SERVER_URI "/profile/password/", NULL, NULL, j_profile, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{sss[ss]}", "old_password", PASSWORD1, "password", PASSWORD1, PASSWORD3); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", SERVER_URI "/profile/password/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{si}", "password", 2); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/profile_list/", NULL, NULL, NULL, NULL, 200, j_profile, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD3); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{sss[so]}", "old_password", PASSWORD1, "password", "", json_null()); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", SERVER_URI "/profile/password/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{si}", "password", 1); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/profile_list/", NULL, NULL, NULL, NULL, 200, j_profile, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD2); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD3); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{sss[sss]}", "old_password", PASSWORD1, "password", "", PASSWORD2, PASSWORD3); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", SERVER_URI "/profile/password/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{si}", "password", 3); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/profile_list/", NULL, NULL, NULL, NULL, 200, j_profile, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD2); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD3); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{sss[ssoss]}", "old_password", PASSWORD1, "password", "", "", json_null(), "", ""); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", SERVER_URI "/profile/password/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{si}", "password", 2); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/profile_list/", NULL, NULL, NULL, NULL, 200, j_profile, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD2); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD3); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{sss[ss]}", "old_password", PASSWORD1, "password", PASSWORD1, PASSWORD2); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", SERVER_URI "/profile/password/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); ulfius_clean_request(&auth_req); o_free(cookie); } END_TEST START_TEST(test_glwd_mod_user_irl_admin_update_password) { json_t * j_profile; j_profile = json_pack("{si}", "password", 2); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/user/" USERNAME, NULL, NULL, NULL, NULL, 200, j_profile, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD3); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{s[s]soss}", "scope", SCOPE, "enabled", json_true(), "password", PASSWORD3); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USERNAME, NULL, NULL, j_profile, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{sss[s]sos[ss]}", "name", NAME, "scope", SCOPE, "enabled", json_true(), "password", PASSWORD1, PASSWORD3); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USERNAME, NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{si}", "password", 2); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/user/" USERNAME, NULL, NULL, NULL, NULL, 200, j_profile, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD3); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{sss[s]sos[so]}", "name", NAME, "scope", SCOPE, "enabled", json_true(), "password", "", json_null()); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USERNAME, NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{si}", "password", 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/user/" USERNAME, NULL, NULL, NULL, NULL, 200, j_profile, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD2); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD3); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{sss[s]sos[sss]}", "name", NAME, "scope", SCOPE, "enabled", json_true(), "password", "", PASSWORD2, PASSWORD3); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USERNAME, NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{si}", "password", 3); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/user/" USERNAME, NULL, NULL, NULL, NULL, 200, j_profile, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD2); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD3); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{sss[s]sos[ssoss]}", "name", NAME, "scope", SCOPE, "enabled", json_true(), "password", "", "", json_null(), "", ""); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USERNAME, NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{si}", "password", 2); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/user/" USERNAME, NULL, NULL, NULL, NULL, 200, j_profile, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD2); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD3); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_profile, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_profile); j_profile = json_pack("{sss[s]sos[ss]}", "name", NAME, "scope", SCOPE, "enabled", json_true(), "password", PASSWORD1, PASSWORD2); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USERNAME, NULL, NULL, j_profile, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_profile); } END_TEST START_TEST(test_glwd_mod_user_irl_user_delete) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/user/" USERNAME "?source=" MOD_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_mod_user_irl_module_delete) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/user/" MOD_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd mod user multiple password irl"); tc_core = tcase_create("test_glwd_mod_user_multiple_password_irl"); tcase_add_test(tc_core, test_glwd_mod_user_irl_module_add); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_add); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_get); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_auth); tcase_add_test(tc_core, test_glwd_mod_user_irl_profile_update_password); tcase_add_test(tc_core, test_glwd_mod_user_irl_admin_update_password); tcase_add_test(tc_core, test_glwd_mod_user_irl_user_delete); tcase_add_test(tc_core, test_glwd_mod_user_irl_module_delete); tcase_set_timeout(tc_core, 90); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); j_params = json_load_file(argv[1], JSON_DECODE_ANY, NULL); ulfius_init_request(&admin_req); if (j_params != NULL) { // Getting a valid session id for authenticated http requests json_object_set_new(j_params, "name", json_string(MOD_NAME)); ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error reading parameters file %s", argv[1]); } json_decref(j_params); ulfius_clean_request(&admin_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oauth2_additional_parameters.c000066400000000000000000000337311415646314000242470ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "user1" #define USER_PASSWORD "password" #define PLUGIN "glwd" #define PLUGIN_NAME "glwd_claims" #define SCOPE_LIST "g_profile" #define CLIENT "client1_id" #define RESPONSE_TYPE "token" struct _u_request admin_req; struct _u_request user_req; START_TEST(test_oauth2_additional_parameters_add_plugin) { json_t * j_param = json_pack("{sssssss{sssssssssisisisosososososos[{ssss}{ssss}{ssss}{ssss}]}}", "module", "oauth2-glewlwyd", "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", "https://glewlwyd.tld", "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_NAME, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "additional-parameters", "user-parameter", "claim-str", "token-parameter", "claim-str", "user-parameter", "claim-number", "token-parameter", "claim-number", "user-parameter", "claim-bool", "token-parameter", "claim-bool", "user-parameter", "claim-mandatory", "token-parameter", "claim-mandatory"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); j_param = json_pack("{ssssssss}", "claim-str", "the-str", "claim-number", "42", "claim-bool", "1", "claim-mandatory", "I'M aliiiiiive!"); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USER_USERNAME, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oauth2_additional_parameters) { struct _u_response resp; struct _u_request req; char * access_token, ** access_token_split = NULL, * str_payload = NULL; json_t * j_payload; size_t str_payload_len = 0; ulfius_init_response(&resp); ulfius_init_request(&req); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); access_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token=")); if (o_strchr(access_token, '&')) { *(o_strchr(access_token, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(split_string(access_token, ".", &access_token_split), 3); ck_assert_int_eq(o_base64url_decode((unsigned char *)access_token_split[1], o_strlen(access_token_split[1]), NULL, &str_payload_len), 1); ck_assert_ptr_ne((str_payload = o_malloc(str_payload_len + 1)), NULL); ck_assert_int_eq(o_base64url_decode((unsigned char *)access_token_split[1], o_strlen(access_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_payload = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_int_eq(json_object_size(j_payload), 12); ck_assert_str_eq(json_string_value(json_object_get(j_payload, "username")), USER_USERNAME); ck_assert_str_eq(json_string_value(json_object_get(j_payload, "scope")), SCOPE_LIST); ck_assert_str_eq(json_string_value(json_object_get(j_payload, "claim-bool")), "1"); ck_assert_str_eq(json_string_value(json_object_get(j_payload, "claim-mandatory")), "I'M aliiiiiive!"); ck_assert_str_eq(json_string_value(json_object_get(j_payload, "claim-number")), "42"); ck_assert_str_eq(json_string_value(json_object_get(j_payload, "claim-str")), "the-str"); ulfius_clean_request(&req); o_free(access_token); o_free(str_payload); free_string_array(access_token_split); json_decref(j_payload); } END_TEST START_TEST(test_oauth2_no_additional_parameters) { struct _u_response resp; struct _u_request req; char * access_token, ** access_token_split = NULL, * str_payload = NULL; json_t * j_payload; size_t str_payload_len = 0; ulfius_init_response(&resp); ulfius_init_request(&req); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); access_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token=")); if (o_strchr(access_token, '&')) { *(o_strchr(access_token, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(split_string(access_token, ".", &access_token_split), 3); ck_assert_int_eq(o_base64url_decode((unsigned char *)access_token_split[1], o_strlen(access_token_split[1]), NULL, &str_payload_len), 1); ck_assert_ptr_ne((str_payload = o_malloc(str_payload_len + 1)), NULL); ck_assert_int_eq(o_base64url_decode((unsigned char *)access_token_split[1], o_strlen(access_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_payload = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_int_eq(json_object_size(j_payload), 8); ck_assert_str_eq(json_string_value(json_object_get(j_payload, "username")), USER_USERNAME); ck_assert_str_eq(json_string_value(json_object_get(j_payload, "scope")), SCOPE_LIST); ck_assert_ptr_eq(json_object_get(j_payload, "claim-bool"), NULL); ck_assert_ptr_eq(json_object_get(j_payload, "claim-mandatory"), NULL); ck_assert_ptr_eq(json_object_get(j_payload, "claim-number"), NULL); ck_assert_ptr_eq(json_object_get(j_payload, "claim-str"), NULL); ulfius_clean_request(&req); o_free(access_token); o_free(str_payload); free_string_array(access_token_split); json_decref(j_payload); } END_TEST START_TEST(test_oauth2_additional_parameters_delete_plugin) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/user/" USER_USERNAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); json_t * j_param = json_pack("{ss ss ss so s[sssss]}", "username", USER_USERNAME, "name", "Dave Lopper 1", "email", "dev1@glewlwyd", "enabled", json_true(), "scope", "g_profile", "openid", "scope1", "scope2", "scope3"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/user/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 additional parameters"); tc_core = tcase_create("test_oauth2_additional_parameters"); tcase_add_test(tc_core, test_oauth2_additional_parameters_add_plugin); tcase_add_test(tc_core, test_oauth2_additional_parameters); tcase_add_test(tc_core, test_oauth2_no_additional_parameters); tcase_add_test(tc_core, test_oauth2_additional_parameters_delete_plugin); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req; struct _u_response auth_resp, scope_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); ulfius_init_request(&user_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&scope_req); ulfius_init_response(&auth_resp); ulfius_init_response(&scope_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "User %s authenticated", USER_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(scope_req.map_header, "Cookie", cookie); u_map_put(user_req.map_header, "Cookie", cookie); o_free(cookie); scope_req.http_verb = strdup("PUT"); scope_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, &scope_resp) != U_OK || scope_resp.status != 200) { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope '%s' for %s error", CLIENT, SCOPE_LIST); do_test = 0; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope OK"); } ulfius_clean_response(&scope_resp); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication user"); do_test = 0; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); } if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, NULL) != U_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "Remove grant scope '%s' for %s error", CLIENT, SCOPE_LIST); } char * url = msprintf("%s/auth/", SERVER_URI); run_simple_test(&user_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL); o_free(url); ulfius_clean_request(&admin_req); ulfius_clean_request(&user_req); ulfius_clean_request(&scope_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oauth2_auth_code.c000066400000000000000000000230411415646314000216400ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "scope1 scope2" #define CLIENT "client1_id" struct _u_request user_req; START_TEST(test_oauth2_auth_invalid_response_type) { char * url = msprintf("%s/glwd/auth?response_type=invalid&g_continue&client_id=client1_id&redirect_uri=..%%2f..%%2ftest-oauth2.html&state=xyzabcd&scope=%s", SERVER_URI, SCOPE_LIST); int res = run_simple_test(NULL, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "unsupported_response_type"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_auth_code_state_ok) { char * url = msprintf("%s/glwd/auth?response_type=code&g_continue&client_id=client1_id&redirect_uri=..%%2f..%%2ftest-oauth2.html&state=xyzabcd&scope=%s", SERVER_URI, SCOPE_LIST); int res = run_simple_test(NULL, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "state=xyzabcd"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_auth_code_ok_redirect_login) { char * url = msprintf("%s/glwd/auth?response_type=code&client_id=client1_id&redirect_uri=..%%2f..%%2ftest-oauth2.html?param=client1_cb1&state=xyz&scope=%s", SERVER_URI, SCOPE_LIST); int res = run_simple_test(NULL, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_auth_code_client_invalid) { char * url = msprintf("%s/glwd/auth?response_type=code&g_continue&client_id=client_error&redirect_uri=..%%2f..%%2ftest-oauth2.html?param=client1_cb1&state=xyz&scope=%s", SERVER_URI, SCOPE_LIST); int res = run_simple_test(NULL, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "unauthorized_client"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_auth_code_uri_invalid) { char * url = msprintf("%s/glwd/auth?response_type=code&g_continue&client_id=client_error&redirect_uri=..%%2f..%%2ftest-oauth2.html?param=invalid&state=xyz&scope=%s", SERVER_URI, SCOPE_LIST); int res = run_simple_test(NULL, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "unauthorized_client"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_auth_code_scope_invalid) { char * url = msprintf("%s/glwd/auth?response_type=code&g_continue&client_id=client1_id&redirect_uri=..%%2f..%%2ftest-oauth2.html?param=client1_cb1&state=xyzabcd&scope=scope4", SERVER_URI); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "invalid_scope"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_auth_code_scope_empty) { char * url = msprintf("%s/glwd/auth?response_type=code&g_continue&client_id=client1_id&redirect_uri=..%%2f..%%2ftest-oauth2.html?param=client1_cb1&state=xyzabcd", SERVER_URI); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "invalid_scope"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_auth_code_ok_redirect_cb_with_code) { char * url = msprintf("%s/glwd/auth?response_type=code&g_continue&client_id=client1_id&redirect_uri=..%%2f..%%2ftest-oauth2.html?param=client1_cb1&state=xyzabcd&scope=%s", SERVER_URI, SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "code="); o_free(url); ck_assert_int_eq(res, 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 auth_code"); tc_core = tcase_create("test_oauth2_auth_code"); tcase_add_test(tc_core, test_oauth2_auth_invalid_response_type); tcase_add_test(tc_core, test_oauth2_auth_code_ok_redirect_login); tcase_add_test(tc_core, test_oauth2_auth_code_state_ok); tcase_add_test(tc_core, test_oauth2_auth_code_client_invalid); tcase_add_test(tc_core, test_oauth2_auth_code_uri_invalid); tcase_add_test(tc_core, test_oauth2_auth_code_scope_invalid); tcase_add_test(tc_core, test_oauth2_auth_code_scope_empty); tcase_add_test(tc_core, test_oauth2_auth_code_ok_redirect_cb_with_code); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, register_req; struct _u_response auth_resp, scope_resp; json_t * j_body, * j_register; int res, do_test = 0, i; char * url; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_request(&scope_req); ulfius_init_request(®ister_req); ulfius_init_response(&scope_resp); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api/glwd" #define CLIENT_ID "client3_id" #define CLIENT_PASSWORD "password" #define SCOPE_LIST "scope2 scope3" struct _u_request user_req; char * code; START_TEST(test_oauth2_client_cred_valid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", SCOPE_LIST); int res = run_simple_test(NULL, "POST", url, CLIENT_ID, CLIENT_PASSWORD, NULL, &body, 200, NULL, "access_token", NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_client_cred_valid_reduced_scope) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", SCOPE_LIST " scope1"); int res = run_simple_test(NULL, "POST", url, CLIENT_ID, CLIENT_PASSWORD, NULL, &body, 200, NULL, "scope\":\"scope2 scope3\"", NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_client_cred_pwd_invalid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", SCOPE_LIST); int res = run_simple_test(NULL, "POST", url, CLIENT_ID, "invalid", NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_client_cred_client_unauthorized) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", SCOPE_LIST); int res = run_simple_test(NULL, "POST", url, "client1_id", CLIENT_PASSWORD, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_client_cred_client_invalid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", SCOPE_LIST); int res = run_simple_test(NULL, "POST", url, "invalid", CLIENT_PASSWORD, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_client_cred_scope_invalid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", "scope4"); int res = run_simple_test(NULL, "POST", url, CLIENT_ID, CLIENT_PASSWORD, NULL, &body, 400, NULL, "scope_invalid", NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_client_cred_empty) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", SCOPE_LIST); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 client credentials"); tc_core = tcase_create("test_oauth2_client_cred"); tcase_add_test(tc_core, test_oauth2_client_cred_valid); tcase_add_test(tc_core, test_oauth2_client_cred_valid_reduced_scope); tcase_add_test(tc_core, test_oauth2_client_cred_pwd_invalid); tcase_add_test(tc_core, test_oauth2_client_cred_client_unauthorized); tcase_add_test(tc_core, test_oauth2_client_cred_client_invalid); tcase_add_test(tc_core, test_oauth2_client_cred_scope_invalid); tcase_add_test(tc_core, test_oauth2_client_cred_empty); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed; Suite *s; SRunner *sr; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); ulfius_clean_request(&user_req); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oauth2_client_secret.c000066400000000000000000000112661415646314000225360ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define USERNAME_ADMIN "admin" #define PASSWORD_ADMIN "password" #define SCOPE_LIST "g_profile scope3" #define CLIENT "client3_id" #define CLIENT_PASSWORD "password" #define CLIENT_SECRET "secret" struct _u_request admin_req; START_TEST(test_oauth2_client_secret_client_secret_client_set_ok) { json_t * j_parameters = json_pack("{ss}", "client_secret", CLIENT_SECRET); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/client/" CLIENT, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oauth2_client_secret_resource_owner_pwd_cred_valid_secret) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", SCOPE_LIST); u_map_put(&body, "username", USERNAME); u_map_put(&body, "password", PASSWORD); ck_assert_int_eq(run_simple_test(NULL, "POST", url, CLIENT, CLIENT_SECRET, NULL, &body, 200, NULL, "refresh_token", NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 400, NULL, NULL, NULL), 1); o_free(url); u_map_clean(&body); } END_TEST START_TEST(test_oauth2_client_secret_resource_owner_pwd_cred_valid_password) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", SCOPE_LIST); u_map_put(&body, "username", USERNAME); u_map_put(&body, "password", PASSWORD); ck_assert_int_eq(run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 200, NULL, "refresh_token", NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "POST", url, CLIENT, CLIENT_SECRET, NULL, &body, 400, NULL, NULL, NULL), 1); o_free(url); u_map_clean(&body); } END_TEST START_TEST(test_oauth2_client_secret_client_secret_client_disable_ok) { json_t * j_parameters = json_pack("{ss}", "client_secret", ""); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/client/" CLIENT, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 code client confidential"); tc_core = tcase_create("test_oauth2_client_secret"); tcase_add_test(tc_core, test_oauth2_client_secret_resource_owner_pwd_cred_valid_password); tcase_add_test(tc_core, test_oauth2_client_secret_client_secret_client_set_ok); tcase_add_test(tc_core, test_oauth2_client_secret_resource_owner_pwd_cred_valid_secret); tcase_add_test(tc_core, test_oauth2_client_secret_client_secret_client_disable_ok); tcase_add_test(tc_core, test_oauth2_client_secret_resource_owner_pwd_cred_valid_password); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0, i; json_t * j_body; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME_ADMIN, "password", PASSWORD_ADMIN); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api/" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "scope1 scope2" #define SCOPE_LIST_PARTIAL "scope1" #define SCOPE_LIST_MAX_USE "scope1 scope2 scope3" #define CLIENT "client1_id" struct _u_request user_req; char * code; START_TEST(test_oauth2_code_code_invalid) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", "../../test-oauth2.html?param=client1_cb1"); u_map_put(&body, "code", "invalid"); o_free(user_req.http_verb); user_req.http_verb = NULL; int res = run_simple_test(&user_req, "POST", url, NULL, NULL, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_code_client_invalid) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", "invalid"); u_map_put(&body, "redirect_uri", "../../test-oauth2.html?param=client1_cb1"); u_map_put(&body, "code", code); o_free(user_req.http_verb); user_req.http_verb = NULL; int res = run_simple_test(&user_req, "POST", url, NULL, NULL, NULL, &body, 403, NULL, "unauthorized_client", NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_code_redirect_uri_invalid) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", "invalid"); u_map_put(&body, "code", code); o_free(user_req.http_verb); user_req.http_verb = NULL; int res = run_simple_test(&user_req, "POST", url, NULL, NULL, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_code_ok) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", "../../test-oauth2.html?param=client1_cb1"); u_map_put(&body, "code", code); o_free(user_req.http_verb); user_req.http_verb = NULL; ck_assert_int_eq(run_simple_test(&user_req, "POST", url, NULL, NULL, NULL, &body, 200, NULL, "refresh_token", NULL), 1); o_free(url); u_map_clean(&body); } END_TEST START_TEST(test_oauth2_code_scope_grant_partial) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie, * code; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST_PARTIAL); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get code code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/glwd/auth?response_type=code&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(code_resp.map_header, "Location"), "code=")+o_strlen("code=")); ck_assert_ptr_ne(code, NULL); ulfius_clean_response(&code_resp); // Get refresh token from code ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("POST"); code_req.http_url = msprintf("%s/glwd/token/", SERVER_URI); u_map_put(code_req.map_post_body, "grant_type", "authorization_code"); u_map_put(code_req.map_post_body, "client_id", CLIENT); u_map_put(code_req.map_post_body, "redirect_uri", "../../test-oauth2.html?param=client1_cb1"); u_map_put(code_req.map_post_body, "code", code); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 200); j_body = ulfius_get_json_body_response(&code_resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_str_eq(json_string_value(json_object_get(j_body, "scope")), SCOPE_LIST_PARTIAL); ulfius_clean_request(&code_req); ulfius_clean_response(&code_resp); o_free(code); json_decref(j_body); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); } END_TEST START_TEST(test_oauth2_code_scope_grant_none) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Try to get code code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/glwd/auth?response_type=code&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "invalid_scope"), NULL); ulfius_clean_request(&code_req); ulfius_clean_response(&code_resp); ulfius_clean_request(&auth_req); } END_TEST START_TEST(test_oauth2_code_scope_grant_all_authorize_partial) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Try to get code code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/glwd/auth?response_type=code&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "login.html"), NULL); ulfius_clean_response(&code_resp); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); ulfius_clean_request(&code_req); } END_TEST START_TEST(test_oauth2_code_retry_with_max_use) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie, * code; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST_MAX_USE); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 88 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get code code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/glwd/auth?response_type=code&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(code_resp.map_header, "Location"), "code=")+o_strlen("code=")); ck_assert_ptr_ne(code, NULL); ulfius_clean_response(&code_resp); // Get refresh token from code ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("POST"); code_req.http_url = msprintf("%s/glwd/token/", SERVER_URI); u_map_put(code_req.map_post_body, "grant_type", "authorization_code"); u_map_put(code_req.map_post_body, "client_id", CLIENT); u_map_put(code_req.map_post_body, "redirect_uri", "../../test-oauth2.html?param=client1_cb1"); u_map_put(code_req.map_post_body, "code", code); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 200); j_body = ulfius_get_json_body_response(&code_resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_str_eq(json_string_value(json_object_get(j_body, "scope")), SCOPE_LIST_MAX_USE); ulfius_clean_response(&code_resp); o_free(code); json_decref(j_body); // Try to get another code with the same session but redirected to login page ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/glwd/auth?response_type=code&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "login.html"), NULL); ulfius_clean_response(&code_resp); // Reauthenticate with scheme mock 88 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get another code ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/glwd/auth?response_type=code&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(code_resp.map_header, "Location"), "code=")+o_strlen("code=")); ck_assert_ptr_ne(code, NULL); ulfius_clean_response(&code_resp); // Get another refresh token from code ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("POST"); code_req.http_url = msprintf("%s/glwd/token/", SERVER_URI); u_map_put(code_req.map_post_body, "grant_type", "authorization_code"); u_map_put(code_req.map_post_body, "client_id", CLIENT); u_map_put(code_req.map_post_body, "redirect_uri", "../../test-oauth2.html?param=client1_cb1"); u_map_put(code_req.map_post_body, "code", code); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 200); j_body = ulfius_get_json_body_response(&code_resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_str_eq(json_string_value(json_object_get(j_body, "scope")), SCOPE_LIST_MAX_USE); ulfius_clean_request(&code_req); ulfius_clean_response(&code_resp); o_free(code); json_decref(j_body); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 code"); tc_core = tcase_create("test_oauth2_code"); tcase_add_test(tc_core, test_oauth2_code_code_invalid); tcase_add_test(tc_core, test_oauth2_code_client_invalid); tcase_add_test(tc_core, test_oauth2_code_redirect_uri_invalid); tcase_add_test(tc_core, test_oauth2_code_ok); tcase_add_test(tc_core, test_oauth2_code_scope_grant_partial); tcase_add_test(tc_core, test_oauth2_code_scope_grant_none); tcase_add_test(tc_core, test_oauth2_code_scope_grant_all_authorize_partial); tcase_add_test(tc_core, test_oauth2_code_retry_with_max_use); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, register_req; struct _u_response auth_resp, scope_resp, code_resp; json_t * j_body, * j_register; int res, do_test = 0, i; char * url; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_request(&scope_req); ulfius_init_request(®ister_req); ulfius_init_response(&auth_resp); ulfius_init_response(&scope_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api/" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "openid" #define CLIENT "client1_id" #define REDIRECT_URI "..%2f..%2ftest-oauth2.html?param=client1_cb1" #define REDIRECT_URI_DECODED "../../test-oauth2.html?param=client1_cb1" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define CODE_CHALLENGE_VALID "V0UN9ToT-UnbxeIx7imQdhFjsAZTmuARpHyuD2ajIIo" #define CODE_CHALLENGE_VALID_2 "GE8iHoAZ3H6to1ERRDVLx8iJD0XLHF0XjmfxWoAibQE" #define CODE_CHALLENGE_42 "NfzP48kCM-QfPKoR2p-lJvTAG28TR-FlWXqxp7naU8I" #define CODE_CHALLENGE_INVALID_CHARSET "Iy8nznHo9PQsgDeabc-fZbPWVFApHSoyRZskN6rH-d0" #define CODE_VERIFIER_VALID "XvkLR4XIl4DbkFz3RLEZUBStp8yIjvF8UtfRv0nkK8DqmrBtWvHmEuyBL2enyLF9" #define CODE_VERIFIER_VALID_2 "6nwfKTI6ODJmm89xBbvX0mOLbk0PbLY2sNS2o9vFixHU8hAktkQw3PwJHX1GAf69" #define CODE_VERIFIER_INVALID_42 "XvkLR4XIl4DbkFz3RLEZUBStp8yIjvF8UtfRv0nkww" #define CODE_VERIFIER_INVALID_129 "XvkLR4XIl4DbkFz3RLEZUBStp8yIjvF8UtfRv0nkK8DqmrBtWvHmEuyBL2enyLF9XvkLR4XIl4DbkFz3RLEZUBStp8yIjvF8UtfRv0nkK8DqmrBtWvHmEuyBL2enyLF9X" #define CODE_VERIFIER_INVALID_CHARSET "XvkLR4XIl4DbkFz3RLEZUBStp8yIjvF8UtfRv0nkK8DqmrBt,vHmEuyBL2enyLF9" #define CODE_CHALLENGE_METHOD_PLAIN "plain" #define CODE_CHALLENGE_METHOD_S256 "S256" #define PLUGIN_MODULE "oauth2-glewlwyd" #define PLUGIN_NAME "challenge" #define PLUGIN_DISPLAY_NAME "Challenge test" #define PLUGIN_JWT_TYPE "sha" #define PLUGIN_JWT_KEY_SIZE "256" #define PLUGIN_KEY "secret" #define PLUGIN_CODE_DURATION 600 #define PLUGIN_REFRESH_TOKEN_DURATION 1209600 #define PLUGIN_ACCESS_TOKEN_DURATION 3600 struct _u_request user_req; struct _u_request admin_req; START_TEST(test_oauth2_code_code_challenge_add_plugin_with_plain) { json_t * j_parameters = json_pack("{sssssssos{sssssssisisisososososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "pkce-allowed", json_true(), "pkce-method-plain-allowed", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oauth2_code_code_challenge_add_plugin_without_plain) { json_t * j_parameters = json_pack("{sssssssos{sssssssisisisososososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "pkce-allowed", json_true(), "pkce-method-plain-allowed", json_false()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oauth2_code_code_challenge_remove_plugin) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oauth2_code_code_challenge_invalid_code_challenge_method) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_CHALLENGE_VALID "&code_challenge_method=error&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"), 1); } END_TEST START_TEST(test_oauth2_code_code_challenge_plain_invalid_code_challenge_method) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_CHALLENGE_VALID "&code_challenge_method=" CODE_CHALLENGE_METHOD_PLAIN "&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"), 1); } END_TEST START_TEST(test_oauth2_code_code_challenge_plain_invalid_length) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_VERIFIER_INVALID_42 "&code_challenge_method=" CODE_CHALLENGE_METHOD_PLAIN "&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"), 1); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_VERIFIER_INVALID_129 "&code_challenge_method=" CODE_CHALLENGE_METHOD_PLAIN "&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"), 1); } END_TEST START_TEST(test_oauth2_code_code_challenge_plain_invalid_charset) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_VERIFIER_INVALID_CHARSET "&code_challenge_method=" CODE_CHALLENGE_METHOD_PLAIN "&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"), 1); } END_TEST START_TEST(test_oauth2_code_code_challenge_plain_method_empty_ok) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_VERIFIER_VALID "&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "code="), 1); } END_TEST START_TEST(test_oauth2_code_code_challenge_plain_method_set_ok) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_VERIFIER_VALID "&code_challenge_method=" CODE_CHALLENGE_METHOD_PLAIN "&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "code="), 1); } END_TEST START_TEST(test_oauth2_code_code_challenge_plain_verifier_invalid_value) { struct _u_response resp; char * code = NULL; struct _u_map body; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_VERIFIER_VALID "&code_challenge_method=" CODE_CHALLENGE_METHOD_PLAIN "&scope=" SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "code_verifier", CODE_VERIFIER_VALID_2); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", REDIRECT_URI_DECODED); u_map_put(&body, "code", code); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); u_map_clean(&body); ulfius_clean_response(&resp); o_free(code); } END_TEST START_TEST(test_oauth2_code_code_challenge_plain_verifier_ok) { struct _u_response resp; char * code = NULL; struct _u_map body; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_VERIFIER_VALID "&code_challenge_method=" CODE_CHALLENGE_METHOD_PLAIN "&scope=" SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "code_verifier", CODE_VERIFIER_VALID); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", REDIRECT_URI_DECODED); u_map_put(&body, "code", code); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 200, NULL, NULL, NULL), 1); u_map_clean(&body); ulfius_clean_response(&resp); o_free(code); } END_TEST START_TEST(test_oauth2_code_code_challenge_s256_invalid_length) { struct _u_response resp; char * code = NULL; struct _u_map body; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_CHALLENGE_42 "&code_challenge_method=" CODE_CHALLENGE_METHOD_S256 "&scope=" SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "code_verifier", CODE_VERIFIER_INVALID_42); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", REDIRECT_URI_DECODED); u_map_put(&body, "code", code); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); u_map_clean(&body); ulfius_clean_response(&resp); o_free(code); } END_TEST START_TEST(test_oauth2_code_code_challenge_s256_invalid_charset) { struct _u_response resp; char * code = NULL; struct _u_map body; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_CHALLENGE_INVALID_CHARSET "&code_challenge_method=" CODE_CHALLENGE_METHOD_S256 "&scope=" SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "code_verifier", CODE_VERIFIER_INVALID_CHARSET); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", REDIRECT_URI_DECODED); u_map_put(&body, "code", code); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); u_map_clean(&body); ulfius_clean_response(&resp); o_free(code); } END_TEST START_TEST(test_oauth2_code_code_challenge_s256_verifier_invalid_value) { struct _u_response resp; char * code = NULL; struct _u_map body; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_CHALLENGE_VALID "&code_challenge_method=" CODE_CHALLENGE_METHOD_S256 "&scope=" SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "code_verifier", CODE_VERIFIER_VALID_2); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", REDIRECT_URI_DECODED); u_map_put(&body, "code", code); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); u_map_clean(&body); ulfius_clean_response(&resp); o_free(code); } END_TEST START_TEST(test_oauth2_code_code_challenge_s256_method_empty_invalid) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_VERIFIER_VALID "&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"), 1); } END_TEST START_TEST(test_oauth2_code_code_challenge_s256_method_set_plain_invalid) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_VERIFIER_VALID "&code_challenge_method=" CODE_CHALLENGE_METHOD_PLAIN "&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"), 1); } END_TEST START_TEST(test_oauth2_code_code_challenge_s256_method_set_ok) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_CHALLENGE_VALID "&code_challenge_method=" CODE_CHALLENGE_METHOD_S256 "&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "code="), 1); } END_TEST START_TEST(test_oauth2_code_code_challenge_s256_verifier_ok) { struct _u_response resp; char * code = NULL; struct _u_map body; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_CHALLENGE_VALID "&code_challenge_method=" CODE_CHALLENGE_METHOD_S256 "&scope=" SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "code_verifier", CODE_VERIFIER_VALID); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", REDIRECT_URI_DECODED); u_map_put(&body, "code", code); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 200, NULL, NULL, NULL), 1); u_map_clean(&body); ulfius_clean_response(&resp); o_free(code); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 code_challenge"); tc_core = tcase_create("test_oauth2_code_challenge"); tcase_add_test(tc_core, test_oauth2_code_code_challenge_add_plugin_with_plain); tcase_add_test(tc_core, test_oauth2_code_code_challenge_invalid_code_challenge_method); tcase_add_test(tc_core, test_oauth2_code_code_challenge_plain_invalid_length); tcase_add_test(tc_core, test_oauth2_code_code_challenge_plain_invalid_charset); tcase_add_test(tc_core, test_oauth2_code_code_challenge_plain_method_empty_ok); tcase_add_test(tc_core, test_oauth2_code_code_challenge_plain_method_set_ok); tcase_add_test(tc_core, test_oauth2_code_code_challenge_plain_verifier_invalid_value); tcase_add_test(tc_core, test_oauth2_code_code_challenge_plain_verifier_ok); tcase_add_test(tc_core, test_oauth2_code_code_challenge_s256_invalid_length); tcase_add_test(tc_core, test_oauth2_code_code_challenge_s256_invalid_charset); tcase_add_test(tc_core, test_oauth2_code_code_challenge_s256_verifier_invalid_value); tcase_add_test(tc_core, test_oauth2_code_code_challenge_s256_method_set_ok); tcase_add_test(tc_core, test_oauth2_code_code_challenge_s256_verifier_ok); tcase_add_test(tc_core, test_oauth2_code_code_challenge_remove_plugin); tcase_add_test(tc_core, test_oauth2_code_code_challenge_add_plugin_without_plain); tcase_add_test(tc_core, test_oauth2_code_code_challenge_plain_invalid_code_challenge_method); tcase_add_test(tc_core, test_oauth2_code_code_challenge_s256_invalid_length); tcase_add_test(tc_core, test_oauth2_code_code_challenge_s256_invalid_charset); tcase_add_test(tc_core, test_oauth2_code_code_challenge_s256_verifier_invalid_value); tcase_add_test(tc_core, test_oauth2_code_code_challenge_s256_method_empty_invalid); tcase_add_test(tc_core, test_oauth2_code_code_challenge_s256_method_set_plain_invalid); tcase_add_test(tc_core, test_oauth2_code_code_challenge_s256_method_set_ok); tcase_add_test(tc_core, test_oauth2_code_code_challenge_s256_verifier_ok); tcase_add_test(tc_core, test_oauth2_code_code_challenge_remove_plugin); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&user_req); ulfius_init_request(&admin_req); ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "scope1 scope2" #define CLIENT "client3_id" #define CLIENT_PASSWORD "password" #define REDIRECT_URI "../../test-oauth2.html?param=client3" #define REDIRECT_URI_ENCODED "..%2f..%2ftest-oauth2.html%3fparam%3dclient3" struct _u_request user_req; char * code; START_TEST(test_oauth2_code_client_confidential_code_invalid) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", REDIRECT_URI); u_map_put(&body, "code", "invalid"); o_free(user_req.http_verb); user_req.http_verb = NULL; int res = run_simple_test(&user_req, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_code_client_confidential_client_invalid) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", "invalid"); u_map_put(&body, "redirect_uri", REDIRECT_URI); u_map_put(&body, "code", code); o_free(user_req.http_verb); user_req.http_verb = NULL; int res = run_simple_test(&user_req, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 403, NULL, "unauthorized_client", NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_code_client_confidential_redirect_uri_invalid) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", "invalid"); u_map_put(&body, "code", code); o_free(user_req.http_verb); user_req.http_verb = NULL; int res = run_simple_test(&user_req, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_code_client_confidential_credentials_invalid) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", REDIRECT_URI); u_map_put(&body, "code", code); o_free(user_req.http_verb); user_req.http_verb = NULL; int res = run_simple_test(&user_req, "POST", url, CLIENT, "error", NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_code_client_confidential_client_id_inconsistent) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", REDIRECT_URI); u_map_put(&body, "code", code); o_free(user_req.http_verb); user_req.http_verb = NULL; int res = run_simple_test(&user_req, "POST", url, "error", CLIENT_PASSWORD, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_code_client_confidential_ok) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", REDIRECT_URI); u_map_put(&body, "code", code); o_free(user_req.http_verb); user_req.http_verb = NULL; int res = run_simple_test(&user_req, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 200, NULL, "refresh_token", NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 code client confidential"); tc_core = tcase_create("test_oauth2_code_client_confidential"); tcase_add_test(tc_core, test_oauth2_code_client_confidential_code_invalid); tcase_add_test(tc_core, test_oauth2_code_client_confidential_client_invalid); tcase_add_test(tc_core, test_oauth2_code_client_confidential_redirect_uri_invalid); tcase_add_test(tc_core, test_oauth2_code_client_confidential_credentials_invalid); tcase_add_test(tc_core, test_oauth2_code_client_confidential_client_id_inconsistent); tcase_add_test(tc_core, test_oauth2_code_client_confidential_ok); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, register_req; struct _u_response auth_resp, scope_resp, code_resp; json_t * j_body, * j_register; int res, do_test = 0, i; char * url; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_request(&scope_req); ulfius_init_request(®ister_req); ulfius_init_response(&scope_resp); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "user1" #define USER_PASSWORD "password" #define PLUGIN_MODULE "oauth2-glewlwyd" #define PLUGIN_NAME "oidc_code_replay" #define SCOPE_LIST "scope1" #define CLIENT_ID "client3_id" #define CLIENT_SECRET "password" #define CLIENT_REDIRECT_URI "../../test-oauth2.html?param=client3" #define CLIENT_REDIRECT_URI_ENCODED "..%2F..%2Ftest-oauth2.html%3Fparam%3Dclient3" #define RESPONSE_TYPE "code" struct _u_request admin_req; struct _u_request user_req; START_TEST(test_oauth2_code_replay_add_plugin_with_revoke_replay) { json_t * j_param = json_pack("{sssssss{sssssssisisisosososososososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_NAME, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-code-revoke-replayed", json_true(), "auth-type-token-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "introspection-revocation-allowed", json_true(), "introspection-revocation-allow-target-client", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oauth2_code_replay_test_revoked_tokens) { struct _u_request req; struct _u_response resp; char * code; const char * refresh_token, * access_token, * access_token_refreshed; json_t * j_body_code, * j_body_refresh, * j_introspect; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE "&client_id=" CLIENT_ID "&redirect_uri=" CLIENT_REDIRECT_URI_ENCODED "&scope=" SCOPE_LIST "&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); ck_assert_ptr_ne(NULL, code); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_body_code = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(refresh_token = json_string_value(json_object_get(j_body_code, "refresh_token")), NULL); ck_assert_ptr_ne(access_token = json_string_value(json_object_get(j_body_code, "access_token")), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "refresh_token", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "refresh_token", refresh_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_body_refresh = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(access_token_refreshed = json_string_value(json_object_get(j_body_refresh, "access_token")), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", refresh_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_true()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", access_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_true()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", access_token_refreshed, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_true()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", refresh_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_false()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", access_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_false()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", access_token_refreshed, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_false()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); json_decref(j_body_refresh); json_decref(j_body_code); o_free(code); } END_TEST START_TEST(test_oauth2_code_replay_add_plugin_without_revoke_replay) { json_t * j_param = json_pack("{sssssss{sssssssisisisosososososososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_NAME, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-code-revoke-replayed", json_false(), "auth-type-token-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "introspection-revocation-allowed", json_true(), "introspection-revocation-allow-target-client", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oauth2_code_replay_test_non_revoked_tokens) { struct _u_request req; struct _u_response resp; char * code; const char * refresh_token, * access_token, * access_token_refreshed; json_t * j_body_code, * j_body_refresh, * j_introspect; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE "&client_id=" CLIENT_ID "&redirect_uri=" CLIENT_REDIRECT_URI_ENCODED "&scope=" SCOPE_LIST "&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); ck_assert_ptr_ne(NULL, code); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_body_code = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(refresh_token = json_string_value(json_object_get(j_body_code, "refresh_token")), NULL); ck_assert_ptr_ne(access_token = json_string_value(json_object_get(j_body_code, "access_token")), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "refresh_token", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "refresh_token", refresh_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_body_refresh = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(access_token_refreshed = json_string_value(json_object_get(j_body_refresh, "access_token")), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", refresh_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_true()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", access_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_true()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", access_token_refreshed, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_true()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", refresh_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_true()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", access_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_true()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", access_token_refreshed, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_true()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); json_decref(j_body_refresh); json_decref(j_body_code); o_free(code); } END_TEST START_TEST(test_oauth2_code_replay_delete_plugin) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc code replay"); tc_core = tcase_create("test_oauth2_code_replay"); tcase_add_test(tc_core, test_oauth2_code_replay_add_plugin_with_revoke_replay); tcase_add_test(tc_core, test_oauth2_code_replay_test_revoked_tokens); tcase_add_test(tc_core, test_oauth2_code_replay_delete_plugin); tcase_add_test(tc_core, test_oauth2_code_replay_add_plugin_without_revoke_replay); tcase_add_test(tc_core, test_oauth2_code_replay_test_non_revoked_tokens); tcase_add_test(tc_core, test_oauth2_code_replay_delete_plugin); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, register_req; struct _u_response auth_resp, scope_resp; int res, do_test = 0, i; json_t * j_body, * j_register; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); ulfius_init_request(&user_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&scope_req); ulfius_init_request(®ister_req); ulfius_init_response(&auth_resp); ulfius_init_response(&scope_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile scope3" #define CLIENT "client1_id" char * refresh_token; START_TEST(test_oauth2_delete_token_token_invalid) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "delete_token"); u_map_put(&body, "refresh_token", "invalid"); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_delete_token_ok) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "delete_token"); u_map_put(&body, "refresh_token", refresh_token); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 200, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_delete_token_token_already_deleted) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "delete_token"); u_map_put(&body, "refresh_token", refresh_token); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 delete token"); tc_core = tcase_create("test_oauth2_delete_token"); tcase_add_test(tc_core, test_oauth2_delete_token_token_invalid); tcase_add_test(tc_core, test_oauth2_delete_token_ok); tcase_add_test(tc_core, test_oauth2_delete_token_token_already_deleted); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/glwd/token/", SERVER_URI); u_map_put(auth_req.map_post_body, "grant_type", "password"); u_map_put(auth_req.map_post_body, "username", USERNAME); u_map_put(auth_req.map_post_body, "password", PASSWORD); u_map_put(auth_req.map_post_body, "scope", SCOPE_LIST); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK) { json_t * json_body = ulfius_get_json_body_response(&auth_resp, NULL); refresh_token = o_strdup(json_string_value(json_object_get(json_body, "refresh_token"))); y_log_message(Y_LOG_LEVEL_INFO, "User %s authenticated", USERNAME); json_decref(json_body); } ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); o_free(refresh_token); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oauth2_delete_token_client_confidential.c000066400000000000000000000116101415646314000264230ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile scope3" #define CLIENT "client3_id" #define CLIENT_PASSWORD "password" char * refresh_token; START_TEST(test_oauth2_delete_token_client_confidential_token_invalid) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "delete_token"); u_map_put(&body, "refresh_token", "invalid"); int res = run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_delete_token_client_confidential_client_invalid) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "delete_token"); u_map_put(&body, "refresh_token", refresh_token); int res = run_simple_test(NULL, "POST", url, CLIENT, "error", NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_delete_token_client_confidential_no_client) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "delete_token"); u_map_put(&body, "refresh_token", refresh_token); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_delete_token_client_confidential_ok) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "delete_token"); u_map_put(&body, "refresh_token", refresh_token); int res = run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 200, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_delete_token_client_confidential_token_already_deleted) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "delete_token"); u_map_put(&body, "refresh_token", refresh_token); int res = run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 delete token client confiddential"); tc_core = tcase_create("test_oauth2_delete_token_client_confidential"); tcase_add_test(tc_core, test_oauth2_delete_token_client_confidential_token_invalid); tcase_add_test(tc_core, test_oauth2_delete_token_client_confidential_client_invalid); tcase_add_test(tc_core, test_oauth2_delete_token_client_confidential_no_client); tcase_add_test(tc_core, test_oauth2_delete_token_client_confidential_ok); tcase_add_test(tc_core, test_oauth2_delete_token_client_confidential_token_already_deleted); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/glwd/token/", SERVER_URI); auth_req.auth_basic_user = strdup(CLIENT); auth_req.auth_basic_password = strdup(CLIENT_PASSWORD); u_map_put(auth_req.map_post_body, "grant_type", "password"); u_map_put(auth_req.map_post_body, "username", USERNAME); u_map_put(auth_req.map_post_body, "password", PASSWORD); u_map_put(auth_req.map_post_body, "scope", SCOPE_LIST); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK) { json_t * json_body = ulfius_get_json_body_response(&auth_resp, NULL); refresh_token = o_strdup(json_string_value(json_object_get(json_body, "refresh_token"))); y_log_message(Y_LOG_LEVEL_INFO, "User %s authenticated", USERNAME); json_decref(json_body); } ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); o_free(refresh_token); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oauth2_device_authorization.c000066400000000000000000001303671415646314000241360ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define SCOPE_LIST "openid g_profile" #define NONCE_TEST "nonce5678" #define STATE_TEST "abcxyz" #define PLUGIN_MODULE "oauth2-glewlwyd" #define PLUGIN_NAME "oauth2_device" #define PLUGIN_DISPLAY_NAME "oauth2 with device authorization" #define PLUGIN_ISS "https://glewlwyd.tld" #define PLUGIN_CODE_DURATION 600 #define PLUGIN_REFRESH_TOKEN_DURATION 1209600 #define PLUGIN_ACCESS_TOKEN_DURATION 3600 #define CLIENT_ID "client_device" #define CLIENT_NAME "client for device" #define CLIENT_SECRET "very-secret" struct _u_request admin_req; struct _u_request user_req; START_TEST(test_oauth2_device_authorization_add_module_ok) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisosososososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", "sha", "jwt-key-size", "256", "key", "secret", "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oauth2", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "auth-type-device-enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oauth2_device_authorization_add_client_confidential_ok) { json_t * j_parameters = json_pack("{sssssssos[s]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "client_secret", CLIENT_SECRET, "confidential", json_true(), "authorization_type", "device_authorization", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oauth2_device_authorization_add_client_public_ok) { json_t * j_parameters = json_pack("{sssssos[s]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "confidential", json_false(), "authorization_type", "device_authorization", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oauth2_device_authorization_add_client_unauthorized) { json_t * j_parameters = json_pack("{sssssos[s]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "confidential", json_false(), "authorization_type", "code", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oauth2_device_authorization_delete_module) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oauth2_device_authorization_delete_client) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_ID, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oauth2_device_authorization_client_cred_post_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "client_secret", CLIENT_SECRET); u_map_put(req.map_post_body, "scope", SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/oauth2_device/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oauth2_device_authorization_client_cred_header_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/oauth2_device/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oauth2_device_authorization_client_cred_header_invalid) { struct _u_request req; struct _u_response resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup("error"); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(403, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oauth2_device_authorization_client_cred_header_no_post_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/oauth2_device/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oauth2_device_authorization_client_pub_post_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/oauth2_device/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oauth2_device_authorization_client_pub_post_invalid) { struct _u_request req; struct _u_response resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(403, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oauth2_device_authorization_user_code_redirect_login_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp; const char * redirect_uri; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/oauth2_device/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device/"); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=device"), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oauth2_device_authorization_user_code_input_invalid) { struct _u_request req; struct _u_response resp; json_t * j_resp; const char * redirect_uri; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/oauth2_device/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=error&g_continue"); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceCodeError"), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oauth2_device_authorization_user_code_input_redirect_login_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp; const char * redirect_uri, * code; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/oauth2_device/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "login.html"), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_resp); } END_TEST START_TEST(test_oauth2_device_authorization_user_code_input_complete_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp, * j_grant; const char * redirect_uri, * code; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/oauth2_device/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", SCOPE_LIST); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); json_decref(j_resp); j_grant = json_pack("{ss}", "scope", ""); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); } END_TEST START_TEST(test_oauth2_device_authorization_device_verification_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp, * j_grant; const char * redirect_uri, * code, * device_code; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/oauth2_device/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", SCOPE_LIST); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", ""); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "device_code", device_code); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "refresh_token"), NULL); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oauth2_device_authorization_device_verification_auth_pending) { struct _u_request req; struct _u_response resp; json_t * j_resp; const char * code, * device_code; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/oauth2_device/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "device_code", device_code); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "error")), "authorization_pending"); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oauth2_device_authorization_device_verification_auth_slow_down) { struct _u_request req; struct _u_response resp; json_t * j_resp; const char * code, * device_code; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/oauth2_device/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "device_code", device_code); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "error")), "authorization_pending"); ulfius_clean_response(&resp); json_decref(j_resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "error")), "slow_down"); ulfius_clean_response(&resp); json_decref(j_resp); ulfius_clean_request(&req); } END_TEST START_TEST(test_oauth2_device_authorization_device_verification_device_code_invalid) { struct _u_request req; struct _u_response resp; json_t * j_resp, * j_grant; const char * redirect_uri, * code; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/oauth2_device/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", SCOPE_LIST); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", ""); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "device_code", "error"); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(400, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oauth2_device_authorization_device_verification_client_invalid) { struct _u_request req; struct _u_response resp; json_t * j_resp, * j_grant; const char * redirect_uri, * code, * device_code; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/oauth2_device/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", SCOPE_LIST); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", ""); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", "client1_id"); u_map_put(req.map_post_body, "device_code", device_code); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(403, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oauth2_device_authorization_device_verification_client_secret_invalid) { struct _u_request req; struct _u_response resp; json_t * j_resp, * j_grant; const char * redirect_uri, * code, * device_code; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/oauth2_device/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", SCOPE_LIST); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", ""); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "device_code", device_code); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup("error"); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(403, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 device authorization"); tc_core = tcase_create("test_oauth2_device_authorization"); tcase_add_test(tc_core, test_oauth2_device_authorization_add_module_ok); tcase_add_test(tc_core, test_oauth2_device_authorization_add_client_public_ok); tcase_add_test(tc_core, test_oauth2_device_authorization_client_pub_post_valid); tcase_add_test(tc_core, test_oauth2_device_authorization_delete_client); tcase_add_test(tc_core, test_oauth2_device_authorization_add_client_unauthorized); tcase_add_test(tc_core, test_oauth2_device_authorization_client_pub_post_invalid); tcase_add_test(tc_core, test_oauth2_device_authorization_delete_client); tcase_add_test(tc_core, test_oauth2_device_authorization_add_client_confidential_ok); tcase_add_test(tc_core, test_oauth2_device_authorization_client_cred_post_valid); tcase_add_test(tc_core, test_oauth2_device_authorization_client_cred_header_valid); tcase_add_test(tc_core, test_oauth2_device_authorization_client_cred_header_no_post_valid); tcase_add_test(tc_core, test_oauth2_device_authorization_client_cred_header_invalid); tcase_add_test(tc_core, test_oauth2_device_authorization_user_code_redirect_login_valid); tcase_add_test(tc_core, test_oauth2_device_authorization_user_code_input_invalid); tcase_add_test(tc_core, test_oauth2_device_authorization_user_code_input_redirect_login_valid); tcase_add_test(tc_core, test_oauth2_device_authorization_user_code_input_complete_valid); tcase_add_test(tc_core, test_oauth2_device_authorization_device_verification_valid); tcase_add_test(tc_core, test_oauth2_device_authorization_device_verification_auth_pending); tcase_add_test(tc_core, test_oauth2_device_authorization_device_verification_auth_slow_down); tcase_add_test(tc_core, test_oauth2_device_authorization_device_verification_device_code_invalid); tcase_add_test(tc_core, test_oauth2_device_authorization_device_verification_client_invalid); tcase_add_test(tc_core, test_oauth2_device_authorization_device_verification_client_secret_invalid); tcase_add_test(tc_core, test_oauth2_device_authorization_delete_client); tcase_add_test(tc_core, test_oauth2_device_authorization_delete_module); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, register_req; struct _u_response auth_resp, scope_resp; json_t * j_body; int res, do_test = 0, i; char * url; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "scope1 scope2" #define SCOPE_LIST_PARTIAL "scope1" #define SCOPE_LIST_MAX_USE "scope1 scope2 scope3" #define CLIENT "client1_id" struct _u_request user_req; char * code; START_TEST(test_oauth2_implicit_redirect_login) { char * url = msprintf("%s/glwd/auth?response_type=token&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST); int res = run_simple_test(NULL, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_implicit_valid) { char * url = msprintf("%s/glwd/auth?response_type=token&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "token="); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_implicit_client_invalid) { char * url = msprintf("%s/glwd/auth?response_type=token&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&scope=%s", SERVER_URI, "invalid", SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=unauthorized_client"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_implicit_redirect_uri_invalid) { char * url = msprintf("%s/glwd/auth?response_type=token&g_continue&client_id=%s&redirect_uri=invalid&state=xyzabcd&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=unauthorized_client"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_implicit_scope_invalid) { char * url = msprintf("%s/glwd/auth?response_type=token&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&scope=%s", SERVER_URI, CLIENT, "scope4"); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_scope"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_implicit_empty) { char * url = msprintf("%s/glwd/auth?response_type=token&state=xyzabcd&g_continue", SERVER_URI); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_implicit_scope_grant_partial) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST_PARTIAL); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get token code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/glwd/auth?response_type=token&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "token="), NULL); ck_assert_str_eq(o_strstr(u_map_get(code_resp.map_header, "Location"), "scope=") + o_strlen("scope="), SCOPE_LIST_PARTIAL); ulfius_clean_response(&code_resp); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); } END_TEST START_TEST(test_oauth2_implicit_scope_grant_none) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Try to get code code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/glwd/auth?response_type=token&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "invalid_scope"), NULL); ulfius_clean_request(&code_req); ulfius_clean_response(&code_resp); ulfius_clean_request(&auth_req); } END_TEST START_TEST(test_oauth2_implicit_scope_grant_all_authorize_partial) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Try to get token code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/glwd/auth?response_type=token&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "login.html"), NULL); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); ulfius_clean_response(&code_resp); } END_TEST START_TEST(test_oauth2_implicit_retry_with_max_use) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST_MAX_USE); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 88 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get token code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/glwd/auth?response_type=token&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "token="), NULL); ck_assert_str_eq(o_strstr(u_map_get(code_resp.map_header, "Location"), "scope=")+o_strlen("scope="), SCOPE_LIST_MAX_USE); ulfius_clean_response(&code_resp); // Try to get another token with the same session but redirected to login page ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/glwd/auth?response_type=token&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "login.html"), NULL); ulfius_clean_response(&code_resp); // Reauthenticate with scheme mock 88 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get another code ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/glwd/auth?response_type=token&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "token="), NULL); ck_assert_str_eq(o_strstr(u_map_get(code_resp.map_header, "Location"), "scope=")+o_strlen("scope="), SCOPE_LIST_MAX_USE); ulfius_clean_response(&code_resp); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 implicit"); tc_core = tcase_create("test_oauth2_implicit"); tcase_add_test(tc_core, test_oauth2_implicit_redirect_login); tcase_add_test(tc_core, test_oauth2_implicit_valid); tcase_add_test(tc_core, test_oauth2_implicit_client_invalid); tcase_add_test(tc_core, test_oauth2_implicit_redirect_uri_invalid); tcase_add_test(tc_core, test_oauth2_implicit_scope_invalid); tcase_add_test(tc_core, test_oauth2_implicit_empty); tcase_add_test(tc_core, test_oauth2_implicit_scope_grant_partial); tcase_add_test(tc_core, test_oauth2_implicit_scope_grant_none); tcase_add_test(tc_core, test_oauth2_implicit_scope_grant_all_authorize_partial); tcase_add_test(tc_core, test_oauth2_implicit_retry_with_max_use); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, register_req; struct _u_response auth_resp, scope_resp; json_t * j_body, * j_register; int res, do_test = 0, i; char * url; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_request(&scope_req); ulfius_init_request(®ister_req); ulfius_init_response(&scope_resp); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #include "../src/glewlwyd-common.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" struct _u_request admin_req; json_t * j_params; START_TEST(test_oauth2_irl_user_module_add) { char * url = SERVER_URI "/mod/user"; ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, json_object_get(j_params, "user_mod"), NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oauth2_irl_client_module_add) { char * url = SERVER_URI "/mod/client"; ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, json_object_get(j_params, "client_mod"), NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oauth2_irl_user_add) { if (json_object_get(j_params, "user_add") == json_true()) { char * url = msprintf(SERVER_URI "/user?source=%s", json_string_value(json_object_get(json_object_get(j_params, "user_mod"), "name"))); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, json_object_get(j_params, "user"), NULL, 200, NULL, NULL, NULL), 1); o_free(url); } } END_TEST START_TEST(test_oauth2_irl_client_add) { if (json_object_get(j_params, "client_add") == json_true()) { char * url = msprintf(SERVER_URI "/client?source=%s", json_string_value(json_object_get(json_object_get(j_params, "client_mod"), "name"))); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, json_object_get(j_params, "client"), NULL, 200, NULL, NULL, NULL), 1); o_free(url); } } END_TEST START_TEST(test_oauth2_irl_run_workflow) { struct _u_request auth_req; struct _u_response auth_resp, resp; struct _u_map body; json_t * j_body, * j_register, * j_element = NULL; char * cookie; size_t index = 0; const char * username = json_string_value(json_object_get(json_object_get(j_params, "user"), "username")), * password = json_string_value(json_object_get(json_object_get(j_params, "user"), "password")), * client_id = json_string_value(json_object_get(json_object_get(j_params, "client"), "client_id")), * client_password = json_string_value(json_object_get(json_object_get(j_params, "client"), "password")); char * url, * redirect_uri_encoded, * scope = NULL, * code; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", username, "password", password); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_gt(auth_resp.nb_cookies, 0); ck_assert_ptr_ne((cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value)), NULL); ck_assert_int_eq(u_map_put(auth_req.map_header, "Cookie", cookie), U_OK); ulfius_clean_response(&auth_resp); json_array_foreach(json_object_get(j_params, "schemes"), index, j_element) { j_register = json_pack("{ss ss ss sO}", "username", username, "scheme_type", json_string_value(json_object_get(j_element, "scheme_type")), "scheme_name", json_string_value(json_object_get(j_element, "scheme_name")), "value", json_object_get(j_element, "register")); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); } json_array_foreach(json_object_get(j_params, "schemes"), index, j_element) { j_register = json_pack("{ss ss ss sO}", "username", username, "scheme_type", json_string_value(json_object_get(j_element, "scheme_type")), "scheme_name", json_string_value(json_object_get(j_element, "scheme_name")), "value", json_object_get(j_element, "value")); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/auth/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); } json_array_foreach(json_object_get(json_object_get(j_params, "user"), "scope"), index, j_element) { if (scope == NULL) { scope = o_strdup(json_string_value(j_element)); } else { scope = mstrcatf(scope, " %s", json_string_value(j_element)); } } url = msprintf("%s/auth/grant/%s", SERVER_URI, json_string_value(json_object_get(json_object_get(j_params, "client"), "client_id"))); j_body = json_pack("{ss}", "scope", scope); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", url, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); o_free(url); // Test implicit framework redirect_uri_encoded = ulfius_url_encode(json_string_value(json_array_get(json_object_get(json_object_get(j_params, "client"), "redirect_uri"), 0))); url = msprintf("%s/glwd/auth?response_type=token&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&scope=%s", SERVER_URI, json_string_value(json_object_get(json_object_get(j_params, "client"), "client_id")), redirect_uri_encoded, scope); ck_assert_int_eq(run_simple_test(&auth_req, "GET", url, client_id, client_password, NULL, NULL, 302, NULL, NULL, "token="), 1); o_free(url); // Test code framework o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_url = msprintf("%s/glwd/auth?response_type=code&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&scope=%s", SERVER_URI, json_string_value(json_object_get(json_object_get(j_params, "client"), "client_id")), redirect_uri_encoded, scope); auth_req.http_verb = o_strdup("GET"); auth_req.auth_basic_user = o_strdup(client_id); auth_req.auth_basic_password = o_strdup(client_password); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); code = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } url = msprintf("%s/glwd/token/", SERVER_URI); u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", json_string_value(json_object_get(json_object_get(j_params, "client"), "client_id"))); u_map_put(&body, "redirect_uri", json_string_value(json_array_get(json_object_get(json_object_get(j_params, "client"), "redirect_uri"), 0))); u_map_put(&body, "code", code); ck_assert_int_eq(run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "POST", url, client_id, "error", NULL, &body, 403, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "POST", url, client_id, client_password, NULL, &body, 200, NULL, "refresh_token", NULL), 1); u_map_clean(&body); ulfius_clean_response(&resp); o_free(url); // Test password framework url = msprintf("%s/glwd/token/", SERVER_URI); u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", scope); u_map_put(&body, "username", username); u_map_put(&body, "password", password); ck_assert_int_eq(run_simple_test(NULL, "POST", url, client_id, client_password, NULL, &body, 200, NULL, NULL, NULL), 1); u_map_clean(&body); o_free(code); o_free(url); o_free(redirect_uri_encoded); o_free(scope); json_array_foreach(json_object_get(j_params, "schemes"), index, j_element) { j_register = json_pack("{ss ss ss sO}", "username", username, "scheme_type", json_string_value(json_object_get(j_element, "scheme_type")), "scheme_name", json_string_value(json_object_get(j_element, "scheme_name")), "value", json_object_get(j_element, "deregister")); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); } ulfius_clean_request(&auth_req); o_free(cookie); } END_TEST START_TEST(test_oauth2_irl_user_delete) { if (json_object_get(j_params, "user_add") == json_true()) { char * url = msprintf(SERVER_URI "/user/%s?source=%s", json_string_value(json_object_get(json_object_get(j_params, "user"), "username")), json_string_value(json_object_get(json_object_get(j_params, "user_mod"), "name"))); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } } END_TEST START_TEST(test_oauth2_irl_client_delete) { if (json_object_get(j_params, "user_add") == json_true()) { char * url = msprintf(SERVER_URI "/client/%s?source=%s", json_string_value(json_object_get(json_object_get(j_params, "client"), "client_id")), json_string_value(json_object_get(json_object_get(j_params, "client_mod"), "name"))); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } } END_TEST START_TEST(test_oauth2_irl_user_module_delete) { char * url = msprintf(SERVER_URI "/mod/user/%s", json_string_value(json_object_get(json_object_get(j_params, "user_mod"), "name"))); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_oauth2_irl_client_module_delete) { char * url = msprintf(SERVER_URI "/mod/client/%s", json_string_value(json_object_get(json_object_get(j_params, "client_mod"), "name"))); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 irl"); tc_core = tcase_create("test_oauth2_irl"); tcase_add_test(tc_core, test_oauth2_irl_user_module_add); tcase_add_test(tc_core, test_oauth2_irl_client_module_add); tcase_add_test(tc_core, test_oauth2_irl_user_add); tcase_add_test(tc_core, test_oauth2_irl_client_add); tcase_add_test(tc_core, test_oauth2_irl_run_workflow); tcase_add_test(tc_core, test_oauth2_irl_user_delete); tcase_add_test(tc_core, test_oauth2_irl_client_delete); tcase_add_test(tc_core, test_oauth2_irl_user_module_delete); tcase_add_test(tc_core, test_oauth2_irl_client_module_delete); tcase_set_timeout(tc_core, 90); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); j_params = json_load_file(argv[1], JSON_DECODE_ANY, NULL); ulfius_init_request(&admin_req); if (j_params != NULL) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error reading parameters file %s", argv[1]); } json_decref(j_params); ulfius_clean_request(&admin_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oauth2_profile.c000066400000000000000000000064321415646314000213520ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile" char * bearer_token; START_TEST(test_oauth2_profile_no_token) { ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/glwd/profile", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oauth2_profile_token_invalid) { struct _u_request user_req; char * invalid_token = o_strndup(bearer_token, o_strlen(bearer_token)-2); ulfius_init_request(&user_req); u_map_put(user_req.map_header, "Authorization", invalid_token); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/glwd/profile", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); o_free(invalid_token); ulfius_clean_request(&user_req); } END_TEST START_TEST(test_oauth2_profile_ok) { struct _u_request user_req; json_t * j_body = json_pack("{ss}", "username", USERNAME); ulfius_init_request(&user_req); u_map_put(user_req.map_header, "Authorization", bearer_token); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/glwd/profile", NULL, NULL, NULL, NULL, 200, j_body, NULL, NULL), 1); json_decref(j_body); ulfius_clean_request(&user_req); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 profile"); tc_core = tcase_create("test_oauth2_profile"); tcase_add_test(tc_core, test_oauth2_profile_no_token); tcase_add_test(tc_core, test_oauth2_profile_token_invalid); tcase_add_test(tc_core, test_oauth2_profile_ok); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/glwd/token/", SERVER_URI); u_map_put(auth_req.map_post_body, "grant_type", "password"); u_map_put(auth_req.map_post_body, "username", USERNAME); u_map_put(auth_req.map_post_body, "password", PASSWORD); u_map_put(auth_req.map_post_body, "scope", SCOPE_LIST); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { json_t * json_body = ulfius_get_json_body_response(&auth_resp, NULL); bearer_token = msprintf("Bearer %s", json_string_value(json_object_get(json_body, "access_token"))); y_log_message(Y_LOG_LEVEL_INFO, "User %s authenticated", USERNAME); json_decref(json_body); } ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); o_free(bearer_token); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oauth2_profile_impersonate.c000066400000000000000000000065571415646314000237700ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "admin" #define PASSWORD "password" #define SCOPE_LIST "g_admin" #define USERNAME_IMPERSONATE "user1" struct _u_request admin_req; START_TEST(test_oauth2_get_profile_ok) { json_t * j_body = json_pack("{ss}", "username", USERNAME_IMPERSONATE); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/glwd/profile?impersonate=" USERNAME_IMPERSONATE, NULL, NULL, NULL, NULL, 200, j_body, NULL, NULL), 1); json_decref(j_body); } END_TEST START_TEST(test_oauth2_get_profile_error) { ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/glwd/profile?impersonate=error", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oauth2_get_token_ok) { ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/glwd/profile/token?impersonate=" USERNAME_IMPERSONATE, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oauth2_get_token_error) { ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/glwd/profile/token?impersonate=error", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 profile impersonate"); tc_core = tcase_create("test_oauth2_profile_impersonate"); tcase_add_test(tc_core, test_oauth2_get_profile_ok); tcase_add_test(tc_core, test_oauth2_get_profile_error); tcase_add_test(tc_core, test_oauth2_get_token_ok); tcase_add_test(tc_core, test_oauth2_get_token_error); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0, i; json_t * j_body; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile" char * bearer_token; char * refresh_token, * refresh_token_all; char user_agent[33]; START_TEST(test_oauth2_refresh_manage_endpoints_noauth) { ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/glwd/profile/token/", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "DELETE", SERVER_URI "/glwd/profile/token/test", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oauth2_refresh_manage_list) { struct _u_request user_req; struct _u_response resp; json_t * j_body = NULL; ulfius_init_request(&user_req); ulfius_init_response(&resp); u_map_put(user_req.map_header, "Authorization", bearer_token); user_req.http_url = o_strdup(SERVER_URI "/glwd/profile/token/"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/glwd/profile/token/?limit=1"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(json_array_size(j_body), 1); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/glwd/profile/token/?offset=1"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/glwd/profile/token/?sort=authorization_type"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/glwd/profile/token/?sort=issued_at&desc"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/glwd/profile/token/?sort=client_id&desc&limit=1"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(json_array_size(j_body), 1); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/glwd/profile/token/?pattern=%s", user_agent); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/glwd/profile/token/?pattern=glwd-oauth2-test-"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/glwd/profile/token/?pattern=error"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(json_array_size(j_body), 0); ulfius_clean_request(&user_req); ulfius_clean_response(&resp); json_decref(j_body); } END_TEST START_TEST(test_oauth2_refresh_manage_delete_not_found) { struct _u_request user_req; struct _u_response resp; ulfius_init_request(&user_req); ulfius_init_response(&resp); u_map_put(user_req.map_header, "Authorization", bearer_token); user_req.http_url = o_strdup(SERVER_URI "/glwd/profile/token/error"); user_req.http_verb = o_strdup("DELETE"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 404); ulfius_clean_request(&user_req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oauth2_refresh_manage_delete_ok) { char * url = msprintf("%s/glwd/token/", SERVER_URI), * token_hash, * token_hash_encoded; struct _u_map body; struct _u_request user_req; struct _u_response resp; int res; json_t * j_body; ulfius_init_request(&user_req); ulfius_init_response(&resp); u_map_put(user_req.map_header, "Authorization", bearer_token); user_req.http_url = msprintf(SERVER_URI "/glwd/profile/token/?sort=issued_at&desc&limit=1&pattern=%s", user_agent); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ck_assert_ptr_ne((token_hash = o_strdup(json_string_value(json_object_get(json_array_get(j_body, 0), "token_hash")))), NULL); ck_assert_ptr_ne((token_hash_encoded = url_encode(token_hash)), NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_init(&body); u_map_put(&body, "grant_type", "refresh_token"); u_map_put(&body, "refresh_token", refresh_token); res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 200, NULL, NULL, NULL); ck_assert_int_eq(res, 1); o_free(user_req.http_url); o_free(user_req.http_verb); u_map_put(user_req.map_header, "Authorization", bearer_token); user_req.http_url = msprintf(SERVER_URI "/glwd/profile/token/%s", token_hash_encoded); user_req.http_verb = o_strdup("DELETE"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); user_req.http_url = msprintf(SERVER_URI "/glwd/profile/token/?sort=issued_at&desc&limit=1&pattern=%s", user_agent); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ck_assert_ptr_eq(json_object_get(json_array_get(j_body, 0), "enabled"), json_false()); res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 400, NULL, NULL, NULL); free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); ulfius_clean_request(&user_req); ulfius_clean_response(&resp); o_free(token_hash); o_free(token_hash_encoded); json_decref(j_body); } END_TEST START_TEST(test_oauth2_refresh_manage_delete_all_ok) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; struct _u_request user_req; struct _u_response resp; int res; json_t * j_body; ulfius_init_request(&user_req); ulfius_init_response(&resp); u_map_put(user_req.map_header, "Authorization", bearer_token); user_req.http_url = msprintf(SERVER_URI "/glwd/profile/token/?sort=issued_at&desc&limit=1&pattern=%s", user_agent); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_init(&body); u_map_put(&body, "grant_type", "refresh_token"); u_map_put(&body, "refresh_token", refresh_token_all); res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 200, NULL, NULL, NULL); ck_assert_int_eq(res, 1); o_free(user_req.http_url); o_free(user_req.http_verb); u_map_put(user_req.map_header, "Authorization", bearer_token); user_req.http_url = msprintf(SERVER_URI "/glwd/profile/token/"); user_req.http_verb = o_strdup("DELETE"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); user_req.http_url = msprintf(SERVER_URI "/glwd/profile/token/?sort=issued_at&desc&limit=1&pattern=%s", user_agent); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ck_assert_ptr_eq(json_object_get(json_array_get(j_body, 0), "enabled"), json_false()); res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 400, NULL, NULL, NULL); free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); ulfius_clean_request(&user_req); ulfius_clean_response(&resp); json_decref(j_body); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 refresh manage"); tc_core = tcase_create("test_oauth2_refresh_manage"); tcase_add_test(tc_core, test_oauth2_refresh_manage_endpoints_noauth); tcase_add_test(tc_core, test_oauth2_refresh_manage_list); tcase_add_test(tc_core, test_oauth2_refresh_manage_delete_not_found); tcase_add_test(tc_core, test_oauth2_refresh_manage_delete_ok); tcase_add_test(tc_core, test_oauth2_refresh_manage_delete_all_ok); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0, number_failed = 1, x[1]; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/glwd/token/", SERVER_URI); gnutls_rnd(GNUTLS_RND_NONCE, x, sizeof(int)); snprintf(user_agent, 32, "glwd-oauth2-test-%d", x[0]); u_map_put(auth_req.map_header, "User-Agent", user_agent); u_map_put(auth_req.map_post_body, "grant_type", "password"); u_map_put(auth_req.map_post_body, "username", USERNAME); u_map_put(auth_req.map_post_body, "password", PASSWORD); u_map_put(auth_req.map_post_body, "scope", SCOPE_LIST); ulfius_init_response(&auth_resp); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { json_t * json_body = ulfius_get_json_body_response(&auth_resp, NULL); bearer_token = msprintf("Bearer %s", json_string_value(json_object_get(json_body, "access_token"))); refresh_token = o_strdup(json_string_value(json_object_get(json_body, "refresh_token"))); y_log_message(Y_LOG_LEVEL_INFO, "User %s authenticated", USERNAME); json_decref(json_body); do_test = 1; } ulfius_clean_response(&auth_resp); ulfius_init_response(&auth_resp); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { json_t * json_body = ulfius_get_json_body_response(&auth_resp, NULL); refresh_token_all = o_strdup(json_string_value(json_object_get(json_body, "refresh_token"))); y_log_message(Y_LOG_LEVEL_INFO, "User %s authenticated", USERNAME); json_decref(json_body); do_test = 1; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } o_free(bearer_token); o_free(refresh_token); o_free(refresh_token_all); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oauth2_refresh_manage_session.c000066400000000000000000000256271415646314000244320ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile" char * bearer_token; char * refresh_token; char user_agent[33]; struct _u_request user_req; START_TEST(test_oauth2_refresh_manage_session_endpoints_noauth) { ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/glwd/profile/token/", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "DELETE", SERVER_URI "/glwd/profile/token/test", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oauth2_refresh_manage_session_list) { struct _u_response resp; json_t * j_body = NULL; ulfius_init_response(&resp); u_map_put(user_req.map_header, "Authorization", bearer_token); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/glwd/profile/token/"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/glwd/profile/token/?limit=1"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(json_array_size(j_body), 1); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/glwd/profile/token/?offset=1"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/glwd/profile/token/?sort=authorization_type"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/glwd/profile/token/?sort=issued_at&desc"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/glwd/profile/token/?sort=client_id&desc&limit=1"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(json_array_size(j_body), 1); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/glwd/profile/token/?pattern=%s", user_agent); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/glwd/profile/token/?pattern=glwd-oauth2-test-"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/glwd/profile/token/?pattern=error"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(json_array_size(j_body), 0); ulfius_clean_response(&resp); json_decref(j_body); } END_TEST START_TEST(test_oauth2_refresh_manage_session_delete_not_found) { struct _u_response resp; ulfius_init_response(&resp); o_free(user_req.http_url); o_free(user_req.http_verb); user_req.http_url = o_strdup(SERVER_URI "/glwd/profile/token/error"); user_req.http_verb = o_strdup("DELETE"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 404); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oauth2_refresh_manage_session_delete_ok) { char * url = SERVER_URI "/glwd/token/", * token_hash, * token_hash_encoded; struct _u_map body; struct _u_response resp; int res; json_t * j_body; ulfius_init_response(&resp); o_free(user_req.http_url); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); user_req.http_url = msprintf(SERVER_URI "/glwd/profile/token/?sort=issued_at&desc&limit=1&pattern=%s", user_agent); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ck_assert_ptr_ne((token_hash = o_strdup(json_string_value(json_object_get(json_array_get(j_body, 0), "token_hash")))), NULL); ck_assert_ptr_ne((token_hash_encoded = url_encode(token_hash)), NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_init(&body); u_map_put(&body, "grant_type", "refresh_token"); u_map_put(&body, "refresh_token", refresh_token); res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 200, NULL, NULL, NULL); ck_assert_int_eq(res, 1); o_free(user_req.http_url); o_free(user_req.http_verb); user_req.http_url = msprintf(SERVER_URI "/glwd/profile/token/%s", token_hash_encoded); user_req.http_verb = o_strdup("DELETE"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); user_req.http_url = msprintf(SERVER_URI "/glwd/profile/token/?sort=issued_at&desc&limit=1&pattern=%s", user_agent); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ck_assert_ptr_eq(json_object_get(json_array_get(j_body, 0), "enabled"), json_false()); res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 400, NULL, NULL, NULL); u_map_clean(&body); ck_assert_int_eq(res, 1); ulfius_clean_response(&resp); o_free(token_hash); o_free(token_hash_encoded); json_decref(j_body); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 refresh manage session"); tc_core = tcase_create("test_oauth2_refresh_manage_session"); tcase_add_test(tc_core, test_oauth2_refresh_manage_session_endpoints_noauth); tcase_add_test(tc_core, test_oauth2_refresh_manage_session_list); tcase_add_test(tc_core, test_oauth2_refresh_manage_session_delete_not_found); tcase_add_test(tc_core, test_oauth2_refresh_manage_session_delete_ok); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, i, do_test = 0, number_failed = 1, x[1]; json_t * j_body; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/glwd/token/", SERVER_URI); gnutls_rnd(GNUTLS_RND_NONCE, x, sizeof(int)); snprintf(user_agent, 32, "glwd-oauth2-test-%d", x[0]); u_map_put(auth_req.map_header, "User-Agent", user_agent); u_map_put(auth_req.map_post_body, "grant_type", "password"); u_map_put(auth_req.map_post_body, "username", USERNAME); u_map_put(auth_req.map_post_body, "password", PASSWORD); u_map_put(auth_req.map_post_body, "scope", SCOPE_LIST); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { json_t * json_body = ulfius_get_json_body_response(&auth_resp, NULL); bearer_token = msprintf("Bearer %s", json_string_value(json_object_get(json_body, "access_token"))); refresh_token = o_strdup(json_string_value(json_object_get(json_body, "refresh_token"))); y_log_message(Y_LOG_LEVEL_INFO, "User %s authenticated", USERNAME); json_decref(json_body); } ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile scope3" char * refresh_token; START_TEST(test_oauth2_refresh_token_token_invalid) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "refresh_token"); u_map_put(&body, "refresh_token", "invalid"); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 400, NULL, NULL, NULL); free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_refresh_token_ok) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "refresh_token"); u_map_put(&body, "refresh_token", refresh_token); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 200, NULL, NULL, NULL); free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 refresh token"); tc_core = tcase_create("test_oauth2_refresh_token"); tcase_add_test(tc_core, test_oauth2_refresh_token_token_invalid); tcase_add_test(tc_core, test_oauth2_refresh_token_ok); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/glwd/token/", SERVER_URI); u_map_put(auth_req.map_post_body, "grant_type", "password"); u_map_put(auth_req.map_post_body, "username", USERNAME); u_map_put(auth_req.map_post_body, "password", PASSWORD); u_map_put(auth_req.map_post_body, "scope", SCOPE_LIST); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { json_t * json_body = ulfius_get_json_body_response(&auth_resp, NULL); refresh_token = o_strdup(json_string_value(json_object_get(json_body, "refresh_token"))); y_log_message(Y_LOG_LEVEL_INFO, "User %s authenticated", USERNAME); json_decref(json_body); } ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); o_free(refresh_token); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oauth2_refresh_token_client_confidential.c000066400000000000000000000103111415646314000266140ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile scope3" #define CLIENT "client3_id" #define CLIENT_PASSWORD "password" char * refresh_token; START_TEST(test_oauth2_refresh_token_token_invalid) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "refresh_token"); u_map_put(&body, "refresh_token", "invalid"); int res = run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_refresh_token_client_invalid) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "refresh_token"); u_map_put(&body, "refresh_token", refresh_token); int res = run_simple_test(NULL, "POST", url, CLIENT, "invalid", NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_refresh_token_no_client) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "refresh_token"); u_map_put(&body, "refresh_token", refresh_token); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_refresh_token_ok) { char * url = msprintf("%s/glwd/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "refresh_token"); u_map_put(&body, "refresh_token", refresh_token); int res = run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 200, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 refresh token client confidential"); tc_core = tcase_create("test_oauth2_refresh_token"); tcase_add_test(tc_core, test_oauth2_refresh_token_token_invalid); tcase_add_test(tc_core, test_oauth2_refresh_token_client_invalid); tcase_add_test(tc_core, test_oauth2_refresh_token_no_client); tcase_add_test(tc_core, test_oauth2_refresh_token_ok); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/glwd/token/", SERVER_URI); u_map_put(auth_req.map_post_body, "grant_type", "password"); u_map_put(auth_req.map_post_body, "username", USERNAME); u_map_put(auth_req.map_post_body, "password", PASSWORD); u_map_put(auth_req.map_post_body, "scope", SCOPE_LIST); auth_req.auth_basic_user = strdup(CLIENT); auth_req.auth_basic_password = strdup(CLIENT_PASSWORD); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { json_t * json_body = ulfius_get_json_body_response(&auth_resp, NULL); refresh_token = o_strdup(json_string_value(json_object_get(json_body, "refresh_token"))); y_log_message(Y_LOG_LEVEL_INFO, "User %s authenticated", USERNAME); json_decref(json_body); } ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); o_free(refresh_token); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oauth2_resource_owner_pwd_cred.c000066400000000000000000000074211415646314000246210ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api/glwd" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile scope3" char * code; START_TEST(test_oauth2_resource_owner_pwd_cred_valid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", SCOPE_LIST); u_map_put(&body, "username", USERNAME); u_map_put(&body, "password", PASSWORD); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 200, NULL, "refresh_token", NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_resource_owner_pwd_cred_pwd_invalid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", SCOPE_LIST); u_map_put(&body, "username", USERNAME); u_map_put(&body, "password", "invalid"); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_resource_owner_pwd_cred_user_invalid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", SCOPE_LIST); u_map_put(&body, "username", "invalid"); u_map_put(&body, "password", PASSWORD); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_resource_owner_pwd_cred_scope_invalid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", "invalid"); u_map_put(&body, "username", USERNAME); u_map_put(&body, "password", PASSWORD); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_resource_owner_pwd_cred_empty) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 resource owner password credential"); tc_core = tcase_create("test_oauth2_resource_owner_pwd_cred"); tcase_add_test(tc_core, test_oauth2_resource_owner_pwd_cred_valid); tcase_add_test(tc_core, test_oauth2_resource_owner_pwd_cred_pwd_invalid); tcase_add_test(tc_core, test_oauth2_resource_owner_pwd_cred_user_invalid); tcase_add_test(tc_core, test_oauth2_resource_owner_pwd_cred_scope_invalid); tcase_add_test(tc_core, test_oauth2_resource_owner_pwd_cred_empty); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed; Suite *s; SRunner *sr; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oauth2_resource_owner_pwd_cred_client_confidential.c000066400000000000000000000107621415646314000307000ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api/glwd" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile scope3" #define CLIENT "client3_id" #define CLIENT_PASSWORD "password" char * code; START_TEST(test_oauth2_resource_owner_pwd_cred_valid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", SCOPE_LIST); u_map_put(&body, "username", USERNAME); u_map_put(&body, "password", PASSWORD); int res = run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 200, NULL, "refresh_token", NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_resource_owner_pwd_cred_pwd_invalid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", SCOPE_LIST); u_map_put(&body, "username", USERNAME); u_map_put(&body, "password", "invalid"); int res = run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_resource_owner_pwd_cred_user_invalid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", SCOPE_LIST); u_map_put(&body, "username", "invalid"); u_map_put(&body, "password", PASSWORD); int res = run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_resource_owner_pwd_cred_client_invalid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", SCOPE_LIST); u_map_put(&body, "username", USERNAME); u_map_put(&body, "password", PASSWORD); int res = run_simple_test(NULL, "POST", url, CLIENT, "invalid", NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_resource_owner_pwd_cred_scope_invalid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", "invalid"); u_map_put(&body, "username", USERNAME); u_map_put(&body, "password", PASSWORD); int res = run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oauth2_resource_owner_pwd_cred_empty) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); int res = run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 resource owner password credential client confidential"); tc_core = tcase_create("test_oauth2_resource_owner_pwd_cred"); tcase_add_test(tc_core, test_oauth2_resource_owner_pwd_cred_valid); tcase_add_test(tc_core, test_oauth2_resource_owner_pwd_cred_pwd_invalid); tcase_add_test(tc_core, test_oauth2_resource_owner_pwd_cred_user_invalid); tcase_add_test(tc_core, test_oauth2_resource_owner_pwd_cred_client_invalid); tcase_add_test(tc_core, test_oauth2_resource_owner_pwd_cred_scope_invalid); tcase_add_test(tc_core, test_oauth2_resource_owner_pwd_cred_empty); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed; Suite *s; SRunner *sr; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oauth2_scheme_required.c000066400000000000000000000217361415646314000230620ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "new_user" #define USER_PASSWORD "password" #define CLIENT "client1_id" #define REDIRECT_URI "..%2f..%2ftest-oauth2.html?param=client1_cb1" #define RESPONSE_TYPE_CODE "code" #define RESPONSE_TYPE_TOKEN "token" #define SCOPE "scheme_required_scope" #define NAME "New Scope" #define DESCRIPTION "Description for test-scope" #define GROUP "group" #define SCHEME1 "mock_scheme_42" #define SCHEME1_VALUE "42" #define SCHEME2 "mock_scheme_88" #define SCHEME2_VALUE "88" #define SCHEME3 "mock_scheme_95" #define SCHEME3_VALUE "95" struct _u_request admin_req; START_TEST(test_oauth2_scheme_required_scope_set) { json_t * j_parameters = json_pack("{ss ss ss so s{s[{ssss}{ssss}{ssss}]}s{si}}", "name", SCOPE, "display_name", NAME, "description", DESCRIPTION, "password_required", json_false(), "scheme", GROUP, "scheme_name", SCHEME1, "scheme_type", "mock", "scheme_name", SCHEME2, "scheme_type", "mock", "scheme_name", SCHEME3, "scheme_type", "mock", "scheme_required", GROUP, 2); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/scope/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssos[sss]ss}", "username", USER_USERNAME, "enabled", json_true(), "scope", SCOPE, "openid", "g_profile", "password", USER_PASSWORD); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/user/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oauth2_scheme_required_auth_flow) { struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); o_free(cookie); j_body = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", SCHEME1, "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", SCHEME2, "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", SCHEME3, "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ss}", "scope", SCOPE " openid"); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", SERVER_URI "/auth/grant/" CLIENT, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE_CODE "&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&nonce=abcdxyz&scope=" SCOPE " openid&g_continue", NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE_TOKEN "&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&nonce=abcdxyz&scope=" SCOPE " openid&g_continue", NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); // Authenticate scheme mock 42 j_body = json_pack("{sssssss{ss}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", SCHEME1, "value", "code", SCHEME1_VALUE); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/auth/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE_CODE "&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&nonce=abcdxyz&scope=" SCOPE " openid&g_continue", NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE_TOKEN "&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&nonce=abcdxyz&scope=" SCOPE " openid&g_continue", NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); // Authenticate scheme mock 95 j_body = json_pack("{sssssss{ss}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", SCHEME3, "value", "code", SCHEME3_VALUE); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/auth/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE_CODE "&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&nonce=abcdxyz&scope=" SCOPE " openid&g_continue", NULL, NULL, NULL, NULL, 302, NULL, NULL, "code="), 1); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE_TOKEN "&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&nonce=abcdxyz&scope=" SCOPE " openid&g_continue", NULL, NULL, NULL, NULL, 302, NULL, NULL, "access_token="), 1); j_body = json_pack("{ss}", "scope", ""); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", SERVER_URI "/auth/grant/" CLIENT, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); } END_TEST START_TEST(test_oauth2_scheme_required_scope_delete) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/user/" USER_USERNAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/scope/" SCOPE, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 scheme required"); tc_core = tcase_create("test_oauth2_scheme_required"); tcase_add_test(tc_core, test_oauth2_scheme_required_scope_set); tcase_add_test(tc_core, test_oauth2_scheme_required_auth_flow); tcase_add_test(tc_core, test_oauth2_scheme_required_scope_delete); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } ulfius_clean_request(&admin_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oauth2_token_introspection.c000066400000000000000000001010171415646314000240050ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile" #define SCOPE_INTROSPECT "g_admin" #define CLIENT_CONFIDENTIAL_1 "client3_id" #define CLIENT_CONFIDENTIAL_1_SECRET "password" #define SCOPE_LIST_CLIENT_CONFIDENTIAL_1 "scope2 scope3" #define CLIENT_CONFIDENTIAL_2 "client4_id" #define CLIENT_CONFIDENTIAL_2_SECRET "secret" #define CLIENT_PUBLIC "client1_id" #define REDIRECT_URI "..%2f..%2ftest-oauth2.html?param=client1_cb1" #define REDIRECT_URI_DECODED "../../test-oauth2.html?param=client1_cb1" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define PLUGIN_MODULE "oauth2-glewlwyd" #define PLUGIN_NAME "introspect" #define PLUGIN_DISPLAY_NAME "Introspection test" #define PLUGIN_JWT_TYPE "sha" #define PLUGIN_JWT_KEY_SIZE "256" #define PLUGIN_KEY "secret" #define PLUGIN_CODE_DURATION 600 #define PLUGIN_REFRESH_TOKEN_DURATION 1209600 #define PLUGIN_ACCESS_TOKEN_DURATION 3600 #define TOKEN_TYPE_HINT_REFRESH "refresh_token" #define TOKEN_TYPE_HINT_ACCESS "access_token" struct _u_request admin_req; START_TEST(test_oauth2_introspection_plugin_add_target_client) { json_t * j_parameters = json_pack("{sssssssos{sssssssisisisososososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "introspection-revocation-allowed", json_true(), "introspection-revocation-allow-target-client", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oauth2_introspection_plugin_add_auth_scope) { json_t * j_parameters = json_pack("{sssssssos{sssssssisisisosososososos[s]}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "introspection-revocation-allowed", json_true(), "introspection-revocation-auth-scope", SCOPE_INTROSPECT); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oauth2_introspection_plugin_add_target_client_check_expiration) { json_t * j_parameters = json_pack("{sssssssos{sssssssisisisososososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", 1, "access-token-duration", 1, "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "introspection-revocation-allowed", json_true(), "introspection-revocation-allow-target-client", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oauth2_introspection_plugin_remove) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oauth2_introspection_invalid_format_target_client) { struct _u_request req; struct _u_response resp; json_t * j_body; const char * access_token; struct _u_map param; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); access_token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(access_token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "error", access_token), U_OK); ck_assert_int_eq(u_map_put(¶m, "error_hint", "error"), U_OK); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, NULL, 400, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, "error", NULL, NULL, 401, NULL, NULL, NULL), 1); u_map_clean(¶m); json_decref(j_body); } END_TEST START_TEST(test_oauth2_introspection_access_token_target_client) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response; const char * token; struct _u_map param; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); j_response = json_pack("{sossssssss}", "active", json_true(), "username", USERNAME, "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_ACCESS, "scope", SCOPE_LIST); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_ACCESS), U_OK); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, j_response, NULL, NULL), 1); u_map_remove_from_key(¶m, "token_type_hint"); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_2, CLIENT_CONFIDENTIAL_2_SECRET, NULL, ¶m, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(u_map_put(¶m, "token", "error"), U_OK); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); } END_TEST START_TEST(test_oauth2_introspection_refresh_token_target_client) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response; const char * token; struct _u_map param; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "refresh_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); j_response = json_pack("{sossssssss}", "active", json_true(), "username", USERNAME, "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_REFRESH, "scope", SCOPE_LIST); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_REFRESH), U_OK); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, j_response, NULL, NULL), 1); u_map_remove_from_key(¶m, "token_type_hint"); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_2, CLIENT_CONFIDENTIAL_2_SECRET, NULL, ¶m, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(u_map_put(¶m, "token", "error"), U_OK); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); } END_TEST START_TEST(test_oauth2_introspection_invalid_format_bearer) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_body_introspect; const char * access_token, * token_auth; struct _u_map param; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); access_token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(access_token, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_put(req.map_post_body, "scope", SCOPE_INTROSPECT); u_map_put(req.map_post_body, "username", ADMIN_USERNAME); u_map_put(req.map_post_body, "password", ADMIN_PASSWORD); o_free(req.auth_basic_user); req.auth_basic_user = NULL; o_free(req.auth_basic_password); req.auth_basic_password = NULL; ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body_introspect = ulfius_get_json_body_response(&resp, NULL); token_auth = json_string_value(json_object_get(j_body_introspect, "access_token")); ck_assert_ptr_ne(token_auth, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "error", access_token), U_OK); ck_assert_int_eq(u_map_put(¶m, "error_hint", "error"), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); tmp = msprintf("Bearer %s", "error"); u_map_put(req.map_header, "Authorization", tmp); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); o_free(tmp); ulfius_clean_request(&req); u_map_clean(¶m); json_decref(j_body); json_decref(j_body_introspect); ulfius_clean_request(&req); } END_TEST START_TEST(test_oauth2_introspection_access_token_target_bearer) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response, * j_body_introspect; const char * token, * token_auth; struct _u_map param; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_put(req.map_post_body, "scope", SCOPE_INTROSPECT); u_map_put(req.map_post_body, "username", ADMIN_USERNAME); u_map_put(req.map_post_body, "password", ADMIN_PASSWORD); o_free(req.auth_basic_user); req.auth_basic_user = NULL; o_free(req.auth_basic_password); req.auth_basic_password = NULL; ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body_introspect = ulfius_get_json_body_response(&resp, NULL); token_auth = json_string_value(json_object_get(j_body_introspect, "access_token")); ck_assert_ptr_ne(token_auth, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); j_response = json_pack("{sossssssss}", "active", json_true(), "username", USERNAME, "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_ACCESS, "scope", SCOPE_LIST); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_ACCESS), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); u_map_remove_from_key(¶m, "token_type_hint"); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(u_map_put(¶m, "token", "error"), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); json_decref(j_body_introspect); ulfius_clean_request(&req); } END_TEST START_TEST(test_oauth2_introspection_access_token_target_bearer_no_client_id) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response, * j_body_introspect; const char * token, * token_auth; struct _u_map param; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_put(req.map_post_body, "scope", SCOPE_INTROSPECT); u_map_put(req.map_post_body, "username", ADMIN_USERNAME); u_map_put(req.map_post_body, "password", ADMIN_PASSWORD); o_free(req.auth_basic_user); req.auth_basic_user = NULL; o_free(req.auth_basic_password); req.auth_basic_password = NULL; ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body_introspect = ulfius_get_json_body_response(&resp, NULL); token_auth = json_string_value(json_object_get(j_body_introspect, "access_token")); ck_assert_ptr_ne(token_auth, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); j_response = json_pack("{sossssss}", "active", json_true(), "username", USERNAME, "token_type", TOKEN_TYPE_HINT_ACCESS, "scope", SCOPE_LIST); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_ACCESS), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); u_map_remove_from_key(¶m, "token_type_hint"); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(u_map_put(¶m, "token", "error"), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); json_decref(j_body_introspect); ulfius_clean_request(&req); } END_TEST START_TEST(test_oauth2_introspection_access_token_target_bearer_client_credentials) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response, * j_body_introspect; const char * token, * token_auth; struct _u_map param; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "client_credentials"); u_map_put(req.map_post_body, "scope", SCOPE_LIST_CLIENT_CONFIDENTIAL_1); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_INTROSPECT); u_map_put(req.map_post_body, "username", ADMIN_USERNAME); u_map_put(req.map_post_body, "password", ADMIN_PASSWORD); o_free(req.auth_basic_user); req.auth_basic_user = NULL; o_free(req.auth_basic_password); req.auth_basic_password = NULL; ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body_introspect = ulfius_get_json_body_response(&resp, NULL); token_auth = json_string_value(json_object_get(j_body_introspect, "access_token")); ck_assert_ptr_ne(token_auth, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); j_response = json_pack("{sossssss}", "active", json_true(), "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_ACCESS, "scope", SCOPE_LIST_CLIENT_CONFIDENTIAL_1); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_ACCESS), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); u_map_remove_from_key(¶m, "token_type_hint"); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(u_map_put(¶m, "token", "error"), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); json_decref(j_body_introspect); ulfius_clean_request(&req); } END_TEST START_TEST(test_oauth2_introspection_refresh_token_target_bearer) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response, * j_body_introspect; const char * token, * token_auth; struct _u_map param; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "refresh_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_put(req.map_post_body, "scope", SCOPE_INTROSPECT); u_map_put(req.map_post_body, "username", ADMIN_USERNAME); u_map_put(req.map_post_body, "password", ADMIN_PASSWORD); o_free(req.auth_basic_user); req.auth_basic_user = NULL; o_free(req.auth_basic_password); req.auth_basic_password = NULL; ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body_introspect = ulfius_get_json_body_response(&resp, NULL); token_auth = json_string_value(json_object_get(j_body_introspect, "access_token")); ck_assert_ptr_ne(token_auth, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); j_response = json_pack("{sossssssss}", "active", json_true(), "username", USERNAME, "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_REFRESH, "scope", SCOPE_LIST); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_REFRESH), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); u_map_remove_from_key(¶m, "token_type_hint"); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(u_map_put(¶m, "token", "error"), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); json_decref(j_body_introspect); ulfius_clean_request(&req); } END_TEST START_TEST(test_oauth2_introspection_token_target_client_check_expiration) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response; const char * access_token, * refresh_token; struct _u_map param; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); access_token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(access_token, NULL); refresh_token = json_string_value(json_object_get(j_body, "refresh_token")); ck_assert_ptr_ne(refresh_token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ck_assert_int_eq(u_map_init(¶m), U_OK); sleep(2); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(u_map_put(¶m, "token", access_token), U_OK); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, j_response, NULL, NULL), 1); ck_assert_int_eq(u_map_put(¶m, "token", refresh_token), U_OK); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 token introspcetion"); tc_core = tcase_create("test_oauth2_token_introspection"); tcase_add_test(tc_core, test_oauth2_introspection_plugin_add_target_client); tcase_add_test(tc_core, test_oauth2_introspection_invalid_format_target_client); tcase_add_test(tc_core, test_oauth2_introspection_access_token_target_client); tcase_add_test(tc_core, test_oauth2_introspection_refresh_token_target_client); tcase_add_test(tc_core, test_oauth2_introspection_plugin_remove); tcase_add_test(tc_core, test_oauth2_introspection_plugin_add_auth_scope); tcase_add_test(tc_core, test_oauth2_introspection_invalid_format_bearer); tcase_add_test(tc_core, test_oauth2_introspection_access_token_target_bearer); tcase_add_test(tc_core, test_oauth2_introspection_refresh_token_target_bearer); tcase_add_test(tc_core, test_oauth2_introspection_access_token_target_bearer_no_client_id); tcase_add_test(tc_core, test_oauth2_introspection_access_token_target_bearer_client_credentials); tcase_add_test(tc_core, test_oauth2_introspection_plugin_remove); tcase_add_test(tc_core, test_oauth2_introspection_plugin_add_target_client_check_expiration); tcase_add_test(tc_core, test_oauth2_introspection_token_target_client_check_expiration); tcase_add_test(tc_core, test_oauth2_introspection_plugin_remove); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&admin_req); ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); j_body = NULL; res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile" #define SCOPE_INTROSPECT "g_admin" #define CLIENT_CONFIDENTIAL_1 "client3_id" #define CLIENT_CONFIDENTIAL_1_SECRET "password" #define CLIENT_CONFIDENTIAL_2 "client4_id" #define CLIENT_CONFIDENTIAL_2_SECRET "secret" #define CLIENT_PUBLIC "client1_id" #define REDIRECT_URI "..%2f..%2ftest-oauth2.html?param=client1_cb1" #define REDIRECT_URI_DECODED "../../test-oauth2.html?param=client1_cb1" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define PLUGIN_MODULE "oauth2-glewlwyd" #define PLUGIN_NAME "introspect" #define PLUGIN_DISPLAY_NAME "Introspection test" #define PLUGIN_JWT_TYPE "sha" #define PLUGIN_JWT_KEY_SIZE "256" #define PLUGIN_KEY "secret" #define PLUGIN_CODE_DURATION 600 #define PLUGIN_REFRESH_TOKEN_DURATION 1209600 #define PLUGIN_ACCESS_TOKEN_DURATION 3600 #define TOKEN_TYPE_HINT_REFRESH "refresh_token" #define TOKEN_TYPE_HINT_ACCESS "access_token" struct _u_request admin_req; START_TEST(test_oauth2_revocation_plugin_add_target_client) { json_t * j_parameters = json_pack("{sssssssos{sssssssisisisososososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "introspection-revocation-allowed", json_true(), "introspection-revocation-allow-target-client", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oauth2_revocation_plugin_add_auth_scope) { json_t * j_parameters = json_pack("{sssssssos{sssssssisisisosososososos[s]}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "introspection-revocation-allowed", json_true(), "introspection-revocation-auth-scope", SCOPE_INTROSPECT); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oauth2_revocation_plugin_remove) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oauth2_revocation_invalid_format_target_client) { struct _u_request req; struct _u_response resp; json_t * j_body; const char * access_token; struct _u_map param; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); access_token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(access_token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "error", access_token), U_OK); ck_assert_int_eq(u_map_put(¶m, "error_hint", "error"), U_OK); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/revoke", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, NULL, 400, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/revoke", CLIENT_CONFIDENTIAL_1, "error", NULL, NULL, 401, NULL, NULL, NULL), 1); u_map_clean(¶m); json_decref(j_body); } END_TEST START_TEST(test_oauth2_revocation_access_token_target_client) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response; const char * token; struct _u_map param; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); j_response = json_pack("{sossssssss}", "active", json_true(), "username", USERNAME, "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_ACCESS, "scope", SCOPE_LIST); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_ACCESS), U_OK); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, j_response, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/revoke", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, NULL, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(u_map_put(¶m, "token", "error"), U_OK); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); } END_TEST START_TEST(test_oauth2_revocation_refresh_token_target_client) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response; const char * token; struct _u_map param; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "refresh_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); j_response = json_pack("{sossssssss}", "active", json_true(), "username", USERNAME, "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_REFRESH, "scope", SCOPE_LIST); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_REFRESH), U_OK); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, j_response, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/revoke", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, NULL, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(u_map_put(¶m, "token", "error"), U_OK); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); } END_TEST START_TEST(test_oauth2_revocation_invalid_format_bearer) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_body_introspect; const char * access_token, * token_auth; struct _u_map param; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); access_token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(access_token, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_put(req.map_post_body, "scope", SCOPE_INTROSPECT); u_map_put(req.map_post_body, "username", ADMIN_USERNAME); u_map_put(req.map_post_body, "password", ADMIN_PASSWORD); o_free(req.auth_basic_user); req.auth_basic_user = NULL; o_free(req.auth_basic_password); req.auth_basic_password = NULL; ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body_introspect = ulfius_get_json_body_response(&resp, NULL); token_auth = json_string_value(json_object_get(j_body_introspect, "access_token")); ck_assert_ptr_ne(token_auth, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "error", access_token), U_OK); ck_assert_int_eq(u_map_put(¶m, "error_hint", "error"), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/revoke", NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); tmp = msprintf("Bearer %s", "error"); u_map_put(req.map_header, "Authorization", tmp); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/revoke", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); o_free(tmp); ulfius_clean_request(&req); u_map_clean(¶m); json_decref(j_body); json_decref(j_body_introspect); ulfius_clean_request(&req); } END_TEST START_TEST(test_oauth2_revocation_access_token_target_bearer) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response, * j_body_introspect; const char * token, * token_auth; struct _u_map param; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_put(req.map_post_body, "scope", SCOPE_INTROSPECT); u_map_put(req.map_post_body, "username", ADMIN_USERNAME); u_map_put(req.map_post_body, "password", ADMIN_PASSWORD); o_free(req.auth_basic_user); req.auth_basic_user = NULL; o_free(req.auth_basic_password); req.auth_basic_password = NULL; ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body_introspect = ulfius_get_json_body_response(&resp, NULL); token_auth = json_string_value(json_object_get(j_body_introspect, "access_token")); ck_assert_ptr_ne(token_auth, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); j_response = json_pack("{sossssssss}", "active", json_true(), "username", USERNAME, "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_ACCESS, "scope", SCOPE_LIST); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_ACCESS), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); u_map_remove_from_key(¶m, "token_type_hint"); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/revoke", NULL, NULL, NULL, ¶m, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/" PLUGIN_NAME "/profile", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); tmp = msprintf("Bearer %s", token); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/" PLUGIN_NAME "/profile", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_true()); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); ck_assert_int_eq(u_map_put(¶m, "token", token_auth), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/revoke", NULL, NULL, NULL, ¶m, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 401, NULL, NULL, NULL), 1); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); json_decref(j_body_introspect); ulfius_clean_request(&req); } END_TEST START_TEST(test_oauth2_revocation_refresh_token_target_bearer) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response, * j_body_introspect; const char * token, * token_auth; struct _u_map param; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "refresh_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_put(req.map_post_body, "scope", SCOPE_INTROSPECT); u_map_put(req.map_post_body, "username", ADMIN_USERNAME); u_map_put(req.map_post_body, "password", ADMIN_PASSWORD); o_free(req.auth_basic_user); req.auth_basic_user = NULL; o_free(req.auth_basic_password); req.auth_basic_password = NULL; ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body_introspect = ulfius_get_json_body_response(&resp, NULL); token_auth = json_string_value(json_object_get(j_body_introspect, "access_token")); ck_assert_ptr_ne(token_auth, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); j_response = json_pack("{sossssssss}", "active", json_true(), "username", USERNAME, "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_REFRESH, "scope", SCOPE_LIST); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_REFRESH), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); u_map_remove_from_key(¶m, "token_type_hint"); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/revoke", NULL, NULL, NULL, ¶m, 200, NULL, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); json_decref(j_body_introspect); ulfius_clean_request(&req); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oauth2 token revocation"); tc_core = tcase_create("test_oauth2_token_revocation"); tcase_add_test(tc_core, test_oauth2_revocation_plugin_add_target_client); tcase_add_test(tc_core, test_oauth2_revocation_invalid_format_target_client); tcase_add_test(tc_core, test_oauth2_revocation_access_token_target_client); tcase_add_test(tc_core, test_oauth2_revocation_refresh_token_target_client); tcase_add_test(tc_core, test_oauth2_revocation_plugin_remove); tcase_add_test(tc_core, test_oauth2_revocation_plugin_add_auth_scope); tcase_add_test(tc_core, test_oauth2_revocation_invalid_format_bearer); tcase_add_test(tc_core, test_oauth2_revocation_access_token_target_bearer); tcase_add_test(tc_core, test_oauth2_revocation_refresh_token_target_bearer); tcase_add_test(tc_core, test_oauth2_revocation_plugin_remove); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&admin_req); ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); j_body = NULL; res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "user1" #define USER_PASSWORD "password" #define PLUGIN "oidc" #define PLUGIN_NAME "oidc_claims" #define SCOPE_LIST "g_profile openid" #define CLIENT "client1_id" #define RESPONSE_TYPE "id_token token" struct _u_request admin_req; struct _u_request user_req; START_TEST(test_oidc_additional_parameters_add_plugin) { json_t * j_param = json_pack("{sssssss{sssssssssisisisosososososososos[{ssss}{ssss}{ssss}{ssss}]}}", "module", "oidc", "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", "https://glewlwyd.tld", "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_NAME, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "additional-parameters", "user-parameter", "claim-str", "token-parameter", "claim-str", "user-parameter", "claim-number", "token-parameter", "claim-number", "user-parameter", "claim-bool", "token-parameter", "claim-bool", "user-parameter", "claim-mandatory", "token-parameter", "claim-mandatory"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); j_param = json_pack("{ssssssss}", "claim-str", "the-str", "claim-number", "42", "claim-bool", "1", "claim-mandatory", "I'M aliiiiiive!"); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USER_USERNAME, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_additional_parameters) { struct _u_response resp; struct _u_request req; char * access_token, ** access_token_split = NULL, * str_payload = NULL; json_t * j_payload; size_t str_payload_len = 0; ulfius_init_response(&resp); ulfius_init_request(&req); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); access_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token=")); if (o_strchr(access_token, '&')) { *(o_strchr(access_token, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(split_string(access_token, ".", &access_token_split), 3); ck_assert_int_eq(o_base64url_decode((unsigned char *)access_token_split[1], o_strlen(access_token_split[1]), NULL, &str_payload_len), 1); ck_assert_ptr_ne((str_payload = o_malloc(str_payload_len + 2)), NULL); ck_assert_int_eq(o_base64url_decode((unsigned char *)access_token_split[1], o_strlen(access_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_payload = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_int_eq(json_object_size(j_payload), 14); ck_assert_ptr_ne(json_string_value(json_object_get(j_payload, "sub")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_payload, "scope")), SCOPE_LIST); ck_assert_str_eq(json_string_value(json_object_get(j_payload, "claim-bool")), "1"); ck_assert_str_eq(json_string_value(json_object_get(j_payload, "claim-mandatory")), "I'M aliiiiiive!"); ck_assert_str_eq(json_string_value(json_object_get(j_payload, "claim-number")), "42"); ck_assert_str_eq(json_string_value(json_object_get(j_payload, "claim-str")), "the-str"); ulfius_clean_request(&req); o_free(access_token); o_free(str_payload); free_string_array(access_token_split); json_decref(j_payload); } END_TEST START_TEST(test_oidc_no_additional_parameters) { struct _u_response resp; struct _u_request req; char * access_token = NULL, ** access_token_split = NULL, * str_payload = NULL; json_t * j_payload; size_t str_payload_len = 0; ulfius_init_response(&resp); ulfius_init_request(&req); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); access_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token=")); if (o_strchr(access_token, '&')) { *(o_strchr(access_token, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(split_string(access_token, ".", &access_token_split), 3); ck_assert_int_gt(o_strlen(access_token), 0); ck_assert_int_eq(o_base64url_decode((unsigned char *)access_token_split[1], o_strlen(access_token_split[1]), NULL, &str_payload_len), 1); ck_assert_ptr_ne((str_payload = o_malloc(str_payload_len + 2)), NULL); ck_assert_int_eq(o_base64url_decode((unsigned char *)access_token_split[1], o_strlen(access_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_payload = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_int_eq(json_object_size(j_payload), 10); ck_assert_ptr_ne(json_string_value(json_object_get(j_payload, "sub")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_payload, "scope")), SCOPE_LIST); ck_assert_ptr_eq(json_object_get(j_payload, "claim-bool"), NULL); ck_assert_ptr_eq(json_object_get(j_payload, "claim-mandatory"), NULL); ck_assert_ptr_eq(json_object_get(j_payload, "claim-number"), NULL); ck_assert_ptr_eq(json_object_get(j_payload, "claim-str"), NULL); ulfius_clean_request(&req); o_free(access_token); o_free(str_payload); free_string_array(access_token_split); json_decref(j_payload); } END_TEST START_TEST(test_oidc_additional_parameters_delete_plugin) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/user/" USER_USERNAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); json_t * j_param = json_pack("{ss ss ss so s[sssss]}", "username", USER_USERNAME, "name", "Dave Lopper 1", "email", "dev1@glewlwyd", "enabled", json_true(), "scope", "g_profile", "openid", "scope1", "scope2", "scope3"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/user/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc additional parameters"); tc_core = tcase_create("test_oidc_additional"); tcase_add_test(tc_core, test_oidc_additional_parameters_add_plugin); tcase_add_test(tc_core, test_oidc_additional_parameters); tcase_add_test(tc_core, test_oidc_no_additional_parameters); tcase_add_test(tc_core, test_oidc_additional_parameters_delete_plugin); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req; struct _u_response auth_resp, scope_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); ulfius_init_request(&user_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&scope_req); ulfius_init_response(&auth_resp); ulfius_init_response(&scope_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "User %s authenticated", USER_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(scope_req.map_header, "Cookie", cookie); u_map_put(user_req.map_header, "Cookie", cookie); o_free(cookie); scope_req.http_verb = strdup("PUT"); scope_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, &scope_resp) != U_OK || scope_resp.status != 200) { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope '%s' for %s error", CLIENT, SCOPE_LIST); do_test = 0; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope OK"); } ulfius_clean_response(&scope_resp); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication user"); do_test = 0; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); } if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, NULL) != U_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "Remove grant scope '%s' for %s error", CLIENT, SCOPE_LIST); } char * url = msprintf("%s/auth/", SERVER_URI); run_simple_test(&user_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL); o_free(url); ulfius_clean_request(&admin_req); ulfius_clean_request(&user_req); ulfius_clean_request(&scope_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_address_claim.c000066400000000000000000000256231415646314000222230ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "user1" #define USER_PASSWORD "password" #define PLUGIN_NAME "oidc_cmail_address" #define SCOPE_LIST "g_profile openid" #define CLIENT "client1_id" #define CLIENT_URL "../../test-oidc.html?param=client1_cb1" #define RESPONSE_TYPE "token id_token" #define ADDR_FORMATTED "formatted value" #define ADDR_STREET_ADDRESS "street_address value" #define ADDR_LOCALITY "locality value" #define ADDR_REGION "region value" #define ADDR_POSTAL_CODE "postal_code value" #define ADDR_COUNTRY "country" struct _u_request admin_req; struct _u_request user_req; START_TEST(test_oidc_address_claim_add_plugin_public) { json_t * j_param = json_pack("{sssssss{sssssssssisisisosososososososos{ssssssssssssss}}}", "module", "oidc", "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", "https://glewlwyd.tld", "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_NAME, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "address-claim", "type", "mandatory", "formatted", "add-formatted", "street_address", "add-street_address", "locality", "add-locality", "region", "add-region", "postal_code", "add-postal_code", "country", "add-country"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); j_param = json_pack("{ssssssssssss}", "add-formatted", ADDR_FORMATTED, "add-street_address", ADDR_STREET_ADDRESS, "add-locality", ADDR_LOCALITY, "add-region", ADDR_REGION, "add-postal_code", ADDR_POSTAL_CODE, "add-country", ADDR_COUNTRY); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USER_USERNAME, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_address_claim_userinfo) { struct _u_response resp; struct _u_request req; char * access_token, * bearer; json_t * j_result; ulfius_init_response(&resp); ulfius_init_request(&req); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE, CLIENT, CLIENT_URL, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); access_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token=")); if (o_strchr(access_token, '&')) { *(o_strchr(access_token, '&')) = '\0'; } ulfius_clean_response(&resp); bearer = msprintf("Bearer %s", access_token); u_map_put(req.map_header, "Authorization", bearer); j_result = json_pack("{s{ssssssssssss}}", "address", "formatted", ADDR_FORMATTED, "street_address", ADDR_STREET_ADDRESS, "locality", ADDR_LOCALITY, "region", ADDR_REGION, "postal_code", ADDR_POSTAL_CODE, "country", ADDR_COUNTRY); ck_assert_ptr_ne(j_result, NULL); ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/" PLUGIN_NAME "/userinfo/?claims=address", NULL, NULL, NULL, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_result); ulfius_clean_request(&req); o_free(access_token); o_free(bearer); } END_TEST START_TEST(test_oidc_address_claim_delete_plugin_public) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); json_t * j_param = json_pack("{sosososososo}", "add-formatted", json_null(), "add-street_address", json_null(), "add-locality", json_null(), "add-region", json_null(), "add-postal_code", json_null(), "add-country", json_null()); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USER_USERNAME, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc address claim"); tc_core = tcase_create("test_oidc_address_claim"); tcase_add_test(tc_core, test_oidc_address_claim_add_plugin_public); tcase_add_test(tc_core, test_oidc_address_claim_userinfo); tcase_add_test(tc_core, test_oidc_address_claim_delete_plugin_public); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req; struct _u_response auth_resp, scope_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); ulfius_init_request(&user_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&scope_req); ulfius_init_response(&auth_resp); ulfius_init_response(&scope_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "User %s authenticated", USER_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(scope_req.map_header, "Cookie", cookie); u_map_put(user_req.map_header, "Cookie", cookie); o_free(cookie); scope_req.http_verb = strdup("PUT"); scope_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, &scope_resp) != U_OK || scope_resp.status != 200) { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope '%s' for %s error", SCOPE_LIST, CLIENT); do_test = 0; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope OK"); } ulfius_clean_response(&scope_resp); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication user %s", USER_USERNAME); do_test = 0; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); } if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } if (do_test) { j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); o_free(scope_req.http_url); scope_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); if (ulfius_send_http_request(&scope_req, NULL) != U_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "Remove grant scope '%s' for %s error", CLIENT, SCOPE_LIST); } char * url = msprintf("%s/auth/", SERVER_URI); run_simple_test(&user_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL); o_free(url); } ulfius_clean_request(&admin_req); ulfius_clean_request(&user_req); ulfius_clean_request(&scope_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_all_algs.c000066400000000000000000000713471415646314000212130ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define SCOPE_LIST "openid" #define NONCE_TEST "nonce5678" #define STATE_TEST "abcxyz" #define PLUGIN_MODULE "oidc" #define PLUGIN_NAME "oidc_keys" #define PLUGIN_DISPLAY_NAME "oidc with multiple keys for signature" #define PLUGIN_ISS "https://glewlwyd.tld" #define PLUGIN_CODE_DURATION 600 #define PLUGIN_REFRESH_TOKEN_DURATION 1209600 #define PLUGIN_ACCESS_TOKEN_DURATION 3600 #define CLIENT_ID "client_keys" #define CLIENT_NAME "client for multiple keys" #define CLIENT_SECRET "very-secret" #define CLIENT_REDIRECT "https://client.glewlwyd.tld" #define RESOURCE "https://resource.tld/" #define RESPONSE_TYPE "id_token token" struct _u_request admin_req; struct _u_request user_req; const char hmac_keys_list[] = "{\ \"keys\": [\ {\ \"kty\": \"oct\",\ \"k\": \"R72SkNI08mVXlNSOKCeRxw\",\ \"alg\": \"HS256\",\ \"kid\": \"HS256\"\ },\ {\ \"kty\": \"oct\",\ \"k\": \"K4fSC0yj046M_UOzVm8xQmEyWp8Uinn-\",\ \"alg\": \"HS384\",\ \"kid\": \"HS384\"\ },\ {\ \"kty\": \"oct\",\ \"k\": \"doVfGuP6lRn0-aAX0X9kc5LT7nVdOPvMT6PR4gbGa0U\",\ \"alg\": \"HS512\",\ \"kid\": \"HS512\"\ }\ ]\ }"; const char keys_list[] = "{\ \"keys\": [\ {\ \"kty\": \"oct\",\ \"k\": \"R72SkNI08mVXlNSOKCeRxw\",\ \"alg\": \"HS256\",\ \"kid\": \"HS256\"\ },\ {\ \"kty\": \"oct\",\ \"k\": \"K4fSC0yj046M_UOzVm8xQmEyWp8Uinn-\",\ \"alg\": \"HS384\",\ \"kid\": \"HS384\"\ },\ {\ \"kty\": \"oct\",\ \"k\": \"doVfGuP6lRn0-aAX0X9kc5LT7nVdOPvMT6PR4gbGa0U\",\ \"alg\": \"HS512\",\ \"kid\": \"HS512\"\ },\ {\ \"kty\": \"RSA\",\ \"n\": \"AKdOqHA5Vvcsgwv1Ag7MWzIuI6Snm4aOaccLcMQOUxXrLyVC7F_yi2BaCK_rJaBKKUo0ybu_KQhtBhlU7k-7izcbYW3K0lfZv5Zk-01BXHUhlniATXoxOp1UaJPD_Wkwqjgdgnwch9Nt56Wh742qlXyeoj8ywlApW-80yv1UperZXDQ0sCnp1ztbGX-D2B8MPyhJlTKPWB8_LJJ_8VveqwS8Cs8xLD3OUd3EZCdqGlgDmjvwg5PWnULATiu1DiOR4nUXLDP0nCa7-s3NqSKhHENcCDNpCFmWCtaMltvOTkh6hdOoZJ9Lq5ZrjL-7UuCwgx3VlcwubByER1s04Ir8-gk\",\ \"e\": \"AQAB\",\ \"d\": \"O2z26xWSzCylR2P5HSR85-_3fQ6DcWG4NJjdruWfoVNt5YBF0TanRsvz9fhB3xM4Y0EovmUBwBppZioCk5N7uVEiZAr8d3PCVzr6_8_NdVU-ywJXgqVlumg21PVyVyCP9WqV5FuF6xVIiE5idiE3A5Kc8nGnDy4Bl49a4mxkmmuTClCRZREYGGJp-zsQCH65VhMfnzAeIEQ0FPZ_b1SkUwlU_PJ5QVXtbkueOu8FJJpwrAVLoYYZE9ccxUeHuCEsW5l1NL3snyqUw1oR299hjBenSFddrdfkjGzKshumhREMnGaNRdYC2oAenOL1LPWM-flL0qgEnvqLkDj2J3FcAQ\",\ \"p\": \"ANG8kXHtC539U-KkdVIT1qGvsR-s8Yg1kGnVPsGBgBhf02rzcg4steMb8NcqVfbPPOjz-8luhVhY0Eij6Q3ok3CefebdtdcvqN-8bXzmEglHCiw0MkcVYw_pIfReB9relyloyKodnSvnsFKTZ8lU-02H9sZ_kmuLU7C0O70jptpB\",\ \"q\": \"AMw2L0-CA9gj2Tf7OVWBckHaYV1pdzfsS-OnKk7Hppw8z1-3xMSX2Iz50GvyLWkFS2V7LsabobLHlEuwtE0g9sj5WlWRKEwoEPkWGG8JOxqDgoqMqB32TCkA467XtYH9Ch6pWVezYFIlsRNGX4YdjH9LxCHNlwRtZB3pxCuPMl3J\",\ \"qi\": \"AJ8ltjqbm9h-i5CMT5FQcUwdUkV-m45dicmMLn2dV3_R6z-CeYTjJYnIyEoA6AdZdQAUfZMsbjKO_bgDey_J-KxIvVby0FZpkfl36yrR93lMOv3A2hsTXpqAaqFX20Ph3w9SypqTH9oXD3cAJ7Rlh_5jdsX1kXzoeQ_VPgEFH-CX\",\ \"dp\": \"Y-S6KVbLh64WfAX0Uulb-pphdEK8rzFD3QRR5Xw2dGV_nprgodutrcOrC_AADZNa4WEDdUcMf62dVlurLpKtVqBGOuUyLJFoj1eBllFGGeEZ-T_LCownKHbTUz5N43LM8E4V9OAx8a1iD5JhhkTRhHXTlWtBY7NyYuEU6trGJ4E\",\ \"dq\": \"BC9MEuYILCK37dTBHQZ1D_JosmBZ6BR4jaa8UDb5LBR273A1oQ23i1QHOF8THSbVn7PBhqJj0uUSHeb2GuqFBCNP_Zbm64CasHAKeiQHSQjO4QX23_5PGzwAbnHPL2W_ElfIE-sCG0zYbxuvE3GBko4767Fp2dZgCdjjgz0A_Dk\",\ \"kid\": \"RS256\",\ \"alg\": \"RS256\"\ },\ {\ \"kty\": \"RSA\",\ \"n\": \"ALnTB6D8SIi9rFFbbEQfqPR2_b95Z3T0_ZKxkPh7s1uENSb7IN5bW-30lSsjifMKgiETbAepKy_JF9nZVQkoweierFOaLRNiZn3WAO3h2NnO9YXt25P7P5BF1klItIXxi7orEIIdYJhL-v6p3FGsv45jYUw9g0t-HcRBVdWrVw7rnAOJn4N_RTdEPjm9VotBsmf4P-1PfbTazJ1SirPlbJ9q5WgrUhx1z_aVQNDKl5kJGYBtldnNiV9Ql3YjW9jO30NnVEXqYj675Oc3gasG0jtWG8jQTV24SBImfMoYMxlzJioXcFYPp1AWtVzYbcDTgZYr0WFrWuBgsQ5cWHEL9BU\",\ \"e\": \"AQAB\",\ \"d\": \"PQptPxygVwq3SCJX9ijQPz23LOacbXbstPtPO9CmojFTpHJp4aDxGcF2Hq2V6xhQlrzih8GyRggwpYcWv-N7jwZQZUYH-I4iTMO3mmzN5v5s-Imhz4KA5suKEJipdDZcR9NNoPA1gtGyqWTy0oGEiylqFLlAH9RVwtoTcBTQjD2YvZXZ6nzG4ALNeqyynn0-PA7rfPRCo0CPL6oHxtTK4hR_0BmxV43eKLsPzlkbiSQlcI7paMLAMrzN-rhEjE4QrTdXpB0MGHzfaTJqoRCnL56N-oHiAJGhDnMVep5-VjNLw_TMg7_LQz5X4F4uS6u2392OTsErmylDPwtiZk_fAQ\",\ \"p\": \"APWISggwJKw80geRUN2BuLQLOOWwPIF-7GunMwb341srz5puNX3VqR5FMlPoM562j_jWe3jndgl6auDIYQKaae4ImPJ80-z_kaNASGPhz-GZC0rPUULEUFWbxVIgXAgvVnQuScc3QS41AxdPuZnCXdxbZFoz0jYZBj2JqWlwUeOZ\",\ \"q\": \"AMG_GB4HQ483yUn_61DaiqZZi_yVK1gEzhmcHSSXnQSG7P0akpbm0iTRt33HF6nNpJ6oXzS7yB_XO-UDfm7hIkpsp7NeGovSg00XQ1uUkU_z8TsIuBEiWlLs3Vy6z8DhuJfUyuVtbe0JE92dXQ9t7iW108JgT0sQvL3QD0oeeeHd\",\ \"qi\": \"X9_Sao4DCwdTBD4LVRI5Zb14Mf9JNiGO_swzVDUsZG79eakYQg1GzRHhx3sG_CsPXMsW-_EOLYEOVvNIc1DKPIExR1Owifp2Tj00Q2BtEj1t6i_5cUexXf5KuacIhnF1O2zrAhSiXXAlEHaA5-hDGIvITtXIY3j2vMGcudzonkU\",\ \"dp\": \"XHGzBgAyXpMLtQO2gZ_MziUHiBthvJPwKdwq7y238WS-ZnOmOjmO0jHVcBgWD2THMjZ3CJ-FJq5rvTRUqik_Rvr_sxTiqfHTgLa8SrcDkPoRcVo7Szsk0Aa1NWWvoPlJwLaI2rPoG6CkCEvhIo42zreuQfQO6oVjfxnsqiE7A8k\",\ \"dq\": \"CDHg_0QnY2NkrDCa72yO-MJI3YIhtzNc1FB6GxYemhZq57m8AY35zXzhWfyVz6TXFa7heAWJTW95JRKkwmPbY3J9FWUhklBxJU2al8EM9GjJB0ozHuZpE6DZLBbquqnRePqTKmkagTAlGvaQ_RoVPJsYPdx8_hvTH-QxAV2QojE\",\ \"kid\": \"RS384\",\ \"alg\": \"RS384\"\ },\ {\ \"kty\": \"RSA\",\ \"n\": \"ANI6-B_hZMpUPoQTLHY5GjqJ1ry0138PgWy8JWfFkKwIKpkFZ_ps9rNLeZf1bztLcSruB4JwZZPk4G-wzB3Adz1Fk4nNjvBnRNiOIgEUlhCMCGPyPuz9Dbxexz8l5Cc6GZOxiMmxcOFM-tbQSlY5oVO0M_vSgiDJblyxyNvOxNDoV1hIRB6NfDKPkQR_6gNHqIKV1LIQ2-9PJn6RI5eR1u7z5no7Vj4ygBKG_hWyA4Mg-yiYNgdgycdboGlx6DJivjHT10NMtQYp2eMjnCmtrGYobtaHmTnyCWoX5V5St9CY_pdl8ymMq9fq7kn0ILd4b9CJEcgjV6WZml49mKf1-vU\",\ \"e\": \"AQAB\",\ \"d\": \"AMkCBRhGZB5oqlWSF6L6Oi_ad21648jjRHZ49rLf3dH_BOvGlYKGCOOpuJso2q-xFIVdjeSUHytnXYitXJzd16TID6dk2dTWiKceTzkFO-6aVbNqfewOkMGZRZ0FV76B-M6UoxtmA24IMpaOFWWYOL5VwJZ40l8S-ei7PDee3eR3ZjrluZEFV0-mLWJ5ndyp4Xc2hT7w0I4V_YgehIkSY52ZDLZD2Ne8fDd4KBFbGXvyD7N1Ipb27N2gRZNJcZfnXOEEWf6lRT52-EhmjQRlVwnIE9phLr5CzJfHIaVbQXXpIjBDezQhHF-Bmz15vWOR6_bk22OwN8tAAD4cNKPUvkE\",\ \"p\": \"APSgxnyWMhFQhaIhKQ3j4G0YpSUyL1VWyi4Lhl9SArU8AUsw1F7RJCdLqtV51iaRfmmewUH2oA2g3gFWXElvZ5xCWHohn7g2OOhlq4CPt_McLRj7zfkT91l8Bkn4LFkpDua6XVYPehs-H-0IBfAS7u2PEc-UYVQUh18s6OFS8ZaJ\",\ \"q\": \"ANwA1xr8goFfiVpXAFiHy3JZlyXW-TcMlW2YEFg9IZc-l6wYzty1iLJlATT_bo79b4CYGCXuem8UtusfR4HyIYH5SCczNpvwqhloOtQ7zEBCGb2QlC6qhR847DxtdYZ7gSM5K08lSBcABKnRMZAlZuhD3V5CMGuRK4vnU3JUUSYN\",\ \"qi\": \"AJ0vFeqZz4OsqpyyHnjIlqVONX0qDgL797gZuPejlLCD2Ur7SA33gKLgLGBUB6xyAyfW_e0I9uGa8MuOGqE3r7NGItajCH3GXyjDz7GrkbQxogmyVE1mLkHnuNKpHcbH8ImVmRYI4RjKoZGpdVUN8YDoYueWjX-dm12asOttpOrM\",\ \"dp\": \"AOy6zXnxa0BAOSHdjzom5J8Os3ocZ5vhIkSO2JlT5tT13ZajCVE8eQ0h948gmXG3aKrTe9fWz6qAm3aV2TcjfRPFTJPcCBGfP1D-WopOCkhUYvwDaZ75iGtrTzaz2E7sIcR8YyiOT68fXovmMMDTwa3Yvvavc8SHHT2oWzD6MFpp\",\ \"dq\": \"AmMD8mgA5nRp4hAFkfBPNbthF2kApSc-y8SVkM-A-MoWDSjrvZs-k2jjHXcT9Pss5YFA6dBvhZr87QoW1YMR9_4DWWGF2yU-Qy5NTRYk_iF5dAQIh4UUEqWkcndhigb2_LHXFXG7GXzHkCwT1JODTUvHMAmZyuD1TvxAfIILq1U\",\ \"kid\": \"RS512\",\ \"alg\": \"RS512\"\ },\ {\ \"kty\": \"EC\",\ \"x\": \"N1o9dzRYW_LYhT_gDKJGabUCcrClQuP6P9CpHrd7BuE\",\ \"y\": \"AOmhhdwGbLQ20Zrftyjawal1LmG9Ai2Fgh_SpBqKJZli\",\ \"d\": \"Az3F0-LiPboaKUt66OW8sj1BK4EGGkFl3rUX08h2G9A\",\ \"crv\": \"P-256\",\ \"kid\": \"ES256\",\ \"alg\": \"ES256\"\ },\ {\ \"kty\": \"EC\",\ \"x\": \"AORgXiOIgXRKHnyHozRKHy9QOUq-m3auoyEUAPmQ7Rv7yX7juRO5bKWFEFTNuwYxEQ\",\ \"y\": \"dCURk3PMy-vL0_-5yCcM6HyWdpK16ptzIFwL0a1DW8UpMSF9hrU9uy_8LD3tstMM\",\ \"d\": \"XGDl5tRyBSJ4gzMb_uK0AvuH9Oayts4vreKmZ3yYl17yQeKE5l42Noa7ZuVZEDCU\",\ \"crv\": \"P-384\",\ \"kid\": \"ES384\",\ \"alg\": \"ES384\"\ },\ {\ \"kty\": \"EC\",\ \"x\": \"AeRnJbSK8mHeovK7BwrxJ-974Euz1JJmU58BZZiHDJjp7SW4G_ZrZlmSk7_iQn44YFvNST26LefiPJdds7wLPsne\",\ \"y\": \"AT1ifPyZZeixmCyLUSGXgq7QIRYBXj5pGFbS90PMHJkzfyYqPX607AhVbuqVaIpZb4zFUgkSLPaU08JihEhACABx\",\ \"d\": \"AQ2CBfGYbElfZH4W0-LUJaiRvI_MdHUFDJGtY4Ns693GwqTjkZZ6Aq9a6htganWMuFE-jVZidkeVW62kctNfw7UP\",\ \"crv\": \"P-521\",\ \"kid\": \"ES512\",\ \"alg\": \"ES512\"\ },\ {\ \"kty\": \"RSA\",\ \"n\": \"AKArSp5VLTNyDMGZnDNeoBmtHB8JBgJAYe45l5boakDsVBAN1Qx8qQbk1lcLJpSr5yYG4f3EmoxKloDGlLk6WDrcitqUIUfwM5lFdl5Vs1MbGffV3nFSbnWi1vW_cJh1KCwMa6S3GAK5w3j0roDDacJ0RxRs9x8MBHCLvT4cuylm2YVn22pfad-LROwO847x8KEFy8jluiArHVfdQv7nSeulr6r-JO9SrUmyeHWKSli1ZXjf6ZlArn63g_bn7WXTCagefP01H3vcSgTTZ4BTcnHcvNSKcwdYZDqyZII_cwJyg206VQzvl1UjgmMqfFzV1iCdyVKbYnuDLF4EFu0jgeU\",\ \"e\": \"AQAB\",\ \"d\": \"AIiCXSZ1EgE2wwh-E1L04x7_G2iYGDbzCIQxMbG8hFKxGTRVla7-0FC_2K53InqzyF3wn8vZNJ89MuiuVzNHNst0DCQe-_6ECnYnbasY_61k-8zuypdq2hoIn4zzjNNjhsmEDHpmUmCAUslUQSYdZpYE5E_UTwp3A5Goh7HYauvtE2tRgfdMusOQlQYyX-CMyJ4TTDDv5j6opo06BWPAXnLP42LdQLLA-nsF-qhIWgju2t_gKQgfvOtCihO3AqMs6Lj1APiBjKuluREcfYRiGr31sMPpegJn45NJBSBz4ge7sAQmrNZ0bEs10RLfAmIlyXv-wneW3wFhjAgRhV7gYgE\",\ \"p\": \"AMlru8fpK4zsRmMvreD-P_pq1a55LNL8zVMd3-aiDatpu_CsoJMxa7GohRSx904eE3zdgTe-AsC4jtgFxQU5E7QQbGc9wUCd186YFfHy8PdHvwDUv4ljaPkdq8XlJaGo-ui7NhKTUE8OFkfACP2AcN0HY3LSl2CjSiJZq08e9lgd\",\ \"q\": \"AMuR_IiAVB6UnpmeuCmT7-KCUx6vcA1G7-pkX2SWBa5n41Z71SgSOclqeE7yIDYCIGNL0_v03iKFs_oVpoH6OWBrMaLZU7pJqDWnvxQqBbiK6WGvJ4ouhd349l_x-jNXIY0OdPctR0WjqNr5JfHW22xeDF27E_aWS5qVHT9CrHZp\",\ \"qi\": \"V8Eyw2hRWqBtuZIS6O4b3j_oc9M5SeX_cyKWFok3QpZNv5OXtBVR9SmZDh-oPPOs--d3Pj7cn9EjyB8ji3NLjRNc1bHlqaUHFPdmViqxWw2OPgBWKHvRmpjWCbmcn9BZ9PHFlRsyNJCqNDgrvlda6gvvtL_SnPy6MsAmj2Zn4JQ\",\ \"dp\": \"AJB5Vhfuh-5tC4_Zgz7_H6TfPKYJBL3R1vTnWNJ1KpjpHoVjTUpHCJhF6C8P9_NwX0oRF76D7DWQK-WHPeqhJiDiJt9mzFcs4L6vGA6T04OLUtWlxD0nsQP-5FbuJi_upQqKPh7Uy6Xo1NJiTBCJMGtaAVs68pm-hk5dQyNdchWB\",\ \"dq\": \"F8MouT5Rk2hBwyjV0nSkUcporXJJICOhqbihsfoZG6ygyt2VmiHWgP5eoMh-ng9NfInDauvAakM1KQIR96YfwHOCzcGUlnA2pFy6Xz4wgMQmTfLGKMkZczm2eKikTg3jqrV_TcMJSMW71iOzHDG6V0H7K43E3MnadWbmjRXZT9E\",\ \"kid\": \"PS256\",\ \"alg\": \"PS256\"\ },\ {\ \"kty\": \"RSA\",\ \"n\": \"AMEdUKWQiNUSkVQDbe2D6FxTG7wlLvzqA7W-UcZXJeumPH4CXngg2cOj1gXLG_D9qODjYjcYduc0HZghxx-ievk1JLyYZsiUwml1xXHmzOHU1VTdQO9RrVO1ZitcR1vRB0ZI0oCBIVqubcNQjhh_k6TNjLZyaToF1AufZyKYVp4LC1-jXp_Ffp74AcMSFroUPJ8FUmv7grqCfL3US7DOfeG2CIaNY6SWY13PdFJ9lh2cMrRWkqgl_1rvIbUvAXc-oAEAYd_x0D3dQvOhE1Txr6WPh-RENGUFAyZmLWH-hyM7KWW1TqTjyPwZob-hAqXmhc7qAWq3GYnvc8QFFr7JNlE\",\ \"e\": \"AQAB\",\ \"d\": \"AK9Cwaxg7i4iOc67hgqnSjxwGJ26SVizsUZCQcj10q55IjFiSQZRGhFaIaUEXolqTNg8xSgnhdHzFGC7VxI4zc5aEssurSmhCIfZoKXsx0i1dh8c5g_MWre7y8vSZdjIbge1k4WYrAK2h1tZQnytW_uXqPrz_tfv7i_WLS3Sf9nRrF2xUMFcZNqwzo_Fq-XX4E5htqaRa6DNm6rk7NoYToO0WycSt_T9TYGT1n80vmwQMXx7Uuu7Ty99hJTf66PqwIzhzKg7vzjdh7raN2VGjURhFJPnJKtb3w-rbXlyy63szS1R1MmxfGl5r52nrl6HE_LdS2vFHTdOVSkC_XWC63k\",\ \"p\": \"ANatNcDP5BjI5ceR6fMPFX6IgXmi-NBrj8Po5jk-Xbeqbx4aQ4Z3-1ZWHb0JhDNXNgvHDPZ5IA5POdLp9Y3awyokr5FFHwFdO02or6dwE8yAYmASzHRiDblzXugymDBmoCLp6KkabcKN_drba6lSEvPFEX5mKlp5ZnrQ-4q8Mwc7\",\ \"q\": \"AOZJkqFAnhA20gwv6oyEqeP_WadJZK7fbFqsPrTWONpF5g1iKnK3s1dXUIL1s5Mh7Xmsw_GzrMp10Zx_pF0RKA2kdJWCWTpXBLEjyNLx_GR606SiHXke5bd5MAislogKoxYkNNTe9UwZ15hXS3aeznSDMvumG02LzoiUllLkcpfj\",\ \"qi\": \"AIl5ah-ymgxJqZ6m0QJQdqBy7CHtn_14a8RYdMUCfVv0Bu8CZhX83Jiely8QUYJMH5dNNv1T3EepKX1J_6TTF9KvhOSIb9XmN5c70MP3x6jG2sdKegJ4-a7EvK9rvcBY8AtQGf9kgTLw76stMuAk2xtNcQBHEI7iLGcvi7-3o4PI\",\ \"dp\": \"AMPmij_qNi873RuyFricEjRGo0h5pO6kySuw865XMDRzjjT9KJOkF7KgoUCpV392XTaALV8aB1unho_muhL6B9EEa0Z4uiOHjZ9_iNOV3itnGN6tKPAnrniGRJxF10WL0SQrKgpuuKyq4HYAd42q_OqA8kbTOmEXmaIH5ROkTDiT\",\ \"dq\": \"VhfJVrmmnh8wldfQEyqBrThIly7sEih7BMcCRm8UIB4jrHs3rV2aEZwWAG_E68uyUVvSgWkPvz0e2SgrFZQVakxCPabWnuXrXiInsR1Ao3v75b-pzx9K-DW5THThbgi0AdIVYkPcZs_-dvijwLwMKSjnhYcLDAmpdAxAysqXQMs\",\ \"kid\": \"PS384\",\ \"alg\": \"PS384\"\ },\ {\ \"kty\": \"RSA\",\ \"n\": \"ALY15QZjkPepRc0dcPt8pcvmINe9z844ZuuB5O6CHmb2fCpQF0fqn0kOC-ZRuaf13WMO9yZ5KHveH9-2FyeMrOljObYvBRgIiwvlg5bSZ8l7jq4BCX9PoBkcIKIfHqaULh8gy6Go9421cOFimoQ20DR3Q1xAKg77qOeiVzhKDtWZUpsZmNgpq3Y71B5ccHIeuczTu-YbjSGE-xiTnIH8RU2198pm_PIDDoBAGvX6MdLpyluz5fzLImFvEOuHlfpZtjLPVD4hfsJ7J1fw_pnKfUsKD_AlCJLC7E6xpCQM616rGC3aCaUwCGaFiNTCaSHPvmSBQ6FAGAWc8iy5b-3P59U\",\ \"e\": \"AQAB\",\ \"d\": \"czYqxVppjJ-kuih2ix3qu72cYK3tWsjBEwLEHad6z8HlCuAviFKrOwc9sPlV-gT1YjBRSCtt1LzDzLGXfHNv1fbew_qBLwwf13rdNJ1_8J9GvXF8btqLMbVeUQR6XfvuGdKjz2lGdn0WjfzKFPWPusz6c7dCoMyxpR06JyZ0sMu5tngizgcosYv4k4eJNhvvNXIYlAMTfr9rvoMb321-1GKs73bhRr7NDQIaC62d0Np2E5u1rnP4Rqn6rJ99ZptodIzKqNJYxMqqdRkdGdMEiLUyheDOjNqyNYvc1O4h9NNKKkJ4CeFHeasAlV8gMREZ3fvYWbJjz5HK7-ECZxWSAQ\",\ \"p\": \"AMAefKNqGgSlUYMBt1J-GR-Q2WzMgGZICjMXq0koR7NNoGAiqc6dECRfHGu7agDKwOjYT22u37UsGwYLLmlkmGM2qtkVw6998vk763JTYlqE1QL4EQv0oYjNw5rYsV1AhsIdKjf1w1d2uVrqc2bZ6nqpGM9XVIb_vJ-1atFnhx2B\",\ \"q\": \"APLL-Tcygf9WxVLdAEV5eQznR2OBJbpgGxQgUoM8r6hL7O9C2FPgWXXb7da-OC-0-Xkm6axDtCsST6qitKd6fNyk5H2YF1LwhJoGIV8Pd58KnxDRPIqF2BoBYJT38It2KTw2zLRVJBHHjxRuAf_tCZpAQZmsVVt_xzZtX6S1ORxV\",\ \"qi\": \"atMm2Yd5Q28B_QhxMxDv7BQbpnjpSyI_UVK2ugj4Y3wLfuFsk4gkDrvrSUVSnbk4BiWAKwHx4PpvIxDfaxaspp0xOJ6Luil-pA4JRU2fQSzDSnjsokpF7jrA0WAlsugyv_DAfMV00qsBAfTNmVVNpTsNE7q9_py1joJnQW7Iibk\",\ \"dp\": \"H6Llk2NddXZjvdcCgSsSqAgKRchHPJCQXWmAY0Omyvf1eN88ZzGl_tdKrtLl2cuJiM5WBhHN9N-Wc6BTvDc8gNn3uFfFKZjr8e7UDrd8crt8-EgxRm66SvTXSSB11I1To5N56E0oJbb_PuFsr3sjCZ_e3gXbfX-PaB1PwbvynAE\",\ \"dq\": \"ALPyMNaT_Hz-Fmj_mn_jM7A4iVRaF1g2eltESWMyyw10mDoL1MD1edg8E94gRQw2mN8N1An0c8eoXpsc0798rKXrH91lp4O09tIg0QLfN80L1jBkovYXlL5RXv-JcyIphQ43nyfYIk97E7QUhC7lw0Qrc3MtomWaIk9YaJZuKdGx\",\ \"kid\": \"PS512\",\ \"alg\": \"PS512\"\ },\ {\ \"kty\": \"OKP\",\ \"x\": \"eKLjYeHBciTpBOKvIslSwbhvLa6Ae1PoBx1qc5knCAo\",\ \"d\": \"huEf2dA6765FzdfPb7N7QXOwJsuqgM8eYtnupFR6Tpg\",\ \"crv\": \"Ed25519\",\ \"kid\": \"EdDSA\",\ \"alg\": \"EdDSA\"\ },\ {\ \"kty\": \"EC\",\ \"x\": \"YKXJmQ4x8J_6PN49KMKQylUWVhCU5L3IO8q05GQN83g\",\ \"y\": \"QgLdjSdd5fbkARJTULCxrXqV4VJaAqI5WjwDzSdhbSI\",\ \"d\": \"YaQLxs-ZLxr1MmwE1h-h6kBnD3_qTKKBhKTJlQVwJWI\",\ \"crv\": \"secp256k1\",\ \"kid\": \"ES256K\",\ \"alg\": \"ES256K\"\ }\ ]\ }"; static void add_client(const char * sign_kid) { json_t * j_parameters = json_pack("{sssssssos[s]s[sss]ssso}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "client_secret", CLIENT_SECRET, "confidential", json_true(), "redirect_uri", CLIENT_REDIRECT, "authorization_type", "code", "token", "id_token", "sign_kid", sign_kid, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } START_TEST(test_oidc_all_algs_add_module_ok) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisosososososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwks-private", keys_list, "default-kid", "RS256", "client-sign_kid-parameter", "sign_kid", "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "auth-type-device-enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_all_algs_add_client_no_kid_ok) { json_t * j_parameters = json_pack("{sssssssos[s]s[sss]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "client_secret", CLIENT_SECRET, "confidential", json_true(), "redirect_uri", CLIENT_REDIRECT, "authorization_type", "code", "token", "id_token", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_all_algs_add_client_hs256_ok) { add_client("HS256"); } END_TEST START_TEST(test_oidc_all_algs_add_client_hs384_ok) { add_client("HS384"); } END_TEST START_TEST(test_oidc_all_algs_add_client_hs512_ok) { add_client("HS512"); } END_TEST START_TEST(test_oidc_all_algs_add_client_rs256_ok) { add_client("RS256"); } END_TEST START_TEST(test_oidc_all_algs_add_client_rs384_ok) { add_client("RS384"); } END_TEST START_TEST(test_oidc_all_algs_add_client_rs512_ok) { add_client("RS512"); } END_TEST #if GNUTLS_VERSION_NUMBER >= 0x030600 START_TEST(test_oidc_all_algs_add_client_es256_ok) { add_client("ES256"); } END_TEST START_TEST(test_oidc_all_algs_add_client_es384_ok) { add_client("ES384"); } END_TEST START_TEST(test_oidc_all_algs_add_client_es512_ok) { add_client("ES512"); } END_TEST START_TEST(test_oidc_all_algs_add_client_ps256_ok) { add_client("PS256"); } END_TEST START_TEST(test_oidc_all_algs_add_client_ps384_ok) { add_client("PS384"); } END_TEST START_TEST(test_oidc_all_algs_add_client_ps512_ok) { add_client("PS512"); } END_TEST START_TEST(test_oidc_all_algs_add_client_eddsa_ok) { add_client("EdDSA"); } END_TEST #if 0 START_TEST(test_oidc_all_algs_add_client_es256k_ok) { add_client("ES256K"); } END_TEST #endif #endif START_TEST(test_oidc_all_algs_delete_client) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_ID, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_all_algs_test_client_ok) { struct _u_request req; struct _u_response resp; char * id_token, * access_token; jwt_t * id_token_jwt, * access_token_jwt; jwks_t * jwks_verify; json_t * j_jwks; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/jwks", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_jwks = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_int_eq(r_jwks_init(&jwks_verify), RHN_OK); ck_assert_int_eq(r_jwks_import_from_json_t(jwks_verify, j_jwks), RHN_OK); ck_assert_int_eq(r_jwks_import_from_json_str(jwks_verify, hmac_keys_list), RHN_OK); json_decref(j_jwks); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth", U_OPT_URL_PARAMETER, "response_type", RESPONSE_TYPE, U_OPT_URL_PARAMETER, "client_id", CLIENT_ID, U_OPT_URL_PARAMETER, "redirect_uri", CLIENT_REDIRECT, U_OPT_URL_PARAMETER, "nonce", NONCE_TEST, U_OPT_URL_PARAMETER, "scope", SCOPE_LIST, U_OPT_URL_PARAMETER, "state", STATE_TEST, U_OPT_URL_PARAMETER, "g_continue", NULL, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); id_token = o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token="); if (o_strchr(id_token, '&') != NULL) { *o_strchr(id_token, '&') = '\0'; } ck_assert_int_eq(r_jwt_init(&id_token_jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(id_token_jwt, id_token, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_jwks(id_token_jwt, NULL, jwks_verify), RHN_OK); ck_assert_int_eq(r_jwt_verify_signature(id_token_jwt, NULL, 0), RHN_OK); access_token = o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token="); if (o_strchr(access_token, '&') != NULL) { *o_strchr(access_token, '&') = '\0'; } ck_assert_int_eq(r_jwt_init(&access_token_jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(access_token_jwt, access_token, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_jwks(access_token_jwt, NULL, jwks_verify), RHN_OK); ck_assert_int_eq(r_jwt_verify_signature(access_token_jwt, NULL, 0), RHN_OK); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(id_token_jwt); r_jwt_free(access_token_jwt); r_jwks_free(jwks_verify); } END_TEST START_TEST(test_oidc_all_algs_delete_module) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc all algs"); tc_core = tcase_create("test_oidc_all_algs"); tcase_add_test(tc_core, test_oidc_all_algs_add_module_ok); tcase_add_test(tc_core, test_oidc_all_algs_add_client_no_kid_ok); tcase_add_test(tc_core, test_oidc_all_algs_test_client_ok); tcase_add_test(tc_core, test_oidc_all_algs_delete_client); tcase_add_test(tc_core, test_oidc_all_algs_add_client_hs256_ok); tcase_add_test(tc_core, test_oidc_all_algs_test_client_ok); tcase_add_test(tc_core, test_oidc_all_algs_delete_client); tcase_add_test(tc_core, test_oidc_all_algs_add_client_hs384_ok); tcase_add_test(tc_core, test_oidc_all_algs_test_client_ok); tcase_add_test(tc_core, test_oidc_all_algs_delete_client); tcase_add_test(tc_core, test_oidc_all_algs_add_client_hs512_ok); tcase_add_test(tc_core, test_oidc_all_algs_test_client_ok); tcase_add_test(tc_core, test_oidc_all_algs_delete_client); tcase_add_test(tc_core, test_oidc_all_algs_add_client_rs256_ok); tcase_add_test(tc_core, test_oidc_all_algs_test_client_ok); tcase_add_test(tc_core, test_oidc_all_algs_delete_client); tcase_add_test(tc_core, test_oidc_all_algs_add_client_rs384_ok); tcase_add_test(tc_core, test_oidc_all_algs_test_client_ok); tcase_add_test(tc_core, test_oidc_all_algs_delete_client); tcase_add_test(tc_core, test_oidc_all_algs_add_client_rs512_ok); tcase_add_test(tc_core, test_oidc_all_algs_test_client_ok); tcase_add_test(tc_core, test_oidc_all_algs_delete_client); #if GNUTLS_VERSION_NUMBER >= 0x030600 tcase_add_test(tc_core, test_oidc_all_algs_add_client_es256_ok); tcase_add_test(tc_core, test_oidc_all_algs_test_client_ok); tcase_add_test(tc_core, test_oidc_all_algs_delete_client); tcase_add_test(tc_core, test_oidc_all_algs_add_client_es384_ok); tcase_add_test(tc_core, test_oidc_all_algs_test_client_ok); tcase_add_test(tc_core, test_oidc_all_algs_delete_client); tcase_add_test(tc_core, test_oidc_all_algs_add_client_es512_ok); tcase_add_test(tc_core, test_oidc_all_algs_test_client_ok); tcase_add_test(tc_core, test_oidc_all_algs_delete_client); tcase_add_test(tc_core, test_oidc_all_algs_add_client_ps256_ok); tcase_add_test(tc_core, test_oidc_all_algs_test_client_ok); tcase_add_test(tc_core, test_oidc_all_algs_delete_client); tcase_add_test(tc_core, test_oidc_all_algs_add_client_ps384_ok); tcase_add_test(tc_core, test_oidc_all_algs_test_client_ok); tcase_add_test(tc_core, test_oidc_all_algs_delete_client); tcase_add_test(tc_core, test_oidc_all_algs_add_client_ps512_ok); tcase_add_test(tc_core, test_oidc_all_algs_test_client_ok); tcase_add_test(tc_core, test_oidc_all_algs_delete_client); tcase_add_test(tc_core, test_oidc_all_algs_add_client_eddsa_ok); tcase_add_test(tc_core, test_oidc_all_algs_test_client_ok); tcase_add_test(tc_core, test_oidc_all_algs_delete_client); #if 0 tcase_add_test(tc_core, test_oidc_all_algs_add_client_es256k_ok); tcase_add_test(tc_core, test_oidc_all_algs_test_client_ok); tcase_add_test(tc_core, test_oidc_all_algs_delete_client); #endif #endif tcase_add_test(tc_core, test_oidc_all_algs_delete_module); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i; char * url; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "scope1 scope2 openid" #define CLIENT "client1_id" #define RESPONSE_TYPE "code" struct _u_request user_req; START_TEST(test_oidc_auth_invalid_response_type) { char * url = msprintf("%s/oidc/auth?response_type=invalid&g_continue&client_id=client1_id&redirect_uri=..%%2f..%%2ftest-oidc.html&state=xyzabcd&scope=%s", SERVER_URI, RESPONSE_TYPE, SCOPE_LIST); int res = run_simple_test(NULL, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "unsupported_response_type"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_auth_code_state_ok) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=client1_id&redirect_uri=..%%2f..%%2ftest-oidc.html&state=xyzabcd&scope=%s", SERVER_URI, RESPONSE_TYPE, SCOPE_LIST); int res = run_simple_test(NULL, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "state=xyzabcd"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_auth_code_ok_redirect_login) { ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE "&client_id=client1_id&redirect_uri=..%2f..%2ftest-oidc.html?param=client1_cb1&state=xyz&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE "&client_id=client1_id&redirect_uri=..%2f..%2ftest-oidc.html?param=client1_cb1&state=xyzabcd&g_continue&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); } END_TEST START_TEST(test_oidc_auth_code_client_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=client_error&redirect_uri=..%%2f..%%2ftest-oidc.html?param=client1_cb1&state=xyz&scope=%s", SERVER_URI, RESPONSE_TYPE, SCOPE_LIST); int res = run_simple_test(NULL, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "unauthorized_client"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_auth_code_uri_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=client_error&redirect_uri=..%%2f..%%2ftest-oidc.html?param=invalid&state=xyz&scope=%s", SERVER_URI, RESPONSE_TYPE, SCOPE_LIST); int res = run_simple_test(NULL, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "unauthorized_client"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_auth_code_scope_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=client1_id&redirect_uri=..%%2f..%%2ftest-oidc.html?param=client1_cb1&state=xyzabcd&scope=scope4", SERVER_URI, RESPONSE_TYPE); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "invalid_scope"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_auth_code_scope_empty) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=client1_id&redirect_uri=..%%2f..%%2ftest-oidc.html?param=client1_cb1&state=xyzabcd", SERVER_URI, RESPONSE_TYPE); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "invalid_scope"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_auth_code_ok_redirect_cb_with_code) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=client1_id&redirect_uri=..%%2f..%%2ftest-oidc.html?param=client1_cb1&state=xyzabcd&nonce=noncexyzabcd1234&scope=%s", SERVER_URI, RESPONSE_TYPE, SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "code="); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_auth_code_ok_redirect_cb_with_code_post) { struct _u_response resp; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/oidc/auth", SERVER_URI); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); u_map_put(user_req.map_post_body, "response_type", RESPONSE_TYPE); u_map_put(user_req.map_post_body, "client_id", CLIENT); u_map_put(user_req.map_post_body, "redirect_uri", "../../test-oauth2.html?param=client1_cb1"); u_map_put(user_req.map_post_body, "state", "xyzabcd"); u_map_put(user_req.map_post_body, "state", "xyzabcd"); u_map_put(user_req.map_post_body, "nonce", "nonce1234"); u_map_put(user_req.map_post_body, "scope", SCOPE_LIST); u_map_put(user_req.map_post_body, "g_continue", "true"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); ulfius_clean_response(&resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc auth_code"); tc_core = tcase_create("test_oidc_auth_code"); tcase_add_test(tc_core, test_oidc_auth_invalid_response_type); tcase_add_test(tc_core, test_oidc_auth_code_ok_redirect_login); tcase_add_test(tc_core, test_oidc_auth_code_state_ok); tcase_add_test(tc_core, test_oidc_auth_code_client_invalid); tcase_add_test(tc_core, test_oidc_auth_code_uri_invalid); tcase_add_test(tc_core, test_oidc_auth_code_scope_invalid); tcase_add_test(tc_core, test_oidc_auth_code_scope_empty); tcase_add_test(tc_core, test_oidc_auth_code_ok_redirect_cb_with_code); tcase_add_test(tc_core, test_oidc_auth_code_ok_redirect_cb_with_code_post); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, register_req; struct _u_response auth_resp, scope_resp; json_t * j_body, * j_register; int res, do_test = 0, i; char * url; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_request(&scope_req); ulfius_init_request(®ister_req); ulfius_init_response(&scope_resp); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "user1" #define USER_PASSWORD "password" #define PLUGIN_NAME "oidc_iss_id" #define SCOPE_LIST "openid" #define CLIENT_ID "client3_id" #define CLIENT_SECRET "password" #define CLIENT_REDIRECT_URI "../../test-oauth2.html?param=client3" #define CLIENT_REDIRECT_URI_ENCODED "..%2F..%2Ftest-oauth2.html%3Fparam%3Dclient3" #define RESPONSE_TYPE_CODE "code" #define RESPONSE_TYPE_CODE_ID_TOKEN "code+id_token" #define ISS "https://glewlwyd.tld" #define ISS_ESCAPED "https%3A%2F%2Fglewlwyd.tld" struct _u_request admin_req; struct _u_request user_req; START_TEST(test_oidc_auth_iss_id_add_plugin) { json_t * j_param = json_pack("{sssssss{sssosssssssisisisososososososososo}}", "module", "oidc", "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", ISS, "oauth-as-iss-id", json_true(), "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_NAME, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-code-revoke-replayed", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_auth_iss_id_add_plugin_no) { json_t * j_param = json_pack("{sssssss{sssosssssssisisisososososososososo}}", "module", "oidc", "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", ISS, "oauth-as-iss-id", json_false(), "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_NAME, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-code-revoke-replayed", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_auth_iss_id_delete_plugin) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_auth_iss_id_code) { struct _u_request req; struct _u_response resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE_CODE "&nonce=nonce1234&client_id=" CLIENT_ID "&redirect_uri=" CLIENT_REDIRECT_URI_ENCODED "&scope=" SCOPE_LIST "&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "iss=" ISS_ESCAPED), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_auth_iss_id_code_id_token) { struct _u_request req; struct _u_response resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE_CODE_ID_TOKEN "&nonce=nonce1234&client_id=" CLIENT_ID "&redirect_uri=" CLIENT_REDIRECT_URI_ENCODED "&scope=" SCOPE_LIST "&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "iss=" ISS_ESCAPED), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_auth_iss_id_response_type_error) { struct _u_request req; struct _u_response resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" "error" "&nonce=nonce1234&client_id=" CLIENT_ID "&redirect_uri=" CLIENT_REDIRECT_URI_ENCODED "&scope=" SCOPE_LIST "&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "iss=" ISS_ESCAPED), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_auth_iss_id_client_invalid) { struct _u_request req; struct _u_response resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE_CODE "&nonce=nonce1234&client_id=" "error" "&redirect_uri=" CLIENT_REDIRECT_URI_ENCODED "&scope=" SCOPE_LIST "&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "iss=" ISS_ESCAPED), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_auth_iss_id_code_iss_no) { struct _u_request req; struct _u_response resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE_CODE "&nonce=nonce1234&client_id=" CLIENT_ID "&redirect_uri=" CLIENT_REDIRECT_URI_ENCODED "&scope=" SCOPE_LIST "&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "iss=" ISS_ESCAPED), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc auth iss id"); tc_core = tcase_create("test_oidc_auth_iss_id"); tcase_add_test(tc_core, test_oidc_auth_iss_id_add_plugin); tcase_add_test(tc_core, test_oidc_auth_iss_id_code); tcase_add_test(tc_core, test_oidc_auth_iss_id_code_id_token); tcase_add_test(tc_core, test_oidc_auth_iss_id_response_type_error); tcase_add_test(tc_core, test_oidc_auth_iss_id_client_invalid); tcase_add_test(tc_core, test_oidc_auth_iss_id_delete_plugin); tcase_add_test(tc_core, test_oidc_auth_iss_id_add_plugin_no); tcase_add_test(tc_core, test_oidc_auth_iss_id_code_iss_no); tcase_add_test(tc_core, test_oidc_auth_iss_id_delete_plugin); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); ulfius_init_request(&user_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "User %s authenticated", USER_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(user_req.map_header, "Cookie", cookie); o_free(cookie); } else { do_test = 0; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication user"); do_test = 0; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); } if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } run_simple_test(&user_req, "DELETE", SERVER_URI "/auth/", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL); ulfius_clean_request(&admin_req); ulfius_clean_request(&user_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_ciba.c000066400000000000000000004046361415646314000203340ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "user1" #define USER_PASSWORD "password" #define PLUGIN_MODULE "oidc" #define PLUGIN_NAME "oidc_ciba" #define PLUGIN_ISS "https://glewlwyd.tld" #define PLUGIN_JWT_TYPE_RSA "rsa" #define PLUGIN_JWT_KEY_SIZE "256" #define PLUGIN_CIBA_DEFAULT_EXPIRATION 600 #define PLUGIN_CIBA_MAXIMUM_EXPIRATION 1200 #define SCOPE_LIST "scope1 openid" #define SCOPE_1 "scope1" #define SCOPE_2 "openid" #define CLIENT_ID_POLL "client_ciba_poll" #define CLIENT_ID_PING "client_ciba_ping" #define CLIENT_ID_PUSH "client_ciba_push" #define CLIENT_SECRET "passwordciba" #define CLIENT_NOTIFICATION_ENDPOINT_PING "https://localhost:2422/cb" #define CLIENT_NOTIFICATION_ENDPOINT_PUSH "https://localhost:2423/cb" #define CLIENT_REDIRECT "https://client.tld" #define CLIENT_PUBKEY_PARAM "pubkey" #define CIBA_CLIENT_NOTIFICATION_TOKEN "ZBMDEshXMWMv8KUbBSUnbRgEYpvM8LyA" #define CIBA_BINDING_MESSAGE "CIBA message grut" #define CB_KEY "cert/server.key" #define CB_CRT "cert/server.crt" #define CIBA_SMTP_PORT 2525 #define CIBA_SMTP_FROM "glewlwyd@example.com" #define CIBA_SMTP_SUBJECT "CIBA message" #define CIBA_SMTP_BODY_PATTERN "Connect " const char pubkey_1_pem[] = "-----BEGIN PUBLIC KEY-----\n"\ "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxaF1egmmQ+0/AAEcv/Jd\n"\ "TCBxi7A05VvDvRgaYzOEWqJF3qv3b6ifD4E4C9+cir6tIyCOJ2WXE4TV9fCWBQjI\n"\ "omacdQjdEwMAEWYYVEz4PPpWZDEeCKKgsz5DnLNyPznMiom82vDREvSqG+yxAEbk\n"\ "W1r+MtcFRd4YSZyJYDSVJ+t2VL7Nt/Gxao/MC8rJP/QHzSbWjwruSwW3JM+75wds\n"\ "JUbHhyIZ5lQ2u06d68ZA9Q4T60i+zE49yoLd8+PIjcrhM3kYIJkcTKyjWqZEV+we\n"\ "16eroS8PX/YN5tw4Od+fKb6I0XILhiyCqKlOg3FHdY8iAK75atI6TvX73ei+zSF3\n"\ "UyEFjDTcnhe6mPHL9REIVmb0+n70feP/SFQqaFRaamxBOkBPsVa9NP4zLJiRn71j\n"\ "ff7opHcT7zaR19cigQxsYmd+rZnhiH+UXel8P7kVTM7OCc7LRSJPG6xY4FtpWE2q\n"\ "abrGaUfBCMGtrH0GqjtbNCnBeBeCiijpN5XPqnGlSXpXrd3mjldlrFhiKVSlnvyp\n"\ "ekdAzA0WvpraBy351TzMsXphokchoynpdP04Pyv7DtI6SrU7sn5QZGoZtRVzNjOE\n"\ "CljVD4HZmc7Xw/kpCcb3sBtT9KOTlGd/ocn7D4Tw04jCCjmPOWhX/kKTrD4Mf+wv\n"\ "kUNxrz0Ea7PClAQf+HeZrjcCAwEAAQ==\n"\ "-----END PUBLIC KEY-----"; const char privkey_1_pem[] = "-----BEGIN RSA PRIVATE KEY-----\n"\ "MIIJKwIBAAKCAgEAxaF1egmmQ+0/AAEcv/JdTCBxi7A05VvDvRgaYzOEWqJF3qv3\n"\ "b6ifD4E4C9+cir6tIyCOJ2WXE4TV9fCWBQjIomacdQjdEwMAEWYYVEz4PPpWZDEe\n"\ "CKKgsz5DnLNyPznMiom82vDREvSqG+yxAEbkW1r+MtcFRd4YSZyJYDSVJ+t2VL7N\n"\ "t/Gxao/MC8rJP/QHzSbWjwruSwW3JM+75wdsJUbHhyIZ5lQ2u06d68ZA9Q4T60i+\n"\ "zE49yoLd8+PIjcrhM3kYIJkcTKyjWqZEV+we16eroS8PX/YN5tw4Od+fKb6I0XIL\n"\ "hiyCqKlOg3FHdY8iAK75atI6TvX73ei+zSF3UyEFjDTcnhe6mPHL9REIVmb0+n70\n"\ "feP/SFQqaFRaamxBOkBPsVa9NP4zLJiRn71jff7opHcT7zaR19cigQxsYmd+rZnh\n"\ "iH+UXel8P7kVTM7OCc7LRSJPG6xY4FtpWE2qabrGaUfBCMGtrH0GqjtbNCnBeBeC\n"\ "iijpN5XPqnGlSXpXrd3mjldlrFhiKVSlnvypekdAzA0WvpraBy351TzMsXphokch\n"\ "oynpdP04Pyv7DtI6SrU7sn5QZGoZtRVzNjOECljVD4HZmc7Xw/kpCcb3sBtT9KOT\n"\ "lGd/ocn7D4Tw04jCCjmPOWhX/kKTrD4Mf+wvkUNxrz0Ea7PClAQf+HeZrjcCAwEA\n"\ "AQKCAgEAiK7oXuT5lxUoc0pRpfdry1DM9v2BN/fFYqye42kn6r88b4qj8RfAGqsW\n"\ "JjAbRmIlwJutdVXvrNI8YIJqX0T57kflgZQFwjZa/62KuhMZxKKmyVinGvXPCujP\n"\ "PjtlkpDZBB/K++l90Axi8jk7GaPuH7jJAkRwIyg9x/t5j44OcrOI/YeRI90HBVo6\n"\ "lxVDJvYRxzNLRVccA8sKJW1+2YPqqcVQDgfpvcEUsWroEb7mAhYgdKO1QfwDBGKK\n"\ "OzmY5WghZMYlh7YdOnJT8Ef6EIz/r8sJCstPaOD2MGqygGRG4koiuiiUC9IQ87FU\n"\ "2deowoehzrM1UUnghefesFVeUOUKbr3Gh7rv5fYzijwtel2fAW29qjdhgqoCq/WQ\n"\ "fQlkT2QnJjeh2Fdr5JFmm84KgTdikOvlROVGQRZkBNCOq0Y/skSr9FDC0PAzU2kA\n"\ "0iXhwUAlWFQlud2Mx4moFwDj40RXpsGNaR6fDXv0fpS5Y1c3NeSU+yC0ycX8/BwE\n"\ "Uq63CcWocATmDrD2IdDnIbv/abjMyh18GIaJLLdx1DZ/cwQdFGgGaqigQZT/pSuu\n"\ "fqLIAXGT+wg0SdP/sSbKCV79DA/k34FC4WkP81aiMaKtbA7CJZgEdEFc/S5vXts3\n"\ "HvDiCFroLEedfInQxkFrI4gyudAZ5YLujlr3fYsJNB96B0GHnGECggEBAPQQSKxv\n"\ "01Oky+jENQwxiZcpI4a5PzLPFFCgEqIjSRamCzrCQ07e97iqhU1b8IvRwxDtX358\n"\ "pFKAq7tmwpN2QQb1T9fqUwCpeQuMwRsZwoaM7ZcTSj2FZ/2djN1ixQfzqQ21VxkM\n"\ "RbrdyExqCSJXnHMcLeiFmu81dVopV2iwDbUQv4jZe/ktPUTH4HKle48Y0v9pu22l\n"\ "D5cknAQGB1gUNfyJ0PbUxZMITrZDz4khhYgxqvJ7GluYRNv2tezV+bb5leXbSLDr\n"\ "RgTKqcl5ZjkgLm9FRNGZZAmlsCHEeB3nvCs2ePQYDuLgEkNtuu39kpLFJO6j70bj\n"\ "nvpaIAcDVpPmEE8CggEBAM9L09Grg2uSrNUGfj9pfpsMn0k5kqV0n3WjX9z5ZLkw\n"\ "LNNrs0SJjb93haO2MPNlyYhctCpPKnfHJKZWaLhFDV6xr+ubf7c3DbBJjPhlV8dU\n"\ "kgmHfIqWDPl6pzN0xC61zC4IE15LgW/JEMpq53fRWnIHdufs+105QO8YOo0CVYKY\n"\ "jqut4hVbYRBSTaeVLb1vj/yhaL0qV7orQoTrpr6Bg20nftBBa+8Md/B5l0QyiSfv\n"\ "OjKnXsjULQdQGbtypQZvu2jUasnUVUQHBgeF5W5WFj8qCGGnmehqY6QissipLoRM\n"\ "cGPaV/gJKisgcorF7sSU/QzcBUmPk377LkzZXGNUYZkCggEBALL4hfI9BmCdxhFo\n"\ "X+YTJWw9dJnEmf1uMN12pHNVILGFDVMHRUg+5LT8BkhWFSzSoxJ0nsQoLm95f3Uq\n"\ "w6BS5RhvJx+T603e+K5phumSmD0GduuD77rxavJlZ/ioBwfvu5Yb1kS95RxEqi6u\n"\ "ywft6wHWNiv+XUDwmJ+HFVvlTgfqwileIjT04argT0yC4PpsH73AEPs0QRx6chXZ\n"\ "PeVu3K/Vd/Co0kEhpGavjy5l8H+QvGSXtRpZrJUIcxu7RSTSHQOzK7jgrjWxT5Q4\n"\ "e6eEW8ioqPByZRNV9rSsV9DGMAwYI9YLFk90NLBRdPQ0MBmEi7KbcEkxfVDkafv6\n"\ "jLBj0q0CggEBAJldyYY7dczVxMcKucbinwfJq+N6E/QTt5JKYDdV0F5utQtqiEQx\n"\ "3MyGejooJkk9yn/3zlfrIElj7cqe7XU/qWeg4L3Y2wHLWnZNxF1WZT4VZMJmGg9S\n"\ "eqDtTNz2C9tfJ4P695FxHX99681GkKAGJPtuaFuo6kQLgu4iJ9eBnZA0nIGJ8VXJ\n"\ "uKNhsRBGf4PDEW1gYeRqemNDdEBxNHmHypusd9dOP7OpruccnnyXQwBnrtAhIjBF\n"\ "QldBvPgBFvUPH0GsvqE6VicxZxWTy635RRZQW8kcPfNFGxkpjsqE2OSKxTArL6BT\n"\ "733e0L+5NzD75cho1ASblA2DerriqcbXfCkCggEBAILHVNisODhO4GC8P709DqGd\n"\ "VdufLZf2Bl7AwjWyYTkpEzEfQCHHUnmOoTCn+OEvnn9lWiaCaTijtlUmos0fCfvS\n"\ "QLk9elciIOmlRk8G1EtnnzYQsTmerLoMJBgQ02hhip8GK47Y7mbZIjaPB625Dv8F\n"\ "4RHd9ZiTzXTGcNc6bldWlNNbbqw9DWS1DORPhdQEPU424qcYvHq/eklFCujWukO8\n"\ "ul3FEZYnTcth2ODSFMb0a0SCuDGkGI8BDI+/4n6+4wIlAXtc8Vt9Ko8WxJjCK/v2\n"\ "Ae9x05eknWZj0JxuyoAjPtJApp0pt25omJwZr/lY5i8T0cL6dDF5nZcA9hN//eo=\n"\ "-----END RSA PRIVATE KEY-----\n"; const char pubkey_2_pem[] = "-----BEGIN PUBLIC KEY-----\n"\ "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAosjEKF8XSrvPPGuQYUz9\n"\ "C7wLHvoJ3UAR3vpHFdJa+Z/N9STXYVh67UtCnM7RW+mbO7BuFFmT1WB2jnLnWzaT\n"\ "zeG3hC/A44imv2uU1mtOIgt5iFiF5N+XZs9opsM9jWsUW2J8uFPK1ohdnXxWkFAf\n"\ "7fTJx2fJbO7fgxjM5GIq7s1f7vbPaAaNTH8h4hnGRKuWg+n15FKjYmGUDD5SQj5d\n"\ "yMrbZx/8DbGm0XX//rQvLf3eXP2r3HFFTbWn9HcimZF6csqhmh4g4Rt5Ou2JcZlr\n"\ "krbSo8gyONelZ+kvKKJwAZG0+6Pu4+MgSHdwGe1B2jUmeQ1EAelsIWvLArCH4c/t\n"\ "3JYFdDe9fMfUcof6C3aseMaa8HW1WHhaH8erSnP5klF0NGhhRWB1lYSCWQ/hT9yg\n"\ "vlTJQ8v/2ocFbsZUi8lQ6bL9Lc4piHyMOeK/2ozm13wk9OHGfLxKYEtzs5V6oevg\n"\ "D7daJ0rtS/h21iLguqKt5rwe7Rzy3boMxVlNGFux0RSP5rzjoGQ/vkuuSiWgyygm\n"\ "TIheeOV/wCds6IDR7s2zrgBxQvsUK1isp5YipHVo4AOuPxNcxGnxLkt0DA7AOYCw\n"\ "h2jngVWmjKgzSYG0EHiiLUrXyMFp4xhyTBLSQpWZn65o8jwABqzWtodMirsZsawI\n"\ "GihzXtw00vRRJDnVpE7EQUUCAwEAAQ==\n"\ "-----END PUBLIC KEY-----\n"; const char privkey_2_pem[] = "-----BEGIN RSA PRIVATE KEY-----\n"\ "MIIJJwIBAAKCAgEAosjEKF8XSrvPPGuQYUz9C7wLHvoJ3UAR3vpHFdJa+Z/N9STX\n"\ "YVh67UtCnM7RW+mbO7BuFFmT1WB2jnLnWzaTzeG3hC/A44imv2uU1mtOIgt5iFiF\n"\ "5N+XZs9opsM9jWsUW2J8uFPK1ohdnXxWkFAf7fTJx2fJbO7fgxjM5GIq7s1f7vbP\n"\ "aAaNTH8h4hnGRKuWg+n15FKjYmGUDD5SQj5dyMrbZx/8DbGm0XX//rQvLf3eXP2r\n"\ "3HFFTbWn9HcimZF6csqhmh4g4Rt5Ou2JcZlrkrbSo8gyONelZ+kvKKJwAZG0+6Pu\n"\ "4+MgSHdwGe1B2jUmeQ1EAelsIWvLArCH4c/t3JYFdDe9fMfUcof6C3aseMaa8HW1\n"\ "WHhaH8erSnP5klF0NGhhRWB1lYSCWQ/hT9ygvlTJQ8v/2ocFbsZUi8lQ6bL9Lc4p\n"\ "iHyMOeK/2ozm13wk9OHGfLxKYEtzs5V6oevgD7daJ0rtS/h21iLguqKt5rwe7Rzy\n"\ "3boMxVlNGFux0RSP5rzjoGQ/vkuuSiWgyygmTIheeOV/wCds6IDR7s2zrgBxQvsU\n"\ "K1isp5YipHVo4AOuPxNcxGnxLkt0DA7AOYCwh2jngVWmjKgzSYG0EHiiLUrXyMFp\n"\ "4xhyTBLSQpWZn65o8jwABqzWtodMirsZsawIGihzXtw00vRRJDnVpE7EQUUCAwEA\n"\ "AQKCAgAgeAsoQk9TCML9OjdCMSkH/in5s8Xj67Ok1ib3o02iHM3n58FOGWfrjOds\n"\ "f5Qi0SW1ciuCw5tJJESBhUnAV9KDblI8e4nt2IPqCntViXG8OK21FHMgg7ifOR+W\n"\ "o7rTSlh1Id/epFgBEpIZp4NcpZ64x+q3a2wx3jOW4ot7wljt6OkJsRENnsDwUStg\n"\ "/eMeL1uGVFRHhnQwW2Y8SvH9ZJMwScYZe7e6w4SSowa8txPyCIEh6zcscStxpJor\n"\ "mNe96daSrvgs1XWZcPFtX5PapM9gJDc/k97wl5LWdxyA7mWI6pc+2iP8MIFv8uLZ\n"\ "SQmpjJBv9MVr3DNFN0r27l8sG11GIPMEg9Y01uEK4PZ5oASRNJbdddTOY9KWsYL4\n"\ "IxRV6ZU5ZGe0+EQWBeJnPfr4cyJd59fWC1hA8FUBAjWc8NimmMAaPu76xW+1Cqwo\n"\ "HgBDNzhQur+20eImvD9MZsVJDRAI8nYe+cVN4FaLEogK9iJFcyjv+R1QEofv8/+N\n"\ "qiibNZtff2mF9Y5YLrZQ3b0eNAzcY3VuQyN2iWsqjmXy4F1AP8ZUAyp2S6d3onDx\n"\ "+m4ua6EUB462gxUCxbYh34IbL+OyLX1xuBEetUd7j++7fK+8K5Dkw/RGd2K+D0m8\n"\ "sz4uPzcmxtZ9QbZQSE/JyBM8VzC5Mqy1NyJdBJcsosV62tywwQKCAQEAz/mQXsGj\n"\ "ErtO+6zr+x7YTF9PeUrATsTNtMDASGlbUIGLXcCwr5sE1lcT7zfMXS6xnP0NQnkF\n"\ "HIfgsBPEwMKSnRXNrS127TTvvxU6CzzDO5K1u7q6hzUvuDhj0iZjCSotBC4Dgg39\n"\ "G1J3N5YY4D+2jtS4yWsdIdCuUB/Sel6/1x6jfmkFSgLSxUATycX06/b+WSAFGS40\n"\ "Z5B+n8gfKF2OvuvdTi1uNr8T4EQXMZRQG8hgn7WRcrHuKmanI/jsWs5SiOPArKf7\n"\ "E+m7glguu9D5dqID5KI7kCUGGf+IhcRvcWoF2dZDTFKiyzfWNidVWj7ua5H+wGbl\n"\ "SYDQ+1UO2sFxKwKCAQEAyF/CL5HFZbUmA/igc8FN46vD1OQWlVqFEXk8B5kgK6hX\n"\ "EiA9lRo0/UeG6/o61vgjtI1F1CKjHPqNUaiCmTXVBXqWWxXiyRqcCBGpZPx2zup0\n"\ "CxcCGQUKYPqYBqoazipUtRIasyr8qGoFwJ23am+HLfK4hCa/+JZIn5cmWxdtZbwN\n"\ "I1raal596coHl23fnMpmnnHOf9RVSgLgcrtzZlou+zQq5QTymBoB0AWiCTt6aBB9\n"\ "dcr1M89F32O6WU9lPHdRDXTiZKFfofP+9+5NSKk8EV5/NKbc4q1czl6OQNB1iaij\n"\ "GdW1HbRK6OMn/QPSPrq/4b0vx1378dw1SCynnsp/TwKCAQAjzJCWTvyUD9vzpHtm\n"\ "WZPf6ZQTi9N4e5XxJDC1BjWqsPHdbHq9b5CwmIKc7dzmuU/ndwOeSpifpZ/+jxiS\n"\ "GawFECi8Q5QyFqeiG7RHFOmCSqmvq9/JwFT7f/FmzibEABeJnaYK7/9+gX51+gdE\n"\ "KpNxGJrKSP7VwEcREbk4STD6hZKdAtTfPYcsXvBJDiMvru9vRfy6reJlZVD2jgsW\n"\ "9qvIn28TsuxCzjdMpkvbw+zaK26+bxGfTZNyhwMERSfiJZu9Zn7W+X9VmSkmsakx\n"\ "uTIHwrGq3GYmVMktFnhYlkycmCRzr9cbvym4k17zKWgexbBiodnZIp0bPuo6KRUK\n"\ "gLvLAoIBAGrV29S/NW/AQU+2vd5aP+xXRef54SyrAe5KJL6sX2HHtP0eIZehTIWL\n"\ "IJ4rYjoCPg9jj8rG2Q93a+gRH+kOsdDlBsv0BGJThMQsnpQQpEw457yN/PlYHauN\n"\ "kYdkJTyth1KwH3pOPj6RoRWNQiFG692M3+LeQlcJ0hj9X18MQ7ENrjeelnxGe34u\n"\ "0RNlaufPZx2t8ntnvD3lAMVLuwDkrs4Th5dqpuqAW10N09J2WxKnUC2BFHIWXtv3\n"\ "8YDy+zhdKeMx4g/jlvjj/ps0/RHz4eok51Asc/OMmcIS2mgmfbTzLFt2/cWjvpkj\n"\ "nMujwPLfYbW7yIDVVKMlVWdxH8Jjl7MCggEAbNq+mJjepyGY5mXTlzAcBFY5HqlD\n"\ "x+Ssapo1waswzRhnXKB15zqU+LQfotBBp7pKxWCytwgOmV2toZf/R0Sqinohh52D\n"\ "0mCvTYldTB5tUpKpnhEGZuOZ24BTtrqNAw2X5NRKTflttiVJgfrMCSDIR8NNX4aw\n"\ "GB8iUNZLyn4e5Zjfa7cLCwn7ngINpU1TaCY9gbHSsYcVcpX7ro6Vr93ox6cRtapB\n"\ "WElzAPXDF0YKMiKzA4fTqVtbamVHz64k7xgWVwdWXDbdqOWOtI0weeeHMy2Y9Ktz\n"\ "7eXh0rK0fG1c35qJ+V61oXdyfapmI7uGH2R5J+Q2LDJw/3AMGb6oCsPfdQ==\n"\ "-----END RSA PRIVATE KEY-----\n"; struct _u_request admin_req; struct _u_request user_req; int counter = 0; void add_client_ciba(const char * client_id, const char * backchannel_token_delivery_mode, const char * backchannel_client_notification_endpoint, const char * backchannel_authentication_request_signing_alg, const char * backchannel_authentication_request_enc_alg) { json_t * j_parameters = json_pack("{sssos[s]sos[ssss]sssss[sss]ssss*ss*ss*}", "client_id", client_id, "enabled", json_true(), "redirect_uri", CLIENT_REDIRECT, "confidential", json_true(), "token_endpoint_auth_method", "client_secret_post", "client_secret_basic", "client_secret_jwt", "private_key_jwt", "client_secret", CLIENT_SECRET, CLIENT_PUBKEY_PARAM, pubkey_1_pem, "authorization_type", "urn:openid:params:grant-type:ciba", "token", "id_token", "backchannel_token_delivery_mode", backchannel_token_delivery_mode, "backchannel_client_notification_endpoint", backchannel_client_notification_endpoint, "backchannel_authentication_request_signing_alg", backchannel_authentication_request_signing_alg, "backchannel_authentication_request_enc_alg", backchannel_authentication_request_enc_alg); ck_assert_ptr_ne(NULL, j_parameters); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } void client_grant_scopes(const char * scopes) { json_t * j_grant = json_pack("{ss}", "scope", scopes); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID_POLL, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID_PING, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID_PUSH, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_grant); } char * get_id_token(const char * client_id) { struct _u_request req; struct _u_response resp; char * id_token = NULL; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=token%20id_token&g_continue&client_id=", U_OPT_HTTP_URL_APPEND, client_id, U_OPT_HTTP_URL_APPEND, "&redirect_uri=" CLIENT_REDIRECT "&nonce=nonce1234&scope=openid", U_OPT_HTTP_VERB, "GET", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token=")); ck_assert_ptr_ne(NULL, id_token); ulfius_clean_request(&req); ulfius_clean_response(&resp); return id_token; } static int callback_client_notification_endpoint_ping(const struct _u_request * request, struct _u_response * response, void * user_data) { counter++; json_t * j_body = ulfius_get_json_body_request(request, NULL); ck_assert_str_eq("Bearer " CIBA_CLIENT_NOTIFICATION_TOKEN, u_map_get(request->map_header, "Authorization")); ck_assert_str_eq(user_data, json_string_value(json_object_get(j_body, "auth_req_id"))); json_decref(j_body); return U_CALLBACK_CONTINUE; } static int callback_client_notification_endpoint_push(const struct _u_request * request, struct _u_response * response, void * user_data) { jwt_t * jwt; counter++; json_t * j_body = ulfius_get_json_body_request(request, NULL); ck_assert_str_eq("Bearer " CIBA_CLIENT_NOTIFICATION_TOKEN, u_map_get(request->map_header, "Authorization")); ck_assert_str_eq(user_data, json_string_value(json_object_get(j_body, "auth_req_id"))); ck_assert_ptr_ne(NULL, json_string_value(json_object_get(j_body, "access_token"))); ck_assert_str_eq("bearer", json_string_value(json_object_get(j_body, "token_type"))); ck_assert_ptr_ne(NULL, json_string_value(json_object_get(j_body, "refresh_token"))); ck_assert_ptr_ne(NULL, json_string_value(json_object_get(j_body, "id_token"))); ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(json_string_value(json_object_get(j_body, "access_token")), R_PARSE_NONE, 0)); ck_assert_str_eq(SCOPE_LIST, r_jwt_get_claim_str_value(jwt, "scope")); r_jwt_free(jwt); json_decref(j_body); return U_CALLBACK_CONTINUE; } static int callback_client_notification_endpoint_push_reduced_scope(const struct _u_request * request, struct _u_response * response, void * user_data) { jwt_t * jwt; counter++; json_t * j_body = ulfius_get_json_body_request(request, NULL); ck_assert_str_eq("Bearer " CIBA_CLIENT_NOTIFICATION_TOKEN, u_map_get(request->map_header, "Authorization")); ck_assert_str_eq(user_data, json_string_value(json_object_get(j_body, "auth_req_id"))); ck_assert_ptr_ne(NULL, json_string_value(json_object_get(j_body, "access_token"))); ck_assert_str_eq("bearer", json_string_value(json_object_get(j_body, "token_type"))); ck_assert_ptr_ne(NULL, json_string_value(json_object_get(j_body, "refresh_token"))); ck_assert_ptr_ne(NULL, json_string_value(json_object_get(j_body, "id_token"))); ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(json_string_value(json_object_get(j_body, "access_token")), R_PARSE_NONE, 0)); ck_assert_str_eq(SCOPE_2, r_jwt_get_claim_str_value(jwt, "scope")); r_jwt_free(jwt); json_decref(j_body); return U_CALLBACK_CONTINUE; } START_TEST(test_oidc_ciba_add_plugin_user_code_no_email_no) { json_t * j_param = json_pack("{sssssss{sssssssssssisisisososososososososossso sosisisosososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE_RSA, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", privkey_2_pem, "cert", pubkey_2_pem, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-device-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "request-parameter-allow-encrypted", json_true(), "oauth-ciba-allowed", json_true(), "oauth-ciba-default-expiry", PLUGIN_CIBA_DEFAULT_EXPIRATION, "oauth-ciba-maximum-expiry", PLUGIN_CIBA_MAXIMUM_EXPIRATION, "oauth-ciba-mode-poll-allowed", json_true(), "oauth-ciba-mode-ping-allowed", json_true(), "oauth-ciba-mode-push-allowed", json_true(), "oauth-ciba-allow-https-non-secure", json_true(), "oauth-ciba-user-code-allowed", json_false(), "oauth-ciba-email-allowed", json_false()); ck_assert_ptr_ne(NULL, j_param); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_ciba_add_plugin_user_code_email_no) { json_t * j_param = json_pack("{sssssss{sssssssssssisisisososososososososossso sosisisososososossso}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE_RSA, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", privkey_2_pem, "cert", pubkey_2_pem, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-device-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "request-parameter-allow-encrypted", json_true(), "oauth-ciba-allowed", json_true(), "oauth-ciba-default-expiry", PLUGIN_CIBA_DEFAULT_EXPIRATION, "oauth-ciba-maximum-expiry", PLUGIN_CIBA_MAXIMUM_EXPIRATION, "oauth-ciba-mode-poll-allowed", json_true(), "oauth-ciba-mode-ping-allowed", json_true(), "oauth-ciba-mode-push-allowed", json_true(), "oauth-ciba-allow-https-non-secure", json_true(), "oauth-ciba-user-code-allowed", json_true(), "oauth-ciba-user-code-property", "username", "oauth-ciba-email-allowed", json_false()); ck_assert_ptr_ne(NULL, j_param); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_ciba_add_plugin_user_code_no_email) { json_t * j_param = json_pack("{sssssss{sssssssssssisisisososososososososossso sosisisososososo sosssisosssss{s{sossss}}}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE_RSA, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", privkey_2_pem, "cert", pubkey_2_pem, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-device-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "request-parameter-allow-encrypted", json_true(), "oauth-ciba-allowed", json_true(), "oauth-ciba-default-expiry", PLUGIN_CIBA_DEFAULT_EXPIRATION, "oauth-ciba-maximum-expiry", PLUGIN_CIBA_MAXIMUM_EXPIRATION, "oauth-ciba-mode-poll-allowed", json_true(), "oauth-ciba-mode-ping-allowed", json_true(), "oauth-ciba-mode-push-allowed", json_true(), "oauth-ciba-allow-https-non-secure", json_true(), "oauth-ciba-user-code-allowed", json_false(), "oauth-ciba-email-allowed", json_true(), "oauth-ciba-email-host", "localhost", "oauth-ciba-email-port", CIBA_SMTP_PORT, "oauth-ciba-email-use-tls", json_false(), "oauth-ciba-email-from", CIBA_SMTP_FROM, "oauth-ciba-email-user-lang-property", "lang", "oauth-ciba-email-templates", "en", "oauth-ciba-email-defaultLang", json_true(), "oauth-ciba-email-subject", CIBA_SMTP_SUBJECT, "oauth-ciba-email-body-pattern", CIBA_SMTP_BODY_PATTERN "{CONNECT_URL}"); ck_assert_ptr_ne(NULL, j_param); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_ciba_add_plugin_expires_soon) { json_t * j_param = json_pack("{sssssss{sssssssssssisisisososososososososossso sosisisosososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE_RSA, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", privkey_2_pem, "cert", pubkey_2_pem, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-device-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "request-parameter-allow-encrypted", json_true(), "oauth-ciba-allowed", json_true(), "oauth-ciba-default-expiry", 1, "oauth-ciba-maximum-expiry", PLUGIN_CIBA_MAXIMUM_EXPIRATION, "oauth-ciba-mode-poll-allowed", json_true(), "oauth-ciba-mode-ping-allowed", json_true(), "oauth-ciba-mode-push-allowed", json_true(), "oauth-ciba-allow-https-non-secure", json_true(), "oauth-ciba-user-code-allowed", json_false(), "oauth-ciba-email-allowed", json_false()); ck_assert_ptr_ne(NULL, j_param); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_ciba_delete_plugin) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_ciba_add_client_poll) { add_client_ciba(CLIENT_ID_POLL, "poll", NULL, NULL, NULL); } END_TEST START_TEST(test_oidc_ciba_add_client_ping) { add_client_ciba(CLIENT_ID_PING, "ping", CLIENT_NOTIFICATION_ENDPOINT_PING, NULL, NULL); } END_TEST START_TEST(test_oidc_ciba_add_client_push) { add_client_ciba(CLIENT_ID_PUSH, "push", CLIENT_NOTIFICATION_ENDPOINT_PUSH, NULL, NULL); } END_TEST START_TEST(test_oidc_ciba_delete_client_poll) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_ID_POLL, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_ciba_delete_client_ping) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_ID_PING, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_ciba_delete_client_push) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_ID_PUSH, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_ciba_client_grant_scopes) { client_grant_scopes(SCOPE_LIST); } END_TEST START_TEST(test_oidc_ciba_client_grant_none) { client_grant_scopes(""); } END_TEST START_TEST(test_oidc_ciba_request_client_notification_token_missing) { struct _u_request req; struct _u_response resp; json_t * j_body; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("invalid_request", json_string_value(json_object_get(j_body, "error"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_request_client_notification_token_invalid_chars) { struct _u_request req; struct _u_response resp; json_t * j_body; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN"~;", U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("invalid_request", json_string_value(json_object_get(j_body, "error"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_request_client_notification_token_invalid_small_length) { struct _u_request req; struct _u_response resp; json_t * j_body; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", "small" , U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("invalid_request", json_string_value(json_object_get(j_body, "error"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_request_client_notification_token_invalid_large_length) { struct _u_request req; struct _u_response resp; json_t * j_body; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN CIBA_CLIENT_NOTIFICATION_TOKEN "0", // Last char "0" so length is 1025 U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("invalid_request", json_string_value(json_object_get(j_body, "error"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_request_login_hint_missing) { struct _u_request req; struct _u_response resp; json_t * j_body; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("invalid_request", json_string_value(json_object_get(j_body, "error"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_request_login_hint_invalid) { struct _u_request req; struct _u_response resp; json_t * j_body; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\"error\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("invalid_request", json_string_value(json_object_get(j_body, "error"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_request_login_hint_error_key) { struct _u_request req; struct _u_response resp; json_t * j_body; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"error\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("invalid_request", json_string_value(json_object_get(j_body, "error"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_request_login_hint_error_format) { struct _u_request req; struct _u_response resp; json_t * j_body; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "error", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("invalid_request", json_string_value(json_object_get(j_body, "error"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_request_login_hint_ok) { struct _u_request req; struct _u_response resp; json_t * j_body; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, json_object_get(j_body, "auth_req_id")); ck_assert_int_eq(PLUGIN_CIBA_DEFAULT_EXPIRATION, json_integer_value(json_object_get(j_body, "expires_in"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_request_id_token_hint_invalid) { struct _u_request req; struct _u_response resp; json_t * j_body; char * id_token = get_id_token(CLIENT_ID_PING); id_token[o_strlen(id_token) - 4] = '\0'; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "id_token_hint", id_token, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("invalid_request", json_string_value(json_object_get(j_body, "error"))); o_free(id_token); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_request_id_token_hint_ok) { struct _u_request req; struct _u_response resp; json_t * j_body; char * id_token = get_id_token(CLIENT_ID_PING); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "id_token_hint", id_token, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, json_object_get(j_body, "auth_req_id")); ck_assert_int_eq(PLUGIN_CIBA_DEFAULT_EXPIRATION, json_integer_value(json_object_get(j_body, "expires_in"))); o_free(id_token); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_request_login_hint_token_invalid_signature) { struct _u_request req; struct _u_response resp; json_t * j_body; jwt_t * jwt; jwk_t * jwk; char * token; ck_assert_int_eq(RHN_OK, r_jwt_init(&jwt)); ck_assert_int_eq(RHN_OK, r_jwt_set_full_claims_json_str(jwt, "{\"username\":\""USER_USERNAME"\"}")); ck_assert_int_eq(RHN_OK, r_jwt_set_sign_alg(jwt, R_JWA_ALG_HS256)); ck_assert_ptr_ne(NULL, jwk = r_jwk_quick_import(R_IMPORT_PASSWORD, "error")); ck_assert_ptr_ne(NULL, token = r_jwt_serialize_signed(jwt, jwk, 0)); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint_token", token, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("invalid_request", json_string_value(json_object_get(j_body, "error"))); o_free(token); r_jwt_free(jwt); r_jwk_free(jwk); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_request_login_hint_token_ok) { struct _u_request req; struct _u_response resp; json_t * j_body; jwt_t * jwt; jwk_t * jwk; char * token; ck_assert_int_eq(RHN_OK, r_jwt_init(&jwt)); ck_assert_int_eq(RHN_OK, r_jwt_set_full_claims_json_str(jwt, "{\"username\":\""USER_USERNAME"\"}")); ck_assert_int_eq(RHN_OK, r_jwt_set_sign_alg(jwt, R_JWA_ALG_HS256)); ck_assert_ptr_ne(NULL, jwk = r_jwk_quick_import(R_IMPORT_PASSWORD, CLIENT_SECRET)); ck_assert_ptr_ne(NULL, token = r_jwt_serialize_signed(jwt, jwk, 0)); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint_token", token, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, json_object_get(j_body, "auth_req_id")); ck_assert_int_eq(PLUGIN_CIBA_DEFAULT_EXPIRATION, json_integer_value(json_object_get(j_body, "expires_in"))); o_free(token); r_jwt_free(jwt); r_jwk_free(jwk); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_request_login_hint_too_much) { struct _u_request req; struct _u_response resp; json_t * j_body; jwt_t * jwt; jwk_t * jwk; char * token; ck_assert_int_eq(RHN_OK, r_jwt_init(&jwt)); ck_assert_int_eq(RHN_OK, r_jwt_set_full_claims_json_str(jwt, "{\"username\":\""USER_USERNAME"\"}")); ck_assert_int_eq(RHN_OK, r_jwt_set_sign_alg(jwt, R_JWA_ALG_HS256)); ck_assert_ptr_ne(NULL, jwk = r_jwk_quick_import(R_IMPORT_PASSWORD, "error")); ck_assert_ptr_ne(NULL, token = r_jwt_serialize_signed(jwt, jwk, 0)); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint_token", token, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("invalid_request", json_string_value(json_object_get(j_body, "error"))); o_free(token); r_jwt_free(jwt); r_jwk_free(jwk); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_signed_request_login_hint_invalid_signature) { struct _u_request req; struct _u_response resp; json_t * j_body; jwt_t * jwt; jwk_t * jwk; char * token; time_t now; int rnd; char jti[12] = {0}; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); snprintf(jti, 11, "jti_%06d", rnd); time(&now); ck_assert_int_eq(RHN_OK, r_jwt_init(&jwt)); ck_assert_ptr_ne(NULL, jwk = r_jwk_quick_import(R_IMPORT_PASSWORD, "error")); ck_assert_int_eq(RHN_OK, r_jwt_set_properties(jwt, RHN_OPT_CLAIM_STR_VALUE, "aud", PLUGIN_ISS, RHN_OPT_CLAIM_STR_VALUE, "iss", CLIENT_ID_PING, RHN_OPT_CLAIM_INT_VALUE, "exp", now+60, RHN_OPT_CLAIM_INT_VALUE, "iat", now, RHN_OPT_CLAIM_INT_VALUE, "nbf", now, RHN_OPT_CLAIM_STR_VALUE, "jti", jti, RHN_OPT_CLAIM_STR_VALUE, "scope", SCOPE_LIST, RHN_OPT_CLAIM_STR_VALUE, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, RHN_OPT_CLAIM_STR_VALUE, "login_hint", "{\"username\":\""USER_USERNAME"\"}", RHN_OPT_SIG_ALG, R_JWA_ALG_HS256, RHN_OPT_SIGN_KEY_JWK, jwk, RHN_OPT_NONE)); ck_assert_ptr_ne(NULL, token = r_jwt_serialize_signed(jwt, jwk, 0)); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_POST_BODY_PARAMETER, "request", token, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(401, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("invalid_client", json_string_value(json_object_get(j_body, "error"))); o_free(token); r_jwt_free(jwt); r_jwk_free(jwk); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_signed_request_login_hint_replay_jti) { struct _u_request req; struct _u_response resp; json_t * j_body; jwt_t * jwt; jwk_t * jwk; char * token; time_t now; int rnd; char jti[12] = {0}; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); snprintf(jti, 11, "jti_%06d", rnd); time(&now); ck_assert_int_eq(RHN_OK, r_jwt_init(&jwt)); ck_assert_ptr_ne(NULL, jwk = r_jwk_quick_import(R_IMPORT_PASSWORD, CLIENT_SECRET)); ck_assert_int_eq(RHN_OK, r_jwt_set_properties(jwt, RHN_OPT_CLAIM_STR_VALUE, "aud", PLUGIN_ISS, RHN_OPT_CLAIM_STR_VALUE, "iss", CLIENT_ID_PING, RHN_OPT_CLAIM_INT_VALUE, "exp", now+60, RHN_OPT_CLAIM_INT_VALUE, "iat", now, RHN_OPT_CLAIM_INT_VALUE, "nbf", now, RHN_OPT_CLAIM_STR_VALUE, "jti", jti, RHN_OPT_CLAIM_STR_VALUE, "scope", SCOPE_LIST, RHN_OPT_CLAIM_STR_VALUE, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, RHN_OPT_CLAIM_STR_VALUE, "login_hint", "{\"username\":\""USER_USERNAME"\"}", RHN_OPT_SIG_ALG, R_JWA_ALG_HS256, RHN_OPT_SIGN_KEY_JWK, jwk, RHN_OPT_NONE)); ck_assert_ptr_ne(NULL, token = r_jwt_serialize_signed(jwt, jwk, 0)); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_POST_BODY_PARAMETER, "request", token, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(401, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("invalid_client", json_string_value(json_object_get(j_body, "error"))); o_free(token); r_jwt_free(jwt); r_jwk_free(jwk); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_signed_request_login_hint_invalid_aud) { struct _u_request req; struct _u_response resp; json_t * j_body; jwt_t * jwt; jwk_t * jwk; char * token; time_t now; int rnd; char jti[12] = {0}; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); snprintf(jti, 11, "jti_%06d", rnd); time(&now); ck_assert_int_eq(RHN_OK, r_jwt_init(&jwt)); ck_assert_ptr_ne(NULL, jwk = r_jwk_quick_import(R_IMPORT_PASSWORD, CLIENT_SECRET)); ck_assert_int_eq(RHN_OK, r_jwt_set_properties(jwt, RHN_OPT_CLAIM_STR_VALUE, "aud", "error", RHN_OPT_CLAIM_STR_VALUE, "iss", CLIENT_ID_PING, RHN_OPT_CLAIM_INT_VALUE, "exp", now+60, RHN_OPT_CLAIM_INT_VALUE, "iat", now, RHN_OPT_CLAIM_INT_VALUE, "nbf", now, RHN_OPT_CLAIM_STR_VALUE, "jti", jti, RHN_OPT_CLAIM_STR_VALUE, "scope", SCOPE_LIST, RHN_OPT_CLAIM_STR_VALUE, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, RHN_OPT_CLAIM_STR_VALUE, "login_hint", "{\"username\":\""USER_USERNAME"\"}", RHN_OPT_SIG_ALG, R_JWA_ALG_HS256, RHN_OPT_SIGN_KEY_JWK, jwk, RHN_OPT_NONE)); ck_assert_ptr_ne(NULL, token = r_jwt_serialize_signed(jwt, jwk, 0)); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_POST_BODY_PARAMETER, "request", token, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(401, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("invalid_request", json_string_value(json_object_get(j_body, "error"))); o_free(token); r_jwt_free(jwt); r_jwk_free(jwk); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_signed_request_login_hint_invalid_iss) { struct _u_request req; struct _u_response resp; json_t * j_body; jwt_t * jwt; jwk_t * jwk; char * token; time_t now; int rnd; char jti[12] = {0}; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); snprintf(jti, 11, "jti_%06d", rnd); time(&now); ck_assert_int_eq(RHN_OK, r_jwt_init(&jwt)); ck_assert_ptr_ne(NULL, jwk = r_jwk_quick_import(R_IMPORT_PASSWORD, CLIENT_SECRET)); ck_assert_int_eq(RHN_OK, r_jwt_set_properties(jwt, RHN_OPT_CLAIM_STR_VALUE, "aud", PLUGIN_ISS, RHN_OPT_CLAIM_STR_VALUE, "iss", "error", RHN_OPT_CLAIM_INT_VALUE, "exp", now+60, RHN_OPT_CLAIM_INT_VALUE, "iat", now, RHN_OPT_CLAIM_INT_VALUE, "nbf", now, RHN_OPT_CLAIM_STR_VALUE, "jti", jti, RHN_OPT_CLAIM_STR_VALUE, "scope", SCOPE_LIST, RHN_OPT_CLAIM_STR_VALUE, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, RHN_OPT_CLAIM_STR_VALUE, "login_hint", "{\"username\":\""USER_USERNAME"\"}", RHN_OPT_SIG_ALG, R_JWA_ALG_HS256, RHN_OPT_SIGN_KEY_JWK, jwk, RHN_OPT_NONE)); ck_assert_ptr_ne(NULL, token = r_jwt_serialize_signed(jwt, jwk, 0)); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_POST_BODY_PARAMETER, "request", token, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(401, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("invalid_client", json_string_value(json_object_get(j_body, "error"))); o_free(token); r_jwt_free(jwt); r_jwk_free(jwk); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_signed_request_login_hint_ok) { struct _u_request req; struct _u_response resp; json_t * j_body; jwt_t * jwt; jwk_t * jwk; char * token; time_t now; int rnd; char jti[12] = {0}; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); snprintf(jti, 11, "jti_%06d", rnd); time(&now); ck_assert_int_eq(RHN_OK, r_jwt_init(&jwt)); ck_assert_ptr_ne(NULL, jwk = r_jwk_quick_import(R_IMPORT_PASSWORD, CLIENT_SECRET)); ck_assert_int_eq(RHN_OK, r_jwt_set_properties(jwt, RHN_OPT_CLAIM_STR_VALUE, "aud", PLUGIN_ISS, RHN_OPT_CLAIM_STR_VALUE, "iss", CLIENT_ID_PING, RHN_OPT_CLAIM_INT_VALUE, "exp", now+60, RHN_OPT_CLAIM_INT_VALUE, "iat", now, RHN_OPT_CLAIM_INT_VALUE, "nbf", now, RHN_OPT_CLAIM_STR_VALUE, "jti", jti, RHN_OPT_CLAIM_STR_VALUE, "scope", SCOPE_LIST, RHN_OPT_CLAIM_STR_VALUE, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, RHN_OPT_CLAIM_STR_VALUE, "login_hint", "{\"username\":\""USER_USERNAME"\"}", RHN_OPT_SIG_ALG, R_JWA_ALG_HS256, RHN_OPT_SIGN_KEY_JWK, jwk, RHN_OPT_NONE)); ck_assert_ptr_ne(NULL, token = r_jwt_serialize_signed(jwt, jwk, 0)); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_POST_BODY_PARAMETER, "request", token, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, json_object_get(j_body, "auth_req_id")); ck_assert_int_eq(PLUGIN_CIBA_DEFAULT_EXPIRATION, json_integer_value(json_object_get(j_body, "expires_in"))); o_free(token); r_jwt_free(jwt); r_jwk_free(jwk); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_client_assertion_request_login_hint_invalid_signature) { struct _u_request req; struct _u_response resp; jwt_t * jwt_request = NULL; jwk_t * jwk; char * request; int rnd; char jti[12] = {0}; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(r_jwt_init(&jwt_request), RHN_OK); ck_assert_ptr_ne(NULL, jwk = r_jwk_quick_import(R_IMPORT_PASSWORD, "error")); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys(jwt_request, jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_ID_PING); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_ID_PING); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/ciba"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+60); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_POST_BODY_PARAMETER, "client_assertion", request, U_OPT_POST_BODY_PARAMETER, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(401, resp.status); r_jwk_free(jwk); o_free(request); r_jwt_free(jwt_request); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_client_assertion_request_login_hint_invalid_aud) { struct _u_request req; struct _u_response resp; jwt_t * jwt_request = NULL; jwk_t * jwk; char * request; int rnd; char jti[12] = {0}; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(r_jwt_init(&jwt_request), RHN_OK); ck_assert_ptr_ne(NULL, jwk = r_jwk_quick_import(R_IMPORT_PASSWORD, CLIENT_SECRET)); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys(jwt_request, jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_ID_PING); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_ID_PING); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/error"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+60); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_POST_BODY_PARAMETER, "client_assertion", request, U_OPT_POST_BODY_PARAMETER, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(401, resp.status); r_jwk_free(jwk); o_free(request); r_jwt_free(jwt_request); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_client_assertion_request_login_hint_ok) { struct _u_request req; struct _u_response resp; json_t * j_body; jwt_t * jwt_request = NULL; jwk_t * jwk; char * request; int rnd; char jti[12] = {0}; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(r_jwt_init(&jwt_request), RHN_OK); ck_assert_ptr_ne(NULL, jwk = r_jwk_quick_import(R_IMPORT_PASSWORD, CLIENT_SECRET)); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys(jwt_request, jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_ID_PING); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_ID_PING); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/ciba"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+60); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_POST_BODY_PARAMETER, "client_assertion", request, U_OPT_POST_BODY_PARAMETER, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, json_object_get(j_body, "auth_req_id")); ck_assert_int_eq(PLUGIN_CIBA_DEFAULT_EXPIRATION, json_integer_value(json_object_get(j_body, "expires_in"))); r_jwk_free(jwk); o_free(request); r_jwt_free(jwt_request); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_user_list) { struct _u_request req; struct _u_response resp; json_t * j_body; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "binding_message", CIBA_BINDING_MESSAGE, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba_user_list", U_OPT_HTTP_VERB, "GET", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&user_req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_ge(json_array_size(j_body), 1); ck_assert_str_eq(CLIENT_ID_PING, json_string_value(json_object_get(json_array_get(j_body, 0), "client_id"))); ck_assert_str_eq(CIBA_BINDING_MESSAGE, json_string_value(json_object_get(json_array_get(j_body, 0), "binding_message"))); ck_assert_int_gt(json_string_length(json_object_get(json_array_get(j_body, 0), "user_req_id")), 0); ck_assert_int_eq(json_array_size(json_object_get(json_array_get(j_body, 0), "scopes")), 2); ck_assert_int_gt(json_string_length(json_object_get(json_array_get(j_body, 0), "connect_uri")), 0); ck_assert_int_gt(json_string_length(json_object_get(json_array_get(j_body, 0), "cancel_uri")), 0); json_decref(j_body); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_user_check) { struct _u_request req; struct _u_response resp; json_t * j_body; size_t nb_ciba; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba_user_list", U_OPT_HTTP_VERB, "GET", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&user_req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_ge(nb_ciba = json_array_size(j_body), 1); ulfius_clean_response(&resp); ck_assert_int_eq(run_simple_test(NULL, "GET", json_string_value(json_object_get(json_array_get(j_body, 0), "connect_uri")), NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); ck_assert_int_eq(run_simple_test(&user_req, "GET", json_string_value(json_object_get(json_array_get(j_body, 0), "connect_uri")), NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); ck_assert_int_eq(run_simple_test(&user_req, "GET", json_string_value(json_object_get(json_array_get(j_body, 0), "cancel_uri")), NULL, NULL, NULL, NULL, 302, NULL, NULL, "ciba_message=cancelled"), 1); json_decref(j_body); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba_user_list", U_OPT_HTTP_VERB, "GET", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&user_req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_eq(nb_ciba-1, json_array_size(j_body)); ulfius_clean_response(&resp); json_decref(j_body); } END_TEST START_TEST(test_oidc_ciba_user_check_accept) { struct _u_request req; struct _u_response resp; json_t * j_body; size_t nb_ciba; char * url; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_POLL, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba_user_list", U_OPT_HTTP_VERB, "GET", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&user_req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_ge(nb_ciba = json_array_size(j_body), 1); ulfius_clean_response(&resp); url = msprintf("%s&g_continue", json_string_value(json_object_get(json_array_get(j_body, 0), "connect_uri"))); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "ciba_message=complete"), 1); o_free(url); json_decref(j_body); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba_user_list", U_OPT_HTTP_VERB, "GET", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&user_req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_eq(nb_ciba-1, json_array_size(j_body)); ulfius_clean_response(&resp); json_decref(j_body); } END_TEST START_TEST(test_oidc_ciba_poll_ok) { struct _u_request req; struct _u_response resp; json_t * j_body; char * url, * auth_req_id; jwt_t * jwt; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_POLL, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, auth_req_id = o_strdup(json_string_value(json_object_get(j_body, "auth_req_id")))); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_body); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_POLL, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "urn:openid:params:grant-type:ciba", U_OPT_POST_BODY_PARAMETER, "auth_req_id", "error", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("invalid_grant", json_string_value(json_object_get(j_body, "error"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_POLL, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "urn:openid:params:grant-type:ciba", U_OPT_POST_BODY_PARAMETER, "auth_req_id", auth_req_id, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("authorization_pending", json_string_value(json_object_get(j_body, "error"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba_user_list", U_OPT_HTTP_VERB, "GET", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&user_req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ulfius_clean_request(&req); ulfius_clean_response(&resp); url = msprintf("%s&g_continue", json_string_value(json_object_get(json_array_get(j_body, 0), "connect_uri"))); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "ciba_message=complete"), 1); o_free(url); json_decref(j_body); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_POLL, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "urn:openid:params:grant-type:ciba", U_OPT_POST_BODY_PARAMETER, "auth_req_id", auth_req_id, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq(auth_req_id, json_string_value(json_object_get(j_body, "auth_req_id"))); ck_assert_ptr_ne(NULL, json_string_value(json_object_get(j_body, "access_token"))); ck_assert_str_eq("bearer", json_string_value(json_object_get(j_body, "token_type"))); ck_assert_ptr_ne(NULL, json_string_value(json_object_get(j_body, "refresh_token"))); ck_assert_ptr_ne(NULL, json_string_value(json_object_get(j_body, "id_token"))); ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(json_string_value(json_object_get(j_body, "access_token")), R_PARSE_NONE, 0)); ck_assert_str_eq(SCOPE_LIST, r_jwt_get_claim_str_value(jwt, "scope")); r_jwt_free(jwt); o_free(auth_req_id); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_ping_ok) { struct _u_request req; struct _u_response resp; json_t * j_body; char * url, * auth_req_id, * key_pem, * cert_pem; struct _u_instance instance; jwt_t * jwt; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, auth_req_id = o_strdup(json_string_value(json_object_get(j_body, "auth_req_id")))); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_body); ck_assert_int_eq(ulfius_init_instance(&instance, 2422, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "cb", 0, &callback_client_notification_endpoint_ping, auth_req_id), U_OK); counter = 0; key_pem = read_file(CB_KEY); cert_pem = read_file(CB_CRT); ck_assert_int_eq(ulfius_start_secure_framework(&instance, key_pem, cert_pem), U_OK); o_free(key_pem); o_free(cert_pem); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "urn:openid:params:grant-type:ciba", U_OPT_POST_BODY_PARAMETER, "auth_req_id", "error", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("invalid_grant", json_string_value(json_object_get(j_body, "error"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "urn:openid:params:grant-type:ciba", U_OPT_POST_BODY_PARAMETER, "auth_req_id", auth_req_id, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("authorization_pending", json_string_value(json_object_get(j_body, "error"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba_user_list", U_OPT_HTTP_VERB, "GET", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&user_req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ulfius_clean_request(&req); ulfius_clean_response(&resp); url = msprintf("%s&g_continue", json_string_value(json_object_get(json_array_get(j_body, 0), "connect_uri"))); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "ciba_message=complete"), 1); o_free(url); json_decref(j_body); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "urn:openid:params:grant-type:ciba", U_OPT_POST_BODY_PARAMETER, "auth_req_id", auth_req_id, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq(auth_req_id, json_string_value(json_object_get(j_body, "auth_req_id"))); ck_assert_ptr_ne(NULL, json_string_value(json_object_get(j_body, "access_token"))); ck_assert_str_eq("bearer", json_string_value(json_object_get(j_body, "token_type"))); ck_assert_ptr_ne(NULL, json_string_value(json_object_get(j_body, "refresh_token"))); ck_assert_ptr_ne(NULL, json_string_value(json_object_get(j_body, "id_token"))); ck_assert_int_eq(1, counter); ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(json_string_value(json_object_get(j_body, "access_token")), R_PARSE_NONE, 0)); ck_assert_str_eq(SCOPE_LIST, r_jwt_get_claim_str_value(jwt, "scope")); r_jwt_free(jwt); o_free(auth_req_id); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); } END_TEST START_TEST(test_oidc_ciba_push_ok) { struct _u_request req; struct _u_response resp; json_t * j_body; char * url, * auth_req_id, * key_pem, * cert_pem; struct _u_instance instance; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PUSH, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, auth_req_id = o_strdup(json_string_value(json_object_get(j_body, "auth_req_id")))); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_body); ck_assert_int_eq(ulfius_init_instance(&instance, 2423, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "cb", 0, &callback_client_notification_endpoint_push, auth_req_id), U_OK); counter = 0; key_pem = read_file(CB_KEY); cert_pem = read_file(CB_CRT); ck_assert_int_eq(ulfius_start_secure_framework(&instance, key_pem, cert_pem), U_OK); o_free(key_pem); o_free(cert_pem); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba_user_list", U_OPT_HTTP_VERB, "GET", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&user_req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ulfius_clean_request(&req); ulfius_clean_response(&resp); url = msprintf("%s&g_continue", json_string_value(json_object_get(json_array_get(j_body, 0), "connect_uri"))); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "ciba_message=complete"), 1); o_free(url); json_decref(j_body); ck_assert_int_eq(1, counter); o_free(auth_req_id); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); } END_TEST START_TEST(test_oidc_ciba_poll_reduced_scope_ok) { struct _u_request req; struct _u_response resp; json_t * j_body; char * url, * auth_req_id; jwt_t * jwt; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_POLL, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, auth_req_id = o_strdup(json_string_value(json_object_get(j_body, "auth_req_id")))); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_body); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba_user_list", U_OPT_HTTP_VERB, "GET", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&user_req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ulfius_clean_request(&req); ulfius_clean_response(&resp); client_grant_scopes(SCOPE_2); url = msprintf("%s&g_continue", json_string_value(json_object_get(json_array_get(j_body, 0), "connect_uri"))); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "ciba_message=complete"), 1); o_free(url); json_decref(j_body); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_POLL, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "urn:openid:params:grant-type:ciba", U_OPT_POST_BODY_PARAMETER, "auth_req_id", auth_req_id, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq(auth_req_id, json_string_value(json_object_get(j_body, "auth_req_id"))); ck_assert_ptr_ne(NULL, json_string_value(json_object_get(j_body, "access_token"))); ck_assert_str_eq("bearer", json_string_value(json_object_get(j_body, "token_type"))); ck_assert_ptr_ne(NULL, json_string_value(json_object_get(j_body, "refresh_token"))); ck_assert_ptr_ne(NULL, json_string_value(json_object_get(j_body, "id_token"))); ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(json_string_value(json_object_get(j_body, "access_token")), R_PARSE_NONE, 0)); ck_assert_str_eq(SCOPE_2, r_jwt_get_claim_str_value(jwt, "scope")); client_grant_scopes(SCOPE_LIST); r_jwt_free(jwt); o_free(auth_req_id); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_push_reduced_scope_ok) { struct _u_request req; struct _u_response resp; json_t * j_body; char * url, * auth_req_id, * key_pem, * cert_pem; struct _u_instance instance; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PUSH, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, auth_req_id = o_strdup(json_string_value(json_object_get(j_body, "auth_req_id")))); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_body); ck_assert_int_eq(ulfius_init_instance(&instance, 2423, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "cb", 0, &callback_client_notification_endpoint_push_reduced_scope, auth_req_id), U_OK); counter = 0; key_pem = read_file(CB_KEY); cert_pem = read_file(CB_CRT); ck_assert_int_eq(ulfius_start_secure_framework(&instance, key_pem, cert_pem), U_OK); o_free(key_pem); o_free(cert_pem); client_grant_scopes(SCOPE_2); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba_user_list", U_OPT_HTTP_VERB, "GET", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&user_req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ulfius_clean_request(&req); ulfius_clean_response(&resp); url = msprintf("%s&g_continue", json_string_value(json_object_get(json_array_get(j_body, 0), "connect_uri"))); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "ciba_message=complete"), 1); o_free(url); json_decref(j_body); ck_assert_int_eq(1, counter); client_grant_scopes(SCOPE_LIST); o_free(auth_req_id); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); } END_TEST START_TEST(test_oidc_ciba_request_user_code_invalid) { struct _u_request req; struct _u_response resp; json_t * j_body; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_POST_BODY_PARAMETER, "user_code", "error", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("invalid_user_code", json_string_value(json_object_get(j_body, "error"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_ciba_request_user_code_ok) { struct _u_request req; struct _u_response resp; json_t * j_body; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_POST_BODY_PARAMETER, "user_code", USER_USERNAME, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, json_object_get(j_body, "auth_req_id")); ck_assert_int_eq(PLUGIN_CIBA_DEFAULT_EXPIRATION, json_integer_value(json_object_get(j_body, "expires_in"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST #define BACKLOG_MAX (10) #define BUF_SIZE 4096 #define STREQU(a,b) (strcmp(a, b) == 0) struct smtp_manager { char * mail_data; unsigned int port; int sockfd; const char * body_pattern; }; /** * * Function that emulates a very simple SMTP server * Taken from Kenneth Finnegan's ccsmtp program * https://gist.github.com/PhirePhly/2914635 * This function is under the GPL2 license * */ static void handle_smtp (struct smtp_manager * manager) { int rc, i; char buffer[BUF_SIZE], bufferout[BUF_SIZE]; int buffer_offset = 0; buffer[BUF_SIZE-1] = '\0'; // Flag for being inside of DATA verb int inmessage = 0; sprintf(bufferout, "220 ulfius.tld SMTP CCSMTP\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); while (1) { fd_set sockset; struct timeval tv; FD_ZERO(&sockset); FD_SET(manager->sockfd, &sockset); tv.tv_sec = 120; // Some SMTP servers pause for ~15s per message tv.tv_usec = 0; // Wait tv timeout for the server to send anything. select(manager->sockfd+1, &sockset, NULL, NULL, &tv); if (!FD_ISSET(manager->sockfd, &sockset)) { y_log_message(Y_LOG_LEVEL_DEBUG, "%d: Socket timed out", manager->sockfd); break; } int buffer_left = BUF_SIZE - buffer_offset - 1; if (buffer_left == 0) { y_log_message(Y_LOG_LEVEL_DEBUG, "%d: Command line too long", manager->sockfd); sprintf(bufferout, "500 Too long\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); buffer_offset = 0; continue; } rc = recv(manager->sockfd, buffer + buffer_offset, buffer_left, 0); if (rc == 0) { y_log_message(Y_LOG_LEVEL_DEBUG, "%d: Remote host closed socket", manager->sockfd); break; } if (rc == -1) { y_log_message(Y_LOG_LEVEL_DEBUG, "%d: Error on socket", manager->sockfd); break; } buffer_offset += rc; char *eol; // Only process one line of the received buffer at a time // If multiple lines were received in a single recv(), goto // back to here for each line // processline: eol = strstr(buffer, "\r\n"); if (eol == NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "%d: Haven't found EOL yet", manager->sockfd); continue; } // Null terminate each line to be processed individually eol[0] = '\0'; if (!inmessage) { // Handle system verbs // Replace all lower case letters so verbs are all caps for (i=0; i<4; i++) { if (islower(buffer[i])) { buffer[i] += 'A' - 'a'; } } // Null-terminate the verb for strcmp buffer[4] = '\0'; // Respond to each verb accordingly. // You should replace these with more meaningful // actions than simply printing everything. // if (STREQU(buffer, "HELO")) { // Initial greeting sprintf(bufferout, "250 Ok\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); } else if (STREQU(buffer, "MAIL")) { // New mail from... sprintf(bufferout, "250 Ok\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); } else if (STREQU(buffer, "RCPT")) { // Mail addressed to... sprintf(bufferout, "250 Ok recipient\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); } else if (STREQU(buffer, "DATA")) { // Message contents... sprintf(bufferout, "354 Continue\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); inmessage = 1; } else if (STREQU(buffer, "RSET")) { // Reset the connection sprintf(bufferout, "250 Ok reset\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); } else if (STREQU(buffer, "NOOP")) { // Do nothing. sprintf(bufferout, "250 Ok noop\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); } else if (STREQU(buffer, "QUIT")) { // Close the connection sprintf(bufferout, "221 Ok\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); break; } else { // The verb used hasn't been implemented. sprintf(bufferout, "502 Command Not Implemented\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); } } else { // We are inside the message after a DATA verb. if (0 == o_strncmp(manager->body_pattern, buffer, o_strlen(manager->body_pattern))) { manager->mail_data = o_strdup(buffer+o_strlen(manager->body_pattern)); } if (STREQU(buffer, ".")) { // A single "." signifies the end sprintf(bufferout, "250 Ok\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); inmessage = 0; } } // Shift the rest of the buffer to the front memmove(buffer, eol+2, BUF_SIZE - (eol + 2 - buffer)); buffer_offset -= (eol - buffer) + 2; // Do we already have additional lines to process? If so, // commit a horrid sin and goto the line processing section again. if (strstr(buffer, "\r\n")) goto processline; } // All done. Clean up everything and exit. shutdown(manager->sockfd, SHUT_WR); close(manager->sockfd); } static void * simple_smtp(void * args) { struct smtp_manager * manager = (struct smtp_manager *)args; int server_fd; struct sockaddr_in address; int opt = 1; int addrlen = sizeof(address); if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) != 0) { if (!setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) { address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons( manager->port ); if (!bind(server_fd, (struct sockaddr *)&address, sizeof(address))) { if (listen(server_fd, 3) >= 0) { if ((manager->sockfd = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) >= 0) { handle_smtp(manager); } else { y_log_message(Y_LOG_LEVEL_ERROR, "simple_smtp - Error accept"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "simple_smtp - Error listen"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "simple_smtp - Error bind"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "simple_smtp - Error setsockopt"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "simple_smtp - Error socket"); } shutdown(server_fd, SHUT_RDWR); close(server_fd); pthread_exit(NULL); } START_TEST(test_oidc_ciba_request_email_ok) { struct smtp_manager manager; pthread_t thread; manager.mail_data = NULL; manager.port = CIBA_SMTP_PORT; manager.sockfd = 0; manager.body_pattern = CIBA_SMTP_BODY_PATTERN; pthread_create(&thread, NULL, simple_smtp, &manager); struct _u_request req; struct _u_response resp; json_t * j_body; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_PING, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, json_object_get(j_body, "auth_req_id")); ck_assert_int_eq(PLUGIN_CIBA_DEFAULT_EXPIRATION, json_integer_value(json_object_get(j_body, "expires_in"))); pthread_join(thread, NULL); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); o_free(manager.mail_data); } END_TEST START_TEST(test_oidc_ciba_poll_expiration_ok) { struct _u_request req; struct _u_response resp; json_t * j_body; char * auth_req_id; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_POLL, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, auth_req_id = o_strdup(json_string_value(json_object_get(j_body, "auth_req_id")))); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_body); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_POLL, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "urn:openid:params:grant-type:ciba", U_OPT_POST_BODY_PARAMETER, "auth_req_id", auth_req_id, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("authorization_pending", json_string_value(json_object_get(j_body, "error"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); sleep(2); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_ID_POLL, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "urn:openid:params:grant-type:ciba", U_OPT_POST_BODY_PARAMETER, "auth_req_id", auth_req_id, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("expired_token", json_string_value(json_object_get(j_body, "error"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); o_free(auth_req_id); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc ciba requests"); tc_core = tcase_create("test_oidc_ciba_requests"); tcase_add_test(tc_core, test_oidc_ciba_add_client_poll); tcase_add_test(tc_core, test_oidc_ciba_add_client_ping); tcase_add_test(tc_core, test_oidc_ciba_add_client_push); tcase_add_test(tc_core, test_oidc_ciba_add_plugin_user_code_no_email_no); tcase_add_test(tc_core, test_oidc_ciba_client_grant_scopes); tcase_add_test(tc_core, test_oidc_ciba_request_client_notification_token_missing); tcase_add_test(tc_core, test_oidc_ciba_request_client_notification_token_invalid_chars); tcase_add_test(tc_core, test_oidc_ciba_request_client_notification_token_invalid_small_length); tcase_add_test(tc_core, test_oidc_ciba_request_client_notification_token_invalid_large_length); tcase_add_test(tc_core, test_oidc_ciba_request_login_hint_missing); tcase_add_test(tc_core, test_oidc_ciba_request_login_hint_invalid); tcase_add_test(tc_core, test_oidc_ciba_request_login_hint_error_key); tcase_add_test(tc_core, test_oidc_ciba_request_login_hint_error_format); tcase_add_test(tc_core, test_oidc_ciba_request_login_hint_ok); tcase_add_test(tc_core, test_oidc_ciba_request_id_token_hint_invalid); tcase_add_test(tc_core, test_oidc_ciba_request_id_token_hint_ok); tcase_add_test(tc_core, test_oidc_ciba_request_login_hint_token_invalid_signature); tcase_add_test(tc_core, test_oidc_ciba_request_login_hint_token_ok); tcase_add_test(tc_core, test_oidc_ciba_request_login_hint_too_much); tcase_add_test(tc_core, test_oidc_ciba_signed_request_login_hint_invalid_signature); tcase_add_test(tc_core, test_oidc_ciba_signed_request_login_hint_replay_jti); tcase_add_test(tc_core, test_oidc_ciba_signed_request_login_hint_invalid_aud); tcase_add_test(tc_core, test_oidc_ciba_signed_request_login_hint_invalid_iss); tcase_add_test(tc_core, test_oidc_ciba_signed_request_login_hint_ok); tcase_add_test(tc_core, test_oidc_ciba_client_assertion_request_login_hint_invalid_signature); tcase_add_test(tc_core, test_oidc_ciba_client_assertion_request_login_hint_invalid_aud); tcase_add_test(tc_core, test_oidc_ciba_client_assertion_request_login_hint_ok); tcase_add_test(tc_core, test_oidc_ciba_user_list); tcase_add_test(tc_core, test_oidc_ciba_user_check); tcase_add_test(tc_core, test_oidc_ciba_user_check_accept); tcase_add_test(tc_core, test_oidc_ciba_poll_ok); tcase_add_test(tc_core, test_oidc_ciba_ping_ok); tcase_add_test(tc_core, test_oidc_ciba_push_ok); tcase_add_test(tc_core, test_oidc_ciba_poll_reduced_scope_ok); tcase_add_test(tc_core, test_oidc_ciba_push_reduced_scope_ok); tcase_add_test(tc_core, test_oidc_ciba_delete_plugin); tcase_add_test(tc_core, test_oidc_ciba_add_plugin_user_code_email_no); tcase_add_test(tc_core, test_oidc_ciba_request_user_code_invalid); tcase_add_test(tc_core, test_oidc_ciba_request_user_code_ok); tcase_add_test(tc_core, test_oidc_ciba_delete_plugin); tcase_add_test(tc_core, test_oidc_ciba_add_plugin_user_code_no_email); tcase_add_test(tc_core, test_oidc_ciba_request_email_ok); tcase_add_test(tc_core, test_oidc_ciba_delete_plugin); tcase_add_test(tc_core, test_oidc_ciba_add_plugin_expires_soon); tcase_add_test(tc_core, test_oidc_ciba_poll_expiration_ok); tcase_add_test(tc_core, test_oidc_ciba_delete_plugin); tcase_add_test(tc_core, test_oidc_ciba_client_grant_none); tcase_add_test(tc_core, test_oidc_ciba_delete_client_poll); tcase_add_test(tc_core, test_oidc_ciba_delete_client_ping); tcase_add_test(tc_core, test_oidc_ciba_delete_client_push); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req; struct _u_response auth_resp, scope_resp; int res, do_test = 0; char * cookie; json_t * j_body; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); ulfius_init_request(&user_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&scope_req); ulfius_init_response(&auth_resp); ulfius_init_response(&scope_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf(SERVER_URI "/auth/"); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "User %s authenticated", USER_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(scope_req.map_header, "Cookie", cookie); u_map_put(user_req.map_header, "Cookie", cookie); o_free(cookie); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication user"); do_test = 0; } j_body = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "register", json_true()); run_simple_test(&user_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL); json_decref(j_body); j_body = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "register", json_true()); run_simple_test(&user_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL); json_decref(j_body); j_body = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "register", json_true()); run_simple_test(&user_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL); json_decref(j_body); j_body = json_pack("{sssssss{ss}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); run_simple_test(&user_req, "POST", SERVER_URI "/auth/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL); json_decref(j_body); j_body = json_pack("{sssssss{ss}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); run_simple_test(&user_req, "POST", SERVER_URI "/auth/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL); json_decref(j_body); j_body = json_pack("{sssssss{ss}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); run_simple_test(&user_req, "POST", SERVER_URI "/auth/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL); json_decref(j_body); ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); } if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); j_body = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "register", json_false()); run_simple_test(&user_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL); json_decref(j_body); j_body = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "register", json_false()); run_simple_test(&user_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL); json_decref(j_body); j_body = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "register", json_false()); run_simple_test(&user_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL); json_decref(j_body); } run_simple_test(&user_req, "DELETE", SERVER_URI "/auth/", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL); ulfius_clean_request(&admin_req); ulfius_clean_request(&user_req); ulfius_clean_request(&scope_req); ulfius_clean_response(&scope_resp); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_claim_request.c000066400000000000000000002552121415646314000222650ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "user1" #define USER2_USERNAME "user2" #define USER_PASSWORD "password" #define USER2_PASSWORD "password" #define PLUGIN_NAME "oidc_claims" #define SCOPE_LIST "g_profile openid" #define SCOPE_1 "g_profile" #define SCOPE_2 "openid" #define CLIENT "client1_id" #define CLIENT_REDIRECT_URI "../../test-oidc.html?param=client1_cb1" #define RESPONSE_TYPE "id_token token" #define CLAIM_STR "the-str" #define CLAIM_STR_2 "the-other-str" #define CLAIM_NUMBER "42" #define CLAIM_NUMBER_2 "43" #define CLAIM_BOOL_TRUE "1" #define CLAIM_BOOL_FALSE "0" #define CLAIM_MANDATORY "I'm aliiiiive!" #define ADDR_FORMATTED "formatted value" #define ADDR_STREET_ADDRESS "street_address value" #define ADDR_LOCALITY "locality value" #define ADDR_REGION "region value" #define ADDR_POSTAL_CODE "postal_code value" #define ADDR_COUNTRY "country value" struct _u_request admin_req; struct _u_request user_req, user2_req; START_TEST(test_oidc_claim_request_add_plugin) { json_t * j_param = json_pack("{sssssss{sssssssssisisisososososososososssssss[{sssoss}{sssossss}{sssossssssss}{ssssso}{sssoss}{sssossss}{sssossssssss}]s{ssssssssssssss}}}", "module", "oidc", "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", "https://glewlwyd.tld", "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_NAME, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "name-claim", "on-demand", "email-claim", "on-demand", "scope-claim", "on-demand", "claims", "name", "claim-str", "on-demand", json_true(), "user-property", "claim-str", "name", "claim-number", "on-demand", json_true(), "type", "number", "user-property", "claim-number", "name", "claim-bool", "on-demand", json_true(), "type", "boolean", "user-property", "claim-bool", "boolean-value-true", "1", "boolean-value-false", "0", "name", "claim-mandatory", "user-property", "claim-mandatory", "mandatory", json_true(), "name", "claim-array-str", "on-demand", json_true(), "user-property", "claim-array-str", "name", "claim-array-number", "on-demand", json_true(), "type", "number", "user-property", "claim-array-number", "name", "claim-array-bool", "on-demand", json_true(), "type", "boolean", "user-property", "claim-array-bool", "boolean-value-true", "1", "boolean-value-false", "0", "address-claim", "type", "mandatory", "formatted", "add-formatted", "street_address", "add-street_address", "locality", "add-locality", "region", "add-region", "postal_code", "add-postal_code", "country", "add-country"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); j_param = json_pack("{sssssssssssssssssssss[ss]s[ss]s[ss]}", "claim-str", CLAIM_STR, "claim-number", CLAIM_NUMBER, "claim-bool", CLAIM_BOOL_TRUE, "claim-mandatory", CLAIM_MANDATORY, "add-formatted", ADDR_FORMATTED, "add-street_address", ADDR_STREET_ADDRESS, "add-locality", ADDR_LOCALITY, "add-region", ADDR_REGION, "add-postal_code", ADDR_POSTAL_CODE, "add-country", ADDR_COUNTRY, "claim-array-str", CLAIM_STR, CLAIM_STR_2, "claim-array-number", CLAIM_NUMBER, CLAIM_NUMBER_2, "claim-array-bool", CLAIM_BOOL_TRUE, CLAIM_BOOL_FALSE); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USER_USERNAME, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_claim_request_user1_id_token_no_claim) { struct _u_response resp; char * id_token, ** id_token_split, str_payload[1024] = {0}; size_t str_payload_len; json_t * j_result; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *(o_strchr(id_token, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((const unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_result = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-mandatory")), CLAIM_MANDATORY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "formatted")), ADDR_FORMATTED); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "street_address")), ADDR_STREET_ADDRESS); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "locality")), ADDR_LOCALITY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "country")), ADDR_COUNTRY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "postal_code")), ADDR_POSTAL_CODE); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "region")), ADDR_REGION); ck_assert_ptr_eq(json_object_get(j_result, "claim-str"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-number"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-bool"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "scope"), NULL); free_string_array(id_token_split); o_free(id_token); json_decref(j_result); } END_TEST START_TEST(test_oidc_claim_request_user2_id_token_no_claim) { struct _u_response resp; char * id_token, ** id_token_split, str_payload[1024] = {0}; size_t str_payload_len; json_t * j_result; ulfius_init_response(&resp); o_free(user2_req.http_url); user2_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST); o_free(user2_req.http_verb); user2_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user2_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *(o_strchr(id_token, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((const unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_result = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-mandatory"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-str"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-number"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-bool"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "address"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "scope"), NULL); free_string_array(id_token_split); o_free(id_token); json_decref(j_result); } END_TEST START_TEST(test_oidc_claim_request_user1_id_token_claim_invalid) { struct _u_response resp; char * claims_str, * claims_str_enc; json_t * j_claims; ulfius_init_response(&resp); ck_assert_ptr_ne((j_claims = json_pack("{s{so}}", "id_token", "claim-str", json_true())), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "error=invalid_request"), NULL); ulfius_clean_response(&resp); o_free(claims_str); o_free(claims_str_enc); json_decref(j_claims); ulfius_init_response(&resp); ck_assert_ptr_ne((j_claims = json_pack("{s{s{si}}}", "id_token", "claim-str", "value", 42)), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "error=invalid_request"), NULL); ulfius_clean_response(&resp); o_free(claims_str); o_free(claims_str_enc); json_decref(j_claims); ulfius_init_response(&resp); ck_assert_ptr_ne((j_claims = json_pack("{s{s{ss}}}", "id_token", "claim-str", "value", "")), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "error=invalid_request"), NULL); ulfius_clean_response(&resp); o_free(claims_str); o_free(claims_str_enc); json_decref(j_claims); ulfius_init_response(&resp); ck_assert_ptr_ne((j_claims = json_pack("{s{s{s[i]}}}", "id_token", "claim-str", "values", 42)), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "error=invalid_request"), NULL); ulfius_clean_response(&resp); o_free(claims_str); o_free(claims_str_enc); json_decref(j_claims); ulfius_init_response(&resp); ck_assert_ptr_ne((j_claims = json_pack("{s{s{s[ss]}}}", "id_token", "claim-str", "values", "plop", "")), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "error=invalid_request"), NULL); ulfius_clean_response(&resp); o_free(claims_str); o_free(claims_str_enc); json_decref(j_claims); } END_TEST START_TEST(test_oidc_claim_request_user1_id_token_claim_str_null) { struct _u_response resp; char * id_token, ** id_token_split, str_payload[1024] = {0}, * claims_str, * claims_str_enc; size_t str_payload_len; json_t * j_result, * j_claims; ck_assert_ptr_ne((j_claims = json_pack("{s{so}}", "id_token", "claim-str", json_null())), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *(o_strchr(id_token, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((const unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_result = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-mandatory")), CLAIM_MANDATORY); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-str")), CLAIM_STR); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "formatted")), ADDR_FORMATTED); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "street_address")), ADDR_STREET_ADDRESS); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "locality")), ADDR_LOCALITY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "country")), ADDR_COUNTRY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "postal_code")), ADDR_POSTAL_CODE); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "region")), ADDR_REGION); ck_assert_ptr_eq(json_object_get(j_result, "claim-number"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-bool"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "scope"), NULL); free_string_array(id_token_split); o_free(id_token); o_free(claims_str); o_free(claims_str_enc); json_decref(j_result); json_decref(j_claims); } END_TEST START_TEST(test_oidc_claim_request_user2_id_token_claim_str_null) { struct _u_response resp; char * id_token, ** id_token_split, str_payload[1024] = {0}, * claims_str, * claims_str_enc; size_t str_payload_len; json_t * j_result, * j_claims; ck_assert_ptr_ne((j_claims = json_pack("{s{so}}", "id_token", "claim-str", json_null())), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); ulfius_init_response(&resp); o_free(user2_req.http_url); user2_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user2_req.http_verb); user2_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user2_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *(o_strchr(id_token, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((const unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_result = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-mandatory"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-str"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-number"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-bool"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "address"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "scope"), NULL); free_string_array(id_token_split); o_free(id_token); o_free(claims_str); o_free(claims_str_enc); json_decref(j_result); json_decref(j_claims); } END_TEST START_TEST(test_oidc_claim_request_user1_id_token_claim_str_value_found) { struct _u_response resp; char * id_token, ** id_token_split, str_payload[1024] = {0}, * claims_str, * claims_str_enc; size_t str_payload_len; json_t * j_result, * j_claims; ck_assert_ptr_ne((j_claims = json_pack("{s{s{ss}}}", "id_token", "claim-str", "value", CLAIM_STR)), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *(o_strchr(id_token, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((const unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_result = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-mandatory")), CLAIM_MANDATORY); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-str")), CLAIM_STR); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "formatted")), ADDR_FORMATTED); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "street_address")), ADDR_STREET_ADDRESS); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "locality")), ADDR_LOCALITY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "country")), ADDR_COUNTRY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "postal_code")), ADDR_POSTAL_CODE); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "region")), ADDR_REGION); ck_assert_ptr_eq(json_object_get(j_result, "claim-number"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-bool"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "scope"), NULL); free_string_array(id_token_split); o_free(id_token); o_free(claims_str); o_free(claims_str_enc); json_decref(j_result); json_decref(j_claims); } END_TEST START_TEST(test_oidc_claim_request_user1_id_token_claim_str_value_not_found) { struct _u_response resp; char * id_token, ** id_token_split, str_payload[1024] = {0}, * claims_str, * claims_str_enc; size_t str_payload_len; json_t * j_result, * j_claims; ck_assert_ptr_ne((j_claims = json_pack("{s{s{ss}}}", "id_token", "claim-str", "value", CLAIM_STR "error")), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *(o_strchr(id_token, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((const unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_result = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-mandatory")), CLAIM_MANDATORY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "formatted")), ADDR_FORMATTED); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "street_address")), ADDR_STREET_ADDRESS); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "locality")), ADDR_LOCALITY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "country")), ADDR_COUNTRY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "postal_code")), ADDR_POSTAL_CODE); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "region")), ADDR_REGION); ck_assert_ptr_eq(json_object_get(j_result, "claim-str"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-number"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-bool"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "scope"), NULL); free_string_array(id_token_split); o_free(id_token); o_free(claims_str); o_free(claims_str_enc); json_decref(j_result); json_decref(j_claims); } END_TEST START_TEST(test_oidc_claim_request_user1_id_token_claim_str_values_found) { struct _u_response resp; char * id_token, ** id_token_split, str_payload[1024] = {0}, * claims_str, * claims_str_enc; size_t str_payload_len; json_t * j_result, * j_claims; ck_assert_ptr_ne((j_claims = json_pack("{s{s{s[sss]}}}", "id_token", "claim-str", "values", "error1", CLAIM_STR, "error2")), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *(o_strchr(id_token, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((const unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_result = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-mandatory")), CLAIM_MANDATORY); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-str")), CLAIM_STR); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "formatted")), ADDR_FORMATTED); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "street_address")), ADDR_STREET_ADDRESS); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "locality")), ADDR_LOCALITY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "country")), ADDR_COUNTRY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "postal_code")), ADDR_POSTAL_CODE); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "region")), ADDR_REGION); ck_assert_ptr_eq(json_object_get(j_result, "claim-number"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-bool"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "scope"), NULL); free_string_array(id_token_split); o_free(id_token); o_free(claims_str); o_free(claims_str_enc); json_decref(j_result); json_decref(j_claims); } END_TEST START_TEST(test_oidc_claim_request_user1_id_token_claim_str_values_not_found) { struct _u_response resp; char * id_token, ** id_token_split, str_payload[1024] = {0}, * claims_str, * claims_str_enc; size_t str_payload_len; json_t * j_result, * j_claims; ck_assert_ptr_ne((j_claims = json_pack("{s{s{s[sss]}}}", "id_token", "claim-str", "values", "error1", CLAIM_STR "error", "error2")), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *(o_strchr(id_token, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((const unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_result = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-mandatory")), CLAIM_MANDATORY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "formatted")), ADDR_FORMATTED); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "street_address")), ADDR_STREET_ADDRESS); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "locality")), ADDR_LOCALITY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "country")), ADDR_COUNTRY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "postal_code")), ADDR_POSTAL_CODE); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "region")), ADDR_REGION); ck_assert_ptr_eq(json_object_get(j_result, "claim-str"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-number"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-bool"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "scope"), NULL); free_string_array(id_token_split); o_free(id_token); o_free(claims_str); o_free(claims_str_enc); json_decref(j_result); json_decref(j_claims); } END_TEST START_TEST(test_oidc_claim_request_user1_id_token_claim_number_value_found) { struct _u_response resp; char * id_token, ** id_token_split, str_payload[1024] = {0}, * claims_str, * claims_str_enc; size_t str_payload_len; json_t * j_result, * j_claims; ck_assert_ptr_ne((j_claims = json_pack("{s{s{ss}}}", "id_token", "claim-number", "value", CLAIM_NUMBER)), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *(o_strchr(id_token, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((const unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_result = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-mandatory")), CLAIM_MANDATORY); ck_assert_int_eq(json_integer_value(json_object_get(j_result, "claim-number")), 42); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "formatted")), ADDR_FORMATTED); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "street_address")), ADDR_STREET_ADDRESS); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "locality")), ADDR_LOCALITY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "country")), ADDR_COUNTRY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "postal_code")), ADDR_POSTAL_CODE); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "region")), ADDR_REGION); ck_assert_ptr_eq(json_object_get(j_result, "claim-str"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-bool"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "scope"), NULL); free_string_array(id_token_split); o_free(id_token); o_free(claims_str); o_free(claims_str_enc); json_decref(j_result); json_decref(j_claims); } END_TEST START_TEST(test_oidc_claim_request_user1_id_token_claim_number_values_found) { struct _u_response resp; char * id_token, ** id_token_split, str_payload[1024] = {0}, * claims_str, * claims_str_enc; size_t str_payload_len; json_t * j_result, * j_claims; ck_assert_ptr_ne((j_claims = json_pack("{s{s{s[sss]}}}", "id_token", "claim-number", "values", CLAIM_NUMBER, "error1", "error2")), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *(o_strchr(id_token, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((const unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_result = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-mandatory")), CLAIM_MANDATORY); ck_assert_int_eq(json_integer_value(json_object_get(j_result, "claim-number")), 42); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "formatted")), ADDR_FORMATTED); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "street_address")), ADDR_STREET_ADDRESS); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "locality")), ADDR_LOCALITY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "country")), ADDR_COUNTRY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "postal_code")), ADDR_POSTAL_CODE); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "region")), ADDR_REGION); ck_assert_ptr_eq(json_object_get(j_result, "claim-str"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-bool"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "scope"), NULL); free_string_array(id_token_split); o_free(id_token); o_free(claims_str); o_free(claims_str_enc); json_decref(j_result); json_decref(j_claims); } END_TEST START_TEST(test_oidc_claim_request_user1_id_token_claim_boolean_value_found) { struct _u_response resp; char * id_token, ** id_token_split, str_payload[1024] = {0}, * claims_str, * claims_str_enc; size_t str_payload_len; json_t * j_result, * j_claims; ck_assert_ptr_ne((j_claims = json_pack("{s{s{ss}}}", "id_token", "claim-bool", "value", CLAIM_BOOL_TRUE)), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *(o_strchr(id_token, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((const unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_result = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-mandatory")), CLAIM_MANDATORY); ck_assert_ptr_eq(json_object_get(j_result, "claim-bool"), json_true()); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "formatted")), ADDR_FORMATTED); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "street_address")), ADDR_STREET_ADDRESS); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "locality")), ADDR_LOCALITY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "country")), ADDR_COUNTRY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "postal_code")), ADDR_POSTAL_CODE); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "region")), ADDR_REGION); ck_assert_ptr_eq(json_object_get(j_result, "claim-str"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-number"), NULL); free_string_array(id_token_split); o_free(id_token); o_free(claims_str); o_free(claims_str_enc); json_decref(j_result); json_decref(j_claims); } END_TEST START_TEST(test_oidc_claim_request_user1_id_token_claim_boolean_values_found) { struct _u_response resp; char * id_token, ** id_token_split, str_payload[1024] = {0}, * claims_str, * claims_str_enc; size_t str_payload_len; json_t * j_result, * j_claims; ck_assert_ptr_ne((j_claims = json_pack("{s{s{s[sss]}}}", "id_token", "claim-bool", "values", CLAIM_BOOL_TRUE, "error1", "error2")), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *(o_strchr(id_token, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((const unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_result = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-mandatory")), CLAIM_MANDATORY); ck_assert_ptr_eq(json_object_get(j_result, "claim-bool"), json_true()); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "formatted")), ADDR_FORMATTED); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "street_address")), ADDR_STREET_ADDRESS); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "locality")), ADDR_LOCALITY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "country")), ADDR_COUNTRY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "postal_code")), ADDR_POSTAL_CODE); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "region")), ADDR_REGION); ck_assert_ptr_eq(json_object_get(j_result, "claim-str"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-number"), NULL); free_string_array(id_token_split); o_free(id_token); o_free(claims_str); o_free(claims_str_enc); json_decref(j_result); json_decref(j_claims); } END_TEST START_TEST(test_oidc_claim_request_user1_id_token_claim_full) { struct _u_response resp; char * id_token, ** id_token_split, str_payload[1024] = {0}, * claims_str, * claims_str_enc; size_t str_payload_len; json_t * j_result, * j_claims; ck_assert_ptr_ne((j_claims = json_pack("{s{sososososososososo}}", "id_token", "claim-str", json_null(), "claim-number", json_null(), "claim-bool", json_null(), "claim-array-str", json_null(), "claim-array-number", json_null(), "claim-array-bool", json_null(), "name", json_null(), "email", json_null(), "scope", json_null())), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *(o_strchr(id_token, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((const unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_result = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-mandatory")), CLAIM_MANDATORY); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-str")), CLAIM_STR); ck_assert_int_eq(json_integer_value(json_object_get(j_result, "claim-number")), 42); ck_assert_int_eq(json_array_size(json_object_get(j_result, "claim-array-str")), 2); ck_assert_int_eq(json_array_size(json_object_get(j_result, "claim-array-number")), 2); ck_assert_int_eq(json_array_size(json_object_get(j_result, "claim-array-bool")), 2); ck_assert_ptr_eq(json_object_get(j_result, "claim-bool"), json_true()); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "formatted")), ADDR_FORMATTED); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "street_address")), ADDR_STREET_ADDRESS); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "locality")), ADDR_LOCALITY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "country")), ADDR_COUNTRY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "postal_code")), ADDR_POSTAL_CODE); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "region")), ADDR_REGION); ck_assert_str_eq(json_string_value(json_object_get(j_result, "name")), "Dave Lopper 1"); ck_assert_str_eq(json_string_value(json_object_get(j_result, "email")), "dev1@glewlwyd"); ck_assert_int_eq(json_array_size(json_object_get(j_result, "scope")), 2); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result, "scope"), 0)), SCOPE_1); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result, "scope"), 1)), SCOPE_2); free_string_array(id_token_split); o_free(id_token); o_free(claims_str); o_free(claims_str_enc); json_decref(j_result); json_decref(j_claims); } END_TEST START_TEST(test_oidc_claim_request_user1_token_userinfo_claim_str_null) { struct _u_response resp; struct _u_request req; char * access_token, * claims_str, * claims_str_enc, * bearer; json_t * j_result, * j_claims; ck_assert_ptr_ne((j_claims = json_pack("{s{so}}", "userinfo", "claim-str", json_null())), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); ulfius_init_response(&resp); ulfius_init_request(&req); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); access_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token=")); if (o_strchr(access_token, '&')) { *(o_strchr(access_token, '&')) = '\0'; } ulfius_clean_response(&resp); bearer = msprintf("Bearer %s", access_token); u_map_put(req.map_header, "Authorization", bearer); j_result = json_pack("{sssss{ssssssssssss}}", "claim-mandatory", CLAIM_MANDATORY, "claim-str", CLAIM_STR, "address", "formatted", ADDR_FORMATTED, "street_address", ADDR_STREET_ADDRESS, "locality", ADDR_LOCALITY, "country", ADDR_COUNTRY, "postal_code", ADDR_POSTAL_CODE, "region", ADDR_REGION); ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/" PLUGIN_NAME "/userinfo/", NULL, NULL, NULL, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_result); o_free(claims_str_enc); o_free(claims_str); o_free(access_token); o_free(bearer); json_decref(j_claims); ulfius_clean_request(&req); } END_TEST START_TEST(test_oidc_claim_request_user1_token_userinfo_claim_full) { struct _u_response resp; struct _u_request req; char * access_token, * claims_str, * claims_str_enc, * bearer; json_t * j_result, * j_claims; ck_assert_ptr_ne((j_claims = json_pack("{s{sosososososo}}", "userinfo", "claim-str", json_null(), "claim-number", json_null(), "claim-bool", json_null(), "name", json_null(), "email", json_null(), "scope", json_null())), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); ulfius_init_response(&resp); ulfius_init_request(&req); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); access_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token=")); if (o_strchr(access_token, '&')) { *(o_strchr(access_token, '&')) = '\0'; } ulfius_clean_response(&resp); bearer = msprintf("Bearer %s", access_token); u_map_put(req.map_header, "Authorization", bearer); j_result = json_pack("{sssss{ssssssssssss}sisosssss[ss]}", "claim-mandatory", CLAIM_MANDATORY, "claim-str", CLAIM_STR, "address", "formatted", ADDR_FORMATTED, "street_address", ADDR_STREET_ADDRESS, "locality", ADDR_LOCALITY, "country", ADDR_COUNTRY, "postal_code", ADDR_POSTAL_CODE, "region", ADDR_REGION, "claim-number", 42, "claim-bool", json_true(), "name", "Dave Lopper 1", "email", "dev1@glewlwyd", "scope", SCOPE_1, SCOPE_2); ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/" PLUGIN_NAME "/userinfo/", NULL, NULL, NULL, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_result); o_free(access_token); o_free(bearer); o_free(claims_str_enc); o_free(claims_str); json_decref(j_claims); ulfius_clean_request(&req); } END_TEST START_TEST(test_oidc_claim_request_user1_code_id_token_userinfo_claim_str_null) { struct _u_response resp; struct _u_request req; const char * access_token = NULL, * id_token = NULL; char * code, * claims_str, * claims_str_enc, ** id_token_split = NULL, str_payload[1024], * bearer = NULL; json_t * j_result, * j_claims, * j_body; size_t str_payload_len; ulfius_init_response(&resp); ck_assert_ptr_ne((j_claims = json_pack("{s{so}s{so}}", "userinfo", "claim-number", json_null(), "id_token", "claim-str", json_null())), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=code&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code=")); if (o_strchr(code, '&')) { *(o_strchr(code, '&')) = '\0'; } ulfius_clean_response(&resp); ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "authorization_code"); u_map_put(req.map_post_body, "client_id", CLIENT); u_map_put(req.map_post_body, "redirect_uri", CLIENT_REDIRECT_URI); u_map_put(req.map_post_body, "code", code); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_ptr_ne((j_body = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((access_token = json_string_value(json_object_get(j_body, "access_token"))), NULL); ck_assert_ptr_ne((id_token = json_string_value(json_object_get(j_body, "id_token"))), NULL); ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((const unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_result = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-mandatory")), CLAIM_MANDATORY); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-str")), CLAIM_STR); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "formatted")), ADDR_FORMATTED); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "street_address")), ADDR_STREET_ADDRESS); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "locality")), ADDR_LOCALITY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "country")), ADDR_COUNTRY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "postal_code")), ADDR_POSTAL_CODE); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "region")), ADDR_REGION); ck_assert_ptr_eq(json_object_get(j_result, "claim-number"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-bool"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "scope"), NULL); bearer = msprintf("Bearer %s", access_token); u_map_put(req.map_header, "Authorization", bearer); json_decref(j_result); j_result = json_pack("{sssis{ssssssssssss}}", "claim-mandatory", CLAIM_MANDATORY, "claim-number", 42, "address", "formatted", ADDR_FORMATTED, "street_address", ADDR_STREET_ADDRESS, "locality", ADDR_LOCALITY, "country", ADDR_COUNTRY, "postal_code", ADDR_POSTAL_CODE, "region", ADDR_REGION); ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/" PLUGIN_NAME "/userinfo/", NULL, NULL, NULL, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_result); o_free(bearer); o_free(claims_str_enc); o_free(claims_str); o_free(code); json_decref(j_claims); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); free_string_array(id_token_split); } END_TEST START_TEST(test_oidc_claim_request_user1_code_id_token_userinfo_claim_full) { struct _u_response resp; struct _u_request req; const char * access_token = NULL, * id_token = NULL; char * code, * claims_str, * claims_str_enc, ** id_token_split = NULL, str_payload[1024], * bearer = NULL; json_t * j_result, * j_claims, * j_body; size_t str_payload_len; ulfius_init_response(&resp); ck_assert_ptr_ne((j_claims = json_pack("{s{sosososososo}s{sosososososo}}", "userinfo", "claim-str", json_null(), "claim-number", json_null(), "claim-bool", json_null(), "name", json_null(), "email", json_null(), "scope", json_null(), "id_token", "claim-str", json_null(), "claim-number", json_null(), "claim-bool", json_null(), "name", json_null(), "email", json_null(), "scope", json_null())), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=code&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code=")); if (o_strchr(code, '&')) { *(o_strchr(code, '&')) = '\0'; } ulfius_clean_response(&resp); ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "authorization_code"); u_map_put(req.map_post_body, "client_id", CLIENT); u_map_put(req.map_post_body, "redirect_uri", CLIENT_REDIRECT_URI); u_map_put(req.map_post_body, "code", code); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_ptr_ne((j_body = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((access_token = json_string_value(json_object_get(j_body, "access_token"))), NULL); ck_assert_ptr_ne((id_token = json_string_value(json_object_get(j_body, "id_token"))), NULL); ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((const unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_result = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-mandatory")), CLAIM_MANDATORY); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-str")), CLAIM_STR); ck_assert_int_eq(json_integer_value(json_object_get(j_result, "claim-number")), 42); ck_assert_ptr_eq(json_object_get(j_result, "claim-bool"), json_true()); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "formatted")), ADDR_FORMATTED); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "street_address")), ADDR_STREET_ADDRESS); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "locality")), ADDR_LOCALITY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "country")), ADDR_COUNTRY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "postal_code")), ADDR_POSTAL_CODE); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "region")), ADDR_REGION); ck_assert_str_eq(json_string_value(json_object_get(j_result, "name")), "Dave Lopper 1"); ck_assert_str_eq(json_string_value(json_object_get(j_result, "email")), "dev1@glewlwyd"); ck_assert_int_eq(json_array_size(json_object_get(j_result, "scope")), 2); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result, "scope"), 0)), SCOPE_1); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result, "scope"), 1)), SCOPE_2); bearer = msprintf("Bearer %s", access_token); u_map_put(req.map_header, "Authorization", bearer); json_decref(j_result); j_result = json_pack("{sssss{ssssssssssss}sisosssss[ss]}", "claim-mandatory", CLAIM_MANDATORY, "claim-str", CLAIM_STR, "address", "formatted", ADDR_FORMATTED, "street_address", ADDR_STREET_ADDRESS, "locality", ADDR_LOCALITY, "country", ADDR_COUNTRY, "postal_code", ADDR_POSTAL_CODE, "region", ADDR_REGION, "claim-number", 42, "claim-bool", json_true(), "name", "Dave Lopper 1", "email", "dev1@glewlwyd", "scope", SCOPE_1, SCOPE_2); ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/" PLUGIN_NAME "/userinfo/", NULL, NULL, NULL, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_result); o_free(bearer); o_free(claims_str_enc); o_free(claims_str); o_free(code); json_decref(j_claims); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); free_string_array(id_token_split); } END_TEST START_TEST(test_oidc_claim_request_user1_refresh_token_userinfo_claim_str_null) { struct _u_response resp; struct _u_request req; const char * access_token = NULL, * refresh_token; char * code, * claims_str, * claims_str_enc, * bearer = NULL; json_t * j_result, * j_claims, * j_body; ulfius_init_response(&resp); ck_assert_ptr_ne((j_claims = json_pack("{s{so}}", "userinfo", "claim-number", json_null())), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=code&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code=")); if (o_strchr(code, '&')) { *(o_strchr(code, '&')) = '\0'; } ulfius_clean_response(&resp); ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "authorization_code"); u_map_put(req.map_post_body, "client_id", CLIENT); u_map_put(req.map_post_body, "redirect_uri", CLIENT_REDIRECT_URI); u_map_put(req.map_post_body, "code", code); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_ptr_ne((j_body = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((refresh_token = json_string_value(json_object_get(j_body, "refresh_token"))), NULL); ulfius_clean_response(&resp); u_map_put(req.map_post_body, "grant_type", "refresh_token"); u_map_put(req.map_post_body, "refresh_token", refresh_token); u_map_remove_from_key(req.map_post_body, "client_id"); u_map_remove_from_key(req.map_post_body, "redirect_uri"); json_decref(j_body); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_ptr_ne((j_body = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((access_token = json_string_value(json_object_get(j_body, "access_token"))), NULL); ulfius_clean_response(&resp); bearer = msprintf("Bearer %s", access_token); u_map_put(req.map_header, "Authorization", bearer); j_result = json_pack("{sssis{ssssssssssss}}", "claim-mandatory", CLAIM_MANDATORY, "claim-number", 42, "address", "formatted", ADDR_FORMATTED, "street_address", ADDR_STREET_ADDRESS, "locality", ADDR_LOCALITY, "country", ADDR_COUNTRY, "postal_code", ADDR_POSTAL_CODE, "region", ADDR_REGION); ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/" PLUGIN_NAME "/userinfo/", NULL, NULL, NULL, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_result); o_free(bearer); o_free(code); o_free(claims_str_enc); o_free(claims_str); json_decref(j_claims); json_decref(j_body); ulfius_clean_request(&req); } END_TEST START_TEST(test_oidc_claim_request_user1_refresh_token_userinfo_claim_full) { struct _u_response resp; struct _u_request req; const char * access_token = NULL, * refresh_token; char * code, * claims_str, * claims_str_enc, * bearer = NULL; json_t * j_result, * j_claims, * j_body; ulfius_init_response(&resp); ck_assert_ptr_ne((j_claims = json_pack("{s{sosososososo}}", "userinfo", "claim-str", json_null(), "claim-number", json_null(), "claim-bool", json_null(), "name", json_null(), "email", json_null(), "scope", json_null())), NULL); ck_assert_ptr_ne((claims_str = json_dumps(j_claims, JSON_COMPACT)), NULL); ck_assert_ptr_ne((claims_str_enc = ulfius_url_encode(claims_str)), NULL); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=code&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s&claims=%s", SERVER_URI, PLUGIN_NAME, CLIENT, CLIENT_REDIRECT_URI, SCOPE_LIST, claims_str_enc); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code=")); if (o_strchr(code, '&')) { *(o_strchr(code, '&')) = '\0'; } ulfius_clean_response(&resp); ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "authorization_code"); u_map_put(req.map_post_body, "client_id", CLIENT); u_map_put(req.map_post_body, "redirect_uri", CLIENT_REDIRECT_URI); u_map_put(req.map_post_body, "code", code); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_ptr_ne((j_body = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((refresh_token = json_string_value(json_object_get(j_body, "refresh_token"))), NULL); ulfius_clean_response(&resp); u_map_put(req.map_post_body, "grant_type", "refresh_token"); u_map_put(req.map_post_body, "refresh_token", refresh_token); u_map_remove_from_key(req.map_post_body, "client_id"); u_map_remove_from_key(req.map_post_body, "redirect_uri"); json_decref(j_body); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_ptr_ne((j_body = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((access_token = json_string_value(json_object_get(j_body, "access_token"))), NULL); ulfius_clean_response(&resp); bearer = msprintf("Bearer %s", access_token); u_map_put(req.map_header, "Authorization", bearer); j_result = json_pack("{sssss{ssssssssssss}sisosssss[ss]}", "claim-mandatory", CLAIM_MANDATORY, "claim-str", CLAIM_STR, "address", "formatted", ADDR_FORMATTED, "street_address", ADDR_STREET_ADDRESS, "locality", ADDR_LOCALITY, "country", ADDR_COUNTRY, "postal_code", ADDR_POSTAL_CODE, "region", ADDR_REGION, "claim-number", 42, "claim-bool", json_true(), "name", "Dave Lopper 1", "email", "dev1@glewlwyd", "scope", SCOPE_1, SCOPE_2); ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/" PLUGIN_NAME "/userinfo/", NULL, NULL, NULL, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_result); o_free(bearer); o_free(claims_str_enc); o_free(code); o_free(claims_str); json_decref(j_claims); json_decref(j_body); ulfius_clean_request(&req); } END_TEST START_TEST(test_oidc_claim_request_user1_request_jwt_id_token_claim_str_null) { jwt_t * jwt_request = NULL; char * request; struct _u_response resp; char * id_token, ** id_token_split, str_payload[1024] = {0}; size_t str_payload_len; json_t * j_result, * j_claims; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_NONE), RHN_OK); ck_assert_ptr_ne((j_claims = json_pack("{s{s{so}}}", "claims", "id_token", "claim-number", json_null())), NULL); r_jwt_set_full_claims_json_t(jwt_request, j_claims); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", "id_token"); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed_unsecure(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); ulfius_init_response(&resp); o_free(user_req.http_verb); o_free(user_req.http_url); user_req.http_verb = o_strdup("GET"); user_req.http_url = msprintf("%s/%s/auth?g_continue&request=%s", SERVER_URI, PLUGIN_NAME, request); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *(o_strchr(id_token, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((const unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_result = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-mandatory")), CLAIM_MANDATORY); ck_assert_int_eq(json_integer_value(json_object_get(j_result, "claim-number")), 42); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "formatted")), ADDR_FORMATTED); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "street_address")), ADDR_STREET_ADDRESS); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "locality")), ADDR_LOCALITY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "country")), ADDR_COUNTRY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "postal_code")), ADDR_POSTAL_CODE); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "region")), ADDR_REGION); ck_assert_ptr_eq(json_object_get(j_result, "claim-str"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "claim-bool"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "scope"), NULL); o_free(request); o_free(id_token); json_decref(j_result); json_decref(j_claims); r_jwt_free(jwt_request); free_string_array(id_token_split); } END_TEST START_TEST(test_oidc_claim_request_user1_request_jwt_id_token_claim_full) { jwt_t * jwt_request = NULL; char * request; struct _u_response resp; char * id_token, ** id_token_split, str_payload[1024] = {0}; size_t str_payload_len; json_t * j_result, * j_claims; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_NONE), RHN_OK); ck_assert_ptr_ne((j_claims = json_pack("{s{s{sosososososo}}}", "claims", "id_token", "claim-str", json_null(), "claim-number", json_null(), "claim-bool", json_null(), "name", json_null(), "email", json_null(), "scope", json_null())), NULL); r_jwt_set_full_claims_json_t(jwt_request, j_claims); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", "id_token"); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed_unsecure(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); ulfius_init_response(&resp); o_free(user_req.http_verb); o_free(user_req.http_url); user_req.http_verb = o_strdup("GET"); user_req.http_url = msprintf("%s/%s/auth?g_continue&request=%s", SERVER_URI, PLUGIN_NAME, request); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *(o_strchr(id_token, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((const unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_result = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-mandatory")), CLAIM_MANDATORY); ck_assert_str_eq(json_string_value(json_object_get(j_result, "claim-str")), CLAIM_STR); ck_assert_int_eq(json_integer_value(json_object_get(j_result, "claim-number")), 42); ck_assert_ptr_eq(json_object_get(j_result, "claim-bool"), json_true()); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "formatted")), ADDR_FORMATTED); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "street_address")), ADDR_STREET_ADDRESS); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "locality")), ADDR_LOCALITY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "country")), ADDR_COUNTRY); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "postal_code")), ADDR_POSTAL_CODE); ck_assert_str_eq(json_string_value(json_object_get(json_object_get(j_result, "address"), "region")), ADDR_REGION); ck_assert_str_eq(json_string_value(json_object_get(j_result, "name")), "Dave Lopper 1"); ck_assert_str_eq(json_string_value(json_object_get(j_result, "email")), "dev1@glewlwyd"); ck_assert_int_eq(json_array_size(json_object_get(j_result, "scope")), 2); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result, "scope"), 0)), SCOPE_1); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result, "scope"), 1)), SCOPE_2); o_free(request); o_free(id_token); json_decref(j_result); json_decref(j_claims); r_jwt_free(jwt_request); free_string_array(id_token_split); } END_TEST START_TEST(test_oidc_claim_request_delete_plugin) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); json_t * j_param = json_pack("{sosososososososososo}", "claim-str", json_null(), "claim-number", json_null(), "claim-bool", json_null(), "claim-mandatory", json_null(), "add-formatted", json_null(), "add-street_address", json_null(), "add-locality", json_null(), "add-region", json_null(), "add-postal_code", json_null(), "add-country", json_null()); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USER_USERNAME, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc claims request"); tc_core = tcase_create("test_oidc_claim_request"); tcase_add_test(tc_core, test_oidc_claim_request_add_plugin); tcase_add_test(tc_core, test_oidc_claim_request_user1_id_token_no_claim); tcase_add_test(tc_core, test_oidc_claim_request_user2_id_token_no_claim); tcase_add_test(tc_core, test_oidc_claim_request_user1_id_token_claim_invalid); tcase_add_test(tc_core, test_oidc_claim_request_user1_id_token_claim_str_null); tcase_add_test(tc_core, test_oidc_claim_request_user2_id_token_claim_str_null); tcase_add_test(tc_core, test_oidc_claim_request_user1_id_token_claim_str_value_found); tcase_add_test(tc_core, test_oidc_claim_request_user1_id_token_claim_str_value_not_found); tcase_add_test(tc_core, test_oidc_claim_request_user1_id_token_claim_str_values_found); tcase_add_test(tc_core, test_oidc_claim_request_user1_id_token_claim_str_values_not_found); tcase_add_test(tc_core, test_oidc_claim_request_user1_id_token_claim_number_value_found); tcase_add_test(tc_core, test_oidc_claim_request_user1_id_token_claim_number_values_found); tcase_add_test(tc_core, test_oidc_claim_request_user1_id_token_claim_boolean_value_found); tcase_add_test(tc_core, test_oidc_claim_request_user1_id_token_claim_boolean_values_found); tcase_add_test(tc_core, test_oidc_claim_request_user1_id_token_claim_full); tcase_add_test(tc_core, test_oidc_claim_request_user1_token_userinfo_claim_str_null); tcase_add_test(tc_core, test_oidc_claim_request_user1_token_userinfo_claim_full); tcase_add_test(tc_core, test_oidc_claim_request_user1_code_id_token_userinfo_claim_str_null); tcase_add_test(tc_core, test_oidc_claim_request_user1_code_id_token_userinfo_claim_full); tcase_add_test(tc_core, test_oidc_claim_request_user1_refresh_token_userinfo_claim_str_null); tcase_add_test(tc_core, test_oidc_claim_request_user1_refresh_token_userinfo_claim_full); tcase_add_test(tc_core, test_oidc_claim_request_user1_request_jwt_id_token_claim_str_null); tcase_add_test(tc_core, test_oidc_claim_request_user1_request_jwt_id_token_claim_full); tcase_add_test(tc_core, test_oidc_claim_request_delete_plugin); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req; struct _u_response auth_resp, scope_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); ulfius_init_request(&user_req); ulfius_init_request(&user2_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&scope_req); ulfius_init_response(&scope_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "User %s authenticated", USER_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(scope_req.map_header, "Cookie", cookie); u_map_put(user_req.map_header, "Cookie", cookie); o_free(cookie); scope_req.http_verb = strdup("PUT"); scope_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, &scope_resp) != U_OK || scope_resp.status != 200) { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope '%s' for %s error", CLIENT, SCOPE_LIST); do_test = 0; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope OK"); } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication user"); do_test = 0; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); ulfius_clean_response(&scope_resp); ulfius_clean_request(&scope_req); } if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&scope_req); ulfius_init_response(&scope_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER2_USERNAME, "password", USER2_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "User %s authenticated", USER2_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(scope_req.map_header, "Cookie", cookie); u_map_put(user2_req.map_header, "Cookie", cookie); o_free(cookie); scope_req.http_verb = strdup("PUT"); scope_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, &scope_resp) != U_OK || scope_resp.status != 200) { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope '%s' for %s error", CLIENT, SCOPE_LIST); do_test = 0; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope OK"); } } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication user"); do_test = 0; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); ulfius_clean_response(&scope_resp); ulfius_clean_request(&scope_req); } if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } char * url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); run_simple_test(&user_req, "PUT", url, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL); run_simple_test(&user2_req, "PUT", url, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL); o_free(url); json_decref(j_body); url = msprintf("%s/auth/", SERVER_URI); run_simple_test(&user_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL); o_free(url); ulfius_clean_request(&admin_req); ulfius_clean_request(&user_req); ulfius_clean_request(&user2_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_claims_scopes.c000066400000000000000000000354331415646314000222550ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "user1" #define USER_PASSWORD "password" #define PLUGIN_NAME "oidc_claims_scope" #define SCOPE1 "openid" #define SCOPE2 "g_profile" #define CLIENT "client1_id" #define CLIENT_REDIRECT_URI "../../test-oauth2.html?param=client1_cb1" #define RESPONSE_TYPE "id_token token" struct _u_request admin_req; struct _u_request user_req; START_TEST(test_oidc_claims_scopes_add_plugin) { json_t * j_param = json_pack("{sssssss{sssssssssisisisososososososososss[s]sss[s]s[{sssoss}{sssoss}{sss[s]ss}{sss[s]ss}]}}", "module", "oidc", "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", "https://glewlwyd.tld", "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_NAME, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "name-claim", "no", "name-claim-scope", SCOPE1, "email-claim", "no", "email-claim-scope", SCOPE2, "claims", "name", "claim-1", "mandatory", json_true(), "user-property", "claim-1", "name", "claim-2", "on-demand", json_true(), "user-property", "claim-2", "name", "claim-3", "scope", SCOPE1, "user-property", "claim-3", "name", "claim-4", "scope", SCOPE2, "user-property", "claim-4"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); j_param = json_pack("{ssssssss}", "claim-1", "claim1", "claim-2", "claim2", "claim-3", "claim3", "claim-4", "claim4"); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USER_USERNAME, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_claims_scopes_scope1) { struct _u_response resp; struct _u_request req; char * access_token, * bearer, * id_token, **id_token_split = NULL; unsigned char payload_dec[1024] = {0}; size_t payload_dec_len = 0; json_t * j_result, * j_payload; ulfius_init_response(&resp); ulfius_init_request(&req); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE, CLIENT, CLIENT_REDIRECT_URI, SCOPE1); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); access_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token=")); if (o_strchr(access_token, '&')) { *(o_strchr(access_token, '&')) = '\0'; } bearer = msprintf("Bearer %s", access_token); u_map_put(req.map_header, "Authorization", bearer); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *(o_strchr(id_token, '&')) = '\0'; } ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((const unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), payload_dec, &payload_dec_len), 1); ck_assert_ptr_ne((j_payload = json_loads((const char *)payload_dec, JSON_DECODE_ANY, NULL)), NULL); ck_assert_ptr_ne(json_object_get(j_payload, "claim-1"), NULL); ck_assert_ptr_eq(json_object_get(j_payload, "claim-2"), NULL); ck_assert_ptr_ne(json_object_get(j_payload, "claim-3"), NULL); ck_assert_ptr_eq(json_object_get(j_payload, "claim-4"), NULL); ck_assert_ptr_ne(json_object_get(j_payload, "name"), NULL); ck_assert_ptr_eq(json_object_get(j_payload, "email"), NULL); json_decref(j_payload); free_string_array(id_token_split); j_result = json_pack("{ssssss}", "claim-1", "claim1", "claim-3", "claim3", "name", "Dave Lopper 1"); ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/" PLUGIN_NAME "/userinfo/", NULL, NULL, NULL, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_result); ulfius_clean_response(&resp); ulfius_clean_request(&req); o_free(access_token); o_free(id_token); o_free(bearer); } END_TEST START_TEST(test_oidc_claims_scopes_claims_all) { struct _u_response resp; struct _u_request req; char * access_token, * bearer, * id_token, **id_token_split = NULL; unsigned char payload_dec[1024] = {0}; size_t payload_dec_len = 0; json_t * j_result, * j_payload; ulfius_init_response(&resp); ulfius_init_request(&req); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE, CLIENT, CLIENT_REDIRECT_URI, SCOPE1 " " SCOPE2); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); access_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token=")); if (o_strchr(access_token, '&')) { *(o_strchr(access_token, '&')) = '\0'; } bearer = msprintf("Bearer %s", access_token); u_map_put(req.map_header, "Authorization", bearer); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *(o_strchr(id_token, '&')) = '\0'; } ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((const unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), payload_dec, &payload_dec_len), 1); ck_assert_ptr_ne((j_payload = json_loads((const char *)payload_dec, JSON_DECODE_ANY, NULL)), NULL); ck_assert_ptr_ne(json_object_get(j_payload, "claim-1"), NULL); ck_assert_ptr_eq(json_object_get(j_payload, "claim-2"), NULL); ck_assert_ptr_ne(json_object_get(j_payload, "claim-3"), NULL); ck_assert_ptr_ne(json_object_get(j_payload, "claim-4"), NULL); ck_assert_ptr_ne(json_object_get(j_payload, "name"), NULL); ck_assert_ptr_ne(json_object_get(j_payload, "email"), NULL); json_decref(j_payload); free_string_array(id_token_split); j_result = json_pack("{ssssss}", "name", "Dave Lopper 1", "email", "dev1@glewlwyd", "claim-1", "claim1", "claim-3", "claim3", "claim-4", "claim4"); ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/" PLUGIN_NAME "/userinfo/", NULL, NULL, NULL, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_result); ulfius_clean_response(&resp); ulfius_clean_request(&req); o_free(access_token); o_free(id_token); o_free(bearer); } END_TEST START_TEST(test_oidc_claims_scopes_delete_plugin) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); json_t * j_param = json_pack("{sosososo}", "claim-1", json_null(), "claim-2", json_null(), "claim-3", json_null(), "claim-4", json_null()); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USER_USERNAME, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc claims scopes"); tc_core = tcase_create("test_oidc_claims_scopes"); tcase_add_test(tc_core, test_oidc_claims_scopes_add_plugin); tcase_add_test(tc_core, test_oidc_claims_scopes_scope1); tcase_add_test(tc_core, test_oidc_claims_scopes_claims_all); tcase_add_test(tc_core, test_oidc_claims_scopes_delete_plugin); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req; struct _u_response auth_resp, scope_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); ulfius_init_request(&user_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&scope_req); ulfius_init_response(&auth_resp); ulfius_init_response(&scope_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "User %s authenticated", USER_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(scope_req.map_header, "Cookie", cookie); u_map_put(user_req.map_header, "Cookie", cookie); o_free(cookie); scope_req.http_verb = strdup("PUT"); scope_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE1 " " SCOPE2); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, &scope_resp) != U_OK || scope_resp.status != 200) { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope '%s' for %s error", CLIENT, SCOPE1 " " SCOPE2); do_test = 0; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope OK"); } ulfius_clean_response(&scope_resp); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication user"); do_test = 0; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); } if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, NULL) != U_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "Remove grant scope '%s' for %s error", CLIENT, SCOPE1 " " SCOPE2); } char * url = msprintf("%s/auth/", SERVER_URI); run_simple_test(&user_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL); o_free(url); ulfius_clean_request(&admin_req); ulfius_clean_request(&user_req); ulfius_clean_request(&scope_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_client_certificate.c000066400000000000000000002455161415646314000232560ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "https://localhost:4593/api/" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "openid" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define PLUGIN_MODULE "oidc" #define PLUGIN_NAME "oidc_client_cert" #define PLUGIN_DISPLAY_NAME "oidc with client mtls authentication" #define PLUGIN_ISS "https://glewlwyd.tld" #define PLUGIN_CODE_DURATION 600 #define PLUGIN_REFRESH_TOKEN_DURATION 1209600 #define PLUGIN_ACCESS_TOKEN_DURATION 3600 #define CLIENT_ID "client_cert" #define CLIENT_NAME "client with certificate" #define CLIENT_SCOPE "scope2" #define CLIENT_REDIRECT_URI "https://client.glewlwyd.tld" #define CLIENT_SUBJECT_DN "CN=client-tls,O=babelouest" #define CLIENT_SAN_DNS "client.glewlwyd.tld" #define CLIENT_SAN_URI "https://client.glewlwyd.tld" #define CLIENT_SAN_IPV4 "1.2.3.4" #define CLIENT_SAN_IPV6 "2001:db8:85a3:8d3:1319:8a2e:370:7348" #define CLIENT_SAN_IPV6_LOCALHOST "::1" #define CLIENT_SAN_EMAIL "client-tls@client.glewlwyd.tld" #define ROOT_CA_CERT_1_PATH "cert/root1.crt" #define ROOT_CA_KEY_1_PATH "cert/root1.key" #define ROOT_CA_CERT_3_PATH "cert/root2.crt" #define ROOT_CA_KEY_3_PATH "cert/root2.key" #define CLIENT_CERT_1_PATH "cert/client1.crt" #define CLIENT_CERT_1_DER_PATH "cert/client1.crt.der" #define CLIENT_KEY_1_PATH "cert/client1.key" #define CLIENT_KEY_1_PASSWORD "" #define CLIENT_CERT_2_PATH "cert/client2.crt" #define CLIENT_CERT_2_DER_PATH "cert/client2.crt.der" #define CLIENT_KEY_2_PATH "cert/client2.key" #define CLIENT_KEY_2_PASSWORD "" #define CLIENT_CERT_3_PATH "cert/client3.crt" #define CLIENT_CERT_3_DER_PATH "cert/client3.crt.der" #define CLIENT_KEY_3_PATH "cert/client3.key" #define CLIENT_KEY_3_PASSWORD "" char client_cert_1_id[128]; char client_cert_2_id[128]; char client_cert_3_id[128]; static char * get_file_content(const char * file_path) { char * buffer = NULL; size_t length, res; FILE * f; f = fopen (file_path, "rb"); if (f) { fseek (f, 0, SEEK_END); length = ftell (f); fseek (f, 0, SEEK_SET); buffer = o_malloc((length+1)*sizeof(char)); if (buffer) { res = fread (buffer, 1, length, f); if (res != length) { fprintf(stderr, "fread warning, reading %zu while expecting %zu", res, length); } // Add null character at the end of buffer, just in case buffer[length] = '\0'; } fclose (f); } else { fprintf(stderr, "error opening file %s\n", file_path); } return buffer; } static int get_certificate_id(const char * file_path, unsigned char * certificate_id) { char * cert_content = get_file_content(file_path); unsigned char cert_digest[128]; size_t cert_digest_len = 128, certificate_id_len; gnutls_x509_crt_t cert = NULL; gnutls_datum_t cert_dat, dat; int ret = 0; if (!gnutls_x509_crt_init(&cert)) { cert_dat.data = (unsigned char *)cert_content; cert_dat.size = o_strlen(cert_content); if (gnutls_x509_crt_import(cert, &cert_dat, GNUTLS_X509_FMT_PEM) >= 0) { if (gnutls_x509_crt_export2(cert, GNUTLS_X509_FMT_DER, &dat) >= 0) { if (gnutls_fingerprint(GNUTLS_DIG_SHA256, &dat, cert_digest, &cert_digest_len) == GNUTLS_E_SUCCESS) { if (o_base64_encode(cert_digest, cert_digest_len, certificate_id, &certificate_id_len)) { certificate_id[certificate_id_len] = '\0'; ret = 1; } } gnutls_free(dat.data); } } } gnutls_x509_crt_deinit(cert); o_free(cert_content); return ret; } struct _u_request admin_req; struct _u_request user_req; START_TEST(test_oidc_client_certificate_add_module_both_no_alias_ok) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisososososososososssssosososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", "sha", "jwt-key-size", "256", "key", "secret", "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "auth-type-device-enabled", json_true(), "client-cert-source", "both", "client-cert-header-name", "x509", "client-cert-self-signed-allowed", json_true(), "client-cert-use-endpoint-aliases", json_false(), "introspection-revocation-allowed", json_true(), "introspection-revocation-allow-target-client", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_client_certificate_add_module_header_no_alias_ok) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisososososososososssssosososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", "sha", "jwt-key-size", "256", "key", "secret", "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "auth-type-device-enabled", json_true(), "client-cert-source", "header", "client-cert-header-name", "x509", "client-cert-self-signed-allowed", json_true(), "client-cert-use-endpoint-aliases", json_false(), "introspection-revocation-allowed", json_true(), "introspection-revocation-allow-target-client", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_client_certificate_add_module_tls_no_alias_ok) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisososososososososssssosososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", "sha", "jwt-key-size", "256", "key", "secret", "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "auth-type-device-enabled", json_true(), "client-cert-source", "TLS", "client-cert-header-name", "x509", "client-cert-self-signed-allowed", json_true(), "client-cert-use-endpoint-aliases", json_false(), "introspection-revocation-allowed", json_true(), "introspection-revocation-allow-target-client", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_client_certificate_add_module_both_with_alias_ok) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisososososososososssssosososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", "sha", "jwt-key-size", "256", "key", "secret", "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "auth-type-device-enabled", json_true(), "client-cert-source", "both", "client-cert-header-name", "x509", "client-cert-self-signed-allowed", json_true(), "client-cert-use-endpoint-aliases", json_true(), "introspection-revocation-allowed", json_true(), "introspection-revocation-allow-target-client", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_client_certificate_add_client_with_certificate_dn) { json_t * j_parameters = json_pack("{sssss[s]sos[sssss]sssss[s]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "scope", CLIENT_SCOPE, "confidential", json_true(), "authorization_type", "code", "client_credentials", "refresh_token", "password", "device_authorization", "token_endpoint_auth_method", "tls_client_auth", "tls_client_auth_subject_dn", CLIENT_SUBJECT_DN, "redirect_uri", CLIENT_REDIRECT_URI, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_client_certificate_add_client_with_invalid_certificate_dn) { json_t * j_parameters = json_pack("{sssss[s]sos[sssss]sssss[s]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "scope", CLIENT_SCOPE, "confidential", json_true(), "authorization_type", "code", "client_credentials", "refresh_token", "password", "device_authorization", "token_endpoint_auth_method", "tls_client_auth", "tls_client_auth_subject_dn", CLIENT_SUBJECT_DN ",DN=error", "redirect_uri", CLIENT_REDIRECT_URI, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_client_certificate_add_client_with_san_dns) { json_t * j_parameters = json_pack("{sssss[s]sos[sssss]sssss[s]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "scope", CLIENT_SCOPE, "confidential", json_true(), "authorization_type", "code", "client_credentials", "refresh_token", "password", "device_authorization", "token_endpoint_auth_method", "tls_client_auth", "tls_client_auth_san_dns", CLIENT_SAN_DNS, "redirect_uri", CLIENT_REDIRECT_URI, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_client_certificate_add_client_with_san_uri) { json_t * j_parameters = json_pack("{sssss[s]sos[sssss]sssss[s]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "scope", CLIENT_SCOPE, "confidential", json_true(), "authorization_type", "code", "client_credentials", "refresh_token", "password", "device_authorization", "token_endpoint_auth_method", "tls_client_auth", "tls_client_auth_san_uri", CLIENT_SAN_URI, "redirect_uri", CLIENT_REDIRECT_URI, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_client_certificate_add_client_with_san_ipv4) { json_t * j_parameters = json_pack("{sssss[s]sos[sssss]sssss[s]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "scope", CLIENT_SCOPE, "confidential", json_true(), "authorization_type", "code", "client_credentials", "refresh_token", "password", "device_authorization", "token_endpoint_auth_method", "tls_client_auth", "tls_client_auth_san_ip", CLIENT_SAN_IPV4, "redirect_uri", CLIENT_REDIRECT_URI, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_client_certificate_add_client_with_san_ipv6) { json_t * j_parameters = json_pack("{sssss[s]sos[sssss]sssss[s]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "scope", CLIENT_SCOPE, "confidential", json_true(), "authorization_type", "code", "client_credentials", "refresh_token", "password", "device_authorization", "token_endpoint_auth_method", "tls_client_auth", "tls_client_auth_san_ip", CLIENT_SAN_IPV6, "redirect_uri", CLIENT_REDIRECT_URI, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_client_certificate_add_client_with_san_ipv6_localhost) { json_t * j_parameters = json_pack("{sssss[s]sos[sssss]sssss[s]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "scope", CLIENT_SCOPE, "confidential", json_true(), "authorization_type", "code", "client_credentials", "refresh_token", "password", "device_authorization", "token_endpoint_auth_method", "tls_client_auth", "tls_client_auth_san_ip", CLIENT_SAN_IPV6_LOCALHOST, "redirect_uri", CLIENT_REDIRECT_URI, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_client_certificate_add_client_with_san_email) { json_t * j_parameters = json_pack("{sssss[s]sos[sssss]sssss[s]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "scope", CLIENT_SCOPE, "confidential", json_true(), "authorization_type", "code", "client_credentials", "refresh_token", "password", "device_authorization", "token_endpoint_auth_method", "tls_client_auth", "tls_client_auth_san_email", CLIENT_SAN_EMAIL, "redirect_uri", CLIENT_REDIRECT_URI, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_client_certificate_add_client_with_self_signed_certificate) { char * cert_content = get_file_content(CLIENT_CERT_3_PATH); jwk_t * jwk; jwks_t * jwks; r_jwk_init(&jwk); r_jwks_init(&jwks); r_jwk_import_from_pem_der(jwk, R_X509_TYPE_CERTIFICATE, R_FORMAT_PEM, (const unsigned char *)cert_content, o_strlen(cert_content)); r_jwks_append_jwk(jwks, jwk); json_t * j_parameters = json_pack("{sssss[s]sos[sssss]sssos[s]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "scope", CLIENT_SCOPE, "confidential", json_true(), "authorization_type", "code", "client_credentials", "refresh_token", "password", "device_authorization", "token_endpoint_auth_method", "self_signed_tls_client_auth", "jwks", r_jwks_export_to_json_t(jwks), "redirect_uri", CLIENT_REDIRECT_URI, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(cert_content); r_jwk_free(jwk); r_jwks_free(jwks); } END_TEST START_TEST(test_oidc_client_certificate_add_client_with_invalid_san_dns) { json_t * j_parameters = json_pack("{sssss[s]sos[sssss]sssss[s]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "scope", CLIENT_SCOPE, "confidential", json_true(), "authorization_type", "code", "client_credentials", "refresh_token", "password", "device_authorization", "token_endpoint_auth_method", "tls_client_auth", "tls_client_auth_san_dns", "error.error", "redirect_uri", CLIENT_REDIRECT_URI, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_client_certificate_add_client_with_invalid_san_uri) { json_t * j_parameters = json_pack("{sssss[s]sos[sssss]sssss[s]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "scope", CLIENT_SCOPE, "confidential", json_true(), "authorization_type", "code", "client_credentials", "refresh_token", "password", "device_authorization", "token_endpoint_auth_method", "tls_client_auth", "tls_client_auth_san_uri", "https://error.error", "redirect_uri", CLIENT_REDIRECT_URI, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_client_certificate_add_client_with_invalid_san_ipv4) { json_t * j_parameters = json_pack("{sssss[s]sos[sssss]sssss[s]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "scope", CLIENT_SCOPE, "confidential", json_true(), "authorization_type", "code", "client_credentials", "refresh_token", "password", "device_authorization", "token_endpoint_auth_method", "tls_client_auth", "tls_client_auth_san_ip", "2.6.8.10", "redirect_uri", CLIENT_REDIRECT_URI, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_client_certificate_add_client_with_invalid_san_ipv6) { json_t * j_parameters = json_pack("{sssss[s]sos[sssss]sssss[s]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "scope", CLIENT_SCOPE, "confidential", json_true(), "authorization_type", "code", "client_credentials", "refresh_token", "password", "device_authorization", "token_endpoint_auth_method", "tls_client_auth", "tls_client_auth_san_ip", "2001:db6:85a3:8d1:1319:8a24:370:73e8", "redirect_uri", CLIENT_REDIRECT_URI, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_client_certificate_add_client_with_invalid_san_email) { json_t * j_parameters = json_pack("{sssss[s]sos[sssss]sssss[s]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "scope", CLIENT_SCOPE, "confidential", json_true(), "authorization_type", "code", "client_credentials", "refresh_token", "password", "device_authorization", "token_endpoint_auth_method", "tls_client_auth", "tls_client_auth_san_email", "error@error.error", "redirect_uri", CLIENT_REDIRECT_URI, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_client_certificate_delete_module) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_client_certificate_delete_client) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_ID, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_client_certificate_client_cred_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp, * j_claims; jwt_t * jwt; ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token/", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "grant_type", "client_credentials", U_OPT_POST_BODY_PARAMETER, "scope", CLIENT_SCOPE, U_OPT_CLIENT_CERT_FILE, CLIENT_CERT_1_PATH, U_OPT_CLIENT_KEY_FILE, CLIENT_KEY_1_PATH, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_gt(json_string_length(json_object_get(j_resp, "access_token")), 0); r_jwt_init(&jwt); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_ptr_ne(NULL, j_claims = r_jwt_get_full_claims_json_t(jwt)); ck_assert_int_gt(json_string_length(json_object_get(json_object_get(j_claims, "cnf"), "x5t#S256")), 0); json_decref(j_claims); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_client_certificate_client_cred_ca_invalid) { struct _u_request req; struct _u_response resp; ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token/", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "grant_type", "client_credentials", U_OPT_POST_BODY_PARAMETER, "scope", CLIENT_SCOPE, U_OPT_CLIENT_CERT_FILE, CLIENT_CERT_2_PATH, U_OPT_CLIENT_KEY_FILE, CLIENT_KEY_2_PATH, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ck_assert_ptr_eq(NULL, ulfius_get_json_body_response(&resp, NULL)); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_client_certificate_client_cred_self_signed_invalid) { struct _u_request req; struct _u_response resp; ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token/", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "grant_type", "client_credentials", U_OPT_POST_BODY_PARAMETER, "scope", CLIENT_SCOPE, U_OPT_CLIENT_CERT_FILE, CLIENT_CERT_3_PATH, U_OPT_CLIENT_KEY_FILE, CLIENT_KEY_3_PATH, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ck_assert_ptr_eq(NULL, ulfius_get_json_body_response(&resp, NULL)); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_client_certificate_client_cred_client_id_invalid) { struct _u_request req; struct _u_response resp; ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token/", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "client_id", "error", U_OPT_POST_BODY_PARAMETER, "grant_type", "client_credentials", U_OPT_POST_BODY_PARAMETER, "scope", CLIENT_SCOPE, U_OPT_CLIENT_CERT_FILE, CLIENT_CERT_1_PATH, U_OPT_CLIENT_KEY_FILE, CLIENT_KEY_1_PATH, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_client_certificate_client_cred_cert_invalid) { struct _u_request req; struct _u_response resp; ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token/", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "grant_type", "client_credentials", U_OPT_POST_BODY_PARAMETER, "scope", CLIENT_SCOPE, U_OPT_CLIENT_CERT_FILE, CLIENT_CERT_1_PATH, U_OPT_CLIENT_KEY_FILE, CLIENT_KEY_1_PATH, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_client_certificate_code_valid) { json_t * j_body, * j_resp, * j_claims; struct _u_request req; struct _u_response resp; char * code, * cookie, * refresh_token, * access_token; jwt_t * jwt; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/auth/", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_JSON_BODY, j_body, U_OPT_NONE), U_OK); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); cookie = msprintf("%s=%s", resp.map_cookie[0].key, resp.map_cookie[0].value); ck_assert_int_eq(u_map_put(req.map_header, "Cookie", cookie), U_OK); o_free(cookie); ulfius_clean_response(&resp); // Set grant ulfius_init_response(&resp); j_body = json_pack("{ss}", "scope", "openid"); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "PUT", U_OPT_HTTP_URL, SERVER_URI "/auth/grant/" CLIENT_ID, U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_JSON_BODY, j_body, U_OPT_NONE), U_OK); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&g_continue&client_id=" CLIENT_ID "&redirect_uri=" CLIENT_REDIRECT_URI "&state=xyzabcd&nonce=nonce1234&scope=openid", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=")+o_strlen("code=")); if (o_strstr(code, "&") != NULL) { *o_strstr(code, "&") = '\0'; } ulfius_clean_response(&resp); // Clean grant ulfius_init_response(&resp); j_body = json_pack("{ss}", "scope", ""); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "PUT", U_OPT_HTTP_URL, SERVER_URI "/auth/grant/" CLIENT_ID, U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_JSON_BODY, j_body, U_OPT_NONE), U_OK); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ulfius_clean_request(&req); // Get tokens ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_CLIENT_CERT_FILE, CLIENT_CERT_1_PATH, U_OPT_CLIENT_KEY_FILE, CLIENT_KEY_1_PATH, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(json_object_get(j_body, "refresh_token"), NULL); ck_assert_ptr_ne(json_object_get(j_body, "access_token"), NULL); refresh_token = o_strdup(json_string_value(json_object_get(j_body, "refresh_token"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Refresh token ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "grant_type", "refresh_token", U_OPT_POST_BODY_PARAMETER, "refresh_token", refresh_token, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_CLIENT_CERT_FILE, CLIENT_CERT_1_PATH, U_OPT_CLIENT_KEY_FILE, CLIENT_KEY_1_PATH, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_gt(json_string_length(json_object_get(j_resp, "access_token")), 0); r_jwt_init(&jwt); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); access_token = o_strdup(json_string_value(json_object_get(j_resp, "access_token"))); ck_assert_ptr_ne(NULL, j_claims = r_jwt_get_full_claims_json_t(jwt)); ck_assert_int_gt(json_string_length(json_object_get(json_object_get(j_claims, "cnf"), "x5t#S256")), 0); json_decref(j_claims); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); // Introspect token ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "token", access_token, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_CLIENT_CERT_FILE, CLIENT_CERT_1_PATH, U_OPT_CLIENT_KEY_FILE, CLIENT_KEY_1_PATH, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_gt(json_string_length(json_object_get(json_object_get(j_resp, "cnf"), "x5t#S256")), 0); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Revoke token ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/revoke", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "token", access_token, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_CLIENT_CERT_FILE, CLIENT_CERT_1_PATH, U_OPT_CLIENT_KEY_FILE, CLIENT_KEY_1_PATH, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); o_free(access_token); o_free(code); o_free(refresh_token); } END_TEST START_TEST(test_oidc_client_certificate_code_ca_invalid) { json_t * j_body; struct _u_request req; struct _u_response resp; char * code, * cookie; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/auth/", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_JSON_BODY, j_body, U_OPT_NONE), U_OK); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); cookie = msprintf("%s=%s", resp.map_cookie[0].key, resp.map_cookie[0].value); ck_assert_int_eq(u_map_put(req.map_header, "Cookie", cookie), U_OK); o_free(cookie); ulfius_clean_response(&resp); // Set grant ulfius_init_response(&resp); j_body = json_pack("{ss}", "scope", "openid"); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "PUT", U_OPT_HTTP_URL, SERVER_URI "/auth/grant/" CLIENT_ID, U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_JSON_BODY, j_body, U_OPT_NONE), U_OK); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&g_continue&client_id=" CLIENT_ID "&redirect_uri=" CLIENT_REDIRECT_URI "&state=xyzabcd&nonce=nonce1234&scope=openid", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=")+o_strlen("code=")); if (o_strstr(code, "&") != NULL) { *o_strstr(code, "&") = '\0'; } ulfius_clean_response(&resp); // Clean grant ulfius_init_response(&resp); j_body = json_pack("{ss}", "scope", ""); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "PUT", U_OPT_HTTP_URL, SERVER_URI "/auth/grant/" CLIENT_ID, U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_JSON_BODY, j_body, U_OPT_NONE), U_OK); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ulfius_clean_request(&req); // Get tokens ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_CLIENT_CERT_FILE, CLIENT_CERT_2_PATH, U_OPT_CLIENT_KEY_FILE, CLIENT_KEY_2_PATH, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); ulfius_clean_request(&req); ulfius_clean_response(&resp); o_free(code); } END_TEST START_TEST(test_oidc_client_certificate_code_self_signed_invalid) { json_t * j_body; struct _u_request req; struct _u_response resp; char * code, * cookie; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/auth/", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_JSON_BODY, j_body, U_OPT_NONE), U_OK); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); cookie = msprintf("%s=%s", resp.map_cookie[0].key, resp.map_cookie[0].value); ck_assert_int_eq(u_map_put(req.map_header, "Cookie", cookie), U_OK); o_free(cookie); ulfius_clean_response(&resp); // Set grant ulfius_init_response(&resp); j_body = json_pack("{ss}", "scope", "openid"); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "PUT", U_OPT_HTTP_URL, SERVER_URI "/auth/grant/" CLIENT_ID, U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_JSON_BODY, j_body, U_OPT_NONE), U_OK); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&g_continue&client_id=" CLIENT_ID "&redirect_uri=" CLIENT_REDIRECT_URI "&state=xyzabcd&nonce=nonce1234&scope=openid", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=")+o_strlen("code=")); if (o_strstr(code, "&") != NULL) { *o_strstr(code, "&") = '\0'; } ulfius_clean_response(&resp); // Clean grant ulfius_init_response(&resp); j_body = json_pack("{ss}", "scope", ""); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "PUT", U_OPT_HTTP_URL, SERVER_URI "/auth/grant/" CLIENT_ID, U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_JSON_BODY, j_body, U_OPT_NONE), U_OK); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ulfius_clean_request(&req); // Get tokens ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_CLIENT_CERT_FILE, CLIENT_CERT_3_PATH, U_OPT_CLIENT_KEY_FILE, CLIENT_KEY_3_PATH, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); ulfius_clean_request(&req); ulfius_clean_response(&resp); o_free(code); } END_TEST START_TEST(test_oidc_client_certificate_resource_owner_pwd_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp, * j_claims; jwt_t * jwt; ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token/", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "grant_type", "password", U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "username", USERNAME, U_OPT_POST_BODY_PARAMETER, "password", PASSWORD, U_OPT_CLIENT_CERT_FILE, CLIENT_CERT_1_PATH, U_OPT_CLIENT_KEY_FILE, CLIENT_KEY_1_PATH, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_gt(json_string_length(json_object_get(j_resp, "access_token")), 0); r_jwt_init(&jwt); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_ptr_ne(NULL, j_claims = r_jwt_get_full_claims_json_t(jwt)); ck_assert_int_gt(json_string_length(json_object_get(json_object_get(j_claims, "cnf"), "x5t#S256")), 0); json_decref(j_claims); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_client_certificate_resource_owner_pwd_ca_invalid) { struct _u_request req; struct _u_response resp; ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token/", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "grant_type", "password", U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "username", USERNAME, U_OPT_POST_BODY_PARAMETER, "password", PASSWORD, U_OPT_CLIENT_CERT_FILE, CLIENT_CERT_2_PATH, U_OPT_CLIENT_KEY_FILE, CLIENT_KEY_2_PATH, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ck_assert_ptr_eq(NULL, ulfius_get_json_body_response(&resp, NULL)); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_client_certificate_resource_owner_pwd_self_signed_invalid) { struct _u_request req; struct _u_response resp; ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token/", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "grant_type", "password", U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "username", USERNAME, U_OPT_POST_BODY_PARAMETER, "password", PASSWORD, U_OPT_CLIENT_CERT_FILE, CLIENT_CERT_3_PATH, U_OPT_CLIENT_KEY_FILE, CLIENT_KEY_3_PATH, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ck_assert_ptr_eq(NULL, ulfius_get_json_body_response(&resp, NULL)); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_client_certificate_device_authorization_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp, * j_grant, * j_claims; const char * redirect_uri, * code, * device_code; jwt_t * jwt; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/device_authorization/", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "grant_type", "device_authorization", U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_CLIENT_CERT_FILE, CLIENT_CERT_1_PATH, U_OPT_CLIENT_KEY_FILE, CLIENT_KEY_1_PATH, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), SERVER_URI PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", SCOPE_LIST); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", ""); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token/", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "grant_type", "urn:ietf:params:oauth:grant-type:device_code", U_OPT_POST_BODY_PARAMETER, "device_code", device_code, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_CLIENT_CERT_FILE, CLIENT_CERT_1_PATH, U_OPT_CLIENT_KEY_FILE, CLIENT_KEY_1_PATH, U_OPT_NONE), U_OK); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "refresh_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "id_token"), NULL); r_jwt_init(&jwt); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_ptr_ne(NULL, j_claims = r_jwt_get_full_claims_json_t(jwt)); ck_assert_int_gt(json_string_length(json_object_get(json_object_get(j_claims, "cnf"), "x5t#S256")), 0); json_decref(j_claims); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_client_certificate_device_authorization_ca_invalid) { struct _u_request req; struct _u_response resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/device_authorization/", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "grant_type", "device_authorization", U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_CLIENT_CERT_FILE, CLIENT_CERT_2_PATH, U_OPT_CLIENT_KEY_FILE, CLIENT_KEY_2_PATH, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(403, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_client_certificate_device_authorization_self_signed_invalid) { struct _u_request req; struct _u_response resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/device_authorization/", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "grant_type", "device_authorization", U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_CLIENT_CERT_FILE, CLIENT_CERT_3_PATH, U_OPT_CLIENT_KEY_FILE, CLIENT_KEY_3_PATH, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(403, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_client_certificate_client_cred_with_alias_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp, * j_claims; jwt_t * jwt; ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token/", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "grant_type", "client_credentials", U_OPT_POST_BODY_PARAMETER, "scope", CLIENT_SCOPE, U_OPT_CLIENT_CERT_FILE, CLIENT_CERT_1_PATH, U_OPT_CLIENT_KEY_FILE, CLIENT_KEY_1_PATH, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/mtls/token/", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "grant_type", "client_credentials", U_OPT_POST_BODY_PARAMETER, "scope", CLIENT_SCOPE, U_OPT_CLIENT_CERT_FILE, CLIENT_CERT_1_PATH, U_OPT_CLIENT_KEY_FILE, CLIENT_KEY_1_PATH, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_gt(json_string_length(json_object_get(j_resp, "access_token")), 0); r_jwt_init(&jwt); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_ptr_ne(NULL, j_claims = r_jwt_get_full_claims_json_t(jwt)); ck_assert_int_gt(json_string_length(json_object_get(json_object_get(j_claims, "cnf"), "x5t#S256")), 0); json_decref(j_claims); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_client_certificate_client_cred_header_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp, * j_claims; jwt_t * jwt; char * cert_content = get_file_content(CLIENT_CERT_1_PATH), * cert_content_escaped = str_replace(cert_content, "\n", ""); ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token/", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "grant_type", "client_credentials", U_OPT_POST_BODY_PARAMETER, "scope", CLIENT_SCOPE, U_OPT_HEADER_PARAMETER, "x509", cert_content_escaped, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_gt(json_string_length(json_object_get(j_resp, "access_token")), 0); r_jwt_init(&jwt); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_ptr_ne(NULL, j_claims = r_jwt_get_full_claims_json_t(jwt)); ck_assert_int_gt(json_string_length(json_object_get(json_object_get(j_claims, "cnf"), "x5t#S256")), 0); json_decref(j_claims); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); o_free(cert_content); o_free(cert_content_escaped); } END_TEST START_TEST(test_oidc_client_certificate_client_cred_header_invalid) { struct _u_request req; struct _u_response resp; char * cert_content = get_file_content(CLIENT_CERT_1_PATH), * cert_content_escaped = str_replace(cert_content, "\n", ""); ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token/", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "grant_type", "client_credentials", U_OPT_POST_BODY_PARAMETER, "scope", CLIENT_SCOPE, U_OPT_HEADER_PARAMETER, "x509", cert_content_escaped, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); o_free(cert_content); o_free(cert_content_escaped); } END_TEST START_TEST(test_oidc_client_certificate_client_cred_self_signed_header_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp, * j_claims; jwt_t * jwt; char * cert_content = get_file_content(CLIENT_CERT_3_PATH), * cert_content_escaped = str_replace(cert_content, "\n", ""); ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token/", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "grant_type", "client_credentials", U_OPT_POST_BODY_PARAMETER, "scope", CLIENT_SCOPE, U_OPT_HEADER_PARAMETER, "x509", cert_content_escaped, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_gt(json_string_length(json_object_get(j_resp, "access_token")), 0); r_jwt_init(&jwt); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_ptr_ne(NULL, j_claims = r_jwt_get_full_claims_json_t(jwt)); ck_assert_int_gt(json_string_length(json_object_get(json_object_get(j_claims, "cnf"), "x5t#S256")), 0); json_decref(j_claims); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); o_free(cert_content); o_free(cert_content_escaped); } END_TEST START_TEST(test_oidc_client_certificate_client_cred_self_signed_header_invalid_cert) { struct _u_request req; struct _u_response resp; char * cert_content = get_file_content(CLIENT_CERT_2_PATH), * cert_content_escaped = str_replace(cert_content, "\n", ""); ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token/", U_OPT_CHECK_SERVER_CERTIFICATE, 0, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "grant_type", "client_credentials", U_OPT_POST_BODY_PARAMETER, "scope", CLIENT_SCOPE, U_OPT_HEADER_PARAMETER, "x509", cert_content_escaped, U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); o_free(cert_content); o_free(cert_content_escaped); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc client certificate"); tc_core = tcase_create("test_glwd_oidc_client_certificate"); tcase_add_test(tc_core, test_oidc_client_certificate_add_module_both_no_alias_ok); tcase_add_test(tc_core, test_oidc_client_certificate_add_client_with_certificate_dn); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_valid); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_header_valid); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_ca_invalid); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_self_signed_invalid); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_client_id_invalid); tcase_add_test(tc_core, test_oidc_client_certificate_code_valid); tcase_add_test(tc_core, test_oidc_client_certificate_code_ca_invalid); tcase_add_test(tc_core, test_oidc_client_certificate_code_self_signed_invalid); tcase_add_test(tc_core, test_oidc_client_certificate_resource_owner_pwd_valid); tcase_add_test(tc_core, test_oidc_client_certificate_resource_owner_pwd_ca_invalid); tcase_add_test(tc_core, test_oidc_client_certificate_resource_owner_pwd_self_signed_invalid); tcase_add_test(tc_core, test_oidc_client_certificate_device_authorization_valid); tcase_add_test(tc_core, test_oidc_client_certificate_device_authorization_ca_invalid); tcase_add_test(tc_core, test_oidc_client_certificate_device_authorization_self_signed_invalid); tcase_add_test(tc_core, test_oidc_client_certificate_delete_client); tcase_add_test(tc_core, test_oidc_client_certificate_add_client_with_invalid_certificate_dn); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_cert_invalid); tcase_add_test(tc_core, test_oidc_client_certificate_delete_client); tcase_add_test(tc_core, test_oidc_client_certificate_add_client_with_san_dns); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_valid); tcase_add_test(tc_core, test_oidc_client_certificate_delete_client); tcase_add_test(tc_core, test_oidc_client_certificate_add_client_with_invalid_san_dns); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_cert_invalid); tcase_add_test(tc_core, test_oidc_client_certificate_delete_client); tcase_add_test(tc_core, test_oidc_client_certificate_add_client_with_san_uri); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_valid); tcase_add_test(tc_core, test_oidc_client_certificate_delete_client); tcase_add_test(tc_core, test_oidc_client_certificate_add_client_with_invalid_san_uri); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_cert_invalid); tcase_add_test(tc_core, test_oidc_client_certificate_delete_client); tcase_add_test(tc_core, test_oidc_client_certificate_add_client_with_san_ipv4); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_valid); tcase_add_test(tc_core, test_oidc_client_certificate_delete_client); tcase_add_test(tc_core, test_oidc_client_certificate_add_client_with_invalid_san_ipv4); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_cert_invalid); tcase_add_test(tc_core, test_oidc_client_certificate_delete_client); tcase_add_test(tc_core, test_oidc_client_certificate_add_client_with_san_ipv6); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_valid); tcase_add_test(tc_core, test_oidc_client_certificate_delete_client); tcase_add_test(tc_core, test_oidc_client_certificate_add_client_with_invalid_san_ipv6); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_cert_invalid); tcase_add_test(tc_core, test_oidc_client_certificate_delete_client); tcase_add_test(tc_core, test_oidc_client_certificate_add_client_with_san_ipv6_localhost); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_valid); tcase_add_test(tc_core, test_oidc_client_certificate_delete_client); tcase_add_test(tc_core, test_oidc_client_certificate_add_client_with_san_email); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_valid); tcase_add_test(tc_core, test_oidc_client_certificate_delete_client); tcase_add_test(tc_core, test_oidc_client_certificate_add_client_with_invalid_san_email); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_cert_invalid); tcase_add_test(tc_core, test_oidc_client_certificate_delete_client); tcase_add_test(tc_core, test_oidc_client_certificate_add_client_with_self_signed_certificate); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_self_signed_header_valid); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_self_signed_header_invalid_cert); tcase_add_test(tc_core, test_oidc_client_certificate_delete_client); tcase_add_test(tc_core, test_oidc_client_certificate_delete_module); tcase_add_test(tc_core, test_oidc_client_certificate_add_module_header_no_alias_ok); tcase_add_test(tc_core, test_oidc_client_certificate_add_client_with_certificate_dn); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_cert_invalid); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_header_valid); tcase_add_test(tc_core, test_oidc_client_certificate_delete_client); tcase_add_test(tc_core, test_oidc_client_certificate_delete_module); tcase_add_test(tc_core, test_oidc_client_certificate_add_module_tls_no_alias_ok); tcase_add_test(tc_core, test_oidc_client_certificate_add_client_with_certificate_dn); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_valid); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_header_invalid); tcase_add_test(tc_core, test_oidc_client_certificate_delete_client); tcase_add_test(tc_core, test_oidc_client_certificate_delete_module); tcase_add_test(tc_core, test_oidc_client_certificate_add_module_both_with_alias_ok); tcase_add_test(tc_core, test_oidc_client_certificate_add_client_with_certificate_dn); tcase_add_test(tc_core, test_oidc_client_certificate_client_cred_with_alias_valid); tcase_add_test(tc_core, test_oidc_client_certificate_delete_client); tcase_add_test(tc_core, test_oidc_client_certificate_delete_module); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&admin_req); admin_req.check_server_certificate = 0; ulfius_init_request(&user_req); user_req.check_server_certificate = 0; ulfius_init_request(&auth_req); auth_req.check_server_certificate = 0; ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api/oidc" #define CLIENT_ID "client3_id" #define CLIENT_PASSWORD "password" #define SCOPE_LIST "scope2 scope3" struct _u_request user_req; char * code; START_TEST(test_oidc_client_cred_valid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", SCOPE_LIST); int res = run_simple_test(NULL, "POST", url, CLIENT_ID, CLIENT_PASSWORD, NULL, &body, 200, NULL, "access_token", NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_client_cred_valid_reduced_scope) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", SCOPE_LIST " scope1"); int res = run_simple_test(NULL, "POST", url, CLIENT_ID, CLIENT_PASSWORD, NULL, &body, 200, NULL, "scope\":\"scope2 scope3\"", NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_client_cred_pwd_invalid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", SCOPE_LIST); int res = run_simple_test(NULL, "POST", url, CLIENT_ID, "invalid", NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_client_cred_client_unauthorized) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", SCOPE_LIST); int res = run_simple_test(NULL, "POST", url, "client1_id", CLIENT_PASSWORD, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_client_cred_client_invalid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", SCOPE_LIST); int res = run_simple_test(NULL, "POST", url, "invalid", CLIENT_PASSWORD, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_client_cred_scope_invalid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", "scope4"); int res = run_simple_test(NULL, "POST", url, CLIENT_ID, CLIENT_PASSWORD, NULL, &body, 400, NULL, "scope_invalid", NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_client_cred_empty) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", SCOPE_LIST); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc client credentials"); tc_core = tcase_create("test_oidc_client_cred"); tcase_add_test(tc_core, test_oidc_client_cred_valid); tcase_add_test(tc_core, test_oidc_client_cred_valid_reduced_scope); tcase_add_test(tc_core, test_oidc_client_cred_pwd_invalid); tcase_add_test(tc_core, test_oidc_client_cred_client_unauthorized); tcase_add_test(tc_core, test_oidc_client_cred_client_invalid); tcase_add_test(tc_core, test_oidc_client_cred_scope_invalid); tcase_add_test(tc_core, test_oidc_client_cred_empty); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed; Suite *s; SRunner *sr; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); ulfius_clean_request(&user_req); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_client_registration.c000066400000000000000000002276621415646314000235100ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define PLUGIN_MODULE "oidc" #define PLUGIN_NAME "register" #define PLUGIN_ISS "https://glewlwyd.tld" #define PLUGIN_PAIRWISE "pairwise" #define PLUGIN_DISPLAY_NAME "Client registration test" #define PLUGIN_JWT_TYPE "sha" #define PLUGIN_JWT_KEY_SIZE "256" #define PLUGIN_KEY "secret" #define PLUGIN_CODE_DURATION 600 #define PLUGIN_REFRESH_TOKEN_DURATION 1209600 #define PLUGIN_ACCESS_TOKEN_DURATION 3600 #define PLUGIN_REGISTER_AUTH_SCOPE "g_profile" #define PLUGIN_REGISTER_DEFAULT_SCOPE "scope3" #define CLIENT_NAME "New Client" #define CLIENT_REDIRECT_URI "https://client.tld/callback" #define CLIENT_TOKEN_AUTH_NONE "none" #define CLIENT_TOKEN_AUTH_SECRET_POST "client_secret_post" #define CLIENT_TOKEN_AUTH_SECRET_BASIC "client_secret_basic" #define CLIENT_TOKEN_AUTH_SECRET_JWT "client_secret_jwt" #define CLIENT_TOKEN_AUTH_PRIVATE_KEY_JWT "private_key_jwt" #define CLIENT_RESPONSE_TYPE_CODE "code" #define CLIENT_RESPONSE_TYPE_TOKEN "token" #define CLIENT_RESPONSE_TYPE_ID_TOKEN "id_token" #define CLIENT_GRANT_TYPE_AUTH_CODE "authorization_code" #define CLIENT_GRANT_TYPE_PASSWORD "password" #define CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS "client_credentials" #define CLIENT_GRANT_TYPE_REFRESH_TOKEN "refresh_token" #define CLIENT_GRANT_TYPE_DELETE_TOKEN "delete_token" #define CLIENT_GRANT_TYPE_DEVICE_AUTH "device_authorization" #define CLIENT_APP_TYPE_WEB "web" #define CLIENT_APP_TYPE_NATIVE "native" #define CLIENT_LOGO_URI "https://client.tld/logo.png" #define CLIENT_CONTACT "contact@client.tld" #define CLIENT_URI "https://client.tld/" #define CLIENT_POLICY_URI "https://client.tld/policy" #define CLIENT_TOS_URI "https://client.tld/tos" #define CLIENT_JWKS_URI "https://client.tld/jwks" #define CLIENT_RESOURCE_IDENTIFIER "https://resource.tld/" #define CLIENT_DEFAULT_KEY_1 "key1" #define CLIENT_DEFAULT_KEY_2 "key2" #define CLIENT_DEFAULT_KEY_OVERWRITTEN "redirect_uri" #define CLIENT_DEFAULT_VALUE_1 "value1" #define CLIENT_DEFAULT_VALUE_2 "value2" #define CLIENT_DEFAULT_VALUE_3 "value3" #define CLIENT_DEFAULT_VALUE_OVERWRITTEN "overwrite-me" #define CLIENT_SECTOR_IDENTIFIER_URI "https://localhost:7344/siu" #define CLIENT_SECTOR_IDENTIFIER_URI_2 "https://localhost:7344/siu/invalid_content_type" #define CLIENT_SECTOR_IDENTIFIER_URI_3 "https://localhost:7344/siu/invalid_format" #define CLIENT_SECTOR_IDENTIFIER_URI_4 "https://localhost:7344/siu/invalid_redirect_uri" #define CB_KEY "cert/server.key" #define CB_CRT "cert/server.crt" const char jwk_pubkey_ecdsa_str[] = "{\"keys\":[{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4\","\ "\"y\":\"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM\",\"use\":\"enc\",\"kid\":\"1\"}]}"; struct _u_request admin_req; static int callback_sector_identifier_uri_valid(const struct _u_request * request, struct _u_response * response, void * user_data) { json_t * j_response = json_pack("[ss]", CLIENT_REDIRECT_URI, "https://example.com"); ulfius_set_json_body_response(response, 200, j_response); json_decref(j_response); return U_CALLBACK_CONTINUE; } static int callback_sector_identifier_uri_invalid_content_type(const struct _u_request * request, struct _u_response * response, void * user_data) { ulfius_set_string_body_response(response, 200, "[\""CLIENT_REDIRECT_URI"\",\"https://example.com\"]"); return U_CALLBACK_CONTINUE; } static int callback_sector_identifier_uri_invalid_format(const struct _u_request * request, struct _u_response * response, void * user_data) { json_t * j_response = json_pack("{s[ss]}", "redirect_uri", CLIENT_REDIRECT_URI, "https://example.com"); ulfius_set_json_body_response(response, 200, j_response); json_decref(j_response); return U_CALLBACK_CONTINUE; } static int callback_sector_identifier_uri_invalid_redirect_uri(const struct _u_request * request, struct _u_response * response, void * user_data) { json_t * j_response = json_pack("[ss]", CLIENT_REDIRECT_URI "/error", "https://example.com"); ulfius_set_json_body_response(response, 200, j_response); json_decref(j_response); return U_CALLBACK_CONTINUE; } START_TEST(test_oidc_registration_plugin_add_using_no_auth_scope) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisososososososos[]s[s]s{s{ss}s{s[ss]}s{s[s]}}}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "register-client-allowed", json_true(), "register-client-auth-scope", "register-client-credentials-scope", PLUGIN_REGISTER_DEFAULT_SCOPE, "register-default-properties", CLIENT_DEFAULT_KEY_1, "value", CLIENT_DEFAULT_VALUE_1, CLIENT_DEFAULT_KEY_2, "value", CLIENT_DEFAULT_VALUE_2, CLIENT_DEFAULT_VALUE_3, CLIENT_DEFAULT_KEY_OVERWRITTEN, "value", CLIENT_DEFAULT_VALUE_OVERWRITTEN); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_registration_plugin_add_using_auth_scope) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisososososososos[s]s[s]s{s{ss}s{s[ss]}}}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "register-client-allowed", json_true(), "register-client-auth-scope", PLUGIN_REGISTER_AUTH_SCOPE, "register-client-credentials-scope", PLUGIN_REGISTER_DEFAULT_SCOPE, "register-default-properties", CLIENT_DEFAULT_KEY_1, "value", CLIENT_DEFAULT_VALUE_1, CLIENT_DEFAULT_KEY_2, "value", CLIENT_DEFAULT_VALUE_2, CLIENT_DEFAULT_VALUE_3); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_registration_plugin_add_using_no_auth_scope_allow_add_resource) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisososososososos[]s[s]sos{s{ss}s{s[ss]}}}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "register-client-allowed", json_true(), "register-client-auth-scope", "register-client-credentials-scope", PLUGIN_REGISTER_DEFAULT_SCOPE, "register-resource-specify-allowed", json_true(), "register-default-properties", CLIENT_DEFAULT_KEY_1, "value", CLIENT_DEFAULT_VALUE_1, CLIENT_DEFAULT_KEY_2, "value", CLIENT_DEFAULT_VALUE_2, CLIENT_DEFAULT_VALUE_3); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_registration_plugin_add_using_no_auth_scope_resource_default) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisososososososos[]s[s]sos[s]s{s{ss}s{s[ss]}}}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "register-client-allowed", json_true(), "register-client-auth-scope", "register-client-credentials-scope", PLUGIN_REGISTER_DEFAULT_SCOPE, "register-resource-specify-allowed", json_false(), "register-resource-default", CLIENT_RESOURCE_IDENTIFIER, "register-default-properties", CLIENT_DEFAULT_KEY_1, "value", CLIENT_DEFAULT_VALUE_1, CLIENT_DEFAULT_KEY_2, "value", CLIENT_DEFAULT_VALUE_2, CLIENT_DEFAULT_VALUE_3); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_registration_plugin_add_using_no_auth_scope_subject_type_pairwise) { json_t * j_parameters = json_pack("{sssssssos{sssssssssssisisisosososososososos[]s[s]s{s{ss}s{s[ss]}s{s[s]}}}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "subject-type", PLUGIN_PAIRWISE, "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-uri-allow-https-non-secure", json_true(), "register-client-allowed", json_true(), "register-client-auth-scope", "register-client-credentials-scope", PLUGIN_REGISTER_DEFAULT_SCOPE, "register-default-properties", CLIENT_DEFAULT_KEY_1, "value", CLIENT_DEFAULT_VALUE_1, CLIENT_DEFAULT_KEY_2, "value", CLIENT_DEFAULT_VALUE_2, CLIENT_DEFAULT_VALUE_3, CLIENT_DEFAULT_KEY_OVERWRITTEN, "value", CLIENT_DEFAULT_VALUE_OVERWRITTEN); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_revocation_plugin_remove) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_registration_no_auth_register_client_error_parameters) { json_t * j_client, * j_jwks = json_loads(jwk_pubkey_ecdsa_str, JSON_DECODE_ANY, NULL); ck_assert_ptr_ne(j_jwks, NULL); // No redirect_uri j_client = json_pack("{sssss[ssssss]s[sss]sss[s]ssssssss}", "client_name", CLIENT_NAME, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 400, NULL, "\"redirect_uris is mandatory and must be an array of strings\"", NULL), 1); json_decref(j_client); // invalid response_types j_client = json_pack("{sss[s]sss[ssssss]s[s]sss[s]ssssssss}", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", "error", "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 400, NULL, "\"response_types must have one of the following values: 'code', 'token', 'id_token'\"", NULL), 1); json_decref(j_client); // invalid grant_types j_client = json_pack("{sss[s]sss[s]s[sss]sss[s]sssssssssO}", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_PRIVATE_KEY_JWT, "grant_types", "error", "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI, "jwks", j_jwks); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 400, NULL, "\"grant_types must have one of the following values: 'authorization_code', 'implicit', 'password', 'client_credentials', 'refresh_token', 'delete_token', 'device_authorization', 'urn:openid:params:grant-type:ciba'\"", NULL), 1); json_decref(j_client); // Invaid application_type j_client = json_pack("{sss[s]sss[ssssss]s[sss]sss[s]ssssssss}", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", "error", "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 400, NULL, "\"application_type is optional and must have one of the following values: 'web', 'native'\"", NULL), 1); json_decref(j_client); // Invalid contacts j_client = json_pack("{sss[s]sss[ssssss]s[sss]sss[i]ssssssss}", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", 42, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 400, NULL, "\"contact value must be a non empty string\"", NULL), 1); json_decref(j_client); // Invalid logo_uri j_client = json_pack("{sss[s]sss[ssssss]s[sss]sss[s]ssssssss}", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", "error", "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 400, NULL, "\"logo_uri is optional and must be a string\"", NULL), 1); json_decref(j_client); // Invalid client_uri j_client = json_pack("{sss[s]sss[ssssss]s[sss]sss[s]ssssssss}", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", "error", "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 400, NULL, "\"client_uri is optional and must be a string\"", NULL), 1); json_decref(j_client); // Invalid policy_uri j_client = json_pack("{sss[s]sss[ssssss]s[sss]sss[s]ssssssss}", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", "error", "tos_uri", CLIENT_TOS_URI); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 400, NULL, "\"policy_uri is optional and must be a string\"", NULL), 1); json_decref(j_client); // Invalid tos_uri j_client = json_pack("{sss[s]sss[ssssss]s[sss]sss[s]ssssssss}", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", "error"); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 400, NULL, "\"tos_uri is optional and must be a string\"", NULL), 1); json_decref(j_client); // Invalid jwks j_client = json_pack("{sss[s]sss[ssssss]s[sss]sss[s]ssssssssss}", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_PRIVATE_KEY_JWT, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI, "jwks", "error"); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 400, NULL, "\"Invalid JWKS\"", NULL), 1); json_decref(j_client); // Invalid jwks_uri j_client = json_pack("{sss[s]sss[ssssss]s[sss]sss[s]ssssssssss}", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_PRIVATE_KEY_JWT, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI, "jwks_uri", "error"); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 400, NULL, "\"jwks_uri is optional and must be an https:// uri\"", NULL), 1); json_decref(j_client); // Invalid jwks_uri and jwks j_client = json_pack("{sss[s]sss[ssssss]s[sss]sss[s]sssssssssOss}", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_PRIVATE_KEY_JWT, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI, "jwks", j_jwks, "jwks_uri", CLIENT_JWKS_URI); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 400, NULL, "\"jwks_uri and jwks can't coexist\"", NULL), 1); json_decref(j_client); json_decref(j_jwks); } END_TEST START_TEST(test_oidc_registration_no_auth_register_client_ok) { json_t * j_client; j_client = json_pack("{sss[s]sss[ssssss]s[sss]sss[s]ssssssss}", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 200, j_client, NULL, NULL), 1); json_decref(j_client); } END_TEST START_TEST(test_oidc_registration_no_auth_register_client_properties_validated) { json_t * j_client, * j_jwks = json_loads(jwk_pubkey_ecdsa_str, JSON_DECODE_ANY, NULL), * j_result; struct _u_request req; struct _u_response resp; ck_assert_ptr_ne(j_jwks, NULL); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); j_client = json_pack("{sss[s]sss[ssssss]s[sss]sss[s]sssssssssO}", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_PRIVATE_KEY_JWT, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI, "jwks", j_jwks); ck_assert_ptr_ne(j_client, NULL); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/register"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_client), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_result, NULL); ck_assert_ptr_ne(json_object_get(j_result, "client_id"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "client_secret"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "registration_access_token"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "registration_client_uri"), NULL); admin_req.http_url = msprintf(SERVER_URI "/client/%s", json_string_value(json_object_get(j_result, "client_id"))); admin_req.http_verb = o_strdup("GET"); json_decref(j_result); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_send_http_request(&admin_req, &resp), U_OK); o_free(admin_req.http_url); o_free(admin_req.http_verb); ck_assert_int_eq(resp.status, 200); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_eq(json_object_get(j_result, "enabled"), json_true()); ck_assert_ptr_ne(json_object_get(j_result, "client_id"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "client_secret"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "registration_access_token"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "registration_client_uri"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "ignored"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "jwks_uri"), NULL); ck_assert_int_eq(json_string_length(json_object_get(j_result, "client_id")), 16); ck_assert_int_eq(json_array_size(json_object_get(j_result, "scope")), 1); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result, "scope"), 0)), PLUGIN_REGISTER_DEFAULT_SCOPE); ck_assert_int_eq(json_array_size(json_object_get(j_result, "redirect_uri")), 1); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result, "redirect_uri"), 0)), CLIENT_REDIRECT_URI); ck_assert_int_eq(json_array_size(json_object_get(j_result, "authorization_type")), 8); ck_assert_str_eq(json_string_value(json_object_get(j_result, "application_type")), CLIENT_APP_TYPE_WEB); ck_assert_int_eq(json_array_size(json_object_get(j_result, "contacts")), 1); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result, "contacts"), 0)), CLIENT_CONTACT); ck_assert_str_eq(json_string_value(json_object_get(j_result, "logo_uri")), CLIENT_LOGO_URI); ck_assert_str_eq(json_string_value(json_object_get(j_result, "client_uri")), CLIENT_URI); ck_assert_str_eq(json_string_value(json_object_get(j_result, "policy_uri")), CLIENT_POLICY_URI); ck_assert_str_eq(json_string_value(json_object_get(j_result, "tos_uri")), CLIENT_TOS_URI); ck_assert_ptr_eq(json_object_get(j_result, "enabled"), json_true()); ck_assert_int_eq(json_equal(json_object_get(j_result, "jwks"), j_jwks), 1); ck_assert_str_eq(json_string_value(json_object_get(j_result, CLIENT_DEFAULT_KEY_1)), CLIENT_DEFAULT_VALUE_1); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result, CLIENT_DEFAULT_KEY_2), 0)), CLIENT_DEFAULT_VALUE_2); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result, CLIENT_DEFAULT_KEY_2), 1)), CLIENT_DEFAULT_VALUE_3); ck_assert_str_ne(json_string_value(json_array_get(json_object_get(j_result, CLIENT_DEFAULT_KEY_OVERWRITTEN), 0)), CLIENT_DEFAULT_VALUE_OVERWRITTEN); json_decref(j_result); json_decref(j_client); json_decref(j_jwks); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_registration_no_auth_register_minimal_client_properties_validated) { json_t * j_client, * j_jwks = json_loads(jwk_pubkey_ecdsa_str, JSON_DECODE_ANY, NULL), * j_result; struct _u_request req; struct _u_response resp; ck_assert_ptr_ne(j_jwks, NULL); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); j_client = json_pack("{sss[s]}", "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_SECRET_BASIC, "redirect_uris", CLIENT_REDIRECT_URI); ck_assert_ptr_ne(j_client, NULL); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/register"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_client), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_result, NULL); ck_assert_ptr_ne(json_object_get(j_result, "client_id"), NULL); ck_assert_ptr_ne(json_object_get(j_result, "client_secret"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "registration_access_token"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "registration_client_uri"), NULL); admin_req.http_url = msprintf(SERVER_URI "/client/%s", json_string_value(json_object_get(j_result, "client_id"))); admin_req.http_verb = o_strdup("GET"); json_decref(j_result); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_send_http_request(&admin_req, &resp), U_OK); o_free(admin_req.http_url); admin_req.http_url = NULL; o_free(admin_req.http_verb); admin_req.http_verb = NULL; ck_assert_int_eq(resp.status, 200); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_eq(json_object_get(j_result, "jwks_uri"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "contacts"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "logo_uri"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "client_uri"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "policy_uri"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "tos_uri"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "jwks"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "enabled"), json_true()); ck_assert_ptr_ne(json_object_get(j_result, "client_id"), NULL); ck_assert_ptr_ne(json_object_get(j_result, "client_secret"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "registration_access_token"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "registration_client_uri"), NULL); ck_assert_int_eq(json_string_length(json_object_get(j_result, "client_id")), 16); ck_assert_int_eq(json_string_length(json_object_get(j_result, "client_secret")), 32); ck_assert_int_eq(json_array_size(json_object_get(j_result, "scope")), 1); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result, "scope"), 0)), PLUGIN_REGISTER_DEFAULT_SCOPE); ck_assert_int_eq(json_array_size(json_object_get(j_result, "redirect_uri")), 1); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result, "redirect_uri"), 0)), CLIENT_REDIRECT_URI); ck_assert_int_eq(json_array_size(json_object_get(j_result, "authorization_type")), 1); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result, "authorization_type"), 0)), CLIENT_RESPONSE_TYPE_CODE); ck_assert_str_eq(json_string_value(json_object_get(j_result, "application_type")), CLIENT_APP_TYPE_WEB); ck_assert_str_eq(json_string_value(json_object_get(j_result, CLIENT_DEFAULT_KEY_1)), CLIENT_DEFAULT_VALUE_1); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result, CLIENT_DEFAULT_KEY_2), 0)), CLIENT_DEFAULT_VALUE_2); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result, CLIENT_DEFAULT_KEY_2), 1)), CLIENT_DEFAULT_VALUE_3); json_decref(j_result); json_decref(j_client); json_decref(j_jwks); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_registration_no_auth_register_public_client_properties_validated) { json_t * j_client, * j_jwks = json_loads(jwk_pubkey_ecdsa_str, JSON_DECODE_ANY, NULL), * j_result; struct _u_request req; struct _u_response resp; ck_assert_ptr_ne(j_jwks, NULL); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); j_client = json_pack("{s[s]so}", "redirect_uris", CLIENT_REDIRECT_URI, "confidential", json_false()); ck_assert_ptr_ne(j_client, NULL); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/register"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_client), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_result, NULL); ck_assert_ptr_ne(json_object_get(j_result, "client_id"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "client_secret"), NULL); admin_req.http_url = msprintf(SERVER_URI "/client/%s", json_string_value(json_object_get(j_result, "client_id"))); admin_req.http_verb = o_strdup("GET"); json_decref(j_result); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_send_http_request(&admin_req, &resp), U_OK); o_free(admin_req.http_url); admin_req.http_url = NULL; o_free(admin_req.http_verb); admin_req.http_verb = NULL; ck_assert_int_eq(resp.status, 200); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_eq(json_object_get(j_result, "jwks_uri"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "contacts"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "logo_uri"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "client_uri"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "policy_uri"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "tos_uri"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "jwks"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "enabled"), json_true()); ck_assert_ptr_ne(json_object_get(j_result, "client_id"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "client_secret"), NULL); ck_assert_int_eq(json_string_length(json_object_get(j_result, "client_id")), 16); ck_assert_int_eq(json_array_size(json_object_get(j_result, "scope")), 1); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result, "scope"), 0)), PLUGIN_REGISTER_DEFAULT_SCOPE); ck_assert_int_eq(json_array_size(json_object_get(j_result, "redirect_uri")), 1); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result, "redirect_uri"), 0)), CLIENT_REDIRECT_URI); ck_assert_int_eq(json_array_size(json_object_get(j_result, "authorization_type")), 1); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result, "authorization_type"), 0)), CLIENT_RESPONSE_TYPE_CODE); ck_assert_str_eq(json_string_value(json_object_get(j_result, "application_type")), CLIENT_APP_TYPE_WEB); ck_assert_str_eq(json_string_value(json_object_get(j_result, CLIENT_DEFAULT_KEY_1)), CLIENT_DEFAULT_VALUE_1); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result, CLIENT_DEFAULT_KEY_2), 0)), CLIENT_DEFAULT_VALUE_2); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result, CLIENT_DEFAULT_KEY_2), 1)), CLIENT_DEFAULT_VALUE_3); json_decref(j_result); json_decref(j_client); json_decref(j_jwks); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_registration_no_auth_register_then_code_flow) { json_t * j_client, * j_result, * j_body; struct _u_request req; struct _u_response resp; char * client_id, * client_secret, * code, * cookie, * refresh_token; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); j_client = json_pack("{sss[s]s[ss]}", "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_SECRET_BASIC, "redirect_uris", CLIENT_REDIRECT_URI, "grant_types", "authorization_code", "refresh_token"); ck_assert_ptr_ne(j_client, NULL); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/register"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_client), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_result, NULL); ck_assert_ptr_ne(json_object_get(j_result, "client_id"), NULL); ck_assert_ptr_ne(json_object_get(j_result, "client_secret"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "registration_access_token"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "registration_client_uri"), NULL); client_id = o_strdup(json_string_value(json_object_get(j_result, "client_id"))); client_secret = o_strdup(json_string_value(json_object_get(j_result, "client_secret"))); json_decref(j_result); json_decref(j_client); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/auth/"); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_body), U_OK); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); cookie = msprintf("%s=%s", resp.map_cookie[0].key, resp.map_cookie[0].value); ck_assert_int_eq(u_map_put(req.map_header, "Cookie", cookie), U_OK); o_free(cookie); ulfius_clean_response(&resp); // Set grant ulfius_init_response(&resp); o_free(req.http_verb); o_free(req.http_url); req.http_verb = strdup("PUT"); req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, client_id); j_body = json_pack("{ss}", "scope", "openid"); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(req.http_verb); o_free(req.http_url); req.http_verb = o_strdup("GET"); req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&g_continue&client_id=%s&redirect_uri=" CLIENT_REDIRECT_URI "&state=xyzabcd&nonce=nonce1234&scope=openid", client_id); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=")+o_strlen("code=")); if (o_strstr(code, "&") != NULL) { *o_strstr(code, "&") = '\0'; } ulfius_clean_response(&resp); // Clean grant ulfius_init_response(&resp); o_free(req.http_verb); o_free(req.http_url); req.http_verb = strdup("PUT"); req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, client_id); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Get tokens ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "authorization_code"); u_map_put(req.map_post_body, "code", code); u_map_put(req.map_post_body, "redirect_uri", CLIENT_REDIRECT_URI); req.auth_basic_user = o_strdup(client_id); req.auth_basic_password = o_strdup(client_secret); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(json_object_get(j_body, "refresh_token"), NULL); ck_assert_ptr_ne(json_object_get(j_body, "access_token"), NULL); refresh_token = o_strdup(json_string_value(json_object_get(j_body, "refresh_token"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Refresh token ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "refresh_token"); u_map_put(req.map_post_body, "refresh_token", refresh_token); req.auth_basic_user = o_strdup(client_id); req.auth_basic_password = o_strdup(client_secret); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(json_object_get(j_body, "access_token"), NULL); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); o_free(client_id); o_free(client_secret); o_free(code); o_free(refresh_token); } END_TEST START_TEST(test_oidc_registration_auth_register_client_without_credentials) { json_t * j_client, * j_jwks = json_loads(jwk_pubkey_ecdsa_str, JSON_DECODE_ANY, NULL); ck_assert_ptr_ne(j_jwks, NULL); j_client = json_pack("{s[s]}", "redirect_uris", CLIENT_REDIRECT_URI); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_client); json_decref(j_jwks); } END_TEST START_TEST(test_oidc_registration_auth_register_client_with_incorrect_credentials) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_client; const char * token; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", "openid"); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); tmp = msprintf("Bearer %s", token); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); o_free(req.http_url); j_client = json_pack("{s[s]}", "redirect_uris", CLIENT_REDIRECT_URI); ck_assert_ptr_ne(j_client, NULL); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/register"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_client), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); json_decref(j_client); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_registration_auth_register_client_with_valid_credentials) { struct _u_request req, req_reg; struct _u_response resp; json_t * j_body, * j_client, * j_result; const char * token; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", PLUGIN_REGISTER_AUTH_SCOPE); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req_reg); ulfius_init_response(&resp); tmp = msprintf("Bearer %s", token); u_map_put(req_reg.map_header, "Authorization", tmp); o_free(tmp); j_client = json_pack("{sss[s]s[s]}", "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_SECRET_BASIC, "redirect_uris", CLIENT_REDIRECT_URI, "grant_types", "client_credentials"); ck_assert_ptr_ne(j_client, NULL); req_reg.http_verb = o_strdup("POST"); req_reg.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/register"); ck_assert_int_eq(ulfius_set_json_body_request(&req_reg, j_client), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req_reg, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_result, NULL); ck_assert_ptr_ne(json_object_get(j_result, "client_id"), NULL); ck_assert_ptr_ne(json_object_get(j_result, "client_secret"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "registration_access_token"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "registration_client_uri"), NULL); admin_req.http_url = msprintf(SERVER_URI "/client/%s", json_string_value(json_object_get(j_result, "client_id"))); admin_req.http_verb = o_strdup("GET"); json_decref(j_result); json_decref(j_client); json_decref(j_body); ulfius_clean_request(&req_reg); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_registration_no_auth_register_client_specify_resource_invalid) { json_t * j_client; j_client = json_pack("{sss[s]sss[ssssss]s[sss]sss[s]sssssssss[s]}", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI, "resource", "error"); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_client); } END_TEST START_TEST(test_oidc_registration_no_auth_register_client_specify_resource_ok) { json_t * j_client; j_client = json_pack("{sss[s]sss[ssssss]s[sss]sss[s]sssssssss[s]}", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI, "resource", CLIENT_RESOURCE_IDENTIFIER); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 200, j_client, NULL, NULL), 1); json_decref(j_client); } END_TEST START_TEST(test_oidc_registration_no_auth_register_client_specify_resource_ignored) { json_t * j_client, * j_result; j_client = json_pack("{sss[s]sss[ssssss]s[sss]sss[s]sssssssss[s]}", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI, "resource", CLIENT_RESOURCE_IDENTIFIER "ignore_me"); j_result = json_pack("{s[s]}", "resource", CLIENT_RESOURCE_IDENTIFIER); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_client); json_decref(j_result); } END_TEST START_TEST(test_oidc_registration_no_auth_register_client_no_specify_resource) { json_t * j_client, * j_result; j_client = json_pack("{sss[s]sss[ssssss]s[sss]sss[s]ssssssss}", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI); j_result = json_pack("{s[s]}", "resource", CLIENT_RESOURCE_IDENTIFIER); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_client); json_decref(j_result); } END_TEST START_TEST(test_oidc_registration_no_auth_register_client_with_sector_identifier_uri) { json_t * j_client; struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, 7344, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "siu", 0, &callback_sector_identifier_uri_valid, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "siu/invalid_content_type", 0, &callback_sector_identifier_uri_invalid_content_type, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "siu/invalid_format", 0, &callback_sector_identifier_uri_invalid_format, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "siu/invalid_redirect_uri", 0, &callback_sector_identifier_uri_invalid_redirect_uri, NULL), U_OK); char * key_pem = read_file(CB_KEY); char * cert_pem = read_file(CB_CRT); ck_assert_ptr_ne(NULL, key_pem); ck_assert_ptr_ne(NULL, cert_pem); ck_assert_int_eq(ulfius_start_secure_framework(&instance, key_pem, cert_pem), U_OK); o_free(key_pem); o_free(cert_pem); j_client = json_pack("{sss[s]sss[ssssss]s[sss]sss[s]ssssssssss}", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI, "sector_identifier_uri", CLIENT_SECTOR_IDENTIFIER_URI_2); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_client); j_client = json_pack("{sss[s]sss[ssssss]s[sss]sss[s]ssssssssss}", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI, "sector_identifier_uri", CLIENT_SECTOR_IDENTIFIER_URI_3); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_client); j_client = json_pack("{sss[s]sss[ssssss]s[sss]sss[s]ssssssssss}", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI, "sector_identifier_uri", CLIENT_SECTOR_IDENTIFIER_URI); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 200, j_client, NULL, NULL), 1); json_decref(j_client); j_client = json_pack("{sss[s]sss[ssssss]s[sss]sss[s]ssssssssssssssss}", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI, "sector_identifier_uri", CLIENT_SECTOR_IDENTIFIER_URI, "request_object_signing_alg", "RS256", "request_object_encryption_alg", "RSA-OAEP-256", "request_object_encryption_enc", "A128CBC-HS256"); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/register", NULL, NULL, j_client, NULL, 200, j_client, NULL, NULL), 1); json_decref(j_client); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc token revocation"); tc_core = tcase_create("test_oidc_token_revocation"); tcase_add_test(tc_core, test_oidc_registration_plugin_add_using_no_auth_scope); tcase_add_test(tc_core, test_oidc_registration_no_auth_register_client_error_parameters); tcase_add_test(tc_core, test_oidc_registration_no_auth_register_client_ok); tcase_add_test(tc_core, test_oidc_registration_no_auth_register_client_properties_validated); tcase_add_test(tc_core, test_oidc_registration_no_auth_register_public_client_properties_validated); tcase_add_test(tc_core, test_oidc_registration_no_auth_register_minimal_client_properties_validated); tcase_add_test(tc_core, test_oidc_registration_no_auth_register_then_code_flow); tcase_add_test(tc_core, test_oidc_revocation_plugin_remove); tcase_add_test(tc_core, test_oidc_registration_plugin_add_using_auth_scope); tcase_add_test(tc_core, test_oidc_registration_auth_register_client_without_credentials); tcase_add_test(tc_core, test_oidc_registration_auth_register_client_with_incorrect_credentials); tcase_add_test(tc_core, test_oidc_registration_auth_register_client_with_valid_credentials); tcase_add_test(tc_core, test_oidc_revocation_plugin_remove); tcase_add_test(tc_core, test_oidc_registration_plugin_add_using_no_auth_scope_allow_add_resource); tcase_add_test(tc_core, test_oidc_registration_no_auth_register_client_specify_resource_invalid); tcase_add_test(tc_core, test_oidc_registration_no_auth_register_client_specify_resource_ok); tcase_add_test(tc_core, test_oidc_revocation_plugin_remove); tcase_add_test(tc_core, test_oidc_registration_plugin_add_using_no_auth_scope_resource_default); tcase_add_test(tc_core, test_oidc_registration_no_auth_register_client_specify_resource_ignored); tcase_add_test(tc_core, test_oidc_registration_no_auth_register_client_no_specify_resource); tcase_add_test(tc_core, test_oidc_revocation_plugin_remove); tcase_add_test(tc_core, test_oidc_registration_plugin_add_using_no_auth_scope_subject_type_pairwise); tcase_add_test(tc_core, test_oidc_registration_no_auth_register_client_with_sector_identifier_uri); tcase_add_test(tc_core, test_oidc_revocation_plugin_remove); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&admin_req); ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); j_body = NULL; res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define PLUGIN_MODULE "oidc" #define PLUGIN_NAME "register_management" #define PLUGIN_ISS "https://glewlwyd.tld" #define PLUGIN_DISPLAY_NAME "Client registration management test" #define PLUGIN_JWT_TYPE "sha" #define PLUGIN_JWT_KEY_SIZE "256" #define PLUGIN_KEY "secret" #define PLUGIN_CODE_DURATION 600 #define PLUGIN_REFRESH_TOKEN_DURATION 1209600 #define PLUGIN_ACCESS_TOKEN_DURATION 3600 #define PLUGIN_REGISTER_AUTH_SCOPE "g_profile" #define PLUGIN_REGISTER_DEFAULT_SCOPE "scope3" #define CLIENT_NAME "New Client" #define CLIENT_REDIRECT_URI "https://client.tld/callback" #define CLIENT_TOKEN_AUTH_NONE "none" #define CLIENT_TOKEN_AUTH_SECRET_POST "client_secret_post" #define CLIENT_TOKEN_AUTH_SECRET_BASIC "client_secret_basic" #define CLIENT_TOKEN_AUTH_SECRET_JWT "client_secret_jwt" #define CLIENT_TOKEN_AUTH_PRIVATE_KEY_JWT "private_key_jwt" #define CLIENT_RESPONSE_TYPE_CODE "code" #define CLIENT_RESPONSE_TYPE_TOKEN "token" #define CLIENT_RESPONSE_TYPE_ID_TOKEN "id_token" #define CLIENT_GRANT_TYPE_AUTH_CODE "authorization_code" #define CLIENT_GRANT_TYPE_IMPLICIT "implicit" #define CLIENT_GRANT_TYPE_PASSWORD "password" #define CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS "client_credentials" #define CLIENT_GRANT_TYPE_REFRESH_TOKEN "refresh_token" #define CLIENT_GRANT_TYPE_DELETE_TOKEN "delete_token" #define CLIENT_GRANT_TYPE_DEVICE_AUTH "device_authorization" #define CLIENT_APP_TYPE_WEB "web" #define CLIENT_APP_TYPE_NATIVE "native" #define CLIENT_LOGO_URI "https://client.tld/logo.png" #define CLIENT_CONTACT "contact@client.tld" #define CLIENT_URI "https://client.tld/" #define CLIENT_POLICY_URI "https://client.tld/policy" #define CLIENT_TOS_URI "https://client.tld/tos" #define CLIENT_JWKS_URI "https://client.tld/jwks" #define CLIENT_DEFAULT_KEY_1 "key1" #define CLIENT_DEFAULT_KEY_2 "key2" #define CLIENT_DEFAULT_VALUE_1 "value1" #define CLIENT_DEFAULT_VALUE_2 "value2" #define CLIENT_DEFAULT_VALUE_3 "value3" const char jwk_pubkey_ecdsa_str[] = "{\"keys\":[{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4\","\ "\"y\":\"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM\",\"use\":\"enc\",\"kid\":\"1\"}]}"; struct _u_request admin_req; START_TEST(test_oidc_registration_plugin_add_using_management) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisososososososos[s]s[s]sos{s{ss}s{s[ss]}}}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "register-client-allowed", json_true(), "register-client-auth-scope", PLUGIN_REGISTER_AUTH_SCOPE, "register-client-credentials-scope", PLUGIN_REGISTER_DEFAULT_SCOPE, "register-client-management-allowed", json_true(), "register-default-properties", CLIENT_DEFAULT_KEY_1, "value", CLIENT_DEFAULT_VALUE_1, CLIENT_DEFAULT_KEY_2, "value", CLIENT_DEFAULT_VALUE_2, CLIENT_DEFAULT_VALUE_3); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_registration_plugin_remove) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_registration_auth_register_client_with_valid_credentials) { struct _u_request req, req_reg; struct _u_response resp; json_t * j_body, * j_client, * j_result; const char * token; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", PLUGIN_REGISTER_AUTH_SCOPE); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req_reg); ulfius_init_response(&resp); tmp = msprintf("Bearer %s", token); u_map_put(req_reg.map_header, "Authorization", tmp); o_free(tmp); j_client = json_pack("{sss[s]s[ss]}", "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_SECRET_BASIC, "redirect_uris", CLIENT_REDIRECT_URI, "grant_types", "authorization_code", "refresh_token"); ck_assert_ptr_ne(j_client, NULL); req_reg.http_verb = o_strdup("POST"); req_reg.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/register"); ck_assert_int_eq(ulfius_set_json_body_request(&req_reg, j_client), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req_reg, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_result, NULL); ck_assert_ptr_ne(json_object_get(j_result, "client_id"), NULL); ck_assert_ptr_ne(json_object_get(j_result, "client_secret"), NULL); ck_assert_ptr_ne(json_object_get(j_result, "registration_access_token"), NULL); ck_assert_ptr_ne(json_object_get(j_result, "registration_client_uri"), NULL); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = msprintf(SERVER_URI "/client/%s", json_string_value(json_object_get(j_result, "client_id"))); admin_req.http_verb = o_strdup("GET"); json_decref(j_result); json_decref(j_client); json_decref(j_body); ulfius_clean_request(&req_reg); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_registration_auth_register_client_management_get) { struct _u_request req, req_reg; struct _u_response resp; json_t * j_body, * j_client, * j_result, * j_result_get; const char * token, * registration_access_token, * registration_client_uri; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", PLUGIN_REGISTER_AUTH_SCOPE); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req_reg); ulfius_init_response(&resp); tmp = msprintf("Bearer %s", token); u_map_put(req_reg.map_header, "Authorization", tmp); o_free(tmp); j_client = json_pack("{sss[s]s[ss]}", "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_SECRET_BASIC, "redirect_uris", CLIENT_REDIRECT_URI, "grant_types", "authorization_code", "refresh_token"); ck_assert_ptr_ne(j_client, NULL); req_reg.http_verb = o_strdup("POST"); req_reg.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/register"); ck_assert_int_eq(ulfius_set_json_body_request(&req_reg, j_client), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req_reg, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_result, NULL); ck_assert_ptr_ne(json_object_get(j_result, "client_id"), NULL); ck_assert_ptr_ne(json_object_get(j_result, "client_secret"), NULL); ck_assert_ptr_ne(registration_access_token = json_string_value(json_object_get(j_result, "registration_access_token")), NULL); ck_assert_ptr_ne(registration_client_uri = json_string_value(json_object_get(j_result, "registration_client_uri")), NULL); json_decref(j_client); json_decref(j_body); ulfius_clean_request(&req_reg); ulfius_clean_response(&resp); ulfius_init_request(&req_reg); ulfius_init_response(&resp); req_reg.http_url = o_strdup(registration_client_uri); tmp = msprintf("Bearer %s", registration_access_token); u_map_put(req_reg.map_header, "Authorization", tmp); o_free(tmp); ck_assert_int_eq(ulfius_send_http_request(&req_reg, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_result_get = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_result_get, NULL); ck_assert_ptr_eq(json_object_get(j_result_get, "registration_access_token"), NULL); ck_assert_ptr_eq(json_object_get(j_result_get, "registration_client_uri"), NULL); json_object_set(j_result_get, "registration_access_token", json_object_get(j_result, "registration_access_token")); json_object_set(j_result_get, "registration_client_uri", json_object_get(j_result, "registration_client_uri")); json_object_set(j_result_get, "client_id_issued_at", json_object_get(j_result, "client_id_issued_at")); json_object_set(j_result_get, "client_secret_expires_at", json_object_get(j_result, "client_secret_expires_at")); ck_assert_int_eq(json_equal(j_result, j_result_get), 1); ulfius_clean_request(&req_reg); ulfius_clean_response(&resp); json_decref(j_result_get); json_decref(j_result); } END_TEST START_TEST(test_oidc_registration_auth_register_client_management_get_invalid_access_token) { struct _u_request req, req_reg; struct _u_response resp; json_t * j_body, * j_client, * j_result; const char * token, * registration_access_token, * registration_client_uri; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", PLUGIN_REGISTER_AUTH_SCOPE); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req_reg); ulfius_init_response(&resp); tmp = msprintf("Bearer %s", token); u_map_put(req_reg.map_header, "Authorization", tmp); o_free(tmp); j_client = json_pack("{sss[s]s[ss]}", "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_SECRET_BASIC, "redirect_uris", CLIENT_REDIRECT_URI, "grant_types", "authorization_code", "refresh_token"); ck_assert_ptr_ne(j_client, NULL); req_reg.http_verb = o_strdup("POST"); req_reg.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/register"); ck_assert_int_eq(ulfius_set_json_body_request(&req_reg, j_client), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req_reg, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_result, NULL); ck_assert_ptr_ne(json_object_get(j_result, "client_id"), NULL); ck_assert_ptr_ne(json_object_get(j_result, "client_secret"), NULL); ck_assert_ptr_ne(registration_access_token = json_string_value(json_object_get(j_result, "registration_access_token")), NULL); ck_assert_ptr_ne(registration_client_uri = json_string_value(json_object_get(j_result, "registration_client_uri")), NULL); json_decref(j_client); json_decref(j_body); ulfius_clean_request(&req_reg); ulfius_clean_response(&resp); ulfius_init_request(&req_reg); ulfius_init_response(&resp); req_reg.http_url = o_strdup(registration_client_uri); tmp = msprintf("Bearer %s", "error"); u_map_put(req_reg.map_header, "Authorization", tmp); o_free(tmp); ck_assert_int_eq(ulfius_send_http_request(&req_reg, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_request(&req_reg); ulfius_clean_response(&resp); json_decref(j_result); } END_TEST START_TEST(test_oidc_registration_auth_register_client_management_get_invalid_clent_id) { struct _u_request req, req_reg; struct _u_response resp; json_t * j_body, * j_client, * j_result; const char * token, * registration_access_token, * registration_client_uri; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", PLUGIN_REGISTER_AUTH_SCOPE); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req_reg); ulfius_init_response(&resp); tmp = msprintf("Bearer %s", token); u_map_put(req_reg.map_header, "Authorization", tmp); o_free(tmp); j_client = json_pack("{sss[s]s[ss]}", "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_SECRET_BASIC, "redirect_uris", CLIENT_REDIRECT_URI, "grant_types", "authorization_code", "refresh_token"); ck_assert_ptr_ne(j_client, NULL); req_reg.http_verb = o_strdup("POST"); req_reg.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/register"); ck_assert_int_eq(ulfius_set_json_body_request(&req_reg, j_client), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req_reg, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_result, NULL); ck_assert_ptr_ne(json_object_get(j_result, "client_id"), NULL); ck_assert_ptr_ne(json_object_get(j_result, "client_secret"), NULL); ck_assert_ptr_ne(registration_access_token = json_string_value(json_object_get(j_result, "registration_access_token")), NULL); ck_assert_ptr_ne(registration_client_uri = json_string_value(json_object_get(j_result, "registration_client_uri")), NULL); json_decref(j_client); json_decref(j_body); ulfius_clean_request(&req_reg); ulfius_clean_response(&resp); ulfius_init_request(&req_reg); ulfius_init_response(&resp); req_reg.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/register/error"); tmp = msprintf("Bearer %s", registration_access_token); u_map_put(req_reg.map_header, "Authorization", tmp); o_free(tmp); ck_assert_int_eq(ulfius_send_http_request(&req_reg, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_request(&req_reg); ulfius_clean_response(&resp); ulfius_init_request(&req_reg); ulfius_init_response(&resp); req_reg.http_url = o_strdup(registration_client_uri); tmp = msprintf("Bearer %s", registration_access_token); u_map_put(req_reg.map_header, "Authorization", tmp); o_free(tmp); ck_assert_int_eq(ulfius_send_http_request(&req_reg, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_request(&req_reg); ulfius_clean_response(&resp); json_decref(j_result); } END_TEST START_TEST(test_oidc_registration_auth_register_client_management_update_error_parameters) { json_t * j_client, * j_jwks = json_loads(jwk_pubkey_ecdsa_str, JSON_DECODE_ANY, NULL); struct _u_request req, req_reg; struct _u_response resp; json_t * j_body, * j_result; const char * token, * registration_access_token, * registration_client_uri; char * tmp; ck_assert_ptr_ne(j_jwks, NULL); ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", PLUGIN_REGISTER_AUTH_SCOPE); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req_reg); ulfius_init_response(&resp); tmp = msprintf("Bearer %s", token); u_map_put(req_reg.map_header, "Authorization", tmp); o_free(tmp); j_client = json_pack("{s[s]}", "redirect_uris", CLIENT_REDIRECT_URI); ck_assert_ptr_ne(j_client, NULL); req_reg.http_verb = o_strdup("POST"); req_reg.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/register"); ck_assert_int_eq(ulfius_set_json_body_request(&req_reg, j_client), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req_reg, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_result, NULL); ck_assert_ptr_ne(json_object_get(j_result, "client_id"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "client_secret"), NULL); ck_assert_ptr_ne(registration_access_token = json_string_value(json_object_get(j_result, "registration_access_token")), NULL); ck_assert_ptr_ne(registration_client_uri = json_string_value(json_object_get(j_result, "registration_client_uri")), NULL); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = msprintf(SERVER_URI "/client/%s", json_string_value(json_object_get(j_result, "client_id"))); admin_req.http_verb = o_strdup("GET"); json_decref(j_client); json_decref(j_body); ulfius_clean_request(&req_reg); ulfius_clean_response(&resp); ulfius_init_request(&req_reg); tmp = msprintf("Bearer %s", registration_access_token); u_map_put(req_reg.map_header, "Authorization", tmp); o_free(tmp); // Invalid client_id j_client = json_pack("{sssss[s]sss[ssssss]s[sss]sss[s]sssssssssO}", "client_id", "error", "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_PRIVATE_KEY_JWT, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI, "jwks", j_jwks); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(&req_reg, "PUT", registration_client_uri, NULL, NULL, j_client, NULL, 400, NULL, "\"Invalid client_id\"", NULL), 1); json_decref(j_client); // No redirect_uri j_client = json_pack("{sssssss[ssssss]s[sss]sss[s]ssssssss}", "client_id", json_string_value(json_object_get(j_result, "client_id")), "client_name", CLIENT_NAME, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(&req_reg, "PUT", registration_client_uri, NULL, NULL, j_client, NULL, 400, NULL, "\"redirect_uris is mandatory and must be an array of strings\"", NULL), 1); json_decref(j_client); // invalid response_types j_client = json_pack("{sssss[s]sss[ssssss]s[s]sss[s]ssssssss}", "client_id", json_string_value(json_object_get(j_result, "client_id")), "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", "error", "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(&req_reg, "PUT", registration_client_uri, NULL, NULL, j_client, NULL, 400, NULL, "\"response_types must have one of the following values: 'code', 'token', 'id_token'\"", NULL), 1); json_decref(j_client); // invalid grant_types j_client = json_pack("{sssss[s]sss[s]s[sss]sss[s]sssssssssO}", "client_id", json_string_value(json_object_get(j_result, "client_id")), "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_PRIVATE_KEY_JWT, "grant_types", "error", "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI, "jwks", j_jwks); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(&req_reg, "PUT", registration_client_uri, NULL, NULL, j_client, NULL, 400, NULL, "\"grant_types must have one of the following values: 'authorization_code', 'implicit', 'password', 'client_credentials', 'refresh_token', 'delete_token', 'device_authorization', 'urn:openid:params:grant-type:ciba'\"", NULL), 1); json_decref(j_client); // Invaid application_type j_client = json_pack("{sssss[s]sss[ssssss]s[sss]sss[s]ssssssss}", "client_id", json_string_value(json_object_get(j_result, "client_id")), "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", "error", "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(&req_reg, "PUT", registration_client_uri, NULL, NULL, j_client, NULL, 400, NULL, "\"application_type is optional and must have one of the following values: 'web', 'native'\"", NULL), 1); json_decref(j_client); // Invalid contacts j_client = json_pack("{sssss[s]sss[ssssss]s[sss]sss[i]ssssssss}", "client_id", json_string_value(json_object_get(j_result, "client_id")), "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", 42, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(&req_reg, "PUT", registration_client_uri, NULL, NULL, j_client, NULL, 400, NULL, "\"contact value must be a non empty string\"", NULL), 1); json_decref(j_client); // Invalid logo_uri j_client = json_pack("{sssss[s]sss[ssssss]s[sss]sss[s]ssssssss}", "client_id", json_string_value(json_object_get(j_result, "client_id")), "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", "error", "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(&req_reg, "PUT", registration_client_uri, NULL, NULL, j_client, NULL, 400, NULL, "\"logo_uri is optional and must be a string\"", NULL), 1); json_decref(j_client); // Invalid client_uri j_client = json_pack("{sssss[s]sss[ssssss]s[sss]sss[s]ssssssss}", "client_id", json_string_value(json_object_get(j_result, "client_id")), "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", "error", "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(&req_reg, "PUT", registration_client_uri, NULL, NULL, j_client, NULL, 400, NULL, "\"client_uri is optional and must be a string\"", NULL), 1); json_decref(j_client); // Invalid policy_uri j_client = json_pack("{sssss[s]sss[ssssss]s[sss]sss[s]ssssssss}", "client_id", json_string_value(json_object_get(j_result, "client_id")), "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", "error", "tos_uri", CLIENT_TOS_URI); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(&req_reg, "PUT", registration_client_uri, NULL, NULL, j_client, NULL, 400, NULL, "\"policy_uri is optional and must be a string\"", NULL), 1); json_decref(j_client); // Invalid tos_uri j_client = json_pack("{sssss[s]sss[ssssss]s[sss]sss[s]ssssssss}", "client_id", json_string_value(json_object_get(j_result, "client_id")), "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_NONE, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", "error"); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(&req_reg, "PUT", registration_client_uri, NULL, NULL, j_client, NULL, 400, NULL, "\"tos_uri is optional and must be a string\"", NULL), 1); json_decref(j_client); // Invalid jwks j_client = json_pack("{sssss[s]sss[ssssss]s[sss]sss[s]ssssssssss}", "client_id", json_string_value(json_object_get(j_result, "client_id")), "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_PRIVATE_KEY_JWT, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI, "jwks", "error"); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(&req_reg, "PUT", registration_client_uri, NULL, NULL, j_client, NULL, 400, NULL, "\"Invalid JWKS\"", NULL), 1); json_decref(j_client); // Invalid jwks_uri j_client = json_pack("{sssss[s]sss[ssssss]s[sss]sss[s]ssssssssss}", "client_id", json_string_value(json_object_get(j_result, "client_id")), "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_PRIVATE_KEY_JWT, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI, "jwks_uri", "error"); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(&req_reg, "PUT", registration_client_uri, NULL, NULL, j_client, NULL, 400, NULL, "\"jwks_uri is optional and must be an https:// uri\"", NULL), 1); json_decref(j_client); // Invalid jwks_uri and jwks j_client = json_pack("{sssss[s]sss[ssssss]s[sss]sss[s]sssssssssOss}", "client_id", json_string_value(json_object_get(j_result, "client_id")), "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_PRIVATE_KEY_JWT, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI, "jwks", j_jwks, "jwks_uri", CLIENT_JWKS_URI); ck_assert_ptr_ne(j_client, NULL); ck_assert_int_eq(run_simple_test(&req_reg, "PUT", registration_client_uri, NULL, NULL, j_client, NULL, 400, NULL, "\"jwks_uri and jwks can't coexist\"", NULL), 1); json_decref(j_client); json_decref(j_result); json_decref(j_jwks); ulfius_clean_request(&req_reg); } END_TEST START_TEST(test_oidc_registration_auth_register_client_management_update) { json_t * j_client, * j_client_update, * j_jwks = json_loads(jwk_pubkey_ecdsa_str, JSON_DECODE_ANY, NULL); struct _u_request req, req_reg; struct _u_response resp; json_t * j_body, * j_result, * j_result_get; const char * token, * registration_access_token, * registration_client_uri; char * tmp; ck_assert_ptr_ne(j_jwks, NULL); ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", PLUGIN_REGISTER_AUTH_SCOPE); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req_reg); ulfius_init_response(&resp); tmp = msprintf("Bearer %s", token); u_map_put(req_reg.map_header, "Authorization", tmp); o_free(tmp); j_client = json_pack("{s[s]}", "redirect_uris", CLIENT_REDIRECT_URI); ck_assert_ptr_ne(j_client, NULL); req_reg.http_verb = o_strdup("POST"); req_reg.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/register"); ck_assert_int_eq(ulfius_set_json_body_request(&req_reg, j_client), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req_reg, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_result, NULL); ck_assert_ptr_ne(json_object_get(j_result, "client_id"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "client_secret"), NULL); ck_assert_ptr_ne(registration_access_token = json_string_value(json_object_get(j_result, "registration_access_token")), NULL); ck_assert_ptr_ne(registration_client_uri = json_string_value(json_object_get(j_result, "registration_client_uri")), NULL); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = msprintf(SERVER_URI "/client/%s", json_string_value(json_object_get(j_result, "client_id"))); admin_req.http_verb = o_strdup("GET"); json_decref(j_body); ulfius_clean_request(&req_reg); ulfius_clean_response(&resp); j_client_update = json_pack("{sOsss[s]sss[sssssss]s[sss]sss[s]sssssssssO}", "client_id", json_object_get(j_result, "client_id"), "client_name", CLIENT_NAME, "redirect_uris", CLIENT_REDIRECT_URI, "token_endpoint_auth_method", CLIENT_TOKEN_AUTH_PRIVATE_KEY_JWT, "grant_types", CLIENT_GRANT_TYPE_AUTH_CODE, CLIENT_GRANT_TYPE_IMPLICIT, CLIENT_GRANT_TYPE_PASSWORD, CLIENT_GRANT_TYPE_CLIENT_CREDENTIALS, CLIENT_GRANT_TYPE_REFRESH_TOKEN, CLIENT_GRANT_TYPE_DELETE_TOKEN, CLIENT_GRANT_TYPE_DEVICE_AUTH, "response_types", CLIENT_RESPONSE_TYPE_CODE, CLIENT_RESPONSE_TYPE_TOKEN, CLIENT_RESPONSE_TYPE_ID_TOKEN, "application_type", CLIENT_APP_TYPE_WEB, "contacts", CLIENT_CONTACT, "logo_uri", CLIENT_LOGO_URI, "client_uri", CLIENT_URI, "policy_uri", CLIENT_POLICY_URI, "tos_uri", CLIENT_TOS_URI, "jwks", j_jwks); ck_assert_ptr_ne(j_client_update, NULL); ulfius_init_request(&req_reg); ulfius_init_response(&resp); req_reg.http_verb = o_strdup("PUT"); req_reg.http_url = o_strdup(registration_client_uri); ck_assert_int_eq(ulfius_set_json_body_request(&req_reg, j_client_update), U_OK); tmp = msprintf("Bearer %s", registration_access_token); u_map_put(req_reg.map_header, "Authorization", tmp); o_free(tmp); ck_assert_int_eq(ulfius_send_http_request(&req_reg, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_request(&req_reg); ulfius_clean_response(&resp); ulfius_init_request(&req_reg); ulfius_init_response(&resp); req_reg.http_url = o_strdup(registration_client_uri); tmp = msprintf("Bearer %s", registration_access_token); u_map_put(req_reg.map_header, "Authorization", tmp); o_free(tmp); ck_assert_int_eq(ulfius_send_http_request(&req_reg, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_result_get = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_result_get, NULL); ck_assert_ptr_eq(json_object_get(j_result_get, "registration_access_token"), NULL); ck_assert_ptr_eq(json_object_get(j_result_get, "registration_client_uri"), NULL); ck_assert_ptr_eq(json_object_get(j_result_get, "client_id_issued_at"), NULL); ck_assert_ptr_eq(json_object_get(j_result_get, "client_secret_expires_at"), NULL); json_object_set_new(j_client_update, "client_secret", json_object_get(j_result_get, "client_secret")); ck_assert_str_eq(json_string_value(json_object_get(j_result_get, CLIENT_DEFAULT_KEY_1)), CLIENT_DEFAULT_VALUE_1); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result_get, CLIENT_DEFAULT_KEY_2), 0)), CLIENT_DEFAULT_VALUE_2); ck_assert_str_eq(json_string_value(json_array_get(json_object_get(j_result_get, CLIENT_DEFAULT_KEY_2), 1)), CLIENT_DEFAULT_VALUE_3); ck_assert_ptr_ne(json_search(j_result_get, j_client_update), NULL); ulfius_clean_request(&req_reg); ulfius_clean_response(&resp); json_decref(j_result_get); json_decref(j_result); json_decref(j_client); json_decref(j_client_update); json_decref(j_jwks); } END_TEST START_TEST(test_oidc_registration_auth_register_client_management_delete) { json_t * j_client; struct _u_request req, req_reg; struct _u_response resp; json_t * j_body, * j_result; const char * token, * registration_access_token, * registration_client_uri; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", PLUGIN_REGISTER_AUTH_SCOPE); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req_reg); ulfius_init_response(&resp); tmp = msprintf("Bearer %s", token); u_map_put(req_reg.map_header, "Authorization", tmp); o_free(tmp); j_client = json_pack("{s[s]}", "redirect_uris", CLIENT_REDIRECT_URI); ck_assert_ptr_ne(j_client, NULL); req_reg.http_verb = o_strdup("POST"); req_reg.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/register"); ck_assert_int_eq(ulfius_set_json_body_request(&req_reg, j_client), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req_reg, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_result = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_result, NULL); ck_assert_ptr_ne(json_object_get(j_result, "client_id"), NULL); ck_assert_ptr_eq(json_object_get(j_result, "client_secret"), NULL); ck_assert_ptr_ne(registration_access_token = json_string_value(json_object_get(j_result, "registration_access_token")), NULL); ck_assert_ptr_ne(registration_client_uri = json_string_value(json_object_get(j_result, "registration_client_uri")), NULL); o_free(admin_req.http_url); o_free(admin_req.http_verb); admin_req.http_url = msprintf(SERVER_URI "/client/%s", json_string_value(json_object_get(j_result, "client_id"))); admin_req.http_verb = o_strdup("GET"); json_decref(j_body); ulfius_clean_request(&req_reg); ulfius_clean_response(&resp); ulfius_init_request(&req_reg); ulfius_init_response(&resp); req_reg.http_verb = o_strdup("DELETE"); req_reg.http_url = o_strdup(registration_client_uri); tmp = msprintf("Bearer %s", registration_access_token); u_map_put(req_reg.map_header, "Authorization", tmp); o_free(tmp); ck_assert_int_eq(ulfius_send_http_request(&req_reg, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_request(&req_reg); ulfius_clean_response(&resp); ulfius_init_request(&req_reg); ulfius_init_response(&resp); req_reg.http_url = o_strdup(registration_client_uri); tmp = msprintf("Bearer %s", registration_access_token); u_map_put(req_reg.map_header, "Authorization", tmp); o_free(tmp); ck_assert_int_eq(ulfius_send_http_request(&req_reg, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_request(&req_reg); ulfius_clean_response(&resp); json_decref(j_result); json_decref(j_client); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc token revocation"); tc_core = tcase_create("test_oidc_token_revocation"); tcase_add_test(tc_core, test_oidc_registration_plugin_add_using_management); tcase_add_test(tc_core, test_oidc_registration_auth_register_client_with_valid_credentials); tcase_add_test(tc_core, test_oidc_registration_auth_register_client_management_get); tcase_add_test(tc_core, test_oidc_registration_auth_register_client_management_get_invalid_access_token); tcase_add_test(tc_core, test_oidc_registration_auth_register_client_management_get_invalid_clent_id); tcase_add_test(tc_core, test_oidc_registration_auth_register_client_management_update_error_parameters); tcase_add_test(tc_core, test_oidc_registration_auth_register_client_management_update); tcase_add_test(tc_core, test_oidc_registration_auth_register_client_management_delete); tcase_add_test(tc_core, test_oidc_registration_plugin_remove); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&admin_req); ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); j_body = NULL; res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define USERNAME_ADMIN "admin" #define PASSWORD_ADMIN "password" #define SCOPE_LIST "g_profile scope3" #define CLIENT "client3_id" #define CLIENT_PASSWORD "password" #define CLIENT_SECRET "secret" struct _u_request admin_req; START_TEST(test_oidc_client_secret_client_set_ok) { json_t * j_parameters = json_pack("{ss}", "client_secret", CLIENT_SECRET); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/client/" CLIENT, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_client_secret_resource_owner_pwd_cred_valid_secret) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", SCOPE_LIST); u_map_put(&body, "username", USERNAME); u_map_put(&body, "password", PASSWORD); ck_assert_int_eq(run_simple_test(NULL, "POST", url, CLIENT, CLIENT_SECRET, NULL, &body, 200, NULL, "refresh_token", NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 400, NULL, NULL, NULL), 1); o_free(url); u_map_clean(&body); } END_TEST START_TEST(test_oidc_client_secret_resource_owner_pwd_cred_valid_password) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", SCOPE_LIST); u_map_put(&body, "username", USERNAME); u_map_put(&body, "password", PASSWORD); ck_assert_int_eq(run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 200, NULL, "refresh_token", NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "POST", url, CLIENT, CLIENT_SECRET, NULL, &body, 400, NULL, NULL, NULL), 1); o_free(url); u_map_clean(&body); } END_TEST START_TEST(test_oidc_client_secret_client_disable_ok) { json_t * j_parameters = json_pack("{ss}", "client_secret", ""); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/client/" CLIENT, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc client secret"); tc_core = tcase_create("test_oidc_client_secret"); tcase_add_test(tc_core, test_oidc_client_secret_resource_owner_pwd_cred_valid_password); tcase_add_test(tc_core, test_oidc_client_secret_client_set_ok); tcase_add_test(tc_core, test_oidc_client_secret_resource_owner_pwd_cred_valid_secret); tcase_add_test(tc_core, test_oidc_client_secret_client_disable_ok); tcase_add_test(tc_core, test_oidc_client_secret_resource_owner_pwd_cred_valid_password); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0, i; json_t * j_body; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME_ADMIN, "password", PASSWORD_ADMIN); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api/" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "openid scope1 scope2" #define SCOPE_LIST_PARTIAL "openid scope1" #define SCOPE_LIST_MAX_USE "openid scope1 scope2 scope3" #define CLIENT "client1_id" char * code; START_TEST(test_oidc_code_code_invalid) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", "../../test-oidc.html?param=client1_cb1"); u_map_put(&body, "code", "invalid"); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_code_client_invalid) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", "invalid"); u_map_put(&body, "redirect_uri", "../../test-oidc.html?param=client1_cb1"); u_map_put(&body, "code", code); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 403, NULL, "unauthorized_client", NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_code_redirect_uri_invalid) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", "invalid"); u_map_put(&body, "code", code); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_code_redirect_uri_incorrect) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", "../../test-oidc.html?param=client1_cb2"); u_map_put(&body, "code", code); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_code_ok) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_payload; char ** id_token_split = NULL, * str_payload = NULL, at_hash[33], at_hash_encoded[64]; size_t str_payload_len = 0, at_hash_len = 33, at_hash_encoded_len = 0; gnutls_datum_t at_data; jwt_t * jwt; ulfius_init_request(&req); ulfius_init_response(&resp); u_map_put(req.map_post_body, "grant_type", "authorization_code"); u_map_put(req.map_post_body, "client_id", CLIENT); u_map_put(req.map_post_body, "redirect_uri", "../../test-oidc.html?param=client1_cb1"); u_map_put(req.map_post_body, "code", code); req.http_verb = o_strdup("POST"); req.http_url = msprintf("%s/oidc/token/", SERVER_URI); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_ptr_ne((j_body = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne(json_object_get(j_body, "refresh_token"), NULL); ck_assert_ptr_ne(json_object_get(j_body, "access_token"), NULL); ck_assert_ptr_ne(json_object_get(j_body, "id_token"), NULL); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_body, "access_token")), 0), RHN_OK); r_jwt_free(jwt); ck_assert_int_eq(split_string(json_string_value(json_object_get(j_body, "id_token")), ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), NULL, &str_payload_len), 1); ck_assert_ptr_ne((str_payload = o_malloc(str_payload_len + 1)), NULL); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_payload = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_int_eq(json_object_size(j_payload), 11); ck_assert_ptr_ne(json_object_get(j_payload, "at_hash"), NULL); ck_assert_ptr_ne(json_object_get(j_payload, "c_hash"), NULL); at_data.data = (unsigned char*)json_string_value(json_object_get(j_body, "access_token")); at_data.size = o_strlen(json_string_value(json_object_get(j_body, "access_token"))); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &at_data, at_hash, &at_hash_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64url_encode((unsigned char *)at_hash, at_hash_len/2, (unsigned char *)at_hash_encoded, &at_hash_encoded_len), 1); ck_assert_str_eq(at_hash_encoded, json_string_value(json_object_get(j_payload, "at_hash"))); at_data.data = (unsigned char*)code; at_data.size = o_strlen(code); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &at_data, at_hash, &at_hash_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64url_encode((unsigned char *)at_hash, at_hash_len/2, (unsigned char *)at_hash_encoded, &at_hash_encoded_len), 1); ck_assert_str_eq(at_hash_encoded, json_string_value(json_object_get(j_payload, "c_hash"))); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_body); json_decref(j_payload); free_string_array(id_token_split); o_free(str_payload); } END_TEST START_TEST(test_oidc_code_scope_grant_partial) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie, * code; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST_PARTIAL); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get code code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=code&nonce=nonce1234&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oidc.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(code_resp.map_header, "Location"), "code=")+o_strlen("code=")); ck_assert_ptr_ne(code, NULL); ulfius_clean_response(&code_resp); // Get refresh token from code ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("POST"); code_req.http_url = msprintf("%s/oidc/token/", SERVER_URI); u_map_put(code_req.map_post_body, "grant_type", "authorization_code"); u_map_put(code_req.map_post_body, "client_id", CLIENT); u_map_put(code_req.map_post_body, "redirect_uri", "../../test-oidc.html?param=client1_cb1"); u_map_put(code_req.map_post_body, "code", code); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 200); j_body = ulfius_get_json_body_response(&code_resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_str_eq(json_string_value(json_object_get(j_body, "scope")), SCOPE_LIST_PARTIAL); ulfius_clean_request(&code_req); ulfius_clean_response(&code_resp); o_free(code); json_decref(j_body); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); } END_TEST START_TEST(test_oidc_code_scope_grant_none) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Try to get code code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=code&nonce=nonce1234&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oidc.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "code="), NULL); ulfius_clean_request(&code_req); ulfius_clean_response(&code_resp); ulfius_clean_request(&auth_req); } END_TEST START_TEST(test_oidc_code_scope_grant_all_authorize_partial) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Try to get code code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=code&nonce=nonce1234&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oidc.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "login.html"), NULL); ulfius_clean_response(&code_resp); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); ulfius_clean_request(&code_req); } END_TEST START_TEST(test_oidc_code_retry_with_max_use) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie, * code; jwt_t * jwt; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST_MAX_USE); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 88 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get code code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=code&nonce=nonce1234&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oidc.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(code_resp.map_header, "Location"), "code=")+o_strlen("code=")); ck_assert_ptr_ne(code, NULL); ulfius_clean_response(&code_resp); // Get refresh token from code ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("POST"); code_req.http_url = msprintf("%s/oidc/token/", SERVER_URI); u_map_put(code_req.map_post_body, "grant_type", "authorization_code"); u_map_put(code_req.map_post_body, "client_id", CLIENT); u_map_put(code_req.map_post_body, "redirect_uri", "../../test-oidc.html?param=client1_cb1"); u_map_put(code_req.map_post_body, "code", code); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 200); j_body = ulfius_get_json_body_response(&code_resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_str_eq(json_string_value(json_object_get(j_body, "scope")), SCOPE_LIST_MAX_USE); ulfius_clean_response(&code_resp); o_free(code); json_decref(j_body); // Try to get another code with the same session but redirected to login page ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=code&nonce=nonce1234&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oidc.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "login.html"), NULL); ulfius_clean_response(&code_resp); // Reauthenticate with scheme mock 88 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get another code ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=code&nonce=nonce1234&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oidc.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(code_resp.map_header, "Location"), "code=")+o_strlen("code=")); ck_assert_ptr_ne(code, NULL); ulfius_clean_response(&code_resp); // Get another refresh token from code ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("POST"); code_req.http_url = msprintf("%s/oidc/token/", SERVER_URI); u_map_put(code_req.map_post_body, "grant_type", "authorization_code"); u_map_put(code_req.map_post_body, "client_id", CLIENT); u_map_put(code_req.map_post_body, "redirect_uri", "../../test-oidc.html?param=client1_cb1"); u_map_put(code_req.map_post_body, "code", code); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 200); j_body = ulfius_get_json_body_response(&code_resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_body, "access_token")), 0), RHN_OK); ck_assert_str_eq(SCOPE_LIST_MAX_USE, r_jwt_get_claim_str_value(jwt, "aud")); r_jwt_free(jwt); ck_assert_str_eq(json_string_value(json_object_get(j_body, "scope")), SCOPE_LIST_MAX_USE); ulfius_clean_request(&code_req); ulfius_clean_response(&code_resp); o_free(code); json_decref(j_body); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc code"); tc_core = tcase_create("test_oidc_code"); tcase_add_test(tc_core, test_oidc_code_code_invalid); tcase_add_test(tc_core, test_oidc_code_client_invalid); tcase_add_test(tc_core, test_oidc_code_redirect_uri_invalid); tcase_add_test(tc_core, test_oidc_code_redirect_uri_incorrect); tcase_add_test(tc_core, test_oidc_code_ok); tcase_add_test(tc_core, test_oidc_code_scope_grant_partial); tcase_add_test(tc_core, test_oidc_code_scope_grant_none); tcase_add_test(tc_core, test_oidc_code_scope_grant_all_authorize_partial); tcase_add_test(tc_core, test_oidc_code_retry_with_max_use); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, register_req, user_req; struct _u_response auth_resp, scope_resp, code_resp; json_t * j_body, * j_register; int res, do_test = 0, i; char * url; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_request(&scope_req); ulfius_init_request(®ister_req); ulfius_init_response(&auth_resp); ulfius_init_response(&scope_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api/" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "openid" #define SCOPE1_REQUIRED "scope1" #define SCOPE2_REQUIRED "scope2" #define SCOPE_NOT_REQUIRED "scope3" #define CLIENT "client1_id" #define CLIENT_CONFIDENTIAL "client3_id" #define REDIRECT_URI "..%2f..%2ftest-oidc.html?param=client1_cb1" #define REDIRECT_URI_CONFIDENTIAL "..%2f..%2ftest-oidc.html?param=client3" #define REDIRECT_URI_DECODED "../../test-oidc.html?param=client1_cb1" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define CODE_CHALLENGE_VALID "V0UN9ToT-UnbxeIx7imQdhFjsAZTmuARpHyuD2ajIIo" #define CODE_CHALLENGE_VALID_2 "GE8iHoAZ3H6to1ERRDVLx8iJD0XLHF0XjmfxWoAibQE" #define CODE_CHALLENGE_42 "NfzP48kCM-QfPKoR2p-lJvTAG28TR-FlWXqxp7naU8I" #define CODE_CHALLENGE_INVALID_CHARSET "Iy8nznHo9PQsgDeabc-fZbPWVFApHSoyRZskN6rH-d0" #define CODE_VERIFIER_VALID "XvkLR4XIl4DbkFz3RLEZUBStp8yIjvF8UtfRv0nkK8DqmrBtWvHmEuyBL2enyLF9" #define CODE_VERIFIER_VALID_2 "6nwfKTI6ODJmm89xBbvX0mOLbk0PbLY2sNS2o9vFixHU8hAktkQw3PwJHX1GAf69" #define CODE_VERIFIER_INVALID_42 "XvkLR4XIl4DbkFz3RLEZUBStp8yIjvF8UtfRv0nkww" #define CODE_VERIFIER_INVALID_129 "XvkLR4XIl4DbkFz3RLEZUBStp8yIjvF8UtfRv0nkK8DqmrBtWvHmEuyBL2enyLF9XvkLR4XIl4DbkFz3RLEZUBStp8yIjvF8UtfRv0nkK8DqmrBtWvHmEuyBL2enyLF9X" #define CODE_VERIFIER_INVALID_CHARSET "XvkLR4XIl4DbkFz3RLEZUBStp8yIjvF8UtfRv0nkK8DqmrBt,vHmEuyBL2enyLF9" #define CODE_CHALLENGE_METHOD_PLAIN "plain" #define CODE_CHALLENGE_METHOD_S256 "S256" #define PLUGIN_MODULE "oidc" #define PLUGIN_NAME "challenge" #define PLUGIN_ISS "https://glewlwyd.tld" #define PLUGIN_DISPLAY_NAME "Challenge test" #define PLUGIN_JWT_TYPE "sha" #define PLUGIN_JWT_KEY_SIZE "256" #define PLUGIN_KEY "secret" #define PLUGIN_CODE_DURATION 600 #define PLUGIN_REFRESH_TOKEN_DURATION 1209600 #define PLUGIN_ACCESS_TOKEN_DURATION 3600 struct _u_request user_req; struct _u_request admin_req; START_TEST(test_oidc_code_code_challenge_add_plugin_with_plain) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisososososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "pkce-allowed", json_true(), "pkce-method-plain-allowed", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_code_code_challenge_add_plugin_without_plain) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisososososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "pkce-allowed", json_true(), "pkce-method-plain-allowed", json_false()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_code_code_challenge_add_plugin_required) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisosososososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "pkce-allowed", json_true(), "pkce-method-plain-allowed", json_false(), "pkce-required", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_code_code_challenge_add_plugin_required_public_client) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisosososososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "pkce-allowed", json_true(), "pkce-method-plain-allowed", json_false(), "pkce-required-public-client", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_code_code_challenge_add_plugin_required_with_scopes) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisosososososososos[ss]}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "pkce-allowed", json_true(), "pkce-method-plain-allowed", json_false(), "pkce-required", json_false(), "pkce-scopes", SCOPE1_REQUIRED, SCOPE2_REQUIRED); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_code_code_challenge_remove_plugin) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_code_code_challenge_invalid_code_challenge_method) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_CHALLENGE_VALID "&code_challenge_method=error&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"), 1); } END_TEST START_TEST(test_oidc_code_code_challenge_plain_invalid_code_challenge_method) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_CHALLENGE_VALID "&code_challenge_method=" CODE_CHALLENGE_METHOD_PLAIN "&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"), 1); } END_TEST START_TEST(test_oidc_code_code_challenge_plain_invalid_length) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_VERIFIER_INVALID_42 "&code_challenge_method=" CODE_CHALLENGE_METHOD_PLAIN "&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"), 1); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_VERIFIER_INVALID_129 "&code_challenge_method=" CODE_CHALLENGE_METHOD_PLAIN "&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"), 1); } END_TEST START_TEST(test_oidc_code_code_challenge_plain_invalid_charset) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_VERIFIER_INVALID_CHARSET "&code_challenge_method=" CODE_CHALLENGE_METHOD_PLAIN "&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"), 1); } END_TEST START_TEST(test_oidc_code_code_challenge_plain_method_empty_ok) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_VERIFIER_VALID "&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "code="), 1); } END_TEST START_TEST(test_oidc_code_code_challenge_plain_method_set_ok) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_VERIFIER_VALID "&code_challenge_method=" CODE_CHALLENGE_METHOD_PLAIN "&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "code="), 1); } END_TEST START_TEST(test_oidc_code_code_challenge_plain_verifier_invalid_value) { struct _u_response resp; char * code = NULL; struct _u_map body; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_VERIFIER_VALID "&code_challenge_method=" CODE_CHALLENGE_METHOD_PLAIN "&scope=" SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "code_verifier", CODE_VERIFIER_VALID_2); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", REDIRECT_URI_DECODED); u_map_put(&body, "code", code); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); u_map_clean(&body); ulfius_clean_response(&resp); o_free(code); } END_TEST START_TEST(test_oidc_code_code_challenge_plain_verifier_ok) { struct _u_response resp; char * code = NULL; struct _u_map body; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_VERIFIER_VALID "&code_challenge_method=" CODE_CHALLENGE_METHOD_PLAIN "&scope=" SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "code_verifier", CODE_VERIFIER_VALID); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", REDIRECT_URI_DECODED); u_map_put(&body, "code", code); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 200, NULL, NULL, NULL), 1); u_map_clean(&body); ulfius_clean_response(&resp); o_free(code); } END_TEST START_TEST(test_oidc_code_code_challenge_s256_invalid_length) { struct _u_response resp; char * code = NULL; struct _u_map body; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_CHALLENGE_42 "&code_challenge_method=" CODE_CHALLENGE_METHOD_S256 "&scope=" SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "code_verifier", CODE_VERIFIER_INVALID_42); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", REDIRECT_URI_DECODED); u_map_put(&body, "code", code); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); u_map_clean(&body); ulfius_clean_response(&resp); o_free(code); } END_TEST START_TEST(test_oidc_code_code_challenge_s256_invalid_charset) { struct _u_response resp; char * code = NULL; struct _u_map body; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_CHALLENGE_INVALID_CHARSET "&code_challenge_method=" CODE_CHALLENGE_METHOD_S256 "&scope=" SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "code_verifier", CODE_VERIFIER_INVALID_CHARSET); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", REDIRECT_URI_DECODED); u_map_put(&body, "code", code); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); u_map_clean(&body); ulfius_clean_response(&resp); o_free(code); } END_TEST START_TEST(test_oidc_code_code_challenge_s256_verifier_invalid_value) { struct _u_response resp; char * code = NULL; struct _u_map body; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_CHALLENGE_VALID "&code_challenge_method=" CODE_CHALLENGE_METHOD_S256 "&scope=" SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "code_verifier", CODE_VERIFIER_VALID_2); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", REDIRECT_URI_DECODED); u_map_put(&body, "code", code); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); u_map_clean(&body); ulfius_clean_response(&resp); o_free(code); } END_TEST START_TEST(test_oidc_code_code_challenge_s256_method_empty_invalid) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_VERIFIER_VALID "&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"), 1); } END_TEST START_TEST(test_oidc_code_code_challenge_s256_method_set_plain_invalid) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_VERIFIER_VALID "&code_challenge_method=" CODE_CHALLENGE_METHOD_PLAIN "&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"), 1); } END_TEST START_TEST(test_oidc_code_code_challenge_s256_method_set_ok) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_CHALLENGE_VALID "&code_challenge_method=" CODE_CHALLENGE_METHOD_S256 "&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "code="), 1); } END_TEST START_TEST(test_oidc_code_code_challenge_s256_verifier_ok) { struct _u_response resp; char * code = NULL; struct _u_map body; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_CHALLENGE_VALID "&code_challenge_method=" CODE_CHALLENGE_METHOD_S256 "&scope=" SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "code_verifier", CODE_VERIFIER_VALID); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", REDIRECT_URI_DECODED); u_map_put(&body, "code", code); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 200, NULL, NULL, NULL), 1); u_map_clean(&body); ulfius_clean_response(&resp); o_free(code); } END_TEST START_TEST(test_oidc_code_code_challenge_missing_ok) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "code="), 1); } END_TEST START_TEST(test_oidc_code_code_challenge_missing_confidential_ok) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT_CONFIDENTIAL "&redirect_uri=" REDIRECT_URI_CONFIDENTIAL "&state=xyzabcd&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "code="), 1); } END_TEST START_TEST(test_oidc_code_code_challenge_missing_error) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"), 1); } END_TEST START_TEST(test_oidc_code_code_challenge_missing_resp_type_token_id_token_ok) { struct _u_response resp; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&user_req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=id_token&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&scope=" SCOPE_LIST, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "&id_token="), NULL); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_code_code_challenge_scopes_required_ok) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&code_challenge=" CODE_CHALLENGE_VALID "&code_challenge_method=" CODE_CHALLENGE_METHOD_S256 "&scope=" SCOPE1_REQUIRED " " SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "code="), 1); } END_TEST START_TEST(test_oidc_code_code_challenge_scopes_required_error) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&scope=" SCOPE1_REQUIRED " " SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"), 1); } END_TEST START_TEST(test_oidc_code_code_challenge_scopes_not_required_ok) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/" PLUGIN_NAME "/auth?response_type=code&nonce=nonce1234&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&scope=" SCOPE_NOT_REQUIRED " " SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "code="), 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc code_challenge"); tc_core = tcase_create("test_oidc_code_challenge"); tcase_add_test(tc_core, test_oidc_code_code_challenge_add_plugin_with_plain); tcase_add_test(tc_core, test_oidc_code_code_challenge_missing_ok); tcase_add_test(tc_core, test_oidc_code_code_challenge_invalid_code_challenge_method); tcase_add_test(tc_core, test_oidc_code_code_challenge_plain_invalid_length); tcase_add_test(tc_core, test_oidc_code_code_challenge_plain_invalid_charset); tcase_add_test(tc_core, test_oidc_code_code_challenge_plain_method_empty_ok); tcase_add_test(tc_core, test_oidc_code_code_challenge_plain_method_set_ok); tcase_add_test(tc_core, test_oidc_code_code_challenge_plain_verifier_invalid_value); tcase_add_test(tc_core, test_oidc_code_code_challenge_plain_verifier_ok); tcase_add_test(tc_core, test_oidc_code_code_challenge_s256_invalid_length); tcase_add_test(tc_core, test_oidc_code_code_challenge_s256_invalid_charset); tcase_add_test(tc_core, test_oidc_code_code_challenge_s256_verifier_invalid_value); tcase_add_test(tc_core, test_oidc_code_code_challenge_s256_method_set_ok); tcase_add_test(tc_core, test_oidc_code_code_challenge_s256_verifier_ok); tcase_add_test(tc_core, test_oidc_code_code_challenge_remove_plugin); tcase_add_test(tc_core, test_oidc_code_code_challenge_add_plugin_without_plain); tcase_add_test(tc_core, test_oidc_code_code_challenge_missing_ok); tcase_add_test(tc_core, test_oidc_code_code_challenge_plain_invalid_code_challenge_method); tcase_add_test(tc_core, test_oidc_code_code_challenge_s256_invalid_length); tcase_add_test(tc_core, test_oidc_code_code_challenge_s256_invalid_charset); tcase_add_test(tc_core, test_oidc_code_code_challenge_s256_verifier_invalid_value); tcase_add_test(tc_core, test_oidc_code_code_challenge_s256_method_empty_invalid); tcase_add_test(tc_core, test_oidc_code_code_challenge_s256_method_set_plain_invalid); tcase_add_test(tc_core, test_oidc_code_code_challenge_s256_method_set_ok); tcase_add_test(tc_core, test_oidc_code_code_challenge_s256_verifier_ok); tcase_add_test(tc_core, test_oidc_code_code_challenge_remove_plugin); tcase_add_test(tc_core, test_oidc_code_code_challenge_add_plugin_required); tcase_add_test(tc_core, test_oidc_code_code_challenge_s256_verifier_ok); tcase_add_test(tc_core, test_oidc_code_code_challenge_missing_error); tcase_add_test(tc_core, test_oidc_code_code_challenge_missing_resp_type_token_id_token_ok); tcase_add_test(tc_core, test_oidc_code_code_challenge_remove_plugin); tcase_add_test(tc_core, test_oidc_code_code_challenge_add_plugin_required_with_scopes); tcase_add_test(tc_core, test_oidc_code_code_challenge_scopes_required_ok); tcase_add_test(tc_core, test_oidc_code_code_challenge_scopes_required_error); tcase_add_test(tc_core, test_oidc_code_code_challenge_scopes_not_required_ok); tcase_add_test(tc_core, test_oidc_code_code_challenge_remove_plugin); tcase_add_test(tc_core, test_oidc_code_code_challenge_add_plugin_required_public_client); tcase_add_test(tc_core, test_oidc_code_code_challenge_missing_confidential_ok); tcase_add_test(tc_core, test_oidc_code_code_challenge_missing_error); tcase_add_test(tc_core, test_oidc_code_code_challenge_missing_resp_type_token_id_token_ok); tcase_add_test(tc_core, test_oidc_code_code_challenge_s256_verifier_ok); tcase_add_test(tc_core, test_oidc_code_code_challenge_remove_plugin); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&user_req); ulfius_init_request(&admin_req); ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "scope1 scope2" #define CLIENT "client3_id" #define CLIENT_PASSWORD "password" #define REDIRECT_URI "../../test-oidc.html?param=client3" #define REDIRECT_URI_ENCODED "..%2f..%2ftest-oidc.html%3fparam%3dclient3" struct _u_request user_req; char * code; START_TEST(test_oidc_code_client_confidential_code_invalid) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", REDIRECT_URI); u_map_put(&body, "code", "invalid"); o_free(user_req.http_verb); user_req.http_verb = NULL; int res = run_simple_test(&user_req, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_code_client_confidential_client_invalid) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", "invalid"); u_map_put(&body, "redirect_uri", REDIRECT_URI); u_map_put(&body, "code", code); o_free(user_req.http_verb); user_req.http_verb = NULL; int res = run_simple_test(&user_req, "POST", url, "invalid", CLIENT_PASSWORD, NULL, &body, 403, NULL, "unauthorized_client", NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_code_client_confidential_redirect_uri_invalid) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", "invalid"); u_map_put(&body, "code", code); o_free(user_req.http_verb); user_req.http_verb = NULL; int res = run_simple_test(&user_req, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_code_client_confidential_ok) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", REDIRECT_URI); u_map_put(&body, "code", code); o_free(user_req.http_verb); user_req.http_verb = NULL; int res = run_simple_test(&user_req, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 200, NULL, "refresh_token", NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc code client confidential"); tc_core = tcase_create("test_oidc_code"); tcase_add_test(tc_core, test_oidc_code_client_confidential_code_invalid); tcase_add_test(tc_core, test_oidc_code_client_confidential_client_invalid); tcase_add_test(tc_core, test_oidc_code_client_confidential_redirect_uri_invalid); tcase_add_test(tc_core, test_oidc_code_client_confidential_ok); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, register_req; struct _u_response auth_resp, scope_resp, code_resp; json_t * j_body, * j_register; int res, do_test = 0, i; char * url; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_request(&scope_req); ulfius_init_request(®ister_req); ulfius_init_response(&scope_resp); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api/" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "scope1 scope2 openid" #define CLIENT "client1_id" char * code; START_TEST(test_oidc_code_idtoken_code_invalid) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", "../../test-oidc.html?param=client1_cb1"); u_map_put(&body, "code", "invalid"); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_code_idtoken_client_invalid) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", "invalid"); u_map_put(&body, "redirect_uri", "../../test-oidc.html?param=client1_cb1"); u_map_put(&body, "code", code); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 403, NULL, "unauthorized_client", NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_code_idtoken_redirect_uri_invalid) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", "invalid"); u_map_put(&body, "code", code); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_code_idtoken_ok) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", "../../test-oidc.html?param=client1_cb1"); u_map_put(&body, "code", code); ck_assert_int_eq(run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 200, NULL, "id_token", NULL), 1); o_free(url); u_map_clean(&body); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc code idtoken"); tc_core = tcase_create("test_oidc_code_idtoken"); tcase_add_test(tc_core, test_oidc_code_idtoken_code_invalid); tcase_add_test(tc_core, test_oidc_code_idtoken_client_invalid); tcase_add_test(tc_core, test_oidc_code_idtoken_redirect_uri_invalid); tcase_add_test(tc_core, test_oidc_code_idtoken_ok); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, register_req, user_req; struct _u_response auth_resp, scope_resp, code_resp; json_t * j_body, * j_register; int res, do_test = 0, i; char * url; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_request(&scope_req); ulfius_init_request(®ister_req); ulfius_init_response(&auth_resp); ulfius_init_response(&scope_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "user1" #define USER_PASSWORD "password" #define PLUGIN_NAME "oidc_code_replay" #define SCOPE_LIST "openid" #define CLIENT_ID "client3_id" #define CLIENT_SECRET "password" #define CLIENT_REDIRECT_URI "../../test-oauth2.html?param=client3" #define CLIENT_REDIRECT_URI_ENCODED "..%2F..%2Ftest-oauth2.html%3Fparam%3Dclient3" #define RESPONSE_TYPE "code" struct _u_request admin_req; struct _u_request user_req; START_TEST(test_oidc_code_replay_add_plugin_with_revoke_replay) { json_t * j_param = json_pack("{sssssss{sssssssssisisisososososososososososo}}", "module", "oidc", "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", "https://glewlwyd.tld", "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_NAME, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-code-revoke-replayed", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "introspection-revocation-allowed", json_true(), "introspection-revocation-allow-target-client", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_code_replay_test_revoked_tokens) { struct _u_request req; struct _u_response resp; char * code; const char * refresh_token, * access_token, * access_token_refreshed; json_t * j_body_code, * j_body_refresh, * j_introspect; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE "&nonce=nonce1234&client_id=" CLIENT_ID "&redirect_uri=" CLIENT_REDIRECT_URI_ENCODED "&scope=" SCOPE_LIST "&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); ck_assert_ptr_ne(NULL, code); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_body_code = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(refresh_token = json_string_value(json_object_get(j_body_code, "refresh_token")), NULL); ck_assert_ptr_ne(access_token = json_string_value(json_object_get(j_body_code, "access_token")), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "refresh_token", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "refresh_token", refresh_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_body_refresh = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(access_token_refreshed = json_string_value(json_object_get(j_body_refresh, "access_token")), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", refresh_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_true()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", access_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_true()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", access_token_refreshed, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_true()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", refresh_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_false()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", access_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_false()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", access_token_refreshed, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_false()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); json_decref(j_body_refresh); json_decref(j_body_code); o_free(code); } END_TEST START_TEST(test_oidc_code_replay_add_plugin_without_revoke_replay) { json_t * j_param = json_pack("{sssssss{sssssssssisisisososososososososososo}}", "module", "oidc", "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", "https://glewlwyd.tld", "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_NAME, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-code-revoke-replayed", json_false(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "introspection-revocation-allowed", json_true(), "introspection-revocation-allow-target-client", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_code_replay_test_non_revoked_tokens) { struct _u_request req; struct _u_response resp; char * code; const char * refresh_token, * access_token, * access_token_refreshed; json_t * j_body_code, * j_body_refresh, * j_introspect; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE "&nonce=nonce1234&client_id=" CLIENT_ID "&redirect_uri=" CLIENT_REDIRECT_URI_ENCODED "&scope=" SCOPE_LIST "&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); ck_assert_ptr_ne(NULL, code); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_body_code = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(refresh_token = json_string_value(json_object_get(j_body_code, "refresh_token")), NULL); ck_assert_ptr_ne(access_token = json_string_value(json_object_get(j_body_code, "access_token")), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "refresh_token", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "refresh_token", refresh_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_body_refresh = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(access_token_refreshed = json_string_value(json_object_get(j_body_refresh, "access_token")), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", refresh_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_true()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", access_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_true()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", access_token_refreshed, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_true()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", refresh_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_true()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", access_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_true()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "token", access_token_refreshed, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_introspect = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_eq(json_object_get(j_introspect, "active"), json_true()); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_introspect); json_decref(j_body_refresh); json_decref(j_body_code); o_free(code); } END_TEST START_TEST(test_oidc_code_replay_delete_plugin) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc code replay"); tc_core = tcase_create("test_oidc_code_replay"); tcase_add_test(tc_core, test_oidc_code_replay_add_plugin_with_revoke_replay); tcase_add_test(tc_core, test_oidc_code_replay_test_revoked_tokens); tcase_add_test(tc_core, test_oidc_code_replay_delete_plugin); tcase_add_test(tc_core, test_oidc_code_replay_add_plugin_without_revoke_replay); tcase_add_test(tc_core, test_oidc_code_replay_test_non_revoked_tokens); tcase_add_test(tc_core, test_oidc_code_replay_delete_plugin); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); ulfius_init_request(&user_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "User %s authenticated", USER_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(user_req.map_header, "Cookie", cookie); o_free(cookie); } else { do_test = 0; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication user"); do_test = 0; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); } if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } run_simple_test(&user_req, "DELETE", SERVER_URI "/auth/", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL); ulfius_clean_request(&admin_req); ulfius_clean_request(&user_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_delete_token.c000066400000000000000000000067671415646314000221030ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile scope3" #define CLIENT "client1_id" char * refresh_token; START_TEST(test_oidc_delete_token_token_invalid) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "delete_token"); u_map_put(&body, "refresh_token", "invalid"); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_delete_token_ok) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "delete_token"); u_map_put(&body, "refresh_token", refresh_token); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 200, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_delete_token_token_already_deleted) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "delete_token"); u_map_put(&body, "refresh_token", refresh_token); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc delete token"); tc_core = tcase_create("test_oidc_delete_token"); tcase_add_test(tc_core, test_oidc_delete_token_token_invalid); tcase_add_test(tc_core, test_oidc_delete_token_ok); tcase_add_test(tc_core, test_oidc_delete_token_token_already_deleted); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/oidc/token/", SERVER_URI); u_map_put(auth_req.map_post_body, "grant_type", "password"); u_map_put(auth_req.map_post_body, "username", USERNAME); u_map_put(auth_req.map_post_body, "password", PASSWORD); u_map_put(auth_req.map_post_body, "scope", SCOPE_LIST); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK) { json_t * json_body = ulfius_get_json_body_response(&auth_resp, NULL); refresh_token = o_strdup(json_string_value(json_object_get(json_body, "refresh_token"))); y_log_message(Y_LOG_LEVEL_INFO, "User %s authenticated", USERNAME); json_decref(json_body); } ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); o_free(refresh_token); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_delete_token_client_confidential.c000066400000000000000000000115571415646314000261510ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile scope3" #define CLIENT "client3_id" #define CLIENT_PASSWORD "password" char * refresh_token; START_TEST(test_oidc_delete_token_client_confidential_token_invalid) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "delete_token"); u_map_put(&body, "refresh_token", "invalid"); int res = run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_delete_token_client_confidential_client_invalid) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "delete_token"); u_map_put(&body, "refresh_token", refresh_token); int res = run_simple_test(NULL, "POST", url, CLIENT, "error", NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_delete_token_client_confidential_no_client) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "delete_token"); u_map_put(&body, "refresh_token", refresh_token); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_delete_token_client_confidential_ok) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "delete_token"); u_map_put(&body, "refresh_token", refresh_token); int res = run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 200, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_delete_token_client_confidential_token_already_deleted) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "delete_token"); u_map_put(&body, "refresh_token", refresh_token); int res = run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc delete token client confidential"); tc_core = tcase_create("test_oidc_delete_token_client_confidential"); tcase_add_test(tc_core, test_oidc_delete_token_client_confidential_token_invalid); tcase_add_test(tc_core, test_oidc_delete_token_client_confidential_client_invalid); tcase_add_test(tc_core, test_oidc_delete_token_client_confidential_no_client); tcase_add_test(tc_core, test_oidc_delete_token_client_confidential_ok); tcase_add_test(tc_core, test_oidc_delete_token_client_confidential_token_already_deleted); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/oidc/token/", SERVER_URI); auth_req.auth_basic_user = strdup(CLIENT); auth_req.auth_basic_password = strdup(CLIENT_PASSWORD); u_map_put(auth_req.map_post_body, "grant_type", "password"); u_map_put(auth_req.map_post_body, "username", USERNAME); u_map_put(auth_req.map_post_body, "password", PASSWORD); u_map_put(auth_req.map_post_body, "scope", SCOPE_LIST); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK) { json_t * json_body = ulfius_get_json_body_response(&auth_resp, NULL); refresh_token = o_strdup(json_string_value(json_object_get(json_body, "refresh_token"))); y_log_message(Y_LOG_LEVEL_INFO, "User %s authenticated", USERNAME); json_decref(json_body); } ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); o_free(refresh_token); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_device_authorization.c000066400000000000000000001310211415646314000236360ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define SCOPE_LIST "openid g_profile" #define NONCE_TEST "nonce5678" #define STATE_TEST "abcxyz" #define PLUGIN_MODULE "oidc" #define PLUGIN_NAME "oidc_device" #define PLUGIN_DISPLAY_NAME "oidc with device authorization" #define PLUGIN_ISS "https://glewlwyd.tld" #define PLUGIN_CODE_DURATION 600 #define PLUGIN_REFRESH_TOKEN_DURATION 1209600 #define PLUGIN_ACCESS_TOKEN_DURATION 3600 #define CLIENT_ID "client_device" #define CLIENT_NAME "client for device" #define CLIENT_SECRET "very-secret" #define RESOURCE "https://resource.tld/" struct _u_request admin_req; struct _u_request user_req; START_TEST(test_oidc_device_authorization_add_module_ok) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisosososososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", "sha", "jwt-key-size", "256", "key", "secret", "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "auth-type-device-enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_device_authorization_add_client_confidential_ok) { json_t * j_parameters = json_pack("{sssssssos[s]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "client_secret", CLIENT_SECRET, "confidential", json_true(), "authorization_type", "device_authorization", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_device_authorization_add_client_public_ok) { json_t * j_parameters = json_pack("{sssssos[s]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "confidential", json_false(), "authorization_type", "device_authorization", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_device_authorization_add_client_unauthorized) { json_t * j_parameters = json_pack("{sssssos[s]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "confidential", json_false(), "authorization_type", "code", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_device_authorization_delete_module) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_device_authorization_delete_client) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_ID, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_device_authorization_client_cred_post_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "client_secret", CLIENT_SECRET); u_map_put(req.map_post_body, "scope", SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_device_authorization_client_cred_header_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_device_authorization_client_cred_header_invalid) { struct _u_request req; struct _u_response resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup("error"); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(403, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_device_authorization_client_cred_header_no_post_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_device_authorization_client_pub_post_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_device_authorization_client_pub_post_invalid) { struct _u_request req; struct _u_response resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(403, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_device_authorization_user_code_redirect_login_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp; const char * redirect_uri; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device/"); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=device"), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_device_authorization_user_code_input_invalid) { struct _u_request req; struct _u_response resp; json_t * j_resp; const char * redirect_uri; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=error&g_continue"); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceCodeError"), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_device_authorization_user_code_input_redirect_login_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp; const char * redirect_uri, * code; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "login.html"), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_resp); } END_TEST START_TEST(test_oidc_device_authorization_user_code_input_complete_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp, * j_grant; const char * redirect_uri, * code; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", SCOPE_LIST); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); json_decref(j_resp); j_grant = json_pack("{ss}", "scope", ""); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); } END_TEST START_TEST(test_oidc_device_authorization_device_verification_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp, * j_grant; const char * redirect_uri, * code, * device_code; jwt_t * jwt; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", SCOPE_LIST); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", ""); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "device_code", device_code); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "refresh_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "id_token"), NULL); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_str_eq(SCOPE_LIST, r_jwt_get_claim_str_value(jwt, "aud")); r_jwt_free(jwt); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_device_authorization_device_verification_auth_pending) { struct _u_request req; struct _u_response resp; json_t * j_resp; const char * code, * device_code; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "device_code", device_code); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "error")), "authorization_pending"); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_device_authorization_device_verification_auth_slow_down) { struct _u_request req; struct _u_response resp; json_t * j_resp; const char * code, * device_code; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "device_code", device_code); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "error")), "authorization_pending"); ulfius_clean_response(&resp); json_decref(j_resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(400, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "error")), "slow_down"); ulfius_clean_response(&resp); json_decref(j_resp); ulfius_clean_request(&req); } END_TEST START_TEST(test_oidc_device_authorization_device_verification_device_code_invalid) { struct _u_request req; struct _u_response resp; json_t * j_resp, * j_grant; const char * redirect_uri, * code; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", SCOPE_LIST); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", ""); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "device_code", "error"); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(400, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_device_authorization_device_verification_client_invalid) { struct _u_request req; struct _u_response resp; json_t * j_resp, * j_grant; const char * redirect_uri, * code, * device_code; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", SCOPE_LIST); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", ""); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", "client1_id"); u_map_put(req.map_post_body, "device_code", device_code); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(403, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_device_authorization_device_verification_client_secret_invalid) { struct _u_request req; struct _u_response resp; json_t * j_resp, * j_grant; const char * redirect_uri, * code, * device_code; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", SCOPE_LIST); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", ""); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "device_code", device_code); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup("error"); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(403, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc device authorization"); tc_core = tcase_create("test_oidc_device_authorization"); tcase_add_test(tc_core, test_oidc_device_authorization_add_module_ok); tcase_add_test(tc_core, test_oidc_device_authorization_add_client_public_ok); tcase_add_test(tc_core, test_oidc_device_authorization_client_pub_post_valid); tcase_add_test(tc_core, test_oidc_device_authorization_delete_client); tcase_add_test(tc_core, test_oidc_device_authorization_add_client_unauthorized); tcase_add_test(tc_core, test_oidc_device_authorization_client_pub_post_invalid); tcase_add_test(tc_core, test_oidc_device_authorization_delete_client); tcase_add_test(tc_core, test_oidc_device_authorization_add_client_confidential_ok); tcase_add_test(tc_core, test_oidc_device_authorization_client_cred_post_valid); tcase_add_test(tc_core, test_oidc_device_authorization_client_cred_header_valid); tcase_add_test(tc_core, test_oidc_device_authorization_client_cred_header_no_post_valid); tcase_add_test(tc_core, test_oidc_device_authorization_client_cred_header_invalid); tcase_add_test(tc_core, test_oidc_device_authorization_user_code_redirect_login_valid); tcase_add_test(tc_core, test_oidc_device_authorization_user_code_input_invalid); tcase_add_test(tc_core, test_oidc_device_authorization_user_code_input_redirect_login_valid); tcase_add_test(tc_core, test_oidc_device_authorization_user_code_input_complete_valid); tcase_add_test(tc_core, test_oidc_device_authorization_device_verification_valid); tcase_add_test(tc_core, test_oidc_device_authorization_device_verification_auth_pending); tcase_add_test(tc_core, test_oidc_device_authorization_device_verification_auth_slow_down); tcase_add_test(tc_core, test_oidc_device_authorization_device_verification_device_code_invalid); tcase_add_test(tc_core, test_oidc_device_authorization_device_verification_client_invalid); tcase_add_test(tc_core, test_oidc_device_authorization_device_verification_client_secret_invalid); tcase_add_test(tc_core, test_oidc_device_authorization_delete_client); tcase_add_test(tc_core, test_oidc_device_authorization_delete_module); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, register_req; struct _u_response auth_resp, scope_resp; json_t * j_body; int res, do_test = 0, i; char * url; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #include "../src/glewlwyd-common.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define PLUGIN_NAME "oidc_claims" #define INTROSPECT_SCOPE "g_admin" #define RAR1 "type1" #define RAR2 "type2" #define RAR3 "type3" #define RAR4 "type4" #define ENRICHED1 "name" #define ENRICHED2 "email" #define SCOPE_1 "g_profile" #define SCOPE_2 "openid" #define SCOPE_3 "g_admin" #define PLUGIN_PAR_PREFIX "urn:ietf:params:oauth:request_uri:" #define PLUGIN_PAR_DURATION 90 #define PLUGIN_CIBA_DEFAULT_EXPIRATION 600 #define PLUGIN_CIBA_MAXIMUM_EXPIRATION 1200 struct _u_request admin_req; START_TEST(test_oidc_discovery_default_test) { json_t * j_result = json_loads("{\"issuer\":\"https://glewlwyd.tld\",\"authorization_endpoint\":\"http://localhost:4593/api/oidc/auth\",\"token_endpoint\":\"http://localhost:4593/api/oidc/token\",\"userinfo_endpoint\":\"http://localhost:4593/api/oidc/userinfo\",\"jwks_uri\":\"http://localhost:4593/api/oidc/jwks\",\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\"],\"id_token_signing_alg_values_supported\":[\"HS256\",\"HS384\",\"HS512\"],\"userinfo_signing_alg_values_supported\":[\"HS256\",\"HS384\",\"HS512\"],\"access_token_signing_alg_values_supported\":[\"HS256\",\"HS384\",\"HS512\"],\"scopes_supported\":[\"openid\"],\"response_types_supported\":[\"code\",\"id_token\",\"token id_token\",\"code id_token\",\"code token id_token\",\"none\",\"password\",\"token\",\"client_credentials\",\"refresh_token\"],\"response_modes_supported\":[\"query\",\"fragment\",\"form_post\"],\"grant_types_supported\":[\"authorization_code\",\"implicit\"],\"display_values_supported\":[\"page\",\"popup\",\"touch\",\"wap\"],\"claim_types_supported\":[\"normal\"],\"claims_parameter_supported\":true,\"claims_supported\":[],\"ui_locales_supported\":[\"en\",\"fr\",\"nl\",\"de\"],\"request_parameter_supported\":true,\"request_uri_parameter_supported\":true,\"require_request_uri_registration\":false,\"subject_types_supported\":[\"public\"]}", JSON_DECODE_ANY, NULL); ck_assert_ptr_ne(j_result, NULL); ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/oidc/.well-known/openid-configuration", NULL, NULL, NULL, NULL, 200, j_result, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/oidc/jwks", NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); json_decref(j_result); } END_TEST START_TEST(test_oidc_discovery_add_plugin) { json_t * j_param = json_pack("{sssssss{sssssssssssisisisosososososososososssssosos[{ssssso}{ssssso}]sssss{ssss}s[ssss]s[s]sosososos[s]sososssisssososisosssosos{s{s[ss]s[ss]s[ss]s[ss]s[ss]}s{s[s]s[ss]s[ss]s[ss]s[s]}s{s[s]s[s]s[s]s[sss]s[s]}s{}}sososssisosisisososososososo}}", "module", "oidc", "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", "https://glewlwyd.tld", "jwt-type", "rsa", "jwt-key-size", "256", "key", "-----BEGIN PRIVATE KEY-----\n" "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDC2kwAziXUf33m\n" "iqWp0yG6o259+nj7hpQLC4UT0Hmz0wmvreDJ/yNbSgOvsxvVdvzL2IaRZ+Gi5mo0\n" "lswWvL6IGz7PZO0kXTq9sdBnNqMOx27HddV9e/2/p0MgibJTbgywY2Sk23QYhJpq\n" "Kq/nU0xlBfSaI5ddZ2RC9ZNkVeGawUKYksTruhAVJqviHN8BoK6VowP5vcxyyOWH\n" "TK9KruDqzCIhqwRTeo0spokBkTN/LCuhVivcHAzUiJVtB4qAiTI9L/zkzhjpKz9P\n" "45aLU54rj011gG8U/6E1USh5nMnPkr+d3oLfkhfS3Zs3kJVdyFQWZpQxiTaI92Fd\n" "2wLvbS0HAgMBAAECggEAD8dTnkETSSjlzhRuI9loAtAXM3Zj86JLPLW7GgaoxEoT\n" "n7lJ2bGicFMHB2ROnbOb9vnas82gtOtJsGaBslmoaCckp/C5T1eJWTEb+i+vdpPp\n" "wZcmKZovyyRFSE4+NYlU17fEv6DRvuaGBpDcW7QgHJIl45F8QWEM+msee2KE+V4G\n" "z/9vAQ+sOlvsb4mJP1tJIBx9Lb5loVREwCRy2Ha9tnWdDNar8EYkOn8si4snPT+E\n" "3ZCy8mlcZyUkZeiS/HdtydxZfoiwrSRYamd1diQpPhWCeRteQ802a7ds0Y2YzgfF\n" "UaYjNuRQm7zA//hwbXS7ELPyNMU15N00bajlG0tUOQKBgQDnLy01l20OneW6A2cI\n" "DIDyYhy5O7uulsaEtJReUlcjEDMkin8b767q2VZHb//3ZH+ipnRYByUUyYUhdOs2\n" "DYRGGeAebnH8wpTT4FCYxUsIUpDfB7RwfdBONgaKewTJz/FPswy1Ye0b5H2c6vVi\n" "m2FZ33HQcoZ3wvFFqyGVnMzpOwKBgQDXxL95yoxUGKa8vMzcE3Cn01szh0dFq0sq\n" "cFpM+HWLVr84CItuG9H6L0KaStEEIOiJsxOVpcXfFFhsJvOGhMA4DQTwH4WuXmXp\n" "1PoVMDlV65PYqvhzwL4+QhvZO2bsrEunITXOmU7CI6kilnAN3LuP4HbqZgoX9lqP\n" "I31VYzLupQKBgGEYck9w0s/xxxtR9ILv5XRnepLdoJzaHHR991aKFKjYU/KD7JDK\n" "INfoAhGs23+HCQhCCtkx3wQVA0Ii/erM0II0ueluD5fODX3TV2ZibnoHW2sgrEsW\n" "vFcs36BnvIIaQMptc+f2QgSV+Z/fGsKYadG6Q+39O7au/HB7SHayzWkjAoGBAMgt\n" "Fzslp9TpXd9iBWjzfCOnGUiP65Z+GWkQ/SXFqD+SRir0+m43zzGdoNvGJ23+Hd6K\n" "TdQbDJ0uoe4MoQeepzoZEgi4JeykVUZ/uVfo+nh06yArVf8FxTm7WVzLGGzgV/uA\n" "+wtl/cRtEyAsk1649yW/KHPEIP8kJdYAJeoO8xSlAoGAERMrkFR7KGYZG1eFNRdV\n" "mJMq+Ibxyw8ks/CbiI+n3yUyk1U8962ol2Q0T4qjBmb26L5rrhNQhneM4e8mo9FX\n" "LlQapYkPvkdrqW0Bp72A/UNAvcGTmN7z5OCJGMUutx2hmEAlrYmpLKS8pM/p9zpK\n" "tEOtzsP5GMDYVlEp1jYSjzQ=\n" "-----END PRIVATE KEY-----", "cert", "-----BEGIN PUBLIC KEY-----\n" "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwtpMAM4l1H995oqlqdMh\n" "uqNuffp4+4aUCwuFE9B5s9MJr63gyf8jW0oDr7Mb1Xb8y9iGkWfhouZqNJbMFry+\n" "iBs+z2TtJF06vbHQZzajDsdux3XVfXv9v6dDIImyU24MsGNkpNt0GISaaiqv51NM\n" "ZQX0miOXXWdkQvWTZFXhmsFCmJLE67oQFSar4hzfAaCulaMD+b3Mcsjlh0yvSq7g\n" "6swiIasEU3qNLKaJAZEzfywroVYr3BwM1IiVbQeKgIkyPS/85M4Y6Ss/T+OWi1Oe\n" "K49NdYBvFP+hNVEoeZzJz5K/nd6C35IX0t2bN5CVXchUFmaUMYk2iPdhXdsC720t\n" "BwIDAQAB\n" "-----END PUBLIC KEY-----", "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_false(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-none-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-device-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "client-pubkey-parameter", "pubkey", "subject-type", "pairwise", "encrypt-out-token-allow", json_true(), "request-parameter-allow-encrypted", json_true(), "claims", "name", "claim1", "user-property", "claim1", "mandatory", json_true(), "name", "claim2", "user-property", "claim2", "on-demand", json_true(), "name-claim", "mandatory", "email-claim", "on-demand", "address-claim", "type", "on-demand", "formatted", "formatted-property", "allowed-scope", "openid", "g_profile", "scope1", "scope2", "jwks-x5c", "-----BEGIN PUBLIC KEY-----\n" "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwtpMAM4l1H995oqlqdMh\n" "uqNuffp4+4aUCwuFE9B5s9MJr63gyf8jW0oDr7Mb1Xb8y9iGkWfhouZqNJbMFry+\n" "iBs+z2TtJF06vbHQZzajDsdux3XVfXv9v6dDIImyU24MsGNkpNt0GISaaiqv51NM\n" "ZQX0miOXXWdkQvWTZFXhmsFCmJLE67oQFSar4hzfAaCulaMD+b3Mcsjlh0yvSq7g\n" "6swiIasEU3qNLKaJAZEzfywroVYr3BwM1IiVbQeKgIkyPS/85M4Y6Ss/T+OWi1Oe\n" "K49NdYBvFP+hNVEoeZzJz5K/nd6C35IX0t2bN5CVXchUFmaUMYk2iPdhXdsC720t\n" "BwIDAQAB\n" "-----END PUBLIC KEY-----", "pkce-allowed", json_true(), "pkce-method-plain-allowed", json_true(), "introspection-revocation-allowed", json_true(), "introspection-revocation-allow-target-client", json_true(), "introspection-revocation-auth-scope", INTROSPECT_SCOPE, "register-client-allowed", json_true(), "session-management-allowed", json_true(), "session-cookie-name", "GLEWLWYD2_OIDC_SID", "session-cookie-expiration", 2419200, "client-cert-source", "TLS", "client-cert-use-endpoint-aliases", json_true(), "oauth-dpop-allowed", json_true(), "oauth-dpop-iat-duration", 60, "oauth-rar-allowed", json_true(), "rar-types-client-property", "authorization_data_types", "rar-allow-auth-unsigned", json_true(), "rar-allow-auth-unencrypted", json_true(), "rar-types", RAR1, "scopes", SCOPE_1, SCOPE_2, "locations", "https://"RAR1"-1.resource.tld", "https://"RAR1"-2.resource.tld", "actions", "action1-"RAR1, "action2-"RAR1, "datatypes", "type1-"RAR1, "type2-"RAR1, "enriched", ENRICHED1, ENRICHED2, RAR2, "scopes", SCOPE_1, "locations", "https://"RAR2"-1.resource.tld", "https://"RAR2"-2.resource.tld", "actions", "action1-"RAR2, "action2-"RAR2, "datatypes", "type1-"RAR2, "type2-"RAR2, "enriched", ENRICHED1, RAR3, "scopes", "g_admin", "locations", "https://"RAR3".resource.tld", "actions", "action-"RAR3, "datatypes", "type1-"RAR3, "type2-"RAR3, "type3-"RAR3, "enriched", ENRICHED2, RAR4, "oauth-par-allowed", json_true(), "oauth-par-required", json_true(), "oauth-par-request_uri-prefix", PLUGIN_PAR_PREFIX, "oauth-par-duration", PLUGIN_PAR_DURATION, "oauth-ciba-allowed", json_true(), "oauth-ciba-default-expiry", PLUGIN_CIBA_DEFAULT_EXPIRATION, "oauth-ciba-maximum-expiry", PLUGIN_CIBA_MAXIMUM_EXPIRATION, "oauth-ciba-mode-poll-allowed", json_true(), "oauth-ciba-mode-ping-allowed", json_true(), "oauth-ciba-mode-push-allowed", json_true(), "oauth-ciba-allow-https-non-secure", json_true(), "oauth-ciba-user-code-allowed", json_false(), "oauth-ciba-email-allowed", json_false(), "oauth-fapi-allow-jarm", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_discovery_new_plugin_test) { struct _u_request req; struct _u_response resp; json_t * j_discovery = json_loads("{\"issuer\": \"https://glewlwyd.tld\", \"authorization_endpoint\": \"http://localhost:4593/api/oidc_claims/auth\", \"token_endpoint\": \"http://localhost:4593/api/oidc_claims/token\", \"userinfo_endpoint\": \"http://localhost:4593/api/oidc_claims/userinfo\", \"jwks_uri\": \"http://localhost:4593/api/oidc_claims/jwks\", \"token_endpoint_auth_methods_supported\": [\"client_secret_basic\", \"client_secret_post\", \"client_secret_jwt\", \"private_key_jwt\", \"tls_client_auth\"], \"id_token_signing_alg_values_supported\": [], \"access_token_encryption_enc_values_supported\": [], \"access_token_encryption_alg_values_supported\": [], \"access_token_signing_alg_values_supported\": [], \"userinfo_signing_alg_values_supported\": [], \"userinfo_encryption_alg_values_supported\": [], \"userinfo_encryption_enc_values_supported\": [], \"request_object_signing_alg_values_supported\": [], \"request_object_encryption_alg_values_supported\": [], \"request_object_encryption_enc_values_supported\": [], \"token_endpoint_auth_signing_alg_values_supported\": [], \"dpop_signing_alg_values_supported\": [], \"id_token_encryption_alg_values_supported\": [], \"id_token_encryption_enc_values_supported\": [], \"scopes_supported\": [\"openid\", \"g_profile\", \"scope1\", \"scope2\"], \"response_types_supported\": [\"code\", \"id_token\", \"token id_token\", \"code id_token\", \"code token id_token\", \"none\", \"refresh_token\"], \"response_modes_supported\": [\"query\", \"fragment\", \"form_post\",\"query.jwt\", \"fragment.jwt\", \"form_post.jwt\", \"jwt\"], \"grant_types_supported\": [\"authorization_code\", \"implicit\", \"urn:ietf:params:oauth:grant-type:device_code\", \"urn:openid:params:grant-type:ciba\"], \"display_values_supported\": [\"page\", \"popup\", \"touch\", \"wap\"], \"claim_types_supported\": [\"normal\"], \"claims_parameter_supported\": true, \"claims_supported\": [\"claim1\", \"claim2\", \"name\", \"email\", \"address\"], \"ui_locales_supported\": [\"en\", \"fr\", \"nl\", \"de\"], \"request_parameter_supported\": true, \"request_uri_parameter_supported\": true, \"require_request_uri_registration\": false, \"subject_types_supported\": [\"pairwise\"], \"code_challenge_methods_supported\": [\"S256\", \"plain\"], \"revocation_endpoint\": \"http://localhost:4593/api/oidc_claims/revoke\", \"introspection_endpoint\": \"http://localhost:4593/api/oidc_claims/introspect\", \"revocation_endpoint_auth_methods_supported\": [\"client_secret_basic\", \"bearer\"], \"introspection_endpoint_auth_methods_supported\": [\"client_secret_basic\", \"bearer\"], \"introspection_signing_alg_values_supported\": [], \"introspection_encryption_alg_values_supported\": [], \"introspection_encryption_enc_values_supported\": [], \"registration_endpoint\": \"http://localhost:4593/api/oidc_claims/register\", \"end_session_endpoint\": \"http://localhost:4593/api/oidc_claims/end_session\", \"check_session_iframe\": \"http://localhost:4593/api/oidc_claims/check_session_iframe\", \"device_authorization_endpoint\": \"http://localhost:4593/api/oidc_claims/device_authorization\", \"mtls_endpoint_aliases\": {\"token_endpoint\": \"http://localhost:4593/api/oidc_claims/mtls/token\", \"device_authorization_endpoint\": \"http://localhost:4593/api/oidc_claims/mtls/device_authorization\", \"revocation_endpoint\": \"http://localhost:4593/api/oidc_claims/mtls/revoke\", \"introspection_endpoint\": \"http://localhost:4593/api/oidc_claims/mtls/introspect\", \"pushed_authorization_request_endpoint\": \"http://localhost:4593/api/oidc_claims/mtls/par\", \"backchannel_authentication_endpoint\": \"http://localhost:4593/api/oidc_claims/mtls/ciba\"}, \"authorization_details_supported\": true, \"authorization_data_types_supported\": [\""RAR1"\",\""RAR2"\",\""RAR3"\",\""RAR4"\"], \"pushed_authorization_request_endpoint\": \"http://localhost:4593/api/oidc_claims/par\", \"require_pushed_authorization_requests\": true, \"backchannel_token_delivery_modes_supported\": [\"poll\",\"ping\",\"push\"], \"backchannel_authentication_endpoint\": \"http://localhost:4593/api/oidc_claims/ciba\", \"backchannel_authentication_request_signing_alg_values_supported\": [], \"backchannel_authentication_request_encryption_alg_values_supported\": [], \"backchannel_authentication_request_encryption_enc_values_supported\": [], \"authorization_signing_alg_values_supported\": [], \"authorization_encryption_alg_values_supported\": [], \"authorization_encryption_enc_values_supported\": [], \"backchannel_user_code_parameter_supported\": false}", JSON_DECODE_ANY, NULL), * j_result; ck_assert_ptr_ne(j_discovery, NULL); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/.well-known/openid-configuration", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_result = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "id_token_signing_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "access_token_encryption_enc_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "access_token_encryption_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "access_token_signing_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "userinfo_signing_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "userinfo_encryption_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "userinfo_encryption_enc_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "request_object_signing_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "request_object_encryption_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "token_endpoint_auth_signing_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "dpop_signing_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "id_token_encryption_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "id_token_encryption_enc_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "introspection_signing_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "introspection_encryption_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "introspection_encryption_enc_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "request_object_encryption_enc_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "backchannel_authentication_request_signing_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "backchannel_authentication_request_encryption_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "backchannel_authentication_request_encryption_enc_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "authorization_signing_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "authorization_encryption_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "authorization_encryption_enc_values_supported"))); ck_assert_int_eq(1, json_equal(j_result, j_discovery)); json_decref(j_result); json_decref(j_discovery); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/jwks", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_result = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_int_gt(json_array_size(json_object_get(j_result, "keys")), 0); json_decref(j_result); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_discovery_delete_plugin) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc discovery"); tc_core = tcase_create("test_oidc_discovery"); tcase_add_test(tc_core, test_oidc_discovery_default_test); tcase_add_test(tc_core, test_oidc_discovery_add_plugin); tcase_add_test(tc_core, test_oidc_discovery_new_plugin_test); tcase_add_test(tc_core, test_oidc_discovery_delete_plugin); tcase_set_timeout(tc_core, 90); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } ulfius_clean_request(&admin_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_dpop.c000066400000000000000000002467141415646314000204010ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "user1" #define USER_PASSWORD "password" #define PLUGIN_NAME "oidc_dpop" #define SCOPE_LIST "g_profile openid" #define SCOPE_1 "g_profile" #define SCOPE_2 "openid" #define CLIENT "client1_id" #define RESPONSE_TYPE "code" #define CLIENT_ID "client_device" #define CLIENT_NAME "client for device" #define CLIENT_SECRET "very-secret" const char jwk_pubkey_sign_str[] = "{\"kty\":\"RSA\",\"n\":\"ANgV1GxZbGBMIqqX5QsNrQQnPLk8UpkqH_60EuaHsI8YnUkPmPVXJ_4z_ziqZizvvjp_RhhXX2DnHEQuYwI-SZaBlK1VJiiWH9"\ "EXrUeazcpEryFUR0I5iBROcgRJfHSvRvC7D83-xg9xC-NGVvIQ2llduYzmaK8rfuiHWlGqow3O2m5os9NTortdQf7BeTniStDokFvZy-I4i24UFkemoNPWZ9MC"\ "N0WTea8n_TQmq9sVHGQtLIFqfblLxbSz_7m4g7_o3WfqlwXkVmCIu1wdzAjZV5BspBGrL0ed5Whpk9-bX69nUDvpcMAaPhuRwZ43e9koVRbVwXCNkne98VAs0_"\ "U\",\"e\":\"AQAB\",\"kid\":\"3\"}"; const char jwk_privkey_sign_str[] = "{\"kty\":\"RSA\",\"n\":\"ANgV1GxZbGBMIqqX5QsNrQQnPLk8UpkqH_60EuaHsI8YnUkPmPVXJ_4z_ziqZizvvjp_RhhXX2DnHEQuYwI-SZaBlK1VJiiWH"\ "9EXrUeazcpEryFUR0I5iBROcgRJfHSvRvC7D83-xg9xC-NGVvIQ2llduYzmaK8rfuiHWlGqow3O2m5os9NTortdQf7BeTniStDokFvZy-I4i24UFkemoNPWZ9M"\ "CN0WTea8n_TQmq9sVHGQtLIFqfblLxbSz_7m4g7_o3WfqlwXkVmCIu1wdzAjZV5BspBGrL0ed5Whpk9-bX69nUDvpcMAaPhuRwZ43e9koVRbVwXCNkne98VAs0"\ "_U\",\"e\":\"AQAB\",\"d\":\"AKOVsyDreb5VJRFcuIrrqYWxZqkc37MQTvR1wrE_HAzYp4n-AuAJQT-Sga6WYY-3V53VaG1ZB93GWIHNVCsImJEWPEYUZj"\ "TnoeKbOBUzPoPYB3UF5oReJYSp9msEbvGvF9d65fYe4DYkcMl4IK5Uz9hDugrPC4VBOmwyu8-DjLkP8OH-N2-KhJvX_kLKgivfzD3KOp6wryLnKuZYn8N4E6rC"\ "iNSfKMgoM60bSHRNi0QHYB2jwqMU5T5EzdpD3Tu_ow6a-sXrW6SG1dtbuStck9hFcQ-QtRCeWoM5pFN8cKOsWBZd1unq-X3gMlCjdXUBUW7BYP44lpYsg1v9l_"\ "Ww64E\",\"p\":\"ANmlFUVM-836aC-wK-DekE3s3gl7GZ-9Qca8iKnaIeMszgyaLYkkbYNPpjjsiQHc37IG3axCaywK40PZqODzovL5PnUpwfNrnlMaI042rN"\ "af8q1L4kvaBTkbO9Wbj0sTLMPt1frLQKBRsNDsYamRcL1SwvTC4aI7cgZBrNIBdPiR\",\"q\":\"AP4qYxRNGaI3aeZh5hgKPSGW82X8Ai2MzIKjzSDYmKGcD"\ "9HPRV0dAUmDCvqyjwCD6tL9iMtZKPz7VK66-KvV1n91WLMDtRzWs_eFFyDY7BYw47o6IQoZ2RxBT3-7WLhlFflaEner8k23zpGOjZbyzt0SIWRAYR0zlb7LrS_"\ "X4fcl\",\"qi\":\"fnlvhYXAn6V0X6gmlwooZUWo9bR7ObChNhrUzMVDOReUVOrzOhlzGhBW1TEFBBr8k44ZWBCTeVEQh--LFHwVvCgEjDBxfjUPUMkeyKZzL"\ "hpIUB_cFBAgI7Fyy0yuPpY0mS1PfMt5Y4b6g_JvdBWZZ8VhTcCVG7qDqoH_IJMXPNg\",\"dp\":\"EAsiQUSGf02JJpLG-UGOw5_FUk-XuPW7honZTSP-QX_J"\ "BJbM6oIb7IUPjLyq8M82Uio9ZvhSbCG1VQgTcdmj1mNXHk3gtS_msNuJZLeVEBEkU2_3k33TyrzeMUXRT0hvkVXT4zPeZLMA5LW4EUbeV6ZlJqPC_DGDm0B2G9"\ "jtpXE\",\"dq\":\"AMTictPUEcpOILO9HG985vPxKeTTfaBpVDbSymDqR_nQmZSOeg3yHQAkCco_rXTZu3rruR7El3K5AlVEMsNxp3IepbIuagrH6qsPpuXkA"\ "6YBAzdMNjHL6hnwIbQxnT1h2M7KzklzogRAIT0x706CEmq_06wEDvZ-8j3VKvhHxBwd\",\"kid\":\"3\"}"; const char jwk_pubkey_sign_str_2[] = "{\"kty\":\"RSA\",\"n\":\"ALZfFvsvNegnsnjhAydGJ17C9Ny5-M1UqRbcgaPUFRqvfn2P2Yz5rjGTnfFKe9E6xANSNzKRdb5ltNeeJT0inSi2meACAXE6"\ "8Ud7d2JvlkxQPvz1tJyCKvQFktGwlqwW5F8r_spfT1qJsf_DpZWjsXFrkY7sdrHJdoeQZDIYx0fsGdzlA0uGoGimPlCCExYLcqsjjh3Dqv8V1xJ4jm5S8198v3"\ "FJXXm5BN_GWAmExuDOq6ul8MqcECXBQ4LavxFlB5kGgPsxvFjTK72_2YdNDQPkKmV56vShm50BaEqzXU0A2MYeTyabX7d4goI_B7IeX5tGqMjBrlX6hNS-VfqG"\ "MVM\",\"e\":\"AQAB\",\"kid\":\"4\"}"; const char jwk_privkey_sign_str_2[] = "{\"kty\":\"RSA\",\"n\":\"ALZfFvsvNegnsnjhAydGJ17C9Ny5-M1UqRbcgaPUFRqvfn2P2Yz5rjGTnfFKe9E6xANSNzKRdb5ltNeeJT0inSi2meACAXE"\ "68Ud7d2JvlkxQPvz1tJyCKvQFktGwlqwW5F8r_spfT1qJsf_DpZWjsXFrkY7sdrHJdoeQZDIYx0fsGdzlA0uGoGimPlCCExYLcqsjjh3Dqv8V1xJ4jm5S8198v"\ "3FJXXm5BN_GWAmExuDOq6ul8MqcECXBQ4LavxFlB5kGgPsxvFjTK72_2YdNDQPkKmV56vShm50BaEqzXU0A2MYeTyabX7d4goI_B7IeX5tGqMjBrlX6hNS-Vfq"\ "GMVM\",\"e\":\"AQAB\",\"d\":\"HyIUlkT0-vDr8t7W3vmG9xJpItVMuCDfzNtP9lvaTnfvLBhGl154clY0_GAuywUxOS_r5GIYq6xJNxX0XX9vPOgPVMKC"\ "5IWfcwiM1O0fx19boWuArcc69fWNnuZ6kl5GFkk4cevbbCVdkcAgoG8Vd7tZWgDcMnWmGnZ35GV-f7Rw3kQTxge4V7T5-I5preMxRAV2YZ1zafIDpYXaOXWL9b"\ "X0vAApb5Vie1btPiOj7lZ_J0ChkkdIW-ZTiQZ0sTRo6c6qLVNHQLKAJ_I6QLMfiHAT8xFir3fgiUxNwxxifYOts_akh3-wJEs4r4G92hohmIiIKp2TABDc3Wrm"\ "FDafYQ\",\"p\":\"ANVUDxAxNuR8Ds5W_3xpGgOKzypYGfimDrU_kRzXsdXOz4EkSYXG2SR7V854vvcgJDzFIihmaI_65LN_pk_6ZE1ddd8Qrud9nMtd5n9ne"\ "EkOGTCsTO-TM4gLjyZQ3FCo_oCsJ6MiQRlOTw5pf1yH69q3QUd5e_5c75MYr4G0fPwn\",\"q\":\"ANrZ0K-ZdBt9uP1Bt0G7YmW3j41wFt1JnmOkX86YX6Q3"\ "wrI4YqiRfolVexAtQ1a1iRVY7ZGXhy_q0rDLPIpfYAy9LSS1NZHb_vu7C-p8hCALxKa6bTGLeT4Z5LABHPBoMVCyKhlANMHhcUeNY76p4JwT1zwT7FIHamKgVK"\ "zv_CD1\",\"qi\":\"GUmL7fbgnNa2IQ13i3Xi3A5dXzgqBeVHb2HjAzCJhNCcg8jslpU4rmMoGAq_WagT-U3_NuUVnGWnHTPWHjFe9MkwxPpSIISbMRorOhsZ"\ "Mrlzg4vdyZ2Kt_zs3yNTb_KOYx6YxU3_93IdFU2XjlnUf4mDThVoTSRfNh-NMJgwLUw\",\"dp\":\"ALBi7IGK78RD_0oFDQIlNOkw4NI2PmMliou6n5Wlktk"\ "iQtiY1GHUZL6Rbay-kcdrwAqvROr6ogJKhMcWCMGgW0bMvCVQeg3WAsr0PR2ixAZDrfhcvtBoefdG93nK6h-XW7ewoKV2MTVnVl6oRDKSACW72DHs9OUAmuaZR"\ "qSMQ7uJ\",\"dq\":\"AIgWpDddtB6YOl157Ov6CwD3eVPZXM50RgLuJwmAJREn_3D1sRvjhYz-08zGaLZVoo3cw7YiRNVeL2_yoY3mKwMg7B6EdHBkHhYJRSq"\ "mDT8kMj__c4E4mscsMNHlj0pLcEce0yDqlSPu_ZMh7-GTH3HOwKvCM9T6eYQk8SKtBNq1\",\"kid\":\"4\"}"; struct _u_request admin_req; struct _u_request user_req; START_TEST(test_oidc_dpop_add_plugin) { json_t * j_param = json_pack("{sssssss{sssssssssisisisosososososososososi}}", "module", "oidc", "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", "https://glewlwyd.tld", "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_NAME, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-device-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "oauth-dpop-allowed", json_true(), "oauth-dpop-iat-duration", 60); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_dpop_get_at_with_jkt_invalid) { struct _u_response resp; struct _u_request req; char * code, jti[17], * dpop_token; json_t * j_dpop_pub; jwt_t * jwt_dpop; jwk_t * jwk_dpop_pub; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oidc.html%%3fparam%%3dclient1_cb1&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code=")); if (o_strchr(code, '&')) { *(o_strchr(code, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(r_jwk_init(&jwk_dpop_pub), RHN_OK); ck_assert_int_eq(r_jwk_import_from_json_str(jwk_dpop_pub, jwk_pubkey_sign_str), RHN_OK); ck_assert_ptr_ne(NULL, j_dpop_pub = r_jwk_export_to_json_t(jwk_dpop_pub)); ck_assert_int_eq(r_jwt_init(&jwt_dpop), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_dpop, jwk_privkey_sign_str, NULL), RHN_OK); srand(time(NULL)+1); snprintf(jti, 16, "%u", (uint)rand()); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_dpop, R_JWA_ALG_RS256), RHN_OK); // No jti ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT, U_OPT_POST_BODY_PARAMETER, "redirect_uri", "../../test-oidc.html?param=client1_cb1", U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); o_free(dpop_token); ulfius_clean_response(&resp); // jti non string ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "jti", 42), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); o_free(dpop_token); ulfius_clean_response(&resp); // Invalid htm ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "GET"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); o_free(dpop_token); ulfius_clean_response(&resp); // No htm ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", NULL), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); o_free(dpop_token); ulfius_clean_response(&resp); // Non string htm ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "htm", 42), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); o_free(dpop_token); ulfius_clean_response(&resp); // Invalid htu ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/error"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); o_free(dpop_token); ulfius_clean_response(&resp); // Non string htu ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "htu", 42), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); o_free(dpop_token); ulfius_clean_response(&resp); // No htu ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", NULL), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); o_free(dpop_token); ulfius_clean_response(&resp); // Non int iat ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "iat", "error"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); o_free(dpop_token); ulfius_clean_response(&resp); // iat too big ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)+30), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); o_free(dpop_token); ulfius_clean_response(&resp); // iat too low ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)-300), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); o_free(dpop_token); ulfius_clean_response(&resp); // No iat ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "iat", NULL), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); o_free(dpop_token); ulfius_clean_response(&resp); // Non string typ ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_header_int_value(jwt_dpop, "typ", 42), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); o_free(dpop_token); ulfius_clean_response(&resp); // Invalid typ ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "error"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); o_free(dpop_token); ulfius_clean_response(&resp); // No typ ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", NULL), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); o_free(dpop_token); ulfius_clean_response(&resp); // Invalid jwk ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "jwk", "error"), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); o_free(dpop_token); ulfius_clean_response(&resp); // Invalid jwk ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_int_value(jwt_dpop, "jwk", 42), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); o_free(dpop_token); ulfius_clean_response(&resp); ulfius_clean_request(&req); json_decref(j_dpop_pub); o_free(code); r_jwt_free(jwt_dpop); r_jwk_free(jwk_dpop_pub); } END_TEST START_TEST(test_oidc_dpop_get_at_with_jkt_jti_replay) { struct _u_response resp; struct _u_request req; char * code, jti[17], * dpop_token; json_t * j_result, * j_dpop_pub, * j_cnf; jwt_t * jwt_dpop, * jwt_at; jwk_t * jwk_dpop_pub; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oidc.html%%3fparam%%3dclient1_cb1&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code=")); if (o_strchr(code, '&')) { *(o_strchr(code, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(r_jwk_init(&jwk_dpop_pub), RHN_OK); ck_assert_int_eq(r_jwk_import_from_json_str(jwk_dpop_pub, jwk_pubkey_sign_str), RHN_OK); ck_assert_ptr_ne(NULL, j_dpop_pub = r_jwk_export_to_json_t(jwk_dpop_pub)); ck_assert_int_eq(r_jwt_init(&jwt_dpop), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_dpop, jwk_privkey_sign_str, NULL), RHN_OK); srand(time(NULL)+2); snprintf(jti, 16, "%u", (uint)rand()); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_dpop, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT, U_OPT_POST_BODY_PARAMETER, "redirect_uri", "../../test-oidc.html?param=client1_cb1", U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_result = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_eq(r_jwt_init(&jwt_at), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt_at, json_string_value(json_object_get(j_result, "access_token")), 0), RHN_OK); ck_assert_ptr_ne(NULL, j_cnf = r_jwt_get_claim_json_t_value(jwt_at, "cnf")); ck_assert_int_gt(json_string_length(json_object_get(j_cnf, "jkt")), 0); json_decref(j_result); json_decref(j_dpop_pub); json_decref(j_cnf); ulfius_clean_response(&resp); ulfius_clean_request(&req); o_free(code); r_jwt_free(jwt_dpop); r_jwt_free(jwt_at); r_jwk_free(jwk_dpop_pub); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code=")); if (o_strchr(code, '&')) { *(o_strchr(code, '&')) = '\0'; } ulfius_clean_response(&resp); ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT, U_OPT_POST_BODY_PARAMETER, "redirect_uri", "../../test-oidc.html?param=client1_cb1", U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); o_free(dpop_token); ulfius_clean_response(&resp); ulfius_clean_request(&req); o_free(code); } END_TEST START_TEST(test_oidc_dpop_get_at_with_jkt) { struct _u_response resp; struct _u_request req; char * code, jti[17], * dpop_token; json_t * j_result, * j_dpop_pub, * j_cnf; jwt_t * jwt_dpop, * jwt_at; jwk_t * jwk_dpop_pub; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oidc.html%%3fparam%%3dclient1_cb1&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code=")); if (o_strchr(code, '&')) { *(o_strchr(code, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(r_jwk_init(&jwk_dpop_pub), RHN_OK); ck_assert_int_eq(r_jwk_import_from_json_str(jwk_dpop_pub, jwk_pubkey_sign_str), RHN_OK); ck_assert_ptr_ne(NULL, j_dpop_pub = r_jwk_export_to_json_t(jwk_dpop_pub)); ck_assert_int_eq(r_jwt_init(&jwt_dpop), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_dpop, jwk_privkey_sign_str, NULL), RHN_OK); srand(time(NULL)+3); snprintf(jti, 16, "%u", (uint)rand()); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_dpop, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT, U_OPT_POST_BODY_PARAMETER, "redirect_uri", "../../test-oidc.html?param=client1_cb1", U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_result = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_eq(r_jwt_init(&jwt_at), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt_at, json_string_value(json_object_get(j_result, "access_token")), 0), RHN_OK); ck_assert_ptr_ne(NULL, j_cnf = r_jwt_get_claim_json_t_value(jwt_at, "cnf")); ck_assert_int_gt(json_string_length(json_object_get(j_cnf, "jkt")), 0); json_decref(j_result); json_decref(j_dpop_pub); json_decref(j_cnf); ulfius_clean_response(&resp); ulfius_clean_request(&req); o_free(code); o_free(dpop_token); r_jwt_free(jwt_dpop); r_jwt_free(jwt_at); r_jwk_free(jwk_dpop_pub); } END_TEST START_TEST(test_oidc_dpop_userinfo_with_jkt_invalid) { struct _u_response resp; struct _u_request req; char * code, jti[17], * dpop_token, * bearer; json_t * j_result, * j_dpop_pub; jwt_t * jwt_dpop; jwk_t * jwk_dpop_pub; unsigned char ath[32] = {0}, ath_enc[64] = {0}; size_t ath_len = 32, ath_enc_len = 64; gnutls_datum_t hash_data; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oidc.html%%3fparam%%3dclient1_cb1&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code=")); if (o_strchr(code, '&')) { *(o_strchr(code, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(r_jwk_init(&jwk_dpop_pub), RHN_OK); ck_assert_int_eq(r_jwk_import_from_json_str(jwk_dpop_pub, jwk_pubkey_sign_str), RHN_OK); ck_assert_ptr_ne(NULL, j_dpop_pub = r_jwk_export_to_json_t(jwk_dpop_pub)); ck_assert_int_eq(r_jwt_init(&jwt_dpop), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_dpop, jwk_privkey_sign_str, NULL), RHN_OK); srand(time(NULL)+4); snprintf(jti, 16, "%u", (uint)rand()); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_dpop, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT, U_OPT_POST_BODY_PARAMETER, "redirect_uri", "../../test-oidc.html?param=client1_cb1", U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_result = ulfius_get_json_body_response(&resp, NULL)); ulfius_clean_response(&resp); ulfius_clean_request(&req); o_free(dpop_token); hash_data.data = (unsigned char*)json_string_value(json_object_get(j_result, "access_token")); hash_data.size = json_string_length(json_object_get(j_result, "access_token")); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &hash_data, ath, &ath_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64url_encode(ath, ath_len, ath_enc, &ath_enc_len), 1); ck_assert_ptr_ne(NULL, bearer = msprintf("Bearer %s", json_string_value(json_object_get(j_result, "access_token")))); ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/userinfo", U_OPT_HEADER_PARAMETER, "Authorization", bearer, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "ath", (const char *)ath_enc), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "GET"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/userinfo"), RHN_OK); ulfius_init_response(&resp); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/userinfo", U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_HEADER_PARAMETER, "Authorization", bearer, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); o_free(dpop_token); ulfius_init_response(&resp); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "htm", 42), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); o_free(dpop_token); ulfius_init_response(&resp); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", NULL), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); o_free(dpop_token); ulfius_init_response(&resp); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "GET"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", "error"), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); o_free(dpop_token); ulfius_init_response(&resp); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "htu", 42), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); o_free(dpop_token); ulfius_init_response(&resp); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", NULL), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); o_free(dpop_token); ulfius_init_response(&resp); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/userinfo"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "iat", "error"), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); o_free(dpop_token); ulfius_init_response(&resp); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)+30), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); o_free(dpop_token); ulfius_init_response(&resp); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)-600), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); o_free(dpop_token); ulfius_init_response(&resp); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "iat", NULL), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); o_free(dpop_token); ulfius_init_response(&resp); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "typ", "error"), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); o_free(dpop_token); ulfius_init_response(&resp); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "typ", 42), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); o_free(dpop_token); ulfius_init_response(&resp); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "typ", NULL), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); o_free(dpop_token); ulfius_init_response(&resp); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jwk", "error"), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); o_free(dpop_token); ulfius_init_response(&resp); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "jwk", 42), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); o_free(dpop_token); ulfius_init_response(&resp); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jwk", NULL), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); o_free(dpop_token); ulfius_init_response(&resp); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "ath", "error"), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); o_free(dpop_token); ulfius_init_response(&resp); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "ath", NULL), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); o_free(dpop_token); r_jwk_free(jwk_dpop_pub); json_decref(j_dpop_pub); ck_assert_int_eq(r_jwk_init(&jwk_dpop_pub), RHN_OK); ck_assert_int_eq(r_jwk_import_from_json_str(jwk_dpop_pub, jwk_pubkey_sign_str_2), RHN_OK); ck_assert_ptr_ne(NULL, j_dpop_pub = r_jwk_export_to_json_t(jwk_dpop_pub)); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ulfius_init_response(&resp); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); o_free(dpop_token); r_jwk_free(jwk_dpop_pub); r_jwt_free(jwt_dpop); ck_assert_int_eq(r_jwk_init(&jwk_dpop_pub), RHN_OK); ck_assert_int_eq(r_jwk_import_from_json_str(jwk_dpop_pub, jwk_pubkey_sign_str_2), RHN_OK); json_decref(j_dpop_pub); ck_assert_ptr_ne(NULL, j_dpop_pub = r_jwk_export_to_json_t(jwk_dpop_pub)); ck_assert_int_eq(r_jwt_init(&jwt_dpop), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_dpop, jwk_privkey_sign_str_2, NULL), RHN_OK); srand(time(NULL)+5); snprintf(jti, 16, "%u", (uint)rand()); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_dpop, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "ath", (const char *)ath_enc), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ulfius_init_response(&resp); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); o_free(dpop_token); r_jwk_free(jwk_dpop_pub); json_decref(j_result); json_decref(j_dpop_pub); ulfius_clean_request(&req); o_free(code); o_free(bearer); r_jwt_free(jwt_dpop); } END_TEST START_TEST(test_oidc_dpop_userinfo_with_jkt) { struct _u_response resp; struct _u_request req; char * code, jti[17], * dpop_token, * bearer; json_t * j_result, * j_dpop_pub; jwt_t * jwt_dpop; jwk_t * jwk_dpop_pub; unsigned char ath[32] = {0}, ath_enc[64] = {0}; size_t ath_len = 32, ath_enc_len = 64; gnutls_datum_t hash_data; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oidc.html%%3fparam%%3dclient1_cb1&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code=")); if (o_strchr(code, '&')) { *(o_strchr(code, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(r_jwk_init(&jwk_dpop_pub), RHN_OK); ck_assert_int_eq(r_jwk_import_from_json_str(jwk_dpop_pub, jwk_pubkey_sign_str), RHN_OK); ck_assert_ptr_ne(NULL, j_dpop_pub = r_jwk_export_to_json_t(jwk_dpop_pub)); ck_assert_int_eq(r_jwt_init(&jwt_dpop), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_dpop, jwk_privkey_sign_str, NULL), RHN_OK); srand(time(NULL)+5); snprintf(jti, 16, "%u", (uint)rand()); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_dpop, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT, U_OPT_POST_BODY_PARAMETER, "redirect_uri", "../../test-oidc.html?param=client1_cb1", U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_result = ulfius_get_json_body_response(&resp, NULL)); ulfius_clean_response(&resp); ulfius_clean_request(&req); o_free(dpop_token); hash_data.data = (unsigned char*)json_string_value(json_object_get(j_result, "access_token")); hash_data.size = json_string_length(json_object_get(j_result, "access_token")); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &hash_data, ath, &ath_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64url_encode(ath, ath_len, ath_enc, &ath_enc_len), 1); ulfius_init_request(&req); ulfius_init_response(&resp); snprintf(jti, 16, "%u", (uint)rand()); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "GET"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/userinfo"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "ath", (const char *)ath_enc), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_ptr_ne(NULL, bearer = msprintf("Bearer %s", json_string_value(json_object_get(j_result, "access_token")))); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/userinfo", U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_HEADER_PARAMETER, "Authorization", bearer, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_result); json_decref(j_dpop_pub); ulfius_clean_response(&resp); ulfius_clean_request(&req); o_free(code); o_free(bearer); o_free(dpop_token); r_jwt_free(jwt_dpop); r_jwk_free(jwk_dpop_pub); } END_TEST START_TEST(test_oidc_dpop_userinfo_with_jkt_jti_replay) { struct _u_response resp; struct _u_request req; char * code, jti[17], * dpop_token, * bearer; json_t * j_result, * j_dpop_pub; jwt_t * jwt_dpop; jwk_t * jwk_dpop_pub; unsigned char ath[32] = {0}, ath_enc[64] = {0}; size_t ath_len = 32, ath_enc_len = 64; gnutls_datum_t hash_data; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oidc.html%%3fparam%%3dclient1_cb1&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code=")); if (o_strchr(code, '&')) { *(o_strchr(code, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(r_jwk_init(&jwk_dpop_pub), RHN_OK); ck_assert_int_eq(r_jwk_import_from_json_str(jwk_dpop_pub, jwk_pubkey_sign_str), RHN_OK); ck_assert_ptr_ne(NULL, j_dpop_pub = r_jwk_export_to_json_t(jwk_dpop_pub)); ck_assert_int_eq(r_jwt_init(&jwt_dpop), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_dpop, jwk_privkey_sign_str, NULL), RHN_OK); srand(time(NULL)+6); snprintf(jti, 16, "%u", (uint)rand()); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_dpop, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT, U_OPT_POST_BODY_PARAMETER, "redirect_uri", "../../test-oidc.html?param=client1_cb1", U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_result = ulfius_get_json_body_response(&resp, NULL)); ulfius_clean_response(&resp); ulfius_clean_request(&req); o_free(dpop_token); hash_data.data = (unsigned char*)json_string_value(json_object_get(j_result, "access_token")); hash_data.size = json_string_length(json_object_get(j_result, "access_token")); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &hash_data, ath, &ath_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64url_encode(ath, ath_len, ath_enc, &ath_enc_len), 1); ulfius_init_request(&req); ulfius_init_response(&resp); snprintf(jti, 16, "%u", (uint)rand()); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "GET"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/userinfo"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "ath", (const char *)ath_enc), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_ptr_ne(NULL, bearer = msprintf("Bearer %s", json_string_value(json_object_get(j_result, "access_token")))); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/userinfo", U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_HEADER_PARAMETER, "Authorization", bearer, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); json_decref(j_result); json_decref(j_dpop_pub); ulfius_clean_response(&resp); ulfius_clean_request(&req); o_free(code); o_free(bearer); o_free(dpop_token); r_jwt_free(jwt_dpop); r_jwk_free(jwk_dpop_pub); } END_TEST START_TEST(test_oidc_dpop_add_client_confidential_ok) { json_t * j_parameters = json_pack("{sssssssos[s]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "client_secret", CLIENT_SECRET, "confidential", json_true(), "authorization_type", "device_authorization", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_dpop_device_verification_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp, * j_grant; const char * code, * redirect_uri, * device_code; jwt_t * jwt; char jti[17], * dpop_token; json_t * j_dpop_pub, * j_cnf; jwt_t * jwt_dpop, * jwt_at; jwk_t * jwk_dpop_pub; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", SCOPE_LIST); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", ""); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(r_jwk_init(&jwk_dpop_pub), RHN_OK); ck_assert_int_eq(r_jwk_import_from_json_str(jwk_dpop_pub, jwk_pubkey_sign_str), RHN_OK); ck_assert_ptr_ne(NULL, j_dpop_pub = r_jwk_export_to_json_t(jwk_dpop_pub)); ck_assert_int_eq(r_jwt_init(&jwt_dpop), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_dpop, jwk_privkey_sign_str, NULL), RHN_OK); srand(time(NULL)+7); snprintf(jti, 16, "%u", (uint)rand()); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_dpop, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "device_code", device_code); u_map_put(req.map_header, "DPoP", dpop_token); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_int_eq(r_jwt_init(&jwt_at), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt_at, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_ptr_ne(NULL, j_cnf = r_jwt_get_claim_json_t_value(jwt_at, "cnf")); ck_assert_int_gt(json_string_length(json_object_get(j_cnf, "jkt")), 0); ck_assert_ptr_ne(json_object_get(j_resp, "refresh_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "id_token"), NULL); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_str_eq(SCOPE_LIST, r_jwt_get_claim_str_value(jwt, "aud")); r_jwt_free(jwt); json_decref(j_resp); json_decref(j_cnf); json_decref(j_dpop_pub); ulfius_clean_request(&req); ulfius_clean_response(&resp); o_free(dpop_token); r_jwt_free(jwt_dpop); r_jwk_free(jwk_dpop_pub); r_jwt_free(jwt_at); } END_TEST START_TEST(test_oidc_dpop_refresh_token_management_with_jkt) { struct _u_response resp; struct _u_request req; char * code, jti[17], * dpop_token, * bearer; json_t * j_result, * j_dpop_pub; jwt_t * jwt_dpop; jwk_t * jwk_dpop_pub; unsigned char ath[32] = {0}, ath_enc[64] = {0}; size_t ath_len = 32, ath_enc_len = 64; gnutls_datum_t hash_data; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oidc.html%%3fparam%%3dclient1_cb1&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code=")); if (o_strchr(code, '&')) { *(o_strchr(code, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(r_jwk_init(&jwk_dpop_pub), RHN_OK); ck_assert_int_eq(r_jwk_import_from_json_str(jwk_dpop_pub, jwk_pubkey_sign_str), RHN_OK); ck_assert_ptr_ne(NULL, j_dpop_pub = r_jwk_export_to_json_t(jwk_dpop_pub)); ck_assert_int_eq(r_jwt_init(&jwt_dpop), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_dpop, jwk_privkey_sign_str, NULL), RHN_OK); srand(time(NULL)+8); snprintf(jti, 16, "%u", (uint)rand()); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_dpop, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "POST"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_int_value(jwt_dpop, "iat", time(NULL)), RHN_OK); ck_assert_int_eq(r_jwt_set_header_str_value(jwt_dpop, "typ", "dpop+jwt"), RHN_OK); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_dpop, "jwk", j_dpop_pub), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT, U_OPT_POST_BODY_PARAMETER, "redirect_uri", "../../test-oidc.html?param=client1_cb1", U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_result = ulfius_get_json_body_response(&resp, NULL)); ulfius_clean_response(&resp); ulfius_clean_request(&req); o_free(dpop_token); hash_data.data = (unsigned char*)json_string_value(json_object_get(j_result, "access_token")); hash_data.size = json_string_length(json_object_get(j_result, "access_token")); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &hash_data, ath, &ath_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64url_encode(ath, ath_len, ath_enc, &ath_enc_len), 1); ulfius_init_request(&req); ulfius_init_response(&resp); snprintf(jti, 16, "%u", (uint)rand()); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "DELETE"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token/hash"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "ath", (const char *)ath_enc), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_ptr_ne(NULL, bearer = msprintf("Bearer %s", json_string_value(json_object_get(j_result, "access_token")))); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "DELETE", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token/hash", U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_HEADER_PARAMETER, "Authorization", bearer, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 404); ulfius_clean_response(&resp); ulfius_clean_request(&req); o_free(dpop_token); ulfius_init_request(&req); ulfius_init_response(&resp); snprintf(jti, 16, "%u", (uint)rand()); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "jti", jti), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htm", "GET"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "htu", SERVER_URI "/" PLUGIN_NAME "/token"), RHN_OK); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt_dpop, "ath", (const char *)ath_enc), RHN_OK); ck_assert_ptr_ne(NULL, dpop_token = r_jwt_serialize_signed(jwt_dpop, NULL, 0)); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_HEADER_PARAMETER, "DPoP", dpop_token, U_OPT_HEADER_PARAMETER, "Authorization", bearer, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ulfius_clean_request(&req); o_free(dpop_token); json_decref(j_result); json_decref(j_dpop_pub); o_free(code); r_jwt_free(jwt_dpop); r_jwk_free(jwk_dpop_pub); ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_HEADER_PARAMETER, "Authorization", bearer, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "DELETE", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token/hash", U_OPT_HEADER_PARAMETER, "Authorization", bearer, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_response(&resp); ulfius_clean_request(&req); o_free(bearer); } END_TEST START_TEST(test_oidc_dpop_delete_client) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_ID, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_dpop_delete_plugin) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc dpop"); tc_core = tcase_create("test_oidc_dpop"); tcase_add_test(tc_core, test_oidc_dpop_add_plugin); tcase_add_test(tc_core, test_oidc_dpop_get_at_with_jkt_invalid); tcase_add_test(tc_core, test_oidc_dpop_get_at_with_jkt_jti_replay); tcase_add_test(tc_core, test_oidc_dpop_get_at_with_jkt); tcase_add_test(tc_core, test_oidc_dpop_userinfo_with_jkt_invalid); tcase_add_test(tc_core, test_oidc_dpop_userinfo_with_jkt); tcase_add_test(tc_core, test_oidc_dpop_userinfo_with_jkt_jti_replay); tcase_add_test(tc_core, test_oidc_dpop_add_client_confidential_ok); tcase_add_test(tc_core, test_oidc_dpop_device_verification_valid); tcase_add_test(tc_core, test_oidc_dpop_refresh_token_management_with_jkt); tcase_add_test(tc_core, test_oidc_dpop_delete_client); tcase_add_test(tc_core, test_oidc_dpop_delete_plugin); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req; struct _u_response auth_resp, scope_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); ulfius_init_request(&user_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&scope_req); ulfius_init_response(&auth_resp); ulfius_init_response(&scope_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "User %s authenticated", USER_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(scope_req.map_header, "Cookie", cookie); u_map_put(user_req.map_header, "Cookie", cookie); o_free(cookie); scope_req.http_verb = strdup("PUT"); scope_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, &scope_resp) != U_OK || scope_resp.status != 200) { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope '%s' for %s error", CLIENT, SCOPE_LIST); do_test = 0; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope OK"); } ulfius_clean_response(&scope_resp); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication user"); do_test = 0; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); } if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, NULL) != U_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "Remove grant scope '%s' for %s error", CLIENT, SCOPE_LIST); } char * url = msprintf("%s/auth/", SERVER_URI); run_simple_test(&user_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL); o_free(url); ulfius_clean_request(&admin_req); ulfius_clean_request(&user_req); ulfius_clean_request(&scope_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_fapi.c000066400000000000000000001677041415646314000203570ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ISS "https://glewlwyd.tld" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "user1" #define USER_PASSWORD "password" #define PLUGIN_NAME "oidc_fapi" #define PLUGIN_JWT_TYPE_RSA "rsa" #define PLUGIN_JWT_KEY_SIZE "256" #define SCOPE_LIST "openid" #define CLIENT_ID "client1_id" #define CLIENT_PUBKEY_ID "client_fapi" #define CLIENT_PUBKEY_PARAM "pubkey" #define CLIENT_JWKS_PARAM "jwks" #define CLIENT_JWKS_URI_PARAM "jwks_uri" #define CLIENT_PUBKEY_NAME "client with pubkey" #define CLIENT_PUBKEY_REDIRECT "https://glewlwyd.local/" #define CLIENT_PUBKEY_REDIRECT_ESCAPED "https%3A%2F%2Fglewlwyd.local%2F" #define CLIENT_SECRET "password" #define CLIENT_REDIRECT_URI "../../test-oidc.html?param=client1_cb1" #define CLIENT_REDIRECT_URI_ENCODED "..%2F..%2Ftest-oidc.html%3Fparam%3Dclient1_cb1" #define CLIENT_SIGN_ALG "PS256" #define CLIENT_ENC_ALG "RSA-OAEP-256" #define CLIENT_ENC_ALG_INVALID "RSA1_5" #define CLIENT_ENC "A128GCM" #define RESPONSE_TYPE_CODE "code" #define RESPONSE_TYPE_CODE_ID_TOKEN "code+id_token" #define RESPONSE_TYPE_CODE_ID_TOKEN_POST "code id_token" #define STATE "Ohana means family. Family means nobody gets left behind or forgotten" #define REQUEST_MAX_EXP 120 #define KID "multiple" #define PLUGIN_CIBA_DEFAULT_EXPIRATION 600 #define PLUGIN_CIBA_MAXIMUM_EXPIRATION 1200 #define CIBA_CLIENT_NOTIFICATION_TOKEN "ZBMDEshXMWMv8KUbBSUnbRgEYpvM8LyA" const char pubkey_1_pem[] = "-----BEGIN PUBLIC KEY-----\n"\ "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxaF1egmmQ+0/AAEcv/Jd\n"\ "TCBxi7A05VvDvRgaYzOEWqJF3qv3b6ifD4E4C9+cir6tIyCOJ2WXE4TV9fCWBQjI\n"\ "omacdQjdEwMAEWYYVEz4PPpWZDEeCKKgsz5DnLNyPznMiom82vDREvSqG+yxAEbk\n"\ "W1r+MtcFRd4YSZyJYDSVJ+t2VL7Nt/Gxao/MC8rJP/QHzSbWjwruSwW3JM+75wds\n"\ "JUbHhyIZ5lQ2u06d68ZA9Q4T60i+zE49yoLd8+PIjcrhM3kYIJkcTKyjWqZEV+we\n"\ "16eroS8PX/YN5tw4Od+fKb6I0XILhiyCqKlOg3FHdY8iAK75atI6TvX73ei+zSF3\n"\ "UyEFjDTcnhe6mPHL9REIVmb0+n70feP/SFQqaFRaamxBOkBPsVa9NP4zLJiRn71j\n"\ "ff7opHcT7zaR19cigQxsYmd+rZnhiH+UXel8P7kVTM7OCc7LRSJPG6xY4FtpWE2q\n"\ "abrGaUfBCMGtrH0GqjtbNCnBeBeCiijpN5XPqnGlSXpXrd3mjldlrFhiKVSlnvyp\n"\ "ekdAzA0WvpraBy351TzMsXphokchoynpdP04Pyv7DtI6SrU7sn5QZGoZtRVzNjOE\n"\ "CljVD4HZmc7Xw/kpCcb3sBtT9KOTlGd/ocn7D4Tw04jCCjmPOWhX/kKTrD4Mf+wv\n"\ "kUNxrz0Ea7PClAQf+HeZrjcCAwEAAQ==\n"\ "-----END PUBLIC KEY-----"; const char privkey_1_pem[] = "-----BEGIN RSA PRIVATE KEY-----\n"\ "MIIJKwIBAAKCAgEAxaF1egmmQ+0/AAEcv/JdTCBxi7A05VvDvRgaYzOEWqJF3qv3\n"\ "b6ifD4E4C9+cir6tIyCOJ2WXE4TV9fCWBQjIomacdQjdEwMAEWYYVEz4PPpWZDEe\n"\ "CKKgsz5DnLNyPznMiom82vDREvSqG+yxAEbkW1r+MtcFRd4YSZyJYDSVJ+t2VL7N\n"\ "t/Gxao/MC8rJP/QHzSbWjwruSwW3JM+75wdsJUbHhyIZ5lQ2u06d68ZA9Q4T60i+\n"\ "zE49yoLd8+PIjcrhM3kYIJkcTKyjWqZEV+we16eroS8PX/YN5tw4Od+fKb6I0XIL\n"\ "hiyCqKlOg3FHdY8iAK75atI6TvX73ei+zSF3UyEFjDTcnhe6mPHL9REIVmb0+n70\n"\ "feP/SFQqaFRaamxBOkBPsVa9NP4zLJiRn71jff7opHcT7zaR19cigQxsYmd+rZnh\n"\ "iH+UXel8P7kVTM7OCc7LRSJPG6xY4FtpWE2qabrGaUfBCMGtrH0GqjtbNCnBeBeC\n"\ "iijpN5XPqnGlSXpXrd3mjldlrFhiKVSlnvypekdAzA0WvpraBy351TzMsXphokch\n"\ "oynpdP04Pyv7DtI6SrU7sn5QZGoZtRVzNjOECljVD4HZmc7Xw/kpCcb3sBtT9KOT\n"\ "lGd/ocn7D4Tw04jCCjmPOWhX/kKTrD4Mf+wvkUNxrz0Ea7PClAQf+HeZrjcCAwEA\n"\ "AQKCAgEAiK7oXuT5lxUoc0pRpfdry1DM9v2BN/fFYqye42kn6r88b4qj8RfAGqsW\n"\ "JjAbRmIlwJutdVXvrNI8YIJqX0T57kflgZQFwjZa/62KuhMZxKKmyVinGvXPCujP\n"\ "PjtlkpDZBB/K++l90Axi8jk7GaPuH7jJAkRwIyg9x/t5j44OcrOI/YeRI90HBVo6\n"\ "lxVDJvYRxzNLRVccA8sKJW1+2YPqqcVQDgfpvcEUsWroEb7mAhYgdKO1QfwDBGKK\n"\ "OzmY5WghZMYlh7YdOnJT8Ef6EIz/r8sJCstPaOD2MGqygGRG4koiuiiUC9IQ87FU\n"\ "2deowoehzrM1UUnghefesFVeUOUKbr3Gh7rv5fYzijwtel2fAW29qjdhgqoCq/WQ\n"\ "fQlkT2QnJjeh2Fdr5JFmm84KgTdikOvlROVGQRZkBNCOq0Y/skSr9FDC0PAzU2kA\n"\ "0iXhwUAlWFQlud2Mx4moFwDj40RXpsGNaR6fDXv0fpS5Y1c3NeSU+yC0ycX8/BwE\n"\ "Uq63CcWocATmDrD2IdDnIbv/abjMyh18GIaJLLdx1DZ/cwQdFGgGaqigQZT/pSuu\n"\ "fqLIAXGT+wg0SdP/sSbKCV79DA/k34FC4WkP81aiMaKtbA7CJZgEdEFc/S5vXts3\n"\ "HvDiCFroLEedfInQxkFrI4gyudAZ5YLujlr3fYsJNB96B0GHnGECggEBAPQQSKxv\n"\ "01Oky+jENQwxiZcpI4a5PzLPFFCgEqIjSRamCzrCQ07e97iqhU1b8IvRwxDtX358\n"\ "pFKAq7tmwpN2QQb1T9fqUwCpeQuMwRsZwoaM7ZcTSj2FZ/2djN1ixQfzqQ21VxkM\n"\ "RbrdyExqCSJXnHMcLeiFmu81dVopV2iwDbUQv4jZe/ktPUTH4HKle48Y0v9pu22l\n"\ "D5cknAQGB1gUNfyJ0PbUxZMITrZDz4khhYgxqvJ7GluYRNv2tezV+bb5leXbSLDr\n"\ "RgTKqcl5ZjkgLm9FRNGZZAmlsCHEeB3nvCs2ePQYDuLgEkNtuu39kpLFJO6j70bj\n"\ "nvpaIAcDVpPmEE8CggEBAM9L09Grg2uSrNUGfj9pfpsMn0k5kqV0n3WjX9z5ZLkw\n"\ "LNNrs0SJjb93haO2MPNlyYhctCpPKnfHJKZWaLhFDV6xr+ubf7c3DbBJjPhlV8dU\n"\ "kgmHfIqWDPl6pzN0xC61zC4IE15LgW/JEMpq53fRWnIHdufs+105QO8YOo0CVYKY\n"\ "jqut4hVbYRBSTaeVLb1vj/yhaL0qV7orQoTrpr6Bg20nftBBa+8Md/B5l0QyiSfv\n"\ "OjKnXsjULQdQGbtypQZvu2jUasnUVUQHBgeF5W5WFj8qCGGnmehqY6QissipLoRM\n"\ "cGPaV/gJKisgcorF7sSU/QzcBUmPk377LkzZXGNUYZkCggEBALL4hfI9BmCdxhFo\n"\ "X+YTJWw9dJnEmf1uMN12pHNVILGFDVMHRUg+5LT8BkhWFSzSoxJ0nsQoLm95f3Uq\n"\ "w6BS5RhvJx+T603e+K5phumSmD0GduuD77rxavJlZ/ioBwfvu5Yb1kS95RxEqi6u\n"\ "ywft6wHWNiv+XUDwmJ+HFVvlTgfqwileIjT04argT0yC4PpsH73AEPs0QRx6chXZ\n"\ "PeVu3K/Vd/Co0kEhpGavjy5l8H+QvGSXtRpZrJUIcxu7RSTSHQOzK7jgrjWxT5Q4\n"\ "e6eEW8ioqPByZRNV9rSsV9DGMAwYI9YLFk90NLBRdPQ0MBmEi7KbcEkxfVDkafv6\n"\ "jLBj0q0CggEBAJldyYY7dczVxMcKucbinwfJq+N6E/QTt5JKYDdV0F5utQtqiEQx\n"\ "3MyGejooJkk9yn/3zlfrIElj7cqe7XU/qWeg4L3Y2wHLWnZNxF1WZT4VZMJmGg9S\n"\ "eqDtTNz2C9tfJ4P695FxHX99681GkKAGJPtuaFuo6kQLgu4iJ9eBnZA0nIGJ8VXJ\n"\ "uKNhsRBGf4PDEW1gYeRqemNDdEBxNHmHypusd9dOP7OpruccnnyXQwBnrtAhIjBF\n"\ "QldBvPgBFvUPH0GsvqE6VicxZxWTy635RRZQW8kcPfNFGxkpjsqE2OSKxTArL6BT\n"\ "733e0L+5NzD75cho1ASblA2DerriqcbXfCkCggEBAILHVNisODhO4GC8P709DqGd\n"\ "VdufLZf2Bl7AwjWyYTkpEzEfQCHHUnmOoTCn+OEvnn9lWiaCaTijtlUmos0fCfvS\n"\ "QLk9elciIOmlRk8G1EtnnzYQsTmerLoMJBgQ02hhip8GK47Y7mbZIjaPB625Dv8F\n"\ "4RHd9ZiTzXTGcNc6bldWlNNbbqw9DWS1DORPhdQEPU424qcYvHq/eklFCujWukO8\n"\ "ul3FEZYnTcth2ODSFMb0a0SCuDGkGI8BDI+/4n6+4wIlAXtc8Vt9Ko8WxJjCK/v2\n"\ "Ae9x05eknWZj0JxuyoAjPtJApp0pt25omJwZr/lY5i8T0cL6dDF5nZcA9hN//eo=\n"\ "-----END RSA PRIVATE KEY-----\n"; const char pubkey_2_pem[] = "-----BEGIN PUBLIC KEY-----\n"\ "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAosjEKF8XSrvPPGuQYUz9\n"\ "C7wLHvoJ3UAR3vpHFdJa+Z/N9STXYVh67UtCnM7RW+mbO7BuFFmT1WB2jnLnWzaT\n"\ "zeG3hC/A44imv2uU1mtOIgt5iFiF5N+XZs9opsM9jWsUW2J8uFPK1ohdnXxWkFAf\n"\ "7fTJx2fJbO7fgxjM5GIq7s1f7vbPaAaNTH8h4hnGRKuWg+n15FKjYmGUDD5SQj5d\n"\ "yMrbZx/8DbGm0XX//rQvLf3eXP2r3HFFTbWn9HcimZF6csqhmh4g4Rt5Ou2JcZlr\n"\ "krbSo8gyONelZ+kvKKJwAZG0+6Pu4+MgSHdwGe1B2jUmeQ1EAelsIWvLArCH4c/t\n"\ "3JYFdDe9fMfUcof6C3aseMaa8HW1WHhaH8erSnP5klF0NGhhRWB1lYSCWQ/hT9yg\n"\ "vlTJQ8v/2ocFbsZUi8lQ6bL9Lc4piHyMOeK/2ozm13wk9OHGfLxKYEtzs5V6oevg\n"\ "D7daJ0rtS/h21iLguqKt5rwe7Rzy3boMxVlNGFux0RSP5rzjoGQ/vkuuSiWgyygm\n"\ "TIheeOV/wCds6IDR7s2zrgBxQvsUK1isp5YipHVo4AOuPxNcxGnxLkt0DA7AOYCw\n"\ "h2jngVWmjKgzSYG0EHiiLUrXyMFp4xhyTBLSQpWZn65o8jwABqzWtodMirsZsawI\n"\ "GihzXtw00vRRJDnVpE7EQUUCAwEAAQ==\n"\ "-----END PUBLIC KEY-----\n"; const char privkey_2_pem[] = "-----BEGIN RSA PRIVATE KEY-----\n"\ "MIIJJwIBAAKCAgEAosjEKF8XSrvPPGuQYUz9C7wLHvoJ3UAR3vpHFdJa+Z/N9STX\n"\ "YVh67UtCnM7RW+mbO7BuFFmT1WB2jnLnWzaTzeG3hC/A44imv2uU1mtOIgt5iFiF\n"\ "5N+XZs9opsM9jWsUW2J8uFPK1ohdnXxWkFAf7fTJx2fJbO7fgxjM5GIq7s1f7vbP\n"\ "aAaNTH8h4hnGRKuWg+n15FKjYmGUDD5SQj5dyMrbZx/8DbGm0XX//rQvLf3eXP2r\n"\ "3HFFTbWn9HcimZF6csqhmh4g4Rt5Ou2JcZlrkrbSo8gyONelZ+kvKKJwAZG0+6Pu\n"\ "4+MgSHdwGe1B2jUmeQ1EAelsIWvLArCH4c/t3JYFdDe9fMfUcof6C3aseMaa8HW1\n"\ "WHhaH8erSnP5klF0NGhhRWB1lYSCWQ/hT9ygvlTJQ8v/2ocFbsZUi8lQ6bL9Lc4p\n"\ "iHyMOeK/2ozm13wk9OHGfLxKYEtzs5V6oevgD7daJ0rtS/h21iLguqKt5rwe7Rzy\n"\ "3boMxVlNGFux0RSP5rzjoGQ/vkuuSiWgyygmTIheeOV/wCds6IDR7s2zrgBxQvsU\n"\ "K1isp5YipHVo4AOuPxNcxGnxLkt0DA7AOYCwh2jngVWmjKgzSYG0EHiiLUrXyMFp\n"\ "4xhyTBLSQpWZn65o8jwABqzWtodMirsZsawIGihzXtw00vRRJDnVpE7EQUUCAwEA\n"\ "AQKCAgAgeAsoQk9TCML9OjdCMSkH/in5s8Xj67Ok1ib3o02iHM3n58FOGWfrjOds\n"\ "f5Qi0SW1ciuCw5tJJESBhUnAV9KDblI8e4nt2IPqCntViXG8OK21FHMgg7ifOR+W\n"\ "o7rTSlh1Id/epFgBEpIZp4NcpZ64x+q3a2wx3jOW4ot7wljt6OkJsRENnsDwUStg\n"\ "/eMeL1uGVFRHhnQwW2Y8SvH9ZJMwScYZe7e6w4SSowa8txPyCIEh6zcscStxpJor\n"\ "mNe96daSrvgs1XWZcPFtX5PapM9gJDc/k97wl5LWdxyA7mWI6pc+2iP8MIFv8uLZ\n"\ "SQmpjJBv9MVr3DNFN0r27l8sG11GIPMEg9Y01uEK4PZ5oASRNJbdddTOY9KWsYL4\n"\ "IxRV6ZU5ZGe0+EQWBeJnPfr4cyJd59fWC1hA8FUBAjWc8NimmMAaPu76xW+1Cqwo\n"\ "HgBDNzhQur+20eImvD9MZsVJDRAI8nYe+cVN4FaLEogK9iJFcyjv+R1QEofv8/+N\n"\ "qiibNZtff2mF9Y5YLrZQ3b0eNAzcY3VuQyN2iWsqjmXy4F1AP8ZUAyp2S6d3onDx\n"\ "+m4ua6EUB462gxUCxbYh34IbL+OyLX1xuBEetUd7j++7fK+8K5Dkw/RGd2K+D0m8\n"\ "sz4uPzcmxtZ9QbZQSE/JyBM8VzC5Mqy1NyJdBJcsosV62tywwQKCAQEAz/mQXsGj\n"\ "ErtO+6zr+x7YTF9PeUrATsTNtMDASGlbUIGLXcCwr5sE1lcT7zfMXS6xnP0NQnkF\n"\ "HIfgsBPEwMKSnRXNrS127TTvvxU6CzzDO5K1u7q6hzUvuDhj0iZjCSotBC4Dgg39\n"\ "G1J3N5YY4D+2jtS4yWsdIdCuUB/Sel6/1x6jfmkFSgLSxUATycX06/b+WSAFGS40\n"\ "Z5B+n8gfKF2OvuvdTi1uNr8T4EQXMZRQG8hgn7WRcrHuKmanI/jsWs5SiOPArKf7\n"\ "E+m7glguu9D5dqID5KI7kCUGGf+IhcRvcWoF2dZDTFKiyzfWNidVWj7ua5H+wGbl\n"\ "SYDQ+1UO2sFxKwKCAQEAyF/CL5HFZbUmA/igc8FN46vD1OQWlVqFEXk8B5kgK6hX\n"\ "EiA9lRo0/UeG6/o61vgjtI1F1CKjHPqNUaiCmTXVBXqWWxXiyRqcCBGpZPx2zup0\n"\ "CxcCGQUKYPqYBqoazipUtRIasyr8qGoFwJ23am+HLfK4hCa/+JZIn5cmWxdtZbwN\n"\ "I1raal596coHl23fnMpmnnHOf9RVSgLgcrtzZlou+zQq5QTymBoB0AWiCTt6aBB9\n"\ "dcr1M89F32O6WU9lPHdRDXTiZKFfofP+9+5NSKk8EV5/NKbc4q1czl6OQNB1iaij\n"\ "GdW1HbRK6OMn/QPSPrq/4b0vx1378dw1SCynnsp/TwKCAQAjzJCWTvyUD9vzpHtm\n"\ "WZPf6ZQTi9N4e5XxJDC1BjWqsPHdbHq9b5CwmIKc7dzmuU/ndwOeSpifpZ/+jxiS\n"\ "GawFECi8Q5QyFqeiG7RHFOmCSqmvq9/JwFT7f/FmzibEABeJnaYK7/9+gX51+gdE\n"\ "KpNxGJrKSP7VwEcREbk4STD6hZKdAtTfPYcsXvBJDiMvru9vRfy6reJlZVD2jgsW\n"\ "9qvIn28TsuxCzjdMpkvbw+zaK26+bxGfTZNyhwMERSfiJZu9Zn7W+X9VmSkmsakx\n"\ "uTIHwrGq3GYmVMktFnhYlkycmCRzr9cbvym4k17zKWgexbBiodnZIp0bPuo6KRUK\n"\ "gLvLAoIBAGrV29S/NW/AQU+2vd5aP+xXRef54SyrAe5KJL6sX2HHtP0eIZehTIWL\n"\ "IJ4rYjoCPg9jj8rG2Q93a+gRH+kOsdDlBsv0BGJThMQsnpQQpEw457yN/PlYHauN\n"\ "kYdkJTyth1KwH3pOPj6RoRWNQiFG692M3+LeQlcJ0hj9X18MQ7ENrjeelnxGe34u\n"\ "0RNlaufPZx2t8ntnvD3lAMVLuwDkrs4Th5dqpuqAW10N09J2WxKnUC2BFHIWXtv3\n"\ "8YDy+zhdKeMx4g/jlvjj/ps0/RHz4eok51Asc/OMmcIS2mgmfbTzLFt2/cWjvpkj\n"\ "nMujwPLfYbW7yIDVVKMlVWdxH8Jjl7MCggEAbNq+mJjepyGY5mXTlzAcBFY5HqlD\n"\ "x+Ssapo1waswzRhnXKB15zqU+LQfotBBp7pKxWCytwgOmV2toZf/R0Sqinohh52D\n"\ "0mCvTYldTB5tUpKpnhEGZuOZ24BTtrqNAw2X5NRKTflttiVJgfrMCSDIR8NNX4aw\n"\ "GB8iUNZLyn4e5Zjfa7cLCwn7ngINpU1TaCY9gbHSsYcVcpX7ro6Vr93ox6cRtapB\n"\ "WElzAPXDF0YKMiKzA4fTqVtbamVHz64k7xgWVwdWXDbdqOWOtI0weeeHMy2Y9Ktz\n"\ "7eXh0rK0fG1c35qJ+V61oXdyfapmI7uGH2R5J+Q2LDJw/3AMGb6oCsPfdQ==\n"\ "-----END RSA PRIVATE KEY-----\n"; const char jwk_pubkey_ecdsa_str[] = "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4\","\ "\"y\":\"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM\",\"use\":\"sig\",\"kid\":\""KID"\",\"alg\":\"ES384\"}"; const char jwk_pubkey_ecdsa_str_invalid_alg[] = "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4\","\ "\"y\":\"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM\",\"use\":\"sig\",\"kid\":\""KID"\",\"alg\":\"ES512\"}"; const char jwk_privkey_ecdsa_str[] = "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4\","\ "\"y\":\"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM\",\"d\":\"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE\","\ "\"use\":\"sig\",\"kid\":\""KID"\",\"alg\":\"ES384\"}"; const char jwk_pubkey_ecdsa_str_2[] = "{\"kty\":\"EC\",\"x\":\"RKL0w34ppc4wuBuzotuWo9d6hGv59uWjgc5oimWQtYU\",\"y\":\"S8EabLKBmyT2v_vPSrpfWnYw6edRm9I60UQlbvSS1eU\""\ ",\"d\":\"KMRJaGpxVer0w9lMjIY_UrjC067tZdEJkL5eaiBVWi8\",\"crv\":\"P-256\",\"kid\":\""KID"\",\"alg\":\"ES256\",\"use\":\"sig\"}"; const char jwk_privkey_ecdsa_str_2[] = "{\"kty\":\"EC\",\"x\":\"RKL0w34ppc4wuBuzotuWo9d6hGv59uWjgc5oimWQtYU\",\"y\":\"S8EabLKBmyT2v_vPSrpfWnYw6edRm9I60UQlbvSS1eU\","\ "\"crv\":\"P-256\",\"kid\":\""KID"\",\"alg\":\"ES256\",\"use\":\"sig\"}"; const char jwk_pubkey_rsa_str[] = "{\"kty\":\"RSA\",\"n\":\"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRX"\ "jBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6"\ "qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw\""\ ",\"e\":\"AQAB\",\"alg\":\"RS256\",\"kid\":\""KID"\",\"use\":\"sig\"}"; const char jwk_privkey_rsa_str[] = "{\"kty\":\"RSA\",\"n\":\"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKR"\ "XjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHz"\ "u6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKg"\ "w\",\"e\":\"AQAB\",\"d\":\"X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2v"\ "v7B6NqXSzUvxT0_YSfqijwp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk"\ "5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoA"\ "C8Q\",\"p\":\"83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPVnwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7"\ "XOuVIYQyqVWlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs\",\"q\":\"3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3v"\ "obLyumqjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgxkIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelx"\ "k\",\"dp\":\"G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oimYwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA7"\ "7Qe_NmtuYZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0\",\"dq\":\"s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA"\ "6huUUvMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cg"\ "k\",\"qi\":\"GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzgUIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_m"\ "HZGJ11rxyR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU\",\"alg\":\"RS256\",\"kid\":\""KID"\",\"use\":\"sig\"}"; struct _u_request admin_req; struct _u_request user_req; START_TEST(test_oidc_fapi_add_plugin) { json_t * j_param = json_pack("{sssssss{sssssssssssisisisosososososososososososissssss sosisisosososososo}}", "module", "oidc", "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", ISS, "jwt-type", PLUGIN_JWT_TYPE_RSA, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", privkey_2_pem, "cert", pubkey_2_pem, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-code-revoke-replayed", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "oauth-fapi-check-all", json_true(), "encrypt-out-token-allow", json_true(), "request-maximum-exp", REQUEST_MAX_EXP, "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "client-jwks-parameter", CLIENT_JWKS_PARAM, "client-jwks_uri-parameter", CLIENT_JWKS_URI_PARAM, "oauth-ciba-allowed", json_true(), "oauth-ciba-default-expiry", PLUGIN_CIBA_DEFAULT_EXPIRATION, "oauth-ciba-maximum-expiry", PLUGIN_CIBA_MAXIMUM_EXPIRATION, "oauth-ciba-mode-poll-allowed", json_true(), "oauth-ciba-mode-ping-allowed", json_true(), "oauth-ciba-mode-push-allowed", json_true(), "oauth-ciba-allow-https-non-secure", json_true(), "oauth-ciba-user-code-allowed", json_false(), "oauth-ciba-email-allowed", json_false()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_fapi_delete_plugin) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_fapi_add_client_pubkey) { json_t * j_client = json_pack("{ss ss so s[s] s[ssss] s[s] ss ss ss ss so}", "client_id", CLIENT_PUBKEY_ID, "name", CLIENT_PUBKEY_NAME, "confidential", json_true(), "redirect_uri", CLIENT_PUBKEY_REDIRECT, "authorization_type", "code", "token", "id_token", "client_credentials", "scope", SCOPE_LIST, CLIENT_PUBKEY_PARAM, pubkey_1_pem, "authorization_signed_response_alg", CLIENT_SIGN_ALG, "authorization_encrypted_response_alg", CLIENT_ENC_ALG, "authorization_encrypted_response_enc", CLIENT_ENC, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_PUBKEY_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_fapi_add_client_jwks) { jwks_t * jwks = r_jwks_quick_import(R_IMPORT_JSON_STR, jwk_pubkey_ecdsa_str, R_IMPORT_JSON_STR, jwk_pubkey_ecdsa_str_2, R_IMPORT_JSON_STR, jwk_pubkey_rsa_str, R_IMPORT_NONE); json_t * j_client = json_pack("{ss ss so s[s] s[ssss] s[s] so ss ss ss so}", "client_id", CLIENT_PUBKEY_ID, "name", CLIENT_PUBKEY_NAME, "confidential", json_true(), "redirect_uri", CLIENT_PUBKEY_REDIRECT, "authorization_type", "code", "token", "id_token", "client_credentials", "scope", SCOPE_LIST, CLIENT_JWKS_PARAM, r_jwks_export_to_json_t(jwks), "authorization_signed_response_alg", CLIENT_SIGN_ALG, "authorization_encrypted_response_alg", CLIENT_ENC_ALG, "authorization_encrypted_response_enc", CLIENT_ENC, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_PUBKEY_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); r_jwks_free(jwks); } END_TEST START_TEST(test_oidc_fapi_add_client_ciba_poll) { json_t * j_client = json_pack("{ss ss so s[s] s[sssss] s[s] ss ss ss ss ss so}", "client_id", CLIENT_PUBKEY_ID, "name", CLIENT_PUBKEY_NAME, "confidential", json_true(), "redirect_uri", CLIENT_PUBKEY_REDIRECT, "authorization_type", "code", "token", "id_token", "client_credentials", "urn:openid:params:grant-type:ciba", "scope", SCOPE_LIST, "client_secret", CLIENT_SECRET, "authorization_signed_response_alg", CLIENT_SIGN_ALG, "authorization_encrypted_response_alg", CLIENT_ENC_ALG, "authorization_encrypted_response_enc", CLIENT_ENC, "backchannel_token_delivery_mode", "poll", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_PUBKEY_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_fapi_add_client_ciba_push) { json_t * j_client = json_pack("{ss ss so s[s] s[sssss] s[s] ss ss ss ss ss so}", "client_id", CLIENT_PUBKEY_ID, "name", CLIENT_PUBKEY_NAME, "confidential", json_true(), "redirect_uri", CLIENT_PUBKEY_REDIRECT, "authorization_type", "code", "token", "id_token", "client_credentials", "urn:openid:params:grant-type:ciba", "scope", SCOPE_LIST, "client_secret", CLIENT_SECRET, "authorization_signed_response_alg", CLIENT_SIGN_ALG, "authorization_encrypted_response_alg", CLIENT_ENC_ALG, "authorization_encrypted_response_enc", CLIENT_ENC, "backchannel_token_delivery_mode", "push", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_PUBKEY_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_fapi_add_client_ciba_poll_public) { json_t * j_client = json_pack("{ss ss so s[s] s[sssss] s[s] ss ss ss ss so}", "client_id", CLIENT_PUBKEY_ID, "name", CLIENT_PUBKEY_NAME, "confidential", json_false(), "redirect_uri", CLIENT_PUBKEY_REDIRECT, "authorization_type", "code", "token", "id_token", "client_credentials", "urn:openid:params:grant-type:ciba", "scope", SCOPE_LIST, "authorization_signed_response_alg", CLIENT_SIGN_ALG, "authorization_encrypted_response_alg", CLIENT_ENC_ALG, "authorization_encrypted_response_enc", CLIENT_ENC, "backchannel_token_delivery_mode", "poll", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_PUBKEY_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_fapi_add_client_jwks_invalid_alg) { jwks_t * jwks = r_jwks_quick_import(R_IMPORT_JSON_STR, jwk_pubkey_ecdsa_str_invalid_alg, R_IMPORT_JSON_STR, jwk_pubkey_ecdsa_str_2, R_IMPORT_JSON_STR, jwk_pubkey_rsa_str, R_IMPORT_NONE); json_t * j_client = json_pack("{ss ss so s[s] s[ssss] s[s] so ss ss ss so}", "client_id", CLIENT_PUBKEY_ID, "name", CLIENT_PUBKEY_NAME, "confidential", json_true(), "redirect_uri", CLIENT_PUBKEY_REDIRECT, "authorization_type", "code", "token", "id_token", "client_credentials", "scope", SCOPE_LIST, CLIENT_JWKS_PARAM, r_jwks_export_to_json_t(jwks), "authorization_signed_response_alg", CLIENT_SIGN_ALG, "authorization_encrypted_response_alg", CLIENT_ENC_ALG, "authorization_encrypted_response_enc", CLIENT_ENC, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_PUBKEY_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); r_jwks_free(jwks); } END_TEST START_TEST(test_oidc_fapi_add_client_jwks_enc_alg_invalid) { jwks_t * jwks = r_jwks_quick_import(R_IMPORT_JSON_STR, jwk_pubkey_ecdsa_str, R_IMPORT_JSON_STR, jwk_pubkey_ecdsa_str_2, R_IMPORT_JSON_STR, jwk_pubkey_rsa_str, R_IMPORT_NONE); json_t * j_client = json_pack("{ss ss so s[s] s[ssss] s[s] so ss ss ss so}", "client_id", CLIENT_PUBKEY_ID, "name", CLIENT_PUBKEY_NAME, "confidential", json_true(), "redirect_uri", CLIENT_PUBKEY_REDIRECT, "authorization_type", "code", "token", "id_token", "client_credentials", "scope", SCOPE_LIST, CLIENT_JWKS_PARAM, r_jwks_export_to_json_t(jwks), "authorization_signed_response_alg", CLIENT_SIGN_ALG, "authorization_encrypted_response_alg", CLIENT_ENC_ALG_INVALID, "authorization_encrypted_response_enc", CLIENT_ENC, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_PUBKEY_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); r_jwks_free(jwks); } END_TEST START_TEST(test_oidc_fapi_delete_client) { json_t * j_param = json_pack("{ss}", "scope", ""); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_PUBKEY_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_PUBKEY_ID, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_fapi_id_token_state_valid) { struct _u_request req; struct _u_response resp, resp2; char * id_token, * code, s_hash[33], s_hash_encoded[64]; size_t s_hash_len = 33, s_hash_encoded_len = 0; gnutls_datum_t s_data; jwt_t * jwt; int alg = GNUTLS_DIG_UNKNOWN; json_t * j_body; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=%s&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_CODE_ID_TOKEN, CLIENT_ID, CLIENT_REDIRECT_URI_ENCODED, STATE, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); id_token = o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token="); if (o_strchr(id_token, '&') != NULL) { *o_strchr(id_token, '&') = '\0'; } code = o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code="); if (o_strchr(code, '&') != NULL) { *o_strchr(code, '&') = '\0'; } ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(id_token, R_IMPORT_NONE, 0)); ck_assert_ptr_ne(r_jwt_get_claim_str_value(jwt, "s_hash"), NULL); switch (r_jwt_get_sign_alg(jwt)) { case R_JWA_ALG_HS256: case R_JWA_ALG_RS256: case R_JWA_ALG_ES256: case R_JWA_ALG_PS256: case R_JWA_ALG_EDDSA: case R_JWA_ALG_ES256K: alg = GNUTLS_DIG_SHA256; break; case R_JWA_ALG_HS384: case R_JWA_ALG_RS384: case R_JWA_ALG_ES384: case R_JWA_ALG_PS384: alg = GNUTLS_DIG_SHA384; break; case R_JWA_ALG_HS512: case R_JWA_ALG_RS512: case R_JWA_ALG_ES512: case R_JWA_ALG_PS512: alg = GNUTLS_DIG_SHA384; break; default: alg = GNUTLS_DIG_UNKNOWN; break; } s_data.data = (unsigned char *)STATE; s_data.size = o_strlen(STATE); ck_assert_int_eq(gnutls_fingerprint(alg, &s_data, s_hash, &s_hash_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64url_encode((unsigned char *)s_hash, s_hash_len/2, (unsigned char *)s_hash_encoded, &s_hash_encoded_len), 1); ck_assert_str_eq(s_hash_encoded, r_jwt_get_claim_str_value(jwt, "s_hash")); r_jwt_free(jwt); ulfius_init_request(&req); ulfius_init_response(&resp2); ck_assert_int_eq(U_OK, ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_NONE)); ck_assert_int_eq(ulfius_send_http_request(&req, &resp2), U_OK); ck_assert_int_eq(200, resp2.status); ck_assert_ptr_ne((j_body = ulfius_get_json_body_response(&resp2, NULL)), NULL); ck_assert_ptr_ne(json_object_get(j_body, "refresh_token"), NULL); ck_assert_ptr_ne(json_object_get(j_body, "access_token"), NULL); ck_assert_ptr_ne(json_object_get(j_body, "id_token"), NULL); ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(json_string_value(json_object_get(j_body, "id_token")), R_IMPORT_NONE, 0)); ck_assert_ptr_ne(r_jwt_get_claim_str_value(jwt, "s_hash"), NULL); switch (r_jwt_get_sign_alg(jwt)) { case R_JWA_ALG_HS256: case R_JWA_ALG_RS256: case R_JWA_ALG_ES256: case R_JWA_ALG_PS256: case R_JWA_ALG_EDDSA: case R_JWA_ALG_ES256K: alg = GNUTLS_DIG_SHA256; break; case R_JWA_ALG_HS384: case R_JWA_ALG_RS384: case R_JWA_ALG_ES384: case R_JWA_ALG_PS384: alg = GNUTLS_DIG_SHA384; break; case R_JWA_ALG_HS512: case R_JWA_ALG_RS512: case R_JWA_ALG_ES512: case R_JWA_ALG_PS512: alg = GNUTLS_DIG_SHA384; break; default: alg = GNUTLS_DIG_UNKNOWN; break; } s_data.data = (unsigned char *)STATE; s_data.size = o_strlen(STATE); ck_assert_int_eq(gnutls_fingerprint(alg, &s_data, s_hash, &s_hash_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64url_encode((unsigned char *)s_hash, s_hash_len/2, (unsigned char *)s_hash_encoded, &s_hash_encoded_len), 1); ck_assert_str_eq(s_hash_encoded, r_jwt_get_claim_str_value(jwt, "s_hash")); json_decref(j_body); ulfius_clean_response(&resp); ulfius_clean_response(&resp2); ulfius_clean_request(&req); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_fapi_request_jwt_response_ok) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); time_t now; time(&now); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256); r_jwt_add_sign_keys_pem_der(jwt_request, R_FORMAT_PEM, (const unsigned char *)privkey_1_pem, sizeof(privkey_1_pem), NULL, 0); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE_CODE_ID_TOKEN_POST); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_int_value(jwt_request, "nbf", now); r_jwt_set_claim_int_value(jwt_request, "exp", now+60); r_jwt_set_claim_str_value(jwt_request, "state", STATE); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/%s/auth?g_continue&request=%s", SERVER_URI, PLUGIN_NAME, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "id_token="), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_fapi_request_jwt_exp_invalid) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); time_t now; time(&now); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256); r_jwt_add_sign_keys_pem_der(jwt_request, R_FORMAT_PEM, (const unsigned char *)privkey_1_pem, sizeof(privkey_1_pem), NULL, 0); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE_CODE_ID_TOKEN_POST); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_int_value(jwt_request, "nbf", now-60); r_jwt_set_claim_int_value(jwt_request, "exp", now-30); r_jwt_set_claim_str_value(jwt_request, "state", STATE); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/%s/auth?g_continue&request=%s", SERVER_URI, PLUGIN_NAME, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_fapi_request_jwt_nbf_invalid) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); time_t now; time(&now); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256); r_jwt_add_sign_keys_pem_der(jwt_request, R_FORMAT_PEM, (const unsigned char *)privkey_1_pem, sizeof(privkey_1_pem), NULL, 0); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE_CODE_ID_TOKEN_POST); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_int_value(jwt_request, "nbf", now+10); r_jwt_set_claim_int_value(jwt_request, "exp", now+30); r_jwt_set_claim_str_value(jwt_request, "state", STATE); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/%s/auth?g_continue&request=%s", SERVER_URI, PLUGIN_NAME, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_fapi_request_jwt_nbf_too_long) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); time_t now; time(&now); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256); r_jwt_add_sign_keys_pem_der(jwt_request, R_FORMAT_PEM, (const unsigned char *)privkey_1_pem, sizeof(privkey_1_pem), NULL, 0); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE_CODE_ID_TOKEN_POST); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_int_value(jwt_request, "nbf", now); r_jwt_set_claim_int_value(jwt_request, "exp", now+(REQUEST_MAX_EXP*2)); r_jwt_set_claim_str_value(jwt_request, "state", STATE); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/%s/auth?g_continue&request=%s", SERVER_URI, PLUGIN_NAME, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_fapi_request_jwt_nbf_missing) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); time_t now; time(&now); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256); r_jwt_add_sign_keys_pem_der(jwt_request, R_FORMAT_PEM, (const unsigned char *)privkey_1_pem, sizeof(privkey_1_pem), NULL, 0); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE_CODE_ID_TOKEN_POST); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_int_value(jwt_request, "exp", now+(REQUEST_MAX_EXP*2)); r_jwt_set_claim_str_value(jwt_request, "state", STATE); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/%s/auth?g_continue&request=%s", SERVER_URI, PLUGIN_NAME, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_fapi_request_jwt_exp_missing) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); time_t now; time(&now); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256); r_jwt_add_sign_keys_pem_der(jwt_request, R_FORMAT_PEM, (const unsigned char *)privkey_1_pem, sizeof(privkey_1_pem), NULL, 0); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE_CODE_ID_TOKEN_POST); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_int_value(jwt_request, "nbf", now); r_jwt_set_claim_str_value(jwt_request, "state", STATE); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/%s/auth?g_continue&request=%s", SERVER_URI, PLUGIN_NAME, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_fapi_request_jwt_multiple_kid_response_ok) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); time_t now; time(&now); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_add_sign_keys_json_str(jwt_request, jwk_privkey_ecdsa_str, NULL); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE_CODE_ID_TOKEN_POST); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_int_value(jwt_request, "nbf", now); r_jwt_set_claim_int_value(jwt_request, "exp", now+60); r_jwt_set_claim_str_value(jwt_request, "state", STATE); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/%s/auth?g_continue&request=%s", SERVER_URI, PLUGIN_NAME, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "id_token="), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_fapi_request_jwt_multiple_kid_invalid_alg) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); time_t now; time(&now); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_add_sign_keys_json_str(jwt_request, jwk_privkey_ecdsa_str, NULL); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE_CODE_ID_TOKEN_POST); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_int_value(jwt_request, "nbf", now); r_jwt_set_claim_int_value(jwt_request, "exp", now+60); r_jwt_set_claim_str_value(jwt_request, "state", STATE); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/%s/auth?g_continue&request=%s", SERVER_URI, PLUGIN_NAME, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_fapi_response_jwt_enc_ok) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); time_t now; time(&now); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_add_sign_keys_json_str(jwt_request, jwk_privkey_ecdsa_str, NULL); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE_CODE_ID_TOKEN_POST); r_jwt_set_claim_str_value(jwt_request, "response_mode", "query.jwt"); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_int_value(jwt_request, "nbf", now); r_jwt_set_claim_int_value(jwt_request, "exp", now+60); r_jwt_set_claim_str_value(jwt_request, "state", STATE); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/%s/auth?g_continue&request=%s", SERVER_URI, PLUGIN_NAME, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "response="), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_fapi_response_jwt_enc_error) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); time_t now; time(&now); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_add_sign_keys_json_str(jwt_request, jwk_privkey_ecdsa_str, NULL); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE_CODE_ID_TOKEN_POST); r_jwt_set_claim_str_value(jwt_request, "response_mode", "jwt"); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_int_value(jwt_request, "nbf", now); r_jwt_set_claim_int_value(jwt_request, "exp", now+60); r_jwt_set_claim_str_value(jwt_request, "state", STATE); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/%s/auth?g_continue&request=%s", SERVER_URI, PLUGIN_NAME, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error="), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_fapi_ciba_request_ok) { struct _u_request req; struct _u_response resp; json_t * j_body; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_PUBKEY_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, json_object_get(j_body, "auth_req_id")); ck_assert_int_eq(PLUGIN_CIBA_DEFAULT_EXPIRATION, json_integer_value(json_object_get(j_body, "expires_in"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_fapi_ciba_request_invalid_delivery_mode) { struct _u_request req; struct _u_response resp; json_t * j_body; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT_PUBKEY_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(401, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("invalid_client", json_string_value(json_object_get(j_body, "error"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_fapi_ciba_request_invalid_client_public) { struct _u_request req; struct _u_response resp; json_t * j_body; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/ciba", U_OPT_HTTP_VERB, "POST", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_PUBKEY_ID, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_notification_token", CIBA_CLIENT_NOTIFICATION_TOKEN, U_OPT_POST_BODY_PARAMETER, "login_hint", "{\"username\":\""USER_USERNAME"\"}", U_OPT_NONE), U_OK); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(401, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq("invalid_client", json_string_value(json_object_get(j_body, "error"))); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc fapi"); tc_core = tcase_create("test_oidc_fapi"); tcase_add_test(tc_core, test_oidc_fapi_add_plugin); tcase_add_test(tc_core, test_oidc_fapi_add_client_pubkey); tcase_add_test(tc_core, test_oidc_fapi_id_token_state_valid); tcase_add_test(tc_core, test_oidc_fapi_request_jwt_response_ok); tcase_add_test(tc_core, test_oidc_fapi_request_jwt_exp_invalid); tcase_add_test(tc_core, test_oidc_fapi_request_jwt_nbf_invalid); tcase_add_test(tc_core, test_oidc_fapi_request_jwt_nbf_too_long); tcase_add_test(tc_core, test_oidc_fapi_request_jwt_nbf_missing); tcase_add_test(tc_core, test_oidc_fapi_request_jwt_exp_missing); tcase_add_test(tc_core, test_oidc_fapi_delete_client); tcase_add_test(tc_core, test_oidc_fapi_add_client_jwks); tcase_add_test(tc_core, test_oidc_fapi_response_jwt_enc_ok); tcase_add_test(tc_core, test_oidc_fapi_request_jwt_multiple_kid_response_ok); tcase_add_test(tc_core, test_oidc_fapi_delete_client); tcase_add_test(tc_core, test_oidc_fapi_add_client_jwks_invalid_alg); tcase_add_test(tc_core, test_oidc_fapi_request_jwt_multiple_kid_invalid_alg); tcase_add_test(tc_core, test_oidc_fapi_delete_client); tcase_add_test(tc_core, test_oidc_fapi_add_client_jwks_enc_alg_invalid); tcase_add_test(tc_core, test_oidc_fapi_response_jwt_enc_error); tcase_add_test(tc_core, test_oidc_fapi_delete_client); tcase_add_test(tc_core, test_oidc_fapi_add_client_ciba_poll); tcase_add_test(tc_core, test_oidc_fapi_ciba_request_ok); tcase_add_test(tc_core, test_oidc_fapi_delete_client); tcase_add_test(tc_core, test_oidc_fapi_add_client_ciba_push); tcase_add_test(tc_core, test_oidc_fapi_ciba_request_invalid_delivery_mode); tcase_add_test(tc_core, test_oidc_fapi_delete_client); tcase_add_test(tc_core, test_oidc_fapi_add_client_ciba_poll_public); tcase_add_test(tc_core, test_oidc_fapi_ciba_request_invalid_client_public); tcase_add_test(tc_core, test_oidc_fapi_delete_client); tcase_add_test(tc_core, test_oidc_fapi_delete_plugin); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); ulfius_init_request(&user_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "User %s authenticated", USER_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(user_req.map_header, "Cookie", cookie); o_free(cookie); } else { do_test = 0; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication user"); do_test = 0; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); } if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } run_simple_test(&user_req, "DELETE", SERVER_URI "/auth/", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL); ulfius_clean_request(&admin_req); ulfius_clean_request(&user_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_hybrid_id_token_code.c000066400000000000000000001004171415646314000235530ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "scope1 scope2 openid" #define SCOPE_LIST_PARTIAL "scope1 openid" #define SCOPE_LIST_PARTIAL_URL "scope1+openid" #define SCOPE_LIST_MAX_USE "scope1 scope2 scope3 openid" #define SCOPE_LIST_MAX_USE_URL "scope1+scope2+scope3+openid" #define CLIENT "client1_id" #define RESPONSE_TYPE "id_token code" struct _u_request user_req; char * code; START_TEST(test_oidc_hybrid_id_token_code_redirect_login) { ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE "&client_id=" CLIENT "&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE "&client_id=" CLIENT "&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&g_continue&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); } END_TEST START_TEST(test_oidc_hybrid_id_token_code_redirect_login_post) { char * url = o_strdup(SERVER_URI "/oidc/auth"); struct _u_map body; u_map_init(&body); u_map_put(&body, "response_type", RESPONSE_TYPE); u_map_put(&body, "redirect_uri", "../../test-oauth2.html?param=client1_cb1"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "state", "xyzabcd"); u_map_put(&body, "nonce", "nonce1234"); u_map_put(&body, "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 302, NULL, NULL, "login.html"), 1); o_free(url); u_map_clean(&body); } END_TEST START_TEST(test_oidc_hybrid_id_token_code_valid) { struct _u_response resp; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_hybrid_id_token_code_valid_post) { struct _u_response resp; char * id_token, * code, ** id_token_split = NULL, * str_payload, at_hash[33], at_hash_encoded[64]; size_t str_payload_len = 0, at_hash_len = 33, at_hash_encoded_len = 0; gnutls_datum_t at_data; json_t * j_payload; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/oidc/auth", SERVER_URI); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); u_map_put(user_req.map_post_body, "response_type", RESPONSE_TYPE); u_map_put(user_req.map_post_body, "client_id", CLIENT); u_map_put(user_req.map_post_body, "redirect_uri", "../../test-oauth2.html?param=client1_cb1"); u_map_put(user_req.map_post_body, "state", "xyzabcd"); u_map_put(user_req.map_post_body, "state", "xyzabcd"); u_map_put(user_req.map_post_body, "nonce", "nonce1234"); u_map_put(user_req.map_post_body, "scope", SCOPE_LIST); u_map_put(user_req.map_post_body, "g_continue", "true"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); id_token = o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token="); if (o_strchr(id_token, '&') != NULL) { *o_strchr(id_token, '&') = '\0'; } code = o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code="); if (o_strchr(code, '&') != NULL) { *o_strchr(code, '&') = '\0'; } ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), NULL, &str_payload_len), 1); ck_assert_ptr_ne((str_payload = o_malloc(str_payload_len + 1)), NULL); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_payload = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_int_eq(json_object_size(j_payload), 10); ck_assert_ptr_ne(json_object_get(j_payload, "c_hash"), NULL); at_data.data = (unsigned char*)code; at_data.size = o_strlen(code); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &at_data, at_hash, &at_hash_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64url_encode((unsigned char *)at_hash, at_hash_len/2, (unsigned char *)at_hash_encoded, &at_hash_encoded_len), 1); ck_assert_str_eq(at_hash_encoded, json_string_value(json_object_get(j_payload, "c_hash"))); ulfius_clean_response(&resp); free_string_array(id_token_split); o_free(str_payload); json_decref(j_payload); } END_TEST START_TEST(test_oidc_hybrid_id_token_code_client_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, "invalid", SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=unauthorized_client"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_hybrid_id_token_code_redirect_uri_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=invalid&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=unauthorized_client"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_hybrid_id_token_code_scope_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, "scope4"); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_scope"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_hybrid_id_token_code_nonce_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"); o_free(url); ck_assert_int_eq(res, 1); url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_hybrid_id_token_code_empty) { char * url = msprintf("%s/oidc/auth?response_type=%s&state=xyzabcd&nonce=nonce1234&g_continue", SERVER_URI, RESPONSE_TYPE); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_hybrid_id_token_code_scope_grant_partial) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST_PARTIAL); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get response_type=%s code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "code="), NULL); ulfius_clean_response(&code_resp); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); } END_TEST START_TEST(test_oidc_hybrid_id_token_code_scope_grant_none) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Try to get code code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&state=xyzabcd&nonce=nonce1234&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ulfius_clean_request(&code_req); ulfius_clean_response(&code_resp); ulfius_clean_request(&auth_req); } END_TEST START_TEST(test_oidc_hybrid_id_token_code_scope_grant_all_authorize_partial) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Try to get response_type=%s code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "login.html"), NULL); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); ulfius_clean_response(&code_resp); } END_TEST START_TEST(test_oidc_hybrid_id_token_code_retry_with_max_use) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST_MAX_USE); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 88 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get response_type=%s code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "code="), NULL); ulfius_clean_response(&code_resp); // Try to get another response_type=%s with the same session but redirected to login page ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "login.html"), NULL); ulfius_clean_response(&code_resp); // Reauthenticate with scheme mock 88 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get another code ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "code="), NULL); ulfius_clean_response(&code_resp); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc hybrid id_token code"); tc_core = tcase_create("test_oidc_hybrid_id_token_code"); tcase_add_test(tc_core, test_oidc_hybrid_id_token_code_redirect_login); tcase_add_test(tc_core, test_oidc_hybrid_id_token_code_redirect_login_post); tcase_add_test(tc_core, test_oidc_hybrid_id_token_code_valid); tcase_add_test(tc_core, test_oidc_hybrid_id_token_code_valid_post); tcase_add_test(tc_core, test_oidc_hybrid_id_token_code_client_invalid); tcase_add_test(tc_core, test_oidc_hybrid_id_token_code_redirect_uri_invalid); tcase_add_test(tc_core, test_oidc_hybrid_id_token_code_scope_invalid); tcase_add_test(tc_core, test_oidc_hybrid_id_token_code_nonce_invalid); tcase_add_test(tc_core, test_oidc_hybrid_id_token_code_empty); tcase_add_test(tc_core, test_oidc_hybrid_id_token_code_scope_grant_partial); tcase_add_test(tc_core, test_oidc_hybrid_id_token_code_scope_grant_none); tcase_add_test(tc_core, test_oidc_hybrid_id_token_code_scope_grant_all_authorize_partial); tcase_add_test(tc_core, test_oidc_hybrid_id_token_code_retry_with_max_use); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, register_req; struct _u_response auth_resp, scope_resp; json_t * j_body, * j_register; int res, do_test = 0, i; char * url; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_request(&scope_req); ulfius_init_request(®ister_req); ulfius_init_response(&scope_resp); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "scope1 scope2 openid" #define SCOPE_LIST_PARTIAL "scope1 openid" #define SCOPE_LIST_PARTIAL_URL "scope1+openid" #define SCOPE_LIST_MAX_USE "scope1 scope2 scope3 openid" #define SCOPE_LIST_MAX_USE_URL "scope1+scope2+scope3+openid" #define CLIENT "client1_id" #define RESPONSE_TYPE "id_token token code" struct _u_request user_req; char * code; START_TEST(test_oidc_hybrid_id_token_token_code_redirect_login) { ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE "&client_id=" CLIENT "&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE "&client_id=" CLIENT "&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&g_continue&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); } END_TEST START_TEST(test_oidc_hybrid_id_token_token_code_redirect_login_post) { char * url = o_strdup(SERVER_URI "/oidc/auth"); struct _u_map body; u_map_init(&body); u_map_put(&body, "response_type", RESPONSE_TYPE); u_map_put(&body, "redirect_uri", "../../test-oauth2.html?param=client1_cb1"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "state", "xyzabcd"); u_map_put(&body, "nonce", "nonce1234"); u_map_put(&body, "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 302, NULL, NULL, "login.html"), 1); o_free(url); u_map_clean(&body); } END_TEST START_TEST(test_oidc_hybrid_id_token_token_code_valid) { struct _u_response resp; char * id_token, * access_token, * code, ** id_token_split = NULL, * str_payload, at_hash[33], at_hash_encoded[64]; size_t str_payload_len = 0, at_hash_len = 33, at_hash_encoded_len = 0; gnutls_datum_t at_data; json_t * j_payload; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); id_token = o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token="); if (o_strchr(id_token, '&') != NULL) { *o_strchr(id_token, '&') = '\0'; } access_token = o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token="); if (o_strchr(access_token, '&') != NULL) { *o_strchr(access_token, '&') = '\0'; } code = o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code="); if (o_strchr(code, '&') != NULL) { *o_strchr(code, '&') = '\0'; } ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), NULL, &str_payload_len), 1); ck_assert_ptr_ne((str_payload = o_malloc(str_payload_len + 1)), NULL); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_payload = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_int_eq(json_object_size(j_payload), 11); ck_assert_ptr_ne(json_object_get(j_payload, "at_hash"), NULL); ck_assert_ptr_ne(json_object_get(j_payload, "c_hash"), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_payload, "nonce")), "nonce1234"); at_data.data = (unsigned char*)access_token; at_data.size = o_strlen(access_token); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &at_data, at_hash, &at_hash_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64url_encode((unsigned char *)at_hash, at_hash_len/2, (unsigned char *)at_hash_encoded, &at_hash_encoded_len), 1); ck_assert_str_eq(at_hash_encoded, json_string_value(json_object_get(j_payload, "at_hash"))); at_data.data = (unsigned char*)code; at_data.size = o_strlen(code); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &at_data, at_hash, &at_hash_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64url_encode((unsigned char *)at_hash, at_hash_len/2, (unsigned char *)at_hash_encoded, &at_hash_encoded_len), 1); ck_assert_str_eq(at_hash_encoded, json_string_value(json_object_get(j_payload, "c_hash"))); ulfius_clean_response(&resp); free_string_array(id_token_split); o_free(str_payload); json_decref(j_payload); } END_TEST START_TEST(test_oidc_hybrid_id_token_token_code_valid_post) { struct _u_response resp; char * id_token, * access_token, * code, ** id_token_split = NULL, * str_payload, at_hash[33], at_hash_encoded[64]; size_t str_payload_len = 0, at_hash_len = 33, at_hash_encoded_len = 0; gnutls_datum_t at_data; json_t * j_payload; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/oidc/auth", SERVER_URI); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); u_map_put(user_req.map_post_body, "response_type", RESPONSE_TYPE); u_map_put(user_req.map_post_body, "client_id", CLIENT); u_map_put(user_req.map_post_body, "redirect_uri", "../../test-oauth2.html?param=client1_cb1"); u_map_put(user_req.map_post_body, "state", "xyzabcd"); u_map_put(user_req.map_post_body, "state", "xyzabcd"); u_map_put(user_req.map_post_body, "nonce", "nonce1234"); u_map_put(user_req.map_post_body, "scope", SCOPE_LIST); u_map_put(user_req.map_post_body, "g_continue", "true"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); id_token = o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token="); if (o_strchr(id_token, '&') != NULL) { *o_strchr(id_token, '&') = '\0'; } access_token = o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token="); if (o_strchr(access_token, '&') != NULL) { *o_strchr(access_token, '&') = '\0'; } code = o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code="); if (o_strchr(code, '&') != NULL) { *o_strchr(code, '&') = '\0'; } ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), NULL, &str_payload_len), 1); ck_assert_ptr_ne((str_payload = o_malloc(str_payload_len + 1)), NULL); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_payload = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_int_eq(json_object_size(j_payload), 11); ck_assert_ptr_ne(json_object_get(j_payload, "at_hash"), NULL); ck_assert_ptr_ne(json_object_get(j_payload, "c_hash"), NULL); at_data.data = (unsigned char*)access_token; at_data.size = o_strlen(access_token); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &at_data, at_hash, &at_hash_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64url_encode((unsigned char *)at_hash, at_hash_len/2, (unsigned char *)at_hash_encoded, &at_hash_encoded_len), 1); ck_assert_str_eq(at_hash_encoded, json_string_value(json_object_get(j_payload, "at_hash"))); at_data.data = (unsigned char*)code; at_data.size = o_strlen(code); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &at_data, at_hash, &at_hash_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64url_encode((unsigned char *)at_hash, at_hash_len/2, (unsigned char *)at_hash_encoded, &at_hash_encoded_len), 1); ck_assert_str_eq(at_hash_encoded, json_string_value(json_object_get(j_payload, "c_hash"))); ulfius_clean_response(&resp); free_string_array(id_token_split); o_free(str_payload); json_decref(j_payload); } END_TEST START_TEST(test_oidc_hybrid_id_token_token_code_client_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, "invalid", SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=unauthorized_client"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_hybrid_id_token_token_code_redirect_uri_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=invalid&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=unauthorized_client"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_hybrid_id_token_token_code_scope_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, "scope4"); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_scope"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_hybrid_id_token_token_code_nonce_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"); o_free(url); ck_assert_int_eq(res, 1); url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_hybrid_id_token_token_code_empty) { char * url = msprintf("%s/oidc/auth?response_type=%s&state=xyzabcd&nonce=nonce1234&g_continue", SERVER_URI, RESPONSE_TYPE); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_hybrid_id_token_token_code_scope_grant_partial) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST_PARTIAL); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get response_type=%s code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "code="), NULL); ck_assert_ptr_ne(o_strstr(o_strstr(u_map_get(code_resp.map_header, "Location"), "scope=") + o_strlen("scope="), SCOPE_LIST_PARTIAL_URL), NULL); ulfius_clean_response(&code_resp); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); } END_TEST START_TEST(test_oidc_hybrid_id_token_token_code_scope_grant_none) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Try to get code code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&nonce=nonce1234&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ulfius_clean_request(&code_req); ulfius_clean_response(&code_resp); ulfius_clean_request(&auth_req); } END_TEST START_TEST(test_oidc_hybrid_id_token_token_code_scope_grant_all_authorize_partial) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Try to get response_type=%s code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "login.html"), NULL); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); ulfius_clean_response(&code_resp); } END_TEST START_TEST(test_oidc_hybrid_id_token_token_code_retry_with_max_use) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST_MAX_USE); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 88 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get response_type=%s code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "code="), NULL); ck_assert_ptr_ne(o_strstr(o_strstr(u_map_get(code_resp.map_header, "Location"), "scope=")+o_strlen("scope="), SCOPE_LIST_MAX_USE_URL), NULL); ulfius_clean_response(&code_resp); // Try to get another response_type=%s with the same session but redirected to login page ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "login.html"), NULL); ulfius_clean_response(&code_resp); // Reauthenticate with scheme mock 88 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get another code ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "code="), NULL); ck_assert_ptr_ne(o_strstr(o_strstr(u_map_get(code_resp.map_header, "Location"), "scope=")+o_strlen("scope="), SCOPE_LIST_MAX_USE_URL), NULL); ulfius_clean_response(&code_resp); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc hybrid id_token token code"); tc_core = tcase_create("test_oidc_hybrid_id_token_token_code"); tcase_add_test(tc_core, test_oidc_hybrid_id_token_token_code_redirect_login); tcase_add_test(tc_core, test_oidc_hybrid_id_token_token_code_redirect_login_post); tcase_add_test(tc_core, test_oidc_hybrid_id_token_token_code_valid); tcase_add_test(tc_core, test_oidc_hybrid_id_token_token_code_valid_post); tcase_add_test(tc_core, test_oidc_hybrid_id_token_token_code_client_invalid); tcase_add_test(tc_core, test_oidc_hybrid_id_token_token_code_redirect_uri_invalid); tcase_add_test(tc_core, test_oidc_hybrid_id_token_token_code_scope_invalid); tcase_add_test(tc_core, test_oidc_hybrid_id_token_token_code_nonce_invalid); tcase_add_test(tc_core, test_oidc_hybrid_id_token_token_code_empty); tcase_add_test(tc_core, test_oidc_hybrid_id_token_token_code_scope_grant_partial); tcase_add_test(tc_core, test_oidc_hybrid_id_token_token_code_scope_grant_none); tcase_add_test(tc_core, test_oidc_hybrid_id_token_token_code_scope_grant_all_authorize_partial); tcase_add_test(tc_core, test_oidc_hybrid_id_token_token_code_retry_with_max_use); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, register_req; struct _u_response auth_resp, scope_resp; json_t * j_body, * j_register; int res, do_test = 0, i; char * url; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_request(&scope_req); ulfius_init_request(®ister_req); ulfius_init_response(&scope_resp); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "scope1 scope2 openid" #define SCOPE_LIST_PARTIAL "scope1 openid" #define SCOPE_LIST_PARTIAL_URL "scope1+openid" #define SCOPE_LIST_MAX_USE "scope1 scope2 scope3 openid" #define SCOPE_LIST_MAX_USE_URL "scope1+scope2+scope3+openid" #define CLIENT "client1_id" #define RESPONSE_TYPE "token code" struct _u_request user_req; char * code; START_TEST(test_oidc_hybrid_token_code_redirect_login) { ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE "&client_id=" CLIENT "&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE "&client_id=" CLIENT "&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&g_continue&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); } END_TEST START_TEST(test_oidc_hybrid_token_code_redirect_login_post) { char * url = o_strdup(SERVER_URI "/oidc/auth"); struct _u_map body; u_map_init(&body); u_map_put(&body, "response_type", RESPONSE_TYPE); u_map_put(&body, "redirect_uri", "../../test-oauth2.html?param=client1_cb1"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "state", "xyzabcd"); u_map_put(&body, "nonce", "nonce1234"); u_map_put(&body, "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 302, NULL, NULL, "login.html"), 1); o_free(url); u_map_clean(&body); } END_TEST START_TEST(test_oidc_hybrid_token_code_valid) { struct _u_response resp; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_hybrid_token_code_valid_post) { struct _u_response resp; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/oidc/auth", SERVER_URI); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); u_map_put(user_req.map_post_body, "response_type", RESPONSE_TYPE); u_map_put(user_req.map_post_body, "client_id", CLIENT); u_map_put(user_req.map_post_body, "redirect_uri", "../../test-oauth2.html?param=client1_cb1"); u_map_put(user_req.map_post_body, "state", "xyzabcd"); u_map_put(user_req.map_post_body, "state", "xyzabcd"); u_map_put(user_req.map_post_body, "nonce", "nonce1234"); u_map_put(user_req.map_post_body, "scope", SCOPE_LIST); u_map_put(user_req.map_post_body, "g_continue", "true"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_hybrid_token_code_client_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, "invalid", SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=unauthorized_client"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_hybrid_token_code_redirect_uri_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=invalid&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=unauthorized_client"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_hybrid_token_code_scope_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, "scope4"); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_scope"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_hybrid_token_code_empty) { char * url = msprintf("%s/oidc/auth?response_type=%s&state=xyzabcd&nonce=nonce1234&g_continue", SERVER_URI, RESPONSE_TYPE); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_hybrid_token_code_scope_grant_partial) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST_PARTIAL); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get response_type=%s code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "code="), NULL); ck_assert_ptr_ne(o_strstr(o_strstr(u_map_get(code_resp.map_header, "Location"), "scope=") + o_strlen("scope="), SCOPE_LIST_PARTIAL_URL), NULL); ulfius_clean_response(&code_resp); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); } END_TEST START_TEST(test_oidc_hybrid_token_code_scope_grant_none) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Try to get code code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "code="), NULL); ulfius_clean_request(&code_req); ulfius_clean_response(&code_resp); ulfius_clean_request(&auth_req); } END_TEST START_TEST(test_oidc_hybrid_token_code_scope_grant_all_authorize_partial) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Try to get response_type=%s code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "login.html"), NULL); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); ulfius_clean_response(&code_resp); } END_TEST START_TEST(test_oidc_hybrid_token_code_retry_with_max_use) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST_MAX_USE); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 88 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get response_type=%s code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "code="), NULL); ck_assert_ptr_ne(o_strstr(o_strstr(u_map_get(code_resp.map_header, "Location"), "scope=")+o_strlen("scope="), SCOPE_LIST_MAX_USE_URL), NULL); ulfius_clean_response(&code_resp); // Try to get another response_type=%s with the same session but redirected to login page ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "login.html"), NULL); ulfius_clean_response(&code_resp); // Reauthenticate with scheme mock 88 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get another code ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "code="), NULL); ck_assert_ptr_ne(o_strstr(o_strstr(u_map_get(code_resp.map_header, "Location"), "scope=")+o_strlen("scope="), SCOPE_LIST_MAX_USE_URL), NULL); ulfius_clean_response(&code_resp); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc hybrid token code"); tc_core = tcase_create("test_oidc_hybrid_token_code"); tcase_add_test(tc_core, test_oidc_hybrid_token_code_redirect_login); tcase_add_test(tc_core, test_oidc_hybrid_token_code_redirect_login_post); tcase_add_test(tc_core, test_oidc_hybrid_token_code_valid); tcase_add_test(tc_core, test_oidc_hybrid_token_code_valid_post); tcase_add_test(tc_core, test_oidc_hybrid_token_code_client_invalid); tcase_add_test(tc_core, test_oidc_hybrid_token_code_redirect_uri_invalid); tcase_add_test(tc_core, test_oidc_hybrid_token_code_scope_invalid); tcase_add_test(tc_core, test_oidc_hybrid_token_code_empty); tcase_add_test(tc_core, test_oidc_hybrid_token_code_scope_grant_partial); tcase_add_test(tc_core, test_oidc_hybrid_token_code_scope_grant_none); tcase_add_test(tc_core, test_oidc_hybrid_token_code_scope_grant_all_authorize_partial); tcase_add_test(tc_core, test_oidc_hybrid_token_code_retry_with_max_use); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, register_req; struct _u_response auth_resp, scope_resp; json_t * j_body, * j_register; int res, do_test = 0, i; char * url; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_request(&scope_req); ulfius_init_request(®ister_req); ulfius_init_response(&scope_resp); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "scope1 scope2 openid" #define SCOPE_LIST_PARTIAL "scope1 openid" #define SCOPE_LIST_PARTIAL_URL "scope1+openid" #define SCOPE_LIST_MAX_USE "scope1 scope2 scope3 openid" #define SCOPE_LIST_MAX_USE_URL "scope1+scope2+scope3+openid" #define CLIENT "client1_id" #define RESPONSE_TYPE "id_token" struct _u_request user_req; char * code; START_TEST(test_oidc_implicit_id_token_redirect_login) { ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE "&client_id=" CLIENT "&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE "&client_id=" CLIENT "&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&g_continue&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); } END_TEST START_TEST(test_oidc_implicit_id_token_redirect_login_post) { char * url = o_strdup(SERVER_URI "/oidc/auth"); struct _u_map body; u_map_init(&body); u_map_put(&body, "response_type", RESPONSE_TYPE); u_map_put(&body, "redirect_uri", "../../test-oauth2.html?param=client1_cb1"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "state", "xyzabcd"); u_map_put(&body, "nonce", "nonce1234"); u_map_put(&body, "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 302, NULL, NULL, "login.html"), 1); o_free(url); u_map_clean(&body); } END_TEST START_TEST(test_oidc_implicit_id_token_valid) { struct _u_response resp; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_implicit_id_token_valid_post) { struct _u_response resp; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/oidc/auth", SERVER_URI); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); u_map_put(user_req.map_post_body, "response_type", RESPONSE_TYPE); u_map_put(user_req.map_post_body, "client_id", CLIENT); u_map_put(user_req.map_post_body, "redirect_uri", "../../test-oauth2.html?param=client1_cb1"); u_map_put(user_req.map_post_body, "state", "xyzabcd"); u_map_put(user_req.map_post_body, "state", "xyzabcd"); u_map_put(user_req.map_post_body, "nonce", "nonce1234"); u_map_put(user_req.map_post_body, "scope", SCOPE_LIST); u_map_put(user_req.map_post_body, "g_continue", "true"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_implicit_id_token_client_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, "invalid", SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=unauthorized_client"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_implicit_id_token_redirect_uri_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=invalid&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=unauthorized_client"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_implicit_id_token_scope_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, "scope4"); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_scope"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_implicit_id_token_nonce_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"); o_free(url); ck_assert_int_eq(res, 1); url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_implicit_id_token_empty) { char * url = msprintf("%s/oidc/auth?response_type=%s&state=xyzabcd&nonce=nonce1234&g_continue", SERVER_URI, RESPONSE_TYPE); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_implicit_id_token_scope_grant_partial) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST_PARTIAL); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get response_type=%s code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ulfius_clean_response(&code_resp); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); } END_TEST START_TEST(test_oidc_implicit_id_token_scope_grant_none) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Try to get code code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&nonce=nonce1234&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ulfius_clean_request(&code_req); ulfius_clean_response(&code_resp); ulfius_clean_request(&auth_req); } END_TEST START_TEST(test_oidc_implicit_id_token_scope_grant_all_authorize_partial) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Try to get response_type=%s code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "login.html"), NULL); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); ulfius_clean_response(&code_resp); } END_TEST START_TEST(test_oidc_implicit_id_token_retry_with_max_use) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST_MAX_USE); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 88 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get response_type=%s code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ulfius_clean_response(&code_resp); // Try to get another response_type=%s with the same session but redirected to login page ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "login.html"), NULL); ulfius_clean_response(&code_resp); // Reauthenticate with scheme mock 88 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get another code ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ulfius_clean_response(&code_resp); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc implicit id_token"); tc_core = tcase_create("test_oidc_implicit_id_token"); tcase_add_test(tc_core, test_oidc_implicit_id_token_redirect_login); tcase_add_test(tc_core, test_oidc_implicit_id_token_redirect_login_post); tcase_add_test(tc_core, test_oidc_implicit_id_token_valid); tcase_add_test(tc_core, test_oidc_implicit_id_token_valid_post); tcase_add_test(tc_core, test_oidc_implicit_id_token_client_invalid); tcase_add_test(tc_core, test_oidc_implicit_id_token_redirect_uri_invalid); tcase_add_test(tc_core, test_oidc_implicit_id_token_scope_invalid); tcase_add_test(tc_core, test_oidc_implicit_id_token_nonce_invalid); tcase_add_test(tc_core, test_oidc_implicit_id_token_empty); tcase_add_test(tc_core, test_oidc_implicit_id_token_scope_grant_partial); tcase_add_test(tc_core, test_oidc_implicit_id_token_scope_grant_none); tcase_add_test(tc_core, test_oidc_implicit_id_token_scope_grant_all_authorize_partial); tcase_add_test(tc_core, test_oidc_implicit_id_token_retry_with_max_use); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, register_req; struct _u_response auth_resp, scope_resp; json_t * j_body, * j_register; int res, do_test = 0, i; char * url; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_request(&scope_req); ulfius_init_request(®ister_req); ulfius_init_response(&scope_resp); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "scope1 scope2 openid" #define SCOPE_LIST_PARTIAL "scope1 openid" #define SCOPE_LIST_PARTIAL_URL "scope1+openid" #define SCOPE_LIST_MAX_USE "scope1 scope2 scope3 openid" #define SCOPE_LIST_MAX_USE_URL "scope1+scope2+scope3+openid" #define CLIENT "client1_id" #define RESPONSE_TYPE "id_token token" struct _u_request user_req; char * code; START_TEST(test_oidc_implicit_id_token_token_redirect_login) { ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE "&client_id=" CLIENT "&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE "&client_id=" CLIENT "&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&g_continue&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); } END_TEST START_TEST(test_oidc_implicit_id_token_token_redirect_login_post) { char * url = o_strdup(SERVER_URI "/oidc/auth"); struct _u_map body; u_map_init(&body); u_map_put(&body, "response_type", RESPONSE_TYPE); u_map_put(&body, "redirect_uri", "../../test-oauth2.html?param=client1_cb1"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "state", "xyzabcd"); u_map_put(&body, "nonce", "nonce1234"); u_map_put(&body, "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 302, NULL, NULL, "login.html"), 1); o_free(url); u_map_clean(&body); } END_TEST START_TEST(test_oidc_implicit_id_token_token_valid) { struct _u_response resp; char * id_token, * access_token, ** id_token_split = NULL, * str_payload, at_hash[33], at_hash_encoded[64]; size_t str_payload_len = 0, at_hash_len = 33, at_hash_encoded_len = 0; gnutls_datum_t at_data; json_t * j_payload; jwt_t * jwt; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); id_token = o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token="); if (o_strchr(id_token, '&') != NULL) { *o_strchr(id_token, '&') = '\0'; } access_token = o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token="); if (o_strchr(access_token, '&') != NULL) { *o_strchr(access_token, '&') = '\0'; } ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, access_token, 0), RHN_OK); ck_assert_str_eq(SCOPE_LIST, r_jwt_get_claim_str_value(jwt, "aud")); r_jwt_free(jwt); ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), NULL, &str_payload_len), 1); ck_assert_ptr_ne((str_payload = o_malloc(str_payload_len + 3)), NULL); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_payload = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_int_eq(json_object_size(j_payload), 10); ck_assert_ptr_ne(json_object_get(j_payload, "at_hash"), NULL); at_data.data = (unsigned char*)access_token; at_data.size = o_strlen(access_token); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &at_data, at_hash, &at_hash_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64url_encode((unsigned char *)at_hash, at_hash_len/2, (unsigned char *)at_hash_encoded, &at_hash_encoded_len), 1); ck_assert_str_eq(at_hash_encoded, json_string_value(json_object_get(j_payload, "at_hash"))); ulfius_clean_response(&resp); free_string_array(id_token_split); o_free(str_payload); json_decref(j_payload); } END_TEST START_TEST(test_oidc_implicit_id_token_token_valid_post) { struct _u_response resp; char * id_token, * access_token, ** id_token_split = NULL, * str_payload, at_hash[33], at_hash_encoded[64]; size_t str_payload_len = 0, at_hash_len = 33, at_hash_encoded_len = 0; gnutls_datum_t at_data; json_t * j_payload; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/oidc/auth", SERVER_URI); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); u_map_put(user_req.map_post_body, "response_type", RESPONSE_TYPE); u_map_put(user_req.map_post_body, "client_id", CLIENT); u_map_put(user_req.map_post_body, "redirect_uri", "../../test-oauth2.html?param=client1_cb1"); u_map_put(user_req.map_post_body, "state", "xyzabcd"); u_map_put(user_req.map_post_body, "state", "xyzabcd"); u_map_put(user_req.map_post_body, "nonce", "nonce1234"); u_map_put(user_req.map_post_body, "scope", SCOPE_LIST); u_map_put(user_req.map_post_body, "g_continue", "true"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); id_token = o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token="); if (o_strchr(id_token, '&') != NULL) { *o_strchr(id_token, '&') = '\0'; } access_token = o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token="); if (o_strchr(access_token, '&') != NULL) { *o_strchr(access_token, '&') = '\0'; } ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), NULL, &str_payload_len), 1); ck_assert_ptr_ne((str_payload = o_malloc(str_payload_len + 3)), NULL); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_payload = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_int_eq(json_object_size(j_payload), 10); ck_assert_ptr_ne(json_object_get(j_payload, "at_hash"), NULL); at_data.data = (unsigned char*)access_token; at_data.size = o_strlen(access_token); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &at_data, at_hash, &at_hash_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64url_encode((unsigned char *)at_hash, at_hash_len/2, (unsigned char *)at_hash_encoded, &at_hash_encoded_len), 1); ck_assert_str_eq(at_hash_encoded, json_string_value(json_object_get(j_payload, "at_hash"))); ulfius_clean_response(&resp); free_string_array(id_token_split); o_free(str_payload); json_decref(j_payload); } END_TEST START_TEST(test_oidc_implicit_id_token_token_client_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, "invalid", SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=unauthorized_client"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_implicit_id_token_token_redirect_uri_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=invalid&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=unauthorized_client"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_implicit_id_token_token_scope_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, "scope4"); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_scope"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_implicit_id_token_token_nonce_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"); o_free(url); ck_assert_int_eq(res, 1); url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_implicit_id_token_token_empty) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE "&state=xyzabcd&nonce=nonce1234&g_continue", NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_implicit_id_token_token_scope_grant_partial) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST_PARTIAL); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get response_type=%s code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(o_strstr(u_map_get(code_resp.map_header, "Location"), "scope=") + o_strlen("scope="), SCOPE_LIST_PARTIAL_URL), NULL); ulfius_clean_response(&code_resp); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); } END_TEST START_TEST(test_oidc_implicit_id_token_token_scope_grant_none) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Try to get code code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&nonce=nonce1234&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ulfius_clean_request(&code_req); ulfius_clean_response(&code_resp); ulfius_clean_request(&auth_req); } END_TEST START_TEST(test_oidc_implicit_id_token_token_scope_grant_all_authorize_partial) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Try to get response_type=%s code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "login.html"), NULL); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); ulfius_clean_response(&code_resp); } END_TEST START_TEST(test_oidc_implicit_id_token_token_retry_with_max_use) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST_MAX_USE); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 88 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get response_type=%s code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(o_strstr(u_map_get(code_resp.map_header, "Location"), "scope=")+o_strlen("scope="), SCOPE_LIST_MAX_USE_URL), NULL); ulfius_clean_response(&code_resp); // Try to get another response_type=%s with the same session but redirected to login page ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "login.html"), NULL); ulfius_clean_response(&code_resp); // Reauthenticate with scheme mock 88 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get another code ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(o_strstr(u_map_get(code_resp.map_header, "Location"), "scope=")+o_strlen("scope="), SCOPE_LIST_MAX_USE_URL), NULL); ulfius_clean_response(&code_resp); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc implicit id_token token"); tc_core = tcase_create("test_oidc_implicit_id_token_token"); tcase_add_test(tc_core, test_oidc_implicit_id_token_token_redirect_login); tcase_add_test(tc_core, test_oidc_implicit_id_token_token_redirect_login_post); tcase_add_test(tc_core, test_oidc_implicit_id_token_token_valid); tcase_add_test(tc_core, test_oidc_implicit_id_token_token_valid_post); tcase_add_test(tc_core, test_oidc_implicit_id_token_token_client_invalid); tcase_add_test(tc_core, test_oidc_implicit_id_token_token_redirect_uri_invalid); tcase_add_test(tc_core, test_oidc_implicit_id_token_token_scope_invalid); tcase_add_test(tc_core, test_oidc_implicit_id_token_token_nonce_invalid); tcase_add_test(tc_core, test_oidc_implicit_id_token_token_empty); tcase_add_test(tc_core, test_oidc_implicit_id_token_token_scope_grant_partial); tcase_add_test(tc_core, test_oidc_implicit_id_token_token_scope_grant_none); tcase_add_test(tc_core, test_oidc_implicit_id_token_token_scope_grant_all_authorize_partial); tcase_add_test(tc_core, test_oidc_implicit_id_token_token_retry_with_max_use); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, register_req; struct _u_response auth_resp, scope_resp; json_t * j_body, * j_register; int res, do_test = 0, i; char * url; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_request(&scope_req); ulfius_init_request(®ister_req); ulfius_init_response(&scope_resp); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "scope1 scope2 openid" #define SCOPE_LIST_PARTIAL "scope1 openid" #define SCOPE_LIST_PARTIAL_URL "scope1+openid" #define SCOPE_LIST_MAX_USE "scope1 scope2 scope3 openid" #define SCOPE_LIST_MAX_USE_URL "scope1+scope2+scope3+openid" #define CLIENT "client1_id" #define RESPONSE_TYPE "none" struct _u_request user_req; char * code; START_TEST(test_oidc_implicit_none_redirect_login) { ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE "&client_id=" CLIENT "&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE "&client_id=" CLIENT "&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&g_continue&scope=" SCOPE_LIST, NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); } END_TEST START_TEST(test_oidc_implicit_none_redirect_login_post) { char * url = o_strdup(SERVER_URI "/oidc/auth"); struct _u_map body; u_map_init(&body); u_map_put(&body, "response_type", RESPONSE_TYPE); u_map_put(&body, "redirect_uri", "../../test-oauth2.html?param=client1_cb1"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "state", "xyzabcd"); u_map_put(&body, "nonce", "nonce1234"); u_map_put(&body, "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 302, NULL, NULL, "login.html"), 1); o_free(url); u_map_clean(&body); } END_TEST START_TEST(test_oidc_implicit_none_valid) { struct _u_response resp; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_implicit_none_valid_post) { struct _u_response resp; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/oidc/auth", SERVER_URI); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); u_map_put(user_req.map_post_body, "response_type", RESPONSE_TYPE); u_map_put(user_req.map_post_body, "client_id", CLIENT); u_map_put(user_req.map_post_body, "redirect_uri", "../../test-oauth2.html?param=client1_cb1"); u_map_put(user_req.map_post_body, "state", "xyzabcd"); u_map_put(user_req.map_post_body, "state", "xyzabcd"); u_map_put(user_req.map_post_body, "nonce", "nonce1234"); u_map_put(user_req.map_post_body, "scope", SCOPE_LIST); u_map_put(user_req.map_post_body, "g_continue", "true"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_implicit_none_client_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, "invalid", SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=unauthorized_client"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_implicit_none_redirect_uri_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=invalid&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=unauthorized_client"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_implicit_none_scope_invalid) { char * url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, "scope4"); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_scope"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_implicit_none_empty) { char * url = msprintf("%s/oidc/auth?response_type=%s&state=xyzabcd&nonce=nonce1234&g_continue", SERVER_URI, RESPONSE_TYPE); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_implicit_none_scope_grant_partial) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST_PARTIAL); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get response_type=%s code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ulfius_clean_response(&code_resp); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); } END_TEST START_TEST(test_oidc_implicit_none_scope_grant_none) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Try to get code code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(code_resp.map_header, "Location"), "invalid_scope"), NULL); ulfius_clean_request(&code_req); ulfius_clean_response(&code_resp); ulfius_clean_request(&auth_req); } END_TEST START_TEST(test_oidc_implicit_none_scope_grant_all_authorize_partial) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Try to get response_type=%s code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "login.html"), NULL); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); ulfius_clean_response(&code_resp); } END_TEST START_TEST(test_oidc_implicit_none_retry_with_max_use) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST_MAX_USE); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 88 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get response_type=%s code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ulfius_clean_response(&code_resp); // Try to get another response_type=%s with the same session but redirected to login page ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "login.html"), NULL); ulfius_clean_response(&code_resp); // Reauthenticate with scheme mock 88 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get another code ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&state=xyzabcd&nonce=nonce1234&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(code_resp.map_header, "Location"), "id_token="), NULL); ulfius_clean_response(&code_resp); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc implicit none"); tc_core = tcase_create("test_oidc_implicit_none"); tcase_add_test(tc_core, test_oidc_implicit_none_redirect_login); tcase_add_test(tc_core, test_oidc_implicit_none_redirect_login_post); tcase_add_test(tc_core, test_oidc_implicit_none_valid); tcase_add_test(tc_core, test_oidc_implicit_none_valid_post); tcase_add_test(tc_core, test_oidc_implicit_none_client_invalid); tcase_add_test(tc_core, test_oidc_implicit_none_redirect_uri_invalid); tcase_add_test(tc_core, test_oidc_implicit_none_scope_invalid); tcase_add_test(tc_core, test_oidc_implicit_none_empty); tcase_add_test(tc_core, test_oidc_implicit_none_scope_grant_partial); tcase_add_test(tc_core, test_oidc_implicit_none_scope_grant_none); tcase_add_test(tc_core, test_oidc_implicit_none_scope_grant_all_authorize_partial); tcase_add_test(tc_core, test_oidc_implicit_none_retry_with_max_use); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, register_req; struct _u_response auth_resp, scope_resp; json_t * j_body, * j_register; int res, do_test = 0, i; char * url; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_request(&scope_req); ulfius_init_request(®ister_req); ulfius_init_response(&scope_resp); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #include "../src/glewlwyd-common.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" struct _u_request admin_req; json_t * j_params; START_TEST(test_glwd_oidc_irl_user_module_add) { char * url = SERVER_URI "/mod/user"; ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, json_object_get(j_params, "user_mod"), NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_oidc_irl_client_module_add) { char * url = SERVER_URI "/mod/client"; ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, json_object_get(j_params, "client_mod"), NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_oidc_irl_user_add) { if (json_object_get(j_params, "user_add") == json_true()) { char * url = msprintf(SERVER_URI "/user?source=%s", json_string_value(json_object_get(json_object_get(j_params, "user_mod"), "name"))); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, json_object_get(j_params, "user"), NULL, 200, NULL, NULL, NULL), 1); o_free(url); } } END_TEST START_TEST(test_glwd_oidc_irl_client_add) { if (json_object_get(j_params, "client_add") == json_true()) { char * url = msprintf(SERVER_URI "/client?source=%s", json_string_value(json_object_get(json_object_get(j_params, "client_mod"), "name"))); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, json_object_get(j_params, "client"), NULL, 200, NULL, NULL, NULL), 1); o_free(url); } } END_TEST START_TEST(test_glwd_oidc_irl_run_workflow) { struct _u_request auth_req; struct _u_response auth_resp, resp; struct _u_map body; json_t * j_body, * j_register, * j_element = NULL; char * cookie; size_t index = 0; const char * username = json_string_value(json_object_get(json_object_get(j_params, "user"), "username")), * password = json_string_value(json_object_get(json_object_get(j_params, "user"), "password")), * client_id = json_string_value(json_object_get(json_object_get(j_params, "client"), "client_id")), * client_password = json_string_value(json_object_get(json_object_get(j_params, "client"), "password")); char * url, * redirect_uri_encoded, * scope = NULL, * code; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", username, "password", password); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_gt(auth_resp.nb_cookies, 0); ck_assert_ptr_ne((cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value)), NULL); ck_assert_int_eq(u_map_put(auth_req.map_header, "Cookie", cookie), U_OK); ulfius_clean_response(&auth_resp); json_array_foreach(json_object_get(j_params, "schemes"), index, j_element) { j_register = json_pack("{ss ss ss sO}", "username", username, "scheme_type", json_string_value(json_object_get(j_element, "scheme_type")), "scheme_name", json_string_value(json_object_get(j_element, "scheme_name")), "value", json_object_get(j_element, "register")); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); } json_array_foreach(json_object_get(j_params, "schemes"), index, j_element) { j_register = json_pack("{ss ss ss sO}", "username", username, "scheme_type", json_string_value(json_object_get(j_element, "scheme_type")), "scheme_name", json_string_value(json_object_get(j_element, "scheme_name")), "value", json_object_get(j_element, "value")); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/auth/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); } json_array_foreach(json_object_get(json_object_get(j_params, "user"), "scope"), index, j_element) { if (scope == NULL) { scope = o_strdup(json_string_value(j_element)); } else { scope = mstrcatf(scope, " %s", json_string_value(j_element)); } } url = msprintf("%s/auth/grant/%s", SERVER_URI, json_string_value(json_object_get(json_object_get(j_params, "client"), "client_id"))); j_body = json_pack("{ss}", "scope", scope); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", url, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); o_free(url); // Test id_token framework redirect_uri_encoded = ulfius_url_encode(json_string_value(json_array_get(json_object_get(json_object_get(j_params, "client"), "redirect_uri"), 0))); url = msprintf("%s/oidc/auth?response_type=id_token&nonce=nonce1234&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce4321&scope=%s", SERVER_URI, json_string_value(json_object_get(json_object_get(j_params, "client"), "client_id")), redirect_uri_encoded, scope); ck_assert_int_eq(run_simple_test(&auth_req, "GET", url, client_id, client_password, NULL, NULL, 302, NULL, NULL, "id_token="), 1); o_free(url); // Test code framework o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_url = msprintf("%s/oidc/auth?response_type=code&nonce=nonce1234&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&scope=%s", SERVER_URI, json_string_value(json_object_get(json_object_get(j_params, "client"), "client_id")), redirect_uri_encoded, scope); auth_req.http_verb = o_strdup("GET"); auth_req.auth_basic_user = o_strdup(client_id); auth_req.auth_basic_password = o_strdup(client_password); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); code = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } url = msprintf("%s/oidc/token/", SERVER_URI); u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", json_string_value(json_object_get(json_object_get(j_params, "client"), "client_id"))); u_map_put(&body, "redirect_uri", json_string_value(json_array_get(json_object_get(json_object_get(j_params, "client"), "redirect_uri"), 0))); u_map_put(&body, "code", code); ck_assert_int_eq(run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "POST", url, client_id, "error", NULL, &body, 403, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "POST", url, client_id, client_password, NULL, &body, 200, NULL, "id_token", NULL), 1); u_map_clean(&body); ulfius_clean_response(&resp); o_free(url); json_array_foreach(json_object_get(j_params, "schemes"), index, j_element) { j_register = json_pack("{ss ss ss sO}", "username", username, "scheme_type", json_string_value(json_object_get(j_element, "scheme_type")), "scheme_name", json_string_value(json_object_get(j_element, "scheme_name")), "value", json_object_get(j_element, "deregister")); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_register, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_register); } ulfius_clean_request(&auth_req); o_free(cookie); o_free(scope); o_free(code); o_free(redirect_uri_encoded); } END_TEST START_TEST(test_glwd_oidc_irl_user_delete) { if (json_object_get(j_params, "user_add") == json_true()) { char * url = msprintf(SERVER_URI "/user/%s?source=%s", json_string_value(json_object_get(json_object_get(j_params, "user"), "username")), json_string_value(json_object_get(json_object_get(j_params, "user_mod"), "name"))); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } } END_TEST START_TEST(test_glwd_oidc_irl_client_delete) { if (json_object_get(j_params, "user_add") == json_true()) { char * url = msprintf(SERVER_URI "/client/%s?source=%s", json_string_value(json_object_get(json_object_get(j_params, "client"), "client_id")), json_string_value(json_object_get(json_object_get(j_params, "client_mod"), "name"))); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } } END_TEST START_TEST(test_glwd_oidc_irl_user_module_delete) { char * url = msprintf(SERVER_URI "/mod/user/%s", json_string_value(json_object_get(json_object_get(j_params, "user_mod"), "name"))); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_oidc_irl_client_module_delete) { char * url = msprintf(SERVER_URI "/mod/client/%s", json_string_value(json_object_get(json_object_get(j_params, "client_mod"), "name"))); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc irl"); tc_core = tcase_create("test_glwd_oidc_irl"); tcase_add_test(tc_core, test_glwd_oidc_irl_user_module_add); tcase_add_test(tc_core, test_glwd_oidc_irl_client_module_add); tcase_add_test(tc_core, test_glwd_oidc_irl_user_add); tcase_add_test(tc_core, test_glwd_oidc_irl_client_add); tcase_add_test(tc_core, test_glwd_oidc_irl_run_workflow); tcase_add_test(tc_core, test_glwd_oidc_irl_user_delete); tcase_add_test(tc_core, test_glwd_oidc_irl_client_delete); tcase_add_test(tc_core, test_glwd_oidc_irl_user_module_delete); tcase_add_test(tc_core, test_glwd_oidc_irl_client_module_delete); tcase_set_timeout(tc_core, 90); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); j_params = json_load_file(argv[1], JSON_DECODE_ANY, NULL); ulfius_init_request(&admin_req); if (j_params != NULL) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error reading parameters file %s", argv[1]); } json_decref(j_params); ulfius_clean_request(&admin_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_jarm.c000066400000000000000000001700001415646314000203500ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ISS "https://glewlwyd.tld" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "user1" #define USER_PASSWORD "password" #define PLUGIN_NAME "oidc_jarm" #define PLUGIN_JWT_TYPE_RSA "rsa" #define PLUGIN_JWT_KEY_SIZE "256" #define SCOPE_LIST "openid" #define CLIENT_ID "client3_id" #define CLIENT_PUBKEY_ID "client_jarm" #define CLIENT_PUBKEY_PARAM "pubkey" #define CLIENT_JWKS_PARAM "jwks" #define CLIENT_JWKS_URI_PARAM "jwks_uri" #define CLIENT_PUBKEY_NAME "client with pubkey" #define CLIENT_PUBKEY_REDIRECT "https://glewlwyd.local/" #define CLIENT_PUBKEY_REDIRECT_ESCAPED "https%3A%2F%2Fglewlwyd.local%2F" #define CLIENT_SECRET "password" #define CLIENT_REDIRECT_URI "../../test-oauth2.html?param=client3" #define CLIENT_REDIRECT_URI_ENCODED "..%2F..%2Ftest-oauth2.html%3Fparam%3Dclient3" #define CLIENT_SIGN_ALG "PS256" #define CLIENT_ENC_ALG "RSA-OAEP-256" #define CLIENT_ENC "A128GCM" #define RESPONSE_TYPE_CODE "code" #define RESPONSE_TYPE_CODE_ID_TOKEN "code+id_token" #define RESPONSE_MODE_QUERY_JWT "query.jwt" #define RESPONSE_MODE_FRAGMENT_JWT "fragment.jwt" #define RESPONSE_MODE_FORM_POST_JWT "form_post.jwt" const char pubkey_1_pem[] = "-----BEGIN PUBLIC KEY-----\n"\ "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxaF1egmmQ+0/AAEcv/Jd\n"\ "TCBxi7A05VvDvRgaYzOEWqJF3qv3b6ifD4E4C9+cir6tIyCOJ2WXE4TV9fCWBQjI\n"\ "omacdQjdEwMAEWYYVEz4PPpWZDEeCKKgsz5DnLNyPznMiom82vDREvSqG+yxAEbk\n"\ "W1r+MtcFRd4YSZyJYDSVJ+t2VL7Nt/Gxao/MC8rJP/QHzSbWjwruSwW3JM+75wds\n"\ "JUbHhyIZ5lQ2u06d68ZA9Q4T60i+zE49yoLd8+PIjcrhM3kYIJkcTKyjWqZEV+we\n"\ "16eroS8PX/YN5tw4Od+fKb6I0XILhiyCqKlOg3FHdY8iAK75atI6TvX73ei+zSF3\n"\ "UyEFjDTcnhe6mPHL9REIVmb0+n70feP/SFQqaFRaamxBOkBPsVa9NP4zLJiRn71j\n"\ "ff7opHcT7zaR19cigQxsYmd+rZnhiH+UXel8P7kVTM7OCc7LRSJPG6xY4FtpWE2q\n"\ "abrGaUfBCMGtrH0GqjtbNCnBeBeCiijpN5XPqnGlSXpXrd3mjldlrFhiKVSlnvyp\n"\ "ekdAzA0WvpraBy351TzMsXphokchoynpdP04Pyv7DtI6SrU7sn5QZGoZtRVzNjOE\n"\ "CljVD4HZmc7Xw/kpCcb3sBtT9KOTlGd/ocn7D4Tw04jCCjmPOWhX/kKTrD4Mf+wv\n"\ "kUNxrz0Ea7PClAQf+HeZrjcCAwEAAQ==\n"\ "-----END PUBLIC KEY-----"; const char privkey_1_pem[] = "-----BEGIN RSA PRIVATE KEY-----\n"\ "MIIJKwIBAAKCAgEAxaF1egmmQ+0/AAEcv/JdTCBxi7A05VvDvRgaYzOEWqJF3qv3\n"\ "b6ifD4E4C9+cir6tIyCOJ2WXE4TV9fCWBQjIomacdQjdEwMAEWYYVEz4PPpWZDEe\n"\ "CKKgsz5DnLNyPznMiom82vDREvSqG+yxAEbkW1r+MtcFRd4YSZyJYDSVJ+t2VL7N\n"\ "t/Gxao/MC8rJP/QHzSbWjwruSwW3JM+75wdsJUbHhyIZ5lQ2u06d68ZA9Q4T60i+\n"\ "zE49yoLd8+PIjcrhM3kYIJkcTKyjWqZEV+we16eroS8PX/YN5tw4Od+fKb6I0XIL\n"\ "hiyCqKlOg3FHdY8iAK75atI6TvX73ei+zSF3UyEFjDTcnhe6mPHL9REIVmb0+n70\n"\ "feP/SFQqaFRaamxBOkBPsVa9NP4zLJiRn71jff7opHcT7zaR19cigQxsYmd+rZnh\n"\ "iH+UXel8P7kVTM7OCc7LRSJPG6xY4FtpWE2qabrGaUfBCMGtrH0GqjtbNCnBeBeC\n"\ "iijpN5XPqnGlSXpXrd3mjldlrFhiKVSlnvypekdAzA0WvpraBy351TzMsXphokch\n"\ "oynpdP04Pyv7DtI6SrU7sn5QZGoZtRVzNjOECljVD4HZmc7Xw/kpCcb3sBtT9KOT\n"\ "lGd/ocn7D4Tw04jCCjmPOWhX/kKTrD4Mf+wvkUNxrz0Ea7PClAQf+HeZrjcCAwEA\n"\ "AQKCAgEAiK7oXuT5lxUoc0pRpfdry1DM9v2BN/fFYqye42kn6r88b4qj8RfAGqsW\n"\ "JjAbRmIlwJutdVXvrNI8YIJqX0T57kflgZQFwjZa/62KuhMZxKKmyVinGvXPCujP\n"\ "PjtlkpDZBB/K++l90Axi8jk7GaPuH7jJAkRwIyg9x/t5j44OcrOI/YeRI90HBVo6\n"\ "lxVDJvYRxzNLRVccA8sKJW1+2YPqqcVQDgfpvcEUsWroEb7mAhYgdKO1QfwDBGKK\n"\ "OzmY5WghZMYlh7YdOnJT8Ef6EIz/r8sJCstPaOD2MGqygGRG4koiuiiUC9IQ87FU\n"\ "2deowoehzrM1UUnghefesFVeUOUKbr3Gh7rv5fYzijwtel2fAW29qjdhgqoCq/WQ\n"\ "fQlkT2QnJjeh2Fdr5JFmm84KgTdikOvlROVGQRZkBNCOq0Y/skSr9FDC0PAzU2kA\n"\ "0iXhwUAlWFQlud2Mx4moFwDj40RXpsGNaR6fDXv0fpS5Y1c3NeSU+yC0ycX8/BwE\n"\ "Uq63CcWocATmDrD2IdDnIbv/abjMyh18GIaJLLdx1DZ/cwQdFGgGaqigQZT/pSuu\n"\ "fqLIAXGT+wg0SdP/sSbKCV79DA/k34FC4WkP81aiMaKtbA7CJZgEdEFc/S5vXts3\n"\ "HvDiCFroLEedfInQxkFrI4gyudAZ5YLujlr3fYsJNB96B0GHnGECggEBAPQQSKxv\n"\ "01Oky+jENQwxiZcpI4a5PzLPFFCgEqIjSRamCzrCQ07e97iqhU1b8IvRwxDtX358\n"\ "pFKAq7tmwpN2QQb1T9fqUwCpeQuMwRsZwoaM7ZcTSj2FZ/2djN1ixQfzqQ21VxkM\n"\ "RbrdyExqCSJXnHMcLeiFmu81dVopV2iwDbUQv4jZe/ktPUTH4HKle48Y0v9pu22l\n"\ "D5cknAQGB1gUNfyJ0PbUxZMITrZDz4khhYgxqvJ7GluYRNv2tezV+bb5leXbSLDr\n"\ "RgTKqcl5ZjkgLm9FRNGZZAmlsCHEeB3nvCs2ePQYDuLgEkNtuu39kpLFJO6j70bj\n"\ "nvpaIAcDVpPmEE8CggEBAM9L09Grg2uSrNUGfj9pfpsMn0k5kqV0n3WjX9z5ZLkw\n"\ "LNNrs0SJjb93haO2MPNlyYhctCpPKnfHJKZWaLhFDV6xr+ubf7c3DbBJjPhlV8dU\n"\ "kgmHfIqWDPl6pzN0xC61zC4IE15LgW/JEMpq53fRWnIHdufs+105QO8YOo0CVYKY\n"\ "jqut4hVbYRBSTaeVLb1vj/yhaL0qV7orQoTrpr6Bg20nftBBa+8Md/B5l0QyiSfv\n"\ "OjKnXsjULQdQGbtypQZvu2jUasnUVUQHBgeF5W5WFj8qCGGnmehqY6QissipLoRM\n"\ "cGPaV/gJKisgcorF7sSU/QzcBUmPk377LkzZXGNUYZkCggEBALL4hfI9BmCdxhFo\n"\ "X+YTJWw9dJnEmf1uMN12pHNVILGFDVMHRUg+5LT8BkhWFSzSoxJ0nsQoLm95f3Uq\n"\ "w6BS5RhvJx+T603e+K5phumSmD0GduuD77rxavJlZ/ioBwfvu5Yb1kS95RxEqi6u\n"\ "ywft6wHWNiv+XUDwmJ+HFVvlTgfqwileIjT04argT0yC4PpsH73AEPs0QRx6chXZ\n"\ "PeVu3K/Vd/Co0kEhpGavjy5l8H+QvGSXtRpZrJUIcxu7RSTSHQOzK7jgrjWxT5Q4\n"\ "e6eEW8ioqPByZRNV9rSsV9DGMAwYI9YLFk90NLBRdPQ0MBmEi7KbcEkxfVDkafv6\n"\ "jLBj0q0CggEBAJldyYY7dczVxMcKucbinwfJq+N6E/QTt5JKYDdV0F5utQtqiEQx\n"\ "3MyGejooJkk9yn/3zlfrIElj7cqe7XU/qWeg4L3Y2wHLWnZNxF1WZT4VZMJmGg9S\n"\ "eqDtTNz2C9tfJ4P695FxHX99681GkKAGJPtuaFuo6kQLgu4iJ9eBnZA0nIGJ8VXJ\n"\ "uKNhsRBGf4PDEW1gYeRqemNDdEBxNHmHypusd9dOP7OpruccnnyXQwBnrtAhIjBF\n"\ "QldBvPgBFvUPH0GsvqE6VicxZxWTy635RRZQW8kcPfNFGxkpjsqE2OSKxTArL6BT\n"\ "733e0L+5NzD75cho1ASblA2DerriqcbXfCkCggEBAILHVNisODhO4GC8P709DqGd\n"\ "VdufLZf2Bl7AwjWyYTkpEzEfQCHHUnmOoTCn+OEvnn9lWiaCaTijtlUmos0fCfvS\n"\ "QLk9elciIOmlRk8G1EtnnzYQsTmerLoMJBgQ02hhip8GK47Y7mbZIjaPB625Dv8F\n"\ "4RHd9ZiTzXTGcNc6bldWlNNbbqw9DWS1DORPhdQEPU424qcYvHq/eklFCujWukO8\n"\ "ul3FEZYnTcth2ODSFMb0a0SCuDGkGI8BDI+/4n6+4wIlAXtc8Vt9Ko8WxJjCK/v2\n"\ "Ae9x05eknWZj0JxuyoAjPtJApp0pt25omJwZr/lY5i8T0cL6dDF5nZcA9hN//eo=\n"\ "-----END RSA PRIVATE KEY-----\n"; const char pubkey_2_pem[] = "-----BEGIN PUBLIC KEY-----\n"\ "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAosjEKF8XSrvPPGuQYUz9\n"\ "C7wLHvoJ3UAR3vpHFdJa+Z/N9STXYVh67UtCnM7RW+mbO7BuFFmT1WB2jnLnWzaT\n"\ "zeG3hC/A44imv2uU1mtOIgt5iFiF5N+XZs9opsM9jWsUW2J8uFPK1ohdnXxWkFAf\n"\ "7fTJx2fJbO7fgxjM5GIq7s1f7vbPaAaNTH8h4hnGRKuWg+n15FKjYmGUDD5SQj5d\n"\ "yMrbZx/8DbGm0XX//rQvLf3eXP2r3HFFTbWn9HcimZF6csqhmh4g4Rt5Ou2JcZlr\n"\ "krbSo8gyONelZ+kvKKJwAZG0+6Pu4+MgSHdwGe1B2jUmeQ1EAelsIWvLArCH4c/t\n"\ "3JYFdDe9fMfUcof6C3aseMaa8HW1WHhaH8erSnP5klF0NGhhRWB1lYSCWQ/hT9yg\n"\ "vlTJQ8v/2ocFbsZUi8lQ6bL9Lc4piHyMOeK/2ozm13wk9OHGfLxKYEtzs5V6oevg\n"\ "D7daJ0rtS/h21iLguqKt5rwe7Rzy3boMxVlNGFux0RSP5rzjoGQ/vkuuSiWgyygm\n"\ "TIheeOV/wCds6IDR7s2zrgBxQvsUK1isp5YipHVo4AOuPxNcxGnxLkt0DA7AOYCw\n"\ "h2jngVWmjKgzSYG0EHiiLUrXyMFp4xhyTBLSQpWZn65o8jwABqzWtodMirsZsawI\n"\ "GihzXtw00vRRJDnVpE7EQUUCAwEAAQ==\n"\ "-----END PUBLIC KEY-----\n"; const char privkey_2_pem[] = "-----BEGIN RSA PRIVATE KEY-----\n"\ "MIIJJwIBAAKCAgEAosjEKF8XSrvPPGuQYUz9C7wLHvoJ3UAR3vpHFdJa+Z/N9STX\n"\ "YVh67UtCnM7RW+mbO7BuFFmT1WB2jnLnWzaTzeG3hC/A44imv2uU1mtOIgt5iFiF\n"\ "5N+XZs9opsM9jWsUW2J8uFPK1ohdnXxWkFAf7fTJx2fJbO7fgxjM5GIq7s1f7vbP\n"\ "aAaNTH8h4hnGRKuWg+n15FKjYmGUDD5SQj5dyMrbZx/8DbGm0XX//rQvLf3eXP2r\n"\ "3HFFTbWn9HcimZF6csqhmh4g4Rt5Ou2JcZlrkrbSo8gyONelZ+kvKKJwAZG0+6Pu\n"\ "4+MgSHdwGe1B2jUmeQ1EAelsIWvLArCH4c/t3JYFdDe9fMfUcof6C3aseMaa8HW1\n"\ "WHhaH8erSnP5klF0NGhhRWB1lYSCWQ/hT9ygvlTJQ8v/2ocFbsZUi8lQ6bL9Lc4p\n"\ "iHyMOeK/2ozm13wk9OHGfLxKYEtzs5V6oevgD7daJ0rtS/h21iLguqKt5rwe7Rzy\n"\ "3boMxVlNGFux0RSP5rzjoGQ/vkuuSiWgyygmTIheeOV/wCds6IDR7s2zrgBxQvsU\n"\ "K1isp5YipHVo4AOuPxNcxGnxLkt0DA7AOYCwh2jngVWmjKgzSYG0EHiiLUrXyMFp\n"\ "4xhyTBLSQpWZn65o8jwABqzWtodMirsZsawIGihzXtw00vRRJDnVpE7EQUUCAwEA\n"\ "AQKCAgAgeAsoQk9TCML9OjdCMSkH/in5s8Xj67Ok1ib3o02iHM3n58FOGWfrjOds\n"\ "f5Qi0SW1ciuCw5tJJESBhUnAV9KDblI8e4nt2IPqCntViXG8OK21FHMgg7ifOR+W\n"\ "o7rTSlh1Id/epFgBEpIZp4NcpZ64x+q3a2wx3jOW4ot7wljt6OkJsRENnsDwUStg\n"\ "/eMeL1uGVFRHhnQwW2Y8SvH9ZJMwScYZe7e6w4SSowa8txPyCIEh6zcscStxpJor\n"\ "mNe96daSrvgs1XWZcPFtX5PapM9gJDc/k97wl5LWdxyA7mWI6pc+2iP8MIFv8uLZ\n"\ "SQmpjJBv9MVr3DNFN0r27l8sG11GIPMEg9Y01uEK4PZ5oASRNJbdddTOY9KWsYL4\n"\ "IxRV6ZU5ZGe0+EQWBeJnPfr4cyJd59fWC1hA8FUBAjWc8NimmMAaPu76xW+1Cqwo\n"\ "HgBDNzhQur+20eImvD9MZsVJDRAI8nYe+cVN4FaLEogK9iJFcyjv+R1QEofv8/+N\n"\ "qiibNZtff2mF9Y5YLrZQ3b0eNAzcY3VuQyN2iWsqjmXy4F1AP8ZUAyp2S6d3onDx\n"\ "+m4ua6EUB462gxUCxbYh34IbL+OyLX1xuBEetUd7j++7fK+8K5Dkw/RGd2K+D0m8\n"\ "sz4uPzcmxtZ9QbZQSE/JyBM8VzC5Mqy1NyJdBJcsosV62tywwQKCAQEAz/mQXsGj\n"\ "ErtO+6zr+x7YTF9PeUrATsTNtMDASGlbUIGLXcCwr5sE1lcT7zfMXS6xnP0NQnkF\n"\ "HIfgsBPEwMKSnRXNrS127TTvvxU6CzzDO5K1u7q6hzUvuDhj0iZjCSotBC4Dgg39\n"\ "G1J3N5YY4D+2jtS4yWsdIdCuUB/Sel6/1x6jfmkFSgLSxUATycX06/b+WSAFGS40\n"\ "Z5B+n8gfKF2OvuvdTi1uNr8T4EQXMZRQG8hgn7WRcrHuKmanI/jsWs5SiOPArKf7\n"\ "E+m7glguu9D5dqID5KI7kCUGGf+IhcRvcWoF2dZDTFKiyzfWNidVWj7ua5H+wGbl\n"\ "SYDQ+1UO2sFxKwKCAQEAyF/CL5HFZbUmA/igc8FN46vD1OQWlVqFEXk8B5kgK6hX\n"\ "EiA9lRo0/UeG6/o61vgjtI1F1CKjHPqNUaiCmTXVBXqWWxXiyRqcCBGpZPx2zup0\n"\ "CxcCGQUKYPqYBqoazipUtRIasyr8qGoFwJ23am+HLfK4hCa/+JZIn5cmWxdtZbwN\n"\ "I1raal596coHl23fnMpmnnHOf9RVSgLgcrtzZlou+zQq5QTymBoB0AWiCTt6aBB9\n"\ "dcr1M89F32O6WU9lPHdRDXTiZKFfofP+9+5NSKk8EV5/NKbc4q1czl6OQNB1iaij\n"\ "GdW1HbRK6OMn/QPSPrq/4b0vx1378dw1SCynnsp/TwKCAQAjzJCWTvyUD9vzpHtm\n"\ "WZPf6ZQTi9N4e5XxJDC1BjWqsPHdbHq9b5CwmIKc7dzmuU/ndwOeSpifpZ/+jxiS\n"\ "GawFECi8Q5QyFqeiG7RHFOmCSqmvq9/JwFT7f/FmzibEABeJnaYK7/9+gX51+gdE\n"\ "KpNxGJrKSP7VwEcREbk4STD6hZKdAtTfPYcsXvBJDiMvru9vRfy6reJlZVD2jgsW\n"\ "9qvIn28TsuxCzjdMpkvbw+zaK26+bxGfTZNyhwMERSfiJZu9Zn7W+X9VmSkmsakx\n"\ "uTIHwrGq3GYmVMktFnhYlkycmCRzr9cbvym4k17zKWgexbBiodnZIp0bPuo6KRUK\n"\ "gLvLAoIBAGrV29S/NW/AQU+2vd5aP+xXRef54SyrAe5KJL6sX2HHtP0eIZehTIWL\n"\ "IJ4rYjoCPg9jj8rG2Q93a+gRH+kOsdDlBsv0BGJThMQsnpQQpEw457yN/PlYHauN\n"\ "kYdkJTyth1KwH3pOPj6RoRWNQiFG692M3+LeQlcJ0hj9X18MQ7ENrjeelnxGe34u\n"\ "0RNlaufPZx2t8ntnvD3lAMVLuwDkrs4Th5dqpuqAW10N09J2WxKnUC2BFHIWXtv3\n"\ "8YDy+zhdKeMx4g/jlvjj/ps0/RHz4eok51Asc/OMmcIS2mgmfbTzLFt2/cWjvpkj\n"\ "nMujwPLfYbW7yIDVVKMlVWdxH8Jjl7MCggEAbNq+mJjepyGY5mXTlzAcBFY5HqlD\n"\ "x+Ssapo1waswzRhnXKB15zqU+LQfotBBp7pKxWCytwgOmV2toZf/R0Sqinohh52D\n"\ "0mCvTYldTB5tUpKpnhEGZuOZ24BTtrqNAw2X5NRKTflttiVJgfrMCSDIR8NNX4aw\n"\ "GB8iUNZLyn4e5Zjfa7cLCwn7ngINpU1TaCY9gbHSsYcVcpX7ro6Vr93ox6cRtapB\n"\ "WElzAPXDF0YKMiKzA4fTqVtbamVHz64k7xgWVwdWXDbdqOWOtI0weeeHMy2Y9Ktz\n"\ "7eXh0rK0fG1c35qJ+V61oXdyfapmI7uGH2R5J+Q2LDJw/3AMGb6oCsPfdQ==\n"\ "-----END RSA PRIVATE KEY-----\n"; struct _u_request admin_req; struct _u_request user_req; START_TEST(test_oidc_jarm_add_plugin) { json_t * j_param = json_pack("{sssssss{sssssssssssisisisososososososososososossssss}}", "module", "oidc", "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", ISS, "jwt-type", PLUGIN_JWT_TYPE_RSA, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", privkey_2_pem, "cert", pubkey_2_pem, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-code-revoke-replayed", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "oauth-fapi-allow-jarm", json_true(), "encrypt-out-token-allow", json_true(), "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "client-jwks-parameter", CLIENT_JWKS_PARAM, "client-jwks_uri-parameter", CLIENT_JWKS_URI_PARAM); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_jarm_delete_plugin) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_jarm_add_client_pubkey) { json_t * j_client = json_pack("{ss ss so s[s] s[ssss] s[s] ss ss ss ss so}", "client_id", CLIENT_PUBKEY_ID, "name", CLIENT_PUBKEY_NAME, "confidential", json_true(), "redirect_uri", CLIENT_PUBKEY_REDIRECT, "authorization_type", "code", "token", "id_token", "client_credentials", "scope", SCOPE_LIST, CLIENT_PUBKEY_PARAM, pubkey_1_pem, "authorization_signed_response_alg", CLIENT_SIGN_ALG, "authorization_encrypted_response_alg", CLIENT_ENC_ALG, "authorization_encrypted_response_enc", CLIENT_ENC, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_PUBKEY_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_jarm_delete_client_pubkey) { json_t * j_param = json_pack("{ss}", "scope", ""); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_PUBKEY_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_PUBKEY_ID, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_jarm_code_query_jwt) { struct _u_request req; struct _u_response resp; jwt_t * jwt; char * response; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE_CODE "&nonce=nonce1234&client_id=" CLIENT_ID "&redirect_uri=" CLIENT_REDIRECT_URI_ENCODED "&scope=" SCOPE_LIST "&response_mode="RESPONSE_MODE_QUERY_JWT"&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "&response="), NULL); response = o_strstr(u_map_get(resp.map_header, "Location"), "&response=") + o_strlen("&response="); if (o_strchr(response, '&') != NULL) { *o_strchr(response, '&') = '\0'; } ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(response, R_PARSE_NONE, 0)); ck_assert_int_eq(R_JWT_TYPE_SIGN, r_jwt_get_type(jwt)); ck_assert_int_eq(R_JWA_ALG_RS256, r_jwt_get_sign_alg(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (const unsigned char *)pubkey_2_pem, sizeof(pubkey_2_pem))); ck_assert_int_eq(RHN_OK, r_jwt_verify_signature(jwt, NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_validate_claims(jwt, R_JWT_CLAIM_ISS, ISS, R_JWT_CLAIM_EXP, R_JWT_CLAIM_NOW, R_JWT_CLAIM_AUD, CLIENT_ID, R_JWT_CLAIM_STR, "code", NULL, R_JWT_CLAIM_NOP)); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_jarm_code_id_token_query_jwt) { struct _u_request req; struct _u_response resp; jwt_t * jwt; char * response; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE_CODE_ID_TOKEN "&nonce=nonce1234&client_id=" CLIENT_ID "&redirect_uri=" CLIENT_REDIRECT_URI_ENCODED "&scope=" SCOPE_LIST "&response_mode="RESPONSE_MODE_QUERY_JWT"&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "&response="), NULL); response = o_strstr(u_map_get(resp.map_header, "Location"), "&response=") + o_strlen("&response="); if (o_strchr(response, '&') != NULL) { *o_strchr(response, '&') = '\0'; } ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(response, R_PARSE_NONE, 0)); ck_assert_int_eq(R_JWT_TYPE_SIGN, r_jwt_get_type(jwt)); ck_assert_int_eq(R_JWA_ALG_RS256, r_jwt_get_sign_alg(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (const unsigned char *)pubkey_2_pem, sizeof(pubkey_2_pem))); ck_assert_int_eq(RHN_OK, r_jwt_verify_signature(jwt, NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_validate_claims(jwt, R_JWT_CLAIM_ISS, ISS, R_JWT_CLAIM_EXP, R_JWT_CLAIM_NOW, R_JWT_CLAIM_AUD, CLIENT_ID, R_JWT_CLAIM_STR, "code", NULL, R_JWT_CLAIM_STR, "id_token", NULL, R_JWT_CLAIM_NOP)); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_jarm_response_type_error_query_jwt) { struct _u_request req; struct _u_response resp; jwt_t * jwt; char * response; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" "error" "&nonce=nonce1234&client_id=" CLIENT_ID "&redirect_uri=" CLIENT_REDIRECT_URI_ENCODED "&scope=" SCOPE_LIST "&response_mode="RESPONSE_MODE_QUERY_JWT"&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "&response="), NULL); response = o_strstr(u_map_get(resp.map_header, "Location"), "&response=") + o_strlen("&response="); if (o_strchr(response, '&') != NULL) { *o_strchr(response, '&') = '\0'; } ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(response, R_PARSE_NONE, 0)); ck_assert_int_eq(R_JWT_TYPE_SIGN, r_jwt_get_type(jwt)); ck_assert_int_eq(R_JWA_ALG_RS256, r_jwt_get_sign_alg(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (const unsigned char *)pubkey_2_pem, sizeof(pubkey_2_pem))); ck_assert_int_eq(RHN_OK, r_jwt_verify_signature(jwt, NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_validate_claims(jwt, R_JWT_CLAIM_ISS, ISS, R_JWT_CLAIM_EXP, R_JWT_CLAIM_NOW, R_JWT_CLAIM_AUD, CLIENT_ID, R_JWT_CLAIM_STR, "error", "unsupported_response_type", R_JWT_CLAIM_NOP)); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_jarm_client_invalid_query_jwt) { struct _u_request req; struct _u_response resp; jwt_t * jwt; char * response; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE_CODE "&nonce=nonce1234&client_id=" "error" "&redirect_uri=" CLIENT_REDIRECT_URI_ENCODED "&scope=" SCOPE_LIST "&response_mode="RESPONSE_MODE_QUERY_JWT"&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "&response="), NULL); response = o_strstr(u_map_get(resp.map_header, "Location"), "&response=") + o_strlen("&response="); if (o_strchr(response, '&') != NULL) { *o_strchr(response, '&') = '\0'; } ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(response, R_PARSE_NONE, 0)); ck_assert_int_eq(R_JWT_TYPE_SIGN, r_jwt_get_type(jwt)); ck_assert_int_eq(R_JWA_ALG_RS256, r_jwt_get_sign_alg(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (const unsigned char *)pubkey_2_pem, sizeof(pubkey_2_pem))); ck_assert_int_eq(RHN_OK, r_jwt_verify_signature(jwt, NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_validate_claims(jwt, R_JWT_CLAIM_ISS, ISS, R_JWT_CLAIM_EXP, R_JWT_CLAIM_NOW, R_JWT_CLAIM_STR, "error", "unauthorized_client", R_JWT_CLAIM_NOP)); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_jarm_code_fragment_jwt) { struct _u_request req; struct _u_response resp; jwt_t * jwt; char * response; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE_CODE "&nonce=nonce1234&client_id=" CLIENT_ID "&redirect_uri=" CLIENT_REDIRECT_URI_ENCODED "&scope=" SCOPE_LIST "&response_mode="RESPONSE_MODE_FRAGMENT_JWT"&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "#response="), NULL); response = o_strstr(u_map_get(resp.map_header, "Location"), "#response=") + o_strlen("#response="); if (o_strchr(response, '&') != NULL) { *o_strchr(response, '&') = '\0'; } ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(response, R_PARSE_NONE, 0)); ck_assert_int_eq(R_JWT_TYPE_SIGN, r_jwt_get_type(jwt)); ck_assert_int_eq(R_JWA_ALG_RS256, r_jwt_get_sign_alg(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (const unsigned char *)pubkey_2_pem, sizeof(pubkey_2_pem))); ck_assert_int_eq(RHN_OK, r_jwt_verify_signature(jwt, NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_validate_claims(jwt, R_JWT_CLAIM_ISS, ISS, R_JWT_CLAIM_EXP, R_JWT_CLAIM_NOW, R_JWT_CLAIM_AUD, CLIENT_ID, R_JWT_CLAIM_STR, "code", NULL, R_JWT_CLAIM_NOP)); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_jarm_code_id_token_fragment_jwt) { struct _u_request req; struct _u_response resp; jwt_t * jwt; char * response; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE_CODE_ID_TOKEN "&nonce=nonce1234&client_id=" CLIENT_ID "&redirect_uri=" CLIENT_REDIRECT_URI_ENCODED "&scope=" SCOPE_LIST "&response_mode="RESPONSE_MODE_FRAGMENT_JWT"&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "#response="), NULL); response = o_strstr(u_map_get(resp.map_header, "Location"), "#response=") + o_strlen("#response="); if (o_strchr(response, '&') != NULL) { *o_strchr(response, '&') = '\0'; } ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(response, R_PARSE_NONE, 0)); ck_assert_int_eq(R_JWT_TYPE_SIGN, r_jwt_get_type(jwt)); ck_assert_int_eq(R_JWA_ALG_RS256, r_jwt_get_sign_alg(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (const unsigned char *)pubkey_2_pem, sizeof(pubkey_2_pem))); ck_assert_int_eq(RHN_OK, r_jwt_verify_signature(jwt, NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_validate_claims(jwt, R_JWT_CLAIM_ISS, ISS, R_JWT_CLAIM_EXP, R_JWT_CLAIM_NOW, R_JWT_CLAIM_AUD, CLIENT_ID, R_JWT_CLAIM_STR, "code", NULL, R_JWT_CLAIM_STR, "id_token", NULL, R_JWT_CLAIM_NOP)); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_jarm_response_type_error_fragment_jwt) { struct _u_request req; struct _u_response resp; jwt_t * jwt; char * response; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" "error" "&nonce=nonce1234&client_id=" CLIENT_ID "&redirect_uri=" CLIENT_REDIRECT_URI_ENCODED "&scope=" SCOPE_LIST "&response_mode="RESPONSE_MODE_FRAGMENT_JWT"&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "#response="), NULL); response = o_strstr(u_map_get(resp.map_header, "Location"), "#response=") + o_strlen("#response="); if (o_strchr(response, '&') != NULL) { *o_strchr(response, '&') = '\0'; } ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(response, R_PARSE_NONE, 0)); ck_assert_int_eq(R_JWT_TYPE_SIGN, r_jwt_get_type(jwt)); ck_assert_int_eq(R_JWA_ALG_RS256, r_jwt_get_sign_alg(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (const unsigned char *)pubkey_2_pem, sizeof(pubkey_2_pem))); ck_assert_int_eq(RHN_OK, r_jwt_verify_signature(jwt, NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_validate_claims(jwt, R_JWT_CLAIM_ISS, ISS, R_JWT_CLAIM_EXP, R_JWT_CLAIM_NOW, R_JWT_CLAIM_AUD, CLIENT_ID, R_JWT_CLAIM_STR, "error", "unsupported_response_type", R_JWT_CLAIM_NOP)); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_jarm_client_invalid_fragment_jwt) { struct _u_request req; struct _u_response resp; jwt_t * jwt; char * response; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE_CODE "&nonce=nonce1234&client_id=" "error" "&redirect_uri=" CLIENT_REDIRECT_URI_ENCODED "&scope=" SCOPE_LIST "&response_mode="RESPONSE_MODE_FRAGMENT_JWT"&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "#response="), NULL); response = o_strstr(u_map_get(resp.map_header, "Location"), "#response=") + o_strlen("#response="); if (o_strchr(response, '&') != NULL) { *o_strchr(response, '&') = '\0'; } ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(response, R_PARSE_NONE, 0)); ck_assert_int_eq(R_JWT_TYPE_SIGN, r_jwt_get_type(jwt)); ck_assert_int_eq(R_JWA_ALG_RS256, r_jwt_get_sign_alg(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (const unsigned char *)pubkey_2_pem, sizeof(pubkey_2_pem))); ck_assert_int_eq(RHN_OK, r_jwt_verify_signature(jwt, NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_validate_claims(jwt, R_JWT_CLAIM_ISS, ISS, R_JWT_CLAIM_EXP, R_JWT_CLAIM_NOW, R_JWT_CLAIM_STR, "error", "unauthorized_client", R_JWT_CLAIM_NOP)); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_jarm_code_form_post_jwt) { struct _u_request req; struct _u_response resp; jwt_t * jwt; char * response; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE_CODE "&nonce=nonce1234&client_id=" CLIENT_ID "&redirect_uri=" CLIENT_REDIRECT_URI_ENCODED "&scope=" SCOPE_LIST "&response_mode="RESPONSE_MODE_FORM_POST_JWT"&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(o_strnstr(resp.binary_body, "name=\"response\" value=\"", resp.binary_body_length), NULL); response = o_strnstr(resp.binary_body, "name=\"response\" value=\"", resp.binary_body_length) + o_strlen("name=\"response\" value=\""); if (o_strchr(response, '"') != NULL) { *o_strchr(response, '"') = '\0'; } ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(response, R_PARSE_NONE, 0)); ck_assert_int_eq(R_JWT_TYPE_SIGN, r_jwt_get_type(jwt)); ck_assert_int_eq(R_JWA_ALG_RS256, r_jwt_get_sign_alg(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (const unsigned char *)pubkey_2_pem, sizeof(pubkey_2_pem))); ck_assert_int_eq(RHN_OK, r_jwt_verify_signature(jwt, NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_validate_claims(jwt, R_JWT_CLAIM_ISS, ISS, R_JWT_CLAIM_EXP, R_JWT_CLAIM_NOW, R_JWT_CLAIM_AUD, CLIENT_ID, R_JWT_CLAIM_STR, "code", NULL, R_JWT_CLAIM_NOP)); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_jarm_code_id_token_form_post_jwt) { struct _u_request req; struct _u_response resp; jwt_t * jwt; char * response; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE_CODE_ID_TOKEN "&nonce=nonce1234&client_id=" CLIENT_ID "&redirect_uri=" CLIENT_REDIRECT_URI_ENCODED "&scope=" SCOPE_LIST "&response_mode="RESPONSE_MODE_FORM_POST_JWT"&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(o_strnstr(resp.binary_body, "name=\"response\" value=\"", resp.binary_body_length), NULL); response = o_strnstr(resp.binary_body, "name=\"response\" value=\"", resp.binary_body_length) + o_strlen("name=\"response\" value=\""); if (o_strchr(response, '"') != NULL) { *o_strchr(response, '"') = '\0'; } ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(response, R_PARSE_NONE, 0)); ck_assert_int_eq(R_JWT_TYPE_SIGN, r_jwt_get_type(jwt)); ck_assert_int_eq(R_JWA_ALG_RS256, r_jwt_get_sign_alg(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (const unsigned char *)pubkey_2_pem, sizeof(pubkey_2_pem))); ck_assert_int_eq(RHN_OK, r_jwt_verify_signature(jwt, NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_validate_claims(jwt, R_JWT_CLAIM_ISS, ISS, R_JWT_CLAIM_EXP, R_JWT_CLAIM_NOW, R_JWT_CLAIM_AUD, CLIENT_ID, R_JWT_CLAIM_STR, "code", NULL, R_JWT_CLAIM_STR, "id_token", NULL, R_JWT_CLAIM_NOP)); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_jarm_response_type_error_form_post_jwt) { struct _u_request req; struct _u_response resp; jwt_t * jwt; char * response; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" "error" "&nonce=nonce1234&client_id=" CLIENT_ID "&redirect_uri=" CLIENT_REDIRECT_URI_ENCODED "&scope=" SCOPE_LIST "&response_mode="RESPONSE_MODE_FORM_POST_JWT"&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(o_strnstr(resp.binary_body, "name=\"response\" value=\"", resp.binary_body_length), NULL); response = o_strnstr(resp.binary_body, "name=\"response\" value=\"", resp.binary_body_length) + o_strlen("name=\"response\" value=\""); if (o_strchr(response, '"') != NULL) { *o_strchr(response, '"') = '\0'; } ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(response, R_PARSE_NONE, 0)); ck_assert_int_eq(R_JWT_TYPE_SIGN, r_jwt_get_type(jwt)); ck_assert_int_eq(R_JWA_ALG_RS256, r_jwt_get_sign_alg(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (const unsigned char *)pubkey_2_pem, sizeof(pubkey_2_pem))); ck_assert_int_eq(RHN_OK, r_jwt_verify_signature(jwt, NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_validate_claims(jwt, R_JWT_CLAIM_ISS, ISS, R_JWT_CLAIM_EXP, R_JWT_CLAIM_NOW, R_JWT_CLAIM_AUD, CLIENT_ID, R_JWT_CLAIM_STR, "error", "unsupported_response_type", R_JWT_CLAIM_NOP)); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_jarm_client_invalid_form_post_jwt) { struct _u_request req; struct _u_response resp; jwt_t * jwt; char * response; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE_CODE "&nonce=nonce1234&client_id=" "error" "&redirect_uri=" CLIENT_REDIRECT_URI_ENCODED "&scope=" SCOPE_LIST "&response_mode="RESPONSE_MODE_FORM_POST_JWT"&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(o_strnstr(resp.binary_body, "name=\"response\" value=\"", resp.binary_body_length), NULL); response = o_strnstr(resp.binary_body, "name=\"response\" value=\"", resp.binary_body_length) + o_strlen("name=\"response\" value=\""); if (o_strchr(response, '"') != NULL) { *o_strchr(response, '"') = '\0'; } ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(response, R_PARSE_NONE, 0)); ck_assert_int_eq(R_JWT_TYPE_SIGN, r_jwt_get_type(jwt)); ck_assert_int_eq(R_JWA_ALG_RS256, r_jwt_get_sign_alg(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (const unsigned char *)pubkey_2_pem, sizeof(pubkey_2_pem))); ck_assert_int_eq(RHN_OK, r_jwt_verify_signature(jwt, NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_validate_claims(jwt, R_JWT_CLAIM_ISS, ISS, R_JWT_CLAIM_EXP, R_JWT_CLAIM_NOW, R_JWT_CLAIM_STR, "error", "unauthorized_client", R_JWT_CLAIM_NOP)); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_jarm_client_pubkey_code_query_jwt) { struct _u_request req; struct _u_response resp; jwt_t * jwt; char * response; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE_CODE "&nonce=nonce1234&client_id=" CLIENT_PUBKEY_ID "&redirect_uri=" CLIENT_PUBKEY_REDIRECT_ESCAPED "&scope=" SCOPE_LIST "&response_mode="RESPONSE_MODE_QUERY_JWT"&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "response="), NULL); response = o_strstr(u_map_get(resp.map_header, "Location"), "response=") + o_strlen("response="); if (o_strchr(response, '&') != NULL) { *o_strchr(response, '&') = '\0'; } ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(response, R_PARSE_NONE, 0)); ck_assert_int_eq(R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, r_jwt_get_type(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_add_enc_keys_pem_der(jwt, R_FORMAT_PEM, (const unsigned char *)privkey_1_pem, sizeof(privkey_1_pem), NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (const unsigned char *)pubkey_2_pem, sizeof(pubkey_2_pem))); ck_assert_int_eq(RHN_OK, r_jwt_decrypt_nested(jwt, NULL, 0)); ck_assert_int_eq(r_str_to_jwa_alg(CLIENT_SIGN_ALG), r_jwt_get_sign_alg(jwt)); ck_assert_int_eq(r_str_to_jwa_alg(CLIENT_ENC_ALG), r_jwt_get_enc_alg(jwt)); ck_assert_int_eq(r_str_to_jwa_enc(CLIENT_ENC), r_jwt_get_enc(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_verify_signature(jwt, NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_validate_claims(jwt, R_JWT_CLAIM_ISS, ISS, R_JWT_CLAIM_EXP, R_JWT_CLAIM_NOW, R_JWT_CLAIM_AUD, CLIENT_PUBKEY_ID, R_JWT_CLAIM_STR, "code", NULL, R_JWT_CLAIM_NOP)); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_jarm_client_pubkey_code_id_token_query_jwt) { struct _u_request req; struct _u_response resp; jwt_t * jwt; char * response; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE_CODE_ID_TOKEN "&nonce=nonce1234&client_id=" CLIENT_PUBKEY_ID "&redirect_uri=" CLIENT_PUBKEY_REDIRECT_ESCAPED "&scope=" SCOPE_LIST "&response_mode="RESPONSE_MODE_QUERY_JWT"&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "response="), NULL); response = o_strstr(u_map_get(resp.map_header, "Location"), "response=") + o_strlen("response="); if (o_strchr(response, '&') != NULL) { *o_strchr(response, '&') = '\0'; } ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(response, R_PARSE_NONE, 0)); ck_assert_int_eq(R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, r_jwt_get_type(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (const unsigned char *)pubkey_2_pem, sizeof(pubkey_2_pem))); ck_assert_int_eq(RHN_OK, r_jwt_add_enc_keys_pem_der(jwt, R_FORMAT_PEM, (const unsigned char *)privkey_1_pem, sizeof(privkey_1_pem), NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_decrypt_nested(jwt, NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_verify_signature(jwt, NULL, 0)); ck_assert_int_eq(r_str_to_jwa_alg(CLIENT_SIGN_ALG), r_jwt_get_sign_alg(jwt)); ck_assert_int_eq(r_str_to_jwa_alg(CLIENT_ENC_ALG), r_jwt_get_enc_alg(jwt)); ck_assert_int_eq(r_str_to_jwa_enc(CLIENT_ENC), r_jwt_get_enc(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_validate_claims(jwt, R_JWT_CLAIM_ISS, ISS, R_JWT_CLAIM_EXP, R_JWT_CLAIM_NOW, R_JWT_CLAIM_AUD, CLIENT_PUBKEY_ID, R_JWT_CLAIM_STR, "code", NULL, R_JWT_CLAIM_STR, "id_token", NULL, R_JWT_CLAIM_NOP)); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_jarm_client_pubkey_code_fragment_jwt) { struct _u_request req; struct _u_response resp; jwt_t * jwt; char * response; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE_CODE "&nonce=nonce1234&client_id=" CLIENT_PUBKEY_ID "&redirect_uri=" CLIENT_PUBKEY_REDIRECT_ESCAPED "&scope=" SCOPE_LIST "&response_mode="RESPONSE_MODE_FRAGMENT_JWT"&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "#response="), NULL); response = o_strstr(u_map_get(resp.map_header, "Location"), "#response=") + o_strlen("#response="); if (o_strchr(response, '&') != NULL) { *o_strchr(response, '&') = '\0'; } ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(response, R_PARSE_NONE, 0)); ck_assert_int_eq(R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, r_jwt_get_type(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_add_enc_keys_pem_der(jwt, R_FORMAT_PEM, (const unsigned char *)privkey_1_pem, sizeof(privkey_1_pem), NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (const unsigned char *)pubkey_2_pem, sizeof(pubkey_2_pem))); ck_assert_int_eq(RHN_OK, r_jwt_decrypt_nested(jwt, NULL, 0)); ck_assert_int_eq(r_str_to_jwa_alg(CLIENT_SIGN_ALG), r_jwt_get_sign_alg(jwt)); ck_assert_int_eq(r_str_to_jwa_alg(CLIENT_ENC_ALG), r_jwt_get_enc_alg(jwt)); ck_assert_int_eq(r_str_to_jwa_enc(CLIENT_ENC), r_jwt_get_enc(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_verify_signature(jwt, NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_validate_claims(jwt, R_JWT_CLAIM_ISS, ISS, R_JWT_CLAIM_EXP, R_JWT_CLAIM_NOW, R_JWT_CLAIM_AUD, CLIENT_PUBKEY_ID, R_JWT_CLAIM_STR, "code", NULL, R_JWT_CLAIM_NOP)); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_jarm_client_pubkey_code_id_token_fragment_jwt) { struct _u_request req; struct _u_response resp; jwt_t * jwt; char * response; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE_CODE_ID_TOKEN "&nonce=nonce1234&client_id=" CLIENT_PUBKEY_ID "&redirect_uri=" CLIENT_PUBKEY_REDIRECT_ESCAPED "&scope=" SCOPE_LIST "&response_mode="RESPONSE_MODE_FRAGMENT_JWT"&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "#response="), NULL); response = o_strstr(u_map_get(resp.map_header, "Location"), "#response=") + o_strlen("#response="); if (o_strchr(response, '&') != NULL) { *o_strchr(response, '&') = '\0'; } ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(response, R_PARSE_NONE, 0)); ck_assert_int_eq(R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, r_jwt_get_type(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (const unsigned char *)pubkey_2_pem, sizeof(pubkey_2_pem))); ck_assert_int_eq(RHN_OK, r_jwt_add_enc_keys_pem_der(jwt, R_FORMAT_PEM, (const unsigned char *)privkey_1_pem, sizeof(privkey_1_pem), NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_decrypt_nested(jwt, NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_verify_signature(jwt, NULL, 0)); ck_assert_int_eq(r_str_to_jwa_alg(CLIENT_SIGN_ALG), r_jwt_get_sign_alg(jwt)); ck_assert_int_eq(r_str_to_jwa_alg(CLIENT_ENC_ALG), r_jwt_get_enc_alg(jwt)); ck_assert_int_eq(r_str_to_jwa_enc(CLIENT_ENC), r_jwt_get_enc(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_validate_claims(jwt, R_JWT_CLAIM_ISS, ISS, R_JWT_CLAIM_EXP, R_JWT_CLAIM_NOW, R_JWT_CLAIM_AUD, CLIENT_PUBKEY_ID, R_JWT_CLAIM_STR, "code", NULL, R_JWT_CLAIM_STR, "id_token", NULL, R_JWT_CLAIM_NOP)); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_jarm_client_pubkey_code_form_post_jwt) { struct _u_request req; struct _u_response resp; jwt_t * jwt; char * response; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE_CODE "&nonce=nonce1234&client_id=" CLIENT_PUBKEY_ID "&redirect_uri=" CLIENT_PUBKEY_REDIRECT_ESCAPED "&scope=" SCOPE_LIST "&response_mode="RESPONSE_MODE_FORM_POST_JWT"&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(o_strnstr(resp.binary_body, "name=\"response\" value=\"", resp.binary_body_length), NULL); response = o_strnstr(resp.binary_body, "name=\"response\" value=\"", resp.binary_body_length) + o_strlen("name=\"response\" value=\""); if (o_strchr(response, '"') != NULL) { *o_strchr(response, '"') = '\0'; } ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(response, R_PARSE_NONE, 0)); ck_assert_int_eq(R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, r_jwt_get_type(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_add_enc_keys_pem_der(jwt, R_FORMAT_PEM, (const unsigned char *)privkey_1_pem, sizeof(privkey_1_pem), NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (const unsigned char *)pubkey_2_pem, sizeof(pubkey_2_pem))); ck_assert_int_eq(RHN_OK, r_jwt_decrypt_nested(jwt, NULL, 0)); ck_assert_int_eq(r_str_to_jwa_alg(CLIENT_SIGN_ALG), r_jwt_get_sign_alg(jwt)); ck_assert_int_eq(r_str_to_jwa_alg(CLIENT_ENC_ALG), r_jwt_get_enc_alg(jwt)); ck_assert_int_eq(r_str_to_jwa_enc(CLIENT_ENC), r_jwt_get_enc(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_verify_signature(jwt, NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_validate_claims(jwt, R_JWT_CLAIM_ISS, ISS, R_JWT_CLAIM_EXP, R_JWT_CLAIM_NOW, R_JWT_CLAIM_AUD, CLIENT_PUBKEY_ID, R_JWT_CLAIM_STR, "code", NULL, R_JWT_CLAIM_NOP)); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST START_TEST(test_oidc_jarm_client_pubkey_code_id_token_form_post_jwt) { struct _u_request req; struct _u_response resp; jwt_t * jwt; char * response; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=" RESPONSE_TYPE_CODE_ID_TOKEN "&nonce=nonce1234&client_id=" CLIENT_PUBKEY_ID "&redirect_uri=" CLIENT_PUBKEY_REDIRECT_ESCAPED "&scope=" SCOPE_LIST "&response_mode="RESPONSE_MODE_FORM_POST_JWT"&g_continue", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(o_strnstr(resp.binary_body, "name=\"response\" value=\"", resp.binary_body_length), NULL); response = o_strnstr(resp.binary_body, "name=\"response\" value=\"", resp.binary_body_length) + o_strlen("name=\"response\" value=\""); if (o_strchr(response, '"') != NULL) { *o_strchr(response, '"') = '\0'; } ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(response, R_PARSE_NONE, 0)); ck_assert_int_eq(R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, r_jwt_get_type(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (const unsigned char *)pubkey_2_pem, sizeof(pubkey_2_pem))); ck_assert_int_eq(RHN_OK, r_jwt_add_enc_keys_pem_der(jwt, R_FORMAT_PEM, (const unsigned char *)privkey_1_pem, sizeof(privkey_1_pem), NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_decrypt_nested(jwt, NULL, 0)); ck_assert_int_eq(RHN_OK, r_jwt_verify_signature(jwt, NULL, 0)); ck_assert_int_eq(r_str_to_jwa_alg(CLIENT_SIGN_ALG), r_jwt_get_sign_alg(jwt)); ck_assert_int_eq(r_str_to_jwa_alg(CLIENT_ENC_ALG), r_jwt_get_enc_alg(jwt)); ck_assert_int_eq(r_str_to_jwa_enc(CLIENT_ENC), r_jwt_get_enc(jwt)); ck_assert_int_eq(RHN_OK, r_jwt_validate_claims(jwt, R_JWT_CLAIM_ISS, ISS, R_JWT_CLAIM_EXP, R_JWT_CLAIM_NOW, R_JWT_CLAIM_AUD, CLIENT_PUBKEY_ID, R_JWT_CLAIM_STR, "code", NULL, R_JWT_CLAIM_STR, "id_token", NULL, R_JWT_CLAIM_NOP)); ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc jarm"); tc_core = tcase_create("test_oidc_jarm"); tcase_add_test(tc_core, test_oidc_jarm_add_plugin); tcase_add_test(tc_core, test_oidc_jarm_code_query_jwt); tcase_add_test(tc_core, test_oidc_jarm_code_id_token_query_jwt); tcase_add_test(tc_core, test_oidc_jarm_response_type_error_query_jwt); tcase_add_test(tc_core, test_oidc_jarm_client_invalid_query_jwt); tcase_add_test(tc_core, test_oidc_jarm_code_fragment_jwt); tcase_add_test(tc_core, test_oidc_jarm_code_id_token_fragment_jwt); tcase_add_test(tc_core, test_oidc_jarm_response_type_error_fragment_jwt); tcase_add_test(tc_core, test_oidc_jarm_client_invalid_fragment_jwt); tcase_add_test(tc_core, test_oidc_jarm_code_form_post_jwt); tcase_add_test(tc_core, test_oidc_jarm_code_id_token_form_post_jwt); tcase_add_test(tc_core, test_oidc_jarm_response_type_error_form_post_jwt); tcase_add_test(tc_core, test_oidc_jarm_client_invalid_form_post_jwt); tcase_add_test(tc_core, test_oidc_jarm_add_client_pubkey); tcase_add_test(tc_core, test_oidc_jarm_client_pubkey_code_query_jwt); tcase_add_test(tc_core, test_oidc_jarm_client_pubkey_code_id_token_query_jwt); tcase_add_test(tc_core, test_oidc_jarm_client_pubkey_code_fragment_jwt); tcase_add_test(tc_core, test_oidc_jarm_client_pubkey_code_id_token_fragment_jwt); tcase_add_test(tc_core, test_oidc_jarm_client_pubkey_code_form_post_jwt); tcase_add_test(tc_core, test_oidc_jarm_client_pubkey_code_id_token_form_post_jwt); tcase_add_test(tc_core, test_oidc_jarm_delete_client_pubkey); tcase_add_test(tc_core, test_oidc_jarm_delete_plugin); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); ulfius_init_request(&user_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "User %s authenticated", USER_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(user_req.map_header, "Cookie", cookie); o_free(cookie); } else { do_test = 0; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication user"); do_test = 0; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); } if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } run_simple_test(&user_req, "DELETE", SERVER_URI "/auth/", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL); ulfius_clean_request(&admin_req); ulfius_clean_request(&user_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_jwks_config.c000066400000000000000000003122131415646314000217260ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define SCOPE_LIST "openid" #define SCOPE_LIST_WITH_AUTH "scope1 openid" #define CLIENT "client4_id" #define CLIENT_PUBLIC "client1_id" #define CLIENT_ERROR "error" #define CLIENT_SECRET "secret" #define REDIRECT_URI "../../test-oidc.html?param=client4" #define REDIRECT_URI_PUBLIC "../../test-oidc.html?param=client1_cb1" #define NONCE_TEST "nonce5678" #define STATE_TEST "abcxyz" #define PLUGIN_MODULE "oidc" #define PLUGIN_NAME "oidc_jwks" #define PLUGIN_DISPLAY_NAME "oidc with jwks" #define PLUGIN_ISS "https://glewlwyd.tld" #define PLUGIN_CODE_DURATION 600 #define PLUGIN_REFRESH_TOKEN_DURATION 1209600 #define PLUGIN_ACCESS_TOKEN_DURATION 3600 #define CLIENT_AUTH_TOKEN_MAX_AGE 3600 #define CLIENT_PUBKEY_PARAM "pubkey" #define CLIENT_JWKS_PARAM "jwks" #define CLIENT_JWKS_URI_PARAM "jwks_uri" #define CLIENT_ID "client_kid" #define CLIENT_NAME "client with kid" #define CLIENT_REDIRECT "https://glewlwyd.local/" #define CLIENT_SCOPE "scope1" #define CLIENT_ENC "A128CBC-HS256" #define CLIENT_PUBKEY_ALG "RSA1_5" #define CLIENT_SECRET_ALG "A128GCMKW" #define KID_1 "key-1" #define KID_2 "key-2" #define KID_3 "key-3" #define KID_4 "key-4" const char jwks_privkey[] = "{\"keys\":[{\"kty\":\"RSA\",\"n\":\"AOidO2hPJFDK-jHdQ6p-SDGNAS3SbTCq1DN7Yv4kmClva" "5FtgLFIG8VG0hvn8RKN2kpqmNOa30KsOlYW9GqUCy6esFn0yqyNC_01IVY67qPIU5SRbCD88UXSfqsnhN" "sFgwU76OmpamqBGXUenZRrewNleNfYLJ6fNQO5n1rOa_UCcOaFqNLjjAcS9Z6e6h4Edlhz6ecYEVW6ZYF" "ODRNmyq_Pf0nZGgUjKXuAzEb8GdhiO99TcsLoc7RxTbfsvqLGofPXhY5EfWksNyeqJtINUEtMC78nADM6" "J_jFyeqBE3Tsqk1M6aQFo-8xy8kQ_bT7pdL9xh9w1UZ_kFg5pBMsaPk\",\"e\":\"AQAB\",\"d\":\"" "ANWOxOPXBPhH4bu18FGu7ojPc62l6ykrLPa26QN6hVhFOvShG-2mzhgoFO4Z23G1WhvIEdbz8NU9WbGAE" "ZVnpXx_pFhyLUCNndwx0xFfuYgUeueDO0pt7vSzdCeeeJK8VLWRcxxStahiLgUgvnts0dskZfWEOjLG59" "0rpemwadzdSGK44zgRhJQ1gr0PrsWoqZXWKT9OE5PwMQwL5cnN3GvXX2-PnxLUnjXzK9VN6nKl0dP67HK" "rBcrHZ4HXbLAoc1BjStidW1MmESNWPBkcnq-yKjmkrXzMbTH0ufJY1zYu6obdgLYDDN_QJgR4w3sXS2tN" "BRMj0XCmgFkAknD7mAE\",\"p\":\"AOoXE9yq9elQvRJ1bV0EjEfa1D1c0eubEHjStluxKA37QnpqAKL" "vMv44DiGdbFAewvkE8gP6Yjv-ls8ynvcJuYjcDyUnN2jjPaYKOopaqoejq0LQJtXGHiPUX5929y-5uDAu" "e0Mu5C2x3_uv0MXZS316-9oIK0ooszurC6tkvDTZ\",\"q\":\"AP5iyitBD-0YUnQ4VIakwWaOx6wtLt" "GuQgTu3o1hneo9rIY5MwvSSfT2PmkjGqFRzhQTv504roz8J2kSSzurV32JGer9H7db45afiDBl_UiEbds" "X_LKScOYGgWnm8yDm2YY_UzwbmD4H0xCDUYszbSSfYiVpBvZIe2sKpOK4YsEh\",\"qi\":\"AMULGD_n" "LWXAfOa_-dtQRP7yXENDQtKMwsTQrVrQgCynH0EVGWEUyPXA8PsHtzUiR_bLNGpdDgiMnfiEvXuv-Y_y9" "kzUCDi9RtA8tzTTq6EUR_YK0MJd4yiRYOnK0OAXTyTQPPlS-pUWk0J9l4p4uxTJe0m7J418naPLS9l7Xf" "kd\",\"dp\":\"AId1u0SimZLd6ctYsGR3UUXzV4X6xG72WF3ScTw2E9ujXiDAXoXqrTN29JZ3JkpmwqS" "fO_0ZUucst9BGlr6VnguYbBsvylyjwvTmTmHpfWzoRR5wnUhvUNmi94KrsPapHfCjtSh3ZgsbN2XJo6IZ" "0BlYpYzR1VsgmjcZD7Oqo05h\",\"dq\":\"ALHiWwUMJhrhmybyDQlqRGN3DGF15vtxI3FXqACtdkPKh" "M4HSY7GqjjFyLa0eXa9QaIAfUlvzX-BA_4RcNJ06mU6bglIn9kURH2baRyO9SK0mC1RBL_Kb3AqtGxdtz" "4Wr52UwpuRoFAgIJO3gFoayAOIAJWwb9HgtY0QkGSKE2SB\",\"kid\":\"" KID_1 "\",\"alg\":\"RS256\"},{\"kty\":\"RS" "A\",\"n\":\"AMZGRVyWHvHCkbGpGF6xdhKSjYwX1q5xtS-9_rATkpGyp5f-vCl9uUgdD0CZZIuuEvWsR" "vp1zt-JZVS9GrnoWBLZXzafHKO4pADMPPGlzaBsEmp4E5S7t6c4LGMgXExDoBs8jbp3TwbUeiyIsHyQzl" "Y5pfg8_2Stp-PtoOWBVpWvhp1uxVuvvIp5TfHK3q2q3Iziggvja-p_cLji95AAXOLBEfaRklMLPM0aMm9" "96-dX7Yq-cUO5ptCBEoRBcPlEJjFP9ZY_Hb0_3W8BIpkvf_zTcGsgIrcx17mIp9yRQcQ9pQNSa5kpk3nf" "v2BZ6tJHu8KfKsA3WesYOkMM_6VoFRk\",\"e\":\"AQAB\",\"d\":\"X3No5N-oWf6vIPYks0F_dAV1" "eyHmyegXr7opqB5kCxwUromFj_vkfKDwvMqbqtrMadTb_qtEQjzkuVyoRyNhcwGOYr9KjE7RNUkddb-6N" "CaS3wqbcpjEGw5h3_6uMFDF_RazrDbPppwXjOTpAa68hqoOCmljEda_4mx9br9LLZePk-g0L1YE62-ShM" "_ThI-azzrCNodKc37j6jVZ8x3gYACM2vtJTc-o7EOO-00bjyDrOKlJGLB9lGTSbSOij-Ilzmd_S58ykku" "I-t6VULFEjnPed46W_S7fsVkTBlHab6zGTFCMWSpj1_EVDXdH2IcGJ1UgrHTQyiYmmKUkIqv6mQ\",\"p" "\":\"ANqoPnFPan_AhngjrRrxtbT5aTyGcqreyrkE9g4zgAFdaSYXkUbIRQXcmp9KvoYk3rXr2SYolWWe" "IhpMIDKbUL6ZR6GXxQw_YcNVVY_haPCI0OyG-qMnVykYIcvgyAUeupZGT3gZaAdXR2BTxNqPepV-MIAIv" "8DU7upttwHu06-H\",\"q\":\"AOgi46IPh9oogaCnlnN7iex3VmbTy1T97vtM9qGIDy-kCipBkfUJbXk" "JnZ6LhSnoeeE5oKZn-CTriBXR4Bsjs7YiSUrEcNU5ZmBIs_56o6SyuniA71L3-bxfvHnwB5ysNFGKgKId" "7Skfx7u9f-AEGs_qA7kQ1w714DbFQYE2Pv5f\",\"qi\":\"eU35yvBcZiZ2Jzs8UU9CT1uJ9z5OjT-hg" "1QlkbQgPHQB1JhFsoMYqBsYpaUttwrCHbTJjW13-7fvEoFXS6RJm4qGg-LfY65LeCcF6djXr6XumqlJNM" "lFWF8aK5AZIiSZ_HUn5mdNRwbiONgmFwYjv3_nqBIfrXJ3C55FR9CcvD4\",\"dp\":\"AMFROlPH2Oq1" "9q1FLYjC5tnoIMioPb0gWK8X2ctYcPXD9nD9KS4hZhT2o6Xt2WCUPGsu57-65cr_8jq5z0Wu18aLki8mF" "crsRq0CRzF8IuF2tPBJrlKNN5xXf5nXVEBimKi_5QbTv4ut-KcLqOFrNP_yn0KzeYUtPUX6VnDZDEMF\"" ",\"dq\":\"AJnn_9pDx8N5VbBpTFPWlXRFDvDv_QClt5u_xEkGh2MDtIWdoaK_lGhKWslyIWDOtHgCGCQ" "cDKaVzk0BMD9uJUldZBCBO3nzK5Asw8G2F-crZHxep83vgRFGvBRwcuuKNMnXNT_G7aV1X5x2oGCq-Aff" "VIaPxrYxiG1nzO1ZRNyr\",\"kid\":\"" KID_2 "\",\"alg\":\"RS512\"},{\"kty\":\"EC\",\"x\":\"AJ6TXabOS7Blc_B" "NQVqQgp1nEwmatr8g9_HlPaoP4MPe\",\"y\":\"ALbKJWr4c4tksiv2IMWbfq09gghvuaR1pO2S_QjjX" "UtZ\",\"d\":\"Xk9lCflRiR6ErF1nqwSYhAFsOteufFotEi2z2xWDHks\",\"crv\":\"P-256\",\"k" "id\":\"" KID_3 "\",\"alg\":\"ES384\"},{\"kty\":\"oct\",\"k\":\"Kt5w3prEDTYJn8IveV1JtjXvtlAX0WRSEcEHxmND" "bP3aY760nHjA4VlCvxJQCYTSE1C1KObMmEnwjHrJzBedP-rqjn0SlzovvYyYWJL7Ujukqg4lhQxkLO5FZ" "jBNy1_i1-J3jPbP-DccD31-Kbxxmudqzd9LFVobDGoNvf-DdkQ\",\"kid\":\"" KID_4 "\",\"alg\":\"HS256\"}]}"; const char jwks_privkey_alg_missing[] = "{\"keys\":[{\"kty\":\"RSA\",\"n\":\"AOidO2hPJFDK-jHdQ6p-SDGNAS3SbTCq1DN7Yv4kmClva" "5FtgLFIG8VG0hvn8RKN2kpqmNOa30KsOlYW9GqUCy6esFn0yqyNC_01IVY67qPIU5SRbCD88UXSfqsnhN" "sFgwU76OmpamqBGXUenZRrewNleNfYLJ6fNQO5n1rOa_UCcOaFqNLjjAcS9Z6e6h4Edlhz6ecYEVW6ZYF" "ODRNmyq_Pf0nZGgUjKXuAzEb8GdhiO99TcsLoc7RxTbfsvqLGofPXhY5EfWksNyeqJtINUEtMC78nADM6" "J_jFyeqBE3Tsqk1M6aQFo-8xy8kQ_bT7pdL9xh9w1UZ_kFg5pBMsaPk\",\"e\":\"AQAB\",\"d\":\"" "ANWOxOPXBPhH4bu18FGu7ojPc62l6ykrLPa26QN6hVhFOvShG-2mzhgoFO4Z23G1WhvIEdbz8NU9WbGAE" "ZVnpXx_pFhyLUCNndwx0xFfuYgUeueDO0pt7vSzdCeeeJK8VLWRcxxStahiLgUgvnts0dskZfWEOjLG59" "0rpemwadzdSGK44zgRhJQ1gr0PrsWoqZXWKT9OE5PwMQwL5cnN3GvXX2-PnxLUnjXzK9VN6nKl0dP67HK" "rBcrHZ4HXbLAoc1BjStidW1MmESNWPBkcnq-yKjmkrXzMbTH0ufJY1zYu6obdgLYDDN_QJgR4w3sXS2tN" "BRMj0XCmgFkAknD7mAE\",\"p\":\"AOoXE9yq9elQvRJ1bV0EjEfa1D1c0eubEHjStluxKA37QnpqAKL" "vMv44DiGdbFAewvkE8gP6Yjv-ls8ynvcJuYjcDyUnN2jjPaYKOopaqoejq0LQJtXGHiPUX5929y-5uDAu" "e0Mu5C2x3_uv0MXZS316-9oIK0ooszurC6tkvDTZ\",\"q\":\"AP5iyitBD-0YUnQ4VIakwWaOx6wtLt" "GuQgTu3o1hneo9rIY5MwvSSfT2PmkjGqFRzhQTv504roz8J2kSSzurV32JGer9H7db45afiDBl_UiEbds" "X_LKScOYGgWnm8yDm2YY_UzwbmD4H0xCDUYszbSSfYiVpBvZIe2sKpOK4YsEh\",\"qi\":\"AMULGD_n" "LWXAfOa_-dtQRP7yXENDQtKMwsTQrVrQgCynH0EVGWEUyPXA8PsHtzUiR_bLNGpdDgiMnfiEvXuv-Y_y9" "kzUCDi9RtA8tzTTq6EUR_YK0MJd4yiRYOnK0OAXTyTQPPlS-pUWk0J9l4p4uxTJe0m7J418naPLS9l7Xf" "kd\",\"dp\":\"AId1u0SimZLd6ctYsGR3UUXzV4X6xG72WF3ScTw2E9ujXiDAXoXqrTN29JZ3JkpmwqS" "fO_0ZUucst9BGlr6VnguYbBsvylyjwvTmTmHpfWzoRR5wnUhvUNmi94KrsPapHfCjtSh3ZgsbN2XJo6IZ" "0BlYpYzR1VsgmjcZD7Oqo05h\",\"dq\":\"ALHiWwUMJhrhmybyDQlqRGN3DGF15vtxI3FXqACtdkPKh" "M4HSY7GqjjFyLa0eXa9QaIAfUlvzX-BA_4RcNJ06mU6bglIn9kURH2baRyO9SK0mC1RBL_Kb3AqtGxdtz" "4Wr52UwpuRoFAgIJO3gFoayAOIAJWwb9HgtY0QkGSKE2SB\",\"kid\":\"" KID_1 "\"},{\"kty\":\"RS" "A\",\"n\":\"AMZGRVyWHvHCkbGpGF6xdhKSjYwX1q5xtS-9_rATkpGyp5f-vCl9uUgdD0CZZIuuEvWsR" "vp1zt-JZVS9GrnoWBLZXzafHKO4pADMPPGlzaBsEmp4E5S7t6c4LGMgXExDoBs8jbp3TwbUeiyIsHyQzl" "Y5pfg8_2Stp-PtoOWBVpWvhp1uxVuvvIp5TfHK3q2q3Iziggvja-p_cLji95AAXOLBEfaRklMLPM0aMm9" "96-dX7Yq-cUO5ptCBEoRBcPlEJjFP9ZY_Hb0_3W8BIpkvf_zTcGsgIrcx17mIp9yRQcQ9pQNSa5kpk3nf" "v2BZ6tJHu8KfKsA3WesYOkMM_6VoFRk\",\"e\":\"AQAB\",\"d\":\"X3No5N-oWf6vIPYks0F_dAV1" "eyHmyegXr7opqB5kCxwUromFj_vkfKDwvMqbqtrMadTb_qtEQjzkuVyoRyNhcwGOYr9KjE7RNUkddb-6N" "CaS3wqbcpjEGw5h3_6uMFDF_RazrDbPppwXjOTpAa68hqoOCmljEda_4mx9br9LLZePk-g0L1YE62-ShM" "_ThI-azzrCNodKc37j6jVZ8x3gYACM2vtJTc-o7EOO-00bjyDrOKlJGLB9lGTSbSOij-Ilzmd_S58ykku" "I-t6VULFEjnPed46W_S7fsVkTBlHab6zGTFCMWSpj1_EVDXdH2IcGJ1UgrHTQyiYmmKUkIqv6mQ\",\"p" "\":\"ANqoPnFPan_AhngjrRrxtbT5aTyGcqreyrkE9g4zgAFdaSYXkUbIRQXcmp9KvoYk3rXr2SYolWWe" "IhpMIDKbUL6ZR6GXxQw_YcNVVY_haPCI0OyG-qMnVykYIcvgyAUeupZGT3gZaAdXR2BTxNqPepV-MIAIv" "8DU7upttwHu06-H\",\"q\":\"AOgi46IPh9oogaCnlnN7iex3VmbTy1T97vtM9qGIDy-kCipBkfUJbXk" "JnZ6LhSnoeeE5oKZn-CTriBXR4Bsjs7YiSUrEcNU5ZmBIs_56o6SyuniA71L3-bxfvHnwB5ysNFGKgKId" "7Skfx7u9f-AEGs_qA7kQ1w714DbFQYE2Pv5f\",\"qi\":\"eU35yvBcZiZ2Jzs8UU9CT1uJ9z5OjT-hg" "1QlkbQgPHQB1JhFsoMYqBsYpaUttwrCHbTJjW13-7fvEoFXS6RJm4qGg-LfY65LeCcF6djXr6XumqlJNM" "lFWF8aK5AZIiSZ_HUn5mdNRwbiONgmFwYjv3_nqBIfrXJ3C55FR9CcvD4\",\"dp\":\"AMFROlPH2Oq1" "9q1FLYjC5tnoIMioPb0gWK8X2ctYcPXD9nD9KS4hZhT2o6Xt2WCUPGsu57-65cr_8jq5z0Wu18aLki8mF" "crsRq0CRzF8IuF2tPBJrlKNN5xXf5nXVEBimKi_5QbTv4ut-KcLqOFrNP_yn0KzeYUtPUX6VnDZDEMF\"" ",\"dq\":\"AJnn_9pDx8N5VbBpTFPWlXRFDvDv_QClt5u_xEkGh2MDtIWdoaK_lGhKWslyIWDOtHgCGCQ" "cDKaVzk0BMD9uJUldZBCBO3nzK5Asw8G2F-crZHxep83vgRFGvBRwcuuKNMnXNT_G7aV1X5x2oGCq-Aff" "VIaPxrYxiG1nzO1ZRNyr\",\"kid\":\"" KID_2 "\",\"alg\":\"RS512\"},{\"kty\":\"EC\",\"x\":\"AJ6TXabOS7Blc_B" "NQVqQgp1nEwmatr8g9_HlPaoP4MPe\",\"y\":\"ALbKJWr4c4tksiv2IMWbfq09gghvuaR1pO2S_QjjX" "UtZ\",\"d\":\"Xk9lCflRiR6ErF1nqwSYhAFsOteufFotEi2z2xWDHks\",\"crv\":\"P-256\",\"k" "id\":\"" KID_3 "\",\"alg\":\"ES384\"},{\"kty\":\"oct\",\"k\":\"Kt5w3prEDTYJn8IveV1JtjXvtlAX0WRSEcEHxmND" "bP3aY760nHjA4VlCvxJQCYTSE1C1KObMmEnwjHrJzBedP-rqjn0SlzovvYyYWJL7Ujukqg4lhQxkLO5FZ" "jBNy1_i1-J3jPbP-DccD31-Kbxxmudqzd9LFVobDGoNvf-DdkQ\",\"kid\":\"" KID_4 "\",\"alg\":\"HS256\"}]}"; const char jwks_privkey_alg_invalid[] = "{\"keys\":[{\"kty\":\"RSA\",\"n\":\"AOidO2hPJFDK-jHdQ6p-SDGNAS3SbTCq1DN7Yv4kmClva" "5FtgLFIG8VG0hvn8RKN2kpqmNOa30KsOlYW9GqUCy6esFn0yqyNC_01IVY67qPIU5SRbCD88UXSfqsnhN" "sFgwU76OmpamqBGXUenZRrewNleNfYLJ6fNQO5n1rOa_UCcOaFqNLjjAcS9Z6e6h4Edlhz6ecYEVW6ZYF" "ODRNmyq_Pf0nZGgUjKXuAzEb8GdhiO99TcsLoc7RxTbfsvqLGofPXhY5EfWksNyeqJtINUEtMC78nADM6" "J_jFyeqBE3Tsqk1M6aQFo-8xy8kQ_bT7pdL9xh9w1UZ_kFg5pBMsaPk\",\"e\":\"AQAB\",\"d\":\"" "ANWOxOPXBPhH4bu18FGu7ojPc62l6ykrLPa26QN6hVhFOvShG-2mzhgoFO4Z23G1WhvIEdbz8NU9WbGAE" "ZVnpXx_pFhyLUCNndwx0xFfuYgUeueDO0pt7vSzdCeeeJK8VLWRcxxStahiLgUgvnts0dskZfWEOjLG59" "0rpemwadzdSGK44zgRhJQ1gr0PrsWoqZXWKT9OE5PwMQwL5cnN3GvXX2-PnxLUnjXzK9VN6nKl0dP67HK" "rBcrHZ4HXbLAoc1BjStidW1MmESNWPBkcnq-yKjmkrXzMbTH0ufJY1zYu6obdgLYDDN_QJgR4w3sXS2tN" "BRMj0XCmgFkAknD7mAE\",\"p\":\"AOoXE9yq9elQvRJ1bV0EjEfa1D1c0eubEHjStluxKA37QnpqAKL" "vMv44DiGdbFAewvkE8gP6Yjv-ls8ynvcJuYjcDyUnN2jjPaYKOopaqoejq0LQJtXGHiPUX5929y-5uDAu" "e0Mu5C2x3_uv0MXZS316-9oIK0ooszurC6tkvDTZ\",\"q\":\"AP5iyitBD-0YUnQ4VIakwWaOx6wtLt" "GuQgTu3o1hneo9rIY5MwvSSfT2PmkjGqFRzhQTv504roz8J2kSSzurV32JGer9H7db45afiDBl_UiEbds" "X_LKScOYGgWnm8yDm2YY_UzwbmD4H0xCDUYszbSSfYiVpBvZIe2sKpOK4YsEh\",\"qi\":\"AMULGD_n" "LWXAfOa_-dtQRP7yXENDQtKMwsTQrVrQgCynH0EVGWEUyPXA8PsHtzUiR_bLNGpdDgiMnfiEvXuv-Y_y9" "kzUCDi9RtA8tzTTq6EUR_YK0MJd4yiRYOnK0OAXTyTQPPlS-pUWk0J9l4p4uxTJe0m7J418naPLS9l7Xf" "kd\",\"dp\":\"AId1u0SimZLd6ctYsGR3UUXzV4X6xG72WF3ScTw2E9ujXiDAXoXqrTN29JZ3JkpmwqS" "fO_0ZUucst9BGlr6VnguYbBsvylyjwvTmTmHpfWzoRR5wnUhvUNmi94KrsPapHfCjtSh3ZgsbN2XJo6IZ" "0BlYpYzR1VsgmjcZD7Oqo05h\",\"dq\":\"ALHiWwUMJhrhmybyDQlqRGN3DGF15vtxI3FXqACtdkPKh" "M4HSY7GqjjFyLa0eXa9QaIAfUlvzX-BA_4RcNJ06mU6bglIn9kURH2baRyO9SK0mC1RBL_Kb3AqtGxdtz" "4Wr52UwpuRoFAgIJO3gFoayAOIAJWwb9HgtY0QkGSKE2SB\",\"kid\":\"" KID_1 "\",\"alg\":\"error\"},{\"kty\":\"RS" "A\",\"n\":\"AMZGRVyWHvHCkbGpGF6xdhKSjYwX1q5xtS-9_rATkpGyp5f-vCl9uUgdD0CZZIuuEvWsR" "vp1zt-JZVS9GrnoWBLZXzafHKO4pADMPPGlzaBsEmp4E5S7t6c4LGMgXExDoBs8jbp3TwbUeiyIsHyQzl" "Y5pfg8_2Stp-PtoOWBVpWvhp1uxVuvvIp5TfHK3q2q3Iziggvja-p_cLji95AAXOLBEfaRklMLPM0aMm9" "96-dX7Yq-cUO5ptCBEoRBcPlEJjFP9ZY_Hb0_3W8BIpkvf_zTcGsgIrcx17mIp9yRQcQ9pQNSa5kpk3nf" "v2BZ6tJHu8KfKsA3WesYOkMM_6VoFRk\",\"e\":\"AQAB\",\"d\":\"X3No5N-oWf6vIPYks0F_dAV1" "eyHmyegXr7opqB5kCxwUromFj_vkfKDwvMqbqtrMadTb_qtEQjzkuVyoRyNhcwGOYr9KjE7RNUkddb-6N" "CaS3wqbcpjEGw5h3_6uMFDF_RazrDbPppwXjOTpAa68hqoOCmljEda_4mx9br9LLZePk-g0L1YE62-ShM" "_ThI-azzrCNodKc37j6jVZ8x3gYACM2vtJTc-o7EOO-00bjyDrOKlJGLB9lGTSbSOij-Ilzmd_S58ykku" "I-t6VULFEjnPed46W_S7fsVkTBlHab6zGTFCMWSpj1_EVDXdH2IcGJ1UgrHTQyiYmmKUkIqv6mQ\",\"p" "\":\"ANqoPnFPan_AhngjrRrxtbT5aTyGcqreyrkE9g4zgAFdaSYXkUbIRQXcmp9KvoYk3rXr2SYolWWe" "IhpMIDKbUL6ZR6GXxQw_YcNVVY_haPCI0OyG-qMnVykYIcvgyAUeupZGT3gZaAdXR2BTxNqPepV-MIAIv" "8DU7upttwHu06-H\",\"q\":\"AOgi46IPh9oogaCnlnN7iex3VmbTy1T97vtM9qGIDy-kCipBkfUJbXk" "JnZ6LhSnoeeE5oKZn-CTriBXR4Bsjs7YiSUrEcNU5ZmBIs_56o6SyuniA71L3-bxfvHnwB5ysNFGKgKId" "7Skfx7u9f-AEGs_qA7kQ1w714DbFQYE2Pv5f\",\"qi\":\"eU35yvBcZiZ2Jzs8UU9CT1uJ9z5OjT-hg" "1QlkbQgPHQB1JhFsoMYqBsYpaUttwrCHbTJjW13-7fvEoFXS6RJm4qGg-LfY65LeCcF6djXr6XumqlJNM" "lFWF8aK5AZIiSZ_HUn5mdNRwbiONgmFwYjv3_nqBIfrXJ3C55FR9CcvD4\",\"dp\":\"AMFROlPH2Oq1" "9q1FLYjC5tnoIMioPb0gWK8X2ctYcPXD9nD9KS4hZhT2o6Xt2WCUPGsu57-65cr_8jq5z0Wu18aLki8mF" "crsRq0CRzF8IuF2tPBJrlKNN5xXf5nXVEBimKi_5QbTv4ut-KcLqOFrNP_yn0KzeYUtPUX6VnDZDEMF\"" ",\"dq\":\"AJnn_9pDx8N5VbBpTFPWlXRFDvDv_QClt5u_xEkGh2MDtIWdoaK_lGhKWslyIWDOtHgCGCQ" "cDKaVzk0BMD9uJUldZBCBO3nzK5Asw8G2F-crZHxep83vgRFGvBRwcuuKNMnXNT_G7aV1X5x2oGCq-Aff" "VIaPxrYxiG1nzO1ZRNyr\",\"kid\":\"" KID_2 "\",\"alg\":\"RS512\"},{\"kty\":\"EC\",\"x\":\"AJ6TXabOS7Blc_B" "NQVqQgp1nEwmatr8g9_HlPaoP4MPe\",\"y\":\"ALbKJWr4c4tksiv2IMWbfq09gghvuaR1pO2S_QjjX" "UtZ\",\"d\":\"Xk9lCflRiR6ErF1nqwSYhAFsOteufFotEi2z2xWDHks\",\"crv\":\"P-256\",\"k" "id\":\"" KID_3 "\",\"alg\":\"ES384\"},{\"kty\":\"oct\",\"k\":\"Kt5w3prEDTYJn8IveV1JtjXvtlAX0WRSEcEHxmND" "bP3aY760nHjA4VlCvxJQCYTSE1C1KObMmEnwjHrJzBedP-rqjn0SlzovvYyYWJL7Ujukqg4lhQxkLO5FZ" "jBNy1_i1-J3jPbP-DccD31-Kbxxmudqzd9LFVobDGoNvf-DdkQ\",\"kid\":\"" KID_4 "\",\"alg\":\"HS256\"}]}"; const char jwks_privkey_kid_missing[] = "{\"keys\":[{\"kty\":\"RSA\",\"n\":\"AOidO2hPJFDK-jHdQ6p-SDGNAS3SbTCq1DN7Yv4kmClva" "5FtgLFIG8VG0hvn8RKN2kpqmNOa30KsOlYW9GqUCy6esFn0yqyNC_01IVY67qPIU5SRbCD88UXSfqsnhN" "sFgwU76OmpamqBGXUenZRrewNleNfYLJ6fNQO5n1rOa_UCcOaFqNLjjAcS9Z6e6h4Edlhz6ecYEVW6ZYF" "ODRNmyq_Pf0nZGgUjKXuAzEb8GdhiO99TcsLoc7RxTbfsvqLGofPXhY5EfWksNyeqJtINUEtMC78nADM6" "J_jFyeqBE3Tsqk1M6aQFo-8xy8kQ_bT7pdL9xh9w1UZ_kFg5pBMsaPk\",\"e\":\"AQAB\",\"d\":\"" "ANWOxOPXBPhH4bu18FGu7ojPc62l6ykrLPa26QN6hVhFOvShG-2mzhgoFO4Z23G1WhvIEdbz8NU9WbGAE" "ZVnpXx_pFhyLUCNndwx0xFfuYgUeueDO0pt7vSzdCeeeJK8VLWRcxxStahiLgUgvnts0dskZfWEOjLG59" "0rpemwadzdSGK44zgRhJQ1gr0PrsWoqZXWKT9OE5PwMQwL5cnN3GvXX2-PnxLUnjXzK9VN6nKl0dP67HK" "rBcrHZ4HXbLAoc1BjStidW1MmESNWPBkcnq-yKjmkrXzMbTH0ufJY1zYu6obdgLYDDN_QJgR4w3sXS2tN" "BRMj0XCmgFkAknD7mAE\",\"p\":\"AOoXE9yq9elQvRJ1bV0EjEfa1D1c0eubEHjStluxKA37QnpqAKL" "vMv44DiGdbFAewvkE8gP6Yjv-ls8ynvcJuYjcDyUnN2jjPaYKOopaqoejq0LQJtXGHiPUX5929y-5uDAu" "e0Mu5C2x3_uv0MXZS316-9oIK0ooszurC6tkvDTZ\",\"q\":\"AP5iyitBD-0YUnQ4VIakwWaOx6wtLt" "GuQgTu3o1hneo9rIY5MwvSSfT2PmkjGqFRzhQTv504roz8J2kSSzurV32JGer9H7db45afiDBl_UiEbds" "X_LKScOYGgWnm8yDm2YY_UzwbmD4H0xCDUYszbSSfYiVpBvZIe2sKpOK4YsEh\",\"qi\":\"AMULGD_n" "LWXAfOa_-dtQRP7yXENDQtKMwsTQrVrQgCynH0EVGWEUyPXA8PsHtzUiR_bLNGpdDgiMnfiEvXuv-Y_y9" "kzUCDi9RtA8tzTTq6EUR_YK0MJd4yiRYOnK0OAXTyTQPPlS-pUWk0J9l4p4uxTJe0m7J418naPLS9l7Xf" "kd\",\"dp\":\"AId1u0SimZLd6ctYsGR3UUXzV4X6xG72WF3ScTw2E9ujXiDAXoXqrTN29JZ3JkpmwqS" "fO_0ZUucst9BGlr6VnguYbBsvylyjwvTmTmHpfWzoRR5wnUhvUNmi94KrsPapHfCjtSh3ZgsbN2XJo6IZ" "0BlYpYzR1VsgmjcZD7Oqo05h\",\"dq\":\"ALHiWwUMJhrhmybyDQlqRGN3DGF15vtxI3FXqACtdkPKh" "M4HSY7GqjjFyLa0eXa9QaIAfUlvzX-BA_4RcNJ06mU6bglIn9kURH2baRyO9SK0mC1RBL_Kb3AqtGxdtz" "4Wr52UwpuRoFAgIJO3gFoayAOIAJWwb9HgtY0QkGSKE2SB\",\"alg\":\"RS256\"},{\"kty\":\"RS" "A\",\"n\":\"AMZGRVyWHvHCkbGpGF6xdhKSjYwX1q5xtS-9_rATkpGyp5f-vCl9uUgdD0CZZIuuEvWsR" "vp1zt-JZVS9GrnoWBLZXzafHKO4pADMPPGlzaBsEmp4E5S7t6c4LGMgXExDoBs8jbp3TwbUeiyIsHyQzl" "Y5pfg8_2Stp-PtoOWBVpWvhp1uxVuvvIp5TfHK3q2q3Iziggvja-p_cLji95AAXOLBEfaRklMLPM0aMm9" "96-dX7Yq-cUO5ptCBEoRBcPlEJjFP9ZY_Hb0_3W8BIpkvf_zTcGsgIrcx17mIp9yRQcQ9pQNSa5kpk3nf" "v2BZ6tJHu8KfKsA3WesYOkMM_6VoFRk\",\"e\":\"AQAB\",\"d\":\"X3No5N-oWf6vIPYks0F_dAV1" "eyHmyegXr7opqB5kCxwUromFj_vkfKDwvMqbqtrMadTb_qtEQjzkuVyoRyNhcwGOYr9KjE7RNUkddb-6N" "CaS3wqbcpjEGw5h3_6uMFDF_RazrDbPppwXjOTpAa68hqoOCmljEda_4mx9br9LLZePk-g0L1YE62-ShM" "_ThI-azzrCNodKc37j6jVZ8x3gYACM2vtJTc-o7EOO-00bjyDrOKlJGLB9lGTSbSOij-Ilzmd_S58ykku" "I-t6VULFEjnPed46W_S7fsVkTBlHab6zGTFCMWSpj1_EVDXdH2IcGJ1UgrHTQyiYmmKUkIqv6mQ\",\"p" "\":\"ANqoPnFPan_AhngjrRrxtbT5aTyGcqreyrkE9g4zgAFdaSYXkUbIRQXcmp9KvoYk3rXr2SYolWWe" "IhpMIDKbUL6ZR6GXxQw_YcNVVY_haPCI0OyG-qMnVykYIcvgyAUeupZGT3gZaAdXR2BTxNqPepV-MIAIv" "8DU7upttwHu06-H\",\"q\":\"AOgi46IPh9oogaCnlnN7iex3VmbTy1T97vtM9qGIDy-kCipBkfUJbXk" "JnZ6LhSnoeeE5oKZn-CTriBXR4Bsjs7YiSUrEcNU5ZmBIs_56o6SyuniA71L3-bxfvHnwB5ysNFGKgKId" "7Skfx7u9f-AEGs_qA7kQ1w714DbFQYE2Pv5f\",\"qi\":\"eU35yvBcZiZ2Jzs8UU9CT1uJ9z5OjT-hg" "1QlkbQgPHQB1JhFsoMYqBsYpaUttwrCHbTJjW13-7fvEoFXS6RJm4qGg-LfY65LeCcF6djXr6XumqlJNM" "lFWF8aK5AZIiSZ_HUn5mdNRwbiONgmFwYjv3_nqBIfrXJ3C55FR9CcvD4\",\"dp\":\"AMFROlPH2Oq1" "9q1FLYjC5tnoIMioPb0gWK8X2ctYcPXD9nD9KS4hZhT2o6Xt2WCUPGsu57-65cr_8jq5z0Wu18aLki8mF" "crsRq0CRzF8IuF2tPBJrlKNN5xXf5nXVEBimKi_5QbTv4ut-KcLqOFrNP_yn0KzeYUtPUX6VnDZDEMF\"" ",\"dq\":\"AJnn_9pDx8N5VbBpTFPWlXRFDvDv_QClt5u_xEkGh2MDtIWdoaK_lGhKWslyIWDOtHgCGCQ" "cDKaVzk0BMD9uJUldZBCBO3nzK5Asw8G2F-crZHxep83vgRFGvBRwcuuKNMnXNT_G7aV1X5x2oGCq-Aff" "VIaPxrYxiG1nzO1ZRNyr\",\"kid\":\"" KID_2 "\",\"alg\":\"RS512\"},{\"kty\":\"EC\",\"x\":\"AJ6TXabOS7Blc_B" "NQVqQgp1nEwmatr8g9_HlPaoP4MPe\",\"y\":\"ALbKJWr4c4tksiv2IMWbfq09gghvuaR1pO2S_QjjX" "UtZ\",\"d\":\"Xk9lCflRiR6ErF1nqwSYhAFsOteufFotEi2z2xWDHks\",\"crv\":\"P-256\",\"k" "id\":\"" KID_3 "\",\"alg\":\"ES384\"},{\"kty\":\"oct\",\"k\":\"Kt5w3prEDTYJn8IveV1JtjXvtlAX0WRSEcEHxmND" "bP3aY760nHjA4VlCvxJQCYTSE1C1KObMmEnwjHrJzBedP-rqjn0SlzovvYyYWJL7Ujukqg4lhQxkLO5FZ" "jBNy1_i1-J3jPbP-DccD31-Kbxxmudqzd9LFVobDGoNvf-DdkQ\",\"kid\":\"" KID_4 "\",\"alg\":\"HS256\"}]}"; const char jwks_privkey_kid_invalid[] = "{\"keys\":[{\"kty\":\"RSA\",\"n\":\"AOidO2hPJFDK-jHdQ6p-SDGNAS3SbTCq1DN7Yv4kmClva" "5FtgLFIG8VG0hvn8RKN2kpqmNOa30KsOlYW9GqUCy6esFn0yqyNC_01IVY67qPIU5SRbCD88UXSfqsnhN" "sFgwU76OmpamqBGXUenZRrewNleNfYLJ6fNQO5n1rOa_UCcOaFqNLjjAcS9Z6e6h4Edlhz6ecYEVW6ZYF" "ODRNmyq_Pf0nZGgUjKXuAzEb8GdhiO99TcsLoc7RxTbfsvqLGofPXhY5EfWksNyeqJtINUEtMC78nADM6" "J_jFyeqBE3Tsqk1M6aQFo-8xy8kQ_bT7pdL9xh9w1UZ_kFg5pBMsaPk\",\"e\":\"AQAB\",\"d\":\"" "ANWOxOPXBPhH4bu18FGu7ojPc62l6ykrLPa26QN6hVhFOvShG-2mzhgoFO4Z23G1WhvIEdbz8NU9WbGAE" "ZVnpXx_pFhyLUCNndwx0xFfuYgUeueDO0pt7vSzdCeeeJK8VLWRcxxStahiLgUgvnts0dskZfWEOjLG59" "0rpemwadzdSGK44zgRhJQ1gr0PrsWoqZXWKT9OE5PwMQwL5cnN3GvXX2-PnxLUnjXzK9VN6nKl0dP67HK" "rBcrHZ4HXbLAoc1BjStidW1MmESNWPBkcnq-yKjmkrXzMbTH0ufJY1zYu6obdgLYDDN_QJgR4w3sXS2tN" "BRMj0XCmgFkAknD7mAE\",\"p\":\"AOoXE9yq9elQvRJ1bV0EjEfa1D1c0eubEHjStluxKA37QnpqAKL" "vMv44DiGdbFAewvkE8gP6Yjv-ls8ynvcJuYjcDyUnN2jjPaYKOopaqoejq0LQJtXGHiPUX5929y-5uDAu" "e0Mu5C2x3_uv0MXZS316-9oIK0ooszurC6tkvDTZ\",\"q\":\"AP5iyitBD-0YUnQ4VIakwWaOx6wtLt" "GuQgTu3o1hneo9rIY5MwvSSfT2PmkjGqFRzhQTv504roz8J2kSSzurV32JGer9H7db45afiDBl_UiEbds" "X_LKScOYGgWnm8yDm2YY_UzwbmD4H0xCDUYszbSSfYiVpBvZIe2sKpOK4YsEh\",\"qi\":\"AMULGD_n" "LWXAfOa_-dtQRP7yXENDQtKMwsTQrVrQgCynH0EVGWEUyPXA8PsHtzUiR_bLNGpdDgiMnfiEvXuv-Y_y9" "kzUCDi9RtA8tzTTq6EUR_YK0MJd4yiRYOnK0OAXTyTQPPlS-pUWk0J9l4p4uxTJe0m7J418naPLS9l7Xf" "kd\",\"dp\":\"AId1u0SimZLd6ctYsGR3UUXzV4X6xG72WF3ScTw2E9ujXiDAXoXqrTN29JZ3JkpmwqS" "fO_0ZUucst9BGlr6VnguYbBsvylyjwvTmTmHpfWzoRR5wnUhvUNmi94KrsPapHfCjtSh3ZgsbN2XJo6IZ" "0BlYpYzR1VsgmjcZD7Oqo05h\",\"dq\":\"ALHiWwUMJhrhmybyDQlqRGN3DGF15vtxI3FXqACtdkPKh" "M4HSY7GqjjFyLa0eXa9QaIAfUlvzX-BA_4RcNJ06mU6bglIn9kURH2baRyO9SK0mC1RBL_Kb3AqtGxdtz" "4Wr52UwpuRoFAgIJO3gFoayAOIAJWwb9HgtY0QkGSKE2SB\",\"kid\":42,\"alg\":\"RS256\"},{\"kty\":\"RS" "A\",\"n\":\"AMZGRVyWHvHCkbGpGF6xdhKSjYwX1q5xtS-9_rATkpGyp5f-vCl9uUgdD0CZZIuuEvWsR" "vp1zt-JZVS9GrnoWBLZXzafHKO4pADMPPGlzaBsEmp4E5S7t6c4LGMgXExDoBs8jbp3TwbUeiyIsHyQzl" "Y5pfg8_2Stp-PtoOWBVpWvhp1uxVuvvIp5TfHK3q2q3Iziggvja-p_cLji95AAXOLBEfaRklMLPM0aMm9" "96-dX7Yq-cUO5ptCBEoRBcPlEJjFP9ZY_Hb0_3W8BIpkvf_zTcGsgIrcx17mIp9yRQcQ9pQNSa5kpk3nf" "v2BZ6tJHu8KfKsA3WesYOkMM_6VoFRk\",\"e\":\"AQAB\",\"d\":\"X3No5N-oWf6vIPYks0F_dAV1" "eyHmyegXr7opqB5kCxwUromFj_vkfKDwvMqbqtrMadTb_qtEQjzkuVyoRyNhcwGOYr9KjE7RNUkddb-6N" "CaS3wqbcpjEGw5h3_6uMFDF_RazrDbPppwXjOTpAa68hqoOCmljEda_4mx9br9LLZePk-g0L1YE62-ShM" "_ThI-azzrCNodKc37j6jVZ8x3gYACM2vtJTc-o7EOO-00bjyDrOKlJGLB9lGTSbSOij-Ilzmd_S58ykku" "I-t6VULFEjnPed46W_S7fsVkTBlHab6zGTFCMWSpj1_EVDXdH2IcGJ1UgrHTQyiYmmKUkIqv6mQ\",\"p" "\":\"ANqoPnFPan_AhngjrRrxtbT5aTyGcqreyrkE9g4zgAFdaSYXkUbIRQXcmp9KvoYk3rXr2SYolWWe" "IhpMIDKbUL6ZR6GXxQw_YcNVVY_haPCI0OyG-qMnVykYIcvgyAUeupZGT3gZaAdXR2BTxNqPepV-MIAIv" "8DU7upttwHu06-H\",\"q\":\"AOgi46IPh9oogaCnlnN7iex3VmbTy1T97vtM9qGIDy-kCipBkfUJbXk" "JnZ6LhSnoeeE5oKZn-CTriBXR4Bsjs7YiSUrEcNU5ZmBIs_56o6SyuniA71L3-bxfvHnwB5ysNFGKgKId" "7Skfx7u9f-AEGs_qA7kQ1w714DbFQYE2Pv5f\",\"qi\":\"eU35yvBcZiZ2Jzs8UU9CT1uJ9z5OjT-hg" "1QlkbQgPHQB1JhFsoMYqBsYpaUttwrCHbTJjW13-7fvEoFXS6RJm4qGg-LfY65LeCcF6djXr6XumqlJNM" "lFWF8aK5AZIiSZ_HUn5mdNRwbiONgmFwYjv3_nqBIfrXJ3C55FR9CcvD4\",\"dp\":\"AMFROlPH2Oq1" "9q1FLYjC5tnoIMioPb0gWK8X2ctYcPXD9nD9KS4hZhT2o6Xt2WCUPGsu57-65cr_8jq5z0Wu18aLki8mF" "crsRq0CRzF8IuF2tPBJrlKNN5xXf5nXVEBimKi_5QbTv4ut-KcLqOFrNP_yn0KzeYUtPUX6VnDZDEMF\"" ",\"dq\":\"AJnn_9pDx8N5VbBpTFPWlXRFDvDv_QClt5u_xEkGh2MDtIWdoaK_lGhKWslyIWDOtHgCGCQ" "cDKaVzk0BMD9uJUldZBCBO3nzK5Asw8G2F-crZHxep83vgRFGvBRwcuuKNMnXNT_G7aV1X5x2oGCq-Aff" "VIaPxrYxiG1nzO1ZRNyr\",\"kid\":\"" KID_2 "\",\"alg\":\"RS512\"},{\"kty\":\"EC\",\"x\":\"AJ6TXabOS7Blc_B" "NQVqQgp1nEwmatr8g9_HlPaoP4MPe\",\"y\":\"ALbKJWr4c4tksiv2IMWbfq09gghvuaR1pO2S_QjjX" "UtZ\",\"d\":\"Xk9lCflRiR6ErF1nqwSYhAFsOteufFotEi2z2xWDHks\",\"crv\":\"P-256\",\"k" "id\":\"" KID_3 "\",\"alg\":\"ES384\"},{\"kty\":\"oct\",\"k\":\"Kt5w3prEDTYJn8IveV1JtjXvtlAX0WRSEcEHxmND" "bP3aY760nHjA4VlCvxJQCYTSE1C1KObMmEnwjHrJzBedP-rqjn0SlzovvYyYWJL7Ujukqg4lhQxkLO5FZ" "jBNy1_i1-J3jPbP-DccD31-Kbxxmudqzd9LFVobDGoNvf-DdkQ\",\"kid\":\"" KID_4 "\",\"alg\":\"HS256\"}]}"; const char jwks_pubkey[] = "{\"keys\":[{\"kty\":\"RSA\",\"n\":\"AOidO2hPJFDK-jHdQ6p-SDGNAS3SbTCq1DN7Yv4kmClva" "5FtgLFIG8VG0hvn8RKN2kpqmNOa30KsOlYW9GqUCy6esFn0yqyNC_01IVY67qPIU5SRbCD88UXSfqsnhN" "sFgwU76OmpamqBGXUenZRrewNleNfYLJ6fNQO5n1rOa_UCcOaFqNLjjAcS9Z6e6h4Edlhz6ecYEVW6ZYF" "ODRNmyq_Pf0nZGgUjKXuAzEb8GdhiO99TcsLoc7RxTbfsvqLGofPXhY5EfWksNyeqJtINUEtMC78nADM6" "J_jFyeqBE3Tsqk1M6aQFo-8xy8kQ_bT7pdL9xh9w1UZ_kFg5pBMsaPk\",\"e\":\"AQAB\",\"kid\":" "\"overruled-" KID_1 "\"},{\"kty\":\"RSA\",\"n\":\"AMZGRVyWHvHCkbGpGF6xdhKSjYwX1q5xtS-9_rATkpGyp" "5f-vCl9uUgdD0CZZIuuEvWsRvp1zt-JZVS9GrnoWBLZXzafHKO4pADMPPGlzaBsEmp4E5S7t6c4LGMgXE" "xDoBs8jbp3TwbUeiyIsHyQzlY5pfg8_2Stp-PtoOWBVpWvhp1uxVuvvIp5TfHK3q2q3Iziggvja-p_cLj" "i95AAXOLBEfaRklMLPM0aMm996-dX7Yq-cUO5ptCBEoRBcPlEJjFP9ZY_Hb0_3W8BIpkvf_zTcGsgIrcx" "17mIp9yRQcQ9pQNSa5kpk3nfv2BZ6tJHu8KfKsA3WesYOkMM_6VoFRk\",\"e\":\"AQAB\",\"kid\":" "\"overruled-" KID_2 "\"},{\"kty\":\"EC\",\"x\":\"AJ6TXabOS7Blc_BNQVqQgp1nEwmatr8g9_HlPaoP4MPe\"" ",\"y\":\"ALbKJWr4c4tksiv2IMWbfq09gghvuaR1pO2S_QjjXUtZ\",\"crv\":\"P-256\",\"kid\"" ":\"overruled-" KID_3 "\"}]}"; const char jwks_pubkey_invalid_jwks[] = "{\"keys\":[{\"kty\":\"RSA\",\"n\":\"AOidO2hPJFDK-jHdQ6p-SDGNAS3SbTCq1DN7Yv4kmClva" "5FtgLFIG8VG0hvn8RKN2kpqmNOa30KsOlYW9GqUCy6esFn0yqyNC_01IVY67qPIU5SRbCD88UXSfqsnhN" "sFgwU76OmpamqBGXUenZRrewNleNfYLJ6fNQO5n1rOa_UCcOaFqNLjjAcS9Z6e6h4Edlhz6ecYEVW6ZYF" "ODRNmyq_Pf0nZGgUjKXuAzEb8GdhiO99TcsLoc7RxTbfsvqLGofPXhY5EfWksNyeqJtINUEtMC78nADM6" "J_jFyeqBE3Tsqk1M6aQFo-8xy8kQ_bT7pdL9xh9w1UZ_kFg5pBMsaPk\",\"e\":\"AQAB\",\"kid\":" "\"overruled-" KID_1 "\"},{\"kty\":\"RSA\",\"n\":\"AMZGRVyWHvHCkbGpGF6xdhKSjYwX1q5xtS-9_rATkpGyp" "5f-vCl9uUgdD0CZZIuuEvWsRvp1zt-JZVS9GrnoWBLZXzafHKO4pADMPPGlzaBsEmp4E5S7t6c4LGMgXE" "xDoBs8jbp3TwbUeiyIsHyQzlY5pfg8_2Stp-PtoOWBVpWvhp1uxVuvvIp5TfHK3q2q3Iziggvja-p_cLj" "i95AAXOLBEfaRklMLPM0aMm996-dX7Yq-cUO5ptCBEoRBcPlEJjFP9ZY_Hb0_3W8BIpkvf_zTcGsgIrcx" "17mIp9yRQcQ9pQNSa5kpk3nfv2BZ6tJHu8KfKsA3WesYOkMM_6VoFRk\",\"e\":\"AQAB\",\"kid\":" "\"overruled-" KID_2 "\"},{\"kty\":\"EC\",\"x\":\"AJ6TXabOS7Blc_BNQVqQgp1nEwmatr8g9_HlPaoP4MPe\"" ",\"y\":\"ALbKJWr4c4tksiv2IMWbfq09gghvuaR1pO2S_QjjXUtZ\",\"crv\":\"P-256\",\"kid\"" ":\"overruled-" KID_3 "\"}]error"; const char pubkey_1_pem[] = "-----BEGIN PUBLIC KEY-----\n"\ "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxaF1egmmQ+0/AAEcv/Jd\n"\ "TCBxi7A05VvDvRgaYzOEWqJF3qv3b6ifD4E4C9+cir6tIyCOJ2WXE4TV9fCWBQjI\n"\ "omacdQjdEwMAEWYYVEz4PPpWZDEeCKKgsz5DnLNyPznMiom82vDREvSqG+yxAEbk\n"\ "W1r+MtcFRd4YSZyJYDSVJ+t2VL7Nt/Gxao/MC8rJP/QHzSbWjwruSwW3JM+75wds\n"\ "JUbHhyIZ5lQ2u06d68ZA9Q4T60i+zE49yoLd8+PIjcrhM3kYIJkcTKyjWqZEV+we\n"\ "16eroS8PX/YN5tw4Od+fKb6I0XILhiyCqKlOg3FHdY8iAK75atI6TvX73ei+zSF3\n"\ "UyEFjDTcnhe6mPHL9REIVmb0+n70feP/SFQqaFRaamxBOkBPsVa9NP4zLJiRn71j\n"\ "ff7opHcT7zaR19cigQxsYmd+rZnhiH+UXel8P7kVTM7OCc7LRSJPG6xY4FtpWE2q\n"\ "abrGaUfBCMGtrH0GqjtbNCnBeBeCiijpN5XPqnGlSXpXrd3mjldlrFhiKVSlnvyp\n"\ "ekdAzA0WvpraBy351TzMsXphokchoynpdP04Pyv7DtI6SrU7sn5QZGoZtRVzNjOE\n"\ "CljVD4HZmc7Xw/kpCcb3sBtT9KOTlGd/ocn7D4Tw04jCCjmPOWhX/kKTrD4Mf+wv\n"\ "kUNxrz0Ea7PClAQf+HeZrjcCAwEAAQ==\n"\ "-----END PUBLIC KEY-----"; const char privkey_1_pem[] = "-----BEGIN RSA PRIVATE KEY-----\n"\ "MIIJKwIBAAKCAgEAxaF1egmmQ+0/AAEcv/JdTCBxi7A05VvDvRgaYzOEWqJF3qv3\n"\ "b6ifD4E4C9+cir6tIyCOJ2WXE4TV9fCWBQjIomacdQjdEwMAEWYYVEz4PPpWZDEe\n"\ "CKKgsz5DnLNyPznMiom82vDREvSqG+yxAEbkW1r+MtcFRd4YSZyJYDSVJ+t2VL7N\n"\ "t/Gxao/MC8rJP/QHzSbWjwruSwW3JM+75wdsJUbHhyIZ5lQ2u06d68ZA9Q4T60i+\n"\ "zE49yoLd8+PIjcrhM3kYIJkcTKyjWqZEV+we16eroS8PX/YN5tw4Od+fKb6I0XIL\n"\ "hiyCqKlOg3FHdY8iAK75atI6TvX73ei+zSF3UyEFjDTcnhe6mPHL9REIVmb0+n70\n"\ "feP/SFQqaFRaamxBOkBPsVa9NP4zLJiRn71jff7opHcT7zaR19cigQxsYmd+rZnh\n"\ "iH+UXel8P7kVTM7OCc7LRSJPG6xY4FtpWE2qabrGaUfBCMGtrH0GqjtbNCnBeBeC\n"\ "iijpN5XPqnGlSXpXrd3mjldlrFhiKVSlnvypekdAzA0WvpraBy351TzMsXphokch\n"\ "oynpdP04Pyv7DtI6SrU7sn5QZGoZtRVzNjOECljVD4HZmc7Xw/kpCcb3sBtT9KOT\n"\ "lGd/ocn7D4Tw04jCCjmPOWhX/kKTrD4Mf+wvkUNxrz0Ea7PClAQf+HeZrjcCAwEA\n"\ "AQKCAgEAiK7oXuT5lxUoc0pRpfdry1DM9v2BN/fFYqye42kn6r88b4qj8RfAGqsW\n"\ "JjAbRmIlwJutdVXvrNI8YIJqX0T57kflgZQFwjZa/62KuhMZxKKmyVinGvXPCujP\n"\ "PjtlkpDZBB/K++l90Axi8jk7GaPuH7jJAkRwIyg9x/t5j44OcrOI/YeRI90HBVo6\n"\ "lxVDJvYRxzNLRVccA8sKJW1+2YPqqcVQDgfpvcEUsWroEb7mAhYgdKO1QfwDBGKK\n"\ "OzmY5WghZMYlh7YdOnJT8Ef6EIz/r8sJCstPaOD2MGqygGRG4koiuiiUC9IQ87FU\n"\ "2deowoehzrM1UUnghefesFVeUOUKbr3Gh7rv5fYzijwtel2fAW29qjdhgqoCq/WQ\n"\ "fQlkT2QnJjeh2Fdr5JFmm84KgTdikOvlROVGQRZkBNCOq0Y/skSr9FDC0PAzU2kA\n"\ "0iXhwUAlWFQlud2Mx4moFwDj40RXpsGNaR6fDXv0fpS5Y1c3NeSU+yC0ycX8/BwE\n"\ "Uq63CcWocATmDrD2IdDnIbv/abjMyh18GIaJLLdx1DZ/cwQdFGgGaqigQZT/pSuu\n"\ "fqLIAXGT+wg0SdP/sSbKCV79DA/k34FC4WkP81aiMaKtbA7CJZgEdEFc/S5vXts3\n"\ "HvDiCFroLEedfInQxkFrI4gyudAZ5YLujlr3fYsJNB96B0GHnGECggEBAPQQSKxv\n"\ "01Oky+jENQwxiZcpI4a5PzLPFFCgEqIjSRamCzrCQ07e97iqhU1b8IvRwxDtX358\n"\ "pFKAq7tmwpN2QQb1T9fqUwCpeQuMwRsZwoaM7ZcTSj2FZ/2djN1ixQfzqQ21VxkM\n"\ "RbrdyExqCSJXnHMcLeiFmu81dVopV2iwDbUQv4jZe/ktPUTH4HKle48Y0v9pu22l\n"\ "D5cknAQGB1gUNfyJ0PbUxZMITrZDz4khhYgxqvJ7GluYRNv2tezV+bb5leXbSLDr\n"\ "RgTKqcl5ZjkgLm9FRNGZZAmlsCHEeB3nvCs2ePQYDuLgEkNtuu39kpLFJO6j70bj\n"\ "nvpaIAcDVpPmEE8CggEBAM9L09Grg2uSrNUGfj9pfpsMn0k5kqV0n3WjX9z5ZLkw\n"\ "LNNrs0SJjb93haO2MPNlyYhctCpPKnfHJKZWaLhFDV6xr+ubf7c3DbBJjPhlV8dU\n"\ "kgmHfIqWDPl6pzN0xC61zC4IE15LgW/JEMpq53fRWnIHdufs+105QO8YOo0CVYKY\n"\ "jqut4hVbYRBSTaeVLb1vj/yhaL0qV7orQoTrpr6Bg20nftBBa+8Md/B5l0QyiSfv\n"\ "OjKnXsjULQdQGbtypQZvu2jUasnUVUQHBgeF5W5WFj8qCGGnmehqY6QissipLoRM\n"\ "cGPaV/gJKisgcorF7sSU/QzcBUmPk377LkzZXGNUYZkCggEBALL4hfI9BmCdxhFo\n"\ "X+YTJWw9dJnEmf1uMN12pHNVILGFDVMHRUg+5LT8BkhWFSzSoxJ0nsQoLm95f3Uq\n"\ "w6BS5RhvJx+T603e+K5phumSmD0GduuD77rxavJlZ/ioBwfvu5Yb1kS95RxEqi6u\n"\ "ywft6wHWNiv+XUDwmJ+HFVvlTgfqwileIjT04argT0yC4PpsH73AEPs0QRx6chXZ\n"\ "PeVu3K/Vd/Co0kEhpGavjy5l8H+QvGSXtRpZrJUIcxu7RSTSHQOzK7jgrjWxT5Q4\n"\ "e6eEW8ioqPByZRNV9rSsV9DGMAwYI9YLFk90NLBRdPQ0MBmEi7KbcEkxfVDkafv6\n"\ "jLBj0q0CggEBAJldyYY7dczVxMcKucbinwfJq+N6E/QTt5JKYDdV0F5utQtqiEQx\n"\ "3MyGejooJkk9yn/3zlfrIElj7cqe7XU/qWeg4L3Y2wHLWnZNxF1WZT4VZMJmGg9S\n"\ "eqDtTNz2C9tfJ4P695FxHX99681GkKAGJPtuaFuo6kQLgu4iJ9eBnZA0nIGJ8VXJ\n"\ "uKNhsRBGf4PDEW1gYeRqemNDdEBxNHmHypusd9dOP7OpruccnnyXQwBnrtAhIjBF\n"\ "QldBvPgBFvUPH0GsvqE6VicxZxWTy635RRZQW8kcPfNFGxkpjsqE2OSKxTArL6BT\n"\ "733e0L+5NzD75cho1ASblA2DerriqcbXfCkCggEBAILHVNisODhO4GC8P709DqGd\n"\ "VdufLZf2Bl7AwjWyYTkpEzEfQCHHUnmOoTCn+OEvnn9lWiaCaTijtlUmos0fCfvS\n"\ "QLk9elciIOmlRk8G1EtnnzYQsTmerLoMJBgQ02hhip8GK47Y7mbZIjaPB625Dv8F\n"\ "4RHd9ZiTzXTGcNc6bldWlNNbbqw9DWS1DORPhdQEPU424qcYvHq/eklFCujWukO8\n"\ "ul3FEZYnTcth2ODSFMb0a0SCuDGkGI8BDI+/4n6+4wIlAXtc8Vt9Ko8WxJjCK/v2\n"\ "Ae9x05eknWZj0JxuyoAjPtJApp0pt25omJwZr/lY5i8T0cL6dDF5nZcA9hN//eo=\n"\ "-----END RSA PRIVATE KEY-----\n"; struct _u_request admin_req; struct _u_request user_req; static int callback_request_jwks_invalid_json (const struct _u_request * request, struct _u_response * response, void * user_data) { ulfius_set_string_body_response(response, 200, jwks_privkey); return U_CALLBACK_COMPLETE; } static int callback_request_jwks_alg_invalid (const struct _u_request * request, struct _u_response * response, void * user_data) { json_t * jwks = json_loads(jwks_privkey_alg_invalid, JSON_DECODE_ANY, NULL); ulfius_set_json_body_response(response, 200, jwks); json_decref(jwks); return U_CALLBACK_COMPLETE; } static int callback_request_jwks_valid (const struct _u_request * request, struct _u_response * response, void * user_data) { json_t * jwks = json_loads(jwks_privkey, JSON_DECODE_ANY, NULL); ulfius_set_json_body_response(response, 200, jwks); json_decref(jwks); return U_CALLBACK_COMPLETE; } START_TEST(test_oidc_jwks_add_module_alg_missing) { json_t * j_parameters = json_pack("{sssssssos{sssssssisisisososososososososososisssssssossssssssssssssssss}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwks-private", jwks_privkey_alg_missing, "default-kid", KID_1, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "request-parameter-allow-encrypted", json_true(), "request-uri-allow-https-non-secure", json_true(), "request-maximum-exp", CLIENT_AUTH_TOKEN_MAX_AGE, "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "client-jwks-parameter", CLIENT_JWKS_PARAM, "client-jwks_uri-parameter", CLIENT_JWKS_URI_PARAM, "encrypt-out-token-allow", json_true(), "client-enc-parameter", "enc", "client-alg-parameter", "alg", "client-alg_kid-parameter", "alg_kid", "client-sign_kid-parameter", "sign_kid", "client-encrypt_code-parameter", "encrypt_code", "client-encrypt_at-parameter", "encrypt_at", "client-encrypt_userinfo-parameter", "encrypt_userinfo", "client-encrypt_id_token-parameter", "encrypt_id_token", "client-encrypt_refresh_token-parameter", "encrypt_refresh_token"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_jwks_add_module_alg_invalid) { json_t * j_parameters = json_pack("{sssssssos{sssssssisisisososososososososososisssssssossssssssssssssssss}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwks-private", jwks_privkey_alg_invalid, "default-kid", KID_1, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "request-parameter-allow-encrypted", json_true(), "request-uri-allow-https-non-secure", json_true(), "request-maximum-exp", CLIENT_AUTH_TOKEN_MAX_AGE, "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "client-jwks-parameter", CLIENT_JWKS_PARAM, "client-jwks_uri-parameter", CLIENT_JWKS_URI_PARAM, "encrypt-out-token-allow", json_true(), "client-enc-parameter", "enc", "client-alg-parameter", "alg", "client-alg_kid-parameter", "alg_kid", "client-sign_kid-parameter", "sign_kid", "client-encrypt_code-parameter", "encrypt_code", "client-encrypt_at-parameter", "encrypt_at", "client-encrypt_userinfo-parameter", "encrypt_userinfo", "client-encrypt_id_token-parameter", "encrypt_id_token", "client-encrypt_refresh_token-parameter", "encrypt_refresh_token"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_jwks_add_module_kid_missing) { json_t * j_parameters = json_pack("{sssssssos{sssssssisisisososososososososososisssssssossssssssssssssssss}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwks-private", jwks_privkey_kid_missing, "default-kid", KID_1, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "request-parameter-allow-encrypted", json_true(), "request-uri-allow-https-non-secure", json_true(), "request-maximum-exp", CLIENT_AUTH_TOKEN_MAX_AGE, "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "client-jwks-parameter", CLIENT_JWKS_PARAM, "client-jwks_uri-parameter", CLIENT_JWKS_URI_PARAM, "encrypt-out-token-allow", json_true(), "client-enc-parameter", "enc", "client-alg-parameter", "alg", "client-alg_kid-parameter", "alg_kid", "client-sign_kid-parameter", "sign_kid", "client-encrypt_code-parameter", "encrypt_code", "client-encrypt_at-parameter", "encrypt_at", "client-encrypt_userinfo-parameter", "encrypt_userinfo", "client-encrypt_id_token-parameter", "encrypt_id_token", "client-encrypt_refresh_token-parameter", "encrypt_refresh_token"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_jwks_add_module_kid_invalid) { json_t * j_parameters = json_pack("{sssssssos{sssssssisisisososososososososososisssssssossssssssssssssssss}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwks-private", jwks_privkey_kid_invalid, "default-kid", KID_1, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "request-parameter-allow-encrypted", json_true(), "request-uri-allow-https-non-secure", json_true(), "request-maximum-exp", CLIENT_AUTH_TOKEN_MAX_AGE, "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "client-jwks-parameter", CLIENT_JWKS_PARAM, "client-jwks_uri-parameter", CLIENT_JWKS_URI_PARAM, "encrypt-out-token-allow", json_true(), "client-enc-parameter", "enc", "client-alg-parameter", "alg", "client-alg_kid-parameter", "alg_kid", "client-sign_kid-parameter", "sign_kid", "client-encrypt_code-parameter", "encrypt_code", "client-encrypt_at-parameter", "encrypt_at", "client-encrypt_userinfo-parameter", "encrypt_userinfo", "client-encrypt_id_token-parameter", "encrypt_id_token", "client-encrypt_refresh_token-parameter", "encrypt_refresh_token"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_jwks_add_module_uri_invalid_json) { struct _u_instance instance; ulfius_init_instance(&instance, 7597, NULL, NULL); ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/jwks", 0, &callback_request_jwks_invalid_json, NULL); ulfius_start_framework(&instance); json_t * j_parameters = json_pack("{sssssssos{sssssssisisisososososososososososisssssssossssssssssssssssss}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwks-uri", "http://localhost:7597/jwks", "default-kid", KID_1, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "request-parameter-allow-encrypted", json_true(), "request-uri-allow-https-non-secure", json_true(), "request-maximum-exp", CLIENT_AUTH_TOKEN_MAX_AGE, "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "client-jwks-parameter", CLIENT_JWKS_PARAM, "client-jwks_uri-parameter", CLIENT_JWKS_URI_PARAM, "encrypt-out-token-allow", json_true(), "client-enc-parameter", "enc", "client-alg-parameter", "alg", "client-alg_kid-parameter", "alg_kid", "client-sign_kid-parameter", "sign_kid", "client-encrypt_code-parameter", "encrypt_code", "client-encrypt_at-parameter", "encrypt_at", "client-encrypt_userinfo-parameter", "encrypt_userinfo", "client-encrypt_id_token-parameter", "encrypt_id_token", "client-encrypt_refresh_token-parameter", "encrypt_refresh_token"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); } END_TEST START_TEST(test_oidc_jwks_add_module_uri_invalid) { struct _u_instance instance; ulfius_init_instance(&instance, 7598, NULL, NULL); ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/jwks", 0, &callback_request_jwks_alg_invalid, NULL); ulfius_start_framework(&instance); json_t * j_parameters = json_pack("{sssssssos{sssssssisisisososososososososososisssssssossssssssssssssssss}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwks-uri", "http://localhost:7598/jwks", "default-kid", KID_1, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "request-parameter-allow-encrypted", json_true(), "request-uri-allow-https-non-secure", json_true(), "request-maximum-exp", CLIENT_AUTH_TOKEN_MAX_AGE, "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "client-jwks-parameter", CLIENT_JWKS_PARAM, "client-jwks_uri-parameter", CLIENT_JWKS_URI_PARAM, "encrypt-out-token-allow", json_true(), "client-enc-parameter", "enc", "client-alg-parameter", "alg", "client-alg_kid-parameter", "alg_kid", "client-sign_kid-parameter", "sign_kid", "client-encrypt_code-parameter", "encrypt_code", "client-encrypt_at-parameter", "encrypt_at", "client-encrypt_userinfo-parameter", "encrypt_userinfo", "client-encrypt_id_token-parameter", "encrypt_id_token", "client-encrypt_refresh_token-parameter", "encrypt_refresh_token"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); } END_TEST START_TEST(test_oidc_jwks_add_module_uri_valid) { struct _u_instance instance; ulfius_init_instance(&instance, 7599, NULL, NULL); ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/jwks", 0, &callback_request_jwks_valid, NULL); ulfius_start_framework(&instance); json_t * j_parameters = json_pack("{sssssssos{sssssssisisisososososososososososisssssssossssssssssssssssss}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwks-uri", "http://localhost:7599/jwks", "default-kid", KID_1, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "request-parameter-allow-encrypted", json_true(), "request-uri-allow-https-non-secure", json_true(), "request-maximum-exp", CLIENT_AUTH_TOKEN_MAX_AGE, "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "client-jwks-parameter", CLIENT_JWKS_PARAM, "client-jwks_uri-parameter", CLIENT_JWKS_URI_PARAM, "encrypt-out-token-allow", json_true(), "client-enc-parameter", "enc", "client-alg-parameter", "alg", "client-alg_kid-parameter", "alg_kid", "client-sign_kid-parameter", "sign_kid", "client-encrypt_code-parameter", "encrypt_code", "client-encrypt_at-parameter", "encrypt_at", "client-encrypt_userinfo-parameter", "encrypt_userinfo", "client-encrypt_id_token-parameter", "encrypt_id_token", "client-encrypt_refresh_token-parameter", "encrypt_refresh_token"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); } END_TEST START_TEST(test_oidc_jwks_add_module_ok) { json_t * j_parameters = json_pack("{sssssssos{sssssssisisisososososososososososisssssssossssssssssssssssss}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwks-private", jwks_privkey, "default-kid", KID_1, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "request-parameter-allow-encrypted", json_true(), "request-uri-allow-https-non-secure", json_true(), "request-maximum-exp", CLIENT_AUTH_TOKEN_MAX_AGE, "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "client-jwks-parameter", CLIENT_JWKS_PARAM, "client-jwks_uri-parameter", CLIENT_JWKS_URI_PARAM, "encrypt-out-token-allow", json_true(), "client-enc-parameter", "enc", "client-alg-parameter", "alg", "client-alg_kid-parameter", "alg_kid", "client-sign_kid-parameter", "sign_kid", "client-encrypt_code-parameter", "encrypt_code", "client-encrypt_at-parameter", "encrypt_at", "client-encrypt_userinfo-parameter", "encrypt_userinfo", "client-encrypt_id_token-parameter", "encrypt_id_token", "client-encrypt_refresh_token-parameter", "encrypt_refresh_token"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_jwks_add_module_public_jwks_invalid) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisososososososososososisssssssossssssssssssssssss}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwks-private", jwks_privkey, "jwks-public", jwks_pubkey_invalid_jwks, "default-kid", KID_1, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "request-parameter-allow-encrypted", json_true(), "request-uri-allow-https-non-secure", json_true(), "request-maximum-exp", CLIENT_AUTH_TOKEN_MAX_AGE, "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "client-jwks-parameter", CLIENT_JWKS_PARAM, "client-jwks_uri-parameter", CLIENT_JWKS_URI_PARAM, "encrypt-out-token-allow", json_true(), "client-enc-parameter", "enc", "client-alg-parameter", "alg", "client-alg_kid-parameter", "alg_kid", "client-sign_kid-parameter", "sign_kid", "client-encrypt_code-parameter", "encrypt_code", "client-encrypt_at-parameter", "encrypt_at", "client-encrypt_userinfo-parameter", "encrypt_userinfo", "client-encrypt_id_token-parameter", "encrypt_id_token", "client-encrypt_refresh_token-parameter", "encrypt_refresh_token"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_jwks_add_module_public_jwks_ok) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisososososososososososisssssssossssssssssssssssss}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwks-private", jwks_privkey, "jwks-public", jwks_pubkey, "default-kid", KID_1, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "request-parameter-allow-encrypted", json_true(), "request-uri-allow-https-non-secure", json_true(), "request-maximum-exp", CLIENT_AUTH_TOKEN_MAX_AGE, "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "client-jwks-parameter", CLIENT_JWKS_PARAM, "client-jwks_uri-parameter", CLIENT_JWKS_URI_PARAM, "encrypt-out-token-allow", json_true(), "client-enc-parameter", "enc", "client-alg-parameter", "alg", "client-alg_kid-parameter", "alg_kid", "client-sign_kid-parameter", "sign_kid", "client-encrypt_code-parameter", "encrypt_code", "client-encrypt_at-parameter", "encrypt_at", "client-encrypt_userinfo-parameter", "encrypt_userinfo", "client-encrypt_id_token-parameter", "encrypt_id_token", "client-encrypt_refresh_token-parameter", "encrypt_refresh_token"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_jwks_delete_module) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_jwks_discovery_valid) { struct _u_request req; struct _u_response resp; json_t * j_discovery = json_loads("{\"issuer\":\"https://glewlwyd.tld\",\"authorization_endpoint\":\"http://localhost:4593/api/oidc_jwks/auth\",\"token_endpoint\":\"http://localhost:4593/api/oidc_jwks/token\",\"userinfo_endpoint\":\"http://localhost:4593/api/oidc_jwks/userinfo\",\"jwks_uri\":\"http://localhost:4593/api/oidc_jwks/jwks\",\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\"],\"id_token_signing_alg_values_supported\":[],\"userinfo_signing_alg_values_supported\":[],\"access_token_signing_alg_values_supported\":[],\"id_token_encryption_alg_values_supported\":[],\"id_token_encryption_enc_values_supported\":[],\"userinfo_encryption_alg_values_supported\":[],\"userinfo_encryption_enc_values_supported\":[],\"access_token_encryption_alg_values_supported\":[],\"access_token_encryption_enc_values_supported\":[],\"request_object_signing_alg_values_supported\":[],\"request_object_encryption_alg_values_supported\":[],\"request_object_encryption_enc_values_supported\":[],\"token_endpoint_auth_signing_alg_values_supported\":[],\"scopes_supported\":[\"openid\"],\"response_types_supported\":[\"code\",\"id_token\",\"token id_token\",\"code id_token\",\"code token id_token\",\"password\",\"token\",\"client_credentials\",\"refresh_token\"],\"response_modes_supported\":[\"query\",\"fragment\",\"form_post\"],\"grant_types_supported\":[\"authorization_code\",\"implicit\"],\"display_values_supported\":[\"page\",\"popup\",\"touch\",\"wap\"],\"claim_types_supported\":[\"normal\"],\"claims_parameter_supported\":true,\"claims_supported\":[],\"ui_locales_supported\":[\"en\",\"fr\",\"nl\",\"de\"],\"request_parameter_supported\":true,\"request_uri_parameter_supported\":true,\"require_request_uri_registration\":false,\"subject_types_supported\":[\"public\"]}", JSON_DECODE_ANY, NULL), * j_result; ck_assert_ptr_ne(j_discovery, NULL); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/.well-known/openid-configuration", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_result = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "id_token_signing_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "access_token_encryption_enc_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "access_token_encryption_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "access_token_signing_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "userinfo_signing_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "userinfo_encryption_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "userinfo_encryption_enc_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "request_object_signing_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "request_object_encryption_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "token_endpoint_auth_signing_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "id_token_encryption_alg_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "id_token_encryption_enc_values_supported"))); ck_assert_int_eq(0, json_array_clear(json_object_get(j_result, "request_object_encryption_enc_values_supported"))); ck_assert_int_eq(1, json_equal(j_result, j_discovery)); json_decref(j_result); json_decref(j_discovery); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/jwks", U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_result = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_int_gt(json_array_size(json_object_get(j_result, "keys")), 0); json_decref(j_result); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_jwks_discovery_public_jwks_valid) { json_t * j_key = json_loads(jwks_pubkey, JSON_DECODE_ANY, NULL); ck_assert_ptr_ne(j_key, NULL); ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/" PLUGIN_NAME "/jwks", NULL, NULL, NULL, NULL, 200, j_key, NULL, NULL), 1); json_decref(j_key); } END_TEST START_TEST(test_oidc_jwks_add_client_sign_kid) { json_t * j_client = json_pack("{ss ss ss so s[s] s[sssss] s[s] ss ss ss ss ss ss ss ss ss so}", "client_id", CLIENT_ID, "client_secret", CLIENT_SECRET, "name", CLIENT_NAME, "confidential", json_true(), "redirect_uri", CLIENT_REDIRECT, "authorization_type", "code", "token", "id_token", "password", "client_credentials", "scope", CLIENT_SCOPE, "sign_kid", KID_2, "pubkey", pubkey_1_pem, "enc", CLIENT_ENC, "alg", CLIENT_PUBKEY_ALG, "encrypt_code", "1", "encrypt_at", "nay", "encrypt_userinfo", "Hell no", "encrypt_id_token", "nope", "encrypt_refresh_token", "absolutely not!", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_jwks_add_client_no_sign_kid) { json_t * j_client = json_pack("{ss ss ss so s[s] s[sssss] s[s] ss ss ss ss ss ss ss ss so}", "client_id", CLIENT_ID, "client_secret", CLIENT_SECRET, "name", CLIENT_NAME, "confidential", json_true(), "redirect_uri", CLIENT_REDIRECT, "authorization_type", "code", "token", "id_token", "password", "client_credentials", "scope", CLIENT_SCOPE, "pubkey", pubkey_1_pem, "enc", CLIENT_ENC, "alg", CLIENT_PUBKEY_ALG, "encrypt_code", "1", "encrypt_at", "nay", "encrypt_userinfo", "Hell no", "encrypt_id_token", "nope", "encrypt_refresh_token", "absolutely not!", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_jwks_add_client_invalid_sign_kid) { json_t * j_client = json_pack("{ss ss ss so s[s] s[sssss] s[s] ss ss ss ss ss ss ss ss ss so}", "client_id", CLIENT_ID, "client_secret", CLIENT_SECRET, "name", CLIENT_NAME, "confidential", json_true(), "redirect_uri", CLIENT_REDIRECT, "authorization_type", "code", "token", "id_token", "password", "client_credentials", "scope", CLIENT_SCOPE, "sign_kid", "error", "pubkey", pubkey_1_pem, "enc", CLIENT_ENC, "alg", CLIENT_PUBKEY_ALG, "encrypt_code", "1", "encrypt_at", "nay", "encrypt_userinfo", "Hell no", "encrypt_id_token", "nope", "encrypt_refresh_token", "absolutely not!", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_jwks_delete_client) { json_t * j_param = json_pack("{ss}", "scope", ""); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_ID, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_jwks_implicit_id_token_valid_sign_kid) { struct _u_response resp; char * id_token; jwt_t * jwt_idt; jwks_t * jwks_pub; jwk_t * jwk; ck_assert_int_eq(r_jwks_init(&jwks_pub), RHN_OK); ck_assert_int_eq(r_jwks_import_from_uri(jwks_pub, SERVER_URI "/" PLUGIN_NAME "/jwks", 0), RHN_OK); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT_ID, CLIENT_REDIRECT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=")+o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *o_strchr(id_token, '&') = '\0'; } ck_assert_int_eq(r_jwt_init(&jwt_idt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt_idt, id_token, 0), RHN_OK); ck_assert_int_eq(R_JWT_TYPE_SIGN, r_jwt_get_type(jwt_idt)); ck_assert_str_eq(KID_2, r_jwt_get_header_str_value(jwt_idt, "kid")); ck_assert_ptr_ne(jwk = r_jwks_get_by_kid(jwks_pub, KID_2), NULL); ck_assert_int_eq(r_jwt_add_sign_keys(jwt_idt, NULL, jwk), RHN_OK); ck_assert_int_eq(r_jwt_verify_signature(jwt_idt, NULL, 0), RHN_OK); o_free(id_token); r_jwt_free(jwt_idt); r_jwk_free(jwk); r_jwks_free(jwks_pub); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_jwks_userinfo_jwt_sign_kid) { struct _u_response resp; struct _u_request req; char * access_token, * bearer; jwt_t * jwt; jwks_t * jwks_pub; jwk_t * jwk; ck_assert_int_eq(r_jwks_init(&jwks_pub), RHN_OK); ck_assert_int_eq(r_jwks_import_from_uri(jwks_pub, SERVER_URI "/" PLUGIN_NAME "/jwks", 0), RHN_OK); ulfius_init_response(&resp); ulfius_init_request(&req); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT_ID, CLIENT_REDIRECT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); access_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token=")); if (o_strchr(access_token, '&')) { *(o_strchr(access_token, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, access_token, 0), RHN_OK); ck_assert_str_eq(KID_2, r_jwt_get_header_str_value(jwt, "kid")); ck_assert_ptr_ne(jwk = r_jwks_get_by_kid(jwks_pub, KID_2), NULL); ck_assert_int_eq(r_jwt_add_sign_keys(jwt, NULL, jwk), RHN_OK); ck_assert_int_eq(r_jwt_verify_signature(jwt, NULL, 0), RHN_OK); r_jwt_free(jwt); r_jwk_free(jwk); bearer = msprintf("Bearer %s", access_token); u_map_put(req.map_header, "Authorization", bearer); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/userinfo/"); u_map_put(req.map_header, "Accept", "application/jwt"); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_str_eq(u_map_get(resp.map_header, "Content-Type"), "application/jwt"); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parsen(jwt, resp.binary_body, resp.binary_body_length, 0), RHN_OK); ck_assert_str_eq(KID_2, r_jwt_get_header_str_value(jwt, "kid")); ck_assert_ptr_ne(jwk = r_jwks_get_by_kid(jwks_pub, KID_2), NULL); ck_assert_int_eq(r_jwt_add_sign_keys(jwt, NULL, jwk), RHN_OK); ck_assert_int_eq(r_jwt_verify_signature(jwt, NULL, 0), RHN_OK); ulfius_clean_response(&resp); r_jwt_free(jwt); r_jwk_free(jwk); r_jwks_free(jwks_pub); ulfius_clean_request(&req); o_free(access_token); o_free(bearer); } END_TEST START_TEST(test_oidc_jwks_client_cred_valid_sign_kid) { struct _u_response resp; struct _u_request req; json_t * j_resp = NULL; jwt_t * jwt; jwks_t * jwks_pub; jwk_t * jwk; ck_assert_int_eq(r_jwks_init(&jwks_pub), RHN_OK); ck_assert_int_eq(r_jwks_import_from_uri(jwks_pub, SERVER_URI "/" PLUGIN_NAME "/jwks", 0), RHN_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "client_credentials"); u_map_put(req.map_post_body, "scope", CLIENT_SCOPE); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_str_eq(KID_2, r_jwt_get_header_str_value(jwt, "kid")); ck_assert_ptr_ne(jwk = r_jwks_get_by_kid(jwks_pub, KID_2), NULL); ck_assert_int_eq(r_jwt_add_sign_keys(jwt, NULL, jwk), RHN_OK); ck_assert_int_eq(r_jwt_verify_signature(jwt, NULL, 0), RHN_OK); json_decref(j_resp); r_jwk_free(jwk); r_jwt_free(jwt); r_jwks_free(jwks_pub); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_jwks_implicit_id_token_valid_sign_kid_invalid) { struct _u_response resp; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT_ID, CLIENT_REDIRECT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "server_error"), NULL); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_jwks_client_cred_valid_sign_kid_invalid) { struct _u_response resp; struct _u_request req; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "client_credentials"); u_map_put(req.map_post_body, "scope", CLIENT_SCOPE); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 500); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_jwks_implicit_id_token_valid_no_sign_kid) { struct _u_response resp; char * id_token; jwt_t * jwt_idt; jwks_t * jwks_pub; jwk_t * jwk; ck_assert_int_eq(r_jwks_init(&jwks_pub), RHN_OK); ck_assert_int_eq(r_jwks_import_from_uri(jwks_pub, SERVER_URI "/" PLUGIN_NAME "/jwks", 0), RHN_OK); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT_ID, CLIENT_REDIRECT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=")+o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *o_strchr(id_token, '&') = '\0'; } ck_assert_int_eq(r_jwt_init(&jwt_idt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt_idt, id_token, 0), RHN_OK); ck_assert_int_eq(R_JWT_TYPE_SIGN, r_jwt_get_type(jwt_idt)); ck_assert_str_eq(KID_1, r_jwt_get_header_str_value(jwt_idt, "kid")); ck_assert_ptr_ne(jwk = r_jwks_get_by_kid(jwks_pub, KID_1), NULL); ck_assert_int_eq(r_jwt_add_sign_keys(jwt_idt, NULL, jwk), RHN_OK); ck_assert_int_eq(r_jwt_verify_signature(jwt_idt, NULL, 0), RHN_OK); o_free(id_token); r_jwt_free(jwt_idt); r_jwk_free(jwk); r_jwks_free(jwks_pub); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_jwks_userinfo_jwt_no_sign_kid) { struct _u_response resp; struct _u_request req; char * access_token, * bearer; jwt_t * jwt; jwks_t * jwks_pub; jwk_t * jwk; ck_assert_int_eq(r_jwks_init(&jwks_pub), RHN_OK); ck_assert_int_eq(r_jwks_import_from_uri(jwks_pub, SERVER_URI "/" PLUGIN_NAME "/jwks", 0), RHN_OK); ulfius_init_response(&resp); ulfius_init_request(&req); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT_ID, CLIENT_REDIRECT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); access_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token=")); if (o_strchr(access_token, '&')) { *(o_strchr(access_token, '&')) = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, access_token, 0), RHN_OK); ck_assert_str_eq(KID_1, r_jwt_get_header_str_value(jwt, "kid")); ck_assert_ptr_ne(jwk = r_jwks_get_by_kid(jwks_pub, KID_1), NULL); ck_assert_int_eq(r_jwt_add_sign_keys(jwt, NULL, jwk), RHN_OK); ck_assert_int_eq(r_jwt_verify_signature(jwt, NULL, 0), RHN_OK); r_jwt_free(jwt); r_jwk_free(jwk); bearer = msprintf("Bearer %s", access_token); u_map_put(req.map_header, "Authorization", bearer); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/userinfo/"); u_map_put(req.map_header, "Accept", "application/jwt"); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_str_eq(u_map_get(resp.map_header, "Content-Type"), "application/jwt"); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parsen(jwt, resp.binary_body, resp.binary_body_length, 0), RHN_OK); ck_assert_str_eq(KID_1, r_jwt_get_header_str_value(jwt, "kid")); ck_assert_ptr_ne(jwk = r_jwks_get_by_kid(jwks_pub, KID_1), NULL); ck_assert_int_eq(r_jwt_add_sign_keys(jwt, NULL, jwk), RHN_OK); ck_assert_int_eq(r_jwt_verify_signature(jwt, NULL, 0), RHN_OK); ulfius_clean_response(&resp); r_jwt_free(jwt); r_jwk_free(jwk); r_jwks_free(jwks_pub); ulfius_clean_request(&req); o_free(access_token); o_free(bearer); } END_TEST START_TEST(test_oidc_jwks_client_cred_valid_no_sign_kid) { struct _u_response resp; struct _u_request req; json_t * j_resp = NULL; jwt_t * jwt; jwks_t * jwks_pub; jwk_t * jwk; ck_assert_int_eq(r_jwks_init(&jwks_pub), RHN_OK); ck_assert_int_eq(r_jwks_import_from_uri(jwks_pub, SERVER_URI "/" PLUGIN_NAME "/jwks", 0), RHN_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "client_credentials"); u_map_put(req.map_post_body, "scope", CLIENT_SCOPE); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_str_eq(KID_1, r_jwt_get_header_str_value(jwt, "kid")); ck_assert_ptr_ne(jwk = r_jwks_get_by_kid(jwks_pub, KID_1), NULL); ck_assert_int_eq(r_jwt_add_sign_keys(jwt, NULL, jwk), RHN_OK); ck_assert_int_eq(r_jwt_verify_signature(jwt, NULL, 0), RHN_OK); json_decref(j_resp); r_jwk_free(jwk); r_jwt_free(jwt); r_jwks_free(jwks_pub); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_jwks_request_token_jwt_nested_rsa_kid_1_ok) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); int rnd; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; jwks_t * jwks_pub; jwk_t * jwk; snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(r_jwks_init(&jwks_pub), RHN_OK); ck_assert_int_eq(r_jwks_import_from_uri(jwks_pub, SERVER_URI "/" PLUGIN_NAME "/jwks", 0), RHN_OK); ck_assert_ptr_ne(jwk = r_jwks_get_by_kid(jwks_pub, KID_1), NULL); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_set_enc_alg(jwt_request, R_JWA_ALG_RSA1_5), RHN_OK); ck_assert_int_eq(r_jwt_set_enc(jwt_request, R_JWA_ENC_A128CBC), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_request, R_FORMAT_PEM, (const unsigned char *)privkey_1_pem, sizeof(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys(jwt_request, NULL, jwk), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_ID); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); request = r_jwt_serialize_nested(jwt_request, R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, NULL, 0, NULL, 0); ck_assert_ptr_ne(request, NULL); u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 200, NULL, "access_token", NULL), 1); r_jwk_free(jwk); r_jwks_free(jwks_pub); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_jwks_request_token_jwt_nested_rsa_kid_2_ok) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); int rnd; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; jwks_t * jwks_pub; jwk_t * jwk; snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(r_jwks_init(&jwks_pub), RHN_OK); ck_assert_int_eq(r_jwks_import_from_uri(jwks_pub, SERVER_URI "/" PLUGIN_NAME "/jwks", 0), RHN_OK); ck_assert_ptr_ne(jwk = r_jwks_get_by_kid(jwks_pub, KID_2), NULL); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_set_enc_alg(jwt_request, R_JWA_ALG_RSA1_5), RHN_OK); ck_assert_int_eq(r_jwt_set_enc(jwt_request, R_JWA_ENC_A128CBC), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_request, R_FORMAT_PEM, (const unsigned char *)privkey_1_pem, sizeof(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys(jwt_request, NULL, jwk), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_ID); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); request = r_jwt_serialize_nested(jwt_request, R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, NULL, 0, NULL, 0); ck_assert_ptr_ne(request, NULL); u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 200, NULL, "access_token", NULL), 1); r_jwk_free(jwk); r_jwks_free(jwks_pub); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_jwks_request_token_jwt_nested_rsa_no_kid_ok) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); int rnd; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; jwks_t * jwks_pub; jwk_t * jwk; snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(r_jwks_init(&jwks_pub), RHN_OK); ck_assert_int_eq(r_jwks_import_from_uri(jwks_pub, SERVER_URI "/" PLUGIN_NAME "/jwks", 0), RHN_OK); ck_assert_ptr_ne(jwk = r_jwks_get_by_kid(jwks_pub, KID_1), NULL); r_jwk_delete_property_str(jwk, "kid"); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_set_enc_alg(jwt_request, R_JWA_ALG_RSA1_5), RHN_OK); ck_assert_int_eq(r_jwt_set_enc(jwt_request, R_JWA_ENC_A128CBC), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_request, R_FORMAT_PEM, (const unsigned char *)privkey_1_pem, sizeof(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys(jwt_request, NULL, jwk), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_ID); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); request = r_jwt_serialize_nested(jwt_request, R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, NULL, 0, NULL, 0); ck_assert_ptr_ne(request, NULL); u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 200, NULL, "access_token", NULL), 1); r_jwk_free(jwk); r_jwks_free(jwks_pub); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_jwks_request_token_jwt_nested_rsa_no_kid_invalid) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); int rnd; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; jwks_t * jwks_pub; jwk_t * jwk; snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(r_jwks_init(&jwks_pub), RHN_OK); ck_assert_int_eq(r_jwks_import_from_uri(jwks_pub, SERVER_URI "/" PLUGIN_NAME "/jwks", 0), RHN_OK); ck_assert_ptr_ne(jwk = r_jwks_get_by_kid(jwks_pub, KID_2), NULL); r_jwk_delete_property_str(jwk, "kid"); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_set_enc_alg(jwt_request, R_JWA_ALG_RSA1_5), RHN_OK); ck_assert_int_eq(r_jwt_set_enc(jwt_request, R_JWA_ENC_A128CBC), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_request, R_FORMAT_PEM, (const unsigned char *)privkey_1_pem, sizeof(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys(jwt_request, NULL, jwk), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_ID); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); request = r_jwt_serialize_nested(jwt_request, R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, NULL, 0, NULL, 0); ck_assert_ptr_ne(request, NULL); u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); r_jwk_free(jwk); r_jwks_free(jwks_pub); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc jwks"); tc_core = tcase_create("test_oidc_jwks"); tcase_add_test(tc_core, test_oidc_jwks_add_module_alg_missing); tcase_add_test(tc_core, test_oidc_jwks_delete_module); tcase_add_test(tc_core, test_oidc_jwks_add_module_alg_invalid); tcase_add_test(tc_core, test_oidc_jwks_delete_module); tcase_add_test(tc_core, test_oidc_jwks_add_module_kid_missing); tcase_add_test(tc_core, test_oidc_jwks_delete_module); tcase_add_test(tc_core, test_oidc_jwks_add_module_kid_invalid); tcase_add_test(tc_core, test_oidc_jwks_delete_module); tcase_add_test(tc_core, test_oidc_jwks_add_module_uri_invalid); tcase_add_test(tc_core, test_oidc_jwks_delete_module); tcase_add_test(tc_core, test_oidc_jwks_add_module_uri_invalid_json); tcase_add_test(tc_core, test_oidc_jwks_delete_module); tcase_add_test(tc_core, test_oidc_jwks_add_module_public_jwks_invalid); tcase_add_test(tc_core, test_oidc_jwks_delete_module); tcase_add_test(tc_core, test_oidc_jwks_add_module_public_jwks_ok); tcase_add_test(tc_core, test_oidc_jwks_discovery_public_jwks_valid); tcase_add_test(tc_core, test_oidc_jwks_delete_module); tcase_add_test(tc_core, test_oidc_jwks_add_module_ok); tcase_add_test(tc_core, test_oidc_jwks_discovery_valid); tcase_add_test(tc_core, test_oidc_jwks_add_client_sign_kid); tcase_add_test(tc_core, test_oidc_jwks_implicit_id_token_valid_sign_kid); tcase_add_test(tc_core, test_oidc_jwks_userinfo_jwt_sign_kid); tcase_add_test(tc_core, test_oidc_jwks_client_cred_valid_sign_kid); tcase_add_test(tc_core, test_oidc_jwks_request_token_jwt_nested_rsa_kid_1_ok); tcase_add_test(tc_core, test_oidc_jwks_request_token_jwt_nested_rsa_kid_2_ok); tcase_add_test(tc_core, test_oidc_jwks_request_token_jwt_nested_rsa_no_kid_ok); tcase_add_test(tc_core, test_oidc_jwks_request_token_jwt_nested_rsa_no_kid_invalid); tcase_add_test(tc_core, test_oidc_jwks_delete_client); tcase_add_test(tc_core, test_oidc_jwks_add_client_no_sign_kid); tcase_add_test(tc_core, test_oidc_jwks_implicit_id_token_valid_no_sign_kid); tcase_add_test(tc_core, test_oidc_jwks_userinfo_jwt_no_sign_kid); tcase_add_test(tc_core, test_oidc_jwks_client_cred_valid_no_sign_kid); tcase_add_test(tc_core, test_oidc_jwks_request_token_jwt_nested_rsa_kid_1_ok); tcase_add_test(tc_core, test_oidc_jwks_request_token_jwt_nested_rsa_kid_2_ok); tcase_add_test(tc_core, test_oidc_jwks_request_token_jwt_nested_rsa_no_kid_ok); tcase_add_test(tc_core, test_oidc_jwks_request_token_jwt_nested_rsa_no_kid_invalid); tcase_add_test(tc_core, test_oidc_jwks_delete_client); tcase_add_test(tc_core, test_oidc_jwks_add_client_invalid_sign_kid); tcase_add_test(tc_core, test_oidc_jwks_implicit_id_token_valid_sign_kid_invalid); tcase_add_test(tc_core, test_oidc_jwks_client_cred_valid_sign_kid_invalid); tcase_add_test(tc_core, test_oidc_jwks_delete_client); tcase_add_test(tc_core, test_oidc_jwks_delete_module); tcase_add_test(tc_core, test_oidc_jwks_add_module_uri_valid); tcase_add_test(tc_core, test_oidc_jwks_discovery_valid); tcase_add_test(tc_core, test_oidc_jwks_add_client_sign_kid); tcase_add_test(tc_core, test_oidc_jwks_implicit_id_token_valid_sign_kid); tcase_add_test(tc_core, test_oidc_jwks_userinfo_jwt_sign_kid); tcase_add_test(tc_core, test_oidc_jwks_client_cred_valid_sign_kid); tcase_add_test(tc_core, test_oidc_jwks_request_token_jwt_nested_rsa_kid_1_ok); tcase_add_test(tc_core, test_oidc_jwks_request_token_jwt_nested_rsa_kid_2_ok); tcase_add_test(tc_core, test_oidc_jwks_request_token_jwt_nested_rsa_no_kid_ok); tcase_add_test(tc_core, test_oidc_jwks_request_token_jwt_nested_rsa_no_kid_invalid); tcase_add_test(tc_core, test_oidc_jwks_delete_client); tcase_add_test(tc_core, test_oidc_jwks_delete_module); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, register_req; struct _u_response auth_resp, scope_resp; json_t * j_body, * j_register; int res, do_test = 0, i; char * url; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define SCOPE_INTROSPECT "g_admin" #define USER_USERNAME "user1" #define USER_PASSWORD "password" #define PLUGIN "oidc" #define SCOPE_LIST "openid" #define CLIENT "client1_id" #define PLUGIN_MODULE "oidc" #define PLUGIN_NAME "oidc_jwt_enc" #define PLUGIN_DISPLAY_NAME "oidc pubkey" #define PLUGIN_ISS "https://glewlwyd.tld" #define PLUGIN_JWT_TYPE "sha" #define PLUGIN_JWT_TYPE_RSA "rsa" #define PLUGIN_JWT_KEY_SIZE "256" #define PLUGIN_KEY "secret" #define PLUGIN_CODE_DURATION 600 #define PLUGIN_REFRESH_TOKEN_DURATION 1209600 #define PLUGIN_ACCESS_TOKEN_DURATION 3600 #define CLIENT_AUTH_TOKEN_MAX_AGE 3600 #define CLIENT_PUBKEY_PARAM "pubkey" #define CLIENT_JWKS_PARAM "jwks" #define CLIENT_JWKS_URI_PARAM "jwks_uri" #define CLIENT_ID "client_encrypt" #define CLIENT_SECRET "short-secret" #define CLIENT_NAME "client with pubkey" #define CLIENT_REDIRECT "https://glewlwyd.local/" #define CLIENT_ENC "A128CBC-HS256" #define CLIENT_PUBKEY_ALG "RSA1_5" #define CLIENT_SECRET_ALG "A128GCMKW" #define CLIENT_SCOPE "scope1" #define KID_1 "1" #define KID_2 "2" #define KID_3 "3" const char pubkey_1_jwk[] = "{\"keys\":[{\"kty\":\"RSA\",\"n\":\"AMWhdXoJpkPtPwABHL_yXUwgcYuwNOVbw70YGmMzhFqiRd6r92-onw-BOAvfnIq-rSMgjidllxOE1fXwlgUIyKJmnHUI3RMDABFmGFRM-Dz6VmQxHgiioLM-Q5yzcj85zIqJvNrw0RL0qhvssQBG5Fta_jLXBUXeGEmciWA0lSfrdlS-zbfxsWqPzAvKyT_0B80m1o8K7ksFtyTPu-cHbCVGx4ciGeZUNrtOnevGQPUOE-tIvsxOPcqC3fPjyI3K4TN5GCCZHEyso1qmRFfsHtenq6EvD1_2DebcODnfnym-iNFyC4YsgqipToNxR3WPIgCu-WrSOk71-93ovs0hd1MhBYw03J4Xupjxy_URCFZm9Pp-9H3j_0hUKmhUWmpsQTpAT7FWvTT-MyyYkZ-9Y33-6KR3E-82kdfXIoEMbGJnfq2Z4Yh_lF3pfD-5FUzOzgnOy0UiTxusWOBbaVhNqmm6xmlHwQjBrax9Bqo7WzQpwXgXgooo6TeVz6pxpUl6V63d5o5XZaxYYilUpZ78qXpHQMwNFr6a2gct-dU8zLF6YaJHIaMp6XT9OD8r-w7SOkq1O7J-UGRqGbUVczYzhApY1Q-B2ZnO18P5KQnG97AbU_Sjk5Rnf6HJ-w-E8NOIwgo5jzloV_5Ck6w-DH_sL5FDca89BGuzwpQEH_h3ma43\",\"e\":\"AQAB\",\"kid\":\"" KID_1 "\"}]}"; const char privkey_1_jwk[] = "{\"kty\":\"RSA\",\"n\":\"AMWhdXoJpkPtPwABHL_yXUwgcYuwNOVbw70YGmMzhFqiRd6r92-onw-BOAvfnIq-rSMgjidllxOE1fXwlgUIyKJmnHUI3RMDABFmGFRM-Dz6VmQxHgiioLM-Q5yzcj85zIqJvNrw0RL0qhvssQBG5Fta_jLXBUXeGEmciWA0lSfrdlS-zbfxsWqPzAvKyT_0B80m1o8K7ksFtyTPu-cHbCVGx4ciGeZUNrtOnevGQPUOE-tIvsxOPcqC3fPjyI3K4TN5GCCZHEyso1qmRFfsHtenq6EvD1_2DebcODnfnym-iNFyC4YsgqipToNxR3WPIgCu-WrSOk71-93ovs0hd1MhBYw03J4Xupjxy_URCFZm9Pp-9H3j_0hUKmhUWmpsQTpAT7FWvTT-MyyYkZ-9Y33-6KR3E-82kdfXIoEMbGJnfq2Z4Yh_lF3pfD-5FUzOzgnOy0UiTxusWOBbaVhNqmm6xmlHwQjBrax9Bqo7WzQpwXgXgooo6TeVz6pxpUl6V63d5o5XZaxYYilUpZ78qXpHQMwNFr6a2gct-dU8zLF6YaJHIaMp6XT9OD8r-w7SOkq1O7J-UGRqGbUVczYzhApY1Q-B2ZnO18P5KQnG97AbU_Sjk5Rnf6HJ-w-E8NOIwgo5jzloV_5Ck6w-DH_sL5FDca89BGuzwpQEH_h3ma43\",\"e\":\"AQAB\",\"d\":\"AIiu6F7k-ZcVKHNKUaX3a8tQzPb9gTf3xWKsnuNpJ-q_PG-Ko_EXwBqrFiYwG0ZiJcCbrXVV76zSPGCCal9E-e5H5YGUBcI2Wv-tiroTGcSipslYpxr1zwrozz47ZZKQ2QQfyvvpfdAMYvI5Oxmj7h-4yQJEcCMoPcf7eY-ODnKziP2HkSPdBwVaOpcVQyb2EcczS0VXHAPLCiVtftmD6qnFUA4H6b3BFLFq6BG-5gIWIHSjtUH8AwRiijs5mOVoIWTGJYe2HTpyU_BH-hCM_6_LCQrLT2jg9jBqsoBkRuJKIroolAvSEPOxVNnXqMKHoc6zNVFJ4IXn3rBVXlDlCm69xoe67-X2M4o8LXpdnwFtvao3YYKqAqv1kH0JZE9kJyY3odhXa-SRZpvOCoE3YpDr5UTlRkEWZATQjqtGP7JEq_RQwtDwM1NpANIl4cFAJVhUJbndjMeJqBcA4-NEV6bBjWkenw179H6UuWNXNzXklPsgtMnF_PwcBFKutwnFqHAE5g6w9iHQ5yG7_2m4zModfBiGiSy3cdQ2f3MEHRRoBmqooEGU_6Urrn6iyAFxk_sINEnT_7Emygle_QwP5N-BQuFpD_NWojGirWwOwiWYBHRBXP0ub17bNx7w4gha6CxHnXyJ0MZBayOIMrnQGeWC7o5a932LCTQfegdBh5xh\",\"p\":\"APQQSKxv01Oky-jENQwxiZcpI4a5PzLPFFCgEqIjSRamCzrCQ07e97iqhU1b8IvRwxDtX358pFKAq7tmwpN2QQb1T9fqUwCpeQuMwRsZwoaM7ZcTSj2FZ_2djN1ixQfzqQ21VxkMRbrdyExqCSJXnHMcLeiFmu81dVopV2iwDbUQv4jZe_ktPUTH4HKle48Y0v9pu22lD5cknAQGB1gUNfyJ0PbUxZMITrZDz4khhYgxqvJ7GluYRNv2tezV-bb5leXbSLDrRgTKqcl5ZjkgLm9FRNGZZAmlsCHEeB3nvCs2ePQYDuLgEkNtuu39kpLFJO6j70bjnvpaIAcDVpPmEE8\",\"q\":\"AM9L09Grg2uSrNUGfj9pfpsMn0k5kqV0n3WjX9z5ZLkwLNNrs0SJjb93haO2MPNlyYhctCpPKnfHJKZWaLhFDV6xr-ubf7c3DbBJjPhlV8dUkgmHfIqWDPl6pzN0xC61zC4IE15LgW_JEMpq53fRWnIHdufs-105QO8YOo0CVYKYjqut4hVbYRBSTaeVLb1vj_yhaL0qV7orQoTrpr6Bg20nftBBa-8Md_B5l0QyiSfvOjKnXsjULQdQGbtypQZvu2jUasnUVUQHBgeF5W5WFj8qCGGnmehqY6QissipLoRMcGPaV_gJKisgcorF7sSU_QzcBUmPk377LkzZXGNUYZk\",\"qi\":\"AILHVNisODhO4GC8P709DqGdVdufLZf2Bl7AwjWyYTkpEzEfQCHHUnmOoTCn-OEvnn9lWiaCaTijtlUmos0fCfvSQLk9elciIOmlRk8G1EtnnzYQsTmerLoMJBgQ02hhip8GK47Y7mbZIjaPB625Dv8F4RHd9ZiTzXTGcNc6bldWlNNbbqw9DWS1DORPhdQEPU424qcYvHq_eklFCujWukO8ul3FEZYnTcth2ODSFMb0a0SCuDGkGI8BDI-_4n6-4wIlAXtc8Vt9Ko8WxJjCK_v2Ae9x05eknWZj0JxuyoAjPtJApp0pt25omJwZr_lY5i8T0cL6dDF5nZcA9hN__eo\",\"dp\":\"ALL4hfI9BmCdxhFoX-YTJWw9dJnEmf1uMN12pHNVILGFDVMHRUg-5LT8BkhWFSzSoxJ0nsQoLm95f3Uqw6BS5RhvJx-T603e-K5phumSmD0GduuD77rxavJlZ_ioBwfvu5Yb1kS95RxEqi6uywft6wHWNiv-XUDwmJ-HFVvlTgfqwileIjT04argT0yC4PpsH73AEPs0QRx6chXZPeVu3K_Vd_Co0kEhpGavjy5l8H-QvGSXtRpZrJUIcxu7RSTSHQOzK7jgrjWxT5Q4e6eEW8ioqPByZRNV9rSsV9DGMAwYI9YLFk90NLBRdPQ0MBmEi7KbcEkxfVDkafv6jLBj0q0\",\"dq\":\"AJldyYY7dczVxMcKucbinwfJq-N6E_QTt5JKYDdV0F5utQtqiEQx3MyGejooJkk9yn_3zlfrIElj7cqe7XU_qWeg4L3Y2wHLWnZNxF1WZT4VZMJmGg9SeqDtTNz2C9tfJ4P695FxHX99681GkKAGJPtuaFuo6kQLgu4iJ9eBnZA0nIGJ8VXJuKNhsRBGf4PDEW1gYeRqemNDdEBxNHmHypusd9dOP7OpruccnnyXQwBnrtAhIjBFQldBvPgBFvUPH0GsvqE6VicxZxWTy635RRZQW8kcPfNFGxkpjsqE2OSKxTArL6BT733e0L-5NzD75cho1ASblA2DerriqcbXfCk\",\"kid\":\"" KID_1 "\"}"; const char pubkey_1_pem[] = "-----BEGIN PUBLIC KEY-----\n"\ "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxaF1egmmQ+0/AAEcv/Jd\n"\ "TCBxi7A05VvDvRgaYzOEWqJF3qv3b6ifD4E4C9+cir6tIyCOJ2WXE4TV9fCWBQjI\n"\ "omacdQjdEwMAEWYYVEz4PPpWZDEeCKKgsz5DnLNyPznMiom82vDREvSqG+yxAEbk\n"\ "W1r+MtcFRd4YSZyJYDSVJ+t2VL7Nt/Gxao/MC8rJP/QHzSbWjwruSwW3JM+75wds\n"\ "JUbHhyIZ5lQ2u06d68ZA9Q4T60i+zE49yoLd8+PIjcrhM3kYIJkcTKyjWqZEV+we\n"\ "16eroS8PX/YN5tw4Od+fKb6I0XILhiyCqKlOg3FHdY8iAK75atI6TvX73ei+zSF3\n"\ "UyEFjDTcnhe6mPHL9REIVmb0+n70feP/SFQqaFRaamxBOkBPsVa9NP4zLJiRn71j\n"\ "ff7opHcT7zaR19cigQxsYmd+rZnhiH+UXel8P7kVTM7OCc7LRSJPG6xY4FtpWE2q\n"\ "abrGaUfBCMGtrH0GqjtbNCnBeBeCiijpN5XPqnGlSXpXrd3mjldlrFhiKVSlnvyp\n"\ "ekdAzA0WvpraBy351TzMsXphokchoynpdP04Pyv7DtI6SrU7sn5QZGoZtRVzNjOE\n"\ "CljVD4HZmc7Xw/kpCcb3sBtT9KOTlGd/ocn7D4Tw04jCCjmPOWhX/kKTrD4Mf+wv\n"\ "kUNxrz0Ea7PClAQf+HeZrjcCAwEAAQ==\n"\ "-----END PUBLIC KEY-----"; const char privkey_1_pem[] = "-----BEGIN RSA PRIVATE KEY-----\n"\ "MIIJKwIBAAKCAgEAxaF1egmmQ+0/AAEcv/JdTCBxi7A05VvDvRgaYzOEWqJF3qv3\n"\ "b6ifD4E4C9+cir6tIyCOJ2WXE4TV9fCWBQjIomacdQjdEwMAEWYYVEz4PPpWZDEe\n"\ "CKKgsz5DnLNyPznMiom82vDREvSqG+yxAEbkW1r+MtcFRd4YSZyJYDSVJ+t2VL7N\n"\ "t/Gxao/MC8rJP/QHzSbWjwruSwW3JM+75wdsJUbHhyIZ5lQ2u06d68ZA9Q4T60i+\n"\ "zE49yoLd8+PIjcrhM3kYIJkcTKyjWqZEV+we16eroS8PX/YN5tw4Od+fKb6I0XIL\n"\ "hiyCqKlOg3FHdY8iAK75atI6TvX73ei+zSF3UyEFjDTcnhe6mPHL9REIVmb0+n70\n"\ "feP/SFQqaFRaamxBOkBPsVa9NP4zLJiRn71jff7opHcT7zaR19cigQxsYmd+rZnh\n"\ "iH+UXel8P7kVTM7OCc7LRSJPG6xY4FtpWE2qabrGaUfBCMGtrH0GqjtbNCnBeBeC\n"\ "iijpN5XPqnGlSXpXrd3mjldlrFhiKVSlnvypekdAzA0WvpraBy351TzMsXphokch\n"\ "oynpdP04Pyv7DtI6SrU7sn5QZGoZtRVzNjOECljVD4HZmc7Xw/kpCcb3sBtT9KOT\n"\ "lGd/ocn7D4Tw04jCCjmPOWhX/kKTrD4Mf+wvkUNxrz0Ea7PClAQf+HeZrjcCAwEA\n"\ "AQKCAgEAiK7oXuT5lxUoc0pRpfdry1DM9v2BN/fFYqye42kn6r88b4qj8RfAGqsW\n"\ "JjAbRmIlwJutdVXvrNI8YIJqX0T57kflgZQFwjZa/62KuhMZxKKmyVinGvXPCujP\n"\ "PjtlkpDZBB/K++l90Axi8jk7GaPuH7jJAkRwIyg9x/t5j44OcrOI/YeRI90HBVo6\n"\ "lxVDJvYRxzNLRVccA8sKJW1+2YPqqcVQDgfpvcEUsWroEb7mAhYgdKO1QfwDBGKK\n"\ "OzmY5WghZMYlh7YdOnJT8Ef6EIz/r8sJCstPaOD2MGqygGRG4koiuiiUC9IQ87FU\n"\ "2deowoehzrM1UUnghefesFVeUOUKbr3Gh7rv5fYzijwtel2fAW29qjdhgqoCq/WQ\n"\ "fQlkT2QnJjeh2Fdr5JFmm84KgTdikOvlROVGQRZkBNCOq0Y/skSr9FDC0PAzU2kA\n"\ "0iXhwUAlWFQlud2Mx4moFwDj40RXpsGNaR6fDXv0fpS5Y1c3NeSU+yC0ycX8/BwE\n"\ "Uq63CcWocATmDrD2IdDnIbv/abjMyh18GIaJLLdx1DZ/cwQdFGgGaqigQZT/pSuu\n"\ "fqLIAXGT+wg0SdP/sSbKCV79DA/k34FC4WkP81aiMaKtbA7CJZgEdEFc/S5vXts3\n"\ "HvDiCFroLEedfInQxkFrI4gyudAZ5YLujlr3fYsJNB96B0GHnGECggEBAPQQSKxv\n"\ "01Oky+jENQwxiZcpI4a5PzLPFFCgEqIjSRamCzrCQ07e97iqhU1b8IvRwxDtX358\n"\ "pFKAq7tmwpN2QQb1T9fqUwCpeQuMwRsZwoaM7ZcTSj2FZ/2djN1ixQfzqQ21VxkM\n"\ "RbrdyExqCSJXnHMcLeiFmu81dVopV2iwDbUQv4jZe/ktPUTH4HKle48Y0v9pu22l\n"\ "D5cknAQGB1gUNfyJ0PbUxZMITrZDz4khhYgxqvJ7GluYRNv2tezV+bb5leXbSLDr\n"\ "RgTKqcl5ZjkgLm9FRNGZZAmlsCHEeB3nvCs2ePQYDuLgEkNtuu39kpLFJO6j70bj\n"\ "nvpaIAcDVpPmEE8CggEBAM9L09Grg2uSrNUGfj9pfpsMn0k5kqV0n3WjX9z5ZLkw\n"\ "LNNrs0SJjb93haO2MPNlyYhctCpPKnfHJKZWaLhFDV6xr+ubf7c3DbBJjPhlV8dU\n"\ "kgmHfIqWDPl6pzN0xC61zC4IE15LgW/JEMpq53fRWnIHdufs+105QO8YOo0CVYKY\n"\ "jqut4hVbYRBSTaeVLb1vj/yhaL0qV7orQoTrpr6Bg20nftBBa+8Md/B5l0QyiSfv\n"\ "OjKnXsjULQdQGbtypQZvu2jUasnUVUQHBgeF5W5WFj8qCGGnmehqY6QissipLoRM\n"\ "cGPaV/gJKisgcorF7sSU/QzcBUmPk377LkzZXGNUYZkCggEBALL4hfI9BmCdxhFo\n"\ "X+YTJWw9dJnEmf1uMN12pHNVILGFDVMHRUg+5LT8BkhWFSzSoxJ0nsQoLm95f3Uq\n"\ "w6BS5RhvJx+T603e+K5phumSmD0GduuD77rxavJlZ/ioBwfvu5Yb1kS95RxEqi6u\n"\ "ywft6wHWNiv+XUDwmJ+HFVvlTgfqwileIjT04argT0yC4PpsH73AEPs0QRx6chXZ\n"\ "PeVu3K/Vd/Co0kEhpGavjy5l8H+QvGSXtRpZrJUIcxu7RSTSHQOzK7jgrjWxT5Q4\n"\ "e6eEW8ioqPByZRNV9rSsV9DGMAwYI9YLFk90NLBRdPQ0MBmEi7KbcEkxfVDkafv6\n"\ "jLBj0q0CggEBAJldyYY7dczVxMcKucbinwfJq+N6E/QTt5JKYDdV0F5utQtqiEQx\n"\ "3MyGejooJkk9yn/3zlfrIElj7cqe7XU/qWeg4L3Y2wHLWnZNxF1WZT4VZMJmGg9S\n"\ "eqDtTNz2C9tfJ4P695FxHX99681GkKAGJPtuaFuo6kQLgu4iJ9eBnZA0nIGJ8VXJ\n"\ "uKNhsRBGf4PDEW1gYeRqemNDdEBxNHmHypusd9dOP7OpruccnnyXQwBnrtAhIjBF\n"\ "QldBvPgBFvUPH0GsvqE6VicxZxWTy635RRZQW8kcPfNFGxkpjsqE2OSKxTArL6BT\n"\ "733e0L+5NzD75cho1ASblA2DerriqcbXfCkCggEBAILHVNisODhO4GC8P709DqGd\n"\ "VdufLZf2Bl7AwjWyYTkpEzEfQCHHUnmOoTCn+OEvnn9lWiaCaTijtlUmos0fCfvS\n"\ "QLk9elciIOmlRk8G1EtnnzYQsTmerLoMJBgQ02hhip8GK47Y7mbZIjaPB625Dv8F\n"\ "4RHd9ZiTzXTGcNc6bldWlNNbbqw9DWS1DORPhdQEPU424qcYvHq/eklFCujWukO8\n"\ "ul3FEZYnTcth2ODSFMb0a0SCuDGkGI8BDI+/4n6+4wIlAXtc8Vt9Ko8WxJjCK/v2\n"\ "Ae9x05eknWZj0JxuyoAjPtJApp0pt25omJwZr/lY5i8T0cL6dDF5nZcA9hN//eo=\n"\ "-----END RSA PRIVATE KEY-----\n"; const char pubkey_2_jwk[] = "{\"keys\":[{\"kty\":\"RSA\",\"n\":\"AKLIxChfF0q7zzxrkGFM_Qu8Cx76Cd1AEd76RxXSWvmfzfUk12FYeu1LQpzO0VvpmzuwbhRZk9Vgdo5y51s2k83ht4QvwOOIpr9rlNZrTiILeYhYheTfl2bPaKbDPY1rFFtifLhTytaIXZ18VpBQH-30ycdnyWzu34MYzORiKu7NX-72z2gGjUx_IeIZxkSrloPp9eRSo2JhlAw-UkI-XcjK22cf_A2xptF1__60Ly393lz9q9xxRU21p_R3IpmRenLKoZoeIOEbeTrtiXGZa5K20qPIMjjXpWfpLyiicAGRtPuj7uPjIEh3cBntQdo1JnkNRAHpbCFrywKwh-HP7dyWBXQ3vXzH1HKH-gt2rHjGmvB1tVh4Wh_Hq0pz-ZJRdDRoYUVgdZWEglkP4U_coL5UyUPL_9qHBW7GVIvJUOmy_S3OKYh8jDniv9qM5td8JPThxny8SmBLc7OVeqHr4A-3WidK7Uv4dtYi4Lqirea8Hu0c8t26DMVZTRhbsdEUj-a846BkP75LrkoloMsoJkyIXnjlf8AnbOiA0e7Ns64AcUL7FCtYrKeWIqR1aOADrj8TXMRp8S5LdAwOwDmAsIdo54FVpoyoM0mBtBB4oi1K18jBaeMYckwS0kKVmZ-uaPI8AAas1raHTIq7GbGsCBooc17cNNL0USQ51aROxEFF\",\"e\":\"AQAB\",\"kid\":\"" KID_2 "\"}]}"; const char privkey_2_jwk[] = "{\"kty\":\"RSA\",\"n\":\"AKLIxChfF0q7zzxrkGFM_Qu8Cx76Cd1AEd76RxXSWvmfzfUk12FYeu1LQpzO0VvpmzuwbhRZk9Vgdo5y51s2k83ht4QvwOOIpr9rlNZrTiILeYhYheTfl2bPaKbDPY1rFFtifLhTytaIXZ18VpBQH-30ycdnyWzu34MYzORiKu7NX-72z2gGjUx_IeIZxkSrloPp9eRSo2JhlAw-UkI-XcjK22cf_A2xptF1__60Ly393lz9q9xxRU21p_R3IpmRenLKoZoeIOEbeTrtiXGZa5K20qPIMjjXpWfpLyiicAGRtPuj7uPjIEh3cBntQdo1JnkNRAHpbCFrywKwh-HP7dyWBXQ3vXzH1HKH-gt2rHjGmvB1tVh4Wh_Hq0pz-ZJRdDRoYUVgdZWEglkP4U_coL5UyUPL_9qHBW7GVIvJUOmy_S3OKYh8jDniv9qM5td8JPThxny8SmBLc7OVeqHr4A-3WidK7Uv4dtYi4Lqirea8Hu0c8t26DMVZTRhbsdEUj-a846BkP75LrkoloMsoJkyIXnjlf8AnbOiA0e7Ns64AcUL7FCtYrKeWIqR1aOADrj8TXMRp8S5LdAwOwDmAsIdo54FVpoyoM0mBtBB4oi1K18jBaeMYckwS0kKVmZ-uaPI8AAas1raHTIq7GbGsCBooc17cNNL0USQ51aROxEFF\",\"e\":\"AQAB\",\"d\":\"IHgLKEJPUwjC_To3QjEpB_4p-bPF4-uzpNYm96NNohzN5-fBThln64znbH-UItEltXIrgsObSSREgYVJwFfSg25SPHuJ7diD6gp7VYlxvDittRRzIIO4nzkflqO600pYdSHf3qRYARKSGaeDXKWeuMfqt2tsMd4zluKLe8JY7ejpCbERDZ7A8FErYP3jHi9bhlRUR4Z0MFtmPErx_WSTMEnGGXu3usOEkqMGvLcT8giBIes3LHErcaSaK5jXvenWkq74LNV1mXDxbV-T2qTPYCQ3P5Pe8JeS1nccgO5liOqXPtoj_DCBb_Li2UkJqYyQb_TFa9wzRTdK9u5fLBtdRiDzBIPWNNbhCuD2eaAEkTSW3XXUzmPSlrGC-CMUVemVOWRntPhEFgXiZz36-HMiXefX1gtYQPBVAQI1nPDYppjAGj7u-sVvtQqsKB4AQzc4ULq_ttHiJrw_TGbFSQ0QCPJ2HvnFTeBWixKICvYiRXMo7_kdUBKH7_P_jaoomzWbX39phfWOWC62UN29HjQM3GN1bkMjdolrKo5l8uBdQD_GVAMqdkund6Jw8fpuLmuhFAeOtoMVAsW2Id-CGy_jsi19cbgRHrVHe4_vu3yvvCuQ5MP0Rndivg9JvLM-Lj83JsbWfUG2UEhPycgTPFcwuTKstTciXQSXLKLFetrcsME\",\"p\":\"AM_5kF7BoxK7Tvus6_se2ExfT3lKwE7EzbTAwEhpW1CBi13AsK-bBNZXE-83zF0usZz9DUJ5BRyH4LATxMDCkp0Vza0tdu00778VOgs8wzuStbu6uoc1L7g4Y9ImYwkqLQQuA4IN_RtSdzeWGOA_to7UuMlrHSHQrlAf0npev9ceo35pBUoC0sVAE8nF9Ov2_lkgBRkuNGeQfp_IHyhdjr7r3U4tbja_E-BEFzGUUBvIYJ-1kXKx7ipmpyP47FrOUojjwKyn-xPpu4JYLrvQ-XaiA-SiO5AlBhn_iIXEb3FqBdnWQ0xSoss31jYnVVo-7muR_sBm5UmA0PtVDtrBcSs\",\"q\":\"AMhfwi-RxWW1JgP4oHPBTeOrw9TkFpVahRF5PAeZICuoVxIgPZUaNP1Hhuv6Otb4I7SNRdQioxz6jVGogpk11QV6llsV4skanAgRqWT8ds7qdAsXAhkFCmD6mAaqGs4qVLUSGrMq_KhqBcCdt2pvhy3yuIQmv_iWSJ-XJlsXbWW8DSNa2mpefenKB5dt35zKZp5xzn_UVUoC4HK7c2ZaLvs0KuUE8pgaAdAFogk7emgQfXXK9TPPRd9jullPZTx3UQ104mShX6Hz_vfuTUipPBFefzSm3OKtXM5ejkDQdYmooxnVtR20SujjJ_0D0j66v-G9L8dd-_HcNUgsp57Kf08\",\"qi\":\"bNq-mJjepyGY5mXTlzAcBFY5HqlDx-Ssapo1waswzRhnXKB15zqU-LQfotBBp7pKxWCytwgOmV2toZf_R0Sqinohh52D0mCvTYldTB5tUpKpnhEGZuOZ24BTtrqNAw2X5NRKTflttiVJgfrMCSDIR8NNX4awGB8iUNZLyn4e5Zjfa7cLCwn7ngINpU1TaCY9gbHSsYcVcpX7ro6Vr93ox6cRtapBWElzAPXDF0YKMiKzA4fTqVtbamVHz64k7xgWVwdWXDbdqOWOtI0weeeHMy2Y9Ktz7eXh0rK0fG1c35qJ-V61oXdyfapmI7uGH2R5J-Q2LDJw_3AMGb6oCsPfdQ\",\"dp\":\"I8yQlk78lA_b86R7ZlmT3-mUE4vTeHuV8SQwtQY1qrDx3Wx6vW-QsJiCnO3c5rlP53cDnkqYn6Wf_o8YkhmsBRAovEOUMhanohu0RxTpgkqpr6vfycBU-3_xZs4mxAAXiZ2mCu__foF-dfoHRCqTcRiaykj-1cBHERG5OEkw-oWSnQLU3z2HLF7wSQ4jL67vb0X8uq3iZWVQ9o4LFvaryJ9vE7LsQs43TKZL28Ps2ituvm8Rn02TcocDBEUn4iWbvWZ-1vl_VZkpJrGpMbkyB8KxqtxmJlTJLRZ4WJZMnJgkc6_XG78puJNe8yloHsWwYqHZ2SKdGz7qOikVCoC7yw\",\"dq\":\"atXb1L81b8BBT7a93lo_7FdF5_nhLKsB7kokvqxfYce0_R4hl6FMhYsgnitiOgI-D2OPysbZD3dr6BEf6Q6x0OUGy_QEYlOExCyelBCkTDjnvI38-Vgdq42Rh2QlPK2HUrAfek4-PpGhFY1CIUbr3Yzf4t5CVwnSGP1fXwxDsQ2uN56WfEZ7fi7RE2Vq589nHa3ye2e8PeUAxUu7AOSuzhOHl2qm6oBbXQ3T0nZbEqdQLYEUchZe2_fxgPL7OF0p4zHiD-OW-OP-mzT9EfPh6iTnUCxz84yZwhLaaCZ9tPMsW3b9xaO-mSOcy6PA8t9htbvIgNVUoyVVZ3EfwmOXsw\",\"kid\":\"" KID_2 "\"}"; const char pubkey_2_pem[] = "-----BEGIN PUBLIC KEY-----\n"\ "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAosjEKF8XSrvPPGuQYUz9\n"\ "C7wLHvoJ3UAR3vpHFdJa+Z/N9STXYVh67UtCnM7RW+mbO7BuFFmT1WB2jnLnWzaT\n"\ "zeG3hC/A44imv2uU1mtOIgt5iFiF5N+XZs9opsM9jWsUW2J8uFPK1ohdnXxWkFAf\n"\ "7fTJx2fJbO7fgxjM5GIq7s1f7vbPaAaNTH8h4hnGRKuWg+n15FKjYmGUDD5SQj5d\n"\ "yMrbZx/8DbGm0XX//rQvLf3eXP2r3HFFTbWn9HcimZF6csqhmh4g4Rt5Ou2JcZlr\n"\ "krbSo8gyONelZ+kvKKJwAZG0+6Pu4+MgSHdwGe1B2jUmeQ1EAelsIWvLArCH4c/t\n"\ "3JYFdDe9fMfUcof6C3aseMaa8HW1WHhaH8erSnP5klF0NGhhRWB1lYSCWQ/hT9yg\n"\ "vlTJQ8v/2ocFbsZUi8lQ6bL9Lc4piHyMOeK/2ozm13wk9OHGfLxKYEtzs5V6oevg\n"\ "D7daJ0rtS/h21iLguqKt5rwe7Rzy3boMxVlNGFux0RSP5rzjoGQ/vkuuSiWgyygm\n"\ "TIheeOV/wCds6IDR7s2zrgBxQvsUK1isp5YipHVo4AOuPxNcxGnxLkt0DA7AOYCw\n"\ "h2jngVWmjKgzSYG0EHiiLUrXyMFp4xhyTBLSQpWZn65o8jwABqzWtodMirsZsawI\n"\ "GihzXtw00vRRJDnVpE7EQUUCAwEAAQ==\n"\ "-----END PUBLIC KEY-----\n"; const char privkey_2_pem[] = "-----BEGIN RSA PRIVATE KEY-----\n"\ "MIIJJwIBAAKCAgEAosjEKF8XSrvPPGuQYUz9C7wLHvoJ3UAR3vpHFdJa+Z/N9STX\n"\ "YVh67UtCnM7RW+mbO7BuFFmT1WB2jnLnWzaTzeG3hC/A44imv2uU1mtOIgt5iFiF\n"\ "5N+XZs9opsM9jWsUW2J8uFPK1ohdnXxWkFAf7fTJx2fJbO7fgxjM5GIq7s1f7vbP\n"\ "aAaNTH8h4hnGRKuWg+n15FKjYmGUDD5SQj5dyMrbZx/8DbGm0XX//rQvLf3eXP2r\n"\ "3HFFTbWn9HcimZF6csqhmh4g4Rt5Ou2JcZlrkrbSo8gyONelZ+kvKKJwAZG0+6Pu\n"\ "4+MgSHdwGe1B2jUmeQ1EAelsIWvLArCH4c/t3JYFdDe9fMfUcof6C3aseMaa8HW1\n"\ "WHhaH8erSnP5klF0NGhhRWB1lYSCWQ/hT9ygvlTJQ8v/2ocFbsZUi8lQ6bL9Lc4p\n"\ "iHyMOeK/2ozm13wk9OHGfLxKYEtzs5V6oevgD7daJ0rtS/h21iLguqKt5rwe7Rzy\n"\ "3boMxVlNGFux0RSP5rzjoGQ/vkuuSiWgyygmTIheeOV/wCds6IDR7s2zrgBxQvsU\n"\ "K1isp5YipHVo4AOuPxNcxGnxLkt0DA7AOYCwh2jngVWmjKgzSYG0EHiiLUrXyMFp\n"\ "4xhyTBLSQpWZn65o8jwABqzWtodMirsZsawIGihzXtw00vRRJDnVpE7EQUUCAwEA\n"\ "AQKCAgAgeAsoQk9TCML9OjdCMSkH/in5s8Xj67Ok1ib3o02iHM3n58FOGWfrjOds\n"\ "f5Qi0SW1ciuCw5tJJESBhUnAV9KDblI8e4nt2IPqCntViXG8OK21FHMgg7ifOR+W\n"\ "o7rTSlh1Id/epFgBEpIZp4NcpZ64x+q3a2wx3jOW4ot7wljt6OkJsRENnsDwUStg\n"\ "/eMeL1uGVFRHhnQwW2Y8SvH9ZJMwScYZe7e6w4SSowa8txPyCIEh6zcscStxpJor\n"\ "mNe96daSrvgs1XWZcPFtX5PapM9gJDc/k97wl5LWdxyA7mWI6pc+2iP8MIFv8uLZ\n"\ "SQmpjJBv9MVr3DNFN0r27l8sG11GIPMEg9Y01uEK4PZ5oASRNJbdddTOY9KWsYL4\n"\ "IxRV6ZU5ZGe0+EQWBeJnPfr4cyJd59fWC1hA8FUBAjWc8NimmMAaPu76xW+1Cqwo\n"\ "HgBDNzhQur+20eImvD9MZsVJDRAI8nYe+cVN4FaLEogK9iJFcyjv+R1QEofv8/+N\n"\ "qiibNZtff2mF9Y5YLrZQ3b0eNAzcY3VuQyN2iWsqjmXy4F1AP8ZUAyp2S6d3onDx\n"\ "+m4ua6EUB462gxUCxbYh34IbL+OyLX1xuBEetUd7j++7fK+8K5Dkw/RGd2K+D0m8\n"\ "sz4uPzcmxtZ9QbZQSE/JyBM8VzC5Mqy1NyJdBJcsosV62tywwQKCAQEAz/mQXsGj\n"\ "ErtO+6zr+x7YTF9PeUrATsTNtMDASGlbUIGLXcCwr5sE1lcT7zfMXS6xnP0NQnkF\n"\ "HIfgsBPEwMKSnRXNrS127TTvvxU6CzzDO5K1u7q6hzUvuDhj0iZjCSotBC4Dgg39\n"\ "G1J3N5YY4D+2jtS4yWsdIdCuUB/Sel6/1x6jfmkFSgLSxUATycX06/b+WSAFGS40\n"\ "Z5B+n8gfKF2OvuvdTi1uNr8T4EQXMZRQG8hgn7WRcrHuKmanI/jsWs5SiOPArKf7\n"\ "E+m7glguu9D5dqID5KI7kCUGGf+IhcRvcWoF2dZDTFKiyzfWNidVWj7ua5H+wGbl\n"\ "SYDQ+1UO2sFxKwKCAQEAyF/CL5HFZbUmA/igc8FN46vD1OQWlVqFEXk8B5kgK6hX\n"\ "EiA9lRo0/UeG6/o61vgjtI1F1CKjHPqNUaiCmTXVBXqWWxXiyRqcCBGpZPx2zup0\n"\ "CxcCGQUKYPqYBqoazipUtRIasyr8qGoFwJ23am+HLfK4hCa/+JZIn5cmWxdtZbwN\n"\ "I1raal596coHl23fnMpmnnHOf9RVSgLgcrtzZlou+zQq5QTymBoB0AWiCTt6aBB9\n"\ "dcr1M89F32O6WU9lPHdRDXTiZKFfofP+9+5NSKk8EV5/NKbc4q1czl6OQNB1iaij\n"\ "GdW1HbRK6OMn/QPSPrq/4b0vx1378dw1SCynnsp/TwKCAQAjzJCWTvyUD9vzpHtm\n"\ "WZPf6ZQTi9N4e5XxJDC1BjWqsPHdbHq9b5CwmIKc7dzmuU/ndwOeSpifpZ/+jxiS\n"\ "GawFECi8Q5QyFqeiG7RHFOmCSqmvq9/JwFT7f/FmzibEABeJnaYK7/9+gX51+gdE\n"\ "KpNxGJrKSP7VwEcREbk4STD6hZKdAtTfPYcsXvBJDiMvru9vRfy6reJlZVD2jgsW\n"\ "9qvIn28TsuxCzjdMpkvbw+zaK26+bxGfTZNyhwMERSfiJZu9Zn7W+X9VmSkmsakx\n"\ "uTIHwrGq3GYmVMktFnhYlkycmCRzr9cbvym4k17zKWgexbBiodnZIp0bPuo6KRUK\n"\ "gLvLAoIBAGrV29S/NW/AQU+2vd5aP+xXRef54SyrAe5KJL6sX2HHtP0eIZehTIWL\n"\ "IJ4rYjoCPg9jj8rG2Q93a+gRH+kOsdDlBsv0BGJThMQsnpQQpEw457yN/PlYHauN\n"\ "kYdkJTyth1KwH3pOPj6RoRWNQiFG692M3+LeQlcJ0hj9X18MQ7ENrjeelnxGe34u\n"\ "0RNlaufPZx2t8ntnvD3lAMVLuwDkrs4Th5dqpuqAW10N09J2WxKnUC2BFHIWXtv3\n"\ "8YDy+zhdKeMx4g/jlvjj/ps0/RHz4eok51Asc/OMmcIS2mgmfbTzLFt2/cWjvpkj\n"\ "nMujwPLfYbW7yIDVVKMlVWdxH8Jjl7MCggEAbNq+mJjepyGY5mXTlzAcBFY5HqlD\n"\ "x+Ssapo1waswzRhnXKB15zqU+LQfotBBp7pKxWCytwgOmV2toZf/R0Sqinohh52D\n"\ "0mCvTYldTB5tUpKpnhEGZuOZ24BTtrqNAw2X5NRKTflttiVJgfrMCSDIR8NNX4aw\n"\ "GB8iUNZLyn4e5Zjfa7cLCwn7ngINpU1TaCY9gbHSsYcVcpX7ro6Vr93ox6cRtapB\n"\ "WElzAPXDF0YKMiKzA4fTqVtbamVHz64k7xgWVwdWXDbdqOWOtI0weeeHMy2Y9Ktz\n"\ "7eXh0rK0fG1c35qJ+V61oXdyfapmI7uGH2R5J+Q2LDJw/3AMGb6oCsPfdQ==\n"\ "-----END RSA PRIVATE KEY-----\n"; const char jwks_privkey[] = "{\"keys\":[{\"kty\":\"RSA\",\"n\":\"ANGS3uxrb3Nu1fN8yP-JOvl_vyj7qu-6EoXYkBlXKxtNEaugyuVb2VByJYcY-mVVmQrlGv3usvLO_42Mub5xlsOrhpcz2ccK3Nup7avKHTTyfub6O6_ozwrN8Z1DHOfrtS11NzLmPOQkyGVT259w8VJFGy4r7KAdbqwsELuJnRO4OuHKvDpxPQ5cTysgFRqaDQFxflrhZlVupcmflImvltycS-6y_1LoRr8BLZ-9-P7PvDGM0bBenJjciib9jY-E4CL5xbbHCSldJ-KlQSW7al1a9rgJEE6ALpJHCA5TgwXP-e03UkNR9gj7VSN4yHgierw7y-ii3NKOCkh8oPJkgyM\",\"e\":\"AQAB\",\"d\":\"Buq1BtKI7CNlVlLxA1YRJzjrIzwjKSRYBijeoKd62bzSb3qwF5_PbVNHH81YPk3z7iMfn-_n4hGo55AUdU-kbTLVD1p7vpECNxHFsVzLV_sE6zSpEaY6Zx3i8pDLfHAQG775omls6w63unhpEDsXC-MSAZUbizFNfMs7hiUFlSf5FHxyzyHez7wXTaLhxZRwIhXX5eLGIs-lSt6dbj9JT0Ws2rUXsW0fuHf-L2XjW9ymnrW5sFz4Bpna3MfoWB3J76da_bWHPAEXmYMfwfTRy-XD8ry6WsmTi1HkPepk9FGPb1iGn3W72y7HV_jnRiV8nRHtsYwAwUyK0oNYOJDhcQ\",\"p\":\"ANHxqqBCBcjsPOxNXP7xqYqEfYRR3ROQNMbJtslfzvbpCKNLi-1JnvSIYftIidKGQT50dTBP_LGnHlJtilOPPlTCIi12zTGveilHz8Hx2zMaqcCTAtl0Jh-KUJE366oFMUKa_2I2gsLqAzwkPBmxHv7qQ3TtnqueAu6JGOlLgpkL\",\"q\":\"AP-MaKAabOFrwBtLVgi7M0ug0S68ICJLWN4cNPpnN-d9V8Gu4D9N9Ba6vS5jgP2rF7mVSNGJQVMHTDATEkeb5UBKFWrpomJ6rTC3Jz9mdo4MLs2yYKA-17Yo0EmmKQRaoGb4RiTWcU61FdxPkfymBWfe7MTgEUs1GZeoaLhBFf1J\",\"qi\":\"eHV_XLC9OtXpb1HaQGi2mjuicbzRKd90yQnSeuM-GUyI9qJ2BEgoUvDlQqmHsp1kSMRK0rsXJbVcABT3HvSVtbnfSlBKwm2tkCr_G5niv1C6NzTu5rDHMikCIuU5EXAByF88rmpEWfRBHiLOviFacS3nWFIVS4qjy3QhcToUn2g\",\"dp\":\"AI0yuOvxm4xnfg37dhktFTbJJtXQbRyUNzqfPaUwH7UmQ5332FMt2Y9jDmr_fVou20CS-KIWmcAtwpHzhD3wsB43Nt9W8GiDOWj8GDm7XQ6A1zxiY18249EAqHESqBgASAIg1rQKL2XCF4ziXd11p4AQtG-2xKltq1EcublmBJ7D\",\"dq\":\"ALwpJ1RTgL4ON3ohY2y0YdWayMtPi52Uuw93125Ul-4j9XzQBZ-3BzXBM9C3RWACHUrxZ5eZJ3c6FVgEgt4lJIzMv65j7sPyLhmDkit1h2-Z-tBMOidjfjNLI-pGpUbmb9rBjexL1Uwtu7XnBMfxemN72fwwO6uToo9vWgPpMIa5\",\"kid\":\"" KID_1 "\"},{\"kty\":\"RSA\",\"n\":\"ALLesxikdsNXzAB1rjsjZk4qB7n1wZnxhzDuctRmFeeecVZbClbF_4AdboKKJU0Ylac1q1HMpqAnLJceXGy4CFyCl_P3R_jRUWQ0qwQOCqq1SgLY1xPHKdU7VMrCbo9pm-bOxoQ1Ac6QgpOZqmpOlvhmgvDQP-Yfu3fHtquMEl69ztq8tECKcJpbkBxeqy5QsfGfeBPoNSwTLKv2L-wGYah6OdugmrhflMjrSa3ZbGWcQxzc5Fpr0-SVLpq5k45kbKz7qxlGDNd545mkAXchuTar_IMOqBQ_u6GJ2hGm38DJsAhNTnudfPk5-c6aZyZgJ1ePgOoQG6SgLhwu7XD2Gf0\",\"e\":\"AQAB\",\"d\":\"J0b9vuCGb7i8xDETNEs-sNVL4wrTG3HNBPKnZnqQPs8tBhBxwcIyq7hKxwF81WQboJ8JYqn0wOA5S2nQU9NJir7mjRz0we981us1zmsi7n9mpB9ngyFNz1P695cgXf-Ly3AGaYuWPPzAn5aztCpTvnIMOMMR9P9s1A2X0C2u7vyS_vT4rnt40Emr8sLK-iKa0RxSzNkS520KexppBqJvrEwEuKwUrXvF89Pn4LZcF43KJBRKzPPXRyeGayK0RiWI6rV13vGbg1p25ojswizuj5I6xSZqun1QlTNkh4lbGH64yL9oDxylyruMB5R-Zyg4iQAgjK_bS-xdmQ3YU76AwQ\",\"p\":\"AN47ss-tPevD9-nJMPVXEBu_IxJ0gDH5Stx_VAevXBhIbil4mnldvJtgQDnaPGp-s120GXtTaMKFKCbDlX2w0gX5RkZdtRQy4Z9nrx2AGYi_V5pPF6UwITKPDAwqBqcSdmIekeSRKT5OhLjHpl8swK0hHycAq4V8-r-o2I1OA11N\",\"q\":\"AM4MR-TDU4Ex7BqoFu1qJIRFZJ1jSDXaLU1qHSEn_HvPuzaROaiTQCq-m-AWh-nMsObxADJyW0dl0Mxgzpo8P27Biqd8ujcwfLBnrqMNL0a2CgmpML0JJ616GCnSPuMZ6ngry_FsI-38x6t0s5CC1d0PxZX4ZkbbG0qZM7vYeBdx\",\"qi\":\"Tc6npYyctyc0ROYTU06BkaoW9lA0eXPYQ6vHWx8foP-KDqBSEmgXiyW9q6UUSVsbPFi1YsMB5X38bbXIr5qcLUB_eFWyuEUgxAtImDA-kKyYqAjdrGqE9w1Eol6P_b0Kfp4NxdvMx9sph9__-uaSIUjFAKhbO5cQ1lb_lJX1iSQ\",\"dp\":\"aegJTu9AkxrRCpjWvBTBmHd-P01FyosDIhGL-h2Xxfq-hQT0mOIS__jeorNeF2JKGF27xwn44rqSZ-bNVxjs_evNkbsWkImu_EhtK6HgiUqmdOM8YXyOVYnWM_XTNcuWnyvyWvrSrN6-YVDD-JdbOyaNsgiftP3agXv1t2F2OQ\",\"dq\":\"IxiuakXnBFuXhtbYyOnKfIlBYRmXJC0ciFIp0gr4k7JHhjzoYFFsnZtH-7x6vBU0kLG-Qdl-uMOb9CQNLPJUL65hyrORGHN22alfcsB9LAM35HtgJOLZUlA4q273bUFt0bhMDbxIxnHHJOpE57mRag0Ur73W1fzeQi-kQzvVANE\",\"kid\":\"" KID_2 "\"},{\"kty\":\"RSA\",\"n\":\"ALOmOGou1SeK8IvwUCzX6D1qDsYdCsAO91aZv9lKJqLJ1sat7EsH_onBVbTr4OV8RH5G09CAh3dWmm7sJUPAgYWjcNtKIGG6bmcpqw6B1QNX4cc6g_1yeALyxMQoxUUkIXF0nhILz2Zu4163J8BmmIq7TVjMrrOmfIO044Z9cdZcagv1rP0GOcMy5qh03Uu_yKj0e7OBTa_FrpOZkRcS-Uq2j4kUGJKydtWvEBc3IpEeal_ZIlaTIvCFhfMCWE377ne0w3BTHTlCVE9n-JJ3UCENIxVfXxvP3zhtBOkNxIqMBUO8RxBW0ugDHu269By5WSBekuU2oLRb6lQkSco9ECM\",\"e\":\"AQAB\",\"d\":\"F6BVrQlJuTCZoB3TvYILpgALv1xUbJvLRZVk2MPavvACkhCPkfKUNDO7_NZEtomYTG8uLi6pIjW-i7X81KM9pYCwN_bQuWmWWXTubTL_-7eUFuqIL03doK2i2RVvlD9DMrOekksBVLxipLM7xB76Esy9SF9q3m-X0o54mdhnn0RBsBY0pr9aPPoch7KXkkNoA2jMZb8DQpkE2x3oz2dS4BzlW46hmVwWHwHaefgIgKaNkNOhi39RnxcRZyptnEjtiD3y6N-Qq2UZWruglY_7LcCA0hYmor7J486w1KH9Gfy3SSii-sEEa-Rb2-s-Be2mUgj3AC47OQ_AaJ_D85GBoQ\",\"p\":\"AM7EW-1rcU1KEj7K1nJSAnRuduY4c3B3wUFdndiAG1U_7c0bGGcsPlIcEJu8nz6wHR-zy7bsm2PNG0AmWVu1GsV3o2W0h7eR6iw8EVhmTDYORzdYG49TM2ZvsWZNueQrn8cLGssB-exeLBkpM4_V4BuhoIADz0refAvSdFsnwnzx\",\"q\":\"AN5s4SjGH8e44xGcH2enOoOUIuFaR-a8JwQIXkYcDqbDAG-57loqjfCnrgiZ19L87FDWumRXUa4jAi7NpTEHiFPTQ2EnkX37DQxPMCcDXvgLAC3HVauy8iwiverXeqzHib5jZ1qhRKic4qSxorB8jygPSvLMVpJMtlUBdZSob25T\",\"qi\":\"b9q5wu5Nm2K87k462BnKIOTHBD3BOH5YQkisOPqA2_C5E9ur7eYZg4c0M1SdauFk_fw7NzZTpGiVfKpqUxtuBIaOsls1lIJCRFLJQLQYBUrG2F1lpMLex7Azisa8qdqDTwTi1N0GBA1_Tl_-1nPuihaL51aTjK3RX_GY7K0R8y8\",\"dp\":\"AK0t470AT56nmo6DP4fyzmGMoAOFZpLdirzf1zQdYEdPyzIOLqtDcFM_dF2sZ0iPI2WJJufoVuIJSXi6Zf-cuXaJFQ87XKzRBuzTxsderxhbbySYpESwMA3tIQ2JrlfAfgutblx4JEboPVE5pBklzpX2EsMF7dpMbGNOeuxaadhR\",\"dq\":\"ALkXsBMnNEEWmVcJLZUrM2VVe-U-JMFc57bSY-lB5eteMNnIxxGfgfi3APtFUrXQJbNrzTY898rKdUGPfh0b4JWpI9QQgmFs7kHFEBQXGQue0-pEjAVS53ZU_ugTopFvhy-5NsPTmfgaffyBmhn1vYefmkYMRyp9zelVSoyH0hrj\",\"kid\":\"" KID_3 "\"}]}"; const char jwks_pubkey[] = "{\"keys\":[{\"kty\":\"RSA\",\"n\":\"ANGS3uxrb3Nu1fN8yP-JOvl_vyj7qu-6EoXYkBlXKxtNEaugyuVb2VByJYcY-mVVmQrlGv3usvLO_42Mub5xlsOrhpcz2ccK3Nup7avKHTTyfub6O6_ozwrN8Z1DHOfrtS11NzLmPOQkyGVT259w8VJFGy4r7KAdbqwsELuJnRO4OuHKvDpxPQ5cTysgFRqaDQFxflrhZlVupcmflImvltycS-6y_1LoRr8BLZ-9-P7PvDGM0bBenJjciib9jY-E4CL5xbbHCSldJ-KlQSW7al1a9rgJEE6ALpJHCA5TgwXP-e03UkNR9gj7VSN4yHgierw7y-ii3NKOCkh8oPJkgyM\",\"e\":\"AQAB\",\"kid\":\"" KID_1 "\"},{\"kty\":\"RSA\",\"n\":\"ALLesxikdsNXzAB1rjsjZk4qB7n1wZnxhzDuctRmFeeecVZbClbF_4AdboKKJU0Ylac1q1HMpqAnLJceXGy4CFyCl_P3R_jRUWQ0qwQOCqq1SgLY1xPHKdU7VMrCbo9pm-bOxoQ1Ac6QgpOZqmpOlvhmgvDQP-Yfu3fHtquMEl69ztq8tECKcJpbkBxeqy5QsfGfeBPoNSwTLKv2L-wGYah6OdugmrhflMjrSa3ZbGWcQxzc5Fpr0-SVLpq5k45kbKz7qxlGDNd545mkAXchuTar_IMOqBQ_u6GJ2hGm38DJsAhNTnudfPk5-c6aZyZgJ1ePgOoQG6SgLhwu7XD2Gf0\",\"e\":\"AQAB\",\"kid\":\"" KID_2 "\"},{\"kty\":\"RSA\",\"n\":\"ALOmOGou1SeK8IvwUCzX6D1qDsYdCsAO91aZv9lKJqLJ1sat7EsH_onBVbTr4OV8RH5G09CAh3dWmm7sJUPAgYWjcNtKIGG6bmcpqw6B1QNX4cc6g_1yeALyxMQoxUUkIXF0nhILz2Zu4163J8BmmIq7TVjMrrOmfIO044Z9cdZcagv1rP0GOcMy5qh03Uu_yKj0e7OBTa_FrpOZkRcS-Uq2j4kUGJKydtWvEBc3IpEeal_ZIlaTIvCFhfMCWE377ne0w3BTHTlCVE9n-JJ3UCENIxVfXxvP3zhtBOkNxIqMBUO8RxBW0ugDHu269By5WSBekuU2oLRb6lQkSco9ECM\",\"e\":\"AQAB\",\"kid\":\"" KID_3 "\"}]}"; struct _u_request admin_req; struct _u_request user_req; START_TEST(test_oidc_jwt_encrypted_add_module_rsa) { json_t * j_parameters = json_pack("{sssssssos{sssssssssssisisisosososososososososososisssssssosssssssssssssssssssosos[s]}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE_RSA, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", privkey_2_pem, "cert", pubkey_2_pem, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-device-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "request-parameter-allow-encrypted", json_true(), "request-uri-allow-https-non-secure", json_true(), "request-maximum-exp", CLIENT_AUTH_TOKEN_MAX_AGE, "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "client-jwks-parameter", CLIENT_JWKS_PARAM, "client-jwks_uri-parameter", CLIENT_JWKS_URI_PARAM, "encrypt-out-token-allow", json_true(), "client-enc-parameter", "enc", "client-alg-parameter", "alg", "client-alg_kid-parameter", "alg_kid", "client-encrypt_code-parameter", "encrypt_code", "client-encrypt_at-parameter", "encrypt_at", "client-encrypt_userinfo-parameter", "encrypt_userinfo", "client-encrypt_id_token-parameter", "encrypt_id_token", "client-encrypt_refresh_token-parameter", "encrypt_refresh_token", "client-encrypt_introspection-parameter", "encrypt_introspection", "introspection-revocation-allowed", json_true(), "introspection-revocation-allow-target-client", json_true(), "introspection-revocation-auth-scope", SCOPE_INTROSPECT); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_jwt_encrypted_delete_module) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_jwt_encrypted_add_client_pubkey) { json_t * j_client = json_pack("{ss ss ss so s[s] s[ssssss] s[s] ss ss ss ss ss ss ss ss ss so}", "client_id", CLIENT_ID, "client_secret", CLIENT_SECRET, "name", CLIENT_NAME, "confidential", json_true(), "redirect_uri", CLIENT_REDIRECT, "authorization_type", "code", "token", "id_token", "password", "client_credentials", "device_authorization", "scope", CLIENT_SCOPE, "pubkey", pubkey_1_pem, "enc", CLIENT_ENC, "alg", CLIENT_PUBKEY_ALG, "encrypt_code", "1", "encrypt_at", "TruE", "encrypt_userinfo", "YES", "encrypt_id_token", "indeed, my friend", "encrypt_refresh_token", "1", "encrypt_introspection", "1", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_jwt_encrypted_add_client_pubkey_partial_enc) { json_t * j_client = json_pack("{ss ss ss so s[s] s[sssss] s[s] ss ss ss ss ss ss ss ss so}", "client_id", CLIENT_ID, "client_secret", CLIENT_SECRET, "name", CLIENT_NAME, "confidential", json_true(), "redirect_uri", CLIENT_REDIRECT, "authorization_type", "code", "token", "id_token", "password", "client_credentials", "scope", CLIENT_SCOPE, "pubkey", pubkey_1_pem, "enc", CLIENT_ENC, "alg", CLIENT_PUBKEY_ALG, "encrypt_code", "1", "encrypt_at", "0", "encrypt_userinfo", "YES", "encrypt_id_token", "no", "encrypt_refresh_token", "1", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_jwt_encrypted_add_client_error) { json_t * j_client = json_pack("{ss ss ss so s[s] s[sssss] s[s] ss ss ss ss ss ss ss ss so}", "client_id", CLIENT_ID, "client_secret", CLIENT_SECRET, "name", CLIENT_NAME, "confidential", json_true(), "redirect_uri", CLIENT_REDIRECT, "authorization_type", "code", "token", "id_token", "password", "client_credentials", "scope", CLIENT_SCOPE, "pubkey", pubkey_1_pem, "enc", CLIENT_ENC, "alg", "error", "encrypt_code", "1", "encrypt_at", "TruE", "encrypt_userinfo", "YES", "encrypt_id_token", "indeed, my friend", "encrypt_refresh_token", "1", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_jwt_encrypted_add_client_jwks) { json_t * j_client = json_pack("{ss ss ss so s[s] s[sssss] s[s] so ss ss ss ss ss ss ss ss so}", "client_id", CLIENT_ID, "client_secret", CLIENT_SECRET, "name", CLIENT_NAME, "confidential", json_true(), "redirect_uri", CLIENT_REDIRECT, "authorization_type", "code", "token", "id_token", "password", "client_credentials", "scope", CLIENT_SCOPE, "jwks", json_loads(jwks_pubkey, JSON_DECODE_ANY, NULL), "alg_kid", KID_2, "enc", CLIENT_ENC, "alg", CLIENT_PUBKEY_ALG, "encrypt_code", "1", "encrypt_at", "TruE", "encrypt_userinfo", "YES", "encrypt_id_token", "indeed, my friend", "encrypt_refresh_token", "1", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_jwt_encrypted_add_client_secret_a128gcmkw) { json_t * j_client = json_pack("{ss ss ss so s[s] s[sssss] s[s] ss ss ss ss ss ss ss so}", "client_id", CLIENT_ID, "client_secret", CLIENT_SECRET, "name", CLIENT_NAME, "confidential", json_true(), "redirect_uri", CLIENT_REDIRECT, "authorization_type", "code", "token", "id_token", "password", "client_credentials", "scope", CLIENT_SCOPE, "enc", CLIENT_ENC, "alg", CLIENT_SECRET_ALG, "encrypt_code", "1", "encrypt_at", "TruE", "encrypt_userinfo", "YES", "encrypt_id_token", "indeed, my friend", "encrypt_refresh_token", "1", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_jwt_encrypted_delete_client_pubkey) { json_t * j_param = json_pack("{ss}", "scope", ""); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_ID, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_jwt_encrypted_token_id_token_valid) { struct _u_response resp; jwt_t * jwt_idt, * jwt_at; char * id_token, * access_token; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?response_type=id_token token&g_continue&client_id=" CLIENT_ID "&redirect_uri=" CLIENT_REDIRECT "&state=xyzabcd&nonce=nonce1234&scope=" SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=")+o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *o_strchr(id_token, '&') = '\0'; } access_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "access_token=")+o_strlen("access_token=")); if (o_strchr(access_token, '&')) { *o_strchr(access_token, '&') = '\0'; } ck_assert_int_eq(r_jwt_init(&jwt_idt), RHN_OK); ck_assert_int_eq(r_jwt_init(&jwt_at), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt_idt, id_token, 0), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt_at, access_token, 0), RHN_OK); ck_assert_int_eq(R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, r_jwt_get_type(jwt_idt)); ck_assert_int_eq(R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, r_jwt_get_type(jwt_at)); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt_idt, R_FORMAT_PEM, (unsigned char *)privkey_1_pem, o_strlen(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt_at, R_FORMAT_PEM, (unsigned char *)privkey_1_pem, o_strlen(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_idt, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_at, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); ck_assert_int_eq(r_jwt_decrypt_verify_signature_nested(jwt_idt, NULL, 0, NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_decrypt_verify_signature_nested(jwt_at, NULL, 0, NULL, 0), RHN_OK); o_free(id_token); o_free(access_token); r_jwt_free(jwt_idt); r_jwt_free(jwt_at); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_jwt_encrypted_code_token_id_token_valid) { struct _u_response resp; jwt_t * jwt_idt, * jwt_at; jwe_t * jwe_code; char * id_token, * access_token, * code; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=code id_token token&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT_ID, CLIENT_REDIRECT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=")+o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *o_strchr(id_token, '&') = '\0'; } access_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "access_token=")+o_strlen("access_token=")); if (o_strchr(access_token, '&')) { *o_strchr(access_token, '&') = '\0'; } code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=")+o_strlen("code=")); if (o_strchr(code, '&')) { *o_strchr(code, '&') = '\0'; } ck_assert_int_eq(r_jwt_init(&jwt_idt), RHN_OK); ck_assert_int_eq(r_jwt_init(&jwt_at), RHN_OK); ck_assert_int_eq(r_jwe_init(&jwe_code), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt_idt, id_token, 0), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt_at, access_token, 0), RHN_OK); ck_assert_int_eq(r_jwe_parse(jwe_code, code, 0), RHN_OK); ck_assert_int_eq(R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, r_jwt_get_type(jwt_idt)); ck_assert_int_eq(R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, r_jwt_get_type(jwt_at)); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt_idt, R_FORMAT_PEM, (unsigned char *)privkey_1_pem, o_strlen(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt_at, R_FORMAT_PEM, (unsigned char *)privkey_1_pem, o_strlen(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwe_add_keys_pem_der(jwe_code, R_FORMAT_PEM, (unsigned char *)privkey_1_pem, o_strlen(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_idt, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_at, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); ck_assert_int_eq(r_jwt_decrypt_verify_signature_nested(jwt_idt, NULL, 0, NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_decrypt_verify_signature_nested(jwt_at, NULL, 0, NULL, 0), RHN_OK); ck_assert_int_eq(r_jwe_decrypt(jwe_code, NULL, 0), RHN_OK); o_free(id_token); o_free(access_token); o_free(code); r_jwt_free(jwt_idt); r_jwt_free(jwt_at); r_jwe_free(jwe_code); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_jwt_encrypted_id_token_valid) { struct _u_response resp; jwt_t * jwt_idt; char * id_token; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT_ID, CLIENT_REDIRECT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=")+o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *o_strchr(id_token, '&') = '\0'; } ck_assert_int_eq(r_jwt_init(&jwt_idt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt_idt, id_token, 0), RHN_OK); ck_assert_int_eq(R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, r_jwt_get_type(jwt_idt)); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt_idt, R_FORMAT_PEM, (unsigned char *)privkey_1_pem, o_strlen(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_idt, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); ck_assert_int_eq(r_jwt_decrypt_verify_signature_nested(jwt_idt, NULL, 0, NULL, 0), RHN_OK); o_free(id_token); r_jwt_free(jwt_idt); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_jwt_encrypted_token_valid) { struct _u_response resp; jwt_t * jwt_at; char * access_token; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=token&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT_ID, CLIENT_REDIRECT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); access_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "access_token=")+o_strlen("access_token=")); if (o_strchr(access_token, '&')) { *o_strchr(access_token, '&') = '\0'; } ck_assert_int_eq(r_jwt_init(&jwt_at), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt_at, access_token, 0), RHN_OK); ck_assert_int_eq(R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, r_jwt_get_type(jwt_at)); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt_at, R_FORMAT_PEM, (unsigned char *)privkey_1_pem, o_strlen(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_at, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); ck_assert_int_eq(r_jwt_decrypt_verify_signature_nested(jwt_at, NULL, 0, NULL, 0), RHN_OK); o_free(access_token); r_jwt_free(jwt_at); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_jwt_encrypted_userinfo_valid) { struct _u_response resp; struct _u_request req; jwe_t * jwt_at; jwt_t * jwt_ui; char * access_token; unsigned const char * at_dec; size_t at_dec_len = 0; char * bearer, * body; json_t * j_result, * j_payload = NULL; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=token&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT_ID, CLIENT_REDIRECT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); access_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "access_token=")+o_strlen("access_token=")); ulfius_clean_response(&resp); if (o_strchr(access_token, '&')) { *o_strchr(access_token, '&') = '\0'; } ck_assert_int_eq(r_jwe_init(&jwt_at), RHN_OK); ck_assert_int_eq(r_jwe_parse(jwt_at, access_token, 0), RHN_OK); ck_assert_int_eq(r_jwe_add_keys_pem_der(jwt_at, R_FORMAT_PEM, (unsigned char *)privkey_1_pem, o_strlen(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwe_decrypt(jwt_at, NULL, 0), RHN_OK); ck_assert_ptr_ne(at_dec = r_jwe_get_payload(jwt_at, &at_dec_len), NULL); ulfius_init_request(&req); bearer = msprintf("Bearer %.*s", at_dec_len, at_dec); u_map_put(req.map_header, "Authorization", bearer); j_result = json_pack("{ss}", "iss", PLUGIN_ISS); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/userinfo/?format=jwt"); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_str_eq(u_map_get(resp.map_header, "Content-Type"), "application/jwt"); body = o_strndup(resp.binary_body, resp.binary_body_length); r_jwt_init(&jwt_ui); ck_assert_int_eq(r_jwt_parse(jwt_ui, body, 0), RHN_OK); ck_assert_int_eq(R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, r_jwt_get_type(jwt_ui)); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt_ui, R_FORMAT_PEM, (unsigned char *)privkey_1_pem, o_strlen(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_ui, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); ck_assert_int_eq(r_jwt_decrypt_verify_signature_nested(jwt_ui, NULL, 0, NULL, 0), RHN_OK); ck_assert_ptr_ne((j_payload = r_jwt_get_full_claims_json_t(jwt_ui)), NULL); ck_assert_ptr_ne(json_search(j_payload, j_result), NULL); json_decref(j_payload); ulfius_clean_response(&resp); ulfius_clean_request(&req); o_free(body); o_free(access_token); o_free(bearer); r_jwe_free(jwt_at); r_jwt_free(jwt_ui); json_decref(j_result); } END_TEST START_TEST(test_oidc_jwt_encrypted_client_cred_valid) { struct _u_request req; struct _u_response resp; jwt_t * jwt; json_t * j_resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "client_credentials"); u_map_put(req.map_post_body, "scope", CLIENT_SCOPE); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_int_eq(R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, r_jwt_get_type(jwt)); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt, R_FORMAT_PEM, (unsigned char *)privkey_1_pem, o_strlen(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); ck_assert_int_eq(r_jwt_decrypt_verify_signature_nested(jwt, NULL, 0, NULL, 0), RHN_OK); r_jwt_free(jwt); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_jwt_encrypted_resource_owner_pwd_cred_valid) { struct _u_request req; struct _u_response resp; jwt_t * jwt; json_t * j_resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "username", USER_USERNAME); u_map_put(req.map_post_body, "password", USER_PASSWORD); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_int_eq(R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, r_jwt_get_type(jwt)); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt, R_FORMAT_PEM, (unsigned char *)privkey_1_pem, o_strlen(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); ck_assert_int_eq(r_jwt_decrypt_verify_signature_nested(jwt, NULL, 0, NULL, 0), RHN_OK); r_jwt_free(jwt); json_decref(j_resp); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "id_token")), 0), RHN_OK); ck_assert_int_eq(R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, r_jwt_get_type(jwt)); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt, R_FORMAT_PEM, (unsigned char *)privkey_1_pem, o_strlen(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); ck_assert_int_eq(r_jwt_decrypt_verify_signature_nested(jwt, NULL, 0, NULL, 0), RHN_OK); r_jwt_free(jwt); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_jwt_encrypted_code_valid) { struct _u_response resp; struct _u_request req; jwt_t * jwt; jwe_t * jwe; char * code, * code_dec; json_t * j_resp; const unsigned char * payload; size_t payload_len = 0; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=code&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT_ID, CLIENT_REDIRECT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=")+o_strlen("code=")); if (o_strchr(code, '&')) { *o_strchr(code, '&') = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(r_jwe_init(&jwe), RHN_OK); ck_assert_int_eq(r_jwe_parse(jwe, code, 0), RHN_OK); ck_assert_int_eq(r_jwe_add_keys_pem_der(jwe, R_FORMAT_PEM, (unsigned char *)privkey_1_pem, o_strlen(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwe_decrypt(jwe, NULL, 0), RHN_OK); ck_assert_ptr_ne(payload = r_jwe_get_payload(jwe, &payload_len), NULL); code_dec = o_strndup((const char *)payload, payload_len); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "authorization_code"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "redirect_uri", CLIENT_REDIRECT); u_map_put(req.map_post_body, "code", code_dec); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_int_eq(R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, r_jwt_get_type(jwt)); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt, R_FORMAT_PEM, (unsigned char *)privkey_1_pem, o_strlen(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); ck_assert_int_eq(r_jwt_decrypt_verify_signature_nested(jwt, NULL, 0, NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "id_token")), 0), RHN_OK); ck_assert_int_eq(R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, r_jwt_get_type(jwt)); ck_assert_int_eq(r_jwt_decrypt_verify_signature_nested(jwt, NULL, 0, NULL, 0), RHN_OK); r_jwt_free(jwt); r_jwe_free(jwe); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); o_free(code); o_free(code_dec); } END_TEST START_TEST(test_oidc_jwt_encrypted_introspection_access_token_target_bearer_jwt) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response, * j_body_introspect, * j_verify; const char * token_auth; char * tmp; jwt_t * jwt; jwe_t * jwe; const unsigned char * payload; size_t payload_len = 0; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USER_USERNAME); u_map_put(req.map_post_body, "password", USER_PASSWORD); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ulfius_clean_response(&resp); ck_assert_int_eq(r_jwe_init(&jwe), RHN_OK); ck_assert_int_eq(r_jwe_parse(jwe, json_string_value(json_object_get(j_body, "access_token")), 0), RHN_OK); ck_assert_int_eq(r_jwe_add_keys_pem_der(jwe, R_FORMAT_PEM, (unsigned char *)privkey_1_pem, o_strlen(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwe_decrypt(jwe, NULL, 0), RHN_OK); payload = r_jwe_get_payload(jwe, &payload_len); ulfius_init_response(&resp); u_map_put(req.map_post_body, "scope", SCOPE_INTROSPECT); u_map_put(req.map_post_body, "username", ADMIN_USERNAME); u_map_put(req.map_post_body, "password", ADMIN_PASSWORD); o_free(req.auth_basic_user); req.auth_basic_user = NULL; o_free(req.auth_basic_password); req.auth_basic_password = NULL; ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body_introspect = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(NULL, token_auth = json_string_value(json_object_get(j_body_introspect, "access_token"))); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); ulfius_init_response(&resp); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/introspect"); u_map_put(req.map_header, "Accept", "application/jwt"); tmp = o_strndup((const char *)payload, payload_len); ck_assert_int_eq(u_map_put(req.map_post_body, "token", tmp), U_OK); ck_assert_int_eq(u_map_put(req.map_post_body, "token_type_hint", "access_token"), U_OK); o_free(tmp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_str_eq("application/jwt", u_map_get(resp.map_header, "Content-Type")); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_gt(resp.binary_body_length, 0); ck_assert_ptr_ne(NULL, resp.binary_body); ck_assert_int_eq(r_jwt_parsen(jwt, resp.binary_body, resp.binary_body_length, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt, R_FORMAT_PEM, (unsigned char *)privkey_1_pem, o_strlen(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); ck_assert_int_eq(r_jwt_decrypt_verify_signature_nested(jwt, NULL, 0, NULL, 0), RHN_OK); ck_assert_ptr_ne(NULL, j_response = r_jwt_get_full_claims_json_t(jwt)); j_verify = json_pack("{sossssssssss}", "active", json_true(), "username", USER_USERNAME, "client_id", CLIENT_ID, "token_type", "access_token", "scope", SCOPE_LIST, "iss", PLUGIN_ISS); ck_assert_ptr_ne(NULL, json_search(j_response, j_verify)); ck_assert_str_eq("token-introspection+jwt", r_jwt_get_header_str_value(jwt, "typ")); r_jwt_free(jwt); r_jwe_free(jwe); json_decref(j_verify); json_decref(j_response); json_decref(j_body); json_decref(j_body_introspect); ulfius_clean_response(&resp); ulfius_clean_request(&req); } END_TEST START_TEST(test_oidc_jwt_encrypted_device_authorization_device_verification_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp, * j_grant; const char * redirect_uri, * code, * device_code; jwt_t * jwt; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", SCOPE_LIST); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", ""); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "device_code", device_code); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "refresh_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "id_token"), NULL); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_int_eq(r_jwt_get_type(jwt), R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt, R_FORMAT_PEM, (unsigned char *)privkey_1_pem, o_strlen(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); ck_assert_int_eq(r_jwt_decrypt_verify_signature_nested(jwt, NULL, 0, NULL, 0), RHN_OK); ck_assert_str_eq("at+jwt", r_jwt_get_header_str_value(jwt, "typ")); r_jwt_free(jwt); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "refresh_token")), 0), RHN_OK); ck_assert_int_eq(r_jwt_get_type(jwt), R_JWT_TYPE_ENCRYPT); r_jwt_free(jwt); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "id_token")), 0), RHN_OK); ck_assert_int_eq(r_jwt_get_type(jwt), R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt, R_FORMAT_PEM, (unsigned char *)privkey_1_pem, o_strlen(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); ck_assert_int_eq(r_jwt_decrypt_verify_signature_nested(jwt, NULL, 0, NULL, 0), RHN_OK); ck_assert_str_eq("JWT", r_jwt_get_header_str_value(jwt, "cty")); r_jwt_free(jwt); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_jwt_encrypted_code_token_id_token_valid_partial_enc) { struct _u_response resp; jwt_t * jwt_idt, * jwt_at; jwe_t * jwe_code; char * id_token, * access_token, * code; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=code id_token token&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT_ID, CLIENT_REDIRECT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=")+o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *o_strchr(id_token, '&') = '\0'; } access_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "access_token=")+o_strlen("access_token=")); if (o_strchr(access_token, '&')) { *o_strchr(access_token, '&') = '\0'; } code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=")+o_strlen("code=")); if (o_strchr(code, '&')) { *o_strchr(code, '&') = '\0'; } ck_assert_int_eq(r_jwt_init(&jwt_idt), RHN_OK); ck_assert_int_eq(r_jwt_init(&jwt_at), RHN_OK); ck_assert_int_eq(r_jwe_init(&jwe_code), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt_idt, id_token, 0), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt_at, access_token, 0), RHN_OK); ck_assert_int_eq(r_jwe_parse(jwe_code, code, 0), RHN_OK); ck_assert_int_eq(R_JWT_TYPE_SIGN, r_jwt_get_type(jwt_idt)); ck_assert_int_eq(R_JWT_TYPE_SIGN, r_jwt_get_type(jwt_at)); ck_assert_int_eq(r_jwe_add_keys_pem_der(jwe_code, R_FORMAT_PEM, (unsigned char *)privkey_1_pem, o_strlen(privkey_1_pem), NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_idt, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_at, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); ck_assert_int_eq(r_jwt_verify_signature(jwt_idt, NULL, 0), RHN_OK); ck_assert_int_eq(r_jwt_verify_signature(jwt_at, NULL, 0), RHN_OK); ck_assert_int_eq(r_jwe_decrypt(jwe_code, NULL, 0), RHN_OK); o_free(id_token); o_free(access_token); o_free(code); r_jwt_free(jwt_idt); r_jwt_free(jwt_at); r_jwe_free(jwe_code); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_jwt_encrypted_id_token_valid_secret_a128gcmkw) { struct _u_response resp; jwt_t * jwt_idt; jwk_t * jwk; char * id_token; unsigned char key[32] = {0}; size_t key_len = 32; gnutls_datum_t key_data; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT_ID, CLIENT_REDIRECT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=")+o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *o_strchr(id_token, '&') = '\0'; } ck_assert_int_eq(r_jwt_init(&jwt_idt), RHN_OK); ck_assert_int_eq(r_jwk_init(&jwk), RHN_OK); key_data.data = (unsigned char *)CLIENT_SECRET; key_data.size = o_strlen(CLIENT_SECRET); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, key, &key_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(r_jwt_add_enc_key_symmetric(jwt_idt, key, 16), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt_idt, id_token, 0), RHN_OK); ck_assert_int_eq(R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, r_jwt_get_type(jwt_idt)); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_idt, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); ck_assert_int_eq(r_jwt_decrypt_verify_signature_nested(jwt_idt, NULL, 0, NULL, 0), RHN_OK); o_free(id_token); r_jwk_free(jwk); r_jwt_free(jwt_idt); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_jwt_encrypted_id_token_valid_jwks) { struct _u_response resp; jwt_t * jwt_idt; jwks_t * jwks; char * id_token; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT_ID, CLIENT_REDIRECT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=")+o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *o_strchr(id_token, '&') = '\0'; } ck_assert_int_eq(r_jwt_init(&jwt_idt), RHN_OK); ck_assert_int_eq(r_jwks_init(&jwks), RHN_OK); ck_assert_int_eq(r_jwks_import_from_json_str(jwks, jwks_privkey), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt_idt, id_token, 0), RHN_OK); ck_assert_int_eq(R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, r_jwt_get_type(jwt_idt)); ck_assert_int_eq(r_jwt_add_enc_jwks(jwt_idt, jwks, NULL), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_idt, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); ck_assert_int_eq(r_jwt_decrypt_verify_signature_nested(jwt_idt, NULL, 0, NULL, 0), RHN_OK); o_free(id_token); r_jwt_free(jwt_idt); r_jwks_free(jwks); ulfius_clean_response(&resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc JWT encrypted"); tc_core = tcase_create("test_oidc_jwt_encrypted"); tcase_add_test(tc_core, test_oidc_jwt_encrypted_add_module_rsa); tcase_add_test(tc_core, test_oidc_jwt_encrypted_add_client_pubkey); tcase_add_test(tc_core, test_oidc_jwt_encrypted_token_id_token_valid); tcase_add_test(tc_core, test_oidc_jwt_encrypted_code_token_id_token_valid); tcase_add_test(tc_core, test_oidc_jwt_encrypted_id_token_valid); tcase_add_test(tc_core, test_oidc_jwt_encrypted_token_valid); tcase_add_test(tc_core, test_oidc_jwt_encrypted_userinfo_valid); tcase_add_test(tc_core, test_oidc_jwt_encrypted_client_cred_valid); tcase_add_test(tc_core, test_oidc_jwt_encrypted_resource_owner_pwd_cred_valid); tcase_add_test(tc_core, test_oidc_jwt_encrypted_code_valid); tcase_add_test(tc_core, test_oidc_jwt_encrypted_introspection_access_token_target_bearer_jwt); tcase_add_test(tc_core, test_oidc_jwt_encrypted_device_authorization_device_verification_valid); tcase_add_test(tc_core, test_oidc_jwt_encrypted_delete_client_pubkey); tcase_add_test(tc_core, test_oidc_jwt_encrypted_add_client_error); tcase_add_test(tc_core, test_oidc_jwt_encrypted_delete_client_pubkey); tcase_add_test(tc_core, test_oidc_jwt_encrypted_add_client_pubkey_partial_enc); tcase_add_test(tc_core, test_oidc_jwt_encrypted_code_token_id_token_valid_partial_enc); tcase_add_test(tc_core, test_oidc_jwt_encrypted_delete_client_pubkey); tcase_add_test(tc_core, test_oidc_jwt_encrypted_add_client_secret_a128gcmkw); tcase_add_test(tc_core, test_oidc_jwt_encrypted_id_token_valid_secret_a128gcmkw); tcase_add_test(tc_core, test_oidc_jwt_encrypted_delete_client_pubkey); tcase_add_test(tc_core, test_oidc_jwt_encrypted_add_client_jwks); tcase_add_test(tc_core, test_oidc_jwt_encrypted_id_token_valid_jwks); tcase_add_test(tc_core, test_oidc_jwt_encrypted_delete_client_pubkey); tcase_add_test(tc_core, test_oidc_jwt_encrypted_delete_module); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req; struct _u_response auth_resp, scope_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); ulfius_init_request(&user_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&scope_req); ulfius_init_response(&auth_resp); ulfius_init_response(&scope_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "User %s authenticated", USER_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(scope_req.map_header, "Cookie", cookie); u_map_put(user_req.map_header, "Cookie", cookie); o_free(cookie); scope_req.http_verb = strdup("PUT"); scope_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, &scope_resp) != U_OK || scope_resp.status != 200) { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope '%s' for %s error", CLIENT, SCOPE_LIST); do_test = 0; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope OK"); } ulfius_clean_response(&scope_resp); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication user"); do_test = 0; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); } if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, NULL) != U_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "Remove grant scope '%s' for %s error", CLIENT, SCOPE_LIST); } char * url = msprintf("%s/auth/", SERVER_URI); run_simple_test(&user_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL); o_free(url); ulfius_clean_request(&admin_req); ulfius_clean_request(&user_req); ulfius_clean_request(&scope_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_only_no_refresh.c000066400000000000000000000320631415646314000226200ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "user1" #define USER_PASSWORD "password" #define PLUGIN "oidc" #define PLUGIN_NAME "oidc_claims" #define SCOPE_LIST "g_profile openid" #define CLIENT "client1_id" #define CLIENT_PASSWORD "password" struct _u_request admin_req; struct _u_request user_req; START_TEST(test_oidc_only_no_refresh_add_plugin) { json_t * j_param = json_pack("{sssssss{sssssssssisisisosososososososo}}", "module", "oidc", "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", "https://glewlwyd.tld", "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_NAME, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_false(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_false()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_only_no_refresh_id_token_token_ok) { struct _u_response resp; struct _u_request req; ulfius_init_response(&resp); ulfius_init_request(&req); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token token&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_only_no_refresh_token_error) { struct _u_response resp; struct _u_request req; ulfius_init_response(&resp); ulfius_init_request(&req); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=token&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "error=unsupported_response_type"), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_only_no_refresh_password_error) { char * url = msprintf("%s/%s/token/", SERVER_URI, PLUGIN_NAME); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", SCOPE_LIST); u_map_put(&body, "username", USER_USERNAME); u_map_put(&body, "password", USER_PASSWORD); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_only_no_refresh_client_error) { char * url = msprintf("%s/%s/token/", SERVER_URI, PLUGIN_NAME); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", SCOPE_LIST); int res = run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_only_no_refresh_refresh_error) { struct _u_response resp; struct _u_request req; json_t * j_body; char * code, * url; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=code&g_continue&client_id=%s&redirect_uri=../../test-oidc.html?param=client1_cb1&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code=")); if (o_strchr(code, '&')) { *(o_strchr(code, '&')) = '\0'; } ulfius_clean_response(&resp); ulfius_init_response(&resp); ulfius_init_request(&req); req.http_verb = o_strdup("POST"); req.http_url = msprintf("%s/%s/token/", SERVER_URI, PLUGIN_NAME); u_map_put(req.map_post_body, "grant_type", "authorization_code"); u_map_put(req.map_post_body, "client_id", CLIENT); u_map_put(req.map_post_body, "redirect_uri", "../../test-oidc.html?param=client1_cb1"); u_map_put(req.map_post_body, "code", code); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_body = ulfius_get_json_body_response(&resp, NULL)), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); ulfius_init_response(&resp); ulfius_init_request(&req); req.http_verb = o_strdup("POST"); url = msprintf("%s/%s/token/", SERVER_URI, PLUGIN_NAME); u_map_put(req.map_post_body, "grant_type", "refresh_token"); u_map_put(req.map_post_body, "refresh_token", json_string_value(json_object_get(j_body, "refresh_token"))); ck_assert_int_eq(run_simple_test(&req, "POST", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_body); o_free(url); o_free(code); } END_TEST START_TEST(test_oidc_only_no_refresh_delete_plugin) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/user/" USER_USERNAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); json_t * j_param = json_pack("{ss ss ss so s[sssss]}", "username", USER_USERNAME, "name", "Dave Lopper 1", "email", "dev1@glewlwyd", "enabled", json_true(), "scope", "g_profile", "openid", "scope1", "scope2", "scope3"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/user/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc only no refresh"); tc_core = tcase_create("test_oidc_only_no_refresh"); tcase_add_test(tc_core, test_oidc_only_no_refresh_add_plugin); tcase_add_test(tc_core, test_oidc_only_no_refresh_id_token_token_ok); tcase_add_test(tc_core, test_oidc_only_no_refresh_token_error); tcase_add_test(tc_core, test_oidc_only_no_refresh_password_error); tcase_add_test(tc_core, test_oidc_only_no_refresh_client_error); tcase_add_test(tc_core, test_oidc_only_no_refresh_refresh_error); tcase_add_test(tc_core, test_oidc_only_no_refresh_delete_plugin); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req; struct _u_response auth_resp, scope_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); ulfius_init_request(&user_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&scope_req); ulfius_init_response(&auth_resp); ulfius_init_response(&scope_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "User %s authenticated", USER_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(scope_req.map_header, "Cookie", cookie); u_map_put(user_req.map_header, "Cookie", cookie); o_free(cookie); scope_req.http_verb = strdup("PUT"); scope_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, &scope_resp) != U_OK || scope_resp.status != 200) { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope '%s' for %s error", CLIENT, SCOPE_LIST); do_test = 0; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope OK"); } ulfius_clean_response(&scope_resp); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication user"); do_test = 0; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); } if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, NULL) != U_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "Remove grant scope '%s' for %s error", CLIENT, SCOPE_LIST); } char * url = msprintf("%s/auth/", SERVER_URI); run_simple_test(&user_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL); o_free(url); ulfius_clean_request(&admin_req); ulfius_clean_request(&user_req); ulfius_clean_request(&scope_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_optional_request_parameters.c000066400000000000000000000470671415646314000252570ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "openid" #define CLIENT "client1_id" #define RESPONSE_TYPE "id_token" struct _u_request user_req; char * code; START_TEST(test_oidc_optional_request_parameters_display) { char * url = msprintf("%s/oidc/auth?response_type=%s&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s&display=page", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); int res = run_simple_test(NULL, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "display=page"); o_free(url); ck_assert_int_eq(res, 1); url = msprintf("%s/oidc/auth?response_type=%s&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s&display=popup", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); res = run_simple_test(NULL, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "display=popup"); o_free(url); ck_assert_int_eq(res, 1); url = msprintf("%s/oidc/auth?response_type=%s&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s&display=touch", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); res = run_simple_test(NULL, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "display=touch"); o_free(url); ck_assert_int_eq(res, 1); url = msprintf("%s/oidc/auth?response_type=%s&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s&display=wap", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); res = run_simple_test(NULL, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "display=wap"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_optional_request_parameters_prompt_none_no_id_token_hint) { struct _u_response resp; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/oidc/auth?response_type=%s&prompt=none&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "error=invalid_request"), NULL); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_optional_request_parameters_prompt_none_id_token_hint_invalid) { struct _u_response resp; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/oidc/auth?response_type=%s&prompt=none&id_token_hint=error&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "error=invalid_request"), NULL); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_optional_request_parameters_prompt_none_id_token_not_last) { struct _u_response resp; char * id_token; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&id_token_hint=error&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *(o_strchr(id_token, '&')) = '\0'; } ulfius_clean_response(&resp); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/oidc/auth?response_type=%s&prompt=none&id_token_hint=error&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "error=invalid_request"), NULL); ulfius_clean_response(&resp); o_free(id_token); } END_TEST START_TEST(test_oidc_optional_request_parameters_prompt_none_id_token_last) { struct _u_response resp; char * id_token; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/oidc/auth?response_type=%s&g_continue&id_token_hint=error&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token=")); if (o_strchr(id_token, '&')) { *(o_strchr(id_token, '&')) = '\0'; } ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/oidc/auth?response_type=%s&prompt=none&id_token_hint=%s&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, id_token, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ulfius_clean_response(&resp); o_free(id_token); } END_TEST START_TEST(test_oidc_optional_request_parameters_prompt) { char * url = msprintf("%s/oidc/auth?response_type=%s&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s&prompt=login", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); int res = run_simple_test(NULL, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "prompt=login"); o_free(url); ck_assert_int_eq(res, 1); url = msprintf("%s/oidc/auth?response_type=%s&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s&prompt=consent", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); res = run_simple_test(NULL, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "prompt=consent"); o_free(url); ck_assert_int_eq(res, 1); url = msprintf("%s/oidc/auth?response_type=%s&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s&prompt=select_account", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); res = run_simple_test(NULL, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "prompt=select_account"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_optional_request_parameters_ui_locales) { char * url = msprintf("%s/oidc/auth?response_type=%s&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s&ui_locales=fr", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); int res = run_simple_test(NULL, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "ui_locales=fr"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_optional_request_parameters_login_hint) { char * url = msprintf("%s/oidc/auth?response_type=%s&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s&login_hint=myrddin", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); int res = run_simple_test(NULL, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "login_hint=myrddin"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_optional_request_parameters_max_age) { char * url = msprintf("%s/oidc/auth?response_type=%s&max_age=300&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s&g_continue", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "id_token="); o_free(url); ck_assert_int_eq(res, 1); url = msprintf("%s/oidc/auth?response_type=%s&max_age=0&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s&g_continue", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_request"); o_free(url); ck_assert_int_eq(res, 1); sleep(2); url = msprintf("%s/oidc/auth?response_type=%s&max_age=1&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s&g_continue", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_optional_request_parameters_response_mode_fragment) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI"/oidc/auth?response_type="RESPONSE_TYPE"&response_mode=fragment&client_id="CLIENT"&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope="SCOPE_LIST"&g_continue", NULL, NULL, NULL, NULL, 302, NULL, NULL, "../../test-oauth2.html?param=client1_cb1#"), 1); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI"/oidc/auth?response_type="RESPONSE_TYPE"&response_mode=fragment&client_id="CLIENT"&redirect_uri=error.html&state=xyzabcd&nonce=nonce1234&scope="SCOPE_LIST"&g_continue", NULL, NULL, NULL, NULL, 302, NULL, NULL, "error.html#"), 1); } END_TEST START_TEST(test_oidc_optional_request_parameters_response_mode_form_post) { char * url = msprintf("%s/oidc/auth?response_type=%s&response_mode=form_post&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s&g_continue", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 200, NULL, "", NULL); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_optional_request_parameters_unknown) { struct _u_response resp; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/oidc/auth?response_type=%s&unknown=error&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "unknown=error"), NULL); ulfius_clean_response(&resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc optional request parameters"); tc_core = tcase_create("test_oidc_optional_request_parameters"); tcase_add_test(tc_core, test_oidc_optional_request_parameters_display); tcase_add_test(tc_core, test_oidc_optional_request_parameters_prompt_none_no_id_token_hint); tcase_add_test(tc_core, test_oidc_optional_request_parameters_prompt_none_id_token_hint_invalid); tcase_add_test(tc_core, test_oidc_optional_request_parameters_prompt_none_id_token_not_last); tcase_add_test(tc_core, test_oidc_optional_request_parameters_prompt_none_id_token_last); tcase_add_test(tc_core, test_oidc_optional_request_parameters_prompt); tcase_add_test(tc_core, test_oidc_optional_request_parameters_ui_locales); tcase_add_test(tc_core, test_oidc_optional_request_parameters_login_hint); tcase_add_test(tc_core, test_oidc_optional_request_parameters_max_age); tcase_add_test(tc_core, test_oidc_optional_request_parameters_response_mode_form_post); tcase_add_test(tc_core, test_oidc_optional_request_parameters_response_mode_fragment); tcase_add_test(tc_core, test_oidc_optional_request_parameters_unknown); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, register_req; struct _u_response auth_resp, scope_resp; json_t * j_body, * j_register; int res, do_test = 0, i; char * url; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_request(&scope_req); ulfius_init_request(®ister_req); ulfius_init_response(&scope_resp); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "user1" #define USER_PASSWORD "password" #define PLUGIN_MODULE "oidc" #define PLUGIN_NAME "oidc_par" #define PLUGIN_ISS "https://glewlwyd.tld" #define PLUGIN_JWT_TYPE_RSA "rsa" #define PLUGIN_JWT_KEY_SIZE "256" #define PLUGIN_PAR_PREFIX "urn:ietf:params:oauth:request_uri:" #define PLUGIN_PAR_DURATION 90 #define SCOPE_LIST "g_profile openid" #define SCOPE_1 "g_profile" #define SCOPE_2 "openid" #define CLIENT "client1_id" #define CLIENT_REDIRECT "../../test-oidc.html?param=client1_cb1" #define RESPONSE_TYPE "code token" #define RAR1 "type1" #define ENRICHED1 "name" #define ENRICHED2 "email" #define CLIENT_PUBKEY_ID "client_par" #define CLIENT_PUBKEY_SECRET "secret_string" #define CLIENT_PUBKEY_AUTH_TOKEN_MAX_AGE 3600 #define CLIENT_PUBKEY_PARAM "pubkey" #define CLIENT_PUBKEY_NAME "client with pubkey" #define CLIENT_PUBKEY_REDIRECT "https://glewlwyd.local/" #define CLIENT_PUBKEY_REDIRECT_ESCAPED "https%3A%2F%2Fglewlwyd.local%2F" #define CLIENT_SCOPE "scope1" #define KID_PUB "pubkey" #define CLIENT_JWKS_PARAM "jwks" #define CLIENT_JWKS_URI_PARAM "jwks_uri" #define PLUGIN_CODE_DURATION 600 #define PLUGIN_REFRESH_TOKEN_DURATION 1209600 #define PLUGIN_ACCESS_TOKEN_DURATION 3600 #define STATE "state1234" #define NONCE "nonce1234" #define CLIENT_AUTH_TOKEN_MAX_AGE 3600 #define CODE_CHALLENGE_VALID "V0UN9ToT-UnbxeIx7imQdhFjsAZTmuARpHyuD2ajIIo" #define CODE_VERIFIER_VALID "XvkLR4XIl4DbkFz3RLEZUBStp8yIjvF8UtfRv0nkK8DqmrBtWvHmEuyBL2enyLF9" #define CODE_CHALLENGE_METHOD_S256 "S256" const char pubkey_1_jwk[] = "{\"keys\":[{\"kty\":\"RSA\",\"n\":\"AMWhdXoJpkPtPwABHL_yXUwgcYuwNOVbw70YGmMzhFqiRd6r92-onw-BOAvfnIq-rSMgjidllxOE1fXwlgUIyKJmnHUI3RMDABFmGFRM-Dz6VmQxHgiioLM-Q5yzcj85zIqJvNrw0RL0qhvssQBG5Fta_jLXBUXeGEmciWA0lSfrdlS-zbfxsWqPzAvKyT_0B80m1o8K7ksFtyTPu-cHbCVGx4ciGeZUNrtOnevGQPUOE-tIvsxOPcqC3fPjyI3K4TN5GCCZHEyso1qmRFfsHtenq6EvD1_2DebcODnfnym-iNFyC4YsgqipToNxR3WPIgCu-WrSOk71-93ovs0hd1MhBYw03J4Xupjxy_URCFZm9Pp-9H3j_0hUKmhUWmpsQTpAT7FWvTT-MyyYkZ-9Y33-6KR3E-82kdfXIoEMbGJnfq2Z4Yh_lF3pfD-5FUzOzgnOy0UiTxusWOBbaVhNqmm6xmlHwQjBrax9Bqo7WzQpwXgXgooo6TeVz6pxpUl6V63d5o5XZaxYYilUpZ78qXpHQMwNFr6a2gct-dU8zLF6YaJHIaMp6XT9OD8r-w7SOkq1O7J-UGRqGbUVczYzhApY1Q-B2ZnO18P5KQnG97AbU_Sjk5Rnf6HJ-w-E8NOIwgo5jzloV_5Ck6w-DH_sL5FDca89BGuzwpQEH_h3ma43\",\"e\":\"AQAB\",\"kid\":\"" KID_PUB "\"}]}"; const char privkey_1_jwk[] = "{\"kty\":\"RSA\",\"n\":\"AMWhdXoJpkPtPwABHL_yXUwgcYuwNOVbw70YGmMzhFqiRd6r92-onw-BOAvfnIq-rSMgjidllxOE1fXwlgUIyKJmnHUI3RMDABFmGFRM-Dz6VmQxHgiioLM-Q5yzcj85zIqJvNrw0RL0qhvssQBG5Fta_jLXBUXeGEmciWA0lSfrdlS-zbfxsWqPzAvKyT_0B80m1o8K7ksFtyTPu-cHbCVGx4ciGeZUNrtOnevGQPUOE-tIvsxOPcqC3fPjyI3K4TN5GCCZHEyso1qmRFfsHtenq6EvD1_2DebcODnfnym-iNFyC4YsgqipToNxR3WPIgCu-WrSOk71-93ovs0hd1MhBYw03J4Xupjxy_URCFZm9Pp-9H3j_0hUKmhUWmpsQTpAT7FWvTT-MyyYkZ-9Y33-6KR3E-82kdfXIoEMbGJnfq2Z4Yh_lF3pfD-5FUzOzgnOy0UiTxusWOBbaVhNqmm6xmlHwQjBrax9Bqo7WzQpwXgXgooo6TeVz6pxpUl6V63d5o5XZaxYYilUpZ78qXpHQMwNFr6a2gct-dU8zLF6YaJHIaMp6XT9OD8r-w7SOkq1O7J-UGRqGbUVczYzhApY1Q-B2ZnO18P5KQnG97AbU_Sjk5Rnf6HJ-w-E8NOIwgo5jzloV_5Ck6w-DH_sL5FDca89BGuzwpQEH_h3ma43\",\"e\":\"AQAB\",\"d\":\"AIiu6F7k-ZcVKHNKUaX3a8tQzPb9gTf3xWKsnuNpJ-q_PG-Ko_EXwBqrFiYwG0ZiJcCbrXVV76zSPGCCal9E-e5H5YGUBcI2Wv-tiroTGcSipslYpxr1zwrozz47ZZKQ2QQfyvvpfdAMYvI5Oxmj7h-4yQJEcCMoPcf7eY-ODnKziP2HkSPdBwVaOpcVQyb2EcczS0VXHAPLCiVtftmD6qnFUA4H6b3BFLFq6BG-5gIWIHSjtUH8AwRiijs5mOVoIWTGJYe2HTpyU_BH-hCM_6_LCQrLT2jg9jBqsoBkRuJKIroolAvSEPOxVNnXqMKHoc6zNVFJ4IXn3rBVXlDlCm69xoe67-X2M4o8LXpdnwFtvao3YYKqAqv1kH0JZE9kJyY3odhXa-SRZpvOCoE3YpDr5UTlRkEWZATQjqtGP7JEq_RQwtDwM1NpANIl4cFAJVhUJbndjMeJqBcA4-NEV6bBjWkenw179H6UuWNXNzXklPsgtMnF_PwcBFKutwnFqHAE5g6w9iHQ5yG7_2m4zModfBiGiSy3cdQ2f3MEHRRoBmqooEGU_6Urrn6iyAFxk_sINEnT_7Emygle_QwP5N-BQuFpD_NWojGirWwOwiWYBHRBXP0ub17bNx7w4gha6CxHnXyJ0MZBayOIMrnQGeWC7o5a932LCTQfegdBh5xh\",\"p\":\"APQQSKxv01Oky-jENQwxiZcpI4a5PzLPFFCgEqIjSRamCzrCQ07e97iqhU1b8IvRwxDtX358pFKAq7tmwpN2QQb1T9fqUwCpeQuMwRsZwoaM7ZcTSj2FZ_2djN1ixQfzqQ21VxkMRbrdyExqCSJXnHMcLeiFmu81dVopV2iwDbUQv4jZe_ktPUTH4HKle48Y0v9pu22lD5cknAQGB1gUNfyJ0PbUxZMITrZDz4khhYgxqvJ7GluYRNv2tezV-bb5leXbSLDrRgTKqcl5ZjkgLm9FRNGZZAmlsCHEeB3nvCs2ePQYDuLgEkNtuu39kpLFJO6j70bjnvpaIAcDVpPmEE8\",\"q\":\"AM9L09Grg2uSrNUGfj9pfpsMn0k5kqV0n3WjX9z5ZLkwLNNrs0SJjb93haO2MPNlyYhctCpPKnfHJKZWaLhFDV6xr-ubf7c3DbBJjPhlV8dUkgmHfIqWDPl6pzN0xC61zC4IE15LgW_JEMpq53fRWnIHdufs-105QO8YOo0CVYKYjqut4hVbYRBSTaeVLb1vj_yhaL0qV7orQoTrpr6Bg20nftBBa-8Md_B5l0QyiSfvOjKnXsjULQdQGbtypQZvu2jUasnUVUQHBgeF5W5WFj8qCGGnmehqY6QissipLoRMcGPaV_gJKisgcorF7sSU_QzcBUmPk377LkzZXGNUYZk\",\"qi\":\"AILHVNisODhO4GC8P709DqGdVdufLZf2Bl7AwjWyYTkpEzEfQCHHUnmOoTCn-OEvnn9lWiaCaTijtlUmos0fCfvSQLk9elciIOmlRk8G1EtnnzYQsTmerLoMJBgQ02hhip8GK47Y7mbZIjaPB625Dv8F4RHd9ZiTzXTGcNc6bldWlNNbbqw9DWS1DORPhdQEPU424qcYvHq_eklFCujWukO8ul3FEZYnTcth2ODSFMb0a0SCuDGkGI8BDI-_4n6-4wIlAXtc8Vt9Ko8WxJjCK_v2Ae9x05eknWZj0JxuyoAjPtJApp0pt25omJwZr_lY5i8T0cL6dDF5nZcA9hN__eo\",\"dp\":\"ALL4hfI9BmCdxhFoX-YTJWw9dJnEmf1uMN12pHNVILGFDVMHRUg-5LT8BkhWFSzSoxJ0nsQoLm95f3Uqw6BS5RhvJx-T603e-K5phumSmD0GduuD77rxavJlZ_ioBwfvu5Yb1kS95RxEqi6uywft6wHWNiv-XUDwmJ-HFVvlTgfqwileIjT04argT0yC4PpsH73AEPs0QRx6chXZPeVu3K_Vd_Co0kEhpGavjy5l8H-QvGSXtRpZrJUIcxu7RSTSHQOzK7jgrjWxT5Q4e6eEW8ioqPByZRNV9rSsV9DGMAwYI9YLFk90NLBRdPQ0MBmEi7KbcEkxfVDkafv6jLBj0q0\",\"dq\":\"AJldyYY7dczVxMcKucbinwfJq-N6E_QTt5JKYDdV0F5utQtqiEQx3MyGejooJkk9yn_3zlfrIElj7cqe7XU_qWeg4L3Y2wHLWnZNxF1WZT4VZMJmGg9SeqDtTNz2C9tfJ4P695FxHX99681GkKAGJPtuaFuo6kQLgu4iJ9eBnZA0nIGJ8VXJuKNhsRBGf4PDEW1gYeRqemNDdEBxNHmHypusd9dOP7OpruccnnyXQwBnrtAhIjBFQldBvPgBFvUPH0GsvqE6VicxZxWTy635RRZQW8kcPfNFGxkpjsqE2OSKxTArL6BT733e0L-5NzD75cho1ASblA2DerriqcbXfCk\",\"kid\":\"" KID_PUB "\"}"; const char pubkey_1_pem[] = "-----BEGIN PUBLIC KEY-----\n"\ "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxaF1egmmQ+0/AAEcv/Jd\n"\ "TCBxi7A05VvDvRgaYzOEWqJF3qv3b6ifD4E4C9+cir6tIyCOJ2WXE4TV9fCWBQjI\n"\ "omacdQjdEwMAEWYYVEz4PPpWZDEeCKKgsz5DnLNyPznMiom82vDREvSqG+yxAEbk\n"\ "W1r+MtcFRd4YSZyJYDSVJ+t2VL7Nt/Gxao/MC8rJP/QHzSbWjwruSwW3JM+75wds\n"\ "JUbHhyIZ5lQ2u06d68ZA9Q4T60i+zE49yoLd8+PIjcrhM3kYIJkcTKyjWqZEV+we\n"\ "16eroS8PX/YN5tw4Od+fKb6I0XILhiyCqKlOg3FHdY8iAK75atI6TvX73ei+zSF3\n"\ "UyEFjDTcnhe6mPHL9REIVmb0+n70feP/SFQqaFRaamxBOkBPsVa9NP4zLJiRn71j\n"\ "ff7opHcT7zaR19cigQxsYmd+rZnhiH+UXel8P7kVTM7OCc7LRSJPG6xY4FtpWE2q\n"\ "abrGaUfBCMGtrH0GqjtbNCnBeBeCiijpN5XPqnGlSXpXrd3mjldlrFhiKVSlnvyp\n"\ "ekdAzA0WvpraBy351TzMsXphokchoynpdP04Pyv7DtI6SrU7sn5QZGoZtRVzNjOE\n"\ "CljVD4HZmc7Xw/kpCcb3sBtT9KOTlGd/ocn7D4Tw04jCCjmPOWhX/kKTrD4Mf+wv\n"\ "kUNxrz0Ea7PClAQf+HeZrjcCAwEAAQ==\n"\ "-----END PUBLIC KEY-----"; const char privkey_1_pem[] = "-----BEGIN RSA PRIVATE KEY-----\n"\ "MIIJKwIBAAKCAgEAxaF1egmmQ+0/AAEcv/JdTCBxi7A05VvDvRgaYzOEWqJF3qv3\n"\ "b6ifD4E4C9+cir6tIyCOJ2WXE4TV9fCWBQjIomacdQjdEwMAEWYYVEz4PPpWZDEe\n"\ "CKKgsz5DnLNyPznMiom82vDREvSqG+yxAEbkW1r+MtcFRd4YSZyJYDSVJ+t2VL7N\n"\ "t/Gxao/MC8rJP/QHzSbWjwruSwW3JM+75wdsJUbHhyIZ5lQ2u06d68ZA9Q4T60i+\n"\ "zE49yoLd8+PIjcrhM3kYIJkcTKyjWqZEV+we16eroS8PX/YN5tw4Od+fKb6I0XIL\n"\ "hiyCqKlOg3FHdY8iAK75atI6TvX73ei+zSF3UyEFjDTcnhe6mPHL9REIVmb0+n70\n"\ "feP/SFQqaFRaamxBOkBPsVa9NP4zLJiRn71jff7opHcT7zaR19cigQxsYmd+rZnh\n"\ "iH+UXel8P7kVTM7OCc7LRSJPG6xY4FtpWE2qabrGaUfBCMGtrH0GqjtbNCnBeBeC\n"\ "iijpN5XPqnGlSXpXrd3mjldlrFhiKVSlnvypekdAzA0WvpraBy351TzMsXphokch\n"\ "oynpdP04Pyv7DtI6SrU7sn5QZGoZtRVzNjOECljVD4HZmc7Xw/kpCcb3sBtT9KOT\n"\ "lGd/ocn7D4Tw04jCCjmPOWhX/kKTrD4Mf+wvkUNxrz0Ea7PClAQf+HeZrjcCAwEA\n"\ "AQKCAgEAiK7oXuT5lxUoc0pRpfdry1DM9v2BN/fFYqye42kn6r88b4qj8RfAGqsW\n"\ "JjAbRmIlwJutdVXvrNI8YIJqX0T57kflgZQFwjZa/62KuhMZxKKmyVinGvXPCujP\n"\ "PjtlkpDZBB/K++l90Axi8jk7GaPuH7jJAkRwIyg9x/t5j44OcrOI/YeRI90HBVo6\n"\ "lxVDJvYRxzNLRVccA8sKJW1+2YPqqcVQDgfpvcEUsWroEb7mAhYgdKO1QfwDBGKK\n"\ "OzmY5WghZMYlh7YdOnJT8Ef6EIz/r8sJCstPaOD2MGqygGRG4koiuiiUC9IQ87FU\n"\ "2deowoehzrM1UUnghefesFVeUOUKbr3Gh7rv5fYzijwtel2fAW29qjdhgqoCq/WQ\n"\ "fQlkT2QnJjeh2Fdr5JFmm84KgTdikOvlROVGQRZkBNCOq0Y/skSr9FDC0PAzU2kA\n"\ "0iXhwUAlWFQlud2Mx4moFwDj40RXpsGNaR6fDXv0fpS5Y1c3NeSU+yC0ycX8/BwE\n"\ "Uq63CcWocATmDrD2IdDnIbv/abjMyh18GIaJLLdx1DZ/cwQdFGgGaqigQZT/pSuu\n"\ "fqLIAXGT+wg0SdP/sSbKCV79DA/k34FC4WkP81aiMaKtbA7CJZgEdEFc/S5vXts3\n"\ "HvDiCFroLEedfInQxkFrI4gyudAZ5YLujlr3fYsJNB96B0GHnGECggEBAPQQSKxv\n"\ "01Oky+jENQwxiZcpI4a5PzLPFFCgEqIjSRamCzrCQ07e97iqhU1b8IvRwxDtX358\n"\ "pFKAq7tmwpN2QQb1T9fqUwCpeQuMwRsZwoaM7ZcTSj2FZ/2djN1ixQfzqQ21VxkM\n"\ "RbrdyExqCSJXnHMcLeiFmu81dVopV2iwDbUQv4jZe/ktPUTH4HKle48Y0v9pu22l\n"\ "D5cknAQGB1gUNfyJ0PbUxZMITrZDz4khhYgxqvJ7GluYRNv2tezV+bb5leXbSLDr\n"\ "RgTKqcl5ZjkgLm9FRNGZZAmlsCHEeB3nvCs2ePQYDuLgEkNtuu39kpLFJO6j70bj\n"\ "nvpaIAcDVpPmEE8CggEBAM9L09Grg2uSrNUGfj9pfpsMn0k5kqV0n3WjX9z5ZLkw\n"\ "LNNrs0SJjb93haO2MPNlyYhctCpPKnfHJKZWaLhFDV6xr+ubf7c3DbBJjPhlV8dU\n"\ "kgmHfIqWDPl6pzN0xC61zC4IE15LgW/JEMpq53fRWnIHdufs+105QO8YOo0CVYKY\n"\ "jqut4hVbYRBSTaeVLb1vj/yhaL0qV7orQoTrpr6Bg20nftBBa+8Md/B5l0QyiSfv\n"\ "OjKnXsjULQdQGbtypQZvu2jUasnUVUQHBgeF5W5WFj8qCGGnmehqY6QissipLoRM\n"\ "cGPaV/gJKisgcorF7sSU/QzcBUmPk377LkzZXGNUYZkCggEBALL4hfI9BmCdxhFo\n"\ "X+YTJWw9dJnEmf1uMN12pHNVILGFDVMHRUg+5LT8BkhWFSzSoxJ0nsQoLm95f3Uq\n"\ "w6BS5RhvJx+T603e+K5phumSmD0GduuD77rxavJlZ/ioBwfvu5Yb1kS95RxEqi6u\n"\ "ywft6wHWNiv+XUDwmJ+HFVvlTgfqwileIjT04argT0yC4PpsH73AEPs0QRx6chXZ\n"\ "PeVu3K/Vd/Co0kEhpGavjy5l8H+QvGSXtRpZrJUIcxu7RSTSHQOzK7jgrjWxT5Q4\n"\ "e6eEW8ioqPByZRNV9rSsV9DGMAwYI9YLFk90NLBRdPQ0MBmEi7KbcEkxfVDkafv6\n"\ "jLBj0q0CggEBAJldyYY7dczVxMcKucbinwfJq+N6E/QTt5JKYDdV0F5utQtqiEQx\n"\ "3MyGejooJkk9yn/3zlfrIElj7cqe7XU/qWeg4L3Y2wHLWnZNxF1WZT4VZMJmGg9S\n"\ "eqDtTNz2C9tfJ4P695FxHX99681GkKAGJPtuaFuo6kQLgu4iJ9eBnZA0nIGJ8VXJ\n"\ "uKNhsRBGf4PDEW1gYeRqemNDdEBxNHmHypusd9dOP7OpruccnnyXQwBnrtAhIjBF\n"\ "QldBvPgBFvUPH0GsvqE6VicxZxWTy635RRZQW8kcPfNFGxkpjsqE2OSKxTArL6BT\n"\ "733e0L+5NzD75cho1ASblA2DerriqcbXfCkCggEBAILHVNisODhO4GC8P709DqGd\n"\ "VdufLZf2Bl7AwjWyYTkpEzEfQCHHUnmOoTCn+OEvnn9lWiaCaTijtlUmos0fCfvS\n"\ "QLk9elciIOmlRk8G1EtnnzYQsTmerLoMJBgQ02hhip8GK47Y7mbZIjaPB625Dv8F\n"\ "4RHd9ZiTzXTGcNc6bldWlNNbbqw9DWS1DORPhdQEPU424qcYvHq/eklFCujWukO8\n"\ "ul3FEZYnTcth2ODSFMb0a0SCuDGkGI8BDI+/4n6+4wIlAXtc8Vt9Ko8WxJjCK/v2\n"\ "Ae9x05eknWZj0JxuyoAjPtJApp0pt25omJwZr/lY5i8T0cL6dDF5nZcA9hN//eo=\n"\ "-----END RSA PRIVATE KEY-----\n"; const char pubkey_2_jwk[] = "{\"keys\":[{\"kty\":\"RSA\",\"n\":\"AKLIxChfF0q7zzxrkGFM_Qu8Cx76Cd1AEd76RxXSWvmfzfUk12FYeu1LQpzO0VvpmzuwbhRZk9Vgdo5y51s2k83ht4QvwOOIpr9rlNZrTiILeYhYheTfl2bPaKbDPY1rFFtifLhTytaIXZ18VpBQH-30ycdnyWzu34MYzORiKu7NX-72z2gGjUx_IeIZxkSrloPp9eRSo2JhlAw-UkI-XcjK22cf_A2xptF1__60Ly393lz9q9xxRU21p_R3IpmRenLKoZoeIOEbeTrtiXGZa5K20qPIMjjXpWfpLyiicAGRtPuj7uPjIEh3cBntQdo1JnkNRAHpbCFrywKwh-HP7dyWBXQ3vXzH1HKH-gt2rHjGmvB1tVh4Wh_Hq0pz-ZJRdDRoYUVgdZWEglkP4U_coL5UyUPL_9qHBW7GVIvJUOmy_S3OKYh8jDniv9qM5td8JPThxny8SmBLc7OVeqHr4A-3WidK7Uv4dtYi4Lqirea8Hu0c8t26DMVZTRhbsdEUj-a846BkP75LrkoloMsoJkyIXnjlf8AnbOiA0e7Ns64AcUL7FCtYrKeWIqR1aOADrj8TXMRp8S5LdAwOwDmAsIdo54FVpoyoM0mBtBB4oi1K18jBaeMYckwS0kKVmZ-uaPI8AAas1raHTIq7GbGsCBooc17cNNL0USQ51aROxEFF\",\"e\":\"AQAB\",\"kid\":\"" KID_PUB "\"}]}"; const char privkey_2_jwk[] = "{\"kty\":\"RSA\",\"n\":\"AKLIxChfF0q7zzxrkGFM_Qu8Cx76Cd1AEd76RxXSWvmfzfUk12FYeu1LQpzO0VvpmzuwbhRZk9Vgdo5y51s2k83ht4QvwOOIpr9rlNZrTiILeYhYheTfl2bPaKbDPY1rFFtifLhTytaIXZ18VpBQH-30ycdnyWzu34MYzORiKu7NX-72z2gGjUx_IeIZxkSrloPp9eRSo2JhlAw-UkI-XcjK22cf_A2xptF1__60Ly393lz9q9xxRU21p_R3IpmRenLKoZoeIOEbeTrtiXGZa5K20qPIMjjXpWfpLyiicAGRtPuj7uPjIEh3cBntQdo1JnkNRAHpbCFrywKwh-HP7dyWBXQ3vXzH1HKH-gt2rHjGmvB1tVh4Wh_Hq0pz-ZJRdDRoYUVgdZWEglkP4U_coL5UyUPL_9qHBW7GVIvJUOmy_S3OKYh8jDniv9qM5td8JPThxny8SmBLc7OVeqHr4A-3WidK7Uv4dtYi4Lqirea8Hu0c8t26DMVZTRhbsdEUj-a846BkP75LrkoloMsoJkyIXnjlf8AnbOiA0e7Ns64AcUL7FCtYrKeWIqR1aOADrj8TXMRp8S5LdAwOwDmAsIdo54FVpoyoM0mBtBB4oi1K18jBaeMYckwS0kKVmZ-uaPI8AAas1raHTIq7GbGsCBooc17cNNL0USQ51aROxEFF\",\"e\":\"AQAB\",\"d\":\"IHgLKEJPUwjC_To3QjEpB_4p-bPF4-uzpNYm96NNohzN5-fBThln64znbH-UItEltXIrgsObSSREgYVJwFfSg25SPHuJ7diD6gp7VYlxvDittRRzIIO4nzkflqO600pYdSHf3qRYARKSGaeDXKWeuMfqt2tsMd4zluKLe8JY7ejpCbERDZ7A8FErYP3jHi9bhlRUR4Z0MFtmPErx_WSTMEnGGXu3usOEkqMGvLcT8giBIes3LHErcaSaK5jXvenWkq74LNV1mXDxbV-T2qTPYCQ3P5Pe8JeS1nccgO5liOqXPtoj_DCBb_Li2UkJqYyQb_TFa9wzRTdK9u5fLBtdRiDzBIPWNNbhCuD2eaAEkTSW3XXUzmPSlrGC-CMUVemVOWRntPhEFgXiZz36-HMiXefX1gtYQPBVAQI1nPDYppjAGj7u-sVvtQqsKB4AQzc4ULq_ttHiJrw_TGbFSQ0QCPJ2HvnFTeBWixKICvYiRXMo7_kdUBKH7_P_jaoomzWbX39phfWOWC62UN29HjQM3GN1bkMjdolrKo5l8uBdQD_GVAMqdkund6Jw8fpuLmuhFAeOtoMVAsW2Id-CGy_jsi19cbgRHrVHe4_vu3yvvCuQ5MP0Rndivg9JvLM-Lj83JsbWfUG2UEhPycgTPFcwuTKstTciXQSXLKLFetrcsME\",\"p\":\"AM_5kF7BoxK7Tvus6_se2ExfT3lKwE7EzbTAwEhpW1CBi13AsK-bBNZXE-83zF0usZz9DUJ5BRyH4LATxMDCkp0Vza0tdu00778VOgs8wzuStbu6uoc1L7g4Y9ImYwkqLQQuA4IN_RtSdzeWGOA_to7UuMlrHSHQrlAf0npev9ceo35pBUoC0sVAE8nF9Ov2_lkgBRkuNGeQfp_IHyhdjr7r3U4tbja_E-BEFzGUUBvIYJ-1kXKx7ipmpyP47FrOUojjwKyn-xPpu4JYLrvQ-XaiA-SiO5AlBhn_iIXEb3FqBdnWQ0xSoss31jYnVVo-7muR_sBm5UmA0PtVDtrBcSs\",\"q\":\"AMhfwi-RxWW1JgP4oHPBTeOrw9TkFpVahRF5PAeZICuoVxIgPZUaNP1Hhuv6Otb4I7SNRdQioxz6jVGogpk11QV6llsV4skanAgRqWT8ds7qdAsXAhkFCmD6mAaqGs4qVLUSGrMq_KhqBcCdt2pvhy3yuIQmv_iWSJ-XJlsXbWW8DSNa2mpefenKB5dt35zKZp5xzn_UVUoC4HK7c2ZaLvs0KuUE8pgaAdAFogk7emgQfXXK9TPPRd9jullPZTx3UQ104mShX6Hz_vfuTUipPBFefzSm3OKtXM5ejkDQdYmooxnVtR20SujjJ_0D0j66v-G9L8dd-_HcNUgsp57Kf08\",\"qi\":\"bNq-mJjepyGY5mXTlzAcBFY5HqlDx-Ssapo1waswzRhnXKB15zqU-LQfotBBp7pKxWCytwgOmV2toZf_R0Sqinohh52D0mCvTYldTB5tUpKpnhEGZuOZ24BTtrqNAw2X5NRKTflttiVJgfrMCSDIR8NNX4awGB8iUNZLyn4e5Zjfa7cLCwn7ngINpU1TaCY9gbHSsYcVcpX7ro6Vr93ox6cRtapBWElzAPXDF0YKMiKzA4fTqVtbamVHz64k7xgWVwdWXDbdqOWOtI0weeeHMy2Y9Ktz7eXh0rK0fG1c35qJ-V61oXdyfapmI7uGH2R5J-Q2LDJw_3AMGb6oCsPfdQ\",\"dp\":\"I8yQlk78lA_b86R7ZlmT3-mUE4vTeHuV8SQwtQY1qrDx3Wx6vW-QsJiCnO3c5rlP53cDnkqYn6Wf_o8YkhmsBRAovEOUMhanohu0RxTpgkqpr6vfycBU-3_xZs4mxAAXiZ2mCu__foF-dfoHRCqTcRiaykj-1cBHERG5OEkw-oWSnQLU3z2HLF7wSQ4jL67vb0X8uq3iZWVQ9o4LFvaryJ9vE7LsQs43TKZL28Ps2ituvm8Rn02TcocDBEUn4iWbvWZ-1vl_VZkpJrGpMbkyB8KxqtxmJlTJLRZ4WJZMnJgkc6_XG78puJNe8yloHsWwYqHZ2SKdGz7qOikVCoC7yw\",\"dq\":\"atXb1L81b8BBT7a93lo_7FdF5_nhLKsB7kokvqxfYce0_R4hl6FMhYsgnitiOgI-D2OPysbZD3dr6BEf6Q6x0OUGy_QEYlOExCyelBCkTDjnvI38-Vgdq42Rh2QlPK2HUrAfek4-PpGhFY1CIUbr3Yzf4t5CVwnSGP1fXwxDsQ2uN56WfEZ7fi7RE2Vq589nHa3ye2e8PeUAxUu7AOSuzhOHl2qm6oBbXQ3T0nZbEqdQLYEUchZe2_fxgPL7OF0p4zHiD-OW-OP-mzT9EfPh6iTnUCxz84yZwhLaaCZ9tPMsW3b9xaO-mSOcy6PA8t9htbvIgNVUoyVVZ3EfwmOXsw\",\"kid\":\"" KID_PUB "\"}"; const char pubkey_2_pem[] = "-----BEGIN PUBLIC KEY-----\n"\ "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAosjEKF8XSrvPPGuQYUz9\n"\ "C7wLHvoJ3UAR3vpHFdJa+Z/N9STXYVh67UtCnM7RW+mbO7BuFFmT1WB2jnLnWzaT\n"\ "zeG3hC/A44imv2uU1mtOIgt5iFiF5N+XZs9opsM9jWsUW2J8uFPK1ohdnXxWkFAf\n"\ "7fTJx2fJbO7fgxjM5GIq7s1f7vbPaAaNTH8h4hnGRKuWg+n15FKjYmGUDD5SQj5d\n"\ "yMrbZx/8DbGm0XX//rQvLf3eXP2r3HFFTbWn9HcimZF6csqhmh4g4Rt5Ou2JcZlr\n"\ "krbSo8gyONelZ+kvKKJwAZG0+6Pu4+MgSHdwGe1B2jUmeQ1EAelsIWvLArCH4c/t\n"\ "3JYFdDe9fMfUcof6C3aseMaa8HW1WHhaH8erSnP5klF0NGhhRWB1lYSCWQ/hT9yg\n"\ "vlTJQ8v/2ocFbsZUi8lQ6bL9Lc4piHyMOeK/2ozm13wk9OHGfLxKYEtzs5V6oevg\n"\ "D7daJ0rtS/h21iLguqKt5rwe7Rzy3boMxVlNGFux0RSP5rzjoGQ/vkuuSiWgyygm\n"\ "TIheeOV/wCds6IDR7s2zrgBxQvsUK1isp5YipHVo4AOuPxNcxGnxLkt0DA7AOYCw\n"\ "h2jngVWmjKgzSYG0EHiiLUrXyMFp4xhyTBLSQpWZn65o8jwABqzWtodMirsZsawI\n"\ "GihzXtw00vRRJDnVpE7EQUUCAwEAAQ==\n"\ "-----END PUBLIC KEY-----\n"; const char privkey_2_pem[] = "-----BEGIN RSA PRIVATE KEY-----\n"\ "MIIJJwIBAAKCAgEAosjEKF8XSrvPPGuQYUz9C7wLHvoJ3UAR3vpHFdJa+Z/N9STX\n"\ "YVh67UtCnM7RW+mbO7BuFFmT1WB2jnLnWzaTzeG3hC/A44imv2uU1mtOIgt5iFiF\n"\ "5N+XZs9opsM9jWsUW2J8uFPK1ohdnXxWkFAf7fTJx2fJbO7fgxjM5GIq7s1f7vbP\n"\ "aAaNTH8h4hnGRKuWg+n15FKjYmGUDD5SQj5dyMrbZx/8DbGm0XX//rQvLf3eXP2r\n"\ "3HFFTbWn9HcimZF6csqhmh4g4Rt5Ou2JcZlrkrbSo8gyONelZ+kvKKJwAZG0+6Pu\n"\ "4+MgSHdwGe1B2jUmeQ1EAelsIWvLArCH4c/t3JYFdDe9fMfUcof6C3aseMaa8HW1\n"\ "WHhaH8erSnP5klF0NGhhRWB1lYSCWQ/hT9ygvlTJQ8v/2ocFbsZUi8lQ6bL9Lc4p\n"\ "iHyMOeK/2ozm13wk9OHGfLxKYEtzs5V6oevgD7daJ0rtS/h21iLguqKt5rwe7Rzy\n"\ "3boMxVlNGFux0RSP5rzjoGQ/vkuuSiWgyygmTIheeOV/wCds6IDR7s2zrgBxQvsU\n"\ "K1isp5YipHVo4AOuPxNcxGnxLkt0DA7AOYCwh2jngVWmjKgzSYG0EHiiLUrXyMFp\n"\ "4xhyTBLSQpWZn65o8jwABqzWtodMirsZsawIGihzXtw00vRRJDnVpE7EQUUCAwEA\n"\ "AQKCAgAgeAsoQk9TCML9OjdCMSkH/in5s8Xj67Ok1ib3o02iHM3n58FOGWfrjOds\n"\ "f5Qi0SW1ciuCw5tJJESBhUnAV9KDblI8e4nt2IPqCntViXG8OK21FHMgg7ifOR+W\n"\ "o7rTSlh1Id/epFgBEpIZp4NcpZ64x+q3a2wx3jOW4ot7wljt6OkJsRENnsDwUStg\n"\ "/eMeL1uGVFRHhnQwW2Y8SvH9ZJMwScYZe7e6w4SSowa8txPyCIEh6zcscStxpJor\n"\ "mNe96daSrvgs1XWZcPFtX5PapM9gJDc/k97wl5LWdxyA7mWI6pc+2iP8MIFv8uLZ\n"\ "SQmpjJBv9MVr3DNFN0r27l8sG11GIPMEg9Y01uEK4PZ5oASRNJbdddTOY9KWsYL4\n"\ "IxRV6ZU5ZGe0+EQWBeJnPfr4cyJd59fWC1hA8FUBAjWc8NimmMAaPu76xW+1Cqwo\n"\ "HgBDNzhQur+20eImvD9MZsVJDRAI8nYe+cVN4FaLEogK9iJFcyjv+R1QEofv8/+N\n"\ "qiibNZtff2mF9Y5YLrZQ3b0eNAzcY3VuQyN2iWsqjmXy4F1AP8ZUAyp2S6d3onDx\n"\ "+m4ua6EUB462gxUCxbYh34IbL+OyLX1xuBEetUd7j++7fK+8K5Dkw/RGd2K+D0m8\n"\ "sz4uPzcmxtZ9QbZQSE/JyBM8VzC5Mqy1NyJdBJcsosV62tywwQKCAQEAz/mQXsGj\n"\ "ErtO+6zr+x7YTF9PeUrATsTNtMDASGlbUIGLXcCwr5sE1lcT7zfMXS6xnP0NQnkF\n"\ "HIfgsBPEwMKSnRXNrS127TTvvxU6CzzDO5K1u7q6hzUvuDhj0iZjCSotBC4Dgg39\n"\ "G1J3N5YY4D+2jtS4yWsdIdCuUB/Sel6/1x6jfmkFSgLSxUATycX06/b+WSAFGS40\n"\ "Z5B+n8gfKF2OvuvdTi1uNr8T4EQXMZRQG8hgn7WRcrHuKmanI/jsWs5SiOPArKf7\n"\ "E+m7glguu9D5dqID5KI7kCUGGf+IhcRvcWoF2dZDTFKiyzfWNidVWj7ua5H+wGbl\n"\ "SYDQ+1UO2sFxKwKCAQEAyF/CL5HFZbUmA/igc8FN46vD1OQWlVqFEXk8B5kgK6hX\n"\ "EiA9lRo0/UeG6/o61vgjtI1F1CKjHPqNUaiCmTXVBXqWWxXiyRqcCBGpZPx2zup0\n"\ "CxcCGQUKYPqYBqoazipUtRIasyr8qGoFwJ23am+HLfK4hCa/+JZIn5cmWxdtZbwN\n"\ "I1raal596coHl23fnMpmnnHOf9RVSgLgcrtzZlou+zQq5QTymBoB0AWiCTt6aBB9\n"\ "dcr1M89F32O6WU9lPHdRDXTiZKFfofP+9+5NSKk8EV5/NKbc4q1czl6OQNB1iaij\n"\ "GdW1HbRK6OMn/QPSPrq/4b0vx1378dw1SCynnsp/TwKCAQAjzJCWTvyUD9vzpHtm\n"\ "WZPf6ZQTi9N4e5XxJDC1BjWqsPHdbHq9b5CwmIKc7dzmuU/ndwOeSpifpZ/+jxiS\n"\ "GawFECi8Q5QyFqeiG7RHFOmCSqmvq9/JwFT7f/FmzibEABeJnaYK7/9+gX51+gdE\n"\ "KpNxGJrKSP7VwEcREbk4STD6hZKdAtTfPYcsXvBJDiMvru9vRfy6reJlZVD2jgsW\n"\ "9qvIn28TsuxCzjdMpkvbw+zaK26+bxGfTZNyhwMERSfiJZu9Zn7W+X9VmSkmsakx\n"\ "uTIHwrGq3GYmVMktFnhYlkycmCRzr9cbvym4k17zKWgexbBiodnZIp0bPuo6KRUK\n"\ "gLvLAoIBAGrV29S/NW/AQU+2vd5aP+xXRef54SyrAe5KJL6sX2HHtP0eIZehTIWL\n"\ "IJ4rYjoCPg9jj8rG2Q93a+gRH+kOsdDlBsv0BGJThMQsnpQQpEw457yN/PlYHauN\n"\ "kYdkJTyth1KwH3pOPj6RoRWNQiFG692M3+LeQlcJ0hj9X18MQ7ENrjeelnxGe34u\n"\ "0RNlaufPZx2t8ntnvD3lAMVLuwDkrs4Th5dqpuqAW10N09J2WxKnUC2BFHIWXtv3\n"\ "8YDy+zhdKeMx4g/jlvjj/ps0/RHz4eok51Asc/OMmcIS2mgmfbTzLFt2/cWjvpkj\n"\ "nMujwPLfYbW7yIDVVKMlVWdxH8Jjl7MCggEAbNq+mJjepyGY5mXTlzAcBFY5HqlD\n"\ "x+Ssapo1waswzRhnXKB15zqU+LQfotBBp7pKxWCytwgOmV2toZf/R0Sqinohh52D\n"\ "0mCvTYldTB5tUpKpnhEGZuOZ24BTtrqNAw2X5NRKTflttiVJgfrMCSDIR8NNX4aw\n"\ "GB8iUNZLyn4e5Zjfa7cLCwn7ngINpU1TaCY9gbHSsYcVcpX7ro6Vr93ox6cRtapB\n"\ "WElzAPXDF0YKMiKzA4fTqVtbamVHz64k7xgWVwdWXDbdqOWOtI0weeeHMy2Y9Ktz\n"\ "7eXh0rK0fG1c35qJ+V61oXdyfapmI7uGH2R5J+Q2LDJw/3AMGb6oCsPfdQ==\n"\ "-----END RSA PRIVATE KEY-----\n"; struct _u_request admin_req; struct _u_request user_req; START_TEST(test_oidc_par_add_plugin_required_no) { json_t * j_param = json_pack("{sssssss{sssssssssssisisisosososososososososssosososssisosssosososos{s{s[ss]s[ss]s[ss]s[ss]s[ss]}}}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE_RSA, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", privkey_2_pem, "cert", pubkey_2_pem, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-device-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "request-parameter-allow-encrypted", json_true(), "oauth-par-allowed", json_true(), "oauth-par-required", json_false(), "oauth-par-request_uri-prefix", PLUGIN_PAR_PREFIX, "oauth-par-duration", PLUGIN_PAR_DURATION, "oauth-rar-allowed", json_true(), "rar-types-client-property", "authorization_data_types", "rar-allow-auth-unsigned", json_true(), "rar-allow-auth-unencrypted", json_true(), "pkce-allowed", json_true(), "pkce-method-plain-allowed", json_false(), "rar-types", RAR1, "scopes", SCOPE_1, SCOPE_2, "locations", "https://"RAR1"-1.resource.tld", "https://"RAR1"-2.resource.tld", "actions", "action1-"RAR1, "action2-"RAR1, "datatypes", "type1-"RAR1, "type2-"RAR1, "enriched", ENRICHED1, ENRICHED2); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_par_add_plugin_required_yes) { json_t * j_param = json_pack("{sssssss{sssssssssssisisisosososososososososssosososssisosssosososos{s{s[ss]s[ss]s[ss]s[ss]s[ss]}}}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE_RSA, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", privkey_2_pem, "cert", pubkey_2_pem, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-device-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "request-parameter-allow-encrypted", json_true(), "oauth-par-allowed", json_true(), "oauth-par-required", json_true(), "oauth-par-request_uri-prefix", PLUGIN_PAR_PREFIX, "oauth-par-duration", PLUGIN_PAR_DURATION, "oauth-rar-allowed", json_true(), "rar-types-client-property", "authorization_data_types", "rar-allow-auth-unsigned", json_true(), "rar-allow-auth-unencrypted", json_true(), "pkce-allowed", json_true(), "pkce-method-plain-allowed", json_false(), "rar-types", RAR1, "scopes", SCOPE_1, SCOPE_2, "locations", "https://"RAR1"-1.resource.tld", "https://"RAR1"-2.resource.tld", "actions", "action1-"RAR1, "action2-"RAR1, "datatypes", "type1-"RAR1, "type2-"RAR1, "enriched", ENRICHED1, ENRICHED2); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_par_delete_plugin) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_par_add_client_pubkey) { json_t * j_client = json_pack("{ss ss ss so s[s] s[ssss] s[s] ss so s[s]}", "client_id", CLIENT_PUBKEY_ID, "name", CLIENT_PUBKEY_NAME, "client_secret", CLIENT_PUBKEY_SECRET, "confidential", json_true(), "redirect_uri", CLIENT_PUBKEY_REDIRECT, "authorization_type", "code", "token", "id_token", "client_credentials", "scope", CLIENT_SCOPE, CLIENT_PUBKEY_PARAM, pubkey_1_pem, "enabled", json_true(), "authorization_data_types", RAR1); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_PUBKEY_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_par_delete_client_pubkey) { json_t * j_param = json_pack("{ss}", "scope", ""); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_PUBKEY_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_PUBKEY_ID, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_par_invalid_parameters) { struct _u_request req; struct _u_response resp; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/par"), U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ulfius_clean_response(&resp); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_POST_BODY_PARAMETER, "response_type", RESPONSE_TYPE, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ulfius_clean_response(&resp); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ulfius_clean_response(&resp); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_POST_BODY_PARAMETER, "client_id", NULL, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ulfius_clean_response(&resp); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_PUBKEY_ID, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_PUBKEY_REDIRECT, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ulfius_clean_response(&resp); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "response_type", "error", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ulfius_clean_response(&resp); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_POST_BODY_PARAMETER, "response_type", RESPONSE_TYPE, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_PUBKEY_REDIRECT, U_OPT_AUTH_BASIC_USER, CLIENT_PUBKEY_ID, U_OPT_AUTH_BASIC_PASSWORD, "error", U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ulfius_clean_response(&resp); ulfius_clean_request(&req); } END_TEST START_TEST(test_oidc_par_client_public) { struct _u_request req; struct _u_response resp; json_t * j_response; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/par"), U_OPT_POST_BODY_PARAMETER, "response_type", RESPONSE_TYPE, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT, U_OPT_POST_BODY_PARAMETER, "nonce", NONCE, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(201, resp.status); ck_assert_ptr_ne(NULL, j_response = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_gt(json_string_length(json_object_get(j_response, "request_uri")), o_strlen(PLUGIN_PAR_PREFIX)); ck_assert_int_eq(0, o_strncmp(json_string_value(json_object_get(j_response, "request_uri")), PLUGIN_PAR_PREFIX, o_strlen(PLUGIN_PAR_PREFIX))); ck_assert_int_eq(PLUGIN_PAR_DURATION, json_integer_value(json_object_get(j_response, "expires_in"))); ulfius_clean_response(&resp); ulfius_clean_request(&req); json_decref(j_response); } END_TEST START_TEST(test_oidc_par_client_confidential) { struct _u_request req; struct _u_response resp; json_t * j_response; jwt_t * jwt_request = NULL; char * request; int rnd; char jti[12] = {0}; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/par"), U_OPT_AUTH_BASIC_USER, CLIENT_PUBKEY_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_PUBKEY_SECRET, U_OPT_POST_BODY_PARAMETER, "response_type", RESPONSE_TYPE, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_PUBKEY_REDIRECT, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "state", STATE, U_OPT_POST_BODY_PARAMETER, "nonce", NONCE, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(201, resp.status); ck_assert_ptr_ne(NULL, j_response = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_gt(json_string_length(json_object_get(j_response, "request_uri")), o_strlen(PLUGIN_PAR_PREFIX)); ck_assert_int_eq(0, o_strncmp(json_string_value(json_object_get(j_response, "request_uri")), PLUGIN_PAR_PREFIX, o_strlen(PLUGIN_PAR_PREFIX))); ck_assert_int_eq(PLUGIN_PAR_DURATION, json_integer_value(json_object_get(j_response, "expires_in"))); ulfius_clean_response(&resp); ulfius_clean_request(&req); json_decref(j_response); ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/par"), U_OPT_POST_BODY_PARAMETER, "response_type", RESPONSE_TYPE, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_PUBKEY_ID, U_OPT_AUTH_BASIC_USER, CLIENT_PUBKEY_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_PUBKEY_SECRET, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_PUBKEY_REDIRECT, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "state", STATE, U_OPT_POST_BODY_PARAMETER, "nonce", NONCE, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(201, resp.status); ck_assert_ptr_ne(NULL, j_response = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_gt(json_string_length(json_object_get(j_response, "request_uri")), o_strlen(PLUGIN_PAR_PREFIX)); ck_assert_int_eq(0, o_strncmp(json_string_value(json_object_get(j_response, "request_uri")), PLUGIN_PAR_PREFIX, o_strlen(PLUGIN_PAR_PREFIX))); ck_assert_int_eq(PLUGIN_PAR_DURATION, json_integer_value(json_object_get(j_response, "expires_in"))); ulfius_clean_response(&resp); ulfius_clean_request(&req); json_decref(j_response); ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/par"), U_OPT_POST_BODY_PARAMETER, "response_type", RESPONSE_TYPE, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_PUBKEY_ID, U_OPT_POST_BODY_PARAMETER, "client_secret", CLIENT_PUBKEY_SECRET, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_PUBKEY_REDIRECT, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "state", STATE, U_OPT_POST_BODY_PARAMETER, "nonce", NONCE, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(201, resp.status); ck_assert_ptr_ne(NULL, j_response = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_gt(json_string_length(json_object_get(j_response, "request_uri")), o_strlen(PLUGIN_PAR_PREFIX)); ck_assert_int_eq(0, o_strncmp(json_string_value(json_object_get(j_response, "request_uri")), PLUGIN_PAR_PREFIX, o_strlen(PLUGIN_PAR_PREFIX))); ck_assert_int_eq(PLUGIN_PAR_DURATION, json_integer_value(json_object_get(j_response, "expires_in"))); ulfius_clean_response(&resp); ulfius_clean_request(&req); json_decref(j_response); r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", STATE); r_jwt_set_claim_str_value(jwt_request, "nonce", NONCE); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/par"), U_OPT_POST_BODY_PARAMETER, "request", request, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(201, resp.status); ck_assert_ptr_ne(NULL, j_response = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_gt(json_string_length(json_object_get(j_response, "request_uri")), o_strlen(PLUGIN_PAR_PREFIX)); ck_assert_int_eq(0, o_strncmp(json_string_value(json_object_get(j_response, "request_uri")), PLUGIN_PAR_PREFIX, o_strlen(PLUGIN_PAR_PREFIX))); ck_assert_int_eq(PLUGIN_PAR_DURATION, json_integer_value(json_object_get(j_response, "expires_in"))); ulfius_clean_response(&resp); ulfius_clean_request(&req); json_decref(j_response); o_free(request); r_jwt_free(jwt_request); jwt_request = NULL; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_set_enc_alg(jwt_request, R_JWA_ALG_RSA1_5); r_jwt_set_enc(jwt_request, R_JWA_ENC_A128CBC); ck_assert_int_eq(r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_PUBKEY_SECRET, o_strlen(CLIENT_PUBKEY_SECRET)), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt_request, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", STATE); r_jwt_set_claim_str_value(jwt_request, "nonce", NONCE); ck_assert_ptr_ne(request = r_jwt_serialize_nested(jwt_request, R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, NULL, 0, NULL, 0), NULL); ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/par"), U_OPT_POST_BODY_PARAMETER, "request", request, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(201, resp.status); ck_assert_ptr_ne(NULL, j_response = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_gt(json_string_length(json_object_get(j_response, "request_uri")), o_strlen(PLUGIN_PAR_PREFIX)); ck_assert_int_eq(0, o_strncmp(json_string_value(json_object_get(j_response, "request_uri")), PLUGIN_PAR_PREFIX, o_strlen(PLUGIN_PAR_PREFIX))); ck_assert_int_eq(PLUGIN_PAR_DURATION, json_integer_value(json_object_get(j_response, "expires_in"))); ulfius_clean_response(&resp); ulfius_clean_request(&req); json_decref(j_response); o_free(request); r_jwt_free(jwt_request); jwt_request = NULL; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); snprintf(jti, 11, "jti_%06d", rnd); r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/par"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/par"), U_OPT_POST_BODY_PARAMETER, "client_assertion", request, U_OPT_POST_BODY_PARAMETER, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", U_OPT_POST_BODY_PARAMETER, "response_type", RESPONSE_TYPE, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_PUBKEY_REDIRECT, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "state", STATE, U_OPT_POST_BODY_PARAMETER, "nonce", NONCE, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(201, resp.status); ck_assert_ptr_ne(NULL, j_response = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_gt(json_string_length(json_object_get(j_response, "request_uri")), o_strlen(PLUGIN_PAR_PREFIX)); ck_assert_int_eq(0, o_strncmp(json_string_value(json_object_get(j_response, "request_uri")), PLUGIN_PAR_PREFIX, o_strlen(PLUGIN_PAR_PREFIX))); ck_assert_int_eq(PLUGIN_PAR_DURATION, json_integer_value(json_object_get(j_response, "expires_in"))); ulfius_clean_response(&resp); ulfius_clean_request(&req); json_decref(j_response); o_free(request); r_jwt_free(jwt_request); jwt_request = NULL; } END_TEST START_TEST(test_oidc_par_token_client_public_invalid_parameters) { struct _u_request req; struct _u_response resp; json_t * j_response; char * request_uri; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/par"), U_OPT_POST_BODY_PARAMETER, "response_type", RESPONSE_TYPE, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(201, resp.status); ck_assert_ptr_ne(NULL, j_response = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_gt(json_string_length(json_object_get(j_response, "request_uri")), o_strlen(PLUGIN_PAR_PREFIX)); ck_assert_int_eq(0, o_strncmp(json_string_value(json_object_get(j_response, "request_uri")), PLUGIN_PAR_PREFIX, o_strlen(PLUGIN_PAR_PREFIX))); ck_assert_int_eq(PLUGIN_PAR_DURATION, json_integer_value(json_object_get(j_response, "expires_in"))); request_uri = o_strdup(json_string_value(json_object_get(j_response, "request_uri"))); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/auth"), U_OPT_URL_PARAMETER, "client_id", "error", U_OPT_URL_PARAMETER, "request_uri", request_uri, U_OPT_URL_PARAMETER, "g_continue", NULL, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/auth"), U_OPT_URL_PARAMETER, "client_id", CLIENT_PUBKEY_ID, U_OPT_URL_PARAMETER, "request_uri", request_uri, U_OPT_URL_PARAMETER, "g_continue", NULL, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ulfius_clean_response(&resp); ulfius_clean_request(&req); request_uri[o_strlen(request_uri) - 2] = '\0'; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/auth"), U_OPT_URL_PARAMETER, "client_id", CLIENT, U_OPT_URL_PARAMETER, "request_uri", request_uri, U_OPT_URL_PARAMETER, "g_continue", NULL, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ulfius_clean_response(&resp); ulfius_clean_request(&req); o_free(request_uri); json_decref(j_response); } END_TEST START_TEST(test_oidc_par_token_client_public_ok) { struct _u_request req; struct _u_response resp; json_t * j_response; char * code; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/par"), U_OPT_POST_BODY_PARAMETER, "response_type", RESPONSE_TYPE, U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT, U_OPT_POST_BODY_PARAMETER, "nonce", NONCE, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "code_challenge", CODE_CHALLENGE_VALID, U_OPT_POST_BODY_PARAMETER, "code_challenge_method", CODE_CHALLENGE_METHOD_S256, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(201, resp.status); ck_assert_ptr_ne(NULL, j_response = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_gt(json_string_length(json_object_get(j_response, "request_uri")), o_strlen(PLUGIN_PAR_PREFIX)); ck_assert_int_eq(0, o_strncmp(json_string_value(json_object_get(j_response, "request_uri")), PLUGIN_PAR_PREFIX, o_strlen(PLUGIN_PAR_PREFIX))); ck_assert_int_eq(PLUGIN_PAR_DURATION, json_integer_value(json_object_get(j_response, "expires_in"))); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/auth"), U_OPT_URL_PARAMETER, "client_id", CLIENT, U_OPT_URL_PARAMETER, "nonce", NONCE, U_OPT_URL_PARAMETER, "request_uri", json_string_value(json_object_get(j_response, "request_uri")), U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(NULL, o_strstr(u_map_get(resp.map_header, "Location"), "login.html")); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); ulfius_copy_request(&req, &user_req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/auth"), U_OPT_URL_PARAMETER, "client_id", CLIENT, U_OPT_URL_PARAMETER, "nonce", NONCE, U_OPT_URL_PARAMETER, "request_uri", json_string_value(json_object_get(j_response, "request_uri")), U_OPT_URL_PARAMETER, "g_continue", NULL, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); ulfius_copy_request(&req, &user_req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/token"), U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT, U_OPT_POST_BODY_PARAMETER, "code_verifier", CODE_VERIFIER_VALID, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ulfius_clean_response(&resp); ulfius_clean_request(&req); o_free(code); json_decref(j_response); } END_TEST START_TEST(test_oidc_auth_client_public_ok) { struct _u_request req; struct _u_response resp; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/auth"), U_OPT_URL_PARAMETER, "client_id", CLIENT, U_OPT_URL_PARAMETER, "nonce", NONCE, U_OPT_URL_PARAMETER, "redirect_uri", CLIENT_REDIRECT, U_OPT_URL_PARAMETER, "scope", SCOPE_LIST, U_OPT_URL_PARAMETER, "response_type", RESPONSE_TYPE, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(NULL, o_strstr(u_map_get(resp.map_header, "Location"), "login.html")); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); ulfius_copy_request(&req, &user_req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/auth"), U_OPT_URL_PARAMETER, "client_id", CLIENT, U_OPT_URL_PARAMETER, "nonce", NONCE, U_OPT_URL_PARAMETER, "redirect_uri", CLIENT_REDIRECT, U_OPT_URL_PARAMETER, "scope", SCOPE_LIST, U_OPT_URL_PARAMETER, "response_type", RESPONSE_TYPE, U_OPT_URL_PARAMETER, "g_continue", NULL, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); } END_TEST START_TEST(test_oidc_auth_client_public_error) { struct _u_request req; struct _u_response resp; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/auth"), U_OPT_URL_PARAMETER, "client_id", CLIENT, U_OPT_URL_PARAMETER, "nonce", NONCE, U_OPT_URL_PARAMETER, "redirect_uri", CLIENT_REDIRECT, U_OPT_URL_PARAMETER, "scope", SCOPE_LIST, U_OPT_URL_PARAMETER, "response_type", RESPONSE_TYPE, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); ulfius_copy_request(&req, &user_req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/auth"), U_OPT_URL_PARAMETER, "client_id", CLIENT, U_OPT_URL_PARAMETER, "nonce", NONCE, U_OPT_URL_PARAMETER, "redirect_uri", CLIENT_REDIRECT, U_OPT_URL_PARAMETER, "scope", SCOPE_LIST, U_OPT_URL_PARAMETER, "response_type", RESPONSE_TYPE, U_OPT_URL_PARAMETER, "g_continue", NULL, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ulfius_clean_response(&resp); ulfius_clean_request(&req); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc pushed authentication requests"); tc_core = tcase_create("test_oidc_pushed_authentication_requests"); tcase_add_test(tc_core, test_oidc_par_add_plugin_required_no); tcase_add_test(tc_core, test_oidc_par_add_client_pubkey); tcase_add_test(tc_core, test_oidc_par_invalid_parameters); tcase_add_test(tc_core, test_oidc_par_client_public); tcase_add_test(tc_core, test_oidc_par_client_confidential); tcase_add_test(tc_core, test_oidc_par_token_client_public_invalid_parameters); tcase_add_test(tc_core, test_oidc_par_token_client_public_ok); tcase_add_test(tc_core, test_oidc_auth_client_public_ok); tcase_add_test(tc_core, test_oidc_par_delete_plugin); tcase_add_test(tc_core, test_oidc_par_add_plugin_required_yes); tcase_add_test(tc_core, test_oidc_par_invalid_parameters); tcase_add_test(tc_core, test_oidc_par_client_public); tcase_add_test(tc_core, test_oidc_par_client_confidential); tcase_add_test(tc_core, test_oidc_par_token_client_public_invalid_parameters); tcase_add_test(tc_core, test_oidc_par_token_client_public_ok); tcase_add_test(tc_core, test_oidc_auth_client_public_error); tcase_add_test(tc_core, test_oidc_par_delete_client_pubkey); tcase_add_test(tc_core, test_oidc_par_delete_plugin); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req; struct _u_response auth_resp, scope_resp; int res, do_test = 0; char * cookie; json_t * j_body; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); ulfius_init_request(&user_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&scope_req); ulfius_init_response(&auth_resp); ulfius_init_response(&scope_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "User %s authenticated", USER_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(scope_req.map_header, "Cookie", cookie); u_map_put(user_req.map_header, "Cookie", cookie); o_free(cookie); scope_req.http_verb = strdup("PUT"); scope_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, &scope_resp) != U_OK || scope_resp.status != 200) { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope '%s' for %s error", CLIENT, SCOPE_LIST); do_test = 0; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope OK"); } ulfius_clean_response(&scope_resp); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication user"); do_test = 0; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); } if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, NULL) != U_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "Remove grant scope '%s' for %s error", CLIENT, SCOPE_LIST); } char * url = msprintf("%s/auth/", SERVER_URI); run_simple_test(&user_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL); o_free(url); ulfius_clean_request(&admin_req); ulfius_clean_request(&user_req); ulfius_clean_request(&scope_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_reduced_scope.c000066400000000000000000001046021415646314000222300ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "user1" #define USER_PASSWORD "password" #define PLUGIN_MODULE "oidc" #define PLUGIN_NAME "oidc_rar" #define PLUGIN_ISS "https://glewlwyd.tld" #define PLUGIN_JWT_TYPE_RSA "rsa" #define PLUGIN_JWT_KEY_SIZE "256" #define SCOPE_OPENID "openid" #define SCOPE_1 "scope1" #define SCOPE_2 "scope2" #define SCOPE_3 "scope3" #define NONCE "nonce1234" #define SCOPE_LIST (SCOPE_OPENID " " SCOPE_1 " " SCOPE_2 " " SCOPE_3) #define CLIENT_ID "client_reduced" #define CLIENT_SECRET "password" #define CLIENT_REDIRECT "https://client.glewlwyd.tld" #define PLUGIN_CODE_DURATION 600 #define PLUGIN_REFRESH_TOKEN_DURATION 1209600 #define PLUGIN_ACCESS_TOKEN_DURATION 3600 #define PLUGIN_PAR_PREFIX "urn:ietf:params:oauth:request_uri:" struct _u_request admin_req; struct _u_request user_req; START_TEST(test_oidc_reduced_scope_add_plugin) { json_t * j_param = json_pack("{sssssss{sssssssssisisisososososososososossss}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", PLUGIN_ISS, "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_NAME, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-device-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "oauth-par-allowed", json_true(), "oauth-par-request_uri-prefix", PLUGIN_PAR_PREFIX, "restrict-scope-client-property", "scope"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_reduced_scope_add_client) { json_t * j_param = json_pack("{sssososss[s]s[sss]s[ssss]}", "client_id", CLIENT_ID, "enabled", json_true(), "confidential", json_true(), "secret", CLIENT_SECRET, "redirect_uri", CLIENT_REDIRECT, "scope", SCOPE_OPENID, SCOPE_1, SCOPE_2, "authorization_type", "code", "token", "id_token", "device_authorization"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_reduced_scope_grant_client) { json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_reduced_scope_delete_plugin) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_reduced_scope_delete_client) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_ID, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_reduced_scope_delete_grant_client) { json_t * j_param = json_pack("{ss}", "scope", ""); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_reduced_scope_auth_scope_not_reduced) { char * scope; struct _u_response resp; struct _u_request * req = ulfius_duplicate_request(&user_req); ck_assert_int_eq(U_OK, ulfius_set_request_properties(req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth", U_OPT_URL_PARAMETER, "client_id", CLIENT_ID, U_OPT_URL_PARAMETER, "redirect_uri", CLIENT_REDIRECT, U_OPT_URL_PARAMETER, "scope", (SCOPE_OPENID " " SCOPE_1 " " SCOPE_2), U_OPT_URL_PARAMETER, "response_type", "id_token token", U_OPT_URL_PARAMETER, "nonce", "nonceAbcdXyz1234", U_OPT_URL_PARAMETER, "g_continue", NULL, U_OPT_NONE)); ulfius_init_response(&resp); ck_assert_int_eq(U_OK, ulfius_send_http_request(req, &resp)); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "scope="), NULL); scope = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "scope=") + o_strlen("scope=")); if (o_strchr(scope, '&') != NULL) { *o_strchr(scope, '&') = '\0'; } ck_assert_str_eq(scope, (SCOPE_OPENID "+" SCOPE_1 "+" SCOPE_2)); ulfius_clean_request_full(req); ulfius_clean_response(&resp); o_free(scope); } END_TEST START_TEST(test_oidc_reduced_scope_auth_scope_reduced) { char * scope, * code; struct _u_response resp; struct _u_request * req = ulfius_duplicate_request(&user_req), code_req; json_t * j_body; ck_assert_int_eq(U_OK, ulfius_set_request_properties(req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth", U_OPT_URL_PARAMETER, "client_id", CLIENT_ID, U_OPT_URL_PARAMETER, "redirect_uri", CLIENT_REDIRECT, U_OPT_URL_PARAMETER, "scope", (SCOPE_OPENID " " SCOPE_1 " " SCOPE_3), U_OPT_URL_PARAMETER, "response_type", "id_token token code", U_OPT_URL_PARAMETER, "nonce", "nonceAbcdXyz1234", U_OPT_URL_PARAMETER, "g_continue", NULL, U_OPT_NONE)); ulfius_init_response(&resp); ck_assert_int_eq(U_OK, ulfius_send_http_request(req, &resp)); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "scope="), NULL); scope = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "scope=") + o_strlen("scope=")); if (o_strchr(scope, '&') != NULL) { *o_strchr(scope, '&') = '\0'; } code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code=")); if (o_strchr(code, '&') != NULL) { *o_strchr(code, '&') = '\0'; } ck_assert_str_eq(scope, (SCOPE_OPENID "+" SCOPE_1)); ulfius_clean_request_full(req); ulfius_clean_response(&resp); ulfius_init_request(&code_req); ulfius_init_response(&resp); ck_assert_int_eq(U_OK, ulfius_set_request_properties(&code_req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT, U_OPT_NONE)); ck_assert_int_eq(U_OK, ulfius_send_http_request(&code_req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq(json_string_value(json_object_get(j_body, "scope")), (SCOPE_OPENID " " SCOPE_1)); json_decref(j_body); ulfius_clean_response(&resp); ulfius_clean_request(&code_req); o_free(scope); o_free(code); } END_TEST START_TEST(test_oidc_reduced_scope_auth_scope_invalid) { struct _u_response resp; struct _u_request * req = ulfius_duplicate_request(&user_req); ck_assert_int_eq(U_OK, ulfius_set_request_properties(req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth", U_OPT_URL_PARAMETER, "client_id", CLIENT_ID, U_OPT_URL_PARAMETER, "redirect_uri", CLIENT_REDIRECT, U_OPT_URL_PARAMETER, "scope", (SCOPE_3), U_OPT_URL_PARAMETER, "response_type", "id_token token", U_OPT_URL_PARAMETER, "nonce", "nonceAbcdXyz1234", U_OPT_URL_PARAMETER, "g_continue", NULL, U_OPT_NONE)); ulfius_init_response(&resp); ck_assert_int_eq(U_OK, ulfius_send_http_request(req, &resp)); ck_assert_int_eq(302, resp.status); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "scope="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "error=invalid_scope"), NULL); ulfius_clean_request_full(req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_reduced_scope_par_scope_not_reduced) { struct _u_request req; struct _u_response resp; json_t * j_response, * j_body; char * code; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/par"), U_OPT_POST_BODY_PARAMETER, "response_type", "code token", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "nonce", NONCE, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT, U_OPT_POST_BODY_PARAMETER, "scope", (SCOPE_OPENID " " SCOPE_1 " " SCOPE_2), U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(201, resp.status); ck_assert_ptr_ne(NULL, j_response = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_gt(json_string_length(json_object_get(j_response, "request_uri")), o_strlen(PLUGIN_PAR_PREFIX)); ck_assert_int_eq(0, o_strncmp(json_string_value(json_object_get(j_response, "request_uri")), PLUGIN_PAR_PREFIX, o_strlen(PLUGIN_PAR_PREFIX))); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); ulfius_copy_request(&req, &user_req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/auth"), U_OPT_URL_PARAMETER, "client_id", CLIENT_ID, U_OPT_URL_PARAMETER, "request_uri", json_string_value(json_object_get(j_response, "request_uri")), U_OPT_URL_PARAMETER, "g_continue", NULL, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); ulfius_copy_request(&req, &user_req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/token"), U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq(json_string_value(json_object_get(j_body, "scope")), (SCOPE_OPENID " " SCOPE_1 " " SCOPE_2)); json_decref(j_body); ulfius_clean_response(&resp); ulfius_clean_request(&req); o_free(code); json_decref(j_response); } END_TEST START_TEST(test_oidc_reduced_scope_par_scope_reduced) { struct _u_request req; struct _u_response resp; json_t * j_response, * j_body; char * code; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/par"), U_OPT_POST_BODY_PARAMETER, "response_type", "code token", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "nonce", NONCE, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT, U_OPT_POST_BODY_PARAMETER, "scope", (SCOPE_OPENID " " SCOPE_1 " " SCOPE_3), U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(201, resp.status); ck_assert_ptr_ne(NULL, j_response = ulfius_get_json_body_response(&resp, NULL)); ck_assert_int_gt(json_string_length(json_object_get(j_response, "request_uri")), o_strlen(PLUGIN_PAR_PREFIX)); ck_assert_int_eq(0, o_strncmp(json_string_value(json_object_get(j_response, "request_uri")), PLUGIN_PAR_PREFIX, o_strlen(PLUGIN_PAR_PREFIX))); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); ulfius_copy_request(&req, &user_req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/auth"), U_OPT_URL_PARAMETER, "client_id", CLIENT_ID, U_OPT_URL_PARAMETER, "request_uri", json_string_value(json_object_get(j_response, "request_uri")), U_OPT_URL_PARAMETER, "g_continue", NULL, U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); ulfius_copy_request(&req, &user_req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/token"), U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq(json_string_value(json_object_get(j_body, "scope")), (SCOPE_OPENID " " SCOPE_1)); json_decref(j_body); ulfius_clean_response(&resp); ulfius_clean_request(&req); o_free(code); json_decref(j_response); } END_TEST START_TEST(test_oidc_reduced_scope_par_scope_invalid) { struct _u_request req; struct _u_response resp; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, (SERVER_URI "/" PLUGIN_NAME "/par"), U_OPT_POST_BODY_PARAMETER, "response_type", "code token", U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT, U_OPT_POST_BODY_PARAMETER, "scope", (SCOPE_3), U_OPT_NONE); ck_assert_int_eq(U_OK, ulfius_send_http_request(&req, &resp)); ck_assert_int_eq(403, resp.status); ulfius_clean_response(&resp); ulfius_clean_request(&req); } END_TEST START_TEST(test_oidc_reduced_scope_device_verification_scope_not_reduced) { struct _u_request req; struct _u_response resp; json_t * j_resp; const char * redirect_uri, * code, * device_code; jwt_t * jwt; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", (SCOPE_OPENID " " SCOPE_1 " " SCOPE_2)); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "device_code", device_code); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "refresh_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "id_token"), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "scope")), (SCOPE_OPENID " " SCOPE_1 " " SCOPE_2)); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_str_eq((SCOPE_OPENID " " SCOPE_1 " " SCOPE_2), r_jwt_get_claim_str_value(jwt, "aud")); r_jwt_free(jwt); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_reduced_scope_device_verification_scope_reduced) { struct _u_request req; struct _u_response resp; json_t * j_resp; const char * redirect_uri, * code, * device_code; jwt_t * jwt; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", (SCOPE_OPENID " " SCOPE_1 " " SCOPE_3)); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "device_code", device_code); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "refresh_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "id_token"), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "scope")), (SCOPE_OPENID " " SCOPE_1)); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_str_eq((SCOPE_OPENID " " SCOPE_1), r_jwt_get_claim_str_value(jwt, "aud")); r_jwt_free(jwt); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_reduced_scope_device_verification_scope_invalid) { struct _u_request req; struct _u_response resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", (SCOPE_3)); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(403, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc reduced scope"); tc_core = tcase_create("test_oidc_reduced_scope"); tcase_add_test(tc_core, test_oidc_reduced_scope_add_plugin); tcase_add_test(tc_core, test_oidc_reduced_scope_add_client); tcase_add_test(tc_core, test_oidc_reduced_scope_grant_client); tcase_add_test(tc_core, test_oidc_reduced_scope_auth_scope_not_reduced); tcase_add_test(tc_core, test_oidc_reduced_scope_auth_scope_reduced); tcase_add_test(tc_core, test_oidc_reduced_scope_auth_scope_invalid); tcase_add_test(tc_core, test_oidc_reduced_scope_par_scope_not_reduced); tcase_add_test(tc_core, test_oidc_reduced_scope_par_scope_reduced); tcase_add_test(tc_core, test_oidc_reduced_scope_par_scope_invalid); tcase_add_test(tc_core, test_oidc_reduced_scope_device_verification_scope_not_reduced); tcase_add_test(tc_core, test_oidc_reduced_scope_device_verification_scope_reduced); tcase_add_test(tc_core, test_oidc_reduced_scope_device_verification_scope_invalid); tcase_add_test(tc_core, test_oidc_reduced_scope_delete_grant_client); tcase_add_test(tc_core, test_oidc_reduced_scope_delete_client); tcase_add_test(tc_core, test_oidc_reduced_scope_delete_plugin); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, register_req; struct _u_response auth_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); ulfius_init_request(&user_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(®ister_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "User %s authenticated", USER_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(user_req.map_header, "Cookie", cookie); u_map_put(register_req.map_header, "Cookie", cookie); o_free(cookie); j_body = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "register", json_true()); run_simple_test(®ister_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL); json_decref(j_body); j_body = json_pack("{sssssss{ss}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); run_simple_test(&user_req, "POST", SERVER_URI "/auth/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL); json_decref(j_body); j_body = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "register", json_true()); run_simple_test(®ister_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL); json_decref(j_body); j_body = json_pack("{sssssss{ss}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); run_simple_test(&user_req, "POST", SERVER_URI "/auth/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL); json_decref(j_body); j_body = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "register", json_true()); run_simple_test(®ister_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL); json_decref(j_body); j_body = json_pack("{sssssss{ss}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); run_simple_test(&user_req, "POST", SERVER_URI "/auth/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL); json_decref(j_body); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication user"); do_test = 0; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); } if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } j_body = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "register", json_false()); run_simple_test(®ister_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL); json_decref(j_body); j_body = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "register", json_false()); run_simple_test(®ister_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL); json_decref(j_body); j_body = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "register", json_false()); run_simple_test(®ister_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL); json_decref(j_body); char * url = msprintf("%s/auth/", SERVER_URI); run_simple_test(&user_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL); o_free(url); ulfius_clean_request(&admin_req); ulfius_clean_request(&user_req); ulfius_clean_request(®ister_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_refresh_manage.c000066400000000000000000000307641415646314000224010ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile" char * bearer_token; char * refresh_token, * refresh_token_all; char user_agent[33]; START_TEST(test_oidc_refresh_manage_endpoints_noauth) { ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/oidc/token/", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "DELETE", SERVER_URI "/oidc/token/test", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_refresh_manage_list) { struct _u_request user_req; struct _u_response resp; json_t * j_body = NULL; ulfius_init_request(&user_req); ulfius_init_response(&resp); u_map_put(user_req.map_header, "Authorization", bearer_token); user_req.http_url = o_strdup(SERVER_URI "/oidc/token/"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/oidc/token/?limit=1"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(json_array_size(j_body), 1); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/oidc/token/?offset=1"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/oidc/token/?sort=authorization_type"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/oidc/token/?sort=issued_at&desc"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/oidc/token/?sort=client_id&desc&limit=1"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(json_array_size(j_body), 1); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/oidc/token/?pattern=%s", user_agent); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/oidc/token/?pattern=oidc-oauth2-test-"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/oidc/token/?pattern=error"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(json_array_size(j_body), 0); ulfius_clean_request(&user_req); ulfius_clean_response(&resp); json_decref(j_body); } END_TEST START_TEST(test_oidc_refresh_manage_delete_not_found) { struct _u_request user_req; struct _u_response resp; ulfius_init_request(&user_req); ulfius_init_response(&resp); u_map_put(user_req.map_header, "Authorization", bearer_token); user_req.http_url = o_strdup(SERVER_URI "/oidc/token/error"); user_req.http_verb = o_strdup("DELETE"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 404); ulfius_clean_request(&user_req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_refresh_manage_delete_ok) { char * url = msprintf("%s/oidc/token/", SERVER_URI), * token_hash, * token_hash_encoded; struct _u_map body; struct _u_request user_req; struct _u_response resp; int res; json_t * j_body; ulfius_init_request(&user_req); ulfius_init_response(&resp); u_map_put(user_req.map_header, "Authorization", bearer_token); user_req.http_url = msprintf(SERVER_URI "/oidc/token/?sort=issued_at&desc&limit=1&pattern=%s", user_agent); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ck_assert_ptr_ne((token_hash = o_strdup(json_string_value(json_object_get(json_array_get(j_body, 0), "token_hash")))), NULL); ck_assert_ptr_ne((token_hash_encoded = url_encode(token_hash)), NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_init(&body); u_map_put(&body, "grant_type", "refresh_token"); u_map_put(&body, "refresh_token", refresh_token); res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 200, NULL, NULL, NULL); ck_assert_int_eq(res, 1); o_free(user_req.http_url); o_free(user_req.http_verb); u_map_put(user_req.map_header, "Authorization", bearer_token); user_req.http_url = msprintf(SERVER_URI "/oidc/token/%s", token_hash_encoded); user_req.http_verb = o_strdup("DELETE"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); user_req.http_url = msprintf(SERVER_URI "/oidc/token/?sort=issued_at&desc&limit=1&pattern=%s", user_agent); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ck_assert_ptr_eq(json_object_get(json_array_get(j_body, 0), "enabled"), json_false()); res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 400, NULL, NULL, NULL); free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); ulfius_clean_request(&user_req); ulfius_clean_response(&resp); o_free(token_hash); o_free(token_hash_encoded); json_decref(j_body); } END_TEST START_TEST(test_oidc_refresh_manage_delete_all_ok) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; struct _u_request user_req; struct _u_response resp; int res; json_t * j_body; ulfius_init_request(&user_req); ulfius_init_response(&resp); u_map_put(user_req.map_header, "Authorization", bearer_token); user_req.http_url = msprintf(SERVER_URI "/oidc/token/"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_init(&body); u_map_put(&body, "grant_type", "refresh_token"); u_map_put(&body, "refresh_token", refresh_token_all); res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 200, NULL, NULL, NULL); u_map_clean(&body); ck_assert_int_eq(res, 1); o_free(user_req.http_url); o_free(user_req.http_verb); u_map_put(user_req.map_header, "Authorization", bearer_token); user_req.http_url = msprintf(SERVER_URI "/oidc/token/"); user_req.http_verb = o_strdup("DELETE"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); user_req.http_url = msprintf(SERVER_URI "/oidc/token/"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); free(url); ulfius_clean_request(&user_req); ulfius_clean_response(&resp); json_decref(j_body); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc refresh manage"); tc_core = tcase_create("test_oidc_refresh_manage"); tcase_add_test(tc_core, test_oidc_refresh_manage_endpoints_noauth); tcase_add_test(tc_core, test_oidc_refresh_manage_list); tcase_add_test(tc_core, test_oidc_refresh_manage_delete_not_found); tcase_add_test(tc_core, test_oidc_refresh_manage_delete_ok); tcase_add_test(tc_core, test_oidc_refresh_manage_delete_all_ok); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0, number_failed = 1, x[1]; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/oidc/token/", SERVER_URI); gnutls_rnd(GNUTLS_RND_NONCE, x, sizeof(int)); snprintf(user_agent, 32, "oidc-oauth2-test-%d", x[0]); u_map_put(auth_req.map_header, "User-Agent", user_agent); u_map_put(auth_req.map_post_body, "grant_type", "password"); u_map_put(auth_req.map_post_body, "username", USERNAME); u_map_put(auth_req.map_post_body, "password", PASSWORD); u_map_put(auth_req.map_post_body, "scope", SCOPE_LIST); ulfius_init_response(&auth_resp); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { json_t * json_body = ulfius_get_json_body_response(&auth_resp, NULL); bearer_token = msprintf("Bearer %s", json_string_value(json_object_get(json_body, "access_token"))); refresh_token = o_strdup(json_string_value(json_object_get(json_body, "refresh_token"))); y_log_message(Y_LOG_LEVEL_INFO, "User %s authenticated - refresh_token", USERNAME); json_decref(json_body); do_test = 1; } ulfius_clean_response(&auth_resp); ulfius_init_response(&auth_resp); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { json_t * json_body = ulfius_get_json_body_response(&auth_resp, NULL); refresh_token_all = o_strdup(json_string_value(json_object_get(json_body, "refresh_token"))); y_log_message(Y_LOG_LEVEL_INFO, "User %s authenticated - refresh_token_all", USERNAME); json_decref(json_body); do_test = 1; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } o_free(bearer_token); o_free(refresh_token); o_free(refresh_token_all); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_refresh_manage_session.c000066400000000000000000000254131415646314000241370ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile" char * bearer_token; char * refresh_token; char user_agent[33]; struct _u_request user_req; START_TEST(test_oidc_refresh_manage_session_endpoints_noauth) { ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/oidc/token/", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "DELETE", SERVER_URI "/oidc/token/test", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_refresh_manage_session_list) { struct _u_response resp; json_t * j_body = NULL; ulfius_init_response(&resp); u_map_put(user_req.map_header, "Authorization", bearer_token); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/oidc/token/"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/oidc/token/?limit=1"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(json_array_size(j_body), 1); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/oidc/token/?offset=1"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/oidc/token/?sort=authorization_type"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/oidc/token/?sort=issued_at&desc"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/oidc/token/?sort=client_id&desc&limit=1"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(json_array_size(j_body), 1); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/oidc/token/?pattern=%s", user_agent); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/oidc/token/?pattern=oidc-oauth2-test-"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "/oidc/token/?pattern=error"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(json_array_size(j_body), 0); ulfius_clean_response(&resp); json_decref(j_body); } END_TEST START_TEST(test_oidc_refresh_manage_session_delete_not_found) { struct _u_response resp; ulfius_init_response(&resp); o_free(user_req.http_url); o_free(user_req.http_verb); user_req.http_url = o_strdup(SERVER_URI "/oidc/token/error"); user_req.http_verb = o_strdup("DELETE"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 404); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_refresh_manage_session_delete_ok) { char * url = SERVER_URI "/oidc/token/", * token_hash, * token_hash_encoded; struct _u_map body; struct _u_response resp; int res; json_t * j_body; ulfius_init_response(&resp); o_free(user_req.http_url); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); user_req.http_url = msprintf(SERVER_URI "/oidc/token/?sort=issued_at&desc&limit=1&pattern=%s", user_agent); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ck_assert_ptr_ne((token_hash = o_strdup(json_string_value(json_object_get(json_array_get(j_body, 0), "token_hash")))), NULL); ck_assert_ptr_ne((token_hash_encoded = url_encode(token_hash)), NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_init(&body); u_map_put(&body, "grant_type", "refresh_token"); u_map_put(&body, "refresh_token", refresh_token); res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 200, NULL, NULL, NULL); ck_assert_int_eq(res, 1); o_free(user_req.http_url); o_free(user_req.http_verb); user_req.http_url = msprintf(SERVER_URI "/oidc/token/%s", token_hash_encoded); user_req.http_verb = o_strdup("DELETE"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ulfius_init_response(&resp); o_free(user_req.http_url); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); user_req.http_url = msprintf(SERVER_URI "/oidc/token/?sort=issued_at&desc&limit=1&pattern=%s", user_agent); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); j_body = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_gt(json_array_size(j_body), 0); ck_assert_ptr_eq(json_object_get(json_array_get(j_body, 0), "enabled"), json_false()); res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 400, NULL, NULL, NULL); u_map_clean(&body); ck_assert_int_eq(res, 1); ulfius_clean_response(&resp); o_free(token_hash); o_free(token_hash_encoded); json_decref(j_body); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc refresh manage session"); tc_core = tcase_create("test_oidc_refresh_manage_session"); tcase_add_test(tc_core, test_oidc_refresh_manage_session_endpoints_noauth); tcase_add_test(tc_core, test_oidc_refresh_manage_session_list); tcase_add_test(tc_core, test_oidc_refresh_manage_session_delete_not_found); tcase_add_test(tc_core, test_oidc_refresh_manage_session_delete_ok); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, i, do_test = 0, number_failed = 1, x[1]; json_t * j_body; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/oidc/token/", SERVER_URI); gnutls_rnd(GNUTLS_RND_NONCE, x, sizeof(int)); snprintf(user_agent, 32, "oidc-oauth2-test-%d", x[0]); u_map_put(auth_req.map_header, "User-Agent", user_agent); u_map_put(auth_req.map_post_body, "grant_type", "password"); u_map_put(auth_req.map_post_body, "username", USERNAME); u_map_put(auth_req.map_post_body, "password", PASSWORD); u_map_put(auth_req.map_post_body, "scope", SCOPE_LIST); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { json_t * json_body = ulfius_get_json_body_response(&auth_resp, NULL); bearer_token = msprintf("Bearer %s", json_string_value(json_object_get(json_body, "access_token"))); refresh_token = o_strdup(json_string_value(json_object_get(json_body, "refresh_token"))); y_log_message(Y_LOG_LEVEL_INFO, "User %s authenticated", USERNAME); json_decref(json_body); } ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile scope3" char * refresh_token; START_TEST(test_oidc_refresh_token_token_invalid) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "refresh_token"); u_map_put(&body, "refresh_token", "invalid"); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 400, NULL, NULL, NULL); free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_refresh_token_ok) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "refresh_token"); u_map_put(&body, "refresh_token", refresh_token); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 200, NULL, NULL, NULL); free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc refresh token"); tc_core = tcase_create("test_oidc_refresh_token"); tcase_add_test(tc_core, test_oidc_refresh_token_token_invalid); tcase_add_test(tc_core, test_oidc_refresh_token_ok); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/oidc/token/", SERVER_URI); u_map_put(auth_req.map_post_body, "grant_type", "password"); u_map_put(auth_req.map_post_body, "username", USERNAME); u_map_put(auth_req.map_post_body, "password", PASSWORD); u_map_put(auth_req.map_post_body, "scope", SCOPE_LIST); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { json_t * json_body = ulfius_get_json_body_response(&auth_resp, NULL); refresh_token = o_strdup(json_string_value(json_object_get(json_body, "refresh_token"))); y_log_message(Y_LOG_LEVEL_INFO, "User %s authenticated", USERNAME); json_decref(json_body); } ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); o_free(refresh_token); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_refresh_token_client_confidential.c000066400000000000000000000105251415646314000263370ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile scope3" #define CLIENT "client3_id" #define CLIENT_PASSWORD "password" char * refresh_token; START_TEST(test_oidc_refresh_token_client_confidential_token_invalid) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "refresh_token"); u_map_put(&body, "refresh_token", "invalid"); int res = run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_refresh_token_client_confidential_client_invalid) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "refresh_token"); u_map_put(&body, "refresh_token", refresh_token); int res = run_simple_test(NULL, "POST", url, CLIENT, "invalid", NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_refresh_token_client_confidential_no_client) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "refresh_token"); u_map_put(&body, "refresh_token", refresh_token); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_refresh_token_client_confidential_ok) { char * url = msprintf("%s/oidc/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "refresh_token"); u_map_put(&body, "refresh_token", refresh_token); int res = run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 200, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc refresh token client confidential"); tc_core = tcase_create("test_oidc_refresh_token"); tcase_add_test(tc_core, test_oidc_refresh_token_client_confidential_token_invalid); tcase_add_test(tc_core, test_oidc_refresh_token_client_confidential_client_invalid); tcase_add_test(tc_core, test_oidc_refresh_token_client_confidential_no_client); tcase_add_test(tc_core, test_oidc_refresh_token_client_confidential_ok); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/oidc/token/", SERVER_URI); u_map_put(auth_req.map_post_body, "grant_type", "password"); u_map_put(auth_req.map_post_body, "username", USERNAME); u_map_put(auth_req.map_post_body, "password", PASSWORD); u_map_put(auth_req.map_post_body, "scope", SCOPE_LIST); auth_req.auth_basic_user = strdup(CLIENT); auth_req.auth_basic_password = strdup(CLIENT_PASSWORD); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { json_t * json_body = ulfius_get_json_body_response(&auth_resp, NULL); refresh_token = o_strdup(json_string_value(json_object_get(json_body, "refresh_token"))); y_log_message(Y_LOG_LEVEL_INFO, "User %s authenticated", USERNAME); json_decref(json_body); } ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); o_free(refresh_token); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_refresh_token_one_use.c000066400000000000000000000500351415646314000237770ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define SCOPE_LIST "openid g_profile" #define NONCE_TEST "nonce5678" #define STATE_TEST "abcxyz" #define PLUGIN_MODULE "oidc" #define PLUGIN_NAME "oidc_refresh_one_use" #define PLUGIN_DISPLAY_NAME "oidc with one use refresh tokens" #define PLUGIN_ISS "https://glewlwyd.tld" #define PLUGIN_CODE_DURATION 600 #define PLUGIN_REFRESH_TOKEN_DURATION 1209600 #define PLUGIN_REFRESH_TOKEN_ONE_USE_ALWAYS "always" #define PLUGIN_REFRESH_TOKEN_ONE_USE_CLIENT_DRIVEN "client-driven" #define PLUGIN_REFRESH_TOKEN_ONE_USE_NEVER "never" #define PLUGIN_ACCESS_TOKEN_DURATION 3600 #define CLIENT_ID "client_refresh_one_use" #define CLIENT_NAME "client one use refresh tokens" #define CLIENT_SECRET "very-secret" struct _u_request admin_req; START_TEST(test_oidc_refresh_token_one_use_add_module_always_ok) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisssisosososososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", "sha", "jwt-key-size", "256", "key", "secret", "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "refresh-token-one-use", PLUGIN_REFRESH_TOKEN_ONE_USE_ALWAYS, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "auth-type-device-enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_refresh_token_one_use_add_module_client_driven_ok) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisssssisosososososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", "sha", "jwt-key-size", "256", "key", "secret", "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "refresh-token-one-use", PLUGIN_REFRESH_TOKEN_ONE_USE_CLIENT_DRIVEN, "client-refresh-token-one-use-parameter", "refresh-token-one-use", "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "auth-type-device-enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_refresh_token_one_use_add_module_never_ok) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisssisosososososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", "sha", "jwt-key-size", "256", "key", "secret", "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "refresh-token-one-use", PLUGIN_REFRESH_TOKEN_ONE_USE_NEVER, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "auth-type-device-enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_refresh_token_one_use_add_client_ok) { json_t * j_parameters = json_pack("{sssssssos[ss]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "client_secret", CLIENT_SECRET, "confidential", json_true(), "authorization_type", "password", "refresh_token", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_refresh_token_one_use_add_client_driven_one_use_ok) { json_t * j_parameters = json_pack("{sssssssssos[ss]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "client_secret", CLIENT_SECRET, "refresh-token-one-use", "1", "confidential", json_true(), "authorization_type", "password", "refresh_token", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_refresh_token_one_use_add_client_driven_multiple_use_ok) { json_t * j_parameters = json_pack("{sssssssssos[ss]so}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "client_secret", CLIENT_SECRET, "refresh-token-one-use", "0", "confidential", json_true(), "authorization_type", "password", "refresh_token", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_refresh_token_one_use_delete_module) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_refresh_token_one_use_delete_client) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_ID, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_refresh_token_one_use_refresh_queue_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp; const char * refresh_token; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "client_secret", CLIENT_SECRET); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_ptr_ne(refresh_token = json_string_value(json_object_get(j_resp, "refresh_token")), NULL); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); u_map_put(req.map_post_body, "grant_type", "refresh_token"); u_map_put(req.map_post_body, "refresh_token", refresh_token); json_decref(j_resp); u_map_remove_from_key(req.map_post_body, "scope"); u_map_remove_from_key(req.map_post_body, "username"); u_map_remove_from_key(req.map_post_body, "password"); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_ptr_ne(refresh_token = json_string_value(json_object_get(j_resp, "refresh_token")), NULL); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); u_map_put(req.map_post_body, "refresh_token", refresh_token); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "refresh_token"), NULL); ulfius_clean_response(&resp); json_decref(j_resp); ulfius_clean_request(&req); } END_TEST START_TEST(test_oidc_refresh_token_one_use_refresh_no_queue_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp; const char * refresh_token; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "client_secret", CLIENT_SECRET); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_ptr_ne(refresh_token = json_string_value(json_object_get(j_resp, "refresh_token")), NULL); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); u_map_put(req.map_post_body, "grant_type", "refresh_token"); u_map_put(req.map_post_body, "refresh_token", refresh_token); json_decref(j_resp); u_map_remove_from_key(req.map_post_body, "scope"); u_map_remove_from_key(req.map_post_body, "username"); u_map_remove_from_key(req.map_post_body, "password"); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_ptr_eq(json_object_get(j_resp, "refresh_token"), NULL); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_ptr_eq(json_object_get(j_resp, "refresh_token"), NULL); ulfius_clean_response(&resp); json_decref(j_resp); ulfius_clean_request(&req); } END_TEST START_TEST(test_oidc_refresh_token_one_use_simulate_attack_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp, * j_resp_orig; const char * refresh_token, * origin_refresh_token; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "client_secret", CLIENT_SECRET); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp_orig = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp_orig, "access_token"), NULL); ck_assert_ptr_ne(origin_refresh_token = json_string_value(json_object_get(j_resp_orig, "refresh_token")), NULL); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); u_map_put(req.map_post_body, "grant_type", "refresh_token"); u_map_put(req.map_post_body, "refresh_token", origin_refresh_token); u_map_remove_from_key(req.map_post_body, "scope"); u_map_remove_from_key(req.map_post_body, "username"); u_map_remove_from_key(req.map_post_body, "password"); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_ptr_ne(refresh_token = json_string_value(json_object_get(j_resp, "refresh_token")), NULL); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); u_map_put(req.map_post_body, "refresh_token", refresh_token); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_ptr_ne(refresh_token = json_string_value(json_object_get(j_resp, "refresh_token")), NULL); ulfius_clean_response(&resp); // Use old and disabled token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); u_map_put(req.map_post_body, "refresh_token", origin_refresh_token); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(400, resp.status); ulfius_clean_response(&resp); // Use newly disabled token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); u_map_put(req.map_post_body, "refresh_token", refresh_token); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(400, resp.status); ulfius_clean_response(&resp); json_decref(j_resp); json_decref(j_resp_orig); ulfius_clean_request(&req); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc refresh token one use"); tc_core = tcase_create("test_oidc_refresh_token_one_use"); tcase_add_test(tc_core, test_oidc_refresh_token_one_use_add_module_always_ok); tcase_add_test(tc_core, test_oidc_refresh_token_one_use_add_client_ok); tcase_add_test(tc_core, test_oidc_refresh_token_one_use_refresh_queue_valid); tcase_add_test(tc_core, test_oidc_refresh_token_one_use_simulate_attack_valid); tcase_add_test(tc_core, test_oidc_refresh_token_one_use_delete_client); tcase_add_test(tc_core, test_oidc_refresh_token_one_use_delete_module); tcase_add_test(tc_core, test_oidc_refresh_token_one_use_add_module_client_driven_ok); tcase_add_test(tc_core, test_oidc_refresh_token_one_use_add_client_driven_one_use_ok); tcase_add_test(tc_core, test_oidc_refresh_token_one_use_refresh_queue_valid); tcase_add_test(tc_core, test_oidc_refresh_token_one_use_simulate_attack_valid); tcase_add_test(tc_core, test_oidc_refresh_token_one_use_delete_client); tcase_add_test(tc_core, test_oidc_refresh_token_one_use_add_client_driven_multiple_use_ok); tcase_add_test(tc_core, test_oidc_refresh_token_one_use_refresh_no_queue_valid); tcase_add_test(tc_core, test_oidc_refresh_token_one_use_delete_module); tcase_add_test(tc_core, test_oidc_refresh_token_one_use_add_module_never_ok); tcase_add_test(tc_core, test_oidc_refresh_token_one_use_refresh_no_queue_valid); tcase_add_test(tc_core, test_oidc_refresh_token_one_use_delete_module); tcase_add_test(tc_core, test_oidc_refresh_token_one_use_delete_client); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define SCOPE_LIST "openid" #define SCOPE_LIST_WITH_AUTH "scope1 openid" #define CLIENT "client4_id" #define CLIENT_PUBLIC "client1_id" #define CLIENT_ERROR "error" #define CLIENT_SECRET "secret" #define REDIRECT_URI "../../test-oidc.html?param=client4" #define REDIRECT_URI_PUBLIC "../../test-oidc.html?param=client1_cb1" #define RESPONSE_TYPE "id_token token" #define NONCE_TEST "nonce5678" #define STATE_TEST "abcxyz" #define PLUGIN_MODULE "oidc" #define PLUGIN_NAME "oidc_pubkey" #define PLUGIN_DISPLAY_NAME "oidc pubkey" #define PLUGIN_ISS "https://glewlwyd.tld" #define PLUGIN_JWT_TYPE "sha" #define PLUGIN_JWT_TYPE_RSA "rsa" #define PLUGIN_JWT_KEY_SIZE "256" #define PLUGIN_KEY "secret" #define PLUGIN_CODE_DURATION 600 #define PLUGIN_REFRESH_TOKEN_DURATION 1209600 #define PLUGIN_ACCESS_TOKEN_DURATION 3600 #define CLIENT_AUTH_TOKEN_MAX_AGE 3600 #define CLIENT_PUBKEY_PARAM "pubkey" #define CLIENT_JWKS_PARAM "jwks" #define CLIENT_JWKS_URI_PARAM "jwks_uri" #define CLIENT_PUBKEY_ID "client_pubkey" #define CLIENT_PUBKEY_NAME "client with pubkey" #define CLIENT_PUBKEY_REDIRECT "https://glewlwyd.local/" #define CLIENT_SCOPE "scope1" #define KID_PUB "pubkey" struct _u_request admin_req; struct _u_request user_req; char * code; const char pubkey_1_jwk[] = "{\"keys\":[{\"kty\":\"RSA\",\"n\":\"AMWhdXoJpkPtPwABHL_yXUwgcYuwNOVbw70YGmMzhFqiRd6r92-onw-BOAvfnIq-rSMgjidllxOE1fXwlgUIyKJmnHUI3RMDABFmGFRM-Dz6VmQxHgiioLM-Q5yzcj85zIqJvNrw0RL0qhvssQBG5Fta_jLXBUXeGEmciWA0lSfrdlS-zbfxsWqPzAvKyT_0B80m1o8K7ksFtyTPu-cHbCVGx4ciGeZUNrtOnevGQPUOE-tIvsxOPcqC3fPjyI3K4TN5GCCZHEyso1qmRFfsHtenq6EvD1_2DebcODnfnym-iNFyC4YsgqipToNxR3WPIgCu-WrSOk71-93ovs0hd1MhBYw03J4Xupjxy_URCFZm9Pp-9H3j_0hUKmhUWmpsQTpAT7FWvTT-MyyYkZ-9Y33-6KR3E-82kdfXIoEMbGJnfq2Z4Yh_lF3pfD-5FUzOzgnOy0UiTxusWOBbaVhNqmm6xmlHwQjBrax9Bqo7WzQpwXgXgooo6TeVz6pxpUl6V63d5o5XZaxYYilUpZ78qXpHQMwNFr6a2gct-dU8zLF6YaJHIaMp6XT9OD8r-w7SOkq1O7J-UGRqGbUVczYzhApY1Q-B2ZnO18P5KQnG97AbU_Sjk5Rnf6HJ-w-E8NOIwgo5jzloV_5Ck6w-DH_sL5FDca89BGuzwpQEH_h3ma43\",\"e\":\"AQAB\",\"kid\":\"" KID_PUB "\"}]}"; const char privkey_1_jwk[] = "{\"kty\":\"RSA\",\"n\":\"AMWhdXoJpkPtPwABHL_yXUwgcYuwNOVbw70YGmMzhFqiRd6r92-onw-BOAvfnIq-rSMgjidllxOE1fXwlgUIyKJmnHUI3RMDABFmGFRM-Dz6VmQxHgiioLM-Q5yzcj85zIqJvNrw0RL0qhvssQBG5Fta_jLXBUXeGEmciWA0lSfrdlS-zbfxsWqPzAvKyT_0B80m1o8K7ksFtyTPu-cHbCVGx4ciGeZUNrtOnevGQPUOE-tIvsxOPcqC3fPjyI3K4TN5GCCZHEyso1qmRFfsHtenq6EvD1_2DebcODnfnym-iNFyC4YsgqipToNxR3WPIgCu-WrSOk71-93ovs0hd1MhBYw03J4Xupjxy_URCFZm9Pp-9H3j_0hUKmhUWmpsQTpAT7FWvTT-MyyYkZ-9Y33-6KR3E-82kdfXIoEMbGJnfq2Z4Yh_lF3pfD-5FUzOzgnOy0UiTxusWOBbaVhNqmm6xmlHwQjBrax9Bqo7WzQpwXgXgooo6TeVz6pxpUl6V63d5o5XZaxYYilUpZ78qXpHQMwNFr6a2gct-dU8zLF6YaJHIaMp6XT9OD8r-w7SOkq1O7J-UGRqGbUVczYzhApY1Q-B2ZnO18P5KQnG97AbU_Sjk5Rnf6HJ-w-E8NOIwgo5jzloV_5Ck6w-DH_sL5FDca89BGuzwpQEH_h3ma43\",\"e\":\"AQAB\",\"d\":\"AIiu6F7k-ZcVKHNKUaX3a8tQzPb9gTf3xWKsnuNpJ-q_PG-Ko_EXwBqrFiYwG0ZiJcCbrXVV76zSPGCCal9E-e5H5YGUBcI2Wv-tiroTGcSipslYpxr1zwrozz47ZZKQ2QQfyvvpfdAMYvI5Oxmj7h-4yQJEcCMoPcf7eY-ODnKziP2HkSPdBwVaOpcVQyb2EcczS0VXHAPLCiVtftmD6qnFUA4H6b3BFLFq6BG-5gIWIHSjtUH8AwRiijs5mOVoIWTGJYe2HTpyU_BH-hCM_6_LCQrLT2jg9jBqsoBkRuJKIroolAvSEPOxVNnXqMKHoc6zNVFJ4IXn3rBVXlDlCm69xoe67-X2M4o8LXpdnwFtvao3YYKqAqv1kH0JZE9kJyY3odhXa-SRZpvOCoE3YpDr5UTlRkEWZATQjqtGP7JEq_RQwtDwM1NpANIl4cFAJVhUJbndjMeJqBcA4-NEV6bBjWkenw179H6UuWNXNzXklPsgtMnF_PwcBFKutwnFqHAE5g6w9iHQ5yG7_2m4zModfBiGiSy3cdQ2f3MEHRRoBmqooEGU_6Urrn6iyAFxk_sINEnT_7Emygle_QwP5N-BQuFpD_NWojGirWwOwiWYBHRBXP0ub17bNx7w4gha6CxHnXyJ0MZBayOIMrnQGeWC7o5a932LCTQfegdBh5xh\",\"p\":\"APQQSKxv01Oky-jENQwxiZcpI4a5PzLPFFCgEqIjSRamCzrCQ07e97iqhU1b8IvRwxDtX358pFKAq7tmwpN2QQb1T9fqUwCpeQuMwRsZwoaM7ZcTSj2FZ_2djN1ixQfzqQ21VxkMRbrdyExqCSJXnHMcLeiFmu81dVopV2iwDbUQv4jZe_ktPUTH4HKle48Y0v9pu22lD5cknAQGB1gUNfyJ0PbUxZMITrZDz4khhYgxqvJ7GluYRNv2tezV-bb5leXbSLDrRgTKqcl5ZjkgLm9FRNGZZAmlsCHEeB3nvCs2ePQYDuLgEkNtuu39kpLFJO6j70bjnvpaIAcDVpPmEE8\",\"q\":\"AM9L09Grg2uSrNUGfj9pfpsMn0k5kqV0n3WjX9z5ZLkwLNNrs0SJjb93haO2MPNlyYhctCpPKnfHJKZWaLhFDV6xr-ubf7c3DbBJjPhlV8dUkgmHfIqWDPl6pzN0xC61zC4IE15LgW_JEMpq53fRWnIHdufs-105QO8YOo0CVYKYjqut4hVbYRBSTaeVLb1vj_yhaL0qV7orQoTrpr6Bg20nftBBa-8Md_B5l0QyiSfvOjKnXsjULQdQGbtypQZvu2jUasnUVUQHBgeF5W5WFj8qCGGnmehqY6QissipLoRMcGPaV_gJKisgcorF7sSU_QzcBUmPk377LkzZXGNUYZk\",\"qi\":\"AILHVNisODhO4GC8P709DqGdVdufLZf2Bl7AwjWyYTkpEzEfQCHHUnmOoTCn-OEvnn9lWiaCaTijtlUmos0fCfvSQLk9elciIOmlRk8G1EtnnzYQsTmerLoMJBgQ02hhip8GK47Y7mbZIjaPB625Dv8F4RHd9ZiTzXTGcNc6bldWlNNbbqw9DWS1DORPhdQEPU424qcYvHq_eklFCujWukO8ul3FEZYnTcth2ODSFMb0a0SCuDGkGI8BDI-_4n6-4wIlAXtc8Vt9Ko8WxJjCK_v2Ae9x05eknWZj0JxuyoAjPtJApp0pt25omJwZr_lY5i8T0cL6dDF5nZcA9hN__eo\",\"dp\":\"ALL4hfI9BmCdxhFoX-YTJWw9dJnEmf1uMN12pHNVILGFDVMHRUg-5LT8BkhWFSzSoxJ0nsQoLm95f3Uqw6BS5RhvJx-T603e-K5phumSmD0GduuD77rxavJlZ_ioBwfvu5Yb1kS95RxEqi6uywft6wHWNiv-XUDwmJ-HFVvlTgfqwileIjT04argT0yC4PpsH73AEPs0QRx6chXZPeVu3K_Vd_Co0kEhpGavjy5l8H-QvGSXtRpZrJUIcxu7RSTSHQOzK7jgrjWxT5Q4e6eEW8ioqPByZRNV9rSsV9DGMAwYI9YLFk90NLBRdPQ0MBmEi7KbcEkxfVDkafv6jLBj0q0\",\"dq\":\"AJldyYY7dczVxMcKucbinwfJq-N6E_QTt5JKYDdV0F5utQtqiEQx3MyGejooJkk9yn_3zlfrIElj7cqe7XU_qWeg4L3Y2wHLWnZNxF1WZT4VZMJmGg9SeqDtTNz2C9tfJ4P695FxHX99681GkKAGJPtuaFuo6kQLgu4iJ9eBnZA0nIGJ8VXJuKNhsRBGf4PDEW1gYeRqemNDdEBxNHmHypusd9dOP7OpruccnnyXQwBnrtAhIjBFQldBvPgBFvUPH0GsvqE6VicxZxWTy635RRZQW8kcPfNFGxkpjsqE2OSKxTArL6BT733e0L-5NzD75cho1ASblA2DerriqcbXfCk\",\"kid\":\"" KID_PUB "\"}"; const char pubkey_1_pem[] = "-----BEGIN PUBLIC KEY-----\n"\ "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxaF1egmmQ+0/AAEcv/Jd\n"\ "TCBxi7A05VvDvRgaYzOEWqJF3qv3b6ifD4E4C9+cir6tIyCOJ2WXE4TV9fCWBQjI\n"\ "omacdQjdEwMAEWYYVEz4PPpWZDEeCKKgsz5DnLNyPznMiom82vDREvSqG+yxAEbk\n"\ "W1r+MtcFRd4YSZyJYDSVJ+t2VL7Nt/Gxao/MC8rJP/QHzSbWjwruSwW3JM+75wds\n"\ "JUbHhyIZ5lQ2u06d68ZA9Q4T60i+zE49yoLd8+PIjcrhM3kYIJkcTKyjWqZEV+we\n"\ "16eroS8PX/YN5tw4Od+fKb6I0XILhiyCqKlOg3FHdY8iAK75atI6TvX73ei+zSF3\n"\ "UyEFjDTcnhe6mPHL9REIVmb0+n70feP/SFQqaFRaamxBOkBPsVa9NP4zLJiRn71j\n"\ "ff7opHcT7zaR19cigQxsYmd+rZnhiH+UXel8P7kVTM7OCc7LRSJPG6xY4FtpWE2q\n"\ "abrGaUfBCMGtrH0GqjtbNCnBeBeCiijpN5XPqnGlSXpXrd3mjldlrFhiKVSlnvyp\n"\ "ekdAzA0WvpraBy351TzMsXphokchoynpdP04Pyv7DtI6SrU7sn5QZGoZtRVzNjOE\n"\ "CljVD4HZmc7Xw/kpCcb3sBtT9KOTlGd/ocn7D4Tw04jCCjmPOWhX/kKTrD4Mf+wv\n"\ "kUNxrz0Ea7PClAQf+HeZrjcCAwEAAQ==\n"\ "-----END PUBLIC KEY-----"; const char privkey_1_pem[] = "-----BEGIN RSA PRIVATE KEY-----\n"\ "MIIJKwIBAAKCAgEAxaF1egmmQ+0/AAEcv/JdTCBxi7A05VvDvRgaYzOEWqJF3qv3\n"\ "b6ifD4E4C9+cir6tIyCOJ2WXE4TV9fCWBQjIomacdQjdEwMAEWYYVEz4PPpWZDEe\n"\ "CKKgsz5DnLNyPznMiom82vDREvSqG+yxAEbkW1r+MtcFRd4YSZyJYDSVJ+t2VL7N\n"\ "t/Gxao/MC8rJP/QHzSbWjwruSwW3JM+75wdsJUbHhyIZ5lQ2u06d68ZA9Q4T60i+\n"\ "zE49yoLd8+PIjcrhM3kYIJkcTKyjWqZEV+we16eroS8PX/YN5tw4Od+fKb6I0XIL\n"\ "hiyCqKlOg3FHdY8iAK75atI6TvX73ei+zSF3UyEFjDTcnhe6mPHL9REIVmb0+n70\n"\ "feP/SFQqaFRaamxBOkBPsVa9NP4zLJiRn71jff7opHcT7zaR19cigQxsYmd+rZnh\n"\ "iH+UXel8P7kVTM7OCc7LRSJPG6xY4FtpWE2qabrGaUfBCMGtrH0GqjtbNCnBeBeC\n"\ "iijpN5XPqnGlSXpXrd3mjldlrFhiKVSlnvypekdAzA0WvpraBy351TzMsXphokch\n"\ "oynpdP04Pyv7DtI6SrU7sn5QZGoZtRVzNjOECljVD4HZmc7Xw/kpCcb3sBtT9KOT\n"\ "lGd/ocn7D4Tw04jCCjmPOWhX/kKTrD4Mf+wvkUNxrz0Ea7PClAQf+HeZrjcCAwEA\n"\ "AQKCAgEAiK7oXuT5lxUoc0pRpfdry1DM9v2BN/fFYqye42kn6r88b4qj8RfAGqsW\n"\ "JjAbRmIlwJutdVXvrNI8YIJqX0T57kflgZQFwjZa/62KuhMZxKKmyVinGvXPCujP\n"\ "PjtlkpDZBB/K++l90Axi8jk7GaPuH7jJAkRwIyg9x/t5j44OcrOI/YeRI90HBVo6\n"\ "lxVDJvYRxzNLRVccA8sKJW1+2YPqqcVQDgfpvcEUsWroEb7mAhYgdKO1QfwDBGKK\n"\ "OzmY5WghZMYlh7YdOnJT8Ef6EIz/r8sJCstPaOD2MGqygGRG4koiuiiUC9IQ87FU\n"\ "2deowoehzrM1UUnghefesFVeUOUKbr3Gh7rv5fYzijwtel2fAW29qjdhgqoCq/WQ\n"\ "fQlkT2QnJjeh2Fdr5JFmm84KgTdikOvlROVGQRZkBNCOq0Y/skSr9FDC0PAzU2kA\n"\ "0iXhwUAlWFQlud2Mx4moFwDj40RXpsGNaR6fDXv0fpS5Y1c3NeSU+yC0ycX8/BwE\n"\ "Uq63CcWocATmDrD2IdDnIbv/abjMyh18GIaJLLdx1DZ/cwQdFGgGaqigQZT/pSuu\n"\ "fqLIAXGT+wg0SdP/sSbKCV79DA/k34FC4WkP81aiMaKtbA7CJZgEdEFc/S5vXts3\n"\ "HvDiCFroLEedfInQxkFrI4gyudAZ5YLujlr3fYsJNB96B0GHnGECggEBAPQQSKxv\n"\ "01Oky+jENQwxiZcpI4a5PzLPFFCgEqIjSRamCzrCQ07e97iqhU1b8IvRwxDtX358\n"\ "pFKAq7tmwpN2QQb1T9fqUwCpeQuMwRsZwoaM7ZcTSj2FZ/2djN1ixQfzqQ21VxkM\n"\ "RbrdyExqCSJXnHMcLeiFmu81dVopV2iwDbUQv4jZe/ktPUTH4HKle48Y0v9pu22l\n"\ "D5cknAQGB1gUNfyJ0PbUxZMITrZDz4khhYgxqvJ7GluYRNv2tezV+bb5leXbSLDr\n"\ "RgTKqcl5ZjkgLm9FRNGZZAmlsCHEeB3nvCs2ePQYDuLgEkNtuu39kpLFJO6j70bj\n"\ "nvpaIAcDVpPmEE8CggEBAM9L09Grg2uSrNUGfj9pfpsMn0k5kqV0n3WjX9z5ZLkw\n"\ "LNNrs0SJjb93haO2MPNlyYhctCpPKnfHJKZWaLhFDV6xr+ubf7c3DbBJjPhlV8dU\n"\ "kgmHfIqWDPl6pzN0xC61zC4IE15LgW/JEMpq53fRWnIHdufs+105QO8YOo0CVYKY\n"\ "jqut4hVbYRBSTaeVLb1vj/yhaL0qV7orQoTrpr6Bg20nftBBa+8Md/B5l0QyiSfv\n"\ "OjKnXsjULQdQGbtypQZvu2jUasnUVUQHBgeF5W5WFj8qCGGnmehqY6QissipLoRM\n"\ "cGPaV/gJKisgcorF7sSU/QzcBUmPk377LkzZXGNUYZkCggEBALL4hfI9BmCdxhFo\n"\ "X+YTJWw9dJnEmf1uMN12pHNVILGFDVMHRUg+5LT8BkhWFSzSoxJ0nsQoLm95f3Uq\n"\ "w6BS5RhvJx+T603e+K5phumSmD0GduuD77rxavJlZ/ioBwfvu5Yb1kS95RxEqi6u\n"\ "ywft6wHWNiv+XUDwmJ+HFVvlTgfqwileIjT04argT0yC4PpsH73AEPs0QRx6chXZ\n"\ "PeVu3K/Vd/Co0kEhpGavjy5l8H+QvGSXtRpZrJUIcxu7RSTSHQOzK7jgrjWxT5Q4\n"\ "e6eEW8ioqPByZRNV9rSsV9DGMAwYI9YLFk90NLBRdPQ0MBmEi7KbcEkxfVDkafv6\n"\ "jLBj0q0CggEBAJldyYY7dczVxMcKucbinwfJq+N6E/QTt5JKYDdV0F5utQtqiEQx\n"\ "3MyGejooJkk9yn/3zlfrIElj7cqe7XU/qWeg4L3Y2wHLWnZNxF1WZT4VZMJmGg9S\n"\ "eqDtTNz2C9tfJ4P695FxHX99681GkKAGJPtuaFuo6kQLgu4iJ9eBnZA0nIGJ8VXJ\n"\ "uKNhsRBGf4PDEW1gYeRqemNDdEBxNHmHypusd9dOP7OpruccnnyXQwBnrtAhIjBF\n"\ "QldBvPgBFvUPH0GsvqE6VicxZxWTy635RRZQW8kcPfNFGxkpjsqE2OSKxTArL6BT\n"\ "733e0L+5NzD75cho1ASblA2DerriqcbXfCkCggEBAILHVNisODhO4GC8P709DqGd\n"\ "VdufLZf2Bl7AwjWyYTkpEzEfQCHHUnmOoTCn+OEvnn9lWiaCaTijtlUmos0fCfvS\n"\ "QLk9elciIOmlRk8G1EtnnzYQsTmerLoMJBgQ02hhip8GK47Y7mbZIjaPB625Dv8F\n"\ "4RHd9ZiTzXTGcNc6bldWlNNbbqw9DWS1DORPhdQEPU424qcYvHq/eklFCujWukO8\n"\ "ul3FEZYnTcth2ODSFMb0a0SCuDGkGI8BDI+/4n6+4wIlAXtc8Vt9Ko8WxJjCK/v2\n"\ "Ae9x05eknWZj0JxuyoAjPtJApp0pt25omJwZr/lY5i8T0cL6dDF5nZcA9hN//eo=\n"\ "-----END RSA PRIVATE KEY-----\n"; const char pubkey_2_jwk[] = "{\"kty\":\"RSA\",\"n\":\"AKLIxChfF0q7zzxrkGFM_Qu8Cx76Cd1AEd76RxXSWvmfzfUk12FYeu1LQpzO0VvpmzuwbhRZk9Vgdo5y51s2k83ht4QvwOOIpr9rlNZrTiILeYhYheTfl2bPaKbDPY1rFFtifLhTytaIXZ18VpBQH-30ycdnyWzu34MYzORiKu7NX-72z2gGjUx_IeIZxkSrloPp9eRSo2JhlAw-UkI-XcjK22cf_A2xptF1__60Ly393lz9q9xxRU21p_R3IpmRenLKoZoeIOEbeTrtiXGZa5K20qPIMjjXpWfpLyiicAGRtPuj7uPjIEh3cBntQdo1JnkNRAHpbCFrywKwh-HP7dyWBXQ3vXzH1HKH-gt2rHjGmvB1tVh4Wh_Hq0pz-ZJRdDRoYUVgdZWEglkP4U_coL5UyUPL_9qHBW7GVIvJUOmy_S3OKYh8jDniv9qM5td8JPThxny8SmBLc7OVeqHr4A-3WidK7Uv4dtYi4Lqirea8Hu0c8t26DMVZTRhbsdEUj-a846BkP75LrkoloMsoJkyIXnjlf8AnbOiA0e7Ns64AcUL7FCtYrKeWIqR1aOADrj8TXMRp8S5LdAwOwDmAsIdo54FVpoyoM0mBtBB4oi1K18jBaeMYckwS0kKVmZ-uaPI8AAas1raHTIq7GbGsCBooc17cNNL0USQ51aROxEFF\",\"e\":\"AQAB\",\"kid\":\"" KID_PUB "\"}"; const char privkey_2_jwk[] = "{\"kty\":\"RSA\",\"n\":\"AKLIxChfF0q7zzxrkGFM_Qu8Cx76Cd1AEd76RxXSWvmfzfUk12FYeu1LQpzO0VvpmzuwbhRZk9Vgdo5y51s2k83ht4QvwOOIpr9rlNZrTiILeYhYheTfl2bPaKbDPY1rFFtifLhTytaIXZ18VpBQH-30ycdnyWzu34MYzORiKu7NX-72z2gGjUx_IeIZxkSrloPp9eRSo2JhlAw-UkI-XcjK22cf_A2xptF1__60Ly393lz9q9xxRU21p_R3IpmRenLKoZoeIOEbeTrtiXGZa5K20qPIMjjXpWfpLyiicAGRtPuj7uPjIEh3cBntQdo1JnkNRAHpbCFrywKwh-HP7dyWBXQ3vXzH1HKH-gt2rHjGmvB1tVh4Wh_Hq0pz-ZJRdDRoYUVgdZWEglkP4U_coL5UyUPL_9qHBW7GVIvJUOmy_S3OKYh8jDniv9qM5td8JPThxny8SmBLc7OVeqHr4A-3WidK7Uv4dtYi4Lqirea8Hu0c8t26DMVZTRhbsdEUj-a846BkP75LrkoloMsoJkyIXnjlf8AnbOiA0e7Ns64AcUL7FCtYrKeWIqR1aOADrj8TXMRp8S5LdAwOwDmAsIdo54FVpoyoM0mBtBB4oi1K18jBaeMYckwS0kKVmZ-uaPI8AAas1raHTIq7GbGsCBooc17cNNL0USQ51aROxEFF\",\"e\":\"AQAB\",\"d\":\"IHgLKEJPUwjC_To3QjEpB_4p-bPF4-uzpNYm96NNohzN5-fBThln64znbH-UItEltXIrgsObSSREgYVJwFfSg25SPHuJ7diD6gp7VYlxvDittRRzIIO4nzkflqO600pYdSHf3qRYARKSGaeDXKWeuMfqt2tsMd4zluKLe8JY7ejpCbERDZ7A8FErYP3jHi9bhlRUR4Z0MFtmPErx_WSTMEnGGXu3usOEkqMGvLcT8giBIes3LHErcaSaK5jXvenWkq74LNV1mXDxbV-T2qTPYCQ3P5Pe8JeS1nccgO5liOqXPtoj_DCBb_Li2UkJqYyQb_TFa9wzRTdK9u5fLBtdRiDzBIPWNNbhCuD2eaAEkTSW3XXUzmPSlrGC-CMUVemVOWRntPhEFgXiZz36-HMiXefX1gtYQPBVAQI1nPDYppjAGj7u-sVvtQqsKB4AQzc4ULq_ttHiJrw_TGbFSQ0QCPJ2HvnFTeBWixKICvYiRXMo7_kdUBKH7_P_jaoomzWbX39phfWOWC62UN29HjQM3GN1bkMjdolrKo5l8uBdQD_GVAMqdkund6Jw8fpuLmuhFAeOtoMVAsW2Id-CGy_jsi19cbgRHrVHe4_vu3yvvCuQ5MP0Rndivg9JvLM-Lj83JsbWfUG2UEhPycgTPFcwuTKstTciXQSXLKLFetrcsME\",\"p\":\"AM_5kF7BoxK7Tvus6_se2ExfT3lKwE7EzbTAwEhpW1CBi13AsK-bBNZXE-83zF0usZz9DUJ5BRyH4LATxMDCkp0Vza0tdu00778VOgs8wzuStbu6uoc1L7g4Y9ImYwkqLQQuA4IN_RtSdzeWGOA_to7UuMlrHSHQrlAf0npev9ceo35pBUoC0sVAE8nF9Ov2_lkgBRkuNGeQfp_IHyhdjr7r3U4tbja_E-BEFzGUUBvIYJ-1kXKx7ipmpyP47FrOUojjwKyn-xPpu4JYLrvQ-XaiA-SiO5AlBhn_iIXEb3FqBdnWQ0xSoss31jYnVVo-7muR_sBm5UmA0PtVDtrBcSs\",\"q\":\"AMhfwi-RxWW1JgP4oHPBTeOrw9TkFpVahRF5PAeZICuoVxIgPZUaNP1Hhuv6Otb4I7SNRdQioxz6jVGogpk11QV6llsV4skanAgRqWT8ds7qdAsXAhkFCmD6mAaqGs4qVLUSGrMq_KhqBcCdt2pvhy3yuIQmv_iWSJ-XJlsXbWW8DSNa2mpefenKB5dt35zKZp5xzn_UVUoC4HK7c2ZaLvs0KuUE8pgaAdAFogk7emgQfXXK9TPPRd9jullPZTx3UQ104mShX6Hz_vfuTUipPBFefzSm3OKtXM5ejkDQdYmooxnVtR20SujjJ_0D0j66v-G9L8dd-_HcNUgsp57Kf08\",\"qi\":\"bNq-mJjepyGY5mXTlzAcBFY5HqlDx-Ssapo1waswzRhnXKB15zqU-LQfotBBp7pKxWCytwgOmV2toZf_R0Sqinohh52D0mCvTYldTB5tUpKpnhEGZuOZ24BTtrqNAw2X5NRKTflttiVJgfrMCSDIR8NNX4awGB8iUNZLyn4e5Zjfa7cLCwn7ngINpU1TaCY9gbHSsYcVcpX7ro6Vr93ox6cRtapBWElzAPXDF0YKMiKzA4fTqVtbamVHz64k7xgWVwdWXDbdqOWOtI0weeeHMy2Y9Ktz7eXh0rK0fG1c35qJ-V61oXdyfapmI7uGH2R5J-Q2LDJw_3AMGb6oCsPfdQ\",\"dp\":\"I8yQlk78lA_b86R7ZlmT3-mUE4vTeHuV8SQwtQY1qrDx3Wx6vW-QsJiCnO3c5rlP53cDnkqYn6Wf_o8YkhmsBRAovEOUMhanohu0RxTpgkqpr6vfycBU-3_xZs4mxAAXiZ2mCu__foF-dfoHRCqTcRiaykj-1cBHERG5OEkw-oWSnQLU3z2HLF7wSQ4jL67vb0X8uq3iZWVQ9o4LFvaryJ9vE7LsQs43TKZL28Ps2ituvm8Rn02TcocDBEUn4iWbvWZ-1vl_VZkpJrGpMbkyB8KxqtxmJlTJLRZ4WJZMnJgkc6_XG78puJNe8yloHsWwYqHZ2SKdGz7qOikVCoC7yw\",\"dq\":\"atXb1L81b8BBT7a93lo_7FdF5_nhLKsB7kokvqxfYce0_R4hl6FMhYsgnitiOgI-D2OPysbZD3dr6BEf6Q6x0OUGy_QEYlOExCyelBCkTDjnvI38-Vgdq42Rh2QlPK2HUrAfek4-PpGhFY1CIUbr3Yzf4t5CVwnSGP1fXwxDsQ2uN56WfEZ7fi7RE2Vq589nHa3ye2e8PeUAxUu7AOSuzhOHl2qm6oBbXQ3T0nZbEqdQLYEUchZe2_fxgPL7OF0p4zHiD-OW-OP-mzT9EfPh6iTnUCxz84yZwhLaaCZ9tPMsW3b9xaO-mSOcy6PA8t9htbvIgNVUoyVVZ3EfwmOXsw\",\"kid\":\"" KID_PUB "\"}"; const char pubkey_2_pem[] = "-----BEGIN PUBLIC KEY-----\n"\ "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAosjEKF8XSrvPPGuQYUz9\n"\ "C7wLHvoJ3UAR3vpHFdJa+Z/N9STXYVh67UtCnM7RW+mbO7BuFFmT1WB2jnLnWzaT\n"\ "zeG3hC/A44imv2uU1mtOIgt5iFiF5N+XZs9opsM9jWsUW2J8uFPK1ohdnXxWkFAf\n"\ "7fTJx2fJbO7fgxjM5GIq7s1f7vbPaAaNTH8h4hnGRKuWg+n15FKjYmGUDD5SQj5d\n"\ "yMrbZx/8DbGm0XX//rQvLf3eXP2r3HFFTbWn9HcimZF6csqhmh4g4Rt5Ou2JcZlr\n"\ "krbSo8gyONelZ+kvKKJwAZG0+6Pu4+MgSHdwGe1B2jUmeQ1EAelsIWvLArCH4c/t\n"\ "3JYFdDe9fMfUcof6C3aseMaa8HW1WHhaH8erSnP5klF0NGhhRWB1lYSCWQ/hT9yg\n"\ "vlTJQ8v/2ocFbsZUi8lQ6bL9Lc4piHyMOeK/2ozm13wk9OHGfLxKYEtzs5V6oevg\n"\ "D7daJ0rtS/h21iLguqKt5rwe7Rzy3boMxVlNGFux0RSP5rzjoGQ/vkuuSiWgyygm\n"\ "TIheeOV/wCds6IDR7s2zrgBxQvsUK1isp5YipHVo4AOuPxNcxGnxLkt0DA7AOYCw\n"\ "h2jngVWmjKgzSYG0EHiiLUrXyMFp4xhyTBLSQpWZn65o8jwABqzWtodMirsZsawI\n"\ "GihzXtw00vRRJDnVpE7EQUUCAwEAAQ==\n"\ "-----END PUBLIC KEY-----\n"; const char privkey_2_pem[] = "-----BEGIN RSA PRIVATE KEY-----\n"\ "MIIJJwIBAAKCAgEAosjEKF8XSrvPPGuQYUz9C7wLHvoJ3UAR3vpHFdJa+Z/N9STX\n"\ "YVh67UtCnM7RW+mbO7BuFFmT1WB2jnLnWzaTzeG3hC/A44imv2uU1mtOIgt5iFiF\n"\ "5N+XZs9opsM9jWsUW2J8uFPK1ohdnXxWkFAf7fTJx2fJbO7fgxjM5GIq7s1f7vbP\n"\ "aAaNTH8h4hnGRKuWg+n15FKjYmGUDD5SQj5dyMrbZx/8DbGm0XX//rQvLf3eXP2r\n"\ "3HFFTbWn9HcimZF6csqhmh4g4Rt5Ou2JcZlrkrbSo8gyONelZ+kvKKJwAZG0+6Pu\n"\ "4+MgSHdwGe1B2jUmeQ1EAelsIWvLArCH4c/t3JYFdDe9fMfUcof6C3aseMaa8HW1\n"\ "WHhaH8erSnP5klF0NGhhRWB1lYSCWQ/hT9ygvlTJQ8v/2ocFbsZUi8lQ6bL9Lc4p\n"\ "iHyMOeK/2ozm13wk9OHGfLxKYEtzs5V6oevgD7daJ0rtS/h21iLguqKt5rwe7Rzy\n"\ "3boMxVlNGFux0RSP5rzjoGQ/vkuuSiWgyygmTIheeOV/wCds6IDR7s2zrgBxQvsU\n"\ "K1isp5YipHVo4AOuPxNcxGnxLkt0DA7AOYCwh2jngVWmjKgzSYG0EHiiLUrXyMFp\n"\ "4xhyTBLSQpWZn65o8jwABqzWtodMirsZsawIGihzXtw00vRRJDnVpE7EQUUCAwEA\n"\ "AQKCAgAgeAsoQk9TCML9OjdCMSkH/in5s8Xj67Ok1ib3o02iHM3n58FOGWfrjOds\n"\ "f5Qi0SW1ciuCw5tJJESBhUnAV9KDblI8e4nt2IPqCntViXG8OK21FHMgg7ifOR+W\n"\ "o7rTSlh1Id/epFgBEpIZp4NcpZ64x+q3a2wx3jOW4ot7wljt6OkJsRENnsDwUStg\n"\ "/eMeL1uGVFRHhnQwW2Y8SvH9ZJMwScYZe7e6w4SSowa8txPyCIEh6zcscStxpJor\n"\ "mNe96daSrvgs1XWZcPFtX5PapM9gJDc/k97wl5LWdxyA7mWI6pc+2iP8MIFv8uLZ\n"\ "SQmpjJBv9MVr3DNFN0r27l8sG11GIPMEg9Y01uEK4PZ5oASRNJbdddTOY9KWsYL4\n"\ "IxRV6ZU5ZGe0+EQWBeJnPfr4cyJd59fWC1hA8FUBAjWc8NimmMAaPu76xW+1Cqwo\n"\ "HgBDNzhQur+20eImvD9MZsVJDRAI8nYe+cVN4FaLEogK9iJFcyjv+R1QEofv8/+N\n"\ "qiibNZtff2mF9Y5YLrZQ3b0eNAzcY3VuQyN2iWsqjmXy4F1AP8ZUAyp2S6d3onDx\n"\ "+m4ua6EUB462gxUCxbYh34IbL+OyLX1xuBEetUd7j++7fK+8K5Dkw/RGd2K+D0m8\n"\ "sz4uPzcmxtZ9QbZQSE/JyBM8VzC5Mqy1NyJdBJcsosV62tywwQKCAQEAz/mQXsGj\n"\ "ErtO+6zr+x7YTF9PeUrATsTNtMDASGlbUIGLXcCwr5sE1lcT7zfMXS6xnP0NQnkF\n"\ "HIfgsBPEwMKSnRXNrS127TTvvxU6CzzDO5K1u7q6hzUvuDhj0iZjCSotBC4Dgg39\n"\ "G1J3N5YY4D+2jtS4yWsdIdCuUB/Sel6/1x6jfmkFSgLSxUATycX06/b+WSAFGS40\n"\ "Z5B+n8gfKF2OvuvdTi1uNr8T4EQXMZRQG8hgn7WRcrHuKmanI/jsWs5SiOPArKf7\n"\ "E+m7glguu9D5dqID5KI7kCUGGf+IhcRvcWoF2dZDTFKiyzfWNidVWj7ua5H+wGbl\n"\ "SYDQ+1UO2sFxKwKCAQEAyF/CL5HFZbUmA/igc8FN46vD1OQWlVqFEXk8B5kgK6hX\n"\ "EiA9lRo0/UeG6/o61vgjtI1F1CKjHPqNUaiCmTXVBXqWWxXiyRqcCBGpZPx2zup0\n"\ "CxcCGQUKYPqYBqoazipUtRIasyr8qGoFwJ23am+HLfK4hCa/+JZIn5cmWxdtZbwN\n"\ "I1raal596coHl23fnMpmnnHOf9RVSgLgcrtzZlou+zQq5QTymBoB0AWiCTt6aBB9\n"\ "dcr1M89F32O6WU9lPHdRDXTiZKFfofP+9+5NSKk8EV5/NKbc4q1czl6OQNB1iaij\n"\ "GdW1HbRK6OMn/QPSPrq/4b0vx1378dw1SCynnsp/TwKCAQAjzJCWTvyUD9vzpHtm\n"\ "WZPf6ZQTi9N4e5XxJDC1BjWqsPHdbHq9b5CwmIKc7dzmuU/ndwOeSpifpZ/+jxiS\n"\ "GawFECi8Q5QyFqeiG7RHFOmCSqmvq9/JwFT7f/FmzibEABeJnaYK7/9+gX51+gdE\n"\ "KpNxGJrKSP7VwEcREbk4STD6hZKdAtTfPYcsXvBJDiMvru9vRfy6reJlZVD2jgsW\n"\ "9qvIn28TsuxCzjdMpkvbw+zaK26+bxGfTZNyhwMERSfiJZu9Zn7W+X9VmSkmsakx\n"\ "uTIHwrGq3GYmVMktFnhYlkycmCRzr9cbvym4k17zKWgexbBiodnZIp0bPuo6KRUK\n"\ "gLvLAoIBAGrV29S/NW/AQU+2vd5aP+xXRef54SyrAe5KJL6sX2HHtP0eIZehTIWL\n"\ "IJ4rYjoCPg9jj8rG2Q93a+gRH+kOsdDlBsv0BGJThMQsnpQQpEw457yN/PlYHauN\n"\ "kYdkJTyth1KwH3pOPj6RoRWNQiFG692M3+LeQlcJ0hj9X18MQ7ENrjeelnxGe34u\n"\ "0RNlaufPZx2t8ntnvD3lAMVLuwDkrs4Th5dqpuqAW10N09J2WxKnUC2BFHIWXtv3\n"\ "8YDy+zhdKeMx4g/jlvjj/ps0/RHz4eok51Asc/OMmcIS2mgmfbTzLFt2/cWjvpkj\n"\ "nMujwPLfYbW7yIDVVKMlVWdxH8Jjl7MCggEAbNq+mJjepyGY5mXTlzAcBFY5HqlD\n"\ "x+Ssapo1waswzRhnXKB15zqU+LQfotBBp7pKxWCytwgOmV2toZf/R0Sqinohh52D\n"\ "0mCvTYldTB5tUpKpnhEGZuOZ24BTtrqNAw2X5NRKTflttiVJgfrMCSDIR8NNX4aw\n"\ "GB8iUNZLyn4e5Zjfa7cLCwn7ngINpU1TaCY9gbHSsYcVcpX7ro6Vr93ox6cRtapB\n"\ "WElzAPXDF0YKMiKzA4fTqVtbamVHz64k7xgWVwdWXDbdqOWOtI0weeeHMy2Y9Ktz\n"\ "7eXh0rK0fG1c35qJ+V61oXdyfapmI7uGH2R5J+Q2LDJw/3AMGb6oCsPfdQ==\n"\ "-----END RSA PRIVATE KEY-----\n"; static int callback_request_uri (const struct _u_request * request, struct _u_response * response, void * user_data) { ulfius_set_string_body_response(response, 200, (const char *)user_data); return U_CALLBACK_COMPLETE; } static int callback_request_ietf_uri (const struct _u_request * request, struct _u_response * response, void * user_data) { ulfius_set_string_body_response(response, 200, (const char *)user_data); u_map_put(response->map_header, ULFIUS_HTTP_HEADER_CONTENT, "application/oauth-authz-req+jwt"); return U_CALLBACK_COMPLETE; } static int callback_request_uri_status_404 (const struct _u_request * request, struct _u_response * response, void * user_data) { response->status = 404; return U_CALLBACK_COMPLETE; } static int callback_request_uri_incomplete_jwt (const struct _u_request * request, struct _u_response * response, void * user_data) { ulfius_set_string_body_response(response, 200, (const char *)(user_data+1)); return U_CALLBACK_COMPLETE; } int callback_jwks_ok (const struct _u_request * request, struct _u_response * response, void * user_data) { json_t * j_jwks = json_loads(pubkey_1_jwk, JSON_DECODE_ANY, NULL); ulfius_set_json_body_response(response, 200, j_jwks); json_decref(j_jwks); return U_CALLBACK_CONTINUE; } START_TEST(test_oidc_request_jwt_redirect_login) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/oidc/auth?request=%s", SERVER_URI, request); ck_assert_int_eq(run_simple_test(NULL, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_ok) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/oidc/auth?g_continue&request=%s", SERVER_URI, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "id_token="), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_client_public_response_ok) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_NONE); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBLIC); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI_PUBLIC); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed_unsecure(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/oidc/auth?g_continue&request=%s", SERVER_URI, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "id_token="), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_unsigned_error) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_NONE); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed_unsecure(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/oidc/auth?g_continue&request=%s", SERVER_URI, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_invalid_signature) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)(CLIENT_SECRET "error"), o_strlen((CLIENT_SECRET "error"))); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/oidc/auth?g_continue&request=%s", SERVER_URI, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_invalid_signature_2) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); request[o_strlen(request) - 2] = '\0'; url = msprintf("%s/oidc/auth?g_continue&request=%s", SERVER_URI, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_unsigned) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_NONE); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed_unsecure(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/oidc/auth?g_continue&request=%s", SERVER_URI, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_error_no_response_type_in_request) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/oidc/auth?g_continue&response_type=" RESPONSE_TYPE "&request=%s", SERVER_URI, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_error_no_client_id_in_request) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/oidc/auth?g_continue&response_type=" RESPONSE_TYPE "&request=%s", SERVER_URI, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_error_client_id_missing) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/oidc/auth?g_continue&request=%s", SERVER_URI, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_error_client_id_invalid) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_ERROR); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/oidc/auth?g_continue&request=%s", SERVER_URI, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_error_request_in_request) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "request", "plop"); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/oidc/auth?g_continue&request=%s", SERVER_URI, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_nonce_supersede) { jwt_t * jwt_request = NULL, * jwt_id_token = NULL; char * request, * id_token = NULL; r_jwt_init(&jwt_request); struct _u_response resp; ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); ulfius_init_response(&resp); o_free(user_req.http_url); o_free(user_req.http_verb); user_req.http_url = msprintf("%s/oidc/auth?g_continue&request=%s&nonce=" NONCE_TEST, SERVER_URI, request); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token=")); ck_assert_ptr_ne(id_token, NULL); if (o_strchr(id_token, '&') != NULL) { *(o_strchr(id_token, '&')) = '\0'; } r_jwt_init(&jwt_id_token); ck_assert_int_eq(r_jwt_parse(jwt_id_token, id_token, 0), RHN_OK); ck_assert_str_eq(r_jwt_get_claim_str_value(jwt_id_token, "nonce"), NONCE_TEST); o_free(request); o_free(id_token); r_jwt_free(jwt_request); r_jwt_free(jwt_id_token); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_request_jwt_response_state_supersede) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/oidc/auth?g_continue&request=%s&state=" STATE_TEST, SERVER_URI, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, STATE_TEST), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_response_type_supersede) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_response resp; ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", "id_token"); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); ulfius_init_response(&resp); o_free(user_req.http_url); o_free(user_req.http_verb); user_req.http_url = msprintf("%s/oidc/auth?g_continue&request=%s", SERVER_URI, request); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); o_free(request); r_jwt_free(jwt_request); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_request_jwt_error_redirect_uri) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", "invalid"); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf("%s/oidc/auth?g_continue&request=%s", SERVER_URI, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error="), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_uri_jwt_response_ok) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); struct _u_instance instance; ulfius_init_instance(&instance, 7597, NULL, NULL); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/", 0, &callback_request_uri, request); ulfius_start_framework(&instance); url = msprintf("%s/oidc/auth?g_continue&request_uri=http://localhost:7597/", SERVER_URI); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "id_token="), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_uri_jwt_no_connection) { ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "/oidc/auth?g_continue&request_uri=http://localhost:7597/", NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_request_uri_jwt_connection_error) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); struct _u_instance instance; ulfius_init_instance(&instance, 7597, NULL, NULL); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/", 0, &callback_request_uri_status_404, request); ulfius_start_framework(&instance); url = msprintf("%s/oidc/auth?g_continue&request_uri=http://localhost:7597/", SERVER_URI); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_uri_jwt_response_incomplete) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); struct _u_instance instance; ulfius_init_instance(&instance, 7597, NULL, NULL); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/", 0, &callback_request_uri_incomplete_jwt, request); ulfius_start_framework(&instance); url = msprintf("%s/oidc/auth?g_continue&request_uri=http://localhost:7597/", SERVER_URI); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_add_module_request_signed) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisosososososososososissssss}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "request-uri-allow-https-non-secure", json_true(), "request-maximum-exp", CLIENT_AUTH_TOKEN_MAX_AGE, "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "client-jwks-parameter", CLIENT_JWKS_PARAM, "client-jwks_uri-parameter", CLIENT_JWKS_URI_PARAM); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_request_jwt_add_client_pubkey) { json_t * j_client = json_pack("{ss ss ss so s[s] s[ssss] s[s] ss so}", "client_id", CLIENT_PUBKEY_ID, "client_secret", CLIENT_SECRET, "name", CLIENT_PUBKEY_NAME, "confidential", json_true(), "redirect_uri", CLIENT_PUBKEY_REDIRECT, "authorization_type", "code", "token", "id_token", "client_credentials", "scope", CLIENT_SCOPE, "pubkey", pubkey_1_pem, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_PUBKEY_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_request_jwt_add_client_pubkey_ietf) { json_t * j_client = json_pack("{ss ss ss so s[s] s[ssss] s[s] ss so ss ss ss}", "client_id", CLIENT_PUBKEY_ID, "client_secret", CLIENT_SECRET, "name", CLIENT_PUBKEY_NAME, "confidential", json_true(), "redirect_uri", CLIENT_PUBKEY_REDIRECT, "authorization_type", "code", "token", "id_token", "client_credentials", "scope", CLIENT_SCOPE, "pubkey", pubkey_1_pem, "enabled", json_true(), "request_object_signing_alg", "RS256", "request_object_encryption_alg", "RSA-OAEP-256", "request_object_encryption_enc", "A256CBC-HS512"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_PUBKEY_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_request_jwt_add_client_jwks) { json_t * j_client = json_pack("{ss ss ss so s[s] s[ssss] s[s] so so}", "client_id", CLIENT_PUBKEY_ID, "secret", CLIENT_SECRET, "name", CLIENT_PUBKEY_NAME, "confidential", json_true(), "redirect_uri", CLIENT_PUBKEY_REDIRECT, "authorization_type", "code", "token", "id_token", "client_credentials", "scope", CLIENT_SCOPE, "jwks", json_loads(pubkey_1_jwk, JSON_DECODE_ANY, NULL), "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_PUBKEY_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_request_jwt_add_client_jwks_uri) { json_t * j_client = json_pack("{ss ss ss so s[s] s[ssss] s[s] ss so}", "client_id", CLIENT_PUBKEY_ID, "secret", CLIENT_SECRET, "name", CLIENT_PUBKEY_NAME, "confidential", json_true(), "redirect_uri", CLIENT_PUBKEY_REDIRECT, "authorization_type", "code", "token", "id_token", "client_credentials", "scope", CLIENT_SCOPE, "jwks_uri", "http://localhost:7462/jwks", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_PUBKEY_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_request_jwt_add_client_multiple) { json_t * j_client = json_pack("{ss ss ss so s[s] s[ssss] s[s] so ss so ss}", "client_id", CLIENT_PUBKEY_ID, "secret", CLIENT_SECRET, "name", CLIENT_PUBKEY_NAME, "confidential", json_true(), "redirect_uri", CLIENT_PUBKEY_REDIRECT, "authorization_type", "code", "token", "id_token", "client_credentials", "scope", CLIENT_SCOPE, "enabled", json_true(), "pubkey", pubkey_2_pem, "jwks", json_pack("{s[o]}", "keys", json_loads(pubkey_2_jwk, JSON_DECODE_ANY, NULL)), "jwks_uri", "http://localhost:7462/jwks"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_PUBKEY_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_request_jwt_delete_client_pubkey) { json_t * j_param = json_pack("{ss}", "scope", ""); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_PUBKEY_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_PUBKEY_ID, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_request_jwt_delete_module_request_signed) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_request_jwt_response_client_pubkey_ok) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s", request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "id_token="), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_client_pubkey_uauthorized) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/oidc/auth?g_continue&request=%s", request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_token_jwt_ok) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; int rnd; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 200, NULL, "access_token", NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_token_jwt_unauthorized) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; int rnd; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/oidc/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_token_jwt_invalid_signature) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; int rnd; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); request[o_strlen(request)-1] = '\0'; ck_assert_ptr_ne(request, NULL); u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_token_jwt_invalid_iss) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; int rnd; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", "error"); r_jwt_set_claim_str_value(jwt_request, "sub", "error"); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); request = r_jwt_serialize_signed(jwt_request, NULL, 0); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); ck_assert_ptr_ne(request, NULL); u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_token_jwt_inconsistent_iss) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; int rnd; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "sub", "error"); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_token_jwt_invalud_aud) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; int rnd; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "aud", "error"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_token_jwt_expired) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; int rnd; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)-(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_token_jwt_max_age_too_long) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; int rnd; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+7200); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_token_jwt_invalid_client_assertion_type) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; int rnd; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "e"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_token_jwt_invalid_kid) { jwt_t * jwt_request = NULL; jwk_t * jwk; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; int rnd; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); r_jwk_init(&jwk); r_jwk_import_from_json_str(jwk, privkey_1_jwk); r_jwk_set_property_str(jwk, "kid", "error"); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); r_jwt_set_header_str_value(jwt_request, "kid", "error"); request = r_jwt_serialize_signed(jwt_request, jwk, 0); ck_assert_ptr_ne(request, NULL); r_jwk_free(jwk); u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_token_jwt_no_jti) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; int rnd; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_token_jwt_jti_duplicate) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; int rnd; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 200, NULL, "access_token", NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_client_pubkey_invalid_signature) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_2_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s", request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_client_pubkey_invalid_kid) { jwt_t * jwt_request = NULL; jwk_t * jwk; char * url, * request; r_jwt_init(&jwt_request); struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); r_jwk_init(&jwk); r_jwk_import_from_json_str(jwk, privkey_1_jwk); r_jwk_set_property_str(jwk, "kid", "error"); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "kid", "error"); request = r_jwt_serialize_signed(jwt_request, jwk, 0); ck_assert_ptr_ne(request, NULL); r_jwk_free(jwk); url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s", request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_client_pubkey_no_kid_ok) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s", request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "id_token="), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_client_pubkey_test_priority) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s", request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "id_token="), 1); o_free(url); o_free(request); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); r_jwks_empty(jwt_request->jwks_privkey_sign); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_2_jwk, NULL), RHN_OK); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s", request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_add_module_request_signed_rsa) { json_t * j_parameters = json_pack("{sssssssos{sssssssssssisisisososososososososososissssss}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE_RSA, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", privkey_2_pem, "cert", pubkey_2_pem, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "request-parameter-allow-encrypted", json_true(), "request-uri-allow-https-non-secure", json_true(), "request-maximum-exp", CLIENT_AUTH_TOKEN_MAX_AGE, "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "client-jwks-parameter", CLIENT_JWKS_PARAM, "client-jwks_uri-parameter", CLIENT_JWKS_URI_PARAM); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_request_jwt_add_module_request_signed_hsa) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisososososososososososissssss}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "request-parameter-allow-encrypted", json_true(), "request-uri-allow-https-non-secure", json_true(), "request-maximum-exp", CLIENT_AUTH_TOKEN_MAX_AGE, "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "client-jwks-parameter", CLIENT_JWKS_PARAM, "client-jwks_uri-parameter", CLIENT_JWKS_URI_PARAM); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_request_jwt_add_module_request_signed_rsa_ietf_strict) { json_t * j_parameters = json_pack("{sssssssos{sssssssssssisisisosososososososososososissssss}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE_RSA, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", privkey_2_pem, "cert", pubkey_2_pem, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "request-parameter-ietf-strict", json_true(), "request-parameter-allow-encrypted", json_true(), "request-uri-allow-https-non-secure", json_true(), "request-maximum-exp", CLIENT_AUTH_TOKEN_MAX_AGE, "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "client-jwks-parameter", CLIENT_JWKS_PARAM, "client-jwks_uri-parameter", CLIENT_JWKS_URI_PARAM); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_request_jwt_nested_rsa_response_ok) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_set_enc_alg(jwt_request, R_JWA_ALG_RSA1_5); r_jwt_set_enc(jwt_request, R_JWA_ENC_A128CBC); ck_assert_int_eq(r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt_request, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_nested(jwt_request, R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, NULL, 0, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s", request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "id_token="), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_nested_rsa_response_invalid_enc_key) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_set_enc_alg(jwt_request, R_JWA_ALG_RSA1_5); r_jwt_set_enc(jwt_request, R_JWA_ENC_A128CBC); ck_assert_int_eq(r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt_request, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_1_pem, o_strlen(pubkey_1_pem)), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_nested(jwt_request, R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, NULL, 0, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s", request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_nested_rsa_response_invalid_token) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_set_enc_alg(jwt_request, R_JWA_ALG_RSA1_5); r_jwt_set_enc(jwt_request, R_JWA_ENC_A128CBC); ck_assert_int_eq(r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt_request, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_nested(jwt_request, R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, NULL, 0, NULL, 0); ck_assert_ptr_ne(request, NULL); request[o_strlen(request)-1] = '\0'; url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s", request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_nested_hsa_response_ok) { jwt_t * jwt_request = NULL; char * url, * request; unsigned char key[32] = {0}; size_t key_len = 32; gnutls_datum_t key_data; jwk_t * jwk; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_set_enc_alg(jwt_request, R_JWA_ALG_A128GCMKW); r_jwt_set_enc(jwt_request, R_JWA_ENC_A128CBC); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); key_data.data = (unsigned char *)PLUGIN_KEY; key_data.size = o_strlen(PLUGIN_KEY); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, key, &key_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(r_jwk_init(&jwk), RHN_OK); ck_assert_int_eq(r_jwk_import_from_symmetric_key(jwk, key, key_len/2), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys(jwt_request, jwk, jwk), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_nested(jwt_request, R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, NULL, 0, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s", request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "id_token="), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); r_jwk_free(jwk); } END_TEST START_TEST(test_oidc_request_jwt_nested_hsa_response_invalid_enc_key) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); unsigned char key[32] = {0}; size_t key_len = 32; gnutls_datum_t key_data; jwk_t * jwk; ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_set_enc_alg(jwt_request, R_JWA_ALG_A128GCMKW); r_jwt_set_enc(jwt_request, R_JWA_ENC_A128CBC); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); key_data.data = (unsigned char *)PLUGIN_KEY; key_data.size = o_strlen(PLUGIN_KEY); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, key, &key_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(r_jwk_init(&jwk), RHN_OK); ck_assert_int_eq(r_jwk_import_from_symmetric_key(jwk, key+1, key_len/2), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys(jwt_request, jwk, jwk), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_nested(jwt_request, R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, NULL, 0, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s", request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); r_jwk_free(jwk); } END_TEST START_TEST(test_oidc_request_jwt_nested_hsa_response_invalid_token) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); unsigned char key[32] = {0}; size_t key_len = 32; gnutls_datum_t key_data; jwk_t * jwk; ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_set_enc_alg(jwt_request, R_JWA_ALG_A128GCMKW); r_jwt_set_enc(jwt_request, R_JWA_ENC_A128CBC); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); key_data.data = (unsigned char *)PLUGIN_KEY; key_data.size = o_strlen(PLUGIN_KEY); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, key, &key_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(r_jwk_init(&jwk), RHN_OK); ck_assert_int_eq(r_jwk_import_from_symmetric_key(jwk, key, key_len/2), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys(jwt_request, jwk, jwk), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); request = r_jwt_serialize_nested(jwt_request, R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, NULL, 0, NULL, 0); ck_assert_ptr_ne(request, NULL); request[o_strlen(request)-1] = '\0'; url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s", request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); r_jwk_free(jwk); } END_TEST START_TEST(test_oidc_request_token_jwt_nested_rsa_ok) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; int rnd; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_set_enc_alg(jwt_request, R_JWA_ALG_RSA1_5), RHN_OK); ck_assert_int_eq(r_jwt_set_enc(jwt_request, R_JWA_ENC_A128CBC), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt_request, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); request = r_jwt_serialize_nested(jwt_request, R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, NULL, 0, NULL, 0); ck_assert_ptr_ne(request, NULL); u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 200, NULL, "access_token", NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_token_jwt_nested_rsa_invalid_enc_key) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; int rnd; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_set_enc_alg(jwt_request, R_JWA_ALG_RSA1_5), RHN_OK); ck_assert_int_eq(r_jwt_set_enc(jwt_request, R_JWA_ENC_A128CBC), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt_request, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_1_pem, o_strlen(pubkey_1_pem)), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); request = r_jwt_serialize_nested(jwt_request, R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, NULL, 0, NULL, 0); ck_assert_ptr_ne(request, NULL); u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_token_jwt_nested_rsa_invalid_token) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; int rnd; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; snprintf(jti, 11, "jti_%06d", rnd); ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_set_enc_alg(jwt_request, R_JWA_ALG_RSA1_5), RHN_OK); ck_assert_int_eq(r_jwt_set_enc(jwt_request, R_JWA_ENC_A128CBC), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt_request, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); request = r_jwt_serialize_nested(jwt_request, R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, NULL, 0, NULL, 0); ck_assert_ptr_ne(request, NULL); request[o_strlen(request)-1] = '\0'; u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_token_jwt_nested_hsa_ok) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; int rnd; unsigned char key[32] = {0}; size_t key_len = 32; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; snprintf(jti, 11, "jti_%06d", rnd); jwk_t * jwk; gnutls_datum_t key_data; ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_set_enc_alg(jwt_request, R_JWA_ALG_A128GCMKW); r_jwt_set_enc(jwt_request, R_JWA_ENC_A128CBC); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); key_data.data = (unsigned char *)PLUGIN_KEY; key_data.size = o_strlen(PLUGIN_KEY); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, key, &key_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(r_jwk_init(&jwk), RHN_OK); ck_assert_int_eq(r_jwk_import_from_symmetric_key(jwk, key, key_len/2), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys(jwt_request, jwk, jwk), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); request = r_jwt_serialize_nested(jwt_request, R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, NULL, 0, NULL, 0); ck_assert_ptr_ne(request, NULL); u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 200, NULL, "access_token", NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); r_jwk_free(jwk); } END_TEST START_TEST(test_oidc_request_token_jwt_nested_hsa_invalid_enc_key) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; int rnd; unsigned char key[32] = {0}; size_t key_len = 32; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; snprintf(jti, 11, "jti_%06d", rnd); jwk_t * jwk; gnutls_datum_t key_data; ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_set_enc_alg(jwt_request, R_JWA_ALG_A128GCMKW); r_jwt_set_enc(jwt_request, R_JWA_ENC_A128CBC); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); key_data.data = (unsigned char *)PLUGIN_KEY; key_data.size = o_strlen(PLUGIN_KEY)-1; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, key, &key_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(r_jwk_init(&jwk), RHN_OK); ck_assert_int_eq(r_jwk_import_from_symmetric_key(jwk, key, key_len/2), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys(jwt_request, jwk, jwk), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); request = r_jwt_serialize_nested(jwt_request, R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, NULL, 0, NULL, 0); ck_assert_ptr_ne(request, NULL); u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); r_jwk_free(jwk); } END_TEST START_TEST(test_oidc_request_token_jwt_nested_hsa_invalid_token) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; int rnd; unsigned char key[32] = {0}; size_t key_len = 32; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; snprintf(jti, 11, "jti_%06d", rnd); jwk_t * jwk; gnutls_datum_t key_data; ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_set_enc_alg(jwt_request, R_JWA_ALG_A128GCMKW); r_jwt_set_enc(jwt_request, R_JWA_ENC_A128CBC); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); key_data.data = (unsigned char *)PLUGIN_KEY; key_data.size = o_strlen(PLUGIN_KEY); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, key, &key_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(r_jwk_init(&jwk), RHN_OK); ck_assert_int_eq(r_jwk_import_from_symmetric_key(jwk, key, key_len/2), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys(jwt_request, jwk, jwk), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); request = r_jwt_serialize_nested(jwt_request, R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, NULL, 0, NULL, 0); ck_assert_ptr_ne(request, NULL); request[o_strlen(request)-1] = '\0'; u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); r_jwk_free(jwk); } END_TEST START_TEST(test_oidc_request_token_jwt_nested_dir_ok) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; int rnd; unsigned char key[64] = {0}; size_t key_len = 64; gnutls_rnd(GNUTLS_RND_NONCE, &rnd, sizeof(int)); char jti[12] = {0}; struct _u_map body; snprintf(jti, 11, "jti_%06d", rnd); jwk_t * jwk; gnutls_datum_t key_data; ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_set_enc_alg(jwt_request, R_JWA_ALG_DIR); r_jwt_set_enc(jwt_request, R_JWA_ENC_A128CBC); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); key_data.data = (unsigned char *)PLUGIN_KEY; key_data.size = o_strlen(PLUGIN_KEY); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA512, &key_data, key, &key_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(r_jwk_init(&jwk), RHN_OK); ck_assert_int_eq(r_jwk_import_from_symmetric_key(jwk, key, key_len/2), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys(jwt_request, jwk, jwk), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "iss", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "sub", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "aud", SERVER_URI "/" PLUGIN_NAME "/token"); r_jwt_set_claim_str_value(jwt_request, "jti", jti); r_jwt_set_claim_int_value(jwt_request, "exp", time(NULL)+(CLIENT_AUTH_TOKEN_MAX_AGE/2)); r_jwt_set_claim_int_value(jwt_request, "iat", time(NULL)); request = r_jwt_serialize_nested(jwt_request, R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, NULL, 0, NULL, 0); ck_assert_ptr_ne(request, NULL); u_map_init(&body); u_map_put(&body, "grant_type", "client_credentials"); u_map_put(&body, "scope", CLIENT_SCOPE); u_map_put(&body, "client_assertion", request); u_map_put(&body, "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/" PLUGIN_NAME "/token", NULL, NULL, NULL, &body, 200, NULL, "access_token", NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); u_map_clean(&body); o_free(request); r_jwt_free(jwt_request); r_jwk_free(jwk); } END_TEST START_TEST(test_oidc_request_jwt_client_public_ietf_response_ok) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_NONE); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBLIC); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI_PUBLIC); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "typ", "oauth-authz-req+jwt"); request = r_jwt_serialize_signed_unsecure(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/"PLUGIN_NAME"/auth?g_continue&request=%s&client_id=" CLIENT_PUBLIC, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "id_token="), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_client_public_ietf_response_invalid_client_id) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_NONE); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBLIC); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI_PUBLIC); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "typ", "oauth-authz-req+jwt"); request = r_jwt_serialize_signed_unsecure(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/"PLUGIN_NAME"/auth?g_continue&request=%s&client_id=error", request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_client_public_ietf_response_invalid_client_id_2) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_NONE); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", "error"); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI_PUBLIC); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "typ", "oauth-authz-req+jwt"); request = r_jwt_serialize_signed_unsecure(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/"PLUGIN_NAME"/auth?g_continue&request=%s&client_id="CLIENT_PUBLIC, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_client_public_ietf_response_invalid_typ) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_NONE); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBLIC); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI_PUBLIC); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "typ", "error"); request = r_jwt_serialize_signed_unsecure(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/"PLUGIN_NAME"/auth?g_continue&request=%s&client_id=" CLIENT_PUBLIC, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_client_public_ietf_nested_error) { jws_t * jws; jwe_t * jwe; char * url, * request, * jws_token; json_t * j_claims = json_pack("{ss ss ss ss ss ss ss}", "aud", REDIRECT_URI, "response_type", RESPONSE_TYPE, "client_id", CLIENT_PUBLIC, "redirect_uri", REDIRECT_URI, "scope", SCOPE_LIST, "state", "xyzabcd", "nonce", "nonce1234"); char * payload = json_dumps(j_claims, JSON_COMPACT); ck_assert_ptr_ne(payload, NULL); ck_assert_int_eq(r_jws_init(&jws), RHN_OK); ck_assert_int_eq(r_jws_set_properties(jws, RHN_OPT_SIG_ALG, R_JWA_ALG_NONE, RHN_OPT_HEADER_STR_VALUE, "typ", "JWT", RHN_OPT_PAYLOAD, payload, o_strlen(payload), RHN_OPT_NONE), RHN_OK); ck_assert_ptr_ne(NULL, jws_token = r_jws_serialize_unsecure(jws, NULL, 0)); r_jws_free(jws); ck_assert_int_eq(r_jwe_init(&jwe), RHN_OK); ck_assert_int_eq(r_jwe_set_properties(jwe, RHN_OPT_ENC_ALG, R_JWA_ALG_RSA1_5, RHN_OPT_ENC, R_JWA_ENC_A128CBC, RHN_OPT_HEADER_STR_VALUE, "typ", "JWT", RHN_OPT_HEADER_STR_VALUE, "cty", "JWT", RHN_OPT_PAYLOAD, jws_token, o_strlen(jws_token), RHN_OPT_ENCRYPT_KEY_PEM_DER, R_FORMAT_PEM, pubkey_2_pem, o_strlen(pubkey_2_pem), RHN_OPT_NONE), RHN_OK); ck_assert_ptr_ne(NULL, request = r_jwe_serialize(jwe, NULL, 0)); r_jwe_free(jwe); url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s", request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); o_free(jws_token); json_decref(j_claims); o_free(payload); } END_TEST START_TEST(test_oidc_request_jwt_response_client_pubkey_ietf_ok) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "typ", "oauth-authz-req+jwt"); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s&client_id="CLIENT_PUBKEY_ID, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "id_token="), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_client_pubkey_ietf_nested_ok) { jwt_t * jwt_request = NULL; jwk_t * jwk_sign, * jwk_enc; char * url, * request; r_jwt_init(&jwt_request); struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_ptr_ne(jwk_sign = r_jwk_quick_import(R_IMPORT_JSON_STR, privkey_1_jwk), NULL); ck_assert_ptr_ne(jwk_enc = r_jwk_quick_import(R_IMPORT_JSON_STR, pubkey_2_jwk), NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_set_enc_alg(jwt_request, R_JWA_ALG_RSA_OAEP_256), RHN_OK); ck_assert_int_eq(r_jwt_set_enc(jwt_request, R_JWA_ENC_A256CBC), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "typ", "oauth-authz-req+jwt"); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_nested(jwt_request, R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, jwk_sign, 0, jwk_enc, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s&client_id="CLIENT_PUBKEY_ID, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "id_token="), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(url); o_free(request); r_jwk_free(jwk_sign); r_jwk_free(jwk_enc); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_client_pubkey_ietf_nested_invalid_alg_enc) { jwt_t * jwt_request = NULL; jwk_t * jwk_sign, * jwk_enc; char * url, * request; r_jwt_init(&jwt_request); struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_ptr_ne(jwk_sign = r_jwk_quick_import(R_IMPORT_JSON_STR, privkey_1_jwk), NULL); ck_assert_ptr_ne(jwk_enc = r_jwk_quick_import(R_IMPORT_JSON_STR, pubkey_2_jwk), NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_set_enc_alg(jwt_request, R_JWA_ALG_RSA_OAEP), RHN_OK); ck_assert_int_eq(r_jwt_set_enc(jwt_request, R_JWA_ENC_A128GCM), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "typ", "oauth-authz-req+jwt"); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_nested(jwt_request, R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, jwk_sign, 0, jwk_enc, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s&client_id="CLIENT_PUBKEY_ID, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(url); o_free(request); r_jwk_free(jwk_sign); r_jwk_free(jwk_enc); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_client_pubkey_ietf_invalid_client_id) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", "error"); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "typ", "oauth-authz-req+jwt"); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s&client_id="CLIENT_PUBKEY_ID, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_client_pubkey_ietf_invalid_signature) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "typ", "oauth-authz-req+jwt"); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); request[o_strlen(request) - 2] = '\0'; url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s&client_id="CLIENT_PUBKEY_ID, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_client_pubkey_ietf_unsigned) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_NONE), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "typ", "oauth-authz-req+jwt"); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed_unsecure(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s&client_id="CLIENT_PUBKEY_ID, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_client_pubkey_ietf_invalid_client_id_2) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "typ", "oauth-authz-req+jwt"); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s&client_id=error", request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_client_pubkey_ietf_invalid_typ) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "typ", "error"); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s&client_id="CLIENT_PUBKEY_ID, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_jwt_response_client_pubkey_ietf_invalid_alg) { jwt_t * jwt_request = NULL; char * url, * request; r_jwt_init(&jwt_request); struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, 7462, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", "/jwks", NULL, 0, &callback_jwks_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS512), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "typ", "oauth-authz-req+jwt"); r_jwt_set_header_str_value(jwt_request, "kid", KID_PUB); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI "/" PLUGIN_NAME "/auth?g_continue&request=%s&client_id="CLIENT_PUBKEY_ID, request); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(url); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_uri_ietf_jwt_response_ok) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; ulfius_init_instance(&instance, 7597, NULL, NULL); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "typ", "oauth-authz-req+jwt"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/", 0, &callback_request_ietf_uri, request); ulfius_start_framework(&instance); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI"/"PLUGIN_NAME"/auth?g_continue&request_uri=http://localhost:7597/&client_id="CLIENT, NULL, NULL, NULL, NULL, 302, NULL, NULL, "id_token="), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_uri_ietf_jwt_response_invalid_client_id) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; ulfius_init_instance(&instance, 7597, NULL, NULL); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", "error"); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "typ", "oauth-authz-req+jwt"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/", 0, &callback_request_ietf_uri, request); ulfius_start_framework(&instance); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI"/"PLUGIN_NAME"/auth?g_continue&request_uri=http://localhost:7597/&client_id="CLIENT, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_uri_ietf_jwt_response_invalid_client_id_2) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; ulfius_init_instance(&instance, 7597, NULL, NULL); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "typ", "oauth-authz-req+jwt"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/", 0, &callback_request_ietf_uri, request); ulfius_start_framework(&instance); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI"/"PLUGIN_NAME"/auth?g_continue&request_uri=http://localhost:7597/&client_id=error", NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(request); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_request_uri_ietf_jwt_response_invalid_typ) { jwt_t * jwt_request = NULL; char * request; r_jwt_init(&jwt_request); struct _u_instance instance; ulfius_init_instance(&instance, 7597, NULL, NULL); ck_assert_ptr_ne(jwt_request, NULL); r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_HS256); r_jwt_add_sign_key_symmetric(jwt_request, (unsigned char *)CLIENT_SECRET, o_strlen(CLIENT_SECRET)); r_jwt_set_claim_str_value(jwt_request, "aud", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", REDIRECT_URI); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); r_jwt_set_header_str_value(jwt_request, "typ", "oauth-authz-req+jwt"); request = r_jwt_serialize_signed(jwt_request, NULL, 0); ck_assert_ptr_ne(request, NULL); ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/", 0, &callback_request_uri, request); ulfius_start_framework(&instance); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI"/"PLUGIN_NAME"/auth?g_continue&request_uri=http://localhost:7597/&client_id="CLIENT, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); o_free(request); r_jwt_free(jwt_request); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc request_jwt"); tc_core = tcase_create("test_oidc_request_jwt"); tcase_add_test(tc_core, test_oidc_request_token_jwt_unauthorized); tcase_add_test(tc_core, test_oidc_request_jwt_response_client_pubkey_uauthorized); tcase_add_test(tc_core, test_oidc_request_jwt_redirect_login); tcase_add_test(tc_core, test_oidc_request_jwt_response_ok); tcase_add_test(tc_core, test_oidc_request_jwt_client_public_response_ok); tcase_add_test(tc_core, test_oidc_request_jwt_unsigned_error); tcase_add_test(tc_core, test_oidc_request_jwt_response_invalid_signature); tcase_add_test(tc_core, test_oidc_request_jwt_response_invalid_signature_2); tcase_add_test(tc_core, test_oidc_request_jwt_response_unsigned); tcase_add_test(tc_core, test_oidc_request_jwt_response_error_no_response_type_in_request); tcase_add_test(tc_core, test_oidc_request_jwt_response_error_no_client_id_in_request); tcase_add_test(tc_core, test_oidc_request_jwt_response_error_client_id_missing); tcase_add_test(tc_core, test_oidc_request_jwt_response_error_client_id_invalid); tcase_add_test(tc_core, test_oidc_request_jwt_response_error_request_in_request); tcase_add_test(tc_core, test_oidc_request_jwt_response_nonce_supersede); tcase_add_test(tc_core, test_oidc_request_jwt_response_state_supersede); tcase_add_test(tc_core, test_oidc_request_jwt_response_response_type_supersede); tcase_add_test(tc_core, test_oidc_request_jwt_error_redirect_uri); tcase_add_test(tc_core, test_oidc_request_uri_jwt_response_ok); tcase_add_test(tc_core, test_oidc_request_uri_jwt_no_connection); tcase_add_test(tc_core, test_oidc_request_uri_jwt_connection_error); tcase_add_test(tc_core, test_oidc_request_uri_jwt_response_incomplete); tcase_add_test(tc_core, test_oidc_request_jwt_add_module_request_signed); tcase_add_test(tc_core, test_oidc_request_jwt_add_client_pubkey); tcase_add_test(tc_core, test_oidc_request_token_jwt_ok); tcase_add_test(tc_core, test_oidc_request_token_jwt_invalid_signature); tcase_add_test(tc_core, test_oidc_request_token_jwt_invalid_iss); tcase_add_test(tc_core, test_oidc_request_token_jwt_inconsistent_iss); tcase_add_test(tc_core, test_oidc_request_token_jwt_invalud_aud); tcase_add_test(tc_core, test_oidc_request_token_jwt_expired); tcase_add_test(tc_core, test_oidc_request_token_jwt_max_age_too_long); tcase_add_test(tc_core, test_oidc_request_token_jwt_invalid_client_assertion_type); tcase_add_test(tc_core, test_oidc_request_token_jwt_no_jti); tcase_add_test(tc_core, test_oidc_request_token_jwt_jti_duplicate); tcase_add_test(tc_core, test_oidc_request_jwt_response_client_pubkey_ok); tcase_add_test(tc_core, test_oidc_request_jwt_response_client_pubkey_invalid_signature); tcase_add_test(tc_core, test_oidc_request_jwt_response_client_pubkey_no_kid_ok); tcase_add_test(tc_core, test_oidc_request_jwt_delete_client_pubkey); tcase_add_test(tc_core, test_oidc_request_jwt_add_client_jwks); tcase_add_test(tc_core, test_oidc_request_token_jwt_ok); tcase_add_test(tc_core, test_oidc_request_token_jwt_invalid_signature); tcase_add_test(tc_core, test_oidc_request_token_jwt_invalid_iss); tcase_add_test(tc_core, test_oidc_request_token_jwt_inconsistent_iss); tcase_add_test(tc_core, test_oidc_request_token_jwt_invalud_aud); tcase_add_test(tc_core, test_oidc_request_token_jwt_expired); tcase_add_test(tc_core, test_oidc_request_token_jwt_max_age_too_long); tcase_add_test(tc_core, test_oidc_request_token_jwt_invalid_client_assertion_type); tcase_add_test(tc_core, test_oidc_request_token_jwt_invalid_kid); tcase_add_test(tc_core, test_oidc_request_token_jwt_no_jti); tcase_add_test(tc_core, test_oidc_request_token_jwt_jti_duplicate); tcase_add_test(tc_core, test_oidc_request_jwt_response_client_pubkey_ok); tcase_add_test(tc_core, test_oidc_request_jwt_response_client_pubkey_invalid_signature); tcase_add_test(tc_core, test_oidc_request_jwt_response_client_pubkey_invalid_kid); tcase_add_test(tc_core, test_oidc_request_jwt_delete_client_pubkey); tcase_add_test(tc_core, test_oidc_request_jwt_add_client_jwks_uri); tcase_add_test(tc_core, test_oidc_request_token_jwt_ok); tcase_add_test(tc_core, test_oidc_request_token_jwt_invalid_signature); tcase_add_test(tc_core, test_oidc_request_token_jwt_invalid_iss); tcase_add_test(tc_core, test_oidc_request_token_jwt_inconsistent_iss); tcase_add_test(tc_core, test_oidc_request_token_jwt_invalud_aud); tcase_add_test(tc_core, test_oidc_request_token_jwt_expired); tcase_add_test(tc_core, test_oidc_request_token_jwt_max_age_too_long); tcase_add_test(tc_core, test_oidc_request_token_jwt_invalid_client_assertion_type); tcase_add_test(tc_core, test_oidc_request_token_jwt_invalid_kid); tcase_add_test(tc_core, test_oidc_request_token_jwt_no_jti); tcase_add_test(tc_core, test_oidc_request_token_jwt_jti_duplicate); tcase_add_test(tc_core, test_oidc_request_jwt_response_client_pubkey_ok); tcase_add_test(tc_core, test_oidc_request_jwt_response_client_pubkey_invalid_signature); tcase_add_test(tc_core, test_oidc_request_jwt_response_client_pubkey_invalid_kid); tcase_add_test(tc_core, test_oidc_request_jwt_delete_client_pubkey); tcase_add_test(tc_core, test_oidc_request_jwt_add_client_multiple); tcase_add_test(tc_core, test_oidc_request_jwt_response_client_pubkey_test_priority); tcase_add_test(tc_core, test_oidc_request_jwt_delete_client_pubkey); tcase_add_test(tc_core, test_oidc_request_jwt_delete_module_request_signed); tcase_add_test(tc_core, test_oidc_request_jwt_add_module_request_signed_rsa); tcase_add_test(tc_core, test_oidc_request_jwt_add_client_pubkey); tcase_add_test(tc_core, test_oidc_request_jwt_nested_rsa_response_ok); tcase_add_test(tc_core, test_oidc_request_jwt_nested_rsa_response_invalid_enc_key); tcase_add_test(tc_core, test_oidc_request_jwt_nested_rsa_response_invalid_token); tcase_add_test(tc_core, test_oidc_request_token_jwt_nested_rsa_ok); tcase_add_test(tc_core, test_oidc_request_token_jwt_nested_rsa_invalid_enc_key); tcase_add_test(tc_core, test_oidc_request_token_jwt_nested_rsa_invalid_token); tcase_add_test(tc_core, test_oidc_request_jwt_delete_client_pubkey); tcase_add_test(tc_core, test_oidc_request_jwt_delete_module_request_signed); tcase_add_test(tc_core, test_oidc_request_jwt_add_module_request_signed_hsa); tcase_add_test(tc_core, test_oidc_request_jwt_add_client_pubkey); tcase_add_test(tc_core, test_oidc_request_jwt_nested_hsa_response_ok); tcase_add_test(tc_core, test_oidc_request_jwt_nested_hsa_response_invalid_enc_key); tcase_add_test(tc_core, test_oidc_request_jwt_nested_hsa_response_invalid_token); tcase_add_test(tc_core, test_oidc_request_token_jwt_nested_hsa_ok); tcase_add_test(tc_core, test_oidc_request_token_jwt_nested_hsa_invalid_enc_key); tcase_add_test(tc_core, test_oidc_request_token_jwt_nested_hsa_invalid_token); tcase_add_test(tc_core, test_oidc_request_token_jwt_nested_dir_ok); tcase_add_test(tc_core, test_oidc_request_jwt_delete_client_pubkey); tcase_add_test(tc_core, test_oidc_request_jwt_delete_module_request_signed); tcase_add_test(tc_core, test_oidc_request_jwt_add_module_request_signed_rsa_ietf_strict); tcase_add_test(tc_core, test_oidc_request_jwt_add_client_pubkey_ietf); tcase_add_test(tc_core, test_oidc_request_jwt_client_public_ietf_response_ok); tcase_add_test(tc_core, test_oidc_request_jwt_client_public_ietf_response_invalid_client_id); tcase_add_test(tc_core, test_oidc_request_jwt_client_public_ietf_response_invalid_client_id_2); tcase_add_test(tc_core, test_oidc_request_jwt_client_public_ietf_response_invalid_typ); tcase_add_test(tc_core, test_oidc_request_jwt_client_public_ietf_nested_error); tcase_add_test(tc_core, test_oidc_request_jwt_response_client_pubkey_ietf_ok); tcase_add_test(tc_core, test_oidc_request_jwt_response_client_pubkey_ietf_nested_ok); tcase_add_test(tc_core, test_oidc_request_jwt_response_client_pubkey_ietf_nested_invalid_alg_enc); tcase_add_test(tc_core, test_oidc_request_jwt_response_client_pubkey_ietf_invalid_client_id); tcase_add_test(tc_core, test_oidc_request_jwt_response_client_pubkey_ietf_invalid_signature); tcase_add_test(tc_core, test_oidc_request_jwt_response_client_pubkey_ietf_unsigned); tcase_add_test(tc_core, test_oidc_request_jwt_response_client_pubkey_ietf_invalid_client_id_2); tcase_add_test(tc_core, test_oidc_request_jwt_response_client_pubkey_ietf_invalid_typ); tcase_add_test(tc_core, test_oidc_request_jwt_response_client_pubkey_ietf_invalid_alg); tcase_add_test(tc_core, test_oidc_request_uri_ietf_jwt_response_ok); tcase_add_test(tc_core, test_oidc_request_uri_ietf_jwt_response_invalid_client_id); tcase_add_test(tc_core, test_oidc_request_uri_ietf_jwt_response_invalid_client_id_2); tcase_add_test(tc_core, test_oidc_request_uri_ietf_jwt_response_invalid_typ); tcase_add_test(tc_core, test_oidc_request_jwt_delete_client_pubkey); tcase_add_test(tc_core, test_oidc_request_jwt_delete_module_request_signed); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, register_req; struct _u_response auth_resp, scope_resp; json_t * j_body, * j_register; int res, do_test = 0, i; char * url; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "user1" #define USER_PASSWORD "password" #define PLUGIN_NAME "oidc_resource" #define SCOPE_LIST "g_profile openid" #define SCOPE_1 "g_profile" #define SCOPE_2 "openid" #define RESPONSE_TYPE_CODE "code" #define RESPONSE_TYPE_TOKEN_ID_TOKEN "token%20id_token" #define CLIENT_ID "client_resource" #define CLIENT_NAME "client for resource" #define CLIENT_SECRET "very-secret" #define CLIENT_RESOURCE_PROPERTY "resource" #define CLIENT_REDIRECT_URI "https://client.tld/" #define CLIENT_REDIRECT_URI_ENC "https%3A%2F%2Fclient.tld%2F" #define RESOURCE1_ENC "https%3A%2F%2Fresource1.tld%2F" #define RESOURCE1 "https://resource1.tld/" #define RESOURCE2_ENC "https%3A%2F%2Fresource2.tld%2F" #define RESOURCE2 "https://resource2.tld/" #define RESOURCE3_ENC "https%3A%2F%2Fresource3.tld%2F" #define RESOURCE3 "https://resource3.tld/" #define RESOURCE_ERR_ENC "https%3A%2F%error.tld%2F" #define RESOURCE_ERR "https://error.tld/" #define RESOURCE_HASH_ENC "https%3A%2F%error.tld%2F%23my_hash" #define RESOURCE_HASH "https://error.tld/#my_hash" struct _u_request admin_req; struct _u_request user_req; START_TEST(test_oidc_resource_add_plugin_scope_or_client_change_not_allowed) { json_t * j_param = json_pack("{sssssss{sssssssssisisisosososososososososss{s[s]s[s]}sosososo}}", "module", "oidc", "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", "https://glewlwyd.tld", "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_NAME, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-device-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "resource-allowed", json_true(), "resource-client-property", CLIENT_RESOURCE_PROPERTY, "resource-scope", SCOPE_1, RESOURCE1, SCOPE_2, RESOURCE2, "resource-scope-and-client-property", json_false(), "resource-change-allowed", json_false(), "introspection-revocation-allowed", json_true(), "introspection-revocation-allow-target-client", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_resource_add_plugin_scope_and_client_change_not_allowed) { json_t * j_param = json_pack("{sssssss{sssssssssisisisosososososososososss{s[s]s[s]}soso}}", "module", "oidc", "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", "https://glewlwyd.tld", "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_NAME, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-device-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "resource-allowed", json_true(), "resource-client-property", CLIENT_RESOURCE_PROPERTY, "resource-scope", SCOPE_1, RESOURCE1, SCOPE_2, RESOURCE3, "resource-scope-and-client-property", json_true(), "resource-change-allowed", json_false()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_resource_add_plugin_scope_or_client_change_allowed) { json_t * j_param = json_pack("{sssssss{sssssssssisisisosososososososososss{s[s]s[s]}soso}}", "module", "oidc", "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", "https://glewlwyd.tld", "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_NAME, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-device-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "resource-allowed", json_true(), "resource-client-property", CLIENT_RESOURCE_PROPERTY, "resource-scope", SCOPE_1, RESOURCE1, SCOPE_2, RESOURCE2, "resource-scope-and-client-property", json_false(), "resource-change-allowed", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_resource_add_plugin_scope_or_client_change_not_allowed_with_hash) { json_t * j_param = json_pack("{sssssss{sssssssssisisisosososososososososss{s[s]s[s]}soso}}", "module", "oidc", "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", "https://glewlwyd.tld", "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_NAME, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-device-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "resource-allowed", json_true(), "resource-client-property", CLIENT_RESOURCE_PROPERTY, "resource-scope", SCOPE_1, RESOURCE1, SCOPE_2, RESOURCE_HASH, "resource-scope-and-client-property", json_false(), "resource-change-allowed", json_false()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_resource_add_client_confidential_ok) { json_t * j_parameters = json_pack("{sssssssos[ssssss]sos[s]s[s]}", "client_id", CLIENT_ID, "client_name", CLIENT_NAME, "client_secret", CLIENT_SECRET, "confidential", json_true(), "authorization_type", "device_authorization", "code", "id_token", "token", "refresh_token", "client_credentials", "enabled", json_true(), CLIENT_RESOURCE_PROPERTY, RESOURCE3, "redirect_uri", CLIENT_REDIRECT_URI); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{ss}", "scope", SCOPE_LIST); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_resource_device_verification_valid_with_resource1) { struct _u_request req; struct _u_response resp; json_t * j_resp; const char * redirect_uri, * code, * device_code; jwt_t * jwt; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "resource", RESOURCE1); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "device_code", device_code); u_map_put(req.map_post_body, "resource", RESOURCE1); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "refresh_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "id_token"), NULL); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_str_eq(RESOURCE1, r_jwt_get_claim_str_value(jwt, "aud")); r_jwt_free(jwt); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_device_verification_invalid_with_resource2) { struct _u_request req; struct _u_response resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "resource", RESOURCE2); u_map_put(req.map_post_body, "scope", SCOPE_1); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(400, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_device_verification_invalid_with_resource_error) { struct _u_request req; struct _u_response resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "resource", RESOURCE_ERR); u_map_put(req.map_post_body, "scope", SCOPE_1); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(400, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_device_verification_valid_without_resource) { struct _u_request req; struct _u_response resp; json_t * j_resp; const char * redirect_uri, * code, * device_code; jwt_t * jwt; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "device_code", device_code); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "refresh_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "id_token"), NULL); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_str_eq(SCOPE_LIST, r_jwt_get_claim_str_value(jwt, "aud")); r_jwt_free(jwt); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_device_verification_invalid_with_resource_change) { struct _u_request req; struct _u_response resp; json_t * j_resp; const char * redirect_uri, * code, * device_code; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "resource", RESOURCE1); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "device_code", device_code); u_map_put(req.map_post_body, "resource", RESOURCE2); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(400, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_device_verification_valid_without_resource1_on_confirmation) { struct _u_request req; struct _u_response resp; json_t * j_resp; const char * redirect_uri, * code, * device_code; jwt_t * jwt; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "resource", RESOURCE1); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "device_code", device_code); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "refresh_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "id_token"), NULL); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_str_eq(RESOURCE1, r_jwt_get_claim_str_value(jwt, "aud")); r_jwt_free(jwt); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_device_verification_invalid_with_resource1_on_confirmation_only) { struct _u_request req; struct _u_response resp; json_t * j_resp; const char * redirect_uri, * code, * device_code; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "device_code", device_code); u_map_put(req.map_post_body, "resource", RESOURCE1); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(400, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_device_verification_valid_with_resource3) { struct _u_request req; struct _u_response resp; json_t * j_resp; const char * redirect_uri, * code, * device_code; jwt_t * jwt; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "resource", RESOURCE3); u_map_put(req.map_post_body, "scope", SCOPE_LIST); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "device_code", device_code); u_map_put(req.map_post_body, "resource", RESOURCE3); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "refresh_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "id_token"), NULL); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_str_eq(RESOURCE3, r_jwt_get_claim_str_value(jwt, "aud")); r_jwt_free(jwt); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_device_verification_invalid_with_resource1) { struct _u_request req; struct _u_response resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "resource", RESOURCE1); u_map_put(req.map_post_body, "scope", SCOPE_1); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(400, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_device_verification_invalid_with_hash) { struct _u_request req; struct _u_response resp; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/device_authorization/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "device_authorization"); u_map_put(req.map_post_body, "client_id", CLIENT_ID); u_map_put(req.map_post_body, "resource", RESOURCE_HASH); u_map_put(req.map_post_body, "scope", SCOPE_1); req.auth_basic_user = o_strdup(CLIENT_ID); req.auth_basic_password = o_strdup(CLIENT_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(400, resp.status); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_implicit_token_id_token_valid_with_resource1) { struct _u_response resp; char * access_token; jwt_t * jwt; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s&resource=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_TOKEN_ID_TOKEN, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_LIST, RESOURCE1_ENC); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); access_token = o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token="); if (o_strchr(access_token, '&') != NULL) { *o_strchr(access_token, '&') = '\0'; } ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, access_token, 0), RHN_OK); ck_assert_str_eq(RESOURCE1, r_jwt_get_claim_str_value(jwt, "aud")); r_jwt_free(jwt); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_implicit_token_id_token_invalid_with_resource1) { struct _u_response resp; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s&resource=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_TOKEN_ID_TOKEN, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_2, RESOURCE1_ENC); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "error=invalid_target"), NULL); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_implicit_token_id_token_invalid_with_resource_error) { struct _u_response resp; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s&resource=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_TOKEN_ID_TOKEN, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_2, RESOURCE_ERR_ENC); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "error=invalid_target"), NULL); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_implicit_token_id_token_valid_without_resource) { struct _u_response resp; char * access_token; jwt_t * jwt; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_TOKEN_ID_TOKEN, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); access_token = o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token="); if (o_strchr(access_token, '&') != NULL) { *o_strchr(access_token, '&') = '\0'; } ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, access_token, 0), RHN_OK); ck_assert_str_eq(SCOPE_LIST, r_jwt_get_claim_str_value(jwt, "aud")); r_jwt_free(jwt); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_implicit_token_id_token_valid_with_resource3) { struct _u_response resp; char * access_token; jwt_t * jwt; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s&resource=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_TOKEN_ID_TOKEN, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_LIST, RESOURCE3_ENC); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); access_token = o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token="); if (o_strchr(access_token, '&') != NULL) { *o_strchr(access_token, '&') = '\0'; } ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, access_token, 0), RHN_OK); ck_assert_str_eq(RESOURCE3, r_jwt_get_claim_str_value(jwt, "aud")); r_jwt_free(jwt); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_implicit_token_id_token_invalid_and_with_resource1) { struct _u_response resp; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s&resource=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_TOKEN_ID_TOKEN, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_LIST, RESOURCE1_ENC); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "error=invalid_target"), NULL); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_implicit_token_id_token_invalid_with_hash) { struct _u_response resp; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s&resource=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_TOKEN_ID_TOKEN, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_2, RESOURCE_HASH_ENC); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "error=invalid_target"), NULL); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_code_valid_with_resource1) { struct _u_request req; struct _u_response resp; const char * access_token; char * code; json_t * j_resp; jwt_t * jwt; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s&resource=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_LIST, RESOURCE1_ENC); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code="); if (o_strchr(code, '&') != NULL) { *o_strchr(code, '&') = '\0'; } ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "resource", RESOURCE1, U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, access_token = json_string_value(json_object_get(j_resp, "access_token"))); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, access_token, 0), RHN_OK); ck_assert_str_eq(RESOURCE1, r_jwt_get_claim_str_value(jwt, "aud")); r_jwt_free(jwt); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_resp); } END_TEST START_TEST(test_oidc_resource_code_invalid_with_resource2) { struct _u_response resp; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s&resource=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_1, RESOURCE2_ENC); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "error=invalid_target"), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_code_invalid_with_resource_error) { struct _u_response resp; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s&resource=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_1, RESOURCE_ERR_ENC); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "error=invalid_target"), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_code_valid_without_resource) { struct _u_request req; struct _u_response resp; const char * access_token; char * code; json_t * j_resp; jwt_t * jwt; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code="); if (o_strchr(code, '&') != NULL) { *o_strchr(code, '&') = '\0'; } ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, access_token = json_string_value(json_object_get(j_resp, "access_token"))); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, access_token, 0), RHN_OK); ck_assert_str_eq(SCOPE_LIST, r_jwt_get_claim_str_value(jwt, "aud")); r_jwt_free(jwt); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_resp); } END_TEST START_TEST(test_oidc_resource_code_invalid_with_resource_change) { struct _u_request req; struct _u_response resp; char * code; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s&resource=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_LIST, RESOURCE1_ENC); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code="); if (o_strchr(code, '&') != NULL) { *o_strchr(code, '&') = '\0'; } ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "resource", RESOURCE2, U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_code_valid_without_resource1_on_confirmation) { struct _u_request req; struct _u_response resp; const char * access_token; char * code; json_t * j_resp; jwt_t * jwt; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s&resource=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_LIST, RESOURCE1_ENC); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code="); if (o_strchr(code, '&') != NULL) { *o_strchr(code, '&') = '\0'; } ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, access_token = json_string_value(json_object_get(j_resp, "access_token"))); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, access_token, 0), RHN_OK); ck_assert_str_eq(RESOURCE1, r_jwt_get_claim_str_value(jwt, "aud")); r_jwt_free(jwt); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_resp); } END_TEST START_TEST(test_oidc_resource_code_invalid_with_resource1_on_confirmation_only) { struct _u_request req; struct _u_response resp; char * code; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code="); if (o_strchr(code, '&') != NULL) { *o_strchr(code, '&') = '\0'; } ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "resource", RESOURCE1, U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 403); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_code_valid_with_resource3) { struct _u_request req; struct _u_response resp; const char * access_token; char * code; json_t * j_resp; jwt_t * jwt; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s&resource=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_LIST, RESOURCE3_ENC); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code="); if (o_strchr(code, '&') != NULL) { *o_strchr(code, '&') = '\0'; } ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "resource", RESOURCE3, U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, access_token = json_string_value(json_object_get(j_resp, "access_token"))); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, access_token, 0), RHN_OK); ck_assert_str_eq(RESOURCE3, r_jwt_get_claim_str_value(jwt, "aud")); r_jwt_free(jwt); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_resp); } END_TEST START_TEST(test_oidc_resource_code_invalid_with_resource1) { struct _u_response resp; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s&resource=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_LIST, RESOURCE1_ENC); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "error=invalid_target"), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_code_invalid_with_hash) { struct _u_response resp; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s&resource=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_1, RESOURCE_HASH_ENC); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "error=invalid_target"), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_refresh_valid_with_resource1) { struct _u_request req; struct _u_response resp; const char * access_token, * refresh_token; char * code; json_t * j_resp; jwt_t * jwt; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s&resource=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_LIST, RESOURCE1_ENC); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code="); if (o_strchr(code, '&') != NULL) { *o_strchr(code, '&') = '\0'; } ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "resource", RESOURCE1, U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, refresh_token = json_string_value(json_object_get(j_resp, "refresh_token"))); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "grant_type", "refresh_token", U_OPT_POST_BODY_PARAMETER, "refresh_token", refresh_token, U_OPT_POST_BODY_PARAMETER, "resource", RESOURCE1, U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, access_token = json_string_value(json_object_get(j_resp, "access_token"))); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, access_token, 0), RHN_OK); ck_assert_str_eq(RESOURCE1, r_jwt_get_claim_str_value(jwt, "aud")); json_decref(j_resp); r_jwt_free(jwt); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_refresh_invalid_with_resource_change) { struct _u_request req; struct _u_response resp; const char * refresh_token; char * code; json_t * j_resp; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s&resource=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_LIST, RESOURCE1_ENC); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code="); if (o_strchr(code, '&') != NULL) { *o_strchr(code, '&') = '\0'; } ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, refresh_token = json_string_value(json_object_get(j_resp, "refresh_token"))); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "grant_type", "refresh_token", U_OPT_POST_BODY_PARAMETER, "refresh_token", refresh_token, U_OPT_POST_BODY_PARAMETER, "resource", RESOURCE2, U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 400); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_refresh_invalid_with_resource_error) { struct _u_request req; struct _u_response resp; const char * refresh_token; char * code; json_t * j_resp; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s&resource=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_LIST, RESOURCE1_ENC); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code="); if (o_strchr(code, '&') != NULL) { *o_strchr(code, '&') = '\0'; } ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, refresh_token = json_string_value(json_object_get(j_resp, "refresh_token"))); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "grant_type", "refresh_token", U_OPT_POST_BODY_PARAMETER, "refresh_token", refresh_token, U_OPT_POST_BODY_PARAMETER, "resource", RESOURCE_ERR, U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 400); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_refresh_valid_with_resource3) { struct _u_request req; struct _u_response resp; const char * access_token, * refresh_token; char * code; json_t * j_resp; jwt_t * jwt; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s&resource=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_LIST, RESOURCE3_ENC); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code="); if (o_strchr(code, '&') != NULL) { *o_strchr(code, '&') = '\0'; } ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "resource", RESOURCE3, U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, refresh_token = json_string_value(json_object_get(j_resp, "refresh_token"))); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "grant_type", "refresh_token", U_OPT_POST_BODY_PARAMETER, "refresh_token", refresh_token, U_OPT_POST_BODY_PARAMETER, "resource", RESOURCE3, U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, access_token = json_string_value(json_object_get(j_resp, "access_token"))); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, access_token, 0), RHN_OK); ck_assert_str_eq(RESOURCE3, r_jwt_get_claim_str_value(jwt, "aud")); json_decref(j_resp); r_jwt_free(jwt); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_refresh_valid_with_resource_change) { struct _u_request req; struct _u_response resp; const char * access_token, * refresh_token; char * code; json_t * j_resp; jwt_t * jwt; ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s&resource=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_CODE, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_LIST, RESOURCE1_ENC); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strstr(u_map_get(resp.map_header, "Location"), "code=") + o_strlen("code="); if (o_strchr(code, '&') != NULL) { *o_strchr(code, '&') = '\0'; } ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT_ID, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT_URI, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_POST_BODY_PARAMETER, "resource", RESOURCE1, U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, refresh_token = json_string_value(json_object_get(j_resp, "refresh_token"))); ck_assert_ptr_ne(NULL, access_token = json_string_value(json_object_get(j_resp, "access_token"))); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, access_token, 0), RHN_OK); ck_assert_str_eq(RESOURCE1, r_jwt_get_claim_str_value(jwt, "aud")); r_jwt_free(jwt); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "grant_type", "refresh_token", U_OPT_POST_BODY_PARAMETER, "refresh_token", refresh_token, U_OPT_POST_BODY_PARAMETER, "resource", RESOURCE3, U_OPT_AUTH_BASIC_USER, CLIENT_ID, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, access_token = json_string_value(json_object_get(j_resp, "access_token"))); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, access_token, 0), RHN_OK); ck_assert_str_eq(RESOURCE3, r_jwt_get_claim_str_value(jwt, "aud")); r_jwt_free(jwt); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_resource_delete_client) { json_t * j_parameters = json_pack("{ss}", "scope", ""); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL); json_decref(j_parameters); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_ID, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_resource_delete_plugin) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_resource_introspect_valid_with_resource1) { struct _u_request req; struct _u_response resp; char * access_token; jwt_t * jwt; json_t * j_result; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s&resource=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE_TOKEN_ID_TOKEN, CLIENT_ID, CLIENT_REDIRECT_URI_ENC, SCOPE_LIST, RESOURCE1_ENC); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); access_token = o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token="); if (o_strchr(access_token, '&') != NULL) { *o_strchr(access_token, '&') = '\0'; } ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, access_token, 0), RHN_OK); ck_assert_str_eq(RESOURCE1, r_jwt_get_claim_str_value(jwt, "aud")); r_jwt_free(jwt); ulfius_init_request(&req); u_map_put(req.map_post_body, "token", access_token); u_map_put(req.map_post_body, "token_type_hint", "access_token"); j_result = json_pack("{ss}", "aud", RESOURCE1); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_ID, CLIENT_SECRET, NULL, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_result); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc resource"); tc_core = tcase_create("test_oidc_resource"); tcase_add_test(tc_core, test_oidc_resource_add_plugin_scope_or_client_change_not_allowed); tcase_add_test(tc_core, test_oidc_resource_add_client_confidential_ok); tcase_add_test(tc_core, test_oidc_resource_device_verification_valid_with_resource1); tcase_add_test(tc_core, test_oidc_resource_device_verification_invalid_with_resource2); tcase_add_test(tc_core, test_oidc_resource_device_verification_invalid_with_resource_error); tcase_add_test(tc_core, test_oidc_resource_device_verification_valid_without_resource); tcase_add_test(tc_core, test_oidc_resource_device_verification_invalid_with_resource_change); tcase_add_test(tc_core, test_oidc_resource_device_verification_valid_without_resource1_on_confirmation); tcase_add_test(tc_core, test_oidc_resource_device_verification_invalid_with_resource1_on_confirmation_only); tcase_add_test(tc_core, test_oidc_resource_implicit_token_id_token_valid_with_resource1); tcase_add_test(tc_core, test_oidc_resource_implicit_token_id_token_invalid_with_resource1); tcase_add_test(tc_core, test_oidc_resource_implicit_token_id_token_invalid_with_resource_error); tcase_add_test(tc_core, test_oidc_resource_implicit_token_id_token_valid_without_resource); tcase_add_test(tc_core, test_oidc_resource_code_valid_with_resource1); tcase_add_test(tc_core, test_oidc_resource_code_invalid_with_resource2); tcase_add_test(tc_core, test_oidc_resource_code_invalid_with_resource_error); tcase_add_test(tc_core, test_oidc_resource_code_valid_without_resource); tcase_add_test(tc_core, test_oidc_resource_code_invalid_with_resource_change); tcase_add_test(tc_core, test_oidc_resource_code_valid_without_resource1_on_confirmation); tcase_add_test(tc_core, test_oidc_resource_code_invalid_with_resource1_on_confirmation_only); tcase_add_test(tc_core, test_oidc_resource_refresh_valid_with_resource1); tcase_add_test(tc_core, test_oidc_resource_refresh_invalid_with_resource_change); tcase_add_test(tc_core, test_oidc_resource_refresh_invalid_with_resource_error); tcase_add_test(tc_core, test_oidc_resource_introspect_valid_with_resource1); tcase_add_test(tc_core, test_oidc_resource_delete_plugin); tcase_add_test(tc_core, test_oidc_resource_add_plugin_scope_and_client_change_not_allowed); tcase_add_test(tc_core, test_oidc_resource_device_verification_valid_with_resource3); tcase_add_test(tc_core, test_oidc_resource_device_verification_invalid_with_resource1); tcase_add_test(tc_core, test_oidc_resource_implicit_token_id_token_valid_with_resource3); tcase_add_test(tc_core, test_oidc_resource_implicit_token_id_token_invalid_and_with_resource1); tcase_add_test(tc_core, test_oidc_resource_code_valid_with_resource3); tcase_add_test(tc_core, test_oidc_resource_code_invalid_with_resource1); tcase_add_test(tc_core, test_oidc_resource_refresh_valid_with_resource3); tcase_add_test(tc_core, test_oidc_resource_delete_plugin); tcase_add_test(tc_core, test_oidc_resource_add_plugin_scope_or_client_change_allowed); tcase_add_test(tc_core, test_oidc_resource_device_verification_invalid_with_resource_change); tcase_add_test(tc_core, test_oidc_resource_code_invalid_with_resource_change); tcase_add_test(tc_core, test_oidc_resource_refresh_valid_with_resource_change); tcase_add_test(tc_core, test_oidc_resource_delete_plugin); tcase_add_test(tc_core, test_oidc_resource_add_plugin_scope_or_client_change_not_allowed_with_hash); tcase_add_test(tc_core, test_oidc_resource_device_verification_invalid_with_hash); tcase_add_test(tc_core, test_oidc_resource_implicit_token_id_token_invalid_with_hash); tcase_add_test(tc_core, test_oidc_resource_code_invalid_with_hash); tcase_add_test(tc_core, test_oidc_resource_delete_client); tcase_add_test(tc_core, test_oidc_resource_delete_plugin); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req; struct _u_response auth_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); ulfius_init_request(&user_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&scope_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "User %s authenticated", USER_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(scope_req.map_header, "Cookie", cookie); u_map_put(user_req.map_header, "Cookie", cookie); o_free(cookie); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication user"); do_test = 0; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); } if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } char * url = msprintf("%s/auth/", SERVER_URI); run_simple_test(&user_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL); o_free(url); ulfius_clean_request(&admin_req); ulfius_clean_request(&user_req); ulfius_clean_request(&scope_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_resource_owner_pwd_cred.c000066400000000000000000000105521415646314000243340ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api/oidc" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile scope3" #define SCOPE_LIST_OPENID "g_profile scope3 openid" START_TEST(test_oidc_resource_owner_pwd_cred_valid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", SCOPE_LIST); u_map_put(&body, "username", USERNAME); u_map_put(&body, "password", PASSWORD); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 200, NULL, "refresh_token", NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_resource_owner_pwd_cred_openid_valid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", SCOPE_LIST_OPENID); u_map_put(&body, "username", USERNAME); u_map_put(&body, "password", PASSWORD); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 200, NULL, "id_token", NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_resource_owner_pwd_cred_pwd_invalid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", SCOPE_LIST); u_map_put(&body, "username", USERNAME); u_map_put(&body, "password", "invalid"); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_resource_owner_pwd_cred_user_invalid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", SCOPE_LIST); u_map_put(&body, "username", "invalid"); u_map_put(&body, "password", PASSWORD); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_resource_owner_pwd_cred_scope_invalid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", "invalid"); u_map_put(&body, "username", USERNAME); u_map_put(&body, "password", PASSWORD); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_resource_owner_pwd_cred_empty) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); int res = run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc resource owner password credential"); tc_core = tcase_create("test_oidc_resource_owner_pwd_cred"); tcase_add_test(tc_core, test_oidc_resource_owner_pwd_cred_valid); tcase_add_test(tc_core, test_oidc_resource_owner_pwd_cred_openid_valid); tcase_add_test(tc_core, test_oidc_resource_owner_pwd_cred_pwd_invalid); tcase_add_test(tc_core, test_oidc_resource_owner_pwd_cred_user_invalid); tcase_add_test(tc_core, test_oidc_resource_owner_pwd_cred_scope_invalid); tcase_add_test(tc_core, test_oidc_resource_owner_pwd_cred_empty); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed; Suite *s; SRunner *sr; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_resource_owner_pwd_cred_client_confidential.c000066400000000000000000000113061415646314000304070ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api/oidc" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile scope3" #define CLIENT "client3_id" #define CLIENT_PASSWORD "password" char * code; START_TEST(test_oidc_resource_owner_pwd_cred_client_confidential_valid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", SCOPE_LIST); u_map_put(&body, "username", USERNAME); u_map_put(&body, "password", PASSWORD); int res = run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 200, NULL, "refresh_token", NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_resource_owner_pwd_cred_client_confidential_pwd_invalid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", SCOPE_LIST); u_map_put(&body, "username", USERNAME); u_map_put(&body, "password", "invalid"); int res = run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_resource_owner_pwd_cred_client_confidential_user_invalid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", SCOPE_LIST); u_map_put(&body, "username", "invalid"); u_map_put(&body, "password", PASSWORD); int res = run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_resource_owner_pwd_cred_client_confidential_client_invalid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", SCOPE_LIST); u_map_put(&body, "username", USERNAME); u_map_put(&body, "password", PASSWORD); int res = run_simple_test(NULL, "POST", url, CLIENT, "invalid", NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_resource_owner_pwd_cred_client_confidential_scope_invalid) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); u_map_put(&body, "scope", "invalid"); u_map_put(&body, "username", USERNAME); u_map_put(&body, "password", PASSWORD); int res = run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 403, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_resource_owner_pwd_cred_client_confidential_empty) { char * url = msprintf("%s/token/", SERVER_URI); struct _u_map body; u_map_init(&body); u_map_put(&body, "grant_type", "password"); int res = run_simple_test(NULL, "POST", url, CLIENT, CLIENT_PASSWORD, NULL, &body, 400, NULL, NULL, NULL); o_free(url); u_map_clean(&body); ck_assert_int_eq(res, 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc resource owner password credential client confidential"); tc_core = tcase_create("test_oidc_resource_owner_pwd_cred"); tcase_add_test(tc_core, test_oidc_resource_owner_pwd_cred_client_confidential_valid); tcase_add_test(tc_core, test_oidc_resource_owner_pwd_cred_client_confidential_pwd_invalid); tcase_add_test(tc_core, test_oidc_resource_owner_pwd_cred_client_confidential_user_invalid); tcase_add_test(tc_core, test_oidc_resource_owner_pwd_cred_client_confidential_client_invalid); tcase_add_test(tc_core, test_oidc_resource_owner_pwd_cred_client_confidential_scope_invalid); tcase_add_test(tc_core, test_oidc_resource_owner_pwd_cred_client_confidential_empty); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed; Suite *s; SRunner *sr; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_rich_auth_requests.c000066400000000000000000002536731415646314000233420ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "user1" #define USER_PASSWORD "password" #define PLUGIN_MODULE "oidc" #define PLUGIN_NAME "oidc_rar" #define PLUGIN_ISS "https://glewlwyd.tld" #define PLUGIN_JWT_TYPE_RSA "rsa" #define PLUGIN_JWT_KEY_SIZE "256" #define SCOPE_LIST "g_profile openid" #define SCOPE_1 "g_profile" #define SCOPE_2 "openid" #define SCOPE_3 "g_admin" #define CLIENT "client3_id" #define CLIENT_PASSWORD "password" #define RESPONSE_TYPE "code token" #define RAR1 "type1" #define RAR2 "type2" #define RAR3 "type3" #define RAR4 "type4" #define ENRICHED1 "name" #define ENRICHED2 "email" #define PRIVILEGE1 "read" #define PRIVILEGE2 "write" #define CLIENT_PUBKEY_ID "client_rar" #define CLIENT_SECRET "secret_string" #define CLIENT_AUTH_TOKEN_MAX_AGE 3600 #define CLIENT_PUBKEY_PARAM "pubkey" #define CLIENT_PUBKEY_NAME "client with pubkey" #define CLIENT_PUBKEY_REDIRECT "https://glewlwyd.local/" #define CLIENT_PUBKEY_REDIRECT_ESCAPED "https%3A%2F%2Fglewlwyd.local%2F" #define CLIENT_SCOPE "scope1" #define KID_PUB "pubkey" #define CLIENT_JWKS_PARAM "jwks" #define CLIENT_JWKS_URI_PARAM "jwks_uri" #define PLUGIN_CODE_DURATION 600 #define PLUGIN_REFRESH_TOKEN_DURATION 1209600 #define PLUGIN_ACCESS_TOKEN_DURATION 3600 const char pubkey_1_jwk[] = "{\"keys\":[{\"kty\":\"RSA\",\"n\":\"AMWhdXoJpkPtPwABHL_yXUwgcYuwNOVbw70YGmMzhFqiRd6r92-onw-BOAvfnIq-rSMgjidllxOE1fXwlgUIyKJmnHUI3RMDABFmGFRM-Dz6VmQxHgiioLM-Q5yzcj85zIqJvNrw0RL0qhvssQBG5Fta_jLXBUXeGEmciWA0lSfrdlS-zbfxsWqPzAvKyT_0B80m1o8K7ksFtyTPu-cHbCVGx4ciGeZUNrtOnevGQPUOE-tIvsxOPcqC3fPjyI3K4TN5GCCZHEyso1qmRFfsHtenq6EvD1_2DebcODnfnym-iNFyC4YsgqipToNxR3WPIgCu-WrSOk71-93ovs0hd1MhBYw03J4Xupjxy_URCFZm9Pp-9H3j_0hUKmhUWmpsQTpAT7FWvTT-MyyYkZ-9Y33-6KR3E-82kdfXIoEMbGJnfq2Z4Yh_lF3pfD-5FUzOzgnOy0UiTxusWOBbaVhNqmm6xmlHwQjBrax9Bqo7WzQpwXgXgooo6TeVz6pxpUl6V63d5o5XZaxYYilUpZ78qXpHQMwNFr6a2gct-dU8zLF6YaJHIaMp6XT9OD8r-w7SOkq1O7J-UGRqGbUVczYzhApY1Q-B2ZnO18P5KQnG97AbU_Sjk5Rnf6HJ-w-E8NOIwgo5jzloV_5Ck6w-DH_sL5FDca89BGuzwpQEH_h3ma43\",\"e\":\"AQAB\",\"kid\":\"" KID_PUB "\"}]}"; const char privkey_1_jwk[] = "{\"kty\":\"RSA\",\"n\":\"AMWhdXoJpkPtPwABHL_yXUwgcYuwNOVbw70YGmMzhFqiRd6r92-onw-BOAvfnIq-rSMgjidllxOE1fXwlgUIyKJmnHUI3RMDABFmGFRM-Dz6VmQxHgiioLM-Q5yzcj85zIqJvNrw0RL0qhvssQBG5Fta_jLXBUXeGEmciWA0lSfrdlS-zbfxsWqPzAvKyT_0B80m1o8K7ksFtyTPu-cHbCVGx4ciGeZUNrtOnevGQPUOE-tIvsxOPcqC3fPjyI3K4TN5GCCZHEyso1qmRFfsHtenq6EvD1_2DebcODnfnym-iNFyC4YsgqipToNxR3WPIgCu-WrSOk71-93ovs0hd1MhBYw03J4Xupjxy_URCFZm9Pp-9H3j_0hUKmhUWmpsQTpAT7FWvTT-MyyYkZ-9Y33-6KR3E-82kdfXIoEMbGJnfq2Z4Yh_lF3pfD-5FUzOzgnOy0UiTxusWOBbaVhNqmm6xmlHwQjBrax9Bqo7WzQpwXgXgooo6TeVz6pxpUl6V63d5o5XZaxYYilUpZ78qXpHQMwNFr6a2gct-dU8zLF6YaJHIaMp6XT9OD8r-w7SOkq1O7J-UGRqGbUVczYzhApY1Q-B2ZnO18P5KQnG97AbU_Sjk5Rnf6HJ-w-E8NOIwgo5jzloV_5Ck6w-DH_sL5FDca89BGuzwpQEH_h3ma43\",\"e\":\"AQAB\",\"d\":\"AIiu6F7k-ZcVKHNKUaX3a8tQzPb9gTf3xWKsnuNpJ-q_PG-Ko_EXwBqrFiYwG0ZiJcCbrXVV76zSPGCCal9E-e5H5YGUBcI2Wv-tiroTGcSipslYpxr1zwrozz47ZZKQ2QQfyvvpfdAMYvI5Oxmj7h-4yQJEcCMoPcf7eY-ODnKziP2HkSPdBwVaOpcVQyb2EcczS0VXHAPLCiVtftmD6qnFUA4H6b3BFLFq6BG-5gIWIHSjtUH8AwRiijs5mOVoIWTGJYe2HTpyU_BH-hCM_6_LCQrLT2jg9jBqsoBkRuJKIroolAvSEPOxVNnXqMKHoc6zNVFJ4IXn3rBVXlDlCm69xoe67-X2M4o8LXpdnwFtvao3YYKqAqv1kH0JZE9kJyY3odhXa-SRZpvOCoE3YpDr5UTlRkEWZATQjqtGP7JEq_RQwtDwM1NpANIl4cFAJVhUJbndjMeJqBcA4-NEV6bBjWkenw179H6UuWNXNzXklPsgtMnF_PwcBFKutwnFqHAE5g6w9iHQ5yG7_2m4zModfBiGiSy3cdQ2f3MEHRRoBmqooEGU_6Urrn6iyAFxk_sINEnT_7Emygle_QwP5N-BQuFpD_NWojGirWwOwiWYBHRBXP0ub17bNx7w4gha6CxHnXyJ0MZBayOIMrnQGeWC7o5a932LCTQfegdBh5xh\",\"p\":\"APQQSKxv01Oky-jENQwxiZcpI4a5PzLPFFCgEqIjSRamCzrCQ07e97iqhU1b8IvRwxDtX358pFKAq7tmwpN2QQb1T9fqUwCpeQuMwRsZwoaM7ZcTSj2FZ_2djN1ixQfzqQ21VxkMRbrdyExqCSJXnHMcLeiFmu81dVopV2iwDbUQv4jZe_ktPUTH4HKle48Y0v9pu22lD5cknAQGB1gUNfyJ0PbUxZMITrZDz4khhYgxqvJ7GluYRNv2tezV-bb5leXbSLDrRgTKqcl5ZjkgLm9FRNGZZAmlsCHEeB3nvCs2ePQYDuLgEkNtuu39kpLFJO6j70bjnvpaIAcDVpPmEE8\",\"q\":\"AM9L09Grg2uSrNUGfj9pfpsMn0k5kqV0n3WjX9z5ZLkwLNNrs0SJjb93haO2MPNlyYhctCpPKnfHJKZWaLhFDV6xr-ubf7c3DbBJjPhlV8dUkgmHfIqWDPl6pzN0xC61zC4IE15LgW_JEMpq53fRWnIHdufs-105QO8YOo0CVYKYjqut4hVbYRBSTaeVLb1vj_yhaL0qV7orQoTrpr6Bg20nftBBa-8Md_B5l0QyiSfvOjKnXsjULQdQGbtypQZvu2jUasnUVUQHBgeF5W5WFj8qCGGnmehqY6QissipLoRMcGPaV_gJKisgcorF7sSU_QzcBUmPk377LkzZXGNUYZk\",\"qi\":\"AILHVNisODhO4GC8P709DqGdVdufLZf2Bl7AwjWyYTkpEzEfQCHHUnmOoTCn-OEvnn9lWiaCaTijtlUmos0fCfvSQLk9elciIOmlRk8G1EtnnzYQsTmerLoMJBgQ02hhip8GK47Y7mbZIjaPB625Dv8F4RHd9ZiTzXTGcNc6bldWlNNbbqw9DWS1DORPhdQEPU424qcYvHq_eklFCujWukO8ul3FEZYnTcth2ODSFMb0a0SCuDGkGI8BDI-_4n6-4wIlAXtc8Vt9Ko8WxJjCK_v2Ae9x05eknWZj0JxuyoAjPtJApp0pt25omJwZr_lY5i8T0cL6dDF5nZcA9hN__eo\",\"dp\":\"ALL4hfI9BmCdxhFoX-YTJWw9dJnEmf1uMN12pHNVILGFDVMHRUg-5LT8BkhWFSzSoxJ0nsQoLm95f3Uqw6BS5RhvJx-T603e-K5phumSmD0GduuD77rxavJlZ_ioBwfvu5Yb1kS95RxEqi6uywft6wHWNiv-XUDwmJ-HFVvlTgfqwileIjT04argT0yC4PpsH73AEPs0QRx6chXZPeVu3K_Vd_Co0kEhpGavjy5l8H-QvGSXtRpZrJUIcxu7RSTSHQOzK7jgrjWxT5Q4e6eEW8ioqPByZRNV9rSsV9DGMAwYI9YLFk90NLBRdPQ0MBmEi7KbcEkxfVDkafv6jLBj0q0\",\"dq\":\"AJldyYY7dczVxMcKucbinwfJq-N6E_QTt5JKYDdV0F5utQtqiEQx3MyGejooJkk9yn_3zlfrIElj7cqe7XU_qWeg4L3Y2wHLWnZNxF1WZT4VZMJmGg9SeqDtTNz2C9tfJ4P695FxHX99681GkKAGJPtuaFuo6kQLgu4iJ9eBnZA0nIGJ8VXJuKNhsRBGf4PDEW1gYeRqemNDdEBxNHmHypusd9dOP7OpruccnnyXQwBnrtAhIjBFQldBvPgBFvUPH0GsvqE6VicxZxWTy635RRZQW8kcPfNFGxkpjsqE2OSKxTArL6BT733e0L-5NzD75cho1ASblA2DerriqcbXfCk\",\"kid\":\"" KID_PUB "\"}"; const char pubkey_1_pem[] = "-----BEGIN PUBLIC KEY-----\n"\ "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxaF1egmmQ+0/AAEcv/Jd\n"\ "TCBxi7A05VvDvRgaYzOEWqJF3qv3b6ifD4E4C9+cir6tIyCOJ2WXE4TV9fCWBQjI\n"\ "omacdQjdEwMAEWYYVEz4PPpWZDEeCKKgsz5DnLNyPznMiom82vDREvSqG+yxAEbk\n"\ "W1r+MtcFRd4YSZyJYDSVJ+t2VL7Nt/Gxao/MC8rJP/QHzSbWjwruSwW3JM+75wds\n"\ "JUbHhyIZ5lQ2u06d68ZA9Q4T60i+zE49yoLd8+PIjcrhM3kYIJkcTKyjWqZEV+we\n"\ "16eroS8PX/YN5tw4Od+fKb6I0XILhiyCqKlOg3FHdY8iAK75atI6TvX73ei+zSF3\n"\ "UyEFjDTcnhe6mPHL9REIVmb0+n70feP/SFQqaFRaamxBOkBPsVa9NP4zLJiRn71j\n"\ "ff7opHcT7zaR19cigQxsYmd+rZnhiH+UXel8P7kVTM7OCc7LRSJPG6xY4FtpWE2q\n"\ "abrGaUfBCMGtrH0GqjtbNCnBeBeCiijpN5XPqnGlSXpXrd3mjldlrFhiKVSlnvyp\n"\ "ekdAzA0WvpraBy351TzMsXphokchoynpdP04Pyv7DtI6SrU7sn5QZGoZtRVzNjOE\n"\ "CljVD4HZmc7Xw/kpCcb3sBtT9KOTlGd/ocn7D4Tw04jCCjmPOWhX/kKTrD4Mf+wv\n"\ "kUNxrz0Ea7PClAQf+HeZrjcCAwEAAQ==\n"\ "-----END PUBLIC KEY-----"; const char privkey_1_pem[] = "-----BEGIN RSA PRIVATE KEY-----\n"\ "MIIJKwIBAAKCAgEAxaF1egmmQ+0/AAEcv/JdTCBxi7A05VvDvRgaYzOEWqJF3qv3\n"\ "b6ifD4E4C9+cir6tIyCOJ2WXE4TV9fCWBQjIomacdQjdEwMAEWYYVEz4PPpWZDEe\n"\ "CKKgsz5DnLNyPznMiom82vDREvSqG+yxAEbkW1r+MtcFRd4YSZyJYDSVJ+t2VL7N\n"\ "t/Gxao/MC8rJP/QHzSbWjwruSwW3JM+75wdsJUbHhyIZ5lQ2u06d68ZA9Q4T60i+\n"\ "zE49yoLd8+PIjcrhM3kYIJkcTKyjWqZEV+we16eroS8PX/YN5tw4Od+fKb6I0XIL\n"\ "hiyCqKlOg3FHdY8iAK75atI6TvX73ei+zSF3UyEFjDTcnhe6mPHL9REIVmb0+n70\n"\ "feP/SFQqaFRaamxBOkBPsVa9NP4zLJiRn71jff7opHcT7zaR19cigQxsYmd+rZnh\n"\ "iH+UXel8P7kVTM7OCc7LRSJPG6xY4FtpWE2qabrGaUfBCMGtrH0GqjtbNCnBeBeC\n"\ "iijpN5XPqnGlSXpXrd3mjldlrFhiKVSlnvypekdAzA0WvpraBy351TzMsXphokch\n"\ "oynpdP04Pyv7DtI6SrU7sn5QZGoZtRVzNjOECljVD4HZmc7Xw/kpCcb3sBtT9KOT\n"\ "lGd/ocn7D4Tw04jCCjmPOWhX/kKTrD4Mf+wvkUNxrz0Ea7PClAQf+HeZrjcCAwEA\n"\ "AQKCAgEAiK7oXuT5lxUoc0pRpfdry1DM9v2BN/fFYqye42kn6r88b4qj8RfAGqsW\n"\ "JjAbRmIlwJutdVXvrNI8YIJqX0T57kflgZQFwjZa/62KuhMZxKKmyVinGvXPCujP\n"\ "PjtlkpDZBB/K++l90Axi8jk7GaPuH7jJAkRwIyg9x/t5j44OcrOI/YeRI90HBVo6\n"\ "lxVDJvYRxzNLRVccA8sKJW1+2YPqqcVQDgfpvcEUsWroEb7mAhYgdKO1QfwDBGKK\n"\ "OzmY5WghZMYlh7YdOnJT8Ef6EIz/r8sJCstPaOD2MGqygGRG4koiuiiUC9IQ87FU\n"\ "2deowoehzrM1UUnghefesFVeUOUKbr3Gh7rv5fYzijwtel2fAW29qjdhgqoCq/WQ\n"\ "fQlkT2QnJjeh2Fdr5JFmm84KgTdikOvlROVGQRZkBNCOq0Y/skSr9FDC0PAzU2kA\n"\ "0iXhwUAlWFQlud2Mx4moFwDj40RXpsGNaR6fDXv0fpS5Y1c3NeSU+yC0ycX8/BwE\n"\ "Uq63CcWocATmDrD2IdDnIbv/abjMyh18GIaJLLdx1DZ/cwQdFGgGaqigQZT/pSuu\n"\ "fqLIAXGT+wg0SdP/sSbKCV79DA/k34FC4WkP81aiMaKtbA7CJZgEdEFc/S5vXts3\n"\ "HvDiCFroLEedfInQxkFrI4gyudAZ5YLujlr3fYsJNB96B0GHnGECggEBAPQQSKxv\n"\ "01Oky+jENQwxiZcpI4a5PzLPFFCgEqIjSRamCzrCQ07e97iqhU1b8IvRwxDtX358\n"\ "pFKAq7tmwpN2QQb1T9fqUwCpeQuMwRsZwoaM7ZcTSj2FZ/2djN1ixQfzqQ21VxkM\n"\ "RbrdyExqCSJXnHMcLeiFmu81dVopV2iwDbUQv4jZe/ktPUTH4HKle48Y0v9pu22l\n"\ "D5cknAQGB1gUNfyJ0PbUxZMITrZDz4khhYgxqvJ7GluYRNv2tezV+bb5leXbSLDr\n"\ "RgTKqcl5ZjkgLm9FRNGZZAmlsCHEeB3nvCs2ePQYDuLgEkNtuu39kpLFJO6j70bj\n"\ "nvpaIAcDVpPmEE8CggEBAM9L09Grg2uSrNUGfj9pfpsMn0k5kqV0n3WjX9z5ZLkw\n"\ "LNNrs0SJjb93haO2MPNlyYhctCpPKnfHJKZWaLhFDV6xr+ubf7c3DbBJjPhlV8dU\n"\ "kgmHfIqWDPl6pzN0xC61zC4IE15LgW/JEMpq53fRWnIHdufs+105QO8YOo0CVYKY\n"\ "jqut4hVbYRBSTaeVLb1vj/yhaL0qV7orQoTrpr6Bg20nftBBa+8Md/B5l0QyiSfv\n"\ "OjKnXsjULQdQGbtypQZvu2jUasnUVUQHBgeF5W5WFj8qCGGnmehqY6QissipLoRM\n"\ "cGPaV/gJKisgcorF7sSU/QzcBUmPk377LkzZXGNUYZkCggEBALL4hfI9BmCdxhFo\n"\ "X+YTJWw9dJnEmf1uMN12pHNVILGFDVMHRUg+5LT8BkhWFSzSoxJ0nsQoLm95f3Uq\n"\ "w6BS5RhvJx+T603e+K5phumSmD0GduuD77rxavJlZ/ioBwfvu5Yb1kS95RxEqi6u\n"\ "ywft6wHWNiv+XUDwmJ+HFVvlTgfqwileIjT04argT0yC4PpsH73AEPs0QRx6chXZ\n"\ "PeVu3K/Vd/Co0kEhpGavjy5l8H+QvGSXtRpZrJUIcxu7RSTSHQOzK7jgrjWxT5Q4\n"\ "e6eEW8ioqPByZRNV9rSsV9DGMAwYI9YLFk90NLBRdPQ0MBmEi7KbcEkxfVDkafv6\n"\ "jLBj0q0CggEBAJldyYY7dczVxMcKucbinwfJq+N6E/QTt5JKYDdV0F5utQtqiEQx\n"\ "3MyGejooJkk9yn/3zlfrIElj7cqe7XU/qWeg4L3Y2wHLWnZNxF1WZT4VZMJmGg9S\n"\ "eqDtTNz2C9tfJ4P695FxHX99681GkKAGJPtuaFuo6kQLgu4iJ9eBnZA0nIGJ8VXJ\n"\ "uKNhsRBGf4PDEW1gYeRqemNDdEBxNHmHypusd9dOP7OpruccnnyXQwBnrtAhIjBF\n"\ "QldBvPgBFvUPH0GsvqE6VicxZxWTy635RRZQW8kcPfNFGxkpjsqE2OSKxTArL6BT\n"\ "733e0L+5NzD75cho1ASblA2DerriqcbXfCkCggEBAILHVNisODhO4GC8P709DqGd\n"\ "VdufLZf2Bl7AwjWyYTkpEzEfQCHHUnmOoTCn+OEvnn9lWiaCaTijtlUmos0fCfvS\n"\ "QLk9elciIOmlRk8G1EtnnzYQsTmerLoMJBgQ02hhip8GK47Y7mbZIjaPB625Dv8F\n"\ "4RHd9ZiTzXTGcNc6bldWlNNbbqw9DWS1DORPhdQEPU424qcYvHq/eklFCujWukO8\n"\ "ul3FEZYnTcth2ODSFMb0a0SCuDGkGI8BDI+/4n6+4wIlAXtc8Vt9Ko8WxJjCK/v2\n"\ "Ae9x05eknWZj0JxuyoAjPtJApp0pt25omJwZr/lY5i8T0cL6dDF5nZcA9hN//eo=\n"\ "-----END RSA PRIVATE KEY-----\n"; const char pubkey_2_pem[] = "-----BEGIN PUBLIC KEY-----\n"\ "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAosjEKF8XSrvPPGuQYUz9\n"\ "C7wLHvoJ3UAR3vpHFdJa+Z/N9STXYVh67UtCnM7RW+mbO7BuFFmT1WB2jnLnWzaT\n"\ "zeG3hC/A44imv2uU1mtOIgt5iFiF5N+XZs9opsM9jWsUW2J8uFPK1ohdnXxWkFAf\n"\ "7fTJx2fJbO7fgxjM5GIq7s1f7vbPaAaNTH8h4hnGRKuWg+n15FKjYmGUDD5SQj5d\n"\ "yMrbZx/8DbGm0XX//rQvLf3eXP2r3HFFTbWn9HcimZF6csqhmh4g4Rt5Ou2JcZlr\n"\ "krbSo8gyONelZ+kvKKJwAZG0+6Pu4+MgSHdwGe1B2jUmeQ1EAelsIWvLArCH4c/t\n"\ "3JYFdDe9fMfUcof6C3aseMaa8HW1WHhaH8erSnP5klF0NGhhRWB1lYSCWQ/hT9yg\n"\ "vlTJQ8v/2ocFbsZUi8lQ6bL9Lc4piHyMOeK/2ozm13wk9OHGfLxKYEtzs5V6oevg\n"\ "D7daJ0rtS/h21iLguqKt5rwe7Rzy3boMxVlNGFux0RSP5rzjoGQ/vkuuSiWgyygm\n"\ "TIheeOV/wCds6IDR7s2zrgBxQvsUK1isp5YipHVo4AOuPxNcxGnxLkt0DA7AOYCw\n"\ "h2jngVWmjKgzSYG0EHiiLUrXyMFp4xhyTBLSQpWZn65o8jwABqzWtodMirsZsawI\n"\ "GihzXtw00vRRJDnVpE7EQUUCAwEAAQ==\n"\ "-----END PUBLIC KEY-----\n"; const char privkey_2_pem[] = "-----BEGIN RSA PRIVATE KEY-----\n"\ "MIIJJwIBAAKCAgEAosjEKF8XSrvPPGuQYUz9C7wLHvoJ3UAR3vpHFdJa+Z/N9STX\n"\ "YVh67UtCnM7RW+mbO7BuFFmT1WB2jnLnWzaTzeG3hC/A44imv2uU1mtOIgt5iFiF\n"\ "5N+XZs9opsM9jWsUW2J8uFPK1ohdnXxWkFAf7fTJx2fJbO7fgxjM5GIq7s1f7vbP\n"\ "aAaNTH8h4hnGRKuWg+n15FKjYmGUDD5SQj5dyMrbZx/8DbGm0XX//rQvLf3eXP2r\n"\ "3HFFTbWn9HcimZF6csqhmh4g4Rt5Ou2JcZlrkrbSo8gyONelZ+kvKKJwAZG0+6Pu\n"\ "4+MgSHdwGe1B2jUmeQ1EAelsIWvLArCH4c/t3JYFdDe9fMfUcof6C3aseMaa8HW1\n"\ "WHhaH8erSnP5klF0NGhhRWB1lYSCWQ/hT9ygvlTJQ8v/2ocFbsZUi8lQ6bL9Lc4p\n"\ "iHyMOeK/2ozm13wk9OHGfLxKYEtzs5V6oevgD7daJ0rtS/h21iLguqKt5rwe7Rzy\n"\ "3boMxVlNGFux0RSP5rzjoGQ/vkuuSiWgyygmTIheeOV/wCds6IDR7s2zrgBxQvsU\n"\ "K1isp5YipHVo4AOuPxNcxGnxLkt0DA7AOYCwh2jngVWmjKgzSYG0EHiiLUrXyMFp\n"\ "4xhyTBLSQpWZn65o8jwABqzWtodMirsZsawIGihzXtw00vRRJDnVpE7EQUUCAwEA\n"\ "AQKCAgAgeAsoQk9TCML9OjdCMSkH/in5s8Xj67Ok1ib3o02iHM3n58FOGWfrjOds\n"\ "f5Qi0SW1ciuCw5tJJESBhUnAV9KDblI8e4nt2IPqCntViXG8OK21FHMgg7ifOR+W\n"\ "o7rTSlh1Id/epFgBEpIZp4NcpZ64x+q3a2wx3jOW4ot7wljt6OkJsRENnsDwUStg\n"\ "/eMeL1uGVFRHhnQwW2Y8SvH9ZJMwScYZe7e6w4SSowa8txPyCIEh6zcscStxpJor\n"\ "mNe96daSrvgs1XWZcPFtX5PapM9gJDc/k97wl5LWdxyA7mWI6pc+2iP8MIFv8uLZ\n"\ "SQmpjJBv9MVr3DNFN0r27l8sG11GIPMEg9Y01uEK4PZ5oASRNJbdddTOY9KWsYL4\n"\ "IxRV6ZU5ZGe0+EQWBeJnPfr4cyJd59fWC1hA8FUBAjWc8NimmMAaPu76xW+1Cqwo\n"\ "HgBDNzhQur+20eImvD9MZsVJDRAI8nYe+cVN4FaLEogK9iJFcyjv+R1QEofv8/+N\n"\ "qiibNZtff2mF9Y5YLrZQ3b0eNAzcY3VuQyN2iWsqjmXy4F1AP8ZUAyp2S6d3onDx\n"\ "+m4ua6EUB462gxUCxbYh34IbL+OyLX1xuBEetUd7j++7fK+8K5Dkw/RGd2K+D0m8\n"\ "sz4uPzcmxtZ9QbZQSE/JyBM8VzC5Mqy1NyJdBJcsosV62tywwQKCAQEAz/mQXsGj\n"\ "ErtO+6zr+x7YTF9PeUrATsTNtMDASGlbUIGLXcCwr5sE1lcT7zfMXS6xnP0NQnkF\n"\ "HIfgsBPEwMKSnRXNrS127TTvvxU6CzzDO5K1u7q6hzUvuDhj0iZjCSotBC4Dgg39\n"\ "G1J3N5YY4D+2jtS4yWsdIdCuUB/Sel6/1x6jfmkFSgLSxUATycX06/b+WSAFGS40\n"\ "Z5B+n8gfKF2OvuvdTi1uNr8T4EQXMZRQG8hgn7WRcrHuKmanI/jsWs5SiOPArKf7\n"\ "E+m7glguu9D5dqID5KI7kCUGGf+IhcRvcWoF2dZDTFKiyzfWNidVWj7ua5H+wGbl\n"\ "SYDQ+1UO2sFxKwKCAQEAyF/CL5HFZbUmA/igc8FN46vD1OQWlVqFEXk8B5kgK6hX\n"\ "EiA9lRo0/UeG6/o61vgjtI1F1CKjHPqNUaiCmTXVBXqWWxXiyRqcCBGpZPx2zup0\n"\ "CxcCGQUKYPqYBqoazipUtRIasyr8qGoFwJ23am+HLfK4hCa/+JZIn5cmWxdtZbwN\n"\ "I1raal596coHl23fnMpmnnHOf9RVSgLgcrtzZlou+zQq5QTymBoB0AWiCTt6aBB9\n"\ "dcr1M89F32O6WU9lPHdRDXTiZKFfofP+9+5NSKk8EV5/NKbc4q1czl6OQNB1iaij\n"\ "GdW1HbRK6OMn/QPSPrq/4b0vx1378dw1SCynnsp/TwKCAQAjzJCWTvyUD9vzpHtm\n"\ "WZPf6ZQTi9N4e5XxJDC1BjWqsPHdbHq9b5CwmIKc7dzmuU/ndwOeSpifpZ/+jxiS\n"\ "GawFECi8Q5QyFqeiG7RHFOmCSqmvq9/JwFT7f/FmzibEABeJnaYK7/9+gX51+gdE\n"\ "KpNxGJrKSP7VwEcREbk4STD6hZKdAtTfPYcsXvBJDiMvru9vRfy6reJlZVD2jgsW\n"\ "9qvIn28TsuxCzjdMpkvbw+zaK26+bxGfTZNyhwMERSfiJZu9Zn7W+X9VmSkmsakx\n"\ "uTIHwrGq3GYmVMktFnhYlkycmCRzr9cbvym4k17zKWgexbBiodnZIp0bPuo6KRUK\n"\ "gLvLAoIBAGrV29S/NW/AQU+2vd5aP+xXRef54SyrAe5KJL6sX2HHtP0eIZehTIWL\n"\ "IJ4rYjoCPg9jj8rG2Q93a+gRH+kOsdDlBsv0BGJThMQsnpQQpEw457yN/PlYHauN\n"\ "kYdkJTyth1KwH3pOPj6RoRWNQiFG692M3+LeQlcJ0hj9X18MQ7ENrjeelnxGe34u\n"\ "0RNlaufPZx2t8ntnvD3lAMVLuwDkrs4Th5dqpuqAW10N09J2WxKnUC2BFHIWXtv3\n"\ "8YDy+zhdKeMx4g/jlvjj/ps0/RHz4eok51Asc/OMmcIS2mgmfbTzLFt2/cWjvpkj\n"\ "nMujwPLfYbW7yIDVVKMlVWdxH8Jjl7MCggEAbNq+mJjepyGY5mXTlzAcBFY5HqlD\n"\ "x+Ssapo1waswzRhnXKB15zqU+LQfotBBp7pKxWCytwgOmV2toZf/R0Sqinohh52D\n"\ "0mCvTYldTB5tUpKpnhEGZuOZ24BTtrqNAw2X5NRKTflttiVJgfrMCSDIR8NNX4aw\n"\ "GB8iUNZLyn4e5Zjfa7cLCwn7ngINpU1TaCY9gbHSsYcVcpX7ro6Vr93ox6cRtapB\n"\ "WElzAPXDF0YKMiKzA4fTqVtbamVHz64k7xgWVwdWXDbdqOWOtI0weeeHMy2Y9Ktz\n"\ "7eXh0rK0fG1c35qJ+V61oXdyfapmI7uGH2R5J+Q2LDJw/3AMGb6oCsPfdQ==\n"\ "-----END RSA PRIVATE KEY-----\n"; struct _u_request admin_req; struct _u_request user_req; START_TEST(test_oidc_rar_add_plugin_unsigned) { json_t * j_param = json_pack("{sssssss{sssssssssisisisosososososososososososssosos{s{s[ss]s[ss]s[ss]s[ss]s[ss]s[s]}s{s[s]s[ss]s[ss]s[ss]s[s]s[s]}s{s[s]s[s]s[s]s[sss]s[s]s[ss]}s{}}}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", PLUGIN_ISS, "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_NAME, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-device-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "introspection-revocation-allowed", json_true(), "introspection-revocation-allow-target-client", json_true(), "oauth-rar-allowed", json_true(), "rar-types-client-property", "authorization_data_types", "rar-allow-auth-unsigned", json_true(), "rar-allow-auth-unencrypted", json_true(), "rar-types", RAR1, "scopes", SCOPE_1, SCOPE_2, "locations", "https://"RAR1"-1.resource.tld", "https://"RAR1"-2.resource.tld", "actions", "action1-"RAR1, "action2-"RAR1, "datatypes", "type1-"RAR1, "type2-"RAR1, "enriched", ENRICHED1, ENRICHED2, "privileges", PRIVILEGE1, RAR2, "scopes", SCOPE_1, "locations", "https://"RAR2"-1.resource.tld", "https://"RAR2"-2.resource.tld", "actions", "action1-"RAR2, "action2-"RAR2, "datatypes", "type1-"RAR2, "type2-"RAR2, "enriched", ENRICHED1, "privileges", PRIVILEGE1, RAR3, "scopes", "g_admin", "locations", "https://"RAR3".resource.tld", "actions", "action-"RAR3, "datatypes", "type1-"RAR3, "type2-"RAR3, "type3-"RAR3, "enriched", ENRICHED2, "privileges", PRIVILEGE1, PRIVILEGE2, RAR4); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_rar_add_plugin_signed_unencrypted) { json_t * j_parameters = json_pack("{sssssssos{sssssssssssisisisososososososososososisssssssosssosos{s{s[ss]s[ss]s[ss]s[ss]s[ss]s[s]}s{s[s]s[ss]s[ss]s[ss]s[s]s[s]}s{s[s]s[s]s[s]s[sss]s[s]s[ss]}s{}}}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE_RSA, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", privkey_2_pem, "cert", pubkey_2_pem, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "request-parameter-allow-encrypted", json_true(), "request-uri-allow-https-non-secure", json_true(), "request-maximum-exp", CLIENT_AUTH_TOKEN_MAX_AGE, "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "client-jwks-parameter", CLIENT_JWKS_PARAM, "client-jwks_uri-parameter", CLIENT_JWKS_URI_PARAM, "oauth-rar-allowed", json_true(), "rar-types-client-property", "authorization_data_types", "rar-allow-auth-unsigned", json_false(), "rar-allow-auth-unencrypted", json_true(), "rar-types", RAR1, "scopes", SCOPE_1, SCOPE_2, "locations", "https://"RAR1"-1.resource.tld", "https://"RAR1"-2.resource.tld", "actions", "action1-"RAR1, "action2-"RAR1, "datatypes", "type1-"RAR1, "type2-"RAR1, "enriched", ENRICHED1, ENRICHED2, "privileges", PRIVILEGE1, RAR2, "scopes", SCOPE_1, "locations", "https://"RAR2"-1.resource.tld", "https://"RAR2"-2.resource.tld", "actions", "action1-"RAR2, "action2-"RAR2, "datatypes", "type1-"RAR2, "type2-"RAR2, "enriched", ENRICHED1, "privileges", PRIVILEGE1, RAR3, "scopes", "g_admin", "locations", "https://"RAR3".resource.tld", "actions", "action-"RAR3, "datatypes", "type1-"RAR3, "type2-"RAR3, "type3-"RAR3, "enriched", ENRICHED2, "privileges", PRIVILEGE1, PRIVILEGE2, RAR4); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_rar_add_plugin_signed_encrypted) { json_t * j_parameters = json_pack("{sssssssos{sssssssssssisisisososososososososososisssssssosssosos{s{s[ss]s[ss]s[ss]s[ss]s[ss]s[s]}s{s[s]s[ss]s[ss]s[ss]s[s]s[s]}s{s[s]s[s]s[s]s[sss]s[s]s[ss]}s{}}}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE_RSA, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", privkey_2_pem, "cert", pubkey_2_pem, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "request-parameter-allow-encrypted", json_true(), "request-uri-allow-https-non-secure", json_true(), "request-maximum-exp", CLIENT_AUTH_TOKEN_MAX_AGE, "client-pubkey-parameter", CLIENT_PUBKEY_PARAM, "client-jwks-parameter", CLIENT_JWKS_PARAM, "client-jwks_uri-parameter", CLIENT_JWKS_URI_PARAM, "oauth-rar-allowed", json_true(), "rar-types-client-property", "authorization_data_types", "rar-allow-auth-unsigned", json_false(), "rar-allow-auth-unencrypted", json_false(), "rar-types", RAR1, "scopes", SCOPE_1, SCOPE_2, "locations", "https://"RAR1"-1.resource.tld", "https://"RAR1"-2.resource.tld", "actions", "action1-"RAR1, "action2-"RAR1, "datatypes", "type1-"RAR1, "type2-"RAR1, "enriched", ENRICHED1, ENRICHED2, "privileges", PRIVILEGE1, RAR2, "scopes", SCOPE_1, "locations", "https://"RAR2"-1.resource.tld", "https://"RAR2"-2.resource.tld", "actions", "action1-"RAR2, "action2-"RAR2, "datatypes", "type1-"RAR2, "type2-"RAR2, "enriched", ENRICHED1, "privileges", PRIVILEGE1, RAR3, "scopes", "g_admin", "locations", "https://"RAR3".resource.tld", "actions", "action-"RAR3, "datatypes", "type1-"RAR3, "type2-"RAR3, "type3-"RAR3, "enriched", ENRICHED2, "privileges", PRIVILEGE1, PRIVILEGE2, RAR4); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_rar_delete_plugin) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_rar_send_invalid_requests) { // type invalid json_t * j_rar = json_pack("[{sss[s]s[s]s[s]s{s[]}s[s]}]", "type", "error", "locations", "https://"RAR1"-1.resource.tld", "actions", "action1-"RAR1, "datatypes", "type1-"RAR1, "access", ENRICHED1, "privileges", PRIVILEGE1); char * str_rar, * rar_encoded, * url; str_rar = json_dumps(j_rar, JSON_COMPACT); rar_encoded = ulfius_url_encode(str_rar); url = msprintf(SERVER_URI"/"PLUGIN_NAME"/auth?response_type="RESPONSE_TYPE"&g_continue&client_id="CLIENT"&redirect_uri=..%%2f..%%2ftest-oidc.html?param=client3&state=xyzabcd&nonce=nonce1234&scope="SCOPE_LIST"&authorization_details=%s", rar_encoded); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "invalid_request"), 1); o_free(url); o_free(str_rar); o_free(rar_encoded); json_decref(j_rar); // locations invalid j_rar = json_pack("[{sss[s]s[s]s[s]s{s[]}s[s]}]", "type", RAR1, "locations", "https://"RAR1"-1.resource.tld-error", "actions", "action1-"RAR1, "datatypes", "type1-"RAR1, "access", ENRICHED1, "privileges", PRIVILEGE1); str_rar = json_dumps(j_rar, JSON_COMPACT); rar_encoded = ulfius_url_encode(str_rar); url = msprintf(SERVER_URI"/"PLUGIN_NAME"/auth?response_type="RESPONSE_TYPE"&g_continue&client_id="CLIENT"&redirect_uri=..%%2f..%%2ftest-oidc.html?param=client3&state=xyzabcd&nonce=nonce1234&scope="SCOPE_LIST"&authorization_details=%s", rar_encoded); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "invalid_request"), 1); o_free(url); o_free(str_rar); o_free(rar_encoded); json_decref(j_rar); // actions invalid j_rar = json_pack("[{sss[s]s[s]s[s]s{s[]}s[s]}]", "type", RAR1, "locations", "https://"RAR1"-1.resource.tld", "actions", "action1-error-"RAR1, "datatypes", "type1-"RAR1, "access", ENRICHED1, "privileges", PRIVILEGE1); str_rar = json_dumps(j_rar, JSON_COMPACT); rar_encoded = ulfius_url_encode(str_rar); url = msprintf(SERVER_URI"/"PLUGIN_NAME"/auth?response_type="RESPONSE_TYPE"&g_continue&client_id="CLIENT"&redirect_uri=..%%2f..%%2ftest-oidc.html?param=client3&state=xyzabcd&nonce=nonce1234&scope="SCOPE_LIST"&authorization_details=%s", rar_encoded); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "invalid_request"), 1); o_free(url); o_free(str_rar); o_free(rar_encoded); json_decref(j_rar); // datatypes invalid j_rar = json_pack("[{sss[s]s[s]s[s]s{s[]}s[s]}]", "type", RAR1, "locations", "https://"RAR1"-1.resource.tld", "actions", "action1-"RAR1, "datatypes", "type1-error-"RAR1, "access", ENRICHED1, "privileges", PRIVILEGE1); str_rar = json_dumps(j_rar, JSON_COMPACT); rar_encoded = ulfius_url_encode(str_rar); url = msprintf(SERVER_URI"/"PLUGIN_NAME"/auth?response_type="RESPONSE_TYPE"&g_continue&client_id="CLIENT"&redirect_uri=..%%2f..%%2ftest-oidc.html?param=client3&state=xyzabcd&nonce=nonce1234&scope="SCOPE_LIST"&authorization_details=%s", rar_encoded); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "invalid_request"), 1); o_free(url); o_free(str_rar); o_free(rar_encoded); json_decref(j_rar); // access invalid j_rar = json_pack("[{sss[s]s[s]s[s]s{s[]}s[s]}]", "type", RAR1, "locations", "https://"RAR1"-1.resource.tld", "actions", "action1-"RAR1, "datatypes", "type1-"RAR1, "access", "error", "privileges", PRIVILEGE1); str_rar = json_dumps(j_rar, JSON_COMPACT); rar_encoded = ulfius_url_encode(str_rar); url = msprintf(SERVER_URI"/"PLUGIN_NAME"/auth?response_type="RESPONSE_TYPE"&g_continue&client_id="CLIENT"&redirect_uri=..%%2f..%%2ftest-oidc.html?param=client3&state=xyzabcd&nonce=nonce1234&scope="SCOPE_LIST"&authorization_details=%s", rar_encoded); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "invalid_request"), 1); o_free(url); o_free(str_rar); o_free(rar_encoded); json_decref(j_rar); // privileges invalid j_rar = json_pack("[{sss[s]s[s]s[s]s{s[]}s[s]}]", "type", RAR1, "locations", "https://"RAR1"-1.resource.tld", "actions", "action1-"RAR1, "datatypes", "type1-"RAR1, "access", ENRICHED1, "privileges", "error"); str_rar = json_dumps(j_rar, JSON_COMPACT); rar_encoded = ulfius_url_encode(str_rar); url = msprintf(SERVER_URI"/"PLUGIN_NAME"/auth?response_type="RESPONSE_TYPE"&g_continue&client_id="CLIENT"&redirect_uri=..%%2f..%%2ftest-oidc.html?param=client3&state=xyzabcd&nonce=nonce1234&scope="SCOPE_LIST"&authorization_details=%s", rar_encoded); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "invalid_request"), 1); o_free(url); o_free(str_rar); o_free(rar_encoded); json_decref(j_rar); j_rar = json_pack("[{sss[s]s[s]s[s]s{s[]}s[s]}]", "type", RAR3, "locations", "https://"RAR3".resource.tld", "actions", "action-"RAR3, "datatypes", "type1-"RAR3, "access", ENRICHED2, "privileges", PRIVILEGE1); str_rar = json_dumps(j_rar, JSON_COMPACT); rar_encoded = ulfius_url_encode(str_rar); url = msprintf(SERVER_URI"/"PLUGIN_NAME"/auth?response_type="RESPONSE_TYPE"&g_continue&client_id="CLIENT"&redirect_uri=..%%2f..%%2ftest-oidc.html?param=client3&state=xyzabcd&nonce=nonce1234&scope="SCOPE_LIST"&authorization_details=%s", rar_encoded); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "invalid_request"), 1); o_free(url); o_free(str_rar); o_free(rar_encoded); json_decref(j_rar); j_rar = json_pack("[{sss[s]s[s]s[s]s{s[]}s[s]}]", "type", RAR1, "locations", "https://"RAR1"-1.resource.tld", "actions", "action1-"RAR1, "datatypes", "type1-"RAR1, "access", ENRICHED1, "privileges", PRIVILEGE1); str_rar = json_dumps(j_rar, JSON_COMPACT); rar_encoded = ulfius_url_encode(str_rar); url = msprintf(SERVER_URI"/"PLUGIN_NAME"/auth?response_type="RESPONSE_TYPE"&g_continue&client_id="CLIENT"&redirect_uri=..%%2f..%%2ftest-oidc.html?param=client3&state=xyzabcd&nonce=nonce1234&scope="SCOPE_3"&authorization_details=%s", rar_encoded); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "invalid_request"), 1); o_free(url); o_free(str_rar); o_free(rar_encoded); json_decref(j_rar); j_rar = json_pack("[{ss}]", "type", RAR4); str_rar = json_dumps(j_rar, JSON_COMPACT); rar_encoded = ulfius_url_encode(str_rar); url = msprintf(SERVER_URI"/"PLUGIN_NAME"/auth?response_type="RESPONSE_TYPE"&g_continue&client_id="CLIENT"&redirect_uri=..%%2f..%%2ftest-oidc.html?param=client3&state=xyzabcd&nonce=nonce1234&scope="SCOPE_LIST"&authorization_details=%s", rar_encoded); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "invalid_request"), 1); o_free(url); o_free(str_rar); o_free(rar_encoded); json_decref(j_rar); } END_TEST START_TEST(test_oidc_rar_consent_error) { struct _u_response resp; ulfius_init_response(&resp); ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI"/"PLUGIN_NAME"/rar/"CLIENT"/"RAR3, U_OPT_HTTP_VERB, "GET", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 404); ulfius_clean_response(&resp); ulfius_init_response(&resp); ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI"/"PLUGIN_NAME"/rar/"CLIENT"/"RAR3"/1", U_OPT_HTTP_VERB, "PUT", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 404); ulfius_clean_response(&resp); ulfius_init_response(&resp); ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI"/"PLUGIN_NAME"/rar/"CLIENT"/error", U_OPT_HTTP_VERB, "GET", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 404); ulfius_clean_response(&resp); ulfius_init_response(&resp); ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI"/"PLUGIN_NAME"/rar/"CLIENT"/error/1", U_OPT_HTTP_VERB, "PUT", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 404); ulfius_clean_response(&resp); ulfius_init_response(&resp); ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI"/"PLUGIN_NAME"/rar/"CLIENT"/error", U_OPT_HTTP_VERB, "DELETE", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 404); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_rar_set_consent_ok) { json_t * j_body, * j_verif; struct _u_response resp; ulfius_init_response(&resp); ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI"/"PLUGIN_NAME"/rar/"CLIENT"/"RAR1, U_OPT_HTTP_VERB, "GET", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); j_verif = json_pack("{so}", "consent", json_false()); ck_assert_ptr_ne(NULL, json_search(j_body, j_verif)); json_decref(j_verif); json_decref(j_body); ulfius_clean_response(&resp); ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI"/"PLUGIN_NAME"/rar/"CLIENT"/"RAR1"/1", U_OPT_HTTP_VERB, "PUT", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, NULL), U_OK); ulfius_init_response(&resp); ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI"/"PLUGIN_NAME"/rar/"CLIENT"/"RAR1, U_OPT_HTTP_VERB, "GET", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); j_verif = json_pack("{so}", "consent", json_true()); ck_assert_ptr_ne(NULL, json_search(j_body, j_verif)); json_decref(j_verif); json_decref(j_body); ulfius_clean_response(&resp); ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI"/"PLUGIN_NAME"/rar/"CLIENT"/"RAR1, U_OPT_HTTP_VERB, "DELETE", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, NULL), U_OK); } END_TEST START_TEST(test_oidc_rar_need_consent) { json_t * j_rar = json_pack("[{sss[s]s[s]s[s]s{s[]}}]", "type", RAR1, "locations", "https://"RAR1"-1.resource.tld", "actions", "action1-"RAR1, "datatypes", "type1-"RAR1, "access", ENRICHED1); char * str_rar, * rar_encoded, * url; str_rar = json_dumps(j_rar, JSON_COMPACT); rar_encoded = ulfius_url_encode(str_rar); url = msprintf(SERVER_URI"/"PLUGIN_NAME"/auth?response_type="RESPONSE_TYPE"&g_continue&client_id="CLIENT"&redirect_uri=..%%2f..%%2ftest-oidc.html?param=client3&state=xyzabcd&nonce=nonce1234&scope="SCOPE_LIST"&authorization_details=%s", rar_encoded); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "consent"), 1); o_free(url); o_free(str_rar); o_free(rar_encoded); json_decref(j_rar); } END_TEST START_TEST(test_oidc_rar_set_consent) { ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI"/"PLUGIN_NAME"/rar/"CLIENT"/"RAR1"/1", U_OPT_HTTP_VERB, "PUT", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, NULL), U_OK); ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI"/"PLUGIN_NAME"/rar/"CLIENT"/"RAR2"/1", U_OPT_HTTP_VERB, "PUT", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, NULL), U_OK); } END_TEST START_TEST(test_oidc_rar_delete_consent) { ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI"/"PLUGIN_NAME"/rar/"CLIENT"/"RAR1, U_OPT_HTTP_VERB, "DELETE", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, NULL), U_OK); ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI"/"PLUGIN_NAME"/rar/"CLIENT"/"RAR2, U_OPT_HTTP_VERB, "DELETE", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, NULL), U_OK); } END_TEST START_TEST(test_oidc_rar_set_consent_client_pubkey) { ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI"/"PLUGIN_NAME"/rar/"CLIENT_PUBKEY_ID"/"RAR1"/1", U_OPT_HTTP_VERB, "PUT", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, NULL), U_OK); ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI"/"PLUGIN_NAME"/rar/"CLIENT_PUBKEY_ID"/"RAR2"/1", U_OPT_HTTP_VERB, "PUT", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, NULL), U_OK); } END_TEST START_TEST(test_oidc_rar_delete_consent_client_pubkey) { ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI"/"PLUGIN_NAME"/rar/"CLIENT_PUBKEY_ID"/"RAR1, U_OPT_HTTP_VERB, "DELETE", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, NULL), U_OK); ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, SERVER_URI"/"PLUGIN_NAME"/rar/"CLIENT_PUBKEY_ID"/"RAR2, U_OPT_HTTP_VERB, "DELETE", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, NULL), U_OK); } END_TEST START_TEST(test_oidc_rar_at_code_refresh_introspect_ok) { struct _u_request req; struct _u_response resp; char * str_rar, * rar_encoded, * url, * code, * access_token; json_t * j_rar, * j_jwt_content, * j_body; jwt_t * jwt; j_rar = json_pack("[{sss[s]s[s]s[s]s{s[]}}]", "type", RAR1, "locations", "https://"RAR1"-1.resource.tld", "actions", "action1-"RAR1, "datatypes", "type1-"RAR1, "access", ENRICHED1); str_rar = json_dumps(j_rar, JSON_COMPACT); rar_encoded = ulfius_url_encode(str_rar); url = msprintf(SERVER_URI"/"PLUGIN_NAME"/auth?response_type="RESPONSE_TYPE"&g_continue&client_id="CLIENT"&redirect_uri=..%%2f..%%2ftest-oidc.html?param=client3&state=xyzabcd&nonce=nonce1234&scope="SCOPE_LIST"&authorization_details=%s", rar_encoded); ulfius_init_response(&resp); ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, url, U_OPT_HTTP_VERB, "GET", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); o_free(url); o_free(str_rar); o_free(rar_encoded); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } access_token = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "access_token=")+strlen("access_token=")); if (strchr(access_token, '&') != NULL) { *strchr(access_token, '&') = '\0'; } r_jwt_init(&jwt); r_jwt_parse(jwt, access_token, 0); json_object_del(json_array_get(j_rar, 0), "access"); j_jwt_content = r_jwt_get_full_claims_json_t(jwt); ck_assert_ptr_ne(NULL, json_object_get(j_jwt_content, "authorization_details")); ck_assert_ptr_ne(NULL, json_search(json_object_get(j_jwt_content, "authorization_details"), json_array_get(j_rar, 0))); ck_assert_int_gt(json_string_length(json_object_get(json_object_get(json_array_get(json_object_get(j_jwt_content, "authorization_details"), 0), "access"), ENRICHED1)), 0); ulfius_clean_response(&resp); r_jwt_free(jwt); ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI"/"PLUGIN_NAME"/token", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_PASSWORD, U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT, U_OPT_POST_BODY_PARAMETER, "redirect_uri", "../../test-oidc.html?param=client3", U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, json_search(json_object_get(j_jwt_content, "authorization_details"), json_array_get(j_rar, 0))); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_jwt_content); r_jwt_init(&jwt); r_jwt_parse(jwt, json_string_value(json_object_get(j_body, "access_token")), 0); j_jwt_content = r_jwt_get_full_claims_json_t(jwt); ck_assert_ptr_ne(NULL, json_object_get(j_jwt_content, "authorization_details")); ck_assert_ptr_ne(NULL, json_search(json_object_get(j_jwt_content, "authorization_details"), json_array_get(j_rar, 0))); ck_assert_int_gt(json_string_length(json_object_get(json_object_get(json_array_get(json_object_get(j_jwt_content, "authorization_details"), 0), "access"), ENRICHED1)), 0); r_jwt_free(jwt); ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI"/"PLUGIN_NAME"/token", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_PASSWORD, U_OPT_POST_BODY_PARAMETER, "grant_type", "refresh_token", U_OPT_POST_BODY_PARAMETER, "refresh_token", json_string_value(json_object_get(j_body, "refresh_token")), U_OPT_NONE); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, json_search(json_object_get(j_jwt_content, "authorization_details"), json_array_get(j_rar, 0))); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_jwt_content); r_jwt_init(&jwt); r_jwt_parse(jwt, json_string_value(json_object_get(j_body, "access_token")), 0); j_jwt_content = r_jwt_get_full_claims_json_t(jwt); ck_assert_ptr_ne(NULL, json_object_get(j_jwt_content, "authorization_details")); ck_assert_ptr_ne(NULL, json_search(json_object_get(j_jwt_content, "authorization_details"), json_array_get(j_rar, 0))); ck_assert_int_gt(json_string_length(json_object_get(json_object_get(json_array_get(json_object_get(j_jwt_content, "authorization_details"), 0), "access"), ENRICHED1)), 0); r_jwt_free(jwt); json_decref(j_jwt_content); json_decref(j_body); ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI"/"PLUGIN_NAME"/introspect", U_OPT_HTTP_VERB, "POST", U_OPT_AUTH_BASIC_USER, CLIENT, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_PASSWORD, U_OPT_POST_BODY_PARAMETER, "token", access_token, U_OPT_POST_BODY_PARAMETER, "token_type_hint", "access_token", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(NULL, j_body = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, json_search(json_object_get(j_body, "authorization_details"), json_array_get(j_rar, 0))); ulfius_clean_request(&req); ulfius_clean_response(&resp); o_free(code); o_free(access_token); json_decref(j_rar); json_decref(j_body); } END_TEST START_TEST(test_oidc_rar_at_reduced_ok) { struct _u_response resp; char * str_rar, * rar_encoded, * url, * access_token; json_t * j_rar, * j_jwt_content; jwt_t * jwt; j_rar = json_pack("[{sss[s]}{sss[s]}]", "type", RAR1, "actions", "action1-"RAR1, "type", RAR3, "actions", "action-"RAR3); str_rar = json_dumps(j_rar, JSON_COMPACT); rar_encoded = ulfius_url_encode(str_rar); url = msprintf(SERVER_URI"/"PLUGIN_NAME"/auth?response_type="RESPONSE_TYPE"&g_continue&client_id="CLIENT"&redirect_uri=..%%2f..%%2ftest-oidc.html?param=client3&state=xyzabcd&nonce=nonce1234&scope="SCOPE_LIST" "SCOPE_3"&authorization_details=%s", rar_encoded); ulfius_init_response(&resp); ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, url, U_OPT_HTTP_VERB, "GET", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); o_free(url); o_free(str_rar); o_free(rar_encoded); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); access_token = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "access_token=")+strlen("access_token=")); if (strchr(access_token, '&') != NULL) { *strchr(access_token, '&') = '\0'; } r_jwt_init(&jwt); r_jwt_parse(jwt, access_token, 0); j_jwt_content = r_jwt_get_full_claims_json_t(jwt); ck_assert_ptr_ne(NULL, json_object_get(j_jwt_content, "authorization_details")); ck_assert_int_eq(1, json_array_size(json_object_get(j_jwt_content, "authorization_details"))); ck_assert_ptr_ne(NULL, json_search(json_object_get(j_jwt_content, "authorization_details"), json_array_get(j_rar, 0))); ck_assert_ptr_eq(json_object_get(json_object_get(json_array_get(json_object_get(j_jwt_content, "authorization_details"), 0), "access"), ENRICHED1), NULL); ck_assert_ptr_eq(json_object_get(json_array_get(json_object_get(j_jwt_content, "authorization_details"), 0), "access"), NULL); ulfius_clean_response(&resp); r_jwt_free(jwt); json_decref(j_jwt_content); json_decref(j_rar); o_free(access_token); } END_TEST START_TEST(test_oidc_rar_device_verification_valid) { struct _u_request req; struct _u_response resp; json_t * j_resp, * j_grant, * j_rar, * j_jwt_content; const char * redirect_uri, * code, * device_code; jwt_t * jwt; char * str_rar; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); j_rar = json_pack("[{sss[s]s[s]s[s]}]", "type", RAR1, "locations", "https://"RAR1"-1.resource.tld", "actions", "action1-"RAR1, "datatypes", "type1-"RAR1); str_rar = json_dumps(j_rar, JSON_COMPACT); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/device_authorization/", U_OPT_POST_BODY_PARAMETER, "grant_type", "device_authorization", U_OPT_POST_BODY_PARAMETER, "client_id", CLIENT, U_OPT_POST_BODY_PARAMETER, "scope", SCOPE_LIST, U_OPT_POST_BODY_PARAMETER, "authorization_details", str_rar, U_OPT_AUTH_BASIC_USER, CLIENT, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_PASSWORD, U_OPT_NONE); o_free(str_rar); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "device_code"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "user_code"), NULL); ck_assert_ptr_ne(code = json_string_value(json_object_get(j_resp, "user_code")), NULL); ck_assert_ptr_ne(device_code = json_string_value(json_object_get(j_resp, "device_code")), NULL); ck_assert_str_eq(json_string_value(json_object_get(j_resp, "verification_uri")), "http://localhost:4593/api/" PLUGIN_NAME "/device"); ck_assert_ptr_ne(json_object_get(j_resp, "verification_uri_complete"), NULL); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "expires_in")), 600); ck_assert_int_eq(json_integer_value(json_object_get(j_resp, "interval")), 5); ulfius_clean_request(&req); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", SCOPE_LIST); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); o_free(user_req.http_url); user_req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/device?code=%s&g_continue", code); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); ck_assert_ptr_ne(redirect_uri = u_map_get(resp.map_header, "Location"), NULL); ck_assert_ptr_ne(o_strstr(redirect_uri, "prompt=deviceComplete"), NULL); ulfius_clean_response(&resp); j_grant = json_pack("{ss}", "scope", ""); run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT, NULL, NULL, j_grant, NULL, 200, NULL, NULL, NULL); json_decref(j_grant); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token/"); req.http_verb = o_strdup("POST"); u_map_put(req.map_post_body, "grant_type", "urn:ietf:params:oauth:grant-type:device_code"); u_map_put(req.map_post_body, "client_id", CLIENT); u_map_put(req.map_post_body, "device_code", device_code); req.auth_basic_user = o_strdup(CLIENT); req.auth_basic_password = o_strdup(CLIENT_PASSWORD); json_decref(j_resp); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(200, resp.status); ck_assert_ptr_ne(j_resp = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "authorization_details"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "access_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "refresh_token"), NULL); ck_assert_ptr_ne(json_object_get(j_resp, "id_token"), NULL); ck_assert_int_eq(1, json_array_size(json_object_get(j_resp, "authorization_details"))); ck_assert_ptr_ne(NULL, json_search(json_object_get(j_resp, "authorization_details"), json_array_get(j_rar, 0))); ck_assert_ptr_eq(json_object_get(json_object_get(json_array_get(json_object_get(j_resp, "authorization_details"), 0), "access"), ENRICHED1), NULL); ck_assert_ptr_eq(json_object_get(json_array_get(json_object_get(j_resp, "authorization_details"), 0), "access"), NULL); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parse(jwt, json_string_value(json_object_get(j_resp, "access_token")), 0), RHN_OK); ck_assert_str_eq(SCOPE_LIST, r_jwt_get_claim_str_value(jwt, "aud")); j_jwt_content = r_jwt_get_full_claims_json_t(jwt); ck_assert_ptr_ne(NULL, json_object_get(j_jwt_content, "authorization_details")); ck_assert_int_eq(1, json_array_size(json_object_get(j_jwt_content, "authorization_details"))); ck_assert_ptr_ne(NULL, json_search(json_object_get(j_jwt_content, "authorization_details"), json_array_get(j_rar, 0))); ck_assert_ptr_eq(json_object_get(json_object_get(json_array_get(json_object_get(j_jwt_content, "authorization_details"), 0), "access"), ENRICHED1), NULL); ck_assert_ptr_eq(json_object_get(json_array_get(json_object_get(j_jwt_content, "authorization_details"), 0), "access"), NULL); r_jwt_free(jwt); json_decref(j_resp); json_decref(j_rar); json_decref(j_jwt_content); ulfius_clean_request(&req); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_rar_add_client_pubkey) { json_t * j_client = json_pack("{ss ss so s[s] s[ssss] s[s] ss so s[sss]}", "client_id", CLIENT_PUBKEY_ID, "name", CLIENT_PUBKEY_NAME, "confidential", json_true(), "redirect_uri", CLIENT_PUBKEY_REDIRECT, "authorization_type", "code", "token", "id_token", "client_credentials", "scope", CLIENT_SCOPE, CLIENT_PUBKEY_PARAM, pubkey_1_pem, "enabled", json_true(), "authorization_data_types", RAR1, RAR2, RAR3); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_client, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_client); json_t * j_param = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_PUBKEY_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_rar_delete_client_pubkey) { json_t * j_param = json_pack("{ss}", "scope", ""); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_PUBKEY_ID, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_PUBKEY_ID, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_rar_at_unsigned_no_rar) { struct _u_response resp; json_t * j_rar = json_pack("[{sss[s]s[s]s[s]s{s[]}}]", "type", RAR1, "locations", "https://"RAR1"-1.resource.tld", "actions", "action1-"RAR1, "datatypes", "type1-"RAR1, "access", ENRICHED1), * j_jwt_content; char * str_rar, * rar_encoded, * url, * access_token; jwt_t * jwt; str_rar = json_dumps(j_rar, JSON_COMPACT); rar_encoded = ulfius_url_encode(str_rar); url = msprintf(SERVER_URI"/"PLUGIN_NAME"/auth?response_type="RESPONSE_TYPE"&g_continue&client_id="CLIENT_PUBKEY_ID"&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope="SCOPE_LIST"&authorization_details=%s", CLIENT_PUBKEY_REDIRECT_ESCAPED, rar_encoded); ulfius_init_response(&resp); ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, url, U_OPT_HTTP_VERB, "GET", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); o_free(url); o_free(str_rar); o_free(rar_encoded); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); access_token = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "access_token=")+strlen("access_token=")); if (strchr(access_token, '&') != NULL) { *strchr(access_token, '&') = '\0'; } r_jwt_init(&jwt); r_jwt_parse(jwt, access_token, 0); j_jwt_content = r_jwt_get_full_claims_json_t(jwt); ck_assert_ptr_eq(NULL, json_object_get(j_jwt_content, "authorization_details")); ulfius_clean_response(&resp); r_jwt_free(jwt); json_decref(j_jwt_content); json_decref(j_rar); o_free(access_token); } END_TEST START_TEST(test_oidc_rar_at_signed_ok) { struct _u_response resp; jwt_t * jwt_request = NULL, * jwt; json_t * j_rar, * j_jwt_content; char * url, * request, * access_token; r_jwt_init(&jwt_request); j_rar = json_pack("[{sss[s]s[s]s[s]s{s[]}}]", "type", RAR1, "locations", "https://"RAR1"-1.resource.tld", "actions", "action1-"RAR1, "datatypes", "type1-"RAR1, "access", ENRICHED1); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); ck_assert_int_eq(r_jwt_set_claim_json_t_value(jwt_request, "authorization_details", j_rar), RHN_OK); ck_assert_ptr_ne(NULL, request = r_jwt_serialize_signed(jwt_request, NULL, 0)); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI"/"PLUGIN_NAME"/auth?g_continue&request=%s", request); ulfius_init_response(&resp); ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, url, U_OPT_HTTP_VERB, "GET", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); o_free(url); access_token = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "access_token=")+strlen("access_token=")); if (strchr(access_token, '&') != NULL) { *strchr(access_token, '&') = '\0'; } r_jwt_init(&jwt); r_jwt_parse(jwt, access_token, 0); j_jwt_content = r_jwt_get_full_claims_json_t(jwt); json_object_del(json_array_get(j_rar, 0), "access"); ck_assert_ptr_ne(NULL, json_object_get(j_jwt_content, "authorization_details")); ck_assert_int_eq(1, json_array_size(json_object_get(j_jwt_content, "authorization_details"))); ck_assert_ptr_ne(NULL, json_search(json_object_get(j_jwt_content, "authorization_details"), json_array_get(j_rar, 0))); ck_assert_ptr_ne(json_object_get(json_object_get(json_array_get(json_object_get(j_jwt_content, "authorization_details"), 0), "access"), ENRICHED1), NULL); ulfius_clean_response(&resp); r_jwt_free(jwt); json_decref(j_jwt_content); json_decref(j_rar); o_free(request); o_free(access_token); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_rar_at_encrypted_ok) { struct _u_response resp; jwt_t * jwt_request = NULL, * jwt; json_t * j_rar, * j_jwt_content; char * url, * request, * access_token; r_jwt_init(&jwt_request); j_rar = json_pack("[{sss[s]s[s]s[s]s{s[]}}]", "type", RAR1, "locations", "https://"RAR1"-1.resource.tld", "actions", "action1-"RAR1, "datatypes", "type1-"RAR1, "access", ENRICHED1); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); r_jwt_set_enc_alg(jwt_request, R_JWA_ALG_RSA1_5); r_jwt_set_enc(jwt_request, R_JWA_ENC_A128CBC); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); ck_assert_int_eq(r_jwt_add_enc_keys_pem_der(jwt_request, R_FORMAT_PEM, NULL, 0, (unsigned char *)pubkey_2_pem, o_strlen(pubkey_2_pem)), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); ck_assert_int_eq(r_jwt_set_claim_json_t_value(jwt_request, "authorization_details", j_rar), RHN_OK); ck_assert_ptr_ne(NULL, request = r_jwt_serialize_nested(jwt_request, R_JWT_TYPE_NESTED_SIGN_THEN_ENCRYPT, NULL, 0, NULL, 0)); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI"/"PLUGIN_NAME"/auth?g_continue&request=%s", request); ulfius_init_response(&resp); ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, url, U_OPT_HTTP_VERB, "GET", U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(302, resp.status); o_free(url); access_token = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "access_token=")+strlen("access_token=")); if (strchr(access_token, '&') != NULL) { *strchr(access_token, '&') = '\0'; } r_jwt_init(&jwt); r_jwt_parse(jwt, access_token, 0); j_jwt_content = r_jwt_get_full_claims_json_t(jwt); json_object_del(json_array_get(j_rar, 0), "access"); ck_assert_ptr_ne(NULL, json_object_get(j_jwt_content, "authorization_details")); ck_assert_int_eq(1, json_array_size(json_object_get(j_jwt_content, "authorization_details"))); ck_assert_ptr_ne(NULL, json_search(json_object_get(j_jwt_content, "authorization_details"), json_array_get(j_rar, 0))); ck_assert_ptr_ne(json_object_get(json_object_get(json_array_get(json_object_get(j_jwt_content, "authorization_details"), 0), "access"), ENRICHED1), NULL); ulfius_clean_response(&resp); r_jwt_free(jwt); json_decref(j_jwt_content); json_decref(j_rar); o_free(request); o_free(access_token); r_jwt_free(jwt_request); } END_TEST START_TEST(test_oidc_rar_at_signed_error) { jwt_t * jwt_request = NULL; json_t * j_rar; char * url, * request; r_jwt_init(&jwt_request); j_rar = json_pack("[{sss[s]s[s]s[s]s{s[]}}]", "type", RAR1, "locations", "https://"RAR1"-1.resource.tld", "actions", "action1-"RAR1, "datatypes", "type1-"RAR1, "access", ENRICHED1); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_json_str(jwt_request, privkey_1_jwk, NULL), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); ck_assert_int_eq(r_jwt_set_claim_json_t_value(jwt_request, "authorization_details", j_rar), RHN_OK); ck_assert_ptr_ne(NULL, request = r_jwt_serialize_signed(jwt_request, NULL, 0)); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI"/"PLUGIN_NAME"/auth?g_continue&request=%s", request); ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, url, U_OPT_HTTP_VERB, "GET", U_OPT_NONE); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); json_decref(j_rar); } END_TEST START_TEST(test_oidc_rar_at_unsigned_error) { jwt_t * jwt_request = NULL; json_t * j_rar; char * url, * request; r_jwt_init(&jwt_request); j_rar = json_pack("[{sss[s]s[s]s[s]s{s[]}}]", "type", RAR1, "locations", "https://"RAR1"-1.resource.tld", "actions", "action1-"RAR1, "datatypes", "type1-"RAR1, "access", ENRICHED1); ck_assert_ptr_ne(jwt_request, NULL); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_request, R_JWA_ALG_NONE), RHN_OK); r_jwt_set_claim_str_value(jwt_request, "aud", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "response_type", RESPONSE_TYPE); r_jwt_set_claim_str_value(jwt_request, "client_id", CLIENT_PUBKEY_ID); r_jwt_set_claim_str_value(jwt_request, "redirect_uri", CLIENT_PUBKEY_REDIRECT); r_jwt_set_claim_str_value(jwt_request, "scope", SCOPE_LIST); r_jwt_set_claim_str_value(jwt_request, "state", "xyzabcd"); r_jwt_set_claim_str_value(jwt_request, "nonce", "nonce1234"); ck_assert_int_eq(r_jwt_set_claim_json_t_value(jwt_request, "authorization_details", j_rar), RHN_OK); ck_assert_ptr_ne(NULL, request = r_jwt_serialize_signed_unsecure(jwt_request, NULL, 0)); ck_assert_ptr_ne(request, NULL); url = msprintf(SERVER_URI"/"PLUGIN_NAME"/auth?g_continue&request=%s", request); ulfius_set_request_properties(&user_req, U_OPT_HTTP_URL, url, U_OPT_HTTP_VERB, "GET", U_OPT_NONE); ck_assert_int_eq(run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); o_free(url); o_free(request); r_jwt_free(jwt_request); json_decref(j_rar); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc rich authentication request"); tc_core = tcase_create("test_oidc_rich_authentication_request"); tcase_add_test(tc_core, test_oidc_rar_add_plugin_unsigned); tcase_add_test(tc_core, test_oidc_rar_send_invalid_requests); tcase_add_test(tc_core, test_oidc_rar_consent_error); tcase_add_test(tc_core, test_oidc_rar_set_consent_ok); tcase_add_test(tc_core, test_oidc_rar_need_consent); tcase_add_test(tc_core, test_oidc_rar_set_consent); tcase_add_test(tc_core, test_oidc_rar_at_code_refresh_introspect_ok); tcase_add_test(tc_core, test_oidc_rar_at_reduced_ok); tcase_add_test(tc_core, test_oidc_rar_device_verification_valid); tcase_add_test(tc_core, test_oidc_rar_delete_consent); tcase_add_test(tc_core, test_oidc_rar_delete_plugin); tcase_add_test(tc_core, test_oidc_rar_add_plugin_signed_unencrypted); tcase_add_test(tc_core, test_oidc_rar_add_client_pubkey); tcase_add_test(tc_core, test_oidc_rar_set_consent_client_pubkey); tcase_add_test(tc_core, test_oidc_rar_at_unsigned_no_rar); tcase_add_test(tc_core, test_oidc_rar_at_unsigned_error); tcase_add_test(tc_core, test_oidc_rar_at_signed_ok); tcase_add_test(tc_core, test_oidc_rar_at_encrypted_ok); tcase_add_test(tc_core, test_oidc_rar_delete_consent_client_pubkey); tcase_add_test(tc_core, test_oidc_rar_delete_client_pubkey); tcase_add_test(tc_core, test_oidc_rar_delete_plugin); tcase_add_test(tc_core, test_oidc_rar_add_plugin_signed_encrypted); tcase_add_test(tc_core, test_oidc_rar_add_client_pubkey); tcase_add_test(tc_core, test_oidc_rar_set_consent_client_pubkey); tcase_add_test(tc_core, test_oidc_rar_at_unsigned_no_rar); tcase_add_test(tc_core, test_oidc_rar_at_unsigned_error); tcase_add_test(tc_core, test_oidc_rar_at_signed_error); tcase_add_test(tc_core, test_oidc_rar_at_encrypted_ok); tcase_add_test(tc_core, test_oidc_rar_delete_consent_client_pubkey); tcase_add_test(tc_core, test_oidc_rar_delete_client_pubkey); tcase_add_test(tc_core, test_oidc_rar_delete_plugin); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, client_req; struct _u_response auth_resp, scope_resp, client_resp; int res, do_test = 0; json_t * j_body, * j_client; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); ulfius_init_request(&user_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&scope_req); ulfius_init_request(&client_req); ulfius_init_response(&auth_resp); ulfius_init_response(&scope_resp); ulfius_init_response(&client_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); u_map_put(client_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); ulfius_set_request_properties(&client_req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/client/" CLIENT, U_OPT_NONE); ulfius_send_http_request(&client_req, &client_resp); j_client = ulfius_get_json_body_response(&client_resp, NULL); ulfius_clean_response(&client_resp); json_object_set_new(j_client, "authorization_data_types", json_pack("[sss]", RAR1, RAR2, RAR3)); json_array_append_new(json_object_get(j_client, "authorization_type"), json_string("device_authorization")); ulfius_set_request_properties(&client_req, U_OPT_HTTP_URL, SERVER_URI "/client/" CLIENT, U_OPT_HTTP_VERB, "PUT", U_OPT_JSON_BODY, j_client, U_OPT_NONE); ulfius_send_http_request(&client_req, NULL); if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "User %s authenticated", USER_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(scope_req.map_header, "Cookie", cookie); u_map_put(user_req.map_header, "Cookie", cookie); o_free(cookie); scope_req.http_verb = strdup("PUT"); scope_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, &scope_resp) != U_OK || scope_resp.status != 200) { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope '%s' for %s error", CLIENT, SCOPE_LIST); do_test = 0; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope OK"); } ulfius_clean_response(&scope_resp); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication user"); do_test = 0; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); } if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, NULL) != U_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "Remove grant scope '%s' for %s error", CLIENT, SCOPE_LIST); } char * url = msprintf("%s/auth/", SERVER_URI); run_simple_test(&user_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL); o_free(url); json_object_set_new(j_client, "authorization_data_types", json_pack("[]")); json_array_remove(json_object_get(j_client, "authorization_type"), json_array_size(json_object_get(j_client, "authorization_type"))-1); ulfius_set_request_properties(&client_req, U_OPT_HTTP_URL, SERVER_URI "/client/" CLIENT, U_OPT_HTTP_VERB, "PUT", U_OPT_JSON_BODY, j_client, U_OPT_NONE); ulfius_send_http_request(&client_req, NULL); json_decref(j_client); ulfius_clean_request(&admin_req); ulfius_clean_request(&user_req); ulfius_clean_request(&scope_req); ulfius_clean_request(&client_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_scheme_required.c000066400000000000000000000244321415646314000225720ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "new_user" #define USER_PASSWORD "password" #define CLIENT "client1_id" #define REDIRECT_URI "..%2f..%2ftest-oauth2.html?param=client1_cb1" #define RESPONSE_TYPE_CODE "code" #define RESPONSE_TYPE_TOKEN "token" #define RESPONSE_TYPE_ID_TOKEN "id_token" #define SCOPE "scheme_required_scope" #define NAME "New Scope" #define DESCRIPTION "Description for test-scope" #define GROUP "group" #define SCHEME1 "mock_scheme_42" #define SCHEME1_VALUE "42" #define SCHEME2 "mock_scheme_88" #define SCHEME2_VALUE "88" #define SCHEME3 "mock_scheme_95" #define SCHEME3_VALUE "95" struct _u_request admin_req; START_TEST(test_oidc_scheme_required_scope_set) { json_t * j_parameters = json_pack("{ss ss ss so s{s[{ssss}{ssss}{ssss}]}s{si}}", "name", SCOPE, "display_name", NAME, "description", DESCRIPTION, "password_required", json_false(), "scheme", GROUP, "scheme_name", SCHEME1, "scheme_type", "mock", "scheme_name", SCHEME2, "scheme_type", "mock", "scheme_name", SCHEME3, "scheme_type", "mock", "scheme_required", GROUP, 2); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/scope/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssos[sss]ss}", "username", USER_USERNAME, "enabled", json_true(), "scope", SCOPE, "openid", "g_profile", "password", USER_PASSWORD); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/user/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_scheme_required_auth_flow) { struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); o_free(cookie); j_body = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", SCHEME1, "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", SCHEME2, "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", SCHEME3, "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ss}", "scope", SCOPE " openid"); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", SERVER_URI "/auth/grant/" CLIENT, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE_CODE "&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&nonce=abcdxyz&scope=" SCOPE " openid&g_continue", NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE_TOKEN "&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&nonce=abcdxyz&scope=" SCOPE " openid&g_continue", NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE_TOKEN " " RESPONSE_TYPE_ID_TOKEN "&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&nonce=abcdxyz&scope=" SCOPE " openid&g_continue", NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); // Authenticate scheme mock 42 j_body = json_pack("{sssssss{ss}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", SCHEME1, "value", "code", SCHEME1_VALUE); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/auth/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE_CODE "&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&nonce=abcdxyz&scope=" SCOPE " openid&g_continue", NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE_TOKEN "&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&nonce=abcdxyz&scope=" SCOPE " openid&g_continue", NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE_TOKEN " " RESPONSE_TYPE_ID_TOKEN "&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&nonce=abcdxyz&scope=" SCOPE " openid&g_continue", NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"), 1); // Authenticate scheme mock 95 j_body = json_pack("{sssssss{ss}}", "username", USER_USERNAME, "scheme_type", "mock", "scheme_name", SCHEME3, "value", "code", SCHEME3_VALUE); ck_assert_int_eq(run_simple_test(&auth_req, "POST", SERVER_URI "/auth/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE_CODE "&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&nonce=abcdxyz&scope=" SCOPE " openid&g_continue", NULL, NULL, NULL, NULL, 302, NULL, NULL, "code="), 1); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE_TOKEN "&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&nonce=abcdxyz&scope=" SCOPE " openid&g_continue", NULL, NULL, NULL, NULL, 302, NULL, NULL, "access_token="), 1); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE_TOKEN " " RESPONSE_TYPE_ID_TOKEN "&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&nonce=abcdxyz&scope=" SCOPE " openid&g_continue", NULL, NULL, NULL, NULL, 302, NULL, NULL, "id_token="), 1); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/oidc/auth?response_type=" RESPONSE_TYPE_TOKEN " " RESPONSE_TYPE_ID_TOKEN "&g_continue&client_id=" CLIENT "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&nonce=abcdxyz&scope=" SCOPE " openid&g_continue", NULL, NULL, NULL, NULL, 302, NULL, NULL, "access_token="), 1); j_body = json_pack("{ss}", "scope", ""); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", SERVER_URI "/auth/grant/" CLIENT, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ulfius_clean_request(&auth_req); ulfius_clean_response(&auth_resp); } END_TEST START_TEST(test_oidc_scheme_required_scope_delete) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/user/" USER_USERNAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/scope/" SCOPE, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc scheme required"); tc_core = tcase_create("test_oidc_scheme_required"); tcase_add_test(tc_core, test_oidc_scheme_required_scope_set); tcase_add_test(tc_core, test_oidc_scheme_required_auth_flow); tcase_add_test(tc_core, test_oidc_scheme_required_scope_delete); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } ulfius_clean_request(&admin_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_session_management.c000066400000000000000000004022441415646314000233060ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define SCOPE_LIST "openid" #define NONCE_TEST "nonce5678" #define STATE_TEST "abcxyz" #define PLUGIN_MODULE "oidc" #define PLUGIN_NAME "oidc_session" #define PLUGIN_DISPLAY_NAME "oidc with session management" #define PLUGIN_ISS "https://glewlwyd.tld" #define PLUGIN_CODE_DURATION 600 #define PLUGIN_REFRESH_TOKEN_DURATION 1209600 #define PLUGIN_ACCESS_TOKEN_DURATION 3600 #define PLUGIN_COOKIE_NAME "GLEWLWYD2_OIDC_SID" #define PLUGIN_COOKIE_EXPIRATION 2419200 #define CB_KEY "cert/server.key" #define CB_CRT "cert/server.crt" #define CLIENT_ID "client_session" #define CLIENT_ID_1 "client_session_1" #define CLIENT_ID_2 "client_session_2" #define CLIENT_ID_3 "client_session_3" #define CLIENT_ID_4 "client_session_4" #define CLIENT_NAME "client with session" #define CLIENT_SECRET "secret with session" #define CLIENT_ORIGIN "https://client.local" #define CLIENT_REDIRECT "https://client.local/redirect" #define CLIENT_REDIRECT_ENC "https%3A%2F%2Fclient.local%2Fredirect" #define CLIENT_REDIRECT_POST_LOGOUT "https://client.local/logout" #define CLIENT_REDIRECT_POST_LOGOUT_ENC "https%3A%2F%2Fclient.local%2Flogout" #define CLIENT_ANOTHER_REDIRECT_POST_LOGOUT "https://anotherclient.local/logout" #define CLIENT_ANOTHER_REDIRECT_POST_LOGOUT_ENC "https%3A%2F%2Fanotherclient.local%2Flogout" #define CLIENT_FRONTCHANNEL_LOGOUT "https://localhost:5468/frontLogout?1" #define CLIENT_FRONTCHANNEL_LOGOUT_2 "https://localhost:5468/frontLogout?2" #define CLIENT_BACKCHANNEL_LOGOUT "https://localhost:5468/backLogout/1" #define CLIENT_BACKCHANNEL_LOGOUT_2 "https://localhost:5468/backLogout/2" #define STATE "stateXyzabcd123" struct _u_request admin_req; struct _u_request user_req; char * cookie_key, * cookie_value; int counter; static pthread_mutex_t log_lock; static pthread_cond_t log_cond; json_t * init_session(const char * client_id, char * sid) { struct _u_request req; struct _u_response resp; char * url, * code; json_t * j_response, * j_body; jwt_t * jwt; url = msprintf(SERVER_URI "/auth/grant/%s", client_id); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", url, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/auth?response_type=id_token+code&g_continue&client_id=", U_OPT_HTTP_URL_APPEND, client_id, U_OPT_HTTP_URL_APPEND, "&redirect_uri=" CLIENT_REDIRECT_ENC "&state="STATE"&nonce=nonce123456&scope=" SCOPE_LIST, U_OPT_COOKIE_PARAMETER, cookie_key, cookie_value, U_OPT_NONE), U_OK); if (o_strlen(sid)) { u_map_put(req.map_cookie, PLUGIN_COOKIE_NAME, sid); } ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); code = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/token", U_OPT_POST_BODY_PARAMETER, "grant_type", "authorization_code", U_OPT_POST_BODY_PARAMETER, "client_id", client_id, U_OPT_POST_BODY_PARAMETER, "client_secret", CLIENT_SECRET, U_OPT_POST_BODY_PARAMETER, "redirect_uri", CLIENT_REDIRECT, U_OPT_POST_BODY_PARAMETER, "code", code, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_response = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_ne(NULL, jwt = r_jwt_quick_parse(json_string_value(json_object_get(j_response, "id_token")), R_PARSE_NONE, 0)); ck_assert_ptr_ne(NULL, r_jwt_get_claim_str_value(jwt, "sid")); if (o_strlen(sid) != o_strlen(r_jwt_get_claim_str_value(jwt, "sid"))) { o_strcpy(sid, r_jwt_get_claim_str_value(jwt, "sid")); } else { ck_assert_str_eq(sid, r_jwt_get_claim_str_value(jwt, "sid")); } ulfius_clean_request(&req); ulfius_clean_response(&resp); r_jwt_free(jwt); j_body = json_pack("{ss}", "scope", ""); ck_assert_int_eq(run_simple_test(&user_req, "PUT", url, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); o_free(url); o_free(code); return j_response; } static int callback_backlogout(const struct _u_request * request, struct _u_response * response, void * user_data) { jwt_t * jwt = r_jwt_quick_parse(u_map_get(request->map_post_body, "logout_token"), R_PARSE_NONE, 0); ck_assert_ptr_ne(NULL, jwt); ck_assert_int_eq(R_JWA_ALG_HS256, r_jwt_get_sign_alg(jwt)); ck_assert_int_eq(r_jwt_validate_claims(jwt, R_JWT_CLAIM_ISS, PLUGIN_ISS, R_JWT_CLAIM_AUD, *u_map_get(request->map_url, "id")=='1'?CLIENT_ID_3:CLIENT_ID_4, R_JWT_CLAIM_SUB, NULL, R_JWT_CLAIM_IAT, R_JWT_CLAIM_NOW, R_JWT_CLAIM_JTI, NULL, R_JWT_CLAIM_NOP), RHN_OK); if (*u_map_get(request->map_url, "id")=='1') { ck_assert_int_eq(r_jwt_validate_claims(jwt, R_JWT_CLAIM_STR, "sid", (const char *)user_data, R_JWT_CLAIM_NOP), RHN_OK); } counter++; r_jwt_free(jwt); pthread_mutex_lock(&log_lock); pthread_cond_signal(&log_cond); pthread_mutex_unlock(&log_lock); return U_CALLBACK_CONTINUE; } START_TEST(test_oidc_session_management_add_module_ok) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisososososososososososososssisoso}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", "sha", "jwt-key-size", "256", "key", "secret", "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "request-parameter-allow", json_true(), "request-uri-allow-https-non-secure", json_true(), "introspection-revocation-allowed", json_true(), "introspection-revocation-allow-target-client", json_true(), "session-management-allowed", json_true(), "session-cookie-name", PLUGIN_COOKIE_NAME, "session-cookie-expiration", PLUGIN_COOKIE_EXPIRATION, "front-channel-logout-allowed", json_true(), "back-channel-logout-allowed", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_session_management_add_client_ok) { json_t * j_parameters = json_pack("{sssss[s]s[s]s[sss]so}", "client_id", CLIENT_ID, "clint_name", CLIENT_NAME, "redirect_uri", CLIENT_REDIRECT, "post_logout_redirect_uris", CLIENT_REDIRECT_POST_LOGOUT, "authorization_type", "code", "token", "id_token", "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_session_management_add_client_channel_ok) { json_t * j_parameters = json_pack("{sssss[s]s[s]s[sss]sssssssoso}", "client_id", CLIENT_ID_1, "clint_name", CLIENT_NAME, "redirect_uri", CLIENT_REDIRECT, "post_logout_redirect_uris", CLIENT_REDIRECT_POST_LOGOUT, "authorization_type", "code", "token", "id_token", "frontchannel_logout_uri", CLIENT_FRONTCHANNEL_LOGOUT, "frontchannel_logout_session_required", "true", "client_secret", CLIENT_SECRET, "enabled", json_true(), "confidential", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssss[s]s[s]s[sss]sssssssoso}", "client_id", CLIENT_ID_2, "clint_name", CLIENT_NAME, "redirect_uri", CLIENT_REDIRECT, "post_logout_redirect_uris", CLIENT_REDIRECT_POST_LOGOUT, "authorization_type", "code", "token", "id_token", "frontchannel_logout_uri", CLIENT_FRONTCHANNEL_LOGOUT_2, "frontchannel_logout_session_required", "false", "client_secret", CLIENT_SECRET, "enabled", json_true(), "confidential", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssss[s]s[s]s[sss]sssssssoso}", "client_id", CLIENT_ID_3, "clint_name", CLIENT_NAME, "redirect_uri", CLIENT_REDIRECT, "post_logout_redirect_uris", CLIENT_REDIRECT_POST_LOGOUT, "authorization_type", "code", "token", "id_token", "backchannel_logout_uri", CLIENT_BACKCHANNEL_LOGOUT, "backchannel_logout_session_required", "true", "client_secret", CLIENT_SECRET, "enabled", json_true(), "confidential", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssss[s]s[s]s[sss]sssssssoso}", "client_id", CLIENT_ID_4, "clint_name", CLIENT_NAME, "redirect_uri", CLIENT_REDIRECT, "post_logout_redirect_uris", CLIENT_REDIRECT_POST_LOGOUT, "authorization_type", "code", "token", "id_token", "backchannel_logout_uri", CLIENT_BACKCHANNEL_LOGOUT_2, "backchannel_logout_session_required", "false", "client_secret", CLIENT_SECRET, "enabled", json_true(), "confidential", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/client/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_session_management_delete_module) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_session_management_delete_client) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_ID, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_session_management_delete_client_channel) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_ID_1, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_ID_2, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_ID_3, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/client/" CLIENT_ID_4, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_session_management_session_state) { struct _u_request auth_req; struct _u_response auth_resp, resp; json_t * j_body; char * cookie; char * session_state, * session_state_dec, * salt, * intermediate; unsigned char hash[32] = {0}, hash_b64[128] = {0}; size_t hash_len = 32, hash_b64_len = 0; gnutls_datum_t data; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_gt(auth_resp.nb_cookies, 0); ck_assert_ptr_ne((cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value)), NULL); ck_assert_int_eq(u_map_put(auth_req.map_header, "Cookie", cookie), U_OK); ulfius_clean_response(&auth_resp); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_url = msprintf("%s/%s/auth?response_type=id_token%%20code&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonceAbcXyz&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT_ID, CLIENT_REDIRECT, SCOPE_LIST); auth_req.http_verb = o_strdup("GET"); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(NULL, o_strstr(u_map_get(resp.map_header, "Location"), "session_state=")); session_state = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "session_state=")+strlen("session_state=")); if (strchr(session_state, '&') != NULL) { *strchr(session_state, '&') = '\0'; } session_state_dec = ulfius_url_decode(session_state); salt = o_strstr(session_state, ".")+1; ck_assert_ptr_ne(NULL, salt); intermediate = msprintf("%s %s %s %s", CLIENT_ID, CLIENT_ORIGIN, USERNAME, salt); data.data = (unsigned char *)intermediate; data.size = o_strlen(intermediate); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &data, hash, &hash_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64_encode(hash, hash_len, hash_b64, &hash_b64_len), 1); hash_b64[hash_b64_len] = '\0'; ck_assert_int_eq(0, o_strncmp((const char *)hash_b64, session_state_dec, hash_b64_len)); j_body = json_pack("{ss}", "scope", ""); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ck_assert_int_eq(run_simple_test(&auth_req, "GET", SERVER_URI "/" PLUGIN_NAME "/check_session_iframe/", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ulfius_clean_response(&resp); ulfius_clean_request(&auth_req); o_free(cookie); o_free(session_state); o_free(session_state_dec); o_free(intermediate); } END_TEST START_TEST(test_oidc_session_management_end_session_no_post_logout) { struct _u_request req; struct _u_response resp; char * id_token; json_t * j_body; j_body = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&state="STATE"&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT_ID, CLIENT_REDIRECT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=")+strlen("id_token=")); if (strchr(id_token, '&') != NULL) { *strchr(id_token, '&') = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); o_free(req.http_verb); o_free(req.http_url); req.http_verb = o_strdup("GET"); req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/end_session?state="STATE"&id_token_hint=%s", id_token); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(NULL, o_strcasestr(u_map_get(resp.map_header, "Location"), STATE)); j_body = json_pack("{ss}", "scope", ""); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); o_free(id_token); } END_TEST START_TEST(test_oidc_session_management_end_session_invalid_post_logout) { struct _u_request req; struct _u_response resp; char * id_token; json_t * j_body; j_body = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&state=stateXyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT_ID, CLIENT_REDIRECT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=")+strlen("id_token=")); if (strchr(id_token, '&') != NULL) { *strchr(id_token, '&') = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); o_free(req.http_verb); o_free(req.http_url); req.http_verb = o_strdup("GET"); req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/end_session?state=stateXyz&post_logout_redirect_uri=%s&id_token_hint=%s", CLIENT_ANOTHER_REDIRECT_POST_LOGOUT_ENC, id_token); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(NULL, o_strcasestr(u_map_get(resp.map_header, "Location"), "stateXyzabcd")); ck_assert_ptr_eq(NULL, o_strcasestr(u_map_get(resp.map_header, "Location"), CLIENT_ANOTHER_REDIRECT_POST_LOGOUT)); j_body = json_pack("{ss}", "scope", ""); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); o_free(id_token); } END_TEST START_TEST(test_oidc_session_management_end_session_valid_post_logout) { struct _u_request req; struct _u_response resp; char * id_token; json_t * j_body; j_body = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&state=stateXyzabcd&nonce=nonce123456&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT_ID, CLIENT_REDIRECT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=")+strlen("id_token=")); if (strchr(id_token, '&') != NULL) { *strchr(id_token, '&') = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); o_free(req.http_verb); o_free(req.http_url); req.http_verb = o_strdup("GET"); req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/end_session?state="STATE"&post_logout_redirect_uri=%s&id_token_hint=%s", CLIENT_REDIRECT_POST_LOGOUT_ENC, id_token); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(NULL, o_strcasestr(u_map_get(resp.map_header, "Location"), STATE)); ck_assert_ptr_ne(NULL, o_strcasestr(u_map_get(resp.map_header, "Location"), CLIENT_REDIRECT_POST_LOGOUT_ENC)); j_body = json_pack("{ss}", "scope", ""); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); o_free(id_token); } END_TEST START_TEST(test_oidc_session_management_end_session_no_state) { struct _u_request req; struct _u_response resp; char * id_token; json_t * j_body; j_body = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=id_token&g_continue&client_id=%s&redirect_uri=%s&nonce=nonce1234567&scope=%s", SERVER_URI, PLUGIN_NAME, CLIENT_ID, CLIENT_REDIRECT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=")+strlen("id_token=")); if (strchr(id_token, '&') != NULL) { *strchr(id_token, '&') = '\0'; } ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_copy_request(&req, &user_req), U_OK); o_free(req.http_verb); o_free(req.http_url); req.http_verb = o_strdup("GET"); req.http_url = msprintf(SERVER_URI "/" PLUGIN_NAME "/end_session?post_logout_redirect_uri=%s&id_token_hint=%s", CLIENT_REDIRECT_POST_LOGOUT_ENC, id_token); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(NULL, o_strcasestr(u_map_get(resp.map_header, "Location"), "state=")); ck_assert_ptr_ne(NULL, o_strcasestr(u_map_get(resp.map_header, "Location"), CLIENT_REDIRECT_POST_LOGOUT_ENC)); j_body = json_pack("{ss}", "scope", ""); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/auth/grant/" CLIENT_ID, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ulfius_clean_request(&req); ulfius_clean_response(&resp); o_free(id_token); } END_TEST START_TEST(test_oidc_session_management_valid_sid) { char sid[64] = {0}; json_t * j_result_1, * j_result_2, * j_result_3, * j_result_4; j_result_1 = init_session(CLIENT_ID_1, sid); j_result_2 = init_session(CLIENT_ID_2, sid); j_result_3 = init_session(CLIENT_ID_3, sid); j_result_4 = init_session(CLIENT_ID_4, sid); json_decref(j_result_1); json_decref(j_result_2); json_decref(j_result_3); json_decref(j_result_4); } END_TEST START_TEST(test_oidc_session_management_invalid_sid) { char sid[64] = {0}, sid_2[64] = {0}; json_t * j_result_1, * j_result_2; j_result_1 = init_session(CLIENT_ID_1, sid); o_strncpy(sid_2, sid, o_strlen(sid_2)/2); j_result_2 = init_session(CLIENT_ID_2, sid_2); ck_assert_int_eq(o_strlen(sid), o_strlen(sid_2)); ck_assert_str_ne(sid, sid_2); json_decref(j_result_1); json_decref(j_result_2); } END_TEST START_TEST(test_oidc_session_management_valid_new_sid) { char sid[64] = {0}, sid_2[64] = {0}; json_t * j_result_1, * j_result_2, * j_result_3, * j_result_4; j_result_1 = init_session(CLIENT_ID_1, sid); j_result_2 = init_session(CLIENT_ID_2, sid); j_result_3 = init_session(CLIENT_ID_3, sid_2); j_result_4 = init_session(CLIENT_ID_4, sid_2); ck_assert_str_ne(sid, sid_2); json_decref(j_result_1); json_decref(j_result_2); json_decref(j_result_3); json_decref(j_result_4); } END_TEST START_TEST(test_oidc_session_management_end_session_id_token_post_redirect_state_ok) { char sid[64] = {0}; json_t * j_result_1; struct _u_request req; struct _u_response resp; j_result_1 = init_session(CLIENT_ID_1, sid); ck_assert_ptr_ne(NULL, json_object_get(j_result_1, "id_token")); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/end_session?id_token_hint=", U_OPT_HTTP_URL_APPEND, json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_HTTP_URL_APPEND, "&post_logout_redirect_uri=" CLIENT_REDIRECT_POST_LOGOUT_ENC, U_OPT_HTTP_URL_APPEND, "&state=" STATE, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "sid="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), sid), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "plugin="PLUGIN_NAME), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "client_id="CLIENT_ID_1), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "prompt=end_session"), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), STATE), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), CLIENT_REDIRECT_POST_LOGOUT_ENC), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_result_1); } END_TEST START_TEST(test_oidc_session_management_end_session_id_token_post_redirect_ok) { char sid[64] = {0}; json_t * j_result_1; struct _u_request req; struct _u_response resp; j_result_1 = init_session(CLIENT_ID_1, sid); ck_assert_ptr_ne(NULL, json_object_get(j_result_1, "id_token")); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/end_session?id_token_hint=", U_OPT_HTTP_URL_APPEND, json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_HTTP_URL_APPEND, "&post_logout_redirect_uri=" CLIENT_REDIRECT_POST_LOGOUT_ENC, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "sid="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), sid), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "plugin="PLUGIN_NAME), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "client_id="CLIENT_ID_1), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "prompt=end_session"), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), STATE), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), CLIENT_REDIRECT_POST_LOGOUT_ENC), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_result_1); } END_TEST START_TEST(test_oidc_session_management_end_session_id_token_post_redirect_invalid_state_ok) { char sid[64] = {0}; json_t * j_result_1; struct _u_request req; struct _u_response resp; j_result_1 = init_session(CLIENT_ID_1, sid); ck_assert_ptr_ne(NULL, json_object_get(j_result_1, "id_token")); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/end_session?id_token_hint=", U_OPT_HTTP_URL_APPEND, json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_HTTP_URL_APPEND, "&post_logout_redirect_uri=" CLIENT_REDIRECT_POST_LOGOUT_ENC "/error", U_OPT_HTTP_URL_APPEND, "&state=" STATE, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "sid="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), sid), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "plugin="PLUGIN_NAME), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "client_id="CLIENT_ID_1), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "prompt=end_session"), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), STATE), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), CLIENT_REDIRECT_POST_LOGOUT_ENC), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_result_1); } END_TEST START_TEST(test_oidc_session_management_end_session_id_token_invalid) { char sid[64] = {0}; json_t * j_result_1; struct _u_request req; struct _u_response resp; j_result_1 = init_session(CLIENT_ID_1, sid); ck_assert_ptr_ne(NULL, json_object_get(j_result_1, "id_token")); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/end_session?id_token_hint=", U_OPT_HTTP_URL_APPEND, "error", U_OPT_HTTP_URL_APPEND, "&post_logout_redirect_uri=" CLIENT_REDIRECT_POST_LOGOUT_ENC, U_OPT_HTTP_URL_APPEND, "&state=" STATE, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "sid="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), sid), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "plugin="PLUGIN_NAME), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "client_id="CLIENT_ID_1), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "prompt=single_logout"), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), STATE), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), CLIENT_REDIRECT_POST_LOGOUT_ENC), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_result_1); } END_TEST START_TEST(test_oidc_session_management_get_session_list_post_redirect_state_ok) { char sid[64] = {0}; json_t * j_result_1, * j_result_2, * j_result_list; struct _u_request req; struct _u_response resp; size_t i; j_result_1 = init_session(CLIENT_ID_1, sid); j_result_2 = init_session(CLIENT_ID_2, sid); ck_assert_ptr_ne(NULL, json_object_get(j_result_1, "id_token")); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/end_session?id_token_hint=", U_OPT_HTTP_URL_APPEND, json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_HTTP_URL_APPEND, "&post_logout_redirect_uri=" CLIENT_REDIRECT_POST_LOGOUT_ENC, U_OPT_HTTP_URL_APPEND, "&state=" STATE, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "sid="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), sid), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "plugin="PLUGIN_NAME), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "client_id="CLIENT_ID_1), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "prompt=end_session"), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "callback_url="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), STATE), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), CLIENT_REDIRECT_POST_LOGOUT_ENC), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/session/", U_OPT_HTTP_URL_APPEND, sid, U_OPT_HTTP_URL_APPEND, "/" CLIENT_ID_1, U_OPT_HTTP_URL_APPEND, "?post_redirect_to=" CLIENT_REDIRECT_POST_LOGOUT_ENC, U_OPT_COOKIE_PARAMETER, cookie_key, cookie_value, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_result_list = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq(sid, json_string_value(json_object_get(j_result_list, "sid"))); ck_assert_str_eq(CLIENT_ID_1, json_string_value(json_object_get(j_result_list, "client_id"))); ck_assert_str_eq(CLIENT_REDIRECT_POST_LOGOUT, json_string_value(json_object_get(j_result_list, "post_redirect_to"))); ck_assert_int_eq(2, json_array_size(json_object_get(j_result_list, "client"))); for (i=0; i<2; i++) { if (0 == o_strcmp(CLIENT_ID_1, json_string_value(json_object_get(json_array_get(json_object_get(j_result_list, "client"), i), "client_id")))) { ck_assert_str_eq(CLIENT_FRONTCHANNEL_LOGOUT, json_string_value(json_object_get(json_array_get(json_object_get(j_result_list, "client"), i), "frontchannel_logout_uri"))); ck_assert_ptr_eq(json_true(), json_object_get(json_array_get(json_object_get(j_result_list, "client"), i), "frontchannel_logout_session_required")); } else if (0 == o_strcmp(CLIENT_ID_2, json_string_value(json_object_get(json_array_get(json_object_get(j_result_list, "client"), i), "client_id")))) { ck_assert_str_eq(CLIENT_FRONTCHANNEL_LOGOUT_2, json_string_value(json_object_get(json_array_get(json_object_get(j_result_list, "client"), i), "frontchannel_logout_uri"))); ck_assert_ptr_eq(json_false(), json_object_get(json_array_get(json_object_get(j_result_list, "client"), i), "frontchannel_logout_session_required")); } else { // This should not happen ck_assert_int_eq(0, 1); } } ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_result_1); json_decref(j_result_2); json_decref(j_result_list); } END_TEST START_TEST(test_oidc_session_management_get_session_list_post_redirect_ok) { char sid[64] = {0}; json_t * j_result_1, * j_result_2, * j_result_list; struct _u_request req; struct _u_response resp; size_t i; j_result_1 = init_session(CLIENT_ID_1, sid); j_result_2 = init_session(CLIENT_ID_2, sid); ck_assert_ptr_ne(NULL, json_object_get(j_result_1, "id_token")); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/end_session?id_token_hint=", U_OPT_HTTP_URL_APPEND, json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_HTTP_URL_APPEND, "&post_logout_redirect_uri=" CLIENT_REDIRECT_POST_LOGOUT_ENC, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "sid="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), sid), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "plugin="PLUGIN_NAME), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "client_id="CLIENT_ID_1), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "prompt=end_session"), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "callback_url="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), STATE), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), CLIENT_REDIRECT_POST_LOGOUT_ENC), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/session/", U_OPT_HTTP_URL_APPEND, sid, U_OPT_HTTP_URL_APPEND, "/" CLIENT_ID_1, U_OPT_HTTP_URL_APPEND, "?post_redirect_to=" CLIENT_REDIRECT_POST_LOGOUT_ENC, U_OPT_COOKIE_PARAMETER, cookie_key, cookie_value, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_result_list = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq(sid, json_string_value(json_object_get(j_result_list, "sid"))); ck_assert_str_eq(CLIENT_ID_1, json_string_value(json_object_get(j_result_list, "client_id"))); ck_assert_str_eq(CLIENT_REDIRECT_POST_LOGOUT, json_string_value(json_object_get(j_result_list, "post_redirect_to"))); ck_assert_int_eq(2, json_array_size(json_object_get(j_result_list, "client"))); for (i=0; i<2; i++) { if (0 == o_strcmp(CLIENT_ID_1, json_string_value(json_object_get(json_array_get(json_object_get(j_result_list, "client"), i), "client_id")))) { ck_assert_str_eq(CLIENT_FRONTCHANNEL_LOGOUT, json_string_value(json_object_get(json_array_get(json_object_get(j_result_list, "client"), i), "frontchannel_logout_uri"))); ck_assert_ptr_eq(json_true(), json_object_get(json_array_get(json_object_get(j_result_list, "client"), i), "frontchannel_logout_session_required")); } else if (0 == o_strcmp(CLIENT_ID_2, json_string_value(json_object_get(json_array_get(json_object_get(j_result_list, "client"), i), "client_id")))) { ck_assert_str_eq(CLIENT_FRONTCHANNEL_LOGOUT_2, json_string_value(json_object_get(json_array_get(json_object_get(j_result_list, "client"), i), "frontchannel_logout_uri"))); ck_assert_ptr_eq(json_false(), json_object_get(json_array_get(json_object_get(j_result_list, "client"), i), "frontchannel_logout_session_required")); } else { // This should not happen ck_assert_int_eq(0, 1); } } ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_result_1); json_decref(j_result_2); json_decref(j_result_list); } END_TEST START_TEST(test_oidc_session_management_get_session_list_ok) { char sid[64] = {0}; json_t * j_result_1, * j_result_2, * j_result_list; struct _u_request req; struct _u_response resp; size_t i; j_result_1 = init_session(CLIENT_ID_1, sid); j_result_2 = init_session(CLIENT_ID_2, sid); ck_assert_ptr_ne(NULL, json_object_get(j_result_1, "id_token")); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/end_session?id_token_hint=", U_OPT_HTTP_URL_APPEND, json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "sid="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), sid), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "plugin="PLUGIN_NAME), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "client_id="CLIENT_ID_1), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "prompt=end_session"), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "callback_url="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), STATE), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), CLIENT_REDIRECT_POST_LOGOUT_ENC), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/session/", U_OPT_HTTP_URL_APPEND, sid, U_OPT_HTTP_URL_APPEND, "/" CLIENT_ID_1, U_OPT_COOKIE_PARAMETER, cookie_key, cookie_value, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_result_list = ulfius_get_json_body_response(&resp, NULL)); ck_assert_str_eq(sid, json_string_value(json_object_get(j_result_list, "sid"))); ck_assert_str_eq(CLIENT_ID_1, json_string_value(json_object_get(j_result_list, "client_id"))); ck_assert_ptr_eq(NULL, json_string_value(json_object_get(j_result_list, "post_redirect_to"))); ck_assert_int_eq(2, json_array_size(json_object_get(j_result_list, "client"))); for (i=0; i<2; i++) { if (0 == o_strcmp(CLIENT_ID_1, json_string_value(json_object_get(json_array_get(json_object_get(j_result_list, "client"), i), "client_id")))) { ck_assert_str_eq(CLIENT_FRONTCHANNEL_LOGOUT, json_string_value(json_object_get(json_array_get(json_object_get(j_result_list, "client"), i), "frontchannel_logout_uri"))); ck_assert_ptr_eq(json_true(), json_object_get(json_array_get(json_object_get(j_result_list, "client"), i), "frontchannel_logout_session_required")); } else if (0 == o_strcmp(CLIENT_ID_2, json_string_value(json_object_get(json_array_get(json_object_get(j_result_list, "client"), i), "client_id")))) { ck_assert_str_eq(CLIENT_FRONTCHANNEL_LOGOUT_2, json_string_value(json_object_get(json_array_get(json_object_get(j_result_list, "client"), i), "frontchannel_logout_uri"))); ck_assert_ptr_eq(json_false(), json_object_get(json_array_get(json_object_get(j_result_list, "client"), i), "frontchannel_logout_session_required")); } else { // This should not happen ck_assert_int_eq(0, 1); } } ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_result_1); json_decref(j_result_2); json_decref(j_result_list); } END_TEST START_TEST(test_oidc_session_management_get_session_list_sid_invalid) { char sid[64] = {0}; json_t * j_result_1, * j_result_2; struct _u_request req; struct _u_response resp; j_result_1 = init_session(CLIENT_ID_1, sid); j_result_2 = init_session(CLIENT_ID_2, sid); ck_assert_ptr_ne(NULL, json_object_get(j_result_1, "id_token")); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/end_session?id_token_hint=", U_OPT_HTTP_URL_APPEND, json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_HTTP_URL_APPEND, "&post_logout_redirect_uri=" CLIENT_REDIRECT_POST_LOGOUT_ENC, U_OPT_HTTP_URL_APPEND, "&state=" STATE, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "sid="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), sid), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "plugin="PLUGIN_NAME), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "client_id="CLIENT_ID_1), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "prompt=end_session"), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "callback_url="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), STATE), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), CLIENT_REDIRECT_POST_LOGOUT_ENC), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/session/", U_OPT_HTTP_URL_APPEND, "error", U_OPT_HTTP_URL_APPEND, "/" CLIENT_ID_1, U_OPT_HTTP_URL_APPEND, "?post_redirect_to=" CLIENT_REDIRECT_POST_LOGOUT_ENC, U_OPT_COOKIE_PARAMETER, cookie_key, cookie_value, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 400); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_result_1); json_decref(j_result_2); } END_TEST START_TEST(test_oidc_session_management_get_session_list_client_id_invalid) { char sid[64] = {0}; json_t * j_result_1, * j_result_2; struct _u_request req; struct _u_response resp; j_result_1 = init_session(CLIENT_ID_1, sid); j_result_2 = init_session(CLIENT_ID_2, sid); ck_assert_ptr_ne(NULL, json_object_get(j_result_1, "id_token")); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/end_session?id_token_hint=", U_OPT_HTTP_URL_APPEND, json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_HTTP_URL_APPEND, "&post_logout_redirect_uri=" CLIENT_REDIRECT_POST_LOGOUT_ENC, U_OPT_HTTP_URL_APPEND, "&state=" STATE, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "sid="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), sid), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "plugin="PLUGIN_NAME), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "client_id="CLIENT_ID_1), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "prompt=end_session"), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "callback_url="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), STATE), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), CLIENT_REDIRECT_POST_LOGOUT_ENC), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/session/", U_OPT_HTTP_URL_APPEND, sid, U_OPT_HTTP_URL_APPEND, "/" "error", U_OPT_HTTP_URL_APPEND, "?post_redirect_to=" CLIENT_REDIRECT_POST_LOGOUT_ENC, U_OPT_COOKIE_PARAMETER, cookie_key, cookie_value, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 400); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_result_1); json_decref(j_result_2); } END_TEST START_TEST(test_oidc_session_management_get_session_list_invalid_user) { char sid[64] = {0}; json_t * j_result_1, * j_result_2; struct _u_request req; struct _u_response resp; j_result_1 = init_session(CLIENT_ID_1, sid); j_result_2 = init_session(CLIENT_ID_2, sid); ck_assert_ptr_ne(NULL, json_object_get(j_result_1, "id_token")); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/end_session?id_token_hint=", U_OPT_HTTP_URL_APPEND, json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "sid="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), sid), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "plugin="PLUGIN_NAME), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "client_id="CLIENT_ID_1), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "prompt=end_session"), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "callback_url="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), STATE), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), CLIENT_REDIRECT_POST_LOGOUT_ENC), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/session/", U_OPT_HTTP_URL_APPEND, sid, U_OPT_HTTP_URL_APPEND, "/" CLIENT_ID_1, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_result_1); json_decref(j_result_2); } END_TEST START_TEST(test_oidc_session_management_delete_session_frontchannel_ok) { char sid[64] = {0}; json_t * j_result_1, * j_result_2, * j_resp; struct _u_request req; struct _u_response resp; j_result_1 = init_session(CLIENT_ID_1, sid); j_result_2 = init_session(CLIENT_ID_2, sid); ck_assert_ptr_ne(NULL, json_object_get(j_result_1, "id_token")); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/end_session?id_token_hint=", U_OPT_HTTP_URL_APPEND, json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "sid="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), sid), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "plugin="PLUGIN_NAME), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "client_id="CLIENT_ID_1), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "prompt=end_session"), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "callback_url="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), STATE), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), CLIENT_REDIRECT_POST_LOGOUT_ENC), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); // CLIENT_ID_1 // Introspect id_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_1, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect access_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "access_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_1, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect refresh_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "refresh_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_1, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // CLIENT_ID_2 // Introspect id_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "id_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_2, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect access_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "access_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_2, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect refresh_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "refresh_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_2, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "DELETE", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/session/", U_OPT_HTTP_URL_APPEND, sid, U_OPT_COOKIE_PARAMETER, cookie_key, cookie_value, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_request(&req); ulfius_clean_response(&resp); // CLIENT_ID_1 // Introspect id_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_1, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_false(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect access_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "access_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_1, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_false(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect refresh_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "refresh_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_1, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_false(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // CLIENT_ID_2 // Introspect id_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "id_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_2, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_false(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect access_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "access_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_2, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_false(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect refresh_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "refresh_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_2, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_false(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_result_1); json_decref(j_result_2); } END_TEST START_TEST(test_oidc_session_management_delete_session_frontchannel_invalid_sid) { char sid[64] = {0}; json_t * j_result_1, * j_result_2, * j_resp; struct _u_request req; struct _u_response resp; j_result_1 = init_session(CLIENT_ID_1, sid); j_result_2 = init_session(CLIENT_ID_2, sid); ck_assert_ptr_ne(NULL, json_object_get(j_result_1, "id_token")); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/end_session?id_token_hint=", U_OPT_HTTP_URL_APPEND, json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "sid="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), sid), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "plugin="PLUGIN_NAME), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "client_id="CLIENT_ID_1), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "prompt=end_session"), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "callback_url="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), STATE), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), CLIENT_REDIRECT_POST_LOGOUT_ENC), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "DELETE", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/session/", U_OPT_HTTP_URL_APPEND, "error", U_OPT_COOKIE_PARAMETER, cookie_key, cookie_value, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 400); ulfius_clean_request(&req); ulfius_clean_response(&resp); // CLIENT_ID_1 // Introspect id_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_1, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect access_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "access_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_1, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect refresh_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "refresh_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_1, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // CLIENT_ID_2 // Introspect id_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "id_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_2, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect access_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "access_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_2, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect refresh_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "refresh_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_2, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_result_1); json_decref(j_result_2); } END_TEST START_TEST(test_oidc_session_management_delete_session_frontchannel_invalid_user) { char sid[64] = {0}; json_t * j_result_1, * j_result_2, * j_resp; struct _u_request req; struct _u_response resp; j_result_1 = init_session(CLIENT_ID_1, sid); j_result_2 = init_session(CLIENT_ID_2, sid); ck_assert_ptr_ne(NULL, json_object_get(j_result_1, "id_token")); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/end_session?id_token_hint=", U_OPT_HTTP_URL_APPEND, json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "sid="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), sid), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "plugin="PLUGIN_NAME), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "client_id="CLIENT_ID_1), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "prompt=end_session"), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "callback_url="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), STATE), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), CLIENT_REDIRECT_POST_LOGOUT_ENC), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "DELETE", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/session/", U_OPT_HTTP_URL_APPEND, sid, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_request(&req); ulfius_clean_response(&resp); // CLIENT_ID_1 // Introspect id_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_1, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect access_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "access_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_1, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect refresh_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "refresh_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_1, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // CLIENT_ID_2 // Introspect id_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "id_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_2, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect access_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "access_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_2, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect refresh_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "refresh_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_2, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_result_1); json_decref(j_result_2); } END_TEST START_TEST(test_oidc_session_management_delete_session_backchannel_ok) { char sid[64] = {0}; json_t * j_result_1, * j_result_2, * j_resp; struct _u_request req; struct _u_response resp; char * key_pem, * cert_pem; struct _u_instance instance; ck_assert_int_eq(0, pthread_mutex_init(&log_lock, NULL)); ck_assert_int_eq(0, pthread_cond_init(&log_cond, NULL)); ck_assert_int_eq(ulfius_init_instance(&instance, 5468, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "backLogout/:id", 0, &callback_backlogout, sid), U_OK); counter = 0; key_pem = read_file(CB_KEY); cert_pem = read_file(CB_CRT); ck_assert_int_eq(ulfius_start_secure_framework(&instance, key_pem, cert_pem), U_OK); o_free(key_pem); o_free(cert_pem); j_result_1 = init_session(CLIENT_ID_3, sid); j_result_2 = init_session(CLIENT_ID_4, sid); ck_assert_ptr_ne(NULL, json_object_get(j_result_1, "id_token")); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/end_session?id_token_hint=", U_OPT_HTTP_URL_APPEND, json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "sid="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), sid), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "plugin="PLUGIN_NAME), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "client_id="CLIENT_ID_3), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "prompt=end_session"), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "callback_url="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), STATE), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), CLIENT_REDIRECT_POST_LOGOUT_ENC), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); // CLIENT_ID_3 // Introspect id_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_3, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect access_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "access_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_3, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect refresh_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "refresh_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_3, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // CLIENT_ID_4 // Introspect id_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "id_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_4, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect access_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "access_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_4, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect refresh_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "refresh_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_4, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "DELETE", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/session/", U_OPT_HTTP_URL_APPEND, sid, U_OPT_COOKIE_PARAMETER, cookie_key, cookie_value, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_request(&req); ulfius_clean_response(&resp); // CLIENT_ID_3 // Introspect id_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_3, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_false(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect access_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "access_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_3, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_false(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect refresh_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "refresh_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_3, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_false(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // CLIENT_ID_4 // Introspect id_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "id_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_4, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_false(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect access_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "access_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_4, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_false(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect refresh_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "refresh_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_4, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_false(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); while (counter < 2) { pthread_mutex_lock(&log_lock); pthread_cond_wait(&log_cond, &log_lock); pthread_mutex_unlock(&log_lock); } ck_assert_int_eq(2, counter); json_decref(j_result_1); json_decref(j_result_2); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); pthread_mutex_destroy(&log_lock); pthread_cond_destroy(&log_cond); } END_TEST START_TEST(test_oidc_session_management_delete_session_backchannel_invalid_sid) { char sid[64] = {0}; json_t * j_result_1, * j_result_2, * j_resp; struct _u_request req; struct _u_response resp; j_result_1 = init_session(CLIENT_ID_3, sid); j_result_2 = init_session(CLIENT_ID_4, sid); ck_assert_ptr_ne(NULL, json_object_get(j_result_1, "id_token")); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/end_session?id_token_hint=", U_OPT_HTTP_URL_APPEND, json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "sid="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), sid), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "plugin="PLUGIN_NAME), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "client_id="CLIENT_ID_3), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "prompt=end_session"), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "callback_url="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), STATE), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), CLIENT_REDIRECT_POST_LOGOUT_ENC), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "DELETE", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/session/", U_OPT_HTTP_URL_APPEND, "error", U_OPT_COOKIE_PARAMETER, cookie_key, cookie_value, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 400); ulfius_clean_request(&req); ulfius_clean_response(&resp); // CLIENT_ID_3 // Introspect id_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_3, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect access_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "access_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_3, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect refresh_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "refresh_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_3, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // CLIENT_ID_4 // Introspect id_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "id_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_4, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect access_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "access_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_4, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect refresh_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "refresh_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_4, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_result_1); json_decref(j_result_2); } END_TEST START_TEST(test_oidc_session_management_delete_session_backchannel_invalid_user) { char sid[64] = {0}; json_t * j_result_1, * j_result_2, * j_resp; struct _u_request req; struct _u_response resp; j_result_1 = init_session(CLIENT_ID_3, sid); j_result_2 = init_session(CLIENT_ID_4, sid); ck_assert_ptr_ne(NULL, json_object_get(j_result_1, "id_token")); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/end_session?id_token_hint=", U_OPT_HTTP_URL_APPEND, json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "sid="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), sid), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "plugin="PLUGIN_NAME), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "client_id="CLIENT_ID_3), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "prompt=end_session"), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "callback_url="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), STATE), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), CLIENT_REDIRECT_POST_LOGOUT_ENC), NULL); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "DELETE", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/session/", U_OPT_HTTP_URL_APPEND, sid, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_request(&req); ulfius_clean_response(&resp); // CLIENT_ID_3 // Introspect id_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "id_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_3, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect access_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "access_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_3, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect refresh_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_1, "refresh_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_3, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // CLIENT_ID_4 // Introspect id_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "id_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_4, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect access_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "access_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_4, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); // Introspect refresh_token ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" PLUGIN_NAME "/introspect", U_OPT_POST_BODY_PARAMETER, "token", json_string_value(json_object_get(j_result_2, "refresh_token")), U_OPT_AUTH_BASIC_USER, CLIENT_ID_4, U_OPT_AUTH_BASIC_PASSWORD, CLIENT_SECRET, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, j_resp = ulfius_get_json_body_response(&resp, NULL)); ck_assert_ptr_eq(json_true(), json_object_get(j_resp, "active")); json_decref(j_resp); ulfius_clean_request(&req); ulfius_clean_response(&resp); json_decref(j_result_1); json_decref(j_result_2); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc session management"); tc_core = tcase_create("test_oidc_session_management"); tcase_add_test(tc_core, test_oidc_session_management_add_module_ok); tcase_add_test(tc_core, test_oidc_session_management_add_client_ok); tcase_add_test(tc_core, test_oidc_session_management_session_state); tcase_add_test(tc_core, test_oidc_session_management_end_session_no_post_logout); tcase_add_test(tc_core, test_oidc_session_management_end_session_invalid_post_logout); tcase_add_test(tc_core, test_oidc_session_management_end_session_valid_post_logout); tcase_add_test(tc_core, test_oidc_session_management_end_session_no_state); tcase_add_test(tc_core, test_oidc_session_management_delete_client); tcase_add_test(tc_core, test_oidc_session_management_add_client_channel_ok); tcase_add_test(tc_core, test_oidc_session_management_valid_sid); tcase_add_test(tc_core, test_oidc_session_management_invalid_sid); tcase_add_test(tc_core, test_oidc_session_management_valid_new_sid); tcase_add_test(tc_core, test_oidc_session_management_end_session_id_token_post_redirect_state_ok); tcase_add_test(tc_core, test_oidc_session_management_end_session_id_token_post_redirect_ok); tcase_add_test(tc_core, test_oidc_session_management_end_session_id_token_post_redirect_invalid_state_ok); tcase_add_test(tc_core, test_oidc_session_management_end_session_id_token_invalid); tcase_add_test(tc_core, test_oidc_session_management_get_session_list_post_redirect_state_ok); tcase_add_test(tc_core, test_oidc_session_management_get_session_list_post_redirect_ok); tcase_add_test(tc_core, test_oidc_session_management_get_session_list_ok); tcase_add_test(tc_core, test_oidc_session_management_get_session_list_sid_invalid); tcase_add_test(tc_core, test_oidc_session_management_get_session_list_client_id_invalid); tcase_add_test(tc_core, test_oidc_session_management_get_session_list_invalid_user); tcase_add_test(tc_core, test_oidc_session_management_delete_session_frontchannel_ok); tcase_add_test(tc_core, test_oidc_session_management_delete_session_frontchannel_invalid_sid); tcase_add_test(tc_core, test_oidc_session_management_delete_session_frontchannel_invalid_user); tcase_add_test(tc_core, test_oidc_session_management_delete_session_backchannel_ok); tcase_add_test(tc_core, test_oidc_session_management_delete_session_backchannel_invalid_sid); tcase_add_test(tc_core, test_oidc_session_management_delete_session_backchannel_invalid_user); tcase_add_test(tc_core, test_oidc_session_management_delete_client_channel); tcase_add_test(tc_core, test_oidc_session_management_delete_module); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp, scope_resp; json_t * j_body; int res, do_test = 0, i; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "user1" #define USER_PASSWORD "password" #define PLUGIN_PUBLIC "oidc_public" #define PLUGIN_PAIRWISE "oidc_pairwise" #define SCOPE_LIST "g_profile openid" #define CLIENT1 "client1_id" #define CLIENT1_URL "../../test-oidc.html?param=client1_cb1" #define CLIENT3 "client3_id" #define CLIENT3_URL "../../test-oidc.html?param=client3" #define CLIENT4 "client4_id" #define CLIENT4_URL "../../test-oidc.html?param=client4" #define RESPONSE_TYPE "id_token" struct _u_request admin_req; struct _u_request user_req; START_TEST(test_oidc_subject_type_add_plugin_public) { json_t * j_param = json_pack("{sssssss{sssssssssisisisososososososososs}}", "module", "oidc", "name", PLUGIN_PUBLIC, "display_name", PLUGIN_PUBLIC, "parameters", "iss", "https://glewlwyd.tld", "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_PUBLIC, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "subject-type", "public"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_subject_type_public_sub_equal) { struct _u_response resp; char * id_token, ** id_token_split = NULL, * str_payload; char * sub_client1 = NULL, * sub_client3 = NULL, * sub_client4; size_t str_payload_len = 0; json_t * j_payload; // Client 1 ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_PUBLIC, RESPONSE_TYPE, CLIENT1, CLIENT1_URL, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); id_token = o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token="); if (o_strchr(id_token, '&') != NULL) { *o_strchr(id_token, '&') = '\0'; } ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), NULL, &str_payload_len), 1); ck_assert_ptr_ne((str_payload = o_malloc(str_payload_len + 3)), NULL); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_payload = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_ptr_ne(json_object_get(j_payload, "sub"), NULL); sub_client1 = o_strdup(json_string_value(json_object_get(j_payload, "sub"))); ulfius_clean_response(&resp); free_string_array(id_token_split); o_free(str_payload); json_decref(j_payload); // Client 3 ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_PUBLIC, RESPONSE_TYPE, CLIENT3, CLIENT3_URL, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); id_token = o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token="); if (o_strchr(id_token, '&') != NULL) { *o_strchr(id_token, '&') = '\0'; } ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), NULL, &str_payload_len), 1); ck_assert_ptr_ne((str_payload = o_malloc(str_payload_len + 3)), NULL); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_payload = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_ptr_ne(json_object_get(j_payload, "sub"), NULL); sub_client3 = o_strdup(json_string_value(json_object_get(j_payload, "sub"))); ulfius_clean_response(&resp); free_string_array(id_token_split); o_free(str_payload); json_decref(j_payload); // Client 4 ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_PUBLIC, RESPONSE_TYPE, CLIENT4, CLIENT4_URL, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); id_token = o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token="); if (o_strchr(id_token, '&') != NULL) { *o_strchr(id_token, '&') = '\0'; } ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), NULL, &str_payload_len), 1); ck_assert_ptr_ne((str_payload = o_malloc(str_payload_len + 3)), NULL); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_payload = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_ptr_ne(json_object_get(j_payload, "sub"), NULL); sub_client4 = o_strdup(json_string_value(json_object_get(j_payload, "sub"))); ulfius_clean_response(&resp); free_string_array(id_token_split); o_free(str_payload); json_decref(j_payload); ck_assert_str_eq(sub_client1, sub_client3); ck_assert_str_eq(sub_client1, sub_client4); o_free(sub_client1); o_free(sub_client3); o_free(sub_client4); } END_TEST START_TEST(test_oidc_subject_type_delete_plugin_public) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_PUBLIC, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_subject_type_add_plugin_pairwise) { json_t * j_param = json_pack("{sssssss{sssssssssisisisososososososososs}}", "module", "oidc", "name", PLUGIN_PAIRWISE, "display_name", PLUGIN_PAIRWISE, "parameters", "iss", "https://glewlwyd.tld", "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_PAIRWISE, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "subject-type", "pairwise"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_subject_type_pairwise_sub_different) { struct _u_response resp; char * id_token, ** id_token_split = NULL, * str_payload; char * sub_client1 = NULL, * sub_client3 = NULL, * sub_client4; size_t str_payload_len = 0; json_t * j_payload; // Client 1 ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_PAIRWISE, RESPONSE_TYPE, CLIENT1, CLIENT1_URL, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); id_token = o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token="); if (o_strchr(id_token, '&') != NULL) { *o_strchr(id_token, '&') = '\0'; } ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), NULL, &str_payload_len), 1); ck_assert_ptr_ne((str_payload = o_malloc(str_payload_len + 3)), NULL); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_payload = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_ptr_ne(json_object_get(j_payload, "sub"), NULL); sub_client1 = o_strdup(json_string_value(json_object_get(j_payload, "sub"))); ulfius_clean_response(&resp); free_string_array(id_token_split); o_free(str_payload); json_decref(j_payload); // Client 3 ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_PAIRWISE, RESPONSE_TYPE, CLIENT3, CLIENT3_URL, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); id_token = o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token="); if (o_strchr(id_token, '&') != NULL) { *o_strchr(id_token, '&') = '\0'; } ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), NULL, &str_payload_len), 1); ck_assert_ptr_ne((str_payload = o_malloc(str_payload_len + 3)), NULL); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_payload = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_ptr_ne(json_object_get(j_payload, "sub"), NULL); sub_client3 = o_strdup(json_string_value(json_object_get(j_payload, "sub"))); ulfius_clean_response(&resp); free_string_array(id_token_split); o_free(str_payload); json_decref(j_payload); // Client 4 ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_PAIRWISE, RESPONSE_TYPE, CLIENT4, CLIENT4_URL, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); id_token = o_strstr(u_map_get(resp.map_header, "Location"), "id_token=") + o_strlen("id_token="); if (o_strchr(id_token, '&') != NULL) { *o_strchr(id_token, '&') = '\0'; } ck_assert_int_eq(split_string(id_token, ".", &id_token_split), 3); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), NULL, &str_payload_len), 1); ck_assert_ptr_ne((str_payload = o_malloc(str_payload_len + 3)), NULL); ck_assert_int_eq(o_base64url_decode((unsigned char *)id_token_split[1], o_strlen(id_token_split[1]), (unsigned char *)str_payload, &str_payload_len), 1); str_payload[str_payload_len] = '\0'; ck_assert_ptr_ne((j_payload = json_loads(str_payload, JSON_DECODE_ANY, NULL)), NULL); ck_assert_ptr_ne(json_object_get(j_payload, "sub"), NULL); sub_client4 = o_strdup(json_string_value(json_object_get(j_payload, "sub"))); ulfius_clean_response(&resp); free_string_array(id_token_split); o_free(str_payload); json_decref(j_payload); ck_assert_str_eq(sub_client1, sub_client3); ck_assert_str_ne(sub_client1, sub_client4); o_free(sub_client1); o_free(sub_client3); o_free(sub_client4); } END_TEST START_TEST(test_oidc_subject_type_delete_plugin_pairwise) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_PAIRWISE, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc subject_type"); tc_core = tcase_create("test_oidc_subject_type"); tcase_add_test(tc_core, test_oidc_subject_type_add_plugin_public); tcase_add_test(tc_core, test_oidc_subject_type_public_sub_equal); tcase_add_test(tc_core, test_oidc_subject_type_delete_plugin_public); tcase_add_test(tc_core, test_oidc_subject_type_add_plugin_pairwise); tcase_add_test(tc_core, test_oidc_subject_type_pairwise_sub_different); tcase_add_test(tc_core, test_oidc_subject_type_delete_plugin_pairwise); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req; struct _u_response auth_resp, scope_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); ulfius_init_request(&user_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&scope_req); ulfius_init_response(&auth_resp); ulfius_init_response(&scope_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "User %s authenticated", USER_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(scope_req.map_header, "Cookie", cookie); u_map_put(user_req.map_header, "Cookie", cookie); o_free(cookie); scope_req.http_verb = strdup("PUT"); scope_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT1); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, &scope_resp) != U_OK || scope_resp.status != 200) { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope '%s' for %s error", SCOPE_LIST, CLIENT1); do_test = 0; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope OK"); } ulfius_clean_response(&scope_resp); ulfius_init_response(&scope_resp); o_free(scope_req.http_url); scope_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT3); if (ulfius_send_http_request(&scope_req, &scope_resp) != U_OK || scope_resp.status != 200) { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope '%s' for %s error", SCOPE_LIST, CLIENT3); do_test = 0; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope OK"); } ulfius_clean_response(&scope_resp); ulfius_init_response(&scope_resp); o_free(scope_req.http_url); scope_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT4); if (ulfius_send_http_request(&scope_req, &scope_resp) != U_OK || scope_resp.status != 200) { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope '%s' for %s error", SCOPE_LIST, CLIENT4); do_test = 0; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope OK"); } ulfius_clean_response(&scope_resp); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication user %s", USER_USERNAME); do_test = 0; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); } if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } if (do_test) { j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); o_free(scope_req.http_url); scope_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT1); if (ulfius_send_http_request(&scope_req, NULL) != U_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "Remove grant scope '%s' for %s error", CLIENT1, SCOPE_LIST); } o_free(scope_req.http_url); scope_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT3); if (ulfius_send_http_request(&scope_req, NULL) != U_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "Remove grant scope '%s' for %s error", CLIENT3, SCOPE_LIST); } o_free(scope_req.http_url); scope_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT4); if (ulfius_send_http_request(&scope_req, NULL) != U_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "Remove grant scope '%s' for %s error", CLIENT4, SCOPE_LIST); } char * url = msprintf("%s/auth/", SERVER_URI); run_simple_test(&user_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL); o_free(url); } ulfius_clean_request(&admin_req); ulfius_clean_request(&user_req); ulfius_clean_request(&scope_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_oidc_token.c000066400000000000000000000702201415646314000205420ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "scope1 scope2" #define SCOPE_LIST_PARTIAL "scope1" #define SCOPE_LIST_MAX_USE "scope1 scope2 scope3" #define CLIENT "client1_id" struct _u_request user_req; char * code; START_TEST(test_oidc_token_redirect_login) { char * url = msprintf("%s/glwd/auth?response_type=token&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST); int res = run_simple_test(NULL, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "login.html"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_token_redirect_login_post) { char * url = o_strdup(SERVER_URI "/oidc/auth"); struct _u_map body; u_map_init(&body); u_map_put(&body, "response_type", "token"); u_map_put(&body, "redirect_uri", "../../test-oauth2.html?param=client1_cb1"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "state", "xyzabcd"); u_map_put(&body, "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(NULL, "POST", url, NULL, NULL, NULL, &body, 302, NULL, NULL, "login.html"), 1); o_free(url); u_map_clean(&body); } END_TEST START_TEST(test_oidc_token_valid) { char * url = msprintf("%s/glwd/auth?response_type=token&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "token="); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_token_valid_post) { struct _u_response resp; ulfius_init_response(&resp); o_free(user_req.http_url); user_req.http_url = msprintf("%s/oidc/auth", SERVER_URI); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); u_map_put(user_req.map_post_body, "response_type", "token"); u_map_put(user_req.map_post_body, "client_id", CLIENT); u_map_put(user_req.map_post_body, "redirect_uri", "../../test-oauth2.html?param=client1_cb1"); u_map_put(user_req.map_post_body, "state", "xyzabcd"); u_map_put(user_req.map_post_body, "state", "xyzabcd"); u_map_put(user_req.map_post_body, "nonce", "nonce1234"); u_map_put(user_req.map_post_body, "scope", SCOPE_LIST); u_map_put(user_req.map_post_body, "g_continue", "true"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); ck_assert_ptr_eq(o_strstr(u_map_get(resp.map_header, "Location"), "code="), NULL); ulfius_clean_response(&resp); } END_TEST START_TEST(test_oidc_token_client_invalid) { char * url = msprintf("%s/glwd/auth?response_type=token&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&scope=%s", SERVER_URI, "invalid", SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=unauthorized_client"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_token_redirect_uri_invalid) { char * url = msprintf("%s/glwd/auth?response_type=token&g_continue&client_id=%s&redirect_uri=invalid&state=xyzabcd&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=unauthorized_client"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_token_scope_invalid) { char * url = msprintf("%s/glwd/auth?response_type=token&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&state=xyzabcd&scope=%s", SERVER_URI, CLIENT, "scope4"); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "error=invalid_scope"); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_token_empty) { char * url = msprintf("%s/glwd/auth?response_type=token&state=xyzabcd&g_continue", SERVER_URI); int res = run_simple_test(&user_req, "GET", url, NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL); o_free(url); ck_assert_int_eq(res, 1); } END_TEST START_TEST(test_oidc_token_scope_grant_partial) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST_PARTIAL); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get token code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/glwd/auth?response_type=token&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "token="), NULL); ck_assert_str_eq(o_strstr(u_map_get(code_resp.map_header, "Location"), "scope=") + o_strlen("scope="), SCOPE_LIST_PARTIAL); ulfius_clean_response(&code_resp); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); } END_TEST START_TEST(test_oidc_token_scope_grant_none) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Try to get code code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/glwd/auth?response_type=token&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "invalid_scope"), NULL); ulfius_clean_request(&code_req); ulfius_clean_response(&code_resp); ulfius_clean_request(&auth_req); } END_TEST START_TEST(test_oidc_token_scope_grant_all_authorize_partial) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Try to get token code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/glwd/auth?response_type=token&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "login.html"), NULL); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); ulfius_clean_response(&code_resp); } END_TEST START_TEST(test_oidc_token_retry_with_max_use) { struct _u_request auth_req, code_req; struct _u_response auth_resp, code_resp; json_t * j_body; char * cookie; ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); ulfius_init_request(&code_req); ulfius_init_response(&code_resp); // Authenticate with password auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); // Get session cookie cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(auth_req.map_header, "Cookie", cookie); u_map_put(code_req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&auth_resp); // Grant access to scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST_MAX_USE); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 42 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_42", "value", "code", "42"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 95 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_95", "value", "code", "95"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Authenticate with scheme mock 88 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get token code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/glwd/auth?response_type=token&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "token="), NULL); ck_assert_str_eq(o_strstr(u_map_get(code_resp.map_header, "Location"), "scope=")+o_strlen("scope="), SCOPE_LIST_MAX_USE); ulfius_clean_response(&code_resp); // Try to get another token with the same session but redirected to login page ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/glwd/auth?response_type=token&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "login.html"), NULL); ulfius_clean_response(&code_resp); // Reauthenticate with scheme mock 88 ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", "mock", "scheme_name", "mock_scheme_88", "value", "code", "88"); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_eq(auth_resp.nb_cookies, 1); ulfius_clean_response(&auth_resp); // Get another code ulfius_init_response(&code_resp); o_free(code_req.http_verb); o_free(code_req.http_url); code_req.http_verb = strdup("GET"); code_req.http_url = msprintf("%s/glwd/auth?response_type=token&g_continue&client_id=%s&redirect_uri=..%%2f..%%2ftest-oauth2.html%%3fparam%%3dclient1_cb1&scope=%s", SERVER_URI, CLIENT, SCOPE_LIST_MAX_USE); ck_assert_int_eq(ulfius_send_http_request(&code_req, &code_resp), U_OK); ck_assert_int_eq(code_resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(code_resp.map_header, "Location"), "token="), NULL); ck_assert_str_eq(o_strstr(u_map_get(code_resp.map_header, "Location"), "scope=")+o_strlen("scope="), SCOPE_LIST_MAX_USE); ulfius_clean_response(&code_resp); // Clean grant scopes ulfius_init_response(&auth_resp); o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_verb = strdup("PUT"); auth_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ulfius_clean_request(&auth_req); ulfius_clean_request(&code_req); ulfius_clean_response(&auth_resp); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc token"); tc_core = tcase_create("test_oidc_token"); tcase_add_test(tc_core, test_oidc_token_redirect_login); tcase_add_test(tc_core, test_oidc_token_redirect_login_post); tcase_add_test(tc_core, test_oidc_token_valid); tcase_add_test(tc_core, test_oidc_token_valid_post); tcase_add_test(tc_core, test_oidc_token_client_invalid); tcase_add_test(tc_core, test_oidc_token_redirect_uri_invalid); tcase_add_test(tc_core, test_oidc_token_scope_invalid); tcase_add_test(tc_core, test_oidc_token_empty); tcase_add_test(tc_core, test_oidc_token_scope_grant_partial); tcase_add_test(tc_core, test_oidc_token_scope_grant_none); tcase_add_test(tc_core, test_oidc_token_scope_grant_all_authorize_partial); tcase_add_test(tc_core, test_oidc_token_retry_with_max_use); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req, register_req; struct _u_response auth_resp, scope_resp; json_t * j_body, * j_register; int res, do_test = 0, i; char * url; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&user_req); ulfius_init_request(&scope_req); ulfius_init_request(®ister_req); ulfius_init_response(&scope_resp); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile" #define SCOPE_INTROSPECT "g_admin" #define CLIENT_CONFIDENTIAL_1 "client3_id" #define CLIENT_CONFIDENTIAL_1_SECRET "password" #define SCOPE_LIST_CLIENT_CONFIDENTIAL_1 "scope2 scope3" #define CLIENT_CONFIDENTIAL_2 "client4_id" #define CLIENT_CONFIDENTIAL_2_SECRET "secret" #define CLIENT_PUBLIC "client1_id" #define REDIRECT_URI "..%2f..%2ftest-oidc.html?param=client3" #define REDIRECT_URI_DECODED "../../test-oidc.html?param=client3" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define PLUGIN_MODULE "oidc" #define PLUGIN_NAME "introspect" #define PLUGIN_ISS "https://glewlwyd.tld" #define PLUGIN_DISPLAY_NAME "Introspection test" #define PLUGIN_JWT_TYPE "sha" #define PLUGIN_JWT_KEY_SIZE "256" #define PLUGIN_KEY "secret" #define PLUGIN_CODE_DURATION 600 #define PLUGIN_REFRESH_TOKEN_DURATION 1209600 #define PLUGIN_ACCESS_TOKEN_DURATION 3600 #define TOKEN_TYPE_HINT_REFRESH "refresh_token" #define TOKEN_TYPE_HINT_ACCESS "access_token" #define TOKEN_TYPE_HINT_ID_TOKEN "id_token" struct _u_request admin_req; START_TEST(test_oidc_introspection_plugin_add_target_client) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisosososososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "introspection-revocation-allowed", json_true(), "introspection-revocation-allow-target-client", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_introspection_plugin_add_auth_scope) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisososososososos[s]}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "introspection-revocation-allowed", json_true(), "introspection-revocation-auth-scope", SCOPE_INTROSPECT); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_introspection_plugin_add_target_client_check_expiration) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisosososososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", 1, "access-token-duration", 1, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "introspection-revocation-allowed", json_true(), "introspection-revocation-allow-target-client", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_introspection_plugin_remove) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_introspection_invalid_format_target_client) { struct _u_request req; struct _u_response resp; json_t * j_body; const char * access_token; struct _u_map param; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); access_token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(access_token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "error", access_token), U_OK); ck_assert_int_eq(u_map_put(¶m, "error_hint", "error"), U_OK); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, NULL, 400, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, "error", NULL, NULL, 401, NULL, NULL, NULL), 1); u_map_clean(¶m); json_decref(j_body); } END_TEST START_TEST(test_oidc_introspection_access_token_target_client) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response; const char * token; struct _u_map param; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); j_response = json_pack("{sossssssss}", "active", json_true(), "username", USERNAME, "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_ACCESS, "scope", SCOPE_LIST); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_ACCESS), U_OK); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, j_response, NULL, NULL), 1); u_map_remove_from_key(¶m, "token_type_hint"); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_2, CLIENT_CONFIDENTIAL_2_SECRET, NULL, ¶m, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(u_map_put(¶m, "token", "error"), U_OK); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); } END_TEST START_TEST(test_oidc_introspection_refresh_token_target_client) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response; const char * token; struct _u_map param; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "refresh_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); j_response = json_pack("{sossssssss}", "active", json_true(), "username", USERNAME, "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_REFRESH, "scope", SCOPE_LIST); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_REFRESH), U_OK); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, j_response, NULL, NULL), 1); u_map_remove_from_key(¶m, "token_type_hint"); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_2, CLIENT_CONFIDENTIAL_2_SECRET, NULL, ¶m, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(u_map_put(¶m, "token", "error"), U_OK); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); } END_TEST START_TEST(test_oidc_introspection_invalid_format_bearer) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_body_introspect; const char * access_token, * token_auth; struct _u_map param; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); access_token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(access_token, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_put(req.map_post_body, "scope", SCOPE_INTROSPECT); u_map_put(req.map_post_body, "username", ADMIN_USERNAME); u_map_put(req.map_post_body, "password", ADMIN_PASSWORD); o_free(req.auth_basic_user); req.auth_basic_user = NULL; o_free(req.auth_basic_password); req.auth_basic_password = NULL; ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body_introspect = ulfius_get_json_body_response(&resp, NULL); token_auth = json_string_value(json_object_get(j_body_introspect, "access_token")); ck_assert_ptr_ne(token_auth, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "error", access_token), U_OK); ck_assert_int_eq(u_map_put(¶m, "error_hint", "error"), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); tmp = msprintf("Bearer %s", "error"); u_map_put(req.map_header, "Authorization", tmp); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); o_free(tmp); ulfius_clean_request(&req); u_map_clean(¶m); json_decref(j_body); json_decref(j_body_introspect); ulfius_clean_request(&req); } END_TEST START_TEST(test_oidc_introspection_access_token_target_bearer) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response, * j_body_introspect; const char * token, * token_auth; struct _u_map param; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_put(req.map_post_body, "scope", SCOPE_INTROSPECT); u_map_put(req.map_post_body, "username", ADMIN_USERNAME); u_map_put(req.map_post_body, "password", ADMIN_PASSWORD); o_free(req.auth_basic_user); req.auth_basic_user = NULL; o_free(req.auth_basic_password); req.auth_basic_password = NULL; ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body_introspect = ulfius_get_json_body_response(&resp, NULL); token_auth = json_string_value(json_object_get(j_body_introspect, "access_token")); ck_assert_ptr_ne(token_auth, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); j_response = json_pack("{sossssssss}", "active", json_true(), "username", USERNAME, "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_ACCESS, "scope", SCOPE_LIST); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_ACCESS), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); u_map_remove_from_key(¶m, "token_type_hint"); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(u_map_put(¶m, "token", "error"), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); json_decref(j_body_introspect); ulfius_clean_request(&req); } END_TEST START_TEST(test_oidc_introspection_access_token_target_bearer_no_client_id) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response, * j_body_introspect; const char * token, * token_auth; struct _u_map param; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_put(req.map_post_body, "scope", SCOPE_INTROSPECT); u_map_put(req.map_post_body, "username", ADMIN_USERNAME); u_map_put(req.map_post_body, "password", ADMIN_PASSWORD); o_free(req.auth_basic_user); req.auth_basic_user = NULL; o_free(req.auth_basic_password); req.auth_basic_password = NULL; ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body_introspect = ulfius_get_json_body_response(&resp, NULL); token_auth = json_string_value(json_object_get(j_body_introspect, "access_token")); ck_assert_ptr_ne(token_auth, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); j_response = json_pack("{sossssss}", "active", json_true(), "username", USERNAME, "token_type", TOKEN_TYPE_HINT_ACCESS, "scope", SCOPE_LIST); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_ACCESS), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); u_map_remove_from_key(¶m, "token_type_hint"); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(u_map_put(¶m, "token", "error"), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); json_decref(j_body_introspect); ulfius_clean_request(&req); } END_TEST START_TEST(test_oidc_introspection_access_token_target_bearer_client_credentials) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response, * j_body_introspect; const char * token, * token_auth; struct _u_map param; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "client_credentials"); u_map_put(req.map_post_body, "scope", SCOPE_LIST_CLIENT_CONFIDENTIAL_1); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_INTROSPECT); u_map_put(req.map_post_body, "username", ADMIN_USERNAME); u_map_put(req.map_post_body, "password", ADMIN_PASSWORD); o_free(req.auth_basic_user); req.auth_basic_user = NULL; o_free(req.auth_basic_password); req.auth_basic_password = NULL; ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body_introspect = ulfius_get_json_body_response(&resp, NULL); token_auth = json_string_value(json_object_get(j_body_introspect, "access_token")); ck_assert_ptr_ne(token_auth, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); j_response = json_pack("{sossssss}", "active", json_true(), "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_ACCESS, "scope", SCOPE_LIST_CLIENT_CONFIDENTIAL_1); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_ACCESS), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); u_map_remove_from_key(¶m, "token_type_hint"); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(u_map_put(¶m, "token", "error"), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); json_decref(j_body_introspect); ulfius_clean_request(&req); } END_TEST START_TEST(test_oidc_introspection_access_token_target_bearer_jwt) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response, * j_body_introspect, * j_verify; const char * token, * token_auth; char * tmp; jwt_t * jwt; jwk_t * jwk; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_put(req.map_post_body, "scope", SCOPE_INTROSPECT); u_map_put(req.map_post_body, "username", ADMIN_USERNAME); u_map_put(req.map_post_body, "password", ADMIN_PASSWORD); o_free(req.auth_basic_user); req.auth_basic_user = NULL; o_free(req.auth_basic_password); req.auth_basic_password = NULL; ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body_introspect = ulfius_get_json_body_response(&resp, NULL); token_auth = json_string_value(json_object_get(j_body_introspect, "access_token")); ck_assert_ptr_ne(token_auth, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); ulfius_init_response(&resp); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/introspect"); u_map_put(req.map_header, "Accept", "application/jwt"); ck_assert_int_eq(u_map_put(req.map_post_body, "token", token), U_OK); ck_assert_int_eq(u_map_put(req.map_post_body, "token_type_hint", TOKEN_TYPE_HINT_ACCESS), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_str_eq("application/jwt", u_map_get(resp.map_header, "Content-Type")); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parsen(jwt, resp.binary_body, resp.binary_body_length, 0), RHN_OK); ck_assert_ptr_ne(NULL, jwk = r_jwk_quick_import(R_IMPORT_SYMKEY, PLUGIN_KEY, o_strlen(PLUGIN_KEY))); ck_assert_int_eq(r_jwt_verify_signature(jwt, jwk, 0), RHN_OK); ck_assert_ptr_ne(NULL, j_response = r_jwt_get_full_claims_json_t(jwt)); j_verify = json_pack("{sossssssssss}", "active", json_true(), "username", USERNAME, "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_ACCESS, "scope", SCOPE_LIST, "iss", PLUGIN_ISS); ck_assert_ptr_ne(NULL, json_search(j_response, j_verify)); ck_assert_str_eq("token-introspection+jwt", r_jwt_get_header_str_value(jwt, "typ")); r_jwt_free(jwt); r_jwk_free(jwk); json_decref(j_verify); json_decref(j_response); json_decref(j_body); json_decref(j_body_introspect); ulfius_clean_response(&resp); ulfius_clean_request(&req); } END_TEST START_TEST(test_oidc_introspection_access_token_target_bearer_jwt_response) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response, * j_body_introspect, * j_verify; const char * token, * token_auth; char * tmp; jwt_t * jwt; jwk_t * jwk; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_put(req.map_post_body, "scope", SCOPE_INTROSPECT); u_map_put(req.map_post_body, "username", ADMIN_USERNAME); u_map_put(req.map_post_body, "password", ADMIN_PASSWORD); o_free(req.auth_basic_user); req.auth_basic_user = NULL; o_free(req.auth_basic_password); req.auth_basic_password = NULL; ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body_introspect = ulfius_get_json_body_response(&resp, NULL); token_auth = json_string_value(json_object_get(j_body_introspect, "access_token")); ck_assert_ptr_ne(token_auth, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); ulfius_init_response(&resp); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/introspect"); u_map_put(req.map_header, "Accept", "application/token-introspection+jwt"); ck_assert_int_eq(u_map_put(req.map_post_body, "token", token), U_OK); ck_assert_int_eq(u_map_put(req.map_post_body, "token_type_hint", TOKEN_TYPE_HINT_ACCESS), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_str_eq("application/token-introspection+jwt", u_map_get(resp.map_header, "Content-Type")); ck_assert_int_eq(r_jwt_init(&jwt), RHN_OK); ck_assert_int_eq(r_jwt_parsen(jwt, resp.binary_body, resp.binary_body_length, 0), RHN_OK); ck_assert_ptr_ne(NULL, jwk = r_jwk_quick_import(R_IMPORT_SYMKEY, PLUGIN_KEY, o_strlen(PLUGIN_KEY))); ck_assert_int_eq(r_jwt_verify_signature(jwt, jwk, 0), RHN_OK); ck_assert_ptr_ne(NULL, j_response = r_jwt_get_full_claims_json_t(jwt)); j_verify = json_pack("{sossssssssss}", "active", json_true(), "username", USERNAME, "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_ACCESS, "scope", SCOPE_LIST, "iss", PLUGIN_ISS); ck_assert_ptr_ne(NULL, json_search(json_object_get(j_response, "token_introspection"), j_verify)); ck_assert_str_eq("token-introspection+jwt", r_jwt_get_header_str_value(jwt, "typ")); ck_assert_str_eq(PLUGIN_ISS, r_jwt_get_claim_str_value(jwt, "iss")); ck_assert_str_eq(SCOPE_LIST, r_jwt_get_claim_str_value(jwt, "aud")); ck_assert_int_gt(r_jwt_get_claim_int_value(jwt, "iat"), 0); r_jwt_free(jwt); r_jwk_free(jwk); json_decref(j_verify); json_decref(j_response); json_decref(j_body); json_decref(j_body_introspect); ulfius_clean_response(&resp); ulfius_clean_request(&req); } END_TEST START_TEST(test_oidc_introspection_refresh_token_target_bearer) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response, * j_body_introspect; const char * token, * token_auth; struct _u_map param; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "refresh_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_put(req.map_post_body, "scope", SCOPE_INTROSPECT); u_map_put(req.map_post_body, "username", ADMIN_USERNAME); u_map_put(req.map_post_body, "password", ADMIN_PASSWORD); o_free(req.auth_basic_user); req.auth_basic_user = NULL; o_free(req.auth_basic_password); req.auth_basic_password = NULL; ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body_introspect = ulfius_get_json_body_response(&resp, NULL); token_auth = json_string_value(json_object_get(j_body_introspect, "access_token")); ck_assert_ptr_ne(token_auth, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); j_response = json_pack("{sossssssss}", "active", json_true(), "username", USERNAME, "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_REFRESH, "scope", SCOPE_LIST); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_REFRESH), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); u_map_remove_from_key(¶m, "token_type_hint"); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(u_map_put(¶m, "token", "error"), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); json_decref(j_body_introspect); ulfius_clean_request(&req); } END_TEST START_TEST(test_oidc_introspection_token_target_client_check_expiration) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response; const char * access_token, * refresh_token; struct _u_map param; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); access_token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(access_token, NULL); refresh_token = json_string_value(json_object_get(j_body, "refresh_token")); ck_assert_ptr_ne(refresh_token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ck_assert_int_eq(u_map_init(¶m), U_OK); sleep(2); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(u_map_put(¶m, "token", access_token), U_OK); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, j_response, NULL, NULL), 1); ck_assert_int_eq(u_map_put(¶m, "token", refresh_token), U_OK); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); } END_TEST START_TEST(test_oidc_introspection_id_token) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_body_introspect, * j_response; char * cookie, * id_token, * tmp; const char * token_auth; struct _u_map param; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/auth/"); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_body), U_OK); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); cookie = msprintf("%s=%s", resp.map_cookie[0].key, resp.map_cookie[0].value); ck_assert_int_eq(u_map_put(req.map_header, "Cookie", cookie), U_OK); o_free(cookie); ulfius_clean_response(&resp); // Set grant ulfius_init_response(&resp); o_free(req.http_verb); o_free(req.http_url); req.http_verb = strdup("PUT"); req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT_CONFIDENTIAL_1); j_body = json_pack("{ss}", "scope", "openid"); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(req.http_verb); o_free(req.http_url); req.http_verb = o_strdup("GET"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/auth?response_type=id_token&nonce=nonce1234&g_continue&client_id=" CLIENT_CONFIDENTIAL_1 "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&nonce=nonce1234&scope=openid"); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=")+o_strlen("id_token=")); if (o_strstr(id_token, "&") != NULL) { *o_strstr(id_token, "&") = '\0'; } ulfius_clean_response(&resp); // Clean grant ulfius_init_response(&resp); o_free(req.http_verb); o_free(req.http_url); req.http_verb = strdup("PUT"); req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT_CONFIDENTIAL_1); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_remove_from_key(req.map_header, "Cookie"); o_free(req.http_verb); o_free(req.http_url); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_INTROSPECT); u_map_put(req.map_post_body, "username", ADMIN_USERNAME); u_map_put(req.map_post_body, "password", ADMIN_PASSWORD); o_free(req.auth_basic_user); req.auth_basic_user = NULL; o_free(req.auth_basic_password); req.auth_basic_password = NULL; ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body_introspect = ulfius_get_json_body_response(&resp, NULL); token_auth = json_string_value(json_object_get(j_body_introspect, "access_token")); ck_assert_ptr_ne(token_auth, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); j_response = json_pack("{sossssss}", "active", json_true(), "username", USERNAME, "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_ID_TOKEN); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", id_token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_ID_TOKEN), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); u_map_remove_from_key(¶m, "token_type_hint"); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); ck_assert_int_eq(u_map_put(¶m, "token", "error"), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_ID_TOKEN), U_OK); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); u_map_clean(¶m); json_decref(j_body_introspect); ulfius_clean_request(&req); o_free(id_token); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc token introspcetion"); tc_core = tcase_create("test_oidc_token_introspection"); tcase_add_test(tc_core, test_oidc_introspection_plugin_add_target_client); tcase_add_test(tc_core, test_oidc_introspection_invalid_format_target_client); tcase_add_test(tc_core, test_oidc_introspection_access_token_target_client); tcase_add_test(tc_core, test_oidc_introspection_refresh_token_target_client); tcase_add_test(tc_core, test_oidc_introspection_plugin_remove); tcase_add_test(tc_core, test_oidc_introspection_plugin_add_auth_scope); tcase_add_test(tc_core, test_oidc_introspection_invalid_format_bearer); tcase_add_test(tc_core, test_oidc_introspection_access_token_target_bearer); tcase_add_test(tc_core, test_oidc_introspection_refresh_token_target_bearer); tcase_add_test(tc_core, test_oidc_introspection_access_token_target_bearer_no_client_id); tcase_add_test(tc_core, test_oidc_introspection_access_token_target_bearer_client_credentials); tcase_add_test(tc_core, test_oidc_introspection_access_token_target_bearer_jwt); tcase_add_test(tc_core, test_oidc_introspection_access_token_target_bearer_jwt_response); tcase_add_test(tc_core, test_oidc_introspection_id_token); tcase_add_test(tc_core, test_oidc_introspection_plugin_remove); tcase_add_test(tc_core, test_oidc_introspection_plugin_add_target_client_check_expiration); tcase_add_test(tc_core, test_oidc_introspection_token_target_client_check_expiration); tcase_add_test(tc_core, test_oidc_introspection_plugin_remove); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&admin_req); ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); j_body = NULL; res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "g_profile" #define SCOPE_INTROSPECT "g_admin" #define CLIENT_CONFIDENTIAL_1 "client3_id" #define CLIENT_CONFIDENTIAL_1_SECRET "password" #define CLIENT_CONFIDENTIAL_2 "client4_id" #define CLIENT_CONFIDENTIAL_2_SECRET "secret" #define CLIENT_PUBLIC "client1_id" #define REDIRECT_URI "..%2f..%2ftest-oidc.html?param=client3" #define REDIRECT_URI_DECODED "../../test-oidc.html?param=client3" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define PLUGIN_MODULE "oidc" #define PLUGIN_NAME "introspect" #define PLUGIN_ISS "https://glewlwyd.tld" #define PLUGIN_DISPLAY_NAME "Introspection test" #define PLUGIN_JWT_TYPE "sha" #define PLUGIN_JWT_KEY_SIZE "256" #define PLUGIN_KEY "secret" #define PLUGIN_CODE_DURATION 600 #define PLUGIN_REFRESH_TOKEN_DURATION 1209600 #define PLUGIN_ACCESS_TOKEN_DURATION 3600 #define TOKEN_TYPE_HINT_REFRESH "refresh_token" #define TOKEN_TYPE_HINT_ACCESS "access_token" #define TOKEN_TYPE_HINT_ID_TOKEN "id_token" struct _u_request admin_req; START_TEST(test_oidc_revocation_plugin_add_target_client) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisosososososososo}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "introspection-revocation-allowed", json_true(), "introspection-revocation-allow-target-client", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_revocation_plugin_add_auth_scope) { json_t * j_parameters = json_pack("{sssssssos{sssssssssisisisososososososos[s]}}", "module", PLUGIN_MODULE, "name", PLUGIN_NAME, "display_name", PLUGIN_DISPLAY_NAME, "enabled", json_true(), "parameters", "iss", PLUGIN_ISS, "jwt-type", PLUGIN_JWT_TYPE, "jwt-key-size", PLUGIN_JWT_KEY_SIZE, "key", PLUGIN_KEY, "code-duration", PLUGIN_CODE_DURATION, "refresh-token-duration", PLUGIN_REFRESH_TOKEN_DURATION, "access-token-duration", PLUGIN_ACCESS_TOKEN_DURATION, "allow-non-oidc", json_true(), "auth-type-client-enabled", json_true(), "auth-type-code-enabled", json_true(), "auth-type-implicit-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "introspection-revocation-allowed", json_true(), "introspection-revocation-auth-scope", SCOPE_INTROSPECT); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_oidc_revocation_plugin_remove) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_revocation_invalid_format_target_client) { struct _u_request req; struct _u_response resp; json_t * j_body; const char * access_token; struct _u_map param; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); access_token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(access_token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "error", access_token), U_OK); ck_assert_int_eq(u_map_put(¶m, "error_hint", "error"), U_OK); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/revoke", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, NULL, 400, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/revoke", CLIENT_CONFIDENTIAL_1, "error", NULL, NULL, 401, NULL, NULL, NULL), 1); u_map_clean(¶m); json_decref(j_body); } END_TEST START_TEST(test_oidc_revocation_access_token_target_client) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response; const char * token; struct _u_map param; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); j_response = json_pack("{sossssssss}", "active", json_true(), "username", USERNAME, "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_ACCESS, "scope", SCOPE_LIST); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_ACCESS), U_OK); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, j_response, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/revoke", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, NULL, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(u_map_put(¶m, "token", "error"), U_OK); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); } END_TEST START_TEST(test_oidc_revocation_refresh_token_target_client) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response; const char * token; struct _u_map param; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "refresh_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); j_response = json_pack("{sossssssss}", "active", json_true(), "username", USERNAME, "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_REFRESH, "scope", SCOPE_LIST); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_REFRESH), U_OK); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, j_response, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/revoke", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, NULL, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", CLIENT_CONFIDENTIAL_1, CLIENT_CONFIDENTIAL_1_SECRET, NULL, ¶m, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(u_map_put(¶m, "token", "error"), U_OK); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); } END_TEST START_TEST(test_oidc_revocation_invalid_format_bearer) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_body_introspect; const char * access_token, * token_auth; struct _u_map param; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); access_token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(access_token, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_put(req.map_post_body, "scope", SCOPE_INTROSPECT); u_map_put(req.map_post_body, "username", ADMIN_USERNAME); u_map_put(req.map_post_body, "password", ADMIN_PASSWORD); o_free(req.auth_basic_user); req.auth_basic_user = NULL; o_free(req.auth_basic_password); req.auth_basic_password = NULL; ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body_introspect = ulfius_get_json_body_response(&resp, NULL); token_auth = json_string_value(json_object_get(j_body_introspect, "access_token")); ck_assert_ptr_ne(token_auth, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "error", access_token), U_OK); ck_assert_int_eq(u_map_put(¶m, "error_hint", "error"), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/revoke", NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); tmp = msprintf("Bearer %s", "error"); u_map_put(req.map_header, "Authorization", tmp); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/revoke", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); o_free(tmp); ulfius_clean_request(&req); u_map_clean(¶m); json_decref(j_body); json_decref(j_body_introspect); ulfius_clean_request(&req); } END_TEST START_TEST(test_oidc_revocation_access_token_target_bearer) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response, * j_body_introspect; const char * token, * token_auth; struct _u_map param; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "access_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_put(req.map_post_body, "scope", SCOPE_INTROSPECT); u_map_put(req.map_post_body, "username", ADMIN_USERNAME); u_map_put(req.map_post_body, "password", ADMIN_PASSWORD); o_free(req.auth_basic_user); req.auth_basic_user = NULL; o_free(req.auth_basic_password); req.auth_basic_password = NULL; ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body_introspect = ulfius_get_json_body_response(&resp, NULL); token_auth = json_string_value(json_object_get(j_body_introspect, "access_token")); ck_assert_ptr_ne(token_auth, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); j_response = json_pack("{sossssssss}", "active", json_true(), "username", USERNAME, "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_ACCESS, "scope", SCOPE_LIST); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_ACCESS), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); u_map_remove_from_key(¶m, "token_type_hint"); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/revoke", NULL, NULL, NULL, ¶m, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/" PLUGIN_NAME "/userinfo", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); tmp = msprintf("Bearer %s", token); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/" PLUGIN_NAME "/userinfo", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_true()); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); ck_assert_int_eq(u_map_put(¶m, "token", token_auth), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/revoke", NULL, NULL, NULL, ¶m, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 401, NULL, NULL, NULL), 1); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); json_decref(j_body_introspect); ulfius_clean_request(&req); } END_TEST START_TEST(test_oidc_revocation_refresh_token_target_bearer) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_response, * j_body_introspect; const char * token, * token_auth; struct _u_map param; char * tmp; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_LIST); u_map_put(req.map_post_body, "username", USERNAME); u_map_put(req.map_post_body, "password", PASSWORD); req.auth_basic_user = o_strdup(CLIENT_CONFIDENTIAL_1); req.auth_basic_password = o_strdup(CLIENT_CONFIDENTIAL_1_SECRET); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body = ulfius_get_json_body_response(&resp, NULL); token = json_string_value(json_object_get(j_body, "refresh_token")); ck_assert_ptr_ne(token, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_put(req.map_post_body, "scope", SCOPE_INTROSPECT); u_map_put(req.map_post_body, "username", ADMIN_USERNAME); u_map_put(req.map_post_body, "password", ADMIN_PASSWORD); o_free(req.auth_basic_user); req.auth_basic_user = NULL; o_free(req.auth_basic_password); req.auth_basic_password = NULL; ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body_introspect = ulfius_get_json_body_response(&resp, NULL); token_auth = json_string_value(json_object_get(j_body_introspect, "access_token")); ck_assert_ptr_ne(token_auth, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); j_response = json_pack("{sossssssss}", "active", json_true(), "username", USERNAME, "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_REFRESH, "scope", SCOPE_LIST); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_REFRESH), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); u_map_remove_from_key(¶m, "token_type_hint"); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/revoke", NULL, NULL, NULL, ¶m, 200, NULL, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); u_map_clean(¶m); json_decref(j_body); json_decref(j_body_introspect); ulfius_clean_request(&req); } END_TEST START_TEST(test_oidc_revocation_id_token) { struct _u_request req; struct _u_response resp; json_t * j_body, * j_body_introspect, * j_response; char * cookie, * id_token, * tmp; const char * token_auth; struct _u_map param; ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/auth/"); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_body), U_OK); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); cookie = msprintf("%s=%s", resp.map_cookie[0].key, resp.map_cookie[0].value); ck_assert_int_eq(u_map_put(req.map_header, "Cookie", cookie), U_OK); o_free(cookie); ulfius_clean_response(&resp); // Set grant ulfius_init_response(&resp); o_free(req.http_verb); o_free(req.http_url); req.http_verb = strdup("PUT"); req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT_CONFIDENTIAL_1); j_body = json_pack("{ss}", "scope", "openid"); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(req.http_verb); o_free(req.http_url); req.http_verb = o_strdup("GET"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/auth?response_type=id_token&g_continue&client_id=" CLIENT_CONFIDENTIAL_1 "&redirect_uri=" REDIRECT_URI "&state=xyzabcd&nonce=nonce1234&scope=openid"); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "id_token="), NULL); id_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "id_token=")+o_strlen("id_token=")); if (o_strstr(id_token, "&") != NULL) { *o_strstr(id_token, "&") = '\0'; } ulfius_clean_response(&resp); // Clean grant ulfius_init_response(&resp); o_free(req.http_verb); o_free(req.http_url); req.http_verb = strdup("PUT"); req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT_CONFIDENTIAL_1); j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ulfius_clean_response(&resp); ulfius_init_response(&resp); u_map_remove_from_key(req.map_header, "Cookie"); o_free(req.http_verb); o_free(req.http_url); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/token"); u_map_put(req.map_post_body, "grant_type", "password"); u_map_put(req.map_post_body, "scope", SCOPE_INTROSPECT); u_map_put(req.map_post_body, "username", ADMIN_USERNAME); u_map_put(req.map_post_body, "password", ADMIN_PASSWORD); o_free(req.auth_basic_user); req.auth_basic_user = NULL; o_free(req.auth_basic_password); req.auth_basic_password = NULL; ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_body_introspect = ulfius_get_json_body_response(&resp, NULL); token_auth = json_string_value(json_object_get(j_body_introspect, "access_token")); ck_assert_ptr_ne(token_auth, NULL); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); tmp = msprintf("Bearer %s", token_auth); u_map_put(req.map_header, "Authorization", tmp); o_free(tmp); j_response = json_pack("{sossssss}", "active", json_true(), "username", USERNAME, "client_id", CLIENT_CONFIDENTIAL_1, "token_type", TOKEN_TYPE_HINT_ID_TOKEN); ck_assert_int_eq(u_map_init(¶m), U_OK); ck_assert_int_eq(u_map_put(¶m, "token", id_token), U_OK); ck_assert_int_eq(u_map_put(¶m, "token_type_hint", TOKEN_TYPE_HINT_ID_TOKEN), U_OK); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/revoke", NULL, NULL, NULL, ¶m, 200, NULL, NULL, NULL), 1); json_decref(j_response); j_response = json_pack("{so}", "active", json_false()); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/introspect", NULL, NULL, NULL, ¶m, 200, j_response, NULL, NULL), 1); json_decref(j_response); u_map_clean(¶m); json_decref(j_body_introspect); ulfius_clean_request(&req); o_free(id_token); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc token revocation"); tc_core = tcase_create("test_oidc_token_revocation"); tcase_add_test(tc_core, test_oidc_revocation_plugin_add_target_client); tcase_add_test(tc_core, test_oidc_revocation_invalid_format_target_client); tcase_add_test(tc_core, test_oidc_revocation_access_token_target_client); tcase_add_test(tc_core, test_oidc_revocation_refresh_token_target_client); tcase_add_test(tc_core, test_oidc_revocation_plugin_remove); tcase_add_test(tc_core, test_oidc_revocation_plugin_add_auth_scope); tcase_add_test(tc_core, test_oidc_revocation_invalid_format_bearer); tcase_add_test(tc_core, test_oidc_revocation_access_token_target_bearer); tcase_add_test(tc_core, test_oidc_revocation_refresh_token_target_bearer); tcase_add_test(tc_core, test_oidc_revocation_id_token); tcase_add_test(tc_core, test_oidc_revocation_plugin_remove); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&admin_req); ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); j_body = NULL; res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "user1" #define USER_PASSWORD "password" #define PLUGIN_NAME "oidc_claims" #define SCOPE_LIST "g_profile openid" #define SCOPE_1 "g_profile" #define SCOPE_2 "openid" #define CLIENT "client1_id" #define RESPONSE_TYPE "id_token token" #define CLAIM_STR "the-str" #define CLAIM_STR_2 "the-other-str" #define CLAIM_NUMBER "42" #define CLAIM_NUMBER_2 "43" #define CLAIM_BOOL_TRUE "1" #define CLAIM_BOOL_FALSE "0" #define CLAIM_MANDATORY "I'm aliiiiive!" struct _u_request admin_req; struct _u_request user_req; START_TEST(test_oidc_userinfo_add_plugin) { json_t * j_param = json_pack("{sssssss{sssssssssisisisososososososososssssss[{sssoss}{sssossss}{sssossssssss}{sssoss}{sssossss}{sssossssssss}{ssssso}]}}", "module", "oidc", "name", PLUGIN_NAME, "display_name", PLUGIN_NAME, "parameters", "iss", "https://glewlwyd.tld", "jwt-type", "sha", "jwt-key-size", "256", "key", "secret_" PLUGIN_NAME, "access-token-duration", 3600, "refresh-token-duration", 1209600, "code-duration", 600, "refresh-token-rolling", json_true(), "allow-non-oidc", json_true(), "auth-type-code-enabled", json_true(), "auth-type-token-enabled", json_true(), "auth-type-id-token-enabled", json_true(), "auth-type-password-enabled", json_true(), "auth-type-client-enabled", json_true(), "auth-type-refresh-enabled", json_true(), "name-claim", "mandatory", "email-claim", "mandatory", "scope-claim", "mandatory", "claims", "name", "claim-str", "mandatory", json_true(), "user-property", "claim-str", "name", "claim-number", "mandatory", json_true(), "type", "number", "user-property", "claim-number", "name", "claim-bool", "mandatory", json_true(), "type", "boolean", "user-property", "claim-bool", "boolean-value-true", "1", "boolean-value-false", "0", "name", "claim-array-str", "mandatory", json_true(), "user-property", "claim-array-str", "name", "claim-array-number", "mandatory", json_true(), "type", "number", "user-property", "claim-array-number", "name", "claim-array-bool", "mandatory", json_true(), "type", "boolean", "user-property", "claim-array-bool", "boolean-value-true", "1", "boolean-value-false", "0", "name", "claim-mandatory", "user-property", "claim-mandatory", "mandatory", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); j_param = json_pack("{sssssssss[ss]s[ss]s[ss]}", "claim-str", CLAIM_STR, "claim-number", CLAIM_NUMBER, "claim-bool", CLAIM_BOOL_TRUE, "claim-mandatory", CLAIM_MANDATORY, "claim-array-str", CLAIM_STR, CLAIM_STR_2, "claim-array-number", CLAIM_NUMBER, CLAIM_NUMBER_2, "claim-array-bool", CLAIM_BOOL_TRUE, CLAIM_BOOL_FALSE); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USER_USERNAME, NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST START_TEST(test_oidc_userinfo_noauth) { ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/" PLUGIN_NAME "/userinfo/", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" PLUGIN_NAME "/userinfo/", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_oidc_userinfo) { struct _u_response resp; struct _u_request req; char * access_token, * bearer; json_t * j_result; ulfius_init_response(&resp); ulfius_init_request(&req); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); access_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token=")); if (o_strchr(access_token, '&')) { *(o_strchr(access_token, '&')) = '\0'; } ulfius_clean_response(&resp); bearer = msprintf("Bearer %s", access_token); u_map_put(req.map_header, "Authorization", bearer); j_result = json_pack("{sssssssisosss[ss]s[ii]s[oo]s[ss]}", "name", "Dave Lopper 1", "email", "dev1@glewlwyd", "claim-str", CLAIM_STR, "claim-number", 42, "claim-bool", json_true(), "claim-mandatory", CLAIM_MANDATORY, "claim-array-str", CLAIM_STR, CLAIM_STR_2, "claim-array-number", 42, 43, "claim-array-bool", json_true(), json_false(), "scope", SCOPE_1, SCOPE_2); ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/" PLUGIN_NAME "/userinfo/", NULL, NULL, NULL, NULL, 200, j_result, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" PLUGIN_NAME "/userinfo/", NULL, NULL, NULL, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_result); ulfius_clean_request(&req); o_free(access_token); o_free(bearer); } END_TEST START_TEST(test_oidc_userinfo_jwt) { struct _u_response resp; struct _u_request req; char * access_token, * bearer, * body; json_t * j_result, * j_payload = NULL; jwt_t * jwt; jwk_t * jwk; ulfius_init_response(&resp); ulfius_init_request(&req); o_free(user_req.http_url); user_req.http_url = msprintf("%s/%s/auth?response_type=%s&g_continue&client_id=%s&redirect_uri=../../test-oauth2.html?param=client1_cb1&nonce=nonce1234&scope=%s", SERVER_URI, PLUGIN_NAME, RESPONSE_TYPE, CLIENT, SCOPE_LIST); o_free(user_req.http_verb); user_req.http_verb = o_strdup("GET"); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); ck_assert_ptr_ne(o_strstr(u_map_get(resp.map_header, "Location"), "access_token="), NULL); access_token = o_strdup(o_strstr(u_map_get(resp.map_header, "Location"), "access_token=") + o_strlen("access_token=")); if (o_strchr(access_token, '&')) { *(o_strchr(access_token, '&')) = '\0'; } ulfius_clean_response(&resp); bearer = msprintf("Bearer %s", access_token); u_map_put(req.map_header, "Authorization", bearer); j_result = json_pack("{sssssssisosss[ss]s[ii]s[oo]s[ss]}", "name", "Dave Lopper 1", "email", "dev1@glewlwyd", "claim-str", CLAIM_STR, "claim-number", 42, "claim-bool", json_true(), "claim-mandatory", CLAIM_MANDATORY, "claim-array-str", CLAIM_STR, CLAIM_STR_2, "claim-array-number", 42, 43, "claim-array-bool", json_true(), json_false(), "scope", SCOPE_1, SCOPE_2); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/userinfo/?format=jwt"); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_str_eq(u_map_get(resp.map_header, "Content-Type"), "application/jwt"); body = o_strndup(resp.binary_body, resp.binary_body_length); r_jwt_init(&jwt); jwk = r_jwk_quick_import(R_IMPORT_SYMKEY, ("secret_" PLUGIN_NAME), o_strlen("secret_" PLUGIN_NAME)); r_jwt_parse(jwt, body, 0); ck_assert_int_eq(r_jwt_verify_signature(jwt, jwk, 0), RHN_OK); ck_assert_ptr_ne((j_payload = r_jwt_get_full_claims_json_t(jwt)), NULL); ck_assert_ptr_ne(json_search(j_payload, j_result), NULL); json_decref(j_payload); ulfius_clean_response(&resp); o_free(body); r_jwt_free(jwt); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); o_free(req.http_url); req.http_url = o_strdup(SERVER_URI "/" PLUGIN_NAME "/userinfo/"); u_map_put(req.map_post_body, "format", "jwt"); req.http_verb = o_strdup("POST"); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_str_eq(u_map_get(resp.map_header, "Content-Type"), "application/jwt"); body = o_strndup(resp.binary_body, resp.binary_body_length); r_jwt_init(&jwt); r_jwt_parse(jwt, body, 0); ck_assert_int_eq(r_jwt_verify_signature(jwt, jwk, 0), RHN_OK); ck_assert_ptr_ne((j_payload = r_jwt_get_full_claims_json_t(jwt)), NULL); ck_assert_ptr_ne(json_search(j_payload, j_result), NULL); json_decref(j_payload); ulfius_clean_response(&resp); o_free(body); r_jwt_free(jwt); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); u_map_remove_from_key(req.map_post_body, "format"); u_map_put(req.map_header, "Accept", "application/jwt"); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_str_eq(u_map_get(resp.map_header, "Content-Type"), "application/jwt"); body = o_strndup(resp.binary_body, resp.binary_body_length); r_jwt_init(&jwt); r_jwt_parse(jwt, body, 0); ck_assert_int_eq(r_jwt_verify_signature(jwt, jwk, 0), RHN_OK); ck_assert_ptr_ne((j_payload = r_jwt_get_full_claims_json_t(jwt)), NULL); ck_assert_ptr_ne(json_search(j_payload, j_result), NULL); json_decref(j_payload); ulfius_clean_response(&resp); o_free(body); r_jwt_free(jwt); r_jwk_free(jwk); json_decref(j_result); ulfius_clean_request(&req); o_free(access_token); o_free(bearer); } END_TEST START_TEST(test_oidc_userinfo_delete_plugin) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" PLUGIN_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/user/" USER_USERNAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); json_t * j_param = json_pack("{ss ss ss so s[sssss]}", "username", USER_USERNAME, "name", "Dave Lopper 1", "email", "dev1@glewlwyd", "enabled", json_true(), "scope", "g_profile", "openid", "scope1", "scope2", "scope3"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/user/", NULL, NULL, j_param, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_param); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc userinfo"); tc_core = tcase_create("test_oidc_userinfo"); tcase_add_test(tc_core, test_oidc_userinfo_add_plugin); tcase_add_test(tc_core, test_oidc_userinfo_noauth); tcase_add_test(tc_core, test_oidc_userinfo); tcase_add_test(tc_core, test_oidc_userinfo_jwt); tcase_add_test(tc_core, test_oidc_userinfo_delete_plugin); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req, scope_req; struct _u_response auth_resp, scope_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); ulfius_init_request(&user_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&scope_req); ulfius_init_response(&auth_resp); ulfius_init_response(&scope_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "User %s authenticated", USER_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(scope_req.map_header, "Cookie", cookie); u_map_put(user_req.map_header, "Cookie", cookie); o_free(cookie); scope_req.http_verb = strdup("PUT"); scope_req.http_url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, &scope_resp) != U_OK || scope_resp.status != 200) { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope '%s' for %s error", CLIENT, SCOPE_LIST); do_test = 0; } else { y_log_message(Y_LOG_LEVEL_DEBUG, "Grant scope OK"); } ulfius_clean_response(&scope_resp); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication user"); do_test = 0; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); } if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } j_body = json_pack("{ss}", "scope", ""); ulfius_set_json_body_request(&scope_req, j_body); json_decref(j_body); if (ulfius_send_http_request(&scope_req, NULL) != U_OK) { y_log_message(Y_LOG_LEVEL_DEBUG, "Remove grant scope '%s' for %s error", CLIENT, SCOPE_LIST); } char * url = msprintf("%s/auth/", SERVER_URI); run_simple_test(&user_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL); o_free(url); ulfius_clean_request(&admin_req); ulfius_clean_request(&user_req); ulfius_clean_request(&scope_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_profile_delete.c000066400000000000000000000330141415646314000214060ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME "admin" #define PASSWORD "password" #define NEW_USERNAME "semias" #define NEW_NAME "Semias from somewhere" #define NEW_PASSWORD "password" #define NEW_EMAIL "esras@glewlwyd.tld" #define NEW_SCOPE "g_profile" #define NEW_SCOPE_2 "scope1" #define SCHEME_TYPE "mock" #define SCHEME_NAME_42 "mock_scheme_42" #define SCHEME_NAME_88 "mock_scheme_88" #define SCHEME_NAME_95 "mock_scheme_95" #define SCHEME_VALUE_42 "42" struct _u_request admin_req; START_TEST(test_glwd_profile_delete_add_user) { json_t * j_parameters = json_pack("{sssssssos[ss]}", "username", NEW_USERNAME, "name", NEW_NAME, "email", NEW_EMAIL, "enabled", json_true(), "scope", NEW_SCOPE, NEW_SCOPE_2); ck_assert_ptr_ne(j_parameters, NULL); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/user/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/user/" NEW_USERNAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_profile_delete_delete_user) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/user/" NEW_USERNAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_profile_delete_register_schemes) { struct _u_request req; struct _u_response resp; json_t * j_body; char * cookie; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", NEW_USERNAME, "password", NEW_PASSWORD); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_gt(resp.nb_cookies, 0); cookie = msprintf("%s=%s", resp.map_cookie[0].key, resp.map_cookie[0].value); ck_assert_ptr_ne(cookie, NULL); ck_assert_int_eq(u_map_put(req.map_header, "Cookie", cookie), U_OK); o_free(cookie); ulfius_clean_response(&resp); j_body = json_pack("{ssssss}", "username", NEW_USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME_42); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{sssssss{so}}", "username", NEW_USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME_42, "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ssssss}", "username", NEW_USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME_42); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ssssss}", "username", NEW_USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME_88); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{sssssss{so}}", "username", NEW_USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME_88, "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ssssss}", "username", NEW_USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME_88); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ssssss}", "username", NEW_USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME_95); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{sssssss{so}}", "username", NEW_USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME_95, "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ssssss}", "username", NEW_USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME_95); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_profile_delete_delete_profile_unavailable) { struct _u_request req; struct _u_response resp; json_t * j_body; char * cookie; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", NEW_USERNAME, "password", NEW_PASSWORD); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_gt(resp.nb_cookies, 0); cookie = msprintf("%s=%s", resp.map_cookie[0].key, resp.map_cookie[0].value); ck_assert_ptr_ne(cookie, NULL); ck_assert_int_eq(u_map_put(req.map_header, "Cookie", cookie), U_OK); o_free(cookie); ulfius_clean_response(&resp); ck_assert_int_eq(run_simple_test(&req, "DELETE", SERVER_URI "/profile/", NULL, NULL, NULL, NULL, 403, NULL, NULL, NULL), 1); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_profile_delete_delete_profile_delete) { struct _u_request req; struct _u_response resp; json_t * j_body; char * cookie; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", NEW_USERNAME, "password", NEW_PASSWORD); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_gt(resp.nb_cookies, 0); cookie = msprintf("%s=%s", resp.map_cookie[0].key, resp.map_cookie[0].value); ck_assert_ptr_ne(cookie, NULL); ck_assert_int_eq(u_map_put(req.map_header, "Cookie", cookie), U_OK); o_free(cookie); ulfius_clean_response(&resp); ck_assert_int_eq(run_simple_test(&req, "DELETE", SERVER_URI "/profile/", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/profile_list/", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ulfius_clean_request(&req); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/user/" NEW_USERNAME, NULL, NULL, NULL, NULL, 404, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_profile_delete_delete_profile_disable) { struct _u_request req; struct _u_response resp; json_t * j_body; char * cookie; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", NEW_USERNAME, "password", NEW_PASSWORD); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_gt(resp.nb_cookies, 0); cookie = msprintf("%s=%s", resp.map_cookie[0].key, resp.map_cookie[0].value); ck_assert_ptr_ne(cookie, NULL); ck_assert_int_eq(u_map_put(req.map_header, "Cookie", cookie), U_OK); o_free(cookie); ulfius_clean_response(&resp); ck_assert_int_eq(run_simple_test(&req, "DELETE", SERVER_URI "/profile/", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/profile_list/", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ulfius_clean_request(&req); j_body = json_pack("{sssssssos[ss]}", "username", NEW_USERNAME, "name", NEW_NAME, "email", NEW_EMAIL, "enabled", json_false(), "scope", NEW_SCOPE, NEW_SCOPE_2); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/user/" NEW_USERNAME, NULL, NULL, NULL, NULL, 200, j_body, NULL, NULL), 1); json_decref(j_body); } END_TEST START_TEST(test_glwd_profile_delete_auth_unavailable) { struct _u_request req; struct _u_response resp; json_t * j_body; ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", NEW_USERNAME, "password", NEW_PASSWORD); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ck_assert_int_eq(resp.nb_cookies, 0); j_body = json_pack("{sssssss{ss}}", "username", NEW_USERNAME, "scheme_type", SCHEME_TYPE, "scheme_name", SCHEME_NAME_42, "value", "code", SCHEME_VALUE_42); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ck_assert_int_eq(resp.nb_cookies, 0); ulfius_clean_response(&resp); ulfius_clean_request(&req); } END_TEST static Suite *glewlwyd_suite_no(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd profile delete - no"); tc_core = tcase_create("test_glwd_profile_delete_no"); tcase_add_test(tc_core, test_glwd_profile_delete_add_user); tcase_add_test(tc_core, test_glwd_profile_delete_register_schemes); tcase_add_test(tc_core, test_glwd_profile_delete_delete_profile_unavailable); tcase_add_test(tc_core, test_glwd_profile_delete_delete_user); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } static Suite *glewlwyd_suite_delete(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd profile delete - delete"); tc_core = tcase_create("test_glwd_profile_delete_delete"); tcase_add_test(tc_core, test_glwd_profile_delete_add_user); tcase_add_test(tc_core, test_glwd_profile_delete_register_schemes); tcase_add_test(tc_core, test_glwd_profile_delete_delete_profile_delete); tcase_add_test(tc_core, test_glwd_profile_delete_auth_unavailable); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } static Suite *glewlwyd_suite_disable(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd profile delete - disable"); tc_core = tcase_create("test_glwd_profile_delete_disable"); tcase_add_test(tc_core, test_glwd_profile_delete_add_user); tcase_add_test(tc_core, test_glwd_profile_delete_register_schemes); tcase_add_test(tc_core, test_glwd_profile_delete_delete_profile_disable); tcase_add_test(tc_core, test_glwd_profile_delete_auth_unavailable); tcase_add_test(tc_core, test_glwd_profile_delete_delete_user); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0, i; json_t * j_body; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_request(&admin_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i 1 && 0 == o_strcmp("delete", argv[1])) { s = glewlwyd_suite_delete(); } else if (argc > 1 && 0 == o_strcmp("disable", argv[1])) { s = glewlwyd_suite_disable(); } else { s = glewlwyd_suite_no(); } sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } ulfius_clean_request(&admin_req); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_prometheus.c000066400000000000000000000420231415646314000206170ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define METRICS_URI "http://localhost:4594/" #define USERNAME "user1" #define PASSWORD "password" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define SCOPE_LIST "g_profile openid" #define CLIENT "client3_id" #define CLIENT_SECRET "password" #define CLIENT_REDIRECT_URI "../../test-oauth2.html?param=client3" #define RESPONSE_TYPE "code" struct _u_request admin_req; struct _u_request user_req; static int get_metrics(const char * metrics, json_t * labels) { struct _u_request req; struct _u_response resp; char * pattern, * str_result = NULL, ** lines = NULL; const char * key = NULL; json_t * j_element = NULL; int first = 1, int_result = -1; size_t i, pattern_len; ck_assert_ptr_ne(NULL, pattern = o_strdup(metrics)); if (labels != NULL) { ck_assert_ptr_ne(NULL, pattern = mstrcatf(pattern, "{")); json_object_foreach(labels, key, j_element) { if (!first) { ck_assert_ptr_ne(NULL, pattern = mstrcatf(pattern, ", ")); } else { first = 0; } ck_assert_ptr_ne(NULL, pattern = mstrcatf(pattern, "%s=\"%s\"", key, json_string_value(j_element))); } ck_assert_ptr_ne(NULL, pattern = mstrcatf(pattern, "}")); } ck_assert_ptr_ne(NULL, pattern = mstrcatf(pattern, " ")); ck_assert_int_gt(pattern_len = o_strlen(pattern), 0); ck_assert_int_eq(ulfius_init_request(&req), U_OK); ck_assert_int_eq(ulfius_init_response(&resp), U_OK); ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "GET", U_OPT_HTTP_URL, METRICS_URI, U_OPT_NONE), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(NULL, str_result = o_strndup(resp.binary_body, resp.binary_body_length)); ck_assert_int_gt(split_string(str_result, "\n", &lines), 0); for (i=0; lines[i] != NULL; i++) { if (o_strncmp(lines[i], pattern, pattern_len) == 0) { int_result = (int)strtol(lines[i]+pattern_len, NULL, 10); break; } } free_string_array(lines); o_free(pattern); o_free(str_result); ulfius_clean_request(&req); ulfius_clean_response(&resp); return int_result; } START_TEST(test_glwd_prometheus_metrics_open_ok) { ck_assert_int_eq(run_simple_test(NULL, "GET", METRICS_URI, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_prometheus_metrics_auth_pwd_increase) { int nbauth_1, nbauth_2, nbauth_lab_1, nbauth_lab_2, nbauth_scheme_1, nbauth_scheme_2; struct _u_request req; struct _u_response resp; json_t * j_body = NULL, * j_label_pwd = json_pack("{ss}", "scheme_type", "password"), * j_label_scheme = json_pack("{ssss}", "scheme_type", "mock", "scheme_name", "mock_scheme_42"); ck_assert_int_ne(-1, nbauth_1 = get_metrics("glewlwyd_auth_user_valid_total", NULL)); ck_assert_int_ne(-1, nbauth_lab_1 = get_metrics("glewlwyd_auth_user_valid_scheme_total", j_label_pwd)); ck_assert_int_ne(-1, nbauth_scheme_1 = get_metrics("glewlwyd_auth_user_valid_scheme_total", j_label_scheme)); ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_eq(resp.nb_cookies, 1); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_ne(-1, nbauth_2 = get_metrics("glewlwyd_auth_user_valid_total", NULL)); ck_assert_int_ne(-1, nbauth_lab_2 = get_metrics("glewlwyd_auth_user_valid_scheme_total", j_label_pwd)); ck_assert_int_ne(-1, nbauth_scheme_2 = get_metrics("glewlwyd_auth_user_valid_scheme_total", j_label_scheme)); ck_assert_int_eq(nbauth_2, nbauth_1+1); ck_assert_int_eq(nbauth_lab_2, nbauth_lab_1+1); ck_assert_int_eq(nbauth_scheme_1, nbauth_scheme_2); json_decref(j_label_pwd); json_decref(j_label_scheme); } END_TEST START_TEST(test_glwd_prometheus_metrics_auth_invalid_pwd_increase) { int nbauth_1, nbauth_2, nbauth_lab_1, nbauth_lab_2, nbauth_scheme_1, nbauth_scheme_2; struct _u_request req; struct _u_response resp; json_t * j_body = NULL, * j_label_pwd = json_pack("{ss}", "scheme_type", "password"), * j_label_scheme = json_pack("{ssss}", "scheme_type", "mock", "scheme_name", "mock_scheme_42"); ck_assert_int_ne(-1, nbauth_1 = get_metrics("glewlwyd_auth_user_invalid_total", NULL)); ck_assert_int_ne(-1, nbauth_lab_1 = get_metrics("glewlwyd_auth_user_invalid_scheme_total", j_label_pwd)); ck_assert_int_ne(-1, nbauth_scheme_1 = get_metrics("glewlwyd_auth_user_invalid_scheme_total", j_label_scheme)); ulfius_init_request(&req); ulfius_init_response(&resp); req.http_verb = strdup("POST"); req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", "error"); ulfius_set_json_body_request(&req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 401); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_ne(-1, nbauth_2 = get_metrics("glewlwyd_auth_user_invalid_total", NULL)); ck_assert_int_ne(-1, nbauth_lab_2 = get_metrics("glewlwyd_auth_user_invalid_scheme_total", j_label_pwd)); ck_assert_int_ne(-1, nbauth_scheme_2 = get_metrics("glewlwyd_auth_user_invalid_scheme_total", j_label_scheme)); ck_assert_int_eq(nbauth_2, nbauth_1+1); ck_assert_int_eq(nbauth_lab_2, nbauth_lab_1+1); ck_assert_int_eq(nbauth_scheme_1, nbauth_scheme_2); json_decref(j_label_pwd); json_decref(j_label_scheme); } END_TEST START_TEST(test_glwd_prometheus_metrics_oidc_flow_ok) { struct _u_request auth_req; struct _u_response auth_resp, resp; struct _u_map body; json_t * j_body; char * cookie; char * url, * redirect_uri_encoded, * code; int code_total_1, code_total_2, idt_total_1, idt_total_2, at_total_1, at_total_2; ck_assert_int_ne(-1, code_total_1 = get_metrics("glewlwyd_oidc_code_total{plugin=\"oidc\"}", NULL)); ck_assert_int_ne(-1, idt_total_1 = get_metrics("glewlwyd_oidc_id_token_total{plugin=\"oidc\"}", NULL)); ck_assert_int_ne(-1, at_total_1 = get_metrics("glewlwyd_oidc_access_token_total{plugin=\"oidc\"}", NULL)); ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_gt(auth_resp.nb_cookies, 0); ck_assert_ptr_ne((cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value)), NULL); ck_assert_int_eq(u_map_put(auth_req.map_header, "Cookie", cookie), U_OK); ulfius_clean_response(&auth_resp); url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", url, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); o_free(url); // Test id_token framework redirect_uri_encoded = ulfius_url_encode(CLIENT_REDIRECT_URI); url = msprintf("%s/oidc/auth?response_type=id_token&nonce=nonce1234&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce4321&scope=%s", SERVER_URI, CLIENT, redirect_uri_encoded, SCOPE_LIST); ck_assert_int_eq(run_simple_test(&auth_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "id_token="), 1); o_free(url); ck_assert_int_ne(-1, idt_total_2 = get_metrics("glewlwyd_oidc_id_token_total{plugin=\"oidc\"}", NULL)); ck_assert_int_eq(idt_total_2, idt_total_1+1); // Test code framework o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_url = msprintf("%s/oidc/auth?response_type=code&nonce=nonce1234&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&scope=%s", SERVER_URI, CLIENT, redirect_uri_encoded, SCOPE_LIST); auth_req.http_verb = o_strdup("GET"); auth_req.auth_basic_user = o_strdup(CLIENT); auth_req.auth_basic_password = o_strdup(CLIENT_SECRET); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); code = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } url = msprintf("%s/oidc/token/", SERVER_URI); u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", CLIENT_REDIRECT_URI); u_map_put(&body, "code", code); ck_assert_int_eq(run_simple_test(NULL, "POST", url, CLIENT, CLIENT_SECRET, NULL, &body, 200, NULL, "id_token", NULL), 1); u_map_clean(&body); ulfius_clean_response(&resp); o_free(url); ck_assert_int_ne(-1, code_total_2 = get_metrics("glewlwyd_oidc_code_total{plugin=\"oidc\"}", NULL)); ck_assert_int_eq(code_total_2, code_total_1+1); ck_assert_int_ne(-1, idt_total_2 = get_metrics("glewlwyd_oidc_id_token_total{plugin=\"oidc\"}", NULL)); ck_assert_int_eq(idt_total_2, idt_total_1+2); ck_assert_int_ne(-1, at_total_2 = get_metrics("glewlwyd_oidc_access_token_total{plugin=\"oidc\"}", NULL)); ck_assert_int_eq(at_total_2, at_total_1+1); ulfius_clean_request(&auth_req); o_free(cookie); o_free(code); o_free(redirect_uri_encoded); } END_TEST START_TEST(test_glwd_prometheus_metrics_glwd_flow_ok) { struct _u_request auth_req; struct _u_response auth_resp, resp; struct _u_map body; json_t * j_body; char * cookie; char * url, * redirect_uri_encoded, * code; int code_total_1, code_total_2, at_total_1, at_total_2; ck_assert_int_ne(-1, code_total_1 = get_metrics("glewlwyd_oauth2_code_total{plugin=\"glwd\"}", NULL)); ck_assert_int_ne(-1, at_total_1 = get_metrics("glewlwyd_oauth2_access_token_total{plugin=\"glwd\"}", NULL)); ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &auth_resp), U_OK); ck_assert_int_eq(auth_resp.status, 200); ck_assert_int_gt(auth_resp.nb_cookies, 0); ck_assert_ptr_ne((cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value)), NULL); ck_assert_int_eq(u_map_put(auth_req.map_header, "Cookie", cookie), U_OK); ulfius_clean_response(&auth_resp); url = msprintf("%s/auth/grant/%s", SERVER_URI, CLIENT); j_body = json_pack("{ss}", "scope", SCOPE_LIST); ck_assert_int_eq(run_simple_test(&auth_req, "PUT", url, NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); o_free(url); // Test token framework redirect_uri_encoded = ulfius_url_encode(CLIENT_REDIRECT_URI); url = msprintf("%s/glwd/auth?response_type=token&nonce=nonce1234&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&nonce=nonce4321&scope=%s", SERVER_URI, CLIENT, redirect_uri_encoded, SCOPE_LIST); ck_assert_int_eq(run_simple_test(&auth_req, "GET", url, NULL, NULL, NULL, NULL, 302, NULL, NULL, "token="), 1); o_free(url); ck_assert_int_ne(-1, at_total_2 = get_metrics("glewlwyd_oauth2_access_token_total{plugin=\"glwd\"}", NULL)); ck_assert_int_eq(at_total_2, at_total_1+1); // Test code framework o_free(auth_req.http_verb); o_free(auth_req.http_url); auth_req.http_url = msprintf("%s/glwd/auth?response_type=code&nonce=nonce1234&g_continue&client_id=%s&redirect_uri=%s&state=xyzabcd&scope=%s", SERVER_URI, CLIENT, redirect_uri_encoded, SCOPE_LIST); auth_req.http_verb = o_strdup("GET"); auth_req.auth_basic_user = o_strdup(CLIENT); auth_req.auth_basic_password = o_strdup(CLIENT_SECRET); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_send_http_request(&auth_req, &resp), U_OK); ck_assert_int_eq(resp.status, 302); code = o_strdup(strstr(u_map_get(resp.map_header, "Location"), "code=")+strlen("code=")); if (strchr(code, '&') != NULL) { *strchr(code, '&') = '\0'; } url = msprintf("%s/glwd/token/", SERVER_URI); u_map_init(&body); u_map_put(&body, "grant_type", "authorization_code"); u_map_put(&body, "client_id", CLIENT); u_map_put(&body, "redirect_uri", CLIENT_REDIRECT_URI); u_map_put(&body, "code", code); ck_assert_int_eq(run_simple_test(NULL, "POST", url, CLIENT, CLIENT_SECRET, NULL, &body, 200, NULL, "token", NULL), 1); u_map_clean(&body); ulfius_clean_response(&resp); o_free(url); ck_assert_int_ne(-1, code_total_2 = get_metrics("glewlwyd_oauth2_code_total{plugin=\"glwd\"}", NULL)); ck_assert_int_eq(code_total_2, code_total_1+1); ck_assert_int_ne(-1, at_total_2 = get_metrics("glewlwyd_oauth2_access_token_total{plugin=\"glwd\"}", NULL)); ck_assert_int_eq(at_total_2, at_total_1+2); ulfius_clean_request(&auth_req); o_free(cookie); o_free(code); o_free(redirect_uri_encoded); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd prometheus_metrics"); tc_core = tcase_create("test_glwd_prometheus_metrics"); tcase_add_test(tc_core, test_glwd_prometheus_metrics_open_ok); tcase_add_test(tc_core, test_glwd_prometheus_metrics_auth_pwd_increase); tcase_add_test(tc_core, test_glwd_prometheus_metrics_auth_invalid_pwd_increase); tcase_add_test(tc_core, test_glwd_prometheus_metrics_oidc_flow_ok); tcase_add_test(tc_core, test_glwd_prometheus_metrics_glwd_flow_ok); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&admin_req); admin_req.check_server_certificate = 0; ulfius_init_request(&user_req); user_req.check_server_certificate = 0; ulfius_init_request(&auth_req); auth_req.check_server_certificate = 0; ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define USERNAME_ADMIN "admin" #define USERNAME "user1" #define EMAIL "dev1@glewlwyd" #define PASSWORD "password" #define MOD_TYPE "register" #define MOD_NAME "register" #define MOD_DISPLAY_NAME "Register" #define SESSION_KEY "G_REGISTER_SESSION" #define SESSION_RESET_CREDENTIALS_KEY "G_CREDENTIALS_SESSION" #define SESSION_DURATION 3600 #define SCOPE "g_profile" #define SCOPE_SCHEME "scope2" #define SCHEME_TYPE "mock" #define SCHEME_NAME "mock_scheme_95" #define SCHEME_DISPLAY_NAME "Mock 95" #define NEW_USERNAME "semias" #define NEW_USERNAME_CANCELLED "esras" #define NEW_USERNAME_INVALID_SESSION "morfessa" #define NEW_NAME "Semias from somewhere" #define NEW_PASSWORD "newpassword" #define NEW_EMAIL "esras@glewlwyd.tld" #define MAIL_CODE_DURATION 600 #define MAIL_CODE_LEGTH 6 #define MAIL_HOST "localhost" #define MAIL_PORT_WITH_USERNAME 2526 #define MAIL_PORT_WITHOUT_USERNAME 2527 #define MAIL_PORT_CODE_EXPIRED 2528 #define MAIL_PORT_MULTILANG 2529 #define MAIL_PORT_UPDATE_EMAIL_INVALID_TOKEN 2530 #define MAIL_PORT_UPDATE_EMAIL_VALID_TOKEN 2531 #define MAIL_PORT_RESET_CREDENTIALS_EMAIL_INVALID_TOKEN 2532 #define MAIL_PORT_RESET_CREDENTIALS_EMAIL_VALID_TOKEN 2533 #define MAIL_PORT_RESET_CREDENTIALS_EMAIL_FLOW 2533 #define MAIL_FROM "glewlwyd" #define MAIL_SUBJECT "Authorization Code" #define MAIL_SUBJECT_FR "Code d'autorisation" #define MAIL_CONTENT_TYPE "plain/text" #define MAIL_BODY_PATTERN "The code or token is " #define MAIL_BODY_PATTERN_FR "Le code ou token se trouve ÃĒtre le suivant " #define MAIL_BODY_CODE "{CODE}" #define MAIL_BODY_TOKEN "{TOKEN}" #define RESET_CREDENTIALS_CODE_PROPERTY "reset-credentials-code-property" #define RESET_CREDENTIALS_CODE_LIST_SIZE 4 #define GLEWLWYD_TOKEN_LENGTH 32 char * mail_host = NULL; #define BACKLOG_MAX (10) #define BUF_SIZE 4096 #define STREQU(a,b) (strcmp(a, b) == 0) struct _u_request admin_req; struct _u_request user_req; struct smtp_manager { char * mail_data; unsigned int port; int sockfd; const char * body_pattern; }; /** * * Function that emulates a very simple SMTP server * Taken from Kenneth Finnegan's ccsmtp program * https://gist.github.com/PhirePhly/2914635 * This function is under the GPL2 license * */ static void handle_smtp (struct smtp_manager * manager) { int rc, i; char buffer[BUF_SIZE], bufferout[BUF_SIZE]; int buffer_offset = 0; buffer[BUF_SIZE-1] = '\0'; // Flag for being inside of DATA verb int inmessage = 0; sprintf(bufferout, "220 ulfius.tld SMTP CCSMTP\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); while (1) { fd_set sockset; struct timeval tv; FD_ZERO(&sockset); FD_SET(manager->sockfd, &sockset); tv.tv_sec = 120; // Some SMTP servers pause for ~15s per message tv.tv_usec = 0; // Wait tv timeout for the server to send anything. select(manager->sockfd+1, &sockset, NULL, NULL, &tv); if (!FD_ISSET(manager->sockfd, &sockset)) { y_log_message(Y_LOG_LEVEL_DEBUG, "%d: Socket timed out", manager->sockfd); break; } int buffer_left = BUF_SIZE - buffer_offset - 1; if (buffer_left == 0) { y_log_message(Y_LOG_LEVEL_DEBUG, "%d: Command line too long", manager->sockfd); sprintf(bufferout, "500 Too long\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); buffer_offset = 0; continue; } rc = recv(manager->sockfd, buffer + buffer_offset, buffer_left, 0); if (rc == 0) { y_log_message(Y_LOG_LEVEL_DEBUG, "%d: Remote host closed socket", manager->sockfd); break; } if (rc == -1) { y_log_message(Y_LOG_LEVEL_DEBUG, "%d: Error on socket", manager->sockfd); break; } buffer_offset += rc; char *eol; // Only process one line of the received buffer at a time // If multiple lines were received in a single recv(), goto // back to here for each line // processline: eol = strstr(buffer, "\r\n"); if (eol == NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "%d: Haven't found EOL yet", manager->sockfd); continue; } // Null terminate each line to be processed individually eol[0] = '\0'; if (!inmessage) { // Handle system verbs // Replace all lower case letters so verbs are all caps for (i=0; i<4; i++) { if (islower(buffer[i])) { buffer[i] += 'A' - 'a'; } } // Null-terminate the verb for strcmp buffer[4] = '\0'; // Respond to each verb accordingly. // You should replace these with more meaningful // actions than simply printing everything. // if (STREQU(buffer, "HELO")) { // Initial greeting sprintf(bufferout, "250 Ok\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); } else if (STREQU(buffer, "MAIL")) { // New mail from... sprintf(bufferout, "250 Ok\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); } else if (STREQU(buffer, "RCPT")) { // Mail addressed to... sprintf(bufferout, "250 Ok recipient\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); } else if (STREQU(buffer, "DATA")) { // Message contents... sprintf(bufferout, "354 Continue\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); inmessage = 1; } else if (STREQU(buffer, "RSET")) { // Reset the connection sprintf(bufferout, "250 Ok reset\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); } else if (STREQU(buffer, "NOOP")) { // Do nothing. sprintf(bufferout, "250 Ok noop\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); } else if (STREQU(buffer, "QUIT")) { // Close the connection sprintf(bufferout, "221 Ok\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); break; } else { // The verb used hasn't been implemented. sprintf(bufferout, "502 Command Not Implemented\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); } } else { // We are inside the message after a DATA verb. if (0 == o_strncmp(manager->body_pattern, buffer, o_strlen(manager->body_pattern))) { manager->mail_data = o_strdup(buffer+o_strlen(manager->body_pattern)); } if (STREQU(buffer, ".")) { // A single "." signifies the end sprintf(bufferout, "250 Ok\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); inmessage = 0; } } // Shift the rest of the buffer to the front memmove(buffer, eol+2, BUF_SIZE - (eol + 2 - buffer)); buffer_offset -= (eol - buffer) + 2; // Do we already have additional lines to process? If so, // commit a horrid sin and goto the line processing section again. if (strstr(buffer, "\r\n")) goto processline; } // All done. Clean up everything and exit. shutdown(manager->sockfd, SHUT_WR); close(manager->sockfd); } static void * simple_smtp(void * args) { struct smtp_manager * manager = (struct smtp_manager *)args; int server_fd; struct sockaddr_in address; int opt = 1; int addrlen = sizeof(address); if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) != 0) { if (!setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) { address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons( manager->port ); if (!bind(server_fd, (struct sockaddr *)&address, sizeof(address))) { if (listen(server_fd, 3) >= 0) { if ((manager->sockfd = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) >= 0) { handle_smtp(manager); } else { y_log_message(Y_LOG_LEVEL_ERROR, "simple_smtp - Error accept"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "simple_smtp - Error listen"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "simple_smtp - Error bind"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "simple_smtp - Error setsockopt"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "simple_smtp - Error socket"); } shutdown(server_fd, SHUT_RDWR); close(server_fd); pthread_exit(NULL); } START_TEST(test_glwd_register_add_mod_error) { json_t * j_body = json_pack("{ss ss ss s{si si s[ss] ss s[{ss ss ss ss}] so} so}", "module", MOD_TYPE, "name", MOD_NAME, "display_name", MOD_DISPLAY_NAME, "parameters", "session-key", SESSION_DURATION, "session-duration", SESSION_DURATION, "scope", SCOPE, SCOPE_SCHEME, "set-password", "always", "schemes", "module", SCHEME_TYPE, "name", SCHEME_NAME, "register", "always", "display_name", SCHEME_DISPLAY_NAME, "verify-email", json_false(), "enabled", json_true()), * j_error = json_string("session-key is mandatory and must be a non empty string"); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_body, NULL, 400, j_error, NULL, NULL), 1); json_decref(j_body); json_decref(j_error); } END_TEST START_TEST(test_glwd_register_add_mod_noverify) { /* { "module":"register", "name":"register", "display_name":"Register", "parameters":{ "session-key":"G_REGISTER_SESSIONE", "session-duration":3600, "scope":["g_profile"], "set-password":"no", "schemes":[ {"module":"otp","name":"otp","register":"always","display_name":"OTP"}, {"module":"webauthn","name":"webauthn","register":"yes","display_name":"Webauthn"} ], "verify-email":true, "email-is-username":true, "verification-code-length":8, "verification-code-duration":600, "host":"localhost", "port":0, "user":"", "password":"", "from":"glewlwyd@localhost", "subject":"Term", "content-type":"", "body-pattern":"Le code est {CODE}", }, "enabled":true }*/ json_t * j_body = json_pack("{ss ss ss s{ss si s[ss] ss s[{ss ss ss ss}] so} so}", "module", MOD_TYPE, "name", MOD_NAME, "display_name", MOD_DISPLAY_NAME, "parameters", "session-key", SESSION_KEY, "session-duration", SESSION_DURATION, "scope", SCOPE, SCOPE_SCHEME, "set-password", "always", "schemes", "module", SCHEME_TYPE, "name", SCHEME_NAME, "register", "always", "display_name", SCHEME_DISPLAY_NAME, "verify-email", json_false(), "enabled", json_true()); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); } END_TEST START_TEST(test_glwd_register_add_mod_verify_with_username) { json_t * j_body = json_pack("{ss ss ss s{ss si s[ss] ss s[{ss ss ss ss}] so so si si ss si ss ss ss ss} so}", "module", MOD_TYPE, "name", MOD_NAME, "display_name", MOD_DISPLAY_NAME, "parameters", "session-key", SESSION_KEY, "session-duration", SESSION_DURATION, "scope", SCOPE, SCOPE_SCHEME, "set-password", "always", "schemes", "module", SCHEME_TYPE, "name", SCHEME_NAME, "register", "always", "display_name", SCHEME_DISPLAY_NAME, "verify-email", json_true(), "email-is-username", json_false(), "verification-code-length", MAIL_CODE_LEGTH, "verification-code-duration", MAIL_CODE_DURATION, "host", MAIL_HOST, "port", MAIL_PORT_WITH_USERNAME, "from", MAIL_FROM, "subject", MAIL_SUBJECT, "content-type", MAIL_CONTENT_TYPE, "body-pattern", MAIL_BODY_PATTERN MAIL_BODY_CODE, "enabled", json_true()); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); } END_TEST START_TEST(test_glwd_register_add_mod_verify_without_username) { json_t * j_body = json_pack("{ss ss ss s{ss si s[ss] ss s[{ss ss ss ss}] so so si si ss si ss ss ss ss} so}", "module", MOD_TYPE, "name", MOD_NAME, "display_name", MOD_DISPLAY_NAME, "parameters", "session-key", SESSION_KEY, "session-duration", SESSION_DURATION, "scope", SCOPE, SCOPE_SCHEME, "set-password", "always", "schemes", "module", SCHEME_TYPE, "name", SCHEME_NAME, "register", "always", "display_name", SCHEME_DISPLAY_NAME, "verify-email", json_true(), "email-is-username", json_true(), "verification-code-length", MAIL_CODE_LEGTH, "verification-code-duration", MAIL_CODE_DURATION, "host", MAIL_HOST, "port", MAIL_PORT_WITHOUT_USERNAME, "from", MAIL_FROM, "subject", MAIL_SUBJECT, "content-type", MAIL_CONTENT_TYPE, "body-pattern", MAIL_BODY_PATTERN MAIL_BODY_CODE, "enabled", json_true()); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); } END_TEST START_TEST(test_glwd_register_add_mod_verify_with_username_token) { json_t * j_body = json_pack("{ss ss ss s{ss si s[ss] ss s[{ss ss ss ss}] so so si si ss si ss ss ss ss} so}", "module", MOD_TYPE, "name", MOD_NAME, "display_name", MOD_DISPLAY_NAME, "parameters", "session-key", SESSION_KEY, "session-duration", SESSION_DURATION, "scope", SCOPE, SCOPE_SCHEME, "set-password", "always", "schemes", "module", SCHEME_TYPE, "name", SCHEME_NAME, "register", "always", "display_name", SCHEME_DISPLAY_NAME, "verify-email", json_true(), "email-is-username", json_false(), "verification-code-length", MAIL_CODE_LEGTH, "verification-code-duration", MAIL_CODE_DURATION, "host", MAIL_HOST, "port", MAIL_PORT_WITH_USERNAME, "from", MAIL_FROM, "subject", MAIL_SUBJECT, "content-type", MAIL_CONTENT_TYPE, "body-pattern", MAIL_BODY_PATTERN MAIL_BODY_TOKEN, "enabled", json_true()); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); } END_TEST START_TEST(test_glwd_register_add_mod_noverify_session_expired) { json_t * j_body = json_pack("{ss ss ss s{ss si s[ss] ss s[{ss ss ss ss}] so} so}", "module", MOD_TYPE, "name", MOD_NAME, "display_name", MOD_DISPLAY_NAME, "parameters", "session-key", SESSION_KEY, "session-duration", 1, "scope", SCOPE, SCOPE_SCHEME, "set-password", "always", "schemes", "module", SCHEME_TYPE, "name", SCHEME_NAME, "register", "always", "display_name", SCHEME_DISPLAY_NAME, "verify-email", json_false(), "enabled", json_true()); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); } END_TEST START_TEST(test_glwd_register_add_mod_verify_without_username_code_expired) { json_t * j_body = json_pack("{ss ss ss s{ss si s[ss] ss s[{ss ss ss ss}] so so si si ss si ss ss ss ss} so}", "module", MOD_TYPE, "name", MOD_NAME, "display_name", MOD_DISPLAY_NAME, "parameters", "session-key", SESSION_KEY, "session-duration", SESSION_DURATION, "scope", SCOPE, SCOPE_SCHEME, "set-password", "always", "schemes", "module", SCHEME_TYPE, "name", SCHEME_NAME, "register", "always", "display_name", SCHEME_DISPLAY_NAME, "verify-email", json_true(), "email-is-username", json_true(), "verification-code-length", MAIL_CODE_LEGTH, "verification-code-duration", 1, "host", MAIL_HOST, "port", MAIL_PORT_CODE_EXPIRED, "from", MAIL_FROM, "subject", MAIL_SUBJECT, "content-type", MAIL_CONTENT_TYPE, "body-pattern", MAIL_BODY_PATTERN MAIL_BODY_CODE, "enabled", json_true()); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); } END_TEST START_TEST(test_glwd_register_add_mod_verify_multilang_without_username) { json_t * j_body = json_pack("{ss ss ss so s{ss si s[ss] ss s[{ss ss ss ss}] so so si si ss si ss ss s{s{sossss}s{sossss}}}}", "module", MOD_TYPE, "name", MOD_NAME, "display_name", MOD_DISPLAY_NAME, "enabled", json_true(), "parameters", "session-key", SESSION_KEY, "session-duration", SESSION_DURATION, "scope", SCOPE, SCOPE_SCHEME, "set-password", "always", "schemes", "module", SCHEME_TYPE, "name", SCHEME_NAME, "register", "always", "display_name", SCHEME_DISPLAY_NAME, "verify-email", json_true(), "email-is-username", json_true(), "verification-code-length", MAIL_CODE_LEGTH, "verification-code-duration", MAIL_CODE_DURATION, "host", MAIL_HOST, "port", MAIL_PORT_MULTILANG, "from", MAIL_FROM, "content-type", MAIL_CONTENT_TYPE, "templates", "en", "defaultLang", json_true(), "subject", MAIL_SUBJECT, "body-pattern", MAIL_BODY_PATTERN MAIL_BODY_CODE, "fr", "defaultLang", json_false(), "subject", MAIL_SUBJECT_FR, "body-pattern", MAIL_BODY_PATTERN_FR MAIL_BODY_CODE); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); } END_TEST START_TEST(test_glwd_register_add_mod_noverify_registration_enabled) { json_t * j_body = json_pack("{ss ss ss s{so ss si s[ss] ss s[{ss ss ss ss}] so} so}", "module", MOD_TYPE, "name", MOD_NAME, "display_name", MOD_DISPLAY_NAME, "parameters", "registration", json_true(), "session-key", SESSION_KEY, "session-duration", SESSION_DURATION, "scope", SCOPE, SCOPE_SCHEME, "set-password", "always", "schemes", "module", SCHEME_TYPE, "name", SCHEME_NAME, "register", "always", "display_name", SCHEME_DISPLAY_NAME, "verify-email", json_false(), "enabled", json_true()); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); } END_TEST START_TEST(test_glwd_register_add_mod_registration_disabled_update_email_enabled_for_token_invalid) { json_t * j_body = json_pack("{ss ss ss so s{so so si ss ss ss si s{s{sossss}s{sossss}}}}", "module", MOD_TYPE, "name", MOD_NAME, "display_name", MOD_DISPLAY_NAME, "enabled", json_true(), "parameters", "registration", json_false(), "update-email", json_true(), "update-email-token-duration", GLEWLWYD_TOKEN_LENGTH, "update-email-from", MAIL_FROM, "update-email-content-type", MAIL_CONTENT_TYPE, "host", MAIL_HOST, "port", MAIL_PORT_UPDATE_EMAIL_INVALID_TOKEN, "templatesUpdateEmail", "en", "defaultLang", json_true(), "subject", MAIL_SUBJECT, "body-pattern", MAIL_BODY_PATTERN MAIL_BODY_TOKEN, "fr", "defaultLang", json_false(), "subject", MAIL_SUBJECT_FR, "body-pattern", MAIL_BODY_PATTERN_FR MAIL_BODY_TOKEN); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); } END_TEST START_TEST(test_glwd_register_add_mod_registration_disabled_update_email_enabled_for_token_valid) { json_t * j_body = json_pack("{ss ss ss so s{so so si ss ss ss si s{s{sossss}s{sossss}}}}", "module", MOD_TYPE, "name", MOD_NAME, "display_name", MOD_DISPLAY_NAME, "enabled", json_true(), "parameters", "registration", json_false(), "update-email", json_true(), "update-email-token-duration", GLEWLWYD_TOKEN_LENGTH, "update-email-from", MAIL_FROM, "update-email-content-type", MAIL_CONTENT_TYPE, "host", MAIL_HOST, "port", MAIL_PORT_UPDATE_EMAIL_VALID_TOKEN, "templatesUpdateEmail", "en", "defaultLang", json_true(), "subject", MAIL_SUBJECT, "body-pattern", MAIL_BODY_PATTERN MAIL_BODY_TOKEN, "fr", "defaultLang", json_false(), "subject", MAIL_SUBJECT_FR, "body-pattern", MAIL_BODY_PATTERN_FR MAIL_BODY_TOKEN); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); } END_TEST START_TEST(test_glwd_register_add_mod_reset_credentials_enabled_email_for_token_invalid) { json_t * j_body = json_pack("{ss ss ss so s{so so so so so si ss ss ss si ss si s{s{sossss}s{sossss}}}}", "module", MOD_TYPE, "name", MOD_NAME, "display_name", MOD_DISPLAY_NAME, "enabled", json_true(), "parameters", "registration", json_false(), "update-email", json_false(), "reset-credentials", json_true(), "reset-credentials-email", json_true(), "reset-credentials-code", json_false(), "reset-credentials-session-duration", SESSION_DURATION, "reset-credentials-session-key", SESSION_RESET_CREDENTIALS_KEY, "reset-credentials-from", MAIL_FROM, "reset-credentials-content-type", MAIL_CONTENT_TYPE, "reset-credentials-token-duration", MAIL_CODE_DURATION, "host", MAIL_HOST, "port", MAIL_PORT_RESET_CREDENTIALS_EMAIL_INVALID_TOKEN, "templatesResetCredentials", "en", "defaultLang", json_true(), "subject", MAIL_SUBJECT, "body-pattern", MAIL_BODY_PATTERN MAIL_BODY_TOKEN, "fr", "defaultLang", json_false(), "subject", MAIL_SUBJECT_FR, "body-pattern", MAIL_BODY_PATTERN_FR MAIL_BODY_TOKEN); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); } END_TEST START_TEST(test_glwd_register_add_mod_reset_credentials_enabled_email_for_token_valid) { json_t * j_body = json_pack("{ss ss ss so s{so so so so so si ss ss ss si ss si s{s{sossss}s{sossss}}}}", "module", MOD_TYPE, "name", MOD_NAME, "display_name", MOD_DISPLAY_NAME, "enabled", json_true(), "parameters", "registration", json_false(), "update-email", json_false(), "reset-credentials", json_true(), "reset-credentials-email", json_true(), "reset-credentials-code", json_false(), "reset-credentials-session-duration", SESSION_DURATION, "reset-credentials-session-key", SESSION_RESET_CREDENTIALS_KEY, "reset-credentials-from", MAIL_FROM, "reset-credentials-content-type", MAIL_CONTENT_TYPE, "reset-credentials-token-duration", MAIL_CODE_DURATION, "host", MAIL_HOST, "port", MAIL_PORT_RESET_CREDENTIALS_EMAIL_VALID_TOKEN, "templatesResetCredentials", "en", "defaultLang", json_true(), "subject", MAIL_SUBJECT, "body-pattern", MAIL_BODY_PATTERN MAIL_BODY_TOKEN, "fr", "defaultLang", json_false(), "subject", MAIL_SUBJECT_FR, "body-pattern", MAIL_BODY_PATTERN_FR MAIL_BODY_TOKEN); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); } END_TEST START_TEST(test_glwd_register_add_mod_reset_credentials_enabled_email_flow) { json_t * j_body = json_pack("{ss ss ss so s{so so so so so si ss ss ss si ss si s{s{sossss}s{sossss}}}}", "module", MOD_TYPE, "name", MOD_NAME, "display_name", MOD_DISPLAY_NAME, "enabled", json_true(), "parameters", "registration", json_false(), "update-email", json_false(), "reset-credentials", json_true(), "reset-credentials-email", json_true(), "reset-credentials-code", json_false(), "reset-credentials-session-duration", SESSION_DURATION, "reset-credentials-session-key", SESSION_RESET_CREDENTIALS_KEY, "reset-credentials-from", MAIL_FROM, "reset-credentials-content-type", MAIL_CONTENT_TYPE, "reset-credentials-token-duration", MAIL_CODE_DURATION, "host", MAIL_HOST, "port", MAIL_PORT_RESET_CREDENTIALS_EMAIL_FLOW, "templatesResetCredentials", "en", "defaultLang", json_true(), "subject", MAIL_SUBJECT, "body-pattern", MAIL_BODY_PATTERN MAIL_BODY_TOKEN, "fr", "defaultLang", json_false(), "subject", MAIL_SUBJECT_FR, "body-pattern", MAIL_BODY_PATTERN_FR MAIL_BODY_TOKEN); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); } END_TEST START_TEST(test_glwd_register_add_mod_reset_credentials_enabled_code) { json_t * j_body = json_pack("{ss ss ss so s{so so so so so si ss ss si}}", "module", MOD_TYPE, "name", MOD_NAME, "display_name", MOD_DISPLAY_NAME, "enabled", json_true(), "parameters", "registration", json_false(), "update-email", json_false(), "reset-credentials", json_true(), "reset-credentials-email", json_false(), "reset-credentials-code", json_true(), "reset-credentials-session-duration", SESSION_DURATION, "reset-credentials-session-key", SESSION_RESET_CREDENTIALS_KEY, "reset-credentials-code-property", RESET_CREDENTIALS_CODE_PROPERTY, "reset-credentials-code-list-size", RESET_CREDENTIALS_CODE_LIST_SIZE); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); } END_TEST START_TEST(test_glwd_register_noverify_check_username) { json_t * j_body = json_pack("{}"); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" MOD_NAME "/username", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{si}", "username", 42); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" MOD_NAME "/username", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ss}", "usernameError", NEW_USERNAME); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" MOD_NAME "/username", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("[{ss}]", "username", NEW_USERNAME); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" MOD_NAME "/username", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ss}", "username", USERNAME); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" MOD_NAME "/username", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ss}", "username", NEW_USERNAME); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" MOD_NAME "/username", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); } END_TEST START_TEST(test_glwd_register_profile_empty) { ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" MOD_NAME "/profile", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_register_noverify_username_exists) { json_t * j_body; ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" MOD_NAME "/profile", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); j_body = json_pack("{ss}", "username", USERNAME_ADMIN); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" MOD_NAME "/username", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); } END_TEST START_TEST(test_glwd_register_noverify_cancel_registration) { json_t * j_body; struct _u_request req; struct _u_response resp; int res; char * cookie; ulfius_init_request(&req); ulfius_init_response(&resp); j_body = json_pack("{ss}", "username", NEW_USERNAME_CANCELLED); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" MOD_NAME "/username", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); // Registration with the new username req.http_url = o_strdup(SERVER_URI "/" MOD_NAME "/register"); req.http_verb = o_strdup("POST"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_body), U_OK); json_decref(j_body); res = ulfius_send_http_request(&req, &resp); ck_assert_int_eq(res, U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_eq(resp.nb_cookies, 1); cookie = msprintf("%s=%s", resp.map_cookie[0].key, resp.map_cookie[0].value); ck_assert_ptr_ne(cookie, NULL); u_map_put(req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&resp); // Set password j_body = json_pack("{ss}", "password", NEW_PASSWORD); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/profile/password", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); // Verify canuse response is 401 for scheme mock j_body = json_pack("{ssssss}", "scheme_name", SCHEME_NAME, "scheme_type", SCHEME_TYPE, "username", NEW_USERNAME_CANCELLED); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/" MOD_NAME "/profile/scheme/register/canuse", NULL, NULL, j_body, NULL, 402, NULL, NULL, NULL), 1); json_decref(j_body); // Register scheme mock j_body = json_pack("{sssssss{so}}", "scheme_name", SCHEME_NAME, "scheme_type", SCHEME_TYPE, "username", NEW_USERNAME_CANCELLED, "value", "register", json_true()); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/profile/scheme/register", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); // Verify canuse response is 200 for scheme mock j_body = json_pack("{ssssss}", "scheme_name", SCHEME_NAME, "scheme_type", SCHEME_TYPE, "username", NEW_USERNAME_CANCELLED); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/" MOD_NAME "/profile/scheme/register/canuse", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); // Cancel registration ck_assert_int_eq(run_simple_test(&req, "DELETE", SERVER_URI "/" MOD_NAME "/profile", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); // Verify session is disabled ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/" MOD_NAME "/profile", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_register_noverify_registration_error_session) { json_t * j_body, * j_response; struct _u_request req; struct _u_response resp; int res; char * cookie, * cookie_invalid; ulfius_init_request(&req); ulfius_init_response(&resp); j_body = json_pack("{ss}", "username", NEW_USERNAME_INVALID_SESSION); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" MOD_NAME "/username", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); // Registration with the new username req.http_url = o_strdup(SERVER_URI "/" MOD_NAME "/register"); req.http_verb = o_strdup("POST"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_body), U_OK); json_decref(j_body); res = ulfius_send_http_request(&req, &resp); ck_assert_int_eq(res, U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_eq(resp.nb_cookies, 1); cookie = msprintf("%s=%s", resp.map_cookie[0].key, resp.map_cookie[0].value); cookie_invalid = msprintf("%s=%s", resp.map_cookie[0].key, (resp.map_cookie[0].value+1)); ck_assert_ptr_ne(cookie, NULL); u_map_put(req.map_header, "Cookie", cookie); ulfius_clean_response(&resp); // Check profile j_response = json_pack("{sssososo}", "username", NEW_USERNAME_INVALID_SESSION, "name", json_null(), "email", json_null(), "password_set", json_false()); ck_assert_ptr_ne(j_response, NULL); ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/" MOD_NAME "/profile", NULL, NULL, NULL, NULL, 200, j_response, NULL, NULL), 1); json_decref(j_response); // Change cookie value u_map_remove_from_key(req.map_header, "Cookie"); u_map_put(req.map_header, "Cookie", cookie_invalid); // Check profile invalid ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/" MOD_NAME "/profile", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); // Complete registration invalid ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/profile/complete", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); // Update name invalid j_body = json_pack("{ss}", "name", NEW_NAME); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/" MOD_NAME "/profile", NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); // Set password invalid j_body = json_pack("{ss}", "password", NEW_PASSWORD); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/profile/password", NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); // Check canuse invalid j_body = json_pack("{ssssss}", "scheme_name", SCHEME_NAME, "scheme_type", SCHEME_TYPE, "username", NEW_USERNAME_INVALID_SESSION); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/" MOD_NAME "/profile/scheme/register/canuse", NULL, NULL, j_body, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_body); // Cancel registration invalid ck_assert_int_eq(run_simple_test(&req, "DELETE", SERVER_URI "/" MOD_NAME "/profile", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); // Change cookie value u_map_remove_from_key(req.map_header, "Cookie"); u_map_put(req.map_header, "Cookie", cookie); // Cancel registration ck_assert_int_eq(run_simple_test(&req, "DELETE", SERVER_URI "/" MOD_NAME "/profile", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(cookie_invalid); o_free(cookie); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_register_noverify_full_registration) { json_t * j_body, * j_response; struct _u_request req; struct _u_response resp; int res; char * cookie; ck_assert_int_eq(run_simple_test(NULL, "GET", SERVER_URI "/" MOD_NAME "/profile", NULL, NULL, NULL, NULL, 401, NULL, NULL, NULL), 1); j_body = json_pack("{ss}", "username", NEW_USERNAME); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" MOD_NAME "/username", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ulfius_init_request(&req); ulfius_init_response(&resp); // Registration with input errors ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/register", NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); j_body = json_pack("{si}", "username", 42); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/register", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("[{ss}]", "username", NEW_USERNAME); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/register", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ss}", "usernameError", NEW_USERNAME); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/register", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); // Registration with the new username req.http_url = o_strdup(SERVER_URI "/" MOD_NAME "/register"); req.http_verb = o_strdup("POST"); j_body = json_pack("{ss}", "username", NEW_USERNAME); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_body), U_OK); json_decref(j_body); res = ulfius_send_http_request(&req, &resp); ck_assert_int_eq(res, U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_eq(resp.nb_cookies, 1); cookie = msprintf("%s=%s", resp.map_cookie[0].key, resp.map_cookie[0].value); ck_assert_ptr_ne(cookie, NULL); u_map_put(req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&resp); // Complete registration impossible ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/profile/complete", NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); // Check profile j_response = json_pack("{sssososo}", "username", NEW_USERNAME, "name", json_null(), "email", json_null(), "password_set", json_false()); ck_assert_ptr_ne(j_response, NULL); ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/" MOD_NAME "/profile", NULL, NULL, NULL, NULL, 200, j_response, NULL, NULL), 1); json_decref(j_response); // Update profile with input errors ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/" MOD_NAME "/profile", NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); j_body = json_pack("{si}", "name", 42); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/" MOD_NAME "/profile", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("[{ss}]", "name", NEW_NAME); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/" MOD_NAME "/profile", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ss}", "nameError", NEW_NAME); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/" MOD_NAME "/profile", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); // Update name j_body = json_pack("{ss}", "name", NEW_NAME); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/" MOD_NAME "/profile", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); // Check profile j_response = json_pack("{sssssoso}", "username", NEW_USERNAME, "name", NEW_NAME, "email", json_null(), "password_set", json_false()); ck_assert_ptr_ne(j_response, NULL); ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/" MOD_NAME "/profile", NULL, NULL, NULL, NULL, 200, j_response, NULL, NULL), 1); json_decref(j_response); // Complete registration impossible ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/profile/complete", NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); // Check profile agin with new name j_response = json_pack("{sssssoso}", "username", NEW_USERNAME, "name", NEW_NAME, "email", json_null(), "password_set", json_false()); ck_assert_ptr_ne(j_response, NULL); ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/" MOD_NAME "/profile", NULL, NULL, NULL, NULL, 200, j_response, NULL, NULL), 1); json_decref(j_response); // Set password with input errors ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/profile/password", NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); j_body = json_pack("{si}", "password", 42); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/profile/password", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("[{ss}]", "password", NEW_PASSWORD); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/profile/password", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ss}", "passwordError", NEW_PASSWORD); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/profile/password", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); // Set password j_body = json_pack("{ss}", "password", NEW_PASSWORD); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/profile/password", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); // Check profile j_response = json_pack("{sssssoso}", "username", NEW_USERNAME, "name", NEW_NAME, "email", json_null(), "password_set", json_true()); ck_assert_ptr_ne(j_response, NULL); ck_assert_int_eq(run_simple_test(&req, "GET", SERVER_URI "/" MOD_NAME "/profile", NULL, NULL, NULL, NULL, 200, j_response, NULL, NULL), 1); json_decref(j_response); // Complete registration impossible ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/profile/complete", NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); // Check canuse with input errors ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/" MOD_NAME "/profile/scheme/register/canuse", NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); j_body = json_pack("{sssssi}", "scheme_name", SCHEME_NAME, "scheme_type", SCHEME_TYPE, "username", 42); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/" MOD_NAME "/profile/scheme/register/canuse", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{sissss}", "scheme_name", 42, "scheme_type", SCHEME_TYPE, "username", NEW_USERNAME); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/" MOD_NAME "/profile/scheme/register/canuse", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); // Verify canuse response is 401 for scheme mock j_body = json_pack("{ssssss}", "scheme_name", SCHEME_NAME, "scheme_type", SCHEME_TYPE, "username", NEW_USERNAME); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/" MOD_NAME "/profile/scheme/register/canuse", NULL, NULL, j_body, NULL, 402, NULL, NULL, NULL), 1); json_decref(j_body); // Register scheme mock with input errors ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/profile/scheme/register", NULL, NULL, NULL, NULL, 400, NULL, NULL, NULL), 1); j_body = json_pack("{sssssis{so}}", "scheme_name", SCHEME_NAME, "scheme_type", SCHEME_TYPE, "username", 42, "value", "register", json_true()); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/profile/scheme/register", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{sisssss{so}}", "scheme_name", 42, "scheme_type", SCHEME_TYPE, "username", NEW_USERNAME, "value", "register", json_true()); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/profile/scheme/register", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{sssssss{si}}", "scheme_name", SCHEME_NAME, "scheme_type", SCHEME_TYPE, "username", NEW_USERNAME, "value", "register", 42); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/profile/scheme/register", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); // Register scheme mock j_body = json_pack("{sssssss{so}}", "scheme_name", SCHEME_NAME, "scheme_type", SCHEME_TYPE, "username", NEW_USERNAME, "value", "register", json_true()); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/profile/scheme/register", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); // Verify canuse response is 200 for scheme mock j_body = json_pack("{ssssss}", "scheme_name", SCHEME_NAME, "scheme_type", SCHEME_TYPE, "username", NEW_USERNAME); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/" MOD_NAME "/profile/scheme/register/canuse", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); // Complete registration ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/profile/complete", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); // Test authentication with new user j_body = json_pack("{ssss}", "username", NEW_USERNAME, "password", NEW_PASSWORD); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_register_verify_with_username_cancel_registration) { struct smtp_manager manager; json_t * j_body; struct _u_request req; struct _u_response resp; int res, i; char * cookie, error_code[MAIL_CODE_LEGTH+1]; pthread_t thread; manager.mail_data = NULL; manager.port = MAIL_PORT_WITH_USERNAME; manager.sockfd = 0; manager.body_pattern = MAIL_BODY_PATTERN; pthread_create(&thread, NULL, simple_smtp, &manager); ulfius_init_request(&req); ulfius_init_response(&resp); j_body = json_pack("{ss}", "username", NEW_USERNAME_CANCELLED); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/" MOD_NAME "/username", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); // Verification with the new username j_body = json_pack("{ssss}", "username", NEW_USERNAME_CANCELLED, "email", NEW_EMAIL); ck_assert_int_eq(run_simple_test(NULL, "PUT", SERVER_URI "/" MOD_NAME "/verify", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); // Send invalid length verification code for (i=0; i #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "https://localhost:4593/api/" #define USERNAME "user1" #define PASSWORD "password" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define MODULE_MODULE "certificate" #define MODULE_NAME "test_certificate" #define MODULE_NAME_2 "test_certificate_2" #define MODULE_NAME_3 "test_certificate_3" #define MODULE_DISPLAY_NAME "Client certificate scheme for test" #define MODULE_EXPIRATION 600 #define MODULE_MAX_USE 0 #define MODULE_HEADER_NAME "SSL_CERT_PEM" #define ROOT_CA_CERT_1_PATH "cert/root1.crt" #define ROOT_CA_KEY_1_PATH "cert/root1.key" #define ROOT_CA_CERT_3_PATH "cert/root2.crt" #define ROOT_CA_KEY_3_PATH "cert/root2.key" #define CLIENT_CERT_1_PATH "cert/user1.crt" #define CLIENT_CERT_1_DER_PATH "cert/user1.crt.der" #define CLIENT_KEY_1_PATH "cert/user1.key" #define CLIENT_KEY_1_PASSWORD "" #define CLIENT_CERT_2_PATH "cert/user2.crt" #define CLIENT_CERT_2_DER_PATH "cert/user2.crt.der" #define CLIENT_KEY_2_PATH "cert/user2.key" #define CLIENT_KEY_2_PASSWORD "" #define CLIENT_CERT_3_PATH "cert/user3.crt" #define CLIENT_CERT_3_DER_PATH "cert/user3.crt.der" #define CLIENT_KEY_3_PATH "cert/user3.key" #define CLIENT_KEY_3_PASSWORD "" #define CLIENT_CERT_DN "cn=Dave Lopper,o=babelouest" #define SCOPE_NAME "scope2" #define SCOPE_DISPLAY_NAME "Glewlwyd mock scope without password" #define SCOPE_DESCRIPTION "Glewlwyd scope 2 scope description" #define SCOPE_PASSWORD_MAX_AGE 0 #define SCOPE_SCHEME_1_TYPE "mock" #define SCOPE_SCHEME_1_NAME "mock_scheme_95" char client_cert_1_id[128]; char client_cert_2_id[128]; char client_cert_3_id[128]; static char * get_file_content(const char * file_path) { char * buffer = NULL; size_t length, res; FILE * f; f = fopen (file_path, "rb"); if (f) { fseek (f, 0, SEEK_END); length = ftell (f); fseek (f, 0, SEEK_SET); buffer = o_malloc((length+1)*sizeof(char)); if (buffer) { res = fread (buffer, 1, length, f); if (res != length) { fprintf(stderr, "fread warning, reading %zu while expecting %zu", res, length); } // Add null character at the end of buffer, just in case buffer[length] = '\0'; } fclose (f); } else { fprintf(stderr, "error opening file %s\n", file_path); } return buffer; } static int get_certificate_id(const char * file_path, unsigned char * certificate_id) { char * cert_content = get_file_content(file_path); unsigned char cert_digest[128]; size_t cert_digest_len = 128, certificate_id_len; gnutls_x509_crt_t cert = NULL; gnutls_datum_t cert_dat, dat; int ret = 0; if (!gnutls_x509_crt_init(&cert)) { cert_dat.data = (unsigned char *)cert_content; cert_dat.size = o_strlen(cert_content); if (gnutls_x509_crt_import(cert, &cert_dat, GNUTLS_X509_FMT_PEM) >= 0) { if (gnutls_x509_crt_export2(cert, GNUTLS_X509_FMT_DER, &dat) >= 0) { if (gnutls_fingerprint(GNUTLS_DIG_SHA256, &dat, cert_digest, &cert_digest_len) == GNUTLS_E_SUCCESS) { if (o_base64_encode(cert_digest, cert_digest_len, certificate_id, &certificate_id_len)) { certificate_id[certificate_id_len] = '\0'; ret = 1; } } gnutls_free(dat.data); } } } gnutls_x509_crt_deinit(cert); o_free(cert_content); return ret; } struct _u_request admin_req; struct _u_request user_req; START_TEST(test_glwd_scheme_certificate_scope_set) { json_t * j_parameters = json_pack("{sssssisos{s[{ssss}{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME); json_t * j_canuse = json_pack("{ssss}", "module", MODULE_MODULE, "name", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/delegate/" USERNAME "/profile/scheme/", NULL, NULL, NULL, NULL, 200, j_canuse, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_canuse); } END_TEST START_TEST(test_glwd_scheme_certificate_scope_2_set) { json_t * j_parameters = json_pack("{sssssisos{s[{ssss}{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2); json_t * j_canuse = json_pack("{ssss}", "module", MODULE_MODULE, "name", MODULE_NAME_2); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/delegate/" USERNAME "/profile/scheme/", NULL, NULL, NULL, NULL, 200, j_canuse, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_canuse); } END_TEST START_TEST(test_glwd_scheme_certificate_scope_3_set) { json_t * j_parameters = json_pack("{sssssisos{s[{ssss}{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_3); json_t * j_canuse = json_pack("{ssss}", "module", MODULE_MODULE, "name", MODULE_NAME_3); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/delegate/" USERNAME "/profile/scheme/", NULL, NULL, NULL, NULL, 200, j_canuse, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_canuse); } END_TEST START_TEST(test_glwd_scheme_certificate_scope_unset) { json_t * j_parameters = json_pack("{sssssisos{s[{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_module_add_scheme_backend) { json_t * j_parameters = json_pack("{sssssssisis{so}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "use-scheme-storage", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_register_scheme_backend_use_cert) { json_t * j_parameters = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "use-certificate"); json_t * j_canuse = json_pack("{ssss}", "module", MODULE_MODULE, "name", MODULE_NAME); user_req.client_cert_file = o_strdup(CLIENT_CERT_1_PATH); user_req.client_key_file = o_strdup(CLIENT_KEY_1_PATH); user_req.client_key_password = o_strdup(CLIENT_KEY_1_PASSWORD); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "profile/scheme/", NULL, NULL, NULL, NULL, 200, j_canuse, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(user_req.client_cert_file); o_free(user_req.client_key_file); o_free(user_req.client_key_password); user_req.client_cert_file = NULL; user_req.client_key_file = NULL; user_req.client_key_password = NULL; json_decref(j_canuse); } END_TEST START_TEST(test_glwd_scheme_certificate_register_scheme_backend_invalid_certificate) { json_t * j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "upload-certificate", "x509", "error"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); char * cert_content = get_file_content(CLIENT_CERT_1_DER_PATH); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "upload-certificate", "x509", cert_content); o_free(cert_content); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_register_scheme_backend) { char * cert_content = get_file_content(CLIENT_CERT_1_PATH); json_t * j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "upload-certificate", "x509", cert_content); o_free(cert_content); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_get_register_scheme_backend) { json_t * j_parameters = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"), * j_result = json_string(client_cert_1_id); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_result); } END_TEST START_TEST(test_glwd_scheme_certificate_test_register_scheme_backend) { json_t * j_parameters = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "test-certificate"), * j_result = json_string(client_cert_1_id); user_req.client_cert_file = o_strdup(CLIENT_CERT_1_PATH); user_req.client_key_file = o_strdup(CLIENT_KEY_1_PATH); user_req.client_key_password = o_strdup(CLIENT_KEY_1_PASSWORD); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, j_result, NULL, NULL), 1); o_free(user_req.client_cert_file); o_free(user_req.client_key_file); o_free(user_req.client_key_password); user_req.client_cert_file = NULL; user_req.client_key_file = NULL; user_req.client_key_password = NULL; json_decref(j_parameters); json_decref(j_result); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_error_no_certificate_scheme_backend) { struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_identify_error_unregistered_certificate_scheme_backend) { struct _u_request req; json_t * j_params = json_pack("{sssss{}}", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; req.client_cert_file = o_strdup(CLIENT_CERT_2_PATH); req.client_key_file = o_strdup(CLIENT_KEY_2_PATH); req.client_key_password = o_strdup(CLIENT_KEY_2_PASSWORD); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_error_unregistered_certificate_scheme_backend) { struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; req.client_cert_file = o_strdup(CLIENT_CERT_2_PATH); req.client_key_file = o_strdup(CLIENT_KEY_2_PATH); req.client_key_password = o_strdup(CLIENT_KEY_2_PASSWORD); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_error_invalid_ca_scheme_backend) { char * cert_content = get_file_content(CLIENT_CERT_3_PATH); json_t * j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "upload-certificate", "x509", cert_content); o_free(cert_content); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; req.client_cert_file = o_strdup(CLIENT_CERT_3_PATH); req.client_key_file = o_strdup(CLIENT_KEY_3_PATH); req.client_key_password = o_strdup(CLIENT_KEY_3_PASSWORD); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "delete-certificate", "certificate_id", client_cert_3_id); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_success_scheme_backend) { struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; req.client_cert_file = o_strdup(CLIENT_CERT_1_PATH); req.client_key_file = o_strdup(CLIENT_KEY_1_PATH); req.client_key_password = o_strdup(CLIENT_KEY_1_PASSWORD); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_identify_success_scheme_backend) { struct _u_request req; json_t * j_params = json_pack("{sssss{}}", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; req.client_cert_file = o_strdup(CLIENT_CERT_1_PATH); req.client_key_file = o_strdup(CLIENT_KEY_1_PATH); req.client_key_password = o_strdup(CLIENT_KEY_1_PASSWORD); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_cert_disabled_enabled_scheme_backend) { json_t * j_parameters = json_pack("{sssssss{ssssso}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "toggle-certificate", "certificate_id", client_cert_1_id, "enabled", json_false()); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, json_false(), NULL, NULL), 1); json_decref(j_parameters); struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; req.client_cert_file = o_strdup(CLIENT_CERT_1_PATH); req.client_key_file = o_strdup(CLIENT_KEY_1_PATH); req.client_key_password = o_strdup(CLIENT_KEY_1_PASSWORD); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); j_parameters = json_pack("{sssssss{ssssso}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "toggle-certificate", "certificate_id", client_cert_1_id, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, json_true(), NULL, NULL), 1); json_decref(j_parameters); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_deregister_scheme_backend) { json_t * j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "delete-certificate", "certificate_id", client_cert_1_id); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_register_scheme_backend_multiple_cert) { char * cert_content = get_file_content(CLIENT_CERT_1_PATH); json_t * j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "upload-certificate", "x509", cert_content); o_free(cert_content); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); cert_content = get_file_content(CLIENT_CERT_2_PATH); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "upload-certificate", "x509", cert_content); o_free(cert_content); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_get_register_scheme_backend_multiple_cert) { char * cert_content = get_file_content(CLIENT_CERT_1_PATH); json_t * j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "upload-certificate", "x509", cert_content), * j_result_1 = json_string(client_cert_1_id), * j_result_2 = json_string(client_cert_2_id); o_free(cert_content); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, j_result_1, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, j_result_2, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_result_1); json_decref(j_result_2); } END_TEST START_TEST(test_glwd_scheme_certificate_test_register_scheme_backend_multiple_cert) { json_t * j_parameters = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "test-certificate"), * j_result = json_string(client_cert_1_id); user_req.client_cert_file = o_strdup(CLIENT_CERT_1_PATH); user_req.client_key_file = o_strdup(CLIENT_KEY_1_PATH); user_req.client_key_password = o_strdup(CLIENT_KEY_1_PASSWORD); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, j_result, NULL, NULL), 1); o_free(user_req.client_cert_file); o_free(user_req.client_key_file); o_free(user_req.client_key_password); user_req.client_cert_file = NULL; user_req.client_key_file = NULL; user_req.client_key_password = NULL; json_decref(j_parameters); json_decref(j_result); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_error_no_certificate_scheme_backend_multiple_cert) { struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_error_invalid_ca_scheme_backend_multiple_cert) { char * cert_content = get_file_content(CLIENT_CERT_3_PATH); json_t * j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "upload-certificate", "x509", cert_content); o_free(cert_content); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; req.client_cert_file = o_strdup(CLIENT_CERT_3_PATH); req.client_key_file = o_strdup(CLIENT_KEY_3_PATH); req.client_key_password = o_strdup(CLIENT_KEY_3_PASSWORD); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "delete-certificate", "certificate_id", client_cert_3_id); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_success_scheme_backend_multiple_cert) { struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; req.client_cert_file = o_strdup(CLIENT_CERT_1_PATH); req.client_key_file = o_strdup(CLIENT_KEY_1_PATH); req.client_key_password = o_strdup(CLIENT_KEY_1_PASSWORD); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_deregister_scheme_backend_multiple_cert) { json_t * j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "delete-certificate", "certificate_id", client_cert_1_id); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "delete-certificate", "certificate_id", client_cert_2_id); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_module_remove_scheme_backend) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_scheme_certificate_module_add_user_properties_pem) { json_t * j_parameters = json_pack("{sssssssisis{sossss}}", "module", MODULE_MODULE, "name", MODULE_NAME_2, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "use-scheme-storage", json_false(), "user-certificate-property", "cert", "user-certificate-format", "PEM"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); char * cert_1_content = get_file_content(CLIENT_CERT_1_PATH); j_parameters = json_pack("{ss}", "cert", cert_1_content); o_free(cert_1_content); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USERNAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_get_register_user_properties_pem) { char * cert_content = get_file_content(CLIENT_CERT_1_PATH); json_t * j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value", "register", "upload-certificate", "x509", cert_content), * j_result = json_string(client_cert_1_id); o_free(cert_content); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_result); } END_TEST START_TEST(test_glwd_scheme_certificate_test_register_user_properties_pem) { json_t * j_parameters = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value", "register", "test-certificate"), * j_result = json_string(client_cert_1_id); user_req.client_cert_file = o_strdup(CLIENT_CERT_1_PATH); user_req.client_key_file = o_strdup(CLIENT_KEY_1_PATH); user_req.client_key_password = o_strdup(CLIENT_KEY_1_PASSWORD); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, j_result, NULL, NULL), 1); o_free(user_req.client_cert_file); o_free(user_req.client_key_file); o_free(user_req.client_key_password); user_req.client_cert_file = NULL; user_req.client_key_file = NULL; user_req.client_key_password = NULL; json_decref(j_parameters); json_decref(j_result); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_error_no_certificate_user_properties_pem) { struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_error_unregistered_certificate_user_properties_pem) { struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; req.client_cert_file = o_strdup(CLIENT_CERT_2_PATH); req.client_key_file = o_strdup(CLIENT_KEY_2_PATH); req.client_key_password = o_strdup(CLIENT_KEY_2_PASSWORD); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_success_user_properties_pem) { struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; req.client_cert_file = o_strdup(CLIENT_CERT_1_PATH); req.client_key_file = o_strdup(CLIENT_KEY_1_PATH); req.client_key_password = o_strdup(CLIENT_KEY_1_PASSWORD); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_module_remove_user_properties_pem) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/scheme/" MODULE_NAME_2, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); json_t * j_parameters = json_pack("{so}", "cert", json_null()); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USERNAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_module_add_user_properties_pem_multiple_cert) { json_t * j_parameters = json_pack("{sssssssisis{sossss}}", "module", MODULE_MODULE, "name", MODULE_NAME_2, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "use-scheme-storage", json_false(), "user-certificate-property", "cert", "user-certificate-format", "PEM"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); char * cert_1_content = get_file_content(CLIENT_CERT_1_PATH), * cert_2_content = get_file_content(CLIENT_CERT_3_PATH); j_parameters = json_pack("{s[ss]}", "cert", cert_1_content, cert_2_content); o_free(cert_1_content); o_free(cert_2_content); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USERNAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_get_register_user_properties_pem_multiple_cert) { char * cert_content = get_file_content(CLIENT_CERT_1_PATH); json_t * j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value", "register", "upload-certificate", "x509", cert_content), * j_result = json_string(client_cert_1_id), * j_result_3 = json_string(client_cert_3_id); o_free(cert_content); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, j_result, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, j_result_3, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_result); json_decref(j_result_3); } END_TEST START_TEST(test_glwd_scheme_certificate_test_register_user_properties_pem_multiple_cert) { json_t * j_parameters = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value", "register", "test-certificate"), * j_result = json_string(client_cert_1_id); user_req.client_cert_file = o_strdup(CLIENT_CERT_1_PATH); user_req.client_key_file = o_strdup(CLIENT_KEY_1_PATH); user_req.client_key_password = o_strdup(CLIENT_KEY_1_PASSWORD); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, j_result, NULL, NULL), 1); o_free(user_req.client_cert_file); o_free(user_req.client_key_file); o_free(user_req.client_key_password); user_req.client_cert_file = NULL; user_req.client_key_file = NULL; user_req.client_key_password = NULL; json_decref(j_parameters); json_decref(j_result); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_error_no_certificate_user_properties_pem_multiple_cert) { struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_error_unregistered_certificate_user_properties_pem_multiple_cert) { struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; req.client_cert_file = o_strdup(CLIENT_CERT_2_PATH); req.client_key_file = o_strdup(CLIENT_KEY_2_PATH); req.client_key_password = o_strdup(CLIENT_KEY_2_PASSWORD); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_error_invalid_ca_user_properties_pem_multiple_cert) { struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; req.client_cert_file = o_strdup(CLIENT_CERT_3_PATH); req.client_key_file = o_strdup(CLIENT_KEY_3_PATH); req.client_key_password = o_strdup(CLIENT_KEY_3_PASSWORD); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_success_user_properties_pem_multiple_cert) { struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; req.client_cert_file = o_strdup(CLIENT_CERT_1_PATH); req.client_key_file = o_strdup(CLIENT_KEY_1_PATH); req.client_key_password = o_strdup(CLIENT_KEY_1_PASSWORD); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_module_remove_user_properties_pem_multiple_cert) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/scheme/" MODULE_NAME_2, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); json_t * j_parameters = json_pack("{so}", "cert", json_null()); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USERNAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_module_add_user_properties_der) { json_t * j_parameters = json_pack("{sssssssisis{sossss}}", "module", MODULE_MODULE, "name", MODULE_NAME_2, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "use-scheme-storage", json_false(), "user-certificate-property", "cert", "user-certificate-format", "DER"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); char * cert_1_content = get_file_content(CLIENT_CERT_1_DER_PATH); j_parameters = json_pack("{ss}", "cert", cert_1_content); o_free(cert_1_content); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USERNAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_get_register_user_properties_der) { json_t * j_parameters = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value", "register", "upload-certificate"), * j_result = json_string(client_cert_1_id); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_result); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_error_no_certificate_user_properties_der) { struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_error_unregistered_certificate_user_properties_der) { struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; req.client_cert_file = o_strdup(CLIENT_CERT_2_PATH); req.client_key_file = o_strdup(CLIENT_KEY_2_PATH); req.client_key_password = o_strdup(CLIENT_KEY_2_PASSWORD); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_success_user_properties_der) { struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; req.client_cert_file = o_strdup(CLIENT_CERT_1_PATH); req.client_key_file = o_strdup(CLIENT_KEY_1_PATH); req.client_key_password = o_strdup(CLIENT_KEY_1_PASSWORD); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_module_remove_user_properties_der) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/scheme/" MODULE_NAME_2, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); json_t * j_parameters = json_pack("{so}", "cert", json_null()); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USERNAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_module_add_scheme_backend_ca_chain) { char * content = get_file_content(ROOT_CA_CERT_1_PATH); json_t * j_parameters = json_pack("{sssssssisis{sos[{ssss}]}}", "module", MODULE_MODULE, "name", MODULE_NAME_3, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "use-scheme-storage", json_true(), "ca-chain", "file-name", ROOT_CA_CERT_1_PATH, "cert-file", content); o_free(content); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_register_scheme_backend_ca_chain) { char * cert_content = get_file_content(CLIENT_CERT_1_PATH); json_t * j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_3, "value", "register", "upload-certificate", "x509", cert_content); o_free(cert_content); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_success_scheme_backend_ca_chain) { struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_3, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; req.client_cert_file = o_strdup(CLIENT_CERT_1_PATH); req.client_key_file = o_strdup(CLIENT_KEY_1_PATH); req.client_key_password = o_strdup(CLIENT_KEY_1_PASSWORD); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_deregister_scheme_backend_ca_chain) { json_t * j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_3, "value", "register", "delete-certificate", "certificate_id", client_cert_1_id); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_module_remove_scheme_backend_ca_chain) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/scheme/" MODULE_NAME_3, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_scheme_certificate_module_add_scheme_backend_invalid_ca_chain) { char * content = get_file_content(ROOT_CA_CERT_3_PATH); json_t * j_parameters = json_pack("{sssssssisis{sos[{ssss}]}}", "module", MODULE_MODULE, "name", MODULE_NAME_3, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "use-scheme-storage", json_true(), "ca-chain", "file-name", ROOT_CA_CERT_1_PATH, "cert-file", content); o_free(content); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_register_scheme_backend_invalid_ca_chain) { char * cert_content = get_file_content(CLIENT_CERT_1_PATH); json_t * j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_3, "value", "register", "upload-certificate", "x509", cert_content); o_free(cert_content); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_error_scheme_backend_invalid_ca_chain) { struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_3, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; req.client_cert_file = o_strdup(CLIENT_CERT_1_PATH); req.client_key_file = o_strdup(CLIENT_KEY_1_PATH); req.client_key_password = o_strdup(CLIENT_KEY_1_PASSWORD); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_deregister_scheme_backend_invalid_ca_chain) { json_t * j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_3, "value", "register", "delete-certificate", "certificate_id", client_cert_1_id); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_module_remove_scheme_backend_invalid_ca_chain) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/scheme/" MODULE_NAME_3, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_scheme_certificate_module_add_scheme_backend_proxyfied) { json_t * j_parameters = json_pack("{sssssssisis{sossss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "use-scheme-storage", json_true(), "cert-source", "both", "header-name", MODULE_HEADER_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_register_scheme_backend_proxyfied) { char * cert_content = get_file_content(CLIENT_CERT_1_PATH); json_t * j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "upload-certificate", "x509", cert_content); o_free(cert_content); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_test_register_scheme_backend_proxyfied) { char * cert_content = get_file_content(CLIENT_CERT_1_PATH); char * cert_content_escaped = str_replace(cert_content, "\n", ""); json_t * j_parameters = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "test-certificate"), * j_result = json_string(client_cert_1_id); u_map_put(user_req.map_header, MODULE_HEADER_NAME, cert_content_escaped); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, j_result, NULL, NULL), 1); o_free(user_req.client_cert_file); o_free(user_req.client_key_file); o_free(user_req.client_key_password); user_req.client_cert_file = NULL; user_req.client_key_file = NULL; user_req.client_key_password = NULL; json_decref(j_parameters); json_decref(j_result); o_free(cert_content); o_free(cert_content_escaped); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_error_no_certificate_scheme_backend_proxyfied) { struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_identify_error_unregistered_certificate_scheme_backend_proxyfied) { char * cert_content = get_file_content(CLIENT_CERT_2_PATH); char * cert_content_escaped = str_replace(cert_content, "\n", ""); struct _u_request req; json_t * j_params = json_pack("{sssss{}}", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ulfius_init_request(&req); u_map_put(user_req.map_header, MODULE_HEADER_NAME, cert_content_escaped); req.check_server_certificate = 0; ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); o_free(cert_content); o_free(cert_content_escaped); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_error_unregistered_certificate_scheme_backend_proxyfied) { char * cert_content = get_file_content(CLIENT_CERT_2_PATH); char * cert_content_escaped = str_replace(cert_content, "\n", ""); struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ulfius_init_request(&req); u_map_put(user_req.map_header, MODULE_HEADER_NAME, cert_content_escaped); req.check_server_certificate = 0; ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); o_free(cert_content); o_free(cert_content_escaped); } END_TEST START_TEST(test_glwd_scheme_certificate_identify_success_scheme_backend_proxyfied) { char * cert_content = get_file_content(CLIENT_CERT_1_PATH); char * cert_content_escaped = str_replace(cert_content, "\n", ""); struct _u_request req; json_t * j_params = json_pack("{sssss{}}", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ulfius_init_request(&req); u_map_put(req.map_header, MODULE_HEADER_NAME, cert_content_escaped); req.check_server_certificate = 0; ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); o_free(cert_content); o_free(cert_content_escaped); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_success_scheme_backend_proxyfied) { char * cert_content = get_file_content(CLIENT_CERT_1_PATH); char * cert_content_escaped = str_replace(cert_content, "\n", ""); struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ulfius_init_request(&req); u_map_put(req.map_header, MODULE_HEADER_NAME, cert_content_escaped); req.check_server_certificate = 0; ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); o_free(cert_content); o_free(cert_content_escaped); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_cert_disabled_enabled_scheme_backend_proxyfied) { char * cert_content = get_file_content(CLIENT_CERT_1_PATH); char * cert_content_escaped = str_replace(cert_content, "\n", ""); json_t * j_parameters = json_pack("{sssssss{ssssso}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "toggle-certificate", "certificate_id", client_cert_1_id, "enabled", json_false()); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, json_false(), NULL, NULL), 1); json_decref(j_parameters); struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; u_map_put(req.map_header, MODULE_HEADER_NAME, cert_content_escaped); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); j_parameters = json_pack("{sssssss{ssssso}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "toggle-certificate", "certificate_id", client_cert_1_id, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, json_true(), NULL, NULL), 1); json_decref(j_parameters); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); o_free(cert_content); o_free(cert_content_escaped); } END_TEST START_TEST(test_glwd_scheme_certificate_deregister_scheme_backend_proxyfied) { json_t * j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "delete-certificate", "certificate_id", client_cert_1_id); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_module_remove_scheme_backend_proxyfied) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_scheme_certificate_module_add_user_properties_dn) { json_t * j_parameters = json_pack("{sssssssisis{soss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "use-scheme-storage", json_false(), "user-dn-property", "dn"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{ss}", "dn", CLIENT_CERT_DN); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USERNAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_success_user_properties_dn) { struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; req.client_cert_file = o_strdup(CLIENT_CERT_1_PATH); req.client_key_file = o_strdup(CLIENT_KEY_1_PATH); req.client_key_password = o_strdup(CLIENT_KEY_1_PASSWORD); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_fail_user_properties_dn) { struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; req.client_cert_file = o_strdup(CLIENT_CERT_1_PATH); req.client_key_file = o_strdup(CLIENT_KEY_1_PATH); req.client_key_password = o_strdup(CLIENT_KEY_1_PASSWORD); json_t * j_parameters = json_pack("{ss}", "dn", "error"); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USERNAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST START_TEST(test_glwd_scheme_certificate_module_remove_user_properties_dn) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); json_t * j_parameters = json_pack("{so}", "dn", json_null()); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USERNAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_module_add_user_properties_dn_certificate) { json_t * j_parameters = json_pack("{sssssssisis{sossssss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "use-scheme-storage", json_false(), "user-dn-property", "dn", "user-certificate-property", "cert", "user-certificate-format", "PEM"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{ss}", "dn", CLIENT_CERT_DN); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USERNAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_certificate_authenticate_success_user_properties_dn_cert_invalid) { struct _u_request req; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); ulfius_init_request(&req); req.check_server_certificate = 0; req.client_cert_file = o_strdup(CLIENT_CERT_1_PATH); req.client_key_file = o_strdup(CLIENT_KEY_1_PATH); req.client_key_password = o_strdup(CLIENT_KEY_1_PASSWORD); char * cert_content = get_file_content(CLIENT_CERT_2_PATH); json_t * j_parameters = json_pack("{ssss}", "dn", CLIENT_CERT_DN, "cert", cert_content); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USERNAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(cert_content); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); cert_content = get_file_content(CLIENT_CERT_1_PATH); j_parameters = json_pack("{ss}", "cert", cert_content); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/user/" USERNAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(cert_content); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); ulfius_clean_request(&req); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd scheme certificate"); tc_core = tcase_create("test_glwd_scheme_certificate"); tcase_add_test(tc_core, test_glwd_scheme_certificate_module_add_scheme_backend); tcase_add_test(tc_core, test_glwd_scheme_certificate_scope_set); tcase_add_test(tc_core, test_glwd_scheme_certificate_register_scheme_backend_use_cert); tcase_add_test(tc_core, test_glwd_scheme_certificate_get_register_scheme_backend); tcase_add_test(tc_core, test_glwd_scheme_certificate_deregister_scheme_backend); tcase_add_test(tc_core, test_glwd_scheme_certificate_register_scheme_backend_invalid_certificate); tcase_add_test(tc_core, test_glwd_scheme_certificate_register_scheme_backend); tcase_add_test(tc_core, test_glwd_scheme_certificate_get_register_scheme_backend); tcase_add_test(tc_core, test_glwd_scheme_certificate_test_register_scheme_backend); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_error_no_certificate_scheme_backend); tcase_add_test(tc_core, test_glwd_scheme_certificate_identify_error_unregistered_certificate_scheme_backend); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_error_unregistered_certificate_scheme_backend); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_error_invalid_ca_scheme_backend); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_success_scheme_backend); tcase_add_test(tc_core, test_glwd_scheme_certificate_identify_success_scheme_backend); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_cert_disabled_enabled_scheme_backend); tcase_add_test(tc_core, test_glwd_scheme_certificate_deregister_scheme_backend); tcase_add_test(tc_core, test_glwd_scheme_certificate_register_scheme_backend_multiple_cert); tcase_add_test(tc_core, test_glwd_scheme_certificate_get_register_scheme_backend_multiple_cert); tcase_add_test(tc_core, test_glwd_scheme_certificate_test_register_scheme_backend_multiple_cert); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_error_no_certificate_scheme_backend_multiple_cert); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_error_invalid_ca_scheme_backend_multiple_cert); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_success_scheme_backend_multiple_cert); tcase_add_test(tc_core, test_glwd_scheme_certificate_deregister_scheme_backend_multiple_cert); tcase_add_test(tc_core, test_glwd_scheme_certificate_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_certificate_module_remove_scheme_backend); tcase_add_test(tc_core, test_glwd_scheme_certificate_module_add_user_properties_pem); tcase_add_test(tc_core, test_glwd_scheme_certificate_scope_2_set); tcase_add_test(tc_core, test_glwd_scheme_certificate_get_register_user_properties_pem); tcase_add_test(tc_core, test_glwd_scheme_certificate_test_register_user_properties_pem); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_error_no_certificate_user_properties_pem); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_error_unregistered_certificate_user_properties_pem); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_success_user_properties_pem); tcase_add_test(tc_core, test_glwd_scheme_certificate_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_certificate_module_remove_user_properties_pem); tcase_add_test(tc_core, test_glwd_scheme_certificate_module_add_user_properties_pem_multiple_cert); tcase_add_test(tc_core, test_glwd_scheme_certificate_scope_2_set); tcase_add_test(tc_core, test_glwd_scheme_certificate_get_register_user_properties_pem_multiple_cert); tcase_add_test(tc_core, test_glwd_scheme_certificate_test_register_user_properties_pem_multiple_cert); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_error_no_certificate_user_properties_pem_multiple_cert); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_error_unregistered_certificate_user_properties_pem_multiple_cert); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_error_invalid_ca_user_properties_pem_multiple_cert); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_success_user_properties_pem_multiple_cert); tcase_add_test(tc_core, test_glwd_scheme_certificate_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_certificate_module_remove_user_properties_pem_multiple_cert); tcase_add_test(tc_core, test_glwd_scheme_certificate_module_add_user_properties_der); tcase_add_test(tc_core, test_glwd_scheme_certificate_scope_2_set); tcase_add_test(tc_core, test_glwd_scheme_certificate_get_register_user_properties_der); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_error_no_certificate_user_properties_der); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_error_unregistered_certificate_user_properties_der); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_success_user_properties_der); tcase_add_test(tc_core, test_glwd_scheme_certificate_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_certificate_module_remove_user_properties_der); tcase_add_test(tc_core, test_glwd_scheme_certificate_module_add_scheme_backend_ca_chain); tcase_add_test(tc_core, test_glwd_scheme_certificate_scope_3_set); tcase_add_test(tc_core, test_glwd_scheme_certificate_register_scheme_backend_ca_chain); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_success_scheme_backend_ca_chain); tcase_add_test(tc_core, test_glwd_scheme_certificate_deregister_scheme_backend_ca_chain); tcase_add_test(tc_core, test_glwd_scheme_certificate_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_certificate_module_remove_scheme_backend_ca_chain); tcase_add_test(tc_core, test_glwd_scheme_certificate_module_add_scheme_backend_invalid_ca_chain); tcase_add_test(tc_core, test_glwd_scheme_certificate_scope_3_set); tcase_add_test(tc_core, test_glwd_scheme_certificate_register_scheme_backend_invalid_ca_chain); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_error_scheme_backend_invalid_ca_chain); tcase_add_test(tc_core, test_glwd_scheme_certificate_deregister_scheme_backend_invalid_ca_chain); tcase_add_test(tc_core, test_glwd_scheme_certificate_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_certificate_module_remove_scheme_backend_invalid_ca_chain); tcase_add_test(tc_core, test_glwd_scheme_certificate_module_add_scheme_backend_proxyfied); tcase_add_test(tc_core, test_glwd_scheme_certificate_scope_set); tcase_add_test(tc_core, test_glwd_scheme_certificate_register_scheme_backend_proxyfied); tcase_add_test(tc_core, test_glwd_scheme_certificate_test_register_scheme_backend_proxyfied); tcase_add_test(tc_core, test_glwd_scheme_certificate_identify_error_unregistered_certificate_scheme_backend_proxyfied); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_error_no_certificate_scheme_backend_proxyfied); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_error_unregistered_certificate_scheme_backend_proxyfied); tcase_add_test(tc_core, test_glwd_scheme_certificate_identify_success_scheme_backend_proxyfied); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_success_scheme_backend_proxyfied); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_cert_disabled_enabled_scheme_backend_proxyfied); tcase_add_test(tc_core, test_glwd_scheme_certificate_deregister_scheme_backend_proxyfied); tcase_add_test(tc_core, test_glwd_scheme_certificate_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_certificate_module_remove_scheme_backend_proxyfied); tcase_add_test(tc_core, test_glwd_scheme_certificate_module_add_user_properties_dn); tcase_add_test(tc_core, test_glwd_scheme_certificate_scope_set); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_success_user_properties_dn); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_fail_user_properties_dn); tcase_add_test(tc_core, test_glwd_scheme_certificate_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_certificate_module_remove_user_properties_dn); tcase_add_test(tc_core, test_glwd_scheme_certificate_module_add_user_properties_dn_certificate); tcase_add_test(tc_core, test_glwd_scheme_certificate_scope_set); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_success_user_properties_dn); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_fail_user_properties_dn); tcase_add_test(tc_core, test_glwd_scheme_certificate_authenticate_success_user_properties_dn_cert_invalid); tcase_add_test(tc_core, test_glwd_scheme_certificate_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_certificate_module_remove_user_properties_dn); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&admin_req); admin_req.check_server_certificate = 0; ulfius_init_request(&user_req); user_req.check_server_certificate = 0; ulfius_init_request(&auth_req); auth_req.check_server_certificate = 0; ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USER_USERNAME "user1" #define USER_PASSWORD "password" #define MODULE_MODULE "mock" #define MODULE_NAME "test_mock" #define MODULE_DISPLAY_NAME "Let's make sure forbidden schemes are forbidden" #define MODULE_EXPIRATION 600 #define MODULE_MAX_USE 0 #define MOD_TYPE "register" #define MOD_NAME "register" #define MOD_DISPLAY_NAME "Register" #define SESSION_KEY "G_REGISTER_SESSION" #define SESSION_RESET_CREDENTIALS_KEY "G_CREDENTIALS_SESSION" #define SESSION_DURATION 3600 #define RESET_CREDENTIALS_CODE_PROPERTY "reset-credentials-code-property" #define RESET_CREDENTIALS_CODE_LIST_SIZE 4 #define SCOPE "g_profile" #define SCOPE_NAME "scope2" #define SCOPE_DISPLAY_NAME "Glewlwyd mock scope without password" #define SCOPE_DESCRIPTION "Glewlwyd scope 2 scope description" #define SCOPE_PASSWORD_MAX_AGE 0 #define SCOPE_SCHEME_1_TYPE "mock" #define SCOPE_SCHEME_1_NAME "mock_scheme_95" #define NEW_USERNAME "semias" struct _u_request admin_req; struct _u_request user_req; void test_profile_access(int forbid_user_profile) { json_t * j_params = json_pack("{ssssss}", "username", USER_USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_params, NULL, forbid_user_profile?403:400, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/delegate/" USER_USERNAME "/profile/scheme/register/", NULL, NULL, j_params, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_params, NULL, forbid_user_profile?403:200, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{ssssss}", "username", USER_USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_params, NULL, forbid_user_profile?403:200, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", json_false()); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_params, NULL, forbid_user_profile?403:200, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{ssssss}", "username", USER_USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "/profile/scheme/register/", NULL, NULL, j_params, NULL, forbid_user_profile?403:400, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/delegate/" USER_USERNAME "/profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{ssssss}", "username", USER_USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/delegate/" USER_USERNAME "/profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{so}}", "username", USER_USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", json_false()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/delegate/" USER_USERNAME "/profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{ssssss}", "username", USER_USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/delegate/" USER_USERNAME "/profile/scheme/register/", NULL, NULL, j_params, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_params); } void test_register_reset_cred_access(int forbid_user_reset_credential) { struct _u_request req; struct _u_response resp; int res; char * cookie; json_t * j_body = json_pack("{ss}", "username", NEW_USERNAME), * j_code; ulfius_init_request(&req); ulfius_init_response(&resp); // Registration with the new username ck_assert_int_eq(ulfius_set_request_properties(&req, U_OPT_HTTP_URL, SERVER_URI "/" MOD_NAME "/register", U_OPT_HTTP_VERB, "POST", U_OPT_JSON_BODY, j_body, U_OPT_NONE), U_OK); json_decref(j_body); res = ulfius_send_http_request(&req, &resp); ck_assert_int_eq(res, U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_int_eq(resp.nb_cookies, 1); cookie = msprintf("%s=%s", resp.map_cookie[0].key, resp.map_cookie[0].value); ck_assert_ptr_ne(cookie, NULL); u_map_put(req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&resp); // Verify get register response is 400 for scheme mock j_body = json_pack("{ssssss}", "scheme_name", MODULE_NAME, "scheme_type", MODULE_MODULE, "username", NEW_USERNAME); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/" MOD_NAME "/profile/scheme/register", NULL, NULL, j_body, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ss ss ss s{so}}", "scheme_name", MODULE_NAME, "scheme_type", MODULE_MODULE, "username", NEW_USERNAME, "value", "register", json_true()); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/profile/scheme/register", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ssssss}", "scheme_name", MODULE_NAME, "scheme_type", MODULE_MODULE, "username", NEW_USERNAME); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/" MOD_NAME "/profile/scheme/register", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{sssssss{so}}", "scheme_name", MODULE_NAME, "scheme_type", MODULE_MODULE, "username", NEW_USERNAME, "value", "register", json_false()); ck_assert_ptr_ne(j_body, NULL); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/profile/scheme/register", NULL, NULL, j_body, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_body); // Cancel registration ck_assert_int_eq(run_simple_test(&req, "DELETE", SERVER_URI "/" MOD_NAME "/profile", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ulfius_clean_request(&req); // Reset credentials ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "PUT", U_OPT_HTTP_URL, SERVER_URI "/" MOD_NAME "/reset-credentials-code", U_OPT_HEADER_PARAMETER, "Cookie", u_map_get(user_req.map_header, "Cookie"), U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne(j_code = ulfius_get_json_body_response(&resp, NULL), NULL); ck_assert_int_eq(RESET_CREDENTIALS_CODE_LIST_SIZE, json_array_size(j_code)); ulfius_clean_response(&resp); ulfius_clean_request(&req); ulfius_init_request(&req); ulfius_init_response(&resp); j_body = json_pack("{sssO}", "username", USER_USERNAME, "code", json_array_get(j_code, 0)); ulfius_set_request_properties(&req, U_OPT_HTTP_VERB, "POST", U_OPT_HTTP_URL, SERVER_URI "/" MOD_NAME "/reset-credentials-code", U_OPT_JSON_BODY, j_body, U_OPT_NONE); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); json_decref(j_body); ulfius_clean_request(&req); ulfius_init_request(&req); cookie = msprintf("%s=%s", resp.map_cookie[0].key, resp.map_cookie[0].value); u_map_put(req.map_header, "Cookie", cookie); o_free(cookie); ulfius_clean_response(&resp); // Test PUT profile/scheme/register endpoint j_body = json_pack("{ssssss}", "scheme_name", MODULE_NAME, "scheme_type", MODULE_MODULE, "username", USER_USERNAME); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/" MOD_NAME "/reset-credentials/profile/scheme/register", NULL, NULL, j_body, NULL, forbid_user_reset_credential?403:400, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{sssssss{so}}", "scheme_name", MODULE_NAME, "scheme_type", MODULE_MODULE, "username", USER_USERNAME, "value", "register", json_true()); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/reset-credentials/profile/scheme/register", NULL, NULL, j_body, NULL, forbid_user_reset_credential?403:200, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{ssssss}", "scheme_name", MODULE_NAME, "scheme_type", MODULE_MODULE, "username", USER_USERNAME); ck_assert_int_eq(run_simple_test(&req, "PUT", SERVER_URI "/" MOD_NAME "/reset-credentials/profile/scheme/register", NULL, NULL, j_body, NULL, forbid_user_reset_credential?403:200, NULL, NULL, NULL), 1); json_decref(j_body); j_body = json_pack("{sssssss{so}}", "scheme_name", MODULE_NAME, "scheme_type", MODULE_MODULE, "username", USER_USERNAME, "value", "register", json_false()); ck_assert_int_eq(run_simple_test(&req, "POST", SERVER_URI "/" MOD_NAME "/reset-credentials/profile/scheme/register", NULL, NULL, j_body, NULL, forbid_user_reset_credential?403:200, NULL, NULL, NULL), 1); json_decref(j_body); ulfius_clean_request(&req); json_decref(j_code); } void add_scheme_mod(int forbid_user_profile, int forbid_user_reset_credential) { json_t * j_parameters = json_pack("{sssssssisisosos{ss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "forbid_user_profile", forbid_user_profile?json_true():json_false(), "forbid_user_reset_credential", forbid_user_reset_credential?json_true():json_false(), "parameters", "mock-value", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssisos{s[{ssss}{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{ss ss ss so s{so so so so so si ss ss si so ss si s[ss] ss s[{ss ss ss ss}]}}", "module", MOD_TYPE, "name", MOD_NAME, "display_name", MOD_DISPLAY_NAME, "enabled", json_true(), "parameters", "registration", json_true(), "update-email", json_false(), "reset-credentials", json_true(), "reset-credentials-email", json_false(), "reset-credentials-code", json_true(), "reset-credentials-session-duration", SESSION_DURATION, "reset-credentials-session-key", SESSION_RESET_CREDENTIALS_KEY, "reset-credentials-code-property", RESET_CREDENTIALS_CODE_PROPERTY, "reset-credentials-code-list-size", RESET_CREDENTIALS_CODE_LIST_SIZE, "verify-email", json_false(), "session-key", SESSION_KEY, "session-duration", SESSION_DURATION, "scope", SCOPE, SCOPE_NAME, "set-password", "no", "schemes", "module", MODULE_MODULE, "name", MODULE_NAME, "register", "always", "display_name", MODULE_DISPLAY_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/plugin/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } START_TEST(test_glwd_http_forbidden_scheme_scope_set_false_false) { add_scheme_mod(0, 0); } END_TEST START_TEST(test_glwd_http_forbidden_scheme_test_access_false_false) { test_profile_access(0); test_register_reset_cred_access(0); } END_TEST START_TEST(test_glwd_http_forbidden_scheme_scope_set_true_false) { add_scheme_mod(1, 0); } END_TEST START_TEST(test_glwd_http_forbidden_scheme_test_access_true_false) { test_profile_access(1); test_register_reset_cred_access(0); } END_TEST START_TEST(test_glwd_http_forbidden_scheme_scope_set_false_true) { add_scheme_mod(0, 1); } END_TEST START_TEST(test_glwd_http_forbidden_scheme_test_access_false_true) { test_profile_access(0); test_register_reset_cred_access(1); } END_TEST START_TEST(test_glwd_http_forbidden_scheme_scope_set_true_true) { add_scheme_mod(1, 1); } END_TEST START_TEST(test_glwd_http_forbidden_scheme_test_access_true_true) { test_profile_access(1); test_register_reset_cred_access(1); } END_TEST START_TEST(test_glwd_http_forbidden_scheme_scope_unset) { json_t * j_parameters = json_pack("{sssssisos{s[{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/plugin/" MOD_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd oidc forbidden scheme"); tc_core = tcase_create("test_oidc_forbidden_scheme"); tcase_add_test(tc_core, test_glwd_http_forbidden_scheme_scope_set_false_false); tcase_add_test(tc_core, test_glwd_http_forbidden_scheme_test_access_false_false); tcase_add_test(tc_core, test_glwd_http_forbidden_scheme_scope_unset); tcase_add_test(tc_core, test_glwd_http_forbidden_scheme_scope_set_true_false); tcase_add_test(tc_core, test_glwd_http_forbidden_scheme_test_access_true_false); tcase_add_test(tc_core, test_glwd_http_forbidden_scheme_scope_unset); tcase_add_test(tc_core, test_glwd_http_forbidden_scheme_scope_set_false_true); tcase_add_test(tc_core, test_glwd_http_forbidden_scheme_test_access_false_true); tcase_add_test(tc_core, test_glwd_http_forbidden_scheme_scope_unset); tcase_add_test(tc_core, test_glwd_http_forbidden_scheme_scope_set_true_true); tcase_add_test(tc_core, test_glwd_http_forbidden_scheme_test_access_true_true); tcase_add_test(tc_core, test_glwd_http_forbidden_scheme_scope_unset); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; int res, do_test = 0; json_t * j_body; char * cookie; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); ulfius_init_request(&admin_req); ulfius_init_request(&user_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "Admin %s authenticated", ADMIN_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication admin"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USER_USERNAME, "password", USER_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { y_log_message(Y_LOG_LEVEL_DEBUG, "User %s authenticated", USER_USERNAME); cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(user_req.map_header, "Cookie", cookie); o_free(cookie); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication user"); do_test = 0; } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); } if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } run_simple_test(&user_req, "DELETE", SERVER_URI "/auth/", NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL); ulfius_clean_request(&admin_req); ulfius_clean_request(&user_req); y_close_logs(); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_scheme_http.c000066400000000000000000000315241415646314000207330ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api" #define CLIENT "client1_id" #define HOST "localhost" #define PORT 2884 #define PORT_UNAVAILABLE 4882 #define PREFIX "/auth" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define USERNAME "user1" #define USERNAME_FORMATTED "prefix/user1|dev1@glewlwyd@suffix" #define PASSWORD "password" #define USERNAME_FORMAT "prefix/{username}|{email}@suffix" #define MODULE_MODULE "http" #define MODULE_NAME "test_http" #define MODULE_DISPLAY_NAME "HTTP Basic auth password scheme for test" #define MODULE_EXPIRATION 600 #define MODULE_MAX_USE 0 #define SCOPE_NAME "scope2" #define SCOPE_DISPLAY_NAME "Glewlwyd mock scope without password" #define SCOPE_DESCRIPTION "Glewlwyd scope 2 scope description" #define SCOPE_PASSWORD_MAX_AGE 0 #define SCOPE_SCHEME_1_TYPE "mock" #define SCOPE_SCHEME_1_NAME "mock_scheme_95" char * host = NULL; struct _u_request admin_req; char * code; /** * Auth function for basic authentication */ int auth_basic (const struct _u_request * request, struct _u_response * response, void * user_data) { if (request->auth_basic_user != NULL && request->auth_basic_password != NULL) { if (0 == o_strcmp(request->auth_basic_user, USERNAME) && 0 == o_strcmp(request->auth_basic_password, PASSWORD)) { return U_CALLBACK_CONTINUE; } else { return U_CALLBACK_UNAUTHORIZED; } } else { return U_CALLBACK_UNAUTHORIZED; } } /** * Auth function for basic authentication with formatted username */ int auth_basic_format (const struct _u_request * request, struct _u_response * response, void * user_data) { if (request->auth_basic_user != NULL && request->auth_basic_password != NULL) { if (0 == o_strcmp(request->auth_basic_user, USERNAME_FORMATTED) && 0 == o_strcmp(request->auth_basic_password, PASSWORD)) { return U_CALLBACK_CONTINUE; } else { return U_CALLBACK_UNAUTHORIZED; } } else { return U_CALLBACK_UNAUTHORIZED; } } START_TEST(test_glwd_http_auth_scheme_scope_set) { json_t * j_parameters = json_pack("{sssssisos{s[{ssss}{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME); json_t * j_canuse = json_pack("{ssss}", "module", MODULE_MODULE, "name", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/delegate/" USERNAME "/profile/scheme/", NULL, NULL, NULL, NULL, 200, j_canuse, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_canuse); } END_TEST START_TEST(test_glwd_http_auth_scheme_scope_unset) { json_t * j_parameters = json_pack("{sssssisos{s[{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_http_auth_scheme_module_add) { char * param_url; if (host == NULL) { param_url = msprintf("http://%s:%d/auth/", HOST, PORT); } else { param_url = msprintf("http://%s:%d/auth/", host, PORT); } json_t * j_parameters = json_pack("{sssssssisis{ssso}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "url", param_url, "check-server-certificate", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); json_decref(j_parameters); o_free(param_url); } END_TEST START_TEST(test_glwd_http_auth_success) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "password", PASSWORD); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); } END_TEST START_TEST(test_glwd_http_auth_fail) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "password", "invalid"); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "/auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); } END_TEST START_TEST(test_glwd_http_auth_scheme_module_delete) { char * url = SERVER_URI "/mod/scheme/" MODULE_NAME; ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_http_auth_scheme_format_module_add) { char * param_url; if (host == NULL) { param_url = msprintf("http://%s:%d/auth/format/", HOST, PORT); } else { param_url = msprintf("http://%s:%d/auth/format/", host, PORT); } json_t * j_parameters = json_pack("{sssssssisis{sssoss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "url", param_url, "check-server-certificate", json_true(), "username-format", USERNAME_FORMAT); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); json_decref(j_parameters); o_free(param_url); } END_TEST START_TEST(test_glwd_http_auth_scheme_format_module_unavailable_add) { char * param_url; if (host == NULL) { param_url = msprintf("http://%s:%d/", HOST, PORT_UNAVAILABLE); } else { param_url = msprintf("http://%s:%d/", host, PORT_UNAVAILABLE); } json_t * j_parameters = json_pack("{sssssssisis{sssoss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "url", param_url, "check-server-certificate", json_true(), "username-format", USERNAME_FORMAT); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); json_decref(j_parameters); o_free(param_url); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd scheme HTTP Auth"); tc_core = tcase_create("test_glwd_http_auth"); tcase_add_test(tc_core, test_glwd_http_auth_scheme_module_add); tcase_add_test(tc_core, test_glwd_http_auth_scheme_scope_set); tcase_add_test(tc_core, test_glwd_http_auth_success); tcase_add_test(tc_core, test_glwd_http_auth_fail); tcase_add_test(tc_core, test_glwd_http_auth_scheme_scope_unset); tcase_add_test(tc_core, test_glwd_http_auth_scheme_module_delete); tcase_add_test(tc_core, test_glwd_http_auth_scheme_format_module_add); tcase_add_test(tc_core, test_glwd_http_auth_scheme_scope_set); tcase_add_test(tc_core, test_glwd_http_auth_success); tcase_add_test(tc_core, test_glwd_http_auth_fail); tcase_add_test(tc_core, test_glwd_http_auth_scheme_scope_unset); tcase_add_test(tc_core, test_glwd_http_auth_scheme_module_delete); tcase_add_test(tc_core, test_glwd_http_auth_scheme_format_module_unavailable_add); tcase_add_test(tc_core, test_glwd_http_auth_scheme_scope_set); tcase_add_test(tc_core, test_glwd_http_auth_fail); tcase_add_test(tc_core, test_glwd_http_auth_scheme_scope_unset); tcase_add_test(tc_core, test_glwd_http_auth_scheme_module_delete); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; struct _u_instance instance; int res, do_test = 0; json_t * j_body; char * cookie; if (argc > 1) { host = argv[1]; } y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); if (ulfius_init_instance(&instance, PORT, NULL, "auth_basic_default") != U_OK) { y_log_message(Y_LOG_LEVEL_INFO, "Error ulfius_init_instance, abort"); return(1); } ulfius_add_endpoint_by_val(&instance, "GET", PREFIX, NULL, 0, &auth_basic, NULL); ulfius_add_endpoint_by_val(&instance, "GET", PREFIX, "/format", 0, &auth_basic_format, NULL); if (ulfius_start_framework(&instance) == U_OK) { y_log_message(Y_LOG_LEVEL_INFO, "Start framework on port %d", instance.port); } else { y_log_message(Y_LOG_LEVEL_INFO, "Error starting framework"); return(1); } ulfius_init_request(&admin_req); // Getting a valid session id for authenticated http requests ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", ADMIN_USERNAME, "password", ADMIN_PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { if (auth_resp.nb_cookies) { cookie = msprintf("%s=%s", auth_resp.map_cookie[0].key, auth_resp.map_cookie[0].value); u_map_put(admin_req.map_header, "Cookie", cookie); o_free(cookie); do_test = 1; } } else { y_log_message(Y_LOG_LEVEL_ERROR, "Error authentication"); } ulfius_clean_response(&auth_resp); ulfius_clean_request(&auth_req); if (do_test) { s = glewlwyd_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); } ulfius_clean_request(&admin_req); y_close_logs(); ulfius_stop_framework(&instance); ulfius_clean_instance(&instance); return (do_test && number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } glewlwyd-2.6.1/test/glewlwyd_scheme_mail.c000066400000000000000000000643241415646314000207020ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api/" #define USERNAME "user1" #define USERNAME_LANG "user_lang" #define NAME_LANG "Dave Lopper with e-mails" #define EMAIL "user_lang@glewlwyd.tld" #define PASSWORD "password" #define SCOPE_LIST "scope2" #define CLIENT "client1_id" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define MODULE_MODULE "email" #define MODULE_NAME "mail" #define MODULE_LANG_NAME "mail_lang" #define MODULE_DISPLAY_NAME "Mail scheme" #define MODULE_EXPIRATION 600 #define MODULE_MAX_USE 0 #define MAIL_CODE_DURATION 600 #define MAIL_CODE_LEGTH 6 #define MAIL_HOST "localhost" #define MAIL_PORT 2525 #define MAIL_FROM "glewlwyd" #define MAIL_CONTENT_TYPE "text/plain; charset=utf-8" #define MAIL_SUBJECT "Authorization Code" #define MAIL_SUBJECT_FR "Code d'autorisation en français" #define MAIL_BODY_PATTERN "The code is " #define MAIL_BODY_PATTERN_FR "Le code en français est " #define MAIL_BODY_CODE "{CODE}" #define SCOPE_NAME "scope2" #define SCOPE_DISPLAY_NAME "Glewlwyd mock scope without password" #define SCOPE_DESCRIPTION "Glewlwyd scope 2 scope description" #define SCOPE_PASSWORD_MAX_AGE 0 #define SCOPE_SCHEME_1_TYPE "mock" #define SCOPE_SCHEME_1_NAME "mock_scheme_95" struct _u_request user_req; struct _u_request admin_req; char * mail_host = NULL; #define BACKLOG_MAX (10) #define BUF_SIZE 4096 #define STREQU(a,b) (strcmp(a, b) == 0) struct smtp_manager { char * mail_data; unsigned int port; int sockfd; const char * body_pattern; }; /** * * Function that emulates a very simple SMTP server * Taken from Kenneth Finnegan's ccsmtp program * https://gist.github.com/PhirePhly/2914635 * This function is under the GPL2 license * */ static void handle_smtp (struct smtp_manager * manager) { int rc, i; char buffer[BUF_SIZE], bufferout[BUF_SIZE]; int buffer_offset = 0; buffer[BUF_SIZE-1] = '\0'; // Flag for being inside of DATA verb int inmessage = 0; sprintf(bufferout, "220 ulfius.tld SMTP CCSMTP\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); while (1) { fd_set sockset; struct timeval tv; FD_ZERO(&sockset); FD_SET(manager->sockfd, &sockset); tv.tv_sec = 120; // Some SMTP servers pause for ~15s per message tv.tv_usec = 0; // Wait tv timeout for the server to send anything. select(manager->sockfd+1, &sockset, NULL, NULL, &tv); if (!FD_ISSET(manager->sockfd, &sockset)) { y_log_message(Y_LOG_LEVEL_DEBUG, "%d: Socket timed out", manager->sockfd); break; } int buffer_left = BUF_SIZE - buffer_offset - 1; if (buffer_left == 0) { y_log_message(Y_LOG_LEVEL_DEBUG, "%d: Command line too long", manager->sockfd); sprintf(bufferout, "500 Too long\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); buffer_offset = 0; continue; } rc = recv(manager->sockfd, buffer + buffer_offset, buffer_left, 0); if (rc == 0) { y_log_message(Y_LOG_LEVEL_DEBUG, "%d: Remote host closed socket", manager->sockfd); break; } if (rc == -1) { y_log_message(Y_LOG_LEVEL_DEBUG, "%d: Error on socket", manager->sockfd); break; } buffer_offset += rc; char *eol; // Only process one line of the received buffer at a time // If multiple lines were received in a single recv(), goto // back to here for each line // processline: eol = strstr(buffer, "\r\n"); if (eol == NULL) { y_log_message(Y_LOG_LEVEL_DEBUG, "%d: Haven't found EOL yet", manager->sockfd); continue; } // Null terminate each line to be processed individually eol[0] = '\0'; if (!inmessage) { // Handle system verbs // Replace all lower case letters so verbs are all caps for (i=0; i<4; i++) { if (islower(buffer[i])) { buffer[i] += 'A' - 'a'; } } // Null-terminate the verb for strcmp buffer[4] = '\0'; // Respond to each verb accordingly. // You should replace these with more meaningful // actions than simply printing everything. // if (STREQU(buffer, "HELO")) { // Initial greeting sprintf(bufferout, "250 Ok\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); } else if (STREQU(buffer, "MAIL")) { // New mail from... sprintf(bufferout, "250 Ok\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); } else if (STREQU(buffer, "RCPT")) { // Mail addressed to... sprintf(bufferout, "250 Ok recipient\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); } else if (STREQU(buffer, "DATA")) { // Message contents... sprintf(bufferout, "354 Continue\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); inmessage = 1; } else if (STREQU(buffer, "RSET")) { // Reset the connection sprintf(bufferout, "250 Ok reset\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); } else if (STREQU(buffer, "NOOP")) { // Do nothing. sprintf(bufferout, "250 Ok noop\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); } else if (STREQU(buffer, "QUIT")) { // Close the connection sprintf(bufferout, "221 Ok\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); break; } else { // The verb used hasn't been implemented. sprintf(bufferout, "502 Command Not Implemented\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); } } else { // We are inside the message after a DATA verb. if (0 == o_strncmp(manager->body_pattern, buffer, o_strlen(manager->body_pattern))) { manager->mail_data = o_strdup(buffer+o_strlen(manager->body_pattern)); } if (STREQU(buffer, ".")) { // A single "." signifies the end sprintf(bufferout, "250 Ok\r\n"); send(manager->sockfd, bufferout, strlen(bufferout), 0); inmessage = 0; } } // Shift the rest of the buffer to the front memmove(buffer, eol+2, BUF_SIZE - (eol + 2 - buffer)); buffer_offset -= (eol - buffer) + 2; // Do we already have additional lines to process? If so, // commit a horrid sin and goto the line processing section again. if (strstr(buffer, "\r\n")) goto processline; } // All done. Clean up everything and exit. shutdown(manager->sockfd, SHUT_WR); close(manager->sockfd); } static void * simple_smtp(void * args) { struct smtp_manager * manager = (struct smtp_manager *)args; int server_fd; struct sockaddr_in address; int opt = 1; int addrlen = sizeof(address); if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) != 0) { if (!setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) { address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons( manager->port ); if (!bind(server_fd, (struct sockaddr *)&address, sizeof(address))) { if (listen(server_fd, 3) >= 0) { if ((manager->sockfd = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) >= 0) { handle_smtp(manager); } else { y_log_message(Y_LOG_LEVEL_ERROR, "simple_smtp - Error accept"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "simple_smtp - Error listen"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "simple_smtp - Error bind"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "simple_smtp - Error setsockopt"); } } else { y_log_message(Y_LOG_LEVEL_ERROR, "simple_smtp - Error socket"); } shutdown(server_fd, SHUT_RDWR); close(server_fd); pthread_exit(NULL); } START_TEST(test_glwd_scheme_mail_scope_set) { json_t * j_parameters = json_pack("{sssssisos{s[{ssss}{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME); json_t * j_canuse = json_pack("{ssss}", "module", MODULE_MODULE, "name", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/delegate/" USERNAME "/profile/scheme/", NULL, NULL, NULL, NULL, 200, j_canuse, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_canuse); } END_TEST START_TEST(test_glwd_scheme_mail_multilang_scope_set) { json_t * j_parameters = json_pack("{sssssisos{s[{ssss}{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_LANG_NAME); json_t * j_canuse = json_pack("{ssss}", "module", MODULE_MODULE, "name", MODULE_LANG_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/delegate/" USERNAME "/profile/scheme/", NULL, NULL, NULL, NULL, 200, j_canuse, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_canuse); } END_TEST START_TEST(test_glwd_scheme_mail_scope_unset) { json_t * j_parameters = json_pack("{sssssisos{s[{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_mail_irl_module_add) { char * url = msprintf("%s/mod/scheme/", SERVER_URI); json_t * j_parameters = json_pack("{sssssssisis{sisisssissssss}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "code-duration", MAIL_CODE_DURATION, "code-length", MAIL_CODE_LEGTH, "host", mail_host==NULL?MAIL_HOST:mail_host, "port", MAIL_PORT, "from", MAIL_FROM, "subject", MAIL_SUBJECT, "body-pattern", MAIL_BODY_PATTERN MAIL_BODY_CODE); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/mod/scheme/%s", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_mail_irl_module_multilang_add) { json_t * j_parameters = json_pack("{sssssssisis{sisisssisssssss{s{sossss}s{sossss}}}}", "module", MODULE_MODULE, "name", MODULE_LANG_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "code-duration", MAIL_CODE_DURATION, "code-length", MAIL_CODE_LEGTH, "host", mail_host==NULL?MAIL_HOST:mail_host, "port", MAIL_PORT, "from", MAIL_FROM, "content-type", MAIL_CONTENT_TYPE, "user-lang-property", "lang", "templates", "en", "defaultLang", json_true(), "subject", MAIL_SUBJECT, "body-pattern", MAIL_BODY_PATTERN MAIL_BODY_CODE, "fr", "defaultLang", json_false(), "subject", MAIL_SUBJECT_FR, "body-pattern", MAIL_BODY_PATTERN_FR MAIL_BODY_CODE); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/mod/scheme/" MODULE_LANG_NAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_mail_irl_user_fr_add) { json_t * j_parameters = json_pack("{sssssssss[s]so}", "username", USERNAME_LANG, "name", NAME_LANG, "lang", "fr", "email", EMAIL, "scope", SCOPE_LIST, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/user/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_mail_irl_user_de_add) { json_t * j_parameters = json_pack("{sssssssss[s]so}", "username", USERNAME_LANG, "name", NAME_LANG, "lang", "de", "email", EMAIL, "scope", SCOPE_LIST, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/user/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_mail_irl_trigger) { struct smtp_manager manager; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); json_t * j_canuse = json_pack("{ssss}", "module", MODULE_MODULE, "name", MODULE_NAME); pthread_t thread; manager.mail_data = NULL; manager.port = MAIL_PORT; manager.sockfd = 0; manager.body_pattern = MAIL_BODY_PATTERN; pthread_create(&thread, NULL, simple_smtp, &manager); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "profile/scheme/", NULL, NULL, NULL, NULL, 200, j_canuse, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "auth/scheme/trigger/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); pthread_join(thread, NULL); o_free(manager.mail_data); json_decref(j_params); json_decref(j_canuse); } END_TEST START_TEST(test_glwd_scheme_mail_irl_validate_error) { struct smtp_manager manager; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); pthread_t thread; manager.mail_data = NULL; manager.port = MAIL_PORT; manager.sockfd = 0; manager.body_pattern = MAIL_BODY_PATTERN; pthread_create(&thread, NULL, simple_smtp, &manager); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "auth/scheme/trigger/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); pthread_join(thread, NULL); o_free(manager.mail_data); json_decref(j_params); j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "error"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_params); } END_TEST START_TEST(test_glwd_scheme_mail_irl_validate_ok) { struct smtp_manager manager; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); pthread_t thread; manager.mail_data = NULL; manager.port = MAIL_PORT; manager.sockfd = 0; manager.body_pattern = MAIL_BODY_PATTERN; pthread_create(&thread, NULL, simple_smtp, &manager); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "auth/scheme/trigger/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); pthread_join(thread, NULL); json_decref(j_params); j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "code", manager.mail_data); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); o_free(manager.mail_data); } END_TEST START_TEST(test_glwd_scheme_mail_irl_validate_not_valid) { struct smtp_manager manager; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value"); pthread_t thread; manager.mail_data = NULL; manager.port = MAIL_PORT; manager.sockfd = 0; manager.body_pattern = MAIL_BODY_PATTERN; pthread_create(&thread, NULL, simple_smtp, &manager); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "auth/scheme/trigger/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); pthread_join(thread, NULL); json_decref(j_params); j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "code", "errorr"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "code", manager.mail_data); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "code", manager.mail_data); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); o_free(manager.mail_data); } END_TEST START_TEST(test_glwd_scheme_mail_irl_validate_lang_fr_ok) { struct smtp_manager manager; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME_LANG, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_LANG_NAME, "value"); pthread_t thread; manager.mail_data = NULL; manager.port = MAIL_PORT; manager.sockfd = 0; manager.body_pattern = MAIL_BODY_PATTERN_FR; pthread_create(&thread, NULL, simple_smtp, &manager); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "auth/scheme/trigger/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); pthread_join(thread, NULL); json_decref(j_params); j_params = json_pack("{sssssss{ss}}", "username", USERNAME_LANG, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_LANG_NAME, "value", "code", manager.mail_data); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); o_free(manager.mail_data); } END_TEST START_TEST(test_glwd_scheme_mail_irl_validate_lang_default_ok) { struct smtp_manager manager; json_t * j_params = json_pack("{sssssss{}}", "username", USERNAME_LANG, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_LANG_NAME, "value"); pthread_t thread; manager.mail_data = NULL; manager.port = MAIL_PORT; manager.sockfd = 0; manager.body_pattern = MAIL_BODY_PATTERN; pthread_create(&thread, NULL, simple_smtp, &manager); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "auth/scheme/trigger/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); pthread_join(thread, NULL); json_decref(j_params); j_params = json_pack("{sssssss{ss}}", "username", USERNAME_LANG, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_LANG_NAME, "value", "code", manager.mail_data); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); o_free(manager.mail_data); } END_TEST START_TEST(test_glwd_scheme_mail_irl_user_remove) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/user/" USERNAME_LANG, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_scheme_mail_irl_module_remove) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_scheme_mail_irl_module_multilang_remove) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/scheme/" MODULE_LANG_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd scheme e-mail"); tc_core = tcase_create("test_glwd_scheme_mail_irl"); tcase_add_test(tc_core, test_glwd_scheme_mail_irl_module_add); tcase_add_test(tc_core, test_glwd_scheme_mail_scope_set); tcase_add_test(tc_core, test_glwd_scheme_mail_irl_trigger); tcase_add_test(tc_core, test_glwd_scheme_mail_irl_validate_error); tcase_add_test(tc_core, test_glwd_scheme_mail_irl_validate_ok); tcase_add_test(tc_core, test_glwd_scheme_mail_irl_validate_not_valid); tcase_add_test(tc_core, test_glwd_scheme_mail_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_mail_irl_module_remove); tcase_add_test(tc_core, test_glwd_scheme_mail_irl_module_multilang_add); tcase_add_test(tc_core, test_glwd_scheme_mail_multilang_scope_set); tcase_add_test(tc_core, test_glwd_scheme_mail_irl_user_fr_add); tcase_add_test(tc_core, test_glwd_scheme_mail_irl_validate_lang_fr_ok); tcase_add_test(tc_core, test_glwd_scheme_mail_irl_user_remove); tcase_add_test(tc_core, test_glwd_scheme_mail_irl_user_de_add); tcase_add_test(tc_core, test_glwd_scheme_mail_irl_validate_lang_default_ok); tcase_add_test(tc_core, test_glwd_scheme_mail_irl_user_remove); tcase_add_test(tc_core, test_glwd_scheme_mail_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_mail_irl_module_multilang_remove); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i; if (argc > 1) { mail_host = argv[1]; } y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&user_req); ulfius_init_request(&admin_req); ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api/" #define USERNAME "user1" #define USERNAME2 "user2" #define PASSWORD "password" #define SCOPE_LIST "scope1 scope2" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define MODULE_MODULE "oauth2" #define MODULE_NAME "test_oauth2" #define MODULE_NAME_2 "test_oauth2_2" #define MODULE_DISPLAY_NAME "OAuth2 scheme for test" #define MODULE_EXPIRATION 600 #define MODULE_MAX_USE 0 #define PROVIDER_PORT 8080 #define REDIRECT_URI "https://www.sso.tld/callback.html" #define SESSION_EXPIRATION 600 #define CODE "codeXyz1234" #define REFRESH_TOKEN "refresh_tokenXyz1234" #define ACCESS_TOKEN "access_tokenXyz1234" #define PROVIDER_NAME "provider" #define PROVIDER_TYPE_OAUTH2 "oauth2" #define PROVIDER_TYPE_OIDC "oidc" #define PROVIDER_RESPONSE_TYPE_CODE "code" #define PROVIDER_RESPONSE_TYPE_TOKEN "token" #define PROVIDER_RESPONSE_TYPE_ID_TOKEN "id_token" #define PROVIDER_LOGO_URI "https://provider.tld/logo.png" #define PROVIDER_LOGO_FA "fa-plus" #define PROVIDER_CLIENT_ID "client1" #define PROVIDER_CLIENT_SECRET "secret" #define PROVIDER_USERID_PROPERTY "username" #define PROVIDER_AUTH_ENDPOINT "http://localhost:8080/auth/" #define PROVIDER_TOKEN_ENDPOINT "http://localhost:8080/token/" #define PROVIDER_USERINFO_ENDPOINT "http://localhost:8080/userinfo" #define PROVIDER_CONFIG_ENDPOINT "http://localhost:8080/.well-known/openid-configuration" #define PROVIDER_SCOPE "scope" #define ISSUER "https://glewlwyd.tld/" #define AUTH_ENDPOINT "http://localhost:8080/auth" #define TOKEN_ENDPOINT "http://localhost:8080/token" #define USERINFO_ENDPOINT "http://localhost:8080/userinfo" #define JWKS_URI "http://localhost:8080/jwks" #define JWKS_URI_INVALID "http://localhost:8080/jwks_invalid" #define AUTH_METHOD_1 "client_secret_basic" #define AUTH_METHOD_2 "client_secret_jwt" #define ALG_VALUE_1 "RS512" #define ALG_VALUE_2 "RS256" #define SCOPE_1 "openid" #define SCOPE_2 "g_profile" #define RESP_TYPE_1 "code" #define RESP_TYPE_2 "id_token" #define RESP_TYPE_3 "token" #define RESP_TYPE_4 "none" #define RESP_TYPE_5 "refresh_token" #define MODE_1 "query" #define MODE_2 "fragment" #define GRANT_TYPE_1 "authorization_code" #define GRANT_TYPE_2 "implicit" #define DISPLAY_1 "page" #define DISPLAY_2 "popup" #define DISPLAY_3 "touch" #define DISPLAY_4 "wap" #define CLAIM_TYPE "normal" #define CLAIMS_PARAM_SUPPORTED "true" #define CLAIMS_SUPPORTED "name" #define DOC "https://glewlwyd.tld/docs" #define LOCALE_1 "en" #define LOCALE_2 "fr" #define LOCALE_3 "nl" #define REQUEST_PARAM "true" #define REQUEST_URI "true" #define REQUIRE_REQUEST_REGIS "false" #define SUBJECT_TYPE "public" #define SCOPE_NAME "scope2" #define SCOPE_DISPLAY_NAME "Glewlwyd mock scope without password" #define SCOPE_DESCRIPTION "Glewlwyd scope 2 scope description" #define SCOPE_PASSWORD_MAX_AGE 0 #define SCOPE_SCHEME_1_TYPE "mock" #define SCOPE_SCHEME_1_NAME "mock_scheme_95" const char openid_configuration_valid[] = "{\ \"issuer\":\"" ISSUER "\",\ \"authorization_endpoint\":\"" AUTH_ENDPOINT "\",\ \"token_endpoint\":\"" TOKEN_ENDPOINT "\",\ \"userinfo_endpoint\":\"" USERINFO_ENDPOINT "\",\ \"jwks_uri\":\"" JWKS_URI "\",\ \"token_endpoint_auth_methods_supported\":[\"" AUTH_METHOD_1 "\",\"" AUTH_METHOD_2 "\"],\ \"id_token_signing_alg_values_supported\":[\"" ALG_VALUE_1 "\",\"" ALG_VALUE_2 "\"],\ \"scopes_supported\":[\"" SCOPE_1 "\",\"" SCOPE_2 "\"],\ \"response_types_supported\":[\"" RESP_TYPE_1 "\",\"" RESP_TYPE_2 "\",\"" RESP_TYPE_3 "\",\"" RESP_TYPE_1 " " RESP_TYPE_2 "\",\"" RESP_TYPE_3 " " RESP_TYPE_2 "\",\"" RESP_TYPE_1 " " RESP_TYPE_2" " RESP_TYPE_3 "\",\"" RESP_TYPE_4 "\",\"" RESP_TYPE_5 "\"],\ \"response_modes_supported\":[\"" MODE_1 "\",\"" MODE_2 "\"],\ \"grant_types_supported\":[\"" GRANT_TYPE_1 "\",\"" GRANT_TYPE_2 "\"],\ \"display_values_supported\":[\"" DISPLAY_1 "\",\"" DISPLAY_2 "\",\"" DISPLAY_3 "\",\"" DISPLAY_4 "\"],\ \"claim_types_supported\":[\"" CLAIM_TYPE "\"],\ \"claims_parameter_supported\":" CLAIMS_PARAM_SUPPORTED ",\ \"claims_supported\":[\"" CLAIMS_SUPPORTED "\"],\ \"service_documentation\":\"" DOC "\",\ \"ui_locales_supported\":[\"" LOCALE_1 "\",\"" LOCALE_2 "\",\"" LOCALE_3 "\"],\ \"request_parameter_supported\":" REQUEST_PARAM ",\ \"request_uri_parameter_supported\":" REQUEST_URI ",\ \"require_request_uri_registration\":" REQUIRE_REQUEST_REGIS ",\ \"subject_types_supported\":[\"" SUBJECT_TYPE "\"]\ }"; const char public_key[] = "-----BEGIN PUBLIC KEY-----\n" "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv\n" "vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc\n" "aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy\n" "tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0\n" "e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb\n" "V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9\n" "MwIDAQAB\n" "-----END PUBLIC KEY-----\n"; const unsigned char private_key[] = "-----BEGIN RSA PRIVATE KEY-----\n" "MIIEogIBAAKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWw\n" "kWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mr\n" "m/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEi\n" "NQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV\n" "3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2\n" "QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQABAoIBACiARq2wkltjtcjs\n" "kFvZ7w1JAORHbEufEO1Eu27zOIlqbgyAcAl7q+/1bip4Z/x1IVES84/yTaM8p0go\n" "amMhvgry/mS8vNi1BN2SAZEnb/7xSxbflb70bX9RHLJqKnp5GZe2jexw+wyXlwaM\n" "+bclUCrh9e1ltH7IvUrRrQnFJfh+is1fRon9Co9Li0GwoN0x0byrrngU8Ak3Y6D9\n" "D8GjQA4Elm94ST3izJv8iCOLSDBmzsPsXfcCUZfmTfZ5DbUDMbMxRnSo3nQeoKGC\n" "0Lj9FkWcfmLcpGlSXTO+Ww1L7EGq+PT3NtRae1FZPwjddQ1/4V905kyQFLamAA5Y\n" "lSpE2wkCgYEAy1OPLQcZt4NQnQzPz2SBJqQN2P5u3vXl+zNVKP8w4eBv0vWuJJF+\n" "hkGNnSxXQrTkvDOIUddSKOzHHgSg4nY6K02ecyT0PPm/UZvtRpWrnBjcEVtHEJNp\n" "bU9pLD5iZ0J9sbzPU/LxPmuAP2Bs8JmTn6aFRspFrP7W0s1Nmk2jsm0CgYEAyH0X\n" "+jpoqxj4efZfkUrg5GbSEhf+dZglf0tTOA5bVg8IYwtmNk/pniLG/zI7c+GlTc9B\n" "BwfMr59EzBq/eFMI7+LgXaVUsM/sS4Ry+yeK6SJx/otIMWtDfqxsLD8CPMCRvecC\n" "2Pip4uSgrl0MOebl9XKp57GoaUWRWRHqwV4Y6h8CgYAZhI4mh4qZtnhKjY4TKDjx\n" "QYufXSdLAi9v3FxmvchDwOgn4L+PRVdMwDNms2bsL0m5uPn104EzM6w1vzz1zwKz\n" "5pTpPI0OjgWN13Tq8+PKvm/4Ga2MjgOgPWQkslulO/oMcXbPwWC3hcRdr9tcQtn9\n" "Imf9n2spL/6EDFId+Hp/7QKBgAqlWdiXsWckdE1Fn91/NGHsc8syKvjjk1onDcw0\n" "NvVi5vcba9oGdElJX3e9mxqUKMrw7msJJv1MX8LWyMQC5L6YNYHDfbPF1q5L4i8j\n" "8mRex97UVokJQRRA452V2vCO6S5ETgpnad36de3MUxHgCOX3qL382Qx9/THVmbma\n" "3YfRAoGAUxL/Eu5yvMK8SAt/dJK6FedngcM3JEFNplmtLYVLWhkIlNRGDwkg3I5K\n" "y18Ae9n7dHVueyslrb6weq7dTkYDi3iOYRW8HRkIQh06wEdbxt0shTzAJvvCQfrB\n" "jg/3747WSsf/zBTcHihTRBdAv6OmdhV4/dD5YBfLAkLrd+mX7iE=\n" "-----END RSA PRIVATE KEY-----\n"; const char id_token_pattern[] = "{\"amr\":[\"password\"],\"aud\":\"%s\",\"auth_time\":%lld" ",\"azp\":\"%s\",\"exp\":%lld,\"iat\":%lld,\"iss\":\"%s\"" ",\"nonce\":\"%s\",\"sub\":\"wRNaPT1UBIw4Cl9eo3yOzoH" "7vE81Phfu\"}"; const char id_token_invalid_sub_pattern[] = "{\"amr\":[\"password\"],\"aud\":\"%s\",\"auth_time\":%lld" ",\"azp\":\"%s\",\"exp\":%lld,\"iat\":%lld,\"iss\":\"%s\"" ",\"nonce\":\"%s\",\"sub\":\"error\"}"; #define EXPIRES_IN 3600 const char code[] = "codeXyz1234"; const char c_hash[] = "xTrH4sIDT1DIDKEmAfED1g"; struct _u_request user_req; struct _u_request admin_req; static int callback_token_error_client (const struct _u_request * request, struct _u_response * response, void * user_data) { json_t * j_result = json_pack("{ss}", "error", "invalid_client"); ulfius_set_json_body_response(response, 400, j_result); json_decref(j_result); return U_CALLBACK_CONTINUE; } static int callback_token_error_format (const struct _u_request * request, struct _u_response * response, void * user_data) { json_t * j_result = json_pack("{sssi}", "access_token", ACCESS_TOKEN, "expires_in", EXPIRES_IN); ulfius_set_json_body_response(response, 400, j_result); json_decref(j_result); return U_CALLBACK_CONTINUE; } static int callback_token_ok (const struct _u_request * request, struct _u_response * response, void * user_data) { json_t * j_result = json_pack("{sssssssi}", "refresh_token", REFRESH_TOKEN, "access_token", ACCESS_TOKEN, "token_type", "bearer", "expires_in", EXPIRES_IN); ulfius_set_json_body_response(response, 200, j_result); json_decref(j_result); return U_CALLBACK_CONTINUE; } static int callback_token_oidc_error_signature (const struct _u_request * request, struct _u_response * response, void * user_data) { jwt_t * jwt; char * grants = NULL, * jwt_str; time_t now; ck_assert_int_eq(r_jwt_init(&jwt), 0); time(&now); grants = msprintf(id_token_pattern, PROVIDER_CLIENT_ID, (long long)now, PROVIDER_CLIENT_ID, (long long)(now + EXPIRES_IN), (long long)now, ISSUER, (const char *)user_data); ck_assert_ptr_ne(grants, NULL); ck_assert_int_eq(r_jwt_set_full_claims_json_str(jwt, grants), 0); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt, "c_hash", c_hash), 0); ck_assert_int_eq(r_jwt_set_sign_alg(jwt, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, private_key, o_strlen((const char *)private_key), NULL, 0), RHN_OK); ck_assert_ptr_ne((jwt_str = r_jwt_serialize_signed(jwt, NULL, 0)), NULL); jwt_str[o_strlen(jwt_str)-2] = '\0'; json_t * j_result = json_pack("{sssssssiss}", "refresh_token", REFRESH_TOKEN, "access_token", ACCESS_TOKEN, "token_type", "bearer", "expires_in", EXPIRES_IN, "id_token", jwt_str); ulfius_set_json_body_response(response, 200, j_result); json_decref(j_result); r_jwt_free(jwt); o_free(grants); o_free(jwt_str); return U_CALLBACK_CONTINUE; } static int callback_token_oidc_invalid_payload (const struct _u_request * request, struct _u_response * response, void * user_data) { jwt_t * jwt; char * grants = NULL, * jwt_str; time_t now; ck_assert_int_eq(r_jwt_init(&jwt), 0); time(&now); grants = msprintf(id_token_pattern, PROVIDER_CLIENT_ID, (long long)now, PROVIDER_CLIENT_ID, (long long)(now - EXPIRES_IN), (long long)now, ISSUER, (const char *)user_data); ck_assert_ptr_ne(grants, NULL); ck_assert_int_eq(r_jwt_set_full_claims_json_str(jwt, grants), 0); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt, "c_hash", c_hash), 0); ck_assert_int_eq(r_jwt_set_sign_alg(jwt, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, private_key, o_strlen((const char *)private_key), NULL, 0), RHN_OK); ck_assert_ptr_ne((jwt_str = r_jwt_serialize_signed(jwt, NULL, 0)), NULL); json_t * j_result = json_pack("{sssssssiss}", "refresh_token", REFRESH_TOKEN, "access_token", ACCESS_TOKEN, "token_type", "bearer", "expires_in", EXPIRES_IN, "id_token", jwt_str); ulfius_set_json_body_response(response, 200, j_result); json_decref(j_result); r_jwt_free(jwt); o_free(grants); o_free(jwt_str); return U_CALLBACK_CONTINUE; } static int callback_token_oidc_ok (const struct _u_request * request, struct _u_response * response, void * user_data) { jwt_t * jwt; char * grants = NULL, * jwt_str; time_t now; ck_assert_int_eq(r_jwt_init(&jwt), 0); time(&now); grants = msprintf(id_token_pattern, PROVIDER_CLIENT_ID, (long long)now, PROVIDER_CLIENT_ID, (long long)(now + EXPIRES_IN), (long long)now, ISSUER, (const char *)user_data); ck_assert_ptr_ne(grants, NULL); ck_assert_int_eq(r_jwt_set_full_claims_json_str(jwt, grants), 0); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt, "c_hash", c_hash), 0); ck_assert_int_eq(r_jwt_set_sign_alg(jwt, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, private_key, o_strlen((const char *)private_key), NULL, 0), RHN_OK); ck_assert_ptr_ne((jwt_str = r_jwt_serialize_signed(jwt, NULL, 0)), NULL); json_t * j_result = json_pack("{sssssssiss}", "refresh_token", REFRESH_TOKEN, "access_token", ACCESS_TOKEN, "token_type", "bearer", "expires_in", EXPIRES_IN, "id_token", jwt_str); ulfius_set_json_body_response(response, 200, j_result); json_decref(j_result); r_jwt_free(jwt); o_free(grants); o_free(jwt_str); return U_CALLBACK_CONTINUE; } static int callback_token_oidc_invalid_userid (const struct _u_request * request, struct _u_response * response, void * user_data) { jwt_t * jwt; char * grants = NULL, * jwt_str; time_t now; ck_assert_int_eq(r_jwt_init(&jwt), 0); time(&now); grants = msprintf(id_token_invalid_sub_pattern, PROVIDER_CLIENT_ID, (long long)now, PROVIDER_CLIENT_ID, (long long)(now + EXPIRES_IN), (long long)now, ISSUER, (const char *)user_data); ck_assert_ptr_ne(grants, NULL); ck_assert_int_eq(r_jwt_set_full_claims_json_str(jwt, grants), 0); ck_assert_int_eq(r_jwt_set_claim_str_value(jwt, "c_hash", c_hash), 0); ck_assert_int_eq(r_jwt_set_sign_alg(jwt, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, private_key, o_strlen((const char *)private_key), NULL, 0), RHN_OK); ck_assert_ptr_ne((jwt_str = r_jwt_serialize_signed(jwt, NULL, 0)), NULL); json_t * j_result = json_pack("{sssssssiss}", "refresh_token", REFRESH_TOKEN, "access_token", ACCESS_TOKEN, "token_type", "bearer", "expires_in", EXPIRES_IN, "id_token", jwt_str); ulfius_set_json_body_response(response, 200, j_result); json_decref(j_result); r_jwt_free(jwt); o_free(grants); o_free(jwt_str); return U_CALLBACK_CONTINUE; } static int callback_userinfo_error_format (const struct _u_request * request, struct _u_response * response, void * user_data) { ulfius_set_string_body_response(response, 200, "username=" USERNAME); return U_CALLBACK_CONTINUE; } static int callback_userinfo_invalid_userid (const struct _u_request * request, struct _u_response * response, void * user_data) { json_t * j_result = json_pack("{ss}", PROVIDER_USERID_PROPERTY, USERNAME2); ulfius_set_json_body_response(response, 200, j_result); json_decref(j_result); return U_CALLBACK_CONTINUE; } static int callback_userinfo_ok (const struct _u_request * request, struct _u_response * response, void * user_data) { json_t * j_result = json_pack("{ss}", PROVIDER_USERID_PROPERTY, USERNAME); ulfius_set_json_body_response(response, 200, j_result); json_decref(j_result); return U_CALLBACK_CONTINUE; } static int callback_openid_configuration_valid (const struct _u_request * request, struct _u_response * response, void * user_data) { json_t * j_response = json_loads(openid_configuration_valid, JSON_DECODE_ANY, NULL); ulfius_set_json_body_response(response, 200, j_response); json_decref(j_response); return U_CALLBACK_CONTINUE; } static int callback_openid_jwks_valid (const struct _u_request * request, struct _u_response * response, void * user_data) { jwk_t * jwk; jwks_t * jwks; r_jwk_init(&jwk); r_jwks_init(&jwks); r_jwk_import_from_pem_der(jwk, R_X509_TYPE_PUBKEY, R_FORMAT_PEM, (unsigned char *)public_key, o_strlen(public_key)); r_jwks_append_jwk(jwks, jwk); r_jwk_free(jwk); json_t * j_response = r_jwks_export_to_json_t(jwks); r_jwks_free(jwks); ulfius_set_json_body_response(response, 200, j_response); json_decref(j_response); return U_CALLBACK_CONTINUE; } START_TEST(test_glwd_scheme_oauth2_scope_set) { json_t * j_parameters = json_pack("{sssssisos{s[{ssss}{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME); json_t * j_canuse = json_pack("{ssss}", "module", MODULE_MODULE, "name", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/delegate/" USERNAME "/profile/scheme/", NULL, NULL, NULL, NULL, 200, j_canuse, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_canuse); } END_TEST START_TEST(test_glwd_scheme_oauth2_scope_collision_set) { json_t * j_parameters = json_pack("{sssssisos{s[{ssss}{ssss}{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2); json_t * j_canuse = json_pack("{ssss}", "module", MODULE_MODULE, "name", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/delegate/" USERNAME "/profile/scheme/", NULL, NULL, NULL, NULL, 200, j_canuse, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_canuse); } END_TEST START_TEST(test_glwd_scheme_oauth2_scope_unset) { json_t * j_parameters = json_pack("{sssssisos{s[{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_module_add_provider_error_parameters) { json_t * j_parameters = json_pack("{sssssssisis{sssis[{ssso}]}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "redirect_uri", REDIRECT_URI, "session_expiration", SESSION_EXPIRATION, "provider_list", "name", PROVIDER_NAME, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssisis{sssis[{sissssssssssssssssssssssso}]}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "redirect_uri", REDIRECT_URI, "session_expiration", SESSION_EXPIRATION, "provider_list", "name", 42, "provider_type", PROVIDER_TYPE_OAUTH2, "logo_uri", PROVIDER_LOGO_URI, "logo_fa", PROVIDER_LOGO_FA, "response_type", PROVIDER_RESPONSE_TYPE_CODE, "client_id", PROVIDER_CLIENT_ID, "client_secret", PROVIDER_CLIENT_SECRET, "userid_property", PROVIDER_USERID_PROPERTY, "auth_endpoint", PROVIDER_AUTH_ENDPOINT, "token_endpoint", PROVIDER_TOKEN_ENDPOINT, "userinfo_endpoint", PROVIDER_USERINFO_ENDPOINT, "scope", PROVIDER_SCOPE, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssisis{sssis[{ssssssssssssssssssssssssso}]}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "redirect_uri", REDIRECT_URI, "session_expiration", SESSION_EXPIRATION, "provider_list", "name", PROVIDER_NAME, "provider_type", "error", "logo_uri", PROVIDER_LOGO_URI, "logo_fa", PROVIDER_LOGO_FA, "response_type", PROVIDER_RESPONSE_TYPE_CODE, "client_id", PROVIDER_CLIENT_ID, "client_secret", PROVIDER_CLIENT_SECRET, "userid_property", PROVIDER_USERID_PROPERTY, "auth_endpoint", PROVIDER_AUTH_ENDPOINT, "token_endpoint", PROVIDER_TOKEN_ENDPOINT, "userinfo_endpoint", PROVIDER_USERINFO_ENDPOINT, "scope", PROVIDER_SCOPE, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssisis{sssis[{ssssssssssssssssssssssssso}]}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "redirect_uri", REDIRECT_URI, "session_expiration", SESSION_EXPIRATION, "provider_list", "name", PROVIDER_NAME, "provider_type", PROVIDER_TYPE_OAUTH2, "logo_uri", PROVIDER_LOGO_URI, "logo_fa", PROVIDER_LOGO_FA, "response_type", "error", "client_id", PROVIDER_CLIENT_ID, "client_secret", PROVIDER_CLIENT_SECRET, "userid_property", PROVIDER_USERID_PROPERTY, "auth_endpoint", PROVIDER_AUTH_ENDPOINT, "token_endpoint", PROVIDER_TOKEN_ENDPOINT, "userinfo_endpoint", PROVIDER_USERINFO_ENDPOINT, "scope", PROVIDER_SCOPE, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssisis{sssis[{ssssssssssssssssssssssso}]}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "redirect_uri", REDIRECT_URI, "session_expiration", SESSION_EXPIRATION, "provider_list", "name", PROVIDER_NAME, "provider_type", PROVIDER_TYPE_OAUTH2, "logo_uri", PROVIDER_LOGO_URI, "logo_fa", PROVIDER_LOGO_FA, "response_type", PROVIDER_RESPONSE_TYPE_CODE, "client_id", PROVIDER_CLIENT_ID, "client_secret", PROVIDER_CLIENT_SECRET, "userid_property", PROVIDER_USERID_PROPERTY, "token_endpoint", PROVIDER_TOKEN_ENDPOINT, "userinfo_endpoint", PROVIDER_USERINFO_ENDPOINT, "scope", PROVIDER_SCOPE, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssisis{sssis[{sssssssssssssssssssssssssi}]}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "redirect_uri", REDIRECT_URI, "session_expiration", SESSION_EXPIRATION, "provider_list", "name", PROVIDER_NAME, "provider_type", PROVIDER_TYPE_OAUTH2, "logo_uri", PROVIDER_LOGO_URI, "logo_fa", PROVIDER_LOGO_FA, "response_type", PROVIDER_RESPONSE_TYPE_CODE, "client_id", PROVIDER_CLIENT_ID, "client_secret", PROVIDER_CLIENT_SECRET, "userid_property", PROVIDER_USERID_PROPERTY, "auth_endpoint", PROVIDER_AUTH_ENDPOINT, "token_endpoint", PROVIDER_TOKEN_ENDPOINT, "userinfo_endpoint", PROVIDER_USERINFO_ENDPOINT, "scope", PROVIDER_SCOPE, "enabled", 42); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_module_add_provider_oauth2_code) { json_t * j_parameters = json_pack("{sssssssisis{sssis[{ssssssssssssssssssssssssso}]}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "redirect_uri", REDIRECT_URI, "session_expiration", SESSION_EXPIRATION, "provider_list", "name", PROVIDER_NAME, "provider_type", PROVIDER_TYPE_OAUTH2, "logo_uri", PROVIDER_LOGO_URI, "logo_fa", PROVIDER_LOGO_FA, "response_type", PROVIDER_RESPONSE_TYPE_CODE, "client_id", PROVIDER_CLIENT_ID, "client_secret", PROVIDER_CLIENT_SECRET, "userid_property", PROVIDER_USERID_PROPERTY, "auth_endpoint", PROVIDER_AUTH_ENDPOINT, "token_endpoint", PROVIDER_TOKEN_ENDPOINT, "userinfo_endpoint", PROVIDER_USERINFO_ENDPOINT, "scope", PROVIDER_SCOPE, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_module_add_provider_oauth2_token) { json_t * j_parameters = json_pack("{sssssssisis{sssis[{ssssssssssssssssssssssssso}]}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "redirect_uri", REDIRECT_URI, "session_expiration", SESSION_EXPIRATION, "provider_list", "name", PROVIDER_NAME, "provider_type", PROVIDER_TYPE_OAUTH2, "logo_uri", PROVIDER_LOGO_URI, "logo_fa", PROVIDER_LOGO_FA, "response_type", PROVIDER_RESPONSE_TYPE_TOKEN, "client_id", PROVIDER_CLIENT_ID, "client_secret", PROVIDER_CLIENT_SECRET, "userid_property", PROVIDER_USERID_PROPERTY, "auth_endpoint", PROVIDER_AUTH_ENDPOINT, "token_endpoint", PROVIDER_TOKEN_ENDPOINT, "userinfo_endpoint", PROVIDER_USERINFO_ENDPOINT, "scope", PROVIDER_SCOPE, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_module_add_provider_oidc_code) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/.well-known/openid-configuration", 0, &callback_openid_configuration_valid, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/jwks", 0, &callback_openid_jwks_valid, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); json_t * j_parameters = json_pack("{sssssssisis{sssis[{ssssssssssssssssso}]}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "redirect_uri", REDIRECT_URI, "session_expiration", SESSION_EXPIRATION, "provider_list", "name", PROVIDER_NAME, "provider_type", PROVIDER_TYPE_OIDC, "logo_uri", PROVIDER_LOGO_URI, "logo_fa", PROVIDER_LOGO_FA, "response_type", PROVIDER_RESPONSE_TYPE_CODE, "client_id", PROVIDER_CLIENT_ID, "client_secret", PROVIDER_CLIENT_SECRET, "config_endpoint", PROVIDER_CONFIG_ENDPOINT, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_module_add_provider_oidc_id_token) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/.well-known/openid-configuration", 0, &callback_openid_configuration_valid, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/jwks", 0, &callback_openid_jwks_valid, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); json_t * j_parameters = json_pack("{sssssssisis{sssis[{ssssssssssssssssso}]}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "redirect_uri", REDIRECT_URI, "session_expiration", SESSION_EXPIRATION, "provider_list", "name", PROVIDER_NAME, "provider_type", PROVIDER_TYPE_OIDC, "logo_uri", PROVIDER_LOGO_URI, "logo_fa", PROVIDER_LOGO_FA, "response_type", PROVIDER_RESPONSE_TYPE_ID_TOKEN, "client_id", PROVIDER_CLIENT_ID, "client_secret", PROVIDER_CLIENT_SECRET, "config_endpoint", PROVIDER_CONFIG_ENDPOINT, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_module_add_provider_oauth2_code_collision) { json_t * j_parameters = json_pack("{sssssssisis{sssis[{ssssssssssssssssssssssssso}]}}", "module", MODULE_MODULE, "name", MODULE_NAME_2, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "redirect_uri", REDIRECT_URI, "session_expiration", SESSION_EXPIRATION, "provider_list", "name", PROVIDER_NAME, "provider_type", PROVIDER_TYPE_OAUTH2, "logo_uri", PROVIDER_LOGO_URI, "logo_fa", PROVIDER_LOGO_FA, "response_type", PROVIDER_RESPONSE_TYPE_CODE, "client_id", PROVIDER_CLIENT_ID, "client_secret", PROVIDER_CLIENT_SECRET, "userid_property", PROVIDER_USERID_PROPERTY, "auth_endpoint", PROVIDER_AUTH_ENDPOINT, "token_endpoint", PROVIDER_TOKEN_ENDPOINT, "userinfo_endpoint", PROVIDER_USERINFO_ENDPOINT, "scope", PROVIDER_SCOPE, "enabled", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/mod/scheme/" MODULE_NAME_2, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_module_remove) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_module_remove_collision) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/scheme/" MODULE_NAME_2, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_register_get_oauth2) { json_t * j_parameters = json_pack("{ssssssso}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "last_session", json_null()), * j_result = json_pack("{ssssss}", "provider", PROVIDER_NAME, "logo_uri", PROVIDER_LOGO_URI, "logo_fa", PROVIDER_LOGO_FA); json_t * j_canuse = json_pack("{ssss}", "module", MODULE_MODULE, "name", MODULE_NAME); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "profile/scheme/", NULL, NULL, NULL, NULL, 200, j_canuse, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_result); json_decref(j_canuse); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_register_invalid_parameters) { json_t * j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "error"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", "error", "action", "new"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_register_twice_forbidden) { json_t * j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "delete"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_register_server_callback_error_state) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_copy_request(&req, &user_req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", "error"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "delete"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_register_server_callback_error_redirect_to) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_copy_request(&req, &user_req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", "error", "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); free_string_array(url_array); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "delete"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_register_server_callback_response_error_scope) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_copy_request(&req, &user_req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "?error=invalid_scope&state=", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "delete"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_register_server_callback_response_error_client) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_copy_request(&req, &user_req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "?error=invalid_client&state=", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "delete"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_register_server_callback_oauth2_code_response_error_client) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_error_client, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_copy_request(&req, &user_req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "delete"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_register_server_callback_oauth2_code_response_error_format) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_error_format, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_copy_request(&req, &user_req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "delete"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_register_server_callback_oauth2_code_userinfo_response_error_format) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_ok, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/userinfo", 0, &callback_userinfo_error_format, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_copy_request(&req, &user_req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "delete"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_trigger_provider_list_no_register) { json_t * j_parameters = json_pack("{sssssss{so}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider_list", json_true()), * j_result_no_auth = json_pack("{ssssss}", "provider", PROVIDER_NAME, "logo_uri", PROVIDER_LOGO_URI, "logo_fa", PROVIDER_LOGO_FA); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/scheme/trigger/", NULL, NULL, j_parameters, NULL, 200, j_result_no_auth, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "auth/scheme/trigger/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_result_no_auth); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_register_oauth2_code_response_type_token) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/userinfo", 0, &callback_userinfo_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_copy_request(&req, &user_req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "#access_token=" ACCESS_TOKEN "&token_type=bearer&expires_in=%d&state=%s", EXPIRES_IN, state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "delete"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_register_oauth2_code_ok) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_ok, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/userinfo", 0, &callback_userinfo_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_copy_request(&req, &user_req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_register_oauth2_code_ok_collision) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_ok, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/userinfo", 0, &callback_userinfo_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_copy_request(&req, &user_req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_register_server_callback_oauth2_token_response_error_client) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_copy_request(&req, &user_req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "#error=invalid_client&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "delete"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_register_server_callback_oauth2_token_response_error_format) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_copy_request(&req, &user_req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "#access_token=" ACCESS_TOKEN "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "delete"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_register_server_callback_oauth2_token_userinfo_response_error_format) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/userinfo", 0, &callback_userinfo_error_format, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_copy_request(&req, &user_req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "#access_token=" ACCESS_TOKEN "&token_type=bearer&expires_in=%d&state=%s", EXPIRES_IN, state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "delete"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_register_oauth2_token_response_type_code) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_copy_request(&req, &user_req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "delete"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_register_oauth2_token_ok) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/userinfo", 0, &callback_userinfo_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_copy_request(&req, &user_req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "#access_token=" ACCESS_TOKEN "&token_type=bearer&expires_in=%d&state=%s", EXPIRES_IN, state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_trigger_provider_list_register) { json_t * j_parameters = json_pack("{sssssss{so}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider_list", json_true()), * j_result = json_pack("{ssssss}", "provider", PROVIDER_NAME, "logo_uri", PROVIDER_LOGO_URI, "logo_fa", PROVIDER_LOGO_FA); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "auth/scheme/trigger/", NULL, NULL, j_parameters, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_result); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_code_invalid_userid) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_ok, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/userinfo", 0, &callback_userinfo_invalid_userid, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_token_invalid_userid) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/userinfo", 0, &callback_userinfo_invalid_userid, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "#access_token=" ACCESS_TOKEN "&token_type=bearer&expires_in=%d&state=%s", EXPIRES_IN, state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_code_error_state) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", "error"); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_error_redirect_to) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", "error", "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_code_error_scope) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "?error=invalid_scope&state=", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_token_error_scope) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "#error=invalid_scope&state=", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_code_error_client) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "?error=invalid_client&state=", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_token_error_client) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "#error=invalid_client&state=", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_code_token_error_client) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_error_client, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_code_token_error_format) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_error_format, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_token_token_error_format) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "#access_token=" ACCESS_TOKEN "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_code_error_format) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_ok, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/userinfo", 0, &callback_userinfo_error_format, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_code_ok) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_ok, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/userinfo", 0, &callback_userinfo_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_identify_oauth2_code_invalid_userid) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_ok, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/userinfo", 0, &callback_userinfo_invalid_userid, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssss{ssss}}", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "action", "trigger", "provider", PROVIDER_NAME); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssss{ssssssss}}", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "action", "verify", "provider", PROVIDER_NAME, "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_identify_oauth2_code_ok) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_ok, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/userinfo", 0, &callback_userinfo_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssss{ssss}}", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "action", "trigger", "provider", PROVIDER_NAME); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssss{ssssssss}}", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "action", "verify", "provider", PROVIDER_NAME, "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_code_error_collision) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_ok, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/userinfo", 0, &callback_userinfo_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_token_ok) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/userinfo", 0, &callback_userinfo_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "#access_token=" ACCESS_TOKEN "&token_type=bearer&expires_in=%d&state=%s", EXPIRES_IN, state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_identify_oauth2_token_invalid_userid) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/userinfo", 0, &callback_userinfo_invalid_userid, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssss{ssss}}", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "action", "trigger", "provider", PROVIDER_NAME); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "#access_token=" ACCESS_TOKEN "&token_type=bearer&expires_in=%d&state=%s", EXPIRES_IN, state); j_parameters = json_pack("{sssss{ssssssss}}", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "verify", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_identify_oauth2_token_ok) { struct _u_instance instance; ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "GET", NULL, "/userinfo", 0, &callback_userinfo_ok, NULL), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssss{ssss}}", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "action", "trigger", "provider", PROVIDER_NAME); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "#access_token=" ACCESS_TOKEN "&token_type=bearer&expires_in=%d&state=%s", EXPIRES_IN, state); j_parameters = json_pack("{sssss{ssssssss}}", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "verify", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_token_error_state) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); tmp = msprintf(REDIRECT_URI "#access_token=" ACCESS_TOKEN "&token_type=bearer&expires_in=%d&state=%s", EXPIRES_IN, state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", "error"); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_oidc_code_register_error_id_token_signature) { struct _u_instance instance; struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL, * nonce = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_copy_request(&req, &user_req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } else if (o_strncmp(url_array[i], "nonce=", o_strlen("nonce=")) == 0) { nonce = url_array[i] + o_strlen("nonce="); } } ck_assert_ptr_ne(state, NULL); ck_assert_ptr_ne(nonce, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_oidc_error_signature, (void *)nonce), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "delete"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_oidc_code_register_error_id_token_invalid_payload) { struct _u_instance instance; struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL, * nonce = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_copy_request(&req, &user_req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } else if (o_strncmp(url_array[i], "nonce=", o_strlen("nonce=")) == 0) { nonce = url_array[i] + o_strlen("nonce="); } } ck_assert_ptr_ne(state, NULL); ck_assert_ptr_ne(nonce, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_oidc_invalid_payload, (void *)nonce), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "delete"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_oidc_code_register_ok) { struct _u_instance instance; struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL, * nonce = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_copy_request(&req, &user_req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } else if (o_strncmp(url_array[i], "nonce=", o_strlen("nonce=")) == 0) { nonce = url_array[i] + o_strlen("nonce="); } } ck_assert_ptr_ne(state, NULL); ck_assert_ptr_ne(nonce, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_oidc_ok, (void *)nonce), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_oidc_code_error_invalid_userid) { struct _u_instance instance; struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL, * nonce = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } else if (o_strncmp(url_array[i], "nonce=", o_strlen("nonce=")) == 0) { nonce = url_array[i] + o_strlen("nonce="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_oidc_invalid_userid, (void *)nonce), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_oidc_code_ok) { struct _u_instance instance; struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL, * nonce = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } else if (o_strncmp(url_array[i], "nonce=", o_strlen("nonce=")) == 0) { nonce = url_array[i] + o_strlen("nonce="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_oidc_ok, (void *)nonce), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_identify_oauth2_oidc_code_invalid_userid) { struct _u_instance instance; struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL, * nonce = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssss{ssss}}", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "action", "trigger", "provider", PROVIDER_NAME); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } else if (o_strncmp(url_array[i], "nonce=", o_strlen("nonce=")) == 0) { nonce = url_array[i] + o_strlen("nonce="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_oidc_invalid_userid, (void *)nonce), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssss{ssssssss}}", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "verify", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_identify_oauth2_oidc_code_ok) { struct _u_instance instance; struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL, * nonce = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssss{ssss}}", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "action", "trigger", "provider", PROVIDER_NAME); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } else if (o_strncmp(url_array[i], "nonce=", o_strlen("nonce=")) == 0) { nonce = url_array[i] + o_strlen("nonce="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_oidc_ok, (void *)nonce), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssss{ssssssss}}", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "verify", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_oidc_error_id_token_signature) { struct _u_instance instance; struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL, * nonce = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_oidc_error_signature, (void *)nonce), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_oidc_error_id_token_payload) { struct _u_instance instance; struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL, * nonce = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); ck_assert_int_eq(ulfius_init_instance(&instance, PROVIDER_PORT, NULL, NULL), U_OK); ck_assert_int_eq(ulfius_add_endpoint_by_val(&instance, "POST", NULL, "/token", 0, &callback_token_oidc_invalid_payload, (void *)nonce), U_OK); ck_assert_int_eq(ulfius_start_framework(&instance), U_OK); tmp = msprintf(REDIRECT_URI "?code=" CODE "&state=%s", state); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); ck_assert_int_eq(ulfius_stop_framework(&instance), U_OK); ulfius_clean_instance(&instance); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_oidc_id_token_register_error_id_token_signature) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL, * nonce = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_copy_request(&req, &user_req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } else if (o_strncmp(url_array[i], "nonce=", o_strlen("nonce=")) == 0) { nonce = url_array[i] + o_strlen("nonce="); } } ck_assert_ptr_ne(state, NULL); ck_assert_ptr_ne(nonce, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); jwt_t * jwt; char * grants = NULL, * jwt_str; time_t now; ck_assert_int_eq(r_jwt_init(&jwt), 0); time(&now); grants = msprintf(id_token_pattern, PROVIDER_CLIENT_ID, (long long)now, PROVIDER_CLIENT_ID, (long long)(now + EXPIRES_IN), (long long)now, ISSUER, (const char *)nonce); ck_assert_ptr_ne(grants, NULL); ck_assert_int_eq(r_jwt_set_full_claims_json_str(jwt, grants), 0); ck_assert_int_eq(r_jwt_set_sign_alg(jwt, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, private_key, o_strlen((const char *)private_key), NULL, 0), RHN_OK); ck_assert_ptr_ne((jwt_str = r_jwt_serialize_signed(jwt, NULL, 0)), NULL); jwt_str[o_strlen(jwt_str)-2] = '\0'; o_free(grants); tmp = msprintf(REDIRECT_URI "#id_token=%s&state=%s&token_type=bearer&expires_in=3600", jwt_str, state); o_free(jwt_str); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "delete"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); r_jwt_free(jwt); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_oidc_id_token_register_error_id_token_invalid_payload) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL, * nonce = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_copy_request(&req, &user_req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } else if (o_strncmp(url_array[i], "nonce=", o_strlen("nonce=")) == 0) { nonce = url_array[i] + o_strlen("nonce="); } } ck_assert_ptr_ne(state, NULL); ck_assert_ptr_ne(nonce, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); jwt_t * jwt; char * grants = NULL, * jwt_str; time_t now; ck_assert_int_eq(r_jwt_init(&jwt), 0); time(&now); grants = msprintf(id_token_pattern, PROVIDER_CLIENT_ID, (long long)now, PROVIDER_CLIENT_ID, (long long)(now - EXPIRES_IN), (long long)now, ISSUER, (const char *)nonce); ck_assert_ptr_ne(grants, NULL); ck_assert_int_eq(r_jwt_set_full_claims_json_str(jwt, grants), 0); ck_assert_int_eq(r_jwt_set_sign_alg(jwt, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, private_key, o_strlen((const char *)private_key), NULL, 0), RHN_OK); ck_assert_ptr_ne((jwt_str = r_jwt_serialize_signed(jwt, NULL, 0)), NULL); o_free(grants); tmp = msprintf(REDIRECT_URI "#id_token=%s&state=%s&token_type=bearer&expires_in=3600", jwt_str, state); o_free(jwt_str); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "delete"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); r_jwt_free(jwt); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_oidc_id_token_register_ok) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL, * nonce = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); ulfius_copy_request(&req, &user_req); j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "new"); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } else if (o_strncmp(url_array[i], "nonce=", o_strlen("nonce=")) == 0) { nonce = url_array[i] + o_strlen("nonce="); } } ck_assert_ptr_ne(state, NULL); ck_assert_ptr_ne(nonce, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); jwt_t * jwt; char * grants = NULL, * jwt_str; time_t now; ck_assert_int_eq(r_jwt_init(&jwt), 0); time(&now); grants = msprintf(id_token_pattern, PROVIDER_CLIENT_ID, (long long)now, PROVIDER_CLIENT_ID, (long long)(now + EXPIRES_IN), (long long)now, ISSUER, (const char *)nonce); ck_assert_ptr_ne(grants, NULL); ck_assert_int_eq(r_jwt_set_full_claims_json_str(jwt, grants), 0); ck_assert_int_eq(r_jwt_set_sign_alg(jwt, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, private_key, o_strlen((const char *)private_key), NULL, 0), RHN_OK); ck_assert_ptr_ne((jwt_str = r_jwt_serialize_signed(jwt, NULL, 0)), NULL); o_free(grants); tmp = msprintf(REDIRECT_URI "#id_token=%s&state=%s&token_type=bearer&expires_in=3600", jwt_str, state); o_free(jwt_str); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); r_jwt_free(jwt); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_id_token_error_userid) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL, * nonce = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } else if (o_strncmp(url_array[i], "nonce=", o_strlen("nonce=")) == 0) { nonce = url_array[i] + o_strlen("nonce="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); jwt_t * jwt; char * grants = NULL, * jwt_str; time_t now; ck_assert_int_eq(r_jwt_init(&jwt), 0); time(&now); grants = msprintf(id_token_invalid_sub_pattern, PROVIDER_CLIENT_ID, (long long)now, PROVIDER_CLIENT_ID, (long long)(now + EXPIRES_IN), (long long)now, ISSUER, (const char *)nonce); ck_assert_ptr_ne(grants, NULL); ck_assert_int_eq(r_jwt_set_full_claims_json_str(jwt, grants), 0); ck_assert_int_eq(r_jwt_set_sign_alg(jwt, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, private_key, o_strlen((const char *)private_key), NULL, 0), RHN_OK); ck_assert_ptr_ne((jwt_str = r_jwt_serialize_signed(jwt, NULL, 0)), NULL); o_free(grants); tmp = msprintf(REDIRECT_URI "#id_token=%s&state=%s&token_type=bearer&expires_in=3600", jwt_str, state); o_free(jwt_str); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); r_jwt_free(jwt); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_auth_oauth2_id_token_ok) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL, * nonce = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } else if (o_strncmp(url_array[i], "nonce=", o_strlen("nonce=")) == 0) { nonce = url_array[i] + o_strlen("nonce="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); jwt_t * jwt; char * grants = NULL, * jwt_str; time_t now; ck_assert_int_eq(r_jwt_init(&jwt), 0); time(&now); grants = msprintf(id_token_pattern, PROVIDER_CLIENT_ID, (long long)now, PROVIDER_CLIENT_ID, (long long)(now + EXPIRES_IN), (long long)now, ISSUER, (const char *)nonce); ck_assert_ptr_ne(grants, NULL); ck_assert_int_eq(r_jwt_set_full_claims_json_str(jwt, grants), 0); ck_assert_int_eq(r_jwt_set_sign_alg(jwt, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, private_key, o_strlen((const char *)private_key), NULL, 0), RHN_OK); ck_assert_ptr_ne((jwt_str = r_jwt_serialize_signed(jwt, NULL, 0)), NULL); o_free(grants); tmp = msprintf(REDIRECT_URI "#id_token=%s&state=%s&token_type=bearer&expires_in=3600", jwt_str, state); o_free(jwt_str); j_parameters = json_pack("{sssssss{ssssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "callback", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); r_jwt_free(jwt); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_identify_oauth2_id_token_invalid_userid) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL, * nonce = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssss{ssss}}", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "action", "trigger", "provider", PROVIDER_NAME); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } else if (o_strncmp(url_array[i], "nonce=", o_strlen("nonce=")) == 0) { nonce = url_array[i] + o_strlen("nonce="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); jwt_t * jwt; char * grants = NULL, * jwt_str; time_t now; ck_assert_int_eq(r_jwt_init(&jwt), 0); time(&now); grants = msprintf(id_token_invalid_sub_pattern, PROVIDER_CLIENT_ID, (long long)now, PROVIDER_CLIENT_ID, (long long)(now + EXPIRES_IN), (long long)now, ISSUER, (const char *)nonce); ck_assert_ptr_ne(grants, NULL); ck_assert_int_eq(r_jwt_set_full_claims_json_str(jwt, grants), 0); ck_assert_int_eq(r_jwt_set_sign_alg(jwt, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, private_key, o_strlen((const char *)private_key), NULL, 0), RHN_OK); ck_assert_ptr_ne((jwt_str = r_jwt_serialize_signed(jwt, NULL, 0)), NULL); o_free(grants); tmp = msprintf(REDIRECT_URI "#id_token=%s&state=%s&token_type=bearer&expires_in=3600", jwt_str, state); o_free(jwt_str); j_parameters = json_pack("{sssss{ssssssss}}", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "verify", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); r_jwt_free(jwt); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_identify_oauth2_id_token_ok) { struct _u_request req; struct _u_response resp; json_t * j_parameters, * j_response; const char * redirect_to, * state = NULL, * nonce = NULL; char ** url_array = NULL, * tmp = NULL; size_t i; ulfius_init_request(&req); ulfius_init_response(&resp); j_parameters = json_pack("{sssss{ssss}}", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "action", "trigger", "provider", PROVIDER_NAME); req.http_verb = o_strdup("POST"); req.http_url = o_strdup(SERVER_URI "auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&req, j_parameters), U_OK); ck_assert_int_eq(ulfius_send_http_request(&req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_response = ulfius_get_json_body_response(&resp, NULL); ck_assert_ptr_ne(j_response, NULL); redirect_to = json_string_value(json_object_get(j_response, "redirect_to")); ck_assert_ptr_ne(redirect_to, NULL); ck_assert_int_eq(split_string(redirect_to+o_strlen(REDIRECT_URI)+1, "&", &url_array), 6); for (i=0; url_array[i]!=NULL; i++) { if (o_strncmp(url_array[i], "state=", o_strlen("state=")) == 0) { state = url_array[i] + o_strlen("state="); } else if (o_strncmp(url_array[i], "nonce=", o_strlen("nonce=")) == 0) { nonce = url_array[i] + o_strlen("nonce="); } } ck_assert_ptr_ne(state, NULL); json_decref(j_parameters); json_decref(j_response); ulfius_clean_request(&req); ulfius_clean_response(&resp); jwt_t * jwt; char * grants = NULL, * jwt_str; time_t now; ck_assert_int_eq(r_jwt_init(&jwt), 0); time(&now); grants = msprintf(id_token_pattern, PROVIDER_CLIENT_ID, (long long)now, PROVIDER_CLIENT_ID, (long long)(now + EXPIRES_IN), (long long)now, ISSUER, (const char *)nonce); ck_assert_ptr_ne(grants, NULL); ck_assert_int_eq(r_jwt_set_full_claims_json_str(jwt, grants), 0); ck_assert_int_eq(r_jwt_set_sign_alg(jwt, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt, R_FORMAT_PEM, private_key, o_strlen((const char *)private_key), NULL, 0), RHN_OK); ck_assert_ptr_ne((jwt_str = r_jwt_serialize_signed(jwt, NULL, 0)), NULL); o_free(grants); tmp = msprintf(REDIRECT_URI "#id_token=%s&state=%s&token_type=bearer&expires_in=3600", jwt_str, state); o_free(jwt_str); j_parameters = json_pack("{sssss{ssssssss}}", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "verify", "redirect_to", tmp, "state", state); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); o_free(tmp); r_jwt_free(jwt); free_string_array(url_array); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_register_delete) { json_t * j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "provider", PROVIDER_NAME, "action", "delete"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_oauth2_irl_register_delete_collision) { json_t * j_parameters = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value", "provider", PROVIDER_NAME, "action", "delete"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd scheme oauth2"); tc_core = tcase_create("test_glwd_scheme_oauth2_irl"); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_module_add_provider_error_parameters); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_module_add_provider_oauth2_code); tcase_add_test(tc_core, test_glwd_scheme_oauth2_scope_set); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_get_oauth2); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_invalid_parameters); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_twice_forbidden); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_server_callback_error_state); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_server_callback_error_redirect_to); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_server_callback_response_error_scope); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_server_callback_response_error_client); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_server_callback_oauth2_code_response_error_client); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_server_callback_oauth2_code_response_error_format); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_server_callback_oauth2_code_userinfo_response_error_format); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_trigger_provider_list_no_register); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_oauth2_code_response_type_token); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_oauth2_code_ok); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_trigger_provider_list_register); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_code_invalid_userid); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_code_error_state); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_error_redirect_to); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_code_error_scope); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_code_error_client); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_code_token_error_client); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_code_token_error_format); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_code_error_format); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_code_ok); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_identify_oauth2_code_invalid_userid); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_identify_oauth2_code_ok); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_delete); tcase_add_test(tc_core, test_glwd_scheme_oauth2_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_module_remove); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_module_add_provider_oauth2_token); tcase_add_test(tc_core, test_glwd_scheme_oauth2_scope_set); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_server_callback_response_error_client); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_server_callback_oauth2_token_response_error_client); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_server_callback_oauth2_token_response_error_format); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_server_callback_oauth2_token_userinfo_response_error_format); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_oauth2_token_response_type_code); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_oauth2_token_ok); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_error_redirect_to); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_token_invalid_userid); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_token_error_state); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_token_error_scope); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_token_error_client); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_token_token_error_format); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_token_ok); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_identify_oauth2_token_invalid_userid); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_identify_oauth2_token_ok); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_delete); tcase_add_test(tc_core, test_glwd_scheme_oauth2_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_module_remove); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_module_add_provider_oidc_code); tcase_add_test(tc_core, test_glwd_scheme_oauth2_scope_set); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_server_callback_error_state); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_server_callback_error_redirect_to); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_server_callback_response_error_scope); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_server_callback_response_error_client); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_server_callback_oauth2_code_response_error_client); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_server_callback_oauth2_code_response_error_format); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_oidc_code_register_error_id_token_signature); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_oidc_code_register_error_id_token_invalid_payload); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_oidc_code_register_ok); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_code_error_scope); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_code_error_client); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_code_token_error_client); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_code_token_error_format); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_oidc_error_id_token_signature); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_oidc_error_id_token_payload); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_oidc_code_error_invalid_userid); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_oidc_code_ok); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_identify_oauth2_oidc_code_invalid_userid); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_identify_oauth2_oidc_code_ok); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_delete); tcase_add_test(tc_core, test_glwd_scheme_oauth2_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_module_remove); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_module_add_provider_oidc_id_token); tcase_add_test(tc_core, test_glwd_scheme_oauth2_scope_set); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_server_callback_response_error_client); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_server_callback_oauth2_token_response_error_client); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_server_callback_oauth2_token_response_error_format); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_oidc_id_token_register_error_id_token_signature); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_oidc_id_token_register_error_id_token_invalid_payload); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_oidc_id_token_register_ok); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_error_redirect_to); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_token_error_state); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_token_error_scope); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_token_error_client); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_token_token_error_format); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_id_token_error_userid); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_id_token_ok); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_identify_oauth2_id_token_invalid_userid); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_identify_oauth2_id_token_ok); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_delete); tcase_add_test(tc_core, test_glwd_scheme_oauth2_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_module_remove); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_module_add_provider_oauth2_code); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_module_add_provider_oauth2_code_collision); tcase_add_test(tc_core, test_glwd_scheme_oauth2_scope_collision_set); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_oauth2_code_ok); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_oauth2_code_ok_collision); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_code_ok); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_auth_oauth2_code_error_collision); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_delete); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_register_delete_collision); tcase_add_test(tc_core, test_glwd_scheme_oauth2_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_module_remove); tcase_add_test(tc_core, test_glwd_scheme_oauth2_irl_module_remove_collision); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i; oath_init(); y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&user_req); ulfius_init_request(&admin_req); ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api/" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "scope1 scope2" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define MODULE_MODULE "otp" #define MODULE_NAME "test_otp" #define MODULE_NAME_2 "test_otp_2" #define MODULE_DISPLAY_NAME "OTP scheme for test" #define MODULE_EXPIRATION 600 #define MODULE_MAX_USE 0 #define OTP_ORIGIN "localhost" #define OTP_SECRET_LENGTH 16 #define OTP_CODE_LEGTH 6 #define OTP_HOTP_WINDOW 0 #define OTP_TOTP_WINDOW 0 #define OTP_TOTP_OFFSET 0 #define OTP_USER_SECRET "ZNCNGCWT3BCZW7FWUCRYGAYQWA======" #define OTP_USER_SECRET_2 "ZNCNGWWTWBCZW7FWUCRYGAYQWA======" #define OTP_USER_TYPE_TOTP "TOTP" #define OTP_USER_STEP_SIZE 30 #define OTP_USER_TYPE_HOTP "HOTP" #define OTP_USER_MOVING_FACTOR 0 #define OTP_USER_TYPE_NONE "NONE" #define SCOPE_NAME "scope2" #define SCOPE_DISPLAY_NAME "Glewlwyd mock scope without password" #define SCOPE_DESCRIPTION "Glewlwyd scope 2 scope description" #define SCOPE_PASSWORD_MAX_AGE 0 #define SCOPE_SCHEME_1_TYPE "mock" #define SCOPE_SCHEME_1_NAME "mock_scheme_95" struct _u_request user_req; struct _u_request admin_req; START_TEST(test_glwd_scheme_otp_scope_set) { json_t * j_parameters = json_pack("{sssssisos{s[{ssss}{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME); json_t * j_canuse = json_pack("{ssss}", "module", MODULE_MODULE, "name", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/delegate/" USERNAME "/profile/scheme/", NULL, NULL, NULL, NULL, 200, j_canuse, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_canuse); } END_TEST START_TEST(test_glwd_scheme_otp_scope_collision_set) { json_t * j_parameters = json_pack("{sssssisos{s[{ssss}{ssss}{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2); json_t * j_canuse = json_pack("{ssss}", "module", MODULE_MODULE, "name", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/delegate/" USERNAME "/profile/scheme/", NULL, NULL, NULL, NULL, 200, j_canuse, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_canuse); } END_TEST START_TEST(test_glwd_scheme_otp_scope_unset) { json_t * j_parameters = json_pack("{sssssisos{s[{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_otp_irl_module_add) { char * url = msprintf("%s/mod/scheme/", SERVER_URI); json_t * j_parameters = json_pack("{sssssssisis{sssisisosisosisi}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "issuer", OTP_ORIGIN, "secret-minimum-size", OTP_SECRET_LENGTH, "otp-length", OTP_CODE_LEGTH, "hotp-allow", json_true(), "hotp-window", OTP_HOTP_WINDOW, "totp-allow", json_true(), "totp-window", OTP_TOTP_WINDOW, "totp-start-offset", OTP_TOTP_OFFSET); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/mod/scheme/%s", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_otp_irl_register_error) { json_t * j_params = json_pack("{sssssss{sssssi}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "secret", "error", "type", OTP_USER_TYPE_TOTP, "time_step_size", OTP_USER_STEP_SIZE); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{sssssi}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "secret", OTP_USER_SECRET, "type", "error", "moving_factor", OTP_USER_MOVING_FACTOR); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{ssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "secret", OTP_USER_SECRET, "type", OTP_USER_TYPE_TOTP, "moving_factor", "error"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_params); } END_TEST START_TEST(test_glwd_scheme_otp_irl_register) { json_t * j_params = json_pack("{sssssss{so}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "generate-secret", json_true()); json_t * j_canuse = json_pack("{ssss}", "module", MODULE_MODULE, "name", MODULE_NAME); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "profile/scheme/", NULL, NULL, NULL, NULL, 200, j_canuse, NULL, NULL), 1); j_params = json_pack("{sssssss{sssssi}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "secret", OTP_USER_SECRET, "type", OTP_USER_TYPE_TOTP, "time_step_size", OTP_USER_STEP_SIZE); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{sssssi}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "secret", OTP_USER_SECRET, "type", OTP_USER_TYPE_HOTP, "moving_factor", OTP_USER_MOVING_FACTOR); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "type", "NONE"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); json_decref(j_canuse); } END_TEST START_TEST(test_glwd_scheme_otp_irl_get_register) { json_t * j_params = json_pack("{sssssss{sssssi}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "secret", OTP_USER_SECRET, "type", OTP_USER_TYPE_HOTP, "moving_factor", OTP_USER_MOVING_FACTOR), * j_result = json_pack("{ss}", "type", OTP_USER_TYPE_NONE); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, j_result, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); json_decref(j_result); j_params = json_pack("{ssssss}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME); j_result = json_pack("{sssssi}", "secret", OTP_USER_SECRET, "type", OTP_USER_TYPE_HOTP, "moving_factor", OTP_USER_MOVING_FACTOR); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_params); json_decref(j_result); j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "type", "NONE"); j_result = json_pack("{ss}", "type", OTP_USER_TYPE_NONE); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_params); json_decref(j_result); } END_TEST START_TEST(test_glwd_scheme_otp_irl_authenticate_error) { json_t * j_params = json_pack("{sssssss{sssssi}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "secret", OTP_USER_SECRET, "type", OTP_USER_TYPE_HOTP, "moving_factor", OTP_USER_MOVING_FACTOR); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "value", "error"); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{si}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "value", 666777); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "value", "66677a"); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "type", "NONE"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); } END_TEST START_TEST(test_glwd_scheme_otp_irl_authenticate_success) { char code[OTP_CODE_LEGTH+1], * secret_dec = NULL; size_t secret_dec_len = 0; json_t * j_params = json_pack("{sssssss{sssssi}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "secret", OTP_USER_SECRET, "type", OTP_USER_TYPE_HOTP, "moving_factor", OTP_USER_MOVING_FACTOR); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); ck_assert_int_eq(oath_base32_decode(OTP_USER_SECRET, strlen(OTP_USER_SECRET), &secret_dec, &secret_dec_len), OATH_OK); ck_assert_int_eq(oath_hotp_generate(secret_dec, secret_dec_len, OTP_USER_MOVING_FACTOR, OTP_CODE_LEGTH, 0, OTP_USER_MOVING_FACTOR, code), OATH_OK); j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "value", code); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "type", "NONE"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{sssssi}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "secret", OTP_USER_SECRET, "type", OTP_USER_TYPE_TOTP, "time_step_size", OTP_USER_STEP_SIZE); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); ck_assert_int_eq(oath_totp_generate(secret_dec, secret_dec_len, time(NULL), OTP_USER_STEP_SIZE, 0, OTP_CODE_LEGTH, code), OATH_OK); j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "value", code); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "type", "NONE"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); free(secret_dec); } END_TEST START_TEST(test_glwd_scheme_otp_irl_authenticate_error_too_soon) { char code[OTP_CODE_LEGTH+1], * secret_dec = NULL; size_t secret_dec_len = 0; json_t * j_params = json_pack("{sssssss{sssssi}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "secret", OTP_USER_SECRET, "type", OTP_USER_TYPE_HOTP, "moving_factor", OTP_USER_MOVING_FACTOR); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); ck_assert_int_eq(oath_base32_decode(OTP_USER_SECRET, strlen(OTP_USER_SECRET), &secret_dec, &secret_dec_len), OATH_OK); ck_assert_int_eq(oath_hotp_generate(secret_dec, secret_dec_len, OTP_USER_MOVING_FACTOR, OTP_CODE_LEGTH, 0, OTP_USER_MOVING_FACTOR, code), OATH_OK); j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "value", code); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "type", "NONE"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{sssssi}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "secret", OTP_USER_SECRET, "type", OTP_USER_TYPE_TOTP, "time_step_size", OTP_USER_STEP_SIZE); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); ck_assert_int_eq(oath_totp_generate(secret_dec, secret_dec_len, time(NULL), OTP_USER_STEP_SIZE, 0, OTP_CODE_LEGTH, code), OATH_OK); j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "value", code); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "type", "NONE"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); free(secret_dec); } END_TEST START_TEST(test_glwd_scheme_otp_irl_module_remove) { char * url = msprintf("%s/mod/scheme/%s", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST START_TEST(test_glwd_scheme_otp_irl_collision_begin) { json_t * j_parameters = json_pack("{sssssssisis{sssisisosisosisi}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "issuer", OTP_ORIGIN, "secret-minimum-size", OTP_SECRET_LENGTH, "otp-length", OTP_CODE_LEGTH, "hotp-allow", json_true(), "hotp-window", OTP_HOTP_WINDOW, "totp-allow", json_true(), "totp-window", OTP_TOTP_WINDOW, "totp-start-offset", OTP_TOTP_OFFSET); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssssisis{sssisisosisosisi}}", "module", MODULE_MODULE, "name", MODULE_NAME_2, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "issuer", OTP_ORIGIN, "secret-minimum-size", OTP_SECRET_LENGTH, "otp-length", OTP_CODE_LEGTH, "hotp-allow", json_true(), "hotp-window", OTP_HOTP_WINDOW, "totp-allow", json_true(), "totp-window", OTP_TOTP_WINDOW, "totp-start-offset", OTP_TOTP_OFFSET); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_otp_irl_collision) { json_t * j_parameters = json_pack("{sssssss{sssssi}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "secret", OTP_USER_SECRET, "type", OTP_USER_TYPE_TOTP, "time_step_size", OTP_USER_STEP_SIZE), * j_result; ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssss{sssssi}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value", "secret", OTP_USER_SECRET_2, "type", OTP_USER_TYPE_HOTP, "moving_factor", OTP_USER_MOVING_FACTOR); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_result = json_pack("{sssssi}", "secret", OTP_USER_SECRET_2, "type", OTP_USER_TYPE_HOTP, "moving_factor", OTP_USER_MOVING_FACTOR); j_parameters = json_pack("{ssssss}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_result); j_result = json_pack("{sssssi}", "secret", OTP_USER_SECRET, "type", OTP_USER_TYPE_TOTP, "time_step_size", OTP_USER_STEP_SIZE); j_parameters = json_pack("{ssssss}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_result); j_parameters = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "type", "NONE"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); j_parameters = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value", "type", "NONE"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_otp_irl_collision_close) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/scheme/" MODULE_NAME_2, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd scheme otp"); tc_core = tcase_create("test_glwd_scheme_otp_irl"); tcase_add_test(tc_core, test_glwd_scheme_otp_irl_module_add); tcase_add_test(tc_core, test_glwd_scheme_otp_scope_set); tcase_add_test(tc_core, test_glwd_scheme_otp_irl_register_error); tcase_add_test(tc_core, test_glwd_scheme_otp_irl_register); tcase_add_test(tc_core, test_glwd_scheme_otp_irl_get_register); tcase_add_test(tc_core, test_glwd_scheme_otp_irl_authenticate_error); tcase_add_test(tc_core, test_glwd_scheme_otp_irl_authenticate_success); tcase_add_test(tc_core, test_glwd_scheme_otp_irl_authenticate_error_too_soon); tcase_add_test(tc_core, test_glwd_scheme_otp_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_otp_irl_module_remove); tcase_add_test(tc_core, test_glwd_scheme_otp_irl_collision_begin); tcase_add_test(tc_core, test_glwd_scheme_otp_scope_collision_set); tcase_add_test(tc_core, test_glwd_scheme_otp_irl_collision); tcase_add_test(tc_core, test_glwd_scheme_otp_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_otp_irl_collision_close); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i; oath_init(); y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&user_req); ulfius_init_request(&admin_req); ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api/" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "scope1 scope2" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define MODULE_MODULE "retype-password" #define MODULE_NAME "test_rp" #define MODULE_DISPLAY_NAME "Retype password scheme for test" #define MODULE_EXPIRATION 600 #define MODULE_MAX_USE 0 #define SCOPE_NAME "scope2" #define SCOPE_DISPLAY_NAME "Glewlwyd mock scope without password" #define SCOPE_DESCRIPTION "Glewlwyd scope 2 scope description" #define SCOPE_PASSWORD_MAX_AGE 0 #define SCOPE_SCHEME_1_TYPE "mock" #define SCOPE_SCHEME_1_NAME "mock_scheme_95" struct _u_request user_req; struct _u_request admin_req; START_TEST(test_glwd_scheme_retype_password_scope_set) { json_t * j_parameters = json_pack("{sssssisos{s[{ssss}{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME); json_t * j_canuse = json_pack("{ssss}", "module", MODULE_MODULE, "name", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/delegate/" USERNAME "/profile/scheme/", NULL, NULL, NULL, NULL, 200, j_canuse, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_canuse); } END_TEST START_TEST(test_glwd_scheme_retype_password_scope_unset) { json_t * j_parameters = json_pack("{sssssisos{s[{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_retype_password_irl_module_add) { char * url = msprintf("%s/mod/scheme/", SERVER_URI); json_t * j_parameters = json_pack("{sssssssisis{}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters"); ck_assert_int_eq(run_simple_test(&admin_req, "POST", url, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); o_free(url); url = msprintf("%s/mod/scheme/%s", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "GET", url, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); o_free(url); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_retype_password_irl_authenticate_error) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "password", "error"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); } END_TEST START_TEST(test_glwd_scheme_retype_password_irl_authenticate_success) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "password", PASSWORD); json_t * j_canuse = json_pack("{ssss}", "module", MODULE_MODULE, "name", MODULE_NAME); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "profile/scheme/", NULL, NULL, NULL, NULL, 200, j_canuse, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_canuse); json_decref(j_params); } END_TEST START_TEST(test_glwd_scheme_retype_password_irl_module_remove) { char * url = msprintf("%s/mod/scheme/%s", SERVER_URI, MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", url, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); o_free(url); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd scheme retype password"); tc_core = tcase_create("test_glwd_scheme_retype_password_irl"); tcase_add_test(tc_core, test_glwd_scheme_retype_password_irl_module_add); tcase_add_test(tc_core, test_glwd_scheme_retype_password_scope_set); tcase_add_test(tc_core, test_glwd_scheme_retype_password_irl_authenticate_error); tcase_add_test(tc_core, test_glwd_scheme_retype_password_irl_authenticate_success); tcase_add_test(tc_core, test_glwd_scheme_retype_password_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_retype_password_irl_module_remove); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i; oath_init(); y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&user_req); ulfius_init_request(&admin_req); ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "unit-tests.h" #define SERVER_URI "http://localhost:4593/api/" #define USERNAME "user1" #define PASSWORD "password" #define SCOPE_LIST "scope1 scope2" #define ADMIN_USERNAME "admin" #define ADMIN_PASSWORD "password" #define MODULE_MODULE "webauthn" #define MODULE_NAME "test_webauthn" #define MODULE_NAME_2 "test_webauthn_2" #define MODULE_DISPLAY_NAME "Webauthn scheme for test" #define MODULE_EXPIRATION 600 #define MODULE_MAX_USE 0 unsigned char credential_id[] = {0x8C, 0x0A, 0x26, 0xFF, 0x22, 0x91, 0xC1, 0xE9, 0xB9, 0x4E, 0x2E, 0x17, 0x1A, 0x98, 0x6A, 0x73, 0x71, 0x9D, 0x43, 0x48, 0xD5, 0xA7, 0x6A, 0x15, 0x7E, 0x38, 0x94, 0x52, 0x77, 0x97, 0x0F, 0xEF, 0x79, 0x50, 0x68, 0x71, 0xDA, 0xEE, 0xEE, 0xB9, 0x94, 0xC3, 0xC2, 0x15, 0x67, 0x65, 0x26, 0x22, 0xE3, 0xF3, 0xAB, 0x3B, 0x78, 0x2E, 0xD5, 0x6F, 0x81, 0x26, 0xE2, 0xA6, 0x01, 0x7D, 0x74, 0x50}; unsigned char credential_id_2[] = {0x79, 0x50, 0x68, 0x71, 0xDA, 0xEE, 0xEE, 0xB9, 0x94, 0xC3, 0xC2, 0x15, 0x67, 0x65, 0x26, 0x22, 0xE3, 0xF3, 0xAB, 0x3B, 0x78, 0x2E, 0xD5, 0x6F, 0x81, 0x26, 0xE2, 0xA6, 0x01, 0x7D, 0x74, 0x50, 0x8C, 0x0A, 0x26, 0xFF, 0x22, 0x91, 0xC1, 0xE9, 0xB9, 0x4E, 0x2E, 0x17, 0x1A, 0x98, 0x6A, 0x73, 0x71, 0x9D, 0x43, 0x48, 0xD5, 0xA7, 0x6A, 0x15, 0x7E, 0x38, 0x94, 0x52, 0x77, 0x97, 0x0F, 0xEF}; #define WEBAUTHN_CREDENTIAL_ID_LEN 64 #define WEBAUTHN_SESSION_MANDATORY json_false() #define WEBAUTHN_SESSION_MANDATORY_2 json_true() #define WEBAUTHN_SEED "8t2w0niodyntwma0wdu8kfdvbcugr4s5s" #define WEBAUTHN_CHALLENGE_LEN 64 #define WEBAUTHN_CREDENTIAL_EXPIRATION 120 #define WEBAUTHN_CREDENTIAL_ASSERTION 120 #define WEBAUTHN_RP_ORIGIN "https://www.glewlwyd.tld" #define WEBAUTHN_RP_ID "www.glewlwyd.tld" #define WEBAUTHN_PUBKEY_CRED_ECDSA_256 -7 #define WEBAUTHN_PUBKEY_CRED_ECDSA_256_CBOR 6 #define WEBAUTHN_PUBKEY_CRED_ECDSA_384 -35 #define WEBAUTHN_PUBKEY_CRED_ECDSA_384_CBOR -34 #define WEBAUTHN_PUBKEY_CRED_ECDSA_512 -36 #define WEBAUTHN_CTS_PROFILE_MATCH 1 #define WEBAUTHN_BASIC_INTEGRITY 1 #define WEBAUTHN_GOOGLE_ROOT_CA_R2 "" #define WEBAUTHN_APPLE_ROOT_CA "" #define WEBAUTHN_CREDENTIAL_NEW_NAME "new_name" #define FLAG_USER_PRESENT 0x01 #define FLAG_USER_VERIFY 0x04 #define FLAG_AT 0x40 #define AAGUID {0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57} #define AAGUID_APPLE {0xf2, 0x4a, 0x8e, 0x70, 0xd0, 0xd3, 0xf8, 0x2c, 0x29, 0x37, 0x32, 0x52, 0x3c, 0xc4, 0xde, 0x5a} #define AAGUID_LEN 16 #define AUTH_DATA_SIZE 1024 #define AUTHENTICATOR_DATA_SIZE 128 #define NONCE_SIZE 256 #define FIDO_CERT_FAKE "-----BEGIN CERTIFICATE-----\ MIIBejCCASGgAwIBAgIUUmwvBcKwJSWZMLC9xtUYQhh/YicwCgYIKoZIzj0EAwIw\ EzERMA8GA1UEAwwIZ2xld2x3eWQwHhcNMTkwNjEyMTY0MjExWhcNMjkwNjA5MTY0\ MjExWjATMREwDwYDVQQDDAhnbGV3bHd5ZDBZMBMGByqGSM49AgEGCCqGSM49AwEH\ A0IABKP9Eu2Rzt15pKqriLiniryG9zsabCq+aNneB+mmIDwRkjaqpKeGwztLEHBG\ TrHh9poToHkaxUuFE/wVD+9GscGjUzBRMB0GA1UdDgQWBBQQv5dX9gxGFfEDD2Zu\ jZQT3FTitDAfBgNVHSMEGDAWgBQQv5dX9gxGFfEDD2ZujZQT3FTitDAPBgNVHRMB\ Af8EBTADAQH/MAoGCCqGSM49BAMCA0cAMEQCIBqkd3kqcKZ/gEsnAVi5sQR3gB04\ U8JNjzPwv//HmV/FAiBT45X52j1G6QGPg82twWR7CZiHbJPe26drWkkoDeT/QQ==\ -----END CERTIFICATE-----" #define FIDO_KEY_FAKE "-----BEGIN EC PRIVATE KEY-----\ MHcCAQEEIPXYkuP2+oERZkj7H5AaKrXnCoUaOFnmLx+HFTYqmJUmoAoGCCqGSM49\ AwEHoUQDQgAEo/0S7ZHO3XmkqquIuKeKvIb3OxpsKr5o2d4H6aYgPBGSNqqkp4bD\ O0sQcEZOseH2mhOgeRrFS4UT/BUP70axwQ==\ -----END EC PRIVATE KEY-----" #define ANDROID_SAFETYNET_CERT_FAKE "-----BEGIN CERTIFICATE-----\ MIIDCDCCAfCgAwIBAgIUanYfCOPdqYtyp33zBq8I7zWTMJkwDQYJKoZIhvcNAQEL\ BQAwHTEbMBkGA1UEAxMSYXR0ZXN0LmFuZHJvaWQuY29tMB4XDTIwMDQxNDIyMjA1\ NVoXDTMwMDIyMTIyMjA1OFowHTEbMBkGA1UEAxMSYXR0ZXN0LmFuZHJvaWQuY29t\ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA20Is5gSlCp+9ShWaqBrb\ ovhLvhCUpsLlQ/q0FTSZ92WaU8J5Y3rlYLemU7vEbmTRlUTbkjFWtViRyotOdYIm\ RS9qew490tWByC8WFYRlwxzYeShO0xPzhoTR0gW1fFrdaHNEYlUn02ZP8SuQ1tsC\ BhKeBClvZpLn74+dnCpbdIUeomQGQWl5gOrI4xT7v1H/HCGBd3WwGp4tjBWNziQD\ FONT5xbGPWI2sTF9oG43fBr/s2CZFLtW2yFA2764/+rHCCIa/bZ1wVIPJIyaj7BQ\ dFffZkcbzpb57yCwnHQII5+Cl5MDBBPFV8amuuFCPkAiYCfj1NMipxcjBjtUFefw\ wwIDAQABo0AwPjAMBgNVHRMBAf8EAjAAMA8GA1UdDwEB/wQFAwMHoAAwHQYDVR0O\ BBYEFGDLOTy6/r6JdNaqCk4eeMByMsd5MA0GCSqGSIb3DQEBCwUAA4IBAQAhssH2\ FmYr8O73jbRYfvqB9Kva75HBxvLzrAIkCzBVVcyaLEhrYCS/LSZtTcKMK4+qtSm9\ hIG+5OTzeKmPO9hHrvGIn+oKFkfvf5ECDWFObVQXEf66WDIrKwu4FcBayg+Ara8I\ opR6p8bu3DvjWeeo3d2UXfvfNWMA6BEVQ33Aap4gK05LPek4wYroHvuWvYdEkwPn\ uSjpCoNkSlkEdugsykIA331yx/xuqwzxj+hrmflV/cQOLiGyQICsyHTrHfXrKlxB\ RM1Bb803T8ucr1SEFV1HHhmCPUEluSzFwfwlklj4g36KTPh1Q6q/Kkpa+W5VsiVQ\ HJsbGgr6C4Mjuymb\ -----END CERTIFICATE-----" #define ANDROID_SAFETYNET_KEY_FAKE "-----BEGIN RSA PRIVATE KEY-----\ MIIEpAIBAAKCAQEA20Is5gSlCp+9ShWaqBrbovhLvhCUpsLlQ/q0FTSZ92WaU8J5\ Y3rlYLemU7vEbmTRlUTbkjFWtViRyotOdYImRS9qew490tWByC8WFYRlwxzYeShO\ 0xPzhoTR0gW1fFrdaHNEYlUn02ZP8SuQ1tsCBhKeBClvZpLn74+dnCpbdIUeomQG\ QWl5gOrI4xT7v1H/HCGBd3WwGp4tjBWNziQDFONT5xbGPWI2sTF9oG43fBr/s2CZ\ FLtW2yFA2764/+rHCCIa/bZ1wVIPJIyaj7BQdFffZkcbzpb57yCwnHQII5+Cl5MD\ BBPFV8amuuFCPkAiYCfj1NMipxcjBjtUFefwwwIDAQABAoIBAGXUPQ/y1kex2nKe\ x/4MwzbUBDFYeAFfAKVquNokXOFmQZ9m8YN/HyqlAE1hJiBzGFcv7J3f5jpA0Sz1\ N9IhSO7Wz6go/BN/709udt41aCGOswbJ7pnfaTlvVBcraZdAiBWrevYEQIPQv43t\ Qs5WVoFFgjfCmqdT0P4UgAl4LpNVJ2/ltAXuEY2I96Znilqghz4BggrSExmHuokI\ mbNcWcAHJguFSEm/0dqWMzUMkKqB2b9Dw9VTBYknHCgWb+zlJLlpXSaCtQzWn6/6\ c+fECs8LFmrZqSV9cE2CVy2cTjEjEBAJ0j8H547/Si4t7BMP43rOTEVxVu+z84Xx\ LGYEJjkCgYEA36TMRpSA5Hgf0PcnaCIfS9Eg4Ws7r/Ea9yZIPd4Z2hGn7m9u0Gzf\ 5vTtp6o3bgp1uHn3oJZM9Rx/fWVbRUcrFnVvRrLckjuCYlu08OGpMfZXvaYDktRM\ oAKioW9HxP+oNHd2tMT70UuHj0qpVgZPxjjhYtNdO8Titr1eOaF//yUCgYEA+vr1\ gThEPTqQTSulktsNW4E6pICy1MHb7eGxMndrdKGO3VujDMaXfkTffPKxGXBHWj16\ AHMi/QpdqJaWqC+AIeVM6SKsgfgvKrs/1xRc1loWUvi95Uj+cgVu+RCY6Awx5tKs\ VH83Y1LhuuG9BZGfrcqXD+QNG7ygTn6RvVQcv8cCgYBT8ZB3QZBrsScIEWzqKjyj\ AZkc41og/RfJAsaE6lO7xXrKBWuLsgIMt4xorXxmwmhTWPx4e1HhgtPbpmquwzrK\ EEQ3PjWKcenLr25oJ9uRFEz0s1aOCz/Do5mVjKZcrDVflCOrUHDQq0/zmeubjXzu\ AzWeGYXaRFlwi/3NFfBsPQKBgQCWJp3J+QeaOfcqs/oSqcqL+/xBfl1+u5v/7Q49\ ywQWerEl7TTW59iqSjoKXodlWK7XZgAfVMKR8CmoOq9XX1Og87XXpE7gUsKlJfFD\ k4MXGj5Q1U+GZO0U+fsyNqPD78fK+C7xPkq8uVEipPqY4k5Ngu5tK1pMRcUCMOaw\ BipM8QKBgQC57Gnk69VrmKzRddQ24/3Ik/J3EaiqIFlvsltVIu/Rx48ACgoQC5za\ V/n46BaNkwghwMPXoO+iN5ToVW7IoSoB0y1dNBcTwseLunL9YRygMbvUVrHmwl99\ 3MhhFr/3pVUHbH3KaoxY6oTi4cwLEsTzUIFJCL7UzzjTvcvdErMwNg==\ -----END RSA PRIVATE KEY-----" #define FIDO_CERT_FAKE_2 "-----BEGIN CERTIFICATE-----\ MIIBezCCASGgAwIBAgIUGEWBYsFpIe6/YHJ6kWoUVbUADRowCgYIKoZIzj0EAwIw\ EzERMA8GA1UEAwwIZ2xld2x3eWQwHhcNMTkwNjE1MDEwODAzWhcNMjkwNjEyMDEw\ ODAzWjATMREwDwYDVQQDDAhnbGV3bHd5ZDBZMBMGByqGSM49AgEGCCqGSM49AwEH\ A0IABIcl9aAuHa/dAR7AFaL7JCnPRDiyIqKAkpYogx3Xbt8CQaVMf6BbqiC5x+xY\ 0biNpFmInG33qBlOW//C8nkLIxyjUzBRMB0GA1UdDgQWBBTOsHyZfQwCs4TbJifc\ GJbnvpDwsjAfBgNVHSMEGDAWgBTOsHyZfQwCs4TbJifcGJbnvpDwsjAPBgNVHRMB\ Af8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIGfzMUMonvqU5HgwxcAgm78R242z\ 1uS2/D+E2LdItVQRAiEAgOhRN6BHmfzjUVGON2wlaDtUy+/8Zc8fKT8lnWkQm9Y=\ -----END CERTIFICATE-----" #define FIDO_KEY_FAKE_2 "-----BEGIN EC PRIVATE KEY-----\ MHcCAQEEINAabck0V2Q+TJfCw940evZ1WpcskheKUOqr5iM1QSwcoAoGCCqGSM49\ AwEHoUQDQgAEhyX1oC4dr90BHsAVovskKc9EOLIiooCSliiDHddu3wJBpUx/oFuq\ ILnH7FjRuI2kWYicbfeoGU5b/8LyeQsjHA==\ -----END EC PRIVATE KEY-----" #define CREDENTIAL_PRIVATE_KEY "-----BEGIN EC PRIVATE KEY-----\ MHcCAQEEIOIr1e/cc961GGJciBw5vuN2tb+Ys1yIw/Aw7u6L41BSoAoGCCqGSM49\ AwEHoUQDQgAEeP5NOxZTvLehKgiEKn9mtfMB4fnGx73nSDe05IWj44TNtN39dOLs\ EVDDxd9+z2IOshiNs+DSccYGlJUtU7f9FQ==\ -----END EC PRIVATE KEY-----" #define CREDENTIAL_PUBLIC_KEY "-----BEGIN PUBLIC KEY-----\ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeP5NOxZTvLehKgiEKn9mtfMB4fnG\ x73nSDe05IWj44TNtN39dOLsEVDDxd9+z2IOshiNs+DSccYGlJUtU7f9FQ==\ -----END PUBLIC KEY-----" #define CREDENTIAL_PRIVATE_KEY_2 "-----BEGIN EC PRIVATE KEY-----\ MHcCAQEEIFr145W0tnInPqkYKmvvvnp3PrBYA+SPj0Hd5UTLNmcHoAoGCCqGSM49\ AwEHoUQDQgAEsia5ROTxhjBd6+XnRu7/DZ8sM5wItH8bQpT9ojrUnc/hKSm9h1AN\ H5JHglHCQphHQPPNjFZxhIamqn7RuYEIBA==\ -----END EC PRIVATE KEY-----" #define CREDENTIAL_PUBLIC_KEY_2 "-----BEGIN PUBLIC KEY-----\ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsia5ROTxhjBd6+XnRu7/DZ8sM5wI\ tH8bQpT9ojrUnc/hKSm9h1ANH5JHglHCQphHQPPNjFZxhIamqn7RuYEIBA==\ -----END PUBLIC KEY-----" #define CREDENTIAL_PACKED_CA_CERT_PATH "cert/packed.crt" #define CREDENTIAL_PACKED_CA_2_CERT_PATH "cert/packed-2.crt" #define CREDENTIAL_PACKED_PRIVATE_KEY_VALID_PATH "cert/client-p-v.key" #define CREDENTIAL_PACKED_PUBLIC_CERT_VALID_PATH "cert/client-p-v.crt" #define CREDENTIAL_PACKED_PRIVATE_KEY_INVALID_UNIT_PATH "cert/client-p-iu.key" #define CREDENTIAL_PACKED_PUBLIC_CERT_INVALID_UNIT_PATH "cert/client-p-iu.crt" #define CREDENTIAL_PACKED_PRIVATE_KEY_INVALID_COUNTRY_PATH "cert/client-p-ic.key" #define CREDENTIAL_PACKED_PUBLIC_CERT_INVALID_COUNTRY_PATH "cert/client-p-ic.crt" #define CREDENTIAL_PACKED_PRIVATE_KEY_MISSING_COUNTRY_PATH "cert/client-p-mc.key" #define CREDENTIAL_PACKED_PUBLIC_CERT_MISSING_COUNTRY_PATH "cert/client-p-mc.crt" #define CREDENTIAL_PACKED_PRIVATE_KEY_MISSING_ORGANIZATION_PATH "cert/client-p-mo.key" #define CREDENTIAL_PACKED_PUBLIC_CERT_MISSING_ORGANIZATION_PATH "cert/client-p-mo.crt" #define CREDENTIAL_PACKED_PRIVATE_KEY_MISSING_COMMON_NAME_PATH "cert/client-p-mcn.key" #define CREDENTIAL_PACKED_PUBLIC_CERT_MISSING_COMMON_NAME_PATH "cert/client-p-mcn.crt" #define CREDENTIAL_PACKED_PRIVATE_KEY_INVALID_AAGUID_PATH "cert/client-p-ia.key" #define CREDENTIAL_PACKED_PUBLIC_CERT_INVALID_AAGUID_PATH "cert/client-p-ia.crt" #define CREDENTIAL_APPLE_CA_CERT_PATH "cert/apple.crt" #define CREDENTIAL_APPLE_INT_CERT_PATH "cert/apple-int.crt" #define CREDENTIAL_APPLE_INT_KEY_PATH "cert/apple-int.key" #define CREDENTIAL_APPLE_INT2_CERT_PATH "cert/apple-int2.crt" #define CREDENTIAL_APPLE_INT2_KEY_PATH "cert/apple-int2.key" #define CREDENTIAL_APPLE2_CA_CERT_PATH "cert/apple2.crt" #define CREDENTIAL_APPLE2_INT_CERT_PATH "cert/apple2-int.crt" #define CREDENTIAL_APPLE2_INT_KEY_PATH "cert/apple2-int.key" #define G_APPLE_ANONYMOUS_ATTESTATION_OID "1.2.840.113635.100.8.2" #define G_APPLE_CERT_LEAF_DN "cn=plop,ou=not a ca,o=not apple,st=QC" #define SCOPE_NAME "scope2" #define SCOPE_DISPLAY_NAME "Glewlwyd mock scope without password" #define SCOPE_DESCRIPTION "Glewlwyd scope 2 scope description" #define SCOPE_PASSWORD_MAX_AGE 0 #define SCOPE_SCHEME_1_TYPE "mock" #define SCOPE_SCHEME_1_NAME "mock_scheme_95" struct _u_request user_req; struct _u_request admin_req; static char * get_file_content(const char * file_path) { char * buffer = NULL; size_t length, res; FILE * f; f = fopen (file_path, "rb"); if (f) { fseek (f, 0, SEEK_END); length = ftell (f); fseek (f, 0, SEEK_SET); buffer = o_malloc((length+1)*sizeof(char)); if (buffer) { res = fread (buffer, 1, length, f); if (res != length) { fprintf(stderr, "fread warning, reading %zu while expecting %zu", res, length); } // Add null character at the end of buffer, just in case buffer[length] = '\0'; } fclose (f); } else { fprintf(stderr, "error opening file %s\n", file_path); } return buffer; } START_TEST(test_glwd_scheme_webauthn_irl_scope_set) { json_t * j_parameters = json_pack("{sssssisos{s[{ssss}{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME); json_t * j_canuse = json_pack("{ssss}", "module", MODULE_MODULE, "name", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/delegate/" USERNAME "/profile/scheme/", NULL, NULL, NULL, NULL, 200, j_canuse, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_canuse); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_scope_collision_set) { json_t * j_parameters = json_pack("{sssssisos{s[{ssss}{ssss}{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2); json_t * j_canuse = json_pack("{ssss}", "module", MODULE_MODULE, "name", MODULE_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/delegate/" USERNAME "/profile/scheme/", NULL, NULL, NULL, NULL, 200, j_canuse, NULL, NULL), 1); json_decref(j_parameters); json_decref(j_canuse); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_scope_unset) { json_t * j_parameters = json_pack("{sssssisos{s[{ssss}]}}", "display_name", SCOPE_DISPLAY_NAME, "description", SCOPE_DESCRIPTION, "password_max_age", SCOPE_PASSWORD_MAX_AGE, "password_required", json_false(), "scheme", "2", "scheme_type", SCOPE_SCHEME_1_TYPE, "scheme_name", SCOPE_SCHEME_1_NAME); ck_assert_int_eq(run_simple_test(&admin_req, "PUT", SERVER_URI "/scope/" SCOPE_NAME, NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_module_add) { json_t * j_parameters = json_pack("{sssssssisis{sosssisisisss[iii]sisisssss{sososososososo}}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "session-mandatory", WEBAUTHN_SESSION_MANDATORY, "seed", WEBAUTHN_SEED, "challenge-length", WEBAUTHN_CHALLENGE_LEN, "credential-expiration", WEBAUTHN_CREDENTIAL_EXPIRATION, "credential-assertion", WEBAUTHN_CREDENTIAL_ASSERTION, "rp-origin", WEBAUTHN_RP_ORIGIN, "pubKey-cred-params", WEBAUTHN_PUBKEY_CRED_ECDSA_256, WEBAUTHN_PUBKEY_CRED_ECDSA_384, WEBAUTHN_PUBKEY_CRED_ECDSA_512, "ctsProfileMatch", WEBAUTHN_CTS_PROFILE_MATCH, "basicIntegrity", WEBAUTHN_BASIC_INTEGRITY, "google-root-ca-r2", WEBAUTHN_GOOGLE_ROOT_CA_R2, "apple-root-ca", "../test/" CREDENTIAL_APPLE_CA_CERT_PATH, "fmt", "packed", json_true(), "tpm", json_true(), "android-key", json_true(), "android-safetynet", json_true(), "fido-u2f", json_true(), "apple", json_true(), "none", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_module_add_2) { json_t * j_parameters = json_pack("{sssssssisis{sosssisisisss[iii]sisiss}}", "module", MODULE_MODULE, "name", MODULE_NAME_2, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "session-mandatory", WEBAUTHN_SESSION_MANDATORY, "seed", WEBAUTHN_SEED, "challenge-length", WEBAUTHN_CHALLENGE_LEN, "credential-expiration", WEBAUTHN_CREDENTIAL_EXPIRATION, "credential-assertion", WEBAUTHN_CREDENTIAL_ASSERTION, "rp-origin", WEBAUTHN_RP_ORIGIN, "pubKey-cred-params", WEBAUTHN_PUBKEY_CRED_ECDSA_256, WEBAUTHN_PUBKEY_CRED_ECDSA_384, WEBAUTHN_PUBKEY_CRED_ECDSA_512, "ctsProfileMatch", WEBAUTHN_CTS_PROFILE_MATCH, "basicIntegrity", WEBAUTHN_BASIC_INTEGRITY, "google-root-ca-r2", WEBAUTHN_GOOGLE_ROOT_CA_R2); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/mod/scheme/" MODULE_NAME_2, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_module_add_3) { json_t * j_parameters = json_pack("{sssssssisis{sosisisisss[iii]sisisss{sosososososo}}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "session-mandatory", WEBAUTHN_SESSION_MANDATORY_2, "challenge-length", WEBAUTHN_CHALLENGE_LEN, "credential-expiration", WEBAUTHN_CREDENTIAL_EXPIRATION, "credential-assertion", WEBAUTHN_CREDENTIAL_ASSERTION, "rp-origin", WEBAUTHN_RP_ORIGIN, "pubKey-cred-params", WEBAUTHN_PUBKEY_CRED_ECDSA_256, WEBAUTHN_PUBKEY_CRED_ECDSA_384, WEBAUTHN_PUBKEY_CRED_ECDSA_512, "ctsProfileMatch", WEBAUTHN_CTS_PROFILE_MATCH, "basicIntegrity", WEBAUTHN_BASIC_INTEGRITY, "google-root-ca-r2", WEBAUTHN_GOOGLE_ROOT_CA_R2, "fmt", "packed", json_true(), "tpm", json_true(), "android-key", json_true(), "android-safetynet", json_true(), "fido-u2f", json_true(), "none", json_true()); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error) { json_t * j_params; j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "error"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{so}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", json_null()); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_params); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_new_credential) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * jwt_response = json_pack("{s[{sssi}{sssi}{sssi}]ss}", "pubKey-cred-params", "type", "public-key", "alg", WEBAUTHN_PUBKEY_CRED_ECDSA_256, "type", "public-key", "alg", WEBAUTHN_PUBKEY_CRED_ECDSA_384, "type", "public-key", "alg", WEBAUTHN_PUBKEY_CRED_ECDSA_512, "rpId", WEBAUTHN_RP_ORIGIN), * j_result, * j_result2; struct _u_response resp; size_t challenge_len; json_t * j_canuse = json_pack("{ssss}", "module", MODULE_MODULE, "name", MODULE_NAME); ck_assert_int_eq(run_simple_test(&user_req, "GET", SERVER_URI "profile/scheme/", NULL, NULL, NULL, NULL, 200, j_canuse, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, jwt_response, NULL, NULL), 1); ulfius_init_response(&resp); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_result = ulfius_get_json_body_response(&resp, NULL); ulfius_clean_response(&resp); ulfius_init_response(&resp); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); j_result2 = ulfius_get_json_body_response(&resp, NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), NULL, &challenge_len), 1); ck_assert_int_eq(challenge_len, WEBAUTHN_CHALLENGE_LEN); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result2, "challenge")), json_string_length(json_object_get(j_result2, "challenge")), NULL, &challenge_len), 1); ck_assert_int_eq(challenge_len, WEBAUTHN_CHALLENGE_LEN); ck_assert_str_ne(json_string_value(json_object_get(j_result, "session")), ""); ck_assert_str_ne(json_string_value(json_object_get(j_result, "session")), json_string_value(json_object_get(j_result2, "session"))); ck_assert_str_ne(json_string_value(json_object_get(j_result, "challenge")), json_string_value(json_object_get(j_result2, "challenge"))); json_decref(j_canuse); json_decref(j_params); json_decref(j_result); json_decref(j_result2); json_decref(jwt_response); ulfius_clean_response(&resp); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_bad_formed_response) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result = NULL, * j_credential = NULL; struct _u_response resp; const char * session; ulfius_init_response(&resp); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); //json_error_t jerr; //y_log_message(Y_LOG_LEVEL_DEBUG, "session '%s'", session); //y_log_message(Y_LOG_LEVEL_DEBUG, "j_result '%s'", json_dumps(j_result, JSON_ENCODE_ANY)); //j_credential = json_pack_ex(&jerr, 0, "{sssssss{sssssss{sssssss{ssss}}}}", j_credential = json_pack("{sssssss{sssssss{sssssss{ssss}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", "error", "rawId", "error", "type", "public-key", "response", "attestationObject", "error", "clientDataJSON", "error"); //y_log_message(Y_LOG_LEVEL_DEBUG, "err %s", jerr.text); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_params); json_decref(j_result); json_decref(j_credential); ulfius_clean_response(&resp); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_invalid_client_data_json_challenge) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{sss{}ssss}", "challenge", "error", "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("clientDataJSON.challenge invalid"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_invalid_client_data_json_rp_origin) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", "error", "type", "webauthn.create"); ck_assert_ptr_ne(j_client_data, NULL); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("clientDataJSON.origin invalid - Client send error, required https://www.glewlwyd.tld"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_invalid_client_data_json_type) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "error"); ck_assert_ptr_ne(j_client_data, NULL); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("clientDataJSON.type invalid"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_invalid_auth_data_rpid) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); auth_data[0]++; rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("authData.rpIdHash invalid"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_invalid_auth_data_flag_at) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT;// | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("authData.Attested credential data not set"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_invalid_auth_data_flag_user_present) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("authData.userPresent not set"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_invalid_auth_data_credential_id) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data[auth_data_len]++; auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("auth_data invalid size"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_invalid_auth_data_credential_id_content) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data[auth_data_len]++; auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("Invalid rawId"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_invalid_auth_data_cose_key_invalid_map) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); //cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); cose_pair.value = cbor_build_string("error"); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("Invalid COSE key"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_invalid_auth_data_cose_key_invalid_alg) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(42); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("Invalid COSE key"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_invalid_data_cose_key_key_x_sign) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); //cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("Invalid COSE key"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_invalid_data_cose_key_key_x_type) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_string("error"); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("Invalid COSE key"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_invalid_data_cose_key_key_alg) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(42); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("Invalid COSE key"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_invalid_auth_data_cose_key_invalid_dump) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data[auth_data_len]++; auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("Invalid COSE key"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_att_stmt_map_size) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(5); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("error"); cose_pair.value = cbor_build_string("error"); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("CBOR map value 'attStmt' invalid format"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_att_stmt_cert_key) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("error"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("CBOR map value 'x5c' invalid format"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_att_stmt_x5c_size) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(2); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("CBOR map value 'attStmt' invalid format"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_sig_base_prefix) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 1; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("Invalid signature"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_sig_base_rpid) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data[1]++; verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("Invalid signature"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_sig_base_client_data_hash) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data[verification_data_offset]++; verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("Invalid signature"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_sig_base_client_id) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data[verification_data_offset]++; verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("Invalid signature"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_sig_base_key_prefix) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x05, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("Invalid signature"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_sig_base_key_x) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data[verification_data_offset]++; verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data[verification_data_offset]++; verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("Invalid signature"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_sig_base_key_y) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data[verification_data_offset]++; verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data[verification_data_offset]++; verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("Invalid signature"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_sig_base_size) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data[verification_data_offset]++; verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset-1; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("Invalid signature"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_sig_base_content) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data[verification_data_offset]++; verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); signature.data[0]++; cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("Invalid signature"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_sig_key) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data[verification_data_offset]++; verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("error"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("Error sig is not a bytestring"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_att_obj_size) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data[verification_data_offset]++; verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(4); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("attestationObject invalid cbor item"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_auth_data_key) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data[verification_data_offset]++; verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("error"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("authData invalid"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_att_stmt_key) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data[verification_data_offset]++; verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("error"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("CBOR map value 'attStmt' invalid format"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_u2f_success) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_obj = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 200, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_obj); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_u2f_success_already_registered) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_stmt; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE; key_data.size = o_strlen(FIDO_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_stmt = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_stmt); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("Credential already registered"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_stmt); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_u2f_2_success) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_cbor; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id_2, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id_2, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY_2; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY_2); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE_2; key_data.size = o_strlen(FIDO_KEY_FAKE_2); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id_2, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE_2; key_data.size = o_strlen(FIDO_CERT_FAKE_2); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_cbor = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_cbor); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id_2, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 200, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_cbor); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_u2f_2_collision_error) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id_2, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id_2, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY_2; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY_2); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE_2; key_data.size = o_strlen(FIDO_KEY_FAKE_2); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id_2, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE_2; key_data.size = o_strlen(FIDO_CERT_FAKE_2); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_obj = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id_2, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 404, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); gnutls_x509_privkey_deinit(key); gnutls_x509_crt_deinit(cert); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_obj); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_u2f_2_in_2_success) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, verification_data_offset = 0, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, rp_id_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id_2, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id_2, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); rp_id_len = auth_data_len; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY_2; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY_2); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE_2; key_data.size = o_strlen(FIDO_KEY_FAKE_2); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id_2, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)FIDO_CERT_FAKE_2; key_data.size = o_strlen(FIDO_CERT_FAKE_2); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_obj = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); verification_data[0] = 0; verification_data_offset = 1; memcpy(verification_data+verification_data_offset, auth_data, rp_id_len); verification_data_offset += rp_id_len; memcpy(verification_data+verification_data_offset, client_data_hash, client_data_hash_len); verification_data_offset += client_data_hash_len; memcpy(verification_data+verification_data_offset, credential_id_2, WEBAUTHN_CREDENTIAL_ID_LEN); verification_data_offset += WEBAUTHN_CREDENTIAL_ID_LEN; memset(verification_data+verification_data_offset, 0x04, 1); verification_data_offset++; memcpy(verification_data+verification_data_offset, key_x.data, key_x.size); verification_data_offset += key_x.size; memcpy(verification_data+verification_data_offset, key_y.data, key_y.size); verification_data_offset += key_y.size; key_data.data = verification_data; key_data.size = verification_data_offset; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("fido-u2f"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 200, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_privkey_deinit(privkey); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_obj); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_test_assertion_trigger_error_session_invalid) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "trigger-assertion"); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_test_assertion_error_session_invalid) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "trigger-assertion"), * j_result, * j_client_data, * j_attestation; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTHENTICATOR_DATA_SIZE], auth_data_enc[AUTHENTICATOR_DATA_SIZE*2], * signature_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, client_data_json_hash_len = 32, auth_data_enc_len, signature_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, signature; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.get"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); key_data.data = (unsigned char *)CREDENTIAL_PRIVATE_KEY; key_data.size = o_strlen(CREDENTIAL_PRIVATE_KEY); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTHENTICATOR_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); client_data_json_hash_len = AUTHENTICATOR_DATA_SIZE - auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, (auth_data+auth_data_len), &client_data_json_hash_len), GNUTLS_E_SUCCESS); auth_data_len += client_data_json_hash_len; ck_assert_int_eq(o_base64_encode(auth_data, 37, auth_data_enc, &auth_data_enc_len), 1); key_data.data = auth_data; key_data.size = auth_data_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, NULL, &signature_enc_len), 1); ck_assert_ptr_ne((signature_enc = o_malloc(signature_enc_len+1)), NULL); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, signature_enc, &signature_enc_len), 1); j_attestation = json_pack("{ss ss ss s{ss ss s{ss% ss% ss s{ss ss ss}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "validate-assertion", "session", session, "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "clientDataJSON", client_data_json_enc, "authenticatorData", auth_data_enc, "signature", signature_enc); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_attestation, NULL, 401, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); json_decref(j_params); json_decref(j_result); json_decref(j_attestation); json_decref(j_client_data); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(signature_enc); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_test_assertion_invalid_challenge) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "trigger-assertion"), * j_result, * j_client_data, * j_attestation; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTHENTICATOR_DATA_SIZE], auth_data_enc[AUTHENTICATOR_DATA_SIZE*2], * signature_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, client_data_json_hash_len = 32, auth_data_enc_len, signature_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, signature; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{sss{}ssss}", "challenge", "error", "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.get"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); key_data.data = (unsigned char *)CREDENTIAL_PRIVATE_KEY; key_data.size = o_strlen(CREDENTIAL_PRIVATE_KEY); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTHENTICATOR_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); client_data_json_hash_len = AUTHENTICATOR_DATA_SIZE - auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, (auth_data+auth_data_len), &client_data_json_hash_len), GNUTLS_E_SUCCESS); auth_data_len += client_data_json_hash_len; ck_assert_int_eq(o_base64_encode(auth_data, 37, auth_data_enc, &auth_data_enc_len), 1); key_data.data = auth_data; key_data.size = auth_data_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, NULL, &signature_enc_len), 1); ck_assert_ptr_ne((signature_enc = o_malloc(signature_enc_len+1)), NULL); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, signature_enc, &signature_enc_len), 1); j_attestation = json_pack("{ss ss ss s{ss ss s{ss% ss% ss s{ss ss ss}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "validate-assertion", "session", session, "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "clientDataJSON", client_data_json_enc, "authenticatorData", auth_data_enc, "signature", signature_enc); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_attestation, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); json_decref(j_params); json_decref(j_result); json_decref(j_attestation); json_decref(j_client_data); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(signature_enc); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_test_assertion_invalid_origin) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "trigger-assertion"), * j_result, * j_client_data, * j_attestation; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTHENTICATOR_DATA_SIZE], auth_data_enc[AUTHENTICATOR_DATA_SIZE*2], * signature_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, client_data_json_hash_len = 32, auth_data_enc_len, signature_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, signature; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", "error", "type", "webauthn.get"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); key_data.data = (unsigned char *)CREDENTIAL_PRIVATE_KEY; key_data.size = o_strlen(CREDENTIAL_PRIVATE_KEY); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTHENTICATOR_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); client_data_json_hash_len = AUTHENTICATOR_DATA_SIZE - auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, (auth_data+auth_data_len), &client_data_json_hash_len), GNUTLS_E_SUCCESS); auth_data_len += client_data_json_hash_len; ck_assert_int_eq(o_base64_encode(auth_data, 37, auth_data_enc, &auth_data_enc_len), 1); key_data.data = auth_data; key_data.size = auth_data_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, NULL, &signature_enc_len), 1); ck_assert_ptr_ne((signature_enc = o_malloc(signature_enc_len+1)), NULL); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, signature_enc, &signature_enc_len), 1); j_attestation = json_pack("{ss ss ss s{ss ss s{ss% ss% ss s{ss ss ss}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "validate-assertion", "session", session, "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "clientDataJSON", client_data_json_enc, "authenticatorData", auth_data_enc, "signature", signature_enc); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_attestation, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); json_decref(j_params); json_decref(j_result); json_decref(j_attestation); json_decref(j_client_data); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(signature_enc); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_test_assertion_invalid_client_data_type) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "trigger-assertion"), * j_result, * j_client_data, * j_attestation; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTHENTICATOR_DATA_SIZE], auth_data_enc[AUTHENTICATOR_DATA_SIZE*2], * signature_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, client_data_json_hash_len = 32, auth_data_enc_len, signature_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, signature; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "error"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); key_data.data = (unsigned char *)CREDENTIAL_PRIVATE_KEY; key_data.size = o_strlen(CREDENTIAL_PRIVATE_KEY); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTHENTICATOR_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); client_data_json_hash_len = AUTHENTICATOR_DATA_SIZE - auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, (auth_data+auth_data_len), &client_data_json_hash_len), GNUTLS_E_SUCCESS); auth_data_len += client_data_json_hash_len; ck_assert_int_eq(o_base64_encode(auth_data, 37, auth_data_enc, &auth_data_enc_len), 1); key_data.data = auth_data; key_data.size = auth_data_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, NULL, &signature_enc_len), 1); ck_assert_ptr_ne((signature_enc = o_malloc(signature_enc_len+1)), NULL); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, signature_enc, &signature_enc_len), 1); j_attestation = json_pack("{ss ss ss s{ss ss s{ss% ss% ss s{ss ss ss}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "validate-assertion", "session", session, "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "clientDataJSON", client_data_json_enc, "authenticatorData", auth_data_enc, "signature", signature_enc); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_attestation, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); json_decref(j_params); json_decref(j_result); json_decref(j_attestation); json_decref(j_client_data); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(signature_enc); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_test_assertion_invalid_client_data_encoded) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "trigger-assertion"), * j_result, * j_client_data, * j_attestation; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTHENTICATOR_DATA_SIZE], auth_data_enc[AUTHENTICATOR_DATA_SIZE*2], * signature_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, client_data_json_hash_len = 32, auth_data_enc_len, signature_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, signature; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.get"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); client_data_json[0]++; ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); key_data.data = (unsigned char *)CREDENTIAL_PRIVATE_KEY; key_data.size = o_strlen(CREDENTIAL_PRIVATE_KEY); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTHENTICATOR_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); client_data_json_hash_len = AUTHENTICATOR_DATA_SIZE - auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, (auth_data+auth_data_len), &client_data_json_hash_len), GNUTLS_E_SUCCESS); auth_data_len += client_data_json_hash_len; ck_assert_int_eq(o_base64_encode(auth_data, 37, auth_data_enc, &auth_data_enc_len), 1); key_data.data = auth_data; key_data.size = auth_data_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, NULL, &signature_enc_len), 1); ck_assert_ptr_ne((signature_enc = o_malloc(signature_enc_len+1)), NULL); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, signature_enc, &signature_enc_len), 1); j_attestation = json_pack("{ss ss ss s{ss ss s{ss% ss% ss s{ss ss ss}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "validate-assertion", "session", session, "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "clientDataJSON", client_data_json_enc, "authenticatorData", auth_data_enc, "signature", signature_enc); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_attestation, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); json_decref(j_params); json_decref(j_result); json_decref(j_attestation); json_decref(j_client_data); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(signature_enc); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_test_assertion_invalid_rp_id_hash) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "trigger-assertion"), * j_result, * j_client_data, * j_attestation; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTHENTICATOR_DATA_SIZE], auth_data_enc[AUTHENTICATOR_DATA_SIZE*2], * signature_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, client_data_json_hash_len = 32, auth_data_enc_len, signature_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, signature; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.get"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); key_data.data = (unsigned char *)CREDENTIAL_PRIVATE_KEY; key_data.size = o_strlen(CREDENTIAL_PRIVATE_KEY); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTHENTICATOR_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); auth_data[0]++; // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); client_data_json_hash_len = AUTHENTICATOR_DATA_SIZE - auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, (auth_data+auth_data_len), &client_data_json_hash_len), GNUTLS_E_SUCCESS); auth_data_len += client_data_json_hash_len; ck_assert_int_eq(o_base64_encode(auth_data, 37, auth_data_enc, &auth_data_enc_len), 1); key_data.data = auth_data; key_data.size = auth_data_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, NULL, &signature_enc_len), 1); ck_assert_ptr_ne((signature_enc = o_malloc(signature_enc_len+1)), NULL); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, signature_enc, &signature_enc_len), 1); j_attestation = json_pack("{ss ss ss s{ss ss s{ss% ss% ss s{ss ss ss}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "validate-assertion", "session", session, "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "clientDataJSON", client_data_json_enc, "authenticatorData", auth_data_enc, "signature", signature_enc); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_attestation, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); json_decref(j_params); json_decref(j_result); json_decref(j_attestation); json_decref(j_client_data); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(signature_enc); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_test_assertion_invalid_flag_user_present) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "trigger-assertion"), * j_result, * j_client_data, * j_attestation; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTHENTICATOR_DATA_SIZE], auth_data_enc[AUTHENTICATOR_DATA_SIZE*2], * signature_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, client_data_json_hash_len = 32, auth_data_enc_len, signature_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, signature; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.get"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); key_data.data = (unsigned char *)CREDENTIAL_PRIVATE_KEY; key_data.size = o_strlen(CREDENTIAL_PRIVATE_KEY); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTHENTICATOR_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = 0; auth_data_len += 5; key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); client_data_json_hash_len = AUTHENTICATOR_DATA_SIZE - auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, (auth_data+auth_data_len), &client_data_json_hash_len), GNUTLS_E_SUCCESS); auth_data_len += client_data_json_hash_len; ck_assert_int_eq(o_base64_encode(auth_data, 37, auth_data_enc, &auth_data_enc_len), 1); key_data.data = auth_data; key_data.size = auth_data_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, NULL, &signature_enc_len), 1); ck_assert_ptr_ne((signature_enc = o_malloc(signature_enc_len+1)), NULL); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, signature_enc, &signature_enc_len), 1); j_attestation = json_pack("{ss ss ss s{ss ss s{ss% ss% ss s{ss ss ss}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "validate-assertion", "session", session, "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "clientDataJSON", client_data_json_enc, "authenticatorData", auth_data_enc, "signature", signature_enc); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_attestation, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); json_decref(j_params); json_decref(j_result); json_decref(j_attestation); json_decref(j_client_data); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(signature_enc); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_test_assertion_invalid_client_data_hash) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "trigger-assertion"), * j_result, * j_client_data, * j_attestation; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTHENTICATOR_DATA_SIZE], auth_data_enc[AUTHENTICATOR_DATA_SIZE*2], * signature_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, client_data_json_hash_len = 32, auth_data_enc_len, signature_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, signature; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.get"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); key_data.data = (unsigned char *)CREDENTIAL_PRIVATE_KEY; key_data.size = o_strlen(CREDENTIAL_PRIVATE_KEY); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTHENTICATOR_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); client_data_json_hash_len = AUTHENTICATOR_DATA_SIZE - auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, (auth_data+auth_data_len), &client_data_json_hash_len), GNUTLS_E_SUCCESS); auth_data[auth_data_len]++; auth_data_len += client_data_json_hash_len; ck_assert_int_eq(o_base64_encode(auth_data, 37, auth_data_enc, &auth_data_enc_len), 1); key_data.data = auth_data; key_data.size = auth_data_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, NULL, &signature_enc_len), 1); ck_assert_ptr_ne((signature_enc = o_malloc(signature_enc_len+1)), NULL); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, signature_enc, &signature_enc_len), 1); j_attestation = json_pack("{ss ss ss s{ss ss s{ss% ss% ss s{ss ss ss}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "validate-assertion", "session", session, "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "clientDataJSON", client_data_json_enc, "authenticatorData", auth_data_enc, "signature", signature_enc); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_attestation, NULL, 401, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); json_decref(j_params); json_decref(j_result); json_decref(j_attestation); json_decref(j_client_data); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(signature_enc); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_test_assertion_invalid_signature) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "trigger-assertion"), * j_result, * j_client_data, * j_attestation; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTHENTICATOR_DATA_SIZE], auth_data_enc[AUTHENTICATOR_DATA_SIZE*2], * signature_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, client_data_json_hash_len = 32, auth_data_enc_len, signature_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, signature; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.get"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); key_data.data = (unsigned char *)CREDENTIAL_PRIVATE_KEY; key_data.size = o_strlen(CREDENTIAL_PRIVATE_KEY); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTHENTICATOR_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); client_data_json_hash_len = AUTHENTICATOR_DATA_SIZE - auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, (auth_data+auth_data_len), &client_data_json_hash_len), GNUTLS_E_SUCCESS); auth_data_len += client_data_json_hash_len; ck_assert_int_eq(o_base64_encode(auth_data, 37, auth_data_enc, &auth_data_enc_len), 1); key_data.data = auth_data; key_data.size = auth_data_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); signature.data[0]++; ck_assert_int_eq(o_base64_encode(signature.data, signature.size, NULL, &signature_enc_len), 1); ck_assert_ptr_ne((signature_enc = o_malloc(signature_enc_len+1)), NULL); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, signature_enc, &signature_enc_len), 1); j_attestation = json_pack("{ss ss ss s{ss ss s{ss% ss% ss s{ss ss ss}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "validate-assertion", "session", session, "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "clientDataJSON", client_data_json_enc, "authenticatorData", auth_data_enc, "signature", signature_enc); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_attestation, NULL, 401, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); json_decref(j_params); json_decref(j_result); json_decref(j_attestation); json_decref(j_client_data); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(signature_enc); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_test_assertion_success) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "trigger-assertion"), * j_result, * j_client_data, * j_attestation; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTHENTICATOR_DATA_SIZE], auth_data_enc[AUTHENTICATOR_DATA_SIZE*2], * signature_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, client_data_json_hash_len = 32, auth_data_enc_len, signature_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, signature; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.get"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); key_data.data = (unsigned char *)CREDENTIAL_PRIVATE_KEY; key_data.size = o_strlen(CREDENTIAL_PRIVATE_KEY); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTHENTICATOR_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); client_data_json_hash_len = AUTHENTICATOR_DATA_SIZE - auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, (auth_data+auth_data_len), &client_data_json_hash_len), GNUTLS_E_SUCCESS); auth_data_len += client_data_json_hash_len; ck_assert_int_eq(o_base64_encode(auth_data, 37, auth_data_enc, &auth_data_enc_len), 1); key_data.data = auth_data; key_data.size = auth_data_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, NULL, &signature_enc_len), 1); ck_assert_ptr_ne((signature_enc = o_malloc(signature_enc_len+1)), NULL); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, signature_enc, &signature_enc_len), 1); j_attestation = json_pack("{ss ss ss s{ss ss s{ss% ss% ss s{ss ss ss}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "validate-assertion", "session", session, "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "clientDataJSON", client_data_json_enc, "authenticatorData", auth_data_enc, "signature", signature_enc); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_attestation, NULL, 200, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); json_decref(j_params); json_decref(j_result); json_decref(j_attestation); json_decref(j_client_data); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(signature_enc); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_test_assertion_invalid_credential_id) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "trigger-assertion"), * j_result, * j_client_data, * j_attestation; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTHENTICATOR_DATA_SIZE], auth_data_enc[AUTHENTICATOR_DATA_SIZE*2], * signature_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, client_data_json_hash_len = 32, auth_data_enc_len, signature_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, signature; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.get"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); key_data.data = (unsigned char *)CREDENTIAL_PRIVATE_KEY; key_data.size = o_strlen(CREDENTIAL_PRIVATE_KEY); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id_2, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id_2, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTHENTICATOR_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); client_data_json_hash_len = AUTHENTICATOR_DATA_SIZE - auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, (auth_data+auth_data_len), &client_data_json_hash_len), GNUTLS_E_SUCCESS); auth_data_len += client_data_json_hash_len; ck_assert_int_eq(o_base64_encode(auth_data, 37, auth_data_enc, &auth_data_enc_len), 1); key_data.data = auth_data; key_data.size = auth_data_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, NULL, &signature_enc_len), 1); ck_assert_ptr_ne((signature_enc = o_malloc(signature_enc_len+1)), NULL); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, signature_enc, &signature_enc_len), 1); j_attestation = json_pack("{ss ss ss s{ss ss s{ss% ss% ss s{ss ss ss}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "validate-assertion", "session", session, "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "clientDataJSON", client_data_json_enc, "authenticatorData", auth_data_enc, "signature", signature_enc); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_attestation, NULL, 401, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); json_decref(j_params); json_decref(j_result); json_decref(j_attestation); json_decref(j_client_data); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(signature_enc); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_trigger_flaggerbasted) { // This test is intended to get a trigger response even with a username that doesn't exist json_t * j_params = json_pack("{ssssss}", "username", "i_am_conholio", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/scheme/trigger/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_trigger_not_flaggerbasted) { // This test is intended to not get a trigger response with a username that doesn't exist json_t * j_params = json_pack("{ssssss}", "username", "i_am_conholio", "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/scheme/trigger/", NULL, NULL, j_params, NULL, 401, NULL, NULL, NULL), 1); json_decref(j_params); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_auth_success) { json_t * j_params = json_pack("{ssssss}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME), * j_result, * j_client_data, * j_attestation; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTHENTICATOR_DATA_SIZE], auth_data_enc[AUTHENTICATOR_DATA_SIZE*2], * signature_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, client_data_json_hash_len = 32, auth_data_enc_len, signature_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, signature; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; struct _u_request request; ulfius_init_request(&request); ulfius_init_response(&resp); ulfius_init_response(&resp_register); request.http_verb = o_strdup("POST"); request.http_url = o_strdup(SERVER_URI "/auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&request, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&request, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.get"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); key_data.data = (unsigned char *)CREDENTIAL_PRIVATE_KEY; key_data.size = o_strlen(CREDENTIAL_PRIVATE_KEY); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTHENTICATOR_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); client_data_json_hash_len = AUTHENTICATOR_DATA_SIZE - auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, (auth_data+auth_data_len), &client_data_json_hash_len), GNUTLS_E_SUCCESS); auth_data_len += client_data_json_hash_len; ck_assert_int_eq(o_base64_encode(auth_data, 37, auth_data_enc, &auth_data_enc_len), 1); key_data.data = auth_data; key_data.size = auth_data_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, NULL, &signature_enc_len), 1); ck_assert_ptr_ne((signature_enc = o_malloc(signature_enc_len+1)), NULL); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, signature_enc, &signature_enc_len), 1); j_attestation = json_pack("{ss ss ss s{ss ss s{ss% ss% ss s{ss ss ss}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "validate-assertion", "session", session, "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "clientDataJSON", client_data_json_enc, "authenticatorData", auth_data_enc, "signature", signature_enc); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_attestation, NULL, 200, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); json_decref(j_params); json_decref(j_result); json_decref(j_attestation); json_decref(j_client_data); ulfius_clean_request(&request); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(signature_enc); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_auth_2_in_2_success) { json_t * j_params = json_pack("{ssssss}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2), * j_result, * j_client_data, * j_attestation; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTHENTICATOR_DATA_SIZE], auth_data_enc[AUTHENTICATOR_DATA_SIZE*2], * signature_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, client_data_json_hash_len = 32, auth_data_enc_len, signature_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, signature; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; struct _u_request request; ulfius_init_request(&request); ulfius_init_response(&resp); ulfius_init_response(&resp_register); request.http_verb = o_strdup("POST"); request.http_url = o_strdup(SERVER_URI "/auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&request, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&request, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.get"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); key_data.data = (unsigned char *)CREDENTIAL_PRIVATE_KEY_2; key_data.size = o_strlen(CREDENTIAL_PRIVATE_KEY_2); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id_2, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id_2, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTHENTICATOR_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); client_data_json_hash_len = AUTHENTICATOR_DATA_SIZE - auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, (auth_data+auth_data_len), &client_data_json_hash_len), GNUTLS_E_SUCCESS); auth_data_len += client_data_json_hash_len; ck_assert_int_eq(o_base64_encode(auth_data, 37, auth_data_enc, &auth_data_enc_len), 1); key_data.data = auth_data; key_data.size = auth_data_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, NULL, &signature_enc_len), 1); ck_assert_ptr_ne((signature_enc = o_malloc(signature_enc_len+1)), NULL); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, signature_enc, &signature_enc_len), 1); j_attestation = json_pack("{ss ss ss s{ss ss s{ss% ss% ss s{ss ss ss}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value", "register", "validate-assertion", "session", session, "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "clientDataJSON", client_data_json_enc, "authenticatorData", auth_data_enc, "signature", signature_enc); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_attestation, NULL, 200, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); json_decref(j_params); json_decref(j_result); json_decref(j_attestation); json_decref(j_client_data); ulfius_clean_request(&request); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(signature_enc); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_auth_2_in_1_error) { json_t * j_params = json_pack("{ssssss}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME), * j_result, * j_client_data, * j_attestation; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTHENTICATOR_DATA_SIZE], auth_data_enc[AUTHENTICATOR_DATA_SIZE*2], * signature_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, client_data_json_hash_len = 32, auth_data_enc_len, signature_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, signature; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; struct _u_request request; ulfius_init_request(&request); ulfius_init_response(&resp); ulfius_init_response(&resp_register); request.http_verb = o_strdup("POST"); request.http_url = o_strdup(SERVER_URI "/auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&request, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&request, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.get"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); key_data.data = (unsigned char *)CREDENTIAL_PRIVATE_KEY_2; key_data.size = o_strlen(CREDENTIAL_PRIVATE_KEY_2); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTHENTICATOR_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); client_data_json_hash_len = AUTHENTICATOR_DATA_SIZE - auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, (auth_data+auth_data_len), &client_data_json_hash_len), GNUTLS_E_SUCCESS); auth_data_len += client_data_json_hash_len; ck_assert_int_eq(o_base64_encode(auth_data, 37, auth_data_enc, &auth_data_enc_len), 1); key_data.data = auth_data; key_data.size = auth_data_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, NULL, &signature_enc_len), 1); ck_assert_ptr_ne((signature_enc = o_malloc(signature_enc_len+1)), NULL); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, signature_enc, &signature_enc_len), 1); j_attestation = json_pack("{ss ss ss s{ss ss s{ss% ss% ss s{ss ss ss}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "validate-assertion", "session", session, "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "clientDataJSON", client_data_json_enc, "authenticatorData", auth_data_enc, "signature", signature_enc); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_attestation, NULL, 401, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); json_decref(j_params); json_decref(j_result); json_decref(j_attestation); json_decref(j_client_data); ulfius_clean_request(&request); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(signature_enc); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_auth_invalid_credential_id) { json_t * j_params = json_pack("{ssssss}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME), * j_result, * j_client_data, * j_attestation; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTHENTICATOR_DATA_SIZE], auth_data_enc[AUTHENTICATOR_DATA_SIZE*2], * signature_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, client_data_json_hash_len = 32, auth_data_enc_len, signature_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, signature; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; struct _u_request request; ulfius_init_request(&request); ulfius_init_response(&resp); ulfius_init_response(&resp_register); request.http_verb = o_strdup("POST"); request.http_url = o_strdup(SERVER_URI "/auth/scheme/trigger/"); ck_assert_int_eq(ulfius_set_json_body_request(&request, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&request, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.get"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); key_data.data = (unsigned char *)CREDENTIAL_PRIVATE_KEY; key_data.size = o_strlen(CREDENTIAL_PRIVATE_KEY); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id_2, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id_2, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTHENTICATOR_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); client_data_json_hash_len = AUTHENTICATOR_DATA_SIZE - auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, (auth_data+auth_data_len), &client_data_json_hash_len), GNUTLS_E_SUCCESS); auth_data_len += client_data_json_hash_len; ck_assert_int_eq(o_base64_encode(auth_data, 37, auth_data_enc, &auth_data_enc_len), 1); key_data.data = auth_data; key_data.size = auth_data_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, NULL, &signature_enc_len), 1); ck_assert_ptr_ne((signature_enc = o_malloc(signature_enc_len+1)), NULL); ck_assert_int_eq(o_base64_encode(signature.data, signature.size, signature_enc, &signature_enc_len), 1); j_attestation = json_pack("{ss ss ss s{ss ss s{ss% ss% ss s{ss ss ss}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "validate-assertion", "session", session, "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "clientDataJSON", client_data_json_enc, "authenticatorData", auth_data_enc, "signature", signature_enc); ck_assert_int_eq(run_simple_test(NULL, "POST", SERVER_URI "auth/", NULL, NULL, j_attestation, NULL, 401, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); json_decref(j_params); json_decref(j_result); json_decref(j_attestation); json_decref(j_client_data); ulfius_clean_request(&request); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(signature_enc); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_remove_credential_success) { json_t * j_params; unsigned char credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2]; size_t credential_id_enc_len; ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); j_params = json_pack("{sssssss{ssss%}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "remove-credential", "credential_id", credential_id_enc, credential_id_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_remove_credential_2_success) { json_t * j_params; unsigned char credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2]; size_t credential_id_enc_len; ck_assert_int_eq(o_base64_encode(credential_id_2, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); j_params = json_pack("{sssssss{ssss%}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "remove-credential", "credential_id", credential_id_enc, credential_id_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_remove_credential_2_in_2_success) { json_t * j_params; unsigned char credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2]; size_t credential_id_enc_len; ck_assert_int_eq(o_base64_encode(credential_id_2, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); j_params = json_pack("{sssssss{ssss%}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value", "register", "remove-credential", "credential_id", credential_id_enc, credential_id_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_safetynet_ver_key) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], * att_obj_ser = NULL, * att_obj_ser_enc, nonce[NONCE_SIZE], nonce_hash[32], nonce_hash_enc[64], * cert_der_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, att_obj_ser_len, att_obj_ser_enc_len, nonce_len, nonce_hash_len = 32, nonce_hash_enc_len, cert_der_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json, * str_response; gnutls_datum_t key_data, key_x, key_y; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj; struct cbor_pair cose_pair; jwt_t * jwt_response; json_t * j_grant; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); cose_pair.key = cbor_build_string("error"); cose_pair.value = cbor_build_string("14366018"); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)ANDROID_SAFETYNET_CERT_FAKE; key_data.size = o_strlen(ANDROID_SAFETYNET_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); ck_assert_int_eq(o_base64_encode(cert_der, cert_der_len, NULL, &cert_der_enc_len), 1); cert_der_enc = o_malloc(cert_der_enc_len+1); ck_assert_int_eq(o_base64_encode(cert_der, cert_der_len, cert_der_enc, &cert_der_enc_len), 1); ck_assert_int_eq(r_jwt_init(&jwt_response), RHN_OK); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_response, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_response, R_FORMAT_PEM, (unsigned char *)ANDROID_SAFETYNET_KEY_FAKE, o_strlen(ANDROID_SAFETYNET_KEY_FAKE), NULL, 0), RHN_OK); j_grant = json_pack("[s]", cert_der_enc); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_response, "x5c", j_grant), RHN_OK); json_decref(j_grant); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); memcpy(nonce, auth_data, auth_data_len); nonce_len = NONCE_SIZE-auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, nonce+auth_data_len, &nonce_len), GNUTLS_E_SUCCESS); nonce_len += auth_data_len; key_data.data = nonce; key_data.size = nonce_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, nonce_hash, &nonce_hash_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64_encode(nonce_hash, nonce_hash_len, nonce_hash_enc, &nonce_hash_enc_len), 1); j_grant = json_pack("{sssisssssosos[s]}", "nonce", nonce_hash_enc, "timestampMs", time(NULL)*1000, "apkPackageName", "com.google.android.gms", "apkDigestSha256", "cGxlYXNlZG9udGRlY29kZW1laW1ub3RhcmVhbGhhc2gK", "ctsProfileMatch", json_true(), "basicIntegrity", json_true(), "apkCertificateDigestSha256", "cGxlYXNlZG9udGRlY29kZW1lZWl0aGVyaXRzZmFrZSEK"); ck_assert_int_eq(r_jwt_set_full_claims_json_t(jwt_response, j_grant), RHN_OK); json_decref(j_grant); str_response = r_jwt_serialize_signed(jwt_response, NULL, 0); if (str_response == NULL) { // TODO: Remove when the test will pass on Travis CI y_log_message(Y_LOG_LEVEL_ERROR, "str_response is NULL: %s", strerror(errno)); } ck_assert_ptr_ne(str_response, NULL); cose_pair.key = cbor_build_string("response"); cose_pair.value = cbor_build_bytestring((unsigned char *)str_response, o_strlen(str_response)); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("android-safetynet"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("version invalid"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); o_free(str_response); o_free(cert_der_enc); r_jwt_free(jwt_response); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_safetynet_ver_type) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], * att_obj_ser = NULL, * att_obj_ser_enc, nonce[NONCE_SIZE], nonce_hash[32], nonce_hash_enc[64], * cert_der_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, att_obj_ser_len, att_obj_ser_enc_len, nonce_len, nonce_hash_len = 32, nonce_hash_enc_len, cert_der_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json, * str_response; gnutls_datum_t key_data, key_x, key_y; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj; struct cbor_pair cose_pair; jwt_t * jwt_response; json_t * j_grant; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); cose_pair.key = cbor_build_string("ver"); cose_pair.value = cbor_build_uint8(42); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)ANDROID_SAFETYNET_CERT_FAKE; key_data.size = o_strlen(ANDROID_SAFETYNET_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); ck_assert_int_eq(o_base64_encode(cert_der, cert_der_len, NULL, &cert_der_enc_len), 1); cert_der_enc = o_malloc(cert_der_enc_len+1); ck_assert_int_eq(o_base64_encode(cert_der, cert_der_len, cert_der_enc, &cert_der_enc_len), 1); ck_assert_int_eq(r_jwt_init(&jwt_response), RHN_OK); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_response, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_response, R_FORMAT_PEM, (unsigned char *)ANDROID_SAFETYNET_KEY_FAKE, o_strlen(ANDROID_SAFETYNET_KEY_FAKE), NULL, 0), RHN_OK); j_grant = json_pack("[s]", cert_der_enc); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_response, "x5c", j_grant), RHN_OK); json_decref(j_grant); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); memcpy(nonce, auth_data, auth_data_len); nonce_len = NONCE_SIZE-auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, nonce+auth_data_len, &nonce_len), GNUTLS_E_SUCCESS); nonce_len += auth_data_len; key_data.data = nonce; key_data.size = nonce_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, nonce_hash, &nonce_hash_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64_encode(nonce_hash, nonce_hash_len, nonce_hash_enc, &nonce_hash_enc_len), 1); j_grant = json_pack("{sssisssssosos[s]}", "nonce", nonce_hash_enc, "timestampMs", time(NULL)*1000, "apkPackageName", "com.google.android.gms", "apkDigestSha256", "cGxlYXNlZG9udGRlY29kZW1laW1ub3RhcmVhbGhhc2gK", "ctsProfileMatch", json_true(), "basicIntegrity", json_true(), "apkCertificateDigestSha256", "cGxlYXNlZG9udGRlY29kZW1lZWl0aGVyaXRzZmFrZSEK"); ck_assert_int_eq(r_jwt_set_full_claims_json_t(jwt_response, j_grant), RHN_OK); json_decref(j_grant); ck_assert_ptr_ne((str_response = r_jwt_serialize_signed(jwt_response, NULL, 0)), NULL); cose_pair.key = cbor_build_string("response"); cose_pair.value = cbor_build_bytestring((unsigned char *)str_response, o_strlen(str_response)); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("android-safetynet"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("version invalid"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); o_free(str_response); o_free(cert_der_enc); r_jwt_free(jwt_response); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_safetynet_cert) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], * att_obj_ser = NULL, * att_obj_ser_enc, nonce[NONCE_SIZE], nonce_hash[32], nonce_hash_enc[64], * cert_der_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, att_obj_ser_len, att_obj_ser_enc_len, nonce_len, nonce_hash_len = 32, nonce_hash_enc_len, cert_der_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json, * str_response; gnutls_datum_t key_data, key_x, key_y; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj; struct cbor_pair cose_pair; jwt_t * jwt_response; json_t * j_grant; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); cose_pair.key = cbor_build_string("ver"); cose_pair.value = cbor_build_string("14366018"); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)ANDROID_SAFETYNET_CERT_FAKE; key_data.size = o_strlen(ANDROID_SAFETYNET_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); ck_assert_int_eq(o_base64_encode(cert_der, cert_der_len, NULL, &cert_der_enc_len), 1); cert_der_enc = o_malloc(cert_der_enc_len+1); ck_assert_int_eq(o_base64_encode(cert_der, cert_der_len, cert_der_enc, &cert_der_enc_len), 1); cert_der_enc[0]++; ck_assert_int_eq(r_jwt_init(&jwt_response), RHN_OK); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_response, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_response, R_FORMAT_PEM, (unsigned char *)ANDROID_SAFETYNET_KEY_FAKE, o_strlen(ANDROID_SAFETYNET_KEY_FAKE), NULL, 0), RHN_OK); j_grant = json_pack("[s]", cert_der_enc); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_response, "x5c", j_grant), RHN_OK); json_decref(j_grant); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); memcpy(nonce, auth_data, auth_data_len); nonce_len = NONCE_SIZE-auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, nonce+auth_data_len, &nonce_len), GNUTLS_E_SUCCESS); nonce_len += auth_data_len; key_data.data = nonce; key_data.size = nonce_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, nonce_hash, &nonce_hash_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64_encode(nonce_hash, nonce_hash_len, nonce_hash_enc, &nonce_hash_enc_len), 1); j_grant = json_pack("{sssisssssosos[s]}", "nonce", nonce_hash_enc, "timestampMs", time(NULL)*1000, "apkPackageName", "com.google.android.gms", "apkDigestSha256", "cGxlYXNlZG9udGRlY29kZW1laW1ub3RhcmVhbGhhc2gK", "ctsProfileMatch", json_true(), "basicIntegrity", json_true(), "apkCertificateDigestSha256", "cGxlYXNlZG9udGRlY29kZW1lZWl0aGVyaXRzZmFrZSEK"); ck_assert_int_eq(r_jwt_set_full_claims_json_t(jwt_response, j_grant), RHN_OK); json_decref(j_grant); ck_assert_ptr_ne((str_response = r_jwt_serialize_signed(jwt_response, NULL, 0)), NULL); cose_pair.key = cbor_build_string("response"); cose_pair.value = cbor_build_bytestring((unsigned char *)str_response, o_strlen(str_response)); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("android-safetynet"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("response invalid"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); o_free(str_response); o_free(cert_der_enc); r_jwt_free(jwt_response); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_safetynet_cert_missing) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], * att_obj_ser = NULL, * att_obj_ser_enc, nonce[NONCE_SIZE], nonce_hash[32], nonce_hash_enc[64], * cert_der_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, att_obj_ser_len, att_obj_ser_enc_len, nonce_len, nonce_hash_len = 32, nonce_hash_enc_len, cert_der_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json, * str_response; gnutls_datum_t key_data, key_x, key_y; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj; struct cbor_pair cose_pair; jwt_t * jwt_response; json_t * j_grant; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); cose_pair.key = cbor_build_string("ver"); cose_pair.value = cbor_build_string("14366018"); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)ANDROID_SAFETYNET_CERT_FAKE; key_data.size = o_strlen(ANDROID_SAFETYNET_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); ck_assert_int_eq(o_base64_encode(cert_der, cert_der_len, NULL, &cert_der_enc_len), 1); cert_der_enc = o_malloc(cert_der_enc_len+1); ck_assert_int_eq(o_base64_encode(cert_der, cert_der_len, cert_der_enc, &cert_der_enc_len), 1); ck_assert_int_eq(r_jwt_init(&jwt_response), RHN_OK); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_response, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_response, R_FORMAT_PEM, (unsigned char *)ANDROID_SAFETYNET_KEY_FAKE, o_strlen(ANDROID_SAFETYNET_KEY_FAKE), NULL, 0), RHN_OK); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); memcpy(nonce, auth_data, auth_data_len); nonce_len = NONCE_SIZE-auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, nonce+auth_data_len, &nonce_len), GNUTLS_E_SUCCESS); nonce_len += auth_data_len; key_data.data = nonce; key_data.size = nonce_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, nonce_hash, &nonce_hash_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64_encode(nonce_hash, nonce_hash_len, nonce_hash_enc, &nonce_hash_enc_len), 1); j_grant = json_pack("{sssisssssosos[s]}", "nonce", nonce_hash_enc, "timestampMs", time(NULL)*1000, "apkPackageName", "com.google.android.gms", "apkDigestSha256", "cGxlYXNlZG9udGRlY29kZW1laW1ub3RhcmVhbGhhc2gK", "ctsProfileMatch", json_true(), "basicIntegrity", json_true(), "apkCertificateDigestSha256", "cGxlYXNlZG9udGRlY29kZW1lZWl0aGVyaXRzZmFrZSEK"); ck_assert_int_eq(r_jwt_set_full_claims_json_t(jwt_response, j_grant), RHN_OK); json_decref(j_grant); ck_assert_ptr_ne((str_response = r_jwt_serialize_signed(jwt_response, NULL, 0)), NULL); cose_pair.key = cbor_build_string("response"); cose_pair.value = cbor_build_bytestring((unsigned char *)str_response, o_strlen(str_response)); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("android-safetynet"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("response invalid"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); o_free(str_response); o_free(cert_der_enc); r_jwt_free(jwt_response); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_safetynet_nonce_invalid) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], * att_obj_ser = NULL, * att_obj_ser_enc, nonce[NONCE_SIZE], nonce_hash[32], nonce_hash_enc[64], * cert_der_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, att_obj_ser_len, att_obj_ser_enc_len, nonce_len, nonce_hash_len = 32, nonce_hash_enc_len, cert_der_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json, * str_response; gnutls_datum_t key_data, key_x, key_y; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj; struct cbor_pair cose_pair; jwt_t * jwt_response; json_t * j_grant; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); cose_pair.key = cbor_build_string("ver"); cose_pair.value = cbor_build_string("14366018"); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)ANDROID_SAFETYNET_CERT_FAKE; key_data.size = o_strlen(ANDROID_SAFETYNET_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); ck_assert_int_eq(o_base64_encode(cert_der, cert_der_len, NULL, &cert_der_enc_len), 1); cert_der_enc = o_malloc(cert_der_enc_len+1); ck_assert_int_eq(o_base64_encode(cert_der, cert_der_len, cert_der_enc, &cert_der_enc_len), 1); ck_assert_int_eq(r_jwt_init(&jwt_response), RHN_OK); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_response, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_response, R_FORMAT_PEM, (unsigned char *)ANDROID_SAFETYNET_KEY_FAKE, o_strlen(ANDROID_SAFETYNET_KEY_FAKE), NULL, 0), RHN_OK); j_grant = json_pack("[s]", cert_der_enc); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_response, "x5c", j_grant), RHN_OK); json_decref(j_grant); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); memcpy(nonce, auth_data, auth_data_len); nonce_len = NONCE_SIZE-auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, nonce+auth_data_len, &nonce_len), GNUTLS_E_SUCCESS); nonce_len += auth_data_len; key_data.data = nonce; key_data.size = nonce_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, nonce_hash, &nonce_hash_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64_encode(nonce_hash, nonce_hash_len, nonce_hash_enc, &nonce_hash_enc_len), 1); nonce_hash_enc[0]++; j_grant = json_pack("{sssisssssosos[s]}", "nonce", nonce_hash_enc, "timestampMs", time(NULL)*1000, "apkPackageName", "com.google.android.gms", "apkDigestSha256", "cGxlYXNlZG9udGRlY29kZW1laW1ub3RhcmVhbGhhc2gK", "ctsProfileMatch", json_true(), "basicIntegrity", json_true(), "apkCertificateDigestSha256", "cGxlYXNlZG9udGRlY29kZW1lZWl0aGVyaXRzZmFrZSEK"); ck_assert_int_eq(r_jwt_set_full_claims_json_t(jwt_response, j_grant), RHN_OK); json_decref(j_grant); ck_assert_ptr_ne((str_response = r_jwt_serialize_signed(jwt_response, NULL, 0)), NULL); cose_pair.key = cbor_build_string("response"); cose_pair.value = cbor_build_bytestring((unsigned char *)str_response, o_strlen(str_response)); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("android-safetynet"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("response invalid"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); o_free(str_response); o_free(cert_der_enc); r_jwt_free(jwt_response); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_safetynet_jws_invalid) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], * att_obj_ser = NULL, * att_obj_ser_enc, nonce[NONCE_SIZE], nonce_hash[32], nonce_hash_enc[64], * cert_der_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, att_obj_ser_len, att_obj_ser_enc_len, nonce_len, nonce_hash_len = 32, nonce_hash_enc_len, cert_der_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json, * str_response; gnutls_datum_t key_data, key_x, key_y; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj; struct cbor_pair cose_pair; jwt_t * jwt_response; json_t * j_grant; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); cose_pair.key = cbor_build_string("ver"); cose_pair.value = cbor_build_string("14366018"); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)ANDROID_SAFETYNET_CERT_FAKE; key_data.size = o_strlen(ANDROID_SAFETYNET_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); ck_assert_int_eq(o_base64_encode(cert_der, cert_der_len, NULL, &cert_der_enc_len), 1); cert_der_enc = o_malloc(cert_der_enc_len+1); ck_assert_int_eq(o_base64_encode(cert_der, cert_der_len, cert_der_enc, &cert_der_enc_len), 1); ck_assert_int_eq(r_jwt_init(&jwt_response), RHN_OK); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_response, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_response, R_FORMAT_PEM, (unsigned char *)ANDROID_SAFETYNET_KEY_FAKE, o_strlen(ANDROID_SAFETYNET_KEY_FAKE), NULL, 0), RHN_OK); j_grant = json_pack("[s]", cert_der_enc); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_response, "x5c", j_grant), RHN_OK); json_decref(j_grant); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); memcpy(nonce, auth_data, auth_data_len); nonce_len = NONCE_SIZE-auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, nonce+auth_data_len, &nonce_len), GNUTLS_E_SUCCESS); nonce_len += auth_data_len; key_data.data = nonce; key_data.size = nonce_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, nonce_hash, &nonce_hash_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64_encode(nonce_hash, nonce_hash_len, nonce_hash_enc, &nonce_hash_enc_len), 1); j_grant = json_pack("{sssisssssosos[s]}", "nonce", nonce_hash_enc, "timestampMs", time(NULL)*1000, "apkPackageName", "com.google.android.gms", "apkDigestSha256", "cGxlYXNlZG9udGRlY29kZW1laW1ub3RhcmVhbGhhc2gK", "ctsProfileMatch", json_true(), "basicIntegrity", json_true(), "apkCertificateDigestSha256", "cGxlYXNlZG9udGRlY29kZW1lZWl0aGVyaXRzZmFrZSEK"); ck_assert_int_eq(r_jwt_set_full_claims_json_t(jwt_response, j_grant), RHN_OK); json_decref(j_grant); ck_assert_ptr_ne((str_response = r_jwt_serialize_signed(jwt_response, NULL, 0)), NULL); str_response[0]++; cose_pair.key = cbor_build_string("response"); cose_pair.value = cbor_build_bytestring((unsigned char *)str_response, o_strlen(str_response)); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("android-safetynet"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("response invalid"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); o_free(str_response); o_free(cert_der_enc); r_jwt_free(jwt_response); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_safetynet_fmt_invalid_key) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], * att_obj_ser = NULL, * att_obj_ser_enc, nonce[NONCE_SIZE], nonce_hash[32], nonce_hash_enc[64], * cert_der_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, att_obj_ser_len, att_obj_ser_enc_len, nonce_len, nonce_hash_len = 32, nonce_hash_enc_len, cert_der_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json, * str_response; gnutls_datum_t key_data, key_x, key_y; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj; struct cbor_pair cose_pair; jwt_t * jwt_response; json_t * j_grant; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); cose_pair.key = cbor_build_string("ver"); cose_pair.value = cbor_build_string("14366018"); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)ANDROID_SAFETYNET_CERT_FAKE; key_data.size = o_strlen(ANDROID_SAFETYNET_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); ck_assert_int_eq(o_base64_encode(cert_der, cert_der_len, NULL, &cert_der_enc_len), 1); cert_der_enc = o_malloc(cert_der_enc_len+1); ck_assert_int_eq(o_base64_encode(cert_der, cert_der_len, cert_der_enc, &cert_der_enc_len), 1); ck_assert_int_eq(r_jwt_init(&jwt_response), RHN_OK); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_response, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_response, R_FORMAT_PEM, (unsigned char *)ANDROID_SAFETYNET_KEY_FAKE, o_strlen(ANDROID_SAFETYNET_KEY_FAKE), NULL, 0), RHN_OK); j_grant = json_pack("[s]", cert_der_enc); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_response, "x5c", j_grant), RHN_OK); json_decref(j_grant); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); memcpy(nonce, auth_data, auth_data_len); nonce_len = NONCE_SIZE-auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, nonce+auth_data_len, &nonce_len), GNUTLS_E_SUCCESS); nonce_len += auth_data_len; key_data.data = nonce; key_data.size = nonce_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, nonce_hash, &nonce_hash_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64_encode(nonce_hash, nonce_hash_len, nonce_hash_enc, &nonce_hash_enc_len), 1); j_grant = json_pack("{sssisssssosos[s]}", "nonce", nonce_hash_enc, "timestampMs", time(NULL)*1000, "apkPackageName", "com.google.android.gms", "apkDigestSha256", "cGxlYXNlZG9udGRlY29kZW1laW1ub3RhcmVhbGhhc2gK", "ctsProfileMatch", json_true(), "basicIntegrity", json_true(), "apkCertificateDigestSha256", "cGxlYXNlZG9udGRlY29kZW1lZWl0aGVyaXRzZmFrZSEK"); ck_assert_int_eq(r_jwt_set_full_claims_json_t(jwt_response, j_grant), RHN_OK); json_decref(j_grant); ck_assert_ptr_ne((str_response = r_jwt_serialize_signed(jwt_response, NULL, 0)), NULL); cose_pair.key = cbor_build_string("response"); cose_pair.value = cbor_build_bytestring((unsigned char *)str_response, o_strlen(str_response)); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("error"); cose_pair.value = cbor_build_string("android-safetynet"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("authData invalid"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); o_free(str_response); o_free(cert_der_enc); r_jwt_free(jwt_response); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_error_safetynet_jws_invalid_signature) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential, * j_error; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], * att_obj_ser = NULL, * att_obj_ser_enc, nonce[NONCE_SIZE], nonce_hash[32], nonce_hash_enc[64], * cert_der_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, att_obj_ser_len, att_obj_ser_enc_len, nonce_len, nonce_hash_len = 32, nonce_hash_enc_len, cert_der_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json, * str_response; gnutls_datum_t key_data, key_x, key_y; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj; struct cbor_pair cose_pair; jwt_t * jwt_response; json_t * j_grant; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); cose_pair.key = cbor_build_string("ver"); cose_pair.value = cbor_build_string("14366018"); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)ANDROID_SAFETYNET_CERT_FAKE; key_data.size = o_strlen(ANDROID_SAFETYNET_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); ck_assert_int_eq(o_base64_encode(cert_der, cert_der_len, NULL, &cert_der_enc_len), 1); cert_der_enc = o_malloc(cert_der_enc_len+1); ck_assert_int_eq(o_base64_encode(cert_der, cert_der_len, cert_der_enc, &cert_der_enc_len), 1); ck_assert_int_eq(r_jwt_init(&jwt_response), RHN_OK); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_response, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_response, R_FORMAT_PEM, (unsigned char *)ANDROID_SAFETYNET_KEY_FAKE, o_strlen(ANDROID_SAFETYNET_KEY_FAKE), NULL, 0), RHN_OK); j_grant = json_pack("[s]", cert_der_enc); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_response, "x5c", j_grant), RHN_OK); json_decref(j_grant); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); memcpy(nonce, auth_data, auth_data_len); nonce_len = NONCE_SIZE-auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, nonce+auth_data_len, &nonce_len), GNUTLS_E_SUCCESS); nonce_len += auth_data_len; key_data.data = nonce; key_data.size = nonce_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, nonce_hash, &nonce_hash_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64_encode(nonce_hash, nonce_hash_len, nonce_hash_enc, &nonce_hash_enc_len), 1); j_grant = json_pack("{sssisssssosos[s]}", "nonce", nonce_hash_enc, "timestampMs", time(NULL)*1000, "apkPackageName", "com.google.android.gms", "apkDigestSha256", "cGxlYXNlZG9udGRlY29kZW1laW1ub3RhcmVhbGhhc2gK", "ctsProfileMatch", json_true(), "basicIntegrity", json_true(), "apkCertificateDigestSha256", "cGxlYXNlZG9udGRlY29kZW1lZWl0aGVyaXRzZmFrZSEK"); ck_assert_int_eq(r_jwt_set_full_claims_json_t(jwt_response, j_grant), RHN_OK); json_decref(j_grant); ck_assert_ptr_ne((str_response = r_jwt_serialize_signed(jwt_response, NULL, 0)), NULL); str_response[o_strlen(str_response)-3]++; cose_pair.key = cbor_build_string("response"); cose_pair.value = cbor_build_bytestring((unsigned char *)str_response, o_strlen(str_response)); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("android-safetynet"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); j_error = json_string("Invalid signature"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, j_error, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); json_decref(j_error); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); o_free(str_response); o_free(cert_der_enc); r_jwt_free(jwt_response); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_safetynet_success) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[1024], * att_obj_ser = NULL, * att_obj_ser_enc, nonce[NONCE_SIZE], nonce_hash[32], nonce_hash_enc[64], * cert_der_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 1024, att_obj_ser_len, att_obj_ser_enc_len, nonce_len, nonce_hash_len = 32, nonce_hash_enc_len, cert_der_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json, * str_response; gnutls_datum_t key_data, key_x, key_y; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj; struct cbor_pair cose_pair; jwt_t * jwt_response; json_t * j_grant; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); cose_pair.key = cbor_build_string("ver"); cose_pair.value = cbor_build_string("14366018"); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)ANDROID_SAFETYNET_CERT_FAKE; key_data.size = o_strlen(ANDROID_SAFETYNET_CERT_FAKE); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); ck_assert_int_eq(o_base64_encode(cert_der, cert_der_len, NULL, &cert_der_enc_len), 1); cert_der_enc = o_malloc(cert_der_enc_len+1); ck_assert_int_eq(o_base64_encode(cert_der, cert_der_len, cert_der_enc, &cert_der_enc_len), 1); ck_assert_int_eq(r_jwt_init(&jwt_response), RHN_OK); ck_assert_int_eq(r_jwt_set_sign_alg(jwt_response, R_JWA_ALG_RS256), RHN_OK); ck_assert_int_eq(r_jwt_add_sign_keys_pem_der(jwt_response, R_FORMAT_PEM, (unsigned char *)ANDROID_SAFETYNET_KEY_FAKE, o_strlen(ANDROID_SAFETYNET_KEY_FAKE), NULL, 0), RHN_OK); j_grant = json_pack("[s]", cert_der_enc); ck_assert_int_eq(r_jwt_set_header_json_t_value(jwt_response, "x5c", j_grant), RHN_OK); json_decref(j_grant); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); memcpy(nonce, auth_data, auth_data_len); nonce_len = NONCE_SIZE-auth_data_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, nonce+auth_data_len, &nonce_len), GNUTLS_E_SUCCESS); nonce_len += auth_data_len; key_data.data = nonce; key_data.size = nonce_len; ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, nonce_hash, &nonce_hash_len), GNUTLS_E_SUCCESS); ck_assert_int_eq(o_base64_encode(nonce_hash, nonce_hash_len, nonce_hash_enc, &nonce_hash_enc_len), 1); j_grant = json_pack("{sssisssssosos[s]}", "nonce", nonce_hash_enc, "timestampMs", time(NULL)*1000, "apkPackageName", "com.google.android.gms", "apkDigestSha256", "cGxlYXNlZG9udGRlY29kZW1laW1ub3RhcmVhbGhhc2gK", "ctsProfileMatch", json_true(), "basicIntegrity", json_true(), "apkCertificateDigestSha256", "cGxlYXNlZG9udGRlY29kZW1lZWl0aGVyaXRzZmFrZSEK"); ck_assert_int_eq(r_jwt_set_full_claims_json_t(jwt_response, j_grant), RHN_OK); json_decref(j_grant); ck_assert_ptr_ne((str_response = r_jwt_serialize_signed(jwt_response, NULL, 0)), NULL); cose_pair.key = cbor_build_string("response"); cose_pair.value = cbor_build_bytestring((unsigned char *)str_response, o_strlen(str_response)); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("android-safetynet"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 200, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(cert_der_enc); o_free(att_obj_ser); o_free(str_response); r_jwt_free(jwt_response); cbor_decref(&att_obj); cbor_decref(&att_stmt); cbor_decref(&cbor_cose); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_packed_x5c_success) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[16*1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 16*1024, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PRIVATE_KEY_VALID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("alg"); cose_pair.value = cbor_build_uint8(WEBAUTHN_PUBKEY_CRED_ECDSA_256_CBOR); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PUBLIC_CERT_VALID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_obj = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("packed"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 200, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_obj); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_packed_x5c_invalid_signature) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[16*1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 16*1024, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PRIVATE_KEY_VALID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("alg"); cose_pair.value = cbor_build_uint8(WEBAUTHN_PUBKEY_CRED_ECDSA_256_CBOR); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PUBLIC_CERT_VALID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_obj = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; key_data.data[0]++; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("packed"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_obj); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_packed_x5c_invalid_algorithm) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[16*1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 16*1024, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PRIVATE_KEY_VALID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("alg"); cose_pair.value = cbor_build_uint8(WEBAUTHN_PUBKEY_CRED_ECDSA_384_CBOR); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PUBLIC_CERT_VALID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_obj = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("packed"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_obj); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_packed_x5c_invalid_cert) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[16*1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 16*1024, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PRIVATE_KEY_VALID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("alg"); cose_pair.value = cbor_build_uint8(WEBAUTHN_PUBKEY_CRED_ECDSA_256_CBOR); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PUBLIC_CERT_VALID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); cert_der[0]++; bs_obj = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("packed"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_obj); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_packed_x5c_no_algorithm) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[16*1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 16*1024, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PRIVATE_KEY_VALID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PUBLIC_CERT_VALID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_obj = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("packed"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_obj); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_packed_x5c_no_signature) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[16*1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 16*1024, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PRIVATE_KEY_VALID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); cose_pair.key = cbor_build_string("alg"); cose_pair.value = cbor_build_uint8(WEBAUTHN_PUBKEY_CRED_ECDSA_256_CBOR); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PUBLIC_CERT_VALID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_obj = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("packed"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_obj); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_packed_self_signed_success) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)CREDENTIAL_PRIVATE_KEY; key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); cose_pair.key = cbor_build_string("alg"); cose_pair.value = cbor_build_uint8(WEBAUTHN_PUBKEY_CRED_ECDSA_256_CBOR); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("packed"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 200, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_packed_self_signed_invalid_signature) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)CREDENTIAL_PRIVATE_KEY; key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); cose_pair.key = cbor_build_string("alg"); cose_pair.value = cbor_build_uint8(WEBAUTHN_PUBKEY_CRED_ECDSA_256_CBOR); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); signature.data[0]++; cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("packed"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_packed_self_signed_invalid_pubkey) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY_2; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY_2); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)CREDENTIAL_PRIVATE_KEY; key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(2); cose_pair.key = cbor_build_string("alg"); cose_pair.value = cbor_build_uint8(WEBAUTHN_PUBKEY_CRED_ECDSA_256_CBOR); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("packed"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_module_add_with_ca) { json_t * j_parameters = json_pack("{sssssssisis{sosssisisisss[iii]sisisssss{sososososososo}s[s]}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "session-mandatory", WEBAUTHN_SESSION_MANDATORY, "seed", WEBAUTHN_SEED, "challenge-length", WEBAUTHN_CHALLENGE_LEN, "credential-expiration", WEBAUTHN_CREDENTIAL_EXPIRATION, "credential-assertion", WEBAUTHN_CREDENTIAL_ASSERTION, "rp-origin", WEBAUTHN_RP_ORIGIN, "pubKey-cred-params", WEBAUTHN_PUBKEY_CRED_ECDSA_256, WEBAUTHN_PUBKEY_CRED_ECDSA_384, WEBAUTHN_PUBKEY_CRED_ECDSA_512, "ctsProfileMatch", WEBAUTHN_CTS_PROFILE_MATCH, "basicIntegrity", WEBAUTHN_BASIC_INTEGRITY, "google-root-ca-r2", WEBAUTHN_GOOGLE_ROOT_CA_R2, "apple-root-ca", "../test/" CREDENTIAL_APPLE_CA_CERT_PATH, "fmt", "packed", json_true(), "tpm", json_true(), "android-key", json_true(), "android-safetynet", json_true(), "fido-u2f", json_true(), "apple", json_true(), "none", json_true(), "root-ca-list", "../test/" CREDENTIAL_PACKED_CA_CERT_PATH); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_module_add_with_ca_2) { json_t * j_parameters = json_pack("{sssssssisis{sosssisisisss[iii]sisisssss{sososososososo}s[s]}}", "module", MODULE_MODULE, "name", MODULE_NAME, "display_name", MODULE_DISPLAY_NAME, "expiration", MODULE_EXPIRATION, "max_use", MODULE_MAX_USE, "parameters", "session-mandatory", WEBAUTHN_SESSION_MANDATORY, "seed", WEBAUTHN_SEED, "challenge-length", WEBAUTHN_CHALLENGE_LEN, "credential-expiration", WEBAUTHN_CREDENTIAL_EXPIRATION, "credential-assertion", WEBAUTHN_CREDENTIAL_ASSERTION, "rp-origin", WEBAUTHN_RP_ORIGIN, "pubKey-cred-params", WEBAUTHN_PUBKEY_CRED_ECDSA_256, WEBAUTHN_PUBKEY_CRED_ECDSA_384, WEBAUTHN_PUBKEY_CRED_ECDSA_512, "ctsProfileMatch", WEBAUTHN_CTS_PROFILE_MATCH, "basicIntegrity", WEBAUTHN_BASIC_INTEGRITY, "google-root-ca-r2", WEBAUTHN_GOOGLE_ROOT_CA_R2, "apple-root-ca", "../test/" CREDENTIAL_APPLE_CA_CERT_PATH, "fmt", "packed", json_true(), "tpm", json_true(), "android-key", json_true(), "android-safetynet", json_true(), "fido-u2f", json_true(), "apple", json_true(), "none", json_true(), "root-ca-list", "../test/" CREDENTIAL_PACKED_CA_2_CERT_PATH); ck_assert_int_eq(run_simple_test(&admin_req, "POST", SERVER_URI "/mod/scheme/", NULL, NULL, j_parameters, NULL, 200, NULL, NULL, NULL), 1); ck_assert_int_eq(run_simple_test(&admin_req, "GET", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, j_parameters, NULL, NULL), 1); json_decref(j_parameters); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_packed_x5c_unregistered_ca) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[16*1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 16*1024, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PRIVATE_KEY_VALID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("alg"); cose_pair.value = cbor_build_uint8(WEBAUTHN_PUBKEY_CRED_ECDSA_256_CBOR); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PUBLIC_CERT_VALID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_obj = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("packed"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_obj); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_packed_x5c_invalid_ui) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[16*1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 16*1024, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PRIVATE_KEY_INVALID_UNIT_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("alg"); cose_pair.value = cbor_build_uint8(WEBAUTHN_PUBKEY_CRED_ECDSA_256_CBOR); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PUBLIC_CERT_INVALID_UNIT_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_obj = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("packed"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_obj); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_packed_x5c_invalid_c) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[16*1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 16*1024, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PRIVATE_KEY_INVALID_COUNTRY_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("alg"); cose_pair.value = cbor_build_uint8(WEBAUTHN_PUBKEY_CRED_ECDSA_256_CBOR); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PUBLIC_CERT_INVALID_COUNTRY_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_obj = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("packed"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_obj); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_packed_x5c_missing_c) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[16*1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 16*1024, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PRIVATE_KEY_MISSING_COUNTRY_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("alg"); cose_pair.value = cbor_build_uint8(WEBAUTHN_PUBKEY_CRED_ECDSA_256_CBOR); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PUBLIC_CERT_MISSING_COUNTRY_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_obj = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("packed"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_obj); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_packed_x5c_missing_o) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[16*1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 16*1024, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PRIVATE_KEY_MISSING_ORGANIZATION_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("alg"); cose_pair.value = cbor_build_uint8(WEBAUTHN_PUBKEY_CRED_ECDSA_256_CBOR); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PUBLIC_CERT_MISSING_ORGANIZATION_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_obj = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("packed"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_obj); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_packed_x5c_missing_cn) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[16*1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 16*1024, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PRIVATE_KEY_MISSING_COMMON_NAME_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("alg"); cose_pair.value = cbor_build_uint8(WEBAUTHN_PUBKEY_CRED_ECDSA_256_CBOR); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PUBLIC_CERT_MISSING_COMMON_NAME_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_obj = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("packed"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_obj); } END_TEST // This test fails with old GnuTLS version becuase the generated certificate doesn't have the required extension #if GNUTLS_VERSION_NUMBER >= 0x030503 START_TEST(test_glwd_scheme_webauthn_irl_register_packed_x5c_invalid_aaguid) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], cert_der[16*1024], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, cert_der_len = 16*1024, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PRIVATE_KEY_INVALID_AAGUID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("alg"); cose_pair.value = cbor_build_uint8(WEBAUTHN_PUBKEY_CRED_ECDSA_256_CBOR); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); ck_assert_int_eq(gnutls_x509_crt_init(&cert), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PUBLIC_CERT_INVALID_AAGUID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_ge(gnutls_x509_crt_import(cert, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, cert_der, &cert_der_len), 0); cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(1); bs_obj = cbor_build_bytestring(cert_der, cert_der_len); cbor_array_set(cose_pair.value, 0, bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); cose_pair.key = cbor_build_string("sig"); cose_pair.value = cbor_build_bytestring(signature.data, signature.size); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("packed"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, NULL, NULL, NULL), 1); gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); cbor_decref(&bs_obj); } END_TEST #endif START_TEST(test_glwd_scheme_webauthn_irl_register_apple_success) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID_APPLE, pubkey_id[128], cbor_cose_dump[512], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL, nonce_base[AUTH_DATA_SIZE+32], expected_nonce[38]; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, expected_nonce_len = 32; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; gnutls_x509_crt_t crt = NULL; gnutls_x509_privkey_t key_issuer = NULL; gnutls_x509_crt_t crt_issuer = NULL; gnutls_datum_t dat, crt_dem, int_dem; int serial = 42; unsigned char expected_nonce_prefix[6] = {0x30, 0x24, 0xa1, 0x22, 0x04, 0x20}; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PRIVATE_KEY_VALID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); // Let's build attStmt att_stmt = cbor_new_definite_map(1); // Let's create leaf certificate using CREDENTIAL_APPLE_INT_KEY_PATH ck_assert_int_ge(gnutls_x509_crt_init(&crt), 0); ck_assert_int_ge(gnutls_x509_privkey_init(&key_issuer), 0); ck_assert_int_ge(gnutls_x509_crt_init(&crt_issuer), 0); // Import key_issuer dat.data = (unsigned char *)get_file_content(CREDENTIAL_APPLE_INT_KEY_PATH); dat.size = o_strlen((const char *)dat.data); ck_assert_int_ge(gnutls_x509_privkey_import(key_issuer, &dat, GNUTLS_X509_FMT_PEM), 0); gnutls_free(dat.data); // Import crt_issuer dat.data = (unsigned char *)get_file_content(CREDENTIAL_APPLE_INT_CERT_PATH); dat.size = o_strlen((const char *)dat.data); ck_assert_int_ge(gnutls_x509_crt_import(crt_issuer, &dat, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_ge(gnutls_x509_crt_export2(crt_issuer, GNUTLS_X509_FMT_DER, &int_dem), 0); gnutls_free(dat.data); // Generate expected_nonce memcpy(nonce_base, auth_data, auth_data_len); memcpy(nonce_base+auth_data_len, client_data_hash, 32); dat.data = nonce_base; dat.size = auth_data_len+32; memcpy(expected_nonce, expected_nonce_prefix, sizeof(expected_nonce_prefix)); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &dat, expected_nonce+sizeof(expected_nonce_prefix), &expected_nonce_len), GNUTLS_E_SUCCESS); // Generate crt signed by issuer ck_assert_int_ge(gnutls_x509_crt_set_version(crt, 3), 0); ck_assert_int_ge(gnutls_x509_crt_set_pubkey(crt, pubkey), 0); ck_assert_int_ge(gnutls_x509_crt_set_dn(crt, G_APPLE_CERT_LEAF_DN, NULL), 0); ck_assert_int_ge(gnutls_x509_crt_set_expiration_time(crt, time(NULL)+(60*60*24*512)), 0); ck_assert_int_ge(gnutls_x509_crt_set_activation_time(crt, time(NULL)), 0); ck_assert_int_ge(gnutls_x509_crt_set_serial(crt, &serial, sizeof(int)), 0); ck_assert_int_ge(gnutls_x509_crt_set_extension_by_oid(crt, G_APPLE_ANONYMOUS_ATTESTATION_OID, expected_nonce, expected_nonce_len+sizeof(expected_nonce_prefix), 0), 0); ck_assert_int_ge(gnutls_x509_crt_sign2(crt, crt_issuer, key_issuer, GNUTLS_DIG_SHA256, 0), 0); ck_assert_int_ge(gnutls_x509_crt_export2(crt, GNUTLS_X509_FMT_DER, &crt_dem), 0); // Set x5c elements cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(2); bs_obj = cbor_build_bytestring(crt_dem.data, crt_dem.size); cbor_array_set(cose_pair.value, 0, bs_obj); cbor_decref(&bs_obj); bs_obj = cbor_build_bytestring(int_dem.data, int_dem.size); cbor_array_set(cose_pair.value, 1, bs_obj); cbor_decref(&bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("apple"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+4); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 200, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_x509_privkey_deinit(key_issuer); gnutls_x509_crt_deinit(crt_issuer); gnutls_x509_crt_deinit(crt); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); gnutls_free(crt_dem.data); gnutls_free(int_dem.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_apple_intermediate_2_success) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID_APPLE, pubkey_id[128], cbor_cose_dump[512], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL, nonce_base[AUTH_DATA_SIZE+32], expected_nonce[38]; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, expected_nonce_len = 32; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; gnutls_x509_crt_t crt = NULL; gnutls_x509_privkey_t key_issuer = NULL; gnutls_x509_crt_t crt_issuer = NULL; gnutls_datum_t dat, crt_dem, int_dem, int2_dem; int serial = 42; unsigned char expected_nonce_prefix[6] = {0x30, 0x24, 0xa1, 0x22, 0x04, 0x20}; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PRIVATE_KEY_VALID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); // Let's build attStmt att_stmt = cbor_new_definite_map(1); // Let's create leaf certificate using CREDENTIAL_APPLE_INT_KEY_PATH ck_assert_int_ge(gnutls_x509_crt_init(&crt), 0); ck_assert_int_ge(gnutls_x509_privkey_init(&key_issuer), 0); ck_assert_int_ge(gnutls_x509_crt_init(&crt_issuer), 0); // Import key_issuer dat.data = (unsigned char *)get_file_content(CREDENTIAL_APPLE_INT2_KEY_PATH); dat.size = o_strlen((const char *)dat.data); ck_assert_int_ge(gnutls_x509_privkey_import(key_issuer, &dat, GNUTLS_X509_FMT_PEM), 0); gnutls_free(dat.data); // Import crt_issuer dat.data = (unsigned char *)get_file_content(CREDENTIAL_APPLE_INT2_CERT_PATH); dat.size = o_strlen((const char *)dat.data); ck_assert_int_ge(gnutls_x509_crt_import(crt_issuer, &dat, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_ge(gnutls_x509_crt_export2(crt_issuer, GNUTLS_X509_FMT_DER, &int_dem), 0); gnutls_free(dat.data); // Import crt_2_issuer dat.data = (unsigned char *)get_file_content(CREDENTIAL_APPLE_INT_CERT_PATH); dat.size = o_strlen((const char *)dat.data); ck_assert_int_ge(gnutls_x509_crt_import(crt_issuer, &dat, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_ge(gnutls_x509_crt_export2(crt_issuer, GNUTLS_X509_FMT_DER, &int2_dem), 0); gnutls_free(dat.data); // Generate expected_nonce memcpy(nonce_base, auth_data, auth_data_len); memcpy(nonce_base+auth_data_len, client_data_hash, 32); dat.data = nonce_base; dat.size = auth_data_len+32; memcpy(expected_nonce, expected_nonce_prefix, sizeof(expected_nonce_prefix)); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &dat, expected_nonce+sizeof(expected_nonce_prefix), &expected_nonce_len), GNUTLS_E_SUCCESS); // Generate crt signed by issuer ck_assert_int_ge(gnutls_x509_crt_set_version(crt, 3), 0); ck_assert_int_ge(gnutls_x509_crt_set_pubkey(crt, pubkey), 0); ck_assert_int_ge(gnutls_x509_crt_set_dn(crt, G_APPLE_CERT_LEAF_DN, NULL), 0); ck_assert_int_ge(gnutls_x509_crt_set_expiration_time(crt, time(NULL)+(60*60*24*512)), 0); ck_assert_int_ge(gnutls_x509_crt_set_activation_time(crt, time(NULL)), 0); ck_assert_int_ge(gnutls_x509_crt_set_serial(crt, &serial, sizeof(int)), 0); ck_assert_int_ge(gnutls_x509_crt_set_extension_by_oid(crt, G_APPLE_ANONYMOUS_ATTESTATION_OID, expected_nonce, expected_nonce_len+sizeof(expected_nonce_prefix), 0), 0); ck_assert_int_ge(gnutls_x509_crt_sign2(crt, crt_issuer, key_issuer, GNUTLS_DIG_SHA256, 0), 0); ck_assert_int_ge(gnutls_x509_crt_export2(crt, GNUTLS_X509_FMT_DER, &crt_dem), 0); // Set x5c elements cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(3); bs_obj = cbor_build_bytestring(crt_dem.data, crt_dem.size); cbor_array_set(cose_pair.value, 0, bs_obj); cbor_decref(&bs_obj); bs_obj = cbor_build_bytestring(int_dem.data, int_dem.size); cbor_array_set(cose_pair.value, 1, bs_obj); cbor_decref(&bs_obj); bs_obj = cbor_build_bytestring(int2_dem.data, int2_dem.size); cbor_array_set(cose_pair.value, 2, bs_obj); cbor_decref(&bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("apple"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+4); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 200, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_x509_privkey_deinit(key_issuer); gnutls_x509_crt_deinit(crt_issuer); gnutls_x509_crt_deinit(crt); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); gnutls_free(crt_dem.data); gnutls_free(int_dem.data); gnutls_free(int2_dem.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_apple_invalid_aaguid) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID_APPLE, pubkey_id[128], cbor_cose_dump[512], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL, nonce_base[AUTH_DATA_SIZE+32], expected_nonce[38]; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, expected_nonce_len = 32; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; gnutls_x509_crt_t crt = NULL; gnutls_x509_privkey_t key_issuer = NULL; gnutls_x509_crt_t crt_issuer = NULL; gnutls_datum_t dat, crt_dem, int_dem; int serial = 42; unsigned char expected_nonce_prefix[6] = {0x30, 0x24, 0xa1, 0x22, 0x04, 0x20}; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid aaguid[0] = 42; memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PRIVATE_KEY_VALID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); // Let's build attStmt att_stmt = cbor_new_definite_map(1); // Let's create leaf certificate using CREDENTIAL_APPLE_INT_KEY_PATH ck_assert_int_ge(gnutls_x509_crt_init(&crt), 0); ck_assert_int_ge(gnutls_x509_privkey_init(&key_issuer), 0); ck_assert_int_ge(gnutls_x509_crt_init(&crt_issuer), 0); // Import key_issuer dat.data = (unsigned char *)get_file_content(CREDENTIAL_APPLE_INT_KEY_PATH); dat.size = o_strlen((const char *)dat.data); ck_assert_int_ge(gnutls_x509_privkey_import(key_issuer, &dat, GNUTLS_X509_FMT_PEM), 0); gnutls_free(dat.data); // Import crt_issuer dat.data = (unsigned char *)get_file_content(CREDENTIAL_APPLE_INT_CERT_PATH); dat.size = o_strlen((const char *)dat.data); ck_assert_int_ge(gnutls_x509_crt_import(crt_issuer, &dat, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_ge(gnutls_x509_crt_export2(crt_issuer, GNUTLS_X509_FMT_DER, &int_dem), 0); gnutls_free(dat.data); // Generate expected_nonce memcpy(nonce_base, auth_data, auth_data_len); memcpy(nonce_base+auth_data_len, client_data_hash, 32); dat.data = nonce_base; dat.size = auth_data_len+32; memcpy(expected_nonce, expected_nonce_prefix, sizeof(expected_nonce_prefix)); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &dat, expected_nonce+sizeof(expected_nonce_prefix), &expected_nonce_len), GNUTLS_E_SUCCESS); // Generate crt signed by issuer ck_assert_int_ge(gnutls_x509_crt_set_version(crt, 3), 0); ck_assert_int_ge(gnutls_x509_crt_set_pubkey(crt, pubkey), 0); ck_assert_int_ge(gnutls_x509_crt_set_dn(crt, G_APPLE_CERT_LEAF_DN, NULL), 0); ck_assert_int_ge(gnutls_x509_crt_set_expiration_time(crt, time(NULL)+(60*60*24*512)), 0); ck_assert_int_ge(gnutls_x509_crt_set_activation_time(crt, time(NULL)), 0); ck_assert_int_ge(gnutls_x509_crt_set_serial(crt, &serial, sizeof(int)), 0); ck_assert_int_ge(gnutls_x509_crt_set_extension_by_oid(crt, G_APPLE_ANONYMOUS_ATTESTATION_OID, expected_nonce, expected_nonce_len+sizeof(expected_nonce_prefix), 0), 0); ck_assert_int_ge(gnutls_x509_crt_sign2(crt, crt_issuer, key_issuer, GNUTLS_DIG_SHA256, 0), 0); ck_assert_int_ge(gnutls_x509_crt_export2(crt, GNUTLS_X509_FMT_DER, &crt_dem), 0); // Set x5c elements cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(2); bs_obj = cbor_build_bytestring(crt_dem.data, crt_dem.size); cbor_array_set(cose_pair.value, 0, bs_obj); cbor_decref(&bs_obj); bs_obj = cbor_build_bytestring(int_dem.data, int_dem.size); cbor_array_set(cose_pair.value, 1, bs_obj); cbor_decref(&bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("apple"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+4); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_x509_privkey_deinit(key_issuer); gnutls_x509_crt_deinit(crt_issuer); gnutls_x509_crt_deinit(crt); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); gnutls_free(crt_dem.data); gnutls_free(int_dem.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_apple_x5c_invalid_nonce) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID_APPLE, pubkey_id[128], cbor_cose_dump[512], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL, nonce_base[AUTH_DATA_SIZE+32], expected_nonce[38]; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, expected_nonce_len = 32; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; gnutls_x509_crt_t crt = NULL; gnutls_x509_privkey_t key_issuer = NULL; gnutls_x509_crt_t crt_issuer = NULL; gnutls_datum_t dat, crt_dem, int_dem; int serial = 42; unsigned char expected_nonce_prefix[6] = {0x30, 0x24, 0xa1, 0x22, 0x04, 0x20}; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PRIVATE_KEY_VALID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); // Let's build attStmt att_stmt = cbor_new_definite_map(1); // Let's create leaf certificate using CREDENTIAL_APPLE_INT_KEY_PATH ck_assert_int_ge(gnutls_x509_crt_init(&crt), 0); ck_assert_int_ge(gnutls_x509_privkey_init(&key_issuer), 0); ck_assert_int_ge(gnutls_x509_crt_init(&crt_issuer), 0); // Import key_issuer dat.data = (unsigned char *)get_file_content(CREDENTIAL_APPLE_INT_KEY_PATH); dat.size = o_strlen((const char *)dat.data); ck_assert_int_ge(gnutls_x509_privkey_import(key_issuer, &dat, GNUTLS_X509_FMT_PEM), 0); gnutls_free(dat.data); // Import crt_issuer dat.data = (unsigned char *)get_file_content(CREDENTIAL_APPLE_INT_CERT_PATH); dat.size = o_strlen((const char *)dat.data); ck_assert_int_ge(gnutls_x509_crt_import(crt_issuer, &dat, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_ge(gnutls_x509_crt_export2(crt_issuer, GNUTLS_X509_FMT_DER, &int_dem), 0); gnutls_free(dat.data); // Generate expected_nonce memcpy(nonce_base, auth_data, auth_data_len); memcpy(nonce_base+auth_data_len, client_data_hash, 32); dat.data = nonce_base; dat.size = auth_data_len+32; memcpy(expected_nonce, expected_nonce_prefix, sizeof(expected_nonce_prefix)); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &dat, expected_nonce+sizeof(expected_nonce_prefix), &expected_nonce_len), GNUTLS_E_SUCCESS); expected_nonce[8]++; // Generate crt signed by issuer ck_assert_int_ge(gnutls_x509_crt_set_version(crt, 3), 0); ck_assert_int_ge(gnutls_x509_crt_set_pubkey(crt, pubkey), 0); ck_assert_int_ge(gnutls_x509_crt_set_dn(crt, G_APPLE_CERT_LEAF_DN, NULL), 0); ck_assert_int_ge(gnutls_x509_crt_set_expiration_time(crt, time(NULL)+(60*60*24*512)), 0); ck_assert_int_ge(gnutls_x509_crt_set_activation_time(crt, time(NULL)), 0); ck_assert_int_ge(gnutls_x509_crt_set_serial(crt, &serial, sizeof(int)), 0); ck_assert_int_ge(gnutls_x509_crt_set_extension_by_oid(crt, G_APPLE_ANONYMOUS_ATTESTATION_OID, expected_nonce, expected_nonce_len+sizeof(expected_nonce_prefix), 0), 0); ck_assert_int_ge(gnutls_x509_crt_sign2(crt, crt_issuer, key_issuer, GNUTLS_DIG_SHA256, 0), 0); ck_assert_int_ge(gnutls_x509_crt_export2(crt, GNUTLS_X509_FMT_DER, &crt_dem), 0); // Set x5c elements cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(2); bs_obj = cbor_build_bytestring(crt_dem.data, crt_dem.size); cbor_array_set(cose_pair.value, 0, bs_obj); cbor_decref(&bs_obj); bs_obj = cbor_build_bytestring(int_dem.data, int_dem.size); cbor_array_set(cose_pair.value, 1, bs_obj); cbor_decref(&bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("apple"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+4); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_x509_privkey_deinit(key_issuer); gnutls_x509_crt_deinit(crt_issuer); gnutls_x509_crt_deinit(crt); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); gnutls_free(crt_dem.data); gnutls_free(int_dem.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_apple_x5c_invalid_pubkey) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID_APPLE, pubkey_id[128], cbor_cose_dump[512], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL, nonce_base[AUTH_DATA_SIZE+32], expected_nonce[38]; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, expected_nonce_len = 32; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; gnutls_x509_crt_t crt = NULL; gnutls_x509_privkey_t key_issuer = NULL; gnutls_x509_crt_t crt_issuer = NULL; gnutls_datum_t dat, crt_dem, int_dem; int serial = 42; unsigned char expected_nonce_prefix[6] = {0x30, 0x24, 0xa1, 0x22, 0x04, 0x20}; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PRIVATE_KEY_VALID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); // Let's build attStmt att_stmt = cbor_new_definite_map(1); // Let's create leaf certificate using CREDENTIAL_APPLE_INT_KEY_PATH ck_assert_int_ge(gnutls_x509_crt_init(&crt), 0); ck_assert_int_ge(gnutls_x509_privkey_init(&key_issuer), 0); ck_assert_int_ge(gnutls_x509_crt_init(&crt_issuer), 0); // Import key_issuer dat.data = (unsigned char *)get_file_content(CREDENTIAL_APPLE_INT_KEY_PATH); dat.size = o_strlen((const char *)dat.data); ck_assert_int_ge(gnutls_x509_privkey_import(key_issuer, &dat, GNUTLS_X509_FMT_PEM), 0); gnutls_free(dat.data); // Import crt_issuer dat.data = (unsigned char *)get_file_content(CREDENTIAL_APPLE_INT_CERT_PATH); dat.size = o_strlen((const char *)dat.data); ck_assert_int_ge(gnutls_x509_crt_import(crt_issuer, &dat, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_ge(gnutls_x509_crt_export2(crt_issuer, GNUTLS_X509_FMT_DER, &int_dem), 0); gnutls_free(dat.data); // Generate expected_nonce memcpy(nonce_base, auth_data, auth_data_len); memcpy(nonce_base+auth_data_len, client_data_hash, 32); dat.data = nonce_base; dat.size = auth_data_len+32; memcpy(expected_nonce, expected_nonce_prefix, sizeof(expected_nonce_prefix)); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &dat, expected_nonce+sizeof(expected_nonce_prefix), &expected_nonce_len), GNUTLS_E_SUCCESS); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY_2; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY_2); gnutls_pubkey_deinit(pubkey); ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); // Generate crt signed by issuer ck_assert_int_ge(gnutls_x509_crt_set_version(crt, 3), 0); ck_assert_int_ge(gnutls_x509_crt_set_pubkey(crt, pubkey), 0); ck_assert_int_ge(gnutls_x509_crt_set_dn(crt, G_APPLE_CERT_LEAF_DN, NULL), 0); ck_assert_int_ge(gnutls_x509_crt_set_expiration_time(crt, time(NULL)+(60*60*24*512)), 0); ck_assert_int_ge(gnutls_x509_crt_set_activation_time(crt, time(NULL)), 0); ck_assert_int_ge(gnutls_x509_crt_set_serial(crt, &serial, sizeof(int)), 0); ck_assert_int_ge(gnutls_x509_crt_set_extension_by_oid(crt, G_APPLE_ANONYMOUS_ATTESTATION_OID, expected_nonce, expected_nonce_len+sizeof(expected_nonce_prefix), 0), 0); ck_assert_int_ge(gnutls_x509_crt_sign2(crt, crt_issuer, key_issuer, GNUTLS_DIG_SHA256, 0), 0); ck_assert_int_ge(gnutls_x509_crt_export2(crt, GNUTLS_X509_FMT_DER, &crt_dem), 0); // Set x5c elements cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(2); bs_obj = cbor_build_bytestring(crt_dem.data, crt_dem.size); cbor_array_set(cose_pair.value, 0, bs_obj); cbor_decref(&bs_obj); bs_obj = cbor_build_bytestring(int_dem.data, int_dem.size); cbor_array_set(cose_pair.value, 1, bs_obj); cbor_decref(&bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("apple"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+4); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_x509_privkey_deinit(key_issuer); gnutls_x509_crt_deinit(crt_issuer); gnutls_x509_crt_deinit(crt); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); gnutls_free(crt_dem.data); gnutls_free(int_dem.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_apple_x5c_invalid_root_ca) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID_APPLE, pubkey_id[128], cbor_cose_dump[512], verification_data[256], client_data_hash[32], * att_obj_ser = NULL, * att_obj_ser_enc = NULL, nonce_base[AUTH_DATA_SIZE+32], expected_nonce[38]; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, client_data_hash_len = 32, att_obj_ser_len = 0, att_obj_ser_enc_len = 0, expected_nonce_len = 32; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y, signature; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj, * bs_obj; struct cbor_pair cose_pair; gnutls_x509_crt_t crt = NULL; gnutls_x509_privkey_t key_issuer = NULL; gnutls_x509_crt_t crt_issuer = NULL; gnutls_datum_t dat, crt_dem, int_dem; int serial = 42; unsigned char expected_nonce_prefix[6] = {0x30, 0x24, 0xa1, 0x22, 0x04, 0x20}; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)get_file_content(CREDENTIAL_PACKED_PRIVATE_KEY_VALID_PATH); key_data.size = o_strlen((const char *)key_data.data); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); o_free(key_data.data); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built key_data.data = (unsigned char *)client_data_json; key_data.size = o_strlen(client_data_json); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, client_data_hash, &client_data_hash_len), GNUTLS_E_SUCCESS); memcpy(verification_data, auth_data, auth_data_len); memcpy(verification_data+auth_data_len, client_data_hash, client_data_hash_len); // Let's build attStmt att_stmt = cbor_new_definite_map(1); // Let's create leaf certificate using CREDENTIAL_APPLE2_INT_KEY_PATH ck_assert_int_ge(gnutls_x509_crt_init(&crt), 0); ck_assert_int_ge(gnutls_x509_privkey_init(&key_issuer), 0); ck_assert_int_ge(gnutls_x509_crt_init(&crt_issuer), 0); // Import key_issuer dat.data = (unsigned char *)get_file_content(CREDENTIAL_APPLE2_INT_KEY_PATH); dat.size = o_strlen((const char *)dat.data); ck_assert_int_ge(gnutls_x509_privkey_import(key_issuer, &dat, GNUTLS_X509_FMT_PEM), 0); gnutls_free(dat.data); // Import crt_issuer dat.data = (unsigned char *)get_file_content(CREDENTIAL_APPLE2_INT_CERT_PATH); dat.size = o_strlen((const char *)dat.data); ck_assert_int_ge(gnutls_x509_crt_import(crt_issuer, &dat, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_ge(gnutls_x509_crt_export2(crt_issuer, GNUTLS_X509_FMT_DER, &int_dem), 0); gnutls_free(dat.data); // Generate expected_nonce memcpy(nonce_base, auth_data, auth_data_len); memcpy(nonce_base+auth_data_len, client_data_hash, 32); dat.data = nonce_base; dat.size = auth_data_len+32; memcpy(expected_nonce, expected_nonce_prefix, sizeof(expected_nonce_prefix)); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &dat, expected_nonce+sizeof(expected_nonce_prefix), &expected_nonce_len), GNUTLS_E_SUCCESS); // Generate crt signed by issuer ck_assert_int_ge(gnutls_x509_crt_set_version(crt, 3), 0); ck_assert_int_ge(gnutls_x509_crt_set_pubkey(crt, pubkey), 0); ck_assert_int_ge(gnutls_x509_crt_set_dn(crt, G_APPLE_CERT_LEAF_DN, NULL), 0); ck_assert_int_ge(gnutls_x509_crt_set_expiration_time(crt, time(NULL)+(60*60*24*512)), 0); ck_assert_int_ge(gnutls_x509_crt_set_activation_time(crt, time(NULL)), 0); ck_assert_int_ge(gnutls_x509_crt_set_serial(crt, &serial, sizeof(int)), 0); ck_assert_int_ge(gnutls_x509_crt_set_extension_by_oid(crt, G_APPLE_ANONYMOUS_ATTESTATION_OID, expected_nonce, expected_nonce_len+sizeof(expected_nonce_prefix), 0), 0); ck_assert_int_ge(gnutls_x509_crt_sign2(crt, crt_issuer, key_issuer, GNUTLS_DIG_SHA256, 0), 0); ck_assert_int_ge(gnutls_x509_crt_export2(crt, GNUTLS_X509_FMT_DER, &crt_dem), 0); // Set x5c elements cose_pair.key = cbor_build_string("x5c"); cose_pair.value = cbor_new_definite_array(2); bs_obj = cbor_build_bytestring(crt_dem.data, crt_dem.size); cbor_array_set(cose_pair.value, 0, bs_obj); cbor_decref(&bs_obj); bs_obj = cbor_build_bytestring(int_dem.data, int_dem.size); cbor_array_set(cose_pair.value, 1, bs_obj); cbor_decref(&bs_obj); ck_assert_int_eq(cbor_map_add(att_stmt, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); key_data.data = verification_data; key_data.size = auth_data_len+client_data_hash_len; ck_assert_int_eq(gnutls_privkey_sign_data(privkey, GNUTLS_DIG_SHA256, 0, &key_data, &signature), 0); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("apple"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+4); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_pubkey_deinit(pubkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_x509_privkey_deinit(key_issuer); gnutls_x509_crt_deinit(crt_issuer); gnutls_x509_crt_deinit(crt); gnutls_free(signature.data); gnutls_free(key_x.data); gnutls_free(key_y.data); gnutls_free(crt_dem.data); gnutls_free(int_dem.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&cbor_cose); cbor_decref(&att_stmt); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_disable_credential_error) { json_t * j_params; j_params = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "disable-credential", "credential_id", "error"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 404, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{sssi}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "disable-credential", "credential_id", 42); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "disable-credential"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_params); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_disable_credential_success) { json_t * j_params; unsigned char credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2]; size_t credential_id_enc_len; ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); j_params = json_pack("{sssssss{ssss%}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "disable-credential", "credential_id", credential_id_enc, credential_id_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_enable_credential_error) { json_t * j_params; j_params = json_pack("{sssssss{ssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "enable-credential", "credential_id", "error"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 404, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{sssi}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "enable-credential", "credential_id", 42); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "enable-credential"); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_params); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_enable_credential_success) { json_t * j_params; unsigned char credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2]; size_t credential_id_enc_len; ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); j_params = json_pack("{sssssss{ssss%}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "enable-credential", "credential_id", credential_id_enc, credential_id_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_edit_credential_error) { json_t * j_params; unsigned char credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2]; size_t credential_id_enc_len; ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); j_params = json_pack("{sssssss{ssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "edit-credential", "credential_id", "error", "name", WEBAUTHN_CREDENTIAL_NEW_NAME); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 404, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{sssiss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "edit-credential", "credential_id", 42, "name", WEBAUTHN_CREDENTIAL_NEW_NAME); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_params); j_params = json_pack("{sssssss{sssssi}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "edit-credential", "credential_id", credential_id_enc, "name", 42); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 400, NULL, NULL, NULL), 1); json_decref(j_params); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_edit_credential_success) { json_t * j_params; unsigned char credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2]; size_t credential_id_enc_len; ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); j_params = json_pack("{sssssss{ssssss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME, "value", "register", "edit-credential", "credential_id", credential_id_enc, "name", WEBAUTHN_CREDENTIAL_NEW_NAME); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_list_credential_success) { json_t * j_params, * j_result; j_params = json_pack("{ssssss}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME); unsigned char credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2]; size_t credential_id_enc_len; ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); j_result = json_stringn((char *)credential_id_enc, credential_id_enc_len); ck_assert_ptr_ne(j_result, NULL); ck_assert_int_eq(run_simple_test(&user_req, "PUT", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, j_result, NULL, NULL), 1); json_decref(j_result); json_decref(j_params); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_none_error_format) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], * att_obj_ser = NULL, * att_obj_ser_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, att_obj_ser_len, att_obj_ser_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_array(0); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("none"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 400, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&att_stmt); cbor_decref(&cbor_cose); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_register_none_success) { json_t * j_params = json_pack("{sssssss{ss}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value", "register", "new-credential"), * j_result, * j_client_data, * j_credential; struct _u_response resp, resp_register; unsigned char challenge_dec[WEBAUTHN_CHALLENGE_LEN], challenge_b64url[WEBAUTHN_CHALLENGE_LEN*2], * client_data_json_enc, credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2], credential_id_enc_url[WEBAUTHN_CREDENTIAL_ID_LEN*2], auth_data[AUTH_DATA_SIZE], aaguid[AAGUID_LEN] = AAGUID, pubkey_id[128], cbor_cose_dump[512], * att_obj_ser = NULL, * att_obj_ser_enc; size_t challenge_dec_len, challenge_b64url_len, client_data_json_enc_len, credential_id_enc_len, credential_id_enc_url_len, auth_data_len = 1024, pubkey_id_len = 128, cbor_cose_dump_max_len = 512, cbor_cose_dump_len, att_obj_ser_len, att_obj_ser_enc_len; const char * session, * challenge, * user_id, * username, * rpid; char * client_data_json; gnutls_datum_t key_data, key_x, key_y; gnutls_pubkey_t pubkey = NULL; gnutls_x509_crt_t cert = NULL; gnutls_x509_privkey_t key = NULL; gnutls_privkey_t privkey = NULL; gnutls_ecc_curve_t curve; cbor_item_t * cbor_cose, * att_stmt, * att_obj; struct cbor_pair cose_pair; ulfius_init_response(&resp); ulfius_init_response(&resp_register); o_free(user_req.http_verb); user_req.http_verb = o_strdup("POST"); o_free(user_req.http_url); user_req.http_url = o_strdup(SERVER_URI "profile/scheme/register/"); ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_params), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp), U_OK); ck_assert_int_eq(resp.status, 200); ck_assert_ptr_ne((j_result = ulfius_get_json_body_response(&resp, NULL)), NULL); ck_assert_ptr_ne((session = json_string_value(json_object_get(j_result, "session"))), NULL); ck_assert_ptr_ne((challenge = json_string_value(json_object_get(j_result, "challenge"))), NULL); ck_assert_ptr_ne((rpid = json_string_value(json_object_get(j_result, "rpId"))), NULL); ck_assert_ptr_ne((user_id = json_string_value(json_object_get(json_object_get(j_result, "user"), "id"))), NULL); ck_assert_ptr_ne((username = json_string_value(json_object_get(json_object_get(j_result, "user"), "name"))), NULL); ck_assert_int_eq(o_base64_decode((unsigned char *)json_string_value(json_object_get(j_result, "challenge")), json_string_length(json_object_get(j_result, "challenge")), challenge_dec, &challenge_dec_len), 1); // Generate clientDataJSON ck_assert_int_eq(o_base64_2_base64url((unsigned char *)challenge, o_strlen(challenge), challenge_b64url, &challenge_b64url_len), 1); j_client_data = json_pack("{ss%s{}ssss}", "challenge", challenge_b64url, challenge_b64url_len, "clientExtensions", "origin", WEBAUTHN_RP_ORIGIN, "type", "webauthn.create"); client_data_json = json_dumps(j_client_data, JSON_COMPACT); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), NULL, &client_data_json_enc_len), 1); client_data_json_enc = o_malloc(client_data_json_enc_len+1); ck_assert_ptr_ne(client_data_json_enc, NULL); ck_assert_int_eq(o_base64_encode((unsigned char *)client_data_json, o_strlen(client_data_json), client_data_json_enc, &client_data_json_enc_len), 1); // Generate credential_id ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); ck_assert_int_eq(o_base64url_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc_url, &credential_id_enc_url_len), 1); // Let's build auth_data memset(auth_data, 0, AUTH_DATA_SIZE); // Set rpId hash key_data.data = (unsigned char *)WEBAUTHN_RP_ID; key_data.size = o_strlen(WEBAUTHN_RP_ID); ck_assert_int_eq(gnutls_fingerprint(GNUTLS_DIG_SHA256, &key_data, auth_data, &auth_data_len), GNUTLS_E_SUCCESS); // Set flags *(auth_data+auth_data_len) = FLAG_USER_PRESENT | FLAG_AT; auth_data_len += 5; // Set aaguid memcpy((auth_data+auth_data_len), aaguid, AAGUID_LEN); auth_data_len += AAGUID_LEN; // Set Credential ID and Credential public key ck_assert_int_eq(gnutls_pubkey_init(&pubkey), 0); ck_assert_int_eq(gnutls_x509_privkey_init(&key), 0); ck_assert_int_eq(gnutls_privkey_init(&privkey), 0); key_data.data = (unsigned char *)CREDENTIAL_PUBLIC_KEY; key_data.size = o_strlen(CREDENTIAL_PUBLIC_KEY); ck_assert_int_eq(gnutls_pubkey_import(pubkey, &key_data, GNUTLS_X509_FMT_PEM), 0); key_data.data = (unsigned char *)FIDO_KEY_FAKE; key_data.size = o_strlen(FIDO_KEY_FAKE); ck_assert_int_eq(gnutls_x509_privkey_import(key, &key_data, GNUTLS_X509_FMT_PEM), 0); ck_assert_int_eq(gnutls_privkey_import_x509(privkey, key, 0), 0); ck_assert_int_eq(gnutls_pubkey_get_key_id(pubkey, 0, pubkey_id, &pubkey_id_len), 0); memset((auth_data+auth_data_len), WEBAUTHN_CREDENTIAL_ID_LEN>>8, 1); memset((auth_data+auth_data_len+1), WEBAUTHN_CREDENTIAL_ID_LEN, 1); auth_data_len += 2; memcpy((auth_data+auth_data_len), credential_id, WEBAUTHN_CREDENTIAL_ID_LEN); auth_data_len += WEBAUTHN_CREDENTIAL_ID_LEN; ck_assert_int_eq(gnutls_pubkey_export_ecc_raw2(pubkey, &curve, &key_x, &key_y, GNUTLS_EXPORT_FLAG_NO_LZ), 0); cbor_cose = cbor_new_definite_map(4); ck_assert_ptr_ne(cbor_cose, NULL); cose_pair.key = cbor_build_uint8(1); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_x.data, key_x.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(2); cbor_mark_negint(cose_pair.key); cose_pair.value = cbor_build_bytestring(key_y.data, key_y.size); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(1); cose_pair.value = cbor_build_uint8(2); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_uint8(3); cose_pair.value = cbor_build_uint8(6); cbor_mark_negint(cose_pair.value); ck_assert_int_eq(cbor_map_add(cbor_cose, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cbor_cose_dump_len = cbor_serialize(cbor_cose, cbor_cose_dump, cbor_cose_dump_max_len); ck_assert_int_gt(cbor_cose_dump_len, 0); memcpy((auth_data+auth_data_len), cbor_cose_dump, cbor_cose_dump_len); auth_data_len += cbor_cose_dump_len; // authData is properly built // Let's build attStmt att_stmt = cbor_new_definite_map(0); // attStmt is properly built // Let's built the attestation object att_obj = cbor_new_definite_map(3); cose_pair.key = cbor_build_string("fmt"); cose_pair.value = cbor_build_string("none"); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("authData"); cose_pair.value = cbor_build_bytestring(auth_data, auth_data_len); ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); cbor_decref(&cose_pair.value); cose_pair.key = cbor_build_string("attStmt"); cose_pair.value = att_stmt; ck_assert_int_eq(cbor_map_add(att_obj, cose_pair), true); cbor_decref(&cose_pair.key); ck_assert_int_gt(cbor_serialize_alloc(att_obj, &att_obj_ser, &att_obj_ser_len), 0); ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, NULL, &att_obj_ser_enc_len), 1); att_obj_ser_enc = o_malloc(att_obj_ser_enc_len+1); att_obj_ser_enc_len = 0; ck_assert_int_eq(o_base64_encode(att_obj_ser, att_obj_ser_len, att_obj_ser_enc, &att_obj_ser_enc_len), 1); j_credential = json_pack("{ss ss ss s{ss ss ss s{ss% ss% ss s{ss% ss%}}}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value", "register", "register-credential", "session", session, "type", "public-key", "credential", "id", credential_id_enc_url, credential_id_enc_url_len, "rawId", credential_id_enc, credential_id_enc_len, "type", "public-key", "response", "attestationObject", att_obj_ser_enc, att_obj_ser_enc_len, "clientDataJSON", client_data_json_enc, client_data_json_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_credential, NULL, 200, NULL, NULL, NULL), 1); /*ck_assert_int_eq(ulfius_set_json_body_request(&user_req, j_credential), U_OK); ck_assert_int_eq(ulfius_send_http_request(&user_req, &resp_register), U_OK); printf("body %.*s\n", (int)resp_register.binary_body_length, (char *)resp_register.binary_body); ck_assert_int_eq(resp_register.status, 200);*/ gnutls_privkey_deinit(privkey); gnutls_x509_crt_deinit(cert); gnutls_x509_privkey_deinit(key); gnutls_pubkey_deinit(pubkey); gnutls_free(key_x.data); gnutls_free(key_y.data); json_decref(j_params); json_decref(j_result); json_decref(j_client_data); json_decref(j_credential); ulfius_clean_response(&resp); ulfius_clean_response(&resp_register); o_free(client_data_json); o_free(client_data_json_enc); o_free(att_obj_ser_enc); o_free(att_obj_ser); cbor_decref(&att_obj); cbor_decref(&att_stmt); cbor_decref(&cbor_cose); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_remove_credential_none_success) { json_t * j_params; unsigned char credential_id_enc[WEBAUTHN_CREDENTIAL_ID_LEN*2]; size_t credential_id_enc_len; ck_assert_int_eq(o_base64_encode(credential_id, WEBAUTHN_CREDENTIAL_ID_LEN, credential_id_enc, &credential_id_enc_len), 1); j_params = json_pack("{sssssss{ssss%}}", "username", USERNAME, "scheme_type", MODULE_MODULE, "scheme_name", MODULE_NAME_2, "value", "register", "remove-credential", "credential_id", credential_id_enc, credential_id_enc_len); ck_assert_int_eq(run_simple_test(&user_req, "POST", SERVER_URI "profile/scheme/register/", NULL, NULL, j_params, NULL, 200, NULL, NULL, NULL), 1); json_decref(j_params); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_module_remove) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/scheme/" MODULE_NAME, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST START_TEST(test_glwd_scheme_webauthn_irl_module_remove_2) { ck_assert_int_eq(run_simple_test(&admin_req, "DELETE", SERVER_URI "/mod/scheme/" MODULE_NAME_2, NULL, NULL, NULL, NULL, 200, NULL, NULL, NULL), 1); } END_TEST static Suite *glewlwyd_suite(void) { Suite *s; TCase *tc_core; s = suite_create("Glewlwyd scheme webauthn"); tc_core = tcase_create("test_glwd_scheme_webauthn_irl"); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_module_add); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_scope_set); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_new_credential); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_bad_formed_response); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_invalid_client_data_json_challenge); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_invalid_client_data_json_rp_origin); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_invalid_client_data_json_type); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_invalid_auth_data_rpid); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_invalid_auth_data_flag_at); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_invalid_auth_data_flag_user_present); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_invalid_auth_data_credential_id); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_invalid_auth_data_credential_id_content); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_invalid_auth_data_cose_key_invalid_map); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_invalid_auth_data_cose_key_invalid_alg); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_invalid_auth_data_cose_key_invalid_dump); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_invalid_data_cose_key_key_x_sign); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_invalid_data_cose_key_key_x_type); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_invalid_data_cose_key_key_alg); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_att_stmt_map_size); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_att_stmt_cert_key); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_att_stmt_x5c_size); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_sig_base_prefix); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_sig_base_rpid); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_sig_base_client_data_hash); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_sig_base_client_id); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_sig_base_key_prefix); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_sig_base_key_x); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_sig_base_key_y); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_sig_base_size); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_sig_base_content); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_sig_key); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_att_obj_size); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_auth_data_key); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_u2f_invalid_att_stmt_key); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_u2f_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_disable_credential_error); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_disable_credential_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_enable_credential_error); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_enable_credential_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_edit_credential_error); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_edit_credential_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_test_assertion_trigger_error_session_invalid); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_test_assertion_error_session_invalid); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_test_assertion_invalid_challenge); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_test_assertion_invalid_origin); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_test_assertion_invalid_client_data_type); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_test_assertion_invalid_client_data_encoded); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_test_assertion_invalid_rp_id_hash); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_test_assertion_invalid_flag_user_present); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_test_assertion_invalid_client_data_hash); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_test_assertion_invalid_signature); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_test_assertion_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_auth_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_remove_credential_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_safetynet_ver_key); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_safetynet_ver_type); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_safetynet_cert); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_safetynet_cert_missing); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_safetynet_nonce_invalid); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_safetynet_jws_invalid); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_safetynet_fmt_invalid_key); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_error_safetynet_jws_invalid_signature); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_safetynet_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_test_assertion_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_auth_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_remove_credential_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_u2f_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_u2f_success_already_registered); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_u2f_2_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_test_assertion_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_test_assertion_invalid_credential_id); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_auth_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_auth_invalid_credential_id); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_list_credential_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_remove_credential_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_remove_credential_2_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_trigger_flaggerbasted); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_packed_x5c_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_remove_credential_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_packed_x5c_invalid_signature); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_packed_x5c_invalid_algorithm); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_packed_x5c_invalid_cert); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_packed_x5c_no_algorithm); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_packed_x5c_no_signature); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_packed_self_signed_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_packed_self_signed_invalid_signature); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_packed_self_signed_invalid_pubkey); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_remove_credential_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_module_remove); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_module_add_with_ca); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_scope_set); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_packed_x5c_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_remove_credential_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_packed_x5c_invalid_ui); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_packed_x5c_invalid_c); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_packed_x5c_missing_c); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_packed_x5c_missing_o); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_packed_x5c_missing_cn); // This test requires GnuTLS 3.5.3 to work #if GNUTLS_VERSION_NUMBER >= 0x030503 tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_packed_x5c_invalid_aaguid); #endif tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_apple_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_remove_credential_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_apple_intermediate_2_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_remove_credential_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_apple_invalid_aaguid); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_apple_x5c_invalid_nonce); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_apple_x5c_invalid_pubkey); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_apple_x5c_invalid_root_ca); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_module_remove); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_module_add_with_ca_2); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_scope_set); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_packed_x5c_unregistered_ca); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_module_remove); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_module_add); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_module_add_2); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_scope_collision_set); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_u2f_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_u2f_2_collision_error); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_u2f_2_in_2_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_auth_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_auth_2_in_2_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_auth_2_in_1_error); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_remove_credential_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_remove_credential_2_in_2_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_none_error_format); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_register_none_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_remove_credential_none_success); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_module_remove); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_module_remove_2); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_module_add_3); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_scope_set); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_trigger_not_flaggerbasted); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_scope_unset); tcase_add_test(tc_core, test_glwd_scheme_webauthn_irl_module_remove); tcase_set_timeout(tc_core, 30); suite_add_tcase(s, tc_core); return s; } int main(int argc, char *argv[]) { int number_failed = 0; Suite *s; SRunner *sr; struct _u_request auth_req; struct _u_response auth_resp; json_t * j_body; int res, do_test = 0, i; y_init_logs("Glewlwyd test", Y_LOG_MODE_CONSOLE, Y_LOG_LEVEL_DEBUG, NULL, "Starting Glewlwyd test"); // Getting a valid session id for authenticated http requests ulfius_init_request(&user_req); ulfius_init_request(&admin_req); ulfius_init_request(&auth_req); ulfius_init_response(&auth_resp); auth_req.http_verb = strdup("POST"); auth_req.http_url = msprintf("%s/auth/", SERVER_URI); j_body = json_pack("{ssss}", "username", USERNAME, "password", PASSWORD); ulfius_set_json_body_request(&auth_req, j_body); json_decref(j_body); res = ulfius_send_http_request(&auth_req, &auth_resp); if (res == U_OK && auth_resp.status == 200) { for (i=0; i$1.log 2>&1 if [ $? -ne 0 ] then printf "${RED}FAIL${NC}\n" else printf "${GREEN}SUCCESS${NC}\n" fi glewlwyd-2.6.1/test/unit-tests.c000066400000000000000000000202051415646314000166230ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include #include #include #include #include "unit-tests.h" char * read_file(const char * filename) { char * buffer = NULL; long length; FILE * f; if (filename != NULL) { f = fopen (filename, "rb"); if (f) { fseek (f, 0, SEEK_END); length = ftell (f); fseek (f, 0, SEEK_SET); buffer = o_malloc (length + 1); if (buffer) { fread (buffer, 1, length, f); buffer[length] = '\0'; } fclose (f); } return buffer; } else { return NULL; } } /** * Developper-friendly response print */ void print_response(struct _u_response * response) { char * dump_json = NULL; json_t * json_body; if (response != NULL) { fprintf(stderr,"Status: %ld\n\n", response->status); json_body = ulfius_get_json_body_response(response, NULL); if (json_body != NULL) { dump_json = json_dumps(json_body, JSON_INDENT(2)); fprintf(stderr,"Json body:\n%s\n\n", dump_json); o_free(dump_json); } else { fprintf(stderr,"String body: %.*s\n\n", (int)response->binary_body_length, (char *)response->binary_body); } json_decref(json_body); } } /** * json_t * json_search(json_t * haystack, json_t * needle) * jansson library addon * Look for an occurence of needle within haystack * If needle is present in haystack, return the reference to the json_t * that is equal to needle * If needle is not found, return NULL */ json_t * json_search(json_t * haystack, json_t * needle) { json_t * value1 = NULL, * value2 = NULL; size_t index = 0; const char * key = NULL; if (!haystack || !needle) return NULL; if (haystack == needle) return haystack; // If both haystack and needle are the same type, test them if (json_typeof(haystack) == json_typeof(needle) && !json_is_object(haystack)) if (json_equal(haystack, needle)) return haystack; // If they are not equals, test json_search in haystack elements recursively if it's an array or an object if (json_is_array(haystack)) { json_array_foreach(haystack, index, value1) { if (json_equal(value1, needle)) { return value1; } else { value2 = json_search(value1, needle); if (value2 != NULL) { return value2; } } } } else if (json_is_object(haystack) && json_is_object(needle)) { int same = 1; json_object_foreach(needle, key, value1) { value2 = json_object_get(haystack, key); if (!json_equal(value1, value2)) { same = 0; } } if (same) { return haystack; } } else if (json_is_object(haystack)) { json_object_foreach(haystack, key, value1) { if (json_equal(value1, needle)) { return value1; } else { value2 = json_search(value1, needle); if (value2 != NULL) { return value2; } } } } return NULL; } int test_request(struct _u_request * req, long int expected_status, json_t * expected_json_body, const char * exptected_string_body, const char * expected_redirect_uri_contains) { int res, to_return = 0; struct _u_response response; json_t * json_body; ulfius_init_response(&response); res = ulfius_send_http_request(req, &response); if (res == U_OK) { if (response.status != expected_status) { fprintf(stderr,"##########################\nError status (%s %s %ld)\n", req->http_verb, req->http_url, expected_status); print_response(&response); fprintf(stderr,"##########################\n\n"); } else if (expected_json_body != NULL) { json_body = ulfius_get_json_body_response(&response, NULL); if (json_body == NULL || json_search(json_body, expected_json_body) == NULL) { char * dump_expected = json_dumps(expected_json_body, JSON_ENCODE_ANY), * dump_response = json_dumps(json_body, JSON_ENCODE_ANY); fprintf(stderr,"##########################\nError json (%s %s)\n", req->http_verb, req->http_url); fprintf(stderr,"Expected result in response:\n%s\nWhile response is:\n%s\n", dump_expected, dump_response); fprintf(stderr,"##########################\n\n"); o_free(dump_expected); o_free(dump_response); } else { to_return = 1; } json_decref(json_body); } else if (exptected_string_body != NULL && o_strnstr((const char *)response.binary_body, exptected_string_body, response.binary_body_length) == NULL) { fprintf(stderr,"##########################\nError (%s %s)\n", req->http_verb, req->http_url); fprintf(stderr,"Expected result in response:\n%s\nWhile response is:\n%s\n", exptected_string_body, (const char *)response.binary_body); fprintf(stderr,"##########################\n\n"); } else if (expected_redirect_uri_contains != NULL && o_strstr(u_map_get(response.map_header, "Location"), expected_redirect_uri_contains) == NULL) { fprintf(stderr,"##########################\nError (%s %s)\n", req->http_verb, req->http_url); fprintf(stderr,"expected_redirect_uri_contains is %s\nwhile redirect_uri is %s\n", expected_redirect_uri_contains, u_map_get(response.map_header, "Location")); fprintf(stderr,"##########################\n\n"); } else { to_return = 1; } } else { fprintf(stderr,"Error in http request: %d\n", res); } ulfius_clean_response(&response); return to_return; } int run_simple_test(struct _u_request * req, const char * method, const char * url, const char * auth_basic_user, const char * auth_basic_password, json_t * json_body, const struct _u_map * body, int expected_status, json_t * expected_json_body, const char * exptected_string_body, const char * expected_redirect_uri_contains) { struct _u_request * request; int res; if (req != NULL) { request = ulfius_duplicate_request(req); } else { request = o_malloc(sizeof (struct _u_request)); ulfius_init_request(request); } ulfius_set_request_properties(request, U_OPT_HTTP_VERB, method, U_OPT_HTTP_URL, url, U_OPT_AUTH_BASIC_USER, auth_basic_user, U_OPT_AUTH_BASIC_PASSWORD, auth_basic_password, U_OPT_NONE); if (body != NULL) { u_map_copy_into(request->map_post_body, body); } else if (json_body != NULL) { ulfius_set_request_properties(request, U_OPT_JSON_BODY, json_body, U_OPT_NONE); } res = test_request(request, expected_status, expected_json_body, exptected_string_body, expected_redirect_uri_contains); ulfius_clean_request_full(request); request = NULL; return res; } /** * Converts a hex character to its integer value */ char from_hex(char ch) { return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10; } /** * Converts an integer value to its hex character */ char to_hex(char code) { static char hex[] = "0123456789abcdef"; return hex[code & 15]; } /** * Returns a url-encoded version of str * IMPORTANT: be sure to free() the returned string after use * Thanks Geek Hideout! * http://www.geekhideout.com/urlcode.shtml */ char * url_encode(const char * str) { char * pstr = (char*)str, * buf = malloc(strlen(str) * 3 + 1), * pbuf = buf; while (* pstr) { if (isalnum(* pstr) || * pstr == '-' || * pstr == '_' || * pstr == '.' || * pstr == '~') * pbuf++ = * pstr; else if (* pstr == ' ') * pbuf++ = '+'; else * pbuf++ = '%', * pbuf++ = to_hex(* pstr >> 4), * pbuf++ = to_hex(* pstr & 15); pstr++; } * pbuf = '\0'; return buf; } /** * Returns a url-decoded version of str * IMPORTANT: be sure to free() the returned string after use * Thanks Geek Hideout! * http://www.geekhideout.com/urlcode.shtml */ char * url_decode(const char * str) { char * pstr = (char*)str, * buf = malloc(strlen(str) + 1), * pbuf = buf; while (* pstr) { if (* pstr == '%') { if (pstr[1] && pstr[2]) { * pbuf++ = from_hex(pstr[1]) << 4 | from_hex(pstr[2]); pstr += 2; } } else if (* pstr == '+') { * pbuf++ = ' '; } else { * pbuf++ = * pstr; } pstr++; } * pbuf = '\0'; return buf; } glewlwyd-2.6.1/test/unit-tests.h000066400000000000000000000015551415646314000166370ustar00rootroot00000000000000/* Public domain, no copyright. Use at your own risk. */ #include #include char * read_file(const char * filename); char * print_map(const struct _u_map * map); void print_response(struct _u_response * response); int test_request(struct _u_request * req, long int expected_status, json_t * expected_json_body, const char * exptected_string_body, const char * expected_redirect_uri_contains); int run_simple_test(struct _u_request * req, const char * method, const char * url, const char * auth_basic_user, const char * auth_basic_password, json_t * json_body, const struct _u_map * body, int expected_status, json_t * expected_json_body, const char * exptected_string_body, const char * expected_redirect_uri_contains); char * url_decode(const char * str); char * url_encode(const char * str); json_t * json_search(json_t * haystack, json_t * needle); glewlwyd-2.6.1/webapp-src/000077500000000000000000000000001415646314000154255ustar00rootroot00000000000000glewlwyd-2.6.1/webapp-src/.babelrc000066400000000000000000000000761415646314000170230ustar00rootroot00000000000000{ "presets": ["@babel/preset-env", "@babel/preset-react"] } glewlwyd-2.6.1/webapp-src/.gitignore000066400000000000000000000000761415646314000174200ustar00rootroot00000000000000/node_modules /output yarn.lock package-lock.json config.json glewlwyd-2.6.1/webapp-src/LICENSE000066400000000000000000000020751415646314000164360ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2019-2021 Nicolas Mora 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. glewlwyd-2.6.1/webapp-src/Makefile000066400000000000000000000030771415646314000170740ustar00rootroot00000000000000# # Glewlwyd SSO Server # # Makefile used to build and install the front-end webapp # # Copyright 2017-2021 Nicolas Mora # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU GENERAL PUBLIC LICENSE # License as published by the Free Software Foundation; # version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU GENERAL PUBLIC LICENSE for more details. # # You should have received a copy of the GNU General Public # License along with this program. If not, see . # WEBAPP_DEST=../webapp JSC=npm all: webapp install-dev: $(JSC) install dev: $(JSC) run dev webapp: build-webapp install-webapp polyfill: build-polyfill-webapp install-polyfill build-webapp: rm -f output/* $(JSC) run build build-polyfill-webapp: rm -f output/* $(JSC) run polyfill install-webapp: find $(WEBAPP_DEST) ! -name config.json ! -name webapp ! -name .gitignore -delete cp -R index.html login.html profile.html callback.html config.json.sample favicon.ico css/ js/ locales/ fonts/ img/ $(WEBAPP_DEST) cp output/*.js $(WEBAPP_DEST) install-polyfill: find $(WEBAPP_DEST) ! -name config.json ! -name webapp ! -name .gitignore -delete cp -R polyfill/index.html polyfill/login.html polyfill/profile.html polyfill/callback.html config.json.sample favicon.ico css/ js/ locales/ fonts/ img/ $(WEBAPP_DEST) cp output/*.js $(WEBAPP_DEST) glewlwyd-2.6.1/webapp-src/README.md000066400000000000000000000025671415646314000167160ustar00rootroot00000000000000# Glewlwyd front-end source files Glewlwyd front-end is a single Page Application based on ReactJS/JQuery. Uses Webpack 4 to build, requires npm or yarn. Many thanks to [Marco DE BOOIJ](https://github.com/mdebooy) for the Dutch translation! ## Install dependencies Prior to running the development instance or building the front-end, you must install the dependencies. ```shell $ npm install ``` ## Run development instance Copy `config.json.sample` to `config.json` and run the webpack dev server: ```shell $ cp config.json.sample config.json $ npm run dev ``` Then open the address [http://localhost:3000/](http://localhost:3000/) on your browser. ## Build front-end ```shell $ make ``` The built web application will be available in `glewlwyd/webapp`. ## Build front-end for Internet Explorer support Internet Explorer 11 doesn't have javascript engine compatible with Glewlwyd Front-end application, by choice. If you really need to support Internet Explorer, you can build the front-end with [babel-polyfill](https://babeljs.io/docs/en/babel-polyfill). This will make the front-end application somehow bigger and slower, that's the reason why this build option is disabled by default. To build the front-end application with babel-polyfill, run the following command: ```shell $ make build-polyfill-webapp ``` The built web application will be available in `glewlwyd/webapp`. glewlwyd-2.6.1/webapp-src/callback.html000066400000000000000000000050121415646314000200450ustar00rootroot00000000000000 Glewlwyd login
glewlwyd-2.6.1/webapp-src/config.json.sample000066400000000000000000000356331415646314000210570ustar00rootroot00000000000000{ "GlewlwydUrl": "./", "ProfileUrl": "profile.html", "AdminUrl": "index.html", "LoginUrl": "login.html", "CallbackPage": "callback.html", "PasswordMinLength": 8, "lang": ["de", "en","fr","nl"], "pattern": { "user": [{ "name": "username", "edit": false, "label": "admin.user-username", "placeholder": "admin.user-username-ph", "forceShow": true, "required": true, "profile-read": true, "profile-write": false },{ "name": "password", "type": "password", "profile": false, "edit": true, "label": "admin.user-password", "placeholder": "admin.user-password-ph", "placeholderConfirm": "admin.user-password-confirm-ph", "forceShow": true, "required": true, "profile-read": false, "profile-write": false },{ "name": "name", "profile": true, "edit": true, "forceShow": true, "label": "admin.user-name", "placeholder": "admin.user-name-ph", "profile-read": true, "profile-write": true },{ "name": "email", "edit": true, "forceShow": true, "label": "admin.user-email", "placeholder": "admin.user-email-ph", "profile-read": true, "profile-write": false },{ "name": "picture", "list": false, "type": "image/image", "forceShow": true, "edit": true, "label": "admin.user-picture", "profile-read": true, "profile-write": false },{ "name": "scope", "list": true, "forceShow": true, "edit": true, "label": "admin.user-scope", "profile-read": true, "profile-write": false },{ "name": "enabled", "type": "boolean", "defaultValue": true, "forceShow": true, "edit": true, "label": "admin.user-enabled" }], "client": [{ "name": "client_id", "edit": false, "forceShow": true, "label": "admin.client-clientid", "placeholder": "admin.client-clientid-ph", "required": true },{ "name": "name", "edit": true, "forceShow": true, "label": "admin.client-name", "placeholder": "admin.client-name-ph" },{ "name": "enabled", "type": "boolean", "defaultValue": true, "edit": true, "forceShow": true, "label": "admin.client-enabled" },{ "name": "scope", "list": true, "edit": true, "forceShow": true, "label": "admin.client-scope" },{ "name": "redirect_uri", "list": true, "edit": true, "forceShow": true, "label": "admin.client-redirect-uri", "placeholder": "admin.client-redirect-uri-ph" },{ "name": "authorization_type", "list": true, "edit": true, "forceShow": true, "label": "admin.client-authorization-type", "listElements": ["code","token","id_token","password","none","client_credentials","refresh_token","delete_token","device_authorization","urn:openid:params:grant-type:ciba"] },{ "name": "response_mode", "edit": true, "forceShow": true, "label": "admin.client-response_mode", "listElements": ["query","fragment","form_post","query.jwt","fragment.jwt","form_post.jwt"] },{ "name": "sector_identifier_uri", "edit": true, "forceShow": true, "label": "admin.client-sector-identifier-uri", "placeholder": "admin.client-sector-identifier-uri-ph" },{ "name": "confidential", "edit": true, "defaultValue": false, "forceShow": true, "type": "boolean", "label": "admin.client-confidential" },{ "name": "token_endpoint_auth_method", "list": true, "edit": true, "forceShow": true, "label": "admin.client-token_endpoint_auth_method", "listElements": ["", "none", "client_secret_post", "client_secret_basic", "client_secret_jwt", "private_key_jwt", "tls_client_auth", "self_signed_tls_client_auth"] },{ "name": "client_secret", "edit": true, "forceShow": true, "label": "admin.client-secret", "placeholder": "admin.client-secret-ph" },{ "name": "password", "type": "password", "profile": false, "edit": true, "forceShow": true, "label": "admin.client-password", "placeholder": "admin.client-password-ph", "placeholderConfirm": "admin.client-password-confirm-ph" },{ "name": "jwks", "type": "jwks", "edit": true, "forceShow": true, "label": "admin.client-jwks", "placeholder": "admin.client-jwks-ph" },{ "name": "jwks_uri", "edit": true, "forceShow": true, "label": "admin.client-jwks_uri", "placeholder": "admin.client-jwks_uri-ph" },{ "name": "pubkey", "type": "file", "edit": true, "forceShow": true, "label": "admin.client-pubkey" },{ "name": "enc", "edit": true, "forceShow": true, "label": "admin.client-enc", "placeholder": "admin.client-enc-ph", "listElements": ["A128CBC-HS256","A192CBC-HS384","A256CBC-HS512","A128GCM","A192GCM","A256GCM"] },{ "name": "alg", "edit": true, "forceShow": true, "label": "admin.client-alg", "placeholder": "admin.client-alg-ph", "listElements": ["RSA1_5","RSA-OAEP","RSA-OAEP-256","A128KW","A192KW","A256KW","dir","ECDH-ES","ECDH-ES+A128KW","ECDH-ES+A192KW","ECDH-ES+A256KW","A128GCMKW","A192GCMKW","A256GCMKW","PBES2-HS256+A128KW","PBES2-HS384+A192KW","PBES2-HS512+A256KW"] },{ "name": "alg_kid", "edit": true, "forceShow": true, "label": "admin.client-alg_kid", "placeholder": "admin.client-alg_kid-ph" },{ "name": "encrypt_code", "edit": true, "forceShow": true, "label": "admin.client-encrypt_code", "listElements": ["no","yes"] },{ "name": "encrypt_at", "edit": true, "forceShow": true, "label": "admin.client-encrypt_at", "listElements": ["no","yes"] },{ "name": "encrypt_userinfo", "edit": true, "forceShow": true, "label": "admin.client-encrypt_userinfo", "listElements": ["no","yes"] },{ "name": "encrypt_id_token", "edit": true, "forceShow": true, "label": "admin.client-encrypt_id_token", "listElements": ["no","yes"] },{ "name": "encrypt_refresh_token", "edit": true, "forceShow": true, "label": "admin.client-encrypt_refresh_token", "listElements": ["no","yes"] },{ "name": "resource", "edit": true, "forceShow": true, "type": "textarea", "list": true, "label": "admin.client-resource" },{ "name": "authorization_data_types", "list": true, "edit": true, "forceShow": true, "label": "admin.client-authorization-data-types", "placeholder": "admin.client-authorization-data-types-ph" },{ "name": "tls_client_auth_san_dns", "edit": true, "forceShow": true, "label": "admin.client-tls_client_auth_san_dns", "placeholder": "admin.client-tls_client_auth_san_dns-ph" },{ "name": "tls_client_auth_san_uri", "edit": true, "forceShow": true, "label": "admin.client-tls_client_auth_san_uri", "placeholder": "admin.client-tls_client_auth_san_uri-ph" },{ "name": "tls_client_auth_san_ip", "edit": true, "forceShow": true, "label": "admin.client-tls_client_auth_san_ip", "placeholder": "admin.client-tls_client_auth_san_ip-ph" },{ "name": "tls_client_auth_san_email", "edit": true, "forceShow": true, "label": "admin.client-tls_client_auth_san_email", "placeholder": "admin.client-tls_client_auth_san_email-ph" },{ "name": "backchannel_token_delivery_mode", "edit": true, "forceShow": true, "label": "admin.client-backchannel_token_delivery_mode", "listElements": ["poll","ping","push"] },{ "name": "backchannel_client_notification_endpoint", "edit": true, "forceShow": true, "label": "admin.client-backchannel_client_notification_endpoint", "placeholder": "admin.client-backchannel_client_notification_endpoint-ph" },{ "name": "backchannel_user_code_parameter", "edit": true, "forceShow": true, "label": "admin.client-backchannel_user_code_parameter", "listElements": ["no","yes"] },{ "name": "request_object_signing_alg", "edit": true, "forceShow": true, "label": "admin.client-request_object_signing_alg", "listElements": ["HS256","HS384","HS512","RS256","RS384","RS512","ES256","ES384","ES512","PS256","PS384","PS512","EdDSA","ES256K"] },{ "name": "request_object_encryption_enc", "edit": true, "forceShow": true, "label": "admin.client-request_object_encryption_enc", "listElements": ["A128CBC-HS256","A192CBC-HS384","A256CBC-HS512","A128GCM","A192GCM","A256GCM"] },{ "name": "request_object_encryption_alg", "edit": true, "forceShow": true, "label": "admin.client-request_object_encryption_alg", "listElements": ["RSA1_5","RSA-OAEP","RSA-OAEP-256","A128KW","A192KW","A256KW","ECDH-ES","ECDH-ES+A128KW","ECDH-ES+A192KW","ECDH-ES+A256KW","A128GCMKW","A192GCMKW","A256GCMKW","PBES2-HS256+A128KW","PBES2-HS384+A192KW","PBES2-HS512+A256KW"] },{ "name": "token_endpoint_signing_alg", "edit": true, "forceShow": true, "label": "admin.client-token_endpoint_signing_alg", "listElements": ["HS256","HS384","HS512","RS256","RS384","RS512","ES256","ES384","ES512","PS256","PS384","PS512","EdDSA","ES256K"] },{ "name": "token_endpoint_encryption_enc", "edit": true, "forceShow": true, "label": "admin.client-token_endpoint_encryption_enc", "listElements": ["A128CBC-HS256","A192CBC-HS384","A256CBC-HS512","A128GCM","A192GCM","A256GCM"] },{ "name": "token_endpoint_encryption_alg", "edit": true, "forceShow": true, "label": "admin.client-token_endpoint_encryption_alg", "listElements": ["RSA1_5","RSA-OAEP","RSA-OAEP-256","A128KW","A192KW","A256KW","ECDH-ES","ECDH-ES+A128KW","ECDH-ES+A192KW","ECDH-ES+A256KW","A128GCMKW","A192GCMKW","A256GCMKW","PBES2-HS256+A128KW","PBES2-HS384+A192KW","PBES2-HS512+A256KW"] },{ "name": "backchannel_authentication_request_signing_alg", "edit": true, "forceShow": true, "label": "admin.client-backchannel_authentication_request_signing_alg", "listElements": ["HS256","HS384","HS512","RS256","RS384","RS512","ES256","ES384","ES512","PS256","PS384","PS512","EdDSA","ES256K"] },{ "name": "backchannel_authentication_request_encryption_enc", "edit": true, "forceShow": true, "label": "admin.client-backchannel_authentication_request_encryption_enc", "listElements": ["A128CBC-HS256","A192CBC-HS384","A256CBC-HS512","A128GCM","A192GCM","A256GCM"] },{ "name": "backchannel_authentication_request_encryption_alg", "edit": true, "forceShow": true, "label": "admin.client-backchannel_authentication_request_encryption_alg", "listElements": ["RSA1_5","RSA-OAEP","RSA-OAEP-256","A128KW","A192KW","A256KW","ECDH-ES","ECDH-ES+A128KW","ECDH-ES+A192KW","ECDH-ES+A256KW","A128GCMKW","A192GCMKW","A256GCMKW","PBES2-HS256+A128KW","PBES2-HS384+A192KW","PBES2-HS512+A256KW"] },{ "name": "post_logout_redirect_uri", "edit": true, "forceShow": true, "label": "admin.client-post_logout_redirect_uri", "placeholder": "admin.client-post_logout_redirect_uri-ph" },{ "name": "frontchannel_logout_uri", "edit": true, "forceShow": true, "label": "admin.client-frontchannel_logout_uri", "placeholder": "admin.client-frontchannel_logout_uri-ph" },{ "name": "frontchannel_logout_session_required", "edit": true, "forceShow": true, "label": "admin.client-frontchannel_logout_session_required", "listElements": ["no","yes"] },{ "name": "backchannel_logout_uri", "edit": true, "forceShow": true, "label": "admin.client-backchannel_logout_uri", "placeholder": "admin.client-backchannel_logout_uri-ph" },{ "name": "backchannel_logout_session_required", "edit": true, "forceShow": true, "label": "admin.client-backchannel_logout_session_required", "listElements": ["no","yes"] }] }, "defaultScheme": "", "sessionSchemes": [ ], "profilePicture": { "property": "picture", "type": "image/*" }, "register": [ ], "register-complete": [ ], "providerMainstreamList": [ { "help_url": "https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/", "name": "Facebook", "logo_fa": "fab fa-facebook", "provider_type": "oauth2", "response_type": "code", "userinfo_endpoint": "https://graph.facebook.com/v5.0/me", "auth_endpoint": "https://www.facebook.com/v5.0/dialog/oauth", "token_endpoint": "https://graph.facebook.com/v5.0/oauth/access_token", "userid_property": "id" }, { "help_url": "https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/", "name": "GitHub", "logo_fa": "fab fa-github", "provider_type": "oauth2", "response_type": "code", "userinfo_endpoint": "https://api.github.com/user", "auth_endpoint": "https://github.com/login/oauth/authorize", "token_endpoint": "https://github.com/login/oauth/access_token", "scope": "read:user", "userid_property": "id" }, { "help_url": "https://docs.gitlab.com/ee/integration/oauth_provider.html", "name": "GitLab", "logo_fa": "fab fa-gitlab", "provider_type": "oauth2", "response_type": "code", "userinfo_endpoint": "https://gitlab.com/api/v4/user", "auth_endpoint": "https://gitlab.com/oauth/authorize", "token_endpoint": "https://gitlab.com/oauth/token", "scope": "read_user profile", "userid_property": "id" }, { "help_url": "https://developers.google.com/identity/protocols/OAuth2", "name": "Google", "logo_fa": "fab fa-google", "provider_type": "oidc", "response_type": "code", "config_endpoint": "https://accounts.google.com/.well-known/openid-configuration" }, { "help_url": "https://developer.yahoo.com/oauth2/guide/openid_connect/getting_started.html", "name": "Yahoo", "logo_fa": "fab fa-yahoo", "provider_type": "oidc", "response_type": "code", "config_endpoint": "https://api.login.yahoo.com/.well-known/openid-configuration" }, { "help_url": "https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-protocols-oauth-code#register-your-application-with-your-ad-tenant", "name": "Microsoft", "logo_fa": "fab fa-windows", "provider_type": "oidc", "response_type": "code", "config_endpoint": "https://login.microsoftonline.com/{tenantid}/.well-known/openid-configuration" } ] } glewlwyd-2.6.1/webapp-src/css/000077500000000000000000000000001415646314000162155ustar00rootroot00000000000000glewlwyd-2.6.1/webapp-src/css/admin-custom.css000066400000000000000000000003001415646314000213200ustar00rootroot00000000000000/* * Glewlwyd admin app custom css * You can update this file to add your own css rules * that will only apply in the admin application (index.html) * This file is under public domain */ glewlwyd-2.6.1/webapp-src/css/bootstrap.css000066400000000000000000006032511415646314000207530ustar00rootroot00000000000000/*! * Bootstrap v4.5.0 (https://getbootstrap.com/) * Copyright 2011-2020 The Bootstrap Authors * Copyright 2011-2020 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ :root { --blue: #007bff; --indigo: #6610f2; --purple: #6f42c1; --pink: #e83e8c; --red: #dc3545; --orange: #fd7e14; --yellow: #ffc107; --green: #28a745; --teal: #20c997; --cyan: #17a2b8; --white: #fff; --gray: #6c757d; --gray-dark: #343a40; --primary: #007bff; --secondary: #6c757d; --success: #28a745; --info: #17a2b8; --warning: #ffc107; --danger: #dc3545; --light: #f8f9fa; --dark: #343a40; --breakpoint-xs: 0; --breakpoint-sm: 576px; --breakpoint-md: 768px; --breakpoint-lg: 992px; --breakpoint-xl: 1200px; --font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } *, *::before, *::after { box-sizing: border-box; } html { font-family: sans-serif; line-height: 1.15; -webkit-text-size-adjust: 100%; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } article, aside, figcaption, figure, footer, header, hgroup, main, nav, section { display: block; } body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 1rem; font-weight: 400; line-height: 1.5; color: #212529; text-align: left; background-color: #fff; } [tabindex="-1"]:focus:not(:focus-visible) { outline: 0 !important; } hr { box-sizing: content-box; height: 0; overflow: visible; } h1, h2, h3, h4, h5, h6 { margin-top: 0; margin-bottom: 0.5rem; } p { margin-top: 0; margin-bottom: 1rem; } abbr[title], abbr[data-original-title] { text-decoration: underline; -webkit-text-decoration: underline dotted; text-decoration: underline dotted; cursor: help; border-bottom: 0; -webkit-text-decoration-skip-ink: none; text-decoration-skip-ink: none; } address { margin-bottom: 1rem; font-style: normal; line-height: inherit; } ol, ul, dl { margin-top: 0; margin-bottom: 1rem; } ol ol, ul ul, ol ul, ul ol { margin-bottom: 0; } dt { font-weight: 700; } dd { margin-bottom: .5rem; margin-left: 0; } blockquote { margin: 0 0 1rem; } b, strong { font-weight: bolder; } small { font-size: 80%; } sub, sup { position: relative; font-size: 75%; line-height: 0; vertical-align: baseline; } sub { bottom: -.25em; } sup { top: -.5em; } a { color: #007bff; text-decoration: none; background-color: transparent; } a:hover { color: #0056b3; text-decoration: underline; } a:not([href]) { color: inherit; text-decoration: none; } a:not([href]):hover { color: inherit; text-decoration: none; } pre, code, kbd, samp { font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 1em; } pre { margin-top: 0; margin-bottom: 1rem; overflow: auto; -ms-overflow-style: scrollbar; } figure { margin: 0 0 1rem; } img { vertical-align: middle; border-style: none; } svg { overflow: hidden; vertical-align: middle; } table { border-collapse: collapse; } caption { padding-top: 0.75rem; padding-bottom: 0.75rem; color: #6c757d; text-align: left; caption-side: bottom; } th { text-align: inherit; } label { display: inline-block; margin-bottom: 0.5rem; } button { border-radius: 0; } button:focus { outline: 1px dotted; outline: 5px auto -webkit-focus-ring-color; } input, button, select, optgroup, textarea { margin: 0; font-family: inherit; font-size: inherit; line-height: inherit; } button, input { overflow: visible; } button, select { text-transform: none; } [role="button"] { cursor: pointer; } select { word-wrap: normal; } button, [type="button"], [type="reset"], [type="submit"] { -webkit-appearance: button; } button:not(:disabled), [type="button"]:not(:disabled), [type="reset"]:not(:disabled), [type="submit"]:not(:disabled) { cursor: pointer; } button::-moz-focus-inner, [type="button"]::-moz-focus-inner, [type="reset"]::-moz-focus-inner, [type="submit"]::-moz-focus-inner { padding: 0; border-style: none; } input[type="radio"], input[type="checkbox"] { box-sizing: border-box; padding: 0; } textarea { overflow: auto; resize: vertical; } fieldset { min-width: 0; padding: 0; margin: 0; border: 0; } legend { display: block; width: 100%; max-width: 100%; padding: 0; margin-bottom: .5rem; font-size: 1.5rem; line-height: inherit; color: inherit; white-space: normal; } progress { vertical-align: baseline; } [type="number"]::-webkit-inner-spin-button, [type="number"]::-webkit-outer-spin-button { height: auto; } [type="search"] { outline-offset: -2px; -webkit-appearance: none; } [type="search"]::-webkit-search-decoration { -webkit-appearance: none; } ::-webkit-file-upload-button { font: inherit; -webkit-appearance: button; } output { display: inline-block; } summary { display: list-item; cursor: pointer; } template { display: none; } [hidden] { display: none !important; } h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { margin-bottom: 0.5rem; font-weight: 500; line-height: 1.2; } h1, .h1 { font-size: 2.5rem; } h2, .h2 { font-size: 2rem; } h3, .h3 { font-size: 1.75rem; } h4, .h4 { font-size: 1.5rem; } h5, .h5 { font-size: 1.25rem; } h6, .h6 { font-size: 1rem; } .lead { font-size: 1.25rem; font-weight: 300; } .display-1 { font-size: 6rem; font-weight: 300; line-height: 1.2; } .display-2 { font-size: 5.5rem; font-weight: 300; line-height: 1.2; } .display-3 { font-size: 4.5rem; font-weight: 300; line-height: 1.2; } .display-4 { font-size: 3.5rem; font-weight: 300; line-height: 1.2; } hr { margin-top: 1rem; margin-bottom: 1rem; border: 0; border-top: 1px solid rgba(0, 0, 0, 0.1); } small, .small { font-size: 80%; font-weight: 400; } mark, .mark { padding: 0.2em; background-color: #fcf8e3; } .list-unstyled { padding-left: 0; list-style: none; } .list-inline { padding-left: 0; list-style: none; } .list-inline-item { display: inline-block; } .list-inline-item:not(:last-child) { margin-right: 0.5rem; } .initialism { font-size: 90%; text-transform: uppercase; } .blockquote { margin-bottom: 1rem; font-size: 1.25rem; } .blockquote-footer { display: block; font-size: 80%; color: #6c757d; } .blockquote-footer::before { content: "\2014\00A0"; } .img-fluid { max-width: 100%; height: auto; } .img-thumbnail { padding: 0.25rem; background-color: #fff; border: 1px solid #dee2e6; border-radius: 0.25rem; max-width: 100%; height: auto; } .figure { display: inline-block; } .figure-img { margin-bottom: 0.5rem; line-height: 1; } .figure-caption { font-size: 90%; color: #6c757d; } code { font-size: 87.5%; color: #e83e8c; word-wrap: break-word; } a > code { color: inherit; } kbd { padding: 0.2rem 0.4rem; font-size: 87.5%; color: #fff; background-color: #212529; border-radius: 0.2rem; } kbd kbd { padding: 0; font-size: 100%; font-weight: 700; } pre { display: block; font-size: 87.5%; color: #212529; } pre code { font-size: inherit; color: inherit; word-break: normal; } .pre-scrollable { max-height: 340px; overflow-y: scroll; } .container { width: 100%; padding-right: 15px; padding-left: 15px; margin-right: auto; margin-left: auto; } @media (min-width: 576px) { .container { max-width: 540px; } } @media (min-width: 768px) { .container { max-width: 720px; } } @media (min-width: 992px) { .container { max-width: 960px; } } @media (min-width: 1200px) { .container { max-width: 1140px; } } .container-fluid, .container-sm, .container-md, .container-lg, .container-xl { width: 100%; padding-right: 15px; padding-left: 15px; margin-right: auto; margin-left: auto; } @media (min-width: 576px) { .container, .container-sm { max-width: 540px; } } @media (min-width: 768px) { .container, .container-sm, .container-md { max-width: 720px; } } @media (min-width: 992px) { .container, .container-sm, .container-md, .container-lg { max-width: 960px; } } @media (min-width: 1200px) { .container, .container-sm, .container-md, .container-lg, .container-xl { max-width: 1140px; } } .row { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; margin-right: -15px; margin-left: -15px; } .no-gutters { margin-right: 0; margin-left: 0; } .no-gutters > .col, .no-gutters > [class*="col-"] { padding-right: 0; padding-left: 0; } .col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, .col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm, .col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md, .col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg, .col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl, .col-xl-auto { position: relative; width: 100%; padding-right: 15px; padding-left: 15px; } .col { -ms-flex-preferred-size: 0; flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; min-width: 0; max-width: 100%; } .row-cols-1 > * { -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .row-cols-2 > * { -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .row-cols-3 > * { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; max-width: 33.333333%; } .row-cols-4 > * { -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .row-cols-5 > * { -ms-flex: 0 0 20%; flex: 0 0 20%; max-width: 20%; } .row-cols-6 > * { -ms-flex: 0 0 16.666667%; flex: 0 0 16.666667%; max-width: 16.666667%; } .col-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; width: auto; max-width: 100%; } .col-1 { -ms-flex: 0 0 8.333333%; flex: 0 0 8.333333%; max-width: 8.333333%; } .col-2 { -ms-flex: 0 0 16.666667%; flex: 0 0 16.666667%; max-width: 16.666667%; } .col-3 { -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .col-4 { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; max-width: 33.333333%; } .col-5 { -ms-flex: 0 0 41.666667%; flex: 0 0 41.666667%; max-width: 41.666667%; } .col-6 { -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .col-7 { -ms-flex: 0 0 58.333333%; flex: 0 0 58.333333%; max-width: 58.333333%; } .col-8 { -ms-flex: 0 0 66.666667%; flex: 0 0 66.666667%; max-width: 66.666667%; } .col-9 { -ms-flex: 0 0 75%; flex: 0 0 75%; max-width: 75%; } .col-10 { -ms-flex: 0 0 83.333333%; flex: 0 0 83.333333%; max-width: 83.333333%; } .col-11 { -ms-flex: 0 0 91.666667%; flex: 0 0 91.666667%; max-width: 91.666667%; } .col-12 { -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .order-first { -ms-flex-order: -1; order: -1; } .order-last { -ms-flex-order: 13; order: 13; } .order-0 { -ms-flex-order: 0; order: 0; } .order-1 { -ms-flex-order: 1; order: 1; } .order-2 { -ms-flex-order: 2; order: 2; } .order-3 { -ms-flex-order: 3; order: 3; } .order-4 { -ms-flex-order: 4; order: 4; } .order-5 { -ms-flex-order: 5; order: 5; } .order-6 { -ms-flex-order: 6; order: 6; } .order-7 { -ms-flex-order: 7; order: 7; } .order-8 { -ms-flex-order: 8; order: 8; } .order-9 { -ms-flex-order: 9; order: 9; } .order-10 { -ms-flex-order: 10; order: 10; } .order-11 { -ms-flex-order: 11; order: 11; } .order-12 { -ms-flex-order: 12; order: 12; } .offset-1 { margin-left: 8.333333%; } .offset-2 { margin-left: 16.666667%; } .offset-3 { margin-left: 25%; } .offset-4 { margin-left: 33.333333%; } .offset-5 { margin-left: 41.666667%; } .offset-6 { margin-left: 50%; } .offset-7 { margin-left: 58.333333%; } .offset-8 { margin-left: 66.666667%; } .offset-9 { margin-left: 75%; } .offset-10 { margin-left: 83.333333%; } .offset-11 { margin-left: 91.666667%; } @media (min-width: 576px) { .col-sm { -ms-flex-preferred-size: 0; flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; min-width: 0; max-width: 100%; } .row-cols-sm-1 > * { -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .row-cols-sm-2 > * { -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .row-cols-sm-3 > * { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; max-width: 33.333333%; } .row-cols-sm-4 > * { -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .row-cols-sm-5 > * { -ms-flex: 0 0 20%; flex: 0 0 20%; max-width: 20%; } .row-cols-sm-6 > * { -ms-flex: 0 0 16.666667%; flex: 0 0 16.666667%; max-width: 16.666667%; } .col-sm-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; width: auto; max-width: 100%; } .col-sm-1 { -ms-flex: 0 0 8.333333%; flex: 0 0 8.333333%; max-width: 8.333333%; } .col-sm-2 { -ms-flex: 0 0 16.666667%; flex: 0 0 16.666667%; max-width: 16.666667%; } .col-sm-3 { -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .col-sm-4 { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; max-width: 33.333333%; } .col-sm-5 { -ms-flex: 0 0 41.666667%; flex: 0 0 41.666667%; max-width: 41.666667%; } .col-sm-6 { -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .col-sm-7 { -ms-flex: 0 0 58.333333%; flex: 0 0 58.333333%; max-width: 58.333333%; } .col-sm-8 { -ms-flex: 0 0 66.666667%; flex: 0 0 66.666667%; max-width: 66.666667%; } .col-sm-9 { -ms-flex: 0 0 75%; flex: 0 0 75%; max-width: 75%; } .col-sm-10 { -ms-flex: 0 0 83.333333%; flex: 0 0 83.333333%; max-width: 83.333333%; } .col-sm-11 { -ms-flex: 0 0 91.666667%; flex: 0 0 91.666667%; max-width: 91.666667%; } .col-sm-12 { -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .order-sm-first { -ms-flex-order: -1; order: -1; } .order-sm-last { -ms-flex-order: 13; order: 13; } .order-sm-0 { -ms-flex-order: 0; order: 0; } .order-sm-1 { -ms-flex-order: 1; order: 1; } .order-sm-2 { -ms-flex-order: 2; order: 2; } .order-sm-3 { -ms-flex-order: 3; order: 3; } .order-sm-4 { -ms-flex-order: 4; order: 4; } .order-sm-5 { -ms-flex-order: 5; order: 5; } .order-sm-6 { -ms-flex-order: 6; order: 6; } .order-sm-7 { -ms-flex-order: 7; order: 7; } .order-sm-8 { -ms-flex-order: 8; order: 8; } .order-sm-9 { -ms-flex-order: 9; order: 9; } .order-sm-10 { -ms-flex-order: 10; order: 10; } .order-sm-11 { -ms-flex-order: 11; order: 11; } .order-sm-12 { -ms-flex-order: 12; order: 12; } .offset-sm-0 { margin-left: 0; } .offset-sm-1 { margin-left: 8.333333%; } .offset-sm-2 { margin-left: 16.666667%; } .offset-sm-3 { margin-left: 25%; } .offset-sm-4 { margin-left: 33.333333%; } .offset-sm-5 { margin-left: 41.666667%; } .offset-sm-6 { margin-left: 50%; } .offset-sm-7 { margin-left: 58.333333%; } .offset-sm-8 { margin-left: 66.666667%; } .offset-sm-9 { margin-left: 75%; } .offset-sm-10 { margin-left: 83.333333%; } .offset-sm-11 { margin-left: 91.666667%; } } @media (min-width: 768px) { .col-md { -ms-flex-preferred-size: 0; flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; min-width: 0; max-width: 100%; } .row-cols-md-1 > * { -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .row-cols-md-2 > * { -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .row-cols-md-3 > * { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; max-width: 33.333333%; } .row-cols-md-4 > * { -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .row-cols-md-5 > * { -ms-flex: 0 0 20%; flex: 0 0 20%; max-width: 20%; } .row-cols-md-6 > * { -ms-flex: 0 0 16.666667%; flex: 0 0 16.666667%; max-width: 16.666667%; } .col-md-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; width: auto; max-width: 100%; } .col-md-1 { -ms-flex: 0 0 8.333333%; flex: 0 0 8.333333%; max-width: 8.333333%; } .col-md-2 { -ms-flex: 0 0 16.666667%; flex: 0 0 16.666667%; max-width: 16.666667%; } .col-md-3 { -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .col-md-4 { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; max-width: 33.333333%; } .col-md-5 { -ms-flex: 0 0 41.666667%; flex: 0 0 41.666667%; max-width: 41.666667%; } .col-md-6 { -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .col-md-7 { -ms-flex: 0 0 58.333333%; flex: 0 0 58.333333%; max-width: 58.333333%; } .col-md-8 { -ms-flex: 0 0 66.666667%; flex: 0 0 66.666667%; max-width: 66.666667%; } .col-md-9 { -ms-flex: 0 0 75%; flex: 0 0 75%; max-width: 75%; } .col-md-10 { -ms-flex: 0 0 83.333333%; flex: 0 0 83.333333%; max-width: 83.333333%; } .col-md-11 { -ms-flex: 0 0 91.666667%; flex: 0 0 91.666667%; max-width: 91.666667%; } .col-md-12 { -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .order-md-first { -ms-flex-order: -1; order: -1; } .order-md-last { -ms-flex-order: 13; order: 13; } .order-md-0 { -ms-flex-order: 0; order: 0; } .order-md-1 { -ms-flex-order: 1; order: 1; } .order-md-2 { -ms-flex-order: 2; order: 2; } .order-md-3 { -ms-flex-order: 3; order: 3; } .order-md-4 { -ms-flex-order: 4; order: 4; } .order-md-5 { -ms-flex-order: 5; order: 5; } .order-md-6 { -ms-flex-order: 6; order: 6; } .order-md-7 { -ms-flex-order: 7; order: 7; } .order-md-8 { -ms-flex-order: 8; order: 8; } .order-md-9 { -ms-flex-order: 9; order: 9; } .order-md-10 { -ms-flex-order: 10; order: 10; } .order-md-11 { -ms-flex-order: 11; order: 11; } .order-md-12 { -ms-flex-order: 12; order: 12; } .offset-md-0 { margin-left: 0; } .offset-md-1 { margin-left: 8.333333%; } .offset-md-2 { margin-left: 16.666667%; } .offset-md-3 { margin-left: 25%; } .offset-md-4 { margin-left: 33.333333%; } .offset-md-5 { margin-left: 41.666667%; } .offset-md-6 { margin-left: 50%; } .offset-md-7 { margin-left: 58.333333%; } .offset-md-8 { margin-left: 66.666667%; } .offset-md-9 { margin-left: 75%; } .offset-md-10 { margin-left: 83.333333%; } .offset-md-11 { margin-left: 91.666667%; } } @media (min-width: 992px) { .col-lg { -ms-flex-preferred-size: 0; flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; min-width: 0; max-width: 100%; } .row-cols-lg-1 > * { -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .row-cols-lg-2 > * { -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .row-cols-lg-3 > * { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; max-width: 33.333333%; } .row-cols-lg-4 > * { -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .row-cols-lg-5 > * { -ms-flex: 0 0 20%; flex: 0 0 20%; max-width: 20%; } .row-cols-lg-6 > * { -ms-flex: 0 0 16.666667%; flex: 0 0 16.666667%; max-width: 16.666667%; } .col-lg-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; width: auto; max-width: 100%; } .col-lg-1 { -ms-flex: 0 0 8.333333%; flex: 0 0 8.333333%; max-width: 8.333333%; } .col-lg-2 { -ms-flex: 0 0 16.666667%; flex: 0 0 16.666667%; max-width: 16.666667%; } .col-lg-3 { -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .col-lg-4 { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; max-width: 33.333333%; } .col-lg-5 { -ms-flex: 0 0 41.666667%; flex: 0 0 41.666667%; max-width: 41.666667%; } .col-lg-6 { -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .col-lg-7 { -ms-flex: 0 0 58.333333%; flex: 0 0 58.333333%; max-width: 58.333333%; } .col-lg-8 { -ms-flex: 0 0 66.666667%; flex: 0 0 66.666667%; max-width: 66.666667%; } .col-lg-9 { -ms-flex: 0 0 75%; flex: 0 0 75%; max-width: 75%; } .col-lg-10 { -ms-flex: 0 0 83.333333%; flex: 0 0 83.333333%; max-width: 83.333333%; } .col-lg-11 { -ms-flex: 0 0 91.666667%; flex: 0 0 91.666667%; max-width: 91.666667%; } .col-lg-12 { -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .order-lg-first { -ms-flex-order: -1; order: -1; } .order-lg-last { -ms-flex-order: 13; order: 13; } .order-lg-0 { -ms-flex-order: 0; order: 0; } .order-lg-1 { -ms-flex-order: 1; order: 1; } .order-lg-2 { -ms-flex-order: 2; order: 2; } .order-lg-3 { -ms-flex-order: 3; order: 3; } .order-lg-4 { -ms-flex-order: 4; order: 4; } .order-lg-5 { -ms-flex-order: 5; order: 5; } .order-lg-6 { -ms-flex-order: 6; order: 6; } .order-lg-7 { -ms-flex-order: 7; order: 7; } .order-lg-8 { -ms-flex-order: 8; order: 8; } .order-lg-9 { -ms-flex-order: 9; order: 9; } .order-lg-10 { -ms-flex-order: 10; order: 10; } .order-lg-11 { -ms-flex-order: 11; order: 11; } .order-lg-12 { -ms-flex-order: 12; order: 12; } .offset-lg-0 { margin-left: 0; } .offset-lg-1 { margin-left: 8.333333%; } .offset-lg-2 { margin-left: 16.666667%; } .offset-lg-3 { margin-left: 25%; } .offset-lg-4 { margin-left: 33.333333%; } .offset-lg-5 { margin-left: 41.666667%; } .offset-lg-6 { margin-left: 50%; } .offset-lg-7 { margin-left: 58.333333%; } .offset-lg-8 { margin-left: 66.666667%; } .offset-lg-9 { margin-left: 75%; } .offset-lg-10 { margin-left: 83.333333%; } .offset-lg-11 { margin-left: 91.666667%; } } @media (min-width: 1200px) { .col-xl { -ms-flex-preferred-size: 0; flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; min-width: 0; max-width: 100%; } .row-cols-xl-1 > * { -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .row-cols-xl-2 > * { -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .row-cols-xl-3 > * { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; max-width: 33.333333%; } .row-cols-xl-4 > * { -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .row-cols-xl-5 > * { -ms-flex: 0 0 20%; flex: 0 0 20%; max-width: 20%; } .row-cols-xl-6 > * { -ms-flex: 0 0 16.666667%; flex: 0 0 16.666667%; max-width: 16.666667%; } .col-xl-auto { -ms-flex: 0 0 auto; flex: 0 0 auto; width: auto; max-width: 100%; } .col-xl-1 { -ms-flex: 0 0 8.333333%; flex: 0 0 8.333333%; max-width: 8.333333%; } .col-xl-2 { -ms-flex: 0 0 16.666667%; flex: 0 0 16.666667%; max-width: 16.666667%; } .col-xl-3 { -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; } .col-xl-4 { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; max-width: 33.333333%; } .col-xl-5 { -ms-flex: 0 0 41.666667%; flex: 0 0 41.666667%; max-width: 41.666667%; } .col-xl-6 { -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .col-xl-7 { -ms-flex: 0 0 58.333333%; flex: 0 0 58.333333%; max-width: 58.333333%; } .col-xl-8 { -ms-flex: 0 0 66.666667%; flex: 0 0 66.666667%; max-width: 66.666667%; } .col-xl-9 { -ms-flex: 0 0 75%; flex: 0 0 75%; max-width: 75%; } .col-xl-10 { -ms-flex: 0 0 83.333333%; flex: 0 0 83.333333%; max-width: 83.333333%; } .col-xl-11 { -ms-flex: 0 0 91.666667%; flex: 0 0 91.666667%; max-width: 91.666667%; } .col-xl-12 { -ms-flex: 0 0 100%; flex: 0 0 100%; max-width: 100%; } .order-xl-first { -ms-flex-order: -1; order: -1; } .order-xl-last { -ms-flex-order: 13; order: 13; } .order-xl-0 { -ms-flex-order: 0; order: 0; } .order-xl-1 { -ms-flex-order: 1; order: 1; } .order-xl-2 { -ms-flex-order: 2; order: 2; } .order-xl-3 { -ms-flex-order: 3; order: 3; } .order-xl-4 { -ms-flex-order: 4; order: 4; } .order-xl-5 { -ms-flex-order: 5; order: 5; } .order-xl-6 { -ms-flex-order: 6; order: 6; } .order-xl-7 { -ms-flex-order: 7; order: 7; } .order-xl-8 { -ms-flex-order: 8; order: 8; } .order-xl-9 { -ms-flex-order: 9; order: 9; } .order-xl-10 { -ms-flex-order: 10; order: 10; } .order-xl-11 { -ms-flex-order: 11; order: 11; } .order-xl-12 { -ms-flex-order: 12; order: 12; } .offset-xl-0 { margin-left: 0; } .offset-xl-1 { margin-left: 8.333333%; } .offset-xl-2 { margin-left: 16.666667%; } .offset-xl-3 { margin-left: 25%; } .offset-xl-4 { margin-left: 33.333333%; } .offset-xl-5 { margin-left: 41.666667%; } .offset-xl-6 { margin-left: 50%; } .offset-xl-7 { margin-left: 58.333333%; } .offset-xl-8 { margin-left: 66.666667%; } .offset-xl-9 { margin-left: 75%; } .offset-xl-10 { margin-left: 83.333333%; } .offset-xl-11 { margin-left: 91.666667%; } } .table { width: 100%; margin-bottom: 1rem; color: #212529; } .table th, .table td { padding: 0.75rem; vertical-align: top; border-top: 1px solid #dee2e6; } .table thead th { vertical-align: bottom; border-bottom: 2px solid #dee2e6; } .table tbody + tbody { border-top: 2px solid #dee2e6; } .table-sm th, .table-sm td { padding: 0.3rem; } .table-bordered { border: 1px solid #dee2e6; } .table-bordered th, .table-bordered td { border: 1px solid #dee2e6; } .table-bordered thead th, .table-bordered thead td { border-bottom-width: 2px; } .table-borderless th, .table-borderless td, .table-borderless thead th, .table-borderless tbody + tbody { border: 0; } .table-striped tbody tr:nth-of-type(odd) { background-color: rgba(0, 0, 0, 0.05); } .table-hover tbody tr:hover { color: #212529; background-color: rgba(0, 0, 0, 0.075); } .table-primary, .table-primary > th, .table-primary > td { background-color: #b8daff; } .table-primary th, .table-primary td, .table-primary thead th, .table-primary tbody + tbody { border-color: #7abaff; } .table-hover .table-primary:hover { background-color: #9fcdff; } .table-hover .table-primary:hover > td, .table-hover .table-primary:hover > th { background-color: #9fcdff; } .table-secondary, .table-secondary > th, .table-secondary > td { background-color: #d6d8db; } .table-secondary th, .table-secondary td, .table-secondary thead th, .table-secondary tbody + tbody { border-color: #b3b7bb; } .table-hover .table-secondary:hover { background-color: #c8cbcf; } .table-hover .table-secondary:hover > td, .table-hover .table-secondary:hover > th { background-color: #c8cbcf; } .table-success, .table-success > th, .table-success > td { background-color: #c3e6cb; } .table-success th, .table-success td, .table-success thead th, .table-success tbody + tbody { border-color: #8fd19e; } .table-hover .table-success:hover { background-color: #b1dfbb; } .table-hover .table-success:hover > td, .table-hover .table-success:hover > th { background-color: #b1dfbb; } .table-info, .table-info > th, .table-info > td { background-color: #bee5eb; } .table-info th, .table-info td, .table-info thead th, .table-info tbody + tbody { border-color: #86cfda; } .table-hover .table-info:hover { background-color: #abdde5; } .table-hover .table-info:hover > td, .table-hover .table-info:hover > th { background-color: #abdde5; } .table-warning, .table-warning > th, .table-warning > td { background-color: #ffeeba; } .table-warning th, .table-warning td, .table-warning thead th, .table-warning tbody + tbody { border-color: #ffdf7e; } .table-hover .table-warning:hover { background-color: #ffe8a1; } .table-hover .table-warning:hover > td, .table-hover .table-warning:hover > th { background-color: #ffe8a1; } .table-danger, .table-danger > th, .table-danger > td { background-color: #f5c6cb; } .table-danger th, .table-danger td, .table-danger thead th, .table-danger tbody + tbody { border-color: #ed969e; } .table-hover .table-danger:hover { background-color: #f1b0b7; } .table-hover .table-danger:hover > td, .table-hover .table-danger:hover > th { background-color: #f1b0b7; } .table-light, .table-light > th, .table-light > td { background-color: #fdfdfe; } .table-light th, .table-light td, .table-light thead th, .table-light tbody + tbody { border-color: #fbfcfc; } .table-hover .table-light:hover { background-color: #ececf6; } .table-hover .table-light:hover > td, .table-hover .table-light:hover > th { background-color: #ececf6; } .table-dark, .table-dark > th, .table-dark > td { background-color: #c6c8ca; } .table-dark th, .table-dark td, .table-dark thead th, .table-dark tbody + tbody { border-color: #95999c; } .table-hover .table-dark:hover { background-color: #b9bbbe; } .table-hover .table-dark:hover > td, .table-hover .table-dark:hover > th { background-color: #b9bbbe; } .table-active, .table-active > th, .table-active > td { background-color: rgba(0, 0, 0, 0.075); } .table-hover .table-active:hover { background-color: rgba(0, 0, 0, 0.075); } .table-hover .table-active:hover > td, .table-hover .table-active:hover > th { background-color: rgba(0, 0, 0, 0.075); } .table .thead-dark th { color: #fff; background-color: #343a40; border-color: #454d55; } .table .thead-light th { color: #495057; background-color: #e9ecef; border-color: #dee2e6; } .table-dark { color: #fff; background-color: #343a40; } .table-dark th, .table-dark td, .table-dark thead th { border-color: #454d55; } .table-dark.table-bordered { border: 0; } .table-dark.table-striped tbody tr:nth-of-type(odd) { background-color: rgba(255, 255, 255, 0.05); } .table-dark.table-hover tbody tr:hover { color: #fff; background-color: rgba(255, 255, 255, 0.075); } @media (max-width: 575.98px) { .table-responsive-sm { display: block; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; } .table-responsive-sm > .table-bordered { border: 0; } } @media (max-width: 767.98px) { .table-responsive-md { display: block; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; } .table-responsive-md > .table-bordered { border: 0; } } @media (max-width: 991.98px) { .table-responsive-lg { display: block; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; } .table-responsive-lg > .table-bordered { border: 0; } } @media (max-width: 1199.98px) { .table-responsive-xl { display: block; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; } .table-responsive-xl > .table-bordered { border: 0; } } .table-responsive { display: block; width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; } .table-responsive > .table-bordered { border: 0; } .form-control { display: block; width: 100%; height: calc(1.5em + 0.75rem + 2px); padding: 0.375rem 0.75rem; font-size: 1rem; font-weight: 400; line-height: 1.5; color: #495057; background-color: #fff; background-clip: padding-box; border: 1px solid #ced4da; border-radius: 0.25rem; transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } @media (prefers-reduced-motion: reduce) { .form-control { transition: none; } } .form-control::-ms-expand { background-color: transparent; border: 0; } .form-control:-moz-focusring { color: transparent; text-shadow: 0 0 0 #495057; } .form-control:focus { color: #495057; background-color: #fff; border-color: #80bdff; outline: 0; box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } .form-control::-webkit-input-placeholder { color: #6c757d; opacity: 1; } .form-control::-moz-placeholder { color: #6c757d; opacity: 1; } .form-control:-ms-input-placeholder { color: #6c757d; opacity: 1; } .form-control::-ms-input-placeholder { color: #6c757d; opacity: 1; } .form-control::placeholder { color: #6c757d; opacity: 1; } .form-control:disabled, .form-control[readonly] { background-color: #e9ecef; opacity: 1; } input[type="date"].form-control, input[type="time"].form-control, input[type="datetime-local"].form-control, input[type="month"].form-control { -webkit-appearance: none; -moz-appearance: none; appearance: none; } select.form-control:focus::-ms-value { color: #495057; background-color: #fff; } .form-control-file, .form-control-range { display: block; width: 100%; } .col-form-label { padding-top: calc(0.375rem + 1px); padding-bottom: calc(0.375rem + 1px); margin-bottom: 0; font-size: inherit; line-height: 1.5; } .col-form-label-lg { padding-top: calc(0.5rem + 1px); padding-bottom: calc(0.5rem + 1px); font-size: 1.25rem; line-height: 1.5; } .col-form-label-sm { padding-top: calc(0.25rem + 1px); padding-bottom: calc(0.25rem + 1px); font-size: 0.875rem; line-height: 1.5; } .form-control-plaintext { display: block; width: 100%; padding: 0.375rem 0; margin-bottom: 0; font-size: 1rem; line-height: 1.5; color: #212529; background-color: transparent; border: solid transparent; border-width: 1px 0; } .form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg { padding-right: 0; padding-left: 0; } .form-control-sm { height: calc(1.5em + 0.5rem + 2px); padding: 0.25rem 0.5rem; font-size: 0.875rem; line-height: 1.5; border-radius: 0.2rem; } .form-control-lg { height: calc(1.5em + 1rem + 2px); padding: 0.5rem 1rem; font-size: 1.25rem; line-height: 1.5; border-radius: 0.3rem; } select.form-control[size], select.form-control[multiple] { height: auto; } textarea.form-control { height: auto; } .form-group { margin-bottom: 1rem; } .form-text { display: block; margin-top: 0.25rem; } .form-row { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; margin-right: -5px; margin-left: -5px; } .form-row > .col, .form-row > [class*="col-"] { padding-right: 5px; padding-left: 5px; } .form-check { position: relative; display: block; padding-left: 1.25rem; } .form-check-input { position: absolute; margin-top: 0.3rem; margin-left: -1.25rem; } .form-check-input[disabled] ~ .form-check-label, .form-check-input:disabled ~ .form-check-label { color: #6c757d; } .form-check-label { margin-bottom: 0; } .form-check-inline { display: -ms-inline-flexbox; display: inline-flex; -ms-flex-align: center; align-items: center; padding-left: 0; margin-right: 0.75rem; } .form-check-inline .form-check-input { position: static; margin-top: 0; margin-right: 0.3125rem; margin-left: 0; } .valid-feedback { display: none; width: 100%; margin-top: 0.25rem; font-size: 80%; color: #28a745; } .valid-tooltip { position: absolute; top: 100%; z-index: 5; display: none; max-width: 100%; padding: 0.25rem 0.5rem; margin-top: .1rem; font-size: 0.875rem; line-height: 1.5; color: #fff; background-color: rgba(40, 167, 69, 0.9); border-radius: 0.25rem; } .was-validated :valid ~ .valid-feedback, .was-validated :valid ~ .valid-tooltip, .is-valid ~ .valid-feedback, .is-valid ~ .valid-tooltip { display: block; } .was-validated .form-control:valid, .form-control.is-valid { border-color: #28a745; padding-right: calc(1.5em + 0.75rem); background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); background-repeat: no-repeat; background-position: right calc(0.375em + 0.1875rem) center; background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); } .was-validated .form-control:valid:focus, .form-control.is-valid:focus { border-color: #28a745; box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); } .was-validated textarea.form-control:valid, textarea.form-control.is-valid { padding-right: calc(1.5em + 0.75rem); background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem); } .was-validated .custom-select:valid, .custom-select.is-valid { border-color: #28a745; padding-right: calc(0.75em + 2.3125rem); background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px, url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); } .was-validated .custom-select:valid:focus, .custom-select.is-valid:focus { border-color: #28a745; box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); } .was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label { color: #28a745; } .was-validated .form-check-input:valid ~ .valid-feedback, .was-validated .form-check-input:valid ~ .valid-tooltip, .form-check-input.is-valid ~ .valid-feedback, .form-check-input.is-valid ~ .valid-tooltip { display: block; } .was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label { color: #28a745; } .was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before { border-color: #28a745; } .was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before { border-color: #34ce57; background-color: #34ce57; } .was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before { box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); } .was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before { border-color: #28a745; } .was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label { border-color: #28a745; } .was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label { border-color: #28a745; box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); } .invalid-feedback { display: none; width: 100%; margin-top: 0.25rem; font-size: 80%; color: #dc3545; } .invalid-tooltip { position: absolute; top: 100%; z-index: 5; display: none; max-width: 100%; padding: 0.25rem 0.5rem; margin-top: .1rem; font-size: 0.875rem; line-height: 1.5; color: #fff; background-color: rgba(220, 53, 69, 0.9); border-radius: 0.25rem; } .was-validated :invalid ~ .invalid-feedback, .was-validated :invalid ~ .invalid-tooltip, .is-invalid ~ .invalid-feedback, .is-invalid ~ .invalid-tooltip { display: block; } .was-validated .form-control:invalid, .form-control.is-invalid { border-color: #dc3545; padding-right: calc(1.5em + 0.75rem); background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e"); background-repeat: no-repeat; background-position: right calc(0.375em + 0.1875rem) center; background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); } .was-validated .form-control:invalid:focus, .form-control.is-invalid:focus { border-color: #dc3545; box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); } .was-validated textarea.form-control:invalid, textarea.form-control.is-invalid { padding-right: calc(1.5em + 0.75rem); background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem); } .was-validated .custom-select:invalid, .custom-select.is-invalid { border-color: #dc3545; padding-right: calc(0.75em + 2.3125rem); background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px, url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); } .was-validated .custom-select:invalid:focus, .custom-select.is-invalid:focus { border-color: #dc3545; box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); } .was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label { color: #dc3545; } .was-validated .form-check-input:invalid ~ .invalid-feedback, .was-validated .form-check-input:invalid ~ .invalid-tooltip, .form-check-input.is-invalid ~ .invalid-feedback, .form-check-input.is-invalid ~ .invalid-tooltip { display: block; } .was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label { color: #dc3545; } .was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before { border-color: #dc3545; } .was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before { border-color: #e4606d; background-color: #e4606d; } .was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before { box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); } .was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before { border-color: #dc3545; } .was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label { border-color: #dc3545; } .was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label { border-color: #dc3545; box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); } .form-inline { display: -ms-flexbox; display: flex; -ms-flex-flow: row wrap; flex-flow: row wrap; -ms-flex-align: center; align-items: center; } .form-inline .form-check { width: 100%; } @media (min-width: 576px) { .form-inline label { display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; -ms-flex-pack: center; justify-content: center; margin-bottom: 0; } .form-inline .form-group { display: -ms-flexbox; display: flex; -ms-flex: 0 0 auto; flex: 0 0 auto; -ms-flex-flow: row wrap; flex-flow: row wrap; -ms-flex-align: center; align-items: center; margin-bottom: 0; } .form-inline .form-control { display: inline-block; width: auto; vertical-align: middle; } .form-inline .form-control-plaintext { display: inline-block; } .form-inline .input-group, .form-inline .custom-select { width: auto; } .form-inline .form-check { display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; -ms-flex-pack: center; justify-content: center; width: auto; padding-left: 0; } .form-inline .form-check-input { position: relative; -ms-flex-negative: 0; flex-shrink: 0; margin-top: 0; margin-right: 0.25rem; margin-left: 0; } .form-inline .custom-control { -ms-flex-align: center; align-items: center; -ms-flex-pack: center; justify-content: center; } .form-inline .custom-control-label { margin-bottom: 0; } } .btn { display: inline-block; font-weight: 400; color: #212529; text-align: center; vertical-align: middle; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; background-color: transparent; border: 1px solid transparent; padding: 0.375rem 0.75rem; font-size: 1rem; line-height: 1.5; border-radius: 0.25rem; transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } @media (prefers-reduced-motion: reduce) { .btn { transition: none; } } .btn:hover { color: #212529; text-decoration: none; } .btn:focus, .btn.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } .btn.disabled, .btn:disabled { opacity: 0.65; } .btn:not(:disabled):not(.disabled) { cursor: pointer; } a.btn.disabled, fieldset:disabled a.btn { pointer-events: none; } .btn-primary { color: #fff; background-color: #007bff; border-color: #007bff; } .btn-primary:hover { color: #fff; background-color: #0069d9; border-color: #0062cc; } .btn-primary:focus, .btn-primary.focus { color: #fff; background-color: #0069d9; border-color: #0062cc; box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5); } .btn-primary.disabled, .btn-primary:disabled { color: #fff; background-color: #007bff; border-color: #007bff; } .btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active, .show > .btn-primary.dropdown-toggle { color: #fff; background-color: #0062cc; border-color: #005cbf; } .btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus, .show > .btn-primary.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5); } .btn-secondary { color: #fff; background-color: #6c757d; border-color: #6c757d; } .btn-secondary:hover { color: #fff; background-color: #5a6268; border-color: #545b62; } .btn-secondary:focus, .btn-secondary.focus { color: #fff; background-color: #5a6268; border-color: #545b62; box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5); } .btn-secondary.disabled, .btn-secondary:disabled { color: #fff; background-color: #6c757d; border-color: #6c757d; } .btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active, .show > .btn-secondary.dropdown-toggle { color: #fff; background-color: #545b62; border-color: #4e555b; } .btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus, .show > .btn-secondary.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5); } .btn-success { color: #fff; background-color: #28a745; border-color: #28a745; } .btn-success:hover { color: #fff; background-color: #218838; border-color: #1e7e34; } .btn-success:focus, .btn-success.focus { color: #fff; background-color: #218838; border-color: #1e7e34; box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5); } .btn-success.disabled, .btn-success:disabled { color: #fff; background-color: #28a745; border-color: #28a745; } .btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active, .show > .btn-success.dropdown-toggle { color: #fff; background-color: #1e7e34; border-color: #1c7430; } .btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus, .show > .btn-success.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5); } .btn-info { color: #fff; background-color: #17a2b8; border-color: #17a2b8; } .btn-info:hover { color: #fff; background-color: #138496; border-color: #117a8b; } .btn-info:focus, .btn-info.focus { color: #fff; background-color: #138496; border-color: #117a8b; box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5); } .btn-info.disabled, .btn-info:disabled { color: #fff; background-color: #17a2b8; border-color: #17a2b8; } .btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active, .show > .btn-info.dropdown-toggle { color: #fff; background-color: #117a8b; border-color: #10707f; } .btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus, .show > .btn-info.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5); } .btn-warning { color: #212529; background-color: #ffc107; border-color: #ffc107; } .btn-warning:hover { color: #212529; background-color: #e0a800; border-color: #d39e00; } .btn-warning:focus, .btn-warning.focus { color: #212529; background-color: #e0a800; border-color: #d39e00; box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5); } .btn-warning.disabled, .btn-warning:disabled { color: #212529; background-color: #ffc107; border-color: #ffc107; } .btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active, .show > .btn-warning.dropdown-toggle { color: #212529; background-color: #d39e00; border-color: #c69500; } .btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus, .show > .btn-warning.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5); } .btn-danger { color: #fff; background-color: #dc3545; border-color: #dc3545; } .btn-danger:hover { color: #fff; background-color: #c82333; border-color: #bd2130; } .btn-danger:focus, .btn-danger.focus { color: #fff; background-color: #c82333; border-color: #bd2130; box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5); } .btn-danger.disabled, .btn-danger:disabled { color: #fff; background-color: #dc3545; border-color: #dc3545; } .btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active, .show > .btn-danger.dropdown-toggle { color: #fff; background-color: #bd2130; border-color: #b21f2d; } .btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus, .show > .btn-danger.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5); } .btn-light { color: #212529; background-color: #f8f9fa; border-color: #f8f9fa; } .btn-light:hover { color: #212529; background-color: #e2e6ea; border-color: #dae0e5; } .btn-light:focus, .btn-light.focus { color: #212529; background-color: #e2e6ea; border-color: #dae0e5; box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5); } .btn-light.disabled, .btn-light:disabled { color: #212529; background-color: #f8f9fa; border-color: #f8f9fa; } .btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active, .show > .btn-light.dropdown-toggle { color: #212529; background-color: #dae0e5; border-color: #d3d9df; } .btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus, .show > .btn-light.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5); } .btn-dark { color: #fff; background-color: #343a40; border-color: #343a40; } .btn-dark:hover { color: #fff; background-color: #23272b; border-color: #1d2124; } .btn-dark:focus, .btn-dark.focus { color: #fff; background-color: #23272b; border-color: #1d2124; box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5); } .btn-dark.disabled, .btn-dark:disabled { color: #fff; background-color: #343a40; border-color: #343a40; } .btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active, .show > .btn-dark.dropdown-toggle { color: #fff; background-color: #1d2124; border-color: #171a1d; } .btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus, .show > .btn-dark.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5); } .btn-outline-primary { color: #007bff; border-color: #007bff; } .btn-outline-primary:hover { color: #fff; background-color: #007bff; border-color: #007bff; } .btn-outline-primary:focus, .btn-outline-primary.focus { box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); } .btn-outline-primary.disabled, .btn-outline-primary:disabled { color: #007bff; background-color: transparent; } .btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active, .show > .btn-outline-primary.dropdown-toggle { color: #fff; background-color: #007bff; border-color: #007bff; } .btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-primary.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); } .btn-outline-secondary { color: #6c757d; border-color: #6c757d; } .btn-outline-secondary:hover { color: #fff; background-color: #6c757d; border-color: #6c757d; } .btn-outline-secondary:focus, .btn-outline-secondary.focus { box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); } .btn-outline-secondary.disabled, .btn-outline-secondary:disabled { color: #6c757d; background-color: transparent; } .btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active, .show > .btn-outline-secondary.dropdown-toggle { color: #fff; background-color: #6c757d; border-color: #6c757d; } .btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-secondary.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); } .btn-outline-success { color: #28a745; border-color: #28a745; } .btn-outline-success:hover { color: #fff; background-color: #28a745; border-color: #28a745; } .btn-outline-success:focus, .btn-outline-success.focus { box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); } .btn-outline-success.disabled, .btn-outline-success:disabled { color: #28a745; background-color: transparent; } .btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active, .show > .btn-outline-success.dropdown-toggle { color: #fff; background-color: #28a745; border-color: #28a745; } .btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-success.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); } .btn-outline-info { color: #17a2b8; border-color: #17a2b8; } .btn-outline-info:hover { color: #fff; background-color: #17a2b8; border-color: #17a2b8; } .btn-outline-info:focus, .btn-outline-info.focus { box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } .btn-outline-info.disabled, .btn-outline-info:disabled { color: #17a2b8; background-color: transparent; } .btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active, .show > .btn-outline-info.dropdown-toggle { color: #fff; background-color: #17a2b8; border-color: #17a2b8; } .btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-info.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } .btn-outline-warning { color: #ffc107; border-color: #ffc107; } .btn-outline-warning:hover { color: #212529; background-color: #ffc107; border-color: #ffc107; } .btn-outline-warning:focus, .btn-outline-warning.focus { box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); } .btn-outline-warning.disabled, .btn-outline-warning:disabled { color: #ffc107; background-color: transparent; } .btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active, .show > .btn-outline-warning.dropdown-toggle { color: #212529; background-color: #ffc107; border-color: #ffc107; } .btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-warning.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); } .btn-outline-danger { color: #dc3545; border-color: #dc3545; } .btn-outline-danger:hover { color: #fff; background-color: #dc3545; border-color: #dc3545; } .btn-outline-danger:focus, .btn-outline-danger.focus { box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); } .btn-outline-danger.disabled, .btn-outline-danger:disabled { color: #dc3545; background-color: transparent; } .btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active, .show > .btn-outline-danger.dropdown-toggle { color: #fff; background-color: #dc3545; border-color: #dc3545; } .btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-danger.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); } .btn-outline-light { color: #f8f9fa; border-color: #f8f9fa; } .btn-outline-light:hover { color: #212529; background-color: #f8f9fa; border-color: #f8f9fa; } .btn-outline-light:focus, .btn-outline-light.focus { box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); } .btn-outline-light.disabled, .btn-outline-light:disabled { color: #f8f9fa; background-color: transparent; } .btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active, .show > .btn-outline-light.dropdown-toggle { color: #212529; background-color: #f8f9fa; border-color: #f8f9fa; } .btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-light.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); } .btn-outline-dark { color: #343a40; border-color: #343a40; } .btn-outline-dark:hover { color: #fff; background-color: #343a40; border-color: #343a40; } .btn-outline-dark:focus, .btn-outline-dark.focus { box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); } .btn-outline-dark.disabled, .btn-outline-dark:disabled { color: #343a40; background-color: transparent; } .btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active, .show > .btn-outline-dark.dropdown-toggle { color: #fff; background-color: #343a40; border-color: #343a40; } .btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-dark.dropdown-toggle:focus { box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); } .btn-link { font-weight: 400; color: #007bff; text-decoration: none; } .btn-link:hover { color: #0056b3; text-decoration: underline; } .btn-link:focus, .btn-link.focus { text-decoration: underline; } .btn-link:disabled, .btn-link.disabled { color: #6c757d; pointer-events: none; } .btn-lg, .btn-group-lg > .btn { padding: 0.5rem 1rem; font-size: 1.25rem; line-height: 1.5; border-radius: 0.3rem; } .btn-sm, .btn-group-sm > .btn { padding: 0.25rem 0.5rem; font-size: 0.875rem; line-height: 1.5; border-radius: 0.2rem; } .btn-block { display: block; width: 100%; } .btn-block + .btn-block { margin-top: 0.5rem; } input[type="submit"].btn-block, input[type="reset"].btn-block, input[type="button"].btn-block { width: 100%; } .fade { transition: opacity 0.15s linear; } @media (prefers-reduced-motion: reduce) { .fade { transition: none; } } .fade:not(.show) { opacity: 0; } .collapse:not(.show) { display: none; } .collapsing { position: relative; height: 0; overflow: hidden; transition: height 0.35s ease; } @media (prefers-reduced-motion: reduce) { .collapsing { transition: none; } } .dropup, .dropright, .dropdown, .dropleft { position: relative; } .dropdown-toggle { white-space: nowrap; } .dropdown-toggle::after { display: inline-block; margin-left: 0.255em; vertical-align: 0.255em; content: ""; border-top: 0.3em solid; border-right: 0.3em solid transparent; border-bottom: 0; border-left: 0.3em solid transparent; } .dropdown-toggle:empty::after { margin-left: 0; } .dropdown-menu { position: absolute; top: 100%; left: 0; z-index: 1000; display: none; float: left; min-width: 10rem; padding: 0.5rem 0; margin: 0.125rem 0 0; font-size: 1rem; color: #212529; text-align: left; list-style: none; background-color: #fff; background-clip: padding-box; border: 1px solid rgba(0, 0, 0, 0.15); border-radius: 0.25rem; } .dropdown-menu-left { right: auto; left: 0; } .dropdown-menu-right { right: 0; left: auto; } @media (min-width: 576px) { .dropdown-menu-sm-left { right: auto; left: 0; } .dropdown-menu-sm-right { right: 0; left: auto; } } @media (min-width: 768px) { .dropdown-menu-md-left { right: auto; left: 0; } .dropdown-menu-md-right { right: 0; left: auto; } } @media (min-width: 992px) { .dropdown-menu-lg-left { right: auto; left: 0; } .dropdown-menu-lg-right { right: 0; left: auto; } } @media (min-width: 1200px) { .dropdown-menu-xl-left { right: auto; left: 0; } .dropdown-menu-xl-right { right: 0; left: auto; } } .dropup .dropdown-menu { top: auto; bottom: 100%; margin-top: 0; margin-bottom: 0.125rem; } .dropup .dropdown-toggle::after { display: inline-block; margin-left: 0.255em; vertical-align: 0.255em; content: ""; border-top: 0; border-right: 0.3em solid transparent; border-bottom: 0.3em solid; border-left: 0.3em solid transparent; } .dropup .dropdown-toggle:empty::after { margin-left: 0; } .dropright .dropdown-menu { top: 0; right: auto; left: 100%; margin-top: 0; margin-left: 0.125rem; } .dropright .dropdown-toggle::after { display: inline-block; margin-left: 0.255em; vertical-align: 0.255em; content: ""; border-top: 0.3em solid transparent; border-right: 0; border-bottom: 0.3em solid transparent; border-left: 0.3em solid; } .dropright .dropdown-toggle:empty::after { margin-left: 0; } .dropright .dropdown-toggle::after { vertical-align: 0; } .dropleft .dropdown-menu { top: 0; right: 100%; left: auto; margin-top: 0; margin-right: 0.125rem; } .dropleft .dropdown-toggle::after { display: inline-block; margin-left: 0.255em; vertical-align: 0.255em; content: ""; } .dropleft .dropdown-toggle::after { display: none; } .dropleft .dropdown-toggle::before { display: inline-block; margin-right: 0.255em; vertical-align: 0.255em; content: ""; border-top: 0.3em solid transparent; border-right: 0.3em solid; border-bottom: 0.3em solid transparent; } .dropleft .dropdown-toggle:empty::after { margin-left: 0; } .dropleft .dropdown-toggle::before { vertical-align: 0; } .dropdown-menu[x-placement^="top"], .dropdown-menu[x-placement^="right"], .dropdown-menu[x-placement^="bottom"], .dropdown-menu[x-placement^="left"] { right: auto; bottom: auto; } .dropdown-divider { height: 0; margin: 0.5rem 0; overflow: hidden; border-top: 1px solid #e9ecef; } .dropdown-item { display: block; width: 100%; padding: 0.25rem 1.5rem; clear: both; font-weight: 400; color: #212529; text-align: inherit; white-space: nowrap; background-color: transparent; border: 0; } .dropdown-item:hover, .dropdown-item:focus { color: #16181b; text-decoration: none; background-color: #f8f9fa; } .dropdown-item.active, .dropdown-item:active { color: #fff; text-decoration: none; background-color: #007bff; } .dropdown-item.disabled, .dropdown-item:disabled { color: #6c757d; pointer-events: none; background-color: transparent; } .dropdown-menu.show { display: block; } .dropdown-header { display: block; padding: 0.5rem 1.5rem; margin-bottom: 0; font-size: 0.875rem; color: #6c757d; white-space: nowrap; } .dropdown-item-text { display: block; padding: 0.25rem 1.5rem; color: #212529; } .btn-group, .btn-group-vertical { position: relative; display: -ms-inline-flexbox; display: inline-flex; vertical-align: middle; } .btn-group > .btn, .btn-group-vertical > .btn { position: relative; -ms-flex: 1 1 auto; flex: 1 1 auto; } .btn-group > .btn:hover, .btn-group-vertical > .btn:hover { z-index: 1; } .btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active, .btn-group-vertical > .btn:focus, .btn-group-vertical > .btn:active, .btn-group-vertical > .btn.active { z-index: 1; } .btn-toolbar { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; -ms-flex-pack: start; justify-content: flex-start; } .btn-toolbar .input-group { width: auto; } .btn-group > .btn:not(:first-child), .btn-group > .btn-group:not(:first-child) { margin-left: -1px; } .btn-group > .btn:not(:last-child):not(.dropdown-toggle), .btn-group > .btn-group:not(:last-child) > .btn { border-top-right-radius: 0; border-bottom-right-radius: 0; } .btn-group > .btn:not(:first-child), .btn-group > .btn-group:not(:first-child) > .btn { border-top-left-radius: 0; border-bottom-left-radius: 0; } .dropdown-toggle-split { padding-right: 0.5625rem; padding-left: 0.5625rem; } .dropdown-toggle-split::after, .dropup .dropdown-toggle-split::after, .dropright .dropdown-toggle-split::after { margin-left: 0; } .dropleft .dropdown-toggle-split::before { margin-right: 0; } .btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split { padding-right: 0.375rem; padding-left: 0.375rem; } .btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split { padding-right: 0.75rem; padding-left: 0.75rem; } .btn-group-vertical { -ms-flex-direction: column; flex-direction: column; -ms-flex-align: start; align-items: flex-start; -ms-flex-pack: center; justify-content: center; } .btn-group-vertical > .btn, .btn-group-vertical > .btn-group { width: 100%; } .btn-group-vertical > .btn:not(:first-child), .btn-group-vertical > .btn-group:not(:first-child) { margin-top: -1px; } .btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle), .btn-group-vertical > .btn-group:not(:last-child) > .btn { border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .btn-group-vertical > .btn:not(:first-child), .btn-group-vertical > .btn-group:not(:first-child) > .btn { border-top-left-radius: 0; border-top-right-radius: 0; } .btn-group-toggle > .btn, .btn-group-toggle > .btn-group > .btn { margin-bottom: 0; } .btn-group-toggle > .btn input[type="radio"], .btn-group-toggle > .btn input[type="checkbox"], .btn-group-toggle > .btn-group > .btn input[type="radio"], .btn-group-toggle > .btn-group > .btn input[type="checkbox"] { position: absolute; clip: rect(0, 0, 0, 0); pointer-events: none; } .input-group { position: relative; display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; -ms-flex-align: stretch; align-items: stretch; width: 100%; } .input-group > .form-control, .input-group > .form-control-plaintext, .input-group > .custom-select, .input-group > .custom-file { position: relative; -ms-flex: 1 1 auto; flex: 1 1 auto; width: 1%; min-width: 0; margin-bottom: 0; } .input-group > .form-control + .form-control, .input-group > .form-control + .custom-select, .input-group > .form-control + .custom-file, .input-group > .form-control-plaintext + .form-control, .input-group > .form-control-plaintext + .custom-select, .input-group > .form-control-plaintext + .custom-file, .input-group > .custom-select + .form-control, .input-group > .custom-select + .custom-select, .input-group > .custom-select + .custom-file, .input-group > .custom-file + .form-control, .input-group > .custom-file + .custom-select, .input-group > .custom-file + .custom-file { margin-left: -1px; } .input-group > .form-control:focus, .input-group > .custom-select:focus, .input-group > .custom-file .custom-file-input:focus ~ .custom-file-label { z-index: 3; } .input-group > .custom-file .custom-file-input:focus { z-index: 4; } .input-group > .form-control:not(:last-child), .input-group > .custom-select:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .input-group > .form-control:not(:first-child), .input-group > .custom-select:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .input-group > .custom-file { display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; } .input-group > .custom-file:not(:last-child) .custom-file-label, .input-group > .custom-file:not(:last-child) .custom-file-label::after { border-top-right-radius: 0; border-bottom-right-radius: 0; } .input-group > .custom-file:not(:first-child) .custom-file-label { border-top-left-radius: 0; border-bottom-left-radius: 0; } .input-group-prepend, .input-group-append { display: -ms-flexbox; display: flex; } .input-group-prepend .btn, .input-group-append .btn { position: relative; z-index: 2; } .input-group-prepend .btn:focus, .input-group-append .btn:focus { z-index: 3; } .input-group-prepend .btn + .btn, .input-group-prepend .btn + .input-group-text, .input-group-prepend .input-group-text + .input-group-text, .input-group-prepend .input-group-text + .btn, .input-group-append .btn + .btn, .input-group-append .btn + .input-group-text, .input-group-append .input-group-text + .input-group-text, .input-group-append .input-group-text + .btn { margin-left: -1px; } .input-group-prepend { margin-right: -1px; } .input-group-append { margin-left: -1px; } .input-group-text { display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; padding: 0.375rem 0.75rem; margin-bottom: 0; font-size: 1rem; font-weight: 400; line-height: 1.5; color: #495057; text-align: center; white-space: nowrap; background-color: #e9ecef; border: 1px solid #ced4da; border-radius: 0.25rem; } .input-group-text input[type="radio"], .input-group-text input[type="checkbox"] { margin-top: 0; } .input-group-lg > .form-control:not(textarea), .input-group-lg > .custom-select { height: calc(1.5em + 1rem + 2px); } .input-group-lg > .form-control, .input-group-lg > .custom-select, .input-group-lg > .input-group-prepend > .input-group-text, .input-group-lg > .input-group-append > .input-group-text, .input-group-lg > .input-group-prepend > .btn, .input-group-lg > .input-group-append > .btn { padding: 0.5rem 1rem; font-size: 1.25rem; line-height: 1.5; border-radius: 0.3rem; } .input-group-sm > .form-control:not(textarea), .input-group-sm > .custom-select { height: calc(1.5em + 0.5rem + 2px); } .input-group-sm > .form-control, .input-group-sm > .custom-select, .input-group-sm > .input-group-prepend > .input-group-text, .input-group-sm > .input-group-append > .input-group-text, .input-group-sm > .input-group-prepend > .btn, .input-group-sm > .input-group-append > .btn { padding: 0.25rem 0.5rem; font-size: 0.875rem; line-height: 1.5; border-radius: 0.2rem; } .input-group-lg > .custom-select, .input-group-sm > .custom-select { padding-right: 1.75rem; } .input-group > .input-group-prepend > .btn, .input-group > .input-group-prepend > .input-group-text, .input-group > .input-group-append:not(:last-child) > .btn, .input-group > .input-group-append:not(:last-child) > .input-group-text, .input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle), .input-group > .input-group-append:last-child > .input-group-text:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .input-group > .input-group-append > .btn, .input-group > .input-group-append > .input-group-text, .input-group > .input-group-prepend:not(:first-child) > .btn, .input-group > .input-group-prepend:not(:first-child) > .input-group-text, .input-group > .input-group-prepend:first-child > .btn:not(:first-child), .input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .custom-control { position: relative; display: block; min-height: 1.5rem; padding-left: 1.5rem; } .custom-control-inline { display: -ms-inline-flexbox; display: inline-flex; margin-right: 1rem; } .custom-control-input { position: absolute; left: 0; z-index: -1; width: 1rem; height: 1.25rem; opacity: 0; } .custom-control-input:checked ~ .custom-control-label::before { color: #fff; border-color: #007bff; background-color: #007bff; } .custom-control-input:focus ~ .custom-control-label::before { box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } .custom-control-input:focus:not(:checked) ~ .custom-control-label::before { border-color: #80bdff; } .custom-control-input:not(:disabled):active ~ .custom-control-label::before { color: #fff; background-color: #b3d7ff; border-color: #b3d7ff; } .custom-control-input[disabled] ~ .custom-control-label, .custom-control-input:disabled ~ .custom-control-label { color: #6c757d; } .custom-control-input[disabled] ~ .custom-control-label::before, .custom-control-input:disabled ~ .custom-control-label::before { background-color: #e9ecef; } .custom-control-label { position: relative; margin-bottom: 0; vertical-align: top; } .custom-control-label::before { position: absolute; top: 0.25rem; left: -1.5rem; display: block; width: 1rem; height: 1rem; pointer-events: none; content: ""; background-color: #fff; border: #adb5bd solid 1px; } .custom-control-label::after { position: absolute; top: 0.25rem; left: -1.5rem; display: block; width: 1rem; height: 1rem; content: ""; background: no-repeat 50% / 50% 50%; } .custom-checkbox .custom-control-label::before { border-radius: 0.25rem; } .custom-checkbox .custom-control-input:checked ~ .custom-control-label::after { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e"); } .custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before { border-color: #007bff; background-color: #007bff; } .custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e"); } .custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before { background-color: rgba(0, 123, 255, 0.5); } .custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before { background-color: rgba(0, 123, 255, 0.5); } .custom-radio .custom-control-label::before { border-radius: 50%; } .custom-radio .custom-control-input:checked ~ .custom-control-label::after { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e"); } .custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before { background-color: rgba(0, 123, 255, 0.5); } .custom-switch { padding-left: 2.25rem; } .custom-switch .custom-control-label::before { left: -2.25rem; width: 1.75rem; pointer-events: all; border-radius: 0.5rem; } .custom-switch .custom-control-label::after { top: calc(0.25rem + 2px); left: calc(-2.25rem + 2px); width: calc(1rem - 4px); height: calc(1rem - 4px); background-color: #adb5bd; border-radius: 0.5rem; transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out; transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out; } @media (prefers-reduced-motion: reduce) { .custom-switch .custom-control-label::after { transition: none; } } .custom-switch .custom-control-input:checked ~ .custom-control-label::after { background-color: #fff; -webkit-transform: translateX(0.75rem); transform: translateX(0.75rem); } .custom-switch .custom-control-input:disabled:checked ~ .custom-control-label::before { background-color: rgba(0, 123, 255, 0.5); } .custom-select { display: inline-block; width: 100%; height: calc(1.5em + 0.75rem + 2px); padding: 0.375rem 1.75rem 0.375rem 0.75rem; font-size: 1rem; font-weight: 400; line-height: 1.5; color: #495057; vertical-align: middle; background: #fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px; border: 1px solid #ced4da; border-radius: 0.25rem; -webkit-appearance: none; -moz-appearance: none; appearance: none; } .custom-select:focus { border-color: #80bdff; outline: 0; box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } .custom-select:focus::-ms-value { color: #495057; background-color: #fff; } .custom-select[multiple], .custom-select[size]:not([size="1"]) { height: auto; padding-right: 0.75rem; background-image: none; } .custom-select:disabled { color: #6c757d; background-color: #e9ecef; } .custom-select::-ms-expand { display: none; } .custom-select:-moz-focusring { color: transparent; text-shadow: 0 0 0 #495057; } .custom-select-sm { height: calc(1.5em + 0.5rem + 2px); padding-top: 0.25rem; padding-bottom: 0.25rem; padding-left: 0.5rem; font-size: 0.875rem; } .custom-select-lg { height: calc(1.5em + 1rem + 2px); padding-top: 0.5rem; padding-bottom: 0.5rem; padding-left: 1rem; font-size: 1.25rem; } .custom-file { position: relative; display: inline-block; width: 100%; height: calc(1.5em + 0.75rem + 2px); margin-bottom: 0; } .custom-file-input { position: relative; z-index: 2; width: 100%; height: calc(1.5em + 0.75rem + 2px); margin: 0; opacity: 0; } .custom-file-input:focus ~ .custom-file-label { border-color: #80bdff; box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } .custom-file-input[disabled] ~ .custom-file-label, .custom-file-input:disabled ~ .custom-file-label { background-color: #e9ecef; } .custom-file-input:lang(en) ~ .custom-file-label::after { content: "Browse"; } .custom-file-input ~ .custom-file-label[data-browse]::after { content: attr(data-browse); } .custom-file-label { position: absolute; top: 0; right: 0; left: 0; z-index: 1; height: calc(1.5em + 0.75rem + 2px); padding: 0.375rem 0.75rem; font-weight: 400; line-height: 1.5; color: #495057; background-color: #fff; border: 1px solid #ced4da; border-radius: 0.25rem; } .custom-file-label::after { position: absolute; top: 0; right: 0; bottom: 0; z-index: 3; display: block; height: calc(1.5em + 0.75rem); padding: 0.375rem 0.75rem; line-height: 1.5; color: #495057; content: "Browse"; background-color: #e9ecef; border-left: inherit; border-radius: 0 0.25rem 0.25rem 0; } .custom-range { width: 100%; height: 1.4rem; padding: 0; background-color: transparent; -webkit-appearance: none; -moz-appearance: none; appearance: none; } .custom-range:focus { outline: none; } .custom-range:focus::-webkit-slider-thumb { box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } .custom-range:focus::-moz-range-thumb { box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } .custom-range:focus::-ms-thumb { box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } .custom-range::-moz-focus-outer { border: 0; } .custom-range::-webkit-slider-thumb { width: 1rem; height: 1rem; margin-top: -0.25rem; background-color: #007bff; border: 0; border-radius: 1rem; -webkit-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; -webkit-appearance: none; appearance: none; } @media (prefers-reduced-motion: reduce) { .custom-range::-webkit-slider-thumb { -webkit-transition: none; transition: none; } } .custom-range::-webkit-slider-thumb:active { background-color: #b3d7ff; } .custom-range::-webkit-slider-runnable-track { width: 100%; height: 0.5rem; color: transparent; cursor: pointer; background-color: #dee2e6; border-color: transparent; border-radius: 1rem; } .custom-range::-moz-range-thumb { width: 1rem; height: 1rem; background-color: #007bff; border: 0; border-radius: 1rem; -moz-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; -moz-appearance: none; appearance: none; } @media (prefers-reduced-motion: reduce) { .custom-range::-moz-range-thumb { -moz-transition: none; transition: none; } } .custom-range::-moz-range-thumb:active { background-color: #b3d7ff; } .custom-range::-moz-range-track { width: 100%; height: 0.5rem; color: transparent; cursor: pointer; background-color: #dee2e6; border-color: transparent; border-radius: 1rem; } .custom-range::-ms-thumb { width: 1rem; height: 1rem; margin-top: 0; margin-right: 0.2rem; margin-left: 0.2rem; background-color: #007bff; border: 0; border-radius: 1rem; -ms-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; appearance: none; } @media (prefers-reduced-motion: reduce) { .custom-range::-ms-thumb { -ms-transition: none; transition: none; } } .custom-range::-ms-thumb:active { background-color: #b3d7ff; } .custom-range::-ms-track { width: 100%; height: 0.5rem; color: transparent; cursor: pointer; background-color: transparent; border-color: transparent; border-width: 0.5rem; } .custom-range::-ms-fill-lower { background-color: #dee2e6; border-radius: 1rem; } .custom-range::-ms-fill-upper { margin-right: 15px; background-color: #dee2e6; border-radius: 1rem; } .custom-range:disabled::-webkit-slider-thumb { background-color: #adb5bd; } .custom-range:disabled::-webkit-slider-runnable-track { cursor: default; } .custom-range:disabled::-moz-range-thumb { background-color: #adb5bd; } .custom-range:disabled::-moz-range-track { cursor: default; } .custom-range:disabled::-ms-thumb { background-color: #adb5bd; } .custom-control-label::before, .custom-file-label, .custom-select { transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } @media (prefers-reduced-motion: reduce) { .custom-control-label::before, .custom-file-label, .custom-select { transition: none; } } .nav { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; padding-left: 0; margin-bottom: 0; list-style: none; } .nav-link { display: block; padding: 0.5rem 1rem; } .nav-link:hover, .nav-link:focus { text-decoration: none; } .nav-link.disabled { color: #6c757d; pointer-events: none; cursor: default; } .nav-tabs { border-bottom: 1px solid #dee2e6; } .nav-tabs .nav-item { margin-bottom: -1px; } .nav-tabs .nav-link { border: 1px solid transparent; border-top-left-radius: 0.25rem; border-top-right-radius: 0.25rem; } .nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus { border-color: #e9ecef #e9ecef #dee2e6; } .nav-tabs .nav-link.disabled { color: #6c757d; background-color: transparent; border-color: transparent; } .nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link { color: #495057; background-color: #fff; border-color: #dee2e6 #dee2e6 #fff; } .nav-tabs .dropdown-menu { margin-top: -1px; border-top-left-radius: 0; border-top-right-radius: 0; } .nav-pills .nav-link { border-radius: 0.25rem; } .nav-pills .nav-link.active, .nav-pills .show > .nav-link { color: #fff; background-color: #007bff; } .nav-fill .nav-item { -ms-flex: 1 1 auto; flex: 1 1 auto; text-align: center; } .nav-justified .nav-item { -ms-flex-preferred-size: 0; flex-basis: 0; -ms-flex-positive: 1; flex-grow: 1; text-align: center; } .tab-content > .tab-pane { display: none; } .tab-content > .active { display: block; } .navbar { position: relative; display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; -ms-flex-align: center; align-items: center; -ms-flex-pack: justify; justify-content: space-between; padding: 0.5rem 1rem; } .navbar .container, .navbar .container-fluid, .navbar .container-sm, .navbar .container-md, .navbar .container-lg, .navbar .container-xl { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; -ms-flex-align: center; align-items: center; -ms-flex-pack: justify; justify-content: space-between; } .navbar-brand { display: inline-block; padding-top: 0.3125rem; padding-bottom: 0.3125rem; margin-right: 1rem; font-size: 1.25rem; line-height: inherit; white-space: nowrap; } .navbar-brand:hover, .navbar-brand:focus { text-decoration: none; } .navbar-nav { display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; padding-left: 0; margin-bottom: 0; list-style: none; } .navbar-nav .nav-link { padding-right: 0; padding-left: 0; } .navbar-nav .dropdown-menu { position: static; float: none; } .navbar-text { display: inline-block; padding-top: 0.5rem; padding-bottom: 0.5rem; } .navbar-collapse { -ms-flex-preferred-size: 100%; flex-basis: 100%; -ms-flex-positive: 1; flex-grow: 1; -ms-flex-align: center; align-items: center; } .navbar-toggler { padding: 0.25rem 0.75rem; font-size: 1.25rem; line-height: 1; background-color: transparent; border: 1px solid transparent; border-radius: 0.25rem; } .navbar-toggler:hover, .navbar-toggler:focus { text-decoration: none; } .navbar-toggler-icon { display: inline-block; width: 1.5em; height: 1.5em; vertical-align: middle; content: ""; background: no-repeat center center; background-size: 100% 100%; } @media (max-width: 575.98px) { .navbar-expand-sm > .container, .navbar-expand-sm > .container-fluid, .navbar-expand-sm > .container-sm, .navbar-expand-sm > .container-md, .navbar-expand-sm > .container-lg, .navbar-expand-sm > .container-xl { padding-right: 0; padding-left: 0; } } @media (min-width: 576px) { .navbar-expand-sm { -ms-flex-flow: row nowrap; flex-flow: row nowrap; -ms-flex-pack: start; justify-content: flex-start; } .navbar-expand-sm .navbar-nav { -ms-flex-direction: row; flex-direction: row; } .navbar-expand-sm .navbar-nav .dropdown-menu { position: absolute; } .navbar-expand-sm .navbar-nav .nav-link { padding-right: 0.5rem; padding-left: 0.5rem; } .navbar-expand-sm > .container, .navbar-expand-sm > .container-fluid, .navbar-expand-sm > .container-sm, .navbar-expand-sm > .container-md, .navbar-expand-sm > .container-lg, .navbar-expand-sm > .container-xl { -ms-flex-wrap: nowrap; flex-wrap: nowrap; } .navbar-expand-sm .navbar-collapse { display: -ms-flexbox !important; display: flex !important; -ms-flex-preferred-size: auto; flex-basis: auto; } .navbar-expand-sm .navbar-toggler { display: none; } } @media (max-width: 767.98px) { .navbar-expand-md > .container, .navbar-expand-md > .container-fluid, .navbar-expand-md > .container-sm, .navbar-expand-md > .container-md, .navbar-expand-md > .container-lg, .navbar-expand-md > .container-xl { padding-right: 0; padding-left: 0; } } @media (min-width: 768px) { .navbar-expand-md { -ms-flex-flow: row nowrap; flex-flow: row nowrap; -ms-flex-pack: start; justify-content: flex-start; } .navbar-expand-md .navbar-nav { -ms-flex-direction: row; flex-direction: row; } .navbar-expand-md .navbar-nav .dropdown-menu { position: absolute; } .navbar-expand-md .navbar-nav .nav-link { padding-right: 0.5rem; padding-left: 0.5rem; } .navbar-expand-md > .container, .navbar-expand-md > .container-fluid, .navbar-expand-md > .container-sm, .navbar-expand-md > .container-md, .navbar-expand-md > .container-lg, .navbar-expand-md > .container-xl { -ms-flex-wrap: nowrap; flex-wrap: nowrap; } .navbar-expand-md .navbar-collapse { display: -ms-flexbox !important; display: flex !important; -ms-flex-preferred-size: auto; flex-basis: auto; } .navbar-expand-md .navbar-toggler { display: none; } } @media (max-width: 991.98px) { .navbar-expand-lg > .container, .navbar-expand-lg > .container-fluid, .navbar-expand-lg > .container-sm, .navbar-expand-lg > .container-md, .navbar-expand-lg > .container-lg, .navbar-expand-lg > .container-xl { padding-right: 0; padding-left: 0; } } @media (min-width: 992px) { .navbar-expand-lg { -ms-flex-flow: row nowrap; flex-flow: row nowrap; -ms-flex-pack: start; justify-content: flex-start; } .navbar-expand-lg .navbar-nav { -ms-flex-direction: row; flex-direction: row; } .navbar-expand-lg .navbar-nav .dropdown-menu { position: absolute; } .navbar-expand-lg .navbar-nav .nav-link { padding-right: 0.5rem; padding-left: 0.5rem; } .navbar-expand-lg > .container, .navbar-expand-lg > .container-fluid, .navbar-expand-lg > .container-sm, .navbar-expand-lg > .container-md, .navbar-expand-lg > .container-lg, .navbar-expand-lg > .container-xl { -ms-flex-wrap: nowrap; flex-wrap: nowrap; } .navbar-expand-lg .navbar-collapse { display: -ms-flexbox !important; display: flex !important; -ms-flex-preferred-size: auto; flex-basis: auto; } .navbar-expand-lg .navbar-toggler { display: none; } } @media (max-width: 1199.98px) { .navbar-expand-xl > .container, .navbar-expand-xl > .container-fluid, .navbar-expand-xl > .container-sm, .navbar-expand-xl > .container-md, .navbar-expand-xl > .container-lg, .navbar-expand-xl > .container-xl { padding-right: 0; padding-left: 0; } } @media (min-width: 1200px) { .navbar-expand-xl { -ms-flex-flow: row nowrap; flex-flow: row nowrap; -ms-flex-pack: start; justify-content: flex-start; } .navbar-expand-xl .navbar-nav { -ms-flex-direction: row; flex-direction: row; } .navbar-expand-xl .navbar-nav .dropdown-menu { position: absolute; } .navbar-expand-xl .navbar-nav .nav-link { padding-right: 0.5rem; padding-left: 0.5rem; } .navbar-expand-xl > .container, .navbar-expand-xl > .container-fluid, .navbar-expand-xl > .container-sm, .navbar-expand-xl > .container-md, .navbar-expand-xl > .container-lg, .navbar-expand-xl > .container-xl { -ms-flex-wrap: nowrap; flex-wrap: nowrap; } .navbar-expand-xl .navbar-collapse { display: -ms-flexbox !important; display: flex !important; -ms-flex-preferred-size: auto; flex-basis: auto; } .navbar-expand-xl .navbar-toggler { display: none; } } .navbar-expand { -ms-flex-flow: row nowrap; flex-flow: row nowrap; -ms-flex-pack: start; justify-content: flex-start; } .navbar-expand > .container, .navbar-expand > .container-fluid, .navbar-expand > .container-sm, .navbar-expand > .container-md, .navbar-expand > .container-lg, .navbar-expand > .container-xl { padding-right: 0; padding-left: 0; } .navbar-expand .navbar-nav { -ms-flex-direction: row; flex-direction: row; } .navbar-expand .navbar-nav .dropdown-menu { position: absolute; } .navbar-expand .navbar-nav .nav-link { padding-right: 0.5rem; padding-left: 0.5rem; } .navbar-expand > .container, .navbar-expand > .container-fluid, .navbar-expand > .container-sm, .navbar-expand > .container-md, .navbar-expand > .container-lg, .navbar-expand > .container-xl { -ms-flex-wrap: nowrap; flex-wrap: nowrap; } .navbar-expand .navbar-collapse { display: -ms-flexbox !important; display: flex !important; -ms-flex-preferred-size: auto; flex-basis: auto; } .navbar-expand .navbar-toggler { display: none; } .navbar-light .navbar-brand { color: rgba(0, 0, 0, 0.9); } .navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus { color: rgba(0, 0, 0, 0.9); } .navbar-light .navbar-nav .nav-link { color: rgba(0, 0, 0, 0.5); } .navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus { color: rgba(0, 0, 0, 0.7); } .navbar-light .navbar-nav .nav-link.disabled { color: rgba(0, 0, 0, 0.3); } .navbar-light .navbar-nav .show > .nav-link, .navbar-light .navbar-nav .active > .nav-link, .navbar-light .navbar-nav .nav-link.show, .navbar-light .navbar-nav .nav-link.active { color: rgba(0, 0, 0, 0.9); } .navbar-light .navbar-toggler { color: rgba(0, 0, 0, 0.5); border-color: rgba(0, 0, 0, 0.1); } .navbar-light .navbar-toggler-icon { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); } .navbar-light .navbar-text { color: rgba(0, 0, 0, 0.5); } .navbar-light .navbar-text a { color: rgba(0, 0, 0, 0.9); } .navbar-light .navbar-text a:hover, .navbar-light .navbar-text a:focus { color: rgba(0, 0, 0, 0.9); } .navbar-dark .navbar-brand { color: #fff; } .navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus { color: #fff; } .navbar-dark .navbar-nav .nav-link { color: rgba(255, 255, 255, 0.5); } .navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus { color: rgba(255, 255, 255, 0.75); } .navbar-dark .navbar-nav .nav-link.disabled { color: rgba(255, 255, 255, 0.25); } .navbar-dark .navbar-nav .show > .nav-link, .navbar-dark .navbar-nav .active > .nav-link, .navbar-dark .navbar-nav .nav-link.show, .navbar-dark .navbar-nav .nav-link.active { color: #fff; } .navbar-dark .navbar-toggler { color: rgba(255, 255, 255, 0.5); border-color: rgba(255, 255, 255, 0.1); } .navbar-dark .navbar-toggler-icon { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); } .navbar-dark .navbar-text { color: rgba(255, 255, 255, 0.5); } .navbar-dark .navbar-text a { color: #fff; } .navbar-dark .navbar-text a:hover, .navbar-dark .navbar-text a:focus { color: #fff; } .card { position: relative; display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; min-width: 0; word-wrap: break-word; background-color: #fff; background-clip: border-box; border: 1px solid rgba(0, 0, 0, 0.125); border-radius: 0.25rem; } .card > hr { margin-right: 0; margin-left: 0; } .card > .list-group { border-top: inherit; border-bottom: inherit; } .card > .list-group:first-child { border-top-width: 0; border-top-left-radius: calc(0.25rem - 1px); border-top-right-radius: calc(0.25rem - 1px); } .card > .list-group:last-child { border-bottom-width: 0; border-bottom-right-radius: calc(0.25rem - 1px); border-bottom-left-radius: calc(0.25rem - 1px); } .card-body { -ms-flex: 1 1 auto; flex: 1 1 auto; min-height: 1px; padding: 1.25rem; } .card-title { margin-bottom: 0.75rem; } .card-subtitle { margin-top: -0.375rem; margin-bottom: 0; } .card-text:last-child { margin-bottom: 0; } .card-link:hover { text-decoration: none; } .card-link + .card-link { margin-left: 1.25rem; } .card-header { padding: 0.75rem 1.25rem; margin-bottom: 0; background-color: rgba(0, 0, 0, 0.03); border-bottom: 1px solid rgba(0, 0, 0, 0.125); } .card-header:first-child { border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0; } .card-header + .list-group .list-group-item:first-child { border-top: 0; } .card-footer { padding: 0.75rem 1.25rem; background-color: rgba(0, 0, 0, 0.03); border-top: 1px solid rgba(0, 0, 0, 0.125); } .card-footer:last-child { border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px); } .card-header-tabs { margin-right: -0.625rem; margin-bottom: -0.75rem; margin-left: -0.625rem; border-bottom: 0; } .card-header-pills { margin-right: -0.625rem; margin-left: -0.625rem; } .card-img-overlay { position: absolute; top: 0; right: 0; bottom: 0; left: 0; padding: 1.25rem; } .card-img, .card-img-top, .card-img-bottom { -ms-flex-negative: 0; flex-shrink: 0; width: 100%; } .card-img, .card-img-top { border-top-left-radius: calc(0.25rem - 1px); border-top-right-radius: calc(0.25rem - 1px); } .card-img, .card-img-bottom { border-bottom-right-radius: calc(0.25rem - 1px); border-bottom-left-radius: calc(0.25rem - 1px); } .card-deck .card { margin-bottom: 15px; } @media (min-width: 576px) { .card-deck { display: -ms-flexbox; display: flex; -ms-flex-flow: row wrap; flex-flow: row wrap; margin-right: -15px; margin-left: -15px; } .card-deck .card { -ms-flex: 1 0 0%; flex: 1 0 0%; margin-right: 15px; margin-bottom: 0; margin-left: 15px; } } .card-group > .card { margin-bottom: 15px; } @media (min-width: 576px) { .card-group { display: -ms-flexbox; display: flex; -ms-flex-flow: row wrap; flex-flow: row wrap; } .card-group > .card { -ms-flex: 1 0 0%; flex: 1 0 0%; margin-bottom: 0; } .card-group > .card + .card { margin-left: 0; border-left: 0; } .card-group > .card:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .card-group > .card:not(:last-child) .card-img-top, .card-group > .card:not(:last-child) .card-header { border-top-right-radius: 0; } .card-group > .card:not(:last-child) .card-img-bottom, .card-group > .card:not(:last-child) .card-footer { border-bottom-right-radius: 0; } .card-group > .card:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .card-group > .card:not(:first-child) .card-img-top, .card-group > .card:not(:first-child) .card-header { border-top-left-radius: 0; } .card-group > .card:not(:first-child) .card-img-bottom, .card-group > .card:not(:first-child) .card-footer { border-bottom-left-radius: 0; } } .card-columns .card { margin-bottom: 0.75rem; } @media (min-width: 576px) { .card-columns { -webkit-column-count: 3; -moz-column-count: 3; column-count: 3; -webkit-column-gap: 1.25rem; -moz-column-gap: 1.25rem; column-gap: 1.25rem; orphans: 1; widows: 1; } .card-columns .card { display: inline-block; width: 100%; } } .accordion > .card { overflow: hidden; } .accordion > .card:not(:last-of-type) { border-bottom: 0; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .accordion > .card:not(:first-of-type) { border-top-left-radius: 0; border-top-right-radius: 0; } .accordion > .card > .card-header { border-radius: 0; margin-bottom: -1px; } .breadcrumb { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; padding: 0.75rem 1rem; margin-bottom: 1rem; list-style: none; background-color: #e9ecef; border-radius: 0.25rem; } .breadcrumb-item { display: -ms-flexbox; display: flex; } .breadcrumb-item + .breadcrumb-item { padding-left: 0.5rem; } .breadcrumb-item + .breadcrumb-item::before { display: inline-block; padding-right: 0.5rem; color: #6c757d; content: "/"; } .breadcrumb-item + .breadcrumb-item:hover::before { text-decoration: underline; } .breadcrumb-item + .breadcrumb-item:hover::before { text-decoration: none; } .breadcrumb-item.active { color: #6c757d; } .pagination { display: -ms-flexbox; display: flex; padding-left: 0; list-style: none; border-radius: 0.25rem; } .page-link { position: relative; display: block; padding: 0.5rem 0.75rem; margin-left: -1px; line-height: 1.25; color: #007bff; background-color: #fff; border: 1px solid #dee2e6; } .page-link:hover { z-index: 2; color: #0056b3; text-decoration: none; background-color: #e9ecef; border-color: #dee2e6; } .page-link:focus { z-index: 3; outline: 0; box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); } .page-item:first-child .page-link { margin-left: 0; border-top-left-radius: 0.25rem; border-bottom-left-radius: 0.25rem; } .page-item:last-child .page-link { border-top-right-radius: 0.25rem; border-bottom-right-radius: 0.25rem; } .page-item.active .page-link { z-index: 3; color: #fff; background-color: #007bff; border-color: #007bff; } .page-item.disabled .page-link { color: #6c757d; pointer-events: none; cursor: auto; background-color: #fff; border-color: #dee2e6; } .pagination-lg .page-link { padding: 0.75rem 1.5rem; font-size: 1.25rem; line-height: 1.5; } .pagination-lg .page-item:first-child .page-link { border-top-left-radius: 0.3rem; border-bottom-left-radius: 0.3rem; } .pagination-lg .page-item:last-child .page-link { border-top-right-radius: 0.3rem; border-bottom-right-radius: 0.3rem; } .pagination-sm .page-link { padding: 0.25rem 0.5rem; font-size: 0.875rem; line-height: 1.5; } .pagination-sm .page-item:first-child .page-link { border-top-left-radius: 0.2rem; border-bottom-left-radius: 0.2rem; } .pagination-sm .page-item:last-child .page-link { border-top-right-radius: 0.2rem; border-bottom-right-radius: 0.2rem; } .badge { display: inline-block; padding: 0.25em 0.4em; font-size: 75%; font-weight: 700; line-height: 1; text-align: center; white-space: nowrap; vertical-align: baseline; border-radius: 0.25rem; transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } @media (prefers-reduced-motion: reduce) { .badge { transition: none; } } a.badge:hover, a.badge:focus { text-decoration: none; } .badge:empty { display: none; } .btn .badge { position: relative; top: -1px; } .badge-pill { padding-right: 0.6em; padding-left: 0.6em; border-radius: 10rem; } .badge-primary { color: #fff; background-color: #007bff; } a.badge-primary:hover, a.badge-primary:focus { color: #fff; background-color: #0062cc; } a.badge-primary:focus, a.badge-primary.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); } .badge-secondary { color: #fff; background-color: #6c757d; } a.badge-secondary:hover, a.badge-secondary:focus { color: #fff; background-color: #545b62; } a.badge-secondary:focus, a.badge-secondary.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); } .badge-success { color: #fff; background-color: #28a745; } a.badge-success:hover, a.badge-success:focus { color: #fff; background-color: #1e7e34; } a.badge-success:focus, a.badge-success.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); } .badge-info { color: #fff; background-color: #17a2b8; } a.badge-info:hover, a.badge-info:focus { color: #fff; background-color: #117a8b; } a.badge-info:focus, a.badge-info.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); } .badge-warning { color: #212529; background-color: #ffc107; } a.badge-warning:hover, a.badge-warning:focus { color: #212529; background-color: #d39e00; } a.badge-warning:focus, a.badge-warning.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); } .badge-danger { color: #fff; background-color: #dc3545; } a.badge-danger:hover, a.badge-danger:focus { color: #fff; background-color: #bd2130; } a.badge-danger:focus, a.badge-danger.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); } .badge-light { color: #212529; background-color: #f8f9fa; } a.badge-light:hover, a.badge-light:focus { color: #212529; background-color: #dae0e5; } a.badge-light:focus, a.badge-light.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); } .badge-dark { color: #fff; background-color: #343a40; } a.badge-dark:hover, a.badge-dark:focus { color: #fff; background-color: #1d2124; } a.badge-dark:focus, a.badge-dark.focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); } .jumbotron { padding: 2rem 1rem; margin-bottom: 2rem; background-color: #e9ecef; border-radius: 0.3rem; } @media (min-width: 576px) { .jumbotron { padding: 4rem 2rem; } } .jumbotron-fluid { padding-right: 0; padding-left: 0; border-radius: 0; } .alert { position: relative; padding: 0.75rem 1.25rem; margin-bottom: 1rem; border: 1px solid transparent; border-radius: 0.25rem; } .alert-heading { color: inherit; } .alert-link { font-weight: 700; } .alert-dismissible { padding-right: 4rem; } .alert-dismissible .close { position: absolute; top: 0; right: 0; padding: 0.75rem 1.25rem; color: inherit; } .alert-primary { color: #004085; background-color: #cce5ff; border-color: #b8daff; } .alert-primary hr { border-top-color: #9fcdff; } .alert-primary .alert-link { color: #002752; } .alert-secondary { color: #383d41; background-color: #e2e3e5; border-color: #d6d8db; } .alert-secondary hr { border-top-color: #c8cbcf; } .alert-secondary .alert-link { color: #202326; } .alert-success { color: #155724; background-color: #d4edda; border-color: #c3e6cb; } .alert-success hr { border-top-color: #b1dfbb; } .alert-success .alert-link { color: #0b2e13; } .alert-info { color: #0c5460; background-color: #d1ecf1; border-color: #bee5eb; } .alert-info hr { border-top-color: #abdde5; } .alert-info .alert-link { color: #062c33; } .alert-warning { color: #856404; background-color: #fff3cd; border-color: #ffeeba; } .alert-warning hr { border-top-color: #ffe8a1; } .alert-warning .alert-link { color: #533f03; } .alert-danger { color: #721c24; background-color: #f8d7da; border-color: #f5c6cb; } .alert-danger hr { border-top-color: #f1b0b7; } .alert-danger .alert-link { color: #491217; } .alert-light { color: #818182; background-color: #fefefe; border-color: #fdfdfe; } .alert-light hr { border-top-color: #ececf6; } .alert-light .alert-link { color: #686868; } .alert-dark { color: #1b1e21; background-color: #d6d8d9; border-color: #c6c8ca; } .alert-dark hr { border-top-color: #b9bbbe; } .alert-dark .alert-link { color: #040505; } @-webkit-keyframes progress-bar-stripes { from { background-position: 1rem 0; } to { background-position: 0 0; } } @keyframes progress-bar-stripes { from { background-position: 1rem 0; } to { background-position: 0 0; } } .progress { display: -ms-flexbox; display: flex; height: 1rem; overflow: hidden; line-height: 0; font-size: 0.75rem; background-color: #e9ecef; border-radius: 0.25rem; } .progress-bar { display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; -ms-flex-pack: center; justify-content: center; overflow: hidden; color: #fff; text-align: center; white-space: nowrap; background-color: #007bff; transition: width 0.6s ease; } @media (prefers-reduced-motion: reduce) { .progress-bar { transition: none; } } .progress-bar-striped { background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-size: 1rem 1rem; } .progress-bar-animated { -webkit-animation: progress-bar-stripes 1s linear infinite; animation: progress-bar-stripes 1s linear infinite; } @media (prefers-reduced-motion: reduce) { .progress-bar-animated { -webkit-animation: none; animation: none; } } .media { display: -ms-flexbox; display: flex; -ms-flex-align: start; align-items: flex-start; } .media-body { -ms-flex: 1; flex: 1; } .list-group { display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; padding-left: 0; margin-bottom: 0; border-radius: 0.25rem; } .list-group-item-action { width: 100%; color: #495057; text-align: inherit; } .list-group-item-action:hover, .list-group-item-action:focus { z-index: 1; color: #495057; text-decoration: none; background-color: #f8f9fa; } .list-group-item-action:active { color: #212529; background-color: #e9ecef; } .list-group-item { position: relative; display: block; padding: 0.75rem 1.25rem; background-color: #fff; border: 1px solid rgba(0, 0, 0, 0.125); } .list-group-item:first-child { border-top-left-radius: inherit; border-top-right-radius: inherit; } .list-group-item:last-child { border-bottom-right-radius: inherit; border-bottom-left-radius: inherit; } .list-group-item.disabled, .list-group-item:disabled { color: #6c757d; pointer-events: none; background-color: #fff; } .list-group-item.active { z-index: 2; color: #fff; background-color: #007bff; border-color: #007bff; } .list-group-item + .list-group-item { border-top-width: 0; } .list-group-item + .list-group-item.active { margin-top: -1px; border-top-width: 1px; } .list-group-horizontal { -ms-flex-direction: row; flex-direction: row; } .list-group-horizontal > .list-group-item:first-child { border-bottom-left-radius: 0.25rem; border-top-right-radius: 0; } .list-group-horizontal > .list-group-item:last-child { border-top-right-radius: 0.25rem; border-bottom-left-radius: 0; } .list-group-horizontal > .list-group-item.active { margin-top: 0; } .list-group-horizontal > .list-group-item + .list-group-item { border-top-width: 1px; border-left-width: 0; } .list-group-horizontal > .list-group-item + .list-group-item.active { margin-left: -1px; border-left-width: 1px; } @media (min-width: 576px) { .list-group-horizontal-sm { -ms-flex-direction: row; flex-direction: row; } .list-group-horizontal-sm > .list-group-item:first-child { border-bottom-left-radius: 0.25rem; border-top-right-radius: 0; } .list-group-horizontal-sm > .list-group-item:last-child { border-top-right-radius: 0.25rem; border-bottom-left-radius: 0; } .list-group-horizontal-sm > .list-group-item.active { margin-top: 0; } .list-group-horizontal-sm > .list-group-item + .list-group-item { border-top-width: 1px; border-left-width: 0; } .list-group-horizontal-sm > .list-group-item + .list-group-item.active { margin-left: -1px; border-left-width: 1px; } } @media (min-width: 768px) { .list-group-horizontal-md { -ms-flex-direction: row; flex-direction: row; } .list-group-horizontal-md > .list-group-item:first-child { border-bottom-left-radius: 0.25rem; border-top-right-radius: 0; } .list-group-horizontal-md > .list-group-item:last-child { border-top-right-radius: 0.25rem; border-bottom-left-radius: 0; } .list-group-horizontal-md > .list-group-item.active { margin-top: 0; } .list-group-horizontal-md > .list-group-item + .list-group-item { border-top-width: 1px; border-left-width: 0; } .list-group-horizontal-md > .list-group-item + .list-group-item.active { margin-left: -1px; border-left-width: 1px; } } @media (min-width: 992px) { .list-group-horizontal-lg { -ms-flex-direction: row; flex-direction: row; } .list-group-horizontal-lg > .list-group-item:first-child { border-bottom-left-radius: 0.25rem; border-top-right-radius: 0; } .list-group-horizontal-lg > .list-group-item:last-child { border-top-right-radius: 0.25rem; border-bottom-left-radius: 0; } .list-group-horizontal-lg > .list-group-item.active { margin-top: 0; } .list-group-horizontal-lg > .list-group-item + .list-group-item { border-top-width: 1px; border-left-width: 0; } .list-group-horizontal-lg > .list-group-item + .list-group-item.active { margin-left: -1px; border-left-width: 1px; } } @media (min-width: 1200px) { .list-group-horizontal-xl { -ms-flex-direction: row; flex-direction: row; } .list-group-horizontal-xl > .list-group-item:first-child { border-bottom-left-radius: 0.25rem; border-top-right-radius: 0; } .list-group-horizontal-xl > .list-group-item:last-child { border-top-right-radius: 0.25rem; border-bottom-left-radius: 0; } .list-group-horizontal-xl > .list-group-item.active { margin-top: 0; } .list-group-horizontal-xl > .list-group-item + .list-group-item { border-top-width: 1px; border-left-width: 0; } .list-group-horizontal-xl > .list-group-item + .list-group-item.active { margin-left: -1px; border-left-width: 1px; } } .list-group-flush { border-radius: 0; } .list-group-flush > .list-group-item { border-width: 0 0 1px; } .list-group-flush > .list-group-item:last-child { border-bottom-width: 0; } .list-group-item-primary { color: #004085; background-color: #b8daff; } .list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus { color: #004085; background-color: #9fcdff; } .list-group-item-primary.list-group-item-action.active { color: #fff; background-color: #004085; border-color: #004085; } .list-group-item-secondary { color: #383d41; background-color: #d6d8db; } .list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus { color: #383d41; background-color: #c8cbcf; } .list-group-item-secondary.list-group-item-action.active { color: #fff; background-color: #383d41; border-color: #383d41; } .list-group-item-success { color: #155724; background-color: #c3e6cb; } .list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus { color: #155724; background-color: #b1dfbb; } .list-group-item-success.list-group-item-action.active { color: #fff; background-color: #155724; border-color: #155724; } .list-group-item-info { color: #0c5460; background-color: #bee5eb; } .list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus { color: #0c5460; background-color: #abdde5; } .list-group-item-info.list-group-item-action.active { color: #fff; background-color: #0c5460; border-color: #0c5460; } .list-group-item-warning { color: #856404; background-color: #ffeeba; } .list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus { color: #856404; background-color: #ffe8a1; } .list-group-item-warning.list-group-item-action.active { color: #fff; background-color: #856404; border-color: #856404; } .list-group-item-danger { color: #721c24; background-color: #f5c6cb; } .list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus { color: #721c24; background-color: #f1b0b7; } .list-group-item-danger.list-group-item-action.active { color: #fff; background-color: #721c24; border-color: #721c24; } .list-group-item-light { color: #818182; background-color: #fdfdfe; } .list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus { color: #818182; background-color: #ececf6; } .list-group-item-light.list-group-item-action.active { color: #fff; background-color: #818182; border-color: #818182; } .list-group-item-dark { color: #1b1e21; background-color: #c6c8ca; } .list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus { color: #1b1e21; background-color: #b9bbbe; } .list-group-item-dark.list-group-item-action.active { color: #fff; background-color: #1b1e21; border-color: #1b1e21; } .close { float: right; font-size: 1.5rem; font-weight: 700; line-height: 1; color: #000; text-shadow: 0 1px 0 #fff; opacity: .5; } .close:hover { color: #000; text-decoration: none; } .close:not(:disabled):not(.disabled):hover, .close:not(:disabled):not(.disabled):focus { opacity: .75; } button.close { padding: 0; background-color: transparent; border: 0; } a.close.disabled { pointer-events: none; } .toast { max-width: 350px; overflow: hidden; font-size: 0.875rem; background-color: rgba(255, 255, 255, 0.85); background-clip: padding-box; border: 1px solid rgba(0, 0, 0, 0.1); box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.1); -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px); opacity: 0; border-radius: 0.25rem; } .toast:not(:last-child) { margin-bottom: 0.75rem; } .toast.showing { opacity: 1; } .toast.show { display: block; opacity: 1; } .toast.hide { display: none; } .toast-header { display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; padding: 0.25rem 0.75rem; color: #6c757d; background-color: rgba(255, 255, 255, 0.85); background-clip: padding-box; border-bottom: 1px solid rgba(0, 0, 0, 0.05); } .toast-body { padding: 0.75rem; } .modal-open { overflow: hidden; } .modal-open .modal { overflow-x: hidden; overflow-y: auto; } .modal { position: fixed; top: 0; left: 0; z-index: 1050; display: none; width: 100%; height: 100%; overflow: hidden; outline: 0; } .modal-dialog { position: relative; width: auto; margin: 0.5rem; pointer-events: none; } .modal.fade .modal-dialog { transition: -webkit-transform 0.3s ease-out; transition: transform 0.3s ease-out; transition: transform 0.3s ease-out, -webkit-transform 0.3s ease-out; -webkit-transform: translate(0, -50px); transform: translate(0, -50px); } @media (prefers-reduced-motion: reduce) { .modal.fade .modal-dialog { transition: none; } } .modal.show .modal-dialog { -webkit-transform: none; transform: none; } .modal.modal-static .modal-dialog { -webkit-transform: scale(1.02); transform: scale(1.02); } .modal-dialog-scrollable { display: -ms-flexbox; display: flex; max-height: calc(100% - 1rem); } .modal-dialog-scrollable .modal-content { max-height: calc(100vh - 1rem); overflow: hidden; } .modal-dialog-scrollable .modal-header, .modal-dialog-scrollable .modal-footer { -ms-flex-negative: 0; flex-shrink: 0; } .modal-dialog-scrollable .modal-body { overflow-y: auto; } .modal-dialog-centered { display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; min-height: calc(100% - 1rem); } .modal-dialog-centered::before { display: block; height: calc(100vh - 1rem); height: -webkit-min-content; height: -moz-min-content; height: min-content; content: ""; } .modal-dialog-centered.modal-dialog-scrollable { -ms-flex-direction: column; flex-direction: column; -ms-flex-pack: center; justify-content: center; height: 100%; } .modal-dialog-centered.modal-dialog-scrollable .modal-content { max-height: none; } .modal-dialog-centered.modal-dialog-scrollable::before { content: none; } .modal-content { position: relative; display: -ms-flexbox; display: flex; -ms-flex-direction: column; flex-direction: column; width: 100%; pointer-events: auto; background-color: #fff; background-clip: padding-box; border: 1px solid rgba(0, 0, 0, 0.2); border-radius: 0.3rem; outline: 0; } .modal-backdrop { position: fixed; top: 0; left: 0; z-index: 1040; width: 100vw; height: 100vh; background-color: #000; } .modal-backdrop.fade { opacity: 0; } .modal-backdrop.show { opacity: 0.5; } .modal-header { display: -ms-flexbox; display: flex; -ms-flex-align: start; align-items: flex-start; -ms-flex-pack: justify; justify-content: space-between; padding: 1rem 1rem; border-bottom: 1px solid #dee2e6; border-top-left-radius: calc(0.3rem - 1px); border-top-right-radius: calc(0.3rem - 1px); } .modal-header .close { padding: 1rem 1rem; margin: -1rem -1rem -1rem auto; } .modal-title { margin-bottom: 0; line-height: 1.5; } .modal-body { position: relative; -ms-flex: 1 1 auto; flex: 1 1 auto; padding: 1rem; } .modal-footer { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; -ms-flex-align: center; align-items: center; -ms-flex-pack: end; justify-content: flex-end; padding: 0.75rem; border-top: 1px solid #dee2e6; border-bottom-right-radius: calc(0.3rem - 1px); border-bottom-left-radius: calc(0.3rem - 1px); } .modal-footer > * { margin: 0.25rem; } .modal-scrollbar-measure { position: absolute; top: -9999px; width: 50px; height: 50px; overflow: scroll; } @media (min-width: 576px) { .modal-dialog { max-width: 500px; margin: 1.75rem auto; } .modal-dialog-scrollable { max-height: calc(100% - 3.5rem); } .modal-dialog-scrollable .modal-content { max-height: calc(100vh - 3.5rem); } .modal-dialog-centered { min-height: calc(100% - 3.5rem); } .modal-dialog-centered::before { height: calc(100vh - 3.5rem); height: -webkit-min-content; height: -moz-min-content; height: min-content; } .modal-sm { max-width: 300px; } } @media (min-width: 992px) { .modal-lg, .modal-xl { max-width: 800px; } } @media (min-width: 1200px) { .modal-xl { max-width: 1140px; } } .tooltip { position: absolute; z-index: 1070; display: block; margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-style: normal; font-weight: 400; line-height: 1.5; text-align: left; text-align: start; text-decoration: none; text-shadow: none; text-transform: none; letter-spacing: normal; word-break: normal; word-spacing: normal; white-space: normal; line-break: auto; font-size: 0.875rem; word-wrap: break-word; opacity: 0; } .tooltip.show { opacity: 0.9; } .tooltip .arrow { position: absolute; display: block; width: 0.8rem; height: 0.4rem; } .tooltip .arrow::before { position: absolute; content: ""; border-color: transparent; border-style: solid; } .bs-tooltip-top, .bs-tooltip-auto[x-placement^="top"] { padding: 0.4rem 0; } .bs-tooltip-top .arrow, .bs-tooltip-auto[x-placement^="top"] .arrow { bottom: 0; } .bs-tooltip-top .arrow::before, .bs-tooltip-auto[x-placement^="top"] .arrow::before { top: 0; border-width: 0.4rem 0.4rem 0; border-top-color: #000; } .bs-tooltip-right, .bs-tooltip-auto[x-placement^="right"] { padding: 0 0.4rem; } .bs-tooltip-right .arrow, .bs-tooltip-auto[x-placement^="right"] .arrow { left: 0; width: 0.4rem; height: 0.8rem; } .bs-tooltip-right .arrow::before, .bs-tooltip-auto[x-placement^="right"] .arrow::before { right: 0; border-width: 0.4rem 0.4rem 0.4rem 0; border-right-color: #000; } .bs-tooltip-bottom, .bs-tooltip-auto[x-placement^="bottom"] { padding: 0.4rem 0; } .bs-tooltip-bottom .arrow, .bs-tooltip-auto[x-placement^="bottom"] .arrow { top: 0; } .bs-tooltip-bottom .arrow::before, .bs-tooltip-auto[x-placement^="bottom"] .arrow::before { bottom: 0; border-width: 0 0.4rem 0.4rem; border-bottom-color: #000; } .bs-tooltip-left, .bs-tooltip-auto[x-placement^="left"] { padding: 0 0.4rem; } .bs-tooltip-left .arrow, .bs-tooltip-auto[x-placement^="left"] .arrow { right: 0; width: 0.4rem; height: 0.8rem; } .bs-tooltip-left .arrow::before, .bs-tooltip-auto[x-placement^="left"] .arrow::before { left: 0; border-width: 0.4rem 0 0.4rem 0.4rem; border-left-color: #000; } .tooltip-inner { max-width: 200px; padding: 0.25rem 0.5rem; color: #fff; text-align: center; background-color: #000; border-radius: 0.25rem; } .popover { position: absolute; top: 0; left: 0; z-index: 1060; display: block; max-width: 276px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-style: normal; font-weight: 400; line-height: 1.5; text-align: left; text-align: start; text-decoration: none; text-shadow: none; text-transform: none; letter-spacing: normal; word-break: normal; word-spacing: normal; white-space: normal; line-break: auto; font-size: 0.875rem; word-wrap: break-word; background-color: #fff; background-clip: padding-box; border: 1px solid rgba(0, 0, 0, 0.2); border-radius: 0.3rem; } .popover .arrow { position: absolute; display: block; width: 1rem; height: 0.5rem; margin: 0 0.3rem; } .popover .arrow::before, .popover .arrow::after { position: absolute; display: block; content: ""; border-color: transparent; border-style: solid; } .bs-popover-top, .bs-popover-auto[x-placement^="top"] { margin-bottom: 0.5rem; } .bs-popover-top > .arrow, .bs-popover-auto[x-placement^="top"] > .arrow { bottom: calc(-0.5rem - 1px); } .bs-popover-top > .arrow::before, .bs-popover-auto[x-placement^="top"] > .arrow::before { bottom: 0; border-width: 0.5rem 0.5rem 0; border-top-color: rgba(0, 0, 0, 0.25); } .bs-popover-top > .arrow::after, .bs-popover-auto[x-placement^="top"] > .arrow::after { bottom: 1px; border-width: 0.5rem 0.5rem 0; border-top-color: #fff; } .bs-popover-right, .bs-popover-auto[x-placement^="right"] { margin-left: 0.5rem; } .bs-popover-right > .arrow, .bs-popover-auto[x-placement^="right"] > .arrow { left: calc(-0.5rem - 1px); width: 0.5rem; height: 1rem; margin: 0.3rem 0; } .bs-popover-right > .arrow::before, .bs-popover-auto[x-placement^="right"] > .arrow::before { left: 0; border-width: 0.5rem 0.5rem 0.5rem 0; border-right-color: rgba(0, 0, 0, 0.25); } .bs-popover-right > .arrow::after, .bs-popover-auto[x-placement^="right"] > .arrow::after { left: 1px; border-width: 0.5rem 0.5rem 0.5rem 0; border-right-color: #fff; } .bs-popover-bottom, .bs-popover-auto[x-placement^="bottom"] { margin-top: 0.5rem; } .bs-popover-bottom > .arrow, .bs-popover-auto[x-placement^="bottom"] > .arrow { top: calc(-0.5rem - 1px); } .bs-popover-bottom > .arrow::before, .bs-popover-auto[x-placement^="bottom"] > .arrow::before { top: 0; border-width: 0 0.5rem 0.5rem 0.5rem; border-bottom-color: rgba(0, 0, 0, 0.25); } .bs-popover-bottom > .arrow::after, .bs-popover-auto[x-placement^="bottom"] > .arrow::after { top: 1px; border-width: 0 0.5rem 0.5rem 0.5rem; border-bottom-color: #fff; } .bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^="bottom"] .popover-header::before { position: absolute; top: 0; left: 50%; display: block; width: 1rem; margin-left: -0.5rem; content: ""; border-bottom: 1px solid #f7f7f7; } .bs-popover-left, .bs-popover-auto[x-placement^="left"] { margin-right: 0.5rem; } .bs-popover-left > .arrow, .bs-popover-auto[x-placement^="left"] > .arrow { right: calc(-0.5rem - 1px); width: 0.5rem; height: 1rem; margin: 0.3rem 0; } .bs-popover-left > .arrow::before, .bs-popover-auto[x-placement^="left"] > .arrow::before { right: 0; border-width: 0.5rem 0 0.5rem 0.5rem; border-left-color: rgba(0, 0, 0, 0.25); } .bs-popover-left > .arrow::after, .bs-popover-auto[x-placement^="left"] > .arrow::after { right: 1px; border-width: 0.5rem 0 0.5rem 0.5rem; border-left-color: #fff; } .popover-header { padding: 0.5rem 0.75rem; margin-bottom: 0; font-size: 1rem; background-color: #f7f7f7; border-bottom: 1px solid #ebebeb; border-top-left-radius: calc(0.3rem - 1px); border-top-right-radius: calc(0.3rem - 1px); } .popover-header:empty { display: none; } .popover-body { padding: 0.5rem 0.75rem; color: #212529; } .carousel { position: relative; } .carousel.pointer-event { -ms-touch-action: pan-y; touch-action: pan-y; } .carousel-inner { position: relative; width: 100%; overflow: hidden; } .carousel-inner::after { display: block; clear: both; content: ""; } .carousel-item { position: relative; display: none; float: left; width: 100%; margin-right: -100%; -webkit-backface-visibility: hidden; backface-visibility: hidden; transition: -webkit-transform 0.6s ease-in-out; transition: transform 0.6s ease-in-out; transition: transform 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out; } @media (prefers-reduced-motion: reduce) { .carousel-item { transition: none; } } .carousel-item.active, .carousel-item-next, .carousel-item-prev { display: block; } .carousel-item-next:not(.carousel-item-left), .active.carousel-item-right { -webkit-transform: translateX(100%); transform: translateX(100%); } .carousel-item-prev:not(.carousel-item-right), .active.carousel-item-left { -webkit-transform: translateX(-100%); transform: translateX(-100%); } .carousel-fade .carousel-item { opacity: 0; transition-property: opacity; -webkit-transform: none; transform: none; } .carousel-fade .carousel-item.active, .carousel-fade .carousel-item-next.carousel-item-left, .carousel-fade .carousel-item-prev.carousel-item-right { z-index: 1; opacity: 1; } .carousel-fade .active.carousel-item-left, .carousel-fade .active.carousel-item-right { z-index: 0; opacity: 0; transition: opacity 0s 0.6s; } @media (prefers-reduced-motion: reduce) { .carousel-fade .active.carousel-item-left, .carousel-fade .active.carousel-item-right { transition: none; } } .carousel-control-prev, .carousel-control-next { position: absolute; top: 0; bottom: 0; z-index: 1; display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; -ms-flex-pack: center; justify-content: center; width: 15%; color: #fff; text-align: center; opacity: 0.5; transition: opacity 0.15s ease; } @media (prefers-reduced-motion: reduce) { .carousel-control-prev, .carousel-control-next { transition: none; } } .carousel-control-prev:hover, .carousel-control-prev:focus, .carousel-control-next:hover, .carousel-control-next:focus { color: #fff; text-decoration: none; outline: 0; opacity: 0.9; } .carousel-control-prev { left: 0; } .carousel-control-next { right: 0; } .carousel-control-prev-icon, .carousel-control-next-icon { display: inline-block; width: 20px; height: 20px; background: no-repeat 50% / 100% 100%; } .carousel-control-prev-icon { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e"); } .carousel-control-next-icon { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e"); } .carousel-indicators { position: absolute; right: 0; bottom: 0; left: 0; z-index: 15; display: -ms-flexbox; display: flex; -ms-flex-pack: center; justify-content: center; padding-left: 0; margin-right: 15%; margin-left: 15%; list-style: none; } .carousel-indicators li { box-sizing: content-box; -ms-flex: 0 1 auto; flex: 0 1 auto; width: 30px; height: 3px; margin-right: 3px; margin-left: 3px; text-indent: -999px; cursor: pointer; background-color: #fff; background-clip: padding-box; border-top: 10px solid transparent; border-bottom: 10px solid transparent; opacity: .5; transition: opacity 0.6s ease; } @media (prefers-reduced-motion: reduce) { .carousel-indicators li { transition: none; } } .carousel-indicators .active { opacity: 1; } .carousel-caption { position: absolute; right: 15%; bottom: 20px; left: 15%; z-index: 10; padding-top: 20px; padding-bottom: 20px; color: #fff; text-align: center; } @-webkit-keyframes spinner-border { to { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } @keyframes spinner-border { to { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } .spinner-border { display: inline-block; width: 2rem; height: 2rem; vertical-align: text-bottom; border: 0.25em solid currentColor; border-right-color: transparent; border-radius: 50%; -webkit-animation: spinner-border .75s linear infinite; animation: spinner-border .75s linear infinite; } .spinner-border-sm { width: 1rem; height: 1rem; border-width: 0.2em; } @-webkit-keyframes spinner-grow { 0% { -webkit-transform: scale(0); transform: scale(0); } 50% { opacity: 1; -webkit-transform: none; transform: none; } } @keyframes spinner-grow { 0% { -webkit-transform: scale(0); transform: scale(0); } 50% { opacity: 1; -webkit-transform: none; transform: none; } } .spinner-grow { display: inline-block; width: 2rem; height: 2rem; vertical-align: text-bottom; background-color: currentColor; border-radius: 50%; opacity: 0; -webkit-animation: spinner-grow .75s linear infinite; animation: spinner-grow .75s linear infinite; } .spinner-grow-sm { width: 1rem; height: 1rem; } .align-baseline { vertical-align: baseline !important; } .align-top { vertical-align: top !important; } .align-middle { vertical-align: middle !important; } .align-bottom { vertical-align: bottom !important; } .align-text-bottom { vertical-align: text-bottom !important; } .align-text-top { vertical-align: text-top !important; } .bg-primary { background-color: #007bff !important; } a.bg-primary:hover, a.bg-primary:focus, button.bg-primary:hover, button.bg-primary:focus { background-color: #0062cc !important; } .bg-secondary { background-color: #6c757d !important; } a.bg-secondary:hover, a.bg-secondary:focus, button.bg-secondary:hover, button.bg-secondary:focus { background-color: #545b62 !important; } .bg-success { background-color: #28a745 !important; } a.bg-success:hover, a.bg-success:focus, button.bg-success:hover, button.bg-success:focus { background-color: #1e7e34 !important; } .bg-info { background-color: #17a2b8 !important; } a.bg-info:hover, a.bg-info:focus, button.bg-info:hover, button.bg-info:focus { background-color: #117a8b !important; } .bg-warning { background-color: #ffc107 !important; } a.bg-warning:hover, a.bg-warning:focus, button.bg-warning:hover, button.bg-warning:focus { background-color: #d39e00 !important; } .bg-danger { background-color: #dc3545 !important; } a.bg-danger:hover, a.bg-danger:focus, button.bg-danger:hover, button.bg-danger:focus { background-color: #bd2130 !important; } .bg-light { background-color: #f8f9fa !important; } a.bg-light:hover, a.bg-light:focus, button.bg-light:hover, button.bg-light:focus { background-color: #dae0e5 !important; } .bg-dark { background-color: #343a40 !important; } a.bg-dark:hover, a.bg-dark:focus, button.bg-dark:hover, button.bg-dark:focus { background-color: #1d2124 !important; } .bg-white { background-color: #fff !important; } .bg-transparent { background-color: transparent !important; } .border { border: 1px solid #dee2e6 !important; } .border-top { border-top: 1px solid #dee2e6 !important; } .border-right { border-right: 1px solid #dee2e6 !important; } .border-bottom { border-bottom: 1px solid #dee2e6 !important; } .border-left { border-left: 1px solid #dee2e6 !important; } .border-0 { border: 0 !important; } .border-top-0 { border-top: 0 !important; } .border-right-0 { border-right: 0 !important; } .border-bottom-0 { border-bottom: 0 !important; } .border-left-0 { border-left: 0 !important; } .border-primary { border-color: #007bff !important; } .border-secondary { border-color: #6c757d !important; } .border-success { border-color: #28a745 !important; } .border-info { border-color: #17a2b8 !important; } .border-warning { border-color: #ffc107 !important; } .border-danger { border-color: #dc3545 !important; } .border-light { border-color: #f8f9fa !important; } .border-dark { border-color: #343a40 !important; } .border-white { border-color: #fff !important; } .rounded-sm { border-radius: 0.2rem !important; } .rounded { border-radius: 0.25rem !important; } .rounded-top { border-top-left-radius: 0.25rem !important; border-top-right-radius: 0.25rem !important; } .rounded-right { border-top-right-radius: 0.25rem !important; border-bottom-right-radius: 0.25rem !important; } .rounded-bottom { border-bottom-right-radius: 0.25rem !important; border-bottom-left-radius: 0.25rem !important; } .rounded-left { border-top-left-radius: 0.25rem !important; border-bottom-left-radius: 0.25rem !important; } .rounded-lg { border-radius: 0.3rem !important; } .rounded-circle { border-radius: 50% !important; } .rounded-pill { border-radius: 50rem !important; } .rounded-0 { border-radius: 0 !important; } .clearfix::after { display: block; clear: both; content: ""; } .d-none { display: none !important; } .d-inline { display: inline !important; } .d-inline-block { display: inline-block !important; } .d-block { display: block !important; } .d-table { display: table !important; } .d-table-row { display: table-row !important; } .d-table-cell { display: table-cell !important; } .d-flex { display: -ms-flexbox !important; display: flex !important; } .d-inline-flex { display: -ms-inline-flexbox !important; display: inline-flex !important; } @media (min-width: 576px) { .d-sm-none { display: none !important; } .d-sm-inline { display: inline !important; } .d-sm-inline-block { display: inline-block !important; } .d-sm-block { display: block !important; } .d-sm-table { display: table !important; } .d-sm-table-row { display: table-row !important; } .d-sm-table-cell { display: table-cell !important; } .d-sm-flex { display: -ms-flexbox !important; display: flex !important; } .d-sm-inline-flex { display: -ms-inline-flexbox !important; display: inline-flex !important; } } @media (min-width: 768px) { .d-md-none { display: none !important; } .d-md-inline { display: inline !important; } .d-md-inline-block { display: inline-block !important; } .d-md-block { display: block !important; } .d-md-table { display: table !important; } .d-md-table-row { display: table-row !important; } .d-md-table-cell { display: table-cell !important; } .d-md-flex { display: -ms-flexbox !important; display: flex !important; } .d-md-inline-flex { display: -ms-inline-flexbox !important; display: inline-flex !important; } } @media (min-width: 992px) { .d-lg-none { display: none !important; } .d-lg-inline { display: inline !important; } .d-lg-inline-block { display: inline-block !important; } .d-lg-block { display: block !important; } .d-lg-table { display: table !important; } .d-lg-table-row { display: table-row !important; } .d-lg-table-cell { display: table-cell !important; } .d-lg-flex { display: -ms-flexbox !important; display: flex !important; } .d-lg-inline-flex { display: -ms-inline-flexbox !important; display: inline-flex !important; } } @media (min-width: 1200px) { .d-xl-none { display: none !important; } .d-xl-inline { display: inline !important; } .d-xl-inline-block { display: inline-block !important; } .d-xl-block { display: block !important; } .d-xl-table { display: table !important; } .d-xl-table-row { display: table-row !important; } .d-xl-table-cell { display: table-cell !important; } .d-xl-flex { display: -ms-flexbox !important; display: flex !important; } .d-xl-inline-flex { display: -ms-inline-flexbox !important; display: inline-flex !important; } } @media print { .d-print-none { display: none !important; } .d-print-inline { display: inline !important; } .d-print-inline-block { display: inline-block !important; } .d-print-block { display: block !important; } .d-print-table { display: table !important; } .d-print-table-row { display: table-row !important; } .d-print-table-cell { display: table-cell !important; } .d-print-flex { display: -ms-flexbox !important; display: flex !important; } .d-print-inline-flex { display: -ms-inline-flexbox !important; display: inline-flex !important; } } .embed-responsive { position: relative; display: block; width: 100%; padding: 0; overflow: hidden; } .embed-responsive::before { display: block; content: ""; } .embed-responsive .embed-responsive-item, .embed-responsive iframe, .embed-responsive embed, .embed-responsive object, .embed-responsive video { position: absolute; top: 0; bottom: 0; left: 0; width: 100%; height: 100%; border: 0; } .embed-responsive-21by9::before { padding-top: 42.857143%; } .embed-responsive-16by9::before { padding-top: 56.25%; } .embed-responsive-4by3::before { padding-top: 75%; } .embed-responsive-1by1::before { padding-top: 100%; } .flex-row { -ms-flex-direction: row !important; flex-direction: row !important; } .flex-column { -ms-flex-direction: column !important; flex-direction: column !important; } .flex-row-reverse { -ms-flex-direction: row-reverse !important; flex-direction: row-reverse !important; } .flex-column-reverse { -ms-flex-direction: column-reverse !important; flex-direction: column-reverse !important; } .flex-wrap { -ms-flex-wrap: wrap !important; flex-wrap: wrap !important; } .flex-nowrap { -ms-flex-wrap: nowrap !important; flex-wrap: nowrap !important; } .flex-wrap-reverse { -ms-flex-wrap: wrap-reverse !important; flex-wrap: wrap-reverse !important; } .flex-fill { -ms-flex: 1 1 auto !important; flex: 1 1 auto !important; } .flex-grow-0 { -ms-flex-positive: 0 !important; flex-grow: 0 !important; } .flex-grow-1 { -ms-flex-positive: 1 !important; flex-grow: 1 !important; } .flex-shrink-0 { -ms-flex-negative: 0 !important; flex-shrink: 0 !important; } .flex-shrink-1 { -ms-flex-negative: 1 !important; flex-shrink: 1 !important; } .justify-content-start { -ms-flex-pack: start !important; justify-content: flex-start !important; } .justify-content-end { -ms-flex-pack: end !important; justify-content: flex-end !important; } .justify-content-center { -ms-flex-pack: center !important; justify-content: center !important; } .justify-content-between { -ms-flex-pack: justify !important; justify-content: space-between !important; } .justify-content-around { -ms-flex-pack: distribute !important; justify-content: space-around !important; } .align-items-start { -ms-flex-align: start !important; align-items: flex-start !important; } .align-items-end { -ms-flex-align: end !important; align-items: flex-end !important; } .align-items-center { -ms-flex-align: center !important; align-items: center !important; } .align-items-baseline { -ms-flex-align: baseline !important; align-items: baseline !important; } .align-items-stretch { -ms-flex-align: stretch !important; align-items: stretch !important; } .align-content-start { -ms-flex-line-pack: start !important; align-content: flex-start !important; } .align-content-end { -ms-flex-line-pack: end !important; align-content: flex-end !important; } .align-content-center { -ms-flex-line-pack: center !important; align-content: center !important; } .align-content-between { -ms-flex-line-pack: justify !important; align-content: space-between !important; } .align-content-around { -ms-flex-line-pack: distribute !important; align-content: space-around !important; } .align-content-stretch { -ms-flex-line-pack: stretch !important; align-content: stretch !important; } .align-self-auto { -ms-flex-item-align: auto !important; align-self: auto !important; } .align-self-start { -ms-flex-item-align: start !important; align-self: flex-start !important; } .align-self-end { -ms-flex-item-align: end !important; align-self: flex-end !important; } .align-self-center { -ms-flex-item-align: center !important; align-self: center !important; } .align-self-baseline { -ms-flex-item-align: baseline !important; align-self: baseline !important; } .align-self-stretch { -ms-flex-item-align: stretch !important; align-self: stretch !important; } @media (min-width: 576px) { .flex-sm-row { -ms-flex-direction: row !important; flex-direction: row !important; } .flex-sm-column { -ms-flex-direction: column !important; flex-direction: column !important; } .flex-sm-row-reverse { -ms-flex-direction: row-reverse !important; flex-direction: row-reverse !important; } .flex-sm-column-reverse { -ms-flex-direction: column-reverse !important; flex-direction: column-reverse !important; } .flex-sm-wrap { -ms-flex-wrap: wrap !important; flex-wrap: wrap !important; } .flex-sm-nowrap { -ms-flex-wrap: nowrap !important; flex-wrap: nowrap !important; } .flex-sm-wrap-reverse { -ms-flex-wrap: wrap-reverse !important; flex-wrap: wrap-reverse !important; } .flex-sm-fill { -ms-flex: 1 1 auto !important; flex: 1 1 auto !important; } .flex-sm-grow-0 { -ms-flex-positive: 0 !important; flex-grow: 0 !important; } .flex-sm-grow-1 { -ms-flex-positive: 1 !important; flex-grow: 1 !important; } .flex-sm-shrink-0 { -ms-flex-negative: 0 !important; flex-shrink: 0 !important; } .flex-sm-shrink-1 { -ms-flex-negative: 1 !important; flex-shrink: 1 !important; } .justify-content-sm-start { -ms-flex-pack: start !important; justify-content: flex-start !important; } .justify-content-sm-end { -ms-flex-pack: end !important; justify-content: flex-end !important; } .justify-content-sm-center { -ms-flex-pack: center !important; justify-content: center !important; } .justify-content-sm-between { -ms-flex-pack: justify !important; justify-content: space-between !important; } .justify-content-sm-around { -ms-flex-pack: distribute !important; justify-content: space-around !important; } .align-items-sm-start { -ms-flex-align: start !important; align-items: flex-start !important; } .align-items-sm-end { -ms-flex-align: end !important; align-items: flex-end !important; } .align-items-sm-center { -ms-flex-align: center !important; align-items: center !important; } .align-items-sm-baseline { -ms-flex-align: baseline !important; align-items: baseline !important; } .align-items-sm-stretch { -ms-flex-align: stretch !important; align-items: stretch !important; } .align-content-sm-start { -ms-flex-line-pack: start !important; align-content: flex-start !important; } .align-content-sm-end { -ms-flex-line-pack: end !important; align-content: flex-end !important; } .align-content-sm-center { -ms-flex-line-pack: center !important; align-content: center !important; } .align-content-sm-between { -ms-flex-line-pack: justify !important; align-content: space-between !important; } .align-content-sm-around { -ms-flex-line-pack: distribute !important; align-content: space-around !important; } .align-content-sm-stretch { -ms-flex-line-pack: stretch !important; align-content: stretch !important; } .align-self-sm-auto { -ms-flex-item-align: auto !important; align-self: auto !important; } .align-self-sm-start { -ms-flex-item-align: start !important; align-self: flex-start !important; } .align-self-sm-end { -ms-flex-item-align: end !important; align-self: flex-end !important; } .align-self-sm-center { -ms-flex-item-align: center !important; align-self: center !important; } .align-self-sm-baseline { -ms-flex-item-align: baseline !important; align-self: baseline !important; } .align-self-sm-stretch { -ms-flex-item-align: stretch !important; align-self: stretch !important; } } @media (min-width: 768px) { .flex-md-row { -ms-flex-direction: row !important; flex-direction: row !important; } .flex-md-column { -ms-flex-direction: column !important; flex-direction: column !important; } .flex-md-row-reverse { -ms-flex-direction: row-reverse !important; flex-direction: row-reverse !important; } .flex-md-column-reverse { -ms-flex-direction: column-reverse !important; flex-direction: column-reverse !important; } .flex-md-wrap { -ms-flex-wrap: wrap !important; flex-wrap: wrap !important; } .flex-md-nowrap { -ms-flex-wrap: nowrap !important; flex-wrap: nowrap !important; } .flex-md-wrap-reverse { -ms-flex-wrap: wrap-reverse !important; flex-wrap: wrap-reverse !important; } .flex-md-fill { -ms-flex: 1 1 auto !important; flex: 1 1 auto !important; } .flex-md-grow-0 { -ms-flex-positive: 0 !important; flex-grow: 0 !important; } .flex-md-grow-1 { -ms-flex-positive: 1 !important; flex-grow: 1 !important; } .flex-md-shrink-0 { -ms-flex-negative: 0 !important; flex-shrink: 0 !important; } .flex-md-shrink-1 { -ms-flex-negative: 1 !important; flex-shrink: 1 !important; } .justify-content-md-start { -ms-flex-pack: start !important; justify-content: flex-start !important; } .justify-content-md-end { -ms-flex-pack: end !important; justify-content: flex-end !important; } .justify-content-md-center { -ms-flex-pack: center !important; justify-content: center !important; } .justify-content-md-between { -ms-flex-pack: justify !important; justify-content: space-between !important; } .justify-content-md-around { -ms-flex-pack: distribute !important; justify-content: space-around !important; } .align-items-md-start { -ms-flex-align: start !important; align-items: flex-start !important; } .align-items-md-end { -ms-flex-align: end !important; align-items: flex-end !important; } .align-items-md-center { -ms-flex-align: center !important; align-items: center !important; } .align-items-md-baseline { -ms-flex-align: baseline !important; align-items: baseline !important; } .align-items-md-stretch { -ms-flex-align: stretch !important; align-items: stretch !important; } .align-content-md-start { -ms-flex-line-pack: start !important; align-content: flex-start !important; } .align-content-md-end { -ms-flex-line-pack: end !important; align-content: flex-end !important; } .align-content-md-center { -ms-flex-line-pack: center !important; align-content: center !important; } .align-content-md-between { -ms-flex-line-pack: justify !important; align-content: space-between !important; } .align-content-md-around { -ms-flex-line-pack: distribute !important; align-content: space-around !important; } .align-content-md-stretch { -ms-flex-line-pack: stretch !important; align-content: stretch !important; } .align-self-md-auto { -ms-flex-item-align: auto !important; align-self: auto !important; } .align-self-md-start { -ms-flex-item-align: start !important; align-self: flex-start !important; } .align-self-md-end { -ms-flex-item-align: end !important; align-self: flex-end !important; } .align-self-md-center { -ms-flex-item-align: center !important; align-self: center !important; } .align-self-md-baseline { -ms-flex-item-align: baseline !important; align-self: baseline !important; } .align-self-md-stretch { -ms-flex-item-align: stretch !important; align-self: stretch !important; } } @media (min-width: 992px) { .flex-lg-row { -ms-flex-direction: row !important; flex-direction: row !important; } .flex-lg-column { -ms-flex-direction: column !important; flex-direction: column !important; } .flex-lg-row-reverse { -ms-flex-direction: row-reverse !important; flex-direction: row-reverse !important; } .flex-lg-column-reverse { -ms-flex-direction: column-reverse !important; flex-direction: column-reverse !important; } .flex-lg-wrap { -ms-flex-wrap: wrap !important; flex-wrap: wrap !important; } .flex-lg-nowrap { -ms-flex-wrap: nowrap !important; flex-wrap: nowrap !important; } .flex-lg-wrap-reverse { -ms-flex-wrap: wrap-reverse !important; flex-wrap: wrap-reverse !important; } .flex-lg-fill { -ms-flex: 1 1 auto !important; flex: 1 1 auto !important; } .flex-lg-grow-0 { -ms-flex-positive: 0 !important; flex-grow: 0 !important; } .flex-lg-grow-1 { -ms-flex-positive: 1 !important; flex-grow: 1 !important; } .flex-lg-shrink-0 { -ms-flex-negative: 0 !important; flex-shrink: 0 !important; } .flex-lg-shrink-1 { -ms-flex-negative: 1 !important; flex-shrink: 1 !important; } .justify-content-lg-start { -ms-flex-pack: start !important; justify-content: flex-start !important; } .justify-content-lg-end { -ms-flex-pack: end !important; justify-content: flex-end !important; } .justify-content-lg-center { -ms-flex-pack: center !important; justify-content: center !important; } .justify-content-lg-between { -ms-flex-pack: justify !important; justify-content: space-between !important; } .justify-content-lg-around { -ms-flex-pack: distribute !important; justify-content: space-around !important; } .align-items-lg-start { -ms-flex-align: start !important; align-items: flex-start !important; } .align-items-lg-end { -ms-flex-align: end !important; align-items: flex-end !important; } .align-items-lg-center { -ms-flex-align: center !important; align-items: center !important; } .align-items-lg-baseline { -ms-flex-align: baseline !important; align-items: baseline !important; } .align-items-lg-stretch { -ms-flex-align: stretch !important; align-items: stretch !important; } .align-content-lg-start { -ms-flex-line-pack: start !important; align-content: flex-start !important; } .align-content-lg-end { -ms-flex-line-pack: end !important; align-content: flex-end !important; } .align-content-lg-center { -ms-flex-line-pack: center !important; align-content: center !important; } .align-content-lg-between { -ms-flex-line-pack: justify !important; align-content: space-between !important; } .align-content-lg-around { -ms-flex-line-pack: distribute !important; align-content: space-around !important; } .align-content-lg-stretch { -ms-flex-line-pack: stretch !important; align-content: stretch !important; } .align-self-lg-auto { -ms-flex-item-align: auto !important; align-self: auto !important; } .align-self-lg-start { -ms-flex-item-align: start !important; align-self: flex-start !important; } .align-self-lg-end { -ms-flex-item-align: end !important; align-self: flex-end !important; } .align-self-lg-center { -ms-flex-item-align: center !important; align-self: center !important; } .align-self-lg-baseline { -ms-flex-item-align: baseline !important; align-self: baseline !important; } .align-self-lg-stretch { -ms-flex-item-align: stretch !important; align-self: stretch !important; } } @media (min-width: 1200px) { .flex-xl-row { -ms-flex-direction: row !important; flex-direction: row !important; } .flex-xl-column { -ms-flex-direction: column !important; flex-direction: column !important; } .flex-xl-row-reverse { -ms-flex-direction: row-reverse !important; flex-direction: row-reverse !important; } .flex-xl-column-reverse { -ms-flex-direction: column-reverse !important; flex-direction: column-reverse !important; } .flex-xl-wrap { -ms-flex-wrap: wrap !important; flex-wrap: wrap !important; } .flex-xl-nowrap { -ms-flex-wrap: nowrap !important; flex-wrap: nowrap !important; } .flex-xl-wrap-reverse { -ms-flex-wrap: wrap-reverse !important; flex-wrap: wrap-reverse !important; } .flex-xl-fill { -ms-flex: 1 1 auto !important; flex: 1 1 auto !important; } .flex-xl-grow-0 { -ms-flex-positive: 0 !important; flex-grow: 0 !important; } .flex-xl-grow-1 { -ms-flex-positive: 1 !important; flex-grow: 1 !important; } .flex-xl-shrink-0 { -ms-flex-negative: 0 !important; flex-shrink: 0 !important; } .flex-xl-shrink-1 { -ms-flex-negative: 1 !important; flex-shrink: 1 !important; } .justify-content-xl-start { -ms-flex-pack: start !important; justify-content: flex-start !important; } .justify-content-xl-end { -ms-flex-pack: end !important; justify-content: flex-end !important; } .justify-content-xl-center { -ms-flex-pack: center !important; justify-content: center !important; } .justify-content-xl-between { -ms-flex-pack: justify !important; justify-content: space-between !important; } .justify-content-xl-around { -ms-flex-pack: distribute !important; justify-content: space-around !important; } .align-items-xl-start { -ms-flex-align: start !important; align-items: flex-start !important; } .align-items-xl-end { -ms-flex-align: end !important; align-items: flex-end !important; } .align-items-xl-center { -ms-flex-align: center !important; align-items: center !important; } .align-items-xl-baseline { -ms-flex-align: baseline !important; align-items: baseline !important; } .align-items-xl-stretch { -ms-flex-align: stretch !important; align-items: stretch !important; } .align-content-xl-start { -ms-flex-line-pack: start !important; align-content: flex-start !important; } .align-content-xl-end { -ms-flex-line-pack: end !important; align-content: flex-end !important; } .align-content-xl-center { -ms-flex-line-pack: center !important; align-content: center !important; } .align-content-xl-between { -ms-flex-line-pack: justify !important; align-content: space-between !important; } .align-content-xl-around { -ms-flex-line-pack: distribute !important; align-content: space-around !important; } .align-content-xl-stretch { -ms-flex-line-pack: stretch !important; align-content: stretch !important; } .align-self-xl-auto { -ms-flex-item-align: auto !important; align-self: auto !important; } .align-self-xl-start { -ms-flex-item-align: start !important; align-self: flex-start !important; } .align-self-xl-end { -ms-flex-item-align: end !important; align-self: flex-end !important; } .align-self-xl-center { -ms-flex-item-align: center !important; align-self: center !important; } .align-self-xl-baseline { -ms-flex-item-align: baseline !important; align-self: baseline !important; } .align-self-xl-stretch { -ms-flex-item-align: stretch !important; align-self: stretch !important; } } .float-left { float: left !important; } .float-right { float: right !important; } .float-none { float: none !important; } @media (min-width: 576px) { .float-sm-left { float: left !important; } .float-sm-right { float: right !important; } .float-sm-none { float: none !important; } } @media (min-width: 768px) { .float-md-left { float: left !important; } .float-md-right { float: right !important; } .float-md-none { float: none !important; } } @media (min-width: 992px) { .float-lg-left { float: left !important; } .float-lg-right { float: right !important; } .float-lg-none { float: none !important; } } @media (min-width: 1200px) { .float-xl-left { float: left !important; } .float-xl-right { float: right !important; } .float-xl-none { float: none !important; } } .user-select-all { -webkit-user-select: all !important; -moz-user-select: all !important; -ms-user-select: all !important; user-select: all !important; } .user-select-auto { -webkit-user-select: auto !important; -moz-user-select: auto !important; -ms-user-select: auto !important; user-select: auto !important; } .user-select-none { -webkit-user-select: none !important; -moz-user-select: none !important; -ms-user-select: none !important; user-select: none !important; } .overflow-auto { overflow: auto !important; } .overflow-hidden { overflow: hidden !important; } .position-static { position: static !important; } .position-relative { position: relative !important; } .position-absolute { position: absolute !important; } .position-fixed { position: fixed !important; } .position-sticky { position: -webkit-sticky !important; position: sticky !important; } .fixed-top { position: fixed; top: 0; right: 0; left: 0; z-index: 1030; } .fixed-bottom { position: fixed; right: 0; bottom: 0; left: 0; z-index: 1030; } @supports ((position: -webkit-sticky) or (position: sticky)) { .sticky-top { position: -webkit-sticky; position: sticky; top: 0; z-index: 1020; } } .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } .sr-only-focusable:active, .sr-only-focusable:focus { position: static; width: auto; height: auto; overflow: visible; clip: auto; white-space: normal; } .shadow-sm { box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important; } .shadow { box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; } .shadow-lg { box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important; } .shadow-none { box-shadow: none !important; } .w-25 { width: 25% !important; } .w-50 { width: 50% !important; } .w-75 { width: 75% !important; } .w-100 { width: 100% !important; } .w-auto { width: auto !important; } .h-25 { height: 25% !important; } .h-50 { height: 50% !important; } .h-75 { height: 75% !important; } .h-100 { height: 100% !important; } .h-auto { height: auto !important; } .mw-100 { max-width: 100% !important; } .mh-100 { max-height: 100% !important; } .min-vw-100 { min-width: 100vw !important; } .min-vh-100 { min-height: 100vh !important; } .vw-100 { width: 100vw !important; } .vh-100 { height: 100vh !important; } .m-0 { margin: 0 !important; } .mt-0, .my-0 { margin-top: 0 !important; } .mr-0, .mx-0 { margin-right: 0 !important; } .mb-0, .my-0 { margin-bottom: 0 !important; } .ml-0, .mx-0 { margin-left: 0 !important; } .m-1 { margin: 0.25rem !important; } .mt-1, .my-1 { margin-top: 0.25rem !important; } .mr-1, .mx-1 { margin-right: 0.25rem !important; } .mb-1, .my-1 { margin-bottom: 0.25rem !important; } .ml-1, .mx-1 { margin-left: 0.25rem !important; } .m-2 { margin: 0.5rem !important; } .mt-2, .my-2 { margin-top: 0.5rem !important; } .mr-2, .mx-2 { margin-right: 0.5rem !important; } .mb-2, .my-2 { margin-bottom: 0.5rem !important; } .ml-2, .mx-2 { margin-left: 0.5rem !important; } .m-3 { margin: 1rem !important; } .mt-3, .my-3 { margin-top: 1rem !important; } .mr-3, .mx-3 { margin-right: 1rem !important; } .mb-3, .my-3 { margin-bottom: 1rem !important; } .ml-3, .mx-3 { margin-left: 1rem !important; } .m-4 { margin: 1.5rem !important; } .mt-4, .my-4 { margin-top: 1.5rem !important; } .mr-4, .mx-4 { margin-right: 1.5rem !important; } .mb-4, .my-4 { margin-bottom: 1.5rem !important; } .ml-4, .mx-4 { margin-left: 1.5rem !important; } .m-5 { margin: 3rem !important; } .mt-5, .my-5 { margin-top: 3rem !important; } .mr-5, .mx-5 { margin-right: 3rem !important; } .mb-5, .my-5 { margin-bottom: 3rem !important; } .ml-5, .mx-5 { margin-left: 3rem !important; } .p-0 { padding: 0 !important; } .pt-0, .py-0 { padding-top: 0 !important; } .pr-0, .px-0 { padding-right: 0 !important; } .pb-0, .py-0 { padding-bottom: 0 !important; } .pl-0, .px-0 { padding-left: 0 !important; } .p-1 { padding: 0.25rem !important; } .pt-1, .py-1 { padding-top: 0.25rem !important; } .pr-1, .px-1 { padding-right: 0.25rem !important; } .pb-1, .py-1 { padding-bottom: 0.25rem !important; } .pl-1, .px-1 { padding-left: 0.25rem !important; } .p-2 { padding: 0.5rem !important; } .pt-2, .py-2 { padding-top: 0.5rem !important; } .pr-2, .px-2 { padding-right: 0.5rem !important; } .pb-2, .py-2 { padding-bottom: 0.5rem !important; } .pl-2, .px-2 { padding-left: 0.5rem !important; } .p-3 { padding: 1rem !important; } .pt-3, .py-3 { padding-top: 1rem !important; } .pr-3, .px-3 { padding-right: 1rem !important; } .pb-3, .py-3 { padding-bottom: 1rem !important; } .pl-3, .px-3 { padding-left: 1rem !important; } .p-4 { padding: 1.5rem !important; } .pt-4, .py-4 { padding-top: 1.5rem !important; } .pr-4, .px-4 { padding-right: 1.5rem !important; } .pb-4, .py-4 { padding-bottom: 1.5rem !important; } .pl-4, .px-4 { padding-left: 1.5rem !important; } .p-5 { padding: 3rem !important; } .pt-5, .py-5 { padding-top: 3rem !important; } .pr-5, .px-5 { padding-right: 3rem !important; } .pb-5, .py-5 { padding-bottom: 3rem !important; } .pl-5, .px-5 { padding-left: 3rem !important; } .m-n1 { margin: -0.25rem !important; } .mt-n1, .my-n1 { margin-top: -0.25rem !important; } .mr-n1, .mx-n1 { margin-right: -0.25rem !important; } .mb-n1, .my-n1 { margin-bottom: -0.25rem !important; } .ml-n1, .mx-n1 { margin-left: -0.25rem !important; } .m-n2 { margin: -0.5rem !important; } .mt-n2, .my-n2 { margin-top: -0.5rem !important; } .mr-n2, .mx-n2 { margin-right: -0.5rem !important; } .mb-n2, .my-n2 { margin-bottom: -0.5rem !important; } .ml-n2, .mx-n2 { margin-left: -0.5rem !important; } .m-n3 { margin: -1rem !important; } .mt-n3, .my-n3 { margin-top: -1rem !important; } .mr-n3, .mx-n3 { margin-right: -1rem !important; } .mb-n3, .my-n3 { margin-bottom: -1rem !important; } .ml-n3, .mx-n3 { margin-left: -1rem !important; } .m-n4 { margin: -1.5rem !important; } .mt-n4, .my-n4 { margin-top: -1.5rem !important; } .mr-n4, .mx-n4 { margin-right: -1.5rem !important; } .mb-n4, .my-n4 { margin-bottom: -1.5rem !important; } .ml-n4, .mx-n4 { margin-left: -1.5rem !important; } .m-n5 { margin: -3rem !important; } .mt-n5, .my-n5 { margin-top: -3rem !important; } .mr-n5, .mx-n5 { margin-right: -3rem !important; } .mb-n5, .my-n5 { margin-bottom: -3rem !important; } .ml-n5, .mx-n5 { margin-left: -3rem !important; } .m-auto { margin: auto !important; } .mt-auto, .my-auto { margin-top: auto !important; } .mr-auto, .mx-auto { margin-right: auto !important; } .mb-auto, .my-auto { margin-bottom: auto !important; } .ml-auto, .mx-auto { margin-left: auto !important; } @media (min-width: 576px) { .m-sm-0 { margin: 0 !important; } .mt-sm-0, .my-sm-0 { margin-top: 0 !important; } .mr-sm-0, .mx-sm-0 { margin-right: 0 !important; } .mb-sm-0, .my-sm-0 { margin-bottom: 0 !important; } .ml-sm-0, .mx-sm-0 { margin-left: 0 !important; } .m-sm-1 { margin: 0.25rem !important; } .mt-sm-1, .my-sm-1 { margin-top: 0.25rem !important; } .mr-sm-1, .mx-sm-1 { margin-right: 0.25rem !important; } .mb-sm-1, .my-sm-1 { margin-bottom: 0.25rem !important; } .ml-sm-1, .mx-sm-1 { margin-left: 0.25rem !important; } .m-sm-2 { margin: 0.5rem !important; } .mt-sm-2, .my-sm-2 { margin-top: 0.5rem !important; } .mr-sm-2, .mx-sm-2 { margin-right: 0.5rem !important; } .mb-sm-2, .my-sm-2 { margin-bottom: 0.5rem !important; } .ml-sm-2, .mx-sm-2 { margin-left: 0.5rem !important; } .m-sm-3 { margin: 1rem !important; } .mt-sm-3, .my-sm-3 { margin-top: 1rem !important; } .mr-sm-3, .mx-sm-3 { margin-right: 1rem !important; } .mb-sm-3, .my-sm-3 { margin-bottom: 1rem !important; } .ml-sm-3, .mx-sm-3 { margin-left: 1rem !important; } .m-sm-4 { margin: 1.5rem !important; } .mt-sm-4, .my-sm-4 { margin-top: 1.5rem !important; } .mr-sm-4, .mx-sm-4 { margin-right: 1.5rem !important; } .mb-sm-4, .my-sm-4 { margin-bottom: 1.5rem !important; } .ml-sm-4, .mx-sm-4 { margin-left: 1.5rem !important; } .m-sm-5 { margin: 3rem !important; } .mt-sm-5, .my-sm-5 { margin-top: 3rem !important; } .mr-sm-5, .mx-sm-5 { margin-right: 3rem !important; } .mb-sm-5, .my-sm-5 { margin-bottom: 3rem !important; } .ml-sm-5, .mx-sm-5 { margin-left: 3rem !important; } .p-sm-0 { padding: 0 !important; } .pt-sm-0, .py-sm-0 { padding-top: 0 !important; } .pr-sm-0, .px-sm-0 { padding-right: 0 !important; } .pb-sm-0, .py-sm-0 { padding-bottom: 0 !important; } .pl-sm-0, .px-sm-0 { padding-left: 0 !important; } .p-sm-1 { padding: 0.25rem !important; } .pt-sm-1, .py-sm-1 { padding-top: 0.25rem !important; } .pr-sm-1, .px-sm-1 { padding-right: 0.25rem !important; } .pb-sm-1, .py-sm-1 { padding-bottom: 0.25rem !important; } .pl-sm-1, .px-sm-1 { padding-left: 0.25rem !important; } .p-sm-2 { padding: 0.5rem !important; } .pt-sm-2, .py-sm-2 { padding-top: 0.5rem !important; } .pr-sm-2, .px-sm-2 { padding-right: 0.5rem !important; } .pb-sm-2, .py-sm-2 { padding-bottom: 0.5rem !important; } .pl-sm-2, .px-sm-2 { padding-left: 0.5rem !important; } .p-sm-3 { padding: 1rem !important; } .pt-sm-3, .py-sm-3 { padding-top: 1rem !important; } .pr-sm-3, .px-sm-3 { padding-right: 1rem !important; } .pb-sm-3, .py-sm-3 { padding-bottom: 1rem !important; } .pl-sm-3, .px-sm-3 { padding-left: 1rem !important; } .p-sm-4 { padding: 1.5rem !important; } .pt-sm-4, .py-sm-4 { padding-top: 1.5rem !important; } .pr-sm-4, .px-sm-4 { padding-right: 1.5rem !important; } .pb-sm-4, .py-sm-4 { padding-bottom: 1.5rem !important; } .pl-sm-4, .px-sm-4 { padding-left: 1.5rem !important; } .p-sm-5 { padding: 3rem !important; } .pt-sm-5, .py-sm-5 { padding-top: 3rem !important; } .pr-sm-5, .px-sm-5 { padding-right: 3rem !important; } .pb-sm-5, .py-sm-5 { padding-bottom: 3rem !important; } .pl-sm-5, .px-sm-5 { padding-left: 3rem !important; } .m-sm-n1 { margin: -0.25rem !important; } .mt-sm-n1, .my-sm-n1 { margin-top: -0.25rem !important; } .mr-sm-n1, .mx-sm-n1 { margin-right: -0.25rem !important; } .mb-sm-n1, .my-sm-n1 { margin-bottom: -0.25rem !important; } .ml-sm-n1, .mx-sm-n1 { margin-left: -0.25rem !important; } .m-sm-n2 { margin: -0.5rem !important; } .mt-sm-n2, .my-sm-n2 { margin-top: -0.5rem !important; } .mr-sm-n2, .mx-sm-n2 { margin-right: -0.5rem !important; } .mb-sm-n2, .my-sm-n2 { margin-bottom: -0.5rem !important; } .ml-sm-n2, .mx-sm-n2 { margin-left: -0.5rem !important; } .m-sm-n3 { margin: -1rem !important; } .mt-sm-n3, .my-sm-n3 { margin-top: -1rem !important; } .mr-sm-n3, .mx-sm-n3 { margin-right: -1rem !important; } .mb-sm-n3, .my-sm-n3 { margin-bottom: -1rem !important; } .ml-sm-n3, .mx-sm-n3 { margin-left: -1rem !important; } .m-sm-n4 { margin: -1.5rem !important; } .mt-sm-n4, .my-sm-n4 { margin-top: -1.5rem !important; } .mr-sm-n4, .mx-sm-n4 { margin-right: -1.5rem !important; } .mb-sm-n4, .my-sm-n4 { margin-bottom: -1.5rem !important; } .ml-sm-n4, .mx-sm-n4 { margin-left: -1.5rem !important; } .m-sm-n5 { margin: -3rem !important; } .mt-sm-n5, .my-sm-n5 { margin-top: -3rem !important; } .mr-sm-n5, .mx-sm-n5 { margin-right: -3rem !important; } .mb-sm-n5, .my-sm-n5 { margin-bottom: -3rem !important; } .ml-sm-n5, .mx-sm-n5 { margin-left: -3rem !important; } .m-sm-auto { margin: auto !important; } .mt-sm-auto, .my-sm-auto { margin-top: auto !important; } .mr-sm-auto, .mx-sm-auto { margin-right: auto !important; } .mb-sm-auto, .my-sm-auto { margin-bottom: auto !important; } .ml-sm-auto, .mx-sm-auto { margin-left: auto !important; } } @media (min-width: 768px) { .m-md-0 { margin: 0 !important; } .mt-md-0, .my-md-0 { margin-top: 0 !important; } .mr-md-0, .mx-md-0 { margin-right: 0 !important; } .mb-md-0, .my-md-0 { margin-bottom: 0 !important; } .ml-md-0, .mx-md-0 { margin-left: 0 !important; } .m-md-1 { margin: 0.25rem !important; } .mt-md-1, .my-md-1 { margin-top: 0.25rem !important; } .mr-md-1, .mx-md-1 { margin-right: 0.25rem !important; } .mb-md-1, .my-md-1 { margin-bottom: 0.25rem !important; } .ml-md-1, .mx-md-1 { margin-left: 0.25rem !important; } .m-md-2 { margin: 0.5rem !important; } .mt-md-2, .my-md-2 { margin-top: 0.5rem !important; } .mr-md-2, .mx-md-2 { margin-right: 0.5rem !important; } .mb-md-2, .my-md-2 { margin-bottom: 0.5rem !important; } .ml-md-2, .mx-md-2 { margin-left: 0.5rem !important; } .m-md-3 { margin: 1rem !important; } .mt-md-3, .my-md-3 { margin-top: 1rem !important; } .mr-md-3, .mx-md-3 { margin-right: 1rem !important; } .mb-md-3, .my-md-3 { margin-bottom: 1rem !important; } .ml-md-3, .mx-md-3 { margin-left: 1rem !important; } .m-md-4 { margin: 1.5rem !important; } .mt-md-4, .my-md-4 { margin-top: 1.5rem !important; } .mr-md-4, .mx-md-4 { margin-right: 1.5rem !important; } .mb-md-4, .my-md-4 { margin-bottom: 1.5rem !important; } .ml-md-4, .mx-md-4 { margin-left: 1.5rem !important; } .m-md-5 { margin: 3rem !important; } .mt-md-5, .my-md-5 { margin-top: 3rem !important; } .mr-md-5, .mx-md-5 { margin-right: 3rem !important; } .mb-md-5, .my-md-5 { margin-bottom: 3rem !important; } .ml-md-5, .mx-md-5 { margin-left: 3rem !important; } .p-md-0 { padding: 0 !important; } .pt-md-0, .py-md-0 { padding-top: 0 !important; } .pr-md-0, .px-md-0 { padding-right: 0 !important; } .pb-md-0, .py-md-0 { padding-bottom: 0 !important; } .pl-md-0, .px-md-0 { padding-left: 0 !important; } .p-md-1 { padding: 0.25rem !important; } .pt-md-1, .py-md-1 { padding-top: 0.25rem !important; } .pr-md-1, .px-md-1 { padding-right: 0.25rem !important; } .pb-md-1, .py-md-1 { padding-bottom: 0.25rem !important; } .pl-md-1, .px-md-1 { padding-left: 0.25rem !important; } .p-md-2 { padding: 0.5rem !important; } .pt-md-2, .py-md-2 { padding-top: 0.5rem !important; } .pr-md-2, .px-md-2 { padding-right: 0.5rem !important; } .pb-md-2, .py-md-2 { padding-bottom: 0.5rem !important; } .pl-md-2, .px-md-2 { padding-left: 0.5rem !important; } .p-md-3 { padding: 1rem !important; } .pt-md-3, .py-md-3 { padding-top: 1rem !important; } .pr-md-3, .px-md-3 { padding-right: 1rem !important; } .pb-md-3, .py-md-3 { padding-bottom: 1rem !important; } .pl-md-3, .px-md-3 { padding-left: 1rem !important; } .p-md-4 { padding: 1.5rem !important; } .pt-md-4, .py-md-4 { padding-top: 1.5rem !important; } .pr-md-4, .px-md-4 { padding-right: 1.5rem !important; } .pb-md-4, .py-md-4 { padding-bottom: 1.5rem !important; } .pl-md-4, .px-md-4 { padding-left: 1.5rem !important; } .p-md-5 { padding: 3rem !important; } .pt-md-5, .py-md-5 { padding-top: 3rem !important; } .pr-md-5, .px-md-5 { padding-right: 3rem !important; } .pb-md-5, .py-md-5 { padding-bottom: 3rem !important; } .pl-md-5, .px-md-5 { padding-left: 3rem !important; } .m-md-n1 { margin: -0.25rem !important; } .mt-md-n1, .my-md-n1 { margin-top: -0.25rem !important; } .mr-md-n1, .mx-md-n1 { margin-right: -0.25rem !important; } .mb-md-n1, .my-md-n1 { margin-bottom: -0.25rem !important; } .ml-md-n1, .mx-md-n1 { margin-left: -0.25rem !important; } .m-md-n2 { margin: -0.5rem !important; } .mt-md-n2, .my-md-n2 { margin-top: -0.5rem !important; } .mr-md-n2, .mx-md-n2 { margin-right: -0.5rem !important; } .mb-md-n2, .my-md-n2 { margin-bottom: -0.5rem !important; } .ml-md-n2, .mx-md-n2 { margin-left: -0.5rem !important; } .m-md-n3 { margin: -1rem !important; } .mt-md-n3, .my-md-n3 { margin-top: -1rem !important; } .mr-md-n3, .mx-md-n3 { margin-right: -1rem !important; } .mb-md-n3, .my-md-n3 { margin-bottom: -1rem !important; } .ml-md-n3, .mx-md-n3 { margin-left: -1rem !important; } .m-md-n4 { margin: -1.5rem !important; } .mt-md-n4, .my-md-n4 { margin-top: -1.5rem !important; } .mr-md-n4, .mx-md-n4 { margin-right: -1.5rem !important; } .mb-md-n4, .my-md-n4 { margin-bottom: -1.5rem !important; } .ml-md-n4, .mx-md-n4 { margin-left: -1.5rem !important; } .m-md-n5 { margin: -3rem !important; } .mt-md-n5, .my-md-n5 { margin-top: -3rem !important; } .mr-md-n5, .mx-md-n5 { margin-right: -3rem !important; } .mb-md-n5, .my-md-n5 { margin-bottom: -3rem !important; } .ml-md-n5, .mx-md-n5 { margin-left: -3rem !important; } .m-md-auto { margin: auto !important; } .mt-md-auto, .my-md-auto { margin-top: auto !important; } .mr-md-auto, .mx-md-auto { margin-right: auto !important; } .mb-md-auto, .my-md-auto { margin-bottom: auto !important; } .ml-md-auto, .mx-md-auto { margin-left: auto !important; } } @media (min-width: 992px) { .m-lg-0 { margin: 0 !important; } .mt-lg-0, .my-lg-0 { margin-top: 0 !important; } .mr-lg-0, .mx-lg-0 { margin-right: 0 !important; } .mb-lg-0, .my-lg-0 { margin-bottom: 0 !important; } .ml-lg-0, .mx-lg-0 { margin-left: 0 !important; } .m-lg-1 { margin: 0.25rem !important; } .mt-lg-1, .my-lg-1 { margin-top: 0.25rem !important; } .mr-lg-1, .mx-lg-1 { margin-right: 0.25rem !important; } .mb-lg-1, .my-lg-1 { margin-bottom: 0.25rem !important; } .ml-lg-1, .mx-lg-1 { margin-left: 0.25rem !important; } .m-lg-2 { margin: 0.5rem !important; } .mt-lg-2, .my-lg-2 { margin-top: 0.5rem !important; } .mr-lg-2, .mx-lg-2 { margin-right: 0.5rem !important; } .mb-lg-2, .my-lg-2 { margin-bottom: 0.5rem !important; } .ml-lg-2, .mx-lg-2 { margin-left: 0.5rem !important; } .m-lg-3 { margin: 1rem !important; } .mt-lg-3, .my-lg-3 { margin-top: 1rem !important; } .mr-lg-3, .mx-lg-3 { margin-right: 1rem !important; } .mb-lg-3, .my-lg-3 { margin-bottom: 1rem !important; } .ml-lg-3, .mx-lg-3 { margin-left: 1rem !important; } .m-lg-4 { margin: 1.5rem !important; } .mt-lg-4, .my-lg-4 { margin-top: 1.5rem !important; } .mr-lg-4, .mx-lg-4 { margin-right: 1.5rem !important; } .mb-lg-4, .my-lg-4 { margin-bottom: 1.5rem !important; } .ml-lg-4, .mx-lg-4 { margin-left: 1.5rem !important; } .m-lg-5 { margin: 3rem !important; } .mt-lg-5, .my-lg-5 { margin-top: 3rem !important; } .mr-lg-5, .mx-lg-5 { margin-right: 3rem !important; } .mb-lg-5, .my-lg-5 { margin-bottom: 3rem !important; } .ml-lg-5, .mx-lg-5 { margin-left: 3rem !important; } .p-lg-0 { padding: 0 !important; } .pt-lg-0, .py-lg-0 { padding-top: 0 !important; } .pr-lg-0, .px-lg-0 { padding-right: 0 !important; } .pb-lg-0, .py-lg-0 { padding-bottom: 0 !important; } .pl-lg-0, .px-lg-0 { padding-left: 0 !important; } .p-lg-1 { padding: 0.25rem !important; } .pt-lg-1, .py-lg-1 { padding-top: 0.25rem !important; } .pr-lg-1, .px-lg-1 { padding-right: 0.25rem !important; } .pb-lg-1, .py-lg-1 { padding-bottom: 0.25rem !important; } .pl-lg-1, .px-lg-1 { padding-left: 0.25rem !important; } .p-lg-2 { padding: 0.5rem !important; } .pt-lg-2, .py-lg-2 { padding-top: 0.5rem !important; } .pr-lg-2, .px-lg-2 { padding-right: 0.5rem !important; } .pb-lg-2, .py-lg-2 { padding-bottom: 0.5rem !important; } .pl-lg-2, .px-lg-2 { padding-left: 0.5rem !important; } .p-lg-3 { padding: 1rem !important; } .pt-lg-3, .py-lg-3 { padding-top: 1rem !important; } .pr-lg-3, .px-lg-3 { padding-right: 1rem !important; } .pb-lg-3, .py-lg-3 { padding-bottom: 1rem !important; } .pl-lg-3, .px-lg-3 { padding-left: 1rem !important; } .p-lg-4 { padding: 1.5rem !important; } .pt-lg-4, .py-lg-4 { padding-top: 1.5rem !important; } .pr-lg-4, .px-lg-4 { padding-right: 1.5rem !important; } .pb-lg-4, .py-lg-4 { padding-bottom: 1.5rem !important; } .pl-lg-4, .px-lg-4 { padding-left: 1.5rem !important; } .p-lg-5 { padding: 3rem !important; } .pt-lg-5, .py-lg-5 { padding-top: 3rem !important; } .pr-lg-5, .px-lg-5 { padding-right: 3rem !important; } .pb-lg-5, .py-lg-5 { padding-bottom: 3rem !important; } .pl-lg-5, .px-lg-5 { padding-left: 3rem !important; } .m-lg-n1 { margin: -0.25rem !important; } .mt-lg-n1, .my-lg-n1 { margin-top: -0.25rem !important; } .mr-lg-n1, .mx-lg-n1 { margin-right: -0.25rem !important; } .mb-lg-n1, .my-lg-n1 { margin-bottom: -0.25rem !important; } .ml-lg-n1, .mx-lg-n1 { margin-left: -0.25rem !important; } .m-lg-n2 { margin: -0.5rem !important; } .mt-lg-n2, .my-lg-n2 { margin-top: -0.5rem !important; } .mr-lg-n2, .mx-lg-n2 { margin-right: -0.5rem !important; } .mb-lg-n2, .my-lg-n2 { margin-bottom: -0.5rem !important; } .ml-lg-n2, .mx-lg-n2 { margin-left: -0.5rem !important; } .m-lg-n3 { margin: -1rem !important; } .mt-lg-n3, .my-lg-n3 { margin-top: -1rem !important; } .mr-lg-n3, .mx-lg-n3 { margin-right: -1rem !important; } .mb-lg-n3, .my-lg-n3 { margin-bottom: -1rem !important; } .ml-lg-n3, .mx-lg-n3 { margin-left: -1rem !important; } .m-lg-n4 { margin: -1.5rem !important; } .mt-lg-n4, .my-lg-n4 { margin-top: -1.5rem !important; } .mr-lg-n4, .mx-lg-n4 { margin-right: -1.5rem !important; } .mb-lg-n4, .my-lg-n4 { margin-bottom: -1.5rem !important; } .ml-lg-n4, .mx-lg-n4 { margin-left: -1.5rem !important; } .m-lg-n5 { margin: -3rem !important; } .mt-lg-n5, .my-lg-n5 { margin-top: -3rem !important; } .mr-lg-n5, .mx-lg-n5 { margin-right: -3rem !important; } .mb-lg-n5, .my-lg-n5 { margin-bottom: -3rem !important; } .ml-lg-n5, .mx-lg-n5 { margin-left: -3rem !important; } .m-lg-auto { margin: auto !important; } .mt-lg-auto, .my-lg-auto { margin-top: auto !important; } .mr-lg-auto, .mx-lg-auto { margin-right: auto !important; } .mb-lg-auto, .my-lg-auto { margin-bottom: auto !important; } .ml-lg-auto, .mx-lg-auto { margin-left: auto !important; } } @media (min-width: 1200px) { .m-xl-0 { margin: 0 !important; } .mt-xl-0, .my-xl-0 { margin-top: 0 !important; } .mr-xl-0, .mx-xl-0 { margin-right: 0 !important; } .mb-xl-0, .my-xl-0 { margin-bottom: 0 !important; } .ml-xl-0, .mx-xl-0 { margin-left: 0 !important; } .m-xl-1 { margin: 0.25rem !important; } .mt-xl-1, .my-xl-1 { margin-top: 0.25rem !important; } .mr-xl-1, .mx-xl-1 { margin-right: 0.25rem !important; } .mb-xl-1, .my-xl-1 { margin-bottom: 0.25rem !important; } .ml-xl-1, .mx-xl-1 { margin-left: 0.25rem !important; } .m-xl-2 { margin: 0.5rem !important; } .mt-xl-2, .my-xl-2 { margin-top: 0.5rem !important; } .mr-xl-2, .mx-xl-2 { margin-right: 0.5rem !important; } .mb-xl-2, .my-xl-2 { margin-bottom: 0.5rem !important; } .ml-xl-2, .mx-xl-2 { margin-left: 0.5rem !important; } .m-xl-3 { margin: 1rem !important; } .mt-xl-3, .my-xl-3 { margin-top: 1rem !important; } .mr-xl-3, .mx-xl-3 { margin-right: 1rem !important; } .mb-xl-3, .my-xl-3 { margin-bottom: 1rem !important; } .ml-xl-3, .mx-xl-3 { margin-left: 1rem !important; } .m-xl-4 { margin: 1.5rem !important; } .mt-xl-4, .my-xl-4 { margin-top: 1.5rem !important; } .mr-xl-4, .mx-xl-4 { margin-right: 1.5rem !important; } .mb-xl-4, .my-xl-4 { margin-bottom: 1.5rem !important; } .ml-xl-4, .mx-xl-4 { margin-left: 1.5rem !important; } .m-xl-5 { margin: 3rem !important; } .mt-xl-5, .my-xl-5 { margin-top: 3rem !important; } .mr-xl-5, .mx-xl-5 { margin-right: 3rem !important; } .mb-xl-5, .my-xl-5 { margin-bottom: 3rem !important; } .ml-xl-5, .mx-xl-5 { margin-left: 3rem !important; } .p-xl-0 { padding: 0 !important; } .pt-xl-0, .py-xl-0 { padding-top: 0 !important; } .pr-xl-0, .px-xl-0 { padding-right: 0 !important; } .pb-xl-0, .py-xl-0 { padding-bottom: 0 !important; } .pl-xl-0, .px-xl-0 { padding-left: 0 !important; } .p-xl-1 { padding: 0.25rem !important; } .pt-xl-1, .py-xl-1 { padding-top: 0.25rem !important; } .pr-xl-1, .px-xl-1 { padding-right: 0.25rem !important; } .pb-xl-1, .py-xl-1 { padding-bottom: 0.25rem !important; } .pl-xl-1, .px-xl-1 { padding-left: 0.25rem !important; } .p-xl-2 { padding: 0.5rem !important; } .pt-xl-2, .py-xl-2 { padding-top: 0.5rem !important; } .pr-xl-2, .px-xl-2 { padding-right: 0.5rem !important; } .pb-xl-2, .py-xl-2 { padding-bottom: 0.5rem !important; } .pl-xl-2, .px-xl-2 { padding-left: 0.5rem !important; } .p-xl-3 { padding: 1rem !important; } .pt-xl-3, .py-xl-3 { padding-top: 1rem !important; } .pr-xl-3, .px-xl-3 { padding-right: 1rem !important; } .pb-xl-3, .py-xl-3 { padding-bottom: 1rem !important; } .pl-xl-3, .px-xl-3 { padding-left: 1rem !important; } .p-xl-4 { padding: 1.5rem !important; } .pt-xl-4, .py-xl-4 { padding-top: 1.5rem !important; } .pr-xl-4, .px-xl-4 { padding-right: 1.5rem !important; } .pb-xl-4, .py-xl-4 { padding-bottom: 1.5rem !important; } .pl-xl-4, .px-xl-4 { padding-left: 1.5rem !important; } .p-xl-5 { padding: 3rem !important; } .pt-xl-5, .py-xl-5 { padding-top: 3rem !important; } .pr-xl-5, .px-xl-5 { padding-right: 3rem !important; } .pb-xl-5, .py-xl-5 { padding-bottom: 3rem !important; } .pl-xl-5, .px-xl-5 { padding-left: 3rem !important; } .m-xl-n1 { margin: -0.25rem !important; } .mt-xl-n1, .my-xl-n1 { margin-top: -0.25rem !important; } .mr-xl-n1, .mx-xl-n1 { margin-right: -0.25rem !important; } .mb-xl-n1, .my-xl-n1 { margin-bottom: -0.25rem !important; } .ml-xl-n1, .mx-xl-n1 { margin-left: -0.25rem !important; } .m-xl-n2 { margin: -0.5rem !important; } .mt-xl-n2, .my-xl-n2 { margin-top: -0.5rem !important; } .mr-xl-n2, .mx-xl-n2 { margin-right: -0.5rem !important; } .mb-xl-n2, .my-xl-n2 { margin-bottom: -0.5rem !important; } .ml-xl-n2, .mx-xl-n2 { margin-left: -0.5rem !important; } .m-xl-n3 { margin: -1rem !important; } .mt-xl-n3, .my-xl-n3 { margin-top: -1rem !important; } .mr-xl-n3, .mx-xl-n3 { margin-right: -1rem !important; } .mb-xl-n3, .my-xl-n3 { margin-bottom: -1rem !important; } .ml-xl-n3, .mx-xl-n3 { margin-left: -1rem !important; } .m-xl-n4 { margin: -1.5rem !important; } .mt-xl-n4, .my-xl-n4 { margin-top: -1.5rem !important; } .mr-xl-n4, .mx-xl-n4 { margin-right: -1.5rem !important; } .mb-xl-n4, .my-xl-n4 { margin-bottom: -1.5rem !important; } .ml-xl-n4, .mx-xl-n4 { margin-left: -1.5rem !important; } .m-xl-n5 { margin: -3rem !important; } .mt-xl-n5, .my-xl-n5 { margin-top: -3rem !important; } .mr-xl-n5, .mx-xl-n5 { margin-right: -3rem !important; } .mb-xl-n5, .my-xl-n5 { margin-bottom: -3rem !important; } .ml-xl-n5, .mx-xl-n5 { margin-left: -3rem !important; } .m-xl-auto { margin: auto !important; } .mt-xl-auto, .my-xl-auto { margin-top: auto !important; } .mr-xl-auto, .mx-xl-auto { margin-right: auto !important; } .mb-xl-auto, .my-xl-auto { margin-bottom: auto !important; } .ml-xl-auto, .mx-xl-auto { margin-left: auto !important; } } .stretched-link::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 1; pointer-events: auto; content: ""; background-color: rgba(0, 0, 0, 0); } .text-monospace { font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important; } .text-justify { text-align: justify !important; } .text-wrap { white-space: normal !important; } .text-nowrap { white-space: nowrap !important; } .text-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .text-left { text-align: left !important; } .text-right { text-align: right !important; } .text-center { text-align: center !important; } @media (min-width: 576px) { .text-sm-left { text-align: left !important; } .text-sm-right { text-align: right !important; } .text-sm-center { text-align: center !important; } } @media (min-width: 768px) { .text-md-left { text-align: left !important; } .text-md-right { text-align: right !important; } .text-md-center { text-align: center !important; } } @media (min-width: 992px) { .text-lg-left { text-align: left !important; } .text-lg-right { text-align: right !important; } .text-lg-center { text-align: center !important; } } @media (min-width: 1200px) { .text-xl-left { text-align: left !important; } .text-xl-right { text-align: right !important; } .text-xl-center { text-align: center !important; } } .text-lowercase { text-transform: lowercase !important; } .text-uppercase { text-transform: uppercase !important; } .text-capitalize { text-transform: capitalize !important; } .font-weight-light { font-weight: 300 !important; } .font-weight-lighter { font-weight: lighter !important; } .font-weight-normal { font-weight: 400 !important; } .font-weight-bold { font-weight: 700 !important; } .font-weight-bolder { font-weight: bolder !important; } .font-italic { font-style: italic !important; } .text-white { color: #fff !important; } .text-primary { color: #007bff !important; } a.text-primary:hover, a.text-primary:focus { color: #0056b3 !important; } .text-secondary { color: #6c757d !important; } a.text-secondary:hover, a.text-secondary:focus { color: #494f54 !important; } .text-success { color: #28a745 !important; } a.text-success:hover, a.text-success:focus { color: #19692c !important; } .text-info { color: #17a2b8 !important; } a.text-info:hover, a.text-info:focus { color: #0f6674 !important; } .text-warning { color: #ffc107 !important; } a.text-warning:hover, a.text-warning:focus { color: #ba8b00 !important; } .text-danger { color: #dc3545 !important; } a.text-danger:hover, a.text-danger:focus { color: #a71d2a !important; } .text-light { color: #f8f9fa !important; } a.text-light:hover, a.text-light:focus { color: #cbd3da !important; } .text-dark { color: #343a40 !important; } a.text-dark:hover, a.text-dark:focus { color: #121416 !important; } .text-body { color: #212529 !important; } .text-muted { color: #6c757d !important; } .text-black-50 { color: rgba(0, 0, 0, 0.5) !important; } .text-white-50 { color: rgba(255, 255, 255, 0.5) !important; } .text-hide { font: 0/0 a; color: transparent; text-shadow: none; background-color: transparent; border: 0; } .text-decoration-none { text-decoration: none !important; } .text-break { word-wrap: break-word !important; } .text-reset { color: inherit !important; } .visible { visibility: visible !important; } .invisible { visibility: hidden !important; } @media print { *, *::before, *::after { text-shadow: none !important; box-shadow: none !important; } a:not(.btn) { text-decoration: underline; } abbr[title]::after { content: " (" attr(title) ")"; } pre { white-space: pre-wrap !important; } pre, blockquote { border: 1px solid #adb5bd; page-break-inside: avoid; } thead { display: table-header-group; } tr, img { page-break-inside: avoid; } p, h2, h3 { orphans: 3; widows: 3; } h2, h3 { page-break-after: avoid; } @page { size: a3; } body { min-width: 992px !important; } .container { min-width: 992px !important; } .navbar { display: none; } .badge { border: 1px solid #000; } .table { border-collapse: collapse !important; } .table td, .table th { background-color: #fff !important; } .table-bordered th, .table-bordered td { border: 1px solid #dee2e6 !important; } .table-dark { color: inherit; } .table-dark th, .table-dark td, .table-dark thead th, .table-dark tbody + tbody { border-color: #dee2e6; } .table .thead-dark th { color: inherit; border-color: #dee2e6; } } /*# sourceMappingURL=bootstrap.css.map */glewlwyd-2.6.1/webapp-src/css/bootstrap.css.map000066400000000000000000017405101415646314000215300ustar00rootroot00000000000000{"version":3,"sources":["../../scss/bootstrap.scss","bootstrap.css","../../scss/_root.scss","../../scss/_reboot.scss","../../scss/_variables.scss","../../scss/vendor/_rfs.scss","../../scss/mixins/_hover.scss","../../scss/_type.scss","../../scss/mixins/_lists.scss","../../scss/_images.scss","../../scss/mixins/_image.scss","../../scss/mixins/_border-radius.scss","../../scss/_code.scss","../../scss/_grid.scss","../../scss/mixins/_grid.scss","../../scss/mixins/_breakpoints.scss","../../scss/mixins/_grid-framework.scss","../../scss/_tables.scss","../../scss/mixins/_table-row.scss","../../scss/_functions.scss","../../scss/_forms.scss","../../scss/mixins/_transition.scss","../../scss/mixins/_forms.scss","../../scss/mixins/_gradients.scss","../../scss/_buttons.scss","../../scss/mixins/_buttons.scss","../../scss/_transitions.scss","../../scss/_dropdown.scss","../../scss/mixins/_caret.scss","../../scss/mixins/_nav-divider.scss","../../scss/_button-group.scss","../../scss/_input-group.scss","../../scss/_custom-forms.scss","../../scss/_nav.scss","../../scss/_navbar.scss","../../scss/_card.scss","../../scss/_breadcrumb.scss","../../scss/_pagination.scss","../../scss/mixins/_pagination.scss","../../scss/_badge.scss","../../scss/mixins/_badge.scss","../../scss/_jumbotron.scss","../../scss/_alert.scss","../../scss/mixins/_alert.scss","../../scss/_progress.scss","../../scss/_media.scss","../../scss/_list-group.scss","../../scss/mixins/_list-group.scss","../../scss/_close.scss","../../scss/_toasts.scss","../../scss/_modal.scss","../../scss/_tooltip.scss","../../scss/mixins/_reset-text.scss","../../scss/_popover.scss","../../scss/_carousel.scss","../../scss/mixins/_clearfix.scss","../../scss/_spinners.scss","../../scss/utilities/_align.scss","../../scss/mixins/_background-variant.scss","../../scss/utilities/_background.scss","../../scss/utilities/_borders.scss","../../scss/utilities/_display.scss","../../scss/utilities/_embed.scss","../../scss/utilities/_flex.scss","../../scss/utilities/_float.scss","../../scss/utilities/_interactions.scss","../../scss/utilities/_overflow.scss","../../scss/utilities/_position.scss","../../scss/utilities/_screenreaders.scss","../../scss/mixins/_screen-reader.scss","../../scss/utilities/_shadows.scss","../../scss/utilities/_sizing.scss","../../scss/utilities/_spacing.scss","../../scss/utilities/_stretched-link.scss","../../scss/utilities/_text.scss","../../scss/mixins/_text-truncate.scss","../../scss/mixins/_text-emphasis.scss","../../scss/mixins/_text-hide.scss","../../scss/utilities/_visibility.scss","../../scss/_print.scss"],"names":[],"mappings":"AAAA;;;;;ECKE;ACJF;EAGI,eAAc;EAAd,iBAAc;EAAd,iBAAc;EAAd,eAAc;EAAd,cAAc;EAAd,iBAAc;EAAd,iBAAc;EAAd,gBAAc;EAAd,eAAc;EAAd,eAAc;EAAd,aAAc;EAAd,eAAc;EAAd,oBAAc;EAId,kBAAc;EAAd,oBAAc;EAAd,kBAAc;EAAd,eAAc;EAAd,kBAAc;EAAd,iBAAc;EAAd,gBAAc;EAAd,eAAc;EAId,kBAAiC;EAAjC,sBAAiC;EAAjC,sBAAiC;EAAjC,sBAAiC;EAAjC,uBAAiC;EAKnC,+MAAyB;EACzB,6GAAwB;ADiB1B;;AEjBA;;;EAGE,sBAAsB;AFoBxB;;AEjBA;EACE,uBAAuB;EACvB,iBAAiB;EACjB,8BAA8B;EAC9B,6CCXa;AH+Bf;;AEdA;EACE,cAAc;AFiBhB;;AEPA;EACE,SAAS;EACT,kMC2OiN;EC3J7M,eAtCY;EFxChB,gBCoP+B;EDnP/B,gBCwP+B;EDvP/B,cCnCgB;EDoChB,gBAAgB;EAChB,sBC9Ca;AHwDf;;AAEA;EECE,qBAAqB;AFCvB;;AEQA;EACE,uBAAuB;EACvB,SAAS;EACT,iBAAiB;AFLnB;;AEkBA;EACE,aAAa;EACb,qBCsNuC;AHrOzC;;AEsBA;EACE,aAAa;EACb,mBCyF8B;AH5GhC;;AE8BA;;EAEE,0BAA0B;EAC1B,yCAAiC;EAAjC,iCAAiC;EACjC,YAAY;EACZ,gBAAgB;EAChB,sCAA8B;EAA9B,8BAA8B;AF3BhC;;AE8BA;EACE,mBAAmB;EACnB,kBAAkB;EAClB,oBAAoB;AF3BtB;;AE8BA;;;EAGE,aAAa;EACb,mBAAmB;AF3BrB;;AE8BA;;;;EAIE,gBAAgB;AF3BlB;;AE8BA;EACE,gBCuJ+B;AHlLjC;;AE8BA;EACE,oBAAoB;EACpB,cAAc;AF3BhB;;AE8BA;EACE,gBAAgB;AF3BlB;;AE8BA;;EAEE,mBC0IkC;AHrKpC;;AE8BA;EExFI,cAAW;AJ8Df;;AEmCA;;EAEE,kBAAkB;EEnGhB,cAAW;EFqGb,cAAc;EACd,wBAAwB;AFhC1B;;AEmCA;EAAM,cAAc;AF/BpB;;AEgCA;EAAM,UAAU;AF5BhB;;AEmCA;EACE,cCtJe;EDuJf,qBCN4C;EDO5C,6BAA6B;AFhC/B;;AKhJE;EHmLE,cCT8D;EDU9D,0BCT+C;AHtBnD;;AEwCA;EACE,cAAc;EACd,qBAAqB;AFrCvB;;AK1JE;EHkME,cAAc;EACd,qBAAqB;AFpCzB;;AE6CA;;;;EAIE,iGC+DgH;ECnN9G,cAAW;AJ2Gf;;AE6CA;EAEE,aAAa;EAEb,mBAAmB;EAEnB,cAAc;EAGd,6BAA6B;AF/C/B;;AEuDA;EAEE,gBAAgB;AFrDlB;;AE6DA;EACE,sBAAsB;EACtB,kBAAkB;AF1DpB;;AE6DA;EAGE,gBAAgB;EAChB,sBAAsB;AF5DxB;;AEoEA;EACE,yBAAyB;AFjE3B;;AEoEA;EACE,oBCmFkC;EDlFlC,uBCkFkC;EDjFlC,cCtQgB;EDuQhB,gBAAgB;EAChB,oBAAoB;AFjEtB;;AEoEA;EAGE,mBAAmB;AFnErB;;AE2EA;EAEE,qBAAqB;EACrB,qBCoK2C;AH7O7C;;AE+EA;EAEE,gBAAgB;AF7ElB;;AEoFA;EACE,mBAAmB;EACnB,0CAA0C;AFjF5C;;AEoFA;;;;;EAKE,SAAS;EACT,oBAAoB;EExPlB,kBAAW;EF0Pb,oBAAoB;AFjFtB;;AEoFA;;EAEE,iBAAiB;AFjFnB;;AEoFA;;EAEE,oBAAoB;AFjFtB;;AAEA;EEsFE,eAAe;AFpFjB;;AE0FA;EACE,iBAAiB;AFvFnB;;AE8FA;;;;EAIE,0BAA0B;AF3F5B;;AEgGE;;;;EAKI,eAAe;AF9FrB;;AEoGA;;;;EAIE,UAAU;EACV,kBAAkB;AFjGpB;;AEoGA;;EAEE,sBAAsB;EACtB,UAAU;AFjGZ;;AEqGA;EACE,cAAc;EAEd,gBAAgB;AFnGlB;;AEsGA;EAME,YAAY;EAEZ,UAAU;EACV,SAAS;EACT,SAAS;AFzGX;;AE8GA;EACE,cAAc;EACd,WAAW;EACX,eAAe;EACf,UAAU;EACV,oBAAoB;EE/RhB,iBAtCY;EFuUhB,oBAAoB;EACpB,cAAc;EACd,mBAAmB;AF3GrB;;AE8GA;EACE,wBAAwB;AF3G1B;;AAEA;;EE+GE,YAAY;AF5Gd;;AAEA;EEkHE,oBAAoB;EACpB,wBAAwB;AFhH1B;;AAEA;EEsHE,wBAAwB;AFpH1B;;AE4HA;EACE,aAAa;EACb,0BAA0B;AFzH5B;;AEgIA;EACE,qBAAqB;AF7HvB;;AEgIA;EACE,kBAAkB;EAClB,eAAe;AF7HjB;;AEgIA;EACE,aAAa;AF7Hf;;AAEA;EEiIE,wBAAwB;AF/H1B;;AMzVA;;EAEE,qBH2SuC;EGzSvC,gBH2S+B;EG1S/B,gBH2S+B;AHgDjC;;AMvVA;EFgHM,iBAtCY;AJiRlB;;AM1VA;EF+GM,eAtCY;AJqRlB;;AM7VA;EF8GM,kBAtCY;AJyRlB;;AMhWA;EF6GM,iBAtCY;AJ6RlB;;AMnWA;EF4GM,kBAtCY;AJiSlB;;AMtWA;EF2GM,eAtCY;AJqSlB;;AMxWA;EFyGM,kBAtCY;EEjEhB,gBH6S+B;AH8DjC;;AMvWA;EFmGM,eAtCY;EE3DhB,gBHgS+B;EG/R/B,gBHuR+B;AHmFjC;;AMxWA;EF8FM,iBAtCY;EEtDhB,gBH4R+B;EG3R/B,gBHkR+B;AHyFjC;;AMzWA;EFyFM,iBAtCY;EEjDhB,gBHwR+B;EGvR/B,gBH6Q+B;AH+FjC;;AM1WA;EFoFM,iBAtCY;EE5ChB,gBHoR+B;EGnR/B,gBHwQ+B;AHqGjC;;AEhVA;EIpBE,gBHmFW;EGlFX,mBHkFW;EGjFX,SAAS;EACT,wCHzCa;AHiZf;;AMhWA;;EFMI,cAAW;EEHb,gBHgO+B;AHmIjC;;AMhWA;;EAEE,cHwQgC;EGvQhC,yBHgRmC;AHmFrC;;AM3VA;EC/EE,eAAe;EACf,gBAAgB;AP8alB;;AM3VA;ECpFE,eAAe;EACf,gBAAgB;APmblB;;AM7VA;EACE,qBAAqB;ANgWvB;;AMjWA;EAII,oBH0P+B;AHuGnC;;AMvVA;EFjCI,cAAW;EEmCb,yBAAyB;AN0V3B;;AMtVA;EACE,mBH0BW;ECXP,kBAtCY;AJiXlB;;AMtVA;EACE,cAAc;EF7CZ,cAAW;EE+Cb,cH1GgB;AHmclB;;AM5VA;EAMI,qBAAqB;AN0VzB;;AQ7cA;ECIE,eAAe;EAGf,YAAY;AT2cd;;AQ5cA;EACE,gBLogCwC;EKngCxC,sBLRa;EKSb,yBLNgB;EOQd,sBPkOgC;EMzOlC,eAAe;EAGf,YAAY;ATodd;;AQtcA;EAEE,qBAAqB;ARwcvB;;AQrcA;EACE,qBAA0B;EAC1B,cAAc;ARwchB;;AQrcA;EJkCI,cAAW;EIhCb,cL3BgB;AHmelB;;AW/eA;EPuEI,gBAAW;EOrEb,cRoCe;EQnCf,qBAAqB;AXkfvB;;AW/eE;EACE,cAAc;AXkflB;;AW7eA;EACE,sBRwlCuC;EC9hCrC,gBAAW;EOxDb,WRTa;EQUb,yBRDgB;EOEd,qBPoO+B;AH4QnC;;AWrfA;EASI,UAAU;EPkDV,eAAW;EOhDX,gBR8Q6B;AHkOjC;;AExSA;ESjME,cAAc;EPyCZ,gBAAW;EOvCb,cRjBgB;AH8flB;;AWhfA;EP0CI,kBAAW;EOlCX,cAAc;EACd,kBAAkB;AX6etB;;AWxeA;EACE,iBR+jCuC;EQ9jCvC,kBAAkB;AX2epB;;AYnhBE;ECDA,WAAW;EACX,mBAA0B;EAC1B,kBAAyB;EACzB,kBAAkB;EAClB,iBAAiB;AbwhBnB;;AcreI;EFtDF;ICWI,gBVuMK;EH8UT;AACF;;Ac3eI;EFtDF;ICWI,gBVwMK;EHmVT;AACF;;AcjfI;EFtDF;ICWI,gBVyMK;EHwVT;AACF;;AcvfI;EFtDF;ICWI,iBV0MM;EH6VV;AACF;;AY7iBE;ECPA,WAAW;EACX,mBAA0B;EAC1B,kBAAyB;EACzB,kBAAkB;EAClB,iBAAiB;AbwjBnB;;AcrgBI;EFrCE;IACE,gBTgMG;EH8WT;AACF;;Ac3gBI;EFrCE;IACE,gBTiMG;EHmXT;AACF;;AcjhBI;EFrCE;IACE,gBTkMG;EHwXT;AACF;;AcvhBI;EFrCE;IACE,iBTmMI;EH6XV;AACF;;AYriBE;EC7BA,oBAAa;EAAb,aAAa;EACb,mBAAe;EAAf,eAAe;EACf,mBAA0B;EAC1B,kBAAyB;AbskB3B;;AYtiBE;EACE,eAAe;EACf,cAAc;AZyiBlB;;AY3iBE;;EAMI,gBAAgB;EAChB,eAAe;AZ0iBrB;;AepmBE;;;;;;EACE,kBAAkB;EAClB,WAAW;EACX,mBAA0B;EAC1B,kBAAyB;Af4mB7B;;AetlBM;EACE,0BAAa;EAAb,aAAa;EACb,oBAAY;EAAZ,YAAY;EACZ,YAAY;EACZ,eAAe;AfylBvB;;AeplBU;EFuBN,kBAAuB;EAAvB,cAAuB;EACvB,eAAwB;AbikB5B;;AezlBU;EFuBN,iBAAuB;EAAvB,aAAuB;EACvB,cAAwB;AbskB5B;;Ae9lBU;EFuBN,wBAAuB;EAAvB,oBAAuB;EACvB,qBAAwB;Ab2kB5B;;AenmBU;EFuBN,iBAAuB;EAAvB,aAAuB;EACvB,cAAwB;AbglB5B;;AexmBU;EFuBN,iBAAuB;EAAvB,aAAuB;EACvB,cAAwB;AbqlB5B;;Ae7mBU;EFuBN,wBAAuB;EAAvB,oBAAuB;EACvB,qBAAwB;Ab0lB5B;;Ae5mBM;EFAJ,kBAAc;EAAd,cAAc;EACd,WAAW;EACX,eAAe;AbgnBjB;;Ae5mBU;EFdR,uBAAsC;EAAtC,mBAAsC;EAItC,oBAAuC;Ab2nBzC;;AejnBU;EFdR,wBAAsC;EAAtC,oBAAsC;EAItC,qBAAuC;AbgoBzC;;AetnBU;EFdR,iBAAsC;EAAtC,aAAsC;EAItC,cAAuC;AbqoBzC;;Ae3nBU;EFdR,wBAAsC;EAAtC,oBAAsC;EAItC,qBAAuC;Ab0oBzC;;AehoBU;EFdR,wBAAsC;EAAtC,oBAAsC;EAItC,qBAAuC;Ab+oBzC;;AeroBU;EFdR,iBAAsC;EAAtC,aAAsC;EAItC,cAAuC;AbopBzC;;Ae1oBU;EFdR,wBAAsC;EAAtC,oBAAsC;EAItC,qBAAuC;AbypBzC;;Ae/oBU;EFdR,wBAAsC;EAAtC,oBAAsC;EAItC,qBAAuC;Ab8pBzC;;AeppBU;EFdR,iBAAsC;EAAtC,aAAsC;EAItC,cAAuC;AbmqBzC;;AezpBU;EFdR,wBAAsC;EAAtC,oBAAsC;EAItC,qBAAuC;AbwqBzC;;Ae9pBU;EFdR,wBAAsC;EAAtC,oBAAsC;EAItC,qBAAuC;Ab6qBzC;;AenqBU;EFdR,kBAAsC;EAAtC,cAAsC;EAItC,eAAuC;AbkrBzC;;AelqBM;EAAwB,kBAAS;EAAT,SAAS;AfsqBvC;;AepqBM;EAAuB,kBZuKG;EYvKH,SZuKG;AHigBhC;;AerqBQ;EAAwB,iBADZ;EACY,QADZ;Af0qBpB;;AezqBQ;EAAwB,iBADZ;EACY,QADZ;Af8qBpB;;Ae7qBQ;EAAwB,iBADZ;EACY,QADZ;AfkrBpB;;AejrBQ;EAAwB,iBADZ;EACY,QADZ;AfsrBpB;;AerrBQ;EAAwB,iBADZ;EACY,QADZ;Af0rBpB;;AezrBQ;EAAwB,iBADZ;EACY,QADZ;Af8rBpB;;Ae7rBQ;EAAwB,iBADZ;EACY,QADZ;AfksBpB;;AejsBQ;EAAwB,iBADZ;EACY,QADZ;AfssBpB;;AersBQ;EAAwB,iBADZ;EACY,QADZ;Af0sBpB;;AezsBQ;EAAwB,iBADZ;EACY,QADZ;Af8sBpB;;Ae7sBQ;EAAwB,kBADZ;EACY,SADZ;AfktBpB;;AejtBQ;EAAwB,kBADZ;EACY,SADZ;AfstBpB;;AertBQ;EAAwB,kBADZ;EACY,SADZ;Af0tBpB;;AeltBY;EFjBV,sBAA8C;AbuuBhD;;AettBY;EFjBV,uBAA8C;Ab2uBhD;;Ae1tBY;EFjBV,gBAA8C;Ab+uBhD;;Ae9tBY;EFjBV,uBAA8C;AbmvBhD;;AeluBY;EFjBV,uBAA8C;AbuvBhD;;AetuBY;EFjBV,gBAA8C;Ab2vBhD;;Ae1uBY;EFjBV,uBAA8C;Ab+vBhD;;Ae9uBY;EFjBV,uBAA8C;AbmwBhD;;AelvBY;EFjBV,gBAA8C;AbuwBhD;;AetvBY;EFjBV,uBAA8C;Ab2wBhD;;Ae1vBY;EFjBV,uBAA8C;Ab+wBhD;;Ac1wBI;EC3BE;IACE,0BAAa;IAAb,aAAa;IACb,oBAAY;IAAZ,YAAY;IACZ,YAAY;IACZ,eAAe;EfyyBrB;EepyBQ;IFuBN,kBAAuB;IAAvB,cAAuB;IACvB,eAAwB;EbgxB1B;EexyBQ;IFuBN,iBAAuB;IAAvB,aAAuB;IACvB,cAAwB;EboxB1B;Ee5yBQ;IFuBN,wBAAuB;IAAvB,oBAAuB;IACvB,qBAAwB;EbwxB1B;EehzBQ;IFuBN,iBAAuB;IAAvB,aAAuB;IACvB,cAAwB;Eb4xB1B;EepzBQ;IFuBN,iBAAuB;IAAvB,aAAuB;IACvB,cAAwB;EbgyB1B;EexzBQ;IFuBN,wBAAuB;IAAvB,oBAAuB;IACvB,qBAAwB;EboyB1B;EetzBI;IFAJ,kBAAc;IAAd,cAAc;IACd,WAAW;IACX,eAAe;EbyzBf;EerzBQ;IFdR,uBAAsC;IAAtC,mBAAsC;IAItC,oBAAuC;Ebm0BvC;EezzBQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;Ebu0BvC;Ee7zBQ;IFdR,iBAAsC;IAAtC,aAAsC;IAItC,cAAuC;Eb20BvC;Eej0BQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;Eb+0BvC;Eer0BQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;Ebm1BvC;Eez0BQ;IFdR,iBAAsC;IAAtC,aAAsC;IAItC,cAAuC;Ebu1BvC;Ee70BQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;Eb21BvC;Eej1BQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;Eb+1BvC;Eer1BQ;IFdR,iBAAsC;IAAtC,aAAsC;IAItC,cAAuC;Ebm2BvC;Eez1BQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;Ebu2BvC;Ee71BQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;Eb22BvC;Eej2BQ;IFdR,kBAAsC;IAAtC,cAAsC;IAItC,eAAuC;Eb+2BvC;Ee/1BI;IAAwB,kBAAS;IAAT,SAAS;Efk2BrC;Eeh2BI;IAAuB,kBZuKG;IYvKH,SZuKG;EH4rB9B;Eeh2BM;IAAwB,iBADZ;IACY,QADZ;Efo2BlB;Een2BM;IAAwB,iBADZ;IACY,QADZ;Efu2BlB;Eet2BM;IAAwB,iBADZ;IACY,QADZ;Ef02BlB;Eez2BM;IAAwB,iBADZ;IACY,QADZ;Ef62BlB;Ee52BM;IAAwB,iBADZ;IACY,QADZ;Efg3BlB;Ee/2BM;IAAwB,iBADZ;IACY,QADZ;Efm3BlB;Eel3BM;IAAwB,iBADZ;IACY,QADZ;Efs3BlB;Eer3BM;IAAwB,iBADZ;IACY,QADZ;Efy3BlB;Eex3BM;IAAwB,iBADZ;IACY,QADZ;Ef43BlB;Ee33BM;IAAwB,iBADZ;IACY,QADZ;Ef+3BlB;Ee93BM;IAAwB,kBADZ;IACY,SADZ;Efk4BlB;Eej4BM;IAAwB,kBADZ;IACY,SADZ;Efq4BlB;Eep4BM;IAAwB,kBADZ;IACY,SADZ;Efw4BlB;Eeh4BU;IFjBV,cAA4B;Ebo5B5B;Een4BU;IFjBV,sBAA8C;Ebu5B9C;Eet4BU;IFjBV,uBAA8C;Eb05B9C;Eez4BU;IFjBV,gBAA8C;Eb65B9C;Ee54BU;IFjBV,uBAA8C;Ebg6B9C;Ee/4BU;IFjBV,uBAA8C;Ebm6B9C;Eel5BU;IFjBV,gBAA8C;Ebs6B9C;Eer5BU;IFjBV,uBAA8C;Eby6B9C;Eex5BU;IFjBV,uBAA8C;Eb46B9C;Ee35BU;IFjBV,gBAA8C;Eb+6B9C;Ee95BU;IFjBV,uBAA8C;Ebk7B9C;Eej6BU;IFjBV,uBAA8C;Ebq7B9C;AACF;;Acj7BI;EC3BE;IACE,0BAAa;IAAb,aAAa;IACb,oBAAY;IAAZ,YAAY;IACZ,YAAY;IACZ,eAAe;Efg9BrB;Ee38BQ;IFuBN,kBAAuB;IAAvB,cAAuB;IACvB,eAAwB;Ebu7B1B;Ee/8BQ;IFuBN,iBAAuB;IAAvB,aAAuB;IACvB,cAAwB;Eb27B1B;Een9BQ;IFuBN,wBAAuB;IAAvB,oBAAuB;IACvB,qBAAwB;Eb+7B1B;Eev9BQ;IFuBN,iBAAuB;IAAvB,aAAuB;IACvB,cAAwB;Ebm8B1B;Ee39BQ;IFuBN,iBAAuB;IAAvB,aAAuB;IACvB,cAAwB;Ebu8B1B;Ee/9BQ;IFuBN,wBAAuB;IAAvB,oBAAuB;IACvB,qBAAwB;Eb28B1B;Ee79BI;IFAJ,kBAAc;IAAd,cAAc;IACd,WAAW;IACX,eAAe;Ebg+Bf;Ee59BQ;IFdR,uBAAsC;IAAtC,mBAAsC;IAItC,oBAAuC;Eb0+BvC;Eeh+BQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;Eb8+BvC;Eep+BQ;IFdR,iBAAsC;IAAtC,aAAsC;IAItC,cAAuC;Ebk/BvC;Eex+BQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;Ebs/BvC;Ee5+BQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;Eb0/BvC;Eeh/BQ;IFdR,iBAAsC;IAAtC,aAAsC;IAItC,cAAuC;Eb8/BvC;Eep/BQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;EbkgCvC;Eex/BQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;EbsgCvC;Ee5/BQ;IFdR,iBAAsC;IAAtC,aAAsC;IAItC,cAAuC;Eb0gCvC;EehgCQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;Eb8gCvC;EepgCQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;EbkhCvC;EexgCQ;IFdR,kBAAsC;IAAtC,cAAsC;IAItC,eAAuC;EbshCvC;EetgCI;IAAwB,kBAAS;IAAT,SAAS;EfygCrC;EevgCI;IAAuB,kBZuKG;IYvKH,SZuKG;EHm2B9B;EevgCM;IAAwB,iBADZ;IACY,QADZ;Ef2gClB;Ee1gCM;IAAwB,iBADZ;IACY,QADZ;Ef8gClB;Ee7gCM;IAAwB,iBADZ;IACY,QADZ;EfihClB;EehhCM;IAAwB,iBADZ;IACY,QADZ;EfohClB;EenhCM;IAAwB,iBADZ;IACY,QADZ;EfuhClB;EethCM;IAAwB,iBADZ;IACY,QADZ;Ef0hClB;EezhCM;IAAwB,iBADZ;IACY,QADZ;Ef6hClB;Ee5hCM;IAAwB,iBADZ;IACY,QADZ;EfgiClB;Ee/hCM;IAAwB,iBADZ;IACY,QADZ;EfmiClB;EeliCM;IAAwB,iBADZ;IACY,QADZ;EfsiClB;EeriCM;IAAwB,kBADZ;IACY,SADZ;EfyiClB;EexiCM;IAAwB,kBADZ;IACY,SADZ;Ef4iClB;Ee3iCM;IAAwB,kBADZ;IACY,SADZ;Ef+iClB;EeviCU;IFjBV,cAA4B;Eb2jC5B;Ee1iCU;IFjBV,sBAA8C;Eb8jC9C;Ee7iCU;IFjBV,uBAA8C;EbikC9C;EehjCU;IFjBV,gBAA8C;EbokC9C;EenjCU;IFjBV,uBAA8C;EbukC9C;EetjCU;IFjBV,uBAA8C;Eb0kC9C;EezjCU;IFjBV,gBAA8C;Eb6kC9C;Ee5jCU;IFjBV,uBAA8C;EbglC9C;Ee/jCU;IFjBV,uBAA8C;EbmlC9C;EelkCU;IFjBV,gBAA8C;EbslC9C;EerkCU;IFjBV,uBAA8C;EbylC9C;EexkCU;IFjBV,uBAA8C;Eb4lC9C;AACF;;AcxlCI;EC3BE;IACE,0BAAa;IAAb,aAAa;IACb,oBAAY;IAAZ,YAAY;IACZ,YAAY;IACZ,eAAe;EfunCrB;EelnCQ;IFuBN,kBAAuB;IAAvB,cAAuB;IACvB,eAAwB;Eb8lC1B;EetnCQ;IFuBN,iBAAuB;IAAvB,aAAuB;IACvB,cAAwB;EbkmC1B;Ee1nCQ;IFuBN,wBAAuB;IAAvB,oBAAuB;IACvB,qBAAwB;EbsmC1B;Ee9nCQ;IFuBN,iBAAuB;IAAvB,aAAuB;IACvB,cAAwB;Eb0mC1B;EeloCQ;IFuBN,iBAAuB;IAAvB,aAAuB;IACvB,cAAwB;Eb8mC1B;EetoCQ;IFuBN,wBAAuB;IAAvB,oBAAuB;IACvB,qBAAwB;EbknC1B;EepoCI;IFAJ,kBAAc;IAAd,cAAc;IACd,WAAW;IACX,eAAe;EbuoCf;EenoCQ;IFdR,uBAAsC;IAAtC,mBAAsC;IAItC,oBAAuC;EbipCvC;EevoCQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;EbqpCvC;Ee3oCQ;IFdR,iBAAsC;IAAtC,aAAsC;IAItC,cAAuC;EbypCvC;Ee/oCQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;Eb6pCvC;EenpCQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;EbiqCvC;EevpCQ;IFdR,iBAAsC;IAAtC,aAAsC;IAItC,cAAuC;EbqqCvC;Ee3pCQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;EbyqCvC;Ee/pCQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;Eb6qCvC;EenqCQ;IFdR,iBAAsC;IAAtC,aAAsC;IAItC,cAAuC;EbirCvC;EevqCQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;EbqrCvC;Ee3qCQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;EbyrCvC;Ee/qCQ;IFdR,kBAAsC;IAAtC,cAAsC;IAItC,eAAuC;Eb6rCvC;Ee7qCI;IAAwB,kBAAS;IAAT,SAAS;EfgrCrC;Ee9qCI;IAAuB,kBZuKG;IYvKH,SZuKG;EH0gC9B;Ee9qCM;IAAwB,iBADZ;IACY,QADZ;EfkrClB;EejrCM;IAAwB,iBADZ;IACY,QADZ;EfqrClB;EeprCM;IAAwB,iBADZ;IACY,QADZ;EfwrClB;EevrCM;IAAwB,iBADZ;IACY,QADZ;Ef2rClB;Ee1rCM;IAAwB,iBADZ;IACY,QADZ;Ef8rClB;Ee7rCM;IAAwB,iBADZ;IACY,QADZ;EfisClB;EehsCM;IAAwB,iBADZ;IACY,QADZ;EfosClB;EensCM;IAAwB,iBADZ;IACY,QADZ;EfusClB;EetsCM;IAAwB,iBADZ;IACY,QADZ;Ef0sClB;EezsCM;IAAwB,iBADZ;IACY,QADZ;Ef6sClB;Ee5sCM;IAAwB,kBADZ;IACY,SADZ;EfgtClB;Ee/sCM;IAAwB,kBADZ;IACY,SADZ;EfmtClB;EeltCM;IAAwB,kBADZ;IACY,SADZ;EfstClB;Ee9sCU;IFjBV,cAA4B;EbkuC5B;EejtCU;IFjBV,sBAA8C;EbquC9C;EeptCU;IFjBV,uBAA8C;EbwuC9C;EevtCU;IFjBV,gBAA8C;Eb2uC9C;Ee1tCU;IFjBV,uBAA8C;Eb8uC9C;Ee7tCU;IFjBV,uBAA8C;EbivC9C;EehuCU;IFjBV,gBAA8C;EbovC9C;EenuCU;IFjBV,uBAA8C;EbuvC9C;EetuCU;IFjBV,uBAA8C;Eb0vC9C;EezuCU;IFjBV,gBAA8C;Eb6vC9C;Ee5uCU;IFjBV,uBAA8C;EbgwC9C;Ee/uCU;IFjBV,uBAA8C;EbmwC9C;AACF;;Ac/vCI;EC3BE;IACE,0BAAa;IAAb,aAAa;IACb,oBAAY;IAAZ,YAAY;IACZ,YAAY;IACZ,eAAe;Ef8xCrB;EezxCQ;IFuBN,kBAAuB;IAAvB,cAAuB;IACvB,eAAwB;EbqwC1B;Ee7xCQ;IFuBN,iBAAuB;IAAvB,aAAuB;IACvB,cAAwB;EbywC1B;EejyCQ;IFuBN,wBAAuB;IAAvB,oBAAuB;IACvB,qBAAwB;Eb6wC1B;EeryCQ;IFuBN,iBAAuB;IAAvB,aAAuB;IACvB,cAAwB;EbixC1B;EezyCQ;IFuBN,iBAAuB;IAAvB,aAAuB;IACvB,cAAwB;EbqxC1B;Ee7yCQ;IFuBN,wBAAuB;IAAvB,oBAAuB;IACvB,qBAAwB;EbyxC1B;Ee3yCI;IFAJ,kBAAc;IAAd,cAAc;IACd,WAAW;IACX,eAAe;Eb8yCf;Ee1yCQ;IFdR,uBAAsC;IAAtC,mBAAsC;IAItC,oBAAuC;EbwzCvC;Ee9yCQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;Eb4zCvC;EelzCQ;IFdR,iBAAsC;IAAtC,aAAsC;IAItC,cAAuC;Ebg0CvC;EetzCQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;Ebo0CvC;Ee1zCQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;Ebw0CvC;Ee9zCQ;IFdR,iBAAsC;IAAtC,aAAsC;IAItC,cAAuC;Eb40CvC;Eel0CQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;Ebg1CvC;Eet0CQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;Ebo1CvC;Ee10CQ;IFdR,iBAAsC;IAAtC,aAAsC;IAItC,cAAuC;Ebw1CvC;Ee90CQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;Eb41CvC;Eel1CQ;IFdR,wBAAsC;IAAtC,oBAAsC;IAItC,qBAAuC;Ebg2CvC;Eet1CQ;IFdR,kBAAsC;IAAtC,cAAsC;IAItC,eAAuC;Ebo2CvC;Eep1CI;IAAwB,kBAAS;IAAT,SAAS;Efu1CrC;Eer1CI;IAAuB,kBZuKG;IYvKH,SZuKG;EHirC9B;Eer1CM;IAAwB,iBADZ;IACY,QADZ;Efy1ClB;Eex1CM;IAAwB,iBADZ;IACY,QADZ;Ef41ClB;Ee31CM;IAAwB,iBADZ;IACY,QADZ;Ef+1ClB;Ee91CM;IAAwB,iBADZ;IACY,QADZ;Efk2ClB;Eej2CM;IAAwB,iBADZ;IACY,QADZ;Efq2ClB;Eep2CM;IAAwB,iBADZ;IACY,QADZ;Efw2ClB;Eev2CM;IAAwB,iBADZ;IACY,QADZ;Ef22ClB;Ee12CM;IAAwB,iBADZ;IACY,QADZ;Ef82ClB;Ee72CM;IAAwB,iBADZ;IACY,QADZ;Efi3ClB;Eeh3CM;IAAwB,iBADZ;IACY,QADZ;Efo3ClB;Een3CM;IAAwB,kBADZ;IACY,SADZ;Efu3ClB;Eet3CM;IAAwB,kBADZ;IACY,SADZ;Ef03ClB;Eez3CM;IAAwB,kBADZ;IACY,SADZ;Ef63ClB;Eer3CU;IFjBV,cAA4B;Eby4C5B;Eex3CU;IFjBV,sBAA8C;Eb44C9C;Ee33CU;IFjBV,uBAA8C;Eb+4C9C;Ee93CU;IFjBV,gBAA8C;Ebk5C9C;Eej4CU;IFjBV,uBAA8C;Ebq5C9C;Eep4CU;IFjBV,uBAA8C;Ebw5C9C;Eev4CU;IFjBV,gBAA8C;Eb25C9C;Ee14CU;IFjBV,uBAA8C;Eb85C9C;Ee74CU;IFjBV,uBAA8C;Ebi6C9C;Eeh5CU;IFjBV,gBAA8C;Ebo6C9C;Een5CU;IFjBV,uBAA8C;Ebu6C9C;Eet5CU;IFjBV,uBAA8C;Eb06C9C;AACF;;AgB99CA;EACE,WAAW;EACX,mBboIW;EanIX,cbSgB;AHw9ClB;;AgBp+CA;;EAQI,gBbwVgC;EavVhC,mBAAmB;EACnB,6BbJc;AHq+ClB;;AgB3+CA;EAcI,sBAAsB;EACtB,gCbTc;AH0+ClB;;AgBh/CA;EAmBI,6Bbbc;AH8+ClB;;AgBx9CA;;EAGI,ebkU+B;AHwpCnC;;AgBj9CA;EACE,yBbnCgB;AHu/ClB;;AgBr9CA;;EAKI,yBbvCc;AH4/ClB;;AgB19CA;;EAWM,wBAA4C;AhBo9ClD;;AgB/8CA;;;;EAKI,SAAS;AhBi9Cb;;AgBz8CA;EAEI,qCb1DW;AHqgDf;;AK1gDE;EW2EI,cbvEY;EawEZ,sCbvES;AH0gDf;;AiBthDE;;;EAII,yBC6F4D;AlB27ClE;;AiB5hDE;;;;EAYM,qBCqF0D;AlBk8ClE;;AK5hDE;EYiBM,yBAJsC;AjBmhD9C;;AiBphDE;;EASQ,yBARoC;AjBwhD9C;;AiB5iDE;;;EAII,yBC6F4D;AlBi9ClE;;AiBljDE;;;;EAYM,qBCqF0D;AlBw9ClE;;AKljDE;EYiBM,yBAJsC;AjByiD9C;;AiB1iDE;;EASQ,yBARoC;AjB8iD9C;;AiBlkDE;;;EAII,yBC6F4D;AlBu+ClE;;AiBxkDE;;;;EAYM,qBCqF0D;AlB8+ClE;;AKxkDE;EYiBM,yBAJsC;AjB+jD9C;;AiBhkDE;;EASQ,yBARoC;AjBokD9C;;AiBxlDE;;;EAII,yBC6F4D;AlB6/ClE;;AiB9lDE;;;;EAYM,qBCqF0D;AlBogDlE;;AK9lDE;EYiBM,yBAJsC;AjBqlD9C;;AiBtlDE;;EASQ,yBARoC;AjB0lD9C;;AiB9mDE;;;EAII,yBC6F4D;AlBmhDlE;;AiBpnDE;;;;EAYM,qBCqF0D;AlB0hDlE;;AKpnDE;EYiBM,yBAJsC;AjB2mD9C;;AiB5mDE;;EASQ,yBARoC;AjBgnD9C;;AiBpoDE;;;EAII,yBC6F4D;AlByiDlE;;AiB1oDE;;;;EAYM,qBCqF0D;AlBgjDlE;;AK1oDE;EYiBM,yBAJsC;AjBioD9C;;AiBloDE;;EASQ,yBARoC;AjBsoD9C;;AiB1pDE;;;EAII,yBC6F4D;AlB+jDlE;;AiBhqDE;;;;EAYM,qBCqF0D;AlBskDlE;;AKhqDE;EYiBM,yBAJsC;AjBupD9C;;AiBxpDE;;EASQ,yBARoC;AjB4pD9C;;AiBhrDE;;;EAII,yBC6F4D;AlBqlDlE;;AiBtrDE;;;;EAYM,qBCqF0D;AlB4lDlE;;AKtrDE;EYiBM,yBAJsC;AjB6qD9C;;AiB9qDE;;EASQ,yBARoC;AjBkrD9C;;AiBtsDE;;;EAII,sCdQS;AHgsDf;;AKrsDE;EYiBM,sCAJsC;AjB4rD9C;;AiB7rDE;;EASQ,sCARoC;AjBisD9C;;AgB3mDA;EAGM,Wb3GS;Ea4GT,yBbpGY;EaqGZ,qBbqQqD;AHu2C3D;;AgBjnDA;EAWM,cb5GY;Ea6GZ,yBblHY;EamHZ,qBblHY;AH4tDlB;;AgBrmDA;EACE,Wb3Ha;Ea4Hb,yBbpHgB;AH4tDlB;;AgB1mDA;;;EAOI,qBbiPuD;AHw3C3D;;AgBhnDA;EAWI,SAAS;AhBymDb;;AgBpnDA;EAgBM,2Cb1IS;AHkvDf;;AK7uDE;EW4IM,WbjJO;EakJP,4CblJO;AHuvDf;;AcrrDI;EEiGA;IAEI,cAAc;IACd,WAAW;IACX,gBAAgB;IAChB,iCAAiC;EhBulDvC;EgB5lDG;IASK,SAAS;EhBslDjB;AACF;;AcjsDI;EEiGA;IAEI,cAAc;IACd,WAAW;IACX,gBAAgB;IAChB,iCAAiC;EhBmmDvC;EgBxmDG;IASK,SAAS;EhBkmDjB;AACF;;Ac7sDI;EEiGA;IAEI,cAAc;IACd,WAAW;IACX,gBAAgB;IAChB,iCAAiC;EhB+mDvC;EgBpnDG;IASK,SAAS;EhB8mDjB;AACF;;AcztDI;EEiGA;IAEI,cAAc;IACd,WAAW;IACX,gBAAgB;IAChB,iCAAiC;EhB2nDvC;EgBhoDG;IASK,SAAS;EhB0nDjB;AACF;;AgBzoDA;EAOQ,cAAc;EACd,WAAW;EACX,gBAAgB;EAChB,iCAAiC;AhBsoDzC;;AgBhpDA;EAcU,SAAS;AhBsoDnB;;AmBnzDA;EACE,cAAc;EACd,WAAW;EACX,mCD8G8D;EC7G9D,yBhB8XkC;ECzQ9B,eAtCY;Ee5EhB,gBhBwR+B;EgBvR/B,gBhB4R+B;EgB3R/B,chBDgB;EgBEhB,sBhBTa;EgBUb,4BAA4B;EAC5B,yBhBPgB;EOOd,sBPkOgC;EiBpO9B,wEjB4e4F;AH60ClG;;AoBrzDM;EDdN;ICeQ,gBAAgB;EpByzDtB;AACF;;AmBz0DA;EAsBI,6BAA6B;EAC7B,SAAS;AnBuzDb;;AmB90DA;EA4BI,kBAAkB;EAClB,0BhBrBc;AH20DlB;;AqB50DE;EACE,clBAc;EkBCd,sBlBRW;EkBSX,qBlB0dsE;EkBzdtE,UAAU;EAKR,gDlBcW;AH6zDjB;;AmB31DA;EAqCI,chB9Bc;EgBgCd,UAAU;AnByzDd;;AmBh2DA;EAqCI,chB9Bc;EgBgCd,UAAU;AnByzDd;;AmBh2DA;EAqCI,chB9Bc;EgBgCd,UAAU;AnByzDd;;AmBh2DA;EAqCI,chB9Bc;EgBgCd,UAAU;AnByzDd;;AmBh2DA;EAqCI,chB9Bc;EgBgCd,UAAU;AnByzDd;;AmBh2DA;EAiDI,yBhB9Cc;EgBgDd,UAAU;AnBkzDd;;AmB9yDA;;;;EAKI,wBAAgB;EAAhB,qBAAgB;EAAhB,gBAAgB;AnBgzDpB;;AmB5yDA;EAOI,chB/Dc;EgBgEd,sBhBvEW;AHg3Df;;AmBpyDA;;EAEE,cAAc;EACd,WAAW;AnBuyDb;;AmB7xDA;EACE,iCDsB8D;ECrB9D,oCDqB8D;ECpB9D,gBAAgB;Ef3Bd,kBAAW;Ee6Bb,gBhBqM+B;AH2lDjC;;AmB7xDA;EACE,+BDc8D;ECb9D,kCDa8D;EdQ1D,kBAtCY;EemBhB,gBhBkI+B;AH8pDjC;;AmB7xDA;EACE,gCDO8D;ECN9D,mCDM8D;EdQ1D,mBAtCY;Ee0BhB,gBhB4H+B;AHoqDjC;;AmBvxDA;EACE,cAAc;EACd,WAAW;EACX,mBAA2B;EAC3B,gBAAgB;EfDZ,eAtCY;EeyChB,gBhBwK+B;EgBvK/B,chBnHgB;EgBoHhB,6BAA6B;EAC7B,yBAAyB;EACzB,mBAAmC;AnB0xDrC;;AmBpyDA;EAcI,gBAAgB;EAChB,eAAe;AnB0xDnB;;AmB9wDA;EACE,kCDjC8D;ECkC9D,uBhByPiC;ECnR7B,mBAtCY;EekEhB,gBhBoF+B;EO7N7B,qBPoO+B;AHurDnC;;AmB9wDA;EACE,gCDzC8D;EC0C9D,oBhBsPgC;ECxR5B,kBAtCY;Ee0EhB,gBhB2E+B;EO5N7B,qBPmO+B;AHgsDnC;;AmB7wDA;EAGI,YAAY;AnB8wDhB;;AmB1wDA;EACE,YAAY;AnB6wDd;;AmBrwDA;EACE,mBhB+U0C;AHy7C5C;;AmBrwDA;EACE,cAAc;EACd,mBhBgU4C;AHw8C9C;;AmBhwDA;EACE,oBAAa;EAAb,aAAa;EACb,mBAAe;EAAf,eAAe;EACf,kBAA0C;EAC1C,iBAAyC;AnBmwD3C;;AmBvwDA;;EAQI,kBAA0C;EAC1C,iBAAyC;AnBowD7C;;AmB3vDA;EACE,kBAAkB;EAClB,cAAc;EACd,qBhBqS6C;AHy9C/C;;AmB3vDA;EACE,kBAAkB;EAClB,kBhBiS2C;EgBhS3C,qBhB+R6C;AH+9C/C;;AmBjwDA;;EAQI,chBzNc;AHu9DlB;;AmB1vDA;EACE,gBAAgB;AnB6vDlB;;AmB1vDA;EACE,2BAAoB;EAApB,oBAAoB;EACpB,sBAAmB;EAAnB,mBAAmB;EACnB,eAAe;EACf,qBhBkR4C;AH2+C9C;;AmBjwDA;EAQI,gBAAgB;EAChB,aAAa;EACb,uBhB6Q4C;EgB5Q5C,cAAc;AnB6vDlB;;AqB18DE;EACE,aAAa;EACb,WAAW;EACX,mBlB+c0C;ECtb1C,cAAW;EiBvBX,clBNa;AHm9DjB;;AqB18DE;EACE,kBAAkB;EAClB,SAAS;EACT,UAAU;EACV,aAAa;EACb,eAAe;EACf,uBlBsyBqC;EkBryBrC,iBAAiB;EjBoEf,mBAtCY;EiB5Bd,gBlB6O6B;EkB5O7B,WlBvDW;EkBwDX,wClBpBa;EOzBb,sBPkOgC;AHyxDpC;;AqB7+DI;;;;EAsCE,cAAc;ArB88DpB;;AqBp/DI;EA4CE,qBlBjCW;EkBoCT,oCHwCwD;EGvCxD,iRHtB0E;EGuB1E,4BAA4B;EAC5B,2DAA6D;EAC7D,gEHoCwD;AlBs6DhE;;AqB7/DI;EAuDI,qBlB5CS;EkB6CT,gDlB7CS;AHu/DjB;;AqBlgEI;EAiEI,oCHsBwD;EGrBxD,kFHqBwD;AlBg7DhE;;AqBvgEI;EAyEE,qBlB9DW;EkBiET,uCHWwD;EGVxD,ujBAA8J;ArBg8DtK;;AqB7gEI;EAiFI,qBlBtES;EkBuET,gDlBvES;AHugEjB;;AqBlhEI;EA0FI,clB/ES;AH2gEjB;;AqBthEI;;;EA+FI,cAAc;ArB67DtB;;AqB5hEI;EAuGI,clB5FS;AHqhEjB;;AqBhiEI;EA0GM,qBlB/FO;AHyhEjB;;AqBpiEI;EAgHM,qBAAkC;EC1IxC,yBD2I+C;ArBw7DnD;;AqBziEI;EAuHM,gDlB5GO;AHkiEjB;;AqB7iEI;EA2HM,qBlBhHO;AHsiEjB;;AqBjjEI;EAqII,qBlB1HS;AH0iEjB;;AqBrjEI;EA0IM,qBlB/HO;EkBgIP,gDlBhIO;AH+iEjB;;AqB9iEE;EACE,aAAa;EACb,WAAW;EACX,mBlB+c0C;ECtb1C,cAAW;EiBvBX,clBTa;AH0jEjB;;AqB9iEE;EACE,kBAAkB;EAClB,SAAS;EACT,UAAU;EACV,aAAa;EACb,eAAe;EACf,uBlBsyBqC;EkBryBrC,iBAAiB;EjBoEf,mBAtCY;EiB5Bd,gBlB6O6B;EkB5O7B,WlBvDW;EkBwDX,wClBvBa;EOtBb,sBPkOgC;AH63DpC;;AqBjlEI;;;;EAsCE,cAAc;ArBkjEpB;;AqBxlEI;EA4CE,qBlBpCW;EkBuCT,oCHwCwD;EGvCxD,4UHtB0E;EGuB1E,4BAA4B;EAC5B,2DAA6D;EAC7D,gEHoCwD;AlB0gEhE;;AqBjmEI;EAuDI,qBlB/CS;EkBgDT,gDlBhDS;AH8lEjB;;AqBtmEI;EAiEI,oCHsBwD;EGrBxD,kFHqBwD;AlBohEhE;;AqB3mEI;EAyEE,qBlBjEW;EkBoET,uCHWwD;EGVxD,knBAA8J;ArBoiEtK;;AqBjnEI;EAiFI,qBlBzES;EkB0ET,gDlB1ES;AH8mEjB;;AqBtnEI;EA0FI,clBlFS;AHknEjB;;AqB1nEI;;;EA+FI,cAAc;ArBiiEtB;;AqBhoEI;EAuGI,clB/FS;AH4nEjB;;AqBpoEI;EA0GM,qBlBlGO;AHgoEjB;;AqBxoEI;EAgHM,qBAAkC;EC1IxC,yBD2I+C;ArB4hEnD;;AqB7oEI;EAuHM,gDlB/GO;AHyoEjB;;AqBjpEI;EA2HM,qBlBnHO;AH6oEjB;;AqBrpEI;EAqII,qBlB7HS;AHipEjB;;AqBzpEI;EA0IM,qBlBlIO;EkBmIP,gDlBnIO;AHspEjB;;AmB56DA;EACE,oBAAa;EAAb,aAAa;EACb,uBAAmB;EAAnB,mBAAmB;EACnB,sBAAmB;EAAnB,mBAAmB;AnB+6DrB;;AmBl7DA;EASI,WAAW;AnB66Df;;Ac5oEI;EKsNJ;IAeM,oBAAa;IAAb,aAAa;IACb,sBAAmB;IAAnB,mBAAmB;IACnB,qBAAuB;IAAvB,uBAAuB;IACvB,gBAAgB;EnB46DpB;EmB97DF;IAuBM,oBAAa;IAAb,aAAa;IACb,kBAAc;IAAd,cAAc;IACd,uBAAmB;IAAnB,mBAAmB;IACnB,sBAAmB;IAAnB,mBAAmB;IACnB,gBAAgB;EnB06DpB;EmBr8DF;IAgCM,qBAAqB;IACrB,WAAW;IACX,sBAAsB;EnBw6D1B;EmB18DF;IAuCM,qBAAqB;EnBs6DzB;EmB78DF;;IA4CM,WAAW;EnBq6Df;EmBj9DF;IAkDM,oBAAa;IAAb,aAAa;IACb,sBAAmB;IAAnB,mBAAmB;IACnB,qBAAuB;IAAvB,uBAAuB;IACvB,WAAW;IACX,eAAe;EnBk6DnB;EmBx9DF;IAyDM,kBAAkB;IAClB,oBAAc;IAAd,cAAc;IACd,aAAa;IACb,qBhBoLwC;IgBnLxC,cAAc;EnBk6DlB;EmB/9DF;IAiEM,sBAAmB;IAAnB,mBAAmB;IACnB,qBAAuB;IAAvB,uBAAuB;EnBi6D3B;EmBn+DF;IAqEM,gBAAgB;EnBi6DpB;AACF;;AuBnvEA;EACE,qBAAqB;EAErB,gBpB4R+B;EoB3R/B,cpBMgB;EoBLhB,kBAAkB;EAGlB,sBAAsB;EACtB,yBAAiB;EAAjB,sBAAiB;EAAjB,qBAAiB;EAAjB,iBAAiB;EACjB,6BAA6B;EAC7B,6BAA2C;ECuF3C,yBrBgSkC;ECzQ9B,eAtCY;EoBiBhB,gBrBgM+B;EOxR7B,sBPkOgC;EiBpO9B,qIjBqb6I;AHo0DnJ;;AoBrvEM;EGdN;IHeQ,gBAAgB;EpByvEtB;AACF;;AKnwEE;EkBUE,cpBNc;EoBOd,qBAAqB;AvB6vEzB;;AuB9wEA;EAsBI,UAAU;EACV,gDpBOa;AHqvEjB;;AuBnxEA;EA6BI,apBsZ6B;AHo2DjC;;AuBvxEA;EAkCI,eAAsD;AvByvE1D;;AuB3uEA;;EAEE,oBAAoB;AvB8uEtB;;AuBruEE;EC3DA,WrBCa;EmBDX,yBnB8Ba;EqB5Bf,qBrB4Be;AHwwEjB;;AKhyEE;EmBAE,WrBLW;EmBDX,yBEDoF;EASpF,qBATyH;AxB6yE7H;;AwBjyEE;EAEE,WrBZW;EmBDX,yBEDoF;EAgBpF,qBAhByH;EAqBvH,gDAAiF;AxB+xEvF;;AwB1xEE;EAEE,WrB1BW;EqB2BX,yBrBEa;EqBDb,qBrBCa;AH2xEjB;;AwBrxEE;;EAGE,WrBtCW;EqBuCX,yBAzCuK;EA6CvK,qBA7C+M;AxBi0EnN;;AwBlxEI;;EAKI,gDAAiF;AxBkxEzF;;AuB1wEE;EC3DA,WrBCa;EmBDX,yBnBOc;EqBLhB,qBrBKgB;AHo0ElB;;AKr0EE;EmBAE,WrBLW;EmBDX,yBEDoF;EASpF,qBATyH;AxBk1E7H;;AwBt0EE;EAEE,WrBZW;EmBDX,yBEDoF;EAgBpF,qBAhByH;EAqBvH,iDAAiF;AxBo0EvF;;AwB/zEE;EAEE,WrB1BW;EqB2BX,yBrBrBc;EqBsBd,qBrBtBc;AHu1ElB;;AwB1zEE;;EAGE,WrBtCW;EqBuCX,yBAzCuK;EA6CvK,qBA7C+M;AxBs2EnN;;AwBvzEI;;EAKI,iDAAiF;AxBuzEzF;;AuB/yEE;EC3DA,WrBCa;EmBDX,yBnBqCa;EqBnCf,qBrBmCe;AH20EjB;;AK12EE;EmBAE,WrBLW;EmBDX,yBEDoF;EASpF,qBATyH;AxBu3E7H;;AwB32EE;EAEE,WrBZW;EmBDX,yBEDoF;EAgBpF,qBAhByH;EAqBvH,+CAAiF;AxBy2EvF;;AwBp2EE;EAEE,WrB1BW;EqB2BX,yBrBSa;EqBRb,qBrBQa;AH81EjB;;AwB/1EE;;EAGE,WrBtCW;EqBuCX,yBAzCuK;EA6CvK,qBA7C+M;AxB24EnN;;AwB51EI;;EAKI,+CAAiF;AxB41EzF;;AuBp1EE;EC3DA,WrBCa;EmBDX,yBnBuCa;EqBrCf,qBrBqCe;AH82EjB;;AK/4EE;EmBAE,WrBLW;EmBDX,yBEDoF;EASpF,qBATyH;AxB45E7H;;AwBh5EE;EAEE,WrBZW;EmBDX,yBEDoF;EAgBpF,qBAhByH;EAqBvH,gDAAiF;AxB84EvF;;AwBz4EE;EAEE,WrB1BW;EqB2BX,yBrBWa;EqBVb,qBrBUa;AHi4EjB;;AwBp4EE;;EAGE,WrBtCW;EqBuCX,yBAzCuK;EA6CvK,qBA7C+M;AxBg7EnN;;AwBj4EI;;EAKI,gDAAiF;AxBi4EzF;;AuBz3EE;EC3DA,crBUgB;EmBVd,yBnBoCa;EqBlCf,qBrBkCe;AHs5EjB;;AKp7EE;EmBAE,crBIc;EmBVd,yBEDoF;EASpF,qBATyH;AxBi8E7H;;AwBr7EE;EAEE,crBHc;EmBVd,yBEDoF;EAgBpF,qBAhByH;EAqBvH,gDAAiF;AxBm7EvF;;AwB96EE;EAEE,crBjBc;EqBkBd,yBrBQa;EqBPb,qBrBOa;AHy6EjB;;AwBz6EE;;EAGE,crB7Bc;EqB8Bd,yBAzCuK;EA6CvK,qBA7C+M;AxBq9EnN;;AwBt6EI;;EAKI,gDAAiF;AxBs6EzF;;AuB95EE;EC3DA,WrBCa;EmBDX,yBnBkCa;EqBhCf,qBrBgCe;AH67EjB;;AKz9EE;EmBAE,WrBLW;EmBDX,yBEDoF;EASpF,qBATyH;AxBs+E7H;;AwB19EE;EAEE,WrBZW;EmBDX,yBEDoF;EAgBpF,qBAhByH;EAqBvH,+CAAiF;AxBw9EvF;;AwBn9EE;EAEE,WrB1BW;EqB2BX,yBrBMa;EqBLb,qBrBKa;AHg9EjB;;AwB98EE;;EAGE,WrBtCW;EqBuCX,yBAzCuK;EA6CvK,qBA7C+M;AxB0/EnN;;AwB38EI;;EAKI,+CAAiF;AxB28EzF;;AuBn8EE;EC3DA,crBUgB;EmBVd,yBnBEc;EqBAhB,qBrBAgB;AHkgFlB;;AK9/EE;EmBAE,crBIc;EmBVd,yBEDoF;EASpF,qBATyH;AxB2gF7H;;AwB//EE;EAEE,crBHc;EmBVd,yBEDoF;EAgBpF,qBAhByH;EAqBvH,iDAAiF;AxB6/EvF;;AwBx/EE;EAEE,crBjBc;EqBkBd,yBrB1Bc;EqB2Bd,qBrB3Bc;AHqhFlB;;AwBn/EE;;EAGE,crB7Bc;EqB8Bd,yBAzCuK;EA6CvK,qBA7C+M;AxB+hFnN;;AwBh/EI;;EAKI,iDAAiF;AxBg/EzF;;AuBx+EE;EC3DA,WrBCa;EmBDX,yBnBSc;EqBPhB,qBrBOgB;AHgiFlB;;AKniFE;EmBAE,WrBLW;EmBDX,yBEDoF;EASpF,qBATyH;AxBgjF7H;;AwBpiFE;EAEE,WrBZW;EmBDX,yBEDoF;EAgBpF,qBAhByH;EAqBvH,8CAAiF;AxBkiFvF;;AwB7hFE;EAEE,WrB1BW;EqB2BX,yBrBnBc;EqBoBd,qBrBpBc;AHmjFlB;;AwBxhFE;;EAGE,WrBtCW;EqBuCX,yBAzCuK;EA6CvK,qBA7C+M;AxBokFnN;;AwBrhFI;;EAKI,8CAAiF;AxBqhFzF;;AuBvgFE;ECPA,crB5Be;EqB6Bf,qBrB7Be;AH+iFjB;;AKvkFE;EmBwDE,WrB7DW;EqB8DX,yBrBjCa;EqBkCb,qBrBlCa;AHqjFjB;;AwBhhFE;EAEE,+CrBvCa;AHyjFjB;;AwB/gFE;EAEE,crB5Ca;EqB6Cb,6BAA6B;AxBihFjC;;AwB9gFE;;EAGE,WrBhFW;EqBiFX,yBrBpDa;EqBqDb,qBrBrDa;AHqkFjB;;AwB9gFI;;EAKI,+CrB5DS;AH0kFjB;;AuBviFE;ECPA,crBnDgB;EqBoDhB,qBrBpDgB;AHsmFlB;;AKvmFE;EmBwDE,WrB7DW;EqB8DX,yBrBxDc;EqByDd,qBrBzDc;AH4mFlB;;AwBhjFE;EAEE,iDrB9Dc;AHgnFlB;;AwB/iFE;EAEE,crBnEc;EqBoEd,6BAA6B;AxBijFjC;;AwB9iFE;;EAGE,WrBhFW;EqBiFX,yBrB3Ec;EqB4Ed,qBrB5Ec;AH4nFlB;;AwB9iFI;;EAKI,iDrBnFU;AHioFlB;;AuBvkFE;ECPA,crBrBe;EqBsBf,qBrBtBe;AHwmFjB;;AKvoFE;EmBwDE,WrB7DW;EqB8DX,yBrB1Ba;EqB2Bb,qBrB3Ba;AH8mFjB;;AwBhlFE;EAEE,+CrBhCa;AHknFjB;;AwB/kFE;EAEE,crBrCa;EqBsCb,6BAA6B;AxBilFjC;;AwB9kFE;;EAGE,WrBhFW;EqBiFX,yBrB7Ca;EqB8Cb,qBrB9Ca;AH8nFjB;;AwB9kFI;;EAKI,+CrBrDS;AHmoFjB;;AuBvmFE;ECPA,crBnBe;EqBoBf,qBrBpBe;AHsoFjB;;AKvqFE;EmBwDE,WrB7DW;EqB8DX,yBrBxBa;EqByBb,qBrBzBa;AH4oFjB;;AwBhnFE;EAEE,gDrB9Ba;AHgpFjB;;AwB/mFE;EAEE,crBnCa;EqBoCb,6BAA6B;AxBinFjC;;AwB9mFE;;EAGE,WrBhFW;EqBiFX,yBrB3Ca;EqB4Cb,qBrB5Ca;AH4pFjB;;AwB9mFI;;EAKI,gDrBnDS;AHiqFjB;;AuBvoFE;ECPA,crBtBe;EqBuBf,qBrBvBe;AHyqFjB;;AKvsFE;EmBwDE,crBpDc;EqBqDd,yBrB3Ba;EqB4Bb,qBrB5Ba;AH+qFjB;;AwBhpFE;EAEE,+CrBjCa;AHmrFjB;;AwB/oFE;EAEE,crBtCa;EqBuCb,6BAA6B;AxBipFjC;;AwB9oFE;;EAGE,crBvEc;EqBwEd,yBrB9Ca;EqB+Cb,qBrB/Ca;AH+rFjB;;AwB9oFI;;EAKI,+CrBtDS;AHosFjB;;AuBvqFE;ECPA,crBxBe;EqByBf,qBrBzBe;AH2sFjB;;AKvuFE;EmBwDE,WrB7DW;EqB8DX,yBrB7Ba;EqB8Bb,qBrB9Ba;AHitFjB;;AwBhrFE;EAEE,+CrBnCa;AHqtFjB;;AwB/qFE;EAEE,crBxCa;EqByCb,6BAA6B;AxBirFjC;;AwB9qFE;;EAGE,WrBhFW;EqBiFX,yBrBhDa;EqBiDb,qBrBjDa;AHiuFjB;;AwB9qFI;;EAKI,+CrBxDS;AHsuFjB;;AuBvsFE;ECPA,crBxDgB;EqByDhB,qBrBzDgB;AH2wFlB;;AKvwFE;EmBwDE,crBpDc;EqBqDd,yBrB7Dc;EqB8Dd,qBrB9Dc;AHixFlB;;AwBhtFE;EAEE,iDrBnEc;AHqxFlB;;AwB/sFE;EAEE,crBxEc;EqByEd,6BAA6B;AxBitFjC;;AwB9sFE;;EAGE,crBvEc;EqBwEd,yBrBhFc;EqBiFd,qBrBjFc;AHiyFlB;;AwB9sFI;;EAKI,iDrBxFU;AHsyFlB;;AuBvuFE;ECPA,crBjDgB;EqBkDhB,qBrBlDgB;AHoyFlB;;AKvyFE;EmBwDE,WrB7DW;EqB8DX,yBrBtDc;EqBuDd,qBrBvDc;AH0yFlB;;AwBhvFE;EAEE,8CrB5Dc;AH8yFlB;;AwB/uFE;EAEE,crBjEc;EqBkEd,6BAA6B;AxBivFjC;;AwB9uFE;;EAGE,WrBhFW;EqBiFX,yBrBzEc;EqB0Ed,qBrB1Ec;AH0zFlB;;AwB9uFI;;EAKI,8CrBjFU;AH+zFlB;;AuB5vFA;EACE,gBpBkN+B;EoBjN/B,cpBhDe;EoBiDf,qBpBgG4C;AH+pF9C;;AKx0FE;EkB4EE,cpB8F8D;EoB7F9D,0BpB8F+C;AHkqFnD;;AuBvwFA;EAYI,0BpByF+C;AHsqFnD;;AuB3wFA;EAiBI,cpBtFc;EoBuFd,oBAAoB;AvB8vFxB;;AuBnvFA;ECPE,oBrB+SgC;ECxR5B,kBAtCY;EoBiBhB,gBrBoI+B;EO5N7B,qBPmO+B;AHonFnC;;AuBtvFA;ECXE,uBrB0SiC;ECnR7B,mBAtCY;EoBiBhB,gBrBqI+B;EO7N7B,qBPoO+B;AH0nFnC;;AuBpvFA;EACE,cAAc;EACd,WAAW;AvBuvFb;;AuBzvFA;EAMI,kBpB4T+B;AH27EnC;;AuBlvFA;;;EAII,WAAW;AvBovFf;;AyB/3FA;ELgBM,gCjBsP2C;AH6nFjD;;AoB/2FM;EKpBN;ILqBQ,gBAAgB;EpBm3FtB;AACF;;AyBz4FA;EAII,UAAU;AzBy4Fd;;AyBr4FA;EAEI,aAAa;AzBu4FjB;;AyBn4FA;EACE,kBAAkB;EAClB,SAAS;EACT,gBAAgB;ELDZ,6BjBuPwC;AHipF9C;;AoBp4FM;EKNN;ILOQ,gBAAgB;EpBw4FtB;AACF;;A0B75FA;;;;EAIE,kBAAkB;A1Bg6FpB;;A0B75FA;EACE,mBAAmB;A1Bg6FrB;;A2B54FI;EACE,qBAAqB;EACrB,oBxBoO0C;EwBnO1C,uBxBkO0C;EwBjO1C,WAAW;EAhCf,uBAA8B;EAC9B,qCAA4C;EAC5C,gBAAgB;EAChB,oCAA2C;A3Bg7F7C;;A2B33FI;EACE,cAAc;A3B83FpB;;A0Bx6FA;EACE,kBAAkB;EAClB,SAAS;EACT,OAAO;EACP,avB8pBsC;EuB7pBtC,aAAa;EACb,WAAW;EACX,gBvBouBuC;EuBnuBvC,iBAA8B;EAC9B,oBAA4B;EtBsGxB,eAtCY;EsB9DhB,cvBXgB;EuBYhB,gBAAgB;EAChB,gBAAgB;EAChB,sBvBvBa;EuBwBb,4BAA4B;EAC5B,qCvBfa;EOCX,sBPkOgC;AHwtFpC;;A0Bn6FI;EACE,WAAW;EACX,OAAO;A1Bs6Fb;;A0Bn6FI;EACE,QAAQ;EACR,UAAU;A1Bs6FhB;;Ac15FI;EYnBA;IACE,WAAW;IACX,OAAO;E1Bi7FX;E0B96FE;IACE,QAAQ;IACR,UAAU;E1Bg7Fd;AACF;;Acr6FI;EYnBA;IACE,WAAW;IACX,OAAO;E1B47FX;E0Bz7FE;IACE,QAAQ;IACR,UAAU;E1B27Fd;AACF;;Ach7FI;EYnBA;IACE,WAAW;IACX,OAAO;E1Bu8FX;E0Bp8FE;IACE,QAAQ;IACR,UAAU;E1Bs8Fd;AACF;;Ac37FI;EYnBA;IACE,WAAW;IACX,OAAO;E1Bk9FX;E0B/8FE;IACE,QAAQ;IACR,UAAU;E1Bi9Fd;AACF;;A0B38FA;EAEI,SAAS;EACT,YAAY;EACZ,aAAa;EACb,uBvBisBuC;AH4wE3C;;A2B5+FI;EACE,qBAAqB;EACrB,oBxBoO0C;EwBnO1C,uBxBkO0C;EwBjO1C,WAAW;EAzBf,aAAa;EACb,qCAA4C;EAC5C,0BAAiC;EACjC,oCAA2C;A3BygG7C;;A2B39FI;EACE,cAAc;A3B89FpB;;A0Bp9FA;EAEI,MAAM;EACN,WAAW;EACX,UAAU;EACV,aAAa;EACb,qBvBmrBuC;AHmyE3C;;A2BngGI;EACE,qBAAqB;EACrB,oBxBoO0C;EwBnO1C,uBxBkO0C;EwBjO1C,WAAW;EAlBf,mCAA0C;EAC1C,eAAe;EACf,sCAA6C;EAC7C,wBAA+B;A3ByhGjC;;A2Bl/FI;EACE,cAAc;A3Bq/FpB;;A2BlhGI;EDmDE,iBAAiB;A1Bm+FvB;;A0B99FA;EAEI,MAAM;EACN,WAAW;EACX,UAAU;EACV,aAAa;EACb,sBvBkqBuC;AH8zE3C;;A2B9hGI;EACE,qBAAqB;EACrB,oBxBoO0C;EwBnO1C,uBxBkO0C;EwBjO1C,WAAW;A3BiiGjB;;A2BriGI;EAgBI,aAAa;A3ByhGrB;;A2BthGM;EACE,qBAAqB;EACrB,qBxBiNwC;EwBhNxC,uBxB+MwC;EwB9MxC,WAAW;EA9BjB,mCAA0C;EAC1C,yBAAgC;EAChC,sCAA6C;A3BwjG/C;;A2BvhGI;EACE,cAAc;A3B0hGpB;;A2BpiGM;EDiDA,iBAAiB;A1Bu/FvB;;A0Bh/FA;EAKI,WAAW;EACX,YAAY;A1B++FhB;;A0B1+FA;EE9GE,SAAS;EACT,gBAAmB;EACnB,gBAAgB;EAChB,6BzBCgB;AH2lGlB;;A0B1+FA;EACE,cAAc;EACd,WAAW;EACX,uBvBspBwC;EuBrpBxC,WAAW;EACX,gBvBsK+B;EuBrK/B,cvBhHgB;EuBiHhB,mBAAmB;EAEnB,mBAAmB;EACnB,6BAA6B;EAC7B,SAAS;A1B4+FX;;AKjmGE;EqBoIE,cvBsnBqD;EuBrnBrD,qBAAqB;EJ/IrB,yBnBEc;AH+mGlB;;A0B7/FA;EAiCI,WvBpJW;EuBqJX,qBAAqB;EJtJrB,yBnB8Ba;AHylGjB;;A0BngGA;EAwCI,cvBrJc;EuBsJd,oBAAoB;EACpB,6BAA6B;A1B+9FjC;;A0Bv9FA;EACE,cAAc;A1B09FhB;;A0Bt9FA;EACE,cAAc;EACd,sBvBgmBwC;EuB/lBxC,gBAAgB;EtBrDZ,mBAtCY;EsB6FhB,cvBzKgB;EuB0KhB,mBAAmB;A1By9FrB;;A0Br9FA;EACE,cAAc;EACd,uBvBslBwC;EuBrlBxC,cvB9KgB;AHsoGlB;;A6BnpGA;;EAEE,kBAAkB;EAClB,2BAAoB;EAApB,oBAAoB;EACpB,sBAAsB;A7BspGxB;;A6B1pGA;;EAOI,kBAAkB;EAClB,kBAAc;EAAd,cAAc;A7BwpGlB;;AKvpGE;;EwBII,UAAU;A7BwpGhB;;A6BrqGA;;;;EAkBM,UAAU;A7B0pGhB;;A6BppGA;EACE,oBAAa;EAAb,aAAa;EACb,mBAAe;EAAf,eAAe;EACf,oBAA2B;EAA3B,2BAA2B;A7BupG7B;;A6B1pGA;EAMI,WAAW;A7BwpGf;;A6BppGA;;EAII,iB1BwM6B;AH68FjC;;A6BzpGA;;EnBHI,0BmBa8B;EnBZ9B,6BmBY8B;A7BqpGlC;;A6B/pGA;;EnBWI,yBmBI6B;EnBH7B,4BmBG6B;A7BspGjC;;A6BtoGA;EACE,wBAAmC;EACnC,uBAAkC;A7ByoGpC;;A6B3oGA;;;EAOI,cAAc;A7B0oGlB;;A6BvoGE;EACE,eAAe;A7B0oGnB;;A6BtoGA;EACE,uBAAsC;EACtC,sBAAqC;A7ByoGvC;;A6BtoGA;EACE,sBAAsC;EACtC,qBAAqC;A7ByoGvC;;A6BrnGA;EACE,0BAAsB;EAAtB,sBAAsB;EACtB,qBAAuB;EAAvB,uBAAuB;EACvB,qBAAuB;EAAvB,uBAAuB;A7BwnGzB;;A6B3nGA;;EAOI,WAAW;A7BynGf;;A6BhoGA;;EAYI,gB1BuH6B;AHkgGjC;;A6BroGA;;EnBrEI,6BmBuF+B;EnBtF/B,4BmBsF+B;A7BynGnC;;A6B3oGA;;EnBnFI,yBmB0G4B;EnBzG5B,0BmByG4B;A7B0nGhC;;A6BzmGA;;EAGI,gBAAgB;A7B2mGpB;;A6B9mGA;;;;EAOM,kBAAkB;EAClB,sBAAsB;EACtB,oBAAoB;A7B8mG1B;;A8BvwGA;EACE,kBAAkB;EAClB,oBAAa;EAAb,aAAa;EACb,mBAAe;EAAf,eAAe;EACf,uBAAoB;EAApB,oBAAoB;EACpB,WAAW;A9B0wGb;;A8B/wGA;;;;EAWI,kBAAkB;EAClB,kBAAc;EAAd,cAAc;EACd,SAAS;EACT,YAAY;EACZ,gBAAgB;A9B2wGpB;;A8B1xGA;;;;;;;;;;;;EAoBM,iB3BuN2B;AH8jGjC;;A8BzyGA;;;EA4BI,UAAU;A9BmxGd;;A8B/yGA;EAiCI,UAAU;A9BkxGd;;A8BnzGA;;EpB4BI,0BoBUmD;EpBTnD,6BoBSmD;A9BmxGvD;;A8BzzGA;;EpB0CI,yBoBHmD;EpBInD,4BoBJmD;A9BwxGvD;;A8B/zGA;EA6CI,oBAAa;EAAb,aAAa;EACb,sBAAmB;EAAnB,mBAAmB;A9BsxGvB;;A8Bp0GA;;EpB4BI,0BoBqB6E;EpBpB7E,6BoBoB6E;A9ByxGjF;;A8B10GA;EpB0CI,yBoBQsE;EpBPtE,4BoBOsE;A9B6xG1E;;A8BlxGA;;EAEE,oBAAa;EAAb,aAAa;A9BqxGf;;A8BvxGA;;EAQI,kBAAkB;EAClB,UAAU;A9BoxGd;;A8B7xGA;;EAYM,UAAU;A9BsxGhB;;A8BlyGA;;;;;;;;EAoBI,iB3B0J6B;AH+nGjC;;A8BrxGA;EAAuB,kB3BsJU;AHmoGjC;;A8BxxGA;EAAsB,iB3BqJW;AHuoGjC;;A8BpxGA;EACE,oBAAa;EAAb,aAAa;EACb,sBAAmB;EAAnB,mBAAmB;EACnB,yB3BiSkC;E2BhSlC,gBAAgB;E1BuBZ,eAtCY;E0BiBhB,gB3B2L+B;E2B1L/B,gB3B+L+B;E2B9L/B,c3B9FgB;E2B+FhB,kBAAkB;EAClB,mBAAmB;EACnB,yB3BtGgB;E2BuGhB,yB3BrGgB;EOOd,sBPkOgC;AHopGpC;;A8BpyGA;;EAkBI,aAAa;A9BuxGjB;;A8B7wGA;;EAEE,gCZX8D;AlB2xGhE;;A8B7wGA;;;;;;EAME,oB3B4QgC;ECxR5B,kBAtCY;E0BoDhB,gB3BiG+B;EO5N7B,qBPmO+B;AHyqGnC;;A8B7wGA;;EAEE,kCZ5B8D;AlB4yGhE;;A8B7wGA;;;;;;EAME,uB3BsPiC;ECnR7B,mBAtCY;E0BqEhB,gB3BiF+B;EO7N7B,qBPoO+B;AHyrGnC;;A8B7wGA;;EAEE,sBAA0E;A9BgxG5E;;A8BrwGA;;;;;;EpB7II,0BoBmJ4B;EpBlJ5B,6BoBkJ4B;A9BywGhC;;A8BtwGA;;;;;;EpBxII,yBoB8I2B;EpB7I3B,4BoB6I2B;A9B0wG/B;;A+B/7GA;EACE,kBAAkB;EAClB,cAAc;EACd,kBAA+C;EAC/C,oBAAqE;A/Bk8GvE;;A+B/7GA;EACE,2BAAoB;EAApB,oBAAoB;EACpB,kB5B+f0C;AHm8F5C;;A+B/7GA;EACE,kBAAkB;EAClB,OAAO;EACP,WAAW;EACX,W5B2f0C;E4B1f1C,eAAkF;EAClF,UAAU;A/Bk8GZ;;A+Bx8GA;EASI,W5BvBW;E4BwBX,qB5BKa;EmB9Bb,yBnB8Ba;AH+7GjB;;A+B98GA;EAoBM,gD5BLW;AHm8GjB;;A+Bl9GA;EAyBI,qB5B4bsE;AHigG1E;;A+Bt9GA;EA6BI,W5B3CW;E4B4CX,yB5Bwf8E;E4Bvf9E,qB5Buf8E;AHs8FlF;;A+B59GA;EAuCM,c5B/CY;AHw+GlB;;A+Bh+GA;EA0CQ,yB5BtDU;AHg/GlB;;A+Bh7GA;EACE,kBAAkB;EAClB,gBAAgB;EAEhB,mBAAmB;A/Bk7GrB;;A+Bt7GA;EASI,kBAAkB;EAClB,YAA+E;EAC/E,aAA+D;EAC/D,cAAc;EACd,W5B8bwC;E4B7bxC,Y5B6bwC;E4B5bxC,oBAAoB;EACpB,WAAW;EACX,sB5BnFW;E4BoFX,yB5BsJ6B;AH2xGjC;;A+Bn8GA;EAwBI,kBAAkB;EAClB,YAA+E;EAC/E,aAA+D;EAC/D,cAAc;EACd,W5B+awC;E4B9axC,Y5B8awC;E4B7axC,WAAW;EACX,mCAAgE;A/B+6GpE;;A+Bt6GA;ErB/FI,sBPkOgC;AHuyGpC;;A+B16GA;EAOM,kOb9D4E;AlBq+GlF;;A+B96GA;EAaM,qB5B1FW;EmB9Bb,yBnB8Ba;AHggHjB;;A+Bn7GA;EAkBM,+KbzE4E;AlB8+GlF;;A+Bv7GA;EAwBM,wC5BrGW;AHwgHjB;;A+B37GA;EA2BM,wC5BxGW;AH4gHjB;;A+B35GA;EAGI,kB5Bga+C;AH4/FnD;;A+B/5GA;EAQM,8KbnG4E;AlB8/GlF;;A+Bn6GA;EAcM,wC5B/HW;AHwhHjB;;A+B/4GA;EACE,qBAA2D;A/Bk5G7D;;A+Bn5GA;EAKM,cAAqD;EACrD,c5BwY+E;E4BvY/E,mBAAmB;EAEnB,qB5BsY4E;AH2gGlF;;A+B15GA;EAaM,wBbnE0D;EaoE1D,0BbpE0D;EaqE1D,uBbjD0D;EakD1D,wBblD0D;EamD1D,yB5BlLY;E4BoLZ,qB5B4X4E;EiB5iB5E,iJjB8f+H;EiB9f/H,yIjB8f+H;EiB9f/H,8KjB8f+H;AHmkGrI;;AoB7jHM;EWyJN;IXxJQ,gBAAgB;EpBikHtB;AACF;;A+B16GA;EA0BM,sB5BhMS;E4BiMT,sCAA4E;EAA5E,8BAA4E;A/Bo5GlF;;A+B/6GA;EAiCM,wC5B1KW;AH4jHjB;;A+Bt4GA;EACE,qBAAqB;EACrB,WAAW;EACX,mCbtG8D;EauG9D,0C5B0KkC;ECzQ9B,eAtCY;E2BwIhB,gB5BoE+B;E4BnE/B,gB5BwE+B;E4BvE/B,c5BrNgB;E4BsNhB,sBAAsB;EACtB,uO5ByW+I;E4BxW/I,yB5B3NgB;EOOd,sBPkOgC;E4BXlC,wBAAgB;EAAhB,qBAAgB;EAAhB,gBAAgB;A/Bu4GlB;;A+Bt5GA;EAkBI,qB5B8PsE;E4B7PtE,UAAU;EAKR,gD5B9MW;AHklHjB;;A+B55GA;EAiCM,c5B7OY;E4B8OZ,sB5BrPS;AHonHf;;A+Bj6GA;EAwCI,YAAY;EACZ,sB5BqIgC;E4BpIhC,sBAAsB;A/B63G1B;;A+Bv6GA;EA8CI,c5B3Pc;E4B4Pd,yB5BhQc;AH6nHlB;;A+B56GA;EAoDI,aAAa;A/B43GjB;;A+Bh7GA;EAyDI,kBAAkB;EAClB,0B5BtQc;AHioHlB;;A+Bv3GA;EACE,kCblK8D;EamK9D,oB5BuHkC;E4BtHlC,uB5BsHkC;E4BrHlC,oB5BsHiC;ECnR7B,mBAtCY;AJ8jHlB;;A+Bv3GA;EACE,gCb1K8D;Ea2K9D,mB5BoHiC;E4BnHjC,sB5BmHiC;E4BlHjC,kB5BmHgC;ECxR5B,kBAtCY;AJskHlB;;A+Bl3GA;EACE,kBAAkB;EAClB,qBAAqB;EACrB,WAAW;EACX,mCb1L8D;Ea2L9D,gBAAgB;A/Bq3GlB;;A+Bl3GA;EACE,kBAAkB;EAClB,UAAU;EACV,WAAW;EACX,mCblM8D;EamM9D,SAAS;EACT,UAAU;A/Bq3GZ;;A+B33GA;EASI,qB5B4KsE;E4B3KtE,gD5B3Ra;AHipHjB;;A+Bh4GA;;EAgBI,yB5B5Tc;AHirHlB;;A+Br4GA;EAqBM,iB5BmUQ;AHijGd;;A+Bz4GA;EA0BI,0BAA0B;A/Bm3G9B;;A+B/2GA;EACE,kBAAkB;EAClB,MAAM;EACN,QAAQ;EACR,OAAO;EACP,UAAU;EACV,mCblO8D;EamO9D,yB5B8CkC;E4B5ClC,gB5BvD+B;E4BwD/B,gB5BnD+B;E4BoD/B,c5BhVgB;E4BiVhB,sB5BxVa;E4ByVb,yB5BrVgB;EOOd,sBPkOgC;AH89GpC;;A+B/3GA;EAkBI,kBAAkB;EAClB,MAAM;EACN,QAAQ;EACR,SAAS;EACT,UAAU;EACV,cAAc;EACd,6BbpP4D;EaqP5D,yB5B4BgC;E4B3BhC,gB5BnE6B;E4BoE7B,c5BhWc;E4BiWd,iBAAiB;ETzWjB,yBnBGc;E4BwWd,oBAAoB;ErB/VpB,kCqBgWgF;A/Bi3GpF;;A+Bv2GA;EACE,WAAW;EACX,cb1Q2B;Ea2Q3B,UAAU;EACV,6BAA6B;EAC7B,wBAAgB;EAAhB,qBAAgB;EAAhB,gBAAgB;A/B02GlB;;A+B/2GA;EAQI,aAAa;A/B22GjB;;A+Bn3GA;EAY8B,gE5BpWb;AH+sHjB;;A+Bv3GA;EAa8B,gE5BrWb;AHmtHjB;;A+B33GA;EAc8B,gE5BtWb;AHutHjB;;A+B/3GA;EAkBI,SAAS;A/Bi3Gb;;A+Bn4GA;EAsBI,W5B4N6C;E4B3N7C,Y5B2N6C;E4B1N7C,oBAAyE;ET9YzE,yBnB8Ba;E4BkXb,S5B2N0C;EO/lB1C,mBPgmB6C;EiBlmB3C,oHjB8f+H;EiB9f/H,4GjB8f+H;E4BpHjI,wBAAgB;EAAhB,gBAAgB;A/Bg3GpB;;AoBtvHM;EWwWN;IXvWQ,wBAAgB;IAAhB,gBAAgB;EpB0vHtB;AACF;;A+Bp5GA;ETtXI,yBnBgnB2E;AH8pG/E;;A+Bx5GA;EAsCI,W5BqMoC;E4BpMpC,c5BqMqC;E4BpMrC,kBAAkB;EAClB,e5BoMuC;E4BnMvC,yB5B5Zc;E4B6Zd,yBAAyB;ErBrZzB,mBPylBoC;AHmrGxC;;A+Bl6GA;EAiDI,W5BiM6C;E4BhM7C,Y5BgM6C;EmBxmB7C,yBnB8Ba;E4B4Yb,S5BiM0C;EO/lB1C,mBPgmB6C;EiBlmB3C,iHjB8f+H;EiB9f/H,4GjB8f+H;E4B1FjI,qBAAgB;EAAhB,gBAAgB;A/Bo3GpB;;AoBpxHM;EWwWN;IXvWQ,qBAAgB;IAAhB,gBAAgB;EpBwxHtB;AACF;;A+Bl7GA;ETtXI,yBnBgnB2E;AH4rG/E;;A+Bt7GA;EAgEI,W5B2KoC;E4B1KpC,c5B2KqC;E4B1KrC,kBAAkB;EAClB,e5B0KuC;E4BzKvC,yB5Btbc;E4Bubd,yBAAyB;ErB/azB,mBPylBoC;AHitGxC;;A+Bh8GA;EA2EI,W5BuK6C;E4BtK7C,Y5BsK6C;E4BrK7C,aAAa;EACb,oB5B7D+B;E4B8D/B,mB5B9D+B;EmBvY/B,yBnB8Ba;E4Byab,S5BoK0C;EO/lB1C,mBPgmB6C;EiBlmB3C,gHjB8f+H;EiB9f/H,4GjB8f+H;E4B7DjI,gBAAgB;A/Bw3GpB;;AoBrzHM;EWwWN;IXvWQ,oBAAgB;IAAhB,gBAAgB;EpByzHtB;AACF;;A+Bn9GA;ETtXI,yBnBgnB2E;AH6tG/E;;A+Bv9GA;EA6FI,W5B8IoC;E4B7IpC,c5B8IqC;E4B7IrC,kBAAkB;EAClB,e5B6IuC;E4B5IvC,6BAA6B;EAC7B,yBAAyB;EACzB,oBAA4C;A/B83GhD;;A+Bj+GA;EAwGI,yB5B1dc;EOQd,mBPylBoC;AHuvGxC;;A+Bt+GA;EA6GI,kBAAkB;EAClB,yB5Bhec;EOQd,mBPylBoC;AH6vGxC;;A+B5+GA;EAoHM,yB5BpeY;AHg2HlB;;A+Bh/GA;EAwHM,eAAe;A/B43GrB;;A+Bp/GA;EA4HM,yB5B5eY;AHw2HlB;;A+Bx/GA;EAgIM,eAAe;A/B43GrB;;A+B5/GA;EAoIM,yB5BpfY;AHg3HlB;;A+Bv3GA;;;EXrfM,4GjB8f+H;AHo3GrI;;AoB92HM;EWifN;;;IXhfQ,gBAAgB;EpBo3HtB;AACF;;AgCr4HA;EACE,oBAAa;EAAb,aAAa;EACb,mBAAe;EAAf,eAAe;EACf,eAAe;EACf,gBAAgB;EAChB,gBAAgB;AhCw4HlB;;AgCr4HA;EACE,cAAc;EACd,oB7B+qBsC;AHytGxC;;AKv4HE;E2BGE,qBAAqB;AhCw4HzB;;AgC94HA;EAWI,c7BXc;E6BYd,oBAAoB;EACpB,eAAe;AhCu4HnB;;AgC/3HA;EACE,gC7BzBgB;AH25HlB;;AgCn4HA;EAII,mB7B2M6B;AHwrHjC;;AgCv4HA;EAQI,6BAAgD;EtBfhD,+BPyNgC;EOxNhC,gCPwNgC;AH2rHpC;;AK/5HE;E2B8BI,qC7BpCY;AHy6HlB;;AgCj5HA;EAgBM,c7BrCY;E6BsCZ,6BAA6B;EAC7B,yBAAyB;AhCq4H/B;;AgCv5HA;;EAwBI,c7B5Cc;E6B6Cd,sB7BpDW;E6BqDX,kC7BrDW;AHy7Hf;;AgC95HA;EA+BI,gB7BgL6B;EOtN7B,yBsBwC4B;EtBvC5B,0BsBuC4B;AhCm4HhC;;AgC13HA;EtB1DI,sBPkOgC;AHstHpC;;AgC93HA;;EAOI,W7B5EW;E6B6EX,yB7BhDa;AH46HjB;;AgCn3HA;EAEI,kBAAc;EAAd,cAAc;EACd,kBAAkB;AhCq3HtB;;AgCj3HA;EAEI,0BAAa;EAAb,aAAa;EACb,oBAAY;EAAZ,YAAY;EACZ,kBAAkB;AhCm3HtB;;AgC12HA;EAEI,aAAa;AhC42HjB;;AgC92HA;EAKI,cAAc;AhC62HlB;;AiCl9HA;EACE,kBAAkB;EAClB,oBAAa;EAAb,aAAa;EACb,mBAAe;EAAf,eAAe;EACf,sBAAmB;EAAnB,mBAAmB;EACnB,sBAA8B;EAA9B,8BAA8B;EAC9B,oB9BmHW;AHk2Hb;;AiC39HA;;EAWI,oBAAa;EAAb,aAAa;EACb,mBAAe;EAAf,eAAe;EACf,sBAAmB;EAAnB,mBAAmB;EACnB,sBAA8B;EAA9B,8BAA8B;AjCq9HlC;;AiCj8HA;EACE,qBAAqB;EACrB,sB9BuqB+E;E8BtqB/E,yB9BsqB+E;E8BrqB/E,kB9BmFW;ECXP,kBAtCY;E6BhChB,oBAAoB;EACpB,mBAAmB;AjCo8HrB;;AK9+HE;E4B6CE,qBAAqB;AjCq8HzB;;AiC57HA;EACE,oBAAa;EAAb,aAAa;EACb,0BAAsB;EAAtB,sBAAsB;EACtB,eAAe;EACf,gBAAgB;EAChB,gBAAgB;AjC+7HlB;;AiCp8HA;EAQI,gBAAgB;EAChB,eAAe;AjCg8HnB;;AiCz8HA;EAaI,gBAAgB;EAChB,WAAW;AjCg8Hf;;AiCv7HA;EACE,qBAAqB;EACrB,mB9B8lBuC;E8B7lBvC,sB9B6lBuC;AH61GzC;;AiC96HA;EACE,6BAAgB;EAAhB,gBAAgB;EAChB,oBAAY;EAAZ,YAAY;EAGZ,sBAAmB;EAAnB,mBAAmB;AjC+6HrB;;AiC36HA;EACE,wB9BymBwC;EChmBpC,kBAtCY;E6B+BhB,cAAc;EACd,6BAA6B;EAC7B,6BAAuC;EvBxGrC,sBPkOgC;AHqzHpC;;AKzhIE;E4B8GE,qBAAqB;AjC+6HzB;;AiCz6HA;EACE,qBAAqB;EACrB,YAAY;EACZ,aAAa;EACb,sBAAsB;EACtB,WAAW;EACX,mCAAmC;EACnC,0BAA0B;AjC46H5B;;Ac9+HI;EmB4EC;;IAGK,gBAAgB;IAChB,eAAe;EjCq6HvB;AACF;;AcngII;EmByFA;IAoBI,yBAAqB;IAArB,qBAAqB;IACrB,oBAA2B;IAA3B,2BAA2B;EjC25HjC;EiCh7HG;IAwBK,uBAAmB;IAAnB,mBAAmB;EjC25H3B;EiCn7HG;IA2BO,kBAAkB;EjC25H5B;EiCt7HG;IA+BO,qB9BkiB6B;I8BjiB7B,oB9BiiB6B;EHy3GvC;EiC17HG;;IAsCK,qBAAiB;IAAjB,iBAAiB;EjCw5HzB;EiC97HG;IAqDK,+BAAwB;IAAxB,wBAAwB;IAGxB,6BAAgB;IAAhB,gBAAgB;EjC04HxB;EiCl8HG;IA4DK,aAAa;EjCy4HrB;AACF;;AclhII;EmB4EC;;IAGK,gBAAgB;IAChB,eAAe;EjCy8HvB;AACF;;AcviII;EmByFA;IAoBI,yBAAqB;IAArB,qBAAqB;IACrB,oBAA2B;IAA3B,2BAA2B;EjC+7HjC;EiCp9HG;IAwBK,uBAAmB;IAAnB,mBAAmB;EjC+7H3B;EiCv9HG;IA2BO,kBAAkB;EjC+7H5B;EiC19HG;IA+BO,qB9BkiB6B;I8BjiB7B,oB9BiiB6B;EH65GvC;EiC99HG;;IAsCK,qBAAiB;IAAjB,iBAAiB;EjC47HzB;EiCl+HG;IAqDK,+BAAwB;IAAxB,wBAAwB;IAGxB,6BAAgB;IAAhB,gBAAgB;EjC86HxB;EiCt+HG;IA4DK,aAAa;EjC66HrB;AACF;;ActjII;EmB4EC;;IAGK,gBAAgB;IAChB,eAAe;EjC6+HvB;AACF;;Ac3kII;EmByFA;IAoBI,yBAAqB;IAArB,qBAAqB;IACrB,oBAA2B;IAA3B,2BAA2B;EjCm+HjC;EiCx/HG;IAwBK,uBAAmB;IAAnB,mBAAmB;EjCm+H3B;EiC3/HG;IA2BO,kBAAkB;EjCm+H5B;EiC9/HG;IA+BO,qB9BkiB6B;I8BjiB7B,oB9BiiB6B;EHi8GvC;EiClgIG;;IAsCK,qBAAiB;IAAjB,iBAAiB;EjCg+HzB;EiCtgIG;IAqDK,+BAAwB;IAAxB,wBAAwB;IAGxB,6BAAgB;IAAhB,gBAAgB;EjCk9HxB;EiC1gIG;IA4DK,aAAa;EjCi9HrB;AACF;;Ac1lII;EmB4EC;;IAGK,gBAAgB;IAChB,eAAe;EjCihIvB;AACF;;Ac/mII;EmByFA;IAoBI,yBAAqB;IAArB,qBAAqB;IACrB,oBAA2B;IAA3B,2BAA2B;EjCugIjC;EiC5hIG;IAwBK,uBAAmB;IAAnB,mBAAmB;EjCugI3B;EiC/hIG;IA2BO,kBAAkB;EjCugI5B;EiCliIG;IA+BO,qB9BkiB6B;I8BjiB7B,oB9BiiB6B;EHq+GvC;EiCtiIG;;IAsCK,qBAAiB;IAAjB,iBAAiB;EjCogIzB;EiC1iIG;IAqDK,+BAAwB;IAAxB,wBAAwB;IAGxB,6BAAgB;IAAhB,gBAAgB;EjCs/HxB;EiC9iIG;IA4DK,aAAa;EjCq/HrB;AACF;;AiCvjIA;EAyBQ,yBAAqB;EAArB,qBAAqB;EACrB,oBAA2B;EAA3B,2BAA2B;AjCkiInC;;AiC5jIA;;EAQU,gBAAgB;EAChB,eAAe;AjCyjIzB;;AiClkIA;EA6BU,uBAAmB;EAAnB,mBAAmB;AjCyiI7B;;AiCtkIA;EAgCY,kBAAkB;AjC0iI9B;;AiC1kIA;EAoCY,qB9BkiB6B;E8BjiB7B,oB9BiiB6B;AHygHzC;;AiC/kIA;;EA2CU,qBAAiB;EAAjB,iBAAiB;AjCyiI3B;;AiCplIA;EA0DU,+BAAwB;EAAxB,wBAAwB;EAGxB,6BAAgB;EAAhB,gBAAgB;AjC4hI1B;;AiCzlIA;EAiEU,aAAa;AjC4hIvB;;AiC/gIA;EAEI,yB9B/MW;AHguIf;;AKjuIE;E4BmNI,yB9BlNS;AHouIf;;AiCvhIA;EAWM,yB9BxNS;AHwuIf;;AKzuIE;E4B4NM,yB9B3NO;AH4uIf;;AiC/hIA;EAkBQ,yB9B/NO;AHgvIf;;AiCniIA;;;;EA0BM,yB9BvOS;AHuvIf;;AiC1iIA;EA+BI,yB9B5OW;E8B6OX,gC9B7OW;AH4vIf;;AiC/iIA;EAoCI,mRfxM8E;AlButIlF;;AiCnjIA;EAwCI,yB9BrPW;AHowIf;;AiCvjIA;EA0CM,yB9BvPS;AHwwIf;;AKzwIE;E4B2PM,yB9B1PO;AH4wIf;;AiC3gIA;EAEI,W9B7QW;AH0xIf;;AKjxIE;E4BuQI,W9BhRS;AH8xIf;;AiCnhIA;EAWM,+B9BtRS;AHkyIf;;AKzxIE;E4BgRM,gC9BzRO;AHsyIf;;AiC3hIA;EAkBQ,gC9B7RO;AH0yIf;;AiC/hIA;;;;EA0BM,W9BrSS;AHizIf;;AiCtiIA;EA+BI,+B9B1SW;E8B2SX,sC9B3SW;AHszIf;;AiC3iIA;EAoCI,yRf5P8E;AlBuwIlF;;AiC/iIA;EAwCI,+B9BnTW;AH8zIf;;AiCnjIA;EA0CM,W9BrTS;AHk0If;;AKzzIE;E4B+SM,W9BxTO;AHs0If;;AkCz0IA;EACE,kBAAkB;EAClB,oBAAa;EAAb,aAAa;EACb,0BAAsB;EAAtB,sBAAsB;EACtB,YAAY;EAEZ,qBAAqB;EACrB,sB/BJa;E+BKb,2BAA2B;EAC3B,sC/BIa;EOCX,sBPkOgC;AHqmIpC;;AkCr1IA;EAaI,eAAe;EACf,cAAc;AlC40IlB;;AkC11IA;EAkBI,mBAAmB;EACnB,sBAAsB;AlC40I1B;;AkC/1IA;EAsBM,mBAAmB;ExBCrB,2CQgH4D;ER/G5D,4CQ+G4D;AlB8tIhE;;AkCr2IA;EA2BM,sBAAsB;ExBUxB,+CQkG4D;ERjG5D,8CQiG4D;AlBouIhE;;AkC10IA;EAGE,kBAAc;EAAd,cAAc;EAGd,eAAe;EACf,gB/BsxByC;AHmjH3C;;AkCr0IA;EACE,sB/BgxBwC;AHwjH1C;;AkCr0IA;EACE,qBAA+B;EAC/B,gBAAgB;AlCw0IlB;;AkCr0IA;EACE,gBAAgB;AlCw0IlB;;AKt3IE;E6BmDE,qBAAqB;AlCu0IzB;;AkCz0IA;EAMI,oB/B+vBuC;AHwkH3C;;AkC/zIA;EACE,wB/BsvByC;E+BrvBzC,gBAAgB;EAEhB,qC/B9Da;E+B+Db,6C/B/Da;AHg4If;;AkCt0IA;ExBzDI,0DwBiE8E;AlCk0IlF;;AkC10IA;EAaM,aAAa;AlCi0InB;;AkC5zIA;EACE,wB/BouByC;E+BluBzC,qC/B/Ea;E+BgFb,0C/BhFa;AH84If;;AkCl0IA;ExB3EI,0DQyH4D;AlBwxIhE;;AkCtzIA;EACE,uBAAiC;EACjC,uB/BktBwC;E+BjtBxC,sBAAgC;EAChC,gBAAgB;AlCyzIlB;;AkCtzIA;EACE,uBAAiC;EACjC,sBAAgC;AlCyzIlC;;AkCrzIA;EACE,kBAAkB;EAClB,MAAM;EACN,QAAQ;EACR,SAAS;EACT,OAAO;EACP,gB/B6sByC;AH2mH3C;;AkCrzIA;;;EAGE,oBAAc;EAAd,cAAc;EACd,WAAW;AlCwzIb;;AkCrzIA;;ExB/GI,2CQgH4D;ER/G5D,4CQ+G4D;AlB0zIhE;;AkCtzIA;;ExBtGI,+CQkG4D;ERjG5D,8CQiG4D;AlBg0IhE;;AkCpzIA;EAEI,mB/BsrBsD;AHgoH1D;;Acn5II;EoB2FJ;IAMI,oBAAa;IAAb,aAAa;IACb,uBAAmB;IAAnB,mBAAmB;IACnB,mB/BgrBsD;I+B/qBtD,kB/B+qBsD;EHwoHxD;EkCh0IF;IAaM,gBAAY;IAAZ,YAAY;IACZ,kB/B0qBoD;I+BzqBpD,gBAAgB;IAChB,iB/BwqBoD;EH8oHxD;AACF;;AkC7yIA;EAII,mB/B0pBsD;AHmpH1D;;Act6II;EoBqHJ;IAQI,oBAAa;IAAb,aAAa;IACb,uBAAmB;IAAnB,mBAAmB;ElC8yIrB;EkCvzIF;IAcM,gBAAY;IAAZ,YAAY;IACZ,gBAAgB;ElC4yIpB;EkC3zIF;IAkBQ,cAAc;IACd,cAAc;ElC4yIpB;EkC/zIF;IxB/II,0BwBwKoC;IxBvKpC,6BwBuKoC;ElC0yItC;EkCn0IF;;IA8BY,0BAA0B;ElCyyIpC;EkCv0IF;;IAmCY,6BAA6B;ElCwyIvC;EkC30IF;IxBjII,yBwByKmC;IxBxKnC,4BwBwKmC;ElCuyIrC;EkC/0IF;;IA6CY,yBAAyB;ElCsyInC;EkCn1IF;;IAkDY,4BAA4B;ElCqyItC;AACF;;AkCzxIA;EAEI,sB/B+kBsC;AH4sH1C;;Acj9II;EoBoLJ;IAMI,uB/B4lBiC;I+B5lBjC,oB/B4lBiC;I+B5lBjC,e/B4lBiC;I+B3lBjC,2B/B4lBuC;I+B5lBvC,wB/B4lBuC;I+B5lBvC,mB/B4lBuC;I+B3lBvC,UAAU;IACV,SAAS;ElC4xIX;EkCryIF;IAYM,qBAAqB;IACrB,WAAW;ElC4xIf;AACF;;AkCnxIA;EAEI,gBAAgB;AlCqxIpB;;AkCvxIA;EAKM,gBAAgB;ExBnOlB,6BwBoOiC;ExBnOjC,4BwBmOiC;AlCuxIrC;;AkC7xIA;ExB5OI,yBwBsP8B;ExBrP9B,0BwBqP8B;AlCwxIlC;;AkClyIA;ExBrPI,gBwBmQ0B;EACxB,mB/BrC2B;AH6zIjC;;AmC9iJA;EACE,oBAAa;EAAb,aAAa;EACb,mBAAe;EAAf,eAAe;EACf,qBhCkiCsC;EgCjiCtC,mBhCoiCsC;EgCliCtC,gBAAgB;EAChB,yBhCEgB;EOSd,sBPkOgC;AHo0IpC;;AmC7iJA;EACE,oBAAa;EAAb,aAAa;AnCgjJf;;AmCjjJA;EAKI,oBhCshCqC;AH0hHzC;;AmCrjJA;EAQM,qBAAqB;EACrB,qBhCkhCmC;EgCjhCnC,chCRY;EgCSZ,YhCuhCuC;AH0hH7C;;AmC5jJA;EAsBI,0BAA0B;AnC0iJ9B;;AmChkJA;EA0BI,qBAAqB;AnC0iJzB;;AmCpkJA;EA8BI,chC5Bc;AHskJlB;;AoCnlJA;EACE,oBAAa;EAAb,aAAa;E7BGb,eAAe;EACf,gBAAgB;EGad,sBPkOgC;AHs2IpC;;AoCplJA;EACE,kBAAkB;EAClB,cAAc;EACd,uBjCmxBwC;EiClxBxC,iBjCuO+B;EiCtO/B,iBjCsxBsC;EiCrxBtC,cjCwBe;EiCtBf,sBjCPa;EiCQb,yBjCLgB;AH2lJlB;;AoC/lJA;EAYI,UAAU;EACV,cjCmK8D;EiClK9D,qBAAqB;EACrB,yBjCZc;EiCad,qBjCZc;AHmmJlB;;AoCvmJA;EAoBI,UAAU;EACV,UjC8wBiC;EiC7wBjC,gDjCQa;AH+kJjB;;AoCnlJA;EAGM,cAAc;E1BahB,+BPoMgC;EOnMhC,kCPmMgC;AHq4IpC;;AoCzlJA;E1BEI,gCPkNgC;EOjNhC,mCPiNgC;AH04IpC;;AoC9lJA;EAcI,UAAU;EACV,WjCxCW;EiCyCX,yBjCZa;EiCab,qBjCba;AHimJjB;;AoCrmJA;EAqBI,cjCxCc;EiCyCd,oBAAoB;EAEpB,YAAY;EACZ,sBjClDW;EiCmDX,qBjChDc;AHmoJlB;;AqC1oJE;EACE,uBlC4xBsC;ECjqBpC,kBAtCY;EiCnFd,gBlCwO6B;AHq6IjC;;AqCxoJM;E3BqCF,8BPqM+B;EOpM/B,iCPoM+B;AHm6InC;;AqCxoJM;E3BkBF,+BPmN+B;EOlN/B,kCPkN+B;AHw6InC;;AqC1pJE;EACE,uBlC0xBqC;EC/pBnC,mBAtCY;EiCnFd,gBlCyO6B;AHo7IjC;;AqCxpJM;E3BqCF,8BPsM+B;EOrM/B,iCPqM+B;AHk7InC;;AqCxpJM;E3BkBF,+BPoN+B;EOnN/B,kCPmN+B;AHu7InC;;AsCxqJA;EACE,qBAAqB;EACrB,qBnC25BsC;EC11BpC,cAAW;EkC/Db,gBnC6R+B;EmC5R/B,cAAc;EACd,kBAAkB;EAClB,mBAAmB;EACnB,wBAAwB;E5BKtB,sBPkOgC;EiBpO9B,qIjBqb6I;AHqvInJ;;AoBtqJM;EkBfN;IlBgBQ,gBAAgB;EpB0qJtB;AACF;;AKhrJE;EiCGI,qBAAqB;AtCirJ3B;;AsC/rJA;EAoBI,aAAa;AtC+qJjB;;AsC1qJA;EACE,kBAAkB;EAClB,SAAS;AtC6qJX;;AsCtqJA;EACE,oBnCg4BsC;EmC/3BtC,mBnC+3BsC;EOt5BpC,oBPy5BqC;AHwyHzC;;AsCjqJE;ECjDA,WpCMa;EoCLb,yBpCkCe;AHorJjB;;AKxsJE;EkCVI,WpCCS;EoCAT,yBAAkC;AvCstJxC;;AuCztJU;EAQJ,UAAU;EACV,+CpCuBW;AH8rJjB;;AsChrJE;ECjDA,WpCMa;EoCLb,yBpCWgB;AH0tJlB;;AKvtJE;EkCVI,WpCCS;EoCAT,yBAAkC;AvCquJxC;;AuCxuJU;EAQJ,UAAU;EACV,iDpCAY;AHouJlB;;AsC/rJE;ECjDA,WpCMa;EoCLb,yBpCyCe;AH2sJjB;;AKtuJE;EkCVI,WpCCS;EoCAT,yBAAkC;AvCovJxC;;AuCvvJU;EAQJ,UAAU;EACV,+CpC8BW;AHqtJjB;;AsC9sJE;ECjDA,WpCMa;EoCLb,yBpC2Ce;AHwtJjB;;AKrvJE;EkCVI,WpCCS;EoCAT,yBAAkC;AvCmwJxC;;AuCtwJU;EAQJ,UAAU;EACV,gDpCgCW;AHkuJjB;;AsC7tJE;ECjDA,cpCegB;EoCdhB,yBpCwCe;AH0uJjB;;AKpwJE;EkCVI,cpCUY;EoCTZ,yBAAkC;AvCkxJxC;;AuCrxJU;EAQJ,UAAU;EACV,+CpC6BW;AHovJjB;;AsC5uJE;ECjDA,WpCMa;EoCLb,yBpCsCe;AH2vJjB;;AKnxJE;EkCVI,WpCCS;EoCAT,yBAAkC;AvCiyJxC;;AuCpyJU;EAQJ,UAAU;EACV,+CpC2BW;AHqwJjB;;AsC3vJE;ECjDA,cpCegB;EoCdhB,yBpCMgB;AH0yJlB;;AKlyJE;EkCVI,cpCUY;EoCTZ,yBAAkC;AvCgzJxC;;AuCnzJU;EAQJ,UAAU;EACV,iDpCLY;AHozJlB;;AsC1wJE;ECjDA,WpCMa;EoCLb,yBpCagB;AHkzJlB;;AKjzJE;EkCVI,WpCCS;EoCAT,yBAAkC;AvC+zJxC;;AuCl0JU;EAQJ,UAAU;EACV,8CpCEY;AH4zJlB;;AwC30JA;EACE,kBAAoD;EACpD,mBrCwzBsC;EqCtzBtC,yBrCKgB;EOSd,qBPmO+B;AH6lJnC;;ActxJI;E0B5DJ;IAQI,kBrCkzBoC;EH6hItC;AACF;;AwC50JA;EACE,gBAAgB;EAChB,eAAe;E9BIb,gB8BHsB;AxC+0J1B;;AyC11JA;EACE,kBAAkB;EAClB,wBtCw9ByC;EsCv9BzC,mBtCw9BsC;EsCv9BtC,6BAA6C;E/BU3C,sBPkOgC;AHknJpC;;AyCz1JA;EAEE,cAAc;AzC21JhB;;AyCv1JA;EACE,gBtCkR+B;AHwkJjC;;AyCl1JA;EACE,mBAAsD;AzCq1JxD;;AyCt1JA;EAKI,kBAAkB;EAClB,MAAM;EACN,QAAQ;EACR,wBtC07BuC;EsCz7BvC,cAAc;AzCq1JlB;;AyC30JE;EC9CA,cxBqGgE;EIhG9D,yBJgG8D;EwBnGhE,qBxBmGgE;AlB0xJlE;;A0C33JE;EACE,yBAAqC;A1C83JzC;;A0C33JE;EACE,cAA0B;A1C83J9B;;AyCz1JE;EC9CA,cxBqGgE;EIhG9D,yBJgG8D;EwBnGhE,qBxBmGgE;AlBwyJlE;;A0Cz4JE;EACE,yBAAqC;A1C44JzC;;A0Cz4JE;EACE,cAA0B;A1C44J9B;;AyCv2JE;EC9CA,cxBqGgE;EIhG9D,yBJgG8D;EwBnGhE,qBxBmGgE;AlBszJlE;;A0Cv5JE;EACE,yBAAqC;A1C05JzC;;A0Cv5JE;EACE,cAA0B;A1C05J9B;;AyCr3JE;EC9CA,cxBqGgE;EIhG9D,yBJgG8D;EwBnGhE,qBxBmGgE;AlBo0JlE;;A0Cr6JE;EACE,yBAAqC;A1Cw6JzC;;A0Cr6JE;EACE,cAA0B;A1Cw6J9B;;AyCn4JE;EC9CA,cxBqGgE;EIhG9D,yBJgG8D;EwBnGhE,qBxBmGgE;AlBk1JlE;;A0Cn7JE;EACE,yBAAqC;A1Cs7JzC;;A0Cn7JE;EACE,cAA0B;A1Cs7J9B;;AyCj5JE;EC9CA,cxBqGgE;EIhG9D,yBJgG8D;EwBnGhE,qBxBmGgE;AlBg2JlE;;A0Cj8JE;EACE,yBAAqC;A1Co8JzC;;A0Cj8JE;EACE,cAA0B;A1Co8J9B;;AyC/5JE;EC9CA,cxBqGgE;EIhG9D,yBJgG8D;EwBnGhE,qBxBmGgE;AlB82JlE;;A0C/8JE;EACE,yBAAqC;A1Ck9JzC;;A0C/8JE;EACE,cAA0B;A1Ck9J9B;;AyC76JE;EC9CA,cxBqGgE;EIhG9D,yBJgG8D;EwBnGhE,qBxBmGgE;AlB43JlE;;A0C79JE;EACE,yBAAqC;A1Cg+JzC;;A0C79JE;EACE,cAA0B;A1Cg+J9B;;A2Cx+JE;EACE;IAAO,2BAAuC;E3C4+JhD;E2C3+JE;IAAK,wBAAwB;E3C8+J/B;AACF;;A2Cj/JE;EACE;IAAO,2BAAuC;E3C4+JhD;E2C3+JE;IAAK,wBAAwB;E3C8+J/B;AACF;;A2C3+JA;EACE,oBAAa;EAAb,aAAa;EACb,YxCi+BsC;EwCh+BtC,gBAAgB;EAChB,cAAc;EvCmHV,kBAtCY;EuC3EhB,yBxCLgB;EOSd,sBPkOgC;AHywJpC;;A2C1+JA;EACE,oBAAa;EAAb,aAAa;EACb,0BAAsB;EAAtB,sBAAsB;EACtB,qBAAuB;EAAvB,uBAAuB;EACvB,gBAAgB;EAChB,WxCjBa;EwCkBb,kBAAkB;EAClB,mBAAmB;EACnB,yBxCSe;EiBpBX,2BjBm+B4C;AHshIlD;;AoBr/JM;EuBDN;IvBEQ,gBAAgB;EpBy/JtB;AACF;;A2Ch/JA;ErBYE,qMAA6I;EqBV7I,0BxC08BsC;AHyiIxC;;A2C/+JE;EACE,0DxC48BkD;EwC58BlD,kDxC48BkD;AHsiItD;;A2C/+JM;EAJJ;IAKM,uBAAe;IAAf,eAAe;E3Cm/JrB;AACF;;A4C9hKA;EACE,oBAAa;EAAb,aAAa;EACb,qBAAuB;EAAvB,uBAAuB;A5CiiKzB;;A4C9hKA;EACE,WAAO;EAAP,OAAO;A5CiiKT;;A6CniKA;EACE,oBAAa;EAAb,aAAa;EACb,0BAAsB;EAAtB,sBAAsB;EAGtB,eAAe;EACf,gBAAgB;EnCQd,sBPkOgC;AH2zJpC;;A6C3hKA;EACE,WAAW;EACX,c1CRgB;E0CShB,mBAAmB;A7C8hKrB;;AKriKE;EwCWE,UAAU;EACV,c1Cdc;E0Ced,qBAAqB;EACrB,yB1CtBc;AHojKlB;;A6CxiKA;EAcI,c1ClBc;E0CmBd,yB1C1Bc;AHwjKlB;;A6CrhKA;EACE,kBAAkB;EAClB,cAAc;EACd,wB1Cg9ByC;E0C78BzC,sB1C3Ca;E0C4Cb,sC1ClCa;AHwjKf;;A6C7hKA;EnCjBI,+BmC2BkC;EnC1BlC,gCmC0BkC;A7CwhKtC;;A6CliKA;EnCHI,mCmCiBqC;EnChBrC,kCmCgBqC;A7CyhKzC;;A6CviKA;EAmBI,c1ClDc;E0CmDd,oBAAoB;EACpB,sB1C1DW;AHklKf;;A6C7iKA;EA0BI,UAAU;EACV,W1ChEW;E0CiEX,yB1CpCa;E0CqCb,qB1CrCa;AH4jKjB;;A6CpjKA;EAiCI,mBAAmB;A7CuhKvB;;A6CxjKA;EAoCM,gB1CiK2B;E0ChK3B,qB1CgK2B;AHw3JjC;;A6C1gKI;EACE,uBAAmB;EAAnB,mBAAmB;A7C6gKzB;;A6C9gKI;EnCtBA,kCP2KgC;EOvLhC,0BmCwCwC;A7C6gK5C;;A6CnhKI;EnClCA,gCPuLgC;EO3KhC,4BmCiC0C;A7C6gK9C;;A6CxhKI;EAeM,aAAa;A7C6gKvB;;A6C5hKI;EAmBM,qB1C+HuB;E0C9HvB,oBAAoB;A7C6gK9B;;A6CjiKI;EAuBQ,iB1C2HqB;E0C1HrB,sB1C0HqB;AHo5JjC;;AczkKI;E+BmCA;IACE,uBAAmB;IAAnB,mBAAmB;E7C0iKvB;E6C3iKE;InCtBA,kCP2KgC;IOvLhC,0BmCwCwC;E7CyiK1C;E6C/iKE;InClCA,gCPuLgC;IO3KhC,4BmCiC0C;E7CwiK5C;E6CnjKE;IAeM,aAAa;E7CuiKrB;E6CtjKE;IAmBM,qB1C+HuB;I0C9HvB,oBAAoB;E7CsiK5B;E6C1jKE;IAuBQ,iB1C2HqB;I0C1HrB,sB1C0HqB;EH46J/B;AACF;;AclmKI;E+BmCA;IACE,uBAAmB;IAAnB,mBAAmB;E7CmkKvB;E6CpkKE;InCtBA,kCP2KgC;IOvLhC,0BmCwCwC;E7CkkK1C;E6CxkKE;InClCA,gCPuLgC;IO3KhC,4BmCiC0C;E7CikK5C;E6C5kKE;IAeM,aAAa;E7CgkKrB;E6C/kKE;IAmBM,qB1C+HuB;I0C9HvB,oBAAoB;E7C+jK5B;E6CnlKE;IAuBQ,iB1C2HqB;I0C1HrB,sB1C0HqB;EHq8J/B;AACF;;Ac3nKI;E+BmCA;IACE,uBAAmB;IAAnB,mBAAmB;E7C4lKvB;E6C7lKE;InCtBA,kCP2KgC;IOvLhC,0BmCwCwC;E7C2lK1C;E6CjmKE;InClCA,gCPuLgC;IO3KhC,4BmCiC0C;E7C0lK5C;E6CrmKE;IAeM,aAAa;E7CylKrB;E6CxmKE;IAmBM,qB1C+HuB;I0C9HvB,oBAAoB;E7CwlK5B;E6C5mKE;IAuBQ,iB1C2HqB;I0C1HrB,sB1C0HqB;EH89J/B;AACF;;AcppKI;E+BmCA;IACE,uBAAmB;IAAnB,mBAAmB;E7CqnKvB;E6CtnKE;InCtBA,kCP2KgC;IOvLhC,0BmCwCwC;E7ConK1C;E6C1nKE;InClCA,gCPuLgC;IO3KhC,4BmCiC0C;E7CmnK5C;E6C9nKE;IAeM,aAAa;E7CknKrB;E6CjoKE;IAmBM,qB1C+HuB;I0C9HvB,oBAAoB;E7CinK5B;E6CroKE;IAuBQ,iB1C2HqB;I0C1HrB,sB1C0HqB;EHu/J/B;AACF;;A6CpmKA;EnCnHI,gBmCoHsB;A7CumK1B;;A6CxmKA;EAII,qB1CwG6B;AHggKjC;;A6C5mKA;EAOM,sBAAsB;A7CymK5B;;A8ClvKE;EACE,c5BkG8D;E4BjG9D,yB5BiG8D;AlBopKlE;;AK1uKE;EyCPM,c5B6F0D;E4B5F1D,yBAAyC;A9CqvKjD;;A8C5vKE;EAWM,W3CPO;E2CQP,yB5BuF0D;E4BtF1D,qB5BsF0D;AlB+pKlE;;A8ClwKE;EACE,c5BkG8D;E4BjG9D,yB5BiG8D;AlBoqKlE;;AK1vKE;EyCPM,c5B6F0D;E4B5F1D,yBAAyC;A9CqwKjD;;A8C5wKE;EAWM,W3CPO;E2CQP,yB5BuF0D;E4BtF1D,qB5BsF0D;AlB+qKlE;;A8ClxKE;EACE,c5BkG8D;E4BjG9D,yB5BiG8D;AlBorKlE;;AK1wKE;EyCPM,c5B6F0D;E4B5F1D,yBAAyC;A9CqxKjD;;A8C5xKE;EAWM,W3CPO;E2CQP,yB5BuF0D;E4BtF1D,qB5BsF0D;AlB+rKlE;;A8ClyKE;EACE,c5BkG8D;E4BjG9D,yB5BiG8D;AlBosKlE;;AK1xKE;EyCPM,c5B6F0D;E4B5F1D,yBAAyC;A9CqyKjD;;A8C5yKE;EAWM,W3CPO;E2CQP,yB5BuF0D;E4BtF1D,qB5BsF0D;AlB+sKlE;;A8ClzKE;EACE,c5BkG8D;E4BjG9D,yB5BiG8D;AlBotKlE;;AK1yKE;EyCPM,c5B6F0D;E4B5F1D,yBAAyC;A9CqzKjD;;A8C5zKE;EAWM,W3CPO;E2CQP,yB5BuF0D;E4BtF1D,qB5BsF0D;AlB+tKlE;;A8Cl0KE;EACE,c5BkG8D;E4BjG9D,yB5BiG8D;AlBouKlE;;AK1zKE;EyCPM,c5B6F0D;E4B5F1D,yBAAyC;A9Cq0KjD;;A8C50KE;EAWM,W3CPO;E2CQP,yB5BuF0D;E4BtF1D,qB5BsF0D;AlB+uKlE;;A8Cl1KE;EACE,c5BkG8D;E4BjG9D,yB5BiG8D;AlBovKlE;;AK10KE;EyCPM,c5B6F0D;E4B5F1D,yBAAyC;A9Cq1KjD;;A8C51KE;EAWM,W3CPO;E2CQP,yB5BuF0D;E4BtF1D,qB5BsF0D;AlB+vKlE;;A8Cl2KE;EACE,c5BkG8D;E4BjG9D,yB5BiG8D;AlBowKlE;;AK11KE;EyCPM,c5B6F0D;E4B5F1D,yBAAyC;A9Cq2KjD;;A8C52KE;EAWM,W3CPO;E2CQP,yB5BuF0D;E4BtF1D,qB5BsF0D;AlB+wKlE;;A+Cr3KA;EACE,YAAY;E3C8HR,iBAtCY;E2CtFhB,gB5CmS+B;E4ClS/B,cAAc;EACd,W5CYa;E4CXb,yB5CCa;E4CAb,WAAW;A/Cw3Kb;;AKn3KE;E0CDE,W5CMW;E4CLX,qBAAqB;A/Cw3KzB;;AKp3KE;E0CCI,YAAY;A/Cu3KlB;;A+C52KA;EACE,UAAU;EACV,6BAA6B;EAC7B,SAAS;A/C+2KX;;A+Cz2KA;EACE,oBAAoB;A/C42KtB;;AgDl5KA;EACE,gB7C44BuC;E6C34BvC,gBAAgB;E5C6HZ,mBAtCY;E4CpFhB,2C7CEa;E6CDb,4BAA4B;EAC5B,oC7C64BmD;E6C54BnD,gD7CSa;E6CRb,mCAA2B;EAA3B,2BAA2B;EAC3B,UAAU;EtCQR,sBPm4BsC;AH0gJ1C;;AgD/5KA;EAcI,sB7Cg4BsC;AHqhJ1C;;AgDn6KA;EAkBI,UAAU;AhDq5Kd;;AgDv6KA;EAsBI,cAAc;EACd,UAAU;AhDq5Kd;;AgD56KA;EA2BI,aAAa;AhDq5KjB;;AgDj5KA;EACE,oBAAa;EAAb,aAAa;EACb,sBAAmB;EAAnB,mBAAmB;EACnB,wB7C42BwC;E6C32BxC,c7CtBgB;E6CuBhB,2C7C7Ba;E6C8Bb,4BAA4B;EAC5B,4C7Co3BoD;AHgiJtD;;AgDj5KA;EACE,gB7Co2BwC;AHgjJ1C;;AiDx7KA;EAEE,gBAAgB;AjD07KlB;;AiD57KA;EAKI,kBAAkB;EAClB,gBAAgB;AjD27KpB;;AiDt7KA;EACE,eAAe;EACf,MAAM;EACN,OAAO;EACP,a9CiqBsC;E8ChqBtC,aAAa;EACb,WAAW;EACX,YAAY;EACZ,gBAAgB;EAGhB,UAAU;AjDu7KZ;;AiDh7KA;EACE,kBAAkB;EAClB,WAAW;EACX,c9Cg5BuC;E8C94BvC,oBAAoB;AjDk7KtB;;AiD/6KE;E7B3BI,2CjBq8BoD;EiBr8BpD,mCjBq8BoD;EiBr8BpD,oEjBq8BoD;E8Cx6BtD,sC9Cs6BmD;E8Ct6BnD,8B9Cs6BmD;AH4gJvD;;AoB38KM;E6BuBJ;I7BtBM,gBAAgB;EpB+8KtB;AACF;;AiDt7KE;EACE,uB9Co6BoC;E8Cp6BpC,e9Co6BoC;AHqhJxC;;AiDr7KE;EACE,8B9Ci6B2C;E8Cj6B3C,sB9Ci6B2C;AHuhJ/C;;AiDp7KA;EACE,oBAAa;EAAb,aAAa;EACb,6B/BgF8D;AlBu2KhE;;AiDz7KA;EAKI,8B/B6E4D;E+B5E5D,gBAAgB;AjDw7KpB;;AiD97KA;;EAWI,oBAAc;EAAd,cAAc;AjDw7KlB;;AiDn8KA;EAeI,gBAAgB;AjDw7KpB;;AiDp7KA;EACE,oBAAa;EAAb,aAAa;EACb,sBAAmB;EAAnB,mBAAmB;EACnB,6B/B4D8D;AlB23KhE;;AiD17KA;EAOI,cAAc;EACd,0B/BuD4D;E+BtD5D,2BAAmB;EAAnB,wBAAmB;EAAnB,mBAAmB;EACnB,WAAW;AjDu7Kf;;AiDj8KA;EAeI,0BAAsB;EAAtB,sBAAsB;EACtB,qBAAuB;EAAvB,uBAAuB;EACvB,YAAY;AjDs7KhB;;AiDv8KA;EAoBM,gBAAgB;AjDu7KtB;;AiD38KA;EAwBM,aAAa;AjDu7KnB;;AiDj7KA;EACE,kBAAkB;EAClB,oBAAa;EAAb,aAAa;EACb,0BAAsB;EAAtB,sBAAsB;EACtB,WAAW;EAGX,oBAAoB;EACpB,sB9C3Ga;E8C4Gb,4BAA4B;EAC5B,oC9CnGa;EOCX,qBPmO+B;E8C7HjC,UAAU;AjDg7KZ;;AiD56KA;EACE,eAAe;EACf,MAAM;EACN,OAAO;EACP,a9CqjBsC;E8CpjBtC,YAAY;EACZ,aAAa;EACb,sB9ClHa;AHiiLf;;AiDt7KA;EAUW,UAAU;AjDg7KrB;;AiD17KA;EAWW,Y9C8zB2B;AHqnJtC;;AiD96KA;EACE,oBAAa;EAAb,aAAa;EACb,qBAAuB;EAAvB,uBAAuB;EACvB,sBAA8B;EAA9B,8BAA8B;EAC9B,kB9C2zBsC;E8C1zBtC,gC9CvIgB;EOiBd,0CQgH4D;ER/G5D,2CQ+G4D;AlBy7KhE;;AiDx7KA;EASI,kB9CszBoC;E8CpzBpC,8BAA6F;AjDk7KjG;;AiD76KA;EACE,gBAAgB;EAChB,gB9C4I+B;AHoyKjC;;AiD36KA;EACE,kBAAkB;EAGlB,kBAAc;EAAd,cAAc;EACd,a9CywBsC;AHmqJxC;;AiDx6KA;EACE,oBAAa;EAAb,aAAa;EACb,mBAAe;EAAf,eAAe;EACf,sBAAmB;EAAnB,mBAAmB;EACnB,kBAAyB;EAAzB,yBAAyB;EACzB,gBAAgE;EAChE,6B9CxKgB;EO+Bd,8CQkG4D;ERjG5D,6CQiG4D;AlBo9KhE;;AiDn7KA;EAcI,eAAwC;AjDy6K5C;;AiDp6KA;EACE,kBAAkB;EAClB,YAAY;EACZ,WAAW;EACX,YAAY;EACZ,gBAAgB;AjDu6KlB;;Ac/iLI;EmCzBJ;IAwKI,gB9CqwBqC;I8CpwBrC,oBAAyC;EjDq6K3C;EiDxjLF;IAuJI,+B/BrE4D;ElBy+K9D;EiD3jLF;IA0JM,gC/BxE0D;ElB4+K9D;EiD3iLF;IA4II,+B/B7E4D;ElB++K9D;EiD9iLF;IA+IM,4B/BhF0D;I+BiF1D,2BAAmB;IAAnB,wBAAmB;IAAnB,mBAAmB;EjDk6KvB;EiD15KA;IAAY,gB9C6uB2B;EHgrJvC;AACF;;ActkLI;EmC4KF;;IAEE,gB9CquBqC;EHyrJvC;AACF;;Ac7kLI;EmCmLF;IAAY,iB9C+tB4B;EHgsJxC;AACF;;AkD9oLA;EACE,kBAAkB;EAClB,a/CqrBsC;E+CprBtC,cAAc;EACd,S/C41BmC;EgDh2BnC,kMhDyRiN;EgDvRjN,kBAAkB;EAClB,gBhDiS+B;EgDhS/B,gBhDqS+B;EgDpS/B,gBAAgB;EAChB,iBAAiB;EACjB,qBAAqB;EACrB,iBAAiB;EACjB,oBAAoB;EACpB,sBAAsB;EACtB,kBAAkB;EAClB,oBAAoB;EACpB,mBAAmB;EACnB,gBAAgB;E/CgHZ,mBAtCY;E8C9EhB,qBAAqB;EACrB,UAAU;AlD2pLZ;;AkDtqLA;EAaW,Y/Cg1B2B;AH60JtC;;AkD1qLA;EAgBI,kBAAkB;EAClB,cAAc;EACd,a/Cg1BqC;E+C/0BrC,c/Cg1BqC;AH80JzC;;AkDjrLA;EAsBM,kBAAkB;EAClB,WAAW;EACX,yBAAyB;EACzB,mBAAmB;AlD+pLzB;;AkD1pLA;EACE,iBAAgC;AlD6pLlC;;AkD9pLA;EAII,SAAS;AlD8pLb;;AkDlqLA;EAOM,MAAM;EACN,6BAAgE;EAChE,sB/CvBS;AHsrLf;;AkD1pLA;EACE,iB/CszBuC;AHu2JzC;;AkD9pLA;EAII,OAAO;EACP,a/CkzBqC;E+CjzBrC,c/CgzBqC;AH82JzC;;AkDpqLA;EASM,QAAQ;EACR,oCAA2F;EAC3F,wB/CvCS;AHssLf;;AkD1pLA;EACE,iBAAgC;AlD6pLlC;;AkD9pLA;EAII,MAAM;AlD8pLV;;AkDlqLA;EAOM,SAAS;EACT,6B/C+xBmC;E+C9xBnC,yB/CrDS;AHotLf;;AkD1pLA;EACE,iB/CwxBuC;AHq4JzC;;AkD9pLA;EAII,QAAQ;EACR,a/CoxBqC;E+CnxBrC,c/CkxBqC;AH44JzC;;AkDpqLA;EASM,OAAO;EACP,oC/C+wBmC;E+C9wBnC,uB/CrES;AHouLf;;AkD1oLA;EACE,gB/C8uBuC;E+C7uBvC,uB/CmvBuC;E+ClvBvC,W/CvGa;E+CwGb,kBAAkB;EAClB,sB/C/Fa;EOCX,sBPkOgC;AH0gLpC;;AoD9vLA;EACE,kBAAkB;EAClB,MAAM;EACN,OAAO;EACP,ajDmrBsC;EiDlrBtC,cAAc;EACd,gBjD82BuC;EgDn3BvC,kMhDyRiN;EgDvRjN,kBAAkB;EAClB,gBhDiS+B;EgDhS/B,gBhDqS+B;EgDpS/B,gBAAgB;EAChB,iBAAiB;EACjB,qBAAqB;EACrB,iBAAiB;EACjB,oBAAoB;EACpB,sBAAsB;EACtB,kBAAkB;EAClB,oBAAoB;EACpB,mBAAmB;EACnB,gBAAgB;E/CgHZ,mBAtCY;EgD7EhB,qBAAqB;EACrB,sBjDNa;EiDOb,4BAA4B;EAC5B,oCjDEa;EOCX,qBPmO+B;AHsiLnC;;AoD3xLA;EAoBI,kBAAkB;EAClB,cAAc;EACd,WjD82BoC;EiD72BpC,cjD82BqC;EiD72BrC,gBjD6N+B;AH8iLnC;;AoDnyLA;EA4BM,kBAAkB;EAClB,cAAc;EACd,WAAW;EACX,yBAAyB;EACzB,mBAAmB;ApD2wLzB;;AoDtwLA;EACE,qBjD+1BuC;AH06JzC;;AoD1wLA;EAII,2BlCkG4D;AlBwqLhE;;AoD9wLA;EAOM,SAAS;EACT,6BAAgE;EAChE,qCjD01BiE;AHi7JvE;;AoDpxLA;EAaM,WjD+L2B;EiD9L3B,6BAAgE;EAChE,sBjD7CS;AHwzLf;;AoDtwLA;EACE,mBjD20BuC;AH87JzC;;AoD1wLA;EAII,yBlC8E4D;EkC7E5D,ajDu0BqC;EiDt0BrC,YjDq0BoC;EiDp0BpC,gBAAgC;ApD0wLpC;;AoDjxLA;EAUM,OAAO;EACP,oCAA2F;EAC3F,uCjDm0BiE;AHw8JvE;;AoDvxLA;EAgBM,SjDwK2B;EiDvK3B,oCAA2F;EAC3F,wBjDpES;AH+0Lf;;AoDtwLA;EACE,kBjDozBuC;AHq9JzC;;AoD1wLA;EAII,wBlCuD4D;AlBmtLhE;;AoD9wLA;EAOM,MAAM;EACN,oCAA2F;EAC3F,wCjD+yBiE;AH49JvE;;AoDpxLA;EAaM,QjDoJ2B;EiDnJ3B,oCAA2F;EAC3F,yBjDxFS;AHm2Lf;;AoD1xLA;EAqBI,kBAAkB;EAClB,MAAM;EACN,SAAS;EACT,cAAc;EACd,WjD2xBoC;EiD1xBpC,oBAAsC;EACtC,WAAW;EACX,gCjD+wBuD;AH0/J3D;;AoDrwLA;EACE,oBjDoxBuC;AHo/JzC;;AoDzwLA;EAII,0BlCuB4D;EkCtB5D,ajDgxBqC;EiD/wBrC,YjD8wBoC;EiD7wBpC,gBAAgC;ApDywLpC;;AoDhxLA;EAUM,QAAQ;EACR,oCjD0wBmC;EiDzwBnC,sCjD4wBiE;AH8/JvE;;AoDtxLA;EAgBM,UjDiH2B;EiDhH3B,oCjDowBmC;EiDnwBnC,uBjD3HS;AHq4Lf;;AoDpvLA;EACE,uBjDquBwC;EiDpuBxC,gBAAgB;EhD3BZ,eAtCY;EgDoEhB,yBjD8tByD;EiD7tBzD,gCAAyE;E1CnIvE,0CQgH4D;ER/G5D,2CQ+G4D;AlB2wLhE;;AoD9vLA;EAUI,aAAa;ApDwvLjB;;AoDpvLA;EACE,uBjDutBwC;EiDttBxC,cjDxJgB;AH+4LlB;;AqDl5LA;EACE,kBAAkB;ArDq5LpB;;AqDl5LA;EACE,uBAAmB;EAAnB,mBAAmB;ArDq5LrB;;AqDl5LA;EACE,kBAAkB;EAClB,WAAW;EACX,gBAAgB;ArDq5LlB;;AsD56LE;EACE,cAAc;EACd,WAAW;EACX,WAAW;AtD+6Lf;;AqDv5LA;EACE,kBAAkB;EAClB,aAAa;EACb,WAAW;EACX,WAAW;EACX,mBAAmB;EACnB,mCAA2B;EAA3B,2BAA2B;EjClBvB,8CjB0jCkF;EiB1jClF,sCjB0jCkF;EiB1jClF,0EjB0jCkF;AHm3JxF;;AoBz6LM;EiCQN;IjCPQ,gBAAgB;EpB66LtB;AACF;;AqD75LA;;;EAGE,cAAc;ArDg6LhB;;AqD75LA;;EAEE,mCAA2B;EAA3B,2BAA2B;ArDg6L7B;;AqD75LA;;EAEE,oCAA4B;EAA5B,4BAA4B;ArDg6L9B;;AqDx5LA;EAEI,UAAU;EACV,4BAA4B;EAC5B,uBAAe;EAAf,eAAe;ArD05LnB;;AqD95LA;;;EAUI,UAAU;EACV,UAAU;ArD05Ld;;AqDr6LA;;EAgBI,UAAU;EACV,UAAU;EjC5DR,2BjByjCkC;AH85JxC;;AoBn9LM;EiCuCN;;IjCtCQ,gBAAgB;EpBw9LtB;AACF;;AqDx5LA;;EAEE,kBAAkB;EAClB,MAAM;EACN,SAAS;EACT,UAAU;EAEV,oBAAa;EAAb,aAAa;EACb,sBAAmB;EAAnB,mBAAmB;EACnB,qBAAuB;EAAvB,uBAAuB;EACvB,UlDq9BsC;EkDp9BtC,WlD1Fa;EkD2Fb,kBAAkB;EAClB,YlDm9BqC;EiBtiCjC,8BjBwiCgD;AHs8JtD;;AoB1+LM;EiCkEN;;IjCjEQ,gBAAgB;EpB++LtB;AACF;;AKr/LE;;;EgDwFE,WlDjGW;EkDkGX,qBAAqB;EACrB,UAAU;EACV,YlD48BmC;AHu9JvC;;AqDh6LA;EACE,OAAO;ArDm6LT;;AqD95LA;EACE,QAAQ;ArDi6LV;;AqD15LA;;EAEE,qBAAqB;EACrB,WlDq8BuC;EkDp8BvC,YlDo8BuC;EkDn8BvC,qCAAqC;ArD65LvC;;AqD35LA;EACE,sNnC1EgF;AlBw+LlF;;AqD55LA;EACE,uNnC7EgF;AlB4+LlF;;AqDt5LA;EACE,kBAAkB;EAClB,QAAQ;EACR,SAAS;EACT,OAAO;EACP,WAAW;EACX,oBAAa;EAAb,aAAa;EACb,qBAAuB;EAAvB,uBAAuB;EACvB,eAAe;EAEf,iBlD25BsC;EkD15BtC,gBlD05BsC;EkDz5BtC,gBAAgB;ArDw5LlB;;AqDp6LA;EAeI,uBAAuB;EACvB,kBAAc;EAAd,cAAc;EACd,WlDy5BqC;EkDx5BrC,WlDy5BoC;EkDx5BpC,iBlD05BoC;EkDz5BpC,gBlDy5BoC;EkDx5BpC,mBAAmB;EACnB,eAAe;EACf,sBlDhKW;EkDiKX,4BAA4B;EAE5B,kCAAiE;EACjE,qCAAoE;EACpE,WAAW;EjC5JT,6BjB+iC+C;AHsgKrD;;AoBjjMM;EiC4HN;IjC3HQ,gBAAgB;EpBqjMtB;AACF;;AqD37LA;EAiCI,UAAU;ArD85Ld;;AqDr5LA;EACE,kBAAkB;EAClB,UAA2C;EAC3C,YAAY;EACZ,SAA0C;EAC1C,WAAW;EACX,iBAAiB;EACjB,oBAAoB;EACpB,WlD3La;EkD4Lb,kBAAkB;ArDw5LpB;;AuDvlMA;EACE;IAAK,iCAAyB;IAAzB,yBAAyB;EvD2lM9B;AACF;;AuD7lMA;EACE;IAAK,iCAAyB;IAAzB,yBAAyB;EvD2lM9B;AACF;;AuDzlMA;EACE,qBAAqB;EACrB,WpDqkC0B;EoDpkC1B,YpDokC0B;EoDnkC1B,2BAA2B;EAC3B,iCAAgD;EAChD,+BAA+B;EAE/B,kBAAkB;EAClB,sDAA8C;EAA9C,8CAA8C;AvD2lMhD;;AuDxlMA;EACE,WpD8jC4B;EoD7jC5B,YpD6jC4B;EoD5jC5B,mBpD8jC4B;AH6hK9B;;AuDplMA;EACE;IACE,2BAAmB;IAAnB,mBAAmB;EvDulMrB;EuDrlMA;IACE,UAAU;IACV,uBAAe;IAAf,eAAe;EvDulMjB;AACF;;AuD9lMA;EACE;IACE,2BAAmB;IAAnB,mBAAmB;EvDulMrB;EuDrlMA;IACE,UAAU;IACV,uBAAe;IAAf,eAAe;EvDulMjB;AACF;;AuDplMA;EACE,qBAAqB;EACrB,WpDqiC0B;EoDpiC1B,YpDoiC0B;EoDniC1B,2BAA2B;EAC3B,8BAA8B;EAE9B,kBAAkB;EAClB,UAAU;EACV,oDAA4C;EAA5C,4CAA4C;AvDslM9C;;AuDnlMA;EACE,WpD8hC4B;EoD7hC5B,YpD6hC4B;AHyjK9B;;AwD1oMA;EAAqB,mCAAmC;AxD8oMxD;;AwD7oMA;EAAqB,8BAA8B;AxDipMnD;;AwDhpMA;EAAqB,iCAAiC;AxDopMtD;;AwDnpMA;EAAqB,iCAAiC;AxDupMtD;;AwDtpMA;EAAqB,sCAAsC;AxD0pM3D;;AwDzpMA;EAAqB,mCAAmC;AxD6pMxD;;AyD/pME;EACE,oCAAmC;AzDkqMvC;;AKxpME;;;EoDLI,oCAAgD;AzDmqMtD;;AyDzqME;EACE,oCAAmC;AzD4qMvC;;AKlqME;;;EoDLI,oCAAgD;AzD6qMtD;;AyDnrME;EACE,oCAAmC;AzDsrMvC;;AK5qME;;;EoDLI,oCAAgD;AzDurMtD;;AyD7rME;EACE,oCAAmC;AzDgsMvC;;AKtrME;;;EoDLI,oCAAgD;AzDisMtD;;AyDvsME;EACE,oCAAmC;AzD0sMvC;;AKhsME;;;EoDLI,oCAAgD;AzD2sMtD;;AyDjtME;EACE,oCAAmC;AzDotMvC;;AK1sME;;;EoDLI,oCAAgD;AzDqtMtD;;AyD3tME;EACE,oCAAmC;AzD8tMvC;;AKptME;;;EoDLI,oCAAgD;AzD+tMtD;;AyDruME;EACE,oCAAmC;AzDwuMvC;;AK9tME;;;EoDLI,oCAAgD;AzDyuMtD;;A0DxuMA;EACE,iCAAmC;A1D2uMrC;;A0DxuMA;EACE,wCAAwC;A1D2uM1C;;A2DtvMA;EAAkB,oCAAoD;A3D0vMtE;;A2DzvMA;EAAkB,wCAAwD;A3D6vM1E;;A2D5vMA;EAAkB,0CAA0D;A3DgwM5E;;A2D/vMA;EAAkB,2CAA2D;A3DmwM7E;;A2DlwMA;EAAkB,yCAAyD;A3DswM3E;;A2DpwMA;EAAmB,oBAAoB;A3DwwMvC;;A2DvwMA;EAAmB,wBAAwB;A3D2wM3C;;A2D1wMA;EAAmB,0BAA0B;A3D8wM7C;;A2D7wMA;EAAmB,2BAA2B;A3DixM9C;;A2DhxMA;EAAmB,yBAAyB;A3DoxM5C;;A2DjxME;EACE,gCAA+B;A3DoxMnC;;A2DrxME;EACE,gCAA+B;A3DwxMnC;;A2DzxME;EACE,gCAA+B;A3D4xMnC;;A2D7xME;EACE,gCAA+B;A3DgyMnC;;A2DjyME;EACE,gCAA+B;A3DoyMnC;;A2DryME;EACE,gCAA+B;A3DwyMnC;;A2DzyME;EACE,gCAA+B;A3D4yMnC;;A2D7yME;EACE,gCAA+B;A3DgzMnC;;A2D5yMA;EACE,6BAA+B;A3D+yMjC;;A2DxyMA;EACE,gCAA2C;A3D2yM7C;;A2DxyMA;EACE,iCAAwC;A3D2yM1C;;A2DxyMA;EACE,0CAAiD;EACjD,2CAAkD;A3D2yMpD;;A2DxyMA;EACE,2CAAkD;EAClD,8CAAqD;A3D2yMvD;;A2DxyMA;EACE,8CAAqD;EACrD,6CAAoD;A3D2yMtD;;A2DxyMA;EACE,0CAAiD;EACjD,6CAAoD;A3D2yMtD;;A2DxyMA;EACE,gCAA2C;A3D2yM7C;;A2DxyMA;EACE,6BAA6B;A3D2yM/B;;A2DxyMA;EACE,+BAAuC;A3D2yMzC;;A2DxyMA;EACE,2BAA2B;A3D2yM7B;;AsDn3ME;EACE,cAAc;EACd,WAAW;EACX,WAAW;AtDs3Mf;;A4D/2MM;EAAwB,wBAA0B;A5Dm3MxD;;A4Dn3MM;EAAwB,0BAA0B;A5Du3MxD;;A4Dv3MM;EAAwB,gCAA0B;A5D23MxD;;A4D33MM;EAAwB,yBAA0B;A5D+3MxD;;A4D/3MM;EAAwB,yBAA0B;A5Dm4MxD;;A4Dn4MM;EAAwB,6BAA0B;A5Du4MxD;;A4Dv4MM;EAAwB,8BAA0B;A5D24MxD;;A4D34MM;EAAwB,+BAA0B;EAA1B,wBAA0B;A5D+4MxD;;A4D/4MM;EAAwB,sCAA0B;EAA1B,+BAA0B;A5Dm5MxD;;Acl2MI;E8CjDE;IAAwB,wBAA0B;E5Dw5MtD;E4Dx5MI;IAAwB,0BAA0B;E5D25MtD;E4D35MI;IAAwB,gCAA0B;E5D85MtD;E4D95MI;IAAwB,yBAA0B;E5Di6MtD;E4Dj6MI;IAAwB,yBAA0B;E5Do6MtD;E4Dp6MI;IAAwB,6BAA0B;E5Du6MtD;E4Dv6MI;IAAwB,8BAA0B;E5D06MtD;E4D16MI;IAAwB,+BAA0B;IAA1B,wBAA0B;E5D66MtD;E4D76MI;IAAwB,sCAA0B;IAA1B,+BAA0B;E5Dg7MtD;AACF;;Ach4MI;E8CjDE;IAAwB,wBAA0B;E5Ds7MtD;E4Dt7MI;IAAwB,0BAA0B;E5Dy7MtD;E4Dz7MI;IAAwB,gCAA0B;E5D47MtD;E4D57MI;IAAwB,yBAA0B;E5D+7MtD;E4D/7MI;IAAwB,yBAA0B;E5Dk8MtD;E4Dl8MI;IAAwB,6BAA0B;E5Dq8MtD;E4Dr8MI;IAAwB,8BAA0B;E5Dw8MtD;E4Dx8MI;IAAwB,+BAA0B;IAA1B,wBAA0B;E5D28MtD;E4D38MI;IAAwB,sCAA0B;IAA1B,+BAA0B;E5D88MtD;AACF;;Ac95MI;E8CjDE;IAAwB,wBAA0B;E5Do9MtD;E4Dp9MI;IAAwB,0BAA0B;E5Du9MtD;E4Dv9MI;IAAwB,gCAA0B;E5D09MtD;E4D19MI;IAAwB,yBAA0B;E5D69MtD;E4D79MI;IAAwB,yBAA0B;E5Dg+MtD;E4Dh+MI;IAAwB,6BAA0B;E5Dm+MtD;E4Dn+MI;IAAwB,8BAA0B;E5Ds+MtD;E4Dt+MI;IAAwB,+BAA0B;IAA1B,wBAA0B;E5Dy+MtD;E4Dz+MI;IAAwB,sCAA0B;IAA1B,+BAA0B;E5D4+MtD;AACF;;Ac57MI;E8CjDE;IAAwB,wBAA0B;E5Dk/MtD;E4Dl/MI;IAAwB,0BAA0B;E5Dq/MtD;E4Dr/MI;IAAwB,gCAA0B;E5Dw/MtD;E4Dx/MI;IAAwB,yBAA0B;E5D2/MtD;E4D3/MI;IAAwB,yBAA0B;E5D8/MtD;E4D9/MI;IAAwB,6BAA0B;E5DigNtD;E4DjgNI;IAAwB,8BAA0B;E5DogNtD;E4DpgNI;IAAwB,+BAA0B;IAA1B,wBAA0B;E5DugNtD;E4DvgNI;IAAwB,sCAA0B;IAA1B,+BAA0B;E5D0gNtD;AACF;;A4DjgNA;EAEI;IAAqB,wBAA0B;E5DogNjD;E4DpgNE;IAAqB,0BAA0B;E5DugNjD;E4DvgNE;IAAqB,gCAA0B;E5D0gNjD;E4D1gNE;IAAqB,yBAA0B;E5D6gNjD;E4D7gNE;IAAqB,yBAA0B;E5DghNjD;E4DhhNE;IAAqB,6BAA0B;E5DmhNjD;E4DnhNE;IAAqB,8BAA0B;E5DshNjD;E4DthNE;IAAqB,+BAA0B;IAA1B,wBAA0B;E5DyhNjD;E4DzhNE;IAAqB,sCAA0B;IAA1B,+BAA0B;E5D4hNjD;AACF;;A6DljNA;EACE,kBAAkB;EAClB,cAAc;EACd,WAAW;EACX,UAAU;EACV,gBAAgB;A7DqjNlB;;A6D1jNA;EAQI,cAAc;EACd,WAAW;A7DsjNf;;A6D/jNA;;;;;EAiBI,kBAAkB;EAClB,MAAM;EACN,SAAS;EACT,OAAO;EACP,WAAW;EACX,YAAY;EACZ,SAAS;A7DsjNb;;A6D9iNE;EAEI,uBAA4F;A7DgjNlG;;A6DljNE;EAEI,mBAA4F;A7DojNlG;;A6DtjNE;EAEI,gBAA4F;A7DwjNlG;;A6D1jNE;EAEI,iBAA4F;A7D4jNlG;;A8DrlNI;EAAgC,kCAA8B;EAA9B,8BAA8B;A9DylNlE;;A8DxlNI;EAAgC,qCAAiC;EAAjC,iCAAiC;A9D4lNrE;;A8D3lNI;EAAgC,0CAAsC;EAAtC,sCAAsC;A9D+lN1E;;A8D9lNI;EAAgC,6CAAyC;EAAzC,yCAAyC;A9DkmN7E;;A8DhmNI;EAA8B,8BAA0B;EAA1B,0BAA0B;A9DomN5D;;A8DnmNI;EAA8B,gCAA4B;EAA5B,4BAA4B;A9DumN9D;;A8DtmNI;EAA8B,sCAAkC;EAAlC,kCAAkC;A9D0mNpE;;A8DzmNI;EAA8B,6BAAyB;EAAzB,yBAAyB;A9D6mN3D;;A8D5mNI;EAA8B,+BAAuB;EAAvB,uBAAuB;A9DgnNzD;;A8D/mNI;EAA8B,+BAAuB;EAAvB,uBAAuB;A9DmnNzD;;A8DlnNI;EAA8B,+BAAyB;EAAzB,yBAAyB;A9DsnN3D;;A8DrnNI;EAA8B,+BAAyB;EAAzB,yBAAyB;A9DynN3D;;A8DvnNI;EAAoC,+BAAsC;EAAtC,sCAAsC;A9D2nN9E;;A8D1nNI;EAAoC,6BAAoC;EAApC,oCAAoC;A9D8nN5E;;A8D7nNI;EAAoC,gCAAkC;EAAlC,kCAAkC;A9DioN1E;;A8DhoNI;EAAoC,iCAAyC;EAAzC,yCAAyC;A9DooNjF;;A8DnoNI;EAAoC,oCAAwC;EAAxC,wCAAwC;A9DuoNhF;;A8DroNI;EAAiC,gCAAkC;EAAlC,kCAAkC;A9DyoNvE;;A8DxoNI;EAAiC,8BAAgC;EAAhC,gCAAgC;A9D4oNrE;;A8D3oNI;EAAiC,iCAA8B;EAA9B,8BAA8B;A9D+oNnE;;A8D9oNI;EAAiC,mCAAgC;EAAhC,gCAAgC;A9DkpNrE;;A8DjpNI;EAAiC,kCAA+B;EAA/B,+BAA+B;A9DqpNpE;;A8DnpNI;EAAkC,oCAAoC;EAApC,oCAAoC;A9DupN1E;;A8DtpNI;EAAkC,kCAAkC;EAAlC,kCAAkC;A9D0pNxE;;A8DzpNI;EAAkC,qCAAgC;EAAhC,gCAAgC;A9D6pNtE;;A8D5pNI;EAAkC,sCAAuC;EAAvC,uCAAuC;A9DgqN7E;;A8D/pNI;EAAkC,yCAAsC;EAAtC,sCAAsC;A9DmqN5E;;A8DlqNI;EAAkC,sCAAiC;EAAjC,iCAAiC;A9DsqNvE;;A8DpqNI;EAAgC,oCAA2B;EAA3B,2BAA2B;A9DwqN/D;;A8DvqNI;EAAgC,qCAAiC;EAAjC,iCAAiC;A9D2qNrE;;A8D1qNI;EAAgC,mCAA+B;EAA/B,+BAA+B;A9D8qNnE;;A8D7qNI;EAAgC,sCAA6B;EAA7B,6BAA6B;A9DirNjE;;A8DhrNI;EAAgC,wCAA+B;EAA/B,+BAA+B;A9DorNnE;;A8DnrNI;EAAgC,uCAA8B;EAA9B,8BAA8B;A9DurNlE;;Ac3qNI;EgDlDA;IAAgC,kCAA8B;IAA9B,8BAA8B;E9DkuNhE;E8DjuNE;IAAgC,qCAAiC;IAAjC,iCAAiC;E9DouNnE;E8DnuNE;IAAgC,0CAAsC;IAAtC,sCAAsC;E9DsuNxE;E8DruNE;IAAgC,6CAAyC;IAAzC,yCAAyC;E9DwuN3E;E8DtuNE;IAA8B,8BAA0B;IAA1B,0BAA0B;E9DyuN1D;E8DxuNE;IAA8B,gCAA4B;IAA5B,4BAA4B;E9D2uN5D;E8D1uNE;IAA8B,sCAAkC;IAAlC,kCAAkC;E9D6uNlE;E8D5uNE;IAA8B,6BAAyB;IAAzB,yBAAyB;E9D+uNzD;E8D9uNE;IAA8B,+BAAuB;IAAvB,uBAAuB;E9DivNvD;E8DhvNE;IAA8B,+BAAuB;IAAvB,uBAAuB;E9DmvNvD;E8DlvNE;IAA8B,+BAAyB;IAAzB,yBAAyB;E9DqvNzD;E8DpvNE;IAA8B,+BAAyB;IAAzB,yBAAyB;E9DuvNzD;E8DrvNE;IAAoC,+BAAsC;IAAtC,sCAAsC;E9DwvN5E;E8DvvNE;IAAoC,6BAAoC;IAApC,oCAAoC;E9D0vN1E;E8DzvNE;IAAoC,gCAAkC;IAAlC,kCAAkC;E9D4vNxE;E8D3vNE;IAAoC,iCAAyC;IAAzC,yCAAyC;E9D8vN/E;E8D7vNE;IAAoC,oCAAwC;IAAxC,wCAAwC;E9DgwN9E;E8D9vNE;IAAiC,gCAAkC;IAAlC,kCAAkC;E9DiwNrE;E8DhwNE;IAAiC,8BAAgC;IAAhC,gCAAgC;E9DmwNnE;E8DlwNE;IAAiC,iCAA8B;IAA9B,8BAA8B;E9DqwNjE;E8DpwNE;IAAiC,mCAAgC;IAAhC,gCAAgC;E9DuwNnE;E8DtwNE;IAAiC,kCAA+B;IAA/B,+BAA+B;E9DywNlE;E8DvwNE;IAAkC,oCAAoC;IAApC,oCAAoC;E9D0wNxE;E8DzwNE;IAAkC,kCAAkC;IAAlC,kCAAkC;E9D4wNtE;E8D3wNE;IAAkC,qCAAgC;IAAhC,gCAAgC;E9D8wNpE;E8D7wNE;IAAkC,sCAAuC;IAAvC,uCAAuC;E9DgxN3E;E8D/wNE;IAAkC,yCAAsC;IAAtC,sCAAsC;E9DkxN1E;E8DjxNE;IAAkC,sCAAiC;IAAjC,iCAAiC;E9DoxNrE;E8DlxNE;IAAgC,oCAA2B;IAA3B,2BAA2B;E9DqxN7D;E8DpxNE;IAAgC,qCAAiC;IAAjC,iCAAiC;E9DuxNnE;E8DtxNE;IAAgC,mCAA+B;IAA/B,+BAA+B;E9DyxNjE;E8DxxNE;IAAgC,sCAA6B;IAA7B,6BAA6B;E9D2xN/D;E8D1xNE;IAAgC,wCAA+B;IAA/B,+BAA+B;E9D6xNjE;E8D5xNE;IAAgC,uCAA8B;IAA9B,8BAA8B;E9D+xNhE;AACF;;AcpxNI;EgDlDA;IAAgC,kCAA8B;IAA9B,8BAA8B;E9D20NhE;E8D10NE;IAAgC,qCAAiC;IAAjC,iCAAiC;E9D60NnE;E8D50NE;IAAgC,0CAAsC;IAAtC,sCAAsC;E9D+0NxE;E8D90NE;IAAgC,6CAAyC;IAAzC,yCAAyC;E9Di1N3E;E8D/0NE;IAA8B,8BAA0B;IAA1B,0BAA0B;E9Dk1N1D;E8Dj1NE;IAA8B,gCAA4B;IAA5B,4BAA4B;E9Do1N5D;E8Dn1NE;IAA8B,sCAAkC;IAAlC,kCAAkC;E9Ds1NlE;E8Dr1NE;IAA8B,6BAAyB;IAAzB,yBAAyB;E9Dw1NzD;E8Dv1NE;IAA8B,+BAAuB;IAAvB,uBAAuB;E9D01NvD;E8Dz1NE;IAA8B,+BAAuB;IAAvB,uBAAuB;E9D41NvD;E8D31NE;IAA8B,+BAAyB;IAAzB,yBAAyB;E9D81NzD;E8D71NE;IAA8B,+BAAyB;IAAzB,yBAAyB;E9Dg2NzD;E8D91NE;IAAoC,+BAAsC;IAAtC,sCAAsC;E9Di2N5E;E8Dh2NE;IAAoC,6BAAoC;IAApC,oCAAoC;E9Dm2N1E;E8Dl2NE;IAAoC,gCAAkC;IAAlC,kCAAkC;E9Dq2NxE;E8Dp2NE;IAAoC,iCAAyC;IAAzC,yCAAyC;E9Du2N/E;E8Dt2NE;IAAoC,oCAAwC;IAAxC,wCAAwC;E9Dy2N9E;E8Dv2NE;IAAiC,gCAAkC;IAAlC,kCAAkC;E9D02NrE;E8Dz2NE;IAAiC,8BAAgC;IAAhC,gCAAgC;E9D42NnE;E8D32NE;IAAiC,iCAA8B;IAA9B,8BAA8B;E9D82NjE;E8D72NE;IAAiC,mCAAgC;IAAhC,gCAAgC;E9Dg3NnE;E8D/2NE;IAAiC,kCAA+B;IAA/B,+BAA+B;E9Dk3NlE;E8Dh3NE;IAAkC,oCAAoC;IAApC,oCAAoC;E9Dm3NxE;E8Dl3NE;IAAkC,kCAAkC;IAAlC,kCAAkC;E9Dq3NtE;E8Dp3NE;IAAkC,qCAAgC;IAAhC,gCAAgC;E9Du3NpE;E8Dt3NE;IAAkC,sCAAuC;IAAvC,uCAAuC;E9Dy3N3E;E8Dx3NE;IAAkC,yCAAsC;IAAtC,sCAAsC;E9D23N1E;E8D13NE;IAAkC,sCAAiC;IAAjC,iCAAiC;E9D63NrE;E8D33NE;IAAgC,oCAA2B;IAA3B,2BAA2B;E9D83N7D;E8D73NE;IAAgC,qCAAiC;IAAjC,iCAAiC;E9Dg4NnE;E8D/3NE;IAAgC,mCAA+B;IAA/B,+BAA+B;E9Dk4NjE;E8Dj4NE;IAAgC,sCAA6B;IAA7B,6BAA6B;E9Do4N/D;E8Dn4NE;IAAgC,wCAA+B;IAA/B,+BAA+B;E9Ds4NjE;E8Dr4NE;IAAgC,uCAA8B;IAA9B,8BAA8B;E9Dw4NhE;AACF;;Ac73NI;EgDlDA;IAAgC,kCAA8B;IAA9B,8BAA8B;E9Do7NhE;E8Dn7NE;IAAgC,qCAAiC;IAAjC,iCAAiC;E9Ds7NnE;E8Dr7NE;IAAgC,0CAAsC;IAAtC,sCAAsC;E9Dw7NxE;E8Dv7NE;IAAgC,6CAAyC;IAAzC,yCAAyC;E9D07N3E;E8Dx7NE;IAA8B,8BAA0B;IAA1B,0BAA0B;E9D27N1D;E8D17NE;IAA8B,gCAA4B;IAA5B,4BAA4B;E9D67N5D;E8D57NE;IAA8B,sCAAkC;IAAlC,kCAAkC;E9D+7NlE;E8D97NE;IAA8B,6BAAyB;IAAzB,yBAAyB;E9Di8NzD;E8Dh8NE;IAA8B,+BAAuB;IAAvB,uBAAuB;E9Dm8NvD;E8Dl8NE;IAA8B,+BAAuB;IAAvB,uBAAuB;E9Dq8NvD;E8Dp8NE;IAA8B,+BAAyB;IAAzB,yBAAyB;E9Du8NzD;E8Dt8NE;IAA8B,+BAAyB;IAAzB,yBAAyB;E9Dy8NzD;E8Dv8NE;IAAoC,+BAAsC;IAAtC,sCAAsC;E9D08N5E;E8Dz8NE;IAAoC,6BAAoC;IAApC,oCAAoC;E9D48N1E;E8D38NE;IAAoC,gCAAkC;IAAlC,kCAAkC;E9D88NxE;E8D78NE;IAAoC,iCAAyC;IAAzC,yCAAyC;E9Dg9N/E;E8D/8NE;IAAoC,oCAAwC;IAAxC,wCAAwC;E9Dk9N9E;E8Dh9NE;IAAiC,gCAAkC;IAAlC,kCAAkC;E9Dm9NrE;E8Dl9NE;IAAiC,8BAAgC;IAAhC,gCAAgC;E9Dq9NnE;E8Dp9NE;IAAiC,iCAA8B;IAA9B,8BAA8B;E9Du9NjE;E8Dt9NE;IAAiC,mCAAgC;IAAhC,gCAAgC;E9Dy9NnE;E8Dx9NE;IAAiC,kCAA+B;IAA/B,+BAA+B;E9D29NlE;E8Dz9NE;IAAkC,oCAAoC;IAApC,oCAAoC;E9D49NxE;E8D39NE;IAAkC,kCAAkC;IAAlC,kCAAkC;E9D89NtE;E8D79NE;IAAkC,qCAAgC;IAAhC,gCAAgC;E9Dg+NpE;E8D/9NE;IAAkC,sCAAuC;IAAvC,uCAAuC;E9Dk+N3E;E8Dj+NE;IAAkC,yCAAsC;IAAtC,sCAAsC;E9Do+N1E;E8Dn+NE;IAAkC,sCAAiC;IAAjC,iCAAiC;E9Ds+NrE;E8Dp+NE;IAAgC,oCAA2B;IAA3B,2BAA2B;E9Du+N7D;E8Dt+NE;IAAgC,qCAAiC;IAAjC,iCAAiC;E9Dy+NnE;E8Dx+NE;IAAgC,mCAA+B;IAA/B,+BAA+B;E9D2+NjE;E8D1+NE;IAAgC,sCAA6B;IAA7B,6BAA6B;E9D6+N/D;E8D5+NE;IAAgC,wCAA+B;IAA/B,+BAA+B;E9D++NjE;E8D9+NE;IAAgC,uCAA8B;IAA9B,8BAA8B;E9Di/NhE;AACF;;Act+NI;EgDlDA;IAAgC,kCAA8B;IAA9B,8BAA8B;E9D6hOhE;E8D5hOE;IAAgC,qCAAiC;IAAjC,iCAAiC;E9D+hOnE;E8D9hOE;IAAgC,0CAAsC;IAAtC,sCAAsC;E9DiiOxE;E8DhiOE;IAAgC,6CAAyC;IAAzC,yCAAyC;E9DmiO3E;E8DjiOE;IAA8B,8BAA0B;IAA1B,0BAA0B;E9DoiO1D;E8DniOE;IAA8B,gCAA4B;IAA5B,4BAA4B;E9DsiO5D;E8DriOE;IAA8B,sCAAkC;IAAlC,kCAAkC;E9DwiOlE;E8DviOE;IAA8B,6BAAyB;IAAzB,yBAAyB;E9D0iOzD;E8DziOE;IAA8B,+BAAuB;IAAvB,uBAAuB;E9D4iOvD;E8D3iOE;IAA8B,+BAAuB;IAAvB,uBAAuB;E9D8iOvD;E8D7iOE;IAA8B,+BAAyB;IAAzB,yBAAyB;E9DgjOzD;E8D/iOE;IAA8B,+BAAyB;IAAzB,yBAAyB;E9DkjOzD;E8DhjOE;IAAoC,+BAAsC;IAAtC,sCAAsC;E9DmjO5E;E8DljOE;IAAoC,6BAAoC;IAApC,oCAAoC;E9DqjO1E;E8DpjOE;IAAoC,gCAAkC;IAAlC,kCAAkC;E9DujOxE;E8DtjOE;IAAoC,iCAAyC;IAAzC,yCAAyC;E9DyjO/E;E8DxjOE;IAAoC,oCAAwC;IAAxC,wCAAwC;E9D2jO9E;E8DzjOE;IAAiC,gCAAkC;IAAlC,kCAAkC;E9D4jOrE;E8D3jOE;IAAiC,8BAAgC;IAAhC,gCAAgC;E9D8jOnE;E8D7jOE;IAAiC,iCAA8B;IAA9B,8BAA8B;E9DgkOjE;E8D/jOE;IAAiC,mCAAgC;IAAhC,gCAAgC;E9DkkOnE;E8DjkOE;IAAiC,kCAA+B;IAA/B,+BAA+B;E9DokOlE;E8DlkOE;IAAkC,oCAAoC;IAApC,oCAAoC;E9DqkOxE;E8DpkOE;IAAkC,kCAAkC;IAAlC,kCAAkC;E9DukOtE;E8DtkOE;IAAkC,qCAAgC;IAAhC,gCAAgC;E9DykOpE;E8DxkOE;IAAkC,sCAAuC;IAAvC,uCAAuC;E9D2kO3E;E8D1kOE;IAAkC,yCAAsC;IAAtC,sCAAsC;E9D6kO1E;E8D5kOE;IAAkC,sCAAiC;IAAjC,iCAAiC;E9D+kOrE;E8D7kOE;IAAgC,oCAA2B;IAA3B,2BAA2B;E9DglO7D;E8D/kOE;IAAgC,qCAAiC;IAAjC,iCAAiC;E9DklOnE;E8DjlOE;IAAgC,mCAA+B;IAA/B,+BAA+B;E9DolOjE;E8DnlOE;IAAgC,sCAA6B;IAA7B,6BAA6B;E9DslO/D;E8DrlOE;IAAgC,wCAA+B;IAA/B,+BAA+B;E9DwlOjE;E8DvlOE;IAAgC,uCAA8B;IAA9B,8BAA8B;E9D0lOhE;AACF;;A+DroOI;EAAwB,sBAAsB;A/DyoOlD;;A+DxoOI;EAAwB,uBAAuB;A/D4oOnD;;A+D3oOI;EAAwB,sBAAsB;A/D+oOlD;;Ac3lOI;EiDtDA;IAAwB,sBAAsB;E/DspOhD;E+DrpOE;IAAwB,uBAAuB;E/DwpOjD;E+DvpOE;IAAwB,sBAAsB;E/D0pOhD;AACF;;AcvmOI;EiDtDA;IAAwB,sBAAsB;E/DkqOhD;E+DjqOE;IAAwB,uBAAuB;E/DoqOjD;E+DnqOE;IAAwB,sBAAsB;E/DsqOhD;AACF;;AcnnOI;EiDtDA;IAAwB,sBAAsB;E/D8qOhD;E+D7qOE;IAAwB,uBAAuB;E/DgrOjD;E+D/qOE;IAAwB,sBAAsB;E/DkrOhD;AACF;;Ac/nOI;EiDtDA;IAAwB,sBAAsB;E/D0rOhD;E+DzrOE;IAAwB,uBAAuB;E/D4rOjD;E+D3rOE;IAAwB,sBAAsB;E/D8rOhD;AACF;;AgEpsOE;EAAyB,mCAA8B;EAA9B,gCAA8B;EAA9B,+BAA8B;EAA9B,2BAA8B;AhEwsOzD;;AgExsOE;EAAyB,oCAA8B;EAA9B,iCAA8B;EAA9B,gCAA8B;EAA9B,4BAA8B;AhE4sOzD;;AgE5sOE;EAAyB,oCAA8B;EAA9B,iCAA8B;EAA9B,gCAA8B;EAA9B,4BAA8B;AhEgtOzD;;AiEhtOE;EAAsB,yBAA2B;AjEotOnD;;AiEptOE;EAAsB,2BAA2B;AjEwtOnD;;AkEvtOE;EAAyB,2BAA8B;AlE2tOzD;;AkE3tOE;EAAyB,6BAA8B;AlE+tOzD;;AkE/tOE;EAAyB,6BAA8B;AlEmuOzD;;AkEnuOE;EAAyB,0BAA8B;AlEuuOzD;;AkEvuOE;EAAyB,mCAA8B;EAA9B,2BAA8B;AlE2uOzD;;AkEtuOA;EACE,eAAe;EACf,MAAM;EACN,QAAQ;EACR,OAAO;EACP,a/DsqBsC;AHmkNxC;;AkEtuOA;EACE,eAAe;EACf,QAAQ;EACR,SAAS;EACT,OAAO;EACP,a/D8pBsC;AH2kNxC;;AkEruO8B;EAD9B;IAEI,wBAAgB;IAAhB,gBAAgB;IAChB,MAAM;IACN,a/DspBoC;EHmlNtC;AACF;;AmEnwOA;ECEE,kBAAkB;EAClB,UAAU;EACV,WAAW;EACX,UAAU;EACV,YAAY;EACZ,gBAAgB;EAChB,sBAAsB;EACtB,mBAAmB;EACnB,SAAS;ApEqwOX;;AoE3vOE;EAEE,gBAAgB;EAChB,WAAW;EACX,YAAY;EACZ,iBAAiB;EACjB,UAAU;EACV,mBAAmB;ApE6vOvB;;AqE1xOA;EAAa,8DAAqC;ArE8xOlD;;AqE7xOA;EAAU,wDAAkC;ArEiyO5C;;AqEhyOA;EAAa,uDAAqC;ArEoyOlD;;AqEnyOA;EAAe,2BAA2B;ArEuyO1C;;AsEtyOI;EAAuB,qBAA4B;AtE0yOvD;;AsE1yOI;EAAuB,qBAA4B;AtE8yOvD;;AsE9yOI;EAAuB,qBAA4B;AtEkzOvD;;AsElzOI;EAAuB,sBAA4B;AtEszOvD;;AsEtzOI;EAAuB,sBAA4B;AtE0zOvD;;AsE1zOI;EAAuB,sBAA4B;AtE8zOvD;;AsE9zOI;EAAuB,sBAA4B;AtEk0OvD;;AsEl0OI;EAAuB,sBAA4B;AtEs0OvD;;AsEt0OI;EAAuB,uBAA4B;AtE00OvD;;AsE10OI;EAAuB,uBAA4B;AtE80OvD;;AsE10OA;EAAU,0BAA0B;AtE80OpC;;AsE70OA;EAAU,2BAA2B;AtEi1OrC;;AsE70OA;EAAc,2BAA2B;AtEi1OzC;;AsEh1OA;EAAc,4BAA4B;AtEo1O1C;;AsEl1OA;EAAU,uBAAuB;AtEs1OjC;;AsEr1OA;EAAU,wBAAwB;AtEy1OlC;;AuEl2OQ;EAAgC,oBAA4B;AvEs2OpE;;AuEr2OQ;;EAEE,wBAAoC;AvEw2O9C;;AuEt2OQ;;EAEE,0BAAwC;AvEy2OlD;;AuEv2OQ;;EAEE,2BAA0C;AvE02OpD;;AuEx2OQ;;EAEE,yBAAsC;AvE22OhD;;AuE13OQ;EAAgC,0BAA4B;AvE83OpE;;AuE73OQ;;EAEE,8BAAoC;AvEg4O9C;;AuE93OQ;;EAEE,gCAAwC;AvEi4OlD;;AuE/3OQ;;EAEE,iCAA0C;AvEk4OpD;;AuEh4OQ;;EAEE,+BAAsC;AvEm4OhD;;AuEl5OQ;EAAgC,yBAA4B;AvEs5OpE;;AuEr5OQ;;EAEE,6BAAoC;AvEw5O9C;;AuEt5OQ;;EAEE,+BAAwC;AvEy5OlD;;AuEv5OQ;;EAEE,gCAA0C;AvE05OpD;;AuEx5OQ;;EAEE,8BAAsC;AvE25OhD;;AuE16OQ;EAAgC,uBAA4B;AvE86OpE;;AuE76OQ;;EAEE,2BAAoC;AvEg7O9C;;AuE96OQ;;EAEE,6BAAwC;AvEi7OlD;;AuE/6OQ;;EAEE,8BAA0C;AvEk7OpD;;AuEh7OQ;;EAEE,4BAAsC;AvEm7OhD;;AuEl8OQ;EAAgC,yBAA4B;AvEs8OpE;;AuEr8OQ;;EAEE,6BAAoC;AvEw8O9C;;AuEt8OQ;;EAEE,+BAAwC;AvEy8OlD;;AuEv8OQ;;EAEE,gCAA0C;AvE08OpD;;AuEx8OQ;;EAEE,8BAAsC;AvE28OhD;;AuE19OQ;EAAgC,uBAA4B;AvE89OpE;;AuE79OQ;;EAEE,2BAAoC;AvEg+O9C;;AuE99OQ;;EAEE,6BAAwC;AvEi+OlD;;AuE/9OQ;;EAEE,8BAA0C;AvEk+OpD;;AuEh+OQ;;EAEE,4BAAsC;AvEm+OhD;;AuEl/OQ;EAAgC,qBAA4B;AvEs/OpE;;AuEr/OQ;;EAEE,yBAAoC;AvEw/O9C;;AuEt/OQ;;EAEE,2BAAwC;AvEy/OlD;;AuEv/OQ;;EAEE,4BAA0C;AvE0/OpD;;AuEx/OQ;;EAEE,0BAAsC;AvE2/OhD;;AuE1gPQ;EAAgC,2BAA4B;AvE8gPpE;;AuE7gPQ;;EAEE,+BAAoC;AvEghP9C;;AuE9gPQ;;EAEE,iCAAwC;AvEihPlD;;AuE/gPQ;;EAEE,kCAA0C;AvEkhPpD;;AuEhhPQ;;EAEE,gCAAsC;AvEmhPhD;;AuEliPQ;EAAgC,0BAA4B;AvEsiPpE;;AuEriPQ;;EAEE,8BAAoC;AvEwiP9C;;AuEtiPQ;;EAEE,gCAAwC;AvEyiPlD;;AuEviPQ;;EAEE,iCAA0C;AvE0iPpD;;AuExiPQ;;EAEE,+BAAsC;AvE2iPhD;;AuE1jPQ;EAAgC,wBAA4B;AvE8jPpE;;AuE7jPQ;;EAEE,4BAAoC;AvEgkP9C;;AuE9jPQ;;EAEE,8BAAwC;AvEikPlD;;AuE/jPQ;;EAEE,+BAA0C;AvEkkPpD;;AuEhkPQ;;EAEE,6BAAsC;AvEmkPhD;;AuEllPQ;EAAgC,0BAA4B;AvEslPpE;;AuErlPQ;;EAEE,8BAAoC;AvEwlP9C;;AuEtlPQ;;EAEE,gCAAwC;AvEylPlD;;AuEvlPQ;;EAEE,iCAA0C;AvE0lPpD;;AuExlPQ;;EAEE,+BAAsC;AvE2lPhD;;AuE1mPQ;EAAgC,wBAA4B;AvE8mPpE;;AuE7mPQ;;EAEE,4BAAoC;AvEgnP9C;;AuE9mPQ;;EAEE,8BAAwC;AvEinPlD;;AuE/mPQ;;EAEE,+BAA0C;AvEknPpD;;AuEhnPQ;;EAEE,6BAAsC;AvEmnPhD;;AuE3mPQ;EAAwB,2BAA2B;AvE+mP3D;;AuE9mPQ;;EAEE,+BAA+B;AvEinPzC;;AuE/mPQ;;EAEE,iCAAiC;AvEknP3C;;AuEhnPQ;;EAEE,kCAAkC;AvEmnP5C;;AuEjnPQ;;EAEE,gCAAgC;AvEonP1C;;AuEnoPQ;EAAwB,0BAA2B;AvEuoP3D;;AuEtoPQ;;EAEE,8BAA+B;AvEyoPzC;;AuEvoPQ;;EAEE,gCAAiC;AvE0oP3C;;AuExoPQ;;EAEE,iCAAkC;AvE2oP5C;;AuEzoPQ;;EAEE,+BAAgC;AvE4oP1C;;AuE3pPQ;EAAwB,wBAA2B;AvE+pP3D;;AuE9pPQ;;EAEE,4BAA+B;AvEiqPzC;;AuE/pPQ;;EAEE,8BAAiC;AvEkqP3C;;AuEhqPQ;;EAEE,+BAAkC;AvEmqP5C;;AuEjqPQ;;EAEE,6BAAgC;AvEoqP1C;;AuEnrPQ;EAAwB,0BAA2B;AvEurP3D;;AuEtrPQ;;EAEE,8BAA+B;AvEyrPzC;;AuEvrPQ;;EAEE,gCAAiC;AvE0rP3C;;AuExrPQ;;EAEE,iCAAkC;AvE2rP5C;;AuEzrPQ;;EAEE,+BAAgC;AvE4rP1C;;AuE3sPQ;EAAwB,wBAA2B;AvE+sP3D;;AuE9sPQ;;EAEE,4BAA+B;AvEitPzC;;AuE/sPQ;;EAEE,8BAAiC;AvEktP3C;;AuEhtPQ;;EAEE,+BAAkC;AvEmtP5C;;AuEjtPQ;;EAEE,6BAAgC;AvEotP1C;;AuE9sPI;EAAmB,uBAAuB;AvEktP9C;;AuEjtPI;;EAEE,2BAA2B;AvEotPjC;;AuEltPI;;EAEE,6BAA6B;AvEqtPnC;;AuEntPI;;EAEE,8BAA8B;AvEstPpC;;AuEptPI;;EAEE,4BAA4B;AvEutPlC;;AchuPI;EyDlDI;IAAgC,oBAA4B;EvEuxPlE;EuEtxPM;;IAEE,wBAAoC;EvEwxP5C;EuEtxPM;;IAEE,0BAAwC;EvEwxPhD;EuEtxPM;;IAEE,2BAA0C;EvEwxPlD;EuEtxPM;;IAEE,yBAAsC;EvEwxP9C;EuEvyPM;IAAgC,0BAA4B;EvE0yPlE;EuEzyPM;;IAEE,8BAAoC;EvE2yP5C;EuEzyPM;;IAEE,gCAAwC;EvE2yPhD;EuEzyPM;;IAEE,iCAA0C;EvE2yPlD;EuEzyPM;;IAEE,+BAAsC;EvE2yP9C;EuE1zPM;IAAgC,yBAA4B;EvE6zPlE;EuE5zPM;;IAEE,6BAAoC;EvE8zP5C;EuE5zPM;;IAEE,+BAAwC;EvE8zPhD;EuE5zPM;;IAEE,gCAA0C;EvE8zPlD;EuE5zPM;;IAEE,8BAAsC;EvE8zP9C;EuE70PM;IAAgC,uBAA4B;EvEg1PlE;EuE/0PM;;IAEE,2BAAoC;EvEi1P5C;EuE/0PM;;IAEE,6BAAwC;EvEi1PhD;EuE/0PM;;IAEE,8BAA0C;EvEi1PlD;EuE/0PM;;IAEE,4BAAsC;EvEi1P9C;EuEh2PM;IAAgC,yBAA4B;EvEm2PlE;EuEl2PM;;IAEE,6BAAoC;EvEo2P5C;EuEl2PM;;IAEE,+BAAwC;EvEo2PhD;EuEl2PM;;IAEE,gCAA0C;EvEo2PlD;EuEl2PM;;IAEE,8BAAsC;EvEo2P9C;EuEn3PM;IAAgC,uBAA4B;EvEs3PlE;EuEr3PM;;IAEE,2BAAoC;EvEu3P5C;EuEr3PM;;IAEE,6BAAwC;EvEu3PhD;EuEr3PM;;IAEE,8BAA0C;EvEu3PlD;EuEr3PM;;IAEE,4BAAsC;EvEu3P9C;EuEt4PM;IAAgC,qBAA4B;EvEy4PlE;EuEx4PM;;IAEE,yBAAoC;EvE04P5C;EuEx4PM;;IAEE,2BAAwC;EvE04PhD;EuEx4PM;;IAEE,4BAA0C;EvE04PlD;EuEx4PM;;IAEE,0BAAsC;EvE04P9C;EuEz5PM;IAAgC,2BAA4B;EvE45PlE;EuE35PM;;IAEE,+BAAoC;EvE65P5C;EuE35PM;;IAEE,iCAAwC;EvE65PhD;EuE35PM;;IAEE,kCAA0C;EvE65PlD;EuE35PM;;IAEE,gCAAsC;EvE65P9C;EuE56PM;IAAgC,0BAA4B;EvE+6PlE;EuE96PM;;IAEE,8BAAoC;EvEg7P5C;EuE96PM;;IAEE,gCAAwC;EvEg7PhD;EuE96PM;;IAEE,iCAA0C;EvEg7PlD;EuE96PM;;IAEE,+BAAsC;EvEg7P9C;EuE/7PM;IAAgC,wBAA4B;EvEk8PlE;EuEj8PM;;IAEE,4BAAoC;EvEm8P5C;EuEj8PM;;IAEE,8BAAwC;EvEm8PhD;EuEj8PM;;IAEE,+BAA0C;EvEm8PlD;EuEj8PM;;IAEE,6BAAsC;EvEm8P9C;EuEl9PM;IAAgC,0BAA4B;EvEq9PlE;EuEp9PM;;IAEE,8BAAoC;EvEs9P5C;EuEp9PM;;IAEE,gCAAwC;EvEs9PhD;EuEp9PM;;IAEE,iCAA0C;EvEs9PlD;EuEp9PM;;IAEE,+BAAsC;EvEs9P9C;EuEr+PM;IAAgC,wBAA4B;EvEw+PlE;EuEv+PM;;IAEE,4BAAoC;EvEy+P5C;EuEv+PM;;IAEE,8BAAwC;EvEy+PhD;EuEv+PM;;IAEE,+BAA0C;EvEy+PlD;EuEv+PM;;IAEE,6BAAsC;EvEy+P9C;EuEj+PM;IAAwB,2BAA2B;EvEo+PzD;EuEn+PM;;IAEE,+BAA+B;EvEq+PvC;EuEn+PM;;IAEE,iCAAiC;EvEq+PzC;EuEn+PM;;IAEE,kCAAkC;EvEq+P1C;EuEn+PM;;IAEE,gCAAgC;EvEq+PxC;EuEp/PM;IAAwB,0BAA2B;EvEu/PzD;EuEt/PM;;IAEE,8BAA+B;EvEw/PvC;EuEt/PM;;IAEE,gCAAiC;EvEw/PzC;EuEt/PM;;IAEE,iCAAkC;EvEw/P1C;EuEt/PM;;IAEE,+BAAgC;EvEw/PxC;EuEvgQM;IAAwB,wBAA2B;EvE0gQzD;EuEzgQM;;IAEE,4BAA+B;EvE2gQvC;EuEzgQM;;IAEE,8BAAiC;EvE2gQzC;EuEzgQM;;IAEE,+BAAkC;EvE2gQ1C;EuEzgQM;;IAEE,6BAAgC;EvE2gQxC;EuE1hQM;IAAwB,0BAA2B;EvE6hQzD;EuE5hQM;;IAEE,8BAA+B;EvE8hQvC;EuE5hQM;;IAEE,gCAAiC;EvE8hQzC;EuE5hQM;;IAEE,iCAAkC;EvE8hQ1C;EuE5hQM;;IAEE,+BAAgC;EvE8hQxC;EuE7iQM;IAAwB,wBAA2B;EvEgjQzD;EuE/iQM;;IAEE,4BAA+B;EvEijQvC;EuE/iQM;;IAEE,8BAAiC;EvEijQzC;EuE/iQM;;IAEE,+BAAkC;EvEijQ1C;EuE/iQM;;IAEE,6BAAgC;EvEijQxC;EuE3iQE;IAAmB,uBAAuB;EvE8iQ5C;EuE7iQE;;IAEE,2BAA2B;EvE+iQ/B;EuE7iQE;;IAEE,6BAA6B;EvE+iQjC;EuE7iQE;;IAEE,8BAA8B;EvE+iQlC;EuE7iQE;;IAEE,4BAA4B;EvE+iQhC;AACF;;AczjQI;EyDlDI;IAAgC,oBAA4B;EvEgnQlE;EuE/mQM;;IAEE,wBAAoC;EvEinQ5C;EuE/mQM;;IAEE,0BAAwC;EvEinQhD;EuE/mQM;;IAEE,2BAA0C;EvEinQlD;EuE/mQM;;IAEE,yBAAsC;EvEinQ9C;EuEhoQM;IAAgC,0BAA4B;EvEmoQlE;EuEloQM;;IAEE,8BAAoC;EvEooQ5C;EuEloQM;;IAEE,gCAAwC;EvEooQhD;EuEloQM;;IAEE,iCAA0C;EvEooQlD;EuEloQM;;IAEE,+BAAsC;EvEooQ9C;EuEnpQM;IAAgC,yBAA4B;EvEspQlE;EuErpQM;;IAEE,6BAAoC;EvEupQ5C;EuErpQM;;IAEE,+BAAwC;EvEupQhD;EuErpQM;;IAEE,gCAA0C;EvEupQlD;EuErpQM;;IAEE,8BAAsC;EvEupQ9C;EuEtqQM;IAAgC,uBAA4B;EvEyqQlE;EuExqQM;;IAEE,2BAAoC;EvE0qQ5C;EuExqQM;;IAEE,6BAAwC;EvE0qQhD;EuExqQM;;IAEE,8BAA0C;EvE0qQlD;EuExqQM;;IAEE,4BAAsC;EvE0qQ9C;EuEzrQM;IAAgC,yBAA4B;EvE4rQlE;EuE3rQM;;IAEE,6BAAoC;EvE6rQ5C;EuE3rQM;;IAEE,+BAAwC;EvE6rQhD;EuE3rQM;;IAEE,gCAA0C;EvE6rQlD;EuE3rQM;;IAEE,8BAAsC;EvE6rQ9C;EuE5sQM;IAAgC,uBAA4B;EvE+sQlE;EuE9sQM;;IAEE,2BAAoC;EvEgtQ5C;EuE9sQM;;IAEE,6BAAwC;EvEgtQhD;EuE9sQM;;IAEE,8BAA0C;EvEgtQlD;EuE9sQM;;IAEE,4BAAsC;EvEgtQ9C;EuE/tQM;IAAgC,qBAA4B;EvEkuQlE;EuEjuQM;;IAEE,yBAAoC;EvEmuQ5C;EuEjuQM;;IAEE,2BAAwC;EvEmuQhD;EuEjuQM;;IAEE,4BAA0C;EvEmuQlD;EuEjuQM;;IAEE,0BAAsC;EvEmuQ9C;EuElvQM;IAAgC,2BAA4B;EvEqvQlE;EuEpvQM;;IAEE,+BAAoC;EvEsvQ5C;EuEpvQM;;IAEE,iCAAwC;EvEsvQhD;EuEpvQM;;IAEE,kCAA0C;EvEsvQlD;EuEpvQM;;IAEE,gCAAsC;EvEsvQ9C;EuErwQM;IAAgC,0BAA4B;EvEwwQlE;EuEvwQM;;IAEE,8BAAoC;EvEywQ5C;EuEvwQM;;IAEE,gCAAwC;EvEywQhD;EuEvwQM;;IAEE,iCAA0C;EvEywQlD;EuEvwQM;;IAEE,+BAAsC;EvEywQ9C;EuExxQM;IAAgC,wBAA4B;EvE2xQlE;EuE1xQM;;IAEE,4BAAoC;EvE4xQ5C;EuE1xQM;;IAEE,8BAAwC;EvE4xQhD;EuE1xQM;;IAEE,+BAA0C;EvE4xQlD;EuE1xQM;;IAEE,6BAAsC;EvE4xQ9C;EuE3yQM;IAAgC,0BAA4B;EvE8yQlE;EuE7yQM;;IAEE,8BAAoC;EvE+yQ5C;EuE7yQM;;IAEE,gCAAwC;EvE+yQhD;EuE7yQM;;IAEE,iCAA0C;EvE+yQlD;EuE7yQM;;IAEE,+BAAsC;EvE+yQ9C;EuE9zQM;IAAgC,wBAA4B;EvEi0QlE;EuEh0QM;;IAEE,4BAAoC;EvEk0Q5C;EuEh0QM;;IAEE,8BAAwC;EvEk0QhD;EuEh0QM;;IAEE,+BAA0C;EvEk0QlD;EuEh0QM;;IAEE,6BAAsC;EvEk0Q9C;EuE1zQM;IAAwB,2BAA2B;EvE6zQzD;EuE5zQM;;IAEE,+BAA+B;EvE8zQvC;EuE5zQM;;IAEE,iCAAiC;EvE8zQzC;EuE5zQM;;IAEE,kCAAkC;EvE8zQ1C;EuE5zQM;;IAEE,gCAAgC;EvE8zQxC;EuE70QM;IAAwB,0BAA2B;EvEg1QzD;EuE/0QM;;IAEE,8BAA+B;EvEi1QvC;EuE/0QM;;IAEE,gCAAiC;EvEi1QzC;EuE/0QM;;IAEE,iCAAkC;EvEi1Q1C;EuE/0QM;;IAEE,+BAAgC;EvEi1QxC;EuEh2QM;IAAwB,wBAA2B;EvEm2QzD;EuEl2QM;;IAEE,4BAA+B;EvEo2QvC;EuEl2QM;;IAEE,8BAAiC;EvEo2QzC;EuEl2QM;;IAEE,+BAAkC;EvEo2Q1C;EuEl2QM;;IAEE,6BAAgC;EvEo2QxC;EuEn3QM;IAAwB,0BAA2B;EvEs3QzD;EuEr3QM;;IAEE,8BAA+B;EvEu3QvC;EuEr3QM;;IAEE,gCAAiC;EvEu3QzC;EuEr3QM;;IAEE,iCAAkC;EvEu3Q1C;EuEr3QM;;IAEE,+BAAgC;EvEu3QxC;EuEt4QM;IAAwB,wBAA2B;EvEy4QzD;EuEx4QM;;IAEE,4BAA+B;EvE04QvC;EuEx4QM;;IAEE,8BAAiC;EvE04QzC;EuEx4QM;;IAEE,+BAAkC;EvE04Q1C;EuEx4QM;;IAEE,6BAAgC;EvE04QxC;EuEp4QE;IAAmB,uBAAuB;EvEu4Q5C;EuEt4QE;;IAEE,2BAA2B;EvEw4Q/B;EuEt4QE;;IAEE,6BAA6B;EvEw4QjC;EuEt4QE;;IAEE,8BAA8B;EvEw4QlC;EuEt4QE;;IAEE,4BAA4B;EvEw4QhC;AACF;;Acl5QI;EyDlDI;IAAgC,oBAA4B;EvEy8QlE;EuEx8QM;;IAEE,wBAAoC;EvE08Q5C;EuEx8QM;;IAEE,0BAAwC;EvE08QhD;EuEx8QM;;IAEE,2BAA0C;EvE08QlD;EuEx8QM;;IAEE,yBAAsC;EvE08Q9C;EuEz9QM;IAAgC,0BAA4B;EvE49QlE;EuE39QM;;IAEE,8BAAoC;EvE69Q5C;EuE39QM;;IAEE,gCAAwC;EvE69QhD;EuE39QM;;IAEE,iCAA0C;EvE69QlD;EuE39QM;;IAEE,+BAAsC;EvE69Q9C;EuE5+QM;IAAgC,yBAA4B;EvE++QlE;EuE9+QM;;IAEE,6BAAoC;EvEg/Q5C;EuE9+QM;;IAEE,+BAAwC;EvEg/QhD;EuE9+QM;;IAEE,gCAA0C;EvEg/QlD;EuE9+QM;;IAEE,8BAAsC;EvEg/Q9C;EuE//QM;IAAgC,uBAA4B;EvEkgRlE;EuEjgRM;;IAEE,2BAAoC;EvEmgR5C;EuEjgRM;;IAEE,6BAAwC;EvEmgRhD;EuEjgRM;;IAEE,8BAA0C;EvEmgRlD;EuEjgRM;;IAEE,4BAAsC;EvEmgR9C;EuElhRM;IAAgC,yBAA4B;EvEqhRlE;EuEphRM;;IAEE,6BAAoC;EvEshR5C;EuEphRM;;IAEE,+BAAwC;EvEshRhD;EuEphRM;;IAEE,gCAA0C;EvEshRlD;EuEphRM;;IAEE,8BAAsC;EvEshR9C;EuEriRM;IAAgC,uBAA4B;EvEwiRlE;EuEviRM;;IAEE,2BAAoC;EvEyiR5C;EuEviRM;;IAEE,6BAAwC;EvEyiRhD;EuEviRM;;IAEE,8BAA0C;EvEyiRlD;EuEviRM;;IAEE,4BAAsC;EvEyiR9C;EuExjRM;IAAgC,qBAA4B;EvE2jRlE;EuE1jRM;;IAEE,yBAAoC;EvE4jR5C;EuE1jRM;;IAEE,2BAAwC;EvE4jRhD;EuE1jRM;;IAEE,4BAA0C;EvE4jRlD;EuE1jRM;;IAEE,0BAAsC;EvE4jR9C;EuE3kRM;IAAgC,2BAA4B;EvE8kRlE;EuE7kRM;;IAEE,+BAAoC;EvE+kR5C;EuE7kRM;;IAEE,iCAAwC;EvE+kRhD;EuE7kRM;;IAEE,kCAA0C;EvE+kRlD;EuE7kRM;;IAEE,gCAAsC;EvE+kR9C;EuE9lRM;IAAgC,0BAA4B;EvEimRlE;EuEhmRM;;IAEE,8BAAoC;EvEkmR5C;EuEhmRM;;IAEE,gCAAwC;EvEkmRhD;EuEhmRM;;IAEE,iCAA0C;EvEkmRlD;EuEhmRM;;IAEE,+BAAsC;EvEkmR9C;EuEjnRM;IAAgC,wBAA4B;EvEonRlE;EuEnnRM;;IAEE,4BAAoC;EvEqnR5C;EuEnnRM;;IAEE,8BAAwC;EvEqnRhD;EuEnnRM;;IAEE,+BAA0C;EvEqnRlD;EuEnnRM;;IAEE,6BAAsC;EvEqnR9C;EuEpoRM;IAAgC,0BAA4B;EvEuoRlE;EuEtoRM;;IAEE,8BAAoC;EvEwoR5C;EuEtoRM;;IAEE,gCAAwC;EvEwoRhD;EuEtoRM;;IAEE,iCAA0C;EvEwoRlD;EuEtoRM;;IAEE,+BAAsC;EvEwoR9C;EuEvpRM;IAAgC,wBAA4B;EvE0pRlE;EuEzpRM;;IAEE,4BAAoC;EvE2pR5C;EuEzpRM;;IAEE,8BAAwC;EvE2pRhD;EuEzpRM;;IAEE,+BAA0C;EvE2pRlD;EuEzpRM;;IAEE,6BAAsC;EvE2pR9C;EuEnpRM;IAAwB,2BAA2B;EvEspRzD;EuErpRM;;IAEE,+BAA+B;EvEupRvC;EuErpRM;;IAEE,iCAAiC;EvEupRzC;EuErpRM;;IAEE,kCAAkC;EvEupR1C;EuErpRM;;IAEE,gCAAgC;EvEupRxC;EuEtqRM;IAAwB,0BAA2B;EvEyqRzD;EuExqRM;;IAEE,8BAA+B;EvE0qRvC;EuExqRM;;IAEE,gCAAiC;EvE0qRzC;EuExqRM;;IAEE,iCAAkC;EvE0qR1C;EuExqRM;;IAEE,+BAAgC;EvE0qRxC;EuEzrRM;IAAwB,wBAA2B;EvE4rRzD;EuE3rRM;;IAEE,4BAA+B;EvE6rRvC;EuE3rRM;;IAEE,8BAAiC;EvE6rRzC;EuE3rRM;;IAEE,+BAAkC;EvE6rR1C;EuE3rRM;;IAEE,6BAAgC;EvE6rRxC;EuE5sRM;IAAwB,0BAA2B;EvE+sRzD;EuE9sRM;;IAEE,8BAA+B;EvEgtRvC;EuE9sRM;;IAEE,gCAAiC;EvEgtRzC;EuE9sRM;;IAEE,iCAAkC;EvEgtR1C;EuE9sRM;;IAEE,+BAAgC;EvEgtRxC;EuE/tRM;IAAwB,wBAA2B;EvEkuRzD;EuEjuRM;;IAEE,4BAA+B;EvEmuRvC;EuEjuRM;;IAEE,8BAAiC;EvEmuRzC;EuEjuRM;;IAEE,+BAAkC;EvEmuR1C;EuEjuRM;;IAEE,6BAAgC;EvEmuRxC;EuE7tRE;IAAmB,uBAAuB;EvEguR5C;EuE/tRE;;IAEE,2BAA2B;EvEiuR/B;EuE/tRE;;IAEE,6BAA6B;EvEiuRjC;EuE/tRE;;IAEE,8BAA8B;EvEiuRlC;EuE/tRE;;IAEE,4BAA4B;EvEiuRhC;AACF;;Ac3uRI;EyDlDI;IAAgC,oBAA4B;EvEkyRlE;EuEjyRM;;IAEE,wBAAoC;EvEmyR5C;EuEjyRM;;IAEE,0BAAwC;EvEmyRhD;EuEjyRM;;IAEE,2BAA0C;EvEmyRlD;EuEjyRM;;IAEE,yBAAsC;EvEmyR9C;EuElzRM;IAAgC,0BAA4B;EvEqzRlE;EuEpzRM;;IAEE,8BAAoC;EvEszR5C;EuEpzRM;;IAEE,gCAAwC;EvEszRhD;EuEpzRM;;IAEE,iCAA0C;EvEszRlD;EuEpzRM;;IAEE,+BAAsC;EvEszR9C;EuEr0RM;IAAgC,yBAA4B;EvEw0RlE;EuEv0RM;;IAEE,6BAAoC;EvEy0R5C;EuEv0RM;;IAEE,+BAAwC;EvEy0RhD;EuEv0RM;;IAEE,gCAA0C;EvEy0RlD;EuEv0RM;;IAEE,8BAAsC;EvEy0R9C;EuEx1RM;IAAgC,uBAA4B;EvE21RlE;EuE11RM;;IAEE,2BAAoC;EvE41R5C;EuE11RM;;IAEE,6BAAwC;EvE41RhD;EuE11RM;;IAEE,8BAA0C;EvE41RlD;EuE11RM;;IAEE,4BAAsC;EvE41R9C;EuE32RM;IAAgC,yBAA4B;EvE82RlE;EuE72RM;;IAEE,6BAAoC;EvE+2R5C;EuE72RM;;IAEE,+BAAwC;EvE+2RhD;EuE72RM;;IAEE,gCAA0C;EvE+2RlD;EuE72RM;;IAEE,8BAAsC;EvE+2R9C;EuE93RM;IAAgC,uBAA4B;EvEi4RlE;EuEh4RM;;IAEE,2BAAoC;EvEk4R5C;EuEh4RM;;IAEE,6BAAwC;EvEk4RhD;EuEh4RM;;IAEE,8BAA0C;EvEk4RlD;EuEh4RM;;IAEE,4BAAsC;EvEk4R9C;EuEj5RM;IAAgC,qBAA4B;EvEo5RlE;EuEn5RM;;IAEE,yBAAoC;EvEq5R5C;EuEn5RM;;IAEE,2BAAwC;EvEq5RhD;EuEn5RM;;IAEE,4BAA0C;EvEq5RlD;EuEn5RM;;IAEE,0BAAsC;EvEq5R9C;EuEp6RM;IAAgC,2BAA4B;EvEu6RlE;EuEt6RM;;IAEE,+BAAoC;EvEw6R5C;EuEt6RM;;IAEE,iCAAwC;EvEw6RhD;EuEt6RM;;IAEE,kCAA0C;EvEw6RlD;EuEt6RM;;IAEE,gCAAsC;EvEw6R9C;EuEv7RM;IAAgC,0BAA4B;EvE07RlE;EuEz7RM;;IAEE,8BAAoC;EvE27R5C;EuEz7RM;;IAEE,gCAAwC;EvE27RhD;EuEz7RM;;IAEE,iCAA0C;EvE27RlD;EuEz7RM;;IAEE,+BAAsC;EvE27R9C;EuE18RM;IAAgC,wBAA4B;EvE68RlE;EuE58RM;;IAEE,4BAAoC;EvE88R5C;EuE58RM;;IAEE,8BAAwC;EvE88RhD;EuE58RM;;IAEE,+BAA0C;EvE88RlD;EuE58RM;;IAEE,6BAAsC;EvE88R9C;EuE79RM;IAAgC,0BAA4B;EvEg+RlE;EuE/9RM;;IAEE,8BAAoC;EvEi+R5C;EuE/9RM;;IAEE,gCAAwC;EvEi+RhD;EuE/9RM;;IAEE,iCAA0C;EvEi+RlD;EuE/9RM;;IAEE,+BAAsC;EvEi+R9C;EuEh/RM;IAAgC,wBAA4B;EvEm/RlE;EuEl/RM;;IAEE,4BAAoC;EvEo/R5C;EuEl/RM;;IAEE,8BAAwC;EvEo/RhD;EuEl/RM;;IAEE,+BAA0C;EvEo/RlD;EuEl/RM;;IAEE,6BAAsC;EvEo/R9C;EuE5+RM;IAAwB,2BAA2B;EvE++RzD;EuE9+RM;;IAEE,+BAA+B;EvEg/RvC;EuE9+RM;;IAEE,iCAAiC;EvEg/RzC;EuE9+RM;;IAEE,kCAAkC;EvEg/R1C;EuE9+RM;;IAEE,gCAAgC;EvEg/RxC;EuE//RM;IAAwB,0BAA2B;EvEkgSzD;EuEjgSM;;IAEE,8BAA+B;EvEmgSvC;EuEjgSM;;IAEE,gCAAiC;EvEmgSzC;EuEjgSM;;IAEE,iCAAkC;EvEmgS1C;EuEjgSM;;IAEE,+BAAgC;EvEmgSxC;EuElhSM;IAAwB,wBAA2B;EvEqhSzD;EuEphSM;;IAEE,4BAA+B;EvEshSvC;EuEphSM;;IAEE,8BAAiC;EvEshSzC;EuEphSM;;IAEE,+BAAkC;EvEshS1C;EuEphSM;;IAEE,6BAAgC;EvEshSxC;EuEriSM;IAAwB,0BAA2B;EvEwiSzD;EuEviSM;;IAEE,8BAA+B;EvEyiSvC;EuEviSM;;IAEE,gCAAiC;EvEyiSzC;EuEviSM;;IAEE,iCAAkC;EvEyiS1C;EuEviSM;;IAEE,+BAAgC;EvEyiSxC;EuExjSM;IAAwB,wBAA2B;EvE2jSzD;EuE1jSM;;IAEE,4BAA+B;EvE4jSvC;EuE1jSM;;IAEE,8BAAiC;EvE4jSzC;EuE1jSM;;IAEE,+BAAkC;EvE4jS1C;EuE1jSM;;IAEE,6BAAgC;EvE4jSxC;EuEtjSE;IAAmB,uBAAuB;EvEyjS5C;EuExjSE;;IAEE,2BAA2B;EvE0jS/B;EuExjSE;;IAEE,6BAA6B;EvE0jSjC;EuExjSE;;IAEE,8BAA8B;EvE0jSlC;EuExjSE;;IAEE,4BAA4B;EvE0jShC;AACF;;AwE5nSA;EAEI,kBAAkB;EAClB,MAAM;EACN,QAAQ;EACR,SAAS;EACT,OAAO;EACP,UAAU;EAEV,oBAAoB;EACpB,WAAW;EAEX,kCAAkC;AxE4nStC;;AyEtoSA;EAAkB,4GAA8C;AzE0oShE;;AyEtoSA;EAAiB,8BAA8B;AzE0oS/C;;AyEzoSA;EAAiB,8BAA8B;AzE6oS/C;;AyE5oSA;EAAiB,8BAA8B;AzEgpS/C;;AyE/oSA;ECTE,gBAAgB;EAChB,uBAAuB;EACvB,mBAAmB;A1E4pSrB;;AyE7oSI;EAAwB,2BAA2B;AzEipSvD;;AyEhpSI;EAAwB,4BAA4B;AzEopSxD;;AyEnpSI;EAAwB,6BAA6B;AzEupSzD;;AclnSI;E2DvCA;IAAwB,2BAA2B;EzE8pSrD;EyE7pSE;IAAwB,4BAA4B;EzEgqStD;EyE/pSE;IAAwB,6BAA6B;EzEkqSvD;AACF;;Ac9nSI;E2DvCA;IAAwB,2BAA2B;EzE0qSrD;EyEzqSE;IAAwB,4BAA4B;EzE4qStD;EyE3qSE;IAAwB,6BAA6B;EzE8qSvD;AACF;;Ac1oSI;E2DvCA;IAAwB,2BAA2B;EzEsrSrD;EyErrSE;IAAwB,4BAA4B;EzEwrStD;EyEvrSE;IAAwB,6BAA6B;EzE0rSvD;AACF;;ActpSI;E2DvCA;IAAwB,2BAA2B;EzEksSrD;EyEjsSE;IAAwB,4BAA4B;EzEosStD;EyEnsSE;IAAwB,6BAA6B;EzEssSvD;AACF;;AyEjsSA;EAAmB,oCAAoC;AzEqsSvD;;AyEpsSA;EAAmB,oCAAoC;AzEwsSvD;;AyEvsSA;EAAmB,qCAAqC;AzE2sSxD;;AyEvsSA;EAAuB,2BAA0C;AzE2sSjE;;AyE1sSA;EAAuB,+BAA4C;AzE8sSnE;;AyE7sSA;EAAuB,2BAA2C;AzEitSlE;;AyEhtSA;EAAuB,2BAAyC;AzEotShE;;AyEntSA;EAAuB,8BAA2C;AzEutSlE;;AyEttSA;EAAuB,6BAA6B;AzE0tSpD;;AyEttSA;EAAc,sBAAwB;AzE0tStC;;A2EjwSE;EACE,yBAAwB;A3EowS5B;;AK1vSE;EsELM,yBAA0E;A3EmwSlF;;A2EzwSE;EACE,yBAAwB;A3E4wS5B;;AKlwSE;EsELM,yBAA0E;A3E2wSlF;;A2EjxSE;EACE,yBAAwB;A3EoxS5B;;AK1wSE;EsELM,yBAA0E;A3EmxSlF;;A2EzxSE;EACE,yBAAwB;A3E4xS5B;;AKlxSE;EsELM,yBAA0E;A3E2xSlF;;A2EjySE;EACE,yBAAwB;A3EoyS5B;;AK1xSE;EsELM,yBAA0E;A3EmySlF;;A2EzySE;EACE,yBAAwB;A3E4yS5B;;AKlySE;EsELM,yBAA0E;A3E2ySlF;;A2EjzSE;EACE,yBAAwB;A3EozS5B;;AK1ySE;EsELM,yBAA0E;A3EmzSlF;;A2EzzSE;EACE,yBAAwB;A3E4zS5B;;AKlzSE;EsELM,yBAA0E;A3E2zSlF;;AyEpxSA;EAAa,yBAA6B;AzEwxS1C;;AyEvxSA;EAAc,yBAA6B;AzE2xS3C;;AyEzxSA;EAAiB,oCAAkC;AzE6xSnD;;AyE5xSA;EAAiB,0CAAkC;AzEgySnD;;AyE5xSA;EGvDE,WAAW;EACX,kBAAkB;EAClB,iBAAiB;EACjB,6BAA6B;EAC7B,SAAS;A5Eu1SX;;AyEhySA;EAAwB,gCAAgC;AzEoySxD;;AyElySA;EACE,gCAAgC;AzEqySlC;;AyEhySA;EAAc,yBAAyB;AzEoySvC;;A6Ep2SA;EACE,8BAA8B;A7Eu2ShC;;A6Ep2SA;EACE,6BAA6B;A7Eu2S/B;;A8Ev2SE;E5EOF;;;I4EDM,4BAA4B;IAE5B,2BAA2B;E9Eu2S/B;E8Ep2SE;IAEI,0BAA0B;E9Eq2ShC;E8E51SE;IACE,6BAA6B;E9E81SjC;EEhqSF;I4E/KM,gCAAgC;E9Ek1SpC;E8Eh1SE;;IAEE,yB3EzCY;I2E0CZ,wBAAwB;E9Ek1S5B;E8E10SE;IACE,2BAA2B;E9E40S/B;E8Ez0SE;;IAEE,wBAAwB;E9E20S5B;E8Ex0SE;;;IAGE,UAAU;IACV,SAAS;E9E00Sb;E8Ev0SE;;IAEE,uBAAuB;E9Ey0S3B;E8Ej0SE;IACE,Q3EgiCgC;EHmyQpC;EE/2SF;I4E+CM,2BAA2C;E9Em0S/C;EYz5SA;IkEyFI,2BAA2C;E9Em0S/C;EiCj5SF;I6CmFM,aAAa;E9Ei0SjB;EsCh6SF;IwCkGM,sB3EtFS;EHu5Sb;EgBp6SF;I8DuGM,oCAAoC;E9Eg0SxC;E8Ej0SE;;IAKI,iCAAmC;E9Eg0SzC;EgBn4SF;;I8D0EQ,oCAAsC;E9E6zS5C;EgBlzSF;I8DNM,cAAc;E9E2zSlB;EiBj7SA;;;;I6D4HM,qB3EvHU;EHk7ShB;EgB70SF;I8DuBM,cAAc;IACd,qB3E7HY;EHs7ShB;AACF","file":"bootstrap.css","sourcesContent":["/*!\n * Bootstrap v4.5.0 (https://getbootstrap.com/)\n * Copyright 2011-2020 The Bootstrap Authors\n * Copyright 2011-2020 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n@import \"functions\";\n@import \"variables\";\n@import \"mixins\";\n@import \"root\";\n@import \"reboot\";\n@import \"type\";\n@import \"images\";\n@import \"code\";\n@import \"grid\";\n@import \"tables\";\n@import \"forms\";\n@import \"buttons\";\n@import \"transitions\";\n@import \"dropdown\";\n@import \"button-group\";\n@import \"input-group\";\n@import \"custom-forms\";\n@import \"nav\";\n@import \"navbar\";\n@import \"card\";\n@import \"breadcrumb\";\n@import \"pagination\";\n@import \"badge\";\n@import \"jumbotron\";\n@import \"alert\";\n@import \"progress\";\n@import \"media\";\n@import \"list-group\";\n@import \"close\";\n@import \"toasts\";\n@import \"modal\";\n@import \"tooltip\";\n@import \"popover\";\n@import \"carousel\";\n@import \"spinners\";\n@import \"utilities\";\n@import \"print\";\n","/*!\n * Bootstrap v4.5.0 (https://getbootstrap.com/)\n * Copyright 2011-2020 The Bootstrap Authors\n * Copyright 2011-2020 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n:root {\n --blue: #007bff;\n --indigo: #6610f2;\n --purple: #6f42c1;\n --pink: #e83e8c;\n --red: #dc3545;\n --orange: #fd7e14;\n --yellow: #ffc107;\n --green: #28a745;\n --teal: #20c997;\n --cyan: #17a2b8;\n --white: #fff;\n --gray: #6c757d;\n --gray-dark: #343a40;\n --primary: #007bff;\n --secondary: #6c757d;\n --success: #28a745;\n --info: #17a2b8;\n --warning: #ffc107;\n --danger: #dc3545;\n --light: #f8f9fa;\n --dark: #343a40;\n --breakpoint-xs: 0;\n --breakpoint-sm: 576px;\n --breakpoint-md: 768px;\n --breakpoint-lg: 992px;\n --breakpoint-xl: 1200px;\n --font-family-sans-serif: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\nhtml {\n font-family: sans-serif;\n line-height: 1.15;\n -webkit-text-size-adjust: 100%;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\narticle, aside, figcaption, figure, footer, header, hgroup, main, nav, section {\n display: block;\n}\n\nbody {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #212529;\n text-align: left;\n background-color: #fff;\n}\n\n[tabindex=\"-1\"]:focus:not(:focus-visible) {\n outline: 0 !important;\n}\n\nhr {\n box-sizing: content-box;\n height: 0;\n overflow: visible;\n}\n\nh1, h2, h3, h4, h5, h6 {\n margin-top: 0;\n margin-bottom: 0.5rem;\n}\n\np {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nabbr[title],\nabbr[data-original-title] {\n text-decoration: underline;\n text-decoration: underline dotted;\n cursor: help;\n border-bottom: 0;\n text-decoration-skip-ink: none;\n}\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: 700;\n}\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0;\n}\n\nblockquote {\n margin: 0 0 1rem;\n}\n\nb,\nstrong {\n font-weight: bolder;\n}\n\nsmall {\n font-size: 80%;\n}\n\nsub,\nsup {\n position: relative;\n font-size: 75%;\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -.25em;\n}\n\nsup {\n top: -.5em;\n}\n\na {\n color: #007bff;\n text-decoration: none;\n background-color: transparent;\n}\n\na:hover {\n color: #0056b3;\n text-decoration: underline;\n}\n\na:not([href]) {\n color: inherit;\n text-decoration: none;\n}\n\na:not([href]):hover {\n color: inherit;\n text-decoration: none;\n}\n\npre,\ncode,\nkbd,\nsamp {\n font-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n font-size: 1em;\n}\n\npre {\n margin-top: 0;\n margin-bottom: 1rem;\n overflow: auto;\n -ms-overflow-style: scrollbar;\n}\n\nfigure {\n margin: 0 0 1rem;\n}\n\nimg {\n vertical-align: middle;\n border-style: none;\n}\n\nsvg {\n overflow: hidden;\n vertical-align: middle;\n}\n\ntable {\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: 0.75rem;\n padding-bottom: 0.75rem;\n color: #6c757d;\n text-align: left;\n caption-side: bottom;\n}\n\nth {\n text-align: inherit;\n}\n\nlabel {\n display: inline-block;\n margin-bottom: 0.5rem;\n}\n\nbutton {\n border-radius: 0;\n}\n\nbutton:focus {\n outline: 1px dotted;\n outline: 5px auto -webkit-focus-ring-color;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0;\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\nbutton,\ninput {\n overflow: visible;\n}\n\nbutton,\nselect {\n text-transform: none;\n}\n\n[role=\"button\"] {\n cursor: pointer;\n}\n\nselect {\n word-wrap: normal;\n}\n\nbutton,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button;\n}\n\nbutton:not(:disabled),\n[type=\"button\"]:not(:disabled),\n[type=\"reset\"]:not(:disabled),\n[type=\"submit\"]:not(:disabled) {\n cursor: pointer;\n}\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n padding: 0;\n border-style: none;\n}\n\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n box-sizing: border-box;\n padding: 0;\n}\n\ntextarea {\n overflow: auto;\n resize: vertical;\n}\n\nfieldset {\n min-width: 0;\n padding: 0;\n margin: 0;\n border: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n max-width: 100%;\n padding: 0;\n margin-bottom: .5rem;\n font-size: 1.5rem;\n line-height: inherit;\n color: inherit;\n white-space: normal;\n}\n\nprogress {\n vertical-align: baseline;\n}\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n[type=\"search\"] {\n outline-offset: -2px;\n -webkit-appearance: none;\n}\n\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n::-webkit-file-upload-button {\n font: inherit;\n -webkit-appearance: button;\n}\n\noutput {\n display: inline-block;\n}\n\nsummary {\n display: list-item;\n cursor: pointer;\n}\n\ntemplate {\n display: none;\n}\n\n[hidden] {\n display: none !important;\n}\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n margin-bottom: 0.5rem;\n font-weight: 500;\n line-height: 1.2;\n}\n\nh1, .h1 {\n font-size: 2.5rem;\n}\n\nh2, .h2 {\n font-size: 2rem;\n}\n\nh3, .h3 {\n font-size: 1.75rem;\n}\n\nh4, .h4 {\n font-size: 1.5rem;\n}\n\nh5, .h5 {\n font-size: 1.25rem;\n}\n\nh6, .h6 {\n font-size: 1rem;\n}\n\n.lead {\n font-size: 1.25rem;\n font-weight: 300;\n}\n\n.display-1 {\n font-size: 6rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-2 {\n font-size: 5.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-3 {\n font-size: 4.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-4 {\n font-size: 3.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\nhr {\n margin-top: 1rem;\n margin-bottom: 1rem;\n border: 0;\n border-top: 1px solid rgba(0, 0, 0, 0.1);\n}\n\nsmall,\n.small {\n font-size: 80%;\n font-weight: 400;\n}\n\nmark,\n.mark {\n padding: 0.2em;\n background-color: #fcf8e3;\n}\n\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline-item {\n display: inline-block;\n}\n\n.list-inline-item:not(:last-child) {\n margin-right: 0.5rem;\n}\n\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\n\n.blockquote {\n margin-bottom: 1rem;\n font-size: 1.25rem;\n}\n\n.blockquote-footer {\n display: block;\n font-size: 80%;\n color: #6c757d;\n}\n\n.blockquote-footer::before {\n content: \"\\2014\\00A0\";\n}\n\n.img-fluid {\n max-width: 100%;\n height: auto;\n}\n\n.img-thumbnail {\n padding: 0.25rem;\n background-color: #fff;\n border: 1px solid #dee2e6;\n border-radius: 0.25rem;\n max-width: 100%;\n height: auto;\n}\n\n.figure {\n display: inline-block;\n}\n\n.figure-img {\n margin-bottom: 0.5rem;\n line-height: 1;\n}\n\n.figure-caption {\n font-size: 90%;\n color: #6c757d;\n}\n\ncode {\n font-size: 87.5%;\n color: #e83e8c;\n word-wrap: break-word;\n}\n\na > code {\n color: inherit;\n}\n\nkbd {\n padding: 0.2rem 0.4rem;\n font-size: 87.5%;\n color: #fff;\n background-color: #212529;\n border-radius: 0.2rem;\n}\n\nkbd kbd {\n padding: 0;\n font-size: 100%;\n font-weight: 700;\n}\n\npre {\n display: block;\n font-size: 87.5%;\n color: #212529;\n}\n\npre code {\n font-size: inherit;\n color: inherit;\n word-break: normal;\n}\n\n.pre-scrollable {\n max-height: 340px;\n overflow-y: scroll;\n}\n\n.container {\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n\n@media (min-width: 576px) {\n .container {\n max-width: 540px;\n }\n}\n\n@media (min-width: 768px) {\n .container {\n max-width: 720px;\n }\n}\n\n@media (min-width: 992px) {\n .container {\n max-width: 960px;\n }\n}\n\n@media (min-width: 1200px) {\n .container {\n max-width: 1140px;\n }\n}\n\n.container-fluid, .container-sm, .container-md, .container-lg, .container-xl {\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n\n@media (min-width: 576px) {\n .container, .container-sm {\n max-width: 540px;\n }\n}\n\n@media (min-width: 768px) {\n .container, .container-sm, .container-md {\n max-width: 720px;\n }\n}\n\n@media (min-width: 992px) {\n .container, .container-sm, .container-md, .container-lg {\n max-width: 960px;\n }\n}\n\n@media (min-width: 1200px) {\n .container, .container-sm, .container-md, .container-lg, .container-xl {\n max-width: 1140px;\n }\n}\n\n.row {\n display: flex;\n flex-wrap: wrap;\n margin-right: -15px;\n margin-left: -15px;\n}\n\n.no-gutters {\n margin-right: 0;\n margin-left: 0;\n}\n\n.no-gutters > .col,\n.no-gutters > [class*=\"col-\"] {\n padding-right: 0;\n padding-left: 0;\n}\n\n.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col,\n.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm,\n.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md,\n.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg,\n.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl,\n.col-xl-auto {\n position: relative;\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n}\n\n.col {\n flex-basis: 0;\n flex-grow: 1;\n min-width: 0;\n max-width: 100%;\n}\n\n.row-cols-1 > * {\n flex: 0 0 100%;\n max-width: 100%;\n}\n\n.row-cols-2 > * {\n flex: 0 0 50%;\n max-width: 50%;\n}\n\n.row-cols-3 > * {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n}\n\n.row-cols-4 > * {\n flex: 0 0 25%;\n max-width: 25%;\n}\n\n.row-cols-5 > * {\n flex: 0 0 20%;\n max-width: 20%;\n}\n\n.row-cols-6 > * {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n}\n\n.col-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n}\n\n.col-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n}\n\n.col-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n}\n\n.col-3 {\n flex: 0 0 25%;\n max-width: 25%;\n}\n\n.col-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n}\n\n.col-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n}\n\n.col-6 {\n flex: 0 0 50%;\n max-width: 50%;\n}\n\n.col-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n}\n\n.col-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n}\n\n.col-9 {\n flex: 0 0 75%;\n max-width: 75%;\n}\n\n.col-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n}\n\n.col-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n}\n\n.col-12 {\n flex: 0 0 100%;\n max-width: 100%;\n}\n\n.order-first {\n order: -1;\n}\n\n.order-last {\n order: 13;\n}\n\n.order-0 {\n order: 0;\n}\n\n.order-1 {\n order: 1;\n}\n\n.order-2 {\n order: 2;\n}\n\n.order-3 {\n order: 3;\n}\n\n.order-4 {\n order: 4;\n}\n\n.order-5 {\n order: 5;\n}\n\n.order-6 {\n order: 6;\n}\n\n.order-7 {\n order: 7;\n}\n\n.order-8 {\n order: 8;\n}\n\n.order-9 {\n order: 9;\n}\n\n.order-10 {\n order: 10;\n}\n\n.order-11 {\n order: 11;\n}\n\n.order-12 {\n order: 12;\n}\n\n.offset-1 {\n margin-left: 8.333333%;\n}\n\n.offset-2 {\n margin-left: 16.666667%;\n}\n\n.offset-3 {\n margin-left: 25%;\n}\n\n.offset-4 {\n margin-left: 33.333333%;\n}\n\n.offset-5 {\n margin-left: 41.666667%;\n}\n\n.offset-6 {\n margin-left: 50%;\n}\n\n.offset-7 {\n margin-left: 58.333333%;\n}\n\n.offset-8 {\n margin-left: 66.666667%;\n}\n\n.offset-9 {\n margin-left: 75%;\n}\n\n.offset-10 {\n margin-left: 83.333333%;\n}\n\n.offset-11 {\n margin-left: 91.666667%;\n}\n\n@media (min-width: 576px) {\n .col-sm {\n flex-basis: 0;\n flex-grow: 1;\n min-width: 0;\n max-width: 100%;\n }\n .row-cols-sm-1 > * {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .row-cols-sm-2 > * {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .row-cols-sm-3 > * {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .row-cols-sm-4 > * {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .row-cols-sm-5 > * {\n flex: 0 0 20%;\n max-width: 20%;\n }\n .row-cols-sm-6 > * {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-sm-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-sm-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-sm-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-sm-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-sm-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-sm-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-sm-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-sm-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-sm-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-sm-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-sm-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-sm-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-sm-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-sm-first {\n order: -1;\n }\n .order-sm-last {\n order: 13;\n }\n .order-sm-0 {\n order: 0;\n }\n .order-sm-1 {\n order: 1;\n }\n .order-sm-2 {\n order: 2;\n }\n .order-sm-3 {\n order: 3;\n }\n .order-sm-4 {\n order: 4;\n }\n .order-sm-5 {\n order: 5;\n }\n .order-sm-6 {\n order: 6;\n }\n .order-sm-7 {\n order: 7;\n }\n .order-sm-8 {\n order: 8;\n }\n .order-sm-9 {\n order: 9;\n }\n .order-sm-10 {\n order: 10;\n }\n .order-sm-11 {\n order: 11;\n }\n .order-sm-12 {\n order: 12;\n }\n .offset-sm-0 {\n margin-left: 0;\n }\n .offset-sm-1 {\n margin-left: 8.333333%;\n }\n .offset-sm-2 {\n margin-left: 16.666667%;\n }\n .offset-sm-3 {\n margin-left: 25%;\n }\n .offset-sm-4 {\n margin-left: 33.333333%;\n }\n .offset-sm-5 {\n margin-left: 41.666667%;\n }\n .offset-sm-6 {\n margin-left: 50%;\n }\n .offset-sm-7 {\n margin-left: 58.333333%;\n }\n .offset-sm-8 {\n margin-left: 66.666667%;\n }\n .offset-sm-9 {\n margin-left: 75%;\n }\n .offset-sm-10 {\n margin-left: 83.333333%;\n }\n .offset-sm-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 768px) {\n .col-md {\n flex-basis: 0;\n flex-grow: 1;\n min-width: 0;\n max-width: 100%;\n }\n .row-cols-md-1 > * {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .row-cols-md-2 > * {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .row-cols-md-3 > * {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .row-cols-md-4 > * {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .row-cols-md-5 > * {\n flex: 0 0 20%;\n max-width: 20%;\n }\n .row-cols-md-6 > * {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-md-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-md-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-md-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-md-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-md-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-md-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-md-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-md-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-md-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-md-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-md-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-md-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-md-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-md-first {\n order: -1;\n }\n .order-md-last {\n order: 13;\n }\n .order-md-0 {\n order: 0;\n }\n .order-md-1 {\n order: 1;\n }\n .order-md-2 {\n order: 2;\n }\n .order-md-3 {\n order: 3;\n }\n .order-md-4 {\n order: 4;\n }\n .order-md-5 {\n order: 5;\n }\n .order-md-6 {\n order: 6;\n }\n .order-md-7 {\n order: 7;\n }\n .order-md-8 {\n order: 8;\n }\n .order-md-9 {\n order: 9;\n }\n .order-md-10 {\n order: 10;\n }\n .order-md-11 {\n order: 11;\n }\n .order-md-12 {\n order: 12;\n }\n .offset-md-0 {\n margin-left: 0;\n }\n .offset-md-1 {\n margin-left: 8.333333%;\n }\n .offset-md-2 {\n margin-left: 16.666667%;\n }\n .offset-md-3 {\n margin-left: 25%;\n }\n .offset-md-4 {\n margin-left: 33.333333%;\n }\n .offset-md-5 {\n margin-left: 41.666667%;\n }\n .offset-md-6 {\n margin-left: 50%;\n }\n .offset-md-7 {\n margin-left: 58.333333%;\n }\n .offset-md-8 {\n margin-left: 66.666667%;\n }\n .offset-md-9 {\n margin-left: 75%;\n }\n .offset-md-10 {\n margin-left: 83.333333%;\n }\n .offset-md-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 992px) {\n .col-lg {\n flex-basis: 0;\n flex-grow: 1;\n min-width: 0;\n max-width: 100%;\n }\n .row-cols-lg-1 > * {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .row-cols-lg-2 > * {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .row-cols-lg-3 > * {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .row-cols-lg-4 > * {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .row-cols-lg-5 > * {\n flex: 0 0 20%;\n max-width: 20%;\n }\n .row-cols-lg-6 > * {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-lg-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-lg-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-lg-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-lg-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-lg-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-lg-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-lg-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-lg-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-lg-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-lg-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-lg-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-lg-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-lg-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-lg-first {\n order: -1;\n }\n .order-lg-last {\n order: 13;\n }\n .order-lg-0 {\n order: 0;\n }\n .order-lg-1 {\n order: 1;\n }\n .order-lg-2 {\n order: 2;\n }\n .order-lg-3 {\n order: 3;\n }\n .order-lg-4 {\n order: 4;\n }\n .order-lg-5 {\n order: 5;\n }\n .order-lg-6 {\n order: 6;\n }\n .order-lg-7 {\n order: 7;\n }\n .order-lg-8 {\n order: 8;\n }\n .order-lg-9 {\n order: 9;\n }\n .order-lg-10 {\n order: 10;\n }\n .order-lg-11 {\n order: 11;\n }\n .order-lg-12 {\n order: 12;\n }\n .offset-lg-0 {\n margin-left: 0;\n }\n .offset-lg-1 {\n margin-left: 8.333333%;\n }\n .offset-lg-2 {\n margin-left: 16.666667%;\n }\n .offset-lg-3 {\n margin-left: 25%;\n }\n .offset-lg-4 {\n margin-left: 33.333333%;\n }\n .offset-lg-5 {\n margin-left: 41.666667%;\n }\n .offset-lg-6 {\n margin-left: 50%;\n }\n .offset-lg-7 {\n margin-left: 58.333333%;\n }\n .offset-lg-8 {\n margin-left: 66.666667%;\n }\n .offset-lg-9 {\n margin-left: 75%;\n }\n .offset-lg-10 {\n margin-left: 83.333333%;\n }\n .offset-lg-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 1200px) {\n .col-xl {\n flex-basis: 0;\n flex-grow: 1;\n min-width: 0;\n max-width: 100%;\n }\n .row-cols-xl-1 > * {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .row-cols-xl-2 > * {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .row-cols-xl-3 > * {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .row-cols-xl-4 > * {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .row-cols-xl-5 > * {\n flex: 0 0 20%;\n max-width: 20%;\n }\n .row-cols-xl-6 > * {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-xl-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-xl-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-xl-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-xl-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-xl-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-xl-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-xl-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-xl-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-xl-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-xl-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-xl-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-xl-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-xl-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-xl-first {\n order: -1;\n }\n .order-xl-last {\n order: 13;\n }\n .order-xl-0 {\n order: 0;\n }\n .order-xl-1 {\n order: 1;\n }\n .order-xl-2 {\n order: 2;\n }\n .order-xl-3 {\n order: 3;\n }\n .order-xl-4 {\n order: 4;\n }\n .order-xl-5 {\n order: 5;\n }\n .order-xl-6 {\n order: 6;\n }\n .order-xl-7 {\n order: 7;\n }\n .order-xl-8 {\n order: 8;\n }\n .order-xl-9 {\n order: 9;\n }\n .order-xl-10 {\n order: 10;\n }\n .order-xl-11 {\n order: 11;\n }\n .order-xl-12 {\n order: 12;\n }\n .offset-xl-0 {\n margin-left: 0;\n }\n .offset-xl-1 {\n margin-left: 8.333333%;\n }\n .offset-xl-2 {\n margin-left: 16.666667%;\n }\n .offset-xl-3 {\n margin-left: 25%;\n }\n .offset-xl-4 {\n margin-left: 33.333333%;\n }\n .offset-xl-5 {\n margin-left: 41.666667%;\n }\n .offset-xl-6 {\n margin-left: 50%;\n }\n .offset-xl-7 {\n margin-left: 58.333333%;\n }\n .offset-xl-8 {\n margin-left: 66.666667%;\n }\n .offset-xl-9 {\n margin-left: 75%;\n }\n .offset-xl-10 {\n margin-left: 83.333333%;\n }\n .offset-xl-11 {\n margin-left: 91.666667%;\n }\n}\n\n.table {\n width: 100%;\n margin-bottom: 1rem;\n color: #212529;\n}\n\n.table th,\n.table td {\n padding: 0.75rem;\n vertical-align: top;\n border-top: 1px solid #dee2e6;\n}\n\n.table thead th {\n vertical-align: bottom;\n border-bottom: 2px solid #dee2e6;\n}\n\n.table tbody + tbody {\n border-top: 2px solid #dee2e6;\n}\n\n.table-sm th,\n.table-sm td {\n padding: 0.3rem;\n}\n\n.table-bordered {\n border: 1px solid #dee2e6;\n}\n\n.table-bordered th,\n.table-bordered td {\n border: 1px solid #dee2e6;\n}\n\n.table-bordered thead th,\n.table-bordered thead td {\n border-bottom-width: 2px;\n}\n\n.table-borderless th,\n.table-borderless td,\n.table-borderless thead th,\n.table-borderless tbody + tbody {\n border: 0;\n}\n\n.table-striped tbody tr:nth-of-type(odd) {\n background-color: rgba(0, 0, 0, 0.05);\n}\n\n.table-hover tbody tr:hover {\n color: #212529;\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-primary,\n.table-primary > th,\n.table-primary > td {\n background-color: #b8daff;\n}\n\n.table-primary th,\n.table-primary td,\n.table-primary thead th,\n.table-primary tbody + tbody {\n border-color: #7abaff;\n}\n\n.table-hover .table-primary:hover {\n background-color: #9fcdff;\n}\n\n.table-hover .table-primary:hover > td,\n.table-hover .table-primary:hover > th {\n background-color: #9fcdff;\n}\n\n.table-secondary,\n.table-secondary > th,\n.table-secondary > td {\n background-color: #d6d8db;\n}\n\n.table-secondary th,\n.table-secondary td,\n.table-secondary thead th,\n.table-secondary tbody + tbody {\n border-color: #b3b7bb;\n}\n\n.table-hover .table-secondary:hover {\n background-color: #c8cbcf;\n}\n\n.table-hover .table-secondary:hover > td,\n.table-hover .table-secondary:hover > th {\n background-color: #c8cbcf;\n}\n\n.table-success,\n.table-success > th,\n.table-success > td {\n background-color: #c3e6cb;\n}\n\n.table-success th,\n.table-success td,\n.table-success thead th,\n.table-success tbody + tbody {\n border-color: #8fd19e;\n}\n\n.table-hover .table-success:hover {\n background-color: #b1dfbb;\n}\n\n.table-hover .table-success:hover > td,\n.table-hover .table-success:hover > th {\n background-color: #b1dfbb;\n}\n\n.table-info,\n.table-info > th,\n.table-info > td {\n background-color: #bee5eb;\n}\n\n.table-info th,\n.table-info td,\n.table-info thead th,\n.table-info tbody + tbody {\n border-color: #86cfda;\n}\n\n.table-hover .table-info:hover {\n background-color: #abdde5;\n}\n\n.table-hover .table-info:hover > td,\n.table-hover .table-info:hover > th {\n background-color: #abdde5;\n}\n\n.table-warning,\n.table-warning > th,\n.table-warning > td {\n background-color: #ffeeba;\n}\n\n.table-warning th,\n.table-warning td,\n.table-warning thead th,\n.table-warning tbody + tbody {\n border-color: #ffdf7e;\n}\n\n.table-hover .table-warning:hover {\n background-color: #ffe8a1;\n}\n\n.table-hover .table-warning:hover > td,\n.table-hover .table-warning:hover > th {\n background-color: #ffe8a1;\n}\n\n.table-danger,\n.table-danger > th,\n.table-danger > td {\n background-color: #f5c6cb;\n}\n\n.table-danger th,\n.table-danger td,\n.table-danger thead th,\n.table-danger tbody + tbody {\n border-color: #ed969e;\n}\n\n.table-hover .table-danger:hover {\n background-color: #f1b0b7;\n}\n\n.table-hover .table-danger:hover > td,\n.table-hover .table-danger:hover > th {\n background-color: #f1b0b7;\n}\n\n.table-light,\n.table-light > th,\n.table-light > td {\n background-color: #fdfdfe;\n}\n\n.table-light th,\n.table-light td,\n.table-light thead th,\n.table-light tbody + tbody {\n border-color: #fbfcfc;\n}\n\n.table-hover .table-light:hover {\n background-color: #ececf6;\n}\n\n.table-hover .table-light:hover > td,\n.table-hover .table-light:hover > th {\n background-color: #ececf6;\n}\n\n.table-dark,\n.table-dark > th,\n.table-dark > td {\n background-color: #c6c8ca;\n}\n\n.table-dark th,\n.table-dark td,\n.table-dark thead th,\n.table-dark tbody + tbody {\n border-color: #95999c;\n}\n\n.table-hover .table-dark:hover {\n background-color: #b9bbbe;\n}\n\n.table-hover .table-dark:hover > td,\n.table-hover .table-dark:hover > th {\n background-color: #b9bbbe;\n}\n\n.table-active,\n.table-active > th,\n.table-active > td {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-hover .table-active:hover {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-hover .table-active:hover > td,\n.table-hover .table-active:hover > th {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table .thead-dark th {\n color: #fff;\n background-color: #343a40;\n border-color: #454d55;\n}\n\n.table .thead-light th {\n color: #495057;\n background-color: #e9ecef;\n border-color: #dee2e6;\n}\n\n.table-dark {\n color: #fff;\n background-color: #343a40;\n}\n\n.table-dark th,\n.table-dark td,\n.table-dark thead th {\n border-color: #454d55;\n}\n\n.table-dark.table-bordered {\n border: 0;\n}\n\n.table-dark.table-striped tbody tr:nth-of-type(odd) {\n background-color: rgba(255, 255, 255, 0.05);\n}\n\n.table-dark.table-hover tbody tr:hover {\n color: #fff;\n background-color: rgba(255, 255, 255, 0.075);\n}\n\n@media (max-width: 575.98px) {\n .table-responsive-sm {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .table-responsive-sm > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 767.98px) {\n .table-responsive-md {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .table-responsive-md > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 991.98px) {\n .table-responsive-lg {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .table-responsive-lg > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 1199.98px) {\n .table-responsive-xl {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .table-responsive-xl > .table-bordered {\n border: 0;\n }\n}\n\n.table-responsive {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n}\n\n.table-responsive > .table-bordered {\n border: 0;\n}\n\n.form-control {\n display: block;\n width: 100%;\n height: calc(1.5em + 0.75rem + 2px);\n padding: 0.375rem 0.75rem;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .form-control {\n transition: none;\n }\n}\n\n.form-control::-ms-expand {\n background-color: transparent;\n border: 0;\n}\n\n.form-control:-moz-focusring {\n color: transparent;\n text-shadow: 0 0 0 #495057;\n}\n\n.form-control:focus {\n color: #495057;\n background-color: #fff;\n border-color: #80bdff;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.form-control::placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control:disabled, .form-control[readonly] {\n background-color: #e9ecef;\n opacity: 1;\n}\n\ninput[type=\"date\"].form-control,\ninput[type=\"time\"].form-control,\ninput[type=\"datetime-local\"].form-control,\ninput[type=\"month\"].form-control {\n appearance: none;\n}\n\nselect.form-control:focus::-ms-value {\n color: #495057;\n background-color: #fff;\n}\n\n.form-control-file,\n.form-control-range {\n display: block;\n width: 100%;\n}\n\n.col-form-label {\n padding-top: calc(0.375rem + 1px);\n padding-bottom: calc(0.375rem + 1px);\n margin-bottom: 0;\n font-size: inherit;\n line-height: 1.5;\n}\n\n.col-form-label-lg {\n padding-top: calc(0.5rem + 1px);\n padding-bottom: calc(0.5rem + 1px);\n font-size: 1.25rem;\n line-height: 1.5;\n}\n\n.col-form-label-sm {\n padding-top: calc(0.25rem + 1px);\n padding-bottom: calc(0.25rem + 1px);\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n.form-control-plaintext {\n display: block;\n width: 100%;\n padding: 0.375rem 0;\n margin-bottom: 0;\n font-size: 1rem;\n line-height: 1.5;\n color: #212529;\n background-color: transparent;\n border: solid transparent;\n border-width: 1px 0;\n}\n\n.form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg {\n padding-right: 0;\n padding-left: 0;\n}\n\n.form-control-sm {\n height: calc(1.5em + 0.5rem + 2px);\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\n.form-control-lg {\n height: calc(1.5em + 1rem + 2px);\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\nselect.form-control[size], select.form-control[multiple] {\n height: auto;\n}\n\ntextarea.form-control {\n height: auto;\n}\n\n.form-group {\n margin-bottom: 1rem;\n}\n\n.form-text {\n display: block;\n margin-top: 0.25rem;\n}\n\n.form-row {\n display: flex;\n flex-wrap: wrap;\n margin-right: -5px;\n margin-left: -5px;\n}\n\n.form-row > .col,\n.form-row > [class*=\"col-\"] {\n padding-right: 5px;\n padding-left: 5px;\n}\n\n.form-check {\n position: relative;\n display: block;\n padding-left: 1.25rem;\n}\n\n.form-check-input {\n position: absolute;\n margin-top: 0.3rem;\n margin-left: -1.25rem;\n}\n\n.form-check-input[disabled] ~ .form-check-label,\n.form-check-input:disabled ~ .form-check-label {\n color: #6c757d;\n}\n\n.form-check-label {\n margin-bottom: 0;\n}\n\n.form-check-inline {\n display: inline-flex;\n align-items: center;\n padding-left: 0;\n margin-right: 0.75rem;\n}\n\n.form-check-inline .form-check-input {\n position: static;\n margin-top: 0;\n margin-right: 0.3125rem;\n margin-left: 0;\n}\n\n.valid-feedback {\n display: none;\n width: 100%;\n margin-top: 0.25rem;\n font-size: 80%;\n color: #28a745;\n}\n\n.valid-tooltip {\n position: absolute;\n top: 100%;\n z-index: 5;\n display: none;\n max-width: 100%;\n padding: 0.25rem 0.5rem;\n margin-top: .1rem;\n font-size: 0.875rem;\n line-height: 1.5;\n color: #fff;\n background-color: rgba(40, 167, 69, 0.9);\n border-radius: 0.25rem;\n}\n\n.was-validated :valid ~ .valid-feedback,\n.was-validated :valid ~ .valid-tooltip,\n.is-valid ~ .valid-feedback,\n.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .form-control:valid, .form-control.is-valid {\n border-color: #28a745;\n padding-right: calc(1.5em + 0.75rem);\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e\");\n background-repeat: no-repeat;\n background-position: right calc(0.375em + 0.1875rem) center;\n background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n\n.was-validated .form-control:valid:focus, .form-control.is-valid:focus {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated textarea.form-control:valid, textarea.form-control.is-valid {\n padding-right: calc(1.5em + 0.75rem);\n background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);\n}\n\n.was-validated .custom-select:valid, .custom-select.is-valid {\n border-color: #28a745;\n padding-right: calc(0.75em + 2.3125rem);\n background: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e\") no-repeat right 0.75rem center/8px 10px, url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e\") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n\n.was-validated .custom-select:valid:focus, .custom-select.is-valid:focus {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label {\n color: #28a745;\n}\n\n.was-validated .form-check-input:valid ~ .valid-feedback,\n.was-validated .form-check-input:valid ~ .valid-tooltip, .form-check-input.is-valid ~ .valid-feedback,\n.form-check-input.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label {\n color: #28a745;\n}\n\n.was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before {\n border-color: #28a745;\n}\n\n.was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before {\n border-color: #34ce57;\n background-color: #34ce57;\n}\n\n.was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before {\n border-color: #28a745;\n}\n\n.was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label {\n border-color: #28a745;\n}\n\n.was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.invalid-feedback {\n display: none;\n width: 100%;\n margin-top: 0.25rem;\n font-size: 80%;\n color: #dc3545;\n}\n\n.invalid-tooltip {\n position: absolute;\n top: 100%;\n z-index: 5;\n display: none;\n max-width: 100%;\n padding: 0.25rem 0.5rem;\n margin-top: .1rem;\n font-size: 0.875rem;\n line-height: 1.5;\n color: #fff;\n background-color: rgba(220, 53, 69, 0.9);\n border-radius: 0.25rem;\n}\n\n.was-validated :invalid ~ .invalid-feedback,\n.was-validated :invalid ~ .invalid-tooltip,\n.is-invalid ~ .invalid-feedback,\n.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .form-control:invalid, .form-control.is-invalid {\n border-color: #dc3545;\n padding-right: calc(1.5em + 0.75rem);\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e\");\n background-repeat: no-repeat;\n background-position: right calc(0.375em + 0.1875rem) center;\n background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n\n.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid {\n padding-right: calc(1.5em + 0.75rem);\n background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);\n}\n\n.was-validated .custom-select:invalid, .custom-select.is-invalid {\n border-color: #dc3545;\n padding-right: calc(0.75em + 2.3125rem);\n background: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e\") no-repeat right 0.75rem center/8px 10px, url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e\") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n\n.was-validated .custom-select:invalid:focus, .custom-select.is-invalid:focus {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label {\n color: #dc3545;\n}\n\n.was-validated .form-check-input:invalid ~ .invalid-feedback,\n.was-validated .form-check-input:invalid ~ .invalid-tooltip, .form-check-input.is-invalid ~ .invalid-feedback,\n.form-check-input.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label {\n color: #dc3545;\n}\n\n.was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before {\n border-color: #dc3545;\n}\n\n.was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before {\n border-color: #e4606d;\n background-color: #e4606d;\n}\n\n.was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before {\n border-color: #dc3545;\n}\n\n.was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label {\n border-color: #dc3545;\n}\n\n.was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.form-inline {\n display: flex;\n flex-flow: row wrap;\n align-items: center;\n}\n\n.form-inline .form-check {\n width: 100%;\n}\n\n@media (min-width: 576px) {\n .form-inline label {\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: 0;\n }\n .form-inline .form-group {\n display: flex;\n flex: 0 0 auto;\n flex-flow: row wrap;\n align-items: center;\n margin-bottom: 0;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-plaintext {\n display: inline-block;\n }\n .form-inline .input-group,\n .form-inline .custom-select {\n width: auto;\n }\n .form-inline .form-check {\n display: flex;\n align-items: center;\n justify-content: center;\n width: auto;\n padding-left: 0;\n }\n .form-inline .form-check-input {\n position: relative;\n flex-shrink: 0;\n margin-top: 0;\n margin-right: 0.25rem;\n margin-left: 0;\n }\n .form-inline .custom-control {\n align-items: center;\n justify-content: center;\n }\n .form-inline .custom-control-label {\n margin-bottom: 0;\n }\n}\n\n.btn {\n display: inline-block;\n font-weight: 400;\n color: #212529;\n text-align: center;\n vertical-align: middle;\n user-select: none;\n background-color: transparent;\n border: 1px solid transparent;\n padding: 0.375rem 0.75rem;\n font-size: 1rem;\n line-height: 1.5;\n border-radius: 0.25rem;\n transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .btn {\n transition: none;\n }\n}\n\n.btn:hover {\n color: #212529;\n text-decoration: none;\n}\n\n.btn:focus, .btn.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.btn.disabled, .btn:disabled {\n opacity: 0.65;\n}\n\n.btn:not(:disabled):not(.disabled) {\n cursor: pointer;\n}\n\na.btn.disabled,\nfieldset:disabled a.btn {\n pointer-events: none;\n}\n\n.btn-primary {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-primary:hover {\n color: #fff;\n background-color: #0069d9;\n border-color: #0062cc;\n}\n\n.btn-primary:focus, .btn-primary.focus {\n color: #fff;\n background-color: #0069d9;\n border-color: #0062cc;\n box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5);\n}\n\n.btn-primary.disabled, .btn-primary:disabled {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active,\n.show > .btn-primary.dropdown-toggle {\n color: #fff;\n background-color: #0062cc;\n border-color: #005cbf;\n}\n\n.btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-primary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5);\n}\n\n.btn-secondary {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-secondary:hover {\n color: #fff;\n background-color: #5a6268;\n border-color: #545b62;\n}\n\n.btn-secondary:focus, .btn-secondary.focus {\n color: #fff;\n background-color: #5a6268;\n border-color: #545b62;\n box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5);\n}\n\n.btn-secondary.disabled, .btn-secondary:disabled {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active,\n.show > .btn-secondary.dropdown-toggle {\n color: #fff;\n background-color: #545b62;\n border-color: #4e555b;\n}\n\n.btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-secondary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5);\n}\n\n.btn-success {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-success:hover {\n color: #fff;\n background-color: #218838;\n border-color: #1e7e34;\n}\n\n.btn-success:focus, .btn-success.focus {\n color: #fff;\n background-color: #218838;\n border-color: #1e7e34;\n box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5);\n}\n\n.btn-success.disabled, .btn-success:disabled {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active,\n.show > .btn-success.dropdown-toggle {\n color: #fff;\n background-color: #1e7e34;\n border-color: #1c7430;\n}\n\n.btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus,\n.show > .btn-success.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5);\n}\n\n.btn-info {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-info:hover {\n color: #fff;\n background-color: #138496;\n border-color: #117a8b;\n}\n\n.btn-info:focus, .btn-info.focus {\n color: #fff;\n background-color: #138496;\n border-color: #117a8b;\n box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);\n}\n\n.btn-info.disabled, .btn-info:disabled {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active,\n.show > .btn-info.dropdown-toggle {\n color: #fff;\n background-color: #117a8b;\n border-color: #10707f;\n}\n\n.btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus,\n.show > .btn-info.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);\n}\n\n.btn-warning {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-warning:hover {\n color: #212529;\n background-color: #e0a800;\n border-color: #d39e00;\n}\n\n.btn-warning:focus, .btn-warning.focus {\n color: #212529;\n background-color: #e0a800;\n border-color: #d39e00;\n box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5);\n}\n\n.btn-warning.disabled, .btn-warning:disabled {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active,\n.show > .btn-warning.dropdown-toggle {\n color: #212529;\n background-color: #d39e00;\n border-color: #c69500;\n}\n\n.btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus,\n.show > .btn-warning.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5);\n}\n\n.btn-danger {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-danger:hover {\n color: #fff;\n background-color: #c82333;\n border-color: #bd2130;\n}\n\n.btn-danger:focus, .btn-danger.focus {\n color: #fff;\n background-color: #c82333;\n border-color: #bd2130;\n box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);\n}\n\n.btn-danger.disabled, .btn-danger:disabled {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active,\n.show > .btn-danger.dropdown-toggle {\n color: #fff;\n background-color: #bd2130;\n border-color: #b21f2d;\n}\n\n.btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus,\n.show > .btn-danger.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);\n}\n\n.btn-light {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-light:hover {\n color: #212529;\n background-color: #e2e6ea;\n border-color: #dae0e5;\n}\n\n.btn-light:focus, .btn-light.focus {\n color: #212529;\n background-color: #e2e6ea;\n border-color: #dae0e5;\n box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5);\n}\n\n.btn-light.disabled, .btn-light:disabled {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active,\n.show > .btn-light.dropdown-toggle {\n color: #212529;\n background-color: #dae0e5;\n border-color: #d3d9df;\n}\n\n.btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus,\n.show > .btn-light.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5);\n}\n\n.btn-dark {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-dark:hover {\n color: #fff;\n background-color: #23272b;\n border-color: #1d2124;\n}\n\n.btn-dark:focus, .btn-dark.focus {\n color: #fff;\n background-color: #23272b;\n border-color: #1d2124;\n box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5);\n}\n\n.btn-dark.disabled, .btn-dark:disabled {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active,\n.show > .btn-dark.dropdown-toggle {\n color: #fff;\n background-color: #1d2124;\n border-color: #171a1d;\n}\n\n.btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus,\n.show > .btn-dark.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5);\n}\n\n.btn-outline-primary {\n color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:hover {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:focus, .btn-outline-primary.focus {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.btn-outline-primary.disabled, .btn-outline-primary:disabled {\n color: #007bff;\n background-color: transparent;\n}\n\n.btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active,\n.show > .btn-outline-primary.dropdown-toggle {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-primary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.btn-outline-secondary {\n color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:hover {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:focus, .btn-outline-secondary.focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-outline-secondary.disabled, .btn-outline-secondary:disabled {\n color: #6c757d;\n background-color: transparent;\n}\n\n.btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active,\n.show > .btn-outline-secondary.dropdown-toggle {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-secondary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-outline-success {\n color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:hover {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:focus, .btn-outline-success.focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-outline-success.disabled, .btn-outline-success:disabled {\n color: #28a745;\n background-color: transparent;\n}\n\n.btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active,\n.show > .btn-outline-success.dropdown-toggle {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-success.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-outline-info {\n color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:hover {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:focus, .btn-outline-info.focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-outline-info.disabled, .btn-outline-info:disabled {\n color: #17a2b8;\n background-color: transparent;\n}\n\n.btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active,\n.show > .btn-outline-info.dropdown-toggle {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-info.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-outline-warning {\n color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:hover {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:focus, .btn-outline-warning.focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-outline-warning.disabled, .btn-outline-warning:disabled {\n color: #ffc107;\n background-color: transparent;\n}\n\n.btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active,\n.show > .btn-outline-warning.dropdown-toggle {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-warning.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-outline-danger {\n color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:hover {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:focus, .btn-outline-danger.focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-outline-danger.disabled, .btn-outline-danger:disabled {\n color: #dc3545;\n background-color: transparent;\n}\n\n.btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active,\n.show > .btn-outline-danger.dropdown-toggle {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-danger.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-outline-light {\n color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:hover {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:focus, .btn-outline-light.focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-outline-light.disabled, .btn-outline-light:disabled {\n color: #f8f9fa;\n background-color: transparent;\n}\n\n.btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active,\n.show > .btn-outline-light.dropdown-toggle {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-light.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-outline-dark {\n color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:hover {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:focus, .btn-outline-dark.focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-outline-dark.disabled, .btn-outline-dark:disabled {\n color: #343a40;\n background-color: transparent;\n}\n\n.btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active,\n.show > .btn-outline-dark.dropdown-toggle {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-dark.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-link {\n font-weight: 400;\n color: #007bff;\n text-decoration: none;\n}\n\n.btn-link:hover {\n color: #0056b3;\n text-decoration: underline;\n}\n\n.btn-link:focus, .btn-link.focus {\n text-decoration: underline;\n}\n\n.btn-link:disabled, .btn-link.disabled {\n color: #6c757d;\n pointer-events: none;\n}\n\n.btn-lg, .btn-group-lg > .btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\n.btn-sm, .btn-group-sm > .btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\n.btn-block {\n display: block;\n width: 100%;\n}\n\n.btn-block + .btn-block {\n margin-top: 0.5rem;\n}\n\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n width: 100%;\n}\n\n.fade {\n transition: opacity 0.15s linear;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .fade {\n transition: none;\n }\n}\n\n.fade:not(.show) {\n opacity: 0;\n}\n\n.collapse:not(.show) {\n display: none;\n}\n\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n transition: height 0.35s ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .collapsing {\n transition: none;\n }\n}\n\n.dropup,\n.dropright,\n.dropdown,\n.dropleft {\n position: relative;\n}\n\n.dropdown-toggle {\n white-space: nowrap;\n}\n\n.dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid;\n border-right: 0.3em solid transparent;\n border-bottom: 0;\n border-left: 0.3em solid transparent;\n}\n\n.dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 10rem;\n padding: 0.5rem 0;\n margin: 0.125rem 0 0;\n font-size: 1rem;\n color: #212529;\n text-align: left;\n list-style: none;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 0.25rem;\n}\n\n.dropdown-menu-left {\n right: auto;\n left: 0;\n}\n\n.dropdown-menu-right {\n right: 0;\n left: auto;\n}\n\n@media (min-width: 576px) {\n .dropdown-menu-sm-left {\n right: auto;\n left: 0;\n }\n .dropdown-menu-sm-right {\n right: 0;\n left: auto;\n }\n}\n\n@media (min-width: 768px) {\n .dropdown-menu-md-left {\n right: auto;\n left: 0;\n }\n .dropdown-menu-md-right {\n right: 0;\n left: auto;\n }\n}\n\n@media (min-width: 992px) {\n .dropdown-menu-lg-left {\n right: auto;\n left: 0;\n }\n .dropdown-menu-lg-right {\n right: 0;\n left: auto;\n }\n}\n\n@media (min-width: 1200px) {\n .dropdown-menu-xl-left {\n right: auto;\n left: 0;\n }\n .dropdown-menu-xl-right {\n right: 0;\n left: auto;\n }\n}\n\n.dropup .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-top: 0;\n margin-bottom: 0.125rem;\n}\n\n.dropup .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0;\n border-right: 0.3em solid transparent;\n border-bottom: 0.3em solid;\n border-left: 0.3em solid transparent;\n}\n\n.dropup .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropright .dropdown-menu {\n top: 0;\n right: auto;\n left: 100%;\n margin-top: 0;\n margin-left: 0.125rem;\n}\n\n.dropright .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid transparent;\n border-right: 0;\n border-bottom: 0.3em solid transparent;\n border-left: 0.3em solid;\n}\n\n.dropright .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropright .dropdown-toggle::after {\n vertical-align: 0;\n}\n\n.dropleft .dropdown-menu {\n top: 0;\n right: 100%;\n left: auto;\n margin-top: 0;\n margin-right: 0.125rem;\n}\n\n.dropleft .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n}\n\n.dropleft .dropdown-toggle::after {\n display: none;\n}\n\n.dropleft .dropdown-toggle::before {\n display: inline-block;\n margin-right: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid transparent;\n border-right: 0.3em solid;\n border-bottom: 0.3em solid transparent;\n}\n\n.dropleft .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropleft .dropdown-toggle::before {\n vertical-align: 0;\n}\n\n.dropdown-menu[x-placement^=\"top\"], .dropdown-menu[x-placement^=\"right\"], .dropdown-menu[x-placement^=\"bottom\"], .dropdown-menu[x-placement^=\"left\"] {\n right: auto;\n bottom: auto;\n}\n\n.dropdown-divider {\n height: 0;\n margin: 0.5rem 0;\n overflow: hidden;\n border-top: 1px solid #e9ecef;\n}\n\n.dropdown-item {\n display: block;\n width: 100%;\n padding: 0.25rem 1.5rem;\n clear: both;\n font-weight: 400;\n color: #212529;\n text-align: inherit;\n white-space: nowrap;\n background-color: transparent;\n border: 0;\n}\n\n.dropdown-item:hover, .dropdown-item:focus {\n color: #16181b;\n text-decoration: none;\n background-color: #f8f9fa;\n}\n\n.dropdown-item.active, .dropdown-item:active {\n color: #fff;\n text-decoration: none;\n background-color: #007bff;\n}\n\n.dropdown-item.disabled, .dropdown-item:disabled {\n color: #6c757d;\n pointer-events: none;\n background-color: transparent;\n}\n\n.dropdown-menu.show {\n display: block;\n}\n\n.dropdown-header {\n display: block;\n padding: 0.5rem 1.5rem;\n margin-bottom: 0;\n font-size: 0.875rem;\n color: #6c757d;\n white-space: nowrap;\n}\n\n.dropdown-item-text {\n display: block;\n padding: 0.25rem 1.5rem;\n color: #212529;\n}\n\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-flex;\n vertical-align: middle;\n}\n\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n flex: 1 1 auto;\n}\n\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover {\n z-index: 1;\n}\n\n.btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active,\n.btn-group-vertical > .btn:focus,\n.btn-group-vertical > .btn:active,\n.btn-group-vertical > .btn.active {\n z-index: 1;\n}\n\n.btn-toolbar {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-start;\n}\n\n.btn-toolbar .input-group {\n width: auto;\n}\n\n.btn-group > .btn:not(:first-child),\n.btn-group > .btn-group:not(:first-child) {\n margin-left: -1px;\n}\n\n.btn-group > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group > .btn-group:not(:last-child) > .btn {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.btn-group > .btn:not(:first-child),\n.btn-group > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.dropdown-toggle-split {\n padding-right: 0.5625rem;\n padding-left: 0.5625rem;\n}\n\n.dropdown-toggle-split::after,\n.dropup .dropdown-toggle-split::after,\n.dropright .dropdown-toggle-split::after {\n margin-left: 0;\n}\n\n.dropleft .dropdown-toggle-split::before {\n margin-right: 0;\n}\n\n.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split {\n padding-right: 0.375rem;\n padding-left: 0.375rem;\n}\n\n.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split {\n padding-right: 0.75rem;\n padding-left: 0.75rem;\n}\n\n.btn-group-vertical {\n flex-direction: column;\n align-items: flex-start;\n justify-content: center;\n}\n\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group {\n width: 100%;\n}\n\n.btn-group-vertical > .btn:not(:first-child),\n.btn-group-vertical > .btn-group:not(:first-child) {\n margin-top: -1px;\n}\n\n.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group-vertical > .btn-group:not(:last-child) > .btn {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.btn-group-vertical > .btn:not(:first-child),\n.btn-group-vertical > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.btn-group-toggle > .btn,\n.btn-group-toggle > .btn-group > .btn {\n margin-bottom: 0;\n}\n\n.btn-group-toggle > .btn input[type=\"radio\"],\n.btn-group-toggle > .btn input[type=\"checkbox\"],\n.btn-group-toggle > .btn-group > .btn input[type=\"radio\"],\n.btn-group-toggle > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n\n.input-group {\n position: relative;\n display: flex;\n flex-wrap: wrap;\n align-items: stretch;\n width: 100%;\n}\n\n.input-group > .form-control,\n.input-group > .form-control-plaintext,\n.input-group > .custom-select,\n.input-group > .custom-file {\n position: relative;\n flex: 1 1 auto;\n width: 1%;\n min-width: 0;\n margin-bottom: 0;\n}\n\n.input-group > .form-control + .form-control,\n.input-group > .form-control + .custom-select,\n.input-group > .form-control + .custom-file,\n.input-group > .form-control-plaintext + .form-control,\n.input-group > .form-control-plaintext + .custom-select,\n.input-group > .form-control-plaintext + .custom-file,\n.input-group > .custom-select + .form-control,\n.input-group > .custom-select + .custom-select,\n.input-group > .custom-select + .custom-file,\n.input-group > .custom-file + .form-control,\n.input-group > .custom-file + .custom-select,\n.input-group > .custom-file + .custom-file {\n margin-left: -1px;\n}\n\n.input-group > .form-control:focus,\n.input-group > .custom-select:focus,\n.input-group > .custom-file .custom-file-input:focus ~ .custom-file-label {\n z-index: 3;\n}\n\n.input-group > .custom-file .custom-file-input:focus {\n z-index: 4;\n}\n\n.input-group > .form-control:not(:last-child),\n.input-group > .custom-select:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .form-control:not(:first-child),\n.input-group > .custom-select:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.input-group > .custom-file {\n display: flex;\n align-items: center;\n}\n\n.input-group > .custom-file:not(:last-child) .custom-file-label,\n.input-group > .custom-file:not(:last-child) .custom-file-label::after {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .custom-file:not(:first-child) .custom-file-label {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.input-group-prepend,\n.input-group-append {\n display: flex;\n}\n\n.input-group-prepend .btn,\n.input-group-append .btn {\n position: relative;\n z-index: 2;\n}\n\n.input-group-prepend .btn:focus,\n.input-group-append .btn:focus {\n z-index: 3;\n}\n\n.input-group-prepend .btn + .btn,\n.input-group-prepend .btn + .input-group-text,\n.input-group-prepend .input-group-text + .input-group-text,\n.input-group-prepend .input-group-text + .btn,\n.input-group-append .btn + .btn,\n.input-group-append .btn + .input-group-text,\n.input-group-append .input-group-text + .input-group-text,\n.input-group-append .input-group-text + .btn {\n margin-left: -1px;\n}\n\n.input-group-prepend {\n margin-right: -1px;\n}\n\n.input-group-append {\n margin-left: -1px;\n}\n\n.input-group-text {\n display: flex;\n align-items: center;\n padding: 0.375rem 0.75rem;\n margin-bottom: 0;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n text-align: center;\n white-space: nowrap;\n background-color: #e9ecef;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n}\n\n.input-group-text input[type=\"radio\"],\n.input-group-text input[type=\"checkbox\"] {\n margin-top: 0;\n}\n\n.input-group-lg > .form-control:not(textarea),\n.input-group-lg > .custom-select {\n height: calc(1.5em + 1rem + 2px);\n}\n\n.input-group-lg > .form-control,\n.input-group-lg > .custom-select,\n.input-group-lg > .input-group-prepend > .input-group-text,\n.input-group-lg > .input-group-append > .input-group-text,\n.input-group-lg > .input-group-prepend > .btn,\n.input-group-lg > .input-group-append > .btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\n.input-group-sm > .form-control:not(textarea),\n.input-group-sm > .custom-select {\n height: calc(1.5em + 0.5rem + 2px);\n}\n\n.input-group-sm > .form-control,\n.input-group-sm > .custom-select,\n.input-group-sm > .input-group-prepend > .input-group-text,\n.input-group-sm > .input-group-append > .input-group-text,\n.input-group-sm > .input-group-prepend > .btn,\n.input-group-sm > .input-group-append > .btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\n.input-group-lg > .custom-select,\n.input-group-sm > .custom-select {\n padding-right: 1.75rem;\n}\n\n.input-group > .input-group-prepend > .btn,\n.input-group > .input-group-prepend > .input-group-text,\n.input-group > .input-group-append:not(:last-child) > .btn,\n.input-group > .input-group-append:not(:last-child) > .input-group-text,\n.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .input-group-append > .btn,\n.input-group > .input-group-append > .input-group-text,\n.input-group > .input-group-prepend:not(:first-child) > .btn,\n.input-group > .input-group-prepend:not(:first-child) > .input-group-text,\n.input-group > .input-group-prepend:first-child > .btn:not(:first-child),\n.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.custom-control {\n position: relative;\n display: block;\n min-height: 1.5rem;\n padding-left: 1.5rem;\n}\n\n.custom-control-inline {\n display: inline-flex;\n margin-right: 1rem;\n}\n\n.custom-control-input {\n position: absolute;\n left: 0;\n z-index: -1;\n width: 1rem;\n height: 1.25rem;\n opacity: 0;\n}\n\n.custom-control-input:checked ~ .custom-control-label::before {\n color: #fff;\n border-color: #007bff;\n background-color: #007bff;\n}\n\n.custom-control-input:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-control-input:focus:not(:checked) ~ .custom-control-label::before {\n border-color: #80bdff;\n}\n\n.custom-control-input:not(:disabled):active ~ .custom-control-label::before {\n color: #fff;\n background-color: #b3d7ff;\n border-color: #b3d7ff;\n}\n\n.custom-control-input[disabled] ~ .custom-control-label, .custom-control-input:disabled ~ .custom-control-label {\n color: #6c757d;\n}\n\n.custom-control-input[disabled] ~ .custom-control-label::before, .custom-control-input:disabled ~ .custom-control-label::before {\n background-color: #e9ecef;\n}\n\n.custom-control-label {\n position: relative;\n margin-bottom: 0;\n vertical-align: top;\n}\n\n.custom-control-label::before {\n position: absolute;\n top: 0.25rem;\n left: -1.5rem;\n display: block;\n width: 1rem;\n height: 1rem;\n pointer-events: none;\n content: \"\";\n background-color: #fff;\n border: #adb5bd solid 1px;\n}\n\n.custom-control-label::after {\n position: absolute;\n top: 0.25rem;\n left: -1.5rem;\n display: block;\n width: 1rem;\n height: 1rem;\n content: \"\";\n background: no-repeat 50% / 50% 50%;\n}\n\n.custom-checkbox .custom-control-label::before {\n border-radius: 0.25rem;\n}\n\n.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e\");\n}\n\n.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before {\n border-color: #007bff;\n background-color: #007bff;\n}\n\n.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e\");\n}\n\n.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-radio .custom-control-label::before {\n border-radius: 50%;\n}\n\n.custom-radio .custom-control-input:checked ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e\");\n}\n\n.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-switch {\n padding-left: 2.25rem;\n}\n\n.custom-switch .custom-control-label::before {\n left: -2.25rem;\n width: 1.75rem;\n pointer-events: all;\n border-radius: 0.5rem;\n}\n\n.custom-switch .custom-control-label::after {\n top: calc(0.25rem + 2px);\n left: calc(-2.25rem + 2px);\n width: calc(1rem - 4px);\n height: calc(1rem - 4px);\n background-color: #adb5bd;\n border-radius: 0.5rem;\n transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-switch .custom-control-label::after {\n transition: none;\n }\n}\n\n.custom-switch .custom-control-input:checked ~ .custom-control-label::after {\n background-color: #fff;\n transform: translateX(0.75rem);\n}\n\n.custom-switch .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-select {\n display: inline-block;\n width: 100%;\n height: calc(1.5em + 0.75rem + 2px);\n padding: 0.375rem 1.75rem 0.375rem 0.75rem;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n vertical-align: middle;\n background: #fff url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e\") no-repeat right 0.75rem center/8px 10px;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n appearance: none;\n}\n\n.custom-select:focus {\n border-color: #80bdff;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-select:focus::-ms-value {\n color: #495057;\n background-color: #fff;\n}\n\n.custom-select[multiple], .custom-select[size]:not([size=\"1\"]) {\n height: auto;\n padding-right: 0.75rem;\n background-image: none;\n}\n\n.custom-select:disabled {\n color: #6c757d;\n background-color: #e9ecef;\n}\n\n.custom-select::-ms-expand {\n display: none;\n}\n\n.custom-select:-moz-focusring {\n color: transparent;\n text-shadow: 0 0 0 #495057;\n}\n\n.custom-select-sm {\n height: calc(1.5em + 0.5rem + 2px);\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n padding-left: 0.5rem;\n font-size: 0.875rem;\n}\n\n.custom-select-lg {\n height: calc(1.5em + 1rem + 2px);\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n padding-left: 1rem;\n font-size: 1.25rem;\n}\n\n.custom-file {\n position: relative;\n display: inline-block;\n width: 100%;\n height: calc(1.5em + 0.75rem + 2px);\n margin-bottom: 0;\n}\n\n.custom-file-input {\n position: relative;\n z-index: 2;\n width: 100%;\n height: calc(1.5em + 0.75rem + 2px);\n margin: 0;\n opacity: 0;\n}\n\n.custom-file-input:focus ~ .custom-file-label {\n border-color: #80bdff;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-file-input[disabled] ~ .custom-file-label,\n.custom-file-input:disabled ~ .custom-file-label {\n background-color: #e9ecef;\n}\n\n.custom-file-input:lang(en) ~ .custom-file-label::after {\n content: \"Browse\";\n}\n\n.custom-file-input ~ .custom-file-label[data-browse]::after {\n content: attr(data-browse);\n}\n\n.custom-file-label {\n position: absolute;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1;\n height: calc(1.5em + 0.75rem + 2px);\n padding: 0.375rem 0.75rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n background-color: #fff;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n}\n\n.custom-file-label::after {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n z-index: 3;\n display: block;\n height: calc(1.5em + 0.75rem);\n padding: 0.375rem 0.75rem;\n line-height: 1.5;\n color: #495057;\n content: \"Browse\";\n background-color: #e9ecef;\n border-left: inherit;\n border-radius: 0 0.25rem 0.25rem 0;\n}\n\n.custom-range {\n width: 100%;\n height: 1.4rem;\n padding: 0;\n background-color: transparent;\n appearance: none;\n}\n\n.custom-range:focus {\n outline: none;\n}\n\n.custom-range:focus::-webkit-slider-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range:focus::-moz-range-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range:focus::-ms-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range::-moz-focus-outer {\n border: 0;\n}\n\n.custom-range::-webkit-slider-thumb {\n width: 1rem;\n height: 1rem;\n margin-top: -0.25rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n appearance: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-range::-webkit-slider-thumb {\n transition: none;\n }\n}\n\n.custom-range::-webkit-slider-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-webkit-slider-runnable-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: #dee2e6;\n border-color: transparent;\n border-radius: 1rem;\n}\n\n.custom-range::-moz-range-thumb {\n width: 1rem;\n height: 1rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n appearance: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-range::-moz-range-thumb {\n transition: none;\n }\n}\n\n.custom-range::-moz-range-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-moz-range-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: #dee2e6;\n border-color: transparent;\n border-radius: 1rem;\n}\n\n.custom-range::-ms-thumb {\n width: 1rem;\n height: 1rem;\n margin-top: 0;\n margin-right: 0.2rem;\n margin-left: 0.2rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n appearance: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-range::-ms-thumb {\n transition: none;\n }\n}\n\n.custom-range::-ms-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-ms-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: transparent;\n border-color: transparent;\n border-width: 0.5rem;\n}\n\n.custom-range::-ms-fill-lower {\n background-color: #dee2e6;\n border-radius: 1rem;\n}\n\n.custom-range::-ms-fill-upper {\n margin-right: 15px;\n background-color: #dee2e6;\n border-radius: 1rem;\n}\n\n.custom-range:disabled::-webkit-slider-thumb {\n background-color: #adb5bd;\n}\n\n.custom-range:disabled::-webkit-slider-runnable-track {\n cursor: default;\n}\n\n.custom-range:disabled::-moz-range-thumb {\n background-color: #adb5bd;\n}\n\n.custom-range:disabled::-moz-range-track {\n cursor: default;\n}\n\n.custom-range:disabled::-ms-thumb {\n background-color: #adb5bd;\n}\n\n.custom-control-label::before,\n.custom-file-label,\n.custom-select {\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-control-label::before,\n .custom-file-label,\n .custom-select {\n transition: none;\n }\n}\n\n.nav {\n display: flex;\n flex-wrap: wrap;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.nav-link {\n display: block;\n padding: 0.5rem 1rem;\n}\n\n.nav-link:hover, .nav-link:focus {\n text-decoration: none;\n}\n\n.nav-link.disabled {\n color: #6c757d;\n pointer-events: none;\n cursor: default;\n}\n\n.nav-tabs {\n border-bottom: 1px solid #dee2e6;\n}\n\n.nav-tabs .nav-item {\n margin-bottom: -1px;\n}\n\n.nav-tabs .nav-link {\n border: 1px solid transparent;\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {\n border-color: #e9ecef #e9ecef #dee2e6;\n}\n\n.nav-tabs .nav-link.disabled {\n color: #6c757d;\n background-color: transparent;\n border-color: transparent;\n}\n\n.nav-tabs .nav-link.active,\n.nav-tabs .nav-item.show .nav-link {\n color: #495057;\n background-color: #fff;\n border-color: #dee2e6 #dee2e6 #fff;\n}\n\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.nav-pills .nav-link {\n border-radius: 0.25rem;\n}\n\n.nav-pills .nav-link.active,\n.nav-pills .show > .nav-link {\n color: #fff;\n background-color: #007bff;\n}\n\n.nav-fill .nav-item {\n flex: 1 1 auto;\n text-align: center;\n}\n\n.nav-justified .nav-item {\n flex-basis: 0;\n flex-grow: 1;\n text-align: center;\n}\n\n.tab-content > .tab-pane {\n display: none;\n}\n\n.tab-content > .active {\n display: block;\n}\n\n.navbar {\n position: relative;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n padding: 0.5rem 1rem;\n}\n\n.navbar .container,\n.navbar .container-fluid, .navbar .container-sm, .navbar .container-md, .navbar .container-lg, .navbar .container-xl {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n}\n\n.navbar-brand {\n display: inline-block;\n padding-top: 0.3125rem;\n padding-bottom: 0.3125rem;\n margin-right: 1rem;\n font-size: 1.25rem;\n line-height: inherit;\n white-space: nowrap;\n}\n\n.navbar-brand:hover, .navbar-brand:focus {\n text-decoration: none;\n}\n\n.navbar-nav {\n display: flex;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.navbar-nav .nav-link {\n padding-right: 0;\n padding-left: 0;\n}\n\n.navbar-nav .dropdown-menu {\n position: static;\n float: none;\n}\n\n.navbar-text {\n display: inline-block;\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n}\n\n.navbar-collapse {\n flex-basis: 100%;\n flex-grow: 1;\n align-items: center;\n}\n\n.navbar-toggler {\n padding: 0.25rem 0.75rem;\n font-size: 1.25rem;\n line-height: 1;\n background-color: transparent;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n}\n\n.navbar-toggler:hover, .navbar-toggler:focus {\n text-decoration: none;\n}\n\n.navbar-toggler-icon {\n display: inline-block;\n width: 1.5em;\n height: 1.5em;\n vertical-align: middle;\n content: \"\";\n background: no-repeat center center;\n background-size: 100% 100%;\n}\n\n@media (max-width: 575.98px) {\n .navbar-expand-sm > .container,\n .navbar-expand-sm > .container-fluid, .navbar-expand-sm > .container-sm, .navbar-expand-sm > .container-md, .navbar-expand-sm > .container-lg, .navbar-expand-sm > .container-xl {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 576px) {\n .navbar-expand-sm {\n flex-flow: row nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-sm .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-sm .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-sm .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-sm > .container,\n .navbar-expand-sm > .container-fluid, .navbar-expand-sm > .container-sm, .navbar-expand-sm > .container-md, .navbar-expand-sm > .container-lg, .navbar-expand-sm > .container-xl {\n flex-wrap: nowrap;\n }\n .navbar-expand-sm .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-sm .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 767.98px) {\n .navbar-expand-md > .container,\n .navbar-expand-md > .container-fluid, .navbar-expand-md > .container-sm, .navbar-expand-md > .container-md, .navbar-expand-md > .container-lg, .navbar-expand-md > .container-xl {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 768px) {\n .navbar-expand-md {\n flex-flow: row nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-md .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-md .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-md .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-md > .container,\n .navbar-expand-md > .container-fluid, .navbar-expand-md > .container-sm, .navbar-expand-md > .container-md, .navbar-expand-md > .container-lg, .navbar-expand-md > .container-xl {\n flex-wrap: nowrap;\n }\n .navbar-expand-md .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-md .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 991.98px) {\n .navbar-expand-lg > .container,\n .navbar-expand-lg > .container-fluid, .navbar-expand-lg > .container-sm, .navbar-expand-lg > .container-md, .navbar-expand-lg > .container-lg, .navbar-expand-lg > .container-xl {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 992px) {\n .navbar-expand-lg {\n flex-flow: row nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-lg .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-lg .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-lg .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-lg > .container,\n .navbar-expand-lg > .container-fluid, .navbar-expand-lg > .container-sm, .navbar-expand-lg > .container-md, .navbar-expand-lg > .container-lg, .navbar-expand-lg > .container-xl {\n flex-wrap: nowrap;\n }\n .navbar-expand-lg .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-lg .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 1199.98px) {\n .navbar-expand-xl > .container,\n .navbar-expand-xl > .container-fluid, .navbar-expand-xl > .container-sm, .navbar-expand-xl > .container-md, .navbar-expand-xl > .container-lg, .navbar-expand-xl > .container-xl {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 1200px) {\n .navbar-expand-xl {\n flex-flow: row nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-xl .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-xl .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-xl .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-xl > .container,\n .navbar-expand-xl > .container-fluid, .navbar-expand-xl > .container-sm, .navbar-expand-xl > .container-md, .navbar-expand-xl > .container-lg, .navbar-expand-xl > .container-xl {\n flex-wrap: nowrap;\n }\n .navbar-expand-xl .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-xl .navbar-toggler {\n display: none;\n }\n}\n\n.navbar-expand {\n flex-flow: row nowrap;\n justify-content: flex-start;\n}\n\n.navbar-expand > .container,\n.navbar-expand > .container-fluid, .navbar-expand > .container-sm, .navbar-expand > .container-md, .navbar-expand > .container-lg, .navbar-expand > .container-xl {\n padding-right: 0;\n padding-left: 0;\n}\n\n.navbar-expand .navbar-nav {\n flex-direction: row;\n}\n\n.navbar-expand .navbar-nav .dropdown-menu {\n position: absolute;\n}\n\n.navbar-expand .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n}\n\n.navbar-expand > .container,\n.navbar-expand > .container-fluid, .navbar-expand > .container-sm, .navbar-expand > .container-md, .navbar-expand > .container-lg, .navbar-expand > .container-xl {\n flex-wrap: nowrap;\n}\n\n.navbar-expand .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n}\n\n.navbar-expand .navbar-toggler {\n display: none;\n}\n\n.navbar-light .navbar-brand {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-nav .nav-link {\n color: rgba(0, 0, 0, 0.5);\n}\n\n.navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus {\n color: rgba(0, 0, 0, 0.7);\n}\n\n.navbar-light .navbar-nav .nav-link.disabled {\n color: rgba(0, 0, 0, 0.3);\n}\n\n.navbar-light .navbar-nav .show > .nav-link,\n.navbar-light .navbar-nav .active > .nav-link,\n.navbar-light .navbar-nav .nav-link.show,\n.navbar-light .navbar-nav .nav-link.active {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-toggler {\n color: rgba(0, 0, 0, 0.5);\n border-color: rgba(0, 0, 0, 0.1);\n}\n\n.navbar-light .navbar-toggler-icon {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e\");\n}\n\n.navbar-light .navbar-text {\n color: rgba(0, 0, 0, 0.5);\n}\n\n.navbar-light .navbar-text a {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-text a:hover, .navbar-light .navbar-text a:focus {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-dark .navbar-brand {\n color: #fff;\n}\n\n.navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus {\n color: #fff;\n}\n\n.navbar-dark .navbar-nav .nav-link {\n color: rgba(255, 255, 255, 0.5);\n}\n\n.navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus {\n color: rgba(255, 255, 255, 0.75);\n}\n\n.navbar-dark .navbar-nav .nav-link.disabled {\n color: rgba(255, 255, 255, 0.25);\n}\n\n.navbar-dark .navbar-nav .show > .nav-link,\n.navbar-dark .navbar-nav .active > .nav-link,\n.navbar-dark .navbar-nav .nav-link.show,\n.navbar-dark .navbar-nav .nav-link.active {\n color: #fff;\n}\n\n.navbar-dark .navbar-toggler {\n color: rgba(255, 255, 255, 0.5);\n border-color: rgba(255, 255, 255, 0.1);\n}\n\n.navbar-dark .navbar-toggler-icon {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e\");\n}\n\n.navbar-dark .navbar-text {\n color: rgba(255, 255, 255, 0.5);\n}\n\n.navbar-dark .navbar-text a {\n color: #fff;\n}\n\n.navbar-dark .navbar-text a:hover, .navbar-dark .navbar-text a:focus {\n color: #fff;\n}\n\n.card {\n position: relative;\n display: flex;\n flex-direction: column;\n min-width: 0;\n word-wrap: break-word;\n background-color: #fff;\n background-clip: border-box;\n border: 1px solid rgba(0, 0, 0, 0.125);\n border-radius: 0.25rem;\n}\n\n.card > hr {\n margin-right: 0;\n margin-left: 0;\n}\n\n.card > .list-group {\n border-top: inherit;\n border-bottom: inherit;\n}\n\n.card > .list-group:first-child {\n border-top-width: 0;\n border-top-left-radius: calc(0.25rem - 1px);\n border-top-right-radius: calc(0.25rem - 1px);\n}\n\n.card > .list-group:last-child {\n border-bottom-width: 0;\n border-bottom-right-radius: calc(0.25rem - 1px);\n border-bottom-left-radius: calc(0.25rem - 1px);\n}\n\n.card-body {\n flex: 1 1 auto;\n min-height: 1px;\n padding: 1.25rem;\n}\n\n.card-title {\n margin-bottom: 0.75rem;\n}\n\n.card-subtitle {\n margin-top: -0.375rem;\n margin-bottom: 0;\n}\n\n.card-text:last-child {\n margin-bottom: 0;\n}\n\n.card-link:hover {\n text-decoration: none;\n}\n\n.card-link + .card-link {\n margin-left: 1.25rem;\n}\n\n.card-header {\n padding: 0.75rem 1.25rem;\n margin-bottom: 0;\n background-color: rgba(0, 0, 0, 0.03);\n border-bottom: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.card-header:first-child {\n border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0;\n}\n\n.card-header + .list-group .list-group-item:first-child {\n border-top: 0;\n}\n\n.card-footer {\n padding: 0.75rem 1.25rem;\n background-color: rgba(0, 0, 0, 0.03);\n border-top: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.card-footer:last-child {\n border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px);\n}\n\n.card-header-tabs {\n margin-right: -0.625rem;\n margin-bottom: -0.75rem;\n margin-left: -0.625rem;\n border-bottom: 0;\n}\n\n.card-header-pills {\n margin-right: -0.625rem;\n margin-left: -0.625rem;\n}\n\n.card-img-overlay {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n padding: 1.25rem;\n}\n\n.card-img,\n.card-img-top,\n.card-img-bottom {\n flex-shrink: 0;\n width: 100%;\n}\n\n.card-img,\n.card-img-top {\n border-top-left-radius: calc(0.25rem - 1px);\n border-top-right-radius: calc(0.25rem - 1px);\n}\n\n.card-img,\n.card-img-bottom {\n border-bottom-right-radius: calc(0.25rem - 1px);\n border-bottom-left-radius: calc(0.25rem - 1px);\n}\n\n.card-deck .card {\n margin-bottom: 15px;\n}\n\n@media (min-width: 576px) {\n .card-deck {\n display: flex;\n flex-flow: row wrap;\n margin-right: -15px;\n margin-left: -15px;\n }\n .card-deck .card {\n flex: 1 0 0%;\n margin-right: 15px;\n margin-bottom: 0;\n margin-left: 15px;\n }\n}\n\n.card-group > .card {\n margin-bottom: 15px;\n}\n\n@media (min-width: 576px) {\n .card-group {\n display: flex;\n flex-flow: row wrap;\n }\n .card-group > .card {\n flex: 1 0 0%;\n margin-bottom: 0;\n }\n .card-group > .card + .card {\n margin-left: 0;\n border-left: 0;\n }\n .card-group > .card:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n }\n .card-group > .card:not(:last-child) .card-img-top,\n .card-group > .card:not(:last-child) .card-header {\n border-top-right-radius: 0;\n }\n .card-group > .card:not(:last-child) .card-img-bottom,\n .card-group > .card:not(:last-child) .card-footer {\n border-bottom-right-radius: 0;\n }\n .card-group > .card:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n }\n .card-group > .card:not(:first-child) .card-img-top,\n .card-group > .card:not(:first-child) .card-header {\n border-top-left-radius: 0;\n }\n .card-group > .card:not(:first-child) .card-img-bottom,\n .card-group > .card:not(:first-child) .card-footer {\n border-bottom-left-radius: 0;\n }\n}\n\n.card-columns .card {\n margin-bottom: 0.75rem;\n}\n\n@media (min-width: 576px) {\n .card-columns {\n column-count: 3;\n column-gap: 1.25rem;\n orphans: 1;\n widows: 1;\n }\n .card-columns .card {\n display: inline-block;\n width: 100%;\n }\n}\n\n.accordion > .card {\n overflow: hidden;\n}\n\n.accordion > .card:not(:last-of-type) {\n border-bottom: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.accordion > .card:not(:first-of-type) {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.accordion > .card > .card-header {\n border-radius: 0;\n margin-bottom: -1px;\n}\n\n.breadcrumb {\n display: flex;\n flex-wrap: wrap;\n padding: 0.75rem 1rem;\n margin-bottom: 1rem;\n list-style: none;\n background-color: #e9ecef;\n border-radius: 0.25rem;\n}\n\n.breadcrumb-item {\n display: flex;\n}\n\n.breadcrumb-item + .breadcrumb-item {\n padding-left: 0.5rem;\n}\n\n.breadcrumb-item + .breadcrumb-item::before {\n display: inline-block;\n padding-right: 0.5rem;\n color: #6c757d;\n content: \"/\";\n}\n\n.breadcrumb-item + .breadcrumb-item:hover::before {\n text-decoration: underline;\n}\n\n.breadcrumb-item + .breadcrumb-item:hover::before {\n text-decoration: none;\n}\n\n.breadcrumb-item.active {\n color: #6c757d;\n}\n\n.pagination {\n display: flex;\n padding-left: 0;\n list-style: none;\n border-radius: 0.25rem;\n}\n\n.page-link {\n position: relative;\n display: block;\n padding: 0.5rem 0.75rem;\n margin-left: -1px;\n line-height: 1.25;\n color: #007bff;\n background-color: #fff;\n border: 1px solid #dee2e6;\n}\n\n.page-link:hover {\n z-index: 2;\n color: #0056b3;\n text-decoration: none;\n background-color: #e9ecef;\n border-color: #dee2e6;\n}\n\n.page-link:focus {\n z-index: 3;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.page-item:first-child .page-link {\n margin-left: 0;\n border-top-left-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.page-item:last-child .page-link {\n border-top-right-radius: 0.25rem;\n border-bottom-right-radius: 0.25rem;\n}\n\n.page-item.active .page-link {\n z-index: 3;\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.page-item.disabled .page-link {\n color: #6c757d;\n pointer-events: none;\n cursor: auto;\n background-color: #fff;\n border-color: #dee2e6;\n}\n\n.pagination-lg .page-link {\n padding: 0.75rem 1.5rem;\n font-size: 1.25rem;\n line-height: 1.5;\n}\n\n.pagination-lg .page-item:first-child .page-link {\n border-top-left-radius: 0.3rem;\n border-bottom-left-radius: 0.3rem;\n}\n\n.pagination-lg .page-item:last-child .page-link {\n border-top-right-radius: 0.3rem;\n border-bottom-right-radius: 0.3rem;\n}\n\n.pagination-sm .page-link {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n.pagination-sm .page-item:first-child .page-link {\n border-top-left-radius: 0.2rem;\n border-bottom-left-radius: 0.2rem;\n}\n\n.pagination-sm .page-item:last-child .page-link {\n border-top-right-radius: 0.2rem;\n border-bottom-right-radius: 0.2rem;\n}\n\n.badge {\n display: inline-block;\n padding: 0.25em 0.4em;\n font-size: 75%;\n font-weight: 700;\n line-height: 1;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: 0.25rem;\n transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .badge {\n transition: none;\n }\n}\n\na.badge:hover, a.badge:focus {\n text-decoration: none;\n}\n\n.badge:empty {\n display: none;\n}\n\n.btn .badge {\n position: relative;\n top: -1px;\n}\n\n.badge-pill {\n padding-right: 0.6em;\n padding-left: 0.6em;\n border-radius: 10rem;\n}\n\n.badge-primary {\n color: #fff;\n background-color: #007bff;\n}\n\na.badge-primary:hover, a.badge-primary:focus {\n color: #fff;\n background-color: #0062cc;\n}\n\na.badge-primary:focus, a.badge-primary.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.badge-secondary {\n color: #fff;\n background-color: #6c757d;\n}\n\na.badge-secondary:hover, a.badge-secondary:focus {\n color: #fff;\n background-color: #545b62;\n}\n\na.badge-secondary:focus, a.badge-secondary.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.badge-success {\n color: #fff;\n background-color: #28a745;\n}\n\na.badge-success:hover, a.badge-success:focus {\n color: #fff;\n background-color: #1e7e34;\n}\n\na.badge-success:focus, a.badge-success.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.badge-info {\n color: #fff;\n background-color: #17a2b8;\n}\n\na.badge-info:hover, a.badge-info:focus {\n color: #fff;\n background-color: #117a8b;\n}\n\na.badge-info:focus, a.badge-info.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.badge-warning {\n color: #212529;\n background-color: #ffc107;\n}\n\na.badge-warning:hover, a.badge-warning:focus {\n color: #212529;\n background-color: #d39e00;\n}\n\na.badge-warning:focus, a.badge-warning.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.badge-danger {\n color: #fff;\n background-color: #dc3545;\n}\n\na.badge-danger:hover, a.badge-danger:focus {\n color: #fff;\n background-color: #bd2130;\n}\n\na.badge-danger:focus, a.badge-danger.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.badge-light {\n color: #212529;\n background-color: #f8f9fa;\n}\n\na.badge-light:hover, a.badge-light:focus {\n color: #212529;\n background-color: #dae0e5;\n}\n\na.badge-light:focus, a.badge-light.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.badge-dark {\n color: #fff;\n background-color: #343a40;\n}\n\na.badge-dark:hover, a.badge-dark:focus {\n color: #fff;\n background-color: #1d2124;\n}\n\na.badge-dark:focus, a.badge-dark.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.jumbotron {\n padding: 2rem 1rem;\n margin-bottom: 2rem;\n background-color: #e9ecef;\n border-radius: 0.3rem;\n}\n\n@media (min-width: 576px) {\n .jumbotron {\n padding: 4rem 2rem;\n }\n}\n\n.jumbotron-fluid {\n padding-right: 0;\n padding-left: 0;\n border-radius: 0;\n}\n\n.alert {\n position: relative;\n padding: 0.75rem 1.25rem;\n margin-bottom: 1rem;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n}\n\n.alert-heading {\n color: inherit;\n}\n\n.alert-link {\n font-weight: 700;\n}\n\n.alert-dismissible {\n padding-right: 4rem;\n}\n\n.alert-dismissible .close {\n position: absolute;\n top: 0;\n right: 0;\n padding: 0.75rem 1.25rem;\n color: inherit;\n}\n\n.alert-primary {\n color: #004085;\n background-color: #cce5ff;\n border-color: #b8daff;\n}\n\n.alert-primary hr {\n border-top-color: #9fcdff;\n}\n\n.alert-primary .alert-link {\n color: #002752;\n}\n\n.alert-secondary {\n color: #383d41;\n background-color: #e2e3e5;\n border-color: #d6d8db;\n}\n\n.alert-secondary hr {\n border-top-color: #c8cbcf;\n}\n\n.alert-secondary .alert-link {\n color: #202326;\n}\n\n.alert-success {\n color: #155724;\n background-color: #d4edda;\n border-color: #c3e6cb;\n}\n\n.alert-success hr {\n border-top-color: #b1dfbb;\n}\n\n.alert-success .alert-link {\n color: #0b2e13;\n}\n\n.alert-info {\n color: #0c5460;\n background-color: #d1ecf1;\n border-color: #bee5eb;\n}\n\n.alert-info hr {\n border-top-color: #abdde5;\n}\n\n.alert-info .alert-link {\n color: #062c33;\n}\n\n.alert-warning {\n color: #856404;\n background-color: #fff3cd;\n border-color: #ffeeba;\n}\n\n.alert-warning hr {\n border-top-color: #ffe8a1;\n}\n\n.alert-warning .alert-link {\n color: #533f03;\n}\n\n.alert-danger {\n color: #721c24;\n background-color: #f8d7da;\n border-color: #f5c6cb;\n}\n\n.alert-danger hr {\n border-top-color: #f1b0b7;\n}\n\n.alert-danger .alert-link {\n color: #491217;\n}\n\n.alert-light {\n color: #818182;\n background-color: #fefefe;\n border-color: #fdfdfe;\n}\n\n.alert-light hr {\n border-top-color: #ececf6;\n}\n\n.alert-light .alert-link {\n color: #686868;\n}\n\n.alert-dark {\n color: #1b1e21;\n background-color: #d6d8d9;\n border-color: #c6c8ca;\n}\n\n.alert-dark hr {\n border-top-color: #b9bbbe;\n}\n\n.alert-dark .alert-link {\n color: #040505;\n}\n\n@keyframes progress-bar-stripes {\n from {\n background-position: 1rem 0;\n }\n to {\n background-position: 0 0;\n }\n}\n\n.progress {\n display: flex;\n height: 1rem;\n overflow: hidden;\n line-height: 0;\n font-size: 0.75rem;\n background-color: #e9ecef;\n border-radius: 0.25rem;\n}\n\n.progress-bar {\n display: flex;\n flex-direction: column;\n justify-content: center;\n overflow: hidden;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n background-color: #007bff;\n transition: width 0.6s ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .progress-bar {\n transition: none;\n }\n}\n\n.progress-bar-striped {\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: 1rem 1rem;\n}\n\n.progress-bar-animated {\n animation: progress-bar-stripes 1s linear infinite;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .progress-bar-animated {\n animation: none;\n }\n}\n\n.media {\n display: flex;\n align-items: flex-start;\n}\n\n.media-body {\n flex: 1;\n}\n\n.list-group {\n display: flex;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n border-radius: 0.25rem;\n}\n\n.list-group-item-action {\n width: 100%;\n color: #495057;\n text-align: inherit;\n}\n\n.list-group-item-action:hover, .list-group-item-action:focus {\n z-index: 1;\n color: #495057;\n text-decoration: none;\n background-color: #f8f9fa;\n}\n\n.list-group-item-action:active {\n color: #212529;\n background-color: #e9ecef;\n}\n\n.list-group-item {\n position: relative;\n display: block;\n padding: 0.75rem 1.25rem;\n background-color: #fff;\n border: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.list-group-item:first-child {\n border-top-left-radius: inherit;\n border-top-right-radius: inherit;\n}\n\n.list-group-item:last-child {\n border-bottom-right-radius: inherit;\n border-bottom-left-radius: inherit;\n}\n\n.list-group-item.disabled, .list-group-item:disabled {\n color: #6c757d;\n pointer-events: none;\n background-color: #fff;\n}\n\n.list-group-item.active {\n z-index: 2;\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.list-group-item + .list-group-item {\n border-top-width: 0;\n}\n\n.list-group-item + .list-group-item.active {\n margin-top: -1px;\n border-top-width: 1px;\n}\n\n.list-group-horizontal {\n flex-direction: row;\n}\n\n.list-group-horizontal > .list-group-item:first-child {\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n}\n\n.list-group-horizontal > .list-group-item:last-child {\n border-top-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n}\n\n.list-group-horizontal > .list-group-item.active {\n margin-top: 0;\n}\n\n.list-group-horizontal > .list-group-item + .list-group-item {\n border-top-width: 1px;\n border-left-width: 0;\n}\n\n.list-group-horizontal > .list-group-item + .list-group-item.active {\n margin-left: -1px;\n border-left-width: 1px;\n}\n\n@media (min-width: 576px) {\n .list-group-horizontal-sm {\n flex-direction: row;\n }\n .list-group-horizontal-sm > .list-group-item:first-child {\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n }\n .list-group-horizontal-sm > .list-group-item:last-child {\n border-top-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n }\n .list-group-horizontal-sm > .list-group-item.active {\n margin-top: 0;\n }\n .list-group-horizontal-sm > .list-group-item + .list-group-item {\n border-top-width: 1px;\n border-left-width: 0;\n }\n .list-group-horizontal-sm > .list-group-item + .list-group-item.active {\n margin-left: -1px;\n border-left-width: 1px;\n }\n}\n\n@media (min-width: 768px) {\n .list-group-horizontal-md {\n flex-direction: row;\n }\n .list-group-horizontal-md > .list-group-item:first-child {\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n }\n .list-group-horizontal-md > .list-group-item:last-child {\n border-top-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n }\n .list-group-horizontal-md > .list-group-item.active {\n margin-top: 0;\n }\n .list-group-horizontal-md > .list-group-item + .list-group-item {\n border-top-width: 1px;\n border-left-width: 0;\n }\n .list-group-horizontal-md > .list-group-item + .list-group-item.active {\n margin-left: -1px;\n border-left-width: 1px;\n }\n}\n\n@media (min-width: 992px) {\n .list-group-horizontal-lg {\n flex-direction: row;\n }\n .list-group-horizontal-lg > .list-group-item:first-child {\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n }\n .list-group-horizontal-lg > .list-group-item:last-child {\n border-top-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n }\n .list-group-horizontal-lg > .list-group-item.active {\n margin-top: 0;\n }\n .list-group-horizontal-lg > .list-group-item + .list-group-item {\n border-top-width: 1px;\n border-left-width: 0;\n }\n .list-group-horizontal-lg > .list-group-item + .list-group-item.active {\n margin-left: -1px;\n border-left-width: 1px;\n }\n}\n\n@media (min-width: 1200px) {\n .list-group-horizontal-xl {\n flex-direction: row;\n }\n .list-group-horizontal-xl > .list-group-item:first-child {\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n }\n .list-group-horizontal-xl > .list-group-item:last-child {\n border-top-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n }\n .list-group-horizontal-xl > .list-group-item.active {\n margin-top: 0;\n }\n .list-group-horizontal-xl > .list-group-item + .list-group-item {\n border-top-width: 1px;\n border-left-width: 0;\n }\n .list-group-horizontal-xl > .list-group-item + .list-group-item.active {\n margin-left: -1px;\n border-left-width: 1px;\n }\n}\n\n.list-group-flush {\n border-radius: 0;\n}\n\n.list-group-flush > .list-group-item {\n border-width: 0 0 1px;\n}\n\n.list-group-flush > .list-group-item:last-child {\n border-bottom-width: 0;\n}\n\n.list-group-item-primary {\n color: #004085;\n background-color: #b8daff;\n}\n\n.list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus {\n color: #004085;\n background-color: #9fcdff;\n}\n\n.list-group-item-primary.list-group-item-action.active {\n color: #fff;\n background-color: #004085;\n border-color: #004085;\n}\n\n.list-group-item-secondary {\n color: #383d41;\n background-color: #d6d8db;\n}\n\n.list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus {\n color: #383d41;\n background-color: #c8cbcf;\n}\n\n.list-group-item-secondary.list-group-item-action.active {\n color: #fff;\n background-color: #383d41;\n border-color: #383d41;\n}\n\n.list-group-item-success {\n color: #155724;\n background-color: #c3e6cb;\n}\n\n.list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus {\n color: #155724;\n background-color: #b1dfbb;\n}\n\n.list-group-item-success.list-group-item-action.active {\n color: #fff;\n background-color: #155724;\n border-color: #155724;\n}\n\n.list-group-item-info {\n color: #0c5460;\n background-color: #bee5eb;\n}\n\n.list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus {\n color: #0c5460;\n background-color: #abdde5;\n}\n\n.list-group-item-info.list-group-item-action.active {\n color: #fff;\n background-color: #0c5460;\n border-color: #0c5460;\n}\n\n.list-group-item-warning {\n color: #856404;\n background-color: #ffeeba;\n}\n\n.list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus {\n color: #856404;\n background-color: #ffe8a1;\n}\n\n.list-group-item-warning.list-group-item-action.active {\n color: #fff;\n background-color: #856404;\n border-color: #856404;\n}\n\n.list-group-item-danger {\n color: #721c24;\n background-color: #f5c6cb;\n}\n\n.list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus {\n color: #721c24;\n background-color: #f1b0b7;\n}\n\n.list-group-item-danger.list-group-item-action.active {\n color: #fff;\n background-color: #721c24;\n border-color: #721c24;\n}\n\n.list-group-item-light {\n color: #818182;\n background-color: #fdfdfe;\n}\n\n.list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus {\n color: #818182;\n background-color: #ececf6;\n}\n\n.list-group-item-light.list-group-item-action.active {\n color: #fff;\n background-color: #818182;\n border-color: #818182;\n}\n\n.list-group-item-dark {\n color: #1b1e21;\n background-color: #c6c8ca;\n}\n\n.list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus {\n color: #1b1e21;\n background-color: #b9bbbe;\n}\n\n.list-group-item-dark.list-group-item-action.active {\n color: #fff;\n background-color: #1b1e21;\n border-color: #1b1e21;\n}\n\n.close {\n float: right;\n font-size: 1.5rem;\n font-weight: 700;\n line-height: 1;\n color: #000;\n text-shadow: 0 1px 0 #fff;\n opacity: .5;\n}\n\n.close:hover {\n color: #000;\n text-decoration: none;\n}\n\n.close:not(:disabled):not(.disabled):hover, .close:not(:disabled):not(.disabled):focus {\n opacity: .75;\n}\n\nbutton.close {\n padding: 0;\n background-color: transparent;\n border: 0;\n}\n\na.close.disabled {\n pointer-events: none;\n}\n\n.toast {\n max-width: 350px;\n overflow: hidden;\n font-size: 0.875rem;\n background-color: rgba(255, 255, 255, 0.85);\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.1);\n box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.1);\n backdrop-filter: blur(10px);\n opacity: 0;\n border-radius: 0.25rem;\n}\n\n.toast:not(:last-child) {\n margin-bottom: 0.75rem;\n}\n\n.toast.showing {\n opacity: 1;\n}\n\n.toast.show {\n display: block;\n opacity: 1;\n}\n\n.toast.hide {\n display: none;\n}\n\n.toast-header {\n display: flex;\n align-items: center;\n padding: 0.25rem 0.75rem;\n color: #6c757d;\n background-color: rgba(255, 255, 255, 0.85);\n background-clip: padding-box;\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n}\n\n.toast-body {\n padding: 0.75rem;\n}\n\n.modal-open {\n overflow: hidden;\n}\n\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n.modal {\n position: fixed;\n top: 0;\n left: 0;\n z-index: 1050;\n display: none;\n width: 100%;\n height: 100%;\n overflow: hidden;\n outline: 0;\n}\n\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 0.5rem;\n pointer-events: none;\n}\n\n.modal.fade .modal-dialog {\n transition: transform 0.3s ease-out;\n transform: translate(0, -50px);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .modal.fade .modal-dialog {\n transition: none;\n }\n}\n\n.modal.show .modal-dialog {\n transform: none;\n}\n\n.modal.modal-static .modal-dialog {\n transform: scale(1.02);\n}\n\n.modal-dialog-scrollable {\n display: flex;\n max-height: calc(100% - 1rem);\n}\n\n.modal-dialog-scrollable .modal-content {\n max-height: calc(100vh - 1rem);\n overflow: hidden;\n}\n\n.modal-dialog-scrollable .modal-header,\n.modal-dialog-scrollable .modal-footer {\n flex-shrink: 0;\n}\n\n.modal-dialog-scrollable .modal-body {\n overflow-y: auto;\n}\n\n.modal-dialog-centered {\n display: flex;\n align-items: center;\n min-height: calc(100% - 1rem);\n}\n\n.modal-dialog-centered::before {\n display: block;\n height: calc(100vh - 1rem);\n height: min-content;\n content: \"\";\n}\n\n.modal-dialog-centered.modal-dialog-scrollable {\n flex-direction: column;\n justify-content: center;\n height: 100%;\n}\n\n.modal-dialog-centered.modal-dialog-scrollable .modal-content {\n max-height: none;\n}\n\n.modal-dialog-centered.modal-dialog-scrollable::before {\n content: none;\n}\n\n.modal-content {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n pointer-events: auto;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 0.3rem;\n outline: 0;\n}\n\n.modal-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n z-index: 1040;\n width: 100vw;\n height: 100vh;\n background-color: #000;\n}\n\n.modal-backdrop.fade {\n opacity: 0;\n}\n\n.modal-backdrop.show {\n opacity: 0.5;\n}\n\n.modal-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n padding: 1rem 1rem;\n border-bottom: 1px solid #dee2e6;\n border-top-left-radius: calc(0.3rem - 1px);\n border-top-right-radius: calc(0.3rem - 1px);\n}\n\n.modal-header .close {\n padding: 1rem 1rem;\n margin: -1rem -1rem -1rem auto;\n}\n\n.modal-title {\n margin-bottom: 0;\n line-height: 1.5;\n}\n\n.modal-body {\n position: relative;\n flex: 1 1 auto;\n padding: 1rem;\n}\n\n.modal-footer {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: flex-end;\n padding: 0.75rem;\n border-top: 1px solid #dee2e6;\n border-bottom-right-radius: calc(0.3rem - 1px);\n border-bottom-left-radius: calc(0.3rem - 1px);\n}\n\n.modal-footer > * {\n margin: 0.25rem;\n}\n\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n\n@media (min-width: 576px) {\n .modal-dialog {\n max-width: 500px;\n margin: 1.75rem auto;\n }\n .modal-dialog-scrollable {\n max-height: calc(100% - 3.5rem);\n }\n .modal-dialog-scrollable .modal-content {\n max-height: calc(100vh - 3.5rem);\n }\n .modal-dialog-centered {\n min-height: calc(100% - 3.5rem);\n }\n .modal-dialog-centered::before {\n height: calc(100vh - 3.5rem);\n height: min-content;\n }\n .modal-sm {\n max-width: 300px;\n }\n}\n\n@media (min-width: 992px) {\n .modal-lg,\n .modal-xl {\n max-width: 800px;\n }\n}\n\n@media (min-width: 1200px) {\n .modal-xl {\n max-width: 1140px;\n }\n}\n\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-style: normal;\n font-weight: 400;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n white-space: normal;\n line-break: auto;\n font-size: 0.875rem;\n word-wrap: break-word;\n opacity: 0;\n}\n\n.tooltip.show {\n opacity: 0.9;\n}\n\n.tooltip .arrow {\n position: absolute;\n display: block;\n width: 0.8rem;\n height: 0.4rem;\n}\n\n.tooltip .arrow::before {\n position: absolute;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n}\n\n.bs-tooltip-top, .bs-tooltip-auto[x-placement^=\"top\"] {\n padding: 0.4rem 0;\n}\n\n.bs-tooltip-top .arrow, .bs-tooltip-auto[x-placement^=\"top\"] .arrow {\n bottom: 0;\n}\n\n.bs-tooltip-top .arrow::before, .bs-tooltip-auto[x-placement^=\"top\"] .arrow::before {\n top: 0;\n border-width: 0.4rem 0.4rem 0;\n border-top-color: #000;\n}\n\n.bs-tooltip-right, .bs-tooltip-auto[x-placement^=\"right\"] {\n padding: 0 0.4rem;\n}\n\n.bs-tooltip-right .arrow, .bs-tooltip-auto[x-placement^=\"right\"] .arrow {\n left: 0;\n width: 0.4rem;\n height: 0.8rem;\n}\n\n.bs-tooltip-right .arrow::before, .bs-tooltip-auto[x-placement^=\"right\"] .arrow::before {\n right: 0;\n border-width: 0.4rem 0.4rem 0.4rem 0;\n border-right-color: #000;\n}\n\n.bs-tooltip-bottom, .bs-tooltip-auto[x-placement^=\"bottom\"] {\n padding: 0.4rem 0;\n}\n\n.bs-tooltip-bottom .arrow, .bs-tooltip-auto[x-placement^=\"bottom\"] .arrow {\n top: 0;\n}\n\n.bs-tooltip-bottom .arrow::before, .bs-tooltip-auto[x-placement^=\"bottom\"] .arrow::before {\n bottom: 0;\n border-width: 0 0.4rem 0.4rem;\n border-bottom-color: #000;\n}\n\n.bs-tooltip-left, .bs-tooltip-auto[x-placement^=\"left\"] {\n padding: 0 0.4rem;\n}\n\n.bs-tooltip-left .arrow, .bs-tooltip-auto[x-placement^=\"left\"] .arrow {\n right: 0;\n width: 0.4rem;\n height: 0.8rem;\n}\n\n.bs-tooltip-left .arrow::before, .bs-tooltip-auto[x-placement^=\"left\"] .arrow::before {\n left: 0;\n border-width: 0.4rem 0 0.4rem 0.4rem;\n border-left-color: #000;\n}\n\n.tooltip-inner {\n max-width: 200px;\n padding: 0.25rem 0.5rem;\n color: #fff;\n text-align: center;\n background-color: #000;\n border-radius: 0.25rem;\n}\n\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: block;\n max-width: 276px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-style: normal;\n font-weight: 400;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n white-space: normal;\n line-break: auto;\n font-size: 0.875rem;\n word-wrap: break-word;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 0.3rem;\n}\n\n.popover .arrow {\n position: absolute;\n display: block;\n width: 1rem;\n height: 0.5rem;\n margin: 0 0.3rem;\n}\n\n.popover .arrow::before, .popover .arrow::after {\n position: absolute;\n display: block;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n}\n\n.bs-popover-top, .bs-popover-auto[x-placement^=\"top\"] {\n margin-bottom: 0.5rem;\n}\n\n.bs-popover-top > .arrow, .bs-popover-auto[x-placement^=\"top\"] > .arrow {\n bottom: calc(-0.5rem - 1px);\n}\n\n.bs-popover-top > .arrow::before, .bs-popover-auto[x-placement^=\"top\"] > .arrow::before {\n bottom: 0;\n border-width: 0.5rem 0.5rem 0;\n border-top-color: rgba(0, 0, 0, 0.25);\n}\n\n.bs-popover-top > .arrow::after, .bs-popover-auto[x-placement^=\"top\"] > .arrow::after {\n bottom: 1px;\n border-width: 0.5rem 0.5rem 0;\n border-top-color: #fff;\n}\n\n.bs-popover-right, .bs-popover-auto[x-placement^=\"right\"] {\n margin-left: 0.5rem;\n}\n\n.bs-popover-right > .arrow, .bs-popover-auto[x-placement^=\"right\"] > .arrow {\n left: calc(-0.5rem - 1px);\n width: 0.5rem;\n height: 1rem;\n margin: 0.3rem 0;\n}\n\n.bs-popover-right > .arrow::before, .bs-popover-auto[x-placement^=\"right\"] > .arrow::before {\n left: 0;\n border-width: 0.5rem 0.5rem 0.5rem 0;\n border-right-color: rgba(0, 0, 0, 0.25);\n}\n\n.bs-popover-right > .arrow::after, .bs-popover-auto[x-placement^=\"right\"] > .arrow::after {\n left: 1px;\n border-width: 0.5rem 0.5rem 0.5rem 0;\n border-right-color: #fff;\n}\n\n.bs-popover-bottom, .bs-popover-auto[x-placement^=\"bottom\"] {\n margin-top: 0.5rem;\n}\n\n.bs-popover-bottom > .arrow, .bs-popover-auto[x-placement^=\"bottom\"] > .arrow {\n top: calc(-0.5rem - 1px);\n}\n\n.bs-popover-bottom > .arrow::before, .bs-popover-auto[x-placement^=\"bottom\"] > .arrow::before {\n top: 0;\n border-width: 0 0.5rem 0.5rem 0.5rem;\n border-bottom-color: rgba(0, 0, 0, 0.25);\n}\n\n.bs-popover-bottom > .arrow::after, .bs-popover-auto[x-placement^=\"bottom\"] > .arrow::after {\n top: 1px;\n border-width: 0 0.5rem 0.5rem 0.5rem;\n border-bottom-color: #fff;\n}\n\n.bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^=\"bottom\"] .popover-header::before {\n position: absolute;\n top: 0;\n left: 50%;\n display: block;\n width: 1rem;\n margin-left: -0.5rem;\n content: \"\";\n border-bottom: 1px solid #f7f7f7;\n}\n\n.bs-popover-left, .bs-popover-auto[x-placement^=\"left\"] {\n margin-right: 0.5rem;\n}\n\n.bs-popover-left > .arrow, .bs-popover-auto[x-placement^=\"left\"] > .arrow {\n right: calc(-0.5rem - 1px);\n width: 0.5rem;\n height: 1rem;\n margin: 0.3rem 0;\n}\n\n.bs-popover-left > .arrow::before, .bs-popover-auto[x-placement^=\"left\"] > .arrow::before {\n right: 0;\n border-width: 0.5rem 0 0.5rem 0.5rem;\n border-left-color: rgba(0, 0, 0, 0.25);\n}\n\n.bs-popover-left > .arrow::after, .bs-popover-auto[x-placement^=\"left\"] > .arrow::after {\n right: 1px;\n border-width: 0.5rem 0 0.5rem 0.5rem;\n border-left-color: #fff;\n}\n\n.popover-header {\n padding: 0.5rem 0.75rem;\n margin-bottom: 0;\n font-size: 1rem;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-top-left-radius: calc(0.3rem - 1px);\n border-top-right-radius: calc(0.3rem - 1px);\n}\n\n.popover-header:empty {\n display: none;\n}\n\n.popover-body {\n padding: 0.5rem 0.75rem;\n color: #212529;\n}\n\n.carousel {\n position: relative;\n}\n\n.carousel.pointer-event {\n touch-action: pan-y;\n}\n\n.carousel-inner {\n position: relative;\n width: 100%;\n overflow: hidden;\n}\n\n.carousel-inner::after {\n display: block;\n clear: both;\n content: \"\";\n}\n\n.carousel-item {\n position: relative;\n display: none;\n float: left;\n width: 100%;\n margin-right: -100%;\n backface-visibility: hidden;\n transition: transform 0.6s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .carousel-item {\n transition: none;\n }\n}\n\n.carousel-item.active,\n.carousel-item-next,\n.carousel-item-prev {\n display: block;\n}\n\n.carousel-item-next:not(.carousel-item-left),\n.active.carousel-item-right {\n transform: translateX(100%);\n}\n\n.carousel-item-prev:not(.carousel-item-right),\n.active.carousel-item-left {\n transform: translateX(-100%);\n}\n\n.carousel-fade .carousel-item {\n opacity: 0;\n transition-property: opacity;\n transform: none;\n}\n\n.carousel-fade .carousel-item.active,\n.carousel-fade .carousel-item-next.carousel-item-left,\n.carousel-fade .carousel-item-prev.carousel-item-right {\n z-index: 1;\n opacity: 1;\n}\n\n.carousel-fade .active.carousel-item-left,\n.carousel-fade .active.carousel-item-right {\n z-index: 0;\n opacity: 0;\n transition: opacity 0s 0.6s;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .carousel-fade .active.carousel-item-left,\n .carousel-fade .active.carousel-item-right {\n transition: none;\n }\n}\n\n.carousel-control-prev,\n.carousel-control-next {\n position: absolute;\n top: 0;\n bottom: 0;\n z-index: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 15%;\n color: #fff;\n text-align: center;\n opacity: 0.5;\n transition: opacity 0.15s ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .carousel-control-prev,\n .carousel-control-next {\n transition: none;\n }\n}\n\n.carousel-control-prev:hover, .carousel-control-prev:focus,\n.carousel-control-next:hover,\n.carousel-control-next:focus {\n color: #fff;\n text-decoration: none;\n outline: 0;\n opacity: 0.9;\n}\n\n.carousel-control-prev {\n left: 0;\n}\n\n.carousel-control-next {\n right: 0;\n}\n\n.carousel-control-prev-icon,\n.carousel-control-next-icon {\n display: inline-block;\n width: 20px;\n height: 20px;\n background: no-repeat 50% / 100% 100%;\n}\n\n.carousel-control-prev-icon {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e\");\n}\n\n.carousel-control-next-icon {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e\");\n}\n\n.carousel-indicators {\n position: absolute;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 15;\n display: flex;\n justify-content: center;\n padding-left: 0;\n margin-right: 15%;\n margin-left: 15%;\n list-style: none;\n}\n\n.carousel-indicators li {\n box-sizing: content-box;\n flex: 0 1 auto;\n width: 30px;\n height: 3px;\n margin-right: 3px;\n margin-left: 3px;\n text-indent: -999px;\n cursor: pointer;\n background-color: #fff;\n background-clip: padding-box;\n border-top: 10px solid transparent;\n border-bottom: 10px solid transparent;\n opacity: .5;\n transition: opacity 0.6s ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .carousel-indicators li {\n transition: none;\n }\n}\n\n.carousel-indicators .active {\n opacity: 1;\n}\n\n.carousel-caption {\n position: absolute;\n right: 15%;\n bottom: 20px;\n left: 15%;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: #fff;\n text-align: center;\n}\n\n@keyframes spinner-border {\n to {\n transform: rotate(360deg);\n }\n}\n\n.spinner-border {\n display: inline-block;\n width: 2rem;\n height: 2rem;\n vertical-align: text-bottom;\n border: 0.25em solid currentColor;\n border-right-color: transparent;\n border-radius: 50%;\n animation: spinner-border .75s linear infinite;\n}\n\n.spinner-border-sm {\n width: 1rem;\n height: 1rem;\n border-width: 0.2em;\n}\n\n@keyframes spinner-grow {\n 0% {\n transform: scale(0);\n }\n 50% {\n opacity: 1;\n transform: none;\n }\n}\n\n.spinner-grow {\n display: inline-block;\n width: 2rem;\n height: 2rem;\n vertical-align: text-bottom;\n background-color: currentColor;\n border-radius: 50%;\n opacity: 0;\n animation: spinner-grow .75s linear infinite;\n}\n\n.spinner-grow-sm {\n width: 1rem;\n height: 1rem;\n}\n\n.align-baseline {\n vertical-align: baseline !important;\n}\n\n.align-top {\n vertical-align: top !important;\n}\n\n.align-middle {\n vertical-align: middle !important;\n}\n\n.align-bottom {\n vertical-align: bottom !important;\n}\n\n.align-text-bottom {\n vertical-align: text-bottom !important;\n}\n\n.align-text-top {\n vertical-align: text-top !important;\n}\n\n.bg-primary {\n background-color: #007bff !important;\n}\n\na.bg-primary:hover, a.bg-primary:focus,\nbutton.bg-primary:hover,\nbutton.bg-primary:focus {\n background-color: #0062cc !important;\n}\n\n.bg-secondary {\n background-color: #6c757d !important;\n}\n\na.bg-secondary:hover, a.bg-secondary:focus,\nbutton.bg-secondary:hover,\nbutton.bg-secondary:focus {\n background-color: #545b62 !important;\n}\n\n.bg-success {\n background-color: #28a745 !important;\n}\n\na.bg-success:hover, a.bg-success:focus,\nbutton.bg-success:hover,\nbutton.bg-success:focus {\n background-color: #1e7e34 !important;\n}\n\n.bg-info {\n background-color: #17a2b8 !important;\n}\n\na.bg-info:hover, a.bg-info:focus,\nbutton.bg-info:hover,\nbutton.bg-info:focus {\n background-color: #117a8b !important;\n}\n\n.bg-warning {\n background-color: #ffc107 !important;\n}\n\na.bg-warning:hover, a.bg-warning:focus,\nbutton.bg-warning:hover,\nbutton.bg-warning:focus {\n background-color: #d39e00 !important;\n}\n\n.bg-danger {\n background-color: #dc3545 !important;\n}\n\na.bg-danger:hover, a.bg-danger:focus,\nbutton.bg-danger:hover,\nbutton.bg-danger:focus {\n background-color: #bd2130 !important;\n}\n\n.bg-light {\n background-color: #f8f9fa !important;\n}\n\na.bg-light:hover, a.bg-light:focus,\nbutton.bg-light:hover,\nbutton.bg-light:focus {\n background-color: #dae0e5 !important;\n}\n\n.bg-dark {\n background-color: #343a40 !important;\n}\n\na.bg-dark:hover, a.bg-dark:focus,\nbutton.bg-dark:hover,\nbutton.bg-dark:focus {\n background-color: #1d2124 !important;\n}\n\n.bg-white {\n background-color: #fff !important;\n}\n\n.bg-transparent {\n background-color: transparent !important;\n}\n\n.border {\n border: 1px solid #dee2e6 !important;\n}\n\n.border-top {\n border-top: 1px solid #dee2e6 !important;\n}\n\n.border-right {\n border-right: 1px solid #dee2e6 !important;\n}\n\n.border-bottom {\n border-bottom: 1px solid #dee2e6 !important;\n}\n\n.border-left {\n border-left: 1px solid #dee2e6 !important;\n}\n\n.border-0 {\n border: 0 !important;\n}\n\n.border-top-0 {\n border-top: 0 !important;\n}\n\n.border-right-0 {\n border-right: 0 !important;\n}\n\n.border-bottom-0 {\n border-bottom: 0 !important;\n}\n\n.border-left-0 {\n border-left: 0 !important;\n}\n\n.border-primary {\n border-color: #007bff !important;\n}\n\n.border-secondary {\n border-color: #6c757d !important;\n}\n\n.border-success {\n border-color: #28a745 !important;\n}\n\n.border-info {\n border-color: #17a2b8 !important;\n}\n\n.border-warning {\n border-color: #ffc107 !important;\n}\n\n.border-danger {\n border-color: #dc3545 !important;\n}\n\n.border-light {\n border-color: #f8f9fa !important;\n}\n\n.border-dark {\n border-color: #343a40 !important;\n}\n\n.border-white {\n border-color: #fff !important;\n}\n\n.rounded-sm {\n border-radius: 0.2rem !important;\n}\n\n.rounded {\n border-radius: 0.25rem !important;\n}\n\n.rounded-top {\n border-top-left-radius: 0.25rem !important;\n border-top-right-radius: 0.25rem !important;\n}\n\n.rounded-right {\n border-top-right-radius: 0.25rem !important;\n border-bottom-right-radius: 0.25rem !important;\n}\n\n.rounded-bottom {\n border-bottom-right-radius: 0.25rem !important;\n border-bottom-left-radius: 0.25rem !important;\n}\n\n.rounded-left {\n border-top-left-radius: 0.25rem !important;\n border-bottom-left-radius: 0.25rem !important;\n}\n\n.rounded-lg {\n border-radius: 0.3rem !important;\n}\n\n.rounded-circle {\n border-radius: 50% !important;\n}\n\n.rounded-pill {\n border-radius: 50rem !important;\n}\n\n.rounded-0 {\n border-radius: 0 !important;\n}\n\n.clearfix::after {\n display: block;\n clear: both;\n content: \"\";\n}\n\n.d-none {\n display: none !important;\n}\n\n.d-inline {\n display: inline !important;\n}\n\n.d-inline-block {\n display: inline-block !important;\n}\n\n.d-block {\n display: block !important;\n}\n\n.d-table {\n display: table !important;\n}\n\n.d-table-row {\n display: table-row !important;\n}\n\n.d-table-cell {\n display: table-cell !important;\n}\n\n.d-flex {\n display: flex !important;\n}\n\n.d-inline-flex {\n display: inline-flex !important;\n}\n\n@media (min-width: 576px) {\n .d-sm-none {\n display: none !important;\n }\n .d-sm-inline {\n display: inline !important;\n }\n .d-sm-inline-block {\n display: inline-block !important;\n }\n .d-sm-block {\n display: block !important;\n }\n .d-sm-table {\n display: table !important;\n }\n .d-sm-table-row {\n display: table-row !important;\n }\n .d-sm-table-cell {\n display: table-cell !important;\n }\n .d-sm-flex {\n display: flex !important;\n }\n .d-sm-inline-flex {\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 768px) {\n .d-md-none {\n display: none !important;\n }\n .d-md-inline {\n display: inline !important;\n }\n .d-md-inline-block {\n display: inline-block !important;\n }\n .d-md-block {\n display: block !important;\n }\n .d-md-table {\n display: table !important;\n }\n .d-md-table-row {\n display: table-row !important;\n }\n .d-md-table-cell {\n display: table-cell !important;\n }\n .d-md-flex {\n display: flex !important;\n }\n .d-md-inline-flex {\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 992px) {\n .d-lg-none {\n display: none !important;\n }\n .d-lg-inline {\n display: inline !important;\n }\n .d-lg-inline-block {\n display: inline-block !important;\n }\n .d-lg-block {\n display: block !important;\n }\n .d-lg-table {\n display: table !important;\n }\n .d-lg-table-row {\n display: table-row !important;\n }\n .d-lg-table-cell {\n display: table-cell !important;\n }\n .d-lg-flex {\n display: flex !important;\n }\n .d-lg-inline-flex {\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 1200px) {\n .d-xl-none {\n display: none !important;\n }\n .d-xl-inline {\n display: inline !important;\n }\n .d-xl-inline-block {\n display: inline-block !important;\n }\n .d-xl-block {\n display: block !important;\n }\n .d-xl-table {\n display: table !important;\n }\n .d-xl-table-row {\n display: table-row !important;\n }\n .d-xl-table-cell {\n display: table-cell !important;\n }\n .d-xl-flex {\n display: flex !important;\n }\n .d-xl-inline-flex {\n display: inline-flex !important;\n }\n}\n\n@media print {\n .d-print-none {\n display: none !important;\n }\n .d-print-inline {\n display: inline !important;\n }\n .d-print-inline-block {\n display: inline-block !important;\n }\n .d-print-block {\n display: block !important;\n }\n .d-print-table {\n display: table !important;\n }\n .d-print-table-row {\n display: table-row !important;\n }\n .d-print-table-cell {\n display: table-cell !important;\n }\n .d-print-flex {\n display: flex !important;\n }\n .d-print-inline-flex {\n display: inline-flex !important;\n }\n}\n\n.embed-responsive {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n overflow: hidden;\n}\n\n.embed-responsive::before {\n display: block;\n content: \"\";\n}\n\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: 0;\n}\n\n.embed-responsive-21by9::before {\n padding-top: 42.857143%;\n}\n\n.embed-responsive-16by9::before {\n padding-top: 56.25%;\n}\n\n.embed-responsive-4by3::before {\n padding-top: 75%;\n}\n\n.embed-responsive-1by1::before {\n padding-top: 100%;\n}\n\n.flex-row {\n flex-direction: row !important;\n}\n\n.flex-column {\n flex-direction: column !important;\n}\n\n.flex-row-reverse {\n flex-direction: row-reverse !important;\n}\n\n.flex-column-reverse {\n flex-direction: column-reverse !important;\n}\n\n.flex-wrap {\n flex-wrap: wrap !important;\n}\n\n.flex-nowrap {\n flex-wrap: nowrap !important;\n}\n\n.flex-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n}\n\n.flex-fill {\n flex: 1 1 auto !important;\n}\n\n.flex-grow-0 {\n flex-grow: 0 !important;\n}\n\n.flex-grow-1 {\n flex-grow: 1 !important;\n}\n\n.flex-shrink-0 {\n flex-shrink: 0 !important;\n}\n\n.flex-shrink-1 {\n flex-shrink: 1 !important;\n}\n\n.justify-content-start {\n justify-content: flex-start !important;\n}\n\n.justify-content-end {\n justify-content: flex-end !important;\n}\n\n.justify-content-center {\n justify-content: center !important;\n}\n\n.justify-content-between {\n justify-content: space-between !important;\n}\n\n.justify-content-around {\n justify-content: space-around !important;\n}\n\n.align-items-start {\n align-items: flex-start !important;\n}\n\n.align-items-end {\n align-items: flex-end !important;\n}\n\n.align-items-center {\n align-items: center !important;\n}\n\n.align-items-baseline {\n align-items: baseline !important;\n}\n\n.align-items-stretch {\n align-items: stretch !important;\n}\n\n.align-content-start {\n align-content: flex-start !important;\n}\n\n.align-content-end {\n align-content: flex-end !important;\n}\n\n.align-content-center {\n align-content: center !important;\n}\n\n.align-content-between {\n align-content: space-between !important;\n}\n\n.align-content-around {\n align-content: space-around !important;\n}\n\n.align-content-stretch {\n align-content: stretch !important;\n}\n\n.align-self-auto {\n align-self: auto !important;\n}\n\n.align-self-start {\n align-self: flex-start !important;\n}\n\n.align-self-end {\n align-self: flex-end !important;\n}\n\n.align-self-center {\n align-self: center !important;\n}\n\n.align-self-baseline {\n align-self: baseline !important;\n}\n\n.align-self-stretch {\n align-self: stretch !important;\n}\n\n@media (min-width: 576px) {\n .flex-sm-row {\n flex-direction: row !important;\n }\n .flex-sm-column {\n flex-direction: column !important;\n }\n .flex-sm-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-sm-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-sm-wrap {\n flex-wrap: wrap !important;\n }\n .flex-sm-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-sm-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .flex-sm-fill {\n flex: 1 1 auto !important;\n }\n .flex-sm-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-sm-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-sm-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-sm-shrink-1 {\n flex-shrink: 1 !important;\n }\n .justify-content-sm-start {\n justify-content: flex-start !important;\n }\n .justify-content-sm-end {\n justify-content: flex-end !important;\n }\n .justify-content-sm-center {\n justify-content: center !important;\n }\n .justify-content-sm-between {\n justify-content: space-between !important;\n }\n .justify-content-sm-around {\n justify-content: space-around !important;\n }\n .align-items-sm-start {\n align-items: flex-start !important;\n }\n .align-items-sm-end {\n align-items: flex-end !important;\n }\n .align-items-sm-center {\n align-items: center !important;\n }\n .align-items-sm-baseline {\n align-items: baseline !important;\n }\n .align-items-sm-stretch {\n align-items: stretch !important;\n }\n .align-content-sm-start {\n align-content: flex-start !important;\n }\n .align-content-sm-end {\n align-content: flex-end !important;\n }\n .align-content-sm-center {\n align-content: center !important;\n }\n .align-content-sm-between {\n align-content: space-between !important;\n }\n .align-content-sm-around {\n align-content: space-around !important;\n }\n .align-content-sm-stretch {\n align-content: stretch !important;\n }\n .align-self-sm-auto {\n align-self: auto !important;\n }\n .align-self-sm-start {\n align-self: flex-start !important;\n }\n .align-self-sm-end {\n align-self: flex-end !important;\n }\n .align-self-sm-center {\n align-self: center !important;\n }\n .align-self-sm-baseline {\n align-self: baseline !important;\n }\n .align-self-sm-stretch {\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 768px) {\n .flex-md-row {\n flex-direction: row !important;\n }\n .flex-md-column {\n flex-direction: column !important;\n }\n .flex-md-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-md-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-md-wrap {\n flex-wrap: wrap !important;\n }\n .flex-md-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-md-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .flex-md-fill {\n flex: 1 1 auto !important;\n }\n .flex-md-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-md-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-md-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-md-shrink-1 {\n flex-shrink: 1 !important;\n }\n .justify-content-md-start {\n justify-content: flex-start !important;\n }\n .justify-content-md-end {\n justify-content: flex-end !important;\n }\n .justify-content-md-center {\n justify-content: center !important;\n }\n .justify-content-md-between {\n justify-content: space-between !important;\n }\n .justify-content-md-around {\n justify-content: space-around !important;\n }\n .align-items-md-start {\n align-items: flex-start !important;\n }\n .align-items-md-end {\n align-items: flex-end !important;\n }\n .align-items-md-center {\n align-items: center !important;\n }\n .align-items-md-baseline {\n align-items: baseline !important;\n }\n .align-items-md-stretch {\n align-items: stretch !important;\n }\n .align-content-md-start {\n align-content: flex-start !important;\n }\n .align-content-md-end {\n align-content: flex-end !important;\n }\n .align-content-md-center {\n align-content: center !important;\n }\n .align-content-md-between {\n align-content: space-between !important;\n }\n .align-content-md-around {\n align-content: space-around !important;\n }\n .align-content-md-stretch {\n align-content: stretch !important;\n }\n .align-self-md-auto {\n align-self: auto !important;\n }\n .align-self-md-start {\n align-self: flex-start !important;\n }\n .align-self-md-end {\n align-self: flex-end !important;\n }\n .align-self-md-center {\n align-self: center !important;\n }\n .align-self-md-baseline {\n align-self: baseline !important;\n }\n .align-self-md-stretch {\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 992px) {\n .flex-lg-row {\n flex-direction: row !important;\n }\n .flex-lg-column {\n flex-direction: column !important;\n }\n .flex-lg-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-lg-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-lg-wrap {\n flex-wrap: wrap !important;\n }\n .flex-lg-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-lg-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .flex-lg-fill {\n flex: 1 1 auto !important;\n }\n .flex-lg-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-lg-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-lg-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-lg-shrink-1 {\n flex-shrink: 1 !important;\n }\n .justify-content-lg-start {\n justify-content: flex-start !important;\n }\n .justify-content-lg-end {\n justify-content: flex-end !important;\n }\n .justify-content-lg-center {\n justify-content: center !important;\n }\n .justify-content-lg-between {\n justify-content: space-between !important;\n }\n .justify-content-lg-around {\n justify-content: space-around !important;\n }\n .align-items-lg-start {\n align-items: flex-start !important;\n }\n .align-items-lg-end {\n align-items: flex-end !important;\n }\n .align-items-lg-center {\n align-items: center !important;\n }\n .align-items-lg-baseline {\n align-items: baseline !important;\n }\n .align-items-lg-stretch {\n align-items: stretch !important;\n }\n .align-content-lg-start {\n align-content: flex-start !important;\n }\n .align-content-lg-end {\n align-content: flex-end !important;\n }\n .align-content-lg-center {\n align-content: center !important;\n }\n .align-content-lg-between {\n align-content: space-between !important;\n }\n .align-content-lg-around {\n align-content: space-around !important;\n }\n .align-content-lg-stretch {\n align-content: stretch !important;\n }\n .align-self-lg-auto {\n align-self: auto !important;\n }\n .align-self-lg-start {\n align-self: flex-start !important;\n }\n .align-self-lg-end {\n align-self: flex-end !important;\n }\n .align-self-lg-center {\n align-self: center !important;\n }\n .align-self-lg-baseline {\n align-self: baseline !important;\n }\n .align-self-lg-stretch {\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 1200px) {\n .flex-xl-row {\n flex-direction: row !important;\n }\n .flex-xl-column {\n flex-direction: column !important;\n }\n .flex-xl-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-xl-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-xl-wrap {\n flex-wrap: wrap !important;\n }\n .flex-xl-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-xl-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .flex-xl-fill {\n flex: 1 1 auto !important;\n }\n .flex-xl-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-xl-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-xl-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-xl-shrink-1 {\n flex-shrink: 1 !important;\n }\n .justify-content-xl-start {\n justify-content: flex-start !important;\n }\n .justify-content-xl-end {\n justify-content: flex-end !important;\n }\n .justify-content-xl-center {\n justify-content: center !important;\n }\n .justify-content-xl-between {\n justify-content: space-between !important;\n }\n .justify-content-xl-around {\n justify-content: space-around !important;\n }\n .align-items-xl-start {\n align-items: flex-start !important;\n }\n .align-items-xl-end {\n align-items: flex-end !important;\n }\n .align-items-xl-center {\n align-items: center !important;\n }\n .align-items-xl-baseline {\n align-items: baseline !important;\n }\n .align-items-xl-stretch {\n align-items: stretch !important;\n }\n .align-content-xl-start {\n align-content: flex-start !important;\n }\n .align-content-xl-end {\n align-content: flex-end !important;\n }\n .align-content-xl-center {\n align-content: center !important;\n }\n .align-content-xl-between {\n align-content: space-between !important;\n }\n .align-content-xl-around {\n align-content: space-around !important;\n }\n .align-content-xl-stretch {\n align-content: stretch !important;\n }\n .align-self-xl-auto {\n align-self: auto !important;\n }\n .align-self-xl-start {\n align-self: flex-start !important;\n }\n .align-self-xl-end {\n align-self: flex-end !important;\n }\n .align-self-xl-center {\n align-self: center !important;\n }\n .align-self-xl-baseline {\n align-self: baseline !important;\n }\n .align-self-xl-stretch {\n align-self: stretch !important;\n }\n}\n\n.float-left {\n float: left !important;\n}\n\n.float-right {\n float: right !important;\n}\n\n.float-none {\n float: none !important;\n}\n\n@media (min-width: 576px) {\n .float-sm-left {\n float: left !important;\n }\n .float-sm-right {\n float: right !important;\n }\n .float-sm-none {\n float: none !important;\n }\n}\n\n@media (min-width: 768px) {\n .float-md-left {\n float: left !important;\n }\n .float-md-right {\n float: right !important;\n }\n .float-md-none {\n float: none !important;\n }\n}\n\n@media (min-width: 992px) {\n .float-lg-left {\n float: left !important;\n }\n .float-lg-right {\n float: right !important;\n }\n .float-lg-none {\n float: none !important;\n }\n}\n\n@media (min-width: 1200px) {\n .float-xl-left {\n float: left !important;\n }\n .float-xl-right {\n float: right !important;\n }\n .float-xl-none {\n float: none !important;\n }\n}\n\n.user-select-all {\n user-select: all !important;\n}\n\n.user-select-auto {\n user-select: auto !important;\n}\n\n.user-select-none {\n user-select: none !important;\n}\n\n.overflow-auto {\n overflow: auto !important;\n}\n\n.overflow-hidden {\n overflow: hidden !important;\n}\n\n.position-static {\n position: static !important;\n}\n\n.position-relative {\n position: relative !important;\n}\n\n.position-absolute {\n position: absolute !important;\n}\n\n.position-fixed {\n position: fixed !important;\n}\n\n.position-sticky {\n position: sticky !important;\n}\n\n.fixed-top {\n position: fixed;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n\n.fixed-bottom {\n position: fixed;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1030;\n}\n\n@supports (position: sticky) {\n .sticky-top {\n position: sticky;\n top: 0;\n z-index: 1020;\n }\n}\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n.sr-only-focusable:active, .sr-only-focusable:focus {\n position: static;\n width: auto;\n height: auto;\n overflow: visible;\n clip: auto;\n white-space: normal;\n}\n\n.shadow-sm {\n box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;\n}\n\n.shadow {\n box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;\n}\n\n.shadow-lg {\n box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important;\n}\n\n.shadow-none {\n box-shadow: none !important;\n}\n\n.w-25 {\n width: 25% !important;\n}\n\n.w-50 {\n width: 50% !important;\n}\n\n.w-75 {\n width: 75% !important;\n}\n\n.w-100 {\n width: 100% !important;\n}\n\n.w-auto {\n width: auto !important;\n}\n\n.h-25 {\n height: 25% !important;\n}\n\n.h-50 {\n height: 50% !important;\n}\n\n.h-75 {\n height: 75% !important;\n}\n\n.h-100 {\n height: 100% !important;\n}\n\n.h-auto {\n height: auto !important;\n}\n\n.mw-100 {\n max-width: 100% !important;\n}\n\n.mh-100 {\n max-height: 100% !important;\n}\n\n.min-vw-100 {\n min-width: 100vw !important;\n}\n\n.min-vh-100 {\n min-height: 100vh !important;\n}\n\n.vw-100 {\n width: 100vw !important;\n}\n\n.vh-100 {\n height: 100vh !important;\n}\n\n.m-0 {\n margin: 0 !important;\n}\n\n.mt-0,\n.my-0 {\n margin-top: 0 !important;\n}\n\n.mr-0,\n.mx-0 {\n margin-right: 0 !important;\n}\n\n.mb-0,\n.my-0 {\n margin-bottom: 0 !important;\n}\n\n.ml-0,\n.mx-0 {\n margin-left: 0 !important;\n}\n\n.m-1 {\n margin: 0.25rem !important;\n}\n\n.mt-1,\n.my-1 {\n margin-top: 0.25rem !important;\n}\n\n.mr-1,\n.mx-1 {\n margin-right: 0.25rem !important;\n}\n\n.mb-1,\n.my-1 {\n margin-bottom: 0.25rem !important;\n}\n\n.ml-1,\n.mx-1 {\n margin-left: 0.25rem !important;\n}\n\n.m-2 {\n margin: 0.5rem !important;\n}\n\n.mt-2,\n.my-2 {\n margin-top: 0.5rem !important;\n}\n\n.mr-2,\n.mx-2 {\n margin-right: 0.5rem !important;\n}\n\n.mb-2,\n.my-2 {\n margin-bottom: 0.5rem !important;\n}\n\n.ml-2,\n.mx-2 {\n margin-left: 0.5rem !important;\n}\n\n.m-3 {\n margin: 1rem !important;\n}\n\n.mt-3,\n.my-3 {\n margin-top: 1rem !important;\n}\n\n.mr-3,\n.mx-3 {\n margin-right: 1rem !important;\n}\n\n.mb-3,\n.my-3 {\n margin-bottom: 1rem !important;\n}\n\n.ml-3,\n.mx-3 {\n margin-left: 1rem !important;\n}\n\n.m-4 {\n margin: 1.5rem !important;\n}\n\n.mt-4,\n.my-4 {\n margin-top: 1.5rem !important;\n}\n\n.mr-4,\n.mx-4 {\n margin-right: 1.5rem !important;\n}\n\n.mb-4,\n.my-4 {\n margin-bottom: 1.5rem !important;\n}\n\n.ml-4,\n.mx-4 {\n margin-left: 1.5rem !important;\n}\n\n.m-5 {\n margin: 3rem !important;\n}\n\n.mt-5,\n.my-5 {\n margin-top: 3rem !important;\n}\n\n.mr-5,\n.mx-5 {\n margin-right: 3rem !important;\n}\n\n.mb-5,\n.my-5 {\n margin-bottom: 3rem !important;\n}\n\n.ml-5,\n.mx-5 {\n margin-left: 3rem !important;\n}\n\n.p-0 {\n padding: 0 !important;\n}\n\n.pt-0,\n.py-0 {\n padding-top: 0 !important;\n}\n\n.pr-0,\n.px-0 {\n padding-right: 0 !important;\n}\n\n.pb-0,\n.py-0 {\n padding-bottom: 0 !important;\n}\n\n.pl-0,\n.px-0 {\n padding-left: 0 !important;\n}\n\n.p-1 {\n padding: 0.25rem !important;\n}\n\n.pt-1,\n.py-1 {\n padding-top: 0.25rem !important;\n}\n\n.pr-1,\n.px-1 {\n padding-right: 0.25rem !important;\n}\n\n.pb-1,\n.py-1 {\n padding-bottom: 0.25rem !important;\n}\n\n.pl-1,\n.px-1 {\n padding-left: 0.25rem !important;\n}\n\n.p-2 {\n padding: 0.5rem !important;\n}\n\n.pt-2,\n.py-2 {\n padding-top: 0.5rem !important;\n}\n\n.pr-2,\n.px-2 {\n padding-right: 0.5rem !important;\n}\n\n.pb-2,\n.py-2 {\n padding-bottom: 0.5rem !important;\n}\n\n.pl-2,\n.px-2 {\n padding-left: 0.5rem !important;\n}\n\n.p-3 {\n padding: 1rem !important;\n}\n\n.pt-3,\n.py-3 {\n padding-top: 1rem !important;\n}\n\n.pr-3,\n.px-3 {\n padding-right: 1rem !important;\n}\n\n.pb-3,\n.py-3 {\n padding-bottom: 1rem !important;\n}\n\n.pl-3,\n.px-3 {\n padding-left: 1rem !important;\n}\n\n.p-4 {\n padding: 1.5rem !important;\n}\n\n.pt-4,\n.py-4 {\n padding-top: 1.5rem !important;\n}\n\n.pr-4,\n.px-4 {\n padding-right: 1.5rem !important;\n}\n\n.pb-4,\n.py-4 {\n padding-bottom: 1.5rem !important;\n}\n\n.pl-4,\n.px-4 {\n padding-left: 1.5rem !important;\n}\n\n.p-5 {\n padding: 3rem !important;\n}\n\n.pt-5,\n.py-5 {\n padding-top: 3rem !important;\n}\n\n.pr-5,\n.px-5 {\n padding-right: 3rem !important;\n}\n\n.pb-5,\n.py-5 {\n padding-bottom: 3rem !important;\n}\n\n.pl-5,\n.px-5 {\n padding-left: 3rem !important;\n}\n\n.m-n1 {\n margin: -0.25rem !important;\n}\n\n.mt-n1,\n.my-n1 {\n margin-top: -0.25rem !important;\n}\n\n.mr-n1,\n.mx-n1 {\n margin-right: -0.25rem !important;\n}\n\n.mb-n1,\n.my-n1 {\n margin-bottom: -0.25rem !important;\n}\n\n.ml-n1,\n.mx-n1 {\n margin-left: -0.25rem !important;\n}\n\n.m-n2 {\n margin: -0.5rem !important;\n}\n\n.mt-n2,\n.my-n2 {\n margin-top: -0.5rem !important;\n}\n\n.mr-n2,\n.mx-n2 {\n margin-right: -0.5rem !important;\n}\n\n.mb-n2,\n.my-n2 {\n margin-bottom: -0.5rem !important;\n}\n\n.ml-n2,\n.mx-n2 {\n margin-left: -0.5rem !important;\n}\n\n.m-n3 {\n margin: -1rem !important;\n}\n\n.mt-n3,\n.my-n3 {\n margin-top: -1rem !important;\n}\n\n.mr-n3,\n.mx-n3 {\n margin-right: -1rem !important;\n}\n\n.mb-n3,\n.my-n3 {\n margin-bottom: -1rem !important;\n}\n\n.ml-n3,\n.mx-n3 {\n margin-left: -1rem !important;\n}\n\n.m-n4 {\n margin: -1.5rem !important;\n}\n\n.mt-n4,\n.my-n4 {\n margin-top: -1.5rem !important;\n}\n\n.mr-n4,\n.mx-n4 {\n margin-right: -1.5rem !important;\n}\n\n.mb-n4,\n.my-n4 {\n margin-bottom: -1.5rem !important;\n}\n\n.ml-n4,\n.mx-n4 {\n margin-left: -1.5rem !important;\n}\n\n.m-n5 {\n margin: -3rem !important;\n}\n\n.mt-n5,\n.my-n5 {\n margin-top: -3rem !important;\n}\n\n.mr-n5,\n.mx-n5 {\n margin-right: -3rem !important;\n}\n\n.mb-n5,\n.my-n5 {\n margin-bottom: -3rem !important;\n}\n\n.ml-n5,\n.mx-n5 {\n margin-left: -3rem !important;\n}\n\n.m-auto {\n margin: auto !important;\n}\n\n.mt-auto,\n.my-auto {\n margin-top: auto !important;\n}\n\n.mr-auto,\n.mx-auto {\n margin-right: auto !important;\n}\n\n.mb-auto,\n.my-auto {\n margin-bottom: auto !important;\n}\n\n.ml-auto,\n.mx-auto {\n margin-left: auto !important;\n}\n\n@media (min-width: 576px) {\n .m-sm-0 {\n margin: 0 !important;\n }\n .mt-sm-0,\n .my-sm-0 {\n margin-top: 0 !important;\n }\n .mr-sm-0,\n .mx-sm-0 {\n margin-right: 0 !important;\n }\n .mb-sm-0,\n .my-sm-0 {\n margin-bottom: 0 !important;\n }\n .ml-sm-0,\n .mx-sm-0 {\n margin-left: 0 !important;\n }\n .m-sm-1 {\n margin: 0.25rem !important;\n }\n .mt-sm-1,\n .my-sm-1 {\n margin-top: 0.25rem !important;\n }\n .mr-sm-1,\n .mx-sm-1 {\n margin-right: 0.25rem !important;\n }\n .mb-sm-1,\n .my-sm-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-sm-1,\n .mx-sm-1 {\n margin-left: 0.25rem !important;\n }\n .m-sm-2 {\n margin: 0.5rem !important;\n }\n .mt-sm-2,\n .my-sm-2 {\n margin-top: 0.5rem !important;\n }\n .mr-sm-2,\n .mx-sm-2 {\n margin-right: 0.5rem !important;\n }\n .mb-sm-2,\n .my-sm-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-sm-2,\n .mx-sm-2 {\n margin-left: 0.5rem !important;\n }\n .m-sm-3 {\n margin: 1rem !important;\n }\n .mt-sm-3,\n .my-sm-3 {\n margin-top: 1rem !important;\n }\n .mr-sm-3,\n .mx-sm-3 {\n margin-right: 1rem !important;\n }\n .mb-sm-3,\n .my-sm-3 {\n margin-bottom: 1rem !important;\n }\n .ml-sm-3,\n .mx-sm-3 {\n margin-left: 1rem !important;\n }\n .m-sm-4 {\n margin: 1.5rem !important;\n }\n .mt-sm-4,\n .my-sm-4 {\n margin-top: 1.5rem !important;\n }\n .mr-sm-4,\n .mx-sm-4 {\n margin-right: 1.5rem !important;\n }\n .mb-sm-4,\n .my-sm-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-sm-4,\n .mx-sm-4 {\n margin-left: 1.5rem !important;\n }\n .m-sm-5 {\n margin: 3rem !important;\n }\n .mt-sm-5,\n .my-sm-5 {\n margin-top: 3rem !important;\n }\n .mr-sm-5,\n .mx-sm-5 {\n margin-right: 3rem !important;\n }\n .mb-sm-5,\n .my-sm-5 {\n margin-bottom: 3rem !important;\n }\n .ml-sm-5,\n .mx-sm-5 {\n margin-left: 3rem !important;\n }\n .p-sm-0 {\n padding: 0 !important;\n }\n .pt-sm-0,\n .py-sm-0 {\n padding-top: 0 !important;\n }\n .pr-sm-0,\n .px-sm-0 {\n padding-right: 0 !important;\n }\n .pb-sm-0,\n .py-sm-0 {\n padding-bottom: 0 !important;\n }\n .pl-sm-0,\n .px-sm-0 {\n padding-left: 0 !important;\n }\n .p-sm-1 {\n padding: 0.25rem !important;\n }\n .pt-sm-1,\n .py-sm-1 {\n padding-top: 0.25rem !important;\n }\n .pr-sm-1,\n .px-sm-1 {\n padding-right: 0.25rem !important;\n }\n .pb-sm-1,\n .py-sm-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-sm-1,\n .px-sm-1 {\n padding-left: 0.25rem !important;\n }\n .p-sm-2 {\n padding: 0.5rem !important;\n }\n .pt-sm-2,\n .py-sm-2 {\n padding-top: 0.5rem !important;\n }\n .pr-sm-2,\n .px-sm-2 {\n padding-right: 0.5rem !important;\n }\n .pb-sm-2,\n .py-sm-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-sm-2,\n .px-sm-2 {\n padding-left: 0.5rem !important;\n }\n .p-sm-3 {\n padding: 1rem !important;\n }\n .pt-sm-3,\n .py-sm-3 {\n padding-top: 1rem !important;\n }\n .pr-sm-3,\n .px-sm-3 {\n padding-right: 1rem !important;\n }\n .pb-sm-3,\n .py-sm-3 {\n padding-bottom: 1rem !important;\n }\n .pl-sm-3,\n .px-sm-3 {\n padding-left: 1rem !important;\n }\n .p-sm-4 {\n padding: 1.5rem !important;\n }\n .pt-sm-4,\n .py-sm-4 {\n padding-top: 1.5rem !important;\n }\n .pr-sm-4,\n .px-sm-4 {\n padding-right: 1.5rem !important;\n }\n .pb-sm-4,\n .py-sm-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-sm-4,\n .px-sm-4 {\n padding-left: 1.5rem !important;\n }\n .p-sm-5 {\n padding: 3rem !important;\n }\n .pt-sm-5,\n .py-sm-5 {\n padding-top: 3rem !important;\n }\n .pr-sm-5,\n .px-sm-5 {\n padding-right: 3rem !important;\n }\n .pb-sm-5,\n .py-sm-5 {\n padding-bottom: 3rem !important;\n }\n .pl-sm-5,\n .px-sm-5 {\n padding-left: 3rem !important;\n }\n .m-sm-n1 {\n margin: -0.25rem !important;\n }\n .mt-sm-n1,\n .my-sm-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-sm-n1,\n .mx-sm-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-sm-n1,\n .my-sm-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-sm-n1,\n .mx-sm-n1 {\n margin-left: -0.25rem !important;\n }\n .m-sm-n2 {\n margin: -0.5rem !important;\n }\n .mt-sm-n2,\n .my-sm-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-sm-n2,\n .mx-sm-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-sm-n2,\n .my-sm-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-sm-n2,\n .mx-sm-n2 {\n margin-left: -0.5rem !important;\n }\n .m-sm-n3 {\n margin: -1rem !important;\n }\n .mt-sm-n3,\n .my-sm-n3 {\n margin-top: -1rem !important;\n }\n .mr-sm-n3,\n .mx-sm-n3 {\n margin-right: -1rem !important;\n }\n .mb-sm-n3,\n .my-sm-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-sm-n3,\n .mx-sm-n3 {\n margin-left: -1rem !important;\n }\n .m-sm-n4 {\n margin: -1.5rem !important;\n }\n .mt-sm-n4,\n .my-sm-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-sm-n4,\n .mx-sm-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-sm-n4,\n .my-sm-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-sm-n4,\n .mx-sm-n4 {\n margin-left: -1.5rem !important;\n }\n .m-sm-n5 {\n margin: -3rem !important;\n }\n .mt-sm-n5,\n .my-sm-n5 {\n margin-top: -3rem !important;\n }\n .mr-sm-n5,\n .mx-sm-n5 {\n margin-right: -3rem !important;\n }\n .mb-sm-n5,\n .my-sm-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-sm-n5,\n .mx-sm-n5 {\n margin-left: -3rem !important;\n }\n .m-sm-auto {\n margin: auto !important;\n }\n .mt-sm-auto,\n .my-sm-auto {\n margin-top: auto !important;\n }\n .mr-sm-auto,\n .mx-sm-auto {\n margin-right: auto !important;\n }\n .mb-sm-auto,\n .my-sm-auto {\n margin-bottom: auto !important;\n }\n .ml-sm-auto,\n .mx-sm-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 768px) {\n .m-md-0 {\n margin: 0 !important;\n }\n .mt-md-0,\n .my-md-0 {\n margin-top: 0 !important;\n }\n .mr-md-0,\n .mx-md-0 {\n margin-right: 0 !important;\n }\n .mb-md-0,\n .my-md-0 {\n margin-bottom: 0 !important;\n }\n .ml-md-0,\n .mx-md-0 {\n margin-left: 0 !important;\n }\n .m-md-1 {\n margin: 0.25rem !important;\n }\n .mt-md-1,\n .my-md-1 {\n margin-top: 0.25rem !important;\n }\n .mr-md-1,\n .mx-md-1 {\n margin-right: 0.25rem !important;\n }\n .mb-md-1,\n .my-md-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-md-1,\n .mx-md-1 {\n margin-left: 0.25rem !important;\n }\n .m-md-2 {\n margin: 0.5rem !important;\n }\n .mt-md-2,\n .my-md-2 {\n margin-top: 0.5rem !important;\n }\n .mr-md-2,\n .mx-md-2 {\n margin-right: 0.5rem !important;\n }\n .mb-md-2,\n .my-md-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-md-2,\n .mx-md-2 {\n margin-left: 0.5rem !important;\n }\n .m-md-3 {\n margin: 1rem !important;\n }\n .mt-md-3,\n .my-md-3 {\n margin-top: 1rem !important;\n }\n .mr-md-3,\n .mx-md-3 {\n margin-right: 1rem !important;\n }\n .mb-md-3,\n .my-md-3 {\n margin-bottom: 1rem !important;\n }\n .ml-md-3,\n .mx-md-3 {\n margin-left: 1rem !important;\n }\n .m-md-4 {\n margin: 1.5rem !important;\n }\n .mt-md-4,\n .my-md-4 {\n margin-top: 1.5rem !important;\n }\n .mr-md-4,\n .mx-md-4 {\n margin-right: 1.5rem !important;\n }\n .mb-md-4,\n .my-md-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-md-4,\n .mx-md-4 {\n margin-left: 1.5rem !important;\n }\n .m-md-5 {\n margin: 3rem !important;\n }\n .mt-md-5,\n .my-md-5 {\n margin-top: 3rem !important;\n }\n .mr-md-5,\n .mx-md-5 {\n margin-right: 3rem !important;\n }\n .mb-md-5,\n .my-md-5 {\n margin-bottom: 3rem !important;\n }\n .ml-md-5,\n .mx-md-5 {\n margin-left: 3rem !important;\n }\n .p-md-0 {\n padding: 0 !important;\n }\n .pt-md-0,\n .py-md-0 {\n padding-top: 0 !important;\n }\n .pr-md-0,\n .px-md-0 {\n padding-right: 0 !important;\n }\n .pb-md-0,\n .py-md-0 {\n padding-bottom: 0 !important;\n }\n .pl-md-0,\n .px-md-0 {\n padding-left: 0 !important;\n }\n .p-md-1 {\n padding: 0.25rem !important;\n }\n .pt-md-1,\n .py-md-1 {\n padding-top: 0.25rem !important;\n }\n .pr-md-1,\n .px-md-1 {\n padding-right: 0.25rem !important;\n }\n .pb-md-1,\n .py-md-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-md-1,\n .px-md-1 {\n padding-left: 0.25rem !important;\n }\n .p-md-2 {\n padding: 0.5rem !important;\n }\n .pt-md-2,\n .py-md-2 {\n padding-top: 0.5rem !important;\n }\n .pr-md-2,\n .px-md-2 {\n padding-right: 0.5rem !important;\n }\n .pb-md-2,\n .py-md-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-md-2,\n .px-md-2 {\n padding-left: 0.5rem !important;\n }\n .p-md-3 {\n padding: 1rem !important;\n }\n .pt-md-3,\n .py-md-3 {\n padding-top: 1rem !important;\n }\n .pr-md-3,\n .px-md-3 {\n padding-right: 1rem !important;\n }\n .pb-md-3,\n .py-md-3 {\n padding-bottom: 1rem !important;\n }\n .pl-md-3,\n .px-md-3 {\n padding-left: 1rem !important;\n }\n .p-md-4 {\n padding: 1.5rem !important;\n }\n .pt-md-4,\n .py-md-4 {\n padding-top: 1.5rem !important;\n }\n .pr-md-4,\n .px-md-4 {\n padding-right: 1.5rem !important;\n }\n .pb-md-4,\n .py-md-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-md-4,\n .px-md-4 {\n padding-left: 1.5rem !important;\n }\n .p-md-5 {\n padding: 3rem !important;\n }\n .pt-md-5,\n .py-md-5 {\n padding-top: 3rem !important;\n }\n .pr-md-5,\n .px-md-5 {\n padding-right: 3rem !important;\n }\n .pb-md-5,\n .py-md-5 {\n padding-bottom: 3rem !important;\n }\n .pl-md-5,\n .px-md-5 {\n padding-left: 3rem !important;\n }\n .m-md-n1 {\n margin: -0.25rem !important;\n }\n .mt-md-n1,\n .my-md-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-md-n1,\n .mx-md-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-md-n1,\n .my-md-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-md-n1,\n .mx-md-n1 {\n margin-left: -0.25rem !important;\n }\n .m-md-n2 {\n margin: -0.5rem !important;\n }\n .mt-md-n2,\n .my-md-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-md-n2,\n .mx-md-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-md-n2,\n .my-md-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-md-n2,\n .mx-md-n2 {\n margin-left: -0.5rem !important;\n }\n .m-md-n3 {\n margin: -1rem !important;\n }\n .mt-md-n3,\n .my-md-n3 {\n margin-top: -1rem !important;\n }\n .mr-md-n3,\n .mx-md-n3 {\n margin-right: -1rem !important;\n }\n .mb-md-n3,\n .my-md-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-md-n3,\n .mx-md-n3 {\n margin-left: -1rem !important;\n }\n .m-md-n4 {\n margin: -1.5rem !important;\n }\n .mt-md-n4,\n .my-md-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-md-n4,\n .mx-md-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-md-n4,\n .my-md-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-md-n4,\n .mx-md-n4 {\n margin-left: -1.5rem !important;\n }\n .m-md-n5 {\n margin: -3rem !important;\n }\n .mt-md-n5,\n .my-md-n5 {\n margin-top: -3rem !important;\n }\n .mr-md-n5,\n .mx-md-n5 {\n margin-right: -3rem !important;\n }\n .mb-md-n5,\n .my-md-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-md-n5,\n .mx-md-n5 {\n margin-left: -3rem !important;\n }\n .m-md-auto {\n margin: auto !important;\n }\n .mt-md-auto,\n .my-md-auto {\n margin-top: auto !important;\n }\n .mr-md-auto,\n .mx-md-auto {\n margin-right: auto !important;\n }\n .mb-md-auto,\n .my-md-auto {\n margin-bottom: auto !important;\n }\n .ml-md-auto,\n .mx-md-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 992px) {\n .m-lg-0 {\n margin: 0 !important;\n }\n .mt-lg-0,\n .my-lg-0 {\n margin-top: 0 !important;\n }\n .mr-lg-0,\n .mx-lg-0 {\n margin-right: 0 !important;\n }\n .mb-lg-0,\n .my-lg-0 {\n margin-bottom: 0 !important;\n }\n .ml-lg-0,\n .mx-lg-0 {\n margin-left: 0 !important;\n }\n .m-lg-1 {\n margin: 0.25rem !important;\n }\n .mt-lg-1,\n .my-lg-1 {\n margin-top: 0.25rem !important;\n }\n .mr-lg-1,\n .mx-lg-1 {\n margin-right: 0.25rem !important;\n }\n .mb-lg-1,\n .my-lg-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-lg-1,\n .mx-lg-1 {\n margin-left: 0.25rem !important;\n }\n .m-lg-2 {\n margin: 0.5rem !important;\n }\n .mt-lg-2,\n .my-lg-2 {\n margin-top: 0.5rem !important;\n }\n .mr-lg-2,\n .mx-lg-2 {\n margin-right: 0.5rem !important;\n }\n .mb-lg-2,\n .my-lg-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-lg-2,\n .mx-lg-2 {\n margin-left: 0.5rem !important;\n }\n .m-lg-3 {\n margin: 1rem !important;\n }\n .mt-lg-3,\n .my-lg-3 {\n margin-top: 1rem !important;\n }\n .mr-lg-3,\n .mx-lg-3 {\n margin-right: 1rem !important;\n }\n .mb-lg-3,\n .my-lg-3 {\n margin-bottom: 1rem !important;\n }\n .ml-lg-3,\n .mx-lg-3 {\n margin-left: 1rem !important;\n }\n .m-lg-4 {\n margin: 1.5rem !important;\n }\n .mt-lg-4,\n .my-lg-4 {\n margin-top: 1.5rem !important;\n }\n .mr-lg-4,\n .mx-lg-4 {\n margin-right: 1.5rem !important;\n }\n .mb-lg-4,\n .my-lg-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-lg-4,\n .mx-lg-4 {\n margin-left: 1.5rem !important;\n }\n .m-lg-5 {\n margin: 3rem !important;\n }\n .mt-lg-5,\n .my-lg-5 {\n margin-top: 3rem !important;\n }\n .mr-lg-5,\n .mx-lg-5 {\n margin-right: 3rem !important;\n }\n .mb-lg-5,\n .my-lg-5 {\n margin-bottom: 3rem !important;\n }\n .ml-lg-5,\n .mx-lg-5 {\n margin-left: 3rem !important;\n }\n .p-lg-0 {\n padding: 0 !important;\n }\n .pt-lg-0,\n .py-lg-0 {\n padding-top: 0 !important;\n }\n .pr-lg-0,\n .px-lg-0 {\n padding-right: 0 !important;\n }\n .pb-lg-0,\n .py-lg-0 {\n padding-bottom: 0 !important;\n }\n .pl-lg-0,\n .px-lg-0 {\n padding-left: 0 !important;\n }\n .p-lg-1 {\n padding: 0.25rem !important;\n }\n .pt-lg-1,\n .py-lg-1 {\n padding-top: 0.25rem !important;\n }\n .pr-lg-1,\n .px-lg-1 {\n padding-right: 0.25rem !important;\n }\n .pb-lg-1,\n .py-lg-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-lg-1,\n .px-lg-1 {\n padding-left: 0.25rem !important;\n }\n .p-lg-2 {\n padding: 0.5rem !important;\n }\n .pt-lg-2,\n .py-lg-2 {\n padding-top: 0.5rem !important;\n }\n .pr-lg-2,\n .px-lg-2 {\n padding-right: 0.5rem !important;\n }\n .pb-lg-2,\n .py-lg-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-lg-2,\n .px-lg-2 {\n padding-left: 0.5rem !important;\n }\n .p-lg-3 {\n padding: 1rem !important;\n }\n .pt-lg-3,\n .py-lg-3 {\n padding-top: 1rem !important;\n }\n .pr-lg-3,\n .px-lg-3 {\n padding-right: 1rem !important;\n }\n .pb-lg-3,\n .py-lg-3 {\n padding-bottom: 1rem !important;\n }\n .pl-lg-3,\n .px-lg-3 {\n padding-left: 1rem !important;\n }\n .p-lg-4 {\n padding: 1.5rem !important;\n }\n .pt-lg-4,\n .py-lg-4 {\n padding-top: 1.5rem !important;\n }\n .pr-lg-4,\n .px-lg-4 {\n padding-right: 1.5rem !important;\n }\n .pb-lg-4,\n .py-lg-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-lg-4,\n .px-lg-4 {\n padding-left: 1.5rem !important;\n }\n .p-lg-5 {\n padding: 3rem !important;\n }\n .pt-lg-5,\n .py-lg-5 {\n padding-top: 3rem !important;\n }\n .pr-lg-5,\n .px-lg-5 {\n padding-right: 3rem !important;\n }\n .pb-lg-5,\n .py-lg-5 {\n padding-bottom: 3rem !important;\n }\n .pl-lg-5,\n .px-lg-5 {\n padding-left: 3rem !important;\n }\n .m-lg-n1 {\n margin: -0.25rem !important;\n }\n .mt-lg-n1,\n .my-lg-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-lg-n1,\n .mx-lg-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-lg-n1,\n .my-lg-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-lg-n1,\n .mx-lg-n1 {\n margin-left: -0.25rem !important;\n }\n .m-lg-n2 {\n margin: -0.5rem !important;\n }\n .mt-lg-n2,\n .my-lg-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-lg-n2,\n .mx-lg-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-lg-n2,\n .my-lg-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-lg-n2,\n .mx-lg-n2 {\n margin-left: -0.5rem !important;\n }\n .m-lg-n3 {\n margin: -1rem !important;\n }\n .mt-lg-n3,\n .my-lg-n3 {\n margin-top: -1rem !important;\n }\n .mr-lg-n3,\n .mx-lg-n3 {\n margin-right: -1rem !important;\n }\n .mb-lg-n3,\n .my-lg-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-lg-n3,\n .mx-lg-n3 {\n margin-left: -1rem !important;\n }\n .m-lg-n4 {\n margin: -1.5rem !important;\n }\n .mt-lg-n4,\n .my-lg-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-lg-n4,\n .mx-lg-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-lg-n4,\n .my-lg-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-lg-n4,\n .mx-lg-n4 {\n margin-left: -1.5rem !important;\n }\n .m-lg-n5 {\n margin: -3rem !important;\n }\n .mt-lg-n5,\n .my-lg-n5 {\n margin-top: -3rem !important;\n }\n .mr-lg-n5,\n .mx-lg-n5 {\n margin-right: -3rem !important;\n }\n .mb-lg-n5,\n .my-lg-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-lg-n5,\n .mx-lg-n5 {\n margin-left: -3rem !important;\n }\n .m-lg-auto {\n margin: auto !important;\n }\n .mt-lg-auto,\n .my-lg-auto {\n margin-top: auto !important;\n }\n .mr-lg-auto,\n .mx-lg-auto {\n margin-right: auto !important;\n }\n .mb-lg-auto,\n .my-lg-auto {\n margin-bottom: auto !important;\n }\n .ml-lg-auto,\n .mx-lg-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 1200px) {\n .m-xl-0 {\n margin: 0 !important;\n }\n .mt-xl-0,\n .my-xl-0 {\n margin-top: 0 !important;\n }\n .mr-xl-0,\n .mx-xl-0 {\n margin-right: 0 !important;\n }\n .mb-xl-0,\n .my-xl-0 {\n margin-bottom: 0 !important;\n }\n .ml-xl-0,\n .mx-xl-0 {\n margin-left: 0 !important;\n }\n .m-xl-1 {\n margin: 0.25rem !important;\n }\n .mt-xl-1,\n .my-xl-1 {\n margin-top: 0.25rem !important;\n }\n .mr-xl-1,\n .mx-xl-1 {\n margin-right: 0.25rem !important;\n }\n .mb-xl-1,\n .my-xl-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-xl-1,\n .mx-xl-1 {\n margin-left: 0.25rem !important;\n }\n .m-xl-2 {\n margin: 0.5rem !important;\n }\n .mt-xl-2,\n .my-xl-2 {\n margin-top: 0.5rem !important;\n }\n .mr-xl-2,\n .mx-xl-2 {\n margin-right: 0.5rem !important;\n }\n .mb-xl-2,\n .my-xl-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-xl-2,\n .mx-xl-2 {\n margin-left: 0.5rem !important;\n }\n .m-xl-3 {\n margin: 1rem !important;\n }\n .mt-xl-3,\n .my-xl-3 {\n margin-top: 1rem !important;\n }\n .mr-xl-3,\n .mx-xl-3 {\n margin-right: 1rem !important;\n }\n .mb-xl-3,\n .my-xl-3 {\n margin-bottom: 1rem !important;\n }\n .ml-xl-3,\n .mx-xl-3 {\n margin-left: 1rem !important;\n }\n .m-xl-4 {\n margin: 1.5rem !important;\n }\n .mt-xl-4,\n .my-xl-4 {\n margin-top: 1.5rem !important;\n }\n .mr-xl-4,\n .mx-xl-4 {\n margin-right: 1.5rem !important;\n }\n .mb-xl-4,\n .my-xl-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-xl-4,\n .mx-xl-4 {\n margin-left: 1.5rem !important;\n }\n .m-xl-5 {\n margin: 3rem !important;\n }\n .mt-xl-5,\n .my-xl-5 {\n margin-top: 3rem !important;\n }\n .mr-xl-5,\n .mx-xl-5 {\n margin-right: 3rem !important;\n }\n .mb-xl-5,\n .my-xl-5 {\n margin-bottom: 3rem !important;\n }\n .ml-xl-5,\n .mx-xl-5 {\n margin-left: 3rem !important;\n }\n .p-xl-0 {\n padding: 0 !important;\n }\n .pt-xl-0,\n .py-xl-0 {\n padding-top: 0 !important;\n }\n .pr-xl-0,\n .px-xl-0 {\n padding-right: 0 !important;\n }\n .pb-xl-0,\n .py-xl-0 {\n padding-bottom: 0 !important;\n }\n .pl-xl-0,\n .px-xl-0 {\n padding-left: 0 !important;\n }\n .p-xl-1 {\n padding: 0.25rem !important;\n }\n .pt-xl-1,\n .py-xl-1 {\n padding-top: 0.25rem !important;\n }\n .pr-xl-1,\n .px-xl-1 {\n padding-right: 0.25rem !important;\n }\n .pb-xl-1,\n .py-xl-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-xl-1,\n .px-xl-1 {\n padding-left: 0.25rem !important;\n }\n .p-xl-2 {\n padding: 0.5rem !important;\n }\n .pt-xl-2,\n .py-xl-2 {\n padding-top: 0.5rem !important;\n }\n .pr-xl-2,\n .px-xl-2 {\n padding-right: 0.5rem !important;\n }\n .pb-xl-2,\n .py-xl-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-xl-2,\n .px-xl-2 {\n padding-left: 0.5rem !important;\n }\n .p-xl-3 {\n padding: 1rem !important;\n }\n .pt-xl-3,\n .py-xl-3 {\n padding-top: 1rem !important;\n }\n .pr-xl-3,\n .px-xl-3 {\n padding-right: 1rem !important;\n }\n .pb-xl-3,\n .py-xl-3 {\n padding-bottom: 1rem !important;\n }\n .pl-xl-3,\n .px-xl-3 {\n padding-left: 1rem !important;\n }\n .p-xl-4 {\n padding: 1.5rem !important;\n }\n .pt-xl-4,\n .py-xl-4 {\n padding-top: 1.5rem !important;\n }\n .pr-xl-4,\n .px-xl-4 {\n padding-right: 1.5rem !important;\n }\n .pb-xl-4,\n .py-xl-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-xl-4,\n .px-xl-4 {\n padding-left: 1.5rem !important;\n }\n .p-xl-5 {\n padding: 3rem !important;\n }\n .pt-xl-5,\n .py-xl-5 {\n padding-top: 3rem !important;\n }\n .pr-xl-5,\n .px-xl-5 {\n padding-right: 3rem !important;\n }\n .pb-xl-5,\n .py-xl-5 {\n padding-bottom: 3rem !important;\n }\n .pl-xl-5,\n .px-xl-5 {\n padding-left: 3rem !important;\n }\n .m-xl-n1 {\n margin: -0.25rem !important;\n }\n .mt-xl-n1,\n .my-xl-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-xl-n1,\n .mx-xl-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-xl-n1,\n .my-xl-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-xl-n1,\n .mx-xl-n1 {\n margin-left: -0.25rem !important;\n }\n .m-xl-n2 {\n margin: -0.5rem !important;\n }\n .mt-xl-n2,\n .my-xl-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-xl-n2,\n .mx-xl-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-xl-n2,\n .my-xl-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-xl-n2,\n .mx-xl-n2 {\n margin-left: -0.5rem !important;\n }\n .m-xl-n3 {\n margin: -1rem !important;\n }\n .mt-xl-n3,\n .my-xl-n3 {\n margin-top: -1rem !important;\n }\n .mr-xl-n3,\n .mx-xl-n3 {\n margin-right: -1rem !important;\n }\n .mb-xl-n3,\n .my-xl-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-xl-n3,\n .mx-xl-n3 {\n margin-left: -1rem !important;\n }\n .m-xl-n4 {\n margin: -1.5rem !important;\n }\n .mt-xl-n4,\n .my-xl-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-xl-n4,\n .mx-xl-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-xl-n4,\n .my-xl-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-xl-n4,\n .mx-xl-n4 {\n margin-left: -1.5rem !important;\n }\n .m-xl-n5 {\n margin: -3rem !important;\n }\n .mt-xl-n5,\n .my-xl-n5 {\n margin-top: -3rem !important;\n }\n .mr-xl-n5,\n .mx-xl-n5 {\n margin-right: -3rem !important;\n }\n .mb-xl-n5,\n .my-xl-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-xl-n5,\n .mx-xl-n5 {\n margin-left: -3rem !important;\n }\n .m-xl-auto {\n margin: auto !important;\n }\n .mt-xl-auto,\n .my-xl-auto {\n margin-top: auto !important;\n }\n .mr-xl-auto,\n .mx-xl-auto {\n margin-right: auto !important;\n }\n .mb-xl-auto,\n .my-xl-auto {\n margin-bottom: auto !important;\n }\n .ml-xl-auto,\n .mx-xl-auto {\n margin-left: auto !important;\n }\n}\n\n.stretched-link::after {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1;\n pointer-events: auto;\n content: \"\";\n background-color: rgba(0, 0, 0, 0);\n}\n\n.text-monospace {\n font-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace !important;\n}\n\n.text-justify {\n text-align: justify !important;\n}\n\n.text-wrap {\n white-space: normal !important;\n}\n\n.text-nowrap {\n white-space: nowrap !important;\n}\n\n.text-truncate {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.text-left {\n text-align: left !important;\n}\n\n.text-right {\n text-align: right !important;\n}\n\n.text-center {\n text-align: center !important;\n}\n\n@media (min-width: 576px) {\n .text-sm-left {\n text-align: left !important;\n }\n .text-sm-right {\n text-align: right !important;\n }\n .text-sm-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 768px) {\n .text-md-left {\n text-align: left !important;\n }\n .text-md-right {\n text-align: right !important;\n }\n .text-md-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 992px) {\n .text-lg-left {\n text-align: left !important;\n }\n .text-lg-right {\n text-align: right !important;\n }\n .text-lg-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 1200px) {\n .text-xl-left {\n text-align: left !important;\n }\n .text-xl-right {\n text-align: right !important;\n }\n .text-xl-center {\n text-align: center !important;\n }\n}\n\n.text-lowercase {\n text-transform: lowercase !important;\n}\n\n.text-uppercase {\n text-transform: uppercase !important;\n}\n\n.text-capitalize {\n text-transform: capitalize !important;\n}\n\n.font-weight-light {\n font-weight: 300 !important;\n}\n\n.font-weight-lighter {\n font-weight: lighter !important;\n}\n\n.font-weight-normal {\n font-weight: 400 !important;\n}\n\n.font-weight-bold {\n font-weight: 700 !important;\n}\n\n.font-weight-bolder {\n font-weight: bolder !important;\n}\n\n.font-italic {\n font-style: italic !important;\n}\n\n.text-white {\n color: #fff !important;\n}\n\n.text-primary {\n color: #007bff !important;\n}\n\na.text-primary:hover, a.text-primary:focus {\n color: #0056b3 !important;\n}\n\n.text-secondary {\n color: #6c757d !important;\n}\n\na.text-secondary:hover, a.text-secondary:focus {\n color: #494f54 !important;\n}\n\n.text-success {\n color: #28a745 !important;\n}\n\na.text-success:hover, a.text-success:focus {\n color: #19692c !important;\n}\n\n.text-info {\n color: #17a2b8 !important;\n}\n\na.text-info:hover, a.text-info:focus {\n color: #0f6674 !important;\n}\n\n.text-warning {\n color: #ffc107 !important;\n}\n\na.text-warning:hover, a.text-warning:focus {\n color: #ba8b00 !important;\n}\n\n.text-danger {\n color: #dc3545 !important;\n}\n\na.text-danger:hover, a.text-danger:focus {\n color: #a71d2a !important;\n}\n\n.text-light {\n color: #f8f9fa !important;\n}\n\na.text-light:hover, a.text-light:focus {\n color: #cbd3da !important;\n}\n\n.text-dark {\n color: #343a40 !important;\n}\n\na.text-dark:hover, a.text-dark:focus {\n color: #121416 !important;\n}\n\n.text-body {\n color: #212529 !important;\n}\n\n.text-muted {\n color: #6c757d !important;\n}\n\n.text-black-50 {\n color: rgba(0, 0, 0, 0.5) !important;\n}\n\n.text-white-50 {\n color: rgba(255, 255, 255, 0.5) !important;\n}\n\n.text-hide {\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n\n.text-decoration-none {\n text-decoration: none !important;\n}\n\n.text-break {\n word-wrap: break-word !important;\n}\n\n.text-reset {\n color: inherit !important;\n}\n\n.visible {\n visibility: visible !important;\n}\n\n.invisible {\n visibility: hidden !important;\n}\n\n@media print {\n *,\n *::before,\n *::after {\n text-shadow: none !important;\n box-shadow: none !important;\n }\n a:not(.btn) {\n text-decoration: underline;\n }\n abbr[title]::after {\n content: \" (\" attr(title) \")\";\n }\n pre {\n white-space: pre-wrap !important;\n }\n pre,\n blockquote {\n border: 1px solid #adb5bd;\n page-break-inside: avoid;\n }\n thead {\n display: table-header-group;\n }\n tr,\n img {\n page-break-inside: avoid;\n }\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n h2,\n h3 {\n page-break-after: avoid;\n }\n @page {\n size: a3;\n }\n body {\n min-width: 992px !important;\n }\n .container {\n min-width: 992px !important;\n }\n .navbar {\n display: none;\n }\n .badge {\n border: 1px solid #000;\n }\n .table {\n border-collapse: collapse !important;\n }\n .table td,\n .table th {\n background-color: #fff !important;\n }\n .table-bordered th,\n .table-bordered td {\n border: 1px solid #dee2e6 !important;\n }\n .table-dark {\n color: inherit;\n }\n .table-dark th,\n .table-dark td,\n .table-dark thead th,\n .table-dark tbody + tbody {\n border-color: #dee2e6;\n }\n .table .thead-dark th {\n color: inherit;\n border-color: #dee2e6;\n }\n}\n\n/*# sourceMappingURL=bootstrap.css.map */","// Do not forget to update getting-started/theming.md!\n:root {\n // Custom variable values only support SassScript inside `#{}`.\n @each $color, $value in $colors {\n --#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors {\n --#{$color}: #{$value};\n }\n\n @each $bp, $value in $grid-breakpoints {\n --breakpoint-#{$bp}: #{$value};\n }\n\n // Use `inspect` for lists so that quoted items keep the quotes.\n // See https://github.com/sass/sass/issues/2383#issuecomment-336349172\n --font-family-sans-serif: #{inspect($font-family-sans-serif)};\n --font-family-monospace: #{inspect($font-family-monospace)};\n}\n","// stylelint-disable at-rule-no-vendor-prefix, declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix\n\n// Reboot\n//\n// Normalization of HTML elements, manually forked from Normalize.css to remove\n// styles targeting irrelevant browsers while applying new styles.\n//\n// Normalize is licensed MIT. https://github.com/necolas/normalize.css\n\n\n// Document\n//\n// 1. Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.\n// 2. Change the default font family in all browsers.\n// 3. Correct the line height in all browsers.\n// 4. Prevent adjustments of font size after orientation changes in IE on Windows Phone and in iOS.\n// 5. Change the default tap highlight to be completely transparent in iOS.\n\n*,\n*::before,\n*::after {\n box-sizing: border-box; // 1\n}\n\nhtml {\n font-family: sans-serif; // 2\n line-height: 1.15; // 3\n -webkit-text-size-adjust: 100%; // 4\n -webkit-tap-highlight-color: rgba($black, 0); // 5\n}\n\n// Shim for \"new\" HTML5 structural elements to display correctly (IE10, older browsers)\n// TODO: remove in v5\n// stylelint-disable-next-line selector-list-comma-newline-after\narticle, aside, figcaption, figure, footer, header, hgroup, main, nav, section {\n display: block;\n}\n\n// Body\n//\n// 1. Remove the margin in all browsers.\n// 2. As a best practice, apply a default `background-color`.\n// 3. Set an explicit initial text-align value so that we can later use\n// the `inherit` value on things like `` elements.\n\nbody {\n margin: 0; // 1\n font-family: $font-family-base;\n @include font-size($font-size-base);\n font-weight: $font-weight-base;\n line-height: $line-height-base;\n color: $body-color;\n text-align: left; // 3\n background-color: $body-bg; // 2\n}\n\n// Future-proof rule: in browsers that support :focus-visible, suppress the focus outline\n// on elements that programmatically receive focus but wouldn't normally show a visible\n// focus outline. In general, this would mean that the outline is only applied if the\n// interaction that led to the element receiving programmatic focus was a keyboard interaction,\n// or the browser has somehow determined that the user is primarily a keyboard user and/or\n// wants focus outlines to always be presented.\n//\n// See https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible\n// and https://developer.paciellogroup.com/blog/2018/03/focus-visible-and-backwards-compatibility/\n[tabindex=\"-1\"]:focus:not(:focus-visible) {\n outline: 0 !important;\n}\n\n\n// Content grouping\n//\n// 1. Add the correct box sizing in Firefox.\n// 2. Show the overflow in Edge and IE.\n\nhr {\n box-sizing: content-box; // 1\n height: 0; // 1\n overflow: visible; // 2\n}\n\n\n//\n// Typography\n//\n\n// Remove top margins from headings\n//\n// By default, `

`-`

` all receive top and bottom margins. We nuke the top\n// margin for easier control within type scales as it avoids margin collapsing.\n// stylelint-disable-next-line selector-list-comma-newline-after\nh1, h2, h3, h4, h5, h6 {\n margin-top: 0;\n margin-bottom: $headings-margin-bottom;\n}\n\n// Reset margins on paragraphs\n//\n// Similarly, the top margin on `

`s get reset. However, we also reset the\n// bottom margin to use `rem` units instead of `em`.\np {\n margin-top: 0;\n margin-bottom: $paragraph-margin-bottom;\n}\n\n// Abbreviations\n//\n// 1. Duplicate behavior to the data-* attribute for our tooltip plugin\n// 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n// 3. Add explicit cursor to indicate changed behavior.\n// 4. Remove the bottom border in Firefox 39-.\n// 5. Prevent the text-decoration to be skipped.\n\nabbr[title],\nabbr[data-original-title] { // 1\n text-decoration: underline; // 2\n text-decoration: underline dotted; // 2\n cursor: help; // 3\n border-bottom: 0; // 4\n text-decoration-skip-ink: none; // 5\n}\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: $dt-font-weight;\n}\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0; // Undo browser default\n}\n\nblockquote {\n margin: 0 0 1rem;\n}\n\nb,\nstrong {\n font-weight: $font-weight-bolder; // Add the correct font weight in Chrome, Edge, and Safari\n}\n\nsmall {\n @include font-size(80%); // Add the correct font size in all browsers\n}\n\n//\n// Prevent `sub` and `sup` elements from affecting the line height in\n// all browsers.\n//\n\nsub,\nsup {\n position: relative;\n @include font-size(75%);\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub { bottom: -.25em; }\nsup { top: -.5em; }\n\n\n//\n// Links\n//\n\na {\n color: $link-color;\n text-decoration: $link-decoration;\n background-color: transparent; // Remove the gray background on active links in IE 10.\n\n @include hover() {\n color: $link-hover-color;\n text-decoration: $link-hover-decoration;\n }\n}\n\n// And undo these styles for placeholder links/named anchors (without href).\n// It would be more straightforward to just use a[href] in previous block, but that\n// causes specificity issues in many other styles that are too complex to fix.\n// See https://github.com/twbs/bootstrap/issues/19402\n\na:not([href]) {\n color: inherit;\n text-decoration: none;\n\n @include hover() {\n color: inherit;\n text-decoration: none;\n }\n}\n\n\n//\n// Code\n//\n\npre,\ncode,\nkbd,\nsamp {\n font-family: $font-family-monospace;\n @include font-size(1em); // Correct the odd `em` font sizing in all browsers.\n}\n\npre {\n // Remove browser default top margin\n margin-top: 0;\n // Reset browser default of `1em` to use `rem`s\n margin-bottom: 1rem;\n // Don't allow content to break outside\n overflow: auto;\n // Disable auto-hiding scrollbar in IE & legacy Edge to avoid overlap,\n // making it impossible to interact with the content\n -ms-overflow-style: scrollbar;\n}\n\n\n//\n// Figures\n//\n\nfigure {\n // Apply a consistent margin strategy (matches our type styles).\n margin: 0 0 1rem;\n}\n\n\n//\n// Images and content\n//\n\nimg {\n vertical-align: middle;\n border-style: none; // Remove the border on images inside links in IE 10-.\n}\n\nsvg {\n // Workaround for the SVG overflow bug in IE10/11 is still required.\n // See https://github.com/twbs/bootstrap/issues/26878\n overflow: hidden;\n vertical-align: middle;\n}\n\n\n//\n// Tables\n//\n\ntable {\n border-collapse: collapse; // Prevent double borders\n}\n\ncaption {\n padding-top: $table-cell-padding;\n padding-bottom: $table-cell-padding;\n color: $table-caption-color;\n text-align: left;\n caption-side: bottom;\n}\n\nth {\n // Matches default `` alignment by inheriting from the ``, or the\n // closest parent with a set `text-align`.\n text-align: inherit;\n}\n\n\n//\n// Forms\n//\n\nlabel {\n // Allow labels to use `margin` for spacing.\n display: inline-block;\n margin-bottom: $label-margin-bottom;\n}\n\n// Remove the default `border-radius` that macOS Chrome adds.\n//\n// Details at https://github.com/twbs/bootstrap/issues/24093\nbutton {\n // stylelint-disable-next-line property-blacklist\n border-radius: 0;\n}\n\n// Work around a Firefox/IE bug where the transparent `button` background\n// results in a loss of the default `button` focus styles.\n//\n// Credit: https://github.com/suitcss/base/\nbutton:focus {\n outline: 1px dotted;\n outline: 5px auto -webkit-focus-ring-color;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0; // Remove the margin in Firefox and Safari\n font-family: inherit;\n @include font-size(inherit);\n line-height: inherit;\n}\n\nbutton,\ninput {\n overflow: visible; // Show the overflow in Edge\n}\n\nbutton,\nselect {\n text-transform: none; // Remove the inheritance of text transform in Firefox\n}\n\n// Set the cursor for non-`